[automerger skipped] RESTRICT AUTOMERGE Fix BAL via notification.publicVersion am: daed0a712a -s ours

am skip reason: Merged-In I8f25d7a5e47890a0496af023149717e1df482f98 with SHA-1 e79cbff7f8 is already in history

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/24449690

Change-Id: I22735d277d738e3e664ec9f73ae189c999c433b4
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/apct-tests/perftests/surfaceflinger/AndroidTest.xml b/apct-tests/perftests/surfaceflinger/AndroidTest.xml
index 11d1106..58cf58b 100644
--- a/apct-tests/perftests/surfaceflinger/AndroidTest.xml
+++ b/apct-tests/perftests/surfaceflinger/AndroidTest.xml
@@ -58,8 +58,8 @@
         <option name="instrumentation-arg" key="report" value="true" />
         <option name="instrumentation-arg" key="arguments" value="-g" />
         <option name="instrumentation-arg" key="events_to_record" value="instructions,cpu-cycles,raw-l3d-cache-refill,sched:sched_waking" />
-        <option name="instrumentation-arg" key="processes_to_record" value="surfaceflinger" />
-        <option name="instrumentation-arg" key="symbols_to_report" value="&quot;commit;android::SurfaceFlinger::commit(;composite;android::SurfaceFlinger::composite(&quot;" />
+        <option name="instrumentation-arg" key="processes_to_record" value="surfaceflinger,android.perftests.surfaceflinger" />
+        <option name="instrumentation-arg" key="symbols_to_report" value="&quot;commit;android::SurfaceFlinger::commit(;composite;android::SurfaceFlinger::composite(;outbound;android::SurfaceComposerClient::Transaction::apply(;inbound;android::BnTransactionCompletedListener::onTransact(&quot;"/>
 
         <!-- should match profiling-iterations -->
         <option name="instrumentation-arg" key="test_iterations" value="525" />
diff --git a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java
index f92c297..dca818e 100644
--- a/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java
+++ b/apct-tests/perftests/surfaceflinger/src/android/surfaceflinger/SurfaceFlingerPerfTest.java
@@ -17,10 +17,14 @@
 package android.surfaceflinger;
 
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Color;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
 
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.filters.LargeTest;
@@ -194,4 +198,16 @@
             mTransaction.apply(true);
         }
     }
+
+    @Test
+    public void bufferQueue() throws Exception {
+        SurfaceView testSV = mActivity.mTestSurfaceView;
+        SurfaceHolder holder = testSV.getHolder();
+        holder.getSurface();
+        for (int i = 0; i < sProfilingIterations; i++) {
+            Canvas canvas = holder.lockCanvas();
+            holder.unlockCanvasAndPost(canvas);
+            mTransaction.apply(true);
+        }
+    }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 13903ac..f429966 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -56,6 +56,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.expresslog.Counter;
 import com.android.server.LocalServices;
 import com.android.server.job.GrantedUriPermissions;
 import com.android.server.job.JobSchedulerInternal;
@@ -161,6 +162,9 @@
     /** If the job is going to be passed an unmetered network. */
     private boolean mHasAccessToUnmetered;
 
+    /** If the effective bucket has been downgraded once due to being buggy. */
+    private boolean mIsDowngradedDueToBuggyApp;
+
     /**
      * The additional set of dynamic constraints that must be met if this is an expedited job that
      * had a long enough run while the device was Dozing or in battery saver.
@@ -1173,18 +1177,32 @@
             // like other ACTIVE apps.
             return ACTIVE_INDEX;
         }
+
+        final int bucketWithMediaExemption;
+        if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
+                && mHasMediaBackupExemption) {
+            // Treat it as if it's at most WORKING_INDEX (lower index grants higher quota) since
+            // media backup jobs are important to the user, and the source package may not have
+            // been used directly in a while.
+            bucketWithMediaExemption = Math.min(WORKING_INDEX, actualBucket);
+        } else {
+            bucketWithMediaExemption = actualBucket;
+        }
+
         // If the app is considered buggy, but hasn't yet been put in the RESTRICTED bucket
         // (potentially because it's used frequently by the user), limit its effective bucket
         // so that it doesn't get to run as much as a normal ACTIVE app.
-        final int highestBucket = isBuggy ? WORKING_INDEX : ACTIVE_INDEX;
-        if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
-                && mHasMediaBackupExemption) {
-            // Treat it as if it's at least WORKING_INDEX since media backup jobs are important
-            // to the user, and the
-            // source package may not have been used directly in a while.
-            return Math.max(highestBucket, Math.min(WORKING_INDEX, actualBucket));
+        if (isBuggy && bucketWithMediaExemption < WORKING_INDEX) {
+            if (!mIsDowngradedDueToBuggyApp) {
+                // Safety check to avoid logging multiple times for the same job.
+                Counter.logIncrementWithUid(
+                        "job_scheduler.value_job_quota_reduced_due_to_buggy_uid",
+                        getTimeoutBlameUid());
+                mIsDowngradedDueToBuggyApp = true;
+            }
+            return WORKING_INDEX;
         }
-        return Math.max(highestBucket, actualBucket);
+        return bucketWithMediaExemption;
     }
 
     /** Returns the real standby bucket of the job. */
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
index 47e1dad..4d41dcd 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -153,6 +153,38 @@
 }
 ```
 
+4. `sync`
+
+A command used to get a response once the command is processed. When several `inject` and `delay`
+commands are used in a row, the `sync` command can be used to track the progress of the command
+queue.
+
+|    Field    |  Type   | Description                                  |
+|:-----------:|:-------:|:---------------------------------------------|
+|    `id`     | integer | Device ID                                    |
+|  `command`  | string  | Must be set to "sync"                        |
+| `syncToken` | string  | The token used to identify this sync command |
+
+Example:
+
+```json5
+{
+  "id": 1,
+  "command": "syncToken",
+  "syncToken": "finished_injecting_events"
+}
+```
+
+This command will result in the following response when it is processed:
+
+```json5
+{
+  "id": 1,
+  "result": "sync",
+  "syncToken": "finished_injecting_events"
+}
+```
+
 ### Notes
 1. As soon as EOF is reached (either in interactive mode, or in file mode),
 the device that was created will be unregistered. There is no
diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java
index 732b33d..2f362d7 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Device.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Device.java
@@ -45,6 +45,7 @@
     private static final int MSG_OPEN_UINPUT_DEVICE = 1;
     private static final int MSG_CLOSE_UINPUT_DEVICE = 2;
     private static final int MSG_INJECT_EVENT = 3;
+    private static final int MSG_SYNC_EVENT = 4;
 
     private final int mId;
     private final HandlerThread mThread;
@@ -119,6 +120,16 @@
     }
 
     /**
+     * Synchronize the uinput command queue by writing a sync response with the provided syncToken
+     * to the output stream when this event is processed.
+     *
+     * @param syncToken  The token for this sync command.
+     */
+    public void syncEvent(String syncToken) {
+        mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), mTimeToSend);
+    }
+
+    /**
      * Close an uinput device.
      *
      */
@@ -171,6 +182,9 @@
                         mCond.notify();
                     }
                     break;
+                case MSG_SYNC_EVENT:
+                    handleSyncEvent((String) msg.obj);
+                    break;
                 default:
                     throw new IllegalArgumentException("Unknown device message");
             }
@@ -184,6 +198,18 @@
             getLooper().myQueue().removeSyncBarrier(mBarrierToken);
             mBarrierToken = 0;
         }
+
+        private void handleSyncEvent(String syncToken) {
+            final JSONObject json = new JSONObject();
+            try {
+                json.put("reason", "sync");
+                json.put("id", mId);
+                json.put("syncToken", syncToken);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not create JSON object ", e);
+            }
+            writeOutputObject(json);
+        }
     }
 
     private class DeviceCallback {
@@ -211,7 +237,7 @@
         }
 
         public void onDeviceVibrating(int value) {
-            JSONObject json = new JSONObject();
+            final JSONObject json = new JSONObject();
             try {
                 json.put("reason", "vibrating");
                 json.put("id", mId);
@@ -219,12 +245,7 @@
             } catch (JSONException e) {
                 throw new RuntimeException("Could not create JSON object ", e);
             }
-            try {
-                mOutputStream.write(json.toString().getBytes());
-                mOutputStream.flush();
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
+            writeOutputObject(json);
         }
 
         public void onDeviceError() {
@@ -234,4 +255,13 @@
             msg.sendToTarget();
         }
     }
+
+    private void writeOutputObject(JSONObject json) {
+        try {
+            mOutputStream.write(json.toString().getBytes());
+            mOutputStream.flush();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java
index 4b090f5..91b4a74 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Event.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Event.java
@@ -25,6 +25,7 @@
 import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Objects;
 import java.util.stream.IntStream;
 
 import src.com.android.commands.uinput.InputAbsInfo;
@@ -36,11 +37,21 @@
 public class Event {
     private static final String TAG = "UinputEvent";
 
-    public static final String COMMAND_REGISTER = "register";
-    public static final String COMMAND_DELAY = "delay";
-    public static final String COMMAND_INJECT = "inject";
     private static final int ABS_CNT = 64;
 
+    enum Command {
+        REGISTER("register"),
+        DELAY("delay"),
+        INJECT("inject"),
+        SYNC("sync");
+
+        final String mCommandName;
+
+        Command(String command) {
+            mCommandName = command;
+        }
+    }
+
     // These constants come from "include/uapi/linux/input.h" in the kernel
     enum Bus {
         USB(0x03), BLUETOOTH(0x05);
@@ -56,7 +67,7 @@
     }
 
     private int mId;
-    private String mCommand;
+    private Command mCommand;
     private String mName;
     private int mVid;
     private int mPid;
@@ -67,12 +78,13 @@
     private int mFfEffectsMax = 0;
     private String mInputport;
     private SparseArray<InputAbsInfo> mAbsInfo;
+    private String mSyncToken;
 
     public int getId() {
         return mId;
     }
 
-    public String getCommand() {
+    public Command getCommand() {
         return mCommand;
     }
 
@@ -116,6 +128,10 @@
         return mInputport;
     }
 
+    public String getSyncToken() {
+        return mSyncToken;
+    }
+
     /**
      * Convert an event to String.
      */
@@ -146,7 +162,14 @@
         }
 
         private void setCommand(String command) {
-            mEvent.mCommand = command;
+            Objects.requireNonNull(command, "Command must not be null");
+            for (Command cmd : Command.values()) {
+                if (cmd.mCommandName.equals(command)) {
+                    mEvent.mCommand = cmd;
+                    return;
+                }
+            }
+            throw new IllegalStateException("Unrecognized command: " + command);
         }
 
         public void setName(String name) {
@@ -189,27 +212,38 @@
             mEvent.mInputport = port;
         }
 
+        public void setSyncToken(String syncToken) {
+            mEvent.mSyncToken = Objects.requireNonNull(syncToken, "Sync token must not be null");
+        }
+
         public Event build() {
             if (mEvent.mId == -1) {
                 throw new IllegalStateException("No event id");
             } else if (mEvent.mCommand == null) {
                 throw new IllegalStateException("Event does not contain a command");
             }
-            if (COMMAND_REGISTER.equals(mEvent.mCommand)) {
-                if (mEvent.mConfiguration == null) {
-                    throw new IllegalStateException(
-                            "Device registration is missing configuration");
+            switch (mEvent.mCommand) {
+                case REGISTER -> {
+                    if (mEvent.mConfiguration == null) {
+                        throw new IllegalStateException(
+                                "Device registration is missing configuration");
+                    }
                 }
-            } else if (COMMAND_DELAY.equals(mEvent.mCommand)) {
-                if (mEvent.mDuration <= 0) {
-                    throw new IllegalStateException("Delay has missing or invalid duration");
+                case DELAY -> {
+                    if (mEvent.mDuration <= 0) {
+                        throw new IllegalStateException("Delay has missing or invalid duration");
+                    }
                 }
-            } else if (COMMAND_INJECT.equals(mEvent.mCommand)) {
-                if (mEvent.mInjections  == null) {
-                    throw new IllegalStateException("Inject command is missing injection data");
+                case INJECT -> {
+                    if (mEvent.mInjections == null) {
+                        throw new IllegalStateException("Inject command is missing injection data");
+                    }
                 }
-            } else {
-                throw new IllegalStateException("Unknown command " + mEvent.mCommand);
+                case SYNC -> {
+                    if (mEvent.mSyncToken == null) {
+                        throw new IllegalStateException("Sync command is missing sync token");
+                    }
+                }
             }
             return mEvent;
         }
@@ -276,6 +310,9 @@
                             case "port":
                                 eb.setInputport(mReader.nextString());
                                 break;
+                            case "syncToken":
+                                eb.setSyncToken(mReader.nextString());
+                                break;
                             default:
                                 mReader.skipValue();
                         }
diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
index 740578e..47b7a354 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
@@ -25,6 +25,7 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
+import java.util.Objects;
 
 /**
  * Uinput class encapsulates execution of "uinput" command. It parses the provided input stream
@@ -96,28 +97,27 @@
 
     private void process(Event e) {
         final int index = mDevices.indexOfKey(e.getId());
-        if (index >= 0) {
-            Device d = mDevices.valueAt(index);
-            if (Event.COMMAND_DELAY.equals(e.getCommand())) {
-                d.addDelay(e.getDuration());
-            } else if (Event.COMMAND_INJECT.equals(e.getCommand())) {
-                d.injectEvent(e.getInjections());
-            } else {
-                if (Event.COMMAND_REGISTER.equals(e.getCommand())) {
-                    error("Device id=" + e.getId() + " is already registered. Ignoring event.");
-                } else {
-                    error("Unknown command \"" + e.getCommand() + "\". Ignoring event.");
-                }
+        if (index < 0) {
+            if (e.getCommand() != Event.Command.REGISTER) {
+                Log.e(TAG, "Unknown device id specified. Ignoring event.");
+                return;
             }
-        } else if (Event.COMMAND_REGISTER.equals(e.getCommand())) {
             registerDevice(e);
-        } else {
-            Log.e(TAG, "Unknown device id specified. Ignoring event.");
+            return;
+        }
+
+        final Device d = mDevices.valueAt(index);
+        switch (Objects.requireNonNull(e.getCommand())) {
+            case REGISTER ->
+                    error("Device id=" + e.getId() + " is already registered. Ignoring event.");
+            case INJECT -> d.injectEvent(e.getInjections());
+            case DELAY -> d.addDelay(e.getDuration());
+            case SYNC -> d.syncEvent(e.getSyncToken());
         }
     }
 
     private void registerDevice(Event e) {
-        if (!Event.COMMAND_REGISTER.equals(e.getCommand())) {
+        if (!Event.Command.REGISTER.equals(e.getCommand())) {
             throw new IllegalStateException(
                     "Tried to send command \"" + e.getCommand() + "\" to an unregistered device!");
         }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0c9569f..250a1d1 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2958,6 +2958,10 @@
     method @Deprecated public boolean isBound();
   }
 
+  public class NotificationRankingUpdate implements android.os.Parcelable {
+    method public final boolean isFdNotNullAndClosed();
+  }
+
 }
 
 package android.service.quickaccesswallet {
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 02b14ad..f8f4cc3 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -510,6 +510,17 @@
     ],
 }
 
+// common protolog sources without classes that rely on Android SDK
+filegroup {
+    name: "protolog-common-no-android-src",
+    srcs: [
+        ":protolog-common-src",
+    ],
+    exclude_srcs: [
+        "com/android/internal/protolog/common/ProtoLog.java",
+    ],
+}
+
 java_library {
     name: "protolog-lib",
     platform_apis: true,
diff --git a/core/java/android/animation/AnimationHandler.java b/core/java/android/animation/AnimationHandler.java
index df8a50a..4fc90ae 100644
--- a/core/java/android/animation/AnimationHandler.java
+++ b/core/java/android/animation/AnimationHandler.java
@@ -16,6 +16,7 @@
 
 package android.animation;
 
+import android.annotation.Nullable;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.ArrayMap;
@@ -91,9 +92,13 @@
     };
 
     public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();
+    private static AnimationHandler sTestHandler = null;
     private boolean mListDirty = false;
 
     public static AnimationHandler getInstance() {
+        if (sTestHandler != null) {
+            return sTestHandler;
+        }
         if (sAnimatorHandler.get() == null) {
             sAnimatorHandler.set(new AnimationHandler());
         }
@@ -101,6 +106,17 @@
     }
 
     /**
+     * Sets an instance that will be returned by {@link #getInstance()} on every thread.
+     * @return  the previously active test handler, if any.
+     * @hide
+     */
+    public static @Nullable AnimationHandler setTestHandler(@Nullable AnimationHandler handler) {
+        AnimationHandler oldHandler = sTestHandler;
+        sTestHandler = handler;
+        return oldHandler;
+    }
+
+    /**
      * System property that controls the behavior of pausing infinite animators when an app
      * is moved to the background.
      *
@@ -369,7 +385,10 @@
      * Return the number of callbacks that have registered for frame callbacks.
      */
     public static int getAnimationCount() {
-        AnimationHandler handler = sAnimatorHandler.get();
+        AnimationHandler handler = sTestHandler;
+        if (handler == null) {
+            handler = sAnimatorHandler.get();
+        }
         if (handler == null) {
             return 0;
         }
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 70c3d7a..f33d299 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -1346,8 +1346,26 @@
         }
         // Set the child animators to the right end:
         if (mShouldResetValuesAtStart) {
-            initChildren();
-            skipToEndValue(!mReversing);
+            if (isInitialized()) {
+                skipToEndValue(!mReversing);
+            } else if (mReversing) {
+                // Reversing but haven't initialized all the children yet.
+                initChildren();
+                skipToEndValue(!mReversing);
+            } else {
+                // If not all children are initialized and play direction is forward
+                for (int i = mEvents.size() - 1; i >= 0; i--) {
+                    if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+                        Animator anim = mEvents.get(i).mNode.mAnimation;
+                        // Only reset the animations that have been initialized to start value,
+                        // so that if they are defined without a start value, they will get the
+                        // values set at the right time (i.e. the next animation run)
+                        if (anim.isInitialized()) {
+                            anim.skipToEndValue(true);
+                        }
+                    }
+                }
+            }
         }
 
         if (mReversing || mStartDelay == 0 || mSeekState.isActive()) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 8021ce0..6e0fc4f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1084,6 +1084,16 @@
         }
 
         /**
+         * Update the forced status bar appearance.
+         * @hide
+         */
+        @Override
+        public void updateStatusBarAppearance(int appearance) {
+            mTaskDescription.setStatusBarAppearance(appearance);
+            setTaskDescription(mTaskDescription);
+        }
+
+        /**
          * Update the forced navigation bar color.
          * @hide
          */
@@ -3882,7 +3892,9 @@
      * it will set up the dispatch to call {@link #onKeyUp} where the action
      * will be performed; for earlier applications, it will perform the
      * action immediately in on-down, as those versions of the platform
-     * behaved.
+     * behaved. This implementation will also take care of {@link KeyEvent#KEYCODE_ESCAPE}
+     * by finishing the activity if it would be closed by touching outside
+     * of it.
      *
      * <p>Other additional default key handling may be performed
      * if configured with {@link #setDefaultKeyMode}.
@@ -3904,6 +3916,11 @@
             return true;
         }
 
+        if (keyCode == KeyEvent.KEYCODE_ESCAPE && mWindow.shouldCloseOnTouchOutside()) {
+            event.startTracking();
+            return true;
+        }
+
         if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
             return false;
         } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
@@ -3999,6 +4016,15 @@
                 return true;
             }
         }
+
+        if (keyCode == KeyEvent.KEYCODE_ESCAPE
+                && mWindow.shouldCloseOnTouchOutside()
+                && event.isTracking()
+                && !event.isCanceled()) {
+            finish();
+            return true;
+        }
+
         return false;
     }
 
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b5ee895..ff0f437 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -81,6 +81,7 @@
 import android.util.DisplayMetrics;
 import android.util.Singleton;
 import android.util.Size;
+import android.view.WindowInsetsController.Appearance;
 import android.window.TaskSnapshot;
 
 import com.android.internal.app.LocalePicker;
@@ -1553,6 +1554,8 @@
         private int mColorBackgroundFloating;
         private int mStatusBarColor;
         private int mNavigationBarColor;
+        @Appearance
+        private int mStatusBarAppearance;
         private boolean mEnsureStatusBarContrastWhenTransparent;
         private boolean mEnsureNavigationBarContrastWhenTransparent;
         private int mResizeMode;
@@ -1653,8 +1656,8 @@
                 final Icon icon = mIconRes == Resources.ID_NULL ? null :
                         Icon.createWithResource(ActivityThread.currentPackageName(), mIconRes);
                 return new TaskDescription(mLabel, icon, mPrimaryColor, mBackgroundColor,
-                        mStatusBarColor, mNavigationBarColor, false, false, RESIZE_MODE_RESIZEABLE,
-                        -1, -1, 0);
+                        mStatusBarColor, mNavigationBarColor, 0, false, false,
+                        RESIZE_MODE_RESIZEABLE, -1, -1, 0);
             }
         }
 
@@ -1672,7 +1675,7 @@
         @Deprecated
         public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
             this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
-                    colorPrimary, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+                    colorPrimary, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
             if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
                 throw new RuntimeException("A TaskDescription's primary color should be opaque");
             }
@@ -1690,7 +1693,7 @@
         @Deprecated
         public TaskDescription(String label, @DrawableRes int iconRes) {
             this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
-                    0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+                    0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /**
@@ -1702,7 +1705,7 @@
          */
         @Deprecated
         public TaskDescription(String label) {
-            this(label, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+            this(label, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /**
@@ -1712,7 +1715,7 @@
          */
         @Deprecated
         public TaskDescription() {
-            this(null, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+            this(null, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /**
@@ -1728,7 +1731,7 @@
         @Deprecated
         public TaskDescription(String label, Bitmap icon, int colorPrimary) {
             this(label, icon != null ? Icon.createWithBitmap(icon) : null, colorPrimary, 0, 0, 0,
-                    false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+                    0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
             if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
                 throw new RuntimeException("A TaskDescription's primary color should be opaque");
             }
@@ -1744,14 +1747,15 @@
          */
         @Deprecated
         public TaskDescription(String label, Bitmap icon) {
-            this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, false, false,
-                    RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+            this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, 0, false,
+                    false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /** @hide */
         public TaskDescription(@Nullable String label, @Nullable Icon icon,
                 int colorPrimary, int colorBackground,
                 int statusBarColor, int navigationBarColor,
+                @Appearance int statusBarAppearance,
                 boolean ensureStatusBarContrastWhenTransparent,
                 boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth,
                 int minHeight, int colorBackgroundFloating) {
@@ -1761,6 +1765,7 @@
             mColorBackground = colorBackground;
             mStatusBarColor = statusBarColor;
             mNavigationBarColor = navigationBarColor;
+            mStatusBarAppearance = statusBarAppearance;
             mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
                     ensureNavigationBarContrastWhenTransparent;
@@ -1789,6 +1794,7 @@
             mColorBackground = other.mColorBackground;
             mStatusBarColor = other.mStatusBarColor;
             mNavigationBarColor = other.mNavigationBarColor;
+            mStatusBarAppearance = other.mStatusBarAppearance;
             mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
                     other.mEnsureNavigationBarContrastWhenTransparent;
@@ -1818,6 +1824,9 @@
             if (other.mNavigationBarColor != 0) {
                 mNavigationBarColor = other.mNavigationBarColor;
             }
+            if (other.mStatusBarAppearance != 0) {
+                mStatusBarAppearance = other.mStatusBarAppearance;
+            }
 
             mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
             mEnsureNavigationBarContrastWhenTransparent =
@@ -2089,6 +2098,14 @@
         /**
          * @hide
          */
+        @Appearance
+        public int getStatusBarAppearance() {
+            return mStatusBarAppearance;
+        }
+
+        /**
+         * @hide
+         */
         public void setEnsureStatusBarContrastWhenTransparent(
                 boolean ensureStatusBarContrastWhenTransparent) {
             mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
@@ -2097,6 +2114,13 @@
         /**
          * @hide
          */
+        public void setStatusBarAppearance(@Appearance int statusBarAppearance) {
+            mStatusBarAppearance = statusBarAppearance;
+        }
+
+        /**
+         * @hide
+         */
         public boolean getEnsureNavigationBarContrastWhenTransparent() {
             return mEnsureNavigationBarContrastWhenTransparent;
         }
@@ -2218,6 +2242,7 @@
             dest.writeInt(mColorBackground);
             dest.writeInt(mStatusBarColor);
             dest.writeInt(mNavigationBarColor);
+            dest.writeInt(mStatusBarAppearance);
             dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent);
             dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent);
             dest.writeInt(mResizeMode);
@@ -2241,6 +2266,7 @@
             mColorBackground = source.readInt();
             mStatusBarColor = source.readInt();
             mNavigationBarColor = source.readInt();
+            mStatusBarAppearance = source.readInt();
             mEnsureStatusBarContrastWhenTransparent = source.readBoolean();
             mEnsureNavigationBarContrastWhenTransparent = source.readBoolean();
             mResizeMode = source.readInt();
@@ -2289,6 +2315,7 @@
                     && mColorBackground == other.mColorBackground
                     && mStatusBarColor == other.mStatusBarColor
                     && mNavigationBarColor == other.mNavigationBarColor
+                    && mStatusBarAppearance == other.mStatusBarAppearance
                     && mEnsureStatusBarContrastWhenTransparent
                             == other.mEnsureStatusBarContrastWhenTransparent
                     && mEnsureNavigationBarContrastWhenTransparent
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 41c58ef..1571fdd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -34,7 +34,6 @@
 import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
 import static android.window.ConfigurationHelper.isDifferentDisplay;
 import static android.window.ConfigurationHelper.shouldUpdateResources;
-import static android.window.ConfigurationHelper.shouldUpdateWindowMetricsBounds;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
@@ -6118,11 +6117,6 @@
     public static boolean shouldReportChange(@Nullable Configuration currentConfig,
             @NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets sizeBuckets,
             int handledConfigChanges, boolean alwaysReportChange) {
-        // Always report changes in window configuration bounds
-        if (shouldUpdateWindowMetricsBounds(currentConfig, newConfig)) {
-            return true;
-        }
-
         final int publicDiff = currentConfig.diffPublicOnly(newConfig);
         // Don't report the change if there's no public diff between current and new config.
         if (publicDiff == 0) {
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 411d157..d0d76a4 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -454,12 +454,11 @@
      */
     protected void onStart() {
         if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
-        if (mContext != null
+        if (allowsRegisterDefaultOnBackInvokedCallback() && mContext != null
                 && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
             // Add onBackPressed as default back behavior.
             mDefaultBackCallback = this::onBackPressed;
             getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
-            mDefaultBackCallback = null;
         }
     }
 
@@ -470,9 +469,18 @@
         if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
         if (mDefaultBackCallback != null) {
             getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback);
+            mDefaultBackCallback = null;
         }
     }
 
+    /**
+     * Whether this dialog allows to register the default onBackInvokedCallback.
+     * @hide
+     */
+    protected boolean allowsRegisterDefaultOnBackInvokedCallback() {
+        return true;
+    }
+
     private static final String DIALOG_SHOWING_TAG = "android:dialogShowing";
     private static final String DIALOG_HIERARCHY_TAG = "android:dialogHierarchy";
 
@@ -694,12 +702,23 @@
      */
     @Override
     public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
-        if ((keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE)
-                && event.isTracking()
-                && !event.isCanceled()
-                && !WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
-            onBackPressed();
-            return true;
+        if (event.isTracking() && !event.isCanceled()) {
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_BACK:
+                    if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)
+                            || !allowsRegisterDefaultOnBackInvokedCallback()) {
+                        onBackPressed();
+                        return true;
+                    }
+                    break;
+                case KeyEvent.KEYCODE_ESCAPE:
+                    if (mCancelable) {
+                        cancel();
+                    } else {
+                        dismiss();
+                    }
+                    return true;
+            }
         }
         return false;
     }
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 930750e..46c677d 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -426,7 +426,7 @@
                 mSharedElementNotified = true;
                 delayCancel();
 
-                if (mExitCallbacks.isReturnTransitionAllowed()) {
+                if (mExitCallbacks != null && mExitCallbacks.isReturnTransitionAllowed()) {
                     mResultReceiver.send(MSG_ALLOW_RETURN_TRANSITION, null);
                 }
 
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 4e2b6fa..d189bab 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -24,7 +24,6 @@
 import android.app.IApplicationThread;
 import android.app.IActivityClientController;
 import android.app.IActivityController;
-import android.app.IAppTask;
 import android.app.IAssistDataReceiver;
 import android.app.IInstrumentationWatcher;
 import android.app.IProcessObserver;
@@ -107,14 +106,6 @@
             in ProfilerInfo profilerInfo, in Bundle options, int userId);
     boolean startNextMatchingActivity(in IBinder callingActivity,
             in Intent intent, in Bundle options);
-
-    /**
-    *  The DreamActivity has to be started in a special way that does not involve the PackageParser.
-    *  The DreamActivity is a framework component inserted in the dream application process. Hence,
-    *  it is not declared in the application's manifest and cannot be parsed. startDreamActivity
-    *  creates the activity and starts it without reaching out to the PackageParser.
-    */
-    boolean startDreamActivity(in Intent intent);
     int startActivityIntentSender(in IApplicationThread caller,
             in IIntentSender target, in IBinder whitelistToken, in Intent fillInIntent,
             in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode,
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index 3d6ab6f..9a818e4 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -20,6 +20,7 @@
 import android.app.GameModeInfo;
 import android.app.GameState;
 import android.app.IGameModeListener;
+import android.app.IGameStateListener;
 
 /**
  * @hide
@@ -49,4 +50,6 @@
     void addGameModeListener(IGameModeListener gameModeListener);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
     void removeGameModeListener(IGameModeListener gameModeListener);
+    void addGameStateListener(IGameStateListener gameStateListener);
+    void removeGameStateListener(IGameStateListener gameStateListener);
 }
diff --git a/core/java/android/app/IGameStateListener.aidl b/core/java/android/app/IGameStateListener.aidl
new file mode 100644
index 0000000..34cff48
--- /dev/null
+++ b/core/java/android/app/IGameStateListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.app.GameState;
+
+/** @hide */
+interface IGameStateListener {
+    /**
+     * Called when the state of the game has changed.
+     */
+    oneway void onGameStateChanged(String packageName, in GameState state, int userId);
+}
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index fbb0748..63cae63 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -24,6 +24,8 @@
 import android.view.WindowContentFrameStats;
 import android.view.WindowAnimationFrameStats;
 import android.os.ParcelFileDescriptor;
+import android.window.ScreenCapture.ScreenCaptureListener;
+import android.window.ScreenCapture.LayerCaptureArgs;
 
 import java.util.List;
 
@@ -43,8 +45,8 @@
     void injectInputEventToInputFilter(in InputEvent event);
     void syncInputTransactions(boolean waitForAnimations);
     boolean setRotation(int rotation);
-    Bitmap takeScreenshot(in Rect crop);
-    Bitmap takeSurfaceControlScreenshot(in SurfaceControl surfaceControl);
+    boolean takeScreenshot(in Rect crop, in ScreenCaptureListener listener);
+    boolean takeSurfaceControlScreenshot(in SurfaceControl surfaceControl, in ScreenCaptureListener listener);
     boolean clearWindowContentFrameStats(int windowId);
     WindowContentFrameStats getWindowContentFrameStats(int windowId);
     void clearWindowAnimationFrameStats();
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 09450f5..e7a2b61e 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -117,6 +117,26 @@
             "android.app.action.CONFIRM_REMOTE_DEVICE_CREDENTIAL";
 
     /**
+     * Intent used to prompt user for device credential for entering repair
+     * mode. If the credential is verified successfully, then the information
+     * needed to verify the credential again will be written to a location that
+     * is available to repair mode. This makes it possible for repair mode to
+     * require that the same credential be provided to exit repair mode.
+     * @hide
+     */
+    public static final String ACTION_PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL =
+            "android.app.action.PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL";
+
+    /**
+     * Intent used to prompt user for device credential that is written by
+     * {@link #ACTION_PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL} for exiting
+     * repair mode.
+     * @hide
+     */
+    public static final String ACTION_CONFIRM_REPAIR_MODE_DEVICE_CREDENTIAL =
+            "android.app.action.CONFIRM_REPAIR_MODE_DEVICE_CREDENTIAL";
+
+    /**
      * A CharSequence dialog title to show to the user when used with a
      * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}.
      * @hide
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 54c8cb0..44068dd 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2837,12 +2837,14 @@
     }
 
     /**
-     * Note all {@link Uri} that are referenced internally, with the expectation
-     * that Uri permission grants will need to be issued to ensure the recipient
-     * of this object is able to render its contents.
-     *
-     * @hide
-     */
+    * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
+    * grants will need to be issued to ensure the recipient of this object is able to render its
+    * contents.
+    * See b/281044385 for more context and examples about what happens when this isn't done
+    * correctly.
+    *
+    * @hide
+    */
     public void visitUris(@NonNull Consumer<Uri> visitor) {
         if (publicVersion != null) {
             publicVersion.visitUris(visitor);
@@ -2886,13 +2888,13 @@
             ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, android.app.Person.class);
             if (people != null && !people.isEmpty()) {
                 for (Person p : people) {
-                    visitor.accept(p.getIconUri());
+                    p.visitUris(visitor);
                 }
             }
 
             final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class);
             if (person != null) {
-                visitor.accept(person.getIconUri());
+                person.visitUris(visitor);
             }
 
             final RemoteInputHistoryItem[] history = extras.getParcelableArray(
@@ -2914,12 +2916,7 @@
             if (!ArrayUtils.isEmpty(messages)) {
                 for (MessagingStyle.Message message : MessagingStyle.Message
                         .getMessagesFromBundleArray(messages)) {
-                    visitor.accept(message.getDataUri());
-
-                    Person senderPerson = message.getSenderPerson();
-                    if (senderPerson != null) {
-                        visitor.accept(senderPerson.getIconUri());
-                    }
+                    message.visitUris(visitor);
                 }
             }
 
@@ -2928,12 +2925,7 @@
             if (!ArrayUtils.isEmpty(historic)) {
                 for (MessagingStyle.Message message : MessagingStyle.Message
                         .getMessagesFromBundleArray(historic)) {
-                    visitor.accept(message.getDataUri());
-
-                    Person senderPerson = message.getSenderPerson();
-                    if (senderPerson != null) {
-                        visitor.accept(senderPerson.getIconUri());
-                    }
+                    message.visitUris(visitor);
                 }
             }
 
@@ -2943,7 +2935,7 @@
         if (isStyle(CallStyle.class) & extras != null) {
             Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class);
             if (callPerson != null) {
-                visitor.accept(callPerson.getIconUri());
+                callPerson.visitUris(visitor);
             }
             visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON, Icon.class));
         }
@@ -3055,10 +3047,9 @@
         // cannot look into the extras as there may be parcelables there that
         // the platform does not know how to handle. To go around that we have
         // an explicit list of the pending intents in the extras bundle.
-        final boolean collectPendingIntents = (allPendingIntents == null);
-        if (collectPendingIntents) {
-            PendingIntent.setOnMarshaledListener(
-                    (PendingIntent intent, Parcel out, int outFlags) -> {
+        PendingIntent.OnMarshaledListener addedListener = null;
+        if (allPendingIntents == null) {
+            addedListener = (PendingIntent intent, Parcel out, int outFlags) -> {
                 if (parcel == out) {
                     synchronized (this) {
                         if (allPendingIntents == null) {
@@ -3067,7 +3058,8 @@
                         allPendingIntents.add(intent);
                     }
                 }
-            });
+            };
+            PendingIntent.addOnMarshaledListener(addedListener);
         }
         try {
             // IMPORTANT: Add marshaling code in writeToParcelImpl as we
@@ -3078,8 +3070,8 @@
                 parcel.writeArraySet(allPendingIntents);
             }
         } finally {
-            if (collectPendingIntents) {
-                PendingIntent.setOnMarshaledListener(null);
+            if (addedListener != null) {
+                PendingIntent.removeOnMarshaledListener(addedListener);
             }
         }
     }
@@ -8845,6 +8837,18 @@
             }
 
             /**
+             * See {@link Notification#visitUris(Consumer)}.
+             *
+             * @hide
+             */
+            public void visitUris(@NonNull Consumer<Uri> visitor) {
+                visitor.accept(getDataUri());
+                if (mSender != null) {
+                    mSender.visitUris(visitor);
+                }
+            }
+
+            /**
              * Returns a list of messages read from the given bundle list, e.g.
              * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}.
              */
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index d8cedb8..7ee1332 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -26,12 +26,14 @@
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
 import android.media.AudioAttributes;
+import android.media.RingtoneManager;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.Preconditions;
@@ -54,6 +56,7 @@
  * A representation of settings that apply to a collection of similarly themed notifications.
  */
 public final class NotificationChannel implements Parcelable {
+    private static final String TAG = "NotificationChannel";
 
     /**
      * The id of the default channel for an app. This id is reserved by the system. All
@@ -959,8 +962,11 @@
         setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
 
         Uri sound = safeUri(parser, ATT_SOUND);
-        setSound(forRestore ? restoreSoundUri(context, sound, pkgInstalled) : sound,
-                safeAudioAttributes(parser));
+
+        final AudioAttributes audioAttributes = safeAudioAttributes(parser);
+        final int usage = audioAttributes.getUsage();
+        setSound(forRestore ? restoreSoundUri(context, sound, pkgInstalled, usage) : sound,
+                audioAttributes);
 
         enableLights(safeBool(parser, ATT_LIGHTS, false));
         setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
@@ -1010,18 +1016,34 @@
         if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
             return uri;
         }
-
         return contentResolver.canonicalize(uri);
     }
 
     @Nullable
-    private Uri getUncanonicalizedSoundUri(ContentResolver contentResolver, @NonNull Uri uri) {
+    private Uri getUncanonicalizedSoundUri(
+            ContentResolver contentResolver, @NonNull Uri uri, int usage) {
         if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(uri)
                 || ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())
                 || ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
             return uri;
         }
-        return contentResolver.uncanonicalize(uri);
+        int ringtoneType = 0;
+
+        // Consistent with UI(SoundPreferenceController.handlePreferenceTreeClick).
+        if (AudioAttributes.USAGE_ALARM == usage) {
+            ringtoneType = RingtoneManager.TYPE_ALARM;
+        } else if (AudioAttributes.USAGE_NOTIFICATION_RINGTONE == usage) {
+            ringtoneType = RingtoneManager.TYPE_RINGTONE;
+        } else {
+            ringtoneType = RingtoneManager.TYPE_NOTIFICATION;
+        }
+        try {
+            return RingtoneManager.getRingtoneUriForRestore(
+                    contentResolver, uri.toString(), ringtoneType);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to uncanonicalized sound uri for " + uri + " " + e);
+            return Settings.System.DEFAULT_NOTIFICATION_URI;
+        }
     }
 
     /**
@@ -1033,7 +1055,8 @@
      * @hide
      */
     @Nullable
-    public Uri restoreSoundUri(Context context, @Nullable Uri uri, boolean pkgInstalled) {
+    public Uri restoreSoundUri(
+            Context context, @Nullable Uri uri, boolean pkgInstalled, int usage) {
         if (uri == null || Uri.EMPTY.equals(uri)) {
             return null;
         }
@@ -1060,7 +1083,7 @@
             }
         }
         mSoundRestored = true;
-        return getUncanonicalizedSoundUri(contentResolver, canonicalizedUri);
+        return getUncanonicalizedSoundUri(contentResolver, canonicalizedUri, usage);
     }
 
     /**
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 705b5ee..c7522b4 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -64,6 +64,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -391,11 +392,12 @@
         void onMarshaled(PendingIntent intent, Parcel parcel, int flags);
     }
 
-    private static final ThreadLocal<OnMarshaledListener> sOnMarshaledListener
-            = new ThreadLocal<>();
+    private static final ThreadLocal<List<OnMarshaledListener>> sOnMarshaledListener =
+            ThreadLocal.withInitial(ArrayList::new);
 
     /**
-     * Registers an listener for pending intents being written to a parcel.
+     * Registers an listener for pending intents being written to a parcel. This replaces any
+     * listeners previously added.
      *
      * @param listener The listener, null to clear.
      *
@@ -403,7 +405,27 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static void setOnMarshaledListener(OnMarshaledListener listener) {
-        sOnMarshaledListener.set(listener);
+        final List<OnMarshaledListener> listeners = sOnMarshaledListener.get();
+        listeners.clear();
+        if (listener != null) {
+            listeners.add(listener);
+        }
+    }
+
+    /**
+     * Adds a listener for pending intents being written to a parcel.
+     * @hide
+     */
+    static void addOnMarshaledListener(OnMarshaledListener listener) {
+        sOnMarshaledListener.get().add(listener);
+    }
+
+    /**
+     * Removes a listener for pending intents being written to a parcel.
+     * @hide
+     */
+    static void removeOnMarshaledListener(OnMarshaledListener listener) {
+        sOnMarshaledListener.get().remove(listener);
     }
 
     private static void checkPendingIntent(int flags, @NonNull Intent intent,
@@ -1451,11 +1473,11 @@
 
     public void writeToParcel(Parcel out, int flags) {
         out.writeStrongBinder(mTarget.asBinder());
-        OnMarshaledListener listener = sOnMarshaledListener.get();
-        if (listener != null) {
-            listener.onMarshaled(this, out, flags);
+        final List<OnMarshaledListener> listeners = sOnMarshaledListener.get();
+        final int numListeners = listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            listeners.get(i).onMarshaled(this, out, flags);
         }
-
     }
 
     public static final @NonNull Creator<PendingIntent> CREATOR = new Creator<PendingIntent>() {
@@ -1483,9 +1505,10 @@
             @NonNull Parcel out) {
         out.writeStrongBinder(sender != null ? sender.mTarget.asBinder() : null);
         if (sender != null) {
-            OnMarshaledListener listener = sOnMarshaledListener.get();
-            if (listener != null) {
-                listener.onMarshaled(sender, out, 0 /* flags */);
+            final List<OnMarshaledListener> listeners = sOnMarshaledListener.get();
+            final int numListeners = listeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                listeners.get(i).onMarshaled(sender, out, 0 /* flags */);
             }
         }
     }
diff --git a/core/java/android/app/Person.java b/core/java/android/app/Person.java
index 97a794d..18fc0ce 100644
--- a/core/java/android/app/Person.java
+++ b/core/java/android/app/Person.java
@@ -24,6 +24,7 @@
 import android.os.Parcelable;
 
 import java.util.Objects;
+import java.util.function.Consumer;
 
 /**
  * Provides an immutable reference to an entity that appears repeatedly on different surfaces of the
@@ -177,6 +178,19 @@
         dest.writeBoolean(mIsBot);
     }
 
+    /**
+     * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
+     * grants will need to be issued to ensure the recipient of this object is able to render its
+     * contents.
+     * See b/281044385 for more context and examples about what happens when this isn't done
+     * correctly.
+     *
+     * @hide
+     */
+    public void visitUris(@NonNull Consumer<Uri> visitor) {
+        visitor.accept(getIconUri());
+    }
+
     /** Builder for the immutable {@link Person} class. */
     public static class Builder {
         @Nullable private CharSequence mName;
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 1ebf565..a87187b 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -55,6 +55,11 @@
 import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 
 final class SharedPreferencesImpl implements SharedPreferences {
     private static final String TAG = "SharedPreferencesImpl";
@@ -119,6 +124,10 @@
     private final ExponentiallyBucketedHistogram mSyncTimes = new ExponentiallyBucketedHistogram(16);
     private int mNumSync = 0;
 
+    private static final ThreadPoolExecutor sLoadExecutor = new ThreadPoolExecutor(0, 1, 10L,
+            TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
+            new SharedPreferencesThreadFactory());
+
     @UnsupportedAppUsage
     SharedPreferencesImpl(File file, int mode) {
         mFile = file;
@@ -135,11 +144,10 @@
         synchronized (mLock) {
             mLoaded = false;
         }
-        new Thread("SharedPreferencesImpl-load") {
-            public void run() {
-                loadFromDisk();
-            }
-        }.start();
+
+        sLoadExecutor.execute(() -> {
+            loadFromDisk();
+        });
     }
 
     private void loadFromDisk() {
@@ -874,4 +882,14 @@
         }
         mcr.setDiskWriteResult(false, false);
     }
+
+
+    private static final class SharedPreferencesThreadFactory implements ThreadFactory {
+        @Override
+        public Thread newThread(Runnable runnable) {
+            Thread thread = Executors.defaultThreadFactory().newThread(runnable);
+            thread.setName("SharedPreferences");
+            return thread;
+        }
+    }
 }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 4f5da99..565226d 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -236,8 +236,6 @@
 import android.view.contentcapture.IContentCaptureManager;
 import android.view.displayhash.DisplayHashManager;
 import android.view.inputmethod.InputMethodManager;
-import android.view.selectiontoolbar.ISelectionToolbarManager;
-import android.view.selectiontoolbar.SelectionToolbarManager;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textservice.TextServicesManager;
 import android.view.translation.ITranslationManager;
@@ -378,15 +376,6 @@
                 return new TextClassificationManager(ctx);
             }});
 
-        registerService(Context.SELECTION_TOOLBAR_SERVICE, SelectionToolbarManager.class,
-                new CachedServiceFetcher<SelectionToolbarManager>() {
-                    @Override
-                    public SelectionToolbarManager createService(ContextImpl ctx) {
-                        IBinder b = ServiceManager.getService(Context.SELECTION_TOOLBAR_SERVICE);
-                        return new SelectionToolbarManager(ctx.getOuterContext(),
-                                ISelectionToolbarManager.Stub.asInterface(b));
-                    }});
-
         registerService(Context.FONT_SERVICE, FontManager.class,
                 new CachedServiceFetcher<FontManager>() {
             @Override
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index c4e4995..634089b 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -242,6 +242,25 @@
     public boolean isLetterboxDoubleTapEnabled;
 
     /**
+     * Whether the user aspect ratio settings button is enabled
+     * @hide
+     */
+    public boolean topActivityEligibleForUserAspectRatioButton;
+
+    /**
+     * Whether the user has forced the activity to be fullscreen through the user aspect ratio
+     * settings.
+     * @hide
+     */
+    public boolean isUserFullscreenOverrideEnabled;
+
+    /**
+     * Hint about the letterbox state of the top activity.
+     * @hide
+     */
+    public boolean topActivityBoundsLetterboxed;
+
+    /**
      * Whether the update comes from a letterbox double-tap action from the user or not.
      * @hide
      */
@@ -460,7 +479,8 @@
     public boolean hasCompatUI() {
         return hasCameraCompatControl() || topActivityInSizeCompat
                 || topActivityEligibleForLetterboxEducation
-                || isLetterboxDoubleTapEnabled;
+                || isLetterboxDoubleTapEnabled
+                || topActivityEligibleForUserAspectRatioButton;
     }
 
     /**
@@ -510,6 +530,8 @@
                 && supportsMultiWindow == that.supportsMultiWindow
                 && displayAreaFeatureId == that.displayAreaFeatureId
                 && isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap
+                && topActivityEligibleForUserAspectRatioButton
+                    == that.topActivityEligibleForUserAspectRatioButton
                 && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
                 && topActivityLetterboxWidth == that.topActivityLetterboxWidth
                 && topActivityLetterboxHeight == that.topActivityLetterboxHeight
@@ -528,7 +550,8 @@
                 && isSleeping == that.isSleeping
                 && Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId)
                 && parentTaskId == that.parentTaskId
-                && Objects.equals(topActivity, that.topActivity);
+                && Objects.equals(topActivity, that.topActivity)
+                && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled;
     }
 
     /**
@@ -543,6 +566,8 @@
                 && taskId == that.taskId
                 && topActivityInSizeCompat == that.topActivityInSizeCompat
                 && isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap
+                && topActivityEligibleForUserAspectRatioButton
+                    == that.topActivityEligibleForUserAspectRatioButton
                 && topActivityEligibleForLetterboxEducation
                     == that.topActivityEligibleForLetterboxEducation
                 && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
@@ -557,7 +582,8 @@
                 && (!hasCompatUI() || configuration.getLayoutDirection()
                     == that.configuration.getLayoutDirection())
                 && (!hasCompatUI() || configuration.uiMode == that.configuration.uiMode)
-                && (!hasCompatUI() || isVisible == that.isVisible);
+                && (!hasCompatUI() || isVisible == that.isVisible)
+                && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled;
     }
 
     /**
@@ -606,11 +632,14 @@
         displayAreaFeatureId = source.readInt();
         cameraCompatControlState = source.readInt();
         isLetterboxDoubleTapEnabled = source.readBoolean();
+        topActivityEligibleForUserAspectRatioButton = source.readBoolean();
+        topActivityBoundsLetterboxed = source.readBoolean();
         isFromLetterboxDoubleTap = source.readBoolean();
         topActivityLetterboxVerticalPosition = source.readInt();
         topActivityLetterboxHorizontalPosition = source.readInt();
         topActivityLetterboxWidth = source.readInt();
         topActivityLetterboxHeight = source.readInt();
+        isUserFullscreenOverrideEnabled = source.readBoolean();
     }
 
     /**
@@ -660,11 +689,14 @@
         dest.writeInt(displayAreaFeatureId);
         dest.writeInt(cameraCompatControlState);
         dest.writeBoolean(isLetterboxDoubleTapEnabled);
+        dest.writeBoolean(topActivityEligibleForUserAspectRatioButton);
+        dest.writeBoolean(topActivityBoundsLetterboxed);
         dest.writeBoolean(isFromLetterboxDoubleTap);
         dest.writeInt(topActivityLetterboxVerticalPosition);
         dest.writeInt(topActivityLetterboxHorizontalPosition);
         dest.writeInt(topActivityLetterboxWidth);
         dest.writeInt(topActivityLetterboxHeight);
+        dest.writeBoolean(isUserFullscreenOverrideEnabled);
     }
 
     @Override
@@ -701,13 +733,17 @@
                 + " topActivityInSizeCompat=" + topActivityInSizeCompat
                 + " topActivityEligibleForLetterboxEducation= "
                         + topActivityEligibleForLetterboxEducation
-                + " topActivityLetterboxed= " + isLetterboxDoubleTapEnabled
-                + " isFromDoubleTap= " + isFromLetterboxDoubleTap
+                + " isLetterboxDoubleTapEnabled= " + isLetterboxDoubleTapEnabled
+                + " topActivityEligibleForUserAspectRatioButton= "
+                + topActivityEligibleForUserAspectRatioButton
+                + " topActivityBoundsLetterboxed= " + topActivityBoundsLetterboxed
+                + " isFromLetterboxDoubleTap= " + isFromLetterboxDoubleTap
                 + " topActivityLetterboxVerticalPosition= " + topActivityLetterboxVerticalPosition
                 + " topActivityLetterboxHorizontalPosition= "
                         + topActivityLetterboxHorizontalPosition
                 + " topActivityLetterboxWidth=" + topActivityLetterboxWidth
                 + " topActivityLetterboxHeight=" + topActivityLetterboxHeight
+                + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled
                 + " locusId=" + mTopActivityLocusId
                 + " displayAreaFeatureId=" + displayAreaFeatureId
                 + " cameraCompatControlState="
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 247d5bc..c3d26d4 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -37,6 +37,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.hardware.HardwareBuffer;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Build;
 import android.os.Handler;
@@ -71,6 +72,8 @@
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
 import android.view.inputmethod.EditorInfo;
+import android.window.ScreenCapture;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -1160,17 +1163,12 @@
         Point displaySize = new Point();
         display.getRealSize(displaySize);
 
-        int rotation = display.getRotation();
-
         // Take the screenshot
-        Bitmap screenShot = null;
+        ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture =
+                ScreenCapture.createSyncCaptureListener();
         try {
-            // Calling out without a lock held.
-            screenShot = mUiAutomationConnection.takeScreenshot(
-                    new Rect(0, 0, displaySize.x, displaySize.y));
-            if (screenShot == null) {
-                Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display "
-                        + mDisplayId);
+            if (!mUiAutomationConnection.takeScreenshot(
+                    new Rect(0, 0, displaySize.x, displaySize.y), syncScreenCapture)) {
                 return null;
             }
         } catch (RemoteException re) {
@@ -1178,10 +1176,23 @@
             return null;
         }
 
-        // Optimization
-        screenShot.setHasAlpha(false);
+        final ScreenshotHardwareBuffer screenshotBuffer =
+                syncScreenCapture.getBuffer();
+        Bitmap screenShot = screenshotBuffer.asBitmap();
+        if (screenShot == null) {
+            Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display "
+                    + mDisplayId);
+            return null;
+        }
+        Bitmap swBitmap;
+        try (HardwareBuffer buffer = screenshotBuffer.getHardwareBuffer()) {
+            swBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, false);
+        }
+        screenShot.recycle();
 
-        return screenShot;
+        // Optimization
+        swBitmap.setHasAlpha(false);
+        return swBitmap;
     }
 
     /**
@@ -1218,12 +1229,27 @@
         // Apply a sync transaction to ensure SurfaceFlinger is flushed before capturing a
         // screenshot.
         new SurfaceControl.Transaction().apply(true);
+        ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture =
+                ScreenCapture.createSyncCaptureListener();
         try {
-            return mUiAutomationConnection.takeSurfaceControlScreenshot(sc);
+            if (!mUiAutomationConnection.takeSurfaceControlScreenshot(sc, syncScreenCapture)) {
+                return null;
+            }
+
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "Error while taking screenshot!", re);
             return null;
         }
+        ScreenCapture.ScreenshotHardwareBuffer captureBuffer =
+                syncScreenCapture.getBuffer();
+        Bitmap screenShot = captureBuffer.asBitmap();
+        Bitmap swBitmap;
+        try (HardwareBuffer buffer = captureBuffer.getHardwareBuffer()) {
+            swBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, false);
+        }
+
+        screenShot.recycle();
+        return swBitmap;
     }
 
     /**
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 34f0964..52949d6 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -25,7 +25,6 @@
 import android.annotation.UserIdInt;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerGlobal;
@@ -51,8 +50,6 @@
 import android.view.accessibility.IAccessibilityManager;
 import android.window.ScreenCapture;
 import android.window.ScreenCapture.CaptureArgs;
-import android.window.ScreenCapture.ScreenshotHardwareBuffer;
-import android.window.ScreenCapture.SynchronousScreenCaptureListener;
 
 import libcore.io.IoUtils;
 
@@ -224,56 +221,54 @@
     }
 
     @Override
-    public Bitmap takeScreenshot(Rect crop) {
+    public boolean takeScreenshot(Rect crop, ScreenCapture.ScreenCaptureListener listener) {
         synchronized (mLock) {
             throwIfCalledByNotTrustedUidLocked();
             throwIfShutdownLocked();
             throwIfNotConnectedLocked();
         }
+
         final long identity = Binder.clearCallingIdentity();
         try {
             final CaptureArgs captureArgs = new CaptureArgs.Builder<>()
                     .setSourceCrop(crop)
                     .build();
-            SynchronousScreenCaptureListener syncScreenCapture =
-                    ScreenCapture.createSyncCaptureListener();
-            mWindowManager.captureDisplay(DEFAULT_DISPLAY, captureArgs,
-                    syncScreenCapture);
-            final ScreenshotHardwareBuffer screenshotBuffer =
-                    syncScreenCapture.getBuffer();
-            return screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+            mWindowManager.captureDisplay(DEFAULT_DISPLAY, captureArgs, listener);
         } catch (RemoteException re) {
             re.rethrowAsRuntimeException();
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
-        return null;
+
+        return true;
     }
 
     @Nullable
     @Override
-    public Bitmap takeSurfaceControlScreenshot(@NonNull SurfaceControl surfaceControl) {
+    public boolean takeSurfaceControlScreenshot(@NonNull SurfaceControl surfaceControl,
+            ScreenCapture.ScreenCaptureListener listener) {
         synchronized (mLock) {
             throwIfCalledByNotTrustedUidLocked();
             throwIfShutdownLocked();
             throwIfNotConnectedLocked();
         }
 
-        ScreenCapture.ScreenshotHardwareBuffer captureBuffer;
         final long identity = Binder.clearCallingIdentity();
         try {
-            captureBuffer = ScreenCapture.captureLayers(
+            ScreenCapture.LayerCaptureArgs args =
                     new ScreenCapture.LayerCaptureArgs.Builder(surfaceControl)
-                            .setChildrenOnly(false)
-                            .build());
+                    .setChildrenOnly(false)
+                    .build();
+            int status = ScreenCapture.captureLayers(args, listener);
+
+            if (status != 0) {
+                return false;
+            }
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
 
-        if (captureBuffer == null) {
-            return null;
-        }
-        return captureBuffer.asBitmap();
+        return true;
     }
 
     @Override
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index d90257a..0ccb9cd 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -315,7 +315,7 @@
     @SystemApi
     public static final int MODE_NIGHT_CUSTOM_TYPE_BEDTIME = 1;
 
-    private IUiModeManager mService;
+    private static Globals sGlobals;
 
     /**
      * Context required for getting the opPackageName of API caller; maybe be {@code null} if the
@@ -341,6 +341,60 @@
             mOnProjectionStateChangedListenerResourceManager =
             new OnProjectionStateChangedListenerResourceManager();
 
+    private static class Globals extends IUiModeManagerCallback.Stub {
+
+        private final IUiModeManager mService;
+        private final Object mGlobalsLock = new Object();
+
+        private float mContrast = ContrastUtils.CONTRAST_DEFAULT_VALUE;
+
+        /**
+         * Map that stores user provided {@link ContrastChangeListener} callbacks,
+         * and the executors on which these callbacks should be called.
+         */
+        private final ArrayMap<ContrastChangeListener, Executor>
+                mContrastChangeListeners = new ArrayMap<>();
+
+        Globals(IUiModeManager service) {
+            mService = service;
+            try {
+                mService.addCallback(this);
+                mContrast = mService.getContrast();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Setup failed: UiModeManagerService is dead", e);
+            }
+        }
+
+        private float getContrast() {
+            synchronized (mGlobalsLock) {
+                return mContrast;
+            }
+        }
+
+        private void addContrastChangeListener(ContrastChangeListener listener, Executor executor) {
+            synchronized (mGlobalsLock) {
+                mContrastChangeListeners.put(listener, executor);
+            }
+        }
+
+        private void removeContrastChangeListener(ContrastChangeListener listener) {
+            synchronized (mGlobalsLock) {
+                mContrastChangeListeners.remove(listener);
+            }
+        }
+
+        @Override
+        public void notifyContrastChanged(float contrast) {
+            synchronized (mGlobalsLock) {
+                // if value changed in the settings, update the cached value and notify listeners
+                if (Math.abs(mContrast - contrast) < 1e-10) return;
+                mContrast = contrast;
+                mContrastChangeListeners.forEach((listener, executor) -> executor.execute(
+                        () -> listener.onContrastChanged(contrast)));
+            }
+        }
+    }
+
     /**
      * Define constants and conversions between {@link ContrastLevel}s and contrast values.
      * <p>
@@ -407,43 +461,18 @@
         }
     }
 
-    /**
-     * Map that stores user provided {@link ContrastChangeListener} callbacks,
-     * and the executors on which these callbacks should be called.
-     */
-    private final ArrayMap<ContrastChangeListener, Executor>
-            mContrastChangeListeners = new ArrayMap<>();
-    private float mContrast;
-
-    private final IUiModeManagerCallback.Stub mCallback = new IUiModeManagerCallback.Stub() {
-        @Override
-        public void notifyContrastChanged(float contrast) {
-            final ArrayMap<ContrastChangeListener, Executor> listeners;
-            synchronized (mLock) {
-                // if value changed in the settings, update the cached value and notify listeners
-                if (Math.abs(mContrast - contrast) < 1e-10) return;
-                mContrast = contrast;
-                listeners = new ArrayMap<>(mContrastChangeListeners);
-            }
-            listeners.forEach((listener, executor) -> executor.execute(
-                    () -> listener.onContrastChanged(mContrast)));
-        }
-    };
-
     @UnsupportedAppUsage
     /*package*/ UiModeManager() throws ServiceNotFoundException {
         this(null /* context */);
     }
 
     /*package*/ UiModeManager(Context context) throws ServiceNotFoundException {
-        mService = IUiModeManager.Stub.asInterface(
+        IUiModeManager service = IUiModeManager.Stub.asInterface(
                 ServiceManager.getServiceOrThrow(Context.UI_MODE_SERVICE));
         mContext = context;
-        try {
-            mService.addCallback(mCallback);
-            mContrast = mService.getContrast();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Setup failed: UiModeManagerService is dead", e);
+        if (service == null) return;
+        synchronized (mLock) {
+            if (sGlobals == null) sGlobals = new Globals(service);
         }
     }
 
@@ -533,9 +562,9 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED)
     public void enableCarMode(@IntRange(from = 0) int priority, @EnableCarMode int flags) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                mService.enableCarMode(flags, priority,
+                sGlobals.mService.enableCarMode(flags, priority,
                         mContext == null ? null : mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -585,9 +614,9 @@
      * @param flags One of the disable car mode flags.
      */
     public void disableCarMode(@DisableCarMode int flags) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                mService.disableCarModeByCallingPackage(flags,
+                sGlobals.mService.disableCarModeByCallingPackage(flags,
                         mContext == null ? null : mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -606,9 +635,9 @@
      * {@link Configuration#UI_MODE_TYPE_VR_HEADSET Configuration.UI_MODE_TYPE_VR_HEADSET}.
      */
     public int getCurrentModeType() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.getCurrentModeType();
+                return sGlobals.mService.getCurrentModeType();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -653,9 +682,9 @@
      * @see #setApplicationNightMode(int)
      */
     public void setNightMode(@NightMode int mode) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                mService.setNightMode(mode);
+                sGlobals.mService.setNightMode(mode);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -674,9 +703,9 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
     public void setNightModeCustomType(@NightModeCustomType int nightModeCustomType) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                mService.setNightModeCustomType(nightModeCustomType);
+                sGlobals.mService.setNightModeCustomType(nightModeCustomType);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -693,9 +722,9 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
     public @NightModeCustomReturnType int getNightModeCustomType() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.getNightModeCustomType();
+                return sGlobals.mService.getNightModeCustomType();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -732,9 +761,9 @@
      * @see #setNightMode(int)
      */
     public void setApplicationNightMode(@NightMode int mode) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                mService.setApplicationNightMode(mode);
+                sGlobals.mService.setApplicationNightMode(mode);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -757,9 +786,9 @@
      * @see #setNightMode(int)
      */
     public @NightMode int getNightMode() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.getNightMode();
+                return sGlobals.mService.getNightMode();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -774,9 +803,9 @@
      */
     @TestApi
     public boolean isUiModeLocked() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.isUiModeLocked();
+                return sGlobals.mService.isUiModeLocked();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -796,9 +825,9 @@
      */
     @TestApi
     public boolean isNightModeLocked() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.isNightModeLocked();
+                return sGlobals.mService.isNightModeLocked();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -820,9 +849,10 @@
     @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
     public boolean setNightModeActivatedForCustomMode(@NightModeCustomType int nightModeCustomType,
             boolean active) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.setNightModeActivatedForCustomMode(nightModeCustomType, active);
+                return sGlobals.mService.setNightModeActivatedForCustomMode(
+                        nightModeCustomType, active);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -838,9 +868,9 @@
      */
     @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
     public boolean setNightModeActivated(boolean active) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.setNightModeActivated(active);
+                return sGlobals.mService.setNightModeActivated(active);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -856,9 +886,9 @@
      */
     @NonNull
     public LocalTime getCustomNightModeStart() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return LocalTime.ofNanoOfDay(mService.getCustomNightModeStart() * 1000);
+                return LocalTime.ofNanoOfDay(sGlobals.mService.getCustomNightModeStart() * 1000);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -874,9 +904,9 @@
      * @param time The time of the day Dark theme should activate
      */
     public void setCustomNightModeStart(@NonNull LocalTime time) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                mService.setCustomNightModeStart(time.toNanoOfDay() / 1000);
+                sGlobals.mService.setCustomNightModeStart(time.toNanoOfDay() / 1000);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -891,9 +921,9 @@
      */
     @NonNull
     public LocalTime getCustomNightModeEnd() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return LocalTime.ofNanoOfDay(mService.getCustomNightModeEnd() * 1000);
+                return LocalTime.ofNanoOfDay(sGlobals.mService.getCustomNightModeEnd() * 1000);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -909,9 +939,9 @@
      * @param time The time of the day Dark theme should deactivate
      */
     public void setCustomNightModeEnd(@NonNull LocalTime time) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                mService.setCustomNightModeEnd(time.toNanoOfDay() / 1000);
+                sGlobals.mService.setCustomNightModeEnd(time.toNanoOfDay() / 1000);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -976,9 +1006,9 @@
     @RequiresPermission(value = android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION,
             conditional = true)
     public boolean requestProjection(@ProjectionType int projectionType) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.requestProjection(new Binder(), projectionType,
+                return sGlobals.mService.requestProjection(new Binder(), projectionType,
                         mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -1005,9 +1035,10 @@
     @RequiresPermission(value = android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION,
             conditional = true)
     public boolean releaseProjection(@ProjectionType int projectionType) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.releaseProjection(projectionType, mContext.getOpPackageName());
+                return sGlobals.mService.releaseProjection(
+                        projectionType, mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1028,9 +1059,9 @@
     @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE)
     @NonNull
     public Set<String> getProjectingPackages(@ProjectionType int projectionType) {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return new ArraySet<>(mService.getProjectingPackages(projectionType));
+                return new ArraySet<>(sGlobals.mService.getProjectingPackages(projectionType));
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1046,9 +1077,9 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE)
     public @ProjectionType int getActiveProjectionTypes() {
-        if (mService != null) {
+        if (sGlobals != null) {
             try {
-                return mService.getActiveProjectionTypes();
+                return sGlobals.mService.getActiveProjectionTypes();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1076,11 +1107,12 @@
                 Slog.i(TAG, "Attempted to add listener that was already added.");
                 return;
             }
-            if (mService != null) {
+            if (sGlobals != null) {
                 InnerListener innerListener = new InnerListener(executor, listener,
                         mOnProjectionStateChangedListenerResourceManager);
                 try {
-                    mService.addOnProjectionStateChangedListener(innerListener, projectionType);
+                    sGlobals.mService.addOnProjectionStateChangedListener(
+                            innerListener, projectionType);
                     mProjectionStateListenerMap.put(listener, innerListener);
                 } catch (RemoteException e) {
                     mOnProjectionStateChangedListenerResourceManager.remove(innerListener);
@@ -1107,9 +1139,9 @@
                 Slog.i(TAG, "Attempted to remove listener that was not added.");
                 return;
             }
-            if (mService != null) {
+            if (sGlobals != null) {
                 try {
-                    mService.removeOnProjectionStateChangedListener(innerListener);
+                    sGlobals.mService.removeOnProjectionStateChangedListener(innerListener);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
@@ -1197,15 +1229,10 @@
      *     <li>       -1 corresponds to the minimum contrast </li>
      *     <li> &nbsp; 1 corresponds to the maximum contrast </li>
      * </ul>
-     *
-     *
-     *
      */
     @FloatRange(from = -1.0f, to = 1.0f)
     public float getContrast() {
-        synchronized (mLock) {
-            return mContrast;
-        }
+        return sGlobals.getContrast();
     }
 
     /**
@@ -1219,9 +1246,7 @@
             @NonNull ContrastChangeListener listener) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(listener);
-        synchronized (mLock) {
-            mContrastChangeListeners.put(listener, executor);
-        }
+        sGlobals.addContrastChangeListener(listener, executor);
     }
 
     /**
@@ -1232,8 +1257,6 @@
      */
     public void removeContrastChangeListener(@NonNull ContrastChangeListener listener) {
         Objects.requireNonNull(listener);
-        synchronized (mLock) {
-            mContrastChangeListeners.remove(listener);
-        }
+        sGlobals.removeContrastChangeListener(listener);
     }
 }
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index b710644..5f5a7df 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -28,6 +28,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Size;
@@ -144,6 +145,7 @@
             throw new IllegalArgumentException("Drawable cannot be null");
         }
 
+        Trace.beginSection("WallpaperColors#fromDrawable");
         Rect initialBounds = drawable.copyBounds();
         int width = drawable.getIntrinsicWidth();
         int height = drawable.getIntrinsicHeight();
@@ -165,6 +167,7 @@
         bitmap.recycle();
 
         drawable.setBounds(initialBounds);
+        Trace.endSection();
         return colors;
     }
 
@@ -195,7 +198,7 @@
     public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap,
             @FloatRange (from = 0f, to = 1f) float dimAmount) {
         Objects.requireNonNull(bitmap, "Bitmap can't be null");
-
+        Trace.beginSection("WallpaperColors#fromBitmap");
         final int bitmapArea = bitmap.getWidth() * bitmap.getHeight();
         boolean shouldRecycle = false;
         if (bitmapArea > MAX_WALLPAPER_EXTRACTION_AREA) {
@@ -247,6 +250,7 @@
             bitmap.recycle();
         }
 
+        Trace.endSection();
         return new WallpaperColors(populationByColor, HINT_FROM_BITMAP | hints);
     }
 
@@ -462,7 +466,7 @@
      * Gets the most visually representative color of the wallpaper.
      * "Visually representative" means easily noticeable in the image,
      * probably happening at high frequency.
-     *
+     *fromBitmap
      * @return A color.
      */
     public @NonNull Color getPrimaryColor() {
@@ -545,6 +549,7 @@
             return 0;
         }
 
+        Trace.beginSection("WallpaperColors#calculateDarkHints");
         dimAmount = MathUtils.saturate(dimAmount);
         int[] pixels = new int[source.getWidth() * source.getHeight()];
         double totalLuminance = 0;
@@ -607,6 +612,7 @@
                     " maxD: " + maxDarkPixels + " numPixels: " + pixels.length);
         }
 
+        Trace.endSection();
         return hints;
     }
 
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index ee888ed..1c5332a 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -853,7 +853,7 @@
     private static boolean isLockscreenLiveWallpaperEnabledHelper() {
         if (sGlobals == null) {
             sIsLockscreenLiveWallpaperEnabled = SystemProperties.getBoolean(
-                    "persist.wm.debug.lockscreen_live_wallpaper", false);
+                    "persist.wm.debug.lockscreen_live_wallpaper", true);
         }
         if (sIsLockscreenLiveWallpaperEnabled == null) {
             try {
@@ -2430,19 +2430,38 @@
     }
 
     /**
-     * Reset all wallpaper to the factory default.
+     * Reset all wallpaper to the factory default. As opposed to {@link #clear()}, if the device
+     * is configured to have a live wallpaper by default, apply it.
      *
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#SET_WALLPAPER}.
      */
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
     public void clearWallpaper() {
+        if (isLockscreenLiveWallpaperEnabled()) {
+            clearWallpaper(FLAG_LOCK | FLAG_SYSTEM, mContext.getUserId());
+            return;
+        }
         clearWallpaper(FLAG_LOCK, mContext.getUserId());
         clearWallpaper(FLAG_SYSTEM, mContext.getUserId());
     }
 
     /**
-     * Clear the wallpaper for a specific user.  The caller must hold the
+     * Clear the wallpaper for a specific user.
+     * <ul>
+     *     <li> When called with {@code which=}{@link #FLAG_LOCK}, clear the lockscreen wallpaper.
+     *     The home screen wallpaper will become visible on the lock screen. </li>
+     *
+     *     <li> When called with {@code which=}{@link #FLAG_SYSTEM}, revert the home screen
+     *     wallpaper to default. The lockscreen wallpaper will be unchanged: if the previous
+     *     wallpaper was shared between home and lock screen, it will become lock screen only. </li>
+     *
+     *     <li> When called with {@code which=}({@link #FLAG_LOCK} | {@link #FLAG_SYSTEM}), put the
+     *     default wallpaper on both home and lock screen, removing any user defined wallpaper.</li>
+     * </ul>
+     * </p>
+     *
+     * The caller must hold the
      * INTERACT_ACROSS_USERS_FULL permission to clear another user's
      * wallpaper, and must hold the SET_WALLPAPER permission in all
      * circumstances.
@@ -2747,8 +2766,9 @@
 
     /**
      * Remove any currently set system wallpaper, reverting to the system's built-in
-     * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
-     * is broadcast.
+     * wallpaper. As opposed to {@link #clearWallpaper()}, this method always set a static wallpaper
+     * with the default image, even if the device is configured to have a live wallpaper by default.
+     * On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
      *
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#SET_WALLPAPER}.
@@ -2763,9 +2783,14 @@
 
     /**
      * Remove one or more currently set wallpapers, reverting to the system default
-     * display for each one.  If {@link #FLAG_SYSTEM} is set in the {@code which}
-     * parameter, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} will be broadcast
-     * upon success.
+     * display for each one. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED}
+     * is broadcast.
+     * <ul>
+     *     <li> If {@link #FLAG_SYSTEM} is set in the {@code which} parameter, put the default
+     *     wallpaper on both home and lock screen, removing any user defined wallpaper. </li>
+     *     <li> When called with {@code which=}{@link #FLAG_LOCK}, clear the lockscreen wallpaper.
+     *     The home screen wallpaper will become visible on the lock screen. </li>
+     * </ul>
      *
      * @param which A bitwise combination of {@link #FLAG_SYSTEM} or
      *   {@link #FLAG_LOCK}
@@ -2775,6 +2800,7 @@
     public void clear(@SetWallpaperFlags int which) throws IOException {
         if ((which & FLAG_SYSTEM) != 0) {
             clear();
+            if (isLockscreenLiveWallpaperEnabled()) return;
         }
         if ((which & FLAG_LOCK) != 0) {
             clearWallpaper(FLAG_LOCK, mContext.getUserId());
@@ -2887,22 +2913,63 @@
             }
         }
 
-        // Check if the package exists
-        if (cn != null) {
-            try {
-                final PackageManager packageManager = context.getPackageManager();
-                packageManager.getPackageInfo(cn.getPackageName(),
-                        PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
-            } catch (PackageManager.NameNotFoundException e) {
-                cn = null;
-            }
+        if (!isComponentExist(context, cn)) {
+            cn = null;
         }
 
         return cn;
     }
 
     /**
+     * Return {@link ComponentName} of the CMF default wallpaper, or
+     * {@link #getDefaultWallpaperComponent(Context)} if none is defined.
+     *
+     * @hide
+     */
+    public static ComponentName getCmfDefaultWallpaperComponent(Context context) {
+        ComponentName cn = null;
+        String[] cmfWallpaperMap = context.getResources().getStringArray(
+                com.android.internal.R.array.default_wallpaper_component_per_device_color);
+        if (cmfWallpaperMap == null || cmfWallpaperMap.length == 0) {
+            Log.d(TAG, "No CMF wallpaper config");
+            return getDefaultWallpaperComponent(context);
+        }
+
+        for (String entry : cmfWallpaperMap) {
+            String[] cmfWallpaper;
+            if (!TextUtils.isEmpty(entry)) {
+                cmfWallpaper = entry.split(",");
+                if (cmfWallpaper != null && cmfWallpaper.length == 2 && VALUE_CMF_COLOR.equals(
+                        cmfWallpaper[0]) && !TextUtils.isEmpty(cmfWallpaper[1])) {
+                    cn = ComponentName.unflattenFromString(cmfWallpaper[1]);
+                    break;
+                }
+            }
+        }
+
+        if (!isComponentExist(context, cn)) {
+            cn = null;
+        }
+
+        return cn;
+    }
+
+    private static boolean isComponentExist(Context context, ComponentName cn) {
+        if (cn == null) {
+            return false;
+        }
+        try {
+            final PackageManager packageManager = context.getPackageManager();
+            packageManager.getPackageInfo(cn.getPackageName(),
+                    PackageManager.MATCH_DIRECT_BOOT_AWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
      * Register a callback for lock wallpaper observation. Only the OS may use this.
      *
      * @return true on success; false on error.
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 0e78275..8dd50f0 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -250,6 +250,16 @@
     public abstract ComponentName getProfileOwnerAsUser(@UserIdInt int userId);
 
     /**
+     * Returns the device owner component for the device, or {@code null} if there is not one.
+     *
+     * @deprecated added temporarily to support Android Role permission granting.
+     * Please contact Android Enterprise Device Policy team before calling this function.
+     */
+    @Deprecated
+    @Nullable
+    public abstract ComponentName getDeviceOwnerComponent(boolean callingUserOnly);
+
+    /**
      * Returns the user id of the device owner, or {@link UserHandle#USER_NULL} if there is not one.
      */
     @UserIdInt
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 5311b09..baf2a47 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -193,8 +193,8 @@
         switch (prevState) {
             // TODO(lifecycler): Extend to support all possible states.
             case ON_START:
-                lifecycleItem = StartActivityItem.obtain(null /* activityOptions */);
-                break;
+                // Fall through to return the PAUSE item to ensure the activity is properly
+                // resumed while relaunching.
             case ON_PAUSE:
                 lifecycleItem = PauseActivityItem.obtain();
                 break;
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index eb672dc..b159321 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -48,9 +48,11 @@
 import com.android.internal.appwidget.IAppWidgetService;
 import com.android.internal.os.BackgroundThread;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
 
 /**
  * Updates AppWidget state; gets information about installed AppWidget providers and other
@@ -785,7 +787,25 @@
             return;
         }
         try {
-            mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId);
+            if (RemoteViews.isAdapterConversionEnabled()) {
+                List<CompletableFuture<Void>> updateFutures = new ArrayList<>();
+                for (int i = 0; i < appWidgetIds.length; i++) {
+                    final int widgetId = appWidgetIds[i];
+                    updateFutures.add(CompletableFuture.runAsync(() -> {
+                        try {
+                            RemoteViews views = mService.getAppWidgetViews(mPackageName, widgetId);
+                            if (views.replaceRemoteCollections(viewId)) {
+                                updateAppWidget(widgetId, views);
+                            }
+                        } catch (Exception e) {
+                            Log.e(TAG, "Error notifying changes in RemoteViews", e);
+                        }
+                    }));
+                }
+                CompletableFuture.allOf(updateFutures.toArray(CompletableFuture[]::new)).join();
+            } else {
+                mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java
index 856bde8..36e0529 100644
--- a/core/java/android/content/ContentCaptureOptions.java
+++ b/core/java/android/content/ContentCaptureOptions.java
@@ -76,6 +76,20 @@
     public final boolean disableFlushForViewTreeAppearing;
 
     /**
+     * Is the content capture receiver enabled.
+     *
+     * @hide
+     */
+    public final boolean enableReceiver;
+
+    /**
+     * Options for the content protection flow.
+     *
+     * @hide
+     */
+    @NonNull public final ContentProtectionOptions contentProtectionOptions;
+
+    /**
      * List of activities explicitly allowlisted for content capture (or {@code null} if allowlisted
      * for all acitivites in the package).
      */
@@ -94,52 +108,99 @@
      * for contexts belonging to the content capture service app.
      */
     public ContentCaptureOptions(int loggingLevel) {
-        this(/* lite= */ true, loggingLevel, /* maxBufferSize= */ 0,
-                /* idleFlushingFrequencyMs= */ 0, /* textChangeFlushingFrequencyMs= */ 0,
-                /* logHistorySize= */ 0, /* disableFlushForViewTreeAppearing= */ false,
+        this(
+                /* lite= */ true,
+                loggingLevel,
+                /* maxBufferSize= */ 0,
+                /* idleFlushingFrequencyMs= */ 0,
+                /* textChangeFlushingFrequencyMs= */ 0,
+                /* logHistorySize= */ 0,
+                /* disableFlushForViewTreeAppearing= */ false,
+                /* enableReceiver= */ false,
+                new ContentProtectionOptions(
+                        /* enableReceiver= */ false,
+                        /* bufferSize= */ 0),
                 /* whitelistedComponents= */ null);
     }
 
-    /**
-     * Default constructor.
-     */
-    public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
-            int textChangeFlushingFrequencyMs, int logHistorySize,
-            @SuppressLint({"ConcreteCollection", "NullableCollection"})
-            @Nullable ArraySet<ComponentName> whitelistedComponents) {
-        this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
-                textChangeFlushingFrequencyMs, logHistorySize,
+    /** Default constructor. */
+    public ContentCaptureOptions(
+            int loggingLevel,
+            int maxBufferSize,
+            int idleFlushingFrequencyMs,
+            int textChangeFlushingFrequencyMs,
+            int logHistorySize,
+            @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
+                    ArraySet<ComponentName> whitelistedComponents) {
+        this(
+                /* lite= */ false,
+                loggingLevel,
+                maxBufferSize,
+                idleFlushingFrequencyMs,
+                textChangeFlushingFrequencyMs,
+                logHistorySize,
                 ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+                ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
+                new ContentProtectionOptions(
+                        ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
+                        ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE),
                 whitelistedComponents);
     }
 
     /** @hide */
-    public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
-            int textChangeFlushingFrequencyMs, int logHistorySize,
+    public ContentCaptureOptions(
+            int loggingLevel,
+            int maxBufferSize,
+            int idleFlushingFrequencyMs,
+            int textChangeFlushingFrequencyMs,
+            int logHistorySize,
             boolean disableFlushForViewTreeAppearing,
-            @SuppressLint({"ConcreteCollection", "NullableCollection"})
-            @Nullable ArraySet<ComponentName> whitelistedComponents) {
-        this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
-                textChangeFlushingFrequencyMs, logHistorySize, disableFlushForViewTreeAppearing,
+            boolean enableReceiver,
+            @NonNull ContentProtectionOptions contentProtectionOptions,
+            @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
+                    ArraySet<ComponentName> whitelistedComponents) {
+        this(
+                /* lite= */ false,
+                loggingLevel,
+                maxBufferSize,
+                idleFlushingFrequencyMs,
+                textChangeFlushingFrequencyMs,
+                logHistorySize,
+                disableFlushForViewTreeAppearing,
+                enableReceiver,
+                contentProtectionOptions,
                 whitelistedComponents);
     }
 
     /** @hide */
     @VisibleForTesting
     public ContentCaptureOptions(@Nullable ArraySet<ComponentName> whitelistedComponents) {
-        this(ContentCaptureManager.LOGGING_LEVEL_VERBOSE,
+        this(
+                ContentCaptureManager.LOGGING_LEVEL_VERBOSE,
                 ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE,
                 ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS,
                 ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS,
                 ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE,
                 ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+                ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER,
+                new ContentProtectionOptions(
+                        ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER,
+                        ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE),
                 whitelistedComponents);
     }
 
-    private ContentCaptureOptions(boolean lite, int loggingLevel, int maxBufferSize,
-            int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize,
+    private ContentCaptureOptions(
+            boolean lite,
+            int loggingLevel,
+            int maxBufferSize,
+            int idleFlushingFrequencyMs,
+            int textChangeFlushingFrequencyMs,
+            int logHistorySize,
             boolean disableFlushForViewTreeAppearing,
-            @Nullable ArraySet<ComponentName> whitelistedComponents) {
+            boolean enableReceiver,
+            @NonNull ContentProtectionOptions contentProtectionOptions,
+            @SuppressLint({"ConcreteCollection", "NullableCollection"}) @Nullable
+                    ArraySet<ComponentName> whitelistedComponents) {
         this.lite = lite;
         this.loggingLevel = loggingLevel;
         this.maxBufferSize = maxBufferSize;
@@ -147,6 +208,8 @@
         this.textChangeFlushingFrequencyMs = textChangeFlushingFrequencyMs;
         this.logHistorySize = logHistorySize;
         this.disableFlushForViewTreeAppearing = disableFlushForViewTreeAppearing;
+        this.enableReceiver = enableReceiver;
+        this.contentProtectionOptions = contentProtectionOptions;
         this.whitelistedComponents = whitelistedComponents;
     }
 
@@ -191,12 +254,22 @@
             return "ContentCaptureOptions [loggingLevel=" + loggingLevel + " (lite)]";
         }
         final StringBuilder string = new StringBuilder("ContentCaptureOptions [");
-        string.append("loggingLevel=").append(loggingLevel)
-            .append(", maxBufferSize=").append(maxBufferSize)
-            .append(", idleFlushingFrequencyMs=").append(idleFlushingFrequencyMs)
-            .append(", textChangeFlushingFrequencyMs=").append(textChangeFlushingFrequencyMs)
-            .append(", logHistorySize=").append(logHistorySize)
-            .append(", disableFlushForViewTreeAppearing=").append(disableFlushForViewTreeAppearing);
+        string.append("loggingLevel=")
+                .append(loggingLevel)
+                .append(", maxBufferSize=")
+                .append(maxBufferSize)
+                .append(", idleFlushingFrequencyMs=")
+                .append(idleFlushingFrequencyMs)
+                .append(", textChangeFlushingFrequencyMs=")
+                .append(textChangeFlushingFrequencyMs)
+                .append(", logHistorySize=")
+                .append(logHistorySize)
+                .append(", disableFlushForViewTreeAppearing=")
+                .append(disableFlushForViewTreeAppearing)
+                .append(", enableReceiver=")
+                .append(enableReceiver)
+                .append(", contentProtectionOptions=")
+                .append(contentProtectionOptions);
         if (whitelistedComponents != null) {
             string.append(", whitelisted=").append(whitelistedComponents);
         }
@@ -210,11 +283,21 @@
             pw.print(", lite");
             return;
         }
-        pw.print(", bufferSize="); pw.print(maxBufferSize);
-        pw.print(", idle="); pw.print(idleFlushingFrequencyMs);
-        pw.print(", textIdle="); pw.print(textChangeFlushingFrequencyMs);
-        pw.print(", logSize="); pw.print(logHistorySize);
-        pw.print(", disableFlushForViewTreeAppearing="); pw.print(disableFlushForViewTreeAppearing);
+        pw.print(", bufferSize=");
+        pw.print(maxBufferSize);
+        pw.print(", idle=");
+        pw.print(idleFlushingFrequencyMs);
+        pw.print(", textIdle=");
+        pw.print(textChangeFlushingFrequencyMs);
+        pw.print(", logSize=");
+        pw.print(logHistorySize);
+        pw.print(", disableFlushForViewTreeAppearing=");
+        pw.print(disableFlushForViewTreeAppearing);
+        pw.print(", enableReceiver=");
+        pw.print(enableReceiver);
+        pw.print(", contentProtectionOptions=[");
+        contentProtectionOptions.dumpShort(pw);
+        pw.print("]");
         if (whitelistedComponents != null) {
             pw.print(", whitelisted="); pw.print(whitelistedComponents);
         }
@@ -236,6 +319,8 @@
         parcel.writeInt(textChangeFlushingFrequencyMs);
         parcel.writeInt(logHistorySize);
         parcel.writeBoolean(disableFlushForViewTreeAppearing);
+        parcel.writeBoolean(enableReceiver);
+        contentProtectionOptions.writeToParcel(parcel);
         parcel.writeArraySet(whitelistedComponents);
     }
 
@@ -254,12 +339,22 @@
                     final int textChangeFlushingFrequencyMs = parcel.readInt();
                     final int logHistorySize = parcel.readInt();
                     final boolean disableFlushForViewTreeAppearing = parcel.readBoolean();
+                    final boolean enableReceiver = parcel.readBoolean();
+                    final ContentProtectionOptions contentProtectionOptions =
+                            ContentProtectionOptions.createFromParcel(parcel);
                     @SuppressWarnings("unchecked")
                     final ArraySet<ComponentName> whitelistedComponents =
                             (ArraySet<ComponentName>) parcel.readArraySet(null);
-                    return new ContentCaptureOptions(loggingLevel, maxBufferSize,
-                            idleFlushingFrequencyMs, textChangeFlushingFrequencyMs, logHistorySize,
-                            disableFlushForViewTreeAppearing, whitelistedComponents);
+                    return new ContentCaptureOptions(
+                            loggingLevel,
+                            maxBufferSize,
+                            idleFlushingFrequencyMs,
+                            textChangeFlushingFrequencyMs,
+                            logHistorySize,
+                            disableFlushForViewTreeAppearing,
+                            enableReceiver,
+                            contentProtectionOptions,
+                            whitelistedComponents);
                 }
 
                 @Override
@@ -267,4 +362,62 @@
                     return new ContentCaptureOptions[size];
                 }
     };
+
+    /**
+     * Content protection options for a given package.
+     *
+     * <p>Does not implement {@code Parcelable} since it is an inner class without a matching AIDL.
+     *
+     * @hide
+     */
+    public static class ContentProtectionOptions {
+
+        /**
+         * Is the content protection receiver enabled.
+         *
+         * @hide
+         */
+        public final boolean enableReceiver;
+
+        /**
+         * Size of the in-memory ring buffer for the content protection flow.
+         *
+         * @hide
+         */
+        public final int bufferSize;
+
+        public ContentProtectionOptions(boolean enableReceiver, int bufferSize) {
+            this.enableReceiver = enableReceiver;
+            this.bufferSize = bufferSize;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder stringBuilder = new StringBuilder("ContentProtectionOptions [");
+            stringBuilder
+                    .append("enableReceiver=")
+                    .append(enableReceiver)
+                    .append(", bufferSize=")
+                    .append(bufferSize);
+            return stringBuilder.append(']').toString();
+        }
+
+        private void dumpShort(@NonNull PrintWriter pw) {
+            pw.print("enableReceiver=");
+            pw.print(enableReceiver);
+            pw.print(", bufferSize=");
+            pw.print(bufferSize);
+        }
+
+        private void writeToParcel(Parcel parcel) {
+            parcel.writeBoolean(enableReceiver);
+            parcel.writeInt(bufferSize);
+        }
+
+        private static ContentProtectionOptions createFromParcel(Parcel parcel) {
+            boolean enableReceiver = parcel.readBoolean();
+            int bufferSize = parcel.readInt();
+            return new ContentProtectionOptions(enableReceiver, bufferSize);
+        }
+    }
 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6d82922..2a6d84b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5298,6 +5298,13 @@
     public static final String APP_PREDICTION_SERVICE = "app_prediction";
 
     /**
+     * Used for reading system-wide, overridable flags.
+     *
+     * @hide
+     */
+    public static final String FEATURE_FLAGS_SERVICE = "feature_flags";
+
+    /**
      * Official published name of the search ui service.
      *
      * <p><b>NOTE: </b> this service is optional; callers of
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 47a5db8..3fdd023 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -770,6 +770,11 @@
 
     void setSplashScreenTheme(String packageName, String themeName, int userId);
 
+    int getUserMinAspectRatio(String packageName, int userId);
+
+    @EnforcePermission("INSTALL_PACKAGES")
+    void setUserMinAspectRatio(String packageName, int userId, int aspectRatio);
+
     List<String> getMimeGroup(String packageName, String group);
 
     boolean isAutoRevokeWhitelisted(String packageName);
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index c9735b0..86087cb 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -61,7 +61,7 @@
 
     void resetThrottling(); // system only API for developer opsions
 
-    void onApplicationActive(String packageName, int userId); // system only API for sysUI
+    oneway void onApplicationActive(String packageName, int userId); // system only API for sysUI
 
     byte[] getBackupPayload(int user);
 
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 4b883cd..537e7d4 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2978,6 +2978,8 @@
          *     <li>{@code requireUserAction} is set to {@link #USER_ACTION_NOT_REQUIRED}.</li>
          *     <li>The app being installed targets:
          *          <ul>
+         *              <li>{@link android.os.Build.VERSION_CODES#Q API 29} or higher on
+         *              Android S  ({@link android.os.Build.VERSION_CODES#S API 31})</li>
          *              <li>{@link android.os.Build.VERSION_CODES#R API 30} or higher on
          *              Android T ({@link android.os.Build.VERSION_CODES#TIRAMISU API 33})</li>
          *              <li>{@link android.os.Build.VERSION_CODES#S API 31} or higher <b>after</b>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 2a0d4ac..54fb4e7 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2360,6 +2360,72 @@
      */
     public static final int INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST = -130;
 
+    /**
+     * App minimum aspect ratio set by the user which will override app-defined aspect ratio.
+     *
+     * @hide
+     */
+    @IntDef(prefix = { "USER_MIN_ASPECT_RATIO_" }, value = {
+            USER_MIN_ASPECT_RATIO_UNSET,
+            USER_MIN_ASPECT_RATIO_SPLIT_SCREEN,
+            USER_MIN_ASPECT_RATIO_DISPLAY_SIZE,
+            USER_MIN_ASPECT_RATIO_4_3,
+            USER_MIN_ASPECT_RATIO_16_9,
+            USER_MIN_ASPECT_RATIO_3_2,
+            USER_MIN_ASPECT_RATIO_FULLSCREEN,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UserMinAspectRatio {}
+
+    /**
+     * No aspect ratio override has been set by user.
+     *
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_UNSET = 0;
+
+    /**
+     * Aspect ratio override code: user forces app to split screen aspect ratio. This is adjusted to
+     * half of the screen without the split screen divider.
+     *
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_SPLIT_SCREEN = 1;
+
+    /**
+     * Aspect ratio override code: user forces app to the aspect ratio of the device display size.
+     * This will be the portrait aspect ratio of the device if the app has fixed portrait
+     * orientation or the landscape aspect ratio of the device if the app has fixed landscape
+     * orientation.
+     *
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_DISPLAY_SIZE = 2;
+
+    /**
+     * Aspect ratio override code: user forces app to 4:3 min aspect ratio
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_4_3 = 3;
+
+    /**
+     * Aspect ratio override code: user forces app to 16:9 min aspect ratio
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_16_9 = 4;
+
+    /**
+     * Aspect ratio override code: user forces app to 3:2 min aspect ratio
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_3_2 = 5;
+
+    /**
+     * Aspect ratio override code: user forces app to fullscreen
+     * @hide
+     */
+    public static final int USER_MIN_ASPECT_RATIO_FULLSCREEN = 6;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "DELETE_" }, value = {
             DELETE_KEEP_DATA,
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index fc3dc79..946b5f3 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -261,7 +261,10 @@
 
         /**
          * @param type the type of the credential to be stored
-         * @param credentialData the full credential creation request data
+         * @param credentialData the full credential creation request data, which must at minimum
+         * contain the required fields observed at the
+         * {@link androidx.credentials.CreateCredentialRequest} Bundle conversion static methods,
+         * because they are required for properly displaying the system credential selector UI
          * @param candidateQueryData the partial request data that will be sent to the provider
          *                           during the initial creation candidate query stage
          */
diff --git a/core/java/android/flags/BooleanFlag.java b/core/java/android/flags/BooleanFlag.java
new file mode 100644
index 0000000..d4a35b2
--- /dev/null
+++ b/core/java/android/flags/BooleanFlag.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+
+/**
+ * A flag representing a true or false value.
+ *
+ * The value will always be the same during the lifetime of the process it is read in.
+ *
+ * @hide
+ */
+public class BooleanFlag extends BooleanFlagBase {
+    private final boolean mDefault;
+
+    /**
+     * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+     * @param name A name for this flag.
+     * @param defaultValue The value of this flag if no other override is present.
+     */
+    BooleanFlag(String namespace, String name, boolean defaultValue) {
+        super(namespace, name);
+        mDefault = defaultValue;
+    }
+
+    @Override
+    @NonNull
+    public Boolean getDefault() {
+        return mDefault;
+    }
+
+    @Override
+    public BooleanFlag defineMetaData(String label, String description, String categoryName) {
+        super.defineMetaData(label, description, categoryName);
+        return this;
+    }
+}
diff --git a/core/java/android/flags/BooleanFlagBase.java b/core/java/android/flags/BooleanFlagBase.java
new file mode 100644
index 0000000..985dbe3
--- /dev/null
+++ b/core/java/android/flags/BooleanFlagBase.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+
+abstract class BooleanFlagBase implements Flag<Boolean> {
+
+    private final String mNamespace;
+    private final String mName;
+    private String mLabel;
+    private String mDescription;
+    private String mCategoryName;
+
+    /**
+     * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+     * @param name A name for this flag.
+     */
+    BooleanFlagBase(String namespace, String name) {
+        mNamespace = namespace;
+        mName = name;
+        mLabel = name;
+    }
+
+    public abstract Boolean getDefault();
+
+    @Override
+    @NonNull
+    public String getNamespace() {
+        return mNamespace;
+    }
+
+    @Override
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    @Override
+    public BooleanFlagBase defineMetaData(String label, String description, String categoryName) {
+        mLabel = label;
+        mDescription = description;
+        mCategoryName = categoryName;
+        return this;
+    }
+
+    @Override
+    @NonNull
+    public String getLabel() {
+        return mLabel;
+    }
+
+    @Override
+    public String getDescription() {
+        return mDescription;
+    }
+
+    @Override
+    public String getCategoryName() {
+        return mCategoryName;
+    }
+
+    @Override
+    @NonNull
+    public String toString() {
+        return getNamespace() + "." + getName() + "[" + getDefault() + "]";
+    }
+}
diff --git a/core/java/android/flags/DynamicBooleanFlag.java b/core/java/android/flags/DynamicBooleanFlag.java
new file mode 100644
index 0000000..271a8c5f4
--- /dev/null
+++ b/core/java/android/flags/DynamicBooleanFlag.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+/**
+ * A flag representing a true or false value.
+ *
+ * The value may be different from one read to the next.
+ *
+ * @hide
+ */
+public class DynamicBooleanFlag extends BooleanFlagBase implements DynamicFlag<Boolean> {
+
+    private final boolean mDefault;
+
+    /**
+     * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+     * @param name A name for this flag.
+     * @param defaultValue The value of this flag if no other override is present.
+     */
+    DynamicBooleanFlag(String namespace, String name, boolean defaultValue) {
+        super(namespace, name);
+        mDefault = defaultValue;
+    }
+
+    @Override
+    public Boolean getDefault() {
+        return mDefault;
+    }
+
+    @Override
+    public DynamicBooleanFlag defineMetaData(String label, String description, String categoryName) {
+        super.defineMetaData(label, description, categoryName);
+        return this;
+    }
+}
diff --git a/core/java/android/flags/DynamicFlag.java b/core/java/android/flags/DynamicFlag.java
new file mode 100644
index 0000000..68819c5
--- /dev/null
+++ b/core/java/android/flags/DynamicFlag.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+/**
+ * A flag for which the value may be different from one read to the next.
+ *
+ * @param <T> The type of value that this flag stores. E.g. Boolean or String.
+ *
+ * @hide
+ */
+public interface DynamicFlag<T> extends Flag<T> {
+    @Override
+    default boolean isDynamic() {
+        return true;
+    }
+}
diff --git a/core/java/android/flags/FeatureFlags.java b/core/java/android/flags/FeatureFlags.java
new file mode 100644
index 0000000..8d3112c
--- /dev/null
+++ b/core/java/android/flags/FeatureFlags.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class for querying constants from the system - primarily booleans.
+ *
+ * Clients using this class can define their flags and their default values in one place,
+ * can override those values on running devices for debugging and testing purposes, and can control
+ * what flags are available to be used on release builds.
+ *
+ * TODO(b/279054964): A lot. This is skeleton code right now.
+ * @hide
+ */
+public class FeatureFlags {
+    private static final String TAG = "FeatureFlags";
+    private static FeatureFlags sInstance;
+    private static final Object sInstanceLock = new Object();
+
+    private final Set<Flag<?>> mKnownFlags = new ArraySet<>();
+    private final Set<Flag<?>> mDirtyFlags = new ArraySet<>();
+
+    private IFeatureFlags mIFeatureFlags;
+    private final Map<String, Map<String, Boolean>> mBooleanOverrides = new HashMap<>();
+    private final Set<ChangeListener> mListeners = new HashSet<>();
+
+    /**
+     * Obtain a per-process instance of FeatureFlags.
+     * @return A singleton instance of {@link FeatureFlags}.
+     */
+    @NonNull
+    public static FeatureFlags getInstance() {
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                sInstance = new FeatureFlags();
+            }
+        }
+
+        return sInstance;
+    }
+
+    /** See {@link FeatureFlagsFake}. */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public static void setInstance(FeatureFlags instance) {
+        synchronized (sInstanceLock) {
+            sInstance = instance;
+        }
+    }
+
+    private final IFeatureFlagsCallback mIFeatureFlagsCallback = new IFeatureFlagsCallback.Stub() {
+        @Override
+        public void onFlagChange(SyncableFlag flag) {
+            for (Flag<?> f : mKnownFlags) {
+                if (flagEqualsSyncableFlag(f, flag)) {
+                    if (f instanceof DynamicFlag<?>) {
+                        if (f instanceof DynamicBooleanFlag) {
+                            String value = flag.getValue();
+                            if (value == null) {  // Null means any existing overrides were erased.
+                                value = ((DynamicBooleanFlag) f).getDefault().toString();
+                            }
+                            addBooleanOverride(flag.getNamespace(), flag.getName(), value);
+                        }
+                        FeatureFlags.this.onFlagChange((DynamicFlag<?>) f);
+                    }
+                    break;
+                }
+            }
+        }
+    };
+
+    private FeatureFlags() {
+        this(null);
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public FeatureFlags(IFeatureFlags iFeatureFlags) {
+        mIFeatureFlags = iFeatureFlags;
+
+        if (mIFeatureFlags != null) {
+            try {
+                mIFeatureFlags.registerCallback(mIFeatureFlagsCallback);
+            } catch (RemoteException e) {
+                // Shouldn't happen with things passed into tests.
+                Log.e(TAG, "Could not register callbacks!", e);
+            }
+        }
+    }
+
+    /**
+     * Construct a new {@link BooleanFlag}.
+     *
+     * Use this instead of constructing a {@link BooleanFlag} directly, as it registers the flag
+     * with the internals of the flagging system.
+     */
+    @NonNull
+    public static BooleanFlag booleanFlag(
+            @NonNull String namespace, @NonNull String name, boolean def) {
+        return getInstance().addFlag(new BooleanFlag(namespace, name, def));
+    }
+
+    /**
+     * Construct a new {@link FusedOffFlag}.
+     *
+     * Use this instead of constructing a {@link FusedOffFlag} directly, as it registers the
+     * flag with the internals of the flagging system.
+     */
+    @NonNull
+    public static FusedOffFlag fusedOffFlag(@NonNull String namespace, @NonNull String name) {
+        return getInstance().addFlag(new FusedOffFlag(namespace, name));
+    }
+
+    /**
+     * Construct a new {@link FusedOnFlag}.
+     *
+     * Use this instead of constructing a {@link FusedOnFlag} directly, as it registers the flag
+     * with the internals of the flagging system.
+     */
+    @NonNull
+    public static FusedOnFlag fusedOnFlag(@NonNull String namespace, @NonNull String name) {
+        return getInstance().addFlag(new FusedOnFlag(namespace, name));
+    }
+
+    /**
+     * Construct a new {@link DynamicBooleanFlag}.
+     *
+     * Use this instead of constructing a {@link DynamicBooleanFlag} directly, as it registers
+     * the flag with the internals of the flagging system.
+     */
+    @NonNull
+    public static DynamicBooleanFlag dynamicBooleanFlag(
+            @NonNull String namespace, @NonNull String name, boolean def) {
+        return getInstance().addFlag(new DynamicBooleanFlag(namespace, name, def));
+    }
+
+    /**
+     * Add a listener to be alerted when a {@link DynamicFlag} changes.
+     *
+     * See also {@link #removeChangeListener(ChangeListener)}.
+     *
+     * @param listener The listener to add.
+     */
+    public void addChangeListener(@NonNull ChangeListener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Remove a listener that was added earlier.
+     *
+     * See also {@link #addChangeListener(ChangeListener)}.
+     *
+     * @param listener The listener to remove.
+     */
+    public void removeChangeListener(@NonNull ChangeListener listener) {
+        mListeners.remove(listener);
+    }
+
+    protected void onFlagChange(@NonNull DynamicFlag<?> flag) {
+        for (ChangeListener l : mListeners) {
+            l.onFlagChanged(flag);
+        }
+    }
+
+    /**
+     * Returns whether the supplied flag is true or not.
+     *
+     * {@link BooleanFlag} should only be used in debug builds. They do not get optimized out.
+     *
+     * The first time a flag is read, its value is cached for the lifetime of the process.
+     */
+    public boolean isEnabled(@NonNull BooleanFlag flag) {
+        return getBooleanInternal(flag);
+    }
+
+    /**
+     * Returns whether the supplied flag is true or not.
+     *
+     * Always returns false.
+     */
+    public boolean isEnabled(@NonNull FusedOffFlag flag) {
+        return false;
+    }
+
+    /**
+     * Returns whether the supplied flag is true or not.
+     *
+     * Always returns true;
+     */
+    public boolean isEnabled(@NonNull FusedOnFlag flag) {
+        return true;
+    }
+
+    /**
+     * Returns whether the supplied flag is true or not.
+     *
+     * Can return a different value for the flag each time it is called if an override comes in.
+     */
+    public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) {
+        return getBooleanInternal(flag);
+    }
+
+    private boolean getBooleanInternal(Flag<Boolean> flag) {
+        sync();
+        Map<String, Boolean> ns = mBooleanOverrides.get(flag.getNamespace());
+        Boolean value = null;
+        if (ns != null) {
+            value = ns.get(flag.getName());
+        }
+        if (value == null) {
+            throw new IllegalStateException("Boolean flag being read but was not synced: " + flag);
+        }
+
+        return value;
+    }
+
+    private <T extends Flag<?>> T addFlag(T flag)  {
+        synchronized (FeatureFlags.class) {
+            mDirtyFlags.add(flag);
+            mKnownFlags.add(flag);
+        }
+        return flag;
+    }
+
+    /**
+     * Sync any known flags that have not yet been synced.
+     *
+     * This is called implicitly when any flag is read, and is not generally needed except in
+     * exceptional circumstances.
+     */
+    public void sync() {
+        synchronized (FeatureFlags.class) {
+            if (mDirtyFlags.isEmpty()) {
+                return;
+            }
+            syncInternal(mDirtyFlags);
+            mDirtyFlags.clear();
+        }
+    }
+
+    /**
+     * Called when new flags have been declared. Gives the implementation a chance to act on them.
+     *
+     * Guaranteed to be called from a synchronized, thread-safe context.
+     */
+    protected void syncInternal(Set<Flag<?>> dirtyFlags) {
+        IFeatureFlags iFeatureFlags = bind();
+        List<SyncableFlag> syncableFlags = new ArrayList<>();
+        for (Flag<?> f : dirtyFlags) {
+            syncableFlags.add(flagToSyncableFlag(f));
+        }
+
+        List<SyncableFlag> serverFlags = List.of();  // Need to initialize the list with something.
+        try {
+            // New values come back from the service.
+            serverFlags = iFeatureFlags.syncFlags(syncableFlags);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        for (Flag<?> f : dirtyFlags) {
+            boolean found = false;
+            for (SyncableFlag sf : serverFlags) {
+                if (flagEqualsSyncableFlag(f, sf)) {
+                    if (f instanceof BooleanFlag || f instanceof DynamicBooleanFlag) {
+                        addBooleanOverride(sf.getNamespace(), sf.getName(), sf.getValue());
+                    }
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                if (f instanceof BooleanFlag) {
+                    addBooleanOverride(
+                            f.getNamespace(),
+                            f.getName(),
+                            ((BooleanFlag) f).getDefault() ? "true" : "false");
+                }
+            }
+        }
+    }
+
+    private void addBooleanOverride(String namespace, String name, String override) {
+        Map<String, Boolean> nsOverrides = mBooleanOverrides.get(namespace);
+        if (nsOverrides == null) {
+            nsOverrides = new HashMap<>();
+            mBooleanOverrides.put(namespace, nsOverrides);
+        }
+        nsOverrides.put(name, parseBoolean(override));
+    }
+
+    private SyncableFlag flagToSyncableFlag(Flag<?> f) {
+        return new SyncableFlag(
+                f.getNamespace(),
+                f.getName(),
+                f.getDefault().toString(),
+                f instanceof DynamicFlag<?>);
+    }
+
+    private IFeatureFlags bind() {
+        if (mIFeatureFlags == null) {
+            mIFeatureFlags = IFeatureFlags.Stub.asInterface(
+                    ServiceManager.getService(Context.FEATURE_FLAGS_SERVICE));
+            try {
+                mIFeatureFlags.registerCallback(mIFeatureFlagsCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to listen for flag changes!");
+            }
+        }
+
+        return mIFeatureFlags;
+    }
+
+    static boolean parseBoolean(String value) {
+        // Check for a truish string.
+        boolean result = value.equalsIgnoreCase("true")
+                || value.equals("1")
+                || value.equalsIgnoreCase("t")
+                || value.equalsIgnoreCase("on");
+        if (!result) {  // Expect a falsish string, else log an error.
+            if (!(value.equalsIgnoreCase("false")
+                    || value.equals("0")
+                    || value.equalsIgnoreCase("f")
+                    || value.equalsIgnoreCase("off"))) {
+                Log.e(TAG,
+                        "Tried parsing " + value + " as boolean but it doesn't look like one. "
+                                + "Value expected to be one of true|false, 1|0, t|f, on|off.");
+            }
+        }
+        return result;
+    }
+
+    private static boolean flagEqualsSyncableFlag(Flag<?> f, SyncableFlag sf) {
+        return f.getName().equals(sf.getName()) && f.getNamespace().equals(sf.getNamespace());
+    }
+
+
+    /**
+     * A simpler listener that is alerted when a {@link DynamicFlag} changes.
+     *
+     * See {@link #addChangeListener(ChangeListener)}
+     */
+    public interface ChangeListener {
+        /**
+         * Called when a {@link DynamicFlag} changes.
+         *
+         * @param flag The flag that has changed.
+         */
+        void onFlagChanged(DynamicFlag<?> flag);
+    }
+}
diff --git a/core/java/android/flags/FeatureFlagsFake.java b/core/java/android/flags/FeatureFlagsFake.java
new file mode 100644
index 0000000..daedcda
--- /dev/null
+++ b/core/java/android/flags/FeatureFlagsFake.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An implementation of {@link FeatureFlags} for testing.
+ *
+ * Before you read a flag from using this Fake, you must set that flag using
+ * {@link #setFlagValue(BooleanFlagBase, boolean)}. This ensures that your tests are deterministic.
+ *
+ * If you are relying on {@link FeatureFlags#getInstance()} to access FeatureFlags in your code
+ * under test, (instead of dependency injection), you can pass an instance of this fake to
+ * {@link FeatureFlags#setInstance(FeatureFlags)}. Be sure to call that method again, passing null,
+ * to ensure hermetic testing - you don't want static state persisting between your test methods.
+ *
+ * @hide
+ */
+public class FeatureFlagsFake extends FeatureFlags {
+    private final Map<BooleanFlagBase, Boolean> mFlagValues = new HashMap<>();
+    private final Set<BooleanFlagBase> mReadFlags = new HashSet<>();
+
+    public FeatureFlagsFake(IFeatureFlags iFeatureFlags) {
+        super(iFeatureFlags);
+    }
+
+    @Override
+    public boolean isEnabled(@NonNull BooleanFlag flag) {
+        return requireFlag(flag);
+    }
+
+    @Override
+    public boolean isEnabled(@NonNull FusedOffFlag flag) {
+        return requireFlag(flag);
+    }
+
+    @Override
+    public boolean isEnabled(@NonNull FusedOnFlag flag) {
+        return requireFlag(flag);
+    }
+
+    @Override
+    public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) {
+        return requireFlag(flag);
+    }
+
+    @Override
+    protected void syncInternal(Set<Flag<?>> dirtyFlags) {
+    }
+
+    /**
+     * Explicitly set a flag's value for reading in tests.
+     *
+     * You _must_ call this for every flag your code-under-test will read. Otherwise, an
+     * {@link IllegalStateException} will be thrown.
+     *
+     * You are able to set values for {@link FusedOffFlag} and {@link FusedOnFlag}, despite those
+     * flags having a fixed value at compile time, since unit tests should still test the state of
+     * those flags as both true and false. I.e. a flag that is off might be turned on in a future
+     * build or vice versa.
+     *
+     * You can not call this method _after_ a non-dynamic flag has been read. Non-dynamic flags
+     * are held stable in the system, so changing a value after reading would not match
+     * real-implementation behavior.
+     *
+     * Calling this method will trigger any {@link android.flags.FeatureFlags.ChangeListener}s that
+     * are registered for the supplied flag if the flag is a {@link DynamicFlag}.
+     *
+     * @param flag  The BooleanFlag that you want to set a value for.
+     * @param value The value that the flag should return when accessed.
+     */
+    public void setFlagValue(@NonNull BooleanFlagBase flag, boolean value) {
+        if (!(flag instanceof DynamicBooleanFlag) && mReadFlags.contains(flag)) {
+            throw new RuntimeException(
+                    "You can not set the value of a flag after it has been read. Tried to set "
+                            + flag + " to " + value + " but it already " + mFlagValues.get(flag));
+        }
+        mFlagValues.put(flag, value);
+        if (flag instanceof DynamicBooleanFlag) {
+            onFlagChange((DynamicFlag<?>) flag);
+        }
+    }
+
+    private boolean requireFlag(BooleanFlagBase flag) {
+        if (!mFlagValues.containsKey(flag)) {
+            throw new IllegalStateException(
+                    "Tried to access " + flag + " in test but no overrided specified. You must "
+                            + "call #setFlagValue for each flag read in a test.");
+        }
+        mReadFlags.add(flag);
+
+        return mFlagValues.get(flag);
+    }
+
+}
diff --git a/core/java/android/flags/Flag.java b/core/java/android/flags/Flag.java
new file mode 100644
index 0000000..b97a4c8
--- /dev/null
+++ b/core/java/android/flags/Flag.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+
+/**
+ * Base class for constants read via {@link android.flags.FeatureFlags}.
+ *
+ * @param <T> The type of value that this flag stores. E.g. Boolean or String.
+ *
+ * @hide
+ */
+public interface Flag<T> {
+    /** The namespace for a flag. Should combine uniquely with its name. */
+    @NonNull
+    String getNamespace();
+
+    /** The name of the flag. Should combine uniquely with its namespace. */
+    @NonNull
+    String getName();
+
+    /** The value of this flag if no override has been set. Null values are not supported. */
+    @NonNull
+    T getDefault();
+
+    /** Returns true if the value of this flag can change at runtime. */
+    default boolean isDynamic() {
+        return false;
+    }
+
+    /**
+     * Add human-readable details to the flag. Flag client's are not required to set this.
+     *
+     * See {@link #getLabel()}, {@link #getDescription()}, and {@link #getCategoryName()}.
+     *
+     * @return Returns `this`, to make a fluent api.
+     */
+    Flag<T> defineMetaData(String label, String description, String categoryName);
+
+    /**
+     * A human-readable name for the flag. Defaults to {@link #getName()}
+     *
+     * See {@link #defineMetaData(String, String, String)}
+     */
+    @NonNull
+    default String getLabel() {
+        return getName();
+    }
+
+    /**
+     * A human-readable description for the flag. Defaults to null if unset.
+     *
+     * See {@link #defineMetaData(String, String, String)}
+     */
+    default String getDescription() {
+        return null;
+    }
+
+    /**
+     * A human-readable category name for the flag. Defaults to null if unset.
+     *
+     * See {@link #defineMetaData(String, String, String)}
+     */
+    default String getCategoryName() {
+        return null;
+    }
+}
diff --git a/core/java/android/flags/FusedOffFlag.java b/core/java/android/flags/FusedOffFlag.java
new file mode 100644
index 0000000..6844b8f
--- /dev/null
+++ b/core/java/android/flags/FusedOffFlag.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+/**
+ * A flag representing a false value.
+ *
+ * The flag can never be changed or overridden. It is false at compile time.
+ *
+ * @hide
+ */
+public final class FusedOffFlag extends BooleanFlagBase {
+    /**
+     * @param namespace A namespace for this flag. See {@link DeviceConfig}.
+     * @param name      A name for this flag.
+     */
+    FusedOffFlag(String namespace, String name) {
+        super(namespace, name);
+    }
+
+    @Override
+    @NonNull
+    public Boolean getDefault() {
+        return false;
+    }
+
+    @Override
+    public FusedOffFlag defineMetaData(String label, String description, String categoryName) {
+        super.defineMetaData(label, description, categoryName);
+        return this;
+    }
+}
diff --git a/core/java/android/flags/FusedOnFlag.java b/core/java/android/flags/FusedOnFlag.java
new file mode 100644
index 0000000..e9adba7
--- /dev/null
+++ b/core/java/android/flags/FusedOnFlag.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+/**
+ * A flag representing a true value.
+ *
+ * The flag can never be changed or overridden. It is true at compile time.
+ *
+ * @hide
+ */
+public final class FusedOnFlag extends BooleanFlagBase {
+    /**
+     * @param namespace A namespace for this flag. See {@link DeviceConfig}.
+     * @param name      A name for this flag.
+     */
+    FusedOnFlag(String namespace, String name) {
+        super(namespace, name);
+    }
+
+    @Override
+    @NonNull
+    public Boolean getDefault() {
+        return true;
+    }
+
+    @Override
+    public FusedOnFlag defineMetaData(String label, String description, String categoryName) {
+        super.defineMetaData(label, description, categoryName);
+        return this;
+    }
+}
diff --git a/core/java/android/flags/IFeatureFlags.aidl b/core/java/android/flags/IFeatureFlags.aidl
new file mode 100644
index 0000000..3efcec9
--- /dev/null
+++ b/core/java/android/flags/IFeatureFlags.aidl
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.flags;
+
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+
+/**
+ * Binder interface for communicating with {@link com.android.server.flags.FeatureFlagsService}.
+ *
+ * This interface is used by {@link android.flags.FeatureFlags} and developers should use that to
+ * interface with the service. FeatureFlags is the "client" in this documentation.
+ *
+ * The methods allow client apps to communicate what flags they care about, and receive back
+ * current values for those flags. For stable flags, this is the finalized value until the device
+ * restarts. For {@link DynamicFlag}s, this is the last known value, though it may change in the
+ * future. Clients can listen for changes to flag values so that it can react accordingly.
+ * @hide
+ */
+interface IFeatureFlags {
+    /**
+     * Synchronize with the {@link com.android.server.flags.FeatureFlagsService} about flags of
+     * interest.
+     *
+     * The client should pass in a list of flags that it is using as {@link SyncableFlag}s, which
+     * includes what it thinks the default values of the flags are.
+     *
+     * The response will contain a list of matching SyncableFlags, whose values are set to what the
+     * value of the flags actually are. The client should update its internal state flag data to
+     * match.
+     *
+     * Generally speaking, if a flag that is passed in is new to the FeatureFlagsService, the
+     * service will cache the passed-in value, and return it back out. If, however, a different
+     * client has synced that flag with the service previously, FeatureFlagsService will return the
+     * existing cached value, which may or may not be what the current client passed in. This allows
+     * FeatureFlagsService to keep clients in agreement with one another.
+     */
+    List<SyncableFlag> syncFlags(in List<SyncableFlag> flagList);
+
+    /**
+     * Pass in an {@link IFeatureFlagsCallback} that will be called whenever a {@link DymamicFlag}
+     * changes.
+     */
+    void registerCallback(IFeatureFlagsCallback callback);
+
+    /**
+     * Remove a {@link IFeatureFlagsCallback} that was previously registered with
+     * {@link #registerCallback}.
+     */
+    void unregisterCallback(IFeatureFlagsCallback callback);
+
+    /**
+     * Query the {@link com.android.server.flags.FeatureFlagsService} for flags, but don't
+     * cache them. See {@link #syncFlags}.
+     *
+     * You almost certainly don't want this method. This is intended for the Flag Flipper
+     * application that needs to query the state of system but doesn't want to affect it by
+     * doing so. All other clients should use {@link syncFlags}.
+     */
+    List<SyncableFlag> queryFlags(in List<SyncableFlag> flagList);
+
+    /**
+     * Change a flags value in the system.
+     *
+     * This is intended for use by the Flag Flipper application.
+     */
+    void overrideFlag(in SyncableFlag flag);
+
+    /**
+     * Restore a flag to its default value.
+     *
+     * This is intended for use by the Flag Flipper application.
+     */
+    void resetFlag(in SyncableFlag flag);
+}
\ No newline at end of file
diff --git a/core/java/android/flags/IFeatureFlagsCallback.aidl b/core/java/android/flags/IFeatureFlagsCallback.aidl
new file mode 100644
index 0000000..f708667
--- /dev/null
+++ b/core/java/android/flags/IFeatureFlagsCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.flags;
+
+import android.flags.SyncableFlag;
+
+/**
+ * Callback for {@link IFeatureFlags#registerCallback} to get alerts when a {@link DynamicFlag}
+ * changes.
+ *
+ * DynamicFlags can change at run time. Stable flags will never result in a call to this method.
+ *
+ * @hide
+ */
+oneway interface IFeatureFlagsCallback {
+    void onFlagChange(in SyncableFlag flag);
+}
\ No newline at end of file
diff --git a/core/java/android/flags/OWNERS b/core/java/android/flags/OWNERS
new file mode 100644
index 0000000..fa125c4
--- /dev/null
+++ b/core/java/android/flags/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 1306523
+
+mankoff@google.com
+pixel@google.com
+
+dsandler@android.com
+
diff --git a/core/java/android/flags/SyncableFlag.aidl b/core/java/android/flags/SyncableFlag.aidl
new file mode 100644
index 0000000..1526ec1
--- /dev/null
+++ b/core/java/android/flags/SyncableFlag.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+/**
+ * A parcelable data class for serializing {@link Flag} across a Binder.
+ */
+parcelable SyncableFlag;
\ No newline at end of file
diff --git a/core/java/android/flags/SyncableFlag.java b/core/java/android/flags/SyncableFlag.java
new file mode 100644
index 0000000..449bcc3c
--- /dev/null
+++ b/core/java/android/flags/SyncableFlag.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public final class SyncableFlag implements Parcelable {
+    private final String mNamespace;
+    private final String mName;
+    private final String mValue;
+    private final boolean mDynamic;
+    private final boolean mOverridden;
+
+    public SyncableFlag(
+            @NonNull String namespace,
+            @NonNull String name,
+            @NonNull String value,
+            boolean dynamic) {
+        this(namespace, name, value, dynamic, false);
+    }
+
+    public SyncableFlag(
+            @NonNull String namespace,
+            @NonNull String name,
+            @NonNull String value,
+            boolean dynamic,
+            boolean overridden
+    ) {
+        mNamespace = namespace;
+        mName = name;
+        mValue = value;
+        mDynamic = dynamic;
+        mOverridden = overridden;
+    }
+
+    @NonNull
+    public String getNamespace() {
+        return mNamespace;
+    }
+
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    @NonNull
+    public String getValue() {
+        return mValue;
+    }
+
+    public boolean isDynamic() {
+        return mDynamic;
+    }
+
+    public boolean isOverridden() {
+        return mOverridden;
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<SyncableFlag> CREATOR = new Parcelable.Creator<>() {
+        public SyncableFlag createFromParcel(Parcel in) {
+            return new SyncableFlag(
+                    in.readString(),
+                    in.readString(),
+                    in.readString(),
+                    in.readBoolean(),
+                    in.readBoolean());
+        }
+
+        public SyncableFlag[] newArray(int size) {
+            return new SyncableFlag[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mNamespace);
+        dest.writeString(mName);
+        dest.writeString(mValue);
+        dest.writeBoolean(mDynamic);
+        dest.writeBoolean(mOverridden);
+    }
+
+    @Override
+    public String toString() {
+        return getNamespace() + "." + getName() + "[" + getValue() + "]";
+    }
+}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 2e40f60..912e8df 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -144,6 +144,7 @@
         private Context mContext;
         private IAuthService mService;
 
+        // LINT.IfChange
         /**
          * Creates a builder for a {@link BiometricPrompt} dialog.
          * @param context The {@link Context} that will be used to build the prompt.
@@ -417,6 +418,7 @@
          * @hide
          */
         @NonNull
+        @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL})
         public Builder setDisallowBiometricsIfPolicyExists(boolean checkDevicePolicyManager) {
             mPromptInfo.setDisallowBiometricsIfPolicyExists(checkDevicePolicyManager);
             return this;
@@ -429,6 +431,7 @@
          * @hide
          */
         @NonNull
+        @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL})
         public Builder setReceiveSystemEvents(boolean set) {
             mPromptInfo.setReceiveSystemEvents(set);
             return this;
@@ -442,6 +445,7 @@
          * @hide
          */
         @NonNull
+        @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
         public Builder setIgnoreEnrollmentState(boolean ignoreEnrollmentState) {
             mPromptInfo.setIgnoreEnrollmentState(ignoreEnrollmentState);
             return this;
@@ -454,10 +458,12 @@
          * @hide
          */
         @NonNull
+        @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
         public Builder setIsForLegacyFingerprintManager(int sensorId) {
             mPromptInfo.setIsForLegacyFingerprintManager(sensorId);
             return this;
         }
+        // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java)
 
         /**
          * Creates a {@link BiometricPrompt}.
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 02aad1d..e275078 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -113,6 +113,7 @@
         dest.writeBoolean(mIsForLegacyFingerprintManager);
     }
 
+    // LINT.IfChange
     public boolean containsTestConfigurations() {
         if (mIsForLegacyFingerprintManager
                 && mAllowedSensorIds.size() == 1
@@ -122,6 +123,10 @@
             return true;
         } else if (mAllowBackgroundAuthentication) {
             return true;
+        } else if (mIsForLegacyFingerprintManager) {
+            return true;
+        } else if (mIgnoreEnrollmentState) {
+            return true;
         }
         return false;
     }
@@ -144,6 +149,7 @@
         }
         return false;
     }
+    // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java)
 
     // Setters
 
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index d48e20e..6baf91d7 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -34,6 +34,7 @@
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.params.ExtensionSessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
+import android.os.Binder;
 import android.os.ConditionVariable;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -48,7 +49,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -346,28 +346,29 @@
             }
         }
 
-        public long registerClient(Context ctx) {
+        public boolean registerClient(Context ctx, IBinder token) {
             synchronized (mLock) {
                 connectToProxyLocked(ctx);
-                if (mProxy != null) {
-                    try {
-                        return mProxy.registerClient();
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Failed to initialize extension! Extension service does "
-                                + " not respond!");
-                        return -1;
-                    }
-                } else {
-                    return -1;
+                if (mProxy == null) {
+                    return false;
                 }
+
+                try {
+                    return mProxy.registerClient(token);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to initialize extension! Extension service does "
+                            + " not respond!");
+                }
+
+                return false;
             }
         }
 
-        public void unregisterClient(long clientId) {
+        public void unregisterClient(IBinder token) {
             synchronized (mLock) {
                 if (mProxy != null) {
                     try {
-                        mProxy.unregisterClient(clientId);
+                        mProxy.unregisterClient(token);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Failed to de-initialize extension! Extension service does"
                                 + " not respond!");
@@ -438,15 +439,15 @@
     /**
      * @hide
      */
-    public static long registerClient(Context ctx) {
-        return CameraExtensionManagerGlobal.get().registerClient(ctx);
+    public static boolean registerClient(Context ctx, IBinder token) {
+        return CameraExtensionManagerGlobal.get().registerClient(ctx, token);
     }
 
     /**
      * @hide
      */
-    public static void unregisterClient(long clientId) {
-        CameraExtensionManagerGlobal.get().unregisterClient(clientId);
+    public static void unregisterClient(IBinder token) {
+        CameraExtensionManagerGlobal.get().unregisterClient(token);
     }
 
     /**
@@ -564,8 +565,9 @@
      */
     public @NonNull List<Integer> getSupportedExtensions() {
         ArrayList<Integer> ret = new ArrayList<>();
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getSupportedExtensions:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             return Collections.unmodifiableList(ret);
         }
 
@@ -576,7 +578,7 @@
                 }
             }
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return Collections.unmodifiableList(ret);
@@ -599,8 +601,9 @@
      * supported device-specific extension
      */
     public boolean isPostviewAvailable(@Extension int extension) {
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#isPostviewAvailable:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -623,7 +626,7 @@
             Log.e(TAG, "Failed to query the extension for postview availability! Extension "
                     + "service does not respond!");
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return false;
@@ -656,9 +659,9 @@
     @NonNull
     public List<Size> getPostviewSupportedSizes(@Extension int extension,
             @NonNull Size captureSize, int format) {
-
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getPostviewSupportedSizes:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -719,7 +722,7 @@
                     + "service does not respond!");
             return Collections.emptyList();
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
     }
 
@@ -756,8 +759,9 @@
         // TODO: Revisit this code once the Extension preview processor output format
         //       ambiguity is resolved in b/169799538.
 
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -787,7 +791,7 @@
                     + " not respond!");
             return new ArrayList<>();
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
     }
 
@@ -814,8 +818,9 @@
     public @NonNull
     List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
         try {
-            long clientId = registerClient(mContext);
-            if (clientId < 0) {
+            final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId);
+            boolean success = registerClient(mContext, token);
+            if (!success) {
                 throw new IllegalArgumentException("Unsupported extensions");
             }
 
@@ -867,7 +872,7 @@
                     }
                 }
             } finally {
-                unregisterClient(clientId);
+                unregisterClient(token);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to query the extension supported sizes! Extension service does"
@@ -888,7 +893,6 @@
      * @param format            device-specific extension output format
      * @return the range of estimated minimal and maximal capture latency in milliseconds
      * or null if no capture latency info can be provided
-     *
      * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG} /
      *                                  {@link ImageFormat#YUV_420_888}; or unsupported extension.
      */
@@ -903,8 +907,9 @@
                 throw new IllegalArgumentException("Unsupported format: " + format);
         }
 
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getEstimatedCaptureLatencyRangeMillis:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -952,7 +957,7 @@
             Log.e(TAG, "Failed to query the extension capture latency! Extension service does"
                     + " not respond!");
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return null;
@@ -968,8 +973,9 @@
      * @throws IllegalArgumentException in case of an unsupported extension.
      */
     public boolean isCaptureProcessProgressAvailable(@Extension int extension) {
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#isCaptureProcessProgressAvailable:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -992,7 +998,7 @@
             Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does"
                     + " not respond!");
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return false;
@@ -1013,8 +1019,9 @@
      */
     @NonNull
     public Set<CaptureRequest.Key> getAvailableCaptureRequestKeys(@Extension int extension) {
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getAvailableCaptureRequestKeys:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -1033,10 +1040,11 @@
             } else {
                 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
                         initializeExtension(extension);
-                extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId));
+                extenders.second.onInit(token, mCameraId,
+                        mCharacteristicsMapNative.get(mCameraId));
                 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
                 captureRequestMeta = extenders.second.getAvailableCaptureRequestKeys();
-                extenders.second.onDeInit();
+                extenders.second.onDeInit(token);
             }
 
             if (captureRequestMeta != null) {
@@ -1067,7 +1075,7 @@
         } catch (RemoteException e) {
             throw new IllegalStateException("Failed to query the available capture request keys!");
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return Collections.unmodifiableSet(ret);
@@ -1092,8 +1100,9 @@
      */
     @NonNull
     public Set<CaptureResult.Key> getAvailableCaptureResultKeys(@Extension int extension) {
-        long clientId = registerClient(mContext);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + "#getAvailableCaptureResultKeys:" + mCameraId);
+        boolean success = registerClient(mContext, token);
+        if (!success) {
             throw new IllegalArgumentException("Unsupported extensions");
         }
 
@@ -1111,10 +1120,11 @@
             } else {
                 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
                         initializeExtension(extension);
-                extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId));
+                extenders.second.onInit(token, mCameraId,
+                        mCharacteristicsMapNative.get(mCameraId));
                 extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId));
                 captureResultMeta = extenders.second.getAvailableCaptureResultKeys();
-                extenders.second.onDeInit();
+                extenders.second.onDeInit(token);
             }
 
             if (captureResultMeta != null) {
@@ -1126,7 +1136,7 @@
                 }
                 CameraCharacteristics resultChars = new CameraCharacteristics(captureResultMeta);
                 Object crKey = CaptureResult.Key.class;
-                Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>)crKey;
+                Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>) crKey;
 
                 ret.addAll(resultChars.getAvailableKeyList(CaptureResult.class, crKeyTyped,
                         resultKeys, /*includeSynthetic*/ true));
@@ -1145,7 +1155,7 @@
         } catch (RemoteException e) {
             throw new IllegalStateException("Failed to query the available capture result keys!");
         } finally {
-            unregisterClient(clientId);
+            unregisterClient(token);
         }
 
         return Collections.unmodifiableSet(ret);
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index a64d66f..c80124c 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -31,6 +31,7 @@
 import android.content.pm.PackageManager;
 import android.graphics.Point;
 import android.hardware.CameraExtensionSessionStats;
+import android.hardware.CameraIdRemapping;
 import android.hardware.CameraStatus;
 import android.hardware.ICameraService;
 import android.hardware.ICameraServiceListener;
@@ -67,6 +68,7 @@
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -181,22 +183,20 @@
             boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
 
             mFoldedDeviceState = folded;
-            ArrayList<WeakReference<DeviceStateListener>> invalidListeners = new ArrayList<>();
-            for (WeakReference<DeviceStateListener> listener : mDeviceStateListeners) {
-                DeviceStateListener callback = listener.get();
+            Iterator<WeakReference<DeviceStateListener>> it = mDeviceStateListeners.iterator();
+            while(it.hasNext()) {
+                DeviceStateListener callback = it.next().get();
                 if (callback != null) {
                     callback.onDeviceStateChanged(folded);
                 } else {
-                    invalidListeners.add(listener);
+                    it.remove();
                 }
             }
-            if (!invalidListeners.isEmpty()) {
-                mDeviceStateListeners.removeAll(invalidListeners);
-            }
         }
 
         public synchronized void addDeviceStateListener(DeviceStateListener listener) {
             listener.onDeviceStateChanged(mFoldedDeviceState);
+            mDeviceStateListeners.removeIf(l -> l.get() == null);
             mDeviceStateListeners.add(new WeakReference<>(listener));
         }
 
@@ -1731,6 +1731,17 @@
     }
 
     /**
+     * Remaps Camera Ids in the CameraService.
+     *
+     * @hide
+    */
+    @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
+    public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
+            throws CameraAccessException, SecurityException, IllegalArgumentException {
+        CameraManagerGlobal.get().remapCameraIds(cameraIdRemapping);
+    }
+
+    /**
      * Reports {@link CameraExtensionSessionStats} to the {@link ICameraService} to be logged for
      * currently active session. Validation is done downstream.
      *
@@ -1803,6 +1814,13 @@
 
         private final Object mLock = new Object();
 
+        /**
+         * The active CameraIdRemapping. This will be used to refresh the cameraIdRemapping state
+         * in the CameraService every time we connect to it, including when the CameraService
+         * Binder dies and we reconnect to it.
+         */
+        @Nullable private CameraIdRemapping mActiveCameraIdRemapping;
+
         // Access only through getCameraService to deal with binder death
         private ICameraService mCameraService;
         private boolean mHasOpenCloseListenerPermission = false;
@@ -1945,6 +1963,41 @@
             } catch (RemoteException e) {
                 // Camera service died in all probability
             }
+
+            if (mActiveCameraIdRemapping != null) {
+                try {
+                    cameraService.remapCameraIds(mActiveCameraIdRemapping);
+                } catch (ServiceSpecificException e) {
+                    // Unexpected failure, ignore and continue.
+                    Log.e(TAG, "Unable to remap camera Ids in the camera service");
+                } catch (RemoteException e) {
+                    // Camera service died in all probability
+                }
+            }
+        }
+
+        /** Updates the cameraIdRemapping state in the CameraService. */
+        public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
+                throws CameraAccessException, SecurityException {
+            synchronized (mLock) {
+                ICameraService cameraService = getCameraService();
+                if (cameraService == null) {
+                    throw new CameraAccessException(
+                            CameraAccessException.CAMERA_DISCONNECTED,
+                            "Camera service is currently unavailable.");
+                }
+
+                try {
+                    cameraService.remapCameraIds(cameraIdRemapping);
+                    mActiveCameraIdRemapping = cameraIdRemapping;
+                } catch (ServiceSpecificException e) {
+                    throwAsPublicException(e);
+                } catch (RemoteException e) {
+                    throw new CameraAccessException(
+                            CameraAccessException.CAMERA_DISCONNECTED,
+                            "Camera service is currently unavailable.");
+                }
+            }
         }
 
         private String[] extractCameraIdListLocked() {
diff --git a/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl
index b52c6500..3b7d801 100644
--- a/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl
+++ b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl
@@ -20,11 +20,13 @@
 import android.hardware.camera2.extension.IImageCaptureExtenderImpl;
 import android.hardware.camera2.extension.IInitializeSessionCallback;
 
+import android.os.IBinder;
+
 /** @hide */
 interface ICameraExtensionsProxyService
 {
-    long registerClient();
-    void unregisterClient(long clientId);
+    boolean registerClient(in IBinder token);
+    void unregisterClient(in IBinder token);
     boolean advancedExtensionsSupported();
     void initializeSession(in IInitializeSessionCallback cb);
     void releaseSession();
diff --git a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
index 754f8f6..5a22418 100644
--- a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
@@ -24,11 +24,13 @@
 import android.hardware.camera2.extension.Size;
 import android.hardware.camera2.extension.SizeList;
 
+import android.os.IBinder;
+
 /** @hide */
 interface IImageCaptureExtenderImpl
 {
-    void onInit(in String cameraId, in CameraMetadataNative cameraCharacteristics);
-    void onDeInit();
+    void onInit(in IBinder token, in String cameraId, in CameraMetadataNative cameraCharacteristics);
+    void onDeInit(in IBinder token);
     @nullable CaptureStageImpl onPresetSession();
     @nullable CaptureStageImpl onEnableSession();
     @nullable CaptureStageImpl onDisableSession();
diff --git a/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
index 01046d0..9ea8a74 100644
--- a/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
@@ -22,11 +22,13 @@
 import android.hardware.camera2.extension.IRequestUpdateProcessorImpl;
 import android.hardware.camera2.extension.SizeList;
 
+import android.os.IBinder;
+
 /** @hide */
 interface IPreviewExtenderImpl
 {
-    void onInit(in String cameraId, in CameraMetadataNative cameraCharacteristics);
-    void onDeInit();
+    void onInit(in IBinder token, in String cameraId, in CameraMetadataNative cameraCharacteristics);
+    void onDeInit(in IBinder token);
     @nullable CaptureStageImpl onPresetSession();
     @nullable CaptureStageImpl onEnableSession();
     @nullable CaptureStageImpl onDisableSession();
diff --git a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
index 13b93a8..0581ec0 100644
--- a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
@@ -25,13 +25,15 @@
 import android.hardware.camera2.extension.LatencyRange;
 import android.hardware.camera2.extension.OutputSurface;
 
+import android.os.IBinder;
+
 /** @hide */
 interface ISessionProcessorImpl
 {
-    CameraSessionConfig initSession(in String cameraId,
+    CameraSessionConfig initSession(in IBinder token, in String cameraId,
             in Map<String, CameraMetadataNative> charsMap, in OutputSurface previewSurface,
             in OutputSurface imageCaptureSurface, in OutputSurface postviewSurface);
-    void deInitSession();
+    void deInitSession(in IBinder token);
     void onCaptureSessionStart(IRequestProcessorImpl requestProcessor);
     void onCaptureSessionEnd();
     int startRepeating(in ICaptureCallback callback);
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 65d4b43..ae700a0 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -60,6 +60,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Size;
@@ -79,7 +80,6 @@
     private final Executor mExecutor;
     private CameraDevice mCameraDevice;
     private final Map<String, CameraMetadataNative> mCharacteristicsMap;
-    private final long mExtensionClientId;
     private final Handler mHandler;
     private final HandlerThread mHandlerThread;
     private final CameraExtensionSession.StateCallback mCallbacks;
@@ -90,6 +90,7 @@
     private final HashMap<Integer, ImageReader> mReaderMap = new HashMap<>();
     private RequestProcessor mRequestProcessor = new RequestProcessor();
     private final int mSessionId;
+    private final IBinder mToken;
 
     private Surface mClientRepeatingRequestSurface;
     private Surface mClientCaptureSurface;
@@ -114,8 +115,9 @@
             @NonNull Map<String, CameraCharacteristics> characteristicsMap,
             @NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId)
             throws CameraAccessException, RemoteException {
-        long clientId = CameraExtensionCharacteristics.registerClient(ctx);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + " : " + sessionId);
+        boolean success = CameraExtensionCharacteristics.registerClient(ctx, token);
+        if (!success) {
             throw new UnsupportedOperationException("Unsupported extension!");
         }
 
@@ -202,11 +204,10 @@
         IAdvancedExtenderImpl extender = CameraExtensionCharacteristics.initializeAdvancedExtension(
                 config.getExtension());
         extender.init(cameraId, characteristicsMapNative);
-
-        CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(clientId,
-                extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface,
+        CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(extender,
+                cameraDevice, characteristicsMapNative, repeatingRequestSurface,
                 burstCaptureSurface, postviewSurface, config.getStateCallback(),
-                config.getExecutor(), sessionId);
+                config.getExecutor(), sessionId, token);
 
         ret.mStatsAggregator.setClientName(ctx.getOpPackageName());
         ret.mStatsAggregator.setExtensionType(config.getExtension());
@@ -216,15 +217,13 @@
         return ret;
     }
 
-    private CameraAdvancedExtensionSessionImpl(long extensionClientId,
-            @NonNull IAdvancedExtenderImpl extender,
+    private CameraAdvancedExtensionSessionImpl(@NonNull IAdvancedExtenderImpl extender,
             @NonNull CameraDeviceImpl cameraDevice,
             Map<String, CameraMetadataNative> characteristicsMap,
             @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
             @Nullable Surface postviewSurface,
             @NonNull StateCallback callback, @NonNull Executor executor,
-            int sessionId) {
-        mExtensionClientId = extensionClientId;
+            int sessionId, @NonNull IBinder token) {
         mAdvancedExtender = extender;
         mCameraDevice = cameraDevice;
         mCharacteristicsMap = characteristicsMap;
@@ -240,6 +239,7 @@
         mSessionClosed = false;
         mInitializeHandler = new InitializeSessionHandler();
         mSessionId = sessionId;
+        mToken = token;
         mInterfaceLock = cameraDevice.mInterfaceLock;
 
         mStatsAggregator = new ExtensionSessionStatsAggregator(mCameraDevice.getId(),
@@ -260,7 +260,8 @@
         OutputSurface postviewSurface = initializeParcelable(mClientPostviewSurface);
 
         mSessionProcessor = mAdvancedExtender.getSessionProcessor();
-        CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mCameraDevice.getId(),
+        CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mToken,
+                mCameraDevice.getId(),
                 mCharacteristicsMap, previewSurface, captureSurface, postviewSurface);
         List<CameraOutputConfig> outputConfigs = sessionConfig.outputConfigs;
         ArrayList<OutputConfiguration> outputList = new ArrayList<>();
@@ -526,7 +527,11 @@
         synchronized (mInterfaceLock) {
             if (mInitialized) {
                 try {
-                    mCaptureSession.stopRepeating();
+                    try {
+                        mCaptureSession.stopRepeating();
+                    } catch (IllegalStateException e) {
+                        // OK: already be closed, nothing else to do
+                    }
                     mSessionProcessor.stopRepeating();
                     mSessionProcessor.onCaptureSessionEnd();
                     mSessionClosed = true;
@@ -565,7 +570,7 @@
                     if (!mSessionClosed) {
                         mSessionProcessor.onCaptureSessionEnd();
                     }
-                    mSessionProcessor.deInitSession();
+                    mSessionProcessor.deInitSession(mToken);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to de-initialize session processor, extension service"
                             + " does not respond!") ;
@@ -573,12 +578,10 @@
                 mSessionProcessor = null;
             }
 
-            if (mExtensionClientId >= 0) {
-                CameraExtensionCharacteristics.unregisterClient(mExtensionClientId);
-                if (mInitialized || (mCaptureSession != null)) {
-                    notifyClose = true;
-                    CameraExtensionCharacteristics.releaseSession();
-                }
+            CameraExtensionCharacteristics.unregisterClient(mToken);
+            if (mInitialized || (mCaptureSession != null)) {
+                notifyClose = true;
+                CameraExtensionCharacteristics.releaseSession();
             }
             mInitialized = false;
 
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 9ebef0b..1db4808 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -56,6 +56,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.LongSparseArray;
@@ -79,7 +80,6 @@
 
     private final Executor mExecutor;
     private final CameraDevice mCameraDevice;
-    private final long mExtensionClientId;
     private final IImageCaptureExtenderImpl mImageExtender;
     private final IPreviewExtenderImpl mPreviewExtender;
     private final Handler mHandler;
@@ -91,6 +91,7 @@
     private final Set<CaptureRequest.Key> mSupportedRequestKeys;
     private final Set<CaptureResult.Key> mSupportedResultKeys;
     private final ExtensionSessionStatsAggregator mStatsAggregator;
+    private final IBinder mToken;
     private boolean mCaptureResultsSupported;
 
     private CameraCaptureSession mCaptureSession = null;
@@ -111,6 +112,7 @@
     private int mPreviewProcessorType = IPreviewExtenderImpl.PROCESSOR_TYPE_NONE;
 
     private boolean mInitialized;
+    private boolean mSessionClosed;
     // Enable/Disable internal preview/(repeating request). Extensions expect
     // that preview/(repeating request) is enabled and active at any point in time.
     // In case the client doesn't explicitly enable repeating requests, the framework
@@ -135,8 +137,9 @@
             @NonNull ExtensionSessionConfiguration config,
             int sessionId)
             throws CameraAccessException, RemoteException {
-        long clientId = CameraExtensionCharacteristics.registerClient(ctx);
-        if (clientId < 0) {
+        final IBinder token = new Binder(TAG + " : " + sessionId);
+        boolean success = CameraExtensionCharacteristics.registerClient(ctx, token);
+        if (!success) {
             throw new UnsupportedOperationException("Unsupported extension!");
         }
 
@@ -224,15 +227,16 @@
         }
 
         extenders.first.init(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
-        extenders.first.onInit(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
+        extenders.first.onInit(token, cameraId,
+                characteristicsMap.get(cameraId).getNativeMetadata());
         extenders.second.init(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
-        extenders.second.onInit(cameraId, characteristicsMap.get(cameraId).getNativeMetadata());
+        extenders.second.onInit(token, cameraId,
+                characteristicsMap.get(cameraId).getNativeMetadata());
 
         CameraExtensionSessionImpl session = new CameraExtensionSessionImpl(
                 extenders.second,
                 extenders.first,
                 supportedPreviewSizes,
-                clientId,
                 cameraDevice,
                 repeatingRequestSurface,
                 burstCaptureSurface,
@@ -240,6 +244,7 @@
                 config.getStateCallback(),
                 config.getExecutor(),
                 sessionId,
+                token,
                 extensionChars.getAvailableCaptureRequestKeys(config.getExtension()),
                 extensionChars.getAvailableCaptureResultKeys(config.getExtension()));
 
@@ -254,7 +259,6 @@
     public CameraExtensionSessionImpl(@NonNull IImageCaptureExtenderImpl imageExtender,
             @NonNull IPreviewExtenderImpl previewExtender,
             @NonNull List<Size> previewSizes,
-            long extensionClientId,
             @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
             @Nullable Surface repeatingRequestSurface,
             @Nullable Surface burstCaptureSurface,
@@ -262,9 +266,9 @@
             @NonNull StateCallback callback,
             @NonNull Executor executor,
             int sessionId,
+            @NonNull IBinder token,
             @NonNull Set<CaptureRequest.Key> requestKeys,
             @Nullable Set<CaptureResult.Key> resultKeys) {
-        mExtensionClientId = extensionClientId;
         mImageExtender = imageExtender;
         mPreviewExtender = previewExtender;
         mCameraDevice = cameraDevice;
@@ -278,8 +282,10 @@
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
         mInitialized = false;
+        mSessionClosed = false;
         mInitializeHandler = new InitializeSessionHandler();
         mSessionId = sessionId;
+        mToken = token;
         mSupportedRequestKeys = requestKeys;
         mSupportedResultKeys = resultKeys;
         mCaptureResultsSupported = !resultKeys.isEmpty();
@@ -775,7 +781,12 @@
         synchronized (mInterfaceLock) {
             if (mInitialized) {
                 mInternalRepeatingRequestEnabled = false;
-                mCaptureSession.stopRepeating();
+                try {
+                    mCaptureSession.stopRepeating();
+                } catch (IllegalStateException e) {
+                    // OK: already be closed, nothing else to do
+                    mSessionClosed = true;
+                }
 
                 ArrayList<CaptureStageImpl> captureStageList = new ArrayList<>();
                 try {
@@ -793,13 +804,14 @@
                     Log.e(TAG, "Failed to disable extension! Extension service does not "
                             + "respond!");
                 }
-                if (!captureStageList.isEmpty()) {
+                if (!captureStageList.isEmpty() && !mSessionClosed) {
                     CaptureRequest disableRequest = createRequest(mCameraDevice, captureStageList,
                             mCameraRepeatingSurface, CameraDevice.TEMPLATE_PREVIEW);
                     mCaptureSession.capture(disableRequest,
                             new CloseRequestHandler(mRepeatingRequestImageCallback), mHandler);
                 }
 
+                mSessionClosed = true;
                 mStatsAggregator.commit(/*isFinal*/true); // Commit stats before closing session
                 mCaptureSession.close();
             }
@@ -854,19 +866,22 @@
             mHandlerThread.quit();
 
             try {
-                mPreviewExtender.onDeInit();
-                mImageExtender.onDeInit();
+                if (!mSessionClosed) {
+                    // return value is omitted. nothing can do after session is closed.
+                    mPreviewExtender.onDisableSession();
+                    mImageExtender.onDisableSession();
+                }
+                mPreviewExtender.onDeInit(mToken);
+                mImageExtender.onDeInit(mToken);
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to release extensions! Extension service does not"
                         + " respond!");
             }
 
-            if (mExtensionClientId >= 0) {
-                CameraExtensionCharacteristics.unregisterClient(mExtensionClientId);
-                if (mInitialized || (mCaptureSession != null)) {
-                    notifyClose = true;
-                    CameraExtensionCharacteristics.releaseSession();
-                }
+            CameraExtensionCharacteristics.unregisterClient(mToken);
+            if (mInitialized || (mCaptureSession != null)) {
+                notifyClose = true;
+                CameraExtensionCharacteristics.releaseSession();
             }
             mInitialized = false;
 
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 21e4ab1..4323bf8 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1764,6 +1764,24 @@
         String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data";
 
         /**
+         * Key for new power controller feature flag. If enabled new DisplayPowerController will
+         * be used.
+         * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
+         * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace.
+         * @hide
+         */
+        String KEY_NEW_POWER_CONTROLLER = "use_newly_structured_display_power_controller";
+
+        /**
+         * Key for normal brightness mode controller feature flag.
+         * It enables NormalBrightnessModeController.
+         * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
+         * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace.
+         * @hide
+         */
+        String KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER = "use_normal_brightness_mode_controller";
+
+        /**
          * Key for disabling screen wake locks while apps are in cached state.
          * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)}
          * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace.
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index d6df033..94bff89 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -413,6 +413,15 @@
     public abstract HostUsiVersion getHostUsiVersion(int displayId);
 
     /**
+     * Get the ALS data for a particular display.
+     *
+     * @param displayId The id of the display.
+     * @return {@link AmbientLightSensorData}
+     */
+    @Nullable
+    public abstract AmbientLightSensorData getAmbientLightSensorData(int displayId);
+
+    /**
      * Get all available DisplayGroupIds.
      */
     public abstract IntArray getDisplayGroupIds();
@@ -669,4 +678,23 @@
             return "RefreshRateLimitation(" + type + ": " + range + ")";
         }
     }
+
+    /**
+     * Class to provide Ambient sensor data using the API
+     * {@link DisplayManagerInternal#getAmbientLightSensorData(int)}
+     */
+    public static final class AmbientLightSensorData {
+        public String sensorName;
+        public String sensorType;
+
+        public AmbientLightSensorData(String name, String type) {
+            sensorName = name;
+            sensorType = type;
+        }
+
+        @Override
+        public String toString() {
+            return "AmbientLightSensorData(" + sensorName + ", " + sensorType + ")";
+        }
+    }
 }
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 9d5073e..7080133 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -177,5 +177,5 @@
     // Internal operation used to clear face biometric scheduler.
     // Ensures that the scheduler is not stuck.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void scheduleWatchdog();
+    oneway void scheduleWatchdog();
 }
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 6195443..f594377 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -980,23 +980,6 @@
     }
 
     /**
-     * @hide
-     */
-    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
-    public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
-        if (mService == null) {
-            Slog.w(TAG, "setUdfpsOverlay: no fingerprint service");
-            return;
-        }
-
-        try {
-            mService.setUdfpsOverlay(controller);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Forwards BiometricStateListener to FingerprintService
      * @param listener new BiometricStateListener being added
      * @hide
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index ff2f313..e2840ec 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -27,7 +27,6 @@
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -203,10 +202,6 @@
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void setSidefpsController(in ISidefpsController controller);
 
-    // Sets the controller for managing the UDFPS overlay.
-    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void setUdfpsOverlay(in IUdfpsOverlay controller);
-
     // Registers BiometricStateListener.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void registerBiometricStateListener(IBiometricStateListener listener);
@@ -218,5 +213,5 @@
     // Internal operation used to clear fingerprint biometric scheduler.
     // Ensures that the scheduler is not stuck.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
-    void scheduleWatchdog();
+    oneway void scheduleWatchdog();
 }
diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
deleted file mode 100644
index c99fccc..0000000
--- a/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
+++ /dev/null
@@ -1,29 +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 android.hardware.fingerprint;
-
-/**
- * Interface for interacting with the under-display fingerprint sensor (UDFPS) overlay.
- * @hide
- */
-oneway interface IUdfpsOverlay {
-    // Shows the overlay.
-    void show(long requestId, int sensorId, int reason);
-
-    // Hides the overlay.
-    void hide(int sensorId);
-}
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 17bbe14..33960c0 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -266,7 +266,7 @@
      */
     public static boolean useTouchpadTapToClick(@NonNull Context context) {
         return Settings.System.getIntForUser(context.getContentResolver(),
-                Settings.System.TOUCHPAD_TAP_TO_CLICK, 0, UserHandle.USER_CURRENT) == 1;
+                Settings.System.TOUCHPAD_TAP_TO_CLICK, 1, UserHandle.USER_CURRENT) == 1;
     }
 
     /**
diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java
index 0311da4..4403251 100644
--- a/core/java/android/hardware/input/KeyboardLayout.java
+++ b/core/java/android/hardware/input/KeyboardLayout.java
@@ -73,7 +73,7 @@
     private final int mProductId;
 
     /** Currently supported Layout types in the KCM files */
-    private enum LayoutType {
+    public enum LayoutType {
         UNDEFINED(0, LAYOUT_TYPE_UNDEFINED),
         QWERTY(1, LAYOUT_TYPE_QWERTY),
         QWERTZ(2, LAYOUT_TYPE_QWERTZ),
@@ -88,9 +88,11 @@
         private final int mValue;
         private final String mName;
         private static final Map<Integer, LayoutType> VALUE_TO_ENUM_MAP = new HashMap<>();
+        private static final Map<String, LayoutType> NAME_TO_ENUM_MAP = new HashMap<>();
         static {
             for (LayoutType type : LayoutType.values()) {
                 VALUE_TO_ENUM_MAP.put(type.mValue, type);
+                NAME_TO_ENUM_MAP.put(type.mName, type);
             }
         }
 
@@ -110,6 +112,25 @@
         private String getName() {
             return mName;
         }
+
+        /**
+         * Returns enum value for provided layout type
+         * @param layoutName name of the layout type
+         * @return int value corresponding to the LayoutType enum that matches the layout name.
+         * (LayoutType.UNDEFINED if no match found)
+         */
+        public static int getLayoutTypeEnumValue(String layoutName) {
+            return NAME_TO_ENUM_MAP.getOrDefault(layoutName, UNDEFINED).getValue();
+        }
+
+        /**
+         * Returns name for provided layout type enum value
+         * @param enumValue value representation for LayoutType enum
+         * @return Layout name corresponding to the enum value (LAYOUT_TYPE_UNDEFINED if not found)
+         */
+        public static String getLayoutNameFromValue(int enumValue) {
+            return VALUE_TO_ENUM_MAP.getOrDefault(enumValue, UNDEFINED).getName();
+        }
     }
 
     @NonNull
diff --git a/core/java/android/hardware/lights/Light.java b/core/java/android/hardware/lights/Light.java
index 1df9b75..18d0b09 100644
--- a/core/java/android/hardware/lights/Light.java
+++ b/core/java/android/hardware/lights/Light.java
@@ -110,6 +110,8 @@
     private final int mOrdinal;
     private final int mType;
     private final int mCapabilities;
+    @Nullable
+    private final int[] mPreferredBrightnessLevels;
 
     /**
      * Creates a new light with the given data.
@@ -117,7 +119,7 @@
      * @hide
      */
     public Light(int id, int ordinal, int type) {
-        this(id, "Light", ordinal, type, 0);
+        this(id, "Light", ordinal, type, 0, null);
     }
 
     /**
@@ -126,11 +128,22 @@
      * @hide
      */
     public Light(int id, String name, int ordinal, int type, int capabilities) {
+        this(id, name, ordinal, type, capabilities, null);
+    }
+
+    /**
+     * Creates a new light with the given data.
+     *
+     * @hide
+     */
+    public Light(int id, String name, int ordinal, int type, int capabilities,
+            @Nullable int[] preferredBrightnessLevels) {
         mId = id;
         mName = name;
         mOrdinal = ordinal;
         mType = type;
         mCapabilities = capabilities;
+        mPreferredBrightnessLevels = preferredBrightnessLevels;
     }
 
     private Light(@NonNull Parcel in) {
@@ -139,6 +152,7 @@
         mOrdinal = in.readInt();
         mType = in.readInt();
         mCapabilities = in.readInt();
+        mPreferredBrightnessLevels = in.createIntArray();
     }
 
     /** Implement the Parcelable interface */
@@ -149,6 +163,7 @@
         dest.writeInt(mOrdinal);
         dest.writeInt(mType);
         dest.writeInt(mCapabilities);
+        dest.writeIntArray(mPreferredBrightnessLevels);
     }
 
     /** Implement the Parcelable interface */
@@ -252,4 +267,17 @@
         return (mCapabilities & LIGHT_CAPABILITY_COLOR_RGB) == LIGHT_CAPABILITY_COLOR_RGB;
     }
 
+    /**
+     * Returns preferred brightness levels for the light which will be used when user
+     * increase/decrease brightness levels for the light (currently only used for Keyboard
+     * backlight control using backlight up/down keys).
+     *
+     * The values in the preferred brightness level array are in the range [0, 255].
+     *
+     * @hide
+     */
+    @Nullable
+    public int[] getPreferredBrightnessLevels() {
+        return mPreferredBrightnessLevels;
+    }
 }
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 70b72c8..b99996ff 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -433,7 +433,7 @@
     @BinderThread
     @Override
     public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
-            int flags, ResultReceiver resultReceiver) {
+            @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
         ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
         mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT,
                 flags, showInputToken, resultReceiver, statsToken));
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index a9c4818..795eb4a 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -103,11 +103,10 @@
 import android.util.Printer;
 import android.util.Xml;
 import android.util.proto.ProtoOutputStream;
-import android.view.BatchedInputEventReceiver.SimpleBatchedInputEventReceiver;
-import android.view.Choreographer;
 import android.view.Gravity;
 import android.view.InputChannel;
 import android.view.InputDevice;
+import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
@@ -607,6 +606,7 @@
     InputConnection mStartedInputConnection;
     EditorInfo mInputEditorInfo;
 
+    @InputMethod.ShowFlags
     int mShowInputFlags;
     boolean mShowInputRequested;
     boolean mLastShowInputRequested;
@@ -807,9 +807,11 @@
             onUnbindInput();
             mInputBinding = null;
             mInputConnection = null;
-            // free-up cached InkWindow surface on detaching from current client.
+
             if (mInkWindow != null) {
-                removeHandwritingInkWindow();
+                finishStylusHandwriting();
+                // free-up InkWindow surface after timeout.
+                scheduleStylusWindowIdleTimeout();
             }
         }
 
@@ -931,8 +933,9 @@
          */
         @MainThread
         @Override
-        public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
-                IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
+        public void showSoftInputWithToken(@InputMethod.ShowFlags int flags,
+                ResultReceiver resultReceiver, IBinder showInputToken,
+                @Nullable ImeTracker.Token statsToken) {
             mSystemCallingShowSoftInput = true;
             mCurShowInputToken = showInputToken;
             mCurStatsToken = statsToken;
@@ -950,7 +953,7 @@
          */
         @MainThread
         @Override
-        public void showSoftInput(int flags, ResultReceiver resultReceiver) {
+        public void showSoftInput(@InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
             ImeTracker.forLogging().onProgress(
                     mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
             if (DEBUG) Log.v(TAG, "showSoftInput()");
@@ -1016,9 +1019,11 @@
             if (!mOnPreparedStylusHwCalled) {
                 // prepare hasn't been called by Stylus HOVER.
                 onPrepareStylusHandwriting();
-                mOnPreparedStylusHwCalled = true;
             }
+            // reset flag as it's not relevant after onStartStylusHandwriting().
+            mOnPreparedStylusHwCalled = false;
             if (onStartStylusHandwriting()) {
+                cancelStylusWindowIdleTimeout();
                 mPrivOps.onStylusHandwritingReady(requestId, Process.myPid());
             } else {
                 Log.i(TAG, "IME is not ready. Can't start Stylus Handwriting");
@@ -1052,17 +1057,22 @@
             stylusEvents.forEach(InputMethodService.this::onStylusHandwritingMotionEvent);
 
             // create receiver for channel
-            mHandwritingEventReceiver = new SimpleBatchedInputEventReceiver(
-                    channel,
-                    Looper.getMainLooper(), Choreographer.getInstance(),
-                    event -> {
+            mHandwritingEventReceiver = new InputEventReceiver(channel, Looper.getMainLooper()) {
+                @Override
+                public void onInputEvent(InputEvent event) {
+                    boolean handled = false;
+                    try {
                         if (!(event instanceof MotionEvent)) {
-                            return false;
+                            return;
                         }
                         onStylusHandwritingMotionEvent((MotionEvent) event);
                         scheduleHandwritingSessionTimeout();
-                        return true;
-                    });
+                        handled = true;
+                    } finally {
+                        finishInputEvent(event, handled);
+                    }
+                }
+            };
             scheduleHandwritingSessionTimeout();
         }
 
@@ -1103,7 +1113,7 @@
          */
         @Override
         public void removeStylusHandwritingWindow() {
-            InputMethodService.this.removeStylusHandwritingWindow();
+            InputMethodService.this.finishAndRemoveStylusHandwritingWindow();
         }
 
         /**
@@ -1321,7 +1331,8 @@
          * InputMethodService#requestShowSelf} or {@link InputMethodService#requestHideSelf}
          */
         @Deprecated
-        public void toggleSoftInput(int showFlags, int hideFlags) {
+        public void toggleSoftInput(@InputMethodManager.ShowFlags int showFlags,
+                @InputMethodManager.HideFlags int hideFlags) {
             InputMethodService.this.onToggleSoftInput(showFlags, hideFlags);
         }
 
@@ -2660,21 +2671,15 @@
      * Typically, this is called when {@link InkWindow} should no longer be holding a surface in
      * memory.
      */
-    private void removeStylusHandwritingWindow() {
+    private void finishAndRemoveStylusHandwritingWindow() {
+        cancelStylusWindowIdleTimeout();
+        mOnPreparedStylusHwCalled = false;
+        mStylusWindowIdleTimeoutRunnable = null;
         if (mInkWindow != null) {
             if (mHandwritingRequestId.isPresent()) {
                 // if handwriting session is still ongoing. This shouldn't happen.
                 finishStylusHandwriting();
             }
-            removeHandwritingInkWindow();
-        }
-    }
-
-    private void removeHandwritingInkWindow() {
-        cancelStylusWindowIdleTimeout();
-        mOnPreparedStylusHwCalled = false;
-        mStylusWindowIdleTimeoutRunnable = null;
-        if (mInkWindow != null) {
             mInkWindow.hide(true /* remove */);
             mInkWindow.destroy();
             mInkWindow = null;
@@ -2700,7 +2705,7 @@
     private Runnable getStylusWindowIdleTimeoutRunnable() {
         if (mStylusWindowIdleTimeoutRunnable == null) {
             mStylusWindowIdleTimeoutRunnable = () -> {
-                removeHandwritingInkWindow();
+                finishAndRemoveStylusHandwritingWindow();
                 mStylusWindowIdleTimeoutRunnable = null;
             };
         }
@@ -2793,18 +2798,16 @@
      * {@link #onEvaluateInputViewShown()}, {@link #onEvaluateFullscreenMode()},
      * and the current configuration to decide whether the input view should
      * be shown at this point.
-     * 
-     * @param flags Provides additional information about the show request,
-     * as per {@link InputMethod#showSoftInput InputMethod.showSoftInput()}.
+     *
      * @param configChange This is true if we are re-showing due to a
      * configuration change.
      * @return Returns true to indicate that the window should be shown.
      */
-    public boolean onShowInputRequested(int flags, boolean configChange) {
+    public boolean onShowInputRequested(@InputMethod.ShowFlags int flags, boolean configChange) {
         if (!onEvaluateInputViewShown()) {
             return false;
         }
-        if ((flags&InputMethod.SHOW_EXPLICIT) == 0) {
+        if ((flags & InputMethod.SHOW_EXPLICIT) == 0) {
             if (!configChange && onEvaluateFullscreenMode() && !isInputViewShown()) {
                 // Don't show if this is not explicitly requested by the user and
                 // the input method is fullscreen unless it is already shown. That
@@ -2830,14 +2833,14 @@
      * exposed to IME authors as an overridable public method without {@code @CallSuper}, we have
      * to have this method to ensure that those internal states are always updated no matter how
      * {@link #onShowInputRequested(int, boolean)} is overridden by the IME author.
-     * @param flags Provides additional information about the show request,
-     * as per {@link InputMethod#showSoftInput InputMethod.showSoftInput()}.
+     *
      * @param configChange This is true if we are re-showing due to a
      * configuration change.
      * @return Returns true to indicate that the window should be shown.
      * @see #onShowInputRequested(int, boolean)
      */
-    private boolean dispatchOnShowInputRequested(int flags, boolean configChange) {
+    private boolean dispatchOnShowInputRequested(@InputMethod.ShowFlags int flags,
+            boolean configChange) {
         final boolean result = onShowInputRequested(flags, configChange);
         mInlineSuggestionSessionController.notifyOnShowInputRequested(result);
         if (result) {
@@ -2981,8 +2984,6 @@
         ImeTracing.getInstance().triggerServiceDump(
                 "InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", mDumper,
                 null /* icProto */);
-        ImeTracker.forLogging().onProgress(mCurStatsToken,
-                ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
         mPrivOps.applyImeVisibilityAsync(setVisible
                 ? mCurShowInputToken : mCurHideInputToken, setVisible, mCurStatsToken);
     }
@@ -3089,7 +3090,8 @@
         mInputStarted = false;
         mStartedInputConnection = null;
         mCurCompletions = null;
-        if (mInkWindow != null) {
+        if (!mOnPreparedStylusHwCalled) {
+            // If IME didn't prepare to show InkWindow for current handwriting session.
             finishStylusHandwriting();
         }
         // Back callback is typically unregistered in {@link #hideWindow()}, but it's possible
@@ -3270,16 +3272,13 @@
      *
      * The input method will continue running, but the user can no longer use it to generate input
      * by touching the screen.
-     *
-     * @see InputMethodManager#HIDE_IMPLICIT_ONLY
-     * @see InputMethodManager#HIDE_NOT_ALWAYS
-     * @param flags Provides additional operating flags.
      */
-    public void requestHideSelf(int flags) {
+    public void requestHideSelf(@InputMethodManager.HideFlags int flags) {
         requestHideSelf(flags, SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_IME);
     }
 
-    private void requestHideSelf(int flags, @SoftInputShowHideReason int reason) {
+    private void requestHideSelf(@InputMethodManager.HideFlags int flags,
+            @SoftInputShowHideReason int reason) {
         ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestHideSelf", mDumper,
                 null /* icProto */);
         mPrivOps.hideMySoftInput(flags, reason);
@@ -3288,12 +3287,8 @@
     /**
      * Show the input method's soft input area, so the user sees the input method window and can
      * interact with it.
-     *
-     * @see InputMethodManager#SHOW_IMPLICIT
-     * @see InputMethodManager#SHOW_FORCED
-     * @param flags Provides additional operating flags.
      */
-    public final void requestShowSelf(int flags) {
+    public final void requestShowSelf(@InputMethodManager.ShowFlags int flags) {
         ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestShowSelf", mDumper,
                 null /* icProto */);
         mPrivOps.showMySoftInput(flags);
@@ -3453,7 +3448,8 @@
     /**
      * Handle a request by the system to toggle the soft input area.
      */
-    private void onToggleSoftInput(int showFlags, int hideFlags) {
+    private void onToggleSoftInput(@InputMethodManager.ShowFlags int showFlags,
+            @InputMethodManager.HideFlags int hideFlags) {
         if (DEBUG) Log.v(TAG, "toggleSoftInput()");
         if (isInputViewShown()) {
             requestHideSelf(
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 5704dac..e4a09a6 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -79,6 +79,13 @@
     @WindowState
     private int mWindowState = WindowState.TOKEN_PENDING;
 
+    @Override
+    protected boolean allowsRegisterDefaultOnBackInvokedCallback() {
+        // Do not register OnBackInvokedCallback from Dialog#onStart, InputMethodService will
+        // register CompatOnBackInvokedCallback for input method window.
+        return false;
+    }
+
     /**
      * Set {@link IBinder} window token to the window.
      *
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 00676f3..01e8fea 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -926,16 +926,19 @@
      * @hide
      */
     @VisibleForTesting
-    public final @NonNull String getTransactionTraceName(int transactionCode) {
+    public final @Nullable String getTransactionTraceName(int transactionCode) {
+        final boolean isInterfaceUserDefined = getMaxTransactionId() == 0;
         if (mTransactionTraceNames == null) {
-            final int highestId = Math.min(getMaxTransactionId(), TRANSACTION_TRACE_NAME_ID_LIMIT);
+            final int highestId = isInterfaceUserDefined ? TRANSACTION_TRACE_NAME_ID_LIMIT
+                    : Math.min(getMaxTransactionId(), TRANSACTION_TRACE_NAME_ID_LIMIT);
             mSimpleDescriptor = getSimpleDescriptor();
             mTransactionTraceNames = new AtomicReferenceArray(highestId + 1);
         }
 
-        final int index = transactionCode - FIRST_CALL_TRANSACTION;
-        if (index < 0 || index >= mTransactionTraceNames.length()) {
-            return mSimpleDescriptor + "#" + transactionCode;
+        final int index = isInterfaceUserDefined
+                ? transactionCode : transactionCode - FIRST_CALL_TRANSACTION;
+        if (index >= mTransactionTraceNames.length() || index < 0) {
+            return null;
         }
 
         String transactionTraceName = mTransactionTraceNames.getAcquire(index);
@@ -1300,19 +1303,9 @@
         final boolean hasFullyQualifiedName = getMaxTransactionId() > 0;
         final String transactionTraceName;
 
-        if (tagEnabled && hasFullyQualifiedName) {
+        if (tagEnabled) {
             // If tracing enabled and we have a fully qualified name, fetch the name
             transactionTraceName = getTransactionTraceName(code);
-        } else if (tagEnabled && isStackTrackingEnabled()) {
-            // If tracing is enabled and we *don't* have a fully qualified name, fetch the
-            // 'best effort' name only for stack tracking. This works around noticeable perf impact
-            // on low latency binder calls (<100us). The tracing call itself is between (1-10us) and
-            // the perf impact can be quite noticeable while benchmarking such binder calls.
-            // The primary culprits are ContentProviders and Cursors which convenienty don't
-            // autogenerate their AIDL and hence will not have a fully qualified name.
-            //
-            // TODO(b/253426478): Relax this constraint after a more robust fix
-            transactionTraceName = getTransactionTraceName(code);
         } else {
             transactionTraceName = null;
         }
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 94971b8..299e7f1 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -115,6 +115,9 @@
     private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default";
     private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle";
     private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native";
+    private static final String SYSTEM_ANGLE_STRING = "system";
+
+    private static final String PROPERTY_RO_ANGLE_SUPPORTED = "ro.gfx.angle.supported";
 
     private ClassLoader mClassLoader;
     private String mLibrarySearchPaths;
@@ -123,6 +126,7 @@
 
     private int mAngleOptInIndex = -1;
     private boolean mEnabledByGameMode = false;
+    private boolean mShouldUseAngle = false;
 
     /**
      * Set up GraphicsEnvironment
@@ -141,19 +145,16 @@
 
         // Setup ANGLE and pass down ANGLE details to the C++ code
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle");
-        boolean useAngle = false;
         if (setupAngle(context, coreSettings, pm, packageName)) {
-            if (shouldUseAngle(context, coreSettings, packageName)) {
-                useAngle = true;
-                setGpuStats(ANGLE_DRIVER_NAME, ANGLE_DRIVER_VERSION_NAME, ANGLE_DRIVER_VERSION_CODE,
-                        0, packageName, getVulkanVersion(pm));
-            }
+            mShouldUseAngle = true;
+            setGpuStats(ANGLE_DRIVER_NAME, ANGLE_DRIVER_VERSION_NAME, ANGLE_DRIVER_VERSION_CODE,
+                    0, packageName, getVulkanVersion(pm));
         }
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
 
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "chooseDriver");
         if (!chooseDriver(context, coreSettings, pm, packageName, appInfoWithMetaData)) {
-            if (!useAngle) {
+            if (!mShouldUseAngle) {
                 setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME,
                         SYSTEM_DRIVER_VERSION_CODE,
                         SystemProperties.getLong(PROPERTY_GFX_DRIVER_BUILD_TIME, 0),
@@ -195,15 +196,16 @@
     }
 
     /**
-     * Query to determine if ANGLE should be used
+     * Query to determine the ANGLE driver choice.
      */
-    private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) {
+    private String queryAngleChoice(Context context, Bundle coreSettings,
+                                               String packageName) {
         if (TextUtils.isEmpty(packageName)) {
             Log.v(TAG, "No package name specified; use the system driver");
-            return false;
+            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
 
-        return shouldUseAngleInternal(context, coreSettings, packageName);
+        return queryAngleChoiceInternal(context, coreSettings, packageName);
     }
 
     private int getVulkanVersion(PackageManager pm) {
@@ -424,10 +426,11 @@
      *    forces a choice;
      * 3) Use ANGLE if isAngleEnabledByGameMode() returns true;
      */
-    private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) {
+    private String queryAngleChoiceInternal(Context context, Bundle bundle,
+                                                       String packageName) {
         // Make sure we have a good package name
         if (TextUtils.isEmpty(packageName)) {
-            return false;
+            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
 
         // Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE
@@ -442,7 +445,7 @@
         }
         if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) {
             Log.v(TAG, "Turn on ANGLE for all applications.");
-            return true;
+            return ANGLE_GL_DRIVER_CHOICE_ANGLE;
         }
 
         // Get the per-application settings lists
@@ -465,7 +468,8 @@
                             + optInPackages.size() + ", "
                         + "number of values: "
                             + optInValues.size());
-            return mEnabledByGameMode;
+            return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE
+                    : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
 
         // See if this application is listed in the per-application settings list
@@ -473,7 +477,8 @@
 
         if (pkgIndex < 0) {
             Log.v(TAG, packageName + " is not listed in per-application setting");
-            return mEnabledByGameMode;
+            return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE
+                    : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
         mAngleOptInIndex = pkgIndex;
 
@@ -484,13 +489,14 @@
                 "ANGLE Developer option for '" + packageName + "' "
                         + "set to: '" + optInValue + "'");
         if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) {
-            return true;
+            return ANGLE_GL_DRIVER_CHOICE_ANGLE;
         } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
-            return false;
+            return ANGLE_GL_DRIVER_CHOICE_NATIVE;
         } else {
             // The user either chose default or an invalid value; go with the default driver or what
             // the game mode indicates
-            return mEnabledByGameMode;
+            return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE
+                    : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
     }
 
@@ -503,10 +509,12 @@
         final List<ResolveInfo> resolveInfos =
                 pm.queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY);
         if (resolveInfos.size() != 1) {
-            Log.e(TAG, "Invalid number of ANGLE packages. Required: 1, Found: "
+            Log.v(TAG, "Invalid number of ANGLE packages. Required: 1, Found: "
                     + resolveInfos.size());
-            for (ResolveInfo resolveInfo : resolveInfos) {
-                Log.e(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName);
+            if (DEBUG) {
+                for (ResolveInfo resolveInfo : resolveInfos) {
+                    Log.d(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName);
+                }
             }
             return "";
         }
@@ -541,26 +549,46 @@
     }
 
     /**
-     * Determine whether ANGLE should be used, set it up if so, and pass ANGLE details down to
-     * the C++ GraphicsEnv class.
+     * Determine whether ANGLE should be used, attempt to set up from apk first, if ANGLE can be
+     * set up from apk, pass ANGLE details down to the C++ GraphicsEnv class via
+     * GraphicsEnv::setAngleInfo(). If apk setup fails, attempt to set up to use system ANGLE.
+     * Return false if both fail.
      *
-     * If ANGLE will be used, GraphicsEnv::setAngleInfo() will be called to enable ANGLE to be
-     * properly used.
-     *
-     * @param context
-     * @param bundle
-     * @param pm
+     * @param context - Context of the application.
+     * @param bundle - Bundle of the application.
+     * @param packageManager - PackageManager of the application process.
      * @param packageName - package name of the application.
-     * @return true: ANGLE setup successfully
-     *         false: ANGLE not setup (not on allowlist, ANGLE not present, etc.)
+     * @return true: can set up to use ANGLE successfully.
+     *         false: can not set up to use ANGLE (not on allowlist, ANGLE not present, etc.)
      */
-    private boolean setupAngle(Context context, Bundle bundle, PackageManager pm,
+    private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager,
             String packageName) {
-
-        if (!shouldUseAngle(context, bundle, packageName)) {
+        final String angleChoice = queryAngleChoice(context, bundle, packageName);
+        if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_DEFAULT)) {
+            return false;
+        }
+        if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
+            nativeSetAngleInfo("", true, packageName, null);
             return false;
         }
 
+        return setupAngleFromApk(context, bundle, packageManager, packageName)
+                || setupAngleFromSystem(context, bundle, packageName);
+    }
+
+    /**
+     * Attempt to set up ANGLE from the packaged apk, if the apk can be found, pass ANGLE details to
+     * the C++ GraphicsEnv class.
+     *
+     * @param context - Context of the application.
+     * @param bundle - Bundle of the application.
+     * @param packageManager - PackageManager of the application process.
+     * @param packageName - package name of the application.
+     * @return true: can set up to use ANGLE apk.
+     *         false: can not set up to use ANGLE apk (ANGLE apk not present, etc.)
+     */
+    private boolean setupAngleFromApk(Context context, Bundle bundle, PackageManager packageManager,
+            String packageName) {
         ApplicationInfo angleInfo = null;
 
         // If the developer has specified a debug package over ADB, attempt to find it
@@ -569,7 +597,7 @@
             Log.v(TAG, "ANGLE debug package enabled: " + anglePkgName);
             try {
                 // Note the debug package does not have to be pre-installed
-                angleInfo = pm.getApplicationInfo(anglePkgName, 0);
+                angleInfo = packageManager.getApplicationInfo(anglePkgName, 0);
             } catch (PackageManager.NameNotFoundException e) {
                 // If the debug package is specified but not found, abort.
                 Log.v(TAG, "ANGLE debug package '" + anglePkgName + "' not installed");
@@ -579,7 +607,7 @@
 
         // Otherwise, check to see if ANGLE is properly installed
         if (angleInfo == null) {
-            anglePkgName = getAnglePackageName(pm);
+            anglePkgName = getAnglePackageName(packageManager);
             if (TextUtils.isEmpty(anglePkgName)) {
                 Log.v(TAG, "Failed to find ANGLE package.");
                 return false;
@@ -588,7 +616,7 @@
             Log.v(TAG, "ANGLE package enabled: " + anglePkgName);
             try {
                 // Production ANGLE libraries must be pre-installed as a system app
-                angleInfo = pm.getApplicationInfo(anglePkgName,
+                angleInfo = packageManager.getApplicationInfo(anglePkgName,
                         PackageManager.MATCH_SYSTEM_ONLY);
             } catch (PackageManager.NameNotFoundException e) {
                 Log.v(TAG, "ANGLE package '" + anglePkgName + "' not installed");
@@ -609,15 +637,39 @@
             Log.d(TAG, "ANGLE package libs: " + paths);
         }
 
-        // If we make it to here, ANGLE will be used.  Call setAngleInfo() with the package name,
-        // and features to use.
+        // If we make it to here, ANGLE apk will be used.  Call nativeSetAngleInfo() with the
+        // application package name and ANGLE features to use.
         final String[] features = getAngleEglFeatures(context, bundle);
-        setAngleInfo(paths, packageName, ANGLE_GL_DRIVER_CHOICE_ANGLE, features);
+        nativeSetAngleInfo(paths, false, packageName, features);
 
         return true;
     }
 
     /**
+     * Attempt to set up ANGLE from system, if the apk can be found, pass ANGLE details to
+     * the C++ GraphicsEnv class.
+     *
+     * @param context - Context of the application.
+     * @param bundle - Bundle of the application.
+     * @param packageName - package name of the application.
+     * @return true: can set up to use system ANGLE.
+     *         false: can not set up to use system ANGLE because it doesn't exist.
+     */
+    private boolean setupAngleFromSystem(Context context, Bundle bundle, String packageName) {
+        final boolean systemAngleSupported = SystemProperties
+                                             .getBoolean(PROPERTY_RO_ANGLE_SUPPORTED, false);
+        if (!systemAngleSupported) {
+            return false;
+        }
+
+        // If we make it to here, system ANGLE will be used.  Call nativeSetAngleInfo() with
+        // the application package name and ANGLE features to use.
+        final String[] features = getAngleEglFeatures(context, bundle);
+        nativeSetAngleInfo(SYSTEM_ANGLE_STRING, false, packageName, features);
+        return true;
+    }
+
+    /**
      * Determine if the "ANGLE In Use" dialog box should be shown.
      */
     private boolean shouldShowAngleInUseDialogBox(Context context) {
@@ -636,44 +688,37 @@
     }
 
     /**
-     * Determine if ANGLE will be used and setup the environment
-     */
-    private boolean setupAndUseAngle(Context context, String packageName) {
-        // Need to make sure we are evaluating ANGLE usage for the correct circumstances
-        if (!setupAngle(context, null, context.getPackageManager(), packageName)) {
-            Log.v(TAG, "Package '" + packageName + "' should not use ANGLE");
-            return false;
-        }
-
-        final boolean useAngle = getShouldUseAngle(packageName);
-        Log.v(TAG, "Package '" + packageName + "' should use ANGLE = '" + useAngle + "'");
-
-        return useAngle;
-    }
-
-    /**
-     * Show the ANGLE in Use Dialog Box
+     * Show the ANGLE in use dialog box.
+     * The ANGLE in use dialog box will show up as long as the application
+     * should use ANGLE. It does not mean the application has successfully
+     * loaded ANGLE because this check happens before the loading completes.
      * @param context
      */
     public void showAngleInUseDialogBox(Context context) {
-        final String packageName = context.getPackageName();
-
-        if (shouldShowAngleInUseDialogBox(context) && setupAndUseAngle(context, packageName)) {
-            final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE);
-            String anglePkg = getAnglePackageName(context.getPackageManager());
-            intent.setPackage(anglePkg);
-
-            context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    Bundle results = getResultExtras(true);
-
-                    String toastMsg = results.getString(INTENT_KEY_A4A_TOAST_MESSAGE);
-                    final Toast toast = Toast.makeText(context, toastMsg, Toast.LENGTH_LONG);
-                    toast.show();
-                }
-            }, null, Activity.RESULT_OK, null, null);
+        if (!shouldShowAngleInUseDialogBox(context)) {
+            return;
         }
+
+        if (!mShouldUseAngle) {
+            return;
+        }
+
+        final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE);
+        final String anglePkg = getAnglePackageName(context.getPackageManager());
+        if (anglePkg.isEmpty()) {
+            return;
+        }
+
+        context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final Bundle results = getResultExtras(true);
+
+                final String toastMsg = results.getString(INTENT_KEY_A4A_TOAST_MESSAGE);
+                final Toast toast = Toast.makeText(context, toastMsg, Toast.LENGTH_LONG);
+                toast.show();
+            }
+        }, null, Activity.RESULT_OK, null, null);
     }
 
     private String[] getAngleEglFeatures(Context context, Bundle coreSettings) {
@@ -901,9 +946,8 @@
     private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries);
     private static native void setGpuStats(String driverPackageName, String driverVersionName,
             long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
-    private static native void setAngleInfo(String path, String appPackage,
-            String devOptIn, String[] features);
-    private static native boolean getShouldUseAngle(String packageName);
+    private static native void nativeSetAngleInfo(String path, boolean useNativeDriver,
+            String packageName, String[] features);
     private static native boolean setInjectLayersPrSetDumpable();
     private static native void nativeToggleAngleAsSystemDriver(boolean enabled);
 
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index cc54266..5c4aa4a 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -52,8 +52,20 @@
       ],
       "name": "FrameworksServicesTests",
       "options": [
-        { "include-filter": "com.android.server.am.BatteryStatsServiceTest" },
-        { "include-filter": "com.android.server.power.stats.BatteryStatsTests" }
+        { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }
+      ]
+    },
+    {
+      "file_patterns": [
+        "BatteryStats[^/]*\\.java",
+        "BatteryUsageStats[^/]*\\.java",
+        "PowerComponents\\.java",
+        "[^/]*BatteryConsumer[^/]*\\.java"
+      ],
+      "name": "FrameworksServicesTests",
+      "options": [
+        { "include-filter": "com.android.server.power.stats" },
+        { "exclude-filter": "com.android.server.power.stats.BatteryStatsTests" }
       ]
     },
     {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 84f1880..2d21718 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1764,10 +1764,19 @@
     /**
      * Specifies if a user is not allowed to use 2g networks.
      *
+     * <p> This is a security feature. 2g has no mutual authentication between a device and
+     * cellular base station and downgrading a device's connection to 2g is a common tactic for
+     * several types of privacy and security compromising attacks that could allow an adversary
+     * to intercept, inject, or modify cellular communications.
+     *
      * <p>This restriction can only be set by a device owner or a profile owner of an
      * organization-owned managed profile on the parent profile.
-     * In all cases, the setting applies globally on the device and will prevent the device from
-     * scanning for or connecting to 2g networks, except in the case of an emergency.
+     * In all cases, the setting applies globally on the device.
+     *
+     * <p> Cellular connectivity loss (where a device would have otherwise successfully
+     * connected to a 2g network) occurs if the device is in an area where only 2g networks are
+     * available. Emergency calls are an exception and are never impacted. The device will still
+     * scan for and connect to a 2g network for emergency calls.
      *
      * <p>Holders of the permission
      * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 218ecc8..88096ab 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -209,6 +209,13 @@
     public static final String ACTION_HIDE_NOTIFICATION =
             "android.os.image.action.HIDE_NOTIFICATION";
 
+    /**
+     * Intent action: notify the service to post a status update when keyguard is dismissed.
+     * @hide
+     */
+    public static final String ACTION_NOTIFY_KEYGUARD_DISMISSED =
+            "android.os.image.action.NOTIFY_KEYGUARD_DISMISSED";
+
     /*
      * Intent Keys
      */
diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java
index 9610b16..536795b 100644
--- a/core/java/android/os/image/DynamicSystemManager.java
+++ b/core/java/android/os/image/DynamicSystemManager.java
@@ -285,4 +285,16 @@
             throw new RuntimeException(e.toString());
         }
     }
+
+    /**
+     * Returns the active DSU slot
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+    public String getActiveDsuSlot() {
+        try {
+            return mService.getActiveDsuSlot();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
 }
diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl
index 755368a..0280ebb 100644
--- a/core/java/android/os/image/IDynamicSystemService.aidl
+++ b/core/java/android/os/image/IDynamicSystemService.aidl
@@ -145,4 +145,10 @@
      */
     @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
     long suggestScratchSize();
+
+    /**
+     * Get the active DSU slot
+     */
+    @EnforcePermission("MANAGE_DYNAMIC_SYSTEM")
+    String getActiveDsuSlot();
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d695c0c..1235b78 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2107,6 +2107,21 @@
             "android.settings.MANAGE_MORE_DEFAULT_APPS_SETTINGS";
 
     /**
+     * Activity Action: Show app screen size list settings for user to override app aspect
+     * ratio.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Can include the following extra {@link android.content.Intent#EXTRA_PACKAGE_NAME} specifying
+     * the name of the package to scroll to in the page.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_USER_ASPECT_RATIO_SETTINGS =
+            "android.settings.MANAGE_USER_ASPECT_RATIO_SETTINGS";
+
+    /**
      * Activity Action: Show notification settings.
      *
      * @hide
@@ -2186,6 +2201,16 @@
             = "android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS";
 
     /**
+     * Intent Extra: The value of {@link android.app.settings.SettingsEnums#EntryPointType} for
+     * settings metrics that logs the entry point about physical keyboard settings.
+     * <p>
+     * This must be passed as an extra field to the {@link #ACTION_HARD_KEYBOARD_SETTINGS}.
+     * @hide
+     */
+    public static final String EXTRA_ENTRYPOINT =
+            "com.android.settings.inputmethod.EXTRA_ENTRYPOINT";
+
+    /**
      * Activity Extra: The package owner of the notification channel settings to display.
      * <p>
      * This must be passed as an extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}.
@@ -4699,6 +4724,15 @@
         public static final String PEAK_REFRESH_RATE = "peak_refresh_rate";
 
         /**
+         * Control whether to stay awake on fold
+         *
+         * If this isn't set, the system falls back to a device specific default.
+         * @hide
+         */
+        @Readable
+        public static final String STAY_AWAKE_ON_FOLD = "stay_awake_on_fold";
+
+        /**
          * The amount of time in milliseconds before the device goes to sleep or begins
          * to dream after a period of inactivity.  This value is also known as the
          * user activity timeout period since the screen isn't necessarily turned off
@@ -7486,6 +7520,14 @@
         public static final String STYLUS_BUTTONS_ENABLED = "stylus_buttons_enabled";
 
         /**
+         * Preferred default user profile to use with the notes task button shortcut.
+         *
+         * @hide
+         */
+        @SuppressLint("NoSettingsProvider")
+        public static final String DEFAULT_NOTE_TASK_PROFILE = "default_note_task_profile";
+
+        /**
          * Host name and port for global http proxy. Uses ':' seperator for
          * between host and port.
          *
@@ -9926,6 +9968,13 @@
         public static final String SPATIAL_AUDIO_ENABLED = "spatial_audio_enabled";
 
         /**
+         * Internal collection of audio device inventory items
+         * The device item stored are {@link com.android.server.audio.AdiDeviceState}
+         * @hide
+         */
+        public static final String AUDIO_DEVICE_INVENTORY = "audio_device_inventory";
+
+        /**
          * Indicates whether notification display on the lock screen is enabled.
          * <p>
          * Type: int (0 for false, 1 for true)
@@ -11065,6 +11114,19 @@
                 "navigation_mode";
 
         /**
+         * The value is from another(source) device's {@link #NAVIGATION_MODE} during restore.
+         * It's supposed to be written only by
+         * {@link com.android.providers.settings.SettingsHelper}.
+         * This setting should not be added into backup array.
+         * <p>Value: -1 = Can't get value from restore(default),
+         *  0 = 3 button,
+         *  1 = 2 button,
+         *  2 = fully gestural.
+         * @hide
+         */
+        public static final String NAVIGATION_MODE_RESTORE = "navigation_mode_restore";
+
+        /**
          * Scale factor for the back gesture inset size on the left side of the screen.
          * @hide
          */
@@ -12605,6 +12667,26 @@
         public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on";
 
         /**
+         * The duration in milliseconds of each action, separated by commas. Ex:
+         *
+         * "18000,18000,18000,18000,0"
+         *
+         * See com.android.internal.telephony.data.DataStallRecoveryManager for more info
+         * @hide
+         */
+        public static final String DSRM_DURATION_MILLIS = "dsrm_duration_millis";
+
+        /**
+         * The list of DSRM enabled actions, separated by commas. Ex:
+         *
+         * "true,true,false,true,true"
+         *
+         * See com.android.internal.telephony.data.DataStallRecoveryManager for more info
+         * @hide
+         */
+        public static final String DSRM_ENABLED_ACTIONS = "dsrm_enabled_actions";
+
+        /**
          * Whether the wifi data connection should remain active even when higher
          * priority networks like Ethernet are active, to keep both networks.
          * In the case where higher priority networks are connected, wifi will be
@@ -17979,6 +18061,15 @@
                 "review_permissions_notification_state";
 
         /**
+         * Whether repair mode is active on the device.
+         * <p>
+         * Set to 1 for true and 0 for false.
+         *
+         * @hide
+         */
+        public static final String REPAIR_MODE_ACTIVE = "repair_mode_active";
+
+        /**
          * Settings migrated from Wear OS settings provider.
          * @hide
          */
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 737c95f..06e86af 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -15,6 +15,8 @@
  */
 package android.service.contentcapture;
 
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_PAUSED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_RESUMED;
 import static android.view.contentcapture.ContentCaptureHelper.sDebug;
 import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
 import static android.view.contentcapture.ContentCaptureHelper.toList;
@@ -37,6 +39,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Slog;
@@ -88,6 +91,18 @@
             "android.service.contentcapture.ContentCaptureService";
 
     /**
+     * The {@link Intent} that must be declared as handled by the protection service.
+     *
+     * <p>To be supported, the service must also require the {@link
+     * android.Manifest.permission#BIND_CONTENT_CAPTURE_SERVICE} permission so that other
+     * applications can not abuse it.
+     *
+     * @hide
+     */
+    public static final String PROTECTION_SERVICE_INTERFACE =
+            "android.service.contentcapture.ContentProtectionService";
+
+    /**
      * Name under which a ContentCaptureService component publishes information about itself.
      *
      * <p>This meta-data should reference an XML resource containing a
@@ -128,10 +143,9 @@
     private long mCallerMismatchTimeout = 1000;
     private long mLastCallerMismatchLog;
 
-    /**
-     * Binder that receives calls from the system server.
-     */
-    private final IContentCaptureService mServerInterface = new IContentCaptureService.Stub() {
+    /** Binder that receives calls from the system server in the content capture flow. */
+    private final IContentCaptureService mContentCaptureServerInterface =
+            new IContentCaptureService.Stub() {
 
         @Override
         public void onConnected(IBinder callback, boolean verbose, boolean debug) {
@@ -187,10 +201,24 @@
         }
     };
 
-    /**
-     * Binder that receives calls from the app.
-     */
-    private final IContentCaptureDirectManager mClientInterface =
+    /** Binder that receives calls from the system server in the content protection flow. */
+    private final IContentProtectionService mContentProtectionServerInterface =
+            new IContentProtectionService.Stub() {
+
+                @Override
+                public void onLoginDetected(
+                        @SuppressWarnings("rawtypes") ParceledListSlice events) {
+                    mHandler.sendMessage(
+                            obtainMessage(
+                                    ContentCaptureService::handleOnLoginDetected,
+                                    ContentCaptureService.this,
+                                    Binder.getCallingUid(),
+                                    events));
+                }
+            };
+
+    /** Binder that receives calls from the app in the content capture flow. */
+    private final IContentCaptureDirectManager mContentCaptureClientInterface =
             new IContentCaptureDirectManager.Stub() {
 
         @Override
@@ -220,9 +248,19 @@
     @Override
     public final IBinder onBind(Intent intent) {
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
-            return mServerInterface.asBinder();
+            return mContentCaptureServerInterface.asBinder();
         }
-        Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+        if (PROTECTION_SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mContentProtectionServerInterface.asBinder();
+        }
+        Log.w(
+                TAG,
+                "Tried to bind to wrong intent (should be "
+                        + SERVICE_INTERFACE
+                        + " or "
+                        + PROTECTION_SERVICE_INTERFACE
+                        + "): "
+                        + intent);
         return null;
     }
 
@@ -456,7 +494,7 @@
         } else {
             stateFlags |= ContentCaptureSession.STATE_DISABLED;
         }
-        setClientState(clientReceiver, stateFlags, mClientInterface.asBinder());
+        setClientState(clientReceiver, stateFlags, mContentCaptureClientInterface.asBinder());
     }
 
     private void handleSendEvents(int uid,
@@ -524,6 +562,27 @@
         writeFlushMetrics(lastSessionId, activityComponent, metrics, options, reason);
     }
 
+    private void handleOnLoginDetected(
+            int uid, @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents) {
+        if (uid != Process.SYSTEM_UID) {
+            Log.e(TAG, "handleOnLoginDetected() not allowed for uid: " + uid);
+            return;
+        }
+        List<ContentCaptureEvent> events = parceledEvents.getList();
+        int sessionIdInt = events.isEmpty() ? NO_SESSION_ID : events.get(0).getSessionId();
+        ContentCaptureSessionId sessionId = new ContentCaptureSessionId(sessionIdInt);
+
+        ContentCaptureEvent startEvent =
+                new ContentCaptureEvent(sessionIdInt, TYPE_SESSION_RESUMED);
+        startEvent.setSelectionIndex(0, events.size());
+        onContentCaptureEvent(sessionId, startEvent);
+
+        events.forEach(event -> onContentCaptureEvent(sessionId, event));
+
+        ContentCaptureEvent endEvent = new ContentCaptureEvent(sessionIdInt, TYPE_SESSION_PAUSED);
+        onContentCaptureEvent(sessionId, endEvent);
+    }
+
     private void handleOnActivitySnapshot(int sessionId, @NonNull SnapshotData snapshotData) {
         onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData);
     }
diff --git a/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java b/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java
index fb60619..4aa5c00 100644
--- a/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java
+++ b/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java
@@ -48,7 +48,7 @@
  *
  * @hide
  */
-public final class ContentCaptureServiceInfo {
+public class ContentCaptureServiceInfo {
 
     private static final String TAG = ContentCaptureServiceInfo.class.getSimpleName();
     private static final String XML_TAG_SERVICE = "content-capture-service";
diff --git a/core/java/android/service/contentcapture/IContentProtectionService.aidl b/core/java/android/service/contentcapture/IContentProtectionService.aidl
new file mode 100644
index 0000000..4a13c3f
--- /dev/null
+++ b/core/java/android/service/contentcapture/IContentProtectionService.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.contentcapture;
+
+import android.content.pm.ParceledListSlice;
+import android.view.contentcapture.ContentCaptureEvent;
+
+/**
+ * Interface from the system server to the content protection service.
+ *
+ * @hide
+ */
+oneway interface IContentProtectionService {
+
+    void onLoginDetected(in ParceledListSlice events);
+}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index cd57de5..7d9c0a3 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -16,6 +16,8 @@
 
 package android.service.dreams;
 
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 import android.annotation.IdRes;
 import android.annotation.LayoutRes;
 import android.annotation.NonNull;
@@ -24,7 +26,6 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.TestApi;
 import android.app.Activity;
-import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
 import android.app.Service;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -243,13 +244,7 @@
 
     private DreamOverlayConnectionHandler mOverlayConnection;
 
-    private final IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() {
-        @Override
-        public void onExitRequested() {
-            // Simply finish dream when exit is requested.
-            mHandler.post(() -> finish());
-        }
-    };
+    private IDreamOverlayCallback mOverlayCallback;
 
 
     public DreamService() {
@@ -630,7 +625,7 @@
     }
 
     /**
-     * Marks this dream as windowless. Only available to doze dreams.
+     * Marks this dream as windowless. It should be called in {@link #onCreate} method.
      *
      * @hide
      *
@@ -640,7 +635,7 @@
     }
 
     /**
-     * Returns whether this dream is windowless. Only available to doze dreams.
+     * Returns whether this dream is windowless.
      *
      * @hide
      */
@@ -876,6 +871,13 @@
         mDreamComponent = new ComponentName(this, getClass());
         mShouldShowComplications = fetchShouldShowComplications(this /*context*/,
                 fetchServiceInfo(this /*context*/, mDreamComponent));
+        mOverlayCallback = new IDreamOverlayCallback.Stub() {
+            @Override
+            public void onExitRequested() {
+                // Simply finish dream when exit is requested.
+                mHandler.post(() -> finish());
+            }
+        };
 
         super.onCreate();
     }
@@ -1082,7 +1084,7 @@
 
         // Just in case destroy came in before detach, let's take care of that now
         detach();
-
+        mOverlayCallback = null;
         super.onDestroy();
     }
 
@@ -1230,8 +1232,10 @@
 
         mDreamToken = dreamToken;
         mCanDoze = canDoze;
-        if (mWindowless && !mCanDoze) {
-            throw new IllegalStateException("Only doze dreams can be windowless");
+        // This is not a security check to prevent malicious dreams but a guard rail to stop
+        // third-party dreams from being windowless and not working well as a result.
+        if (mWindowless && !mCanDoze && !isCallerSystemUi()) {
+            throw new IllegalStateException("Only doze or SystemUI dreams can be windowless.");
         }
 
         mDispatchAfterOnAttachedToWindow = () -> {
@@ -1264,9 +1268,7 @@
                     fetchDreamLabel(this, serviceInfo, isPreviewMode));
 
             try {
-                if (!ActivityTaskManager.getService().startDreamActivity(i)) {
-                    detach();
-                }
+                mDreamManager.startDreamActivity(i);
             } catch (SecurityException e) {
                 Log.w(mTag,
                         "Received SecurityException trying to start DreamActivity. "
@@ -1366,6 +1368,11 @@
         }
     }
 
+    private boolean isCallerSystemUi() {
+        return checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+                == PERMISSION_GRANTED;
+    }
+
     private int applyFlags(int oldFlags, int flags, int mask) {
         return (oldFlags&~mask) | (flags&mask);
     }
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 609425c..dd8b3de 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -17,6 +17,7 @@
 package android.service.dreams;
 
 import android.content.ComponentName;
+import android.content.Intent;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.IBinder;
@@ -45,4 +46,5 @@
     void setDreamComponentsForUser(int userId, in ComponentName[] componentNames);
     void setSystemDreamComponent(in ComponentName componentName);
     void registerDreamOverlayService(in ComponentName componentName);
+    void startDreamActivity(in Intent intent);
 }
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index a853714..75640bd 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -16,32 +16,117 @@
 package android.service.notification;
 
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.SharedMemory;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+
+import java.nio.ByteBuffer;
 
 /**
+ * Represents an update to notification rankings.
  * @hide
  */
+@SuppressLint({"ParcelNotFinal", "ParcelCreator"})
+@TestApi
 public class NotificationRankingUpdate implements Parcelable {
     private final NotificationListenerService.RankingMap mRankingMap;
 
+    // The ranking map is stored in shared memory when parceled, for sending across the binder.
+    // This is done because the ranking map can grow large if there are many notifications.
+    private SharedMemory mRankingMapFd = null;
+    private final String mSharedMemoryName = "NotificationRankingUpdatedSharedMemory";
+
+    /**
+     * @hide
+     */
     public NotificationRankingUpdate(NotificationListenerService.Ranking[] rankings) {
         mRankingMap = new NotificationListenerService.RankingMap(rankings);
     }
 
+    /**
+     * @hide
+     */
     public NotificationRankingUpdate(Parcel in) {
-        mRankingMap = in.readParcelable(getClass().getClassLoader(), android.service.notification.NotificationListenerService.RankingMap.class);
+        if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
+                SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+            // Recover the ranking map from the SharedMemory and store it in mapParcel.
+            final Parcel mapParcel = Parcel.obtain();
+            ByteBuffer buffer = null;
+            try {
+                // The ranking map should be stored in shared memory when it is parceled, so we
+                // unwrap the SharedMemory object.
+                mRankingMapFd = in.readParcelable(getClass().getClassLoader(), SharedMemory.class);
+
+                // In the case that the ranking map can't be read, readParcelable may return null.
+                // In this case, we set mRankingMap to null;
+                if (mRankingMapFd == null) {
+                    mRankingMap = null;
+                    return;
+                }
+                // We only need read-only access to the shared memory region.
+                buffer = mRankingMapFd.mapReadOnly();
+                if (buffer == null) {
+                    mRankingMap = null;
+                    return;
+                }
+                byte[] payload = new byte[buffer.remaining()];
+                buffer.get(payload);
+                mapParcel.unmarshall(payload, 0, payload.length);
+                mapParcel.setDataPosition(0);
+
+                mRankingMap = mapParcel.readParcelable(getClass().getClassLoader(),
+                        android.service.notification.NotificationListenerService.RankingMap.class);
+            } catch (ErrnoException e) {
+                // TODO(b/284297289): remove throw when associated flag is moved to droidfood, to
+                // avoid crashes; change to Log.wtf.
+                throw new RuntimeException(e);
+            } finally {
+                mapParcel.recycle();
+                if (buffer != null) {
+                    mRankingMapFd.unmap(buffer);
+                }
+            }
+        } else {
+            mRankingMap = in.readParcelable(getClass().getClassLoader(),
+                    android.service.notification.NotificationListenerService.RankingMap.class);
+        }
     }
 
+    /**
+     * Confirms that the SharedMemory file descriptor is closed. Should only be used for testing.
+     * @hide
+     */
+    @TestApi
+    public final boolean isFdNotNullAndClosed() {
+        return mRankingMapFd != null && mRankingMapFd.getFd() == -1;
+    }
+
+    /**
+     * @hide
+     */
     public NotificationListenerService.RankingMap getRankingMap() {
         return mRankingMap;
     }
 
+    /**
+     * @hide
+     */
     @Override
     public int describeContents() {
         return 0;
     }
 
+    /**
+     * @hide
+     */
     @Override
     public boolean equals(@Nullable Object o) {
         if (this == o) return true;
@@ -51,11 +136,51 @@
         return mRankingMap.equals(other.mRankingMap);
     }
 
+    /**
+     * @hide
+     */
     @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeParcelable(mRankingMap, flags);
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
+                SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+            final Parcel mapParcel = Parcel.obtain();
+            try {
+                // Parcels the ranking map and measures its size.
+                mapParcel.writeParcelable(mRankingMap, flags);
+                int mapSize = mapParcel.dataSize();
+
+                // Creates a new SharedMemory object with enough space to hold the ranking map.
+                SharedMemory mRankingMapFd = SharedMemory.create(mSharedMemoryName, mapSize);
+                if (mRankingMapFd == null) {
+                    return;
+                }
+
+                // Gets a read/write buffer mapping the entire shared memory region.
+                final ByteBuffer buffer = mRankingMapFd.mapReadWrite();
+
+                // Puts the ranking map into the shared memory region buffer.
+                buffer.put(mapParcel.marshall(), 0, mapSize);
+
+                // Protects the region from being written to, by setting it to be read-only.
+                mRankingMapFd.setProtect(OsConstants.PROT_READ);
+
+                // Puts the SharedMemory object in the parcel.
+                out.writeParcelable(mRankingMapFd, flags);
+            } catch (ErrnoException e) {
+                // TODO(b/284297289): remove throw when associated flag is moved to droidfood, to
+                // avoid crashes; change to Log.wtf.
+                throw new RuntimeException(e);
+            } finally {
+                mapParcel.recycle();
+            }
+        } else {
+            out.writeParcelable(mRankingMap, flags);
+        }
     }
 
+    /**
+    * @hide
+    */
     public static final @android.annotation.NonNull Parcelable.Creator<NotificationRankingUpdate> CREATOR
             = new Parcelable.Creator<NotificationRankingUpdate>() {
         public NotificationRankingUpdate createFromParcel(Parcel parcel) {
diff --git a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
deleted file mode 100644
index ad73a53..0000000
--- a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
+++ /dev/null
@@ -1,153 +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 android.service.selectiontoolbar;
-
-import static android.view.selectiontoolbar.SelectionToolbarManager.ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR;
-import static android.view.selectiontoolbar.SelectionToolbarManager.NO_TOOLBAR_ID;
-
-import android.util.Pair;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.view.selectiontoolbar.ShowInfo;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.UUID;
-
-/**
- * The default implementation of {@link SelectionToolbarRenderService}.
- *
- * <p><b>NOTE:<b/> The requests are handled on the service main thread.
- *
- *  @hide
- */
-// TODO(b/214122495): fix class not found then move to system service folder
-public final class DefaultSelectionToolbarRenderService extends SelectionToolbarRenderService {
-
-    private static final String TAG = "DefaultSelectionToolbarRenderService";
-
-    // TODO(b/215497659): handle remove if the client process dies.
-    // Only show one toolbar, dismiss the old ones and remove from cache
-    private final SparseArray<Pair<Long, RemoteSelectionToolbar>> mToolbarCache =
-            new SparseArray<>();
-
-    /**
-     * Only allow one package to create one toolbar.
-     */
-    private boolean canShowToolbar(int uid, ShowInfo showInfo) {
-        if (showInfo.getWidgetToken() != NO_TOOLBAR_ID) {
-            return true;
-        }
-        return mToolbarCache.indexOfKey(uid) < 0;
-    }
-
-    @Override
-    public void onShow(int callingUid, ShowInfo showInfo,
-            SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper) {
-        if (!canShowToolbar(callingUid, showInfo)) {
-            Slog.e(TAG, "Do not allow multiple toolbar for the app.");
-            callbackWrapper.onError(ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR);
-            return;
-        }
-        long widgetToken = showInfo.getWidgetToken() == NO_TOOLBAR_ID
-                ? UUID.randomUUID().getMostSignificantBits()
-                : showInfo.getWidgetToken();
-
-        if (mToolbarCache.indexOfKey(callingUid) < 0) {
-            RemoteSelectionToolbar toolbar = new RemoteSelectionToolbar(this,
-                    widgetToken, showInfo,
-                    callbackWrapper, this::transferTouch);
-            mToolbarCache.put(callingUid, new Pair<>(widgetToken, toolbar));
-        }
-        Slog.v(TAG, "onShow() for " + widgetToken);
-        Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.get(callingUid);
-        if (toolbarPair.first == widgetToken) {
-            toolbarPair.second.show(showInfo);
-        } else {
-            Slog.w(TAG, "onShow() for unknown " + widgetToken);
-        }
-    }
-
-    @Override
-    public void onHide(long widgetToken) {
-        RemoteSelectionToolbar toolbar = getRemoteSelectionToolbarByTokenLocked(widgetToken);
-        if (toolbar != null) {
-            Slog.v(TAG, "onHide() for " + widgetToken);
-            toolbar.hide(widgetToken);
-        }
-    }
-
-    @Override
-    public void onDismiss(long widgetToken) {
-        RemoteSelectionToolbar toolbar = getRemoteSelectionToolbarByTokenLocked(widgetToken);
-        if (toolbar != null) {
-            Slog.v(TAG, "onDismiss() for " + widgetToken);
-            toolbar.dismiss(widgetToken);
-            removeRemoteSelectionToolbarByTokenLocked(widgetToken);
-        }
-    }
-
-    @Override
-    public void onToolbarShowTimeout(int callingUid) {
-        Slog.w(TAG, "onToolbarShowTimeout for callingUid = " + callingUid);
-        Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.get(callingUid);
-        if (toolbarPair != null) {
-            RemoteSelectionToolbar remoteToolbar = toolbarPair.second;
-            remoteToolbar.dismiss(toolbarPair.first);
-            remoteToolbar.onToolbarShowTimeout();
-            mToolbarCache.remove(callingUid);
-        }
-    }
-
-    private RemoteSelectionToolbar getRemoteSelectionToolbarByTokenLocked(long widgetToken) {
-        for (int i = 0; i < mToolbarCache.size(); i++) {
-            Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.valueAt(i);
-            if (toolbarPair.first == widgetToken) {
-                return toolbarPair.second;
-            }
-        }
-        return null;
-    }
-
-    private void removeRemoteSelectionToolbarByTokenLocked(long widgetToken) {
-        for (int i = 0; i < mToolbarCache.size(); i++) {
-            Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.valueAt(i);
-            if (toolbarPair.first == widgetToken) {
-                mToolbarCache.remove(mToolbarCache.keyAt(i));
-                return;
-            }
-        }
-    }
-
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        int size = mToolbarCache.size();
-        pw.print("number selectionToolbar: "); pw.println(size);
-        String pfx = "  ";
-        for (int i = 0; i < size; i++) {
-            pw.print("#"); pw.println(i);
-            int callingUid = mToolbarCache.keyAt(i);
-            pw.print(pfx); pw.print("callingUid: "); pw.println(callingUid);
-            Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.valueAt(i);
-            RemoteSelectionToolbar selectionToolbar = toolbarPair.second;
-            pw.print(pfx); pw.print("selectionToolbar: ");
-            selectionToolbar.dump(pfx, pw);
-            pw.println();
-        }
-    }
-}
-
diff --git a/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java b/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java
deleted file mode 100644
index adc9251..0000000
--- a/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java
+++ /dev/null
@@ -1,89 +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 android.service.selectiontoolbar;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.widget.LinearLayout;
-
-import java.io.PrintWriter;
-
-/**
- * This class is the root view for the selection toolbar. It is responsible for
- * detecting the click on the item and to also transfer input focus to the application.
- *
- * @hide
- */
-@SuppressLint("ViewConstructor")
-public class FloatingToolbarRoot extends LinearLayout {
-
-    private static final boolean DEBUG = false;
-    private static final String TAG = "FloatingToolbarRoot";
-
-    private final IBinder mTargetInputToken;
-    private final SelectionToolbarRenderService.TransferTouchListener mTransferTouchListener;
-    private final Rect mContentRect = new Rect();
-
-    private int mLastDownX = -1;
-    private int mLastDownY = -1;
-
-    public FloatingToolbarRoot(Context context, IBinder targetInputToken,
-            SelectionToolbarRenderService.TransferTouchListener transferTouchListener) {
-        super(context);
-        mTargetInputToken = targetInputToken;
-        mTransferTouchListener = transferTouchListener;
-        setFocusable(false);
-    }
-
-    /**
-     * Sets the Rect that shows the selection toolbar content.
-     */
-    public void setContentRect(Rect contentRect) {
-        mContentRect.set(contentRect);
-    }
-
-    @Override
-    @SuppressLint("ClickableViewAccessibility")
-    public boolean dispatchTouchEvent(MotionEvent event) {
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            mLastDownX = (int) event.getX();
-            mLastDownY = (int) event.getY();
-            if (DEBUG) {
-                Log.d(TAG, "downX=" + mLastDownX + " downY=" + mLastDownY);
-            }
-            // TODO(b/215497659): Check FLAG_WINDOW_IS_PARTIALLY_OBSCURED
-            if (!mContentRect.contains(mLastDownX, mLastDownY)) {
-                if (DEBUG) {
-                    Log.d(TAG, "Transfer touch focus to application.");
-                }
-                mTransferTouchListener.onTransferTouch(getViewRootImpl().getInputToken(),
-                        mTargetInputToken);
-            }
-        }
-        return super.dispatchTouchEvent(event);
-    }
-
-    void dump(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.println("FloatingToolbarRoot:");
-        pw.print(prefix + "  "); pw.print("last down X: "); pw.println(mLastDownX);
-        pw.print(prefix + "  "); pw.print("last down Y: "); pw.println(mLastDownY);
-    }
-}
diff --git a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl
deleted file mode 100644
index 79281b8..0000000
--- a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl
+++ /dev/null
@@ -1,32 +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 android.service.selectiontoolbar;
-
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.ShowInfo;
-
-/**
- * The service to render the selection toolbar menus.
- *
- * @hide
- */
-oneway interface ISelectionToolbarRenderService {
-    void onConnected(in IBinder callback);
-    void onShow(int callingUid, in ShowInfo showInfo, in ISelectionToolbarCallback callback);
-    void onHide(long widgetToken);
-    void onDismiss(int callingUid, long widgetToken);
-}
diff --git a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl
deleted file mode 100644
index f6c47dd..0000000
--- a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl
+++ /dev/null
@@ -1,28 +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 android.service.selectiontoolbar;
-
-import android.os.IBinder;
-
-/**
- * The interface from the SelectionToolbarRenderService to the system.
- *
- * @hide
- */
-oneway interface ISelectionToolbarRenderServiceCallback {
-    void transferTouch(in IBinder source, in IBinder target);
-}
diff --git a/core/java/android/service/selectiontoolbar/OWNERS b/core/java/android/service/selectiontoolbar/OWNERS
deleted file mode 100644
index 5500b92..0000000
--- a/core/java/android/service/selectiontoolbar/OWNERS
+++ /dev/null
@@ -1,10 +0,0 @@
-# Bug component: 709498
-
-augale@google.com
-joannechung@google.com
-licha@google.com
-lpeter@google.com
-svetoslavganov@google.com
-toki@google.com
-tonymak@google.com
-tymtsai@google.com
\ No newline at end of file
diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
deleted file mode 100644
index 59e3a5e..0000000
--- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
+++ /dev/null
@@ -1,1398 +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 android.service.selectiontoolbar;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.IBinder;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Size;
-import android.view.ContextThemeWrapper;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.view.animation.Transformation;
-import android.view.selectiontoolbar.ShowInfo;
-import android.view.selectiontoolbar.ToolbarMenuItem;
-import android.view.selectiontoolbar.WidgetInfo;
-import android.widget.ArrayAdapter;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.android.internal.R;
-import com.android.internal.util.Preconditions;
-import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
-
-import java.io.PrintWriter;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * This class is responsible for rendering/animation of the selection toolbar in the remote
- * system process. It holds 2 panels (i.e. main panel and overflow panel) and an overflow
- * button to transition between panels.
- *
- *  @hide
- */
-// TODO(b/215497659): share code with LocalFloatingToolbarPopup
-final class RemoteSelectionToolbar {
-    private static final String TAG = "RemoteSelectionToolbar";
-
-    /* Minimum and maximum number of items allowed in the overflow. */
-    private static final int MIN_OVERFLOW_SIZE = 2;
-    private static final int MAX_OVERFLOW_SIZE = 4;
-
-    private final Context mContext;
-
-    /* Margins between the popup window and its content. */
-    private final int mMarginHorizontal;
-    private final int mMarginVertical;
-
-    /* View components */
-    private final ViewGroup mContentContainer;  // holds all contents.
-    private final ViewGroup mMainPanel;  // holds menu items that are initially displayed.
-    // holds menu items hidden in the overflow.
-    private final OverflowPanel mOverflowPanel;
-    private final ImageButton mOverflowButton;  // opens/closes the overflow.
-    /* overflow button drawables. */
-    private final Drawable mArrow;
-    private final Drawable mOverflow;
-    private final AnimatedVectorDrawable mToArrow;
-    private final AnimatedVectorDrawable mToOverflow;
-
-    private final OverflowPanelViewHelper mOverflowPanelViewHelper;
-
-    /* Animation interpolators. */
-    private final Interpolator mLogAccelerateInterpolator;
-    private final Interpolator mFastOutSlowInInterpolator;
-    private final Interpolator mLinearOutSlowInInterpolator;
-    private final Interpolator mFastOutLinearInInterpolator;
-
-    /* Animations. */
-    private final AnimatorSet mShowAnimation;
-    private final AnimatorSet mDismissAnimation;
-    private final AnimatorSet mHideAnimation;
-    private final AnimationSet mOpenOverflowAnimation;
-    private final AnimationSet mCloseOverflowAnimation;
-    private final Animation.AnimationListener mOverflowAnimationListener;
-
-    private final Rect mViewPortOnScreen = new Rect();  // portion of screen we can draw in.
-
-    private final int mLineHeight;
-    private final int mIconTextSpacing;
-
-    private final long mSelectionToolbarToken;
-    private IBinder mHostInputToken;
-    private final SelectionToolbarRenderService.RemoteCallbackWrapper mCallbackWrapper;
-    private final SelectionToolbarRenderService.TransferTouchListener mTransferTouchListener;
-    private int mPopupWidth;
-    private int mPopupHeight;
-    // Coordinates to show the toolbar relative to the specified view port
-    private final Point mRelativeCoordsForToolbar = new Point();
-    private List<ToolbarMenuItem> mMenuItems;
-    private SurfaceControlViewHost mSurfaceControlViewHost;
-    private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
-
-    /**
-     * @see OverflowPanelViewHelper#preparePopupContent().
-     */
-    private final Runnable mPreparePopupContentRTLHelper = new Runnable() {
-        @Override
-        public void run() {
-            setPanelsStatesAtRestingPosition();
-            mContentContainer.setAlpha(1);
-        }
-    };
-
-    private boolean mDismissed = true; // tracks whether this popup is dismissed or dismissing.
-    private boolean mHidden; // tracks whether this popup is hidden or hiding.
-
-    /* Calculated sizes for panels and overflow button. */
-    private final Size mOverflowButtonSize;
-    private Size mOverflowPanelSize;  // Should be null when there is no overflow.
-    private Size mMainPanelSize;
-
-    /* Menu items and click listeners */
-    private final View.OnClickListener mMenuItemButtonOnClickListener;
-
-    private boolean mOpenOverflowUpwards;  // Whether the overflow opens upwards or downwards.
-    private boolean mIsOverflowOpen;
-
-    private int mTransitionDurationScale;  // Used to scale the toolbar transition duration.
-
-    private final Rect mPreviousContentRect = new Rect();
-
-    private final Rect mTempContentRect = new Rect();
-    private final Rect mTempContentRectForRoot = new Rect();
-    private final int[] mTempCoords = new int[2];
-
-    RemoteSelectionToolbar(Context context, long selectionToolbarToken, ShowInfo showInfo,
-            SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper,
-            SelectionToolbarRenderService.TransferTouchListener transferTouchListener) {
-        mContext = applyDefaultTheme(context, showInfo.isIsLightTheme());
-        mSelectionToolbarToken = selectionToolbarToken;
-        mCallbackWrapper = callbackWrapper;
-        mTransferTouchListener = transferTouchListener;
-        mHostInputToken = showInfo.getHostInputToken();
-        mContentContainer = createContentContainer(mContext);
-        mMarginHorizontal = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
-        mMarginVertical = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin);
-        mLineHeight = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.floating_toolbar_height);
-        mIconTextSpacing = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.floating_toolbar_icon_text_spacing);
-
-        // Interpolators
-        mLogAccelerateInterpolator = new LogAccelerateInterpolator();
-        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
-                mContext, android.R.interpolator.fast_out_slow_in);
-        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
-                mContext, android.R.interpolator.linear_out_slow_in);
-        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
-                mContext, android.R.interpolator.fast_out_linear_in);
-
-        // Drawables. Needed for views.
-        mArrow = mContext.getResources()
-                .getDrawable(R.drawable.ft_avd_tooverflow, mContext.getTheme());
-        mArrow.setAutoMirrored(true);
-        mOverflow = mContext.getResources()
-                .getDrawable(R.drawable.ft_avd_toarrow, mContext.getTheme());
-        mOverflow.setAutoMirrored(true);
-        mToArrow = (AnimatedVectorDrawable) mContext.getResources()
-                .getDrawable(R.drawable.ft_avd_toarrow_animation, mContext.getTheme());
-        mToArrow.setAutoMirrored(true);
-        mToOverflow = (AnimatedVectorDrawable) mContext.getResources()
-                .getDrawable(R.drawable.ft_avd_tooverflow_animation, mContext.getTheme());
-        mToOverflow.setAutoMirrored(true);
-
-        // Views
-        mOverflowButton = createOverflowButton();
-        mOverflowButtonSize = measure(mOverflowButton);
-        mMainPanel = createMainPanel();
-        mOverflowPanelViewHelper = new OverflowPanelViewHelper(mContext, mIconTextSpacing);
-        mOverflowPanel = createOverflowPanel();
-
-        // Animation. Need views.
-        mOverflowAnimationListener = createOverflowAnimationListener();
-        mOpenOverflowAnimation = new AnimationSet(true);
-        mOpenOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
-        mCloseOverflowAnimation = new AnimationSet(true);
-        mCloseOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
-        mShowAnimation = createEnterAnimation(mContentContainer,
-                new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        updateFloatingToolbarRootContentRect();
-                    }
-                });
-        mDismissAnimation = createExitAnimation(
-                mContentContainer,
-                150,  // startDelay
-                new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        // TODO(b/215497659): should dismiss window after animation
-                        mContentContainer.removeAllViews();
-                        mSurfaceControlViewHost.release();
-                        mSurfaceControlViewHost = null;
-                        mSurfacePackage = null;
-                    }
-                });
-        mHideAnimation = createExitAnimation(
-                mContentContainer,
-                0,  // startDelay
-                null); // TODO(b/215497659): should handle hide after animation
-        mMenuItemButtonOnClickListener = v -> {
-            Object tag = v.getTag();
-            if (!(tag instanceof ToolbarMenuItem)) {
-                return;
-            }
-            mCallbackWrapper.onMenuItemClicked((ToolbarMenuItem) tag);
-        };
-    }
-
-    private void updateFloatingToolbarRootContentRect() {
-        if (mSurfaceControlViewHost == null) {
-            return;
-        }
-        final FloatingToolbarRoot root = (FloatingToolbarRoot) mSurfaceControlViewHost.getView();
-        mContentContainer.getLocationOnScreen(mTempCoords);
-        int contentLeft = mTempCoords[0];
-        int contentTop = mTempCoords[1];
-        mTempContentRectForRoot.set(contentLeft, contentTop,
-                contentLeft + mContentContainer.getWidth(),
-                contentTop + mContentContainer.getHeight());
-        root.setContentRect(mTempContentRectForRoot);
-    }
-
-    private WidgetInfo createWidgetInfo() {
-        mTempContentRect.set(mRelativeCoordsForToolbar.x, mRelativeCoordsForToolbar.y,
-                mRelativeCoordsForToolbar.x + mPopupWidth,
-                mRelativeCoordsForToolbar.y + mPopupHeight);
-        return new WidgetInfo(mSelectionToolbarToken, mTempContentRect, getSurfacePackage());
-    }
-
-    private SurfaceControlViewHost.SurfacePackage getSurfacePackage() {
-        if (mSurfaceControlViewHost == null) {
-            final FloatingToolbarRoot contentHolder = new FloatingToolbarRoot(mContext,
-                    mHostInputToken, mTransferTouchListener);
-            contentHolder.addView(mContentContainer);
-            mSurfaceControlViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(),
-                    mHostInputToken, "RemoteSelectionToolbar");
-            mSurfaceControlViewHost.setView(contentHolder, mPopupWidth, mPopupHeight);
-        }
-        if (mSurfacePackage == null) {
-            mSurfacePackage = mSurfaceControlViewHost.getSurfacePackage();
-        }
-        return mSurfacePackage;
-    }
-
-    private void layoutMenuItems(
-            List<ToolbarMenuItem> menuItems,
-            int suggestedWidth) {
-        cancelOverflowAnimations();
-        clearPanels();
-
-        menuItems = layoutMainPanelItems(menuItems, getAdjustedToolbarWidth(suggestedWidth));
-        if (!menuItems.isEmpty()) {
-            // Add remaining items to the overflow.
-            layoutOverflowPanelItems(menuItems);
-        }
-        updatePopupSize();
-    }
-
-    public void onToolbarShowTimeout() {
-        mCallbackWrapper.onToolbarShowTimeout();
-    }
-
-    /**
-     * Show the specified selection toolbar.
-     */
-    public void show(ShowInfo showInfo) {
-        debugLog("show() for " + showInfo);
-
-        mMenuItems = showInfo.getMenuItems();
-        mViewPortOnScreen.set(showInfo.getViewPortOnScreen());
-
-        debugLog("show(): layoutRequired=" + showInfo.isLayoutRequired());
-        if (showInfo.isLayoutRequired()) {
-            layoutMenuItems(mMenuItems, showInfo.getSuggestedWidth());
-        }
-        Rect contentRect = showInfo.getContentRect();
-        if (!isShowing()) {
-            show(contentRect);
-        } else if (!mPreviousContentRect.equals(contentRect)) {
-            updateCoordinates(contentRect);
-        }
-        mPreviousContentRect.set(contentRect);
-    }
-
-    private void show(Rect contentRectOnScreen) {
-        Objects.requireNonNull(contentRectOnScreen);
-
-        mHidden = false;
-        mDismissed = false;
-        cancelDismissAndHideAnimations();
-        cancelOverflowAnimations();
-        refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
-        preparePopupContent();
-        mCallbackWrapper.onShown(createWidgetInfo());
-        // TODO(b/215681595): Use Choreographer to coordinate for show between different thread
-        mShowAnimation.start();
-    }
-
-    /**
-     * Dismiss the specified selection toolbar.
-     */
-    public void dismiss(long floatingToolbarToken) {
-        debugLog("dismiss for " + floatingToolbarToken);
-        if (mDismissed) {
-            return;
-        }
-        mHidden = false;
-        mDismissed = true;
-
-        mHideAnimation.cancel();
-        mDismissAnimation.start();
-    }
-
-    /**
-     * Hide the specified selection toolbar.
-     */
-    public void hide(long floatingToolbarToken) {
-        debugLog("hide for " + floatingToolbarToken);
-        if (!isShowing()) {
-            return;
-        }
-
-        mHidden = true;
-        mHideAnimation.start();
-    }
-
-    public boolean isShowing() {
-        return !mDismissed && !mHidden;
-    }
-
-    private void updateCoordinates(Rect contentRectOnScreen) {
-        Objects.requireNonNull(contentRectOnScreen);
-
-        if (!isShowing()) {
-            return;
-        }
-        cancelOverflowAnimations();
-        refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
-        preparePopupContent();
-        WidgetInfo widgetInfo = createWidgetInfo();
-        mSurfaceControlViewHost.relayout(mPopupWidth, mPopupHeight);
-        mCallbackWrapper.onWidgetUpdated(widgetInfo);
-    }
-
-    private void refreshCoordinatesAndOverflowDirection(Rect contentRectOnScreen) {
-        // Initialize x ensuring that the toolbar isn't rendered behind the nav bar in
-        // landscape.
-        final int x = Math.min(
-                contentRectOnScreen.centerX() - mPopupWidth / 2,
-                mViewPortOnScreen.right - mPopupWidth);
-
-        final int y;
-
-        final int availableHeightAboveContent =
-                contentRectOnScreen.top - mViewPortOnScreen.top;
-        final int availableHeightBelowContent =
-                mViewPortOnScreen.bottom - contentRectOnScreen.bottom;
-
-        final int margin = 2 * mMarginVertical;
-        final int toolbarHeightWithVerticalMargin = mLineHeight + margin;
-
-        if (!hasOverflow()) {
-            if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin) {
-                // There is enough space at the top of the content.
-                y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
-            } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin) {
-                // There is enough space at the bottom of the content.
-                y = contentRectOnScreen.bottom;
-            } else if (availableHeightBelowContent >= mLineHeight) {
-                // Just enough space to fit the toolbar with no vertical margins.
-                y = contentRectOnScreen.bottom - mMarginVertical;
-            } else {
-                // Not enough space. Prefer to position as high as possible.
-                y = Math.max(
-                        mViewPortOnScreen.top,
-                        contentRectOnScreen.top - toolbarHeightWithVerticalMargin);
-            }
-        } else {
-            // Has an overflow.
-            final int minimumOverflowHeightWithMargin =
-                    calculateOverflowHeight(MIN_OVERFLOW_SIZE) + margin;
-            final int availableHeightThroughContentDown =
-                    mViewPortOnScreen.bottom - contentRectOnScreen.top
-                            + toolbarHeightWithVerticalMargin;
-            final int availableHeightThroughContentUp =
-                    contentRectOnScreen.bottom - mViewPortOnScreen.top
-                            + toolbarHeightWithVerticalMargin;
-
-            if (availableHeightAboveContent >= minimumOverflowHeightWithMargin) {
-                // There is enough space at the top of the content rect for the overflow.
-                // Position above and open upwards.
-                updateOverflowHeight(availableHeightAboveContent - margin);
-                y = contentRectOnScreen.top - mPopupHeight;
-                mOpenOverflowUpwards = true;
-            } else if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin
-                    && availableHeightThroughContentDown >= minimumOverflowHeightWithMargin) {
-                // There is enough space at the top of the content rect for the main panel
-                // but not the overflow.
-                // Position above but open downwards.
-                updateOverflowHeight(availableHeightThroughContentDown - margin);
-                y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
-                mOpenOverflowUpwards = false;
-            } else if (availableHeightBelowContent >= minimumOverflowHeightWithMargin) {
-                // There is enough space at the bottom of the content rect for the overflow.
-                // Position below and open downwards.
-                updateOverflowHeight(availableHeightBelowContent - margin);
-                y = contentRectOnScreen.bottom;
-                mOpenOverflowUpwards = false;
-            } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin
-                    && mViewPortOnScreen.height() >= minimumOverflowHeightWithMargin) {
-                // There is enough space at the bottom of the content rect for the main panel
-                // but not the overflow.
-                // Position below but open upwards.
-                updateOverflowHeight(availableHeightThroughContentUp - margin);
-                y = contentRectOnScreen.bottom + toolbarHeightWithVerticalMargin
-                        - mPopupHeight;
-                mOpenOverflowUpwards = true;
-            } else {
-                // Not enough space.
-                // Position at the top of the view port and open downwards.
-                updateOverflowHeight(mViewPortOnScreen.height() - margin);
-                y = mViewPortOnScreen.top;
-                mOpenOverflowUpwards = false;
-            }
-        }
-        mRelativeCoordsForToolbar.set(x, y);
-    }
-
-    private void cancelDismissAndHideAnimations() {
-        mDismissAnimation.cancel();
-        mHideAnimation.cancel();
-    }
-
-    private void cancelOverflowAnimations() {
-        mContentContainer.clearAnimation();
-        mMainPanel.animate().cancel();
-        mOverflowPanel.animate().cancel();
-        mToArrow.stop();
-        mToOverflow.stop();
-    }
-
-    private void openOverflow() {
-        final int targetWidth = mOverflowPanelSize.getWidth();
-        final int targetHeight = mOverflowPanelSize.getHeight();
-        final int startWidth = mContentContainer.getWidth();
-        final int startHeight = mContentContainer.getHeight();
-        final float startY = mContentContainer.getY();
-        final float left = mContentContainer.getX();
-        final float right = left + mContentContainer.getWidth();
-        Animation widthAnimation = new Animation() {
-            @Override
-            protected void applyTransformation(float interpolatedTime, Transformation t) {
-                int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
-                setWidth(mContentContainer, startWidth + deltaWidth);
-                if (isInRTLMode()) {
-                    mContentContainer.setX(left);
-
-                    // Lock the panels in place.
-                    mMainPanel.setX(0);
-                    mOverflowPanel.setX(0);
-                } else {
-                    mContentContainer.setX(right - mContentContainer.getWidth());
-
-                    // Offset the panels' positions so they look like they're locked in place
-                    // on the screen.
-                    mMainPanel.setX(mContentContainer.getWidth() - startWidth);
-                    mOverflowPanel.setX(mContentContainer.getWidth() - targetWidth);
-                }
-            }
-        };
-        Animation heightAnimation = new Animation() {
-            @Override
-            protected void applyTransformation(float interpolatedTime, Transformation t) {
-                int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
-                setHeight(mContentContainer, startHeight + deltaHeight);
-                if (mOpenOverflowUpwards) {
-                    mContentContainer.setY(
-                            startY - (mContentContainer.getHeight() - startHeight));
-                    positionContentYCoordinatesIfOpeningOverflowUpwards();
-                }
-            }
-        };
-        final float overflowButtonStartX = mOverflowButton.getX();
-        final float overflowButtonTargetX =
-                isInRTLMode() ? overflowButtonStartX + targetWidth - mOverflowButton.getWidth()
-                        : overflowButtonStartX - targetWidth + mOverflowButton.getWidth();
-        Animation overflowButtonAnimation = new Animation() {
-            @Override
-            protected void applyTransformation(float interpolatedTime, Transformation t) {
-                float overflowButtonX = overflowButtonStartX
-                        + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
-                float deltaContainerWidth =
-                        isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth;
-                float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
-                mOverflowButton.setX(actualOverflowButtonX);
-                updateFloatingToolbarRootContentRect();
-            }
-        };
-        widthAnimation.setInterpolator(mLogAccelerateInterpolator);
-        widthAnimation.setDuration(getAnimationDuration());
-        heightAnimation.setInterpolator(mFastOutSlowInInterpolator);
-        heightAnimation.setDuration(getAnimationDuration());
-        overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
-        overflowButtonAnimation.setDuration(getAnimationDuration());
-        mOpenOverflowAnimation.getAnimations().clear();
-        mOpenOverflowAnimation.addAnimation(widthAnimation);
-        mOpenOverflowAnimation.addAnimation(heightAnimation);
-        mOpenOverflowAnimation.addAnimation(overflowButtonAnimation);
-        mContentContainer.startAnimation(mOpenOverflowAnimation);
-        mIsOverflowOpen = true;
-        mMainPanel.animate()
-                .alpha(0).withLayer()
-                .setInterpolator(mLinearOutSlowInInterpolator)
-                .setDuration(250)
-                .start();
-        mOverflowPanel.setAlpha(1); // fadeIn in 0ms.
-    }
-
-    private void closeOverflow() {
-        final int targetWidth = mMainPanelSize.getWidth();
-        final int startWidth = mContentContainer.getWidth();
-        final float left = mContentContainer.getX();
-        final float right = left + mContentContainer.getWidth();
-        Animation widthAnimation = new Animation() {
-            @Override
-            protected void applyTransformation(float interpolatedTime, Transformation t) {
-                int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
-                setWidth(mContentContainer, startWidth + deltaWidth);
-                if (isInRTLMode()) {
-                    mContentContainer.setX(left);
-
-                    // Lock the panels in place.
-                    mMainPanel.setX(0);
-                    mOverflowPanel.setX(0);
-                } else {
-                    mContentContainer.setX(right - mContentContainer.getWidth());
-
-                    // Offset the panels' positions so they look like they're locked in place
-                    // on the screen.
-                    mMainPanel.setX(mContentContainer.getWidth() - targetWidth);
-                    mOverflowPanel.setX(mContentContainer.getWidth() - startWidth);
-                }
-            }
-        };
-        final int targetHeight = mMainPanelSize.getHeight();
-        final int startHeight = mContentContainer.getHeight();
-        final float bottom = mContentContainer.getY() + mContentContainer.getHeight();
-        Animation heightAnimation = new Animation() {
-            @Override
-            protected void applyTransformation(float interpolatedTime, Transformation t) {
-                int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
-                setHeight(mContentContainer, startHeight + deltaHeight);
-                if (mOpenOverflowUpwards) {
-                    mContentContainer.setY(bottom - mContentContainer.getHeight());
-                    positionContentYCoordinatesIfOpeningOverflowUpwards();
-                }
-            }
-        };
-        final float overflowButtonStartX = mOverflowButton.getX();
-        final float overflowButtonTargetX =
-                isInRTLMode() ? overflowButtonStartX - startWidth + mOverflowButton.getWidth()
-                        : overflowButtonStartX + startWidth - mOverflowButton.getWidth();
-        Animation overflowButtonAnimation = new Animation() {
-            @Override
-            protected void applyTransformation(float interpolatedTime, Transformation t) {
-                float overflowButtonX = overflowButtonStartX
-                        + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
-                float deltaContainerWidth =
-                        isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth;
-                float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
-                mOverflowButton.setX(actualOverflowButtonX);
-                updateFloatingToolbarRootContentRect();
-            }
-        };
-        widthAnimation.setInterpolator(mFastOutSlowInInterpolator);
-        widthAnimation.setDuration(getAnimationDuration());
-        heightAnimation.setInterpolator(mLogAccelerateInterpolator);
-        heightAnimation.setDuration(getAnimationDuration());
-        overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
-        overflowButtonAnimation.setDuration(getAnimationDuration());
-        mCloseOverflowAnimation.getAnimations().clear();
-        mCloseOverflowAnimation.addAnimation(widthAnimation);
-        mCloseOverflowAnimation.addAnimation(heightAnimation);
-        mCloseOverflowAnimation.addAnimation(overflowButtonAnimation);
-        mContentContainer.startAnimation(mCloseOverflowAnimation);
-        mIsOverflowOpen = false;
-        mMainPanel.animate()
-                .alpha(1).withLayer()
-                .setInterpolator(mFastOutLinearInInterpolator)
-                .setDuration(100)
-                .start();
-        mOverflowPanel.animate()
-                .alpha(0).withLayer()
-                .setInterpolator(mLinearOutSlowInInterpolator)
-                .setDuration(150)
-                .start();
-    }
-
-    /**
-     * Defines the position of the floating toolbar popup panels when transition animation has
-     * stopped.
-     */
-    private void setPanelsStatesAtRestingPosition() {
-        mOverflowButton.setEnabled(true);
-        mOverflowPanel.awakenScrollBars();
-
-        if (mIsOverflowOpen) {
-            // Set open state.
-            final Size containerSize = mOverflowPanelSize;
-            setSize(mContentContainer, containerSize);
-            mMainPanel.setAlpha(0);
-            mMainPanel.setVisibility(View.INVISIBLE);
-            mOverflowPanel.setAlpha(1);
-            mOverflowPanel.setVisibility(View.VISIBLE);
-            mOverflowButton.setImageDrawable(mArrow);
-            mOverflowButton.setContentDescription(mContext.getString(
-                    R.string.floating_toolbar_close_overflow_description));
-
-            // Update x-coordinates depending on RTL state.
-            if (isInRTLMode()) {
-                mContentContainer.setX(mMarginHorizontal);  // align left
-                mMainPanel.setX(0);  // align left
-                mOverflowButton.setX(// align right
-                        containerSize.getWidth() - mOverflowButtonSize.getWidth());
-                mOverflowPanel.setX(0);  // align left
-            } else {
-                mContentContainer.setX(// align right
-                        mPopupWidth - containerSize.getWidth() - mMarginHorizontal);
-                mMainPanel.setX(-mContentContainer.getX());  // align right
-                mOverflowButton.setX(0);  // align left
-                mOverflowPanel.setX(0);  // align left
-            }
-
-            // Update y-coordinates depending on overflow's open direction.
-            if (mOpenOverflowUpwards) {
-                mContentContainer.setY(mMarginVertical);  // align top
-                mMainPanel.setY(// align bottom
-                        containerSize.getHeight() - mContentContainer.getHeight());
-                mOverflowButton.setY(// align bottom
-                        containerSize.getHeight() - mOverflowButtonSize.getHeight());
-                mOverflowPanel.setY(0);  // align top
-            } else {
-                // opens downwards.
-                mContentContainer.setY(mMarginVertical);  // align top
-                mMainPanel.setY(0);  // align top
-                mOverflowButton.setY(0);  // align top
-                mOverflowPanel.setY(mOverflowButtonSize.getHeight());  // align bottom
-            }
-        } else {
-            // Overflow not open. Set closed state.
-            final Size containerSize = mMainPanelSize;
-            setSize(mContentContainer, containerSize);
-            mMainPanel.setAlpha(1);
-            mMainPanel.setVisibility(View.VISIBLE);
-            mOverflowPanel.setAlpha(0);
-            mOverflowPanel.setVisibility(View.INVISIBLE);
-            mOverflowButton.setImageDrawable(mOverflow);
-            mOverflowButton.setContentDescription(mContext.getString(
-                    R.string.floating_toolbar_open_overflow_description));
-
-            if (hasOverflow()) {
-                // Update x-coordinates depending on RTL state.
-                if (isInRTLMode()) {
-                    mContentContainer.setX(mMarginHorizontal);  // align left
-                    mMainPanel.setX(0);  // align left
-                    mOverflowButton.setX(0);  // align left
-                    mOverflowPanel.setX(0);  // align left
-                } else {
-                    mContentContainer.setX(// align right
-                            mPopupWidth - containerSize.getWidth() - mMarginHorizontal);
-                    mMainPanel.setX(0);  // align left
-                    mOverflowButton.setX(// align right
-                            containerSize.getWidth() - mOverflowButtonSize.getWidth());
-                    mOverflowPanel.setX(// align right
-                            containerSize.getWidth() - mOverflowPanelSize.getWidth());
-                }
-
-                // Update y-coordinates depending on overflow's open direction.
-                if (mOpenOverflowUpwards) {
-                    mContentContainer.setY(// align bottom
-                            mMarginVertical + mOverflowPanelSize.getHeight()
-                                    - containerSize.getHeight());
-                    mMainPanel.setY(0);  // align top
-                    mOverflowButton.setY(0);  // align top
-                    mOverflowPanel.setY(// align bottom
-                            containerSize.getHeight() - mOverflowPanelSize.getHeight());
-                } else {
-                    // opens downwards.
-                    mContentContainer.setY(mMarginVertical);  // align top
-                    mMainPanel.setY(0);  // align top
-                    mOverflowButton.setY(0);  // align top
-                    mOverflowPanel.setY(mOverflowButtonSize.getHeight());  // align bottom
-                }
-            } else {
-                // No overflow.
-                mContentContainer.setX(mMarginHorizontal);  // align left
-                mContentContainer.setY(mMarginVertical);  // align top
-                mMainPanel.setX(0);  // align left
-                mMainPanel.setY(0);  // align top
-            }
-        }
-    }
-
-    private void updateOverflowHeight(int suggestedHeight) {
-        if (hasOverflow()) {
-            final int maxItemSize =
-                    (suggestedHeight - mOverflowButtonSize.getHeight()) / mLineHeight;
-            final int newHeight = calculateOverflowHeight(maxItemSize);
-            if (mOverflowPanelSize.getHeight() != newHeight) {
-                mOverflowPanelSize = new Size(mOverflowPanelSize.getWidth(), newHeight);
-            }
-            setSize(mOverflowPanel, mOverflowPanelSize);
-            if (mIsOverflowOpen) {
-                setSize(mContentContainer, mOverflowPanelSize);
-                if (mOpenOverflowUpwards) {
-                    final int deltaHeight = mOverflowPanelSize.getHeight() - newHeight;
-                    mContentContainer.setY(mContentContainer.getY() + deltaHeight);
-                    mOverflowButton.setY(mOverflowButton.getY() - deltaHeight);
-                }
-            } else {
-                setSize(mContentContainer, mMainPanelSize);
-            }
-            updatePopupSize();
-        }
-    }
-
-    private void updatePopupSize() {
-        int width = 0;
-        int height = 0;
-        if (mMainPanelSize != null) {
-            width = Math.max(width, mMainPanelSize.getWidth());
-            height = Math.max(height, mMainPanelSize.getHeight());
-        }
-        if (mOverflowPanelSize != null) {
-            width = Math.max(width, mOverflowPanelSize.getWidth());
-            height = Math.max(height, mOverflowPanelSize.getHeight());
-        }
-
-        mPopupWidth = width + mMarginHorizontal * 2;
-        mPopupHeight = height + mMarginVertical * 2;
-        maybeComputeTransitionDurationScale();
-    }
-
-    private int getAdjustedToolbarWidth(int suggestedWidth) {
-        int width = suggestedWidth;
-        int maximumWidth = mViewPortOnScreen.width() - 2 * mContext.getResources()
-                .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
-        if (width <= 0) {
-            width = mContext.getResources()
-                    .getDimensionPixelSize(R.dimen.floating_toolbar_preferred_width);
-        }
-        return Math.min(width, maximumWidth);
-    }
-
-    private boolean isInRTLMode() {
-        return mContext.getApplicationInfo().hasRtlSupport()
-                && mContext.getResources().getConfiguration().getLayoutDirection()
-                == View.LAYOUT_DIRECTION_RTL;
-    }
-
-    private boolean hasOverflow() {
-        return mOverflowPanelSize != null;
-    }
-
-    /**
-     * Fits as many menu items in the main panel and returns a list of the menu items that
-     * were not fit in.
-     *
-     * @return The menu items that are not included in this main panel.
-     */
-    private List<ToolbarMenuItem> layoutMainPanelItems(List<ToolbarMenuItem> menuItems,
-            int toolbarWidth) {
-        final LinkedList<ToolbarMenuItem> remainingMenuItems = new LinkedList<>();
-        // add the overflow menu items to the end of the remainingMenuItems list.
-        final LinkedList<ToolbarMenuItem> overflowMenuItems = new LinkedList();
-        for (ToolbarMenuItem menuItem : menuItems) {
-            if (menuItem.getItemId() != android.R.id.textAssist
-                    && menuItem.getPriority() == ToolbarMenuItem.PRIORITY_OVERFLOW) {
-                overflowMenuItems.add(menuItem);
-            } else {
-                remainingMenuItems.add(menuItem);
-            }
-        }
-        remainingMenuItems.addAll(overflowMenuItems);
-
-        mMainPanel.removeAllViews();
-        mMainPanel.setPaddingRelative(0, 0, 0, 0);
-
-        int availableWidth = toolbarWidth;
-        boolean isFirstItem = true;
-        while (!remainingMenuItems.isEmpty()) {
-            ToolbarMenuItem menuItem = remainingMenuItems.peek();
-            // if this is the first item, regardless of requiresOverflow(), it should be
-            // displayed on the main panel. Otherwise all items including this one will be
-            // overflow items, and should be displayed in overflow panel.
-            if (!isFirstItem && menuItem.getPriority() == ToolbarMenuItem.PRIORITY_OVERFLOW) {
-                break;
-            }
-            final boolean showIcon = isFirstItem && menuItem.getItemId() == R.id.textAssist;
-            final View menuItemButton = createMenuItemButton(
-                    mContext, menuItem, mIconTextSpacing, showIcon);
-            if (!showIcon && menuItemButton instanceof LinearLayout) {
-                ((LinearLayout) menuItemButton).setGravity(Gravity.CENTER);
-            }
-            // Adding additional start padding for the first button to even out button spacing.
-            if (isFirstItem) {
-                menuItemButton.setPaddingRelative(
-                        (int) (1.5 * menuItemButton.getPaddingStart()),
-                        menuItemButton.getPaddingTop(),
-                        menuItemButton.getPaddingEnd(),
-                        menuItemButton.getPaddingBottom());
-            }
-            // Adding additional end padding for the last button to even out button spacing.
-            boolean isLastItem = remainingMenuItems.size() == 1;
-            if (isLastItem) {
-                menuItemButton.setPaddingRelative(
-                        menuItemButton.getPaddingStart(),
-                        menuItemButton.getPaddingTop(),
-                        (int) (1.5 * menuItemButton.getPaddingEnd()),
-                        menuItemButton.getPaddingBottom());
-            }
-            menuItemButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
-            final int menuItemButtonWidth = Math.min(
-                    menuItemButton.getMeasuredWidth(), toolbarWidth);
-            // Check if we can fit an item while reserving space for the overflowButton.
-            final boolean canFitWithOverflow =
-                    menuItemButtonWidth <= availableWidth - mOverflowButtonSize.getWidth();
-            final boolean canFitNoOverflow =
-                    isLastItem && menuItemButtonWidth <= availableWidth;
-            if (canFitWithOverflow || canFitNoOverflow) {
-                menuItemButton.setTag(menuItem);
-                menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
-                // Set tooltips for main panel items, but not overflow items (b/35726766).
-                menuItemButton.setTooltipText(menuItem.getTooltipText());
-                mMainPanel.addView(menuItemButton);
-                final ViewGroup.LayoutParams params = menuItemButton.getLayoutParams();
-                params.width = menuItemButtonWidth;
-                menuItemButton.setLayoutParams(params);
-                availableWidth -= menuItemButtonWidth;
-                remainingMenuItems.pop();
-            } else {
-                break;
-            }
-            isFirstItem = false;
-        }
-        if (!remainingMenuItems.isEmpty()) {
-            // Reserve space for overflowButton.
-            mMainPanel.setPaddingRelative(0, 0, mOverflowButtonSize.getWidth(), 0);
-        }
-        mMainPanelSize = measure(mMainPanel);
-
-        return remainingMenuItems;
-    }
-
-    private void layoutOverflowPanelItems(List<ToolbarMenuItem> menuItems) {
-        ArrayAdapter<ToolbarMenuItem> overflowPanelAdapter =
-                (ArrayAdapter<ToolbarMenuItem>) mOverflowPanel.getAdapter();
-        overflowPanelAdapter.clear();
-        final int size = menuItems.size();
-        for (int i = 0; i < size; i++) {
-            overflowPanelAdapter.add(menuItems.get(i));
-        }
-        mOverflowPanel.setAdapter(overflowPanelAdapter);
-        if (mOpenOverflowUpwards) {
-            mOverflowPanel.setY(0);
-        } else {
-            mOverflowPanel.setY(mOverflowButtonSize.getHeight());
-        }
-        int width = Math.max(getOverflowWidth(), mOverflowButtonSize.getWidth());
-        int height = calculateOverflowHeight(MAX_OVERFLOW_SIZE);
-        mOverflowPanelSize = new Size(width, height);
-        setSize(mOverflowPanel, mOverflowPanelSize);
-    }
-
-    /**
-     * Resets the content container and appropriately position it's panels.
-     */
-    private void preparePopupContent() {
-        mContentContainer.removeAllViews();
-        // Add views in the specified order so they stack up as expected.
-        // Order: overflowPanel, mainPanel, overflowButton.
-        if (hasOverflow()) {
-            mContentContainer.addView(mOverflowPanel);
-        }
-        mContentContainer.addView(mMainPanel);
-        if (hasOverflow()) {
-            mContentContainer.addView(mOverflowButton);
-        }
-        setPanelsStatesAtRestingPosition();
-
-        // The positioning of contents in RTL is wrong when the view is first rendered.
-        // Hide the view and post a runnable to recalculate positions and render the view.
-        // TODO: Investigate why this happens and fix.
-        if (isInRTLMode()) {
-            mContentContainer.setAlpha(0);
-            mContentContainer.post(mPreparePopupContentRTLHelper);
-        }
-    }
-
-    /**
-     * Clears out the panels and their container. Resets their calculated sizes.
-     */
-    private void clearPanels() {
-        mIsOverflowOpen = false;
-        mMainPanelSize = null;
-        mMainPanel.removeAllViews();
-        mOverflowPanelSize = null;
-        ArrayAdapter<ToolbarMenuItem> overflowPanelAdapter =
-                (ArrayAdapter<ToolbarMenuItem>) mOverflowPanel.getAdapter();
-        overflowPanelAdapter.clear();
-        mOverflowPanel.setAdapter(overflowPanelAdapter);
-        mContentContainer.removeAllViews();
-    }
-
-    private void positionContentYCoordinatesIfOpeningOverflowUpwards() {
-        if (mOpenOverflowUpwards) {
-            mMainPanel.setY(mContentContainer.getHeight() - mMainPanelSize.getHeight());
-            mOverflowButton.setY(mContentContainer.getHeight() - mOverflowButton.getHeight());
-            mOverflowPanel.setY(mContentContainer.getHeight() - mOverflowPanelSize.getHeight());
-        }
-    }
-
-    private int getOverflowWidth() {
-        int overflowWidth = 0;
-        final int count = mOverflowPanel.getAdapter().getCount();
-        for (int i = 0; i < count; i++) {
-            ToolbarMenuItem menuItem = (ToolbarMenuItem) mOverflowPanel.getAdapter().getItem(i);
-            overflowWidth =
-                    Math.max(mOverflowPanelViewHelper.calculateWidth(menuItem), overflowWidth);
-        }
-        return overflowWidth;
-    }
-
-    private int calculateOverflowHeight(int maxItemSize) {
-        // Maximum of 4 items, minimum of 2 if the overflow has to scroll.
-        int actualSize = Math.min(
-                MAX_OVERFLOW_SIZE,
-                Math.min(
-                        Math.max(MIN_OVERFLOW_SIZE, maxItemSize),
-                        mOverflowPanel.getCount()));
-        int extension = 0;
-        if (actualSize < mOverflowPanel.getCount()) {
-            // The overflow will require scrolling to get to all the items.
-            // Extend the height so that part of the hidden items is displayed.
-            extension = (int) (mLineHeight * 0.5f);
-        }
-        return actualSize * mLineHeight
-                + mOverflowButtonSize.getHeight()
-                + extension;
-    }
-
-    /**
-     * NOTE: Use only in android.view.animation.* animations. Do not use in android.animation.*
-     * animations. See comment about this in the code.
-     */
-    private int getAnimationDuration() {
-        if (mTransitionDurationScale < 150) {
-            // For smaller transition, decrease the time.
-            return 200;
-        } else if (mTransitionDurationScale > 300) {
-            // For bigger transition, increase the time.
-            return 300;
-        }
-
-        // Scale the animation duration with getDurationScale(). This allows
-        // android.view.animation.* animations to scale just like android.animation.* animations
-        // when  animator duration scale is adjusted in "Developer Options".
-        // For this reason, do not use this method for android.animation.* animations.
-        return (int) (250 * ValueAnimator.getDurationScale());
-    }
-
-    private void maybeComputeTransitionDurationScale() {
-        if (mMainPanelSize != null && mOverflowPanelSize != null) {
-            int w = mMainPanelSize.getWidth() - mOverflowPanelSize.getWidth();
-            int h = mOverflowPanelSize.getHeight() - mMainPanelSize.getHeight();
-            mTransitionDurationScale = (int) (Math.sqrt(w * w + h * h)
-                    / mContentContainer.getContext().getResources().getDisplayMetrics().density);
-        }
-    }
-
-    private ViewGroup createMainPanel() {
-        return new LinearLayout(mContext) {
-            @Override
-            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-                if (isOverflowAnimating()) {
-                    // Update widthMeasureSpec to make sure that this view is not clipped
-                    // as we offset its coordinates with respect to its parent.
-                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(
-                            mMainPanelSize.getWidth(),
-                            MeasureSpec.EXACTLY);
-                }
-                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-            }
-
-            @Override
-            public boolean onInterceptTouchEvent(MotionEvent ev) {
-                // Intercept the touch event while the overflow is animating.
-                return isOverflowAnimating();
-            }
-        };
-    }
-
-    private ImageButton createOverflowButton() {
-        final ImageButton overflowButton = (ImageButton) LayoutInflater.from(mContext)
-                .inflate(R.layout.floating_popup_overflow_button, null);
-        overflowButton.setImageDrawable(mOverflow);
-        overflowButton.setOnClickListener(v -> {
-            if (isShowing()) {
-                preparePopupContent();
-                WidgetInfo widgetInfo = createWidgetInfo();
-                mSurfaceControlViewHost.relayout(mPopupWidth, mPopupHeight);
-                mCallbackWrapper.onWidgetUpdated(widgetInfo);
-            }
-            if (mIsOverflowOpen) {
-                overflowButton.setImageDrawable(mToOverflow);
-                mToOverflow.start();
-                closeOverflow();
-            } else {
-                overflowButton.setImageDrawable(mToArrow);
-                mToArrow.start();
-                openOverflow();
-            }
-        });
-        return overflowButton;
-    }
-
-    private OverflowPanel createOverflowPanel() {
-        final OverflowPanel overflowPanel = new OverflowPanel(this);
-        overflowPanel.setLayoutParams(new ViewGroup.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
-        overflowPanel.setDivider(null);
-        overflowPanel.setDividerHeight(0);
-
-        final ArrayAdapter adapter =
-                new ArrayAdapter<ToolbarMenuItem>(mContext, 0) {
-                    @Override
-                    public View getView(int position, View convertView, ViewGroup parent) {
-                        return mOverflowPanelViewHelper.getView(
-                                getItem(position), mOverflowPanelSize.getWidth(), convertView);
-                    }
-                };
-        overflowPanel.setAdapter(adapter);
-        overflowPanel.setOnItemClickListener((parent, view, position, id) -> {
-            ToolbarMenuItem menuItem =
-                    (ToolbarMenuItem) overflowPanel.getAdapter().getItem(position);
-            mCallbackWrapper.onMenuItemClicked(menuItem);
-        });
-        return overflowPanel;
-    }
-
-    private boolean isOverflowAnimating() {
-        final boolean overflowOpening = mOpenOverflowAnimation.hasStarted()
-                && !mOpenOverflowAnimation.hasEnded();
-        final boolean overflowClosing = mCloseOverflowAnimation.hasStarted()
-                && !mCloseOverflowAnimation.hasEnded();
-        return overflowOpening || overflowClosing;
-    }
-
-    private Animation.AnimationListener createOverflowAnimationListener() {
-        return new Animation.AnimationListener() {
-            @Override
-            public void onAnimationStart(Animation animation) {
-                // Disable the overflow button while it's animating.
-                // It will be re-enabled when the animation stops.
-                mOverflowButton.setEnabled(false);
-                // Ensure both panels have visibility turned on when the overflow animation
-                // starts.
-                mMainPanel.setVisibility(View.VISIBLE);
-                mOverflowPanel.setVisibility(View.VISIBLE);
-            }
-
-            @Override
-            public void onAnimationEnd(Animation animation) {
-                // Posting this because it seems like this is called before the animation
-                // actually ends.
-                mContentContainer.post(() -> {
-                    setPanelsStatesAtRestingPosition();
-                });
-            }
-
-            @Override
-            public void onAnimationRepeat(Animation animation) {
-            }
-        };
-    }
-
-    private static Size measure(View view) {
-        Preconditions.checkState(view.getParent() == null);
-        view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
-        return new Size(view.getMeasuredWidth(), view.getMeasuredHeight());
-    }
-
-    private static void setSize(View view, int width, int height) {
-        view.setMinimumWidth(width);
-        view.setMinimumHeight(height);
-        ViewGroup.LayoutParams params = view.getLayoutParams();
-        params = (params == null) ? new ViewGroup.LayoutParams(0, 0) : params;
-        params.width = width;
-        params.height = height;
-        view.setLayoutParams(params);
-    }
-
-    private static void setSize(View view, Size size) {
-        setSize(view, size.getWidth(), size.getHeight());
-    }
-
-    private static void setWidth(View view, int width) {
-        ViewGroup.LayoutParams params = view.getLayoutParams();
-        setSize(view, width, params.height);
-    }
-
-    private static void setHeight(View view, int height) {
-        ViewGroup.LayoutParams params = view.getLayoutParams();
-        setSize(view, params.width, height);
-    }
-
-    /**
-     * A custom ListView for the overflow panel.
-     */
-    private static final class OverflowPanel extends ListView {
-
-        private final RemoteSelectionToolbar mPopup;
-
-        OverflowPanel(RemoteSelectionToolbar popup) {
-            super(Objects.requireNonNull(popup).mContext);
-            this.mPopup = popup;
-            setScrollBarDefaultDelayBeforeFade(ViewConfiguration.getScrollDefaultDelay() * 3);
-            setScrollIndicators(View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
-        }
-
-        @Override
-        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-            // Update heightMeasureSpec to make sure that this view is not clipped
-            // as we offset it's coordinates with respect to its parent.
-            int height = mPopup.mOverflowPanelSize.getHeight()
-                    - mPopup.mOverflowButtonSize.getHeight();
-            heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
-            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        }
-
-        @Override
-        public boolean dispatchTouchEvent(MotionEvent ev) {
-            if (mPopup.isOverflowAnimating()) {
-                // Eat the touch event.
-                return true;
-            }
-            return super.dispatchTouchEvent(ev);
-        }
-
-        @Override
-        protected boolean awakenScrollBars() {
-            return super.awakenScrollBars();
-        }
-    }
-
-    /**
-     * A custom interpolator used for various floating toolbar animations.
-     */
-    private static final class LogAccelerateInterpolator implements Interpolator {
-
-        private static final int BASE = 100;
-        private static final float LOGS_SCALE = 1f / computeLog(1, BASE);
-
-        private static float computeLog(float t, int base) {
-            return (float) (1 - Math.pow(base, -t));
-        }
-
-        @Override
-        public float getInterpolation(float t) {
-            return 1 - computeLog(1 - t, BASE) * LOGS_SCALE;
-        }
-    }
-
-    /**
-     * A helper for generating views for the overflow panel.
-     */
-    private static final class OverflowPanelViewHelper {
-        private final Context mContext;
-        private final View mCalculator;
-        private final int mIconTextSpacing;
-        private final int mSidePadding;
-
-        OverflowPanelViewHelper(Context context, int iconTextSpacing) {
-            mContext = Objects.requireNonNull(context);
-            mIconTextSpacing = iconTextSpacing;
-            mSidePadding = context.getResources()
-                    .getDimensionPixelSize(R.dimen.floating_toolbar_overflow_side_padding);
-            mCalculator = createMenuButton(null);
-        }
-
-        public View getView(ToolbarMenuItem menuItem, int minimumWidth, View convertView) {
-            Objects.requireNonNull(menuItem);
-            if (convertView != null) {
-                updateMenuItemButton(
-                        convertView, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
-            } else {
-                convertView = createMenuButton(menuItem);
-            }
-            convertView.setMinimumWidth(minimumWidth);
-            return convertView;
-        }
-
-        public int calculateWidth(ToolbarMenuItem menuItem) {
-            updateMenuItemButton(
-                    mCalculator, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
-            mCalculator.measure(
-                    View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
-            return mCalculator.getMeasuredWidth();
-        }
-
-        private View createMenuButton(ToolbarMenuItem menuItem) {
-            View button = createMenuItemButton(
-                    mContext, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
-            button.setPadding(mSidePadding, 0, mSidePadding, 0);
-            return button;
-        }
-
-        private boolean shouldShowIcon(ToolbarMenuItem menuItem) {
-            if (menuItem != null) {
-                return menuItem.getGroupId() == android.R.id.textAssist;
-            }
-            return  false;
-        }
-    }
-
-    /**
-     * Creates and returns a menu button for the specified menu item.
-     */
-    private static View createMenuItemButton(
-            Context context, ToolbarMenuItem menuItem, int iconTextSpacing, boolean showIcon) {
-        final View menuItemButton = LayoutInflater.from(context)
-                .inflate(R.layout.floating_popup_menu_button, null);
-        if (menuItem != null) {
-            updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing, showIcon);
-        }
-        return menuItemButton;
-    }
-
-    /**
-     * Updates the specified menu item button with the specified menu item data.
-     */
-    private static void updateMenuItemButton(View menuItemButton, ToolbarMenuItem menuItem,
-            int iconTextSpacing, boolean showIcon) {
-        final TextView buttonText = menuItemButton.findViewById(
-                R.id.floating_toolbar_menu_item_text);
-        buttonText.setEllipsize(null);
-        if (TextUtils.isEmpty(menuItem.getTitle())) {
-            buttonText.setVisibility(View.GONE);
-        } else {
-            buttonText.setVisibility(View.VISIBLE);
-            buttonText.setText(menuItem.getTitle());
-        }
-        final ImageView buttonIcon = menuItemButton.findViewById(
-                R.id.floating_toolbar_menu_item_image);
-        if (menuItem.getIcon() == null || !showIcon) {
-            buttonIcon.setVisibility(View.GONE);
-            buttonText.setPaddingRelative(0, 0, 0, 0);
-        } else {
-            buttonIcon.setVisibility(View.VISIBLE);
-            buttonIcon.setImageDrawable(
-                    menuItem.getIcon().loadDrawable(menuItemButton.getContext()));
-            buttonText.setPaddingRelative(iconTextSpacing, 0, 0, 0);
-        }
-        final CharSequence contentDescription = menuItem.getContentDescription();
-        if (TextUtils.isEmpty(contentDescription)) {
-            menuItemButton.setContentDescription(menuItem.getTitle());
-        } else {
-            menuItemButton.setContentDescription(contentDescription);
-        }
-    }
-
-    private static ViewGroup createContentContainer(Context context) {
-        ViewGroup contentContainer = (ViewGroup) LayoutInflater.from(context)
-                .inflate(R.layout.floating_popup_container, null);
-        contentContainer.setLayoutParams(new ViewGroup.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
-        contentContainer.setTag(FloatingToolbar.FLOATING_TOOLBAR_TAG);
-        contentContainer.setClipToOutline(true);
-        return contentContainer;
-    }
-
-    /**
-     * Creates an "appear" animation for the specified view.
-     *
-     * @param view  The view to animate
-     */
-    private static AnimatorSet createEnterAnimation(View view, Animator.AnimatorListener listener) {
-        AnimatorSet animation = new AnimatorSet();
-        animation.playTogether(
-                ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(150));
-        animation.addListener(listener);
-        return animation;
-    }
-
-    /**
-     * Creates a "disappear" animation for the specified view.
-     *
-     * @param view  The view to animate
-     * @param startDelay  The start delay of the animation
-     * @param listener  The animation listener
-     */
-    private static AnimatorSet createExitAnimation(
-            View view, int startDelay, Animator.AnimatorListener listener) {
-        AnimatorSet animation =  new AnimatorSet();
-        animation.playTogether(
-                ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(100));
-        animation.setStartDelay(startDelay);
-        if (listener != null) {
-            animation.addListener(listener);
-        }
-        return animation;
-    }
-
-    /**
-     * Returns a re-themed context with controlled look and feel for views.
-     */
-    private static Context applyDefaultTheme(Context originalContext, boolean isLightTheme) {
-        int themeId =
-                isLightTheme ? R.style.Theme_DeviceDefault_Light : R.style.Theme_DeviceDefault;
-        return new ContextThemeWrapper(originalContext, themeId);
-    }
-
-    private static void debugLog(String message) {
-        if (Log.isLoggable(FloatingToolbar.FLOATING_TOOLBAR_TAG, Log.DEBUG)) {
-            Log.v(TAG, message);
-        }
-    }
-
-    void dump(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.print("toolbar token: "); pw.println(mSelectionToolbarToken);
-        pw.print(prefix); pw.print("dismissed: "); pw.println(mDismissed);
-        pw.print(prefix); pw.print("hidden: "); pw.println(mHidden);
-        pw.print(prefix); pw.print("popup width: "); pw.println(mPopupWidth);
-        pw.print(prefix); pw.print("popup height: "); pw.println(mPopupHeight);
-        pw.print(prefix); pw.print("relative coords: "); pw.println(mRelativeCoordsForToolbar);
-        pw.print(prefix); pw.print("main panel size: "); pw.println(mMainPanelSize);
-        boolean hasOverflow = hasOverflow();
-        pw.print(prefix); pw.print("has overflow: "); pw.println(hasOverflow);
-        if (hasOverflow) {
-            pw.print(prefix); pw.print("overflow open: "); pw.println(mIsOverflowOpen);
-            pw.print(prefix); pw.print("overflow size: "); pw.println(mOverflowPanelSize);
-        }
-        if (mSurfaceControlViewHost != null) {
-            FloatingToolbarRoot root = (FloatingToolbarRoot) mSurfaceControlViewHost.getView();
-            root.dump(prefix, pw);
-        }
-        if (mMenuItems != null) {
-            int menuItemSize = mMenuItems.size();
-            pw.print(prefix); pw.print("number menu items: "); pw.println(menuItemSize);
-            for (int i = 0; i < menuItemSize; i++) {
-                pw.print(prefix); pw.print("#"); pw.println(i);
-                pw.print(prefix + "  "); pw.println(mMenuItems.get(i));
-            }
-        }
-    }
-}
diff --git a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java
deleted file mode 100644
index a10b6a8..0000000
--- a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java
+++ /dev/null
@@ -1,49 +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 android.service.selectiontoolbar;
-
-import android.view.selectiontoolbar.ToolbarMenuItem;
-import android.view.selectiontoolbar.WidgetInfo;
-
-/**
- * The callback that the render service uses to communicate with the host of the selection toolbar
- * container.
- *
- * @hide
- */
-public interface SelectionToolbarRenderCallback {
-    /**
-     * The selection toolbar is shown.
-     */
-    void onShown(WidgetInfo widgetInfo);
-    /**
-     * The selection toolbar has changed.
-     */
-    void onWidgetUpdated(WidgetInfo info);
-    /**
-     * The menu item on the selection toolbar has been clicked.
-     */
-    void onMenuItemClicked(ToolbarMenuItem item);
-    /**
-     * The toolbar doesn't be dismissed after showing on a given timeout.
-     */
-    void onToolbarShowTimeout();
-    /**
-     * The error occurred when operating on the selection toolbar.
-     */
-    void onError(int errorCode);
-}
diff --git a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java
deleted file mode 100644
index f33feae..0000000
--- a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java
+++ /dev/null
@@ -1,257 +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 android.service.selectiontoolbar;
-
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
-import android.annotation.CallSuper;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Service;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.ShowInfo;
-import android.view.selectiontoolbar.ToolbarMenuItem;
-import android.view.selectiontoolbar.WidgetInfo;
-
-/**
- * Service for rendering selection toolbar.
- *
- * @hide
- */
-public abstract class SelectionToolbarRenderService extends Service {
-
-    private static final String TAG = "SelectionToolbarRenderService";
-
-    // TODO(b/215497659): read from DeviceConfig
-    // The timeout to clean the cache if the client forgot to call dismiss()
-    private static final int CACHE_CLEAN_AFTER_SHOW_TIMEOUT_IN_MS = 10 * 60 * 1000; // 10 minutes
-
-    /**
-     * The {@link Intent} that must be declared as handled by the service.
-     *
-     * <p>To be supported, the service must also require the
-     * {@link android.Manifest.permission#BIND_SELECTION_TOOLBAR_RENDER_SERVICE} permission so
-     * that other applications can not abuse it.
-     */
-    public static final String SERVICE_INTERFACE =
-            "android.service.selectiontoolbar.SelectionToolbarRenderService";
-
-    private Handler mHandler;
-    private ISelectionToolbarRenderServiceCallback mServiceCallback;
-
-    private final SparseArray<Pair<RemoteCallbackWrapper, CleanCacheRunnable>> mCache =
-            new SparseArray<>();
-
-    /**
-     * Binder to receive calls from system server.
-     */
-    private final ISelectionToolbarRenderService mInterface =
-            new ISelectionToolbarRenderService.Stub() {
-
-        @Override
-        public void onShow(int callingUid, ShowInfo showInfo, ISelectionToolbarCallback callback) {
-            if (mCache.indexOfKey(callingUid) < 0) {
-                mCache.put(callingUid, new Pair<>(new RemoteCallbackWrapper(callback),
-                        new CleanCacheRunnable(callingUid)));
-            }
-            Pair<RemoteCallbackWrapper, CleanCacheRunnable> toolbarPair = mCache.get(callingUid);
-            CleanCacheRunnable cleanRunnable = toolbarPair.second;
-            mHandler.removeCallbacks(cleanRunnable);
-            mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onShow,
-                    SelectionToolbarRenderService.this, callingUid, showInfo,
-                    toolbarPair.first));
-            mHandler.postDelayed(cleanRunnable, CACHE_CLEAN_AFTER_SHOW_TIMEOUT_IN_MS);
-        }
-
-        @Override
-        public void onHide(long widgetToken) {
-            mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onHide,
-                    SelectionToolbarRenderService.this, widgetToken));
-        }
-
-        @Override
-        public void onDismiss(int callingUid, long widgetToken) {
-            mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onDismiss,
-                    SelectionToolbarRenderService.this, widgetToken));
-            Pair<RemoteCallbackWrapper, CleanCacheRunnable> toolbarPair = mCache.get(callingUid);
-            if (toolbarPair != null) {
-                mHandler.removeCallbacks(toolbarPair.second);
-                mCache.remove(callingUid);
-            }
-        }
-
-        @Override
-        public void onConnected(IBinder callback) {
-            mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::handleOnConnected,
-                    SelectionToolbarRenderService.this, callback));
-        }
-    };
-
-    @CallSuper
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mHandler = new Handler(Looper.getMainLooper(), null, true);
-    }
-
-    @Override
-    @Nullable
-    public final IBinder onBind(@NonNull Intent intent) {
-        if (SERVICE_INTERFACE.equals(intent.getAction())) {
-            return mInterface.asBinder();
-        }
-        Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
-        return null;
-    }
-
-    private void handleOnConnected(@NonNull IBinder callback) {
-        mServiceCallback = ISelectionToolbarRenderServiceCallback.Stub.asInterface(callback);
-    }
-
-    protected void transferTouch(@NonNull IBinder source, @NonNull IBinder target) {
-        final ISelectionToolbarRenderServiceCallback callback = mServiceCallback;
-        if (callback == null) {
-            Log.e(TAG, "transferTouch(): no server callback");
-            return;
-        }
-        try {
-            callback.transferTouch(source, target);
-        } catch (RemoteException e) {
-            // no-op
-        }
-    }
-
-    /**
-     * Called when showing the selection toolbar.
-     */
-    public abstract void onShow(int callingUid, ShowInfo showInfo,
-            RemoteCallbackWrapper callbackWrapper);
-
-    /**
-     * Called when hiding the selection toolbar.
-     */
-    public abstract void onHide(long widgetToken);
-
-
-    /**
-     * Called when dismissing the selection toolbar.
-     */
-    public abstract void onDismiss(long widgetToken);
-
-    /**
-     * Called when showing the selection toolbar for a specific timeout. This avoids the client
-     * forgot to call dismiss to clean the state.
-     */
-    public void onToolbarShowTimeout(int callingUid) {
-        // no-op
-    }
-
-    /**
-     * Callback to notify the client toolbar events.
-     */
-    public static final class RemoteCallbackWrapper implements SelectionToolbarRenderCallback {
-
-        private final ISelectionToolbarCallback mRemoteCallback;
-
-        RemoteCallbackWrapper(ISelectionToolbarCallback remoteCallback) {
-            // TODO(b/215497659): handle if the binder dies.
-            mRemoteCallback = remoteCallback;
-        }
-
-        @Override
-        public void onShown(WidgetInfo widgetInfo) {
-            try {
-                mRemoteCallback.onShown(widgetInfo);
-            } catch (RemoteException e) {
-                // no-op
-            }
-        }
-
-        @Override
-        public void onToolbarShowTimeout() {
-            try {
-                mRemoteCallback.onToolbarShowTimeout();
-            } catch (RemoteException e) {
-                // no-op
-            }
-        }
-
-        @Override
-        public void onWidgetUpdated(WidgetInfo widgetInfo) {
-            try {
-                mRemoteCallback.onWidgetUpdated(widgetInfo);
-            } catch (RemoteException e) {
-                // no-op
-            }
-        }
-
-        @Override
-        public void onMenuItemClicked(ToolbarMenuItem item) {
-            try {
-                mRemoteCallback.onMenuItemClicked(item);
-            } catch (RemoteException e) {
-                // no-op
-            }
-        }
-
-        @Override
-        public void onError(int errorCode) {
-            try {
-                mRemoteCallback.onError(errorCode);
-            } catch (RemoteException e) {
-                // no-op
-            }
-        }
-    }
-
-    private class CleanCacheRunnable implements Runnable {
-
-        int mCleanUid;
-
-        CleanCacheRunnable(int cleanUid) {
-            mCleanUid = cleanUid;
-        }
-
-        @Override
-        public void run() {
-            Pair<RemoteCallbackWrapper, CleanCacheRunnable> toolbarPair = mCache.get(mCleanUid);
-            if (toolbarPair != null) {
-                Log.w(TAG, "CleanCacheRunnable: remove " + mCleanUid + " from cache.");
-                mCache.remove(mCleanUid);
-                onToolbarShowTimeout(mCleanUid);
-            }
-        }
-    }
-
-    /**
-     * A listener to notify the service to the transfer touch focus.
-     */
-    public interface TransferTouchListener {
-        /**
-         * Notify the service to transfer the touch focus.
-         */
-        void onTransferTouch(IBinder source, IBinder target);
-    }
-}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 6371da4..ab9cff0 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -25,7 +25,6 @@
 import android.graphics.text.LineBreakConfig;
 import android.graphics.text.LineBreaker;
 import android.os.Build;
-import android.os.SystemProperties;
 import android.text.style.LeadingMarginSpan;
 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
 import android.text.style.LineHeightSpan;
@@ -33,7 +32,6 @@
 import android.util.Log;
 import android.util.Pools.SynchronizedPool;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
 
@@ -75,13 +73,6 @@
      * default values.
      */
     public final static class Builder {
-        // The content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE.
-        private static final int DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE = 3;
-
-        // The property of content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE.
-        private static final String PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE =
-                "android.phrase.linecount.threshold";
-
         private Builder() {}
 
         /**
@@ -440,55 +431,11 @@
          */
         @NonNull
         public StaticLayout build() {
-            reviseLineBreakConfig();
             StaticLayout result = new StaticLayout(this);
             Builder.recycle(this);
             return result;
         }
 
-        private void reviseLineBreakConfig() {
-            boolean autoPhraseBreaking = mLineBreakConfig.getAutoPhraseBreaking();
-            int wordStyle = mLineBreakConfig.getLineBreakWordStyle();
-            if (autoPhraseBreaking) {
-                if (wordStyle != LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) {
-                    if (shouldEnablePhraseBreaking()) {
-                        mLineBreakConfig = LineBreakConfig.getLineBreakConfig(
-                                mLineBreakConfig.getLineBreakStyle(),
-                                LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE,
-                                mLineBreakConfig.getAutoPhraseBreaking());
-                    }
-                }
-            }
-        }
-
-        private boolean shouldEnablePhraseBreaking() {
-            if (TextUtils.isEmpty(mText) || mWidth <= 0) {
-                return false;
-            }
-            int lineLimit = SystemProperties.getInt(
-                    PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE,
-                    DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE);
-            double desiredWidth = (double) Layout.getDesiredWidth(mText, mStart,
-                    mEnd, mPaint, mTextDir);
-            int lineCount = (int) Math.ceil(desiredWidth / mWidth);
-            if (lineCount > 0 && lineCount <= lineLimit) {
-                return true;
-            }
-            return false;
-        }
-
-        /**
-         * Get the line break word style.
-         *
-         * @return The current line break word style.
-         *
-         * @hide
-         */
-        @VisibleForTesting
-        public int getLineBreakWordStyle() {
-            return mLineBreakConfig.getLineBreakWordStyle();
-        }
-
         private CharSequence mText;
         private int mStart;
         private int mEnd;
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index ff7d8bb..827600c 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -86,9 +86,6 @@
     public static final String SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST =
             "settings_need_connected_ble_device_for_broadcast";
 
-    /** @hide */
-    public static final String SETTINGS_AUTO_TEXT_WRAPPING = "settings_auto_text_wrapping";
-
     /**
      * Enable new language and keyboard settings UI
      * @hide
@@ -152,6 +149,13 @@
      */
     public static final String SETTINGS_BIOMETRICS2_ENROLLMENT = "settings_biometrics2_enrollment";
 
+    /**
+     * Flag to enable/disable FingerprintSettings v2
+     * @hide
+     */
+    public static final String SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS =
+            "settings_biometrics2_fingerprint";
+
     /** Flag to enable/disable entire page in Accessibility -> Hearing aids
      *  @hide
      */
@@ -225,7 +229,6 @@
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
         DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
         DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true");
-        DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true");
@@ -243,6 +246,7 @@
         DEFAULT_FLAGS.put(SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "true");
         DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true");
+        DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
@@ -253,7 +257,6 @@
         PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
         PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
         PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
-        PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING);
         PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI);
         PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
         PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD);
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index dc06671..1ed5d3f 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -23,6 +23,9 @@
 import android.hardware.HardwareBuffer;
 import android.window.SurfaceSyncGroup;
 
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
 /**
  * Provides an interface to the root-Surface of a View Hierarchy or Window. This
  * is used in combination with the {@link android.view.SurfaceControl} API to enable
@@ -167,4 +170,40 @@
      */
     default void setChildBoundingInsets(@NonNull Rect insets) {
     }
+
+    /**
+     * Add a trusted presentation listener on the SurfaceControl associated with this window.
+     *
+     * @param t          Transaction that the trusted presentation listener is added on. This should
+     *                   be applied by the caller.
+     * @param thresholds The {@link SurfaceControl.TrustedPresentationThresholds} that will specify
+     *                   when the to invoke the callback.
+     * @param executor   The {@link Executor} where the callback will be invoked on.
+     * @param listener   The {@link Consumer} that will receive the callbacks when entered or
+     *                   exited the threshold.
+     *
+     * @see SurfaceControl.Transaction#setTrustedPresentationCallback(SurfaceControl,
+     * SurfaceControl.TrustedPresentationThresholds, Executor, Consumer)
+     *
+     * @hide b/287076178 un-hide with API bump
+     */
+    default void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
+            @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
+    }
+
+    /**
+     * Remove a trusted presentation listener on the SurfaceControl associated with this window.
+     *
+     * @param t          Transaction that the trusted presentation listener removed on. This should
+     *                   be applied by the caller.
+     * @param listener   The {@link Consumer} that was previously registered with
+     *                   addTrustedPresentationCallback that should be removed.
+     *
+     * @see SurfaceControl.Transaction#clearTrustedPresentationCallback(SurfaceControl)
+     * @hide b/287076178 un-hide with API bump
+     */
+    default void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+            @NonNull Consumer<Boolean> listener) {
+    }
 }
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index ab58306b..4cfec99 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -208,6 +208,7 @@
                             candidateView.getHandwritingDelegatorCallback().run();
                             mState.mHasPreparedHandwritingDelegation = true;
                         } else {
+                            mState.mPendingConnectedView = new WeakReference<>(candidateView);
                             requestFocusWithoutReveal(candidateView);
                         }
                     }
@@ -269,8 +270,9 @@
                 mShowHoverIconForConnectedView = false;
                 return;
             }
-            if (mState != null && mState.mShouldInitHandwriting) {
-                tryStartHandwriting();
+            if (mState != null && mState.mPendingConnectedView != null
+                    && mState.mPendingConnectedView.get() == view) {
+                startHandwriting(view);
             }
         }
     }
@@ -295,40 +297,6 @@
         }
     }
 
-    /**
-     * Try to initiate handwriting. For this method to successfully send startHandwriting signal,
-     * the following 3 conditions should meet:
-     *   a) The stylus movement exceeds the touchSlop.
-     *   b) A View has built InputConnection with IME.
-     *   c) The stylus event lands into the connected View's boundary.
-     * This method will immediately fail without any side effect if condition a or b is not met.
-     * However, if both condition a and b are met but the condition c is not met, it will reset the
-     * internal states. And HandwritingInitiator won't attempt to call startHandwriting until the
-     * next ACTION_DOWN.
-     */
-    private void tryStartHandwriting() {
-        if (!mState.mExceedHandwritingSlop) {
-            return;
-        }
-        final View connectedView = getConnectedView();
-        if (connectedView == null) {
-            return;
-        }
-
-        if (!connectedView.isAutoHandwritingEnabled()) {
-            clearConnectedView();
-            return;
-        }
-
-        final Rect handwritingArea = getViewHandwritingArea(connectedView);
-        if (isInHandwritingArea(
-                handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) {
-            startHandwriting(connectedView);
-        } else {
-            mState.mShouldInitHandwriting = false;
-        }
-    }
-
     /** Starts a stylus handwriting session for the view. */
     @VisibleForTesting
     public void startHandwriting(@NonNull View view) {
@@ -631,6 +599,7 @@
         private boolean mHasInitiatedHandwriting;
 
         private boolean mHasPreparedHandwritingDelegation;
+
         /**
          * Whether the current ongoing stylus MotionEvent sequence already exceeds the
          * handwriting slop.
@@ -639,6 +608,12 @@
          */
         private boolean mExceedHandwritingSlop;
 
+        /**
+         * A view which has requested focus and is pending input connection creation. When an input
+         * connection is created for the view, a handwriting session should be started for the view.
+         */
+        private WeakReference<View> mPendingConnectedView = null;
+
         /** The pointer id of the stylus pointer that is being tracked. */
         private final int mStylusPointerId;
         /** The time stamp when the stylus pointer goes down. */
diff --git a/core/java/android/view/ISurfaceControlViewHost.aidl b/core/java/android/view/ISurfaceControlViewHost.aidl
index 15008ae..fd4b329 100644
--- a/core/java/android/view/ISurfaceControlViewHost.aidl
+++ b/core/java/android/view/ISurfaceControlViewHost.aidl
@@ -19,6 +19,7 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.view.InsetsState;
+import android.view.ISurfaceControlViewHostParent;
 import android.window.ISurfaceSyncGroup;
 
 /**
@@ -34,4 +35,9 @@
     oneway void onDispatchDetachedFromWindow();
     oneway void onInsetsChanged(in InsetsState state, in Rect insetFrame);
     ISurfaceSyncGroup getSurfaceSyncGroup();
+    /**
+     * Attaches the parent interface so the embedded content can communicate back to the parent.
+     * If null is passed in, it will remove the parent interface and no more updates will be sent.
+     */
+    oneway void attachParentInterface(in @nullable ISurfaceControlViewHostParent parentInterface);
 }
diff --git a/core/java/android/view/ISurfaceControlViewHostParent.aidl b/core/java/android/view/ISurfaceControlViewHostParent.aidl
new file mode 100644
index 0000000..f42e001
--- /dev/null
+++ b/core/java/android/view/ISurfaceControlViewHostParent.aidl
@@ -0,0 +1,27 @@
+/*
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+import android.view.WindowManager;
+
+/**
+ * API from embedded content in SurfaceControlViewHost to parent containing the embedded.
+ * {@hide}
+ */
+oneway interface ISurfaceControlViewHostParent {
+    void updateParams(in WindowManager.LayoutParams[] childAttrs);
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 209729b..6d96bb94 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -471,10 +471,23 @@
      * Requests Keyboard Shortcuts from the displayed window.
      *
      * @param receiver The receiver to deliver the results to.
+     * @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
+     *                 not triggered by a KeyEvent.
+     * @see #requestImeKeyboardShortcuts(IResultReceiver, int)
      */
     void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId);
 
     /**
+     * Requests Keyboard Shortcuts from currently selected IME.
+     *
+     * @param receiver The receiver to deliver the results to.
+     * @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
+     *                 not triggered by a KeyEvent.
+     * @see #requestAppKeyboardShortcuts(IResultReceiver, int)
+     */
+    void requestImeKeyboardShortcuts(IResultReceiver receiver, int deviceId);
+
+    /**
      * Retrieves the current stable insets from the primary display.
      */
     @UnsupportedAppUsage
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 48fb719..a436e08a 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -37,6 +37,7 @@
 import android.os.Parcelable;
 import android.os.Vibrator;
 import android.os.VibratorManager;
+import android.text.TextUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -483,10 +484,12 @@
         mSources = sources;
         mKeyboardType = keyboardType;
         mKeyCharacterMap = keyCharacterMap;
-        if (keyboardLanguageTag != null) {
-            mKeyboardLanguageTag = ULocale
+        if (!TextUtils.isEmpty(keyboardLanguageTag)) {
+            String langTag;
+            langTag = ULocale
                     .createCanonical(ULocale.forLanguageTag(keyboardLanguageTag))
                     .toLanguageTag();
+            mKeyboardLanguageTag = TextUtils.equals(langTag, "und") ? null : langTag;
         } else {
             mKeyboardLanguageTag = null;
         }
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 5019b85..4ecfc40 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -69,6 +69,7 @@
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.internal.inputmethod.ImeTracing;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.util.function.TriFunction;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -77,7 +78,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
-import java.util.function.BiFunction;
 
 /**
  * Implements {@link WindowInsetsController} on the client.
@@ -302,11 +302,9 @@
     public static final int ANIMATION_TYPE_NONE = -1;
 
     /** Running animation will show insets */
-    @VisibleForTesting
     public static final int ANIMATION_TYPE_SHOW = 0;
 
     /** Running animation will hide insets */
-    @VisibleForTesting
     public static final int ANIMATION_TYPE_HIDE = 1;
 
     /** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */
@@ -447,21 +445,21 @@
                     if (mInputMethodJankContext == null) return;
                     ImeTracker.forJank().onRequestAnimation(
                             mInputMethodJankContext,
-                            mShow ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
+                            getAnimationType(),
                             !mHasAnimationCallbacks);
                 }
 
                 @Override
                 public void onAnimationCancel(Animator animation) {
                     if (mInputMethodJankContext == null) return;
-                    ImeTracker.forJank().onCancelAnimation();
+                    ImeTracker.forJank().onCancelAnimation(getAnimationType());
                 }
 
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     onAnimationFinish();
                     if (mInputMethodJankContext == null) return;
-                    ImeTracker.forJank().onFinishAnimation();
+                    ImeTracker.forJank().onFinishAnimation(getAnimationType());
                 }
             });
             if (!mHasAnimationCallbacks) {
@@ -562,6 +560,14 @@
                 }
             }
         }
+
+        /**
+         * Returns the current animation type.
+         */
+        @AnimationType
+        private int getAnimationType() {
+            return mShow ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE;
+        }
     }
 
     /**
@@ -621,7 +627,8 @@
     private final InsetsState mLastDispatchedState = new InsetsState();
 
     private final Rect mFrame = new Rect();
-    private final BiFunction<InsetsController, InsetsSource, InsetsSourceConsumer> mConsumerCreator;
+    private final TriFunction<InsetsController, Integer, Integer, InsetsSourceConsumer>
+            mConsumerCreator;
     private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>();
     private final InsetsSourceConsumer mImeSourceConsumer;
     private final Host mHost;
@@ -689,13 +696,6 @@
 
                     // Don't change the indexes of the sources while traversing. Remove it later.
                     mPendingRemoveIndexes.add(index1);
-
-                    // Remove the consumer as well except the IME one. IME consumer should always
-                    // be there since we need to communicate with InputMethodManager no matter we
-                    // have the source or not.
-                    if (source1.getType() != ime()) {
-                        mSourceConsumers.remove(source1.getId());
-                    }
                 }
 
                 @Override
@@ -750,12 +750,12 @@
             };
 
     public InsetsController(Host host) {
-        this(host, (controller, source) -> {
-            if (source.getType() == ime()) {
-                return new ImeInsetsSourceConsumer(source.getId(), controller.mState,
+        this(host, (controller, id, type) -> {
+            if (type == ime()) {
+                return new ImeInsetsSourceConsumer(id, controller.mState,
                         Transaction::new, controller);
             } else {
-                return new InsetsSourceConsumer(source.getId(), source.getType(), controller.mState,
+                return new InsetsSourceConsumer(id, type, controller.mState,
                         Transaction::new, controller);
             }
         }, host.getHandler());
@@ -763,7 +763,7 @@
 
     @VisibleForTesting
     public InsetsController(Host host,
-            BiFunction<InsetsController, InsetsSource, InsetsSourceConsumer> consumerCreator,
+            TriFunction<InsetsController, Integer, Integer, InsetsSourceConsumer> consumerCreator,
             Handler handler) {
         mHost = host;
         mConsumerCreator = consumerCreator;
@@ -797,7 +797,7 @@
             }
 
             WindowInsets insets = state.calculateInsets(mFrame, mState /* ignoringVisibilityState*/,
-                    mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeSystemBars(),
+                    mLastInsets.isRound(), false /* alwaysConsumeSystemBars */,
                     mLastLegacySoftInputMode, mLastLegacyWindowFlags, mLastLegacySystemUiFlags,
                     mWindowType, mLastWindowingMode, null /* idSideMap */);
             mHost.dispatchWindowInsetsAnimationProgress(insets,
@@ -815,7 +815,7 @@
         };
 
         // Make mImeSourceConsumer always non-null.
-        mImeSourceConsumer = getSourceConsumer(new InsetsSource(ID_IME, ime()));
+        mImeSourceConsumer = getSourceConsumer(ID_IME, ime());
     }
 
     @VisibleForTesting
@@ -841,16 +841,15 @@
         return mLastDispatchedState;
     }
 
-    @VisibleForTesting
     public boolean onStateChanged(InsetsState state) {
         boolean stateChanged = false;
         if (!CAPTION_ON_SHELL) {
-            stateChanged = !mState.equals(state, true /* excludingCaptionInsets */,
-                    false /* excludeInvisibleIme */)
+            stateChanged = !mState.equals(state, true /* excludesCaptionBar */,
+                    false /* excludesInvisibleIme */)
                     || captionInsetsUnchanged();
         } else {
-            stateChanged = !mState.equals(state, false /* excludingCaptionInsets */,
-                    false /* excludeInvisibleIme */);
+            stateChanged = !mState.equals(state, false /* excludesCaptionBar */,
+                    false /* excludesInvisibleIme */);
         }
         if (!stateChanged && mLastDispatchedState.equals(state)) {
             return false;
@@ -863,8 +862,8 @@
         applyLocalVisibilityOverride();
         updateCompatSysUiVisibility();
 
-        if (!mState.equals(lastState, false /* excludingCaptionInsets */,
-                true /* excludeInvisibleIme */)) {
+        if (!mState.equals(lastState, false /* excludesCaptionBar */,
+                true /* excludesInvisibleIme */)) {
             if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged");
             mHost.notifyInsetsChanged();
             if (lastState.getDisplayFrame().equals(mState.getDisplayFrame())) {
@@ -893,7 +892,12 @@
                     cancelledUserAnimationTypes[0] |= type;
                 }
             }
-            getSourceConsumer(source).updateSource(source, animationType);
+            final InsetsSourceConsumer consumer = mSourceConsumers.get(source.getId());
+            if (consumer != null) {
+                consumer.updateSource(source, animationType);
+            } else {
+                mState.addSource(source);
+            }
             existingTypes |= type;
             if (source.isVisible()) {
                 visibleTypes |= type;
@@ -997,8 +1001,8 @@
 
         @InsetsType int controllableTypes = 0;
         int consumedControlCount = 0;
-        final int[] showTypes = new int[1];
-        final int[] hideTypes = new int[1];
+        final @InsetsType int[] showTypes = new int[1];
+        final @InsetsType int[] hideTypes = new int[1];
 
         // Ensure to update all existing source consumers
         for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
@@ -1014,15 +1018,12 @@
             consumer.setControl(control, showTypes, hideTypes);
         }
 
+        // Ensure to create source consumers if not available yet.
         if (consumedControlCount != mTmpControlArray.size()) {
-            // Whoops! The server sent us some controls without sending corresponding sources.
             for (int i = mTmpControlArray.size() - 1; i >= 0; i--) {
                 final InsetsSourceControl control = mTmpControlArray.valueAt(i);
-                final InsetsSourceConsumer consumer = mSourceConsumers.get(control.getId());
-                if (consumer == null) {
-                    control.release(SurfaceControl::release);
-                    Log.e(TAG, control + " has no consumer.");
-                }
+                getSourceConsumer(control.getId(), control.getType())
+                        .setControl(control, showTypes, hideTypes);
             }
         }
 
@@ -1587,6 +1588,11 @@
         if (type == ime()) {
             abortPendingImeControlRequest();
         }
+        if (consumer.getType() != ime()) {
+            // IME consumer should always be there since we need to communicate with
+            // InputMethodManager no matter we have the control or not.
+            mSourceConsumers.remove(consumer.getId());
+        }
     }
 
     private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) {
@@ -1640,21 +1646,20 @@
     }
 
     @VisibleForTesting
-    public @NonNull InsetsSourceConsumer getSourceConsumer(InsetsSource source) {
-        final int sourceId = source.getId();
-        InsetsSourceConsumer consumer = mSourceConsumers.get(sourceId);
+    public @NonNull InsetsSourceConsumer getSourceConsumer(int id, int type) {
+        InsetsSourceConsumer consumer = mSourceConsumers.get(id);
         if (consumer != null) {
             return consumer;
         }
-        if (source.getType() == ime() && mImeSourceConsumer != null) {
+        if (type == ime() && mImeSourceConsumer != null) {
             // WindowInsets.Type.ime() should be only provided by one source.
             mSourceConsumers.remove(mImeSourceConsumer.getId());
             consumer = mImeSourceConsumer;
-            consumer.setId(sourceId);
+            consumer.setId(id);
         } else {
-            consumer = mConsumerCreator.apply(this, source);
+            consumer = mConsumerCreator.apply(this, id, type);
         }
-        mSourceConsumers.put(sourceId, consumer);
+        mSourceConsumers.put(id, consumer);
         return consumer;
     }
 
@@ -1663,8 +1668,7 @@
         return mImeSourceConsumer;
     }
 
-    @VisibleForTesting
-    public void notifyVisibilityChanged() {
+    void notifyVisibilityChanged() {
         mHost.notifyInsetsChanged();
     }
 
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index 37b6c77..83bdb08 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -164,6 +164,10 @@
         return mFlags;
     }
 
+    public boolean hasFlags(@Flags int mask) {
+        return (mFlags & mask) == mask;
+    }
+
     public InsetsFrameProvider setInsetsSize(Insets insetsSize) {
         mInsetsSize = insetsSize;
         return this;
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 9fc42ff..6441186 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -58,9 +58,26 @@
      */
     public static final int FLAG_SUPPRESS_SCRIM = 1;
 
+    /**
+     * Controls whether the insets frame will be used to move {@link RoundedCorner} inward with the
+     * insets frame size when calculating the rounded corner insets to other windows.
+     *
+     * For example, task bar will draw fake rounded corners above itself, so we need to move the
+     * rounded corner up by the task bar insets size to make other windows see a rounded corner
+     * above the task bar.
+     */
+    public static final int FLAG_INSETS_ROUNDED_CORNER = 1 << 1;
+
+    /**
+     * Controls whether the insets provided by this source should be forcibly consumed.
+     */
+    public static final int FLAG_FORCE_CONSUMING = 1 << 2;
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = "FLAG_", value = {
             FLAG_SUPPRESS_SCRIM,
+            FLAG_INSETS_ROUNDED_CORNER,
+            FLAG_FORCE_CONSUMING,
     })
     public @interface Flags {}
 
@@ -78,7 +95,6 @@
     private @Nullable Rect mVisibleFrame;
 
     private boolean mVisible;
-    private boolean mInsetsRoundedCornerFrame;
 
     private final Rect mTmpFrame = new Rect();
 
@@ -98,7 +114,6 @@
                 ? new Rect(other.mVisibleFrame)
                 : null;
         mFlags = other.mFlags;
-        mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame;
     }
 
     public void set(InsetsSource other) {
@@ -108,7 +123,6 @@
                 ? new Rect(other.mVisibleFrame)
                 : null;
         mFlags = other.mFlags;
-        mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame;
     }
 
     public InsetsSource setFrame(int left, int top, int right, int bottom) {
@@ -136,6 +150,11 @@
         return this;
     }
 
+    public InsetsSource setFlags(@Flags int flags, @Flags int mask) {
+        mFlags = (mFlags & ~mask) | (flags & mask);
+        return this;
+    }
+
     public int getId() {
         return mId;
     }
@@ -160,20 +179,15 @@
         return mFlags;
     }
 
+    public boolean hasFlags(int flags) {
+        return (mFlags & flags) == flags;
+    }
+
     boolean isUserControllable() {
         // If mVisibleFrame is null, it will be the same area as mFrame.
         return mVisibleFrame == null || !mVisibleFrame.isEmpty();
     }
 
-    public boolean insetsRoundedCornerFrame() {
-        return mInsetsRoundedCornerFrame;
-    }
-
-    public InsetsSource setInsetsRoundedCornerFrame(boolean insetsRoundedCornerFrame) {
-        mInsetsRoundedCornerFrame = insetsRoundedCornerFrame;
-        return this;
-    }
-
     /**
      * Calculates the insets this source will cause to a client window.
      *
@@ -317,6 +331,12 @@
         if ((flags & FLAG_SUPPRESS_SCRIM) != 0) {
             joiner.add("SUPPRESS_SCRIM");
         }
+        if ((flags & FLAG_INSETS_ROUNDED_CORNER) != 0) {
+            joiner.add("INSETS_ROUNDED_CORNER");
+        }
+        if ((flags & FLAG_FORCE_CONSUMING) != 0) {
+            joiner.add("FORCE_CONSUMING");
+        }
         return joiner.toString();
     }
 
@@ -347,7 +367,6 @@
         }
         pw.print(" visible="); pw.print(mVisible);
         pw.print(" flags="); pw.print(flagsToString(mFlags));
-        pw.print(" insetsRoundedCornerFrame="); pw.print(mInsetsRoundedCornerFrame);
         pw.println();
     }
 
@@ -372,14 +391,12 @@
         if (mFlags != that.mFlags) return false;
         if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true;
         if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
-        if (mInsetsRoundedCornerFrame != that.mInsetsRoundedCornerFrame) return false;
         return mFrame.equals(that.mFrame);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags,
-                mInsetsRoundedCornerFrame);
+        return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags);
     }
 
     public InsetsSource(Parcel in) {
@@ -393,7 +410,6 @@
         }
         mVisible = in.readBoolean();
         mFlags = in.readInt();
-        mInsetsRoundedCornerFrame = in.readBoolean();
     }
 
     @Override
@@ -414,7 +430,6 @@
         }
         dest.writeBoolean(mVisible);
         dest.writeInt(mFlags);
-        dest.writeBoolean(mInsetsRoundedCornerFrame);
     }
 
     @Override
@@ -424,7 +439,6 @@
                 + " mFrame=" + mFrame.toShortString()
                 + " mVisible=" + mVisible
                 + " mFlags=[" + flagsToString(mFlags) + "]"
-                + (mInsetsRoundedCornerFrame ? " insetsRoundedCornerFrame" : "")
                 + "}";
     }
 
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 467d720..34b2884 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -149,9 +149,12 @@
             // Check if we need to restore server visibility.
             final InsetsSource localSource = mState.peekSource(mId);
             final InsetsSource serverSource = mController.getLastDispatchedState().peekSource(mId);
-            if (localSource != null && serverSource != null
-                    && localSource.isVisible() != serverSource.isVisible()) {
-                localSource.setVisible(serverSource.isVisible());
+            final boolean localVisible = localSource != null && localSource.isVisible();
+            final boolean serverVisible = serverSource != null && serverSource.isVisible();
+            if (localSource != null) {
+                localSource.setVisible(serverVisible);
+            }
+            if (localVisible != serverVisible) {
                 mController.notifyVisibilityChanged();
             }
         } else {
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index b55d25a..af24140 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.InsetsStateProto.DISPLAY_CUTOUT;
 import static android.view.InsetsStateProto.DISPLAY_FRAME;
 import static android.view.InsetsStateProto.SOURCES;
@@ -143,12 +145,18 @@
         boolean[] typeVisibilityMap = new boolean[Type.SIZE];
         final Rect relativeFrame = new Rect(frame);
         final Rect relativeFrameMax = new Rect(frame);
+        @InsetsType int forceConsumingTypes = 0;
         @InsetsType int suppressScrimTypes = 0;
         for (int i = mSources.size() - 1; i >= 0; i--) {
             final InsetsSource source = mSources.valueAt(i);
+            final @InsetsType int type = source.getType();
+
+            if ((source.getFlags() & InsetsSource.FLAG_FORCE_CONSUMING) != 0) {
+                forceConsumingTypes |= type;
+            }
 
             if ((source.getFlags() & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) {
-                suppressScrimTypes |= source.getType();
+                suppressScrimTypes |= type;
             }
 
             processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
@@ -156,7 +164,7 @@
 
             // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
             // target.
-            if (source.getType() != WindowInsets.Type.ime()) {
+            if (type != WindowInsets.Type.ime()) {
                 InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null
                         ? ignoringVisibilityState.peekSource(source.getId())
                         : source;
@@ -177,13 +185,13 @@
         if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) {
             compatInsetsTypes &= ~statusBars();
         }
-        if (clearsCompatInsets(windowType, legacyWindowFlags, windowingMode)
-                && !alwaysConsumeSystemBars) {
-            compatInsetsTypes = 0;
+        if (clearsCompatInsets(windowType, legacyWindowFlags, windowingMode)) {
+            // Clear all types but forceConsumingTypes.
+            compatInsetsTypes &= forceConsumingTypes;
         }
 
         return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
-                alwaysConsumeSystemBars, suppressScrimTypes, calculateRelativeCutout(frame),
+                forceConsumingTypes, suppressScrimTypes, calculateRelativeCutout(frame),
                 calculateRelativeRoundedCorners(frame),
                 calculateRelativePrivacyIndicatorBounds(frame),
                 calculateRelativeDisplayShape(frame),
@@ -220,7 +228,7 @@
         final Rect roundedCornerFrame = new Rect(mRoundedCornerFrame);
         for (int i = mSources.size() - 1; i >= 0; i--) {
             final InsetsSource source = mSources.valueAt(i);
-            if (source.insetsRoundedCornerFrame()) {
+            if (source.hasFlags(FLAG_INSETS_ROUNDED_CORNER)) {
                 final Insets insets = source.calculateInsets(roundedCornerFrame, false);
                 roundedCornerFrame.inset(insets);
             }
@@ -289,9 +297,8 @@
 
     public Insets calculateVisibleInsets(Rect frame, int windowType, int windowingMode,
             @SoftInputModeFlags int softInputMode, int windowFlags) {
-        if (clearsCompatInsets(windowType, windowFlags, windowingMode)) {
-            return Insets.NONE;
-        }
+        final boolean clearsCompatInsets = clearsCompatInsets(
+                windowType, windowFlags, windowingMode);
         final int softInputAdjustMode = softInputMode & SOFT_INPUT_MASK_ADJUST;
         final int visibleInsetsTypes = softInputAdjustMode != SOFT_INPUT_ADJUST_NOTHING
                 ? systemBars() | ime()
@@ -302,6 +309,9 @@
             if ((source.getType() & visibleInsetsTypes) == 0) {
                 continue;
             }
+            if (clearsCompatInsets && !source.hasFlags(FLAG_FORCE_CONSUMING)) {
+                continue;
+            }
             insets = Insets.max(source.calculateVisibleInsets(frame), insets);
         }
         return insets;
@@ -370,11 +380,17 @@
             @InternalInsetsSide @Nullable SparseIntArray idSideMap,
             @Nullable boolean[] typeVisibilityMap, Insets insets, int type) {
         int index = indexOf(type);
-        Insets existing = typeInsetsMap[index];
-        if (existing == null) {
-            typeInsetsMap[index] = insets;
-        } else {
-            typeInsetsMap[index] = Insets.max(existing, insets);
+
+        // Don't put Insets.NONE into typeInsetsMap. Otherwise, two WindowInsets can be considered
+        // as non-equal while they provide the same insets of each type from WindowInsets#getInsets
+        // if one WindowInsets has Insets.NONE for a type and the other has null for the same type.
+        if (!Insets.NONE.equals(insets)) {
+            Insets existing = typeInsetsMap[index];
+            if (existing == null) {
+                typeInsetsMap[index] = insets;
+            } else {
+                typeInsetsMap[index] = Insets.max(existing, insets);
+            }
         }
 
         if (typeVisibilityMap != null) {
@@ -686,15 +702,14 @@
      * An equals method can exclude the caption insets. This is useful because we assemble the
      * caption insets information on the client side, and when we communicate with server, it's
      * excluded.
-     * @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but
-     *                                           ignore the caption insets source value.
-     * @param excludeInvisibleImeFrames If {@link WindowInsets.Type#ime()} frames should be ignored
-     *                                  when IME is not visible.
+     * @param excludesCaptionBar If {@link Type#captionBar()}} should be ignored.
+     * @param excludesInvisibleIme If {@link WindowInsets.Type#ime()} should be ignored when IME is
+     *                             not visible.
      * @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise.
      */
     @VisibleForTesting
-    public boolean equals(@Nullable Object o, boolean excludingCaptionInsets,
-            boolean excludeInvisibleImeFrames) {
+    public boolean equals(@Nullable Object o, boolean excludesCaptionBar,
+            boolean excludesInvisibleIme) {
         if (this == o) { return true; }
         if (o == null || getClass() != o.getClass()) { return false; }
 
@@ -711,29 +726,35 @@
 
         final SparseArray<InsetsSource> thisSources = mSources;
         final SparseArray<InsetsSource> thatSources = state.mSources;
-        if (!excludingCaptionInsets && !excludeInvisibleImeFrames) {
+        if (!excludesCaptionBar && !excludesInvisibleIme) {
             return thisSources.contentEquals(thatSources);
         } else {
             final int thisSize = thisSources.size();
             final int thatSize = thatSources.size();
             int thisIndex = 0;
             int thatIndex = 0;
-            while (thisIndex < thisSize && thatIndex < thatSize) {
+            while (thisIndex < thisSize || thatIndex < thatSize) {
+                InsetsSource thisSource = thisIndex < thisSize
+                        ? thisSources.valueAt(thisIndex)
+                        : null;
+
                 // Seek to the next non-excluding source of ours.
-                InsetsSource thisSource = thisSources.valueAt(thisIndex);
                 while (thisSource != null
-                        && (excludingCaptionInsets && thisSource.getType() == captionBar()
-                                || excludeInvisibleImeFrames && thisSource.getType() == ime()
+                        && (excludesCaptionBar && thisSource.getType() == captionBar()
+                                || excludesInvisibleIme && thisSource.getType() == ime()
                                         && !thisSource.isVisible())) {
                     thisIndex++;
                     thisSource = thisIndex < thisSize ? thisSources.valueAt(thisIndex) : null;
                 }
 
+                InsetsSource thatSource = thatIndex < thatSize
+                        ? thatSources.valueAt(thatIndex)
+                        : null;
+
                 // Seek to the next non-excluding source of theirs.
-                InsetsSource thatSource = thatSources.valueAt(thatIndex);
                 while (thatSource != null
-                        && (excludingCaptionInsets && thatSource.getType() == captionBar()
-                                || excludeInvisibleImeFrames && thatSource.getType() == ime()
+                        && (excludesCaptionBar && thatSource.getType() == captionBar()
+                                || excludesInvisibleIme && thatSource.getType() == ime()
                                         && !thatSource.isVisible())) {
                     thatIndex++;
                     thatSource = thatIndex < thatSize ? thatSources.valueAt(thatIndex) : null;
@@ -746,7 +767,7 @@
                 thisIndex++;
                 thatIndex++;
             }
-            return thisIndex >= thisSize && thatIndex >= thatSize;
+            return true;
         }
     }
 
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 1af8ca2..c703af5 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1285,6 +1285,8 @@
      * </ul>
      * These values are relative to the state from the last event, not accumulated, so developers
      * should make sure to process this axis value for all batched historical events.
+     * <p>
+     * This axis is only set on the first pointer in a motion event.
      */
     public static final int AXIS_GESTURE_X_OFFSET = 48;
 
@@ -1304,6 +1306,8 @@
      * </ul>
      * These values are relative to the state from the last event, not accumulated, so developers
      * should make sure to process this axis value for all batched historical events.
+     * <p>
+     * This axis is only set on the first pointer in a motion event.
      */
     public static final int AXIS_GESTURE_SCROLL_X_DISTANCE = 50;
 
@@ -1324,14 +1328,29 @@
      * </ul>
      * These values are relative to the state from the last event, not accumulated, so developers
      * should make sure to process this axis value for all batched historical events.
+     * <p>
+     * This axis is only set on the first pointer in a motion event.
      */
     public static final int AXIS_GESTURE_PINCH_SCALE_FACTOR = 52;
 
+    /**
+     * Axis constant: the number of fingers being used in a multi-finger swipe gesture.
+     * <p>
+     * <ul>
+     * <li>For a touch pad, reports the number of fingers being used in a multi-finger swipe gesture
+     * (with CLASSIFICATION_MULTI_FINGER_SWIPE).
+     * </ul>
+     * <p>
+     * Since CLASSIFICATION_MULTI_FINGER_SWIPE is a hidden API, so is this axis. It is only set on
+     * the first pointer in a motion event.
+     * @hide
+     */
+    public static final int AXIS_GESTURE_SWIPE_FINGER_COUNT = 53;
+
     // NOTE: If you add a new axis here you must also add it to:
     //  frameworks/native/include/android/input.h
     //  frameworks/native/libs/input/InputEventLabels.cpp
-    //  platform/cts/tests/tests/view/src/android/view/cts/MotionEventTest.java
-    //    (testAxisFromToString)
+    //  cts/tests/tests/view/src/android/view/cts/MotionEventTest.java (testAxisFromToString)
 
     // Symbolic names of all axes.
     private static final SparseArray<String> AXIS_SYMBOLIC_NAMES = new SparseArray<String>();
@@ -1387,6 +1406,7 @@
         names.append(AXIS_GESTURE_SCROLL_X_DISTANCE, "AXIS_GESTURE_SCROLL_X_DISTANCE");
         names.append(AXIS_GESTURE_SCROLL_Y_DISTANCE, "AXIS_GESTURE_SCROLL_Y_DISTANCE");
         names.append(AXIS_GESTURE_PINCH_SCALE_FACTOR, "AXIS_GESTURE_PINCH_SCALE_FACTOR");
+        names.append(AXIS_GESTURE_SWIPE_FINGER_COUNT, "AXIS_GESTURE_SWIPE_FINGER_COUNT");
     }
 
     /**
diff --git a/core/java/android/view/NotificationTopLineView.java b/core/java/android/view/NotificationTopLineView.java
index bd20f5b..a2919f5 100644
--- a/core/java/android/view/NotificationTopLineView.java
+++ b/core/java/android/view/NotificationTopLineView.java
@@ -21,6 +21,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
+import android.os.Trace;
 import android.util.AttributeSet;
 import android.widget.RemoteViews;
 
@@ -106,6 +107,7 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Trace.beginSection("NotificationTopLineView#onMeasure");
         final int givenWidth = MeasureSpec.getSize(widthMeasureSpec);
         final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
         final boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;
@@ -161,6 +163,7 @@
                     .finish();
         }
         setMeasuredDimension(givenWidth, wrapHeight ? maxChildHeight : givenHeight);
+        Trace.endSection();
     }
 
     @Override
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c11f497..e673676 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -47,6 +47,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.gui.DropInputMode;
+import android.gui.StalledTransactionInfo;
 import android.hardware.DataSpace;
 import android.hardware.HardwareBuffer;
 import android.hardware.OverlayProperties;
@@ -292,6 +293,7 @@
             long nativeObject, long nativeTpc, TrustedPresentationThresholds thresholds);
     private static native void nativeClearTrustedPresentationCallback(long transactionObj,
             long nativeObject);
+    private static native StalledTransactionInfo nativeGetStalledTransactionInfo(int pid);
 
     /**
      * Transforms that can be applied to buffers as they are displayed to a window.
@@ -4363,4 +4365,11 @@
         callback.accept(fence);
     }
 
+    /**
+     * @hide
+     */
+    public static StalledTransactionInfo getStalledTransactionInfo(int pid) {
+        return nativeGetStalledTransactionInfo(pid);
+    }
+
 }
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index c8cf7d9..effc127 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -54,7 +54,7 @@
     private final static String TAG = "SurfaceControlViewHost";
     private final ViewRootImpl mViewRoot;
     private final CloseGuard mCloseGuard = CloseGuard.get();
-    private WindowlessWindowManager mWm;
+    private final WindowlessWindowManager mWm;
 
     private SurfaceControl mSurfaceControl;
     private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
@@ -67,9 +67,7 @@
                 return;
             }
             mViewRoot.mHandler.post(() -> {
-                if (mWm != null) {
-                    mWm.setConfiguration(configuration);
-                }
+                mWm.setConfiguration(configuration);
                 if (mViewRoot != null) {
                     mViewRoot.forceWmRelayout();
                 }
@@ -116,6 +114,11 @@
             }
             return null;
         }
+
+        @Override
+        public void attachParentInterface(@Nullable ISurfaceControlViewHostParent parentInterface) {
+            mViewRoot.mHandler.post(() -> mWm.setParentInterface(parentInterface));
+        }
     }
 
     private ISurfaceControlViewHost mRemoteInterface = new ISurfaceControlViewHostImpl();
@@ -148,10 +151,11 @@
         private SurfaceControl mSurfaceControl;
         private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
         private final IBinder mInputToken;
+        @NonNull
         private final ISurfaceControlViewHost mRemoteInterface;
 
         SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection,
-               IBinder inputToken, ISurfaceControlViewHost ri) {
+                IBinder inputToken, @NonNull ISurfaceControlViewHost ri) {
             mSurfaceControl = sc;
             mAccessibilityEmbeddedConnection = connection;
             mInputToken = inputToken;
@@ -213,6 +217,7 @@
         /**
          * @hide
          */
+        @NonNull
         public ISurfaceControlViewHost getRemoteInterface() {
             return mRemoteInterface;
         }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 0e4cf89..5b69d7f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
 import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
 import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_SUBLAYER;
 import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAYER;
@@ -40,6 +41,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.ArraySet;
 import android.util.AttributeSet;
@@ -54,6 +56,8 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Consumer;
@@ -302,6 +306,26 @@
     private SurfaceControl mBlastSurfaceControl;
     private BLASTBufferQueue mBlastBufferQueue;
 
+    private final ConcurrentLinkedQueue<WindowManager.LayoutParams> mEmbeddedWindowParams =
+            new ConcurrentLinkedQueue<>();
+
+    private final ISurfaceControlViewHostParent mSurfaceControlViewHostParent =
+            new ISurfaceControlViewHostParent.Stub() {
+        @Override
+        public void updateParams(WindowManager.LayoutParams[] childAttrs) {
+            mEmbeddedWindowParams.clear();
+            mEmbeddedWindowParams.addAll(Arrays.asList(childAttrs));
+
+            if (isAttachedToWindow()) {
+                runOnUiThread(() -> {
+                    if (mParent != null) {
+                        mParent.recomputeViewAttributes(SurfaceView.this);
+                    }
+                });
+            }
+        }
+    };
+
     public SurfaceView(Context context) {
         this(context, null);
     }
@@ -801,9 +825,18 @@
                 mBlastSurfaceControl = null;
             }
 
-            if (releaseSurfacePackage && mSurfacePackage != null) {
-                mSurfacePackage.release();
-                mSurfacePackage = null;
+            if (mSurfacePackage != null) {
+                try {
+                    mSurfacePackage.getRemoteInterface().attachParentInterface(null);
+                    mEmbeddedWindowParams.clear();
+                } catch (RemoteException e) {
+                    Log.d(TAG, "Failed to remove parent interface from SCVH. Likely SCVH is "
+                            + "already dead");
+                }
+                if (releaseSurfacePackage) {
+                    mSurfacePackage.release();
+                    mSurfacePackage = null;
+                }
             }
 
             applyTransactionOnVriDraw(transaction);
@@ -1307,7 +1340,7 @@
             mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
                     .setName(name)
                     .setLocalOwnerView(this)
-                    .setParent(viewRoot.getBoundsLayer())
+                    .setParent(viewRoot.updateAndGetBoundsLayer(surfaceUpdateTransaction))
                     .setCallsite("SurfaceView.updateSurface")
                     .setContainerLayer()
                     .build();
@@ -1854,6 +1887,12 @@
             applyTransactionOnVriDraw(transaction);
         }
         mSurfacePackage = p;
+        try {
+            mSurfacePackage.getRemoteInterface().attachParentInterface(
+                    mSurfaceControlViewHostParent);
+        } catch (RemoteException e) {
+            Log.d(TAG, "Failed to attach parent interface to SCVH. Likely SCVH is already dead.");
+        }
 
         if (isFocused()) {
             requestEmbeddedFocus(true);
@@ -2014,4 +2053,19 @@
             mBlastBufferQueue.mergeWithNextTransaction(transaction, frameNumber);
         }
     }
+
+    @Override
+    void performCollectViewAttributes(AttachInfo attachInfo, int visibility) {
+        super.performCollectViewAttributes(attachInfo, visibility);
+        if (mEmbeddedWindowParams.isEmpty()) {
+            return;
+        }
+
+        for (WindowManager.LayoutParams embeddedWindowAttr : mEmbeddedWindowParams) {
+            if ((embeddedWindowAttr.flags & FLAG_KEEP_SCREEN_ON) == FLAG_KEEP_SCREEN_ON) {
+                attachInfo.mKeepScreenOn = true;
+                break;
+            }
+        }
+    }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f1cde3b..2499be9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -21821,6 +21821,8 @@
         mCurrentAnimation = null;
 
         if ((mViewFlags & TOOLTIP) == TOOLTIP) {
+            removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+            removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
             hideTooltip();
         }
 
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index d80819f..0e7c6d1 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -181,7 +181,7 @@
     private static final int TOUCH_SLOP = 8;
 
     /** Distance a stylus touch can wander before we think the user is handwriting in dips. */
-    private static final int HANDWRITING_SLOP = 4;
+    private static final int HANDWRITING_SLOP = 2;
 
     /**
      * Defines the minimum size of the touch target for a scrollbar in dips
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d457847..1b1098d 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2043,7 +2043,10 @@
         final float x = event.getXDispatchLocation(pointerIndex);
         final float y = event.getYDispatchLocation(pointerIndex);
         if (isOnScrollbarThumb(x, y) || isDraggingScrollBar()) {
-            return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW);
+            // Return null here so that it fallbacks to the default PointerIcon for the source
+            // device. For mouse, the default PointerIcon is PointerIcon.TYPE_ARROW.
+            // For stylus, the default PointerIcon is PointerIcon.TYPE_NULL.
+            return null;
         }
         // Check what the child under the pointer says about the pointer.
         final int childrenCount = mChildrenCount;
@@ -2536,7 +2539,7 @@
         final int childrenCount = mChildrenCount;
         if (childrenCount != 0) {
             final float x = event.getXDispatchLocation(0);
-            final float y = event.getXDispatchLocation(0);
+            final float y = event.getYDispatchLocation(0);
 
             final ArrayList<View> preorderedList = buildOrderedChildList();
             final boolean customOrder = preorderedList == null
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a28be4b..6edf0e2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -311,6 +311,16 @@
             SystemProperties.getBoolean("persist.wm.debug.client_transient", false);
 
     /**
+     * Whether the client (system UI) is handling the immersive confirmation window. If
+     * {@link CLIENT_TRANSIENT} is set to true, the immersive confirmation window will always be the
+     * client instance and this flag will be ignored. Otherwise, the immersive confirmation window
+     * can be switched freely by this flag.
+     * @hide
+     */
+    public static final boolean CLIENT_IMMERSIVE_CONFIRMATION =
+            SystemProperties.getBoolean("persist.wm.debug.client_immersive_confirmation", false);
+
+    /**
      * Whether the client should compute the window frame on its own.
      * @hide
      */
@@ -716,9 +726,9 @@
 
     /**
      * Child container layer of {@code mSurface} with the same bounds as its parent, and cropped to
-     * the surface insets. This surface is created only if a client requests it via {@link
-     * #getBoundsLayer()}. By parenting to this bounds surface, child surfaces can ensure they do
-     * not draw into the surface inset region set by the parent window.
+     * the surface insets. This surface is created only if a client requests it via
+     * {@link #updateAndGetBoundsLayer(Transaction)}. By parenting to this bounds surface, child
+     * surfaces can ensure they do not draw into the surface inset region set by the parent window.
      */
     private SurfaceControl mBoundsLayer;
     private final SurfaceSession mSurfaceSession = new SurfaceSession();
@@ -1798,6 +1808,9 @@
                 // Request to update light center.
                 mAttachInfo.mNeedsUpdateLightCenter = true;
             }
+            if ((changes & WindowManager.LayoutParams.COLOR_MODE_CHANGED) != 0) {
+                invalidate();
+            }
             if (mWindowAttributes.packageName == null) {
                 mWindowAttributes.packageName = mBasePackageName;
             }
@@ -1900,9 +1913,10 @@
                 && !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
         final boolean displayChanged = mDisplay.getDisplayId() != displayId;
         final boolean compatScaleChanged = mTmpFrames.compatScale != compatScale;
+        final boolean dragResizingChanged = mPendingDragResizing != dragResizing;
         if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
                 && !displayChanged && !forceNextWindowRelayout
-                && !compatScaleChanged) {
+                && !compatScaleChanged && !dragResizingChanged) {
             return;
         }
 
@@ -2261,7 +2275,7 @@
      * <p>Parenting to this layer will ensure that its children are cropped by the view's surface
      * insets.
      */
-    public SurfaceControl getBoundsLayer() {
+    public SurfaceControl updateAndGetBoundsLayer(Transaction t) {
         if (mBoundsLayer == null) {
             mBoundsLayer = new SurfaceControl.Builder(mSurfaceSession)
                     .setContainerLayer()
@@ -2269,8 +2283,8 @@
                     .setParent(getSurfaceControl())
                     .setCallsite("ViewRootImpl.getBoundsLayer")
                     .build();
-            setBoundsLayerCrop(mTransaction);
-            mTransaction.show(mBoundsLayer).apply();
+            setBoundsLayerCrop(t);
+            t.show(mBoundsLayer);
         }
        return mBoundsLayer;
     }
@@ -3772,6 +3786,11 @@
         boolean cancelDueToPreDrawListener = mAttachInfo.mTreeObserver.dispatchOnPreDraw();
         boolean cancelAndRedraw = cancelDueToPreDrawListener
                  || (cancelDraw && mDrewOnceForSync);
+        if (cancelAndRedraw) {
+            Log.d(mTag, "Cancelling draw."
+                    + " cancelDueToPreDrawListener=" + cancelDueToPreDrawListener
+                    + " cancelDueToSync=" + (cancelDraw && mDrewOnceForSync));
+        }
         if (!cancelAndRedraw) {
             // A sync was already requested before the WMS requested sync. This means we need to
             // sync the buffer, regardless if WMS wants to sync the buffer.
@@ -3819,7 +3838,7 @@
                 }
                 mPendingTransitions.clear();
             }
-            if (!performDraw() && mActiveSurfaceSyncGroup != null) {
+            if (!performDraw(mActiveSurfaceSyncGroup) && mActiveSurfaceSyncGroup != null) {
                 mActiveSurfaceSyncGroup.markSyncReady();
             }
         }
@@ -3854,7 +3873,15 @@
         mWmsRequestSyncGroupState = WMS_SYNC_PENDING;
         mWmsRequestSyncGroup = new SurfaceSyncGroup("wmsSync-" + mTag, t -> {
             mWmsRequestSyncGroupState = WMS_SYNC_MERGED;
-            reportDrawFinished(t, seqId);
+            // See b/286355097. If the current process is not system, then invoking finishDraw on
+            // any thread is fine since once it calls into system process, finishDrawing will run
+            // on a different thread. However, when the current process is system, the finishDraw in
+            // system server will be run on the current thread, which could result in a deadlock.
+            if (mWindowSession instanceof Binder) {
+                reportDrawFinished(t, seqId);
+            } else {
+                mHandler.postAtFrontOfQueue(() -> reportDrawFinished(t, seqId));
+            }
         });
         if (DEBUG_BLAST) {
             Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName());
@@ -4582,6 +4609,10 @@
         });
     }
 
+    /**
+     * These callbacks check if the draw failed for any reason and apply
+     * those transactions directly so they don't get stuck forever.
+     */
     private void registerCallbackForPendingTransactions() {
         Transaction t = new Transaction();
         t.merge(mPendingTransaction);
@@ -4610,7 +4641,7 @@
         });
     }
 
-    private boolean performDraw() {
+    private boolean performDraw(@Nullable SurfaceSyncGroup surfaceSyncGroup) {
         mLastPerformDrawSkippedReason = null;
         if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
             mLastPerformDrawSkippedReason = "screen_off";
@@ -4620,7 +4651,7 @@
             return false;
         }
 
-        final boolean fullRedrawNeeded = mFullRedrawNeeded || mActiveSurfaceSyncGroup != null;
+        final boolean fullRedrawNeeded = mFullRedrawNeeded || surfaceSyncGroup != null;
         mFullRedrawNeeded = false;
 
         mIsDrawing = true;
@@ -4628,22 +4659,12 @@
 
         addFrameCommitCallbackIfNeeded();
 
-        boolean usingAsyncReport = isHardwareEnabled() && mActiveSurfaceSyncGroup != null;
-        if (usingAsyncReport) {
-            registerCallbacksForSync(mSyncBuffer, mActiveSurfaceSyncGroup);
-        } else if (mHasPendingTransactions) {
-            // These callbacks are only needed if there's no sync involved and there were calls to
-            // applyTransactionOnDraw. These callbacks check if the draw failed for any reason and
-            // apply those transactions directly so they don't get stuck forever.
-            registerCallbackForPendingTransactions();
-        }
-        mHasPendingTransactions = false;
+        boolean usingAsyncReport;
 
         try {
-            boolean canUseAsync = draw(fullRedrawNeeded, usingAsyncReport && mSyncBuffer);
-            if (usingAsyncReport && !canUseAsync) {
+            usingAsyncReport = draw(fullRedrawNeeded, surfaceSyncGroup, mSyncBuffer);
+            if (mAttachInfo.mThreadedRenderer != null && !usingAsyncReport) {
                 mAttachInfo.mThreadedRenderer.setFrameCallback(null);
-                usingAsyncReport = false;
             }
         } finally {
             mIsDrawing = false;
@@ -4681,10 +4702,12 @@
             }
 
             if (mSurfaceHolder != null && mSurface.isValid()) {
-                final SurfaceSyncGroup surfaceSyncGroup = mActiveSurfaceSyncGroup;
-                SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() ->
-                        mHandler.post(() -> surfaceSyncGroup.markSyncReady()));
-                mActiveSurfaceSyncGroup = null;
+                usingAsyncReport = true;
+                SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> {
+                    if (surfaceSyncGroup != null) {
+                        surfaceSyncGroup.markSyncReady();
+                    }
+                });
 
                 SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
 
@@ -4695,8 +4718,9 @@
                 }
             }
         }
-        if (mActiveSurfaceSyncGroup != null && !usingAsyncReport) {
-            mActiveSurfaceSyncGroup.markSyncReady();
+
+        if (surfaceSyncGroup != null && !usingAsyncReport) {
+            surfaceSyncGroup.markSyncReady();
         }
         if (mPerformContentCapture) {
             performContentCaptureInitialReport();
@@ -4789,7 +4813,8 @@
         }
     }
 
-    private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
+    private boolean draw(boolean fullRedrawNeeded,
+            @Nullable SurfaceSyncGroup activeSyncGroup, boolean syncBuffer) {
         Surface surface = mSurface;
         if (!surface.isValid()) {
             return false;
@@ -4933,9 +4958,19 @@
                     mAttachInfo.mThreadedRenderer.setTargetHdrSdrRatio(mRenderHdrSdrRatio);
                 }
 
-                if (forceDraw) {
-                    mAttachInfo.mThreadedRenderer.forceDrawNextFrame();
+                if (activeSyncGroup != null) {
+                    registerCallbacksForSync(syncBuffer, activeSyncGroup);
+                    if (syncBuffer) {
+                        mAttachInfo.mThreadedRenderer.forceDrawNextFrame();
+                    }
+                } else if (mHasPendingTransactions) {
+                    // Register a calback if there's no sync involved but there were calls to
+                    // applyTransactionOnDraw. If there is a sync involved, the sync callback will
+                    // handle merging the pending transaction.
+                    registerCallbackForPendingTransactions();
                 }
+                mHasPendingTransactions = false;
+
                 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
             } else {
                 // If we get here with a disabled & requested hardware renderer, something went
@@ -5481,6 +5516,7 @@
         if (desiredRatio != mDesiredHdrSdrRatio) {
             mDesiredHdrSdrRatio = desiredRatio;
             updateRenderHdrSdrRatio();
+            invalidate();
 
             if (mDesiredHdrSdrRatio < 1.01f) {
                 mDisplay.unregisterHdrSdrRatioChangedListener(mHdrSdrRatioChangedListener);
@@ -11171,7 +11207,8 @@
     @Nullable public SurfaceControl.Transaction buildReparentTransaction(
         @NonNull SurfaceControl child) {
         if (mSurfaceControl.isValid()) {
-          return new SurfaceControl.Transaction().reparent(child, getBoundsLayer());
+            Transaction t = new Transaction();
+            return t.reparent(child, updateAndGetBoundsLayer(t));
         }
         return null;
     }
@@ -11553,4 +11590,17 @@
         mChildBoundingInsetsChanged = true;
         scheduleTraversals();
     }
+
+    @Override
+    public void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+            @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
+            @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
+        t.setTrustedPresentationCallback(getSurfaceControl(), thresholds, executor, listener);
+    }
+
+    @Override
+    public void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+            @NonNull Consumer<Boolean> listener) {
+        t.clearTrustedPresentationCallback(getSurfaceControl());
+    }
 }
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 21fe87f..2f04b0c 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -658,6 +658,12 @@
         void updateStatusBarColor(int color);
 
         /**
+         * Update the status bar appearance.
+         */
+
+        void updateStatusBarAppearance(int appearance);
+
+        /**
          * Update the navigation bar color to a forced one.
          */
         void updateNavigationBarColor(int color);
@@ -1039,6 +1045,9 @@
         if (mDecorCallback != null) {
             mDecorCallback.onSystemBarAppearanceChanged(appearance);
         }
+        if (mWindowControllerCallback != null) {
+            mWindowControllerCallback.updateStatusBarAppearance(appearance);
+        }
     }
 
     /** @hide */
@@ -1482,6 +1491,11 @@
     }
 
     /** @hide */
+    public boolean shouldCloseOnTouchOutside() {
+        return mCloseOnTouchOutside;
+    }
+
+    /** @hide */
     @SuppressWarnings("HiddenAbstractMethod")
     @UnsupportedAppUsage
     public abstract void alwaysReadCloseOnTouchAttr();
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 4acaea8..57a4161 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -84,13 +84,7 @@
     @Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds;
     @Nullable private final DisplayShape mDisplayShape;
 
-    /**
-     * In multi-window we force show the navigation bar. Because we don't want that the surface size
-     * changes in this mode, we instead have a flag whether the navigation bar size should always
-     * be consumed, so the app is treated like there is no virtual navigation bar at all.
-     */
-    private final boolean mAlwaysConsumeSystemBars;
-
+    private final @InsetsType int mForceConsumingTypes;
     private final @InsetsType int mSuppressScrimTypes;
     private final boolean mSystemWindowInsetsConsumed;
     private final boolean mStableInsetsConsumed;
@@ -117,7 +111,7 @@
 
     static {
         CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null),
-                createCompatVisibilityMap(createCompatTypeMap(null)), false, false, 0, null,
+                createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, 0, null,
                 null, null, null, systemBars(), false);
     }
 
@@ -137,7 +131,8 @@
             @Nullable Insets[] typeMaxInsetsMap,
             boolean[] typeVisibilityMap,
             boolean isRound,
-            boolean alwaysConsumeSystemBars, @InsetsType int suppressScrimTypes,
+            @InsetsType int forceConsumingTypes,
+            @InsetsType int suppressScrimTypes,
             DisplayCutout displayCutout,
             RoundedCorners roundedCorners,
             PrivacyIndicatorBounds privacyIndicatorBounds,
@@ -155,7 +150,7 @@
 
         mTypeVisibilityMap = typeVisibilityMap;
         mIsRound = isRound;
-        mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+        mForceConsumingTypes = forceConsumingTypes;
         mSuppressScrimTypes = suppressScrimTypes;
         mCompatInsetsTypes = compatInsetsTypes;
         mCompatIgnoreVisibility = compatIgnoreVisibility;
@@ -178,7 +173,7 @@
         this(src.mSystemWindowInsetsConsumed ? null : src.mTypeInsetsMap,
                 src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap,
                 src.mTypeVisibilityMap, src.mIsRound,
-                src.mAlwaysConsumeSystemBars, src.mSuppressScrimTypes,
+                src.mForceConsumingTypes, src.mSuppressScrimTypes,
                 displayCutoutCopyConstructorArgument(src),
                 src.mRoundedCorners,
                 src.mPrivacyIndicatorBounds,
@@ -235,7 +230,7 @@
     /** @hide */
     @UnsupportedAppUsage
     public WindowInsets(Rect systemWindowInsets) {
-        this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, 0,
+        this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, 0,
                 null, null, null, null, systemBars(), false /* compatIgnoreVisibility */);
     }
 
@@ -556,7 +551,7 @@
         return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap,
                 mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
                 mTypeVisibilityMap,
-                mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes,
+                mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
                 null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape,
                 mCompatInsetsTypes, mCompatIgnoreVisibility);
     }
@@ -607,7 +602,7 @@
     public WindowInsets consumeSystemWindowInsets() {
         return new WindowInsets(null, null,
                 mTypeVisibilityMap,
-                mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes,
+                mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
                 // If the system window insets types contain displayCutout, we should also consume
                 // it.
                 (mCompatInsetsTypes & displayCutout()) != 0
@@ -895,8 +890,8 @@
     /**
      * @hide
      */
-    public boolean shouldAlwaysConsumeSystemBars() {
-        return mAlwaysConsumeSystemBars;
+    public @InsetsType int getForceConsumingTypes() {
+        return mForceConsumingTypes;
     }
 
     /**
@@ -930,6 +925,8 @@
         result.append("\n    ");
         result.append(mDisplayShape != null ? "displayShape=" + mDisplayShape : "");
         result.append("\n    ");
+        result.append("forceConsumingTypes=" + Type.toString(mForceConsumingTypes));
+        result.append("\n    ");
         result.append("suppressScrimTypes=" + Type.toString(mSuppressScrimTypes));
         result.append("\n    ");
         result.append("compatInsetsTypes=" + Type.toString(mCompatInsetsTypes));
@@ -1027,7 +1024,7 @@
                         ? null
                         : insetInsets(mTypeMaxInsetsMap, left, top, right, bottom),
                 mTypeVisibilityMap,
-                mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes,
+                mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
                 mDisplayCutoutConsumed
                         ? null
                         : mDisplayCutout == null
@@ -1050,7 +1047,7 @@
         WindowInsets that = (WindowInsets) o;
 
         return mIsRound == that.mIsRound
-                && mAlwaysConsumeSystemBars == that.mAlwaysConsumeSystemBars
+                && mForceConsumingTypes == that.mForceConsumingTypes
                 && mSuppressScrimTypes == that.mSuppressScrimTypes
                 && mSystemWindowInsetsConsumed == that.mSystemWindowInsetsConsumed
                 && mStableInsetsConsumed == that.mStableInsetsConsumed
@@ -1068,7 +1065,7 @@
     public int hashCode() {
         return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
                 Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
-                mAlwaysConsumeSystemBars, mSuppressScrimTypes, mSystemWindowInsetsConsumed,
+                mForceConsumingTypes, mSuppressScrimTypes, mSystemWindowInsetsConsumed,
                 mStableInsetsConsumed, mDisplayCutoutConsumed, mPrivacyIndicatorBounds,
                 mDisplayShape);
     }
@@ -1134,7 +1131,7 @@
         private DisplayShape mDisplayShape = DisplayShape.NONE;
 
         private boolean mIsRound;
-        private boolean mAlwaysConsumeSystemBars;
+        private @InsetsType int mForceConsumingTypes;
         private @InsetsType int mSuppressScrimTypes;
 
         private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds();
@@ -1162,7 +1159,7 @@
             mDisplayCutout = displayCutoutCopyConstructorArgument(insets);
             mRoundedCorners = insets.mRoundedCorners;
             mIsRound = insets.mIsRound;
-            mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars;
+            mForceConsumingTypes = insets.mForceConsumingTypes;
             mSuppressScrimTypes = insets.mSuppressScrimTypes;
             mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
             mDisplayShape = insets.mDisplayShape;
@@ -1433,7 +1430,15 @@
         /** @hide */
         @NonNull
         public Builder setAlwaysConsumeSystemBars(boolean alwaysConsumeSystemBars) {
-            mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+            // TODO (b/277891341): Remove this and related usages. This has been replaced by
+            //                     #setForceConsumingTypes.
+            return this;
+        }
+
+        /** @hide */
+        @NonNull
+        public Builder setForceConsumingTypes(@InsetsType int forceConsumingTypes) {
+            mForceConsumingTypes = forceConsumingTypes;
             return this;
         }
 
@@ -1453,7 +1458,7 @@
         public WindowInsets build() {
             return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
                     mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
-                    mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, mDisplayCutout,
+                    mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutout,
                     mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, systemBars(),
                     false /* compatIgnoreVisibility */);
         }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 157d95d..a00e797 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -913,7 +913,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION =
             "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
 
@@ -983,7 +982,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Make this public API.
     String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
             "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
 
@@ -1018,7 +1016,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
 
     /**
@@ -1056,7 +1053,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION =
             "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
 
@@ -1102,7 +1098,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH =
             "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
 
@@ -1151,7 +1146,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
             "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
 
@@ -1189,7 +1183,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE =
             "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
 
@@ -1233,7 +1226,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE =
             "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
 
@@ -1300,6 +1292,102 @@
             "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
 
     /**
+     * Application level
+     * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+     * tag that (when set to false) informs the system the app has opted out of the
+     * user-facing aspect ratio compatibility override.
+     *
+     * <p>The compatibility override enables device users to set the app's aspect
+     * ratio or force the app to fill the display regardless of the aspect
+     * ratio or orientation specified in the app manifest.
+     *
+     * <p>The aspect ratio compatibility override is exposed to users in device
+     * settings. A menu in device settings lists all apps that have not opted out of
+     * the compatibility override. Users select apps from the menu and set the
+     * app aspect ratio on a per-app basis. Typically, the menu is available
+     * only on large screen devices.
+     *
+     * <p>When users apply the aspect ratio override, the minimum aspect ratio
+     * specified in the app manifest is overridden. If users choose a
+     * full-screen aspect ratio, the orientation of the activity is forced to
+     * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER};
+     * see {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE} to
+     * disable the full-screen option only.
+     *
+     * <p>The user override is intended to improve the app experience on devices
+     * that have the ignore orientation request display setting enabled by OEMs
+     * (enables compatibility mode for fixed orientation on Android 12 (API
+     * level 31) or higher; see
+     * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
+     * Large screen app compatibility</a>
+     * for more details).
+     *
+     * <p>To opt out of the user aspect ratio compatibility override, add this property
+     * to your app manifest and set the value to {@code false}. Your app will be excluded
+     * from the list of apps in device settings, and users will not be able to override
+     * the app's aspect ratio.
+     *
+     * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE"
+     *     android:value="false"/&gt;
+     * &lt;/application&gt;
+     * </pre>
+     * @hide
+     */
+    // TODO(b/294227289): Make this public API
+    String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE =
+            "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE";
+
+    /**
+     * Application level
+     * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+     * tag that (when set to false) informs the system the app has opted out of the
+     * full-screen option of the user aspect ratio compatibility override settings. (For
+     * background information about the user aspect ratio compatibility override, see
+     * {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE}.)
+     *
+     * <p>When users apply the full-screen compatibility override, the orientation
+     * of the activity is forced to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER}.
+     *
+     * <p>The user override is intended to improve the app experience on devices
+     * that have the ignore orientation request display setting enabled by OEMs
+     * (enables compatibility mode for fixed orientation on Android 12 (API
+     * level 31) or higher; see
+     * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
+     * Large screen app compatibility</a>
+     * for more details).
+     *
+     * <p>To opt out of the full-screen option of the user aspect ratio compatibility
+     * override, add this property to your app manifest and set the value to {@code false}.
+     * Your app will have full-screen option removed from the list of user aspect ratio
+     * override options in device settings, and users will not be able to apply
+     * full-screen override to your app.
+     *
+     * <p><b>Note:</b> If {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE} is
+     * {@code false}, this property has no effect.
+     *
+     * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE"
+     *     android:value="false"/&gt;
+     * &lt;/application&gt;
+     * </pre>
+     * @hide
+     */
+    // TODO(b/294227289): Make this public API
+    String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE =
+            "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
+
+    /**
      * @hide
      */
     public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
@@ -1385,15 +1473,28 @@
             "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
 
     /**
-     * Request for keyboard shortcuts to be retrieved asynchronously.
+     * Request for app's keyboard shortcuts to be retrieved asynchronously.
      *
      * @param receiver The callback to be triggered when the result is ready.
+     * @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
+     *                 not triggered by a KeyEvent.
      *
      * @hide
      */
     public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
 
     /**
+     * Request for ime's keyboard shortcuts to be retrieved asynchronously.
+     *
+     * @param receiver The callback to be triggered when the result is ready.
+     * @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
+     *                 not triggered by a KeyEvent.
+     *
+     * @hide
+     */
+    default void requestImeKeyboardShortcuts(KeyboardShortcutsReceiver receiver, int deviceId) {};
+
+    /**
      * Return the touch region for the current IME window, or an empty region if there is none.
      *
      * @return The region of the IME that is accepting touch inputs, or null if there is no IME, no
@@ -3086,6 +3187,16 @@
         public static final int PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE = 1 << 16;
 
         /**
+         * Flag to indicate that this window is a immersive mode confirmation window. The window
+         * should be ignored when calculating insets control. This is used for prompt window
+         * triggered by insets visibility changes. If it can take over the insets control, the
+         * visibility will change unexpectedly and the window may dismiss itself. Power button panic
+         * handling will be disabled when this window exists.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW = 1 << 17;
+
+        /**
          * Flag to indicate that any window added by an application process that is of type
          * {@link #TYPE_TOAST} or that requires
          * {@link android.app.AppOpsManager#OP_SYSTEM_ALERT_WINDOW} permission should be hidden when
@@ -3229,6 +3340,7 @@
                 PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
                 PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS,
                 PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
+                PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW,
                 SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
                 PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
                 PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
@@ -3313,6 +3425,10 @@
                         equals = PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
                         name = "SUSTAINED_PERFORMANCE_MODE"),
                 @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW,
+                        equals = PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW,
+                        name = "IMMERSIVE_CONFIRMATION_WINDOW"),
+                @ViewDebug.FlagToString(
                         mask = SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
                         equals = SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
                         name = "HIDE_NON_SYSTEM_OVERLAY_WINDOWS"),
@@ -3723,6 +3839,7 @@
          * @see #ROTATION_ANIMATION_ROTATE
          * @see #ROTATION_ANIMATION_CROSSFADE
          * @see #ROTATION_ANIMATION_JUMPCUT
+         * @see #ROTATION_ANIMATION_SEAMLESS
          */
         public int rotationAnimation = ROTATION_ANIMATION_ROTATE;
 
@@ -4295,17 +4412,6 @@
         public InsetsFrameProvider[] providedInsets;
 
         /**
-         * If specified, the frame that used to calculate relative {@link RoundedCorner} will be
-         * the window frame of this window minus the insets that this window provides.
-         *
-         * Task bar will draw fake rounded corners above itself, so we need this insets to calculate
-         * correct rounded corners for this window.
-         *
-         * @hide
-         */
-        public boolean insetsRoundedCornerFrame = false;
-
-        /**
          * {@link LayoutParams} to be applied to the window when layout with a assigned rotation.
          * This will make layout during rotation change smoothly.
          *
@@ -4761,7 +4867,6 @@
             out.writeBoolean(mFitInsetsIgnoringVisibility);
             out.writeBoolean(preferMinimalPostProcessing);
             out.writeInt(mBlurBehindRadius);
-            out.writeBoolean(insetsRoundedCornerFrame);
             out.writeBoolean(mWallpaperTouchEventsEnabled);
             out.writeTypedArray(providedInsets, 0 /* parcelableFlags */);
             checkNonRecursiveParams();
@@ -4833,7 +4938,6 @@
             mFitInsetsIgnoringVisibility = in.readBoolean();
             preferMinimalPostProcessing = in.readBoolean();
             mBlurBehindRadius = in.readInt();
-            insetsRoundedCornerFrame = in.readBoolean();
             mWallpaperTouchEventsEnabled = in.readBoolean();
             providedInsets = in.createTypedArray(InsetsFrameProvider.CREATOR);
             paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
@@ -5141,11 +5245,6 @@
                 changes |= LAYOUT_CHANGED;
             }
 
-            if (insetsRoundedCornerFrame != o.insetsRoundedCornerFrame) {
-                insetsRoundedCornerFrame = o.insetsRoundedCornerFrame;
-                changes |= LAYOUT_CHANGED;
-            }
-
             if (paramsForRotation != o.paramsForRotation) {
                 if ((changes & LAYOUT_CHANGED) == 0) {
                     if (paramsForRotation != null && o.paramsForRotation != null
@@ -5383,10 +5482,6 @@
                     sb.append(prefix).append("    ").append(providedInsets[i]);
                 }
             }
-            if (insetsRoundedCornerFrame) {
-                sb.append(" insetsRoundedCornerFrame=");
-                sb.append(insetsRoundedCornerFrame);
-            }
             if (paramsForRotation != null && paramsForRotation.length != 0) {
                 sb.append(System.lineSeparator());
                 sb.append(prefix).append("  paramsForRotation:");
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index df3e0bb..b57163c 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -215,14 +215,36 @@
             @Override
             public void send(int resultCode, Bundle resultData) throws RemoteException {
                 List<KeyboardShortcutGroup> result =
-                        resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY, android.view.KeyboardShortcutGroup.class);
+                        resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY,
+                                android.view.KeyboardShortcutGroup.class);
                 receiver.onKeyboardShortcutsReceived(result);
             }
         };
         try {
             WindowManagerGlobal.getWindowManagerService()
-                .requestAppKeyboardShortcuts(resultReceiver, deviceId);
+                    .requestAppKeyboardShortcuts(resultReceiver, deviceId);
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void requestImeKeyboardShortcuts(
+            final KeyboardShortcutsReceiver receiver, int deviceId) {
+        IResultReceiver resultReceiver = new IResultReceiver.Stub() {
+            @Override
+            public void send(int resultCode, Bundle resultData) throws RemoteException {
+                List<KeyboardShortcutGroup> result =
+                        resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY,
+                                android.view.KeyboardShortcutGroup.class);
+                receiver.onKeyboardShortcutsReceived(result);
+            }
+        };
+        try {
+            WindowManagerGlobal.getWindowManagerService()
+                    .requestImeKeyboardShortcuts(resultReceiver, deviceId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index afc567e..c88048c 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -97,6 +97,12 @@
      */
     String EXTRA_START_REASON = "android.intent.extra.EXTRA_START_REASON";
 
+    /**
+     * Set to {@code true} when intent was invoked from pressing one of the brightness keys.
+     * @hide
+     */
+    String EXTRA_FROM_BRIGHTNESS_KEY = "android.intent.extra.FROM_BRIGHTNESS_KEY";
+
     // TODO: move this to a more appropriate place.
     interface PointerEventListener {
         /**
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 96bfb2d..7d3d283 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -49,7 +49,8 @@
 
     private class State {
         SurfaceControl mSurfaceControl;
-        WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+        final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+        final WindowManager.LayoutParams mLastReportedParams = new WindowManager.LayoutParams();
         int mDisplayId;
         IBinder mInputChannelToken;
         Region mInputRegion;
@@ -94,6 +95,8 @@
     private final MergedConfiguration mTmpConfig = new MergedConfiguration();
     private final WindowlessWindowLayout mLayout = new WindowlessWindowLayout();
 
+    private ISurfaceControlViewHostParent mParentInterface;
+
     public WindowlessWindowManager(Configuration c, SurfaceControl rootSurface,
             IBinder hostInputToken) {
         mRootSurface = rootSurface;
@@ -244,6 +247,7 @@
         final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE |
                         WindowManagerGlobal.ADD_FLAG_USE_BLAST;
 
+        sendLayoutParamsToParent();
         // Include whether the window is in touch mode.
         return isInTouchModeInternal(displayId) ? res | WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE
                 : res;
@@ -425,6 +429,7 @@
             outInsetsState.set(mInsetsState);
         }
 
+        sendLayoutParamsToParent();
         return 0;
     }
 
@@ -645,4 +650,45 @@
             " we shouldn't get here!");
         return false;
     }
+
+    void setParentInterface(@Nullable ISurfaceControlViewHostParent parentInterface) {
+        IBinder oldInterface = mParentInterface == null ? null : mParentInterface.asBinder();
+        IBinder newInterface = parentInterface == null ? null : parentInterface.asBinder();
+        // If the parent interface has changed, it needs to clear the last reported params so it
+        // will update the new interface with the params.
+        if (oldInterface != newInterface) {
+            clearLastReportedParams();
+        }
+        mParentInterface = parentInterface;
+        sendLayoutParamsToParent();
+    }
+
+    private void clearLastReportedParams() {
+        WindowManager.LayoutParams emptyParam = new WindowManager.LayoutParams();
+        for (State windowInfo : mStateForWindow.values()) {
+            windowInfo.mLastReportedParams.copyFrom(emptyParam);
+        }
+    }
+
+    private void sendLayoutParamsToParent() {
+        if (mParentInterface == null) {
+            return;
+        }
+        WindowManager.LayoutParams[] params =
+                new WindowManager.LayoutParams[mStateForWindow.size()];
+        int index = 0;
+        boolean hasChanges = false;
+        for (State windowInfo : mStateForWindow.values()) {
+            int changes = windowInfo.mLastReportedParams.copyFrom(windowInfo.mParams);
+            hasChanges |= (changes != 0);
+            params[index++] = windowInfo.mParams;
+        }
+
+        if (hasChanges) {
+            try {
+                mParentInterface.updateParams(params);
+            } catch (RemoteException e) {
+            }
+        }
+    }
 }
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
index 8a30f8c..a11c6d0 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
@@ -115,4 +115,13 @@
      * @param callback the interface to be called.
      */
     void setConnectionCallback(in IWindowMagnificationConnectionCallback callback);
+
+    /**
+     * Notify System UI the magnification scale on the specified display for userId is changed.
+     *
+     * @param userId the user id.
+     * @param displayId the logical display id.
+     * @param scale magnification scale.
+     */
+    void onUserMagnificationScaleChanged(int userId, int displayId, float scale);
 }
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
index adfeb6d..21b4334 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnectionCallback.aidl
@@ -57,8 +57,9 @@
      *
      * @param displayId The logical display id.
      * @param scale the target scale, or {@link Float#NaN} to leave unchanged
+     * @param updatePersistence whether the new scale should be persisted in Settings
      */
-    void onPerformScaleAction(int displayId, float scale);
+    void onPerformScaleAction(int displayId, float scale, boolean updatePersistence);
 
     /**
      * Called when the accessibility action is performed.
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 739c1bf..d729d49 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -4214,6 +4214,14 @@
         }
 
         @Override
+        public void requestHideFillUiWhenDestroyed(int sessionId, AutofillId id) {
+            final AutofillManager afm = mAfm.get();
+            if (afm != null) {
+                afm.post(() -> afm.requestHideFillUi(id, true));
+            }
+        }
+
+        @Override
         public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 51afe4c..917a974 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -79,6 +79,11 @@
     void requestHideFillUi(int sessionId, in AutofillId id);
 
     /**
+     * Requests hiding the fill UI when it's destroyed
+     */
+    void requestHideFillUiWhenDestroyed(int sessionId, in AutofillId id);
+
+    /**
      * Notifies no fill UI will be shown, and also mark the state as finished if necessary (if
      * sessionFinishedState != 0).
      */
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 668351b..2c7d326 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -52,6 +52,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
 import com.android.internal.util.SyncResultReceiver;
 
 import java.io.PrintWriter;
@@ -352,6 +353,30 @@
     public static final String DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING =
             "disable_flush_for_view_tree_appearing";
 
+    /**
+     * Enables the content protection receiver.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER =
+            "enable_content_protection_receiver";
+
+    /**
+     * Sets the size of the app blocklist for the content protection flow.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE =
+            "content_protection_apps_blocklist_size";
+
+    /**
+     * Sets the size of the in-memory ring buffer for the content protection flow.
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE =
+            "content_protection_buffer_size";
+
     /** @hide */
     @TestApi
     public static final int LOGGING_LEVEL_OFF = 0;
@@ -384,6 +409,14 @@
     public static final int DEFAULT_LOG_HISTORY_SIZE = 10;
     /** @hide */
     public static final boolean DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING = false;
+    /** @hide */
+    public static final boolean DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER = true;
+    /** @hide */
+    public static final boolean DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER = false;
+    /** @hide */
+    public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 5000;
+    /** @hide */
+    public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150;
 
     private final Object mLock = new Object();
 
@@ -414,6 +447,9 @@
     @Nullable // set on-demand by addDumpable()
     private Dumper mDumpable;
 
+    // Created here in order to live across activity and session changes
+    @Nullable private final RingBuffer<ContentCaptureEvent> mContentProtectionEventBuffer;
+
     /** @hide */
     public interface ContentCaptureClient {
         /**
@@ -424,12 +460,15 @@
     }
 
     /** @hide */
-    static class StrippedContext {
-        final String mPackageName;
-        final String mContext;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static class StrippedContext {
+        @NonNull final String mPackageName;
+        @NonNull final String mContext;
         final @UserIdInt int mUserId;
 
-        private StrippedContext(Context context) {
+        /** @hide */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+        public StrippedContext(@NonNull Context context) {
             mPackageName = context.getPackageName();
             mContext = context.toString();
             mUserId = context.getUserId();
@@ -440,6 +479,7 @@
             return mContext;
         }
 
+        @NonNull
         public String getPackageName() {
             return mPackageName;
         }
@@ -469,6 +509,16 @@
         mHandler = Handler.createAsync(Looper.getMainLooper());
 
         mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager();
+
+        if (mOptions.contentProtectionOptions.enableReceiver
+                && mOptions.contentProtectionOptions.bufferSize > 0) {
+            mContentProtectionEventBuffer =
+                    new RingBuffer(
+                            ContentCaptureEvent.class,
+                            mOptions.contentProtectionOptions.bufferSize);
+        } else {
+            mContentProtectionEventBuffer = null;
+        }
     }
 
     /**
@@ -837,6 +887,13 @@
         activity.addDumpable(mDumpable);
     }
 
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    @Nullable
+    public RingBuffer<ContentCaptureEvent> getContentProtectionEventBuffer() {
+        return mContentProtectionEventBuffer;
+    }
+
     // NOTE: ContentCaptureManager cannot implement it directly as it would be exposed as public API
     private final class Dumper implements Dumpable {
         @Override
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 62044aa..dc3d323 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -191,20 +191,22 @@
     static final long NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS = 258825825L;
 
     /** @hide */
-    @IntDef(prefix = { "FLUSH_REASON_" }, value = {
-            FLUSH_REASON_FULL,
-            FLUSH_REASON_VIEW_ROOT_ENTERED,
-            FLUSH_REASON_SESSION_STARTED,
-            FLUSH_REASON_SESSION_FINISHED,
-            FLUSH_REASON_IDLE_TIMEOUT,
-            FLUSH_REASON_TEXT_CHANGE_TIMEOUT,
-            FLUSH_REASON_SESSION_CONNECTED,
-            FLUSH_REASON_FORCE_FLUSH,
-            FLUSH_REASON_VIEW_TREE_APPEARING,
-            FLUSH_REASON_VIEW_TREE_APPEARED
-    })
+    @IntDef(
+            prefix = {"FLUSH_REASON_"},
+            value = {
+                FLUSH_REASON_FULL,
+                FLUSH_REASON_VIEW_ROOT_ENTERED,
+                FLUSH_REASON_SESSION_STARTED,
+                FLUSH_REASON_SESSION_FINISHED,
+                FLUSH_REASON_IDLE_TIMEOUT,
+                FLUSH_REASON_TEXT_CHANGE_TIMEOUT,
+                FLUSH_REASON_SESSION_CONNECTED,
+                FLUSH_REASON_FORCE_FLUSH,
+                FLUSH_REASON_VIEW_TREE_APPEARING,
+                FLUSH_REASON_VIEW_TREE_APPEARED
+            })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface FlushReason{}
+    public @interface FlushReason {}
 
     private final Object mLock = new Object();
 
@@ -686,7 +688,7 @@
             case FLUSH_REASON_VIEW_TREE_APPEARED:
                 return "VIEW_TREE_APPEARED";
             default:
-                return "UNKOWN-" + reason;
+                return "UNKNOWN-" + reason;
         }
     }
 
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index a641110..1487997 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -17,6 +17,7 @@
 package android.view.contentcapture;
 
 import android.content.ComponentName;
+import android.content.pm.ParceledListSlice;
 import android.view.contentcapture.ContentCaptureContext;
 import android.view.contentcapture.ContentCaptureEvent;
 import android.view.contentcapture.DataRemovalRequest;
@@ -108,4 +109,9 @@
      */
     void registerContentCaptureOptionsCallback(String packageName,
                                                in IContentCaptureOptionsCallback callback);
+
+    /**
+     * Notifies the system server that a login was detected.
+     */
+    void onLoginDetected(in ParceledListSlice<ContentCaptureEvent> events);
 }
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index efd50e7..2241fd5 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -52,8 +52,10 @@
 import android.util.TimeUtils;
 import android.view.autofill.AutofillId;
 import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import android.view.contentprotection.ContentProtectionEventProcessor;
 import android.view.inputmethod.BaseInputConnection;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IResultReceiver;
 
 import java.io.PrintWriter;
@@ -118,9 +120,13 @@
     /**
      * Direct interface to the service binder object - it's used to send the events, including the
      * last ones (when the session is finished)
+     *
+     * @hide
      */
-    @NonNull
-    private IContentCaptureDirectManager mDirectServiceInterface;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @Nullable
+    public IContentCaptureDirectManager mDirectServiceInterface;
+
     @Nullable
     private DeathRecipient mDirectServiceVulture;
 
@@ -131,14 +137,19 @@
     @Nullable
     private IBinder mShareableActivityToken;
 
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @Nullable
-    private ComponentName mComponentName;
+    public ComponentName mComponentName;
 
     /**
      * List of events held to be sent as a batch.
+     *
+     * @hide
      */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @Nullable
-    private ArrayList<ContentCaptureEvent> mEvents;
+    public ArrayList<ContentCaptureEvent> mEvents;
 
     // Used just for debugging purposes (on dump)
     private long mNextFlush;
@@ -157,6 +168,11 @@
     @NonNull
     private final SessionStateReceiver mSessionStateReceiver;
 
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @Nullable
+    public ContentProtectionEventProcessor mContentProtectionEventProcessor;
+
     private static class SessionStateReceiver extends IResultReceiver.Stub {
         private final WeakReference<MainContentCaptureSession> mMainSession;
 
@@ -194,8 +210,12 @@
         }
     }
 
-    protected MainContentCaptureSession(@NonNull ContentCaptureManager.StrippedContext context,
-            @NonNull ContentCaptureManager manager, @NonNull Handler handler,
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+    public MainContentCaptureSession(
+            @NonNull ContentCaptureManager.StrippedContext context,
+            @NonNull ContentCaptureManager manager,
+            @NonNull Handler handler,
             @NonNull IContentCaptureManager systemServerInterface) {
         mContext = context;
         mManager = manager;
@@ -273,15 +293,16 @@
     }
 
     /**
-     * Callback from {@code system_server} after call to
-     * {@link IContentCaptureManager#startSession(IBinder, ComponentName, String, int,
-     * IResultReceiver)}.
+     * Callback from {@code system_server} after call to {@link
+     * IContentCaptureManager#startSession(IBinder, ComponentName, String, int, IResultReceiver)}.
      *
      * @param resultCode session state
      * @param binder handle to {@code IContentCaptureDirectManager}
+     * @hide
      */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @UiThread
-    private void onSessionStarted(int resultCode, @Nullable IBinder binder) {
+    public void onSessionStarted(int resultCode, @Nullable IBinder binder) {
         if (binder != null) {
             mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
             mDirectServiceVulture = () -> {
@@ -296,6 +317,20 @@
             }
         }
 
+        // Should not be possible for mComponentName to be null here but check anyway
+        if (mManager.mOptions.contentProtectionOptions.enableReceiver
+                && mManager.getContentProtectionEventBuffer() != null
+                && mComponentName != null) {
+            mContentProtectionEventProcessor =
+                    new ContentProtectionEventProcessor(
+                            mManager.getContentProtectionEventBuffer(),
+                            mHandler,
+                            mSystemServerInterface,
+                            mComponentName.getPackageName());
+        } else {
+            mContentProtectionEventProcessor = null;
+        }
+
         if ((resultCode & STATE_DISABLED) != 0) {
             resetSession(resultCode);
         } else {
@@ -311,8 +346,10 @@
         }
     }
 
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @UiThread
-    private void sendEvent(@NonNull ContentCaptureEvent event) {
+    public void sendEvent(@NonNull ContentCaptureEvent event) {
         sendEvent(event, /* forceFlush= */ false);
     }
 
@@ -337,6 +374,25 @@
             if (sVerbose) Log.v(TAG, "handleSendEvent(): ignoring when disabled");
             return;
         }
+
+        if (isContentProtectionReceiverEnabled()) {
+            sendContentProtectionEvent(event);
+        }
+        if (isContentCaptureReceiverEnabled()) {
+            sendContentCaptureEvent(event, forceFlush);
+        }
+    }
+
+    @UiThread
+    private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) {
+        if (mContentProtectionEventProcessor != null) {
+            mContentProtectionEventProcessor.processEvent(event);
+        }
+    }
+
+    @UiThread
+    private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
+        final int eventType = event.getType();
         final int maxBufferSize = mManager.mOptions.maxBufferSize;
         if (mEvents == null) {
             if (sVerbose) {
@@ -528,9 +584,11 @@
         flush(reason);
     }
 
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     @Override
     @UiThread
-    void flush(@FlushReason int reason) {
+    public void flush(@FlushReason int reason) {
         if (mEvents == null || mEvents.size() == 0) {
             if (sVerbose) {
                 Log.v(TAG, "Don't flush for empty event buffer.");
@@ -544,6 +602,10 @@
             return;
         }
 
+        if (!isContentCaptureReceiverEnabled()) {
+            return;
+        }
+
         if (mDirectServiceInterface == null) {
             if (sVerbose) {
                 Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, "
@@ -607,8 +669,10 @@
         return new ParceledListSlice<>(events);
     }
 
+    /** hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @UiThread
-    private void destroySession() {
+    public void destroySession() {
         if (sDebug) {
             Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
                     + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
@@ -626,12 +690,15 @@
             mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
         }
         mDirectServiceInterface = null;
+        mContentProtectionEventProcessor = null;
     }
 
     // TODO(b/122454205): once we support multiple sessions, we might need to move some of these
     // clearings out.
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @UiThread
-    private void resetSession(int newState) {
+    public void resetSession(int newState) {
         if (sVerbose) {
             Log.v(TAG, "handleResetSession(" + getActivityName() + "): from "
                     + getStateAsString(mState) + " to " + getStateAsString(newState));
@@ -651,6 +718,7 @@
             }
         }
         mDirectServiceInterface = null;
+        mContentProtectionEventProcessor = null;
         mHandler.removeMessages(MSG_FLUSH);
     }
 
@@ -878,4 +946,14 @@
     private String getDebugState(@FlushReason int reason) {
         return getDebugState() + ", reason=" + getFlushReasonAsString(reason);
     }
+
+    @UiThread
+    private boolean isContentProtectionReceiverEnabled() {
+        return mManager.mOptions.contentProtectionOptions.enableReceiver;
+    }
+
+    @UiThread
+    private boolean isContentCaptureReceiverEnabled() {
+        return mManager.mOptions.enableReceiver;
+    }
 }
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index 044a31f..f218995 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -480,6 +480,11 @@
         return mLocaleList;
     }
 
+    /** @hide */
+    public void setTextIdEntry(@NonNull String textIdEntry) {
+        mTextIdEntry = textIdEntry;
+    }
+
     private void writeSelfToParcel(@NonNull Parcel parcel, int parcelFlags) {
         long nodeFlags = mFlags;
 
diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
new file mode 100644
index 0000000..b44abf3
--- /dev/null
+++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.text.InputType;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.ViewNode;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Main entry point for processing {@link ContentCaptureEvent} for the content protection flow.
+ *
+ * @hide
+ */
+public class ContentProtectionEventProcessor {
+
+    private static final String TAG = "ContentProtectionEventProcessor";
+
+    private static final List<Integer> PASSWORD_FIELD_INPUT_TYPES =
+            Collections.unmodifiableList(
+                    Arrays.asList(
+                            InputType.TYPE_NUMBER_VARIATION_PASSWORD,
+                            InputType.TYPE_TEXT_VARIATION_PASSWORD,
+                            InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
+                            InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD));
+
+    private static final List<String> PASSWORD_TEXTS =
+            Collections.unmodifiableList(
+                    Arrays.asList("password", "pass word", "code", "pin", "credential"));
+
+    private static final List<String> ADDITIONAL_SUSPICIOUS_TEXTS =
+            Collections.unmodifiableList(
+                    Arrays.asList("user", "mail", "phone", "number", "login", "log in", "sign in"));
+
+    private static final Duration MIN_DURATION_BETWEEN_FLUSHING = Duration.ofSeconds(3);
+
+    private static final String ANDROID_CLASS_NAME_PREFIX = "android.";
+
+    private static final Set<Integer> EVENT_TYPES_TO_STORE =
+            Collections.unmodifiableSet(
+                    new HashSet<>(
+                            Arrays.asList(
+                                    ContentCaptureEvent.TYPE_VIEW_APPEARED,
+                                    ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
+                                    ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED)));
+
+    private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150;
+
+    @NonNull private final RingBuffer<ContentCaptureEvent> mEventBuffer;
+
+    @NonNull private final Handler mHandler;
+
+    @NonNull private final IContentCaptureManager mContentCaptureManager;
+
+    @NonNull private final String mPackageName;
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean mPasswordFieldDetected = false;
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean mSuspiciousTextDetected = false;
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @Nullable
+    public Instant mLastFlushTime;
+
+    private int mResetLoginRemainingEventsToProcess;
+
+    public ContentProtectionEventProcessor(
+            @NonNull RingBuffer<ContentCaptureEvent> eventBuffer,
+            @NonNull Handler handler,
+            @NonNull IContentCaptureManager contentCaptureManager,
+            @NonNull String packageName) {
+        mEventBuffer = eventBuffer;
+        mHandler = handler;
+        mContentCaptureManager = contentCaptureManager;
+        mPackageName = packageName;
+    }
+
+    /** Main entry point for {@link ContentCaptureEvent} processing. */
+    @UiThread
+    public void processEvent(@NonNull ContentCaptureEvent event) {
+        if (EVENT_TYPES_TO_STORE.contains(event.getType())) {
+            storeEvent(event);
+        }
+        if (event.getType() == ContentCaptureEvent.TYPE_VIEW_APPEARED) {
+            processViewAppearedEvent(event);
+        }
+    }
+
+    @UiThread
+    private void storeEvent(@NonNull ContentCaptureEvent event) {
+        // Ensure receiver gets the package name which might not be set
+        ViewNode viewNode = (event.getViewNode() != null) ? event.getViewNode() : new ViewNode();
+        viewNode.setTextIdEntry(mPackageName);
+        event.setViewNode(viewNode);
+        mEventBuffer.append(event);
+    }
+
+    @UiThread
+    private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) {
+        mPasswordFieldDetected |= isPasswordField(event);
+        mSuspiciousTextDetected |= isSuspiciousText(event);
+        if (mPasswordFieldDetected && mSuspiciousTextDetected) {
+            loginDetected();
+        } else {
+            maybeResetLoginFlags();
+        }
+    }
+
+    @UiThread
+    private void loginDetected() {
+        if (mLastFlushTime == null
+                || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) {
+            flush();
+        }
+        resetLoginFlags();
+    }
+
+    @UiThread
+    private void resetLoginFlags() {
+        mPasswordFieldDetected = false;
+        mSuspiciousTextDetected = false;
+        mResetLoginRemainingEventsToProcess = 0;
+    }
+
+    @UiThread
+    private void maybeResetLoginFlags() {
+        if (mPasswordFieldDetected || mSuspiciousTextDetected) {
+            if (mResetLoginRemainingEventsToProcess <= 0) {
+                mResetLoginRemainingEventsToProcess = RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS;
+            } else {
+                mResetLoginRemainingEventsToProcess--;
+                if (mResetLoginRemainingEventsToProcess <= 0) {
+                    resetLoginFlags();
+                }
+            }
+        }
+    }
+
+    @UiThread
+    private void flush() {
+        mLastFlushTime = Instant.now();
+
+        // Note the thread annotations, do not move clearEvents to mHandler
+        ParceledListSlice<ContentCaptureEvent> events = clearEvents();
+        mHandler.post(() -> handlerOnLoginDetected(events));
+    }
+
+    @UiThread
+    @NonNull
+    private ParceledListSlice<ContentCaptureEvent> clearEvents() {
+        List<ContentCaptureEvent> events = Arrays.asList(mEventBuffer.toArray());
+        mEventBuffer.clear();
+        return new ParceledListSlice<>(events);
+    }
+
+    private void handlerOnLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
+        try {
+            mContentCaptureManager.onLoginDetected(events);
+        } catch (Exception ex) {
+            Log.e(TAG, "Failed to flush events for: " + mPackageName, ex);
+        }
+    }
+
+    private boolean isPasswordField(@NonNull ContentCaptureEvent event) {
+        return isPasswordField(event.getViewNode());
+    }
+
+    private boolean isPasswordField(@Nullable ViewNode viewNode) {
+        if (viewNode == null) {
+            return false;
+        }
+        return isAndroidPasswordField(viewNode) || isWebViewPasswordField(viewNode);
+    }
+
+    private boolean isAndroidPasswordField(@NonNull ViewNode viewNode) {
+        if (!isAndroidViewNode(viewNode)) {
+            return false;
+        }
+        int inputType = viewNode.getInputType();
+        return PASSWORD_FIELD_INPUT_TYPES.stream()
+                .anyMatch(passwordInputType -> (inputType & passwordInputType) != 0);
+    }
+
+    private boolean isWebViewPasswordField(@NonNull ViewNode viewNode) {
+        if (viewNode.getClassName() != null) {
+            return false;
+        }
+        return isPasswordText(ContentProtectionUtils.getViewNodeText(viewNode));
+    }
+
+    private boolean isAndroidViewNode(@NonNull ViewNode viewNode) {
+        String className = viewNode.getClassName();
+        return className != null && className.startsWith(ANDROID_CLASS_NAME_PREFIX);
+    }
+
+    private boolean isSuspiciousText(@NonNull ContentCaptureEvent event) {
+        return isSuspiciousText(ContentProtectionUtils.getEventText(event))
+                || isSuspiciousText(ContentProtectionUtils.getViewNodeText(event));
+    }
+
+    private boolean isSuspiciousText(@Nullable String text) {
+        if (text == null) {
+            return false;
+        }
+        if (isPasswordText(text)) {
+            return true;
+        }
+        String lowerCaseText = text.toLowerCase();
+        return ADDITIONAL_SUSPICIOUS_TEXTS.stream()
+                .anyMatch(suspiciousText -> lowerCaseText.contains(suspiciousText));
+    }
+
+    private boolean isPasswordText(@Nullable String text) {
+        if (text == null) {
+            return false;
+        }
+        String lowerCaseText = text.toLowerCase();
+        return PASSWORD_TEXTS.stream()
+                .anyMatch(passwordText -> lowerCaseText.contains(passwordText));
+    }
+}
diff --git a/core/java/android/view/contentprotection/ContentProtectionUtils.java b/core/java/android/view/contentprotection/ContentProtectionUtils.java
new file mode 100644
index 0000000..9abf6f1
--- /dev/null
+++ b/core/java/android/view/contentprotection/ContentProtectionUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ViewNode;
+
+/**
+ * Utilities for reading data from {@link ContentCaptureEvent} and {@link ViewNode}.
+ *
+ * @hide
+ */
+public final class ContentProtectionUtils {
+
+    /** Returns the text extracted directly from the {@link ContentCaptureEvent}, if set. */
+    @Nullable
+    public static String getEventText(@NonNull ContentCaptureEvent event) {
+        CharSequence text = event.getText();
+        if (text == null) {
+            return null;
+        }
+        return text.toString();
+    }
+
+    /** Returns the text extracted from the event's {@link ViewNode}, if set. */
+    @Nullable
+    public static String getViewNodeText(@NonNull ContentCaptureEvent event) {
+        ViewNode viewNode = event.getViewNode();
+        if (viewNode == null) {
+            return null;
+        }
+        return getViewNodeText(viewNode);
+    }
+
+    /** Returns the text extracted directly from the {@link ViewNode}, if set. */
+    @Nullable
+    public static String getViewNodeText(@NonNull ViewNode viewNode) {
+        CharSequence text = viewNode.getText();
+        if (text == null) {
+            return null;
+        }
+        return text.toString();
+    }
+}
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index ce2c180..467daa0 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -295,8 +295,8 @@
 
     @AnyThread
     static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
-            @Nullable ResultReceiver resultReceiver,
+            @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+            int lastClickToolType, @Nullable ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {
         final IInputMethodManager service = getService();
         if (service == null) {
@@ -312,7 +312,7 @@
 
     @AnyThread
     static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, int flags,
+            @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
             @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         final IInputMethodManager service = getService();
         if (service == null) {
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index f0d1019..03d1cd8 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -16,8 +16,12 @@
 
 package android.view.inputmethod;
 
+import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
+import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
+
 import static com.android.internal.inputmethod.InputMethodDebug.softInputDisplayReasonToString;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_ANIMATION;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_HIDE_ANIMATION;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_SHOW_ANIMATION;
 import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_HIDDEN;
 import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN;
 
@@ -696,20 +700,22 @@
         /**
          * Called when the animation, which is going to be monitored, starts.
          *
-         * @param jankContext context which is needed by {@link InteractionJankMonitor}
-         * @param animType {@link AnimationType}
+         * @param jankContext context which is needed by {@link InteractionJankMonitor}.
+         * @param animType the animation type.
          * @param useSeparatedThread {@code true} if the animation is handled by the app,
          *                           {@code false} if the animation will be scheduled on the
-         *                           {@link android.view.InsetsAnimationThread}
+         *                           {@link android.view.InsetsAnimationThread}.
          */
         public void onRequestAnimation(@NonNull InputMethodJankContext jankContext,
                 @AnimationType int animType, boolean useSeparatedThread) {
+            final int cujType = getImeInsetsCujFromAnimation(animType);
             if (jankContext.getDisplayContext() == null
-                    || jankContext.getTargetSurfaceControl() == null) {
+                    || jankContext.getTargetSurfaceControl() == null
+                    || cujType == -1) {
                 return;
             }
             final Configuration.Builder builder = Configuration.Builder.withSurface(
-                            CUJ_IME_INSETS_ANIMATION,
+                            cujType,
                             jankContext.getDisplayContext(),
                             jankContext.getTargetSurfaceControl())
                     .setTag(String.format(Locale.US, "%d@%d@%s", animType,
@@ -719,16 +725,44 @@
 
         /**
          * Called when the animation, which is going to be monitored, cancels.
+         *
+         * @param animType the animation type.
          */
-        public void onCancelAnimation() {
-            InteractionJankMonitor.getInstance().cancel(CUJ_IME_INSETS_ANIMATION);
+        public void onCancelAnimation(@AnimationType int animType) {
+            final int cujType = getImeInsetsCujFromAnimation(animType);
+            if (cujType == -1) {
+                InteractionJankMonitor.getInstance().cancel(cujType);
+            }
         }
 
         /**
          * Called when the animation, which is going to be monitored, ends.
+         *
+         * @param animType the animation type.
          */
-        public void onFinishAnimation() {
-            InteractionJankMonitor.getInstance().end(CUJ_IME_INSETS_ANIMATION);
+        public void onFinishAnimation(@AnimationType int animType) {
+            final int cujType = getImeInsetsCujFromAnimation(animType);
+            if (cujType != -1) {
+                InteractionJankMonitor.getInstance().end(cujType);
+            }
+        }
+
+        /**
+         * A helper method to translate animation type to CUJ type for IME animations.
+         *
+         * @param animType the animation type.
+         * @return the integer in {@link com.android.internal.jank.InteractionJankMonitor.CujType},
+         * or {@code -1} if the animation type is not supported for tracking yet.
+         */
+        private static int getImeInsetsCujFromAnimation(@AnimationType int animType) {
+            switch (animType) {
+                case ANIMATION_TYPE_SHOW:
+                    return CUJ_IME_INSETS_SHOW_ANIMATION;
+                case ANIMATION_TYPE_HIDE:
+                    return CUJ_IME_INSETS_HIDE_ANIMATION;
+                default:
+                    return -1;
+            }
         }
     }
 
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 92380ed..9340f46 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -17,6 +17,7 @@
 package android.view.inputmethod;
 
 import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -36,6 +37,8 @@
 import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
 import com.android.internal.inputmethod.InputMethodNavButtonFlags;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -269,6 +272,14 @@
      */
     @MainThread
     public void revokeSession(InputMethodSession session);
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "SHOW_" }, value = {
+            SHOW_EXPLICIT,
+            SHOW_FORCED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ShowFlags {}
     
     /**
      * Flag for {@link #showSoftInput}: this show has been explicitly
@@ -288,8 +299,6 @@
     /**
      * Request that any soft input part of the input method be shown to the user.
      *
-     * @param flags Provides additional information about the show request.
-     * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set.
      * @param resultReceiver The client requesting the show may wish to
      * be told the impact of their request, which should be supplied here.
      * The result code should be
@@ -304,7 +313,7 @@
      * @hide
      */
     @MainThread
-    public default void showSoftInputWithToken(int flags, ResultReceiver resultReceiver,
+    public default void showSoftInputWithToken(@ShowFlags int flags, ResultReceiver resultReceiver,
             IBinder showInputToken, @Nullable ImeTracker.Token statsToken) {
         showSoftInput(flags, resultReceiver);
     }
@@ -312,8 +321,6 @@
     /**
      * Request that any soft input part of the input method be shown to the user.
      * 
-     * @param flags Provides additional information about the show request.
-     * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set.
      * @param resultReceiver The client requesting the show may wish to
      * be told the impact of their request, which should be supplied here.
      * The result code should be
@@ -323,11 +330,12 @@
      * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
      */
     @MainThread
-    public void showSoftInput(int flags, ResultReceiver resultReceiver);
+    public void showSoftInput(@ShowFlags int flags, ResultReceiver resultReceiver);
 
     /**
      * Request that any soft input part of the input method be hidden from the user.
-     * @param flags Provides additional information about the show request.
+     *
+     * @param flags Provides additional information about the hide request.
      * Currently always 0.
      * @param resultReceiver The client requesting the show may wish to
      * be told the impact of their request, which should be supplied here.
@@ -350,7 +358,8 @@
 
     /**
      * Request that any soft input part of the input method be hidden from the user.
-     * @param flags Provides additional information about the show request.
+     *
+     * @param flags Provides additional information about the hide request.
      * Currently always 0.
      * @param resultReceiver The client requesting the show may wish to
      * be told the impact of their request, which should be supplied here.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3cac1e5..31abac8 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -39,6 +39,7 @@
 import android.annotation.DisplayContext;
 import android.annotation.DrawableRes;
 import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
@@ -50,6 +51,7 @@
 import android.annotation.UiThread;
 import android.annotation.UserIdInt;
 import android.app.ActivityThread;
+import android.app.PropertyInvalidatedCache;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -121,6 +123,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Proxy;
 import java.util.Arrays;
 import java.util.Collections;
@@ -536,6 +540,13 @@
     @UnsupportedAppUsage
     Rect mCursorRect = new Rect();
 
+    /** Cached value for {@link #isStylusHandwritingAvailable} for userId. */
+    @GuardedBy("mH")
+    private PropertyInvalidatedCache<Integer, Boolean> mStylusHandwritingAvailableCache;
+
+    private static final String CACHE_KEY_STYLUS_HANDWRITING_PROPERTY =
+            "cache_key.system_server.stylus_handwriting";
+
     @GuardedBy("mH")
     private int mCursorSelStart;
     @GuardedBy("mH")
@@ -662,6 +673,15 @@
     private static final int MSG_UPDATE_VIRTUAL_DISPLAY_TO_SCREEN_MATRIX = 30;
     private static final int MSG_ON_SHOW_REQUESTED = 31;
 
+    /**
+     * Calling this will invalidate Local stylus handwriting availability Cache which
+     * forces the next query in any process to recompute the cache.
+     * @hide
+     */
+    public static void invalidateLocalStylusHandwritingAvailabilityCaches() {
+        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_STYLUS_HANDWRITING_PROPERTY);
+    }
+
     private static boolean isAutofillUIShowing(View servedView) {
         AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class);
         return afm != null && afm.isAutofillUiShowing();
@@ -1577,8 +1597,21 @@
         if (fallbackContext == null) {
             return false;
         }
-
-        return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(userId);
+        boolean isAvailable;
+        synchronized (mH) {
+            if (mStylusHandwritingAvailableCache == null) {
+                mStylusHandwritingAvailableCache = new PropertyInvalidatedCache<>(
+                        4 /* maxEntries */, CACHE_KEY_STYLUS_HANDWRITING_PROPERTY) {
+                    @Override
+                    public Boolean recompute(Integer userId) {
+                        return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(
+                                userId);
+                    }
+                };
+            }
+            isAvailable = mStylusHandwritingAvailableCache.query(userId);
+        }
+        return isAvailable;
     }
 
     /**
@@ -2004,6 +2037,14 @@
         }
     }
 
+    /** @hide */
+    @IntDef(flag = true, prefix = { "SHOW_" }, value = {
+            SHOW_IMPLICIT,
+            SHOW_FORCED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ShowFlags {}
+
     /**
      * Flag for {@link #showSoftInput} to indicate that this is an implicit
      * request to show the input window, not as the result of a direct request
@@ -2035,10 +2076,8 @@
      *             {@link View#isFocused view focus}, and its containing window has
      *             {@link View#hasWindowFocus window focus}. Otherwise the call fails and
      *             returns {@code false}.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #SHOW_IMPLICIT} bit set.
      */
-    public boolean showSoftInput(View view, int flags) {
+    public boolean showSoftInput(View view, @ShowFlags int flags) {
         // Re-dispatch if there is a context mismatch.
         final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
         if (fallbackImm != null) {
@@ -2101,21 +2140,20 @@
      *             {@link View#isFocused view focus}, and its containing window has
      *             {@link View#hasWindowFocus window focus}. Otherwise the call fails and
      *             returns {@code false}.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #SHOW_IMPLICIT} bit set.
      * @param resultReceiver If non-null, this will be called by the IME when
      * it has processed your request to tell you what it has done.  The result
      * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
      * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
      * {@link #RESULT_HIDDEN}.
      */
-    public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
+    public boolean showSoftInput(View view, @ShowFlags int flags, ResultReceiver resultReceiver) {
         return showSoftInput(view, null /* statsToken */, flags, resultReceiver,
                 SoftInputShowHideReason.SHOW_SOFT_INPUT);
     }
 
-    private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, int flags,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+    private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken,
+            @ShowFlags int flags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         if (statsToken == null) {
             statsToken = ImeTracker.forLogging().onRequestShow(null /* component */,
                     Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason);
@@ -2127,7 +2165,7 @@
         // Re-dispatch if there is a context mismatch.
         final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
         if (fallbackImm != null) {
-            return fallbackImm.showSoftInput(view, flags, resultReceiver);
+            return fallbackImm.showSoftInput(view, statsToken, flags, resultReceiver, reason);
         }
 
         checkFocus();
@@ -2169,7 +2207,7 @@
      */
     @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
-    public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
+    public void showSoftInputUnchecked(@ShowFlags int flags, ResultReceiver resultReceiver) {
         synchronized (mH) {
             final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestShow(
                     null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
@@ -2200,6 +2238,14 @@
         }
     }
 
+    /** @hide */
+    @IntDef(flag = true, prefix = { "HIDE_" }, value = {
+            HIDE_IMPLICIT_ONLY,
+            HIDE_NOT_ALWAYS,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface HideFlags {}
+
     /**
      * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)}
      * to indicate that the soft input window should only be hidden if it was not explicitly shown
@@ -2221,10 +2267,8 @@
      *
      * @param windowToken The token of the window that is making the request,
      * as returned by {@link View#getWindowToken() View.getWindowToken()}.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
      */
-    public boolean hideSoftInputFromWindow(IBinder windowToken, int flags) {
+    public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags) {
         return hideSoftInputFromWindow(windowToken, flags, null);
     }
 
@@ -2246,21 +2290,19 @@
      *
      * @param windowToken The token of the window that is making the request,
      * as returned by {@link View#getWindowToken() View.getWindowToken()}.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
      * @param resultReceiver If non-null, this will be called by the IME when
      * it has processed your request to tell you what it has done.  The result
      * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
      * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
      * {@link #RESULT_HIDDEN}.
      */
-    public boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
+    public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags,
             ResultReceiver resultReceiver) {
         return hideSoftInputFromWindow(windowToken, flags, resultReceiver,
                 SoftInputShowHideReason.HIDE_SOFT_INPUT);
     }
 
-    private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
+    private boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
                 null /* component */, Process.myUid(),
@@ -2463,12 +2505,6 @@
      * If not the input window will be displayed.
      * @param windowToken The token of the window that is making the request,
      * as returned by {@link View#getWindowToken() View.getWindowToken()}.
-     * @param showFlags Provides additional operating flags.  May be
-     * 0 or have the {@link #SHOW_IMPLICIT},
-     * {@link #SHOW_FORCED} bit set.
-     * @param hideFlags Provides additional operating flags.  May be
-     * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
-     * {@link #HIDE_NOT_ALWAYS} bit set.
      *
      * @deprecated Use {@link #showSoftInput(View, int)} or
      * {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead.
@@ -2477,7 +2513,8 @@
      * has an effect if the calling app is the current IME focus.
      */
     @Deprecated
-    public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) {
+    public void toggleSoftInputFromWindow(IBinder windowToken, @ShowFlags int showFlags,
+            @HideFlags int hideFlags) {
         ImeTracing.getInstance().triggerClientDump(
                 "InputMethodManager#toggleSoftInputFromWindow", InputMethodManager.this,
                 null /* icProto */);
@@ -2495,12 +2532,6 @@
      *
      * If the input window is already displayed, it gets hidden.
      * If not the input window will be displayed.
-     * @param showFlags Provides additional operating flags.  May be
-     * 0 or have the {@link #SHOW_IMPLICIT},
-     * {@link #SHOW_FORCED} bit set.
-     * @param hideFlags Provides additional operating flags.  May be
-     * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
-     * {@link #HIDE_NOT_ALWAYS} bit set.
      *
      * @deprecated Use {@link #showSoftInput(View, int)} or
      * {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead.
@@ -2509,7 +2540,7 @@
      * has an effect if the calling app is the current IME focus.
      */
     @Deprecated
-    public void toggleSoftInput(int showFlags, int hideFlags) {
+    public void toggleSoftInput(@ShowFlags int showFlags, @HideFlags int hideFlags) {
         ImeTracing.getInstance().triggerClientDump(
                 "InputMethodManager#toggleSoftInput", InputMethodManager.this,
                 null /* icProto */);
@@ -3522,15 +3553,12 @@
      * @param token Supplies the identifying token given to an input method
      * when it was started, which allows it to perform this operation on
      * itself.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
-     * {@link #HIDE_NOT_ALWAYS} bit set.
      * @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was
      * intended for IME developers who should be accessing APIs through the service. APIs in this
      * class are intended for app developers interacting with the IME.
      */
     @Deprecated
-    public void hideSoftInputFromInputMethod(IBinder token, int flags) {
+    public void hideSoftInputFromInputMethod(IBinder token, @HideFlags int flags) {
         InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput(
                 flags, SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION);
     }
@@ -3544,15 +3572,12 @@
      * @param token Supplies the identifying token given to an input method
      * when it was started, which allows it to perform this operation on
      * itself.
-     * @param flags Provides additional operating flags.  Currently may be
-     * 0 or have the {@link #SHOW_IMPLICIT} or
-     * {@link #SHOW_FORCED} bit set.
      * @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was
      * intended for IME developers who should be accessing APIs through the service. APIs in this
      * class are intended for app developers interacting with the IME.
      */
     @Deprecated
-    public void showSoftInputFromInputMethod(IBinder token, int flags) {
+    public void showSoftInputFromInputMethod(IBinder token, @ShowFlags int flags) {
         InputMethodPrivilegedOperationsRegistry.get(token).showMySoftInput(flags);
     }
 
@@ -4361,15 +4386,14 @@
      * @param icProto {@link InputConnection} call data in proto format.
      * @hide
      */
-    @GuardedBy("mH")
     public void dumpDebug(ProtoOutputStream proto, @Nullable byte[] icProto) {
-        if (!isImeSessionAvailableLocked()) {
-            return;
-        }
-
-        proto.write(DISPLAY_ID, mDisplayId);
-        final long token = proto.start(INPUT_METHOD_MANAGER);
         synchronized (mH) {
+            if (!isImeSessionAvailableLocked()) {
+                return;
+            }
+
+            proto.write(DISPLAY_ID, mDisplayId);
+            final long token = proto.start(INPUT_METHOD_MANAGER);
             proto.write(CUR_ID, mCurBindState.mImeId);
             proto.write(FULLSCREEN_MODE, mFullscreenMode);
             proto.write(ACTIVE, mActive);
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
index af6af14..4f48cb6 100644
--- a/core/java/android/view/inputmethod/InputMethodSession.java
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -169,12 +169,6 @@
     /**
      * Toggle the soft input window.
      * Applications can toggle the state of the soft input window.
-     * @param showFlags Provides additional operating flags.  May be
-     * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT},
-     * {@link InputMethodManager#SHOW_FORCED} bit set.
-     * @param hideFlags Provides additional operating flags.  May be
-     * 0 or have the {@link  InputMethodManager#HIDE_IMPLICIT_ONLY},
-     * {@link  InputMethodManager#HIDE_NOT_ALWAYS} bit set.
      *
      * @deprecated Starting in {@link android.os.Build.VERSION_CODES#S} the system no longer invokes
      * this method, instead it explicitly shows or hides the IME. An {@code InputMethodService}
@@ -182,7 +176,8 @@
      * InputMethodService#requestShowSelf} or {@link InputMethodService#requestHideSelf}
      */
     @Deprecated
-    public void toggleSoftInput(int showFlags, int hideFlags);
+    public void toggleSoftInput(@InputMethodManager.ShowFlags int showFlags,
+            @InputMethodManager.HideFlags int hideFlags);
 
     /**
      * This method is called when the cursor and/or the character position relevant to text input
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index aa9225b..e9d7b9b 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -1118,7 +1118,7 @@
             @InputConnection.CursorUpdateFilter int cursorUpdateFilter, int imeDisplayId) {
         final InputConnection ic = getInputConnection();
         if (ic == null || !isActive()) {
-            Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
+            Log.w(TAG, "requestCursorUpdates on inactive InputConnection");
             return false;
         }
         if (mParentInputMethodManager.mRequestCursorUpdateDisplayIdCheck.get()
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
index 05717dd..7eee33f 100644
--- a/core/java/android/view/inputmethod/TextAppearanceInfo.java
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -238,7 +238,10 @@
                 .setFontFeatureSettings(textPaint.getFontFeatureSettings())
                 .setFontVariationSettings(textPaint.getFontVariationSettings())
                 .setTextScaleX(textPaint.getTextScaleX())
-                .setTextColor(textPaint.getColor())
+                // When there is a hint text (text length is 0), the text color should be the normal
+                // text color rather than hint text color.
+                .setTextColor(text.length() == 0
+                        ? textView.getCurrentTextColor() : textPaint.getColor())
                 .setLinkTextColor(textPaint.linkColor)
                 .setAllCaps(textView.isAllCaps())
                 .setFallbackLineSpacing(textView.isFallbackLineSpacing())
diff --git a/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl b/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl
deleted file mode 100644
index aaeb120..0000000
--- a/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl
+++ /dev/null
@@ -1,32 +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 android.view.selectiontoolbar;
-
-import android.view.selectiontoolbar.ToolbarMenuItem;
-import android.view.selectiontoolbar.WidgetInfo;
-
-/**
- * Binder interface to notify the selection toolbar events from one process to the other.
- * @hide
- */
-oneway interface ISelectionToolbarCallback {
-    void onShown(in WidgetInfo info);
-    void onWidgetUpdated(in WidgetInfo info);
-    void onToolbarShowTimeout();
-    void onMenuItemClicked(in ToolbarMenuItem item);
-    void onError(int errorCode);
-}
diff --git a/core/java/android/view/selectiontoolbar/ISelectionToolbarManager.aidl b/core/java/android/view/selectiontoolbar/ISelectionToolbarManager.aidl
deleted file mode 100644
index 4a647ad..0000000
--- a/core/java/android/view/selectiontoolbar/ISelectionToolbarManager.aidl
+++ /dev/null
@@ -1,31 +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 android.view.selectiontoolbar;
-
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.ShowInfo;
-
-/**
- * Mediator between apps and selection toolbar service implementation.
- *
- * @hide
- */
-oneway interface ISelectionToolbarManager {
-    void showToolbar(in ShowInfo showInfo, in ISelectionToolbarCallback callback, int userId);
-    void hideToolbar(long widgetToken, int userId);
-    void dismissToolbar(long widgetToken, int userId);
-}
\ No newline at end of file
diff --git a/core/java/android/view/selectiontoolbar/OWNERS b/core/java/android/view/selectiontoolbar/OWNERS
deleted file mode 100644
index 5500b92..0000000
--- a/core/java/android/view/selectiontoolbar/OWNERS
+++ /dev/null
@@ -1,10 +0,0 @@
-# Bug component: 709498
-
-augale@google.com
-joannechung@google.com
-licha@google.com
-lpeter@google.com
-svetoslavganov@google.com
-toki@google.com
-tonymak@google.com
-tymtsai@google.com
\ No newline at end of file
diff --git a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
deleted file mode 100644
index 6de0316..0000000
--- a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
+++ /dev/null
@@ -1,122 +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 android.view.selectiontoolbar;
-
-import android.annotation.NonNull;
-import android.annotation.SystemService;
-import android.content.Context;
-import android.os.RemoteException;
-import android.provider.DeviceConfig;
-
-import java.util.Objects;
-
-/**
- * The {@link SelectionToolbarManager} class provides ways for apps to control the
- * selection toolbar.
- *
- * @hide
- */
-@SystemService(Context.SELECTION_TOOLBAR_SERVICE)
-public final class SelectionToolbarManager {
-
-    private static final String TAG = "SelectionToolbar";
-
-    /**
-     * The tag which uses for enabling debug log dump. To enable it, we can use command "adb shell
-     * setprop log.tag.UiTranslation DEBUG".
-     */
-    public static final String LOG_TAG = "SelectionToolbar";
-
-    /**
-     * Whether system selection toolbar is enabled.
-     */
-    private static final String REMOTE_SELECTION_TOOLBAR_ENABLED =
-            "remote_selection_toolbar_enabled";
-
-    /**
-     * Used to mark a toolbar that has no toolbar token id.
-     */
-    public static final long NO_TOOLBAR_ID = 0;
-
-    /**
-     * The error code that do not allow to create multiple toolbar.
-     */
-    public static final int ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR = 1;
-
-    @NonNull
-    private final Context mContext;
-    private final ISelectionToolbarManager mService;
-
-    public SelectionToolbarManager(@NonNull Context context,
-            @NonNull ISelectionToolbarManager service) {
-        mContext = Objects.requireNonNull(context);
-        mService = service;
-    }
-
-    /**
-     * Request to show selection toolbar for a given View.
-     */
-    public void showToolbar(@NonNull ShowInfo showInfo,
-            @NonNull ISelectionToolbarCallback callback) {
-        try {
-            Objects.requireNonNull(showInfo);
-            Objects.requireNonNull(callback);
-            mService.showToolbar(showInfo, callback, mContext.getUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Request to hide selection toolbar.
-     */
-    public void hideToolbar(long widgetToken) {
-        try {
-            mService.hideToolbar(widgetToken, mContext.getUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Dismiss to dismiss selection toolbar.
-     */
-    public void dismissToolbar(long widgetToken) {
-        try {
-            mService.dismissToolbar(widgetToken, mContext.getUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    private boolean isRemoteSelectionToolbarEnabled() {
-        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SELECTION_TOOLBAR,
-                REMOTE_SELECTION_TOOLBAR_ENABLED, false);
-    }
-
-    /**
-     * Returns {@code true} if remote render selection toolbar enabled, otherwise
-     * returns {@code false}.
-     */
-    public static boolean isRemoteSelectionToolbarEnabled(Context context) {
-        SelectionToolbarManager manager = context.getSystemService(SelectionToolbarManager.class);
-        if (manager != null) {
-            return manager.isRemoteSelectionToolbarEnabled();
-        }
-        return false;
-    }
-}
diff --git a/core/java/android/view/selectiontoolbar/ShowInfo.aidl b/core/java/android/view/selectiontoolbar/ShowInfo.aidl
deleted file mode 100644
index dce9c15..0000000
--- a/core/java/android/view/selectiontoolbar/ShowInfo.aidl
+++ /dev/null
@@ -1,22 +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 android.view.selectiontoolbar;
-
-/**
- * @hide
- */
-parcelable ShowInfo;
diff --git a/core/java/android/view/selectiontoolbar/ShowInfo.java b/core/java/android/view/selectiontoolbar/ShowInfo.java
deleted file mode 100644
index 28b4480..0000000
--- a/core/java/android/view/selectiontoolbar/ShowInfo.java
+++ /dev/null
@@ -1,361 +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 android.view.selectiontoolbar;
-
-import android.annotation.NonNull;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.os.Parcelable;
-
-import com.android.internal.util.DataClass;
-
-import java.util.List;
-
-
-/**
- * The class holds menu information for render service to render the selection toolbar.
- *
- * @hide
- */
-@DataClass(genToString = true, genEqualsHashCode = true)
-public final class ShowInfo implements Parcelable {
-
-    /**
-     * The token that is used to identify the selection toolbar. This is initially set to 0
-     * until a selection toolbar has been created for the showToolbar request.
-     */
-    private final long mWidgetToken;
-
-    /**
-     * If the toolbar menu items need to be re-layout.
-     */
-    private final boolean mLayoutRequired;
-
-    /**
-     * The menu items to be rendered in the selection toolbar.
-     */
-    @NonNull
-    private final List<ToolbarMenuItem> mMenuItems;
-
-    /**
-     * A rect specifying where the selection toolbar on the screen.
-     */
-    @NonNull
-    private final Rect mContentRect;
-
-    /**
-     * A recommended maximum suggested width of the selection toolbar.
-     */
-    private final int mSuggestedWidth;
-
-    /**
-     * The portion of the screen that is available to the selection toolbar.
-     */
-    @NonNull
-    private final Rect mViewPortOnScreen;
-
-    /**
-     * The host application's input token, this allows the remote render service to transfer
-     * the touch focus to the host application.
-     */
-    @NonNull
-    private final IBinder mHostInputToken;
-
-    /**
-     * If the host application uses light theme.
-     */
-    private final boolean mIsLightTheme;
-
-
-
-    // Code below generated by codegen v1.0.23.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/selectiontoolbar/ShowInfo.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
-    /**
-     * Creates a new ShowInfo.
-     *
-     * @param widgetToken
-     *   The token that is used to identify the selection toolbar. This is initially set to 0
-     *   until a selection toolbar has been created for the showToolbar request.
-     * @param layoutRequired
-     *   If the toolbar menu items need to be re-layout.
-     * @param menuItems
-     *   The menu items to be rendered in the selection toolbar.
-     * @param contentRect
-     *   A rect specifying where the selection toolbar on the screen.
-     * @param suggestedWidth
-     *   A recommended maximum suggested width of the selection toolbar.
-     * @param viewPortOnScreen
-     *   The portion of the screen that is available to the selection toolbar.
-     * @param hostInputToken
-     *   The host application's input token, this allows the remote render service to transfer
-     *   the touch focus to the host application.
-     * @param isLightTheme
-     *   If the host application uses light theme.
-     */
-    @DataClass.Generated.Member
-    public ShowInfo(
-            long widgetToken,
-            boolean layoutRequired,
-            @NonNull List<ToolbarMenuItem> menuItems,
-            @NonNull Rect contentRect,
-            int suggestedWidth,
-            @NonNull Rect viewPortOnScreen,
-            @NonNull IBinder hostInputToken,
-            boolean isLightTheme) {
-        this.mWidgetToken = widgetToken;
-        this.mLayoutRequired = layoutRequired;
-        this.mMenuItems = menuItems;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mMenuItems);
-        this.mContentRect = contentRect;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mContentRect);
-        this.mSuggestedWidth = suggestedWidth;
-        this.mViewPortOnScreen = viewPortOnScreen;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mViewPortOnScreen);
-        this.mHostInputToken = hostInputToken;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mHostInputToken);
-        this.mIsLightTheme = isLightTheme;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    /**
-     * The token that is used to identify the selection toolbar. This is initially set to 0
-     * until a selection toolbar has been created for the showToolbar request.
-     */
-    @DataClass.Generated.Member
-    public long getWidgetToken() {
-        return mWidgetToken;
-    }
-
-    /**
-     * If the toolbar menu items need to be re-layout.
-     */
-    @DataClass.Generated.Member
-    public boolean isLayoutRequired() {
-        return mLayoutRequired;
-    }
-
-    /**
-     * The menu items to be rendered in the selection toolbar.
-     */
-    @DataClass.Generated.Member
-    public @NonNull List<ToolbarMenuItem> getMenuItems() {
-        return mMenuItems;
-    }
-
-    /**
-     * A rect specifying where the selection toolbar on the screen.
-     */
-    @DataClass.Generated.Member
-    public @NonNull Rect getContentRect() {
-        return mContentRect;
-    }
-
-    /**
-     * A recommended maximum suggested width of the selection toolbar.
-     */
-    @DataClass.Generated.Member
-    public int getSuggestedWidth() {
-        return mSuggestedWidth;
-    }
-
-    /**
-     * The portion of the screen that is available to the selection toolbar.
-     */
-    @DataClass.Generated.Member
-    public @NonNull Rect getViewPortOnScreen() {
-        return mViewPortOnScreen;
-    }
-
-    /**
-     * The host application's input token, this allows the remote render service to transfer
-     * the touch focus to the host application.
-     */
-    @DataClass.Generated.Member
-    public @NonNull IBinder getHostInputToken() {
-        return mHostInputToken;
-    }
-
-    /**
-     * If the host application uses light theme.
-     */
-    @DataClass.Generated.Member
-    public boolean isIsLightTheme() {
-        return mIsLightTheme;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public String toString() {
-        // You can override field toString logic by defining methods like:
-        // String fieldNameToString() { ... }
-
-        return "ShowInfo { " +
-                "widgetToken = " + mWidgetToken + ", " +
-                "layoutRequired = " + mLayoutRequired + ", " +
-                "menuItems = " + mMenuItems + ", " +
-                "contentRect = " + mContentRect + ", " +
-                "suggestedWidth = " + mSuggestedWidth + ", " +
-                "viewPortOnScreen = " + mViewPortOnScreen + ", " +
-                "hostInputToken = " + mHostInputToken + ", " +
-                "isLightTheme = " + mIsLightTheme +
-        " }";
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public boolean equals(@android.annotation.Nullable Object o) {
-        // You can override field equality logic by defining either of the methods like:
-        // boolean fieldNameEquals(ShowInfo other) { ... }
-        // boolean fieldNameEquals(FieldType otherValue) { ... }
-
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        @SuppressWarnings("unchecked")
-        ShowInfo that = (ShowInfo) o;
-        //noinspection PointlessBooleanExpression
-        return true
-                && mWidgetToken == that.mWidgetToken
-                && mLayoutRequired == that.mLayoutRequired
-                && java.util.Objects.equals(mMenuItems, that.mMenuItems)
-                && java.util.Objects.equals(mContentRect, that.mContentRect)
-                && mSuggestedWidth == that.mSuggestedWidth
-                && java.util.Objects.equals(mViewPortOnScreen, that.mViewPortOnScreen)
-                && java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
-                && mIsLightTheme == that.mIsLightTheme;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int hashCode() {
-        // You can override field hashCode logic by defining methods like:
-        // int fieldNameHashCode() { ... }
-
-        int _hash = 1;
-        _hash = 31 * _hash + Long.hashCode(mWidgetToken);
-        _hash = 31 * _hash + Boolean.hashCode(mLayoutRequired);
-        _hash = 31 * _hash + java.util.Objects.hashCode(mMenuItems);
-        _hash = 31 * _hash + java.util.Objects.hashCode(mContentRect);
-        _hash = 31 * _hash + mSuggestedWidth;
-        _hash = 31 * _hash + java.util.Objects.hashCode(mViewPortOnScreen);
-        _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
-        _hash = 31 * _hash + Boolean.hashCode(mIsLightTheme);
-        return _hash;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
-        // You can override field parcelling by defining methods like:
-        // void parcelFieldName(Parcel dest, int flags) { ... }
-
-        int flg = 0;
-        if (mLayoutRequired) flg |= 0x2;
-        if (mIsLightTheme) flg |= 0x80;
-        dest.writeInt(flg);
-        dest.writeLong(mWidgetToken);
-        dest.writeParcelableList(mMenuItems, flags);
-        dest.writeTypedObject(mContentRect, flags);
-        dest.writeInt(mSuggestedWidth);
-        dest.writeTypedObject(mViewPortOnScreen, flags);
-        dest.writeStrongBinder(mHostInputToken);
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int describeContents() { return 0; }
-
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
-    @DataClass.Generated.Member
-    /* package-private */ ShowInfo(@NonNull android.os.Parcel in) {
-        // You can override field unparcelling by defining methods like:
-        // static FieldType unparcelFieldName(Parcel in) { ... }
-
-        int flg = in.readInt();
-        boolean layoutRequired = (flg & 0x2) != 0;
-        boolean isLightTheme = (flg & 0x80) != 0;
-        long widgetToken = in.readLong();
-        List<ToolbarMenuItem> menuItems = new java.util.ArrayList<>();
-        in.readParcelableList(menuItems, ToolbarMenuItem.class.getClassLoader(), android.view.selectiontoolbar.ToolbarMenuItem.class);
-        Rect contentRect = (Rect) in.readTypedObject(Rect.CREATOR);
-        int suggestedWidth = in.readInt();
-        Rect viewPortOnScreen = (Rect) in.readTypedObject(Rect.CREATOR);
-        IBinder hostInputToken = (IBinder) in.readStrongBinder();
-
-        this.mWidgetToken = widgetToken;
-        this.mLayoutRequired = layoutRequired;
-        this.mMenuItems = menuItems;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mMenuItems);
-        this.mContentRect = contentRect;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mContentRect);
-        this.mSuggestedWidth = suggestedWidth;
-        this.mViewPortOnScreen = viewPortOnScreen;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mViewPortOnScreen);
-        this.mHostInputToken = hostInputToken;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mHostInputToken);
-        this.mIsLightTheme = isLightTheme;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public static final @NonNull Parcelable.Creator<ShowInfo> CREATOR
-            = new Parcelable.Creator<ShowInfo>() {
-        @Override
-        public ShowInfo[] newArray(int size) {
-            return new ShowInfo[size];
-        }
-
-        @Override
-        public ShowInfo createFromParcel(@NonNull android.os.Parcel in) {
-            return new ShowInfo(in);
-        }
-    };
-
-    @DataClass.Generated(
-            time = 1645108384245L,
-            codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ShowInfo.java",
-            inputSignatures = "private final  long mWidgetToken\nprivate final  boolean mLayoutRequired\nprivate final @android.annotation.NonNull java.util.List<android.view.selectiontoolbar.ToolbarMenuItem> mMenuItems\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final  int mSuggestedWidth\nprivate final @android.annotation.NonNull android.graphics.Rect mViewPortOnScreen\nprivate final @android.annotation.NonNull android.os.IBinder mHostInputToken\nprivate final  boolean mIsLightTheme\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
-    @Deprecated
-    private void __metadata() {}
-
-
-    //@formatter:on
-    // End of generated code
-
-}
diff --git a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.aidl b/core/java/android/view/selectiontoolbar/ToolbarMenuItem.aidl
deleted file mode 100644
index 711a85a..0000000
--- a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.aidl
+++ /dev/null
@@ -1,22 +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 android.view.selectiontoolbar;
-
-/**
- * @hide
- */
-parcelable ToolbarMenuItem;
diff --git a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java b/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java
deleted file mode 100644
index 89347c6..0000000
--- a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java
+++ /dev/null
@@ -1,543 +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 android.view.selectiontoolbar;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.drawable.Icon;
-import android.os.Parcelable;
-import android.view.MenuItem;
-
-import com.android.internal.util.DataClass;
-
-/**
- * The menu item that is used to show the selection toolbar.
- *
- * @hide
- */
-@DataClass(genBuilder = true, genToString = true, genEqualsHashCode = true)
-public final class ToolbarMenuItem implements Parcelable {
-
-    /**
-     * The priority of menu item is unknown.
-     */
-    public static final int PRIORITY_UNKNOWN = 0;
-
-    /**
-     * The priority of menu item is shown in primary selection toolbar.
-     */
-    public static final int PRIORITY_PRIMARY = 1;
-
-    /**
-     * The priority of menu item is shown in overflow selection toolbar.
-     */
-    public static final int PRIORITY_OVERFLOW = 2;
-
-    /**
-     * The id of the menu item.
-     *
-     * @see MenuItem#getItemId()
-     */
-    private final int mItemId;
-
-    /**
-     * The title of the menu item.
-     *
-     * @see MenuItem#getTitle()
-     */
-    @NonNull
-    private final CharSequence mTitle;
-
-    /**
-     * The content description of the menu item.
-     *
-     * @see MenuItem#getContentDescription()
-     */
-    @Nullable
-    private final CharSequence mContentDescription;
-
-    /**
-     * The group id of the menu item.
-     *
-     * @see MenuItem#getGroupId()
-     */
-    private final int mGroupId;
-
-    /**
-     * The icon id of the menu item.
-     *
-     * @see MenuItem#getIcon()
-     */
-    @Nullable
-    private final Icon mIcon;
-
-    /**
-     * The tooltip text of the menu item.
-     *
-     * @see MenuItem#getTooltipText()
-     */
-    @Nullable
-    private final CharSequence mTooltipText;
-
-    /**
-     * The priority of the menu item used to display the order of the menu item.
-     */
-    private final int mPriority;
-
-    /**
-     * Returns the priority from a given {@link MenuItem}.
-     */
-    public static int getPriorityFromMenuItem(MenuItem menuItem) {
-        if (menuItem.requiresActionButton()) {
-            return PRIORITY_PRIMARY;
-        } else if (menuItem.requiresOverflow()) {
-            return PRIORITY_OVERFLOW;
-        }
-        return PRIORITY_UNKNOWN;
-    }
-
-
-
-
-    // Code below generated by codegen v1.0.23.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
-    @android.annotation.IntDef(prefix = "PRIORITY_", value = {
-        PRIORITY_UNKNOWN,
-        PRIORITY_PRIMARY,
-        PRIORITY_OVERFLOW
-    })
-    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
-    @DataClass.Generated.Member
-    public @interface Priority {}
-
-    @DataClass.Generated.Member
-    public static String priorityToString(@Priority int value) {
-        switch (value) {
-            case PRIORITY_UNKNOWN:
-                    return "PRIORITY_UNKNOWN";
-            case PRIORITY_PRIMARY:
-                    return "PRIORITY_PRIMARY";
-            case PRIORITY_OVERFLOW:
-                    return "PRIORITY_OVERFLOW";
-            default: return Integer.toHexString(value);
-        }
-    }
-
-    @DataClass.Generated.Member
-    /* package-private */ ToolbarMenuItem(
-            int itemId,
-            @NonNull CharSequence title,
-            @Nullable CharSequence contentDescription,
-            int groupId,
-            @Nullable Icon icon,
-            @Nullable CharSequence tooltipText,
-            int priority) {
-        this.mItemId = itemId;
-        this.mTitle = title;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mTitle);
-        this.mContentDescription = contentDescription;
-        this.mGroupId = groupId;
-        this.mIcon = icon;
-        this.mTooltipText = tooltipText;
-        this.mPriority = priority;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    /**
-     * The id of the menu item.
-     *
-     * @see MenuItem#getItemId()
-     */
-    @DataClass.Generated.Member
-    public int getItemId() {
-        return mItemId;
-    }
-
-    /**
-     * The title of the menu item.
-     *
-     * @see MenuItem#getTitle()
-     */
-    @DataClass.Generated.Member
-    public @NonNull CharSequence getTitle() {
-        return mTitle;
-    }
-
-    /**
-     * The content description of the menu item.
-     *
-     * @see MenuItem#getContentDescription()
-     */
-    @DataClass.Generated.Member
-    public @Nullable CharSequence getContentDescription() {
-        return mContentDescription;
-    }
-
-    /**
-     * The group id of the menu item.
-     *
-     * @see MenuItem#getGroupId()
-     */
-    @DataClass.Generated.Member
-    public int getGroupId() {
-        return mGroupId;
-    }
-
-    /**
-     * The icon id of the menu item.
-     *
-     * @see MenuItem#getIcon()
-     */
-    @DataClass.Generated.Member
-    public @Nullable Icon getIcon() {
-        return mIcon;
-    }
-
-    /**
-     * The tooltip text of the menu item.
-     *
-     * @see MenuItem#getTooltipText()
-     */
-    @DataClass.Generated.Member
-    public @Nullable CharSequence getTooltipText() {
-        return mTooltipText;
-    }
-
-    /**
-     * The priority of the menu item used to display the order of the menu item.
-     */
-    @DataClass.Generated.Member
-    public int getPriority() {
-        return mPriority;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public String toString() {
-        // You can override field toString logic by defining methods like:
-        // String fieldNameToString() { ... }
-
-        return "ToolbarMenuItem { " +
-                "itemId = " + mItemId + ", " +
-                "title = " + mTitle + ", " +
-                "contentDescription = " + mContentDescription + ", " +
-                "groupId = " + mGroupId + ", " +
-                "icon = " + mIcon + ", " +
-                "tooltipText = " + mTooltipText + ", " +
-                "priority = " + mPriority +
-        " }";
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public boolean equals(@Nullable Object o) {
-        // You can override field equality logic by defining either of the methods like:
-        // boolean fieldNameEquals(ToolbarMenuItem other) { ... }
-        // boolean fieldNameEquals(FieldType otherValue) { ... }
-
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        @SuppressWarnings("unchecked")
-        ToolbarMenuItem that = (ToolbarMenuItem) o;
-        //noinspection PointlessBooleanExpression
-        return true
-                && mItemId == that.mItemId
-                && java.util.Objects.equals(mTitle, that.mTitle)
-                && java.util.Objects.equals(mContentDescription, that.mContentDescription)
-                && mGroupId == that.mGroupId
-                && java.util.Objects.equals(mIcon, that.mIcon)
-                && java.util.Objects.equals(mTooltipText, that.mTooltipText)
-                && mPriority == that.mPriority;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int hashCode() {
-        // You can override field hashCode logic by defining methods like:
-        // int fieldNameHashCode() { ... }
-
-        int _hash = 1;
-        _hash = 31 * _hash + mItemId;
-        _hash = 31 * _hash + java.util.Objects.hashCode(mTitle);
-        _hash = 31 * _hash + java.util.Objects.hashCode(mContentDescription);
-        _hash = 31 * _hash + mGroupId;
-        _hash = 31 * _hash + java.util.Objects.hashCode(mIcon);
-        _hash = 31 * _hash + java.util.Objects.hashCode(mTooltipText);
-        _hash = 31 * _hash + mPriority;
-        return _hash;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
-        // You can override field parcelling by defining methods like:
-        // void parcelFieldName(Parcel dest, int flags) { ... }
-
-        byte flg = 0;
-        if (mContentDescription != null) flg |= 0x4;
-        if (mIcon != null) flg |= 0x10;
-        if (mTooltipText != null) flg |= 0x20;
-        dest.writeByte(flg);
-        dest.writeInt(mItemId);
-        dest.writeCharSequence(mTitle);
-        if (mContentDescription != null) dest.writeCharSequence(mContentDescription);
-        dest.writeInt(mGroupId);
-        if (mIcon != null) dest.writeTypedObject(mIcon, flags);
-        if (mTooltipText != null) dest.writeCharSequence(mTooltipText);
-        dest.writeInt(mPriority);
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int describeContents() { return 0; }
-
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
-    @DataClass.Generated.Member
-    /* package-private */ ToolbarMenuItem(@NonNull android.os.Parcel in) {
-        // You can override field unparcelling by defining methods like:
-        // static FieldType unparcelFieldName(Parcel in) { ... }
-
-        byte flg = in.readByte();
-        int itemId = in.readInt();
-        CharSequence title = (CharSequence) in.readCharSequence();
-        CharSequence contentDescription = (flg & 0x4) == 0 ? null : (CharSequence) in.readCharSequence();
-        int groupId = in.readInt();
-        Icon icon = (flg & 0x10) == 0 ? null : (Icon) in.readTypedObject(Icon.CREATOR);
-        CharSequence tooltipText = (flg & 0x20) == 0 ? null : (CharSequence) in.readCharSequence();
-        int priority = in.readInt();
-
-        this.mItemId = itemId;
-        this.mTitle = title;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mTitle);
-        this.mContentDescription = contentDescription;
-        this.mGroupId = groupId;
-        this.mIcon = icon;
-        this.mTooltipText = tooltipText;
-        this.mPriority = priority;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public static final @NonNull Parcelable.Creator<ToolbarMenuItem> CREATOR
-            = new Parcelable.Creator<ToolbarMenuItem>() {
-        @Override
-        public ToolbarMenuItem[] newArray(int size) {
-            return new ToolbarMenuItem[size];
-        }
-
-        @Override
-        public ToolbarMenuItem createFromParcel(@NonNull android.os.Parcel in) {
-            return new ToolbarMenuItem(in);
-        }
-    };
-
-    /**
-     * A builder for {@link ToolbarMenuItem}
-     */
-    @SuppressWarnings("WeakerAccess")
-    @DataClass.Generated.Member
-    public static final class Builder {
-
-        private int mItemId;
-        private @NonNull CharSequence mTitle;
-        private @Nullable CharSequence mContentDescription;
-        private int mGroupId;
-        private @Nullable Icon mIcon;
-        private @Nullable CharSequence mTooltipText;
-        private int mPriority;
-
-        private long mBuilderFieldsSet = 0L;
-
-        /**
-         * Creates a new Builder.
-         *
-         * @param itemId
-         *   The id of the menu item.
-         * @param title
-         *   The title of the menu item.
-         * @param contentDescription
-         *   The content description of the menu item.
-         * @param groupId
-         *   The group id of the menu item.
-         * @param icon
-         *   The icon id of the menu item.
-         * @param tooltipText
-         *   The tooltip text of the menu item.
-         * @param priority
-         *   The priority of the menu item used to display the order of the menu item.
-         */
-        public Builder(
-                int itemId,
-                @NonNull CharSequence title,
-                @Nullable CharSequence contentDescription,
-                int groupId,
-                @Nullable Icon icon,
-                @Nullable CharSequence tooltipText,
-                int priority) {
-            mItemId = itemId;
-            mTitle = title;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mTitle);
-            mContentDescription = contentDescription;
-            mGroupId = groupId;
-            mIcon = icon;
-            mTooltipText = tooltipText;
-            mPriority = priority;
-        }
-
-        /**
-         * The id of the menu item.
-         *
-         * @see MenuItem#getItemId()
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setItemId(int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x1;
-            mItemId = value;
-            return this;
-        }
-
-        /**
-         * The title of the menu item.
-         *
-         * @see MenuItem#getTitle()
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setTitle(@NonNull CharSequence value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x2;
-            mTitle = value;
-            return this;
-        }
-
-        /**
-         * The content description of the menu item.
-         *
-         * @see MenuItem#getContentDescription()
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setContentDescription(@NonNull CharSequence value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x4;
-            mContentDescription = value;
-            return this;
-        }
-
-        /**
-         * The group id of the menu item.
-         *
-         * @see MenuItem#getGroupId()
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setGroupId(int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x8;
-            mGroupId = value;
-            return this;
-        }
-
-        /**
-         * The icon id of the menu item.
-         *
-         * @see MenuItem#getIcon()
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setIcon(@NonNull Icon value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x10;
-            mIcon = value;
-            return this;
-        }
-
-        /**
-         * The tooltip text of the menu item.
-         *
-         * @see MenuItem#getTooltipText()
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setTooltipText(@NonNull CharSequence value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x20;
-            mTooltipText = value;
-            return this;
-        }
-
-        /**
-         * The priority of the menu item used to display the order of the menu item.
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setPriority(int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x40;
-            mPriority = value;
-            return this;
-        }
-
-        /** Builds the instance. This builder should not be touched after calling this! */
-        public @NonNull ToolbarMenuItem build() {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x80; // Mark builder used
-
-            ToolbarMenuItem o = new ToolbarMenuItem(
-                    mItemId,
-                    mTitle,
-                    mContentDescription,
-                    mGroupId,
-                    mIcon,
-                    mTooltipText,
-                    mPriority);
-            return o;
-        }
-
-        private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x80) != 0) {
-                throw new IllegalStateException(
-                        "This Builder should not be reused. Use a new Builder instance instead");
-            }
-        }
-    }
-
-    @DataClass.Generated(
-            time = 1643200806234L,
-            codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java",
-            inputSignatures = "public static final  int PRIORITY_UNKNOWN\npublic static final  int PRIORITY_PRIMARY\npublic static final  int PRIORITY_OVERFLOW\nprivate final  int mItemId\nprivate final @android.annotation.NonNull java.lang.CharSequence mTitle\nprivate final @android.annotation.Nullable java.lang.CharSequence mContentDescription\nprivate final  int mGroupId\nprivate final @android.annotation.Nullable android.graphics.drawable.Icon mIcon\nprivate final @android.annotation.Nullable java.lang.CharSequence mTooltipText\nprivate final  int mPriority\npublic static  int getPriorityFromMenuItem(android.view.MenuItem)\nclass ToolbarMenuItem extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true)")
-    @Deprecated
-    private void __metadata() {}
-
-
-    //@formatter:on
-    // End of generated code
-
-}
diff --git a/core/java/android/view/selectiontoolbar/WidgetInfo.aidl b/core/java/android/view/selectiontoolbar/WidgetInfo.aidl
deleted file mode 100644
index 1057c51..0000000
--- a/core/java/android/view/selectiontoolbar/WidgetInfo.aidl
+++ /dev/null
@@ -1,22 +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 android.view.selectiontoolbar;
-
-/**
- * @hide
- */
-parcelable WidgetInfo;
diff --git a/core/java/android/view/selectiontoolbar/WidgetInfo.java b/core/java/android/view/selectiontoolbar/WidgetInfo.java
deleted file mode 100644
index 5d0fd47..0000000
--- a/core/java/android/view/selectiontoolbar/WidgetInfo.java
+++ /dev/null
@@ -1,228 +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 android.view.selectiontoolbar;
-
-import android.annotation.NonNull;
-import android.graphics.Rect;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.SurfaceControlViewHost;
-
-import com.android.internal.util.DataClass;
-
-/**
- * The class holds the rendered content and the related information from the render service to
- * be used to show on the selection toolbar.
- *
- * @hide
- */
-@DataClass(genToString = true, genEqualsHashCode = true)
-public final class WidgetInfo implements Parcelable {
-
-    /**
-     * The token that is used to identify the selection toolbar.
-     */
-    private final long mWidgetToken;
-
-    /**
-     * A Rect that defines the size and positioning of the remote view with respect to
-     * its host window.
-     */
-    @NonNull
-    private final Rect mContentRect;
-
-    /**
-     * The SurfacePackage pointing to the remote view.
-     */
-    @NonNull
-    private final SurfaceControlViewHost.SurfacePackage mSurfacePackage;
-
-
-
-    // Code below generated by codegen v1.0.23.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/selectiontoolbar/WidgetInfo.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
-    /**
-     * Creates a new WidgetInfo.
-     *
-     * @param widgetToken
-     *   The token that is used to identify the selection toolbar.
-     * @param contentRect
-     *   A Rect that defines the size and positioning of the remote view with respect to
-     *   its host window.
-     * @param surfacePackage
-     *   The SurfacePackage pointing to the remote view.
-     */
-    @DataClass.Generated.Member
-    public WidgetInfo(
-            long widgetToken,
-            @NonNull Rect contentRect,
-            @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) {
-        this.mWidgetToken = widgetToken;
-        this.mContentRect = contentRect;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mContentRect);
-        this.mSurfacePackage = surfacePackage;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mSurfacePackage);
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    /**
-     * The token that is used to identify the selection toolbar.
-     */
-    @DataClass.Generated.Member
-    public long getWidgetToken() {
-        return mWidgetToken;
-    }
-
-    /**
-     * A Rect that defines the size and positioning of the remote view with respect to
-     * its host window.
-     */
-    @DataClass.Generated.Member
-    public @NonNull Rect getContentRect() {
-        return mContentRect;
-    }
-
-    /**
-     * The SurfacePackage pointing to the remote view.
-     */
-    @DataClass.Generated.Member
-    public @NonNull SurfaceControlViewHost.SurfacePackage getSurfacePackage() {
-        return mSurfacePackage;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public String toString() {
-        // You can override field toString logic by defining methods like:
-        // String fieldNameToString() { ... }
-
-        return "WidgetInfo { " +
-                "widgetToken = " + mWidgetToken + ", " +
-                "contentRect = " + mContentRect + ", " +
-                "surfacePackage = " + mSurfacePackage +
-        " }";
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public boolean equals(@android.annotation.Nullable Object o) {
-        // You can override field equality logic by defining either of the methods like:
-        // boolean fieldNameEquals(WidgetInfo other) { ... }
-        // boolean fieldNameEquals(FieldType otherValue) { ... }
-
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        @SuppressWarnings("unchecked")
-        WidgetInfo that = (WidgetInfo) o;
-        //noinspection PointlessBooleanExpression
-        return true
-                && mWidgetToken == that.mWidgetToken
-                && java.util.Objects.equals(mContentRect, that.mContentRect)
-                && java.util.Objects.equals(mSurfacePackage, that.mSurfacePackage);
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int hashCode() {
-        // You can override field hashCode logic by defining methods like:
-        // int fieldNameHashCode() { ... }
-
-        int _hash = 1;
-        _hash = 31 * _hash + Long.hashCode(mWidgetToken);
-        _hash = 31 * _hash + java.util.Objects.hashCode(mContentRect);
-        _hash = 31 * _hash + java.util.Objects.hashCode(mSurfacePackage);
-        return _hash;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        // You can override field parcelling by defining methods like:
-        // void parcelFieldName(Parcel dest, int flags) { ... }
-
-        dest.writeLong(mWidgetToken);
-        dest.writeTypedObject(mContentRect, flags);
-        dest.writeTypedObject(mSurfacePackage, flags);
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int describeContents() { return 0; }
-
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
-    @DataClass.Generated.Member
-    /* package-private */ WidgetInfo(@NonNull Parcel in) {
-        // You can override field unparcelling by defining methods like:
-        // static FieldType unparcelFieldName(Parcel in) { ... }
-
-        long widgetToken = in.readLong();
-        Rect contentRect = (Rect) in.readTypedObject(Rect.CREATOR);
-        SurfaceControlViewHost.SurfacePackage surfacePackage = (SurfaceControlViewHost.SurfacePackage) in.readTypedObject(SurfaceControlViewHost.SurfacePackage.CREATOR);
-
-        this.mWidgetToken = widgetToken;
-        this.mContentRect = contentRect;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mContentRect);
-        this.mSurfacePackage = surfacePackage;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mSurfacePackage);
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public static final @NonNull Parcelable.Creator<WidgetInfo> CREATOR
-            = new Parcelable.Creator<WidgetInfo>() {
-        @Override
-        public WidgetInfo[] newArray(int size) {
-            return new WidgetInfo[size];
-        }
-
-        @Override
-        public WidgetInfo createFromParcel(@NonNull Parcel in) {
-            return new WidgetInfo(in);
-        }
-    };
-
-    @DataClass.Generated(
-            time = 1643281495056L,
-            codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/WidgetInfo.java",
-            inputSignatures = "private final  long mWidgetToken\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final @android.annotation.NonNull android.view.SurfaceControlViewHost.SurfacePackage mSurfacePackage\nclass WidgetInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
-    @Deprecated
-    private void __metadata() {}
-
-
-    //@formatter:on
-    // End of generated code
-
-}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index adeb889..6ad1960 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -4703,7 +4703,7 @@
 
     @Override
     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
-        if (mFastScroll != null) {
+        if (mFastScroll != null && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
             PointerIcon pointerIcon = mFastScroll.onResolvePointerIcon(event, pointerIndex);
             if (pointerIcon != null) {
                 return pointerIcon;
diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java
index 634cbe3..405099d 100644
--- a/core/java/android/widget/Button.java
+++ b/core/java/android/widget/Button.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.InputDevice;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
@@ -173,7 +174,8 @@
 
     @Override
     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
-        if (getPointerIcon() == null && isClickable() && isEnabled()) {
+        if (getPointerIcon() == null && isClickable() && isEnabled()
+                && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
             return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
         }
         return super.onResolvePointerIcon(event, pointerIndex);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 2dbff58..f39122a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -8105,6 +8105,16 @@
         private final Paint mHighlightPaint;
         private final Path mHighlightPath;
 
+        /**
+         * Whether it is in the progress of updating transformation method. It's needed because
+         * {@link TextView#setTransformationMethod(TransformationMethod)} will eventually call
+         * {@link TextView#setText(CharSequence)}.
+         * Because it normally should exit insert mode when {@link TextView#setText(CharSequence)}
+         * is called externally, we need this boolean to distinguish whether setText is triggered
+         * by setTransformation or not.
+         */
+        private boolean mUpdatingTransformationMethod;
+
         InsertModeController(@NonNull TextView textView) {
             mTextView = Objects.requireNonNull(textView);
             mIsInsertModeActive = false;
@@ -8112,16 +8122,10 @@
             mHighlightPaint = new Paint();
             mHighlightPath = new Path();
 
-            // The highlight color is supposed to be 12% of the color primary40. We can't
-            // directly access Material 3 theme. But because Material 3 sets the colorPrimary to
-            // be primary40, here we hardcoded it to be 12% of colorPrimary.
-            final TypedValue typedValue = new TypedValue();
-            mTextView.getContext().getTheme()
-                    .resolveAttribute(R.attr.colorPrimary, typedValue, true);
-            final int colorPrimary = typedValue.data;
-            final int highlightColor = ColorUtils.setAlphaComponent(colorPrimary,
-                    (int) (0.12f * Color.alpha(colorPrimary)));
-            mHighlightPaint.setColor(highlightColor);
+            // Insert mode highlight color is 20% opacity of the default text color.
+            int color = mTextView.getTextColors().getDefaultColor();
+            color = ColorUtils.setAlphaComponent(color, (int) (0.2f * Color.alpha(color)));
+            mHighlightPaint.setColor(color);
         }
 
         /**
@@ -8143,7 +8147,7 @@
             final boolean isSingleLine = mTextView.isSingleLine();
             mInsertModeTransformationMethod = new InsertModeTransformationMethod(offset,
                     isSingleLine, oldTransformationMethod);
-            mTextView.setTransformationMethodInternal(mInsertModeTransformationMethod);
+            setTransformationMethod(mInsertModeTransformationMethod, true);
             Selection.setSelection((Spannable) mTextView.getText(), offset);
 
             mIsInsertModeActive = true;
@@ -8151,6 +8155,10 @@
         }
 
         void exitInsertMode() {
+            exitInsertMode(true);
+        }
+
+        void exitInsertMode(boolean updateText) {
             if (!mIsInsertModeActive) return;
             if (mInsertModeTransformationMethod == null
                     || mInsertModeTransformationMethod != mTextView.getTransformationMethod()) {
@@ -8163,7 +8171,7 @@
             final int selectionEnd = mTextView.getSelectionEnd();
             final TransformationMethod oldTransformationMethod =
                     mInsertModeTransformationMethod.getOldTransformationMethod();
-            mTextView.setTransformationMethodInternal(oldTransformationMethod);
+            setTransformationMethod(oldTransformationMethod, updateText);
             Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
             mIsInsertModeActive = false;
         }
@@ -8184,22 +8192,55 @@
         }
 
         /**
-         * Notify the {@link InsertModeController} before the TextView's
-         * {@link TransformationMethod} is updated. If it's not in the insert mode,
-         * the given method is directly returned. Otherwise, it will wrap the given transformation
-         * method with an {@link InsertModeTransformationMethod} and then return.
-         *
-         * @param oldTransformationMethod the new {@link TransformationMethod} to be set on the
-         *                             TextView.
-         * @return the updated {@link TransformationMethod} to be set on the Textview.
+         * Update the TransformationMethod on the {@link TextView}.
+         * @param method the new method to be set on the {@link TextView}/
+         * @param updateText whether to update the text during setTransformationMethod call.
          */
-        TransformationMethod updateTransformationMethod(
-                TransformationMethod oldTransformationMethod) {
-            if (!mIsInsertModeActive) return oldTransformationMethod;
+        private void setTransformationMethod(TransformationMethod method, boolean updateText) {
+            mUpdatingTransformationMethod = true;
+            mTextView.setTransformationMethodInternal(method, updateText);
+            mUpdatingTransformationMethod = false;
+        }
 
+        /**
+         * Notify the InsertMode controller that the {@link TextView} is about to set its text.
+         */
+        void beforeSetText() {
+            // TextView#setText is called because our call to
+            // TextView#setTransformationMethodInternal in enterInsertMode(), exitInsertMode() or
+            // updateTransformationMethod().
+            // Do nothing in this case.
+            if (mUpdatingTransformationMethod) {
+                return;
+            }
+            // TextView#setText is called externally. Exit InsertMode but don't update text again
+            // when calling setTransformationMethod.
+            exitInsertMode(/* updateText */ false);
+        }
+
+        /**
+         * Notify the {@link InsertModeController} that TextView#setTransformationMethod is called.
+         * If it's not in the insert mode, the given transformation method is directly set to the
+         * TextView. Otherwise, it will wrap the given transformation method with an
+         * {@link InsertModeTransformationMethod} and then set it on the TextView.
+         *
+         * @param transformationMethod the new {@link TransformationMethod} to be set on the
+         *                             TextView.
+         */
+        void updateTransformationMethod(TransformationMethod transformationMethod) {
+            if (!mIsInsertModeActive) {
+                setTransformationMethod(transformationMethod, /* updateText */ true);
+                return;
+            }
+
+            // Changing TransformationMethod will reset selection range to [0, 0), we need to
+            // manually restore the old selection range.
+            final int selectionStart = mTextView.getSelectionStart();
+            final int selectionEnd = mTextView.getSelectionEnd();
             mInsertModeTransformationMethod = mInsertModeTransformationMethod.update(
-                    oldTransformationMethod, mTextView.isSingleLine());
-            return mInsertModeTransformationMethod;
+                    transformationMethod, mTextView.isSingleLine());
+            setTransformationMethod(mInsertModeTransformationMethod, /* updateText */ true);
+            Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
         }
     }
 
@@ -8211,6 +8252,9 @@
         return mInsertModeController.enterInsertMode(offset);
     }
 
+    /**
+     * Exit insert mode if this editor is in insert mode.
+     */
     void exitInsertMode() {
         if (mInsertModeController == null) return;
         mInsertModeController.exitInsertMode();
@@ -8222,18 +8266,19 @@
      * @param method the {@link TransformationMethod} to be set on the TextView.
      */
     void setTransformationMethod(TransformationMethod method) {
-        if (mInsertModeController == null || !mInsertModeController.mIsInsertModeActive) {
-            mTextView.setTransformationMethodInternal(method);
+        if (mInsertModeController == null) {
+            mTextView.setTransformationMethodInternal(method, /* updateText */ true);
             return;
         }
+        mInsertModeController.updateTransformationMethod(method);
+    }
 
-        // Changing TransformationMethod will reset selection range to [0, 0), we need to
-        // manually restore the old selection range.
-        final int selectionStart = mTextView.getSelectionStart();
-        final int selectionEnd = mTextView.getSelectionEnd();
-        method = mInsertModeController.updateTransformationMethod(method);
-        mTextView.setTransformationMethodInternal(method);
-        Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
+    /**
+     * Notify that the Editor that the associated {@link TextView} is about to set its text.
+     */
+    void beforeSetText() {
+        if (mInsertModeController == null) return;
+        mInsertModeController.beforeSetText();
     }
 
     /**
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 1d6778b..55b2251 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -1498,6 +1498,11 @@
      * @return The unconsumed delta after the EdgeEffects have had an opportunity to consume.
      */
     private int consumeFlingInStretch(int unconsumed) {
+        int scrollX = getScrollX();
+        if (scrollX < 0 || scrollX > getScrollRange()) {
+            // We've overscrolled, so don't stretch
+            return unconsumed;
+        }
         if (unconsumed > 0 && mEdgeGlowLeft != null && mEdgeGlowLeft.getDistance() != 0f) {
             int size = getWidth();
             float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size;
diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java
index e1b0c91..b6c5396 100644
--- a/core/java/android/widget/ImageButton.java
+++ b/core/java/android/widget/ImageButton.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.widget.RemoteViews.RemoteView;
@@ -99,7 +100,8 @@
 
     @Override
     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
-        if (getPointerIcon() == null && isClickable() && isEnabled()) {
+        if (getPointerIcon() == null && isClickable() && isEnabled()
+                && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
             return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
         }
         return super.onResolvePointerIcon(event, pointerIndex);
diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index f3600b0..edf0f48 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -38,6 +38,7 @@
 import android.util.StateSet;
 import android.util.TypedValue;
 import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.View;
@@ -1060,9 +1061,11 @@
         if (!isEnabled()) {
             return null;
         }
-        final int degrees = getDegreesFromXY(event.getX(), event.getY(), false);
-        if (degrees != -1) {
-            return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
+        if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+            final int degrees = getDegreesFromXY(event.getX(), event.getY(), false);
+            if (degrees != -1) {
+                return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
+            }
         }
         return super.onResolvePointerIcon(event, pointerIndex);
     }
diff --git a/core/java/android/widget/RemoteViews.aidl b/core/java/android/widget/RemoteViews.aidl
index ec86410..6a5fc03 100644
--- a/core/java/android/widget/RemoteViews.aidl
+++ b/core/java/android/widget/RemoteViews.aidl
@@ -17,3 +17,4 @@
 package android.widget;
 
 parcelable RemoteViews;
+parcelable RemoteViews.RemoteCollectionItems;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7f96266..7903dd64 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -33,16 +33,19 @@
 import android.app.Activity;
 import android.app.ActivityOptions;
 import android.app.ActivityThread;
+import android.app.AppGlobals;
 import android.app.Application;
 import android.app.LoadedApk;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
 import android.appwidget.AppWidgetHostView;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.ColorStateList;
@@ -65,10 +68,12 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.StrictMode;
 import android.os.UserHandle;
 import android.system.Os;
@@ -98,8 +103,10 @@
 import android.widget.CompoundButton.OnCheckedChangeListener;
 
 import com.android.internal.R;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.internal.util.Preconditions;
+import com.android.internal.widget.IRemoteViewsFactory;
 
 import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
@@ -124,7 +131,9 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Stack;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -232,6 +241,7 @@
     private static final int NIGHT_MODE_REFLECTION_ACTION_TAG = 30;
     private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31;
     private static final int ATTRIBUTE_REFLECTION_ACTION_TAG = 32;
+    private static final int SET_REMOTE_ADAPTER_TAG = 33;
 
     /** @hide **/
     @IntDef(prefix = "MARGIN_", value = {
@@ -323,6 +333,13 @@
             (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
 
     /**
+     * The maximum waiting time for remote adapter conversion in milliseconds
+     *
+     * @hide
+     */
+    private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 5000;
+
+    /**
      * Application that hosts the remote views.
      *
      * @hide
@@ -412,6 +429,13 @@
     /** Class cookies of the Parcel this instance was read from. */
     private Map<Class, Object> mClassCookies;
 
+    /**
+     * {@link LayoutInflater.Factory2} which will be passed into a {@link LayoutInflater} instance
+     * used by this class.
+     */
+    @Nullable
+    private LayoutInflater.Factory2 mLayoutInflaterFactory2;
+
     private static final InteractionHandler DEFAULT_INTERACTION_HANDLER =
             (view, pendingIntent, response) ->
                     startPendingIntent(view, pendingIntent, response.getLaunchOptions(view));
@@ -431,6 +455,29 @@
     }
 
     /**
+     * Sets {@link LayoutInflater.Factory2} to be passed into {@link LayoutInflater} used
+     * by this class instance. It has to be set before the views are inflated to have any effect.
+     *
+     * The factory callbacks will be called on the background thread so the implementation needs
+     * to be thread safe.
+     *
+     * @hide
+     */
+    public void setLayoutInflaterFactory(@Nullable LayoutInflater.Factory2 factory) {
+        mLayoutInflaterFactory2 = factory;
+    }
+
+    /**
+     * Returns currently set {@link LayoutInflater.Factory2}.
+     *
+     * @hide
+     */
+    @Nullable
+    public LayoutInflater.Factory2 getLayoutInflaterFactory() {
+        return mLayoutInflaterFactory2;
+    }
+
+    /**
      * Reduces all images and ensures that they are all below the given sizes.
      *
      * @param maxWidth the maximum width allowed
@@ -739,6 +786,42 @@
         }
     }
 
+    /**
+     * @hide
+     * @return True if there is a change
+     */
+    public boolean replaceRemoteCollections(int viewId) {
+        boolean isActionReplaced = false;
+        if (mActions != null) {
+            for (int i = 0; i < mActions.size(); i++) {
+                Action action = mActions.get(i);
+                if (action instanceof SetRemoteCollectionItemListAdapterAction itemsAction
+                        && itemsAction.viewId == viewId
+                        && itemsAction.mServiceIntent != null) {
+                    mActions.set(i, new SetRemoteCollectionItemListAdapterAction(itemsAction.viewId,
+                            itemsAction.mServiceIntent));
+                    isActionReplaced = true;
+                } else if (action instanceof ViewGroupActionAdd groupAction
+                        && groupAction.mNestedViews != null) {
+                    isActionReplaced |= groupAction.mNestedViews.replaceRemoteCollections(viewId);
+                }
+            }
+        }
+        if (mSizedRemoteViews != null) {
+            for (int i = 0; i < mSizedRemoteViews.size(); i++) {
+                isActionReplaced |= mSizedRemoteViews.get(i).replaceRemoteCollections(viewId);
+            }
+        }
+        if (mLandscape != null) {
+            isActionReplaced |= mLandscape.replaceRemoteCollections(viewId);
+        }
+        if (mPortrait != null) {
+            isActionReplaced |= mPortrait.replaceRemoteCollections(viewId);
+        }
+
+        return isActionReplaced;
+    }
+
     private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) {
         if (icon != null && (icon.getType() == Icon.TYPE_URI
                 || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
@@ -960,6 +1043,11 @@
             return SET_REMOTE_VIEW_ADAPTER_LIST_TAG;
         }
 
+        @Override
+        public String getUniqueKey() {
+            return (SET_REMOTE_ADAPTER_TAG + "_" + viewId);
+        }
+
         int viewTypeCount;
         ArrayList<RemoteViews> list;
     }
@@ -1006,28 +1094,101 @@
     }
 
     private class SetRemoteCollectionItemListAdapterAction extends Action {
-        private final RemoteCollectionItems mItems;
+        private @NonNull CompletableFuture<RemoteCollectionItems> mItemsFuture;
+        final Intent mServiceIntent;
 
-        SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) {
+        SetRemoteCollectionItemListAdapterAction(@IdRes int id,
+                @NonNull RemoteCollectionItems items) {
             viewId = id;
-            mItems = items;
-            mItems.setHierarchyRootData(getHierarchyRootData());
+            items.setHierarchyRootData(getHierarchyRootData());
+            mItemsFuture = CompletableFuture.completedFuture(items);
+            mServiceIntent = null;
+        }
+
+        SetRemoteCollectionItemListAdapterAction(@IdRes int id, Intent intent) {
+            viewId = id;
+            mItemsFuture = getItemsFutureFromIntentWithTimeout(intent);
+            setHierarchyRootData(getHierarchyRootData());
+            mServiceIntent = intent;
+        }
+
+        private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout(
+                Intent intent) {
+            if (intent == null) {
+                Log.e(LOG_TAG, "Null intent received when generating adapter future");
+                return CompletableFuture.completedFuture(new RemoteCollectionItems
+                        .Builder().build());
+            }
+
+            final Context context = ActivityThread.currentApplication();
+            final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
+
+            context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
+                    result.defaultExecutor(), new ServiceConnection() {
+                        @Override
+                        public void onServiceConnected(ComponentName componentName,
+                                IBinder iBinder) {
+                            RemoteCollectionItems items;
+                            try {
+                                items = IRemoteViewsFactory.Stub.asInterface(iBinder)
+                                        .getRemoteCollectionItems();
+                            } catch (RemoteException re) {
+                                items = new RemoteCollectionItems.Builder().build();
+                                Log.e(LOG_TAG, "Error getting collection items from the factory",
+                                        re);
+                            } finally {
+                                context.unbindService(this);
+                            }
+
+                            result.complete(items);
+                        }
+
+                        @Override
+                        public void onServiceDisconnected(ComponentName componentName) { }
+                    });
+
+            result.completeOnTimeout(
+                    new RemoteCollectionItems.Builder().build(),
+                    MAX_ADAPTER_CONVERSION_WAITING_TIME_MS, TimeUnit.MILLISECONDS);
+
+            return result;
         }
 
         SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
             viewId = parcel.readInt();
-            mItems = new RemoteCollectionItems(parcel, getHierarchyRootData());
+            mItemsFuture = CompletableFuture.completedFuture(
+                    new RemoteCollectionItems(parcel, getHierarchyRootData()));
+            mServiceIntent = parcel.readTypedObject(Intent.CREATOR);
         }
 
         @Override
         public void setHierarchyRootData(HierarchyRootData rootData) {
-            mItems.setHierarchyRootData(rootData);
+            mItemsFuture = mItemsFuture
+                    .thenApply(rc -> {
+                        rc.setHierarchyRootData(rootData);
+                        return rc;
+                    });
+        }
+
+        private static RemoteCollectionItems getCollectionItemsFromFuture(
+                CompletableFuture<RemoteCollectionItems> itemsFuture) {
+            RemoteCollectionItems items;
+            try {
+                items = itemsFuture.get();
+            } catch (Exception e) {
+                Log.e(LOG_TAG, "Error getting collection items from future", e);
+                items = new RemoteCollectionItems.Builder().build();
+            }
+
+            return items;
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(viewId);
-            mItems.writeToParcel(dest, flags, /* attached= */ true);
+            RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+            items.writeToParcel(dest, flags, /* attached= */ true);
+            dest.writeTypedObject(mServiceIntent, flags);
         }
 
         @Override
@@ -1036,6 +1197,8 @@
             View target = root.findViewById(viewId);
             if (target == null) return;
 
+            RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+
             // Ensure that we are applying to an AppWidget root
             if (!(rootParent instanceof AppWidgetHostView)) {
                 Log.e(LOG_TAG, "setRemoteAdapter can only be used for "
@@ -1056,10 +1219,10 @@
             // recycling in setAdapter, so we must call setAdapter again if the number of view types
             // increases.
             if (adapter instanceof RemoteCollectionItemsAdapter
-                    && adapter.getViewTypeCount() >= mItems.getViewTypeCount()) {
+                    && adapter.getViewTypeCount() >= items.getViewTypeCount()) {
                 try {
                     ((RemoteCollectionItemsAdapter) adapter).setData(
-                            mItems, params.handler, params.colorResources);
+                            items, params.handler, params.colorResources);
                 } catch (Throwable throwable) {
                     // setData should never failed with the validation in the items builder, but if
                     // it does, catch and rethrow.
@@ -1069,7 +1232,7 @@
             }
 
             try {
-                adapterView.setAdapter(new RemoteCollectionItemsAdapter(mItems,
+                adapterView.setAdapter(new RemoteCollectionItemsAdapter(items,
                         params.handler, params.colorResources));
             } catch (Throwable throwable) {
                 // This could throw if the AdapterView somehow doesn't accept BaseAdapter due to
@@ -1082,6 +1245,11 @@
         public int getActionTag() {
             return SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG;
         }
+
+        @Override
+        public String getUniqueKey() {
+            return (SET_REMOTE_ADAPTER_TAG + "_" + viewId);
+        }
     }
 
     private class SetRemoteViewsAdapterIntent extends Action {
@@ -4638,10 +4806,24 @@
      *            providing data to the RemoteViewsAdapter
      */
     public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
+        if (isAdapterConversionEnabled()) {
+            addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
+            return;
+        }
         addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
     }
 
     /**
+     * @hide
+     * @return True if the remote adapter conversion is enabled
+     */
+    public static boolean isAdapterConversionEnabled() {
+        return AppGlobals.getIntCoreSetting(
+                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
+                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) != 0;
+    }
+
+    /**
      * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView,
      * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}.
      * This is a simpler but less flexible approach to populating collection widgets. Its use is
@@ -5648,6 +5830,9 @@
         // we don't add a filter to the static version returned by getSystemService.
         inflater = inflater.cloneInContext(inflationContext);
         inflater.setFilter(shouldUseStaticFilter() ? INFLATER_FILTER : this);
+        if (mLayoutInflaterFactory2 != null) {
+            inflater.setFactory2(mLayoutInflaterFactory2);
+        }
         View v = inflater.inflate(rv.getLayoutId(), parent, false);
         if (mViewId != View.NO_ID) {
             v.setId(mViewId);
@@ -6760,6 +6945,7 @@
             // something is going to start.
             opts.setPendingIntentBackgroundActivityStartMode(
                     ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+            opts.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
             return Pair.create(intent, opts);
         }
     }
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index 214e5cc..a250a86 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -43,6 +43,13 @@
     private static final Object sLock = new Object();
 
     /**
+     * Used for determining the maximum number of entries to retrieve from RemoteViewsFactory
+     *
+     * @hide
+     */
+    private static final int MAX_NUM_ENTRY = 10;
+
+    /**
      * An interface for an adapter between a remote collection view (ListView, GridView, etc) and
      * the underlying data for that view.  The implementor is responsible for making a RemoteView
      * for each item in the data set. This interface is a thin wrapper around {@link Adapter}.
@@ -227,6 +234,30 @@
             }
         }
 
+        @Override
+        public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+            RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems
+                    .Builder().build();
+
+            try {
+                RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
+                        new RemoteViews.RemoteCollectionItems.Builder();
+                mFactory.onDataSetChanged();
+
+                itemsBuilder.setHasStableIds(mFactory.hasStableIds());
+                final int numOfEntries = Math.min(mFactory.getCount(), MAX_NUM_ENTRY);
+                for (int i = 0; i < numOfEntries; i++) {
+                    itemsBuilder.addItem(mFactory.getItemId(i), mFactory.getViewAt(i));
+                }
+
+                items = itemsBuilder.build();
+            } catch (Exception ex) {
+                Thread t = Thread.currentThread();
+                Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+            }
+            return items;
+        }
+
         private RemoteViewsFactory mFactory;
         private boolean mIsCreated;
     }
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index cb5dbe6..e591e9e 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -1545,6 +1545,11 @@
      * @return The unconsumed delta after the EdgeEffects have had an opportunity to consume.
      */
     private int consumeFlingInStretch(int unconsumed) {
+        int scrollY = getScrollY();
+        if (scrollY < 0 || scrollY > getScrollRange()) {
+            // We've overscrolled, so don't stretch
+            return unconsumed;
+        }
         if (unconsumed > 0 && mEdgeGlowTop != null && mEdgeGlowTop.getDistance() != 0f) {
             int size = getHeight();
             float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size;
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index 6c53a44..1317b51 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -39,6 +39,7 @@
 import android.util.IntArray;
 import android.util.MathUtils;
 import android.util.StateSet;
+import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
@@ -1041,12 +1042,15 @@
         if (!isEnabled()) {
             return null;
         }
-        // Add 0.5f to event coordinates to match the logic in onTouchEvent.
-        final int x = (int) (event.getX() + 0.5f);
-        final int y = (int) (event.getY() + 0.5f);
-        final int dayUnderPointer = getDayAtLocation(x, y);
-        if (dayUnderPointer >= 0) {
-            return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
+
+        if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+            // Add 0.5f to event coordinates to match the logic in onTouchEvent.
+            final int x = (int) (event.getX() + 0.5f);
+            final int y = (int) (event.getY() + 0.5f);
+            final int dayUnderPointer = getDayAtLocation(x, y);
+            if (dayUnderPointer >= 0) {
+                return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
+            }
         }
         return super.onResolvePointerIcon(event, pointerIndex);
     }
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index ad431ef..ecc41a5 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -38,6 +38,7 @@
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.View;
@@ -935,7 +936,8 @@
 
     @Override
     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
-        if (getPointerIcon() == null && isClickable() && isEnabled()) {
+        if (getPointerIcon() == null && isClickable() && isEnabled()
+                && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
             return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND);
         }
         return super.onResolvePointerIcon(event, pointerIndex);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index db7d484..b74c879 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -156,7 +156,6 @@
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
-import android.util.FeatureFlagUtils;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -831,11 +830,6 @@
     private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
     private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
 
-    // The auto option for LINE_BREAK_WORD_STYLE_PHRASE may not be applied in recycled view due to
-    // one-way flag flipping. This is a tentative limitation during experiment and will not have the
-    // issue once this is finalized to LINE_BREAK_WORD_STYLE_PHRASE_AUTO option.
-    private boolean mUserSpeficiedLineBreakwordStyle = false;
-
     // This is used to reflect the current user preference for changing font weight and making text
     // more bold.
     private int mFontWeightAdjustment;
@@ -1546,9 +1540,6 @@
                     break;
 
                 case com.android.internal.R.styleable.TextView_lineBreakWordStyle:
-                    if (a.hasValue(attr)) {
-                        mUserSpeficiedLineBreakwordStyle = true;
-                    }
                     mLineBreakWordStyle = a.getInt(attr,
                             LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE);
                     break;
@@ -2795,11 +2786,22 @@
         if (mEditor != null) {
             mEditor.setTransformationMethod(method);
         } else {
-            setTransformationMethodInternal(method);
+            setTransformationMethodInternal(method, /* updateText */ true);
         }
     }
 
-    void setTransformationMethodInternal(@Nullable TransformationMethod method) {
+    /**
+     * Set the transformation that is applied to the text that this TextView is displaying,
+     * optionally call the setText.
+     * @param method the new transformation method to be set.
+     * @param updateText whether the call {@link #setText} which will update the TextView to display
+     *                   the new content. This method is helpful when updating
+     *                   {@link TransformationMethod} inside {@link #setText}. It should only be
+     *                   false if text will be updated immediately after this call, otherwise the
+     *                   TextView will enter an inconsistent state.
+     */
+    void setTransformationMethodInternal(@Nullable TransformationMethod method,
+            boolean updateText) {
         if (method == mTransformation) {
             // Avoid the setText() below if the transformation is
             // the same.
@@ -2821,7 +2823,9 @@
             mAllowTransformationLengthChange = false;
         }
 
-        setText(mText);
+        if (updateText) {
+            setText(mText);
+        }
 
         if (hasPasswordTransformationMethod()) {
             notifyViewAccessibilityStateChangedIfNeeded(
@@ -4337,7 +4341,6 @@
                     break;
                 case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle:
                     attributes.mHasLineBreakWordStyle = true;
-                    mUserSpeficiedLineBreakwordStyle = true;
                     attributes.mLineBreakWordStyle =
                             appearance.getInt(attr, attributes.mLineBreakWordStyle);
                     break;
@@ -5073,7 +5076,6 @@
      * @param lineBreakWordStyle The line-break word style for the text.
      */
     public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
-        mUserSpeficiedLineBreakwordStyle = true;
         if (mLineBreakWordStyle != lineBreakWordStyle) {
             mLineBreakWordStyle = lineBreakWordStyle;
             if (mLayout != null) {
@@ -5109,12 +5111,8 @@
      * @see PrecomputedText
      */
     public @NonNull PrecomputedText.Params getTextMetricsParams() {
-        final boolean autoPhraseBreaking =
-                !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
-                        FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
         return new PrecomputedText.Params(new TextPaint(mTextPaint),
-                LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle,
-                        autoPhraseBreaking),
+                LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle),
                 getTextDirectionHeuristic(),
                 mBreakStrategy, mHyphenationFrequency);
     }
@@ -5134,7 +5132,6 @@
         LineBreakConfig lineBreakConfig = params.getLineBreakConfig();
         mLineBreakStyle = lineBreakConfig.getLineBreakStyle();
         mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle();
-        mUserSpeficiedLineBreakwordStyle = true;
         if (mLayout != null) {
             nullLayouts();
             requestLayout();
@@ -6941,12 +6938,26 @@
      *                                  parameters used to create the PrecomputedText mismatches
      *                                  with this TextView.
      */
-    @android.view.RemotableViewMethod
+    @android.view.RemotableViewMethod(asyncImpl = "setTextAsync")
     public final void setText(CharSequence text) {
         setText(text, mBufferType);
     }
 
     /**
+     * RemotableViewMethod's asyncImpl of {@link #setText(CharSequence)}.
+     * This should be called on a background thread, and returns a Runnable which is then must be
+     * called on the main thread to complete the operation and set text.
+     * @param text text to be displayed
+     * @return Runnable that sets text; must be called on the main thread by the caller of this
+     * method to complete the operation
+     * @hide
+     */
+    @NonNull
+    public Runnable setTextAsync(@Nullable CharSequence text) {
+        return () -> setText(text);
+    }
+
+    /**
      * Sets the text to be displayed but retains the cursor position. Same as
      * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the
      * new text.
@@ -7000,6 +7011,9 @@
     @UnsupportedAppUsage
     private void setText(CharSequence text, BufferType type,
                          boolean notifyBefore, int oldlen) {
+        if (mEditor != null) {
+            mEditor.beforeSetText();
+        }
         mTextSetFromXmlOrResourceId = false;
         if (text == null) {
             text = "";
@@ -7061,13 +7075,10 @@
             if (mTextDir == null) {
                 mTextDir = getTextDirectionHeuristic();
             }
-            final boolean autoPhraseBreaking =
-                    !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
-                            FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
             final @PrecomputedText.Params.CheckResultUsableResult int checkResult =
                     precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy,
                             mHyphenationFrequency, LineBreakConfig.getLineBreakConfig(
-                                    mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+                                    mLineBreakStyle, mLineBreakWordStyle));
             switch (checkResult) {
                 case PrecomputedText.Params.UNUSABLE:
                     throw new IllegalArgumentException(
@@ -9223,18 +9234,20 @@
 
     @Override
     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
-        if (mSpannable != null && mLinksClickable) {
-            final float x = event.getX(pointerIndex);
-            final float y = event.getY(pointerIndex);
-            final int offset = getOffsetForPosition(x, y);
-            final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
-                    ClickableSpan.class);
-            if (clickables.length > 0) {
-                return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
+        if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+            if (mSpannable != null && mLinksClickable) {
+                final float x = event.getX(pointerIndex);
+                final float y = event.getY(pointerIndex);
+                final int offset = getOffsetForPosition(x, y);
+                final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
+                        ClickableSpan.class);
+                if (clickables.length > 0) {
+                    return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
+                }
             }
-        }
-        if (isTextSelectable() || isTextEditable()) {
-            return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
+            if (isTextSelectable() || isTextEditable()) {
+                return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT);
+            }
         }
         return super.onResolvePointerIcon(event, pointerIndex);
     }
@@ -10622,9 +10635,6 @@
             }
             // TODO: code duplication with makeSingleLayout()
             if (mHintLayout == null) {
-                final boolean autoPhraseBreaking =
-                        !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
-                                FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
                 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
                         mHint.length(), mTextPaint, hintWidth)
                         .setAlignment(alignment)
@@ -10637,7 +10647,7 @@
                         .setJustificationMode(mJustificationMode)
                         .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
                         .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
-                                mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+                                mLineBreakStyle, mLineBreakWordStyle));
                 if (shouldEllipsize) {
                     builder.setEllipsize(mEllipsize)
                             .setEllipsizedWidth(ellipsisWidth);
@@ -10741,9 +10751,6 @@
             }
         }
         if (result == null) {
-            final boolean autoPhraseBreaking =
-                    !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
-                            FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
             StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
                     0, mTransformed.length(), mTextPaint, wantWidth)
                     .setAlignment(alignment)
@@ -10756,7 +10763,7 @@
                     .setJustificationMode(mJustificationMode)
                     .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
                     .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
-                            mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+                            mLineBreakStyle, mLineBreakWordStyle));
             if (shouldEllipsize) {
                 builder.setEllipsize(effectiveEllipsize)
                         .setEllipsizedWidth(ellipsisWidth);
@@ -11114,9 +11121,6 @@
 
         final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(
                 text, 0, text.length(),  mTempTextPaint, Math.round(availableSpace.right));
-        final boolean autoPhraseBreaking =
-                !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext,
-                        FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING);
         layoutBuilder.setAlignment(getLayoutAlignment())
                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
                 .setIncludePad(getIncludeFontPadding())
@@ -11127,7 +11131,7 @@
                 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
                 .setTextDirection(getTextDirectionHeuristic())
                 .setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
-                        mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking));
+                        mLineBreakStyle, mLineBreakWordStyle));
 
         final StaticLayout layout = layoutBuilder.build();
 
@@ -13809,13 +13813,14 @@
     }
 
     /**
-     * Helper method to set {@code rect} to the text content's non-clipped area in the view's
-     * coordinates.
+     * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates.
+     * This method obtains the view's visible rectangle whereas the method
+     * {@link #getContentVisibleRect} returns the text layout's visible rectangle.
      *
      * @return true if at least part of the text content is visible; false if the text content is
      * completely clipped or translated out of the visible area.
      */
-    private boolean getContentVisibleRect(Rect rect) {
+    private boolean getViewVisibleRect(Rect rect) {
         if (!getLocalVisibleRect(rect)) {
             return false;
         }
@@ -13824,6 +13829,20 @@
         // view's coordinates. So we need to offset it with the negative scrolled amount to convert
         // it to view's coordinate.
         rect.offset(-getScrollX(), -getScrollY());
+        return true;
+    }
+
+    /**
+     * Helper method to set {@code rect} to the text content's non-clipped area in the view's
+     * coordinates.
+     *
+     * @return true if at least part of the text content is visible; false if the text content is
+     * completely clipped or translated out of the visible area.
+     */
+    private boolean getContentVisibleRect(Rect rect) {
+        if (!getViewVisibleRect(rect)) {
+            return false;
+        }
         // Clip the view's visible rect with the text layout's visible rect.
         return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(),
                 getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
@@ -13953,14 +13972,25 @@
         builder.setMatrix(viewToScreenMatrix);
 
         if (includeEditorBounds) {
-            final RectF editorBounds = new RectF();
-            editorBounds.set(0 /* left */, 0 /* top */,
-                    getWidth(), getHeight());
-            final RectF handwritingBounds = new RectF(
-                    -getHandwritingBoundsOffsetLeft(),
-                    -getHandwritingBoundsOffsetTop(),
-                    getWidth() + getHandwritingBoundsOffsetRight(),
-                    getHeight() + getHandwritingBoundsOffsetBottom());
+            if (mTempRect == null) {
+                mTempRect = new Rect();
+            }
+            final Rect bounds = mTempRect;
+            final RectF editorBounds;
+            final RectF handwritingBounds;
+            if (getViewVisibleRect(bounds)) {
+                editorBounds = new RectF(bounds);
+                handwritingBounds = new RectF(editorBounds);
+                handwritingBounds.top -= getHandwritingBoundsOffsetTop();
+                handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
+                handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
+                handwritingBounds.right += getHandwritingBoundsOffsetRight();
+            } else {
+                // The editor is not visible at all, return empty rectangles. We still need to
+                // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo.
+                editorBounds = new RectF();
+                handwritingBounds = new RectF();
+            }
             EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
             EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds)
                     .setHandwritingBounds(handwritingBounds).build();
diff --git a/core/java/android/window/ConfigurationHelper.java b/core/java/android/window/ConfigurationHelper.java
index 269ce08..e32adcf 100644
--- a/core/java/android/window/ConfigurationHelper.java
+++ b/core/java/android/window/ConfigurationHelper.java
@@ -106,7 +106,7 @@
      * @see WindowManager#getCurrentWindowMetrics()
      * @see WindowManager#getMaximumWindowMetrics()
      */
-    public static boolean shouldUpdateWindowMetricsBounds(@NonNull Configuration currentConfig,
+    private static boolean shouldUpdateWindowMetricsBounds(@NonNull Configuration currentConfig,
             @NonNull Configuration newConfig) {
         final Rect currentBounds = currentConfig.windowConfiguration.getBounds();
         final Rect newBounds = newConfig.windowConfiguration.getBounds();
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 534c9de..5ba2f6c 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -80,13 +80,8 @@
      * Finishes a transition. This must be called for all created transitions.
      * @param transitionToken Which transition to finish
      * @param t Changes to make before finishing but in the same SF Transaction. Can be null.
-     * @param callback Called when t is finished applying.
-     * @return An ID for the sync operation (see {@link #applySyncTransaction}. This will be
-     *         negative if no sync transaction was attached (null t or callback)
      */
-    int finishTransition(in IBinder transitionToken,
-            in @nullable WindowContainerTransaction t,
-            in IWindowContainerTransactionCallback callback);
+    void finishTransition(in IBinder transitionToken, in @nullable WindowContainerTransaction t);
 
     /** @return An interface enabling the management of task organizers. */
     ITaskOrganizerController getTaskOrganizerController();
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 413f0cc..43fa0be 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -73,6 +73,21 @@
     /** Sets the relative bounds with {@link WindowContainerTransaction#setRelativeBounds}. */
     public static final int OP_TYPE_SET_RELATIVE_BOUNDS = 9;
 
+    /**
+     * Reorders the TaskFragment to be the front-most TaskFragment in the Task.
+     * Note that there could still have other WindowContainer on top of the front-most
+     * TaskFragment, such as a non-embedded Activity.
+     */
+    public static final int OP_TYPE_REORDER_TO_FRONT = 10;
+
+    /**
+     * Sets the activity navigation to be isolated, where the activity navigation on the
+     * TaskFragment is separated from the rest activities in the Task. Activities cannot be
+     * started on an isolated TaskFragment unless the activities are launched from the same
+     * TaskFragment or explicitly requested to.
+     */
+    public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11;
+
     @IntDef(prefix = { "OP_TYPE_" }, value = {
             OP_TYPE_UNKNOWN,
             OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -84,7 +99,9 @@
             OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT,
             OP_TYPE_SET_COMPANION_TASK_FRAGMENT,
             OP_TYPE_SET_ANIMATION_PARAMS,
-            OP_TYPE_SET_RELATIVE_BOUNDS
+            OP_TYPE_SET_RELATIVE_BOUNDS,
+            OP_TYPE_REORDER_TO_FRONT,
+            OP_TYPE_SET_ISOLATED_NAVIGATION
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface OperationType {}
@@ -110,11 +127,14 @@
     @Nullable
     private final TaskFragmentAnimationParams mAnimationParams;
 
+    private final boolean mIsolatedNav;
+
     private TaskFragmentOperation(@OperationType int opType,
             @Nullable TaskFragmentCreationParams taskFragmentCreationParams,
             @Nullable IBinder activityToken, @Nullable Intent activityIntent,
             @Nullable Bundle bundle, @Nullable IBinder secondaryFragmentToken,
-            @Nullable TaskFragmentAnimationParams animationParams) {
+            @Nullable TaskFragmentAnimationParams animationParams,
+            boolean isolatedNav) {
         mOpType = opType;
         mTaskFragmentCreationParams = taskFragmentCreationParams;
         mActivityToken = activityToken;
@@ -122,6 +142,7 @@
         mBundle = bundle;
         mSecondaryFragmentToken = secondaryFragmentToken;
         mAnimationParams = animationParams;
+        mIsolatedNav = isolatedNav;
     }
 
     private TaskFragmentOperation(Parcel in) {
@@ -132,6 +153,7 @@
         mBundle = in.readBundle(getClass().getClassLoader());
         mSecondaryFragmentToken = in.readStrongBinder();
         mAnimationParams = in.readTypedObject(TaskFragmentAnimationParams.CREATOR);
+        mIsolatedNav = in.readBoolean();
     }
 
     @Override
@@ -143,6 +165,7 @@
         dest.writeBundle(mBundle);
         dest.writeStrongBinder(mSecondaryFragmentToken);
         dest.writeTypedObject(mAnimationParams, flags);
+        dest.writeBoolean(mIsolatedNav);
     }
 
     @NonNull
@@ -215,6 +238,14 @@
         return mAnimationParams;
     }
 
+    /**
+     * Returns whether the activity navigation on this TaskFragment is isolated. This is only
+     * useful when the op type is {@link OP_TYPE_SET_ISOLATED_NAVIGATION}.
+     */
+    public boolean isIsolatedNav() {
+        return mIsolatedNav;
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
@@ -237,6 +268,7 @@
         if (mAnimationParams != null) {
             sb.append(", animationParams=").append(mAnimationParams);
         }
+        sb.append(", isolatedNav=").append(mIsolatedNav);
 
         sb.append('}');
         return sb.toString();
@@ -245,7 +277,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent,
-                mBundle, mSecondaryFragmentToken, mAnimationParams);
+                mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav);
     }
 
     @Override
@@ -260,7 +292,8 @@
                 && Objects.equals(mActivityIntent, other.mActivityIntent)
                 && Objects.equals(mBundle, other.mBundle)
                 && Objects.equals(mSecondaryFragmentToken, other.mSecondaryFragmentToken)
-                && Objects.equals(mAnimationParams, other.mAnimationParams);
+                && Objects.equals(mAnimationParams, other.mAnimationParams)
+                && mIsolatedNav == other.mIsolatedNav;
     }
 
     @Override
@@ -292,6 +325,8 @@
         @Nullable
         private TaskFragmentAnimationParams mAnimationParams;
 
+        private boolean mIsolatedNav;
+
         /**
          * @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
          */
@@ -355,12 +390,22 @@
         }
 
         /**
+         * Sets the activity navigation of this TaskFragment to be isolated.
+         */
+        @NonNull
+        public Builder setIsolatedNav(boolean isolatedNav) {
+            mIsolatedNav = isolatedNav;
+            return this;
+        }
+
+        /**
          * Constructs the {@link TaskFragmentOperation}.
          */
         @NonNull
         public TaskFragmentOperation build() {
             return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken,
-                    mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams);
+                    mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams,
+                    mIsolatedNav);
         }
     }
 }
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 9f804b1..3323ae5 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -688,6 +688,7 @@
                         .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type)
                                 .setSource(InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE)
                                 .setArbitraryRectangle(frame))
+                        .setInsetsFrameOwner(owner)
                         .build();
         mHierarchyOps.add(hierarchyOp);
         return this;
@@ -712,6 +713,7 @@
                 new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER)
                         .setContainer(receiver.asBinder())
                         .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type))
+                        .setInsetsFrameOwner(owner)
                         .build();
         mHierarchyOps.add(hierarchyOp);
         return this;
@@ -1344,8 +1346,12 @@
         @Nullable
         private IBinder mReparent;
 
+        @Nullable
         private InsetsFrameProvider mInsetsFrameProvider;
 
+        @Nullable
+        private IBinder mInsetsFrameOwner;
+
         // Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
         private boolean mToTop;
 
@@ -1478,6 +1484,7 @@
             mContainer = copy.mContainer;
             mReparent = copy.mReparent;
             mInsetsFrameProvider = copy.mInsetsFrameProvider;
+            mInsetsFrameOwner = copy.mInsetsFrameOwner;
             mToTop = copy.mToTop;
             mReparentTopOnly = copy.mReparentTopOnly;
             mWindowingModes = copy.mWindowingModes;
@@ -1496,6 +1503,7 @@
             mContainer = in.readStrongBinder();
             mReparent = in.readStrongBinder();
             mInsetsFrameProvider = in.readTypedObject(InsetsFrameProvider.CREATOR);
+            mInsetsFrameOwner = in.readStrongBinder();
             mToTop = in.readBoolean();
             mReparentTopOnly = in.readBoolean();
             mWindowingModes = in.createIntArray();
@@ -1527,6 +1535,11 @@
             return mInsetsFrameProvider;
         }
 
+        @Nullable
+        public IBinder getInsetsFrameOwner() {
+            return mInsetsFrameOwner;
+        }
+
         @NonNull
         public IBinder getContainer() {
             return mContainer;
@@ -1657,7 +1670,8 @@
                 case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER:
                 case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER:
                     sb.append("container=").append(mContainer)
-                            .append(" provider=").append(mInsetsFrameProvider);
+                            .append(" provider=").append(mInsetsFrameProvider)
+                            .append(" owner=").append(mInsetsFrameOwner);
                     break;
                 case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
                     sb.append("container=").append(mContainer)
@@ -1697,6 +1711,7 @@
             dest.writeStrongBinder(mContainer);
             dest.writeStrongBinder(mReparent);
             dest.writeTypedObject(mInsetsFrameProvider, flags);
+            dest.writeStrongBinder(mInsetsFrameOwner);
             dest.writeBoolean(mToTop);
             dest.writeBoolean(mReparentTopOnly);
             dest.writeIntArray(mWindowingModes);
@@ -1737,8 +1752,12 @@
             @Nullable
             private IBinder mReparent;
 
+            @Nullable
             private InsetsFrameProvider mInsetsFrameProvider;
 
+            @Nullable
+            private IBinder mInsetsFrameOwner;
+
             private boolean mToTop;
 
             private boolean mReparentTopOnly;
@@ -1782,8 +1801,13 @@
                 return this;
             }
 
-            Builder setInsetsFrameProvider(InsetsFrameProvider providers) {
-                mInsetsFrameProvider = providers;
+            Builder setInsetsFrameProvider(InsetsFrameProvider provider) {
+                mInsetsFrameProvider = provider;
+                return this;
+            }
+
+            Builder setInsetsFrameOwner(IBinder owner) {
+                mInsetsFrameOwner = owner;
                 return this;
             }
 
@@ -1854,6 +1878,7 @@
                         ? Arrays.copyOf(mActivityTypes, mActivityTypes.length)
                         : null;
                 hierarchyOp.mInsetsFrameProvider = mInsetsFrameProvider;
+                hierarchyOp.mInsetsFrameOwner = mInsetsFrameOwner;
                 hierarchyOp.mToTop = mToTop;
                 hierarchyOp.mReparentTopOnly = mReparentTopOnly;
                 hierarchyOp.mLaunchOptions = mLaunchOptions;
diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java
index 25bf85c..7fc55a7 100644
--- a/core/java/android/window/WindowInfosListenerForTest.java
+++ b/core/java/android/window/WindowInfosListenerForTest.java
@@ -87,6 +87,14 @@
             this.isTrustedOverlay = (inputConfig & InputConfig.TRUSTED_OVERLAY) != 0;
             this.isVisible = (inputConfig & InputConfig.NOT_VISIBLE) == 0;
         }
+
+        @Override
+        public String toString() {
+            return name + ", frame=" + bounds
+                    + ", isVisible=" + isVisible
+                    + ", isTrustedOverlay=" + isTrustedOverlay
+                    + ", token=" + windowToken;
+        }
     }
 
     private static final String TAG = "WindowInfosListenerForTest";
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 954f686..2858f0a 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -145,13 +145,13 @@
         for (int i = 0; i < possibleDisplayInfos.size(); i++) {
             currentDisplayInfo = possibleDisplayInfos.get(i);
 
-            // Calculate max bounds for this rotation and state.
-            Rect maxBounds = new Rect(0, 0, currentDisplayInfo.logicalWidth,
-                    currentDisplayInfo.logicalHeight);
+            // Calculate max bounds for natural rotation and state.
+            Rect maxBounds = new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
+                    currentDisplayInfo.getNaturalHeight());
 
-            // Calculate insets for the rotated max bounds.
+            // Calculate insets for the natural max bounds.
             final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0;
-            // Initialize insets based upon display rotation. Note any window-provided insets
+            // Initialize insets based on Surface.ROTATION_0. Note any window-provided insets
             // will not be set.
             windowInsets = getWindowInsetsFromServerForDisplay(
                     currentDisplayInfo.displayId, null /* token */,
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 695d01e..2dc2cbc 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -115,19 +115,15 @@
      * Finishes a running transition.
      * @param transitionToken The transition to finish. Can't be null.
      * @param t A set of window operations to apply before finishing.
-     * @param callback A sync callback (if provided). See {@link #applySyncTransaction}.
-     * @return An ID for the sync operation if performed. See {@link #applySyncTransaction}.
      *
      * @hide
      */
     @SuppressLint("ExecutorRegistration")
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
-    public int finishTransition(@NonNull IBinder transitionToken,
-            @Nullable WindowContainerTransaction t,
-            @Nullable WindowContainerTransactionCallback callback) {
+    public void finishTransition(@NonNull IBinder transitionToken,
+            @Nullable WindowContainerTransaction t) {
         try {
-            return getWindowOrganizerController().finishTransition(transitionToken, t,
-                    callback != null ? callback.mInterface : null);
+            getWindowOrganizerController().finishTransition(transitionToken, t);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index f2ae973..611da3c 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -34,6 +34,7 @@
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.util.Log;
 import android.view.Display;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams.WindowType;
@@ -52,6 +53,8 @@
 @UiContext
 public abstract class WindowProviderService extends Service implements WindowProvider {
 
+    private static final String TAG = WindowProviderService.class.getSimpleName();
+
     private final Bundle mOptions;
     private final WindowTokenClient mWindowToken = new WindowTokenClient();
     private final WindowContextController mController = new WindowContextController(mWindowToken);
@@ -194,8 +197,16 @@
     public final Context createServiceBaseContext(ActivityThread mainThread,
             LoadedApk packageInfo) {
         final Context context = super.createServiceBaseContext(mainThread, packageInfo);
-        final Display display = context.getSystemService(DisplayManager.class)
-                .getDisplay(getInitialDisplayId());
+        final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+        final int initialDisplayId = getInitialDisplayId();
+        Display display = displayManager.getDisplay(initialDisplayId);
+        // Fallback to use the default display if the initial display to start WindowProviderService
+        // is detached.
+        if (display == null) {
+            Log.e(TAG, "Display with id " + initialDisplayId + " not found, falling back to "
+                    + "DEFAULT_DISPLAY");
+            display = displayManager.getDisplay(DEFAULT_DISPLAY);
+        }
         return context.createTokenContext(mWindowToken, display);
     }
 
diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
index 94c230b..2c49303 100644
--- a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
+++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
@@ -17,7 +17,7 @@
 package com.android.internal.accessibility.common;
 
 /**
- * Collection of common constants for accessibility shortcut.
+ * Collection of common constants for accessibility magnification.
  */
 public final class MagnificationConstants {
     private MagnificationConstants() {}
@@ -27,4 +27,10 @@
      * the min value, there will be no obvious magnification effect.
      */
     public static final float PERSISTED_SCALE_MIN_VALUE = 1.3f;
+
+    /** Minimum supported value for magnification scale. */
+    public static final float SCALE_MIN_VALUE = 1.0f;
+
+    /** Maximum supported value for magnification scale. */
+    public static final float SCALE_MAX_VALUE = 8.0f;
 }
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index 57cc38c..4261a0f 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -59,6 +59,8 @@
     public static final int INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS = 6;
     /** value for INVOCATION_TYPE_KEY: press on physcial assistant button */
     public static final int INVOCATION_TYPE_ASSIST_BUTTON = 7;
+    /** value for INVOCATION_TYPE_KEY: long press on nav handle */
+    public static final int INVOCATION_TYPE_NAV_HANDLE_LONG_PRESS = 8;
 
     private final Context mContext;
     private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
@@ -232,6 +234,23 @@
     }
 
     /**
+     * Allows subscription to {@link android.service.voice.VisualQueryDetectionService} service
+     * status.
+     *
+     * @param listener to receive visual service start/stop events.
+     */
+    public void subscribeVisualQueryRecognitionStatus(IVisualQueryRecognitionStatusListener
+            listener) {
+        try {
+            if (mVoiceInteractionManagerService != null) {
+                mVoiceInteractionManagerService.subscribeVisualQueryRecognitionStatus(listener);
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to register visual query detection start listener", e);
+        }
+    }
+
+    /**
      * Enables visual detection service.
      *
      * @param listener to receive visual attention gained/lost events.
diff --git a/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl b/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl
new file mode 100644
index 0000000..cc49a75
--- /dev/null
+++ b/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package com.android.internal.app;
+
+
+ oneway interface IVisualQueryRecognitionStatusListener {
+    /**
+     * Called when {@link VisualQueryDetectionService#onStartDetection} is scheduled from the system
+     * server via {@link VoiceInteractionManagerService#StartPerceiving}.
+     */
+    void onStartPerceiving();
+
+    /**
+     * Called when {@link VisualQueryDetectionService#onStopDetection} is scheduled from the system
+     * server via {@link VoiceInteractionManagerService#StopPerceiving}.
+     */
+    void onStopPerceiving();
+ }
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 24d5afc..314ed69 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -40,6 +40,7 @@
 import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.app.IVisualQueryRecognitionStatusListener;
 
 interface IVoiceInteractionManagerService {
     void showSession(in Bundle sessionArgs, int flags, String attributionTag);
@@ -325,6 +326,9 @@
     void shutdownHotwordDetectionService();
 
     @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
+    void subscribeVisualQueryRecognitionStatus(in IVisualQueryRecognitionStatusListener listener);
+
+    @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
     void enableVisualQueryDetection(in IVisualQueryDetectionAttentionListener Listener);
 
     @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index 43d263b..b3b0603 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -390,12 +390,17 @@
     public static Set<LocaleInfo> transformImeLanguageTagToLocaleInfo(
             List<InputMethodSubtype> list) {
         Set<LocaleInfo> imeLocales = new HashSet<>();
+        Set<String> languageTagSet = new HashSet<>();
         for (InputMethodSubtype subtype : list) {
-            Locale locale = Locale.forLanguageTag(subtype.getLanguageTag());
-            LocaleInfo cacheInfo  = getLocaleInfo(locale, sLocaleCache);
-            LocaleInfo localeInfo = new LocaleInfo(cacheInfo);
-            localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE;
-            imeLocales.add(localeInfo);
+            String languageTag = subtype.getLanguageTag();
+            if (!languageTagSet.contains(languageTag)) {
+                languageTagSet.add(languageTag);
+                Locale locale = Locale.forLanguageTag(languageTag);
+                LocaleInfo cacheInfo = getLocaleInfo(locale, sLocaleCache);
+                LocaleInfo localeInfo = new LocaleInfo(cacheInfo);
+                localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE;
+                imeLocales.add(localeInfo);
+            }
         }
         return imeLocales;
     }
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 50f393b..ac15f11 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -31,6 +31,7 @@
 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED;
 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB;
 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY;
+import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.PermissionChecker.PID_UNKNOWN;
 import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
@@ -39,6 +40,7 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
 import android.annotation.UiThread;
@@ -67,6 +69,7 @@
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.Insets;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -92,6 +95,7 @@
 import android.view.Window;
 import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.AbsListView;
 import android.widget.AdapterView;
 import android.widget.Button;
@@ -356,6 +360,12 @@
         // flag set, we are now losing it.  That should be a very rare case
         // and we can live with this.
         intent.setFlags(intent.getFlags()&~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+
+        // If FLAG_ACTIVITY_LAUNCH_ADJACENT was set, ResolverActivity was opened in the alternate
+        // side, which means we want to open the target app on the same side as ResolverActivity.
+        if ((intent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
+            intent.setFlags(intent.getFlags() & ~FLAG_ACTIVITY_LAUNCH_ADJACENT);
+        }
         return intent;
     }
 
@@ -481,6 +491,14 @@
             rdl.setOnApplyWindowInsetsListener(this::onApplyWindowInsets);
 
             mResolverDrawerLayout = rdl;
+
+            for (int i = 0, size = mMultiProfilePagerAdapter.getCount(); i < size; i++) {
+                View view = mMultiProfilePagerAdapter.getItem(i).rootView.findViewById(
+                        R.id.resolver_list);
+                if (view != null) {
+                    view.setAccessibilityDelegate(new AppListAccessibilityDelegate(rdl));
+                }
+            }
         }
 
         mProfileView = findViewById(R.id.profile_button);
@@ -2600,4 +2618,41 @@
         }
         return resolveInfo.userHandle;
     }
+
+    /**
+     * An a11y delegate that expands resolver drawer when gesture navigation reaches a partially
+     * invisible target in the list.
+     */
+    private static class AppListAccessibilityDelegate extends View.AccessibilityDelegate {
+        private final ResolverDrawerLayout mDrawer;
+        @Nullable
+        private final View mBottomBar;
+        private final Rect mRect = new Rect();
+
+        private AppListAccessibilityDelegate(ResolverDrawerLayout drawer) {
+            mDrawer = drawer;
+            mBottomBar = mDrawer.findViewById(R.id.button_bar_container);
+        }
+
+        @Override
+        public boolean onRequestSendAccessibilityEvent(@androidx.annotation.NonNull ViewGroup host,
+                @NonNull View child,
+                @NonNull AccessibilityEvent event) {
+            boolean result = super.onRequestSendAccessibilityEvent(host, child, event);
+            if (result && event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
+                    && mDrawer.isCollapsed()) {
+                child.getBoundsOnScreen(mRect);
+                int childTop = mRect.top;
+                int childBottom = mRect.bottom;
+                mDrawer.getBoundsOnScreen(mRect, true);
+                int bottomBarHeight = mBottomBar == null ? 0 : mBottomBar.getHeight();
+                int drawerTop = mRect.top;
+                int drawerBottom = mRect.bottom - bottomBarHeight;
+                if (drawerTop > childTop || childBottom > drawerBottom) {
+                    mDrawer.setCollapsed(false);
+                }
+            }
+            return result;
+        }
+    }
 }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index dd52de4..3aa554a 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -532,6 +532,24 @@
     public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot";
 
     /**
+     * (boolean) Whether to enable the adapter conversion in RemoteViews
+     */
+    public static final String REMOTEVIEWS_ADAPTER_CONVERSION =
+            "CursorControlFeature__remoteviews_adapter_conversion";
+
+    /**
+     * The key name used in app core settings for {@link #REMOTEVIEWS_ADAPTER_CONVERSION}
+     */
+    public static final String KEY_REMOTEVIEWS_ADAPTER_CONVERSION =
+            "systemui__remoteviews_adapter_conversion";
+
+    /**
+     * Default value for whether the adapter conversion is enabled or not. This is set for
+     * RemoteViews and should not be a common practice.
+     */
+    public static final boolean REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT = false;
+
+    /**
      * (boolean) Whether the task manager should show a stop button if the app is allowlisted
      * by the user.
      */
@@ -574,6 +592,11 @@
      */
     public static final String COMBINED_BROADCAST_ENABLED = "combined_broadcast_enabled";
 
+    /**
+     * (boolean) Whether to allow cursor hover states for certain elements.
+     */
+    public static final String CURSOR_HOVER_STATES_ENABLED = "cursor_hover_states_enabled";
+
     private SystemUiDeviceConfigFlags() {
     }
 }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index c9e7600..561b5a6 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -66,25 +66,24 @@
         public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI =
                 releasedFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
 
-        /** Gating the ability for users to dismiss ongoing event notifications */
-        public static final Flag ALLOW_DISMISS_ONGOING =
-                releasedFlag("persist.sysui.notification.ongoing_dismissal");
-
         /** Gating the redaction of OTP notifications on the lockscreen */
         public static final Flag OTP_REDACTION =
                 devFlag("persist.sysui.notification.otp_redaction");
 
-        /** Gating the removal of sorting-notifications-by-interruptiveness. */
-        public static final Flag NO_SORT_BY_INTERRUPTIVENESS =
-                releasedFlag("persist.sysui.notification.no_sort_by_interruptiveness");
-
         /** Gating the logging of DND state change events. */
         public static final Flag LOG_DND_STATE_EVENTS =
                 releasedFlag("persist.sysui.notification.log_dnd_state_events");
 
         /** Gating the holding of WakeLocks until NLSes are told about a new notification. */
         public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION =
-                devFlag("persist.sysui.notification.wake_lock_for_posting_notification");
+                releasedFlag("persist.sysui.notification.wake_lock_for_posting_notification");
+
+        /** Gating storing NotificationRankingUpdate ranking map in shared memory. */
+        public static final Flag RANKING_UPDATE_ASHMEM = devFlag(
+                "persist.sysui.notification.ranking_update_ashmem");
+
+        public static final Flag PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS = devFlag(
+                "persist.sysui.notification.propagate_channel_updates_to_conversations");
     }
 
     //// == End of flags.  Everything below this line is the implementation. == ////
diff --git a/core/java/com/android/internal/flags/CoreFlags.java b/core/java/com/android/internal/flags/CoreFlags.java
new file mode 100644
index 0000000..f177ef8
--- /dev/null
+++ b/core/java/com/android/internal/flags/CoreFlags.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.flags;
+
+import android.flags.BooleanFlag;
+import android.flags.DynamicBooleanFlag;
+import android.flags.FeatureFlags;
+import android.flags.FusedOffFlag;
+import android.flags.FusedOnFlag;
+import android.flags.SyncableFlag;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Flags defined here are can be read by code in core.
+ *
+ * Flags not defined here will throw a security exception if third-party processes attempts to read
+ * them.
+ *
+ * DO NOT define a flag here unless you explicitly intend for that flag to be readable by code that
+ * runs inside a third party process.
+ */
+public abstract class CoreFlags {
+    private static final List<SyncableFlag> sKnownFlags = new ArrayList<>();
+
+    public static BooleanFlag BOOL_FLAG = booleanFlag("core", "bool_flag", false);
+    public static FusedOffFlag OFF_FLAG = fusedOffFlag("core", "off_flag");
+    public static FusedOnFlag ON_FLAG = fusedOnFlag("core", "on_flag");
+    public static DynamicBooleanFlag DYN_FLAG = dynamicBooleanFlag("core", "dyn_flag", true);
+
+    /** Returns true if the passed in flag matches a flag in this class. */
+    public static boolean isCoreFlag(SyncableFlag flag) {
+        for (SyncableFlag knownFlag : sKnownFlags) {
+            if (knownFlag.getName().equals(flag.getName())
+                    && knownFlag.getNamespace().equals(flag.getNamespace())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static List<SyncableFlag> getCoreFlags() {
+        return sKnownFlags;
+    }
+
+    private static BooleanFlag booleanFlag(String namespace, String name, boolean defaultValue) {
+        BooleanFlag f = FeatureFlags.booleanFlag(namespace, name, defaultValue);
+
+        sKnownFlags.add(new SyncableFlag(namespace, name, Boolean.toString(defaultValue), false));
+
+        return f;
+    }
+
+    private static FusedOffFlag fusedOffFlag(String namespace, String name) {
+        FusedOffFlag f = FeatureFlags.fusedOffFlag(namespace, name);
+
+        sKnownFlags.add(new SyncableFlag(namespace, name, "false", false));
+
+        return f;
+    }
+
+    private static FusedOnFlag fusedOnFlag(String namespace, String name) {
+        FusedOnFlag f = FeatureFlags.fusedOnFlag(namespace, name);
+
+        sKnownFlags.add(new SyncableFlag(namespace, name, "true", false));
+
+        return f;
+    }
+
+    private static DynamicBooleanFlag dynamicBooleanFlag(
+            String namespace, String name, boolean defaultValue) {
+        DynamicBooleanFlag f = FeatureFlags.dynamicBooleanFlag(namespace, name, defaultValue);
+
+        sKnownFlags.add(new SyncableFlag(namespace, name, Boolean.toString(defaultValue), true));
+
+        return f;
+    }
+}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 66e3333..30ebbe2 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -26,6 +26,7 @@
 import android.util.Log;
 import android.view.View;
 import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.GuardedBy;
@@ -253,13 +254,11 @@
     /**
      * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, int, AndroidFuture)}
      *
-     * @param flags additional operating flags
      * @param reason the reason to hide soft input
-     * @see android.view.inputmethod.InputMethodManager#HIDE_IMPLICIT_ONLY
-     * @see android.view.inputmethod.InputMethodManager#HIDE_NOT_ALWAYS
      */
     @AnyThread
-    public void hideMySoftInput(int flags, @SoftInputShowHideReason int reason) {
+    public void hideMySoftInput(@InputMethodManager.HideFlags int flags,
+            @SoftInputShowHideReason int reason) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
             return;
@@ -275,13 +274,9 @@
 
     /**
      * Calls {@link IInputMethodPrivilegedOperations#showMySoftInput(int, AndroidFuture)}
-     *
-     * @param flags additional operating flags
-     * @see android.view.inputmethod.InputMethodManager#SHOW_IMPLICIT
-     * @see android.view.inputmethod.InputMethodManager#SHOW_FORCED
      */
     @AnyThread
-    public void showMySoftInput(int flags) {
+    public void showMySoftInput(@InputMethodManager.ShowFlags int flags) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
             return;
@@ -391,8 +386,12 @@
             @Nullable ImeTracker.Token statsToken) {
         final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
         if (ops == null) {
+            ImeTracker.forLogging().onFailed(statsToken,
+                    ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
             return;
         }
+        ImeTracker.forLogging().onProgress(statsToken,
+                ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
         try {
             ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible, statsToken);
         } catch (RemoteException e) {
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index e530aec..03d7450 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -25,9 +25,11 @@
 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
 import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
@@ -68,6 +70,7 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_FROM_STATUS_BAR;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
@@ -255,11 +258,21 @@
     public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
     public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67;
     public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
-    public static final int CUJ_IME_INSETS_ANIMATION = 69;
     public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70;
     public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71;
+    // 72 - 77 are reserved for b/281564325.
 
-    private static final int LAST_CUJ = CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
+    /**
+     * In some cases when we do not have any end-target, we play a simple slide-down animation.
+     * eg: Open an app from Overview/Task switcher such that there is no home-screen icon.
+     * eg: Exit the app using back gesture.
+     */
+    public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78;
+    public static final int CUJ_SHADE_EXPAND_FROM_STATUS_BAR = 79;
+    public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80;
+    public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81;
+
+    private static final int LAST_CUJ = CUJ_IME_INSETS_HIDE_ANIMATION;
     private static final int NO_STATSD_LOGGING = -1;
 
     // Used to convert CujType to InteractionType enum value for statsd logging.
@@ -337,9 +350,20 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[69] = NO_STATSD_LOGGING; // This is deprecated.
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
+        // 72 - 77 are reserved for b/281564325.
+        CUJ_TO_STATSD_INTERACTION_TYPE[72] = NO_STATSD_LOGGING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[73] = NO_STATSD_LOGGING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[74] = NO_STATSD_LOGGING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[75] = NO_STATSD_LOGGING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[76] = NO_STATSD_LOGGING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[77] = NO_STATSD_LOGGING;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_EXPAND_FROM_STATUS_BAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_FROM_STATUS_BAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
     }
 
     private static class InstanceHolder {
@@ -436,9 +460,12 @@
             CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
             CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
             CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
-            CUJ_IME_INSETS_ANIMATION,
             CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION,
             CUJ_LAUNCHER_OPEN_SEARCH_RESULT,
+            CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK,
+            CUJ_SHADE_EXPAND_FROM_STATUS_BAR,
+            CUJ_IME_INSETS_SHOW_ANIMATION,
+            CUJ_IME_INSETS_HIDE_ANIMATION,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -1044,12 +1071,18 @@
                 return "LAUNCHER_CLOSE_ALL_APPS_SWIPE";
             case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME:
                 return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME";
-            case CUJ_IME_INSETS_ANIMATION:
-                return "IME_INSETS_ANIMATION";
             case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION:
                 return "LOCKSCREEN_CLOCK_MOVE_ANIMATION";
             case CUJ_LAUNCHER_OPEN_SEARCH_RESULT:
                 return "LAUNCHER_OPEN_SEARCH_RESULT";
+            case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK:
+                return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK";
+            case CUJ_SHADE_EXPAND_FROM_STATUS_BAR:
+                return "SHADE_EXPAND_FROM_STATUS_BAR";
+            case CUJ_IME_INSETS_SHOW_ANIMATION:
+                return "IME_INSETS_SHOW_ANIMATION";
+            case CUJ_IME_INSETS_HIDE_ANIMATION:
+                return "IME_INSETS_HIDE_ANIMATION";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index a0e2934..f8a5520 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -58,6 +58,7 @@
         int APP_REGISTERED = 7;
         int SHORT_FGS_TIMEOUT = 8;
         int JOB_SERVICE = 9;
+        int APP_START = 10;
     }
 
     /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
@@ -186,4 +187,10 @@
     public static TimeoutRecord forJobService(String reason) {
         return TimeoutRecord.endingNow(TimeoutKind.JOB_SERVICE, reason);
     }
+
+    /** Record for app startup timeout. */
+    @NonNull
+    public static TimeoutRecord forAppStart(String reason) {
+        return TimeoutRecord.endingNow(TimeoutKind.APP_START, reason);
+    }
 }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 2063542..965277c 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -195,6 +195,12 @@
      */
     public static final int PROFILEABLE = 1 << 24;
 
+    /**
+     * Enable ptrace.  This is enabled on eng, if the app is debuggable, or if
+     * the persist.debug.ptrace.enabled property is set.
+     */
+    public static final int DEBUG_ENABLE_PTRACE = 1 << 25;
+
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
     /** Default external storage should be mounted. */
@@ -1015,18 +1021,36 @@
                           "persist.debug.dalvik.vm.jdwp.enabled").equals("1");
 
     /**
+     * This will enable ptrace by default for all apps. It is OK to cache this property
+     * because we expect to reboot the system whenever this property changes
+     */
+    private static final boolean ENABLE_PTRACE = SystemProperties.get(
+                          "persist.debug.ptrace.enabled").equals("1");
+
+    /**
      * Applies debugger system properties to the zygote arguments.
      *
-     * For eng builds all apps are debuggable. On userdebug and user builds
-     * if persist.debug.dalvik.vm.jdwp.enabled is 1 all apps are
-     * debuggable. Otherwise, the debugger state is specified via the
-     * "--enable-jdwp" flag in the spawn request.
+     * For eng builds all apps are debuggable with JDWP and ptrace.
+     *
+     * On userdebug builds if persist.debug.dalvik.vm.jdwp.enabled
+     * is 1 all apps are debuggable with JDWP and ptrace. Otherwise, the
+     * debugger state is specified via the "--enable-jdwp" flag in the
+     * spawn request.
+     *
+     * On userdebug builds if persist.debug.ptrace.enabled is 1 all
+     * apps are debuggable with ptrace.
      *
      * @param args non-null; zygote spawner args
      */
     static void applyDebuggerSystemProperty(ZygoteArguments args) {
-        if (Build.IS_ENG || ENABLE_JDWP) {
+        if (Build.IS_ENG || (Build.IS_USERDEBUG && ENABLE_JDWP)) {
             args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
+            // Also enable ptrace when JDWP is enabled for consistency with
+            // before persist.debug.ptrace.enabled existed.
+            args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_PTRACE;
+        }
+        if (Build.IS_ENG || (Build.IS_USERDEBUG && ENABLE_PTRACE)) {
+            args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_PTRACE;
         }
     }
 
@@ -1049,7 +1073,8 @@
         int peerUid = peer.getUid();
 
         if (args.mInvokeWith != null && peerUid != 0
-                && (args.mRuntimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) {
+                && (args.mRuntimeFlags
+                    & (Zygote.DEBUG_ENABLE_JDWP | Zygote.DEBUG_ENABLE_PTRACE)) == 0) {
             throw new ZygoteSecurityException("Peer is permitted to specify an "
                 + "explicit invoke-with wrapper command only for debuggable "
                 + "applications.");
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index af1fdd7..bb86801 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -232,7 +232,7 @@
     private boolean mLastHasRightStableInset = false;
     private boolean mLastHasLeftStableInset = false;
     private int mLastWindowFlags = 0;
-    private boolean mLastShouldAlwaysConsumeSystemBars = false;
+    private @InsetsType int mLastForceConsumingTypes = 0;
     private @InsetsType int mLastSuppressScrimTypes = 0;
 
     private int mRootScrollY = 0;
@@ -1111,19 +1111,19 @@
                     : controller.getSystemBarsAppearance();
 
             if (insets != null) {
-                mLastShouldAlwaysConsumeSystemBars = insets.shouldAlwaysConsumeSystemBars();
+                mLastForceConsumingTypes = insets.getForceConsumingTypes();
 
-                final boolean clearsCompatInsets =
-                        clearsCompatInsets(attrs.type, attrs.flags,
-                                getResources().getConfiguration().windowConfiguration
-                                        .getWindowingMode())
-                        && !mLastShouldAlwaysConsumeSystemBars;
+                @InsetsType int compatInsetsTypes =
+                        WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout();
+                if (clearsCompatInsets(attrs.type, attrs.flags,
+                        getResources().getConfiguration().windowConfiguration.getWindowingMode())) {
+                    compatInsetsTypes &= mLastForceConsumingTypes;
+                }
                 final Insets stableBarInsets = insets.getInsetsIgnoringVisibility(
                         WindowInsets.Type.systemBars());
-                final Insets systemInsets = clearsCompatInsets
+                final Insets systemInsets = compatInsetsTypes == 0
                         ? Insets.NONE
-                        : Insets.min(insets.getInsets(WindowInsets.Type.systemBars()
-                                | WindowInsets.Type.displayCutout()), stableBarInsets);
+                        : Insets.min(insets.getInsets(compatInsetsTypes), stableBarInsets);
                 mLastTopInset = systemInsets.top;
                 mLastBottomInset = systemInsets.bottom;
                 mLastRightInset = systemInsets.right;
@@ -1208,7 +1208,8 @@
                         && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
                         && decorFitsSystemWindows
                         && !hideNavigation)
-                || (mLastShouldAlwaysConsumeSystemBars && hideNavigation);
+                || ((mLastForceConsumingTypes & WindowInsets.Type.navigationBars()) != 0
+                        && hideNavigation);
 
         boolean consumingNavBar =
                 ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
@@ -1224,13 +1225,15 @@
         boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0
                 || (attrs.flags & FLAG_FULLSCREEN) != 0
                 || (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0;
-        boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
-                && decorFitsSystemWindows
-                && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
-                && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
-                && mForceWindowDrawsBarBackgrounds
-                && mLastTopInset != 0
-                || (mLastShouldAlwaysConsumeSystemBars && fullscreen);
+        boolean consumingStatusBar =
+                ((sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
+                        && decorFitsSystemWindows
+                        && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
+                        && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
+                        && mForceWindowDrawsBarBackgrounds
+                        && mLastTopInset != 0)
+                || ((mLastForceConsumingTypes & WindowInsets.Type.statusBars()) != 0
+                        && fullscreen);
 
         int consumedTop = consumingStatusBar ? mLastTopInset : 0;
         int consumedRight = consumingNavBar ? mLastRightInset : 0;
@@ -1434,9 +1437,9 @@
     private void updateColorViewInt(final ColorViewState state, int color, int dividerColor,
             int size, boolean verticalBar, boolean seascape, int sideMargin, boolean animate,
             boolean force, @InsetsType int requestedVisibleTypes) {
+        final @InsetsType int type = state.attributes.insetsType;
         state.present = state.attributes.isPresent(
-                (requestedVisibleTypes & state.attributes.insetsType) != 0
-                        || mLastShouldAlwaysConsumeSystemBars,
+                (requestedVisibleTypes & type) != 0 || (mLastForceConsumingTypes & type) != 0,
                 mWindow.getAttributes().flags, force);
         boolean show = state.attributes.isVisible(state.present, color,
                 mWindow.getAttributes().flags, force);
diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
deleted file mode 100644
index a065e2b..0000000
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
- * Copyright (C) 2015 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.policy;
-
-import static android.view.WindowManager.DOCKED_INVALID;
-import static android.view.WindowManager.DOCKED_LEFT;
-import static android.view.WindowManager.DOCKED_RIGHT;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import java.util.ArrayList;
-
-/**
- * Calculates the snap targets and the snap position given a position and a velocity. All positions
- * here are to be interpreted as the left/top edge of the divider rectangle.
- *
- * @hide
- */
-public class DividerSnapAlgorithm {
-
-    private static final int MIN_FLING_VELOCITY_DP_PER_SECOND = 400;
-    private static final int MIN_DISMISS_VELOCITY_DP_PER_SECOND = 600;
-
-    /**
-     * 3 snap targets: left/top has 16:9 ratio (for videos), 1:1, and right/bottom has 16:9 ratio
-     */
-    private static final int SNAP_MODE_16_9 = 0;
-
-    /**
-     * 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio)
-     */
-    private static final int SNAP_FIXED_RATIO = 1;
-
-    /**
-     * 1 snap target: 1:1
-     */
-    private static final int SNAP_ONLY_1_1 = 2;
-
-    /**
-     * 1 snap target: minimized height, (1 - minimized height)
-     */
-    private static final int SNAP_MODE_MINIMIZED = 3;
-
-    private final float mMinFlingVelocityPxPerSecond;
-    private final float mMinDismissVelocityPxPerSecond;
-    private final int mDisplayWidth;
-    private final int mDisplayHeight;
-    private final int mDividerSize;
-    private final ArrayList<SnapTarget> mTargets = new ArrayList<>();
-    private final Rect mInsets = new Rect();
-    private final int mSnapMode;
-    private final boolean mFreeSnapMode;
-    private final int mMinimalSizeResizableTask;
-    private final int mTaskHeightInMinimizedMode;
-    private final float mFixedRatio;
-    private boolean mIsHorizontalDivision;
-
-    /** The first target which is still splitting the screen */
-    private final SnapTarget mFirstSplitTarget;
-
-    /** The last target which is still splitting the screen */
-    private final SnapTarget mLastSplitTarget;
-
-    private final SnapTarget mDismissStartTarget;
-    private final SnapTarget mDismissEndTarget;
-    private final SnapTarget mMiddleTarget;
-
-    public static DividerSnapAlgorithm create(Context ctx, Rect insets) {
-        DisplayInfo displayInfo = new DisplayInfo();
-        ctx.getSystemService(DisplayManager.class).getDisplay(
-                Display.DEFAULT_DISPLAY).getDisplayInfo(displayInfo);
-        int dividerWindowWidth = ctx.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.docked_stack_divider_thickness);
-        int dividerInsets = ctx.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.docked_stack_divider_insets);
-        return new DividerSnapAlgorithm(ctx.getResources(),
-                displayInfo.logicalWidth, displayInfo.logicalHeight,
-                dividerWindowWidth - 2 * dividerInsets,
-                ctx.getApplicationContext().getResources().getConfiguration().orientation
-                        == Configuration.ORIENTATION_PORTRAIT,
-                insets);
-    }
-
-    public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
-            boolean isHorizontalDivision, Rect insets) {
-        this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets,
-                DOCKED_INVALID, false /* minimized */, true /* resizable */);
-    }
-
-    public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
-        boolean isHorizontalDivision, Rect insets, int dockSide) {
-        this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets,
-            dockSide, false /* minimized */, true /* resizable */);
-    }
-
-    public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
-            boolean isHorizontalDivision, Rect insets, int dockSide, boolean isMinimizedMode,
-            boolean isHomeResizable) {
-        mMinFlingVelocityPxPerSecond =
-                MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
-        mMinDismissVelocityPxPerSecond =
-                MIN_DISMISS_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
-        mDividerSize = dividerSize;
-        mDisplayWidth = displayWidth;
-        mDisplayHeight = displayHeight;
-        mIsHorizontalDivision = isHorizontalDivision;
-        mInsets.set(insets);
-        mSnapMode = isMinimizedMode ? SNAP_MODE_MINIMIZED :
-                res.getInteger(com.android.internal.R.integer.config_dockedStackDividerSnapMode);
-        mFreeSnapMode = res.getBoolean(
-                com.android.internal.R.bool.config_dockedStackDividerFreeSnapMode);
-        mFixedRatio = res.getFraction(
-                com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1);
-        mMinimalSizeResizableTask = res.getDimensionPixelSize(
-                com.android.internal.R.dimen.default_minimal_size_resizable_task);
-        mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize(
-                com.android.internal.R.dimen.task_height_of_minimized_mode) : 0;
-        calculateTargets(isHorizontalDivision, dockSide);
-        mFirstSplitTarget = mTargets.get(1);
-        mLastSplitTarget = mTargets.get(mTargets.size() - 2);
-        mDismissStartTarget = mTargets.get(0);
-        mDismissEndTarget = mTargets.get(mTargets.size() - 1);
-        mMiddleTarget = mTargets.get(mTargets.size() / 2);
-        mMiddleTarget.isMiddleTarget = true;
-    }
-
-    /**
-     * @return whether it's feasible to enable split screen in the current configuration, i.e. when
-     *         snapping in the middle both tasks are larger than the minimal task size.
-     */
-    public boolean isSplitScreenFeasible() {
-        int statusBarSize = mInsets.top;
-        int navBarSize = mIsHorizontalDivision ? mInsets.bottom : mInsets.right;
-        int size = mIsHorizontalDivision
-                ? mDisplayHeight
-                : mDisplayWidth;
-        int availableSpace = size - navBarSize - statusBarSize - mDividerSize;
-        return availableSpace / 2 >= mMinimalSizeResizableTask;
-    }
-
-    public SnapTarget calculateSnapTarget(int position, float velocity) {
-        return calculateSnapTarget(position, velocity, true /* hardDismiss */);
-    }
-
-    /**
-     * @param position the top/left position of the divider
-     * @param velocity current dragging velocity
-     * @param hardDismiss if set, make it a bit harder to get reach the dismiss targets
-     */
-    public SnapTarget calculateSnapTarget(int position, float velocity, boolean hardDismiss) {
-        if (position < mFirstSplitTarget.position && velocity < -mMinDismissVelocityPxPerSecond) {
-            return mDismissStartTarget;
-        }
-        if (position > mLastSplitTarget.position && velocity > mMinDismissVelocityPxPerSecond) {
-            return mDismissEndTarget;
-        }
-        if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) {
-            return snap(position, hardDismiss);
-        }
-        if (velocity < 0) {
-            return mFirstSplitTarget;
-        } else {
-            return mLastSplitTarget;
-        }
-    }
-
-    public SnapTarget calculateNonDismissingSnapTarget(int position) {
-        SnapTarget target = snap(position, false /* hardDismiss */);
-        if (target == mDismissStartTarget) {
-            return mFirstSplitTarget;
-        } else if (target == mDismissEndTarget) {
-            return mLastSplitTarget;
-        } else {
-            return target;
-        }
-    }
-
-    public float calculateDismissingFraction(int position) {
-        if (position < mFirstSplitTarget.position) {
-            return 1f - (float) (position - getStartInset())
-                    / (mFirstSplitTarget.position - getStartInset());
-        } else if (position > mLastSplitTarget.position) {
-            return (float) (position - mLastSplitTarget.position)
-                    / (mDismissEndTarget.position - mLastSplitTarget.position - mDividerSize);
-        }
-        return 0f;
-    }
-
-    public SnapTarget getClosestDismissTarget(int position) {
-        if (position < mFirstSplitTarget.position) {
-            return mDismissStartTarget;
-        } else if (position > mLastSplitTarget.position) {
-            return mDismissEndTarget;
-        } else if (position - mDismissStartTarget.position
-                < mDismissEndTarget.position - position) {
-            return mDismissStartTarget;
-        } else {
-            return mDismissEndTarget;
-        }
-    }
-
-    public SnapTarget getFirstSplitTarget() {
-        return mFirstSplitTarget;
-    }
-
-    public SnapTarget getLastSplitTarget() {
-        return mLastSplitTarget;
-    }
-
-    public SnapTarget getDismissStartTarget() {
-        return mDismissStartTarget;
-    }
-
-    public SnapTarget getDismissEndTarget() {
-        return mDismissEndTarget;
-    }
-
-    private int getStartInset() {
-        if (mIsHorizontalDivision) {
-            return mInsets.top;
-        } else {
-            return mInsets.left;
-        }
-    }
-
-    private int getEndInset() {
-        if (mIsHorizontalDivision) {
-            return mInsets.bottom;
-        } else {
-            return mInsets.right;
-        }
-    }
-
-    private boolean shouldApplyFreeSnapMode(int position) {
-        if (!mFreeSnapMode) {
-            return false;
-        }
-        if (!isFirstSplitTargetAvailable() || !isLastSplitTargetAvailable()) {
-            return false;
-        }
-        return mFirstSplitTarget.position < position && position < mLastSplitTarget.position;
-    }
-
-    private SnapTarget snap(int position, boolean hardDismiss) {
-        if (shouldApplyFreeSnapMode(position)) {
-            return new SnapTarget(position, position, SnapTarget.FLAG_NONE);
-        }
-        int minIndex = -1;
-        float minDistance = Float.MAX_VALUE;
-        int size = mTargets.size();
-        for (int i = 0; i < size; i++) {
-            SnapTarget target = mTargets.get(i);
-            float distance = Math.abs(position - target.position);
-            if (hardDismiss) {
-                distance /= target.distanceMultiplier;
-            }
-            if (distance < minDistance) {
-                minIndex = i;
-                minDistance = distance;
-            }
-        }
-        return mTargets.get(minIndex);
-    }
-
-    private void calculateTargets(boolean isHorizontalDivision, int dockedSide) {
-        mTargets.clear();
-        int dividerMax = isHorizontalDivision
-                ? mDisplayHeight
-                : mDisplayWidth;
-        int startPos = -mDividerSize;
-        if (dockedSide == DOCKED_RIGHT) {
-            startPos += mInsets.left;
-        }
-        mTargets.add(new SnapTarget(startPos, startPos, SnapTarget.FLAG_DISMISS_START,
-                0.35f));
-        switch (mSnapMode) {
-            case SNAP_MODE_16_9:
-                addRatio16_9Targets(isHorizontalDivision, dividerMax);
-                break;
-            case SNAP_FIXED_RATIO:
-                addFixedDivisionTargets(isHorizontalDivision, dividerMax);
-                break;
-            case SNAP_ONLY_1_1:
-                addMiddleTarget(isHorizontalDivision);
-                break;
-            case SNAP_MODE_MINIMIZED:
-                addMinimizedTarget(isHorizontalDivision, dockedSide);
-                break;
-        }
-        mTargets.add(new SnapTarget(dividerMax, dividerMax, SnapTarget.FLAG_DISMISS_END, 0.35f));
-    }
-
-    private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition,
-            int bottomPosition, int dividerMax) {
-        maybeAddTarget(topPosition, topPosition - getStartInset());
-        addMiddleTarget(isHorizontalDivision);
-        maybeAddTarget(bottomPosition,
-                dividerMax - getEndInset() - (bottomPosition + mDividerSize));
-    }
-
-    private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) {
-        int start = isHorizontalDivision ? mInsets.top : mInsets.left;
-        int end = isHorizontalDivision
-                ? mDisplayHeight - mInsets.bottom
-                : mDisplayWidth - mInsets.right;
-        int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2;
-        int topPosition = start + size;
-        int bottomPosition = end - size - mDividerSize;
-        addNonDismissingTargets(isHorizontalDivision, topPosition, bottomPosition, dividerMax);
-    }
-
-    private void addRatio16_9Targets(boolean isHorizontalDivision, int dividerMax) {
-        int start = isHorizontalDivision ? mInsets.top : mInsets.left;
-        int end = isHorizontalDivision
-                ? mDisplayHeight - mInsets.bottom
-                : mDisplayWidth - mInsets.right;
-        int startOther = isHorizontalDivision ? mInsets.left : mInsets.top;
-        int endOther = isHorizontalDivision
-                ? mDisplayWidth - mInsets.right
-                : mDisplayHeight - mInsets.bottom;
-        float size = 9.0f / 16.0f * (endOther - startOther);
-        int sizeInt = (int) Math.floor(size);
-        int topPosition = start + sizeInt;
-        int bottomPosition = end - sizeInt - mDividerSize;
-        addNonDismissingTargets(isHorizontalDivision, topPosition, bottomPosition, dividerMax);
-    }
-
-    /**
-     * Adds a target at {@param position} but only if the area with size of {@param smallerSize}
-     * meets the minimal size requirement.
-     */
-    private void maybeAddTarget(int position, int smallerSize) {
-        if (smallerSize >= mMinimalSizeResizableTask) {
-            mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
-        }
-    }
-
-    private void addMiddleTarget(boolean isHorizontalDivision) {
-        int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
-                mInsets, mDisplayWidth, mDisplayHeight, mDividerSize);
-        mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
-    }
-
-    private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) {
-        // In portrait offset the position by the statusbar height, in landscape add the statusbar
-        // height as well to match portrait offset
-        int position = mTaskHeightInMinimizedMode + mInsets.top;
-        if (!isHorizontalDivision) {
-            if (dockedSide == DOCKED_LEFT) {
-                position += mInsets.left;
-            } else if (dockedSide == DOCKED_RIGHT) {
-                position = mDisplayWidth - position - mInsets.right - mDividerSize;
-            }
-        }
-        mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
-    }
-
-    public SnapTarget getMiddleTarget() {
-        return mMiddleTarget;
-    }
-
-    public SnapTarget getNextTarget(SnapTarget snapTarget) {
-        int index = mTargets.indexOf(snapTarget);
-        if (index != -1 && index < mTargets.size() - 1) {
-            return mTargets.get(index + 1);
-        }
-        return snapTarget;
-    }
-
-    public SnapTarget getPreviousTarget(SnapTarget snapTarget) {
-        int index = mTargets.indexOf(snapTarget);
-        if (index != -1 && index > 0) {
-            return mTargets.get(index - 1);
-        }
-        return snapTarget;
-    }
-
-    /**
-     * @return whether or not there are more than 1 split targets that do not include the two
-     * dismiss targets, used in deciding to display the middle target for accessibility
-     */
-    public boolean showMiddleSplitTargetForAccessibility() {
-        return (mTargets.size() - 2) > 1;
-    }
-
-    public boolean isFirstSplitTargetAvailable() {
-        return mFirstSplitTarget != mMiddleTarget;
-    }
-
-    public boolean isLastSplitTargetAvailable() {
-        return mLastSplitTarget != mMiddleTarget;
-    }
-
-    /**
-     * Cycles through all non-dismiss targets with a stepping of {@param increment}. It moves left
-     * if {@param increment} is negative and moves right otherwise.
-     */
-    public SnapTarget cycleNonDismissTarget(SnapTarget snapTarget, int increment) {
-        int index = mTargets.indexOf(snapTarget);
-        if (index != -1) {
-            SnapTarget newTarget = mTargets.get((index + mTargets.size() + increment)
-                    % mTargets.size());
-            if (newTarget == mDismissStartTarget) {
-                return mLastSplitTarget;
-            } else if (newTarget == mDismissEndTarget) {
-                return mFirstSplitTarget;
-            } else {
-                return newTarget;
-            }
-        }
-        return snapTarget;
-    }
-
-    /**
-     * Represents a snap target for the divider.
-     */
-    public static class SnapTarget {
-        public static final int FLAG_NONE = 0;
-
-        /** If the divider reaches this value, the left/top task should be dismissed. */
-        public static final int FLAG_DISMISS_START = 1;
-
-        /** If the divider reaches this value, the right/bottom task should be dismissed */
-        public static final int FLAG_DISMISS_END = 2;
-
-        /** Position of this snap target. The right/bottom edge of the top/left task snaps here. */
-        public final int position;
-
-        /**
-         * Like {@link #position}, but used to calculate the task bounds which might be different
-         * from the stack bounds.
-         */
-        public final int taskPosition;
-
-        public final int flag;
-
-        public boolean isMiddleTarget;
-
-        /**
-         * Multiplier used to calculate distance to snap position. The lower this value, the harder
-         * it's to snap on this target
-         */
-        private final float distanceMultiplier;
-
-        public SnapTarget(int position, int taskPosition, int flag) {
-            this(position, taskPosition, flag, 1f);
-        }
-
-        public SnapTarget(int position, int taskPosition, int flag, float distanceMultiplier) {
-            this.position = position;
-            this.taskPosition = taskPosition;
-            this.flag = flag;
-            this.distanceMultiplier = distanceMultiplier;
-        }
-    }
-}
diff --git a/core/java/com/android/internal/policy/DockedDividerUtils.java b/core/java/com/android/internal/policy/DockedDividerUtils.java
deleted file mode 100644
index b61b9de..0000000
--- a/core/java/com/android/internal/policy/DockedDividerUtils.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2015 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.policy;
-
-import static android.view.WindowManager.DOCKED_BOTTOM;
-import static android.view.WindowManager.DOCKED_INVALID;
-import static android.view.WindowManager.DOCKED_LEFT;
-import static android.view.WindowManager.DOCKED_RIGHT;
-import static android.view.WindowManager.DOCKED_TOP;
-
-import android.content.res.Resources;
-import android.graphics.Rect;
-
-/**
- * Utility functions for docked stack divider used by both window manager and System UI.
- *
- * @hide
- */
-public class DockedDividerUtils {
-
-    public static void calculateBoundsForPosition(int position, int dockSide, Rect outRect,
-            int displayWidth, int displayHeight, int dividerSize) {
-        outRect.set(0, 0, displayWidth, displayHeight);
-        switch (dockSide) {
-            case DOCKED_LEFT:
-                outRect.right = position;
-                break;
-            case DOCKED_TOP:
-                outRect.bottom = position;
-                break;
-            case DOCKED_RIGHT:
-                outRect.left = position + dividerSize;
-                break;
-            case DOCKED_BOTTOM:
-                outRect.top = position + dividerSize;
-                break;
-        }
-        sanitizeStackBounds(outRect, dockSide == DOCKED_LEFT || dockSide == DOCKED_TOP);
-    }
-
-    /**
-     * Makes sure that the bounds are always valid, i. e. they are at least one pixel high and wide.
-     *
-     * @param bounds The bounds to sanitize.
-     * @param topLeft Pass true if the bounds are at the top/left of the screen, false if they are
-     *                at the bottom/right. This is used to determine in which direction to extend
-     *                the bounds.
-     */
-    public static void sanitizeStackBounds(Rect bounds, boolean topLeft) {
-
-        // If the bounds are either on the top or left of the screen, rather move it further to the
-        // left/top to make it more offscreen. If they are on the bottom or right, push them off the
-        // screen by moving it even more to the bottom/right.
-        if (topLeft) {
-            if (bounds.left >= bounds.right) {
-                bounds.left = bounds.right - 1;
-            }
-            if (bounds.top >= bounds.bottom) {
-                bounds.top = bounds.bottom - 1;
-            }
-        } else {
-            if (bounds.right <= bounds.left) {
-                bounds.right = bounds.left + 1;
-            }
-            if (bounds.bottom <= bounds.top) {
-                bounds.bottom = bounds.top + 1;
-            }
-        }
-    }
-
-    public static int calculatePositionForBounds(Rect bounds, int dockSide, int dividerSize) {
-        switch (dockSide) {
-            case DOCKED_LEFT:
-                return bounds.right;
-            case DOCKED_TOP:
-                return bounds.bottom;
-            case DOCKED_RIGHT:
-                return bounds.left - dividerSize;
-            case DOCKED_BOTTOM:
-                return bounds.top - dividerSize;
-            default:
-                return 0;
-        }
-    }
-
-    public static int calculateMiddlePosition(boolean isHorizontalDivision, Rect insets,
-            int displayWidth, int displayHeight, int dividerSize) {
-        int start = isHorizontalDivision ? insets.top : insets.left;
-        int end = isHorizontalDivision
-                ? displayHeight - insets.bottom
-                : displayWidth - insets.right;
-        return start + (end - start) / 2 - dividerSize / 2;
-    }
-
-    public static int invertDockSide(int dockSide) {
-        switch (dockSide) {
-            case DOCKED_LEFT:
-                return DOCKED_RIGHT;
-            case DOCKED_TOP:
-                return DOCKED_BOTTOM;
-            case DOCKED_RIGHT:
-                return DOCKED_LEFT;
-            case DOCKED_BOTTOM:
-                return DOCKED_TOP;
-            default:
-                return DOCKED_INVALID;
-        }
-    }
-
-    /** Returns the inset distance from the divider window edge to the dividerview. */
-    public static int getDividerInsets(Resources res) {
-        return res.getDimensionPixelSize(com.android.internal.R.dimen.docked_stack_divider_insets);
-    }
-
-    /** Returns the size of the divider */
-    public static int getDividerSize(Resources res, int dividerInsets) {
-        final int windowWidth = res.getDimensionPixelSize(
-                com.android.internal.R.dimen.docked_stack_divider_thickness);
-        return windowWidth - 2 * dividerInsets;
-    }
-
-    /** Returns the docked-stack side */
-    public static int getDockSide(int displayWidth, int displayHeight) {
-        return displayWidth > displayHeight ? DOCKED_LEFT : DOCKED_TOP;
-    }
-}
diff --git a/core/java/com/android/internal/protolog/common/ProtoLog.java b/core/java/com/android/internal/protolog/common/ProtoLog.java
index 93765cd..8870096 100644
--- a/core/java/com/android/internal/protolog/common/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLog.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.protolog.common;
 
+import android.util.Log;
+
 /**
  * ProtoLog API - exposes static logging methods. Usage of this API is similar
  * to {@code android.utils.Log} class. Instead of plain text log messages each call consists of
@@ -53,6 +55,9 @@
             throw new UnsupportedOperationException(
                     "ProtoLog calls MUST be processed with ProtoLogTool");
         }
+        if (group.isLogToLogcat()) {
+            Log.d(group.getTag(), String.format(messageString, args));
+        }
     }
 
     /**
@@ -68,6 +73,9 @@
             throw new UnsupportedOperationException(
                     "ProtoLog calls MUST be processed with ProtoLogTool");
         }
+        if (group.isLogToLogcat()) {
+            Log.v(group.getTag(), String.format(messageString, args));
+        }
     }
 
     /**
@@ -83,6 +91,9 @@
             throw new UnsupportedOperationException(
                     "ProtoLog calls MUST be processed with ProtoLogTool");
         }
+        if (group.isLogToLogcat()) {
+            Log.i(group.getTag(), String.format(messageString, args));
+        }
     }
 
     /**
@@ -98,6 +109,9 @@
             throw new UnsupportedOperationException(
                     "ProtoLog calls MUST be processed with ProtoLogTool");
         }
+        if (group.isLogToLogcat()) {
+            Log.w(group.getTag(), String.format(messageString, args));
+        }
     }
 
     /**
@@ -113,6 +127,9 @@
             throw new UnsupportedOperationException(
                     "ProtoLog calls MUST be processed with ProtoLogTool");
         }
+        if (group.isLogToLogcat()) {
+            Log.e(group.getTag(), String.format(messageString, args));
+        }
     }
 
     /**
@@ -128,5 +145,8 @@
             throw new UnsupportedOperationException(
                     "ProtoLog calls MUST be processed with ProtoLogTool");
         }
+        if (group.isLogToLogcat()) {
+            Log.wtf(group.getTag(), String.format(messageString, args));
+        }
     }
 }
diff --git a/core/java/com/android/internal/statusbar/IAppClipsService.aidl b/core/java/com/android/internal/statusbar/IAppClipsService.aidl
index 013d0d3..d6ab8bc 100644
--- a/core/java/com/android/internal/statusbar/IAppClipsService.aidl
+++ b/core/java/com/android/internal/statusbar/IAppClipsService.aidl
@@ -23,4 +23,6 @@
  */
 interface IAppClipsService {
     boolean canLaunchCaptureContentActivityForNote(in int taskId);
-}
\ No newline at end of file
+
+    int canLaunchCaptureContentActivityForNoteInternal(in int taskId);
+}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index d2564fb..c6f5086 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -63,6 +63,19 @@
     void cancelPreloadRecentApps();
     void showScreenPinningRequest(int taskId);
 
+    /**
+     * Notify system UI the immersive prompt should be dismissed as confirmed, and the confirmed
+     * status should be saved without user clicking on the button. This could happen when a user
+     * swipe on the edge with the confirmation prompt showing.
+     */
+    void confirmImmersivePrompt();
+
+    /**
+     * Notify system UI the immersive mode changed. This shall be removed when client immersive is
+     * enabled.
+     */
+    void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode);
+
     void dismissKeyboardShortcutsMenu();
     void toggleKeyboardShortcutsMenu(int deviceId);
 
diff --git a/core/java/com/android/internal/util/NotificationMessagingUtil.java b/core/java/com/android/internal/util/NotificationMessagingUtil.java
index d3cc0e7..10856b3 100644
--- a/core/java/com/android/internal/util/NotificationMessagingUtil.java
+++ b/core/java/com/android/internal/util/NotificationMessagingUtil.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.util;
 
+import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
@@ -39,10 +40,12 @@
 
     private static final String DEFAULT_SMS_APP_SETTING = Settings.Secure.SMS_DEFAULT_APPLICATION;
     private final Context mContext;
-    private SparseArray<String> mDefaultSmsApp = new SparseArray<>();
+    private final SparseArray<String> mDefaultSmsApp = new SparseArray<>();
+    private final Object mStateLock;
 
-    public NotificationMessagingUtil(Context context) {
+    public NotificationMessagingUtil(Context context, @Nullable Object stateLock) {
         mContext = context;
+        mStateLock = stateLock != null ? stateLock : new Object();
         mContext.getContentResolver().registerContentObserver(
                 Settings.Secure.getUriFor(DEFAULT_SMS_APP_SETTING), false, mSmsContentObserver);
     }
@@ -63,16 +66,20 @@
     private boolean isDefaultMessagingApp(StatusBarNotification sbn) {
         final int userId = sbn.getUserId();
         if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
-        if (mDefaultSmsApp.get(userId) == null) {
-            cacheDefaultSmsApp(userId);
+        synchronized (mStateLock) {
+            if (mDefaultSmsApp.get(userId) == null) {
+                cacheDefaultSmsApp(userId);
+            }
+            return Objects.equals(mDefaultSmsApp.get(userId), sbn.getPackageName());
         }
-        return Objects.equals(mDefaultSmsApp.get(userId), sbn.getPackageName());
     }
 
     private void cacheDefaultSmsApp(int userId) {
-        mDefaultSmsApp.put(userId, Settings.Secure.getStringForUser(
-                mContext.getContentResolver(),
-                Settings.Secure.SMS_DEFAULT_APPLICATION, userId));
+        String smsApp = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+        synchronized (mStateLock) {
+            mDefaultSmsApp.put(userId, smsApp);
+        }
     }
 
     private final ContentObserver mSmsContentObserver = new ContentObserver(
diff --git a/core/java/com/android/internal/util/TraceBuffer.java b/core/java/com/android/internal/util/TraceBuffer.java
index fcc77bd4..c23e902 100644
--- a/core/java/com/android/internal/util/TraceBuffer.java
+++ b/core/java/com/android/internal/util/TraceBuffer.java
@@ -18,6 +18,7 @@
 
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.File;
@@ -39,12 +40,13 @@
  * {@hide}
  */
 public class TraceBuffer<P, S extends P, T extends P> {
-    private final Object mBufferLock = new Object();
-
     private final ProtoProvider<P, S, T> mProtoProvider;
+    @GuardedBy("this")
     private final Queue<T> mBuffer = new ArrayDeque<>();
     private final Consumer mProtoDequeuedCallback;
+    @GuardedBy("this")
     private int mBufferUsedSize;
+    @GuardedBy("this")
     private int mBufferCapacity;
 
     /**
@@ -115,18 +117,18 @@
         resetBuffer();
     }
 
-    public int getAvailableSpace() {
+    public synchronized int getAvailableSpace() {
         return mBufferCapacity - mBufferUsedSize;
     }
 
     /**
      * Returns buffer size.
      */
-    public int size() {
+    public synchronized int size() {
         return mBuffer.size();
     }
 
-    public void setCapacity(int capacity) {
+    public synchronized void setCapacity(int capacity) {
         mBufferCapacity = capacity;
     }
 
@@ -137,22 +139,19 @@
      * @throws IllegalStateException if the element cannot be added because it is larger
      *                               than the buffer size.
      */
-    public void add(T proto) {
+    public synchronized void add(T proto) {
         int protoLength = mProtoProvider.getItemSize(proto);
         if (protoLength > mBufferCapacity) {
             throw new IllegalStateException("Trace object too large for the buffer. Buffer size:"
                     + mBufferCapacity + " Object size: " + protoLength);
         }
-        synchronized (mBufferLock) {
-            discardOldest(protoLength);
-            mBuffer.add(proto);
-            mBufferUsedSize += protoLength;
-            mBufferLock.notify();
-        }
+        discardOldest(protoLength);
+        mBuffer.add(proto);
+        mBufferUsedSize += protoLength;
     }
 
     @VisibleForTesting
-    public boolean contains(byte[] other) {
+    public synchronized boolean contains(byte[] other) {
         return mBuffer.stream()
                 .anyMatch(p -> Arrays.equals(mProtoProvider.getBytes(p), other));
     }
@@ -160,15 +159,13 @@
     /**
      * Writes the trace buffer to disk inside the encapsulatingProto.
      */
-    public void writeTraceToFile(File traceFile, S encapsulatingProto)
+    public synchronized void writeTraceToFile(File traceFile, S encapsulatingProto)
             throws IOException {
-        synchronized (mBufferLock) {
-            traceFile.delete();
-            try (OutputStream os = new FileOutputStream(traceFile)) {
-                traceFile.setReadable(true /* readable */, false /* ownerOnly */);
-                mProtoProvider.write(encapsulatingProto, mBuffer, os);
-                os.flush();
-            }
+        traceFile.delete();
+        try (OutputStream os = new FileOutputStream(traceFile)) {
+            traceFile.setReadable(true /* readable */, false /* ownerOnly */);
+            mProtoProvider.write(encapsulatingProto, mBuffer, os);
+            os.flush();
         }
     }
 
@@ -199,31 +196,27 @@
     /**
      * Removes all elements from the buffer
      */
-    public void resetBuffer() {
-        synchronized (mBufferLock) {
-            if (mProtoDequeuedCallback != null) {
-                for (T item : mBuffer) {
-                    mProtoDequeuedCallback.accept(item);
-                }
+    public synchronized void resetBuffer() {
+        if (mProtoDequeuedCallback != null) {
+            for (T item : mBuffer) {
+                mProtoDequeuedCallback.accept(item);
             }
-            mBuffer.clear();
-            mBufferUsedSize = 0;
         }
+        mBuffer.clear();
+        mBufferUsedSize = 0;
     }
 
     @VisibleForTesting
-    public int getBufferSize() {
+    public synchronized int getBufferSize() {
         return mBufferUsedSize;
     }
 
     /**
      * Returns the buffer status in human-readable form.
      */
-    public String getStatus() {
-        synchronized (mBufferLock) {
-            return "Buffer size: " + mBufferCapacity + " bytes" + "\n"
-                    + "Buffer usage: " + mBufferUsedSize + " bytes" + "\n"
-                    + "Elements in the buffer: " + mBuffer.size();
-        }
+    public synchronized String getStatus() {
+        return "Buffer size: " + mBufferCapacity + " bytes" + "\n"
+                + "Buffer usage: " + mBufferUsedSize + " bytes" + "\n"
+                + "Elements in the buffer: " + mBuffer.size();
     }
 }
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 635adca..5b6b360 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -383,7 +383,11 @@
         updateContentEndPaddings();
     }
 
-    @RemotableViewMethod
+    /**
+     * Set conversation data
+     * @param extras Bundle contains conversation data
+     */
+    @RemotableViewMethod(asyncImpl = "setDataAsync")
     public void setData(Bundle extras) {
         Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
         List<Notification.MessagingStyle.Message> newMessages
@@ -393,8 +397,7 @@
                 = Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
 
         // mUser now set (would be nice to avoid the side effect but WHATEVER)
-        setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, android.app.Person.class));
-
+        final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class);
         // Append remote input history to newMessages (again, side effect is lame but WHATEVS)
         RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
                 extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, android.app.RemoteInputHistoryItem.class);
@@ -402,11 +405,32 @@
 
         boolean showSpinner =
                 extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
-        // bind it, baby
-        bind(newMessages, newHistoricMessages, showSpinner);
-
         int unreadCount = extras.getInt(Notification.EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
-        setUnreadCount(unreadCount);
+
+        // convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
+        // if they exist
+        final List<MessagingMessage> newMessagingMessages =
+                createMessages(newMessages, /* isHistoric= */false,
+                        /* usePrecomputedText= */false);
+        final List<MessagingMessage> newHistoricMessagingMessages =
+                createMessages(newHistoricMessages, /* isHistoric= */true,
+                        /* usePrecomputedText= */false);
+        // bind it, baby
+        bindViews(user, showSpinner, unreadCount,
+                newMessagingMessages,
+                newHistoricMessagingMessages);
+    }
+
+    /**
+     * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
+     * This should be called on a background thread, and returns a Runnable which is then must be
+     * called on the main thread to complete the operation and set text.
+     * @param extras Bundle contains conversation data
+     * @hide
+     */
+    @NonNull
+    public Runnable setDataAsync(Bundle extras) {
+        return () -> setData(extras);
     }
 
     @Override
@@ -436,15 +460,17 @@
         }
     }
 
-    private void bind(List<Notification.MessagingStyle.Message> newMessages,
-            List<Notification.MessagingStyle.Message> newHistoricMessages,
-            boolean showSpinner) {
-        // convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
-        // if they exist
-        List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
-                true /* isHistoric */);
-        List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
 
+    private void bindViews(Person user,
+            boolean showSpinner, int unreadCount, List<MessagingMessage> newMessagingMessages,
+            List<MessagingMessage> newHistoricMessagingMessages) {
+        setUser(user);
+        setUnreadCount(unreadCount);
+        bind(showSpinner, newMessagingMessages, newHistoricMessagingMessages);
+    }
+
+    private void bind(boolean showSpinner, List<MessagingMessage> messages,
+            List<MessagingMessage> historicMessages) {
         // Copy our groups, before they get clobbered
         ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
 
@@ -957,15 +983,17 @@
      * @param newMessages the messages to parse.
      */
     private List<MessagingMessage> createMessages(
-            List<Notification.MessagingStyle.Message> newMessages, boolean historic) {
+            List<Notification.MessagingStyle.Message> newMessages, boolean isHistoric,
+            boolean usePrecomputedText) {
         List<MessagingMessage> result = new ArrayList<>();
         for (int i = 0; i < newMessages.size(); i++) {
             Notification.MessagingStyle.Message m = newMessages.get(i);
             MessagingMessage message = findAndRemoveMatchingMessage(m);
             if (message == null) {
-                message = MessagingMessage.createMessage(this, m, mImageResolver);
+                message = MessagingMessage.createMessage(this, m,
+                        mImageResolver, usePrecomputedText);
             }
-            message.setIsHistoric(historic);
+            message.setIsHistoric(isHistoric);
             result.add(message);
         }
         return result;
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
index c06dab9..918d9c0 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -39,5 +39,6 @@
     boolean hasStableIds();
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     boolean isCreated();
+    RemoteViews.RemoteCollectionItems getRemoteCollectionItems();
 }
 
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 1ac5e1f..0704cb8 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.os.Trace;
 import android.text.BoringLayout;
 import android.text.Layout;
 import android.text.StaticLayout;
@@ -62,12 +63,15 @@
     public ImageFloatingTextView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL_FAST);
+        setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY);
     }
 
     @Override
     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
             Layout.Alignment alignment, boolean shouldEllipsize,
             TextUtils.TruncateAt effectiveEllipsize, boolean useSaved) {
+        Trace.beginSection("ImageFloatingTextView#makeSingleLayout");
         TransformationMethod transformationMethod = getTransformationMethod();
         CharSequence text = getText();
         if (transformationMethod != null) {
@@ -81,8 +85,8 @@
                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
                 .setIncludePad(getIncludeFontPadding())
                 .setUseLineSpacingFromFallbacks(true)
-                .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
-                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL_FAST);
+                .setBreakStrategy(getBreakStrategy())
+                .setHyphenationFrequency(getHyphenationFrequency());
         int maxLines;
         if (mMaxLinesForHeight > 0) {
             maxLines = mMaxLinesForHeight;
@@ -110,7 +114,9 @@
             builder.setIndents(null, margins);
         }
 
-        return builder.build();
+        final StaticLayout result = builder.build();
+        Trace.endSection();
+        return result;
     }
 
     /**
@@ -135,6 +141,7 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Trace.beginSection("ImageFloatingTextView#onMeasure");
         int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mPaddingTop - mPaddingBottom;
         if (getLayout() != null && getLayout().getHeight() != availableHeight) {
             // We've been measured before and the new size is different than before, lets make sure
@@ -161,6 +168,7 @@
                 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
             }
         }
+        Trace.endSection();
     }
 
     @Override
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index e60a5c3..1b1efee 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -47,6 +47,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
@@ -160,9 +161,17 @@
      */
     public static final int VERIFY_FLAG_REQUEST_GK_PW_HANDLE = 1 << 0;
 
+    /**
+     * Flag provided to {@link #verifyCredential(LockscreenCredential, int, int)} . If set, the
+     * method writes the password data to the repair mode file after the credential is verified
+     * successfully.
+     */
+    public static final int VERIFY_FLAG_WRITE_REPAIR_MODE_PW = 1 << 1;
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, value = {
-            VERIFY_FLAG_REQUEST_GK_PW_HANDLE
+            VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
+            VERIFY_FLAG_WRITE_REPAIR_MODE_PW
     })
     public @interface VerifyFlag {}
 
@@ -171,6 +180,11 @@
      */
     public static final int USER_FRP = UserHandle.USER_NULL + 1;
 
+    /**
+     * Special user id for triggering the exiting repair mode verification flow.
+     */
+    public static final int USER_REPAIR_MODE = UserHandle.USER_NULL + 2;
+
     public final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
     public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
     @Deprecated
@@ -201,6 +215,8 @@
     public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle";
     public static final String PASSWORD_HISTORY_DELIMITER = ",";
 
+    private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
+
     /**
      * drives the pin auto confirmation feature availability in code logic.
      */
@@ -389,7 +405,7 @@
 
     @UnsupportedAppUsage
     public void reportFailedPasswordAttempt(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+        if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
             return;
         }
         getDevicePolicyManager().reportFailedPasswordAttempt(userId);
@@ -398,7 +414,7 @@
 
     @UnsupportedAppUsage
     public void reportSuccessfulPasswordAttempt(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+        if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
             return;
         }
         getDevicePolicyManager().reportSuccessfulPasswordAttempt(userId);
@@ -406,21 +422,21 @@
     }
 
     public void reportPasswordLockout(int timeoutMs, int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+        if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
             return;
         }
         getTrustManager().reportUnlockLockout(timeoutMs, userId);
     }
 
     public int getCurrentFailedPasswordAttempts(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+        if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
             return 0;
         }
         return getDevicePolicyManager().getCurrentFailedPasswordAttempts(userId);
     }
 
     public int getMaximumFailedPasswordsForWipe(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
+        if (isSpecialUserId(mContext, userId, /* checkDeviceSupported= */ true)) {
             return 0;
         }
         return getDevicePolicyManager().getMaximumFailedPasswordsForWipe(
@@ -757,6 +773,17 @@
         }
     }
 
+    /** Returns the credential type corresponding to the given PIN or password quality. */
+    public static int pinOrPasswordQualityToCredentialType(int quality) {
+        if (isQualityAlphabeticPassword(quality)) {
+            return CREDENTIAL_TYPE_PASSWORD;
+        }
+        if (isQualityNumericPin(quality)) {
+            return CREDENTIAL_TYPE_PIN;
+        }
+        throw new IllegalArgumentException("Quality is neither Pin nor password: " + quality);
+    }
+
     /**
      * Save a new lockscreen credential.
      *
@@ -991,7 +1018,7 @@
                 }
                 @Override
                 public boolean shouldBypassCache(Integer userHandle) {
-                    return userHandle == USER_FRP;
+                    return isSpecialUserId(userHandle);
                 }
             };
 
@@ -1055,7 +1082,10 @@
      */
     @UnsupportedAppUsage
     public boolean isVisiblePatternEnabled(int userId) {
-        return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, false, userId);
+        // Default to true, since this gets explicitly set to true when a pattern is first set
+        // anyway, which makes true the user-visible default.  The low-level default should be the
+        // same, in order for FRP credential verification to get the same default.
+        return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, true, userId);
     }
 
     /**
@@ -1105,9 +1135,10 @@
     @UnsupportedAppUsage
     public long setLockoutAttemptDeadline(int userId, int timeoutMs) {
         final long deadline = SystemClock.elapsedRealtime() + timeoutMs;
-        if (userId == USER_FRP) {
-            // For secure password storage (that is required for FRP), the underlying storage also
-            // enforces the deadline. Since we cannot store settings for the FRP user, don't.
+        if (isSpecialUserId(userId)) {
+            // For secure password storage (that is required for special users such as FRP), the
+            // underlying storage also enforces the deadline. Since we cannot store settings
+            // for special users, don't.
             return deadline;
         }
         mLockoutDeadlines.put(userId, deadline);
@@ -1847,6 +1878,64 @@
     }
 
     /**
+     * Return {@code true} if repair mode is supported by the device.
+     */
+    public static boolean isRepairModeSupported(Context context) {
+        return context.getResources().getBoolean(
+                com.android.internal.R.bool.config_repairModeSupported);
+    }
+
+    /**
+     * Return {@code true} if repair mode is active on the device.
+     */
+    public static boolean isRepairModeActive(Context context) {
+        return Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.REPAIR_MODE_ACTIVE, /* def= */ 0) > 0;
+    }
+
+    /**
+     * Return {@code true} if repair mode is supported by the device and the user has been granted
+     * admin privileges.
+     */
+    public static boolean canUserEnterRepairMode(Context context, UserInfo info) {
+        return info != null && info.isAdmin() && isRepairModeSupported(context);
+    }
+
+    /**
+     * Return {@code true} if GSI is running on the device.
+     */
+    public static boolean isGsiRunning() {
+        return SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0;
+    }
+
+    /**
+     * Return {@code true} if the given user id is a special user such as {@link #USER_FRP}.
+     */
+    public static boolean isSpecialUserId(int userId) {
+        return isSpecialUserId(/* context= */ null, userId, /* checkDeviceSupported= */ false);
+    }
+
+    /**
+     * Return {@code true} if the given user id is a special user for the verification flow.
+     *
+     * @param checkDeviceSupported {@code true} to check the specified user is supported
+     *                             by the device.
+     */
+    private static boolean isSpecialUserId(@Nullable Context context, int userId,
+            boolean checkDeviceSupported) {
+        switch (userId) {
+            case USER_FRP:
+                if (checkDeviceSupported) return frpCredentialEnabled(context);
+                return true;
+
+            case USER_REPAIR_MODE:
+                if (checkDeviceSupported) return isRepairModeSupported(context);
+                return true;
+        }
+        return false;
+    }
+
+    /**
      * Attempt to rederive the unified work challenge for the specified profile user and unlock the
      * user. If successful, this would allow the user to leave quiet mode automatically without
      * additional user authentication.
diff --git a/core/java/com/android/internal/widget/MessagingImageMessage.java b/core/java/com/android/internal/widget/MessagingImageMessage.java
index 098bce1..c132d6a 100644
--- a/core/java/com/android/internal/widget/MessagingImageMessage.java
+++ b/core/java/com/android/internal/widget/MessagingImageMessage.java
@@ -93,8 +93,9 @@
     }
 
     @Override
-    public boolean setMessage(Notification.MessagingStyle.Message message) {
-        MessagingMessage.super.setMessage(message);
+    public boolean setMessage(Notification.MessagingStyle.Message message,
+            boolean usePrecomputedText) {
+        MessagingMessage.super.setMessage(message, usePrecomputedText);
         Drawable drawable;
         try {
             Uri uri = message.getDataUri();
@@ -114,32 +115,42 @@
         }
         mDrawable = drawable;
         mAspectRatio = ((float) mDrawable.getIntrinsicWidth()) / intrinsicHeight;
-        setImageDrawable(drawable);
-        setContentDescription(message.getText());
+        if (!usePrecomputedText) {
+            finalizeInflate();
+        }
         return true;
     }
 
     static MessagingMessage createMessage(IMessagingLayout layout,
-            Notification.MessagingStyle.Message m, ImageResolver resolver) {
+            Notification.MessagingStyle.Message m, ImageResolver resolver,
+            boolean usePrecomputedText) {
         MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
         MessagingImageMessage createdMessage = sInstancePool.acquire();
         if (createdMessage == null) {
             createdMessage = (MessagingImageMessage) LayoutInflater.from(
                     layout.getContext()).inflate(
-                            R.layout.notification_template_messaging_image_message,
-                            messagingLinearLayout,
-                            false);
+                    R.layout.notification_template_messaging_image_message,
+                    messagingLinearLayout,
+                    false);
             createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
         }
         createdMessage.setImageResolver(resolver);
-        boolean created = createdMessage.setMessage(m);
-        if (!created) {
+        // MessagingImageMessage does not use usePrecomputedText.
+        boolean populated = createdMessage.setMessage(m, /* usePrecomputedText= */false);
+        if (!populated) {
             createdMessage.recycle();
-            return MessagingTextMessage.createMessage(layout, m);
+            return MessagingTextMessage.createMessage(layout, m, usePrecomputedText);
         }
         return createdMessage;
     }
 
+
+    @Override
+    public void finalizeInflate() {
+        setImageDrawable(mDrawable);
+        setContentDescription(getMessage().getText());
+    }
+
     private void setImageResolver(ImageResolver resolver) {
         mImageResolver = resolver;
     }
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 9d142f6..83557cd 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -156,7 +156,11 @@
         mConversationTitle = conversationTitle;
     }
 
-    @RemotableViewMethod
+    /**
+     * Set Messaging data
+     * @param extras Bundle contains messaging data
+     */
+    @RemotableViewMethod(asyncImpl = "setDataAsync")
     public void setData(Bundle extras) {
         Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
         List<Notification.MessagingStyle.Message> newMessages
@@ -168,9 +172,28 @@
         RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
                 extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, android.app.RemoteInputHistoryItem.class);
         addRemoteInputHistoryToMessages(newMessages, history);
+
+        final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class);
         boolean showSpinner =
                 extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
-        bind(newMessages, newHistoricMessages, showSpinner);
+
+        final List<MessagingMessage> historicMessagingMessages = createMessages(newHistoricMessages,
+                /* isHistoric= */true, /* usePrecomputedText= */ false);
+        final List<MessagingMessage> newMessagingMessages =
+                createMessages(newMessages, /* isHistoric= */false, /* usePrecomputedText= */false);
+        bindViews(user, showSpinner, historicMessagingMessages, newMessagingMessages);
+    }
+
+    /**
+     * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
+     * This should be called on a background thread, and returns a Runnable which is then must be
+     * called on the main thread to complete the operation and set text.
+     * @param extras Bundle contains messaging data
+     * @hide
+     */
+    @NonNull
+    public Runnable setDataAsync(Bundle extras) {
+        return () -> setData(extras);
     }
 
     @Override
@@ -195,14 +218,15 @@
         }
     }
 
-    private void bind(List<Notification.MessagingStyle.Message> newMessages,
-            List<Notification.MessagingStyle.Message> newHistoricMessages,
-            boolean showSpinner) {
+    private void bindViews(Person user, boolean showSpinner,
+            List<MessagingMessage> historicMessagingMessages,
+            List<MessagingMessage> newMessagingMessages) {
+        setUser(user);
+        bind(showSpinner, historicMessagingMessages, newMessagingMessages);
+    }
 
-        List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
-                true /* isHistoric */);
-        List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
-
+    private void bind(boolean showSpinner, List<MessagingMessage> historicMessages,
+            List<MessagingMessage> messages) {
         ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
         addMessagesToGroups(historicMessages, messages, showSpinner);
 
@@ -494,15 +518,17 @@
      * @param newMessages the messages to parse.
      */
     private List<MessagingMessage> createMessages(
-            List<Notification.MessagingStyle.Message> newMessages, boolean historic) {
+            List<Notification.MessagingStyle.Message> newMessages, boolean isHistoric,
+            boolean usePrecomputedText) {
         List<MessagingMessage> result = new ArrayList<>();
         for (int i = 0; i < newMessages.size(); i++) {
             Notification.MessagingStyle.Message m = newMessages.get(i);
             MessagingMessage message = findAndRemoveMatchingMessage(m);
             if (message == null) {
-                message = MessagingMessage.createMessage(this, m, mImageResolver);
+                message = MessagingMessage.createMessage(this, m,
+                        mImageResolver, usePrecomputedText);
             }
-            message.setIsHistoric(historic);
+            message.setIsHistoric(isHistoric);
             result.add(message);
         }
         return result;
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
index 5ecd3b8..ad90a63 100644
--- a/core/java/com/android/internal/widget/MessagingMessage.java
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -34,11 +34,12 @@
     String IMAGE_MIME_TYPE_PREFIX = "image/";
 
     static MessagingMessage createMessage(IMessagingLayout layout,
-            Notification.MessagingStyle.Message m, ImageResolver resolver) {
+            Notification.MessagingStyle.Message m, ImageResolver resolver,
+            boolean usePrecomputedText) {
         if (hasImage(m) && !ActivityManager.isLowRamDeviceStatic()) {
-            return MessagingImageMessage.createMessage(layout, m, resolver);
+            return MessagingImageMessage.createMessage(layout, m, resolver, usePrecomputedText);
         } else {
-            return MessagingTextMessage.createMessage(layout, m);
+            return MessagingTextMessage.createMessage(layout, m, usePrecomputedText);
         }
     }
 
@@ -55,9 +56,11 @@
 
     /**
      * Set a message for this view.
+     *
      * @return true if setting the message worked
      */
-    default boolean setMessage(Notification.MessagingStyle.Message message) {
+    default boolean setMessage(Notification.MessagingStyle.Message message,
+            boolean usePrecomputedText) {
         getState().setMessage(message);
         return true;
     }
@@ -151,4 +154,10 @@
     void setVisibility(int visibility);
 
     int getVisibility();
+
+    /**
+     * Finalize inflation of the MessagingMessages, which should be called on Main Thread.
+     * @hide
+     */
+    void finalizeInflate();
 }
diff --git a/core/java/com/android/internal/widget/MessagingTextMessage.java b/core/java/com/android/internal/widget/MessagingTextMessage.java
index 19791db..bd62aad 100644
--- a/core/java/com/android/internal/widget/MessagingTextMessage.java
+++ b/core/java/com/android/internal/widget/MessagingTextMessage.java
@@ -23,7 +23,9 @@
 import android.app.Notification;
 import android.content.Context;
 import android.text.Layout;
+import android.text.PrecomputedText;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.widget.RemoteViews;
 
@@ -35,10 +37,13 @@
 @RemoteViews.RemoteView
 public class MessagingTextMessage extends ImageFloatingTextView implements MessagingMessage {
 
+    private static final String TAG = "MessagingTextMessage";
     private static final MessagingPool<MessagingTextMessage> sInstancePool =
             new MessagingPool<>(20);
     private final MessagingMessageState mState = new MessagingMessageState(this);
 
+    private PrecomputedText mPrecomputedText = null;
+
     public MessagingTextMessage(@NonNull Context context) {
         super(context);
     }
@@ -63,25 +68,32 @@
     }
 
     @Override
-    public boolean setMessage(Notification.MessagingStyle.Message message) {
-        MessagingMessage.super.setMessage(message);
-        setText(message.getText());
+    public boolean setMessage(Notification.MessagingStyle.Message message,
+            boolean usePrecomputedText) {
+        MessagingMessage.super.setMessage(message, usePrecomputedText);
+        if (usePrecomputedText) {
+            mPrecomputedText = PrecomputedText.create(message.getText(), getTextMetricsParams());
+        } else {
+            setText(message.getText());
+            mPrecomputedText = null;
+        }
+
         return true;
     }
 
     static MessagingMessage createMessage(IMessagingLayout layout,
-            Notification.MessagingStyle.Message m) {
+            Notification.MessagingStyle.Message m, boolean usePrecomputedText) {
         MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
         MessagingTextMessage createdMessage = sInstancePool.acquire();
         if (createdMessage == null) {
             createdMessage = (MessagingTextMessage) LayoutInflater.from(
                     layout.getContext()).inflate(
-                            R.layout.notification_template_messaging_text_message,
-                            messagingLinearLayout,
-                            false);
+                    R.layout.notification_template_messaging_text_message,
+                    messagingLinearLayout,
+                    false);
             createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
         }
-        createdMessage.setMessage(m);
+        createdMessage.setMessage(m, usePrecomputedText);
         return createdMessage;
     }
 
@@ -135,4 +147,20 @@
     public void setColor(int color) {
         setTextColor(color);
     }
+
+    @Override
+    public void finalizeInflate() {
+        try {
+            setText(mPrecomputedText != null ? mPrecomputedText
+                    : getState().getMessage().getText());
+        } catch (IllegalArgumentException exception) {
+            Log.wtf(
+                    /* tag = */ TAG,
+                    /* msg = */ "PrecomputedText setText failed for TextView:" + this,
+                    /* tr = */ exception
+            );
+            mPrecomputedText = null;
+            setText(getState().getMessage().getText());
+        }
+    }
 }
diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java
index 07ee9b5..d4dd1e7 100644
--- a/core/java/com/android/internal/widget/NotificationExpandButton.java
+++ b/core/java/com/android/internal/widget/NotificationExpandButton.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
 import android.util.AttributeSet;
 import android.view.RemotableViewMethod;
 import android.view.View;
@@ -42,7 +44,7 @@
 @RemoteViews.RemoteView
 public class NotificationExpandButton extends FrameLayout {
 
-    private View mPillView;
+    private Drawable mPillDrawable;
     private TextView mNumberView;
     private ImageView mIconView;
     private boolean mExpanded;
@@ -73,7 +75,10 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mPillView = findViewById(R.id.expand_button_pill);
+
+        final View pillView = findViewById(R.id.expand_button_pill);
+        final LayerDrawable layeredPill = (LayerDrawable) pillView.getBackground();
+        mPillDrawable = layeredPill.findDrawableByLayerId(R.id.expand_button_pill_colorized_layer);
         mNumberView = findViewById(R.id.expand_button_number);
         mIconView = findViewById(R.id.expand_button_icon);
     }
@@ -156,7 +161,7 @@
     private void updateColors() {
         if (shouldShowNumber()) {
             if (mHighlightPillColor != 0) {
-                mPillView.setBackgroundTintList(ColorStateList.valueOf(mHighlightPillColor));
+                mPillDrawable.setTintList(ColorStateList.valueOf(mHighlightPillColor));
             }
             mIconView.setColorFilter(mHighlightTextColor);
             if (mHighlightTextColor != 0) {
@@ -164,7 +169,7 @@
             }
         } else {
             if (mDefaultPillColor != 0) {
-                mPillView.setBackgroundTintList(ColorStateList.valueOf(mDefaultPillColor));
+                mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor));
             }
             mIconView.setColorFilter(mDefaultTextColor);
             if (mDefaultTextColor != 0) {
diff --git a/core/java/com/android/internal/widget/OWNERS b/core/java/com/android/internal/widget/OWNERS
index d068a3a..e2672f5 100644
--- a/core/java/com/android/internal/widget/OWNERS
+++ b/core/java/com/android/internal/widget/OWNERS
@@ -21,3 +21,6 @@
 per-file ObservableTextView.java = file:/services/core/java/com/android/server/notification/OWNERS
 per-file RemeasuringLinearLayout.java = file:/services/core/java/com/android/server/notification/OWNERS
 per-file ViewClippingUtil.java = file:/services/core/java/com/android/server/notification/OWNERS
+
+# Appwidget related
+per-file *RemoteViews* = file:/services/appwidget/java/com/android/server/appwidget/OWNERS
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index f55d15d..e65b4b6 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -163,6 +163,8 @@
     @UnsupportedAppUsage
     private boolean mPrintCoords = true;
 
+    private float mDensity;
+
     public PointerLocationView(Context c) {
         super(c);
         setFocusableInTouchMode(true);
@@ -365,11 +367,8 @@
                 drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor,
                         ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint);
 
-                // Draw the orientation arrow.
-                float arrowSize = ps.mCoords.toolMajor * 0.7f;
-                if (arrowSize < 20) {
-                    arrowSize = 20;
-                }
+                // Draw the orientation arrow, and ensure it has a minimum size of 24dp.
+                final float arrowSize = Math.max(ps.mCoords.toolMajor * 0.7f, 24 * mDensity);
                 mPaint.setARGB(255, pressureLevel, 255, 0);
                 float orientationVectorX = (float) (Math.sin(ps.mCoords.orientation)
                         * arrowSize);
@@ -398,7 +397,7 @@
                 canvas.drawCircle(
                         ps.mCoords.x + orientationVectorX * tiltScale,
                         ps.mCoords.y + orientationVectorY * tiltScale,
-                        3.0f, mPaint);
+                        3.0f * mDensity, mPaint);
 
                 // Draw the current bounding box
                 if (ps.mHasBoundingBox) {
@@ -1003,10 +1002,10 @@
 
     // Compute size by display density.
     private void configureDensityDependentFactors() {
-        final float density = getResources().getDisplayMetrics().density;
-        mTextPaint.setTextSize(10 * density);
-        mPaint.setStrokeWidth(1 * density);
-        mCurrentPointPaint.setStrokeWidth(1 * density);
-        mPathPaint.setStrokeWidth(1 * density);
+        mDensity = getResources().getDisplayMetrics().density;
+        mTextPaint.setTextSize(10 * mDensity);
+        mPaint.setStrokeWidth(1 * mDensity);
+        mCurrentPointPaint.setStrokeWidth(1 * mDensity);
+        mPathPaint.setStrokeWidth(1 * mDensity);
     }
 }
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
index f7af67b..e9449eb 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
@@ -21,7 +21,6 @@
 import android.graphics.Rect;
 import android.view.MenuItem;
 import android.view.View;
-import android.view.selectiontoolbar.SelectionToolbarManager;
 import android.widget.PopupWindow;
 
 import java.util.List;
@@ -89,14 +88,10 @@
             @Nullable PopupWindow.OnDismissListener onDismiss);
 
     /**
-     * Returns {@link RemoteFloatingToolbarPopup} implementation if the system selection toolbar
-     * enabled, otherwise returns {@link LocalFloatingToolbarPopup} implementation.
+     * Returns {@link LocalFloatingToolbarPopup} implementation.
      */
     static FloatingToolbarPopup createInstance(Context context, View parent) {
-        boolean enabled = SelectionToolbarManager.isRemoteSelectionToolbarEnabled(context);
-        return enabled
-                ? new RemoteFloatingToolbarPopup(context, parent)
-                : new LocalFloatingToolbarPopup(context, parent);
+        return new LocalFloatingToolbarPopup(context, parent);
     }
 
 }
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/OWNERS b/core/java/com/android/internal/widget/floatingtoolbar/OWNERS
deleted file mode 100644
index ed9425c..0000000
--- a/core/java/com/android/internal/widget/floatingtoolbar/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /core/java/android/view/selectiontoolbar/OWNERS
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
deleted file mode 100644
index 8787c39..0000000
--- a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
+++ /dev/null
@@ -1,572 +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.internal.widget.floatingtoolbar;
-
-import static android.view.selectiontoolbar.SelectionToolbarManager.NO_TOOLBAR_ID;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UiThread;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.MenuItem;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.SelectionToolbarManager;
-import android.view.selectiontoolbar.ShowInfo;
-import android.view.selectiontoolbar.ToolbarMenuItem;
-import android.view.selectiontoolbar.WidgetInfo;
-import android.widget.LinearLayout;
-import android.widget.PopupWindow;
-
-import com.android.internal.R;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * A popup window used by the floating toolbar to render menu items in the remote system process.
- *
- * It holds 2 panels (i.e. main panel and overflow panel) and an overflow button
- * to transition between panels.
- */
-public final class RemoteFloatingToolbarPopup implements FloatingToolbarPopup {
-
-    private static final boolean DEBUG =
-            Log.isLoggable(FloatingToolbar.FLOATING_TOOLBAR_TAG, Log.VERBOSE);
-
-    private static final int TOOLBAR_STATE_SHOWN = 1;
-    private static final int TOOLBAR_STATE_HIDDEN = 2;
-    private static final int TOOLBAR_STATE_DISMISSED = 3;
-
-    @IntDef(prefix = {"TOOLBAR_STATE_"}, value = {
-            TOOLBAR_STATE_SHOWN,
-            TOOLBAR_STATE_HIDDEN,
-            TOOLBAR_STATE_DISMISSED
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ToolbarState {
-    }
-
-    @NonNull
-    private final SelectionToolbarManager mSelectionToolbarManager;
-    // Parent for the popup window.
-    @NonNull
-    private final View mParent;
-    // A popup window used for showing menu items rendered by the remote system process
-    @NonNull
-    private final PopupWindow mPopupWindow;
-    // The callback to handle remote rendered selection toolbar.
-    @NonNull
-    private final SelectionToolbarCallbackImpl mSelectionToolbarCallback;
-
-    // tracks this popup state.
-    private @ToolbarState int mState;
-
-    // The token of the current showing floating toolbar.
-    private long mFloatingToolbarToken;
-    private final Rect mPreviousContentRect = new Rect();
-    private List<MenuItem> mMenuItems;
-    private MenuItem.OnMenuItemClickListener mMenuItemClickListener;
-    private int mSuggestedWidth;
-    private final Rect mScreenViewPort = new Rect();
-    private boolean mWidthChanged = true;
-    private final boolean mIsLightTheme;
-
-    private final int[] mCoordsOnScreen = new int[2];
-    private final int[] mCoordsOnWindow = new int[2];
-
-    public RemoteFloatingToolbarPopup(Context context, View parent) {
-        mParent = Objects.requireNonNull(parent);
-        mPopupWindow = createPopupWindow(context);
-        mSelectionToolbarManager = context.getSystemService(SelectionToolbarManager.class);
-        mSelectionToolbarCallback = new SelectionToolbarCallbackImpl(this);
-        mIsLightTheme = isLightTheme(context);
-        mFloatingToolbarToken = NO_TOOLBAR_ID;
-    }
-
-    private boolean isLightTheme(Context context) {
-        TypedArray a = context.obtainStyledAttributes(new int[]{R.attr.isLightTheme});
-        boolean isLightTheme = a.getBoolean(0, true);
-        a.recycle();
-        return isLightTheme;
-    }
-
-    @UiThread
-    @Override
-    public void show(List<MenuItem> menuItems,
-            MenuItem.OnMenuItemClickListener menuItemClickListener, Rect contentRect) {
-        Objects.requireNonNull(menuItems);
-        Objects.requireNonNull(menuItemClickListener);
-        if (isShowing() && Objects.equals(menuItems, mMenuItems)
-                && Objects.equals(contentRect, mPreviousContentRect)) {
-            if (DEBUG) {
-                Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "Ignore duplicate show() for the same content.");
-            }
-            return;
-        }
-
-        boolean isLayoutRequired = mMenuItems == null
-                || !MenuItemRepr.reprEquals(menuItems, mMenuItems)
-                || mWidthChanged;
-        if (isLayoutRequired) {
-            mSelectionToolbarManager.dismissToolbar(mFloatingToolbarToken);
-            doDismissPopupWindow();
-        }
-        mMenuItemClickListener = menuItemClickListener;
-        mMenuItems = menuItems;
-
-        mParent.getWindowVisibleDisplayFrame(mScreenViewPort);
-        final int suggestWidth = mSuggestedWidth > 0
-                ? mSuggestedWidth
-                : mParent.getResources().getDimensionPixelSize(
-                        R.dimen.floating_toolbar_preferred_width);
-        final ShowInfo showInfo = new ShowInfo(
-                mFloatingToolbarToken, isLayoutRequired,
-                getToolbarMenuItems(mMenuItems),
-                contentRect,
-                suggestWidth,
-                mScreenViewPort,
-                mParent.getViewRootImpl().getInputToken(), mIsLightTheme);
-        if (DEBUG) {
-            Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                    "RemoteFloatingToolbarPopup.show() for " + showInfo);
-        }
-        mSelectionToolbarManager.showToolbar(showInfo, mSelectionToolbarCallback);
-        mPreviousContentRect.set(contentRect);
-    }
-
-    @UiThread
-    @Override
-    public void dismiss() {
-        if (mState == TOOLBAR_STATE_DISMISSED) {
-            Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                    "The floating toolbar already dismissed.");
-            return;
-        }
-        if (DEBUG) {
-            Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                    "RemoteFloatingToolbarPopup.dismiss().");
-        }
-        mSelectionToolbarManager.dismissToolbar(mFloatingToolbarToken);
-        doDismissPopupWindow();
-    }
-
-    @UiThread
-    @Override
-    public void hide() {
-        if (mState == TOOLBAR_STATE_DISMISSED || mState == TOOLBAR_STATE_HIDDEN) {
-            if (DEBUG) {
-                Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "The floating toolbar already dismissed or hidden.");
-            }
-            return;
-        }
-        if (DEBUG) {
-            Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                    "RemoteFloatingToolbarPopup.hide().");
-        }
-        mSelectionToolbarManager.hideToolbar(mFloatingToolbarToken);
-        mState = TOOLBAR_STATE_HIDDEN;
-        mPopupWindow.dismiss();
-    }
-
-    @UiThread
-    @Override
-    public void setSuggestedWidth(int suggestedWidth) {
-        int difference = Math.abs(suggestedWidth - mSuggestedWidth);
-        mWidthChanged = difference > (mSuggestedWidth * 0.2);
-        mSuggestedWidth = suggestedWidth;
-    }
-
-    @Override
-    public void setWidthChanged(boolean widthChanged) {
-        mWidthChanged = widthChanged;
-    }
-
-    @UiThread
-    @Override
-    public boolean isHidden() {
-        return mState == TOOLBAR_STATE_HIDDEN;
-    }
-
-    @UiThread
-    @Override
-    public boolean isShowing() {
-        return mState == TOOLBAR_STATE_SHOWN;
-    }
-
-    @UiThread
-    @Override
-    public boolean setOutsideTouchable(boolean outsideTouchable,
-            @Nullable PopupWindow.OnDismissListener onDismiss) {
-        if (mState == TOOLBAR_STATE_DISMISSED) {
-            return false;
-        }
-        boolean ret = false;
-        if (mPopupWindow.isOutsideTouchable() ^ outsideTouchable) {
-            mPopupWindow.setOutsideTouchable(outsideTouchable);
-            mPopupWindow.setFocusable(!outsideTouchable);
-            mPopupWindow.update();
-            ret = true;
-        }
-        mPopupWindow.setOnDismissListener(onDismiss);
-        return ret;
-    }
-
-    private void updatePopupWindowContent(WidgetInfo widgetInfo) {
-        if (DEBUG) {
-            Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, "updatePopupWindowContent.");
-        }
-        ViewGroup contentContainer = (ViewGroup) mPopupWindow.getContentView();
-        contentContainer.removeAllViews();
-        SurfaceView surfaceView = new SurfaceView(mParent.getContext());
-        surfaceView.setZOrderOnTop(true);
-        surfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT);
-        surfaceView.setChildSurfacePackage(widgetInfo.getSurfacePackage());
-        contentContainer.addView(surfaceView);
-    }
-
-    private MenuItem getMenuItemByToolbarMenuItem(ToolbarMenuItem toolbarMenuItem) {
-        for (MenuItem item : mMenuItems) {
-            if (toolbarMenuItem.getItemId() == item.getItemId()) {
-                return item;
-            }
-        }
-        return null;
-    }
-
-    private Point getCoordinatesInWindow(int x, int y) {
-        // We later specify the location of PopupWindow relative to the attached window.
-        // The idea here is that 1) we can get the location of a View in both window coordinates
-        // and screen coordinates, where the offset between them should be equal to the window
-        // origin, and 2) we can use an arbitrary for this calculation while calculating the
-        // location of the rootview is supposed to be least expensive.
-        // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can avoid
-        // the following calculation.
-        mParent.getRootView().getLocationOnScreen(mCoordsOnScreen);
-        mParent.getRootView().getLocationInWindow(mCoordsOnWindow);
-        int windowLeftOnScreen = mCoordsOnScreen[0] - mCoordsOnWindow[0];
-        int windowTopOnScreen = mCoordsOnScreen[1] - mCoordsOnWindow[1];
-        return new Point(Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen));
-    }
-
-    private static List<ToolbarMenuItem> getToolbarMenuItems(List<MenuItem> menuItems) {
-        final List<ToolbarMenuItem> list = new ArrayList<>(menuItems.size());
-        for (MenuItem menuItem : menuItems) {
-            // TODO: use ToolbarMenuItem.Builder(MenuItem) instead
-            ToolbarMenuItem toolbarMenuItem = new ToolbarMenuItem.Builder(menuItem.getItemId(),
-                    menuItem.getTitle(), menuItem.getContentDescription(), menuItem.getGroupId(),
-                    convertDrawableToIcon(menuItem.getIcon()),
-                    menuItem.getTooltipText(),
-                    ToolbarMenuItem.getPriorityFromMenuItem(menuItem)).build();
-            list.add(toolbarMenuItem);
-        }
-        return list;
-    }
-
-    private static Icon convertDrawableToIcon(Drawable drawable) {
-        if (drawable == null) {
-            return null;
-        }
-        if (drawable instanceof BitmapDrawable) {
-            final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
-            if (bitmapDrawable.getBitmap() != null) {
-                return Icon.createWithBitmap(bitmapDrawable.getBitmap());
-            }
-        }
-        final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
-                drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
-        final Canvas canvas = new Canvas(bitmap);
-        drawable.setBounds(0, 0, canvas.getWidth(),  canvas.getHeight());
-        drawable.draw(canvas);
-        return Icon.createWithBitmap(bitmap);
-    }
-
-    private static PopupWindow createPopupWindow(Context content) {
-        ViewGroup popupContentHolder = new LinearLayout(content);
-        PopupWindow popupWindow = new PopupWindow(popupContentHolder);
-        popupWindow.setClippingEnabled(false);
-        popupWindow.setWindowLayoutType(
-                WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL);
-        popupWindow.setAnimationStyle(0);
-        popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
-        return popupWindow;
-    }
-
-    private void doDismissPopupWindow() {
-        if (DEBUG) {
-            Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, "RemoteFloatingToolbarPopup.doDismiss().");
-        }
-        mState = TOOLBAR_STATE_DISMISSED;
-        mMenuItems = null;
-        mMenuItemClickListener = null;
-        mFloatingToolbarToken = 0;
-        mSuggestedWidth = 0;
-        mWidthChanged = true;
-        resetCoords();
-        mPreviousContentRect.setEmpty();
-        mScreenViewPort.setEmpty();
-        mPopupWindow.dismiss();
-    }
-
-    private void resetCoords() {
-        mCoordsOnScreen[0] = 0;
-        mCoordsOnScreen[1] = 0;
-        mCoordsOnWindow[0] = 0;
-        mCoordsOnWindow[1] = 0;
-    }
-
-    private void runOnUiThread(Runnable runnable) {
-        mParent.post(runnable);
-    }
-
-    private void onShow(WidgetInfo info) {
-        runOnUiThread(() -> {
-            mFloatingToolbarToken = info.getWidgetToken();
-            mState = TOOLBAR_STATE_SHOWN;
-            updatePopupWindowContent(info);
-            Rect contentRect = info.getContentRect();
-            mPopupWindow.setWidth(contentRect.width());
-            mPopupWindow.setHeight(contentRect.height());
-            final Point coords = getCoordinatesInWindow(contentRect.left, contentRect.top);
-            mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, coords.x, coords.y);
-        });
-    }
-
-    private void onWidgetUpdated(WidgetInfo info) {
-        runOnUiThread(() -> {
-            if (!isShowing()) {
-                Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "onWidgetUpdated(): The widget isn't showing.");
-                return;
-            }
-            updatePopupWindowContent(info);
-            Rect contentRect = info.getContentRect();
-            Point coords = getCoordinatesInWindow(contentRect.left, contentRect.top);
-            if (DEBUG) {
-                Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "PopupWindow x= " + coords.x + " y= " + coords.y + " w="
-                                + contentRect.width() + " h=" + contentRect.height());
-            }
-            mPopupWindow.update(coords.x, coords.y, contentRect.width(), contentRect.height());
-        });
-    }
-
-    private void onToolbarShowTimeout() {
-        runOnUiThread(() -> {
-            if (mState == TOOLBAR_STATE_DISMISSED) {
-                return;
-            }
-            doDismissPopupWindow();
-        });
-    }
-
-    private void onMenuItemClicked(ToolbarMenuItem toolbarMenuItem) {
-        runOnUiThread(() -> {
-            if (mMenuItems == null || mMenuItemClickListener == null) {
-                return;
-            }
-            MenuItem item = getMenuItemByToolbarMenuItem(toolbarMenuItem);
-            if (DEBUG) {
-                Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "SelectionToolbarCallbackImpl onMenuItemClicked. toolbarMenuItem="
-                                + toolbarMenuItem + " item=" + item);
-            }
-            // TODO: handle the menu item like clipboard
-            if (item != null) {
-                mMenuItemClickListener.onMenuItemClick(item);
-            } else {
-                Log.e(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "onMenuItemClicked: cannot find menu item.");
-            }
-        });
-    }
-
-    private static class SelectionToolbarCallbackImpl extends ISelectionToolbarCallback.Stub {
-
-        private final WeakReference<RemoteFloatingToolbarPopup> mRemotePopup;
-
-        SelectionToolbarCallbackImpl(RemoteFloatingToolbarPopup popup) {
-            mRemotePopup = new WeakReference<>(popup);
-        }
-
-        @Override
-        public void onShown(WidgetInfo info) {
-            if (DEBUG) {
-                Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "SelectionToolbarCallbackImpl onShown: " + info);
-            }
-            final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get();
-            if (remoteFloatingToolbarPopup != null) {
-                remoteFloatingToolbarPopup.onShow(info);
-            } else {
-                Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "Lost remoteFloatingToolbarPopup reference for onShown.");
-            }
-        }
-
-        @Override
-        public void onWidgetUpdated(WidgetInfo info) {
-            if (DEBUG) {
-                Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "SelectionToolbarCallbackImpl onWidgetUpdated: info = " + info);
-            }
-            final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get();
-            if (remoteFloatingToolbarPopup != null) {
-                remoteFloatingToolbarPopup.onWidgetUpdated(info);
-            } else {
-                Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "Lost remoteFloatingToolbarPopup reference for onWidgetUpdated.");
-            }
-        }
-
-        @Override
-        public void onToolbarShowTimeout() {
-            final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get();
-            if (remoteFloatingToolbarPopup != null) {
-                remoteFloatingToolbarPopup.onToolbarShowTimeout();
-            } else {
-                Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "Lost remoteFloatingToolbarPopup reference for onToolbarShowTimeout.");
-            }
-        }
-
-        @Override
-        public void onMenuItemClicked(ToolbarMenuItem toolbarMenuItem) {
-            final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get();
-            if (remoteFloatingToolbarPopup != null) {
-                remoteFloatingToolbarPopup.onMenuItemClicked(toolbarMenuItem);
-            } else {
-                Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "Lost remoteFloatingToolbarPopup reference for onMenuItemClicked.");
-            }
-        }
-
-        @Override
-        public void onError(int errorCode) {
-            if (DEBUG) {
-                Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "SelectionToolbarCallbackImpl onError: " + errorCode);
-            }
-        }
-    }
-
-    /**
-     * Represents the identity of a MenuItem that is rendered in a FloatingToolbarPopup.
-     */
-    static final class MenuItemRepr {
-
-        public final int mItemId;
-        public final int mGroupId;
-        @Nullable
-        public final String mTitle;
-        @Nullable private final Drawable mIcon;
-
-        private MenuItemRepr(
-                int itemId, int groupId, @Nullable CharSequence title,
-                @Nullable Drawable icon) {
-            mItemId = itemId;
-            mGroupId = groupId;
-            mTitle = (title == null) ? null : title.toString();
-            mIcon = icon;
-        }
-
-        /**
-         * Creates an instance of MenuItemRepr for the specified menu item.
-         */
-        public static MenuItemRepr of(MenuItem menuItem) {
-            return new MenuItemRepr(
-                    menuItem.getItemId(),
-                    menuItem.getGroupId(),
-                    menuItem.getTitle(),
-                    menuItem.getIcon());
-        }
-
-        /**
-         * Returns this object's hashcode.
-         */
-        @Override
-        public int hashCode() {
-            return Objects.hash(mItemId, mGroupId, mTitle, mIcon);
-        }
-
-        /**
-         * Returns true if this object is the same as the specified object.
-         */
-        @Override
-        public boolean equals(Object o) {
-            if (o == this) {
-                return true;
-            }
-            if (!(o instanceof LocalFloatingToolbarPopup.MenuItemRepr)) {
-                return false;
-            }
-            final MenuItemRepr other = (MenuItemRepr) o;
-            return mItemId == other.mItemId
-                    && mGroupId == other.mGroupId
-                    && TextUtils.equals(mTitle, other.mTitle)
-                    // Many Drawables (icons) do not implement equals(). Using equals() here instead
-                    // of reference comparisons in case a Drawable subclass implements equals().
-                    && Objects.equals(mIcon, other.mIcon);
-        }
-
-        /**
-         * Returns true if the two menu item collections are the same based on MenuItemRepr.
-         */
-        public static boolean reprEquals(
-                Collection<MenuItem> menuItems1, Collection<MenuItem> menuItems2) {
-            if (menuItems1.size() != menuItems2.size()) {
-                return false;
-            }
-
-            final Iterator<MenuItem> menuItems2Iter = menuItems2.iterator();
-            for (MenuItem menuItem1 : menuItems1) {
-                final MenuItem menuItem2 = menuItems2Iter.next();
-                if (!MenuItemRepr.of(menuItem1).equals(
-                        MenuItemRepr.of(menuItem2))) {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-}
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 416d991..eb5f297 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -163,10 +163,9 @@
 
     mInfo.touchOcclusionMode = static_cast<TouchOcclusionMode>(
             env->GetIntField(obj, gInputWindowHandleClassInfo.touchOcclusionMode));
-    mInfo.ownerPid = env->GetIntField(obj,
-            gInputWindowHandleClassInfo.ownerPid);
-    mInfo.ownerUid = env->GetIntField(obj,
-            gInputWindowHandleClassInfo.ownerUid);
+    mInfo.ownerPid = gui::Pid{env->GetIntField(obj, gInputWindowHandleClassInfo.ownerPid)};
+    mInfo.ownerUid = gui::Uid{
+            static_cast<uid_t>(env->GetIntField(obj, gInputWindowHandleClassInfo.ownerUid))};
     mInfo.packageName = getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>");
     mInfo.displayId = env->GetIntField(obj,
             gInputWindowHandleClassInfo.displayId);
@@ -308,8 +307,10 @@
 
     env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.touchOcclusionMode,
                      static_cast<int32_t>(windowInfo.touchOcclusionMode));
-    env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.ownerPid, windowInfo.ownerPid);
-    env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.ownerUid, windowInfo.ownerUid);
+    env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.ownerPid,
+                     windowInfo.ownerPid.val());
+    env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.ownerUid,
+                     windowInfo.ownerUid.val());
     ScopedLocalRef<jstring> packageName(env, env->NewStringUTF(windowInfo.packageName.data()));
     env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.packageName,
                         packageName.get());
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index 01dbceb..8fc30d1 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -16,11 +16,12 @@
 
 #define LOG_TAG "GraphicsEnvironment"
 
-#include <vector>
-
 #include <graphicsenv/GraphicsEnv.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <nativeloader/native_loader.h>
+
+#include <vector>
+
 #include "core_jni_helpers.h"
 
 namespace {
@@ -49,11 +50,10 @@
                                                     appPackageNameChars.c_str(), vulkanVersion);
 }
 
-void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName,
-                         jstring devOptIn, jobjectArray featuresObj) {
+void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useNativeDriver,
+                         jstring packageName, jobjectArray featuresObj) {
     ScopedUtfChars pathChars(env, path);
-    ScopedUtfChars appNameChars(env, appName);
-    ScopedUtfChars devOptInChars(env, devOptIn);
+    ScopedUtfChars packageNameChars(env, packageName);
 
     std::vector<std::string> features;
     if (featuresObj != nullptr) {
@@ -73,13 +73,8 @@
         }
     }
 
-    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), appNameChars.c_str(),
-                                                     devOptInChars.c_str(), features);
-}
-
-bool shouldUseAngle_native(JNIEnv* env, jobject clazz, jstring appName) {
-    ScopedUtfChars appNameChars(env, appName);
-    return android::GraphicsEnv::getInstance().shouldUseAngle(appNameChars.c_str());
+    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useNativeDriver,
+                                                     packageNameChars.c_str(), features);
 }
 
 void setLayerPaths_native(JNIEnv* env, jobject clazz, jobject classLoader, jstring layerPaths) {
@@ -123,11 +118,8 @@
          reinterpret_cast<void*>(setGpuStats_native)},
         {"setInjectLayersPrSetDumpable", "()Z",
          reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
-        {"setAngleInfo",
-         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V",
+        {"nativeSetAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
          reinterpret_cast<void*>(setAngleInfo_native)},
-        {"getShouldUseAngle", "(Ljava/lang/String;)Z",
-         reinterpret_cast<void*>(shouldUseAngle_native)},
         {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
          reinterpret_cast<void*>(setLayerPaths_native)},
         {"setDebugLayers", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayers_native)},
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 4249253..dbe0338 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -257,6 +257,14 @@
     jmethodID onTrustedPresentationChanged;
 } gTrustedPresentationCallbackClassInfo;
 
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+    jfieldID layerName;
+    jfieldID bufferId;
+    jfieldID frameNumber;
+} gStalledTransactionInfoClassInfo;
+
 constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) {
     switch (colorMode) {
         case ui::ColorMode::DISPLAY_P3:
@@ -2032,6 +2040,29 @@
     return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeTpc));
 }
 
+static jobject nativeGetStalledTransactionInfo(JNIEnv* env, jclass clazz, jint pid) {
+    std::optional<gui::StalledTransactionInfo> stalledTransactionInfo =
+            SurfaceComposerClient::getStalledTransactionInfo(pid);
+    if (!stalledTransactionInfo) {
+        return nullptr;
+    }
+
+    jobject jStalledTransactionInfo = env->NewObject(gStalledTransactionInfoClassInfo.clazz,
+                                                     gStalledTransactionInfoClassInfo.ctor);
+    if (!jStalledTransactionInfo) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", nullptr);
+        return nullptr;
+    }
+
+    env->SetObjectField(jStalledTransactionInfo, gStalledTransactionInfoClassInfo.layerName,
+                        env->NewStringUTF(String8{stalledTransactionInfo->layerName}));
+    env->SetLongField(jStalledTransactionInfo, gStalledTransactionInfoClassInfo.bufferId,
+                      static_cast<jlong>(stalledTransactionInfo->bufferId));
+    env->SetLongField(jStalledTransactionInfo, gStalledTransactionInfoClassInfo.frameNumber,
+                      static_cast<jlong>(stalledTransactionInfo->frameNumber));
+    return jStalledTransactionInfo;
+}
+
 // ----------------------------------------------------------------------------
 
 SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl(JNIEnv* env,
@@ -2281,6 +2312,8 @@
     {"nativeCreateTpc", "(Landroid/view/SurfaceControl$TrustedPresentationCallback;)J",
             (void*)nativeCreateTpc},
     {"getNativeTrustedPresentationCallbackFinalizer", "()J", (void*)getNativeTrustedPresentationCallbackFinalizer },
+    {"nativeGetStalledTransactionInfo", "(I)Landroid/gui/StalledTransactionInfo;",
+            (void*) nativeGetStalledTransactionInfo },
         // clang-format on
 };
 
@@ -2524,6 +2557,18 @@
     gTrustedPresentationCallbackClassInfo.onTrustedPresentationChanged =
             GetMethodIDOrDie(env, trustedPresentationCallbackClazz, "onTrustedPresentationChanged",
                              "(Z)V");
+
+    jclass stalledTransactionInfoClazz = FindClassOrDie(env, "android/gui/StalledTransactionInfo");
+    gStalledTransactionInfoClassInfo.clazz = MakeGlobalRefOrDie(env, stalledTransactionInfoClazz);
+    gStalledTransactionInfoClassInfo.ctor =
+            GetMethodIDOrDie(env, stalledTransactionInfoClazz, "<init>", "()V");
+    gStalledTransactionInfoClassInfo.layerName =
+            GetFieldIDOrDie(env, stalledTransactionInfoClazz, "layerName", "Ljava/lang/String;");
+    gStalledTransactionInfoClassInfo.bufferId =
+            GetFieldIDOrDie(env, stalledTransactionInfoClazz, "bufferId", "J");
+    gStalledTransactionInfoClassInfo.frameNumber =
+            GetFieldIDOrDie(env, stalledTransactionInfoClazz, "frameNumber", "J");
+
     return err;
 }
 
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index ce806a0..c368fa8 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -356,6 +356,7 @@
     GWP_ASAN_LEVEL_DEFAULT = 3 << 21,
     NATIVE_HEAP_ZERO_INIT_ENABLED = 1 << 23,
     PROFILEABLE = 1 << 24,
+    DEBUG_ENABLE_PTRACE = 1 << 25,
 };
 
 enum UnsolicitedZygoteMessageTypes : uint32_t {
@@ -1887,8 +1888,10 @@
     }
 
     // Set process properties to enable debugging if required.
-    if ((runtime_flags & RuntimeFlags::DEBUG_ENABLE_JDWP) != 0) {
+    if ((runtime_flags & RuntimeFlags::DEBUG_ENABLE_PTRACE) != 0) {
         EnableDebugger();
+        // Don't pass unknown flag to the ART runtime.
+        runtime_flags &= ~RuntimeFlags::DEBUG_ENABLE_PTRACE;
     }
     if ((runtime_flags & RuntimeFlags::PROFILE_FROM_SHELL) != 0) {
         // simpleperf needs the process to be dumpable to profile it.
diff --git a/core/proto/android/input/keyboard_configured.proto b/core/proto/android/input/keyboard_configured.proto
new file mode 100644
index 0000000..0b4bf8e
--- /dev/null
+++ b/core/proto/android/input/keyboard_configured.proto
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package com.android.internal.os;
+
+option java_outer_classname = "KeyboardConfiguredProto";
+
+/**
+ * RepeatedKeyboardLayout proto from input_extension_atoms.proto,
+ * duplicated here so that it's accessible in the build.
+ * Must be kept in sync with the version in input_extension_atoms.proto.
+ */
+
+// Message containing the repeated field for KeyboardLayoutConfig
+message RepeatedKeyboardLayoutConfig {
+  repeated KeyboardLayoutConfig keyboard_layout_config = 1;
+}
+
+// Keyboard layout configured when the device is connected
+// used in KeyboardConfigured atom
+message KeyboardLayoutConfig {
+  // Keyboard configuration details
+  // Layout type mappings found at:
+  // frameworks/base/core/res/res/values/attrs.xml
+  optional int32 keyboard_layout_type = 1;
+  // PK language language tag (e.g. en-US, ru-Cyrl, etc). This will follow
+  // BCP-47 language tag standards.
+  optional string keyboard_language_tag = 2;
+  // Selected keyboard layout name (e.g. English(US), English(Dvorak), etc.)
+  optional string keyboard_layout_name = 3;
+  // Criteria for layout selection (such as user, device, virtual keyboard based)
+  // IntDef annotation at:
+  // services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+  optional int32 layout_selection_criteria = 4;
+  // Keyboard layout type provided by IME
+  optional int32 ime_layout_type = 5;
+  // Language tag provided by IME (e.g. en-US, ru-Cyrl etc.)
+  optional string ime_language_tag = 6;
+}
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 325ebbe..8e619a8 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -609,4 +609,6 @@
     optional bool animation_in_progress = 1;
     optional int32 last_back_type = 2;
     optional bool show_wallpaper = 3;
+    optional string main_open_activity = 4;
+    optional bool animation_running = 5;
 }
\ No newline at end of file
diff --git a/core/proto/android/server/windowmanagertransitiontrace.proto b/core/proto/android/server/windowmanagertransitiontrace.proto
index a950a79..34ccb48 100644
--- a/core/proto/android/server/windowmanagertransitiontrace.proto
+++ b/core/proto/android/server/windowmanagertransitiontrace.proto
@@ -56,6 +56,7 @@
   repeated Target targets = 8;
   optional int32 flags = 9;
   optional int64 abort_time_ns = 10;
+  optional int64 starting_window_remove_time_ns = 11;
 }
 
 message Target {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2f9f6ae..10cf353 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6094,7 +6094,7 @@
     <!-- @hide @SystemApi Allows an application to observe usage time of apps. The app can register
          for callbacks when apps reach a certain usage time limit, etc. -->
     <permission android:name="android.permission.OBSERVE_APP_USAGE"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- @hide @TestApi @SystemApi Allows an application to change the app idle state of an app.
          <p>Not for use by third-party applications. -->
@@ -6724,7 +6724,7 @@
          it will be ignored.
         @hide -->
     <permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"
-      android:protectionLevel="signature|privileged" />
+      android:protectionLevel="signature|privileged|role" />
 
     <!-- @SystemApi Allows entering or exiting car mode using a specified priority.
         This permission is required to use UiModeManager while specifying a priority for the calling
@@ -7655,6 +7655,24 @@
     <permission android:name="android.permission.GET_ANY_PROVIDER_TYPE"
                 android:protectionLevel="signature" />
 
+
+    <!-- @hide Allows internal applications to read and synchronize non-core flags.
+         Apps without this permission can only read a subset of flags specifically intended
+         for use in "core", (i.e. third party apps). Apps with this permission can define their
+         own flags, and federate those values with other system-level apps.
+         <p>Not for use by third-party applications.
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.SYNC_FLAGS"
+        android:protectionLevel="signature" />
+
+    <!-- @hide Allows internal applications to override flags in the FeatureFlags service.
+         <p>Not for use by third-party applications.
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.WRITE_FLAGS"
+        android:protectionLevel="signature" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/color-night/notification_expand_button_state_tint.xml b/core/res/res/color-night/notification_expand_button_state_tint.xml
new file mode 100644
index 0000000..a794d53
--- /dev/null
+++ b/core/res/res/color-night/notification_expand_button_state_tint.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.06"/>
+    <item android:state_hovered="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.03"/>
+    <item android:color="@android:color/system_on_surface_dark" android:alpha="0.00"/>
+</selector>
\ No newline at end of file
diff --git a/core/res/res/color/letterbox_background.xml b/core/res/res/color/letterbox_background.xml
deleted file mode 100644
index 955948a..0000000
--- a/core/res/res/color/letterbox_background.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?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.
-  -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@color/system_neutral1_500" android:lStar="5" />
-</selector>
diff --git a/core/res/res/color/notification_expand_button_state_tint.xml b/core/res/res/color/notification_expand_button_state_tint.xml
new file mode 100644
index 0000000..67b2c25
--- /dev/null
+++ b/core/res/res/color/notification_expand_button_state_tint.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:color="@android:color/system_on_surface_light" android:alpha="0.12"/>
+    <item android:state_hovered="true" android:color="@android:color/system_on_surface_light" android:alpha="0.08"/>
+    <item android:color="@android:color/system_on_surface_light" android:alpha="0.00"/>
+</selector>
\ No newline at end of file
diff --git a/core/res/res/drawable/expand_button_pill_bg.xml b/core/res/res/drawable/expand_button_pill_bg.xml
index f95044a..a14d33c 100644
--- a/core/res/res/drawable/expand_button_pill_bg.xml
+++ b/core/res/res/drawable/expand_button_pill_bg.xml
@@ -13,7 +13,17 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <corners android:radius="@dimen/notification_expand_button_pill_height" />
-    <solid android:color="@android:color/white" />
-</shape>
\ No newline at end of file
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/expand_button_pill_colorized_layer">
+        <shape xmlns:android="http://schemas.android.com/apk/res/android">
+            <corners android:radius="@dimen/notification_expand_button_pill_height" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+    <item>
+        <shape xmlns:android="http://schemas.android.com/apk/res/android">
+            <corners android:radius="@dimen/notification_expand_button_pill_height" />
+            <solid android:color="@color/notification_expand_button_state_tint" />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_bluetooth_share_icon.xml b/core/res/res/drawable/ic_bluetooth_share_icon.xml
deleted file mode 100644
index 6acfd57..0000000
--- a/core/res/res/drawable/ic_bluetooth_share_icon.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-     Copyright (C) 2019 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<!-- This drawable should only be used by the Bluetooth application for its share target icon. -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24"
-    android:tint="@*android:color/accent_device_default_light">
-
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L11,14.41V22h1l5.71-5.71L13.41,12L17.71,7.71z M13,5.83 l1.88,1.88L13,9.59V5.83z M14.88,16.29L13,18.17v-3.76L14.88,16.29z" />
-</vector>
diff --git a/core/res/res/layout/notification_expand_button.xml b/core/res/res/layout/notification_expand_button.xml
index 8eae064..63fe471 100644
--- a/core/res/res/layout/notification_expand_button.xml
+++ b/core/res/res/layout/notification_expand_button.xml
@@ -34,6 +34,7 @@
         android:background="@drawable/expand_button_pill_bg"
         android:gravity="center_vertical"
         android:layout_gravity="center_vertical"
+        android:duplicateParentState="true"
         >
 
         <TextView
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 8f0cd6a..74dd269 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Gebruik biometrie of skermslot"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verifieer dat dit jy is"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Gebruik jou biometrie om voort te gaan"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Gebruik jou vingerafdruk om voort te gaan"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Gebruik jou gesig om voort te gaan"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Gebruik jou biometriese data of skermslot om voort te gaan"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometriese hardeware is nie beskikbaar nie"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Stawing is gekanselleer"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"WEIER"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Kies invoermetode"</string>
     <string name="show_ime" msgid="6406112007347443383">"Hou dit op die skerm terwyl fisieke sleutelbord aktief is"</string>
-    <string name="hardware" msgid="1800597768237606953">"Wys virtuele sleutelbord"</string>
+    <string name="hardware" msgid="3611039921284836033">"Gebruik skermsleutelbord"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Stel <xliff:g id="DEVICE_NAME">%s</xliff:g> op"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Stel fisieke sleutelborde op"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tik om taal en uitleg te kies"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 44c0a6e..64c52bb 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"ባዮሜትሪክስ ወይም ማያ ገፅ መቆለፊያን ይጠቀሙ"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"እርስዎን መሆንዎን ያረጋግጡ"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"ለመቀጠል ባዮሜትሪክዎን ይጠቀሙ"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"ለመቀጠል የእርስዎን የጣት አሻራ ይጠቀሙ"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"ለመቀጠል የእርስዎን መልክ ይጠቀሙ"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"ለመቀጠል የባዮሜትሪክ ወይም የማያ ገፅ ቁልፍዎን ይጠቀሙ"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ባዮሜትራዊ ሃርድዌር አይገኝም"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ማረጋገጥ ተሰርዟል"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"አትቀበል"</string>
     <string name="select_input_method" msgid="3971267998568587025">"የግቤት ስልት ምረጥ"</string>
     <string name="show_ime" msgid="6406112007347443383">"አካላዊ የቁልፍ ሰሌዳ ገቢር ሆኖ ሳለ በማያ ገፅ ላይ አቆየው"</string>
-    <string name="hardware" msgid="1800597768237606953">"ምናባዊ የቁልፍ ሰሌዳን አሳይ"</string>
+    <string name="hardware" msgid="3611039921284836033">"የማያ ገጽ ላይ የቁልፍ ሰሌዳ ይጠቀሙ"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g>ን ያዋቅሩ"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"አካላዊ የቁልፍ ሰሌዳዎችን ያዋቅሩ"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ቋንቋ እና አቀማመጥን ለመምረጥ መታ ያድርጉ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 9dcf75b..b8a887c 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -620,8 +620,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"استخدام المقاييس الحيوية أو قفل الشاشة"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"إثبات هويتك"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"استخدام المقاييس الحيوية للمتابعة"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"استخدِم بصمة إصبعك للمتابعة"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"استخدِم وجهك للمتابعة"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"استخدام المقاييس الحيوية أو قفل الشاشة للمتابعة"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"معدّات المقاييس الحيوية غير متاحة."</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"تم إلغاء المصادقة."</string>
@@ -1401,7 +1399,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"رفض"</string>
     <string name="select_input_method" msgid="3971267998568587025">"اختيار أسلوب الإدخال"</string>
     <string name="show_ime" msgid="6406112007347443383">"استمرار عرضها على الشاشة عندما تكون لوحة المفاتيح الخارجية متصلة"</string>
-    <string name="hardware" msgid="1800597768237606953">"إظهار لوحة المفاتيح الافتراضية"</string>
+    <string name="hardware" msgid="3611039921284836033">"استخدام لوحة المفاتيح على الشاشة"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"إعداد <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"إعداد لوحات المفاتيح الخارجية"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"انقر لاختيار لغة وتنسيق"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 28588c7..2f9a904 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"বায়\'মেট্ৰিক অথবা স্ক্ৰীন লক ব্যৱহাৰ কৰক"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"এইয়া আপুনিয়েই বুলি সত্যাপন কৰক"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"অব্যাহত ৰাখিবলৈ আপোনাৰ বায়\'মেট্ৰিক ব্যৱহাৰ কৰক"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"অব্যাহত ৰাখিবলৈ আপোনাৰ ফিংগাৰপ্ৰিণ্ট ব্যৱহাৰ কৰক"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"অব্যাহত ৰাখিবলৈ নিজৰ মুখাৱয়ব ব্যৱহাৰ কৰক"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"অব্যাহত ৰাখিবলৈ আপোনাৰ বায়’মেট্ৰিক অথবা স্ক্ৰীন লক ব্যৱহাৰ কৰক"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"বায়োমেট্ৰিক হাৰ্ডৱেৰ উপলব্ধ নহয়"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"বিশ্বাসযোগ্যতাৰ প্ৰমাণীকৰণ বাতিল কৰা হৈছে"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"প্ৰত্যাখ্যান কৰক"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ইনপুট পদ্ধতি বাছনি কৰক"</string>
     <string name="show_ime" msgid="6406112007347443383">"কায়িক কীব’ৰ্ড সক্ৰিয় হৈ থাকোঁতে ইয়াক স্ক্ৰীনত ৰাখক"</string>
-    <string name="hardware" msgid="1800597768237606953">"ভাৰ্শ্বুৱল কীব\'ৰ্ড দেখুৱাওক"</string>
+    <string name="hardware" msgid="3611039921284836033">"অন-স্ক্ৰীন কীব’ৰ্ড ব্যৱহাৰ কৰক"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> কনফিগাৰ কৰক"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ভৌতিক কীব’ৰ্ড কনফিগাৰ কৰক"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ভাষা আৰু চানেকি বাছনি কৰিবলৈ ইয়াত টিপক"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 48e5cc3..ef1c0fb 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Biometrik məlumatlardan və ya ekran kilidindən istifadə edin"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Kimliyinizi doğrulayın"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Davam etmək üçün biometrik məlumatlarınızdan istifadə edin"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Barmaq izi ilə davam edin"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Üz ilə davam edin"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Davam etmək üçün biometrik məlumatlar və ya ekran kilidinizdən istifadə edin"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrik proqram əlçatan deyil"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Doğrulama ləğv edildi"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RƏDD EDİN"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Daxiletmə metodunu seçin"</string>
     <string name="show_ime" msgid="6406112007347443383">"Fiziki klaviatura aktiv olanda görünsün"</string>
-    <string name="hardware" msgid="1800597768237606953">"Virtual klaviaturanı göstərin"</string>
+    <string name="hardware" msgid="3611039921284836033">"Ekran klaviaturası işlədin"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> cihazını konfiqurasiya edin"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Fiziki klaviaturaları konfiqurasiya edin"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Dil və tərtibatı seçmək üçün tıklayın"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index cdb513d..f0e08f0 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Koristite biometriju ili zaključavanje ekrana"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Potvrdite svoj identitet"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Koristite biometrijski podatak da biste nastavili"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Nastavite pomoću otiska prsta"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Potvrdite identitet licem da biste nastavili"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Koristite biometrijski podatak ili zaključavanje ekrana da biste nastavili"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrijski hardver nije dostupan"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Potvrda identiteta je otkazana"</string>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ODBIJ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Izbor metoda unosa"</string>
     <string name="show_ime" msgid="6406112007347443383">"Zadržava se na ekranu dok je fizička tastatura aktivna"</string>
-    <string name="hardware" msgid="1800597768237606953">"Prikaži virtuelnu tastaturu"</string>
+    <string name="hardware" msgid="3611039921284836033">"Koristi tastaturu na ekranu"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurišite uređaj <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurišite fizičke tastature"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Dodirnite da biste izabrali jezik i raspored"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 51af5b1..f4a3ab1c 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -618,8 +618,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Выкарыстоўваць біяметрыю ці блакіроўку экрана"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Спраўдзіце, што гэта вы"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Каб працягнуць, скарыстайце свае біяметрычныя даныя"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Каб працягнуць, скарыстайце адбітак пальца"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Каб працягнуць, скарыстайце распазнаванне твару"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Каб працягнуць, скарыстайце біяметрычныя даныя ці сродак разблакіроўкі экрана"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Біяметрычнае абсталяванне недаступнае"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Аўтэнтыфікацыя скасавана"</string>
@@ -1399,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"АДХІЛІЦЬ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Выберыце метад уводу"</string>
     <string name="show_ime" msgid="6406112007347443383">"Захоўваць яе на экране ў той час, калі фізічная клавіятура актыўная"</string>
-    <string name="hardware" msgid="1800597768237606953">"Паказаць віртуальную клавіятуру"</string>
+    <string name="hardware" msgid="3611039921284836033">"Экранная клавіятура"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Наладзьце прыладу \"<xliff:g id="DEVICE_NAME">%s</xliff:g>\""</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Наладзьце фізічныя клавіятуры"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Дакраніцеся, каб выбраць мову і раскладку"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index a6c966c..522c4fa 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Използване на биометрични данни или опцията за заключване на екрана"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Потвърдете, че сте вие"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Използвайте биометричните си данни, за да продължите"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Използвайте отпечатъка си, за да продължите"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Използвайте лицето си, за да продължите"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Използвайте биометричните си данни или опцията за заключване на екрана, за да продължите"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометричният хардуер не е налице"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Удостоверяването бе анулирано"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ОТХВЪРЛЯНЕ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Избор на метод на въвеждане"</string>
     <string name="show_ime" msgid="6406112007347443383">"Показване на екрана, докато физическата клавиатура е активна"</string>
-    <string name="hardware" msgid="1800597768237606953">"Показване на вирт. клавиатура"</string>
+    <string name="hardware" msgid="3611039921284836033">"Ползв. на екранната клавиатура"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Конфигуриране на <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Конфигуриране на физически клавиатури"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Докоснете, за да изберете език и подредба"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index b305350..c3482b0 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"বায়োমেট্রিক্স অথবা স্ক্রিন লক ব্যবহার করুন"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"আপনার পরিচয় যাচাই করুন"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"চালিয়ে যেতে আপনার বায়োমেট্রিক্স ব্যবহার করুন"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"চালিয়ে যেতে ফিঙ্গারপ্রিন্ট ব্যবহার করুন"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"চালিয়ে যেতে আপনার ফেস ব্যবহার করুন"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"চালিয়ে যেতে আপনার বায়োমেট্রিক্স বা স্ক্রিন লক ব্যবহার করুন"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"বায়োমেট্রিক হার্ডওয়্যার পাওয়া যাবে না"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"যাচাইকরণ বাতিল হয়েছে"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"অস্বীকার করুন"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ইনপুট পদ্ধতি বেছে নিন"</string>
     <string name="show_ime" msgid="6406112007347443383">"ফিজিক্যাল কীবোর্ড সক্রিয় থাকার সময় এটিকে স্ক্রীনে রাখুন"</string>
-    <string name="hardware" msgid="1800597768237606953">"ভার্চুয়াল কীবোর্ড দেখুন"</string>
+    <string name="hardware" msgid="3611039921284836033">"স্ক্রিনের কীবোর্ড ব্যবহার করুন"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> কনফিগার করুন"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ফিজিক্যাল কীবোর্ড কনফিগার করুন"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ভাষা ও লেআউট বেছে নিতে ট্যাপ করুন"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 7aa4cc6..5df986a 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Koristi biometriju ili zaključavanje ekrana"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Potvrdite identitet"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Koristite biometriju da nastavite"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Potvrdite identitet otiskom prsta da nastavite"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Potvrdite identitet licem da nastavite"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Koristite biometriju ili zaključavanje ekrana da nastavite"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrijski hardver nije dostupan"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentifikacija je otkazana"</string>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ODBACI"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Odaberite način unosa"</string>
     <string name="show_ime" msgid="6406112007347443383">"Prikaži na ekranu dok je fizička tastatura aktivna"</string>
-    <string name="hardware" msgid="1800597768237606953">"Prikaz virtuelne tastature"</string>
+    <string name="hardware" msgid="3611039921284836033">"Koristi tastaturu na ekranu"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurirajte uređaj <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurirajte fizičke tastature"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Dodirnite da odaberete jezik i raspored"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 12bc9c1..4f60cac 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Fes servir la biometria o el bloqueig de pantalla"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verifica la teva identitat"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Utilitza la teva biometria per continuar"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Utilitza l\'empremta digital per continuar"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Utilitza la cara per continuar"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Utilitza la biometria o el bloqueig de pantalla per continuar"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Maquinari biomètric no disponible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"S\'ha cancel·lat l\'autenticació"</string>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"REBUTJA"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Selecciona un mètode d\'introducció"</string>
     <string name="show_ime" msgid="6406112007347443383">"Mantén-lo en pantalla mentre el teclat físic està actiu"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostra el teclat virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Utilitza el teclat en pantalla"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configura <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configura els teclats físics"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Toca per seleccionar l\'idioma i la disposició"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index c691742..b944831 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -618,8 +618,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Použít biometrii nebo zámek obrazovky"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Potvrďte, že jste to vy"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Pokračujte biometrickým ověřením"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Pokračujte přiložením prstu"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Pokračujte ověřením obličeje"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Pokračujte ověřením pomocí biometrických údajů nebo zámku obrazovky"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrický hardware není k dispozici"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Ověření bylo zrušeno"</string>
@@ -1399,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ODMÍTNOUT"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Vybrat metodu zadávání"</string>
     <string name="show_ime" msgid="6406112007347443383">"Ponechat na obrazovce, když je aktivní fyzická klávesnice"</string>
-    <string name="hardware" msgid="1800597768237606953">"Zobrazit virtuální klávesnici"</string>
+    <string name="hardware" msgid="3611039921284836033">"Použít softwarovou klávesnici"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Nakonfigurujte zařízení <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Nakonfigurujte fyzické klávesnice"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Klepnutím vyberte jazyk a rozvržení"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 45124fb..41048c7 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Brug biometri eller skærmlås"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verificer, at det er dig"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Brug dine biometriske data for at fortsætte"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Brug dit fingeraftryk for at fortsætte"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Brug dit ansigt for at fortsætte"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Brug dine biometriske data eller din skærmlås for at fortsætte"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrisk hardware er ikke tilgængelig"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Godkendelsen blev annulleret"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"AFVIS"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Vælg inputmetode"</string>
     <string name="show_ime" msgid="6406112007347443383">"Behold den på skærmen, mens det fysiske tastatur er aktivt"</string>
-    <string name="hardware" msgid="1800597768237606953">"Vis virtuelt tastatur"</string>
+    <string name="hardware" msgid="3611039921284836033">"Brug skærmtastaturet"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurer <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurer fysiske tastaturer"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tryk for at vælge sprog og layout"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index ad4f2f4..82cf412 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Biometrisches Verfahren oder Displaysperre verwenden"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Deine Identität bestätigen"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Mithilfe eines biometrischen Verfahrens fortfahren"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Fingerabdruck verwenden, um fortzufahren"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Gesichtserkennung verwenden, um fortzufahren"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Verwende deine biometrischen Daten oder deine Display-Entsperrmethode, um fortzufahren"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrische Hardware nicht verfügbar"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentifizierung abgebrochen"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ABLEHNEN"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Eingabemethode wählen"</string>
     <string name="show_ime" msgid="6406112007347443383">"Bildschirmtastatur auch dann anzeigen, wenn physische Tastatur aktiv ist"</string>
-    <string name="hardware" msgid="1800597768237606953">"Virtuelle Tastatur einblenden"</string>
+    <string name="hardware" msgid="3611039921284836033">"Bildschirmtastatur verwenden"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> konfigurieren"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Physische Tastaturen konfigurieren"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Zum Auswählen von Sprache und Layout tippen"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 62b776a..430c577 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Χρήση βιομετρικών ή κλειδώματος οθόνης"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Επαλήθευση ταυτότητας"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Χρησιμοποιήστε βιομετρικά για να συνεχίσετε"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Χρησιμοποιήστε το δακτυλικό σας αποτύπωμα για να συνεχίσετε"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Χρησιμοποιήστε το πρόσωπό σας για να συνεχίσετε"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Χρήση βιομετρικών στοιχείων ή κλειδώματος οθόνης για συνέχεια"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Δεν υπάρχει διαθέσιμος βιομετρικός εξοπλισμός"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Ο έλεγχος ταυτότητας ακυρώθηκε"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ΑΠΟΡΡΙΨΗ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Επιλογή μεθόδου εισόδου"</string>
     <string name="show_ime" msgid="6406112007347443383">"Να παραμένει στην οθόνη όταν είναι ενεργό το κανονικό πληκτρολόγιο"</string>
-    <string name="hardware" msgid="1800597768237606953">"Εμφάνιση εικονικού πληκτρολ."</string>
+    <string name="hardware" msgid="3611039921284836033">"Χρήση πληκτρολογίου οθόνης"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Διαμόρφωση <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Διαμόρφωση φυσικών πληκτρολογίων"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Πατήστε για να επιλέξετε γλώσσα και διάταξη"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 888d2c9..30ca510 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Use biometrics or screen lock"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verify that it’s you"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Use your biometric to continue"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Use your fingerprint to continue"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Use your face to continue"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Use your biometric or screen lock to continue"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometric hardware unavailable"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication cancelled"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"DECLINE"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Choose input method"</string>
     <string name="show_ime" msgid="6406112007347443383">"Keep it on screen while physical keyboard is active"</string>
-    <string name="hardware" msgid="1800597768237606953">"Show virtual keyboard"</string>
+    <string name="hardware" msgid="3611039921284836033">"Use on-screen keyboard"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configure <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configure physical keyboards"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tap to select language and layout"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 3641b09..3231167 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Use biometrics or screen lock"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verify it’s you"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Use your biometric to continue"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Use your fingerprint to continue"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Use your face to continue"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Use your biometric or screen lock to continue"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometric hardware unavailable"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication canceled"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"DECLINE"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Choose input method"</string>
     <string name="show_ime" msgid="6406112007347443383">"Keep it on screen while physical keyboard is active"</string>
-    <string name="hardware" msgid="1800597768237606953">"Show virtual keyboard"</string>
+    <string name="hardware" msgid="3611039921284836033">"Use on-screen keyboard"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configure <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configure physical keyboards"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tap to select language and layout"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index ee28a95..03a1bd1 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Use biometrics or screen lock"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verify that it’s you"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Use your biometric to continue"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Use your fingerprint to continue"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Use your face to continue"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Use your biometric or screen lock to continue"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometric hardware unavailable"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication cancelled"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"DECLINE"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Choose input method"</string>
     <string name="show_ime" msgid="6406112007347443383">"Keep it on screen while physical keyboard is active"</string>
-    <string name="hardware" msgid="1800597768237606953">"Show virtual keyboard"</string>
+    <string name="hardware" msgid="3611039921284836033">"Use on-screen keyboard"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configure <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configure physical keyboards"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tap to select language and layout"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index c254def..c82001e 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Use biometrics or screen lock"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verify that it’s you"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Use your biometric to continue"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Use your fingerprint to continue"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Use your face to continue"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Use your biometric or screen lock to continue"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometric hardware unavailable"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication cancelled"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"DECLINE"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Choose input method"</string>
     <string name="show_ime" msgid="6406112007347443383">"Keep it on screen while physical keyboard is active"</string>
-    <string name="hardware" msgid="1800597768237606953">"Show virtual keyboard"</string>
+    <string name="hardware" msgid="3611039921284836033">"Use on-screen keyboard"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configure <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configure physical keyboards"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tap to select language and layout"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 22087de..68c325b 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‏‎‎‎‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‎‎‏‎‏‎‏‎‎‏‎‏‏‎‎‎‎Use biometrics or screen lock‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‎‏‏‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‎‎‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‎‎‏‎‎‎‏‎‎‏‎‎‏‎‎Verify it’s you‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‎‎‎‎‎‎‎‎‎‎‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‏‏‎‎‏‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎Use your biometric to continue‎‏‎‎‏‎"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‎‎‏‎‎‎‏‎‏‏‏‎‎Use your fingerprint to continue‎‏‎‎‏‎"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‏‏‏‎‏‏‎‎‎Use your face to continue‎‏‎‎‏‎"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‏‏‎‎‎‏‏‏‏‏‏‎‎‎‎‎‎‎‎‎‏‏‏‎‏‎‏‎‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎Use your biometric or screen lock to continue‎‏‎‎‏‎"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‎‏‏‏‎‎‏‎‏‎‎‎‏‏‎‎‎‎‎Biometric hardware unavailable‎‏‎‎‏‎"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‎‏‏‏‏‎‎‎‎‏‏‎‎‎‏‏‎‎‎‎‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎Authentication canceled‎‏‎‎‏‎"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‏‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‎‎DECLINE‎‏‎‎‏‎"</string>
     <string name="select_input_method" msgid="3971267998568587025">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‎‏‎‎‎‏‏‏‎‏‎‎‏‏‏‏‏‎‎‎‏‎‎‎‏‎Choose input method‎‏‎‎‏‎"</string>
     <string name="show_ime" msgid="6406112007347443383">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‎‎‏‏‏‎‎‎‏‎‏‎‎‏‏‏‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‏‎‎‎‏‎‏‏‎‏‎‏‏‎‏‏‏‎Keep it on screen while physical keyboard is active‎‏‎‎‏‎"</string>
-    <string name="hardware" msgid="1800597768237606953">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‎‏‎‏‎‏‎‏‏‏‎‎‏‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‏‎‏‎‎‏‎Show virtual keyboard‎‏‎‎‏‎"</string>
+    <string name="hardware" msgid="3611039921284836033">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‎‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‎‎‎‏‎Use on-screen keyboard‎‏‎‎‏‎"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‏‎‎‎‎‎‎‏‎‏‎‎‏‎‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‏‏‎‎‏‏‏‎‏‏‎‎‏‎‏‎‏‎Configure ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‎‎‎‏‏‎‎‏‏‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‎‎‎‎‏‎‎‏‏‎‏‎‏‎‏‎‏‎‎‎‏‎‎‎‏‎‎‏‎‎Configure physical keyboards‎‏‎‎‏‎"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‏‏‎‎‏‎‎‏‏‎‎‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‏‏‏‎‎‎‎‎‏‎‏‎‎Tap to select language and layout‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 2782cbd..2c0adce 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Usar datos biométricos o bloqueo de pantalla"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Comprueba que eres tú"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Usa tus datos biométricos para continuar"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Usa tu huella dactilar para continuar"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Usa el rostro para continuar"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Usa tus datos biométricos o bloqueo de pantalla para continuar"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"No hay hardware biométrico disponible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Se canceló la autenticación"</string>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RECHAZAR"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Selecciona el método de entrada"</string>
     <string name="show_ime" msgid="6406112007347443383">"Mientras el teclado físico está activo"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostrar teclado virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Usar teclado en pantalla"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configura <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configura teclados físicos"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Presiona para seleccionar el idioma y el diseño"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 29789e9..ed789a1 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Usar biometría o bloqueo de pantalla"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verifica que eres tú"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Usa tu biometría para continuar"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Usa la huella digital para continuar"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Usa la cara para continuar"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Usa la biometría o tu bloqueo de pantalla para continuar"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biométrico no disponible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autenticación cancelada"</string>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RECHAZAR"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Selecciona un método de entrada"</string>
     <string name="show_ime" msgid="6406112007347443383">"Mantenlo en pantalla mientras el teclado físico está activo"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostrar teclado virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Usar teclado en pantalla"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configura <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configura teclados físicos"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Toca para seleccionar el idioma y el diseño"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 7ab747f..c25ecb6 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Biomeetria või ekraaniluku kasutamine"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Kinnitage oma isik"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Jätkamiseks kasutage biomeetriat"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Jätkamiseks kasutage oma sõrmejälge"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Jätkamiseks kasutage oma nägu"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Jätkamiseks kasutage oma biomeetrilisi andmeid või ekraanilukku"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biomeetriline riistvara ei ole saadaval"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentimine tühistati"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"KEELDU"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Valige sisestusmeetod"</string>
     <string name="show_ime" msgid="6406112007347443383">"Hoia seda ekraanil, kui füüsiline klaviatuur on aktiivne"</string>
-    <string name="hardware" msgid="1800597768237606953">"Virtuaalse klaviatuuri kuvam."</string>
+    <string name="hardware" msgid="3611039921284836033">"Kasuta ekraaniklaviatuuri"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Seadistage <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Seadistage füüsilised klaviatuurid"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Puudutage keele ja paigutuse valimiseks"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index e47290d..c3a8c43 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Erabili sistema biometrikoak edo pantailaren blokeoa"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Egiaztatu zeu zarela"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Aurrera egiteko, erabili sistema biometrikoak"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Aurrera egiteko, erabili hatz-marka"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Aurrera egiteko, erabili aurpegia"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Aurrera egiteko, erabili sistema biometrikoak edo pantailaren blokeoa"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometrikoa ez dago erabilgarri"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Utzi da autentifikazioa"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"BAZTERTU"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Aukeratu idazketa-metodoa"</string>
     <string name="show_ime" msgid="6406112007347443383">"Erakutsi pantailan teklatu fisikoa aktibo dagoen bitartean"</string>
-    <string name="hardware" msgid="1800597768237606953">"Erakutsi teklatu birtuala"</string>
+    <string name="hardware" msgid="3611039921284836033">"Erabili pantailako teklatua"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfiguratu <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfiguratu teklatu fisikoak"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Hizkuntza eta diseinua hautatzeko, sakatu hau"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 1d963fa..67ae35d 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"استفاده از زیست‌سنجشی یا قفل صفحه"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"تأیید کنید این شمایید"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"برای ادامه، از زیست‌سنجشی استفاده کنید"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"برای ادامه، از اثر انگشتتان استفاده کنید"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"برای ادامه، از چهره‌تان استفاده کنید"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"برای ادامه، از زیست‌سنجشی یا قفل صفحه استفاده کنید"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"سخت‌افزار زیست‌سنجی دردسترس نیست"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"اصالت‌سنجی لغو شد"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"نپذیرفتن"</string>
     <string name="select_input_method" msgid="3971267998568587025">"انتخاب روش ورودی"</string>
     <string name="show_ime" msgid="6406112007347443383">"وقتی صفحه‌کلید فیزیکی فعال است این ویرایشگر را روی صفحه نگه‌می‌دارد"</string>
-    <string name="hardware" msgid="1800597768237606953">"نمایش صفحه‌کلید مجازی"</string>
+    <string name="hardware" msgid="3611039921284836033">"استفاده از صفحه‌کلید مجازی"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"پیکربندی <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"پیکربندی صفحه‌کلیدهای فیزیکی"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"برای انتخاب زبان و چیدمان ضربه بزنید"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 60fcfcc..c47f7ba 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Käytä biometriikkaa tai näytön lukitusta"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Vahvista henkilöllisyytesi"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Jatka käyttämällä biometriikkaa"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Jatka sormenjäljen avulla"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Jatka kasvojen avulla"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Jatka biometriikan tai näytön lukituksen avulla"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrinen laitteisto ei käytettävissä"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Todennus peruutettu"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"HYLKÄÄ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Valitse syöttötapa"</string>
     <string name="show_ime" msgid="6406112007347443383">"Pidä näytöllä, kun fyysinen näppäimistö on aktiivinen"</string>
-    <string name="hardware" msgid="1800597768237606953">"Näytä virtuaalinen näppäimistö"</string>
+    <string name="hardware" msgid="3611039921284836033">"Käytä näyttönäppäimistöä"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Määritä <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Määritä fyysiset näppäimistöt"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Valitse kieli ja asettelu koskettamalla."</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 7fa7a48..be6687c 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Utiliser les données biométriques ou le verrouillage de l\'écran"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Confirmez que c\'est vous"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Utilisez votre méthode d\'authentification biométrique pour continuer"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Utilisez votre empreinte digitale pour continuer"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Utilisez votre visage pour continuer"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Utilisez vos données biométriques 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>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"REFUSER"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Sélectionnez le mode de saisie"</string>
     <string name="show_ime" msgid="6406112007347443383">"Afficher lorsque le clavier physique est activé"</string>
-    <string name="hardware" msgid="1800597768237606953">"Afficher le clavier virtuel"</string>
+    <string name="hardware" msgid="3611039921284836033">"Utiliser le clavier à l\'écran"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configurer <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configurer les claviers physiques"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Touchez pour sélectionner la langue et la configuration du clavier"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index ac7e389..4f6d262 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Utiliser la biométrie ou le verrouillage de l\'écran"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Confirmez votre identité"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Utilisez la biométrie pour continuer"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Utilisez votre empreinte digitale pour continuer"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Utilisez la reconnaissance faciale pour continuer"</string>
     <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>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"REFUSER"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Sélectionnez le mode de saisie"</string>
     <string name="show_ime" msgid="6406112007347443383">"Afficher le clavier virtuel même lorsque le clavier physique est actif"</string>
-    <string name="hardware" msgid="1800597768237606953">"Afficher le clavier virtuel"</string>
+    <string name="hardware" msgid="3611039921284836033">"Utiliser le clavier à l\'écran"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configurer <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configurez les claviers physiques"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Appuyez pour sélectionner la langue et la disposition"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 35fd97c..9dd72cc 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Utilizar desbloqueo biométrico ou credencial do dispositivo"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verifica que es ti"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Para continuar, utiliza o desbloqueo biométrico"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Usa a impresión dixital para continuar"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Usa o recoñecemento facial para continuar"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Para continuar, utiliza o desbloqueo biométrico ou o bloqueo de pantalla"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"O hardware biométrico non está dispoñible"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Cancelouse a autenticación"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ANULAR"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Escoller método de introdución de texto"</string>
     <string name="show_ime" msgid="6406112007347443383">"Móstrase na pantalla mentres o teclado físico estea activo"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostrar o teclado virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Utilizar o teclado en pantalla"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configura o teclado (<xliff:g id="DEVICE_NAME">%s</xliff:g>)"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configura os teclados físicos"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Toca para seleccionar o idioma e o deseño"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 2f54c7c..e79adb9 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"બાયોમેટ્રિક્સ અથવા સ્ક્રીન લૉકનો ઉપયોગ કરો"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"તે તમે જ છો એ ચકાસો"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"આગળ વધવા માટે બાયોમેટ્રિકનો ઉપયોગ કરો"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"ચાલુ રાખવા માટે તમારી ફિંગરપ્રિન્ટનો ઉપયોગ કરો"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"ચાલુ રાખવા માટે તમારા ચહેરાનો ઉપયોગ કરો"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"ચાલુ રાખવા માટે તમારા બાયોમેટ્રિક ડેટા અથવા સ્ક્રીન લૉક સુવિધાનો ઉપયોગ કરો"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"બાયોમેટ્રિક હાર્ડવેર ઉપલબ્ધ નથી"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"પ્રમાણીકરણ રદ કર્યું"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"નકારો"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ઇનપુટ પદ્ધતિ પસંદ કરો"</string>
     <string name="show_ime" msgid="6406112007347443383">"જ્યારે ભૌતિક કીબોર્ડ સક્રિય હોય ત્યારે તેને સ્ક્રીન પર રાખો"</string>
-    <string name="hardware" msgid="1800597768237606953">"વર્ચ્યુઅલ કીબોર્ડ બતાવો"</string>
+    <string name="hardware" msgid="3611039921284836033">"ઑન-સ્ક્રીન કીબોર્ડનો ઉપયોગ કરો"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g>ની ગોઠવણી કરો"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ભૌતિક કીબોર્ડની ગોઠવણી કરો"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ભાષા અને લેઆઉટ પસંદ કરવા માટે ટૅપ કરો"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 8b2fbd2..fbec97e 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"बायोमेट्रिक्स या स्क्रीन लॉक का क्रेडेंशियल इस्तेमाल करें"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"अपनी पहचान की पुष्टि करें"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"जारी रखने के लिए, बायोमेट्रिक्स इस्तेमाल करें"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"जारी रखने के लिए, अपने फ़िंगरप्रिंट की मदद से पहचान की पुष्टि करें"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"जारी रखने के लिए, अपने चेहरा की मदद से पहचान की पुष्टि करें"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"जारी रखने के लिए, बायोमेट्रिक या स्क्रीन लॉक क्रेडेंशियल डालकर पुष्टि करें"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"बायोमेट्रिक हार्डवेयर उपलब्ध नहीं है"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"प्रमाणीकरण रद्द किया गया"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"अस्वीकार करें"</string>
     <string name="select_input_method" msgid="3971267998568587025">"इनपुट का तरीका चुनें"</string>
     <string name="show_ime" msgid="6406112007347443383">"सामान्य कीबोर्ड के सक्रिय होने के दौरान इसे स्‍क्रीन पर बनाए रखें"</string>
-    <string name="hardware" msgid="1800597768237606953">"वर्चुअल कीबोर्ड दिखाएं"</string>
+    <string name="hardware" msgid="3611039921284836033">"ऑन-स्क्रीन कीबोर्ड इस्तेमाल करें"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> को कॉन्फ़िगर करें"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"फ़िज़िकल कीबोर्ड को कॉन्फ़िगर करें"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"भाषा और लेआउट चुनने के लिए टैप करें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index b6ed6e6..d9b84f1 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Upotreba biometrijske autentifikacije ili zaključavanja zaslona"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Potvrdite da ste to vi"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Upotrijebite svoju biometrijsku autentifikaciju da biste nastavili"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Za nastavak upotrijebite otisak prsta"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Za nastavak se identificirajte licem"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Za nastavak se identificirajte biometrijski ili vjerodajnicom zaključavanja zaslona"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrijski hardver nije dostupan"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentifikacija otkazana"</string>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ODBIJ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Odabir načina unosa"</string>
     <string name="show_ime" msgid="6406112007347443383">"Zadrži na zaslonu dok je fizička tipkovnica aktivna"</string>
-    <string name="hardware" msgid="1800597768237606953">"Prikaži virtualnu tipkovnicu"</string>
+    <string name="hardware" msgid="3611039921284836033">"Upotreba zaslonske tipkovnice"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurirajte uređaj <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurirajte fizičke tipkovnice"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Dodirnite da biste odabrali jezik i raspored"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 5ac7182..92d7727 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"A folytatás biometriai feloldással vagy képernyőzárral lehetséges"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Igazolja, hogy Ön az"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"A folytatás biometriai feloldással lehetséges"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"A folytatáshoz használja ujjlenyomatát"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"A folytatáshoz használja az arcalapú feloldást"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"A folytatás biometriai feloldással vagy a képernyőzár feloldásával lehetséges"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrikus hardver nem áll rendelkezésre"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Hitelesítés megszakítva"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ELUTASÍTÁS"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Beviteli mód kiválasztása"</string>
     <string name="show_ime" msgid="6406112007347443383">"Maradjon a képernyőn, amíg a billentyűzet aktív"</string>
-    <string name="hardware" msgid="1800597768237606953">"Virtuális billentyűzet"</string>
+    <string name="hardware" msgid="3611039921284836033">"Képernyő-billentyűzet"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"A(z) <xliff:g id="DEVICE_NAME">%s</xliff:g> beállítása"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Fizikai billentyűzetek beállítása"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Koppintson a nyelv és a billentyűzetkiosztás kiválasztásához"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 0baff6b..94381a7 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Օգտագործել կենսաչափական համակարգեր կամ էկրանի կողպում"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Հաստատեք ձեր ինքնությունը"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Շարունակելու համար օգտագործեք կենսաչափական համակարգեր"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Շարունակելու համար օգտագործեք ձեր մատնահետքը"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Շարունակելու համար օգտագործեք դեմքով իսկորոշումը"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Շարունակելու համար օգտագործեք ձեր կենսաչափական տվյալները կամ էկրանի կողպումը"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Կենսաչափական սարքը հասանելի չէ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Նույնականացումը չեղարկվեց"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ՄԵՐԺԵԼ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Ընտրեք ներածման եղանակը"</string>
     <string name="show_ime" msgid="6406112007347443383">"Պահել էկրանին, երբ ֆիզիկական ստեղնաշարն ակտիվ է"</string>
-    <string name="hardware" msgid="1800597768237606953">"Ցույց տալ վիրտուալ ստեղնաշարը"</string>
+    <string name="hardware" msgid="3611039921284836033">"Օգտագործել էկրանային ստեղնաշար"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Կարգավորեք <xliff:g id="DEVICE_NAME">%s</xliff:g> սարքը"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Կարգավորեք ֆիզիկական ստեղնաշարերը"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Հպեք՝ լեզուն և դասավորությունն ընտրելու համար"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 8904ed9..7ec0787 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Gunakan biometrik atau kunci layar"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verifikasi bahwa ini memang Anda"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Gunakan biometrik untuk melanjutkan"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Gunakan sidik jari untuk melanjutkan"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Gunakan wajah untuk melanjutkan"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Gunakan biometrik atau kunci layar untuk melanjutkan"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometrik tidak tersedia"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentikasi dibatalkan"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"TOLAK"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Pilih metode masukan"</string>
     <string name="show_ime" msgid="6406112007347443383">"Biarkan di layar meski keyboard fisik aktif"</string>
-    <string name="hardware" msgid="1800597768237606953">"Tampilkan keyboard virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Gunakan keyboard virtual"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurasi <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurasi keyboard fisik"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Ketuk untuk memilih bahasa dan tata letak"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index b4b378f..1c5738d 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Nota lífkenni eða skjálás"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Staðfestu hver þú ert"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Notaðu lífkenni til að halda áfram"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Notaðu fingrafarið til að halda áfram"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Notaðu andlitið til að halda áfram"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Notaðu lífkenni eða skjálás til að halda áfram"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Lífkennavélbúnaður ekki tiltækur"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Hætt við auðkenningu"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"HAFNA"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Veldu innsláttaraðferð"</string>
     <string name="show_ime" msgid="6406112007347443383">"Halda því á skjánum meðan vélbúnaðarlyklaborðið er virkt"</string>
-    <string name="hardware" msgid="1800597768237606953">"Sýna sýndarlyklaborð"</string>
+    <string name="hardware" msgid="3611039921284836033">"Nota skjályklaborð"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Stilla <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Stilla vélbúnaðarlyklaborð"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Ýttu til að velja tungumál og útlit"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 49d3fffa..d932fcc 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Usa la biometria o il blocco schermo"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verifica la tua identità"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Usa la biometria per continuare"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Utilizza la tua impronta per continuare"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Usa il tuo volto per continuare"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Per continuare devi usare i tuoi dati biometrici o il tuo blocco schermo"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometrico non disponibile"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autenticazione annullata"</string>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RIFIUTO"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Scegli il metodo di immissione"</string>
     <string name="show_ime" msgid="6406112007347443383">"Tieni sullo schermo quando è attiva la tastiera fisica"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostra tastiera virtuale"</string>
+    <string name="hardware" msgid="3611039921284836033">"Usa tastiera sullo schermo"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configura <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configura le tastiere fisiche"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tocca per selezionare la lingua e il layout"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index c7b2204..33a2ada 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"שימוש במידע ביומטרי בנעילת מסך"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"אימות הזהות שלך"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"יש להשתמש במידע ביומטרי כדי להמשיך"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"צריך להשתמש בטביעת האצבע כדי להמשיך"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"צריך להשתמש בזיהוי הפנים כדי להמשיך"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"יש להשתמש במידע הביומטרי או בנעילת המסך כדי להמשיך"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"חומרה ביומטרית לא זמינה"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"האימות בוטל"</string>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"עדיף שלא"</string>
     <string name="select_input_method" msgid="3971267998568587025">"בחירה של שיטת הזנה"</string>
     <string name="show_ime" msgid="6406112007347443383">"להשאיר במסך בזמן שהמקלדת הפיזית פעילה"</string>
-    <string name="hardware" msgid="1800597768237606953">"הצגת מקלדת וירטואלית"</string>
+    <string name="hardware" msgid="3611039921284836033">"שימוש במקלדת שמופיעה במסך"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"הגדרה של <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"הגדרת מקלדות פיזיות"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"יש להקיש כדי לבחור שפה ופריסה"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 9b1032c..b5a9363 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"生体認証または画面ロックの使用"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"本人確認"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"続行するには生体認証を使用してください"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"続行するには指紋認証を使用してください"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"続行するには顔認証を使用してください"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"続行するには、生体認証または画面ロックを使用してください"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"生体認証ハードウェアが利用できません"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"認証をキャンセルしました"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"共有しない"</string>
     <string name="select_input_method" msgid="3971267998568587025">"入力方法の選択"</string>
     <string name="show_ime" msgid="6406112007347443383">"物理キーボードが有効になっていても画面に表示させます"</string>
-    <string name="hardware" msgid="1800597768237606953">"仮想キーボードの表示"</string>
+    <string name="hardware" msgid="3611039921284836033">"画面キーボードの使用"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g>の設定"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"物理キーボードの設定"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"タップして言語とレイアウトを選択してください"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 68b58e9..4c4a521 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"გამოიყენეთ ბიომეტრიული სისტემა ან ეკრანის დაბლოკვა"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"დაადასტურეთ ვინაობა"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"გასაგრძელებლად გამოიყენეთ თქვენი ბიომეტრიული მონაცემები"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"გასაგრძელებლად გამოიყენეთ თქვენი თითის ანაბეჭდი"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"გასაგრძელებლად გამოიყენეთ სახის ამოცნობა"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"გასაგრძელებლად გამოიყენეთ თქვენი ბიომეტრიული მონაცემები ან ეკრანის განბლოკვის ნიმუში"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ბიომეტრიული აპარატურა მიუწვდომელია"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ავტორიზაცია გაუქმდა"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"უარყოფა"</string>
     <string name="select_input_method" msgid="3971267998568587025">"აირჩიეთ შეყვანის მეთოდი"</string>
     <string name="show_ime" msgid="6406112007347443383">"აქტიური ფიზიკური კლავიატურისას ეკრანზე შენარჩუნება"</string>
-    <string name="hardware" msgid="1800597768237606953">"ვირტუალური კლავიატურის ჩვენება"</string>
+    <string name="hardware" msgid="3611039921284836033">"ეკრანული კლავიატურის გამოყენება"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"მოახდინეთ <xliff:g id="DEVICE_NAME">%s</xliff:g>-ის კონფიგურირება"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"მოახდინეთ ფიზიკური კლავიატურების კონფიგურირება"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"შეეხეთ ენისა და განლაგების ასარჩევად"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 764b9ef..33de73c 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Биометриканы немесе экран құлпын пайдалану"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Бұл сіз екеніңізді растаңыз"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Жалғастыру үшін биометрикаңызды пайдаланыңыз."</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Жалғастыру үшін саусақ ізін пайдаланыңыз."</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Жалғастыру үшін бетті анықтау функциясын пайдаланыңыз."</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Жалғастыру үшін биометриканы немесе экран құлпын пайдаланыңыз."</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометрикалық жабдық жоқ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Аутентификациядан бас тартылды."</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ҚАБЫЛДАМАУ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Енгізу әдісін таңдау"</string>
     <string name="show_ime" msgid="6406112007347443383">"Физикалық пернетақта қосулы кезде оны экранға шығару"</string>
-    <string name="hardware" msgid="1800597768237606953">"Виртуалдық пернетақтаны көрсету"</string>
+    <string name="hardware" msgid="3611039921284836033">"Экрандағы пернетақтаны пайдалану"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> конфигурациялау"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Физикалық пернетақталарды конфигурациялау"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Тіл мен пернетақта схемасын таңдау үшін түртіңіз."</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 3b1de2f..347276d 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"ប្រើជីវមាត្រ ឬ​ការចាក់សោអេក្រង់"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"ផ្ទៀងផ្ទាត់ថាជាអ្នក"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"ប្រើជីវមាត្រ​របស់អ្នក ដើម្បីបន្ត"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"ប្រើ​ស្នាមម្រាមដៃ​របស់អ្នក ដើម្បីបន្ត"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"ប្រើមុខរបស់អ្នក ដើម្បីបន្ត"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"ប្រើការចាក់សោអេក្រង់ ឬជីវមាត្ររបស់អ្នក ដើម្បីបន្ត"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"មិនអាច​ប្រើឧបករណ៍​ស្កេន​ស្នាមម្រាមដៃ​បានទេ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"បាន​បោះបង់​ការ​ផ្ទៀងផ្ទាត់"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"បដិសេធ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ជ្រើស​វិធីសាស្ត្រ​បញ្ចូល"</string>
     <string name="show_ime" msgid="6406112007347443383">"ទុកវានៅលើអេក្រង់ខណៈពេលក្តារចុចពិតប្រាកដកំពុងសកម្ម"</string>
-    <string name="hardware" msgid="1800597768237606953">"បង្ហាញក្ដារចុចនិម្មិត"</string>
+    <string name="hardware" msgid="3611039921284836033">"ប្រើក្ដារចុច​លើអេក្រង់"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"កំណត់រចនាសម្ព័ន្ធ <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"កំណត់រចនាសម្ព័ន្ធ​ក្ដារចុចរូបវន្ត"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ប៉ះដើម្បីជ្រើសភាសា និងប្លង់"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index f3d4741..5a3d0e6 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"ಬಯೋಮೆಟ್ರಿಕ್ಸ್ ಅಥವಾ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಅನ್ನು ಬಳಸಿ"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"ಇದು ನೀವೇ ಎಂದು ಪರಿಶೀಲಿಸಿ"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"ಮುಂದುವರಿಸಲು ನಿಮ್ಮ ಬಯೋಮೆಟ್ರಿಕ್ ಬಳಸಿ"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"ಮುಂದುವರಿಸಲು ನಿಮ್ಮ ಫಿಂಗರ್ ಪ್ರಿಂಟ್ ಅನ್ನು ಬಳಸಿ"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"ಮುಂದುವರಿಸಲು ನಿಮ್ಮ ಮುಖವನ್ನು ಬಳಸಿ"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"ಮುಂದುವರಿಸಲು ನಿಮ್ಮ ಬಯೋಮೆಟ್ರಿಕ್ ಅಥವಾ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಅನ್ನು ಬಳಸಿ"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ಬಯೋಮೆಟ್ರಿಕ್ ಹಾರ್ಡ್‌ವೇರ್‌ ಲಭ್ಯವಿಲ್ಲ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ಪ್ರಮಾಣೀಕರಣವನ್ನು ರದ್ದುಗೊಳಿಸಲಾಗಿದೆ"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ನಿರಾಕರಿಸಿ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ಇನ್‌ಪುಟ್‌‌ ವಿಧಾನವನ್ನು ಆರಿಸಿ"</string>
     <string name="show_ime" msgid="6406112007347443383">"ಭೌತಿಕ ಕೀಬೋರ್ಡ್ ಸಕ್ರಿಯವಾಗಿರುವಾಗ ಅದನ್ನು ಪರದೆಯ ಮೇಲಿರಿಸಿ"</string>
-    <string name="hardware" msgid="1800597768237606953">"ವರ್ಚುವಲ್ ಕೀಬೋರ್ಡ್ ತೋರಿಸಿ"</string>
+    <string name="hardware" msgid="3611039921284836033">"ಆನ್-ಸ್ಕ್ರೀನ್ ಕೀಬೋರ್ಡ್ ಬಳಸಿ"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> ಕಾನ್ಫಿಗರ್ ಮಾಡಿ"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ಭೌತಿಕ ಕೀಬೋರ್ಡ್‌ಗಳನ್ನು ಕಾನ್ಫಿಗರ್ ಮಾಡಿ"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ಭಾಷೆ ಮತ್ತು ವಿನ್ಯಾಸವನ್ನು ಆಯ್ಕೆ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index e11d6b2..2460f96 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"생체 인식 또는 화면 잠금을 사용"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"본인 확인"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"생체 인식을 사용하여 계속하세요"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"계속하려면 지문을 인증하세요"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"계속하려면 얼굴로 인증하세요"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"계속하려면 생체 인식이나 화면 잠금을 사용하세요"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"생체 인식 하드웨어를 사용할 수 없음"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"인증이 취소되었습니다."</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"거부"</string>
     <string name="select_input_method" msgid="3971267998568587025">"입력 방법 선택"</string>
     <string name="show_ime" msgid="6406112007347443383">"물리적 키보드가 활성 상태인 경우 화면에 켜 둠"</string>
-    <string name="hardware" msgid="1800597768237606953">"가상 키보드 표시"</string>
+    <string name="hardware" msgid="3611039921284836033">"터치 키보드 사용"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> 설정"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"실제 키보드 설정"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"탭하여 언어와 레이아웃을 선택하세요."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 3624fc8..c11b9ccf 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Биометрикалык жөндөөнү же экрандын кулпусун колдонуу"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Өзүңүздү ырастаңыз"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Улантуу үчүн биометрикалык жөндөөнү колдонуу"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Улантуу үчүн манжаңызды сканерге тийгизиңиз"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Улантуу үчүн жүзүңүздү көрсөтүңүз"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Улантуу үчүн биометрикалык маалыматты же экрандын кулпусун колдонуңуз"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометрикалык аппарат жеткиликсиз"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Аныктыгын текшерүү жокко чыгарылды"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ЧЕТКЕ КАГУУ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Дайын киргизүү ыкмасын тандаңыз"</string>
     <string name="show_ime" msgid="6406112007347443383">"Баскычтоп иштетилгенде экранда көрүнүп турат"</string>
-    <string name="hardware" msgid="1800597768237606953">"Виртуалдык баскычтоп"</string>
+    <string name="hardware" msgid="3611039921284836033">"Экрандагы баскычтопту колдонуу"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> түзмөгүн конфигурациялоо"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Физикалык баскычтопторду конфигурациялоо"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Тил жана калып тандоо үчүн таптап коюңуз"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 1f72574..2d5bdeb 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"ໃຊ້ລະບົບຊີວະມິຕິ ຫຼື ການລັອກໜ້າຈໍ"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"ຢັ້ງຢືນວ່າແມ່ນທ່ານ"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"ໃຊ້ລະບົບຊີວະມິຕິຂອງທ່ານເພື່ອດຳເນີນການຕໍ່"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"ໃຊ້ລາຍນິ້ວມືຂອງທ່ານເພື່ອສືບຕໍ່"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"ໃຊ້ໃບໜ້າຂອງທ່ານເພື່ອສືບຕໍ່"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"ໃຊ້ລະບົບຊີວະມິຕິ ຫຼື ການລັອກໜ້າຈໍຂອງທ່ານເພື່ອດຳເນີນການຕໍ່"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ຮາດແວຊີວະມິຕິບໍ່ສາມາດໃຊ້ໄດ້"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ຍົກເລີກການຮັບຮອງຄວາມຖືກຕ້ອງແລ້ວ"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ປະຕິເສດ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ເລືອກຮູບແບບການປ້ອນ"</string>
     <string name="show_ime" msgid="6406112007347443383">"ເປີດໃຊ້ໃຫ້ມັນຢູ່ໃນໜ້າຈໍໃນຂະນະທີ່ໃຊ້ແປ້ນພິມພາຍນອກຢູ່"</string>
-    <string name="hardware" msgid="1800597768237606953">"ສະແດງແປ້ນພິມສະເໝືອນ"</string>
+    <string name="hardware" msgid="3611039921284836033">"ໃຊ້ແປ້ນພິມໃນໜ້າຈໍ"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"ຕັ້ງຄ່າ <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ຕັ້ງຄ່າແປ້ນພິມແທ້"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ແຕະເພື່ອເລືອກພາສາ ແລະ ໂຄງແປ້ນພິມ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 12321bf..b61ff0c 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -618,8 +618,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Naudoti biometrinius duomenis arba ekrano užraktą"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Patvirtinkite, kad tai jūs"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Norėdami tęsti, naudokite biometrinius duomenis"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Jei norite tęsti, naudokite piršto atspaudą"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Jei norite tęsti, naudokite veido atpažinimo funkciją"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Jei norite tęsti, naudokite biometrinius duomenis arba ekrano užraktą"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrinė aparatinė įranga nepasiekiama"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentifikavimas atšauktas"</string>
@@ -1399,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ATMESTI"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Pasirinkite įvesties metodą"</string>
     <string name="show_ime" msgid="6406112007347443383">"Palikti ekrane, kol fizinė klaviatūra aktyvi"</string>
-    <string name="hardware" msgid="1800597768237606953">"Rodyti virtualiąją klaviatūrą"</string>
+    <string name="hardware" msgid="3611039921284836033">"Ekrano klaviatūros naudojimas"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"„<xliff:g id="DEVICE_NAME">%s</xliff:g>“ konfigūravimas"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Fizinių klaviatūrų konfigūravimas"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Palieskite, kad pasirinktumėte kalbą ir išdėstymą"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 93e2c34..7ea3d59 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Biometrijas vai ekrāna bloķēšanas izmantošana"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Apstipriniet, ka tas esat jūs"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Lai turpinātu, izmantojiet biometriju"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Izmantojiet pirksta nospiedumu, lai turpinātu"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Izmantojiet autorizāciju pēc sejas, lai turpinātu"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Izmantojiet biometrijas datus vai ekrāna bloķēšanas opciju, lai turpinātu"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrisko datu aparatūra nav pieejama"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentifikācija ir atcelta"</string>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"NORAIDĪT"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Ievades metodes izvēle"</string>
     <string name="show_ime" msgid="6406112007347443383">"Paturēt ekrānā, kamēr ir aktīva fiziskā tastatūra"</string>
-    <string name="hardware" msgid="1800597768237606953">"Virtuālās tastatūras rādīšana"</string>
+    <string name="hardware" msgid="3611039921284836033">"Izmantojiet ekrāna tastatūru"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Jākonfigurē <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurējiet fiziskās tastatūras"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Pieskarieties, lai atlasītu valodu un izkārtojumu"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index d09fb15..c4af21f 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Користи биометрика или заклучен екран"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Потврдете дека сте вие"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Користете ја вашата биометрика за да продолжите"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Користете го отпечатокот за да продолжите"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Користете го вашиот лик за да продолжите"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Користете ја вашата биометрика или заклучување екран за да продолжите"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометрискиот хардвер е недостапен"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Проверката е откажана"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ОДБИЈ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Одбери метод на внес"</string>
     <string name="show_ime" msgid="6406112007347443383">"Прикажувај ја на екранот додека е активна физичката тастатура"</string>
-    <string name="hardware" msgid="1800597768237606953">"Прикажи виртуелна тастатура"</string>
+    <string name="hardware" msgid="3611039921284836033">"Користете тастатура на екран"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Конфигурирање на <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Конфигурирање физички тастатури"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Допрете за избирање јазик и распоред"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index c5ceed6..54669fc 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"ബയോമെട്രിക്‌സ് അല്ലെങ്കിൽ സ്‌ക്രീൻ ലോക്ക് ഉപയോഗിക്കുക"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"ഇത് നിങ്ങളാണെന്ന് പരിശോധിച്ചുറപ്പിക്കുക"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"തുടരാൻ ബയോമെട്രിക് ഉപയോഗിക്കുക"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"തുടരാൻ നിങ്ങളുടെ ഫിംഗർപ്രിന്റ് ഉപയോഗിക്കുക"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"തുടരാൻ നിങ്ങളുടെ മുഖം ഉപയോഗിക്കുക"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"തുടരാൻ നിങ്ങളുടെ ബയോമെട്രിക്‌ അല്ലെങ്കിൽ സ്‌ക്രീൻ ലോക്ക് ഉപയോഗിക്കുക"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ബയോമെട്രിക് ഹാർ‌ഡ്‌വെയർ ലഭ്യമല്ല"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"പരിശോധിച്ചുറപ്പിക്കൽ റദ്ദാക്കി"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"നിരസിക്കുക"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ഇൻപുട്ട് രീതി തിരഞ്ഞെടുക്കുക"</string>
     <string name="show_ime" msgid="6406112007347443383">"ഫിസിക്കൽ കീബോർഡ് സജീവമായിരിക്കുമ്പോൾ സ്ക്രീനിൽ നിലനിർത്തുക"</string>
-    <string name="hardware" msgid="1800597768237606953">"വെർച്വൽ കീബോർഡ് കാണിക്കുക"</string>
+    <string name="hardware" msgid="3611039921284836033">"ഓൺ-സ്ക്രീൻ കീബോർഡ് ഉപയോഗിക്കൂ"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> കോൺഫിഗർ ചെയ്യുക"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"യഥാർത്ഥ കീബോർഡുകൾ കോൺഫിഗർ ചെയ്യുക"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ഭാഷയും ലേഔട്ടും തിരഞ്ഞെടുക്കുന്നതിന് ടാപ്പ് ചെയ്യുക"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index bfed4b6..ad4830c 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Биометр эсвэл дэлгэцийн түгжээ ашиглах"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Өөрийгөө мөн гэдгийг баталгаажуулаарай"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Үргэлжлүүлэхийн тулд биометрээ ашиглана уу"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Үргэлжлүүлэхийн тулд хурууныхаа хээг ашиглана уу"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Үргэлжлүүлэхийн тулд царайгаа ашиглана уу"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Үргэлжлүүлэхийн тулд биометр эсвэл дэлгэцийн түгжээгээ ашиглана уу"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометрийн техник хангамж боломжгүй байна"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Нотолгоог цуцаллаа"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ТАТГАЛЗАХ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Оруулах аргыг сонгоно уу"</string>
     <string name="show_ime" msgid="6406112007347443383">"Биет гар идэвхтэй үед үүнийг дэлгэцэд харуулна уу"</string>
-    <string name="hardware" msgid="1800597768237606953">"Хийсвэр гарыг харуулах"</string>
+    <string name="hardware" msgid="3611039921284836033">"Дэлгэц дээрх гарыг ашиглах"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g>-г тохируулна уу"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Биет гарыг тохируулна уу"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Хэл болон бүдүүвчийг сонгохын тулд дарна уу"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index aa0f3c7..1b6efd8 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"बायोमेट्रिक किंवा स्क्रीन लॉक वापरा"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"हे तुम्हीच आहात याची पडताळणी करा"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"पुढे सुरू ठेवण्यासाठी तुमचे बायोमेट्रिक वापरा"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"पुढे सुरू ठेवण्‍यासाठी तुमची फिंगरप्रिंट वापरा"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"पुढे सुरू ठेवण्यासाठी तुमचा चेहरा वापरा"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"पुढे सुरू ठेवण्यासाठी तुमचे बायोमेट्रिक किंवा स्क्रीन लॉक वापरा"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"बायोमेट्रिक हार्डवेअर उपलब्ध नाही"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ऑथेंटिकेशन रद्द केले"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"नकार द्या"</string>
     <string name="select_input_method" msgid="3971267998568587025">"इनपुट पद्धत निवडा"</string>
     <string name="show_ime" msgid="6406112007347443383">"भौतिक कीबोर्ड सक्रिय असताना त्यास स्क्रीनवर ठेवा"</string>
-    <string name="hardware" msgid="1800597768237606953">"व्हर्च्युअल कीबोर्ड दर्शवा"</string>
+    <string name="hardware" msgid="3611039921284836033">"ऑन-स्क्रीन कीबोर्ड वापरा"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> कॉंफिगर करा"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"वास्तविक कीबोर्ड कॉंफिगर करा"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"भाषा आणि लेआउट निवडण्यासाठी टॅप करा"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 5dbf2a7..13bb503 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Gunakan biometrik atau kunci skrin"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Sahkan itu anda"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Gunakan biometrik anda untuk meneruskan"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Gunakan cap jari anda untuk teruskan"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Gunakan muka anda untuk teruskan"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Gunakan biometrik atau kunci skrin anda untuk meneruskan pengesahan"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Perkakasan biometrik tidak tersedia"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Pengesahan dibatalkan"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"TOLAK"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Pilih kaedah input"</string>
     <string name="show_ime" msgid="6406112007347443383">"Pastikannya pada skrin, semasa papan kekunci fizikal aktif"</string>
-    <string name="hardware" msgid="1800597768237606953">"Tunjukkan papan kekunci maya"</string>
+    <string name="hardware" msgid="3611039921284836033">"Guna papan kekunci pada skrin"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurasikan <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurasikan papan kekunci fizikal"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Ketik untuk memilih bahasa dan reka letak"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index c499cc4..0650586 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"ဇီဝမက်ထရစ်အချက်အလက်များ (သို့) ဖန်သားပြင်လော့ခ်ချခြင်းကို သုံးခြင်း"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"သင်ဖြစ်ကြောင်း အတည်ပြုပါ"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"ရှေ့ဆက်ရန် သင်၏ ဇီဝမက်ထရစ်အချက်အလက်ကို သုံးပါ"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"ရှေ့ဆက်ရန် သင့်လက်ဗွေကို သုံးပါ"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"ရှေ့ဆက်ရန် သင့်မျက်နှာကို သုံးပါ"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"ရှေ့ဆက်ရန် သင်၏ ဇီဝမက်ထရစ်အချက်အလက် (သို့) ဖန်သားပြင်လော့ခ်ကို သုံးပါ"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ဇီဝအချက်အလက်သုံး ကွန်ပျူတာစက်ပစ္စည်း မရရှိနိုင်ပါ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"အထောက်အထားစိစစ်ခြင်းကို ပယ်ဖျက်လိုက်သည်"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ငြင်းပယ်ပါ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ထည့်သွင်းရေး နည်းကို ရွေးရန်"</string>
     <string name="show_ime" msgid="6406112007347443383">"စက်၏ကီးဘုတ် ဖွင့်ထားစဉ်တွင် ၎င်းကို ဖန်သားပြင်ပေါ်တွင် ဆက်ထားပါ"</string>
-    <string name="hardware" msgid="1800597768237606953">"ပကတိအသွင်ကီးဘုတ်ပြရန်"</string>
+    <string name="hardware" msgid="3611039921284836033">"မျက်နှာပြင် လက်ကွက် သုံးခြင်း"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> ကို စီစဉ်သတ်မှတ်ရန်"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ပကတိကီးဘုတ်များကို စီစဉ်သတ်မှတ်ရန်"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ဘာသာစကားနှင့် အသွင်အပြင်ရွေးချယ်ရန် တို့ပါ"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 582da2a..e157d17 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Bruk biometri eller skjermlås"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Bekreft at det er deg"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Bruk biometri for å fortsette"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Bruk fingeravtrykket for å fortsette"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Bruk ansiktet for å fortsette"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Bruk biometri eller skjermlåsen for å fortsette"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrisk maskinvare er utilgjengelig"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentiseringen er avbrutt"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"AVSLÅ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Velg inndatametode"</string>
     <string name="show_ime" msgid="6406112007347443383">"Ha den på skjermen mens det fysiske tastaturet er aktivt"</string>
-    <string name="hardware" msgid="1800597768237606953">"Vis det virtuelle tastaturet"</string>
+    <string name="hardware" msgid="3611039921284836033">"Bruk skjermtastaturet"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurer <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurer de fysiske tastaturene"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Trykk for å velge språk og layout"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index e3ad288..45bb058 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"बायोमेट्रिक्स वा स्क्रिन लक प्रयोग गर्नुहोस्"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"यो व्यक्ति तपाईं नै हो भन्ने प्रमाणित गर्नुहोस्"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"जारी राख्न आफ्नो बायोमेट्रिक प्रयोग गर्नुहोस्"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"जारी राख्न आफ्नो फिंगरप्रिन्ट प्रयोग गर्नुहोस्"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"जारी राख्न आफ्नो अनुहार प्रयोग गर्नुहोस्"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"जारी राख्न आफ्नो बायोमेट्रिक वा स्क्रिन लक प्रयोग गरी पुष्टि गर्नुहोस्"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"बायोमेट्रिक हार्डवेयर उपलब्ध छैन"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"प्रमाणीकरण रद्द गरियो"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"अस्वीकार गर्नुहोस्"</string>
     <string name="select_input_method" msgid="3971267998568587025">"निवेश विधि छान्नुहोस्"</string>
     <string name="show_ime" msgid="6406112007347443383">"फिजिकल किबोर्ड सक्रिय हुँदा यसलाई स्क्रिनमा राखियोस्"</string>
-    <string name="hardware" msgid="1800597768237606953">"भर्चुअल किबोर्ड देखाउनुहोस्"</string>
+    <string name="hardware" msgid="3611039921284836033">"अनस्क्रिन किबोर्ड प्रयोग गर्नुहोस्"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> कन्फिगर गर्नुहोस्"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"भौतिक किबोर्डहरू कन्फिगर गर्नुहोस्"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"भाषा र लेआउट चयन गर्न ट्याप गर्नुहोस्"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 813a067..14f4919 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Biometrische gegevens of schermvergrendeling gebruiken"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Je identiteit verifiëren"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Gebruik je biometrische gegevens om door te gaan"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Gebruik je vingerafdruk om door te gaan"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Gebruik je gezicht om door te gaan"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Gebruik je biometrische gegevens of schermvergrendeling om door te gaan"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrische hardware niet beschikbaar"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Verificatie geannuleerd"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"WEIGEREN"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Invoermethode selecteren"</string>
     <string name="show_ime" msgid="6406112007347443383">"Toon op het scherm terwijl het fysieke toetsenbord actief is"</string>
-    <string name="hardware" msgid="1800597768237606953">"Virtueel toetsenbord tonen"</string>
+    <string name="hardware" msgid="3611039921284836033">"Schermtoetsenbord gebruiken"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> instellen"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Fysieke toetsenborden instellen"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tik om een taal en indeling te selecteren"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index b050650..5b333c1 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"ବାୟୋମେଟ୍ରିକ୍ସ ବା ସ୍କ୍ରିନ୍ ଲକ୍ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"ଏହା ଆପଣ ବୋଲି ଯାଞ୍ଚ କରନ୍ତୁ"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"ଜାରି ରଖିବାକୁ ଆପଣଙ୍କ ବାୟୋମେଟ୍ରିକ୍ସ ବ୍ୟବହାର କରନ୍ତୁ"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"ଜାରି ରଖିବାକୁ ଆପଣଙ୍କ ଟିପଚିହ୍ନ ବ୍ୟବହାର କରନ୍ତୁ"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"ଜାରି ରଖିବାକୁ ଆପଣଙ୍କ ଫେସ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"ଜାରି ରଖିବାକୁ ଆପଣଙ୍କ ବାୟୋମେଟ୍ରିକ୍ କିମ୍ବା ସ୍କ୍ରିନ୍ ଲକ୍ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ବାୟୋମେଟ୍ରିକ୍‌ ହାର୍ଡୱେର୍‌ ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ପ୍ରାମାଣିକତାକୁ ବାତିଲ୍ କରାଯାଇଛି"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ଅଗ୍ରାହ୍ୟ କରନ୍ତୁ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ଇନପୁଟ୍ ପଦ୍ଧତି ବାଛନ୍ତୁ"</string>
     <string name="show_ime" msgid="6406112007347443383">"ଫିଜିକାଲ୍‌ କୀବୋର୍ଡ ସକ୍ରିୟ ଥିବାବେଳେ ଏହାକୁ ସ୍କ୍ରିନ୍‌ ଉପରେ ରଖନ୍ତୁ"</string>
-    <string name="hardware" msgid="1800597768237606953">"ଭର୍ଚୁଆଲ୍ କୀ’ବୋର୍ଡ ଦେଖାନ୍ତୁ"</string>
+    <string name="hardware" msgid="3611039921284836033">"ଅନ-ସ୍କ୍ରିନ କୀବୋର୍ଡ ବ୍ୟବହାର କର"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g>କୁ କନଫିଗର କରନ୍ତୁ"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ଫିଜିକାଲ କୀବୋର୍ଡଗୁଡ଼ିକୁ କନଫିଗର କରନ୍ତୁ"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ଭାଷା ଓ ଲେଆଉଟ ଚୟନ କରିବା ପାଇଁ ଟାପ କରନ୍ତୁ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index fec2958..c8e8d9a 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"ਬਾਇਓਮੈਟ੍ਰਿਕ ਜਾਂ ਸਕ੍ਰੀਨ ਲਾਕ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"ਆਪਣੀ ਪਛਾਣ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"ਜਾਰੀ ਰੱਖਣ ਲਈ ਆਪਣੇ ਬਾਇਓਮੈਟ੍ਰਿਕ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"ਜਾਰੀ ਰੱਖਣ ਲਈ ਆਪਣੇ ਫਿੰਗਰਪ੍ਰਿੰਟ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"ਜਾਰੀ ਰੱਖਣ ਲਈ ਆਪਣੇ ਚਿਹਰੇ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"ਜਾਰੀ ਰੱਖਣ ਲਈ ਆਪਣਾ ਬਾਇਓਮੈਟ੍ਰਿਕ ਜਾਂ ਸਕ੍ਰੀਨ ਲਾਕ ਵਰਤੋ"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ਬਾਇਓਮੈਟ੍ਰਿਕ ਹਾਰਡਵੇਅਰ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ਪ੍ਰਮਾਣੀਕਰਨ ਰੱਦ ਕੀਤਾ ਗਿਆ"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ਅਸਵੀਕਾਰ ਕਰੋ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ਇਨਪੁਟ ਵਿਧੀ ਚੁਣੋ"</string>
     <string name="show_ime" msgid="6406112007347443383">"ਭੌਤਿਕ ਕੀ-ਬੋਰਡ ਸਰਗਰਮ ਹੋਣ ਦੌਰਾਨ ਇਸ ਨੂੰ ਸਕ੍ਰੀਨ \'ਤੇ ਬਣਾਈ ਰੱਖੋ"</string>
-    <string name="hardware" msgid="1800597768237606953">"ਆਭਾਸੀ ਕੀ-ਬੋਰਡ ਦਿਖਾਓ"</string>
+    <string name="hardware" msgid="3611039921284836033">"ਆਨ-ਸਕ੍ਰੀਨ ਕੀ-ਬੋਰਡ ਨੂੰ ਵਰਤੋ"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> ਦਾ ਸੰਰੂਪਣ ਕਰੋ"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ਭੌਤਿਕ ਕੀ-ਬੋਰਡਾਂ ਦਾ ਸੰਰੂਪਣ ਕਰੋ"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"ਭਾਸ਼ਾ ਅਤੇ ਖਾਕਾ ਚੁਣਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 866f3e6..5f308d8 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -618,8 +618,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Używaj biometrii lub blokady ekranu"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Potwierdź, że to Ty"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Użyj biometrii, by kontynuować"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Aby kontynuować, użyj odcisku palca"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Aby kontynuować, użyj rozpoznawania twarzy"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Aby kontynuować, użyj biometrii lub blokady ekranu"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Sprzęt biometryczny niedostępny"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Anulowano uwierzytelnianie"</string>
@@ -1399,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ODRZUĆ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Wybierz metodę wprowadzania"</string>
     <string name="show_ime" msgid="6406112007347443383">"Pozostaw na ekranie, gdy aktywna jest klawiatura fizyczna"</string>
-    <string name="hardware" msgid="1800597768237606953">"Pokaż klawiaturę wirtualną"</string>
+    <string name="hardware" msgid="3611039921284836033">"Używaj klawiatury ekranowej"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Skonfiguruj urządzenie <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Skonfiguruj klawiatury fizyczne"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Kliknij, aby wybrać język i układ"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index bec25c0..2af7768 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Usar biometria ou bloqueio de tela"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Confirme que é você"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Use seus dados biométricos para continuar"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Use sua impressão digital para continuar"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Use seu rosto para continuar"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Use sua autenticação biométrica ou o bloqueio de tela para continuar"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biométrico indisponível"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autenticação cancelada"</string>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RECUSAR"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Selecione o método de entrada"</string>
     <string name="show_ime" msgid="6406112007347443383">"Mantém o teclado virtual na tela enquanto o teclado físico está ativo"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostrar teclado virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Usar teclado na tela"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configure o dispositivo <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configure teclados físicos"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Toque para selecionar o idioma e o layout"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 39a377bc..42d1f6a 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Usar a biometria ou o bloqueio de ecrã"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Confirme a sua identidade"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Utilize a biometria para continuar."</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Use a impressão digital para continuar"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Use o rosto para continuar"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Utilize a biometria ou o bloqueio de ecrã para continuar"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biométrico indisponível."</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autenticação cancelada"</string>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RECUSAR"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Escolher o método de entrada"</string>
     <string name="show_ime" msgid="6406112007347443383">"Manter no ecrã enquanto o teclado físico estiver ativo"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostrar o teclado virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Usar o teclado no ecrã"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configure o dispositivo <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configure teclados físicos"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Toque para selecionar o idioma e o esquema"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index bec25c0..2af7768 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Usar biometria ou bloqueio de tela"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Confirme que é você"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Use seus dados biométricos para continuar"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Use sua impressão digital para continuar"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Use seu rosto para continuar"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Use sua autenticação biométrica ou o bloqueio de tela para continuar"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biométrico indisponível"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autenticação cancelada"</string>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RECUSAR"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Selecione o método de entrada"</string>
     <string name="show_ime" msgid="6406112007347443383">"Mantém o teclado virtual na tela enquanto o teclado físico está ativo"</string>
-    <string name="hardware" msgid="1800597768237606953">"Mostrar teclado virtual"</string>
+    <string name="hardware" msgid="3611039921284836033">"Usar teclado na tela"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configure o dispositivo <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configure teclados físicos"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Toque para selecionar o idioma e o layout"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 629db1b..97e66c9 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Folosește sistemele biometrice sau blocarea ecranului"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Confirmă-ți identitatea"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Folosește sistemele biometrice pentru a continua"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Folosește amprenta pentru a continua"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Folosește-ți chipul pentru a continua"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Folosește sistemele biometrice sau blocarea ecranului pentru a continua"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometric indisponibil"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentificarea a fost anulată"</string>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"REFUZ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Alege metoda de introducere de text"</string>
     <string name="show_ime" msgid="6406112007347443383">"Se păstrează pe ecran cât timp este activată tastatura fizică"</string>
-    <string name="hardware" msgid="1800597768237606953">"Afișează tastatura virtuală"</string>
+    <string name="hardware" msgid="3611039921284836033">"Folosește tastatura pe ecran"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Configurează <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Configurează tastaturi fizice"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Atinge pentru a selecta limba și aspectul"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index de58239..a1e1e03 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -618,8 +618,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Использовать биометрию или блокировку экрана"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Подтвердите, что это вы"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Чтобы продолжить, используйте биометрические данные."</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Чтобы продолжить, прикоснитесь пальцем к сканеру."</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Чтобы продолжить, используйте фейсконтроль."</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Чтобы продолжить, используйте биометрию или данные для разблокировки экрана."</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометрическое оборудование недоступно"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Аутентификация отменена"</string>
@@ -1399,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ОТКЛОНИТЬ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Выберите способ ввода"</string>
     <string name="show_ime" msgid="6406112007347443383">"Не скрывать экранную клавиатуру, когда включена физическая"</string>
-    <string name="hardware" msgid="1800597768237606953">"Виртуальная клавиатура"</string>
+    <string name="hardware" msgid="3611039921284836033">"Использовать экранную клавиатуру"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Настройте устройство \"<xliff:g id="DEVICE_NAME">%s</xliff:g>\""</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Настройте физические клавиатуры"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Нажмите, чтобы выбрать язык и раскладку"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 6c310dc..c4c0aa7 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"ජෛවමිතික හෝ තිර අගුල භාවිත කරන්න"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"එය ඔබ බව තහවුරු කරන්න"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"ඉදිරියට යාමට ඔබගේ ජෛවමිතික භාවිත කරන්න"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"ඉදිරියට යාමට ඔබේ ඇඟිලි සලකුණ භාවිත කරන්න"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"ඉදිරියට යාමට ඔබේ මුහුණ භාවිත කරන්න"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"ඉදිරියට යාමට ඔබගේ ජෛවමිතික හෝ තිර අගුල භාවිත කරන්න"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ජීවමිතික දෘඪාංග ලබා ගත නොහැකිය"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"සත්‍යාපනය අවලංගු කළා"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ප්‍රතික්ෂේප කරන්න"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ආදාන ක්‍රමයක් තෝරන්න"</string>
     <string name="show_ime" msgid="6406112007347443383">"භෞතික යතුරු පුවරුව සක්‍රිය අතරතුර එය තිරය මත තබා ගන්න"</string>
-    <string name="hardware" msgid="1800597768237606953">"අතථ්‍ය යතුරු පුවරුව පෙන්වන්න"</string>
+    <string name="hardware" msgid="3611039921284836033">"තිරය මත යතුරු පුවරුව භාවිතය"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> වින්‍යාස කරන්න"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"භෞතික යතුරුපුවරුව වින්‍යාස කරන්න"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"භාෂාව හා පිරිසැලසුම තේරීමට තට්ටු කරන්න"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 594218b..4b31793 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -618,8 +618,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Použiť biometrické údaje alebo zámku obrazovky"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Overenie, že ste to vy"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Ak chcete pokračovať, použite biometrický údaj"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Pokračujte nasnímaním odtlačku prsta"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Pokračujte nasnímaním tváre"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Pokračujte použitím biometrických údajov alebo zámky obrazovky"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrický hardvér nie je k dispozícii"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Overenie bolo zrušené"</string>
@@ -1399,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ODMIETNUŤ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Zvoliť metódu vstupu"</string>
     <string name="show_ime" msgid="6406112007347443383">"Ponechať na obrazovke, keď je aktívna fyzická klávesnica"</string>
-    <string name="hardware" msgid="1800597768237606953">"Zobraziť virtuálnu klávesnicu"</string>
+    <string name="hardware" msgid="3611039921284836033">"Použiť klávesnicu na obrazovke"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Nakonfigurujte <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Nakonfigurujte fyzické klávesnice"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Klepnutím vyberte jazyk a rozloženie"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index a2b775d..630df53 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -618,8 +618,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Uporaba biometrike ali odklepanja s poverilnico"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Preverite, da ste res vi"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Za nadaljevanje uporabite biometrični podatek."</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Za nadaljevanje uporabite prstni odtis"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Za nadaljevanje uporabite obraz"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Za nadaljevanje uporabite biometrični podatek ali odklepanje s poverilnico."</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Strojna oprema za biometrične podatke ni na voljo"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Preverjanje pristnosti je preklicano"</string>
@@ -1399,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"NE SPREJMEM"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Izberite način vnosa"</string>
     <string name="show_ime" msgid="6406112007347443383">"Ohrani na zaslonu, dokler je aktivna fizična tipkovnica"</string>
-    <string name="hardware" msgid="1800597768237606953">"Pokaži navidezno tipkovnico"</string>
+    <string name="hardware" msgid="3611039921284836033">"Uporaba zaslonske tipkovnice"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfiguriranje naprave <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfiguriranje fizičnih tipkovnic"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Dotaknite se, če želite izbrati jezik in postavitev."</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index d143b00..7a89279 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Përdor sistemet biometrike ose kyçjen e ekranit"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verifiko që je ti"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Përdor sistemet e tua biometrike për të vazhduar"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Përdor gjurmën e gishtit për të vazhduar"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Përdor fytyrën tënde për të vazhduar"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Përdor sistemet e tua biometrike ose kyçjen e ekranit për të vazhduar"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Nuk ofrohet harduer biometrik"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Vërtetimi u anulua"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"REFUZO"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Zgjidh metodën e hyrjes"</string>
     <string name="show_ime" msgid="6406112007347443383">"Mbaje në ekran ndërsa tastiera fizike është aktive"</string>
-    <string name="hardware" msgid="1800597768237606953">"Shfaq tastierën virtuale"</string>
+    <string name="hardware" msgid="3611039921284836033">"Përdor tastierën në ekran"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfiguro <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfiguro tastierat fizike"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Trokit për të zgjedhur gjuhën dhe strukturën"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index f16f5ae..27886aa 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -617,8 +617,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Користите биометрију или закључавање екрана"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Потврдите свој идентитет"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Користите биометријски податак да бисте наставили"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Наставите помоћу отиска прста"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Потврдите идентитет лицем да бисте наставили"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Користите биометријски податак или закључавање екрана да бисте наставили"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Биометријски хардвер није доступан"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Потврда идентитета је отказана"</string>
@@ -1398,7 +1396,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ОДБИЈ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Избор метода уноса"</string>
     <string name="show_ime" msgid="6406112007347443383">"Задржава се на екрану док је физичка тастатура активна"</string>
-    <string name="hardware" msgid="1800597768237606953">"Прикажи виртуелну тастатуру"</string>
+    <string name="hardware" msgid="3611039921284836033">"Користи тастатуру на екрану"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Конфигуришите уређај <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Конфигуришите физичке тастатуре"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Додирните да бисте изабрали језик и распоред"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 3977522..fad1d52 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Använd biometrisk data eller skärmlåset"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verifiera din identitet"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Fortsätt med hjälp av din biometriska data"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Fortsätt med hjälp av ditt fingeravtryck"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Fortsätt med hjälp av ditt ansikte"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Fortsätt med hjälp av din biometriska data eller skärmlåset"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrisk maskinvara är inte tillgänglig"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentiseringen avbröts"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"AVVISA"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Välj inmatningsmetod"</string>
     <string name="show_ime" msgid="6406112007347443383">"Ha kvar det på skärmen när det fysiska tangentbordet används"</string>
-    <string name="hardware" msgid="1800597768237606953">"Visa virtuellt tangentbord"</string>
+    <string name="hardware" msgid="3611039921284836033">"Använd skärmtangentbord"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Konfigurera <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Konfigurera fysiska tangentbord"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Tryck om du vill välja språk och layout"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index fdaf1bb..cd6d698 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Tumia bayometriki au mbinu ya kufunga skrini"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Thibitisha kuwa ni wewe"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Tumia bayometriki yako ili uendelee"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Tumia alama ya kidole chako ili uendelee"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Tumia uso wako ili uendelee"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Tumia bayometriki au mbinu yako ya kufunga skrini ili uendelee"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Maunzi ya bayometriki hayapatikani"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Imeghairi uthibitishaji"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"KATAA"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Chagua njia ya ingizo"</string>
     <string name="show_ime" msgid="6406112007347443383">"Ionyeshe kwenye skrini wakati kibodi halisi inatumika"</string>
-    <string name="hardware" msgid="1800597768237606953">"Onyesha kibodi pepe"</string>
+    <string name="hardware" msgid="3611039921284836033">"Tumia kibodi ya skrini"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Wekea mipangilio <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Wekea kibodi halisi mipangilio"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Gusa ili uchague lugha na muundo"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index a84ff14..518b786 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"பயோமெட்ரிக்ஸையோ திரைப் பூட்டையோ பயன்படுத்து"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"நீங்கள்தான் என உறுதிசெய்க"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"தொடர உங்கள் பயோமெட்ரிக்கைப் பயன்படுத்துங்கள்"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"தொடர்வதற்கு உங்கள் கைரேகையைப் பயன்படுத்துங்கள்"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"தொடர்வதற்கு உங்கள் முகத்தைப் பயன்படுத்துங்கள்"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"தொடர, உங்கள் பயோமெட்ரிக்கையோ திரைப் பூட்டையோ பயன்படுத்துங்கள்"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"பயோமெட்ரிக் வன்பொருள் இல்லை"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"அங்கீகரிப்பு ரத்தானது"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"வேண்டாம்"</string>
     <string name="select_input_method" msgid="3971267998568587025">"உள்ளீட்டு முறையைத் தேர்வுசெய்க"</string>
     <string name="show_ime" msgid="6406112007347443383">"கைமுறை கீபோர்டு இயக்கத்தில் இருக்கும் போது IMEஐ திரையில் வைத்திரு"</string>
-    <string name="hardware" msgid="1800597768237606953">"விர்ச்சுவல் கீபோர்டை காட்டு"</string>
+    <string name="hardware" msgid="3611039921284836033">"ஸ்கிரீன் கீபோர்டை உபயோகி"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> சாதனத்தை உள்ளமைத்தல்"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"கீபோர்டுகளை உள்ளமைத்தல்"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"மொழியையும் தளவமைப்பையும் தேர்ந்தெடுக்க, தட்டவும்"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 059b6b9..3cf0609 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"బయోమెట్రిక్స్‌ను లేదా స్క్రీన్ లాక్‌ను ఉపయోగించండి"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"ఇది మీరేనని వెరిఫై చేసుకోండి"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"కొనసాగించడానికి, మీ బయోమెట్రిక్‌ను ఉపయోగించండి"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"కొనసాగించడానికి మీ వేలిముద్రను ఉపయోగించండి"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"కొనసాగించడానికి మీ ముఖాన్ని ఉపయోగించండి"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"కొనసాగించడానికి మీ బయోమెట్రిక్ లేదా స్క్రీన్ లాక్‌ను ఉపయోగించండి"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"బయోమెట్రిక్ హార్డ్‌వేర్‌ అందుబాటులో లేదు"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ప్రమాణీకరణ రద్దు చేయబడింది"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"తిరస్కరిస్తున్నాను"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ఇన్‌పుట్ పద్ధతిని ఎంచుకోండి"</string>
     <string name="show_ime" msgid="6406112007347443383">"దీన్ని భౌతిక కీబోర్డ్ యాక్టివ్‌గా ఉన్నప్పుడు స్క్రీన్‌పై ఉంచుతుంది"</string>
-    <string name="hardware" msgid="1800597768237606953">"వర్చువల్ కీబోర్డ్‌ను చూపు"</string>
+    <string name="hardware" msgid="3611039921284836033">"స్క్రీన్‌పై కీబోర్డ్ ఉపయోగించు"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g>‌ను కాన్ఫిగర్ చేయండి"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"ఫిజికల్ కీబోర్డ్‌లను కాన్ఫిగ‌ర్ చేయండి"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"భాషను, లేఅవుట్‌ను ఎంచుకోవడానికి ట్యాప్ చేయండి"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index c2494d9..f2ced77 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"ใช้ข้อมูลไบโอเมตริกหรือการล็อกหน้าจอ"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"ยืนยันว่าเป็นตัวคุณ"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"ใช้ข้อมูลไบโอเมตริกเพื่อดำเนินการต่อ"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"ใช้ลายนิ้วมือของคุณเพื่อดำเนินการต่อ"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"ใช้ใบหน้าของคุณเพื่อดำเนินการต่อ"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"ใช้ข้อมูลไบโอเมตริกหรือการล็อกหน้าจอเพื่อดำเนินการต่อ"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ฮาร์ดแวร์ไบโอเมตริกไม่พร้อมใช้งาน"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"ยกเลิกการตรวจสอบสิทธิ์แล้ว"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ปฏิเสธ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"เลือกวิธีการป้อนข้อมูล"</string>
     <string name="show_ime" msgid="6406112007347443383">"เปิดทิ้งไว้บนหน้าจอในระหว่างใช้งานแป้นพิมพ์จริง"</string>
-    <string name="hardware" msgid="1800597768237606953">"แสดงแป้นพิมพ์เสมือน"</string>
+    <string name="hardware" msgid="3611039921284836033">"ใช้แป้นพิมพ์บนหน้าจอ"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"กำหนดค่า <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"กำหนดค่าแป้นพิมพ์จริง"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"แตะเพื่อเลือกภาษาและรูปแบบ"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index b6116e2..826e2b0 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Gumamit ng biometrics o lock ng screen"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"I-verify na ikaw ito"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Gamitin ang iyong biometric para magpatuloy"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Gamitin ang iyong fingerprint para magpatuloy"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Gamitin ang iyong mukha para magpatuloy"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Gamitin ang iyong biometric o lock ng screen para magpatuloy"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Walang biometric hardware"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Nakansela ang pag-authenticate"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"TANGGIHAN"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Pumili ng pamamaraan ng pag-input"</string>
     <string name="show_ime" msgid="6406112007347443383">"Panatilihin ito sa screen habang aktibo ang pisikal na keyboard"</string>
-    <string name="hardware" msgid="1800597768237606953">"Ipakita ang virtual keyboard"</string>
+    <string name="hardware" msgid="3611039921284836033">"Gumamit ng on-screen keyboard"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"I-configure ang <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"I-configure ang mga pisikal na keyboard"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"I-tap upang pumili ng wika at layout"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 44499c5..3ab7339 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Biyometri veya ekran kilidi kullan"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Siz olduğunuzu doğrulayın"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Devam etmek için biyometri kullanın"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Devam etmek için parmak izinizi kullanın"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Devam etmek için yüzünüzü kullanın"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Devam etmek için biyometrik kimlik bilginizi veya ekran kilidinizi kullanın"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biyometrik donanım kullanılamıyor"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Kimlik doğrulama iptal edildi"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"REDDET"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Giriş yöntemini seçin"</string>
     <string name="show_ime" msgid="6406112007347443383">"Fiziksel klavye etkin durumdayken ekranda tut"</string>
-    <string name="hardware" msgid="1800597768237606953">"Sanal klavyeyi göster"</string>
+    <string name="hardware" msgid="3611039921284836033">"Ekran klavyesi kullanın"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> ayarlarını yapılandır"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Fiziksel klavyeleri yapılandırın"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Dili ve düzeni seçmek için dokunun"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 74eaf03..40fca1c 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -618,8 +618,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Використовувати біометрію або дані для розблокування екрана"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Підтвердьте, що це ви"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Щоб продовжити, скористайтеся біометрією"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Щоб продовжити, скористайтеся відбитком пальця"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Щоб продовжити, скористайтеся фейс-контролем"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Щоб продовжити, скористайтеся біометрією або даними для розблокування екрана"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Біометричне апаратне забезпечення недоступне"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Автентифікацію скасовано"</string>
@@ -1399,7 +1397,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"ВІДХИЛИТИ"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Вибрати метод введення"</string>
     <string name="show_ime" msgid="6406112007347443383">"Утримуйте на екрані, коли активна фізична клавіатура"</string>
-    <string name="hardware" msgid="1800597768237606953">"Показати віртуальну клавіатуру"</string>
+    <string name="hardware" msgid="3611039921284836033">"Екранна клавіатура"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Налаштуйте клавіатуру \"<xliff:g id="DEVICE_NAME">%s</xliff:g>\""</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Налаштуйте фізичні клавіатури"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Торкніться, щоб вибрати мову та розкладку"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 7a52a09..94b1c51 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"بایو میٹرکس یا اسکرین لاک استعمال کریں"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"توثیق کریں کہ یہ آپ ہیں"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"جاری رکھنے کیلئے اپنا بایو میٹرک استعمال کریں"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"جاری رکھنے کے لیے اپنا فنگر پرنٹ استعمال کریں"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"جاری رکھنے کے لیے اپنے چہرے کا استعمال کریں"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"جاری رکھنے کے لیے اپنے بایو میٹرک اور اسکرین لاک کا استعمال کریں"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"بایومیٹرک ہارڈ ویئر دستیاب نہیں ہے"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"تصدیق کا عمل منسوخ ہو گیا"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"مسترد کریں"</string>
     <string name="select_input_method" msgid="3971267998568587025">"ان پٹ کا طریقہ منتخب کریں"</string>
     <string name="show_ime" msgid="6406112007347443383">"‏جب فزیکل کی بورڈ فعال ہو تو IME کو اسکرین پر رکھیں"</string>
-    <string name="hardware" msgid="1800597768237606953">"ورچوئل کی بورڈ دکھائیں"</string>
+    <string name="hardware" msgid="3611039921284836033">"آن اسکرین کی بورڈ استعمال کریں"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"<xliff:g id="DEVICE_NAME">%s</xliff:g> کنفیگر کریں"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"فزیکل کی بورڈز کنفیگر کریں"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"زبان اور لے آؤٹ منتخب کرنے کیلئے تھپتھپائیں"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 58fd9da..bf0d60d 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Biometrika yoki ekran qulfi"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Oʻzingizni taniting"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Davom etish uchun biometrik tasdiqlang"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Davom etish uchun barmoq izingizdan foydalaning"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Yuz tekshiruvi bilan davom eting"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Davom etish uchun biometrika yoki ekran qulfidan foydalaning"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrik sensor ishlamayapti"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentifikatsiya bekor qilindi"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"RAD ETISH"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Matn kiritish usulini tanlang"</string>
     <string name="show_ime" msgid="6406112007347443383">"Tashqi klaviatura ulanganida ekranda chiqib turadi"</string>
-    <string name="hardware" msgid="1800597768237606953">"Virtual klaviatura"</string>
+    <string name="hardware" msgid="3611039921284836033">"Ekrandagi klaviatura"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Sozlang: <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Tashqi klaviaturalarni sozlang"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Til va sxemani belgilash uchun bosing"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index ba8d410..177bf89 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Dùng dữ liệu sinh trắc học hoặc phương thức khóa màn hình"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Xác minh danh tính của bạn"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Dùng dữ liệu sinh trắc học của bạn để tiếp tục"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Hãy dùng vân tay để tiếp tục"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Hãy dùng khuôn mặt để tiếp tục"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Dùng dữ liệu sinh trắc học của bạn hoặc phương thức khóa màn hình để tiếp tục"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Không có phần cứng sinh trắc học"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Đã hủy xác thực"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"TỪ CHỐI"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Chọn phương thức nhập"</string>
     <string name="show_ime" msgid="6406112007347443383">"Hiện bàn phím ảo trên màn hình trong khi bàn phím vật lý đang hoạt động"</string>
-    <string name="hardware" msgid="1800597768237606953">"Hiện bàn phím ảo"</string>
+    <string name="hardware" msgid="3611039921284836033">"Sử dụng bàn phím ảo"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Định cấu hình <xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Định cấu hình bàn phím vật lý"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Nhấn để chọn ngôn ngữ và bố cục"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 7652142..b89f6b2 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"使用生物识别或屏幕锁定凭据"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"验证是您本人在操作"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"使用生物识别验证身份才能继续"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"如需继续操作,请使用指纹验证身份"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"如需继续操作,请刷脸验证身份"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"使用生物识别或屏幕锁定凭据验证身份,才能继续操作"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"生物识别硬件无法使用"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"身份验证已取消"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"拒绝"</string>
     <string name="select_input_method" msgid="3971267998568587025">"选择输入法"</string>
     <string name="show_ime" msgid="6406112007347443383">"开启后,连接到实体键盘时,它会一直显示在屏幕上"</string>
-    <string name="hardware" msgid="1800597768237606953">"显示虚拟键盘"</string>
+    <string name="hardware" msgid="3611039921284836033">"使用屏幕键盘"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"配置“<xliff:g id="DEVICE_NAME">%s</xliff:g>”"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"配置物理键盘"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"点按即可选择语言和布局"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 20fbd8a..819106a 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"使用生物識別或螢幕鎖定"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"驗證是你本人"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"如要繼續操作,請使用使用生物識別驗證身分"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"如要繼續操作,請使用你的指紋驗證身分"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"如要繼續操作,請使用你的面孔驗證身分"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"請使用生物識別或螢幕鎖定功能驗證身分,才能繼續操作"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"無法使用生物識別硬件"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"已取消驗證"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"拒絕"</string>
     <string name="select_input_method" msgid="3971267998568587025">"選擇輸入法"</string>
     <string name="show_ime" msgid="6406112007347443383">"在實體鍵盤處於連接狀態時保持顯示"</string>
-    <string name="hardware" msgid="1800597768237606953">"顯示虛擬鍵盤"</string>
+    <string name="hardware" msgid="3611039921284836033">"使用屏幕鍵盤"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"設定「<xliff:g id="DEVICE_NAME">%s</xliff:g>」"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"設定實體鍵盤"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"輕按即可選取語言和鍵盤配置"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 58ad7eb2..746be60 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"使用生物特徵辨識或螢幕鎖定功能"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"驗證你的身分"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"如要繼續操作,請使用生物特徵辨識功能驗證身分"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"如要繼續操作,請使用指紋驗證身分"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"如要繼續操作,請使用臉孔驗證身分"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"請使用生物特徵辨識或螢幕鎖定功能驗證身分,才能繼續操作"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"無法使用生物特徵辨識硬體"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"已取消驗證"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"拒絕"</string>
     <string name="select_input_method" msgid="3971267998568587025">"選擇輸入法"</string>
     <string name="show_ime" msgid="6406112007347443383">"使用實體鍵盤時仍繼續顯示螢幕小鍵盤"</string>
-    <string name="hardware" msgid="1800597768237606953">"顯示虛擬鍵盤"</string>
+    <string name="hardware" msgid="3611039921284836033">"使用螢幕小鍵盤"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"設定「<xliff:g id="DEVICE_NAME">%s</xliff:g>」"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"設定實體鍵盤"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"輕觸即可選取語言和版面配置"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index b5736bb..9908707 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -616,8 +616,6 @@
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Sebenzisa i-biometrics noma ukukhiya isikrini"</string>
     <string name="biometric_dialog_default_title" msgid="55026799173208210">"Qinisekisa ukuthi nguwe"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Sebenzisa i-biometric yakho ukuze uqhubeke"</string>
-    <string name="biometric_dialog_fingerprint_subtitle" msgid="2520227942533751342">"Sebenzisa isigxivizo sakho somunwe ukuze uqhubeke"</string>
-    <string name="biometric_dialog_face_subtitle" msgid="5269284162191087084">"Sebenzisa ubuso bakho ukuze uqhubeke"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Sebenzisa i-biometric noma ukukhiya isikrini ukuze uqhubeke"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"I-Biometric hardware ayitholakali"</string>
     <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Ukufakazela ubuqiniso kukhanseliwe"</string>
@@ -1397,7 +1395,7 @@
     <string name="decline_remote_bugreport_action" msgid="4040894777519784346">"YENQABA"</string>
     <string name="select_input_method" msgid="3971267998568587025">"Khetha indlela yokufaka"</string>
     <string name="show_ime" msgid="6406112007347443383">"Yigcine kusikrini ngenkathi kusebenza ikhibhodi ephathekayo"</string>
-    <string name="hardware" msgid="1800597768237606953">"Bonisa ikhibhodi ebonakalayo"</string>
+    <string name="hardware" msgid="3611039921284836033">"Sebenzisa ikhibhodi ekuskrini"</string>
     <string name="select_keyboard_layout_notification_title" msgid="5823199895322205589">"Lungisa i-<xliff:g id="DEVICE_NAME">%s</xliff:g>"</string>
     <string name="select_multiple_keyboards_layout_notification_title" msgid="6999491025126641938">"Lungiselela amakhibhodi aphathekayo"</string>
     <string name="select_keyboard_layout_notification_message" msgid="8835158247369158154">"Thepha ukuze ukhethe ulimi nesakhiwo"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 6f7bc53..735e590 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2465,7 +2465,7 @@
                duration of the vector animation automatically. -->
         <attr name="windowSplashScreenAnimationDuration" format="integer"/>
 
-        <!-- Place an drawable image in the bottom of the starting window, it can be used to
+        <!-- Place a drawable image in the bottom of the starting window. The image can be used to
              represent the branding of the application. -->
         <attr name="windowSplashScreenBrandingImage" format="reference"/>
         <!-- Set a background behind the splash screen icon. This is useful if there is not enough
@@ -3245,7 +3245,7 @@
 
         <!-- Specifies the id of a view for which this view serves as a label for
              accessibility purposes. For example, a TextView before an EditText in
-             the UI usually specifies what infomation is contained in the EditText.
+             the UI usually specifies what information is contained in the EditText.
              Hence, the TextView is a label for the EditText. -->
         <attr name="labelFor" format="reference" />
 
@@ -6787,7 +6787,7 @@
              edges of a bitmap when rotated. Default value is false. -->
         <attr name="antialias" format="boolean" />
         <!-- Enables or disables bitmap filtering. Filtering is used when the bitmap is
-             shrunk or stretched to smooth its apperance. Default value is true. -->
+             shrunk or stretched to smooth its appearance. Default value is true. -->
         <attr name="filter" format="boolean" />
         <!-- Enables or disables dithering of the bitmap if the bitmap does not have the
              same pixel configuration as the screen (for instance: a ARGB 8888 bitmap with
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b7d088b..95f1493 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -634,7 +634,7 @@
          able to return to it. -->
     <attr name="noHistory" format="boolean" />
 
-    <!-- Specify whether an acitivty's task state should always be maintained
+    <!-- Specify whether an activity's task state should always be maintained
          by the system, or if it is allowed to reset the task to its initial
          state in certain situations.
 
@@ -731,15 +731,17 @@
          This is equivalent to calling {@link android.app.Activity#setVrModeEnabled} with the
          the given component name within the Activity that this attribute is set for.
          Declaring this will prevent the system from leaving VR mode during an Activity
-         transtion from one VR activity to another. -->
+         transition from one VR activity to another. -->
     <attr name="enableVrMode" format="string" />
 
-    <!-- Flag allowing the activity to specify which screen rotation animation
-         it desires.  Valid values are "rotate", "crossfade", and "jumpcut"
-         as described in {@link android.view.WindowManager.LayoutParams#rotationAnimation}.
-         Specifying your Rotation animation in the WindowManager.LayoutParams
-         may be racy with app startup and updattransitions occuring during application startup and thusly
-         the manifest attribute is preferred.
+    <!-- Flag that specifies the activity's preferred screen rotation animation.
+         Valid values are "rotate", "crossfade", "jumpcut", and "seamless" as
+         described in
+         {@link android.view.WindowManager.LayoutParams#rotationAnimation}.
+         Specifying your rotation animation in
+         <code>WindowManager.LayoutParams</code> may be racy with app startup
+         and update transitions that occur during application startup; and so,
+         specify the animation in the manifest attribute.
     -->
     <attr name="rotationAnimation">
       <flag name="rotate" value= "0" />
@@ -830,7 +832,7 @@
         <enum name="singleInstance" value="3" />
         <!-- The activity can only be running as the root activity of the task, the first activity
             that created the task, and therefore there will only be one instance of this activity
-            in a task. In constrast to the {@code singleTask} launch mode, this activity can be
+            in a task. In contrast to the {@code singleTask} launch mode, this activity can be
             started in multiple instances in different tasks if the
             {@code FLAG_ACTIVITY_MULTIPLE_TASK} or {@code FLAG_ACTIVITY_NEW_DOCUMENT} is set.-->
         <enum name="singleInstancePerTask" value="4" />
@@ -1328,7 +1330,7 @@
          <p>Such a document is any kind of item for which an application may want to
          maintain multiple simultaneous instances. Examples might be text files, web
          pages, spreadsheets, or emails. Each such document will be in a separate
-         task in the recent taskss list.
+         task in the recent tasks list.
 
          <p>This attribute is equivalent to adding the flag {@link
          android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} to every Intent used to launch
@@ -1771,7 +1773,7 @@
     </attr>
 
     <!-- Enable hardware memory tagging (ARM MTE) in this process.
-         When enabled, heap memory bugs like use-after-free and buffer overlow
+         When enabled, heap memory bugs like use-after-free and buffer overflow
          are detected and result in an immediate ("sync" mode) or delayed ("async"
          mode) crash instead of a silent memory corruption. Sync mode, while slower,
          provides enhanced bug reports including stack traces at the time of allocation
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index bd0fe40..d86d53f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -45,8 +45,7 @@
         <item><xliff:g id="id">@string/status_bar_phone_signal</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_secure</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
-        <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_connected_display</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_camera</xliff:g></item>
@@ -55,6 +54,8 @@
         <item><xliff:g id="id">@string/status_bar_mute</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_volume</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_zen</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
+        <item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item>
         <item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
@@ -72,6 +73,7 @@
     <string translatable="false" name="status_bar_sync_failing">sync_failing</string>
     <string translatable="false" name="status_bar_sync_active">sync_active</string>
     <string translatable="false" name="status_bar_cast">cast</string>
+    <string translatable="false" name="status_bar_connected_display">connected_display</string>
     <string translatable="false" name="status_bar_hotspot">hotspot</string>
     <string translatable="false" name="status_bar_location">location</string>
     <string translatable="false" name="status_bar_bluetooth">bluetooth</string>
@@ -1675,16 +1677,56 @@
          in the config_autoBrightnessLevels array.  This array should have size one greater
          than the size of the config_autoBrightnessLevels array.
          The brightness values must be between 0 and 255 and be non-decreasing.
+
          This must be overridden in platform specific overlays -->
     <integer-array name="config_autoBrightnessButtonBacklightValues">
     </integer-array>
 
-    <!-- Array of output values for keyboard backlight corresponding to the lux values
-         in the config_autoBrightnessLevels array.  This array should have size one greater
-         than the size of the config_autoBrightnessLevels array.
-         The brightness values must be between 0 and 255 and be non-decreasing.
+    <!-- Smoothing constant for Ambient keyboard backlight change. It should contain value
+         in the range (0.0, 1.0] that will be used to calculate smoothed lux values using
+         simple exponential smoothing. This value indicated how quickly we transition to
+         the lux values provided by the Ambient light sensor.
+         Simple formula for newLuxValue = (1-constant)*currLuxValue + constant*rawLuxValue, where
+         rawLuxValue is the value provided by the Ambient light sensor. (e.g. value of 1.0 means we
+         immediately start using the value provided by the Ambient light sensor)
          This must be overridden in platform specific overlays -->
-    <integer-array name="config_autoBrightnessKeyboardBacklightValues">
+    <item name="config_autoKeyboardBrightnessSmoothingConstant" format="float" type="dimen">
+        1.0
+    </item>
+
+    <!-- Array of output values for keyboard backlight corresponding to the lux values
+         in the config_autoKeyboardBacklight(Increase/Decrease)LuxThreshold arrays.
+         The brightness values must be between 0 and 255 and be non-decreasing.
+
+         This can be overridden in platform specific overlays -->
+    <integer-array name="config_autoKeyboardBacklightBrightnessValues">
+        <item>102</item>
+        <item>153</item>
+        <item>0</item>
+    </integer-array>
+
+    <!-- Array of threshold values for keyboard backlight corresponding to the values
+         in the config_autoKeyboardBacklightBrightnessValues array.
+         These lux values indicate when to move to a lower keyboard backlight value,
+         as defined in config_autoKeyboardBacklightBrightnessValues array.
+
+         This can be overridden in platform specific overlays -->
+    <integer-array name="config_autoKeyboardBacklightDecreaseLuxThreshold">
+        <item>-1</item>
+        <item>40</item>
+        <item>150</item>
+    </integer-array>
+
+    <!-- Array of threshold values for keyboard backlight corresponding to the values
+         in the config_autoKeyboardBacklightBrightnessValues array.
+         These lux values indicate when to move to a higher keyboard backlight value,
+         as defined in config_autoKeyboardBacklightBrightnessValues array.
+
+         This can be overridden in platform specific overlays -->
+    <integer-array name="config_autoKeyboardBacklightIncreaseLuxThreshold">
+        <item>55</item>
+        <item>200</item>
+        <item>-1</item>
     </integer-array>
 
     <!-- An array describing the screen's backlight values corresponding to the brightness
@@ -1817,6 +1859,17 @@
          specified -->
     <string name="default_wallpaper_component" translatable="false">@null</string>
 
+    <!-- Default wallpaper component per device color map, each item is a comma separated key-value
+         pair with key being a device color and value being the corresponding wallpaper component.
+         The component with its key matching the device color will be the default wallpaper, the
+         default wallpaper component will be the default if this config is not specified.
+
+         E.g. for SLV color, and com.android.example/com.android.example.SlVDefaultWallpaper
+         <item>SLV,com.android.example/com.android.example.SlVDefaultWallpaper</item> -->
+    <string-array name="default_wallpaper_component_per_device_color" translatable="false">
+        <!-- Add packages here -->
+    </string-array>
+
     <!-- By default a product has no distinct default lock wallpaper -->
     <item name="default_lock_wallpaper" type="drawable">@null</item>
 
@@ -2641,6 +2694,9 @@
          backlight values -->
     <bool name="config_displayBrightnessBucketsInDoze">false</bool>
 
+    <!-- True to skip the fade animation on display off event -->
+    <bool name="config_displayColorFadeDisabled">false</bool>
+
     <!-- Power Management: Specifies whether to decouple the auto-suspend state of the
          device from the display on/off state.
 
@@ -4432,6 +4488,14 @@
     -->
     <string name="config_defaultContentCaptureService" translatable="false"></string>
 
+    <!-- The package name for the system's content protection service.
+         This service must be trusted, as it can be activated without explicit consent of the user.
+         If no service with the specified name exists on the device, content protection will be
+         disabled.
+         Example: "com.android.contentprotection/.ContentProtectionService"
+    -->
+    <string name="config_defaultContentProtectionService" translatable="false"></string>
+
     <!-- The package name for the system's augmented autofill service.
          This service must be trusted, as it can be activated without explicit consent of the user.
          If no service with the specified name exists on the device, augmented autofill wil be
@@ -5063,13 +5127,18 @@
          a threshold.
          For example, no higher refresh rate if
              display brightness <= disp0 && ambient brightness <= amb0
-             || display brightness <= disp1 && ambient brightness <= amb1 -->
+             || display brightness <= disp1 && ambient brightness <= amb1
+         Brightness thresholds are paired with lux thresholds - they both have to be met.
+         A negative brightness or lux value means that only one threshold should be used - e.g. if
+         the brightness value is negative, only the lux threshold is applied. -->
+    <!-- Low zone brightness thresholds in the range [0, 255] -->
     <integer-array translatable="false" name="config_brightnessThresholdsOfPeakRefreshRate">
          <!--
            <item>disp0</item>
            <item>disp1</item>
         -->
     </integer-array>
+    <!-- Low zone lux thresholds -->
     <integer-array translatable="false" name="config_ambientThresholdsOfPeakRefreshRate">
          <!--
            <item>amb0</item>
@@ -5090,14 +5159,18 @@
          may have lower display brightness requirements for the flickering is visible.
          For example, fixed refresh rate if
              display brightness >= disp0 && ambient brightness >= amb0
-             || display brightness >= disp1 && ambient brightness >= amb1 -->
+             || display brightness >= disp1 && ambient brightness >= amb1
+         Brightness thresholds are paired with lux thresholds - they both have to be met.
+         A negative brightness or lux value means that only one threshold should be used - e.g. if
+         the brightness value is negative, only the lux threshold is applied. -->
+    <!-- High zone brightness thresholds in the range [0, 255] -->
     <integer-array translatable="false" name="config_highDisplayBrightnessThresholdsOfFixedRefreshRate">
          <!--
            <item>disp0</item>
            <item>disp1</item>
         -->
     </integer-array>
-
+    <!-- High zone lux thresholds -->
     <integer-array translatable="false" name="config_highAmbientBrightnessThresholdsOfFixedRefreshRate">
          <!--
            <item>amb0</item>
@@ -5263,6 +5336,12 @@
     <!-- Default value for performant auth feature. -->
     <bool name="config_performantAuthDefault">false</bool>
 
+    <!-- Threshold for false rejection rate (FRR) of biometric authentication. Applies for both
+         fingerprint and face. If a dual-modality device only enrolled a single biometric and
+         experiences high FRR (above threshold), system notification will be sent to encourage user
+         to enroll the other eligible biometric. -->
+    <fraction name="config_biometricNotificationFrrThreshold">30%</fraction>
+
     <!-- The component name for the default profile supervisor, which can be set as a profile owner
     even after user setup is complete. The defined component should be used for supervision purposes
     only. The component must be part of a system app. -->
@@ -5526,13 +5605,13 @@
 
     <!-- Blur radius for the Option 3 in R.integer.config_letterboxBackgroundType. Values < 0 are
         ignored and 0 is used. -->
-    <dimen name="config_letterboxBackgroundWallpaperBlurRadius">31dp</dimen>
+    <dimen name="config_letterboxBackgroundWallpaperBlurRadius">24dp</dimen>
 
     <!-- Alpha of a black translucent scrim showed over wallpaper letterbox background when
         the Option 3 is selected for R.integer.config_letterboxBackgroundType.
         Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead. -->
     <item name="config_letterboxBackgroundWallaperDarkScrimAlpha" format="float" type="dimen">
-        0.5
+        0.75
     </item>
 
     <!-- Corners appearance of the letterbox background.
@@ -5557,7 +5636,7 @@
             but isn't supported on the device or both dark scrim alpha and blur radius aren't
             provided.
      -->
-    <color name="config_letterboxBackgroundColor">@color/letterbox_background</color>
+    <color name="config_letterboxBackgroundColor">@color/system_neutral1_1000</color>
 
     <!-- Horizontal position of a center of the letterboxed app window.
         0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
@@ -5656,6 +5735,12 @@
          TODO(b/255532890) Enable when ignoreOrientationRequest is set -->
     <bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool>
 
+    <!-- Whether per-app user aspect ratio override settings is enabled -->
+    <bool name="config_appCompatUserAppAspectRatioSettingsIsEnabled">false</bool>
+
+    <!-- Whether per-app fullscreen override option is allowed in user aspect ratio settings -->
+    <bool name="config_appCompatUserAppAspectRatioFullscreenIsEnabled">false</bool>
+
     <!-- Whether sending compat fake focus for split screen resumed activities is enabled.
          Needed because some game engines wait to get focus before drawing the content of
          the app which isn't guaranteed by default in multi-window modes. -->
@@ -6198,7 +6283,7 @@
 
     <!-- Flag indicating whether the show Stylus pointer icon.
      If set, a pointer icon will be shown over the location of a stylus pointer.-->
-    <bool name="config_enableStylusPointerIcon">false</bool>
+    <bool name="config_enableStylusPointerIcon">true</bool>
 
     <!-- Determines whether SafetyCenter feature is enabled. -->
     <bool name="config_enableSafetyCenter">true</bool>
@@ -6459,4 +6544,9 @@
     <!-- Whether the AOSP support for app cloning building blocks is to be enabled for the
          device. -->
     <bool name="config_enableAppCloningBuildingBlocks">true</bool>
+
+    <!-- Enables or disables support for repair mode. The feature creates a secure
+         environment to protect the user's privacy when the device is being repaired.
+         Off by default, since OEMs may have had a similar feature on their devices. -->
+    <bool name="config_repairModeSupported">false</bool>
 </resources>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 4ae54a0..b7a5bc8 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -67,6 +67,15 @@
     <bool name="auto_data_switch_ping_test_before_switch">true</bool>
     <java-symbol type="bool" name="auto_data_switch_ping_test_before_switch" />
 
+    <!-- Define the tolerated gap of score for auto data switch decision, larger than which the
+         device will switch to the SIM with higher score. The score is used in conjunction with the
+         score table defined in
+         CarrierConfigManager#KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_STRING_ARRAY.
+         If 0, the device always switch to the higher score SIM.
+         If < 0, the network type and signal strength based auto switch is disabled. -->
+    <integer name="auto_data_switch_score_tolerance">3000</integer>
+    <java-symbol type="integer" name="auto_data_switch_score_tolerance" />
+
     <!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec
          tunnels across service restart. If iwlan tunnels are not persisted across restart,
          Framework will clean up dangling data connections when service restarts -->
@@ -142,6 +151,23 @@
     <integer name="config_timeout_to_receive_delivered_ack_millis">300000</integer>
     <java-symbol type="integer" name="config_timeout_to_receive_delivered_ack_millis" />
 
+    <!-- Telephony config for services supported by satellite providers. The format of each config
+         string in the array is as follows: "PLMN_1:service_1,service_2,..."
+         where PLMN is the satellite PLMN of a provider and service is an integer with the
+         following value:
+            1 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VOICE}
+            2 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_DATA}
+            3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS}
+            4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO}
+            5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}
+         Example of a config string: "10011:2,3"
+
+         The PLMNs not configured in this array will be ignored and will not be used for satellite
+         scanning. -->
+    <string-array name="config_satellite_services_supported_by_providers" translatable="false">
+    </string-array>
+    <java-symbol type="array" name="config_satellite_services_supported_by_providers" />
+
     <!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks
          will not perform handover if the target transport is out of service, or VoPS not
          supported. The network will be torn down on the source transport, and will be
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 24da59a..b129321 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -95,8 +95,10 @@
     <dimen name="navigation_bar_height_landscape_car_mode">96dp</dimen>
     <!-- Width of the navigation bar when it is placed vertically on the screen in car mode -->
     <dimen name="navigation_bar_width_car_mode">96dp</dimen>
-    <!-- Height of notification icons in the status bar -->
+    <!-- Original dp height of notification icons in the status bar  -->
     <dimen name="status_bar_icon_size">22dip</dimen>
+    <!-- New sp height of notification icons in the status bar  -->
+    <dimen name="status_bar_icon_size_sp">22sp</dimen>
     <!-- Desired size of system icons in status bar. -->
     <dimen name="status_bar_system_icon_size">15dp</dimen>
     <!-- Intrinsic size of most system icons in status bar. This is the default value that
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index dfff8d4..9c018c30 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1780,10 +1780,6 @@
     <string name="biometric_dialog_default_title">Verify it\u2019s you</string>
     <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face). [CHAR LIMIT=70] -->
     <string name="biometric_dialog_default_subtitle">Use your biometric to continue</string>
-    <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with fingerprint. [CHAR LIMIT=70] -->
-    <string name="biometric_dialog_fingerprint_subtitle">Use your fingerprint to continue</string>
-    <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with face. [CHAR LIMIT=70] -->
-    <string name="biometric_dialog_face_subtitle">Use your face to continue</string>
     <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face) or their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=90] -->
     <string name="biometric_or_screen_lock_dialog_default_subtitle">Use your biometric or screen lock to continue</string>
 
@@ -1893,22 +1889,22 @@
     <!-- Content description which should be used for the fingerprint icon. -->
     <string name="fingerprint_icon_content_description">Fingerprint icon</string>
 
+    <!-- Notification name shown when the system requires the user to set up device unlock. [CHAR LIMIT=NONE] -->
+    <string name="device_unlock_notification_name">Device unlock</string>
+    <!-- Notification title shown when the system suggests the user to set up another way to unlock. [CHAR LIMIT=NONE] -->
+    <string name="alternative_unlock_setup_notification_title">Try another way to unlock</string>
+    <!-- Notification content shown when the system suggests the user to enroll their face. [CHAR LIMIT=NONE] -->
+    <string name="alternative_face_setup_notification_content">Use Face Unlock when your fingerprint isn\'t recognized, like when your fingers are wet</string>
+    <!-- Notification content shown when the system suggests the user to enroll their fingerprint. [CHAR LIMIT=NONE] -->
+    <string name="alternative_fp_setup_notification_content">Use Fingerprint Unlock when your face isn\'t recognized, like when there\'s not enough light</string>
     <!-- Notification name shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
     <string name="face_recalibrate_notification_name">Face Unlock</string>
     <!-- Notification title shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
     <string name="face_recalibrate_notification_title">Issue with Face Unlock</string>
     <!-- Notification content shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
     <string name="face_recalibrate_notification_content">Tap to delete your face model, then add your face again</string>
-    <!-- Title of a notification that directs the user to set up Face Unlock by enrolling their face. [CHAR LIMIT=NONE] -->
-    <string name="face_setup_notification_title">Set up Face Unlock</string>
-    <!-- Contents of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] -->
-    <string name="face_setup_notification_content">Unlock your phone by looking at it</string>
     <!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] -->
     <string name="face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string>
-    <!-- Title of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
-    <string name="fingerprint_setup_notification_title">Set up more ways to unlock</string>
-    <!-- Contents of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
-    <string name="fingerprint_setup_notification_content">Tap to add a fingerprint</string>
 
     <!-- Notification name shown when the system requires the user to re-calibrate their fingerprint. [CHAR LIMIT=NONE] -->
     <string name="fingerprint_recalibrate_notification_name">Fingerprint Unlock</string>
@@ -3798,7 +3794,7 @@
          keyboard is connected -->
     <string name="show_ime">Keep it on screen while physical keyboard is active</string>
     <!-- Title of the physical keyboard category in the input method selector [CHAR LIMIT=30] -->
-    <string name="hardware">Show virtual keyboard</string>
+    <string name="hardware">Use on-screen keyboard</string>
 
     <!-- Title of the notification to prompt the user to configure physical keyboard settings. [CHAR LIMIT=NOTIF_TITLE] -->
     <string name="select_keyboard_layout_notification_title">Configure <xliff:g id="device_name" example="Foobar USB Keyboard">%s</xliff:g></string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4213190..b94ede9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -520,6 +520,7 @@
   <java-symbol type="dimen" name="config_viewConfigurationHandwritingSlop" />
   <java-symbol type="dimen" name="config_viewConfigurationHoverSlop" />
   <java-symbol type="dimen" name="config_ambiguousGestureMultiplier" />
+  <java-symbol type="dimen" name="config_autoKeyboardBrightnessSmoothingConstant" />
   <java-symbol type="dimen" name="config_viewMinFlingVelocity" />
   <java-symbol type="dimen" name="config_viewMaxFlingVelocity" />
   <java-symbol type="dimen" name="config_viewMinRotaryEncoderFlingVelocity" />
@@ -1888,11 +1889,13 @@
   <java-symbol type="anim" name="dream_activity_open_enter" />
   <java-symbol type="anim" name="dream_activity_close_exit" />
   <java-symbol type="array" name="config_autoBrightnessButtonBacklightValues" />
-  <java-symbol type="array" name="config_autoBrightnessKeyboardBacklightValues" />
   <java-symbol type="array" name="config_autoBrightnessLcdBacklightValues" />
   <java-symbol type="array" name="config_autoBrightnessLcdBacklightValues_doze" />
   <java-symbol type="array" name="config_autoBrightnessLevels" />
   <java-symbol type="array" name="config_autoBrightnessLevelsIdle" />
+  <java-symbol type="array" name="config_autoKeyboardBacklightBrightnessValues" />
+  <java-symbol type="array" name="config_autoKeyboardBacklightDecreaseLuxThreshold" />
+  <java-symbol type="array" name="config_autoKeyboardBacklightIncreaseLuxThreshold" />
   <java-symbol type="array" name="config_ambientThresholdLevels" />
   <java-symbol type="array" name="config_ambientBrighteningThresholds" />
   <java-symbol type="array" name="config_ambientDarkeningThresholds" />
@@ -2116,6 +2119,7 @@
   <java-symbol type="string" name="data_usage_rapid_body" />
   <java-symbol type="string" name="data_usage_rapid_app_body" />
   <java-symbol type="string" name="default_wallpaper_component" />
+  <java-symbol type="array" name="default_wallpaper_component_per_device_color" />
   <java-symbol type="string" name="device_storage_monitor_notification_channel" />
   <java-symbol type="string" name="dlg_ok" />
   <java-symbol type="string" name="dump_heap_notification" />
@@ -2295,6 +2299,7 @@
 
   <java-symbol type="bool" name="config_alwaysUseCdmaRssi" />
   <java-symbol type="dimen" name="status_bar_icon_size" />
+  <java-symbol type="dimen" name="status_bar_icon_size_sp" />
   <java-symbol type="dimen" name="status_bar_system_icon_size" />
   <java-symbol type="dimen" name="status_bar_system_icon_intrinsic_size" />
   <java-symbol type="drawable" name="list_selector_pressed_holo_dark" />
@@ -2567,8 +2572,6 @@
   <java-symbol type="string" name="biometric_or_screen_lock_app_setting_name" />
   <java-symbol type="string" name="biometric_dialog_default_title" />
   <java-symbol type="string" name="biometric_dialog_default_subtitle" />
-  <java-symbol type="string" name="biometric_dialog_face_subtitle" />
-  <java-symbol type="string" name="biometric_dialog_fingerprint_subtitle" />
   <java-symbol type="string" name="biometric_or_screen_lock_dialog_default_subtitle" />
   <java-symbol type="string" name="biometric_error_hw_unavailable" />
   <java-symbol type="string" name="biometric_error_user_canceled" />
@@ -2578,6 +2581,15 @@
   <java-symbol type="string" name="biometric_error_device_not_secured" />
   <java-symbol type="string" name="biometric_error_generic" />
 
+  <!-- Biometric FRR config -->
+  <java-symbol type="fraction" name="config_biometricNotificationFrrThreshold" />
+
+  <!-- Biometric FRR notification messages -->
+  <java-symbol type="string" name="device_unlock_notification_name" />
+  <java-symbol type="string" name="alternative_unlock_setup_notification_title" />
+  <java-symbol type="string" name="alternative_face_setup_notification_content" />
+  <java-symbol type="string" name="alternative_fp_setup_notification_content" />
+
   <!-- Device credential strings for BiometricManager -->
   <java-symbol type="string" name="screen_lock_app_setting_name" />
   <java-symbol type="string" name="screen_lock_dialog_default_subtitle" />
@@ -3050,6 +3062,7 @@
   <java-symbol type="id" name="header_text_secondary" />
   <java-symbol type="id" name="expand_button" />
   <java-symbol type="id" name="expand_button_pill" />
+  <java-symbol type="id" name="expand_button_pill_colorized_layer" />
   <java-symbol type="id" name="expand_button_number" />
   <java-symbol type="id" name="expand_button_icon" />
   <java-symbol type="id" name="alternate_expand_target" />
@@ -3099,6 +3112,7 @@
   <java-symbol type="string" name="status_bar_sync_failing" />
   <java-symbol type="string" name="status_bar_sync_active" />
   <java-symbol type="string" name="status_bar_cast" />
+  <java-symbol type="string" name="status_bar_connected_display" />
   <java-symbol type="string" name="status_bar_hotspot" />
   <java-symbol type="string" name="status_bar_location" />
   <java-symbol type="string" name="status_bar_bluetooth" />
@@ -3767,6 +3781,7 @@
   <java-symbol type="string" name="config_defaultTextClassifierPackage" />
   <java-symbol type="string" name="config_defaultWellbeingPackage" />
   <java-symbol type="string" name="config_defaultContentCaptureService" />
+  <java-symbol type="string" name="config_defaultContentProtectionService" />
   <java-symbol type="string" name="config_defaultAugmentedAutofillService" />
   <java-symbol type="string" name="config_defaultTranslationService" />
   <java-symbol type="string" name="config_defaultAppPredictionService" />
@@ -3848,6 +3863,7 @@
   <java-symbol type="bool" name="config_dozeSupportsAodWallpaper" />
   <java-symbol type="bool" name="config_displayBlanksAfterDoze" />
   <java-symbol type="bool" name="config_displayBrightnessBucketsInDoze" />
+  <java-symbol type="bool" name="config_displayColorFadeDisabled" />
   <java-symbol type="integer" name="config_storageManagerDaystoRetainDefault" />
   <java-symbol type="string" name="config_headlineFontFamily" />
   <java-symbol type="string" name="config_headlineFontFamilyMedium" />
@@ -4429,9 +4445,6 @@
   <!-- Set to true to make assistant show in front of the dream/screensaver. -->
   <java-symbol type="bool" name="config_assistantOnTopOfDream"/>
 
-  <!-- Set to true to enable letterboxing on translucent activities. -->
-  <java-symbol type="bool" name="config_letterboxIsEnabledForTranslucentActivities" />
-
   <java-symbol type="string" name="config_overrideComponentUiPackage" />
 
   <java-symbol type="string" name="notification_channel_network_status" />
@@ -4527,6 +4540,13 @@
   <java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
   <java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" />
   <java-symbol type="bool" name="config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled" />
+  <!-- Set to true to enable letterboxing on translucent activities. -->
+  <java-symbol type="bool" name="config_letterboxIsEnabledForTranslucentActivities" />
+
+  <!-- Whether per-app user aspect ratio override settings is enabled -->
+  <java-symbol type="bool" name="config_appCompatUserAppAspectRatioSettingsIsEnabled" />
+  <java-symbol type="bool" name="config_appCompatUserAppAspectRatioFullscreenIsEnabled" />
+
   <java-symbol type="bool" name="config_isCompatFakeFocusEnabled" />
   <java-symbol type="bool" name="config_isWindowManagerCameraCompatTreatmentEnabled" />
   <java-symbol type="bool" name="config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled" />
@@ -4917,6 +4937,8 @@
 
   <java-symbol type="bool" name="config_safetyProtectionEnabled" />
 
+  <java-symbol type="bool" name="config_repairModeSupported" />
+
   <java-symbol type="string" name="config_devicePolicyManagementUpdater" />
 
   <java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" />
@@ -4937,7 +4959,6 @@
   <!-- For VirtualDeviceManager -->
   <java-symbol type="string" name="vdm_camera_access_denied" />
   <java-symbol type="string" name="vdm_secure_window" />
-  <java-symbol type="string" name="vdm_pip_blocked" />
 
   <java-symbol type="color" name="camera_privacy_light_day"/>
   <java-symbol type="color" name="camera_privacy_light_night"/>
diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp
index 436f058..85d54e0 100644
--- a/core/tests/BroadcastRadioTests/Android.bp
+++ b/core/tests/BroadcastRadioTests/Android.bp
@@ -45,6 +45,7 @@
     libs: ["android.test.base"],
     test_suites: [
         "general-tests",
+        "automotive-general-tests",
     ],
     // mockito-target-inline dependency
     jni_libs: [
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index c1deba3..31755ef 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -231,6 +231,28 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.widget.HorizontalScrollViewActivity"
+                android:label="HorizontalScrollViewActivity"
+                android:screenOrientation="portrait"
+                android:exported="true"
+                android:theme="@android:style/Theme.Material.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.widget.ScrollViewActivity"
+                android:label="ScrollViewActivity"
+                android:screenOrientation="portrait"
+                android:exported="true"
+                android:theme="@android:style/Theme.Material.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="android.widget.DatePickerActivity"
                 android:label="DatePickerActivity"
                 android:screenOrientation="portrait"
@@ -1716,6 +1738,17 @@
             <meta-data android:name="android.view.im"
                        android:resource="@xml/ime_meta_handwriting"/>
         </service>
+
+        <activity android:name="android.widget.PointerIconTestActivity"
+                  android:label="PointerIconTestActivity"
+                  android:screenOrientation="portrait"
+                  android:exported="true"
+                  android:theme="@android:style/Theme.Material.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
new file mode 100644
index 0000000..866e1a9
--- /dev/null
+++ b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2015 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
+  -->
+
+<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/horizontal_scroll_view">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <View
+            android:background="#F00"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#880"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#0F0"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#088"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#00F"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#808"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#F00"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#880"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#0F0"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#088"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#00F"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#808"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#F00"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#880"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#0F0"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#088"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#00F"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#808"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+    </LinearLayout>
+</HorizontalScrollView>
diff --git a/core/tests/coretests/res/layout/activity_scroll_view.xml b/core/tests/coretests/res/layout/activity_scroll_view.xml
new file mode 100644
index 0000000..61fabf8
--- /dev/null
+++ b/core/tests/coretests/res/layout/activity_scroll_view.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2015 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
+  -->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/scroll_view">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <View
+            android:background="#F00"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#880"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#0F0"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#088"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#00F"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#808"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#F00"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#880"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#0F0"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#088"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#00F"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#808"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#F00"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#880"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#0F0"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#088"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#00F"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#808"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+    </LinearLayout>
+</ScrollView>
diff --git a/core/tests/coretests/res/layout/pointer_icon_test.xml b/core/tests/coretests/res/layout/pointer_icon_test.xml
new file mode 100644
index 0000000..a2a6447
--- /dev/null
+++ b/core/tests/coretests/res/layout/pointer_icon_test.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+    <TextView
+        android:id="@+id/textview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Test"/>
+
+    <EditText
+        android:id="@+id/edittext"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Test"/>
+
+    <Button
+        android:id="@+id/button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Test"/>
+
+    <ImageButton
+        android:id="@+id/imagebutton"
+        android:layout_width="50dp"
+        android:layout_height="50dp"/>
+
+    <Spinner
+        android:id="@+id/spinner"
+        android:layout_width="50dp"
+        android:layout_height="50dp"/>
+
+    <RadialTimePickerView
+        android:id="@+id/timepicker"
+        android:layout_width="200dp"
+        android:layout_height="200dp"/>
+
+    <CalendarView
+        android:id="@+id/calendar"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/core/tests/coretests/res/values/overlayable_icons_test.xml b/core/tests/coretests/res/values/overlayable_icons_test.xml
index 7ea1848..6a50e9a 100644
--- a/core/tests/coretests/res/values/overlayable_icons_test.xml
+++ b/core/tests/coretests/res/values/overlayable_icons_test.xml
@@ -21,7 +21,6 @@
     <item>@*android:drawable/ic_audio_alarm</item>
     <item>@*android:drawable/ic_audio_alarm_mute</item>
     <item>@*android:drawable/ic_battery_80_24dp</item>
-    <item>@*android:drawable/ic_bluetooth_share_icon</item>
     <item>@*android:drawable/ic_bt_headphones_a2dp</item>
     <item>@*android:drawable/ic_bt_headset_hfp</item>
     <item>@*android:drawable/ic_bt_hearing_aid</item>
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
index 0525443..0c07470 100644
--- a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
@@ -24,6 +24,7 @@
 import android.util.Property;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.rule.ActivityTestRule;
@@ -36,6 +37,8 @@
 import org.junit.Test;
 
 import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 @SmallTest
 public class AnimatorSetActivityTest {
@@ -613,6 +616,68 @@
         });
     }
 
+    @Test
+    public void initAfterStartNotification() throws Throwable {
+        Property<int[], Integer> property = new Property<>(Integer.class, "firstValue") {
+            @Override
+            public Integer get(int[] target) {
+                throw new IllegalStateException("Shouldn't be called");
+            }
+
+            @Override
+            public void set(int[] target, Integer value) {
+                target[0] = value;
+            }
+        };
+        int[] target = new int[1];
+        ObjectAnimator animator1 = ObjectAnimator.ofInt(target, property, 0, 100);
+        ObjectAnimator animator2 = ObjectAnimator.ofInt(target, property, 0, 100);
+        ObjectAnimator animator3 = ObjectAnimator.ofInt(target, property, 0, 100);
+        animator1.setDuration(10);
+        animator2.setDuration(10);
+        animator3.setDuration(10);
+        AnimatorSet set = new AnimatorSet();
+        set.playSequentially(animator1, animator2, animator3);
+        final int[] values = new int[4];
+        animator2.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(@NonNull Animator animation, boolean isReverse) {
+                values[0] = target[0];
+                animator2.setIntValues(target[0], target[0] + 100);
+            }
+
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+                values[1] = target[0];
+            }
+        });
+        animator3.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(@NonNull Animator animation, boolean isReverse) {
+                values[2] = target[0];
+                animator3.setIntValues(target[0], target[0] + 100);
+            }
+
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+                values[3] = target[0];
+            }
+        });
+        final CountDownLatch latch = new CountDownLatch(1);
+        set.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+                latch.countDown();
+            }
+        });
+        mActivityRule.runOnUiThread(() -> set.start());
+        assertTrue(latch.await(1, TimeUnit.SECONDS));
+        assertEquals(100, values[0]);
+        assertEquals(200, values[1]);
+        assertEquals(200, values[2]);
+        assertEquals(300, values[3]);
+    }
+
     /**
      * Check that the animator list contains exactly the given animators and nothing else.
      */
diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java
index 647bfe8..d8305f0 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java
@@ -16,19 +16,52 @@
 
 package android.app;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.TestCase.assertEquals;
 
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.AttributionSource;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.pm.ApplicationInfo;
+import android.database.MatrixCursor;
+import android.media.AudioAttributes;
 import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
 import android.os.Parcel;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.provider.MediaStore.Audio.AudioColumns;
+import android.test.mock.MockContentResolver;
+import android.util.Xml;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
 import com.google.common.base.Strings;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.lang.reflect.Field;
 
 @RunWith(AndroidJUnit4.class)
@@ -36,6 +69,88 @@
 public class NotificationChannelTest {
     private final String CLASS = "android.app.NotificationChannel";
 
+    Context mContext;
+    ContentProvider mContentProvider;
+    IContentProvider mIContentProvider;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = mock(Context.class);
+        when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+        MockContentResolver mContentResolver = new MockContentResolver(mContext);
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        mContentProvider = mock(ContentProvider.class);
+        mIContentProvider = mock(IContentProvider.class);
+        when(mContentProvider.getIContentProvider()).thenReturn(mIContentProvider);
+        doAnswer(
+                invocation -> {
+                        AttributionSource attributionSource = invocation.getArgument(0);
+                        Uri uri = invocation.getArgument(1);
+                        RemoteCallback cb = invocation.getArgument(2);
+                        IContentProvider mock = (IContentProvider) (invocation.getMock());
+                        AsyncTask.SERIAL_EXECUTOR.execute(
+                                () -> {
+                                final Bundle bundle = new Bundle();
+                                try {
+                                        bundle.putParcelable(
+                                                ContentResolver.REMOTE_CALLBACK_RESULT,
+                                                mock.canonicalize(attributionSource, uri));
+                                } catch (RemoteException e) {
+                                        /* consume */
+                                }
+                                cb.sendResult(bundle);
+                                });
+                        return null;
+                })
+            .when(mIContentProvider)
+            .canonicalizeAsync(any(), any(), any());
+        doAnswer(
+                invocation -> {
+                        AttributionSource attributionSource = invocation.getArgument(0);
+                        Uri uri = invocation.getArgument(1);
+                        RemoteCallback cb = invocation.getArgument(2);
+                        IContentProvider mock = (IContentProvider) (invocation.getMock());
+                        AsyncTask.SERIAL_EXECUTOR.execute(
+                                () -> {
+                                final Bundle bundle = new Bundle();
+                                try {
+                                        bundle.putParcelable(
+                                                ContentResolver.REMOTE_CALLBACK_RESULT,
+                                                mock.uncanonicalize(attributionSource, uri));
+                                } catch (RemoteException e) {
+                                        /* consume */
+                                }
+                                cb.sendResult(bundle);
+                                });
+                        return null;
+                })
+            .when(mIContentProvider)
+            .uncanonicalizeAsync(any(), any(), any());
+        doAnswer(
+                invocation -> {
+                        Uri uri = invocation.getArgument(0);
+                        RemoteCallback cb = invocation.getArgument(1);
+                        IContentProvider mock = (IContentProvider) (invocation.getMock());
+                        AsyncTask.SERIAL_EXECUTOR.execute(
+                                () -> {
+                                final Bundle bundle = new Bundle();
+                                try {
+                                        bundle.putString(
+                                                ContentResolver.REMOTE_CALLBACK_RESULT,
+                                                mock.getType(uri));
+                                } catch (RemoteException e) {
+                                        /* consume */
+                                }
+                                cb.sendResult(bundle);
+                                });
+                        return null;
+                })
+            .when(mIContentProvider)
+            .getTypeAsync(any(), any());
+
+        mContentResolver.addProvider("media", mContentProvider);
+    }
+
     @Test
     public void testLongStringFields() {
         NotificationChannel channel = new NotificationChannel("id", "name", 3);
@@ -103,4 +218,139 @@
         assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
                 fromParcel.getSound().toString().length());
     }
+
+    @Test
+    public void testRestoreSoundUri_customLookup() throws Exception {
+        Uri uriToBeRestoredUncanonicalized = Uri.parse("content://media/1");
+        Uri uriToBeRestoredCanonicalized = Uri.parse("content://media/1?title=Song&canonical=1");
+        Uri uriAfterRestoredUncanonicalized = Uri.parse("content://media/100");
+        Uri uriAfterRestoredCanonicalized = Uri.parse("content://media/100?title=Song&canonical=1");
+
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {"_id"});
+        cursor.addRow(new Object[] {100L});
+
+        when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredUncanonicalized)))
+                .thenReturn(uriToBeRestoredCanonicalized);
+
+        // Mock the failure of regular uncanonicalize.
+        when(mIContentProvider.uncanonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+                .thenReturn(null);
+
+        // Mock the custom lookup in RingtoneManager.getRingtoneUriForRestore.
+        when(mIContentProvider.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+        // Mock the canonicalize in RingtoneManager.getRingtoneUriForRestore.
+        when(mIContentProvider.canonicalize(any(), eq(uriAfterRestoredUncanonicalized)))
+                .thenReturn(uriAfterRestoredCanonicalized);
+
+        assertThat(
+                        channel.restoreSoundUri(
+                                mContext,
+                                uriToBeRestoredUncanonicalized,
+                                true,
+                                AudioAttributes.USAGE_NOTIFICATION))
+                .isEqualTo(uriAfterRestoredCanonicalized);
+    }
+
+    @Test
+    public void testWriteXmlForBackup_customLookup_notificationUsage() throws Exception {
+        testWriteXmlForBackup_customLookup(
+                AudioAttributes.USAGE_NOTIFICATION, AudioColumns.IS_NOTIFICATION);
+    }
+
+    @Test
+    public void testWriteXmlForBackup_customLookup_alarmUsage() throws Exception {
+        testWriteXmlForBackup_customLookup(AudioAttributes.USAGE_ALARM, AudioColumns.IS_ALARM);
+    }
+
+    @Test
+    public void testWriteXmlForBackup_customLookup_ringtoneUsage() throws Exception {
+        testWriteXmlForBackup_customLookup(
+                AudioAttributes.USAGE_NOTIFICATION_RINGTONE, AudioColumns.IS_RINGTONE);
+    }
+
+    @Test
+    public void testWriteXmlForBackup_customLookup_unknownUsage() throws Exception {
+        testWriteXmlForBackup_customLookup(
+                AudioAttributes.USAGE_UNKNOWN, AudioColumns.IS_NOTIFICATION);
+    }
+
+    private void testWriteXmlForBackup_customLookup(int usage, String customQuerySelection)
+            throws Exception {
+        Uri uriToBeRestoredUncanonicalized = Uri.parse("content://media/1");
+        Uri uriToBeRestoredCanonicalized = Uri.parse("content://media/1?title=Song&canonical=1");
+        Uri uriAfterRestoredUncanonicalized = Uri.parse("content://media/100");
+        Uri uriAfterRestoredCanonicalized = Uri.parse("content://media/100?title=Song&canonical=1");
+
+        AudioAttributes mAudioAttributes =
+                new AudioAttributes.Builder()
+                        .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+                        .setUsage(usage)
+                        .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+                        .build();
+
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+        channel.setSound(uriToBeRestoredCanonicalized, mAudioAttributes);
+
+        TypedXmlSerializer serializer = Xml.newFastSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+
+        // mock the canonicalize in writeXmlForBackup -> getSoundForBackup
+        when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredUncanonicalized)))
+                .thenReturn(uriToBeRestoredCanonicalized);
+        when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+                .thenReturn(uriToBeRestoredCanonicalized);
+
+        channel.writeXmlForBackup(serializer, mContext);
+        serializer.endDocument();
+        serializer.flush();
+
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        byte[] byteArray = baos.toByteArray();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
+        parser.nextTag();
+
+        NotificationChannel targetChannel = new NotificationChannel("id", "name", 3);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {"_id"});
+        cursor.addRow(new Object[] {100L});
+
+        when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+                .thenReturn(uriToBeRestoredCanonicalized);
+
+        // Mock the failure of regular uncanonicalize.
+        when(mIContentProvider.uncanonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+                .thenReturn(null);
+
+        Bundle expectedBundle =
+                ContentResolver.createSqlQueryBundle(
+                        customQuerySelection + "=1 AND title=?", new String[] {"Song"}, null);
+
+        // Mock the custom lookup in RingtoneManager.getRingtoneUriForRestore.
+        when(mIContentProvider.query(
+                        any(),
+                        any(),
+                        any(),
+                        // any(),
+                        argThat(
+                                queryBundle -> {
+                                    return queryBundle != null
+                                            && expectedBundle
+                                                    .toString()
+                                                    .equals(queryBundle.toString());
+                                }),
+                        any()))
+                .thenReturn(cursor);
+
+        // Mock the canonicalize in RingtoneManager.getRingtoneUriForRestore.
+        when(mIContentProvider.canonicalize(any(), eq(uriAfterRestoredUncanonicalized)))
+                .thenReturn(uriAfterRestoredCanonicalized);
+
+        targetChannel.populateFromXmlForRestore(parser, true, mContext);
+        assertThat(targetChannel.getSound()).isEqualTo(uriAfterRestoredCanonicalized);
+    }
 }
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index eba7f58..7c69e65 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -271,6 +271,54 @@
     }
 
     @Test
+    public void allPendingIntents_resilientToAnotherNotificationInExtras() {
+        PendingIntent contentIntent = createPendingIntent("content");
+        PendingIntent actionIntent = createPendingIntent("action");
+        Notification another = new Notification.Builder(mContext, "channel").build();
+        Bundle bundleContainingAnotherNotification = new Bundle();
+        bundleContainingAnotherNotification.putParcelable(null, another);
+        Notification source = new Notification.Builder(mContext, "channel")
+                .setContentIntent(contentIntent)
+                .addAction(new Notification.Action.Builder(null, "action", actionIntent).build())
+                .setExtras(bundleContainingAnotherNotification)
+                .build();
+
+        Parcel p = Parcel.obtain();
+        source.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        Notification unparceled = new Notification(p);
+
+        assertThat(unparceled.allPendingIntents).containsExactly(contentIntent, actionIntent);
+    }
+
+    @Test
+    public void allPendingIntents_alsoInPublicVersion() {
+        PendingIntent contentIntent = createPendingIntent("content");
+        PendingIntent actionIntent = createPendingIntent("action");
+        PendingIntent publicContentIntent = createPendingIntent("publicContent");
+        PendingIntent publicActionIntent = createPendingIntent("publicAction");
+        Notification source = new Notification.Builder(mContext, "channel")
+                .setContentIntent(contentIntent)
+                .addAction(new Notification.Action.Builder(null, "action", actionIntent).build())
+                .setPublicVersion(new Notification.Builder(mContext, "channel")
+                        .setContentIntent(publicContentIntent)
+                        .addAction(new Notification.Action.Builder(
+                                null, "publicAction", publicActionIntent).build())
+                        .build())
+                .build();
+
+        Parcel p = Parcel.obtain();
+        source.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        Notification unparceled = new Notification(p);
+
+        assertThat(unparceled.allPendingIntents).containsExactly(contentIntent, actionIntent,
+                publicContentIntent, publicActionIntent);
+        assertThat(unparceled.publicVersion.allPendingIntents).containsExactly(publicContentIntent,
+                publicActionIntent);
+    }
+
+    @Test
     public void messagingStyle_isGroupConversation() {
         mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
         Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index 81eb213..5ac99db 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -128,6 +128,7 @@
                 0x222222,                // colorBackground
                 0x333333,                // statusBarColor
                 0x444444,                // navigationBarColor
+                0,                       // statusBarAppearance
                 true,                    // ensureStatusBarContrastWhenTransparent
                 true,                    // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_RESIZEABLE,  // resizeMode
@@ -152,6 +153,7 @@
                 0x222222,                  // colorBackground
                 0x333333,                  // statusBarColor
                 0x444444,                  // navigationBarColor
+                0,                         // statusBarAppearance
                 false,                     // ensureStatusBarContrastWhenTransparent
                 false,                     // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_UNRESIZEABLE,  // resizeMode
@@ -167,6 +169,7 @@
                 0x2222222,               // colorBackground
                 0x3333332,               // statusBarColor
                 0x4444442,               // navigationBarColor
+                0,                       // statusBarAppearance
                 true,                    // ensureStatusBarContrastWhenTransparent
                 true,                    // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_RESIZEABLE,  // resizeMode
@@ -197,6 +200,7 @@
                 0x222222,                  // colorBackground
                 0x333333,                  // statusBarColor
                 0x444444,                  // navigationBarColor
+                0,                         // statusBarAppearance
                 false,                     // ensureStatusBarContrastWhenTransparent
                 false,                     // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_UNRESIZEABLE,  // resizeMode
@@ -219,6 +223,7 @@
                 0x222222,                  // colorBackground
                 0x333333,                  // statusBarColor
                 0x444444,                  // navigationBarColor
+                0,                         // statusBarAppearance
                 false,                     // ensureStatusBarContrastWhenTransparent
                 false,                     // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_UNRESIZEABLE,  // resizeMode
@@ -250,6 +255,7 @@
             assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
             assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
             assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
+            assertEquals(td1.getStatusBarAppearance(), td2.getStatusBarAppearance());
             assertEquals(td1.getResizeMode(), td2.getResizeMode());
             assertEquals(td1.getMinWidth(), td2.getMinWidth());
             assertEquals(td1.getMinHeight(), td2.getMinHeight());
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index b73a87c..f9a7148 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -451,8 +451,10 @@
         final Rect bounds = activity.getWindowManager().getCurrentWindowMetrics().getBounds();
         assertEquals(activityConfigPortrait.windowConfiguration.getBounds(), bounds);
 
-        // Ensure changes in window configuration bounds are reported
-        assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
+        // Ensure that Activity#onConfigurationChanged() not be called because the changes in
+        // WindowConfiguration shouldn't be reported, and we only apply the latest Configuration
+        // update in transaction.
+        assertEquals(numOfConfig, activity.mNumOfConfigChanges);
     }
 
     @Test
@@ -884,12 +886,13 @@
             mConfig.setTo(config);
             ++mNumOfConfigChanges;
 
-            if (mConfigLatch != null) {
+            final CountDownLatch configLatch = mConfigLatch;
+            if (configLatch != null) {
                 if (mTestLatch != null) {
                     mTestLatch.countDown();
                 }
                 try {
-                    mConfigLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
+                    configLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
                 } catch (InterruptedException e) {
                     throw new IllegalStateException(e);
                 }
diff --git a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
index c6f4fa2..f8348d2 100644
--- a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
+++ b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java
@@ -22,6 +22,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Parcel;
 import android.util.ArraySet;
 import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient;
 
@@ -40,16 +41,29 @@
 @RunWith(MockitoJUnitRunner.class)
 public class ContentCaptureOptionsTest {
 
-    private final ComponentName mContextComponent = new ComponentName("marco", "polo");
-    private final ComponentName mComp1 = new ComponentName("comp", "one");
-    private final ComponentName mComp2 = new ComponentName("two", "comp");
+    private static final ComponentName CONTEXT_COMPONENT = new ComponentName("marco", "polo");
+    private static final ComponentName COMPONENT1 = new ComponentName("comp", "one");
+    private static final ComponentName COMPONENT2 = new ComponentName("two", "comp");
+    private static final ContentCaptureOptions CONTENT_CAPTURE_OPTIONS =
+            new ContentCaptureOptions(
+                    /* loggingLevel= */ 1000,
+                    /* maxBufferSize= */ 1001,
+                    /* idleFlushingFrequencyMs= */ 1002,
+                    /* textChangeFlushingFrequencyMs= */ 1003,
+                    /* logHistorySize= */ 1004,
+                    /* disableFlushForViewTreeAppearing= */ true,
+                    /* enableReceiver= */ false,
+                    new ContentCaptureOptions.ContentProtectionOptions(
+                            /* enableReceiver= */ true,
+                            /* bufferSize= */ 2001),
+                    /* whitelistedComponents= */ toSet(COMPONENT1, COMPONENT2));
 
     @Mock private Context mContext;
     @Mock private ContentCaptureClient mClient;
 
     @Before
     public void setExpectation() {
-        when(mClient.contentCaptureClientGetComponentName()).thenReturn(mContextComponent);
+        when(mClient.contentCaptureClientGetComponentName()).thenReturn(CONTEXT_COMPONENT);
         when(mContext.getContentCaptureClient()).thenReturn(mClient);
     }
 
@@ -67,26 +81,27 @@
 
     @Test
     public void testIsWhitelisted_notWhitelisted() {
-        ContentCaptureOptions options = new ContentCaptureOptions(toSet(mComp1, mComp2));
+        ContentCaptureOptions options = new ContentCaptureOptions(toSet(COMPONENT1, COMPONENT2));
         assertThat(options.isWhitelisted(mContext)).isFalse();
     }
 
     @Test
     public void testIsWhitelisted_whitelisted() {
-        ContentCaptureOptions options = new ContentCaptureOptions(toSet(mComp1, mContextComponent));
+        ContentCaptureOptions options =
+                new ContentCaptureOptions(toSet(COMPONENT1, CONTEXT_COMPONENT));
         assertThat(options.isWhitelisted(mContext)).isTrue();
     }
 
     @Test
     public void testIsWhitelisted_invalidContext() {
-        ContentCaptureOptions options = new ContentCaptureOptions(toSet(mContextComponent));
+        ContentCaptureOptions options = new ContentCaptureOptions(toSet(CONTEXT_COMPONENT));
         Context invalidContext = mock(Context.class); // has no client
         assertThat(options.isWhitelisted(invalidContext)).isFalse();
     }
 
     @Test
     public void testIsWhitelisted_clientWithNullComponentName() {
-        ContentCaptureOptions options = new ContentCaptureOptions(toSet(mContextComponent));
+        ContentCaptureOptions options = new ContentCaptureOptions(toSet(CONTEXT_COMPONENT));
         ContentCaptureClient client = mock(ContentCaptureClient.class);
         Context context = mock(Context.class);
         when(context.getContentCaptureClient()).thenReturn(client);
@@ -94,8 +109,69 @@
         assertThat(options.isWhitelisted(context)).isFalse();
     }
 
+    @Test
+    public void testToString() {
+        String actual = CONTENT_CAPTURE_OPTIONS.toString();
+
+        String expected =
+                new StringBuilder("ContentCaptureOptions [")
+                        .append("loggingLevel=")
+                        .append(CONTENT_CAPTURE_OPTIONS.loggingLevel)
+                        .append(", maxBufferSize=")
+                        .append(CONTENT_CAPTURE_OPTIONS.maxBufferSize)
+                        .append(", idleFlushingFrequencyMs=")
+                        .append(CONTENT_CAPTURE_OPTIONS.idleFlushingFrequencyMs)
+                        .append(", textChangeFlushingFrequencyMs=")
+                        .append(CONTENT_CAPTURE_OPTIONS.textChangeFlushingFrequencyMs)
+                        .append(", logHistorySize=")
+                        .append(CONTENT_CAPTURE_OPTIONS.logHistorySize)
+                        .append(", disableFlushForViewTreeAppearing=")
+                        .append(CONTENT_CAPTURE_OPTIONS.disableFlushForViewTreeAppearing)
+                        .append(", enableReceiver=")
+                        .append(CONTENT_CAPTURE_OPTIONS.enableReceiver)
+                        .append(", contentProtectionOptions=ContentProtectionOptions [")
+                        .append("enableReceiver=")
+                        .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver)
+                        .append(", bufferSize=")
+                        .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize)
+                        .append("], whitelisted=")
+                        .append(CONTENT_CAPTURE_OPTIONS.whitelistedComponents)
+                        .append(']')
+                        .toString();
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testParcelSerializationDeserialization() {
+        Parcel parcel = Parcel.obtain();
+        CONTENT_CAPTURE_OPTIONS.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        ContentCaptureOptions actual = ContentCaptureOptions.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertThat(actual).isNotNull();
+        assertThat(actual.loggingLevel).isEqualTo(CONTENT_CAPTURE_OPTIONS.loggingLevel);
+        assertThat(actual.maxBufferSize).isEqualTo(CONTENT_CAPTURE_OPTIONS.maxBufferSize);
+        assertThat(actual.idleFlushingFrequencyMs)
+                .isEqualTo(CONTENT_CAPTURE_OPTIONS.idleFlushingFrequencyMs);
+        assertThat(actual.textChangeFlushingFrequencyMs)
+                .isEqualTo(CONTENT_CAPTURE_OPTIONS.textChangeFlushingFrequencyMs);
+        assertThat(actual.logHistorySize).isEqualTo(CONTENT_CAPTURE_OPTIONS.logHistorySize);
+        assertThat(actual.disableFlushForViewTreeAppearing)
+                .isEqualTo(CONTENT_CAPTURE_OPTIONS.disableFlushForViewTreeAppearing);
+        assertThat(actual.enableReceiver).isEqualTo(CONTENT_CAPTURE_OPTIONS.enableReceiver);
+        assertThat(actual.contentProtectionOptions).isNotNull();
+        assertThat(actual.contentProtectionOptions.enableReceiver)
+                .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver);
+        assertThat(actual.contentProtectionOptions.bufferSize)
+                .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize);
+        assertThat(actual.whitelistedComponents)
+                .containsExactlyElementsIn(CONTENT_CAPTURE_OPTIONS.whitelistedComponents);
+    }
+
     @NonNull
-    private ArraySet<ComponentName> toSet(@Nullable ComponentName... comps) {
+    private static ArraySet<ComponentName> toSet(@Nullable ComponentName... comps) {
         ArraySet<ComponentName> set = new ArraySet<>();
         if (comps != null) {
             for (int i = 0; i < comps.length; i++) {
diff --git a/core/tests/coretests/src/android/content/TEST_MAPPING b/core/tests/coretests/src/android/content/TEST_MAPPING
new file mode 100644
index 0000000..bbc2458
--- /dev/null
+++ b/core/tests/coretests/src/android/content/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.content.ContentCaptureOptionsTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index df4fb44..0941a2b 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -137,7 +137,7 @@
             );
         });
 
-        PollingCheck.waitFor(/* timeout= */ 5000, () -> {
+        PollingCheck.waitFor(/* timeout= */ 7000, () -> {
             AtomicBoolean isActivityAtCorrectScale = new AtomicBoolean(false);
             rule.getScenario().onActivity(activity ->
                     isActivityAtCorrectScale.set(
@@ -146,12 +146,7 @@
                                 .fontScale == fontScale
                     )
             );
-            return isActivityAtCorrectScale.get() && InstrumentationRegistry
-                    .getInstrumentation()
-                    .getContext()
-                    .getResources()
-                    .getConfiguration()
-                    .fontScale == fontScale;
+            return isActivityAtCorrectScale.get();
         });
     }
 
diff --git a/core/tests/coretests/src/android/flags/FeatureFlagsTest.java b/core/tests/coretests/src/android/flags/FeatureFlagsTest.java
new file mode 100644
index 0000000..3fc9439
--- /dev/null
+++ b/core/tests/coretests/src/android/flags/FeatureFlagsTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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 android.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+@SmallTest
+@Presubmit
+public class FeatureFlagsTest {
+
+    IFeatureFlagsFake mIFeatureFlagsFake = new IFeatureFlagsFake();
+    FeatureFlags mFeatureFlags = new FeatureFlags(mIFeatureFlagsFake);
+
+    @Before
+    public void setup() {
+        FeatureFlags.setInstance(mFeatureFlags);
+    }
+
+    @Test
+    public void testFusedOff_Disabled() {
+        FusedOffFlag flag = FeatureFlags.fusedOffFlag("test", "a");
+        assertThat(mFeatureFlags.isEnabled(flag)).isFalse();
+    }
+
+    @Test
+    public void testFusedOn_Enabled() {
+        FusedOnFlag flag = FeatureFlags.fusedOnFlag("test", "a");
+        assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+    }
+
+    @Test
+    public void testBooleanFlag_DefaultDisabled() {
+        BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", false);
+        assertThat(mFeatureFlags.isEnabled(flag)).isFalse();
+    }
+
+    @Test
+    public void testBooleanFlag_DefaultEnabled() {
+        BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", true);
+        assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+    }
+
+    @Test
+    public void testDynamicBooleanFlag_DefaultDisabled() {
+        DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+        assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isFalse();
+    }
+
+    @Test
+    public void testDynamicBooleanFlag_DefaultEnabled() {
+        DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", true);
+        assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue();
+    }
+
+    @Test
+    public void testBooleanFlag_OverrideBeforeRead() {
+        BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", false);
+        SyncableFlag syncableFlag = new SyncableFlag(
+                flag.getNamespace(), flag.getName(), "true", false);
+
+        mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+        assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+    }
+
+    @Test
+    public void testFusedOffFlag_OverrideHasNoEffect() {
+        FusedOffFlag flag = FeatureFlags.fusedOffFlag("test", "a");
+        SyncableFlag syncableFlag = new SyncableFlag(
+                flag.getNamespace(), flag.getName(), "true", false);
+
+        mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+        assertThat(mFeatureFlags.isEnabled(flag)).isFalse();
+    }
+
+    @Test
+    public void testFusedOnFlag_OverrideHasNoEffect() {
+        FusedOnFlag flag = FeatureFlags.fusedOnFlag("test", "a");
+        SyncableFlag syncableFlag = new SyncableFlag(
+                flag.getNamespace(), flag.getName(), "false", false);
+
+        mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+        assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+    }
+
+    @Test
+    public void testDynamicFlag_OverrideBeforeRead() {
+        DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+        SyncableFlag syncableFlag = new SyncableFlag(
+                flag.getNamespace(), flag.getName(), "true", true);
+
+        mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+        // Changes to true
+        assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue();
+    }
+
+    @Test
+    public void testDynamicFlag_OverrideAfterRead() {
+        DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+        SyncableFlag syncableFlag = new SyncableFlag(
+                flag.getNamespace(), flag.getName(), "true", true);
+
+        // Starts false
+        assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isFalse();
+
+        mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+        // Changes to true
+        assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue();
+    }
+
+    @Test
+    public void testDynamicFlag_FiresListener() {
+        DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+        AtomicBoolean called = new AtomicBoolean(false);
+        FeatureFlags.ChangeListener listener = flag1 -> called.set(true);
+
+        mFeatureFlags.addChangeListener(listener);
+
+        SyncableFlag syncableFlag = new SyncableFlag(
+                flag.getNamespace(), flag.getName(), flag.getDefault().toString(), true);
+
+        mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+        // Fires listener.
+        assertThat(called.get()).isTrue();
+    }
+}
diff --git a/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java b/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java
new file mode 100644
index 0000000..bc5d8aa
--- /dev/null
+++ b/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.flags;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+class IFeatureFlagsFake implements IFeatureFlags {
+
+    private final Set<IFeatureFlagsCallback> mCallbacks = new HashSet<>();
+
+    List<SyncableFlag> mOverrides;
+
+    @Override
+    public IBinder asBinder() {
+        return null;
+    }
+
+    @Override
+    public List<SyncableFlag> syncFlags(List<SyncableFlag> flagList) {
+        return mOverrides == null ? flagList : mOverrides;
+    }
+
+    @Override
+    public List<SyncableFlag> queryFlags(List<SyncableFlag> flagList) {
+        return mOverrides == null ? flagList : mOverrides;    }
+
+    @Override
+    public void overrideFlag(SyncableFlag syncableFlag) {
+        SyncableFlag match = findFlag(syncableFlag);
+        if (match != null) {
+            mOverrides.remove(match);
+        }
+
+        mOverrides.add(syncableFlag);
+
+        for (IFeatureFlagsCallback cb : mCallbacks) {
+            try {
+                cb.onFlagChange(syncableFlag);
+            } catch (RemoteException e) {
+                // does not happen in fakes.
+            }
+        }
+    }
+
+    @Override
+    public void resetFlag(SyncableFlag syncableFlag) {
+        SyncableFlag match = findFlag(syncableFlag);
+        if (match != null) {
+            mOverrides.remove(match);
+        }
+
+        for (IFeatureFlagsCallback cb : mCallbacks) {
+            try {
+                cb.onFlagChange(syncableFlag);
+            } catch (RemoteException e) {
+                // does not happen in fakes.
+            }
+        }
+    }
+
+    private SyncableFlag findFlag(SyncableFlag syncableFlag) {
+        SyncableFlag match = null;
+        for (SyncableFlag sf : mOverrides) {
+            if (sf.getName().equals(syncableFlag.getName())
+                    && sf.getNamespace().equals(syncableFlag.getNamespace())) {
+                match = sf;
+                break;
+            }
+        }
+
+        return match;
+    }
+    @Override
+    public void registerCallback(IFeatureFlagsCallback callback) {
+        mCallbacks.add(callback);
+    }
+
+    @Override
+    public void unregisterCallback(IFeatureFlagsCallback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    public void setFlagOverrides(List<SyncableFlag> flagList) {
+        mOverrides = flagList;
+        for (SyncableFlag sf : flagList) {
+            for (IFeatureFlagsCallback cb : mCallbacks) {
+                try {
+                    cb.onFlagChange(sf);
+                } catch (RemoteException e) {
+                    // does not happen in fakes.
+                }
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/os/AidlTest.java b/core/tests/coretests/src/android/os/AidlTest.java
index 5f54b09..d0c3470 100644
--- a/core/tests/coretests/src/android/os/AidlTest.java
+++ b/core/tests/coretests/src/android/os/AidlTest.java
@@ -28,12 +28,14 @@
 
     private IAidlTest mRemote;
     private AidlObject mLocal;
+    private NonAutoGeneratedObject mNonAutoGenerated;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         mLocal = new AidlObject();
         mRemote = IAidlTest.Stub.asInterface(mLocal);
+        mNonAutoGenerated = new NonAutoGeneratedObject("NonAutoGeneratedObject");
     }
 
     private static boolean check(TestParcelable p, int n, String s) {
@@ -84,6 +86,12 @@
         }
     }
 
+    private static class NonAutoGeneratedObject extends Binder {
+        NonAutoGeneratedObject(String descriptor) {
+            super(descriptor);
+        }
+    }
+
     private static class AidlObject extends IAidlTest.Stub {
         public IInterface queryLocalInterface(String descriptor) {
             // overriding this to return null makes asInterface always
@@ -194,7 +202,7 @@
                 TestParcelable[] a1, TestParcelable[] a2) {
             return null;
         }
-        
+
         public void voidSecurityException() {
             throw new SecurityException("gotcha!");
         }
@@ -396,7 +404,7 @@
         assertEquals("s2[1]", s2[1]);
         assertEquals("s2[2]", s2[2]);
     }
-    
+
     @SmallTest
     public void testVoidSecurityException() throws Exception {
         boolean good = false;
@@ -407,7 +415,7 @@
         }
         assertEquals(good, true);
     }
-    
+
     @SmallTest
     public void testIntSecurityException() throws Exception {
         boolean good = false;
@@ -420,7 +428,7 @@
     }
 
     @SmallTest
-    public void testGetTransactionName() throws Exception {
+    public void testGetTransactionNameAutoGenerated() throws Exception {
         assertEquals(15, mLocal.getMaxTransactionId());
 
         assertEquals("booleanArray",
@@ -430,12 +438,21 @@
         assertEquals("parcelableIn",
                 mLocal.getTransactionName(IAidlTest.Stub.TRANSACTION_parcelableIn));
 
-        assertEquals("IAidlTest:booleanArray",
+        assertEquals("AIDL::java::IAidlTest::booleanArray::server",
                 mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_booleanArray));
-        assertEquals("IAidlTest:voidSecurityException",
+        assertEquals("AIDL::java::IAidlTest::voidSecurityException::server",
                 mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_voidSecurityException));
-        assertEquals("IAidlTest:parcelableIn",
+        assertEquals("AIDL::java::IAidlTest::parcelableIn::server",
                 mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_parcelableIn));
     }
-}
 
+    @SmallTest
+    public void testGetTransactionNameNonAutoGenerated() throws Exception {
+        assertEquals(0, mNonAutoGenerated.getMaxTransactionId());
+
+        assertEquals("AIDL::java::NonAutoGeneratedObject::#0::server",
+                mNonAutoGenerated.getTransactionTraceName(0));
+        assertEquals("AIDL::java::NonAutoGeneratedObject::#1::server",
+                mNonAutoGenerated.getTransactionTraceName(1));
+    }
+}
diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
new file mode 100644
index 0000000..a84ac55
--- /dev/null
+++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class NotificationRankingUpdateTest {
+
+    private static final String NOTIFICATION_CHANNEL_ID = "test_channel_id";
+    private static final String TEST_KEY = "key";
+
+    private NotificationChannel mNotificationChannel;
+
+    // TODO(b/284297289): remove this flag set once resolved.
+    @Parameterized.Parameters(name = "rankingUpdateAshmem={0}")
+    public static Boolean[] getRankingUpdateAshmem() {
+        return new Boolean[] { true, false };
+    }
+
+    @Parameterized.Parameter
+    public boolean mRankingUpdateAshmem;
+
+    @Before
+    public void setUp() {
+        mNotificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "test channel",
+                NotificationManager.IMPORTANCE_DEFAULT);
+
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = flag -> {
+            if (flag.mSysPropKey.equals(RANKING_UPDATE_ASHMEM.mSysPropKey)) {
+                return mRankingUpdateAshmem;
+            }
+            return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag);
+        };
+    }
+
+    @After
+    public void tearDown() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
+    }
+
+    public NotificationListenerService.Ranking createTestRanking(String key, int rank) {
+        NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
+        ranking.populate(
+                /* key= */ key,
+                /* rank= */ rank,
+                /* matchesInterruptionFilter= */ false,
+                /* visibilityOverride= */ 0,
+                /* suppressedVisualEffects= */ 0,
+                mNotificationChannel.getImportance(),
+                /* explanation= */ null,
+                /* overrideGroupKey= */ null,
+                mNotificationChannel,
+                /* overridePeople= */ null,
+                /* snoozeCriteria= */ null,
+                /* showBadge= */ true,
+                /* userSentiment= */ 0,
+                /* hidden= */ false,
+                /* lastAudiblyAlertedMs= */ -1,
+                /* noisy= */ false,
+                /* smartActions= */ null,
+                /* smartReplies= */ null,
+                /* canBubble= */ false,
+                /* isTextChanged= */ false,
+                /* isConversation= */ false,
+                /* shortcutInfo= */ null,
+                /* rankingAdjustment= */ 0,
+                /* isBubble= */ false,
+                /* proposedImportance= */ 0,
+                /* sensitiveContent= */ false
+        );
+        return ranking;
+    }
+
+    @Test
+    public void testRankingUpdate_rankingConstructor() {
+        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+
+        NotificationListenerService.RankingMap retrievedRankings = rankingUpdate.getRankingMap();
+        NotificationListenerService.Ranking retrievedRanking =
+                new NotificationListenerService.Ranking();
+        assertTrue(retrievedRankings.getRanking(TEST_KEY, retrievedRanking));
+        assertEquals(123, retrievedRanking.getRank());
+    }
+
+    @Test
+    public void testRankingUpdate_parcelConstructor() {
+        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+
+        Parcel parceledRankingUpdate = Parcel.obtain();
+        rankingUpdate.writeToParcel(parceledRankingUpdate, 0);
+        parceledRankingUpdate.setDataPosition(0);
+
+        NotificationRankingUpdate retrievedRankingUpdate = new NotificationRankingUpdate(
+                parceledRankingUpdate);
+
+        NotificationListenerService.RankingMap retrievedRankings =
+                retrievedRankingUpdate.getRankingMap();
+        assertNotNull(retrievedRankings);
+        assertTrue(retrievedRankingUpdate.isFdNotNullAndClosed());
+        NotificationListenerService.Ranking retrievedRanking =
+                new NotificationListenerService.Ranking();
+        assertTrue(retrievedRankings.getRanking(TEST_KEY, retrievedRanking));
+        assertEquals(123, retrievedRanking.getRank());
+        assertTrue(retrievedRankingUpdate.equals(rankingUpdate));
+        parceledRankingUpdate.recycle();
+    }
+
+    @Test
+    public void testRankingUpdate_emptyParcelInCheck() {
+        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+
+        Parcel parceledRankingUpdate = Parcel.obtain();
+        rankingUpdate.writeToParcel(parceledRankingUpdate, 0);
+
+        // This will fail to read the parceledRankingUpdate, because the data position hasn't
+        // been reset, so it'll find no data to read.
+        NotificationRankingUpdate retrievedRankingUpdate = new NotificationRankingUpdate(
+                parceledRankingUpdate);
+        assertNull(retrievedRankingUpdate.getRankingMap());
+    }
+
+    @Test
+    public void testRankingUpdate_describeContents() {
+        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+        assertEquals(0, rankingUpdate.describeContents());
+    }
+
+    @Test
+    public void testRankingUpdate_equals() {
+        NotificationListenerService.Ranking ranking = createTestRanking(TEST_KEY, 123);
+        NotificationRankingUpdate rankingUpdate = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking});
+        // Reflexive equality.
+        assertTrue(rankingUpdate.equals(rankingUpdate));
+        // Null or wrong class inequality.
+        assertFalse(rankingUpdate.equals(null));
+        assertFalse(rankingUpdate.equals(ranking));
+
+        // Different ranking contents inequality.
+        NotificationListenerService.Ranking ranking2 = createTestRanking(TEST_KEY, 456);
+        NotificationRankingUpdate rankingUpdate2 = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking2});
+        assertFalse(rankingUpdate.equals(rankingUpdate2));
+
+        // Same ranking contents equality.
+        ranking2 = createTestRanking(TEST_KEY, 123);
+        rankingUpdate2 = new NotificationRankingUpdate(
+                new NotificationListenerService.Ranking[]{ranking2});
+        assertTrue(rankingUpdate.equals(rankingUpdate2));
+    }
+}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index 925da49..0ebf03f 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -24,7 +24,6 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
-import android.graphics.text.LineBreakConfig;
 import android.os.LocaleList;
 import android.platform.test.annotations.Presubmit;
 import android.text.Layout.Alignment;
@@ -926,24 +925,4 @@
         assertEquals(0, layout.getHeight(true));
         assertEquals(2, layout.getLineCount());
     }
-
-    @Test
-    public void testBuilder_autoPhraseBreaking() {
-        {
-            // setAutoPhraseBreaking true
-            LineBreakConfig lineBreakConfig = new LineBreakConfig.Builder()
-                    .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_STYLE_NONE)
-                    .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
-                    .setAutoPhraseBreaking(true)
-                    .build();
-            final String text = "これが正解。";
-            // Obtain.
-            StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0,
-                    text.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
-            builder.setLineBreakConfig(lineBreakConfig);
-            builder.build();
-            assertEquals(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE,
-                    builder.getLineBreakWordStyle());
-        }
-    }
 }
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 0692052..b8f0d5c 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -131,10 +131,10 @@
             mTestClock = new OffsettableClock();
             mTestHandler = new TestHandler(null, mTestClock);
             mTestHost = spy(new TestHost(mViewRoot));
-            mController = new InsetsController(mTestHost, (controller, source) -> {
-                if (source.getType() == ime()) {
-                    return new InsetsSourceConsumer(source.getId(), source.getType(),
-                            controller.getState(), Transaction::new, controller) {
+            mController = new InsetsController(mTestHost, (controller, id, type) -> {
+                if (type == ime()) {
+                    return new InsetsSourceConsumer(id, type, controller.getState(),
+                            Transaction::new, controller) {
 
                         private boolean mImeRequestedShow;
 
@@ -150,8 +150,8 @@
                         }
                     };
                 } else {
-                    return new InsetsSourceConsumer(source.getId(), source.getType(),
-                            controller.getState(), Transaction::new, controller);
+                    return new InsetsSourceConsumer(id, type, controller.getState(),
+                            Transaction::new, controller);
                 }
             }, mTestHandler);
             final Rect rect = new Rect(5, 5, 5, 5);
@@ -182,7 +182,8 @@
     @Test
     public void testControlsChanged() {
         mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
-        assertNotNull(mController.getSourceConsumer(mStatusSource).getControl().getLeash());
+        assertNotNull(
+                mController.getSourceConsumer(ID_STATUS_BAR, statusBars()).getControl().getLeash());
         mController.addOnControllableInsetsChangedListener(
                 ((controller, typeMask) -> assertEquals(statusBars(), typeMask)));
     }
@@ -194,7 +195,7 @@
         mController.addOnControllableInsetsChangedListener(listener);
         mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
         mController.onControlsChanged(new InsetsSourceControl[0]);
-        assertNull(mController.getSourceConsumer(mStatusSource).getControl());
+        assertNull(mController.getSourceConsumer(ID_STATUS_BAR, statusBars()).getControl());
         InOrder inOrder = Mockito.inOrder(listener);
         inOrder.verify(listener).onControllableInsetsChanged(eq(mController), eq(0));
         inOrder.verify(listener).onControllableInsetsChanged(eq(mController), eq(statusBars()));
@@ -254,7 +255,7 @@
         // only the original thread that created view hierarchy can touch its views
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
-            mController.getSourceConsumer(mImeSource).onWindowFocusGained(true);
+            mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
             mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             // When using the animation thread, this must not invoke onReady()
@@ -271,7 +272,7 @@
         prepareControls();
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mController.getSourceConsumer(mImeSource).onWindowFocusGained(true);
+            mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
             mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             mController.show(all());
@@ -284,7 +285,7 @@
             mController.hide(all());
             mController.cancelExistingAnimations();
             assertEquals(0, mController.getRequestedVisibleTypes() & types);
-            mController.getSourceConsumer(mImeSource).onWindowFocusLost();
+            mController.getSourceConsumer(ID_IME, ime()).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -294,14 +295,14 @@
         InsetsSourceControl ime = createControl(ID_IME, ime());
         mController.onControlsChanged(new InsetsSourceControl[] { ime });
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mController.getSourceConsumer(mImeSource).onWindowFocusGained(true);
+            mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
             mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             mController.cancelExistingAnimations();
             assertTrue(isRequestedVisible(mController, ime()));
             mController.hide(ime(), true /* fromIme */, null /* statsToken */);
             mController.cancelExistingAnimations();
             assertFalse(isRequestedVisible(mController, ime()));
-            mController.getSourceConsumer(mImeSource).onWindowFocusLost();
+            mController.getSourceConsumer(ID_IME, ime()).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -914,7 +915,7 @@
             // Simulate IME insets is not controllable
             mController.onControlsChanged(new InsetsSourceControl[0]);
             final InsetsSourceConsumer imeInsetsConsumer =
-                    mController.getSourceConsumer(mImeSource);
+                    mController.getSourceConsumer(ID_IME, ime());
             assertNull(imeInsetsConsumer.getControl());
 
             // Verify IME requested visibility should be updated to IME consumer from controller.
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 988e690..655cb45 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -219,10 +219,10 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             InsetsState state = new InsetsState();
             ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot);
-            InsetsController insetsController = new InsetsController(host, (controller, source) -> {
-                if (source.getType() == ime()) {
+            InsetsController insetsController = new InsetsController(host, (ic, id, type) -> {
+                if (type == ime()) {
                     return new InsetsSourceConsumer(ID_IME, ime(), state,
-                            () -> mMockTransaction, controller) {
+                            () -> mMockTransaction, ic) {
                         @Override
                         public int requestShow(boolean fromController,
                                 ImeTracker.Token statsToken) {
@@ -230,11 +230,9 @@
                         }
                     };
                 }
-                return new InsetsSourceConsumer(source.getId(), source.getType(),
-                        controller.getState(), Transaction::new, controller);
+                return new InsetsSourceConsumer(id, type, ic.getState(), Transaction::new, ic);
             }, host.getHandler());
-            InsetsSource imeSource = new InsetsSource(ID_IME, ime());
-            InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(imeSource);
+            InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(ID_IME, ime());
 
             // Initial IME insets source control with its leash.
             imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash,
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index fde1a6d..b06cd39 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -43,6 +43,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -290,6 +291,18 @@
     }
 
     @Test
+    public void testCalculateInsets_emptyIme() {
+        WindowInsets insets1 = mState.calculateInsets(new Rect(), null, false, false,
+                SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+        mState.getOrCreateSource(ID_IME, ime());
+        WindowInsets insets2 = mState.calculateInsets(new Rect(), null, false, false,
+                SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+        assertEquals(Insets.NONE, insets1.getInsets(ime()));
+        assertEquals(Insets.NONE, insets2.getInsets(ime()));
+        assertEquals(insets1, insets2);
+    }
+
+    @Test
     public void testStripForDispatch() {
         mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
                 .setFrame(new Rect(0, 0, 100, 100))
@@ -304,6 +317,73 @@
     }
 
     @Test
+    public void testEquals() {
+        final InsetsState state1 = new InsetsState();
+        final InsetsState state2 = new InsetsState();
+        assertTrue(state1.equals(state2));
+
+        state1.addSource(new InsetsSource(ID_STATUS_BAR, statusBars()));
+        assertFalse(state1.equals(state2));
+
+        state2.addSource(new InsetsSource(ID_STATUS_BAR, statusBars()));
+        assertTrue(state1.equals(state2));
+
+        state2.addSource(new InsetsSource(ID_NAVIGATION_BAR, navigationBars()));
+        assertFalse(state1.equals(state2));
+    }
+
+    @Test
+    public void testEquals_excludesCaptionBar() {
+        final InsetsState state1 = new InsetsState();
+        final InsetsState state2 = new InsetsState();
+
+        state1.addSource(new InsetsSource(ID_CAPTION_BAR, captionBar()).setFrame(0, 0, 0, 5));
+        assertFalse(state1.equals(
+                state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+        assertTrue(state1.equals(
+                state2, true /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+
+        state2.addSource(new InsetsSource(ID_CAPTION_BAR, captionBar()).setFrame(0, 0, 0, 10));
+        assertFalse(state1.equals(
+                state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+        assertTrue(state1.equals(
+                state2, true /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+
+        state1.addSource(new InsetsSource(ID_STATUS_BAR, statusBars()));
+        state2.addSource(new InsetsSource(ID_STATUS_BAR, statusBars()));
+        assertFalse(state1.equals(
+                state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+        assertTrue(state1.equals(
+                state2, true /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+    }
+
+    @Test
+    public void testEquals_excludesInvisibleIme() {
+        final InsetsState state1 = new InsetsState();
+        final InsetsState state2 = new InsetsState();
+
+        final InsetsSource imeSource1 = new InsetsSource(ID_IME, ime()).setVisible(true);
+        state1.addSource(imeSource1);
+        assertFalse(state1.equals(
+                state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+        assertFalse(state1.equals(
+                state2, false /* excludesCaptionBar */, true /* excludesInvisibleIme */));
+
+        imeSource1.setVisible(false);
+        assertFalse(state1.equals(
+                state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+        assertTrue(state1.equals(
+                state2, false /* excludesCaptionBar */, true /* excludesInvisibleIme */));
+
+        final InsetsSource imeSource2 = new InsetsSource(ID_IME, ime()).setFrame(0, 0, 0, 10);
+        state2.addSource(imeSource2);
+        assertFalse(state1.equals(
+                state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+        assertTrue(state1.equals(
+                state2, false /* excludesCaptionBar */, true /* excludesInvisibleIme */));
+    }
+
+    @Test
     public void testEquals_differentRect() {
         mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
                 .setFrame(new Rect(0, 0, 100, 100));
@@ -404,17 +484,6 @@
     }
 
     @Test
-    public void testEquals_excludeInvisibleIme() {
-        mState.getOrCreateSource(ID_IME, ime())
-                .setFrame(new Rect(0, 0, 100, 100))
-                .setVisible(false);
-        mState2.getOrCreateSource(ID_IME, ime())
-                .setFrame(new Rect(0, 0, 100, 200))
-                .setVisible(false);
-        assertTrue(mState2.equals(mState, true, true /* excludeInvisibleIme */));
-    }
-
-    @Test
     public void testParcelUnparcel() {
         mState.getOrCreateSource(ID_IME, ime())
                 .setFrame(new Rect(0, 0, 100, 100))
diff --git a/core/tests/coretests/src/android/view/ViewGroupTest.java b/core/tests/coretests/src/android/view/ViewGroupTest.java
index b37c8fd..bce3f3e 100644
--- a/core/tests/coretests/src/android/view/ViewGroupTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupTest.java
@@ -49,11 +49,11 @@
     public void testDispatchMouseEventsUnderCursor() {
         final Context context = getInstrumentation().getContext();
         final TestView viewGroup = new TestView(context, 0 /* left */, 0 /* top */,
-                200 /* right */, 200 /* bottom */);
+                200 /* right */, 100 /* bottom */);
         final TestView viewA = spy(new TestView(context, 0 /* left */, 0 /* top */,
-                100 /* right */, 200 /* bottom */));
+                100 /* right */, 100 /* bottom */));
         final TestView viewB = spy(new TestView(context, 100 /* left */, 0 /* top */,
-                200 /* right */, 200 /* bottom */));
+                200 /* right */, 100 /* bottom */));
 
         viewGroup.addView(viewA);
         viewGroup.addView(viewB);
@@ -73,10 +73,10 @@
         MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[2];
         coords[0] = new MotionEvent.PointerCoords();
         coords[0].x = 80;
-        coords[0].y = 100;
+        coords[0].y = 50;
         coords[1] = new MotionEvent.PointerCoords();
         coords[1].x = 240;
-        coords[1].y = 100;
+        coords[1].y = 50;
 
         MotionEvent event;
         // Make sure the down event is active with a pointer which coordinate is different from the
@@ -91,6 +91,10 @@
         viewGroup.onResolvePointerIcon(event, 0 /* pointerIndex */);
         verify(viewB).onResolvePointerIcon(event, 0);
 
+        event.setAction(MotionEvent.ACTION_SCROLL);
+        viewGroup.dispatchGenericMotionEvent(event);
+        verify(viewB).dispatchGenericMotionEvent(event);
+
         event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */,
                 MotionEvent.ACTION_POINTER_DOWN | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT),
                 2 /* pointerCount */, properties, coords, 0 /* metaState */, 0 /* buttonState */,
@@ -102,8 +106,13 @@
         viewGroup.onResolvePointerIcon(event, 1 /* pointerIndex */);
         verify(viewB).onResolvePointerIcon(event, 1);
 
+        event.setAction(MotionEvent.ACTION_SCROLL);
+        viewGroup.dispatchGenericMotionEvent(event);
+        verify(viewB).dispatchGenericMotionEvent(event);
+
         verify(viewA, never()).dispatchTouchEvent(any());
         verify(viewA, never()).onResolvePointerIcon(any(), anyInt());
+        verify(viewA, never()).dispatchGenericMotionEvent(any());
     }
 
     /**
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index b4ba23c..69abf5f 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -40,14 +40,14 @@
     @Test
     public void systemWindowInsets_afterConsuming_isConsumed() {
         assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null,
-                null, false, false, 0, null, null, null, null,
+                null, false, 0, 0, null, null, null, null,
                 WindowInsets.Type.systemBars(), false)
                 .consumeSystemWindowInsets().isConsumed());
     }
 
     @Test
     public void multiNullConstructor_isConsumed() {
-        assertTrue(new WindowInsets(null, null, null, false, false, 0, null, null, null, null,
+        assertTrue(new WindowInsets(null, null, null, false, 0, 0, null, null, null, null,
                 WindowInsets.Type.systemBars(), false).isConsumed());
     }
 
@@ -63,7 +63,7 @@
         boolean[] visible = new boolean[SIZE];
         WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0));
         WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
-        WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false,
+        WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, 0,
                 0, null, null, null, DisplayShape.NONE, systemBars(),
                 true /* compatIgnoreVisibility */);
         assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
index 17ed4c4..101f7c2 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
@@ -15,14 +15,17 @@
  */
 package android.view.contentcapture;
 
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.mock;
 import static org.testng.Assert.assertThrows;
 
 import android.content.ContentCaptureOptions;
 import android.content.Context;
 
+import com.android.internal.util.RingBuffer;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -37,9 +40,15 @@
 @RunWith(MockitoJUnitRunner.class)
 public class ContentCaptureManagerTest {
 
+    private static final int BUFFER_SIZE = 100;
+
+    private static final ContentCaptureOptions EMPTY_OPTIONS = new ContentCaptureOptions(null);
+
     @Mock
     private Context mMockContext;
 
+    @Mock private IContentCaptureManager mMockContentCaptureManager;
+
     @Test
     public void testConstructor_invalidParametersThrowsException() {
         assertThrows(NullPointerException.class,
@@ -48,11 +57,65 @@
     }
 
     @Test
+    public void testConstructor_contentProtection_default_bufferNotCreated() {
+        ContentCaptureManager manager =
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
+
+        assertThat(manager.getContentProtectionEventBuffer()).isNull();
+    }
+
+    @Test
+    public void testConstructor_contentProtection_disabled_bufferNotCreated() {
+        ContentCaptureOptions options =
+                createOptions(
+                        new ContentCaptureOptions.ContentProtectionOptions(
+                                /* enableReceiver= */ false, BUFFER_SIZE));
+
+        ContentCaptureManager manager =
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+
+        assertThat(manager.getContentProtectionEventBuffer()).isNull();
+    }
+
+    @Test
+    public void testConstructor_contentProtection_invalidBufferSize_bufferNotCreated() {
+        ContentCaptureOptions options =
+                createOptions(
+                        new ContentCaptureOptions.ContentProtectionOptions(
+                                /* enableReceiver= */ true, /* bufferSize= */ 0));
+
+        ContentCaptureManager manager =
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+
+        assertThat(manager.getContentProtectionEventBuffer()).isNull();
+    }
+
+    @Test
+    public void testConstructor_contentProtection_enabled_bufferCreated() {
+        ContentCaptureOptions options =
+                createOptions(
+                        new ContentCaptureOptions.ContentProtectionOptions(
+                                /* enableReceiver= */ true, BUFFER_SIZE));
+
+        ContentCaptureManager manager =
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+        RingBuffer<ContentCaptureEvent> buffer = manager.getContentProtectionEventBuffer();
+
+        assertThat(buffer).isNotNull();
+        ContentCaptureEvent[] expected = new ContentCaptureEvent[BUFFER_SIZE];
+        int offset = 3;
+        for (int i = 0; i < BUFFER_SIZE + offset; i++) {
+            ContentCaptureEvent event = new ContentCaptureEvent(i, TYPE_SESSION_STARTED);
+            buffer.append(event);
+            expected[(i + BUFFER_SIZE - offset) % BUFFER_SIZE] = event;
+        }
+        assertThat(buffer.toArray()).isEqualTo(expected);
+    }
+
+    @Test
     public void testRemoveData_invalidParametersThrowsException() {
-        final IContentCaptureManager mockService = mock(IContentCaptureManager.class);
-        final ContentCaptureOptions options = new ContentCaptureOptions(null);
         final ContentCaptureManager manager =
-                new ContentCaptureManager(mMockContext, mockService, options);
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
 
         assertThrows(NullPointerException.class, () -> manager.removeData(null));
     }
@@ -60,10 +123,8 @@
     @Test
     @SuppressWarnings("GuardedBy")
     public void testFlushViewTreeAppearingEventDisabled_setAndGet() {
-        final IContentCaptureManager mockService = mock(IContentCaptureManager.class);
-        final ContentCaptureOptions options = new ContentCaptureOptions(null);
         final ContentCaptureManager manager =
-                new ContentCaptureManager(mMockContext, mockService, options);
+                new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
 
         assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
         manager.setFlushViewTreeAppearingEventDisabled(true);
@@ -71,4 +132,18 @@
         manager.setFlushViewTreeAppearingEventDisabled(false);
         assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
     }
+
+    private ContentCaptureOptions createOptions(
+            ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) {
+        return new ContentCaptureOptions(
+                /* loggingLevel= */ 0,
+                /* maxBufferSize= */ 0,
+                /* idleFlushingFrequencyMs= */ 0,
+                /* textChangeFlushingFrequencyMs= */ 0,
+                /* logHistorySize= */ 0,
+                /* disableFlushForViewTreeAppearing= */ false,
+                /* enableReceiver= */ true,
+                contentProtectionOptions,
+                /* whitelistedComponents= */ null);
+    }
 }
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index 27d58b8..23b9b9b 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -27,9 +27,12 @@
 import android.view.autofill.AutofillId;
 import android.view.contentcapture.ViewNode.ViewStructureImpl;
 
+import com.google.common.collect.ImmutableMap;
+
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
 
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -37,6 +40,8 @@
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.util.Map;
+
 /**
  * Unit tests for {@link ContentCaptureSession}.
  *
@@ -126,6 +131,7 @@
                 () -> mSession1.notifyViewsDisappeared(new AutofillId(42, 108), new long[] {666}));
     }
 
+    @Ignore("b/286134492")
     @Test
     public void testNotifyViewsDisappeared_noSendTreeEventBeforeU() {
         MyContentCaptureSession session = new MyContentCaptureSession(121);
@@ -135,6 +141,7 @@
         assertThat(session.mInternalNotifyViewTreeEventFinishedCount).isEqualTo(0);
     }
 
+    @Ignore("b/286134492")
     @EnableCompatChanges({ContentCaptureSession.NOTIFY_NODES_DISAPPEAR_NOW_SENDS_TREE_EVENTS})
     @Test
     public void testNotifyViewsDisappeared_sendTreeEventSinceU() {
@@ -145,6 +152,34 @@
         assertThat(session.mInternalNotifyViewTreeEventFinishedCount).isEqualTo(1);
     }
 
+    @Test
+    public void testGetFlushReasonAsString() {
+        int invalidFlushReason = ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARED + 1;
+        Map<Integer, String> expectedMap =
+                new ImmutableMap.Builder<Integer, String>()
+                        .put(ContentCaptureSession.FLUSH_REASON_FULL, "FULL")
+                        .put(ContentCaptureSession.FLUSH_REASON_VIEW_ROOT_ENTERED, "VIEW_ROOT")
+                        .put(ContentCaptureSession.FLUSH_REASON_SESSION_STARTED, "STARTED")
+                        .put(ContentCaptureSession.FLUSH_REASON_SESSION_FINISHED, "FINISHED")
+                        .put(ContentCaptureSession.FLUSH_REASON_IDLE_TIMEOUT, "IDLE")
+                        .put(ContentCaptureSession.FLUSH_REASON_TEXT_CHANGE_TIMEOUT, "TEXT_CHANGE")
+                        .put(ContentCaptureSession.FLUSH_REASON_SESSION_CONNECTED, "CONNECTED")
+                        .put(ContentCaptureSession.FLUSH_REASON_FORCE_FLUSH, "FORCE_FLUSH")
+                        .put(
+                                ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARING,
+                                "VIEW_TREE_APPEARING")
+                        .put(
+                                ContentCaptureSession.FLUSH_REASON_VIEW_TREE_APPEARED,
+                                "VIEW_TREE_APPEARED")
+                        .put(invalidFlushReason, "UNKNOWN-" + invalidFlushReason)
+                        .build();
+
+        expectedMap.forEach(
+                (reason, expected) ->
+                        assertThat(ContentCaptureSession.getFlushReasonAsString(reason))
+                                .isEqualTo(expected));
+    }
+
     // Cannot use @Spy because we need to pass the session id on constructor
     private class MyContentCaptureSession extends ContentCaptureSession {
         int mInternalNotifyViewTreeEventStartedCount = 0;
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
new file mode 100644
index 0000000..3373b8b
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentcapture;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.contentprotection.ContentProtectionEventProcessor;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test for {@link MainContentCaptureSession}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksCoreTests:android.view.contentcapture.MainContentCaptureSessionTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MainContentCaptureSessionTest {
+
+    private static final int BUFFER_SIZE = 100;
+
+    private static final int REASON = 123;
+
+    private static final ContentCaptureEvent EVENT =
+            new ContentCaptureEvent(/* sessionId= */ 0, TYPE_SESSION_STARTED);
+
+    private static final ComponentName COMPONENT_NAME =
+            new ComponentName("com.test.package", "TestClass");
+
+    private static final Context sContext = ApplicationProvider.getApplicationContext();
+
+    private static final ContentCaptureManager.StrippedContext sStrippedContext =
+            new ContentCaptureManager.StrippedContext(sContext);
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private IContentCaptureManager mMockSystemServerInterface;
+
+    @Mock private ContentProtectionEventProcessor mMockContentProtectionEventProcessor;
+
+    @Mock private IContentCaptureDirectManager mMockContentCaptureDirectManager;
+
+    @Test
+    public void onSessionStarted_contentProtectionEnabled_processorCreated() {
+        MainContentCaptureSession session = createSession();
+        assertThat(session.mContentProtectionEventProcessor).isNull();
+
+        session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+        assertThat(session.mContentProtectionEventProcessor).isNotNull();
+    }
+
+    @Test
+    public void onSessionStarted_contentProtectionDisabled_processorNotCreated() {
+        MainContentCaptureSession session =
+                createSession(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ false);
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+        assertThat(session.mContentProtectionEventProcessor).isNull();
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+    }
+
+    @Test
+    public void onSessionStarted_contentProtectionNoBuffer_processorNotCreated() {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        new ContentCaptureOptions.ContentProtectionOptions(
+                                /* enableReceiver= */ true, -BUFFER_SIZE));
+        MainContentCaptureSession session = createSession(options);
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+        assertThat(session.mContentProtectionEventProcessor).isNull();
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+    }
+
+    @Test
+    public void onSessionStarted_noComponentName_processorNotCreated() {
+        MainContentCaptureSession session = createSession();
+        session.mComponentName = null;
+
+        session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+        assertThat(session.mContentProtectionEventProcessor).isNull();
+    }
+
+    @Test
+    public void sendEvent_contentCaptureDisabled_contentProtectionDisabled() {
+        MainContentCaptureSession session =
+                createSession(
+                        /* enableContentCaptureReceiver= */ false,
+                        /* enableContentProtectionReceiver= */ false);
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.sendEvent(EVENT);
+
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mEvents).isNull();
+    }
+
+    @Test
+    public void sendEvent_contentCaptureDisabled_contentProtectionEnabled() {
+        MainContentCaptureSession session =
+                createSession(
+                        /* enableContentCaptureReceiver= */ false,
+                        /* enableContentProtectionReceiver= */ true);
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.sendEvent(EVENT);
+
+        verify(mMockContentProtectionEventProcessor).processEvent(EVENT);
+        assertThat(session.mEvents).isNull();
+    }
+
+    @Test
+    public void sendEvent_contentCaptureEnabled_contentProtectionDisabled() {
+        MainContentCaptureSession session =
+                createSession(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ false);
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.sendEvent(EVENT);
+
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mEvents).isNotNull();
+        assertThat(session.mEvents).containsExactly(EVENT);
+    }
+
+    @Test
+    public void sendEvent_contentCaptureEnabled_contentProtectionEnabled() {
+        MainContentCaptureSession session = createSession();
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.sendEvent(EVENT);
+
+        verify(mMockContentProtectionEventProcessor).processEvent(EVENT);
+        assertThat(session.mEvents).isNotNull();
+        assertThat(session.mEvents).containsExactly(EVENT);
+    }
+
+    @Test
+    public void sendEvent_contentProtectionEnabled_processorNotCreated() {
+        MainContentCaptureSession session =
+                createSession(
+                        /* enableContentCaptureReceiver= */ false,
+                        /* enableContentProtectionReceiver= */ true);
+
+        session.sendEvent(EVENT);
+
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mEvents).isNull();
+    }
+
+    @Test
+    public void flush_contentCaptureDisabled_contentProtectionDisabled() throws Exception {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ false,
+                        /* enableContentProtectionReceiver= */ false);
+        MainContentCaptureSession session = createSession(options);
+        session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.flush(REASON);
+
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        verifyZeroInteractions(mMockContentCaptureDirectManager);
+        assertThat(session.mEvents).containsExactly(EVENT);
+    }
+
+    @Test
+    public void flush_contentCaptureDisabled_contentProtectionEnabled() {
+        MainContentCaptureSession session =
+                createSession(
+                        /* enableContentCaptureReceiver= */ false,
+                        /* enableContentProtectionReceiver= */ true);
+        session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.flush(REASON);
+
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        verifyZeroInteractions(mMockContentCaptureDirectManager);
+        assertThat(session.mEvents).containsExactly(EVENT);
+    }
+
+    @Test
+    public void flush_contentCaptureEnabled_contentProtectionDisabled() throws Exception {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ false);
+        MainContentCaptureSession session = createSession(options);
+        session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.flush(REASON);
+
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mEvents).isEmpty();
+        assertEventFlushedContentCapture(options);
+    }
+
+    @Test
+    public void flush_contentCaptureEnabled_contentProtectionEnabled() throws Exception {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        /* enableContentProtectionReceiver= */ true);
+        MainContentCaptureSession session = createSession(options);
+        session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+        session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+        session.flush(REASON);
+
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mEvents).isEmpty();
+        assertEventFlushedContentCapture(options);
+    }
+
+    @Test
+    public void destroySession() throws Exception {
+        MainContentCaptureSession session = createSession();
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.destroySession();
+
+        verify(mMockSystemServerInterface).finishSession(anyInt());
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mDirectServiceInterface).isNull();
+        assertThat(session.mContentProtectionEventProcessor).isNull();
+    }
+
+    @Test
+    public void resetSession() {
+        MainContentCaptureSession session = createSession();
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.resetSession(/* newState= */ 0);
+
+        verifyZeroInteractions(mMockSystemServerInterface);
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+        assertThat(session.mDirectServiceInterface).isNull();
+        assertThat(session.mContentProtectionEventProcessor).isNull();
+    }
+
+    private static ContentCaptureOptions createOptions(
+            boolean enableContentCaptureReceiver,
+            ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) {
+        return new ContentCaptureOptions(
+                /* loggingLevel= */ 0,
+                BUFFER_SIZE,
+                /* idleFlushingFrequencyMs= */ 0,
+                /* textChangeFlushingFrequencyMs= */ 0,
+                /* logHistorySize= */ 0,
+                /* disableFlushForViewTreeAppearing= */ false,
+                enableContentCaptureReceiver,
+                contentProtectionOptions,
+                /* whitelistedComponents= */ null);
+    }
+
+    private static ContentCaptureOptions createOptions(
+            boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) {
+        return createOptions(
+                enableContentCaptureReceiver,
+                new ContentCaptureOptions.ContentProtectionOptions(
+                        enableContentProtectionReceiver, BUFFER_SIZE));
+    }
+
+    private ContentCaptureManager createManager(ContentCaptureOptions options) {
+        return new ContentCaptureManager(sContext, mMockSystemServerInterface, options);
+    }
+
+    private MainContentCaptureSession createSession(ContentCaptureManager manager) {
+        MainContentCaptureSession session =
+                new MainContentCaptureSession(
+                        sStrippedContext,
+                        manager,
+                        new Handler(Looper.getMainLooper()),
+                        mMockSystemServerInterface);
+        session.mComponentName = COMPONENT_NAME;
+        return session;
+    }
+
+    private MainContentCaptureSession createSession(ContentCaptureOptions options) {
+        return createSession(createManager(options));
+    }
+
+    private MainContentCaptureSession createSession(
+            boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) {
+        return createSession(
+                createOptions(enableContentCaptureReceiver, enableContentProtectionReceiver));
+    }
+
+    private MainContentCaptureSession createSession() {
+        return createSession(
+                /* enableContentCaptureReceiver= */ true,
+                /* enableContentProtectionReceiver= */ true);
+    }
+
+    private void assertEventFlushedContentCapture(ContentCaptureOptions options) throws Exception {
+        ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class);
+        verify(mMockContentCaptureDirectManager)
+                .sendEvents(captor.capture(), eq(REASON), eq(options));
+
+        assertThat(captor.getValue()).isNotNull();
+        List<ContentCaptureEvent> actual = captor.getValue().getList();
+        assertThat(actual).isNotNull();
+        assertThat(actual).containsExactly(EVENT);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING b/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING
new file mode 100644
index 0000000..f8beac2
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.view.contentcapture"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
index 93315f1..a4e77f5 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
@@ -41,49 +41,60 @@
 
     private final Context mContext = InstrumentationRegistry.getTargetContext();
 
+    private final View mView = new View(mContext);
+
+    private final ViewStructureImpl mViewStructure = new ViewStructureImpl(mView);
+
+    private final ViewNode mViewNode = mViewStructure.getNode();
+
     @Mock
     private HtmlInfo mHtmlInfoMock;
 
     @Test
     public void testUnsupportedProperties() {
-        View view = new View(mContext);
+        mViewStructure.setChildCount(1);
+        assertThat(mViewNode.getChildCount()).isEqualTo(0);
 
-        ViewStructureImpl structure = new ViewStructureImpl(view);
-        ViewNode node = structure.getNode();
+        mViewStructure.addChildCount(1);
+        assertThat(mViewNode.getChildCount()).isEqualTo(0);
 
-        structure.setChildCount(1);
-        assertThat(node.getChildCount()).isEqualTo(0);
+        assertThat(mViewStructure.newChild(0)).isNull();
+        assertThat(mViewNode.getChildCount()).isEqualTo(0);
 
-        structure.addChildCount(1);
-        assertThat(node.getChildCount()).isEqualTo(0);
+        assertThat(mViewStructure.asyncNewChild(0)).isNull();
+        assertThat(mViewNode.getChildCount()).isEqualTo(0);
 
-        assertThat(structure.newChild(0)).isNull();
-        assertThat(node.getChildCount()).isEqualTo(0);
+        mViewStructure.asyncCommit();
+        assertThat(mViewNode.getChildCount()).isEqualTo(0);
 
-        assertThat(structure.asyncNewChild(0)).isNull();
-        assertThat(node.getChildCount()).isEqualTo(0);
+        mViewStructure.setWebDomain("Y U NO SET?");
+        assertThat(mViewNode.getWebDomain()).isNull();
 
-        structure.asyncCommit();
-        assertThat(node.getChildCount()).isEqualTo(0);
+        assertThat(mViewStructure.newHtmlInfoBuilder("WHATEVER")).isNull();
 
-        structure.setWebDomain("Y U NO SET?");
-        assertThat(node.getWebDomain()).isNull();
+        mViewStructure.setHtmlInfo(mHtmlInfoMock);
+        assertThat(mViewNode.getHtmlInfo()).isNull();
 
-        assertThat(structure.newHtmlInfoBuilder("WHATEVER")).isNull();
+        mViewStructure.setDataIsSensitive(true);
 
-        structure.setHtmlInfo(mHtmlInfoMock);
-        assertThat(node.getHtmlInfo()).isNull();
-
-        structure.setDataIsSensitive(true);
-
-        assertThat(structure.getTempRect()).isNull();
+        assertThat(mViewStructure.getTempRect()).isNull();
 
         // Graphic properties
-        structure.setElevation(6.66f);
-        assertThat(node.getElevation()).isWithin(1.0e-10f).of(0f);
-        structure.setAlpha(66.6f);
-        assertThat(node.getAlpha()).isWithin(1.0e-10f).of(1.0f);
-        structure.setTransformation(Matrix.IDENTITY_MATRIX);
-        assertThat(node.getTransformation()).isNull();
+        mViewStructure.setElevation(6.66f);
+        assertThat(mViewNode.getElevation()).isEqualTo(0f);
+        mViewStructure.setAlpha(66.6f);
+        assertThat(mViewNode.getAlpha()).isEqualTo(1.0f);
+        mViewStructure.setTransformation(Matrix.IDENTITY_MATRIX);
+        assertThat(mViewNode.getTransformation()).isNull();
+    }
+
+    @Test
+    public void testGetSet_textIdEntry() {
+        assertThat(mViewNode.getTextIdEntry()).isNull();
+
+        String expected = "TEXT_ID_ENTRY";
+        mViewNode.setTextIdEntry(expected);
+
+        assertThat(mViewNode.getTextIdEntry()).isEqualTo(expected);
     }
 }
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
new file mode 100644
index 0000000..39a2e0e
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.InputType;
+import android.view.View;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.ViewNode;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.RingBuffer;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Test for {@link ContentProtectionEventProcessor}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksCoreTests:android.view.contentprotection.ContentProtectionEventProcessorTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionEventProcessorTest {
+
+    private static final String PACKAGE_NAME = "com.test.package.name";
+
+    private static final String ANDROID_CLASS_NAME = "android.test.some.class.name";
+
+    private static final String PASSWORD_TEXT = "ENTER PASSWORD HERE";
+
+    private static final String SUSPICIOUS_TEXT = "PLEASE SIGN IN";
+
+    private static final String SAFE_TEXT = "SAFE TEXT";
+
+    private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent();
+
+    private static final ContentCaptureEvent[] BUFFERED_EVENTS =
+            new ContentCaptureEvent[] {PROCESS_EVENT};
+
+    private static final Set<Integer> EVENT_TYPES_TO_STORE =
+            ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED);
+
+    private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150;
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private RingBuffer<ContentCaptureEvent> mMockEventBuffer;
+
+    @Mock private IContentCaptureManager mMockContentCaptureManager;
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+
+    private ContentProtectionEventProcessor mContentProtectionEventProcessor;
+
+    @Before
+    public void setup() {
+        mContentProtectionEventProcessor =
+                new ContentProtectionEventProcessor(
+                        mMockEventBuffer,
+                        new Handler(Looper.getMainLooper()),
+                        mMockContentCaptureManager,
+                        PACKAGE_NAME);
+    }
+
+    @Test
+    public void processEvent_buffer_storesOnlySubsetOfEventTypes() {
+        List<ContentCaptureEvent> expectedEvents = new ArrayList<>();
+        for (int type = -100; type <= 100; type++) {
+            ContentCaptureEvent event = createEvent(type);
+            if (EVENT_TYPES_TO_STORE.contains(type)) {
+                expectedEvents.add(event);
+            }
+
+            mContentProtectionEventProcessor.processEvent(event);
+        }
+
+        assertThat(expectedEvents).hasSize(EVENT_TYPES_TO_STORE.size());
+        expectedEvents.forEach((expectedEvent) -> verify(mMockEventBuffer).append(expectedEvent));
+        verifyNoMoreInteractions(mMockEventBuffer);
+    }
+
+    @Test
+    public void processEvent_buffer_setsTextIdEntry_withoutExistingViewNode() {
+        ContentCaptureEvent event = createStoreEvent();
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(event.getViewNode()).isNotNull();
+        assertThat(event.getViewNode().getTextIdEntry()).isEqualTo(PACKAGE_NAME);
+        verify(mMockEventBuffer).append(event);
+    }
+
+    @Test
+    public void processEvent_buffer_setsTextIdEntry_withExistingViewNode() {
+        ViewNode viewNode = new ViewNode();
+        viewNode.setTextIdEntry(PACKAGE_NAME + "TO BE OVERWRITTEN");
+        ContentCaptureEvent event = createStoreEvent();
+        event.setViewNode(viewNode);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(event.getViewNode()).isSameInstanceAs(viewNode);
+        assertThat(viewNode.getTextIdEntry()).isEqualTo(PACKAGE_NAME);
+        verify(mMockEventBuffer).append(event);
+    }
+
+    @Test
+    public void processEvent_loginDetected_inspectsOnlyTypeViewAppeared() {
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+
+        for (int type = -100; type <= 100; type++) {
+            if (type == TYPE_VIEW_APPEARED) {
+                continue;
+            }
+
+            mContentProtectionEventProcessor.processEvent(createEvent(type));
+
+            assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+            assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        }
+
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void processEvent_loginDetected() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer).clear();
+        verify(mMockEventBuffer).toArray();
+        assertOnLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_passwordFieldNotDetected() {
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void processEvent_loginDetected_suspiciousTextNotDetected() {
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void processEvent_loginDetected_withoutViewNode() {
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void processEvent_loginDetected_belowResetLimit() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+        for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; i++) {
+            mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+        }
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer).clear();
+        verify(mMockEventBuffer).toArray();
+        assertOnLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_aboveResetLimit() throws Exception {
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+        for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS + 1; i++) {
+            mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+        }
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+    }
+
+    @Test
+    public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer).clear();
+        verify(mMockEventBuffer).toArray();
+        assertOnLoginDetected();
+    }
+
+    @Test
+    public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5);
+
+        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, times(2)).clear();
+        verify(mMockEventBuffer, times(2)).toArray();
+        assertOnLoginDetected(PROCESS_EVENT, /* times= */ 2);
+    }
+
+    @Test
+    public void isPasswordField_android() {
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isPasswordField_android_withoutClassName() {
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        /* className= */ null, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isPasswordField_android_wrongClassName() {
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        "wrong.prefix" + ANDROID_CLASS_NAME,
+                        InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isPasswordField_android_wrongInputType() {
+        ContentCaptureEvent event =
+                createAndroidPasswordFieldEvent(
+                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_NORMAL);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isPasswordField_webView() throws Exception {
+        ContentCaptureEvent event =
+                createWebViewPasswordFieldEvent(
+                        /* className= */ null, /* eventText= */ null, PASSWORD_TEXT);
+        when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[] {event});
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer).clear();
+        verify(mMockEventBuffer).toArray();
+        assertOnLoginDetected(event, /* times= */ 1);
+    }
+
+    @Test
+    public void isPasswordField_webView_withClassName() {
+        ContentCaptureEvent event =
+                createWebViewPasswordFieldEvent(
+                        /* className= */ "any.class.name", /* eventText= */ null, PASSWORD_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isPasswordField_webView_withSafeViewNodeText() {
+        ContentCaptureEvent event =
+                createWebViewPasswordFieldEvent(
+                        /* className= */ null, /* eventText= */ null, SAFE_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isPasswordField_webView_withEventText() {
+        ContentCaptureEvent event =
+                createWebViewPasswordFieldEvent(/* className= */ null, PASSWORD_TEXT, SAFE_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isSuspiciousText_withSafeText() {
+        ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SAFE_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isSuspiciousText_eventText_suspiciousText() {
+        ContentCaptureEvent event = createSuspiciousTextEvent(SUSPICIOUS_TEXT, SAFE_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isSuspiciousText_viewNodeText_suspiciousText() {
+        ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SUSPICIOUS_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isSuspiciousText_eventText_passwordText() {
+        ContentCaptureEvent event = createSuspiciousTextEvent(PASSWORD_TEXT, SAFE_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    @Test
+    public void isSuspiciousText_viewNodeText_passwordText() {
+        // Specify the class to differ from {@link isPasswordField_webView} test in this version
+        ContentCaptureEvent event =
+                createProcessEvent(
+                        "test.class.not.a.web.view", /* inputType= */ 0, SAFE_TEXT, PASSWORD_TEXT);
+
+        mContentProtectionEventProcessor.processEvent(event);
+
+        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
+    }
+
+    private static ContentCaptureEvent createEvent(int type) {
+        return new ContentCaptureEvent(/* sessionId= */ 123, type);
+    }
+
+    private static ContentCaptureEvent createStoreEvent() {
+        return createEvent(TYPE_VIEW_TEXT_CHANGED);
+    }
+
+    private static ContentCaptureEvent createProcessEvent() {
+        return createEvent(TYPE_VIEW_APPEARED);
+    }
+
+    private ContentCaptureEvent createProcessEvent(
+            @Nullable String className,
+            int inputType,
+            @Nullable String eventText,
+            @Nullable String viewNodeText) {
+        View view = new View(mContext);
+        ViewStructureImpl viewStructure = new ViewStructureImpl(view);
+        if (className != null) {
+            viewStructure.setClassName(className);
+        }
+        if (viewNodeText != null) {
+            viewStructure.setText(viewNodeText);
+        }
+        viewStructure.setInputType(inputType);
+
+        ContentCaptureEvent event = createProcessEvent();
+        event.setViewNode(viewStructure.getNode());
+        if (eventText != null) {
+            event.setText(eventText);
+        }
+
+        return event;
+    }
+
+    private ContentCaptureEvent createAndroidPasswordFieldEvent(
+            @Nullable String className, int inputType) {
+        return createProcessEvent(
+                className, inputType, /* eventText= */ null, /* viewNodeText= */ null);
+    }
+
+    private ContentCaptureEvent createWebViewPasswordFieldEvent(
+            @Nullable String className, @Nullable String eventText, @Nullable String viewNodeText) {
+        return createProcessEvent(className, /* inputType= */ 0, eventText, viewNodeText);
+    }
+
+    private ContentCaptureEvent createSuspiciousTextEvent(
+            @Nullable String eventText, @Nullable String viewNodeText) {
+        return createProcessEvent(
+                /* className= */ null, /* inputType= */ 0, eventText, viewNodeText);
+    }
+
+    private void assertOnLoginDetected() throws Exception {
+        assertOnLoginDetected(PROCESS_EVENT, /* times= */ 1);
+    }
+
+    private void assertOnLoginDetected(ContentCaptureEvent event, int times) throws Exception {
+        ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class);
+        verify(mMockContentCaptureManager, times(times)).onLoginDetected(captor.capture());
+
+        assertThat(captor.getValue()).isNotNull();
+        List<ContentCaptureEvent> actual = captor.getValue().getList();
+        assertThat(actual).isNotNull();
+        assertThat(actual).containsExactly(event);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
new file mode 100644
index 0000000..1459799
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2013 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.view.contentprotection;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.View;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ViewNode;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for {@link ContentProtectionUtils}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksCoreTests:android.view.contentprotection.ContentProtectionUtilsTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionUtilsTest {
+
+    private static final String TEXT = "TEST_TEXT";
+
+    private static final ContentCaptureEvent EVENT = createEvent();
+
+    private static final ViewNode VIEW_NODE = new ViewNode();
+
+    private static final ViewNode VIEW_NODE_WITH_TEXT = createViewNodeWithText();
+
+    @Test
+    public void event_getEventText_null() {
+        String actual = ContentProtectionUtils.getEventText(EVENT);
+
+        assertThat(actual).isNull();
+    }
+
+    @Test
+    public void event_getEventText_notNull() {
+        ContentCaptureEvent event = createEvent();
+        event.setText(TEXT);
+
+        String actual = ContentProtectionUtils.getEventText(event);
+
+        assertThat(actual).isEqualTo(TEXT);
+    }
+
+    @Test
+    public void event_getViewNodeText_null() {
+        String actual = ContentProtectionUtils.getViewNodeText(EVENT);
+
+        assertThat(actual).isNull();
+    }
+
+    @Test
+    public void event_getViewNodeText_notNull() {
+        ContentCaptureEvent event = createEvent();
+        event.setViewNode(VIEW_NODE_WITH_TEXT);
+
+        String actual = ContentProtectionUtils.getViewNodeText(event);
+
+        assertThat(actual).isEqualTo(TEXT);
+    }
+
+    @Test
+    public void viewNode_getViewNodeText_null() {
+        String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE);
+
+        assertThat(actual).isNull();
+    }
+
+    @Test
+    public void viewNode_getViewNodeText_notNull() {
+        String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE_WITH_TEXT);
+
+        assertThat(actual).isEqualTo(TEXT);
+    }
+
+    private static ContentCaptureEvent createEvent() {
+        return new ContentCaptureEvent(/* sessionId= */ 123, TYPE_SESSION_STARTED);
+    }
+
+    private static ViewNode createViewNodeWithText() {
+        View view = new View(ApplicationProvider.getApplicationContext());
+        ViewStructureImpl viewStructure = new ViewStructureImpl(view);
+        viewStructure.setText(TEXT);
+        return viewStructure.getNode();
+    }
+}
diff --git a/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING b/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING
new file mode 100644
index 0000000..3cd4e17
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentprotection/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.view.contentprotection"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
index f93cd18..0750cf1 100644
--- a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
@@ -37,6 +37,7 @@
 import android.text.style.ScaleXSpan;
 import android.text.style.StyleSpan;
 import android.text.style.TypefaceSpan;
+import android.text.util.Linkify;
 import android.view.ViewGroup;
 import android.widget.EditText;
 
@@ -53,7 +54,7 @@
 @RunWith(AndroidJUnit4.class)
 public class TextAppearanceInfoTest {
     private static final float EPSILON = 0.0000001f;
-    private static final String TEST_TEXT = "Happy birthday!";
+    private static final String TEST_TEXT = "Hello: google.com";
     private static final float TEXT_SIZE = 16.5f;
     private static final LocaleList TEXT_LOCALES = LocaleList.forLanguageTags("en,ja");
     private static final String FONT_FAMILY_NAME = "sans-serif";
@@ -84,39 +85,7 @@
 
     @Before
     public void setUp() {
-        mEditText.setText(mSpannableText);
-        mEditText.getPaint().setTextSize(TEXT_SIZE);
-        mEditText.setTextLocales(TEXT_LOCALES);
-        Typeface family = Typeface.create(FONT_FAMILY_NAME, Typeface.NORMAL);
-        mEditText.setTypeface(
-                Typeface.create(family, TEXT_WEIGHT, (TEXT_STYLE & Typeface.ITALIC) != 0));
-        mEditText.setAllCaps(ALL_CAPS);
-        mEditText.setShadowLayer(SHADOW_RADIUS, SHADOW_DX, SHADOW_DY, SHADOW_COLOR);
-        mEditText.setElegantTextHeight(ELEGANT_TEXT_HEIGHT);
-        mEditText.setFallbackLineSpacing(FALLBACK_LINE_SPACING);
-        mEditText.setLetterSpacing(LETTER_SPACING);
-        mEditText.setFontFeatureSettings(FONT_FEATURE_SETTINGS);
-        mEditText.setFontVariationSettings(FONT_VARIATION_SETTINGS);
-        mEditText.setLineBreakStyle(LINE_BREAK_STYLE);
-        mEditText.setLineBreakWordStyle(LINE_BREAK_WORD_STYLE);
-        mEditText.setTextScaleX(TEXT_SCALEX);
-        mEditText.setHighlightColor(HIGHLIGHT_TEXT_COLOR);
-        mEditText.setTextColor(TEXT_COLOR);
-        mEditText.setHintTextColor(HINT_TEXT_COLOR);
-        mEditText.setLinkTextColor(LINK_TEXT_COLOR);
-        ViewGroup.LayoutParams params =
-                new ViewGroup.LayoutParams(
-                        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-        mEditText.setLayoutParams(params);
-        mEditText.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-        Bitmap bitmap =
-                Bitmap.createBitmap(
-                        Math.max(1, mEditText.getMeasuredWidth()),
-                        Math.max(1, mEditText.getMeasuredHeight()),
-                        Bitmap.Config.ARGB_8888);
-        mEditText.layout(0, 0, mEditText.getMeasuredWidth(), mEditText.getMeasuredHeight());
-        mCanvas = new Canvas(bitmap);
-        mEditText.draw(mCanvas);
+        initEditText(mSpannableText);
     }
 
     @Test
@@ -233,6 +202,43 @@
         assertEquals(info1.getSystemFontFamilyName(), FONT_FAMILY_NAME);
     }
 
+    private void initEditText(CharSequence text) {
+        mEditText.setText(text);
+        mEditText.getPaint().setTextSize(TEXT_SIZE);
+        mEditText.setTextLocales(TEXT_LOCALES);
+        Typeface family = Typeface.create(FONT_FAMILY_NAME, Typeface.NORMAL);
+        mEditText.setTypeface(
+                Typeface.create(family, TEXT_WEIGHT, (TEXT_STYLE & Typeface.ITALIC) != 0));
+        mEditText.setAllCaps(ALL_CAPS);
+        mEditText.setShadowLayer(SHADOW_RADIUS, SHADOW_DX, SHADOW_DY, SHADOW_COLOR);
+        mEditText.setElegantTextHeight(ELEGANT_TEXT_HEIGHT);
+        mEditText.setFallbackLineSpacing(FALLBACK_LINE_SPACING);
+        mEditText.setLetterSpacing(LETTER_SPACING);
+        mEditText.setFontFeatureSettings(FONT_FEATURE_SETTINGS);
+        mEditText.setFontVariationSettings(FONT_VARIATION_SETTINGS);
+        mEditText.setLineBreakStyle(LINE_BREAK_STYLE);
+        mEditText.setLineBreakWordStyle(LINE_BREAK_WORD_STYLE);
+        mEditText.setTextScaleX(TEXT_SCALEX);
+        mEditText.setHighlightColor(HIGHLIGHT_TEXT_COLOR);
+        mEditText.setTextColor(TEXT_COLOR);
+        mEditText.setHintTextColor(HINT_TEXT_COLOR);
+        mEditText.setHint("Hint text");
+        mEditText.setLinkTextColor(LINK_TEXT_COLOR);
+        mEditText.setAutoLinkMask(Linkify.WEB_URLS);
+        ViewGroup.LayoutParams params =
+                new ViewGroup.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        mEditText.setLayoutParams(params);
+        mEditText.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        Bitmap bitmap =
+                Bitmap.createBitmap(
+                        Math.max(1, mEditText.getMeasuredWidth()),
+                        Math.max(1, mEditText.getMeasuredHeight()),
+                        Bitmap.Config.ARGB_8888);
+        mEditText.layout(0, 0, mEditText.getMeasuredWidth(), mEditText.getMeasuredHeight());
+        mCanvas = new Canvas(bitmap);
+        mEditText.draw(mCanvas);
+    }
     private void assertTextAppearanceInfoContentsEqual(TextAppearanceInfo textAppearanceInfo) {
         assertEquals(textAppearanceInfo.getTextSize(), TEXT_SIZE, EPSILON);
         assertEquals(textAppearanceInfo.getTextLocales(), TEXT_LOCALES);
@@ -258,6 +264,15 @@
         assertEquals(textAppearanceInfo.getLinkTextColor(), LINK_TEXT_COLOR);
     }
 
+    @Test
+    public void testCreateFromTextView_withHintText() {
+        // Make hint text display
+        initEditText("");
+
+        // The text color should not be hint color
+        assertTextAppearanceInfoContentsEqual(TextAppearanceInfo.createFromTextView(mEditText));
+    }
+
     static class CustomForegroundColorSpan extends ForegroundColorSpan {
         @Nullable public TextPaint lastTextPaint = null;
 
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 34eac35..f1eef75 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -75,7 +75,7 @@
     private static final int HW_BOUNDS_OFFSETS_TOP_PX = 20;
     private static final int HW_BOUNDS_OFFSETS_RIGHT_PX = 30;
     private static final int HW_BOUNDS_OFFSETS_BOTTOM_PX = 40;
-    private int mHandwritingSlop = 4;
+    private int mHandwritingSlop = 2;
 
     private static final Rect sHwArea1;
     private static final Rect sHwArea2;
@@ -221,8 +221,10 @@
 
     @Test
     public void onTouchEvent_startHandwriting_inputConnectionBuilt_stylusMoveInExtendedHWArea() {
+        // The stylus down point is between mTestView1 and  mTestView2, but it is within the
+        // extended handwriting area of both views. It is closer to mTestView1.
         final int x1 = sHwArea1.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2;
-        final int y1 = sHwArea1.bottom + HW_BOUNDS_OFFSETS_BOTTOM_PX / 2;
+        final int y1 = sHwArea1.bottom + (sHwArea2.top - sHwArea1.bottom) / 3;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent1);
 
@@ -231,10 +233,14 @@
         MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
-        // InputConnection is created after stylus movement.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        // First create InputConnection for mTestView2 and verify that handwriting is not started.
+        mHandwritingInitiator.onInputConnectionCreated(mTestView2);
+        verify(mHandwritingInitiator, never()).startHandwriting(mTestView2);
 
-        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
+        // Next create InputConnection for mTextView1. Handwriting is started for this view since
+        // the stylus down point is closest to this view.
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        verify(mHandwritingInitiator).startHandwriting(mTestView1);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java b/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java
index 1a01987..62adc20 100644
--- a/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java
+++ b/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java
@@ -30,6 +30,7 @@
 import android.view.Gravity;
 import android.view.View;
 import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.EditorBoundsInfo;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -54,8 +55,15 @@
     private static final int[] sLocationOnScreen = new int[2];
     private static Typeface sTypeface;
     private static final float TEXT_SIZE = 1f;
-    // The line height of the test font font is 1.2 * textSize.
+    // The line height of the test font is 1.2 * textSize.
     private static final int LINE_HEIGHT = 12;
+    private static final int HW_BOUNDS_OFFSET_LEFT = 10;
+    private static final int HW_BOUNDS_OFFSET_TOP = 20;
+    private static final int HW_BOUNDS_OFFSET_RIGHT = 30;
+    private static final int HW_BOUNDS_OFFSET_BOTTOM = 40;
+
+
+    // Default text has 5 lines of text. The needed width is 50px and the needed height is 60px.
     private static final CharSequence DEFAULT_TEXT = "X\nXX\nXXX\nXXXX\nXXXXX";
     private static final ImmutableList<RectF> DEFAULT_LINE_BOUNDS = ImmutableList.of(
             new RectF(0f, 0f, 10f, LINE_HEIGHT),
@@ -131,6 +139,55 @@
     }
 
     @Test
+    public void testEditorBoundsInfo_allVisible() {
+        // The needed width and height of the DEFAULT_TEXT are 50 px and 60 px respectfully.
+        int width = 100;
+        int height = 200;
+        setupEditText(DEFAULT_TEXT, width, height);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+        EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
+        assertThat(editorBoundsInfo).isNotNull();
+        assertThat(editorBoundsInfo.getEditorBounds()).isEqualTo(new RectF(0, 0, width, height));
+        assertThat(editorBoundsInfo.getHandwritingBounds())
+                .isEqualTo(new RectF(-HW_BOUNDS_OFFSET_LEFT, -HW_BOUNDS_OFFSET_TOP,
+                        width + HW_BOUNDS_OFFSET_RIGHT, height + HW_BOUNDS_OFFSET_BOTTOM));
+    }
+
+    @Test
+    public void testEditorBoundsInfo_scrolled() {
+        // The height of the editor will be 60 px.
+        int width = 100;
+        int visibleTop = 10;
+        int visibleBottom = 30;
+        setupVerticalClippedEditText(width, visibleTop, visibleBottom);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+        EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
+        assertThat(editorBoundsInfo).isNotNull();
+        assertThat(editorBoundsInfo.getEditorBounds())
+                .isEqualTo(new RectF(0, visibleTop, width, visibleBottom));
+        assertThat(editorBoundsInfo.getHandwritingBounds())
+                .isEqualTo(new RectF(-HW_BOUNDS_OFFSET_LEFT, visibleTop - HW_BOUNDS_OFFSET_TOP,
+                        width + HW_BOUNDS_OFFSET_RIGHT, visibleBottom + HW_BOUNDS_OFFSET_BOTTOM));
+    }
+
+    @Test
+    public void testEditorBoundsInfo_invisible() {
+        // The height of the editor will be 60px. Scroll it to 70px will make it invisible.
+        int width = 100;
+        int visibleTop = 70;
+        int visibleBottom = 70;
+        setupVerticalClippedEditText(width, visibleTop, visibleBottom);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+        EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
+        assertThat(editorBoundsInfo).isNotNull();
+        assertThat(editorBoundsInfo.getEditorBounds()).isEqualTo(new RectF(0, 0, 0, 0));
+        assertThat(editorBoundsInfo.getHandwritingBounds()).isEqualTo(new RectF(0, 0, 0, 0));
+    }
+
+    @Test
     public void testVisibleLineBounds_allVisible() {
         setupEditText(DEFAULT_TEXT, /* height= */ 100);
         CursorAnchorInfo cursorAnchorInfo =
@@ -465,32 +522,26 @@
     }
 
     private void setupVerticalClippedEditText(int visibleTop, int visibleBottom) {
+        setupVerticalClippedEditText(1000, visibleTop, visibleBottom);
+    }
+
+    /**
+     * Helper method to create an EditText in a vertical ScrollView so that its visible bounds
+     * is Rect(0, visibleTop, width, visibleBottom) in the EditText's coordinates. Both ScrollView
+     * and EditText's width is set to the given width.
+     */
+    private void setupVerticalClippedEditText(int width, int visibleTop, int visibleBottom) {
         ScrollView scrollView = new ScrollView(mActivity);
-        mEditText = new EditText(mActivity);
-        mEditText.setTypeface(sTypeface);
-        mEditText.setText(DEFAULT_TEXT);
-        mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, TEXT_SIZE);
-
-        mEditText.setPadding(0, 0, 0, 0);
-        mEditText.setCompoundDrawables(null, null, null, null);
-        mEditText.setCompoundDrawablePadding(0);
-
-        mEditText.scrollTo(0, 0);
-        mEditText.setLineSpacing(0f, 1f);
-
-        // Place the text layout top to the view's top.
-        mEditText.setGravity(Gravity.TOP);
-        int width = 1000;
-        int height = visibleBottom - visibleTop;
+        createEditText();
+        int scrollViewHeight = visibleBottom - visibleTop;
 
         scrollView.addView(mEditText, new FrameLayout.LayoutParams(
                 View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
                 View.MeasureSpec.makeMeasureSpec(5 * LINE_HEIGHT, View.MeasureSpec.EXACTLY)));
         scrollView.measure(
                 View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
-        scrollView.layout(0, 0, width, height);
-
+                View.MeasureSpec.makeMeasureSpec(scrollViewHeight, View.MeasureSpec.EXACTLY));
+        scrollView.layout(0, 0, width, scrollViewHeight);
         scrollView.scrollTo(0, visibleTop);
     }
 
@@ -499,6 +550,11 @@
         measureEditText(height);
     }
 
+    private void setupEditText(CharSequence text, int width, int height) {
+        createEditText(text);
+        measureEditText(width, height);
+    }
+
     private void setupEditText(CharSequence text, int height, float lineSpacing,
             float lineMultiplier) {
         createEditText(text);
@@ -537,6 +593,8 @@
         mEditText.setTypeface(sTypeface);
         mEditText.setText(text);
         mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, TEXT_SIZE);
+        mEditText.setHandwritingBoundsOffsets(HW_BOUNDS_OFFSET_LEFT, HW_BOUNDS_OFFSET_TOP,
+                HW_BOUNDS_OFFSET_RIGHT, HW_BOUNDS_OFFSET_BOTTOM);
 
         mEditText.setPadding(0, 0, 0, 0);
         mEditText.setCompoundDrawables(null, null, null, null);
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewActivity.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewActivity.java
new file mode 100644
index 0000000..2101354
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * An activity for testing the TextView widget.
+ */
+public class HorizontalScrollViewActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_horizontal_scroll_view);
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
new file mode 100644
index 0000000..86f26e5
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+import android.util.PollingCheck;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+@Presubmit
+public class HorizontalScrollViewFunctionalTest {
+    private HorizontalScrollViewActivity mActivity;
+    private HorizontalScrollView mHorizontalScrollView;
+    @Rule
+    public ActivityTestRule<HorizontalScrollViewActivity> mActivityRule = new ActivityTestRule<>(
+            HorizontalScrollViewActivity.class);
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = mActivityRule.getActivity();
+        mHorizontalScrollView = mActivity.findViewById(R.id.horizontal_scroll_view);
+    }
+
+    @Test
+    public void testScrollAfterFlingTop() {
+        mHorizontalScrollView.scrollTo(100, 0);
+        mHorizontalScrollView.fling(-10000);
+        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0);
+        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f);
+        assertEquals(0, mHorizontalScrollView.getScrollX());
+    }
+
+    @Test
+    public void testScrollAfterFlingBottom() {
+        int childWidth = mHorizontalScrollView.getChildAt(0).getWidth();
+        int maxScroll = childWidth - mHorizontalScrollView.getWidth();
+        mHorizontalScrollView.scrollTo(maxScroll - 100, 0);
+        mHorizontalScrollView.fling(10000);
+        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0);
+        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f);
+        assertEquals(maxScroll, mHorizontalScrollView.getScrollX());
+    }
+}
+
diff --git a/core/tests/coretests/src/android/widget/PointerIconTest.java b/core/tests/coretests/src/android/widget/PointerIconTest.java
new file mode 100644
index 0000000..8e9e1a5
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/PointerIconTest.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.UiThread;
+import android.app.Instrumentation;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.PointerIcon;
+import android.view.View;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PointerIconTest {
+    private Instrumentation mInstrumentation;
+
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    }
+
+    @Rule
+    public ActivityScenarioRule<PointerIconTestActivity> mActivityScenarioRule =
+            new ActivityScenarioRule<>(PointerIconTestActivity.class);
+
+    @Test
+    @UiThread
+    public void button_mouse_onResolvePointerIcon_returnsTypeHand() {
+        assertOnResolvePointerIconForMouseEvent(R.id.button, PointerIcon.TYPE_HAND);
+    }
+
+    @Test
+    @UiThread
+    public void button_mouse_disabled_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.button, /* enabled */ false, /* clickable */ true,
+                /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void button_mouse_unclickable_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.button, /* enabled */ true, /* clickable */ false,
+                /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void button_stylus_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.button, /* enabled */ true, /* clickable */ true,
+                /* isMouse */ false);
+    }
+
+    @Test
+    @UiThread
+    public void imageButton_mouse_onResolvePointerIconreturnsTypeHand() {
+        assertOnResolvePointerIconForMouseEvent(R.id.imagebutton, PointerIcon.TYPE_HAND);
+    }
+
+    @Test
+    @UiThread
+    public void imageButton_mouse_diabled_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.imagebutton, /* enabled */ false,
+                /* clickable */ true, /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void imageButton_mouse_unclickable_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.imagebutton, /* enabled */ true,
+                /* clickable */ false, /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void imageButton_stylus_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.imagebutton, /* enabled */ true,
+                /* clickable */ true, /* isMouse */ false);
+    }
+
+    @Test
+    @UiThread
+    public void textView_mouse_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.textview, /* enabled */ true,
+                /* clickable */ true, /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void textView_stylus_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.textview, /* enabled */ true,
+                /* clickable */ true, /* isMouse */ false);
+    }
+
+    @Test
+    @UiThread
+    public void editText_mouse_onResolvePointerIcon_returnsTypeText() {
+        assertOnResolvePointerIconForMouseEvent(R.id.edittext, PointerIcon.TYPE_TEXT);
+    }
+
+    @Test
+    @UiThread
+    public void editText_stylus_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.edittext, /* enabled */ true,
+                /* clickable */ true, /* isMouse */ false);
+    }
+
+    @Test
+    @UiThread
+    public void spinner_mouse_onResolvePointerIcon_returnsTypeHand() {
+        assertOnResolvePointerIconForMouseEvent(R.id.spinner, PointerIcon.TYPE_HAND);
+    }
+
+    @Test
+    @UiThread
+    public void spinner_mouse_disabled_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.spinner, /* enabled */ false,
+                /* clickable */ true, /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void spinner_mouse_unclickable_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.spinner, /* enabled */ true,
+                /* clickable */ false, /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void spinner_stylus_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.spinner, /* enabled */ true, /* clickable */ true,
+                /* isMouse */ false);
+    }
+
+    @Test
+    @UiThread
+    public void radialTimePickerView_mouse_onResolvePointerIcon_returnsTypeHand() {
+        assertOnResolvePointerIconForMouseEvent(R.id.timepicker, PointerIcon.TYPE_HAND);
+
+    }
+
+    @Test
+    @UiThread
+    public void radialTimePickerView_mouse_disabled_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.timepicker, /* enabled */ false,
+                /* clickable */ true, /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void radialTimePickerView_stylus_onResolvePointerIcon_returnsNull() {
+        assertOnResolvePointerIconReturnNull(R.id.timepicker, /* enabled */ true,
+                /* clickable */ true, /* isMouse */ false);
+    }
+
+    @Test
+    @UiThread
+    public void calendarView_mouse_onResolvePointerIcon_returnsTypeHand() {
+        assertPointerIconForCalendarView(/* pointerType */ PointerIcon.TYPE_HAND,
+                /* isMouse */ true);
+    }
+
+    @Test
+    @UiThread
+    public void calendarView_stylus_onResolvePointerIcon_returnsNull() {
+        assertPointerIconForCalendarView(/* pointerType */ Integer.MIN_VALUE, /* isMouse */ false);
+    }
+
+    /**
+     * Assert {@link View#onResolvePointerIcon} method for {@link CalendarView}.
+     *
+     * @param pointerType the expected type of the {@link PointerIcon}.
+     *                    When {@link Integer#MIN_VALUE} is passed, it will verify that the
+     *                    returned {@link PointerIcon} is null.
+     * @param isMouse if true, mouse events are used to test the given view. Otherwise, it uses
+     *               stylus events to test the view.
+     */
+    void assertPointerIconForCalendarView(int pointerType, boolean isMouse) {
+        Calendar calendar = new GregorianCalendar();
+        calendar.set(2023, 0, 1);
+        long time = calendar.getTimeInMillis();
+        mActivityScenarioRule.getScenario().onActivity(activity -> {
+            CalendarView calendarView = activity.findViewById(R.id.calendar);
+            calendarView.setDate(time, /* animate */ false, /* center */true);
+        });
+
+        // Wait for setDate to finish and then verify.
+        mInstrumentation.waitForIdleSync();
+        mActivityScenarioRule.getScenario().onActivity(activity -> {
+            CalendarView calendarView = activity.findViewById(R.id.calendar);
+            Rect bounds = new Rect();
+            calendarView.getBoundsForDate(time, bounds);
+            MotionEvent event = createHoverEvent(isMouse, bounds.centerX(), bounds.centerY());
+            PointerIcon icon = calendarView.onResolvePointerIcon(event, /* pointerIndex */ 0);
+            if (pointerType != Integer.MIN_VALUE) {
+                assertThat(icon.getType()).isEqualTo(pointerType);
+            } else {
+                assertThat(icon).isNull();
+            }
+        });
+    }
+
+    /**
+     * Assert that the given view's {@link View#onResolvePointerIcon(MotionEvent, int)} method
+     * returns a {@link PointerIcon} with the specified pointer type. The passed {@link MotionEvent}
+     * locates at the center of the view.
+     *
+     * @param resId the resource id of the view to be tested.
+     * @param pointerType the expected pointer type. When {@link Integer#MIN_VALUE} is passed, it
+     *                   will verify that the returned {@link PointerIcon} is null.
+     */
+    public void assertOnResolvePointerIconForMouseEvent(int resId, int pointerType) {
+        mActivityScenarioRule.getScenario().onActivity(activity -> {
+            View view = activity.findViewById(resId);
+            MotionEvent event = createHoverEvent(/* isMouse */ true, /* x */ 0, /* y */ 0);
+            PointerIcon icon = view.onResolvePointerIcon(event, /* pointerIndex */ 0);
+            if (pointerType != Integer.MIN_VALUE) {
+                assertThat(icon.getType()).isEqualTo(pointerType);
+            } else {
+                assertThat(icon).isNull();
+            }
+        });
+    }
+
+    /**
+     * Assert that the given view's {@link View#onResolvePointerIcon(MotionEvent, int)} method
+     * returns a {@link PointerIcon} with the specified pointer type. The passed {@link MotionEvent}
+     * locates at the center of the view.
+     *
+     * @param resId the resource id of the view to be tested.
+     * @param enabled whether the tested  view is enabled.
+     * @param clickable whether the tested view is clickable.
+     * @param isMouse if true, mouse events are used to test the given view. Otherwise, it uses
+     *               stylus events to test the view.
+     */
+    public void assertOnResolvePointerIconReturnNull(int resId, boolean enabled, boolean clickable,
+            boolean isMouse) {
+        mActivityScenarioRule.getScenario().onActivity(activity -> {
+            View view = activity.findViewById(resId);
+            view.setEnabled(enabled);
+            view.setClickable(clickable);
+            MotionEvent event = createHoverEvent(isMouse, /* x */ 0, /* y */ 0);
+            PointerIcon icon = view.onResolvePointerIcon(event, /* pointerIndex */ 0);
+            assertThat(icon).isNull();
+        });
+    }
+
+
+    /**
+     * Create a hover {@link MotionEvent} for testing.
+     *
+     * @param isMouse if true, a {@link MotionEvent} from mouse is returned. Otherwise,
+     *               a {@link MotionEvent} from stylus is returned.
+     * @param x the x coordinate of the returned {@link MotionEvent}
+     * @param y the y coordinate of the returned {@link MotionEvent}
+     */
+    private MotionEvent createHoverEvent(boolean isMouse, int x, int y) {
+        MotionEvent.PointerProperties[] properties = MotionEvent.PointerProperties.createArray(1);
+        properties[0].toolType =
+                isMouse ? MotionEvent.TOOL_TYPE_MOUSE : MotionEvent.TOOL_TYPE_STYLUS;
+
+        MotionEvent.PointerCoords[] coords = MotionEvent.PointerCoords.createArray(1);
+        coords[0].x = x;
+        coords[0].y = y;
+
+        int source = isMouse ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_STYLUS;
+        long eventTime = SystemClock.uptimeMillis();
+        return MotionEvent.obtain(/* downTime */ 0, eventTime, MotionEvent.ACTION_HOVER_MOVE,
+                /* pointerCount */ 1, properties, coords, /* metaState */ 0, /* buttonState */ 0,
+                /* xPrecision */ 1, /* yPrecision */ 1, /* deviceId */ 0, /* edgeFlags */ 0,
+                source, /* flags */ 0);
+    }
+
+}
diff --git a/core/tests/coretests/src/android/widget/PointerIconTestActivity.java b/core/tests/coretests/src/android/widget/PointerIconTestActivity.java
new file mode 100644
index 0000000..d491fb0
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/PointerIconTestActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+public class PointerIconTestActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.pointer_icon_test);
+
+        RadialTimePickerView timePicker = findViewById(R.id.timepicker);
+        // Set the time of TimePicker to 0:00 so that the test is stable.
+        timePicker.setCurrentHour(0);
+        timePicker.setCurrentMinute(0);
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
index 184b9ea..4f722ce 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
@@ -353,6 +353,23 @@
         public boolean isCreated() {
             return false;
         }
+
+        @Override
+        public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+            RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
+                    new RemoteViews.RemoteCollectionItems.Builder();
+            itemsBuilder.setHasStableIds(hasStableIds())
+                    .setViewTypeCount(getViewTypeCount());
+            try {
+                for (int i = 0; i < mCount; i++) {
+                    itemsBuilder.addItem(getItemId(i), getViewAt(i));
+                }
+            } catch (RemoteException e) {
+                // No-op
+            }
+
+            return itemsBuilder.build();
+        }
     }
 
     private static class DistinctIntent extends Intent {
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 73aa936..c442755 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertEquals;
 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.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.spy;
@@ -39,11 +40,15 @@
 import android.os.Binder;
 import android.os.Looper;
 import android.os.Parcel;
+import android.util.AttributeSet;
 import android.util.SizeF;
 import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -827,4 +832,71 @@
         verify(visitor, times(1)).accept(eq(icon3S.getUri()));
         verify(visitor, times(1)).accept(eq(icon4S.getUri()));
     }
+
+    @Test
+    public void layoutInflaterFactory_nothingSet_returnsNull() {
+        final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test);
+        assertNull(rv.getLayoutInflaterFactory());
+    }
+
+    @Test
+    public void layoutInflaterFactory_replacesImageView_viewReplaced() {
+        final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test);
+        final View replacement = new FrameLayout(mContext);
+        replacement.setId(1337);
+
+        LayoutInflater.Factory2 factory = createLayoutInflaterFactory("ImageView", replacement);
+        rv.setLayoutInflaterFactory(factory);
+
+        // Now inflate the views.
+        View inflated = rv.apply(mContext, mContainer);
+
+        assertEquals(factory, rv.getLayoutInflaterFactory());
+        View replacedFrameLayout = inflated.findViewById(1337);
+        assertNotNull(replacedFrameLayout);
+        assertEquals(replacement, replacedFrameLayout);
+        // ImageView should be fully replaced.
+        assertNull(inflated.findViewById(R.id.image));
+    }
+
+    @Test
+    public void layoutInflaterFactory_replacesImageView_settersStillFunctional() {
+        final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test);
+        final TextView replacement = new TextView(mContext);
+        replacement.setId(R.id.text);
+        final String testText = "testText";
+        rv.setLayoutInflaterFactory(createLayoutInflaterFactory("TextView", replacement));
+        rv.setTextViewText(R.id.text, testText);
+
+
+        // Now inflate the views.
+        View inflated = rv.apply(mContext, mContainer);
+
+        TextView replacedTextView = inflated.findViewById(R.id.text);
+        assertSame(replacement, replacedTextView);
+        assertEquals(testText, replacedTextView.getText());
+    }
+
+    private static LayoutInflater.Factory2 createLayoutInflaterFactory(String viewTypeToReplace,
+            View replacementView) {
+        return new LayoutInflater.Factory2() {
+            @Nullable
+            @Override
+            public View onCreateView(@Nullable View parent, @NonNull String name,
+                                     @NonNull Context context, @NonNull AttributeSet attrs) {
+                if (viewTypeToReplace.equals(name)) {
+                    return replacementView;
+                }
+
+                return null;
+            }
+
+            @Nullable
+            @Override
+            public View onCreateView(@NonNull String name, @NonNull Context context,
+                                     @NonNull AttributeSet attrs) {
+                return null;
+            }
+        };
+    }
 }
diff --git a/core/tests/coretests/src/android/widget/ScrollViewActivity.java b/core/tests/coretests/src/android/widget/ScrollViewActivity.java
new file mode 100644
index 0000000..899d631
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/ScrollViewActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * An activity for testing the TextView widget.
+ */
+public class ScrollViewActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_scroll_view);
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
new file mode 100644
index 0000000..a49bb6a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+import android.util.PollingCheck;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+@Presubmit
+public class ScrollViewFunctionalTest {
+    private ScrollViewActivity mActivity;
+    private ScrollView mScrollView;
+    @Rule
+    public ActivityTestRule<ScrollViewActivity> mActivityRule = new ActivityTestRule<>(
+            ScrollViewActivity.class);
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = mActivityRule.getActivity();
+        mScrollView = mActivity.findViewById(R.id.scroll_view);
+    }
+
+    @Test
+    public void testScrollAfterFlingTop() {
+        mScrollView.scrollTo(0, 100);
+        mScrollView.fling(-10000);
+        PollingCheck.waitFor(() -> mScrollView.mEdgeGlowTop.getDistance() > 0);
+        PollingCheck.waitFor(() -> mScrollView.mEdgeGlowTop.getDistance() == 0f);
+        assertEquals(0, mScrollView.getScrollY());
+    }
+
+    @Test
+    public void testScrollAfterFlingBottom() {
+        int childHeight = mScrollView.getChildAt(0).getHeight();
+        int maxScroll = childHeight - mScrollView.getHeight();
+        mScrollView.scrollTo(0, maxScroll - 100);
+        mScrollView.fling(10000);
+        PollingCheck.waitFor(() -> mScrollView.mEdgeGlowBottom.getDistance() > 0);
+        PollingCheck.waitFor(() -> mScrollView.mEdgeGlowBottom.getDistance() == 0f);
+        assertEquals(maxScroll, mScrollView.getScrollY());
+    }
+}
+
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index a6e74d0..68c0693 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -23,12 +23,12 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -254,18 +254,15 @@
         callbackInfo1.getCallback().onBackStarted(mBackEvent);
 
         waitForIdle();
-        verify(mCallback1).onBackStarted(any(BackEvent.class));
-        verifyZeroInteractions(mCallback2);
+        verify(mCallback1, times(1)).onBackStarted(any(BackEvent.class));
+        verify(mCallback2, never()).onBackStarted(any(BackEvent.class));
+        clearInvocations(mCallback1);
 
         callbackInfo2.getCallback().onBackStarted(mBackEvent);
 
         waitForIdle();
-        verify(mCallback2).onBackStarted(any(BackEvent.class));
-
-        // Calls sequence: BackProgressAnimator.onBackStarted() -> BackProgressAnimator.reset() ->
-        // Spring.animateToFinalPosition(0). This causes a progress event to be fired.
-        verify(mCallback1, atMost(1)).onBackProgressed(any(BackEvent.class));
-        verifyNoMoreInteractions(mCallback1);
+        verify(mCallback1, never()).onBackStarted(any(BackEvent.class));
+        verify(mCallback2, times(1)).onBackStarted(any(BackEvent.class));
     }
 
     @Test
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index 8f83461..b61f995 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -79,6 +79,7 @@
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -93,6 +94,8 @@
     private static final String ENUM_NAME_PREFIX =
             "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__";
 
+    private static final ArrayList<String> DEPRECATED_VALUES = new ArrayList<>();
+
     private ViewAttachTestActivity mActivity;
     private View mView;
     private HandlerThread mWorker;
@@ -126,6 +129,7 @@
                 CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE"));
         ENUM_NAME_EXCEPTION_MAP.put(
                 CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING"));
+        DEPRECATED_VALUES.add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION");
     }
 
     private static String getEnumName(String name) {
@@ -239,6 +243,7 @@
 
         Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
                 .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
+                        && !DEPRECATED_VALUES.contains(f.getName())
                         && Modifier.isStatic(f.getModifiers())
                         && f.getType() == int.class)
                 .collect(Collectors.toMap(this::getIntFieldChecked, Field::getName));
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index a1a4265..84dd274 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -169,7 +169,7 @@
 
     private WindowInsets insetsWith(Insets content, DisplayCutout cutout) {
         return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null,
-                false, false, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false);
+                false, 0, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false);
     }
 
     private ViewGroup createViewGroupWithId(int id) {
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
index 6167c4b..1a668f7 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -37,10 +37,23 @@
     }
 
     @Test
+    public void testUserRepairMode_isNotRegularUser() {
+        assertTrue(LockPatternUtils.USER_REPAIR_MODE < 0);
+    }
+
+    @Test
     public void testUserFrp_isNotAReservedSpecialUser() throws Exception {
         assertNotEquals(UserHandle.USER_NULL, LockPatternUtils.USER_FRP);
         assertNotEquals(UserHandle.USER_ALL, LockPatternUtils.USER_FRP);
         assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_FRP);
         assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_FRP);
     }
+
+    @Test
+    public void testUserRepairMode_isNotAReservedSpecialUser() throws Exception {
+        assertNotEquals(UserHandle.USER_NULL, LockPatternUtils.USER_REPAIR_MODE);
+        assertNotEquals(UserHandle.USER_ALL, LockPatternUtils.USER_REPAIR_MODE);
+        assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_REPAIR_MODE);
+        assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_REPAIR_MODE);
+    }
 }
diff --git a/core/tests/mockingcoretests/Android.bp b/core/tests/mockingcoretests/Android.bp
index 96811be..741e535 100644
--- a/core/tests/mockingcoretests/Android.bp
+++ b/core/tests/mockingcoretests/Android.bp
@@ -56,7 +56,10 @@
     ],
 
     platform_apis: true,
-    test_suites: ["device-tests"],
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
 
     certificate: "platform",
 }
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index be2c27d..3e0e36d 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -181,7 +181,7 @@
 
             // Verify for ON_START state. Activity should be relaunched.
             getInstrumentation().runOnMainSync(() -> clientSession.startActivity(r));
-            recreateAndVerifyRelaunched(activityThread, activity[0], r, ON_START);
+            recreateAndVerifyRelaunched(activityThread, activity[0], r, ON_PAUSE);
 
             // Verify for ON_RESUME state. Activity should be relaunched.
             getInstrumentation().runOnMainSync(() -> clientSession.resumeActivity(r));
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index e278c52..dcc9686 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -37,6 +37,7 @@
         <permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" />
         <permission name="android.permission.MASTER_CLEAR"/>
         <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+        <permission name="android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED" />
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
         <permission name="android.permission.MOVE_PACKAGE"/>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 2c85fe4..c4530f6 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -346,4 +346,8 @@
 
     <!-- Allow IMS service entitlement app to schedule jobs to run when app in background. -->
     <allow-in-power-save-except-idle package="com.android.imsserviceentitlement" />
+
+    <!-- Allow device lock controller app to schedule jobs and alarms when app in background,
+        otherwise, it may not be able to enforce provision for managed devices. -->
+    <allow-in-power-save package="com.android.devicelockcontroller" />
 </permissions>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 93e44f1..71e9263 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -427,12 +427,6 @@
       "group": "WM_DEBUG_BACK_PREVIEW",
       "at": "com\/android\/server\/wm\/BackNavigationController.java"
     },
-    "-1715268616": {
-      "message": "Last window, removing starting window %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STARTING_WINDOW",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "-1710206702": {
       "message": "Display id=%d is frozen while keyguard locked, return %d",
       "level": "VERBOSE",
@@ -691,6 +685,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "-1449515133": {
+      "message": "Content Recording: stopping active projection for display %d",
+      "level": "ERROR",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "-1443029505": {
       "message": "SAFE MODE ENABLED (menu=%d s=%d dpad=%d trackball=%d)",
       "level": "INFO",
@@ -1279,6 +1279,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-921346089": {
+      "message": "Content Recording: Unable to tell MediaProjectionManagerService to stop the active projection for display %d: %s",
+      "level": "ERROR",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "-917215012": {
       "message": "%s: caller %d is using old GET_TASKS but privileged; allowing",
       "level": "WARN",
@@ -2227,12 +2233,6 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "-88873335": {
-      "message": "Content Recording: Unable to tell MediaProjectionManagerService to stop the active projection: %s",
-      "level": "ERROR",
-      "group": "WM_DEBUG_CONTENT_RECORDING",
-      "at": "com\/android\/server\/wm\/ContentRecorder.java"
-    },
     "-87705714": {
       "message": "findFocusedWindow: focusedApp=null using new focus @ %s",
       "level": "VERBOSE",
@@ -3385,6 +3385,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "975028389": {
+      "message": "unable to call receiver for empty keyboard shortcuts",
+      "level": "ERROR",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "975275467": {
       "message": "Set animatingExit: reason=remove\/isAnimating win=%s",
       "level": "VERBOSE",
@@ -4051,12 +4057,6 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
-    "1671994402": {
-      "message": "Nulling last startingData",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STARTING_WINDOW",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "1674747211": {
       "message": "%s forcing orientation to %d for display id=%d",
       "level": "VERBOSE",
@@ -4237,12 +4237,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "1853793312": {
-      "message": "Notify removed startingWindow %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_STARTING_WINDOW",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "1856783490": {
       "message": "resumeTopActivity: Restarting %s",
       "level": "DEBUG",
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index d0327159..0c493f5 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -94,11 +94,6 @@
         private @LineBreakWordStyle int mLineBreakWordStyle =
                 LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
 
-        // Whether or not enabling phrase breaking automatically.
-        // TODO(b/226012260): Remove this and add LINE_BREAK_WORD_STYLE_PHRASE_AUTO after
-        // the experiment.
-        private boolean mAutoPhraseBreaking = false;
-
         /**
          * Builder constructor.
          */
@@ -128,22 +123,12 @@
         }
 
         /**
-         * Enables or disables the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}.
-         *
-         * @hide
-         */
-        public @NonNull Builder setAutoPhraseBreaking(boolean autoPhraseBreaking) {
-            mAutoPhraseBreaking = autoPhraseBreaking;
-            return this;
-        }
-
-        /**
          * Builds a {@link LineBreakConfig} instance.
          *
          * @return The {@code LineBreakConfig} instance.
          */
         public @NonNull LineBreakConfig build() {
-            return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mAutoPhraseBreaking);
+            return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle);
         }
     }
 
@@ -164,23 +149,6 @@
                 .build();
     }
 
-    /**
-     * Create the LineBreakConfig instance.
-     *
-     * @param lineBreakStyle the line break style for text wrapping.
-     * @param lineBreakWordStyle the line break word style for text wrapping.
-     * @return the {@link LineBreakConfig} instance.     *
-     * @hide
-     */
-    public static @NonNull LineBreakConfig getLineBreakConfig(@LineBreakStyle int lineBreakStyle,
-            @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
-        LineBreakConfig.Builder builder = new LineBreakConfig.Builder();
-        return builder.setLineBreakStyle(lineBreakStyle)
-                .setLineBreakWordStyle(lineBreakWordStyle)
-                .setAutoPhraseBreaking(autoPhraseBreaking)
-                .build();
-    }
-
     /** @hide */
     public static final LineBreakConfig NONE =
             new Builder().setLineBreakStyle(LINE_BREAK_STYLE_NONE)
@@ -188,7 +156,6 @@
 
     private final @LineBreakStyle int mLineBreakStyle;
     private final @LineBreakWordStyle int mLineBreakWordStyle;
-    private final boolean mAutoPhraseBreaking;
 
     /**
      * Constructor with line-break parameters.
@@ -197,10 +164,9 @@
      * {@code LineBreakConfig} instance.
      */
     private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
-            @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
+            @LineBreakWordStyle int lineBreakWordStyle) {
         mLineBreakStyle = lineBreakStyle;
         mLineBreakWordStyle = lineBreakWordStyle;
-        mAutoPhraseBreaking = autoPhraseBreaking;
     }
 
     /**
@@ -221,17 +187,6 @@
         return mLineBreakWordStyle;
     }
 
-    /**
-     * Used to identify if the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled.
-     *
-     * @return The result that records whether or not the automation of
-     * {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled.
-     * @hide
-     */
-    public boolean getAutoPhraseBreaking() {
-        return mAutoPhraseBreaking;
-    }
-
     @Override
     public boolean equals(Object o) {
         if (o == null) return false;
@@ -239,8 +194,7 @@
         if (!(o instanceof LineBreakConfig)) return false;
         LineBreakConfig that = (LineBreakConfig) o;
         return (mLineBreakStyle == that.mLineBreakStyle)
-                && (mLineBreakWordStyle == that.mLineBreakWordStyle)
-                && (mAutoPhraseBreaking == that.mAutoPhraseBreaking);
+                && (mLineBreakWordStyle == that.mLineBreakWordStyle);
     }
 
     @Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 76e0e1e..55eabb0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -48,7 +48,7 @@
     // TODO(b/241126279) Introduce constants to better version functionality
     @Override
     public int getVendorApiLevel() {
-        return 3;
+        return 4;
     }
 
     @NonNull
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index d94e8e4..4d73c20 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -17,7 +17,9 @@
 package androidx.window.extensions.embedding;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
 
 import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
 import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
@@ -340,6 +342,20 @@
         wct.deleteTaskFragment(fragmentToken);
     }
 
+    void reorderTaskFragmentToFront(@NonNull WindowContainerTransaction wct,
+            @NonNull IBinder fragmentToken) {
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_REORDER_TO_FRONT).build();
+        wct.addTaskFragmentOperation(fragmentToken, operation);
+    }
+
+    void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct,
+            @NonNull IBinder fragmentToken, boolean isolatedNav) {
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_SET_ISOLATED_NAVIGATION).setIsolatedNav(isolatedNav).build();
+        wct.addTaskFragmentOperation(fragmentToken, operation);
+    }
+
     void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
         mFragmentInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
     }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 18497ad..381e9d4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -32,7 +32,7 @@
  */
 class SplitContainer {
     @NonNull
-    private final TaskFragmentContainer mPrimaryContainer;
+    private TaskFragmentContainer mPrimaryContainer;
     @NonNull
     private final TaskFragmentContainer mSecondaryContainer;
     @NonNull
@@ -46,17 +46,35 @@
     @NonNull
     private final IBinder mToken;
 
+    /**
+     * Whether the selection of which container is primary can be changed at runtime. Runtime
+     * updates is currently possible only for {@link SplitPinContainer}
+     *
+     * @see SplitPinContainer
+     */
+    private final boolean mIsPrimaryContainerMutable;
+
     SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
             @NonNull Activity primaryActivity,
             @NonNull TaskFragmentContainer secondaryContainer,
             @NonNull SplitRule splitRule,
             @NonNull SplitAttributes splitAttributes) {
+        this(primaryContainer, primaryActivity, secondaryContainer, splitRule, splitAttributes,
+                false /* isPrimaryContainerMutable */);
+    }
+
+    SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
+            @NonNull Activity primaryActivity,
+            @NonNull TaskFragmentContainer secondaryContainer,
+            @NonNull SplitRule splitRule,
+            @NonNull SplitAttributes splitAttributes, boolean isPrimaryContainerMutable) {
         mPrimaryContainer = primaryContainer;
         mSecondaryContainer = secondaryContainer;
         mSplitRule = splitRule;
         mDefaultSplitAttributes = splitRule.getDefaultSplitAttributes();
         mCurrentSplitAttributes = splitAttributes;
         mToken = new Binder("SplitContainer");
+        mIsPrimaryContainerMutable = isPrimaryContainerMutable;
 
         if (shouldFinishPrimaryWithSecondary(splitRule)) {
             if (mPrimaryContainer.getRunningActivityCount() == 1
@@ -74,6 +92,13 @@
         }
     }
 
+    void setPrimaryContainer(@NonNull TaskFragmentContainer primaryContainer) {
+        if (!mIsPrimaryContainerMutable) {
+            throw new IllegalStateException("Cannot update primary TaskFragmentContainer");
+        }
+        mPrimaryContainer = primaryContainer;
+    }
+
     @NonNull
     TaskFragmentContainer getPrimaryContainer() {
         return mPrimaryContainer;
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 89f4890..bcbf728 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -213,6 +213,97 @@
     }
 
     @Override
+    public boolean pinTopActivityStack(int taskId, @NonNull SplitPinRule splitPinRule) {
+        synchronized (mLock) {
+            final TaskContainer task = getTaskContainer(taskId);
+            if (task == null) {
+                Log.e(TAG, "Cannot find the task for id: " + taskId);
+                return false;
+            }
+
+            final TaskFragmentContainer topContainer =
+                    task.getTopNonFinishingTaskFragmentContainer();
+            // Cannot pin the TaskFragment if no other TaskFragment behind it.
+            if (topContainer == null || task.indexOf(topContainer) <= 0) {
+                Log.w(TAG, "Cannot find an ActivityStack to pin or split");
+                return false;
+            }
+            // Abort if the top container is already pinned.
+            if (task.getSplitPinContainer() != null) {
+                Log.w(TAG, "There is already a pinned ActivityStack.");
+                return false;
+            }
+
+            // Find a valid adjacent TaskFragmentContainer
+            final TaskFragmentContainer primaryContainer =
+                    task.getNonFinishingTaskFragmentContainerBelow(topContainer);
+            if (primaryContainer == null) {
+                Log.w(TAG, "Cannot find another ActivityStack to split");
+                return false;
+            }
+
+            // Abort if no space to split.
+            final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
+                    task.getTaskProperties(), splitPinRule,
+                    splitPinRule.getDefaultSplitAttributes(),
+                    getActivitiesMinDimensionsPair(primaryContainer.getTopNonFinishingActivity(),
+                            topContainer.getTopNonFinishingActivity()));
+            if (!SplitPresenter.shouldShowSplit(calculatedSplitAttributes)) {
+                Log.w(TAG, "No space to split, abort pinning top ActivityStack.");
+                return false;
+            }
+
+            // Registers a Split
+            final SplitPinContainer splitPinContainer = new SplitPinContainer(primaryContainer,
+                    topContainer, splitPinRule, calculatedSplitAttributes);
+            task.addSplitContainer(splitPinContainer);
+
+            // Updates the Split
+            final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+            final WindowContainerTransaction wct = transactionRecord.getTransaction();
+
+            mPresenter.setTaskFragmentIsolatedNavigation(wct,
+                    splitPinContainer.getSecondaryContainer().getTaskFragmentToken(),
+                    true /* isolatedNav */);
+            mPresenter.updateSplitContainer(splitPinContainer, wct);
+            transactionRecord.apply(false /* shouldApplyIndependently */);
+            updateCallbackIfNecessary();
+            return true;
+        }
+    }
+
+    @Override
+    public void unpinTopActivityStack(int taskId){
+        synchronized (mLock) {
+            final TaskContainer task = getTaskContainer(taskId);
+            if (task == null) {
+                Log.e(TAG, "Cannot find the task to unpin, id: " + taskId);
+                return;
+            }
+
+            final SplitPinContainer splitPinContainer = task.getSplitPinContainer();
+            if (splitPinContainer == null) {
+                Log.e(TAG, "No ActivityStack is pinned.");
+                return;
+            }
+
+            // Remove the SplitPinContainer from the task.
+            final TaskFragmentContainer containerToUnpin =
+                    splitPinContainer.getSecondaryContainer();
+            task.removeSplitPinContainer();
+
+            // Resets the isolated navigation and updates the container.
+            final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
+            final WindowContainerTransaction wct = transactionRecord.getTransaction();
+            mPresenter.setTaskFragmentIsolatedNavigation(wct,
+                    containerToUnpin.getTaskFragmentToken(), false /* isolated */);
+            updateContainer(wct, containerToUnpin);
+            transactionRecord.apply(false /* shouldApplyIndependently */);
+            updateCallbackIfNecessary();
+        }
+    }
+
+    @Override
     public void setSplitAttributesCalculator(
             @NonNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator) {
         synchronized (mLock) {
@@ -308,7 +399,8 @@
 
             forAllTaskContainers(taskContainer -> {
                 synchronized (mLock) {
-                    final List<TaskFragmentContainer> containers = taskContainer.mContainers;
+                    final List<TaskFragmentContainer> containers =
+                            taskContainer.getTaskFragmentContainers();
                     // Clean up the TaskFragmentContainers by the z-order from the lowest.
                     for (int i = 0; i < containers.size(); i++) {
                         final TaskFragmentContainer container = containers.get(i);
@@ -611,8 +703,7 @@
             @NonNull TaskContainer taskContainer) {
         // Update all TaskFragments in the Task. Make a copy of the list since some may be
         // removed on updating.
-        final List<TaskFragmentContainer> containers =
-                new ArrayList<>(taskContainer.mContainers);
+        final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
         for (int i = containers.size() - 1; i >= 0; i--) {
             final TaskFragmentContainer container = containers.get(i);
             // Wait until onTaskFragmentAppeared to update new container.
@@ -672,7 +763,7 @@
         if (targetContainer == null) {
             // When there is no embedding rule matched, try to place it in the top container
             // like a normal launch.
-            targetContainer = taskContainer.getTopTaskFragmentContainer();
+            targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer();
         }
         if (targetContainer == null) {
             return;
@@ -777,7 +868,8 @@
             return true;
         }
 
-        if (!isOnReparent && getContainerWithActivity(activity) == null
+        final TaskFragmentContainer container = getContainerWithActivity(activity);
+        if (!isOnReparent && container == null
                 && getTaskFragmentTokenFromActivityClientRecord(activity) != null) {
             // We can't find the new launched activity in any recorded container, but it is
             // currently placed in an embedded TaskFragment. This can happen in two cases:
@@ -789,10 +881,21 @@
             return true;
         }
 
-        final TaskFragmentContainer container = getContainerWithActivity(activity);
-        if (!isOnReparent && container != null
-                && container.getTaskContainer().getTopTaskFragmentContainer() != container) {
-            // Do not resolve if the launched activity is not the top-most container in the Task.
+        // Skip resolving if the activity is on a pinned TaskFragmentContainer.
+        // TODO(b/243518738): skip resolving for overlay container.
+        if (container != null) {
+            final TaskContainer taskContainer = container.getTaskContainer();
+            if (taskContainer.isTaskFragmentContainerPinned(container)) {
+                return true;
+            }
+        }
+
+        final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
+        if (!isOnReparent && taskContainer != null
+                && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
+                        != container) {
+            // Do not resolve if the launched activity is not the top-most container (excludes
+            // the pinned container) in the Task.
             return true;
         }
 
@@ -888,7 +991,8 @@
         if (taskContainer == null) {
             return;
         }
-        final TaskFragmentContainer targetContainer = taskContainer.getTopTaskFragmentContainer();
+        final TaskFragmentContainer targetContainer =
+                taskContainer.getTopNonFinishingTaskFragmentContainer();
         if (targetContainer == null) {
             return;
         }
@@ -1188,6 +1292,19 @@
     @GuardedBy("mLock")
     TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
             int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
+        // Skip resolving if started from pinned TaskFragmentContainer.
+        // TODO(b/243518738): skip resolving for overlay container.
+        if (launchingActivity != null) {
+            final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
+                    launchingActivity);
+            final TaskContainer taskContainer =
+                    taskFragmentContainer != null ? taskFragmentContainer.getTaskContainer() : null;
+            if (taskContainer != null && taskContainer.isTaskFragmentContainerPinned(
+                    taskFragmentContainer)) {
+                return null;
+            }
+        }
+
         /*
          * We will check the following to see if there is any embedding rule matched:
          * 1. Whether the new activity intent should always expand.
@@ -1213,11 +1330,13 @@
 
         // 3. Whether the top activity (if any) should be split with the new activity intent.
         final TaskContainer taskContainer = getTaskContainer(taskId);
-        if (taskContainer == null || taskContainer.getTopTaskFragmentContainer() == null) {
+        if (taskContainer == null
+                || taskContainer.getTopNonFinishingTaskFragmentContainer() == null) {
             // There is no other activity in the Task to check split with.
             return null;
         }
-        final TaskFragmentContainer topContainer = taskContainer.getTopTaskFragmentContainer();
+        final TaskFragmentContainer topContainer =
+                taskContainer.getTopNonFinishingTaskFragmentContainer();
         final Activity topActivity = topContainer.getTopNonFinishingActivity();
         if (topActivity != null && topActivity != launchingActivity) {
             final TaskFragmentContainer container = getSecondaryContainerForSplitIfAny(wct,
@@ -1331,7 +1450,8 @@
         // Check pending appeared activity first because there can be a delay for the server
         // update.
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+                    .getTaskFragmentContainers();
             for (int j = containers.size() - 1; j >= 0; j--) {
                 final TaskFragmentContainer container = containers.get(j);
                 if (container.hasPendingAppearedActivity(activityToken)) {
@@ -1342,7 +1462,8 @@
 
         // Check appeared activity if there is no such pending appeared activity.
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+                    .getTaskFragmentContainers();
             for (int j = containers.size() - 1; j >= 0; j--) {
                 final TaskFragmentContainer container = containers.get(j);
                 if (container.hasAppearedActivity(activityToken)) {
@@ -1418,7 +1539,7 @@
         if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
             removeExistingSecondaryContainers(wct, primaryContainer);
         }
-        primaryContainer.getTaskContainer().mSplitContainers.add(splitContainer);
+        primaryContainer.getTaskContainer().addSplitContainer(splitContainer);
     }
 
     /** Cleanups all the dependencies when the TaskFragment is entering PIP. */
@@ -1430,8 +1551,9 @@
             return;
         }
         final List<SplitContainer> splitsToRemove = new ArrayList<>();
+        final List<SplitContainer> splitContainers = taskContainer.getSplitContainers();
         final Set<TaskFragmentContainer> containersToUpdate = new ArraySet<>();
-        for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
+        for (SplitContainer splitContainer : splitContainers) {
             if (splitContainer.getPrimaryContainer() != container
                     && splitContainer.getSecondaryContainer() != container) {
                 continue;
@@ -1449,7 +1571,7 @@
             }
         }
         container.resetDependencies();
-        taskContainer.mSplitContainers.removeAll(splitsToRemove);
+        taskContainer.removeSplitContainers(splitsToRemove);
         // If there is any TaskFragment split with the PIP TaskFragment, update their presentations
         // since the split is dismissed.
         // We don't want to close any of them even if they are dependencies of the PIP TaskFragment.
@@ -1471,7 +1593,7 @@
     void removeContainers(@NonNull TaskContainer taskContainer,
             @NonNull List<TaskFragmentContainer> containers) {
         // Remove all split containers that included this one
-        taskContainer.mContainers.removeAll(containers);
+        taskContainer.removeTaskFragmentContainers(containers);
         // Marked as a pending removal which will be removed after it is actually removed on the
         // server side (#onTaskFragmentVanished).
         // In this way, we can keep track of the Task bounds until we no longer have any
@@ -1481,7 +1603,8 @@
 
         // Cleanup any split references.
         final List<SplitContainer> containersToRemove = new ArrayList<>();
-        for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
+        final List<SplitContainer> splitContainers = taskContainer.getSplitContainers();
+        for (SplitContainer splitContainer : splitContainers) {
             if (containersToRemove.contains(splitContainer)) {
                 // Don't need to check because it has been in the remove list.
                 continue;
@@ -1492,10 +1615,12 @@
                 containersToRemove.add(splitContainer);
             }
         }
-        taskContainer.mSplitContainers.removeAll(containersToRemove);
+        taskContainer.removeSplitContainers(containersToRemove);
 
         // Cleanup any dependent references.
-        for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) {
+        final List<TaskFragmentContainer> taskFragmentContainers =
+                taskContainer.getTaskFragmentContainers();
+        for (TaskFragmentContainer containerToUpdate : taskFragmentContainers) {
             containerToUpdate.removeContainersToFinishOnExit(containers);
         }
     }
@@ -1520,6 +1645,13 @@
             return;
         }
 
+        // If the secondary container is pinned, it should not be removed.
+        final SplitContainer activeContainer =
+                getActiveSplitForContainer(existingSplitContainer.getSecondaryContainer());
+        if (activeContainer instanceof SplitPinContainer) {
+            return;
+        }
+
         existingSplitContainer.getSecondaryContainer().finish(
                 false /* shouldFinishDependent */, mPresenter, wct, this);
     }
@@ -1534,8 +1666,9 @@
         if (taskContainer == null) {
             return null;
         }
-        for (int i = taskContainer.mContainers.size() - 1; i >= 0; i--) {
-            final TaskFragmentContainer container = taskContainer.mContainers.get(i);
+        final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
+        for (int i = containers.size() - 1; i >= 0; i--) {
+            final TaskFragmentContainer container = containers.get(i);
             if (!container.isFinished() && (container.getRunningActivityCount() > 0
                     // We may be waiting for the top TaskFragment to become non-empty after
                     // creation. In that case, we don't want to treat the TaskFragment below it as
@@ -1560,6 +1693,7 @@
             // background.
             return;
         }
+
         if (launchPlaceholderIfNecessary(wct, container)) {
             // Placeholder was launched, the positions will be updated when the activity is added
             // to the secondary container.
@@ -1572,7 +1706,7 @@
             // If the info is not available yet the task fragment will be expanded when it's ready
             return;
         }
-        SplitContainer splitContainer = getActiveSplitForContainer(container);
+        final SplitContainer splitContainer = getActiveSplitForContainer(container);
         if (splitContainer == null) {
             return;
         }
@@ -1629,7 +1763,7 @@
     /** Whether the given split is the topmost split in the Task. */
     private boolean isTopMostSplit(@NonNull SplitContainer splitContainer) {
         final List<SplitContainer> splitContainers = splitContainer.getPrimaryContainer()
-                .getTaskContainer().mSplitContainers;
+                .getTaskContainer().getSplitContainers();
         return splitContainer == splitContainers.get(splitContainers.size() - 1);
     }
 
@@ -1641,7 +1775,8 @@
         if (container == null) {
             return null;
         }
-        final List<SplitContainer> splitContainers = container.getTaskContainer().mSplitContainers;
+        final List<SplitContainer> splitContainers =
+                container.getTaskContainer().getSplitContainers();
         if (splitContainers.isEmpty()) {
             return null;
         }
@@ -1665,7 +1800,7 @@
             @NonNull TaskFragmentContainer firstContainer,
             @NonNull TaskFragmentContainer secondContainer) {
         final List<SplitContainer> splitContainers = firstContainer.getTaskContainer()
-                .mSplitContainers;
+                .getSplitContainers();
         for (int i = splitContainers.size() - 1; i >= 0; i--) {
             final SplitContainer splitContainer = splitContainers.get(i);
             final TaskFragmentContainer primary = splitContainer.getPrimaryContainer();
@@ -1755,6 +1890,10 @@
             // Don't launch placeholder for primary split container.
             return false;
         }
+        if (splitContainer instanceof SplitPinContainer) {
+            // Don't launch placeholder if pinned
+            return false;
+        }
         return true;
     }
 
@@ -1930,7 +2069,8 @@
     @GuardedBy("mLock")
     TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
+            final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+                    .getTaskFragmentContainers();
             for (TaskFragmentContainer container : containers) {
                 if (container.getTaskFragmentToken().equals(fragmentToken)) {
                     return container;
@@ -1945,7 +2085,7 @@
     @GuardedBy("mLock")
     SplitContainer getSplitContainer(@NonNull IBinder token) {
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            final List<SplitContainer> containers = mTaskContainers.valueAt(i).mSplitContainers;
+            final List<SplitContainer> containers = mTaskContainers.valueAt(i).getSplitContainers();
             for (SplitContainer container : containers) {
                 if (container.getToken().equals(token)) {
                     return container;
@@ -2000,8 +2140,9 @@
      * Returns {@code true} if an Activity with the provided component name should always be
      * expanded to occupy full task bounds. Such activity must not be put in a split.
      */
+    @VisibleForTesting
     @GuardedBy("mLock")
-    private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
+    boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
         for (EmbeddingRule rule : mSplitRules) {
             if (!(rule instanceof ActivityRule)) {
                 continue;
@@ -2091,7 +2232,7 @@
                 }
                 for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
                     final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
-                            .mContainers;
+                            .getTaskFragmentContainers();
                     for (int j = containers.size() - 1; j >= 0; j--) {
                         final TaskFragmentContainer container = containers.get(j);
                         if (!container.hasActivity(activityToken)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java
new file mode 100644
index 0000000..03c77a0
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPinContainer.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Client-side descriptor of a split that holds two containers while the secondary
+ * container is pinned on top of the Task and the primary container is the container that is
+ * currently below the secondary container. The primary container could be updated to
+ * another container whenever the existing primary container is removed or no longer
+ * be the container that's right behind the secondary container.
+ */
+class SplitPinContainer extends SplitContainer {
+
+    SplitPinContainer(@NonNull TaskFragmentContainer primaryContainer,
+            @NonNull TaskFragmentContainer secondaryContainer,
+            @NonNull SplitPinRule splitPinRule,
+            @NonNull SplitAttributes splitAttributes) {
+        super(primaryContainer, primaryContainer.getTopNonFinishingActivity(), secondaryContainer,
+                splitPinRule, splitAttributes, true /* isPrimaryContainerMutable */);
+    }
+
+    @Override
+    public String toString() {
+        return "SplitPinContainer{"
+                + " primaryContainer=" + getPrimaryContainer()
+                + " secondaryContainer=" + getSecondaryContainer()
+                + " splitPinRule=" + getSplitRule()
+                + " splitAttributes" + getCurrentSplitAttributes()
+                + "}";
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 53d39d9..5de6acf 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -336,10 +336,6 @@
         // value.
         final SplitRule rule = splitContainer.getSplitRule();
         final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer();
-        final Activity activity = primaryContainer.getTopNonFinishingActivity();
-        if (activity == null) {
-            return;
-        }
         final TaskContainer taskContainer = splitContainer.getTaskContainer();
         final TaskProperties taskProperties = taskContainer.getTaskProperties();
         final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
@@ -424,6 +420,14 @@
         container.setLastRequestedBounds(fragmentOptions.getInitialRelativeBounds());
         container.setLastRequestedWindowingMode(fragmentOptions.getWindowingMode());
         super.createTaskFragment(wct, fragmentOptions);
+
+        // Reorders the pinned TaskFragment to front to ensure it is the front-most TaskFragment.
+        final SplitPinContainer pinnedContainer =
+                container.getTaskContainer().getSplitPinContainer();
+        if (pinnedContainer != null) {
+            reorderTaskFragmentToFront(wct,
+                    pinnedContainer.getSecondaryContainer().getTaskFragmentToken());
+        }
     }
 
     @Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 4b15bb1..463c8ce 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -51,11 +51,15 @@
 
     /** Active TaskFragments in this Task. */
     @NonNull
-    final List<TaskFragmentContainer> mContainers = new ArrayList<>();
+    private final List<TaskFragmentContainer> mContainers = new ArrayList<>();
 
     /** Active split pairs in this Task. */
     @NonNull
-    final List<SplitContainer> mSplitContainers = new ArrayList<>();
+    private final List<SplitContainer> mSplitContainers = new ArrayList<>();
+
+    /** Active pin split pair in this Task. */
+    @Nullable
+    private SplitPinContainer mSplitPinContainer;
 
     @NonNull
     private final Configuration mConfiguration;
@@ -174,11 +178,36 @@
     }
 
     @Nullable
-    TaskFragmentContainer getTopTaskFragmentContainer() {
-        if (mContainers.isEmpty()) {
-            return null;
+    TaskFragmentContainer getTopNonFinishingTaskFragmentContainer() {
+        return getTopNonFinishingTaskFragmentContainer(true /* includePin */);
+    }
+
+    @Nullable
+    TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) {
+        for (int i = mContainers.size() - 1; i >= 0; i--) {
+            final TaskFragmentContainer container = mContainers.get(i);
+            if (!includePin && isTaskFragmentContainerPinned(container)) {
+                continue;
+            }
+            if (!container.isFinished()) {
+                return container;
+            }
         }
-        return mContainers.get(mContainers.size() - 1);
+        return null;
+    }
+
+    /** Gets a non-finishing container below the given one. */
+    @Nullable
+    TaskFragmentContainer getNonFinishingTaskFragmentContainerBelow(
+            @NonNull TaskFragmentContainer current) {
+        final int index = mContainers.indexOf(current);
+        for (int i = index - 1; i >= 0; i--) {
+            final TaskFragmentContainer container = mContainers.get(i);
+            if (!container.isFinished()) {
+                return container;
+            }
+        }
+        return null;
     }
 
     @Nullable
@@ -207,6 +236,129 @@
         return false;
     }
 
+    /**
+     * Returns a list of {@link SplitContainer}. Do not modify the containers directly on the
+     * returned list. Use {@link #addSplitContainer} or {@link #removeSplitContainers} instead.
+     */
+    @NonNull
+    List<SplitContainer> getSplitContainers() {
+        return mSplitContainers;
+    }
+
+    void addSplitContainer(@NonNull SplitContainer splitContainer) {
+        if (splitContainer instanceof SplitPinContainer) {
+            mSplitPinContainer = (SplitPinContainer) splitContainer;
+            mSplitContainers.add(splitContainer);
+            return;
+        }
+
+        // Keeps the SplitPinContainer on the top of the list.
+        mSplitContainers.remove(mSplitPinContainer);
+        mSplitContainers.add(splitContainer);
+        if (mSplitPinContainer != null) {
+            mSplitContainers.add(mSplitPinContainer);
+        }
+    }
+
+    void removeSplitContainers(@NonNull List<SplitContainer> containers) {
+        mSplitContainers.removeAll(containers);
+    }
+
+    void removeSplitPinContainer() {
+        if (mSplitPinContainer == null) {
+            return;
+        }
+
+        final TaskFragmentContainer primaryContainer = mSplitPinContainer.getPrimaryContainer();
+        final TaskFragmentContainer secondaryContainer = mSplitPinContainer.getSecondaryContainer();
+        mSplitContainers.remove(mSplitPinContainer);
+        mSplitPinContainer = null;
+
+        // Remove the other SplitContainers that contains the unpinned container (unless it
+        // is the current top-most split-pair), since the state are no longer valid.
+        final List<SplitContainer> splitsToRemove = new ArrayList<>();
+        for (SplitContainer splitContainer : mSplitContainers) {
+            if (splitContainer.getSecondaryContainer().equals(secondaryContainer)
+                    && !splitContainer.getPrimaryContainer().equals(primaryContainer)) {
+                splitsToRemove.add(splitContainer);
+            }
+        }
+        removeSplitContainers(splitsToRemove);
+    }
+
+    @Nullable
+    SplitPinContainer getSplitPinContainer() {
+        return mSplitPinContainer;
+    }
+
+    boolean isTaskFragmentContainerPinned(@NonNull TaskFragmentContainer taskFragmentContainer) {
+        return mSplitPinContainer != null
+                && mSplitPinContainer.getSecondaryContainer() == taskFragmentContainer;
+    }
+
+    void addTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
+        mContainers.add(taskFragmentContainer);
+        onTaskFragmentContainerUpdated();
+    }
+
+    void addTaskFragmentContainer(int index, @NonNull TaskFragmentContainer taskFragmentContainer) {
+        mContainers.add(index, taskFragmentContainer);
+        onTaskFragmentContainerUpdated();
+    }
+
+    void removeTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
+        mContainers.remove(taskFragmentContainer);
+        onTaskFragmentContainerUpdated();
+    }
+
+    void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) {
+        mContainers.removeAll(taskFragmentContainer);
+        onTaskFragmentContainerUpdated();
+    }
+
+    void clearTaskFragmentContainer() {
+        mContainers.clear();
+        onTaskFragmentContainerUpdated();
+    }
+
+    /**
+     * Returns a list of {@link TaskFragmentContainer}. Do not modify the containers directly on
+     * the returned list. Use {@link #addTaskFragmentContainer},
+     * {@link #removeTaskFragmentContainer} or other related methods instead.
+     */
+    @NonNull
+    List<TaskFragmentContainer> getTaskFragmentContainers() {
+        return mContainers;
+    }
+
+    private void onTaskFragmentContainerUpdated() {
+        if (mSplitPinContainer == null) {
+            return;
+        }
+
+        final TaskFragmentContainer pinnedContainer = mSplitPinContainer.getSecondaryContainer();
+        final int pinnedContainerIndex = mContainers.indexOf(pinnedContainer);
+        if (pinnedContainerIndex <= 0) {
+            removeSplitPinContainer();
+            return;
+        }
+
+        // Ensure the pinned container is top-most.
+        if (pinnedContainerIndex != mContainers.size() - 1) {
+            mContainers.remove(pinnedContainer);
+            mContainers.add(pinnedContainer);
+        }
+
+        // Update the primary container adjacent to the pinned container if needed.
+        final TaskFragmentContainer adjacentContainer =
+                getNonFinishingTaskFragmentContainerBelow(pinnedContainer);
+        if (adjacentContainer == null) {
+            removeSplitPinContainer();
+        } else if (mSplitPinContainer.getPrimaryContainer() != adjacentContainer) {
+            mSplitPinContainer.setPrimaryContainer(adjacentContainer);
+        }
+    }
+
     /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
     void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
         for (SplitContainer container : mSplitContainers) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 60be9d1..61df335 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -180,23 +180,25 @@
                 throw new IllegalArgumentException(
                         "pairedPrimaryContainer must be in the same Task");
             }
-            final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
-            taskContainer.mContainers.add(primaryIndex + 1, this);
+            final int primaryIndex = taskContainer.indexOf(pairedPrimaryContainer);
+            taskContainer.addTaskFragmentContainer(primaryIndex + 1, this);
         } else if (pendingAppearedActivity != null) {
             // The TaskFragment will be positioned right above the pending appeared Activity. If any
             // existing TaskFragment is empty with pending Intent, it is likely that the Activity of
             // the pending Intent hasn't been created yet, so the new Activity should be below the
             // empty TaskFragment.
-            int i = taskContainer.mContainers.size() - 1;
+            final List<TaskFragmentContainer> containers =
+                    taskContainer.getTaskFragmentContainers();
+            int i = containers.size() - 1;
             for (; i >= 0; i--) {
-                final TaskFragmentContainer container = taskContainer.mContainers.get(i);
+                final TaskFragmentContainer container = containers.get(i);
                 if (!container.isEmpty() || container.getPendingAppearedIntent() == null) {
                     break;
                 }
             }
-            taskContainer.mContainers.add(i + 1, this);
+            taskContainer.addTaskFragmentContainer(i + 1, this);
         } else {
-            taskContainer.mContainers.add(this);
+            taskContainer.addTaskFragmentContainer(this);
         }
         if (pendingAppearedActivity != null) {
             addPendingAppearedActivity(pendingAppearedActivity);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index a45a8a1..2eacaaf 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -304,7 +304,7 @@
      * {@link IllegalArgumentException} since this can cause negative UI effects down stream.
      *
      * @param context a proxy for the {@link android.view.Window} that contains the
-     * {@link DisplayFeature}.
+     *                {@link DisplayFeature}.
      * @return a {@link List}  of {@link DisplayFeature}s that are within the
      * {@link android.view.Window} of the {@link Activity}
      */
@@ -336,10 +336,32 @@
             rotateRectToDisplayRotation(displayId, featureRect);
             transformToWindowSpaceRect(windowConfiguration, featureRect);
 
-            if (!isZero(featureRect)) {
+            if (isZero(featureRect)) {
                 // TODO(b/228641877): Remove guarding when fixed.
-                features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
+                continue;
             }
+            if (featureRect.left != 0 && featureRect.top != 0) {
+                throw new IllegalArgumentException("Bounding rectangle must start at the top or "
+                        + "left of the window. BaseFeatureRect: " + baseFeature.getRect()
+                        + ", FeatureRect: " + featureRect
+                        + ", WindowConfiguration: " + windowConfiguration);
+
+            }
+            if (featureRect.left == 0
+                    && featureRect.width() != windowConfiguration.getBounds().width()) {
+                throw new IllegalArgumentException("Horizontal FoldingFeature must have full width."
+                        + " BaseFeatureRect: " + baseFeature.getRect()
+                        + ", FeatureRect: " + featureRect
+                        + ", WindowConfiguration: " + windowConfiguration);
+            }
+            if (featureRect.top == 0
+                    && featureRect.height() != windowConfiguration.getBounds().height()) {
+                throw new IllegalArgumentException("Vertical FoldingFeature must have full height."
+                        + " BaseFeatureRect: " + baseFeature.getRect()
+                        + ", FeatureRect: " + featureRect
+                        + ", WindowConfiguration: " + windowConfiguration);
+            }
+            features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
         }
         return features;
     }
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 ff08782..b2ffad7 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
@@ -183,23 +183,23 @@
         // tf2 has running activity so is active.
         final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
         doReturn(1).when(tf2).getRunningActivityCount();
-        taskContainer.mContainers.add(tf2);
+        taskContainer.addTaskFragmentContainer(tf2);
         // tf3 is finished so is not active.
         final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
         doReturn(true).when(tf3).isFinished();
         doReturn(false).when(tf3).isWaitingActivityAppear();
-        taskContainer.mContainers.add(tf3);
+        taskContainer.addTaskFragmentContainer(tf3);
         mSplitController.mTaskContainers.put(TASK_ID, taskContainer);
 
         assertWithMessage("Must return tf2 because tf3 is not active.")
                 .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
 
-        taskContainer.mContainers.remove(tf3);
+        taskContainer.removeTaskFragmentContainer(tf3);
 
         assertWithMessage("Must return tf2 because tf2 has running activity.")
                 .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
 
-        taskContainer.mContainers.remove(tf2);
+        taskContainer.removeTaskFragmentContainer(tf2);
 
         assertWithMessage("Must return tf because we are waiting for tf1 to appear.")
                 .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
@@ -320,11 +320,11 @@
         doReturn(tf).when(splitContainer).getSecondaryContainer();
         doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
         doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
-        final List<SplitContainer> splitContainers =
-                mSplitController.getTaskContainer(TASK_ID).mSplitContainers;
-        splitContainers.add(splitContainer);
+        final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+        taskContainer.addSplitContainer(splitContainer);
         // Add a mock SplitContainer on top of splitContainer
-        splitContainers.add(1, mock(SplitContainer.class));
+        final SplitContainer splitContainer2 = mock(SplitContainer.class);
+        taskContainer.addSplitContainer(splitContainer2);
 
         mSplitController.updateContainer(mTransaction, tf);
 
@@ -332,7 +332,9 @@
 
         // Verify if one or both containers in the top SplitContainer are finished,
         // dismissPlaceholder() won't be called.
-        splitContainers.remove(1);
+        final ArrayList<SplitContainer> splitContainersToRemove = new ArrayList<>();
+        splitContainersToRemove.add(splitContainer2);
+        taskContainer.removeSplitContainers(splitContainersToRemove);
         doReturn(true).when(tf).isFinished();
 
         mSplitController.updateContainer(mTransaction, tf);
@@ -363,7 +365,8 @@
         final Activity r1 = createMockActivity();
         addSplitTaskFragments(r0, r1);
         final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
-        final TaskFragmentContainer taskFragmentContainer = taskContainer.mContainers.get(0);
+        final TaskFragmentContainer taskFragmentContainer =
+                taskContainer.getTaskFragmentContainers().get(0);
         spyOn(taskContainer);
 
         // No update when the Task is invisible.
@@ -377,7 +380,7 @@
         doReturn(true).when(taskContainer).isVisible();
         mSplitController.updateContainer(mTransaction, taskFragmentContainer);
 
-        verify(mSplitPresenter).updateSplitContainer(taskContainer.mSplitContainers.get(0),
+        verify(mSplitPresenter).updateSplitContainer(taskContainer.getSplitContainers().get(0),
                 mTransaction);
     }
 
@@ -592,6 +595,18 @@
     }
 
     @Test
+    public void testResolveStartActivityIntent_skipIfPinned() {
+        final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+        final TaskContainer taskContainer = container.getTaskContainer();
+        spyOn(taskContainer);
+        final Intent intent = new Intent();
+        setupSplitRule(mActivity, intent);
+        doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(container);
+        assertNull(mSplitController.resolveStartActivityIntent(mTransaction, TASK_ID, intent,
+                mActivity));
+    }
+
+    @Test
     public void testPlaceActivityInTopContainer() {
         mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
 
@@ -1041,6 +1056,29 @@
     }
 
     @Test
+    public void testResolveActivityToContainer_skipIfNonTopOrPinned() {
+        final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+        final Activity pinnedActivity = createMockActivity();
+        final TaskFragmentContainer topContainer = mSplitController.newContainer(pinnedActivity,
+                TASK_ID);
+        final TaskContainer taskContainer = container.getTaskContainer();
+        spyOn(taskContainer);
+        doReturn(container).when(taskContainer).getTopNonFinishingTaskFragmentContainer(false);
+        doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(topContainer);
+
+        // No need to handle when the new launched activity is in a pinned TaskFragment.
+        assertTrue(mSplitController.resolveActivityToContainer(mTransaction, pinnedActivity,
+                false /* isOnReparent */));
+        verify(mSplitController, never()).shouldExpand(any(), any());
+
+        // Should proceed to resolve if the new launched activity is in the next top TaskFragment
+        // (e.g. the top-most TaskFragment is pinned)
+        mSplitController.resolveActivityToContainer(mTransaction, mActivity,
+                false /* isOnReparent */);
+        verify(mSplitController).shouldExpand(any(), any());
+    }
+
+    @Test
     public void testGetPlaceholderOptions() {
         // Setup to make sure a transaction record is started.
         mTransactionManager.startNewTransaction();
@@ -1090,8 +1128,8 @@
         verify(mTransaction).finishActivity(mActivity.getActivityToken());
         verify(mTransaction).finishActivity(secondaryActivity0.getActivityToken());
         verify(mTransaction).finishActivity(secondaryActivity1.getActivityToken());
-        assertTrue(taskContainer.mContainers.isEmpty());
-        assertTrue(taskContainer.mSplitContainers.isEmpty());
+        assertTrue(taskContainer.getTaskFragmentContainers().isEmpty());
+        assertTrue(taskContainer.getSplitContainers().isEmpty());
     }
 
     @Test
@@ -1363,15 +1401,13 @@
         TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
         tf.setInfo(mTransaction, createMockTaskFragmentInfo(tf, mActivity));
 
-        List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID)
-                .mContainers;
-
-        assertEquals(containers.get(0), tf);
+        final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID);
+        assertEquals(taskContainer.getTaskFragmentContainers().get(0), tf);
 
         mSplitController.finishActivityStacks(Collections.singleton(tf.getTaskFragmentToken()));
 
         verify(mSplitPresenter).deleteTaskFragment(any(), eq(tf.getTaskFragmentToken()));
-        assertTrue(containers.isEmpty());
+        assertTrue(taskContainer.getTaskFragmentContainers().isEmpty());
     }
 
     @Test
@@ -1381,10 +1417,8 @@
         bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity));
         topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity()));
 
-        List<TaskFragmentContainer> containers = mSplitController.mTaskContainers.get(TASK_ID)
-                .mContainers;
-
-        assertEquals(containers.size(), 2);
+        final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID);
+        assertEquals(taskContainer.getTaskFragmentContainers().size(), 2);
 
         Set<IBinder> activityStackTokens = new ArraySet<>(new IBinder[]{
                 topTf.getTaskFragmentToken(), bottomTf.getTaskFragmentToken()});
@@ -1403,7 +1437,7 @@
                         + "regardless of the order in ActivityStack set",
                 topTf.getTaskFragmentToken(), fragmentTokens.get(1));
 
-        assertTrue(containers.isEmpty());
+        assertTrue(taskContainer.getTaskFragmentContainers().isEmpty());
     }
 
     @Test
@@ -1463,6 +1497,51 @@
         verify(testRecord).apply(eq(false));
     }
 
+    @Test
+    public void testPinTopActivityStack() {
+        // Create two activities.
+        final Activity primaryActivity = createMockActivity();
+        final Activity secondaryActivity = createMockActivity();
+
+        // Unable to pin if not being embedded.
+        SplitPinRule splitPinRule = new SplitPinRule.Builder(new SplitAttributes.Builder().build(),
+                parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+        assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+        // Split the two activities.
+        addSplitTaskFragments(primaryActivity, secondaryActivity);
+        final TaskFragmentContainer primaryContainer =
+                mSplitController.getContainerWithActivity(primaryActivity);
+        spyOn(primaryContainer);
+
+        // Unable to pin if no valid TaskFragment.
+        doReturn(true).when(primaryContainer).isFinished();
+        assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+        // Otherwise, should pin successfully.
+        doReturn(false).when(primaryContainer).isFinished();
+        assertTrue(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+        // Unable to pin if there is already a pinned TaskFragment
+        assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule));
+
+        // Unable to pin on an unknown Task.
+        assertFalse(mSplitController.pinTopActivityStack(TASK_ID + 1, splitPinRule));
+
+        // Gets the current size of all the SplitContainers.
+        final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+        final int splitContainerCount = taskContainer.getSplitContainers().size();
+
+        // Create another activity and split with primary activity.
+        final Activity thirdActivity = createMockActivity();
+        addSplitTaskFragments(primaryActivity, thirdActivity);
+
+        // Ensure another SplitContainer is added and the pinned TaskFragment still on top
+        assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1);
+        assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity()
+                == secondaryActivity);
+    }
+
     /** Creates a mock activity in the organizer process. */
     private Activity createMockActivity() {
         return createMockActivity(TASK_ID);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 13e7092..000c65a 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -127,7 +127,7 @@
         assertFalse(taskContainer.isEmpty());
 
         taskContainer.mFinishedContainer.add(tf.getTaskFragmentToken());
-        taskContainer.mContainers.clear();
+        taskContainer.clearTaskFragmentContainer();
 
         assertFalse(taskContainer.isEmpty());
     }
@@ -135,15 +135,15 @@
     @Test
     public void testGetTopTaskFragmentContainer() {
         final TaskContainer taskContainer = createTestTaskContainer();
-        assertNull(taskContainer.getTopTaskFragmentContainer());
+        assertNull(taskContainer.getTopNonFinishingTaskFragmentContainer());
 
         final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
                 new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
-        assertEquals(tf0, taskContainer.getTopTaskFragmentContainer());
+        assertEquals(tf0, taskContainer.getTopNonFinishingTaskFragmentContainer());
 
         final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
                 new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
-        assertEquals(tf1, taskContainer.getTopTaskFragmentContainer());
+        assertEquals(tf1, taskContainer.getTopNonFinishingTaskFragmentContainer());
     }
 
     @Test
@@ -152,13 +152,13 @@
         assertNull(taskContainer.getTopNonFinishingActivity());
 
         final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class);
-        taskContainer.mContainers.add(tf0);
+        taskContainer.addTaskFragmentContainer(tf0);
         final Activity activity0 = mock(Activity.class);
         doReturn(activity0).when(tf0).getTopNonFinishingActivity();
         assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
 
         final TaskFragmentContainer tf1 = mock(TaskFragmentContainer.class);
-        taskContainer.mContainers.add(tf1);
+        taskContainer.addTaskFragmentContainer(tf1);
         assertEquals(activity0, taskContainer.getTopNonFinishingActivity());
 
         final Activity activity1 = mock(Activity.class);
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 54978bd..603272e 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -42,16 +42,19 @@
 filegroup {
     name: "wm_shell_util-sources",
     srcs: [
-        "src/com/android/wm/shell/util/**/*.java",
-        "src/com/android/wm/shell/common/split/SplitScreenConstants.java",
-        "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
-        "src/com/android/wm/shell/common/TransactionPool.java",
-        "src/com/android/wm/shell/common/bubbles/*.java",
-        "src/com/android/wm/shell/common/TriangleShape.java",
         "src/com/android/wm/shell/animation/Interpolators.java",
+        "src/com/android/wm/shell/animation/PhysicsAnimator.kt",
+        "src/com/android/wm/shell/common/bubbles/*.kt",
+        "src/com/android/wm/shell/common/bubbles/*.java",
+        "src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt",
+        "src/com/android/wm/shell/common/split/SplitScreenConstants.java",
+        "src/com/android/wm/shell/common/TransactionPool.java",
+        "src/com/android/wm/shell/common/TriangleShape.java",
+        "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java",
         "src/com/android/wm/shell/pip/PipContentOverlay.java",
         "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
-        "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java",
+        "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
+        "src/com/android/wm/shell/util/**/*.java",
     ],
     path: "src",
 }
@@ -148,6 +151,7 @@
     ],
     static_libs: [
         "androidx.appcompat_appcompat",
+        "androidx.core_core-animation",
         "androidx.arch.core_core-runtime",
         "androidx-constraintlayout_constraintlayout",
         "androidx.dynamicanimation_dynamicanimation",
diff --git a/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml
new file mode 100644
index 0000000..d99d64d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_section.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <ripple android:color="#99999999">
+            <item android:drawable="@drawable/bubble_manage_menu_bg" />
+        </ripple>
+    </item>
+    <item android:drawable="@drawable/bubble_manage_menu_bg" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
new file mode 100644
index 0000000..02b7075
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M180,840Q156,840 138,822Q120,804 120,780L120,180Q120,156 138,138Q156,120 180,120L780,120Q804,120 822,138Q840,156 840,180L840,780Q840,804 822,822Q804,840 780,840L180,840ZM180,780L780,780Q780,780 780,780Q780,780 780,780L780,277L180,277L180,780Q180,780 180,780Q180,780 180,780Z" />
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
index 5d77713..ce24275 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -13,13 +13,20 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-    <group android:translateY="8.0">
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="128dp"
+    android:height="4dp"
+    android:viewportWidth="128"
+    android:viewportHeight="4"
+    >
+    <group>
+        <clip-path
+            android:pathData="M2 0H126C127.105 0 128 0.895431 128 2C128 3.10457 127.105 4 126 4H2C0.895431 4 0 3.10457 0 2C0 0.895431 0.895431 0 2 0Z"
+            />
         <path
-            android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
+            android:pathData="M0 0V4H128V0"
+            android:fillColor="@android:color/black"
+            />
     </group>
 </vector>
diff --git a/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml b/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml
new file mode 100644
index 0000000..f450846
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_expand_less.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M18.59,16.41L20,15L12,7L4,15L5.41,16.41L12,9.83"
+      android:fillColor="#5F6368"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml
new file mode 100644
index 0000000..6e4752c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="48"
+        android:viewportHeight="48">
+    <path
+        android:fillColor="@color/compat_controls_background"
+        android:strokeAlpha="0.8"
+        android:fillAlpha="0.8"
+        android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/>
+    <group
+        android:translateX="12"
+        android:translateY="12">
+        <path
+            android:fillColor="@color/compat_controls_text"
+            android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/>
+    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml
new file mode 100644
index 0000000..141a1ce
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@color/compat_background_ripple">
+    <item android:drawable="@drawable/user_aspect_ratio_settings_button"/>
+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
new file mode 100644
index 0000000..ddcd5c6
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/bubble_bar_manage_menu_item_height"
+    android:gravity="center_vertical"
+    android:paddingStart="@dimen/bubble_menu_padding"
+    android:paddingEnd="@dimen/bubble_menu_padding"
+    android:background="@drawable/bubble_manage_menu_row">
+
+    <ImageView
+        android:id="@+id/bubble_bar_menu_item_icon"
+        android:layout_width="@dimen/bubble_bar_manage_menu_item_icon_size"
+        android:layout_height="@dimen/bubble_bar_manage_menu_item_icon_size"
+        android:contentDescription="@null"/>
+
+    <TextView
+        android:id="@+id/bubble_bar_menu_item_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
+
+</com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
new file mode 100644
index 0000000..82e5aee
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<com.android.wm.shell.bubbles.bar.BubbleBarMenuView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_horizontal"
+    android:minWidth="@dimen/bubble_bar_manage_menu_min_width"
+    android:orientation="vertical"
+    android:elevation="@dimen/bubble_manage_menu_elevation"
+    android:paddingTop="@dimen/bubble_bar_manage_menu_padding_top"
+    android:paddingHorizontal="@dimen/bubble_bar_manage_menu_padding"
+    android:paddingBottom="@dimen/bubble_bar_manage_menu_padding"
+    android:clipToPadding="false">
+
+    <LinearLayout
+        android:id="@+id/bubble_bar_manage_menu_bubble_section"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/bubble_bar_manage_menu_item_height"
+        android:orientation="horizontal"
+        android:gravity="center_vertical"
+        android:paddingStart="14dp"
+        android:paddingEnd="12dp"
+        android:background="@drawable/bubble_manage_menu_section"
+        android:elevation="@dimen/bubble_manage_menu_elevation">
+
+        <ImageView
+            android:id="@+id/bubble_bar_manage_menu_bubble_icon"
+            android:layout_width="@dimen/bubble_menu_icon_size"
+            android:layout_height="@dimen/bubble_menu_icon_size"
+            android:contentDescription="@null" />
+
+        <TextView
+            android:id="@+id/bubble_bar_manage_menu_bubble_title"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_weight="1"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
+
+        <ImageView
+            android:id="@+id/bubble_bar_manage_menu_dismiss_icon"
+            android:layout_width="@dimen/bubble_bar_manage_menu_dismiss_icon_size"
+            android:layout_height="@dimen/bubble_bar_manage_menu_dismiss_icon_size"
+            android:layout_marginStart="8dp"
+            android:contentDescription="@null"
+            android:src="@drawable/ic_expand_less"
+            app:tint="?android:attr/textColorPrimary" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/bubble_bar_manage_menu_actions_section"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:layout_marginTop="@dimen/bubble_bar_manage_menu_section_spacing"
+        android:background="@drawable/bubble_manage_menu_bg"
+        android:elevation="@dimen/bubble_manage_menu_elevation" />
+
+</com.android.wm.shell.bubbles.bar.BubbleBarMenuView>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
index dfaeeeb..257fe15 100644
--- a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
@@ -55,7 +55,7 @@
 
     <include android:id="@+id/size_compat_hint"
         android:visibility="gone"
-        android:layout_width="@dimen/size_compat_hint_width"
+        android:layout_width="@dimen/compat_hint_width"
         android:layout_height="wrap_content"
         layout="@layout/compat_mode_hint"/>
 
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index fb1980a..7e0c207 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -78,6 +78,19 @@
         android:layout_weight="1"/>
 
     <ImageButton
+        android:id="@+id/maximize_window"
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        android:padding="9dp"
+        android:layout_marginEnd="8dp"
+        android:contentDescription="@string/maximize_button_text"
+        android:src="@drawable/decor_desktop_mode_maximize_button_dark"
+        android:scaleType="fitCenter"
+        android:gravity="end"
+        android:background="@null"
+        android:tint="@color/desktop_mode_caption_maximize_button_dark"/>
+
+    <ImageButton
         android:id="@+id/close_window"
         android:layout_width="40dp"
         android:layout_height="40dp"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
index 1d6864c..d93e9ba 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
@@ -25,9 +25,9 @@
 
     <ImageButton
         android:id="@+id/caption_handle"
-        android:layout_width="176dp"
+        android:layout_width="128dp"
         android:layout_height="42dp"
-        android:paddingHorizontal="24dp"
+        android:paddingVertical="19dp"
         android:contentDescription="@string/handle_text"
         android:src="@drawable/decor_handle_dark"
         tools:tint="@color/desktop_mode_caption_handle_bar_dark"
diff --git a/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
new file mode 100644
index 0000000..433d854
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.wm.shell.compatui.UserAspectRatioSettingsLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:gravity="bottom|end">
+
+    <include android:id="@+id/user_aspect_ratio_settings_hint"
+        android:visibility="gone"
+        android:layout_width="@dimen/compat_hint_width"
+        android:layout_height="wrap_content"
+        layout="@layout/compat_mode_hint"/>
+
+    <ImageButton
+        android:id="@+id/user_aspect_ratio_settings_button"
+        android:visibility="gone"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/compat_button_margin"
+        android:layout_marginBottom="@dimen/compat_button_margin"
+        android:src="@drawable/user_aspect_ratio_settings_button_ripple"
+        android:background="@android:color/transparent"
+        android:contentDescription="@string/user_aspect_ratio_settings_button_description"/>
+
+</com.android.wm.shell.compatui.UserAspectRatioSettingsLayout>
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 171a6b2..b2ec98b 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -30,6 +30,9 @@
     <color name="bubbles_light">#FFFFFF</color>
     <color name="bubbles_dark">@color/GM2_grey_800</color>
     <color name="bubbles_icon_tint">@color/GM2_grey_700</color>
+    <color name="bubble_bar_expanded_view_handle_light">#EBffffff</color>
+    <color name="bubble_bar_expanded_view_handle_dark">#99000000</color>
+    <color name="bubble_bar_expanded_view_menu_close">#DC362E</color>
 
     <!-- PiP -->
     <color name="pip_custom_close_bg">#D93025</color>
@@ -61,6 +64,8 @@
     <color name="desktop_mode_caption_expand_button_dark">#48473A</color>
     <color name="desktop_mode_caption_close_button_light">#EFF1F2</color>
     <color name="desktop_mode_caption_close_button_dark">#1C1C17</color>
+    <color name="desktop_mode_caption_maximize_button_light">#EFF1F2</color>
+    <color name="desktop_mode_caption_maximize_button_dark">#1C1C17</color>
     <color name="desktop_mode_caption_app_name_light">#EFF1F2</color>
     <color name="desktop_mode_caption_app_name_dark">#1C1C17</color>
     <color name="desktop_mode_caption_menu_text_color">#191C1D</color>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 2be34c9..0502a99 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -226,10 +226,26 @@
     <dimen name="bubble_user_education_padding_end">58dp</dimen>
     <!-- Padding between the bubble and the user education text. -->
     <dimen name="bubble_user_education_stack_padding">16dp</dimen>
-    <!-- Size of the bubble bar (height), should match transient_taskbar_size in Launcher. -->
-    <dimen name="bubblebar_size">72dp</dimen>
-    <!-- The size of the drag handle / menu shown along with a bubble bar expanded view. -->
-    <dimen name="bubblebar_expanded_view_menu_size">16dp</dimen>
+    <!-- The size of the caption bar inset at the top of bubble bar expanded view. -->
+    <dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen>
+    <!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. -->
+    <dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen>
+    <!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. -->
+    <dimen name="bubble_bar_expanded_view_caption_dot_spacing">4dp</dimen>
+    <!-- Minimum width of the bubble bar manage menu. -->
+    <dimen name="bubble_bar_manage_menu_min_width">200dp</dimen>
+    <!-- Size of the dismiss icon in the bubble bar manage menu. -->
+    <dimen name="bubble_bar_manage_menu_dismiss_icon_size">16dp</dimen>
+    <!-- Padding of the bubble bar manage menu, provides space for menu shadows -->
+    <dimen name="bubble_bar_manage_menu_padding">8dp</dimen>
+    <!-- Top padding of the bubble bar manage menu -->
+    <dimen name="bubble_bar_manage_menu_padding_top">2dp</dimen>
+    <!-- Spacing between sections of the bubble bar manage menu -->
+    <dimen name="bubble_bar_manage_menu_section_spacing">2dp</dimen>
+    <!-- Height of an item in the bubble bar manage menu. -->
+    <dimen name="bubble_bar_manage_menu_item_height">52dp</dimen>
+    <!-- Size of the icons in the bubble bar manage menu. -->
+    <dimen name="bubble_bar_manage_menu_item_icon_size">20dp</dimen>
 
     <!-- Bottom and end margin for compat buttons. -->
     <dimen name="compat_button_margin">24dp</dimen>
@@ -244,8 +260,8 @@
         + compat_button_margin - compat_hint_corner_radius - compat_hint_point_width / 2). -->
     <dimen name="compat_hint_padding_end">7dp</dimen>
 
-    <!-- The width of the size compat hint. -->
-    <dimen name="size_compat_hint_width">188dp</dimen>
+    <!-- The width of the compat hint. -->
+    <dimen name="compat_hint_width">188dp</dimen>
 
     <!-- The width of the camera compat hint. -->
     <dimen name="camera_compat_hint_width">143dp</dimen>
@@ -398,7 +414,11 @@
     <!-- The radius of the caption menu shadow. -->
     <dimen name="desktop_mode_handle_menu_shadow_radius">2dp</dimen>
 
-    <dimen name="freeform_resize_handle">30dp</dimen>
+    <dimen name="freeform_resize_handle">15dp</dimen>
 
     <dimen name="freeform_resize_corner">44dp</dimen>
+
+    <!-- The height of the area at the top of the screen where a freeform task will transition to
+    fullscreen if dragged until the top bound of the task is within the area. -->
+    <dimen name="desktop_mode_transition_area_height">16dp</dimen>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index b192fdf..8cbc3d0 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -173,7 +173,13 @@
     <string name="accessibility_bubble_dismissed">Bubble dismissed.</string>
 
     <!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
-    <string name="restart_button_description">Tap to restart this app for a better view.</string>
+    <string name="restart_button_description">Tap to restart this app for a better view</string>
+
+    <!-- Tooltip text of the button for the user aspect ratio settings. [CHAR LIMIT=NONE] -->
+    <string name="user_aspect_ratio_settings_button_hint">Change this app\'s aspect ratio in Settings</string>
+
+    <!-- Content description of the button for the user aspect ratio settings. [CHAR LIMIT=NONE] -->
+    <string name="user_aspect_ratio_settings_button_description">Change aspect ratio</string>
 
     <!-- Description of the camera compat button for applying stretched issues treatment in the hint for
          compatibility control. [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 39f861d..5cf9175 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -50,7 +50,7 @@
     final SurfaceControl mLeash;
     /** Area in absolute coordinate that the animation surface shouldn't go beyond. */
     @NonNull
-    private final Rect mWholeAnimationBounds = new Rect();
+    final Rect mWholeAnimationBounds = new Rect();
     /**
      * Area in absolute coordinate that should represent all the content to show for this window.
      * This should be the end bounds for opening window, and start bounds for closing window in case
@@ -229,20 +229,7 @@
             mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
             t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
             t.setAlpha(mLeash, mTransformation.getAlpha());
-
-            // The following applies an inverse scale to the clip-rect so that it crops "after" the
-            // scale instead of before.
-            mVecs[1] = mVecs[2] = 0;
-            mVecs[0] = mVecs[3] = 1;
-            mTransformation.getMatrix().mapVectors(mVecs);
-            mVecs[0] = 1.f / mVecs[0];
-            mVecs[3] = 1.f / mVecs[3];
-            final Rect clipRect = mTransformation.getClipRect();
-            mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f);
-            mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f);
-            mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f);
-            mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f);
-            t.setCrop(mLeash, mRect);
+            t.setWindowCrop(mLeash, mWholeAnimationBounds.width(), mWholeAnimationBounds.height());
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 1793a3d..4640106 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -26,7 +26,6 @@
 import android.view.animation.Animation;
 import android.view.animation.AnimationSet;
 import android.view.animation.AnimationUtils;
-import android.view.animation.ClipRectAnimation;
 import android.view.animation.Interpolator;
 import android.view.animation.LinearInterpolator;
 import android.view.animation.ScaleAnimation;
@@ -189,14 +188,6 @@
                 startBounds.top - endBounds.top, 0);
         endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
         endSet.addAnimation(endTranslate);
-        // The end leash is resizing, we should update the window crop based on the clip rect.
-        final Rect startClip = new Rect(startBounds);
-        final Rect endClip = new Rect(endBounds);
-        startClip.offsetTo(0, 0);
-        endClip.offsetTo(0, 0);
-        final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
-        clipAnim.setDuration(CHANGE_ANIMATION_DURATION);
-        endSet.addAnimation(clipAnim);
         endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(),
                 parentBounds.height());
         endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 57d374b..06ce371 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -186,6 +186,6 @@
         if (callback == null) {
             throw new IllegalStateException("No finish callback found");
         }
-        callback.onTransitionFinished(null /* wct */, null /* wctCB */);
+        callback.onTransitionFinished(null /* wct */);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
index 798250d..26edd7d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
@@ -117,6 +117,20 @@
      * @param endValue  the end value of the animator
      * @param velocity  the current velocity of the motion
      */
+    public void apply(androidx.core.animation.Animator animator,
+            float currValue, float endValue, float velocity) {
+        apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
+    }
+
+    /**
+     * Applies the interpolator and length to the animator, such that the fling animation is
+     * consistent with the finger motion.
+     *
+     * @param animator  the animator to apply
+     * @param currValue the current value
+     * @param endValue  the end value of the animator
+     * @param velocity  the current velocity of the motion
+     */
     public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
             float velocity) {
         apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
@@ -152,6 +166,24 @@
      * @param maxDistance the maximum distance for this interaction; the maximum animation length
      *                    gets multiplied by the ratio between the actual distance and this value
      */
+    public void apply(androidx.core.animation.Animator animator,
+            float currValue, float endValue, float velocity, float maxDistance) {
+        AnimatorProperties properties = getProperties(currValue, endValue, velocity, maxDistance);
+        animator.setDuration(properties.mDuration);
+        animator.setInterpolator(properties.getInterpolator());
+    }
+
+    /**
+     * Applies the interpolator and length to the animator, such that the fling animation is
+     * consistent with the finger motion.
+     *
+     * @param animator    the animator to apply
+     * @param currValue   the current value
+     * @param endValue    the end value of the animator
+     * @param velocity    the current velocity of the motion
+     * @param maxDistance the maximum distance for this interaction; the maximum animation length
+     *                    gets multiplied by the ratio between the actual distance and this value
+     */
     public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
             float velocity, float maxDistance) {
         AnimatorProperties properties = getProperties(currValue, endValue, velocity,
@@ -367,6 +399,11 @@
     private static class AnimatorProperties {
         Interpolator mInterpolator;
         long mDuration;
+
+        /** Get an AndroidX interpolator wrapper of the current mInterpolator */
+        public androidx.core.animation.Interpolator getInterpolator() {
+            return mInterpolator::getInterpolation;
+        }
     }
 
     /** Builder for {@link #FlingAnimationUtils}. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index edefe9e..74a243d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -371,7 +371,15 @@
 
         @Override
         public void onBackCancelled() {
-            mProgressAnimator.onBackCancelled(CrossActivityAnimation.this::finishAnimation);
+            mProgressAnimator.onBackCancelled(() -> {
+                // mProgressAnimator can reach finish stage earlier than mLeavingProgressSpring,
+                // and if we release all animation leash first, the leavingProgressSpring won't
+                // able to update the animation anymore, which cause flicker.
+                // Here should force update the closing animation target to the final stage before
+                // release it.
+                setLeavingProgress(0);
+                finishAnimation();
+            });
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 102f2cb..7e09c98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -422,6 +422,7 @@
         }
         if (mBubbleBarExpandedView != null) {
             mBubbleBarExpandedView.cleanUpExpandedState();
+            mBubbleBarExpandedView = null;
         }
         if (mIntent != null) {
             mIntent.unregisterCancelListener(mIntentCancelListener);
@@ -549,10 +550,10 @@
     /**
      * Set visibility of bubble in the expanded state.
      *
-     * @param visibility {@code true} if the expanded bubble should be visible on the screen.
-     *
-     * Note that this contents visibility doesn't affect visibility at {@link android.view.View},
+     * <p>Note that this contents visibility doesn't affect visibility at {@link android.view.View},
      * and setting {@code false} actually means rendering the expanded view in transparent.
+     *
+     * @param visibility {@code true} if the expanded bubble should be visible on the screen.
      */
     @Override
     public void setTaskViewVisibility(boolean visibility) {
@@ -855,7 +856,8 @@
         return mIsAppBubble;
     }
 
-    Intent getSettingsIntent(final Context context) {
+    /** Creates open app settings intent */
+    public Intent getSettingsIntent(final Context context) {
         final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
         intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
         final int uid = getUid(context);
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 0fdfbb8..a70cf00 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
@@ -25,7 +25,6 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
-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.Bubbles.DISMISS_BLOCKED;
@@ -37,6 +36,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.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
 
 import android.annotation.BinderThread;
@@ -64,7 +64,6 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.service.notification.NotificationListenerService;
@@ -86,12 +85,14 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.launcher3.icons.BubbleIconFactory;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
+import com.android.wm.shell.bubbles.properties.BubbleProperties;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -143,16 +144,6 @@
     private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
     private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
 
-    // TODO(b/256873975) Should use proper flag when available to shell/launcher
-    /**
-     * Whether bubbles are showing in the bubble bar from launcher. This is only available
-     * on large screens and {@link BubbleController#isShowingAsBubbleBar()} should be used
-     * to check all conditions that indicate if the bubble bar is in use.
-     */
-    private static final boolean BUBBLE_BAR_ENABLED =
-            SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
-
-
     /**
      * Common interface to send updates to bubble views.
      */
@@ -195,6 +186,7 @@
     private final ShellController mShellController;
     private final ShellCommandHandler mShellCommandHandler;
     private final IWindowManager mWmService;
+    private final BubbleProperties mBubbleProperties;
 
     // Used to post to main UI thread
     private final ShellExecutor mMainExecutor;
@@ -291,7 +283,8 @@
             @ShellBackgroundThread ShellExecutor bgExecutor,
             TaskViewTransitions taskViewTransitions,
             SyncTransactionQueue syncQueue,
-            IWindowManager wmService) {
+            IWindowManager wmService,
+            BubbleProperties bubbleProperties) {
         mContext = context;
         mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
@@ -328,6 +321,7 @@
         mDragAndDropController = dragAndDropController;
         mSyncQueue = syncQueue;
         mWmService = wmService;
+        mBubbleProperties = bubbleProperties;
         shellInit.addInitCallback(this::onInit, this);
     }
 
@@ -518,11 +512,14 @@
     /**
      * Sets a listener to be notified of bubble updates. This is used by launcher so that
      * it may render bubbles in itself. Only one listener is supported.
+     *
+     * <p>If bubble bar is supported, bubble views will be updated to switch to bar mode.
      */
     public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) {
-        if (isShowingAsBubbleBar()) {
-            // Only set the listener if bubble bar is showing.
+        if (canShowAsBubbleBar() && listener != null) {
+            // Only set the listener if we can show the bubble bar.
             mBubbleStateListener = listener;
+            setUpBubbleViewsForMode();
             sendInitialListenerUpdate();
         } else {
             mBubbleStateListener = null;
@@ -531,9 +528,15 @@
 
     /**
      * Unregisters the {@link Bubbles.BubbleStateListener}.
+     *
+     * <p>If there's an existing listener, then we're switching back to stack mode and bubble views
+     * will be updated accordingly.
      */
     public void unregisterBubbleStateListener() {
-        mBubbleStateListener = null;
+        if (mBubbleStateListener != null) {
+            mBubbleStateListener = null;
+            setUpBubbleViewsForMode();
+        }
     }
 
     /**
@@ -645,8 +648,12 @@
 
     /** Whether bubbles are showing in the bubble bar. */
     public boolean isShowingAsBubbleBar() {
-        // TODO(b/269670598): should also check that we're in gesture nav
-        return BUBBLE_BAR_ENABLED && mBubblePositioner.isLargeScreen();
+        return canShowAsBubbleBar() && mBubbleStateListener != null;
+    }
+
+    /** Whether the current configuration supports showing as bubble bar. */
+    private boolean canShowAsBubbleBar() {
+        return mBubbleProperties.isBubbleBarEnabled() && mBubblePositioner.isLargeScreen();
     }
 
     /** Whether this userId belongs to the current user. */
@@ -719,6 +726,7 @@
             // TODO(b/273312602): consider foldables where we do need a stack view when folded
             if (mLayerView == null) {
                 mLayerView = new BubbleBarLayerView(mContext, this);
+                mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
             }
         } else {
             if (mStackView == null) {
@@ -773,12 +781,12 @@
         try {
             mAddedToWindowManager = true;
             registerBroadcastReceiver();
-            mBubbleData.getOverflow().initialize(this);
+            mBubbleData.getOverflow().initialize(this, isShowingAsBubbleBar());
             // (TODO: b/273314541) some duplication in the inset listener
             if (isShowingAsBubbleBar()) {
                 mWindowManager.addView(mLayerView, mWmLayoutParams);
                 mLayerView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
-                    if (!windowInsets.equals(mWindowInsets)) {
+                    if (!windowInsets.equals(mWindowInsets) && mLayerView != null) {
                         mWindowInsets = windowInsets;
                         mBubblePositioner.update();
                         mLayerView.onDisplaySizeChanged();
@@ -788,7 +796,7 @@
             } else {
                 mWindowManager.addView(mStackView, mWmLayoutParams);
                 mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> {
-                    if (!windowInsets.equals(mWindowInsets)) {
+                    if (!windowInsets.equals(mWindowInsets) && mStackView != null) {
                         mWindowInsets = windowInsets;
                         mBubblePositioner.update();
                         mStackView.onDisplaySizeChanged();
@@ -810,13 +818,17 @@
      * @param interceptBack whether back should be intercepted or not.
      */
     void updateWindowFlagsForBackpress(boolean interceptBack) {
-        if (mStackView != null && mAddedToWindowManager) {
+        if (mAddedToWindowManager) {
             mWmLayoutParams.flags = interceptBack
                     ? 0
                     : WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                             | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
             mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
-            mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
+            if (mStackView != null) {
+                mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
+            } else if (mLayerView != null) {
+                mWindowManager.updateViewLayout(mLayerView, mWmLayoutParams);
+            }
         }
     }
 
@@ -993,9 +1005,7 @@
     }
 
     private void onNotificationPanelExpandedChanged(boolean expanded) {
-        if (DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "onNotificationPanelExpandedChanged: expanded=" + expanded);
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "onNotificationPanelExpandedChanged: expanded=%b", expanded);
         if (mStackView != null && mStackView.isExpanded()) {
             if (expanded) {
                 mStackView.stopMonitoringSwipeUpGesture();
@@ -1042,6 +1052,20 @@
         mBubbleData.setExpanded(false /* expanded */);
     }
 
+    /**
+     * Update expanded state when a single bubble is dragged in Launcher.
+     * Will be called only when bubble bar is expanded.
+     * @param bubbleKey key of the bubble to collapse/expand
+     * @param isBeingDragged whether the bubble is being dragged
+     */
+    public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
+        if (mBubbleData.getSelectedBubble() != null
+                && mBubbleData.getSelectedBubble().getKey().equals(bubbleKey)) {
+            // Should collapse/expand only if equals to selected bubble.
+            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ !isBeingDragged);
+        }
+    }
+
     @VisibleForTesting
     public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
         boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key)
@@ -1065,9 +1089,19 @@
      * Expands and selects the provided bubble as long as it already exists in the stack or the
      * overflow.
      *
-     * This is used by external callers (launcher).
+     * <p>This is used by external callers (launcher).
      */
-    public void expandStackAndSelectBubbleFromLauncher(String key) {
+    @VisibleForTesting
+    public void expandStackAndSelectBubbleFromLauncher(String key, int bubbleBarOffsetX,
+            int bubbleBarOffsetY) {
+        mBubblePositioner.setBubbleBarPosition(bubbleBarOffsetX, bubbleBarOffsetY);
+
+        if (BubbleOverflow.KEY.equals(key)) {
+            mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
+            mLayerView.showExpandedView(mBubbleData.getOverflow());
+            return;
+        }
+
         Bubble b = mBubbleData.getAnyBubbleWithkey(key);
         if (b == null) {
             return;
@@ -1220,6 +1254,13 @@
     }
 
     /**
+     * Dismiss bubble if it exists and remove it from the stack
+     */
+    public void dismissBubble(Bubble bubble, @Bubbles.DismissReason int reason) {
+        mBubbleData.dismissBubbleWithKey(bubble.getKey(), reason);
+    }
+
+    /**
      * Performs a screenshot that may exclude the bubble layer, if one is present. The screenshot
      * can be access via the supplied {@link SynchronousScreenCaptureListener#getBuffer()}
      * asynchronously.
@@ -1259,7 +1300,9 @@
             return;
         }
         mOverflowDataLoadNeeded = false;
-        mDataRepository.loadBubbles(mCurrentUserId, (bubbles) -> {
+        List<UserInfo> users = mUserManager.getAliveUsers();
+        List<Integer> userIds = users.stream().map(userInfo -> userInfo.id).toList();
+        mDataRepository.loadBubbles(mCurrentUserId, userIds, (bubbles) -> {
             bubbles.forEach(bubble -> {
                 if (mBubbleData.hasAnyBubbleWithKey(bubble.getKey())) {
                     // if the bubble is already active, there's no need to push it to overflow
@@ -1278,6 +1321,50 @@
         });
     }
 
+    void setUpBubbleViewsForMode() {
+        mBubbleViewCallback = isShowingAsBubbleBar()
+                ? mBubbleBarViewCallback
+                : mBubbleStackViewCallback;
+
+        // reset the overflow so that it can be re-added later if needed.
+        if (mStackView != null) {
+            mStackView.resetOverflowView();
+            mStackView.removeAllViews();
+        }
+        // cleanup existing bubble views so they can be recreated later if needed.
+        mBubbleData.getBubbles().forEach(Bubble::cleanupViews);
+
+        // remove the current bubble container from window manager, null it out, and create a new
+        // container based on the current mode.
+        removeFromWindowManagerMaybe();
+        mLayerView = null;
+        mStackView = null;
+        ensureBubbleViewsAndWindowCreated();
+
+        // inflate bubble views
+        BubbleViewInfoTask.Callback callback = null;
+        if (!isShowingAsBubbleBar()) {
+            callback = b -> {
+                if (mStackView != null) {
+                    mStackView.addBubble(b);
+                    mStackView.setSelectedBubble(b);
+                } else {
+                    Log.w(TAG, "Tried to add a bubble to the stack but the stack is null");
+                }
+            };
+        }
+        for (int i = mBubbleData.getBubbles().size() - 1; i >= 0; i--) {
+            Bubble bubble = mBubbleData.getBubbles().get(i);
+            bubble.inflate(callback,
+                    mContext,
+                    this,
+                    mStackView,
+                    mLayerView,
+                    mBubbleIconFactory,
+                    false /* skipInflation */);
+        }
+    }
+
     /**
      * Adds or updates a bubble associated with the provided notification entry.
      *
@@ -1364,6 +1451,17 @@
         }
     }
 
+    /**
+     * Removes all the bubbles.
+     * <p>
+     * Must be called from the main thread.
+     */
+    @VisibleForTesting
+    @MainThread
+    public void removeAllBubbles(@Bubbles.DismissReason int reason) {
+        mBubbleData.dismissAll(reason);
+    }
+
     private void onEntryAdded(BubbleEntry entry) {
         if (canLaunchInTaskView(mContext, entry)) {
             updateBubble(entry);
@@ -1738,7 +1836,7 @@
             // Update the cached state for queries from SysUI
             mImpl.mCachedState.update(update);
 
-            if (isShowingAsBubbleBar() && mBubbleStateListener != null) {
+            if (isShowingAsBubbleBar()) {
                 BubbleBarUpdate bubbleBarUpdate = update.toBubbleBarUpdate();
                 // Some updates aren't relevant to the bubble bar so check first.
                 if (bubbleBarUpdate.anythingChanged()) {
@@ -1846,7 +1944,7 @@
             if (mStackView != null) {
                 mStackView.setVisibility(VISIBLE);
             }
-            if (mLayerView != null && isStackExpanded()) {
+            if (mLayerView != null) {
                 mLayerView.setVisibility(VISIBLE);
             }
         }
@@ -1860,10 +1958,17 @@
     }
 
     @VisibleForTesting
+    @Nullable
     public BubbleStackView getStackView() {
         return mStackView;
     }
 
+    @VisibleForTesting
+    @Nullable
+    public BubbleBarLayerView getLayerView() {
+        return mLayerView;
+    }
+
     /**
      * Check if notification panel is in an expanded state.
      * Makes a call to System UI process and delivers the result via {@code callback} on the
@@ -2002,27 +2107,30 @@
 
         @Override
         public void registerBubbleListener(IBubblesListener listener) {
-            mMainExecutor.execute(() -> {
-                mListener.register(listener);
-            });
+            mMainExecutor.execute(() -> mListener.register(listener));
         }
 
         @Override
         public void unregisterBubbleListener(IBubblesListener listener) {
-            mMainExecutor.execute(() -> mListener.unregister());
+            mMainExecutor.execute(mListener::unregister);
         }
 
         @Override
-        public void showBubble(String key, boolean onLauncherHome) {
-            mMainExecutor.execute(() -> {
-                mBubblePositioner.setShowingInBubbleBar(onLauncherHome);
-                mController.expandStackAndSelectBubbleFromLauncher(key);
-            });
+        public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
+            mMainExecutor.execute(
+                    () -> mController.expandStackAndSelectBubbleFromLauncher(
+                            key, bubbleBarOffsetX, bubbleBarOffsetY));
         }
 
         @Override
-        public void removeBubble(String key, int reason) {
-            // TODO (b/271466616) allow removals from launcher
+        public void removeBubble(String key) {
+            mMainExecutor.execute(
+                    () -> mController.removeBubble(key, Bubbles.DISMISS_USER_GESTURE));
+        }
+
+        @Override
+        public void removeAllBubbles() {
+            mMainExecutor.execute(() -> mController.removeAllBubbles(Bubbles.DISMISS_USER_GESTURE));
         }
 
         @Override
@@ -2031,8 +2139,8 @@
         }
 
         @Override
-        public void onTaskbarStateChanged(int newState) {
-            // TODO (b/269670598)
+        public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
+            mMainExecutor.execute(() -> mController.onBubbleDrag(bubbleKey, isBeingDragged));
         }
     }
 
@@ -2144,7 +2252,7 @@
                     pw.println("   suppressing: " + key);
                 }
 
-                pw.print("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values());
+                pw.println("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values());
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
index e37c785..df12999 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
@@ -17,7 +17,6 @@
 
 import android.annotation.SuppressLint
 import android.annotation.UserIdInt
-import android.content.Context
 import android.content.pm.LauncherApps
 import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED
 import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC
@@ -25,6 +24,8 @@
 import android.content.pm.UserInfo
 import android.os.UserHandle
 import android.util.Log
+import android.util.SparseArray
+import com.android.internal.annotations.VisibleForTesting
 import com.android.wm.shell.bubbles.Bubbles.BubbleMetadataFlagListener
 import com.android.wm.shell.bubbles.storage.BubbleEntity
 import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
@@ -33,19 +34,19 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.yield
 
-internal class BubbleDataRepository(
-    context: Context,
+class BubbleDataRepository(
     private val launcherApps: LauncherApps,
-    private val mainExecutor: ShellExecutor
+    private val mainExecutor: ShellExecutor,
+    private val persistentRepository: BubblePersistentRepository,
 ) {
     private val volatileRepository = BubbleVolatileRepository(launcherApps)
-    private val persistentRepository = BubblePersistentRepository(context)
 
-    private val ioScope = CoroutineScope(Dispatchers.IO)
+    private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
     private var job: Job? = null
 
     // For use in Bubble construction.
@@ -98,6 +99,44 @@
         if (volatileRepository.sanitizeBubbles(userIds)) persistToDisk()
     }
 
+    /**
+     * Removes all entities that don't have a user in the activeUsers list, if any entities were
+     * removed it persists the new list to disk.
+     */
+    @VisibleForTesting
+    fun filterForActiveUsersAndPersist(
+            activeUsers: List<Int>,
+            entitiesByUser: SparseArray<List<BubbleEntity>>
+    ): SparseArray<List<BubbleEntity>> {
+        val validEntitiesByUser = SparseArray<List<BubbleEntity>>()
+        var entitiesChanged = false
+        for (i in 0 until entitiesByUser.size()) {
+            val parentUserId = entitiesByUser.keyAt(i)
+            if (activeUsers.contains(parentUserId)) {
+                val validEntities = mutableListOf<BubbleEntity>()
+                // Check if each of the bubbles in the top-level user still has a valid user
+                // as it could belong to a profile and have a different id from the parent.
+                for (entity in entitiesByUser.get(parentUserId)) {
+                    if (activeUsers.contains(entity.userId)) {
+                        validEntities.add(entity)
+                    } else {
+                        entitiesChanged = true
+                    }
+                }
+                if (validEntities.isNotEmpty()) {
+                    validEntitiesByUser.put(parentUserId, validEntities)
+                }
+            } else {
+                entitiesChanged = true
+            }
+        }
+        if (entitiesChanged) {
+            persistToDisk(validEntitiesByUser)
+            return validEntitiesByUser
+        }
+        return entitiesByUser
+    }
+
     private fun transform(bubbles: List<Bubble>): List<BubbleEntity> {
         return bubbles.mapNotNull { b ->
             BubbleEntity(
@@ -129,15 +168,18 @@
      * Job C resumes and reaches yield() and is then cancelled
      * Job D resumes and performs another blocking I/O
      */
-    private fun persistToDisk() {
+    @VisibleForTesting
+    fun persistToDisk(
+            entitiesByUser: SparseArray<List<BubbleEntity>> = volatileRepository.bubbles
+    ) {
         val prev = job
-        job = ioScope.launch {
+        job = coroutineScope.launch {
             // if there was an ongoing disk I/O operation, they can be cancelled
             prev?.cancelAndJoin()
             // check for cancellation before disk I/O
             yield()
             // save to disk
-            persistentRepository.persistsToDisk(volatileRepository.bubbles)
+            persistentRepository.persistsToDisk(entitiesByUser)
         }
     }
 
@@ -148,7 +190,11 @@
      *           bubbles.
      */
     @SuppressLint("WrongConstant")
-    fun loadBubbles(userId: Int, cb: (List<Bubble>) -> Unit) = ioScope.launch {
+    fun loadBubbles(
+            userId: Int,
+            currentUsers: List<Int>,
+            cb: (List<Bubble>) -> Unit
+    ) = coroutineScope.launch {
         /**
          * Load BubbleEntity from disk.
          * e.g.
@@ -159,7 +205,12 @@
          * ]
          */
         val entitiesByUser = persistentRepository.readFromDisk()
-        val entities = entitiesByUser.get(userId) ?: return@launch
+
+        // Before doing anything, validate that the entities we loaded are valid & have an existing
+        // user.
+        val validEntitiesByUser = filterForActiveUsersAndPersist(currentUsers, entitiesByUser)
+
+        val entities = validEntitiesByUser.get(userId) ?: return@launch
         volatileRepository.addBubbles(userId, entities)
         /**
          * Extract userId/packageName from these entities.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index dce6b56..250e010 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -47,7 +47,6 @@
     static final boolean DEBUG_USER_EDUCATION = false;
     static final boolean DEBUG_POSITIONER = false;
     public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
-    static final boolean DEBUG_BUBBLE_GESTURE = false;
     public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;
 
     private static final boolean FORCE_SHOW_USER_EDUCATION = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index e1a3f3a..e698601 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -312,9 +312,13 @@
                         + " bubble=" + getBubbleKey());
             }
             if (mBubble != null) {
-                // Must post because this is called from a binder thread.
-                post(() -> mController.removeBubble(
-                        mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED));
+                mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+            }
+            if (mTaskView != null) {
+                // Release the surface
+                mTaskView.release();
+                removeView(mTaskView);
+                mTaskView = null;
             }
         }
 
@@ -948,9 +952,9 @@
             mTaskView.onLocationChanged();
         }
         if (mIsOverflow) {
-            post(() -> {
-                mOverflowView.show();
-            });
+            // post this to the looper so that the view has a chance to be laid out before it can
+            // calculate row and column sizes correctly.
+            post(() -> mOverflowView.show());
         }
     }
 
@@ -1058,8 +1062,10 @@
     }
 
     /**
-     * Cleans up anything related to the task and {@code TaskView}. If this view should be reused
-     * after this method is called, then
+     * Cleans up anything related to the task. The TaskView itself is released after the task
+     * has been removed.
+     *
+     * If this view should be reused after this method is called, then
      * {@link #initialize(BubbleController, BubbleStackView, boolean)} must be invoked first.
      */
     public void cleanUpExpandedState() {
@@ -1081,10 +1087,7 @@
             }
         }
         if (mTaskView != null) {
-            // Release the surface & other task view related things
-            mTaskView.release();
-            removeView(mTaskView);
-            mTaskView = null;
+            mTaskView.setVisibility(GONE);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index df7f5b4..df19757 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -44,6 +44,7 @@
 
     private val inflater: LayoutInflater = LayoutInflater.from(context)
     private var expandedView: BubbleExpandedView?
+    private var bubbleBarExpandedView: BubbleBarExpandedView? = null
     private var overflowBtn: BadgedImageView?
 
     init {
@@ -53,19 +54,26 @@
     }
 
     /** Call before use and again if cleanUpExpandedState was called. */
-    fun initialize(controller: BubbleController) {
-        createExpandedView()
-        getExpandedView()?.initialize(controller, controller.stackView, true /* isOverflow */)
+    fun initialize(controller: BubbleController, forBubbleBar: Boolean) {
+        if (forBubbleBar) {
+            createBubbleBarExpandedView().initialize(controller, true /* isOverflow */)
+        } else {
+            createExpandedView()
+                .initialize(controller, controller.stackView, true /* isOverflow */)
+        }
     }
 
     fun cleanUpExpandedState() {
         expandedView?.cleanUpExpandedState()
         expandedView = null
+        bubbleBarExpandedView?.cleanUpExpandedState()
+        bubbleBarExpandedView = null
     }
 
     fun update() {
         updateResources()
         getExpandedView()?.applyThemeAttrs()
+        getBubbleBarExpandedView()?.applyThemeAttrs()
         // Apply inset and new style to fresh icon drawable.
         getIconView()?.setIconImageResource(R.drawable.bubble_ic_overflow_button)
         updateBtnTheme()
@@ -151,26 +159,39 @@
         overflowBtn?.updateDotVisibility(true /* animate */)
     }
 
-    fun createExpandedView(): BubbleExpandedView? {
-        expandedView =
+    /** Creates the expanded view for bubbles showing in the stack view. */
+    private fun createExpandedView(): BubbleExpandedView {
+        val view =
             inflater.inflate(
                 R.layout.bubble_expanded_view,
                 null /* root */,
                 false /* attachToRoot */
             ) as BubbleExpandedView
-        expandedView?.applyThemeAttrs()
+        view.applyThemeAttrs()
+        expandedView = view
         updateResources()
-        return expandedView
+        return view
     }
 
     override fun getExpandedView(): BubbleExpandedView? {
         return expandedView
     }
 
-    override fun getBubbleBarExpandedView(): BubbleBarExpandedView? {
-        return null
+    /** Creates the expanded view for bubbles showing in the bubble bar. */
+    private fun createBubbleBarExpandedView(): BubbleBarExpandedView {
+        val view =
+            inflater.inflate(
+                R.layout.bubble_bar_expanded_view,
+                null, /* root */
+                false /* attachToRoot*/
+            ) as BubbleBarExpandedView
+        view.applyThemeAttrs()
+        bubbleBarExpandedView = view
+        return view
     }
 
+    override fun getBubbleBarExpandedView(): BubbleBarExpandedView? = bubbleBarExpandedView
+
     override fun getDotColor(): Int {
         return dotColor
     }
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 d101b0c..2c10065 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
@@ -22,6 +22,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Insets;
+import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -102,10 +103,7 @@
     private int[] mPaddings = new int[4];
 
     private boolean mShowingInBubbleBar;
-    private boolean mBubblesOnHome;
-    private int mBubbleBarSize;
-    private int mBubbleBarHomeAdjustment;
-    private final PointF mBubbleBarPosition = new PointF();
+    private final Point mBubbleBarPosition = new Point();
 
     public BubblePositioner(Context context, WindowManager windowManager) {
         mContext = context;
@@ -166,11 +164,9 @@
         mSpacingBetweenBubbles = res.getDimensionPixelSize(R.dimen.bubble_spacing);
         mDefaultMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
         mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
-        mBubbleBarHomeAdjustment = mExpandedViewPadding / 2;
         mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mBubbleOffscreenAmount = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
         mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
-        mBubbleBarSize = res.getDimensionPixelSize(R.dimen.bubblebar_size);
 
         if (mShowingInBubbleBar) {
             mExpandedViewLargeScreenWidth = isLandscape()
@@ -665,14 +661,26 @@
         final boolean startOnLeft =
                 mContext.getResources().getConfiguration().getLayoutDirection()
                         != LAYOUT_DIRECTION_RTL;
-        final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset(
-                R.dimen.bubble_stack_starting_offset_y);
-        // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge
-        return new BubbleStackView.RelativeStackPosition(
-                startOnLeft,
-                startingVerticalOffset / mPositionRect.height())
-                .getAbsolutePositionInRegion(getAllowableStackPositionRegion(
-                        1 /* default starts with 1 bubble */));
+        final RectF allowableStackPositionRegion = getAllowableStackPositionRegion(
+                1 /* default starts with 1 bubble */);
+        if (isLargeScreen()) {
+            // We want the stack to be visually centered on the edge, so we need to base it
+            // of a rect that includes insets.
+            final float desiredY = mScreenRect.height() / 2f - (mBubbleSize / 2f);
+            final float offset = desiredY / mScreenRect.height();
+            return new BubbleStackView.RelativeStackPosition(
+                    startOnLeft,
+                    offset)
+                    .getAbsolutePositionInRegion(allowableStackPositionRegion);
+        } else {
+            final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset(
+                    R.dimen.bubble_stack_starting_offset_y);
+            // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge
+            return new BubbleStackView.RelativeStackPosition(
+                    startOnLeft,
+                    startingVerticalOffset / mPositionRect.height())
+                    .getAbsolutePositionInRegion(allowableStackPositionRegion);
+        }
     }
 
     /**
@@ -723,27 +731,36 @@
     }
 
     /**
-     * Sets whether bubbles are showing on launcher home, in which case positions are different.
+     * Sets the position of the bubble bar in screen coordinates.
+     *
+     * @param offsetX the offset of the bubble bar from the edge of the screen on the X axis
+     * @param offsetY the offset of the bubble bar from the edge of the screen on the Y axis
      */
-    public void setBubblesOnHome(boolean bubblesOnHome) {
-        mBubblesOnHome = bubblesOnHome;
+    public void setBubbleBarPosition(int offsetX, int offsetY) {
+        mBubbleBarPosition.set(
+                getAvailableRect().width() - offsetX,
+                getAvailableRect().height() + mInsets.top + mInsets.bottom - offsetY);
     }
 
     /**
      * How wide the expanded view should be when showing from the bubble bar.
      */
-    public int getExpandedViewWidthForBubbleBar() {
-        return mExpandedViewLargeScreenWidth;
+    public int getExpandedViewWidthForBubbleBar(boolean isOverflow) {
+        return isOverflow ? mOverflowWidth : mExpandedViewLargeScreenWidth;
     }
 
     /**
      * How tall the expanded view should be when showing from the bubble bar.
      */
-    public int getExpandedViewHeightForBubbleBar() {
-        return getAvailableRect().height()
-                - mBubbleBarSize
-                - mExpandedViewPadding * 2
-                - getBubbleBarHomeAdjustment();
+    public int getExpandedViewHeightForBubbleBar(boolean isOverflow) {
+        return isOverflow
+                ? mOverflowHeight
+                : getExpandedViewBottomForBubbleBar() - mInsets.top - mExpandedViewPadding;
+    }
+
+    /** The bottom position of the expanded view when showing above the bubble bar. */
+    public int getExpandedViewBottomForBubbleBar() {
+        return mBubbleBarPosition.y - mExpandedViewPadding;
     }
 
     /**
@@ -756,19 +773,7 @@
     /**
      * Returns the on screen co-ordinates of the bubble bar.
      */
-    public PointF getBubbleBarPosition() {
-        mBubbleBarPosition.set(getAvailableRect().width() - mBubbleBarSize,
-                getAvailableRect().height() - mBubbleBarSize
-                        - mExpandedViewPadding - getBubbleBarHomeAdjustment());
+    public Point getBubbleBarPosition() {
         return mBubbleBarPosition;
     }
-
-    /**
-     * When bubbles are shown on launcher home, there's an extra bit of padding that needs to
-     * be applied between the expanded view and the bubble bar. This returns the adjustment value
-     * if bubbles are showing on home.
-     */
-    private int getBubbleBarHomeAdjustment() {
-        return mBubblesOnHome ? mBubbleBarHomeAdjustment : 0;
-    }
 }
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 68fea41..da5974f 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
@@ -21,11 +21,11 @@
 
 import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
 import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
 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.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -75,6 +75,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
@@ -88,6 +89,8 @@
 import com.android.wm.shell.bubbles.animation.StackAnimationController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.common.bubbles.RelativeTouchListener;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
 import java.io.PrintWriter;
@@ -106,8 +109,10 @@
 public class BubbleStackView extends FrameLayout
         implements ViewTreeObserver.OnComputeInternalInsetsListener {
 
+    // LINT.IfChange
     public static final boolean ENABLE_FLING_TO_DISMISS_BUBBLE =
             SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_bubble", true);
+    // LINT.ThenChange(com/android/launcher3/taskbar/bubbles/BubbleDismissController.java)
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
 
@@ -307,6 +312,7 @@
 
         String bubblesOnScreen = BubbleDebugConfig.formatBubblesString(
                 getBubblesOnScreen(), getExpandedBubble());
+        pw.print("  stack visibility :       "); pw.println(getVisibility());
         pw.print("  bubbles on screen:       "); pw.println(bubblesOnScreen);
         pw.print("  gestureInProgress:       "); pw.println(mIsGestureInProgress);
         pw.print("  showingDismiss:          "); pw.println(mDismissView.isShowing());
@@ -968,6 +974,8 @@
         mBubbleContainer.bringToFront();
 
         mBubbleOverflow = mBubbleData.getOverflow();
+
+        resetOverflowView();
         mBubbleContainer.addView(mBubbleOverflow.getIconView(),
                 mBubbleContainer.getChildCount() /* index */,
                 new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
@@ -1179,6 +1187,7 @@
             removeView(mDismissView);
         }
         mDismissView = new DismissView(getContext());
+        DismissViewUtils.setup(mDismissView);
         int elevation = getResources().getDimensionPixelSize(R.dimen.bubble_elevation);
 
         addView(mDismissView);
@@ -1494,9 +1503,6 @@
         getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
         getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater);
         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
-        if (mBubbleOverflow != null) {
-            mBubbleOverflow.cleanUpExpandedState();
-        }
     }
 
     @Override
@@ -2019,9 +2025,7 @@
      * Monitor for swipe up gesture that is used to collapse expanded view
      */
     void startMonitoringSwipeUpGesture() {
-        if (DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "startMonitoringSwipeUpGesture");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "startMonitoringSwipeUpGesture");
         stopMonitoringSwipeUpGestureInternal();
 
         if (isGestureNavEnabled()) {
@@ -2041,9 +2045,7 @@
      * Stop monitoring for swipe up gesture
      */
     void stopMonitoringSwipeUpGesture() {
-        if (DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "stopMonitoringSwipeUpGesture");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "stopMonitoringSwipeUpGesture");
         stopMonitoringSwipeUpGestureInternal();
     }
 
@@ -3411,6 +3413,19 @@
     }
 
     /**
+     * Removes the overflow view from the stack. This allows for re-adding it later to a new stack.
+     */
+    void resetOverflowView() {
+        BadgedImageView overflowIcon = mBubbleOverflow.getIconView();
+        if (overflowIcon != null) {
+            PhysicsAnimationLayout parent = (PhysicsAnimationLayout) overflowIcon.getParent();
+            if (parent != null) {
+                parent.removeViewNoAnimation(overflowIcon);
+            }
+        }
+    }
+
+    /**
      * Holds some commonly queried information about the stack.
      */
     public static class StackViewState {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 7a58159..da4a989 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -15,6 +15,7 @@
  */
 package com.android.wm.shell.bubbles;
 
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -110,6 +111,9 @@
                 try {
                     options.setTaskAlwaysOnTop(true);
                     options.setLaunchedFromBubble(true);
+                    options.setPendingIntentBackgroundActivityStartMode(
+                            MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                    options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
 
                     Intent fillInIntent = new Intent();
                     // Apply flags to make behaviour match documentLaunchMode=always.
@@ -117,11 +121,19 @@
                     fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
 
                     if (mBubble.isAppBubble()) {
-                        PendingIntent pi = PendingIntent.getActivity(mContext, 0,
-                                mBubble.getAppBubbleIntent(),
-                                PendingIntent.FLAG_MUTABLE,
-                                null);
-                        mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
+                        Context context =
+                                mContext.createContextAsUser(
+                                        mBubble.getUser(), Context.CONTEXT_RESTRICTED);
+                        PendingIntent pi = PendingIntent.getActivity(
+                                context,
+                                /* requestCode= */ 0,
+                                mBubble.getAppBubbleIntent()
+                                        .addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+                                        .addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
+                                PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
+                                /* options= */ null);
+                        mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
+                                launchBounds);
                     } else if (mBubble.hasMetadataShortcutId()) {
                         options.setApplyActivityFlagsForBubbles(true);
                         mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 8ab9841..66e6930 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -104,7 +104,11 @@
 
     @Override
     protected BubbleViewInfo doInBackground(Void... voids) {
-        if (mController.get().isShowingAsBubbleBar()) {
+        if (!verifyState()) {
+            // If we're in an inconsistent state, then switched modes and should just bail now.
+            return null;
+        }
+        if (mLayerView.get() != null) {
             return BubbleViewInfo.populateForBubbleBar(mContext.get(), mController.get(),
                     mLayerView.get(), mIconFactory, mBubble, mSkipInflation);
         } else {
@@ -118,7 +122,11 @@
         if (isCancelled() || viewInfo == null) {
             return;
         }
+
         mMainExecutor.execute(() -> {
+            if (!verifyState()) {
+                return;
+            }
             mBubble.setViewInfo(viewInfo);
             if (mCallback != null) {
                 mCallback.onBubbleViewsReady(mBubble);
@@ -126,6 +134,14 @@
         });
     }
 
+    private boolean verifyState() {
+        if (mController.get().isShowingAsBubbleBar()) {
+            return mLayerView.get() != null;
+        } else {
+            return mStackView.get() != null;
+        }
+    }
+
     /**
      * Info necessary to render a bubble.
      */
@@ -160,39 +176,14 @@
                 LayoutInflater inflater = LayoutInflater.from(c);
                 info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
                         R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
-                info.bubbleBarExpandedView.initialize(controller);
+                info.bubbleBarExpandedView.initialize(controller, false /* isOverflow */);
             }
 
-            if (b.getShortcutInfo() != null) {
-                info.shortcutInfo = b.getShortcutInfo();
-            }
-
-            // App name & app icon
-            PackageManager pm = BubbleController.getPackageManagerForUser(c,
-                    b.getUser().getIdentifier());
-            ApplicationInfo appInfo;
-            Drawable badgedIcon;
-            Drawable appIcon;
-            try {
-                appInfo = pm.getApplicationInfo(
-                        b.getPackageName(),
-                        PackageManager.MATCH_UNINSTALLED_PACKAGES
-                                | PackageManager.MATCH_DISABLED_COMPONENTS
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_AWARE);
-                if (appInfo != null) {
-                    info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
-                }
-                appIcon = pm.getApplicationIcon(b.getPackageName());
-                badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser());
-            } catch (PackageManager.NameNotFoundException exception) {
-                // If we can't find package... don't think we should show the bubble.
-                Log.w(TAG, "Unable to find package: " + b.getPackageName());
+            if (!populateCommonInfo(info, c, b, iconFactory)) {
+                // if we failed to update common fields return null
                 return null;
             }
 
-            info.rawBadgeBitmap = iconFactory.getBadgeBitmap(badgedIcon, false).icon;
-
             return info;
         }
 
@@ -215,66 +206,11 @@
                 info.expandedView.initialize(controller, stackView, false /* isOverflow */);
             }
 
-            if (b.getShortcutInfo() != null) {
-                info.shortcutInfo = b.getShortcutInfo();
-            }
-
-            // App name & app icon
-            PackageManager pm = BubbleController.getPackageManagerForUser(c,
-                    b.getUser().getIdentifier());
-            ApplicationInfo appInfo;
-            Drawable badgedIcon;
-            Drawable appIcon;
-            try {
-                appInfo = pm.getApplicationInfo(
-                        b.getPackageName(),
-                        PackageManager.MATCH_UNINSTALLED_PACKAGES
-                                | PackageManager.MATCH_DISABLED_COMPONENTS
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_AWARE);
-                if (appInfo != null) {
-                    info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
-                }
-                appIcon = pm.getApplicationIcon(b.getPackageName());
-                badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser());
-            } catch (PackageManager.NameNotFoundException exception) {
-                // If we can't find package... don't think we should show the bubble.
-                Log.w(TAG, "Unable to find package: " + b.getPackageName());
+            if (!populateCommonInfo(info, c, b, iconFactory)) {
+                // if we failed to update common fields return null
                 return null;
             }
 
-            // Badged bubble image
-            Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo,
-                    b.getIcon());
-            if (bubbleDrawable == null) {
-                // Default to app icon
-                bubbleDrawable = appIcon;
-            }
-
-            BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
-                    b.isImportantConversation());
-            info.badgeBitmap = badgeBitmapInfo.icon;
-            // Raw badge bitmap never includes the important conversation ring
-            info.rawBadgeBitmap = b.isImportantConversation()
-                    ? iconFactory.getBadgeBitmap(badgedIcon, false).icon
-                    : badgeBitmapInfo.icon;
-
-            float[] bubbleBitmapScale = new float[1];
-            info.bubbleBitmap = iconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale);
-
-            // Dot color & placement
-            Path iconPath = PathParser.createPathFromPathData(
-                    c.getResources().getString(com.android.internal.R.string.config_icon_mask));
-            Matrix matrix = new Matrix();
-            float scale = bubbleBitmapScale[0];
-            float radius = DEFAULT_PATH_SIZE / 2f;
-            matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
-                    radius /* pivot y */);
-            iconPath.transform(matrix);
-            info.dotPath = iconPath;
-            info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
-                    Color.WHITE, WHITE_SCRIM_ALPHA);
-
             // Flyout
             info.flyoutMessage = b.getFlyoutMessage();
             if (info.flyoutMessage != null) {
@@ -285,6 +221,75 @@
         }
     }
 
+    /**
+     * Modifies the given {@code info} object and populates common fields in it.
+     *
+     * <p>This method returns {@code true} if the update was successful and {@code false} otherwise.
+     * Callers should assume that the info object is unusable if the update was unsuccessful.
+     */
+    private static boolean populateCommonInfo(
+            BubbleViewInfo info, Context c, Bubble b, BubbleIconFactory iconFactory) {
+        if (b.getShortcutInfo() != null) {
+            info.shortcutInfo = b.getShortcutInfo();
+        }
+
+        // App name & app icon
+        PackageManager pm = BubbleController.getPackageManagerForUser(c,
+                b.getUser().getIdentifier());
+        ApplicationInfo appInfo;
+        Drawable badgedIcon;
+        Drawable appIcon;
+        try {
+            appInfo = pm.getApplicationInfo(
+                    b.getPackageName(),
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES
+                            | PackageManager.MATCH_DISABLED_COMPONENTS
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+            if (appInfo != null) {
+                info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
+            }
+            appIcon = pm.getApplicationIcon(b.getPackageName());
+            badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser());
+        } catch (PackageManager.NameNotFoundException exception) {
+            // If we can't find package... don't think we should show the bubble.
+            Log.w(TAG, "Unable to find package: " + b.getPackageName());
+            return false;
+        }
+
+        // Badged bubble image
+        Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, b.getIcon());
+        if (bubbleDrawable == null) {
+            // Default to app icon
+            bubbleDrawable = appIcon;
+        }
+
+        BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
+                b.isImportantConversation());
+        info.badgeBitmap = badgeBitmapInfo.icon;
+        // Raw badge bitmap never includes the important conversation ring
+        info.rawBadgeBitmap = b.isImportantConversation() // is this needed for bar?
+                ? iconFactory.getBadgeBitmap(badgedIcon, false).icon
+                : badgeBitmapInfo.icon;
+
+        float[] bubbleBitmapScale = new float[1];
+        info.bubbleBitmap = iconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale);
+
+        // Dot color & placement
+        Path iconPath = PathParser.createPathFromPathData(
+                c.getResources().getString(com.android.internal.R.string.config_icon_mask));
+        Matrix matrix = new Matrix();
+        float scale = bubbleBitmapScale[0];
+        float radius = DEFAULT_PATH_SIZE / 2f;
+        matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
+                radius /* pivot y */);
+        iconPath.transform(matrix);
+        info.dotPath = iconPath;
+        info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
+                Color.WHITE, WHITE_SCRIM_ALPHA);
+        return true;
+    }
+
     @Nullable
     static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) {
         Objects.requireNonNull(context);
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 4d329dd..759246e 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
@@ -60,9 +60,11 @@
             DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
             DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
             DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED,
-            DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED})
+            DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK, DISMISS_USER_REMOVED,
+            DISMISS_SWITCH_TO_STACK})
     @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
-    @interface DismissReason {}
+    @interface DismissReason {
+    }
 
     int DISMISS_USER_GESTURE = 1;
     int DISMISS_AGED = 2;
@@ -80,6 +82,7 @@
     int DISMISS_NO_BUBBLE_UP = 14;
     int DISMISS_RELOAD_FROM_DISK = 15;
     int DISMISS_USER_REMOVED = 16;
+    int DISMISS_SWITCH_TO_STACK = 17;
 
     /** Returns a binder that can be passed to an external process to manipulate Bubbles. */
     default IBubbles createExternalInterface() {
@@ -120,8 +123,8 @@
 
     /**
      * This method has different behavior depending on:
-     *    - if an app bubble exists
-     *    - if an app bubble is expanded
+     * - if an app bubble exists
+     * - if an app bubble is expanded
      *
      * If no app bubble exists, this will add and expand a bubble with the provided intent. The
      * intent must be explicit (i.e. include a package name or fully qualified component class name)
@@ -135,13 +138,13 @@
      * the bubble or bubble stack.
      *
      * Some notes:
-     *    - Only one app bubble is supported at a time, regardless of users. Multi-users support is
-     *      tracked in b/273533235.
-     *    - Calling this method with a different intent than the existing app bubble will do nothing
+     * - Only one app bubble is supported at a time, regardless of users. Multi-users support is
+     * tracked in b/273533235.
+     * - Calling this method with a different intent than the existing app bubble will do nothing
      *
      * @param intent the intent to display in the bubble expanded view.
-     * @param user the {@link UserHandle} of the user to start this activity for.
-     * @param icon the {@link Icon} to use for the bubble view.
+     * @param user   the {@link UserHandle} of the user to start this activity for.
+     * @param icon   the {@link Icon} to use for the bubble view.
      */
     void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon);
 
@@ -172,13 +175,12 @@
      * {@link Bubble#setSuppressNotification}.  For the case of suppressed summaries, we also add
      * {@link BubbleData#addSummaryToSuppress}.
      *
-     * @param entry the notification of the BubbleEntry should be removed.
-     * @param children the list of child notification of the BubbleEntry from 1st param entry,
-     *                 this will be null if entry does have no children.
+     * @param entry          the notification of the BubbleEntry should be removed.
+     * @param children       the list of child notification of the BubbleEntry from 1st param entry,
+     *                       this will be null if entry does have no children.
      * @param removeCallback the remove callback for SystemUI side to remove notification, the int
      *                       number should be list position of children list and use -1 for
      *                       removing the parent notification.
-     *
      * @return true if we want to intercept the dismissal of the entry, else false.
      */
     boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children,
@@ -200,9 +202,9 @@
     /**
      * Called when new notification entry updated.
      *
-     * @param entry the {@link BubbleEntry} by the notification.
+     * @param entry          the {@link BubbleEntry} by the notification.
      * @param shouldBubbleUp {@code true} if this notification should bubble up.
-     * @param fromSystem {@code true} if this update is from NotificationManagerService.
+     * @param fromSystem     {@code true} if this update is from NotificationManagerService.
      */
     void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem);
 
@@ -218,7 +220,7 @@
      * filtering and sorting. This is used to dismiss or create bubbles based on changes in
      * permissions on the notification channel or the global setting.
      *
-     * @param rankingMap the updated ranking map from NotificationListenerService
+     * @param rankingMap     the updated ranking map from NotificationListenerService
      * @param entryDataByKey a map of ranking key to bubble entry and whether the entry should
      *                       bubble up
      */
@@ -230,9 +232,9 @@
      * Called when a notification channel is modified, in response to
      * {@link NotificationListenerService#onNotificationChannelModified}.
      *
-     * @param pkg the package the notification channel belongs to.
-     * @param user the user the notification channel belongs to.
-     * @param channel the channel being modified.
+     * @param pkg              the package the notification channel belongs to.
+     * @param user             the user the notification channel belongs to.
+     * @param channel          the channel being modified.
      * @param modificationType the type of modification that occurred to the channel.
      */
     void onNotificationChannelModified(
@@ -300,7 +302,7 @@
          * Called when the expansion state of the bubble stack changes.
          *
          * @param isExpanding whether it's expanding or collapsing
-         * @param key the notification key associated with bubble being expanded
+         * @param key         the notification key associated with bubble being expanded
          */
         void onBubbleExpandChanged(boolean isExpanding, String key);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
index 3a3a378..1375684 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
@@ -18,10 +18,10 @@
 
 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.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.content.Context;
 import android.hardware.input.InputManager;
-import android.util.Log;
 import android.view.Choreographer;
 import android.view.InputChannel;
 import android.view.InputEventReceiver;
@@ -29,6 +29,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
 
 /**
@@ -58,9 +59,7 @@
      * @param listener listener that is notified of touch events
      */
     void start(MotionEventListener listener) {
-        if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "start monitoring bubbles swipe up gesture");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "start monitoring bubbles swipe up gesture");
 
         stopInternal();
 
@@ -76,9 +75,7 @@
     }
 
     void stop() {
-        if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "stop monitoring bubbles swipe up gesture");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "stop monitoring bubbles swipe up gesture");
         stopInternal();
     }
 
@@ -94,9 +91,7 @@
     }
 
     private void onInterceptTouch() {
-        if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "intercept touch event");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "intercept touch event");
         if (mInputMonitor != null) {
             mInputMonitor.pilferPointers();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
index 844526c..b7107f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
@@ -16,19 +16,20 @@
 
 package com.android.wm.shell.bubbles;
 
-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.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.content.Context;
 import android.graphics.PointF;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.protolog.common.ProtoLog;
+
 /**
  * Handles {@link MotionEvent}s for bubbles that begin in the nav bar area
  */
@@ -112,10 +113,8 @@
     private boolean isInGestureRegion(MotionEvent ev) {
         // Only handles touch events beginning in navigation bar system gesture zone
         if (mPositioner.getNavBarGestureZone().contains((int) ev.getX(), (int) ev.getY())) {
-            if (DEBUG_BUBBLE_GESTURE) {
-                Log.d(TAG, "handling touch y=" + ev.getY()
-                        + " navBarGestureZone=" + mPositioner.getNavBarGestureZone());
-            }
+            ProtoLog.d(WM_SHELL_BUBBLES, "handling touch x=%d y=%d navBarGestureZone=%s",
+                    (int) ev.getX(), (int) ev.getY(), mPositioner.getNavBarGestureZone());
             return true;
         }
         return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
deleted file mode 100644
index 67ecb91..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
+++ /dev/null
@@ -1,159 +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.wm.shell.bubbles
-
-import android.animation.ObjectAnimator
-import android.content.Context
-import android.graphics.Color
-import android.graphics.drawable.GradientDrawable
-import android.util.IntProperty
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowInsets
-import android.view.WindowManager
-import android.widget.FrameLayout
-import androidx.dynamicanimation.animation.DynamicAnimation
-import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
-import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
-import com.android.wm.shell.R
-import com.android.wm.shell.animation.PhysicsAnimator
-import com.android.wm.shell.common.DismissCircleView
-
-/*
- * View that handles interactions between DismissCircleView and BubbleStackView.
- */
-class DismissView(context: Context) : FrameLayout(context) {
-
-    var circle = DismissCircleView(context)
-    var isShowing = false
-    var targetSizeResId: Int
-
-    private val animator = PhysicsAnimator.getInstance(circle)
-    private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
-    private val DISMISS_SCRIM_FADE_MS = 200L
-    private var wm: WindowManager =
-            context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
-    private var gradientDrawable = createGradient()
-
-    private val GRADIENT_ALPHA: IntProperty<GradientDrawable> =
-            object : IntProperty<GradientDrawable>("alpha") {
-        override fun setValue(d: GradientDrawable, percent: Int) {
-            d.alpha = percent
-        }
-        override fun get(d: GradientDrawable): Int {
-            return d.alpha
-        }
-    }
-
-    init {
-        setLayoutParams(LayoutParams(
-            ViewGroup.LayoutParams.MATCH_PARENT,
-            resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height),
-            Gravity.BOTTOM))
-        updatePadding()
-        setClipToPadding(false)
-        setClipChildren(false)
-        setVisibility(View.INVISIBLE)
-        setBackgroundDrawable(gradientDrawable)
-
-        targetSizeResId = R.dimen.dismiss_circle_size
-        val targetSize: Int = resources.getDimensionPixelSize(targetSizeResId)
-        addView(circle, LayoutParams(targetSize, targetSize,
-                Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL))
-        // start with circle offscreen so it's animated up
-        circle.setTranslationY(resources.getDimensionPixelSize(
-                R.dimen.floating_dismiss_gradient_height).toFloat())
-    }
-
-    /**
-     * Animates this view in.
-     */
-    fun show() {
-        if (isShowing) return
-        isShowing = true
-        setVisibility(View.VISIBLE)
-        val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
-                gradientDrawable.alpha, 255)
-        alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS)
-        alphaAnim.start()
-
-        animator.cancel()
-        animator
-            .spring(DynamicAnimation.TRANSLATION_Y, 0f, spring)
-            .start()
-    }
-
-    /**
-     * Animates this view out, as well as the circle that encircles the bubbles, if they
-     * were dragged into the target and encircled.
-     */
-    fun hide() {
-        if (!isShowing) return
-        isShowing = false
-        val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
-                gradientDrawable.alpha, 0)
-        alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS)
-        alphaAnim.start()
-        animator
-            .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(),
-                spring)
-            .withEndActions({ setVisibility(View.INVISIBLE) })
-            .start()
-    }
-
-    /**
-     * Cancels the animator for the dismiss target.
-     */
-    fun cancelAnimators() {
-        animator.cancel()
-    }
-
-    fun updateResources() {
-        updatePadding()
-        layoutParams.height = resources.getDimensionPixelSize(
-                R.dimen.floating_dismiss_gradient_height)
-
-        val targetSize = resources.getDimensionPixelSize(targetSizeResId)
-        circle.layoutParams.width = targetSize
-        circle.layoutParams.height = targetSize
-        circle.requestLayout()
-    }
-
-    private fun createGradient(): GradientDrawable {
-        val gradientColor = context.resources.getColor(android.R.color.system_neutral1_900)
-        val alpha = 0.7f * 255
-        val gradientColorWithAlpha = Color.argb(alpha.toInt(),
-                Color.red(gradientColor),
-                Color.green(gradientColor),
-                Color.blue(gradientColor))
-        val gd = GradientDrawable(
-                GradientDrawable.Orientation.BOTTOM_TOP,
-                intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT))
-        gd.setDither(true)
-        gd.setAlpha(0)
-        return gd
-    }
-
-    private fun updatePadding() {
-        val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets()
-        val navInset = insets.getInsetsIgnoringVisibility(
-                WindowInsets.Type.navigationBars())
-        setPadding(0, 0, 0, navInset.bottom +
-                resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin))
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt
new file mode 100644
index 0000000..ed36240
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:JvmName("DismissViewUtils")
+
+package com.android.wm.shell.bubbles
+
+import com.android.wm.shell.R
+import com.android.wm.shell.common.bubbles.DismissView
+
+fun DismissView.setup() {
+    setup(DismissView.Config(
+            targetSizeResId = R.dimen.dismiss_circle_size,
+            iconSizeResId = R.dimen.dismiss_target_x_size,
+            bottomMarginResId = R.dimen.floating_dismiss_bottom_margin,
+            floatingGradientHeightResId = R.dimen.floating_dismiss_gradient_height,
+            floatingGradientColorResId = android.R.color.system_neutral1_900,
+            backgroundResId = R.drawable.dismiss_circle_background,
+            iconResId = R.drawable.pip_ic_close_white
+    ))
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 862e818..4dda068 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -29,12 +29,14 @@
 
     oneway void unregisterBubbleListener(in IBubblesListener listener) = 2;
 
-    oneway void showBubble(in String key, in boolean onLauncherHome) = 3;
+    oneway void showBubble(in String key, in int bubbleBarOffsetX, in int bubbleBarOffsetY) = 3;
 
-    oneway void removeBubble(in String key, in int reason) = 4;
+    oneway void removeBubble(in String key) = 4;
 
-    oneway void collapseBubbles() = 5;
+    oneway void removeAllBubbles() = 5;
 
-    oneway void onTaskbarStateChanged(in int newState) = 6;
+    oneway void collapseBubbles() = 6;
+
+    oneway void onBubbleDrag(in String key, in boolean isBeingDragged) = 7;
 
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index e95e8e5..1b41f79 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -41,9 +41,9 @@
     private val ANIMATE_DURATION: Long = 200
 
     private val positioner: BubblePositioner = positioner
-    private val manageView by lazy { findViewById<ViewGroup>(R.id.manage_education_view) }
-    private val manageButton by lazy { findViewById<Button>(R.id.manage_button) }
-    private val gotItButton by lazy { findViewById<Button>(R.id.got_it) }
+    private val manageView by lazy { requireViewById<ViewGroup>(R.id.manage_education_view) }
+    private val manageButton by lazy { requireViewById<Button>(R.id.manage_button) }
+    private val gotItButton by lazy { requireViewById<Button>(R.id.got_it) }
 
     private var isHiding = false
     private var realManageButtonRect = Rect()
@@ -122,7 +122,7 @@
             manageButton
                 .setOnClickListener {
                     hide()
-                    expandedView.findViewById<View>(R.id.manage_button).performClick()
+                    expandedView.requireViewById<View>(R.id.manage_button).performClick()
                 }
             gotItButton.setOnClickListener { hide() }
             setOnClickListener { hide() }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
deleted file mode 100644
index ea9d065..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
+++ /dev/null
@@ -1,168 +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.wm.shell.bubbles
-
-import android.graphics.PointF
-import android.view.MotionEvent
-import android.view.VelocityTracker
-import android.view.View
-import android.view.ViewConfiguration
-import kotlin.math.hypot
-
-/**
- * Listener which receives [onDown], [onMove], and [onUp] events, with relevant information about
- * the coordinates of the touch and the view relative to the initial ACTION_DOWN event and the
- * view's initial position.
- */
-abstract class RelativeTouchListener : View.OnTouchListener {
-
-    /**
-     * Called when an ACTION_DOWN event is received for the given view.
-     *
-     * @return False if the object is not interested in MotionEvents at this time, or true if we
-     * should consume this event and subsequent events, and begin calling [onMove].
-     */
-    abstract fun onDown(v: View, ev: MotionEvent): Boolean
-
-    /**
-     * Called when an ACTION_MOVE event is received for the given view. This signals that the view
-     * is being dragged.
-     *
-     * @param viewInitialX The view's translationX value when this touch gesture started.
-     * @param viewInitialY The view's translationY value when this touch gesture started.
-     * @param dx Horizontal distance covered since the initial ACTION_DOWN event, in pixels.
-     * @param dy Vertical distance covered since the initial ACTION_DOWN event, in pixels.
-     */
-    abstract fun onMove(
-        v: View,
-        ev: MotionEvent,
-        viewInitialX: Float,
-        viewInitialY: Float,
-        dx: Float,
-        dy: Float
-    )
-
-    /**
-     * Called when an ACTION_UP event is received for the given view. This signals that a drag or
-     * fling gesture has completed.
-     *
-     * @param viewInitialX The view's translationX value when this touch gesture started.
-     * @param viewInitialY The view's translationY value when this touch gesture started.
-     * @param dx Horizontal distance covered, in pixels.
-     * @param dy Vertical distance covered, in pixels.
-     * @param velX The final horizontal velocity of the gesture, in pixels/second.
-     * @param velY The final vertical velocity of the gesture, in pixels/second.
-     */
-    abstract fun onUp(
-        v: View,
-        ev: MotionEvent,
-        viewInitialX: Float,
-        viewInitialY: Float,
-        dx: Float,
-        dy: Float,
-        velX: Float,
-        velY: Float
-    )
-
-    /** The raw coordinates of the last ACTION_DOWN event. */
-    private val touchDown = PointF()
-
-    /** The coordinates of the view, at the time of the last ACTION_DOWN event. */
-    private val viewPositionOnTouchDown = PointF()
-
-    private val velocityTracker = VelocityTracker.obtain()
-
-    private var touchSlop: Int = -1
-    private var movedEnough = false
-
-    private var performedLongClick = false
-
-    @Suppress("UNCHECKED_CAST")
-    override fun onTouch(v: View, ev: MotionEvent): Boolean {
-        addMovement(ev)
-
-        val dx = ev.rawX - touchDown.x
-        val dy = ev.rawY - touchDown.y
-
-        when (ev.action) {
-            MotionEvent.ACTION_DOWN -> {
-                if (!onDown(v, ev)) {
-                    return false
-                }
-
-                // Grab the touch slop, it might have changed if the config changed since the
-                // last gesture.
-                touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop
-
-                touchDown.set(ev.rawX, ev.rawY)
-                viewPositionOnTouchDown.set(v.translationX, v.translationY)
-
-                performedLongClick = false
-                v.handler.postDelayed({
-                    if (v.isLongClickable) {
-                        performedLongClick = v.performLongClick()
-                    }
-                }, ViewConfiguration.getLongPressTimeout().toLong())
-            }
-
-            MotionEvent.ACTION_MOVE -> {
-                if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
-                    movedEnough = true
-                    v.handler.removeCallbacksAndMessages(null)
-                }
-
-                if (movedEnough) {
-                    onMove(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy)
-                }
-            }
-
-            MotionEvent.ACTION_UP -> {
-                if (movedEnough) {
-                    velocityTracker.computeCurrentVelocity(1000 /* units */)
-                    onUp(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy,
-                            velocityTracker.xVelocity, velocityTracker.yVelocity)
-                } else if (!performedLongClick) {
-                    v.performClick()
-                } else {
-                    v.handler.removeCallbacksAndMessages(null)
-                }
-
-                velocityTracker.clear()
-                movedEnough = false
-            }
-
-            MotionEvent.ACTION_CANCEL -> {
-                v.handler.removeCallbacksAndMessages(null)
-                velocityTracker.clear()
-                movedEnough = false
-            }
-        }
-
-        return true
-    }
-
-    /**
-     * Adds a movement to the velocity tracker using raw screen coordinates.
-     */
-    private fun addMovement(event: MotionEvent) {
-        val deltaX = event.rawX - event.x
-        val deltaY = event.rawY - event.y
-        event.offsetLocation(deltaX, deltaY)
-        velocityTracker.addMovement(event)
-        event.offsetLocation(-deltaX, -deltaY)
-    }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index d0598cd..5e3a077 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -48,9 +48,9 @@
     private val positioner: BubblePositioner = positioner
     private val controller: BubbleController = controller
 
-    private val view by lazy { findViewById<View>(R.id.stack_education_layout) }
-    private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) }
-    private val descTextView by lazy { findViewById<TextView>(R.id.stack_education_description) }
+    private val view by lazy { requireViewById<View>(R.id.stack_education_layout) }
+    private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) }
+    private val descTextView by lazy { requireViewById<TextView>(R.id.stack_education_description) }
 
     var isHiding = false
         private set
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
index beb1c5f..f3cc514 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
@@ -327,6 +327,12 @@
         addViewInternal(child, index, params, false /* isReorder */);
     }
 
+    /** Removes the child view immediately. */
+    public void removeViewNoAnimation(View view) {
+        super.removeView(view);
+        view.setTag(R.id.physics_animator_tag, null);
+    }
+
     @Override
     public void removeView(View view) {
         if (mController != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 23f65f9..689323b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -21,12 +21,15 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.content.Context;
-import android.graphics.PointF;
+import android.graphics.Point;
 import android.util.Log;
 import android.widget.FrameLayout;
 
+import androidx.annotation.Nullable;
+
 import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.bubbles.BubbleViewProvider;
 import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
@@ -110,7 +113,8 @@
     /**
      * Animates the provided bubble's expanded view to the expanded state.
      */
-    public void animateExpansion(BubbleViewProvider expandedBubble) {
+    public void animateExpansion(BubbleViewProvider expandedBubble,
+            @Nullable Runnable afterAnimation) {
         mExpandedBubble = expandedBubble;
         if (mExpandedBubble == null) {
             return;
@@ -132,7 +136,7 @@
         bev.setVisibility(VISIBLE);
 
         // Set the pivot point for the scale, so the view animates out from the bubble bar.
-        PointF bubbleBarPosition = mPositioner.getBubbleBarPosition();
+        Point bubbleBarPosition = mPositioner.getBubbleBarPosition();
         mExpandedViewContainerMatrix.setScale(
                 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
                 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
@@ -159,6 +163,9 @@
                     bev.setAnimationMatrix(null);
                     updateExpandedView();
                     bev.setSurfaceZOrderedOnTop(false);
+                    if (afterAnimation != null) {
+                        afterAnimation.run();
+                    }
                 })
                 .start();
     }
@@ -208,6 +215,14 @@
         mExpandedViewAlphaAnimator.reverse();
     }
 
+    /**
+     * Cancel current animations
+     */
+    public void cancelAnimations() {
+        PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
+        mExpandedViewAlphaAnimator.cancel();
+    }
+
     private void updateExpandedView() {
         if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) {
             Log.w(TAG, "Trying to update the expanded view without a bubble");
@@ -215,9 +230,10 @@
         }
         BubbleBarExpandedView bbev = mExpandedBubble.getBubbleBarExpandedView();
 
+        boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
         final int padding = mPositioner.getBubbleBarExpandedViewPadding();
-        final int width = mPositioner.getExpandedViewWidthForBubbleBar();
-        final int height = mPositioner.getExpandedViewHeightForBubbleBar();
+        final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
+        final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bbev.getLayoutParams();
         lp.width = width;
         lp.height = height;
@@ -227,7 +243,8 @@
         } else {
             bbev.setX(mPositioner.getAvailableRect().width() - width - padding);
         }
-        bbev.setY(mPositioner.getInsets().top + padding);
+        bbev.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height);
         bbev.updateLocation();
+        bbev.maybeShowOverflow();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index b8f049b..6b6d6ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -16,12 +16,16 @@
 
 package com.android.wm.shell.bubbles.bar;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.Outline;
+import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewOutlineProvider;
 import android.widget.FrameLayout;
@@ -30,9 +34,14 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubble;
 import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleOverflowContainerView;
 import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
+import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.taskview.TaskView;
 
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
 /**
  * Expanded view of a bubble when it's part of the bubble bar.
  *
@@ -44,12 +53,18 @@
     private static final int INVALID_TASK_ID = -1;
 
     private BubbleController mController;
+    private boolean mIsOverflow;
     private BubbleTaskViewHelper mBubbleTaskViewHelper;
+    private BubbleBarMenuViewController mMenuViewController;
+    private @Nullable Supplier<Rect> mLayerBoundsSupplier;
+    private @Nullable Consumer<String> mUnBubbleConversationCallback;
 
-    private HandleView mMenuView;
-    private TaskView mTaskView;
+    private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext());
+    private @Nullable TaskView mTaskView;
+    private @Nullable BubbleOverflowContainerView mOverflowView;
 
-    private int mMenuHeight;
+    private int mCaptionHeight;
+
     private int mBackgroundColor;
     private float mCornerRadius = 0f;
 
@@ -83,11 +98,9 @@
         super.onFinishInflate();
         Context context = getContext();
         setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation));
-        mMenuHeight = context.getResources().getDimensionPixelSize(
-                R.dimen.bubblebar_expanded_view_menu_size);
-        mMenuView = new HandleView(context);
-        addView(mMenuView);
-
+        mCaptionHeight = context.getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_caption_height);
+        addView(mHandleView);
         applyThemeAttrs();
         setClipToOutline(true);
         setOutlineProvider(new ViewOutlineProvider() {
@@ -98,23 +111,78 @@
         });
     }
 
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        // Hide manage menu when view disappears
+        mMenuViewController.hideMenu(false /* animated */);
+    }
+
     /** Set the BubbleController on the view, must be called before doing anything else. */
-    public void initialize(BubbleController controller) {
+    public void initialize(BubbleController controller, boolean isOverflow) {
         mController = controller;
-        mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController,
-                /* listener= */ this,
-                /* viewParent= */ this);
-        mTaskView = mBubbleTaskViewHelper.getTaskView();
-        addView(mTaskView);
-        mTaskView.setEnableSurfaceClipping(true);
-        mTaskView.setCornerRadius(mCornerRadius);
+        mIsOverflow = isOverflow;
+
+        if (mIsOverflow) {
+            mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
+                    R.layout.bubble_overflow_container, null /* root */);
+            mOverflowView.setBubbleController(mController);
+            addView(mOverflowView);
+        } else {
+
+            mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController,
+                    /* listener= */ this,
+                    /* viewParent= */ this);
+            mTaskView = mBubbleTaskViewHelper.getTaskView();
+            addView(mTaskView);
+            mTaskView.setEnableSurfaceClipping(true);
+            mTaskView.setCornerRadius(mCornerRadius);
+
+            // Handle view needs to draw on top of task view.
+            bringChildToFront(mHandleView);
+        }
+        mMenuViewController = new BubbleBarMenuViewController(mContext, this);
+        mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() {
+            @Override
+            public void onMenuVisibilityChanged(boolean visible) {
+                if (mTaskView == null || mLayerBoundsSupplier == null) return;
+                // Updates the obscured touchable region for the task surface.
+                mTaskView.setObscuredTouchRect(visible ? mLayerBoundsSupplier.get() : null);
+            }
+
+            @Override
+            public void onUnBubbleConversation(Bubble bubble) {
+                if (mUnBubbleConversationCallback != null) {
+                    mUnBubbleConversationCallback.accept(bubble.getKey());
+                }
+            }
+
+            @Override
+            public void onOpenAppSettings(Bubble bubble) {
+                mController.collapseStack();
+                mContext.startActivityAsUser(bubble.getSettingsIntent(mContext), bubble.getUser());
+            }
+
+            @Override
+            public void onDismissBubble(Bubble bubble) {
+                mController.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED);
+            }
+        });
+        mHandleView.setOnClickListener(view -> {
+            mMenuViewController.showMenu(true /* animated */);
+        });
+    }
+
+    public BubbleBarHandleView getHandleView() {
+        return mHandleView;
     }
 
     // TODO (b/275087636): call this when theme/config changes
-    void applyThemeAttrs() {
+    /** Updates the view based on the current theme. */
+    public void applyThemeAttrs() {
         boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
                 mContext.getResources());
-        final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
+        final TypedArray ta = mContext.obtainStyledAttributes(new int[]{
                 android.R.attr.dialogCornerRadius,
                 android.R.attr.colorBackgroundFloating});
         mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0;
@@ -123,14 +191,12 @@
 
         ta.recycle();
 
-        mMenuView.setCornerRadius(mCornerRadius);
-        mMenuHeight = getResources().getDimensionPixelSize(
-                R.dimen.bubblebar_expanded_view_menu_size);
+        mCaptionHeight = getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_caption_height);
 
         if (mTaskView != null) {
             mTaskView.setCornerRadius(mCornerRadius);
-            mTaskView.setElevation(150);
-            updateMenuColor();
+            updateHandleColor(true /* animated */);
         }
     }
 
@@ -138,35 +204,33 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         int height = MeasureSpec.getSize(heightMeasureSpec);
-
-        // Add corner radius here so that the menu extends behind the rounded corners of TaskView.
-        int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height);
-        measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
+        int menuViewHeight = Math.min(mCaptionHeight, height);
+        measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
                 MeasureSpec.getMode(heightMeasureSpec)));
 
         if (mTaskView != null) {
-            int taskViewHeight = height - menuViewHeight;
-            measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight,
+            measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(height,
                     MeasureSpec.getMode(heightMeasureSpec)));
         }
     }
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        // Drag handle above
-        final int dragHandleBottom = t + mMenuView.getMeasuredHeight();
-        mMenuView.layout(l, t, r, dragHandleBottom);
+        super.onLayout(changed, l, t, r, b);
+        final int captionBottom = t + mCaptionHeight;
         if (mTaskView != null) {
-            // Subtract radius so that the menu extends behind the rounded corners of TaskView.
-            mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r,
-                    dragHandleBottom + mTaskView.getMeasuredHeight());
+            mTaskView.layout(l, t, r,
+                    t + mTaskView.getMeasuredHeight());
+            mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
         }
+        // Handle draws on top of task view in the caption area.
+        mHandleView.layout(l, t, r, captionBottom);
     }
 
     @Override
     public void onTaskCreated() {
         setContentVisibility(true);
-        updateMenuColor();
+        updateHandleColor(false /* animated */);
     }
 
     @Override
@@ -187,11 +251,13 @@
             }
             mBubbleTaskViewHelper.cleanUpTaskView();
         }
+        mMenuViewController.hideMenu(false /* animated */);
     }
 
-    /** Updates the bubble shown in this task view. */
+    /** Updates the bubble shown in the expanded view. */
     public void update(Bubble bubble) {
         mBubbleTaskViewHelper.update(bubble);
+        mMenuViewController.updateMenu(bubble);
     }
 
     /** The task id of the activity shown in the task view, if it exists. */
@@ -199,12 +265,33 @@
         return mBubbleTaskViewHelper != null ? mBubbleTaskViewHelper.getTaskId() : INVALID_TASK_ID;
     }
 
+    /** Sets layer bounds supplier used for obscured touchable region of task view */
+    void setLayerBoundsSupplier(@Nullable Supplier<Rect> supplier) {
+        mLayerBoundsSupplier = supplier;
+    }
+
+    /** Sets the function to call to un-bubble the given conversation. */
+    public void setUnBubbleConversationCallback(
+            @Nullable Consumer<String> unBubbleConversationCallback) {
+        mUnBubbleConversationCallback = unBubbleConversationCallback;
+    }
+
     /**
      * Call when the location or size of the view has changed to update TaskView.
      */
     public void updateLocation() {
-        if (mTaskView == null) return;
-        mTaskView.onLocationChanged();
+        if (mTaskView != null) {
+            mTaskView.onLocationChanged();
+        }
+    }
+
+    /** Shows the expanded view for the overflow if it exists. */
+    void maybeShowOverflow() {
+        if (mOverflowView != null) {
+            // post this to the looper so that the view has a chance to be laid out before it can
+            // calculate row and column sizes correctly.
+            post(() -> mOverflowView.show());
+        }
     }
 
     /** Sets the alpha of the task view. */
@@ -218,17 +305,21 @@
         }
     }
 
-    /** Updates the menu bar to be the status bar color specified by the app. */
-    private void updateMenuColor() {
-        if (mTaskView == null) return;
-        ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo();
-        final int taskBgColor = info.taskDescription.getStatusBarColor();
-        final int color = Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
-        if (color != -1) {
-            mMenuView.setBackgroundColor(color);
-        } else {
-            mMenuView.setBackgroundColor(mBackgroundColor);
+    /**
+     * Updates the handle color based on the task view status bar or background color; if those
+     * are transparent it defaults to the background color pulled from system theme attributes.
+     */
+    private void updateHandleColor(boolean animated) {
+        if (mTaskView == null || mTaskView.getTaskInfo() == null) return;
+        int color = mBackgroundColor;
+        ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription;
+        if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) {
+            color = taskDescription.getStatusBarColor();
+        } else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) {
+            color = taskDescription.getBackgroundColor();
         }
+        final boolean isRegionDark = Color.luminance(color) <= 0.5;
+        mHandleView.updateHandleColor(isRegionDark, animated);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
new file mode 100644
index 0000000..2b7a070
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Outline;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import androidx.annotation.ColorInt;
+import androidx.core.content.ContextCompat;
+
+import com.android.wm.shell.R;
+
+/**
+ * Handle view to show at the top of a bubble bar expanded view.
+ */
+public class BubbleBarHandleView extends View {
+    private static final long COLOR_CHANGE_DURATION = 120;
+
+    // The handle view is currently rendered as 3 evenly spaced dots.
+    private int mDotSize;
+    private int mDotSpacing;
+    // Path used to draw the dots
+    private final Path mPath = new Path();
+
+    private @ColorInt int mHandleLightColor;
+    private @ColorInt int mHandleDarkColor;
+    private @Nullable ObjectAnimator mColorChangeAnim;
+
+    public BubbleBarHandleView(Context context) {
+        this(context, null /* attrs */);
+    }
+
+    public BubbleBarHandleView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /* defStyleAttr */);
+    }
+
+    public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
+    }
+
+    public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mDotSize = getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_caption_dot_size);
+        mDotSpacing = getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_caption_dot_spacing);
+        mHandleLightColor = ContextCompat.getColor(getContext(),
+                R.color.bubble_bar_expanded_view_handle_light);
+        mHandleDarkColor = ContextCompat.getColor(getContext(),
+                R.color.bubble_bar_expanded_view_handle_dark);
+
+        setClipToOutline(true);
+        setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                final int handleCenterX = view.getWidth() / 2;
+                final int handleCenterY = view.getHeight() / 2;
+                final int handleTotalWidth = mDotSize * 3 + mDotSpacing * 2;
+                final int handleLeft = handleCenterX - handleTotalWidth / 2;
+                final int handleTop = handleCenterY - mDotSize / 2;
+                final int handleBottom = handleTop + mDotSize;
+                RectF dot1 = new RectF(
+                        handleLeft, handleTop,
+                        handleLeft + mDotSize, handleBottom);
+                RectF dot2 = new RectF(
+                        dot1.right + mDotSpacing, handleTop,
+                        dot1.right + mDotSpacing + mDotSize, handleBottom
+                );
+                RectF dot3 = new RectF(
+                        dot2.right + mDotSpacing, handleTop,
+                        dot2.right + mDotSpacing + mDotSize, handleBottom
+                );
+                mPath.reset();
+                mPath.addOval(dot1, Path.Direction.CW);
+                mPath.addOval(dot2, Path.Direction.CW);
+                mPath.addOval(dot3, Path.Direction.CW);
+                outline.setPath(mPath);
+            }
+        });
+    }
+
+    /**
+     * Updates the handle color.
+     *
+     * @param isRegionDark Whether the background behind the handle is dark, and thus the handle
+     *                     should be light (and vice versa).
+     * @param animated      Whether to animate the change, or apply it immediately.
+     */
+    public void updateHandleColor(boolean isRegionDark, boolean animated) {
+        int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor;
+        if (mColorChangeAnim != null) {
+            mColorChangeAnim.cancel();
+        }
+        if (animated) {
+            mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor);
+            mColorChangeAnim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mColorChangeAnim = null;
+                }
+            });
+            mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION);
+            mColorChangeAnim.start();
+        } else {
+            setBackgroundColor(newColor);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index b1a725b..bc04bfc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -24,14 +24,18 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.ColorDrawable;
+import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
 
 import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.bubbles.BubbleViewProvider;
 
+import java.util.function.Consumer;
+
 /**
  * Similar to {@link com.android.wm.shell.bubbles.BubbleStackView}, this view is added to window
  * manager to display bubbles. However, it is only used when bubbles are being displayed in
@@ -53,6 +57,7 @@
     @Nullable
     private BubbleViewProvider mExpandedBubble;
     private BubbleBarExpandedView mExpandedView;
+    private @Nullable Consumer<String> mUnBubbleConversationCallback;
 
     // TODO(b/273310265) - currently the view is always on the right, need to update for RTL.
     /** Whether the expanded view is displaying on the left of the screen or not. */
@@ -64,6 +69,10 @@
     private final Region mTouchableRegion = new Region();
     private final Rect mTempRect = new Rect();
 
+    // Used to ensure touch target size for the menu shown on a bubble expanded view
+    private TouchDelegate mHandleTouchDelegate;
+    private final Rect mHandleTouchBounds = new Rect();
+
     public BubbleBarLayerView(Context context, BubbleController controller) {
         super(context);
         mBubbleController = controller;
@@ -141,17 +150,42 @@
             mExpandedView = null;
         }
         if (mExpandedView == null) {
+            if (expandedView.getParent() != null) {
+                // Expanded view might be animating collapse and is still attached
+                // Cancel current animations and remove from parent
+                mAnimationHelper.cancelAnimations();
+                removeView(expandedView);
+            }
             mExpandedBubble = b;
             mExpandedView = expandedView;
-            final int width = mPositioner.getExpandedViewWidthForBubbleBar();
-            final int height = mPositioner.getExpandedViewHeightForBubbleBar();
+            boolean isOverflowExpanded = b.getKey().equals(BubbleOverflow.KEY);
+            final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
+            final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
             mExpandedView.setVisibility(GONE);
+            mExpandedView.setUnBubbleConversationCallback(mUnBubbleConversationCallback);
+            mExpandedView.setLayerBoundsSupplier(() -> new Rect(0, 0, getWidth(), getHeight()));
+            mExpandedView.setUnBubbleConversationCallback(bubbleKey -> {
+                if (mUnBubbleConversationCallback != null) {
+                    mUnBubbleConversationCallback.accept(bubbleKey);
+                }
+            });
+            mExpandedView.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height);
             addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
         }
 
         mIsExpanded = true;
         mBubbleController.getSysuiProxy().onStackExpandChanged(true);
-        mAnimationHelper.animateExpansion(mExpandedBubble);
+        mAnimationHelper.animateExpansion(mExpandedBubble, () -> {
+            if (mExpandedView == null) return;
+            // Touch delegate for the menu
+            BubbleBarHandleView view = mExpandedView.getHandleView();
+            view.getBoundsOnScreen(mHandleTouchBounds);
+            mHandleTouchBounds.top -= mPositioner.getBubblePaddingTop();
+            mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds,
+                    mExpandedView.getHandleView());
+            setTouchDelegate(mHandleTouchDelegate);
+        });
+
         showScrim(true);
     }
 
@@ -162,15 +196,23 @@
         mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
         mBubbleController.getSysuiProxy().onStackExpandChanged(false);
         mExpandedView = null;
+        setTouchDelegate(null);
         showScrim(false);
     }
 
+    /** Sets the function to call to un-bubble the given conversation. */
+    public void setUnBubbleConversationCallback(
+            @Nullable Consumer<String> unBubbleConversationCallback) {
+        mUnBubbleConversationCallback = unBubbleConversationCallback;
+    }
+
     /** Updates the expanded view size and position. */
     private void updateExpandedView() {
         if (mExpandedView == null) return;
+        boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
         final int padding = mPositioner.getBubbleBarExpandedViewPadding();
-        final int width = mPositioner.getExpandedViewWidthForBubbleBar();
-        final int height = mPositioner.getExpandedViewHeightForBubbleBar();
+        final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded);
+        final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
         FrameLayout.LayoutParams lp = (LayoutParams) mExpandedView.getLayoutParams();
         lp.width = width;
         lp.height = height;
@@ -180,7 +222,7 @@
         } else {
             mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding);
         }
-        mExpandedView.setY(mPositioner.getInsets().top + padding);
+        mExpandedView.setY(mPositioner.getExpandedViewBottomForBubbleBar() - height);
         mExpandedView.updateLocation();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
new file mode 100644
index 0000000..00b9777
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuItemView.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Bubble bar expanded view menu item view to display menu action details
+ */
+public class BubbleBarMenuItemView extends LinearLayout {
+    private ImageView mImageView;
+    private TextView mTextView;
+
+    public BubbleBarMenuItemView(Context context) {
+        this(context, null /* attrs */);
+    }
+
+    public BubbleBarMenuItemView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /* defStyleAttr */);
+    }
+
+    public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
+    }
+
+    public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mImageView = findViewById(R.id.bubble_bar_menu_item_icon);
+        mTextView = findViewById(R.id.bubble_bar_menu_item_title);
+    }
+
+    /**
+     * Update menu item with the details and tint color
+     */
+    void update(Icon icon, String title, @ColorInt int tint) {
+        if (tint == Color.TRANSPARENT) {
+            final TypedArray typedArray = getContext().obtainStyledAttributes(
+                    new int[]{android.R.attr.textColorPrimary});
+            mTextView.setTextColor(typedArray.getColor(0, Color.BLACK));
+        } else {
+            icon.setTint(tint);
+            mTextView.setTextColor(tint);
+        }
+
+        mImageView.setImageIcon(icon);
+        mTextView.setText(title);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
new file mode 100644
index 0000000..211fe0d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.Bubble;
+
+import java.util.ArrayList;
+
+/**
+ * Bubble bar expanded view menu
+ */
+public class BubbleBarMenuView extends LinearLayout {
+    private ViewGroup mBubbleSectionView;
+    private ViewGroup mActionsSectionView;
+    private ImageView mBubbleIconView;
+    private TextView mBubbleTitleView;
+
+    public BubbleBarMenuView(Context context) {
+        this(context, null /* attrs */);
+    }
+
+    public BubbleBarMenuView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /* defStyleAttr */);
+    }
+
+    public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
+    }
+
+    public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mBubbleSectionView = findViewById(R.id.bubble_bar_manage_menu_bubble_section);
+        mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section);
+        mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon);
+        mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title);
+    }
+
+    /** Update menu details with bubble info */
+    void updateInfo(Bubble bubble) {
+        if (bubble.getIcon() != null) {
+            mBubbleIconView.setImageIcon(bubble.getIcon());
+        } else {
+            mBubbleIconView.setImageBitmap(bubble.getBubbleIcon());
+        }
+        mBubbleTitleView.setText(bubble.getTitle());
+    }
+
+    /**
+     * Update menu action items views
+     * @param actions used to populate menu item views
+     */
+    void updateActions(ArrayList<MenuAction> actions) {
+        mActionsSectionView.removeAllViews();
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+
+        for (MenuAction action : actions) {
+            BubbleBarMenuItemView itemView = (BubbleBarMenuItemView) inflater.inflate(
+                    R.layout.bubble_bar_menu_item, mActionsSectionView, false);
+            itemView.update(action.mIcon, action.mTitle, action.mTint);
+            itemView.setOnClickListener(action.mOnClick);
+            mActionsSectionView.addView(itemView);
+        }
+    }
+
+    /** Sets on close menu listener */
+    void setOnCloseListener(Runnable onClose) {
+        mBubbleSectionView.setOnClickListener(view -> {
+            onClose.run();
+        });
+    }
+
+    /**
+     * Overridden to proxy to section views alpha.
+     * @implNote
+     * If animate alpha on the parent (menu container) view, section view shadows get distorted.
+     * To prevent distortion and artifacts alpha changes applied directly on the section views.
+     */
+    @Override
+    public void setAlpha(float alpha) {
+        mBubbleSectionView.setAlpha(alpha);
+        mActionsSectionView.setAlpha(alpha);
+    }
+
+    /**
+     * Overridden to proxy section view alpha value.
+     * @implNote
+     * The assumption is that both section views have the same alpha value
+     */
+    @Override
+    public float getAlpha() {
+        return mBubbleSectionView.getAlpha();
+    }
+
+    /**
+     * Menu action details used to create menu items
+     */
+    static class MenuAction {
+        private Icon mIcon;
+        private @ColorInt int mTint;
+        private String mTitle;
+        private OnClickListener mOnClick;
+
+        MenuAction(Icon icon, String title, OnClickListener onClick) {
+            this(icon, title, Color.TRANSPARENT, onClick);
+        }
+
+        MenuAction(Icon icon, String title, @ColorInt int tint, OnClickListener onClick) {
+            this.mIcon = icon;
+            this.mTitle = title;
+            this.mTint = tint;
+            this.mOnClick = onClick;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
new file mode 100644
index 0000000..8be140c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.core.content.ContextCompat;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.Bubble;
+
+import java.util.ArrayList;
+
+/**
+ * Manages bubble bar expanded view menu presentation and animations
+ */
+class BubbleBarMenuViewController {
+    private static final float MENU_INITIAL_SCALE = 0.5f;
+    private final Context mContext;
+    private final ViewGroup mRootView;
+    private @Nullable Listener mListener;
+    private @Nullable Bubble mBubble;
+    private @Nullable BubbleBarMenuView mMenuView;
+    /** A transparent view used to intercept touches to collapse menu when presented */
+    private @Nullable View mScrimView;
+    private @Nullable PhysicsAnimator<BubbleBarMenuView> mMenuAnimator;
+    private PhysicsAnimator.SpringConfig mMenuSpringConfig;
+
+    BubbleBarMenuViewController(Context context, ViewGroup rootView) {
+        mContext = context;
+        mRootView = rootView;
+        mMenuSpringConfig = new PhysicsAnimator.SpringConfig(
+                SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+    }
+
+    /** Sets menu actions listener */
+    void setListener(@Nullable Listener listener) {
+        mListener = listener;
+    }
+
+    /** Update menu with bubble */
+    void updateMenu(@NonNull Bubble bubble) {
+        mBubble = bubble;
+    }
+
+    /**
+     * Show bubble bar expanded view menu
+     * @param animated if should animate transition
+     */
+    void showMenu(boolean animated) {
+        if (mMenuView == null || mScrimView == null) {
+            setupMenu();
+        }
+        cancelAnimations();
+        mMenuView.setVisibility(View.VISIBLE);
+        mScrimView.setVisibility(View.VISIBLE);
+        Runnable endActions = () -> {
+            mMenuView.getChildAt(0).requestAccessibilityFocus();
+            if (mListener != null) {
+                mListener.onMenuVisibilityChanged(true /* isShown */);
+            }
+        };
+        if (animated) {
+            animateTransition(true /* show */, endActions);
+        } else {
+            endActions.run();
+        }
+    }
+
+    /**
+     * Hide bubble bar expanded view menu
+     * @param animated if should animate transition
+     */
+    void hideMenu(boolean animated) {
+        if (mMenuView == null || mScrimView == null) return;
+        cancelAnimations();
+        Runnable endActions = () -> {
+            mMenuView.setVisibility(View.GONE);
+            mScrimView.setVisibility(View.GONE);
+            if (mListener != null) {
+                mListener.onMenuVisibilityChanged(false /* isShown */);
+            }
+        };
+        if (animated) {
+            animateTransition(false /* show */, endActions);
+        } else {
+            endActions.run();
+        }
+    }
+
+    /**
+     * Animate show/hide menu transition
+     * @param show if should show or hide the menu
+     * @param endActions will be called when animation ends
+     */
+    private void animateTransition(boolean show, Runnable endActions) {
+        if (mMenuView == null) return;
+        mMenuAnimator = PhysicsAnimator.getInstance(mMenuView);
+        mMenuAnimator.setDefaultSpringConfig(mMenuSpringConfig);
+        mMenuAnimator
+                .spring(DynamicAnimation.ALPHA, show ? 1f : 0f)
+                .spring(DynamicAnimation.SCALE_Y, show ? 1f : MENU_INITIAL_SCALE)
+                .withEndActions(() -> {
+                    mMenuAnimator = null;
+                    endActions.run();
+                })
+                .start();
+    }
+
+    /** Cancel running animations */
+    private void cancelAnimations() {
+        if (mMenuAnimator != null) {
+            mMenuAnimator.cancel();
+            mMenuAnimator = null;
+        }
+    }
+
+    /** Sets up and inflate menu views */
+    private void setupMenu() {
+        // Menu view setup
+        mMenuView = (BubbleBarMenuView) LayoutInflater.from(mContext).inflate(
+                R.layout.bubble_bar_menu_view, mRootView, false);
+        mMenuView.setAlpha(0f);
+        mMenuView.setPivotY(0f);
+        mMenuView.setScaleY(MENU_INITIAL_SCALE);
+        mMenuView.setOnCloseListener(() -> hideMenu(true  /* animated */));
+        if (mBubble != null) {
+            mMenuView.updateInfo(mBubble);
+            mMenuView.updateActions(createMenuActions(mBubble));
+        }
+        // Scrim view setup
+        mScrimView = new View(mContext);
+        mScrimView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+        mScrimView.setOnClickListener(view -> hideMenu(true  /* animated */));
+        // Attach to root view
+        mRootView.addView(mScrimView);
+        mRootView.addView(mMenuView);
+    }
+
+    /**
+     * Creates menu actions to populate menu view
+     * @param bubble used to create actions depending on bubble type
+     */
+    private ArrayList<BubbleBarMenuView.MenuAction> createMenuActions(Bubble bubble) {
+        ArrayList<BubbleBarMenuView.MenuAction> menuActions = new ArrayList<>();
+        Resources resources = mContext.getResources();
+
+        if (bubble.isConversation()) {
+            // Don't bubble conversation action
+            menuActions.add(new BubbleBarMenuView.MenuAction(
+                    Icon.createWithResource(mContext, R.drawable.bubble_ic_stop_bubble),
+                    resources.getString(R.string.bubbles_dont_bubble_conversation),
+                    view -> {
+                        hideMenu(true /* animated */);
+                        if (mListener != null) {
+                            mListener.onUnBubbleConversation(bubble);
+                        }
+                    }
+            ));
+            // Open settings action
+            Icon appIcon = bubble.getRawAppBadge() != null ? Icon.createWithBitmap(
+                    bubble.getRawAppBadge()) : null;
+            menuActions.add(new BubbleBarMenuView.MenuAction(
+                    appIcon,
+                    resources.getString(R.string.bubbles_app_settings, bubble.getAppName()),
+                    view -> {
+                        hideMenu(true /* animated */);
+                        if (mListener != null) {
+                            mListener.onOpenAppSettings(bubble);
+                        }
+                    }
+            ));
+        }
+
+        // Dismiss bubble action
+        menuActions.add(new BubbleBarMenuView.MenuAction(
+                Icon.createWithResource(resources, R.drawable.ic_remove_no_shadow),
+                resources.getString(R.string.bubble_dismiss_text),
+                ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_menu_close),
+                view -> {
+                    hideMenu(true /* animated */);
+                    if (mListener != null) {
+                        mListener.onDismissBubble(bubble);
+                    }
+                }
+        ));
+
+        return menuActions;
+    }
+
+    /**
+     * Bubble bar expanded view menu actions listener
+     */
+    interface Listener {
+        /**
+         * Called when manage menu is shown/hidden
+         * If animated will be called when animation ends
+         */
+        void onMenuVisibilityChanged(boolean visible);
+
+        /**
+         * Un-bubbles conversation and removes the bubble from the stack
+         * This conversation will not be bubbled with new messages
+         * @see com.android.wm.shell.bubbles.BubbleController
+         */
+        void onUnBubbleConversation(Bubble bubble);
+
+        /**
+         * Launches app notification bubble settings for the bubble with intent created in:
+         * {@code Bubble.getSettingsIntent}
+         */
+        void onOpenAppSettings(Bubble bubble);
+
+        /**
+         * Dismiss bubble and remove it from the bubble stack
+         */
+        void onDismissBubble(Bubble bubble);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java
deleted file mode 100644
index 9ee8a9d..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/HandleView.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.wm.shell.bubbles.bar;
-
-import android.content.Context;
-import android.view.Gravity;
-import android.widget.LinearLayout;
-
-/**
- * Handle / menu view to show at the top of a bubble bar expanded view.
- */
-public class HandleView extends LinearLayout {
-
-    // TODO(b/273307221): implement the manage menu in this view.
-    public HandleView(Context context) {
-        super(context);
-        setOrientation(LinearLayout.HORIZONTAL);
-        setGravity(Gravity.CENTER);
-    }
-
-    /**
-     * The menu extends past the top of the TaskView because of the rounded corners. This means
-     * to center content in the menu we must subtract the radius (i.e. the amount of space covered
-     * by TaskView).
-     */
-    public void setCornerRadius(float radius) {
-        setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
new file mode 100644
index 0000000..85aaa8e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles.properties
+
+/**
+ * An interface for exposing bubble properties via flags which can be controlled easily in tests.
+ */
+interface BubbleProperties {
+    /**
+     * Whether bubble bar is enabled.
+     *
+     * When this is `true`, depending on additional factors, such as screen size and taskbar state,
+     * bubbles will be displayed in the bubble bar instead of floating.
+     *
+     * When this is `false`, bubbles will be floating.
+     */
+    val isBubbleBarEnabled: Boolean
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
new file mode 100644
index 0000000..9d8b9a6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles.properties
+
+import android.os.SystemProperties
+
+/** Provides bubble properties in production. */
+object ProdBubbleProperties : BubbleProperties {
+
+    // TODO(b/256873975) Should use proper flag when available to shell/launcher
+    override val isBubbleBarEnabled =
+        SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java
deleted file mode 100644
index e0c782d..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java
+++ /dev/null
@@ -1,64 +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.wm.shell.common;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.view.Gravity;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-import com.android.wm.shell.R;
-
-/**
- * Circular view with a semitransparent, circular background with an 'X' inside it.
- *
- * This is used by both Bubbles and PIP as the dismiss target.
- */
-public class DismissCircleView extends FrameLayout {
-
-    private final ImageView mIconView = new ImageView(getContext());
-
-    public DismissCircleView(Context context) {
-        super(context);
-        final Resources res = getResources();
-
-        setBackground(res.getDrawable(R.drawable.dismiss_circle_background));
-
-        mIconView.setImageDrawable(res.getDrawable(R.drawable.pip_ic_close_white));
-        addView(mIconView);
-
-        setViewSizes();
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        final Resources res = getResources();
-        setBackground(res.getDrawable(R.drawable.dismiss_circle_background));
-        setViewSizes();
-    }
-
-    /** Retrieves the current dimensions for the icon and circle and applies them. */
-    private void setViewSizes() {
-        final Resources res = getResources();
-        final int iconSize = res.getDimensionPixelSize(R.dimen.dismiss_target_x_size);
-        mIconView.setLayoutParams(
-                new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER));
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
new file mode 100644
index 0000000..81592c3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * Controller to manage behavior of activities launched with
+ * [android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT].
+ */
+class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) {
+
+    /** Allows to temporarily disable launch adjacent handling */
+    var launchAdjacentEnabled: Boolean = true
+        set(value) {
+            if (field != value) {
+                KtProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value)
+                field = value
+                container?.let { c ->
+                    if (value) {
+                        enableContainer(c)
+                    } else {
+                        disableContainer((c))
+                    }
+                }
+            }
+        }
+    private var container: WindowContainerToken? = null
+
+    /**
+     * Set [container] as the new launch adjacent flag root container.
+     *
+     * If launch adjacent handling is disabled through [setLaunchAdjacentEnabled], won't set the
+     * container until after it is enabled again.
+     *
+     * @see WindowContainerTransaction.setLaunchAdjacentFlagRoot
+     */
+    fun setLaunchAdjacentRoot(container: WindowContainerToken) {
+        KtProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container")
+        this.container = container
+        if (launchAdjacentEnabled) {
+            enableContainer(container)
+        }
+    }
+
+    /**
+     * Clear a container previously set through [setLaunchAdjacentRoot].
+     *
+     * Always clears the container, regardless of [launchAdjacentEnabled] value.
+     *
+     * @see WindowContainerTransaction.clearLaunchAdjacentFlagRoot
+     */
+    fun clearLaunchAdjacentRoot() {
+        KtProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container")
+        container?.let {
+            disableContainer(it)
+            container = null
+        }
+    }
+
+    private fun enableContainer(container: WindowContainerToken) {
+        KtProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container")
+        val wct = WindowContainerTransaction()
+        wct.setLaunchAdjacentFlagRoot(container)
+        syncQueue.queue(wct)
+    }
+
+    private fun disableContainer(container: WindowContainerToken) {
+        KtProtoLog.v(WM_SHELL_TASK_ORG, "disable launch adjacent flag root container")
+        val wct = WindowContainerTransaction()
+        wct.clearLaunchAdjacentFlagRoot(container)
+        syncQueue.queue(wct)
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
new file mode 100644
index 0000000..7af0389
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS
@@ -0,0 +1 @@
+madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
index 21355a3..24608d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
@@ -129,6 +129,11 @@
         return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION) != 0;
     }
 
+    /** Sets the flags for this bubble. */
+    public void setFlags(int flags) {
+        mFlags = flags;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java
new file mode 100644
index 0000000..7c5bb21
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java
@@ -0,0 +1,76 @@
+/*
+ * 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.wm.shell.common.bubbles;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.view.Gravity;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import androidx.annotation.DimenRes;
+import androidx.annotation.DrawableRes;
+import androidx.core.content.ContextCompat;
+
+/**
+ * Circular view with a semitransparent, circular background with an 'X' inside it.
+ *
+ * This is used by both Bubbles and PIP as the dismiss target.
+ */
+public class DismissCircleView extends FrameLayout {
+    @DrawableRes int mBackgroundResId;
+    @DimenRes int mIconSizeResId;
+
+    private final ImageView mIconView = new ImageView(getContext());
+
+    public DismissCircleView(Context context) {
+        super(context);
+        addView(mIconView);
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        setBackground(ContextCompat.getDrawable(getContext(), mBackgroundResId));
+        setViewSizes();
+    }
+
+    /**
+     * Sets up view with the provided resource ids.
+     * Decouples resource dependency in order to be used externally (e.g. Launcher)
+     *
+     * @param backgroundResId drawable resource id of the circle background
+     * @param iconResId drawable resource id of the icon for the dismiss view
+     * @param iconSizeResId dimen resource id of the icon size
+     */
+    public void setup(@DrawableRes int backgroundResId, @DrawableRes int iconResId,
+            @DimenRes int iconSizeResId) {
+        mBackgroundResId = backgroundResId;
+        mIconSizeResId = iconSizeResId;
+
+        setBackground(ContextCompat.getDrawable(getContext(), backgroundResId));
+        mIconView.setImageDrawable(ContextCompat.getDrawable(getContext(), iconResId));
+        setViewSizes();
+    }
+
+    /** Retrieves the current dimensions for the icon and circle and applies them. */
+    private void setViewSizes() {
+        final int iconSize = getResources().getDimensionPixelSize(mIconSizeResId);
+        mIconView.setLayoutParams(
+                new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER));
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
new file mode 100644
index 0000000..d275a0b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
@@ -0,0 +1,222 @@
+/*
+ * 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.wm.shell.common.bubbles
+
+import android.animation.ObjectAnimator
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.GradientDrawable
+import android.util.IntProperty
+import android.util.Log
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.widget.FrameLayout
+import androidx.annotation.ColorRes
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.core.content.ContextCompat
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
+import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
+import com.android.wm.shell.animation.PhysicsAnimator
+
+/**
+ * View that handles interactions between DismissCircleView and BubbleStackView.
+ *
+ * @note [setup] method should be called after initialisation
+ */
+class DismissView(context: Context) : FrameLayout(context) {
+    /**
+     * The configuration is used to provide module specific resource ids
+     *
+     * @see [setup] method
+     */
+    data class Config(
+            /** dimen resource id of the dismiss target circle view size */
+            @DimenRes val targetSizeResId: Int,
+            /** dimen resource id of the icon size in the dismiss target */
+            @DimenRes val iconSizeResId: Int,
+            /** dimen resource id of the bottom margin for the dismiss target */
+            @DimenRes var bottomMarginResId: Int,
+            /** dimen resource id of the height for dismiss area gradient */
+            @DimenRes val floatingGradientHeightResId: Int,
+            /** color resource id of the dismiss area gradient color */
+            @ColorRes val floatingGradientColorResId: Int,
+            /** drawable resource id of the dismiss target background */
+            @DrawableRes val backgroundResId: Int,
+            /** drawable resource id of the icon for the dismiss target */
+            @DrawableRes val iconResId: Int
+    )
+
+    companion object {
+        private const val SHOULD_SETUP =
+                "The view isn't ready. Should be called after `setup`"
+        private val TAG = DismissView::class.simpleName
+    }
+
+    var circle = DismissCircleView(context)
+    var isShowing = false
+    var config: Config? = null
+
+    private val animator = PhysicsAnimator.getInstance(circle)
+    private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
+    private val DISMISS_SCRIM_FADE_MS = 200L
+    private var wm: WindowManager =
+            context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+    private var gradientDrawable: GradientDrawable? = null
+
+    private val GRADIENT_ALPHA: IntProperty<GradientDrawable> =
+            object : IntProperty<GradientDrawable>("alpha") {
+        override fun setValue(d: GradientDrawable, percent: Int) {
+            d.alpha = percent
+        }
+        override fun get(d: GradientDrawable): Int {
+            return d.alpha
+        }
+    }
+
+    init {
+        setClipToPadding(false)
+        setClipChildren(false)
+        setVisibility(View.INVISIBLE)
+        addView(circle)
+    }
+
+    /**
+     * Sets up view with the provided resource ids.
+     *
+     * Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called
+     * with default params in module specific extension:
+     * @see [DismissView.setup] in DismissViewExt.kt
+     */
+    fun setup(config: Config) {
+        this.config = config
+
+        // Setup layout
+        layoutParams = LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                resources.getDimensionPixelSize(config.floatingGradientHeightResId),
+                Gravity.BOTTOM)
+        updatePadding()
+
+        // Setup gradient
+        gradientDrawable = createGradient(color = config.floatingGradientColorResId)
+        setBackgroundDrawable(gradientDrawable)
+
+        // Setup DismissCircleView
+        circle.setup(config.backgroundResId, config.iconResId, config.iconSizeResId)
+        val targetSize: Int = resources.getDimensionPixelSize(config.targetSizeResId)
+        circle.layoutParams = LayoutParams(targetSize, targetSize,
+                Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
+        // Initial position with circle offscreen so it's animated up
+        circle.translationY = resources.getDimensionPixelSize(config.floatingGradientHeightResId)
+                .toFloat()
+    }
+
+    /**
+     * Animates this view in.
+     */
+    fun show() {
+        if (isShowing) return
+        val gradientDrawable = checkExists(gradientDrawable) ?: return
+        isShowing = true
+        setVisibility(View.VISIBLE)
+        val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
+                gradientDrawable.alpha, 255)
+        alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS)
+        alphaAnim.start()
+
+        animator.cancel()
+        animator
+            .spring(DynamicAnimation.TRANSLATION_Y, 0f, spring)
+            .start()
+    }
+
+    /**
+     * Animates this view out, as well as the circle that encircles the bubbles, if they
+     * were dragged into the target and encircled.
+     */
+    fun hide() {
+        if (!isShowing) return
+        val gradientDrawable = checkExists(gradientDrawable) ?: return
+        isShowing = false
+        val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA,
+                gradientDrawable.alpha, 0)
+        alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS)
+        alphaAnim.start()
+        animator
+            .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(),
+                spring)
+            .withEndActions({ setVisibility(View.INVISIBLE) })
+            .start()
+    }
+
+    /**
+     * Cancels the animator for the dismiss target.
+     */
+    fun cancelAnimators() {
+        animator.cancel()
+    }
+
+    fun updateResources() {
+        val config = checkExists(config) ?: return
+        updatePadding()
+        layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId)
+        val targetSize = resources.getDimensionPixelSize(config.targetSizeResId)
+        circle.layoutParams.width = targetSize
+        circle.layoutParams.height = targetSize
+        circle.requestLayout()
+    }
+
+    private fun createGradient(@ColorRes color: Int): GradientDrawable {
+        val gradientColor = ContextCompat.getColor(context, color)
+        val alpha = 0.7f * 255
+        val gradientColorWithAlpha = Color.argb(alpha.toInt(),
+                Color.red(gradientColor),
+                Color.green(gradientColor),
+                Color.blue(gradientColor))
+        val gd = GradientDrawable(
+                GradientDrawable.Orientation.BOTTOM_TOP,
+                intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT))
+        gd.setDither(true)
+        gd.setAlpha(0)
+        return gd
+    }
+
+    private fun updatePadding() {
+        val config = checkExists(config) ?: return
+        val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets()
+        val navInset = insets.getInsetsIgnoringVisibility(
+                WindowInsets.Type.navigationBars())
+        setPadding(0, 0, 0, navInset.bottom +
+                resources.getDimensionPixelSize(config.bottomMarginResId))
+    }
+
+    /**
+     * Checks if the value is set up and exists, if not logs an exception.
+     * Used for convenient logging in case `setup` wasn't called before
+     *
+     * @return value provided as argument
+     */
+    private fun <T>checkExists(value: T?): T? {
+        if (value == null) Log.e(TAG, SHOULD_SETUP)
+        return value
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
new file mode 100644
index 0000000..cc37bd3a4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
@@ -0,0 +1,168 @@
+/*
+ * 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.wm.shell.common.bubbles
+
+import android.graphics.PointF
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import android.view.View
+import android.view.ViewConfiguration
+import kotlin.math.hypot
+
+/**
+ * Listener which receives [onDown], [onMove], and [onUp] events, with relevant information about
+ * the coordinates of the touch and the view relative to the initial ACTION_DOWN event and the
+ * view's initial position.
+ */
+abstract class RelativeTouchListener : View.OnTouchListener {
+
+    /**
+     * Called when an ACTION_DOWN event is received for the given view.
+     *
+     * @return False if the object is not interested in MotionEvents at this time, or true if we
+     * should consume this event and subsequent events, and begin calling [onMove].
+     */
+    abstract fun onDown(v: View, ev: MotionEvent): Boolean
+
+    /**
+     * Called when an ACTION_MOVE event is received for the given view. This signals that the view
+     * is being dragged.
+     *
+     * @param viewInitialX The view's translationX value when this touch gesture started.
+     * @param viewInitialY The view's translationY value when this touch gesture started.
+     * @param dx Horizontal distance covered since the initial ACTION_DOWN event, in pixels.
+     * @param dy Vertical distance covered since the initial ACTION_DOWN event, in pixels.
+     */
+    abstract fun onMove(
+        v: View,
+        ev: MotionEvent,
+        viewInitialX: Float,
+        viewInitialY: Float,
+        dx: Float,
+        dy: Float
+    )
+
+    /**
+     * Called when an ACTION_UP event is received for the given view. This signals that a drag or
+     * fling gesture has completed.
+     *
+     * @param viewInitialX The view's translationX value when this touch gesture started.
+     * @param viewInitialY The view's translationY value when this touch gesture started.
+     * @param dx Horizontal distance covered, in pixels.
+     * @param dy Vertical distance covered, in pixels.
+     * @param velX The final horizontal velocity of the gesture, in pixels/second.
+     * @param velY The final vertical velocity of the gesture, in pixels/second.
+     */
+    abstract fun onUp(
+        v: View,
+        ev: MotionEvent,
+        viewInitialX: Float,
+        viewInitialY: Float,
+        dx: Float,
+        dy: Float,
+        velX: Float,
+        velY: Float
+    )
+
+    /** The raw coordinates of the last ACTION_DOWN event. */
+    private val touchDown = PointF()
+
+    /** The coordinates of the view, at the time of the last ACTION_DOWN event. */
+    private val viewPositionOnTouchDown = PointF()
+
+    private val velocityTracker = VelocityTracker.obtain()
+
+    private var touchSlop: Int = -1
+    private var movedEnough = false
+
+    private var performedLongClick = false
+
+    @Suppress("UNCHECKED_CAST")
+    override fun onTouch(v: View, ev: MotionEvent): Boolean {
+        addMovement(ev)
+
+        val dx = ev.rawX - touchDown.x
+        val dy = ev.rawY - touchDown.y
+
+        when (ev.action) {
+            MotionEvent.ACTION_DOWN -> {
+                if (!onDown(v, ev)) {
+                    return false
+                }
+
+                // Grab the touch slop, it might have changed if the config changed since the
+                // last gesture.
+                touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop
+
+                touchDown.set(ev.rawX, ev.rawY)
+                viewPositionOnTouchDown.set(v.translationX, v.translationY)
+
+                performedLongClick = false
+                v.handler.postDelayed({
+                    if (v.isLongClickable) {
+                        performedLongClick = v.performLongClick()
+                    }
+                }, ViewConfiguration.getLongPressTimeout().toLong())
+            }
+
+            MotionEvent.ACTION_MOVE -> {
+                if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
+                    movedEnough = true
+                    v.handler.removeCallbacksAndMessages(null)
+                }
+
+                if (movedEnough) {
+                    onMove(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy)
+                }
+            }
+
+            MotionEvent.ACTION_UP -> {
+                if (movedEnough) {
+                    velocityTracker.computeCurrentVelocity(1000 /* units */)
+                    onUp(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy,
+                            velocityTracker.xVelocity, velocityTracker.yVelocity)
+                } else if (!performedLongClick) {
+                    v.performClick()
+                } else {
+                    v.handler.removeCallbacksAndMessages(null)
+                }
+
+                velocityTracker.clear()
+                movedEnough = false
+            }
+
+            MotionEvent.ACTION_CANCEL -> {
+                v.handler.removeCallbacksAndMessages(null)
+                velocityTracker.clear()
+                movedEnough = false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Adds a movement to the velocity tracker using raw screen coordinates.
+     */
+    private fun addMovement(event: MotionEvent) {
+        val deltaX = event.rawX - event.x
+        val deltaY = event.rawY - event.y
+        event.offsetLocation(deltaX, deltaY)
+        velocityTracker.addMovement(event)
+        event.offsetLocation(-deltaX, -deltaY)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
new file mode 100644
index 0000000..fd000ee
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.PointF
+import android.util.Size
+import com.android.wm.shell.R
+import com.android.wm.shell.pip.PipDisplayLayoutState
+
+class LegacySizeSpecSource(
+        private val context: Context,
+        private val pipDisplayLayoutState: PipDisplayLayoutState
+) : SizeSpecSource {
+
+    private var mDefaultMinSize = 0
+    /** The absolute minimum an overridden size's edge can be */
+    private var mOverridableMinSize = 0
+    /** The preferred minimum (and default minimum) size specified by apps.  */
+    private var mOverrideMinSize: Size? = null
+
+    private var mDefaultSizePercent = 0f
+    private var mMinimumSizePercent = 0f
+    private var mMaxAspectRatioForMinSize = 0f
+    private var mMinAspectRatioForMinSize = 0f
+
+    init {
+        reloadResources()
+    }
+
+    private fun reloadResources() {
+        val res: Resources = context.getResources()
+
+        mDefaultMinSize = res.getDimensionPixelSize(
+                R.dimen.default_minimal_size_pip_resizable_task)
+        mOverridableMinSize = res.getDimensionPixelSize(
+                R.dimen.overridable_minimal_size_pip_resizable_task)
+
+        mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent)
+        mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1)
+
+        mMaxAspectRatioForMinSize = res.getFloat(
+                R.dimen.config_pictureInPictureAspectRatioLimitForMinSize)
+        mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize
+    }
+
+    override fun onConfigurationChanged() {
+        reloadResources()
+    }
+
+    override fun getMaxSize(aspectRatio: Float): Size {
+        val insetBounds = pipDisplayLayoutState.insetBounds
+
+        val shorterLength: Int = Math.min(getDisplayBounds().width(),
+                getDisplayBounds().height())
+        val totalHorizontalPadding: Int = (insetBounds.left +
+                (getDisplayBounds().width() - insetBounds.right))
+        val totalVerticalPadding: Int = (insetBounds.top +
+                (getDisplayBounds().height() - insetBounds.bottom))
+
+        return if (aspectRatio > 1f) {
+            val maxWidth = Math.max(getDefaultSize(aspectRatio).width,
+                    shorterLength - totalHorizontalPadding)
+            val maxHeight = (maxWidth / aspectRatio).toInt()
+            Size(maxWidth, maxHeight)
+        } else {
+            val maxHeight = Math.max(getDefaultSize(aspectRatio).height,
+                    shorterLength - totalVerticalPadding)
+            val maxWidth = (maxHeight * aspectRatio).toInt()
+            Size(maxWidth, maxHeight)
+        }
+    }
+
+    override fun getDefaultSize(aspectRatio: Float): Size {
+        if (mOverrideMinSize != null) {
+            return getMinSize(aspectRatio)
+        }
+        val smallestDisplaySize: Int = Math.min(getDisplayBounds().width(),
+                getDisplayBounds().height())
+        val minSize = Math.max(getMinEdgeSize().toFloat(),
+                smallestDisplaySize * mDefaultSizePercent).toInt()
+        val width: Int
+        val height: Int
+        if (aspectRatio <= mMinAspectRatioForMinSize ||
+                aspectRatio > mMaxAspectRatioForMinSize) {
+            // Beyond these points, we can just use the min size as the shorter edge
+            if (aspectRatio <= 1) {
+                // Portrait, width is the minimum size
+                width = minSize
+                height = Math.round(width / aspectRatio)
+            } else {
+                // Landscape, height is the minimum size
+                height = minSize
+                width = Math.round(height * aspectRatio)
+            }
+        } else {
+            // Within these points, ensure that the bounds fit within the radius of the limits
+            // at the points
+            val widthAtMaxAspectRatioForMinSize: Float = mMaxAspectRatioForMinSize * minSize
+            val radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize.toFloat())
+            height = Math.round(Math.sqrt((radius * radius /
+                    (aspectRatio * aspectRatio + 1)).toDouble())).toInt()
+            width = Math.round(height * aspectRatio)
+        }
+        return Size(width, height)
+    }
+
+    override fun getMinSize(aspectRatio: Float): Size {
+        if (mOverrideMinSize != null) {
+            return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
+        }
+        val shorterLength: Int = Math.min(getDisplayBounds().width(),
+                getDisplayBounds().height())
+        val minWidth: Int
+        val minHeight: Int
+        if (aspectRatio > 1f) {
+            minWidth = Math.min(getDefaultSize(aspectRatio).width.toFloat(),
+                    shorterLength * mMinimumSizePercent).toInt()
+            minHeight = (minWidth / aspectRatio).toInt()
+        } else {
+            minHeight = Math.min(getDefaultSize(aspectRatio).height.toFloat(),
+                    shorterLength * mMinimumSizePercent).toInt()
+            minWidth = (minHeight * aspectRatio).toInt()
+        }
+        return Size(minWidth, minHeight)
+    }
+
+    override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size {
+        val smallestSize = Math.min(size.width, size.height)
+        val minSize = Math.max(getMinEdgeSize(), smallestSize)
+        val width: Int
+        val height: Int
+        if (aspectRatio <= 1) {
+            // Portrait, width is the minimum size.
+            width = minSize
+            height = Math.round(width / aspectRatio)
+        } else {
+            // Landscape, height is the minimum size
+            height = minSize
+            width = Math.round(height * aspectRatio)
+        }
+        return Size(width, height)
+    }
+
+    private fun getDisplayBounds() = pipDisplayLayoutState.displayBounds
+
+    /** Sets the preferred size of PIP as specified by the activity in PIP mode.  */
+    override fun setOverrideMinSize(overrideMinSize: Size?) {
+        mOverrideMinSize = overrideMinSize
+    }
+
+    /** Returns the preferred minimal size specified by the activity in PIP.  */
+    override fun getOverrideMinSize(): Size? {
+        val overrideMinSize = mOverrideMinSize ?: return null
+        return if (overrideMinSize.width < mOverridableMinSize ||
+                overrideMinSize.height < mOverridableMinSize) {
+            Size(mOverridableMinSize, mOverridableMinSize)
+        } else {
+            overrideMinSize
+        }
+    }
+
+    private fun getMinEdgeSize(): Int {
+        return if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize()
+    }
+
+    /**
+     * Returns the adjusted overridden min size if it is set; otherwise, returns null.
+     *
+     *
+     * Overridden min size needs to be adjusted in its own way while making sure that the target
+     * aspect ratio is maintained
+     *
+     * @param aspectRatio target aspect ratio
+     */
+    private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? {
+        val size = getOverrideMinSize() ?: return null
+        val sizeAspectRatio = size.width / size.height.toFloat()
+        return if (sizeAspectRatio > aspectRatio) {
+            // Size is wider, fix the width and increase the height
+            Size(size.width, (size.width / aspectRatio).toInt())
+        } else {
+            // Size is taller, fix the height and adjust the width.
+            Size((size.height * aspectRatio).toInt(), size.height)
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
new file mode 100644
index 0000000..c563068
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip
+
+import android.content.Context
+import android.content.res.Resources
+import android.os.SystemProperties
+import android.util.Size
+import com.android.wm.shell.R
+import com.android.wm.shell.pip.PipDisplayLayoutState
+import java.io.PrintWriter
+
+class PhoneSizeSpecSource(
+        private val context: Context,
+        private val pipDisplayLayoutState: PipDisplayLayoutState
+) : SizeSpecSource {
+    private var DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16
+
+    private var mDefaultMinSize = 0
+    /** The absolute minimum an overridden size's edge can be */
+    private var mOverridableMinSize = 0
+    /** The preferred minimum (and default minimum) size specified by apps.  */
+    private var mOverrideMinSize: Size? = null
+
+
+    /** Default and minimum percentages for the PIP size logic.  */
+    private val mDefaultSizePercent: Float
+    private val mMinimumSizePercent: Float
+
+    /** Aspect ratio that the PIP size spec logic optimizes for.  */
+    private var mOptimizedAspectRatio = 0f
+
+    init {
+        mDefaultSizePercent = SystemProperties
+                .get("com.android.wm.shell.pip.phone.def_percentage", "0.6").toFloat()
+        mMinimumSizePercent = SystemProperties
+                .get("com.android.wm.shell.pip.phone.min_percentage", "0.5").toFloat()
+
+        reloadResources()
+    }
+
+    private fun reloadResources() {
+        val res: Resources = context.getResources()
+
+        mDefaultMinSize = res.getDimensionPixelSize(
+                R.dimen.default_minimal_size_pip_resizable_task)
+        mOverridableMinSize = res.getDimensionPixelSize(
+                R.dimen.overridable_minimal_size_pip_resizable_task)
+
+        val requestedOptAspRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio)
+        // make sure the optimized aspect ratio is valid with a default value to fall back to
+        mOptimizedAspectRatio = if (requestedOptAspRatio > 1) {
+            DEFAULT_OPTIMIZED_ASPECT_RATIO
+        } else {
+            requestedOptAspRatio
+        }
+    }
+
+    override fun onConfigurationChanged() {
+        reloadResources()
+    }
+
+    /**
+     * Calculates the max size of PIP.
+     *
+     * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge.
+     * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the
+     * whole screen. A linear function is used to calculate these sizes.
+     *
+     * @param aspectRatio aspect ratio of the PIP window
+     * @return dimensions of the max size of the PIP
+     */
+    override fun getMaxSize(aspectRatio: Float): Size {
+        val insetBounds = pipDisplayLayoutState.insetBounds
+        val displayBounds = pipDisplayLayoutState.displayBounds
+
+        val totalHorizontalPadding: Int = (insetBounds.left +
+                (displayBounds.width() - insetBounds.right))
+        val totalVerticalPadding: Int = (insetBounds.top +
+                (displayBounds.height() - insetBounds.bottom))
+        val shorterLength: Int = Math.min(displayBounds.width() - totalHorizontalPadding,
+                displayBounds.height() - totalVerticalPadding)
+        var maxWidth: Int
+        val maxHeight: Int
+
+        // use the optimized max sizing logic only within a certain aspect ratio range
+        if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) {
+            // this formula and its derivation is explained in b/198643358#comment16
+            maxWidth = Math.round(mOptimizedAspectRatio * shorterLength +
+                    shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1 + aspectRatio))
+            // make sure the max width doesn't go beyond shorter screen length after rounding
+            maxWidth = Math.min(maxWidth, shorterLength)
+            maxHeight = Math.round(maxWidth / aspectRatio)
+        } else {
+            if (aspectRatio > 1f) {
+                maxWidth = shorterLength
+                maxHeight = Math.round(maxWidth / aspectRatio)
+            } else {
+                maxHeight = shorterLength
+                maxWidth = Math.round(maxHeight * aspectRatio)
+            }
+        }
+        return Size(maxWidth, maxHeight)
+    }
+
+    /**
+     * Decreases the dimensions by a percentage relative to max size to get default size.
+     *
+     * @param aspectRatio aspect ratio of the PIP window
+     * @return dimensions of the default size of the PIP
+     */
+    override fun getDefaultSize(aspectRatio: Float): Size {
+        val minSize = getMinSize(aspectRatio)
+        if (mOverrideMinSize != null) {
+            return minSize
+        }
+        val maxSize = getMaxSize(aspectRatio)
+        val defaultWidth = Math.max(Math.round(maxSize.width * mDefaultSizePercent),
+                minSize.width)
+        val defaultHeight = Math.round(defaultWidth / aspectRatio)
+        return Size(defaultWidth, defaultHeight)
+    }
+
+    /**
+     * Decreases the dimensions by a certain percentage relative to max size to get min size.
+     *
+     * @param aspectRatio aspect ratio of the PIP window
+     * @return dimensions of the min size of the PIP
+     */
+    override fun getMinSize(aspectRatio: Float): Size {
+        // if there is an overridden min size provided, return that
+        if (mOverrideMinSize != null) {
+            return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
+        }
+        val maxSize = getMaxSize(aspectRatio)
+        var minWidth = Math.round(maxSize.width * mMinimumSizePercent)
+        var minHeight = Math.round(maxSize.height * mMinimumSizePercent)
+
+        // make sure the calculated min size is not smaller than the allowed default min size
+        if (aspectRatio > 1f) {
+            minHeight = Math.max(minHeight, mDefaultMinSize)
+            minWidth = Math.round(minHeight * aspectRatio)
+        } else {
+            minWidth = Math.max(minWidth, mDefaultMinSize)
+            minHeight = Math.round(minWidth / aspectRatio)
+        }
+        return Size(minWidth, minHeight)
+    }
+
+    /**
+     * Returns the size for target aspect ratio making sure new size conforms with the rules.
+     *
+     *
+     * Recalculates the dimensions such that the target aspect ratio is achieved, while
+     * maintaining the same maximum size to current size ratio.
+     *
+     * @param size current size
+     * @param aspectRatio target aspect ratio
+     */
+    override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size {
+        if (size == mOverrideMinSize) {
+            return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
+        }
+
+        val currAspectRatio = size.width.toFloat() / size.height
+
+        // getting the percentage of the max size that current size takes
+        val currentMaxSize = getMaxSize(currAspectRatio)
+        val currentPercent = size.width.toFloat() / currentMaxSize.width
+
+        // getting the max size for the target aspect ratio
+        val updatedMaxSize = getMaxSize(aspectRatio)
+        var width = Math.round(updatedMaxSize.width * currentPercent)
+        var height = Math.round(updatedMaxSize.height * currentPercent)
+
+        // adjust the dimensions if below allowed min edge size
+        val minEdgeSize =
+                if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize()
+
+        if (width < minEdgeSize && aspectRatio <= 1) {
+            width = minEdgeSize
+            height = Math.round(width / aspectRatio)
+        } else if (height < minEdgeSize && aspectRatio > 1) {
+            height = minEdgeSize
+            width = Math.round(height * aspectRatio)
+        }
+
+        // reduce the dimensions of the updated size to the calculated percentage
+        return Size(width, height)
+    }
+
+    /** Sets the preferred size of PIP as specified by the activity in PIP mode.  */
+    override fun setOverrideMinSize(overrideMinSize: Size?) {
+        mOverrideMinSize = overrideMinSize
+    }
+
+    /** Returns the preferred minimal size specified by the activity in PIP.  */
+    override fun getOverrideMinSize(): Size? {
+        val overrideMinSize = mOverrideMinSize ?: return null
+        return if (overrideMinSize.width < mOverridableMinSize ||
+                overrideMinSize.height < mOverridableMinSize) {
+            Size(mOverridableMinSize, mOverridableMinSize)
+        } else {
+            overrideMinSize
+        }
+    }
+
+    /**
+     * Returns the adjusted overridden min size if it is set; otherwise, returns null.
+     *
+     *
+     * Overridden min size needs to be adjusted in its own way while making sure that the target
+     * aspect ratio is maintained
+     *
+     * @param aspectRatio target aspect ratio
+     */
+    private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? {
+        val size = getOverrideMinSize() ?: return null
+        val sizeAspectRatio = size.width / size.height.toFloat()
+        return if (sizeAspectRatio > aspectRatio) {
+            // Size is wider, fix the width and increase the height
+            Size(size.width, (size.width / aspectRatio).toInt())
+        } else {
+            // Size is taller, fix the height and adjust the width.
+            Size((size.height * aspectRatio).toInt(), size.height)
+        }
+    }
+
+    override fun dump(pw: PrintWriter, prefix: String) {
+        val innerPrefix = "$prefix  "
+        pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize)
+        pw.println(innerPrefix + "mOverridableMinSize=" + mOverridableMinSize)
+        pw.println(innerPrefix + "mDefaultMinSize=" + mDefaultMinSize)
+        pw.println(innerPrefix + "mDefaultSizePercent=" + mDefaultSizePercent)
+        pw.println(innerPrefix + "mMinimumSizePercent=" + mMinimumSizePercent)
+        pw.println(innerPrefix + "mOptimizedAspectRatio=" + mOptimizedAspectRatio)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
new file mode 100644
index 0000000..a141ff9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common.pip
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.PackageManager
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.pip.PipUtils
+
+class PipAppOpsListener(
+    private val mContext: Context,
+    private val mCallback: Callback,
+    private val mMainExecutor: ShellExecutor
+) {
+    private val mAppOpsManager: AppOpsManager = checkNotNull(
+        mContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager)
+    private val mAppOpsChangedListener = AppOpsManager.OnOpChangedListener { _, packageName ->
+        try {
+            // Dismiss the PiP once the user disables the app ops setting for that package
+            val topPipActivityInfo = PipUtils.getTopPipActivity(mContext)
+            val componentName = topPipActivityInfo.first ?: return@OnOpChangedListener
+            val userId = topPipActivityInfo.second
+            val appInfo = mContext.packageManager
+                .getApplicationInfoAsUser(packageName, 0, userId)
+            if (appInfo.packageName == componentName.packageName &&
+                mAppOpsManager.checkOpNoThrow(
+                    AppOpsManager.OP_PICTURE_IN_PICTURE, appInfo.uid,
+                    packageName
+                ) != AppOpsManager.MODE_ALLOWED
+            ) {
+                mMainExecutor.execute { mCallback.dismissPip() }
+            }
+        } catch (e: PackageManager.NameNotFoundException) {
+            // Unregister the listener if the package can't be found
+            unregisterAppOpsListener()
+        }
+    }
+
+    fun onActivityPinned(packageName: String) {
+        // Register for changes to the app ops setting for this package while it is in PiP
+        registerAppOpsListener(packageName)
+    }
+
+    fun onActivityUnpinned() {
+        // Unregister for changes to the previously PiP'ed package
+        unregisterAppOpsListener()
+    }
+
+    private fun registerAppOpsListener(packageName: String) {
+        mAppOpsManager.startWatchingMode(
+            AppOpsManager.OP_PICTURE_IN_PICTURE, packageName,
+            mAppOpsChangedListener
+        )
+    }
+
+    private fun unregisterAppOpsListener() {
+        mAppOpsManager.stopWatchingMode(mAppOpsChangedListener)
+    }
+
+    /** Callback for PipAppOpsListener to request changes to the PIP window.  */
+    interface Callback {
+        /** Dismisses the PIP window.  */
+        fun dismissPip()
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt
new file mode 100644
index 0000000..7b3b9ef
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip
+
+import android.util.Size
+import java.io.PrintWriter
+
+interface SizeSpecSource {
+    /** Returns max size allowed for the PIP window  */
+    fun getMaxSize(aspectRatio: Float): Size
+
+    /** Returns default size for the PIP window  */
+    fun getDefaultSize(aspectRatio: Float): Size
+
+    /** Returns min size allowed for the PIP window  */
+    fun getMinSize(aspectRatio: Float): Size
+
+    /** Returns the adjusted size based on current size and target aspect ratio  */
+    fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size
+
+    /** Overrides the minimum pip size requested by the app */
+    fun setOverrideMinSize(overrideMinSize: Size?)
+
+    /** Returns the minimum pip size requested by the app */
+    fun getOverrideMinSize(): Size?
+
+    /** Returns the minimum edge size of the override minimum size, or 0 if not set.  */
+    fun getOverrideMinEdgeSize(): Int {
+        val overrideMinSize = getOverrideMinSize() ?: return 0
+        return Math.min(overrideMinSize.width, overrideMinSize.height)
+    }
+
+    fun onConfigurationChanged() {}
+
+    /** Dumps the internal state of the size spec */
+    fun dump(pw: PrintWriter, prefix: String) {}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
index c76937d..ec26800 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
@@ -76,6 +76,9 @@
     private int mCurrentHeight;
     private AnimatorSet mAnimator;
     private boolean mTouching;
+    private boolean mHovering;
+    private final int mHoveringWidth;
+    private final int mHoveringHeight;
 
     public DividerHandleView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
@@ -87,6 +90,8 @@
         mCurrentHeight = mHeight;
         mTouchingWidth = mWidth > mHeight ? mWidth / 2 : mWidth;
         mTouchingHeight = mHeight > mWidth ? mHeight / 2 : mHeight;
+        mHoveringWidth = mWidth > mHeight ? ((int) (mWidth * 1.5f)) : mWidth;
+        mHoveringHeight = mHeight > mWidth ? ((int) (mHeight * 1.5f)) : mHeight;
     }
 
     /** Sets touching state for this handle view. */
@@ -94,24 +99,32 @@
         if (touching == mTouching) {
             return;
         }
+        setInputState(touching, animate, mTouchingWidth, mTouchingHeight);
+        mTouching = touching;
+    }
+
+    /** Sets hovering state for this handle view. */
+    public void setHovering(boolean hovering, boolean animate) {
+        if (hovering == mHovering) {
+            return;
+        }
+        setInputState(hovering, animate, mHoveringWidth, mHoveringHeight);
+        mHovering = hovering;
+    }
+
+    private void setInputState(boolean stateOn, boolean animate, int stateWidth, int stateHeight) {
         if (mAnimator != null) {
             mAnimator.cancel();
             mAnimator = null;
         }
         if (!animate) {
-            if (touching) {
-                mCurrentWidth = mTouchingWidth;
-                mCurrentHeight = mTouchingHeight;
-            } else {
-                mCurrentWidth = mWidth;
-                mCurrentHeight = mHeight;
-            }
+            mCurrentWidth = stateOn ? stateWidth : mWidth;
+            mCurrentHeight = stateOn ? stateHeight : mHeight;
             invalidate();
         } else {
-            animateToTarget(touching ? mTouchingWidth : mWidth,
-                    touching ? mTouchingHeight : mHeight, touching);
+            animateToTarget(stateOn ? stateWidth : mWidth,
+                    stateOn ? stateHeight : mHeight, stateOn);
         }
-        mTouching = touching;
     }
 
     private void animateToTarget(int targetWidth, int targetHeight, boolean touching) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
new file mode 100644
index 0000000..1901e0b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.split;
+
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Calculates the snap targets and the snap position given a position and a velocity. All positions
+ * here are to be interpreted as the left/top edge of the divider rectangle.
+ *
+ * @hide
+ */
+public class DividerSnapAlgorithm {
+
+    private static final int MIN_FLING_VELOCITY_DP_PER_SECOND = 400;
+    private static final int MIN_DISMISS_VELOCITY_DP_PER_SECOND = 600;
+
+    /**
+     * 3 snap targets: left/top has 16:9 ratio (for videos), 1:1, and right/bottom has 16:9 ratio
+     */
+    private static final int SNAP_MODE_16_9 = 0;
+
+    /**
+     * 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio)
+     */
+    private static final int SNAP_FIXED_RATIO = 1;
+
+    /**
+     * 1 snap target: 1:1
+     */
+    private static final int SNAP_ONLY_1_1 = 2;
+
+    /**
+     * 1 snap target: minimized height, (1 - minimized height)
+     */
+    private static final int SNAP_MODE_MINIMIZED = 3;
+
+    private final float mMinFlingVelocityPxPerSecond;
+    private final float mMinDismissVelocityPxPerSecond;
+    private final int mDisplayWidth;
+    private final int mDisplayHeight;
+    private final int mDividerSize;
+    private final ArrayList<SnapTarget> mTargets = new ArrayList<>();
+    private final Rect mInsets = new Rect();
+    private final int mSnapMode;
+    private final boolean mFreeSnapMode;
+    private final int mMinimalSizeResizableTask;
+    private final int mTaskHeightInMinimizedMode;
+    private final float mFixedRatio;
+    private boolean mIsHorizontalDivision;
+
+    /** The first target which is still splitting the screen */
+    private final SnapTarget mFirstSplitTarget;
+
+    /** The last target which is still splitting the screen */
+    private final SnapTarget mLastSplitTarget;
+
+    private final SnapTarget mDismissStartTarget;
+    private final SnapTarget mDismissEndTarget;
+    private final SnapTarget mMiddleTarget;
+
+    public static DividerSnapAlgorithm create(Context ctx, Rect insets) {
+        DisplayInfo displayInfo = new DisplayInfo();
+        ctx.getSystemService(DisplayManager.class).getDisplay(
+                Display.DEFAULT_DISPLAY).getDisplayInfo(displayInfo);
+        int dividerWindowWidth = ctx.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_divider_thickness);
+        int dividerInsets = ctx.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_divider_insets);
+        return new DividerSnapAlgorithm(ctx.getResources(),
+                displayInfo.logicalWidth, displayInfo.logicalHeight,
+                dividerWindowWidth - 2 * dividerInsets,
+                ctx.getApplicationContext().getResources().getConfiguration().orientation
+                        == Configuration.ORIENTATION_PORTRAIT,
+                insets);
+    }
+
+    public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
+            boolean isHorizontalDivision, Rect insets) {
+        this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets,
+                DOCKED_INVALID, false /* minimized */, true /* resizable */);
+    }
+
+    public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
+        boolean isHorizontalDivision, Rect insets, int dockSide) {
+        this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets,
+            dockSide, false /* minimized */, true /* resizable */);
+    }
+
+    public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
+            boolean isHorizontalDivision, Rect insets, int dockSide, boolean isMinimizedMode,
+            boolean isHomeResizable) {
+        mMinFlingVelocityPxPerSecond =
+                MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
+        mMinDismissVelocityPxPerSecond =
+                MIN_DISMISS_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
+        mDividerSize = dividerSize;
+        mDisplayWidth = displayWidth;
+        mDisplayHeight = displayHeight;
+        mIsHorizontalDivision = isHorizontalDivision;
+        mInsets.set(insets);
+        mSnapMode = isMinimizedMode ? SNAP_MODE_MINIMIZED :
+                res.getInteger(com.android.internal.R.integer.config_dockedStackDividerSnapMode);
+        mFreeSnapMode = res.getBoolean(
+                com.android.internal.R.bool.config_dockedStackDividerFreeSnapMode);
+        mFixedRatio = res.getFraction(
+                com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1);
+        mMinimalSizeResizableTask = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.default_minimal_size_resizable_task);
+        mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize(
+                com.android.internal.R.dimen.task_height_of_minimized_mode) : 0;
+        calculateTargets(isHorizontalDivision, dockSide);
+        mFirstSplitTarget = mTargets.get(1);
+        mLastSplitTarget = mTargets.get(mTargets.size() - 2);
+        mDismissStartTarget = mTargets.get(0);
+        mDismissEndTarget = mTargets.get(mTargets.size() - 1);
+        mMiddleTarget = mTargets.get(mTargets.size() / 2);
+        mMiddleTarget.isMiddleTarget = true;
+    }
+
+    /**
+     * @return whether it's feasible to enable split screen in the current configuration, i.e. when
+     *         snapping in the middle both tasks are larger than the minimal task size.
+     */
+    public boolean isSplitScreenFeasible() {
+        int statusBarSize = mInsets.top;
+        int navBarSize = mIsHorizontalDivision ? mInsets.bottom : mInsets.right;
+        int size = mIsHorizontalDivision
+                ? mDisplayHeight
+                : mDisplayWidth;
+        int availableSpace = size - navBarSize - statusBarSize - mDividerSize;
+        return availableSpace / 2 >= mMinimalSizeResizableTask;
+    }
+
+    public SnapTarget calculateSnapTarget(int position, float velocity) {
+        return calculateSnapTarget(position, velocity, true /* hardDismiss */);
+    }
+
+    /**
+     * @param position the top/left position of the divider
+     * @param velocity current dragging velocity
+     * @param hardDismiss if set, make it a bit harder to get reach the dismiss targets
+     */
+    public SnapTarget calculateSnapTarget(int position, float velocity, boolean hardDismiss) {
+        if (position < mFirstSplitTarget.position && velocity < -mMinDismissVelocityPxPerSecond) {
+            return mDismissStartTarget;
+        }
+        if (position > mLastSplitTarget.position && velocity > mMinDismissVelocityPxPerSecond) {
+            return mDismissEndTarget;
+        }
+        if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) {
+            return snap(position, hardDismiss);
+        }
+        if (velocity < 0) {
+            return mFirstSplitTarget;
+        } else {
+            return mLastSplitTarget;
+        }
+    }
+
+    public SnapTarget calculateNonDismissingSnapTarget(int position) {
+        SnapTarget target = snap(position, false /* hardDismiss */);
+        if (target == mDismissStartTarget) {
+            return mFirstSplitTarget;
+        } else if (target == mDismissEndTarget) {
+            return mLastSplitTarget;
+        } else {
+            return target;
+        }
+    }
+
+    public float calculateDismissingFraction(int position) {
+        if (position < mFirstSplitTarget.position) {
+            return 1f - (float) (position - getStartInset())
+                    / (mFirstSplitTarget.position - getStartInset());
+        } else if (position > mLastSplitTarget.position) {
+            return (float) (position - mLastSplitTarget.position)
+                    / (mDismissEndTarget.position - mLastSplitTarget.position - mDividerSize);
+        }
+        return 0f;
+    }
+
+    public SnapTarget getClosestDismissTarget(int position) {
+        if (position < mFirstSplitTarget.position) {
+            return mDismissStartTarget;
+        } else if (position > mLastSplitTarget.position) {
+            return mDismissEndTarget;
+        } else if (position - mDismissStartTarget.position
+                < mDismissEndTarget.position - position) {
+            return mDismissStartTarget;
+        } else {
+            return mDismissEndTarget;
+        }
+    }
+
+    public SnapTarget getFirstSplitTarget() {
+        return mFirstSplitTarget;
+    }
+
+    public SnapTarget getLastSplitTarget() {
+        return mLastSplitTarget;
+    }
+
+    public SnapTarget getDismissStartTarget() {
+        return mDismissStartTarget;
+    }
+
+    public SnapTarget getDismissEndTarget() {
+        return mDismissEndTarget;
+    }
+
+    private int getStartInset() {
+        if (mIsHorizontalDivision) {
+            return mInsets.top;
+        } else {
+            return mInsets.left;
+        }
+    }
+
+    private int getEndInset() {
+        if (mIsHorizontalDivision) {
+            return mInsets.bottom;
+        } else {
+            return mInsets.right;
+        }
+    }
+
+    private boolean shouldApplyFreeSnapMode(int position) {
+        if (!mFreeSnapMode) {
+            return false;
+        }
+        if (!isFirstSplitTargetAvailable() || !isLastSplitTargetAvailable()) {
+            return false;
+        }
+        return mFirstSplitTarget.position < position && position < mLastSplitTarget.position;
+    }
+
+    private SnapTarget snap(int position, boolean hardDismiss) {
+        if (shouldApplyFreeSnapMode(position)) {
+            return new SnapTarget(position, position, SnapTarget.FLAG_NONE);
+        }
+        int minIndex = -1;
+        float minDistance = Float.MAX_VALUE;
+        int size = mTargets.size();
+        for (int i = 0; i < size; i++) {
+            SnapTarget target = mTargets.get(i);
+            float distance = Math.abs(position - target.position);
+            if (hardDismiss) {
+                distance /= target.distanceMultiplier;
+            }
+            if (distance < minDistance) {
+                minIndex = i;
+                minDistance = distance;
+            }
+        }
+        return mTargets.get(minIndex);
+    }
+
+    private void calculateTargets(boolean isHorizontalDivision, int dockedSide) {
+        mTargets.clear();
+        int dividerMax = isHorizontalDivision
+                ? mDisplayHeight
+                : mDisplayWidth;
+        int startPos = -mDividerSize;
+        if (dockedSide == DOCKED_RIGHT) {
+            startPos += mInsets.left;
+        }
+        mTargets.add(new SnapTarget(startPos, startPos, SnapTarget.FLAG_DISMISS_START,
+                0.35f));
+        switch (mSnapMode) {
+            case SNAP_MODE_16_9:
+                addRatio16_9Targets(isHorizontalDivision, dividerMax);
+                break;
+            case SNAP_FIXED_RATIO:
+                addFixedDivisionTargets(isHorizontalDivision, dividerMax);
+                break;
+            case SNAP_ONLY_1_1:
+                addMiddleTarget(isHorizontalDivision);
+                break;
+            case SNAP_MODE_MINIMIZED:
+                addMinimizedTarget(isHorizontalDivision, dockedSide);
+                break;
+        }
+        mTargets.add(new SnapTarget(dividerMax, dividerMax, SnapTarget.FLAG_DISMISS_END, 0.35f));
+    }
+
+    private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition,
+            int bottomPosition, int dividerMax) {
+        maybeAddTarget(topPosition, topPosition - getStartInset());
+        addMiddleTarget(isHorizontalDivision);
+        maybeAddTarget(bottomPosition,
+                dividerMax - getEndInset() - (bottomPosition + mDividerSize));
+    }
+
+    private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) {
+        int start = isHorizontalDivision ? mInsets.top : mInsets.left;
+        int end = isHorizontalDivision
+                ? mDisplayHeight - mInsets.bottom
+                : mDisplayWidth - mInsets.right;
+        int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2;
+        int topPosition = start + size;
+        int bottomPosition = end - size - mDividerSize;
+        addNonDismissingTargets(isHorizontalDivision, topPosition, bottomPosition, dividerMax);
+    }
+
+    private void addRatio16_9Targets(boolean isHorizontalDivision, int dividerMax) {
+        int start = isHorizontalDivision ? mInsets.top : mInsets.left;
+        int end = isHorizontalDivision
+                ? mDisplayHeight - mInsets.bottom
+                : mDisplayWidth - mInsets.right;
+        int startOther = isHorizontalDivision ? mInsets.left : mInsets.top;
+        int endOther = isHorizontalDivision
+                ? mDisplayWidth - mInsets.right
+                : mDisplayHeight - mInsets.bottom;
+        float size = 9.0f / 16.0f * (endOther - startOther);
+        int sizeInt = (int) Math.floor(size);
+        int topPosition = start + sizeInt;
+        int bottomPosition = end - sizeInt - mDividerSize;
+        addNonDismissingTargets(isHorizontalDivision, topPosition, bottomPosition, dividerMax);
+    }
+
+    /**
+     * Adds a target at {@param position} but only if the area with size of {@param smallerSize}
+     * meets the minimal size requirement.
+     */
+    private void maybeAddTarget(int position, int smallerSize) {
+        if (smallerSize >= mMinimalSizeResizableTask) {
+            mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+        }
+    }
+
+    private void addMiddleTarget(boolean isHorizontalDivision) {
+        int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
+                mInsets, mDisplayWidth, mDisplayHeight, mDividerSize);
+        mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+    }
+
+    private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) {
+        // In portrait offset the position by the statusbar height, in landscape add the statusbar
+        // height as well to match portrait offset
+        int position = mTaskHeightInMinimizedMode + mInsets.top;
+        if (!isHorizontalDivision) {
+            if (dockedSide == DOCKED_LEFT) {
+                position += mInsets.left;
+            } else if (dockedSide == DOCKED_RIGHT) {
+                position = mDisplayWidth - position - mInsets.right - mDividerSize;
+            }
+        }
+        mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+    }
+
+    public SnapTarget getMiddleTarget() {
+        return mMiddleTarget;
+    }
+
+    public SnapTarget getNextTarget(SnapTarget snapTarget) {
+        int index = mTargets.indexOf(snapTarget);
+        if (index != -1 && index < mTargets.size() - 1) {
+            return mTargets.get(index + 1);
+        }
+        return snapTarget;
+    }
+
+    public SnapTarget getPreviousTarget(SnapTarget snapTarget) {
+        int index = mTargets.indexOf(snapTarget);
+        if (index != -1 && index > 0) {
+            return mTargets.get(index - 1);
+        }
+        return snapTarget;
+    }
+
+    /**
+     * @return whether or not there are more than 1 split targets that do not include the two
+     * dismiss targets, used in deciding to display the middle target for accessibility
+     */
+    public boolean showMiddleSplitTargetForAccessibility() {
+        return (mTargets.size() - 2) > 1;
+    }
+
+    public boolean isFirstSplitTargetAvailable() {
+        return mFirstSplitTarget != mMiddleTarget;
+    }
+
+    public boolean isLastSplitTargetAvailable() {
+        return mLastSplitTarget != mMiddleTarget;
+    }
+
+    /**
+     * Cycles through all non-dismiss targets with a stepping of {@param increment}. It moves left
+     * if {@param increment} is negative and moves right otherwise.
+     */
+    public SnapTarget cycleNonDismissTarget(SnapTarget snapTarget, int increment) {
+        int index = mTargets.indexOf(snapTarget);
+        if (index != -1) {
+            SnapTarget newTarget = mTargets.get((index + mTargets.size() + increment)
+                    % mTargets.size());
+            if (newTarget == mDismissStartTarget) {
+                return mLastSplitTarget;
+            } else if (newTarget == mDismissEndTarget) {
+                return mFirstSplitTarget;
+            } else {
+                return newTarget;
+            }
+        }
+        return snapTarget;
+    }
+
+    /**
+     * Represents a snap target for the divider.
+     */
+    public static class SnapTarget {
+        public static final int FLAG_NONE = 0;
+
+        /** If the divider reaches this value, the left/top task should be dismissed. */
+        public static final int FLAG_DISMISS_START = 1;
+
+        /** If the divider reaches this value, the right/bottom task should be dismissed */
+        public static final int FLAG_DISMISS_END = 2;
+
+        /** Position of this snap target. The right/bottom edge of the top/left task snaps here. */
+        public final int position;
+
+        /**
+         * Like {@link #position}, but used to calculate the task bounds which might be different
+         * from the stack bounds.
+         */
+        public final int taskPosition;
+
+        public final int flag;
+
+        public boolean isMiddleTarget;
+
+        /**
+         * Multiplier used to calculate distance to snap position. The lower this value, the harder
+         * it's to snap on this target
+         */
+        private final float distanceMultiplier;
+
+        public SnapTarget(int position, int taskPosition, int flag) {
+            this(position, taskPosition, flag, 1f);
+        }
+
+        public SnapTarget(int position, int taskPosition, int flag, float distanceMultiplier) {
+            this.position = position;
+            this.taskPosition = taskPosition;
+            this.flag = flag;
+            this.distanceMultiplier = distanceMultiplier;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 69f0bad..0b0c693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -17,14 +17,19 @@
 package com.android.wm.shell.common.split;
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
 import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
 
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CURSOR_HOVER_STATES_ENABLED;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.provider.DeviceConfig;
 import android.util.AttributeSet;
 import android.util.Property;
 import android.view.GestureDetector;
@@ -32,6 +37,7 @@
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.MotionEvent;
+import android.view.PointerIcon;
 import android.view.SurfaceControlViewHost;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -46,7 +52,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
@@ -222,7 +228,7 @@
             for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
                 final InsetsSource source = insetsState.sourceAt(i);
                 if (source.getType() == WindowInsets.Type.navigationBars()
-                        && source.insetsRoundedCornerFrame()) {
+                        && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
                     mTempRect.inset(source.calculateVisibleInsets(mTempRect));
                 }
             }
@@ -270,6 +276,12 @@
     }
 
     @Override
+    public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
+        return PointerIcon.getSystemIcon(getContext(),
+                isLandscape() ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW);
+    }
+
+    @Override
     public boolean onTouch(View v, MotionEvent event) {
         if (mSplitLayout == null || !mInteractive) {
             return false;
@@ -371,6 +383,43 @@
         mViewHost.relayout(lp);
     }
 
+    @Override
+    public boolean onHoverEvent(MotionEvent event) {
+        if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED,
+                /* defaultValue = */ false)) {
+            return false;
+        }
+
+        if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
+            setHovering();
+            return true;
+        } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
+            releaseHovering();
+            return true;
+        }
+        return false;
+    }
+
+    @VisibleForTesting
+    void setHovering() {
+        mHandle.setHovering(true, true);
+        mHandle.animate()
+                .setInterpolator(Interpolators.TOUCH_RESPONSE)
+                .setDuration(TOUCH_ANIMATION_DURATION)
+                .translationZ(mTouchElevation)
+                .start();
+    }
+
+    @VisibleForTesting
+    void releaseHovering() {
+        mHandle.setHovering(false, true);
+        mHandle.animate()
+                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
+                .translationZ(0)
+                .start();
+    }
+
     /**
      * Set divider should interactive to user or not.
      *
@@ -384,7 +433,7 @@
                 "Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
                 from);
         mInteractive = interactive;
-        if (!mInteractive && mMoving) {
+        if (!mInteractive && hideHandle && mMoving) {
             final int position = mSplitLayout.getDividePosition();
             mSplitLayout.flingDividePosition(
                     mLastDraggingPosition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
new file mode 100644
index 0000000..f25dfea
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.split;
+
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+
+import android.content.res.Resources;
+import android.graphics.Rect;
+
+/**
+ * Utility functions for docked stack divider used by both window manager and System UI.
+ *
+ * @hide
+ */
+public class DockedDividerUtils {
+
+    public static void calculateBoundsForPosition(int position, int dockSide, Rect outRect,
+            int displayWidth, int displayHeight, int dividerSize) {
+        outRect.set(0, 0, displayWidth, displayHeight);
+        switch (dockSide) {
+            case DOCKED_LEFT:
+                outRect.right = position;
+                break;
+            case DOCKED_TOP:
+                outRect.bottom = position;
+                break;
+            case DOCKED_RIGHT:
+                outRect.left = position + dividerSize;
+                break;
+            case DOCKED_BOTTOM:
+                outRect.top = position + dividerSize;
+                break;
+        }
+        sanitizeStackBounds(outRect, dockSide == DOCKED_LEFT || dockSide == DOCKED_TOP);
+    }
+
+    /**
+     * Makes sure that the bounds are always valid, i. e. they are at least one pixel high and wide.
+     *
+     * @param bounds The bounds to sanitize.
+     * @param topLeft Pass true if the bounds are at the top/left of the screen, false if they are
+     *                at the bottom/right. This is used to determine in which direction to extend
+     *                the bounds.
+     */
+    public static void sanitizeStackBounds(Rect bounds, boolean topLeft) {
+
+        // If the bounds are either on the top or left of the screen, rather move it further to the
+        // left/top to make it more offscreen. If they are on the bottom or right, push them off the
+        // screen by moving it even more to the bottom/right.
+        if (topLeft) {
+            if (bounds.left >= bounds.right) {
+                bounds.left = bounds.right - 1;
+            }
+            if (bounds.top >= bounds.bottom) {
+                bounds.top = bounds.bottom - 1;
+            }
+        } else {
+            if (bounds.right <= bounds.left) {
+                bounds.right = bounds.left + 1;
+            }
+            if (bounds.bottom <= bounds.top) {
+                bounds.bottom = bounds.top + 1;
+            }
+        }
+    }
+
+    public static int calculatePositionForBounds(Rect bounds, int dockSide, int dividerSize) {
+        switch (dockSide) {
+            case DOCKED_LEFT:
+                return bounds.right;
+            case DOCKED_TOP:
+                return bounds.bottom;
+            case DOCKED_RIGHT:
+                return bounds.left - dividerSize;
+            case DOCKED_BOTTOM:
+                return bounds.top - dividerSize;
+            default:
+                return 0;
+        }
+    }
+
+    public static int calculateMiddlePosition(boolean isHorizontalDivision, Rect insets,
+            int displayWidth, int displayHeight, int dividerSize) {
+        int start = isHorizontalDivision ? insets.top : insets.left;
+        int end = isHorizontalDivision
+                ? displayHeight - insets.bottom
+                : displayWidth - insets.right;
+        return start + (end - start) / 2 - dividerSize / 2;
+    }
+
+    public static int invertDockSide(int dockSide) {
+        switch (dockSide) {
+            case DOCKED_LEFT:
+                return DOCKED_RIGHT;
+            case DOCKED_TOP:
+                return DOCKED_BOTTOM;
+            case DOCKED_RIGHT:
+                return DOCKED_LEFT;
+            case DOCKED_BOTTOM:
+                return DOCKED_TOP;
+            default:
+                return DOCKED_INVALID;
+        }
+    }
+
+    /** Returns the inset distance from the divider window edge to the dividerview. */
+    public static int getDividerInsets(Resources res) {
+        return res.getDimensionPixelSize(com.android.internal.R.dimen.docked_stack_divider_insets);
+    }
+
+    /** Returns the size of the divider */
+    public static int getDividerSize(Resources res, int dividerInsets) {
+        final int windowWidth = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.docked_stack_divider_thickness);
+        return windowWidth - 2 * dividerInsets;
+    }
+
+    /** Returns the docked-stack side */
+    public static int getDockSide(int displayWidth, int displayHeight) {
+        return displayWidth > displayHeight ? DOCKED_LEFT : DOCKED_TOP;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index a9ccdf6..2b10377 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -323,7 +323,11 @@
             }
         }
         if (mShown) {
-            fadeOutDecor(()-> animFinishedCallback.accept(true));
+            fadeOutDecor(()-> {
+                if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+                    animFinishedCallback.accept(true);
+                }
+            });
         } else {
             // Decor surface is hidden so release it directly.
             releaseDecor(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index f70d3ae..5d7e532 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -25,8 +25,8 @@
 import static android.view.WindowManager.DOCKED_TOP;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
-import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
-import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
+import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
+import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
 import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
 import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -58,8 +58,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.internal.policy.DockedDividerUtils;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.animation.Interpolators;
@@ -593,9 +591,6 @@
     void flingDividePosition(int from, int to, int duration,
             @Nullable Runnable flingFinishedCallback) {
         if (from == to) {
-            // No animation run, still callback to stop resizing.
-            mSplitLayoutHandler.onLayoutSizeChanged(this);
-
             if (flingFinishedCallback != null) {
                 flingFinishedCallback.run();
             }
@@ -725,10 +720,6 @@
         return bounds.width() > bounds.height();
     }
 
-    public boolean isDensityChanged(int densityDpi) {
-        return mDensity != densityDpi;
-    }
-
     /**
      * Return if this layout is landscape.
      */
@@ -773,15 +764,13 @@
             ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
         boolean boundsChanged = false;
         if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
-            wct.setBounds(task1.token, mBounds1);
-            wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
+            setTaskBounds(wct, task1, mBounds1);
             mWinBounds1.set(mBounds1);
             mWinToken1 = task1.token;
             boundsChanged = true;
         }
         if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
-            wct.setBounds(task2.token, mBounds2);
-            wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));
+            setTaskBounds(wct, task2, mBounds2);
             mWinBounds2.set(mBounds2);
             mWinToken2 = task2.token;
             boundsChanged = true;
@@ -789,6 +778,13 @@
         return boundsChanged;
     }
 
+    /** Set bounds to the {@link WindowContainerTransaction} for single task. */
+    public void setTaskBounds(WindowContainerTransaction wct,
+            ActivityManager.RunningTaskInfo task, Rect bounds) {
+        wct.setBounds(task.token, bounds);
+        wct.setSmallestScreenWidthDp(task.token, getSmallestWidthDp(bounds));
+    }
+
     private int getSmallestWidthDp(Rect bounds) {
         mTempRect.set(bounds);
         mTempRect.inset(getDisplayStableInsets(mContext));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index 0289da9..d7ea1c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -89,4 +89,9 @@
             int userId1, int userId2) {
         return (packageName1 != null && packageName1.equals(packageName2)) && (userId1 == userId2);
     }
+
+    /** Generates a common log message for split screen failures */
+    public static String splitFailureMessage(String caller, String reason) {
+        return "(" + caller + ") Splitscreen aborted: " + reason;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 62b0799..54f8984 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -16,12 +16,17 @@
 
 package com.android.wm.shell.compatui;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskInfo;
 import android.app.TaskInfo.CameraCompatControlState;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
+import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
@@ -41,7 +46,6 @@
 import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
 import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
@@ -104,6 +108,13 @@
     private Set<Integer> mSetOfTaskIdsShowingRestartDialog = new HashSet<>();
 
     /**
+     * The active user aspect ratio settings button layout if there is one (there can be at most
+     * one active).
+     */
+    @Nullable
+    private UserAspectRatioSettingsWindowManager mUserAspectRatioSettingsLayout;
+
+    /**
      * The active Letterbox Education layout if there is one (there can be at most one active).
      *
      * <p>An active layout is a layout that is eligible to be shown for the associated task but
@@ -121,38 +132,51 @@
     /** Avoid creating display context frequently for non-default display. */
     private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
 
+    @NonNull
     private final Context mContext;
+    @NonNull
     private final ShellController mShellController;
+    @NonNull
     private final DisplayController mDisplayController;
+    @NonNull
     private final DisplayInsetsController mDisplayInsetsController;
+    @NonNull
     private final DisplayImeController mImeController;
+    @NonNull
     private final SyncTransactionQueue mSyncQueue;
+    @NonNull
     private final ShellExecutor mMainExecutor;
+    @NonNull
     private final Lazy<Transitions> mTransitionsLazy;
+    @NonNull
     private final DockStateReader mDockStateReader;
+    @NonNull
     private final CompatUIConfiguration mCompatUIConfiguration;
     // Only show each hint once automatically in the process life.
+    @NonNull
     private final CompatUIHintsState mCompatUIHintsState;
+    @NonNull
     private final CompatUIShellCommandHandler mCompatUIShellCommandHandler;
 
-    private CompatUICallback mCallback;
+    @Nullable
+    private CompatUICallback mCompatUICallback;
 
     // Indicates if the keyguard is currently showing, in which case compat UIs shouldn't
     // be shown.
     private boolean mKeyguardShowing;
 
-    public CompatUIController(Context context,
-            ShellInit shellInit,
-            ShellController shellController,
-            DisplayController displayController,
-            DisplayInsetsController displayInsetsController,
-            DisplayImeController imeController,
-            SyncTransactionQueue syncQueue,
-            ShellExecutor mainExecutor,
-            Lazy<Transitions> transitionsLazy,
-            DockStateReader dockStateReader,
-            CompatUIConfiguration compatUIConfiguration,
-            CompatUIShellCommandHandler compatUIShellCommandHandler) {
+    public CompatUIController(@NonNull Context context,
+            @NonNull ShellInit shellInit,
+            @NonNull ShellController shellController,
+            @NonNull DisplayController displayController,
+            @NonNull DisplayInsetsController displayInsetsController,
+            @NonNull DisplayImeController imeController,
+            @NonNull SyncTransactionQueue syncQueue,
+            @NonNull ShellExecutor mainExecutor,
+            @NonNull Lazy<Transitions> transitionsLazy,
+            @NonNull DockStateReader dockStateReader,
+            @NonNull CompatUIConfiguration compatUIConfiguration,
+            @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler) {
         mContext = context;
         mShellController = shellController;
         mDisplayController = displayController;
@@ -175,9 +199,9 @@
         mCompatUIShellCommandHandler.onInit();
     }
 
-    /** Sets the callback for UI interactions. */
-    public void setCompatUICallback(CompatUICallback callback) {
-        mCallback = callback;
+    /** Sets the callback for Compat UI interactions. */
+    public void setCompatUICallback(@NonNull CompatUICallback compatUiCallback) {
+        mCompatUICallback = compatUiCallback;
     }
 
     /**
@@ -187,7 +211,7 @@
      * @param taskInfo {@link TaskInfo} task the activity is in.
      * @param taskListener listener to handle the Task Surface placement.
      */
-    public void onCompatInfoChanged(TaskInfo taskInfo,
+    public void onCompatInfoChanged(@NonNull TaskInfo taskInfo,
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         if (taskInfo != null && !taskInfo.topActivityInSizeCompat) {
             mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
@@ -203,6 +227,16 @@
         createOrUpdateRestartDialogLayout(taskInfo, taskListener);
         if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) {
             createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
+            // The user aspect ratio button should not be handled when a new TaskInfo is
+            // sent because of a double tap or when in multi-window mode.
+            if (taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+                mUserAspectRatioSettingsLayout.release();
+                mUserAspectRatioSettingsLayout = null;
+                return;
+            }
+            if (!taskInfo.isFromLetterboxDoubleTap) {
+                createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener);
+            }
         }
     }
 
@@ -280,8 +314,8 @@
         return mDisplaysWithIme.contains(displayId);
     }
 
-    private void createOrUpdateCompatLayout(TaskInfo taskInfo,
-            ShellTaskOrganizer.TaskListener taskListener) {
+    private void createOrUpdateCompatLayout(@NonNull TaskInfo taskInfo,
+            @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId);
         if (layout != null) {
             if (layout.needsToBeRecreated(taskInfo, taskListener)) {
@@ -314,7 +348,7 @@
     CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
             ShellTaskOrganizer.TaskListener taskListener) {
         return new CompatUIWindowManager(context,
-                taskInfo, mSyncQueue, mCallback, taskListener,
+                taskInfo, mSyncQueue, mCompatUICallback, taskListener,
                 mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState,
                 mCompatUIConfiguration, this::onRestartButtonClicked);
     }
@@ -328,12 +362,12 @@
             mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId);
             onCompatInfoChanged(taskInfoState.first, taskInfoState.second);
         } else {
-            mCallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId);
+            mCompatUICallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId);
         }
     }
 
-    private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo,
-            ShellTaskOrganizer.TaskListener taskListener) {
+    private void createOrUpdateLetterboxEduLayout(@NonNull TaskInfo taskInfo,
+            @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         if (mActiveLetterboxEduLayout != null) {
             if (mActiveLetterboxEduLayout.needsToBeRecreated(taskInfo, taskListener)) {
                 mActiveLetterboxEduLayout.release();
@@ -342,6 +376,7 @@
                 if (!mActiveLetterboxEduLayout.updateCompatInfo(taskInfo, taskListener,
                         showOnDisplay(mActiveLetterboxEduLayout.getDisplayId()))) {
                     // The layout is no longer eligible to be shown, clear active layout.
+                    mActiveLetterboxEduLayout.release();
                     mActiveLetterboxEduLayout = null;
                 }
                 return;
@@ -371,19 +406,13 @@
             ShellTaskOrganizer.TaskListener taskListener) {
         return new LetterboxEduWindowManager(context, taskInfo,
                 mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
-                mTransitionsLazy.get(), this::onLetterboxEduDismissed, mDockStateReader,
-                mCompatUIConfiguration);
+                mTransitionsLazy.get(),
+                stateInfo -> createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second),
+                mDockStateReader, mCompatUIConfiguration);
     }
 
-    private void onLetterboxEduDismissed(
-            Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
-        mActiveLetterboxEduLayout = null;
-        // We need to update the UI
-        createOrUpdateReachabilityEduLayout(stateInfo.first, stateInfo.second);
-    }
-
-    private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo,
-            ShellTaskOrganizer.TaskListener taskListener) {
+    private void createOrUpdateRestartDialogLayout(@NonNull TaskInfo taskInfo,
+            @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         RestartDialogWindowManager layout =
                 mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId);
         if (layout != null) {
@@ -428,7 +457,7 @@
     private void onRestartDialogCallback(
             Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
         mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId);
-        mCallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId);
+        mCompatUICallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId);
     }
 
     private void onRestartDialogDismissCallback(
@@ -437,8 +466,8 @@
         onCompatInfoChanged(stateInfo.first, stateInfo.second);
     }
 
-    private void createOrUpdateReachabilityEduLayout(TaskInfo taskInfo,
-            ShellTaskOrganizer.TaskListener taskListener) {
+    private void createOrUpdateReachabilityEduLayout(@NonNull TaskInfo taskInfo,
+            @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         if (mActiveReachabilityEduLayout != null) {
             if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener)) {
                 mActiveReachabilityEduLayout.release();
@@ -448,6 +477,7 @@
                 if (!mActiveReachabilityEduLayout.updateCompatInfo(taskInfo, taskListener,
                         showOnDisplay(mActiveReachabilityEduLayout.getDisplayId()))) {
                     // The layout is no longer eligible to be shown, remove from active layouts.
+                    mActiveReachabilityEduLayout.release();
                     mActiveReachabilityEduLayout = null;
                 }
                 return;
@@ -478,14 +508,67 @@
             ShellTaskOrganizer.TaskListener taskListener) {
         return new ReachabilityEduWindowManager(context, taskInfo, mSyncQueue,
                 taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
-                mCompatUIConfiguration, mMainExecutor);
+                mCompatUIConfiguration, mMainExecutor, this::onInitialReachabilityEduDismissed);
     }
 
+    private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo,
+            @NonNull ShellTaskOrganizer.TaskListener taskListener) {
+        // We need to update the UI otherwise it will not be shown until the user relaunches the app
+        createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener);
+    }
+
+    private void createOrUpdateUserAspectRatioSettingsLayout(@NonNull TaskInfo taskInfo,
+            @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+        if (mUserAspectRatioSettingsLayout != null) {
+            if (mUserAspectRatioSettingsLayout.needsToBeRecreated(taskInfo, taskListener)) {
+                mUserAspectRatioSettingsLayout.release();
+                mUserAspectRatioSettingsLayout = null;
+            } else {
+                // UI already exists, update the UI layout.
+                if (!mUserAspectRatioSettingsLayout.updateCompatInfo(taskInfo, taskListener,
+                        showOnDisplay(mUserAspectRatioSettingsLayout.getDisplayId()))) {
+                    mUserAspectRatioSettingsLayout.release();
+                    mUserAspectRatioSettingsLayout = null;
+                }
+                return;
+            }
+        }
+
+        // Create a new UI layout.
+        final Context context = getOrCreateDisplayContext(taskInfo.displayId);
+        if (context == null) {
+            return;
+        }
+        final UserAspectRatioSettingsWindowManager newLayout =
+                createUserAspectRatioSettingsWindowManager(context, taskInfo, taskListener);
+        if (newLayout.createLayout(showOnDisplay(taskInfo.displayId))) {
+            // The new layout is eligible to be shown, add it the active layouts.
+            mUserAspectRatioSettingsLayout = newLayout;
+        }
+    }
+
+    @VisibleForTesting
+    @NonNull
+    UserAspectRatioSettingsWindowManager createUserAspectRatioSettingsWindowManager(
+            @NonNull Context context, @NonNull TaskInfo taskInfo,
+            @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+        return new UserAspectRatioSettingsWindowManager(context, taskInfo, mSyncQueue,
+                taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
+                mCompatUIHintsState, this::launchUserAspectRatioSettings, mMainExecutor);
+    }
+
+    private void launchUserAspectRatioSettings(
+            @NonNull TaskInfo taskInfo, @NonNull ShellTaskOrganizer.TaskListener taskListener) {
+        final Intent intent = new Intent(Settings.ACTION_MANAGE_USER_ASPECT_RATIO_SETTINGS);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(intent);
+    }
 
     private void removeLayouts(int taskId) {
-        final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId);
-        if (layout != null) {
-            layout.release();
+        final CompatUIWindowManager compatLayout = mActiveCompatLayouts.get(taskId);
+        if (compatLayout != null) {
+            compatLayout.release();
             mActiveCompatLayouts.remove(taskId);
         }
 
@@ -506,6 +589,12 @@
             mActiveReachabilityEduLayout.release();
             mActiveReachabilityEduLayout = null;
         }
+
+        if (mUserAspectRatioSettingsLayout != null
+                && mUserAspectRatioSettingsLayout.getTaskId() == taskId) {
+            mUserAspectRatioSettingsLayout.release();
+            mUserAspectRatioSettingsLayout = null;
+        }
     }
 
     private Context getOrCreateDisplayContext(int displayId) {
@@ -561,6 +650,10 @@
         if (mActiveReachabilityEduLayout != null && condition.test(mActiveReachabilityEduLayout)) {
             callback.accept(mActiveReachabilityEduLayout);
         }
+        if (mUserAspectRatioSettingsLayout != null && condition.test(
+                mUserAspectRatioSettingsLayout)) {
+            callback.accept(mUserAspectRatioSettingsLayout);
+        }
     }
 
     /** An implementation of {@link OnInsetsChangedListener} for a given display id. */
@@ -595,4 +688,14 @@
             insetsChanged(insetsState);
         }
     }
+
+    /**
+     * A class holding the state of the compat UI hints, which is shared between all compat UI
+     * window managers.
+     */
+    static class CompatUIHintsState {
+        boolean mHasShownSizeCompatHint;
+        boolean mHasShownCameraCompatHint;
+        boolean mHasShownUserAspectRatioSettingsButtonHint;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 065806d..ce3c509 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -38,6 +38,7 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
 
 import java.util.function.Consumer;
 
@@ -235,15 +236,4 @@
         return mCameraCompatControlState != CAMERA_COMPAT_CONTROL_HIDDEN
                 && mCameraCompatControlState != CAMERA_COMPAT_CONTROL_DISMISSED;
     }
-
-    /**
-     * A class holding the state of the compat UI hints, which is shared between all compat UI
-     * window managers.
-     */
-    static class CompatUIHintsState {
-        @VisibleForTesting
-        boolean mHasShownSizeCompatHint;
-        @VisibleForTesting
-        boolean mHasShownCameraCompatHint;
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
index 95bb1fe..9de3f9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
@@ -36,6 +36,8 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 
+import java.util.function.BiConsumer;
+
 /**
  * Window manager for the reachability education
  */
@@ -73,6 +75,8 @@
     // we need to animate them.
     private boolean mHasLetterboxSizeChanged;
 
+    private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback;
+
     @Nullable
     @VisibleForTesting
     ReachabilityEduLayout mLayout;
@@ -80,7 +84,8 @@
     ReachabilityEduWindowManager(Context context, TaskInfo taskInfo,
             SyncTransactionQueue syncQueue,
             ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
-            CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor) {
+            CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor,
+            BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onDismissCallback) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled;
         mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition;
@@ -89,6 +94,7 @@
         mTopActivityLetterboxHeight = taskInfo.topActivityLetterboxHeight;
         mCompatUIConfiguration = compatUIConfiguration;
         mMainExecutor = mainExecutor;
+        mOnDismissCallback = onDismissCallback;
     }
 
     @Override
@@ -217,13 +223,17 @@
             return;
         }
         final TaskInfo lastTaskInfo = getLastTaskInfo();
+        final boolean hasSeenHorizontalReachabilityEdu =
+                mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(lastTaskInfo);
+        final boolean hasSeenVerticalReachabilityEdu =
+                mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(lastTaskInfo);
         final boolean eligibleForDisplayHorizontalEducation = mForceUpdate
-                || !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(lastTaskInfo)
+                || !hasSeenHorizontalReachabilityEdu
                 || (mHasUserDoubleTapped
                     && (mLetterboxHorizontalPosition == REACHABILITY_LEFT_OR_UP_POSITION
                         || mLetterboxHorizontalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
         final boolean eligibleForDisplayVerticalEducation = mForceUpdate
-                || !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(lastTaskInfo)
+                || !hasSeenVerticalReachabilityEdu
                 || (mHasUserDoubleTapped
                     && (mLetterboxVerticalPosition == REACHABILITY_LEFT_OR_UP_POSITION
                         || mLetterboxVerticalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
@@ -239,6 +249,14 @@
             if (!mHasLetterboxSizeChanged) {
                 updateHideTime();
                 mMainExecutor.executeDelayed(this::hideReachability, DISAPPEAR_DELAY_MS);
+                // If reachability education has been seen for the first time, trigger callback to
+                // display aspect ratio settings button once reachability education disappears
+                if (hasShownHorizontalReachabilityEduFirstTime(hasSeenHorizontalReachabilityEdu)
+                        || hasShownVerticalReachabilityEduFirstTime(
+                        hasSeenVerticalReachabilityEdu)) {
+                    mMainExecutor.executeDelayed(this::triggerOnDismissCallback,
+                            DISAPPEAR_DELAY_MS);
+                }
             }
             mHasUserDoubleTapped = false;
         } else {
@@ -246,6 +264,38 @@
         }
     }
 
+    /**
+     * Compares the value of
+     * {@link CompatUIConfiguration#hasSeenHorizontalReachabilityEducation} before and after the
+     * layout is shown. Horizontal reachability education is considered seen for the first time if
+     * prior to viewing the layout,
+     * {@link CompatUIConfiguration#hasSeenHorizontalReachabilityEducation} is {@code false}
+     * but becomes {@code true} once the current layout is shown.
+     */
+    private boolean hasShownHorizontalReachabilityEduFirstTime(
+            boolean previouslyShownHorizontalReachabilityEducation) {
+        return !previouslyShownHorizontalReachabilityEducation
+                && mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(getLastTaskInfo());
+    }
+
+    /**
+     * Compares the value of
+     * {@link CompatUIConfiguration#hasSeenVerticalReachabilityEducation} before and after the
+     * layout is shown. Horizontal reachability education is considered seen for the first time if
+     * prior to viewing the layout,
+     * {@link CompatUIConfiguration#hasSeenVerticalReachabilityEducation} is {@code false}
+     * but becomes {@code true} once the current layout is shown.
+     */
+    private boolean hasShownVerticalReachabilityEduFirstTime(
+            boolean previouslyShownVerticalReachabilityEducation) {
+        return !previouslyShownVerticalReachabilityEducation
+                && mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(getLastTaskInfo());
+    }
+
+    private void triggerOnDismissCallback() {
+        mOnDismissCallback.accept(getLastTaskInfo(), getTaskListener());
+    }
+
     private void hideReachability() {
         if (mLayout == null || !shouldHideEducation()) {
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
new file mode 100644
index 0000000..5eeb3b6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.IdRes;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Layout for the user aspect ratio button which opens the app list page in settings
+ * and allows users to change apps aspect ratio.
+ */
+public class UserAspectRatioSettingsLayout extends LinearLayout {
+
+    private static final float ALPHA_FULL_TRANSPARENT = 0f;
+
+    private static final float ALPHA_FULL_OPAQUE = 1f;
+
+    private static final long VISIBILITY_ANIMATION_DURATION_MS = 50;
+
+    private static final String ALPHA_PROPERTY_NAME = "alpha";
+
+    private UserAspectRatioSettingsWindowManager mWindowManager;
+
+    public UserAspectRatioSettingsLayout(Context context) {
+        this(context, null);
+    }
+
+    public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    void inject(@NonNull UserAspectRatioSettingsWindowManager windowManager) {
+        mWindowManager = windowManager;
+    }
+
+    void setUserAspectRatioSettingsHintVisibility(boolean show) {
+        setViewVisibility(R.id.user_aspect_ratio_settings_hint, show);
+    }
+
+    void setUserAspectRatioButtonVisibility(boolean show) {
+        setViewVisibility(R.id.user_aspect_ratio_settings_button, show);
+        // Hint should never be visible without button.
+        if (!show) {
+            setUserAspectRatioSettingsHintVisibility(/* show= */ false);
+        }
+    }
+
+    private void setViewVisibility(@IdRes int resId, boolean show) {
+        final View view = findViewById(resId);
+        int visibility = show ? View.VISIBLE : View.GONE;
+        if (view.getVisibility() == visibility) {
+            return;
+        }
+        if (show) {
+            showItem(view);
+        } else {
+            view.setVisibility(visibility);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        // Need to relayout after changes like hiding / showing a hint since they affect size.
+        // Doing this directly in setUserAspectRatioButtonVisibility can result in flaky animation.
+        mWindowManager.relayout();
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        final ImageButton userAspectRatioButton =
+                findViewById(R.id.user_aspect_ratio_settings_button);
+        userAspectRatioButton.setOnClickListener(
+                view -> mWindowManager.onUserAspectRatioSettingsButtonClicked());
+        userAspectRatioButton.setOnLongClickListener(view -> {
+            mWindowManager.onUserAspectRatioSettingsButtonLongClicked();
+            return true;
+        });
+
+        final LinearLayout sizeCompatHint = findViewById(R.id.user_aspect_ratio_settings_hint);
+        ((TextView) sizeCompatHint.findViewById(R.id.compat_mode_hint_text))
+                .setText(R.string.user_aspect_ratio_settings_button_hint);
+        sizeCompatHint.setOnClickListener(
+                view -> setUserAspectRatioSettingsHintVisibility(/* show= */ false));
+    }
+
+    private void showItem(@NonNull View view) {
+        view.setVisibility(View.VISIBLE);
+        final ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME,
+                ALPHA_FULL_TRANSPARENT, ALPHA_FULL_OPAQUE);
+        fadeIn.setDuration(VISIBILITY_ANIMATION_DURATION_MS);
+        fadeIn.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                view.setVisibility(View.VISIBLE);
+            }
+        });
+        fadeIn.start();
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
new file mode 100644
index 0000000..bd53dc7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+
+import java.util.function.BiConsumer;
+
+/**
+ * Window manager for the user aspect ratio settings button which allows users to go to
+ * app settings and change apps aspect ratio.
+ */
+class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract {
+
+    private static final long SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 500L;
+
+    private static final long HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 4000L;
+
+    private long mNextButtonHideTimeMs = -1L;
+
+    private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnButtonClicked;
+
+    private final ShellExecutor mShellExecutor;
+
+    @VisibleForTesting
+    @NonNull
+    final CompatUIHintsState mCompatUIHintsState;
+
+    @Nullable
+    private UserAspectRatioSettingsLayout mLayout;
+
+    // Remember the last reported states in case visibility changes due to keyguard or IME updates.
+    @VisibleForTesting
+    boolean mHasUserAspectRatioSettingsButton;
+
+    UserAspectRatioSettingsWindowManager(@NonNull Context context, @NonNull TaskInfo taskInfo,
+            @NonNull SyncTransactionQueue syncQueue,
+            @Nullable ShellTaskOrganizer.TaskListener taskListener,
+            @NonNull DisplayLayout displayLayout, @NonNull CompatUIHintsState compatUIHintsState,
+            @NonNull BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onButtonClicked,
+            @NonNull ShellExecutor shellExecutor) {
+        super(context, taskInfo, syncQueue, taskListener, displayLayout);
+        mShellExecutor = shellExecutor;
+        mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+        mCompatUIHintsState = compatUIHintsState;
+        mOnButtonClicked = onButtonClicked;
+    }
+
+    @Override
+    protected int getZOrder() {
+        return TASK_CHILD_LAYER_COMPAT_UI + 1;
+    }
+
+    @Override
+    protected @Nullable View getLayout() {
+        return mLayout;
+    }
+
+    @Override
+    protected void removeLayout() {
+        mLayout = null;
+    }
+
+    @Override
+    protected boolean eligibleToShowLayout() {
+        return mHasUserAspectRatioSettingsButton;
+    }
+
+    @Override
+    protected View createLayout() {
+        mLayout = inflateLayout();
+        mLayout.inject(this);
+
+        updateVisibilityOfViews();
+
+        return mLayout;
+    }
+
+    @VisibleForTesting
+    UserAspectRatioSettingsLayout inflateLayout() {
+        return (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate(
+                R.layout.user_aspect_ratio_settings_layout, null);
+    }
+
+    @Override
+    public boolean updateCompatInfo(@NonNull TaskInfo taskInfo,
+            @NonNull ShellTaskOrganizer.TaskListener taskListener, boolean canShow) {
+        final boolean prevHasUserAspectRatioSettingsButton = mHasUserAspectRatioSettingsButton;
+        mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+
+        if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
+            return false;
+        }
+
+        if (prevHasUserAspectRatioSettingsButton != mHasUserAspectRatioSettingsButton) {
+            updateVisibilityOfViews();
+        }
+        return true;
+    }
+
+    /** Called when the user aspect ratio settings button is clicked. */
+    void onUserAspectRatioSettingsButtonClicked() {
+        mOnButtonClicked.accept(getLastTaskInfo(), getTaskListener());
+    }
+
+    /** Called when the user aspect ratio settings button is long clicked. */
+    void onUserAspectRatioSettingsButtonLongClicked() {
+        if (mLayout == null) {
+            return;
+        }
+        mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+        mNextButtonHideTimeMs = updateHideTime(HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+        mShellExecutor.executeDelayed(this::hideUserAspectRatioButton,
+                HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+    }
+
+    @Override
+    @VisibleForTesting
+    public void updateSurfacePosition() {
+        if (mLayout == null) {
+            return;
+        }
+        // Position of the button in the container coordinate.
+        final Rect taskBounds = getTaskBounds();
+        final Rect taskStableBounds = getTaskStableBounds();
+        final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+                ? taskStableBounds.left - taskBounds.left
+                : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth();
+        final int positionY = taskStableBounds.bottom - taskBounds.top
+                - mLayout.getMeasuredHeight();
+        updateSurfacePosition(positionX, positionY);
+    }
+
+    @VisibleForTesting
+    void updateVisibilityOfViews() {
+        if (mHasUserAspectRatioSettingsButton) {
+            mShellExecutor.executeDelayed(this::showUserAspectRatioButton,
+                    SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+            mNextButtonHideTimeMs = updateHideTime(HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+            mShellExecutor.executeDelayed(this::hideUserAspectRatioButton,
+                    HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+        } else {
+            mShellExecutor.removeCallbacks(this::showUserAspectRatioButton);
+            mShellExecutor.execute(this::hideUserAspectRatioButton);
+        }
+    }
+
+    private void showUserAspectRatioButton() {
+        if (mLayout == null) {
+            return;
+        }
+        mLayout.setUserAspectRatioButtonVisibility(true);
+        // Only show by default for the first time.
+        if (!mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint) {
+            mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+            mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true;
+        }
+    }
+
+    private void hideUserAspectRatioButton() {
+        if (mLayout == null || !isHideDelayReached(mNextButtonHideTimeMs)) {
+            return;
+        }
+        mLayout.setUserAspectRatioButtonVisibility(false);
+    }
+
+    private boolean isHideDelayReached(long nextHideTime) {
+        return SystemClock.uptimeMillis() >= nextHideTime;
+    }
+
+    private long updateHideTime(long hideDelay) {
+        return SystemClock.uptimeMillis() + hideDelay;
+    }
+
+    private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) {
+        return  taskInfo.topActivityEligibleForUserAspectRatioButton
+                && (taskInfo.topActivityBoundsLetterboxed
+                    || taskInfo.isUserFullscreenOverrideEnabled);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
deleted file mode 100644
index 14daae0..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ /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.wm.shell.dagger;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.SystemClock;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipMediaController;
-import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
-import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
-import com.android.wm.shell.pip.PipTaskOrganizer;
-import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
-import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
-import com.android.wm.shell.pip.tv.TvPipBoundsController;
-import com.android.wm.shell.pip.tv.TvPipBoundsState;
-import com.android.wm.shell.pip.tv.TvPipController;
-import com.android.wm.shell.pip.tv.TvPipMenuController;
-import com.android.wm.shell.pip.tv.TvPipNotificationController;
-import com.android.wm.shell.pip.tv.TvPipTaskOrganizer;
-import com.android.wm.shell.pip.tv.TvPipTransition;
-import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
-
-import dagger.Module;
-import dagger.Provides;
-
-import java.util.Optional;
-
-/**
- * Provides TV specific dependencies for Pip.
- */
-@Module(includes = {WMShellBaseModule.class})
-public abstract class TvPipModule {
-    @WMSingleton
-    @Provides
-    static Optional<Pip> providePip(
-            Context context,
-            ShellInit shellInit,
-            ShellController shellController,
-            TvPipBoundsState tvPipBoundsState,
-            PipDisplayLayoutState pipDisplayLayoutState,
-            TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
-            TvPipBoundsController tvPipBoundsController,
-            PipAppOpsListener pipAppOpsListener,
-            PipTaskOrganizer pipTaskOrganizer,
-            TvPipMenuController tvPipMenuController,
-            PipMediaController pipMediaController,
-            PipTransitionController pipTransitionController,
-            TvPipNotificationController tvPipNotificationController,
-            TaskStackListenerImpl taskStackListener,
-            PipParamsChangedForwarder pipParamsChangedForwarder,
-            DisplayController displayController,
-            WindowManagerShellWrapper windowManagerShellWrapper,
-            @ShellMainThread Handler mainHandler, // needed for registerReceiverForAllUsers()
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.of(
-                TvPipController.create(
-                        context,
-                        shellInit,
-                        shellController,
-                        tvPipBoundsState,
-                        pipDisplayLayoutState,
-                        tvPipBoundsAlgorithm,
-                        tvPipBoundsController,
-                        pipAppOpsListener,
-                        pipTaskOrganizer,
-                        pipTransitionController,
-                        tvPipMenuController,
-                        pipMediaController,
-                        tvPipNotificationController,
-                        taskStackListener,
-                        pipParamsChangedForwarder,
-                        displayController,
-                        windowManagerShellWrapper,
-                        mainHandler,
-                        mainExecutor));
-    }
-
-    @WMSingleton
-    @Provides
-    static TvPipBoundsController provideTvPipBoundsController(
-            Context context,
-            @ShellMainThread Handler mainHandler,
-            TvPipBoundsState tvPipBoundsState,
-            TvPipBoundsAlgorithm tvPipBoundsAlgorithm) {
-        return new TvPipBoundsController(
-                context,
-                SystemClock::uptimeMillis,
-                mainHandler,
-                tvPipBoundsState,
-                tvPipBoundsAlgorithm);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipSnapAlgorithm providePipSnapAlgorithm() {
-        return new PipSnapAlgorithm();
-    }
-
-    @WMSingleton
-    @Provides
-    static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
-            TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
-            PipSizeSpecHandler pipSizeSpecHandler) {
-        return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm,
-                pipSizeSpecHandler);
-    }
-
-    @WMSingleton
-    @Provides
-    static TvPipBoundsState provideTvPipBoundsState(Context context,
-            PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
-        return new TvPipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
-            PipDisplayLayoutState pipDisplayLayoutState) {
-        return new PipSizeSpecHandler(context, pipDisplayLayoutState);
-    }
-
-    // Handler needed for loadDrawableAsync() in PipControlsViewController
-    @WMSingleton
-    @Provides
-    static PipTransitionController provideTvPipTransition(
-            Context context,
-            ShellInit shellInit,
-            ShellTaskOrganizer shellTaskOrganizer,
-            Transitions transitions,
-            TvPipBoundsState tvPipBoundsState,
-            PipDisplayLayoutState pipDisplayLayoutState,
-            PipTransitionState pipTransitionState,
-            TvPipMenuController pipMenuController,
-            TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
-            PipAnimationController pipAnimationController,
-            PipSurfaceTransactionHelper pipSurfaceTransactionHelper) {
-        return new TvPipTransition(context, shellInit, shellTaskOrganizer, transitions,
-                tvPipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
-                tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
-                Optional.empty());
-    }
-
-    @WMSingleton
-    @Provides
-    static TvPipMenuController providesTvPipMenuController(
-            Context context,
-            TvPipBoundsState tvPipBoundsState,
-            SystemWindows systemWindows,
-            @ShellMainThread Handler mainHandler) {
-        return new TvPipMenuController(context, tvPipBoundsState, systemWindows, mainHandler);
-    }
-
-    @WMSingleton
-    @Provides
-    static TvPipNotificationController provideTvPipNotificationController(Context context,
-            PipMediaController pipMediaController,
-            PipParamsChangedForwarder pipParamsChangedForwarder) {
-        return new TvPipNotificationController(context, pipMediaController,
-                pipParamsChangedForwarder);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
-            pipSurfaceTransactionHelper) {
-        return new PipAnimationController(pipSurfaceTransactionHelper);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipTransitionState providePipTransitionState() {
-        return new PipTransitionState();
-    }
-
-    @WMSingleton
-    @Provides
-    static PipTaskOrganizer providePipTaskOrganizer(Context context,
-            TvPipMenuController tvPipMenuController,
-            SyncTransactionQueue syncTransactionQueue,
-            TvPipBoundsState tvPipBoundsState,
-            PipDisplayLayoutState pipDisplayLayoutState,
-            PipTransitionState pipTransitionState,
-            TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
-            PipAnimationController pipAnimationController,
-            PipTransitionController pipTransitionController,
-            PipParamsChangedForwarder pipParamsChangedForwarder,
-            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
-            Optional<SplitScreenController> splitScreenControllerOptional,
-            DisplayController displayController,
-            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new TvPipTaskOrganizer(context,
-                syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState,
-                tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
-                pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
-                splitScreenControllerOptional, displayController, pipUiEventLogger,
-                shellTaskOrganizer, mainExecutor);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipParamsChangedForwarder providePipParamsChangedForwarder() {
-        return new PipParamsChangedForwarder();
-    }
-
-    @WMSingleton
-    @Provides
-    static PipAppOpsListener providePipAppOpsListener(Context context,
-            PipTaskOrganizer pipTaskOrganizer,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new PipAppOpsListener(context, pipTaskOrganizer::removePip, mainExecutor);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 12d51f5..9facbd5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -25,11 +25,13 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.pip.TvPipModule;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -86,13 +88,14 @@
             TransactionPool transactionPool,
             IconProvider iconProvider,
             Optional<RecentTasksController> recentTasks,
+            LaunchAdjacentController launchAdjacentController,
             @ShellMainThread ShellExecutor mainExecutor,
             Handler mainHandler,
             SystemWindows systemWindows) {
         return new TvSplitScreenController(context, shellInit, shellCommandHandler, shellController,
                 shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController,
                 displayImeController, displayInsetsController, dragAndDropController, transitions,
-                transactionPool, iconProvider, recentTasks, mainExecutor, mainHandler,
-                systemWindows);
+                transactionPool, iconProvider, recentTasks, launchAdjacentController, mainExecutor,
+                mainHandler, systemWindows);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index c491fed..1a84f4b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -20,7 +20,6 @@
 
 import android.app.ActivityTaskManager;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.SystemProperties;
 import android.view.IWindowManager;
@@ -46,6 +45,7 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
@@ -74,11 +74,6 @@
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedController;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipMediaController;
-import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
-import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
@@ -102,13 +97,13 @@
 import com.android.wm.shell.unfold.UnfoldTransitionHandler;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
-import java.util.Optional;
-
 import dagger.BindsOptionalOf;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+
 /**
  * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
  * accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -127,6 +122,12 @@
 
     @WMSingleton
     @Provides
+    static FloatingContentCoordinator provideFloatingContentCoordinator() {
+        return new FloatingContentCoordinator();
+    }
+
+    @WMSingleton
+    @Provides
     static DisplayController provideDisplayController(Context context,
             IWindowManager wmService,
             ShellInit shellInit,
@@ -275,6 +276,13 @@
         return new WindowManagerShellWrapper(mainExecutor);
     }
 
+    @WMSingleton
+    @Provides
+    static LaunchAdjacentController provideLaunchAdjacentController(
+            SyncTransactionQueue syncQueue) {
+        return new LaunchAdjacentController(syncQueue);
+    }
+
     //
     // Back animation
     //
@@ -462,40 +470,6 @@
     }
 
     //
-    // Pip (optional feature)
-    //
-
-    @WMSingleton
-    @Provides
-    static FloatingContentCoordinator provideFloatingContentCoordinator() {
-        return new FloatingContentCoordinator();
-    }
-
-    // Needs handler for registering broadcast receivers
-    @WMSingleton
-    @Provides
-    static PipMediaController providePipMediaController(Context context,
-            @ShellMainThread Handler mainHandler) {
-        return new PipMediaController(context, mainHandler);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
-        return new PipSurfaceTransactionHelper(context);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
-            PackageManager packageManager) {
-        return new PipUiEventLogger(uiEventLogger, packageManager);
-    }
-
-    @BindsOptionalOf
-    abstract PipTouchHandler optionalPipTouchHandler();
-
-    //
     // Recent tasks
     //
 
@@ -829,8 +803,6 @@
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<BubbleController> bubblesOptional,
             Optional<SplitScreenController> splitScreenOptional,
-            Optional<Pip> pipOptional,
-            Optional<PipTouchHandler> pipTouchHandlerOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Optional<UnfoldAnimationController> unfoldAnimationController,
             Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index cff3172..065e7b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.dagger;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.LauncherApps;
 import android.os.Handler;
@@ -36,25 +37,29 @@
 import com.android.wm.shell.bubbles.BubbleDataRepository;
 import com.android.wm.shell.bubbles.BubbleLogger;
 import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.properties.ProdBubbleProperties;
+import com.android.wm.shell.bubbles.storage.BubblePersistentRepository;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.annotations.ShellAnimationThread;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.pip.PipModule;
 import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
 import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
+import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.freeform.FreeformComponents;
 import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -62,27 +67,7 @@
 import com.android.wm.shell.freeform.FreeformTaskTransitionObserver;
 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
 import com.android.wm.shell.onehanded.OneHandedController;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
-import com.android.wm.shell.pip.PipBoundsAlgorithm;
-import com.android.wm.shell.pip.PipBoundsState;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.PipMediaController;
-import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipSnapAlgorithm;
-import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
-import com.android.wm.shell.pip.PipTaskOrganizer;
-import com.android.wm.shell.pip.PipTransition;
 import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm;
-import com.android.wm.shell.pip.phone.PhonePipMenuController;
-import com.android.wm.shell.pip.phone.PipController;
-import com.android.wm.shell.pip.phone.PipMotionHelper;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -122,7 +107,10 @@
  * This module only defines Shell dependencies for handheld SystemUI implementation.  Common
  * dependencies should go into {@link WMShellBaseModule}.
  */
-@Module(includes = WMShellBaseModule.class)
+@Module(includes = {
+        WMShellBaseModule.class,
+        PipModule.class
+})
 public abstract class WMShellModule {
 
     //
@@ -180,11 +168,12 @@
             IWindowManager wmService) {
         return new BubbleController(context, shellInit, shellCommandHandler, shellController, data,
                 null /* synchronizer */, floatingContentCoordinator,
-                new BubbleDataRepository(context, launcherApps, mainExecutor),
+                new BubbleDataRepository(launcherApps, mainExecutor,
+                        new BubblePersistentRepository(context)),
                 statusBarService, windowManager, windowManagerShellWrapper, userManager,
                 launcherApps, logger, taskStackListener, organizer, positioner, displayController,
                 oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor,
-                taskViewTransitions, syncQueue, wmService);
+                taskViewTransitions, syncQueue, wmService, ProdBubbleProperties.INSTANCE);
     }
 
     //
@@ -197,33 +186,35 @@
             Context context,
             @ShellMainThread Handler mainHandler,
             @ShellMainThread Choreographer mainChoreographer,
+            ShellInit shellInit,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
+            ShellController shellController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopModeController> desktopModeController,
-            Optional<DesktopTasksController> desktopTasksController,
-            Optional<SplitScreenController> splitScreenController) {
+            Optional<DesktopTasksController> desktopTasksController) {
         if (DesktopModeStatus.isAnyEnabled()) {
             return new DesktopModeWindowDecorViewModel(
                     context,
                     mainHandler,
                     mainChoreographer,
+                    shellInit,
                     taskOrganizer,
                     displayController,
+                    shellController,
                     syncQueue,
                     transitions,
                     desktopModeController,
-                    desktopTasksController,
-                    splitScreenController);
+                    desktopTasksController);
         }
         return new CaptionWindowDecorViewModel(
-                    context,
-                    mainHandler,
-                    mainChoreographer,
-                    taskOrganizer,
-                    displayController,
-                    syncQueue);
+                context,
+                mainHandler,
+                mainChoreographer,
+                taskOrganizer,
+                displayController,
+                syncQueue);
     }
 
     //
@@ -263,8 +254,13 @@
     static FreeformTaskTransitionHandler provideFreeformTaskTransitionHandler(
             ShellInit shellInit,
             Transitions transitions,
-            WindowDecorViewModel windowDecorViewModel) {
-        return new FreeformTaskTransitionHandler(shellInit, transitions, windowDecorViewModel);
+            Context context,
+            WindowDecorViewModel windowDecorViewModel,
+            DisplayController displayController,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellAnimationThread ShellExecutor animExecutor) {
+        return new FreeformTaskTransitionHandler(shellInit, transitions, context,
+                windowDecorViewModel, displayController, mainExecutor, animExecutor);
     }
 
     @WMSingleton
@@ -327,200 +323,15 @@
             TransactionPool transactionPool,
             IconProvider iconProvider,
             Optional<RecentTasksController> recentTasks,
+            LaunchAdjacentController launchAdjacentController,
+            Optional<WindowDecorViewModel> windowDecorViewModel,
+            Optional<DesktopTasksController> desktopTasksController,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
                 shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
                 displayImeController, displayInsetsController, dragAndDropController, transitions,
-                transactionPool, iconProvider, recentTasks, mainExecutor);
-    }
-
-    //
-    // Pip
-    //
-
-    @WMSingleton
-    @Provides
-    static Optional<Pip> providePip(Context context,
-            ShellInit shellInit,
-            ShellCommandHandler shellCommandHandler,
-            ShellController shellController,
-            DisplayController displayController,
-            PipAnimationController pipAnimationController,
-            PipAppOpsListener pipAppOpsListener,
-            PipBoundsAlgorithm pipBoundsAlgorithm,
-            PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
-            PipBoundsState pipBoundsState,
-            PipSizeSpecHandler pipSizeSpecHandler,
-            PipDisplayLayoutState pipDisplayLayoutState,
-            PipMotionHelper pipMotionHelper,
-            PipMediaController pipMediaController,
-            PhonePipMenuController phonePipMenuController,
-            PipTaskOrganizer pipTaskOrganizer,
-            PipTransitionState pipTransitionState,
-            PipTouchHandler pipTouchHandler,
-            PipTransitionController pipTransitionController,
-            WindowManagerShellWrapper windowManagerShellWrapper,
-            TaskStackListenerImpl taskStackListener,
-            PipParamsChangedForwarder pipParamsChangedForwarder,
-            DisplayInsetsController displayInsetsController,
-            TabletopModeController pipTabletopController,
-            Optional<OneHandedController> oneHandedController,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return Optional.ofNullable(PipController.create(
-                context, shellInit, shellCommandHandler, shellController,
-                displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm,
-                pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipDisplayLayoutState,
-                pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
-                pipTransitionState, pipTouchHandler, pipTransitionController,
-                windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
-                displayInsetsController, pipTabletopController, oneHandedController, mainExecutor));
-    }
-
-    @WMSingleton
-    @Provides
-    static PipBoundsState providePipBoundsState(Context context,
-            PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
-        return new PipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipSnapAlgorithm providePipSnapAlgorithm() {
-        return new PipSnapAlgorithm();
-    }
-
-    @WMSingleton
-    @Provides
-    static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) {
-        return new PhonePipKeepClearAlgorithm(context);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
-            PipDisplayLayoutState pipDisplayLayoutState) {
-        return new PipSizeSpecHandler(context, pipDisplayLayoutState);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
-            PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
-            PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
-            PipSizeSpecHandler pipSizeSpecHandler) {
-        return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
-                pipKeepClearAlgorithm, pipSizeSpecHandler);
-    }
-
-    // Handler is used by Icon.loadDrawableAsync
-    @WMSingleton
-    @Provides
-    static PhonePipMenuController providesPipPhoneMenuController(Context context,
-            PipBoundsState pipBoundsState, PipMediaController pipMediaController,
-            SystemWindows systemWindows,
-            Optional<SplitScreenController> splitScreenOptional,
-            PipUiEventLogger pipUiEventLogger,
-            @ShellMainThread ShellExecutor mainExecutor,
-            @ShellMainThread Handler mainHandler) {
-        return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
-                systemWindows, splitScreenOptional, pipUiEventLogger, mainExecutor, mainHandler);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipTouchHandler providePipTouchHandler(Context context,
-            ShellInit shellInit,
-            PhonePipMenuController menuPhoneController,
-            PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipBoundsState pipBoundsState,
-            PipSizeSpecHandler pipSizeSpecHandler,
-            PipTaskOrganizer pipTaskOrganizer,
-            PipMotionHelper pipMotionHelper,
-            FloatingContentCoordinator floatingContentCoordinator,
-            PipUiEventLogger pipUiEventLogger,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
-                pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper,
-                floatingContentCoordinator, pipUiEventLogger, mainExecutor);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipTransitionState providePipTransitionState() {
-        return new PipTransitionState();
-    }
-
-    @WMSingleton
-    @Provides
-    static PipTaskOrganizer providePipTaskOrganizer(Context context,
-            SyncTransactionQueue syncTransactionQueue,
-            PipTransitionState pipTransitionState,
-            PipBoundsState pipBoundsState,
-            PipDisplayLayoutState pipDisplayLayoutState,
-            PipBoundsAlgorithm pipBoundsAlgorithm,
-            PhonePipMenuController menuPhoneController,
-            PipAnimationController pipAnimationController,
-            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
-            PipTransitionController pipTransitionController,
-            PipParamsChangedForwarder pipParamsChangedForwarder,
-            Optional<SplitScreenController> splitScreenControllerOptional,
-            DisplayController displayController,
-            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new PipTaskOrganizer(context,
-                syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
-                pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
-                pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
-                splitScreenControllerOptional, displayController, pipUiEventLogger,
-                shellTaskOrganizer, mainExecutor);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
-            pipSurfaceTransactionHelper) {
-        return new PipAnimationController(pipSurfaceTransactionHelper);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipTransitionController providePipTransitionController(Context context,
-            ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
-            PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState,
-            PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController,
-            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
-            Optional<SplitScreenController> splitScreenOptional) {
-        return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
-                pipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
-                pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
-                splitScreenOptional);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipAppOpsListener providePipAppOpsListener(Context context,
-            PipTouchHandler pipTouchHandler,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipMotionHelper providePipMotionHelper(Context context,
-            PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
-            PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
-            PipTransitionController pipTransitionController,
-            FloatingContentCoordinator floatingContentCoordinator) {
-        return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
-                menuController, pipSnapAlgorithm, pipTransitionController,
-                floatingContentCoordinator);
-    }
-
-    @WMSingleton
-    @Provides
-    static PipParamsChangedForwarder providePipParamsChangedForwarder() {
-        return new PipParamsChangedForwarder();
+                transactionPool, iconProvider, recentTasks, launchAdjacentController,
+                windowDecorViewModel, desktopTasksController, mainExecutor);
     }
 
     //
@@ -532,13 +343,16 @@
     static DefaultMixedHandler provideDefaultMixedHandler(
             ShellInit shellInit,
             Optional<SplitScreenController> splitScreenOptional,
-            Optional<PipTouchHandler> pipTouchHandlerOptional,
+            @Nullable PipTransitionController pipTransitionController,
             Optional<RecentsTransitionHandler> recentsTransitionHandler,
             KeyguardTransitionHandler keyguardTransitionHandler,
+            Optional<DesktopModeController> desktopModeController,
+            Optional<DesktopTasksController> desktopTasksController,
             Optional<UnfoldTransitionHandler> unfoldHandler,
             Transitions transitions) {
         return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
-                pipTouchHandlerOptional, recentsTransitionHandler, keyguardTransitionHandler,
+                pipTransitionController, recentsTransitionHandler,
+                keyguardTransitionHandler, desktopModeController, desktopTasksController,
                 unfoldHandler);
     }
 
@@ -573,13 +387,13 @@
         animators.add(fullscreenAnimator);
 
         return new UnfoldAnimationController(
-                        shellInit,
-                        transactionPool,
-                        progressProvider.get(),
-                        animators,
-                        unfoldTransitionHandler,
-                        mainExecutor
-                );
+                shellInit,
+                transactionPool,
+                progressProvider.get(),
+                animators,
+                unfoldTransitionHandler,
+                mainExecutor
+        );
     }
 
     @Provides
@@ -671,6 +485,7 @@
     static DesktopTasksController provideDesktopTasksController(
             Context context,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             DisplayController displayController,
             ShellTaskOrganizer shellTaskOrganizer,
@@ -679,13 +494,16 @@
             Transitions transitions,
             EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
             ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
+            ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
             @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+            LaunchAdjacentController launchAdjacentController,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
-        return new DesktopTasksController(context, shellInit, shellController, displayController,
-                shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions,
-                enterDesktopTransitionHandler, exitDesktopTransitionHandler,
-                desktopModeTaskRepository, mainExecutor);
+        return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
+                displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
+                transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler,
+                toggleResizeDesktopTaskTransitionHandler, desktopModeTaskRepository,
+                launchAdjacentController, mainExecutor);
     }
 
     @WMSingleton
@@ -697,6 +515,13 @@
 
     @WMSingleton
     @Provides
+    static ToggleResizeDesktopTaskTransitionHandler provideToggleResizeDesktopTaskTransitionHandler(
+            Transitions transitions) {
+        return new ToggleResizeDesktopTaskTransitionHandler(transitions);
+    }
+
+    @WMSingleton
+    @Provides
     static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
             Transitions transitions,
             Context context
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
new file mode 100644
index 0000000..9bf973f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.dagger.pip;
+
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
+import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.dagger.WMShellBaseModule;
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
+import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipParamsChangedForwarder;
+import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransition;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipTransitionState;
+import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm;
+import com.android.wm.shell.pip.phone.PhonePipMenuController;
+import com.android.wm.shell.pip.phone.PipController;
+import com.android.wm.shell.pip.phone.PipMotionHelper;
+import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+import dagger.Module;
+import dagger.Provides;
+
+import java.util.Optional;
+
+/**
+ * Provides dependencies from {@link com.android.wm.shell.pip}, this implementation is meant to be
+ * replaced by the sibling {@link Pip2Module}.
+ */
+@Module(includes = {
+        Pip1SharedModule.class,
+        WMShellBaseModule.class
+})
+public abstract class Pip1Module {
+    @WMSingleton
+    @Provides
+    static Optional<Pip> providePip1(Context context,
+            ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
+            ShellController shellController,
+            DisplayController displayController,
+            PipAnimationController pipAnimationController,
+            PipAppOpsListener pipAppOpsListener,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
+            PipBoundsState pipBoundsState,
+            PipDisplayLayoutState pipDisplayLayoutState,
+            PipMotionHelper pipMotionHelper,
+            PipMediaController pipMediaController,
+            PhonePipMenuController phonePipMenuController,
+            PipTaskOrganizer pipTaskOrganizer,
+            PipTransitionState pipTransitionState,
+            PipTouchHandler pipTouchHandler,
+            PipTransitionController pipTransitionController,
+            WindowManagerShellWrapper windowManagerShellWrapper,
+            TaskStackListenerImpl taskStackListener,
+            PipParamsChangedForwarder pipParamsChangedForwarder,
+            DisplayInsetsController displayInsetsController,
+            TabletopModeController pipTabletopController,
+            Optional<OneHandedController> oneHandedController,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        if (PipUtils.isPip2ExperimentEnabled()) {
+            return Optional.empty();
+        } else {
+            return Optional.ofNullable(PipController.create(
+                    context, shellInit, shellCommandHandler, shellController,
+                    displayController, pipAnimationController, pipAppOpsListener,
+                    pipBoundsAlgorithm,
+                    pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState,
+                    pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+                    pipTransitionState, pipTouchHandler, pipTransitionController,
+                    windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+                    displayInsetsController, pipTabletopController, oneHandedController,
+                    mainExecutor));
+        }
+    }
+
+    @WMSingleton
+    @Provides
+    static PipBoundsState providePipBoundsState(Context context,
+            SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
+        return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipSnapAlgorithm providePipSnapAlgorithm() {
+        return new PipSnapAlgorithm();
+    }
+
+    @WMSingleton
+    @Provides
+    static PhonePipKeepClearAlgorithm providePhonePipKeepClearAlgorithm(Context context) {
+        return new PhonePipKeepClearAlgorithm(context);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
+            PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
+            PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
+            PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
+        return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
+                pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource);
+    }
+
+    // Handler is used by Icon.loadDrawableAsync
+    @WMSingleton
+    @Provides
+    static PhonePipMenuController providesPipPhoneMenuController(Context context,
+            PipBoundsState pipBoundsState, PipMediaController pipMediaController,
+            SystemWindows systemWindows,
+            Optional<SplitScreenController> splitScreenOptional,
+            PipUiEventLogger pipUiEventLogger,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler) {
+        return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
+                systemWindows, splitScreenOptional, pipUiEventLogger, mainExecutor, mainHandler);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipTouchHandler providePipTouchHandler(Context context,
+            ShellInit shellInit,
+            PhonePipMenuController menuPhoneController,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipBoundsState pipBoundsState,
+            SizeSpecSource sizeSpecSource,
+            PipTaskOrganizer pipTaskOrganizer,
+            PipMotionHelper pipMotionHelper,
+            FloatingContentCoordinator floatingContentCoordinator,
+            PipUiEventLogger pipUiEventLogger,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
+                pipBoundsState, sizeSpecSource, pipTaskOrganizer, pipMotionHelper,
+                floatingContentCoordinator, pipUiEventLogger, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipTransitionState providePipTransitionState() {
+        return new PipTransitionState();
+    }
+
+    @WMSingleton
+    @Provides
+    static PipTaskOrganizer providePipTaskOrganizer(Context context,
+            SyncTransactionQueue syncTransactionQueue,
+            PipTransitionState pipTransitionState,
+            PipBoundsState pipBoundsState,
+            PipDisplayLayoutState pipDisplayLayoutState,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PhonePipMenuController menuPhoneController,
+            PipAnimationController pipAnimationController,
+            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+            PipTransitionController pipTransitionController,
+            PipParamsChangedForwarder pipParamsChangedForwarder,
+            Optional<SplitScreenController> splitScreenControllerOptional,
+            DisplayController displayController,
+            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new PipTaskOrganizer(context,
+                syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
+                pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
+                pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+                splitScreenControllerOptional, displayController, pipUiEventLogger,
+                shellTaskOrganizer, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
+            pipSurfaceTransactionHelper) {
+        return new PipAnimationController(pipSurfaceTransactionHelper);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipTransition providePipTransition(Context context,
+            ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
+            PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState,
+            PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController,
+            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+            Optional<SplitScreenController> splitScreenOptional) {
+        return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
+                pipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
+                pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
+                splitScreenOptional);
+    }
+
+    @WMSingleton
+    @Provides
+    static SizeSpecSource provideSizeSpecSource(Context context,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        return new PhoneSizeSpecSource(context, pipDisplayLayoutState);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipAppOpsListener providePipAppOpsListener(Context context,
+            PipTouchHandler pipTouchHandler,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipMotionHelper providePipMotionHelper(Context context,
+            PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
+            PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
+            PipTransitionController pipTransitionController,
+            FloatingContentCoordinator floatingContentCoordinator) {
+        return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
+                menuController, pipSnapAlgorithm, pipTransitionController,
+                floatingContentCoordinator);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipParamsChangedForwarder providePipParamsChangedForwarder() {
+        return new PipParamsChangedForwarder();
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
new file mode 100644
index 0000000..e8fae24
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.dagger.pip;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipUiEventLogger;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides shared dependencies from {@link com.android.wm.shell.pip}, this implementation is
+ * shared with {@link TvPipModule} and possibly other form factors.
+ */
+@Module
+public abstract class Pip1SharedModule {
+    // Needs handler for registering broadcast receivers
+    @WMSingleton
+    @Provides
+    static PipMediaController providePipMediaController(Context context,
+            @ShellMainThread Handler mainHandler) {
+        return new PipMediaController(context, mainHandler);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
+        return new PipSurfaceTransactionHelper(context);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
+            PackageManager packageManager) {
+        return new PipUiEventLogger(uiEventLogger, packageManager);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
new file mode 100644
index 0000000..c7c6e8a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.dagger.pip;
+
+import android.annotation.Nullable;
+
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip2.PipTransition;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be
+ * the successor of its sibling {@link Pip1SharedModule}.
+ */
+@Module
+public abstract class Pip2Module {
+    @WMSingleton
+    @Provides
+    @Nullable
+    static PipTransition providePipTransition() {
+        return null;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
new file mode 100644
index 0000000..04032bb1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.dagger.pip;
+
+import android.annotation.Nullable;
+
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipUtils;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides dependencies for external components / modules reference PiP and extracts away the
+ * selection of legacy and new PiP implementation.
+ */
+@Module(includes = {
+        Pip1Module.class,
+        Pip2Module.class
+})
+public abstract class PipModule {
+    @WMSingleton
+    @Provides
+    static PipTransitionController providePipTransitionController(
+            com.android.wm.shell.pip.PipTransition legacyPipTransition,
+            @Nullable com.android.wm.shell.pip2.PipTransition newPipTransition) {
+        if (PipUtils.isPip2ExperimentEnabled() && newPipTransition != null) {
+            return newPipTransition;
+        } else {
+            return legacyPipTransition;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
new file mode 100644
index 0000000..80ffbb0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.dagger.pip;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.SystemClock;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.pip.LegacySizeSpecSource;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.dagger.WMShellBaseModule;
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
+import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipParamsChangedForwarder;
+import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipTransitionState;
+import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
+import com.android.wm.shell.pip.tv.TvPipBoundsController;
+import com.android.wm.shell.pip.tv.TvPipBoundsState;
+import com.android.wm.shell.pip.tv.TvPipController;
+import com.android.wm.shell.pip.tv.TvPipMenuController;
+import com.android.wm.shell.pip.tv.TvPipNotificationController;
+import com.android.wm.shell.pip.tv.TvPipTaskOrganizer;
+import com.android.wm.shell.pip.tv.TvPipTransition;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+import dagger.Module;
+import dagger.Provides;
+
+import java.util.Optional;
+
+/**
+ * Provides TV specific dependencies for Pip.
+ */
+@Module(includes = {
+        WMShellBaseModule.class,
+        Pip1SharedModule.class})
+public abstract class TvPipModule {
+    @WMSingleton
+    @Provides
+    static Optional<Pip> providePip(
+            Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            TvPipBoundsState tvPipBoundsState,
+            PipDisplayLayoutState pipDisplayLayoutState,
+            TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+            TvPipBoundsController tvPipBoundsController,
+            PipAppOpsListener pipAppOpsListener,
+            PipTaskOrganizer pipTaskOrganizer,
+            TvPipMenuController tvPipMenuController,
+            PipMediaController pipMediaController,
+            PipTransitionController pipTransitionController,
+            TvPipNotificationController tvPipNotificationController,
+            TaskStackListenerImpl taskStackListener,
+            PipParamsChangedForwarder pipParamsChangedForwarder,
+            DisplayController displayController,
+            WindowManagerShellWrapper windowManagerShellWrapper,
+            @ShellMainThread Handler mainHandler, // needed for registerReceiverForAllUsers()
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return Optional.of(
+                TvPipController.create(
+                        context,
+                        shellInit,
+                        shellController,
+                        tvPipBoundsState,
+                        pipDisplayLayoutState,
+                        tvPipBoundsAlgorithm,
+                        tvPipBoundsController,
+                        pipAppOpsListener,
+                        pipTaskOrganizer,
+                        pipTransitionController,
+                        tvPipMenuController,
+                        pipMediaController,
+                        tvPipNotificationController,
+                        taskStackListener,
+                        pipParamsChangedForwarder,
+                        displayController,
+                        windowManagerShellWrapper,
+                        mainHandler,
+                        mainExecutor));
+    }
+
+    @WMSingleton
+    @Provides
+    static TvPipBoundsController provideTvPipBoundsController(
+            Context context,
+            @ShellMainThread Handler mainHandler,
+            TvPipBoundsState tvPipBoundsState,
+            TvPipBoundsAlgorithm tvPipBoundsAlgorithm) {
+        return new TvPipBoundsController(
+                context,
+                SystemClock::uptimeMillis,
+                mainHandler,
+                tvPipBoundsState,
+                tvPipBoundsAlgorithm);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipSnapAlgorithm providePipSnapAlgorithm() {
+        return new PipSnapAlgorithm();
+    }
+
+    @WMSingleton
+    @Provides
+    static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
+            TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
+            PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
+        return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm,
+                pipDisplayLayoutState, sizeSpecSource);
+    }
+
+    @WMSingleton
+    @Provides
+    static TvPipBoundsState provideTvPipBoundsState(Context context,
+            SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
+        return new TvPipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
+    }
+
+    @WMSingleton
+    @Provides
+    static SizeSpecSource provideSizeSpecSource(Context context,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        return new LegacySizeSpecSource(context, pipDisplayLayoutState);
+    }
+
+    // Handler needed for loadDrawableAsync() in PipControlsViewController
+    @WMSingleton
+    @Provides
+    static PipTransitionController provideTvPipTransition(
+            Context context,
+            ShellInit shellInit,
+            ShellTaskOrganizer shellTaskOrganizer,
+            Transitions transitions,
+            TvPipBoundsState tvPipBoundsState,
+            PipDisplayLayoutState pipDisplayLayoutState,
+            PipTransitionState pipTransitionState,
+            TvPipMenuController pipMenuController,
+            TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+            PipAnimationController pipAnimationController,
+            PipSurfaceTransactionHelper pipSurfaceTransactionHelper) {
+        return new TvPipTransition(context, shellInit, shellTaskOrganizer, transitions,
+                tvPipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController,
+                tvPipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
+                Optional.empty());
+    }
+
+    @WMSingleton
+    @Provides
+    static TvPipMenuController providesTvPipMenuController(
+            Context context,
+            TvPipBoundsState tvPipBoundsState,
+            SystemWindows systemWindows,
+            @ShellMainThread Handler mainHandler) {
+        return new TvPipMenuController(context, tvPipBoundsState, systemWindows, mainHandler);
+    }
+
+    @WMSingleton
+    @Provides
+    static TvPipNotificationController provideTvPipNotificationController(Context context,
+            PipMediaController pipMediaController,
+            PipParamsChangedForwarder pipParamsChangedForwarder) {
+        return new TvPipNotificationController(context, pipMediaController,
+                pipParamsChangedForwarder);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
+            pipSurfaceTransactionHelper) {
+        return new PipAnimationController(pipSurfaceTransactionHelper);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipTransitionState providePipTransitionState() {
+        return new PipTransitionState();
+    }
+
+    @WMSingleton
+    @Provides
+    static PipTaskOrganizer providePipTaskOrganizer(Context context,
+            TvPipMenuController tvPipMenuController,
+            SyncTransactionQueue syncTransactionQueue,
+            TvPipBoundsState tvPipBoundsState,
+            PipDisplayLayoutState pipDisplayLayoutState,
+            PipTransitionState pipTransitionState,
+            TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+            PipAnimationController pipAnimationController,
+            PipTransitionController pipTransitionController,
+            PipParamsChangedForwarder pipParamsChangedForwarder,
+            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+            Optional<SplitScreenController> splitScreenControllerOptional,
+            DisplayController displayController,
+            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new TvPipTaskOrganizer(context,
+                syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState,
+                tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
+                pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+                splitScreenControllerOptional, displayController, pipUiEventLogger,
+                shellTaskOrganizer, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipParamsChangedForwarder providePipParamsChangedForwarder() {
+        return new PipParamsChangedForwarder();
+    }
+
+    @WMSingleton
+    @Provides
+    static PipAppOpsListener providePipAppOpsListener(Context context,
+            PipTaskOrganizer pipTaskOrganizer,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new PipAppOpsListener(context, pipTaskOrganizer::removePip, mainExecutor);
+    }
+}
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 b9d2be2..5b24d7a 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
@@ -33,6 +33,7 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.graphics.Region;
 import android.net.Uri;
@@ -414,6 +415,25 @@
     }
 
     /**
+     * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
+     * This is intended to be used when desktop mode is part of another animation but isn't, itself,
+     * animating.
+     */
+    public void syncSurfaceState(@NonNull TransitionInfo info,
+            SurfaceControl.Transaction finishTransaction) {
+        // Add rounded corners to freeform windows
+        final TypedArray ta = mContext.obtainStyledAttributes(
+                new int[]{android.R.attr.dialogCornerRadius});
+        final int cornerRadius = ta.getDimensionPixelSize(0, 0);
+        ta.recycle();
+        for (TransitionInfo.Change change: info.getChanges()) {
+            if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+                finishTransaction.setCornerRadius(change.getLeash(), cornerRadius);
+            }
+        }
+    }
+
+    /**
      * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
      */
     private final class SettingsObserver extends ContentObserver {
@@ -500,6 +520,11 @@
         }
 
         @Override
+        public void showDesktopApp(int taskId) throws RemoteException {
+            // TODO
+        }
+
+        @Override
         public int getVisibleTaskCount(int displayId) throws RemoteException {
             int[] result = new int[1];
             executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount",
@@ -508,5 +533,25 @@
             );
             return result[0];
         }
+
+        @Override
+        public void onDesktopSplitSelectAnimComplete(RunningTaskInfo taskInfo) {
+
+        }
+
+        @Override
+        public void stashDesktopApps(int displayId) throws RemoteException {
+            // Stashing of desktop apps not needed. Apps always launch on desktop
+        }
+
+        @Override
+        public void hideStashedDesktopApps(int displayId) throws RemoteException {
+            // Stashing of desktop apps not needed. Apps always launch on desktop
+        }
+
+        @Override
+        public void setTaskListener(IDesktopTaskListener listener) throws RemoteException {
+            // TODO(b/261234402): move visibility from sysui state to listener
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 76ca68b..517f9f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -54,6 +54,15 @@
     public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean(
             "persist.wm.debug.desktop_change_display", false);
 
+
+    /**
+     * Flag to indicate that desktop stashing is enabled.
+     * When enabled, swiping home from desktop stashes the open apps. Next app that launches,
+     * will be added to the desktop.
+     */
+    private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean(
+            "persist.wm.debug.desktop_stashing", false);
+
     /**
      * Return {@code true} if desktop mode support is enabled
      */
@@ -84,6 +93,13 @@
     }
 
     /**
+     * Return {@code true} if desktop task stashing is enabled when going home.
+     * Allows users to use home screen to add tasks to desktop.
+     */
+    public static boolean isStashingEnabled() {
+        return IS_STASHING_ENABLED;
+    }
+    /**
      * Check if desktop mode is active
      *
      * @return {@code true} if active
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 3ab175d..711df0d 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
@@ -25,6 +25,7 @@
 import androidx.core.util.valueIterator
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.util.KtProtoLog
+import java.io.PrintWriter
 import java.util.concurrent.Executor
 import java.util.function.Consumer
 
@@ -43,6 +44,7 @@
          */
         val activeTasks: ArraySet<Int> = ArraySet(),
         val visibleTasks: ArraySet<Int> = ArraySet(),
+        var stashed: Boolean = false
     )
 
     // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
@@ -85,8 +87,10 @@
         visibleTasksListeners[visibleTasksListener] = executor
         displayData.keyIterator().forEach { displayId ->
             val visibleTasks = getVisibleTaskCount(displayId)
+            val stashed = isStashed(displayId)
             executor.execute {
                 visibleTasksListener.onVisibilityChanged(displayId, visibleTasks > 0)
+                visibleTasksListener.onStashedChanged(displayId, stashed)
             }
         }
     }
@@ -312,6 +316,52 @@
     }
 
     /**
+     * Update stashed status on display with id [displayId]
+     */
+    fun setStashed(displayId: Int, stashed: Boolean) {
+        val data = displayData.getOrCreate(displayId)
+        val oldValue = data.stashed
+        data.stashed = stashed
+        if (oldValue != stashed) {
+            KtProtoLog.d(
+                    WM_SHELL_DESKTOP_MODE,
+                    "DesktopTaskRepo: mark stashed=%b displayId=%d",
+                    stashed,
+                    displayId
+            )
+            visibleTasksListeners.forEach { (listener, executor) ->
+                executor.execute { listener.onStashedChanged(displayId, stashed) }
+            }
+        }
+    }
+
+    /**
+     * Check if display with id [displayId] has desktop tasks stashed
+     */
+    fun isStashed(displayId: Int): Boolean {
+        return displayData[displayId]?.stashed ?: false
+    }
+
+    internal fun dump(pw: PrintWriter, prefix: String) {
+        val innerPrefix = "$prefix  "
+        pw.println("${prefix}DesktopModeTaskRepository")
+        dumpDisplayData(pw, innerPrefix)
+        pw.println("${innerPrefix}freeformTasksInZOrder=${freeformTasksInZOrder.toDumpString()}")
+        pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}")
+        pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}")
+    }
+
+    private fun dumpDisplayData(pw: PrintWriter, prefix: String) {
+        val innerPrefix = "$prefix  "
+        displayData.forEach { displayId, data ->
+            pw.println("${prefix}Display $displayId:")
+            pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}")
+            pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}")
+            pw.println("${innerPrefix}stashed=${data.stashed}")
+        }
+    }
+
+    /**
      * Defines interface for classes that can listen to changes for active tasks in desktop mode.
      */
     interface ActiveTasksListener {
@@ -331,5 +381,15 @@
          */
         @JvmDefault
         fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {}
+
+        /**
+         * Called when the desktop stashed status changes.
+         */
+        @JvmDefault
+        fun onStashedChanged(displayId: Int, stashed: Boolean) {}
     }
 }
+
+private fun <T> Iterable<T>.toDumpString(): String {
+    return joinToString(separator = ", ", prefix = "[", postfix = "]")
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 91bb155..1d46e75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -16,19 +16,23 @@
 
 package com.android.wm.shell.desktopmode
 
+import android.R
 import android.app.ActivityManager.RunningTaskInfo
 import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.app.WindowConfiguration.WindowingMode
 import android.content.Context
+import android.content.res.TypedArray
 import android.graphics.Point
 import android.graphics.Rect
 import android.graphics.Region
 import android.os.IBinder
 import android.os.SystemProperties
+import android.util.DisplayMetrics.DENSITY_DEFAULT
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.view.WindowManager.TRANSIT_NONE
@@ -36,7 +40,6 @@
 import android.view.WindowManager.TRANSIT_TO_FRONT
 import android.window.TransitionInfo
 import android.window.TransitionRequestInfo
-import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
 import androidx.annotation.BinderThread
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -44,18 +47,25 @@
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.ExecutorUtils
 import com.android.wm.shell.common.ExternalInterfaceBinder
+import com.android.wm.shell.common.LaunchAdjacentController
 import com.android.wm.shell.common.RemoteCallable
 import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SingleInstanceRemoteListener
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.common.annotations.ExternalThread
 import com.android.wm.shell.common.annotations.ShellMainThread
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.splitscreen.SplitScreenController
+import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.sysui.ShellSharedConstants
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.util.KtProtoLog
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
+import java.io.PrintWriter
 import java.util.concurrent.Executor
 import java.util.function.Consumer
 
@@ -63,6 +73,7 @@
 class DesktopTasksController(
         private val context: Context,
         shellInit: ShellInit,
+        private val shellCommandHandler: ShellCommandHandler,
         private val shellController: ShellController,
         private val displayController: DisplayController,
         private val shellTaskOrganizer: ShellTaskOrganizer,
@@ -71,7 +82,10 @@
         private val transitions: Transitions,
         private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
         private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
+        private val toggleResizeDesktopTaskTransitionHandler:
+        ToggleResizeDesktopTaskTransitionHandler,
         private val desktopModeTaskRepository: DesktopModeTaskRepository,
+        private val launchAdjacentController: LaunchAdjacentController,
         @ShellMainThread private val mainExecutor: ShellExecutor
 ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
 
@@ -82,6 +96,18 @@
         visualIndicator?.releaseVisualIndicator(t)
         visualIndicator = null
     }
+    private val taskVisibilityListener = object : VisibleTasksListener {
+        override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+            launchAdjacentController.launchAdjacentEnabled = !hasVisibleFreeformTasks
+        }
+    }
+
+    private val transitionAreaHeight
+        get() = context.resources.getDimensionPixelSize(
+                com.android.wm.shell.R.dimen.desktop_mode_transition_area_height)
+
+    // This is public to avoid cyclic dependency; it is set by SplitScreenController
+    lateinit var splitScreenController: SplitScreenController
 
     init {
         desktopMode = DesktopModeImpl()
@@ -92,12 +118,14 @@
 
     private fun onInit() {
         KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
+        shellCommandHandler.addDumpCallback(this::dump, this)
         shellController.addExternalInterface(
             ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
             { createExternalInterface() },
             this
         )
         transitions.addHandler(this)
+        desktopModeTaskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor)
     }
 
     /** Show all tasks, that are part of the desktop, on top of launcher */
@@ -118,78 +146,139 @@
         }
     }
 
+    /**
+     * Stash desktop tasks on display with id [displayId].
+     *
+     * When desktop tasks are stashed, launcher home screen icons are fully visible. New apps
+     * launched in this state will be added to the desktop. Existing desktop tasks will be brought
+     * back to front during the launch.
+     */
+    fun stashDesktopApps(displayId: Int) {
+        if (DesktopModeStatus.isStashingEnabled()) {
+            KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps")
+            desktopModeTaskRepository.setStashed(displayId, true)
+        }
+    }
+
+    /**
+     * Clear the stashed state for the given display
+     */
+    fun hideStashedDesktopApps(displayId: Int) {
+        if (DesktopModeStatus.isStashingEnabled()) {
+            KtProtoLog.v(
+                    WM_SHELL_DESKTOP_MODE,
+                    "DesktopTasksController: hideStashedApps displayId=%d",
+                    displayId
+            )
+            desktopModeTaskRepository.setStashed(displayId, false)
+        }
+    }
+
     /** Get number of tasks that are marked as visible */
     fun getVisibleTaskCount(displayId: Int): Int {
         return desktopModeTaskRepository.getVisibleTaskCount(displayId)
     }
 
     /** Move a task with given `taskId` to desktop */
-    fun moveToDesktop(taskId: Int) {
-        shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task) }
+    fun moveToDesktop(
+            decor: DesktopModeWindowDecoration,
+            taskId: Int,
+            wct: WindowContainerTransaction = WindowContainerTransaction()
+    ) {
+        shellTaskOrganizer.getRunningTaskInfo(taskId)?.let {
+            task -> moveToDesktop(decor, task, wct)
+        }
     }
 
-    /** Move a task to desktop */
-    fun moveToDesktop(task: RunningTaskInfo) {
+    /**
+     * Move a task to desktop
+     */
+    fun moveToDesktop(
+            decor: DesktopModeWindowDecoration,
+            task: RunningTaskInfo,
+            wct: WindowContainerTransaction = WindowContainerTransaction()
+    ) {
         KtProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
             "DesktopTasksController: moveToDesktop taskId=%d",
             task.taskId
         )
-        val wct = WindowContainerTransaction()
         // Bring other apps to front first
         bringDesktopAppsToFront(task.displayId, wct)
-        addMoveToDesktopChanges(wct, task.token)
+        addMoveToDesktopChanges(wct, task)
+
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+            enterDesktopTaskTransitionHandler.moveToDesktop(wct, decor)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
         }
     }
 
     /**
-     * Moves a single task to freeform and sets the taskBounds to the passed in bounds,
-     * startBounds
+     * The first part of the animated move to desktop transition. Applies the changes to move task
+     * to desktop mode and sets the taskBounds to the passed in bounds, startBounds. This is
+     * followed with a call to {@link finishMoveToDesktop} or {@link cancelMoveToDesktop}.
      */
-    fun moveToFreeform(taskInfo: RunningTaskInfo, startBounds: Rect) {
+    fun startMoveToDesktop(
+            taskInfo: RunningTaskInfo,
+            startBounds: Rect,
+            dragToDesktopValueAnimator: MoveToDesktopAnimator
+    ) {
         KtProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
-            "DesktopTasksController: moveToFreeform with bounds taskId=%d",
+            "DesktopTasksController: startMoveToDesktop taskId=%d",
             taskInfo.taskId
         )
         val wct = WindowContainerTransaction()
         moveHomeTaskToFront(wct)
-        addMoveToDesktopChanges(wct, taskInfo.getToken())
+        addMoveToDesktopChanges(wct, taskInfo)
         wct.setBounds(taskInfo.token, startBounds)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            enterDesktopTaskTransitionHandler.startTransition(
-                    Transitions.TRANSIT_ENTER_FREEFORM, wct, mOnAnimationFinishedCallback)
+            enterDesktopTaskTransitionHandler.startMoveToDesktop(wct, dragToDesktopValueAnimator,
+                    mOnAnimationFinishedCallback)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
         }
     }
 
-    /** Brings apps to front and sets freeform task bounds */
-    private fun moveToDesktopWithAnimation(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
+    /**
+     * The second part of the animated move to desktop transition, called after
+     * {@link startMoveToDesktop}. Brings apps to front and sets freeform task bounds.
+     */
+    private fun finalizeMoveToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
         KtProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
-            "DesktopTasksController: moveToDesktop with animation taskId=%d",
+            "DesktopTasksController: finalizeMoveToDesktop taskId=%d",
             taskInfo.taskId
         )
         val wct = WindowContainerTransaction()
         bringDesktopAppsToFront(taskInfo.displayId, wct)
-        addMoveToDesktopChanges(wct, taskInfo.getToken())
+        addMoveToDesktopChanges(wct, taskInfo)
         wct.setBounds(taskInfo.token, freeformBounds)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            enterDesktopTaskTransitionHandler.startTransition(
-                Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct, mOnAnimationFinishedCallback)
+            enterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct,
+                    mOnAnimationFinishedCallback)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
             releaseVisualIndicator()
         }
     }
 
+    /**
+     * Perform needed cleanup transaction once animation is complete. Bounds need to be set
+     * here instead of initial wct to both avoid flicker and to have task bounds to use for
+     * the staging animation.
+     *
+     * @param taskInfo task entering split that requires a bounds update
+     */
+    fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
+        val wct = WindowContainerTransaction()
+        wct.setBounds(taskInfo.token, Rect())
+        shellTaskOrganizer.applyTransaction(wct)
+    }
+
     /** Move a task with given `taskId` to fullscreen */
     fun moveToFullscreen(taskId: Int) {
         shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToFullscreen(task) }
@@ -204,7 +293,7 @@
         )
 
         val wct = WindowContainerTransaction()
-        addMoveToFullscreenChanges(wct, task.token)
+        addMoveToFullscreenChanges(wct, task)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
         } else {
@@ -213,21 +302,30 @@
     }
 
     /**
-     * Move a task to fullscreen after being dragged from fullscreen and released back into
-     * status bar area
+     * The second part of the animated move to desktop transition, called after
+     * {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen
+     * and released back into status bar area.
      */
-    fun cancelMoveToFreeform(task: RunningTaskInfo, position: Point) {
+    fun cancelMoveToDesktop(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) {
         KtProtoLog.v(
-                WM_SHELL_DESKTOP_MODE,
-                "DesktopTasksController: cancelMoveToFreeform taskId=%d",
-                task.taskId
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopTasksController: cancelMoveToDesktop taskId=%d",
+            task.taskId
         )
         val wct = WindowContainerTransaction()
-        addMoveToFullscreenChanges(wct, task.token)
+        wct.setBounds(task.token, Rect())
+
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, position,
-                    mOnAnimationFinishedCallback)
+            enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct,
+                    moveToDesktopAnimator) { t ->
+                val callbackWCT = WindowContainerTransaction()
+                visualIndicator?.releaseVisualIndicator(t)
+                visualIndicator = null
+                addMoveToFullscreenChanges(callbackWCT, task)
+                transitions.startTransition(TRANSIT_CHANGE, callbackWCT, null /* handler */)
+            }
         } else {
+            addMoveToFullscreenChanges(wct, task)
             shellTaskOrganizer.applyTransaction(wct)
             releaseVisualIndicator()
         }
@@ -240,7 +338,7 @@
                 task.taskId
         )
         val wct = WindowContainerTransaction()
-        addMoveToFullscreenChanges(wct, task.token)
+        addMoveToFullscreenChanges(wct, task)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             exitDesktopTaskTransitionHandler.startTransition(
@@ -252,6 +350,11 @@
     }
 
     /** Move a task to the front */
+    fun moveTaskToFront(taskId: Int) {
+        shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveTaskToFront(task) }
+    }
+
+    /** Move a task to the front */
     fun moveTaskToFront(taskInfo: RunningTaskInfo) {
         KtProtoLog.v(
             WM_SHELL_DESKTOP_MODE,
@@ -331,6 +434,49 @@
         }
     }
 
+    /** Quick-resizes a desktop task, toggling between the stable bounds and the default bounds. */
+    fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo, windowDecor: DesktopModeWindowDecoration) {
+        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+
+        val stableBounds = Rect()
+        displayLayout.getStableBounds(stableBounds)
+        val destinationBounds = Rect()
+        if (taskInfo.configuration.windowConfiguration.bounds == stableBounds) {
+            // The desktop task is currently occupying the whole stable bounds, toggle to the
+            // default bounds.
+            getDefaultDesktopTaskBounds(
+                density = taskInfo.configuration.densityDpi.toFloat() / DENSITY_DEFAULT,
+                stableBounds = stableBounds,
+                outBounds = destinationBounds
+            )
+        } else {
+            // Toggle to the stable bounds.
+            destinationBounds.set(stableBounds)
+        }
+
+        val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            toggleResizeDesktopTaskTransitionHandler.startTransition(
+                wct,
+                taskInfo.taskId,
+                windowDecor
+            )
+        } else {
+            shellTaskOrganizer.applyTransaction(wct)
+        }
+    }
+
+    private fun getDefaultDesktopTaskBounds(density: Float, stableBounds: Rect, outBounds: Rect) {
+        val width = (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f).toInt()
+        val height = (DESKTOP_MODE_DEFAULT_HEIGHT_DP * density + 0.5f).toInt()
+        outBounds.set(0, 0, width, height)
+        // Center the task in stable bounds
+        outBounds.offset(
+            stableBounds.centerX() - outBounds.centerX(),
+            stableBounds.centerY() - outBounds.centerY()
+        )
+    }
+
     /**
      * Get windowing move for a given `taskId`
      *
@@ -397,87 +543,204 @@
         transition: IBinder,
         request: TransitionRequestInfo
     ): WindowContainerTransaction? {
+        KtProtoLog.v(
+            WM_SHELL_DESKTOP_MODE,
+            "DesktopTasksController: handleRequest request=%s",
+            request
+        )
         // Check if we should skip handling this transition
+        var reason = ""
+        val triggerTask = request.triggerTask
         val shouldHandleRequest =
             when {
                 // Only handle open or to front transitions
-                request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> false
+                request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> {
+                    reason = "transition type not handled (${request.type})"
+                    false
+                }
                 // Only handle when it is a task transition
-                request.triggerTask == null -> false
+                triggerTask == null -> {
+                    reason = "triggerTask is null"
+                    false
+                }
                 // Only handle standard type tasks
-                request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> false
+                triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> {
+                    reason = "activityType not handled (${triggerTask.activityType})"
+                    false
+                }
                 // Only handle fullscreen or freeform tasks
-                request.triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
-                        request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> false
+                triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
+                        triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> {
+                    reason = "windowingMode not handled (${triggerTask.windowingMode})"
+                    false
+                }
                 // Otherwise process it
                 else -> true
             }
 
         if (!shouldHandleRequest) {
+            KtProtoLog.v(
+                    WM_SHELL_DESKTOP_MODE,
+                    "DesktopTasksController: skipping handleRequest reason=%s",
+                    reason
+            )
             return null
         }
 
-        val task: RunningTaskInfo = request.triggerTask
-        val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
-
-        // Check if we should switch a fullscreen task to freeform
-        if (task.windowingMode == WINDOWING_MODE_FULLSCREEN) {
-            // If there are any visible desktop tasks, switch the task to freeform
-            if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
-                KtProtoLog.d(
-                    WM_SHELL_DESKTOP_MODE,
-                    "DesktopTasksController: switch fullscreen task to freeform on transition" +
-                        " taskId=%d",
-                    task.taskId
-                )
-                return WindowContainerTransaction().also { wct ->
-                    addMoveToDesktopChanges(wct, task.token)
+        val result = triggerTask?.let { task ->
+            when {
+                // If display has tasks stashed, handle as stashed launch
+                desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task)
+                // Check if fullscreen task should be updated
+                task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task)
+                // Check if freeform task should be updated
+                task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task)
+                else -> {
+                    null
                 }
             }
         }
+        KtProtoLog.v(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopTasksController: handleRequest result=%s",
+                result ?: "null"
+        )
+        return result
+    }
 
-        // CHeck if we should switch a freeform task to fullscreen
-        if (task.windowingMode == WINDOWING_MODE_FREEFORM) {
-            // If no visible desktop tasks, switch this task to freeform as the transition came
-            // outside of this controller
-            if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
-                KtProtoLog.d(
+    /**
+     * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
+     * This is intended to be used when desktop mode is part of another animation but isn't, itself,
+     * animating.
+     */
+    fun syncSurfaceState(
+            info: TransitionInfo,
+            finishTransaction: SurfaceControl.Transaction
+    ) {
+        // Add rounded corners to freeform windows
+        val ta: TypedArray = context.obtainStyledAttributes(
+                intArrayOf(R.attr.dialogCornerRadius))
+        val cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat()
+        ta.recycle()
+        info.changes
+                .filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
+                .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
+    }
+
+    private fun handleFreeformTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
+        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
+        val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
+        if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
+            KtProtoLog.d(
                     WM_SHELL_DESKTOP_MODE,
                     "DesktopTasksController: switch freeform task to fullscreen oon transition" +
-                        " taskId=%d",
+                            " taskId=%d",
                     task.taskId
-                )
-                return WindowContainerTransaction().also { wct ->
-                    addMoveToFullscreenChanges(wct, task.token)
-                }
+            )
+            return WindowContainerTransaction().also { wct ->
+                addMoveToFullscreenChanges(wct, task)
             }
         }
         return null
     }
 
+    private fun handleFullscreenTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
+        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFullscreenTaskLaunch")
+        val activeTasks = desktopModeTaskRepository.getActiveTasks(task.displayId)
+        if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
+            KtProtoLog.d(
+                    WM_SHELL_DESKTOP_MODE,
+                    "DesktopTasksController: switch fullscreen task to freeform on transition" +
+                            " taskId=%d",
+                    task.taskId
+            )
+            return WindowContainerTransaction().also { wct ->
+                addMoveToDesktopChanges(wct, task)
+            }
+        }
+        return null
+    }
+
+    private fun handleStashedTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction {
+        KtProtoLog.d(
+                WM_SHELL_DESKTOP_MODE,
+                "DesktopTasksController: launch apps with stashed on transition taskId=%d",
+                task.taskId
+        )
+        val wct = WindowContainerTransaction()
+        bringDesktopAppsToFront(task.displayId, wct)
+        addMoveToDesktopChanges(wct, task)
+        desktopModeTaskRepository.setStashed(task.displayId, false)
+        return wct
+    }
+
     private fun addMoveToDesktopChanges(
         wct: WindowContainerTransaction,
-        token: WindowContainerToken
+        taskInfo: RunningTaskInfo
     ) {
-        wct.setWindowingMode(token, WINDOWING_MODE_FREEFORM)
-        wct.reorder(token, true /* onTop */)
+        val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode
+        val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FREEFORM) {
+            // Display windowing is freeform, set to undefined and inherit it
+            WINDOWING_MODE_UNDEFINED
+        } else {
+            WINDOWING_MODE_FREEFORM
+        }
+        wct.setWindowingMode(taskInfo.token, targetWindowingMode)
+        wct.reorder(taskInfo.token, true /* onTop */)
         if (isDesktopDensityOverrideSet()) {
-            wct.setDensityDpi(token, getDesktopDensityDpi())
+            wct.setDensityDpi(taskInfo.token, getDesktopDensityDpi())
         }
     }
 
     private fun addMoveToFullscreenChanges(
         wct: WindowContainerTransaction,
-        token: WindowContainerToken
+        taskInfo: RunningTaskInfo
     ) {
-        wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN)
-        wct.setBounds(token, null)
+        val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode
+        val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+            // Display windowing is fullscreen, set to undefined and inherit it
+            WINDOWING_MODE_UNDEFINED
+        } else {
+            WINDOWING_MODE_FULLSCREEN
+        }
+        wct.setWindowingMode(taskInfo.token, targetWindowingMode)
+        wct.setBounds(taskInfo.token, Rect())
         if (isDesktopDensityOverrideSet()) {
-            wct.setDensityDpi(token, getFullscreenDensityDpi())
+            wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
         }
     }
 
-    private fun getFullscreenDensityDpi(): Int {
+    /**
+     * Adds split screen changes to a transaction. Note that bounds are not reset here due to
+     * animation; see {@link onDesktopSplitSelectAnimComplete}
+     */
+    private fun addMoveToSplitChanges(
+        wct: WindowContainerTransaction,
+        taskInfo: RunningTaskInfo
+    ) {
+        wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
+        // The task's density may have been overridden in freeform; revert it here as we don't
+        // want it overridden in multi-window.
+        wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
+    }
+
+    /**
+     * Requests a task be transitioned from desktop to split select. Applies needed windowing
+     * changes if this transition is enabled.
+     */
+    fun requestSplit(
+        taskInfo: RunningTaskInfo
+    ) {
+        val windowingMode = taskInfo.windowingMode
+        if (windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM
+        ) {
+            val wct = WindowContainerTransaction()
+            addMoveToSplitChanges(wct, taskInfo)
+            splitScreenController.requestEnterSplitSelect(taskInfo, wct)
+        }
+    }
+
+    private fun getDefaultDensityDpi(): Int {
         return context.resources.displayMetrics.densityDpi
     }
 
@@ -508,13 +771,12 @@
             y: Float
     ) {
         if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
-            val statusBarHeight = getStatusBarHeight(taskInfo)
-            if (y <= statusBarHeight && visualIndicator == null) {
+            if (y <= transitionAreaHeight && visualIndicator == null) {
                 visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
                         displayController, context, taskSurface, shellTaskOrganizer,
                         rootTaskDisplayAreaOrganizer)
                 visualIndicator?.createFullscreenIndicatorWithAnimatedBounds()
-            } else if (y > statusBarHeight && visualIndicator != null) {
+            } else if (y > transitionAreaHeight && visualIndicator != null) {
                 releaseVisualIndicator()
             }
         }
@@ -524,14 +786,18 @@
      * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area.
      *
      * @param taskInfo the task being dragged.
-     * @param position position of surface when drag ends
+     * @param position position of surface when drag ends.
+     * @param y the Y position of the top edge of the task
+     * @param windowDecor the window decoration for the task being dragged
      */
     fun onDragPositioningEnd(
             taskInfo: RunningTaskInfo,
-            position: Point
+            position: Point,
+            y: Float,
+            windowDecor: DesktopModeWindowDecoration
     ) {
-        val statusBarHeight = getStatusBarHeight(taskInfo)
-        if (position.y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+        if (y <= transitionAreaHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+            windowDecor.incrementRelayoutBlock()
             moveToFullscreenWithAnimation(taskInfo, position)
         }
     }
@@ -549,9 +815,9 @@
             taskSurface: SurfaceControl,
             y: Float
     ) {
-        // If the motion event is above the status bar, return since we do not need to show the
-        // visual indicator at this point.
-        if (y < getStatusBarHeight(taskInfo)) {
+        // If the motion event is above the status bar and the visual indicator is not yet visible,
+        // return since we do not need to show the visual indicator at this point.
+        if (y < getStatusBarHeight(taskInfo) && visualIndicator == null) {
             return
         }
         if (visualIndicator == null) {
@@ -580,7 +846,7 @@
             taskInfo: RunningTaskInfo,
             freeformBounds: Rect
     ) {
-        moveToDesktopWithAnimation(taskInfo, freeformBounds)
+        finalizeMoveToDesktop(taskInfo, freeformBounds)
     }
 
     private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
@@ -632,6 +898,12 @@
         desktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor)
     }
 
+    private fun dump(pw: PrintWriter, prefix: String) {
+        val innerPrefix = "$prefix  "
+        pw.println("${prefix}DesktopTasksController")
+        desktopModeTaskRepository.dump(pw, innerPrefix)
+    }
+
     /** The interface for calls from outside the shell, within the host process. */
     @ExternalThread
     private inner class DesktopModeImpl : DesktopMode {
@@ -658,8 +930,51 @@
     @BinderThread
     private class IDesktopModeImpl(private var controller: DesktopTasksController?) :
         IDesktopMode.Stub(), ExternalInterfaceBinder {
+
+        private lateinit var remoteListener:
+                SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>
+
+        private val listener: VisibleTasksListener = object : VisibleTasksListener {
+            override fun onVisibilityChanged(displayId: Int, visible: Boolean) {
+                KtProtoLog.v(
+                        WM_SHELL_DESKTOP_MODE,
+                        "IDesktopModeImpl: onVisibilityChanged display=%d visible=%b",
+                        displayId,
+                        visible
+                )
+                remoteListener.call { l -> l.onVisibilityChanged(displayId, visible) }
+            }
+
+            override fun onStashedChanged(displayId: Int, stashed: Boolean) {
+                KtProtoLog.v(
+                        WM_SHELL_DESKTOP_MODE,
+                        "IDesktopModeImpl: onStashedChanged display=%d stashed=%b",
+                        displayId,
+                        stashed
+                )
+                remoteListener.call { l -> l.onStashedChanged(displayId, stashed) }
+            }
+        }
+
+        init {
+            remoteListener =
+                    SingleInstanceRemoteListener<DesktopTasksController, IDesktopTaskListener>(
+                            controller,
+                            { c ->
+                                c.desktopModeTaskRepository.addVisibleTasksListener(
+                                        listener,
+                                        c.mainExecutor
+                                )
+                            },
+                            { c ->
+                                c.desktopModeTaskRepository.removeVisibleTasksListener(listener)
+                            }
+                    )
+        }
+
         /** Invalidates this instance, preventing future calls from updating the controller. */
         override fun invalidate() {
+            remoteListener.unregister()
             controller = null
         }
 
@@ -670,6 +985,27 @@
             ) { c -> c.showDesktopApps(displayId) }
         }
 
+        override fun stashDesktopApps(displayId: Int) {
+            ExecutorUtils.executeRemoteCallWithTaskPermission(
+                    controller,
+                    "stashDesktopApps"
+            ) { c -> c.stashDesktopApps(displayId) }
+        }
+
+        override fun hideStashedDesktopApps(displayId: Int) {
+            ExecutorUtils.executeRemoteCallWithTaskPermission(
+                    controller,
+                    "hideStashedDesktopApps"
+            ) { c -> c.hideStashedDesktopApps(displayId) }
+        }
+
+        override fun showDesktopApp(taskId: Int) {
+            ExecutorUtils.executeRemoteCallWithTaskPermission(
+                    controller,
+                    "showDesktopApp"
+            ) { c -> c.moveTaskToFront(taskId) }
+        }
+
         override fun getVisibleTaskCount(displayId: Int): Int {
             val result = IntArray(1)
             ExecutorUtils.executeRemoteCallWithTaskPermission(
@@ -680,13 +1016,40 @@
             )
             return result[0]
         }
+
+        override fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
+            ExecutorUtils.executeRemoteCallWithTaskPermission(
+                controller,
+                "onDesktopSplitSelectAnimComplete"
+            ) { c -> c.onDesktopSplitSelectAnimComplete(taskInfo) }
+        }
+
+        override fun setTaskListener(listener: IDesktopTaskListener?) {
+            KtProtoLog.v(
+                    WM_SHELL_DESKTOP_MODE,
+                    "IDesktopModeImpl: set task listener=%s",
+                    listener ?: "null"
+            )
+            ExecutorUtils.executeRemoteCallWithTaskPermission(
+                    controller,
+                    "setTaskListener"
+            ) { _ -> listener?.let { remoteListener.register(it) } ?: remoteListener.unregister() }
+        }
     }
 
     companion object {
         private val DESKTOP_DENSITY_OVERRIDE =
-            SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 0)
+            SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 284)
         private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000)
 
+        // Override default freeform task width when desktop mode is enabled. In dips.
+        private val DESKTOP_MODE_DEFAULT_WIDTH_DP =
+            SystemProperties.getInt("persist.wm.debug.desktop_mode.default_width", 840)
+
+        // Override default freeform task height when desktop mode is enabled. In dips.
+        private val DESKTOP_MODE_DEFAULT_HEIGHT_DP =
+            SystemProperties.getInt("persist.wm.debug.desktop_mode.default_height", 630)
+
         /**
          * Check if desktop density override is enabled
          */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 3733b91..1128d91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -17,15 +17,16 @@
 package com.android.wm.shell.desktopmode;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
-import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.IBinder;
+import android.util.Slog;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.TransitionInfo;
@@ -36,6 +37,8 @@
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration;
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -48,18 +51,18 @@
  */
 public class EnterDesktopTaskTransitionHandler implements Transitions.TransitionHandler {
 
+    private static final String TAG = "EnterDesktopTaskTransitionHandler";
     private final Transitions mTransitions;
     private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
 
-    // The size of the screen during drag relative to the fullscreen size
-    public static final float DRAG_FREEFORM_SCALE = 0.4f;
     // The size of the screen after drag relative to the fullscreen size
     public static final float FINAL_FREEFORM_SCALE = 0.6f;
     public static final int FREEFORM_ANIMATION_DURATION = 336;
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
-    private Point mPosition;
     private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
+    private MoveToDesktopAnimator mMoveToDesktopAnimator;
+    private DesktopModeWindowDecoration mDesktopModeWindowDecoration;
 
     public EnterDesktopTaskTransitionHandler(
             Transitions transitions) {
@@ -79,7 +82,7 @@
      * @param wct WindowContainerTransaction for transition
      * @param onAnimationEndCallback to be called after animation
      */
-    public void startTransition(@WindowManager.TransitionType int type,
+    private void startTransition(@WindowManager.TransitionType int type,
             @NonNull WindowContainerTransaction wct,
             Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
         mOnAnimationFinishedCallback = onAnimationEndCallback;
@@ -88,19 +91,58 @@
     }
 
     /**
+     * Starts Transition of type TRANSIT_START_DRAG_TO_DESKTOP_MODE
+     * @param wct WindowContainerTransaction for transition
+     * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
+     *                              to desktop animation
+     * @param onAnimationEndCallback to be called after animation
+     */
+    public void startMoveToDesktop(@NonNull WindowContainerTransaction wct,
+            @NonNull MoveToDesktopAnimator moveToDesktopAnimator,
+            Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+        mMoveToDesktopAnimator = moveToDesktopAnimator;
+        startTransition(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, wct,
+                onAnimationEndCallback);
+    }
+
+    /**
+     * Starts Transition of type TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
+     * @param wct WindowContainerTransaction for transition
+     * @param onAnimationEndCallback to be called after animation
+     */
+    public void finalizeMoveToDesktop(@NonNull WindowContainerTransaction wct,
+            Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+        startTransition(Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE, wct,
+                onAnimationEndCallback);
+    }
+
+    /**
      * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
      * @param wct WindowContainerTransaction for transition
-     * @param position Position of task when transition is triggered
+     * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
+     *                              to desktop animation
      * @param onAnimationEndCallback to be called after animation
      */
     public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct,
-            Point position,
+            MoveToDesktopAnimator moveToDesktopAnimator,
             Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
-        mPosition = position;
-        startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct,
+        mMoveToDesktopAnimator = moveToDesktopAnimator;
+        startTransition(Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE, wct,
                 onAnimationEndCallback);
     }
 
+    /**
+     * Starts Transition of type TRANSIT_MOVE_TO_DESKTOP
+     * @param wct WindowContainerTransaction for transition
+     * @param decor {@link DesktopModeWindowDecoration} of task being animated
+     */
+    public void moveToDesktop(@NonNull WindowContainerTransaction wct,
+            DesktopModeWindowDecoration decor) {
+        mDesktopModeWindowDecoration = decor;
+        startTransition(Transitions.TRANSIT_MOVE_TO_DESKTOP, wct,
+                null /* onAnimationEndCallback */);
+    }
+
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startT,
@@ -140,105 +182,209 @@
         }
 
         final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-        if (type == Transitions.TRANSIT_ENTER_FREEFORM
+        if (type == Transitions.TRANSIT_MOVE_TO_DESKTOP
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
-            // Transitioning to freeform but keeping fullscreen bounds, so the crop is set
-            // to null and we don't require an animation
-            final SurfaceControl sc = change.getLeash();
-            startT.setWindowCrop(sc, null);
-            startT.apply();
-            mTransitions.getMainExecutor().execute(
-                    () -> finishCallback.onTransitionFinished(null, null));
-            return true;
+            return animateMoveToDesktop(change, startT, finishCallback);
         }
 
-        Rect endBounds = change.getEndAbsBounds();
-        if (type == Transitions.TRANSIT_ENTER_DESKTOP_MODE
+        if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE
+                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+            return animateStartDragToDesktopMode(change, startT, finishT, finishCallback);
+        }
+
+        final Rect endBounds = change.getEndAbsBounds();
+        if (type == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
                 && !endBounds.isEmpty()) {
-            // This Transition animates a task to freeform bounds after being dragged into freeform
-            // mode and brings the remaining freeform tasks to front
-            final SurfaceControl sc = change.getLeash();
-            startT.setWindowCrop(sc, endBounds.width(),
-                    endBounds.height());
-            startT.apply();
-
-            // We want to find the scale of the current bounds relative to the end bounds. The
-            // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be
-            // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to
-            // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds
-            final ValueAnimator animator =
-                    ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
-            animator.setDuration(FREEFORM_ANIMATION_DURATION);
-            final SurfaceControl.Transaction t = mTransactionSupplier.get();
-            animator.addUpdateListener(animation -> {
-                final float animationValue = (float) animation.getAnimatedValue();
-                t.setScale(sc, animationValue, animationValue);
-
-                final float animationWidth = endBounds.width() * animationValue;
-                final float animationHeight = endBounds.height() * animationValue;
-                final int animationX = endBounds.centerX() - (int) (animationWidth / 2);
-                final int animationY = endBounds.centerY() - (int) (animationHeight / 2);
-
-                t.setPosition(sc, animationX, animationY);
-                t.apply();
-            });
-
-            animator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (mOnAnimationFinishedCallback != null) {
-                        mOnAnimationFinishedCallback.accept(finishT);
-                    }
-                    mTransitions.getMainExecutor().execute(
-                            () -> finishCallback.onTransitionFinished(null, null));
-                }
-            });
-
-            animator.start();
-            return true;
+            return animateFinalizeDragToDesktopMode(change, startT, finishT, finishCallback,
+                    endBounds);
         }
 
-        if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
-                && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
-                && mPosition != null) {
-            // This Transition animates a task to fullscreen after being dragged from the status
-            // bar and then released back into the status bar area
-            final SurfaceControl sc = change.getLeash();
-            // Hide the first (fullscreen) frame because the animation will start from the smaller
-            // scale size.
-            startT.hide(sc)
-                    .setWindowCrop(sc, endBounds.width(), endBounds.height())
-                    .apply();
-
-            final ValueAnimator animator = new ValueAnimator();
-            animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f);
-            animator.setDuration(FREEFORM_ANIMATION_DURATION);
-            final SurfaceControl.Transaction t = mTransactionSupplier.get();
-            animator.addUpdateListener(animation -> {
-                final float scale = (float) animation.getAnimatedValue();
-                t.setPosition(sc, mPosition.x * (1 - scale), mPosition.y * (1 - scale))
-                        .setScale(sc, scale, scale)
-                        .show(sc)
-                        .apply();
-            });
-            animator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (mOnAnimationFinishedCallback != null) {
-                        mOnAnimationFinishedCallback.accept(finishT);
-                    }
-                    mTransitions.getMainExecutor().execute(
-                            () -> finishCallback.onTransitionFinished(null, null));
-                }
-            });
-            animator.start();
-            return true;
+        if (type == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
+                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+            return animateCancelDragToDesktopMode(change, startT, finishT, finishCallback,
+                    endBounds);
         }
 
         return false;
     }
 
+    private boolean animateMoveToDesktop(
+            @NonNull TransitionInfo.Change change,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        if (mDesktopModeWindowDecoration == null) {
+            Slog.e(TAG, "Window Decoration is not available for this transition");
+            return false;
+        }
+
+        final SurfaceControl leash = change.getLeash();
+        final Rect startBounds = change.getStartAbsBounds();
+        startT.setPosition(leash, startBounds.left, startBounds.right)
+                .setWindowCrop(leash, startBounds.width(), startBounds.height())
+                .show(leash);
+        mDesktopModeWindowDecoration.showResizeVeil(startT, startBounds);
+
+        final ValueAnimator animator = ValueAnimator.ofObject(new RectEvaluator(),
+                change.getStartAbsBounds(), change.getEndAbsBounds());
+        animator.setDuration(FREEFORM_ANIMATION_DURATION);
+        SurfaceControl.Transaction t = mTransactionSupplier.get();
+        animator.addUpdateListener(animation -> {
+            final Rect animationValue = (Rect) animator.getAnimatedValue();
+            t.setPosition(leash, animationValue.left, animationValue.right)
+                    .setWindowCrop(leash, animationValue.width(), animationValue.height())
+                    .show(leash);
+            mDesktopModeWindowDecoration.updateResizeVeil(t, animationValue);
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mDesktopModeWindowDecoration.hideResizeVeil();
+                mTransitions.getMainExecutor().execute(
+                        () -> finishCallback.onTransitionFinished(null));
+            }
+        });
+        animator.start();
+        return true;
+    }
+
+    private boolean animateStartDragToDesktopMode(
+            @NonNull TransitionInfo.Change change,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        // Transitioning to freeform but keeping fullscreen bounds, so the crop is set
+        // to null and we don't require an animation
+        final SurfaceControl sc = change.getLeash();
+        startT.setWindowCrop(sc, null);
+
+        if (mMoveToDesktopAnimator == null
+                || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+            Slog.e(TAG, "No animator available for this transition");
+            return false;
+        }
+
+        // Calculate and set position of the task
+        final PointF position = mMoveToDesktopAnimator.getPosition();
+        startT.setPosition(sc, position.x, position.y);
+        finishT.setPosition(sc, position.x, position.y);
+
+        startT.apply();
+
+        mTransitions.getMainExecutor().execute(
+                () -> finishCallback.onTransitionFinished(null));
+
+        return true;
+    }
+
+    private boolean animateFinalizeDragToDesktopMode(
+            @NonNull TransitionInfo.Change change,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull Transitions.TransitionFinishCallback finishCallback,
+            @NonNull Rect endBounds) {
+        // This Transition animates a task to freeform bounds after being dragged into freeform
+        // mode and brings the remaining freeform tasks to front
+        final SurfaceControl sc = change.getLeash();
+        startT.setWindowCrop(sc, endBounds.width(),
+                endBounds.height());
+        startT.apply();
+
+        // End the animation that shrinks the window when task is first dragged from fullscreen
+        if (mMoveToDesktopAnimator != null) {
+            mMoveToDesktopAnimator.endAnimator();
+        }
+
+        // We want to find the scale of the current bounds relative to the end bounds. The
+        // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be
+        // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to
+        // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds
+        final ValueAnimator animator =
+                ValueAnimator.ofFloat(
+                        MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
+        animator.setDuration(FREEFORM_ANIMATION_DURATION);
+        final SurfaceControl.Transaction t = mTransactionSupplier.get();
+        animator.addUpdateListener(animation -> {
+            final float animationValue = (float) animation.getAnimatedValue();
+            t.setScale(sc, animationValue, animationValue);
+
+            final float animationWidth = endBounds.width() * animationValue;
+            final float animationHeight = endBounds.height() * animationValue;
+            final int animationX = endBounds.centerX() - (int) (animationWidth / 2);
+            final int animationY = endBounds.centerY() - (int) (animationHeight / 2);
+
+            t.setPosition(sc, animationX, animationY);
+            t.apply();
+        });
+
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mOnAnimationFinishedCallback != null) {
+                    mOnAnimationFinishedCallback.accept(finishT);
+                }
+                mTransitions.getMainExecutor().execute(
+                        () -> finishCallback.onTransitionFinished(null));
+            }
+        });
+
+        animator.start();
+        return true;
+    }
+    private boolean animateCancelDragToDesktopMode(
+            @NonNull TransitionInfo.Change change,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull Transitions.TransitionFinishCallback finishCallback,
+            @NonNull Rect endBounds) {
+        // This Transition animates a task to fullscreen after being dragged from the status
+        // bar and then released back into the status bar area
+        final SurfaceControl sc = change.getLeash();
+        // Hide the first (fullscreen) frame because the animation will start from the smaller
+        // scale size.
+        startT.hide(sc)
+                .setWindowCrop(sc, endBounds.width(), endBounds.height())
+                .apply();
+
+        if (mMoveToDesktopAnimator == null
+                || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+            Slog.e(TAG, "No animator available for this transition");
+            return false;
+        }
+
+        // End the animation that shrinks the window when task is first dragged from fullscreen
+        mMoveToDesktopAnimator.endAnimator();
+
+        final ValueAnimator animator = new ValueAnimator();
+        animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f);
+        animator.setDuration(FREEFORM_ANIMATION_DURATION);
+        final SurfaceControl.Transaction t = mTransactionSupplier.get();
+
+        // Get position of the task
+        final float x = mMoveToDesktopAnimator.getPosition().x;
+        final float y = mMoveToDesktopAnimator.getPosition().y;
+
+        animator.addUpdateListener(animation -> {
+            final float scale = (float) animation.getAnimatedValue();
+            t.setPosition(sc, x * (1 - scale), y * (1 - scale))
+                    .setScale(sc, scale, scale)
+                    .show(sc)
+                    .apply();
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mOnAnimationFinishedCallback != null) {
+                    mOnAnimationFinishedCallback.accept(finishT);
+                }
+                mTransitions.getMainExecutor().execute(
+                        () -> finishCallback.onTransitionFinished(null));
+            }
+        });
+        animator.start();
+        return true;
+    }
+
     @Nullable
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index 3ad5edf..7342bd1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -168,7 +168,7 @@
                         mOnAnimationFinishedCallback.accept(finishT);
                     }
                     mTransitions.getMainExecutor().execute(
-                            () -> finishCallback.onTransitionFinished(null, null));
+                            () -> finishCallback.onTransitionFinished(null));
                 }
             });
             animator.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index 899d672..47edfd4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -16,6 +16,9 @@
 
 package com.android.wm.shell.desktopmode;
 
+import android.app.ActivityManager.RunningTaskInfo;
+import com.android.wm.shell.desktopmode.IDesktopTaskListener;
+
 /**
  * Interface that is exposed to remote callers to manipulate desktop mode features.
  */
@@ -24,6 +27,21 @@
     /** Show apps on the desktop on the given display */
     void showDesktopApps(int displayId);
 
+    /** Stash apps on the desktop to allow launching another app from home screen */
+    void stashDesktopApps(int displayId);
+
+    /** Hide apps that may be stashed */
+    void hideStashedDesktopApps(int displayId);
+
+    /** Bring task with the given id to front */
+    oneway void showDesktopApp(int taskId);
+
     /** Get count of visible desktop tasks on the given display */
     int getVisibleTaskCount(int displayId);
+
+    /** Perform cleanup transactions after the animation to split select is complete */
+    oneway void onDesktopSplitSelectAnimComplete(in RunningTaskInfo taskInfo);
+
+    /** Set listener that will receive callbacks about updates to desktop tasks */
+    oneway void setTaskListener(IDesktopTaskListener listener);
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
new file mode 100644
index 0000000..39128a8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode;
+
+/**
+ * Allows external processes to register a listener in WMShell to get updates about desktop task
+ * state.
+ */
+interface IDesktopTaskListener {
+
+    /** Desktop task visibility has change. Visible if at least 1 task is visible. */
+    oneway void onVisibilityChanged(int displayId, boolean visible);
+
+    /** Desktop task stashed status has changed. */
+    oneway void onStashedChanged(int displayId, boolean stashed);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
new file mode 100644
index 0000000..9debb25
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.animation.Animator
+import android.animation.RectEvaluator
+import android.animation.ValueAnimator
+import android.graphics.Rect
+import android.os.IBinder
+import android.util.SparseArray
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import androidx.core.animation.addListener
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import java.util.function.Supplier
+
+/** Handles the animation of quick resizing of desktop tasks. */
+class ToggleResizeDesktopTaskTransitionHandler(
+    private val transitions: Transitions,
+    private val transactionSupplier: Supplier<SurfaceControl.Transaction>
+) : Transitions.TransitionHandler {
+
+    private val rectEvaluator = RectEvaluator(Rect())
+    private val taskToDecorationMap = SparseArray<DesktopModeWindowDecoration>()
+
+    private var boundsAnimator: Animator? = null
+
+    constructor(
+        transitions: Transitions
+    ) : this(transitions, Supplier { SurfaceControl.Transaction() })
+
+    /** Starts a quick resize transition. */
+    fun startTransition(
+        wct: WindowContainerTransaction,
+        taskId: Int,
+        windowDecoration: DesktopModeWindowDecoration
+    ) {
+        // Pause relayout until the transition animation finishes.
+        windowDecoration.incrementRelayoutBlock()
+        transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
+        taskToDecorationMap.put(taskId, windowDecoration)
+    }
+
+    override fun startAnimation(
+        transition: IBinder,
+        info: TransitionInfo,
+        startTransaction: SurfaceControl.Transaction,
+        finishTransaction: SurfaceControl.Transaction,
+        finishCallback: Transitions.TransitionFinishCallback
+    ): Boolean {
+        val change = findRelevantChange(info)
+        val leash = change.leash
+        val taskId = checkNotNull(change.taskInfo).taskId
+        val startBounds = change.startAbsBounds
+        val endBounds = change.endAbsBounds
+        val windowDecor =
+            taskToDecorationMap.removeReturnOld(taskId)
+                ?: throw IllegalStateException("Window decoration not found for task $taskId")
+
+        val tx = transactionSupplier.get()
+        boundsAnimator?.cancel()
+        boundsAnimator =
+            ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds)
+                .setDuration(RESIZE_DURATION_MS)
+                .apply {
+                    addListener(
+                        onStart = {
+                            startTransaction
+                                .setPosition(
+                                    leash,
+                                    startBounds.left.toFloat(),
+                                    startBounds.top.toFloat()
+                                )
+                                .setWindowCrop(leash, startBounds.width(), startBounds.height())
+                                .show(leash)
+                            windowDecor.showResizeVeil(startTransaction, startBounds)
+                        },
+                        onEnd = {
+                            finishTransaction
+                                .setPosition(
+                                    leash,
+                                    endBounds.left.toFloat(),
+                                    endBounds.top.toFloat()
+                                )
+                                .setWindowCrop(leash, endBounds.width(), endBounds.height())
+                                .show(leash)
+                            windowDecor.hideResizeVeil()
+                            finishCallback.onTransitionFinished(null)
+                            boundsAnimator = null
+                        }
+                    )
+                    addUpdateListener { anim ->
+                        val rect = anim.animatedValue as Rect
+                        tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
+                            .setWindowCrop(leash, rect.width(), rect.height())
+                            .show(leash)
+                        windowDecor.updateResizeVeil(tx, rect)
+                    }
+                    start()
+                }
+        return true
+    }
+
+    override fun handleRequest(
+        transition: IBinder,
+        request: TransitionRequestInfo
+    ): WindowContainerTransaction? {
+        return null
+    }
+
+    private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change {
+        val matchingChanges =
+            info.changes.filter { c ->
+                !isWallpaper(c) && isValidTaskChange(c) && c.mode == TRANSIT_CHANGE
+            }
+        if (matchingChanges.size != 1) {
+            throw IllegalStateException(
+                "Expected 1 relevant change but found: ${matchingChanges.size}"
+            )
+        }
+        return matchingChanges.first()
+    }
+
+    private fun isWallpaper(change: TransitionInfo.Change): Boolean {
+        return (change.flags and TransitionInfo.FLAG_IS_WALLPAPER) != 0
+    }
+
+    private fun isValidTaskChange(change: TransitionInfo.Change): Boolean {
+        return change.taskInfo != null && change.taskInfo?.taskId != -1
+    }
+
+    companion object {
+        private const val RESIZE_DURATION_MS = 300L
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index 99922fb..f9ea1d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -21,11 +21,17 @@
 building to check the log state (is enabled) before printing the print format style log.
 
 **Notes**
-- ProtoLogs currently only work from soong builds (ie. via make/mp). We need to reimplement the
-  tool for use with SysUI-studio
+- ProtoLogs are only fully supported from soong builds (ie. via make/mp). In SysUI-studio it falls
+  back to log via Logcat
 - Non-text ProtoLogs are not currently supported with the Shell library (you can't view them with
   traces in Winscope)
 
+### Kotlin
+
+Protolog tool does not yet have support for Kotlin code (see [b/168581922](https://b.corp.google.com/issues/168581922)).
+For logging in Kotlin, use the [KtProtoLog](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt)
+class which has a similar API to the Java ProtoLog class.
+
 ### Enabling ProtoLog command line logging
 Run these commands to enable protologs for both WM Core and WM Shell to print to logcat.
 ```shell
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index be2489d..0bf8ec3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -16,9 +16,6 @@
 
 package com.android.wm.shell.draganddrop;
 
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.DragEvent.ACTION_DRAG_ENDED;
 import static android.view.DragEvent.ACTION_DRAG_ENTERED;
@@ -38,6 +35,7 @@
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
 
+import android.app.ActivityTaskManager;
 import android.content.ClipDescription;
 import android.content.ComponentCallbacks2;
 import android.content.Context;
@@ -205,8 +203,6 @@
         final Context context = mDisplayController.getDisplayContext(displayId)
                 .createWindowContext(TYPE_APPLICATION_OVERLAY, null);
         final WindowManager wm = context.getSystemService(WindowManager.class);
-
-        // TODO(b/169894807): Figure out the right layer for this, needs to be below the task bar
         final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
                 TYPE_APPLICATION_OVERLAY,
@@ -279,15 +275,11 @@
         }
 
         if (event.getAction() == ACTION_DRAG_STARTED) {
-            final boolean hasValidClipData = event.getClipData().getItemCount() > 0
-                    && (description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY)
-                            || description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT)
-                            || description.hasMimeType(MIMETYPE_APPLICATION_TASK));
-            pd.isHandlingDrag = hasValidClipData;
+            pd.isHandlingDrag = DragUtils.canHandleDrag(event);
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
                     "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
                     pd.isHandlingDrag, event.getClipData().getItemCount(),
-                    getMimeTypes(description));
+                    DragUtils.getMimeTypesConcatenated(description));
         }
 
         if (!pd.isHandlingDrag) {
@@ -300,10 +292,13 @@
                     Slog.w(TAG, "Unexpected drag start during an active drag");
                     return false;
                 }
+                // TODO(b/290391688): Also update the session data with task stack changes
                 InstanceId loggerSessionId = mLogger.logStart(event);
                 pd.activeDragCount++;
-                pd.dragLayout.prepare(mDisplayController.getDisplayLayout(displayId),
-                        event.getClipData(), loggerSessionId);
+                pd.dragSession = new DragSession(mContext, ActivityTaskManager.getInstance(),
+                        mDisplayController.getDisplayLayout(displayId), event.getClipData());
+                pd.dragSession.update();
+                pd.dragLayout.prepare(pd.dragSession, loggerSessionId);
                 setDropTargetWindowVisibility(pd, View.VISIBLE);
                 notifyDragStarted();
                 break;
@@ -324,7 +319,7 @@
                 break;
             }
             case ACTION_DRAG_ENDED:
-                // TODO(b/169894807): Ensure sure it's not possible to get ENDED without DROP
+                // TODO(b/290391688): Ensure sure it's not possible to get ENDED without DROP
                 // or EXITED
                 if (pd.dragLayout.hasDropped()) {
                     mLogger.logDrop();
@@ -362,17 +357,6 @@
         pd.setWindowVisibility(visibility);
     }
 
-    private String getMimeTypes(ClipDescription description) {
-        String mimeTypes = "";
-        for (int i = 0; i < description.getMimeTypeCount(); i++) {
-            if (i > 0) {
-                mimeTypes += ", ";
-            }
-            mimeTypes += description.getMimeType(i);
-        }
-        return mimeTypes;
-    }
-
     /**
      * Returns if any displays are currently ready to handle a drag/drop.
      */
@@ -462,6 +446,8 @@
         // A count of the number of active drags in progress to ensure that we only hide the window
         // when all the drag animations have completed
         int activeDragCount;
+        // The active drag session
+        DragSession dragSession;
 
         PerDisplay(int dispId, Context c, WindowManager w, FrameLayout rv, DragLayout dl) {
             displayId = dispId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index fb08c87..e70768b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -21,7 +21,6 @@
 import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.ClipDescription.EXTRA_ACTIVITY_OPTIONS;
 import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
@@ -41,16 +40,13 @@
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
 
-import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.PendingIntent;
-import android.app.WindowConfiguration;
 import android.content.ActivityNotFoundException;
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.content.pm.LauncherApps;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -67,14 +63,12 @@
 
 import com.android.internal.logging.InstanceId;
 import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * The policy for handling drag and drop operations to shell.
@@ -84,7 +78,6 @@
     private static final String TAG = DragAndDropPolicy.class.getSimpleName();
 
     private final Context mContext;
-    private final ActivityTaskManager mActivityTaskManager;
     private final Starter mStarter;
     private final SplitScreenController mSplitScreen;
     private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>();
@@ -94,14 +87,12 @@
     private DragSession mSession;
 
     public DragAndDropPolicy(Context context, SplitScreenController splitScreen) {
-        this(context, ActivityTaskManager.getInstance(), splitScreen, new DefaultStarter(context));
+        this(context, splitScreen, new DefaultStarter(context));
     }
 
     @VisibleForTesting
-    DragAndDropPolicy(Context context, ActivityTaskManager activityTaskManager,
-            SplitScreenController splitScreen, Starter starter) {
+    DragAndDropPolicy(Context context, SplitScreenController splitScreen, Starter starter) {
         mContext = context;
-        mActivityTaskManager = activityTaskManager;
         mSplitScreen = splitScreen;
         mStarter = mSplitScreen != null ? mSplitScreen : starter;
     }
@@ -109,11 +100,9 @@
     /**
      * Starts a new drag session with the given initial drag data.
      */
-    void start(DisplayLayout displayLayout, ClipData data, InstanceId loggerSessionId) {
+    void start(DragSession session, InstanceId loggerSessionId) {
         mLoggerSessionId = loggerSessionId;
-        mSession = new DragSession(mActivityTaskManager, displayLayout, data);
-        // TODO(b/169894807): Also update the session data with task stack changes
-        mSession.update();
+        mSession = session;
         RectF disallowHitRegion = (RectF) mSession.dragData.getExtra(EXTRA_DISALLOW_HIT_REGION);
         if (disallowHitRegion == null) {
             mDisallowHitRegion.setEmpty();
@@ -123,13 +112,6 @@
     }
 
     /**
-     * Returns the last running task.
-     */
-    ActivityManager.RunningTaskInfo getLatestRunningTask() {
-        return mSession.runningTaskInfo;
-    }
-
-    /**
      * Returns the number of targets.
      */
     int getNumTargets() {
@@ -286,49 +268,6 @@
     }
 
     /**
-     * Per-drag session data.
-     */
-    private static class DragSession {
-        private final ActivityTaskManager mActivityTaskManager;
-        private final ClipData mInitialDragData;
-
-        final DisplayLayout displayLayout;
-        Intent dragData;
-        ActivityManager.RunningTaskInfo runningTaskInfo;
-        @WindowConfiguration.WindowingMode
-        int runningTaskWinMode = WINDOWING_MODE_UNDEFINED;
-        @WindowConfiguration.ActivityType
-        int runningTaskActType = ACTIVITY_TYPE_STANDARD;
-        boolean dragItemSupportsSplitscreen;
-
-        DragSession(ActivityTaskManager activityTaskManager,
-                DisplayLayout dispLayout, ClipData data) {
-            mActivityTaskManager = activityTaskManager;
-            mInitialDragData = data;
-            displayLayout = dispLayout;
-        }
-
-        /**
-         * Updates the session data based on the current state of the system.
-         */
-        void update() {
-            List<ActivityManager.RunningTaskInfo> tasks =
-                    mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
-            if (!tasks.isEmpty()) {
-                final ActivityManager.RunningTaskInfo task = tasks.get(0);
-                runningTaskInfo = task;
-                runningTaskWinMode = task.getWindowingMode();
-                runningTaskActType = task.getActivityType();
-            }
-
-            final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
-            dragItemSupportsSplitscreen = info == null
-                    || ActivityInfo.isResizeableMode(info.resizeMode);
-            dragData = mInitialDragData.getItemAt(0).getIntent();
-        }
-    }
-
-    /**
      * Interface for actually committing the task launches.
      */
     public interface Starter {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index fe42822a..205a455 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -35,7 +35,6 @@
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.StatusBarManager;
-import android.content.ClipData;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Color;
@@ -53,14 +52,13 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
-import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.util.ArrayList;
 
 /**
- * Coordinates the visible drop targets for the current drag.
+ * Coordinates the visible drop targets for the current drag within a single display.
  */
 public class DragLayout extends LinearLayout {
 
@@ -86,6 +84,7 @@
 
     private boolean mIsShowing;
     private boolean mHasDropped;
+    private DragSession mSession;
 
     @SuppressLint("WrongConstant")
     public DragLayout(Context context, SplitScreenController splitScreenController,
@@ -182,16 +181,19 @@
         return mHasDropped;
     }
 
-    public void prepare(DisplayLayout displayLayout, ClipData initialData,
-            InstanceId loggerSessionId) {
-        mPolicy.start(displayLayout, initialData, loggerSessionId);
+    /**
+     * Called when a new drag is started.
+     */
+    public void prepare(DragSession session, InstanceId loggerSessionId) {
+        mPolicy.start(session, loggerSessionId);
+        mSession = session;
         mHasDropped = false;
         mCurrentTarget = null;
 
         boolean alreadyInSplit = mSplitScreenController != null
                 && mSplitScreenController.isSplitScreenVisible();
         if (!alreadyInSplit) {
-            ActivityManager.RunningTaskInfo taskInfo1 = mPolicy.getLatestRunningTask();
+            ActivityManager.RunningTaskInfo taskInfo1 = mSession.runningTaskInfo;
             if (taskInfo1 != null) {
                 final int activityType = taskInfo1.getActivityType();
                 if (activityType == ACTIVITY_TYPE_STANDARD) {
@@ -356,7 +358,16 @@
      */
     public void hide(DragEvent event, Runnable hideCompleteCallback) {
         mIsShowing = false;
-        animateSplitContainers(false, hideCompleteCallback);
+        animateSplitContainers(false, () -> {
+            if (hideCompleteCallback != null) {
+                hideCompleteCallback.run();
+            }
+            switch (event.getAction()) {
+                case DragEvent.ACTION_DROP:
+                case DragEvent.ACTION_DRAG_ENDED:
+                    mSession = null;
+            }
+        });
         // Reset the state if we previously force-ignore the bottom margin
         mDropZoneView1.setForceIgnoreBottomMargin(false);
         mDropZoneView2.setForceIgnoreBottomMargin(false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
new file mode 100644
index 0000000..478b6a9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static android.content.Intent.EXTRA_USER;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.app.WindowConfiguration;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.net.Uri;
+import android.os.UserHandle;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.common.DisplayLayout;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Per-drag session data.
+ */
+public class DragSession {
+    private final ActivityTaskManager mActivityTaskManager;
+    private final ClipData mInitialDragData;
+
+    final DisplayLayout displayLayout;
+    Intent dragData;
+    ActivityManager.RunningTaskInfo runningTaskInfo;
+    @WindowConfiguration.WindowingMode
+    int runningTaskWinMode = WINDOWING_MODE_UNDEFINED;
+    @WindowConfiguration.ActivityType
+    int runningTaskActType = ACTIVITY_TYPE_STANDARD;
+    boolean dragItemSupportsSplitscreen;
+
+    DragSession(Context context, ActivityTaskManager activityTaskManager,
+            DisplayLayout dispLayout, ClipData data) {
+        mActivityTaskManager = activityTaskManager;
+        mInitialDragData = data;
+        displayLayout = dispLayout;
+    }
+
+    /**
+     * Updates the session data based on the current state of the system.
+     */
+    void update() {
+        List<ActivityManager.RunningTaskInfo> tasks =
+                mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
+        if (!tasks.isEmpty()) {
+            final ActivityManager.RunningTaskInfo task = tasks.get(0);
+            runningTaskInfo = task;
+            runningTaskWinMode = task.getWindowingMode();
+            runningTaskActType = task.getActivityType();
+        }
+
+        final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
+        dragItemSupportsSplitscreen = info == null
+                || ActivityInfo.isResizeableMode(info.resizeMode);
+        dragData = mInitialDragData.getItemAt(0).getIntent();
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
new file mode 100644
index 0000000..7c0883d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.draganddrop;
+
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+
+import android.content.ClipDescription;
+import android.view.DragEvent;
+
+/** Collection of utility classes for handling drag and drop. */
+public class DragUtils {
+    private static final String TAG = "DragUtils";
+
+    /**
+     * Returns whether we can handle this particular drag.
+     */
+    public static boolean canHandleDrag(DragEvent event) {
+        return event.getClipData().getItemCount() > 0
+                && (isAppDrag(event.getClipDescription()));
+    }
+
+    /**
+     * Returns whether this clip data description represents an app drag.
+     */
+    public static boolean isAppDrag(ClipDescription description) {
+        return description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY)
+                || description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT)
+                || description.hasMimeType(MIMETYPE_APPLICATION_TASK);
+    }
+
+    /**
+     * Returns a list of the mime types provided in the clip description.
+     */
+    public static String getMimeTypesConcatenated(ClipDescription description) {
+        String mimeTypes = "";
+        for (int i = 0; i < description.getMimeTypeCount(); i++) {
+            if (i > 0) {
+                mimeTypes += ", ";
+            }
+            mimeTypes += description.getMimeType(i);
+        }
+        return mimeTypes;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java
deleted file mode 100644
index 73deea5..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java
+++ /dev/null
@@ -1,165 +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.wm.shell.draganddrop;
-
-import android.animation.ObjectAnimator;
-import android.animation.RectEvaluator;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.IntProperty;
-import android.util.Property;
-import android.view.animation.Interpolator;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.graphics.ColorUtils;
-import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.R;
-
-/**
- * Drawable to draw the region that the target will have once it is dropped.
- */
-public class DropOutlineDrawable extends Drawable {
-
-    private static final int BOUNDS_DURATION = 200;
-    private static final int ALPHA_DURATION = 135;
-
-    private final IntProperty<DropOutlineDrawable> ALPHA =
-            new IntProperty<DropOutlineDrawable>("alpha") {
-        @Override
-        public void setValue(DropOutlineDrawable d, int alpha) {
-            d.setAlpha(alpha);
-        }
-
-        @Override
-        public Integer get(DropOutlineDrawable d) {
-            return d.getAlpha();
-        }
-    };
-
-    private final Property<DropOutlineDrawable, Rect> BOUNDS =
-            new Property<DropOutlineDrawable, Rect>(Rect.class, "bounds") {
-        @Override
-        public void set(DropOutlineDrawable d, Rect bounds) {
-            d.setRegionBounds(bounds);
-        }
-
-        @Override
-        public Rect get(DropOutlineDrawable d) {
-            return d.getRegionBounds();
-        }
-    };
-
-    private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
-    private ObjectAnimator mBoundsAnimator;
-    private ObjectAnimator mAlphaAnimator;
-
-    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-    private final Rect mBounds = new Rect();
-    private final float mCornerRadius;
-    private final int mMaxAlpha;
-    private int mColor;
-
-    public DropOutlineDrawable(Context context) {
-        super();
-        // TODO(b/169894807): Use corner specific radii and maybe lower radius for non-edge corners
-        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
-        mColor = context.getColor(R.color.drop_outline_background);
-        mMaxAlpha = Color.alpha(mColor);
-        // Initialize as hidden
-        ALPHA.set(this, 0);
-    }
-
-    @Override
-    public void setColorFilter(@Nullable ColorFilter colorFilter) {
-        // Do nothing
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        mColor = ColorUtils.setAlphaComponent(mColor, alpha);
-        mPaint.setColor(mColor);
-        invalidateSelf();
-    }
-
-    @Override
-    public int getAlpha() {
-        return Color.alpha(mColor);
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    @Override
-    protected void onBoundsChange(Rect bounds) {
-        invalidateSelf();
-    }
-
-    @Override
-    public void draw(@NonNull Canvas canvas) {
-        canvas.drawRoundRect(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom,
-                mCornerRadius, mCornerRadius, mPaint);
-    }
-
-    public void setRegionBounds(Rect bounds) {
-        mBounds.set(bounds);
-        invalidateSelf();
-    }
-
-    public Rect getRegionBounds() {
-        return mBounds;
-    }
-
-    ObjectAnimator startBoundsAnimation(Rect toBounds, Interpolator interpolator) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Animate bounds: from=%s to=%s",
-                mBounds, toBounds);
-        if (mBoundsAnimator != null) {
-            mBoundsAnimator.cancel();
-        }
-        mBoundsAnimator = ObjectAnimator.ofObject(this, BOUNDS, mRectEvaluator,
-                mBounds, toBounds);
-        mBoundsAnimator.setDuration(BOUNDS_DURATION);
-        mBoundsAnimator.setInterpolator(interpolator);
-        mBoundsAnimator.start();
-        return mBoundsAnimator;
-    }
-
-    ObjectAnimator startVisibilityAnimation(boolean visible, Interpolator interpolator) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Animate alpha: from=%d to=%d",
-                Color.alpha(mColor), visible ? mMaxAlpha : 0);
-        if (mAlphaAnimator != null) {
-            mAlphaAnimator.cancel();
-        }
-        mAlphaAnimator = ObjectAnimator.ofInt(this, ALPHA, Color.alpha(mColor),
-                visible ? mMaxAlpha : 0);
-        mAlphaAnimator.setDuration(ALPHA_DURATION);
-        mAlphaAnimator.setInterpolator(interpolator);
-        mAlphaAnimator.start();
-        return mAlphaAnimator;
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 04fc79a..8402775 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -19,9 +19,15 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration;
+import android.content.Context;
+import android.graphics.Rect;
 import android.os.IBinder;
+import android.util.ArrayMap;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.TransitionInfo;
@@ -31,6 +37,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -39,23 +47,37 @@
 import java.util.List;
 
 /**
- * The {@link Transitions.TransitionHandler} that handles freeform task maximizing and restoring
- * transitions.
+ * The {@link Transitions.TransitionHandler} that handles freeform task maximizing, closing, and
+ * restoring transitions.
  */
 public class FreeformTaskTransitionHandler
         implements Transitions.TransitionHandler, FreeformTaskTransitionStarter {
-
+    private static final int CLOSE_ANIM_DURATION = 400;
+    private final Context mContext;
     private final Transitions mTransitions;
     private final WindowDecorViewModel mWindowDecorViewModel;
+    private final DisplayController mDisplayController;
+    private final ShellExecutor mMainExecutor;
+    private final ShellExecutor mAnimExecutor;
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
 
+    private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();
+
     public FreeformTaskTransitionHandler(
             ShellInit shellInit,
             Transitions transitions,
-            WindowDecorViewModel windowDecorViewModel) {
+            Context context,
+            WindowDecorViewModel windowDecorViewModel,
+            DisplayController displayController,
+            ShellExecutor mainExecutor,
+            ShellExecutor animExecutor) {
         mTransitions = transitions;
+        mContext = context;
         mWindowDecorViewModel = windowDecorViewModel;
+        mDisplayController = displayController;
+        mMainExecutor = mainExecutor;
+        mAnimExecutor = animExecutor;
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             shellInit.addInitCallback(this::onInit, this);
         }
@@ -103,6 +125,14 @@
             @NonNull SurfaceControl.Transaction finishT,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         boolean transitionHandled = false;
+        final ArrayList<Animator> animations = new ArrayList<>();
+        final Runnable onAnimFinish = () -> {
+            if (!animations.isEmpty()) return;
+            mMainExecutor.execute(() -> {
+                mAnimations.remove(transition);
+                finishCallback.onTransitionFinished(null /* wct */);
+            });
+        };
         for (TransitionInfo.Change change : info.getChanges()) {
             if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
                 continue;
@@ -121,21 +151,45 @@
                 case WindowManager.TRANSIT_TO_BACK:
                     transitionHandled |= startMinimizeTransition(transition);
                     break;
+                case WindowManager.TRANSIT_CLOSE:
+                    if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+                        transitionHandled |= startCloseTransition(transition, change,
+                                finishT, animations, onAnimFinish);
+                    }
+                    break;
             }
         }
-
-        mPendingTransitionTokens.remove(transition);
-
         if (!transitionHandled) {
             return false;
         }
-
+        mAnimations.put(transition, animations);
+        // startT must be applied before animations start.
         startT.apply();
-        mTransitions.getMainExecutor().execute(
-                () -> finishCallback.onTransitionFinished(null, null));
+        mAnimExecutor.execute(() -> {
+            for (Animator anim : animations) {
+                anim.start();
+            }
+        });
+        // Run this here in case no animators are created.
+        onAnimFinish.run();
+        mPendingTransitionTokens.remove(transition);
         return true;
     }
 
+    @Override
+    public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        ArrayList<Animator> animations = mAnimations.get(mergeTarget);
+        if (animations == null) return;
+        mAnimExecutor.execute(() -> {
+            for (Animator anim : animations) {
+                anim.end();
+            }
+        });
+
+    }
+
     private boolean startChangeTransition(
             IBinder transition,
             int type,
@@ -165,6 +219,36 @@
         return mPendingTransitionTokens.contains(transition);
     }
 
+    private boolean startCloseTransition(IBinder transition, TransitionInfo.Change change,
+            SurfaceControl.Transaction finishT, ArrayList<Animator> animations,
+            Runnable onAnimFinish) {
+        if (!mPendingTransitionTokens.contains(transition)) return false;
+        int screenHeight = mDisplayController
+                .getDisplayLayout(change.getTaskInfo().displayId).height();
+        ValueAnimator animator = new ValueAnimator();
+        animator.setDuration(CLOSE_ANIM_DURATION)
+                .setFloatValues(0f, 1f);
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        SurfaceControl sc = change.getLeash();
+        finishT.hide(sc);
+        Rect startBounds = new Rect(change.getTaskInfo().configuration.windowConfiguration
+                .getBounds());
+        animator.addUpdateListener(animation -> {
+            t.setPosition(sc, startBounds.left,
+                    startBounds.top + (animation.getAnimatedFraction() * screenHeight));
+            t.apply();
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                animations.remove(animator);
+                onAnimFinish.run();
+            }
+        });
+        animations.add(animator);
+        return true;
+    }
+
     @Nullable
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index 56bd188..2ef92ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -19,7 +19,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
 import static android.view.WindowManager.TRANSIT_SLEEP;
@@ -169,7 +168,7 @@
                             // Post our finish callback to let startAnimation finish first.
                             mMainExecutor.executeDelayed(() -> {
                                 mStartedTransitions.remove(transition);
-                                finishCallback.onTransitionFinished(wct, null);
+                                finishCallback.onTransitionFinished(wct);
                             }, 0);
                         }
                     });
@@ -206,7 +205,7 @@
                 // implementing an AIDL interface.
                 Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e);
             }
-            nextFinishCallback.onTransitionFinished(null, null);
+            nextFinishCallback.onTransitionFinished(null);
         } else if (nextInfo.getType() == TRANSIT_SLEEP) {
             // An empty SLEEP transition comes in as a signal to abort transitions whenever a sleep
             // token is held. In cases where keyguard is showing, we are running the animation for
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index f34d2a8..a9aa6ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -67,4 +67,11 @@
      * view hierarchy or destroyed.
      */
     default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
+
+    /**
+     * @return {@link PipTransitionController} instance.
+     */
+    default PipTransitionController getPipTransitionController() {
+        return null;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
deleted file mode 100644
index 48a3fc2..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
+++ /dev/null
@@ -1,94 +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.wm.shell.pip;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
-
-import android.app.AppOpsManager;
-import android.app.AppOpsManager.OnOpChangedListener;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.util.Pair;
-
-import com.android.wm.shell.common.ShellExecutor;
-
-public class PipAppOpsListener {
-    private static final String TAG = PipAppOpsListener.class.getSimpleName();
-
-    private Context mContext;
-    private ShellExecutor mMainExecutor;
-    private AppOpsManager mAppOpsManager;
-    private Callback mCallback;
-
-    private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
-        @Override
-        public void onOpChanged(String op, String packageName) {
-            try {
-                // Dismiss the PiP once the user disables the app ops setting for that package
-                final Pair<ComponentName, Integer> topPipActivityInfo =
-                        PipUtils.getTopPipActivity(mContext);
-                if (topPipActivityInfo.first != null) {
-                    final ApplicationInfo appInfo = mContext.getPackageManager()
-                            .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
-                    if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
-                            mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
-                                    packageName) != MODE_ALLOWED) {
-                        mMainExecutor.execute(() -> mCallback.dismissPip());
-                    }
-                }
-            } catch (NameNotFoundException e) {
-                // Unregister the listener if the package can't be found
-                unregisterAppOpsListener();
-            }
-        }
-    };
-
-    public PipAppOpsListener(Context context, Callback callback, ShellExecutor mainExecutor) {
-        mContext = context;
-        mMainExecutor = mainExecutor;
-        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
-        mCallback = callback;
-    }
-
-    public void onActivityPinned(String packageName) {
-        // Register for changes to the app ops setting for this package while it is in PiP
-        registerAppOpsListener(packageName);
-    }
-
-    public void onActivityUnpinned() {
-        // Unregister for changes to the previously PiP'ed package
-        unregisterAppOpsListener();
-    }
-
-    private void registerAppOpsListener(String packageName) {
-        mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName,
-                mAppOpsChangedListener);
-    }
-
-    private void unregisterAppOpsListener() {
-        mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
-    }
-
-    /** Callback for PipAppOpsListener to request changes to the PIP window. */
-    public interface Callback {
-        /** Dismisses the PIP window. */
-        void dismissPip();
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index f51eb52..ac711ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -28,7 +28,7 @@
 import android.view.Gravity;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 
 import java.io.PrintWriter;
 
@@ -41,7 +41,8 @@
     private static final float INVALID_SNAP_FRACTION = -1f;
 
     @NonNull private final PipBoundsState mPipBoundsState;
-    @NonNull protected final PipSizeSpecHandler mPipSizeSpecHandler;
+    @NonNull protected final PipDisplayLayoutState mPipDisplayLayoutState;
+    @NonNull protected final SizeSpecSource mSizeSpecSource;
     private final PipSnapAlgorithm mSnapAlgorithm;
     private final PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
 
@@ -53,11 +54,13 @@
     public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState,
             @NonNull PipSnapAlgorithm pipSnapAlgorithm,
             @NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
-            @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
+            @NonNull PipDisplayLayoutState pipDisplayLayoutState,
+            @NonNull SizeSpecSource sizeSpecSource) {
         mPipBoundsState = pipBoundsState;
         mSnapAlgorithm = pipSnapAlgorithm;
         mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
-        mPipSizeSpecHandler = pipSizeSpecHandler;
+        mPipDisplayLayoutState = pipDisplayLayoutState;
+        mSizeSpecSource = sizeSpecSource;
         reloadResources(context);
         // Initialize the aspect ratio to the default aspect ratio.  Don't do this in reload
         // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
@@ -74,11 +77,6 @@
                 R.dimen.config_pictureInPictureDefaultAspectRatio);
         mDefaultStackGravity = res.getInteger(
                 R.integer.config_defaultPictureInPictureGravity);
-        final String screenEdgeInsetsDpString = res.getString(
-                R.string.config_defaultPictureInPictureScreenEdgeInsets);
-        final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
-                ? Size.parseSize(screenEdgeInsetsDpString)
-                : null;
         mMinAspectRatio = res.getFloat(
                 com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
         mMaxAspectRatio = res.getFloat(
@@ -160,8 +158,8 @@
             // If either dimension is smaller than the allowed minimum, adjust them
             // according to mOverridableMinSize
             return new Size(
-                    Math.max(windowLayout.minWidth, mPipSizeSpecHandler.getOverrideMinEdgeSize()),
-                    Math.max(windowLayout.minHeight, mPipSizeSpecHandler.getOverrideMinEdgeSize()));
+                    Math.max(windowLayout.minWidth, getOverrideMinEdgeSize()),
+                    Math.max(windowLayout.minHeight, getOverrideMinEdgeSize()));
         }
         return null;
     }
@@ -255,10 +253,10 @@
         final Size size;
         if (useCurrentMinEdgeSize || useCurrentSize) {
             // Use the existing size but adjusted to the new aspect ratio.
-            size = mPipSizeSpecHandler.getSizeForAspectRatio(
+            size = mSizeSpecSource.getSizeForAspectRatio(
                     new Size(stackBounds.width(), stackBounds.height()), aspectRatio);
         } else {
-            size = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
+            size = mSizeSpecSource.getDefaultSize(aspectRatio);
         }
 
         final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
@@ -287,7 +285,7 @@
         getInsetBounds(insetBounds);
 
         // Calculate the default size
-        defaultSize = mPipSizeSpecHandler.getDefaultSize(mDefaultAspectRatio);
+        defaultSize = mSizeSpecSource.getDefaultSize(mDefaultAspectRatio);
 
         // Now that we have the default size, apply the snap fraction if valid or position the
         // bounds using the default gravity.
@@ -309,7 +307,11 @@
      * Populates the bounds on the screen that the PIP can be visible in.
      */
     public void getInsetBounds(Rect outRect) {
-        outRect.set(mPipSizeSpecHandler.getInsetBounds());
+        outRect.set(mPipDisplayLayoutState.getInsetBounds());
+    }
+
+    private int getOverrideMinEdgeSize() {
+        return mSizeSpecSource.getOverrideMinEdgeSize();
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 9a775df..279ffc5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -36,7 +36,7 @@
 import com.android.internal.util.function.TriConsumer;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.io.PrintWriter;
@@ -87,7 +87,7 @@
     private int mStashOffset;
     private @Nullable PipReentryState mPipReentryState;
     private final LauncherState mLauncherState = new LauncherState();
-    private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler;
+    private final @NonNull SizeSpecSource mSizeSpecSource;
     private @Nullable ComponentName mLastPipComponentName;
     private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
     private boolean mIsImeShowing;
@@ -127,17 +127,20 @@
     private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
     private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
 
-    public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler,
-            PipDisplayLayoutState pipDisplayLayoutState) {
+    public PipBoundsState(@NonNull Context context, @NonNull SizeSpecSource sizeSpecSource,
+            @NonNull PipDisplayLayoutState pipDisplayLayoutState) {
         mContext = context;
         reloadResources();
-        mPipSizeSpecHandler = pipSizeSpecHandler;
+        mSizeSpecSource = sizeSpecSource;
         mPipDisplayLayoutState = pipDisplayLayoutState;
     }
 
     /** Reloads the resources. */
     public void onConfigurationChanged() {
         reloadResources();
+
+        // update the size spec resources upon config change too
+        mSizeSpecSource.onConfigurationChanged();
     }
 
     private void reloadResources() {
@@ -319,7 +322,7 @@
     /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
     public void setOverrideMinSize(@Nullable Size overrideMinSize) {
         final boolean changed = !Objects.equals(overrideMinSize, getOverrideMinSize());
-        mPipSizeSpecHandler.setOverrideMinSize(overrideMinSize);
+        mSizeSpecSource.setOverrideMinSize(overrideMinSize);
         if (changed && mOnMinimalSizeChangeCallback != null) {
             mOnMinimalSizeChangeCallback.run();
         }
@@ -328,12 +331,12 @@
     /** Returns the preferred minimal size specified by the activity in PIP. */
     @Nullable
     public Size getOverrideMinSize() {
-        return mPipSizeSpecHandler.getOverrideMinSize();
+        return mSizeSpecSource.getOverrideMinSize();
     }
 
     /** Returns the minimum edge size of the override minimum size, or 0 if not set. */
     public int getOverrideMinEdgeSize() {
-        return mPipSizeSpecHandler.getOverrideMinEdgeSize();
+        return mSizeSpecSource.getOverrideMinEdgeSize();
     }
 
     /** Get the state of the bounds in motion. */
@@ -613,5 +616,6 @@
         }
         mLauncherState.dump(pw, innerPrefix);
         mMotionBoundsState.dump(pw, innerPrefix);
+        mSizeSpecSource.dump(pw, innerPrefix);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
index 0f76af4..456f85b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
@@ -16,12 +16,18 @@
 
 package com.android.wm.shell.pip;
 
+import static com.android.wm.shell.pip.PipUtils.dpToPx;
+
 import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
 import android.graphics.Rect;
+import android.util.Size;
 import android.view.Surface;
 
 import androidx.annotation.NonNull;
 
+import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.dagger.WMSingleton;
 
@@ -40,13 +46,51 @@
     private int mDisplayId;
     @NonNull private DisplayLayout mDisplayLayout;
 
+    private Point mScreenEdgeInsets = null;
+
     @Inject
     public PipDisplayLayoutState(Context context) {
         mContext = context;
         mDisplayLayout = new DisplayLayout();
+        reloadResources();
     }
 
-    /** Update the display layout. */
+    /** Responds to configuration change. */
+    public void onConfigurationChanged() {
+        reloadResources();
+    }
+
+    private void reloadResources() {
+        Resources res = mContext.getResources();
+
+        final String screenEdgeInsetsDpString = res.getString(
+                R.string.config_defaultPictureInPictureScreenEdgeInsets);
+        final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
+                ? Size.parseSize(screenEdgeInsetsDpString)
+                : null;
+        mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
+                : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
+                        dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
+    }
+
+    public Point getScreenEdgeInsets() {
+        return mScreenEdgeInsets;
+    }
+
+    /**
+     * Returns the inset bounds the PIP window can be visible in.
+     */
+    public Rect getInsetBounds() {
+        Rect insetBounds = new Rect();
+        Rect insets = getDisplayLayout().stableInsets();
+        insetBounds.set(insets.left + getScreenEdgeInsets().x,
+                insets.top + getScreenEdgeInsets().y,
+                getDisplayLayout().width() - insets.right - getScreenEdgeInsets().x,
+                getDisplayLayout().height() - insets.bottom - getScreenEdgeInsets().y);
+        return insetBounds;
+    }
+
+    /** Set the display layout. */
     public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
         mDisplayLayout.set(displayLayout);
     }
@@ -87,5 +131,6 @@
         pw.println(prefix + TAG);
         pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
         pw.println(innerPrefix + "getDisplayBounds=" + getDisplayBounds());
+        pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
index 2590cab..ddffb5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
@@ -150,13 +150,15 @@
         mContext = context;
         mMainHandler = mainHandler;
         mHandlerExecutor = new HandlerExecutor(mMainHandler);
-        IntentFilter mediaControlFilter = new IntentFilter();
-        mediaControlFilter.addAction(ACTION_PLAY);
-        mediaControlFilter.addAction(ACTION_PAUSE);
-        mediaControlFilter.addAction(ACTION_NEXT);
-        mediaControlFilter.addAction(ACTION_PREV);
-        mContext.registerReceiverForAllUsers(mMediaActionReceiver, mediaControlFilter,
-                SYSTEMUI_PERMISSION, mainHandler, Context.RECEIVER_EXPORTED);
+        if (!PipUtils.isPip2ExperimentEnabled()) {
+            IntentFilter mediaControlFilter = new IntentFilter();
+            mediaControlFilter.addAction(ACTION_PLAY);
+            mediaControlFilter.addAction(ACTION_PAUSE);
+            mediaControlFilter.addAction(ACTION_NEXT);
+            mediaControlFilter.addAction(ACTION_PREV);
+            mContext.registerReceiverForAllUsers(mMediaActionReceiver, mediaControlFilter,
+                    SYSTEMUI_PERMISSION, mainHandler, Context.RECEIVER_EXPORTED);
+        }
 
         // Creates the standard media buttons that we may show.
         mPauseAction = getDefaultRemoteAction(R.string.pip_pause,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 08da485..19c60c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -63,7 +63,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.RemoteException;
-import android.os.SystemProperties;
 import android.view.Choreographer;
 import android.view.Display;
 import android.view.Surface;
@@ -364,13 +363,15 @@
         mMainExecutor = mainExecutor;
 
         // TODO: Can be removed once wm components are created on the shell-main thread
-        mMainExecutor.execute(() -> {
-            mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
-        });
-        mTaskOrganizer.addFocusListener(this);
-        mPipTransitionController.setPipOrganizer(this);
-        displayController.addDisplayWindowListener(this);
-        pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
+        if (!PipUtils.isPip2ExperimentEnabled()) {
+            mMainExecutor.execute(() -> {
+                mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
+            });
+            mTaskOrganizer.addFocusListener(this);
+            mPipTransitionController.setPipOrganizer(this);
+            displayController.addDisplayWindowListener(this);
+            pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
+        }
     }
 
     public PipTransitionController getTransitionController() {
@@ -590,7 +591,8 @@
                 SplitScreenController split = mSplitScreenOptional.get();
                 if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) {
                     split.prepareExitSplitScreen(wct, split.getStageOfTask(
-                            mTaskInfo.lastParentTaskIdBeforePip));
+                            mTaskInfo.lastParentTaskIdBeforePip),
+                            SplitScreenController.EXIT_REASON_APP_FINISHED);
                 }
             }
             mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds);
@@ -1732,17 +1734,13 @@
                 // animation.
                 // TODO(b/272819817): cleanup the null-check and extra logging.
                 final boolean hasTopActivityInfo = mTaskInfo.topActivityInfo != null;
-                if (!hasTopActivityInfo) {
-                    ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
-                            "%s: TaskInfo.topActivityInfo is null", TAG);
-                }
-                if (SystemProperties.getBoolean(
-                        "persist.wm.debug.enable_pip_app_icon_overlay", true)
-                        && hasTopActivityInfo) {
+                if (hasTopActivityInfo) {
                     animator.setAppIconContentOverlay(
                             mContext, currentBounds, mTaskInfo.topActivityInfo,
                             mPipBoundsState.getLauncherState().getAppIconSizePx());
                 } else {
+                    ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                            "%s: TaskInfo.topActivityInfo is null", TAG);
                     animator.setColorContentOverlay(mContext);
                 }
             } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index e3d53fc..db7e2c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -49,7 +49,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
-import android.os.SystemProperties;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -447,7 +446,7 @@
         // handler if there is a pending PiP animation.
         final Transitions.TransitionFinishCallback finishCallback = mFinishCallback;
         mFinishCallback = null;
-        finishCallback.onTransitionFinished(wct, null /* callback */);
+        finishCallback.onTransitionFinished(wct);
     }
 
     @Override
@@ -456,7 +455,7 @@
         // for example, when app crashes while in PiP and exit transition has not started
         mCurrentPipTaskToken = null;
         if (mFinishCallback == null) return;
-        mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
+        mFinishCallback.onTransitionFinished(null /* wct */);
         mFinishCallback = null;
         mFinishTransaction = null;
     }
@@ -586,7 +585,7 @@
         final boolean useLocalLeash = activitySc != null;
         final boolean toFullscreen = pipChange.getEndAbsBounds().equals(
                 mPipBoundsState.getDisplayBounds());
-        mFinishCallback = (wct, wctCB) -> {
+        mFinishCallback = (wct) -> {
             mPipOrganizer.onExitPipFinished(taskInfo);
 
             // TODO(b/286346098): remove the OPEN app flicker completely
@@ -610,7 +609,7 @@
                 mPipAnimationController.resetAnimatorState();
                 finishTransaction.remove(pipLeash);
             }
-            finishCallback.onTransitionFinished(wct, wctCB);
+            finishCallback.onTransitionFinished(wct);
         };
         mFinishTransaction = finishTransaction;
 
@@ -750,7 +749,7 @@
         finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
                 mPipDisplayLayoutState.getDisplayBounds());
         mPipOrganizer.onExitPipFinished(taskInfo);
-        finishCallback.onTransitionFinished(null, null);
+        finishCallback.onTransitionFinished(null);
     }
 
     /** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */
@@ -902,17 +901,13 @@
                 // animation.
                 // TODO(b/272819817): cleanup the null-check and extra logging.
                 final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
-                if (!hasTopActivityInfo) {
-                    ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
-                            "%s: TaskInfo.topActivityInfo is null", TAG);
-                }
-                if (SystemProperties.getBoolean(
-                        "persist.wm.debug.enable_pip_app_icon_overlay", true)
-                        && hasTopActivityInfo) {
+                if (hasTopActivityInfo) {
                     animator.setAppIconContentOverlay(
                             mContext, currentBounds, taskInfo.topActivityInfo,
                             mPipBoundsState.getLauncherState().getAppIconSizePx());
                 } else {
+                    ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+                            "%s: TaskInfo.topActivityInfo is null", TAG);
                     animator.setColorContentOverlay(mContext);
                 }
             } else {
@@ -1045,7 +1040,7 @@
         startTransaction.apply();
 
         mPipOrganizer.onExitPipFinished(taskInfo);
-        finishCallback.onTransitionFinished(null, null);
+        finishCallback.onTransitionFinished(null);
     }
 
     private void resetPrevPip(@NonNull TransitionInfo.Change prevPipTaskChange,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 6362793..0f74f9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -142,8 +142,10 @@
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipAnimationController = pipAnimationController;
         mTransitions = transitions;
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            shellInit.addInitCallback(this::onInit, this);
+        if (!PipUtils.isPip2ExperimentEnabled()) {
+            if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+                shellInit.addInitCallback(this::onInit, this);
+            }
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
index 8b98790..3cd9848 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
@@ -27,6 +27,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Pair;
@@ -46,6 +47,9 @@
     // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal.
     private static final double EPSILON = 1e-7;
 
+    private static final String ENABLE_PIP2_IMPLEMENTATION =
+            "persist.wm.debug.enable_pip2_implementation";
+
     /**
      * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
      * The component name may be null if no such activity exists.
@@ -134,4 +138,8 @@
             return null;
         }
     }
+
+    public static boolean isPip2ExperimentEnabled() {
+        return SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
index f9332e4..2d34035 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipKeepClearAlgorithm.java
@@ -40,6 +40,7 @@
                     "persist.wm.debug.enable_pip_keep_clear_algorithm_gravity", false);
 
     protected int mKeepClearAreasPadding;
+    private int mImeOffset;
 
     public PhonePipKeepClearAlgorithm(Context context) {
         reloadResources(context);
@@ -48,6 +49,7 @@
     private void reloadResources(Context context) {
         final Resources res = context.getResources();
         mKeepClearAreasPadding = res.getDimensionPixelSize(R.dimen.pip_keep_clear_areas_padding);
+        mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
     }
 
     /**
@@ -61,7 +63,7 @@
         Rect insets = new Rect();
         pipBoundsAlgorithm.getInsetBounds(insets);
         if (pipBoundsState.isImeShowing()) {
-            insets.bottom -= pipBoundsState.getImeHeight();
+            insets.bottom -= (pipBoundsState.getImeHeight() + mImeOffset);
         }
         // if PiP is stashed we only adjust the vertical position if it's outside of insets and
         // ignore all keep clear areas, since it's already on the side
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index bbd17cd..b872267 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -75,6 +75,7 @@
 import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.pip.IPip;
@@ -82,7 +83,6 @@
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
@@ -142,7 +142,6 @@
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
     private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
     private PipBoundsState mPipBoundsState;
-    private PipSizeSpecHandler mPipSizeSpecHandler;
     private PipDisplayLayoutState mPipDisplayLayoutState;
     private PipMotionHelper mPipMotionHelper;
     private PipTouchHandler mTouchHandler;
@@ -352,6 +351,10 @@
                             mMainExecutor.executeDelayed(
                                     mMovePipInResponseToKeepClearAreasChangeCallback,
                                     PIP_KEEP_CLEAR_AREAS_DELAY);
+
+                            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                                    "onKeepClearAreasChanged: restricted=%s, unrestricted=%s",
+                                    restricted, unrestricted);
                         }
                     }
                 }
@@ -402,7 +405,6 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
             PipBoundsState pipBoundsState,
-            PipSizeSpecHandler pipSizeSpecHandler,
             PipDisplayLayoutState pipDisplayLayoutState,
             PipMotionHelper pipMotionHelper,
             PipMediaController pipMediaController,
@@ -426,7 +428,7 @@
 
         return new PipController(context, shellInit, shellCommandHandler, shellController,
                 displayController, pipAnimationController, pipAppOpsListener,
-                pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler,
+                pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
                 pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController,
                 pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
                 windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
@@ -444,7 +446,6 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
-            PipSizeSpecHandler pipSizeSpecHandler,
             @NonNull PipDisplayLayoutState pipDisplayLayoutState,
             PipMotionHelper pipMotionHelper,
             PipMediaController pipMediaController,
@@ -461,8 +462,6 @@
             Optional<OneHandedController> oneHandedController,
             ShellExecutor mainExecutor
     ) {
-
-
         mContext = context;
         mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
@@ -472,7 +471,6 @@
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
         mPipBoundsState = pipBoundsState;
-        mPipSizeSpecHandler = pipSizeSpecHandler;
         mPipDisplayLayoutState = pipDisplayLayoutState;
         mPipMotionHelper = pipMotionHelper;
         mPipTaskOrganizer = pipTaskOrganizer;
@@ -493,7 +491,9 @@
         mDisplayInsetsController = displayInsetsController;
         mTabletopModeController = tabletopModeController;
 
-        shellInit.addInitCallback(this::onInit, this);
+        if (!PipUtils.isPip2ExperimentEnabled()) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
     }
 
     private void onInit() {
@@ -707,7 +707,7 @@
             // Try to move the PiP window if we have entered PiP mode.
             if (mPipTransitionState.hasEnteredPip()) {
                 final Rect pipBounds = mPipBoundsState.getBounds();
-                final Point edgeInsets = mPipSizeSpecHandler.getScreenEdgeInsets();
+                final Point edgeInsets = mPipDisplayLayoutState.getScreenEdgeInsets();
                 if ((pipBounds.height() + 2 * edgeInsets.y) > (displayBounds.height() / 2)) {
                     // PiP bounds is too big to fit either half, bail early.
                     return;
@@ -766,7 +766,7 @@
         mPipBoundsAlgorithm.onConfigurationChanged(mContext);
         mTouchHandler.onConfigurationChanged();
         mPipBoundsState.onConfigurationChanged();
-        mPipSizeSpecHandler.onConfigurationChanged();
+        mPipDisplayLayoutState.onConfigurationChanged();
     }
 
     @Override
@@ -947,12 +947,19 @@
      * Sets both shelf visibility and its height.
      */
     private void setShelfHeight(boolean visible, int height) {
+        if (mEnablePipKeepClearAlgorithm) {
+            // turn this into Launcher keep clear area registration instead
+            setLauncherKeepClearAreaHeight(visible, height);
+            return;
+        }
         if (!mIsKeyguardShowingOrAnimating) {
             setShelfHeightLocked(visible, height);
         }
     }
 
     private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "setLauncherKeepClearAreaHeight: visible=%b, height=%d", visible, height);
         if (visible) {
             Rect rect = new Rect(
                     0, mPipBoundsState.getDisplayBounds().bottom - height,
@@ -1010,9 +1017,10 @@
             int launcherRotation, Rect hotseatKeepClearArea) {
 
         if (mEnablePipKeepClearAlgorithm) {
-            // pre-emptively add the keep clear area for Hotseat, so that it is taken into account
+            // preemptively add the keep clear area for Hotseat, so that it is taken into account
             // when calculating the entry destination bounds of PiP window
-            mPipBoundsState.getRestrictedKeepClearAreas().add(hotseatKeepClearArea);
+            mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG,
+                    hotseatKeepClearArea);
         } else {
             int shelfHeight = hotseatKeepClearArea.height();
             setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight);
@@ -1212,7 +1220,6 @@
         mPipTaskOrganizer.dump(pw, innerPrefix);
         mPipBoundsState.dump(pw, innerPrefix);
         mPipInputConsumer.dump(pw, innerPrefix);
-        mPipSizeSpecHandler.dump(pw, innerPrefix);
         mPipDisplayLayoutState.dump(pw, innerPrefix);
     }
 
@@ -1261,6 +1268,11 @@
                 PipController.this.showPictureInPictureMenu();
             });
         }
+
+        @Override
+        public PipTransitionController getPipTransitionController() {
+            return mPipTransitionController;
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index 9729a40..da455f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -33,9 +33,10 @@
 import androidx.annotation.NonNull;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.bubbles.DismissView;
-import com.android.wm.shell.common.DismissCircleView;
+import com.android.wm.shell.bubbles.DismissViewUtils;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.bubbles.DismissCircleView;
+import com.android.wm.shell.common.bubbles.DismissView;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 import com.android.wm.shell.pip.PipUiEventLogger;
 
@@ -106,6 +107,7 @@
         }
 
         mTargetViewContainer = new DismissView(mContext);
+        DismissViewUtils.setup(mTargetViewContainer);
         mTargetView = mTargetViewContainer.getCircle();
         mTargetViewContainer.setOnApplyWindowInsetsListener((view, windowInsets) -> {
             if (!windowInsets.equals(mWindowInsets)) {
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 43d3f36..b251f6f 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
@@ -41,7 +41,7 @@
 import com.android.wm.shell.animation.PhysicsAnimator;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
-import com.android.wm.shell.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
deleted file mode 100644
index 7971c04..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ /dev/null
@@ -1,535 +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.wm.shell.pip.phone;
-
-import static com.android.wm.shell.pip.PipUtils.dpToPx;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.SystemProperties;
-import android.util.Size;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-
-import java.io.PrintWriter;
-
-/**
- * Acts as a source of truth for appropriate size spec for PIP.
- */
-public class PipSizeSpecHandler {
-    private static final String TAG = PipSizeSpecHandler.class.getSimpleName();
-
-    @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState;
-
-    private final SizeSpecSource mSizeSpecSourceImpl;
-
-    /** The preferred minimum (and default minimum) size specified by apps. */
-    @Nullable private Size mOverrideMinSize;
-    private int mOverridableMinSize;
-
-    /** Used to store values obtained from resource files. */
-    private Point mScreenEdgeInsets;
-    private float mMinAspectRatioForMinSize;
-    private float mMaxAspectRatioForMinSize;
-    private int mDefaultMinSize;
-
-    @NonNull private final Context mContext;
-
-    private interface SizeSpecSource {
-        /** Returns max size allowed for the PIP window */
-        Size getMaxSize(float aspectRatio);
-
-        /** Returns default size for the PIP window */
-        Size getDefaultSize(float aspectRatio);
-
-        /** Returns min size allowed for the PIP window */
-        Size getMinSize(float aspectRatio);
-
-        /** Returns the adjusted size based on current size and target aspect ratio */
-        Size getSizeForAspectRatio(Size size, float aspectRatio);
-
-        /** Updates internal resources on configuration changes */
-        default void reloadResources() {}
-    }
-
-    /**
-     * Determines PIP window size optimized for large screens and aspect ratios close to 1:1
-     */
-    private class SizeSpecLargeScreenOptimizedImpl implements SizeSpecSource {
-        private static final float DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16;
-
-        /** Default and minimum percentages for the PIP size logic. */
-        private final float mDefaultSizePercent;
-        private final float mMinimumSizePercent;
-
-        /** Aspect ratio that the PIP size spec logic optimizes for. */
-        private float mOptimizedAspectRatio;
-
-        private SizeSpecLargeScreenOptimizedImpl() {
-            mDefaultSizePercent = Float.parseFloat(SystemProperties
-                    .get("com.android.wm.shell.pip.phone.def_percentage", "0.6"));
-            mMinimumSizePercent = Float.parseFloat(SystemProperties
-                    .get("com.android.wm.shell.pip.phone.min_percentage", "0.5"));
-        }
-
-        @Override
-        public void reloadResources() {
-            final Resources res = mContext.getResources();
-
-            mOptimizedAspectRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio);
-            // make sure the optimized aspect ratio is valid with a default value to fall back to
-            if (mOptimizedAspectRatio > 1) {
-                mOptimizedAspectRatio = DEFAULT_OPTIMIZED_ASPECT_RATIO;
-            }
-        }
-
-        /**
-         * Calculates the max size of PIP.
-         *
-         * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge.
-         * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the
-         * whole screen. A linear function is used to calculate these sizes.
-         *
-         * @param aspectRatio aspect ratio of the PIP window
-         * @return dimensions of the max size of the PIP
-         */
-        @Override
-        public Size getMaxSize(float aspectRatio) {
-            final int totalHorizontalPadding = getInsetBounds().left
-                    + (getDisplayBounds().width() - getInsetBounds().right);
-            final int totalVerticalPadding = getInsetBounds().top
-                    + (getDisplayBounds().height() - getInsetBounds().bottom);
-
-            final int shorterLength = (int) (1f * Math.min(
-                    getDisplayBounds().width() - totalHorizontalPadding,
-                    getDisplayBounds().height() - totalVerticalPadding));
-
-            int maxWidth, maxHeight;
-
-            // use the optimized max sizing logic only within a certain aspect ratio range
-            if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) {
-                // this formula and its derivation is explained in b/198643358#comment16
-                maxWidth = (int) (mOptimizedAspectRatio * shorterLength
-                        + shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1
-                        + aspectRatio));
-                maxHeight = Math.round(maxWidth / aspectRatio);
-            } else {
-                if (aspectRatio > 1f) {
-                    maxWidth = shorterLength;
-                    maxHeight = Math.round(maxWidth / aspectRatio);
-                } else {
-                    maxHeight = shorterLength;
-                    maxWidth = Math.round(maxHeight * aspectRatio);
-                }
-            }
-
-            return new Size(maxWidth, maxHeight);
-        }
-
-        /**
-         * Decreases the dimensions by a percentage relative to max size to get default size.
-         *
-         * @param aspectRatio aspect ratio of the PIP window
-         * @return dimensions of the default size of the PIP
-         */
-        @Override
-        public Size getDefaultSize(float aspectRatio) {
-            Size minSize = this.getMinSize(aspectRatio);
-
-            if (mOverrideMinSize != null) {
-                return minSize;
-            }
-
-            Size maxSize = this.getMaxSize(aspectRatio);
-
-            int defaultWidth = Math.max(Math.round(maxSize.getWidth() * mDefaultSizePercent),
-                    minSize.getWidth());
-            int defaultHeight = Math.round(defaultWidth / aspectRatio);
-
-            return new Size(defaultWidth, defaultHeight);
-        }
-
-        /**
-         * Decreases the dimensions by a certain percentage relative to max size to get min size.
-         *
-         * @param aspectRatio aspect ratio of the PIP window
-         * @return dimensions of the min size of the PIP
-         */
-        @Override
-        public Size getMinSize(float aspectRatio) {
-            // if there is an overridden min size provided, return that
-            if (mOverrideMinSize != null) {
-                return adjustOverrideMinSizeToAspectRatio(aspectRatio);
-            }
-
-            Size maxSize = this.getMaxSize(aspectRatio);
-
-            int minWidth = Math.round(maxSize.getWidth() * mMinimumSizePercent);
-            int minHeight = Math.round(maxSize.getHeight() * mMinimumSizePercent);
-
-            // make sure the calculated min size is not smaller than the allowed default min size
-            if (aspectRatio > 1f) {
-                minHeight = Math.max(minHeight, mDefaultMinSize);
-                minWidth = Math.round(minHeight * aspectRatio);
-            } else {
-                minWidth = Math.max(minWidth, mDefaultMinSize);
-                minHeight = Math.round(minWidth / aspectRatio);
-            }
-            return new Size(minWidth, minHeight);
-        }
-
-        /**
-         * Returns the size for target aspect ratio making sure new size conforms with the rules.
-         *
-         * <p>Recalculates the dimensions such that the target aspect ratio is achieved, while
-         * maintaining the same maximum size to current size ratio.
-         *
-         * @param size current size
-         * @param aspectRatio target aspect ratio
-         */
-        @Override
-        public Size getSizeForAspectRatio(Size size, float aspectRatio) {
-            float currAspectRatio = (float) size.getWidth() / size.getHeight();
-
-            // getting the percentage of the max size that current size takes
-            Size currentMaxSize = getMaxSize(currAspectRatio);
-            float currentPercent = (float) size.getWidth() / currentMaxSize.getWidth();
-
-            // getting the max size for the target aspect ratio
-            Size updatedMaxSize = getMaxSize(aspectRatio);
-
-            int width = Math.round(updatedMaxSize.getWidth() * currentPercent);
-            int height = Math.round(updatedMaxSize.getHeight() * currentPercent);
-
-            // adjust the dimensions if below allowed min edge size
-            if (width < getMinEdgeSize() && aspectRatio <= 1) {
-                width = getMinEdgeSize();
-                height = Math.round(width / aspectRatio);
-            } else if (height < getMinEdgeSize() && aspectRatio > 1) {
-                height = getMinEdgeSize();
-                width = Math.round(height * aspectRatio);
-            }
-
-            // reduce the dimensions of the updated size to the calculated percentage
-            return new Size(width, height);
-        }
-    }
-
-    private class SizeSpecDefaultImpl implements SizeSpecSource {
-        private float mDefaultSizePercent;
-        private float mMinimumSizePercent;
-
-        @Override
-        public void reloadResources() {
-            final Resources res = mContext.getResources();
-
-            mMaxAspectRatioForMinSize = res.getFloat(
-                    R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
-            mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
-
-            mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent);
-            mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1);
-        }
-
-        @Override
-        public Size getMaxSize(float aspectRatio) {
-            final int shorterLength = Math.min(getDisplayBounds().width(),
-                    getDisplayBounds().height());
-
-            final int totalHorizontalPadding = getInsetBounds().left
-                    + (getDisplayBounds().width() - getInsetBounds().right);
-            final int totalVerticalPadding = getInsetBounds().top
-                    + (getDisplayBounds().height() - getInsetBounds().bottom);
-
-            final int maxWidth, maxHeight;
-
-            if (aspectRatio > 1f) {
-                maxWidth = (int) Math.max(getDefaultSize(aspectRatio).getWidth(),
-                        shorterLength - totalHorizontalPadding);
-                maxHeight = (int) (maxWidth / aspectRatio);
-            } else {
-                maxHeight = (int) Math.max(getDefaultSize(aspectRatio).getHeight(),
-                        shorterLength - totalVerticalPadding);
-                maxWidth = (int) (maxHeight * aspectRatio);
-            }
-
-            return new Size(maxWidth, maxHeight);
-        }
-
-        @Override
-        public Size getDefaultSize(float aspectRatio) {
-            if (mOverrideMinSize != null) {
-                return this.getMinSize(aspectRatio);
-            }
-
-            final int smallestDisplaySize = Math.min(getDisplayBounds().width(),
-                    getDisplayBounds().height());
-            final int minSize = (int) Math.max(getMinEdgeSize(),
-                    smallestDisplaySize * mDefaultSizePercent);
-
-            final int width;
-            final int height;
-
-            if (aspectRatio <= mMinAspectRatioForMinSize
-                    || aspectRatio > mMaxAspectRatioForMinSize) {
-                // Beyond these points, we can just use the min size as the shorter edge
-                if (aspectRatio <= 1) {
-                    // Portrait, width is the minimum size
-                    width = minSize;
-                    height = Math.round(width / aspectRatio);
-                } else {
-                    // Landscape, height is the minimum size
-                    height = minSize;
-                    width = Math.round(height * aspectRatio);
-                }
-            } else {
-                // Within these points, ensure that the bounds fit within the radius of the limits
-                // at the points
-                final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
-                final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
-                height = (int) Math.round(Math.sqrt((radius * radius)
-                        / (aspectRatio * aspectRatio + 1)));
-                width = Math.round(height * aspectRatio);
-            }
-
-            return new Size(width, height);
-        }
-
-        @Override
-        public Size getMinSize(float aspectRatio) {
-            if (mOverrideMinSize != null) {
-                return adjustOverrideMinSizeToAspectRatio(aspectRatio);
-            }
-
-            final int shorterLength = Math.min(getDisplayBounds().width(),
-                    getDisplayBounds().height());
-            final int minWidth, minHeight;
-
-            if (aspectRatio > 1f) {
-                minWidth = (int) Math.min(getDefaultSize(aspectRatio).getWidth(),
-                        shorterLength * mMinimumSizePercent);
-                minHeight = (int) (minWidth / aspectRatio);
-            } else {
-                minHeight = (int) Math.min(getDefaultSize(aspectRatio).getHeight(),
-                        shorterLength * mMinimumSizePercent);
-                minWidth = (int) (minHeight * aspectRatio);
-            }
-
-            return new Size(minWidth, minHeight);
-        }
-
-        @Override
-        public Size getSizeForAspectRatio(Size size, float aspectRatio) {
-            final int smallestSize = Math.min(size.getWidth(), size.getHeight());
-            final int minSize = Math.max(getMinEdgeSize(), smallestSize);
-
-            final int width;
-            final int height;
-            if (aspectRatio <= 1) {
-                // Portrait, width is the minimum size.
-                width = minSize;
-                height = Math.round(width / aspectRatio);
-            } else {
-                // Landscape, height is the minimum size
-                height = minSize;
-                width = Math.round(height * aspectRatio);
-            }
-
-            return new Size(width, height);
-        }
-    }
-
-    public PipSizeSpecHandler(Context context, PipDisplayLayoutState pipDisplayLayoutState) {
-        mContext = context;
-        mPipDisplayLayoutState = pipDisplayLayoutState;
-
-        // choose between two implementations of size spec logic
-        if (supportsPipSizeLargeScreen()) {
-            mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl();
-        } else {
-            mSizeSpecSourceImpl = new SizeSpecDefaultImpl();
-        }
-
-        reloadResources();
-    }
-
-    /** Reloads the resources */
-    public void onConfigurationChanged() {
-        reloadResources();
-    }
-
-    private void reloadResources() {
-        final Resources res = mContext.getResources();
-
-        mDefaultMinSize = res.getDimensionPixelSize(
-                R.dimen.default_minimal_size_pip_resizable_task);
-        mOverridableMinSize = res.getDimensionPixelSize(
-                R.dimen.overridable_minimal_size_pip_resizable_task);
-
-        final String screenEdgeInsetsDpString = res.getString(
-                R.string.config_defaultPictureInPictureScreenEdgeInsets);
-        final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
-                ? Size.parseSize(screenEdgeInsetsDpString)
-                : null;
-        mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
-                : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
-                        dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
-
-        // update the internal resources of the size spec source's stub
-        mSizeSpecSourceImpl.reloadResources();
-    }
-
-    @NonNull
-    private Rect getDisplayBounds() {
-        return mPipDisplayLayoutState.getDisplayBounds();
-    }
-
-    public Point getScreenEdgeInsets() {
-        return mScreenEdgeInsets;
-    }
-
-    /**
-     * Returns the inset bounds the PIP window can be visible in.
-     */
-    public Rect getInsetBounds() {
-        Rect insetBounds = new Rect();
-        DisplayLayout displayLayout = mPipDisplayLayoutState.getDisplayLayout();
-        Rect insets = displayLayout.stableInsets();
-        insetBounds.set(insets.left + mScreenEdgeInsets.x,
-                insets.top + mScreenEdgeInsets.y,
-                displayLayout.width() - insets.right - mScreenEdgeInsets.x,
-                displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
-        return insetBounds;
-    }
-
-    /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
-    public void setOverrideMinSize(@Nullable Size overrideMinSize) {
-        mOverrideMinSize = overrideMinSize;
-    }
-
-    /** Returns the preferred minimal size specified by the activity in PIP. */
-    @Nullable
-    public Size getOverrideMinSize() {
-        if (mOverrideMinSize != null
-                && (mOverrideMinSize.getWidth() < mOverridableMinSize
-                || mOverrideMinSize.getHeight() < mOverridableMinSize)) {
-            return new Size(mOverridableMinSize, mOverridableMinSize);
-        }
-
-        return mOverrideMinSize;
-    }
-
-    /** Returns the minimum edge size of the override minimum size, or 0 if not set. */
-    public int getOverrideMinEdgeSize() {
-        if (mOverrideMinSize == null) return 0;
-        return Math.min(getOverrideMinSize().getWidth(), getOverrideMinSize().getHeight());
-    }
-
-    public int getMinEdgeSize() {
-        return mOverrideMinSize == null ? mDefaultMinSize : getOverrideMinEdgeSize();
-    }
-
-    /**
-     * Returns the size for the max size spec.
-     */
-    public Size getMaxSize(float aspectRatio) {
-        return mSizeSpecSourceImpl.getMaxSize(aspectRatio);
-    }
-
-    /**
-     * Returns the size for the default size spec.
-     */
-    public Size getDefaultSize(float aspectRatio) {
-        return mSizeSpecSourceImpl.getDefaultSize(aspectRatio);
-    }
-
-    /**
-     * Returns the size for the min size spec.
-     */
-    public Size getMinSize(float aspectRatio) {
-        return mSizeSpecSourceImpl.getMinSize(aspectRatio);
-    }
-
-    /**
-     * Returns the adjusted size so that it conforms to the given aspectRatio.
-     *
-     * @param size current size
-     * @param aspectRatio target aspect ratio
-     */
-    public Size getSizeForAspectRatio(@NonNull Size size, float aspectRatio) {
-        if (size.equals(mOverrideMinSize)) {
-            return adjustOverrideMinSizeToAspectRatio(aspectRatio);
-        }
-
-        return mSizeSpecSourceImpl.getSizeForAspectRatio(size, aspectRatio);
-    }
-
-    /**
-     * Returns the adjusted overridden min size if it is set; otherwise, returns null.
-     *
-     * <p>Overridden min size needs to be adjusted in its own way while making sure that the target
-     * aspect ratio is maintained
-     *
-     * @param aspectRatio target aspect ratio
-     */
-    @Nullable
-    @VisibleForTesting
-    Size adjustOverrideMinSizeToAspectRatio(float aspectRatio) {
-        if (mOverrideMinSize == null) {
-            return null;
-        }
-        final Size size = getOverrideMinSize();
-        final float sizeAspectRatio = size.getWidth() / (float) size.getHeight();
-        if (sizeAspectRatio > aspectRatio) {
-            // Size is wider, fix the width and increase the height
-            return new Size(size.getWidth(), (int) (size.getWidth() / aspectRatio));
-        } else {
-            // Size is taller, fix the height and adjust the width.
-            return new Size((int) (size.getHeight() * aspectRatio), size.getHeight());
-        }
-    }
-
-    @VisibleForTesting
-    boolean supportsPipSizeLargeScreen() {
-        // TODO(b/271468706): switch Tv to having a dedicated SizeSpecSource once the SizeSpecSource
-        // can be injected
-        return SystemProperties
-                .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true) && !isTv();
-    }
-
-    private boolean isTv() {
-        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
-    }
-
-    /** Dumps internal state. */
-    public void dump(PrintWriter pw, String prefix) {
-        final String innerPrefix = prefix + "  ";
-        pw.println(prefix + TAG);
-        pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl);
-        pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
-        pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 2c4f76b..ab65c9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -51,12 +51,14 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ShellInit;
 
@@ -84,7 +86,7 @@
     private final Context mContext;
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
     @NonNull private final PipBoundsState mPipBoundsState;
-    @NonNull private final PipSizeSpecHandler mPipSizeSpecHandler;
+    @NonNull private final SizeSpecSource mSizeSpecSource;
     private final PipUiEventLogger mPipUiEventLogger;
     private final PipDismissTargetHandler mPipDismissTargetHandler;
     private final PipTaskOrganizer mPipTaskOrganizer;
@@ -178,7 +180,7 @@
             PhonePipMenuController menuController,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
-            @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+            @NonNull SizeSpecSource sizeSpecSource,
             PipTaskOrganizer pipTaskOrganizer,
             PipMotionHelper pipMotionHelper,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -189,7 +191,7 @@
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipBoundsState = pipBoundsState;
-        mPipSizeSpecHandler = pipSizeSpecHandler;
+        mSizeSpecSource = sizeSpecSource;
         mPipTaskOrganizer = pipTaskOrganizer;
         mMenuController = menuController;
         mPipUiEventLogger = pipUiEventLogger;
@@ -228,7 +230,9 @@
         // TODO(b/181599115): This should really be initializes as part of the pip controller, but
         // until all PIP implementations derive from the controller, just initialize the touch handler
         // if it is needed
-        shellInit.addInitCallback(this::onInit, this);
+        if (!PipUtils.isPip2ExperimentEnabled()) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
     }
 
     /**
@@ -410,7 +414,7 @@
 
         // Calculate the expanded size
         float aspectRatio = (float) normalBounds.width() / normalBounds.height();
-        Size expandedSize = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
+        Size expandedSize = mSizeSpecSource.getDefaultSize(aspectRatio);
         mPipBoundsState.setExpandedBounds(
                 new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight()));
         Rect expandedMovementBounds = new Rect();
@@ -514,10 +518,10 @@
     private void updatePinchResizeSizeConstraints(float aspectRatio) {
         final int minWidth, minHeight, maxWidth, maxHeight;
 
-        minWidth = mPipSizeSpecHandler.getMinSize(aspectRatio).getWidth();
-        minHeight = mPipSizeSpecHandler.getMinSize(aspectRatio).getHeight();
-        maxWidth = mPipSizeSpecHandler.getMaxSize(aspectRatio).getWidth();
-        maxHeight = mPipSizeSpecHandler.getMaxSize(aspectRatio).getHeight();
+        minWidth = mSizeSpecSource.getMinSize(aspectRatio).getWidth();
+        minHeight = mSizeSpecSource.getMinSize(aspectRatio).getHeight();
+        maxWidth = mSizeSpecSource.getMaxSize(aspectRatio).getWidth();
+        maxHeight = mSizeSpecSource.getMaxSize(aspectRatio).getHeight();
 
         mPipResizeGestureHandler.updateMinSize(minWidth, minHeight);
         mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index 825b969..cd58ff4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -36,10 +36,11 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
@@ -62,9 +63,10 @@
     public TvPipBoundsAlgorithm(Context context,
             @NonNull TvPipBoundsState tvPipBoundsState,
             @NonNull PipSnapAlgorithm pipSnapAlgorithm,
-            @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
+            @NonNull PipDisplayLayoutState pipDisplayLayoutState,
+            @NonNull SizeSpecSource sizeSpecSource) {
         super(context, tvPipBoundsState, pipSnapAlgorithm,
-                new PipKeepClearAlgorithmInterface() {}, pipSizeSpecHandler);
+                new PipKeepClearAlgorithmInterface() {}, pipDisplayLayoutState, sizeSpecSource);
         this.mTvPipBoundsState = tvPipBoundsState;
         this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm();
         reloadResources(context);
@@ -291,7 +293,7 @@
                 expandedSize = mTvPipBoundsState.getTvExpandedSize();
             } else {
                 int maxHeight = displayLayout.height()
-                        - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().y)
+                        - (2 * mPipDisplayLayoutState.getScreenEdgeInsets().y)
                         - pipDecorations.top - pipDecorations.bottom;
                 float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
 
@@ -311,7 +313,7 @@
                 expandedSize = mTvPipBoundsState.getTvExpandedSize();
             } else {
                 int maxWidth = displayLayout.width()
-                        - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().x)
+                        - (2 * mPipDisplayLayoutState.getScreenEdgeInsets().x)
                         - pipDecorations.left - pipDecorations.right;
                 float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
                 if (maxWidth > aspectRatioWidth) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index e1737ec..4757efc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -29,10 +29,10 @@
 import android.view.Gravity;
 import android.view.View;
 
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -76,9 +76,9 @@
     private Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
 
     public TvPipBoundsState(@NonNull Context context,
-            @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+            @NonNull SizeSpecSource sizeSpecSource,
             @NonNull PipDisplayLayoutState pipDisplayLayoutState) {
-        super(context, pipSizeSpecHandler, pipDisplayLayoutState);
+        super(context, sizeSpecSource, pipDisplayLayoutState);
         mContext = context;
         updateDefaultGravity();
         mPreviousCollapsedGravity = mDefaultGravity;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 02eeb2a..5e583c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -47,10 +47,10 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
new file mode 100644
index 0000000..8ab85d0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipTransition.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2;
+
+import android.annotation.NonNull;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+
+/** Placeholder, for demonstrate purpose only. */
+public abstract class PipTransition extends PipTransitionController {
+    public PipTransition(
+            @NonNull ShellInit shellInit,
+            @NonNull ShellTaskOrganizer shellTaskOrganizer,
+            @NonNull Transitions transitions,
+            PipBoundsState pipBoundsState,
+            PipMenuController pipMenuController,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipAnimationController pipAnimationController) {
+        super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
+                pipBoundsAlgorithm, pipAnimationController);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md
new file mode 100644
index 0000000..415ea8f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/README.md
@@ -0,0 +1,3 @@
+# PLACEHOLDER
+
+This is meant to be the doc for the pip2 package.
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 2a61445..05e4af3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -35,7 +35,7 @@
     WM_SHELL_RECENTS_TRANSITION(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
             "ShellRecents"),
     WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
-            Consts.TAG_WM_SHELL),
+            "ShellDragAndDrop"),
     WM_SHELL_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_STARTING_WINDOW),
     WM_SHELL_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
@@ -49,12 +49,14 @@
             Consts.TAG_WM_SPLIT_SCREEN),
     WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
-    WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+    WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
             Consts.TAG_WM_SHELL),
     WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
     WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
+    WM_SHELL_BUBBLES(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            "Bubbles"),
     TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 843e5af..a11d952 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -567,15 +567,18 @@
                         && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
                     // Tasks that are always on top (e.g. bubbles), will handle their own transition
                     // as they are on top of everything else. So cancel the merge here.
-                    cancel("task #" + taskInfo.taskId + " is always_on_top");
+                    cancel(false /* toHome */, false /* withScreenshots */,
+                            "task #" + taskInfo.taskId + " is always_on_top");
                     return;
                 }
                 final boolean isRootTask = taskInfo != null
                         && TransitionInfo.isIndependent(change, info);
+                final boolean isRecentsTask = mRecentsTask != null
+                        && mRecentsTask.equals(change.getContainer());
                 hasTaskChange = hasTaskChange || isRootTask;
                 final boolean isLeafTask = leafTaskFilter.test(change);
                 if (TransitionUtil.isOpeningType(change.getMode())) {
-                    if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+                    if (isRecentsTask) {
                         recentsOpening = change;
                     } else if (isRootTask || isLeafTask) {
                         if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
@@ -590,7 +593,7 @@
                         openingTaskIsLeafs.add(isLeafTask ? 1 : 0);
                     }
                 } else if (TransitionUtil.isClosingType(change.getMode())) {
-                    if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
+                    if (isRecentsTask) {
                         foundRecentsClosing = true;
                     } else if (isRootTask || isLeafTask) {
                         if (closingTasks == null) {
@@ -601,7 +604,8 @@
                 } else if (change.getMode() == TRANSIT_CHANGE) {
                     // Finish recents animation if the display is changed, so the default
                     // transition handler can play the animation such as rotation effect.
-                    if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) {
+                    if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)
+                            && info.getType() == TRANSIT_CHANGE) {
                         // This call to cancel will use the screenshots taken preemptively in
                         // handleMidTransitionRequest() prior to the display changing
                         cancel(mWillFinishToHome, true /* withScreenshots */, "display change");
@@ -611,7 +615,7 @@
                     if (!TransitionUtil.isOrderOnly(change) && isLeafTask) {
                         hasChangingApp = true;
                     } else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME
-                            && !mRecentsTask.equals(change.getContainer())) {
+                            && !isRecentsTask ) {
                         // Unless it is a 3p launcher. This means that the 3p launcher was already
                         // visible (eg. the "pausing" task is translucent over the 3p launcher).
                         // Treat it as if we are "re-opening" the 3p launcher.
@@ -767,7 +771,7 @@
                     Slog.e(TAG, "Error sending appeared tasks to recents animation", e);
                 }
             }
-            finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            finishCallback.onTransitionFinished(null /* wct */);
         }
 
         /** For now, just set-up a jump-cut to the new activity. */
@@ -933,7 +937,7 @@
                 }
             }
             cleanUp();
-            finishCB.onTransitionFinished(wct.isEmpty() ? null : wct, null /* wctCB */);
+            finishCB.onTransitionFinished(wct.isEmpty() ? null : wct);
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index c414e70..14304a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -27,6 +27,7 @@
 import android.window.RemoteTransition;
 
 import com.android.wm.shell.splitscreen.ISplitScreenListener;
+import com.android.wm.shell.splitscreen.ISplitSelectListener;
 
 /**
  * Interface that is exposed to remote callers to manipulate the splitscreen feature.
@@ -44,6 +45,16 @@
     oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
 
     /**
+     * Registers a split select listener.
+     */
+    oneway void registerSplitSelectListener(in ISplitSelectListener listener) = 20;
+
+    /**
+     * Unregisters a split select listener.
+     */
+    oneway void unregisterSplitSelectListener(in ISplitSelectListener listener) = 21;
+
+    /**
      * Removes a task from the side stage.
      */
     oneway void removeFromSideStage(int taskId) = 4;
@@ -148,4 +159,4 @@
      */
     RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
 }
-// Last id = 19
\ No newline at end of file
+// Last id = 21
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
new file mode 100644
index 0000000..7171da5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.wm.shell.splitscreen;
+
+import android.app.ActivityManager.RunningTaskInfo;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get split-select callbacks.
+ */
+interface ISplitSelectListener {
+    /**
+     * Called when a task requests to enter split select
+     */
+    boolean onRequestSplitSelect(in RunningTaskInfo taskInfo);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index 89538cb..e52235f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -24,6 +24,9 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+
+import java.util.Optional;
 
 /**
  * Main stage for split-screen mode. When split-screen is active all standard activity types launch
@@ -35,9 +38,10 @@
 
     MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
             StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession, IconProvider iconProvider) {
+            SurfaceSession surfaceSession, IconProvider iconProvider,
+            Optional<WindowDecorViewModel> windowDecorViewModel) {
         super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
-                iconProvider);
+                iconProvider, windowDecorViewModel);
     }
 
     boolean isActive() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
index 8639b36..9903113 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
@@ -25,6 +25,9 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+
+import java.util.Optional;
 
 /**
  * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
@@ -37,9 +40,10 @@
 
     SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
             StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession, IconProvider iconProvider) {
+            SurfaceSession surfaceSession, IconProvider iconProvider,
+            Optional<WindowDecorViewModel> windowDecorViewModel) {
         super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
-                iconProvider);
+                iconProvider, windowDecorViewModel);
     }
 
     boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 2f2bc77..f20fe0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.app.ActivityManager;
 import android.graphics.Rect;
 
 import com.android.wm.shell.common.annotations.ExternalThread;
@@ -63,6 +64,13 @@
         default void onSplitVisibilityChanged(boolean visible) {}
     }
 
+    /** Callback interface for listening to requests to enter split select */
+    interface SplitSelectListener {
+        default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo) {
+            return false;
+        }
+    }
+
     /** Registers listener that gets split screen callback. */
     void registerSplitScreenListener(@NonNull SplitScreenListener listener,
             @NonNull Executor executor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index e7a367f..210bf68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -31,6 +31,7 @@
 import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
 import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
+import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -50,6 +51,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.util.Slog;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
@@ -76,6 +78,7 @@
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -84,6 +87,7 @@
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.draganddrop.DragAndDropPolicy;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -94,12 +98,14 @@
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Optional;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Class manages split-screen multitasking mode and implements the main interface
@@ -171,6 +177,9 @@
     private final TransactionPool mTransactionPool;
     private final IconProvider mIconProvider;
     private final Optional<RecentTasksController> mRecentTasksOptional;
+    private final LaunchAdjacentController mLaunchAdjacentController;
+    private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
+    private final Optional<DesktopTasksController> mDesktopTasksController;
     private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
     private final String[] mAppsSupportMultiInstances;
 
@@ -197,6 +206,9 @@
             TransactionPool transactionPool,
             IconProvider iconProvider,
             Optional<RecentTasksController> recentTasks,
+            LaunchAdjacentController launchAdjacentController,
+            Optional<WindowDecorViewModel> windowDecorViewModel,
+            Optional<DesktopTasksController> desktopTasksController,
             ShellExecutor mainExecutor) {
         mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
@@ -213,6 +225,9 @@
         mTransactionPool = transactionPool;
         mIconProvider = iconProvider;
         mRecentTasksOptional = recentTasks;
+        mLaunchAdjacentController = launchAdjacentController;
+        mWindowDecorViewModel = windowDecorViewModel;
+        mDesktopTasksController = desktopTasksController;
         mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
         // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
         //                    override for this controller from the base module
@@ -242,6 +257,9 @@
             TransactionPool transactionPool,
             IconProvider iconProvider,
             RecentTasksController recentTasks,
+            LaunchAdjacentController launchAdjacentController,
+            WindowDecorViewModel windowDecorViewModel,
+            DesktopTasksController desktopTasksController,
             ShellExecutor mainExecutor,
             StageCoordinator stageCoordinator) {
         mShellCommandHandler = shellCommandHandler;
@@ -259,6 +277,9 @@
         mTransactionPool = transactionPool;
         mIconProvider = iconProvider;
         mRecentTasksOptional = Optional.of(recentTasks);
+        mLaunchAdjacentController = launchAdjacentController;
+        mWindowDecorViewModel = Optional.of(windowDecorViewModel);
+        mDesktopTasksController = Optional.of(desktopTasksController);
         mStageCoordinator = stageCoordinator;
         mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
         shellInit.addInitCallback(this::onInit, this);
@@ -291,13 +312,16 @@
             mStageCoordinator = createStageCoordinator();
         }
         mDragAndDropController.ifPresent(controller -> controller.setSplitScreenController(this));
+        mWindowDecorViewModel.ifPresent(viewModel -> viewModel.setSplitScreenController(this));
+        mDesktopTasksController.ifPresent(controller -> controller.setSplitScreenController(this));
     }
 
     protected StageCoordinator createStageCoordinator() {
         return new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
                 mTaskOrganizer, mDisplayController, mDisplayImeController,
-                mDisplayInsetsController, mTransitions, mTransactionPool,
-                mIconProvider, mMainExecutor, mRecentTasksOptional);
+                mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider,
+                mMainExecutor, mRecentTasksOptional, mLaunchAdjacentController,
+                mWindowDecorViewModel);
     }
 
     @Override
@@ -407,8 +431,9 @@
      * transition.
      */
     public void prepareExitSplitScreen(WindowContainerTransaction wct,
-            @StageType int stageToTop) {
+            @StageType int stageToTop, @ExitReason int reason) {
         mStageCoordinator.prepareExitSplitScreen(stageToTop, wct);
+        mStageCoordinator.clearSplitPairedInRecents(reason);
     }
 
     public void enterSplitScreen(int taskId, boolean leftOrTop) {
@@ -451,6 +476,16 @@
         mStageCoordinator.unregisterSplitScreenListener(listener);
     }
 
+    /** Register a split select listener */
+    public void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+        mStageCoordinator.registerSplitSelectListener(listener);
+    }
+
+    /** Unregister a split select listener */
+    public void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+        mStageCoordinator.unregisterSplitSelectListener(listener);
+    }
+
     public void goToFullscreenFromSplit() {
         mStageCoordinator.goToFullscreenFromSplit();
     }
@@ -468,6 +503,16 @@
         return mStageCoordinator.getActivateSplitPosition(taskInfo);
     }
 
+    /**
+     * Move a task to split select
+     * @param taskInfo the task being moved to split select
+     * @param wct transaction to apply if this is a valid request
+     */
+    public void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+            WindowContainerTransaction wct) {
+        mStageCoordinator.requestEnterSplitSelect(taskInfo, wct);
+    }
+
     public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
         final int[] result = new int[1];
         IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -537,6 +582,8 @@
             } else {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startShortcut",
+                        "app package " + packageName + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
                 return;
@@ -566,6 +613,8 @@
                 taskId = INVALID_TASK_ID;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startShortcutAndTaskWithLegacyTransition",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
@@ -598,12 +647,14 @@
                 taskId = INVALID_TASK_ID;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startShortcutAndTask",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
         }
-        mStageCoordinator.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
-                splitPosition, splitRatio, remoteTransition, instanceId);
+        mStageCoordinator.startShortcutAndTask(shortcutInfo, activityOptions.toBundle(), taskId,
+                options2, splitPosition, splitRatio, remoteTransition, instanceId);
     }
 
     /**
@@ -633,6 +684,8 @@
                 taskId = INVALID_TASK_ID;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startIntentAndTaskWithLegacyTransition",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
@@ -663,6 +716,8 @@
                 taskId = INVALID_TASK_ID;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startIntentAndTask",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
@@ -691,6 +746,8 @@
                 pendingIntent2 = null;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startIntentsWithLegacyTransition",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
@@ -709,24 +766,38 @@
         Intent fillInIntent2 = null;
         final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
         final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
+        final ActivityOptions activityOptions1 = options1 != null
+                ? ActivityOptions.fromBundle(options1) : ActivityOptions.makeBasic();
+        final ActivityOptions activityOptions2 = options2 != null
+                ? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic();
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
             if (supportMultiInstancesSplit(packageName1)) {
                 fillInIntent1 = new Intent();
                 fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                 fillInIntent2 = new Intent();
                 fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+
+                if (shortcutInfo1 != null) {
+                    activityOptions1.setApplyMultipleTaskFlagForShortcut(true);
+                }
+                if (shortcutInfo2 != null) {
+                    activityOptions2.setApplyMultipleTaskFlagForShortcut(true);
+                }
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
             } else {
                 pendingIntent2 = null;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startIntents",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
             }
         }
-        mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, options1,
-                pendingIntent2, fillInIntent2, shortcutInfo2, options2, splitPosition, splitRatio,
-                remoteTransition, instanceId);
+        mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1,
+                activityOptions1.toBundle(), pendingIntent2, fillInIntent2, shortcutInfo2,
+                activityOptions2.toBundle(), splitPosition, splitRatio, remoteTransition,
+                instanceId);
     }
 
     @Override
@@ -766,6 +837,8 @@
             } else {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
+                Log.w(TAG, splitFailureMessage("startIntent",
+                        "app package " + packageName1 + " does not support multi-instance"));
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
                 return;
@@ -1043,6 +1116,8 @@
         private SplitScreenController mController;
         private final SingleInstanceRemoteListener<SplitScreenController,
                 ISplitScreenListener> mListener;
+        private final SingleInstanceRemoteListener<SplitScreenController,
+                ISplitSelectListener> mSelectListener;
         private final SplitScreen.SplitScreenListener mSplitScreenListener =
                 new SplitScreen.SplitScreenListener() {
                     @Override
@@ -1056,11 +1131,25 @@
                     }
                 };
 
+        private final SplitScreen.SplitSelectListener mSplitSelectListener =
+                new SplitScreen.SplitSelectListener() {
+                    @Override
+                    public boolean onRequestEnterSplitSelect(
+                            ActivityManager.RunningTaskInfo taskInfo) {
+                        AtomicBoolean result = new AtomicBoolean(false);
+                        mSelectListener.call(l -> result.set(l.onRequestSplitSelect(taskInfo)));
+                        return result.get();
+                    }
+                };
+
         public ISplitScreenImpl(SplitScreenController controller) {
             mController = controller;
             mListener = new SingleInstanceRemoteListener<>(controller,
                     c -> c.registerSplitScreenListener(mSplitScreenListener),
                     c -> c.unregisterSplitScreenListener(mSplitScreenListener));
+            mSelectListener = new SingleInstanceRemoteListener<>(controller,
+                    c -> c.registerSplitSelectListener(mSplitSelectListener),
+                    c -> c.unregisterSplitSelectListener(mSplitSelectListener));
         }
 
         /**
@@ -1086,6 +1175,18 @@
         }
 
         @Override
+        public void registerSplitSelectListener(ISplitSelectListener listener) {
+            executeRemoteCallWithTaskPermission(mController, "registerSplitSelectListener",
+                    (controller) -> mSelectListener.register(listener));
+        }
+
+        @Override
+        public void unregisterSplitSelectListener(ISplitSelectListener listener) {
+            executeRemoteCallWithTaskPermission(mController, "unregisterSplitSelectListener",
+                    (controller) -> mSelectListener.unregister());
+        }
+
+        @Override
         public void exitSplitScreen(int toTopTaskId) {
             executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
                     (controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index d21f8a4..7dec12a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -43,7 +43,6 @@
 import android.window.TransitionInfo;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransactionCallback;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.TransactionPool;
@@ -109,7 +108,7 @@
             if (pendingTransition.mCanceled) {
                 // The pending transition was canceled, so skip playing animation.
                 startTransaction.apply();
-                onFinish(null /* wct */, null /* wctCB */);
+                onFinish(null /* wct */);
                 return;
             }
 
@@ -211,7 +210,7 @@
             }
         }
         t.apply();
-        onFinish(null /* wct */, null /* wctCB */);
+        onFinish(null /* wct */);
     }
 
     /** Play animation for drag divider dismiss transition. */
@@ -238,7 +237,7 @@
                     mAnimations.remove(va);
                     if (animated) {
                         mTransitions.getMainExecutor().execute(() -> {
-                            onFinish(null /* wct */, null /* wctCB */);
+                            onFinish(null /* wct */);
                         });
                     }
                 });
@@ -250,7 +249,7 @@
             }
         }
         startTransaction.apply();
-        onFinish(null /* wct */, null /* wctCB */);
+        onFinish(null /* wct */);
     }
 
     /** Play animation for resize transition. */
@@ -283,7 +282,7 @@
                     mAnimations.remove(va);
                     if (animated) {
                         mTransitions.getMainExecutor().execute(() -> {
-                            onFinish(null /* wct */, null /* wctCB */);
+                            onFinish(null /* wct */);
                         });
                     }
                 });
@@ -291,7 +290,7 @@
         }
 
         startTransaction.apply();
-        onFinish(null /* wct */, null /* wctCB */);
+        onFinish(null /* wct */);
     }
 
     boolean isPendingTransition(IBinder transition) {
@@ -325,8 +324,10 @@
 
     void startFullscreenTransition(WindowContainerTransaction wct,
             @Nullable RemoteTransition handler) {
-        mTransitions.startTransition(TRANSIT_OPEN, wct,
-                new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler));
+        OneShotRemoteHandler fullscreenHandler =
+                new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler);
+        fullscreenHandler.setTransition(mTransitions
+                .startTransition(TRANSIT_OPEN, wct, fullscreenHandler));
     }
 
 
@@ -391,7 +392,7 @@
         if (mPendingResize != null) {
             mPendingResize.cancel(null);
             mAnimations.clear();
-            onFinish(null /* wct */, null /* wctCB */);
+            onFinish(null /* wct */);
         }
 
         IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
@@ -450,7 +451,7 @@
         }
     }
 
-    void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
+    void onFinish(WindowContainerTransaction wct) {
         if (!mAnimations.isEmpty()) return;
 
         if (wct == null) wct = new WindowContainerTransaction();
@@ -470,7 +471,7 @@
 
         mOnFinish.run();
         if (mFinishCallback != null) {
-            mFinishCallback.onTransitionFinished(wct /* wct */, wctCB /* wctCB */);
+            mFinishCallback.onTransitionFinished(wct /* wct */);
             mFinishCallback = null;
         }
     }
@@ -495,7 +496,7 @@
                 mTransactionPool.release(transaction);
                 mTransitions.getMainExecutor().execute(() -> {
                     mAnimations.remove(va);
-                    onFinish(null /* wct */, null /* wctCB */);
+                    onFinish(null /* wct */);
                 });
             }
         });
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 7699e2f..6970068 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
@@ -28,6 +28,7 @@
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.transitTypeToString;
@@ -41,6 +42,7 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
+import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -81,7 +83,6 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.hardware.devicestate.DeviceStateManager;
 import android.os.Bundle;
@@ -121,6 +122,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ScreenshotUtils;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -138,11 +140,14 @@
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.util.SplitBounds;
 import com.android.wm.shell.util.TransitionUtil;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 
 /**
  * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -182,6 +187,7 @@
     private final ShellTaskOrganizer mTaskOrganizer;
     private final Context mContext;
     private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+    private final Set<SplitScreen.SplitSelectListener> mSelectListeners = new HashSet<>();
     private final DisplayController mDisplayController;
     private final DisplayImeController mDisplayImeController;
     private final DisplayInsetsController mDisplayInsetsController;
@@ -193,6 +199,8 @@
     // if user is opening another task(s).
     private final ArrayList<Integer> mPausingTasks = new ArrayList<>();
     private final Optional<RecentTasksController> mRecentTasks;
+    private final LaunchAdjacentController mLaunchAdjacentController;
+    private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
 
     private final Rect mTempRect1 = new Rect();
     private final Rect mTempRect2 = new Rect();
@@ -213,6 +221,8 @@
     private boolean mIsDropEntering;
     private boolean mIsExiting;
     private boolean mIsRootTranslucent;
+    @VisibleForTesting
+    int mTopStageAfterFoldDismiss;
 
     private DefaultMixedHandler mMixedHandler;
     private final Toast mSplitUnsupportedToast;
@@ -270,7 +280,9 @@
             DisplayInsetsController displayInsetsController, Transitions transitions,
             TransactionPool transactionPool,
             IconProvider iconProvider, ShellExecutor mainExecutor,
-            Optional<RecentTasksController> recentTasks) {
+            Optional<RecentTasksController> recentTasks,
+            LaunchAdjacentController launchAdjacentController,
+            Optional<WindowDecorViewModel> windowDecorViewModel) {
         mContext = context;
         mDisplayId = displayId;
         mSyncQueue = syncQueue;
@@ -278,6 +290,8 @@
         mLogger = new SplitscreenEventLogger();
         mMainExecutor = mainExecutor;
         mRecentTasks = recentTasks;
+        mLaunchAdjacentController = launchAdjacentController;
+        mWindowDecorViewModel = windowDecorViewModel;
 
         taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
 
@@ -288,7 +302,8 @@
                 mMainStageListener,
                 mSyncQueue,
                 mSurfaceSession,
-                iconProvider);
+                iconProvider,
+                mWindowDecorViewModel);
         mSideStage = new SideStage(
                 mContext,
                 mTaskOrganizer,
@@ -296,7 +311,8 @@
                 mSideStageListener,
                 mSyncQueue,
                 mSurfaceSession,
-                iconProvider);
+                iconProvider,
+                mWindowDecorViewModel);
         mDisplayController = displayController;
         mDisplayImeController = displayImeController;
         mDisplayInsetsController = displayInsetsController;
@@ -323,7 +339,9 @@
             DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
             Transitions transitions, TransactionPool transactionPool,
             ShellExecutor mainExecutor,
-            Optional<RecentTasksController> recentTasks) {
+            Optional<RecentTasksController> recentTasks,
+            LaunchAdjacentController launchAdjacentController,
+            Optional<WindowDecorViewModel> windowDecorViewModel) {
         mContext = context;
         mDisplayId = displayId;
         mSyncQueue = syncQueue;
@@ -340,6 +358,8 @@
         mLogger = new SplitscreenEventLogger();
         mMainExecutor = mainExecutor;
         mRecentTasks = recentTasks;
+        mLaunchAdjacentController = launchAdjacentController;
+        mWindowDecorViewModel = windowDecorViewModel;
         mDisplayController.addDisplayWindowListener(this);
         transitions.addHandler(this);
         mSplitUnsupportedToast = Toast.makeText(mContext,
@@ -445,6 +465,15 @@
         return mLogger;
     }
 
+    void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+            WindowContainerTransaction wct) {
+        boolean enteredSplitSelect = false;
+        for (SplitScreen.SplitSelectListener listener : mSelectListeners) {
+            enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo);
+        }
+        if (enteredSplitSelect) mTaskOrganizer.applyTransaction(wct);
+    }
+
     void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
             Bundle options, UserHandle user) {
         final boolean isEnteringSplit = !isSplitActive();
@@ -459,6 +488,8 @@
                 if (isEnteringSplit && mSideStage.getChildCount() == 0) {
                     mMainExecutor.execute(() -> exitSplitScreen(
                             null /* childrenToTop */, EXIT_REASON_UNKNOWN));
+                    Log.w(TAG, splitFailureMessage("startShortcut",
+                            "side stage was not populated"));
                     mSplitUnsupportedToast.show();
                 }
 
@@ -546,6 +577,8 @@
                 if (isEnteringSplit && mSideStage.getChildCount() == 0) {
                     mMainExecutor.execute(() -> exitSplitScreen(
                             null /* childrenToTop */, EXIT_REASON_UNKNOWN));
+                    Log.w(TAG, splitFailureMessage("startIntentLegacy",
+                            "side stage was not populated"));
                     mSplitUnsupportedToast.show();
                 }
 
@@ -718,12 +751,12 @@
             mMainStage.activate(wct, false /* reparent */);
         }
 
+        setSideStagePosition(splitPosition, wct);
         mSplitLayout.setDivideRatio(splitRatio);
         updateWindowBounds(mSplitLayout, wct);
         wct.reorder(mRootTaskInfo.token, true);
         setRootForceTranslucent(false, wct);
 
-        setSideStagePosition(splitPosition, wct);
         options1 = options1 != null ? options1 : new Bundle();
         addActivityOptions(options1, mSideStage);
         if (shortcutInfo1 != null) {
@@ -1076,6 +1109,8 @@
             mMainExecutor.execute(() ->
                     exitSplitScreen(mMainStage.getChildCount() == 0
                             ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+            Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled",
+                    "main or side stage was not populated."));
             mSplitUnsupportedToast.show();
         } else {
             mSyncQueue.queue(evictWct);
@@ -1095,6 +1130,8 @@
         if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
             mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
                     ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+            Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished",
+                    "main or side stage was not populated"));
             mSplitUnsupportedToast.show();
             return;
         }
@@ -1279,20 +1316,30 @@
         final boolean mainStageVisible = mMainStage.mRootTaskInfo.isVisible;
         final boolean oneStageVisible =
                 mMainStage.mRootTaskInfo.isVisible != mSideStage.mRootTaskInfo.isVisible;
-        if (oneStageVisible) {
+        if (oneStageVisible && !ENABLE_SHELL_TRANSITIONS) {
             // Dismiss split because there's show-when-locked activity showing on top of keyguard.
             // Also make sure the task contains show-when-locked activity remains on top after split
             // dismissed.
-            if (!ENABLE_SHELL_TRANSITIONS) {
-                final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
-                exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
-            } else {
-                final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+            final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
+            exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+        }
+
+        // Dismiss split if the flag record any side of stages.
+        if (mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+            if (ENABLE_SHELL_TRANSITIONS) {
+                // Need manually clear here due to this transition might be aborted due to keyguard
+                // on top and lead to no visible change.
+                clearSplitPairedInRecents(EXIT_REASON_DEVICE_FOLDED);
                 final WindowContainerTransaction wct = new WindowContainerTransaction();
-                prepareExitSplitScreen(dismissTop, wct);
-                mSplitTransitions.startDismissTransition(wct, this, dismissTop,
-                        EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+                prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
+                mSplitTransitions.startDismissTransition(wct, this,
+                        mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
+            } else {
+                exitSplitScreen(
+                        mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
+                        EXIT_REASON_DEVICE_FOLDED);
             }
+            mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
         }
     }
 
@@ -1330,15 +1377,8 @@
         if (!mMainStage.isActive() || mIsExiting) return;
 
         onSplitScreenExit();
+        clearSplitPairedInRecents(exitReason);
 
-        mRecentTasks.ifPresent(recentTasks -> {
-            // Notify recents if we are exiting in a way that breaks the pair, and disable further
-            // updates to splits in the recents until we enter split again
-            if (shouldBreakPairedTaskInRecents(exitReason) && mShouldUpdateRecents) {
-                recentTasks.removeSplitPair(mMainStage.getTopVisibleChildTaskId());
-                recentTasks.removeSplitPair(mSideStage.getTopVisibleChildTaskId());
-            }
-        });
         mShouldUpdateRecents = false;
         mIsDividerRemoteAnimating = false;
         mSplitRequest = null;
@@ -1465,6 +1505,17 @@
         }
     }
 
+    void clearSplitPairedInRecents(@ExitReason int exitReason) {
+        if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) return;
+
+        mRecentTasks.ifPresent(recentTasks -> {
+            // Notify recents if we are exiting in a way that breaks the pair, and disable further
+            // updates to splits in the recents until we enter split again
+            mMainStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
+            mSideStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
+        });
+    }
+
     /**
      * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
      * an existing WindowContainerTransaction (rather than applying immediately). This is intended
@@ -1547,6 +1598,8 @@
             // split bounds.
             wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token,
                     SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
+            mSplitLayout.getInvisibleBounds(mTempRect1);
+            mSplitLayout.setTaskBounds(wct, mSideStage.mRootTaskInfo, mTempRect1);
         }
         wct.reorder(mRootTaskInfo.token, true);
         setRootForceTranslucent(false, wct);
@@ -1616,6 +1669,14 @@
         mListeners.remove(listener);
     }
 
+    void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+        mSelectListeners.add(listener);
+    }
+
+    void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+        mSelectListeners.remove(listener);
+    }
+
     void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
         listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
         listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
@@ -1733,12 +1794,11 @@
         if (mRootTaskInfo == null || mRootTaskInfo.taskId != taskInfo.taskId) {
             throw new IllegalArgumentException(this + "\n Unknown task info changed: " + taskInfo);
         }
-
+        mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo));
         mRootTaskInfo = taskInfo;
         if (mSplitLayout != null
                 && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
-                && mMainStage.isActive()
-                && !ENABLE_SHELL_TRANSITIONS) {
+                && mMainStage.isActive()) {
             // Clear the divider remote animating flag as the divider will be re-rendered to apply
             // the new rotation config.
             mIsDividerRemoteAnimating = false;
@@ -1781,7 +1841,6 @@
         wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
         // Make the stages adjacent to each other so they occlude what's behind them.
         wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
-        wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
         setRootForceTranslucent(true, wct);
         mSplitLayout.getInvisibleBounds(mTempRect1);
         wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
@@ -1789,6 +1848,7 @@
         mSyncQueue.runInSync(t -> {
             t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top);
         });
+        mLaunchAdjacentController.setLaunchAdjacentRoot(mSideStage.mRootTaskInfo.token);
     }
 
     /** Callback when split roots have child task appeared under it, this is a little different from
@@ -1818,9 +1878,7 @@
 
     private void onRootTaskVanished() {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
-        if (mRootTaskInfo != null) {
-            wct.clearLaunchAdjacentFlagRoot(mRootTaskInfo.token);
-        }
+        mLaunchAdjacentController.clearLaunchAdjacentRoot();
         applyExitSplitScreen(null /* childrenToTop */, wct, EXIT_REASON_ROOT_TASK_VANISHED);
         mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, mSplitLayout);
     }
@@ -2178,21 +2236,11 @@
         mDisplayController.addDisplayChangingController(this::onDisplayChange);
     }
 
-    @Override
-    public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
-        if (displayId != DEFAULT_DISPLAY) {
-            return;
-        }
-        if (mSplitLayout != null && mSplitLayout.isDensityChanged(newConfig.densityDpi)
-                && mMainStage.isActive()
-                && mSplitLayout.updateConfiguration(newConfig)
-                && ENABLE_SHELL_TRANSITIONS) {
-            mSplitLayout.update(null /* t */);
-            onLayoutSizeChanged(mSplitLayout);
-        }
-    }
-
-    void updateSurfaces(SurfaceControl.Transaction transaction) {
+    /**
+     * Update surfaces of the split screen layout based on the current state
+     * @param transaction to write the updates to
+     */
+    public void updateSurfaces(SurfaceControl.Transaction transaction) {
         updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false);
         mSplitLayout.update(transaction);
     }
@@ -2211,26 +2259,18 @@
 
     @VisibleForTesting
     void onFoldedStateChanged(boolean folded) {
-        int topStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+        mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
         if (!folded) return;
 
-        if (!mMainStage.isActive()) return;
+        if (!isSplitActive() || !isSplitScreenVisible()) return;
 
+        // To avoid split dismiss when user fold the device and unfold to use later, we only
+        // record the flag here and try to dismiss on wakeUp callback to ensure split dismiss
+        // when user interact on phone folded.
         if (mMainStage.isFocused()) {
-            topStageAfterFoldDismiss = STAGE_TYPE_MAIN;
+            mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
         } else if (mSideStage.isFocused()) {
-            topStageAfterFoldDismiss = STAGE_TYPE_SIDE;
-        }
-
-        if (ENABLE_SHELL_TRANSITIONS) {
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
-            prepareExitSplitScreen(topStageAfterFoldDismiss, wct);
-            mSplitTransitions.startDismissTransition(wct, this,
-                    topStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
-        } else {
-            exitSplitScreen(
-                    topStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
-                    EXIT_REASON_DEVICE_FOLDED);
+            mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
         }
     }
 
@@ -2364,6 +2404,15 @@
                     // so appends operations to exit split.
                     prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
                 }
+            } else if (type == TRANSIT_KEYGUARD_OCCLUDE && triggerTask.topActivity != null
+                    && isSplitScreenVisible()) {
+                // Split include show when lock activity case, check the top activity under which
+                // stage and move it to the top.
+                int top = triggerTask.topActivity.equals(mMainStage.mRootTaskInfo.topActivity)
+                        ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+                prepareExitSplitScreen(top, out);
+                mSplitTransitions.setDismissTransition(transition, top,
+                        EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
             }
 
             // When split in the background, it should be only opening/dismissing transition and
@@ -2519,6 +2568,9 @@
                 // so don't handle it.
                 Log.e(TAG, "Somehow removed the last task in a stage outside of a proper "
                         + "transition.");
+                // This new transition would be merged to current one so we need to clear
+                // tile manually here.
+                clearSplitPairedInRecents(EXIT_REASON_APP_FINISHED);
                 final WindowContainerTransaction wct = new WindowContainerTransaction();
                 final int dismissTop = (dismissStages.size() == 1
                         && getStageType(dismissStages.valueAt(0)) == STAGE_TYPE_MAIN)
@@ -2688,19 +2740,27 @@
                 == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
             // Open to side should only be used when split already active and foregorund.
             if (mainChild == null && sideChild == null) {
-                Log.w(TAG, "Launched a task in split, but didn't receive any task in transition.");
+                Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
+                        "Launched a task in split, but didn't receive any task in transition."));
                 // This should happen when the target app is already on front, so just cancel.
                 mSplitTransitions.mPendingEnter.cancel(null);
                 return true;
             }
         } else {
             if (mainChild == null || sideChild == null) {
-                Log.w(TAG, "Launched 2 tasks in split, but didn't receive"
-                        + " 2 tasks in transition. Possibly one of them failed to launch");
                 final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
                         (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
                 mSplitTransitions.mPendingEnter.cancel(
                         (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
+                Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
+                        "launched 2 tasks in split, but didn't receive "
+                        + "2 tasks in transition. Possibly one of them failed to launch"));
+                if (mRecentTasks.isPresent() && mainChild != null) {
+                    mRecentTasks.get().removeSplitPair(mainChild.getTaskInfo().taskId);
+                }
+                if (mRecentTasks.isPresent() && sideChild != null) {
+                    mRecentTasks.get().removeSplitPair(sideChild.getTaskInfo().taskId);
+                }
                 mSplitUnsupportedToast.show();
                 return true;
             }
@@ -3158,7 +3218,7 @@
         }
 
         @Override
-        public void onNoLongerSupportMultiWindow() {
+        public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) {
             if (mMainStage.isActive()) {
                 final boolean isMainStage = mMainStageListener == this;
                 if (!ENABLE_SHELL_TRANSITIONS) {
@@ -3173,6 +3233,9 @@
                 prepareExitSplitScreen(stageType, wct);
                 mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
                         EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+                Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow",
+                        "app package " + taskInfo.baseActivity.getPackageName()
+                        + " does not support splitscreen, or is a controlled activity type"));
                 mSplitUnsupportedToast.show();
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index a01eddb..af7bf36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -51,8 +51,11 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import java.io.PrintWriter;
+import java.util.Optional;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -79,7 +82,7 @@
 
         void onRootTaskVanished();
 
-        void onNoLongerSupportMultiWindow();
+        void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo);
     }
 
     private final Context mContext;
@@ -87,6 +90,7 @@
     private final SurfaceSession mSurfaceSession;
     private final SyncTransactionQueue mSyncQueue;
     private final IconProvider mIconProvider;
+    private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
 
     protected ActivityManager.RunningTaskInfo mRootTaskInfo;
     protected SurfaceControl mRootLeash;
@@ -98,12 +102,14 @@
 
     StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
             StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession, IconProvider iconProvider) {
+            SurfaceSession surfaceSession, IconProvider iconProvider,
+            Optional<WindowDecorViewModel> windowDecorViewModel) {
         mContext = context;
         mCallbacks = callbacks;
         mSyncQueue = syncQueue;
         mSurfaceSession = surfaceSession;
         mIconProvider = iconProvider;
+        mWindowDecorViewModel = windowDecorViewModel;
         taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
     }
 
@@ -202,6 +208,7 @@
     @Override
     @CallSuper
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+        mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo));
         if (mRootTaskInfo.taskId == taskInfo.taskId) {
             // Inflates split decor view only when the root task is visible.
             if (!ENABLE_SHELL_TRANSITIONS && mRootTaskInfo.isVisible != taskInfo.isVisible) {
@@ -220,7 +227,7 @@
                     taskInfo.getWindowingMode())) {
                 // Leave split screen if the task no longer supports multi window or have
                 // uncontrolled task.
-                mCallbacks.onNoLongerSupportMultiWindow();
+                mCallbacks.onNoLongerSupportMultiWindow(taskInfo);
                 return;
             }
             mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
@@ -341,6 +348,13 @@
         wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
     }
 
+    void doForAllChildTasks(Consumer<Integer> consumer) {
+        for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
+            final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
+            consumer.accept(taskInfo.taskId);
+        }
+    }
+
     /** Collects all the current child tasks and prepares transaction to evict them to display. */
     void evictAllChildren(WindowContainerTransaction wct) {
         for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index 27d520d..c101425 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -27,6 +27,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
@@ -58,6 +59,7 @@
     private final TransactionPool mTransactionPool;
     private final IconProvider mIconProvider;
     private final Optional<RecentTasksController> mRecentTasksOptional;
+    private final LaunchAdjacentController mLaunchAdjacentController;
 
     private final Handler mMainHandler;
     private final SystemWindows mSystemWindows;
@@ -77,13 +79,15 @@
             TransactionPool transactionPool,
             IconProvider iconProvider,
             Optional<RecentTasksController> recentTasks,
+            LaunchAdjacentController launchAdjacentController,
             ShellExecutor mainExecutor,
             Handler mainHandler,
             SystemWindows systemWindows) {
         super(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer,
                 syncQueue, rootTDAOrganizer, displayController, displayImeController,
                 displayInsetsController, dragAndDropController, transitions, transactionPool,
-                iconProvider, recentTasks, mainExecutor);
+                iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
+                Optional.empty(), mainExecutor);
 
         mTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
@@ -96,6 +100,7 @@
         mTransactionPool = transactionPool;
         mIconProvider = iconProvider;
         mRecentTasksOptional = recentTasks;
+        mLaunchAdjacentController = launchAdjacentController;
 
         mMainHandler = mainHandler;
         mSystemWindows = systemWindows;
@@ -111,7 +116,7 @@
                 mTaskOrganizer, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mTransitions, mTransactionPool,
                 mIconProvider, mMainExecutor, mMainHandler,
-                mRecentTasksOptional, mSystemWindows);
+                mRecentTasksOptional, mLaunchAdjacentController, mSystemWindows);
     }
 
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
index 4d563fb..7947691 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
@@ -24,6 +24,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
@@ -51,10 +52,11 @@
             IconProvider iconProvider, ShellExecutor mainExecutor,
             Handler mainHandler,
             Optional<RecentTasksController> recentTasks,
+            LaunchAdjacentController launchAdjacentController,
             SystemWindows systemWindows) {
         super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController,
                 displayInsetsController, transitions, transactionPool, iconProvider,
-                mainExecutor, recentTasks);
+                mainExecutor, recentTasks, launchAdjacentController, Optional.empty());
 
         mTvSplitMenuController = new TvSplitMenuController(context, this,
                 systemWindows, mainHandler);
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 dc91a11..29be343 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
@@ -385,8 +385,8 @@
     private static int estimateWindowBGColor(Drawable themeBGDrawable) {
         final DrawableColorTester themeBGTester = new DrawableColorTester(
                 themeBGDrawable, DrawableColorTester.TRANSLUCENT_FILTER /* filterType */);
-        if (themeBGTester.passFilterRatio() != 1) {
-            // the window background is translucent, unable to draw
+        if (themeBGTester.passFilterRatio() < 0.5f) {
+            // more than half pixels of the window background is translucent, unable to draw
             Slog.w(TAG, "Window background is translucent, fill background with black color");
             return getSystemBGColor();
         } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
index 5f54f58..56c0d0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -38,8 +38,6 @@
     public static final String KEY_EXTRA_SHELL_RECENT_TASKS = "extra_shell_recent_tasks";
     // See IBackAnimation.aidl
     public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
-    // See IFloatingTasks.aidl
-    public static final String KEY_EXTRA_SHELL_FLOATING_TASKS = "extra_shell_floating_tasks";
     // See IDesktopMode.aidl
     public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
     // See IDragAndDrop.aidl
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index 4faa929..0d77a2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.view.SurfaceControl;
@@ -69,8 +70,10 @@
     private final Rect mTmpRect = new Rect();
     private final Rect mTmpRootRect = new Rect();
     private final int[] mTmpLocation = new int[2];
+    private final Rect mBoundsOnScreen = new Rect();
     private final TaskViewTaskController mTaskViewTaskController;
     private Region mObscuredTouchRegion;
+    private Insets mCaptionInsets;
 
     public TaskView(Context context, TaskViewTaskController taskViewTaskController) {
         super(context, null, 0, 0, true /* disableBackgroundLayer */);
@@ -169,6 +172,25 @@
     }
 
     /**
+     * Sets a region of the task to inset to allow for a caption bar. Currently only top insets
+     * are supported.
+     * <p>
+     * This region will be factored in as an area of taskview that is not touchable activity
+     * content (i.e. you don't need to additionally set {@link #setObscuredTouchRect(Rect)} for
+     * the caption area).
+     *
+     * @param captionInsets the insets to apply to task view.
+     */
+    public void setCaptionInsets(Insets captionInsets) {
+        mCaptionInsets = captionInsets;
+        if (captionInsets == null) {
+            // If captions are null we can set them now; otherwise they'll get set in
+            // onComputeInternalInsets.
+            mTaskViewTaskController.setCaptionInsets(null);
+        }
+    }
+
+    /**
      * Call when view position or size has changed. Do not call when animating.
      */
     public void onLocationChanged() {
@@ -230,6 +252,15 @@
         getLocationInWindow(mTmpLocation);
         mTmpRect.set(mTmpLocation[0], mTmpLocation[1],
                 mTmpLocation[0] + getWidth(), mTmpLocation[1] + getHeight());
+        if (mCaptionInsets != null) {
+            mTmpRect.inset(mCaptionInsets);
+            getBoundsOnScreen(mBoundsOnScreen);
+            mTaskViewTaskController.setCaptionInsets(new Rect(
+                    mBoundsOnScreen.left,
+                    mBoundsOnScreen.top,
+                    mBoundsOnScreen.right + getWidth(),
+                    mBoundsOnScreen.top + mCaptionInsets.top));
+        }
         inoutInfo.touchableRegion.op(mTmpRect, Region.Op.DIFFERENCE);
 
         if (mObscuredTouchRegion != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 163cf50..adae21b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -33,6 +33,7 @@
 import android.util.CloseGuard;
 import android.util.Slog;
 import android.view.SurfaceControl;
+import android.view.WindowInsets;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -82,6 +83,10 @@
     private TaskView.Listener mListener;
     private Executor mListenerExecutor;
 
+    /** Used to inset the activity content to allow space for a caption bar. */
+    private final Binder mCaptionInsetsOwner = new Binder();
+    private Rect mCaptionInsets;
+
     public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
             TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
         mContext = context;
@@ -89,9 +94,11 @@
         mShellExecutor = organizer.getExecutor();
         mSyncQueue = syncQueue;
         mTaskViewTransitions = taskViewTransitions;
-        if (mTaskViewTransitions != null) {
-            mTaskViewTransitions.addTaskView(this);
-        }
+        mShellExecutor.execute(() -> {
+            if (mTaskViewTransitions != null) {
+                mTaskViewTransitions.addTaskView(this);
+            }
+        });
         mGuard.open("release");
     }
 
@@ -220,10 +227,10 @@
     }
 
     private void performRelease() {
-        if (mTaskViewTransitions != null) {
-            mTaskViewTransitions.removeTaskView(this);
-        }
         mShellExecutor.execute(() -> {
+            if (mTaskViewTransitions != null) {
+                mTaskViewTransitions.removeTaskView(this);
+            }
             mTaskOrganizer.removeListener(this);
             resetTaskInfo();
         });
@@ -312,18 +319,12 @@
         // we know about -- so leave clean-up here even if shell transitions are enabled.
         if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
 
-        if (mListener != null) {
-            final int taskId = taskInfo.taskId;
-            mListenerExecutor.execute(() -> {
-                mListener.onTaskRemovalStarted(taskId);
-            });
-        }
-        mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
+        final SurfaceControl taskLeash = mTaskLeash;
+        handleAndNotifyTaskRemoval(mTaskInfo);
 
         // Unparent the task when this surface is destroyed
-        mTransaction.reparent(mTaskLeash, null).apply();
+        mTransaction.reparent(taskLeash, null).apply();
         resetTaskInfo();
-        mTaskViewBase.onTaskVanished(taskInfo);
     }
 
     @Override
@@ -411,9 +412,12 @@
         if (mTaskToken == null) {
             return;
         }
-        // Sync Transactions can't operate simultaneously with shell transition collection.
+
         if (isUsingShellTransitions()) {
-            mTaskViewTransitions.setTaskBounds(this, boundsOnScreen);
+            mShellExecutor.execute(() -> {
+                // Sync Transactions can't operate simultaneously with shell transition collection.
+                mTaskViewTransitions.setTaskBounds(this, boundsOnScreen);
+            });
             return;
         }
 
@@ -431,9 +435,37 @@
             Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
             return;
         }
+        mShellExecutor.execute(() -> {
+            WindowContainerTransaction wct = new WindowContainerTransaction();
+            wct.removeTask(mTaskToken);
+            mTaskViewTransitions.closeTaskView(wct, this);
+        });
+    }
+
+    /**
+     * Sets a region of the task to inset to allow for a caption bar.
+     *
+     * @param captionInsets the rect for the insets in screen coordinates.
+     */
+    void setCaptionInsets(Rect captionInsets) {
+        if (mCaptionInsets != null && mCaptionInsets.equals(captionInsets)) {
+            return;
+        }
+        mCaptionInsets = captionInsets;
+        applyCaptionInsetsIfNeeded();
+    }
+
+    void applyCaptionInsetsIfNeeded() {
+        if (mTaskToken == null) return;
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.removeTask(mTaskToken);
-        mTaskViewTransitions.closeTaskView(wct, this);
+        if (mCaptionInsets != null) {
+            wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
+                    WindowInsets.Type.captionBar(), mCaptionInsets);
+        } else {
+            wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
+                    WindowInsets.Type.captionBar());
+        }
+        mTaskOrganizer.applyTransaction(wct);
     }
 
     /** Should be called when the client surface is destroyed. */
@@ -467,6 +499,20 @@
         }
     }
 
+    /** Notifies listeners of a task being removed and stops intercepting back presses on it. */
+    private void handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo) {
+        if (taskInfo != null) {
+            if (mListener != null) {
+                final int taskId = taskInfo.taskId;
+                mListenerExecutor.execute(() -> {
+                    mListener.onTaskRemovalStarted(taskId);
+                });
+            }
+            mTaskViewBase.onTaskVanished(taskInfo);
+            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, false);
+        }
+    }
+
     /** Returns the task info for the task in the TaskView. */
     @Nullable
     public ActivityManager.RunningTaskInfo getTaskInfo() {
@@ -492,18 +538,12 @@
      */
     void cleanUpPendingTask() {
         if (mPendingInfo != null) {
-            if (mListener != null) {
-                final int taskId = mPendingInfo.taskId;
-                mListenerExecutor.execute(() -> {
-                    mListener.onTaskRemovalStarted(taskId);
-                });
-            }
-            mTaskViewBase.onTaskVanished(mPendingInfo);
-            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mPendingInfo.token, false);
+            final ActivityManager.RunningTaskInfo pendingInfo = mPendingInfo;
+            handleAndNotifyTaskRemoval(pendingInfo);
 
             // Make sure the task is removed
             WindowContainerTransaction wct = new WindowContainerTransaction();
-            wct.removeTask(mPendingInfo.token);
+            wct.removeTask(pendingInfo.token);
             mTaskViewTransitions.closeTaskView(wct, this);
         }
         resetTaskInfo();
@@ -528,16 +568,7 @@
      * is used instead.
      */
     void prepareCloseAnimation() {
-        if (mTaskToken != null) {
-            if (mListener != null) {
-                final int taskId = mTaskInfo.taskId;
-                mListenerExecutor.execute(() -> {
-                    mListener.onTaskRemovalStarted(taskId);
-                });
-            }
-            mTaskViewBase.onTaskVanished(mTaskInfo);
-            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
-        }
+        handleAndNotifyTaskRemoval(mTaskInfo);
         resetTaskInfo();
     }
 
@@ -564,6 +595,7 @@
             mTaskViewTransitions.updateBoundsState(this, boundsOnScreen);
             mTaskViewTransitions.updateVisibilityState(this, true /* visible */);
             wct.setBounds(mTaskToken, boundsOnScreen);
+            applyCaptionInsetsIfNeeded();
         } else {
             // The surface has already been destroyed before the task has appeared,
             // so go ahead and hide the task entirely
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 5baf2e3..cefbb17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -202,15 +202,10 @@
         if (taskView == null) return null;
         // Opening types should all be initiated by shell
         if (!TransitionUtil.isClosingType(request.getType())) return null;
-        PendingTransition pending = findPendingCloseTransition(taskView);
-        if (pending == null) {
-            pending = new PendingTransition(request.getType(), null, taskView, null /* cookie */);
-        }
-        if (pending.mClaimed != null) {
-            throw new IllegalStateException("Task is closing in 2 collecting transitions?"
-                    + " This state doesn't make sense");
-        }
+        PendingTransition pending = new PendingTransition(request.getType(), null,
+                taskView, null /* cookie */);
         pending.mClaimed = transition;
+        mPending.add(pending);
         return new WindowContainerTransaction();
     }
 
@@ -409,7 +404,7 @@
         }
         // No animation, just show it immediately.
         startTransaction.apply();
-        finishCallback.onTransitionFinished(wct, null /* wctCB */);
+        finishCallback.onTransitionFinished(wct);
         startNextTransition();
         return true;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index d0a361a..986560b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -40,13 +40,14 @@
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransactionCallback;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.desktopmode.DesktopModeController;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
 import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -70,6 +71,8 @@
     private RecentsTransitionHandler mRecentsHandler;
     private StageCoordinator mSplitHandler;
     private final KeyguardTransitionHandler mKeyguardHandler;
+    private DesktopModeController mDesktopModeController;
+    private DesktopTasksController mDesktopTasksController;
     private UnfoldTransitionHandler mUnfoldHandler;
 
     private static class MixedTransition {
@@ -87,8 +90,11 @@
         /** Keyguard exit/occlude/unocclude transition. */
         static final int TYPE_KEYGUARD = 5;
 
+        /** Recents Transition while in desktop mode. */
+        static final int TYPE_RECENTS_DURING_DESKTOP = 6;
+
         /** Fuld/Unfold transition. */
-        static final int TYPE_UNFOLD = 6;
+        static final int TYPE_UNFOLD = 7;
 
         /** The default animation for this mixed transition. */
         static final int ANIM_TYPE_DEFAULT = 0;
@@ -117,14 +123,7 @@
             mTransition = transition;
         }
 
-        void joinFinishArgs(WindowContainerTransaction wct,
-                WindowContainerTransactionCallback wctCB) {
-            if (wctCB != null) {
-                // Technically can probably support 1, but don't want to encourage CB usage since
-                // it creates instabliity, so just throw.
-                throw new IllegalArgumentException("Can't mix transitions that require finish"
-                        + " sync callback");
-            }
+        void joinFinishArgs(WindowContainerTransaction wct) {
             if (wct != null) {
                 if (mFinishWCT == null) {
                     mFinishWCT = wct;
@@ -139,17 +138,20 @@
 
     public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player,
             Optional<SplitScreenController> splitScreenControllerOptional,
-            Optional<PipTouchHandler> pipTouchHandlerOptional,
+            @Nullable PipTransitionController pipTransitionController,
             Optional<RecentsTransitionHandler> recentsHandlerOptional,
             KeyguardTransitionHandler keyguardHandler,
+            Optional<DesktopModeController> desktopModeControllerOptional,
+            Optional<DesktopTasksController> desktopTasksControllerOptional,
             Optional<UnfoldTransitionHandler> unfoldHandler) {
         mPlayer = player;
         mKeyguardHandler = keyguardHandler;
-        if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent()
+        if (Transitions.ENABLE_SHELL_TRANSITIONS
+                && pipTransitionController != null
                 && splitScreenControllerOptional.isPresent()) {
             // Add after dependencies because it is higher priority
             shellInit.addInitCallback(() -> {
-                mPipHandler = pipTouchHandlerOptional.get().getTransitionHandler();
+                mPipHandler = pipTransitionController;
                 mSplitHandler = splitScreenControllerOptional.get().getTransitionHandler();
                 mPlayer.addHandler(this);
                 if (mSplitHandler != null) {
@@ -159,6 +161,8 @@
                 if (mRecentsHandler != null) {
                     mRecentsHandler.addMixer(this);
                 }
+                mDesktopModeController = desktopModeControllerOptional.orElse(null);
+                mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
                 mUnfoldHandler = unfoldHandler.orElse(null);
             }, this);
         }
@@ -239,7 +243,8 @@
 
     @Override
     public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
-        if (mRecentsHandler != null && mSplitHandler.isSplitScreenVisible()) {
+        if (mRecentsHandler != null && (mSplitHandler.isSplitScreenVisible()
+                || DesktopModeStatus.isActive(mPlayer.getContext()))) {
             return this;
         }
         return null;
@@ -254,6 +259,13 @@
                     MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
             mixed.mLeftoversHandler = mRecentsHandler;
             mActiveTransitions.add(mixed);
+        } else if (DesktopModeStatus.isActive(mPlayer.getContext())) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+                    + "desktop mode is active, so treat it as Mixed.");
+            final MixedTransition mixed = new MixedTransition(
+                    MixedTransition.TYPE_RECENTS_DURING_DESKTOP, transition);
+            mixed.mLeftoversHandler = mRecentsHandler;
+            mActiveTransitions.add(mixed);
         } else {
             throw new IllegalStateException("Accepted a recents transition but don't know how to"
                     + " handle it");
@@ -340,6 +352,9 @@
         } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
             return animateKeyguard(mixed, info, startTransaction, finishTransaction,
                     finishCallback);
+        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+            return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction,
+                    finishCallback);
         } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
             return animateUnfold(mixed, info, startTransaction, finishTransaction, finishCallback);
         } else {
@@ -366,12 +381,12 @@
                 info.getChanges().remove(i);
             }
         }
-        Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+        Transitions.TransitionFinishCallback finishCB = (wct) -> {
             --mixed.mInFlightSubAnimations;
-            mixed.joinFinishArgs(wct, wctCB);
+            mixed.joinFinishArgs(wct);
             if (mixed.mInFlightSubAnimations > 0) return;
             mActiveTransitions.remove(mixed);
-            finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
+            finishCallback.onTransitionFinished(mixed.mFinishWCT);
         };
         if (pipChange == null) {
             if (mixed.mLeftoversHandler != null) {
@@ -438,15 +453,15 @@
             return false;
         }
         final boolean isGoingHome = homeIsOpening;
-        Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+        Transitions.TransitionFinishCallback finishCB = (wct) -> {
             --mixed.mInFlightSubAnimations;
-            mixed.joinFinishArgs(wct, wctCB);
+            mixed.joinFinishArgs(wct);
             if (mixed.mInFlightSubAnimations > 0) return;
             mActiveTransitions.remove(mixed);
             if (isGoingHome) {
                 mSplitHandler.onTransitionAnimationComplete();
             }
-            finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
+            finishCallback.onTransitionFinished(mixed.mFinishWCT);
         };
         if (isGoingHome || mSplitHandler.getSplitItemPosition(pipChange.getLastParent())
                 != SPLIT_POSITION_UNDEFINED) {
@@ -563,12 +578,12 @@
         // We need to split the transition into 2 parts: the split part and the display part.
         mixed.mInFlightSubAnimations = 2;
 
-        Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+        Transitions.TransitionFinishCallback finishCB = (wct) -> {
             --mixed.mInFlightSubAnimations;
-            mixed.joinFinishArgs(wct, wctCB);
+            mixed.joinFinishArgs(wct);
             if (mixed.mInFlightSubAnimations > 0) return;
             mActiveTransitions.remove(mixed);
-            finishCallback.onTransitionFinished(mixed.mFinishWCT, null /* wctCB */);
+            finishCallback.onTransitionFinished(mixed.mFinishWCT);
         };
 
         // Dispatch the display change. This will most-likely be taken by the default handler.
@@ -591,7 +606,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         // Split-screen is only interested in the recents transition finishing (and merging), so
         // just wrap finish and start recents animation directly.
-        Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+        Transitions.TransitionFinishCallback finishCB = (wct) -> {
             mixed.mInFlightSubAnimations = 0;
             mActiveTransitions.remove(mixed);
             // If pair-to-pair switching, the post-recents clean-up isn't needed.
@@ -603,7 +618,7 @@
                 mSplitHandler.onRecentsPairToPairAnimationFinish(wct);
             }
             mSplitHandler.onTransitionAnimationComplete();
-            finishCallback.onTransitionFinished(wct, wctCB);
+            finishCallback.onTransitionFinished(wct);
         };
         mixed.mInFlightSubAnimations = 1;
         mSplitHandler.onRecentsInSplitAnimationStart(info);
@@ -621,11 +636,11 @@
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        final Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+        final Transitions.TransitionFinishCallback finishCB = (wct) -> {
             mixed.mInFlightSubAnimations--;
             if (mixed.mInFlightSubAnimations == 0) {
                 mActiveTransitions.remove(mixed);
-                finishCallback.onTransitionFinished(wct, wctCB);
+                finishCallback.onTransitionFinished(wct);
             }
         };
         mixed.mInFlightSubAnimations++;
@@ -641,22 +656,49 @@
         return true;
     }
 
+    private boolean animateRecentsDuringDesktop(@NonNull final MixedTransition mixed,
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        boolean consumed = mRecentsHandler.startAnimation(
+                mixed.mTransition, info, startTransaction, finishTransaction, finishCallback);
+        if (!consumed) {
+            return false;
+        }
+        //Sync desktop mode state (proto 1)
+        if (mDesktopModeController != null) {
+            mDesktopModeController.syncSurfaceState(info, finishTransaction);
+            return true;
+        }
+        //Sync desktop mode state (proto 2)
+        if (mDesktopTasksController != null) {
+            mDesktopTasksController.syncSurfaceState(info, finishTransaction);
+            return true;
+        }
+
+        return false;
+    }
+
     private boolean animateUnfold(@NonNull final MixedTransition mixed,
             @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        final Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+        final Transitions.TransitionFinishCallback finishCB = (wct) -> {
             mixed.mInFlightSubAnimations--;
             if (mixed.mInFlightSubAnimations > 0) return;
             mActiveTransitions.remove(mixed);
-            finishCallback.onTransitionFinished(wct, wctCB);
+            finishCallback.onTransitionFinished(wct);
         };
         mixed.mInFlightSubAnimations = 1;
         // Sync pip state.
         if (mPipHandler != null) {
             mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction);
         }
+        if (mSplitHandler != null && mSplitHandler.isSplitActive()) {
+            mSplitHandler.updateSurfaces(startTransaction);
+        }
         return mUnfoldHandler.startAnimation(
                 mixed.mTransition, info, startTransaction, finishTransaction, finishCB);
     }
@@ -727,6 +769,9 @@
                         finishCallback);
             } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
                 mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+            } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+                mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+                        finishCallback);
             } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
                 mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
             } else {
@@ -754,6 +799,8 @@
             mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
         } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
             mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
+        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+            mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
         } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
             mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index e52fd00..7df658e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -300,7 +300,7 @@
         // immediately finishes since there is no animation for screen-wake.
         if (info.getType() == WindowManager.TRANSIT_WAKE && !info.isKeyguardGoingAway()) {
             startTransaction.apply();
-            finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            finishCallback.onTransitionFinished(null /* wct */);
             return true;
         }
 
@@ -309,7 +309,7 @@
                 || (info.getFlags() & WindowManager.TRANSIT_FLAG_INVISIBLE) != 0) {
             startTransaction.apply();
             finishTransaction.apply();
-            finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            finishCallback.onTransitionFinished(null /* wct */);
             return true;
         }
 
@@ -323,7 +323,7 @@
         final Runnable onAnimFinish = () -> {
             if (!animations.isEmpty()) return;
             mAnimations.remove(transition);
-            finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            finishCallback.onTransitionFinished(null /* wct */);
         };
 
         final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks =
@@ -407,7 +407,7 @@
                             change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
                 }
                 // Rotation change of independent non display window container.
-                if (change.getParent() == null
+                if (change.getParent() == null && !change.hasFlags(FLAG_IS_DISPLAY)
                         && change.getStartRotation() != change.getEndRotation()) {
                     startRotationAnimation(startTransaction, change, info,
                             ROTATION_ANIMATION_ROTATE, animations, onAnimFinish);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4e3d220..fab2dd2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -68,7 +68,7 @@
         final IBinder.DeathRecipient remoteDied = () -> {
             Log.e(Transitions.TAG, "Remote transition died, finishing");
             mMainExecutor.execute(
-                    () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
+                    () -> finishCallback.onTransitionFinished(null /* wct */));
         };
         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
             @Override
@@ -81,7 +81,7 @@
                     finishTransaction.merge(sct);
                 }
                 mMainExecutor.execute(() -> {
-                    finishCallback.onTransitionFinished(wct, null /* wctCB */);
+                    finishCallback.onTransitionFinished(wct);
                 });
             }
         };
@@ -104,7 +104,7 @@
             if (mRemote.asBinder() != null) {
                 mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
             }
-            finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            finishCallback.onTransitionFinished(null /* wct */);
         }
         return true;
     }
@@ -122,8 +122,7 @@
                 // remote applied the transaction, but applying twice will break surfaceflinger
                 // so just assume the worst-case and clear the local transaction.
                 t.clear();
-                mMainExecutor.execute(
-                        () -> finishCallback.onTransitionFinished(wct, null /* wctCB */));
+                mMainExecutor.execute(() -> finishCallback.onTransitionFinished(wct));
             }
         };
         try {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index a242c72..bbf67a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -133,7 +133,7 @@
                 }
                 mMainExecutor.execute(() -> {
                     mRequestedRemotes.remove(transition);
-                    finishCallback.onTransitionFinished(wct, null /* wctCB */);
+                    finishCallback.onTransitionFinished(wct);
                 });
             }
         };
@@ -153,8 +153,7 @@
             Log.e(Transitions.TAG, "Error running remote transition.", e);
             unhandleDeath(remote.asBinder(), finishCallback);
             mRequestedRemotes.remove(transition);
-            mMainExecutor.execute(
-                    () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
+            mMainExecutor.execute(() -> finishCallback.onTransitionFinished(null /* wct */));
         }
         return true;
     }
@@ -186,9 +185,12 @@
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         final RemoteTransition remoteTransition = mRequestedRemotes.get(mergeTarget);
-        final IRemoteTransition remote = remoteTransition.getRemoteTransition();
+        if (remoteTransition == null) return;
+
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "   Merge into remote: %s",
                 remoteTransition);
+
+        final IRemoteTransition remote = remoteTransition.getRemoteTransition();
         if (remote == null) return;
 
         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
@@ -207,7 +209,7 @@
                                 + "that the mergeTarget's RemoteTransition impl erroneously "
                                 + "accepted/ran the merge request after finishing the mergeTarget");
                     }
-                    finishCallback.onTransitionFinished(wct, null /* wctCB */);
+                    finishCallback.onTransitionFinished(wct);
                 });
             }
         };
@@ -313,8 +315,7 @@
                     }
                 }
                 for (int i = mPendingFinishCallbacks.size() - 1; i >= 0; --i) {
-                    mPendingFinishCallbacks.get(i).onTransitionFinished(
-                            null /* wct */, null /* wctCB */);
+                    mPendingFinishCallbacks.get(i).onTransitionFinished(null /* wct */);
                 }
                 mPendingFinishCallbacks.clear();
             });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
index d279595..87c438a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
@@ -43,7 +43,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         mSleepTransitions.remove(transition);
         startTransaction.apply();
-        finishCallback.onTransitionFinished(null, null);
+        finishCallback.onTransitionFinished(null);
         return true;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 2327d86..b4d0a31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -27,6 +27,7 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.fixScale;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
@@ -61,7 +62,6 @@
 import android.window.TransitionMetrics;
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransactionCallback;
 import android.window.WindowOrganizer;
 
 import androidx.annotation.BinderThread;
@@ -148,19 +148,28 @@
     /** Transition type for maximize to freeform transition. */
     public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9;
 
-    /** Transition type to freeform in desktop mode. */
-    public static final int TRANSIT_ENTER_FREEFORM = WindowManager.TRANSIT_FIRST_CUSTOM + 10;
+    /** Transition type for starting the move to desktop mode. */
+    public static final int TRANSIT_START_DRAG_TO_DESKTOP_MODE =
+            WindowManager.TRANSIT_FIRST_CUSTOM + 10;
 
-    /** Transition type to freeform in desktop mode. */
-    public static final int TRANSIT_ENTER_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 11;
+    /** Transition type for finalizing the move to desktop mode. */
+    public static final int TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE =
+            WindowManager.TRANSIT_FIRST_CUSTOM + 11;
 
     /** Transition type to fullscreen from desktop mode. */
     public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
 
     /** Transition type to animate back to fullscreen when drag to freeform is cancelled. */
-    public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE =
+    public static final int TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE =
             WindowManager.TRANSIT_FIRST_CUSTOM + 13;
 
+    /** Transition type to animate the toggle resize between the max and default desktop sizes. */
+    public static final int TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE =
+            WindowManager.TRANSIT_FIRST_CUSTOM + 14;
+
+    /** Transition to animate task to desktop. */
+    public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15;
+
     private final WindowOrganizer mOrganizer;
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
@@ -724,11 +733,15 @@
         final int changeSize = info.getChanges().size();
         boolean taskChange = false;
         boolean transferStartingWindow = false;
+        int noAnimationBehindStartingWindow = 0;
         boolean allOccluded = changeSize > 0;
         for (int i = changeSize - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
             taskChange |= change.getTaskInfo() != null;
             transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
+            if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)) {
+                noAnimationBehindStartingWindow++;
+            }
             if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
                 allOccluded = false;
             }
@@ -736,9 +749,11 @@
         // There does not need animation when:
         // A. Transfer starting window. Apply transfer starting window directly if there is no other
         // task change. Since this is an activity->activity situation, we can detect it by selecting
-        // transitions with only 2 changes where neither are tasks and one is a starting-window
-        // recipient.
-        if (!taskChange && transferStartingWindow && changeSize == 2
+        // transitions with only 2 changes where
+        // 1. neither are tasks, and
+        // 2. one is a starting-window recipient, or all change is behind starting window.
+        if (!taskChange && (transferStartingWindow || noAnimationBehindStartingWindow == changeSize)
+                && changeSize == 2
                 // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all
                 // changes are underneath another change.
                 || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
@@ -813,7 +828,7 @@
                     ready.mStartT.apply();
                 }
                 // finish now since there's nothing to animate. Calls back into processReadyQueue
-                onFinish(ready, null, null);
+                onFinish(ready, null);
                 return;
             }
             playTransition(ready);
@@ -833,7 +848,7 @@
                 + " in case they can be merged", ready, playing);
         mTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
         playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
-                playing.mToken, (wct, cb) -> onMerged(playing, ready));
+                playing.mToken, (wct) -> onMerged(playing, ready));
     }
 
     private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
@@ -883,7 +898,7 @@
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
                     active.mHandler);
             boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo,
-                    active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active, wct, cb));
+                    active.mStartT, active.mFinishT, (wct) -> onFinish(active, wct));
             if (consumed) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
                 mTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
@@ -892,7 +907,7 @@
         }
         // Otherwise give every other handler a chance
         active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT,
-                active.mFinishT, (wct, cb) -> onFinish(active, wct, cb), active.mHandler);
+                active.mFinishT, (wct) -> onFinish(active, wct), active.mHandler);
     }
 
     /**
@@ -969,8 +984,7 @@
     }
 
     private void onFinish(ActiveTransition active,
-            @Nullable WindowContainerTransaction wct,
-            @Nullable WindowContainerTransactionCallback wctCB) {
+            @Nullable WindowContainerTransaction wct) {
         final Track track = mTracks.get(active.getTrack());
         if (track.mActiveTransition != active) {
             Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
@@ -1019,11 +1033,11 @@
         // Now perform all the finish callbacks (starting with the playing one and then all the
         // transitions merged into it).
         releaseSurfaces(active.mInfo);
-        mOrganizer.finishTransition(active.mToken, wct, wctCB);
+        mOrganizer.finishTransition(active.mToken, wct);
         if (active.mMerged != null) {
             for (int iM = 0; iM < active.mMerged.size(); ++iM) {
                 ActiveTransition merged = active.mMerged.get(iM);
-                mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
+                mOrganizer.finishTransition(merged.mToken, null /* wct */);
                 releaseSurfaces(merged.mInfo);
             }
             active.mMerged.clear();
@@ -1162,7 +1176,7 @@
                     forceFinish.mHandler.onTransitionConsumed(
                             forceFinish.mToken, true /* aborted */, null /* finishTransaction */);
                 }
-                onFinish(forceFinish, null, null);
+                onFinish(forceFinish, null);
             }
         }
         if (track.isIdle() || mReadyDuringSync.isEmpty()) {
@@ -1182,7 +1196,7 @@
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge sync %s"
                     + " into %s via a SLEEP proxy", nextSync, playing);
             playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT,
-                    playing.mToken, (wct, cb) -> {});
+                    playing.mToken, (wct) -> {});
             // it's possible to complete immediately. If that happens, just repeat the signal
             // loop until we either finish everything or start playing an animation that isn't
             // finishing immediately.
@@ -1210,11 +1224,8 @@
          * The transition must not touch the surfaces after this has been called.
          *
          * @param wct A WindowContainerTransaction to run along with the transition clean-up.
-         * @param wctCB A sync callback that will be run when the transition clean-up is done and
-         *              wct has been applied.
          */
-        void onTransitionFinished(@Nullable WindowContainerTransaction wct,
-                @Nullable WindowContainerTransactionCallback wctCB);
+        void onTransitionFinished(@Nullable WindowContainerTransaction wct);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index f148412..2eb6e71 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -169,7 +169,7 @@
             animator.stop();
         }
 
-        mFinishCallback.onTransitionFinished(null, null);
+        mFinishCallback.onTransitionFinished(null);
         mFinishCallback = null;
         mTransition = null;
     }
@@ -193,7 +193,7 @@
             }
             // Apply changes happening during the unfold animation immediately
             t.apply();
-            finishCallback.onTransitionFinished(null, null);
+            finishCallback.onTransitionFinished(null);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
index f81fc6f..6bba0d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
@@ -110,7 +110,7 @@
         for (int i = state.sourceSize() - 1; i >= 0; i--) {
             final InsetsSource source = state.sourceAt(i);
             if (source.getType() == WindowInsets.Type.navigationBars()
-                    && source.insetsRoundedCornerFrame()) {
+                    && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
                 return source;
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index a4cf149..bb5d546 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -50,11 +50,11 @@
 import com.android.wm.shell.unfold.UnfoldAnimationController;
 import com.android.wm.shell.unfold.UnfoldBackgroundController;
 
+import dagger.Lazy;
+
 import java.util.Optional;
 import java.util.concurrent.Executor;
 
-import dagger.Lazy;
-
 /**
  * This helper class contains logic that calculates scaling and cropping parameters
  * for the folding/unfolding animation. As an input it receives TaskInfo objects and
@@ -149,7 +149,7 @@
         for (int i = state.sourceSize() - 1; i >= 0; i--) {
             final InsetsSource source = state.sourceAt(i);
             if (source.getType() == WindowInsets.Type.navigationBars()
-                    && source.insetsRoundedCornerFrame()) {
+                    && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
                 return source;
             }
         }
@@ -270,7 +270,6 @@
     @Override
     public void prepareStartTransaction(Transaction transaction) {
         mUnfoldBackgroundController.ensureBackground(transaction);
-        mSplitScreenController.get().get().updateSplitScreenSurfaces(transaction);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 39fb793..cf16920 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -33,11 +33,14 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.Nullable;
+
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
 
 /**
@@ -89,6 +92,9 @@
     }
 
     @Override
+    public void setSplitScreenController(SplitScreenController splitScreenController) {}
+
+    @Override
     public boolean onTaskOpening(
             RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
@@ -187,7 +193,7 @@
 
         final DragPositioningCallback dragPositioningCallback =
                 new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
-                        null /* disallowedAreaForEndBounds */);
+                        0 /* disallowedAreaForEndBoundsHeight */);
         final CaptionTouchEventListener touchEventListener =
                 new CaptionTouchEventListener(taskInfo, dragPositioningCallback);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
@@ -254,7 +260,7 @@
          * @return {@code true} if a drag is happening; or {@code false} if it is not
          */
         @Override
-        public boolean handleMotionEvent(MotionEvent e) {
+        public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
             final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
             if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
                 return false;
@@ -268,7 +274,10 @@
                     return false;
                 }
                 case MotionEvent.ACTION_MOVE: {
-                    int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+                    if (e.findPointerIndex(mDragPointerId) == -1) {
+                        mDragPointerId = e.getPointerId(0);
+                    }
+                    final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     mDragPositioningCallback.onDragPositioningMove(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     mIsDragging = true;
@@ -276,7 +285,10 @@
                 }
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
-                    int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+                    if (e.findPointerIndex(mDragPointerId) == -1) {
+                        mDragPointerId = e.getPointerId(0);
+                    }
+                    final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     mDragPositioningCallback.onDragPositioningEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     final boolean wasDragging = mIsDragging;
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 116af70..ce81910 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
@@ -113,7 +113,7 @@
         mRelayoutParams.reset();
         mRelayoutParams.mRunningTaskInfo = taskInfo;
         mRelayoutParams.mLayoutResId = R.layout.caption_window_decor;
-        mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
+        mRelayoutParams.mCaptionHeightId = getCaptionHeightId();
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
         mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
 
@@ -143,8 +143,11 @@
                     mHandler,
                     mChoreographer,
                     mDisplay.getDisplayId(),
+                    0 /* taskCornerRadius */,
                     mDecorationContainerSurface,
-                    mDragPositioningCallback);
+                    mDragPositioningCallback,
+                    mSurfaceControlBuilderSupplier,
+                    mSurfaceControlTransactionSupplier);
         }
 
         final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
@@ -221,4 +224,9 @@
         closeDragResizeListener();
         super.close();
     }
+
+    @Override
+    int getCaptionHeightId() {
+        return R.dimen.freeform_decor_caption_height;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 9fd57d7..2be7a49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -19,12 +19,14 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.DRAG_FREEFORM_SCALE;
 import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
 import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
+import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -42,6 +44,7 @@
 import android.os.Looper;
 import android.util.SparseArray;
 import android.view.Choreographer;
+import android.view.GestureDetector;
 import android.view.InputChannel;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
@@ -53,6 +56,7 @@
 import android.view.WindowManager;
 import android.window.TransitionInfo;
 import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -68,6 +72,9 @@
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.TaskCornersListener;
 
@@ -85,6 +92,7 @@
     private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
     private final ActivityTaskManager mActivityTaskManager;
     private final ShellTaskOrganizer mTaskOrganizer;
+    private final ShellController mShellController;
     private final Context mContext;
     private final Handler mMainHandler;
     private final Choreographer mMainChoreographer;
@@ -106,37 +114,41 @@
     private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
     private final Transitions mTransitions;
 
-    private Optional<SplitScreenController> mSplitScreenController;
+    private SplitScreenController mSplitScreenController;
 
-    private ValueAnimator mDragToDesktopValueAnimator;
+    private MoveToDesktopAnimator mMoveToDesktopAnimator;
     private final Rect mDragToDesktopAnimationStartBounds = new Rect();
-    private boolean mDragToDesktopAnimationStarted;
+    private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener;
 
     public DesktopModeWindowDecorViewModel(
             Context context,
             Handler mainHandler,
             Choreographer mainChoreographer,
+            ShellInit shellInit,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
+            ShellController shellController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopModeController> desktopModeController,
-            Optional<DesktopTasksController> desktopTasksController,
-            Optional<SplitScreenController> splitScreenController) {
+            Optional<DesktopTasksController> desktopTasksController
+    ) {
         this(
                 context,
                 mainHandler,
                 mainChoreographer,
+                shellInit,
                 taskOrganizer,
                 displayController,
+                shellController,
                 syncQueue,
                 transitions,
                 desktopModeController,
                 desktopTasksController,
-                splitScreenController,
                 new DesktopModeWindowDecoration.Factory(),
                 new InputMonitorFactory(),
-                SurfaceControl.Transaction::new);
+                SurfaceControl.Transaction::new,
+                new DesktopModeKeyguardChangeListener());
     }
 
     @VisibleForTesting
@@ -144,23 +156,25 @@
             Context context,
             Handler mainHandler,
             Choreographer mainChoreographer,
+            ShellInit shellInit,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
+            ShellController shellController,
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopModeController> desktopModeController,
             Optional<DesktopTasksController> desktopTasksController,
-            Optional<SplitScreenController> splitScreenController,
             DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
             InputMonitorFactory inputMonitorFactory,
-            Supplier<SurfaceControl.Transaction> transactionFactory) {
+            Supplier<SurfaceControl.Transaction> transactionFactory,
+            DesktopModeKeyguardChangeListener desktopModeKeyguardChangeListener) {
         mContext = context;
         mMainHandler = mainHandler;
         mMainChoreographer = mainChoreographer;
         mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
         mTaskOrganizer = taskOrganizer;
+        mShellController = shellController;
         mDisplayController = displayController;
-        mSplitScreenController = splitScreenController;
         mSyncQueue = syncQueue;
         mTransitions = transitions;
         mDesktopModeController = desktopModeController;
@@ -169,6 +183,13 @@
         mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
         mInputMonitorFactory = inputMonitorFactory;
         mTransactionFactory = transactionFactory;
+        mDesktopModeKeyguardChangeListener = desktopModeKeyguardChangeListener;
+
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    private void onInit() {
+        mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
     }
 
     @Override
@@ -177,6 +198,11 @@
     }
 
     @Override
+    public void setSplitScreenController(SplitScreenController splitScreenController) {
+        mSplitScreenController = splitScreenController;
+    }
+
+    @Override
     public boolean onTaskOpening(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
@@ -193,7 +219,11 @@
             @NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change) {
         if (change.getMode() == WindowManager.TRANSIT_CHANGE
-                && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE)) {
+                && (info.getType() == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
+                || info.getType() == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
+                || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
+                || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
+                || info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) {
             mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
                     .addTransitionPausingRelayout(transition);
         }
@@ -225,7 +255,6 @@
             removeTaskFromEventReceiver(oldTaskInfo.displayId);
             incrementEventReceiverTasks(taskInfo.displayId);
         }
-
         decoration.relayout(taskInfo);
     }
 
@@ -236,7 +265,6 @@
             SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT) {
         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
-
         if (!shouldShowWindowDecor(taskInfo)) {
             if (decoration != null) {
                 destroyWindowDecoration(taskInfo);
@@ -275,15 +303,17 @@
         }
     }
 
-    private class DesktopModeTouchEventListener implements
-            View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
+    private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
+            implements View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
 
         private final int mTaskId;
         private final WindowContainerToken mTaskToken;
         private final DragPositioningCallback mDragPositioningCallback;
         private final DragDetector mDragDetector;
+        private final GestureDetector mGestureDetector;
 
         private boolean mIsDragging;
+        private boolean mShouldClick;
         private int mDragPointerId = -1;
 
         private DesktopModeTouchEventListener(
@@ -293,6 +323,7 @@
             mTaskToken = taskInfo.token;
             mDragPositioningCallback = dragPositioningCallback;
             mDragDetector = new DragDetector(this);
+            mGestureDetector = new GestureDetector(mContext, this);
         }
 
         @Override
@@ -301,14 +332,14 @@
             final int id = v.getId();
             if (id == R.id.close_window || id == R.id.close_button) {
                 mTaskOperations.closeTask(mTaskToken);
-                if (mSplitScreenController.isPresent()
-                        && mSplitScreenController.get().isSplitScreenVisible()) {
-                    int remainingTaskPosition = mTaskId == mSplitScreenController.get()
+                if (mSplitScreenController != null
+                        && mSplitScreenController.isSplitScreenVisible()) {
+                    int remainingTaskPosition = mTaskId == mSplitScreenController
                             .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId
                             ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
-                    ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController.get()
+                    ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController
                             .getTaskInfo(remainingTaskPosition);
-                    mSplitScreenController.get().moveTaskToFullscreen(remainingTask.taskId);
+                    mSplitScreenController.moveTaskToFullscreen(remainingTask.taskId);
                 }
             } else if (id == R.id.back_button) {
                 mTaskOperations.injectBackKey();
@@ -321,12 +352,24 @@
                 }
             } else if (id == R.id.desktop_button) {
                 mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
-                mDesktopTasksController.ifPresent(c -> c.moveToDesktop(mTaskId));
+                if (mDesktopTasksController.isPresent()) {
+                    final WindowContainerTransaction wct = new WindowContainerTransaction();
+                    // App sometimes draws before the insets from WindowDecoration#relayout have
+                    // been added, so they must be added here
+                    mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
+                    decoration.incrementRelayoutBlock();
+                    mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
+                }
                 decoration.closeHandleMenu();
             } else if (id == R.id.fullscreen_button) {
                 mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
                 mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
                 decoration.closeHandleMenu();
+            } else if (id == R.id.split_screen_button) {
+                decoration.closeHandleMenu();
+                mDesktopTasksController.ifPresent(c -> {
+                    c.requestSplit(decoration.mTaskInfo);
+                });
             } else if (id == R.id.collapse_menu_button) {
                 decoration.closeHandleMenu();
             } else if (id == R.id.select_button) {
@@ -336,6 +379,11 @@
                     mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId));
                     decoration.closeHandleMenu();
                 }
+            } else if (id == R.id.maximize_window) {
+                final RunningTaskInfo taskInfo = decoration.mTaskInfo;
+                mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(
+                        taskInfo, decoration));
+                decoration.closeHandleMenu();
             }
         }
 
@@ -347,7 +395,7 @@
                 return false;
             }
             moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
-            return mDragDetector.onMotionEvent(e);
+            return mDragDetector.onMotionEvent(v, e);
         }
 
         private void moveTaskToFront(RunningTaskInfo taskInfo) {
@@ -362,7 +410,7 @@
          * @return {@code true} if the motion event is handled.
          */
         @Override
-        public boolean handleMotionEvent(MotionEvent e) {
+        public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
             final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
             if (DesktopModeStatus.isProto2Enabled()
                     && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
@@ -373,6 +421,9 @@
                     == WINDOWING_MODE_FULLSCREEN) {
                 return false;
             }
+            if (mGestureDetector.onTouchEvent(e)) {
+                return true;
+            }
             switch (e.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN: {
                     mDragPointerId = e.getPointerId(0);
@@ -380,21 +431,38 @@
                             0 /* ctrlType */, e.getRawX(0),
                             e.getRawY(0));
                     mIsDragging = false;
-                    return false;
+                    mShouldClick = true;
+                    return true;
                 }
                 case MotionEvent.ACTION_MOVE: {
                     final DesktopModeWindowDecoration decoration =
                             mWindowDecorByTaskId.get(mTaskId);
+                    if (e.findPointerIndex(mDragPointerId) == -1) {
+                        mDragPointerId = e.getPointerId(0);
+                    }
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
-                    mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
-                            decoration.mTaskSurface, e.getRawY(dragPointerIdx)));
-                    mDragPositioningCallback.onDragPositioningMove(
+                    final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningMove(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+                    mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
+                            decoration.mTaskSurface, newTaskBounds.top));
                     mIsDragging = true;
+                    mShouldClick = false;
                     return true;
                 }
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
+                    final boolean wasDragging = mIsDragging;
+                    if (!wasDragging) {
+                        if (mShouldClick && v != null) {
+                            v.performClick();
+                            mShouldClick = false;
+                            return true;
+                        }
+                        return false;
+                    }
+                    if (e.findPointerIndex(mDragPointerId) == -1) {
+                        mDragPointerId = e.getPointerId(0);
+                    }
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     // Position of the task is calculated by subtracting the raw location of the
                     // motion event (the location of the motion relative to the display) by the
@@ -402,17 +470,25 @@
                     final Point position = new Point(
                             (int) (e.getRawX(dragPointerIdx) - e.getX(dragPointerIdx)),
                             (int) (e.getRawY(dragPointerIdx) - e.getY(dragPointerIdx)));
-                    mDragPositioningCallback.onDragPositioningEnd(
+                    final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
-                            position));
-                    final boolean wasDragging = mIsDragging;
+                            position, newTaskBounds.top, mWindowDecorByTaskId.get(mTaskId)));
                     mIsDragging = false;
-                    return wasDragging;
+                    return true;
                 }
             }
             return true;
         }
+
+        @Override
+        public boolean onDoubleTap(@NonNull MotionEvent e) {
+            final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+            mDesktopTasksController.ifPresent(c -> {
+                c.toggleDesktopTaskSize(taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId));
+            });
+            return true;
+        }
     }
 
     // InputEventReceiver to listen for touch input outside of caption bounds
@@ -540,9 +616,11 @@
                             relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
                     boolean dragFromStatusBarAllowed = false;
                     if (DesktopModeStatus.isProto2Enabled()) {
-                        // In proto2 any full screen task can be dragged to freeform
-                        dragFromStatusBarAllowed = relevantDecor.mTaskInfo.getWindowingMode()
-                                == WINDOWING_MODE_FULLSCREEN;
+                        // In proto2 any full screen or multi-window task can be dragged to
+                        // freeform.
+                        final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
+                        dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN
+                                || windowingMode == WINDOWING_MODE_MULTI_WINDOW;
                     }
 
                     if (dragFromStatusBarAllowed && relevantDecor.checkTouchEventInHandle(ev)) {
@@ -553,7 +631,7 @@
             }
             case MotionEvent.ACTION_UP: {
                 if (relevantDecor == null) {
-                    mDragToDesktopAnimationStarted = false;
+                    mMoveToDesktopAnimator = null;
                     mTransitionDragActive = false;
                     return;
                 }
@@ -567,14 +645,14 @@
                         } else if (DesktopModeStatus.isProto1Enabled()) {
                             mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                         }
-                        mDragToDesktopAnimationStarted = false;
+                        mMoveToDesktopAnimator = null;
                         return;
-                    } else if (mDragToDesktopAnimationStarted) {
-                        Point position = new Point((int) ev.getX(), (int) ev.getY());
+                    } else if (mMoveToDesktopAnimator != null) {
+                        relevantDecor.incrementRelayoutBlock();
                         mDesktopTasksController.ifPresent(
-                                c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
-                                        position));
-                        mDragToDesktopAnimationStarted = false;
+                                c -> c.cancelMoveToDesktop(relevantDecor.mTaskInfo,
+                                        mMoveToDesktopAnimator));
+                        mMoveToDesktopAnimator = null;
                         return;
                     }
                 }
@@ -594,21 +672,19 @@
                     final int statusBarHeight = getStatusBarHeight(
                             relevantDecor.mTaskInfo.displayId);
                     if (ev.getY() > statusBarHeight) {
-                        if (!mDragToDesktopAnimationStarted) {
-                            mDragToDesktopAnimationStarted = true;
+                        if (mMoveToDesktopAnimator == null) {
+                            mMoveToDesktopAnimator = new MoveToDesktopAnimator(
+                                    mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo,
+                                    relevantDecor.mTaskSurface);
                             mDesktopTasksController.ifPresent(
-                                    c -> c.moveToFreeform(relevantDecor.mTaskInfo,
-                                            mDragToDesktopAnimationStartBounds));
-                            startAnimation(relevantDecor);
+                                    c -> c.startMoveToDesktop(relevantDecor.mTaskInfo,
+                                            mDragToDesktopAnimationStartBounds,
+                                            mMoveToDesktopAnimator));
+                            mMoveToDesktopAnimator.startAnimation();
                         }
                     }
-                    if (mDragToDesktopAnimationStarted) {
-                        Transaction t = mTransactionFactory.get();
-                        float width = (float) mDragToDesktopValueAnimator.getAnimatedValue()
-                                * mDragToDesktopAnimationStartBounds.width();
-                        float x = ev.getX() - (width / 2);
-                        t.setPosition(relevantDecor.mTaskSurface, x, ev.getY());
-                        t.apply();
+                    if (mMoveToDesktopAnimator != null) {
+                        mMoveToDesktopAnimator.updatePosition(ev);
                     }
                 }
                 break;
@@ -616,7 +692,7 @@
 
             case MotionEvent.ACTION_CANCEL: {
                 mTransitionDragActive = false;
-                mDragToDesktopAnimationStarted = false;
+                mMoveToDesktopAnimator = null;
             }
         }
     }
@@ -683,24 +759,9 @@
         animator.start();
     }
 
-    private void startAnimation(@NonNull DesktopModeWindowDecoration focusedDecor) {
-        mDragToDesktopValueAnimator = ValueAnimator.ofFloat(1f, DRAG_FREEFORM_SCALE);
-        mDragToDesktopValueAnimator.setDuration(FREEFORM_ANIMATION_DURATION);
-        final Transaction t = mTransactionFactory.get();
-        mDragToDesktopValueAnimator.addUpdateListener(animation -> {
-            final float animatorValue = (float) animation.getAnimatedValue();
-            SurfaceControl sc = focusedDecor.mTaskSurface;
-            t.setScale(sc, animatorValue, animatorValue);
-            t.apply();
-        });
-
-        mDragToDesktopValueAnimator.start();
-    }
-
     @Nullable
     private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
-        if (mSplitScreenController.isPresent()
-                && mSplitScreenController.get().isSplitScreenVisible()) {
+        if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) {
             // We can't look at focused task here as only one task will have focus.
             return getSplitScreenDecor(ev);
         } else {
@@ -711,9 +772,9 @@
     @Nullable
     private DesktopModeWindowDecoration getSplitScreenDecor(MotionEvent ev) {
         ActivityManager.RunningTaskInfo topOrLeftTask =
-                mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
+                mSplitScreenController.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
         ActivityManager.RunningTaskInfo bottomOrRightTask =
-                mSplitScreenController.get().getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+                mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
         if (topOrLeftTask != null && topOrLeftTask.getConfiguration()
                 .windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) {
             return mWindowDecorByTaskId.get(topOrLeftTask.taskId);
@@ -765,12 +826,18 @@
 
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
-        if (mSplitScreenController.isPresent()
-                && mSplitScreenController.get().isTaskRootOrStageRoot(taskInfo.taskId)) {
+        if (mSplitScreenController != null
+                && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) {
+            return false;
+        }
+        if (mDesktopModeKeyguardChangeListener.isKeyguardVisibleAndOccluded()
+                && taskInfo.isFocused) {
             return false;
         }
         return DesktopModeStatus.isProto2Enabled()
+                && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
                 && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
+                && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop()
                 && mDisplayController.getDisplayContext(taskInfo.displayId)
                 .getResources().getConfiguration().smallestScreenWidthDp >= 600;
     }
@@ -796,9 +863,10 @@
                         mMainChoreographer,
                         mSyncQueue);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
+        windowDecoration.createResizeVeil();
 
         final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback(
-                windowDecoration, taskInfo);
+                windowDecoration);
         final DesktopModeTouchEventListener touchEventListener =
                 new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
 
@@ -811,20 +879,17 @@
         incrementEventReceiverTasks(taskInfo.displayId);
     }
     private DragPositioningCallback createDragPositioningCallback(
-            @NonNull DesktopModeWindowDecoration windowDecoration,
-            @NonNull RunningTaskInfo taskInfo) {
-        final int screenWidth = mDisplayController.getDisplayLayout(taskInfo.displayId).width();
-        final Rect disallowedAreaForEndBounds = new Rect(0, 0, screenWidth,
-                getStatusBarHeight(taskInfo.displayId));
+            @NonNull DesktopModeWindowDecoration windowDecoration) {
+        final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
+                R.dimen.desktop_mode_transition_area_height);
         if (!DesktopModeStatus.isVeiledResizeEnabled()) {
             return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration,
-                    mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
-                    mTransactionFactory);
+                    mDisplayController, mDragStartListener, mTransactionFactory,
+                    transitionAreaHeight);
         } else {
-            windowDecoration.createResizeVeil();
             return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration,
-                    mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
-                    mTransitions);
+                    mDisplayController, mDragStartListener, mTransitions,
+                    transitionAreaHeight);
         }
     }
 
@@ -857,6 +922,22 @@
             mDesktopTasksController.ifPresent(d -> d.removeCornersForTask(taskId));
         }
     }
+
+    static class DesktopModeKeyguardChangeListener implements KeyguardChangeListener {
+        private boolean mIsKeyguardVisible;
+        private boolean mIsKeyguardOccluded;
+
+        @Override
+        public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+                boolean animatingDismiss) {
+            mIsKeyguardVisible = visible;
+            mIsKeyguardOccluded = occluded;
+        }
+
+        public boolean isKeyguardVisibleAndOccluded() {
+            return mIsKeyguardVisible && mIsKeyguardOccluded;
+        }
+    }
 }
 
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index ce11b26..a359395 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -23,7 +23,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.content.res.TypedArray;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -39,6 +38,7 @@
 import android.view.ViewConfiguration;
 import android.window.WindowContainerTransaction;
 
+import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -178,15 +178,12 @@
         mRelayoutParams.reset();
         mRelayoutParams.mRunningTaskInfo = taskInfo;
         mRelayoutParams.mLayoutResId = windowDecorLayoutId;
-        mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
+        mRelayoutParams.mCaptionHeightId = getCaptionHeightId();
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
         mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
 
-        final TypedArray ta = mContext.obtainStyledAttributes(
-                new int[]{android.R.attr.dialogCornerRadius});
-        mRelayoutParams.mCornerRadius = ta.getDimensionPixelSize(0, 0);
-        ta.recycle();
-
+        mRelayoutParams.mCornerRadius =
+                (int) ScreenDecorationsUtils.getWindowCornerRadius(mContext);
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
 
@@ -235,8 +232,11 @@
                     mHandler,
                     mChoreographer,
                     mDisplay.getDisplayId(),
+                    mRelayoutParams.mCornerRadius,
                     mDecorationContainerSurface,
-                    mDragPositioningCallback);
+                    mDragPositioningCallback,
+                    mSurfaceControlBuilderSupplier,
+                    mSurfaceControlTransactionSupplier);
         }
 
         final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
@@ -294,23 +294,37 @@
     }
 
     /**
-     * Fade in the resize veil
+     * Show the resize veil.
      */
-    void showResizeVeil(Rect taskBounds) {
+    public void showResizeVeil(Rect taskBounds) {
         mResizeVeil.showVeil(mTaskSurface, taskBounds);
     }
 
     /**
+     * Show the resize veil.
+     */
+    public void showResizeVeil(SurfaceControl.Transaction tx, Rect taskBounds) {
+        mResizeVeil.showVeil(tx, mTaskSurface, taskBounds, false /* fadeIn */);
+    }
+
+    /**
      * Set new bounds for the resize veil
      */
-    void updateResizeVeil(Rect newBounds) {
+    public void updateResizeVeil(Rect newBounds) {
         mResizeVeil.updateResizeVeil(newBounds);
     }
 
     /**
+     * Set new bounds for the resize veil
+     */
+    public void updateResizeVeil(SurfaceControl.Transaction tx, Rect newBounds) {
+        mResizeVeil.updateResizeVeil(tx, newBounds);
+    }
+
+    /**
      * Fade the resize veil out.
      */
-    void hideResizeVeil() {
+    public void hideResizeVeil() {
         mResizeVeil.hideVeil();
     }
 
@@ -479,6 +493,11 @@
         }
     }
 
+    @Override
+    int getCaptionHeightId() {
+        return R.dimen.freeform_decor_caption_height;
+    }
+
     /**
      * Add transition to mTransitionsPausingRelayout
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index 65b5a7a..da26898 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -24,6 +24,9 @@
 
 import android.graphics.PointF;
 import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
 
 /**
  * A detector for touch inputs that differentiates between drag and click inputs. It receives a flow
@@ -54,14 +57,24 @@
      *
      * @return the result returned by {@link #mEventHandler}, or the result when
      * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
-    */
+     */
     boolean onMotionEvent(MotionEvent ev) {
+        return onMotionEvent(null /* view */, ev);
+    }
+
+    /**
+     * The receiver of the {@link MotionEvent} flow.
+     *
+     * @return the result returned by {@link #mEventHandler}, or the result when
+     * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
+    */
+    boolean onMotionEvent(View v, MotionEvent ev) {
         final boolean isTouchScreen =
                 (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
         if (!isTouchScreen) {
             // Only touches generate noisy moves, so mouse/trackpad events don't need to filtered
             // to take the slop threshold into consideration.
-            return mEventHandler.handleMotionEvent(ev);
+            return mEventHandler.handleMotionEvent(v, ev);
         }
         switch (ev.getActionMasked()) {
             case ACTION_DOWN: {
@@ -69,12 +82,15 @@
                 float rawX = ev.getRawX(0);
                 float rawY = ev.getRawY(0);
                 mInputDownPoint.set(rawX, rawY);
-                mResultOfDownAction = mEventHandler.handleMotionEvent(ev);
+                mResultOfDownAction = mEventHandler.handleMotionEvent(v, ev);
                 return mResultOfDownAction;
             }
             case ACTION_MOVE: {
+                if (ev.findPointerIndex(mDragPointerId) == -1) {
+                    mDragPointerId = ev.getPointerId(0);
+                }
+                final int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
                 if (!mIsDragEvent) {
-                    int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
                     float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
                     float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
                     // Touches generate noisy moves, so only once the move is past the touch
@@ -84,7 +100,7 @@
                 // The event handler should only be notified about 'move' events if a drag has been
                 // detected.
                 if (mIsDragEvent) {
-                    return mEventHandler.handleMotionEvent(ev);
+                    return mEventHandler.handleMotionEvent(v, ev);
                 } else {
                     return mResultOfDownAction;
                 }
@@ -92,10 +108,10 @@
             case ACTION_UP:
             case ACTION_CANCEL: {
                 resetState();
-                return mEventHandler.handleMotionEvent(ev);
+                return mEventHandler.handleMotionEvent(v, ev);
             }
             default:
-                return mEventHandler.handleMotionEvent(ev);
+                return mEventHandler.handleMotionEvent(v, ev);
         }
     }
 
@@ -111,6 +127,6 @@
     }
 
     interface MotionEventHandler {
-        boolean handleMotionEvent(MotionEvent ev);
+        boolean handleMotionEvent(@Nullable View v, MotionEvent ev);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index 4e98f0c..1669cf4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -17,12 +17,15 @@
 package com.android.wm.shell.windowdecor;
 
 import android.annotation.IntDef;
+import android.graphics.Rect;
 
 /**
  * Callback called when receiving drag-resize or drag-move related input events.
  */
 public interface DragPositioningCallback {
-    @IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
+    @IntDef(flag = true, value = {
+            CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM
+    })
     @interface CtrlType {}
 
     int CTRL_TYPE_UNDEFINED = 0;
@@ -44,13 +47,15 @@
      * Called when the pointer moves during a drag-resize or drag-move.
      * @param x x coordinate in window decoration coordinate system of the new pointer location
      * @param y y coordinate in window decoration coordinate system of the new pointer location
+     * @return the updated task bounds
      */
-    void onDragPositioningMove(float x, float y);
+    Rect onDragPositioningMove(float x, float y);
 
     /**
      * Called when a drag-resize or drag-move stops.
      * @param x x coordinate in window decoration coordinate system where the drag resize stops
      * @param y y coordinate in window decoration coordinate system where the drag resize stops
+     * @return the final bounds for the dragged task
      */
-    void onDragPositioningEnd(float x, float y);
+    Rect onDragPositioningEnd(float x, float y);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 09e29bc..e32bd42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -83,8 +83,6 @@
         // Make sure the new resizing destination in any direction falls within the stable bounds.
         // If not, set the bounds back to the old location that was valid to avoid conflicts with
         // some regions such as the gesture area.
-        displayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
-                .getStableBounds(stableBounds);
         if ((ctrlType & CTRL_TYPE_LEFT) != 0) {
             final int candidateLeft = repositionTaskBounds.left + (int) delta.x;
             repositionTaskBounds.left = (candidateLeft > stableBounds.left)
@@ -136,7 +134,7 @@
                 repositionTaskBounds.top);
     }
 
-    static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+    private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
             PointF repositionStartPoint, float x, float y) {
         final float deltaX = x - repositionStartPoint.x;
         final float deltaY = y - repositionStartPoint.y;
@@ -145,6 +143,23 @@
     }
 
     /**
+     * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
+     * the bounds are outside of the stable bounds, they are shifted to place task at the top of the
+     * stable bounds.
+     */
+    static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds,
+            PointF repositionStartPoint, float x, float y)  {
+        updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
+                x, y);
+
+        // If task is outside of stable bounds (in the status bar area), shift the task down.
+        if (stableBounds.top > repositionTaskBounds.top) {
+            final int yShift =  stableBounds.top - repositionTaskBounds.top;
+            repositionTaskBounds.offset(0, yShift);
+        }
+    }
+
+    /**
      * Apply a bounds change to a task.
      * @param windowDecoration decor of task we are changing bounds for
      * @param taskBounds new bounds of this task
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 287d861..7c6fb99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -18,8 +18,11 @@
 
 import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
 
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
@@ -42,11 +45,14 @@
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.SurfaceControl;
+import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.view.BaseIWindow;
 
+import java.util.function.Supplier;
+
 /**
  * An input event listener registered to InputDispatcher to receive input events on task edges and
  * and corners. Converts them to drag resize requests.
@@ -55,11 +61,11 @@
  */
 class DragResizeInputListener implements AutoCloseable {
     private static final String TAG = "DragResizeInputListener";
-
     private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
     private final Handler mHandler;
     private final Choreographer mChoreographer;
     private final InputManager mInputManager;
+    private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
 
     private final int mDisplayId;
     private final BaseIWindow mFakeWindow;
@@ -69,10 +75,15 @@
     private final TaskResizeInputEventReceiver mInputEventReceiver;
     private final DragPositioningCallback mCallback;
 
+    private final SurfaceControl mInputSinkSurface;
+    private final BaseIWindow mFakeSinkWindow;
+    private final InputChannel mSinkInputChannel;
+
     private int mTaskWidth;
     private int mTaskHeight;
     private int mResizeHandleThickness;
     private int mCornerSize;
+    private int mTaskCornerRadius;
 
     private Rect mLeftTopCornerBounds;
     private Rect mRightTopCornerBounds;
@@ -87,15 +98,20 @@
             Handler handler,
             Choreographer choreographer,
             int displayId,
+            int taskCornerRadius,
             SurfaceControl decorationSurface,
-            DragPositioningCallback callback) {
+            DragPositioningCallback callback,
+            Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+            Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
         mInputManager = context.getSystemService(InputManager.class);
         mHandler = handler;
         mChoreographer = choreographer;
+        mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
         mDisplayId = displayId;
+        mTaskCornerRadius = taskCornerRadius;
         mDecorationSurface = decorationSurface;
-        // Use a fake window as the backing surface is a container layer and we don't want to create
-        // a buffer layer for it so we can't use ViewRootImpl.
+        // Use a fake window as the backing surface is a container layer, and we don't want to
+        // create a buffer layer for it, so we can't use ViewRootImpl.
         mFakeWindow = new BaseIWindow();
         mFakeWindow.setSession(mWindowSession);
         mFocusGrantToken = new Binder();
@@ -108,7 +124,7 @@
                     null /* hostInputToken */,
                     FLAG_NOT_FOCUSABLE,
                     PRIVATE_FLAG_TRUSTED_OVERLAY,
-                    0 /* inputFeatures */,
+                    INPUT_FEATURE_SPY,
                     TYPE_APPLICATION,
                     null /* windowToken */,
                     mFocusGrantToken,
@@ -123,15 +139,39 @@
         mCallback = callback;
         mDragDetector = new DragDetector(mInputEventReceiver);
         mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
+
+        mInputSinkSurface = surfaceControlBuilderSupplier.get()
+                .setName("TaskInputSink of " + decorationSurface)
+                .setContainerLayer()
+                .setParent(mDecorationSurface)
+                .build();
+        mSurfaceControlTransactionSupplier.get()
+                .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER)
+                .show(mInputSinkSurface)
+                .apply();
+        mFakeSinkWindow = new BaseIWindow();
+        mSinkInputChannel = new InputChannel();
+        try {
+            mWindowSession.grantInputChannel(
+                    mDisplayId,
+                    mInputSinkSurface,
+                    mFakeSinkWindow,
+                    null /* hostInputToken */,
+                    FLAG_NOT_FOCUSABLE,
+                    0 /* privateFlags */,
+                    INPUT_FEATURE_NO_INPUT_CHANNEL,
+                    TYPE_INPUT_CONSUMER,
+                    null /* windowToken */,
+                    mFocusGrantToken,
+                    "TaskInputSink of " + decorationSurface,
+                    mSinkInputChannel);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 
     /**
-     * Updates geometry of this drag resize handler. Needs to be called every time there is a size
-     * change to notify the input event receiver it's ready to take the next input event. Otherwise
-     * it'll keep batching move events and the drag resize process is stalled.
-     *
-     * This is also used to update the touch regions of this handler every event dispatched here is
-     * a potential resize request.
+     * Updates the geometry (the touch region) of this drag resize handler.
      *
      * @param taskWidth The width of the task.
      * @param taskHeight The height of the task.
@@ -221,7 +261,35 @@
                     mDecorationSurface,
                     FLAG_NOT_FOCUSABLE,
                     PRIVATE_FLAG_TRUSTED_OVERLAY,
-                    0 /* inputFeatures */,
+                    INPUT_FEATURE_SPY,
+                    touchRegion);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        mSurfaceControlTransactionSupplier.get()
+                .setWindowCrop(mInputSinkSurface, mTaskWidth, mTaskHeight)
+                .apply();
+        // The touch region of the TaskInputSink should be the touch region of this
+        // DragResizeInputHandler minus the task bounds. Pilfering events isn't enough to prevent
+        // input windows from handling down events, which will bring tasks in the back to front.
+        //
+        // Note not the entire touch region responds to both mouse and touchscreen events.
+        // Therefore, in the region that only responds to one of them, it would be a no-op to
+        // perform a gesture in the other type of events. We currently only have a mouse-only region
+        // out of the task bounds, and due to the roughness of touchscreen events, it's not a severe
+        // issue. However, were there touchscreen-only a region out of the task bounds, mouse
+        // gestures will become no-op in that region, even though the mouse gestures may appear to
+        // be performed on the input window behind the resize handle.
+        touchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE);
+        try {
+            mWindowSession.updateInputChannel(
+                    mSinkInputChannel.getToken(),
+                    mDisplayId,
+                    mInputSinkSurface,
+                    FLAG_NOT_FOCUSABLE,
+                    0 /* privateFlags */,
+                    INPUT_FEATURE_NO_INPUT_CHANNEL,
                     touchRegion);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
@@ -250,6 +318,16 @@
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
+
+        mSinkInputChannel.dispose();
+        try {
+            mWindowSession.remove(mFakeSinkWindow);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        mSurfaceControlTransactionSupplier.get()
+                .remove(mInputSinkSurface)
+                .apply();
     }
 
     private class TaskResizeInputEventReceiver extends InputEventReceiver
@@ -258,6 +336,7 @@
         private final Runnable mConsumeBatchEventRunnable;
         private boolean mConsumeBatchEventScheduled;
         private boolean mShouldHandleEvents;
+        private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
 
         private TaskResizeInputEventReceiver(
                 InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -303,7 +382,7 @@
         }
 
         @Override
-        public boolean handleMotionEvent(MotionEvent e) {
+        public boolean handleMotionEvent(View v, MotionEvent e) {
             boolean result = false;
             // Check if this is a touch event vs mouse event.
             // Touch events are tracked in four corners. Other events are tracked in resize edges.
@@ -318,6 +397,8 @@
                         mShouldHandleEvents = isInResizeHandleBounds(x, y);
                     }
                     if (mShouldHandleEvents) {
+                        mInputManager.pilferPointers(mInputChannel.getToken());
+
                         mDragPointerId = e.getPointerId(0);
                         float rawX = e.getRawX(0);
                         float rawY = e.getRawY(0);
@@ -357,7 +438,6 @@
                     break;
                 }
                 case MotionEvent.ACTION_HOVER_EXIT:
-                    mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT);
                     result = true;
                     break;
             }
@@ -383,19 +463,71 @@
         @DragPositioningCallback.CtrlType
         private int calculateResizeHandlesCtrlType(float x, float y) {
             int ctrlType = 0;
-            if (x < 0) {
+            // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
+            // sides will use the bounds specified in setGeometry and not go into task bounds.
+            if (x < mTaskCornerRadius) {
                 ctrlType |= CTRL_TYPE_LEFT;
             }
-            if (x > mTaskWidth) {
+            if (x > mTaskWidth - mTaskCornerRadius) {
                 ctrlType |= CTRL_TYPE_RIGHT;
             }
-            if (y < 0) {
+            if (y < mTaskCornerRadius) {
                 ctrlType |= CTRL_TYPE_TOP;
             }
-            if (y > mTaskHeight) {
+            if (y > mTaskHeight - mTaskCornerRadius) {
                 ctrlType |= CTRL_TYPE_BOTTOM;
             }
-            return ctrlType;
+            // Check distances from the center if it's in one of four corners.
+            if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
+                    && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
+                return checkDistanceFromCenter(ctrlType, x, y);
+            }
+            // Otherwise, we should make sure we don't resize tasks inside task bounds.
+            return (x < 0 || y < 0 || x >= mTaskWidth || y >= mTaskHeight) ? ctrlType : 0;
+        }
+
+        // If corner input is not within appropriate distance of corner radius, do not use it.
+        // If input is not on a corner or is within valid distance, return ctrlType.
+        @DragPositioningCallback.CtrlType
+        private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType,
+                float x, float y) {
+            int centerX;
+            int centerY;
+
+            // Determine center of rounded corner circle; this is simply the corner if radius is 0.
+            switch (ctrlType) {
+                case CTRL_TYPE_LEFT | CTRL_TYPE_TOP: {
+                    centerX = mTaskCornerRadius;
+                    centerY = mTaskCornerRadius;
+                    break;
+                }
+                case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM: {
+                    centerX = mTaskCornerRadius;
+                    centerY = mTaskHeight - mTaskCornerRadius;
+                    break;
+                }
+                case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP: {
+                    centerX = mTaskWidth - mTaskCornerRadius;
+                    centerY = mTaskCornerRadius;
+                    break;
+                }
+                case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM: {
+                    centerX = mTaskWidth - mTaskCornerRadius;
+                    centerY = mTaskHeight - mTaskCornerRadius;
+                    break;
+                }
+                default: {
+                    throw new IllegalArgumentException("ctrlType should be complex, but it's 0x"
+                            + Integer.toHexString(ctrlType));
+                }
+            }
+            double distanceFromCenter = Math.hypot(x - centerX, y - centerY);
+
+            if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness
+                    && distanceFromCenter >= mTaskCornerRadius) {
+                return ctrlType;
+            }
+            return 0;
         }
 
         @DragPositioningCallback.CtrlType
@@ -439,7 +571,19 @@
                     cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
                     break;
             }
-            mInputManager.setPointerIconType(cursorType);
+            // Only update the cursor type to default once so that views behind the decor container
+            // layer that aren't in the active resizing regions have chances to update the cursor
+            // type. We would like to enforce the cursor type by setting the cursor type multilple
+            // times in active regions because we shouldn't allow the views behind to change it, as
+            // we'll pilfer the gesture initiated in this area. This is necessary because 1) we
+            // should allow the views behind regions only for touches to set the cursor type; and 2)
+            // there is a small region out of each rounded corner that's inside the task bounds,
+            // where views in the task can receive input events because we can't set touch regions
+            // of input sinks to have rounded corners.
+            if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) {
+                mInputManager.setPointerIconType(cursorType);
+                mLastCursorType = cursorType;
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 9082323..e0ee252 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -21,8 +21,6 @@
 import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 
-import androidx.annotation.Nullable;
-
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 
@@ -42,28 +40,29 @@
     private final Rect mTaskBoundsAtDragStart = new Rect();
     private final PointF mRepositionStartPoint = new PointF();
     private final Rect mRepositionTaskBounds = new Rect();
-    // If a task move (not resize) finishes in this region, the positioner will not attempt to
+    // If a task move (not resize) finishes with the positions y less than this value, do not
     // finalize the bounds there using WCT#setBounds
-    private final Rect mDisallowedAreaForEndBounds;
+    private final int mDisallowedAreaForEndBoundsHeight;
     private boolean mHasDragResized;
     private int mCtrlType;
 
     FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
-            DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds) {
-        this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds,
-                dragStartListener -> {}, SurfaceControl.Transaction::new);
+            DisplayController displayController, int disallowedAreaForEndBoundsHeight) {
+        this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {},
+                SurfaceControl.Transaction::new, disallowedAreaForEndBoundsHeight);
     }
 
     FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
-            DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds,
+            DisplayController displayController,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
-            Supplier<SurfaceControl.Transaction> supplier) {
+            Supplier<SurfaceControl.Transaction> supplier,
+            int disallowedAreaForEndBoundsHeight) {
         mTaskOrganizer = taskOrganizer;
         mWindowDecoration = windowDecoration;
         mDisplayController = displayController;
-        mDisallowedAreaForEndBounds = new Rect(disallowedAreaForEndBounds);
         mDragStartListener = dragStartListener;
         mTransactionSupplier = supplier;
+        mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
     }
 
     @Override
@@ -79,10 +78,14 @@
             mTaskOrganizer.applyTransaction(wct);
         }
         mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+        if (mStableBounds.isEmpty()) {
+            mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
+                    .getStableBounds(mStableBounds);
+        }
     }
 
     @Override
-    public void onDragPositioningMove(float x, float y) {
+    public Rect onDragPositioningMove(float x, float y) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint);
         if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
@@ -103,10 +106,11 @@
                     mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
             t.apply();
         }
+        return new Rect(mRepositionTaskBounds);
     }
 
     @Override
-    public void onDragPositioningEnd(float x, float y) {
+    public Rect onDragPositioningEnd(float x, float y) {
         // If task has been resized or task was dragged into area outside of
         // mDisallowedAreaForEndBounds, apply WCT to finish it.
         if (isResizing() && mHasDragResized) {
@@ -121,10 +125,10 @@
             }
             mTaskOrganizer.applyTransaction(wct);
         } else if (mCtrlType == CTRL_TYPE_UNDEFINED
-                && !mDisallowedAreaForEndBounds.contains((int) x, (int) y)) {
+                && y > mDisallowedAreaForEndBoundsHeight) {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
-            DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
-                    mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
+            DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
+                    mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
             wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
             mTaskOrganizer.applyTransaction(wct);
         }
@@ -133,6 +137,7 @@
         mRepositionStartPoint.set(0, 0);
         mCtrlType = CTRL_TYPE_UNDEFINED;
         mHasDragResized = false;
+        return new Rect(mRepositionTaskBounds);
     }
 
     private boolean isResizing() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
new file mode 100644
index 0000000..b2267dd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
@@ -0,0 +1,72 @@
+package com.android.wm.shell.windowdecor
+
+import android.animation.ValueAnimator
+import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.PointF
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.SurfaceControl
+
+/**
+ * Creates an animator to shrink and position task after a user drags a fullscreen task from
+ * the top of the screen to transition it into freeform and before the user releases the task. The
+ * MoveToDesktopAnimator object also holds information about the state of the task that are
+ * accessed by the EnterDesktopTaskTransitionHandler.
+ */
+class MoveToDesktopAnimator @JvmOverloads constructor(
+        private val startBounds: Rect,
+        private val taskInfo: RunningTaskInfo,
+        private val taskSurface: SurfaceControl,
+        private val transactionFactory: () -> SurfaceControl.Transaction =
+                SurfaceControl::Transaction
+) {
+    companion object {
+        // The size of the screen during drag relative to the fullscreen size
+        const val DRAG_FREEFORM_SCALE: Float = 0.4f
+        const val ANIMATION_DURATION = 336
+    }
+
+    private val animatedTaskWidth
+        get() = dragToDesktopAnimator.animatedValue as Float * startBounds.width()
+    private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f,
+            DRAG_FREEFORM_SCALE)
+            .setDuration(ANIMATION_DURATION.toLong())
+            .apply {
+                val t = SurfaceControl.Transaction()
+                addUpdateListener { animation ->
+                    val animatorValue = animation.animatedValue as Float
+                    t.setScale(taskSurface, animatorValue, animatorValue)
+                            .apply()
+                }
+            }
+
+    val taskId get() = taskInfo.taskId
+    val position: PointF = PointF(0.0f, 0.0f)
+
+    /**
+     * Starts the animation that scales the task down.
+     */
+    fun startAnimation() {
+        dragToDesktopAnimator.start()
+    }
+
+    /**
+     * Uses the position of the motion event and the current scale of the task as defined by the
+     * ValueAnimator to update the local position variable and set the task surface's position
+     */
+    fun updatePosition(ev: MotionEvent) {
+        position.x = ev.x - animatedTaskWidth / 2
+        position.y = ev.y
+
+        val t = transactionFactory()
+        t.setPosition(taskSurface, position.x, position.y)
+        t.apply()
+    }
+
+    /**
+     * Ends the animation, setting the scale and position to the final animation value
+     */
+    fun endAnimator() {
+        dragToDesktopAnimator.end()
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index 8277109..bfce72b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -102,11 +102,17 @@
     }
 
     /**
-     * Animate veil's alpha to 1, fading it in.
+     * Shows the veil surface/view.
+     *
+     * @param t the transaction to apply in sync with the veil draw
+     * @param parentSurface the surface that the veil should be a child of
+     * @param taskBounds the bounds of the task that owns the veil
+     * @param fadeIn if true, the veil will fade-in with an animation, if false, it will be shown
+     *               immediately
      */
-    public void showVeil(SurfaceControl parentSurface, Rect taskBounds) {
+    public void showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface,
+            Rect taskBounds, boolean fadeIn) {
         // Parent surface can change, ensure it is up to date.
-        SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
         if (!parentSurface.equals(mParentSurface)) {
             t.reparent(mVeilSurface, parentSurface);
             mParentSurface = parentSurface;
@@ -115,22 +121,36 @@
         int backgroundColorId = getBackgroundColorId();
         mViewHost.getView().setBackgroundColor(mContext.getColor(backgroundColorId));
 
-        final ValueAnimator animator = new ValueAnimator();
-        animator.setFloatValues(0f, 1f);
-        animator.setDuration(RESIZE_ALPHA_DURATION);
-        animator.addUpdateListener(animation -> {
-            t.setAlpha(mVeilSurface, animator.getAnimatedFraction());
-            t.apply();
-        });
-
         relayout(taskBounds, t);
-        t.show(mVeilSurface)
-                .addTransactionCommittedListener(mContext.getMainExecutor(), () -> animator.start())
-                .setAlpha(mVeilSurface, 0);
+        if (fadeIn) {
+            final ValueAnimator animator = new ValueAnimator();
+            animator.setFloatValues(0f, 1f);
+            animator.setDuration(RESIZE_ALPHA_DURATION);
+            animator.addUpdateListener(animation -> {
+                t.setAlpha(mVeilSurface, animator.getAnimatedFraction());
+                t.apply();
+            });
+
+            t.show(mVeilSurface)
+                    .addTransactionCommittedListener(
+                            mContext.getMainExecutor(), () -> animator.start())
+                    .setAlpha(mVeilSurface, 0);
+        } else {
+            // Show the veil immediately at full opacity.
+            t.show(mVeilSurface).setAlpha(mVeilSurface, 1);
+        }
         mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
     }
 
     /**
+     * Animate veil's alpha to 1, fading it in.
+     */
+    public void showVeil(SurfaceControl parentSurface, Rect taskBounds) {
+        SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+        showVeil(t, parentSurface, taskBounds, true /* fadeIn */);
+    }
+
+    /**
      * Update veil bounds to match bounds changes.
      * @param newBounds bounds to update veil to.
      */
@@ -147,6 +167,16 @@
      */
     public void updateResizeVeil(Rect newBounds) {
         SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+        updateResizeVeil(t, newBounds);
+    }
+
+    /**
+     * Calls relayout to update task and veil bounds.
+     *
+     * @param t a transaction to be applied in sync with the veil draw.
+     * @param newBounds bounds to update veil to.
+     */
+    public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) {
         relayout(newBounds, t);
         mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 58c78e6..c9c58de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -53,33 +53,33 @@
     private final Rect mTaskBoundsAtDragStart = new Rect();
     private final PointF mRepositionStartPoint = new PointF();
     private final Rect mRepositionTaskBounds = new Rect();
-    // If a task move (not resize) finishes in this region, the positioner will not attempt to
+    // If a task move (not resize) finishes with the positions y less than this value, do not
     // finalize the bounds there using WCT#setBounds
-    private final Rect mDisallowedAreaForEndBounds = new Rect();
+    private final int mDisallowedAreaForEndBoundsHeight;
     private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
     private int mCtrlType;
 
     public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
             DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
-            Rect disallowedAreaForEndBounds,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
-            Transitions transitions) {
-        this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds,
-                dragStartListener, SurfaceControl.Transaction::new, transitions);
+            Transitions transitions,
+            int disallowedAreaForEndBoundsHeight) {
+        this(taskOrganizer, windowDecoration, displayController, dragStartListener,
+                SurfaceControl.Transaction::new, transitions, disallowedAreaForEndBoundsHeight);
     }
 
     public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
             DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
-            Rect disallowedAreaForEndBounds,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
-            Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) {
+            Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
+            int disallowedAreaForEndBoundsHeight) {
         mTaskOrganizer = taskOrganizer;
         mDesktopWindowDecoration = windowDecoration;
         mDisplayController = displayController;
         mDragStartListener = dragStartListener;
-        mDisallowedAreaForEndBounds.set(disallowedAreaForEndBounds);
         mTransactionSupplier = supplier;
         mTransitions = transitions;
+        mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
     }
 
     @Override
@@ -98,10 +98,14 @@
         }
         mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
         mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+        if (mStableBounds.isEmpty()) {
+            mDisplayController.getDisplayLayout(mDesktopWindowDecoration.mDisplay.getDisplayId())
+                    .getStableBounds(mStableBounds);
+        }
     }
 
     @Override
-    public void onDragPositioningMove(float x, float y) {
+    public Rect onDragPositioningMove(float x, float y) {
         PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint);
         if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
                 mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
@@ -110,14 +114,14 @@
         } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
             DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
-                    mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t,
-                    x, y);
+                    mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
             t.apply();
         }
+        return new Rect(mRepositionTaskBounds);
     }
 
     @Override
-    public void onDragPositioningEnd(float x, float y) {
+    public Rect onDragPositioningEnd(float x, float y) {
         PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y,
                 mRepositionStartPoint);
         if (isResizing()) {
@@ -138,9 +142,9 @@
                 // won't be called.
                 mDesktopWindowDecoration.hideResizeVeil();
             }
-        } else if (!mDisallowedAreaForEndBounds.contains((int) x, (int) y)) {
-            DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
-                    mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
+        } else if (y > mDisallowedAreaForEndBoundsHeight) {
+            DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
+                    mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
             DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
                     mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer);
         }
@@ -148,6 +152,7 @@
         mCtrlType = CTRL_TYPE_UNDEFINED;
         mTaskBoundsAtDragStart.setEmpty();
         mRepositionStartPoint.set(0, 0);
+        return new Rect(mRepositionTaskBounds);
     }
 
     private boolean isResizing() {
@@ -163,7 +168,7 @@
         startTransaction.apply();
         mDesktopWindowDecoration.hideResizeVeil();
         mCtrlType = CTRL_TYPE_UNDEFINED;
-        finishCallback.onTransitionFinished(null, null);
+        finishCallback.onTransitionFinished(null);
         return true;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index 9f03d9a..ae1a3d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -22,6 +22,7 @@
 import android.window.TransitionInfo;
 
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.splitscreen.SplitScreenController;
 
 /**
  * The interface used by some {@link com.android.wm.shell.ShellTaskOrganizer.TaskListener} to help
@@ -39,6 +40,11 @@
     void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter);
 
     /**
+     * Sets the {@link SplitScreenController} if available.
+     */
+    void setSplitScreenController(SplitScreenController splitScreenController);
+
+    /**
      * Creates a window decoration for the given task. Can be {@code null} for Fullscreen tasks but
      * not Freeform ones.
      *
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 ac5ff20..0b0d9d5 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
@@ -64,6 +64,24 @@
         implements AutoCloseable {
 
     /**
+     * The Z-order of {@link #mCaptionContainerSurface}.
+     * <p>
+     * We use {@link #mDecorationContainerSurface} to define input window for task resizing; by
+     * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input
+     * prior to caption view itself, treating corner inputs as resize events rather than
+     * repositioning.
+     */
+    static final int CAPTION_LAYER_Z_ORDER = -1;
+    /**
+     * The Z-order of the task input sink in {@link DragPositioningCallback}.
+     * <p>
+     * This task input sink is used to prevent undesired dispatching of motion events out of task
+     * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle
+     * input events first.
+     */
+    static final int INPUT_SINK_Z_ORDER = -2;
+
+    /**
      * System-wide context. Only used to create context with overridden configurations.
      */
     final Context mContext;
@@ -237,7 +255,9 @@
 
         final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
         final int captionWidth = taskBounds.width();
+
         startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
+                .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
                 .show(mCaptionContainerSurface);
 
         if (ViewRootImpl.CAPTION_ON_SHELL) {
@@ -248,6 +268,9 @@
             mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
             wct.addInsetsSource(mTaskInfo.token,
                     mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
+            wct.addInsetsSource(mTaskInfo.token,
+                    mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
+                    mCaptionInsetsRect);
         } else {
             startT.hide(mCaptionContainerSurface);
         }
@@ -264,6 +287,7 @@
                 .setColor(mTaskSurface, mTmpColor)
                 .show(mTaskSurface);
         finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
+                .setShadowRadius(mTaskSurface, shadowRadius)
                 .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
         if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
@@ -301,6 +325,10 @@
         }
     }
 
+    int getCaptionHeightId() {
+        return Resources.ID_NULL;
+    }
+
     /**
      * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or
      * registers {@link #mOnDisplaysChangedListener} if it doesn't.
@@ -345,6 +373,8 @@
         final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get();
         wct.removeInsetsSource(mTaskInfo.token,
                 mOwner, 0 /* index */, WindowInsets.Type.captionBar());
+        wct.removeInsetsSource(mTaskInfo.token,
+                mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures());
         mTaskOrganizer.applyTransaction(wct);
     }
 
@@ -413,6 +443,21 @@
                 mSurfaceControlTransactionSupplier);
     }
 
+    /**
+     * Adds caption inset source to a WCT
+     */
+    public void addCaptionInset(WindowContainerTransaction wct) {
+        final int captionHeightId = getCaptionHeightId();
+        if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL) {
+            return;
+        }
+
+        final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId);
+        final Rect captionInsets = new Rect(0, 0, 0, captionHeight);
+        wct.addInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, WindowInsets.Type.captionBar(),
+                captionInsets);
+    }
+
     static class RelayoutParams {
         RunningTaskInfo mRunningTaskInfo;
         int mLayoutResId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index b67acd5..a9eb882 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -23,13 +23,14 @@
         appIcon: Drawable
 ) : DesktopModeWindowDecorationViewHolder(rootView) {
 
-    private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
-    private val captionHandle: View = rootView.findViewById(R.id.caption_handle)
-    private val openMenuButton: View = rootView.findViewById(R.id.open_menu_button)
-    private val closeWindowButton: ImageButton = rootView.findViewById(R.id.close_window)
-    private val expandMenuButton: ImageButton = rootView.findViewById(R.id.expand_menu_button)
-    private val appNameTextView: TextView = rootView.findViewById(R.id.application_name)
-    private val appIconImageView: ImageView = rootView.findViewById(R.id.application_icon)
+    private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
+    private val captionHandle: View = rootView.requireViewById(R.id.caption_handle)
+    private val openMenuButton: View = rootView.requireViewById(R.id.open_menu_button)
+    private val closeWindowButton: ImageButton = rootView.requireViewById(R.id.close_window)
+    private val expandMenuButton: ImageButton = rootView.requireViewById(R.id.expand_menu_button)
+    private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window)
+    private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name)
+    private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon)
 
     init {
         captionView.setOnTouchListener(onCaptionTouchListener)
@@ -37,6 +38,7 @@
         openMenuButton.setOnClickListener(onCaptionButtonClickListener)
         openMenuButton.setOnTouchListener(onCaptionTouchListener)
         closeWindowButton.setOnClickListener(onCaptionButtonClickListener)
+        maximizeWindowButton.setOnClickListener(onCaptionButtonClickListener)
         closeWindowButton.setOnTouchListener(onCaptionTouchListener)
         appNameTextView.text = appName
         appIconImageView.setImageDrawable(appIcon)
@@ -45,10 +47,14 @@
     override fun bindData(taskInfo: RunningTaskInfo) {
 
         val captionDrawable = captionView.background as GradientDrawable
-        captionDrawable.setColor(taskInfo.taskDescription.statusBarColor)
+        taskInfo.taskDescription?.statusBarColor?.let {
+            captionDrawable.setColor(it)
+        }
 
         closeWindowButton.imageTintList = ColorStateList.valueOf(
                 getCaptionCloseButtonColor(taskInfo))
+        maximizeWindowButton.imageTintList = ColorStateList.valueOf(
+                getCaptionMaximizeButtonColor(taskInfo))
         expandMenuButton.imageTintList = ColorStateList.valueOf(
                 getCaptionExpandButtonColor(taskInfo))
         appNameTextView.setTextColor(getCaptionAppNameTextColor(taskInfo))
@@ -70,6 +76,14 @@
         }
     }
 
+    private fun getCaptionMaximizeButtonColor(taskInfo: RunningTaskInfo): Int {
+        return if (shouldUseLightCaptionColors(taskInfo)) {
+            context.getColor(R.color.desktop_mode_caption_maximize_button_light)
+        } else {
+            context.getColor(R.color.desktop_mode_caption_maximize_button_dark)
+        }
+    }
+
     private fun getCaptionExpandButtonColor(taskInfo: RunningTaskInfo): Int {
         return if (shouldUseLightCaptionColors(taskInfo)) {
             context.getColor(R.color.desktop_mode_caption_expand_button_light)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 47a12a0..9374ac9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -17,8 +17,8 @@
         onCaptionButtonClickListener: View.OnClickListener
 ) : DesktopModeWindowDecorationViewHolder(rootView) {
 
-    private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
-    private val captionHandle: ImageButton = rootView.findViewById(R.id.caption_handle)
+    private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
+    private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
 
     init {
         captionView.setOnTouchListener(onCaptionTouchListener)
@@ -27,9 +27,10 @@
     }
 
     override fun bindData(taskInfo: RunningTaskInfo) {
-        val captionColor = taskInfo.taskDescription.statusBarColor
-        val captionDrawable = captionView.background as GradientDrawable
-        captionDrawable.setColor(captionColor)
+        taskInfo.taskDescription?.statusBarColor?.let { captionColor ->
+            val captionDrawable = captionView.background as GradientDrawable
+            captionDrawable.setColor(captionColor)
+        }
 
         captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index 514ea52..49e8d15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -1,28 +1,38 @@
 package com.android.wm.shell.windowdecor.viewholder
 
 import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.content.Context
 import android.graphics.Color
 import android.view.View
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
 
 /**
  * Encapsulates the root [View] of a window decoration and its children to facilitate looking up
  * children (via findViewById) and updating to the latest data from [RunningTaskInfo].
  */
 internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) {
-    val context: Context = rootView.context
+  val context: Context = rootView.context
 
-    /**
-     * A signal to the view holder that new data is available and that the views should be updated
-     * to reflect it.
-     */
-    abstract fun bindData(taskInfo: RunningTaskInfo)
+  /**
+   * A signal to the view holder that new data is available and that the views should be updated to
+   * reflect it.
+   */
+  abstract fun bindData(taskInfo: RunningTaskInfo)
 
-    /**
-     * Whether the caption items should use the 'light' color variant so that there's good contrast
-     * with the caption background color.
-     */
-    protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
-        return Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
-    }
+  /**
+   * Whether the caption items should use the 'light' color variant so that there's good contrast
+   * with the caption background color.
+   */
+  protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
+    return taskInfo.taskDescription
+        ?.let { taskDescription ->
+          if (Color.alpha(taskDescription.statusBarColor) != 0 &&
+              taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+            Color.valueOf(taskDescription.statusBarColor).luminance() < 0.5
+          } else {
+            taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
+          }
+        } ?: false
+  }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index b6696c7..b062fbd 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -23,14 +23,67 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_test {
-    name: "WMShellFlickerTests",
+filegroup {
+    name: "WMShellFlickerTestsUtils-src",
+    srcs: ["src/com/android/wm/shell/flicker/utils/*.kt"],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsBase-src",
+    srcs: ["src/com/android/wm/shell/flicker/*.kt"],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsBubbles-src",
+    srcs: ["src/com/android/wm/shell/flicker/bubble/*.kt"],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsPip-src",
+    srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsSplitScreen-src",
     srcs: [
-        "src/**/*.java",
-        "src/**/*.kt",
+        "src/com/android/wm/shell/flicker/splitscreen/*.kt",
+        "src/com/android/wm/shell/flicker/splitscreen/benchmark/*.kt",
     ],
-    manifest: "AndroidManifest.xml",
-    test_config: "AndroidTest.xml",
+}
+
+filegroup {
+    name: "WMShellFlickerServiceTests-src",
+    srcs: [
+        "src/com/android/wm/shell/flicker/service/**/*.kt",
+    ],
+}
+
+java_library {
+    name: "wm-shell-flicker-utils",
+    platform_apis: true,
+    optimize: {
+        enabled: false,
+    },
+    srcs: [
+        ":WMShellFlickerTestsUtils-src",
+    ],
+    static_libs: [
+        "androidx.test.ext.junit",
+        "flickertestapplib",
+        "flickerlib",
+        "flickerlib-helpers",
+        "platform-test-annotations",
+        "wm-flicker-common-app-helpers",
+        "wm-flicker-common-assertions",
+        "launcher-helper-lib",
+        "launcher-aosp-tapl",
+    ],
+}
+
+java_defaults {
+    name: "WMShellFlickerTestsDefault",
+    manifest: "manifests/AndroidManifest.xml",
+    test_config_template: "AndroidTestTemplate.xml",
     platform_apis: true,
     certificate: "platform",
     optimize: {
@@ -39,17 +92,85 @@
     test_suites: ["device-tests"],
     libs: ["android.test.runner"],
     static_libs: [
+        "wm-shell-flicker-utils",
         "androidx.test.ext.junit",
+        "flickertestapplib",
         "flickerlib",
-        "flickerlib-apphelpers",
         "flickerlib-helpers",
-        "truth-prebuilt",
-        "app-helpers-core",
+        "platform-test-annotations",
+        "wm-flicker-common-app-helpers",
+        "wm-flicker-common-assertions",
         "launcher-helper-lib",
         "launcher-aosp-tapl",
-        "wm-flicker-common-assertions",
-        "wm-flicker-common-app-helpers",
-        "platform-test-annotations",
-        "flickertestapplib",
+    ],
+    data: [
+        ":FlickerTestApp",
+        "trace_config/*",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsOther",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestOther.xml"],
+    package_name: "com.android.wm.shell.flicker",
+    instrumentation_target_package: "com.android.wm.shell.flicker",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    exclude_srcs: [
+        ":WMShellFlickerTestsBubbles-src",
+        ":WMShellFlickerTestsPip-src",
+        ":WMShellFlickerTestsSplitScreen-src",
+        ":WMShellFlickerServiceTests-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsBubbles",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestBubbles.xml"],
+    package_name: "com.android.wm.shell.flicker.bubbles",
+    instrumentation_target_package: "com.android.wm.shell.flicker.bubbles",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsBubbles-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsPip",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestPip.xml"],
+    package_name: "com.android.wm.shell.flicker.pip",
+    instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsPip-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsSplitScreen",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"],
+    package_name: "com.android.wm.shell.flicker.splitscreen",
+    instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsSplitScreen-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerServiceTests",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestService.xml"],
+    package_name: "com.android.wm.shell.flicker.service",
+    instrumentation_target_package: "com.android.wm.shell.flicker.service",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerServiceTests-src",
     ],
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
deleted file mode 100644
index 4721741..0000000
--- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.wm.shell.flicker">
-
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
-    <!-- Read and write traces from external storage -->
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <!-- Allow the test to write directly to /sdcard/ -->
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <!-- Write secure settings -->
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <!-- Capture screen contents -->
-    <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
-    <!-- Enable / Disable tracing !-->
-    <uses-permission android:name="android.permission.DUMP" />
-    <!-- Run layers trace -->
-    <uses-permission android:name="android.permission.HARDWARE_TEST"/>
-    <!-- Capture screen recording -->
-    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
-    <!-- Workaround grant runtime permission exception from b/152733071 -->
-    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
-    <uses-permission android:name="android.permission.READ_LOGS"/>
-    <!-- Force-stop test apps -->
-    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
-    <!-- Control test app's media session -->
-    <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
-    <!-- ATM.removeRootTasksWithActivityTypes() -->
-    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
-    <!-- Enable bubble notification-->
-    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
-
-    <!-- Allow the test to write directly to /sdcard/ -->
-    <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
-        <uses-library android:name="android.test.runner"/>
-
-        <service android:name=".NotificationListener"
-                 android:exported="true"
-                 android:label="WMShellTestsNotificationListenerService"
-                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
-            <intent-filter>
-                <action android:name="android.service.notification.NotificationListenerService" />
-            </intent-filter>
-        </service>
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.wm.shell.flicker"
-                     android:label="WindowManager Shell Flicker Tests">
-    </instrumentation>
-</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
deleted file mode 100644
index b5937ae..0000000
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright 2020 Google Inc. All Rights Reserved.
- -->
-<configuration description="Runs WindowManager Shell Flicker Tests">
-    <option name="test-tag" value="FlickerTests" />
-    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
-        <!-- keeps the screen on during tests -->
-        <option name="screen-always-on" value="on" />
-        <!-- prevents the phone from restarting -->
-        <option name="force-skip-system-props" value="true" />
-        <!-- set WM tracing verbose level to all -->
-        <option name="run-command" value="cmd window tracing level all" />
-        <!-- set WM tracing to frame (avoid incomplete states) -->
-        <option name="run-command" value="cmd window tracing frame" />
-        <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
-        <option name="run-command" value="pm disable com.google.android.internal.betterbug" />
-        <!-- ensure lock screen mode is swipe -->
-        <option name="run-command" value="locksettings set-disabled false" />
-        <!-- restart launcher to activate TAPL -->
-        <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
-        <!-- Ensure output directory is empty at the start -->
-        <option name="run-command" value="rm -rf /sdcard/flicker" />
-        <!-- Increase trace size: 20mb for WM and 80mb for SF -->
-        <option name="run-command" value="cmd window tracing size 20480" />
-        <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
-        <option name="run-command" value="settings put system show_touches 1" />
-        <option name="run-command" value="settings put system pointer_location 1" />
-        <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
-        <option name="teardown-command" value="settings delete system show_touches" />
-        <option name="teardown-command" value="settings delete system pointer_location" />
-        <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true"/>
-        <option name="test-file-name" value="WMShellFlickerTests.apk"/>
-        <option name="test-file-name" value="FlickerTestApp.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="package" value="com.android.wm.shell.flicker"/>
-        <option name="shell-timeout" value="6600s" />
-        <option name="test-timeout" value="6000s" />
-        <option name="hidden-api-checks" value="false" />
-    </test>
-    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
-        <option name="directory-keys" value="/sdcard/flicker" />
-        <option name="collect-on-run-ended-only" value="true" />
-        <option name="clean-up" value="true" />
-    </metrics_collector>
-</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
new file mode 100644
index 0000000..c8a9637
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
+    <option name="test-tag" value="FlickerTests"/>
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false"/>
+
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <!-- keeps the screen on during tests -->
+        <option name="screen-always-on" value="on"/>
+        <!-- prevents the phone from restarting -->
+        <option name="force-skip-system-props" value="true"/>
+        <!-- set WM tracing verbose level to all -->
+        <option name="run-command" value="cmd window tracing level all"/>
+        <!-- set WM tracing to frame (avoid incomplete states) -->
+        <option name="run-command" value="cmd window tracing frame"/>
+        <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+        <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
+        <!-- ensure lock screen mode is swipe -->
+        <option name="run-command" value="locksettings set-disabled false"/>
+        <!-- restart launcher to activate TAPL -->
+        <option name="run-command"
+                value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
+        <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+        <option name="run-command" value="cmd window tracing size 20480"/>
+        <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="test-user-token" value="%TEST_USER%"/>
+        <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
+        <option name="run-command" value="settings put system show_touches 1"/>
+        <option name="run-command" value="settings put system pointer_location 1"/>
+        <option name="teardown-command"
+                value="settings delete secure show_ime_with_hard_keyboard"/>
+        <option name="teardown-command" value="settings delete system show_touches"/>
+        <option name="teardown-command" value="settings delete system pointer_location"/>
+        <option name="teardown-command"
+                value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true"/>
+        <option name="test-file-name" value="{MODULE}.apk"/>
+        <option name="test-file-name" value="FlickerTestApp.apk"/>
+    </target_preparer>
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file"
+                key="trace_config.textproto"
+                value="/data/misc/perfetto-traces/trace_config.textproto"
+        />
+        <!--Install the content provider automatically when we push some file in sdcard folder.-->
+        <!--Needed to avoid the installation during the test suite.-->
+        <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="{PACKAGE}"/>
+        <option name="shell-timeout" value="6600s"/>
+        <option name="test-timeout" value="6000s"/>
+        <option name="hidden-api-checks" value="false"/>
+        <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
+        <option name="instrumentation-arg"
+                key="perfetto_config_file"
+                value="trace_config.textproto"
+        />
+        <option name="instrumentation-arg" key="per_run" value="true"/>
+    </test>
+    <!-- Needed for pulling the collected trace config on to the host -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.wm.shell.flicker/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker.pip/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker.splitscreen/files"/>
+        <option name="directory-keys"
+            value="/data/user/0/com.android.server.wm.flicker.service/files"/>
+        <option name="collect-on-run-ended-only" value="true"/>
+        <option name="clean-up" value="true"/>
+    </metrics_collector>
+</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
new file mode 100644
index 0000000..6a87de4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.android.wm.shell.flicker">
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <!-- Read and write traces from external storage -->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <!-- Allow the test to write directly to /sdcard/ -->
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <!-- Write secure settings -->
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <!-- Capture screen contents -->
+    <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+    <!-- Enable / Disable tracing !-->
+    <uses-permission android:name="android.permission.DUMP" />
+    <!-- Run layers trace -->
+    <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+    <!-- Capture screen recording -->
+    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+    <!-- Workaround grant runtime permission exception from b/152733071 -->
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+    <uses-permission android:name="android.permission.READ_LOGS"/>
+    <!-- Force-stop test apps -->
+    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
+    <!-- Control test app's media session -->
+    <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
+    <!-- ATM.removeRootTasksWithActivityTypes() -->
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+    <!-- Enable bubble notification-->
+    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+
+    <!-- Allow the test to write directly to /sdcard/ -->
+    <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+
+        <service android:name=".NotificationListener"
+                 android:exported="true"
+                 android:label="WMShellTestsNotificationListenerService"
+                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService" />
+            </intent-filter>
+        </service>
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+    </application>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml
new file mode 100644
index 0000000..437871f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestBubbles.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.wm.shell.flicker.bubble">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker.bubble"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml
new file mode 100644
index 0000000..cf642f6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestOther.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.wm.shell.flicker">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml
new file mode 100644
index 0000000..5a8155a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestPip.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.wm.shell.flicker.pip">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker.pip"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml
new file mode 100644
index 0000000..c7aca1a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestService.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.wm.shell.flicker.service">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker.service"
+                     android:label="WindowManager Flicker Service Tests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml
new file mode 100644
index 0000000..887d8db
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifestSplitScreen.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.wm.shell.flicker.splitscreen">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.wm.shell.flicker.splitscreen"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
index e06e074..0f3e0f5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseBenchmarkTest.kt
@@ -19,14 +19,14 @@
 import android.app.Instrumentation
 import android.tools.device.flicker.junit.FlickerBuilderProvider
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.tapl.LauncherInstrumentation
 
 abstract class BaseBenchmarkTest
 @JvmOverloads
 constructor(
-    protected open val flicker: FlickerTest,
+    protected open val flicker: LegacyFlickerTest,
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
     protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
 ) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index c98c5a0..735fbfb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -18,9 +18,10 @@
 
 import android.app.Instrumentation
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.utils.ICommonAssertions
 
 /**
  * Base test class containing common assertions for [ComponentNameMatcher.NAV_BAR],
@@ -30,7 +31,7 @@
 abstract class BaseTest
 @JvmOverloads
 constructor(
-    override val flicker: FlickerTest,
+    override val flicker: LegacyFlickerTest,
     instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
     tapl: LauncherInstrumentation = LauncherInstrumentation()
 ) : BaseBenchmarkTest(flicker, instrumentation, tapl), ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
deleted file mode 100644
index 798cc95..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ /dev/null
@@ -1,455 +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.
- */
-
-@file:JvmName("CommonAssertions")
-
-package com.android.wm.shell.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.datatypes.Region
-import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
-import android.tools.common.flicker.subject.layers.LayersTraceSubject
-import android.tools.common.traces.component.IComponentMatcher
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.helpers.WindowUtils
-
-fun FlickerTest.appPairsDividerIsVisibleAtEnd() {
-    assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
-}
-
-fun FlickerTest.appPairsDividerIsInvisibleAtEnd() {
-    assertLayersEnd { this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
-}
-
-fun FlickerTest.appPairsDividerBecomesVisible() {
-    assertLayers {
-        this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
-            .then()
-            .isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
-    }
-}
-
-fun FlickerTest.splitScreenEntered(
-    component1: IComponentMatcher,
-    component2: IComponentMatcher,
-    fromOtherApp: Boolean,
-    appExistAtStart: Boolean = true
-) {
-    if (fromOtherApp) {
-        if (appExistAtStart) {
-            appWindowIsInvisibleAtStart(component1)
-        } else {
-            appWindowIsNotContainAtStart(component1)
-        }
-    } else {
-        appWindowIsVisibleAtStart(component1)
-    }
-    if (appExistAtStart) {
-        appWindowIsInvisibleAtStart(component2)
-    } else {
-        appWindowIsNotContainAtStart(component2)
-    }
-    splitScreenDividerIsInvisibleAtStart()
-
-    appWindowIsVisibleAtEnd(component1)
-    appWindowIsVisibleAtEnd(component2)
-    splitScreenDividerIsVisibleAtEnd()
-}
-
-fun FlickerTest.splitScreenDismissed(
-    component1: IComponentMatcher,
-    component2: IComponentMatcher,
-    toHome: Boolean
-) {
-    appWindowIsVisibleAtStart(component1)
-    appWindowIsVisibleAtStart(component2)
-    splitScreenDividerIsVisibleAtStart()
-
-    appWindowIsInvisibleAtEnd(component1)
-    if (toHome) {
-        appWindowIsInvisibleAtEnd(component2)
-    } else {
-        appWindowIsVisibleAtEnd(component2)
-    }
-    splitScreenDividerIsInvisibleAtEnd()
-}
-
-fun FlickerTest.splitScreenDividerIsVisibleAtStart() {
-    assertLayersStart { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
-}
-
-fun FlickerTest.splitScreenDividerIsVisibleAtEnd() {
-    assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
-}
-
-fun FlickerTest.splitScreenDividerIsInvisibleAtStart() {
-    assertLayersStart { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
-}
-
-fun FlickerTest.splitScreenDividerIsInvisibleAtEnd() {
-    assertLayersEnd { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
-}
-
-fun FlickerTest.splitScreenDividerBecomesVisible() {
-    layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
-}
-
-fun FlickerTest.splitScreenDividerBecomesInvisible() {
-    assertLayers {
-        this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
-            .then()
-            .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
-    }
-}
-
-fun FlickerTest.layerBecomesVisible(component: IComponentMatcher) {
-    assertLayers { this.isInvisible(component).then().isVisible(component) }
-}
-
-fun FlickerTest.layerBecomesInvisible(component: IComponentMatcher) {
-    assertLayers { this.isVisible(component).then().isInvisible(component) }
-}
-
-fun FlickerTest.layerIsVisibleAtEnd(component: IComponentMatcher) {
-    assertLayersEnd { this.isVisible(component) }
-}
-
-fun FlickerTest.layerKeepVisible(component: IComponentMatcher) {
-    assertLayers { this.isVisible(component) }
-}
-
-fun FlickerTest.splitAppLayerBoundsBecomesVisible(
-    component: IComponentMatcher,
-    landscapePosLeft: Boolean,
-    portraitPosTop: Boolean
-) {
-    assertLayers {
-        this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
-            .then()
-            .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
-            .then()
-            .splitAppLayerBoundsSnapToDivider(
-                component,
-                landscapePosLeft,
-                portraitPosTop,
-                scenario.endRotation
-            )
-    }
-}
-
-fun FlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) {
-    assertLayers {
-        this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
-            .then()
-            .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
-            .then()
-            // TODO(b/245472831): Verify the component should snap to divider.
-            .isVisible(component)
-    }
-}
-
-fun FlickerTest.splitAppLayerBoundsBecomesInvisible(
-    component: IComponentMatcher,
-    landscapePosLeft: Boolean,
-    portraitPosTop: Boolean
-) {
-    assertLayers {
-        this.splitAppLayerBoundsSnapToDivider(
-                component,
-                landscapePosLeft,
-                portraitPosTop,
-                scenario.endRotation
-            )
-            .then()
-            .isVisible(component, true)
-            .then()
-            .isInvisible(component)
-    }
-}
-
-fun FlickerTest.splitAppLayerBoundsIsVisibleAtEnd(
-    component: IComponentMatcher,
-    landscapePosLeft: Boolean,
-    portraitPosTop: Boolean
-) {
-    assertLayersEnd {
-        splitAppLayerBoundsSnapToDivider(
-            component,
-            landscapePosLeft,
-            portraitPosTop,
-            scenario.endRotation
-        )
-    }
-}
-
-fun FlickerTest.splitAppLayerBoundsKeepVisible(
-    component: IComponentMatcher,
-    landscapePosLeft: Boolean,
-    portraitPosTop: Boolean
-) {
-    assertLayers {
-        splitAppLayerBoundsSnapToDivider(
-            component,
-            landscapePosLeft,
-            portraitPosTop,
-            scenario.endRotation
-        )
-    }
-}
-
-fun FlickerTest.splitAppLayerBoundsChanges(
-    component: IComponentMatcher,
-    landscapePosLeft: Boolean,
-    portraitPosTop: Boolean
-) {
-    assertLayers {
-        if (landscapePosLeft) {
-            splitAppLayerBoundsSnapToDivider(
-                component,
-                landscapePosLeft,
-                portraitPosTop,
-                scenario.endRotation
-            )
-        } else {
-            splitAppLayerBoundsSnapToDivider(
-                    component,
-                    landscapePosLeft,
-                    portraitPosTop,
-                    scenario.endRotation
-                )
-                .then()
-                .isInvisible(component)
-                .then()
-                .splitAppLayerBoundsSnapToDivider(
-                    component,
-                    landscapePosLeft,
-                    portraitPosTop,
-                    scenario.endRotation
-                )
-        }
-    }
-}
-
-fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider(
-    component: IComponentMatcher,
-    landscapePosLeft: Boolean,
-    portraitPosTop: Boolean,
-    rotation: Rotation
-): LayersTraceSubject {
-    return invoke("splitAppLayerBoundsSnapToDivider") {
-        it.splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, rotation)
-    }
-}
-
-fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
-    component: IComponentMatcher,
-    landscapePosLeft: Boolean,
-    portraitPosTop: Boolean,
-    rotation: Rotation
-): LayerTraceEntrySubject {
-    val displayBounds = WindowUtils.getDisplayBounds(rotation)
-    return invoke {
-        val dividerRegion =
-            layer(SPLIT_SCREEN_DIVIDER_COMPONENT)?.visibleRegion?.region
-                ?: error("$SPLIT_SCREEN_DIVIDER_COMPONENT component not found")
-        visibleRegion(component)
-            .coversAtMost(
-                if (displayBounds.width > displayBounds.height) {
-                    if (landscapePosLeft) {
-                        Region.from(
-                            0,
-                            0,
-                            (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
-                            displayBounds.bounds.bottom
-                        )
-                    } else {
-                        Region.from(
-                            (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
-                            0,
-                            displayBounds.bounds.right,
-                            displayBounds.bounds.bottom
-                        )
-                    }
-                } else {
-                    if (portraitPosTop) {
-                        Region.from(
-                            0,
-                            0,
-                            displayBounds.bounds.right,
-                            (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2
-                        )
-                    } else {
-                        Region.from(
-                            0,
-                            (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2,
-                            displayBounds.bounds.right,
-                            displayBounds.bounds.bottom
-                        )
-                    }
-                }
-            )
-    }
-}
-
-fun FlickerTest.appWindowBecomesVisible(component: IComponentMatcher) {
-    assertWm {
-        this.isAppWindowInvisible(component)
-            .then()
-            .notContains(component, isOptional = true)
-            .then()
-            .isAppWindowInvisible(component, isOptional = true)
-            .then()
-            .isAppWindowVisible(component)
-    }
-}
-
-fun FlickerTest.appWindowBecomesInvisible(component: IComponentMatcher) {
-    assertWm { this.isAppWindowVisible(component).then().isAppWindowInvisible(component) }
-}
-
-fun FlickerTest.appWindowIsVisibleAtStart(component: IComponentMatcher) {
-    assertWmStart { this.isAppWindowVisible(component) }
-}
-
-fun FlickerTest.appWindowIsVisibleAtEnd(component: IComponentMatcher) {
-    assertWmEnd { this.isAppWindowVisible(component) }
-}
-
-fun FlickerTest.appWindowIsInvisibleAtStart(component: IComponentMatcher) {
-    assertWmStart { this.isAppWindowInvisible(component) }
-}
-
-fun FlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) {
-    assertWmEnd { this.isAppWindowInvisible(component) }
-}
-
-fun FlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) {
-    assertWmStart { this.notContains(component) }
-}
-
-fun FlickerTest.appWindowKeepVisible(component: IComponentMatcher) {
-    assertWm { this.isAppWindowVisible(component) }
-}
-
-fun FlickerTest.dockedStackDividerIsVisibleAtEnd() {
-    assertLayersEnd { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) }
-}
-
-fun FlickerTest.dockedStackDividerBecomesVisible() {
-    assertLayers {
-        this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
-            .then()
-            .isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
-    }
-}
-
-fun FlickerTest.dockedStackDividerBecomesInvisible() {
-    assertLayers {
-        this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
-            .then()
-            .isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
-    }
-}
-
-fun FlickerTest.dockedStackDividerNotExistsAtEnd() {
-    assertLayersEnd { this.notContains(DOCKED_STACK_DIVIDER_COMPONENT) }
-}
-
-fun FlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd(
-    rotation: Rotation,
-    primaryComponent: IComponentMatcher
-) {
-    assertLayersEnd {
-        val dividerRegion =
-            layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT)?.visibleRegion?.region
-                ?: error("$APP_PAIR_SPLIT_DIVIDER_COMPONENT component not found")
-        visibleRegion(primaryComponent).overlaps(getPrimaryRegion(dividerRegion, rotation))
-    }
-}
-
-fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd(
-    rotation: Rotation,
-    primaryComponent: IComponentMatcher
-) {
-    assertLayersEnd {
-        val dividerRegion =
-            layer(DOCKED_STACK_DIVIDER_COMPONENT)?.visibleRegion?.region
-                ?: error("$DOCKED_STACK_DIVIDER_COMPONENT component not found")
-        visibleRegion(primaryComponent).overlaps(getPrimaryRegion(dividerRegion, rotation))
-    }
-}
-
-fun FlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd(
-    rotation: Rotation,
-    secondaryComponent: IComponentMatcher
-) {
-    assertLayersEnd {
-        val dividerRegion =
-            layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT)?.visibleRegion?.region
-                ?: error("$APP_PAIR_SPLIT_DIVIDER_COMPONENT component not found")
-        visibleRegion(secondaryComponent).overlaps(getSecondaryRegion(dividerRegion, rotation))
-    }
-}
-
-fun FlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd(
-    rotation: Rotation,
-    secondaryComponent: IComponentMatcher
-) {
-    assertLayersEnd {
-        val dividerRegion =
-            layer(DOCKED_STACK_DIVIDER_COMPONENT)?.visibleRegion?.region
-                ?: error("$DOCKED_STACK_DIVIDER_COMPONENT component not found")
-        visibleRegion(secondaryComponent).overlaps(getSecondaryRegion(dividerRegion, rotation))
-    }
-}
-
-fun getPrimaryRegion(dividerRegion: Region, rotation: Rotation): Region {
-    val displayBounds = WindowUtils.getDisplayBounds(rotation)
-    return if (rotation.isRotated()) {
-        Region.from(
-            0,
-            0,
-            dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
-            displayBounds.bounds.bottom
-        )
-    } else {
-        Region.from(
-            0,
-            0,
-            displayBounds.bounds.right,
-            dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset
-        )
-    }
-}
-
-fun getSecondaryRegion(dividerRegion: Region, rotation: Rotation): Region {
-    val displayBounds = WindowUtils.getDisplayBounds(rotation)
-    return if (rotation.isRotated()) {
-        Region.from(
-            dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset,
-            0,
-            displayBounds.bounds.right,
-            displayBounds.bounds.bottom
-        )
-    } else {
-        Region.from(
-            0,
-            dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
-            displayBounds.bounds.right,
-            displayBounds.bounds.bottom
-        )
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
deleted file mode 100644
index 3bc1e2a..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
+++ /dev/null
@@ -1,35 +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.
- */
-
-@file:JvmName("CommonConstants")
-
-package com.android.wm.shell.flicker
-
-import android.tools.common.traces.component.ComponentNameMatcher
-
-const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
-const val LAUNCHER_UI_PACKAGE_NAME = "com.google.android.apps.nexuslauncher"
-val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentNameMatcher("", "AppPairSplitDivider#")
-val DOCKED_STACK_DIVIDER_COMPONENT = ComponentNameMatcher("", "DockedStackDivider#")
-val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentNameMatcher("", "StageCoordinatorSplitDivider#")
-val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
-
-enum class Direction {
-    UP,
-    DOWN,
-    LEFT,
-    RIGHT
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt
deleted file mode 100644
index 02d9a056..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/ICommonAssertions.kt
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker
-
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerTest
-import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
-import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
-import com.android.server.wm.flicker.statusBarLayerPositionAtStartAndEnd
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
-import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible
-import org.junit.Assume
-import org.junit.Test
-
-interface ICommonAssertions {
-    val flicker: FlickerTest
-
-    /** Checks that all parts of the screen are covered during the transition */
-    @Presubmit @Test fun entireScreenCovered() = flicker.entireScreenCovered()
-
-    /**
-     * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
-     */
-    @Presubmit
-    @Test
-    fun navBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    /**
-     * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the
-     * transition
-     */
-    @Presubmit
-    @Test
-    fun navBarLayerPositionAtStartAndEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarLayerPositionAtStartAndEnd()
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition
-     *
-     * Note: Phones only
-     */
-    @Presubmit
-    @Test
-    fun navBarWindowIsAlwaysVisible() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarWindowIsAlwaysVisible()
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible during the whole transition
-     */
-    @Presubmit
-    @Test
-    fun taskBarLayerIsVisibleAtStartAndEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
-        flicker.taskBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
-     *
-     * Note: Large screen only
-     */
-    @Presubmit
-    @Test
-    fun taskBarWindowIsAlwaysVisible() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
-        flicker.taskBarWindowIsAlwaysVisible()
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible during the whole
-     * transition
-     */
-    @Presubmit
-    @Test
-    fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd()
-
-    /**
-     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
-     * transition
-     */
-    @Presubmit
-    @Test
-    fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd()
-
-    /**
-     * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
-     * transition
-     */
-    @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible()
-
-    /**
-     * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
-     * entries.
-     */
-    @Presubmit
-    @Test
-    fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
-    }
-
-    /**
-     * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive
-     * entries.
-     */
-    @Presubmit
-    @Test
-    fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
-        flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt
deleted file mode 100644
index 87b94ff..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt
+++ /dev/null
@@ -1,61 +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.wm.shell.flicker
-
-import android.app.Instrumentation
-import android.content.Context
-import android.provider.Settings
-import android.util.Log
-import com.android.compatibility.common.util.SystemUtil
-import java.io.IOException
-
-object MultiWindowUtils {
-    private fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
-        try {
-            SystemUtil.runShellCommand(instrumentation, cmd)
-        } catch (e: IOException) {
-            Log.e(MultiWindowUtils::class.simpleName, "executeShellCommand error! $e")
-        }
-    }
-
-    fun getDevEnableNonResizableMultiWindow(context: Context): Int =
-        Settings.Global.getInt(
-            context.contentResolver,
-            Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW
-        )
-
-    fun setDevEnableNonResizableMultiWindow(context: Context, configValue: Int) =
-        Settings.Global.putInt(
-            context.contentResolver,
-            Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
-            configValue
-        )
-
-    fun setSupportsNonResizableMultiWindow(instrumentation: Instrumentation, configValue: Int) =
-        executeShellCommand(
-            instrumentation,
-            createConfigSupportsNonResizableMultiWindowCommand(configValue)
-        )
-
-    fun resetMultiWindowConfig(instrumentation: Instrumentation) =
-        executeShellCommand(instrumentation, resetMultiWindowConfigCommand)
-
-    private fun createConfigSupportsNonResizableMultiWindowCommand(configValue: Int): String =
-        "wm set-multi-window-config --supportsNonResizable $configValue"
-
-    private const val resetMultiWindowConfigCommand: String = "wm reset-multi-window-config"
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt
deleted file mode 100644
index e0ef924..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt
+++ /dev/null
@@ -1,101 +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.wm.shell.flicker
-
-import android.service.notification.NotificationListenerService
-import android.service.notification.StatusBarNotification
-import android.util.Log
-import com.android.compatibility.common.util.SystemUtil.runShellCommand
-
-class NotificationListener : NotificationListenerService() {
-    private val notifications: MutableMap<Any, StatusBarNotification> = mutableMapOf()
-
-    override fun onNotificationPosted(sbn: StatusBarNotification) {
-        if (DEBUG) Log.d(TAG, "onNotificationPosted: $sbn")
-        notifications[sbn.key] = sbn
-    }
-
-    override fun onNotificationRemoved(sbn: StatusBarNotification) {
-        if (DEBUG) Log.d(TAG, "onNotificationRemoved: $sbn")
-        notifications.remove(sbn.key)
-    }
-
-    override fun onListenerConnected() {
-        if (DEBUG) Log.d(TAG, "onListenerConnected")
-        instance = this
-    }
-
-    override fun onListenerDisconnected() {
-        if (DEBUG) Log.d(TAG, "onListenerDisconnected")
-        instance = null
-        notifications.clear()
-    }
-
-    companion object {
-        private const val DEBUG = false
-        private const val TAG = "WMShellFlickerTests_NotificationListener"
-
-        private const val CMD_NOTIFICATION_ALLOW_LISTENER = "cmd notification allow_listener %s"
-        private const val CMD_NOTIFICATION_DISALLOW_LISTENER =
-            "cmd notification disallow_listener %s"
-        private const val COMPONENT_NAME = "com.android.wm.shell.flicker/.NotificationListener"
-
-        private var instance: NotificationListener? = null
-
-        fun startNotificationListener(): Boolean {
-            if (instance != null) {
-                return true
-            }
-
-            runShellCommand(CMD_NOTIFICATION_ALLOW_LISTENER.format(COMPONENT_NAME))
-            return wait { instance != null }
-        }
-
-        fun stopNotificationListener(): Boolean {
-            if (instance == null) {
-                return true
-            }
-
-            runShellCommand(CMD_NOTIFICATION_DISALLOW_LISTENER.format(COMPONENT_NAME))
-            return wait { instance == null }
-        }
-
-        fun findNotification(
-            predicate: (StatusBarNotification) -> Boolean
-        ): StatusBarNotification? {
-            instance?.run {
-                return notifications.values.firstOrNull(predicate)
-            }
-                ?: throw IllegalStateException("NotificationListenerService is not connected")
-        }
-
-        fun waitForNotificationToAppear(
-            predicate: (StatusBarNotification) -> Boolean
-        ): StatusBarNotification? {
-            instance?.let {
-                return waitForResult(extractor = { it.notifications.values.firstOrNull(predicate) })
-                    .second
-            }
-                ?: throw IllegalStateException("NotificationListenerService is not connected")
-        }
-
-        fun waitForNotificationToDisappear(predicate: (StatusBarNotification) -> Boolean): Boolean {
-            return instance?.let { wait { it.notifications.values.none(predicate) } }
-                ?: throw IllegalStateException("NotificationListenerService is not connected")
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
deleted file mode 100644
index 556cb06..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
+++ /dev/null
@@ -1,47 +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.
- */
-
-@file:JvmName("WaitUtils")
-
-package com.android.wm.shell.flicker
-
-import android.os.SystemClock
-
-private const val DEFAULT_TIMEOUT = 10000L
-private const val DEFAULT_POLL_INTERVAL = 1000L
-
-fun wait(condition: () -> Boolean): Boolean {
-    val (success, _) = waitForResult(extractor = condition, validator = { it })
-    return success
-}
-
-fun <R> waitForResult(
-    timeout: Long = DEFAULT_TIMEOUT,
-    interval: Long = DEFAULT_POLL_INTERVAL,
-    extractor: () -> R,
-    validator: (R) -> Boolean = { it != null }
-): Pair<Boolean, R?> {
-    val startTime = SystemClock.uptimeMillis()
-    do {
-        val result = extractor()
-        if (validator(result)) {
-            return (true to result)
-        }
-        SystemClock.sleep(interval)
-    } while (SystemClock.uptimeMillis() - startTime < timeout)
-
-    return (false to null)
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
index 6178156..36acb58 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -17,28 +17,29 @@
 package com.android.wm.shell.flicker.appcompat
 
 import android.content.Context
-import android.system.helpers.CommandsHelper
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import com.android.server.wm.flicker.helpers.setRotation
+import android.tools.device.flicker.legacy.FlickerTestData
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import com.android.server.wm.flicker.helpers.LetterboxAppHelper
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.legacy.IFlickerTestData
+import com.android.server.wm.flicker.helpers.setRotation
 import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerKeepVisible
-import org.junit.After
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtStart
+import com.android.wm.shell.flicker.utils.appWindowKeepVisible
+import com.android.wm.shell.flicker.utils.layerKeepVisible
+
 import org.junit.Assume
 import org.junit.Before
-import org.junit.runners.Parameterized
+import org.junit.Rule
 
-abstract class BaseAppCompat(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     protected val context: Context = instrumentation.context
     protected val letterboxApp = LetterboxAppHelper(instrumentation)
-    lateinit var cmdHelper: CommandsHelper
-    private lateinit var letterboxStyle: HashMap<String, String>
+
+    @JvmField
+    @Rule
+    val letterboxRule: LetterboxRule = LetterboxRule()
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -48,62 +49,17 @@
                 letterboxApp.launchViaIntent(wmHelper)
                 setEndRotation()
             }
-            teardown {
-                letterboxApp.exit(wmHelper)
-            }
+            teardown { letterboxApp.exit(wmHelper) }
         }
 
     @Before
     fun before() {
-        cmdHelper = CommandsHelper.getInstance(instrumentation)
-        Assume.assumeTrue(tapl.isTablet && isIgnoreOrientationRequest())
-        letterboxStyle = mapLetterboxStyle()
-        setLetterboxEducationEnabled(false)
+        Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest)
     }
 
-    @After
-    fun after() {
-        resetLetterboxEducationEnabled()
-    }
+    fun FlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation)
 
-    private fun mapLetterboxStyle(): HashMap<String, String> {
-        val res = cmdHelper.executeShellCommand("wm get-letterbox-style")
-        val lines = res.lines()
-        val map = HashMap<String, String>()
-        for (line in lines) {
-            val keyValuePair = line.split(":")
-            if (keyValuePair.size == 2) {
-                val key = keyValuePair[0].trim()
-                map[key] = keyValuePair[1].trim()
-            }
-        }
-        return map
-    }
-
-    private fun getLetterboxStyle(): HashMap<String, String> {
-        if (!::letterboxStyle.isInitialized) {
-            letterboxStyle = mapLetterboxStyle()
-        }
-        return letterboxStyle
-    }
-
-    private fun resetLetterboxEducationEnabled() {
-        val enabled = getLetterboxStyle().getValue("Is education enabled")
-        cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled")
-    }
-
-    private fun setLetterboxEducationEnabled(enabled: Boolean) {
-        cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled")
-    }
-
-    private fun isIgnoreOrientationRequest(): Boolean {
-        val res = cmdHelper.executeShellCommand("wm get-ignore-orientation-request")
-        return res != null && res.contains("true")
-    }
-
-    fun IFlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation)
-
-    fun IFlickerTestData.setEndRotation() = setRotation(flicker.scenario.endRotation)
+    fun FlickerTestData.setEndRotation() = setRotation(flicker.scenario.endRotation)
 
     /** Checks that app entering letterboxed state have rounded corners */
     fun assertLetterboxAppAtStartHasRoundedCorners() {
@@ -118,7 +74,7 @@
 
     /** Only run on tests with config_letterboxActivityCornersRadius != 0 in devices */
     private fun assumeLetterboxRoundedCornersEnabled() {
-        Assume.assumeTrue(getLetterboxStyle().getValue("Corner radius") != "0")
+        Assume.assumeTrue(letterboxRule.hasCornerRadius)
     }
 
     fun assertLetterboxAppVisibleAtStartAndEnd() {
@@ -126,25 +82,21 @@
         flicker.appWindowIsVisibleAtEnd(letterboxApp)
     }
 
+    fun assertLetterboxAppKeepVisible() {
+        assertLetterboxAppWindowKeepVisible()
+        assertLetterboxAppLayerKeepVisible()
+    }
+
     fun assertAppLetterboxedAtEnd() =
-            flicker.assertLayersEnd { isVisible(ComponentNameMatcher.LETTERBOX) }
+        flicker.assertLayersEnd { isVisible(ComponentNameMatcher.LETTERBOX) }
 
     fun assertAppLetterboxedAtStart() =
-            flicker.assertLayersStart { isVisible(ComponentNameMatcher.LETTERBOX) }
+        flicker.assertLayersStart { isVisible(ComponentNameMatcher.LETTERBOX) }
+
+    fun assertAppStaysLetterboxed() =
+        flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) }
 
     fun assertLetterboxAppLayerKeepVisible() = flicker.layerKeepVisible(letterboxApp)
 
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.rotationTests()
-        }
-    }
+    fun assertLetterboxAppWindowKeepVisible() = flicker.appWindowKeepVisible(letterboxApp)
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
new file mode 100644
index 0000000..5a1136f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.app.Instrumentation
+import android.system.helpers.CommandsHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * JUnit Rule to handle letterboxStyles and states
+ */
+class LetterboxRule(
+        private val withLetterboxEducationEnabled: Boolean = false,
+        private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+        private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation)
+) : TestRule {
+
+    private val execAdb: (String) -> String = {cmd -> cmdHelper.executeShellCommand(cmd)}
+    private lateinit var _letterboxStyle: MutableMap<String, String>
+
+    val letterboxStyle: Map<String, String>
+        get() {
+            if (!::_letterboxStyle.isInitialized) {
+                _letterboxStyle = mapLetterboxStyle()
+            }
+            return _letterboxStyle
+        }
+
+    val cornerRadius: Int?
+        get() = asInt(letterboxStyle["Corner radius"])
+
+    val hasCornerRadius: Boolean
+        get() {
+            val radius = cornerRadius
+            return radius != null && radius > 0
+        }
+
+    val isIgnoreOrientationRequest: Boolean
+        get() = execAdb("wm get-ignore-orientation-request")?.contains("true") ?: false
+
+    override fun apply(base: Statement?, description: Description?): Statement {
+        resetLetterboxStyle()
+        _letterboxStyle = mapLetterboxStyle()
+        val isLetterboxEducationEnabled = _letterboxStyle.getValue("Is education enabled")
+        var hasLetterboxEducationStateChanged = false
+        if ("$withLetterboxEducationEnabled" != isLetterboxEducationEnabled) {
+            hasLetterboxEducationStateChanged = true
+            execAdb("wm set-letterbox-style --isEducationEnabled " +
+                    withLetterboxEducationEnabled)
+        }
+        return try {
+            object : Statement() {
+                @Throws(Throwable::class)
+                override fun evaluate() {
+                    base!!.evaluate()
+                }
+            }
+        } finally {
+            if (hasLetterboxEducationStateChanged) {
+                execAdb("wm set-letterbox-style --isEducationEnabled " +
+                        isLetterboxEducationEnabled
+                )
+            }
+            resetLetterboxStyle()
+        }
+    }
+
+    private fun mapLetterboxStyle(): HashMap<String, String> {
+        val res = execAdb("wm get-letterbox-style")
+        val lines = res.lines()
+        val map = HashMap<String, String>()
+        for (line in lines) {
+            val keyValuePair = line.split(":")
+            if (keyValuePair.size == 2) {
+                val key = keyValuePair[0].trim()
+                map[key] = keyValuePair[1].trim()
+            }
+        }
+        return map
+    }
+
+    private fun resetLetterboxStyle() {
+        execAdb("wm reset-letterbox-style")
+    }
+
+    private fun asInt(str: String?): Int? = try {
+        str?.toInt()
+    } catch (e: NumberFormatException) {
+        null
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index c2141a3..67d5718 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -17,10 +17,12 @@
 package com.android.wm.shell.flicker.appcompat
 
 import android.platform.test.annotations.Postsubmit
+import android.tools.common.flicker.assertions.FlickerTest
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -29,7 +31,7 @@
 /**
  * Test launching app in size compat mode.
  *
- * To run this test: `atest WMShellFlickerTests:OpenAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsOther:OpenAppInSizeCompatModeTest`
  *
  * Actions:
  * ```
@@ -45,7 +47,7 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class OpenAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) {
+class OpenAppInSizeCompatModeTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -70,9 +72,7 @@
     @Test
     fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtEndHasRoundedCorners()
 
-    @Postsubmit
-    @Test
-    fun appIsLetterboxedAtEnd() = assertAppLetterboxedAtEnd()
+    @Postsubmit @Test fun appIsLetterboxedAtEnd() = assertAppLetterboxedAtEnd()
 
     /**
      * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
@@ -90,4 +90,18 @@
                 .isInvisible(ComponentNameMatcher.ROTATION)
         }
     }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.rotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return LegacyFlickerTestFactory.rotationTests()
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
new file mode 100644
index 0000000..e6ca261
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching app in size compat mode.
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:OpenTransparentActivityTest`
+ *
+ * Actions:
+ * ```
+ *     Launch a letteboxed app and then a transparent activity from it. We test the bounds
+ *     are the same.
+ * ```
+ *
+ * Notes:
+ * ```
+ *     Some default assertions (e.g., nav bar, status bar and screen covered)
+ *     are inherited [BaseTest]
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseAppCompat(flicker) {
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                letterboxTranslucentLauncherApp.launchViaIntent(wmHelper)
+            }
+            transitions {
+                waitAndGetLaunchTransparent()?.click() ?: error("Launch Transparent not found")
+            }
+            teardown {
+                letterboxTranslucentApp.exit(wmHelper)
+                letterboxTranslucentLauncherApp.exit(wmHelper)
+            }
+        }
+
+    /**
+     * Checks the transparent activity is launched on top of the opaque one
+     */
+    @Postsubmit
+    @Test
+    fun translucentActivityIsLaunchedOnTopOfOpaqueActivity() {
+        flicker.assertWm {
+            this.isAppWindowOnTop(letterboxTranslucentLauncherApp)
+                .then()
+                .isAppWindowOnTop(letterboxTranslucentApp)
+        }
+    }
+
+    /**
+     * Checks that the activity is letterboxed
+     */
+    @Postsubmit
+    @Test
+    fun translucentActivityIsLetterboxed() {
+        flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) }
+    }
+
+    /**
+     * Checks that the translucent activity inherits bounds from the opaque one.
+     */
+    @Postsubmit
+    @Test
+    fun translucentActivityInheritsBoundsFromOpaqueActivity() {
+        flicker.assertLayersEnd {
+            this.visibleRegion(letterboxTranslucentApp)
+                .coversExactly(visibleRegion(letterboxTranslucentLauncherApp).region)
+        }
+    }
+
+    /**
+     * Checks that the translucent activity has rounded corners
+     */
+    @Postsubmit
+    @Test
+    fun translucentActivityHasRoundedCorners() {
+        flicker.assertLayersEnd {
+            this.hasRoundedCorners(letterboxTranslucentApp)
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return LegacyFlickerTestFactory
+                .nonRotationTests(supportedRotations = listOf(Rotation.ROTATION_90))
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
new file mode 100644
index 0000000..d3f3c5b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.datatypes.Rect
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switching to letterboxed app from launcher
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:QuickSwitchLauncherToLetterboxAppTest`
+ *
+ * Actions:
+ * ```
+ *     Launch a letterboxed app
+ *     Navigate home to show launcher
+ *     Swipe right from the bottom of the screen to quick switch back to the app
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) :
+    BaseAppCompat(flicker) {
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+
+            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+
+            letterboxApp.launchViaIntent(wmHelper)
+            tapl.goHome()
+            wmHelper
+                .StateSyncBuilder()
+                .withHomeActivityVisible()
+                .withWindowSurfaceDisappeared(letterboxApp)
+                .waitForAndVerify()
+
+            startDisplayBounds =
+                wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+        }
+        transitions {
+            tapl.workspace.quickSwitchToPreviousApp()
+            wmHelper
+                .StateSyncBuilder()
+                .withFullScreenApp(letterboxApp)
+                .withNavOrTaskBarVisible()
+                .withStatusBarVisible()
+                .waitForAndVerify()
+        }
+        teardown { letterboxApp.exit(wmHelper) }
+    }
+
+    /**
+     * Checks that [letterboxApp] is the top window at the end of the transition once we have fully
+     * quick switched from the launcher back to the [letterboxApp].
+     */
+    @Postsubmit
+    @Test
+    fun endsWithAppBeingOnTop() {
+        flicker.assertWmEnd { this.isAppWindowOnTop(letterboxApp) }
+    }
+
+    /** Checks that the transition starts with the home activity being tagged as visible. */
+    @Postsubmit
+    @Test
+    fun startsWithHomeActivityFlaggedVisible() {
+        flicker.assertWmStart { this.isHomeActivityVisible() }
+    }
+
+    /**
+     * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] windows
+     * filling/covering exactly display size
+     */
+    @Postsubmit
+    @Test
+    fun startsWithLauncherWindowsCoverFullScreen() {
+        flicker.assertWmStart {
+            this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
+        }
+    }
+
+    /**
+     * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] layers
+     * filling/covering exactly the display size.
+     */
+    @Postsubmit
+    @Test
+    fun startsWithLauncherLayersCoverFullScreen() {
+        flicker.assertLayersStart {
+            this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
+        }
+    }
+
+    /**
+     * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] being the top
+     * window.
+     */
+    @Postsubmit
+    @Test
+    fun startsWithLauncherBeingOnTop() {
+        flicker.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) }
+    }
+
+    /**
+     * Checks that the transition ends with the home activity being flagged as not visible. By this
+     * point we should have quick switched away from the launcher back to the [letterboxApp].
+     */
+    @Postsubmit
+    @Test
+    fun endsWithHomeActivityFlaggedInvisible() {
+        flicker.assertWmEnd { this.isHomeActivityInvisible() }
+    }
+
+    /**
+     * Checks that [letterboxApp]'s window starts off invisible and becomes visible at some point
+     * before the end of the transition and then stays visible until the end of the transition.
+     */
+    @Postsubmit
+    @Test
+    fun appWindowBecomesAndStaysVisible() {
+        flicker.assertWm {
+            this.isAppWindowInvisible(letterboxApp).then().isAppWindowVisible(letterboxApp)
+        }
+    }
+
+    /**
+     * Checks that [letterboxApp]'s layer starts off invisible and becomes visible at some point
+     * before the end of the transition and then stays visible until the end of the transition.
+     */
+    @Postsubmit
+    @Test
+    fun appLayerBecomesAndStaysVisible() {
+        flicker.assertLayers { this.isInvisible(letterboxApp).then().isVisible(letterboxApp) }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] window starts off visible and becomes
+     * invisible at some point before the end of the transition and then stays invisible until the
+     * end of the transition.
+     */
+    @Postsubmit
+    @Test
+    fun launcherWindowBecomesAndStaysInvisible() {
+        flicker.assertWm {
+            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] layer starts off visible and becomes
+     * invisible at some point before the end of the transition and then stays invisible until the
+     * end of the transition.
+     */
+    @Postsubmit
+    @Test
+    fun launcherLayerBecomesAndStaysInvisible() {
+        flicker.assertLayers {
+            this.isVisible(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isInvisible(ComponentNameMatcher.LAUNCHER)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] window is visible at least until the app
+     * window is visible. Ensures that at any point, either the launcher or [letterboxApp] windows
+     * are at least partially visible.
+     */
+    @Postsubmit
+    @Test
+    fun appWindowIsVisibleOnceLauncherWindowIsInvisible() {
+        flicker.assertWm {
+            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isAppWindowVisible(letterboxApp)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] layer is visible at least until the app layer
+     * is visible. Ensures that at any point, either the launcher or [letterboxApp] layers are at
+     * least partially visible.
+     */
+    @Postsubmit
+    @Test
+    fun appLayerIsVisibleOnceLauncherLayerIsInvisible() {
+        flicker.assertLayers {
+            this.isVisible(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isVisible(letterboxApp)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LETTERBOX] layer is visible as soon as the
+     * [letterboxApp] layer is visible at the end of the transition once we have fully quick
+     * switched from the launcher back to the [letterboxApp].
+     */
+    @Postsubmit
+    @Test
+    fun appAndLetterboxLayersBothVisibleOnceLauncherIsInvisible() {
+        flicker.assertLayers {
+            this.isVisible(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isVisible(letterboxApp)
+                .isVisible(ComponentNameMatcher.LETTERBOX)
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
+    companion object {
+        /** {@inheritDoc} */
+        private var startDisplayBounds = Rect.EMPTY
+
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return LegacyFlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
+                supportedRotations = listOf(Rotation.ROTATION_90)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
new file mode 100644
index 0000000..68fa8d2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.RequiresDevice
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a fixed portrait letterboxed app in landscape and repositioning to the right.
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:RepositionFixedPortraitAppTest`
+ *
+ * Actions:
+ *
+ *  ```
+ *      Launch a fixed portrait app in landscape to letterbox app
+ *      Double tap to the right to reposition app and wait for app to move
+ *  ```
+ *
+ * Notes:
+ *
+ *  ```
+ *      Some default assertions (e.g., nav bar, status bar and screen covered)
+ *      are inherited [BaseTest]
+ *  ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class RepositionFixedPortraitAppTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
+
+    val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation).bounds
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                setStartRotation()
+                letterboxApp.launchViaIntent(wmHelper)
+            }
+            transitions {
+                letterboxApp.repositionHorizontally(displayBounds, true)
+                letterboxApp.waitForAppToMoveHorizontallyTo(wmHelper, displayBounds, true)
+            }
+            teardown {
+                letterboxApp.repositionHorizontally(displayBounds, false)
+                letterboxApp.exit(wmHelper)
+            }
+        }
+
+    @Postsubmit
+    @Test
+    fun letterboxedAppHasRoundedCorners() = assertLetterboxAppAtEndHasRoundedCorners()
+
+    @Postsubmit @Test fun letterboxAppLayerKeepVisible() = assertLetterboxAppLayerKeepVisible()
+
+    @Postsubmit @Test fun appStaysLetterboxed() = assertAppStaysLetterboxed()
+
+    @Postsubmit @Test fun appKeepVisible() = assertLetterboxAppKeepVisible()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return LegacyFlickerTestFactory.nonRotationTests(
+                supportedRotations = listOf(Rotation.ROTATION_90)
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
index b0e1a42..fcb6931a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
@@ -17,9 +17,11 @@
 package com.android.wm.shell.flicker.appcompat
 
 import android.platform.test.annotations.Postsubmit
+import android.tools.common.flicker.assertions.FlickerTest
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import androidx.test.filters.RequiresDevice
 import org.junit.Test
@@ -29,7 +31,7 @@
 /**
  * Test restarting app in size compat mode.
  *
- * To run this test: `atest WMShellFlickerTests:RestartAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsOther:RestartAppInSizeCompatModeTest`
  *
  * Actions:
  * ```
@@ -46,7 +48,7 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class RestartAppInSizeCompatModeTest(flicker: FlickerTest) : BaseAppCompat(flicker) {
+class RestartAppInSizeCompatModeTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -69,13 +71,9 @@
         }
     }
 
-    @Postsubmit
-    @Test
-    fun appLayerKeepVisible() = assertLetterboxAppLayerKeepVisible()
+    @Postsubmit @Test fun appLayerKeepVisible() = assertLetterboxAppLayerKeepVisible()
 
-    @Postsubmit
-    @Test
-    fun appIsLetterboxedAtStart() = assertAppLetterboxedAtStart()
+    @Postsubmit @Test fun appIsLetterboxedAtStart() = assertAppLetterboxedAtStart()
 
     @Postsubmit
     @Test
@@ -88,4 +86,18 @@
         val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
         flicker.assertLayersEnd { visibleRegion(letterboxApp).coversAtMost(displayBounds) }
     }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.rotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTest> {
+            return LegacyFlickerTestFactory.rotationTests()
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
new file mode 100644
index 0000000..ea0392c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.content.Context
+import android.tools.device.flicker.legacy.FlickerTestData
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.helpers.FIND_TIMEOUT
+import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.BaseTest
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+
+abstract class TransparentBaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
+    protected val context: Context = instrumentation.context
+    protected val letterboxTranslucentLauncherApp = LetterboxAppHelper(
+        instrumentation,
+        launcherName = ActivityOptions.LaunchTransparentActivity.LABEL,
+        component = ActivityOptions.LaunchTransparentActivity.COMPONENT.toFlickerComponent()
+    )
+    protected val letterboxTranslucentApp = LetterboxAppHelper(
+        instrumentation,
+        launcherName = ActivityOptions.TransparentActivity.LABEL,
+        component = ActivityOptions.TransparentActivity.COMPONENT.toFlickerComponent()
+    )
+
+    @JvmField
+    @Rule
+    val letterboxRule: LetterboxRule = LetterboxRule()
+
+    @Before
+    fun before() {
+        Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest)
+    }
+
+    protected fun FlickerTestData.waitAndGetLaunchTransparent(): UiObject2? =
+        device.wait(
+            Until.findObject(By.text("Launch Transparent")),
+            FIND_TIMEOUT
+        )
+
+    protected fun FlickerTestData.goBack() = device.pressBack()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index bab81d7..5c7d1d8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -23,9 +23,9 @@
 import android.os.ServiceManager
 import android.tools.common.Rotation
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.legacy.IFlickerTestData
+import android.tools.device.flicker.legacy.FlickerTestData
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.SYSTEMUI_PACKAGE
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiObject2
@@ -35,7 +35,7 @@
 import org.junit.runners.Parameterized
 
 /** Base configurations for Bubble flicker tests */
-abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class BaseBubbleScreen(flicker: LegacyFlickerTest) : BaseTest(flicker) {
 
     protected val context: Context = instrumentation.context
     protected val testApp = LaunchBubbleHelper(instrumentation)
@@ -72,28 +72,31 @@
                     uid,
                     NotificationManager.BUBBLE_PREFERENCE_NONE
                 )
-                testApp.exit()
+                device.wait(
+                    Until.gone(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)),
+                    FIND_OBJECT_TIMEOUT
+                )
+                testApp.exit(wmHelper)
             }
 
             extraSpec(this)
         }
     }
 
-    protected fun IFlickerTestData.waitAndGetAddBubbleBtn(): UiObject2? =
+    protected fun FlickerTestData.waitAndGetAddBubbleBtn(): UiObject2? =
         device.wait(Until.findObject(By.text("Add Bubble")), FIND_OBJECT_TIMEOUT)
-    protected fun IFlickerTestData.waitAndGetCancelAllBtn(): UiObject2? =
+    protected fun FlickerTestData.waitAndGetCancelAllBtn(): UiObject2? =
         device.wait(Until.findObject(By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT)
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
 
-        const val FIND_OBJECT_TIMEOUT = 2000L
+        const val FIND_OBJECT_TIMEOUT = 4000L
         const val SYSTEM_UI_PACKAGE = SYSTEMUI_PACKAGE
         const val BUBBLE_RES_NAME = "bubble_view"
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
index 2474ecf..bc565bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
@@ -21,7 +21,7 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiObject2
@@ -44,7 +44,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FlakyTest(bugId = 217777115)
-open class ChangeActiveActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class ChangeActiveActivityFromBubbleTest(flicker: LegacyFlickerTest) :
+    BaseBubbleScreen(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
index bdfdad5..abc6b9f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTestCfArm.kt
@@ -17,11 +17,11 @@
 package com.android.wm.shell.flicker.bubble
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class ChangeActiveActivityFromBubbleTestCfArm(flicker: FlickerTest) :
+open class ChangeActiveActivityFromBubbleTestCfArm(flicker: LegacyFlickerTest) :
     ChangeActiveActivityFromBubbleTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
index 8474ce0..3f28ae8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt
@@ -19,9 +19,10 @@
 import android.content.Context
 import android.graphics.Point
 import android.platform.test.annotations.Presubmit
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.util.DisplayMetrics
 import android.view.WindowManager
 import androidx.test.filters.RequiresDevice
@@ -44,7 +45,7 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class DragToDismissBubbleScreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class DragToDismissBubbleScreenTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
 
     private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
     private val displaySize = DisplayMetrics()
@@ -73,4 +74,14 @@
     open fun testAppIsAlwaysVisible() {
         flicker.assertLayers { this.isVisible(testApp) }
     }
+
+    @Presubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        flicker.assertLayers {
+            this.visibleLayersShownMoreThanOneConsecutiveEntry(
+                LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + listOf(testApp)
+            )
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
index 62fa7b4..ee55eca 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTestCfArm.kt
@@ -17,11 +17,11 @@
 package com.android.wm.shell.flicker.bubble
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class DragToDismissBubbleScreenTestCfArm(flicker: FlickerTest) :
+class DragToDismissBubbleScreenTestCfArm(flicker: LegacyFlickerTest) :
     DragToDismissBubbleScreenTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
index 889e177..26aca18 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -21,7 +21,7 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.view.WindowInsets
 import android.view.WindowManager
 import androidx.test.filters.RequiresDevice
@@ -48,7 +48,8 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class OpenActivityFromBubbleOnLocksreenTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+class OpenActivityFromBubbleOnLocksreenTest(flicker: LegacyFlickerTest) :
+    BaseBubbleScreen(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -80,7 +81,7 @@
                 instrumentation.uiAutomation.syncInputTransactions()
                 val showBubble =
                     device.wait(
-                        Until.findObject(By.res("com.android.systemui", "bubble_view")),
+                        Until.findObject(By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)),
                         FIND_OBJECT_TIMEOUT
                     )
                 showBubble?.click() ?: error("Bubble notify not found")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
index 07ba4133..5085394 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt
@@ -19,7 +19,7 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
@@ -42,7 +42,7 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class OpenActivityFromBubbleTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class OpenActivityFromBubbleTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
index 6c61710..6a46d23 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTestCfArm.kt
@@ -17,10 +17,11 @@
 package com.android.wm.shell.flicker.bubble
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class OpenActivityFromBubbleTestCfArm(flicker: FlickerTest) : OpenActivityFromBubbleTest(flicker)
+class OpenActivityFromBubbleTestCfArm(flicker: LegacyFlickerTest) :
+    OpenActivityFromBubbleTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
index 29f76d0..a926bb7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt
@@ -19,7 +19,7 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
@@ -41,7 +41,7 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class SendBubbleNotificationTest(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
+open class SendBubbleNotificationTest(flicker: LegacyFlickerTest) : BaseBubbleScreen(flicker) {
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -55,6 +55,7 @@
                     FIND_OBJECT_TIMEOUT
                 )
                     ?: error("No bubbles found")
+                device.waitForIdle()
             }
         }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
index e323ebf..a401cb4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTestCfArm.kt
@@ -17,11 +17,11 @@
 package com.android.wm.shell.flicker.bubble
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class SendBubbleNotificationTestCfArm(flicker: FlickerTest) :
+open class SendBubbleNotificationTestCfArm(flicker: LegacyFlickerTest) :
     SendBubbleNotificationTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
index f7ce870..c335d3d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
@@ -16,26 +16,19 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.app.Instrumentation
-import android.os.SystemClock
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.Rotation
-import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import android.tools.device.traces.parsers.toFlickerComponent
 import androidx.test.filters.RequiresDevice
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import androidx.test.uiautomator.UiObject2
-import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -69,17 +62,16 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) :
-        AutoEnterPipOnGoToHomeTest(flicker) {
+class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) :
+    AutoEnterPipOnGoToHomeTest(flicker) {
     private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
     /** Second app used to enter split screen mode */
-    protected val secondAppForSplitScreen = getSplitScreenApp(instrumentation)
-    fun getSplitScreenApp(instrumentation: Instrumentation): StandardAppHelper =
-            SimpleAppHelper(
-                    instrumentation,
-                    ActivityOptions.SplitScreen.Primary.LABEL,
-                    ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
-            )
+    private val secondAppForSplitScreen =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Primary.LABEL,
+            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+        )
 
     /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
@@ -88,14 +80,7 @@
                 secondAppForSplitScreen.launchViaIntent(wmHelper)
                 pipApp.launchViaIntent(wmHelper)
                 tapl.goHome()
-                enterSplitScreen()
-                // wait until split screen is established
-                wmHelper
-                        .StateSyncBuilder()
-                        .withWindowSurfaceAppeared(pipApp)
-                        .withWindowSurfaceAppeared(secondAppForSplitScreen)
-                        .withSplitDividerVisible()
-                        .waitForAndVerify()
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, pipApp, secondAppForSplitScreen)
                 pipApp.enableAutoEnterForPipActivity()
             }
             teardown {
@@ -107,46 +92,6 @@
             transitions { tapl.goHome() }
         }
 
-    // TODO(b/285400227) merge the code in a common utility - this is copied from SplitScreenUtils
-    private val TIMEOUT_MS = 3_000L
-    private val overviewSnapshotSelector: BySelector
-        get() = By.res(LAUNCHER_UI_PACKAGE_NAME, "snapshot")
-    private fun enterSplitScreen() {
-        // Note: The initial split position in landscape is different between tablet and phone.
-        // In landscape, tablet will let the first app split to right side, and phone will
-        // split to left side.
-        if (tapl.isTablet) {
-            // TAPL's currentTask on tablet is sometimes not what we expected if the overview
-            // contains more than 3 task views. We need to use uiautomator directly to find the
-            // second task to split.
-            tapl.workspace.switchToOverview().overviewActions.clickSplit()
-            val snapshots = tapl.device.wait(Until.findObjects(overviewSnapshotSelector),
-                    TIMEOUT_MS)
-            if (snapshots == null || snapshots.size < 1) {
-                error("Fail to find a overview snapshot to split.")
-            }
-
-            // Find the second task in the upper right corner in split select mode by sorting
-            // 'left' in descending order and 'top' in ascending order.
-            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
-                t2.getVisibleBounds().left - t1.getVisibleBounds().left
-            }
-            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
-                t1.getVisibleBounds().top - t2.getVisibleBounds().top
-            }
-            snapshots[0].click()
-        } else {
-            tapl.workspace
-                    .switchToOverview()
-                    .currentTask
-                    .tapMenu()
-                    .tapSplitMenuItem()
-                    .currentTask
-                    .open()
-        }
-        SystemClock.sleep(TIMEOUT_MS)
-    }
-
     @Presubmit
     @Test
     override fun pipOverlayLayerAppearThenDisappear() {
@@ -190,11 +135,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                    // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
-                    supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index b95732e..2f7a25e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -19,7 +19,7 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
 import org.junit.Assume
 import org.junit.FixMethodOrder
@@ -53,10 +53,9 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
-    override val thisTransition: FlickerBuilder.() -> Unit = {
-        transitions { tapl.goHome() }
-    }
+open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
+    EnterPipViaAppUiButtonTest(flicker) {
+    override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index afcc172..68bc9a2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -20,7 +20,7 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -53,7 +53,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ClosePipBySwipingDownTest(flicker: FlickerTest) : ClosePipTransition(flicker) {
+open class ClosePipBySwipingDownTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions {
             val pipRegion = wmHelper.getWindowRegion(pipApp).bounds
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt
index 02f6010..7a66889 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTestCfArm.kt
@@ -18,8 +18,8 @@
 
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -28,20 +28,20 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ClosePipBySwipingDownTestCfArm(flicker: FlickerTest) : ClosePipBySwipingDownTest(flicker) {
+class ClosePipBySwipingDownTestCfArm(flicker: LegacyFlickerTest) :
+    ClosePipBySwipingDownTest(flicker) {
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+         * orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
index e52b71e..a17144b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
@@ -20,14 +20,14 @@
 import android.tools.common.Rotation
 import android.tools.common.traces.component.ComponentNameMatcher.Companion.LAUNCHER
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.Test
 import org.junit.runners.Parameterized
 
 /** Base class for exiting pip (closing pip window) without returning to the app */
-abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker) {
+abstract class ClosePipTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         setup { this.setRotation(flicker.scenario.startRotation) }
         teardown { this.setRotation(Rotation.ROTATION_0) }
@@ -74,15 +74,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+         * orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index 86fe583..dc48696 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -19,7 +19,7 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -53,7 +53,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ClosePipWithDismissButtonTest(flicker: FlickerTest) : ClosePipTransition(flicker) {
+open class ClosePipWithDismissButtonTest(flicker: LegacyFlickerTest) : ClosePipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.closePipWindow(wmHelper) }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt
index 05262fe..718b14b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTestCfArm.kt
@@ -18,8 +18,8 @@
 
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -28,21 +28,20 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ClosePipWithDismissButtonTestCfArm(flicker: FlickerTest) :
+open class ClosePipWithDismissButtonTestCfArm(flicker: LegacyFlickerTest) :
     ClosePipWithDismissButtonTest(flicker) {
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+         * orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 01d67cc..5e39262 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -19,7 +19,7 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
 import org.junit.Assume
 import org.junit.FixMethodOrder
@@ -44,10 +44,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTransition(flicker) {
-    override val thisTransition: FlickerBuilder.() -> Unit = {
-        transitions { tapl.goHome() }
-    }
+open class EnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
+    override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
 
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt
index 90f99c0..2b3e76a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTestCfArm.kt
@@ -17,7 +17,7 @@
 package com.android.wm.shell.flicker.pip
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -27,4 +27,5 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipOnUserLeaveHintTestCfArm(flicker: FlickerTest) : EnterPipOnUserLeaveHintTest(flicker)
+class EnterPipOnUserLeaveHintTestCfArm(flicker: LegacyFlickerTest) :
+    EnterPipOnUserLeaveHintTest(flicker)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index 5480144..ec35837 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -21,11 +21,12 @@
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.entireScreenCovered
@@ -68,15 +69,13 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flicker) {
+open class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     private val testApp = FixedOrientationAppHelper(instrumentation)
     private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
     private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
 
     override val thisTransition: FlickerBuilder.() -> Unit = {
-        teardown {
-            testApp.exit(wmHelper)
-        }
+        teardown { testApp.exit(wmHelper) }
         transitions {
             // Enter PiP, and assert that the PiP is within bounds now that the device is back
             // in portrait
@@ -95,14 +94,13 @@
         setup {
             // Launch a portrait only app on the fullscreen stack
             testApp.launchViaIntent(
-                    wmHelper,
-                    stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString())
+                wmHelper,
+                stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString())
             )
             // Launch the PiP activity fixed as landscape, but don't enter PiP
             pipApp.launchViaIntent(
-                    wmHelper,
-                    stringExtras =
-                    mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
+                wmHelper,
+                stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
             )
         }
     }
@@ -207,13 +205,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+            return LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt
index 5841666..9264219 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationCfArm.kt
@@ -17,9 +17,10 @@
 package com.android.wm.shell.flicker.pip
 
 import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -29,19 +30,19 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipToOtherOrientationCfArm(flicker: FlickerTest) :
+open class EnterPipToOtherOrientationCfArm(flicker: LegacyFlickerTest) :
     EnterPipToOtherOrientation(flicker) {
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+            return LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
index cdbdb85..6d20740 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
@@ -20,16 +20,14 @@
 import android.tools.common.Rotation
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.Test
 import org.junit.runners.Parameterized
 
-abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) {
+abstract class EnterPipTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     override val defaultEnterPip: FlickerBuilder.() -> Unit = {
-        setup {
-            pipApp.launchViaIntent(wmHelper)
-        }
+        setup { pipApp.launchViaIntent(wmHelper) }
     }
 
     /** Checks [pipApp] window remains visible throughout the animation */
@@ -126,15 +124,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+         * orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index 95725b6..76c811c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -18,7 +18,7 @@
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
@@ -50,7 +50,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipViaAppUiButtonTest(flicker: FlickerTest) : EnterPipTransition(flicker) {
+open class EnterPipViaAppUiButtonTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.clickEnterPipButton(wmHelper) }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt
index 4390f0b..78e8049 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTestCfArm.kt
@@ -18,8 +18,8 @@
 
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -28,20 +28,20 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipViaAppUiButtonTestCfArm(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
+class EnterPipViaAppUiButtonTestCfArm(flicker: LegacyFlickerTest) :
+    EnterPipViaAppUiButtonTest(flicker) {
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+         * orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 5ac9829..dfffba8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -19,14 +19,14 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import org.junit.Test
 import org.junit.runners.Parameterized
 
 /** Base class for pip expand tests */
-abstract class ExitPipToAppTransition(flicker: FlickerTest) : PipTransition(flicker) {
+abstract class ExitPipToAppTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     protected val testApp = SimpleAppHelper(instrumentation)
 
     /**
@@ -130,15 +130,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 0b3d16a..b80b748 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -18,7 +18,7 @@
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
@@ -52,7 +52,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExitPipToAppViaExpandButtonTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
+open class ExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
+    ExitPipToAppTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         setup {
             // launch an app behind the pip one
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt
index eccb85d..e25c0d6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTestCfArm.kt
@@ -18,8 +18,8 @@
 
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -28,21 +28,20 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipToAppViaExpandButtonTestCfArm(flicker: FlickerTest) :
+class ExitPipToAppViaExpandButtonTestCfArm(flicker: LegacyFlickerTest) :
     ExitPipToAppViaExpandButtonTest(flicker) {
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index bb2d40b..f003ed8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -18,7 +18,7 @@
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
@@ -51,7 +51,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExitPipToAppViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
+open class ExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) : ExitPipToAppTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         setup {
             // launch an app behind the pip one
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt
index 6ab6a1f..be19f3c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTestCfArm.kt
@@ -18,8 +18,8 @@
 
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -28,20 +28,20 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipToAppViaIntentTestCfArm(flicker: FlickerTest) : ExitPipToAppViaIntentTest(flicker) {
+class ExitPipToAppViaIntentTestCfArm(flicker: LegacyFlickerTest) :
+    ExitPipToAppViaIntentTest(flicker) {
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+         * orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index fd16b6e..a1d3a11 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -21,8 +21,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -55,7 +55,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) {
+open class ExpandPipOnDoubleClickTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.doubleClickPipWindow(wmHelper) }
     }
@@ -142,15 +142,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt
index c096234..3095cac 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTestTestCfArm.kt
@@ -18,8 +18,8 @@
 
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -28,21 +28,20 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnDoubleClickTestTestCfArm(flicker: FlickerTest) :
+class ExpandPipOnDoubleClickTestTestCfArm(flicker: LegacyFlickerTest) :
     ExpandPipOnDoubleClickTest(flicker) {
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
index 253aa4c..8c8d280 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -20,8 +20,8 @@
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -34,7 +34,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) {
+open class ExpandPipOnPinchOpenTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) }
     }
@@ -55,15 +55,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
index e064bf2..1a1ce68 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTestCfArm.kt
@@ -18,8 +18,8 @@
 
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -28,20 +28,20 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnPinchOpenTestCfArm(flicker: FlickerTest) : ExpandPipOnPinchOpenTest(flicker) {
+class ExpandPipOnPinchOpenTestCfArm(flicker: LegacyFlickerTest) :
+    ExpandPipOnPinchOpenTest(flicker) {
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 094060f..421ad75 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -19,9 +19,9 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.Direction
+import com.android.wm.shell.flicker.utils.Direction
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -55,7 +55,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MovePipDownOnShelfHeightChange(flicker: FlickerTest) : MovePipShelfHeightTransition(flicker) {
+class MovePipDownOnShelfHeightChange(flicker: LegacyFlickerTest) :
+    MovePipShelfHeightTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         teardown { testApp.exit(wmHelper) }
         transitions { testApp.launchViaIntent(wmHelper) }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index ff51c27..dffc822 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -18,11 +18,12 @@
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.ImeAppHelper
@@ -38,7 +39,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransition(flicker) {
+open class MovePipOnImeVisibilityChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     private val imeApp = ImeAppHelper(instrumentation)
 
     override val thisTransition: FlickerBuilder.() -> Unit = {
@@ -80,7 +81,7 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+            return LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt
index d3d77d2..63292a4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTestCfArm.kt
@@ -17,9 +17,10 @@
 package com.android.wm.shell.flicker.pip
 
 import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -28,7 +29,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MovePipOnImeVisibilityChangeTestCfArm(flicker: FlickerTest) :
+class MovePipOnImeVisibilityChangeTestCfArm(flicker: LegacyFlickerTest) :
     MovePipOnImeVisibilityChangeTest(flicker) {
     companion object {
         private const val TAG_IME_VISIBLE = "imeIsVisible"
@@ -36,7 +37,7 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+            return LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
index 109354a..a8fb63d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
@@ -19,15 +19,15 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
 import android.tools.common.flicker.subject.region.RegionSubject
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
-import com.android.wm.shell.flicker.Direction
+import com.android.wm.shell.flicker.utils.Direction
 import org.junit.Test
 import org.junit.runners.Parameterized
 
 /** Base class for pip tests with Launcher shelf height change */
-abstract class MovePipShelfHeightTransition(flicker: FlickerTest) : PipTransition(flicker) {
+abstract class MovePipShelfHeightTransition(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     protected val testApp = FixedOrientationAppHelper(instrumentation)
 
     /** Checks [pipApp] window remains visible throughout the animation */
@@ -111,15 +111,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index 27b061b..992f1bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -19,9 +19,9 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.Direction
+import com.android.wm.shell.flicker.utils.Direction
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -55,14 +55,13 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class MovePipUpOnShelfHeightChangeTest(flicker: FlickerTest) :
+open class MovePipUpOnShelfHeightChangeTest(flicker: LegacyFlickerTest) :
     MovePipShelfHeightTransition(flicker) {
-    override val thisTransition: FlickerBuilder.() -> Unit =
-        {
-            setup { testApp.launchViaIntent(wmHelper) }
-            transitions { tapl.pressHome() }
-            teardown { testApp.exit(wmHelper) }
-        }
+    override val thisTransition: FlickerBuilder.() -> Unit = {
+        setup { testApp.launchViaIntent(wmHelper) }
+        transitions { tapl.pressHome() }
+        teardown { testApp.exit(wmHelper) }
+    }
 
     /** Checks that the visible region of [pipApp] window always moves up during the animation. */
     @Presubmit @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index 9f81ba8..0c6fc56 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -16,12 +16,12 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -34,7 +34,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) {
+class PipDragTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     private var isDraggedLeft: Boolean = true
 
     override val thisTransition: FlickerBuilder.() -> Unit = {
@@ -62,7 +62,7 @@
         }
     }
 
-    @Postsubmit
+    @Presubmit
     @Test
     fun pipLayerMovesAwayFromEdge() {
         flicker.assertLayers {
@@ -81,13 +81,11 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index 9fe9f52..de64f78 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -17,12 +17,12 @@
 package com.android.wm.shell.flicker.pip
 
 import android.graphics.Rect
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.setRotation
@@ -38,7 +38,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipDragThenSnapTest(flicker: FlickerTest) : PipTransition(flicker) {
+class PipDragThenSnapTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     // represents the direction in which the pip window should be snapping
     private var willSnapRight: Boolean = true
 
@@ -80,7 +80,7 @@
     /**
      * Checks that the visible region area of [pipApp] moves to closest edge during the animation.
      */
-    @Postsubmit
+    @Presubmit
     @Test
     fun pipLayerMovesToClosestEdge() {
         flicker.assertLayers {
@@ -99,15 +99,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index 60bf5ff..0295741 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -16,13 +16,12 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -35,14 +34,13 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 270677470)
-class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) {
+class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) }
     }
 
     /** Checks that the visible region area of [pipApp] always decreases during the animation. */
-    @Postsubmit
+    @Presubmit
     @Test
     fun pipLayerAreaDecreases() {
         flicker.assertLayers {
@@ -57,15 +55,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index 17a178f..096af39 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -22,7 +22,7 @@
 import android.tools.common.Rotation
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
 import android.tools.device.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.PipAppHelper
@@ -32,7 +32,7 @@
 import com.google.common.truth.Truth
 import org.junit.Test
 
-abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class PipTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     protected val pipApp = PipAppHelper(instrumentation)
     protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
     protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
@@ -78,16 +78,16 @@
     /** Defines the default method of entering PiP */
     protected open val defaultEnterPip: FlickerBuilder.() -> Unit = {
         setup {
-            pipApp.launchViaIntentAndWaitForPip(wmHelper,
-                    stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true"))
+            pipApp.launchViaIntentAndWaitForPip(
+                wmHelper,
+                stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
+            )
         }
     }
 
     /** Defines the default teardown required to clean up after the test */
     protected open val defaultTeardown: FlickerBuilder.() -> Unit = {
-        teardown {
-            pipApp.exit(wmHelper)
-        }
+        teardown { pipApp.exit(wmHelper) }
     }
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index c618e5a..c315e74 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -21,10 +21,11 @@
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -46,7 +47,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransition(flicker) {
+open class SetRequestedOrientationWhilePinned(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
     private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
 
@@ -69,20 +70,19 @@
         setup {
             // Launch the PiP activity fixed as landscape.
             pipApp.launchViaIntent(
-                    wmHelper,
-                    stringExtras =
-                    mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
+                wmHelper,
+                stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
             )
             // Enter PiP.
             broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP)
             // System bar may fade out during fixed rotation.
             wmHelper
-                    .StateSyncBuilder()
-                    .withPipShown()
-                    .withRotation(Rotation.ROTATION_0)
-                    .withNavOrTaskBarVisible()
-                    .withStatusBarVisible()
-                    .waitForAndVerify()
+                .StateSyncBuilder()
+                .withPipShown()
+                .withRotation(Rotation.ROTATION_0)
+                .withNavOrTaskBarVisible()
+                .withStatusBarVisible()
+                .waitForAndVerify()
         }
     }
 
@@ -150,7 +150,7 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+            return LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index 43d6c8f..0ff9cff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -17,10 +17,11 @@
 package com.android.wm.shell.flicker.pip
 
 import android.platform.test.annotations.Presubmit
+import android.tools.common.flicker.assertions.FlickerTest
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -58,7 +59,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowPipAndRotateDisplay(flicker: FlickerTest) : PipTransition(flicker) {
+open class ShowPipAndRotateDisplay(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     private val testApp = SimpleAppHelper(instrumentation)
     private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
     private val screenBoundsEnd = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
@@ -154,13 +155,13 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+         * orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.rotationTests()
+            return LegacyFlickerTestFactory.rotationTests()
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt
index b7a2c47..2516471 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplayCfArm.kt
@@ -16,9 +16,10 @@
 
 package com.android.wm.shell.flicker.pip
 
+import android.tools.common.flicker.assertions.FlickerTest
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -27,18 +28,18 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowPipAndRotateDisplayCfArm(flicker: FlickerTest) : ShowPipAndRotateDisplay(flicker) {
+class ShowPipAndRotateDisplayCfArm(flicker: LegacyFlickerTest) : ShowPipAndRotateDisplay(flicker) {
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
-         * and navigation modes.
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen
+         * orientation and navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.rotationTests()
+            return LegacyFlickerTestFactory.rotationTests()
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
index 0432a84..d4cd6da 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
@@ -20,8 +20,8 @@
 import androidx.test.filters.RequiresDevice
 import androidx.test.uiautomator.UiObject2
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.wait
+import com.android.wm.shell.flicker.utils.SYSTEM_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.utils.wait
 import org.junit.Assert.assertNull
 import org.junit.Assert.assertTrue
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
index 90406c5..4402e21 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
@@ -21,11 +21,11 @@
 import android.os.Bundle
 import android.service.notification.StatusBarNotification
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.NotificationListener.Companion.findNotification
-import com.android.wm.shell.flicker.NotificationListener.Companion.startNotificationListener
-import com.android.wm.shell.flicker.NotificationListener.Companion.stopNotificationListener
-import com.android.wm.shell.flicker.NotificationListener.Companion.waitForNotificationToAppear
-import com.android.wm.shell.flicker.NotificationListener.Companion.waitForNotificationToDisappear
+import com.android.wm.shell.flicker.utils.NotificationListener.Companion.findNotification
+import com.android.wm.shell.flicker.utils.NotificationListener.Companion.startNotificationListener
+import com.android.wm.shell.flicker.utils.NotificationListener.Companion.stopNotificationListener
+import com.android.wm.shell.flicker.utils.NotificationListener.Companion.waitForNotificationToAppear
+import com.android.wm.shell.flicker.utils.NotificationListener.Companion.waitForNotificationToDisappear
 import org.junit.After
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertNull
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
index 6104b7b..47bff8d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
@@ -24,7 +24,7 @@
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import android.view.Surface.ROTATION_0
 import android.view.Surface.rotationToString
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.utils.SYSTEM_UI_PACKAGE_NAME
 import org.junit.After
 import org.junit.Assert.assertFalse
 import org.junit.Assume.assumeTrue
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
index b0adbe1..4aee61a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvUtils.kt
@@ -22,7 +22,7 @@
 import androidx.test.uiautomator.UiDevice
 import androidx.test.uiautomator.UiObject2
 import androidx.test.uiautomator.Until
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.utils.SYSTEM_UI_PACKAGE_NAME
 
 /** Id of the root view in the com.android.wm.shell.pip.tv.PipMenuActivity */
 private const val TV_PIP_MENU_ROOT_ID = "tv_pip_menu"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
new file mode 100644
index 0000000..610cede
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service
+
+import android.app.Instrumentation
+import android.platform.test.rule.NavigationModeRule
+import android.platform.test.rule.PressHomeRule
+import android.platform.test.rule.UnlockScreenRule
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.apphelpers.MessagingAppHelper
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.device.flicker.rules.LaunchAppRule
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.RuleChain
+
+object Utils {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    fun testSetupRule(navigationMode: NavBar, rotation: Rotation): RuleChain {
+        return RuleChain.outerRule(UnlockScreenRule())
+            .around(
+                NavigationModeRule(navigationMode.value, /* changeNavigationModeAfterTest */ false)
+            )
+            .around(
+                LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false)
+            )
+            .around(RemoveAllTasksButHomeRule())
+            .around(
+                ChangeDisplayOrientationRule(
+                    rotation,
+                    resetOrientationAfterTest = false,
+                    clearCacheAfterParsing = false
+                )
+            )
+            .around(PressHomeRule())
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..566adec
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+
+@RequiresDevice
+class CopyContentInSplitGesturalNavLandscapeBenchmark : CopyContentInSplit(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun copyContentInSplit() = super.copyContentInSplit()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..92b6227
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+
+@RequiresDevice
+class CopyContentInSplitGesturalNavPortraitBenchmark : CopyContentInSplit(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun copyContentInSplit() = super.copyContentInSplit()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..e6d56b5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByDividerGesturalNavLandscapeBenchmark :
+    DismissSplitScreenByDivider(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..6752c58
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByDividerGesturalNavPortraitBenchmark :
+    DismissSplitScreenByDivider(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..7c9ab99
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark :
+    DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..4b79571
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+
+@RequiresDevice
+class DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark :
+    DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..0495079
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+
+@RequiresDevice
+class DragDividerToResizeGesturalNavLandscapeBenchmark : DragDividerToResize(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dragDividerToResize() = super.dragDividerToResize()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..71ef48b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+
+@RequiresDevice
+class DragDividerToResizeGesturalNavPortraitBenchmark : DragDividerToResize(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun dragDividerToResize() = super.dragDividerToResize()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..c78729c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark :
+    EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..30bce2f6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark :
+    EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..b33ea7c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark :
+    EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromNotification() =
+        super.enterSplitScreenByDragFromNotification()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..07a86a5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark :
+    EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromNotification() =
+        super.enterSplitScreenByDragFromNotification()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..9a1d127
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark :
+    EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..266e268
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark :
+    EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..83fc30b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark :
+    EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..b2f1929
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark :
+    EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..dae92dd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark :
+    EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..732047b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+
+@RequiresDevice
+class EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark :
+    EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..1de7efd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+
+@RequiresDevice
+class SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark :
+    SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..1a046aa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+
+@RequiresDevice
+class SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark :
+    SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..6e88f0e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark :
+    SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..d26a29c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark :
+    SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..4a552b0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark :
+    SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..b7376ea
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark :
+    SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..b2d05e4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark :
+    SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..6de31b1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark :
+    SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..aab18a6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark :
+    SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..b074f2c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.Rotation
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+
+@RequiresDevice
+class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark :
+    SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
new file mode 100644
index 0000000..c402aa4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RequiresDevice
+@RunWith(BlockJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
new file mode 100644
index 0000000..840401c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.benchmark
+
+import android.platform.test.annotations.PlatinumTest
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RequiresDevice
+@RunWith(BlockJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark : UnlockKeyguardToSplitScreen() {
+    @PlatinumTest(focusArea = "sysui")
+    @Presubmit
+    @Test
+    override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
new file mode 100644
index 0000000..a5c5122
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
+    @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
new file mode 100644
index 0000000..092fb67
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
+    @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
new file mode 100644
index 0000000..8cb25fe
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByDividerGesturalNavLandscape :
+    DismissSplitScreenByDivider(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+    @Test
+    override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
new file mode 100644
index 0000000..fa1be63
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByDividerGesturalNavPortrait :
+    DismissSplitScreenByDivider(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+    @Test
+    override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..aa35237
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByGoHomeGesturalNavLandscape :
+    DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+    @Test
+    override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..e195360
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByGoHomeGesturalNavPortrait :
+    DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+    @Test
+    override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
new file mode 100644
index 0000000..c1b3aad
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
+    @Test
+    override fun dragDividerToResize() = super.dragDividerToResize()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
new file mode 100644
index 0000000..c6e2e85
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
+    @Test
+    override fun dragDividerToResize() = super.dragDividerToResize()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
new file mode 100644
index 0000000..5f771c7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
+    EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
new file mode 100644
index 0000000..729a401
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
+    EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
new file mode 100644
index 0000000..6e4cf9f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
+    EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromNotification() =
+        super.enterSplitScreenByDragFromNotification()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
new file mode 100644
index 0000000..cc28702
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
+    EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromNotification() =
+        super.enterSplitScreenByDragFromNotification()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
new file mode 100644
index 0000000..736604f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
+    EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
new file mode 100644
index 0000000..8df8dfa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
+    EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
new file mode 100644
index 0000000..378f055
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
+    EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
new file mode 100644
index 0000000..b33d262
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
+    EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
new file mode 100644
index 0000000..b1d3858
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenFromOverviewGesturalNavLandscape :
+    EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
new file mode 100644
index 0000000..6d824c7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenFromOverviewGesturalNavPortrait :
+    EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+    @Test
+    override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
new file mode 100644
index 0000000..f1d3d0c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchAppByDoubleTapDividerGesturalNavLandscape :
+    SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios([])
+    @Test
+    override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
new file mode 100644
index 0000000..a867bac
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchAppByDoubleTapDividerGesturalNavPortrait :
+    SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios([])
+    @Test
+    override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
new file mode 100644
index 0000000..76247ba
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
+    SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
new file mode 100644
index 0000000..e179da8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
+    SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..20f554f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromHomeGesturalNavLandscape :
+    SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..f7776ee
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromHomeGesturalNavPortrait :
+    SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
new file mode 100644
index 0000000..00f6073
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromRecentGesturalNavLandscape :
+    SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
new file mode 100644
index 0000000..b3340e7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromRecentGesturalNavPortrait :
+    SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
new file mode 100644
index 0000000..3da61e5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
new file mode 100644
index 0000000..627ae18
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
new file mode 100644
index 0000000..7cbc1c3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
new file mode 100644
index 0000000..2eb81e0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
+
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
new file mode 100644
index 0000000..e530f63
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CopyContentInSplit
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+    private val textEditApp = SplitScreenUtils.getIme(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp)
+    }
+
+    @Test
+    open fun copyContentInSplit() {
+        SplitScreenUtils.copyContentInSplit(instrumentation, device, primaryApp, textEditApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
new file mode 100644
index 0000000..e9fc437
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class DismissSplitScreenByDivider
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+    }
+
+    @Test
+    open fun dismissSplitScreenByDivider() {
+        if (tapl.isTablet) {
+            SplitScreenUtils.dragDividerToDismissSplit(
+                device,
+                wmHelper,
+                dragToRight = false,
+                dragToBottom = true
+            )
+        } else {
+            SplitScreenUtils.dragDividerToDismissSplit(
+                device,
+                wmHelper,
+                dragToRight = true,
+                dragToBottom = true
+            )
+        }
+        wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
new file mode 100644
index 0000000..416692c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class DismissSplitScreenByGoHome
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+    }
+
+    @Test
+    open fun dismissSplitScreenByGoHome() {
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
new file mode 100644
index 0000000..494a246
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class DragDividerToResize
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+    }
+
+    @Test
+    open fun dragDividerToResize() {
+        SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
new file mode 100644
index 0000000..369bdfc
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromAllApps
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        tapl.goHome()
+        primaryApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun enterSplitScreenByDragFromAllApps() {
+        tapl.launchedAppState.taskbar
+            .openAllApps()
+            .getAppIcon(secondaryApp.appName)
+            .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
new file mode 100644
index 0000000..776c397
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromNotification
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+    private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        // Send a notification
+        sendNotificationApp.launchViaIntent(wmHelper)
+        sendNotificationApp.postNotification(wmHelper)
+        tapl.goHome()
+        primaryApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun enterSplitScreenByDragFromNotification() {
+        SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+        sendNotificationApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
new file mode 100644
index 0000000..5d67dc7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromShortcut
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(tapl.isTablet)
+
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        tapl.goHome()
+        SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+        primaryApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun enterSplitScreenByDragFromShortcut() {
+        tapl.launchedAppState.taskbar
+            .getAppIcon(secondaryApp.appName)
+            .openDeepShortcutMenu()
+            .getMenuItem("Split Screen Secondary Activity")
+            .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+
+        // TODO: Do we want this check in here? Add to the other tests?
+        //        flicker.splitScreenEntered(
+        //                primaryApp,
+        //                secondaryApp,
+        //                fromOtherApp = false,
+        //                appExistAtStart = false
+        //        )
+    }
+
+    @After
+    fun teardwon() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
new file mode 100644
index 0000000..5bbb42f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenByDragFromTaskbar
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        tapl.goHome()
+        SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+        primaryApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun enterSplitScreenByDragFromTaskbar() {
+        tapl.launchedAppState.taskbar
+            .getAppIcon(secondaryApp.appName)
+            .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
new file mode 100644
index 0000000..c2100f6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class EnterSplitScreenFromOverview
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        primaryApp.launchViaIntent(wmHelper)
+        secondaryApp.launchViaIntent(wmHelper)
+        tapl.goHome()
+        wmHelper
+            .StateSyncBuilder()
+            .withAppTransitionIdle()
+            .withHomeActivityVisible()
+            .waitForAndVerify()
+    }
+
+    @Test
+    open fun enterSplitScreenFromOverview() {
+        SplitScreenUtils.splitFromOverview(tapl, device)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
new file mode 100644
index 0000000..70f3bed
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.graphics.Point
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.WindowUtils
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchAppByDoubleTapDivider
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        tapl.workspace.switchToOverview().dismissAllTasks()
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+    }
+
+    @Test
+    open fun switchAppByDoubleTapDivider() {
+        SplitScreenUtils.doubleTapDividerToSwitch(device)
+        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+        waitForLayersToSwitch(wmHelper)
+        waitForWindowsToSwitch(wmHelper)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+
+    private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("appWindowsSwitched") {
+                val primaryAppWindow =
+                    it.wmState.visibleWindows.firstOrNull { window ->
+                        primaryApp.windowMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+                val secondaryAppWindow =
+                    it.wmState.visibleWindows.firstOrNull { window ->
+                        secondaryApp.windowMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+
+                if (isLandscape(rotation)) {
+                    return@add if (isTablet()) {
+                        secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+                    } else {
+                        primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+                    }
+                } else {
+                    return@add if (isTablet()) {
+                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                    } else {
+                        primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+                    }
+                }
+            }
+            .waitForAndVerify()
+    }
+
+    private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("appLayersSwitched") {
+                val primaryAppLayer =
+                    it.layerState.visibleLayers.firstOrNull { window ->
+                        primaryApp.layerMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+                val secondaryAppLayer =
+                    it.layerState.visibleLayers.firstOrNull { window ->
+                        secondaryApp.layerMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+
+                val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
+                val secondaryVisibleRegion =
+                    secondaryAppLayer.visibleRegion?.bounds ?: return@add false
+
+                if (isLandscape(rotation)) {
+                    return@add if (isTablet()) {
+                        secondaryVisibleRegion.right <= primaryVisibleRegion.left
+                    } else {
+                        primaryVisibleRegion.right <= secondaryVisibleRegion.left
+                    }
+                } else {
+                    return@add if (isTablet()) {
+                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                    } else {
+                        primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+                    }
+                }
+            }
+            .waitForAndVerify()
+    }
+
+    private fun isLandscape(rotation: Rotation): Boolean {
+        val displayBounds = WindowUtils.getDisplayBounds(rotation)
+        return displayBounds.width > displayBounds.height
+    }
+
+    private fun isTablet(): Boolean {
+        val sizeDp: Point = device.displaySizeDp
+        val LARGE_SCREEN_DP_THRESHOLD = 600
+        return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
new file mode 100644
index 0000000..86f394d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBackToSplitFromAnotherApp
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+    private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+        thirdApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
+    }
+
+    @Test
+    open fun switchBackToSplitFromAnotherApp() {
+        tapl.launchedAppState.quickSwitchToPreviousApp()
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
new file mode 100644
index 0000000..d7b611e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBackToSplitFromHome
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @Test
+    open fun switchBackToSplitFromHome() {
+        tapl.workspace.quickSwitchToPreviousApp()
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
new file mode 100644
index 0000000..bf4c381
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBackToSplitFromRecent
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @Test
+    open fun switchBackToSplitFromRecent() {
+        tapl.workspace.switchToOverview().currentTask.open()
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
new file mode 100644
index 0000000..4a9c32f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class SwitchBetweenSplitPairs
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+    private val thirdApp = SplitScreenUtils.getIme(instrumentation)
+    private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+        SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
+    }
+
+    @Test
+    open fun switchBetweenSplitPairs() {
+        tapl.launchedAppState.quickSwitchToPreviousApp()
+        SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+        thirdApp.exit(wmHelper)
+        fourthApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
new file mode 100644
index 0000000..383a6b3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.splitscreen.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class UnlockKeyguardToSplitScreen {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
+
+    @Rule
+    @JvmField
+    val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+    @Before
+    fun setup() {
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(Rotation.ROTATION_0.value)
+
+        SplitScreenUtils.enterSplitViaIntent(wmHelper, primaryApp, secondaryApp)
+    }
+
+    @Test
+    open fun unlockKeyguardToSplitScreen() {
+        device.sleep()
+        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+        device.wakeUp()
+        device.pressMenu()
+        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        primaryApp.exit(wmHelper)
+        secondaryApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index d1f0980..3702be9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -17,25 +17,20 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.common.traces.component.EdgeExtensionComponentMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
-import com.android.wm.shell.flicker.appWindowKeepVisible
-import com.android.wm.shell.flicker.layerKeepVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowKeepVisible
+import com.android.wm.shell.flicker.utils.layerKeepVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsKeepVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -45,13 +40,13 @@
 /**
  * Test copy content from the left to the right side of the split-screen.
  *
- * To run this test: `atest WMShellFlickerTests:CopyContentInSplit`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:CopyContentInSplit`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CopyContentInSplit(override val flicker: FlickerTest) :
+class CopyContentInSplit(override val flicker: LegacyFlickerTest) :
     CopyContentInSplitBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -60,21 +55,6 @@
             thisTransition(this)
         }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    override fun cujCompleted() {
-        flicker.appWindowIsVisibleAtStart(primaryApp)
-        flicker.appWindowIsVisibleAtStart(textEditApp)
-        flicker.splitScreenDividerIsVisibleAtStart()
-
-        flicker.appWindowIsVisibleAtEnd(primaryApp)
-        flicker.appWindowIsVisibleAtEnd(textEditApp)
-        flicker.splitScreenDividerIsVisibleAtEnd()
-
-        // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
-    }
-
     @Presubmit
     @Test
     fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
@@ -136,8 +116,6 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 4505b99..8b90630 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -21,17 +21,17 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.helpers.WindowUtils
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesInvisible
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerBecomesInvisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
-import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByDividerBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerBecomesInvisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesInvisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -41,13 +41,13 @@
 /**
  * Test dismiss split screen by dragging the divider bar.
  *
- * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByDivider`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:DismissSplitScreenByDivider`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByDivider(override val flicker: FlickerTest) :
+class DismissSplitScreenByDivider(override val flicker: LegacyFlickerTest) :
     DismissSplitScreenByDividerBenchmark(flicker), ICommonAssertions {
 
     override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index e05b221..50f6a38 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -20,15 +20,15 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesInvisible
-import com.android.wm.shell.flicker.layerBecomesInvisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
-import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByGoHomeBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.utils.layerBecomesInvisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesInvisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,13 +38,13 @@
 /**
  * Test dismiss split screen by go home.
  *
- * To run this test: `atest WMShellFlickerTests:DismissSplitScreenByGoHome`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:DismissSplitScreenByGoHome`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByGoHome(override val flicker: FlickerTest) :
+class DismissSplitScreenByGoHome(override val flicker: LegacyFlickerTest) :
     DismissSplitScreenByGoHomeBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -154,8 +154,6 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 63c5d14..ca9c130 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -17,23 +17,18 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
-import com.android.wm.shell.flicker.appWindowKeepVisible
-import com.android.wm.shell.flicker.layerKeepVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowKeepVisible
+import com.android.wm.shell.flicker.utils.layerKeepVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsChanges
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -43,13 +38,13 @@
 /**
  * Test resize split by dragging the divider bar.
  *
- * To run this test: `atest WMShellFlickerTests:DragDividerToResize`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:DragDividerToResize`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DragDividerToResize(override val flicker: FlickerTest) :
+class DragDividerToResize(override val flicker: LegacyFlickerTest) :
     DragDividerToResizeBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -58,19 +53,6 @@
             thisTransition(this)
         }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    override fun cujCompleted() {
-        flicker.appWindowIsVisibleAtStart(primaryApp)
-        flicker.appWindowIsVisibleAtStart(secondaryApp)
-        flicker.splitScreenDividerIsVisibleAtStart()
-
-        flicker.appWindowIsVisibleAtEnd(primaryApp)
-        flicker.appWindowIsVisibleAtEnd(secondaryApp)
-        flicker.splitScreenDividerIsVisibleAtEnd()
-    }
-
     @Presubmit
     @Test
     fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
@@ -127,8 +109,6 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index e558686..f8d1e1f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -22,19 +22,19 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromAllAppsBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -45,13 +45,13 @@
  * Test enter split screen by dragging app icon from all apps. This test is only for large screen
  * devices.
  *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromAllApps`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromAllApps`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromAllApps(override val flicker: FlickerTest) :
+class EnterSplitScreenByDragFromAllApps(override val flicker: LegacyFlickerTest) :
     EnterSplitScreenByDragFromAllAppsBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -160,11 +160,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index ab8ecc5..ff5d935 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -22,18 +22,18 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromNotificationBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -44,13 +44,13 @@
  * Test enter split screen by dragging app icon from notification. This test is only for large
  * screen devices.
  *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromNotification`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromNotification`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromNotification(override val flicker: FlickerTest) :
+class EnterSplitScreenByDragFromNotification(override val flicker: LegacyFlickerTest) :
     EnterSplitScreenByDragFromNotificationBenchmark(flicker), ICommonAssertions {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -162,11 +162,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index 516ca97..7c71077 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -21,17 +21,17 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromShortcutBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -41,13 +41,13 @@
 /**
  * Test enter split screen by dragging a shortcut. This test is only for large screen devices.
  *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromShortcut`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromShortcut`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromShortcut(override val flicker: FlickerTest) :
+class EnterSplitScreenByDragFromShortcut(override val flicker: LegacyFlickerTest) :
     EnterSplitScreenByDragFromShortcutBenchmark(flicker), ICommonAssertions {
 
     override val transition: FlickerBuilder.() -> Unit
@@ -105,11 +105,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 4af7e24..8371706 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -22,19 +22,19 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromTaskbarBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -45,13 +45,13 @@
  * Test enter split screen by dragging app icon from taskbar. This test is only for large screen
  * devices.
  *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromTaskbar`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenByDragFromTaskbar`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromTaskbar(override val flicker: FlickerTest) :
+class EnterSplitScreenByDragFromTaskbar(override val flicker: LegacyFlickerTest) :
     EnterSplitScreenByDragFromTaskbarBenchmark(flicker), ICommonAssertions {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
@@ -163,10 +163,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index faad9e8..0bfdbb4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -20,17 +20,17 @@
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenFromOverviewBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -40,13 +40,13 @@
 /**
  * Test enter split screen from Overview.
  *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenFromOverview`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:EnterSplitScreenFromOverview`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenFromOverview(override val flicker: FlickerTest) :
+class EnterSplitScreenFromOverview(override val flicker: LegacyFlickerTest) :
     EnterSplitScreenFromOverviewBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -106,8 +106,6 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
index 195b73a..7ce995a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -18,11 +18,12 @@
 
 import android.content.Context
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.wm.shell.flicker.BaseBenchmarkTest
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 
-abstract class SplitScreenBase(flicker: FlickerTest) : BaseBenchmarkTest(flicker) {
+abstract class SplitScreenBase(flicker: LegacyFlickerTest) : BaseBenchmarkTest(flicker) {
     protected val context: Context = instrumentation.context
     protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
     protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
deleted file mode 100644
index 1063dfd..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
+++ /dev/null
@@ -1,376 +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.wm.shell.flicker.splitscreen
-
-import android.app.Instrumentation
-import android.graphics.Point
-import android.os.SystemClock
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.common.traces.component.IComponentMatcher
-import android.tools.common.traces.component.IComponentNameMatcher
-import android.tools.device.apphelpers.StandardAppHelper
-import android.tools.device.traces.parsers.WindowManagerStateHelper
-import android.tools.device.traces.parsers.toFlickerComponent
-import android.view.InputDevice
-import android.view.MotionEvent
-import android.view.ViewConfiguration
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.UiObject2
-import androidx.test.uiautomator.Until
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.helpers.ImeAppHelper
-import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
-import com.android.server.wm.flicker.helpers.NotificationAppHelper
-import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import org.junit.Assert.assertNotNull
-
-internal object SplitScreenUtils {
-    private const val TIMEOUT_MS = 3_000L
-    private const val DRAG_DURATION_MS = 1_000L
-    private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
-    private const val DIVIDER_BAR = "docked_divider_handle"
-    private const val OVERVIEW_SNAPSHOT = "snapshot"
-    private const val GESTURE_STEP_MS = 16L
-    private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L
-    private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
-
-    private val notificationScrollerSelector: BySelector
-        get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
-    private val notificationContentSelector: BySelector
-        get() = By.text("Flicker Test Notification")
-    private val dividerBarSelector: BySelector
-        get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
-    private val overviewSnapshotSelector: BySelector
-        get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT)
-
-    fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
-        SimpleAppHelper(
-            instrumentation,
-            ActivityOptions.SplitScreen.Primary.LABEL,
-            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
-        )
-
-    fun getSecondary(instrumentation: Instrumentation): StandardAppHelper =
-        SimpleAppHelper(
-            instrumentation,
-            ActivityOptions.SplitScreen.Secondary.LABEL,
-            ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent()
-        )
-
-    fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper =
-        NonResizeableAppHelper(instrumentation)
-
-    fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper =
-        NotificationAppHelper(instrumentation)
-
-    fun getIme(instrumentation: Instrumentation): ImeAppHelper = ImeAppHelper(instrumentation)
-
-    fun waitForSplitComplete(
-        wmHelper: WindowManagerStateHelper,
-        primaryApp: IComponentMatcher,
-        secondaryApp: IComponentMatcher,
-    ) {
-        wmHelper
-            .StateSyncBuilder()
-            .withWindowSurfaceAppeared(primaryApp)
-            .withWindowSurfaceAppeared(secondaryApp)
-            .withSplitDividerVisible()
-            .waitForAndVerify()
-    }
-
-    fun enterSplit(
-        wmHelper: WindowManagerStateHelper,
-        tapl: LauncherInstrumentation,
-        device: UiDevice,
-        primaryApp: StandardAppHelper,
-        secondaryApp: StandardAppHelper
-    ) {
-        primaryApp.launchViaIntent(wmHelper)
-        secondaryApp.launchViaIntent(wmHelper)
-        tapl.goHome()
-        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-        splitFromOverview(tapl, device)
-        waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-    }
-
-    fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
-        // Note: The initial split position in landscape is different between tablet and phone.
-        // In landscape, tablet will let the first app split to right side, and phone will
-        // split to left side.
-        if (tapl.isTablet) {
-            // TAPL's currentTask on tablet is sometimes not what we expected if the overview
-            // contains more than 3 task views. We need to use uiautomator directly to find the
-            // second task to split.
-            tapl.workspace.switchToOverview().overviewActions.clickSplit()
-            val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
-            if (snapshots == null || snapshots.size < 1) {
-                error("Fail to find a overview snapshot to split.")
-            }
-
-            // Find the second task in the upper right corner in split select mode by sorting
-            // 'left' in descending order and 'top' in ascending order.
-            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
-                t2.getVisibleBounds().left - t1.getVisibleBounds().left
-            }
-            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
-                t1.getVisibleBounds().top - t2.getVisibleBounds().top
-            }
-            snapshots[0].click()
-        } else {
-            tapl.workspace
-                .switchToOverview()
-                .currentTask
-                .tapMenu()
-                .tapSplitMenuItem()
-                .currentTask
-                .open()
-        }
-        SystemClock.sleep(TIMEOUT_MS)
-    }
-
-    fun dragFromNotificationToSplit(
-        instrumentation: Instrumentation,
-        device: UiDevice,
-        wmHelper: WindowManagerStateHelper
-    ) {
-        val displayBounds =
-            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
-                ?: error("Display not found")
-
-        // Pull down the notifications
-        device.swipe(
-            displayBounds.centerX(),
-            5,
-            displayBounds.centerX(),
-            displayBounds.bottom,
-            50 /* steps */
-        )
-        SystemClock.sleep(TIMEOUT_MS)
-
-        // Find the target notification
-        val notificationScroller =
-            device.wait(Until.findObject(notificationScrollerSelector), TIMEOUT_MS)
-                ?: error("Unable to find view $notificationScrollerSelector")
-        var notificationContent = notificationScroller.findObject(notificationContentSelector)
-
-        while (notificationContent == null) {
-            device.swipe(
-                displayBounds.centerX(),
-                displayBounds.centerY(),
-                displayBounds.centerX(),
-                displayBounds.centerY() - 150,
-                20 /* steps */
-            )
-            notificationContent = notificationScroller.findObject(notificationContentSelector)
-        }
-
-        // Drag to split
-        val dragStart = notificationContent.visibleCenter
-        val dragMiddle = Point(dragStart.x + 50, dragStart.y)
-        val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
-        val downTime = SystemClock.uptimeMillis()
-
-        touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart)
-        // It needs a horizontal movement to trigger the drag
-        touchMove(
-            instrumentation,
-            downTime,
-            SystemClock.uptimeMillis(),
-            DRAG_DURATION_MS,
-            dragStart,
-            dragMiddle
-        )
-        touchMove(
-            instrumentation,
-            downTime,
-            SystemClock.uptimeMillis(),
-            DRAG_DURATION_MS,
-            dragMiddle,
-            dragEnd
-        )
-        // Wait for a while to start splitting
-        SystemClock.sleep(TIMEOUT_MS)
-        touch(
-            instrumentation,
-            MotionEvent.ACTION_UP,
-            downTime,
-            SystemClock.uptimeMillis(),
-            GESTURE_STEP_MS,
-            dragEnd
-        )
-        SystemClock.sleep(TIMEOUT_MS)
-    }
-
-    fun touch(
-        instrumentation: Instrumentation,
-        action: Int,
-        downTime: Long,
-        eventTime: Long,
-        duration: Long,
-        point: Point
-    ) {
-        val motionEvent =
-            MotionEvent.obtain(downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0)
-        motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
-        instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
-        motionEvent.recycle()
-        SystemClock.sleep(duration)
-    }
-
-    fun touchMove(
-        instrumentation: Instrumentation,
-        downTime: Long,
-        eventTime: Long,
-        duration: Long,
-        from: Point,
-        to: Point
-    ) {
-        val steps: Long = duration / GESTURE_STEP_MS
-        var currentTime = eventTime
-        var currentX = from.x.toFloat()
-        var currentY = from.y.toFloat()
-        val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
-        val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
-
-        for (i in 1..steps) {
-            val motionMove =
-                MotionEvent.obtain(
-                    downTime,
-                    currentTime,
-                    MotionEvent.ACTION_MOVE,
-                    currentX,
-                    currentY,
-                    0
-                )
-            motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
-            instrumentation.uiAutomation.injectInputEvent(motionMove, true)
-            motionMove.recycle()
-
-            currentTime += GESTURE_STEP_MS
-            if (i == steps - 1) {
-                currentX = to.x.toFloat()
-                currentY = to.y.toFloat()
-            } else {
-                currentX += stepX
-                currentY += stepY
-            }
-            SystemClock.sleep(GESTURE_STEP_MS)
-        }
-    }
-
-    fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) {
-        tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
-        val allApps = tapl.workspace.switchToAllApps()
-        allApps.freeze()
-        try {
-            allApps.getAppIcon(appName).dragToHotseat(0)
-        } finally {
-            allApps.unfreeze()
-        }
-    }
-
-    fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) {
-        val displayBounds =
-            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
-                ?: error("Display not found")
-        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-        dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200)
-
-        wmHelper
-            .StateSyncBuilder()
-            .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
-            .waitForAndVerify()
-    }
-
-    fun dragDividerToDismissSplit(
-        device: UiDevice,
-        wmHelper: WindowManagerStateHelper,
-        dragToRight: Boolean,
-        dragToBottom: Boolean
-    ) {
-        val displayBounds =
-            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
-                ?: error("Display not found")
-        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-        dividerBar.drag(
-            Point(
-                if (dragToRight) {
-                    displayBounds.width * 4 / 5
-                } else {
-                    displayBounds.width * 1 / 5
-                },
-                if (dragToBottom) {
-                    displayBounds.height * 4 / 5
-                } else {
-                    displayBounds.height * 1 / 5
-                }
-            )
-        )
-    }
-
-    fun doubleTapDividerToSwitch(device: UiDevice) {
-        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-        val interval =
-            (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2
-        dividerBar.click()
-        SystemClock.sleep(interval.toLong())
-        dividerBar.click()
-    }
-
-    fun copyContentInSplit(
-        instrumentation: Instrumentation,
-        device: UiDevice,
-        sourceApp: IComponentNameMatcher,
-        destinationApp: IComponentNameMatcher,
-    ) {
-        // Copy text from sourceApp
-        val textView =
-            device.wait(
-                Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")),
-                TIMEOUT_MS
-            )
-        assertNotNull("Unable to find the TextView", textView)
-        textView.click(LONG_PRESS_TIME_MS)
-
-        val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
-        assertNotNull("Unable to find the copy button", copyBtn)
-        copyBtn.click()
-
-        // Paste text to destinationApp
-        val editText =
-            device.wait(
-                Until.findObject(By.res(destinationApp.packageName, "plain_text_input")),
-                TIMEOUT_MS
-            )
-        assertNotNull("Unable to find the EditText", editText)
-        editText.click(LONG_PRESS_TIME_MS)
-
-        val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
-        assertNotNull("Unable to find the paste button", pasteBtn)
-        pasteBtn.click()
-
-        // Verify text
-        if (!textView.text.contentEquals(editText.text)) {
-            error("Fail to copy content in split")
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index 03b8a75..fac97c8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -16,25 +16,21 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerKeepVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchAppByDoubleTapDividerBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerKeepVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -44,13 +40,13 @@
 /**
  * Test double tap the divider bar to switch the two apps.
  *
- * To run this test: `atest WMShellFlickerTests:SwitchAppByDoubleTapDivider`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchAppByDoubleTapDivider`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchAppByDoubleTapDivider(override val flicker: FlickerTest) :
+class SwitchAppByDoubleTapDivider(override val flicker: LegacyFlickerTest) :
     SwitchAppByDoubleTapDividerBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -59,19 +55,6 @@
             thisTransition(this)
         }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    override fun cujCompleted() {
-        flicker.appWindowIsVisibleAtStart(primaryApp)
-        flicker.appWindowIsVisibleAtStart(secondaryApp)
-        flicker.splitScreenDividerIsVisibleAtStart()
-
-        flicker.appWindowIsVisibleAtEnd(primaryApp)
-        flicker.appWindowIsVisibleAtEnd(secondaryApp)
-        flicker.splitScreenDividerIsVisibleAtEnd()
-    }
-
     @Presubmit
     @Test
     fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
@@ -120,11 +103,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 078d95d..88bbc0e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -21,15 +21,15 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromAnotherAppBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -39,13 +39,13 @@
 /**
  * Test quick switch to split pair from another app.
  *
- * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromAnotherApp`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBackToSplitFromAnotherApp`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromAnotherApp(override val flicker: FlickerTest) :
+class SwitchBackToSplitFromAnotherApp(override val flicker: LegacyFlickerTest) :
     SwitchBackToSplitFromAnotherAppBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -149,11 +149,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 7c84243..e85dc24 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -21,15 +21,15 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromHomeBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -39,13 +39,13 @@
 /**
  * Test quick switch to split pair from home.
  *
- * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromHome`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBackToSplitFromHome`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromHome(override val flicker: FlickerTest) :
+class SwitchBackToSplitFromHome(override val flicker: LegacyFlickerTest) :
     SwitchBackToSplitFromHomeBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -149,11 +149,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index 7c46d3e..f7a9ed0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -21,15 +21,15 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromRecentBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -39,13 +39,13 @@
 /**
  * Test switch back to split pair from recent.
  *
- * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromRecent`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBackToSplitFromRecent`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromRecent(override val flicker: FlickerTest) :
+class SwitchBackToSplitFromRecent(override val flicker: LegacyFlickerTest) :
     SwitchBackToSplitFromRecentBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -149,11 +149,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index 674ba40..66f9b85 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -17,27 +17,21 @@
 package com.android.wm.shell.flicker.splitscreen
 
 import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowBecomesInvisible
-import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.appWindowIsInvisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
-import com.android.wm.shell.flicker.layerBecomesInvisible
-import com.android.wm.shell.flicker.layerBecomesVisible
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.utils.appWindowBecomesVisible
+import com.android.wm.shell.flicker.utils.layerBecomesInvisible
+import com.android.wm.shell.flicker.utils.layerBecomesVisible
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsSnapToDivider
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -47,13 +41,13 @@
 /**
  * Test quick switch between two split pairs.
  *
- * To run this test: `atest WMShellFlickerTests:SwitchBetweenSplitPairs`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:SwitchBetweenSplitPairs`
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBetweenSplitPairs(override val flicker: FlickerTest) :
+class SwitchBetweenSplitPairs(override val flicker: LegacyFlickerTest) :
     SwitchBetweenSplitPairsBenchmark(flicker), ICommonAssertions {
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -62,21 +56,6 @@
             thisTransition(this)
         }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    override fun cujCompleted() {
-        flicker.appWindowIsVisibleAtStart(thirdApp)
-        flicker.appWindowIsVisibleAtStart(fourthApp)
-        flicker.splitScreenDividerIsVisibleAtStart()
-
-        flicker.appWindowIsVisibleAtEnd(primaryApp)
-        flicker.appWindowIsVisibleAtEnd(secondaryApp)
-        flicker.appWindowIsInvisibleAtEnd(thirdApp)
-        flicker.appWindowIsInvisibleAtEnd(fourthApp)
-        flicker.splitScreenDividerIsVisibleAtEnd()
-    }
-
     @Presubmit
     @Test
     fun splitScreenDividerInvisibleAtMiddle() =
@@ -223,8 +202,6 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
index 676c150..851391d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
@@ -21,15 +21,15 @@
 import android.tools.common.flicker.subject.region.RegionSubject
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.ICommonAssertions
-import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.layerIsVisibleAtEnd
-import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitscreen.benchmark.UnlockKeyguardToSplitScreenBenchmark
+import com.android.wm.shell.flicker.utils.ICommonAssertions
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -37,21 +37,21 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test unlocking insecure keyguard to back to split screen tasks and verify the transition behavior.
+ * Test unlocking insecure keyguard to back to split screen tasks and verify the transition
+ * behavior.
  *
- * To run this test: `atest WMShellFlickerTests:UnlockKeyguardToSplitScreen`
+ * To run this test: `atest WMShellFlickerTestsSplitScreen:UnlockKeyguardToSplitScreen`
  */
 @RequiresDevice
 @Postsubmit
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class UnlockKeyguardToSplitScreen(override val flicker: FlickerTest) :
-        UnlockKeyguardToSplitScreenBenchmark(flicker), ICommonAssertions {
+class UnlockKeyguardToSplitScreen(override val flicker: LegacyFlickerTest) :
+    UnlockKeyguardToSplitScreenBenchmark(flicker), ICommonAssertions {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
-            defaultSetup(this)
             defaultTeardown(this)
             thisTransition(this)
         }
@@ -65,33 +65,35 @@
 
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() =
-            flicker.splitAppLayerBoundsIsVisibleAtEnd(
-                    primaryApp,
-                    landscapePosLeft = false,
-                    portraitPosTop = false
-            )
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
+            primaryApp,
+            landscapePosLeft = false,
+            portraitPosTop = false
+        )
 
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() =
-            flicker.splitAppLayerBoundsIsVisibleAtEnd(
-                    secondaryApp,
-                    landscapePosLeft = true,
-                    portraitPosTop = true
-            )
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
+            secondaryApp,
+            landscapePosLeft = true,
+            portraitPosTop = true
+        )
 
-    @Test
-    fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
+    @Test fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
 
-    @Test
-    fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
+    @Test fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
 
     @Test
     fun notOverlapsForPrimaryAndSecondaryAppLayers() {
         flicker.assertLayers {
             this.invoke("notOverlapsForPrimaryAndSecondaryLayers") {
-                val primaryAppRegions = it.subjects.filter { subject ->
-                    subject.name.contains(primaryApp.toLayerName()) && subject.isVisible
-                }.mapNotNull { primaryApp -> primaryApp.layer.visibleRegion }.toTypedArray()
+                val primaryAppRegions =
+                    it.subjects
+                        .filter { subject ->
+                            subject.name.contains(primaryApp.toLayerName()) && subject.isVisible
+                        }
+                        .mapNotNull { primaryApp -> primaryApp.layer.visibleRegion }
+                        .toTypedArray()
 
                 val primaryAppRegionArea = RegionSubject(primaryAppRegions, it.timestamp)
                 it.visibleRegion(secondaryApp).notOverlaps(primaryAppRegionArea.region)
@@ -102,10 +104,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                    supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
index c3c5f88..e5c1e75 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -16,18 +16,15 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -36,7 +33,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CopyContentInSplitBenchmark(override val flicker: FlickerTest) :
+abstract class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val textEditApp = SplitScreenUtils.getIme(instrumentation)
     protected val magnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
@@ -54,26 +51,9 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    open fun cujCompleted() {
-        // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
-    }
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
index 37cd18f..e4e1af9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -16,18 +16,14 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenDismissed
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -36,7 +32,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DismissSplitScreenByDividerBenchmark(flicker: FlickerTest) : SplitScreenBase(flicker) {
+abstract class DismissSplitScreenByDividerBenchmark(override val flicker: LegacyFlickerTest) :
+    SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
             setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
@@ -60,24 +57,9 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
index 0ec6dc9..b2dd02b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -16,18 +16,14 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenDismissed
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DismissSplitScreenByGoHomeBenchmark(override val flicker: FlickerTest) :
+abstract class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -47,24 +43,9 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
index 190e2e7..0788591 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -16,19 +16,16 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -37,7 +34,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class DragDividerToResizeBenchmark(override val flicker: FlickerTest) :
+abstract class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -45,32 +42,14 @@
             transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
     }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    open fun cujCompleted() {
-        // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
-        // robust enough to get the correct end state.
-    }
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
index 3a1d1a4..884e451 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -16,21 +16,17 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: FlickerTest) :
+abstract class EnterSplitScreenByDragFromAllAppsBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
 
     protected val thisTransition: FlickerBuilder.() -> Unit
@@ -57,38 +53,18 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet)
     }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(
-            primaryApp,
-            secondaryApp,
-            fromOtherApp = false,
-            appExistAtStart = false
-        )
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
index 2033b7d..e5c40b6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt
@@ -16,21 +16,17 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,8 +35,9 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromNotificationBenchmark(override val flicker: FlickerTest) :
-    SplitScreenBase(flicker) {
+abstract class EnterSplitScreenByDragFromNotificationBenchmark(
+    override val flicker: LegacyFlickerTest
+) : SplitScreenBase(flicker) {
     protected val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -58,21 +55,6 @@
             teardown { sendNotificationApp.exit(wmHelper) }
         }
 
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(primaryApp, sendNotificationApp, fromOtherApp = false)
-
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet)
@@ -81,11 +63,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
index b7a7110..0451001 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -16,21 +16,17 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,8 +35,9 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromShortcutBenchmark(flicker: FlickerTest) :
-    SplitScreenBase(flicker) {
+abstract class EnterSplitScreenByDragFromShortcutBenchmark(
+    override val flicker: LegacyFlickerTest
+) : SplitScreenBase(flicker) {
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet)
@@ -62,33 +59,13 @@
         }
     }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(
-            primaryApp,
-            secondaryApp,
-            fromOtherApp = false,
-            appExistAtStart = false
-        )
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
index b1ce62f..9e0ca1b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -16,21 +16,17 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,7 +35,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: FlickerTest) :
+abstract class EnterSplitScreenByDragFromTaskbarBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -56,26 +52,6 @@
             }
         }
 
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() =
-        flicker.splitScreenEntered(
-            primaryApp,
-            secondaryApp,
-            fromOtherApp = false,
-            appExistAtStart = false
-        )
-
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet)
@@ -84,10 +60,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
index 14f0745..06b4fe7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -16,18 +16,14 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -36,7 +32,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterSplitScreenFromOverviewBenchmark(override val flicker: FlickerTest) :
+abstract class EnterSplitScreenFromOverviewBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -56,24 +52,9 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index 65fb135..007b751 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -16,21 +16,18 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -39,7 +36,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchAppByDoubleTapDividerBenchmark(override val flicker: FlickerTest) :
+abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -53,14 +50,6 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
     private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
         wmHelper
             .StateSyncBuilder()
@@ -134,22 +123,13 @@
         return displayBounds.width > displayBounds.height
     }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    open fun cujCompleted() {
-        // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
-        // robust enough to get the correct end state.
-    }
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
index b333aba..10c8eeb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -16,19 +16,15 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: FlickerTest) :
+abstract class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
 
@@ -55,27 +51,13 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
index a27540e..a6e750f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -16,19 +16,15 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromHomeBenchmark(override val flicker: FlickerTest) :
+abstract class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -53,27 +49,13 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
index 18bf4ff..7e8d5fb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -16,19 +16,15 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.splitScreenEntered
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -37,7 +33,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBackToSplitFromRecentBenchmark(override val flicker: FlickerTest) :
+abstract class SwitchBackToSplitFromRecentBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
@@ -53,27 +49,13 @@
             }
         }
 
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            withoutTracing(this)
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit
-    @Test
-    fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
index c5fe61e..56edad1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -16,17 +16,14 @@
 
 package com.android.wm.shell.flicker.splitscreen.benchmark
 
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
-import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -35,7 +32,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SwitchBetweenSplitPairsBenchmark(override val flicker: FlickerTest) :
+abstract class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerTest) :
     SplitScreenBase(flicker) {
     protected val thirdApp = SplitScreenUtils.getIme(instrumentation)
     protected val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
@@ -64,14 +61,9 @@
             thisTransition(this)
         }
 
-    @PlatinumTest(focusArea = "sysui")
-    @Presubmit @Test open fun cujCompleted() {}
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
index 5f16e5b..065d4d6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt
@@ -19,11 +19,11 @@
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.SplitScreenBase
-import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -33,11 +33,11 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class UnlockKeyguardToSplitScreenBenchmark(override val flicker: FlickerTest) :
-        SplitScreenBase(flicker) {
+abstract class UnlockKeyguardToSplitScreenBenchmark(override val flicker: LegacyFlickerTest) :
+    SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+            setup { SplitScreenUtils.enterSplitViaIntent(wmHelper, primaryApp, secondaryApp) }
             transitions {
                 device.sleep()
                 wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
@@ -47,21 +47,12 @@
             }
         }
 
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            defaultSetup(this)
-            defaultTeardown(this)
-            thisTransition(this)
-        }
-
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                    supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
new file mode 100644
index 0000000..e5c124c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
@@ -0,0 +1,455 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("CommonAssertions")
+
+package com.android.wm.shell.flicker.utils
+
+import android.tools.common.Rotation
+import android.tools.common.datatypes.Region
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.traces.component.IComponentMatcher
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.helpers.WindowUtils
+
+fun LegacyFlickerTest.appPairsDividerIsVisibleAtEnd() {
+    assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
+}
+
+fun LegacyFlickerTest.appPairsDividerIsInvisibleAtEnd() {
+    assertLayersEnd { this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
+}
+
+fun LegacyFlickerTest.appPairsDividerBecomesVisible() {
+    assertLayers {
+        this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
+            .then()
+            .isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
+    }
+}
+
+fun LegacyFlickerTest.splitScreenEntered(
+    component1: IComponentMatcher,
+    component2: IComponentMatcher,
+    fromOtherApp: Boolean,
+    appExistAtStart: Boolean = true
+) {
+    if (fromOtherApp) {
+        if (appExistAtStart) {
+            appWindowIsInvisibleAtStart(component1)
+        } else {
+            appWindowIsNotContainAtStart(component1)
+        }
+    } else {
+        appWindowIsVisibleAtStart(component1)
+    }
+    if (appExistAtStart) {
+        appWindowIsInvisibleAtStart(component2)
+    } else {
+        appWindowIsNotContainAtStart(component2)
+    }
+    splitScreenDividerIsInvisibleAtStart()
+
+    appWindowIsVisibleAtEnd(component1)
+    appWindowIsVisibleAtEnd(component2)
+    splitScreenDividerIsVisibleAtEnd()
+}
+
+fun LegacyFlickerTest.splitScreenDismissed(
+    component1: IComponentMatcher,
+    component2: IComponentMatcher,
+    toHome: Boolean
+) {
+    appWindowIsVisibleAtStart(component1)
+    appWindowIsVisibleAtStart(component2)
+    splitScreenDividerIsVisibleAtStart()
+
+    appWindowIsInvisibleAtEnd(component1)
+    if (toHome) {
+        appWindowIsInvisibleAtEnd(component2)
+    } else {
+        appWindowIsVisibleAtEnd(component2)
+    }
+    splitScreenDividerIsInvisibleAtEnd()
+}
+
+fun LegacyFlickerTest.splitScreenDividerIsVisibleAtStart() {
+    assertLayersStart { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
+}
+
+fun LegacyFlickerTest.splitScreenDividerIsVisibleAtEnd() {
+    assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
+}
+
+fun LegacyFlickerTest.splitScreenDividerIsInvisibleAtStart() {
+    assertLayersStart { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
+}
+
+fun LegacyFlickerTest.splitScreenDividerIsInvisibleAtEnd() {
+    assertLayersEnd { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
+}
+
+fun LegacyFlickerTest.splitScreenDividerBecomesVisible() {
+    layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+}
+
+fun LegacyFlickerTest.splitScreenDividerBecomesInvisible() {
+    assertLayers {
+        this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+            .then()
+            .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+    }
+}
+
+fun LegacyFlickerTest.layerBecomesVisible(component: IComponentMatcher) {
+    assertLayers { this.isInvisible(component).then().isVisible(component) }
+}
+
+fun LegacyFlickerTest.layerBecomesInvisible(component: IComponentMatcher) {
+    assertLayers { this.isVisible(component).then().isInvisible(component) }
+}
+
+fun LegacyFlickerTest.layerIsVisibleAtEnd(component: IComponentMatcher) {
+    assertLayersEnd { this.isVisible(component) }
+}
+
+fun LegacyFlickerTest.layerKeepVisible(component: IComponentMatcher) {
+    assertLayers { this.isVisible(component) }
+}
+
+fun LegacyFlickerTest.splitAppLayerBoundsBecomesVisible(
+    component: IComponentMatcher,
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean
+) {
+    assertLayers {
+        this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
+            .then()
+            .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
+            .then()
+            .splitAppLayerBoundsSnapToDivider(
+                component,
+                landscapePosLeft,
+                portraitPosTop,
+                scenario.endRotation
+            )
+    }
+}
+
+fun LegacyFlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) {
+    assertLayers {
+        this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
+            .then()
+            .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
+            .then()
+            // TODO(b/245472831): Verify the component should snap to divider.
+            .isVisible(component)
+    }
+}
+
+fun LegacyFlickerTest.splitAppLayerBoundsBecomesInvisible(
+    component: IComponentMatcher,
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean
+) {
+    assertLayers {
+        this.splitAppLayerBoundsSnapToDivider(
+                component,
+                landscapePosLeft,
+                portraitPosTop,
+                scenario.endRotation
+            )
+            .then()
+            .isVisible(component, true)
+            .then()
+            .isInvisible(component)
+    }
+}
+
+fun LegacyFlickerTest.splitAppLayerBoundsIsVisibleAtEnd(
+    component: IComponentMatcher,
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean
+) {
+    assertLayersEnd {
+        splitAppLayerBoundsSnapToDivider(
+            component,
+            landscapePosLeft,
+            portraitPosTop,
+            scenario.endRotation
+        )
+    }
+}
+
+fun LegacyFlickerTest.splitAppLayerBoundsKeepVisible(
+    component: IComponentMatcher,
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean
+) {
+    assertLayers {
+        splitAppLayerBoundsSnapToDivider(
+            component,
+            landscapePosLeft,
+            portraitPosTop,
+            scenario.endRotation
+        )
+    }
+}
+
+fun LegacyFlickerTest.splitAppLayerBoundsChanges(
+    component: IComponentMatcher,
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean
+) {
+    assertLayers {
+        if (landscapePosLeft) {
+            splitAppLayerBoundsSnapToDivider(
+                component,
+                landscapePosLeft,
+                portraitPosTop,
+                scenario.endRotation
+            )
+        } else {
+            splitAppLayerBoundsSnapToDivider(
+                    component,
+                    landscapePosLeft,
+                    portraitPosTop,
+                    scenario.endRotation
+                )
+                .then()
+                .isInvisible(component)
+                .then()
+                .splitAppLayerBoundsSnapToDivider(
+                    component,
+                    landscapePosLeft,
+                    portraitPosTop,
+                    scenario.endRotation
+                )
+        }
+    }
+}
+
+fun LayersTraceSubject.splitAppLayerBoundsSnapToDivider(
+    component: IComponentMatcher,
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean,
+    rotation: Rotation
+): LayersTraceSubject {
+    return invoke("splitAppLayerBoundsSnapToDivider") {
+        it.splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, rotation)
+    }
+}
+
+fun LayerTraceEntrySubject.splitAppLayerBoundsSnapToDivider(
+    component: IComponentMatcher,
+    landscapePosLeft: Boolean,
+    portraitPosTop: Boolean,
+    rotation: Rotation
+): LayerTraceEntrySubject {
+    val displayBounds = WindowUtils.getDisplayBounds(rotation)
+    return invoke {
+        val dividerRegion =
+            layer(SPLIT_SCREEN_DIVIDER_COMPONENT)?.visibleRegion?.region
+                ?: error("$SPLIT_SCREEN_DIVIDER_COMPONENT component not found")
+        visibleRegion(component)
+            .coversAtMost(
+                if (displayBounds.width > displayBounds.height) {
+                    if (landscapePosLeft) {
+                        Region.from(
+                            0,
+                            0,
+                            (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+                            displayBounds.bounds.bottom
+                        )
+                    } else {
+                        Region.from(
+                            (dividerRegion.bounds.left + dividerRegion.bounds.right) / 2,
+                            0,
+                            displayBounds.bounds.right,
+                            displayBounds.bounds.bottom
+                        )
+                    }
+                } else {
+                    if (portraitPosTop) {
+                        Region.from(
+                            0,
+                            0,
+                            displayBounds.bounds.right,
+                            (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2
+                        )
+                    } else {
+                        Region.from(
+                            0,
+                            (dividerRegion.bounds.top + dividerRegion.bounds.bottom) / 2,
+                            displayBounds.bounds.right,
+                            displayBounds.bounds.bottom
+                        )
+                    }
+                }
+            )
+    }
+}
+
+fun LegacyFlickerTest.appWindowBecomesVisible(component: IComponentMatcher) {
+    assertWm {
+        this.isAppWindowInvisible(component)
+            .then()
+            .notContains(component, isOptional = true)
+            .then()
+            .isAppWindowInvisible(component, isOptional = true)
+            .then()
+            .isAppWindowVisible(component)
+    }
+}
+
+fun LegacyFlickerTest.appWindowBecomesInvisible(component: IComponentMatcher) {
+    assertWm { this.isAppWindowVisible(component).then().isAppWindowInvisible(component) }
+}
+
+fun LegacyFlickerTest.appWindowIsVisibleAtStart(component: IComponentMatcher) {
+    assertWmStart { this.isAppWindowVisible(component) }
+}
+
+fun LegacyFlickerTest.appWindowIsVisibleAtEnd(component: IComponentMatcher) {
+    assertWmEnd { this.isAppWindowVisible(component) }
+}
+
+fun LegacyFlickerTest.appWindowIsInvisibleAtStart(component: IComponentMatcher) {
+    assertWmStart { this.isAppWindowInvisible(component) }
+}
+
+fun LegacyFlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) {
+    assertWmEnd { this.isAppWindowInvisible(component) }
+}
+
+fun LegacyFlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) {
+    assertWmStart { this.notContains(component) }
+}
+
+fun LegacyFlickerTest.appWindowKeepVisible(component: IComponentMatcher) {
+    assertWm { this.isAppWindowVisible(component) }
+}
+
+fun LegacyFlickerTest.dockedStackDividerIsVisibleAtEnd() {
+    assertLayersEnd { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) }
+}
+
+fun LegacyFlickerTest.dockedStackDividerBecomesVisible() {
+    assertLayers {
+        this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
+            .then()
+            .isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
+    }
+}
+
+fun LegacyFlickerTest.dockedStackDividerBecomesInvisible() {
+    assertLayers {
+        this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
+            .then()
+            .isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
+    }
+}
+
+fun LegacyFlickerTest.dockedStackDividerNotExistsAtEnd() {
+    assertLayersEnd { this.notContains(DOCKED_STACK_DIVIDER_COMPONENT) }
+}
+
+fun LegacyFlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd(
+    rotation: Rotation,
+    primaryComponent: IComponentMatcher
+) {
+    assertLayersEnd {
+        val dividerRegion =
+            layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT)?.visibleRegion?.region
+                ?: error("$APP_PAIR_SPLIT_DIVIDER_COMPONENT component not found")
+        visibleRegion(primaryComponent).overlaps(getPrimaryRegion(dividerRegion, rotation))
+    }
+}
+
+fun LegacyFlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd(
+    rotation: Rotation,
+    primaryComponent: IComponentMatcher
+) {
+    assertLayersEnd {
+        val dividerRegion =
+            layer(DOCKED_STACK_DIVIDER_COMPONENT)?.visibleRegion?.region
+                ?: error("$DOCKED_STACK_DIVIDER_COMPONENT component not found")
+        visibleRegion(primaryComponent).overlaps(getPrimaryRegion(dividerRegion, rotation))
+    }
+}
+
+fun LegacyFlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd(
+    rotation: Rotation,
+    secondaryComponent: IComponentMatcher
+) {
+    assertLayersEnd {
+        val dividerRegion =
+            layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT)?.visibleRegion?.region
+                ?: error("$APP_PAIR_SPLIT_DIVIDER_COMPONENT component not found")
+        visibleRegion(secondaryComponent).overlaps(getSecondaryRegion(dividerRegion, rotation))
+    }
+}
+
+fun LegacyFlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd(
+    rotation: Rotation,
+    secondaryComponent: IComponentMatcher
+) {
+    assertLayersEnd {
+        val dividerRegion =
+            layer(DOCKED_STACK_DIVIDER_COMPONENT)?.visibleRegion?.region
+                ?: error("$DOCKED_STACK_DIVIDER_COMPONENT component not found")
+        visibleRegion(secondaryComponent).overlaps(getSecondaryRegion(dividerRegion, rotation))
+    }
+}
+
+fun getPrimaryRegion(dividerRegion: Region, rotation: Rotation): Region {
+    val displayBounds = WindowUtils.getDisplayBounds(rotation)
+    return if (rotation.isRotated()) {
+        Region.from(
+            0,
+            0,
+            dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
+            displayBounds.bounds.bottom
+        )
+    } else {
+        Region.from(
+            0,
+            0,
+            displayBounds.bounds.right,
+            dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset
+        )
+    }
+}
+
+fun getSecondaryRegion(dividerRegion: Region, rotation: Rotation): Region {
+    val displayBounds = WindowUtils.getDisplayBounds(rotation)
+    return if (rotation.isRotated()) {
+        Region.from(
+            dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset,
+            0,
+            displayBounds.bounds.right,
+            displayBounds.bounds.bottom
+        )
+    } else {
+        Region.from(
+            0,
+            dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
+            displayBounds.bounds.right,
+            displayBounds.bounds.bottom
+        )
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt
new file mode 100644
index 0000000..3b66d6a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonConstants.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("CommonConstants")
+
+package com.android.wm.shell.flicker.utils
+
+import android.tools.common.traces.component.ComponentNameMatcher
+
+const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
+const val LAUNCHER_UI_PACKAGE_NAME = "com.google.android.apps.nexuslauncher"
+val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentNameMatcher("", "AppPairSplitDivider#")
+val DOCKED_STACK_DIVIDER_COMPONENT = ComponentNameMatcher("", "DockedStackDivider#")
+val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentNameMatcher("", "StageCoordinatorSplitDivider#")
+val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
+
+enum class Direction {
+    UP,
+    DOWN,
+    LEFT,
+    RIGHT
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
new file mode 100644
index 0000000..7f58ced
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/ICommonAssertions.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.utils
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.statusBarLayerPositionAtStartAndEnd
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible
+import org.junit.Assume
+import org.junit.Test
+
+interface ICommonAssertions {
+    val flicker: LegacyFlickerTest
+
+    /** Checks that all parts of the screen are covered during the transition */
+    @Presubmit @Test fun entireScreenCovered() = flicker.entireScreenCovered()
+
+    /**
+     * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
+     */
+    @Presubmit
+    @Test
+    fun navBarLayerIsVisibleAtStartAndEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    /**
+     * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the
+     * transition
+     */
+    @Presubmit
+    @Test
+    fun navBarLayerPositionAtStartAndEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerPositionAtStartAndEnd()
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition
+     *
+     * Note: Phones only
+     */
+    @Presubmit
+    @Test
+    fun navBarWindowIsAlwaysVisible() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarWindowIsAlwaysVisible()
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible during the whole transition
+     */
+    @Presubmit
+    @Test
+    fun taskBarLayerIsVisibleAtStartAndEnd() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
+     *
+     * Note: Large screen only
+     */
+    @Presubmit
+    @Test
+    fun taskBarWindowIsAlwaysVisible() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarWindowIsAlwaysVisible()
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible during the whole
+     * transition
+     */
+    @Presubmit
+    @Test
+    fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /**
+     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
+     * transition
+     */
+    @Presubmit
+    @Test
+    fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd()
+
+    /**
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
+     * transition
+     */
+    @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible()
+
+    /**
+     * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
+     * entries.
+     */
+    @Presubmit
+    @Test
+    fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
+    }
+
+    /**
+     * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive
+     * entries.
+     */
+    @Presubmit
+    @Test
+    fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+        flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MultiWindowUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MultiWindowUtils.kt
new file mode 100644
index 0000000..9b3a480
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MultiWindowUtils.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.wm.shell.flicker.utils
+
+import android.app.Instrumentation
+import android.content.Context
+import android.provider.Settings
+import android.util.Log
+import com.android.compatibility.common.util.SystemUtil
+import java.io.IOException
+
+object MultiWindowUtils {
+    private fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
+        try {
+            SystemUtil.runShellCommand(instrumentation, cmd)
+        } catch (e: IOException) {
+            Log.e(MultiWindowUtils::class.simpleName, "executeShellCommand error! $e")
+        }
+    }
+
+    fun getDevEnableNonResizableMultiWindow(context: Context): Int =
+        Settings.Global.getInt(
+            context.contentResolver,
+            Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW
+        )
+
+    fun setDevEnableNonResizableMultiWindow(context: Context, configValue: Int) =
+        Settings.Global.putInt(
+            context.contentResolver,
+            Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
+            configValue
+        )
+
+    fun setSupportsNonResizableMultiWindow(instrumentation: Instrumentation, configValue: Int) =
+        executeShellCommand(
+            instrumentation,
+            createConfigSupportsNonResizableMultiWindowCommand(configValue)
+        )
+
+    fun resetMultiWindowConfig(instrumentation: Instrumentation) =
+        executeShellCommand(instrumentation, resetMultiWindowConfigCommand)
+
+    private fun createConfigSupportsNonResizableMultiWindowCommand(configValue: Int): String =
+        "wm set-multi-window-config --supportsNonResizable $configValue"
+
+    private const val resetMultiWindowConfigCommand: String = "wm reset-multi-window-config"
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/NotificationListener.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/NotificationListener.kt
new file mode 100644
index 0000000..529c125
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/NotificationListener.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.wm.shell.flicker.utils
+
+import android.service.notification.NotificationListenerService
+import android.service.notification.StatusBarNotification
+import android.util.Log
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+
+class NotificationListener : NotificationListenerService() {
+    private val notifications: MutableMap<Any, StatusBarNotification> = mutableMapOf()
+
+    override fun onNotificationPosted(sbn: StatusBarNotification) {
+        if (DEBUG) Log.d(TAG, "onNotificationPosted: $sbn")
+        notifications[sbn.key] = sbn
+    }
+
+    override fun onNotificationRemoved(sbn: StatusBarNotification) {
+        if (DEBUG) Log.d(TAG, "onNotificationRemoved: $sbn")
+        notifications.remove(sbn.key)
+    }
+
+    override fun onListenerConnected() {
+        if (DEBUG) Log.d(TAG, "onListenerConnected")
+        instance = this
+    }
+
+    override fun onListenerDisconnected() {
+        if (DEBUG) Log.d(TAG, "onListenerDisconnected")
+        instance = null
+        notifications.clear()
+    }
+
+    companion object {
+        private const val DEBUG = false
+        private const val TAG = "WMShellFlickerTests_NotificationListener"
+
+        private const val CMD_NOTIFICATION_ALLOW_LISTENER = "cmd notification allow_listener %s"
+        private const val CMD_NOTIFICATION_DISALLOW_LISTENER =
+            "cmd notification disallow_listener %s"
+        private const val COMPONENT_NAME = "com.android.wm.shell.flicker/.NotificationListener"
+
+        private var instance: NotificationListener? = null
+
+        fun startNotificationListener(): Boolean {
+            if (instance != null) {
+                return true
+            }
+
+            runShellCommand(CMD_NOTIFICATION_ALLOW_LISTENER.format(COMPONENT_NAME))
+            return wait { instance != null }
+        }
+
+        fun stopNotificationListener(): Boolean {
+            if (instance == null) {
+                return true
+            }
+
+            runShellCommand(CMD_NOTIFICATION_DISALLOW_LISTENER.format(COMPONENT_NAME))
+            return wait { instance == null }
+        }
+
+        fun findNotification(
+            predicate: (StatusBarNotification) -> Boolean
+        ): StatusBarNotification? {
+            instance?.run {
+                return notifications.values.firstOrNull(predicate)
+            }
+                ?: throw IllegalStateException("NotificationListenerService is not connected")
+        }
+
+        fun waitForNotificationToAppear(
+            predicate: (StatusBarNotification) -> Boolean
+        ): StatusBarNotification? {
+            instance?.let {
+                return waitForResult(extractor = { it.notifications.values.firstOrNull(predicate) })
+                    .second
+            }
+                ?: throw IllegalStateException("NotificationListenerService is not connected")
+        }
+
+        fun waitForNotificationToDisappear(predicate: (StatusBarNotification) -> Boolean): Boolean {
+            return instance?.let { wait { it.notifications.values.none(predicate) } }
+                ?: throw IllegalStateException("NotificationListenerService is not connected")
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
new file mode 100644
index 0000000..3f8a1ae
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.utils
+
+import android.app.Instrumentation
+import android.graphics.Point
+import android.os.SystemClock
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.common.traces.component.IComponentMatcher
+import android.tools.common.traces.component.IComponentNameMatcher
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.tools.device.traces.parsers.toFlickerComponent
+import android.view.InputDevice
+import android.view.MotionEvent
+import android.view.ViewConfiguration
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary
+import org.junit.Assert.assertNotNull
+
+object SplitScreenUtils {
+    private const val TIMEOUT_MS = 3_000L
+    private const val DRAG_DURATION_MS = 1_000L
+    private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
+    private const val DIVIDER_BAR = "docked_divider_handle"
+    private const val OVERVIEW_SNAPSHOT = "snapshot"
+    private const val GESTURE_STEP_MS = 16L
+    private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L
+    private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
+
+    private val notificationScrollerSelector: BySelector
+        get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
+    private val notificationContentSelector: BySelector
+        get() = By.text("Flicker Test Notification")
+    private val dividerBarSelector: BySelector
+        get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
+    private val overviewSnapshotSelector: BySelector
+        get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT)
+
+    fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Primary.LABEL,
+            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+        )
+
+    fun getSecondary(instrumentation: Instrumentation): StandardAppHelper =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Secondary.LABEL,
+            ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent()
+        )
+
+    fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper =
+        NonResizeableAppHelper(instrumentation)
+
+    fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper =
+        NotificationAppHelper(instrumentation)
+
+    fun getIme(instrumentation: Instrumentation): ImeAppHelper = ImeAppHelper(instrumentation)
+
+    fun waitForSplitComplete(
+        wmHelper: WindowManagerStateHelper,
+        primaryApp: IComponentMatcher,
+        secondaryApp: IComponentMatcher,
+    ) {
+        wmHelper
+            .StateSyncBuilder()
+            .withWindowSurfaceAppeared(primaryApp)
+            .withWindowSurfaceAppeared(secondaryApp)
+            .withSplitDividerVisible()
+            .waitForAndVerify()
+    }
+
+    fun enterSplit(
+        wmHelper: WindowManagerStateHelper,
+        tapl: LauncherInstrumentation,
+        device: UiDevice,
+        primaryApp: StandardAppHelper,
+        secondaryApp: StandardAppHelper
+    ) {
+        primaryApp.launchViaIntent(wmHelper)
+        secondaryApp.launchViaIntent(wmHelper)
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+        splitFromOverview(tapl, device)
+        waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    fun enterSplitViaIntent(
+        wmHelper: WindowManagerStateHelper,
+        primaryApp: StandardAppHelper,
+        secondaryApp: StandardAppHelper
+    ) {
+        val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true")
+        primaryApp.launchViaIntent(wmHelper, null, null, stringExtras)
+        waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
+        // Note: The initial split position in landscape is different between tablet and phone.
+        // In landscape, tablet will let the first app split to right side, and phone will
+        // split to left side.
+        if (tapl.isTablet) {
+            // TAPL's currentTask on tablet is sometimes not what we expected if the overview
+            // contains more than 3 task views. We need to use uiautomator directly to find the
+            // second task to split.
+            tapl.workspace.switchToOverview().overviewActions.clickSplit()
+            val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
+            if (snapshots == null || snapshots.size < 1) {
+                error("Fail to find a overview snapshot to split.")
+            }
+
+            // Find the second task in the upper right corner in split select mode by sorting
+            // 'left' in descending order and 'top' in ascending order.
+            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+                t2.getVisibleBounds().left - t1.getVisibleBounds().left
+            }
+            snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+                t1.getVisibleBounds().top - t2.getVisibleBounds().top
+            }
+            snapshots[0].click()
+        } else {
+            tapl.workspace
+                .switchToOverview()
+                .currentTask
+                .tapMenu()
+                .tapSplitMenuItem()
+                .currentTask
+                .open()
+        }
+        SystemClock.sleep(TIMEOUT_MS)
+    }
+
+    fun dragFromNotificationToSplit(
+        instrumentation: Instrumentation,
+        device: UiDevice,
+        wmHelper: WindowManagerStateHelper
+    ) {
+        val displayBounds =
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+                ?: error("Display not found")
+
+        // Pull down the notifications
+        device.swipe(
+            displayBounds.centerX(),
+            5,
+            displayBounds.centerX(),
+            displayBounds.bottom,
+            50 /* steps */
+        )
+        SystemClock.sleep(TIMEOUT_MS)
+
+        // Find the target notification
+        val notificationScroller =
+            device.wait(Until.findObject(notificationScrollerSelector), TIMEOUT_MS)
+                ?: error("Unable to find view $notificationScrollerSelector")
+        var notificationContent = notificationScroller.findObject(notificationContentSelector)
+
+        while (notificationContent == null) {
+            device.swipe(
+                displayBounds.centerX(),
+                displayBounds.centerY(),
+                displayBounds.centerX(),
+                displayBounds.centerY() - 150,
+                20 /* steps */
+            )
+            notificationContent = notificationScroller.findObject(notificationContentSelector)
+        }
+
+        // Drag to split
+        val dragStart = notificationContent.visibleCenter
+        val dragMiddle = Point(dragStart.x + 50, dragStart.y)
+        val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
+        val downTime = SystemClock.uptimeMillis()
+
+        touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart)
+        // It needs a horizontal movement to trigger the drag
+        touchMove(
+            instrumentation,
+            downTime,
+            SystemClock.uptimeMillis(),
+            DRAG_DURATION_MS,
+            dragStart,
+            dragMiddle
+        )
+        touchMove(
+            instrumentation,
+            downTime,
+            SystemClock.uptimeMillis(),
+            DRAG_DURATION_MS,
+            dragMiddle,
+            dragEnd
+        )
+        // Wait for a while to start splitting
+        SystemClock.sleep(TIMEOUT_MS)
+        touch(
+            instrumentation,
+            MotionEvent.ACTION_UP,
+            downTime,
+            SystemClock.uptimeMillis(),
+            GESTURE_STEP_MS,
+            dragEnd
+        )
+        SystemClock.sleep(TIMEOUT_MS)
+    }
+
+    fun touch(
+        instrumentation: Instrumentation,
+        action: Int,
+        downTime: Long,
+        eventTime: Long,
+        duration: Long,
+        point: Point
+    ) {
+        val motionEvent =
+            MotionEvent.obtain(downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0)
+        motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
+        instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
+        motionEvent.recycle()
+        SystemClock.sleep(duration)
+    }
+
+    fun touchMove(
+        instrumentation: Instrumentation,
+        downTime: Long,
+        eventTime: Long,
+        duration: Long,
+        from: Point,
+        to: Point
+    ) {
+        val steps: Long = duration / GESTURE_STEP_MS
+        var currentTime = eventTime
+        var currentX = from.x.toFloat()
+        var currentY = from.y.toFloat()
+        val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
+        val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
+
+        for (i in 1..steps) {
+            val motionMove =
+                MotionEvent.obtain(
+                    downTime,
+                    currentTime,
+                    MotionEvent.ACTION_MOVE,
+                    currentX,
+                    currentY,
+                    0
+                )
+            motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
+            instrumentation.uiAutomation.injectInputEvent(motionMove, true)
+            motionMove.recycle()
+
+            currentTime += GESTURE_STEP_MS
+            if (i == steps - 1) {
+                currentX = to.x.toFloat()
+                currentY = to.y.toFloat()
+            } else {
+                currentX += stepX
+                currentY += stepY
+            }
+            SystemClock.sleep(GESTURE_STEP_MS)
+        }
+    }
+
+    fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) {
+        tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
+        val allApps = tapl.workspace.switchToAllApps()
+        allApps.freeze()
+        try {
+            allApps.getAppIcon(appName).dragToHotseat(0)
+        } finally {
+            allApps.unfreeze()
+        }
+    }
+
+    fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) {
+        val displayBounds =
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+                ?: error("Display not found")
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200)
+
+        wmHelper
+            .StateSyncBuilder()
+            .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
+            .waitForAndVerify()
+    }
+
+    fun dragDividerToDismissSplit(
+        device: UiDevice,
+        wmHelper: WindowManagerStateHelper,
+        dragToRight: Boolean,
+        dragToBottom: Boolean
+    ) {
+        val displayBounds =
+            wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace
+                ?: error("Display not found")
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        dividerBar.drag(
+            Point(
+                if (dragToRight) {
+                    displayBounds.right
+                } else {
+                    displayBounds.left
+                },
+                if (dragToBottom) {
+                    displayBounds.bottom
+                } else {
+                    displayBounds.top
+                }
+            )
+        )
+    }
+
+    fun doubleTapDividerToSwitch(device: UiDevice) {
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        val interval =
+            (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2
+        dividerBar.click()
+        SystemClock.sleep(interval.toLong())
+        dividerBar.click()
+    }
+
+    fun copyContentInSplit(
+        instrumentation: Instrumentation,
+        device: UiDevice,
+        sourceApp: IComponentNameMatcher,
+        destinationApp: IComponentNameMatcher,
+    ) {
+        // Copy text from sourceApp
+        val textView =
+            device.wait(
+                Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")),
+                TIMEOUT_MS
+            )
+        assertNotNull("Unable to find the TextView", textView)
+        textView.click(LONG_PRESS_TIME_MS)
+
+        val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+        assertNotNull("Unable to find the copy button", copyBtn)
+        copyBtn.click()
+
+        // Paste text to destinationApp
+        val editText =
+            device.wait(
+                Until.findObject(By.res(destinationApp.packageName, "plain_text_input")),
+                TIMEOUT_MS
+            )
+        assertNotNull("Unable to find the EditText", editText)
+        editText.click(LONG_PRESS_TIME_MS)
+
+        val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+        assertNotNull("Unable to find the paste button", pasteBtn)
+        pasteBtn.click()
+
+        // Verify text
+        if (!textView.text.contentEquals(editText.text)) {
+            error("Fail to copy content in split")
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/WaitUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/WaitUtils.kt
new file mode 100644
index 0000000..cf2df4e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/WaitUtils.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+@file:JvmName("WaitUtils")
+
+package com.android.wm.shell.flicker.utils
+
+import android.os.SystemClock
+
+private const val DEFAULT_TIMEOUT = 10000L
+private const val DEFAULT_POLL_INTERVAL = 1000L
+
+fun wait(condition: () -> Boolean): Boolean {
+    val (success, _) = waitForResult(extractor = condition, validator = { it })
+    return success
+}
+
+fun <R> waitForResult(
+    timeout: Long = DEFAULT_TIMEOUT,
+    interval: Long = DEFAULT_POLL_INTERVAL,
+    extractor: () -> R,
+    validator: (R) -> Boolean = { it != null }
+): Pair<Boolean, R?> {
+    val startTime = SystemClock.uptimeMillis()
+    do {
+        val result = extractor()
+        if (validator(result)) {
+            return (true to result)
+        }
+        SystemClock.sleep(interval)
+    } while (SystemClock.uptimeMillis() - startTime < timeout)
+
+    return (false to null)
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
new file mode 100644
index 0000000..406ada9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
@@ -0,0 +1,75 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+  size_kb: 63488
+  fill_policy: RING_BUFFER
+}
+
+data_sources {
+  config {
+    name: "linux.process_stats"
+    target_buffer: 0
+    # polled per-process memory counters and process/thread names.
+    # If you don't want the polled counters, remove the "process_stats_config"
+    # section, but keep the data source itself as it still provides on-demand
+    # thread/process naming for ftrace data below.
+    process_stats_config {
+      scan_all_processes_on_start: true
+    }
+  }
+}
+
+data_sources: {
+  config {
+    name: "linux.ftrace"
+    ftrace_config {
+      ftrace_events: "ftrace/print"
+      ftrace_events: "task/task_newtask"
+      ftrace_events: "task/task_rename"
+      atrace_categories: "ss"
+      atrace_categories: "wm"
+      atrace_categories: "am"
+      atrace_categories: "aidl"
+      atrace_categories: "input"
+      atrace_categories: "binder_driver"
+      atrace_categories: "sched_process_exit"
+      atrace_apps: "com.android.server.wm.flicker.testapp"
+      atrace_apps: "com.android.systemui"
+      atrace_apps: "com.android.wm.shell.flicker"
+      atrace_apps: "com.android.wm.shell.flicker.other"
+      atrace_apps: "com.android.wm.shell.flicker.bubbles"
+      atrace_apps: "com.android.wm.shell.flicker.pip"
+      atrace_apps: "com.android.wm.shell.flicker.splitscreen"
+      atrace_apps: "com.google.android.apps.nexuslauncher"
+    }
+  }
+}
+
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index ad4d97f..c2f184a 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -43,6 +43,7 @@
         "frameworks-base-testutils",
         "kotlinx-coroutines-android",
         "kotlinx-coroutines-core",
+        "mockito-kotlin2",
         "mockito-target-extended-minus-junit4",
         "truth-prebuilt",
         "testables",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
index 8278c67..0dc16f4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java
@@ -64,18 +64,27 @@
     }
 
     @Test
-    public void test_initialize() {
+    public void test_initialize_forStack() {
         assertThat(mOverflow.getExpandedView()).isNull();
 
-        mOverflow.initialize(mBubbleController);
+        mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false);
 
         assertThat(mOverflow.getExpandedView()).isNotNull();
         assertThat(mOverflow.getExpandedView().getBubbleKey()).isEqualTo(BubbleOverflow.KEY);
+        assertThat(mOverflow.getBubbleBarExpandedView()).isNull();
+    }
+
+    @Test
+    public void test_initialize_forBubbleBar() {
+        mOverflow.initialize(mBubbleController, /* forBubbleBar= */ true);
+
+        assertThat(mOverflow.getBubbleBarExpandedView()).isNotNull();
+        assertThat(mOverflow.getExpandedView()).isNull();
     }
 
     @Test
     public void test_cleanUpExpandedState() {
-        mOverflow.createExpandedView();
+        mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false);
         assertThat(mOverflow.getExpandedView()).isNotNull();
 
         mOverflow.cleanUpExpandedState();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java
new file mode 100644
index 0000000..a97c19f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java
@@ -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.wm.shell;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.os.IBinder;
+import android.window.WindowContainerToken;
+
+/**
+ * {@link WindowContainerToken} wrapper that supports a mock binder
+ */
+public class MockToken {
+    private final WindowContainerToken mToken;
+
+    public MockToken() {
+        mToken = mock(WindowContainerToken.class);
+        IBinder binder = mock(IBinder.class);
+        when(mToken.asBinder()).thenReturn(binder);
+    }
+
+    public WindowContainerToken token() {
+        return mToken;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 4fca8b4..2d93047 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -92,7 +92,7 @@
                 .build();
         final Animator animator = mAnimRunner.createAnimator(
                 info, mStartTransaction, mFinishTransaction,
-                () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */),
+                () -> mFinishCallback.onTransitionFinished(null /* wct */),
                 new ArrayList());
 
         // The animation should be empty when it is behind starting window.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
index ab1ccd4..0b2265d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
@@ -75,7 +75,7 @@
         assertNotNull(mAnimRunner);
         mAnimSpec = mAnimRunner.mAnimationSpec;
         assertNotNull(mAnimSpec);
-        mFinishCallback = (wct, wctCB) -> {};
+        mFinishCallback = (wct) -> {};
         spyOn(mController);
         spyOn(mAnimRunner);
         spyOn(mAnimSpec);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index ba34f1f7..270dbc4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -217,12 +217,10 @@
         doReturn(animator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
         mController.startAnimation(mTransition, info, mStartTransaction,
                 mFinishTransaction, mFinishCallback);
-        verify(mFinishCallback, never()).onTransitionFinished(any(), any());
+        verify(mFinishCallback, never()).onTransitionFinished(any());
         mController.mergeAnimation(mTransition, info, new SurfaceControl.Transaction(),
-                mTransition,
-                (wct, cb) -> {
-                });
-        verify(mFinishCallback).onTransitionFinished(any(), any());
+                mTransition, (wct) -> {});
+        verify(mFinishCallback).onTransitionFinished(any());
     }
 
     @Test
@@ -238,9 +236,9 @@
         mController.startAnimation(mTransition, info, mStartTransaction,
                 mFinishTransaction, mFinishCallback);
 
-        verify(mFinishCallback, never()).onTransitionFinished(any(), any());
+        verify(mFinishCallback, never()).onTransitionFinished(any());
         mController.onAnimationFinished(mTransition);
-        verify(mFinishCallback).onTransitionFinished(any(), any());
+        verify(mFinishCallback).onTransitionFinished(any());
 
         // Should not call finish when the finish has already been called.
         assertThrows(IllegalStateException.class,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
new file mode 100644
index 0000000..e359957
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.app.ActivityTaskManager
+import android.content.pm.LauncherApps
+import android.os.Handler
+import android.os.Looper
+import android.util.SparseArray
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.bubbles.storage.BubbleEntity
+import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
+import com.android.wm.shell.common.HandlerExecutor
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+
+class BubbleDataRepositoryTest : ShellTestCase() {
+
+    private val user0BubbleEntities = listOf(
+        BubbleEntity(
+            userId = 0,
+            packageName = "com.example.messenger",
+            shortcutId = "shortcut-1",
+            key = "0k1",
+            desiredHeight = 120,
+            desiredHeightResId = 0,
+            title = null,
+            taskId = 1,
+            locus = null,
+            isDismissable = true
+        ),
+        BubbleEntity(
+            userId = 10,
+            packageName = "com.example.chat",
+            shortcutId = "alice and bob",
+            key = "0k2",
+            desiredHeight = 0,
+            desiredHeightResId = 16537428,
+            title = "title",
+            taskId = 2,
+            locus = null
+        ),
+        BubbleEntity(
+            userId = 0,
+            packageName = "com.example.messenger",
+            shortcutId = "shortcut-2",
+            key = "0k3",
+            desiredHeight = 120,
+            desiredHeightResId = 0,
+            title = null,
+            taskId = ActivityTaskManager.INVALID_TASK_ID,
+            locus = null
+        )
+    )
+
+    private val user1BubbleEntities = listOf(
+        BubbleEntity(
+            userId = 1,
+            packageName = "com.example.messenger",
+            shortcutId = "shortcut-1",
+            key = "1k1",
+            desiredHeight = 120,
+            desiredHeightResId = 0,
+            title = null,
+            taskId = 3,
+            locus = null,
+            isDismissable = true
+        ),
+        BubbleEntity(
+            userId = 12,
+            packageName = "com.example.chat",
+            shortcutId = "alice and bob",
+            key = "1k2",
+            desiredHeight = 0,
+            desiredHeightResId = 16537428,
+            title = "title",
+            taskId = 4,
+            locus = null
+        ),
+        BubbleEntity(
+            userId = 1,
+            packageName = "com.example.messenger",
+            shortcutId = "shortcut-2",
+            key = "1k3",
+            desiredHeight = 120,
+            desiredHeightResId = 0,
+            title = null,
+            taskId = ActivityTaskManager.INVALID_TASK_ID,
+            locus = null
+        ),
+        BubbleEntity(
+            userId = 12,
+            packageName = "com.example.chat",
+            shortcutId = "alice",
+            key = "1k4",
+            desiredHeight = 0,
+            desiredHeightResId = 16537428,
+            title = "title",
+            taskId = 5,
+            locus = null
+        )
+    )
+
+    private val testHandler = Handler(Looper.getMainLooper())
+    private val mainExecutor = HandlerExecutor(testHandler)
+    private val launcherApps = mock<LauncherApps>()
+
+    private val persistedBubbles = SparseArray<List<BubbleEntity>>()
+
+    private lateinit var dataRepository: BubbleDataRepository
+    private lateinit var persistentRepository: BubblePersistentRepository
+
+    @Before
+    fun setup() {
+        persistentRepository = BubblePersistentRepository(mContext)
+        dataRepository = spy(BubbleDataRepository(launcherApps, mainExecutor, persistentRepository))
+
+        persistedBubbles.put(0, user0BubbleEntities)
+        persistedBubbles.put(1, user1BubbleEntities)
+    }
+
+    @After
+    fun teardown() {
+        // Clean up any persisted bubbles for the next run
+        persistentRepository.persistsToDisk(SparseArray())
+    }
+
+    @Test
+    fun testFilterForActiveUsersAndPersist_allValid() {
+        // Matches all the users in user0BubbleEntities & user1BubbleEntities
+        val activeUserIds = listOf(0, 10, 1, 12)
+
+        val validEntitiesByUser = dataRepository.filterForActiveUsersAndPersist(
+            activeUserIds, persistedBubbles)
+
+        // No invalid users, so no changes
+        assertThat(persistedBubbles).isEqualTo(validEntitiesByUser)
+
+        // No invalid users, so no persist to disk happened
+        verify(dataRepository, never()).persistToDisk(any())
+    }
+
+    @Test
+    fun testFilterForActiveUsersAndPersist_invalidParent() {
+        // When we start, we do have user 0 bubbles.
+        assertThat(persistedBubbles.get(0)).isNotEmpty()
+
+        val activeUserIds = listOf(10, 1, 12) // Missing user 0
+        val validEntitiesByUser = dataRepository.filterForActiveUsersAndPersist(
+            activeUserIds, persistedBubbles)
+
+        // We no longer have any user 0 bubbles.
+        assertThat(validEntitiesByUser.get(0)).isNull()
+        // User 1 bubbles should be the same.
+        assertThat(validEntitiesByUser.get(1)).isEqualTo(user1BubbleEntities)
+
+        // Verify that persist to disk happened with the new valid entities list.
+        verify(dataRepository).persistToDisk(validEntitiesByUser)
+    }
+
+    @Test
+    fun testFilterForActiveUsersAndPersist_invalidChild() {
+        // Build a list to compare against (remove all user 12 bubbles)
+        val (user1EntitiesWithUser12, user1EntitiesWithoutUser12) =
+            user1BubbleEntities.partition { it.userId == 12 }
+
+        // Verify we start with user 12 bubbles
+        assertThat(persistedBubbles.get(1).containsAll(user1EntitiesWithUser12)).isTrue()
+
+        val activeUserIds = listOf(0, 10, 1) // Missing user 1's child user 12
+        val validEntitiesByUser = dataRepository.filterForActiveUsersAndPersist(
+            activeUserIds, persistedBubbles)
+
+        // We no longer have any user 12 bubbles.
+        assertThat(validEntitiesByUser.get(1)).isEqualTo(user1EntitiesWithoutUser12)
+
+        // Verify that persist to disk happened with the new valid entities list.
+        verify(dataRepository).persistToDisk(validEntitiesByUser)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
new file mode 100644
index 0000000..139724f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.View.LAYOUT_DIRECTION_LTR;
+import static android.view.View.LAYOUT_DIRECTION_RTL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableResources;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests operations and the resulting state managed by {@link BubblePositioner}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class BubblePositionerTest extends ShellTestCase {
+
+    private static final int MIN_WIDTH_FOR_TABLET = 600;
+
+    private BubblePositioner mPositioner;
+    private Configuration mConfiguration;
+
+    @Mock
+    private WindowManager mWindowManager;
+    @Mock
+    private WindowMetrics mWindowMetrics;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mConfiguration = spy(new Configuration());
+        TestableResources testableResources = mContext.getOrCreateTestableResources();
+        testableResources.overrideConfiguration(mConfiguration);
+
+        mPositioner = new BubblePositioner(mContext, mWindowManager);
+    }
+
+    @Test
+    public void testUpdate() {
+        Insets insets = Insets.of(10, 20, 5, 15);
+        Rect screenBounds = new Rect(0, 0, 1000, 1200);
+        Rect availableRect = new Rect(screenBounds);
+        availableRect.inset(insets);
+
+        new WindowManagerConfig()
+                .setInsets(insets)
+                .setScreenBounds(screenBounds)
+                .setUpConfig();
+        mPositioner.update();
+
+        assertThat(mPositioner.getAvailableRect()).isEqualTo(availableRect);
+        assertThat(mPositioner.isLandscape()).isFalse();
+        assertThat(mPositioner.isLargeScreen()).isFalse();
+        assertThat(mPositioner.getInsets()).isEqualTo(insets);
+    }
+
+    @Test
+    public void testShowBubblesVertically_phonePortrait() {
+        new WindowManagerConfig().setOrientation(ORIENTATION_PORTRAIT).setUpConfig();
+        mPositioner.update();
+
+        assertThat(mPositioner.showBubblesVertically()).isFalse();
+    }
+
+    @Test
+    public void testShowBubblesVertically_phoneLandscape() {
+        new WindowManagerConfig().setOrientation(ORIENTATION_LANDSCAPE).setUpConfig();
+        mPositioner.update();
+
+        assertThat(mPositioner.isLandscape()).isTrue();
+        assertThat(mPositioner.showBubblesVertically()).isTrue();
+    }
+
+    @Test
+    public void testShowBubblesVertically_tablet() {
+        new WindowManagerConfig().setLargeScreen().setUpConfig();
+        mPositioner.update();
+
+        assertThat(mPositioner.showBubblesVertically()).isTrue();
+    }
+
+    /** If a resting position hasn't been set, calling it will return the default position. */
+    @Test
+    public void testGetRestingPosition_returnsDefaultPosition() {
+        new WindowManagerConfig().setUpConfig();
+        mPositioner.update();
+
+        PointF restingPosition = mPositioner.getRestingPosition();
+        PointF defaultPosition = mPositioner.getDefaultStartPosition();
+
+        assertThat(restingPosition).isEqualTo(defaultPosition);
+    }
+
+    /** If a resting position has been set, it'll return that instead of the default position. */
+    @Test
+    public void testGetRestingPosition_returnsRestingPosition() {
+        new WindowManagerConfig().setUpConfig();
+        mPositioner.update();
+
+        PointF restingPosition = new PointF(100, 100);
+        mPositioner.setRestingPosition(restingPosition);
+
+        assertThat(mPositioner.getRestingPosition()).isEqualTo(restingPosition);
+    }
+
+    /** Test that the default resting position on phone is in upper left. */
+    @Test
+    public void testGetRestingPosition_bubble_onPhone() {
+        new WindowManagerConfig().setUpConfig();
+        mPositioner.update();
+
+        RectF allowableStackRegion =
+                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+        PointF restingPosition = mPositioner.getRestingPosition();
+
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left);
+        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
+    }
+
+    @Test
+    public void testGetRestingPosition_bubble_onPhone_RTL() {
+        new WindowManagerConfig().setLayoutDirection(LAYOUT_DIRECTION_RTL).setUpConfig();
+        mPositioner.update();
+
+        RectF allowableStackRegion =
+                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+        PointF restingPosition = mPositioner.getRestingPosition();
+
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right);
+        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
+    }
+
+    /** Test that the default resting position on tablet is middle left. */
+    @Test
+    public void testGetRestingPosition_chatBubble_onTablet() {
+        new WindowManagerConfig().setLargeScreen().setUpConfig();
+        mPositioner.update();
+
+        RectF allowableStackRegion =
+                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+        PointF restingPosition = mPositioner.getRestingPosition();
+
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left);
+        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
+    }
+
+    @Test
+    public void testGetRestingPosition_chatBubble_onTablet_RTL() {
+        new WindowManagerConfig().setLargeScreen().setLayoutDirection(
+                LAYOUT_DIRECTION_RTL).setUpConfig();
+        mPositioner.update();
+
+        RectF allowableStackRegion =
+                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+        PointF restingPosition = mPositioner.getRestingPosition();
+
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right);
+        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
+    }
+
+    /**
+     * Calculates the Y position bubbles should be placed based on the config. Based on
+     * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and
+     * {@link BubbleStackView.RelativeStackPosition}.
+     */
+    private float getDefaultYPosition() {
+        final boolean isTablet = mPositioner.isLargeScreen();
+
+        // On tablet the position is centered, on phone it is an offset from the top.
+        final float desiredY = isTablet
+                ? mPositioner.getScreenRect().height() / 2f - (mPositioner.getBubbleSize() / 2f)
+                : mContext.getResources().getDimensionPixelOffset(
+                        R.dimen.bubble_stack_starting_offset_y);
+        // Since we're visually centering the bubbles on tablet, use total screen height rather
+        // than the available height.
+        final float height = isTablet
+                ? mPositioner.getScreenRect().height()
+                : mPositioner.getAvailableRect().height();
+        float offsetPercent = desiredY / height;
+        offsetPercent = Math.max(0f, Math.min(1f, offsetPercent));
+        final RectF allowableStackRegion =
+                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+        return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent;
+    }
+
+    /**
+     * Sets up window manager to return config values based on what you need for the test.
+     * By default it sets up a portrait phone without any insets.
+     */
+    private class WindowManagerConfig {
+        private Rect mScreenBounds = new Rect(0, 0, 1000, 2000);
+        private boolean mIsLargeScreen = false;
+        private int mOrientation = ORIENTATION_PORTRAIT;
+        private int mLayoutDirection = LAYOUT_DIRECTION_LTR;
+        private Insets mInsets = Insets.of(0, 0, 0, 0);
+
+        public WindowManagerConfig setScreenBounds(Rect screenBounds) {
+            mScreenBounds = screenBounds;
+            return this;
+        }
+
+        public WindowManagerConfig setLargeScreen() {
+            mIsLargeScreen = true;
+            return this;
+        }
+
+        public WindowManagerConfig setOrientation(int orientation) {
+            mOrientation = orientation;
+            return this;
+        }
+
+        public WindowManagerConfig setLayoutDirection(int layoutDirection) {
+            mLayoutDirection = layoutDirection;
+            return this;
+        }
+
+        public WindowManagerConfig setInsets(Insets insets) {
+            mInsets = insets;
+            return this;
+        }
+
+        public void setUpConfig() {
+            mConfiguration.smallestScreenWidthDp = mIsLargeScreen
+                    ? MIN_WIDTH_FOR_TABLET
+                    : MIN_WIDTH_FOR_TABLET - 1;
+            mConfiguration.orientation = mOrientation;
+
+            when(mConfiguration.getLayoutDirection()).thenReturn(mLayoutDirection);
+            WindowInsets windowInsets = mock(WindowInsets.class);
+            when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(mInsets);
+            when(mWindowMetrics.getWindowInsets()).thenReturn(windowInsets);
+            when(mWindowMetrics.getBounds()).thenReturn(mScreenBounds);
+            when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
new file mode 100644
index 0000000..d38b848
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.bar;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.drawable.ColorDrawable;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.core.content.ContextCompat;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class BubbleBarHandleViewTest extends ShellTestCase {
+    private BubbleBarHandleView mHandleView;
+
+    @Before
+    public void setup() {
+        mHandleView = new BubbleBarHandleView(mContext);
+    }
+
+    @Test
+    public void testUpdateHandleColor_lightBg() {
+        mHandleView.updateHandleColor(false /* isRegionDark */, false /* animated */);
+
+        assertTrue(mHandleView.getClipToOutline());
+        assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
+        ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
+        assertEquals(bgDrawable.getColor(),
+                ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_dark));
+    }
+
+    @Test
+    public void testUpdateHandleColor_darkBg() {
+        mHandleView.updateHandleColor(true /* isRegionDark */, false /* animated */);
+
+        assertTrue(mHandleView.getClipToOutline());
+        assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
+        ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
+        assertEquals(bgDrawable.getColor(),
+                ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light));
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt
new file mode 100644
index 0000000..9dc816b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.MockToken
+import com.android.wm.shell.ShellTestCase
+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.any
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LaunchAdjacentControllerTest : ShellTestCase() {
+
+    private lateinit var controller: LaunchAdjacentController
+
+    @Mock private lateinit var syncQueue: SyncTransactionQueue
+
+    @Before
+    fun setUp() {
+        controller = LaunchAdjacentController(syncQueue)
+    }
+
+    @Test
+    fun newInstance_enabledByDefault() {
+        assertThat(controller.launchAdjacentEnabled).isTrue()
+    }
+
+    @Test
+    fun setLaunchAdjacentRoot_launchAdjacentEnabled_setsFlagRoot() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        val wct = getLatestTransactionOrFail()
+        assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+    }
+
+    @Test
+    fun setLaunchAdjacentRoot_launchAdjacentDisabled_doesNotUpdateFlagRoot() {
+        val token = MockToken().token()
+        controller.launchAdjacentEnabled = false
+        controller.setLaunchAdjacentRoot(token)
+        verify(syncQueue, never()).queue(any())
+    }
+
+    @Test
+    fun clearLaunchAdjacentRoot_launchAdjacentEnabled_clearsFlagRoot() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        controller.clearLaunchAdjacentRoot()
+        val wct = getLatestTransactionOrFail()
+        assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+    }
+
+    @Test
+    fun clearLaunchAdjacentRoot_launchAdjacentDisabled_clearsFlagRoot() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        controller.launchAdjacentEnabled = false
+        clearInvocations(syncQueue)
+
+        controller.clearLaunchAdjacentRoot()
+        val wct = getLatestTransactionOrFail()
+        assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+    }
+
+    @Test
+    fun setLaunchAdjacentEnabled_wasDisabledWithContainerSet_setsFlagRoot() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        controller.launchAdjacentEnabled = false
+        clearInvocations(syncQueue)
+
+        controller.launchAdjacentEnabled = true
+        val wct = getLatestTransactionOrFail()
+        assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+    }
+
+    @Test
+    fun setLaunchAdjacentEnabled_containerNotSet_doesNotUpdateFlagRoot() {
+        controller.launchAdjacentEnabled = false
+        controller.launchAdjacentEnabled = true
+        verify(syncQueue, never()).queue(any())
+    }
+
+    @Test
+    fun setLaunchAdjacentEnabled_multipleTimes_setsFlagRootOnce() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        controller.launchAdjacentEnabled = true
+        controller.launchAdjacentEnabled = true
+        // Only execute once
+        verify(syncQueue).queue(any())
+    }
+
+    @Test
+    fun setLaunchAdjacentDisabled_containerSet_clearsFlagRoot() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        controller.launchAdjacentEnabled = false
+        val wct = getLatestTransactionOrFail()
+        assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+    }
+
+    @Test
+    fun setLaunchAdjacentDisabled_containerNotSet_doesNotUpdateFlagRoot() {
+        controller.launchAdjacentEnabled = false
+        verify(syncQueue, never()).queue(any())
+    }
+
+    @Test
+    fun setLaunchAdjacentDisabled_multipleTimes_setsFlagRootOnce() {
+        val token = MockToken().token()
+        controller.setLaunchAdjacentRoot(token)
+        clearInvocations(syncQueue)
+        controller.launchAdjacentEnabled = false
+        controller.launchAdjacentEnabled = false
+        // Only execute once
+        verify(syncQueue).queue(any())
+    }
+
+    private fun getLatestTransactionOrFail(): WindowContainerTransaction {
+        val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        verify(syncQueue, atLeastOnce()).queue(arg.capture())
+        return arg.allValues.last().also { assertThat(it).isNotNull() }
+    }
+}
+
+private fun WindowContainerTransaction.getSetLaunchAdjacentFlagRootContainer(): IBinder {
+    return hierarchyOps
+        // Find the operation with the correct type
+        .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT }
+        // For set flag root operation, toTop is false
+        .filter { op -> !op.toTop }
+        .map { it.container }
+        .first()
+}
+
+private fun WindowContainerTransaction.getClearLaunchAdjacentFlagRootContainer(): IBinder {
+    return hierarchyOps
+        // Find the operation with the correct type
+        .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT }
+        // For clear flag root operation, toTop is true
+        .filter { op -> op.toTop }
+        .map { it.container }
+        .first()
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
new file mode 100644
index 0000000..145c8f0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common.split;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CURSOR_HOVER_STATES_ENABLED;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.view.InputDevice;
+import android.view.InsetsState;
+import android.view.MotionEvent;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for {@link DividerView} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DividerViewTest extends ShellTestCase {
+    private @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
+    private @Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler;
+    private @Mock DisplayController mDisplayController;
+    private @Mock DisplayImeController mDisplayImeController;
+    private @Mock ShellTaskOrganizer mTaskOrganizer;
+    private SplitLayout mSplitLayout;
+    private DividerView mDividerView;
+
+    @Before
+    @UiThreadTest
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        Configuration configuration = getConfiguration();
+        mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration,
+                mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController,
+                mTaskOrganizer, SplitLayout.PARALLAX_NONE);
+        SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
+                mContext,
+                configuration, mCallbacks);
+        splitWindowManager.init(mSplitLayout, new InsetsState());
+        mDividerView = spy((DividerView) splitWindowManager.getDividerView());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testHoverDividerView() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED,
+                "true", false);
+
+        Rect dividerBounds = mSplitLayout.getDividerBounds();
+        int x = dividerBounds.centerX();
+        int y = dividerBounds.centerY();
+        long downTime = SystemClock.uptimeMillis();
+        mDividerView.onHoverEvent(getMotionEvent(downTime, MotionEvent.ACTION_HOVER_ENTER, x, y));
+
+        verify(mDividerView, times(1)).setHovering();
+
+        mDividerView.onHoverEvent(getMotionEvent(downTime, MotionEvent.ACTION_HOVER_EXIT, x, y));
+
+        verify(mDividerView, times(1)).releaseHovering();
+
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED,
+                "false", false);
+    }
+
+    private static MotionEvent getMotionEvent(long eventTime, int action, float x, float y) {
+        MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
+        properties.id = 0;
+        properties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+
+        MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+        coords.pressure = 1;
+        coords.size = 1;
+        coords.x = x;
+        coords.y = y;
+
+        return MotionEvent.obtain(eventTime, eventTime, action, 1,
+                new MotionEvent.PointerProperties[]{properties},
+                new MotionEvent.PointerCoords[]{coords}, 0, 0, 1.0f, 1.0f, 0, 0,
+                InputDevice.SOURCE_TOUCHSCREEN, 0);
+    }
+
+    private static Configuration getConfiguration() {
+        final Configuration configuration = new Configuration();
+        configuration.unset();
+        configuration.orientation = ORIENTATION_LANDSCAPE;
+        configuration.windowConfiguration.setRotation(0);
+        configuration.windowConfiguration.setBounds(new Rect(0, 0, 1080, 2160));
+        return configuration;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 443cea2..fe2da5d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -38,7 +38,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index a6501f0..efc69ebd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -58,6 +58,8 @@
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
+import dagger.Lazy;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -66,8 +68,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import dagger.Lazy;
-
 /**
  * Tests for {@link CompatUIController}.
  *
@@ -82,21 +82,36 @@
 
     private CompatUIController mController;
     private ShellInit mShellInit;
-    private @Mock ShellController mMockShellController;
-    private @Mock DisplayController mMockDisplayController;
-    private @Mock DisplayInsetsController mMockDisplayInsetsController;
-    private @Mock DisplayLayout mMockDisplayLayout;
-    private @Mock DisplayImeController mMockImeController;
-    private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
-    private @Mock SyncTransactionQueue mMockSyncQueue;
-    private @Mock ShellExecutor mMockExecutor;
-    private @Mock Lazy<Transitions> mMockTransitionsLazy;
-    private @Mock CompatUIWindowManager mMockCompatLayout;
-    private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout;
-    private @Mock RestartDialogWindowManager mMockRestartDialogLayout;
-    private @Mock DockStateReader mDockStateReader;
-    private @Mock CompatUIConfiguration mCompatUIConfiguration;
-    private @Mock CompatUIShellCommandHandler mCompatUIShellCommandHandler;
+    @Mock
+    private ShellController mMockShellController;
+    @Mock
+    private DisplayController mMockDisplayController;
+    @Mock
+    private DisplayInsetsController mMockDisplayInsetsController;
+    @Mock
+    private DisplayLayout mMockDisplayLayout;
+    @Mock
+    private DisplayImeController mMockImeController;
+    @Mock
+    private ShellTaskOrganizer.TaskListener mMockTaskListener;
+    @Mock
+    private SyncTransactionQueue mMockSyncQueue;
+    @Mock
+    private ShellExecutor mMockExecutor;
+    @Mock
+    private Lazy<Transitions> mMockTransitionsLazy;
+    @Mock
+    private CompatUIWindowManager mMockCompatLayout;
+    @Mock
+    private LetterboxEduWindowManager mMockLetterboxEduLayout;
+    @Mock
+    private RestartDialogWindowManager mMockRestartDialogLayout;
+    @Mock
+    private DockStateReader mDockStateReader;
+    @Mock
+    private CompatUIConfiguration mCompatUIConfiguration;
+    @Mock
+    private CompatUIShellCommandHandler mCompatUIShellCommandHandler;
 
     @Captor
     ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 5f294d5..3bce2b8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -44,7 +44,7 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
 
 import junit.framework.Assert;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 78c3cbd..4c837e6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -53,7 +53,7 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
 
 import junit.framework.Assert;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
index 973a99c..a802f15a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -40,6 +40,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.function.BiConsumer;
+
 /**
  * Tests for {@link ReachabilityEduWindowManager}.
  *
@@ -57,6 +59,8 @@
     private CompatUIConfiguration mCompatUIConfiguration;
     @Mock
     private DisplayLayout mDisplayLayout;
+    @Mock
+    private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback;
     private TestShellExecutor mExecutor;
     private TaskInfo mTaskInfo;
     private ReachabilityEduWindowManager mWindowManager;
@@ -104,6 +108,7 @@
 
     private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) {
         return new ReachabilityEduWindowManager(mContext, taskInfo, mSyncTransactionQueue,
-                mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor);
+                mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor,
+                mOnDismissCallback);
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
new file mode 100644
index 0000000..1fee153
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.app.TaskInfo.CameraCompatControlState;
+import android.content.ComponentName;
+import android.testing.AndroidTestingRunner;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.SurfaceControlViewHost;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import junit.framework.Assert;
+
+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 java.util.function.BiConsumer;
+
+/**
+ * Tests for {@link UserAspectRatioSettingsLayout}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:UserAspectRatioSettingsLayoutTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
+
+    private static final int TASK_ID = 1;
+
+    @Mock
+    private SyncTransactionQueue mSyncTransactionQueue;
+    @Mock
+    private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener>
+            mOnUserAspectRatioSettingsButtonClicked;
+    @Mock
+    private ShellTaskOrganizer.TaskListener mTaskListener;
+    @Mock
+    private SurfaceControlViewHost mViewHost;
+    @Captor
+    private ArgumentCaptor<ShellTaskOrganizer.TaskListener> mUserAspectRatioTaskListenerCaptor;
+    @Captor
+    private ArgumentCaptor<TaskInfo> mUserAspectRationTaskInfoCaptor;
+
+    private UserAspectRatioSettingsWindowManager mWindowManager;
+    private UserAspectRatioSettingsLayout mLayout;
+    private TaskInfo mTaskInfo;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+        mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
+                mSyncTransactionQueue, mTaskListener, new DisplayLayout(),
+                new CompatUIController.CompatUIHintsState(),
+                mOnUserAspectRatioSettingsButtonClicked, new TestShellExecutor());
+
+        mLayout = (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate(
+                R.layout.user_aspect_ratio_settings_layout, null);
+        mLayout.inject(mWindowManager);
+
+        spyOn(mWindowManager);
+        spyOn(mLayout);
+        doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
+        doReturn(mLayout).when(mWindowManager).inflateLayout();
+    }
+
+    @Test
+    public void testOnClickForUserAspectRatioSettingsButton() {
+        final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button);
+        button.performClick();
+
+        verify(mWindowManager).onUserAspectRatioSettingsButtonClicked();
+        verify(mOnUserAspectRatioSettingsButtonClicked).accept(
+                mUserAspectRationTaskInfoCaptor.capture(),
+                mUserAspectRatioTaskListenerCaptor.capture());
+        final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result =
+                new Pair<>(mUserAspectRationTaskInfoCaptor.getValue(),
+                        mUserAspectRatioTaskListenerCaptor.getValue());
+        Assert.assertEquals(mTaskInfo, result.first);
+        Assert.assertEquals(mTaskListener, result.second);
+    }
+
+    @Test
+    public void testOnLongClickForUserAspectRatioButton() {
+        doNothing().when(mWindowManager).onUserAspectRatioSettingsButtonLongClicked();
+
+        final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button);
+        button.performLongClick();
+
+        verify(mWindowManager).onUserAspectRatioSettingsButtonLongClicked();
+    }
+
+    @Test
+    public void testOnClickForUserAspectRatioSettingsHint() {
+        mWindowManager.mHasUserAspectRatioSettingsButton = true;
+        mWindowManager.createLayout(/* canShow= */ true);
+        final LinearLayout sizeCompatHint = mLayout.findViewById(
+                R.id.user_aspect_ratio_settings_hint);
+        sizeCompatHint.performClick();
+
+        verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ false);
+    }
+
+    private static TaskInfo createTaskInfo(boolean hasSizeCompat,
+            @CameraCompatControlState int cameraCompatControlState) {
+        ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+        taskInfo.taskId = TASK_ID;
+        taskInfo.topActivityInSizeCompat = hasSizeCompat;
+        taskInfo.cameraCompatControlState = cameraCompatControlState;
+        taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
+        return taskInfo;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
new file mode 100644
index 0000000..b48538c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.view.WindowInsets.Type.navigationBars;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.app.TaskInfo;
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.util.Pair;
+import android.view.DisplayInfo;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
+
+import junit.framework.Assert;
+
+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 java.util.HashSet;
+import java.util.Set;
+import java.util.function.BiConsumer;
+
+/**
+ * Tests for {@link UserAspectRatioSettingsWindowManager}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:UserAspectRatioSettingsWindowManagerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
+
+    private static final int TASK_ID = 1;
+
+    @Mock private SyncTransactionQueue mSyncTransactionQueue;
+    @Mock
+    private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener>
+            mOnUserAspectRatioSettingsButtonClicked;
+    @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+    @Mock private UserAspectRatioSettingsLayout mLayout;
+    @Mock private SurfaceControlViewHost mViewHost;
+    @Captor
+    private ArgumentCaptor<ShellTaskOrganizer.TaskListener> mUserAspectRatioTaskListenerCaptor;
+    @Captor
+    private ArgumentCaptor<TaskInfo> mUserAspectRationTaskInfoCaptor;
+
+    private final Set<String> mPackageNameCache = new HashSet<>();
+
+    private UserAspectRatioSettingsWindowManager mWindowManager;
+    private TaskInfo mTaskInfo;
+
+    private TestShellExecutor mExecutor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mExecutor = new TestShellExecutor();
+        mTaskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+                false, /* topActivityBoundsLetterboxed */ true);
+        mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
+                mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+                mOnUserAspectRatioSettingsButtonClicked, mExecutor);
+        spyOn(mWindowManager);
+        doReturn(mLayout).when(mWindowManager).inflateLayout();
+        doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
+    }
+
+    @Test
+    public void testCreateUserAspectRatioButton() {
+        // Doesn't create layout if show is false.
+        mWindowManager.mHasUserAspectRatioSettingsButton = true;
+        assertTrue(mWindowManager.createLayout(/* canShow= */ false));
+
+        verify(mWindowManager, never()).inflateLayout();
+
+        // Doesn't create hint popup.
+        mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true;
+        assertTrue(mWindowManager.createLayout(/* canShow= */ true));
+
+        verify(mWindowManager).inflateLayout();
+        mExecutor.flushAll();
+        verify(mLayout).setUserAspectRatioButtonVisibility(/* show= */ true);
+        verify(mLayout, never()).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+
+        // Creates hint popup.
+        clearInvocations(mWindowManager);
+        clearInvocations(mLayout);
+        mWindowManager.release();
+        mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = false;
+        assertTrue(mWindowManager.createLayout(/* canShow= */ true));
+
+        verify(mWindowManager).inflateLayout();
+        assertNotNull(mLayout);
+        mExecutor.flushAll();
+        verify(mLayout).setUserAspectRatioButtonVisibility(/* show= */ true);
+        verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+        assertTrue(mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint);
+
+        // Returns false and doesn't create layout if mHasUserAspectRatioSettingsButton is false.
+        clearInvocations(mWindowManager);
+        mWindowManager.release();
+        mWindowManager.mHasUserAspectRatioSettingsButton = false;
+        assertFalse(mWindowManager.createLayout(/* canShow= */ true));
+
+        verify(mWindowManager, never()).inflateLayout();
+    }
+
+    @Test
+    public void testRelease() {
+        mWindowManager.mHasUserAspectRatioSettingsButton = true;
+        mWindowManager.createLayout(/* canShow= */ true);
+
+        verify(mWindowManager).inflateLayout();
+
+        mWindowManager.release();
+
+        verify(mViewHost).release();
+    }
+
+    @Test
+    public void testUpdateCompatInfo() {
+        mWindowManager.mHasUserAspectRatioSettingsButton = true;
+        mWindowManager.createLayout(/* canShow= */ true);
+
+        // No diff
+        clearInvocations(mWindowManager);
+        TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+                true, /* topActivityBoundsLetterboxed */ true);
+        assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
+
+        verify(mWindowManager, never()).updateSurfacePosition();
+        verify(mWindowManager, never()).release();
+        verify(mWindowManager, never()).createLayout(anyBoolean());
+
+
+        // Change task listener, recreate button.
+        clearInvocations(mWindowManager);
+        final ShellTaskOrganizer.TaskListener newTaskListener = mock(
+                ShellTaskOrganizer.TaskListener.class);
+        assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+        verify(mWindowManager).release();
+        verify(mWindowManager).createLayout(/* canShow= */ true);
+
+        // Change has eligibleForUserAspectRatioButton to false, dispose the component
+        clearInvocations(mWindowManager);
+        clearInvocations(mLayout);
+        taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+                false, /* topActivityBoundsLetterboxed */ true);
+        assertFalse(
+                mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+        verify(mWindowManager).release();
+    }
+
+    @Test
+    public void testUpdateCompatInfoLayoutNotInflatedYet() {
+        mWindowManager.mHasUserAspectRatioSettingsButton = true;
+        mWindowManager.createLayout(/* canShow= */ false);
+
+        verify(mWindowManager, never()).inflateLayout();
+
+        // Change topActivityInSizeCompat to false and pass canShow true, layout shouldn't be
+        // inflated
+        clearInvocations(mWindowManager);
+        TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+                false, /* topActivityBoundsLetterboxed */ true);
+        mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+        verify(mWindowManager, never()).inflateLayout();
+
+        // Change topActivityInSizeCompat to true and pass canShow true, layout should be inflated.
+        clearInvocations(mWindowManager);
+        taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+                true, /* topActivityBoundsLetterboxed */ true);
+        mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+        verify(mWindowManager).inflateLayout();
+    }
+
+    @Test
+    public void testUpdateDisplayLayout() {
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = 1000;
+        displayInfo.logicalHeight = 2000;
+        final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo,
+                mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
+
+        mWindowManager.updateDisplayLayout(displayLayout1);
+        verify(mWindowManager).updateSurfacePosition();
+
+        // No update if the display bounds is the same.
+        clearInvocations(mWindowManager);
+        final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo,
+                mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
+        mWindowManager.updateDisplayLayout(displayLayout2);
+        verify(mWindowManager, never()).updateSurfacePosition();
+    }
+
+    @Test
+    public void testUpdateDisplayLayoutInsets() {
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = 1000;
+        displayInfo.logicalHeight = 2000;
+        final DisplayLayout displayLayout = new DisplayLayout(displayInfo,
+                mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false);
+
+        mWindowManager.updateDisplayLayout(displayLayout);
+        verify(mWindowManager).updateSurfacePosition();
+
+        // Update if the insets change on the existing display layout
+        clearInvocations(mWindowManager);
+        InsetsState insetsState = new InsetsState();
+        insetsState.setDisplayFrame(new Rect(0, 0, 1000, 2000));
+        InsetsSource insetsSource = new InsetsSource(
+                InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+        insetsSource.setFrame(0, 1800, 1000, 2000);
+        insetsState.addSource(insetsSource);
+        displayLayout.setInsets(mContext.getResources(), insetsState);
+        mWindowManager.updateDisplayLayout(displayLayout);
+        verify(mWindowManager).updateSurfacePosition();
+    }
+
+    @Test
+    public void testUpdateVisibility() {
+        // Create button if it is not created.
+        mWindowManager.removeLayout();
+        mWindowManager.mHasUserAspectRatioSettingsButton = true;
+        mWindowManager.updateVisibility(/* canShow= */ true);
+
+        verify(mWindowManager).createLayout(/* canShow= */ true);
+
+        // Hide button.
+        clearInvocations(mWindowManager);
+        doReturn(View.VISIBLE).when(mLayout).getVisibility();
+        mWindowManager.updateVisibility(/* canShow= */ false);
+
+        verify(mWindowManager, never()).createLayout(anyBoolean());
+        verify(mLayout).setVisibility(View.GONE);
+
+        // Show button.
+        doReturn(View.GONE).when(mLayout).getVisibility();
+        mWindowManager.updateVisibility(/* canShow= */ true);
+
+        verify(mWindowManager, never()).createLayout(anyBoolean());
+        verify(mLayout).setVisibility(View.VISIBLE);
+    }
+
+    @Test
+    public void testAttachToParentSurface() {
+        final SurfaceControl.Builder b = new SurfaceControl.Builder();
+        mWindowManager.attachToParentSurface(b);
+
+        verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b);
+    }
+
+    @Test
+    public void testOnUserAspectRatioButtonClicked() {
+        mWindowManager.onUserAspectRatioSettingsButtonClicked();
+
+        verify(mOnUserAspectRatioSettingsButtonClicked).accept(
+                mUserAspectRationTaskInfoCaptor.capture(),
+                mUserAspectRatioTaskListenerCaptor.capture());
+        final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result =
+                new Pair<>(mUserAspectRationTaskInfoCaptor.getValue(),
+                        mUserAspectRatioTaskListenerCaptor.getValue());
+        Assert.assertEquals(mTaskInfo, result.first);
+        Assert.assertEquals(mTaskListener, result.second);
+    }
+
+    @Test
+    public void testOnUserAspectRatioButtonLongClicked_showHint() {
+       // Not create hint popup.
+        mWindowManager.mHasUserAspectRatioSettingsButton = true;
+        mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true;
+        mWindowManager.createLayout(/* canShow= */ true);
+
+        verify(mWindowManager).inflateLayout();
+        verify(mLayout, never()).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+
+        mWindowManager.onUserAspectRatioSettingsButtonLongClicked();
+
+        verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ true);
+    }
+
+    @Test
+    public void testWhenDockedStateHasChanged_needsToBeRecreated() {
+        ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo();
+        newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK;
+
+        Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
+    }
+
+    private static TaskInfo createTaskInfo(boolean eligibleForUserAspectRatioButton,
+            boolean topActivityBoundsLetterboxed) {
+        ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+        taskInfo.taskId = TASK_ID;
+        taskInfo.topActivityEligibleForUserAspectRatioButton = eligibleForUserAspectRatioButton;
+        taskInfo.topActivityBoundsLetterboxed = topActivityBoundsLetterboxed;
+        taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
+        taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
+        return taskInfo;
+    }
+}
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 d6387ee..605a762 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
@@ -59,6 +59,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.MockToken;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
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 3bc2f0e..3fe78ef 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
@@ -129,6 +129,18 @@
     }
 
     @Test
+    fun addListener_notifiesStashed() {
+        repo.setStashed(DEFAULT_DISPLAY, true)
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+        executor.flushAll()
+
+        assertThat(listener.stashedOnDefaultDisplay).isTrue()
+        assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
+    }
+
+    @Test
     fun addListener_tasksOnDifferentDisplay_doesNotNotify() {
         repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
         val listener = TestVisibilityListener()
@@ -313,6 +325,65 @@
         assertThat(tasks.first()).isEqualTo(6)
     }
 
+    @Test
+    fun setStashed_stateIsUpdatedForTheDisplay() {
+        repo.setStashed(DEFAULT_DISPLAY, true)
+        assertThat(repo.isStashed(DEFAULT_DISPLAY)).isTrue()
+        assertThat(repo.isStashed(SECOND_DISPLAY)).isFalse()
+
+        repo.setStashed(DEFAULT_DISPLAY, false)
+        assertThat(repo.isStashed(DEFAULT_DISPLAY)).isFalse()
+    }
+
+    @Test
+    fun setStashed_notifyListener() {
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+        repo.setStashed(DEFAULT_DISPLAY, true)
+        executor.flushAll()
+        assertThat(listener.stashedOnDefaultDisplay).isTrue()
+        assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
+
+        repo.setStashed(DEFAULT_DISPLAY, false)
+        executor.flushAll()
+        assertThat(listener.stashedOnDefaultDisplay).isFalse()
+        assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(2)
+    }
+
+    @Test
+    fun setStashed_secondCallDoesNotNotify() {
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+        repo.setStashed(DEFAULT_DISPLAY, true)
+        repo.setStashed(DEFAULT_DISPLAY, true)
+        executor.flushAll()
+        assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
+    }
+
+    @Test
+    fun setStashed_tracksPerDisplay() {
+        val listener = TestVisibilityListener()
+        val executor = TestShellExecutor()
+        repo.addVisibleTasksListener(listener, executor)
+
+        repo.setStashed(DEFAULT_DISPLAY, true)
+        executor.flushAll()
+        assertThat(listener.stashedOnDefaultDisplay).isTrue()
+        assertThat(listener.stashedOnSecondaryDisplay).isFalse()
+
+        repo.setStashed(SECOND_DISPLAY, true)
+        executor.flushAll()
+        assertThat(listener.stashedOnDefaultDisplay).isTrue()
+        assertThat(listener.stashedOnSecondaryDisplay).isTrue()
+
+        repo.setStashed(DEFAULT_DISPLAY, false)
+        executor.flushAll()
+        assertThat(listener.stashedOnDefaultDisplay).isFalse()
+        assertThat(listener.stashedOnSecondaryDisplay).isTrue()
+    }
+
     class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
         var activeChangesOnDefaultDisplay = 0
         var activeChangesOnSecondaryDisplay = 0
@@ -332,6 +403,12 @@
         var visibleChangesOnDefaultDisplay = 0
         var visibleChangesOnSecondaryDisplay = 0
 
+        var stashedOnDefaultDisplay = false
+        var stashedOnSecondaryDisplay = false
+
+        var stashedChangesOnDefaultDisplay = 0
+        var stashedChangesOnSecondaryDisplay = 0
+
         override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
             when (displayId) {
                 DEFAULT_DISPLAY -> {
@@ -345,6 +422,20 @@
                 else -> fail("Visible task listener received unexpected display id: $displayId")
             }
         }
+
+        override fun onStashedChanged(displayId: Int, stashed: Boolean) {
+            when (displayId) {
+                DEFAULT_DISPLAY -> {
+                    stashedOnDefaultDisplay = stashed
+                    stashedChangesOnDefaultDisplay++
+                }
+                SECOND_DISPLAY -> {
+                    stashedOnSecondaryDisplay = stashed
+                    stashedChangesOnDefaultDisplay++
+                }
+                else -> fail("Visible task listener received unexpected display id: $displayId")
+            }
+        }
     }
 
     companion object {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 1335ebf..5d87cf8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -39,21 +39,25 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.ExtendedMockito.never
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.wm.shell.MockToken
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.TestShellExecutor
 import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.LaunchAdjacentController
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
+import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.After
@@ -77,6 +81,7 @@
 class DesktopTasksControllerTest : ShellTestCase() {
 
     @Mock lateinit var testExecutor: ShellExecutor
+    @Mock lateinit var shellCommandHandler: ShellCommandHandler
     @Mock lateinit var shellController: ShellController
     @Mock lateinit var displayController: DisplayController
     @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
@@ -85,12 +90,17 @@
     @Mock lateinit var transitions: Transitions
     @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
     @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
+    @Mock lateinit var mToggleResizeDesktopTaskTransitionHandler:
+            ToggleResizeDesktopTaskTransitionHandler
+    @Mock lateinit var launchAdjacentController: LaunchAdjacentController
+    @Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var controller: DesktopTasksController
     private lateinit var shellInit: ShellInit
     private lateinit var desktopModeTaskRepository: DesktopModeTaskRepository
 
+    private val shellExecutor = TestShellExecutor()
     // Mock running tasks are registered here so we can get the list from mock shell task organizer
     private val runningTasks = mutableListOf<RunningTaskInfo>()
 
@@ -114,6 +124,7 @@
         return DesktopTasksController(
             context,
             shellInit,
+            shellCommandHandler,
             shellController,
             displayController,
             shellTaskOrganizer,
@@ -122,8 +133,10 @@
             transitions,
             enterDesktopTransitionHandler,
             exitDesktopTransitionHandler,
+            mToggleResizeDesktopTaskTransitionHandler,
             desktopModeTaskRepository,
-            TestShellExecutor()
+            launchAdjacentController,
+            shellExecutor
         )
     }
 
@@ -262,17 +275,28 @@
     }
 
     @Test
-    fun moveToDesktop() {
+    fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() {
         val task = setUpFullscreenTask()
-        controller.moveToDesktop(task)
-        val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+        task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
+        controller.moveToDesktop(desktopModeWindowDecoration, task)
+        val wct = getLatestMoveToDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
             .isEqualTo(WINDOWING_MODE_FREEFORM)
     }
 
     @Test
+    fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() {
+        val task = setUpFullscreenTask()
+        task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
+        controller.moveToDesktop(desktopModeWindowDecoration, task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+                .isEqualTo(WINDOWING_MODE_UNDEFINED)
+    }
+
+    @Test
     fun moveToDesktop_nonExistentTask_doesNothing() {
-        controller.moveToDesktop(999)
+        controller.moveToDesktop(desktopModeWindowDecoration, 999)
         verifyWCTNotExecuted()
     }
 
@@ -283,9 +307,9 @@
         val fullscreenTask = setUpFullscreenTask()
         markTaskHidden(freeformTask)
 
-        controller.moveToDesktop(fullscreenTask)
+        controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTask)
 
-        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+        with(getLatestMoveToDesktopWct()) {
             // Operations should include home task, freeform task
             assertThat(hierarchyOps).hasSize(3)
             assertReorderSequence(homeTask, freeformTask, fullscreenTask)
@@ -305,9 +329,9 @@
         val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
         markTaskHidden(freeformTaskSecond)
 
-        controller.moveToDesktop(fullscreenTaskDefault)
+        controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTaskDefault)
 
-        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+        with(getLatestMoveToDesktopWct()) {
             // Check that hierarchy operations do not include tasks from second display
             assertThat(hierarchyOps.map { it.container })
                 .doesNotContain(homeTaskSecond.token.asBinder())
@@ -317,12 +341,23 @@
     }
 
     @Test
-    fun moveToFullscreen() {
+    fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
         val task = setUpFreeformTask()
+        task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
         controller.moveToFullscreen(task)
         val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
-            .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+            .isEqualTo(WINDOWING_MODE_UNDEFINED)
+    }
+
+    @Test
+    fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() {
+        val task = setUpFreeformTask()
+        task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
+        controller.moveToFullscreen(task)
+        val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+        assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+                .isEqualTo(WINDOWING_MODE_FULLSCREEN)
     }
 
     @Test
@@ -345,6 +380,18 @@
     }
 
     @Test
+    fun moveTaskToFront_postsWctWithReorderOp() {
+        val task1 = setUpFreeformTask()
+        setUpFreeformTask()
+
+        controller.moveTaskToFront(task1)
+
+        val wct = getLatestWct(expectTransition = TRANSIT_TO_FRONT)
+        assertThat(wct.hierarchyOps).hasSize(1)
+        wct.assertReorderAt(index = 0, task1)
+    }
+
+    @Test
     fun moveToNextDisplay_noOtherDisplays() {
         whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
         val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -451,6 +498,28 @@
     }
 
     @Test
+    fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
+
+        val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
+        markTaskHidden(stashedFreeformTask)
+
+        val fullscreenTask = createFullscreenTask(DEFAULT_DISPLAY)
+
+        controller.stashDesktopApps(DEFAULT_DISPLAY)
+
+        val result = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+        assertThat(result).isNotNull()
+        result!!.assertReorderSequence(stashedFreeformTask, fullscreenTask)
+        assertThat(result.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+                .isEqualTo(WINDOWING_MODE_FREEFORM)
+
+        // Stashed state should be cleared
+        assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
+    }
+
+    @Test
     fun handleRequest_freeformTask_freeformVisible_returnNull() {
         assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
@@ -501,6 +570,26 @@
     }
 
     @Test
+    fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
+
+        val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
+        markTaskHidden(stashedFreeformTask)
+
+        val freeformTask = createFreeformTask(DEFAULT_DISPLAY)
+
+        controller.stashDesktopApps(DEFAULT_DISPLAY)
+
+        val result = controller.handleRequest(Binder(), createTransition(freeformTask))
+        assertThat(result).isNotNull()
+        result?.assertReorderSequence(stashedFreeformTask, freeformTask)
+
+        // Stashed state should be cleared
+        assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
+    }
+
+    @Test
     fun handleRequest_notOpenOrToFrontTransition_returnNull() {
         assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
@@ -539,6 +628,50 @@
         assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
     }
 
+    @Test
+    fun stashDesktopApps_stateUpdates() {
+        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
+
+        controller.stashDesktopApps(DEFAULT_DISPLAY)
+
+        assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue()
+        assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isFalse()
+    }
+
+    @Test
+    fun hideStashedDesktopApps_stateUpdates() {
+        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
+
+        desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true)
+        desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true)
+        controller.hideStashedDesktopApps(DEFAULT_DISPLAY)
+
+        assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
+        // Check that second display is not affected
+        assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isTrue()
+    }
+
+    @Test
+    fun desktopTasksVisibilityChange_visible_setLaunchAdjacentDisabled() {
+        val task = setUpFreeformTask()
+        clearInvocations(launchAdjacentController)
+
+        markTaskVisible(task)
+        shellExecutor.flushAll()
+        verify(launchAdjacentController).launchAdjacentEnabled = false
+    }
+
+    @Test
+    fun desktopTasksVisibilityChange_invisible_setLaunchAdjacentEnabled() {
+        val task = setUpFreeformTask()
+        markTaskVisible(task)
+        clearInvocations(launchAdjacentController)
+
+        markTaskHidden(task)
+        shellExecutor.flushAll()
+        verify(launchAdjacentController).launchAdjacentEnabled = true
+    }
+
     private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
         val task = createFreeformTask(displayId)
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
@@ -590,6 +723,16 @@
         return arg.value
     }
 
+    private fun getLatestMoveToDesktopWct(): WindowContainerTransaction {
+        val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        if (ENABLE_SHELL_TRANSITIONS) {
+            verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
+        } else {
+            verify(shellTaskOrganizer).applyTransaction(arg.capture())
+        }
+        return arg.value
+    }
+
     private fun verifyWCTNotExecuted() {
         if (ENABLE_SHELL_TRANSITIONS) {
             verify(transitions, never()).startTransition(anyInt(), any(), isNull())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index cf1ff32..29a757c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -22,6 +22,7 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.view.Display.DEFAULT_DISPLAY
+import com.android.wm.shell.MockToken
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 
 class DesktopTestHelpers {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
index 8592dea..772d97d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
@@ -28,6 +28,7 @@
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.view.SurfaceControl;
@@ -41,6 +42,7 @@
 
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
 
 import junit.framework.AssertionFailedError;
 
@@ -73,6 +75,10 @@
     ShellExecutor mExecutor;
     @Mock
     SurfaceControl mSurfaceControl;
+    @Mock
+    MoveToDesktopAnimator mMoveToDesktopAnimator;
+    @Mock
+    PointF mPosition;
 
     private EnterDesktopTaskTransitionHandler mEnterDesktopTaskTransitionHandler;
 
@@ -82,6 +88,7 @@
 
         doReturn(mExecutor).when(mTransitions).getMainExecutor();
         doReturn(mAnimationT).when(mTransactionFactory).get();
+        doReturn(mPosition).when(mMoveToDesktopAnimator).getPosition();
 
         mEnterDesktopTaskTransitionHandler = new EnterDesktopTaskTransitionHandler(mTransitions,
                 mTransactionFactory);
@@ -89,16 +96,20 @@
 
     @Test
     public void testEnterFreeformAnimation() {
-        final int transitionType = Transitions.TRANSIT_ENTER_FREEFORM;
         final int taskId = 1;
         WindowContainerTransaction wct = new WindowContainerTransaction();
         doReturn(mToken).when(mTransitions)
-                .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
-        mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
+                .startTransition(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE, wct,
+                        mEnterDesktopTaskTransitionHandler);
+        doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId();
+
+        mEnterDesktopTaskTransitionHandler.startMoveToDesktop(wct,
+                mMoveToDesktopAnimator, null);
 
         TransitionInfo.Change change =
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
-        TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_FREEFORM, change);
+        TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE,
+                change);
 
 
         assertTrue(mEnterDesktopTaskTransitionHandler
@@ -110,17 +121,18 @@
 
     @Test
     public void testTransitEnterDesktopModeAnimation() throws Throwable {
-        final int transitionType = Transitions.TRANSIT_ENTER_DESKTOP_MODE;
+        final int transitionType = Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE;
         final int taskId = 1;
         WindowContainerTransaction wct = new WindowContainerTransaction();
         doReturn(mToken).when(mTransitions)
                 .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
-        mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
+        mEnterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct, null);
 
         TransitionInfo.Change change =
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
         change.setEndAbsBounds(new Rect(0, 0, 1, 1));
-        TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_DESKTOP_MODE, change);
+        TransitionInfo info = createTransitionInfo(
+                Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE, change);
 
         runOnUiThread(() -> {
             try {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java
deleted file mode 100644
index 09d474d..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java
+++ /dev/null
@@ -1,40 +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.wm.shell.desktopmode;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.os.IBinder;
-import android.window.WindowContainerToken;
-
-/**
- * {@link WindowContainerToken} wrapper that supports a mock binder
- */
-class MockToken {
-    private final WindowContainerToken mToken;
-
-    MockToken() {
-        mToken = mock(WindowContainerToken.class);
-        IBinder binder = mock(IBinder.class);
-        when(mToken.asBinder()).thenReturn(binder);
-    }
-
-    WindowContainerToken token() {
-        return mToken;
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 7c1da35..527dc01 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -133,8 +133,7 @@
         mPortraitDisplayLayout = new DisplayLayout(info2, res, false, false);
         mInsets = Insets.of(0, 0, 0, 0);
 
-        mPolicy = spy(new DragAndDropPolicy(
-                mContext, mActivityTaskManager, mSplitScreenStarter, mSplitScreenStarter));
+        mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mSplitScreenStarter));
         mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
         mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
         setClipDataResizeable(mNonResizeableActivityClipData, false);
@@ -206,7 +205,10 @@
     @Test
     public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() {
         setRunningTask(mHomeTask);
-        mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+        DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
+                mLandscapeDisplayLayout, mActivityClipData);
+        dragSession.update();
+        mPolicy.start(dragSession, mLoggerSessionId);
         ArrayList<Target> targets = assertExactTargetTypes(
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
 
@@ -218,7 +220,10 @@
     @Test
     public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
         setRunningTask(mFullscreenAppTask);
-        mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+        DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
+                mLandscapeDisplayLayout, mActivityClipData);
+        dragSession.update();
+        mPolicy.start(dragSession, mLoggerSessionId);
         ArrayList<Target> targets = assertExactTargetTypes(
                 mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
 
@@ -235,7 +240,10 @@
     @Test
     public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
         setRunningTask(mFullscreenAppTask);
-        mPolicy.start(mPortraitDisplayLayout, mActivityClipData, mLoggerSessionId);
+        DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
+                mPortraitDisplayLayout, mActivityClipData);
+        dragSession.update();
+        mPolicy.start(dragSession, mLoggerSessionId);
         ArrayList<Target> targets = assertExactTargetTypes(
                 mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
 
@@ -252,7 +260,10 @@
     @Test
     public void testTargetHitRects() {
         setRunningTask(mFullscreenAppTask);
-        mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
+        DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
+                mLandscapeDisplayLayout, mActivityClipData);
+        dragSession.update();
+        mPolicy.start(dragSession, mLoggerSessionId);
         ArrayList<Target> targets = mPolicy.getTargets(mInsets);
         for (Target t : targets) {
             assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.left, t.hitRegion.top) == t);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index addc233..bf1b7f9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -32,7 +32,8 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -60,7 +61,8 @@
 
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
     private DisplayInfo mDefaultDisplayInfo;
-    private PipBoundsState mPipBoundsState; private PipSizeSpecHandler mPipSizeSpecHandler;
+    private PipBoundsState mPipBoundsState;
+    private SizeSpecSource mSizeSpecSource;
     private PipDisplayLayoutState mPipDisplayLayoutState;
 
 
@@ -68,11 +70,12 @@
     public void setUp() throws Exception {
         initializeMockResources();
         mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
-        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
-        mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+
+        mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+        mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
                 new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
-                mPipSizeSpecHandler);
+                mPipDisplayLayoutState, mSizeSpecSource);
 
         DisplayLayout layout =
                 new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true);
@@ -132,7 +135,7 @@
 
     @Test
     public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() {
-        final Size defaultSize = mPipSizeSpecHandler.getDefaultSize(DEFAULT_ASPECT_RATIO);
+        final Size defaultSize = mSizeSpecSource.getDefaultSize(DEFAULT_ASPECT_RATIO);
 
         mPipBoundsState.setOverrideMinSize(null);
         final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index f320004..4341c4c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -35,7 +35,8 @@
 import com.android.internal.util.function.TriConsumer;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -58,6 +59,7 @@
     private static final int OVERRIDABLE_MIN_SIZE = 40;
 
     private PipBoundsState mPipBoundsState;
+    private SizeSpecSource mSizeSpecSource;
     private ComponentName mTestComponentName1;
     private ComponentName mTestComponentName2;
 
@@ -69,8 +71,8 @@
                 OVERRIDABLE_MIN_SIZE);
 
         PipDisplayLayoutState pipDisplayLayoutState = new PipDisplayLayoutState(mContext);
-        mPipBoundsState = new PipBoundsState(mContext,
-                new PipSizeSpecHandler(mContext, pipDisplayLayoutState), pipDisplayLayoutState);
+        mSizeSpecSource = new PhoneSizeSpecSource(mContext, pipDisplayLayoutState);
+        mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, pipDisplayLayoutState);
         mTestComponentName1 = new ComponentName(mContext, "component1");
         mTestComponentName2 = new ComponentName(mContext, "component2");
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 842c699..1e3fe42 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -52,8 +52,9 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import org.junit.Before;
@@ -87,7 +88,7 @@
     private PipBoundsState mPipBoundsState;
     private PipTransitionState mPipTransitionState;
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
-    private PipSizeSpecHandler mPipSizeSpecHandler;
+    private SizeSpecSource mSizeSpecSource;
     private PipDisplayLayoutState mPipDisplayLayoutState;
 
     private ComponentName mComponent1;
@@ -99,12 +100,12 @@
         mComponent1 = new ComponentName(mContext, "component1");
         mComponent2 = new ComponentName(mContext, "component2");
         mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
-        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
-        mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+        mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+        mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
         mPipTransitionState = new PipTransitionState();
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
                 new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
-                mPipSizeSpecHandler);
+                mPipDisplayLayoutState, mSizeSpecSource);
         mMainExecutor = new TestShellExecutor();
         mPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockSyncTransactionQueue,
                 mPipTransitionState, mPipBoundsState, mPipDisplayLayoutState,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
new file mode 100644
index 0000000..024cba3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.phone;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.SystemProperties;
+import android.testing.AndroidTestingRunner;
+import android.util.Size;
+import android.view.DisplayInfo;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.exceptions.misusing.InvalidUseOfMatchersException;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Unit test against {@link PhoneSizeSpecSource}
+ */
+@RunWith(AndroidTestingRunner.class)
+public class PhoneSizeSpecSourceTest extends ShellTestCase {
+    /** A sample overridden min edge size. */
+    private static final int OVERRIDE_MIN_EDGE_SIZE = 40;
+    /** A sample default min edge size */
+    private static final int DEFAULT_MIN_EDGE_SIZE = 40;
+    /** Display edge size */
+    private static final int DISPLAY_EDGE_SIZE = 1000;
+    /** Default sizing percentage */
+    private static final float DEFAULT_PERCENT = 0.6f;
+    /** Minimum sizing percentage */
+    private static final float MIN_PERCENT = 0.5f;
+    /** Aspect ratio that the new PIP size spec logic optimizes for. */
+    private static final float OPTIMIZED_ASPECT_RATIO = 9f / 16;
+
+    /** A map of aspect ratios to be tested to expected sizes */
+    private static Map<Float, Size> sExpectedMaxSizes;
+    private static Map<Float, Size> sExpectedDefaultSizes;
+    private static Map<Float, Size> sExpectedMinSizes;
+    /** A static mockito session object to mock {@link SystemProperties} */
+    private static StaticMockitoSession sStaticMockitoSession;
+
+    @Mock private Context mContext;
+    @Mock private Resources mResources;
+
+    private PipDisplayLayoutState mPipDisplayLayoutState;
+    private SizeSpecSource mSizeSpecSource;
+
+    /**
+     * Sets up static Mockito session for SystemProperties and mocks necessary static methods.
+     */
+    private static void setUpStaticSystemPropertiesSession() {
+        sStaticMockitoSession = mockitoSession()
+                .mockStatic(SystemProperties.class).startMocking();
+        when(SystemProperties.get(anyString(), anyString())).thenAnswer(invocation -> {
+            String property = invocation.getArgument(0);
+            if (property.equals("com.android.wm.shell.pip.phone.def_percentage")) {
+                return Float.toString(DEFAULT_PERCENT);
+            } else if (property.equals("com.android.wm.shell.pip.phone.min_percentage")) {
+                return Float.toString(MIN_PERCENT);
+            }
+
+            // throw an exception if illegal arguments are used for these tests
+            throw new InvalidUseOfMatchersException(
+                String.format("Argument %s does not match", property)
+            );
+        });
+    }
+
+    /**
+     * Initializes the map with the aspect ratios to be tested and corresponding expected max sizes.
+     */
+    private static void initExpectedSizes() {
+        sExpectedMaxSizes = new HashMap<>();
+        sExpectedDefaultSizes = new HashMap<>();
+        sExpectedMinSizes = new HashMap<>();
+
+        sExpectedMaxSizes.put(16f / 9, new Size(1000, 563));
+        sExpectedDefaultSizes.put(16f / 9, new Size(600, 338));
+        sExpectedMinSizes.put(16f / 9, new Size(501, 282));
+
+        sExpectedMaxSizes.put(4f / 3, new Size(893, 670));
+        sExpectedDefaultSizes.put(4f / 3, new Size(536, 402));
+        sExpectedMinSizes.put(4f / 3, new Size(447, 335));
+
+        sExpectedMaxSizes.put(3f / 4, new Size(670, 893));
+        sExpectedDefaultSizes.put(3f / 4, new Size(402, 536));
+        sExpectedMinSizes.put(3f / 4, new Size(335, 447));
+
+        sExpectedMaxSizes.put(9f / 16, new Size(563, 1001));
+        sExpectedDefaultSizes.put(9f / 16, new Size(338, 601));
+        sExpectedMinSizes.put(9f / 16, new Size(282, 501));
+    }
+
+    private void forEveryTestCaseCheck(Map<Float, Size> expectedSizes,
+            Function<Float, Size> callback) {
+        for (Map.Entry<Float, Size> expectedSizesEntry : expectedSizes.entrySet()) {
+            float aspectRatio = expectedSizesEntry.getKey();
+            Size expectedSize = expectedSizesEntry.getValue();
+
+            Assert.assertEquals(expectedSize, callback.apply(aspectRatio));
+        }
+    }
+
+    @Before
+    public void setUp() {
+        initExpectedSizes();
+
+        when(mResources.getDimensionPixelSize(anyInt())).thenReturn(DEFAULT_MIN_EDGE_SIZE);
+        when(mResources.getFloat(anyInt())).thenReturn(OPTIMIZED_ASPECT_RATIO);
+        when(mResources.getString(anyInt())).thenReturn("0x0");
+        when(mResources.getDisplayMetrics())
+                .thenReturn(getContext().getResources().getDisplayMetrics());
+
+        // set up the mock context for spec handler specifically
+        when(mContext.getResources()).thenReturn(mResources);
+
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = DISPLAY_EDGE_SIZE;
+        displayInfo.logicalHeight = DISPLAY_EDGE_SIZE;
+
+        // use the parent context (not the mocked one) to obtain the display layout
+        // this is done to avoid unnecessary mocking while allowing for custom display dimensions
+        DisplayLayout displayLayout = new DisplayLayout(displayInfo, getContext().getResources(),
+                false, false);
+        mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
+        mPipDisplayLayoutState.setDisplayLayout(displayLayout);
+
+        setUpStaticSystemPropertiesSession();
+        mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+
+        // no overridden min edge size by default
+        mSizeSpecSource.setOverrideMinSize(null);
+    }
+
+    @After
+    public void cleanUp() {
+        sStaticMockitoSession.finishMocking();
+    }
+
+    @Test
+    public void testGetMaxSize() {
+        forEveryTestCaseCheck(sExpectedMaxSizes,
+                (aspectRatio) -> mSizeSpecSource.getMaxSize(aspectRatio));
+    }
+
+    @Test
+    public void testGetDefaultSize() {
+        forEveryTestCaseCheck(sExpectedDefaultSizes,
+                (aspectRatio) -> mSizeSpecSource.getDefaultSize(aspectRatio));
+    }
+
+    @Test
+    public void testGetMinSize() {
+        forEveryTestCaseCheck(sExpectedMinSizes,
+                (aspectRatio) -> mSizeSpecSource.getMinSize(aspectRatio));
+    }
+
+    @Test
+    public void testGetSizeForAspectRatio_noOverrideMinSize() {
+        // an initial size with 16:9 aspect ratio
+        Size initSize = new Size(600, 337);
+
+        Size expectedSize = new Size(338, 601);
+        Size actualSize = mSizeSpecSource.getSizeForAspectRatio(initSize, 9f / 16);
+
+        Assert.assertEquals(expectedSize, actualSize);
+    }
+
+    @Test
+    public void testGetSizeForAspectRatio_withOverrideMinSize() {
+        // an initial size with a 1:1 aspect ratio
+        Size initSize = new Size(OVERRIDE_MIN_EDGE_SIZE, OVERRIDE_MIN_EDGE_SIZE);
+        mSizeSpecSource.setOverrideMinSize(initSize);
+
+        Size expectedSize = new Size(40, 71);
+        Size actualSize = mSizeSpecSource.getSizeForAspectRatio(initSize, 9f / 16);
+
+        Assert.assertEquals(expectedSize, actualSize);
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 85167cb..0ae2908 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -55,9 +55,9 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
@@ -109,7 +109,6 @@
     @Mock private PipMotionHelper mMockPipMotionHelper;
     @Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
     @Mock private PipBoundsState mMockPipBoundsState;
-    @Mock private PipSizeSpecHandler mMockPipSizeSpecHandler;
     @Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
     @Mock private TaskStackListenerImpl mMockTaskStackListener;
     @Mock private ShellExecutor mMockExecutor;
@@ -134,7 +133,7 @@
         mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
                 mShellController, mMockDisplayController, mMockPipAnimationController,
                 mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
-                mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState,
+                mMockPipBoundsState, mMockPipDisplayLayoutState,
                 mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController,
                 mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
                 mMockPipTransitionController, mMockWindowManagerShellWrapper,
@@ -226,7 +225,7 @@
         assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
                 mShellController, mMockDisplayController, mMockPipAnimationController,
                 mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
-                mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState,
+                mMockPipBoundsState, mMockPipDisplayLayoutState,
                 mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController,
                 mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
                 mMockPipTransitionController, mMockWindowManagerShellWrapper,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 1dfdbf6..689b5c5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -36,6 +36,8 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
@@ -87,7 +89,7 @@
 
     private PipBoundsState mPipBoundsState;
 
-    private PipSizeSpecHandler mPipSizeSpecHandler;
+    private SizeSpecSource mSizeSpecSource;
 
     private PipDisplayLayoutState mPipDisplayLayoutState;
 
@@ -97,13 +99,14 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
-        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
-        mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+        mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+        mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
         final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm();
         final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm =
                 new PipKeepClearAlgorithmInterface() {};
         final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
-                mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipSizeSpecHandler);
+                mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState,
+                mSizeSpecSource);
         final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
                 mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
                 mMockPipTransitionController, mFloatingContentCoordinator);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
deleted file mode 100644
index 1379aed..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip.phone;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.SystemProperties;
-import android.testing.AndroidTestingRunner;
-import android.util.Size;
-import android.view.DisplayInfo;
-
-import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.exceptions.misusing.InvalidUseOfMatchersException;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Function;
-
-/**
- * Unit test against {@link PipSizeSpecHandler} with feature flag on.
- */
-@RunWith(AndroidTestingRunner.class)
-public class PipSizeSpecHandlerTest extends ShellTestCase {
-    /** A sample overridden min edge size. */
-    private static final int OVERRIDE_MIN_EDGE_SIZE = 40;
-    /** A sample default min edge size */
-    private static final int DEFAULT_MIN_EDGE_SIZE = 40;
-    /** Display edge size */
-    private static final int DISPLAY_EDGE_SIZE = 1000;
-    /** Default sizing percentage */
-    private static final float DEFAULT_PERCENT = 0.6f;
-    /** Minimum sizing percentage */
-    private static final float MIN_PERCENT = 0.5f;
-    /** Aspect ratio that the new PIP size spec logic optimizes for. */
-    private static final float OPTIMIZED_ASPECT_RATIO = 9f / 16;
-
-    /** A map of aspect ratios to be tested to expected sizes */
-    private static Map<Float, Size> sExpectedMaxSizes;
-    private static Map<Float, Size> sExpectedDefaultSizes;
-    private static Map<Float, Size> sExpectedMinSizes;
-    /** A static mockito session object to mock {@link SystemProperties} */
-    private static StaticMockitoSession sStaticMockitoSession;
-
-    @Mock private Context mContext;
-    @Mock private Resources mResources;
-
-    private PipDisplayLayoutState mPipDisplayLayoutState;
-    private TestPipSizeSpecHandler mPipSizeSpecHandler;
-
-    /**
-     * Sets up static Mockito session for SystemProperties and mocks necessary static methods.
-     */
-    private static void setUpStaticSystemPropertiesSession() {
-        sStaticMockitoSession = mockitoSession()
-                .mockStatic(SystemProperties.class).startMocking();
-        when(SystemProperties.get(anyString(), anyString())).thenAnswer(invocation -> {
-            String property = invocation.getArgument(0);
-            if (property.equals("com.android.wm.shell.pip.phone.def_percentage")) {
-                return Float.toString(DEFAULT_PERCENT);
-            } else if (property.equals("com.android.wm.shell.pip.phone.min_percentage")) {
-                return Float.toString(MIN_PERCENT);
-            }
-
-            // throw an exception if illegal arguments are used for these tests
-            throw new InvalidUseOfMatchersException(
-                String.format("Argument %s does not match", property)
-            );
-        });
-    }
-
-    /**
-     * Initializes the map with the aspect ratios to be tested and corresponding expected max sizes.
-     */
-    private static void initExpectedSizes() {
-        sExpectedMaxSizes = new HashMap<>();
-        sExpectedDefaultSizes = new HashMap<>();
-        sExpectedMinSizes = new HashMap<>();
-
-        sExpectedMaxSizes.put(16f / 9, new Size(1000, 563));
-        sExpectedDefaultSizes.put(16f / 9, new Size(600, 338));
-        sExpectedMinSizes.put(16f / 9, new Size(501, 282));
-
-        sExpectedMaxSizes.put(4f / 3, new Size(892, 669));
-        sExpectedDefaultSizes.put(4f / 3, new Size(535, 401));
-        sExpectedMinSizes.put(4f / 3, new Size(447, 335));
-
-        sExpectedMaxSizes.put(3f / 4, new Size(669, 892));
-        sExpectedDefaultSizes.put(3f / 4, new Size(401, 535));
-        sExpectedMinSizes.put(3f / 4, new Size(335, 447));
-
-        sExpectedMaxSizes.put(9f / 16, new Size(562, 999));
-        sExpectedDefaultSizes.put(9f / 16, new Size(337, 599));
-        sExpectedMinSizes.put(9f / 16, new Size(281, 500));
-    }
-
-    private void forEveryTestCaseCheck(Map<Float, Size> expectedSizes,
-            Function<Float, Size> callback) {
-        for (Map.Entry<Float, Size> expectedSizesEntry : expectedSizes.entrySet()) {
-            float aspectRatio = expectedSizesEntry.getKey();
-            Size expectedSize = expectedSizesEntry.getValue();
-
-            Assert.assertEquals(expectedSize, callback.apply(aspectRatio));
-        }
-    }
-
-    @Before
-    public void setUp() {
-        initExpectedSizes();
-
-        when(mResources.getDimensionPixelSize(anyInt())).thenReturn(DEFAULT_MIN_EDGE_SIZE);
-        when(mResources.getFloat(anyInt())).thenReturn(OPTIMIZED_ASPECT_RATIO);
-        when(mResources.getString(anyInt())).thenReturn("0x0");
-        when(mResources.getDisplayMetrics())
-                .thenReturn(getContext().getResources().getDisplayMetrics());
-
-        // set up the mock context for spec handler specifically
-        when(mContext.getResources()).thenReturn(mResources);
-
-        DisplayInfo displayInfo = new DisplayInfo();
-        displayInfo.logicalWidth = DISPLAY_EDGE_SIZE;
-        displayInfo.logicalHeight = DISPLAY_EDGE_SIZE;
-
-        // use the parent context (not the mocked one) to obtain the display layout
-        // this is done to avoid unnecessary mocking while allowing for custom display dimensions
-        DisplayLayout displayLayout = new DisplayLayout(displayInfo, getContext().getResources(),
-                false, false);
-        mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
-        mPipDisplayLayoutState.setDisplayLayout(displayLayout);
-
-        setUpStaticSystemPropertiesSession();
-        mPipSizeSpecHandler = new TestPipSizeSpecHandler(mContext, mPipDisplayLayoutState);
-
-        // no overridden min edge size by default
-        mPipSizeSpecHandler.setOverrideMinSize(null);
-    }
-
-    @After
-    public void cleanUp() {
-        sStaticMockitoSession.finishMocking();
-    }
-
-    @Test
-    public void testGetMaxSize() {
-        forEveryTestCaseCheck(sExpectedMaxSizes,
-                (aspectRatio) -> mPipSizeSpecHandler.getMaxSize(aspectRatio));
-    }
-
-    @Test
-    public void testGetDefaultSize() {
-        forEveryTestCaseCheck(sExpectedDefaultSizes,
-                (aspectRatio) -> mPipSizeSpecHandler.getDefaultSize(aspectRatio));
-    }
-
-    @Test
-    public void testGetMinSize() {
-        forEveryTestCaseCheck(sExpectedMinSizes,
-                (aspectRatio) -> mPipSizeSpecHandler.getMinSize(aspectRatio));
-    }
-
-    @Test
-    public void testGetSizeForAspectRatio_noOverrideMinSize() {
-        // an initial size with 16:9 aspect ratio
-        Size initSize = new Size(600, 337);
-
-        Size expectedSize = new Size(337, 599);
-        Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
-
-        Assert.assertEquals(expectedSize, actualSize);
-    }
-
-    @Test
-    public void testGetSizeForAspectRatio_withOverrideMinSize() {
-        // an initial size with a 1:1 aspect ratio
-        mPipSizeSpecHandler.setOverrideMinSize(new Size(OVERRIDE_MIN_EDGE_SIZE,
-                OVERRIDE_MIN_EDGE_SIZE));
-        // make sure initial size is same as override min size
-        Size initSize = mPipSizeSpecHandler.getOverrideMinSize();
-
-        Size expectedSize = new Size(40, 71);
-        Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
-
-        Assert.assertEquals(expectedSize, actualSize);
-    }
-
-    static class TestPipSizeSpecHandler extends PipSizeSpecHandler {
-
-        TestPipSizeSpecHandler(Context context, PipDisplayLayoutState displayLayoutState) {
-            super(context, displayLayoutState);
-        }
-
-        @Override
-        boolean supportsPipSizeLargeScreen() {
-            return true;
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 10b1ddf..852183c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -33,6 +33,8 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
@@ -92,7 +94,7 @@
     private PipSnapAlgorithm mPipSnapAlgorithm;
     private PipMotionHelper mMotionHelper;
     private PipResizeGestureHandler mPipResizeGestureHandler;
-    private PipSizeSpecHandler mPipSizeSpecHandler;
+    private SizeSpecSource mSizeSpecSource;
     private PipDisplayLayoutState mPipDisplayLayoutState;
 
     private DisplayLayout mDisplayLayout;
@@ -108,16 +110,16 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
-        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
-        mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+        mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+        mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
         mPipSnapAlgorithm = new PipSnapAlgorithm();
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
-                new PipKeepClearAlgorithmInterface() {}, mPipSizeSpecHandler);
+                new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource);
         PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
                 mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
                 mMockPipTransitionController, mFloatingContentCoordinator);
         mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
-                mPipBoundsAlgorithm, mPipBoundsState, mPipSizeSpecHandler, mPipTaskOrganizer,
+                mPipBoundsAlgorithm, mPipBoundsState, mSizeSpecSource, mPipTaskOrganizer,
                 pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
         // We aren't actually using ShellInit, so just call init directly
         mPipTouchHandler.onInit();
@@ -162,8 +164,8 @@
 
         // getting the expected min and max size
         float aspectRatio = (float) mPipBounds.width() / mPipBounds.height();
-        Size expectedMinSize = mPipSizeSpecHandler.getMinSize(aspectRatio);
-        Size expectedMaxSize = mPipSizeSpecHandler.getMaxSize(aspectRatio);
+        Size expectedMinSize = mSizeSpecSource.getMinSize(aspectRatio);
+        Size expectedMaxSize = mSizeSpecSource.getMaxSize(aspectRatio);
 
         assertEquals(expectedMovementBounds, mPipBoundsState.getNormalMovementBounds());
         verify(mPipResizeGestureHandler, times(1))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
index f9b7723..da6951b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -26,9 +26,10 @@
 import android.view.Gravity;
 
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.pip.LegacySizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -47,7 +48,7 @@
 
     private TvPipBoundsState mTvPipBoundsState;
     private TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
-    private PipSizeSpecHandler mPipSizeSpecHandler;
+    private SizeSpecSource mSizeSpecSource;
     private PipDisplayLayoutState mPipDisplayLayoutState;
 
     @Before
@@ -57,11 +58,11 @@
         }
         MockitoAnnotations.initMocks(this);
         mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
-        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
-        mTvPipBoundsState = new TvPipBoundsState(mContext, mPipSizeSpecHandler,
+        mSizeSpecSource = new LegacySizeSpecSource(mContext, mPipDisplayLayoutState);
+        mTvPipBoundsState = new TvPipBoundsState(mContext, mSizeSpecSource,
                 mPipDisplayLayoutState);
         mTvPipBoundsAlgorithm = new TvPipBoundsAlgorithm(mContext, mTvPipBoundsState,
-                mMockPipSnapAlgorithm, mPipSizeSpecHandler);
+                mMockPipSnapAlgorithm, mPipDisplayLayoutState, mSizeSpecSource);
 
         setRTL(false);
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
index 68cb57c..b1befc4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
@@ -41,6 +41,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Optional;
+
 /** Tests for {@link MainStage} */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -61,7 +63,7 @@
         MockitoAnnotations.initMocks(this);
         mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
         mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
-                mSyncQueue, mSurfaceSession, mIconProvider);
+                mSyncQueue, mSurfaceSession, mIconProvider, Optional.empty());
         mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
index 3b42a48..549bd3f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
@@ -46,6 +46,8 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 
+import java.util.Optional;
+
 /** Tests for {@link SideStage} */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -66,7 +68,7 @@
         MockitoAnnotations.initMocks(this);
         mRootTask = new TestRunningTaskInfoBuilder().build();
         mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
-                mSyncQueue, mSurfaceSession, mIconProvider);
+                mSyncQueue, mSurfaceSession, mIconProvider, Optional.empty());
         mSideStage.onTaskAppeared(mRootTask, mRootLeash);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index fb17d87..568db91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -61,9 +61,11 @@
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -71,6 +73,7 @@
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.sysui.ShellSharedConstants;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -102,6 +105,9 @@
     @Mock IconProvider mIconProvider;
     @Mock StageCoordinator mStageCoordinator;
     @Mock RecentTasksController mRecentTasks;
+    @Mock LaunchAdjacentController mLaunchAdjacentController;
+    @Mock WindowDecorViewModel mWindowDecorViewModel;
+    @Mock DesktopTasksController mDesktopTasksController;
     @Captor ArgumentCaptor<Intent> mIntentCaptor;
 
     private ShellController mShellController;
@@ -117,7 +123,8 @@
                 mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
                 mRootTDAOrganizer, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
-                mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator));
+                mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+                mDesktopTasksController, mMainExecutor, mStageCoordinator));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index 4e446c6..a3009a5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -31,12 +31,14 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import java.util.Optional;
 
@@ -76,10 +78,13 @@
                 DisplayInsetsController insetsController, SplitLayout splitLayout,
                 Transitions transitions, TransactionPool transactionPool,
                 ShellExecutor mainExecutor,
-                Optional<RecentTasksController> recentTasks) {
+                Optional<RecentTasksController> recentTasks,
+                LaunchAdjacentController launchAdjacentController,
+                Optional<WindowDecorViewModel> windowDecorViewModel) {
             super(context, displayId, syncQueue, taskOrganizer, mainStage,
                     sideStage, displayController, imeController, insetsController, splitLayout,
-                    transitions, transactionPool, mainExecutor, recentTasks);
+                    transitions, transactionPool, mainExecutor, recentTasks,
+                    launchAdjacentController, windowDecorViewModel);
 
             // Prepare root task for testing.
             mRootTask = new TestRunningTaskInfoBuilder().build();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 44c76de..5efd9ad 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -70,6 +70,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
@@ -77,6 +78,7 @@
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.transition.DefaultMixedHandler;
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -101,7 +103,9 @@
     @Mock private Transitions mTransitions;
     @Mock private SurfaceSession mSurfaceSession;
     @Mock private IconProvider mIconProvider;
+    @Mock private WindowDecorViewModel mWindowDecorViewModel;
     @Mock private ShellExecutor mMainExecutor;
+    @Mock private LaunchAdjacentController mLaunchAdjacentController;
     @Mock private DefaultMixedHandler mMixedHandler;
     private SplitLayout mSplitLayout;
     private MainStage mMainStage;
@@ -123,16 +127,17 @@
         mSplitLayout = SplitTestUtils.createMockSplitLayout();
         mMainStage = spy(new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
                 StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
-                mIconProvider));
+                mIconProvider, Optional.of(mWindowDecorViewModel)));
         mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
         mSideStage = spy(new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
                 StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
-                mIconProvider));
+                mIconProvider, Optional.of(mWindowDecorViewModel)));
         mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
         mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
                 mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
                 mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
-                mTransactionPool, mMainExecutor, Optional.empty());
+                mTransactionPool, mMainExecutor, Optional.empty(),
+                mLaunchAdjacentController, Optional.empty());
         mStageCoordinator.setMixedHandler(mMixedHandler);
         mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
         doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 2dcdc74..cc4db22 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -65,6 +65,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
@@ -107,6 +108,8 @@
     private DisplayInsetsController mDisplayInsetsController;
     @Mock
     private TransactionPool mTransactionPool;
+    @Mock
+    private LaunchAdjacentController mLaunchAdjacentController;
 
     private final Rect mBounds1 = new Rect(10, 20, 30, 40);
     private final Rect mBounds2 = new Rect(5, 10, 15, 20);
@@ -130,7 +133,7 @@
         mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
                 mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
-                mMainExecutor, Optional.empty()));
+                mMainExecutor, Optional.empty(), mLaunchAdjacentController, Optional.empty()));
         mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build();
 
         when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
@@ -344,13 +347,20 @@
     }
 
     @Test
-    public void testExitSplitScreenAfterFolded() {
-        when(mMainStage.isActive()).thenReturn(true);
+    public void testExitSplitScreenAfterFoldedAndWakeUp() {
         when(mMainStage.isFocused()).thenReturn(true);
         when(mMainStage.getTopVisibleChildTaskId()).thenReturn(INVALID_TASK_ID);
+        mSideStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build();
+        mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().setVisible(true).build();
+        when(mStageCoordinator.isSplitActive()).thenReturn(true);
+        when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true);
 
         mStageCoordinator.onFoldedStateChanged(true);
 
+        assertEquals(mStageCoordinator.mTopStageAfterFoldDismiss, STAGE_TYPE_MAIN);
+
+        mStageCoordinator.onFinishedWakingUp();
+
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             verify(mTaskOrganizer).startNewTransition(eq(TRANSIT_SPLIT_DISMISS), notNull());
         } else {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 1a1bebd..946a7ef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -43,6 +43,7 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -52,6 +53,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Optional;
+
 /**
  * Tests for {@link StageTaskListener}
  * Build/Install/Run:
@@ -71,6 +74,8 @@
     private SyncTransactionQueue mSyncQueue;
     @Mock
     private IconProvider mIconProvider;
+    @Mock
+    private WindowDecorViewModel mWindowDecorViewModel;
     @Captor
     private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
     private SurfaceSession mSurfaceSession = new SurfaceSession();
@@ -89,7 +94,8 @@
                 mCallbacks,
                 mSyncQueue,
                 mSurfaceSession,
-                mIconProvider);
+                mIconProvider,
+                Optional.of(mWindowDecorViewModel));
         mRootTask = new TestRunningTaskInfoBuilder().build();
         mRootTask.parentTaskId = INVALID_TASK_ID;
         mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
@@ -155,7 +161,7 @@
         childTask.supportsMultiWindow = false;
 
         mStageTaskListener.onTaskInfoChanged(childTask);
-        verify(mCallbacks).onNoLongerSupportMultiWindow();
+        verify(mCallbacks).onNoLongerSupportMultiWindow(childTask);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 1b38956..d098d33 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -40,6 +40,7 @@
 import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.testing.AndroidTestingRunner;
@@ -563,4 +564,72 @@
         mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
         verify(mTaskViewTaskController, never()).cleanUpPendingTask();
     }
+
+    @Test
+    public void testSetCaptionInsets_noTaskInitially() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+        Rect insets = new Rect(0, 400, 0, 0);
+        mTaskView.setCaptionInsets(Insets.of(insets));
+        mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+
+        verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+        verify(mOrganizer, never()).applyTransaction(any());
+
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+        reset(mOrganizer);
+        reset(mTaskViewTaskController);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
+        mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+
+        verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+        verify(mOrganizer).applyTransaction(any());
+    }
+
+    @Test
+    public void testSetCaptionInsets_withTask() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
+        reset(mTaskViewTaskController);
+        reset(mOrganizer);
+
+        Rect insets = new Rect(0, 400, 0, 0);
+        mTaskView.setCaptionInsets(Insets.of(insets));
+        mTaskView.onComputeInternalInsets(new ViewTreeObserver.InternalInsetsInfo());
+        verify(mTaskViewTaskController).applyCaptionInsetsIfNeeded();
+        verify(mOrganizer).applyTransaction(any());
+    }
+
+    @Test
+    public void testReleaseInOnTaskRemoval_noNPE() {
+        mTaskViewTaskController = spy(new TaskViewTaskController(mContext, mOrganizer,
+                mTaskViewTransitions, mSyncQueue));
+        mTaskView = new TaskView(mContext, mTaskViewTaskController);
+        mTaskView.setListener(mExecutor, new TaskView.Listener() {
+            @Override
+            public void onTaskRemovalStarted(int taskId) {
+                mTaskView.release();
+            }
+        });
+
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+
+        assertThat(mTaskViewTaskController.getTaskInfo()).isEqualTo(mTaskInfo);
+
+        mTaskViewTaskController.prepareCloseAnimation();
+
+        assertThat(mTaskViewTaskController.getTaskInfo()).isNull();
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index ff380e9..99a1ac6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -176,7 +176,7 @@
         assertEquals(1, mDefaultHandler.activeCount());
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any());
     }
 
     @Test
@@ -299,7 +299,7 @@
         assertTrue(remoteCalled[0]);
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken), eq(remoteFinishWCT), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken), eq(remoteFinishWCT));
     }
 
     @Test
@@ -449,7 +449,7 @@
         assertTrue(remoteCalled[0]);
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any());
     }
 
     @Test
@@ -524,20 +524,20 @@
         // default handler doesn't merge by default, so it shouldn't increment active count.
         assertEquals(1, mDefaultHandler.activeCount());
         assertEquals(0, mDefaultHandler.mergeCount());
-        verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any(), any());
-        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any());
+        verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any());
+        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any());
 
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
         // first transition finished
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any());
-        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any());
+        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any());
         // But now the "queued" transition is running
         assertEquals(1, mDefaultHandler.activeCount());
 
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any());
     }
 
     @Test
@@ -565,15 +565,15 @@
         // it should still only have 1 active, but then show 1 merged
         assertEquals(1, mDefaultHandler.activeCount());
         assertEquals(1, mDefaultHandler.mergeCount());
-        verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any(), any());
+        verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any());
         // We don't tell organizer it is finished yet (since we still want to maintain ordering)
-        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any());
+        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any());
 
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
         // transition + merged all finished.
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any());
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any());
         // Make sure nothing was queued
         assertEquals(0, mDefaultHandler.activeCount());
     }
@@ -599,8 +599,7 @@
         requestStartTransition(transitions, transitTokenNotReady);
 
         mDefaultHandler.setSimulateMerge(true);
-        mDefaultHandler.mFinishes.get(0).second.onTransitionFinished(
-                null /* wct */, null /* wctCB */);
+        mDefaultHandler.mFinishes.get(0).second.onTransitionFinished(null /* wct */);
 
         // Make sure that the non-ready transition is not merged.
         assertEquals(0, mDefaultHandler.mergeCount());
@@ -823,8 +822,8 @@
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
         // first transition finished
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any());
-        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any());
+        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any());
         // But now the "queued" transition is running
         assertEquals(1, mDefaultHandler.activeCount());
 
@@ -835,7 +834,7 @@
 
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any());
 
         // runnable2 and runnable3 are executed after the second transition finishes because there
         // are no other active transitions, runnable1 isn't executed again.
@@ -1449,13 +1448,13 @@
             if (mFinishOnSync && info.getType() == TRANSIT_SLEEP) {
                 for (int i = 0; i < mFinishes.size(); ++i) {
                     if (mFinishes.get(i).first != mergeTarget) continue;
-                    mFinishes.remove(i).second.onTransitionFinished(null, null);
+                    mFinishes.remove(i).second.onTransitionFinished(null);
                     return;
                 }
             }
             if (!(mSimulateMerge || mShouldMerge.contains(transition))) return;
             mMerged.add(transition);
-            finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            finishCallback.onTransitionFinished(null /* wct */);
         }
 
         @Nullable
@@ -1478,7 +1477,7 @@
                     mFinishes;
             mFinishes = new ArrayList<>();
             for (int i = finishes.size() - 1; i >= 0; --i) {
-                finishes.get(i).second.onTransitionFinished(null /* wct */, null /* wctCB */);
+                finishes.get(i).second.onTransitionFinished(null /* wct */);
             }
             mShouldMerge.clear();
         }
@@ -1486,7 +1485,7 @@
         void finishOne() {
             Pair<IBinder, Transitions.TransitionFinishCallback> fin = mFinishes.remove(0);
             mMerged.clear();
-            fin.second.onTransitionFinished(null /* wct */, null /* wctCB */);
+            fin.second.onTransitionFinished(null /* wct */);
         }
 
         int activeCount() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
index 41bab95..596d6dd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -18,12 +18,15 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.any;
+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.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -53,7 +56,8 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
-import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
@@ -82,7 +86,6 @@
     @Mock private ShellTaskOrganizer mTaskOrganizer;
     @Mock private DisplayController mDisplayController;
     @Mock private DisplayLayout mDisplayLayout;
-    @Mock private SplitScreenController mSplitScreenController;
     @Mock private SyncTransactionQueue mSyncQueue;
     @Mock private DesktopModeController mDesktopModeController;
     @Mock private DesktopTasksController mDesktopTasksController;
@@ -92,6 +95,11 @@
     @Mock private DesktopModeWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
     @Mock private Supplier<SurfaceControl.Transaction> mTransactionFactory;
     @Mock private SurfaceControl.Transaction mTransaction;
+    @Mock private Display mDisplay;
+    @Mock private ShellController mShellController;
+    @Mock private ShellInit mShellInit;
+    @Mock private DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
+            mDesktopModeKeyguardChangeListener;
     private final List<InputManager> mMockInputManagers = new ArrayList<>();
 
     private DesktopModeWindowDecorViewModel mDesktopModeWindowDecorViewModel;
@@ -105,16 +113,18 @@
                 mContext,
                 mMainHandler,
                 mMainChoreographer,
+                mShellInit,
                 mTaskOrganizer,
                 mDisplayController,
+                mShellController,
                 mSyncQueue,
                 mTransitions,
                 Optional.of(mDesktopModeController),
                 Optional.of(mDesktopTasksController),
-                Optional.of(mSplitScreenController),
                 mDesktopModeWindowDecorFactory,
                 mMockInputMonitorFactory,
-                mTransactionFactory
+                mTransactionFactory,
+                mDesktopModeKeyguardChangeListener
             );
 
         doReturn(mDesktopModeWindowDecoration)
@@ -123,12 +133,16 @@
         doReturn(mTransaction).when(mTransactionFactory).get();
         doReturn(mDisplayLayout).when(mDisplayController).getDisplayLayout(anyInt());
         doReturn(STABLE_INSETS).when(mDisplayLayout).stableInsets();
+        doNothing().when(mShellController).addKeyguardChangeListener(any());
 
         when(mMockInputMonitorFactory.create(any(), any())).thenReturn(mInputMonitor);
         // InputChannel cannot be mocked because it passes to InputEventReceiver.
         final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
         inputChannels[0].dispose();
         when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]);
+
+        mDesktopModeWindowDecoration.mDisplay = mDisplay;
+        doReturn(Display.DEFAULT_DISPLAY).when(mDisplay).getDisplayId();
     }
 
     @Test
@@ -254,6 +268,32 @@
         verify(mInputMonitor, times(1)).dispose();
     }
 
+    @Test
+    public void testCaptionIsNotCreatedWhenKeyguardIsVisible() throws Exception {
+        doReturn(true).when(
+                mDesktopModeKeyguardChangeListener).isKeyguardVisibleAndOccluded();
+
+        final int taskId = 1;
+        final ActivityManager.RunningTaskInfo taskInfo =
+                createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FULLSCREEN);
+        taskInfo.isFocused = true;
+        SurfaceControl surfaceControl = mock(SurfaceControl.class);
+        runOnMainThread(() -> {
+            final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+            final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+            mDesktopModeWindowDecorViewModel.onTaskOpening(
+                    taskInfo, surfaceControl, startT, finishT);
+
+            taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+            taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+            mDesktopModeWindowDecorViewModel.onTaskChanging(
+                    taskInfo, surfaceControl, startT, finishT);
+        });
+        verify(mDesktopModeWindowDecorFactory, never())
+                .create(any(), any(), any(), any(), any(), any(), any(), any());
+    }
+
     private void runOnMainThread(Runnable r) throws Exception {
         final Handler mainHandler = new Handler(Looper.getMainLooper());
         final CountDownLatch latch = new CountDownLatch(1);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
index 8f84008..3fbab0f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
@@ -55,7 +55,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        `when`(eventHandler.handleMotionEvent(any())).thenReturn(true)
+        `when`(eventHandler.handleMotionEvent(any(), any())).thenReturn(true)
 
         dragDetector = DragDetector(eventHandler)
         dragDetector.setTouchSlop(SLOP)
@@ -72,13 +72,13 @@
     @Test
     fun testNoMove_passesDownAndUp() {
         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
                     it.source == InputDevice.SOURCE_TOUCHSCREEN
         })
 
         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y &&
                     it.source == InputDevice.SOURCE_TOUCHSCREEN
         })
@@ -86,12 +86,12 @@
 
     @Test
     fun testMoveInSlop_touch_passesDownAndUp() {
-        `when`(eventHandler.handleMotionEvent(argThat {
+        `when`(eventHandler.handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_DOWN
         })).thenReturn(false)
 
         assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
                     it.source == InputDevice.SOURCE_TOUCHSCREEN
         })
@@ -99,12 +99,12 @@
         val newX = X + SLOP - 1
         assertFalse(
                 dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
-        verify(eventHandler, never()).handleMotionEvent(argThat {
+        verify(eventHandler, never()).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_MOVE
         })
 
         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
                     it.source == InputDevice.SOURCE_TOUCHSCREEN
         })
@@ -112,13 +112,13 @@
 
     @Test
     fun testMoveInSlop_mouse_passesDownMoveAndUp() {
-        `when`(eventHandler.handleMotionEvent(argThat {
+        `when`(eventHandler.handleMotionEvent(any(), argThat {
             it.action == MotionEvent.ACTION_DOWN
         })).thenReturn(false)
 
         assertFalse(dragDetector.onMotionEvent(
                 createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
                     it.source == InputDevice.SOURCE_MOUSE
         })
@@ -126,14 +126,14 @@
         val newX = X + SLOP - 1
         assertTrue(dragDetector.onMotionEvent(
                 createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
                     it.source == InputDevice.SOURCE_MOUSE
         })
 
         assertTrue(dragDetector.onMotionEvent(
                 createMotionEvent(MotionEvent.ACTION_UP, newX, Y, isTouch = false)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
                     it.source == InputDevice.SOURCE_MOUSE
         })
@@ -141,25 +141,25 @@
 
     @Test
     fun testMoveBeyondSlop_passesDownMoveAndUp() {
-        `when`(eventHandler.handleMotionEvent(argThat {
+        `when`(eventHandler.handleMotionEvent(any(), argThat {
             it.action == MotionEvent.ACTION_DOWN
         })).thenReturn(false)
 
         assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
                     it.source == InputDevice.SOURCE_TOUCHSCREEN
         })
 
         val newX = X + SLOP + 1
         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
                     it.source == InputDevice.SOURCE_TOUCHSCREEN
         })
 
         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
                     it.source == InputDevice.SOURCE_TOUCHSCREEN
         })
@@ -167,12 +167,12 @@
 
     @Test
     fun testPassesHoverEnter() {
-        `when`(eventHandler.handleMotionEvent(argThat {
+        `when`(eventHandler.handleMotionEvent(any(), argThat {
             it.action == MotionEvent.ACTION_HOVER_ENTER
         })).thenReturn(false)
 
         assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_ENTER)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_HOVER_ENTER && it.x == X && it.y == Y
         })
     }
@@ -180,7 +180,7 @@
     @Test
     fun testPassesHoverMove() {
         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_MOVE)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_HOVER_MOVE && it.x == X && it.y == Y
         })
     }
@@ -188,7 +188,7 @@
     @Test
     fun testPassesHoverExit() {
         assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_EXIT)))
-        verify(eventHandler).handleMotionEvent(argThat {
+        verify(eventHandler).handleMotionEvent(any(), argThat {
             return@argThat it.action == MotionEvent.ACTION_HOVER_EXIT && it.x == X && it.y == Y
         })
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 69604dd..6f0599a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -22,6 +22,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.any
 import org.mockito.Mockito.argThat
@@ -71,16 +72,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        taskPositioner =
-            FluidResizeTaskPositioner(
-                mockShellTaskOrganizer,
-                mockWindowDecoration,
-                mockDisplayController,
-                DISALLOWED_AREA_FOR_END_BOUNDS,
-                mockDragStartListener,
-                mockTransactionFactory
-        )
-
         whenever(taskToken.asBinder()).thenReturn(taskBinder)
         whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
         whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
@@ -101,6 +92,15 @@
         }
         mockWindowDecoration.mDisplay = mockDisplay
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+
+        taskPositioner = FluidResizeTaskPositioner(
+                mockShellTaskOrganizer,
+                mockWindowDecoration,
+                mockDisplayController,
+                mockDragStartListener,
+                mockTransactionFactory,
+                DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+        )
     }
 
     @Test
@@ -544,7 +544,7 @@
         )
 
         val newX = STARTING_BOUNDS.right.toFloat() + 5
-        val newY = STARTING_BOUNDS.top.toFloat() + 5
+        val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
         taskPositioner.onDragPositioningMove(
                 newX,
                 newY
@@ -614,6 +614,38 @@
         })
     }
 
+    @Test
+    fun testDragResize_drag_taskPositionedInStableBounds() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_UNDEFINED, // drag
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        val newX = STARTING_BOUNDS.left.toFloat()
+        val newY = STABLE_BOUNDS.top.toFloat() - 5
+        taskPositioner.onDragPositioningMove(
+                newX,
+                newY
+        )
+        verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+        taskPositioner.onDragPositioningEnd(
+                newX,
+                newY
+        )
+        // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
+        // but not in disallowed end bounds area.
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds.top ==
+                        STABLE_BOUNDS.top
+            }
+        })
+    }
+
     companion object {
         private const val TASK_ID = 5
         private const val MIN_WIDTH = 10
@@ -622,10 +654,11 @@
         private const val DEFAULT_MIN = 40
         private const val DISPLAY_ID = 1
         private const val NAVBAR_HEIGHT = 50
+        private const val CAPTION_HEIGHT = 50
+        private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
         private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
-        private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+        private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
         private val STABLE_INSETS = Rect(0, 50, 0, 0)
-        private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 300, 300)
         private val DISALLOWED_RESIZE_AREA = Rect(
                 DISPLAY_BOUNDS.left,
                 DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
@@ -633,7 +666,7 @@
                 DISPLAY_BOUNDS.bottom)
         private val STABLE_BOUNDS = Rect(
                 DISPLAY_BOUNDS.left,
-                DISPLAY_BOUNDS.top,
+                DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
                 DISPLAY_BOUNDS.right,
                 DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
         )
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 4147dd8..3465ddd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -89,17 +89,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        taskPositioner =
-            VeiledResizeTaskPositioner(
-                mockShellTaskOrganizer,
-                mockDesktopWindowDecoration,
-                mockDisplayController,
-                DISALLOWED_AREA_FOR_END_BOUNDS,
-                mockDragStartListener,
-                mockTransactionFactory,
-                mockTransitions
-            )
-
         whenever(taskToken.asBinder()).thenReturn(taskBinder)
         whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
         whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
@@ -119,6 +108,17 @@
         }
         mockDesktopWindowDecoration.mDisplay = mockDisplay
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+
+        taskPositioner =
+                VeiledResizeTaskPositioner(
+                        mockShellTaskOrganizer,
+                        mockDesktopWindowDecoration,
+                        mockDisplayController,
+                        mockDragStartListener,
+                        mockTransactionFactory,
+                        mockTransitions,
+                        DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+                )
     }
 
     @Test
@@ -269,7 +269,7 @@
         )
 
         val newX = STARTING_BOUNDS.left.toFloat() + 5
-        val newY = STARTING_BOUNDS.top.toFloat() + 5
+        val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
         taskPositioner.onDragPositioningMove(
                 newX,
                 newY
@@ -334,6 +334,38 @@
         })
     }
 
+    @Test
+    fun testDragResize_drag_taskPositionedInStableBounds() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_UNDEFINED, // drag
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        val newX = STARTING_BOUNDS.left.toFloat()
+        val newY = STABLE_BOUNDS.top.toFloat() - 5
+        taskPositioner.onDragPositioningMove(
+                newX,
+                newY
+        )
+        verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+        taskPositioner.onDragPositioningEnd(
+                newX,
+                newY
+        )
+        // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
+        // but not in disallowed end bounds area.
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds.top ==
+                        STABLE_BOUNDS.top
+            }
+        })
+    }
+
     companion object {
         private const val TASK_ID = 5
         private const val MIN_WIDTH = 10
@@ -342,12 +374,13 @@
         private const val DEFAULT_MIN = 40
         private const val DISPLAY_ID = 1
         private const val NAVBAR_HEIGHT = 50
+        private const val CAPTION_HEIGHT = 50
+        private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
         private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
-        private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
-        private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 50, 50)
+        private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
         private val STABLE_BOUNDS = Rect(
             DISPLAY_BOUNDS.left,
-            DISPLAY_BOUNDS.top,
+            DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
             DISPLAY_BOUNDS.right,
             DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
         )
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 5a2326b..7fc1c99 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
@@ -57,7 +57,6 @@
 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;
@@ -316,7 +315,8 @@
         releaseOrder.verify(t).remove(captionContainerSurface);
         releaseOrder.verify(t).remove(decorContainerSurface);
         releaseOrder.verify(t).apply();
-        verify(mMockWindowContainerTransaction)
+        // Expect to remove two insets sources, the caption insets and the mandatory gesture insets.
+        verify(mMockWindowContainerTransaction, Mockito.times(2))
                 .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
     }
 
@@ -410,15 +410,17 @@
         verify(additionalWindowSurfaceBuilder).build();
         verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0);
         final int width = WindowDecoration.loadDimensionPixelSize(
-                mContext.getResources(), mCaptionMenuWidthId);
+                windowDecor.mDecorWindowContext.getResources(), mCaptionMenuWidthId);
         final int height = WindowDecoration.loadDimensionPixelSize(
-                mContext.getResources(), mRelayoutParams.mCaptionHeightId);
+                windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId);
         verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height);
-        final int shadowRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(),
+        final int shadowRadius = WindowDecoration.loadDimensionPixelSize(
+                windowDecor.mDecorWindowContext.getResources(),
                 mCaptionMenuShadowRadiusId);
         verify(mMockSurfaceControlAddWindowT)
                 .setShadowRadius(additionalWindowSurface, shadowRadius);
-        final int cornerRadius = WindowDecoration.loadDimensionPixelSize(mContext.getResources(),
+        final int cornerRadius = WindowDecoration.loadDimensionPixelSize(
+                windowDecor.mDecorWindowContext.getResources(),
                 mCaptionMenuCornerRadiusId);
         verify(mMockSurfaceControlAddWindowT)
                 .setCornerRadius(additionalWindowSurface, cornerRadius);
@@ -513,8 +515,7 @@
 
     private TestWindowDecoration createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
-        return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
-                mMockDisplayController, mMockShellTaskOrganizer,
+        return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
                 taskInfo, testSurface,
                 new MockObjectSupplier<>(mMockSurfaceControlBuilders,
                         () -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
index 96bfb78..d1dd8df 100644
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.kt
@@ -23,6 +23,7 @@
 import android.util.Log
 import com.android.dream.lowlight.dagger.LowLightDreamModule
 import com.android.dream.lowlight.dagger.qualifiers.Application
+import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.TimeoutCancellationException
@@ -103,6 +104,11 @@
                 )
             } catch (ex: TimeoutCancellationException) {
                 Log.e(TAG, "timed out while waiting for low light animation", ex)
+            } catch (ex: CancellationException) {
+                Log.w(TAG, "low light transition animation cancelled")
+                // Catch the cancellation so that we still set the system dream component if the
+                // animation is cancelled, such as by a user tapping to wake as the transition to
+                // low light happens.
             }
             dreamManager.setSystemDreamComponent(
                 if (shouldEnterLowLight) lowLightDreamComponent else null
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
index 4736030..de1aee5 100644
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.kt
@@ -110,15 +110,5 @@
                 }
             }
             animator.addListener(listener)
-            continuation.invokeOnCancellation {
-                try {
-                    animator.removeListener(listener)
-                    animator.cancel()
-                } catch (exception: IndexOutOfBoundsException) {
-                    // TODO(b/285666217): remove this try/catch once a proper fix is implemented.
-                    // Cancelling the animator can cause an exception since we may be removing a
-                    // listener during the cancellation. See b/285666217 for more details.
-                }
-            }
         }
 }
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt b/libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt
new file mode 100644
index 0000000..f69c84d
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/util/TruncatedInterpolator.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dream.lowlight.util
+
+import android.view.animation.Interpolator
+
+/**
+ * Interpolator wrapper that shortens another interpolator from its original duration to a portion
+ * of that duration.
+ *
+ * For example, an `originalDuration` of 1000 and a `newDuration` of 200 results in an animation
+ * that when played for 200ms is the exact same as the first 200ms of a 1000ms animation if using
+ * the original interpolator.
+ *
+ * This is useful for the transition between the user dream and the low light clock as some
+ * animations are defined in the spec to be longer than the total duration of the animation. For
+ * example, the low light clock exit translation animation is defined to last >1s while the actual
+ * fade out of the low light clock is only 250ms, meaning the clock isn't visible anymore after
+ * 250ms.
+ *
+ * Since the dream framework currently only allows one dream to be visible and running, we use this
+ * interpolator to play just the first 250ms of the translation animation. Simply reducing the
+ * duration of the animation would result in the text exiting much faster than intended, so a custom
+ * interpolator is needed.
+ */
+class TruncatedInterpolator(
+    private val baseInterpolator: Interpolator,
+    originalDuration: Float,
+    newDuration: Float
+) : Interpolator {
+    private val scaleFactor: Float
+
+    init {
+        scaleFactor = newDuration / originalDuration
+    }
+
+    override fun getInterpolation(input: Float): Float {
+        return baseInterpolator.getInterpolation(input * scaleFactor)
+    }
+}
diff --git a/libs/dream/lowlight/tests/Android.bp b/libs/dream/lowlight/tests/Android.bp
index 2d79090..64b53cb 100644
--- a/libs/dream/lowlight/tests/Android.bp
+++ b/libs/dream/lowlight/tests/Android.bp
@@ -27,6 +27,7 @@
         "androidx.test.runner",
         "androidx.test.rules",
         "androidx.test.ext.junit",
+        "animationlib",
         "frameworks-base-testutils",
         "junit",
         "kotlinx_coroutines_test",
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
index 2a886bc..de84adb 100644
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.kt
@@ -152,6 +152,21 @@
         verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
     }
 
+    @Test
+    fun setAmbientLightMode_animationCancelled_SetsSystemDream() = testScope.runTest {
+        mLowLightDreamManager.setAmbientLightMode(LowLightDreamManager.AMBIENT_LIGHT_MODE_LOW_LIGHT)
+        runCurrent()
+        cancelEnterAnimations()
+        runCurrent()
+        // Animation never finishes, but we should still set the system dream
+        verify(mDreamManager).setSystemDreamComponent(DREAM_COMPONENT)
+    }
+
+    private fun cancelEnterAnimations() {
+        val listener = withArgCaptor { verify(mEnterAnimator).addListener(capture()) }
+        listener.onAnimationCancel(mEnterAnimator)
+    }
+
     private fun completeEnterAnimations() {
         val listener = withArgCaptor { verify(mEnterAnimator).addListener(capture()) }
         listener.onAnimationEnd(mEnterAnimator)
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
index 4c526a6..9ae304f 100644
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.kt
@@ -158,26 +158,6 @@
         assertThat(job.isCancelled).isTrue()
     }
 
-    @Test
-    fun shouldCancelAnimatorWhenJobCancelled() = testScope.runTest {
-        whenever(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator)
-        val coordinator = LowLightTransitionCoordinator()
-        coordinator.setLowLightEnterListener(mEnterListener)
-        val job = launch {
-            coordinator.waitForLowLightTransitionAnimation(timeout = TIMEOUT, entering = true)
-        }
-        runCurrent()
-        // Animator listener is added and the runnable is not run yet.
-        verify(mAnimator).addListener(mAnimatorListenerCaptor.capture())
-        verify(mAnimator, never()).cancel()
-        assertThat(job.isCompleted).isFalse()
-
-        job.cancel()
-        // We should have removed the listener and cancelled the animator
-        verify(mAnimator).removeListener(mAnimatorListenerCaptor.value)
-        verify(mAnimator).cancel()
-    }
-
     companion object {
         private val TIMEOUT = 1.toDuration(DurationUnit.SECONDS)
     }
diff --git a/libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.kt b/libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.kt
new file mode 100644
index 0000000..190f02e
--- /dev/null
+++ b/libs/dream/lowlight/tests/src/com/android/dream/lowlight/util/TruncatedInterpolatorTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.dream.lowlight.util
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TruncatedInterpolatorTest {
+    @Test
+    fun truncatedInterpolator_matchesRegularInterpolator() {
+        val originalInterpolator = Interpolators.EMPHASIZED
+        val truncatedInterpolator =
+            TruncatedInterpolator(originalInterpolator, ORIGINAL_DURATION_MS, NEW_DURATION_MS)
+
+        // Both interpolators should start at the same value.
+        var animationPercent = 0f
+        Truth.assertThat(truncatedInterpolator.getInterpolation(animationPercent))
+            .isEqualTo(originalInterpolator.getInterpolation(animationPercent))
+
+        animationPercent = 1f
+        Truth.assertThat(truncatedInterpolator.getInterpolation(animationPercent))
+            .isEqualTo(originalInterpolator.getInterpolation(animationPercent * DURATION_RATIO))
+
+        animationPercent = 0.25f
+        Truth.assertThat(truncatedInterpolator.getInterpolation(animationPercent))
+            .isEqualTo(originalInterpolator.getInterpolation(animationPercent * DURATION_RATIO))
+    }
+
+    companion object {
+        private const val ORIGINAL_DURATION_MS: Float = 1000f
+        private const val NEW_DURATION_MS: Float = 200f
+        private const val DURATION_RATIO: Float = NEW_DURATION_MS / ORIGINAL_DURATION_MS
+    }
+}
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index 00919dc..b870023 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -79,7 +79,7 @@
 
 void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) {
     ATRACE_NAME("initShaderDiskCache");
-    std::lock_guard<std::mutex> lock(mMutex);
+    std::lock_guard lock(mMutex);
 
     // Emulators can switch between different renders either as part of config
     // or snapshot migration. Also, program binaries may not work well on some
@@ -88,23 +88,21 @@
         mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename));
         validateCache(identity, size);
         mInitialized = true;
+        if (identity != nullptr && size > 0 && mIDHash.size()) {
+            set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size());
+        }
     }
 }
 
 void ShaderCache::setFilename(const char* filename) {
-    std::lock_guard<std::mutex> lock(mMutex);
+    std::lock_guard lock(mMutex);
     mFilename = filename;
 }
 
-BlobCache* ShaderCache::getBlobCacheLocked() {
-    LOG_ALWAYS_FATAL_IF(!mInitialized, "ShaderCache has not been initialized");
-    return mBlobCache.get();
-}
-
 sk_sp<SkData> ShaderCache::load(const SkData& key) {
     ATRACE_NAME("ShaderCache::load");
     size_t keySize = key.size();
-    std::lock_guard<std::mutex> lock(mMutex);
+    std::lock_guard lock(mMutex);
     if (!mInitialized) {
         return nullptr;
     }
@@ -115,8 +113,7 @@
     if (!valueBuffer) {
         return nullptr;
     }
-    BlobCache* bc = getBlobCacheLocked();
-    size_t valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
+    size_t valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
     int maxTries = 3;
     while (valueSize > mObservedBlobValueSize && maxTries > 0) {
         mObservedBlobValueSize = std::min(valueSize, maxValueSize);
@@ -126,7 +123,7 @@
             return nullptr;
         }
         valueBuffer = newValueBuffer;
-        valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
+        valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
         maxTries--;
     }
     if (!valueSize) {
@@ -143,16 +140,17 @@
     return SkData::MakeFromMalloc(valueBuffer, valueSize);
 }
 
-namespace {
-// Helper for BlobCache::set to trace the result.
-void set(BlobCache* cache, const void* key, size_t keySize, const void* value, size_t valueSize) {
-    switch (cache->set(key, keySize, value, valueSize)) {
+void ShaderCache::set(const void* key, size_t keySize, const void* value, size_t valueSize) {
+    switch (mBlobCache->set(key, keySize, value, valueSize)) {
         case BlobCache::InsertResult::kInserted:
             // This is what we expect/hope. It means the cache is large enough.
             return;
         case BlobCache::InsertResult::kDidClean: {
             ATRACE_FORMAT("ShaderCache: evicted an entry to fit {key: %lu value %lu}!", keySize,
                           valueSize);
+            if (mIDHash.size()) {
+                set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size());
+            }
             return;
         }
         case BlobCache::InsertResult::kNotEnoughSpace: {
@@ -172,22 +170,22 @@
         }
     }
 }
-}  // namespace
 
 void ShaderCache::saveToDiskLocked() {
     ATRACE_NAME("ShaderCache::saveToDiskLocked");
     if (mInitialized && mBlobCache) {
-        if (mIDHash.size()) {
-            auto key = sIDKey;
-            set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size());
-        }
+        // The most straightforward way to make ownership shared
+        mMutex.unlock();
+        mMutex.lock_shared();
         mBlobCache->writeToFile();
+        mMutex.unlock_shared();
+        mMutex.lock();
     }
 }
 
 void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) {
     ATRACE_NAME("ShaderCache::store");
-    std::lock_guard<std::mutex> lock(mMutex);
+    std::lock_guard lock(mMutex);
     mNumShadersCachedInRam++;
     ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam);
 
@@ -204,11 +202,10 @@
 
     const void* value = data.data();
 
-    BlobCache* bc = getBlobCacheLocked();
     if (mInStoreVkPipelineInProgress) {
         if (mOldPipelineCacheSize == -1) {
             // Record the initial pipeline cache size stored in the file.
-            mOldPipelineCacheSize = bc->get(key.data(), keySize, nullptr, 0);
+            mOldPipelineCacheSize = mBlobCache->get(key.data(), keySize, nullptr, 0);
         }
         if (mNewPipelineCacheSize != -1 && mNewPipelineCacheSize == valueSize) {
             // There has not been change in pipeline cache size. Stop trying to save.
@@ -223,13 +220,13 @@
         mNewPipelineCacheSize = -1;
         mTryToStorePipelineCache = true;
     }
-    set(bc, key.data(), keySize, value, valueSize);
+    set(key.data(), keySize, value, valueSize);
 
     if (!mSavePending && mDeferredSaveDelayMs > 0) {
         mSavePending = true;
         std::thread deferredSaveThread([this]() {
             usleep(mDeferredSaveDelayMs * 1000);  // milliseconds to microseconds
-            std::lock_guard<std::mutex> lock(mMutex);
+            std::lock_guard lock(mMutex);
             // Store file on disk if there a new shader or Vulkan pipeline cache size changed.
             if (mCacheDirty || mNewPipelineCacheSize != mOldPipelineCacheSize) {
                 saveToDiskLocked();
@@ -245,11 +242,12 @@
 
 void ShaderCache::onVkFrameFlushed(GrDirectContext* context) {
     {
-        std::lock_guard<std::mutex> lock(mMutex);
-
+        mMutex.lock_shared();
         if (!mInitialized || !mTryToStorePipelineCache) {
+            mMutex.unlock_shared();
             return;
         }
+        mMutex.unlock_shared();
     }
     mInStoreVkPipelineInProgress = true;
     context->storeVkPipelineCacheData();
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index f5506d6..7495550 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -19,8 +19,10 @@
 #include <GrContextOptions.h>
 #include <SkRefCnt.h>
 #include <cutils/compiler.h>
+#include <ftl/shared_mutex.h>
+#include <utils/Mutex.h>
+
 #include <memory>
-#include <mutex>
 #include <string>
 #include <vector>
 
@@ -94,25 +96,23 @@
     void operator=(const ShaderCache&) = delete;
 
     /**
-     * "getBlobCacheLocked" returns the BlobCache object being used to store the
-     * key/value blob pairs.  If the BlobCache object has not yet been created,
-     * this will do so, loading the serialized cache contents from disk if
-     * possible.
-     */
-    BlobCache* getBlobCacheLocked();
-
-    /**
      * "validateCache" updates the cache to match the given identity.  If the
      * cache currently has the wrong identity, all entries in the cache are cleared.
      */
-    bool validateCache(const void* identity, ssize_t size);
+    bool validateCache(const void* identity, ssize_t size) REQUIRES(mMutex);
 
     /**
-     * "saveToDiskLocked" attemps to save the current contents of the cache to
+     * Helper for BlobCache::set to trace the result and ensure the identity hash
+     * does not get evicted.
+     */
+    void set(const void* key, size_t keySize, const void* value, size_t valueSize) REQUIRES(mMutex);
+
+    /**
+     * "saveToDiskLocked" attempts to save the current contents of the cache to
      * disk. If the identity hash exists, we will insert the identity hash into
      * the cache for next validation.
      */
-    void saveToDiskLocked();
+    void saveToDiskLocked() REQUIRES(mMutex);
 
     /**
      * "mInitialized" indicates whether the ShaderCache is in the initialized
@@ -122,16 +122,14 @@
      * the load and store methods will return without performing any cache
      * operations.
      */
-    bool mInitialized = false;
+    bool mInitialized GUARDED_BY(mMutex) = false;
 
     /**
-     * "mBlobCache" is the cache in which the key/value blob pairs are stored.  It
-     * is initially NULL, and will be initialized by getBlobCacheLocked the
-     * first time it's needed.
-     * The blob cache contains the Android build number. We treat version mismatches as an empty
-     * cache (logic implemented in BlobCache::unflatten).
+     * "mBlobCache" is the cache in which the key/value blob pairs are stored.
+     * The blob cache contains the Android build number. We treat version mismatches
+     * as an empty cache (logic implemented in BlobCache::unflatten).
      */
-    std::unique_ptr<FileBlobCache> mBlobCache;
+    std::unique_ptr<FileBlobCache> mBlobCache GUARDED_BY(mMutex);
 
     /**
      * "mFilename" is the name of the file for storing cache contents in between
@@ -140,7 +138,7 @@
      * empty string indicates that the cache should not be saved to or restored
      * from disk.
      */
-    std::string mFilename;
+    std::string mFilename GUARDED_BY(mMutex);
 
     /**
      * "mIDHash" is the current identity hash for the cache validation. It is
@@ -149,7 +147,7 @@
      * indicates that cache validation is not performed, and the hash should
      * not be stored on disk.
      */
-    std::vector<uint8_t> mIDHash;
+    std::vector<uint8_t> mIDHash GUARDED_BY(mMutex);
 
     /**
      * "mSavePending" indicates whether or not a deferred save operation is
@@ -159,7 +157,7 @@
      * contents to disk, unless mDeferredSaveDelayMs is 0 in which case saving
      * is disabled.
      */
-    bool mSavePending = false;
+    bool mSavePending GUARDED_BY(mMutex) = false;
 
     /**
      *  "mObservedBlobValueSize" is the maximum value size observed by the cache reading function.
@@ -174,16 +172,16 @@
     unsigned int mDeferredSaveDelayMs = 4 * 1000;
 
     /**
-     * "mMutex" is the mutex used to prevent concurrent access to the member
+     * "mMutex" is the shared mutex used to prevent concurrent access to the member
      * variables. It must be locked whenever the member variables are accessed.
      */
-    mutable std::mutex mMutex;
+    mutable ftl::SharedMutex mMutex;
 
     /**
      *  If set to "true", the next call to onVkFrameFlushed, will invoke
      * GrCanvas::storeVkPipelineCacheData. This does not guarantee that data will be stored on disk.
      */
-    bool mTryToStorePipelineCache = true;
+    bool mTryToStorePipelineCache GUARDED_BY(mMutex) = true;
 
     /**
      * This flag is used by "ShaderCache::store" to distinguish between shader data and
@@ -195,16 +193,16 @@
      *  "mNewPipelineCacheSize" has the size of the new Vulkan pipeline cache data. It is used
      *  to prevent unnecessary disk writes, if the pipeline cache size has not changed.
      */
-    size_t mNewPipelineCacheSize = -1;
+    size_t mNewPipelineCacheSize GUARDED_BY(mMutex) = -1;
     /**
      *  "mOldPipelineCacheSize" has the size of the Vulkan pipeline cache data stored on disk.
      */
-    size_t mOldPipelineCacheSize = -1;
+    size_t mOldPipelineCacheSize GUARDED_BY(mMutex) = -1;
 
     /**
      *  "mCacheDirty" is true when there is new shader cache data, which is not saved to disk.
      */
-    bool mCacheDirty = false;
+    bool mCacheDirty GUARDED_BY(mMutex) = false;
 
     /**
      * "sCache" is the singleton ShaderCache object.
@@ -221,7 +219,7 @@
      * interesting to keep track of how many shaders are stored in RAM. This
      * class provides a convenient entry point for that.
      */
-    int mNumShadersCachedInRam = 0;
+    int mNumShadersCachedInRam GUARDED_BY(mMutex) = 0;
 
     friend class ShaderCacheTestUtils;  // used for unit testing
 };
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 16b35ff..a5518eb 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -405,8 +405,17 @@
 
     // If the previous frame was dropped we don't need to hold onto it, so
     // just keep using the previous frame's structure instead
-    if (!wasSkipped(mCurrentFrameInfo)) {
+    if (wasSkipped(mCurrentFrameInfo)) {
+        // Use the oldest skipped frame in case we skip more than a single frame
+        if (!mSkippedFrameInfo) {
+            mSkippedFrameInfo.emplace();
+            mSkippedFrameInfo->vsyncId =
+                mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
+            mSkippedFrameInfo->startTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
+        }
+    } else {
         mCurrentFrameInfo = mJankTracker.startFrame();
+        mSkippedFrameInfo.reset();
     }
 
     mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
@@ -602,10 +611,18 @@
         if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
             const auto inputEventId =
                     static_cast<int32_t>(mCurrentFrameInfo->get(FrameInfoIndex::InputEventId));
-            native_window_set_frame_timeline_info(
-                    mNativeSurface->getNativeWindow(), frameCompleteNr, vsyncId, inputEventId,
-                    mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime),
-                    solelyTextureViewUpdates);
+            const ANativeWindowFrameTimelineInfo ftl = {
+                    .frameNumber = frameCompleteNr,
+                    .frameTimelineVsyncId = vsyncId,
+                    .inputEventId = inputEventId,
+                    .startTimeNanos = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime),
+                    .useForRefreshRateSelection = solelyTextureViewUpdates,
+                    .skippedFrameVsyncId = mSkippedFrameInfo ? mSkippedFrameInfo->vsyncId
+                                                             : UiFrameInfoBuilder::INVALID_VSYNC_ID,
+                    .skippedFrameStartTimeNanos =
+                            mSkippedFrameInfo ? mSkippedFrameInfo->startTime : 0,
+            };
+            native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), ftl);
         }
     }
 
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 5219b57..32ac5af 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -366,6 +366,12 @@
 
     ColorMode mColorMode = ColorMode::Default;
     float mTargetSdrHdrRatio = 1.f;
+
+    struct SkippedFrameInfo {
+        int64_t vsyncId;
+        int64_t startTime;
+    };
+    std::optional<SkippedFrameInfo> mSkippedFrameInfo;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 7bcd45c..9aa2e1d 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -49,7 +49,7 @@
      */
     static void reinitializeAllFields(ShaderCache& cache) {
         ShaderCache newCache = ShaderCache();
-        std::lock_guard<std::mutex> lock(cache.mMutex);
+        std::lock_guard lock(cache.mMutex), newLock(newCache.mMutex);
         // By order of declaration
         cache.mInitialized = newCache.mInitialized;
         cache.mBlobCache.reset(nullptr);
@@ -72,7 +72,7 @@
      * manually, as seen in the "terminate" testing helper function.
      */
     static void setSaveDelayMs(ShaderCache& cache, unsigned int saveDelayMs) {
-        std::lock_guard<std::mutex> lock(cache.mMutex);
+        std::lock_guard lock(cache.mMutex);
         cache.mDeferredSaveDelayMs = saveDelayMs;
     }
 
@@ -81,7 +81,7 @@
      * Next call to "initShaderDiskCache" will load again the in-memory cache from disk.
      */
     static void terminate(ShaderCache& cache, bool saveContent) {
-        std::lock_guard<std::mutex> lock(cache.mMutex);
+        std::lock_guard lock(cache.mMutex);
         if (saveContent) {
             cache.saveToDiskLocked();
         }
@@ -93,6 +93,7 @@
      */
     template <typename T>
     static bool validateCache(ShaderCache& cache, std::vector<T> hash) {
+        std::lock_guard lock(cache.mMutex);
         return cache.validateCache(hash.data(), hash.size() * sizeof(T));
     }
 
@@ -108,7 +109,7 @@
      */
     static void waitForPendingSave(ShaderCache& cache, const int timeoutMs = 50) {
         {
-            std::lock_guard<std::mutex> lock(cache.mMutex);
+            std::lock_guard lock(cache.mMutex);
             ASSERT_TRUE(cache.mSavePending);
         }
         bool saving = true;
@@ -123,7 +124,7 @@
             usleep(delayMicroseconds);
             elapsedMilliseconds += (float)delayMicroseconds / 1000;
 
-            std::lock_guard<std::mutex> lock(cache.mMutex);
+            std::lock_guard lock(cache.mMutex);
             saving = cache.mSavePending;
         }
     }
diff --git a/media/java/android/media/AudioFocusRequest.java b/media/java/android/media/AudioFocusRequest.java
index 4c0850b..4ad3cd1 100644
--- a/media/java/android/media/AudioFocusRequest.java
+++ b/media/java/android/media/AudioFocusRequest.java
@@ -39,8 +39,8 @@
  * but there is only one the user would really listen to (focus on), while the other plays in
  * the background. An example of this is driving directions being spoken while music plays at
  * a reduced volume (a.k.a. ducking).
- * <p>When an application requests audio focus, it expresses its intention to “own” audio focus to
- * play audio. Let’s review the different types of focus requests, the return value after a request,
+ * <p>When an application requests audio focus, it expresses its intention to "own" audio focus to
+ * play audio. Let's review the different types of focus requests, the return value after a request,
  * and the responses to a loss.
  * <p class="note">Note: applications should not play anything until granted focus.</p>
  *
@@ -51,7 +51,7 @@
  * <li>{@link AudioManager#AUDIOFOCUS_GAIN} expresses the fact that your application is now the
  * sole source of audio that the user is listening to. The duration of the audio playback is
  * unknown, and is possibly very long: after the user finishes interacting with your application,
- * (s)he doesn’t expect another audio stream to resume. Examples of uses of this focus gain are
+ * (s)he doesn't expect another audio stream to resume. Examples of uses of this focus gain are
  * for music playback, for a game or a video player.</li>
  *
  * <li>{@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT} is for a situation when you know your
@@ -60,20 +60,20 @@
  * for playing an alarm, or during a VoIP call. The playback is known to be finite: the alarm will
  * time-out or be dismissed, the VoIP call has a beginning and an end. When any of those events
  * ends, and if the user was listening to music when it started, the user expects music to resume,
- * but didn’t wish to listen to both at the same time.</li>
+ * but didn't wish to listen to both at the same time.</li>
  *
  * <li>{@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}: this focus request type is similar
  * to {@code AUDIOFOCUS_GAIN_TRANSIENT} for the temporary aspect of the focus request, but it also
  * expresses the fact during the time you own focus, you allow another application to keep playing
- * at a reduced volume, “ducked”. Examples are when playing driving directions or notifications,
- * it’s ok for music to keep playing, but not loud enough that it would prevent the directions to
- * be hard to understand. A typical attenuation by the “ducked” application is a factor of 0.2f
+ * at a reduced volume, "ducked". Examples are when playing driving directions or notifications,
+ * it's ok for music to keep playing, but not loud enough that it would prevent the directions to
+ * be hard to understand. A typical attenuation by the "ducked" application is a factor of 0.2f
  * (or -14dB), that can for instance be applied with {@code MediaPlayer.setVolume(0.2f)} when
  * using this class for playback.</li>
  *
  * <li>{@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE} is also for a temporary request,
  * but also expresses that your application expects the device to not play anything else. This is
- * typically used if you are doing audio recording or speech recognition, and don’t want for
+ * typically used if you are doing audio recording or speech recognition, and don't want for
  * examples notifications to be played by the system during that time.</li>
  * </ul>
  *
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 4759689..e8c9d0d 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6929,6 +6929,114 @@
 
     /**
      * @hide
+     * Describes an audio device that has not been categorized with a specific
+     * audio type.
+     */
+    public static final int AUDIO_DEVICE_CATEGORY_UNKNOWN = 0;
+
+    /**
+     * @hide
+     * Describes an audio device which is categorized as something different.
+     */
+    public static final int AUDIO_DEVICE_CATEGORY_OTHER = 1;
+
+    /**
+     * @hide
+     * Describes an audio device which was categorized as speakers.
+     */
+    public static final int AUDIO_DEVICE_CATEGORY_SPEAKER = 2;
+
+    /**
+     * @hide
+     * Describes an audio device which was categorized as headphones.
+     */
+    public static final int AUDIO_DEVICE_CATEGORY_HEADPHONES = 3;
+
+    /**
+     * @hide
+     * Describes an audio device which was categorized as car-kit.
+     */
+    public static final int AUDIO_DEVICE_CATEGORY_CARKIT = 4;
+
+    /**
+     * @hide
+     * Describes an audio device which was categorized as watch.
+     */
+    public static final int AUDIO_DEVICE_CATEGORY_WATCH = 5;
+
+    /**
+     * @hide
+     * Describes an audio device which was categorized as hearing aid.
+     */
+    public static final int AUDIO_DEVICE_CATEGORY_HEARING_AID = 6;
+
+    /**
+     * @hide
+     * Describes an audio device which was categorized as receiver.
+     */
+    public static final int AUDIO_DEVICE_CATEGORY_RECEIVER = 7;
+
+    /** @hide */
+    @IntDef(flag = false, prefix = "AUDIO_DEVICE_CATEGORY", value = {
+            AUDIO_DEVICE_CATEGORY_UNKNOWN,
+            AUDIO_DEVICE_CATEGORY_OTHER,
+            AUDIO_DEVICE_CATEGORY_SPEAKER,
+            AUDIO_DEVICE_CATEGORY_HEADPHONES,
+            AUDIO_DEVICE_CATEGORY_CARKIT,
+            AUDIO_DEVICE_CATEGORY_WATCH,
+            AUDIO_DEVICE_CATEGORY_HEARING_AID,
+            AUDIO_DEVICE_CATEGORY_RECEIVER }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AudioDeviceCategory {}
+
+    /** @hide */
+    public static String audioDeviceCategoryToString(int audioDeviceCategory) {
+        switch (audioDeviceCategory) {
+            case AUDIO_DEVICE_CATEGORY_UNKNOWN: return "AUDIO_DEVICE_CATEGORY_UNKNOWN";
+            case AUDIO_DEVICE_CATEGORY_OTHER: return "AUDIO_DEVICE_CATEGORY_OTHER";
+            case AUDIO_DEVICE_CATEGORY_SPEAKER: return "AUDIO_DEVICE_CATEGORY_SPEAKER";
+            case AUDIO_DEVICE_CATEGORY_HEADPHONES: return "AUDIO_DEVICE_CATEGORY_HEADPHONES";
+            case AUDIO_DEVICE_CATEGORY_CARKIT: return "AUDIO_DEVICE_CATEGORY_CARKIT";
+            case AUDIO_DEVICE_CATEGORY_WATCH: return "AUDIO_DEVICE_CATEGORY_WATCH";
+            case AUDIO_DEVICE_CATEGORY_HEARING_AID: return "AUDIO_DEVICE_CATEGORY_HEARING_AID";
+            case AUDIO_DEVICE_CATEGORY_RECEIVER: return "AUDIO_DEVICE_CATEGORY_RECEIVER";
+            default:
+                return new StringBuilder("unknown AudioDeviceCategory ").append(
+                        audioDeviceCategory).toString();
+        }
+    }
+
+    /**
+     * @hide
+     * Sets the audio device type of a Bluetooth device given its MAC address
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    public void setBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle,
+            @AudioDeviceCategory int btAudioDeviceType) {
+        try {
+            getService().setBluetoothAudioDeviceCategory(address, isBle, btAudioDeviceType);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Gets the audio device type of a Bluetooth device given its MAC address
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    @AudioDeviceCategory
+    public int getBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle) {
+        try {
+            return getService().getBluetoothAudioDeviceCategory(address, isBle);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
      * Sound dose warning at every 100% of dose during integration window
      */
     public static final int CSD_WARNING_DOSE_REACHED_1X = 1;
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 7ce189b..180c7fd 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -322,6 +322,12 @@
     @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
     boolean isCsdEnabled();
 
+    @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+    oneway void setBluetoothAudioDeviceCategory(in String address, boolean isBle, int deviceType);
+
+    @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+    int getBluetoothAudioDeviceCategory(in String address, boolean isBle);
+
     int setHdmiSystemAudioSupported(boolean on);
 
     boolean isHdmiSystemAudioSupported();
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 4323c73..1ee5aa3 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1057,7 +1057,7 @@
      * this API to pass the cookies as a list of HttpCookie. If the app has not installed
      * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with
      * the provided cookies. If the app has installed its own handler already, this API requires the
-     * handler to be of CookieManager type such that the API can update the manager’s CookieStore.
+     * handler to be of CookieManager type such that the API can update the manager's CookieStore.
      *
      * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
      * but that can be changed with key/value pairs through the headers parameter with
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index d2b21ae..43acdd5 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -46,7 +46,9 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.BaseColumns;
 import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.AudioColumns;
 import android.provider.MediaStore.MediaColumns;
 import android.provider.Settings;
 import android.provider.Settings.System;
@@ -507,6 +509,95 @@
         return getUriFromCursor(mContext, mCursor);
     }
 
+    /**
+     * Gets the valid ringtone uri by a given uri string and ringtone type for the restore purpose.
+     *
+     * @param contentResolver ContentResolver to execute media query.
+     * @param value a canonicalized uri which refers to the ringtone.
+     * @param ringtoneType an integer representation of the kind of uri that is being restored, can
+     *     be RingtoneManager.TYPE_RINGTONE, RingtoneManager.TYPE_NOTIFICATION, or
+     *     RingtoneManager.TYPE_ALARM.
+     * @hide
+     */
+    public static @Nullable Uri getRingtoneUriForRestore(
+            @NonNull ContentResolver contentResolver, @Nullable String value, int ringtoneType)
+            throws FileNotFoundException, IllegalArgumentException {
+        if (value == null) {
+            // Return a valid null. It means the null value is intended instead of a failure.
+            return null;
+        }
+
+        Uri ringtoneUri;
+        final Uri canonicalUri = Uri.parse(value);
+
+        // Try to get the media uri via the regular uncanonicalize method first.
+        ringtoneUri = contentResolver.uncanonicalize(canonicalUri);
+        if (ringtoneUri != null) {
+            // Canonicalize it to make the result contain the right metadata of the media asset.
+            ringtoneUri = contentResolver.canonicalize(ringtoneUri);
+            return ringtoneUri;
+        }
+
+        // Query the media by title and ringtone type.
+        final String title = canonicalUri.getQueryParameter(AudioColumns.TITLE);
+        Uri baseUri = ContentUris.removeId(canonicalUri).buildUpon().clearQuery().build();
+        String ringtoneTypeSelection = "";
+        switch (ringtoneType) {
+            case RingtoneManager.TYPE_RINGTONE:
+                ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_RINGTONE;
+                break;
+            case RingtoneManager.TYPE_NOTIFICATION:
+                ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_NOTIFICATION;
+                break;
+            case RingtoneManager.TYPE_ALARM:
+                ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_ALARM;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown ringtone type: " + ringtoneType);
+        }
+
+        final String selection = ringtoneTypeSelection + "=1 AND " + AudioColumns.TITLE + "=?";
+        Cursor cursor = null;
+        try {
+            cursor =
+                    contentResolver.query(
+                            baseUri,
+                            /* projection */ new String[] {BaseColumns._ID},
+                            /* selection */ selection,
+                            /* selectionArgs */ new String[] {title},
+                            /* sortOrder */ null,
+                            /* cancellationSignal */ null);
+
+        } catch (IllegalArgumentException e) {
+            throw new FileNotFoundException("Volume not found for " + baseUri);
+        }
+        if (cursor == null) {
+            throw new FileNotFoundException("Missing cursor for " + baseUri);
+        } else if (cursor.getCount() == 0) {
+            FileUtils.closeQuietly(cursor);
+            throw new FileNotFoundException("No item found for " + baseUri);
+        } else if (cursor.getCount() > 1) {
+            // Find more than 1 result.
+            // We are not sure which one is the right ringtone file so just abandon this case.
+            FileUtils.closeQuietly(cursor);
+            throw new FileNotFoundException(
+                    "Find multiple ringtone candidates by title+ringtone_type query: count: "
+                            + cursor.getCount());
+        }
+        if (cursor.moveToFirst()) {
+            ringtoneUri = ContentUris.withAppendedId(baseUri, cursor.getLong(0));
+            FileUtils.closeQuietly(cursor);
+        } else {
+            FileUtils.closeQuietly(cursor);
+            throw new FileNotFoundException("Failed to read row from the result.");
+        }
+
+        // Canonicalize it to make the result contain the right metadata of the media asset.
+        ringtoneUri = contentResolver.canonicalize(ringtoneUri);
+        Log.v(TAG, "Find a valid result: " + ringtoneUri);
+        return ringtoneUri;
+    }
+
     private static Uri getUriFromCursor(Context context, Cursor cursor) {
         final Uri uri = ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)),
                 cursor.getLong(ID_COLUMN_INDEX));
diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java
index f3dfeb6..2795cfe 100644
--- a/media/java/android/media/audiofx/Visualizer.java
+++ b/media/java/android/media/audiofx/Visualizer.java
@@ -455,11 +455,13 @@
      * a number of consecutive 8-bit (unsigned) mono PCM samples equal to the capture size returned
      * by {@link #getCaptureSize()}.
      * <p>This method must be called when the Visualizer is enabled.
-     * @param waveform array of bytes where the waveform should be returned
+     * @param waveform array of bytes where the waveform should be returned, array length must be
+     * at least equals to the capture size returned by {@link #getCaptureSize()}.
      * @return {@link #SUCCESS} in case of success,
      * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT}
      * in case of failure.
      * @throws IllegalStateException
+     * @throws IllegalArgumentException
      */
     public int getWaveForm(byte[] waveform)
     throws IllegalStateException {
@@ -467,6 +469,12 @@
             if (mState != STATE_ENABLED) {
                 throw(new IllegalStateException("getWaveForm() called in wrong state: "+mState));
             }
+            int captureSize = getCaptureSize();
+            if (captureSize > waveform.length) {
+                throw(new IllegalArgumentException("getWaveForm() called with illegal size: "
+                                                   + waveform.length + " expecting at least "
+                                                   + captureSize + " bytes"));
+            }
             return native_getWaveForm(waveform);
         }
     }
diff --git a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl
index 2231ce1..e46d34e 100644
--- a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl
+++ b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl
@@ -17,9 +17,22 @@
 package android.media.projection;
 
 import android.media.projection.MediaProjectionInfo;
+import android.view.ContentRecordingSession;
 
 /** {@hide} */
 oneway interface IMediaProjectionWatcherCallback {
     void onStart(in MediaProjectionInfo info);
     void onStop(in MediaProjectionInfo info);
+    /**
+     * Called when the {@link ContentRecordingSession} was set for the current media
+     * projection.
+     *
+     * @param info    always present and contains information about the media projection host.
+     * @param session the recording session for the current media projection. Can be
+     *                {@code null} when the recording will stop.
+     */
+    void onRecordingSessionSet(
+        in MediaProjectionInfo info,
+        in @nullable ContentRecordingSession session
+    );
 }
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index fb72c7b..223b432c 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -83,6 +83,7 @@
         try {
             mImpl.start(new MediaProjectionCallback());
         } catch (RemoteException e) {
+            Log.e(TAG, "Content Recording: Failed to start media projection", e);
             throw new RuntimeException("Failed to start media projection", e);
         }
         mDisplayManager = displayManager;
@@ -105,11 +106,18 @@
      * @see #unregisterCallback
      */
     public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
-        final Callback c = Objects.requireNonNull(callback);
-        if (handler == null) {
-            handler = new Handler();
+        try {
+            final Callback c = Objects.requireNonNull(callback);
+            if (handler == null) {
+                handler = new Handler();
+            }
+            mCallbacks.put(c, new CallbackRecord(c, handler));
+        } catch (NullPointerException e) {
+            Log.e(TAG, "Content Recording: cannot register null Callback", e);
+            throw e;
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Content Recording: failed to create new Handler to register Callback", e);
         }
-        mCallbacks.put(c, new CallbackRecord(c, handler));
     }
 
     /**
@@ -120,8 +128,13 @@
      * @see #registerCallback
      */
     public void unregisterCallback(@NonNull Callback callback) {
-        final Callback c = Objects.requireNonNull(callback);
-        mCallbacks.remove(c);
+        try {
+            final Callback c = Objects.requireNonNull(callback);
+            mCallbacks.remove(c);
+        } catch (NullPointerException e) {
+            Log.d(TAG, "Content Recording: cannot unregister null Callback", e);
+            throw e;
+        }
     }
 
     /**
@@ -203,9 +216,11 @@
             @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
         if (shouldMediaProjectionRequireCallback()) {
             if (mCallbacks.isEmpty()) {
-                throw new IllegalStateException(
+                final IllegalStateException e = new IllegalStateException(
                         "Must register a callback before starting capture, to manage resources in"
                                 + " response to MediaProjection states.");
+                Log.e(TAG, "Content Recording: no callback registered for virtual display", e);
+                throw e;
             }
         }
         final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
@@ -272,6 +287,7 @@
      */
     public void stop() {
         try {
+            Log.d(TAG, "Content Recording: stopping projection");
             mImpl.stop();
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to stop projection", e);
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 5703c42..9790d02 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -29,6 +29,7 @@
 import android.os.ServiceManager;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.view.ContentRecordingSession;
 import android.view.Surface;
 
 import java.util.Map;
@@ -255,6 +256,7 @@
      */
     public void stopActiveProjection() {
         try {
+            Log.d(TAG, "Content Recording: stopping active projection");
             mService.stopActiveProjection();
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to stop the currently active media projection", e);
@@ -268,6 +270,7 @@
      */
     public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
         if (callback == null) {
+            Log.w(TAG, "Content Recording: cannot add null callback");
             throw new IllegalArgumentException("callback must not be null");
         }
         CallbackDelegate delegate = new CallbackDelegate(callback, handler);
@@ -285,6 +288,7 @@
      */
     public void removeCallback(@NonNull Callback callback) {
         if (callback == null) {
+            Log.w(TAG, "ContentRecording: cannot remove null callback");
             throw new IllegalArgumentException("callback must not be null");
         }
         CallbackDelegate delegate = mCallbacks.remove(callback);
@@ -300,7 +304,22 @@
     /** @hide */
     public static abstract class Callback {
         public abstract void onStart(MediaProjectionInfo info);
+
         public abstract void onStop(MediaProjectionInfo info);
+
+        /**
+         * Called when the {@link ContentRecordingSession} was set for the current media
+         * projection.
+         *
+         * @param info    always present and contains information about the media projection host.
+         * @param session the recording session for the current media projection. Can be
+         *                {@code null} when the recording will stop.
+         */
+        public void onRecordingSessionSet(
+                @NonNull MediaProjectionInfo info,
+                @Nullable ContentRecordingSession session
+        ) {
+        }
     }
 
     /** @hide */
@@ -335,5 +354,13 @@
                 }
             });
         }
+
+        @Override
+        public void onRecordingSessionSet(
+                @NonNull final MediaProjectionInfo info,
+                @Nullable final ContentRecordingSession session
+        ) {
+            mHandler.post(() -> mCallback.onRecordingSessionSet(info, session));
+        }
     }
 }
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 29e8716..cda919f 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -442,6 +442,7 @@
      * but it must be released if your activity or service is being destroyed.
      */
     public void release() {
+        setCallback(null);
         try {
             mBinder.destroySession();
         } catch (RemoteException e) {
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index dea7f03..5ea98c0c 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -2615,7 +2615,7 @@
             return;
         }
         auto cryptoInfo =
-                cryptoInfoObj ? NativeCryptoInfo{size} : NativeCryptoInfo{env, cryptoInfoObj};
+                cryptoInfoObj ? NativeCryptoInfo{env, cryptoInfoObj} : NativeCryptoInfo{size};
         if (env->ExceptionCheck()) {
             // Creation of cryptoInfo failed. Let the exception bubble up.
             return;
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.html b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
index 67d2184..ad18a9d 100644
--- a/packages/CarrierDefaultApp/assets/slice_purchase_test.html
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.html
@@ -20,7 +20,7 @@
     <meta charset="UTF-8">
     <meta name="description" content="
     This is a HTML page that calls and verifies responses from the @JavascriptInterface functions of
-    SlicePurchaseWebInterface. Test slice purchase application behavior using ADB shell commands and
+    DataBoostWebServiceFlow. Test slice purchase application behavior using ADB shell commands and
     the APIs below:
 
     FROM TERMINAL:
@@ -65,8 +65,8 @@
     <p id="requested_capability"></p>
 
     <h2>Notify purchase successful</h2>
-    <button type="button" onclick="testNotifyPurchaseSuccessful(60000)">
-        Notify purchase successful for 1 minute
+    <button type="button" onclick="testNotifyPurchaseSuccessful()">
+        Notify purchase successful
     </button>
     <p id="purchase_successful"></p>
 
@@ -75,5 +75,13 @@
         Notify purchase failed
     </button>
     <p id="purchase_failed"></p>
+
+    <h2>Dismiss flow</h2>
+    <button type="button" onclick="testDismissFlow()">
+        Dismiss flow
+    </button>
+    <p id="dismiss_flow"></p>
+
+    <h2>Test <a href="http://www.google.com">hyperlink</a></h2>
 </body>
 </html>
diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.js b/packages/CarrierDefaultApp/assets/slice_purchase_test.js
index 02c4fea..be397a1 100644
--- a/packages/CarrierDefaultApp/assets/slice_purchase_test.js
+++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.js
@@ -15,19 +15,25 @@
  */
 
 function testGetRequestedCapability() {
-    let capability = SlicePurchaseWebInterface.getRequestedCapability();
+    let capability = DataBoostWebServiceFlow.getRequestedCapability();
     document.getElementById("requested_capability").innerHTML =
             "Premium capability requested: " + capability;
 }
 
-function testNotifyPurchaseSuccessful(duration_ms_long = 0) {
-    SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration_ms_long);
+function testNotifyPurchaseSuccessful() {
+    DataBoostWebServiceFlow.notifyPurchaseSuccessful();
     document.getElementById("purchase_successful").innerHTML =
-            "Notified purchase success for duration: " + duration_ms_long;
+            "Notified purchase successful.";
 }
 
 function testNotifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") {
-    SlicePurchaseWebInterface.notifyPurchaseFailed(failure_code, failure_reason);
+    DataBoostWebServiceFlow.notifyPurchaseFailed(failure_code, failure_reason);
     document.getElementById("purchase_failed").innerHTML =
             "Notified purchase failed.";
 }
+
+function testDismissFlow() {
+    DataBoostWebServiceFlow.dismissFlow();
+    document.getElementById("dismiss_flow").innerHTML =
+            "Called dismiss flow.";
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
new file mode 100644
index 0000000..4500a22
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
@@ -0,0 +1,108 @@
+/*
+ * 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.carrierdefaultapp;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.telephony.TelephonyManager;
+import android.webkit.JavascriptInterface;
+
+import com.android.phone.slice.SlicePurchaseController;
+
+/**
+ * Data boost web service flow interface allowing carrier websites to send responses back to the
+ * slice purchase application using JavaScript.
+ */
+public class DataBoostWebServiceFlow {
+    @NonNull SlicePurchaseActivity mActivity;
+
+    public DataBoostWebServiceFlow(@NonNull SlicePurchaseActivity activity) {
+        mActivity = activity;
+    }
+
+    /**
+     * Interface method allowing the carrier website to get the premium capability
+     * that was requested to purchase.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function getRequestedCapability(duration) {
+     *         DataBoostWebServiceFlow.getRequestedCapability();
+     *     }
+     * </script>
+     */
+    @JavascriptInterface
+    @TelephonyManager.PremiumCapability public int getRequestedCapability() {
+        return mActivity.mCapability;
+    }
+
+    /**
+     * Interface method allowing the carrier website to notify the slice purchase application of
+     * a successful premium capability purchase and the duration for which the premium capability is
+     * purchased.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function notifyPurchaseSuccessful() {
+     *         DataBoostWebServiceFlow.notifyPurchaseSuccessful();
+     *     }
+     * </script>
+     */
+    @JavascriptInterface
+    public void notifyPurchaseSuccessful() {
+        mActivity.onPurchaseSuccessful();
+    }
+
+    /**
+     * Interface method allowing the carrier website to notify the slice purchase application of
+     * a failed premium capability purchase.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function notifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") {
+     *         DataBoostWebServiceFlow.notifyPurchaseFailed();
+     *     }
+     * </script>
+     *
+     * @param failureCode The failure code.
+     * @param failureReason If the failure code is
+     *                      {@link SlicePurchaseController#FAILURE_CODE_UNKNOWN},
+     *                      the human-readable reason for failure.
+     */
+    @JavascriptInterface
+    public void notifyPurchaseFailed(@SlicePurchaseController.FailureCode int failureCode,
+            @Nullable String failureReason) {
+        mActivity.onPurchaseFailed(failureCode, failureReason);
+    }
+
+    /**
+     * Interface method allowing the carrier website to notify the slice purchase application that
+     * the service flow ended prematurely. This can be due to user action, an error in the
+     * web sheet logic, or an error on the network side.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function dismissFlow() {
+     *         DataBoostWebServiceFlow.dismissFlow();
+     *     }
+     * </script>
+     */
+    @JavascriptInterface
+    public void dismissFlow() {
+        mActivity.onDismissFlow();
+    }
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index 946185a..fcc4ec1 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -27,26 +27,28 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
+import android.webkit.CookieManager;
 import android.webkit.WebView;
+import android.webkit.WebViewClient;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.phone.slice.SlicePurchaseController;
 
 import java.net.URL;
-import java.util.concurrent.TimeUnit;
+import java.util.Base64;
 
 /**
  * Activity that launches when the user clicks on the performance boost notification.
  * This will open a {@link WebView} for the carrier website to allow the user to complete the
  * premium capability purchase.
  * The carrier website can get the requested premium capability using the JavaScript interface
- * method {@code SlicePurchaseWebInterface.getRequestedCapability()}.
+ * method {@code DataBoostWebServiceFlow.getRequestedCapability()}.
  * If the purchase is successful, the carrier website shall notify the slice purchase application
  * using the JavaScript interface method
- * {@code SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration)}, where {@code duration} is
- * the optional duration of the performance boost.
+ * {@code DataBoostWebServiceFlow.notifyPurchaseSuccessful()}.
  * If the purchase was not successful, the carrier website shall notify the slice purchase
  * application using the JavaScript interface method
- * {@code SlicePurchaseWebInterface.notifyPurchaseFailed(code, reason)}, where {@code code} is the
+ * {@code DataBoostWebServiceFlow.notifyPurchaseFailed(code, reason)}, where {@code code} is the
  * {@link SlicePurchaseController.FailureCode} indicating the reason for failure and {@code reason}
  * is the human-readable reason for failure if the failure code is
  * {@link SlicePurchaseController#FAILURE_CODE_UNKNOWN}.
@@ -56,11 +58,17 @@
 public class SlicePurchaseActivity extends Activity {
     private static final String TAG = "SlicePurchaseActivity";
 
+    private static final int CONTENTS_TYPE_UNSPECIFIED = 0;
+    private static final int CONTENTS_TYPE_JSON = 1;
+    private static final int CONTENTS_TYPE_XML = 2;
+
     @NonNull private WebView mWebView;
     @NonNull private Context mApplicationContext;
     @NonNull private Intent mIntent;
     @NonNull private URL mUrl;
     @TelephonyManager.PremiumCapability protected int mCapability;
+    @Nullable private String mUserData;
+    private int mContentsType;
     private boolean mIsUserTriggeredFinish;
 
     @Override
@@ -72,6 +80,7 @@
         mCapability = mIntent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
                 SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
         String url = mIntent.getStringExtra(SlicePurchaseController.EXTRA_PURCHASE_URL);
+        mUserData = mIntent.getStringExtra(SlicePurchaseController.EXTRA_USER_DATA);
         mApplicationContext = getApplicationContext();
         mIsUserTriggeredFinish = true;
         logd("onCreate: subId=" + subId + ", capability="
@@ -81,7 +90,17 @@
         SlicePurchaseBroadcastReceiver.cancelNotification(mApplicationContext, mCapability);
 
         // Verify purchase URL is valid
-        mUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url);
+        String contentsType = mIntent.getStringExtra(SlicePurchaseController.EXTRA_CONTENTS_TYPE);
+        mContentsType = CONTENTS_TYPE_UNSPECIFIED;
+        if (!TextUtils.isEmpty(contentsType)) {
+            if (contentsType.equals("json")) {
+                mContentsType = CONTENTS_TYPE_JSON;
+            } else if (contentsType.equals("xml")) {
+                mContentsType = CONTENTS_TYPE_XML;
+            }
+        }
+        mUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url, mUserData,
+                mContentsType == CONTENTS_TYPE_UNSPECIFIED);
         if (mUrl == null) {
             String error = "Unable to create a purchase URL.";
             loge(error);
@@ -95,6 +114,20 @@
             return;
         }
 
+        // Verify user data exists if contents type is specified
+        if (mContentsType != CONTENTS_TYPE_UNSPECIFIED && TextUtils.isEmpty(mUserData)) {
+            String error = "Contents type was specified but user data does not exist.";
+            loge(error);
+            Intent data = new Intent();
+            data.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE,
+                    SlicePurchaseController.FAILURE_CODE_NO_USER_DATA);
+            data.putExtra(SlicePurchaseController.EXTRA_FAILURE_REASON, error);
+            SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
+                    mIntent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
+            finishAndRemoveTask();
+            return;
+        }
+
         // Verify intent is valid
         if (!SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)) {
             loge("Not starting SlicePurchaseActivity with an invalid Intent: " + mIntent);
@@ -114,19 +147,15 @@
             return;
         }
 
-        // Create and configure WebView
-        setupWebView();
+        // Clear any cookies that might be persisted from previous sessions before loading WebView
+        CookieManager.getInstance().removeAllCookies(value -> setupWebView());
     }
 
-    protected void onPurchaseSuccessful(long duration) {
+    protected void onPurchaseSuccessful() {
         logd("onPurchaseSuccessful: Carrier website indicated successfully purchased premium "
-                + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability)
-                + (duration > 0 ? " for " + TimeUnit.MILLISECONDS.toMinutes(duration) + " minutes."
-                : "."));
-        Intent intent = new Intent();
-        intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_DURATION, duration);
-        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
-                mIntent, SlicePurchaseController.EXTRA_INTENT_SUCCESS, intent);
+                + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability));
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+                mIntent, SlicePurchaseController.EXTRA_INTENT_SUCCESS);
         finishAndRemoveTask();
     }
 
@@ -143,6 +172,14 @@
         finishAndRemoveTask();
     }
 
+    protected void onDismissFlow() {
+        logd("onDismissFlow: Dismiss flow called while purchasing premium capability "
+                + TelephonyManager.convertPremiumCapabilityToString(mCapability));
+        SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
+                mIntent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
+        finishAndRemoveTask();
+    }
+
     @Override
     public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
         // Pressing back in the WebView will go to the previous page instead of closing
@@ -173,24 +210,57 @@
     private void setupWebView() {
         // Create WebView
         mWebView = new WebView(this);
+        mWebView.setWebViewClient(new WebViewClient());
 
         // Enable JavaScript for the carrier purchase website to send results back to
         //  the slice purchase application.
         mWebView.getSettings().setJavaScriptEnabled(true);
         mWebView.addJavascriptInterface(
-                new SlicePurchaseWebInterface(this), "SlicePurchaseWebInterface");
+                new DataBoostWebServiceFlow(this), "DataBoostWebServiceFlow");
 
         // Display WebView
         setContentView(mWebView);
 
-        // Load the URL
-        String userData = mIntent.getStringExtra(SlicePurchaseController.EXTRA_USER_DATA);
-        if (TextUtils.isEmpty(userData)) {
-            logd("Starting WebView with url: " + mUrl.toString());
-            mWebView.loadUrl(mUrl.toString());
+        // Start the WebView
+        startWebView(mWebView, mUrl.toString(), mContentsType, mUserData);
+    }
+
+    /**
+     * Send the URL to the WebView as either a GET or POST request, based on the contents type:
+     * <ul>
+     *     <li>
+     *         CONTENTS_TYPE_UNSPECIFIED:
+     *         If the user data exists, append it to the purchase URL and load it as a GET request.
+     *         If the user data does not exist, load just the purchase URL as a GET request.
+     *     </li>
+     *     <li>
+     *         CONTENTS_TYPE_JSON or CONTENTS_TYPE_XML:
+     *         The user data must exist. Send the JSON or XML formatted user data in a POST request.
+     *         If the user data is encoded, it must be prefaced by {@code encodedValue=} and will be
+     *         encoded in Base64. Decode the user data and send it in the POST request.
+     *     </li>
+     * </ul>
+     * @param webView The WebView to start.
+     * @param url The URL to start the WebView with.
+     * @param contentsType The contents type of the userData.
+     * @param userData The user data to send with the GET or POST request, if it exists.
+     */
+    @VisibleForTesting
+    public static void startWebView(@NonNull WebView webView, @NonNull String url, int contentsType,
+            @Nullable String userData) {
+        if (contentsType == CONTENTS_TYPE_UNSPECIFIED) {
+            logd("Starting WebView GET with url: " + url);
+            webView.loadUrl(url);
         } else {
-            logd("Starting WebView with url: " + mUrl.toString() + ", userData=" + userData);
-            mWebView.postUrl(mUrl.toString(), userData.getBytes());
+            byte[] data = userData.getBytes();
+            String[] split = userData.split("encodedValue=");
+            if (split.length > 1) {
+                logd("Decoding encoded value: " + split[1]);
+                data = Base64.getDecoder().decode(split[1]);
+            }
+            logd("Starting WebView POST with url: " + url + ", contentsType: " + contentsType
+                    + ", data: " + new String(data));
+            webView.postUrl(url, data);
         }
     }
 
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index 23b9766..eccf604 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -173,7 +173,9 @@
         }
 
         String purchaseUrl = intent.getStringExtra(SlicePurchaseController.EXTRA_PURCHASE_URL);
-        if (getPurchaseUrl(purchaseUrl) == null) {
+        String userData = intent.getStringExtra(SlicePurchaseController.EXTRA_USER_DATA);
+        String contentsType = intent.getStringExtra(SlicePurchaseController.EXTRA_CONTENTS_TYPE);
+        if (getPurchaseUrl(purchaseUrl, userData, TextUtils.isEmpty(contentsType)) == null) {
             loge("isIntentValid: invalid purchase URL: " + purchaseUrl);
             return false;
         }
@@ -189,18 +191,47 @@
                 && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED)
                 && isPendingIntentValid(intent,
                         SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION)
+                && isPendingIntentValid(intent,
+                        SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED)
                 && isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_SUCCESS)
                 && isPendingIntentValid(intent,
                         SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
     }
 
     /**
+     * Get the {@link URL} from the given purchase URL String and user data, if it is valid.
+     *
+     * @param purchaseUrl The purchase URL String to use to create the URL.
+     * @param userData The user data parameter from the entitlement server.
+     * @param shouldAppendUserData If this is {@code true} and the {@code userData} exists,
+     *        the {@code userData} should be appended to the {@code purchaseUrl} to create the URL.
+     *        If this is false, only the {@code purchaseUrl} should be used and the {@code userData}
+     *        will be sent as data to the POST request instead.
+     * @return The URL from the given purchase URL and user data or {@code null} if it is invalid.
+     */
+    @Nullable public static URL getPurchaseUrl(@Nullable String purchaseUrl,
+            @Nullable String userData, boolean shouldAppendUserData) {
+        if (purchaseUrl == null) {
+            return null;
+        }
+        // Only append user data if it exists, otherwise just return the purchase URL
+        if (!shouldAppendUserData || TextUtils.isEmpty(userData)) {
+            return getPurchaseUrl(purchaseUrl);
+        }
+        URL url = getPurchaseUrl(purchaseUrl + "?" + userData);
+        if (url == null) {
+            url = getPurchaseUrl(purchaseUrl);
+        }
+        return url;
+    }
+
+    /**
      * Get the {@link URL} from the given purchase URL String, if it is valid.
      *
      * @param purchaseUrl The purchase URL String to use to create the URL.
      * @return The purchase URL from the given String or {@code null} if it is invalid.
      */
-    @Nullable public static URL getPurchaseUrl(@Nullable String purchaseUrl) {
+    @Nullable private static URL getPurchaseUrl(@Nullable String purchaseUrl) {
         if (!URLUtil.isValidUrl(purchaseUrl)) {
             return null;
         }
@@ -247,6 +278,8 @@
             case SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
             case SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION:
                 return "not default data subscription";
+            case SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED:
+                return "notifications disabled";
             case SlicePurchaseController.EXTRA_INTENT_SUCCESS: return "success";
             case SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN:
                 return "notification shown";
@@ -292,26 +325,45 @@
     }
 
     private void onDisplayPerformanceBoostNotification(@NonNull Context context,
-            @NonNull Intent intent, boolean repeat) {
-        if (!repeat && !isIntentValid(intent)) {
+            @NonNull Intent intent, boolean localeChanged) {
+        if (!localeChanged && !isIntentValid(intent)) {
             sendSlicePurchaseAppResponse(intent,
                     SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
             return;
         }
 
         Resources res = getResources(context);
-        NotificationChannel channel = new NotificationChannel(
-                PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID,
-                res.getString(R.string.performance_boost_notification_channel),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        // CarrierDefaultApp notifications are unblockable by default. Make this channel blockable
-        //  to allow users to disable notifications posted to this channel without affecting other
-        //  notifications in this application.
-        channel.setBlockable(true);
-        context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
+        NotificationManager notificationManager =
+                context.getSystemService(NotificationManager.class);
+        NotificationChannel channel = notificationManager.getNotificationChannel(
+                PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID);
+        if (channel == null) {
+            channel = new NotificationChannel(
+                    PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID,
+                    res.getString(R.string.performance_boost_notification_channel),
+                    NotificationManager.IMPORTANCE_DEFAULT);
+            // CarrierDefaultApp notifications are unblockable by default.
+            // Make this channel blockable to allow users to disable notifications posted to this
+            // channel without affecting other notifications in this application.
+            channel.setBlockable(true);
+            context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
+        } else if (localeChanged) {
+            // If the channel already exists but the locale has changed, update the channel name.
+            channel.setName(res.getString(R.string.performance_boost_notification_channel));
+        }
+
+        boolean channelNotificationsDisabled =
+                channel.getImportance() == NotificationManager.IMPORTANCE_NONE;
+        if (channelNotificationsDisabled || !notificationManager.areNotificationsEnabled()) {
+            // If notifications are disabled for the app or channel, fail the purchase request.
+            logd("Purchase request failed because notifications are disabled for the "
+                    + (channelNotificationsDisabled ? "channel." : "application."));
+            sendSlicePurchaseAppResponse(intent,
+                    SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED);
+            return;
+        }
 
         String carrier = intent.getStringExtra(SlicePurchaseController.EXTRA_CARRIER);
-
         Notification notification =
                 new Notification.Builder(context, PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID)
                         .setContentTitle(res.getString(
@@ -340,11 +392,12 @@
 
         int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
                 SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
-        logd((repeat ? "Update" : "Display") + " the performance boost notification for capability "
+        logd((localeChanged ? "Update" : "Display")
+                + " the performance boost notification for capability "
                 + TelephonyManager.convertPremiumCapabilityToString(capability));
         context.getSystemService(NotificationManager.class).notifyAsUser(
                 PERFORMANCE_BOOST_NOTIFICATION_TAG, capability, notification, UserHandle.ALL);
-        if (!repeat) {
+        if (!localeChanged) {
             sIntents.put(capability, intent);
             sendSlicePurchaseAppResponse(intent,
                     SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.java
deleted file mode 100644
index 8547898..0000000
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseWebInterface.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.carrierdefaultapp;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.telephony.TelephonyManager;
-import android.webkit.JavascriptInterface;
-
-import com.android.phone.slice.SlicePurchaseController;
-
-/**
- * Slice purchase web interface class allowing carrier websites to send responses back to the
- * slice purchase application using JavaScript.
- */
-public class SlicePurchaseWebInterface {
-    @NonNull SlicePurchaseActivity mActivity;
-
-    public SlicePurchaseWebInterface(@NonNull SlicePurchaseActivity activity) {
-        mActivity = activity;
-    }
-
-    /**
-     * Interface method allowing the carrier website to get the premium capability
-     * that was requested to purchase.
-     *
-     * This can be called using the JavaScript below:
-     * <script type="text/javascript">
-     *     function getRequestedCapability(duration) {
-     *         SlicePurchaseWebInterface.getRequestedCapability();
-     *     }
-     * </script>
-     */
-    @JavascriptInterface
-    @TelephonyManager.PremiumCapability public int getRequestedCapability() {
-        return mActivity.mCapability;
-    }
-
-    /**
-     * Interface method allowing the carrier website to notify the slice purchase application of
-     * a successful premium capability purchase and the duration for which the premium capability is
-     * purchased.
-     *
-     * This can be called using the JavaScript below:
-     * <script type="text/javascript">
-     *     function notifyPurchaseSuccessful(duration_ms_long = 0) {
-     *         SlicePurchaseWebInterface.notifyPurchaseSuccessful(duration_ms_long);
-     *     }
-     * </script>
-     *
-     * @param duration The duration for which the premium capability is purchased in milliseconds.
-     */
-    @JavascriptInterface
-    public void notifyPurchaseSuccessful(long duration) {
-        mActivity.onPurchaseSuccessful(duration);
-    }
-
-    /**
-     * Interface method allowing the carrier website to notify the slice purchase application of
-     * a failed premium capability purchase.
-     *
-     * This can be called using the JavaScript below:
-     * <script type="text/javascript">
-     *     function notifyPurchaseFailed(failure_code = 0, failure_reason = "unknown") {
-     *         SlicePurchaseWebInterface.notifyPurchaseFailed();
-     *     }
-     * </script>
-     *
-     * @param failureCode The failure code.
-     * @param failureReason If the failure code is
-     *                      {@link SlicePurchaseController#FAILURE_CODE_UNKNOWN},
-     *                      the human-readable reason for failure.
-     */
-    @JavascriptInterface
-    public void notifyPurchaseFailed(@SlicePurchaseController.FailureCode int failureCode,
-            @Nullable String failureReason) {
-        mActivity.onPurchaseFailed(failureCode, failureReason);
-    }
-}
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
index daf0b42..1ec180b 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseActivityTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.app.NotificationManager;
@@ -33,6 +34,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.ActivityUnitTestCase;
+import android.webkit.WebView;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -46,6 +48,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Base64;
+
 @RunWith(AndroidJUnit4.class)
 public class SlicePurchaseActivityTest extends ActivityUnitTestCase<SlicePurchaseActivity> {
     private static final String CARRIER = "Some Carrier";
@@ -53,10 +57,13 @@
     private static final int PHONE_ID = 0;
 
     @Mock PendingIntent mPendingIntent;
+    @Mock PendingIntent mSuccessfulIntent;
     @Mock PendingIntent mCanceledIntent;
+    @Mock PendingIntent mRequestFailedIntent;
     @Mock CarrierConfigManager mCarrierConfigManager;
     @Mock NotificationManager mNotificationManager;
     @Mock PersistableBundle mPersistableBundle;
+    @Mock WebView mWebView;
 
     private SlicePurchaseActivity mSlicePurchaseActivity;
     private Context mContext;
@@ -107,25 +114,28 @@
         doReturn(true).when(mCanceledIntent).isBroadcast();
         doReturn(mCanceledIntent).when(spiedIntent).getParcelableExtra(
                 eq(SlicePurchaseController.EXTRA_INTENT_CANCELED), eq(PendingIntent.class));
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mSuccessfulIntent).getCreatorPackage();
+        doReturn(true).when(mSuccessfulIntent).isBroadcast();
+        doReturn(mSuccessfulIntent).when(spiedIntent).getParcelableExtra(
+                eq(SlicePurchaseController.EXTRA_INTENT_SUCCESS), eq(PendingIntent.class));
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mRequestFailedIntent)
+                .getCreatorPackage();
+        doReturn(true).when(mRequestFailedIntent).isBroadcast();
+        doReturn(mRequestFailedIntent).when(spiedIntent).getParcelableExtra(
+                eq(SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED), eq(PendingIntent.class));
 
         mSlicePurchaseActivity = startActivity(spiedIntent, null, null);
     }
 
     @Test
     public void testOnPurchaseSuccessful() throws Exception {
-        int duration = 5 * 60 * 1000; // 5 minutes
-        int invalidDuration = -1;
-        mSlicePurchaseActivity.onPurchaseSuccessful(duration);
-        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        verify(mPendingIntent).send(eq(mContext), eq(0), intentCaptor.capture());
-        Intent intent = intentCaptor.getValue();
-        assertEquals(duration, intent.getLongExtra(
-                SlicePurchaseController.EXTRA_PURCHASE_DURATION, invalidDuration));
+        mSlicePurchaseActivity.onPurchaseSuccessful();
+        verify(mSuccessfulIntent).send();
     }
 
     @Test
     public void testOnPurchaseFailed() throws Exception {
-        int failureCode = SlicePurchaseController.FAILURE_CODE_SERVER_UNREACHABLE;
+        int failureCode = SlicePurchaseController.FAILURE_CODE_CARRIER_URL_UNAVAILABLE;
         String failureReason = "Server unreachable";
         mSlicePurchaseActivity.onPurchaseFailed(failureCode, failureReason);
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -142,4 +152,29 @@
         mSlicePurchaseActivity.onDestroy();
         verify(mCanceledIntent).send();
     }
+
+    @Test
+    public void testOnDismissFlow() throws Exception {
+        mSlicePurchaseActivity.onDismissFlow();
+        verify(mRequestFailedIntent).send();
+    }
+
+    @Test
+    public void testStartWebView() {
+        // unspecified contents type
+        SlicePurchaseActivity.startWebView(mWebView, URL, 0 /* CONTENTS_TYPE_UNSPECIFIED */, null);
+        verify(mWebView).loadUrl(eq(URL));
+
+        // specified contents type with user data
+        String userData = "userData";
+        byte[] userDataBytes = userData.getBytes();
+        SlicePurchaseActivity.startWebView(mWebView, URL, 1 /* CONTENTS_TYPE_JSON */, userData);
+        verify(mWebView).postUrl(eq(URL), eq(userDataBytes));
+
+        // specified contents type with encoded user data
+        byte[] encodedUserData = Base64.getEncoder().encode(userDataBytes);
+        userData = "encodedValue=" + new String(encodedUserData);
+        SlicePurchaseActivity.startWebView(mWebView, URL, 1 /* CONTENTS_TYPE_JSON */, userData);
+        verify(mWebView, times(2)).postUrl(eq(URL), eq(userDataBytes));
+    }
 }
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
index 952789c..3c8ef6e 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
@@ -32,6 +32,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -72,6 +73,7 @@
     @Mock PendingIntent mContentIntent1;
     @Mock PendingIntent mContentIntent2;
     @Mock PendingIntent mNotificationShownIntent;
+    @Mock PendingIntent mNotificationsDisabledIntent;
     @Mock Context mContext;
     @Mock Resources mResources;
     @Mock Configuration mConfiguration;
@@ -90,6 +92,7 @@
         doReturn("").when(mResources).getString(anyInt());
         doReturn(mNotificationManager).when(mContext)
                 .getSystemService(eq(NotificationManager.class));
+        doReturn(true).when(mNotificationManager).areNotificationsEnabled();
         doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
         doReturn(mPackageManager).when(mContext).getPackageManager();
         doReturn(mSpiedResources).when(mContext).getResources();
@@ -160,14 +163,35 @@
                 "file:///android_asset/slice_store_test.html"
         };
 
+        // test invalid URLs
         for (String url : invalidUrls) {
-            URL purchaseUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url);
+            URL purchaseUrl = SlicePurchaseBroadcastReceiver.getPurchaseUrl(url, null, false);
             assertNull(purchaseUrl);
         }
 
+        // test asset URL
         assertEquals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE,
                 SlicePurchaseBroadcastReceiver.getPurchaseUrl(
-                        SlicePurchaseController.SLICE_PURCHASE_TEST_FILE).toString());
+                        SlicePurchaseController.SLICE_PURCHASE_TEST_FILE, null, false).toString());
+
+        // test normal URL
+        String validUrl = "http://www.google.com";
+        assertEquals(validUrl,
+                SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, null, false).toString());
+
+        // test normal URL with user data but no append
+        String userData = "encryptedUserData=data";
+        assertEquals(validUrl,
+                SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, userData, false)
+                        .toString());
+
+        // test normal URL with user data and append
+        assertEquals(validUrl + "?" + userData,
+                SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, userData, true).toString());
+
+        // test normal URL without user data and append
+        assertEquals(validUrl,
+                SlicePurchaseBroadcastReceiver.getPurchaseUrl(validUrl, null, true).toString());
     }
 
     @Test
@@ -200,12 +224,10 @@
         doReturn(true).when(mPendingIntent).isBroadcast();
         doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
                 anyString(), eq(PendingIntent.class));
-        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mNotificationShownIntent)
-                .getCreatorPackage();
-        doReturn(true).when(mNotificationShownIntent).isBroadcast();
-        doReturn(mNotificationShownIntent).when(mIntent).getParcelableExtra(
-                eq(SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN),
-                eq(PendingIntent.class));
+        createValidPendingIntent(mNotificationShownIntent,
+                SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
+        createValidPendingIntent(mNotificationsDisabledIntent,
+                SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED);
 
         // spy notification intents to prevent PendingIntent issues
         doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
@@ -232,6 +254,12 @@
         mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
     }
 
+    private void createValidPendingIntent(@NonNull PendingIntent intent, @NonNull String extra) {
+        doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(intent).getCreatorPackage();
+        doReturn(true).when(intent).isBroadcast();
+        doReturn(intent).when(mIntent).getParcelableExtra(eq(extra), eq(PendingIntent.class));
+    }
+
     @Test
     public void testNotificationCanceled() {
         // send ACTION_NOTIFICATION_CANCELED
@@ -314,4 +342,22 @@
         clearInvocations(mConfiguration);
         return captor.getValue();
     }
+
+    @Test
+    public void testNotificationsDisabled() throws Exception {
+        doReturn(false).when(mNotificationManager).areNotificationsEnabled();
+
+        displayPerformanceBoostNotification();
+
+        // verify notification was not shown
+        verify(mNotificationManager, never()).notifyAsUser(
+                eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
+                eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+                any(),
+                eq(UserHandle.ALL));
+        verify(mNotificationShownIntent, never()).send();
+
+        // verify SlicePurchaseController was notified that notifications are disabled
+        verify(mNotificationsDisabledIntent).send();
+    }
 }
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index aae30df..a0b3469 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -144,7 +144,7 @@
                         android:visibility="gone"
                         android:duplicateParentState="true"
                         android:clickable="false"
-                        android:text="@string/consent_no" />
+                        android:text="@string/consent_cancel" />
 
                 </LinearLayout>
 
diff --git a/packages/CompanionDeviceManager/res/values-af/strings.xml b/packages/CompanionDeviceManager/res/values-af/strings.xml
index 2c0af5d..6c54f70 100644
--- a/packages/CompanionDeviceManager/res/values-af/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-af/strings.xml
@@ -17,15 +17,14 @@
 <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">"Metgeseltoestel-bestuurder"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Gee &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang tot &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Gee die app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang tot &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"horlosie"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Kies \'n <xliff:g id="PROFILE_NAME">%1$s</xliff:g> om deur &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; bestuur te word"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Hierdie app is nodig om jou <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te bestuur. <xliff:g id="APP_NAME">%2$s</xliff:g> sal toegelaat word om inligting te sinkroniseer, soos die naam van iemand wat bel, interaksie met jou kennisgewings te hê, en sal toegang tot jou Foon-, SMS-, Kontakte-, Kalender-, Oproeprekords-, en Toestelle in die Omtrek-toestemmings hê."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Hierdie app sal toegelaat word om inligting te sinkroniseer, soos die naam van iemand wat bel, en sal toegang tot hierdie toestemmings op jou <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> hê"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Kies ’n toestel wat &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; moet bestuur"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Kies ’n <xliff:g id="PROFILE_NAME">%1$s</xliff:g> om op te stel"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Hierdie app sal toegelaat word om inligting te sinkroniseer, soos die naam van iemand wat bel, en sal toegang tot hierdie toestemmings op jou <xliff:g id="DEVICE_NAME">%1$s</xliff:g> hê"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Laat &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toe om &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; te bestuur?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"bril"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Hierdie app is nodig om <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te bestuur. <xliff:g id="APP_NAME">%2$s</xliff:g> sal toegelaat word om interaksie met jou kennisgewings te hê en sal toegang tot jou Foon-, SMS-, Kontakte-, Mikrofoon-, en Toestelle in die Omtrek-toestemmings hê."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Hierdie app sal toegang tot hierdie toestemmings op jou <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> hê"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"toestel"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Hierdie app sal toegang tot hierdie toestemmings op jou <xliff:g id="DEVICE_NAME">%1$s</xliff:g> hê"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Gee &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang tot hierdie inligting op jou foon"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Oorkruistoestel-dienste"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> versoek tans namens jou <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> toestemming om apps tussen jou toestelle te stroom"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Laat &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; toe om hierdie handeling uit te voer?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> versoek tans namens jou <xliff:g id="DEVICE_NAME">%2$s</xliff:g> toestemming om apps en ander stelselkenmerke na toestelle in die omtrek te stroom"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"toestel"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Hierdie app sal inligting kan sinkroniseer, soos die naam van iemand wat bel, tussen jou foon en <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Hierdie app sal inligting kan sinkroniseer, soos die naam van iemand wat bel, tussen jou foon en die gekose toestel"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Laat toe"</string>
     <string name="consent_no" msgid="2640796915611404382">"Moenie toelaat nie"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Kanselleer"</string>
     <string name="consent_back" msgid="2560683030046918882">"Terug"</string>
     <string name="permission_expand" msgid="893185038020887411">"Vou <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> uit"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Vou <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> in"</string>
diff --git a/packages/CompanionDeviceManager/res/values-am/strings.xml b/packages/CompanionDeviceManager/res/values-am/strings.xml
index b8964b5..d9ddf71 100644
--- a/packages/CompanionDeviceManager/res/values-am/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-am/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<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;ን እንዲደርስ ይፈቀድለት?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"መተግበሪያው &lt;strong&gt;<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;ን እንዲደርስ ይፈቀድለት?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ሰዓት"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"በ&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; የሚተዳደር <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ይምረጡ"</string>
-    <string name="summary_watch" msgid="898569637110705523">"የእርስዎን <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ለማስተዳደር ይህ መተግበሪያ ያስፈልጋል። <xliff:g id="APP_NAME">%2$s</xliff:g> እንደ የሚደውል ሰው ስም፣ ከማሳወቂያዎችዎ ጋር መስተጋብር እንዲፈጥር እና የእርስዎን ስልክ፣ ኤስኤምኤስ፣ ዕውቅያዎች፣ የቀን መቁጠሪያ፣ የጥሪ ምዝግብ ማስታወሻዎች እና በአቅራቢያ ያሉ መሣሪያዎችን መድረስ ያሉ መረጃዎችን እንዲያሰምር ይፈቀድለታል።"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"ይህ መተግበሪያ እንደ የሚደውል ሰው ስም ያለ መረጃን እንዲያሰምር እና እነዚህን ፈቃዶች በእርስዎ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> ላይ እንዲደርስ ይፈቀድለታል"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; የሚያስተዳድረው መሣሪያ ይምረጡ"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"የሚያዋቅሩት <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ይምረጡ"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"ይህ መተግበሪያ እንደ የሚደውል ሰው ስም ያለ መረጃን እንዲያሰምር እና እነዚህን ፈቃዶች በእርስዎ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ላይ እንዲደርስ ይፈቀድለታል"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<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;ን እንዲያስተዳድር ይፈቅዳሉ?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"መነጽሮች"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"ይህ መተግበሪያ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ን ለማስተዳደር ያስፈልጋል። <xliff:g id="APP_NAME">%2$s</xliff:g> ከማሳወቂያዎችዎ ጋር መስተጋብር እንዲፈጥር እና የእርስዎን ስልክ፣ ኤስኤምኤስ፣ ዕውቂያዎች፣ ማይክሮፎን እና በአቅራቢያ ያሉ መሣሪያዎች ፈቃዶችን እንዲደርስ ይፈቀድለታል።"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"ይህ መተግበሪያ በእርስዎ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> ላይ እነዚህን ፈቃዶች እንዲደርስ ይፈቀድለታል"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"መሣሪያ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"ይህ መተግበሪያ በእርስዎ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ላይ እነዚህን ፈቃዶች እንዲደርስ ይፈቀድለታል"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ይህን መረጃ ከስልክዎ እንዲደርስበት ይፍቀዱለት"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"መሣሪያ ተሻጋሪ አገልግሎቶች"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> በእርስዎ መሣሪያዎች መካከል መተግበሪያዎችን በዥረት ለመልቀቅ የእርስዎን <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ወክሎ ፈቃድ እየጠየቀ ነው"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ይህን እርምጃ እንዲወስድ ፈቃድ ይሰጠው?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> የእርስዎን <xliff:g id="DEVICE_NAME">%2$s</xliff:g> በመወከል በአቅራቢያ ላሉ መሣሪያዎች መተግበሪያዎች እና ሌሎች የስርዓት ባህሪያትን በዥረት ለመልቀቅ ፈቃድ እየጠየቀ ነው"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"መሣሪያ"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"ይህ መተግበሪያ እንደ የሚደውል ሰው ስም ያለ መረጃን በስልክዎ እና <xliff:g id="DEVICE_NAME">%1$s</xliff:g> መካከል ማስመር ይችላል"</string>
     <string name="summary_generic" msgid="1761976003668044801">"ይህ መተግበሪያ እንደ የሚደውል ሰው ስም ያለ መረጃን በስልክዎ እና በተመረጠው መሣሪያ መካከል ማስመር ይችላል"</string>
     <string name="consent_yes" msgid="8344487259618762872">"ፍቀድ"</string>
     <string name="consent_no" msgid="2640796915611404382">"አትፍቀድ"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ይቅር"</string>
     <string name="consent_back" msgid="2560683030046918882">"ተመለስ"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ን ዘርጋ"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ን ሰብስብ"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ar/strings.xml b/packages/CompanionDeviceManager/res/values-ar/strings.xml
index 3a4a211..8a6f227 100644
--- a/packages/CompanionDeviceManager/res/values-ar/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ar/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"‏هل تريد السماح لتطبيق &lt;strong&gt;<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;؟"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"‏هل تريد السماح لـ &lt;strong&gt;<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;؟"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"الساعة"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"‏اختَر <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ليديرها تطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"هذا التطبيق مطلوب لإدارة \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". سيتم السماح لتطبيق \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" بمزامنة المعلومات، مثلاً اسم المتصل، والتفاعل مع الإشعارات والوصول إلى هاتفك، والرسائل القصيرة، وجهات الاتصال، والتقويم، وسجلات المكالمات وأذونات الأجهزة المجاورة."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"سيتم السماح لهذا التطبيق بمزامنة المعلومات، مثلاً اسم المتصل، والوصول إلى الأذونات التالية على <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>."</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"‏اختيار جهاز ليديره تطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"اختيار \"<xliff:g id="PROFILE_NAME">%1$s</xliff:g>\" لإعداده"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"سيتم السماح لهذا التطبيق بمزامنة المعلومات، مثلاً اسم المتصل، والوصول إلى هذه الأذونات على \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\"."</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"‏السماح لتطبيق &lt;strong&gt;<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;"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"النظارة"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"يجب توفّر هذا التطبيق لإدارة \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". سيتم السماح لتطبيق \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" بالتفاعل مع الإشعارات والوصول إلى أذونات الهاتف والرسائل القصيرة وجهات الاتصال والميكروفون والأجهزة المجاورة."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"سيتم السماح لهذا التطبيق بالوصول إلى الأذونات التالية على <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>."</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"جهاز"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"سيتم السماح لهذا التطبيق بالوصول إلى هذه الأذونات على \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\"."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"‏السماح لتطبيق &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; بالوصول إلى هذه المعلومات من هاتفك"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"الخدمات التي تعمل بين الأجهزة"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"يطلب تطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" الحصول على إذن نيابةً عن \"<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>\" لبثّ محتوى التطبيقات بين أجهزتك."</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"‏هل تريد السماح للتطبيق &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; باتّخاذ هذا الإجراء؟"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"يطلب \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" الحصول على إذن نيابةً عن \"<xliff:g id="DEVICE_NAME">%2$s</xliff:g>\" لبثّ التطبيقات وميزات النظام الأخرى إلى أجهزتك المجاورة."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"جهاز"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"سيتمكّن هذا التطبيق من مزامنة المعلومات، مثل اسم المتصل، بين هاتفك و\"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\"."</string>
     <string name="summary_generic" msgid="1761976003668044801">"سيتمكّن هذا التطبيق من مزامنة المعلومات، مثل اسم المتصل، بين هاتفك والجهاز المحدّد."</string>
     <string name="consent_yes" msgid="8344487259618762872">"السماح"</string>
     <string name="consent_no" msgid="2640796915611404382">"عدم السماح"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"إلغاء"</string>
     <string name="consent_back" msgid="2560683030046918882">"رجوع"</string>
     <string name="permission_expand" msgid="893185038020887411">"توسيع <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"تصغير <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-as/strings.xml b/packages/CompanionDeviceManager/res/values-as/strings.xml
index 1ac0ed3..28a06e6 100644
--- a/packages/CompanionDeviceManager/res/values-as/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-as/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<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; এক্সেছ কৰিবলৈ দিবনে?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<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; এক্সেছ কৰিবলৈ দিবনে?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ঘড়ী"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;এ পৰিচালনা কৰিব লগা এটা <xliff:g id="PROFILE_NAME">%1$s</xliff:g> বাছনি কৰক"</string>
-    <string name="summary_watch" msgid="898569637110705523">"আপোনাৰ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> পৰিচালনা কৰিবলৈ এই এপ্‌টোৰ আৱশ্যক। <xliff:g id="APP_NAME">%2$s</xliff:g>ক কল কৰোঁতাৰ নামৰ দৰে তথ্য ছিংক কৰিবলৈ, আপোনাৰ জাননীৰ সৈতে ভাব-বিনিময় কৰিবলৈ আৰু আপোনাৰ ফ’ন, এছএমএছ, সম্পৰ্ক, কেলেণ্ডাৰ, কল লগ আৰু নিকটৱৰ্তী ডিভাইচৰ অনুমতি এক্সেছ কৰিবলৈ দিয়া হ’ব।"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"এই এপ্‌টোক ফ’ন কৰা লোকৰ নামৰ দৰে তথ্য ছিংক কৰিবলৈ আৰু আপোনাৰ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>ত এই অনুমতিসমূহ এক্সেছ কৰিবলৈ অনুমতি দিয়া হ’ব"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;এ পৰিচালনা কৰিবলগীয়া এটা ডিভাইচ বাছনি কৰক"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"ছেট আপ কৰিবলৈ এটা <xliff:g id="PROFILE_NAME">%1$s</xliff:g> বাছনি কৰক"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"এই এপ্‌টোক ফ’ন কৰা লোকৰ নামৰ দৰে তথ্য ছিংক কৰিবলৈ আৰু আপোনাৰ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ত এই অনুমতিসমূহ এক্সেছ কৰিবলৈ অনুমতি দিয়া হ’ব"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<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; পৰিচালনা কৰিবলৈ দিবনে?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"চছ্‌মা"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> পৰিচালনা কৰিবলৈ এই এপ্‌টোৰ আৱশ্যক। <xliff:g id="APP_NAME">%2$s</xliff:g>ক আপোনাৰ অনুমতিসমূহৰ সৈতে ভাব-বিনিময় কৰিবলৈ আৰু আপোনাৰ ফ’ন, এছএমএছ, সম্পৰ্ক, মাইক্ৰ’ফ’ন আৰু নিকটৱৰ্তী ডিভাইচৰ অনুমতিসমূহ এক্সেছ কৰিবলৈ দিয়া হ’ব।"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"এই এপ্‌টোক আপোনাৰ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>ত এই অনুমতিসমূহ এক্সেছ কৰিবলৈ অনুমতি দিয়া হ’ব"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ডিভাইচ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"এই এপ্‌টোক আপোনাৰ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ত এই অনুমতিসমূহ এক্সেছ কৰিবলৈ অনুমতি দিয়া হ’ব"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ক আপোনাৰ ফ’নৰ পৰা এই তথ্যখিনি এক্সেছ কৰাৰ অনুমতি দিয়ক"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ক্ৰছ-ডিভাইচ সেৱাসমূহ"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ আপোনাৰ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>ৰ হৈ আপোনাৰ ডিভাইচসমূহৰ মাজত এপ্‌ ষ্ট্ৰীম কৰাৰ বাবে অনুৰোধ জনাইছে"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;ক এই কাৰ্যটো সম্পাদন কৰিবলৈ দিবনে?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ আপোনাৰ <xliff:g id="DEVICE_NAME">%2$s</xliff:g>ৰ হৈ নিকটৱৰ্তী ডিভাইচত এপ্‌ আৰু ছিষ্টেমৰ অন্য সুবিধাসমূহ ষ্ট্ৰীম কৰাৰ অনুমতি দিবলৈ অনুৰোধ জনাইছে"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ডিভাইচ"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"এই এপ্‌টোৱে আপোনাৰ ফ’ন আৰু বাছনি কৰা <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ৰ মাজত কল কৰোঁতাৰ নামৰ দৰে তথ্য ছিংক কৰিব পাৰিব"</string>
     <string name="summary_generic" msgid="1761976003668044801">"এই এপ্‌টোৱে আপোনাৰ ফ’ন আৰু বাছনি কৰা ডিভাইচটোৰ মাজত কল কৰোঁতাৰ নামৰ দৰে তথ্য ছিংক কৰিব পাৰিব"</string>
     <string name="consent_yes" msgid="8344487259618762872">"অনুমতি দিয়ক"</string>
     <string name="consent_no" msgid="2640796915611404382">"অনুমতি নিদিব"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"বাতিল কৰক"</string>
     <string name="consent_back" msgid="2560683030046918882">"উভতি যাওক"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> বিস্তাৰ কৰক"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> সংকোচন কৰক"</string>
diff --git a/packages/CompanionDeviceManager/res/values-az/strings.xml b/packages/CompanionDeviceManager/res/values-az/strings.xml
index 79a045d..18ac2ae 100644
--- a/packages/CompanionDeviceManager/res/values-az/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-az/strings.xml
@@ -17,15 +17,14 @@
 <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">"Kompanyon Cihaz Meneceri"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tətbiqinə &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazına daxil olmaq icazəsi verilsin?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tətbiqinə &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazına daxil olmaq icazəsi verilsin?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"izləyin"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; tərəfindən idarə ediləcək <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Tətbiq <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazını idarə etmək üçün lazımdır. <xliff:g id="APP_NAME">%2$s</xliff:g> zəng edənin adı kimi məlumatları sinxronlaşdıracaq, bildirişlərə giriş edəcək, habelə Telefon, SMS, Kontaktlar, Təqvim, Zəng qeydləri və Yaxınlıqdakı cihazlar üzrə icazələrə daxil olacaq."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Bu tətbiq zəng edənin adı kimi məlumatları sinxronlaşdıra, <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> bu icazələrə daxil ola biləcək"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tərəfindən idarə ediləcək cihaz seçin"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Ayarlamaq üçün <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Bu tətbiq zəng edənin adı kimi məlumatları sinxronlaşdıra, <xliff:g id="DEVICE_NAME">%1$s</xliff:g> bu icazələrə daxil ola biləcək"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tətbiqinə &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazını idarə etmək icazəsi verilsin?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"eynək"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Bu tətbiq <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazını idarə etmək üçün lazımdır. <xliff:g id="APP_NAME">%2$s</xliff:g> bildirişlərə, Telefon, SMS, Kontaktlar, Mikrofon və Yaxınlıqdakı cihazlar icazələrinə giriş əldə edəcək."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Bu tətbiq <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> bu icazələrə daxil ola biləcək"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"cihazda"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Bu tətbiq <xliff:g id="DEVICE_NAME">%1$s</xliff:g> bu icazələrə daxil ola biləcək"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tətbiqinə telefonunuzdan bu məlumata giriş icazəsi verin"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cihazlararası xidmətlər"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> adından cihazlar arasında tətbiqləri yayımlamaq icazəsi istəyir"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; cihazına bu əməliyyatı yerinə yetirmək icazəsi verilsin?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DEVICE_NAME">%2$s</xliff:g> adından tətbiq və digər sistem funksiyalarını yaxınlıqdakı cihazlara yayımlamaq icazəsi sitəyir"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"cihaz"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Tətbiq zəng edənin adı kimi məlumatları telefon ilə <xliff:g id="DEVICE_NAME">%1$s</xliff:g> arasında sinxronlaşdıracaq"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Tətbiq zəng edənin adı kimi məlumatları telefon ilə seçilmiş cihaz arasında sinxronlaşdıracaq"</string>
     <string name="consent_yes" msgid="8344487259618762872">"İcazə verin"</string>
     <string name="consent_no" msgid="2640796915611404382">"İcazə verməyin"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Ləğv edin"</string>
     <string name="consent_back" msgid="2560683030046918882">"Geriyə"</string>
     <string name="permission_expand" msgid="893185038020887411">"Genişləndirin: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Yığcamlaşdırın: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
index bb16290..b37ab4b9 100644
--- a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
@@ -17,15 +17,14 @@
 <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">"Menadžer pridruženog uređaja"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Dozvolite da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pristupa uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Dozvolite da aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pristupa uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"sat"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Odaberite <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Ova aplikacija je potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> će dobiti dozvolu za sinhronizovanje informacija, poput osobe koja upućuje poziv, za interakciju sa obaveštenjima i pristup dozvolama za telefon, SMS, kontakte, kalendar, evidencije poziva i uređaje u blizini."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Ovoj aplikaciji će biti dozvoljeno da sinhronizuje podatke, poput imena osobe koja upućuje poziv, i pristupa tim dozvolama na vašem uređaju (<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>)"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Odaberite uređaj kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Odaberite profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> koji želite da podesite"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Ovoj aplikaciji će biti dozvoljeno da sinhronizuje podatke, poput imena osobe koja upućuje poziv, i pristupa tim dozvolama na vašem uređaju (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Želite li da dozvolite da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; upravlja uređajem &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"naočare"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Ova aplikacija je potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> će dobiti dozvolu za interakciju sa obaveštenjima i pristup dozvolama za telefon, SMS, kontakte, mikrofon i uređaje u blizini."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Ovoj aplikaciji će biti dozvoljeno da pristupa ovim dozvolama na vašem uređaju (<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>)"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"uređaj"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Ovoj aplikaciji će biti dozvoljeno da pristupa ovim dozvolama na vašem uređaju (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Dozvolite da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pristupa ovim informacijama sa telefona"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Usluge na više uređaja"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> zahteva dozvolu u ime uređaja <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> za strimovanje aplikacija između uređaja"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Želite li da dozvolite da &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; obavi ovu radnju?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahteva dozvolu u ime uređaja <xliff:g id="DEVICE_NAME">%2$s</xliff:g> da strimuje aplikacije i druge sistemske funkcije na uređaje u blizini"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Ova aplikacija će moći da sinhronizuje podatke, poput imena osobe koja upućuje poziv, između telefona i uređaja <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Ova aplikacija će moći da sinhronizuje podatke, poput imena osobe koja upućuje poziv, između telefona i odabranog uređaja"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Dozvoli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne dozvoli"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Otkaži"</string>
     <string name="consent_back" msgid="2560683030046918882">"Nazad"</string>
     <string name="permission_expand" msgid="893185038020887411">"Proširi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Skupi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-be/strings.xml b/packages/CompanionDeviceManager/res/values-be/strings.xml
index 9c12f5a..41889fd 100644
--- a/packages/CompanionDeviceManager/res/values-be/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-be/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"Дазволіць праграме &lt;strong&gt;<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;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Дазволіць праграме &lt;strong&gt;<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;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"гадзіннік"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Выберыце прыладу (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), якой будзе кіраваць праграма &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Гэта праграма неабходная для кіравання прыладай \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". <xliff:g id="APP_NAME">%2$s</xliff:g> зможа сінхранізаваць інфармацыю (напрыклад, імя таго, хто звоніць), узаемадзейнічаць з вашымі апавяшчэннямі, а таксама атрымае доступ да тэлефона, SMS, кантактаў, календара, журналаў выклікаў і прылад паблізу."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Гэта праграма зможа сінхранізаваць інфармацыю (напрыклад, імя таго, хто звоніць) на вашай прыладзе \"<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>\" і атрымае наступныя дазволы"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Выберыце прыладу (<xliff:g id="APP_NAME">%1$s</xliff:g>), якой будзе кіраваць праграма &lt;strong&gt;&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Выберыце імя <xliff:g id="PROFILE_NAME">%1$s</xliff:g> для наладжвання"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Гэтая праграма зможа сінхранізаваць інфармацыю (напрыклад, імя таго, хто звоніць) на вашай прыладзе \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\" і атрымае наступныя дазволы"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Дазволіць праграме &lt;strong&gt;<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;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"акуляры"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Гэта праграма неабходная для кіравання прыладай \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". <xliff:g id="APP_NAME">%2$s</xliff:g> зможа ўзаемадзейнічаць з вашымі апавяшчэннямі і атрымае доступ да тэлефона, SMS, кантактаў, мікрафона і прылад паблізу."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Гэта праграма будзе мець на вашай прыладзе \"<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>\" наступныя дазволы"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"прылада"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Гэтая праграма будзе мець на вашай прыладзе \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\" наступныя дазволы"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Дазвольце праграме &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; мець доступ да гэтай інфармацыі з вашага тэлефона"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Сэрвісы для некалькіх прылад"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>\" на трансляцыю праграм паміж вашымі прыладамі"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Дазволіць прыладзе &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; выканаць гэта дзеянне?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запытвае дазвол ад імя вашай прылады \"<xliff:g id="DEVICE_NAME">%2$s</xliff:g>\" на перадачу плынню змесціва праграм і іншых функцый сістэмы на прылады паблізу"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"прылада"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Гэта праграма зможа сінхранізаваць інфармацыю (напрыклад, імя таго, хто звоніць) паміж тэлефонам і прыладай \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\""</string>
     <string name="summary_generic" msgid="1761976003668044801">"Гэта праграма зможа сінхранізаваць інфармацыю (напрыклад, імя таго, хто звоніць) паміж тэлефонам і выбранай прыладай"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Дазволіць"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дазваляць"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Скасаваць"</string>
     <string name="consent_back" msgid="2560683030046918882">"Назад"</string>
     <string name="permission_expand" msgid="893185038020887411">"Разгарнуць <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Згарнуць <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-bg/strings.xml b/packages/CompanionDeviceManager/res/values-bg/strings.xml
index 4df4177..bb53c5c 100644
--- a/packages/CompanionDeviceManager/res/values-bg/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bg/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Да се разреши ли на &lt;strong&gt;<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;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Да се разреши ли на приложението &lt;strong&gt;<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;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"часовник"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Изберете <xliff:g id="PROFILE_NAME">%1$s</xliff:g> за управление от &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Това приложение е необходимо за управление на <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ще получи право да синхронизира различна информация, като например името на обаждащия се, да взаимодейства с известията ви и достъп до разрешенията за телефона, SMS съобщенията, контактите, календара, списъците с обажданията и устройствата в близост."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Това приложение ще получи право да синхронизира различна информация, като например името на обаждащия се, и достъп до следните разрешения за вашия <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Изберете устройство, което да се управлява от &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Изберете <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, за да го настроите"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Това приложение ще получи право да синхронизира различна информация, като например името на обаждащия се, и достъп до следните разрешения за вашия <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Разрешавате ли на &lt;strong&gt;<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;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"очилата"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Това приложение е необходимо за управление на <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Приложението <xliff:g id="APP_NAME">%2$s</xliff:g> ще получи право да взаимодейства с известията ви, както и достъп до разрешенията за телефона, SMS съобщенията, контактите, микрофона и устройствата в близост."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Това приложение ще има достъп до следните разрешения за вашия <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>:"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"устройство"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Това приложение ще има достъп до следните разрешения за вашето <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Разрешете на &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да осъществява достъп до тази информация от телефона ви"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Услуги за различни устройства"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> иска разрешение от името на <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> да предава поточно приложения между устройствата ви"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Разрешавате ли на &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; да предприема това действие?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> иска разрешение от името на <xliff:g id="DEVICE_NAME">%2$s</xliff:g> да предава поточно приложения и други системни функции към устройства в близост"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"устройство"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Това приложение ще може да синхронизира различна информация, като например името на обаждащия се, между телефона ви и <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Това приложение ще може да синхронизира различна информация, като например името на обаждащия се, между телефона ви и избраното устройство"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Разрешаване"</string>
     <string name="consent_no" msgid="2640796915611404382">"Забраняване"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Отказ"</string>
     <string name="consent_back" msgid="2560683030046918882">"Назад"</string>
     <string name="permission_expand" msgid="893185038020887411">"Разгъване на <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Свиване на <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-bn/strings.xml b/packages/CompanionDeviceManager/res/values-bn/strings.xml
index 735b620..bc0af0b 100644
--- a/packages/CompanionDeviceManager/res/values-bn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bn/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<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; অ্যাক্সেস করার অনুমতি দেবেন?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; অ্যাক্সেস করার জন্য &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; অ্যাপকে কি অনুমতি দিতে চান?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ঘড়ি"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> বেছে নিন যেটি &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ম্যানেজ করবে"</string>
-    <string name="summary_watch" msgid="898569637110705523">"আপনার <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ম্যানেজ করতে এই অ্যাপটি প্রয়োজন। <xliff:g id="APP_NAME">%2$s</xliff:g> অ্যাপকে কলারের নাম ও আপনার বিজ্ঞপ্তির সাথে ইন্টার‌্যাক্ট করা সংক্রান্ত তথ্য সিঙ্কের অনুমতি দেওয়া হবে এবং আপনার ফোন, এসএমএস, পরিচিতি, ক্যালেন্ডার, কল লগ এবং আশেপাশের ডিভাইস ব্যবহার করার অনুমতির মতো তথ্যে অ্যাক্সেস দেওয়া হবে।"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"এই অ্যাপকে, কল করছেন এমন কোনও ব্যক্তির নামের মতো তথ্য সিঙ্ক এবং আপনার <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>-এ এইসব অনুমতি অ্যাক্সেস করতে দেওয়া হবে"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ম্যানেজ করা যাবে এমন একটি ডিভাইস বেছে নিন"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"সেট-আপ করতে কোনও <xliff:g id="PROFILE_NAME">%1$s</xliff:g> বেছে নিন"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"এই অ্যাপকে, কল করছেন এমন কোনও ব্যক্তির নামের মতো তথ্য সিঙ্ক করতে এবং আপনার <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-এ এইসব অনুমতি অ্যাক্সেস করতে দেওয়া হবে"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"আপনি কি &lt;strong&gt;<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;-কে অনুমতি দেবেন?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"চশমা"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ম্যানেজ করতে এই অ্যাপ দরকার। <xliff:g id="APP_NAME">%2$s</xliff:g>-কে আপনার বিজ্ঞপ্তির সাথে ইন্টার‌্যাক্ট করার এবং ফোন, এসএমএস, পরিচিতি, মাইক্রোফোন ও আশেপাশের ডিভাইসের অনুমতি অ্যাক্সেস করতে দেওয়া হবে।"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"এই অ্যাপ আপনার <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>-এ এইসব অনুমতি অ্যাক্সেস করতে পারবে"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ডিভাইস"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"এই অ্যাপ আপনার <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-এ এইসব অনুমতি অ্যাক্সেস করতে পারবে"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"আপনার ফোন থেকে &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; অ্যাপকে এই তথ্য অ্যাক্সেস করার অনুমতি দিন"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ক্রস-ডিভাইস পরিষেবা"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"আপনার ডিভাইসগুলির মধ্যে অ্যাপ স্ট্রিম করার জন্য <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>-এর হয়ে অনুমতি চাইছে"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;-কে এই কাজটি করতে দেবেন?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"আশেপাশের ডিভাইসে অ্যাপ ও অন্যান্য সিস্টেম ফিচার স্ট্রিম করার জন্য আপনার <xliff:g id="DEVICE_NAME">%2$s</xliff:g>-এর হয়ে <xliff:g id="APP_NAME">%1$s</xliff:g> অনুমতি চেয়ে অনুরোধ করছে"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ডিভাইস"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"এই অ্যাপ, আপনার ফোন এবং <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ডিভাইসের মধ্যে তথ্য সিঙ্ক করতে পারবে, যেমন কোনও কলারের নাম"</string>
     <string name="summary_generic" msgid="1761976003668044801">"এই অ্যাপ, আপনার ফোন এবং বেছে নেওয়া ডিভাইসের মধ্যে তথ্য সিঙ্ক করতে পারবে, যেমন কোনও কলারের নাম"</string>
     <string name="consent_yes" msgid="8344487259618762872">"অনুমতি দিন"</string>
     <string name="consent_no" msgid="2640796915611404382">"অনুমতি দেবেন না"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"বাতিল করুন"</string>
     <string name="consent_back" msgid="2560683030046918882">"ফিরুন"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> বড় করুন"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> আড়াল করুন"</string>
diff --git a/packages/CompanionDeviceManager/res/values-bs/strings.xml b/packages/CompanionDeviceManager/res/values-bs/strings.xml
index 2de0838..538ee35 100644
--- a/packages/CompanionDeviceManager/res/values-bs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bs/strings.xml
@@ -17,15 +17,14 @@
 <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">"Prateći upravitelj uređaja"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Dozvoliti aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Dozvoliti aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"sat"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Odaberite profil \"<xliff:g id="PROFILE_NAME">%1$s</xliff:g>\" kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Ova aplikacija je potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikaciji <xliff:g id="APP_NAME">%2$s</xliff:g> će biti dozvoljeni sinhroniziranje informacija, kao što je ime osobe koja upućuje poziv, interakcija s obavještenjima i pristup odobrenjima za Telefon, SMS, Kontakte, Kalendar, Zapisnike poziva i Uređaje u blizini."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Aplikaciji će biti dozvoljeni sinhroniziranje informacija, kao što je ime osobe koja upućuje poziv i pristup ovim odobrenjima na uređaju <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Odaberite uređaj kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Odaberite <xliff:g id="PROFILE_NAME">%1$s</xliff:g> da postavite"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Aplikaciji će biti dozvoljeni sinhroniziranje informacija, kao što je ime osobe koja upućuje poziv i pristup ovim odobrenjima na uređaju <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Dozvoliti aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da upravlja uređajem &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"naočale"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Ova aplikacija je potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikaciji <xliff:g id="APP_NAME">%2$s</xliff:g> će biti dozvoljena interakcija s obavještenjima i pristup odobrenjima za Telefon, SMS, Kontakte, Mikrofon i Uređaje u blizini."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Aplikaciji će biti dozvoljen pristup ovim odobrenjima na uređaju <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"uređaj"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Aplikaciji će biti dozvoljen pristup ovim odobrenjima na uređaju <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Dozvolite da aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pristupa ovim informacijama s telefona"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Usluga na više uređaja"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> u ime uređaja <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> zahtijeva odobrenje da prenosi aplikacije između vaših uređaja"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Dozvoliti uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; da poduzme ovu radnju?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> u ime uređaja <xliff:g id="DEVICE_NAME">%2$s</xliff:g> traži odobrenje da prenosi aplikacije i druge funkcije sistema na uređajima u blizini"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Ova aplikacija će moći sinhronizirati informacije, kao što je ime osobe koja upućuje poziv, između vašeg telefona i uređaja <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Ova aplikacija će moći sinhronizirati informacije, kao što je ime osobe koja upućuje poziv, između vašeg telefona i odabranog uređaja"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Dozvoli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nemoj dozvoliti"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Otkaži"</string>
     <string name="consent_back" msgid="2560683030046918882">"Nazad"</string>
     <string name="permission_expand" msgid="893185038020887411">"Proširivanje stavke <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Sužavanje stavke <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ca/strings.xml b/packages/CompanionDeviceManager/res/values-ca/strings.xml
index 38a6dbc..b6f182d 100644
--- a/packages/CompanionDeviceManager/res/values-ca/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ca/strings.xml
@@ -17,15 +17,14 @@
 <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">"Gestor de dispositius complementaris"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Permet que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; accedeixi a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Vols permetre que l\'aplicació &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; accedeixi a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"rellotge"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Tria un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> perquè el gestioni &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Aquesta aplicació es necessita per gestionar el dispositiu (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>). <xliff:g id="APP_NAME">%2$s</xliff:g> tindrà permís per sincronitzar informació, com ara el nom d\'algú que truca, per interaccionar amb les teves notificacions i accedir al telèfon, als SMS, als contactes, al calendari, als registres de trucades i als dispositius propers."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Aquesta aplicació podrà sincronitzar informació, com ara el nom d\'algú que truca, i accedir a aquests permisos al dispositiu (<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>)"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Tria un dispositiu perquè el gestioni &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Tria un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> per configurar"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Aquesta aplicació podrà sincronitzar informació, com ara el nom d\'algú que truca, i accedir a aquests permisos al dispositiu (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Permet que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; gestioni &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ulleres"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Aquesta aplicació es necessita per gestionar el dispositiu (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>). <xliff:g id="APP_NAME">%2$s</xliff:g> tindrà permís per interaccionar amb les teves notificacions i accedir al telèfon, als SMS, als contactes, al micròfon i als dispositius propers."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Aquesta aplicació podrà accedir a aquests permisos del dispositiu (<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>)"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositiu"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Aquesta aplicació podrà accedir a aquests permisos del dispositiu (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permet que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; accedeixi a aquesta informació del telèfon"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Serveis multidispositiu"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> demana permís en nom del teu dispositiu (<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>) per reproduir en continu aplicacions entre els dispositius"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Vols permetre que &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; dugui a terme aquesta acció?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> sol·licita permís en nom del teu dispositiu (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>) per reproduir en continu aplicacions i altres funcions del sistema en dispositius propers"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositiu"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Aquesta aplicació podrà sincronitzar informació, com ara el nom d\'algú que truca, entre el teu telèfon i el dispositiu (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Aquesta aplicació podrà sincronitzar informació, com ara el nom d\'algú que truca, entre el teu telèfon i el dispositiu triat"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permet"</string>
     <string name="consent_no" msgid="2640796915611404382">"No permetis"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancel·la"</string>
     <string name="consent_back" msgid="2560683030046918882">"Enrere"</string>
     <string name="permission_expand" msgid="893185038020887411">"Desplega <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Replega <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-cs/strings.xml b/packages/CompanionDeviceManager/res/values-cs/strings.xml
index 7151823..b62496d 100644
--- a/packages/CompanionDeviceManager/res/values-cs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-cs/strings.xml
@@ -17,15 +17,14 @@
 <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">"Správce doprovodných zařízení"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Povolit aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; přístup k &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Povolit aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; přístup k &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"hodinky"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Vyberte <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, které chcete spravovat pomocí aplikace &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Tato aplikace je nutná ke správě zařízení <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude moci synchronizovat údaje, jako je jméno volajícího, interagovat s vašimi oznámeními a získat přístup k vašim oprávněním k telefonu, SMS, kontaktům, kalendáři, seznamům hovorů a zařízením v okolí."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Tato aplikace bude moci synchronizovat údaje, jako je jméno volajícího, a získat přístup k těmto oprávněním v <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Vyberte zařízení, které chcete spravovat pomocí aplikace &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Vyberte zařízení <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, které chcete nastavit"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Tato aplikace bude moci synchronizovat údaje, jako je jméno volajícího, a získat na zařízení <xliff:g id="DEVICE_NAME">%1$s</xliff:g> přístup k těmto oprávněním"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Povolit aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; spravovat zařízení &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"brýle"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Tato aplikace je nutná ke správě zařízení <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude moci interagovat s vašimi oznámeními a získat přístup k vašim oprávněním k telefonu, SMS, kontaktům, mikrofonu a zařízením v okolí."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Tato aplikace bude mít ve vašem <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> povolený přístup k těmto oprávněním:"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"zařízení"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Tato aplikace bude mít ve vašem <xliff:g id="DEVICE_NAME">%1$s</xliff:g> povolený přístup k těmto oprávněním"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Povolte aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; přístup k těmto informacím z vašeho telefonu"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Služby pro více zařízení"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> požaduje za vaše zařízení <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> oprávnění ke streamování aplikací mezi zařízeními"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Povolit zařízení &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; podniknout tuto akci?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> žádá jménem vašeho zařízení <xliff:g id="DEVICE_NAME">%2$s</xliff:g> o oprávnění streamovat aplikace a další systémové funkce do zařízení v okolí"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"zařízení"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Tato aplikace bude moci synchronizovat údaje, jako je jméno volajícího, mezi vaším telefonem a zařízením <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Tato aplikace bude moci synchronizovat údaje, jako je jméno volajícího, mezi vaším telefonem a vybraným zařízením"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Povolit"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nepovolovat"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Zrušit"</string>
     <string name="consent_back" msgid="2560683030046918882">"Zpět"</string>
     <string name="permission_expand" msgid="893185038020887411">"Rozbalit sekci <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Sbalit sekci <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-da/strings.xml b/packages/CompanionDeviceManager/res/values-da/strings.xml
index 14f2454..df63e6c 100644
--- a/packages/CompanionDeviceManager/res/values-da/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-da/strings.xml
@@ -17,15 +17,14 @@
 <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">"Medfølgende enhedsadministrator"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Vil du give &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; adgang til &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Vil du give appen &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; adgang til &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ur"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Vælg det <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, som skal administreres af &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Du skal bruge denne app for at administrere <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tilladelse til at interagere med dine notifikationer og synkronisere oplysninger som f.eks. navnet på en person, der ringer, og appen får adgang til dine tilladelser for Opkald, Sms, Kalender, Opkaldshistorik og Enheder i nærheden."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Denne app får tilladelse til at synkronisere oplysninger, f.eks. navne på dem, der ringer, og adgang til disse tilladelser på din <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Vælg en enhed, som skal administreres af &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Vælg en <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, som du vil konfigurere"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Denne app får tilladelse til at synkronisere oplysninger, f.eks. navne på dem, der ringer, og adgang til disse tilladelser på din <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Vil du tillade, at &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; administrerer &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"briller"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Du skal bruge denne app for at administrere <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tilladelse til at interagere med dine notifikationer og tilgå tilladelserne Telefon, Sms, Kontakter, Mikrofon og Enheder i nærheden."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Denne app får adgang til disse tilladelser på din <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"enhed"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Denne app får adgang til disse tilladelser på din <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Giv &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; adgang til disse oplysninger fra din telefon"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Tjenester, som kan tilsluttes en anden enhed"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> anmoder om tilladelse på vegne af din <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> til at streame apps mellem dine enheder"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Vil du tillade, at &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; foretager denne handling?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> anmoder om tilladelse på vegne af din <xliff:g id="DEVICE_NAME">%2$s</xliff:g> til at streame apps og andre systemfunktioner til enheder i nærheden"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"enhed"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Denne app vil kunne synkronisere oplysninger som f.eks. navnet på en person, der ringer, mellem din telefon og <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Denne app vil kunne synkronisere oplysninger som f.eks. navnet på en person, der ringer, mellem din telefon og den valgte enhed"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Tillad"</string>
     <string name="consent_no" msgid="2640796915611404382">"Tillad ikke"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Annuller"</string>
     <string name="consent_back" msgid="2560683030046918882">"Tilbage"</string>
     <string name="permission_expand" msgid="893185038020887411">"Udvid <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Skjul <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-de/strings.xml b/packages/CompanionDeviceManager/res/values-de/strings.xml
index d8c6ac3b..f487f94 100644
--- a/packages/CompanionDeviceManager/res/values-de/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-de/strings.xml
@@ -17,15 +17,14 @@
 <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">"Begleitgerät-Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Zulassen, dass &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; auf das Gerät &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; zugreifen darf?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Zulassen, dass &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; auf &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; zugreifen darf?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"Smartwatch"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Gerät „<xliff:g id="PROFILE_NAME">%1$s</xliff:g>“ auswählen, das von &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; verwaltet werden soll"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Diese App wird zur Verwaltung deines Geräts (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) benötigt. <xliff:g id="APP_NAME">%2$s</xliff:g> darf dann Daten wie den Namen eines Anrufers synchronisieren, mit deinen Benachrichtigungen interagieren und auf die Berechtigungen „Telefon“, „SMS“, „Kontakte“, „Kalender“, „Anruflisten“ und „Geräte in der Nähe“ zugreifen."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Diese App darf dann Daten wie den Namen eines Anrufers synchronisieren und auf folgende Berechtigungen auf deinem <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> zugreifen"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Gerät auswählen, das von &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; verwaltet werden soll"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> zum Einrichten auswählen"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Diese App darf dann Daten wie den Namen eines Anrufers synchronisieren und auf diese Berechtigungen auf deinem <xliff:g id="DEVICE_NAME">%1$s</xliff:g> zugreifen"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Zulassen, dass &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; das Gerät &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; verwalten darf"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"Glass-Geräte"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Diese App wird zur Verwaltung deines Geräts (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) benötigt. <xliff:g id="APP_NAME">%2$s</xliff:g> darf mit deinen Benachrichtigungen interagieren und auf die Berechtigungen „Telefon“, „SMS“, „Kontakte“, „Mikrofon“ und „Geräte in der Nähe“ zugreifen."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Diese App darf dann auf die folgenden Berechtigungen auf deinem <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> zugreifen:"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"Gerät"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Diese App darf dann auf diese Berechtigungen auf deinem <xliff:g id="DEVICE_NAME">%1$s</xliff:g> zugreifen:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; Zugriff auf diese Informationen von deinem Smartphone gewähren"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Geräteübergreifende Dienste"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> bittet für dein <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> um die Berechtigung zum Streamen von Apps zwischen deinen Geräten"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Darf das Gerät &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; diese Aktion ausführen?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> bittet für dein Gerät (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>) um die Berechtigung, Apps und andere Systemfunktionen auf Geräte in der Nähe zu streamen"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"Gerät"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Diese App kann dann Daten wie den Namen eines Anrufers zwischen deinem Smartphone und deinem Gerät (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) synchronisieren"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Diese App kann dann Daten wie den Namen eines Anrufers zwischen deinem Smartphone und dem ausgewählten Gerät synchronisieren"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Zulassen"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nicht zulassen"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Abbrechen"</string>
     <string name="consent_back" msgid="2560683030046918882">"Zurück"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> maximieren"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> minimieren"</string>
diff --git a/packages/CompanionDeviceManager/res/values-el/strings.xml b/packages/CompanionDeviceManager/res/values-el/strings.xml
index 37fc39f..1c8ecb7 100644
--- a/packages/CompanionDeviceManager/res/values-el/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-el/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"Να επιτρέπεται στην εφαρμογή &lt;strong&gt;<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; ;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Να επιτρέπεται στην εφαρμογή &lt;strong&gt;<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;;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ρολόι"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Επιλέξτε ένα προφίλ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> για διαχείριση από την εφαρμογή &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Αυτή η εφαρμογή είναι απαραίτητη για τη διαχείριση της συσκευής <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Η εφαρμογή <xliff:g id="APP_NAME">%2$s</xliff:g> θα μπορεί να συγχρονίζει πληροφορίες, όπως το όνομα ενός ατόμου που σας καλεί, να αλληλεπιδρά με τις ειδοποιήσεις σας και να αποκτά πρόσβαση στις άδειες Τηλέφωνο, SMS, Επαφές, Ημερολόγιο, Αρχεία καταγρ. κλήσ. και Συσκευές σε κοντινή απόσταση."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Αυτή η εφαρμογή θα μπορεί να συγχρονίζει πληροφορίες, όπως το όνομα ενός ατόμου που σας καλεί, και να αποκτά πρόσβαση σε αυτές τις άδειες στη συσκευή <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Επιλέξτε μια συσκευή για διαχείριση μέσω της εφαρμογής &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Επιλέξτε ένα προφίλ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> για ρύθμιση"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Αυτή η εφαρμογή θα μπορεί να συγχρονίζει πληροφορίες, όπως το όνομα ενός ατόμου που σας καλεί, και να αποκτά πρόσβαση σε αυτές τις άδειες στη συσκευή <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Να επιτρέπεται στην εφαρμογή &lt;strong&gt;<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; ;"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"γυαλιά"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Αυτή η εφαρμογή είναι απαραίτητη για τη διαχείριση της συσκευής <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Θα επιτρέπεται στην εφαρμογή <xliff:g id="APP_NAME">%2$s</xliff:g> να αλληλεπιδρά με τις ειδοποιήσεις σας και να αποκτά πρόσβαση στις άδειες για το Τηλέφωνο, τα SMS, τις Επαφές, το Μικρόφωνο και τις Συσκευές σε κοντινή απόσταση."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Αυτή η εφαρμογή θα μπορεί να έχει πρόσβαση σε αυτές τις άδειες στη συσκευή <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"συσκευή"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Αυτή η εφαρμογή θα μπορεί να έχει πρόσβαση σε αυτές τις άδειες στη συσκευή <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Να επιτρέπεται στο &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; η πρόσβαση σε αυτές τις πληροφορίες από το τηλέφωνό σας."</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Υπηρεσίες πολλών συσκευών"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> ζητά εκ μέρους της συσκευής σας <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> άδεια για ροή εφαρμογών μεταξύ των συσκευών σας"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Να επιτρέπεται στη συσκευή &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; να εκτελεί αυτήν την ενέργεια;"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> ζητά άδεια εκ μέρους της συσκευής σας <xliff:g id="DEVICE_NAME">%2$s</xliff:g> για ροή εφαρμογών και άλλων λειτουργιών του συστήματος σε συσκευές σε κοντινή απόσταση"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"συσκευή"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Αυτή η εφαρμογή θα μπορεί να συγχρονίζει πληροφορίες μεταξύ του τηλεφώνου και της συσκευής <xliff:g id="DEVICE_NAME">%1$s</xliff:g>, όπως το όνομα ενός ατόμου που σας καλεί."</string>
     <string name="summary_generic" msgid="1761976003668044801">"Αυτή η εφαρμογή θα μπορεί να συγχρονίζει πληροφορίες μεταξύ του τηλεφώνου και της επιλεγμένης συσκευής σας, όπως το όνομα ενός ατόμου που σας καλεί."</string>
     <string name="consent_yes" msgid="8344487259618762872">"Να επιτρέπεται"</string>
     <string name="consent_no" msgid="2640796915611404382">"Να μην επιτρέπεται"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Ακύρωση"</string>
     <string name="consent_back" msgid="2560683030046918882">"Πίσω"</string>
     <string name="permission_expand" msgid="893185038020887411">"Ανάπτυξη <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Σύμπτυξη <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
index 2727d86..ca12145 100644
--- a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Allow the app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"This app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Choose a device to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to set up"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to manage &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"glasses"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"This app is needed to manage <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, microphone and Nearby devices permissions."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"This app will be allowed to access these permissions on your <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"device"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"This app will be allowed to access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> to stream apps between your devices"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Allow &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; to take this action?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_NAME">%2$s</xliff:g> to stream apps and other system features to nearby devices"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"This app will be able to sync info, like the name of someone calling, between your phone and the chosen device"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
     <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string>
     <string name="consent_back" msgid="2560683030046918882">"Back"</string>
     <string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
index cc221b4..bd0342d 100644
--- a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Allow the app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"This app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Choose a device to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to set up"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to manage &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"glasses"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"This app is needed to manage <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"This app will be allowed to access these permissions on your <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"device"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"This app will be allowed to access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> to stream apps between your devices"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Allow &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; to take this action?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_NAME">%2$s</xliff:g> to stream apps and other system features to nearby devices"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"This app will be able to sync info, like the name of someone calling, between your phone and the chosen device"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
     <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string>
     <string name="consent_back" msgid="2560683030046918882">"Back"</string>
     <string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
index 2727d86..ca12145 100644
--- a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Allow the app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"This app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Choose a device to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to set up"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to manage &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"glasses"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"This app is needed to manage <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, microphone and Nearby devices permissions."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"This app will be allowed to access these permissions on your <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"device"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"This app will be allowed to access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> to stream apps between your devices"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Allow &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; to take this action?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_NAME">%2$s</xliff:g> to stream apps and other system features to nearby devices"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"This app will be able to sync info, like the name of someone calling, between your phone and the chosen device"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
     <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string>
     <string name="consent_back" msgid="2560683030046918882">"Back"</string>
     <string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
index 2727d86..ca12145 100644
--- a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Allow the app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"This app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Choose a device to be managed by &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to set up"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to manage &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"glasses"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"This app is needed to manage <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, microphone and Nearby devices permissions."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"This app will be allowed to access these permissions on your <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"device"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"This app will be allowed to access these permissions on your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to access this information from your phone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> to stream apps between your devices"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Allow &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; to take this action?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_NAME">%2$s</xliff:g> to stream apps and other system features to nearby devices"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"This app will be able to sync info, like the name of someone calling, between your phone and the chosen device"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
     <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancel"</string>
     <string name="consent_back" msgid="2560683030046918882">"Back"</string>
     <string name="permission_expand" msgid="893185038020887411">"Expand <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Collapse <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
index 431a69c..70af915 100644
--- a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
@@ -17,15 +17,14 @@
 <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">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎Companion Device Manager‎‏‎‎‏‎"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‎‏‎‎‎‏‏‏‎‎‏‏‏‏‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to access &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;?‎‏‎‎‏‎"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎‎‎‏‏‎‏‎‎‎‏‏‎‏‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‏‎‎Allow the app &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to access &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;?‎‏‎‎‏‎"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎watch‎‏‎‎‏‎"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‎‏‎‏‏‏‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎Choose a ‎‏‎‎‏‏‎<xliff:g id="PROFILE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to be managed by &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;‎‏‎‎‏‎"</string>
-    <string name="summary_watch" msgid="898569637110705523">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‏‏‏‎‎‎‎‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‏‏‏‏‏‎‎‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‏‎‎‏‏‎This app is needed to manage your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎. ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.‎‏‎‎‏‎"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‎‎‎‎‏‏‎‏‏‏‎‎‏‎‎‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‏‎This app will be allowed to sync info, like the name of someone calling, and access these permissions on your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‎‎‏‎‏‏‎‏‏‎‎‏‎‎‎‏‏‎‎‏‏‏‏‎‏‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‎‎‎Choose a device to be managed by &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;‎‏‎‎‏‎"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‎‏‏‏‏‎‏‎‏‎Choose a ‎‏‎‎‏‏‎<xliff:g id="PROFILE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to set up‎‏‎‎‏‎"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‎‎‎‏‎‎‎‏‎‏‏‎‏‏‎‎This app will be allowed to sync info, like the name of someone calling, and access these permissions on your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‎‏‏‎‎‏‎‏‎‏‏‎‎‏‎‏‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to manage &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;?‎‏‎‎‏‎"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‏‏‎‎‏‏‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‎‏‎‏‎‎‎‏‏‎‎‏‏‏‏‎‏‏‏‏‎‎glasses‎‏‎‎‏‎"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‎‎‏‎‎‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‏‏‎‏‏‏‎‎‎‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎This app is needed to manage ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎. ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.‎‏‎‎‏‎"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‎‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‎‏‏‎‎This app will be allowed to access these permissions on your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‎‏‎‎‏‏‎‎‏‏‎‏‏‏‎‏‏‎‎‎‎‎‏‏‏‏‏‏‎‎‎‎‏‎‏‏‎‏‎‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎device‎‏‎‎‏‎"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‎‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‎‏‎‎‎‏‎‎‏‏‎This app will be allowed to access these permissions on your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‏‏‎‎‎‏‎‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‏‎‎‏‏‎‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to access this information from your phone‎‏‎‎‏‎"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‏‏‏‎‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‎‎‎‎‏‎‏‎‏‎‎‏‎‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎‏‎Cross-device services‎‏‎‎‏‎"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‎‎‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‏‎‎‎‏‎‏‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is requesting permission on behalf of your ‎‏‎‎‏‏‎<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ to stream apps between your devices‎‏‎‎‏‎"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‏‎‎‎‎‎‏‎‎‏‏‎‎‏‏‏‏‎‎‎‎‎‏‏‏‎‎‎‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‏‎‎‏‎‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to take this action?‎‏‎‎‏‎"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‏‏‎‎‏‎‎‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is requesting permission on behalf of your ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ to stream apps and other system features to nearby devices‎‏‎‎‏‎"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‎‎‎‎‎‎‎‏‏‏‎‏‏‎‏‏‎‎‎device‎‏‎‎‏‎"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‏‎‏‎‏‎‏‎‎‎‏‏‏‎‏‎‎‏‎‎‏‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‎‏‎This app will be able to sync info, like the name of someone calling, between your phone and ‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="summary_generic" msgid="1761976003668044801">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‎‎‏‏‏‏‎‎‏‎‏‏‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‎‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎This app will be able to sync info, like the name of someone calling, between your phone and the chosen device‎‏‎‎‏‎"</string>
     <string name="consent_yes" msgid="8344487259618762872">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‎‎‏‎‎‏‎‏‏‏‏‎‎‏‏‏‎‎‏‏‏‏‎‎‏‎‏‏‎‎‏‎‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎Allow‎‏‎‎‏‎"</string>
     <string name="consent_no" msgid="2640796915611404382">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‎‎‏‎‏‏‏‏‎‎Don’t allow‎‏‎‎‏‎"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‏‎‎‏‎‎‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‎‎‎‏‎Cancel‎‏‎‎‏‎"</string>
     <string name="consent_back" msgid="2560683030046918882">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‎‎‏‎‎‏‎‏‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‏‎‎‎‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‎‎Back‎‏‎‎‏‎"</string>
     <string name="permission_expand" msgid="893185038020887411">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‏‎‎‏‎‏‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‎‏‏‎‏‏‏‎‎‎‎‏‏‎‏‏‏‎‎‏‏‎Expand ‎‏‎‎‏‏‎<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‎‎‎‎‎‏‏‎‏‎‎‎Collapse ‎‏‎‎‏‏‎<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
diff --git a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
index 26868f6..cba0b46 100644
--- a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
@@ -17,15 +17,14 @@
 <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">"Administrador de dispositivo complementario"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"¿Quieres permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"¿Quieres permitir que la app de &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"reloj"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para que la app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; lo administre"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Esta app es necesaria para administrar tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> podrá sincronizar información, como el nombre de la persona que llama, interactuar con tus notificaciones y acceder a los permisos de Teléfono, SMS, Contactos, Calendario, Llamadas y Dispositivos cercanos."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Esta app podrá sincronizar información, como el nombre de alguien cuando te llame, y acceder a los siguientes permisos en tu <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Elige un dispositivo para que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; lo administre"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para configurar"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Esta app podrá sincronizar información, como el nombre de alguien cuando te llame, y acceder a los siguientes permisos en tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Permite que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; administre &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"Gafas"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Esta app es necesaria para administrar <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> podrá interactuar con tus notificaciones y acceder a los permisos de Teléfono, SMS, Contactos, Micrófono y Dispositivos cercanos."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Esta app podrá acceder a los siguientes permisos en tu <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositivo"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Esta app podrá acceder a los siguientes permisos en tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permite que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información de tu teléfono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Servicios multidispositivo"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicita tu permiso en nombre de <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para transmitir apps entre dispositivos"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"¿Permites que &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; realice esta acción?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> está solicitando permiso en nombre de tu <xliff:g id="DEVICE_NAME">%2$s</xliff:g> para transmitir apps y otras funciones del sistema a dispositivos cercanos"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Esta app podrá sincronizar información, como el nombre de la persona que llama, entre el teléfono y <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Esta app podrá sincronizar información, como el nombre de la persona que llama, entre el teléfono y el dispositivo elegido"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"No permitir"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
     <string name="consent_back" msgid="2560683030046918882">"Atrás"</string>
     <string name="permission_expand" msgid="893185038020887411">"Expandir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Contraer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-es/strings.xml b/packages/CompanionDeviceManager/res/values-es/strings.xml
index fedb8ae..50d8806 100644
--- a/packages/CompanionDeviceManager/res/values-es/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es/strings.xml
@@ -17,15 +17,14 @@
 <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">"Gestor de dispositivos complementario"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"¿Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a tu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"¿Permitir que la aplicación &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"reloj"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para gestionarlo con &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Se necesita esta aplicación para gestionar tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> podrá sincronizar información (por ejemplo, el nombre de la persona que te llama), interactuar con tus notificaciones y acceder a tus permisos de teléfono, SMS, contactos, calendario, registros de llamadas y dispositivos cercanos."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Esta aplicación podrá sincronizar información, como el nombre de la persona que llama, y acceder a estos permisos de tu <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Elige un dispositivo para que lo gestione &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Elige el <xliff:g id="PROFILE_NAME">%1$s</xliff:g> que quieras configurar"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Esta aplicación podrá sincronizar información, como el nombre de la persona que llama, y acceder a estos permisos de tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"¿Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; gestione &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"gafas"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Se necesita esta aplicación para gestionar <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> podrá interactuar con tus notificaciones y acceder a tus permisos de teléfono, SMS, contactos, micrófono y dispositivos cercanos."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Esta aplicación podrá acceder a estos permisos de tu <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositivo"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Esta aplicación podrá acceder a estos permisos de tu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información de tu teléfono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Servicios multidispositivo"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pidiendo permiso en nombre de tu <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para emitir aplicaciones en otros dispositivos tuyos"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"¿Permitir que &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; realice esta acción?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pidiendo permiso en nombre de tu <xliff:g id="DEVICE_NAME">%2$s</xliff:g> para emitir aplicaciones y otras funciones del sistema en dispositivos cercanos"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Esta aplicación podrá sincronizar información (por ejemplo, el nombre de la persona que te llama) entre tu teléfono y <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Esta aplicación podrá sincronizar información (por ejemplo, el nombre de la persona que te llama) entre tu teléfono y el dispositivo que elijas"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"No permitir"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
     <string name="consent_back" msgid="2560683030046918882">"Atrás"</string>
     <string name="permission_expand" msgid="893185038020887411">"Desplegar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Contraer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-et/strings.xml b/packages/CompanionDeviceManager/res/values-et/strings.xml
index 29a04bc..2eff7eb 100644
--- a/packages/CompanionDeviceManager/res/values-et/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-et/strings.xml
@@ -17,15 +17,14 @@
 <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">"Kaasseadme haldur"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Andke rakendusele &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; juurdepääs seadmele &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Kas anda rakendusele &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; juurdepääs seadmele &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"käekell"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Valige <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, mida haldab rakendus &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Seda rakendust on vaja teie seadme <xliff:g id="DEVICE_NAME">%1$s</xliff:g> haldamiseks. Rakendusel <xliff:g id="APP_NAME">%2$s</xliff:g> lubatakse sünkroonida teavet, näiteks helistaja nime, kasutada teie märguandeid ning pääseda juurde teie telefoni, SMS-ide, kontaktide, kalendri, kõnelogide ja läheduses olevate seadmete lubadele."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Sellel rakendusel lubatakse sünkroonida teavet (nt helistaja nime) ja antakse need load teie seadmes <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Valige seade, mida haldab rakendus &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Valige <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, mis seadistada"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Sellel rakendusel lubatakse sünkroonida teavet (nt helistaja nime) ja antakse need load teie seadmes <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Lubage rakendusel &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; hallata seadet &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"prillid"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Seda rakendust on vaja seadme <xliff:g id="DEVICE_NAME">%1$s</xliff:g> haldamiseks. Rakendusel <xliff:g id="APP_NAME">%2$s</xliff:g> lubatakse kasutada teie märguandeid ning pääseda juurde teie telefoni, SMS-ide, kontaktide, mikrofoni ja läheduses olevate seadmete lubadele."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Sellele rakendusele antakse need load teie seadmes <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"seade"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Sellele rakendusele antakse need load teie seadmes <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Lubage rakendusel &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pääseda teie telefonis juurde sellele teabele"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Seadmeülesed teenused"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> taotleb teie seadme <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> nimel luba teie seadmete vahel rakendusi voogesitada"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Kas lubada seadmel &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; teha seda toimingut?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> taotleb teie seadme <xliff:g id="DEVICE_NAME">%2$s</xliff:g> nimel luba voogesitada rakendusi ja muid süsteemi funktsioone läheduses olevatesse seadmetesse"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"seade"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"See rakendus saab sünkroonida teavet, näiteks helistaja nime, teie telefoni ja seadme <xliff:g id="DEVICE_NAME">%1$s</xliff:g> vahel"</string>
     <string name="summary_generic" msgid="1761976003668044801">"See rakendus saab sünkroonida teavet, näiteks helistaja nime, teie telefoni ja valitud seadme vahel"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Luba"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ära luba"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Tühista"</string>
     <string name="consent_back" msgid="2560683030046918882">"Tagasi"</string>
     <string name="permission_expand" msgid="893185038020887411">"Laienda: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Ahenda: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-eu/strings.xml b/packages/CompanionDeviceManager/res/values-eu/strings.xml
index a4f29e3..e130074 100644
--- a/packages/CompanionDeviceManager/res/values-eu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-eu/strings.xml
@@ -17,15 +17,14 @@
 <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">"Gailu osagarriaren kudeatzailea"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; erabiltzeko baimena eman nahi diozu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; gailua erabiltzeko baimena eman nahi diozu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"erlojua"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Aukeratu &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; aplikazioak kudeatu beharreko <xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Aplikazioa <xliff:g id="DEVICE_NAME">%1$s</xliff:g> kudeatzeko behar da. Informazioa sinkronizatzeko (esate baterako, deitzaileen izenak), jakinarazpenekin interakzioan aritzeko, eta telefonoa, SMSak, kontaktuak, egutegia, deien erregistroak eta inguruko gailuak erabiltzeko baimena izango du <xliff:g id="APP_NAME">%2$s</xliff:g> aplikazioak."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>n informazioa sinkronizatu (esate baterako, deitzaileen izenak) eta baimen hauek erabili ahalko ditu aplikazioak"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Aukeratu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioak kudeatu behar duen gailua"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Aukeratu konfiguratu nahi duzun <xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Informazioa sinkronizatu (esate baterako, deitzaileen izenak) eta baimen hauek erabili ahalko ditu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>n aplikazioak"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; kudeatzeko baimena eman nahi diozu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"betaurrekoak"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Aplikazioa \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\" izeneko gailua kudeatzeko behar da. Jakinarazpenekin interakzioan aritzeko, eta telefonoa, SMSak, kontaktuak, mikrofonoa eta inguruko gailuak erabiltzeko baimena izango du <xliff:g id="APP_NAME">%2$s</xliff:g> aplikazioak."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>n baimen hauek erabili ahalko ditu aplikazioak:"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"gailua"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Baimen hauek erabili ahalko ditu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>n aplikazioak:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Eman informazioa telefonotik hartzeko baimena &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Gailu baterako baino gehiagotarako zerbitzuak"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Gailu batetik bestera aplikazioak igortzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> gailuaren izenean"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Ekintza hau gauzatzeko baimena eman nahi diozu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikazioak eta sistemaren beste eginbide batzuk inguruko gailuetara igortzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_NAME">%2$s</xliff:g> gailuaren izenean"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"gailua"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Telefonoaren eta <xliff:g id="DEVICE_NAME">%1$s</xliff:g> gailuaren artean informazioa sinkronizatzeko gai izango da aplikazioa (esate baterako, deitzaileen izenak)"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Telefonoaren eta hautatutako gailuaren artean informazioa sinkronizatzeko gai izango da aplikazioa (esate baterako, deitzaileen izenak)"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Eman baimena"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ez eman baimenik"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Utzi"</string>
     <string name="consent_back" msgid="2560683030046918882">"Atzera"</string>
     <string name="permission_expand" msgid="893185038020887411">"Zabaldu <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Tolestu <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-fa/strings.xml b/packages/CompanionDeviceManager/res/values-fa/strings.xml
index d6c1a13..5e78aa5 100644
--- a/packages/CompanionDeviceManager/res/values-fa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fa/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"‏به &lt;strong&gt;<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; دسترسی پیدا کند؟"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"‏به برنامه &lt;strong&gt;<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; دسترسی پیدا کند؟"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ساعت"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"‏انتخاب <xliff:g id="PROFILE_NAME">%1$s</xliff:g> برای مدیریت کردن با &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>‏&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"این برنامه برای مدیریت <xliff:g id="DEVICE_NAME">%1$s</xliff:g> شما لازم است. به <xliff:g id="APP_NAME">%2$s</xliff:g> اجازه داده می‌شود اطلاعاتی مثل نام شخصی را که تماس می‌گیرد همگام‌سازی کند، با اعلان‌های شما تعامل داشته باشد، و به اجازه‌های «تلفن»، «پیامک»، «مخاطبین»، «تقویم»، «گزارش‌های تماس»، و «دستگاه‌های اطراف» دسترسی داشته باشد."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"به این برنامه اجازه داده می‌شود اطلاعاتی مثل نام تماس‌گیرنده را همگام‌سازی کند و به این اجازه‌ها در <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> شما دسترسی داشته باشد"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"‏انتخاب دستگاه برای مدیریت کردن با &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"انتخاب <xliff:g id="PROFILE_NAME">%1$s</xliff:g> برای راه‌اندازی"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"به این برنامه اجازه داده می‌شود اطلاعاتی مثل نام تماس‌گیرنده را همگام‌سازی کند و به این اجازه‌ها در <xliff:g id="DEVICE_NAME">%1$s</xliff:g> شما دسترسی داشته باشد"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"‏به &lt;strong&gt;<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; را مدیریت کند؟"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"عینک"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"این برنامه برای مدیریت <xliff:g id="DEVICE_NAME">%1$s</xliff:g> لازم است. به <xliff:g id="APP_NAME">%2$s</xliff:g> اجازه داده می‌شود با اعلان‌های شما تعامل داشته باشد و به اجازه‌های تلفنتان، پیامک، «مخاطبین»، «میکروفون»، و «دستگاه‌های اطراف» دسترسی داشته باشد."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"این برنامه مجاز می‌شود به این اجازه‌ها در <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> شما دسترسی پیدا کند"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"دستگاه"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"این برنامه مجاز می‌شود به این اجازه‌ها در <xliff:g id="DEVICE_NAME">%1$s</xliff:g> شما دسترسی پیدا کند"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"‏اجازه دادن به &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; برای دسترسی به اطلاعات تلفن"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"سرویس‌های بین‌دستگاهی"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> ازطرف <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> اجازه می‌خواهد برنامه‌ها را بین دستگاه‌های شما جاری‌سازی کند"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"‏به &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; اجازه داده شود این اقدام را انجام دهد؟"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> ازطرف <xliff:g id="DEVICE_NAME">%2$s</xliff:g> اجازه می‌خواهد تا برنامه‌ها و دیگر ویژگی‌های سیستم را در دستگاه‌های اطراف جاری‌سازی کند."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"دستگاه"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"این برنامه مجاز می‌شود اطلاعتی مثل نام شخصی را که تماس می‌گیرد بین تلفن شما و <xliff:g id="DEVICE_NAME">%1$s</xliff:g> همگام‌سازی کند"</string>
     <string name="summary_generic" msgid="1761976003668044801">"این برنامه مجاز می‌شود اطلاعتی مثل نام شخصی را که تماس می‌گیرد بین تلفن شما و دستگاه انتخاب‌شده همگام‌سازی کند"</string>
     <string name="consent_yes" msgid="8344487259618762872">"اجازه دادن"</string>
     <string name="consent_no" msgid="2640796915611404382">"اجازه ندادن"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"لغو"</string>
     <string name="consent_back" msgid="2560683030046918882">"برگشتن"</string>
     <string name="permission_expand" msgid="893185038020887411">"ازهم بازکردن <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"جمع کردن <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-fi/strings.xml b/packages/CompanionDeviceManager/res/values-fi/strings.xml
index 72122cb..552c473 100644
--- a/packages/CompanionDeviceManager/res/values-fi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fi/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Sallitaanko, että &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; saa pääsyn laitteeseen: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Sallitaanko, että soellus (&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;) saa pääsyn laitteeseen: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"kello"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Valitse <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, jota &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; hallinnoi"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Ylläpitoon (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) tarvitaan tätä sovellusta. <xliff:g id="APP_NAME">%2$s</xliff:g> saa luvan synkronoida tietoja (esimerkiksi soittajan nimen), hallinnoida ilmoituksiasi sekä pääsyn puhelimeen, tekstiviesteihin, yhteystietoihin, kalenteriin, puhelulokeihin ja lähellä olevat laitteet ‑lupiin."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Sovellus saa luvan synkronoida tietoja (esimerkiksi soittajan nimen) ja pääsyn näihin lupiin laitteella (<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>)"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Valitse laite, jota &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; hallinnoi"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Valitse <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, niin voit suorittaa käyttöönoton"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Sovellus saa luvan synkronoida tietoja (esimerkiksi soittajan nimen) ja pääsyn näihin lupiin laitteella (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Salli, että &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; saa ylläpitää laitetta: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"lasit"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Tätä sovellusta tarvitaan kohteen <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ylläpitoon. <xliff:g id="APP_NAME">%2$s</xliff:g> saa luvan hallinnoida ilmoituksiasi sekä pääsyn puhelimeen, tekstiviesteihin, yhteystietoihin, mikrofoniin ja lähellä olevat laitteet ‑lupiin."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Tämä sovellus saa käyttää näitä lupia laitteella (<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>)"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"laite"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Tämä sovellus saa käyttää näitä lupia laitteella (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Salli, että &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; saa pääsyn näihin puhelimesi tietoihin"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Laitteidenväliset palvelut"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> pyytää laitteesi (<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>) puolesta lupaa striimata sovelluksia laitteidesi välillä"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Sallitko, että &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; voi suorittaa tämän toiminnon?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> pyytää laitteesi (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>) puolesta lupaa striimata sovelluksia ja muita järjestelmän ominaisuuksia lähellä oleviin laitteisiin."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"laite"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Sovellus voi synkronoida tietoja (esimerkiksi soittajan nimen) puhelimesi ja laitteen (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) välillä"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Sovellus voi synkronoida tietoja (esimerkiksi soittajan nimen) puhelimesi ja valitun laitteen välillä"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Salli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Älä salli"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Peruuta"</string>
     <string name="consent_back" msgid="2560683030046918882">"Takaisin"</string>
     <string name="permission_expand" msgid="893185038020887411">"Laajenna <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Tiivistä <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
index 6b3ed28..753875b 100644
--- a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
@@ -17,15 +17,14 @@
 <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">"Gestionnaire d\'appareil compagnon"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Autoriser l\'application &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Choisissez un(e) <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré(e) par &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Cette application est nécessaire pour gérer votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> aura l\'autorisation de synchroniser des informations, comme le nom de l\'appelant, d\'interagir avec vos notifications et d\'accéder à vos autorisations pour le téléphone, les messages texte, les contacts, l\'agenda, les journaux d\'appels et les appareils à proximité."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Cette application sera autorisée à synchroniser des informations, comme le nom de l\'appelant, et à accéder à ces autorisations sur votre <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Choisir un appareil qui sera géré par &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Choisir un appareil (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) pour le configurer"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Cette application sera autorisée à synchroniser des informations, comme le nom de l\'appelant, et à accéder à ces autorisations sur votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à gérer &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"lunettes"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Cette application est nécessaire pour gérer vos <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. L\'application <xliff:g id="APP_NAME">%2$s</xliff:g> sera autorisée à interagir avec vos notifications et à accéder à vos autorisations pour le téléphone, les messages texte, les contacts, le microphone et les appareils à proximité."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Cette application pourra accéder à ces autorisations sur votre <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"appareil"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Cette application pourra accéder à ces autorisations sur votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Autorisez &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à ces informations à partir de votre téléphone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Services multiappareils"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> pour diffuser des applications entre vos appareils"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Autoriser &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; à effectuer cette action?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation, au nom de votre <xliff:g id="DEVICE_NAME">%2$s</xliff:g>, de diffuser des applications et d\'autres fonctionnalités du système sur des appareils à proximité"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Cette application pourra synchroniser des informations, comme le nom de l\'appelant, entre votre téléphone et <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Cette application pourra synchroniser des informations, comme le nom de l\'appelant, entre votre téléphone et l\'appareil sélectionné"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Annuler"</string>
     <string name="consent_back" msgid="2560683030046918882">"Retour"</string>
     <string name="permission_expand" msgid="893185038020887411">"Développer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Réduire <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-fr/strings.xml b/packages/CompanionDeviceManager/res/values-fr/strings.xml
index b649f27..d3704a0 100644
--- a/packages/CompanionDeviceManager/res/values-fr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr/strings.xml
@@ -17,15 +17,14 @@
 <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">"Gestionnaire d\'appareils associés"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Autoriser l\'appli &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Sélectionnez le/la <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré(e) par &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Cette appli est nécessaire pour gérer <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> aura l\'autorisation de synchroniser des infos (comme le nom de l\'appelant), d\'interagir avec vos notifications et d\'accéder à votre téléphone, à votre agenda, ainsi qu\'à vos SMS, contacts, journaux d\'appels et appareils à proximité."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Cette appli sera autorisée à synchroniser des infos (comme le nom de l\'appelant) et disposera de ces autorisations sur votre <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Sélectionner l\'appareil qui sera géré par &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Sélectionner votre <xliff:g id="PROFILE_NAME">%1$s</xliff:g> à configurer"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Cette appli sera autorisée à synchroniser des infos (comme le nom de l\'appelant) et disposera de ces autorisations sur votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à gérer &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"lunettes"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Cette appli est nécessaire pour gérer <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> aura l\'autorisation d\'interagir avec vos notifications et d\'accéder aux autorisations du téléphone, des SMS, des contacts, du micro et des appareils à proximité."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Cette appli sera autorisée à accéder à ces autorisations sur votre <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"appareil"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Cette appli sera autorisée à accéder à ces autorisations sur votre <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à accéder à ces informations depuis votre téléphone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Services inter-appareils"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> pour caster des applis d\'un appareil à l\'autre"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Autoriser &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; à effectuer cette action ?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_NAME">%2$s</xliff:g> de diffuser des applis et d\'autres fonctionnalités système en streaming sur des appareils à proximité"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Cette appli pourra synchroniser des infos, comme le nom de l\'appelant, entre votre téléphone et <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Cette appli pourra synchroniser des infos, comme le nom de l\'appelant, entre votre téléphone et l\'appareil choisi"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Annuler"</string>
     <string name="consent_back" msgid="2560683030046918882">"Retour"</string>
     <string name="permission_expand" msgid="893185038020887411">"Développer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Réduire <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-gl/strings.xml b/packages/CompanionDeviceManager/res/values-gl/strings.xml
index e7ff1e3..a7aa72d 100644
--- a/packages/CompanionDeviceManager/res/values-gl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gl/strings.xml
@@ -17,15 +17,14 @@
 <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">"Xestor de dispositivos complementarios"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Queres permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda ao dispositivo (&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;)?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Queres permitir que a aplicación &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda ao dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"reloxo"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Escolle un dispositivo (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) para que o xestione a aplicación &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Esta aplicación é necesaria para xestionar o teu dispositivo (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>). <xliff:g id="APP_NAME">%2$s</xliff:g> poderá sincronizar información (por exemplo, o nome de quen chama), interactuar coas túas notificacións e acceder aos permisos do teu teléfono, das SMS, dos contactos, do calendario, dos rexistros de chamadas e dos dispositivos próximos."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Esta aplicación poderá sincronizar información (por exemplo, o nome de quen chama) e acceder a estes permisos do dispositivo (<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>)"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Escolle un dispositivo para que o xestione a aplicación &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Escolle o perfil (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) que queiras configurar"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Esta aplicación poderá sincronizar información (por exemplo, o nome de quen chama) e acceder a estes permisos do dispositivo (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Queres permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; xestione o dispositivo (&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;)?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"lentes"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Esta aplicación é necesaria para xestionar o dispositivo (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>). <xliff:g id="APP_NAME">%2$s</xliff:g> poderá interactuar coas túas notificacións e acceder aos permisos do teu teléfono, das SMS, dos contactos, do micrófono e dos dispositivos próximos."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Esta aplicación poderá acceder a estes permisos do dispositivo (<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>)"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositivo"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Esta aplicación poderá acceder a estes permisos do dispositivo (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permitir que a aplicación &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acceda a esta información desde o teu teléfono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Servizos multidispositivo"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> está solicitando permiso en nome do teu dispositivo (<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>) para emitir contido de aplicacións entre os teus aparellos"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Queres permitir que &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; leve a cabo esta acción?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> está solicitando permiso en nome do teu dispositivo (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>) para emitir o contido das aplicacións e doutras funcións do sistema en dispositivos próximos"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Esta aplicación poderá sincronizar información (por exemplo, o nome de quen chama) entre o teléfono e o dispositivo (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Esta aplicación poderá sincronizar información (por exemplo, o nome de quen chama) entre o teléfono e o dispositivo escollido"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Non permitir"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
     <string name="consent_back" msgid="2560683030046918882">"Atrás"</string>
     <string name="permission_expand" msgid="893185038020887411">"Despregar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Contraer <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-gu/strings.xml b/packages/CompanionDeviceManager/res/values-gu/strings.xml
index 436d6cb..443fb4b 100644
--- a/packages/CompanionDeviceManager/res/values-gu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gu/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<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; ઍક્સેસ કરવાની મંજૂરી આપીએ?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"શું &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;ને ઍક્સેસ કરવા માટે, &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ઍપને મંજૂરી આપીએ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"સ્માર્ટવૉચ"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; દ્વારા મેનેજ કરવા માટે કોઈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> પસંદ કરો"</string>
-    <string name="summary_watch" msgid="898569637110705523">"તમારા <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ને મેનેજ કરવા માટે આ ઍપ જરૂરી છે. <xliff:g id="APP_NAME">%2$s</xliff:g>ને કૉલ કરનાર વ્યક્તિનું નામ જેવી માહિતી સિંક કરવાની, તમારા નોટિફિકેશન સાથે ક્રિયાપ્રતિક્રિયા કરવાની અને તમારો ફોન, SMS, સંપર્કો, Calendar, કૉલ લૉગ તથા નજીકના ડિવાઇસની પરવાનગીઓ ઍક્સેસ કરવાની મંજૂરી આપવામાં આવશે."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"આ ઍપને, કૉલ કરનાર વ્યક્તિનું નામ જેવી માહિતી સિંક કરવાની અને તમારા <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> પર આ પરવાનગીઓ ઍક્સેસ કરવાની મંજૂરી આપવામાં આવશે"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; દ્વારા મેનેજ કરવા માટે કોઈ ડિવાઇસ પસંદ કરો"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"સેટઅપ કરવા માટે કોઈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> પસંદ કરો"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"આ ઍપને, કૉલ કરનાર વ્યક્તિનું નામ જેવી માહિતી સિંક કરવાની અને તમારા <xliff:g id="DEVICE_NAME">%1$s</xliff:g> પર આ પરવાનગીઓ ઍક્સેસ કરવાની મંજૂરી આપવામાં આવશે"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<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; મેનેજ કરવા માટે મંજૂરી આપીએ?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ચશ્માં"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>ને મેનેજ કરવા માટે આ ઍપ જરૂરી છે. <xliff:g id="APP_NAME">%2$s</xliff:g>ને તમારા નોટિફિકેશન સાથે ક્રિયાપ્રતિક્રિયા કરવાની અને તમારો ફોન, SMS, સંપર્કો, માઇક્રોફોન તથા નજીકના ડિવાઇસની પરવાનગીઓ ઍક્સેસ કરવાની મંજૂરી આપવામાં આવશે."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"આ ઍપને તમારા <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> પર આ પરવાનગીઓ ઍક્સેસ કરવાની મંજૂરી મળશે"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ડિવાઇસ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"આ ઍપને તમારા <xliff:g id="DEVICE_NAME">%1$s</xliff:g> પર આ પરવાનગીઓ ઍક્સેસ કરવાની મંજૂરી મળશે"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"તમારા ફોનમાંથી આ માહિતી ઍક્સેસ કરવા માટે, &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;ને મંજૂરી આપો"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ક્રોસ-ડિવાઇસ સેવાઓ"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> તમારા <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> વતી તમારા ડિવાઇસ વચ્ચે ઍપ સ્ટ્રીમ કરવાની પરવાનગીની વિનંતી કરી રહી છે"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;ને આ પગલું ભરવાની મંજૂરી આપીએ?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> નજીકના ડિવાઇસ પર ઍપ અને સિસ્ટમની અન્ય સુવિધાઓ સ્ટ્રીમ કરવા તમારા <xliff:g id="DEVICE_NAME">%2$s</xliff:g> વતી પરવાનગીની વિનંતી કરી રહી છે"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ડિવાઇસ"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"આ ઍપ તમારા ફોન અને <xliff:g id="DEVICE_NAME">%1$s</xliff:g> વચ્ચે, કૉલ કરનાર કોઈ વ્યક્તિનું નામ જેવી માહિતી સિંક કરી શકશે"</string>
     <string name="summary_generic" msgid="1761976003668044801">"આ ઍપ તમારા ફોન અને પસંદ કરેલા ડિવાઇસ વચ્ચે, કૉલ કરનાર કોઈ વ્યક્તિનું નામ જેવી માહિતી સિંક કરી શકશે"</string>
     <string name="consent_yes" msgid="8344487259618762872">"મંજૂરી આપો"</string>
     <string name="consent_no" msgid="2640796915611404382">"મંજૂરી આપશો નહીં"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"રદ કરો"</string>
     <string name="consent_back" msgid="2560683030046918882">"પાછળ"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ને મોટું કરો"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>ને નાનું કરો"</string>
diff --git a/packages/CompanionDeviceManager/res/values-hi/strings.xml b/packages/CompanionDeviceManager/res/values-hi/strings.xml
index dd8e831..494af37 100644
--- a/packages/CompanionDeviceManager/res/values-hi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hi/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"क्या &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; को ऐक्सेस करने के लिए &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; को अनुमति देनी है?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"क्या &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; को ऐक्सेस करने के लिए &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ऐप्लिकेशन को अनुमति देनी है?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"स्मार्टवॉच"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"कोई <xliff:g id="PROFILE_NAME">%1$s</xliff:g> चुनें, ताकि उसे &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; की मदद से मैनेज किया जा सके"</string>
-    <string name="summary_watch" msgid="898569637110705523">"यह ऐप्लिकेशन, <xliff:g id="DEVICE_NAME">%1$s</xliff:g> को मैनेज करने के लिए ज़रूरी है. <xliff:g id="APP_NAME">%2$s</xliff:g> को डिवाइस की जानकारी सिंक करने की अनुमति होगी. जैसे, कॉल करने वाले व्यक्ति का नाम. इसे आपकी सूचनाओं पर कार्रवाई करने के साथ-साथ आपके फ़ोन, एसएमएस, संपर्कों, कैलेंडर, कॉल लॉग, और आस-पास मौजूद डिवाइसों को ऐक्सेस करने की अनुमति भी होगी."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"यह ऐप्लिकेशन, आपके <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> पर इन अनुमतियों को ऐक्सेस करने के साथ-साथ कॉल करने वाले व्यक्ति के नाम जैसी जानकारी सिंक कर पाएगा"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; से मैनेज किया जाने वाला डिवाइस चुनें"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"सेट अप करने के लिए कोई <xliff:g id="PROFILE_NAME">%1$s</xliff:g> चुनें"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"यह ऐप्लिकेशन, आपके <xliff:g id="DEVICE_NAME">%1$s</xliff:g> पर इन अनुमतियों को ऐक्सेस करने के साथ-साथ कॉल करने वाले व्यक्ति के नाम जैसी जानकारी सिंक कर पाएगा"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"क्या &lt;strong&gt;<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; मैनेज करने की अनुमति देनी है?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"चश्मा"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"यह ऐप्लिकेशन, <xliff:g id="DEVICE_NAME">%1$s</xliff:g> मैनेज करने के लिए ज़रूरी है. <xliff:g id="APP_NAME">%2$s</xliff:g> को डिवाइस की सूचनाओं पर कार्रवाई करने की अनुमति होगी. इसे आपके फ़ोन, मैसेज, संपर्कों, माइक्रोफ़ोन, और आस-पास मौजूद डिवाइसों को ऐक्सेस करने की अनुमति भी होगी."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"यह ऐप्लिकेशन, आपके <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> पर इन अनुमतियों को ऐक्सेस कर पाएगा"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"डिवाइस"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"यह ऐप्लिकेशन, आपके <xliff:g id="DEVICE_NAME">%1$s</xliff:g> पर इन अनुमतियों को ऐक्सेस कर पाएगा"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; को अपने फ़ोन से यह जानकारी ऐक्सेस करने की अनुमति दें"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"क्रॉस-डिवाइस से जुड़ी सेवाएं"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> आपके <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> की ओर से, आपके डिवाइसों के बीच ऐप्लिकेशन स्ट्रीम करने की अनुमति मांग रहा है"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"क्या &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; को यह कार्रवाई करने की अनुमति देनी है?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> आपके <xliff:g id="DEVICE_NAME">%2$s</xliff:g> की ओर से, ऐप्लिकेशन और दूसरे सिस्टम की सुविधाओं को आस-पास मौजूद डिवाइसों पर स्ट्रीम करने की अनुमति मांग रहा है"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"डिवाइस"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"यह ऐप्लिकेशन, आपके फ़ोन और <xliff:g id="DEVICE_NAME">%1$s</xliff:g> के बीच जानकारी सिंक करेगा. जैसे, कॉल करने वाले व्यक्ति का नाम"</string>
     <string name="summary_generic" msgid="1761976003668044801">"यह ऐप्लिकेशन, आपके फ़ोन और चुने हुए डिवाइस के बीच जानकारी सिंक करेगा. जैसे, कॉल करने वाले व्यक्ति का नाम"</string>
     <string name="consent_yes" msgid="8344487259618762872">"अनुमति दें"</string>
     <string name="consent_no" msgid="2640796915611404382">"अनुमति न दें"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"रद्द करें"</string>
     <string name="consent_back" msgid="2560683030046918882">"वापस जाएं"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> को बड़ा करें"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> को छोटा करें"</string>
diff --git a/packages/CompanionDeviceManager/res/values-hr/strings.xml b/packages/CompanionDeviceManager/res/values-hr/strings.xml
index d32a319..5e175bc 100644
--- a/packages/CompanionDeviceManager/res/values-hr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hr/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Želite li dopustiti aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Želite li dopustiti aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"satom"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Odaberite <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Ta je aplikacija potrebna za upravljanje vašim uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME">%2$s</xliff:g> moći će sinkronizirati podatke, primjerice ime pozivatelja, stupati u interakciju s vašim obavijestima i pristupati vašim dopuštenjima za telefon, SMS-ove, kontakte, kalendar, zapisnike poziva i uređaje u blizini."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Aplikacija će moći sinkronizirati podatke kao što je ime pozivatelja i pristupiti tim dopuštenjima na vašem <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Odaberite uređaj kojim će upravljati aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Odaberite profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> koji želite postaviti"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Aplikacija će moći sinkronizirati podatke kao što je ime pozivatelja i pristupiti tim dopuštenjima na vašem uređaju <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Dopustiti aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da upravlja uređajem &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"naočale"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Ta je aplikacija potrebna za upravljanje uređajem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacija <xliff:g id="APP_NAME">%2$s</xliff:g> moći će stupati u interakciju s vašim obavijestima i pristupati vašim dopuštenjima za telefon, SMS-ove, kontakte, mikrofon i uređaje u blizini."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Aplikacija će moći pristupati ovim dopuštenjima na vašem <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"uređaj"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Aplikacija će moći pristupati ovim dopuštenjima na vašem uređaju <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Omogućite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da pristupa informacijama s vašeg telefona"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Usluge na različitim uređajima"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahtijeva dopuštenje u ime vašeg uređaja <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> za stream aplikacija s jednog uređaja na drugi"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Želite li uređaju &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; dopustiti da izvrši tu radnju?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> zahtijeva dopuštenje u ime vašeg uređaja <xliff:g id="DEVICE_NAME">%2$s</xliff:g> za emitiranje aplikacija i drugih značajki sustava na uređajima u blizini"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Ta će aplikacija moći sinkronizirati podatke između vašeg telefona i uređaja <xliff:g id="DEVICE_NAME">%1$s</xliff:g>, primjerice ime pozivatelja"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Ta će aplikacija moći sinkronizirati podatke između vašeg telefona i odabranog uređaja, primjerice ime pozivatelja"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Dopusti"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nemoj dopustiti"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Odustani"</string>
     <string name="consent_back" msgid="2560683030046918882">"Natrag"</string>
     <string name="permission_expand" msgid="893185038020887411">"Proširi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Sažmi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-hu/strings.xml b/packages/CompanionDeviceManager/res/values-hu/strings.xml
index 5943178..ba480f6 100644
--- a/packages/CompanionDeviceManager/res/values-hu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hu/strings.xml
@@ -17,15 +17,14 @@
 <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">"Társeszközök kezelője"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Engedélyezi a(z) &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; hozzáférését a következőhöz: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Engedélyezi a(z) &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; alkalmazás számára, hogy hozzáférjen a következőhöz: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"óra"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"A(z) &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; alkalmazással kezelni kívánt <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiválasztása"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Szükség van erre az alkalmazásra a következő kezeléséhez: <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A(z) <xliff:g id="APP_NAME">%2$s</xliff:g> képes lesz szinkronizálni információkat (például a hívó fél nevét), műveleteket végezhet majd az értesítésekkel, és hozzáférhet majd a Telefon, az SMS, a Névjegyek, a Naptár, a Hívásnaplók és a Közeli eszközök engedélyekhez."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Ez az alkalmazás képes lesz szinkronizálni információkat (például a hívó fél nevét), és hozzáférhet majd ezekhez az engedélyekhez az Ön <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> eszközén"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"A(z) &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; alkalmazással kezelni kívánt eszköz kiválasztása"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Válassza ki a beállítani kívánt <xliff:g id="PROFILE_NAME">%1$s</xliff:g> nevet."</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Ez az alkalmazás képes lesz szinkronizálni információkat (például a hívó fél nevét), és hozzáférhet majd ezekhez az engedélyekhez az Ön <xliff:g id="DEVICE_NAME">%1$s</xliff:g> eszközén"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Engedélyezi, hogy a(z) &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; kezelje a következő eszközt: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"szemüveg"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Erre az alkalmazásra szükség van a következő eszköz kezeléséhez: <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A(z) <xliff:g id="APP_NAME">%2$s</xliff:g> műveleteket végezhet majd az értesítésekkel, és hozzáférhet majd a Telefon, az SMS, a Névjegyek, a Mikrofon és a Közeli eszközök engedélyekhez."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Az alkalmazás hozzáférhet majd ezekhez az engedélyekhez az Ön <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> eszközén"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"eszköz"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Az alkalmazás hozzáférhet majd ezekhez az engedélyekhez az Ön <xliff:g id="DEVICE_NAME">%1$s</xliff:g> eszközén"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Engedélyezi a(z) „<xliff:g id="APP_NAME">%1$s</xliff:g>” alkalmazás számára az információhoz való hozzáférést a telefonról"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Többeszközös szolgáltatások"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> engedélyt kér a(z) <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> nevében az alkalmazások eszközök közötti streameléséhez"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Engedélyezi a(z) &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; számára ennek a műveletnek a végrehajtását?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> engedélyt kér a(z) <xliff:g id="DEVICE_NAME">%2$s</xliff:g> nevében az alkalmazások és más rendszerfunkciók közeli eszközökre történő streamelésére"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"eszköz"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Ez az alkalmazás képes lesz szinkronizálni az olyan információkat a telefon és a(z) <xliff:g id="DEVICE_NAME">%1$s</xliff:g> eszköz között, mint például a hívó fél neve."</string>
     <string name="summary_generic" msgid="1761976003668044801">"Ez az alkalmazás képes lesz szinkronizálni az olyan információkat a telefon és a kiválasztott eszköz között, mint például a hívó fél neve."</string>
     <string name="consent_yes" msgid="8344487259618762872">"Engedélyezés"</string>
     <string name="consent_no" msgid="2640796915611404382">"Tiltás"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Mégse"</string>
     <string name="consent_back" msgid="2560683030046918882">"Vissza"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> kibontása"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> összecsukása"</string>
diff --git a/packages/CompanionDeviceManager/res/values-hy/strings.xml b/packages/CompanionDeviceManager/res/values-hy/strings.xml
index 17f46ed..9385fd1 100644
--- a/packages/CompanionDeviceManager/res/values-hy/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hy/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Թույլատրե՞լ &lt;strong&gt;<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; սարքը"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Թույլատրե՞լ &lt;strong&gt;<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; սարքը"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ժամացույց"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Ընտրեք <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ը, որը պետք է կառավարվի &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; հավելվածի կողմից"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Այս հավելվածն անհրաժեշտ է ձեր <xliff:g id="DEVICE_NAME">%1$s</xliff:g> պրոֆիլը կառավարելու համար։ <xliff:g id="APP_NAME">%2$s</xliff:g> հավելվածը կկարողանա համաժամացնել տվյալները, օր․՝ զանգողի անունը, փոխազդել ձեր ծանուցումների հետ և կստանա «Հեռախոս», «SMS», «Կոնտակտներ», «Օրացույց», «Կանչերի ցուցակ» և «Մոտակա սարքեր» թույլտվությունները։"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Այս հավելվածը կկարողանա համաժամացնել տվյալները, օր․՝ զանգողի անունը, և կստանա հետևյալ թույլտվությունները ձեր <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>ում"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Ընտրեք սարքը, որը պետք է կառավարվի &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածի միջոցով"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Կարգավորելու համար ընտրեք <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ը"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Այս հավելվածը կկարողանա համաժամացնել տվյալները, օր․՝ զանգողի անունը, և կստանա հետևյալ թույլտվությունները ձեր <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ում"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Թույլատրե՞լ &lt;strong&gt;<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; սարքը"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ակնոց"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Այս հավելվածն անհրաժեշտ է <xliff:g id="DEVICE_NAME">%1$s</xliff:g> սարքը կառավարելու համար։ <xliff:g id="APP_NAME">%2$s</xliff:g> հավելվածը կկարողանա փոխազդել ձեր ծանուցումների հետ և կստանա «Հեռախոս», «SMS», «Կոնտակտներ», «Խոսափող» և «Մոտակա սարքեր» թույլտվությունները։"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Այս հավելվածը կստանա հետևյալ թույլտվությունները ձեր <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>ում"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"սարք"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Այս հավելվածը կստանա հետևյալ թույլտվությունները ձեր <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ում"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Թույլատրեք &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածին օգտագործել այս տեղեկությունները ձեր հեռախոսից"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Միջսարքային ծառայություններ"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը ձեր <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> սարքի անունից թույլտվություն է խնդրում՝ ձեր սարքերի միջև հավելվածներ հեռարձակելու համար"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Թույլատրե՞լ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; հավելվածին կատարել այս գործողությունը"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը ձեր <xliff:g id="DEVICE_NAME">%2$s</xliff:g> սարքի անունից թույլտվություն է խնդրում՝ մոտակա սարքերին հավելվածներ և համակարգի այլ գործառույթներ հեռարձակելու համար"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"սարք"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Այս հավելվածը կկարողանա համաժամացնել ձեր հեռախոսի և <xliff:g id="DEVICE_NAME">%1$s</xliff:g> սարքի տվյալները, օր․՝ զանգողի անունը"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Այս հավելվածը կկարողանա համաժամացնել ձեր հեռախոսի և ընտրված սարքի տվյալները, օր․՝ զանգողի անունը"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Թույլատրել"</string>
     <string name="consent_no" msgid="2640796915611404382">"Չթույլատրել"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Չեղարկել"</string>
     <string name="consent_back" msgid="2560683030046918882">"Հետ"</string>
     <string name="permission_expand" msgid="893185038020887411">"Ծավալել «<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>» բաժինը"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Ծալել «<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>» բաժինը"</string>
diff --git a/packages/CompanionDeviceManager/res/values-in/strings.xml b/packages/CompanionDeviceManager/res/values-in/strings.xml
index 39ffcd7..c03a5ab 100644
--- a/packages/CompanionDeviceManager/res/values-in/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-in/strings.xml
@@ -17,15 +17,14 @@
 <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">"Pengelola Perangkat Pendamping"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Izinkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengakses &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Izinkan aplikasi &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengakses &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"smartwatch"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk dikelola oleh &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Aplikasi ini diperlukan untuk mengelola <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> akan diizinkan menyinkronkan info, seperti nama penelepon, berinteraksi dengan notifikasi, dan mengakses izin Telepon, SMS, Kontak, Kalender, Log panggilan, dan Perangkat di sekitar."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Aplikasi ini akan diizinkan menyinkronkan info, seperti nama penelepon, dan mengakses izin ini di <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> Anda"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Pilih perangkat untuk dikelola oleh &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk disiapkan"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Aplikasi ini akan diizinkan menyinkronkan info, seperti nama penelepon, dan mengakses izin ini di <xliff:g id="DEVICE_NAME">%1$s</xliff:g> Anda"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Izinkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengelola &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"kacamata pintar"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Aplikasi ini diperlukan untuk mengelola <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> akan diizinkan berinteraksi dengan notifikasi dan mengakses izin Ponsel, SMS, Kontak, Mikrofon, dan Perangkat di sekitar."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Aplikasi ini akan diizinkan mengakses izin ini di <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> Anda"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"perangkat"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Aplikasi ini akan diizinkan mengakses izin ini di <xliff:g id="DEVICE_NAME">%1$s</xliff:g> Anda"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Izinkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; untuk mengakses informasi ini dari ponsel Anda"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Layanan lintas perangkat"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> meminta izin atas nama <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> untuk menstreaming aplikasi di antara perangkat Anda"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Izinkan &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; melakukan tindakan ini?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> meminta izin atas nama <xliff:g id="DEVICE_NAME">%2$s</xliff:g> untuk menstreaming aplikasi dan fitur sistem lainnya ke perangkat di sekitar"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"perangkat"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Aplikasi ini akan dapat menyinkronkan info, seperti nama penelepon, antara ponsel dan <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Aplikasi ini akan dapat menyinkronkan info, seperti nama penelepon, antara ponsel dan perangkat yang dipilih"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Izinkan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Jangan izinkan"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Batalkan"</string>
     <string name="consent_back" msgid="2560683030046918882">"Kembali"</string>
     <string name="permission_expand" msgid="893185038020887411">"Luaskan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Ciutkan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-is/strings.xml b/packages/CompanionDeviceManager/res/values-is/strings.xml
index 3f55d5d..fc9f5a1 100644
--- a/packages/CompanionDeviceManager/res/values-is/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-is/strings.xml
@@ -17,15 +17,14 @@
 <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">"Stjórnun fylgdartækja"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Veita &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aðgang að &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Veita forritinu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aðgang að &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"úr"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Velja <xliff:g id="PROFILE_NAME">%1$s</xliff:g> sem &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; á að stjórna"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Þetta forrit er nauðsynlegt til að stjórna <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> fær heimild til að samstilla upplýsingar, t.d. nafn þess sem hringir, og bregðast við tilkynningum og fær aðgang að heimildum fyrir síma, SMS, tengiliði, dagatal, símtalaskrár og nálæg tæki."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Þetta forrit fær heimild til að samstilla upplýsingar, t.d. nafn þess sem hringir, og fær aðgang að eftirfarandi heimildum í <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Veldu tæki sem &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; á að stjórna"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Veldu <xliff:g id="PROFILE_NAME">%1$s</xliff:g> til að setja upp"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Þetta forrit fær heimild til að samstilla upplýsingar, t.d. nafn þess sem hringir, og fær aðgang að eftirfarandi heimildum í <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Leyfa &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; að stjórna &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"gleraugu"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Þetta forrit er nauðsynlegt til að stjórna <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> fær heimild til að bregðast við tilkynningum og fær aðgang að heimildum fyrir síma, SMS, tengiliði, hljóðnema og nálæg tæki."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Þetta forrit fær aðgang að eftirfarandi heimildum í <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"tæki"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Þetta forrit fær aðgang að eftirfarandi heimildum í <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Veita &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aðgang að þessum upplýsingum úr símanum þínum"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Þjónustur á milli tækja"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> biður um heimild fyrir <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> til að streyma forritum á milli tækjanna þinna"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Leyfa &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; að framkvæma þessa aðgerð?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> biður um heimild fyrir <xliff:g id="DEVICE_NAME">%2$s</xliff:g> til að streyma forritum og öðrum kerfiseiginleikum í nálægum tækjum"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"tæki"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Þetta forrit mun geta samstillt upplýsingar, t.d. nafn þess sem hringir, á milli símans og <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Þetta forrit mun geta samstillt upplýsingar, t.d. nafn þess sem hringir, á milli símans og valins tækis"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Leyfa"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ekki leyfa"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Hætta við"</string>
     <string name="consent_back" msgid="2560683030046918882">"Til baka"</string>
     <string name="permission_expand" msgid="893185038020887411">"Stækka <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Minnka <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-it/strings.xml b/packages/CompanionDeviceManager/res/values-it/strings.xml
index 117d2aa..be431b6 100644
--- a/packages/CompanionDeviceManager/res/values-it/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-it/strings.xml
@@ -17,15 +17,14 @@
 <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">"Gestione dispositivi associati"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Vuoi consentire all\'app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di accedere a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Vuoi consentire all\'app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di accedere a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"orologio"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Scegli un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> da gestire con &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Questa app è necessaria per gestire <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> potrà sincronizzare informazioni, ad esempio il nome di un chiamante, interagire con le tue notifiche e accedere alle autorizzazioni Telefono, SMS, Contatti, Calendario, Registri chiamate e Dispositivi nelle vicinanze."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Questa app potrà sincronizzare informazioni, ad esempio il nome di un chiamante, e accedere alle seguenti autorizzazioni su <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>:"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Scegli un dispositivo che sia gestito da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Scegli un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> da configurare"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Questa app potrà sincronizzare informazioni, ad esempio il nome di un chiamante, e accedere alle seguenti autorizzazioni su <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Vuoi consentire all\'app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di gestire &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"occhiali"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Questa app è necessaria per gestire <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> potrà interagire con le tue notifiche e accedere alle autorizzazioni Telefono, SMS, Contatti, Microfono e Dispositivi nelle vicinanze."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Questa app potrà accedere alle seguenti autorizzazioni su <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>:"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositivo"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Questa app potrà accedere alle seguenti autorizzazioni su <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Consenti a &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di accedere a queste informazioni dal tuo telefono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Servizi cross-device"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> richiede per conto del tuo <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> l\'autorizzazione a trasmettere app in streaming tra i dispositivi"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Vuoi consentire a &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; di compiere questa azione?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> richiede per conto di <xliff:g id="DEVICE_NAME">%2$s</xliff:g> l\'autorizzazione a trasmettere in streaming app e altre funzionalità di sistema ai dispositivi nelle vicinanze"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Questa app potrà sincronizzare informazioni, ad esempio il nome di un chiamante, tra il telefono e <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Questa app potrà sincronizzare informazioni, ad esempio il nome di un chiamante, tra il telefono e il dispositivo scelto"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Consenti"</string>
     <string name="consent_no" msgid="2640796915611404382">"Non consentire"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Annulla"</string>
     <string name="consent_back" msgid="2560683030046918882">"Indietro"</string>
     <string name="permission_expand" msgid="893185038020887411">"Espandi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Comprimi <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-iw/strings.xml b/packages/CompanionDeviceManager/res/values-iw/strings.xml
index 573b8a7..8982660 100644
--- a/packages/CompanionDeviceManager/res/values-iw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-iw/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"‏לאשר לאפליקציה ‎&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&amp;g;‎‏ לגשת אל ‎&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;‎‏?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"‏לאפשר לאפליקציה &lt;strong&gt;<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;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"שעון"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"‏בחירת <xliff:g id="PROFILE_NAME">%1$s</xliff:g> לניהול באמצעות &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"‏האפליקציה הזו נחוצה כדי לנהל את <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. האפליקציה <xliff:g id="APP_NAME">%2$s</xliff:g> תוכל לסנכרן מידע, כמו השם של מישהו שמתקשר, לבצע פעולות בהתראות ולקבל הרשאות גישה לטלפון, ל-SMS, לאנשי הקשר, ליומן, ליומני השיחות ולמכשירים בקרבת מקום."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"האפליקציה הזו תוכל לסנכרן מידע, כמו השם של מישהו שמתקשר, ולגשת להרשאות האלה ב<xliff:g id="DEVICE_TYPE">%1$s</xliff:g> שלך"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"‏בחירה של מכשיר לניהול באמצעות &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"בחירת <xliff:g id="PROFILE_NAME">%1$s</xliff:g> להגדרה"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"האפליקציה הזו תוכל לסנכרן מידע, כמו השם של מישהו שמתקשר, ולגשת להרשאות האלה ב<xliff:g id="DEVICE_NAME">%1$s</xliff:g> שלך"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"‏מתן הרשאה לאפליקציה ‎&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&amp;g;‎‏ לנהל את ‎&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;‎‏"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"משקפיים"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"‏האפליקציה הזו נחוצה כדי לנהל את \'<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\'. האפליקציה <xliff:g id="APP_NAME">%2$s</xliff:g> תוכל לבצע פעולות בהתראות ותקבל הרשאות גישה לטלפון, ל-SMS, לאנשי הקשר, למיקרופון ולמכשירים בקרבת מקום."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"האפליקציה הזו תוכל לגשת להרשאות האלה ב<xliff:g id="DEVICE_TYPE">%1$s</xliff:g> שלך"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"מכשיר"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"האפליקציה הזו תוכל לגשת להרשאות האלה ב<xliff:g id="DEVICE_NAME">%1$s</xliff:g> שלך"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"‏מתן אישור לאפליקציה &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; לגשת למידע הזה מהטלפון שלך"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"שירותים למספר מכשירים"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מבקשת הרשאה עבור המכשיר <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> כדי לשדר אפליקציות בין המכשירים שלך"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"‏לתת הרשאה למכשיר &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; לבצע את הפעולה הזו?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מבקשת הרשאה עבור <xliff:g id="DEVICE_NAME">%2$s</xliff:g> כדי להעביר אפליקציות ותכונות מערכת אחרות בסטרימינג למכשירים בקרבת מקום"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"מכשיר"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"האפליקציה הזו תוכל לסנכרן מידע, כמו השם של מישהו שמתקשר, מהטלפון שלך למכשיר <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"האפליקציה הזו תוכל לסנכרן מידע, כמו השם של מישהו שמתקשר, מהטלפון שלך למכשיר שבחרת"</string>
     <string name="consent_yes" msgid="8344487259618762872">"יש אישור"</string>
     <string name="consent_no" msgid="2640796915611404382">"אין אישור"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ביטול"</string>
     <string name="consent_back" msgid="2560683030046918882">"חזרה"</string>
     <string name="permission_expand" msgid="893185038020887411">"הרחבה של <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"כיווץ של <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ja/strings.xml b/packages/CompanionDeviceManager/res/values-ja/strings.xml
index 0439d5f1..186944a 100644
--- a/packages/CompanionDeviceManager/res/values-ja/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ja/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<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; へのアクセスを許可しますか?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<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; へのアクセスを許可しますか?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ウォッチ"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; の管理対象となる<xliff:g id="PROFILE_NAME">%1$s</xliff:g>の選択"</string>
-    <string name="summary_watch" msgid="898569637110705523">"このアプリは<xliff:g id="DEVICE_NAME">%1$s</xliff:g>の管理に必要です。<xliff:g id="APP_NAME">%2$s</xliff:g> は通話相手の名前などの情報を同期したり、デバイスの通知を使用したり、電話、SMS、連絡先、カレンダー、通話履歴、付近のデバイスの権限にアクセスしたりできるようになります。"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"このアプリは、通話相手の名前などの情報を同期したり、<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>の以下の権限にアクセスしたりできるようになります"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; の管理対象となるデバイスの選択"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"設定する<xliff:g id="PROFILE_NAME">%1$s</xliff:g>の選択"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"このアプリは、通話相手の名前などの情報を同期したり、<xliff:g id="DEVICE_NAME">%1$s</xliff:g>の以下の権限にアクセスしたりできるようになります"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<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; の管理を許可しますか?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"眼鏡"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"このアプリは <xliff:g id="DEVICE_NAME">%1$s</xliff:g> の管理に必要です。<xliff:g id="APP_NAME">%2$s</xliff:g> はデバイスの通知を使用したり、電話、SMS、連絡先、マイク、付近のデバイスの権限にアクセスしたりできるようになります。"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"このアプリは、<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>の以下の権限にアクセスできるようになります"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"デバイス"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"このアプリは、<xliff:g id="DEVICE_NAME">%1$s</xliff:g>の以下の権限にアクセスできるようになります"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"スマートフォンのこの情報へのアクセスを &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; に許可"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"クロスデバイス サービス"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> が <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> に代わってデバイス間でアプリをストリーミングする権限をリクエストしています"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; にこの操作の実行を許可しますか?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> が <xliff:g id="DEVICE_NAME">%2$s</xliff:g> に代わって、アプリやその他のシステム機能を付近のデバイスにストリーミングする権限をリクエストしています"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"デバイス"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"このアプリは、あなたのスマートフォンと <xliff:g id="DEVICE_NAME">%1$s</xliff:g> との間で、通話相手の名前などの情報を同期できるようになります"</string>
     <string name="summary_generic" msgid="1761976003668044801">"このアプリは、あなたのスマートフォンと選択したデバイスとの間で、通話相手の名前などの情報を同期できるようになります"</string>
     <string name="consent_yes" msgid="8344487259618762872">"許可"</string>
     <string name="consent_no" msgid="2640796915611404382">"許可しない"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"キャンセル"</string>
     <string name="consent_back" msgid="2560683030046918882">"戻る"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>を開く"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>を閉じる"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ka/strings.xml b/packages/CompanionDeviceManager/res/values-ka/strings.xml
index e62afad..a7c6042 100644
--- a/packages/CompanionDeviceManager/res/values-ka/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ka/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"მიანიჭებთ &lt;strong&gt;<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; მოწყობილობაზე წვდომას?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"ნებას რთავთ &lt;strong&gt;<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; მოწყობილობაზე?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"საათი"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"აირჩიეთ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, რომელიც უნდა მართოს &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;-მა"</string>
-    <string name="summary_watch" msgid="898569637110705523">"ეს აპი საჭიროა თქვენი <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ს სამართავად. <xliff:g id="APP_NAME">%2$s</xliff:g>-ს ექნება ისეთი ინფორმაციის სინქრონიზაციის უფლება, როგორიც იმ ადამიანის სახელია, რომელიც გირეკავთ; ასევე, თქვენს შეტყობინებებთან ინტერაქციისა და თქვენს ტელეფონზე, SMS-ებზე, კონტაქტებზე, კალენდარზე, ზარების ჟურნალებსა და ახლომახლო მოწყობილობების ნებართვებზე წვდომის უფლება."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"ეს აპი შეძლებს ინფორმაციის სინქრონიზებას (მაგალითად, იმ ადამიანის სახელი, რომელიც გირეკავთ) და ამ წვდომებზე უფლების მოპოვებას თქვენს <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>-ში"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"აირჩიეთ მოწყობილობა, რომელიც უნდა მართოს &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; აპმა"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"აირჩიეთ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> დასაყენებლად"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"ეს აპი შეძლებს, დაასინქრონოს ინფორმაცია, მაგალითად, შემომავალი ზარის ავტორის სახელი და წვდომა იქონიოს ამ ნებართვებზე თქვენს <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ში"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"ნება დართეთ &lt;strong&gt;<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;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"სათვალე"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"ეს აპი საჭიროა თქვენი <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ის სამართავად. <xliff:g id="APP_NAME">%2$s</xliff:g> შეძლებს თქვენს შეტყობინებებთან ინტერაქციას და თქვენს ტელეფონზე, SMS-ებზე, კონტაქტებზე, მიკროფონსა და ახლომახლო მოწყობილობების ნებართვებზე წვდომას."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"ეს აპი შეძლებს ამ ნებართვებზე წვდომას თქვენს <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>-ში"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"მოწყობილობა"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"ეს აპი შეძლებს ამ ნებართვებზე წვდომას თქვენს <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ში"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"ნება დართეთ, რომ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; აპს ჰქონდეს ამ ინფორმაციაზე წვდომა თქვენი ტელეფონიდან"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"მოწყობილობათშორისი სერვისები"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> ითხოვს უფლებას თქვენი <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>-ის სახელით, რომ მოწყობილობებს შორის სტრიმინგი შეძლოს"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"გსურთ ნება მისცეთ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ს&lt;/strong&gt; ამ მოქმედების შესასრულებლად?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> ითხოვს თქვენი <xliff:g id="DEVICE_NAME">%2$s</xliff:g>-ის სახელით აპების და სისტემის სხვა ფუნქციების ახლომახლო მოწყობილობებზე სტრიმინგის ნებართვას"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"მოწყობილობა"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"ეს აპი შეძლებს ინფორმაციის სინქრონიზებას თქვენს ტელეფონსა და თქვენ მიერ არჩეულ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ს შორის, მაგალითად, იმ ადამიანის სახელის, რომელიც გირეკავთ"</string>
     <string name="summary_generic" msgid="1761976003668044801">"ეს აპი შეძლებს ინფორმაციის სინქრონიზებას თქვენს ტელეფონსა და თქვენ მიერ არჩეულ მოწყობილობას შორის, მაგალითად, იმ ადამიანის სახელის, რომელიც გირეკავთ"</string>
     <string name="consent_yes" msgid="8344487259618762872">"დაშვება"</string>
     <string name="consent_no" msgid="2640796915611404382">"არ დაიშვას"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"გაუქმება"</string>
     <string name="consent_back" msgid="2560683030046918882">"უკან"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-ის გაფართოება"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-ის ჩაკეცვა"</string>
diff --git a/packages/CompanionDeviceManager/res/values-kk/strings.xml b/packages/CompanionDeviceManager/res/values-kk/strings.xml
index 92951cb..5c6d24a 100644
--- a/packages/CompanionDeviceManager/res/values-kk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kk/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<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; құрылғысын пайдалануға рұқсат беру керек пе?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<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;? құрылғысына кіруге рұқсат беріңіз"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"сағат"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; арқылы басқарылатын <xliff:g id="PROFILE_NAME">%1$s</xliff:g> құрылғысын таңдаңыз"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Бұл қолданба <xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысын басқару үшін қажет. <xliff:g id="APP_NAME">%2$s</xliff:g> қолданбасы қоңырау шалушының аты сияқты деректі синхрондау, хабарландыруларды оқу және телефон, SMS, контактілер, күнтізбе, қоңырау журналдары мен маңайдағы құрылғылар рұқсаттарын пайдалана алады."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Бұл қолданба қоңырау шалушының аты сияқты деректі синхрондай алады және <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> құрылғысындағы мына рұқсаттарды пайдалана алады."</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; арқылы басқарылатын құрылғыны таңдаңыз"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Реттеу үшін <xliff:g id="PROFILE_NAME">%1$s</xliff:g> таңдаңыз"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Бұл қолданба қоңырау шалушының аты сияқты деректі синхрондай алады және <xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысындағы мына рұқсаттарды пайдалана алады."</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<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; құрылғысын басқаруға рұқсат беру керек пе?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"көзілдірік"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Бұл қолданба <xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысын басқару үшін қажет. <xliff:g id="APP_NAME">%2$s</xliff:g> қолданбасына хабарландыруларды оқуға, телефонды, хабарларды, контактілерді, микрофон мен маңайдағы құрылғыларды пайдалануға рұқсат беріледі."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Бұл қолданба <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> құрылғысында осы рұқсаттарды пайдалана алады."</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"құрылғы"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Бұл қолданба <xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысында осы рұқсаттарды пайдалана алады."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; қолданбасына телефоныңыздағы осы ақпаратты пайдалануға рұқсат беріңіз."</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Аралық құрылғы қызметтері"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> атынан құрылғылар арасында қолданбалар трансляциялау үшін рұқсат сұрайды."</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; құрылғысына бұл әрекетті орындауға рұқсат беру керек пе?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы <xliff:g id="DEVICE_NAME">%2$s</xliff:g> атынан қолданбалар мен басқа да жүйе функцияларын маңайдағы құрылғыларға трансляциялау рұқсатын сұрап тұр."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"құрылғы"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Бұл қолданба телефон мен <xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысы арасында деректі (мысалы, қоңырау шалушының атын) синхрондай алады."</string>
     <string name="summary_generic" msgid="1761976003668044801">"Бұл қолданба телефон мен таңдалған құрылғы арасында деректі (мысалы, қоңырау шалушының атын) синхрондай алады."</string>
     <string name="consent_yes" msgid="8344487259618762872">"Рұқсат беру"</string>
     <string name="consent_no" msgid="2640796915611404382">"Рұқсат бермеу"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Бас тарту"</string>
     <string name="consent_back" msgid="2560683030046918882">"Артқа"</string>
     <string name="permission_expand" msgid="893185038020887411">"\"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\" панелін жаю"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"\"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\" панелін жию"</string>
diff --git a/packages/CompanionDeviceManager/res/values-km/strings.xml b/packages/CompanionDeviceManager/res/values-km/strings.xml
index 18bd88c..c734cb6 100644
--- a/packages/CompanionDeviceManager/res/values-km/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-km/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"អនុញ្ញាតឱ្យ &lt;strong&gt;<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; ឬ?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"អនុញ្ញាតឱ្យកម្មវិធី &lt;strong&gt;<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; ឬ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"នាឡិកា"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"ជ្រើសរើស <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ដើម្បីឱ្យស្ថិតក្រោម​ការគ្រប់គ្រងរបស់ &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"ត្រូវការកម្មវិធីនេះ ដើម្បីគ្រប់គ្រង <xliff:g id="DEVICE_NAME">%1$s</xliff:g> របស់អ្នក។ <xliff:g id="APP_NAME">%2$s</xliff:g> នឹងត្រូវបានអនុញ្ញាតឱ្យ​ធ្វើសមកាលកម្មព័ត៌មាន ដូចជាឈ្មោះមនុស្សដែលហៅទូរសព្ទជាដើម ធ្វើអន្តរកម្មជាមួយការជូនដំណឹងរបស់អ្នក និងចូលប្រើការអនុញ្ញាតទូរសព្ទ, SMS, ទំនាក់ទំនង, ប្រតិទិន, កំណត់ហេតុហៅទូរសព្ទ និងឧបករណ៍នៅជិតរបស់អ្នក។"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"កម្មវិធីនេះនឹងត្រូវបានអនុញ្ញាតឱ្យធ្វើសមកាលកម្មព័ត៌មាន ដូចជាឈ្មោះមនុស្សដែលហៅទូរសព្ទជាដើម និងចូលប្រើការអនុញ្ញាតទាំងនេះនៅលើ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> របស់អ្នក"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"ជ្រើសរើសឧបករណ៍ ដើម្បីដាក់ក្រោម​ការគ្រប់គ្រងរបស់ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"ជ្រើសរើស <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ដើម្បីរៀបចំ"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"កម្មវិធីនេះនឹងត្រូវបានអនុញ្ញាតឱ្យធ្វើសមកាលកម្មព័ត៌មាន ដូចជាឈ្មោះមនុស្សដែលហៅទូរសព្ទជាដើម និងចូលប្រើប្រាស់ការអនុញ្ញាតទាំងនេះនៅលើ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> របស់អ្នក"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"អនុញ្ញាតឱ្យ &lt;strong&gt;<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; ឬ?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"វ៉ែនតា"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"ត្រូវការកម្មវិធីនេះ ដើម្បីគ្រប់គ្រង <xliff:g id="DEVICE_NAME">%1$s</xliff:g>។ <xliff:g id="APP_NAME">%2$s</xliff:g> នឹងត្រូវបានអនុញ្ញាតឱ្យ​ធ្វើអន្តរកម្មជាមួយ​ការជូនដំណឹងរបស់អ្នក និងចូលប្រើការអនុញ្ញាត​របស់ទូរសព្ទ, SMS, ទំនាក់ទំនង, មីក្រូហ្វូន និងឧបករណ៍នៅជិត​របស់អ្នក។"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"កម្មវិធីនេះ​នឹងត្រូវបានអនុញ្ញាតឱ្យ​ចូលប្រើការអនុញ្ញាតទាំងនេះ​នៅលើ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> របស់អ្នក"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ឧបករណ៍"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"កម្មវិធីនេះ​នឹងត្រូវបានអនុញ្ញាតឱ្យ​ចូលប្រើប្រាស់ការអនុញ្ញាតទាំងនេះ​នៅលើ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> របស់អ្នក"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"អនុញ្ញាតឱ្យ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ចូលប្រើព័ត៌មាននេះពីទូរសព្ទរបស់អ្នក"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"សេវាកម្មឆ្លងកាត់ឧបករណ៍"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងស្នើសុំការអនុញ្ញាតជំនួសឱ្យ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> របស់អ្នក ដើម្បីបញ្ចាំងកម្មវិធីរវាងឧបករណ៍របស់អ្នក"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"អនុញ្ញាតឱ្យ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ធ្វើសកម្មភាពនេះឬ?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងស្នើសុំ​ការអនុញ្ញាតជំនួសឱ្យ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> របស់អ្នក ដើម្បីចាក់ផ្សាយកម្មវិធី និងមុខងារប្រព័ន្ធផ្សេងទៀត​ទៅកាន់​ឧបករណ៍នៅជិត"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ឧបករណ៍"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"កម្មវិធីនេះនឹងអាចធ្វើសមកាលកម្មព័ត៌មាន ដូចជាឈ្មោះមនុស្សដែលហៅទូរសព្ទជាដើម ​រវាង <xliff:g id="DEVICE_NAME">%1$s</xliff:g> និងទូរសព្ទរបស់អ្នក"</string>
     <string name="summary_generic" msgid="1761976003668044801">"កម្មវិធីនេះនឹងអាច​ធ្វើសមកាលកម្មព័ត៌មាន ដូចជាឈ្មោះមនុស្សដែលហៅទូរសព្ទជាដើម​ រវាងឧបករណ៍ដែលបានជ្រើសរើស និងទូរសព្ទរបស់អ្នក"</string>
     <string name="consent_yes" msgid="8344487259618762872">"អនុញ្ញាត"</string>
     <string name="consent_no" msgid="2640796915611404382">"មិនអនុញ្ញាត"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"បោះបង់"</string>
     <string name="consent_back" msgid="2560683030046918882">"ថយក្រោយ"</string>
     <string name="permission_expand" msgid="893185038020887411">"ពង្រីក <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"បង្រួម <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-kn/strings.xml b/packages/CompanionDeviceManager/res/values-kn/strings.xml
index 1080b63..5f04cd3 100644
--- a/packages/CompanionDeviceManager/res/values-kn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kn/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ಅನ್ನು ಪ್ರವೇಶಿಸಲು &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಗೆ ಅನುಮತಿಸಬೇಕೇ?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ಅನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸಬೇಕೆ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ವೀಕ್ಷಿಸಿ"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ಮೂಲಕ ನಿರ್ವಹಿಸಬೇಕಾದ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
-    <string name="summary_watch" msgid="898569637110705523">"ನಿಮ್ಮ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ಅನ್ನು ನಿರ್ವಹಿಸಲು ಈ ಆ್ಯಪ್‌ನ ಅಗತ್ಯವಿದೆ. ಕರೆ ಮಾಡುವವರ ಹೆಸರು, ನಿಮ್ಮ ಅಧಿಸೂಚನೆಗಳೊಂದಿಗೆ ಸಂವಹನ ನಡೆಸಲು ಮತ್ತು ಫೋನ್, SMS, ಸಂಪರ್ಕಗಳು, ಕ್ಯಾಲೆಂಡರ್, ಕರೆಯ ಲಾಗ್‌ಗಳು ಮತ್ತು ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಗಳ ದೃಢೀಕರಣಗಳಂತಹ ಮಾಹಿತಿಯನ್ನು ಸಿಂಕ್ ಮಾಡಲು <xliff:g id="APP_NAME">%2$s</xliff:g> ಗೆ ಸಾಧ್ಯವಾಗುತ್ತದೆ."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"ಕರೆ ಮಾಡುವವರ ಹೆಸರಿನಂತಹ ಮಾಹಿತಿಯನ್ನು ಸಿಂಕ್ ಮಾಡಲು ಮತ್ತು ಈ ಅನುಮತಿಗಳನ್ನು ನಿಮ್ಮ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> ನಲ್ಲಿ ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಈ ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸಲಾಗುತ್ತದೆ"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಮೂಲಕ ನಿರ್ವಹಿಸಬೇಕಾದ ಸಾಧನವನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"ಸೆಟಪ್ ಮಾಡಲು <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ಅನ್ನು ಆರಿಸಿ"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"ಕರೆ ಮಾಡುವವರ ಹೆಸರಿನಂತಹ ಮಾಹಿತಿಯನ್ನು ಸಿಂಕ್ ಮಾಡಲು ಮತ್ತು ಈ ಅನುಮತಿಗಳನ್ನು ನಿಮ್ಮ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ನಲ್ಲಿ ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಈ ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸಲಾಗುತ್ತದೆ"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;? ನಿರ್ವಹಿಸಲು &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಗೆ ಅನುಮತಿಸಬೇಕೇ?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ಗ್ಲಾಸ್‌ಗಳು"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ಅನ್ನು ನಿರ್ವಹಿಸಲು ಈ ಆ್ಯಪ್‌ನ ಅಗತ್ಯವಿದೆ. <xliff:g id="APP_NAME">%2$s</xliff:g> ನಿಮ್ಮ ಅಧಿಸೂಚನೆಗಳೊಂದಿಗೆ ಸಂವಹನ ನಡೆಸಲು ಮತ್ತು ನಿಮ್ಮ ಫೋನ್, SMS, ಸಂಪರ್ಕಗಳು, ಮೈಕ್ರೊಫೋನ್ ಮತ್ತು ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಗಳ ಅನುಮತಿಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಅನುಮತಿಸಲಾಗುತ್ತದೆ."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"ನಿಮ್ಮ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> ನಲ್ಲಿ ಈ ಅನುಮತಿಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಈ ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸಲಾಗುತ್ತದೆ"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ಸಾಧನ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"ನಿಮ್ಮ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ನಲ್ಲಿ ಈ ಅನುಮತಿಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಈ ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸಲಾಗುತ್ತದೆ"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"ನಿಮ್ಮ ಫೋನ್ ಮೂಲಕ ಈ ಮಾಹಿತಿಯನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ಗೆ ಅನುಮತಿಸಿ"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ಕ್ರಾಸ್-ಡಿವೈಸ್ ಸೇವೆಗಳು"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"ನಿಮ್ಮ ಸಾಧನಗಳ ನಡುವೆ ಆ್ಯಪ್‌ಗಳನ್ನು ಸ್ಟ್ರೀಮ್ ಮಾಡಲು ನಿಮ್ಮ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ನ ಪರವಾಗಿ <xliff:g id="APP_NAME">%1$s</xliff:g> ಅನುಮತಿಯನ್ನು ವಿನಂತಿಸಿಕೊಳ್ಳುತ್ತಿದೆ"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"ಈ ಆ್ಯಕ್ಷನ್ ಅನ್ನು ತೆಗೆದುಕೊಳ್ಳಲು &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ಅನುಮತಿಸಬೇಕೇ?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಗಳಿಗೆ ಆ್ಯಪ್‌ಗಳು ಮತ್ತು ಇತರ ಸಿಸ್ಟಂ ಫೀಚರ್‌ಗಳನ್ನು ಸ್ಟ್ರೀಮ್ ಮಾಡಲು ನಿಮ್ಮ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> ರ ಪರವಾಗಿ <xliff:g id="APP_NAME">%1$s</xliff:g> ಅನುಮತಿಯನ್ನು ವಿನಂತಿಸುತ್ತಿದೆ"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ಸಾಧನ"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"ಮೊಬೈಲ್ ಫೋನ್ ಮತ್ತು <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ಸಾಧನದ ನಡುವೆ, ಕರೆ ಮಾಡುವವರ ಹೆಸರಿನಂತಹ ಮಾಹಿತಿಯನ್ನು ಸಿಂಕ್ ಮಾಡಲು ಈ ಆ್ಯಪ್‌ಗೆ ಸಾಧ್ಯವಾಗುತ್ತದೆ"</string>
     <string name="summary_generic" msgid="1761976003668044801">"ಮೊಬೈಲ್ ಫೋನ್ ಮತ್ತು ಆಯ್ಕೆಮಾಡಿದ ಸಾಧನದ ನಡುವೆ, ಕರೆ ಮಾಡುವವರ ಹೆಸರಿನಂತಹ ಮಾಹಿತಿಯನ್ನು ಸಿಂಕ್ ಮಾಡಲು ಈ ಆ್ಯಪ್‌ಗೆ ಸಾಧ್ಯವಾಗುತ್ತದೆ"</string>
     <string name="consent_yes" msgid="8344487259618762872">"ಅನುಮತಿಸಿ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ಅನುಮತಿಸಬೇಡಿ"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ರದ್ದುಮಾಡಿ"</string>
     <string name="consent_back" msgid="2560683030046918882">"ಹಿಂದೆ"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ಅನ್ನು ವಿಸ್ತರಿಸಿ"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ಅನ್ನು ಕುಗ್ಗಿಸಿ"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ko/strings.xml b/packages/CompanionDeviceManager/res/values-ko/strings.xml
index b131a2f..e4c56b2 100644
--- a/packages/CompanionDeviceManager/res/values-ko/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ko/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<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;에 액세스하도록 허용하시겠습니까?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<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;에 액세스하도록 허용하시겠습니까?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"시계"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;에서 관리할 <xliff:g id="PROFILE_NAME">%1$s</xliff:g>를 선택"</string>
-    <string name="summary_watch" msgid="898569637110705523">"이 앱은 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 기기를 관리하는 데 필요합니다. <xliff:g id="APP_NAME">%2$s</xliff:g>에서 정보(예: 발신자 이름)를 동기화하고, 알림과 상호작용하고, 전화, SMS, 연락처, 캘린더, 통화 기록 및 근처 기기에 액세스할 수 있게 됩니다."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"이 앱이 정보(예: 발신자 이름)를 동기화하고 <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>에서 이러한 권한에 액세스할 수 있게 됩니다."</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;에서 관리할 기기 선택"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"설정할 <xliff:g id="PROFILE_NAME">%1$s</xliff:g> 선택"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"이 앱이 정보(예: 발신자 이름)를 동기화하고 <xliff:g id="DEVICE_NAME">%1$s</xliff:g>에서 이러한 권한에 액세스할 수 있게 됩니다."</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<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;? 기기를 관리하도록 허용"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"안경"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"이 앱은 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 기기를 관리하는 데 필요합니다. <xliff:g id="APP_NAME">%2$s</xliff:g>에서 알림과 상호작용하고 내 전화, SMS, 연락처, 마이크, 근처 기기에 대한 권한을 갖게 됩니다."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"앱이 <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>에서 이러한 권한에 액세스할 수 있게 됩니다."</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"기기"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"앱이 <xliff:g id="DEVICE_NAME">%1$s</xliff:g>에서 이러한 권한에 액세스할 수 있게 됩니다."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;이 휴대전화의 이 정보에 액세스하도록 허용합니다."</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"교차 기기 서비스"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> 대신 기기 간에 앱을 스트리밍할 수 있는 권한을 요청하고 있습니다."</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; 기기가 이 작업을 수행하도록 허용하시겠습니까?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 <xliff:g id="DEVICE_NAME">%2$s</xliff:g> 대신 근처 기기로 앱 및 기타 시스템 기능을 스트리밍할 권한을 요청하고 있습니다."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"기기"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"이 앱에서 휴대전화와 <xliff:g id="DEVICE_NAME">%1$s</xliff:g> 간에 정보(예: 발신자 이름)를 동기화할 수 있게 됩니다."</string>
     <string name="summary_generic" msgid="1761976003668044801">"이 앱에서 휴대전화와 선택한 기기 간에 정보(예: 발신자 이름)를 동기화할 수 있게 됩니다."</string>
     <string name="consent_yes" msgid="8344487259618762872">"허용"</string>
     <string name="consent_no" msgid="2640796915611404382">"허용 안함"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"취소"</string>
     <string name="consent_back" msgid="2560683030046918882">"뒤로"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> 펼치기"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> 접기"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml
index 568a1739..d33cced 100644
--- a/packages/CompanionDeviceManager/res/values-ky/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<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; түзмөгүнө кирүүгө уруксат бересизби?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<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; түзмөгүнө кирүүгө уруксат бересизби?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"саат"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; тарабынан башкарылсын"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Бул колдонмо <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгүңүздү башкаруу үчүн керек. <xliff:g id="APP_NAME">%2$s</xliff:g> маалыматты шайкештирип, мисалы, билдирмелериңизди көрүп, телефонуңуз, SMS билдирүүлөр, байланыштар, жылнаама, чалуулар тизмеси жана жакын жердеги түзмөктөргө болгон уруксаттарды пайдалана алат."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Бул колдонмого маалыматты, мисалы, чалып жаткан адамдын аты-жөнүн шайкештирүүгө жана <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> түзмөгүңүздө төмөнкүлөрдү аткарууга уруксат берилет"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; аркылуу башкарыла турган түзмөктү тандаңыз"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Тууралоо үчүн <xliff:g id="PROFILE_NAME">%1$s</xliff:g> тандаңыз"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Бул колдонмого маалыматты, мисалы, чалып жаткан адамдын аты-жөнүн шайкештирүүгө жана <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгүңүздө төмөнкүлөрдү аткарууга уруксат берилет"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<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; түзмөгүн тескөөгө уруксат бересизби?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"көз айнектер"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Бул колдонмо <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгүн башкаруу үчүн керек. <xliff:g id="APP_NAME">%2$s</xliff:g> билдирмелериңизди көрүп, телефонуңуз, SMS билдирүүлөр, Байланыштар, Микрофон жана Жакын жердеги түзмөктөргө болгон уруксаттарды пайдалана алат."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Бул колдонмого <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> түзмөгүңүздө төмөнкүлөрдү аткарууга уруксат берилет"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"түзмөк"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Бул колдонмого <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгүңүздө төмөнкүлөрдү аткарууга уруксат берилет"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; колдонмосуна телефонуңуздагы ушул маалыматты көрүүгө уруксат бериңиз"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Түзмөктөр аралык кызматтар"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> түзмөгүңүздүн атынан түзмөктөрүңүздүн ортосунда колдонмолорду алып ойнотууга уруксат сурап жатат"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; түзмөгүнө бул аракетти аткарууга уруксат бересизби?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="DEVICE_NAME">%2$s</xliff:g> түзмөгүңүздүн атынан жакын жердеги түзмөктөрдө колдонмолорду жана системанын башка функцияларын алып ойнотууга уруксат сурап жатат"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"түзмөк"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Бул колдонмо маалыматты шайкештире алат, мисалы, чалып жаткан кишинин атын телефон жана <xliff:g id="DEVICE_NAME">%1$s</xliff:g> түзмөгү менен шайкештирет"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Бул колдонмо маалыматты шайкештире алат, мисалы, чалып жаткан кишинин атын телефон жана тандалган түзмөк менен шайкештирет"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Ооба"</string>
     <string name="consent_no" msgid="2640796915611404382">"Уруксат берилбесин"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Жокко чыгаруу"</string>
     <string name="consent_back" msgid="2560683030046918882">"Артка"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> жайып көрсөтүү"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> жыйыштыруу"</string>
diff --git a/packages/CompanionDeviceManager/res/values-lo/strings.xml b/packages/CompanionDeviceManager/res/values-lo/strings.xml
index 08ea7a9..0072e4b 100644
--- a/packages/CompanionDeviceManager/res/values-lo/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lo/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"ອະນຸຍາດ &lt;strong&gt;<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; ບໍ?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"ອະນຸຍາດໃຫ້ແອັບ &lt;strong&gt;<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; ບໍ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ໂມງ"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"ເລືອກ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ເພື່ອໃຫ້ຖືກຈັດການໂດຍ &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"ຕ້ອງໃຊ້ແອັບນີ້ເພື່ອຈັດການ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ຂອງທ່ານ. <xliff:g id="APP_NAME">%2$s</xliff:g> ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ຊິ້ງຂໍ້ມູນ ເຊັ່ນ: ຊື່ຂອງຄົນທີ່ໂທເຂົ້າ, ການໂຕ້ຕອບກັບການແຈ້ງເຕືອນຂອງທ່ານ ແລະ ສິດເຂົ້າເຖິງໂທລະສັບ, SMS, ລາຍຊື່ຜູ້ຕິດຕໍ່, ປະຕິທິນ, ບັນທຶກການໂທ ແລະ ອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງຂອງທ່ານ."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"ແອັບນີ້ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ຊິ້ງຂໍ້ມູນ, ເຊັ່ນ: ຊື່ຂອງຄົນທີ່ໂທເຂົ້າ ແລະ ສິດເຂົ້າເຖິງການອະນຸຍາດເຫຼົ່ານີ້ຢູ່ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> ຂອງທ່ານ"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"ເລືອກອຸປະກອນທີ່ຈະໃຫ້ມີການຈັດການໂດຍ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"ເລືອກ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ທີ່ຈະຕັ້ງຄ່າ"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"ແອັບນີ້ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ຊິ້ງຂໍ້ມູນ, ເຊັ່ນ: ຊື່ຂອງຄົນທີ່ໂທເຂົ້າ ແລະ ສິດເຂົ້າເຖິງການອະນຸຍາດເຫຼົ່ານີ້ຢູ່ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ຂອງທ່ານ"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"ອະນຸຍາດ &lt;strong&gt;<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; ບໍ?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ແວ່ນຕາ"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"ຕ້ອງໃຊ້ແອັບນີ້ເພື່ອຈັດການ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ໂຕ້ຕອບກັບການແຈ້ງເຕືອນຂອງທ່ານ ແລະ ການອະນຸຍາດສິດເຂົ້າເຖິງໂທລະສັບ, SMS, ລາຍຊື່ຜູ້ຕິດຕໍ່, ໄມໂຄຣໂຟນ ແລະ ອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງຂອງທ່ານ."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"ແອັບນີ້ຈະໄດ້ຮັບສິດເຂົ້າເຖິງການອະນຸຍາດເຫຼົ່ານີ້ຢູ່ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> ຂອງທ່ານ"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ອຸປະກອນ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"ແອັບນີ້ຈະໄດ້ຮັບສິດເຂົ້າເຖິງການອະນຸຍາດເຫຼົ່ານີ້ຢູ່ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ຂອງທ່ານ"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"ອະນຸຍາດ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ໃຫ້ເຂົ້າເຖິງຂໍ້ມູນນີ້ຈາກໂທລະສັບຂອງທ່ານໄດ້"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ບໍລິການຂ້າມອຸປະກອນ"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງຮ້ອງຂໍການອະນຸຍາດໃນນາມຂອງ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ເພື່ອສະຕຣີມແອັບລະຫວ່າງອຸປະກອນຕ່າງໆຂອງທ່ານ"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"ອະນຸຍາດ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ເພື່ອດຳເນີນຄຳສັ່ງນີ້ບໍ?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກໍາລັງຮ້ອງຂໍການອະນຸຍາດໃນນາມ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> ຂອງທ່ານເພື່ອສະຕຣີມແອັບ ແລະ ຄຸນສົມບັດລະບົບອື່ນໆໄປຫາອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງ"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ອຸປະກອນ"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"ແອັບນີ້ຈະສາມາດຊິ້ງຂໍ້ມູນ ເຊັ່ນ: ຊື່ຂອງຄົນທີ່ໂທເຂົ້າ, ລະຫວ່າງໂທລະສັບຂອງທ່ານ ແລະ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ໄດ້"</string>
     <string name="summary_generic" msgid="1761976003668044801">"ແອັບນີ້ຈະສາມາດຊິ້ງຂໍ້ມູນ ເຊັ່ນ: ຊື່ຂອງຄົນທີ່ໂທເຂົ້າ, ລະຫວ່າງໂທລະສັບຂອງທ່ານ ແລະ ອຸປະກອນທີ່ເລືອກໄວ້ໄດ້"</string>
     <string name="consent_yes" msgid="8344487259618762872">"ອະນຸຍາດ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ບໍ່ອະນຸຍາດ"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ຍົກເລີກ"</string>
     <string name="consent_back" msgid="2560683030046918882">"ກັບຄືນ"</string>
     <string name="permission_expand" msgid="893185038020887411">"ຂະຫຍາຍ <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"ຫຍໍ້ <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ລົງ"</string>
diff --git a/packages/CompanionDeviceManager/res/values-lt/strings.xml b/packages/CompanionDeviceManager/res/values-lt/strings.xml
index 0a800ee..65a266d 100644
--- a/packages/CompanionDeviceManager/res/values-lt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lt/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Leisti &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pasiekti &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Leisti programai &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pasiekti &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"laikrodį"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Jūsų <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, kurį valdys &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; (pasirinkite)"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Ši programa reikalinga norint tvarkyti jūsų įrenginį „<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“. Programai „<xliff:g id="APP_NAME">%2$s</xliff:g>“ bus leidžiama sinchronizuoti tam tikrą informaciją, pvz., skambinančio asmens vardą, sąveikauti su jūsų pranešimais ir pasiekti jūsų leidimus „Telefonas“, „SMS“, „Kontaktai“, „Kalendorius“, „Skambučių žurnalai“ ir „Įrenginiai netoliese."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Šiai programai bus leidžiama sinchronizuoti tam tikrą informaciją, pvz., skambinančio asmens vardą, ir pasiekti toliau nurodytus leidimus jūsų <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Įrenginio, kuris bus valdomas naudojant programą &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;, pasirinkimas"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Norimo nustatyti <xliff:g id="PROFILE_NAME">%1$s</xliff:g> pasirinkimas"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Šiai programai bus leidžiama sinchronizuoti tam tikrą informaciją, pvz., skambinančio asmens vardą, ir pasiekti toliau nurodytus <xliff:g id="DEVICE_NAME">%1$s</xliff:g> leidimus"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Leisti &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; valdyti &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"akiniai"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Ši programa reikalinga norint tvarkyti įrenginį „<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“. Programai „<xliff:g id="APP_NAME">%2$s</xliff:g>“ bus leidžiama sąveikauti su jūsų pranešimais ir pasiekti jūsų leidimus „Telefonas“, „SMS“, „Kontaktai“, „Mikrofonas“ ir „Įrenginiai netoliese“."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Šiai programai bus leidžiama pasiekti toliau nurodytus leidimus jūsų <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>."</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"įrenginio"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Šiai programai bus leidžiama pasiekti toliau nurodytus <xliff:g id="DEVICE_NAME">%1$s</xliff:g> leidimus."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Leisti &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pasiekti šią informaciją iš jūsų telefono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Pasl. keliuose įrenginiuose"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ prašo leidimo jūsų „<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>“ vardu, kad galėtų srautu perduoti programas iš vieno įrenginio į kitą"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Leisti &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; atlikti šį veiksmą?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ prašo leidimo jūsų „<xliff:g id="DEVICE_NAME">%2$s</xliff:g>“ vardu, kad galėtų srautu perduoti programas ir kitas sistemos funkcijas įrenginiams netoliese"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"įrenginys"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Ši programa galės sinchronizuoti tam tikrą informaciją, pvz., skambinančio asmens vardą, su jūsų telefonu ir įrenginiu „<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Ši programa galės sinchronizuoti tam tikrą informaciją, pvz., skambinančio asmens vardą, su jūsų telefonu ir pasirinktu įrenginiu"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Leisti"</string>
     <string name="consent_no" msgid="2640796915611404382">"Neleisti"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Atšaukti"</string>
     <string name="consent_back" msgid="2560683030046918882">"Atgal"</string>
     <string name="permission_expand" msgid="893185038020887411">"Išskleisti „<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>“"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Sutraukti „<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>“"</string>
diff --git a/packages/CompanionDeviceManager/res/values-lv/strings.xml b/packages/CompanionDeviceManager/res/values-lv/strings.xml
index 43dedbd..1d29edc 100644
--- a/packages/CompanionDeviceManager/res/values-lv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lv/strings.xml
@@ -17,15 +17,14 @@
 <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">"Palīgierīču pārzinis"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Vai atļaut lietotnei &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; piekļūt ierīcei &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Vai atļaut lietotnei &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; piekļūt lietotnei &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"pulkstenis"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Profila (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) izvēle, ko pārvaldīt lietotnē &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Šī lietotne ir nepieciešama jūsu ierīces (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) pārvaldībai. <xliff:g id="APP_NAME">%2$s</xliff:g> drīkstēs sinhronizēt informāciju (piemēram, zvanītāja vārdu), mijiedarboties ar jūsu paziņojumiem un piekļūt atļaujām Tālrunis, Īsziņas, Kontaktpersonas, Kalendārs, Zvanu žurnāli un Tuvumā esošas ierīces."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Šī lietotne drīkstēs sinhronizēt informāciju, piemēram, zvanītāja vārdu, un piekļūt norādītajām atļaujām jūsu <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>."</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Izvēlieties ierīci, ko pārvaldīt lietotnē &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Jāizvēlas <xliff:g id="PROFILE_NAME">%1$s</xliff:g> iestatīšanai"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Šī lietotne drīkstēs sinhronizēt informāciju, piemēram, zvanītāja vārdu, un piekļūt norādītajām atļaujām jūsu ierīcē (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)."</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Vai atļaut lietotnei &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; piekļūt ierīcei &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"brilles"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Šī lietotne ir nepieciešama šādas ierīces pārvaldībai: <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> drīkstēs mijiedarboties ar jūsu paziņojumiem un piekļūt atļaujām Tālrunis, Īsziņas, Kontaktpersonas, Mikrofons un Tuvumā esošas ierīces."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Šai lietotnei tiks sniegta piekļuve norādītajām atļaujām jūsu <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>."</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ierīce"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Šai lietotnei tiks sniegta piekļuve norādītajām atļaujām jūsu ierīcē (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Atļaut lietotnei &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; piekļūt šai informācijai no jūsu tālruņa"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Vairāku ierīču pakalpojumi"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> pieprasa atļauju straumēt lietotnes starp jūsu ierīcēm šīs ierīces vārdā: <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>."</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Vai atļaut ierīcei &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; veikt šo darbību?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> pieprasa atļauju tuvumā esošās ierīcēs straumēt lietotnes un citas sistēmas funkcijas šīs ierīces vārdā: <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ierīce"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Šī lietotne varēs sinhronizēt informāciju (piemēram, zvanītāja vārdu) starp jūsu tālruni un šo ierīci: <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Šī lietotne varēs sinhronizēt informāciju (piemēram, zvanītāja vārdu) starp jūsu tālruni un izvēlēto ierīci"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Atļaut"</string>
     <string name="consent_no" msgid="2640796915611404382">"Neatļaut"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Atcelt"</string>
     <string name="consent_back" msgid="2560683030046918882">"Atpakaļ"</string>
     <string name="permission_expand" msgid="893185038020887411">"Izvērst: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Sakļaut: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-mk/strings.xml b/packages/CompanionDeviceManager/res/values-mk/strings.xml
index 7d053a7..e72e960 100644
--- a/packages/CompanionDeviceManager/res/values-mk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mk/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Ќе дозволите &lt;strong&gt;<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;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Дозволувате апликацијата &lt;strong&gt;<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;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"часовник"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Изберете <xliff:g id="PROFILE_NAME">%1$s</xliff:g> со којшто ќе управува &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Апликацијава е потребна за управување со вашиот <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ќе може да ги синхронизира податоците како што се имињата на јавувачите, да остварува интеракција со известувањата и да пристапува до дозволите за „Телефон“, SMS, „Контакти“, „Календар“, „Евиденција на повици“ и „Уреди во близина“."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Оваа апликација ќе има дозвола да ги синхронизира податоците како што се имињата на јавувачите и да пристапува до следниве дозволи на вашиот <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Изберете уред со којшто ќе управува &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Изберете <xliff:g id="PROFILE_NAME">%1$s</xliff:g> за поставување"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Апликацијава ќе има дозвола да ги синхронизира податоците како што се имињата на јавувачите и да пристапува до следниве дозволи на вашиот <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Ќе дозволите &lt;strong&gt;<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;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"очила"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Апликацијава е потребна за управување со <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ќе може да остварува интеракција со известувањата и да пристапува до дозволите за „Телефон“, SMS, „Контакти“, „Микрофон“ и „Уреди во близина“."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Апликацијава ќе може да пристапува до овие дозволи на <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"уред"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Апликацијава ќе може да пристапува до овие дозволи на <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Овозможете &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да пристапува до овие податоци на телефонот"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Повеќенаменски услуги"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> бара дозвола во име на вашиот <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> за да стримува апликации помеѓу вашите уреди"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Ќе дозволите &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; да го преземе ова дејство?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> бара дозвола во име на вашиот <xliff:g id="DEVICE_NAME">%2$s</xliff:g> за да стримува апликации и други системски функции на уредите во близина"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"уред"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Оваа апликација ќе може да ги синхронизира податоците како што се имињата на јавувачите помеѓу вашиот телефон и <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Оваа апликација ќе може да ги синхронизира податоците како што се имињата на јавувачите помеѓу вашиот телефон и избраниот уред"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Дозволи"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дозволувај"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Откажи"</string>
     <string name="consent_back" msgid="2560683030046918882">"Назад"</string>
     <string name="permission_expand" msgid="893185038020887411">"Прошири <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Собери <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ml/strings.xml b/packages/CompanionDeviceManager/res/values-ml/strings.xml
index 21dcfa1..1703742 100644
--- a/packages/CompanionDeviceManager/res/values-ml/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ml/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ആക്‌സസ് ചെയ്യാൻ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; എന്നതിനെ അനുവദിക്കണോ?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ആക്‌സസ് ചെയ്യാൻ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; എന്ന ആപ്പിനെ അനുവദിക്കണോ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"വാച്ച്"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ഉപയോഗിച്ച് മാനേജ് ചെയ്യുന്നതിന് ഒരു <xliff:g id="PROFILE_NAME">%1$s</xliff:g> തിരഞ്ഞെടുക്കുക"</string>
-    <string name="summary_watch" msgid="898569637110705523">"നിങ്ങളുടെ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> മാനേജ് ചെയ്യാൻ ഈ ആപ്പ് ആവശ്യമാണ്. വിളിക്കുന്നയാളുടെ പേര് പോലുള്ള വിവരങ്ങൾ സമന്വയിപ്പിക്കുന്നതിനും നിങ്ങളുടെ അറിയിപ്പുകളുമായി സംവദിക്കാനും നിങ്ങളുടെ ഫോൺ, SMS, Contacts, Calendar, കോൾ ചരിത്രം, സമീപമുള്ള ഉപകരണങ്ങളുടെ അനുമതികൾ എന്നിവ ആക്‌സസ് ചെയ്യാനും <xliff:g id="APP_NAME">%2$s</xliff:g> ആപ്പിനെ അനുവദിക്കും."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"വിളിക്കുന്നയാളുടെ പേര് പോലുള്ള വിവരങ്ങൾ സമന്വയിപ്പിക്കാനും നിങ്ങളുടെ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> എന്നതിൽ ഈ അനുമതികൾ ആക്സസ് ചെയ്യാനും ഈ ആപ്പിനെ അനുവദിക്കും"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ഉപയോഗിച്ച് മാനേജ് ചെയ്യുന്നതിന് ഒരു ഉപകരണം തിരഞ്ഞെടുക്കുക"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"സജ്ജീകരിക്കാൻ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> തിരഞ്ഞെടുക്കുക"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"വിളിക്കുന്നയാളുടെ പേര് പോലുള്ള വിവരങ്ങൾ സമന്വയിപ്പിക്കാനും നിങ്ങളുടെ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> എന്നതിൽ ഈ അനുമതികൾ ആക്സസ് ചെയ്യാനും ഈ ആപ്പിനെ അനുവദിക്കും"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;? മാനേജ് ചെയ്യാൻ, &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; എന്നതിനെ അനുവദിക്കുക"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ഗ്ലാസുകൾ"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> മാനേജ് ചെയ്യാൻ ഈ ആപ്പ് ആവശ്യമാണ്. നിങ്ങളുടെ അറിയിപ്പുകളുമായി ഇടപഴകാനും ഫോൺ, SMS, കോൺടാക്റ്റുകൾ, മൈക്രോഫോൺ, സമീപമുള്ള ഉപകരണങ്ങളുടെ അനുമതികൾ എന്നിവ ആക്‌സസ് ചെയ്യാനും <xliff:g id="APP_NAME">%2$s</xliff:g> എന്നതിനെ അനുവദിക്കും."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"നിങ്ങളുടെ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> എന്നതിൽ ഇനിപ്പറയുന്ന അനുമതികൾ ആക്‌സസ് ചെയ്യാൻ ഈ ആപ്പിനെ അനുവദിക്കും"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ഉപകരണം"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"നിങ്ങളുടെ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> എന്നതിൽ ഇനിപ്പറയുന്ന അനുമതികൾ ആക്സസ് ചെയ്യാൻ ഈ ആപ്പിനെ അനുവദിക്കും"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"നിങ്ങളുടെ ഫോണിൽ നിന്ന് ഈ വിവരങ്ങൾ ആക്‌സസ് ചെയ്യാൻ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ആപ്പിനെ അനുവദിക്കുക"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ക്രോസ്-ഉപകരണ സേവനങ്ങൾ"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"നിങ്ങളുടെ ഉപകരണങ്ങളിൽ ഒന്നിൽ നിന്ന് അടുത്തതിലേക്ക് ആപ്പുകൾ സ്ട്രീം ചെയ്യാൻ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> എന്ന ഉപകരണത്തിന് വേണ്ടി <xliff:g id="APP_NAME">%1$s</xliff:g> എന്നത് അനുമതി അഭ്യർത്ഥിക്കുന്നു"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"ഈ പ്രവർത്തനം നടത്താൻ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; എന്നതിനെ അനുവദിക്കണോ?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"സമീപമുള്ള ഉപകരണങ്ങളിൽ ആപ്പുകളും മറ്റ് സിസ്റ്റം ഫീച്ചറുകളും സ്ട്രീം ചെയ്യാൻ നിങ്ങളുടെ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> എന്നതിന് വേണ്ടി <xliff:g id="APP_NAME">%1$s</xliff:g> അനുമതി അഭ്യർത്ഥിക്കുന്നു"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ഉപകരണം"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"വിളിക്കുന്നയാളുടെ പേര് പോലുള്ള വിവരങ്ങൾ നിങ്ങളുടെ ഫോണിനും <xliff:g id="DEVICE_NAME">%1$s</xliff:g> എന്നതിനും ഇടയിൽ സമന്വയിപ്പിക്കുന്നതിന് ഈ ആപ്പിന് കഴിയും"</string>
     <string name="summary_generic" msgid="1761976003668044801">"വിളിക്കുന്നയാളുടെ പേര് പോലുള്ള വിവരങ്ങൾ നിങ്ങളുടെ ഫോണിനും തിരഞ്ഞെടുത്ത ഉപകരണത്തിനും ഇടയിൽ സമന്വയിപ്പിക്കുന്നതിന് ഈ ആപ്പിന് കഴിയും"</string>
     <string name="consent_yes" msgid="8344487259618762872">"അനുവദിക്കുക"</string>
     <string name="consent_no" msgid="2640796915611404382">"അനുവദിക്കരുത്"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"റദ്ദാക്കുക"</string>
     <string name="consent_back" msgid="2560683030046918882">"മടങ്ങുക"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> വികസിപ്പിക്കുക"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ചുരുക്കുക"</string>
diff --git a/packages/CompanionDeviceManager/res/values-mn/strings.xml b/packages/CompanionDeviceManager/res/values-mn/strings.xml
index 0c31abf..613c9aa 100644
--- a/packages/CompanionDeviceManager/res/values-mn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mn/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;-д &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-д хандахыг зөвшөөрөх үү?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;-д &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; аппыг хандахыг зөвшөөрөх үү?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"цаг"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;-н удирдах<xliff:g id="PROFILE_NAME">%1$s</xliff:g>-г сонгоно уу"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Энэ апп таны <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-г удирдахад шаардлагатай. <xliff:g id="APP_NAME">%2$s</xliff:g>-д залгаж буй хүний нэр зэрэг мэдээллийг синк хийх, таны мэдэгдэлтэй харилцан үйлдэл хийх, Утас, SMS, Харилцагчид, Календарь, Дуудлагын жагсаалт болон Ойролцоох төхөөрөмжүүдийн зөвшөөрөлд хандахыг зөвшөөрнө."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Энэ аппад залгаж буй хүний нэр зэрэг мэдээллийг синк хийх болон таны <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>-н эдгээр зөвшөөрөлд хандахыг зөвшөөрнө"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-н удирдах төхөөрөмжийг сонгоно уу"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Тохируулахын тулд <xliff:g id="PROFILE_NAME">%1$s</xliff:g> сонгоно уу"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Энэ аппад залгаж буй хүний нэр зэрэг мэдээллийг синк хийх болон таны <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-н эдгээр зөвшөөрөлд хандахыг зөвшөөрнө"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<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;-г удирдахыг зөвшөөрөх үү?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"нүдний шил"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Энэ апп <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-г удирдахад шаардлагатай. <xliff:g id="APP_NAME">%2$s</xliff:g>-д таны мэдэгдэлтэй харилцан үйлдэл хийх, Утас, SMS, Харилцагчид, Микрофон болон Ойролцоох төхөөрөмжүүдийн зөвшөөрөлд хандахыг зөвшөөрнө."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Энэ апп таны <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>-н эдгээр зөвшөөрөлд хандах эрхтэй байх болно"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"төхөөрөмж"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Энэ апп таны <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-н эдгээр зөвшөөрөлд хандах эрхтэй байх болно"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-д таны утаснаас энэ мэдээлэлд хандахыг зөвшөөрнө үү"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Төхөөрөмж хоорондын үйлчилгээ"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Таны төхөөрөмжүүд хооронд апп дамжуулахын тулд <xliff:g id="APP_NAME">%1$s</xliff:g> таны <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>-н өмнөөс зөвшөөрөл хүсэж байна"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;-д энэ үйлдлийг хийхийг зөвшөөрөх үү?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> таны <xliff:g id="DEVICE_NAME">%2$s</xliff:g>-н өмнөөс аппууд болон системийн бусад онцлогийг ойролцоох төхөөрөмжүүд рүү дамжуулах зөвшөөрөл хүсэж байна"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"төхөөрөмж"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Энэ апп залгаж буй хүний нэр зэрэг мэдээллийг таны утас болон <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-н хооронд синк хийх боломжтой болно"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Энэ апп залгаж буй хүний нэр зэрэг мэдээллийг таны утас болон сонгосон төхөөрөмжийн хооронд синк хийх боломжтой болно"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Зөвшөөрөх"</string>
     <string name="consent_no" msgid="2640796915611404382">"Бүү зөвшөөр"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Цуцлах"</string>
     <string name="consent_back" msgid="2560683030046918882">"Буцах"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-г дэлгэх"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>-г хураах"</string>
diff --git a/packages/CompanionDeviceManager/res/values-mr/strings.xml b/packages/CompanionDeviceManager/res/values-mr/strings.xml
index b94cfac..50ab307 100644
--- a/packages/CompanionDeviceManager/res/values-mr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mr/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<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; अ‍ॅक्सेस करण्याची अनुमती द्यायची आहे का?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<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; अ‍ॅक्सेस करण्याची अनुमती द्यायची आहे का?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"वॉच"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; द्वारे व्यवस्थापित करण्यासाठी <xliff:g id="PROFILE_NAME">%1$s</xliff:g> निवडा"</string>
-    <string name="summary_watch" msgid="898569637110705523">"तुमचे <xliff:g id="DEVICE_NAME">%1$s</xliff:g> व्यवस्थापित करण्यासाठी हे ॲप आवश्यक आहे. <xliff:g id="APP_NAME">%2$s</xliff:g> ला कॉल करत असलेल्या एखाद्या व्यक्तीचे नाव यासारखी माहिती सिंक करण्याची, तुमच्या सूचनांसोबत संवाद साधण्याची आणि तुमचा फोन, एसएमएस, संपर्क, कॅलेंडर, कॉल लॉग व जवळपासच्या डिव्हाइसच्या परवानग्या अ‍ॅक्सेस करण्याची अनुमती मिळेल."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"या अ‍ॅपला कॉल करत असलेल्या एखाद्या व्यक्तीचे नाव यासारखी माहिती सिंक करण्याची आणि तुमच्या <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> वर पुढील परवानग्या अ‍ॅक्सेस करण्याची अनुमती दिली जाईल"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; द्वारे व्यवस्थापित करण्यासाठी डिव्हाइस निवडा"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"सेट करण्यासाठी <xliff:g id="PROFILE_NAME">%1$s</xliff:g> निवडा"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"या अ‍ॅपला कॉल करत असलेल्या एखाद्या व्यक्तीचे नाव यासारखी माहिती सिंक करण्याची आणि तुमच्या <xliff:g id="DEVICE_NAME">%1$s</xliff:g> वर या परवानग्या अ‍ॅक्सेस करण्याची अनुमती असेल"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<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; व्यवस्थापित करण्याची अनुमती द्यायची आहे?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"Glasses"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> व्यवस्थापित करण्यासाठी हे ॲप आवश्यक आहे. <xliff:g id="APP_NAME">%2$s</xliff:g> ला तुमच्या सूचनांसोबत संवाद साधण्याची आणि तुमचा फोन, एसएमएस, संपर्क, मायक्रोफोन व जवळपासच्या डिव्हाइसच्या परवानग्या अ‍ॅक्सेस करण्याची अनुमती मिळेल."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"या अ‍ॅपला तुमच्या <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> वर या परवानग्या अ‍ॅक्सेस करण्याची अनुमती दिली जाईल"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"डिव्हाइस"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"या अ‍ॅपला तुमच्या <xliff:g id="DEVICE_NAME">%1$s</xliff:g> वर या परवानग्या अ‍ॅक्सेस करण्याची अनुमती असेल"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ला ही माहिती तुमच्या फोनवरून अ‍ॅक्सेस करण्यासाठी अनुमती द्या"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"क्रॉस-डिव्हाइस सेवा"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"तुमच्या डिव्हाइसदरम्यान ॲप्स स्ट्रीम करण्यासाठी <xliff:g id="APP_NAME">%1$s</xliff:g> हे तुमच्या <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> च्या वतीने परवानगीची विनंती करत आहे"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ला ही कृती करण्याची अनुमती द्यायची आहे का?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> हे जवळपासच्या डिव्हाइसवर अ‍ॅप्स आणि इतर सिस्टीम वैशिष्‍ट्ये स्ट्रीम करण्यासाठी तुमच्या <xliff:g id="DEVICE_NAME">%2$s</xliff:g> च्या वतीने परवानगीची विनंती करा"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"डिव्हाइस"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"हे ॲप तुमचा फोन आणि <xliff:g id="DEVICE_NAME">%1$s</xliff:g> दरम्यान कॉल करत असलेल्‍या एखाद्या व्यक्तीचे नाव यासारखी माहिती सिंक करू शकेल"</string>
     <string name="summary_generic" msgid="1761976003668044801">"हे ॲप तुमचा फोन आणि निवडलेल्या डिव्‍हाइसदरम्यान कॉल करत असलेल्‍या एखाद्या व्यक्तीचे नाव यासारखी माहिती सिंक करू शकेल"</string>
     <string name="consent_yes" msgid="8344487259618762872">"अनुमती द्या"</string>
     <string name="consent_no" msgid="2640796915611404382">"अनुमती देऊ नका"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"रद्द करा"</string>
     <string name="consent_back" msgid="2560683030046918882">"मागे जा"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> चा विस्तार करा"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> कोलॅप्स करा"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ms/strings.xml b/packages/CompanionDeviceManager/res/values-ms/strings.xml
index 93745c8..ef110b6 100644
--- a/packages/CompanionDeviceManager/res/values-ms/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ms/strings.xml
@@ -17,15 +17,14 @@
 <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">"Pengurus Peranti Rakan"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Benarkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; untuk mengakses &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Benarkan apl &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengakses &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"jam tangan"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk diurus oleh &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Apl ini diperlukan untuk mengurus <xliff:g id="DEVICE_NAME">%1$s</xliff:g> anda. <xliff:g id="APP_NAME">%2$s</xliff:g> akan dibenarkan untuk menyegerakkan maklumat seperti nama individu yang memanggil, berinteraksi dengan pemberitahuan anda dan mengakses kebenaran Telefon, SMS, Kenalan, Kalendar, Log panggilan dan Peranti berdekatan anda."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Apl ini akan dibenarkan untuk menyegerakkan maklumat seperti nama seseorang yang membuat panggilan dan mengakses kebenaran ini pada <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> anda"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Pilih peranti untuk diurus oleh &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Pilih <xliff:g id="PROFILE_NAME">%1$s</xliff:g> untuk disediakan"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Apl ini akan dibenarkan untuk menyegerakkan maklumat seperti nama individu yang membuat panggilan dan mengakses kebenaran ini pada <xliff:g id="DEVICE_NAME">%1$s</xliff:g> anda"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Benarkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengurus &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"cermin mata"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Apl ini diperlukan untuk mengurus <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> akan dibenarkan untuk berinteraksi dengan pemberitahuan anda dan mengakses kebenaran Telefon, SMS, Kenalan, Mikrofon dan Peranti berdekatan anda."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Apl ini akan dibenarkan untuk mengakses kebenaran yang berikut pada <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> anda"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"peranti"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Apl ini akan dibenarkan untuk mengakses kebenaran yang berikut pada <xliff:g id="DEVICE_NAME">%1$s</xliff:g> anda"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Benarkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; mengakses maklumat ini daripada telefon anda"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Perkhidmatan silang peranti"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang meminta kebenaran bagi pihak <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> anda untuk menstrim apl antara peranti anda"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Benarkan &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; mengambil tindakan ini?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang meminta kebenaran bagi pihak <xliff:g id="DEVICE_NAME">%2$s</xliff:g> anda untuk menstrim apl dan ciri sistem yang lain pada peranti berdekatan"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"peranti"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Apl ini akan dapat menyegerakkan maklumat seperti nama individu yang memanggil, antara telefon anda dengan <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Apl ini akan dapat menyegerakkan maklumat seperti nama individu yang memanggil, antara telefon anda dengan peranti yang dipilih"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Benarkan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Jangan benarkan"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Batal"</string>
     <string name="consent_back" msgid="2560683030046918882">"Kembali"</string>
     <string name="permission_expand" msgid="893185038020887411">"Kembangkan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Kuncupkan <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-my/strings.xml b/packages/CompanionDeviceManager/res/values-my/strings.xml
index 3f298a0..57f2e23 100644
--- a/packages/CompanionDeviceManager/res/values-my/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-my/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<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;? သုံးခွင့်ပြုခြင်း"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ကို သုံးရန် &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; အက်ပ်ကို ခွင့်ပြုမလား။"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"နာရီ"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; က စီမံခန့်ခွဲရန် <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ကို ရွေးချယ်ပါ"</string>
-    <string name="summary_watch" msgid="898569637110705523">"သင်၏ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်လိုအပ်သည်။ ခေါ်ဆိုသူ၏အမည်ကဲ့သို့ အချက်အလက်ကို စင့်ခ်လုပ်ရန်၊ သင်၏ဖုန်း၊ SMS စာတိုစနစ်၊ အဆက်အသွယ်များ၊ ပြက္ခဒိန်၊ ခေါ်ဆိုမှတ်တမ်းနှင့် အနီးတစ်ဝိုက်ရှိ စက်များဆိုင်ရာ ခွင့်ပြုချက်များသုံးရန်၊ အကြောင်းကြားချက်များနှင့် ပြန်လှန်တုံ့ပြန်ရန် <xliff:g id="APP_NAME">%2$s</xliff:g> ကို ခွင့်ပြုမည်။"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"ခေါ်ဆိုသူ၏အမည်ကဲ့သို့ အချက်အလက်ကို စင့်ခ်လုပ်ရန်နှင့် သင့် <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> တွင် ၎င်းခွင့်ပြုချက်များရယူရန် ဤအက်ပ်ကိုခွင့်ပြုမည်"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; က စီမံခန့်ခွဲရန် စက်တစ်ခုကို ရွေးပါ"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"စနစ်ထည့်သွင်းရန် <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ကို ရွေးပါ"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"ခေါ်ဆိုသူ၏အမည်ကဲ့သို့ အချက်အလက်ကို စင့်ခ်လုပ်ရန်နှင့် သင့် <xliff:g id="DEVICE_NAME">%1$s</xliff:g> တွင် ၎င်းခွင့်ပြုချက်များရယူရန် ဤအက်ပ်ကိုခွင့်ပြုမည်"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; ကို &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; အား စီမံခွင့်ပြုမလား။"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"မျက်မှန်"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်လိုအပ်သည်။ သင်၏ဖုန်း၊ SMS စာတိုစနစ်၊ အဆက်အသွယ်များ၊ မိုက်ခရိုဖုန်းနှင့် အနီးတစ်ဝိုက်ရှိ စက်များဆိုင်ရာ ခွင့်ပြုချက်များသုံးရန်၊ အကြောင်းကြားချက်များနှင့် ပြန်လှန်တုံ့ပြန်ရန် <xliff:g id="APP_NAME">%2$s</xliff:g> ကို ခွင့်ပြုမည်။"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"သင့် <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> တွင် ၎င်းခွင့်ပြုချက်များရယူရန် ဤအက်ပ်ကိုခွင့်ပြုမည်"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"စက်"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"သင့် <xliff:g id="DEVICE_NAME">%1$s</xliff:g> တွင် ၎င်းခွင့်ပြုချက်များရယူရန် ဤအက်ပ်ကိုခွင့်ပြုမည်"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ကို သင့်ဖုန်းမှ ဤအချက်အလက် သုံးခွင့်ပြုမည်"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"စက်များကြားသုံး ဝန်ဆောင်မှုများ"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် သင်၏စက်များအကြား အက်ပ်များတိုက်ရိုက်လွှင့်ရန် <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ကိုယ်စား ခွင့်ပြုချက်တောင်းနေသည်"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ကို ဤသို့လုပ်ဆောင်ခွင့်ပြုမလား။"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် အနီးတစ်ဝိုက်ရှိ အက်ပ်များနှင့် အခြားစနစ်အင်္ဂါရပ်များကို တိုက်ရိုက်ဖွင့်ရန် သင့် <xliff:g id="DEVICE_NAME">%2$s</xliff:g> ကိုယ်စား ခွင့်ပြုချက်တောင်းနေသည်"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"စက်"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"ဤအက်ပ်သည် သင့်ဖုန်းနှင့် <xliff:g id="DEVICE_NAME">%1$s</xliff:g> အကြား ခေါ်ဆိုသူ၏အမည်ကဲ့သို့ အချက်အလက်ကို စင့်ခ်လုပ်နိုင်ပါမည်"</string>
     <string name="summary_generic" msgid="1761976003668044801">"ဤအက်ပ်သည် သင့်ဖုန်းနှင့် ရွေးထားသောစက်အကြား ခေါ်ဆိုသူ၏အမည်ကဲ့သို့ အချက်အလက်ကို စင့်ခ်လုပ်နိုင်ပါမည်"</string>
     <string name="consent_yes" msgid="8344487259618762872">"ခွင့်ပြုရန်"</string>
     <string name="consent_no" msgid="2640796915611404382">"ခွင့်မပြုပါ"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"မလုပ်တော့"</string>
     <string name="consent_back" msgid="2560683030046918882">"နောက်သို့"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ကို ပိုပြရန်"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ကို လျှော့ပြရန်"</string>
diff --git a/packages/CompanionDeviceManager/res/values-nb/strings.xml b/packages/CompanionDeviceManager/res/values-nb/strings.xml
index 2c794d9..083ec99 100644
--- a/packages/CompanionDeviceManager/res/values-nb/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nb/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Gi &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tilgang til &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Vil du gi &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;-appen tilgang til &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"klokke"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Velg <xliff:g id="PROFILE_NAME">%1$s</xliff:g> som skal administreres av &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Denne appen kreves for å administrere <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tillatelse til å synkronisere informasjon som navnet til noen som ringer, og samhandle med varslene dine, og får tilgang til tillatelsene for telefon, SMS, kontakter, kalender, samtalelogger og enheter i nærheten."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Denne appen får tillatelse til å synkronisere informasjon som navnet til noen som ringer, og har disse tillatelsene på din/ditt <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Velg en enhet som skal administreres av &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Velg <xliff:g id="PROFILE_NAME">%1$s</xliff:g> som skal konfigureres"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Denne appen får tillatelse til å synkronisere informasjon som navnet til noen som ringer, og har disse tillatelsene på <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Vil du la &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; administrere &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"briller"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Denne appen kreves for å administrere <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tilgang til varslene dine og får tillatelsene for telefon, SMS, kontakter, mikrofon og enheter i nærheten."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Denne appen får disse tillatelsene på din/ditt <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"enheten"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Denne appen får disse tillatelsene på <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Gi &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tilgang til denne informasjonen fra telefonen din"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Tjenester på flere enheter"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> ber om tillatelse til å strømme apper mellom enhetene dine, på vegne av <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Vil du la &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; gjøre dette?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> ber om tillatelse på vegne av <xliff:g id="DEVICE_NAME">%2$s</xliff:g> til å strømme apper og andre systemfunksjoner til enheter i nærheten"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"enhet"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Denne appen kan synkronisere informasjon som navnet til noen som ringer, mellom telefonen og <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Denne appen kan synkronisere informasjon som navnet til noen som ringer, mellom telefonen og den valgte enheten"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Tillat"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ikke tillat"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Avbryt"</string>
     <string name="consent_back" msgid="2560683030046918882">"Tilbake"</string>
     <string name="permission_expand" msgid="893185038020887411">"Vis <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Skjul <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ne/strings.xml b/packages/CompanionDeviceManager/res/values-ne/strings.xml
index 1f4506a..7a1df5f 100644
--- a/packages/CompanionDeviceManager/res/values-ne/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ne/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<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; प्रयोग गर्ने अनुमति दिने हो?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<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; प्रयोग गर्ने अनुमति दिने हो?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"घडी"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"आफूले &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; प्रयोग गरी व्यवस्थापन गर्न चाहेको <xliff:g id="PROFILE_NAME">%1$s</xliff:g> चयन गर्नुहोस्"</string>
-    <string name="summary_watch" msgid="898569637110705523">"तपाईंको <xliff:g id="DEVICE_NAME">%1$s</xliff:g> व्यवस्थापन गर्न यो एप चाहिन्छ। <xliff:g id="APP_NAME">%2$s</xliff:g> लाई कल गर्ने व्यक्तिको नाम जस्ता जानकारी सिंक गर्ने, तपाईंका सूचना हेर्ने र फोन, SMS, कन्ट्याक्ट, पात्रो, कल लग तथा नजिकैका डिभाइससम्बन्धी अनुमतिहरू हेर्ने तथा प्रयोग गर्ने अनुमति दिइने छ।"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"तपाईंको <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> मा यो एपलाई कल गर्ने व्यक्तिको नाम जस्ता जानकारी सिंक गर्ने र यी कुराहरू गर्ने अनुमति दिइने छ"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"आफूले &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; प्रयोग गरी व्यवस्थापन गर्न चाहेको डिभाइस चयन गर्नुहोस्"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"सेट अप गर्नका लागि <xliff:g id="PROFILE_NAME">%1$s</xliff:g> छनौट गर्नुहोस्"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"तपाईंको <xliff:g id="DEVICE_NAME">%1$s</xliff:g> मा यो एपलाई कल गर्ने व्यक्तिको नाम जस्ता जानकारी सिंक गर्ने र यी कुराहरू गर्ने अनुमति दिइने छ"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<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; व्यवस्थापन गर्ने अनुमति दिने हो?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"चस्मा"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> व्यवस्थापन गर्न यो एप चाहिन्छ। <xliff:g id="APP_NAME">%2$s</xliff:g> लाई तपाईंका सूचना हेर्ने र फोन, SMS, कन्ट्याक्ट, माइक्रोफोन तथा नजिकैका डिभाइससम्बन्धी अनुमतिहरू हेर्ने तथा प्रयोग गर्ने अनुमति दिइने छ।"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"तपाईंको <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> मा यो एपलाई निम्न अनुमति दिइने छ:"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"डिभाइस"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"तपाईंको <xliff:g id="DEVICE_NAME">%1$s</xliff:g> मा यो एपलाई निम्न अनुमति दिइने छ:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; लाई तपाईंको फोनमा भएको यो जानकारी हेर्ने तथा प्रयोग गर्ने अनुमति दिनुहोस्"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"क्रस-डिभाइस सेवाहरू"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> तपाईंको डिभाइस <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> को तर्फबाट तपाईंका कुनै एउटा डिभाइसबाट अर्को डिभाइसमा एप स्ट्रिम गर्ने अनुमति माग्दै छ"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; लाई यो कार्य गर्ने अनुमति दिने हो?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> तपाईंको डिभाइस <xliff:g id="DEVICE_NAME">%2$s</xliff:g> को तर्फबाट नजिकैका डिभाइसहरूमा एप र सिस्टमका अन्य सुविधाहरू स्ट्रिम गर्ने अनुमति माग्दै छ"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"यन्त्र"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"यो एपले तपाईंको फोन र तपाईंले छनौट गर्ने <xliff:g id="DEVICE_NAME">%1$s</xliff:g> का बिचमा कल गर्ने व्यक्तिको नाम जस्ता जानकारी सिंक गर्न सक्ने छ।"</string>
     <string name="summary_generic" msgid="1761976003668044801">"यो एपले तपाईंको फोन र तपाईंले छनौट गर्ने डिभाइसका बिचमा कल गर्ने व्यक्तिको नाम जस्ता जानकारी सिंक गर्न सक्ने छ।"</string>
     <string name="consent_yes" msgid="8344487259618762872">"अनुमति दिनुहोस्"</string>
     <string name="consent_no" msgid="2640796915611404382">"अनुमति नदिनुहोस्"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"रद्द गर्नुहोस्"</string>
     <string name="consent_back" msgid="2560683030046918882">"पछाडि"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> एक्स्पान्ड गर्नुहोस्"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> कोल्याप्स गर्नुहोस्"</string>
diff --git a/packages/CompanionDeviceManager/res/values-nl/strings.xml b/packages/CompanionDeviceManager/res/values-nl/strings.xml
index 7c79cb4..71d14e7 100644
--- a/packages/CompanionDeviceManager/res/values-nl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nl/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang geven tot &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"De app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang geven tot &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"smartwatch"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Een <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiezen om te beheren met &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Deze app is vereist om je <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="APP_NAME">%2$s</xliff:g> mag informatie (zoals de naam van iemand die belt) synchroniseren, mag interactie hebben met je meldingen en krijgt toegang tot de rechten Telefoon, Sms, Contacten, Agenda, Gesprekslijsten en Apparaten in de buurt."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Deze app kan informatie synchroniseren (zoals de naam van iemand die belt) en krijgt toegang tot deze rechten op je <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Een apparaat kiezen om te beheren met &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Een <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiezen om in te stellen"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Deze app kan informatie synchroniseren (zoals de naam van iemand die belt) en krijgt toegang tot deze rechten op je <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toestaan &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; te beheren?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"brillen"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Deze app is nodig om <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="APP_NAME">%2$s</xliff:g> mag interactie hebben met je meldingen en krijgt toegang tot de rechten voor Telefoon, Sms, Contacten, Microfoon en Apparaten in de buurt."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Deze app krijgt toegang tot deze rechten op je <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"apparaat"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Deze app krijgt toegang tot deze rechten op je <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang geven tot deze informatie op je telefoon"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device-services"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> vraagt namens jouw <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> toestemming om apps te streamen tussen je apparaten"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Toestaan dat &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; deze actie uitvoert?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> vraagt namens je <xliff:g id="DEVICE_NAME">%2$s</xliff:g> toestemming om apps en andere systeemfuncties naar apparaten in de buurt te streamen"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"apparaat"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Deze app kan informatie, zoals de naam van iemand die belt, synchroniseren tussen je telefoon en <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Deze app kan informatie, zoals de naam van iemand die belt, synchroniseren tussen je telefoon en het gekozen apparaat"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Toestaan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Niet toestaan"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Annuleren"</string>
     <string name="consent_back" msgid="2560683030046918882">"Terug"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> uitvouwen"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> samenvouwen"</string>
diff --git a/packages/CompanionDeviceManager/res/values-or/strings.xml b/packages/CompanionDeviceManager/res/values-or/strings.xml
index dbff4e2..a058374 100644
--- a/packages/CompanionDeviceManager/res/values-or/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-or/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;କୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;କୁ ଅନୁମତି ଦେବେ?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;କୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ଆପକୁ ଅନୁମତି ଦେବେ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ୱାଚ୍"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ଦ୍ୱାରା ପରିଚାଳିତ ହେବା ପାଇଁ ଏକ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>କୁ ବାଛନ୍ତୁ"</string>
-    <string name="summary_watch" msgid="898569637110705523">"ଆପଣଙ୍କ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>କୁ ପରିଚାଳନା କରିବା ପାଇଁ ଏହି ଆପ ଆବଶ୍ୟକ। କଲ କରୁଥିବା ଯେ କୌଣସି ବ୍ୟକ୍ତିଙ୍କ ନାମ ପରି ସୂଚନା ସିଙ୍କ କରିବା, ଆପଣଙ୍କ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ସହ ଇଣ୍ଟରାକ୍ଟ କରିବା ଏବଂ ଆପଣଙ୍କର ଫୋନ, SMS, କଣ୍ଟାକ୍ଟ, କେଲେଣ୍ଡର, କଲ ଲଗ ଓ ଆଖପାଖର ଡିଭାଇସ ଅନୁମତିଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%2$s</xliff:g>କୁ ଅନୁମତି ଦିଆଯିବ।"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"କଲ କରୁଥିବା ଯେ କୌଣସି ବ୍ୟକ୍ତିଙ୍କ ନାମ ପରି ସୂଚନା ସିଙ୍କ କରିବାକୁ ଏବଂ ଆପଣଙ୍କ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>ରେ ଏହି ଅନୁମତିଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦିଆଯିବ"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ଦ୍ୱାରା ପରିଚାଳିତ ହେବା ପାଇଁ ଏକ ଡିଭାଇସ ବାଛନ୍ତୁ"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"ସେଟ ଅପ କରିବାକୁ ଏକ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ବାଛନ୍ତୁ"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"କଲ କରୁଥିବା ଯେ କୌଣସି ବ୍ୟକ୍ତିଙ୍କ ନାମ ପରି ସୂଚନା ସିଙ୍କ କରିବାକୁ ଏବଂ ଆପଣଙ୍କ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ରେ ଏହି ଅନୁମତିଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦିଆଯିବ"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;କୁ ପରିଚାଳନା କରିବା ପାଇଁ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;କୁ ଅନୁମତି ଦେବେ?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ଚଷମା"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>କୁ ପରିଚାଳନା କରିବା ପାଇଁ ଏହି ଆପ ଆବଶ୍ୟକ। ଆପଣଙ୍କ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ସହ ଇଣ୍ଟରାକ୍ଟ କରିବା ଏବଂ ଆପଣଙ୍କର ଫୋନ, SMS, କଣ୍ଟାକ୍ଟ, ମାଇକ୍ରୋଫୋନ ଓ ଆଖପାଖର ଡିଭାଇସ ଅନୁମତିଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%2$s</xliff:g>କୁ ଅନୁମତି ଦିଆଯିବ।"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"ଆପଣଙ୍କ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>ରେ ଏହି ଅନୁମତିଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦିଆଯିବ"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ଡିଭାଇସ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"ଆପଣଙ୍କ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>ରେ ଏହି ଅନୁମତିଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦିଆଯିବ"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"ଆପଣଙ୍କ ଫୋନରୁ ଏହି ସୂଚନାକୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;କୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"କ୍ରସ-ଡିଭାଇସ ସେବାଗୁଡ଼ିକ"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"ଆପଣଙ୍କ ଡିଭାଇସଗୁଡ଼ିକ ମଧ୍ୟରେ ଆପ୍ସକୁ ଷ୍ଟ୍ରିମ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଆପଣଙ୍କର <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ତରଫରୁ ଅନୁମତି ପାଇଁ ଅନୁରୋଧ କରୁଛି"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"ଏହି ପଦକ୍ଷେପ ନେବା ପାଇଁ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;କୁ ଅନୁମତି ଦେବେ?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"ଆଖପାଖର ଡିଭାଇସଗୁଡ଼ିକରେ ଆପ୍ସ ଏବଂ ଅନ୍ୟ ସିଷ୍ଟମ ଫିଚରଗୁଡ଼ିକୁ ଷ୍ଟ୍ରିମ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଆପଣଙ୍କ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> ତରଫରୁ ଅନୁମତି ପାଇଁ ଅନୁରୋଧ କରୁଛି"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ଡିଭାଇସ୍"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"ଆପଣଙ୍କ ଫୋନ ଏବଂ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ମଧ୍ୟରେ, କଲ କରୁଥିବା ଯେ କୌଣସି ବ୍ୟକ୍ତିଙ୍କ ନାମ ପରି ସୂଚନା ସିଙ୍କ କରିବାକୁ ଏହି ଆପ ସକ୍ଷମ ହେବ"</string>
     <string name="summary_generic" msgid="1761976003668044801">"ଆପଣଙ୍କ ଫୋନ ଏବଂ ବଛାଯାଇଥିବା ଡିଭାଇସ ମଧ୍ୟରେ, କଲ କରୁଥିବା ଯେ କୌଣସି ବ୍ୟକ୍ତିଙ୍କ ନାମ ପରି ସୂଚନା ସିଙ୍କ କରିବାକୁ ଏହି ଆପ ସକ୍ଷମ ହେବ"</string>
     <string name="consent_yes" msgid="8344487259618762872">"ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ଅନୁମତି ଦିଅନ୍ତୁ ନାହିଁ"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ବାତିଲ କରନ୍ତୁ"</string>
     <string name="consent_back" msgid="2560683030046918882">"ପଛକୁ ଫେରନ୍ତୁ"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>କୁ ବିସ୍ତାର କରନ୍ତୁ"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>କୁ ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
diff --git a/packages/CompanionDeviceManager/res/values-pa/strings.xml b/packages/CompanionDeviceManager/res/values-pa/strings.xml
index 91b5264..984ec48 100644
--- a/packages/CompanionDeviceManager/res/values-pa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pa/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"ਕੀ &lt;strong&gt;<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; ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"ਕੀ &lt;strong&gt;<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; ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ਸਮਾਰਟ-ਵਾਚ"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ਵੱਲੋਂ ਪ੍ਰਬੰਧਿਤ ਕੀਤੇ ਜਾਣ ਲਈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ਚੁਣੋ"</string>
-    <string name="summary_watch" msgid="898569637110705523">"ਇਹ ਐਪ ਤੁਹਾਡੇ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਲਈ ਲੋੜੀਂਦੀ ਹੈ। <xliff:g id="APP_NAME">%2$s</xliff:g> ਨੂੰ ਕਾਲਰ ਦੇ ਨਾਮ ਵਰਗੀ ਜਾਣਕਾਰੀ ਨੂੰ ਸਿੰਕ ਕਰਨ, ਤੁਹਾਡੀਆਂ ਸੂਚਨਾਵਾਂ ਨਾਲ ਅੰਤਰਕਿਰਿਆ ਕਰਨ ਅਤੇ ਤੁਹਾਡੇ ਫ਼ੋਨ, SMS, ਸੰਪਰਕਾਂ, ਕੈਲੰਡਰ, ਕਾਲ ਲੌਗਾਂ ਅਤੇ ਨਜ਼ਦੀਕੀ ਡੀਵਾਈਸਾਂ ਸੰਬੰਧੀ ਇਜਾਜ਼ਤਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ।"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"ਇਸ ਐਪ ਨੂੰ ਤੁਹਾਡੇ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> \'ਤੇ ਕਾਲਰ ਦੇ ਨਾਮ ਵਰਗੀ ਜਾਣਕਾਰੀ ਨੂੰ ਸਿੰਕ ਕਰਨ ਅਤੇ ਇਨ੍ਹਾਂ ਇਜਾਜ਼ਤਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ਵੱਲੋਂ ਪ੍ਰਬੰਧਿਤ ਕੀਤੇ ਜਾਣ ਲਈ ਕੋਈ ਡੀਵਾਈਸ ਚੁਣੋ"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"ਸੈੱਟਅੱਪ ਕਰਨ ਲਈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ਚੁਣੋ"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"ਇਸ ਐਪ ਨੂੰ ਤੁਹਾਡੇ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> \'ਤੇ ਕਾਲਰ ਦੇ ਨਾਮ ਵਰਗੀ ਜਾਣਕਾਰੀ ਨੂੰ ਸਿੰਕ ਕਰਨ ਅਤੇ ਇਨ੍ਹਾਂ ਇਜਾਜ਼ਤਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"ਕੀ &lt;strong&gt;<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; ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ਐਨਕਾਂ"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"ਇਹ ਐਪ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਲਈ ਲੋੜੀਂਦੀ ਹੈ। <xliff:g id="APP_NAME">%2$s</xliff:g> ਨੂੰ ਤੁਹਾਡੀਆਂ ਸੂਚਨਾਵਾਂ ਨਾਲ ਅੰਤਰਕਿਰਿਆ ਕਰਨ ਅਤੇ ਤੁਹਾਡੇ ਫ਼ੋਨ, SMS, ਸੰਪਰਕਾਂ, ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਅਤੇ ਨਜ਼ਦੀਕੀ ਡੀਵਾਈਸਾਂ ਸੰਬੰਧੀ ਇਜਾਜ਼ਤਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ।"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"ਇਸ ਐਪ ਨੂੰ ਤੁਹਾਡੇ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> \'ਤੇ ਇਨ੍ਹਾਂ ਇਜਾਜ਼ਤਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"ਡੀਵਾਈਸ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"ਇਸ ਐਪ ਨੂੰ ਤੁਹਾਡੇ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> \'ਤੇ ਇਨ੍ਹਾਂ ਇਜਾਜ਼ਤਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ਨੂੰ ਤੁਹਾਡੇ ਫ਼ੋਨ ਤੋਂ ਇਸ ਜਾਣਕਾਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"ਕ੍ਰਾਸ-ਡੀਵਾਈਸ ਸੇਵਾਵਾਂ"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਤੁਹਾਡੇ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> ਦੀ ਤਰਫ਼ੋਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸਾਂ ਵਿਚਕਾਰ ਐਪਾਂ ਨੂੰ ਸਟ੍ਰੀਮ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਮੰਗ ਰਹੀ ਹੈ"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"ਕੀ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ਨੂੰ ਇਹ ਕਾਰਵਾਈ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਤੁਹਾਡੇ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> ਦੀ ਤਰਫ਼ੋਂ ਨਜ਼ਦੀਕੀ ਡੀਵਾਈਸਾਂ \'ਤੇ ਐਪਾਂ ਅਤੇ ਹੋਰ ਸਿਸਟਮ ਸੰਬੰਧੀ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਸਟ੍ਰੀਮ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਮੰਗ ਰਹੀ ਹੈ"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"ਡੀਵਾਈਸ"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"ਇਹ ਐਪ ਤੁਹਾਡੇ ਫ਼ੋਨ ਅਤੇ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ਵਿਚਕਾਰ ਕਾਲਰ ਦੇ ਨਾਮ ਵਰਗੀ ਜਾਣਕਾਰੀ ਨੂੰ ਸਿੰਕ ਕਰ ਸਕੇਗੀ"</string>
     <string name="summary_generic" msgid="1761976003668044801">"ਇਹ ਐਪ ਤੁਹਾਡੇ ਫ਼ੋਨ ਅਤੇ ਚੁਣੇ ਗਏ ਡੀਵਾਈਸ ਵਿਚਕਾਰ ਕਾਲਰ ਦੇ ਨਾਮ ਵਰਗੀ ਜਾਣਕਾਰੀ ਨੂੰ ਸਿੰਕ ਕਰ ਸਕੇਗੀ"</string>
     <string name="consent_yes" msgid="8344487259618762872">"ਆਗਿਆ ਦਿਓ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ਆਗਿਆ ਨਾ ਦਿਓ"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ਰੱਦ ਕਰੋ"</string>
     <string name="consent_back" msgid="2560683030046918882">"ਪਿੱਛੇ"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ਨੂੰ ਸਮੇਟੋ"</string>
diff --git a/packages/CompanionDeviceManager/res/values-pl/strings.xml b/packages/CompanionDeviceManager/res/values-pl/strings.xml
index 1432a45..7ca172e 100644
--- a/packages/CompanionDeviceManager/res/values-pl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pl/strings.xml
@@ -17,15 +17,14 @@
 <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">"Menedżer urządzeń towarzyszących"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Zezwolić na dostęp aplikacji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; do tego urządzenia (&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;)?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Zezwolić aplikacji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na dostęp do urządzenia &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"zegarek"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Wybierz <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, którym ma zarządzać aplikacja &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Ta aplikacja jest niezbędna do zarządzania urządzeniem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacja <xliff:g id="APP_NAME">%2$s</xliff:g> będzie mogła synchronizować informacje takie jak nazwa osoby dzwoniącej, korzystać z powiadomień oraz uprawnień dotyczących telefonu, SMS-ów, kontaktów, kalendarza, rejestrów połączeń i Urządzeń w pobliżu."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Aplikacja będzie mogła synchronizować informacje takie jak nazwa dzwoniącego oraz korzystać z tych uprawnień na Twoim urządzeniu (<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>)"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Wybierz urządzenie, którym ma zarządzać aplikacja &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Wybierz profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, aby go skonfigurować"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Aplikacja będzie mogła synchronizować informacje takie jak nazwa dzwoniącego oraz korzystać z tych uprawnień na Twoim urządzeniu (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Zezwolić na dostęp aplikacji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; do urządzenia &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"Okulary"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Ta aplikacja jest niezbędna do zarządzania urządzeniem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Aplikacja <xliff:g id="APP_NAME">%2$s</xliff:g> będzie mogła wchodzić w interakcję z powiadomieniami i korzystać z uprawnień dotyczących telefonu, SMS-ów, kontaktów, mikrofonu oraz urządzeń w pobliżu."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Aplikacja będzie miała dostęp do tych uprawnień na Twoim urządzeniu (<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>)"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"urządzenie"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Aplikacja będzie miała dostęp do tych uprawnień na Twoim urządzeniu (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Zezwól urządzeniu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na dostęp do tych informacji na Twoim telefonie"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Usługi na innym urządzeniu"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> prosi w imieniu urządzenia <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> o uprawnienia dotyczące strumieniowego odtwarzania treści z aplikacji na innym urządzeniu"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Zezwolić urządzeniu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; na wykonanie tego działania?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> prosi w imieniu urządzenia <xliff:g id="DEVICE_NAME">%2$s</xliff:g> o uprawnienia do strumieniowego odtwarzania treści i innych funkcji systemowych na urządzeniach w pobliżu"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"urządzenie"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Ta aplikacja może synchronizować informacje takie jak nazwa osoby dzwoniącej między Twoim telefonem i urządzeniem <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Ta aplikacja może synchronizować informacje takie jak nazwa osoby dzwoniącej między Twoim telefonem i wybranym urządzeniem"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Zezwól"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nie zezwalaj"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Anuluj"</string>
     <string name="consent_back" msgid="2560683030046918882">"Wstecz"</string>
     <string name="permission_expand" msgid="893185038020887411">"Rozwiń sekcję <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Zwiń sekcję <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
index c45cda6..26647c8 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
@@ -17,15 +17,14 @@
 <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">"Gerenciador de dispositivos complementar"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"O app <xliff:g id="APP_NAME">%2$s</xliff:g> é necessário para gerenciar o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Ele poderá sincronizar informações, como o nome de quem está ligando, interagir com suas notificações e acessar as permissões do Telefone, SMS, contatos, agenda, registro de chamadas e dispositivos por perto."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"O app poderá sincronizar informações, como o nome de quem está ligando, e acessar estas permissões no seu <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Escolha um dispositivo para ser gerenciado pelo app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para configurar"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"O app poderá sincronizar informações, como o nome de quem está ligando, e acessar estas permissões no seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; gerencie o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"óculos"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"O app <xliff:g id="APP_NAME">%2$s</xliff:g> é necessário para gerenciar o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Ele poderá interagir com suas notificações e acessar suas permissões de telefone, SMS, contatos, microfone e dispositivos por perto."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"O app poderá acessar estas permissões no seu <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositivo"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"O app poderá acessar estas permissões no seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse estas informações do smartphone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Serviços entre dispositivos"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu dispositivo <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para fazer streaming de apps entre seus dispositivos"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Permitir que o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; realize esta ação?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu dispositivo <xliff:g id="DEVICE_NAME">%2$s</xliff:g> para fazer streaming de apps e de outros recursos do sistema para dispositivos por perto"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"O app poderá sincronizar informações, como o nome de quem está ligando, entre seu smartphone e o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"O app poderá sincronizar informações, como o nome de quem está ligando, entre seu smartphone e o dispositivo escolhido"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
     <string name="consent_back" msgid="2560683030046918882">"Voltar"</string>
     <string name="permission_expand" msgid="893185038020887411">"Abrir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Fechar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
index 8619ff4..ba60f56 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
@@ -17,15 +17,14 @@
 <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">"Gestor de dispositivos associados"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Permitir que a app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aceda ao &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Permitir que a app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aceda ao &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerido pela app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Esta app é necessária para gerir o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A app <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder sincronizar informações, como o nome do autor de uma chamada, interagir com as suas notificações e aceder às autorizações do Telemóvel, SMS, Contactos, Calendário, Registos de chamadas e Dispositivos próximos."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Esta app vai poder sincronizar informações, como o nome do autor de uma chamada, e aceder a estas autorizações no seu <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Escolha um dispositivo para ser gerido pela app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Escolha um perfil de <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para configurar"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Esta app vai poder sincronizar informações, como o nome do autor de uma chamada, e aceder a estas autorizações no seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Permita que a app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; faça a gestão do dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"óculos"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Esta app é necessária para gerir o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. A app <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com as suas notificações e aceder às autorizações do Telemóvel, SMS, Contactos, Microfone e Dispositivos próximos."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Esta app vai poder aceder a estas autorizações no seu <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositivo"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Esta app vai poder aceder a estas autorizações no seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permita que a app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aceda a estas informações do seu telemóvel"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Serviços entre dispositivos"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a pedir autorização em nome do seu dispositivo <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para fazer stream de apps entre os seus dispositivos"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Permitir que o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; faça esta ação?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a pedir autorização em nome do dispositivo <xliff:g id="DEVICE_NAME">%2$s</xliff:g> para fazer stream de apps e outras funcionalidades do sistema para dispositivos próximos"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Esta app vai poder sincronizar informações, como o nome do autor de uma chamada, entre o telemóvel e o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Esta app vai poder sincronizar informações, como o nome do autor de uma chamada, entre o telemóvel e o dispositivo escolhido"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
     <string name="consent_back" msgid="2560683030046918882">"Voltar"</string>
     <string name="permission_expand" msgid="893185038020887411">"Expandir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Reduzir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-pt/strings.xml b/packages/CompanionDeviceManager/res/values-pt/strings.xml
index c45cda6..26647c8 100644
--- a/packages/CompanionDeviceManager/res/values-pt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt/strings.xml
@@ -17,15 +17,14 @@
 <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">"Gerenciador de dispositivos complementar"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"O app <xliff:g id="APP_NAME">%2$s</xliff:g> é necessário para gerenciar o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Ele poderá sincronizar informações, como o nome de quem está ligando, interagir com suas notificações e acessar as permissões do Telefone, SMS, contatos, agenda, registro de chamadas e dispositivos por perto."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"O app poderá sincronizar informações, como o nome de quem está ligando, e acessar estas permissões no seu <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Escolha um dispositivo para ser gerenciado pelo app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para configurar"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"O app poderá sincronizar informações, como o nome de quem está ligando, e acessar estas permissões no seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; gerencie o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"óculos"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"O app <xliff:g id="APP_NAME">%2$s</xliff:g> é necessário para gerenciar o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Ele poderá interagir com suas notificações e acessar suas permissões de telefone, SMS, contatos, microfone e dispositivos por perto."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"O app poderá acessar estas permissões no seu <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispositivo"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"O app poderá acessar estas permissões no seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; acesse estas informações do smartphone"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Serviços entre dispositivos"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu dispositivo <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para fazer streaming de apps entre seus dispositivos"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Permitir que o dispositivo &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; realize esta ação?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu dispositivo <xliff:g id="DEVICE_NAME">%2$s</xliff:g> para fazer streaming de apps e de outros recursos do sistema para dispositivos por perto"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"O app poderá sincronizar informações, como o nome de quem está ligando, entre seu smartphone e o dispositivo <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"O app poderá sincronizar informações, como o nome de quem está ligando, entre seu smartphone e o dispositivo escolhido"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Cancelar"</string>
     <string name="consent_back" msgid="2560683030046918882">"Voltar"</string>
     <string name="permission_expand" msgid="893185038020887411">"Abrir <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Fechar <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ro/strings.xml b/packages/CompanionDeviceManager/res/values-ro/strings.xml
index f74e08ee..84182e8 100644
--- a/packages/CompanionDeviceManager/res/values-ro/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ro/strings.xml
@@ -17,15 +17,14 @@
 <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">"Manager de dispozitiv Companion"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Permiți ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să acceseze dispozitivul &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Permiți ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să acceseze dispozitivul &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ceas"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Alege un profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g> pe care să îl gestioneze &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Această aplicație este necesară pentru a gestiona <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> va putea să sincronizeze informații, cum ar fi numele unui apelant, să interacționeze cu notificările tale și să îți acceseze permisiunile pentru Telefon, SMS, Agendă, Calendar, Jurnale de apeluri și Dispozitive din apropiere."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Aplicația va putea să sincronizeze informații, cum ar fi numele unui apelant, și să acceseze aceste permisiuni pe <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Alege un dispozitiv pe care să îl gestioneze &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Alege un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> de configurat"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Aplicația va putea să sincronizeze informații, cum ar fi numele unui apelant, și să acceseze aceste permisiuni pe <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Permiți ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să gestioneze &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"ochelari"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Această aplicație este necesară pentru a gestiona <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> va putea să interacționeze cu notificările tale și să-ți acceseze permisiunile pentru Telefon, SMS, Agendă, Microfon și Dispozitive din apropiere."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Aplicația va putea să acceseze următoarele permisiuni pe <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"dispozitiv"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Aplicația va putea să acceseze următoarele permisiuni pe <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Permite ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să acceseze aceste informații de pe telefon"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Servicii pe mai multe dispozitive"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicită permisiunea pentru <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> de a reda în stream aplicații între dispozitivele tale"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Permiți ca &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; să realizeze această acțiune?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> solicită permisiunea pentru <xliff:g id="DEVICE_NAME">%2$s</xliff:g> de a reda în stream conținut din aplicații și alte funcții de sistem pe dispozitivele din apropiere"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"dispozitiv"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Aplicația va putea să sincronizeze informații, cum ar fi numele unui apelant, între telefonul tău și <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Aplicația va putea să sincronizeze informații, cum ar fi numele unui apelant, între telefonul tău și dispozitivul ales"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Permite"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nu permite"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Anulează"</string>
     <string name="consent_back" msgid="2560683030046918882">"Înapoi"</string>
     <string name="permission_expand" msgid="893185038020887411">"Extinde <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Restrânge <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ru/strings.xml b/packages/CompanionDeviceManager/res/values-ru/strings.xml
index cdf1668..911bddc 100644
--- a/packages/CompanionDeviceManager/res/values-ru/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ru/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"Предоставить приложению &lt;strong&gt;<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;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Предоставить приложению &lt;strong&gt;<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;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"часы"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Выберите устройство (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>), которым будет управлять приложение &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Это приложение необходимо для управления устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Приложение \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" сможет синхронизировать данные, например из журнала звонков, а также получит доступ к уведомлениям и разрешениям \"Телефон\", \"Контакты\", \"Календарь\", \"Список вызовов\", \"Устройства поблизости\" и SMS."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Это приложение сможет синхронизировать данные, например имена вызывающих абонентов, а также получит указанные разрешения на <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>."</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Выберите устройство, которым будет управлять приложение &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Выберите <xliff:g id="PROFILE_NAME">%1$s</xliff:g> для настройки"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Это приложение сможет синхронизировать данные, например имена вызывающих абонентов, а также получит указанные разрешения на <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Разрешить приложению &lt;strong&gt;<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;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"Очки"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Это приложение необходимо для управления устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Приложение \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" сможет взаимодействовать с уведомлениями, а также получит разрешения \"Телефон\", SMS, \"Контакты\", \"Микрофон\" и \"Устройства поблизости\"."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Это приложение получит указанные разрешения на <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>."</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"устройстве"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Это приложение получит указанные разрешения на <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Разрешите приложению &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; получать эту информацию с вашего телефона"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Сервисы стриминга приложений"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запрашивает разрешение от имени вашего устройства <xliff:g id="DISPLAY_NAME">%2$s</xliff:g>, чтобы транслировать приложения между устройствами."</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Разрешить приложению &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; выполнять это действие?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" от имени вашего устройства \"<xliff:g id="DEVICE_NAME">%2$s</xliff:g>\" запрашивает разрешение транслировать приложения и системные функции на устройства поблизости."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"устройство"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Приложение сможет синхронизировать информацию между телефоном и устройством \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\", например данные из журнала звонков."</string>
     <string name="summary_generic" msgid="1761976003668044801">"Приложение сможет синхронизировать информацию между телефоном и выбранным устройством, например данные из журнала звонков."</string>
     <string name="consent_yes" msgid="8344487259618762872">"Разрешить"</string>
     <string name="consent_no" msgid="2640796915611404382">"Запретить"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Отмена"</string>
     <string name="consent_back" msgid="2560683030046918882">"Назад"</string>
     <string name="permission_expand" msgid="893185038020887411">"Разворачивать список \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\"."</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Сворачивать список \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\"."</string>
diff --git a/packages/CompanionDeviceManager/res/values-si/strings.xml b/packages/CompanionDeviceManager/res/values-si/strings.xml
index 41224c6..daa2058 100644
--- a/packages/CompanionDeviceManager/res/values-si/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-si/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<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; වෙත ප්‍රවේශ වීමට ඉඩ දෙන්න ද?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<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; වෙත ප්‍රවේශ වීමට ඉඩ දෙන්න ද?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ඔරලෝසුව"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; මගින් කළමනාකරණය කරනු ලැබීමට <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ක් තෝරන්න"</string>
-    <string name="summary_watch" msgid="898569637110705523">"මෙම යෙදුමට ඔබේ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> කළමනාකරණය කිරීමට අවශ්‍යයි. <xliff:g id="APP_NAME">%2$s</xliff:g> හට අමතන කෙනෙකුගේ නම වැනි, තතු සමමුහුර්ත කිරීමට, ඔබේ දැනුම්දීම් සමග අන්තර්ක්‍රියා කිරීමට සහ ඔබේ දුරකථනය, SMS, සම්බන්ධතා, දින දර්ශනය, ඇමතුම් ලොග සහ අවට උපාංග අවසර වෙත ප්‍රවේශ වීමට ඉඩ දෙනු ඇත."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"මෙම යෙදුමට අමතන කෙනෙකුගේ නම වැනි, තතු සමමුහුර්ත කිරීමට, සහ ඔබේ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> මත මෙම අවසර වෙත ප්‍රවේශ වීමට ඉඩ දෙනු ඇත"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; විසින් කළමනා කරනු ලැබීමට උපාංගයක් තෝරන්න"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"සැකසීමට <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ක් තෝරන්න"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"මෙම යෙදුමට අමතන කෙනෙකුගේ නම වැනි, තොරතුරු සමමුහූර්ත කිරීමට, සහ ඔබේ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> මත මෙම අවසර වෙත ප්‍රවේශ වීමට ඉඩ දෙනු ලැබේ"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<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; කළමනා කිරීමට ඉඩ දෙන්න ද?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"කණ්ණාඩි"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> කළමනා කිරීමට මෙම යෙදුම අවශ්‍යයි. <xliff:g id="APP_NAME">%2$s</xliff:g> හට ඔබේ දැනුම්දීම් සමග අන්තර්ක්‍රියා කිරීමට සහ ඔබේ දුරකථනය, කෙටි පණිවුඩය, සම්බන්ධතා, මයික්‍රොෆෝනය සහ අවට උපාංග අවසර වෙත ප්‍රවේශ වීමට ඉඩ දෙයි."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"මෙම යෙදුමට ඔබේ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> මත මෙම අවසර වෙත ප්‍රවේශ වීමට ඉඩ දෙනු ඇත"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"උපාංගය"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"මෙම යෙදුමට ඔබේ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> මත මෙම අවසර වෙත ප්‍රවේශ වීමට ඉඩ දෙනු ලැබේ"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; හට ඔබගේ දුරකථනයෙන් මෙම තොරතුරුවලට ප්‍රවේශ වීමට ඉඩ දෙන්න"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"හරස්-උපාංග සේවා"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> ඔබේ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> වෙනුවෙන් ඔබේ උපාංග අතර යෙදුම් ප්‍රවාහ කිරීමට අවසරය ඉල්ලමින් සිටියි"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"මෙම ක්‍රියාව කිරීමට &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; හට ඉඩ දෙන්න ද?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> ඔබේ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> වෙනුවෙන් යෙදුම් සහ අනෙකුත් පද්ධති විශේෂාංග අවට උපාංග වෙත ප්‍රවාහ කිරීමට අවසර ඉල්ලයි"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"උපාංගය"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"මෙම යෙදුමට ඔබේ දුරකථනය සහ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> අතර, අමතන කෙනෙකුගේ නම වැනි, තතු සමමුහුර්ත කිරීමට හැකි වනු ඇත"</string>
     <string name="summary_generic" msgid="1761976003668044801">"මෙම යෙදුමට ඔබේ දුරකථනය සහ තෝරා ගත් උපාංගය අතර, අමතන කෙනෙකුගේ නම වැනි, තතු සමමුහුර්ත කිරීමට හැකි වනු ඇත"</string>
     <string name="consent_yes" msgid="8344487259618762872">"ඉඩ දෙන්න"</string>
     <string name="consent_no" msgid="2640796915611404382">"ඉඩ නොදෙන්න"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"අවලංගු කරන්න"</string>
     <string name="consent_back" msgid="2560683030046918882">"ආපසු"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> විදහන්න"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> හකුළන්න"</string>
diff --git a/packages/CompanionDeviceManager/res/values-sk/strings.xml b/packages/CompanionDeviceManager/res/values-sk/strings.xml
index 220f00e..929b56c 100644
--- a/packages/CompanionDeviceManager/res/values-sk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sk/strings.xml
@@ -17,15 +17,14 @@
 <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">"Správca sprievodných zariadení"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Chcete povoliť aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; prístup k zariadeniu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Chcete povoliť aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; prístup k zariadeniu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"hodinky"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Vyberte profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, ktorý bude spravovať aplikácia &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Táto aplikácia sa vyžaduje na správu zariadenia <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude môcť synchronizovať informácie, napríklad meno volajúceho, interagovať s vašimi upozorneniami a získavať prístup k povoleniam telefónu, SMS, kontaktov, kalendára, zoznamu hovorov a zariadení v okolí."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Táto aplikácia bude môcť synchronizovať informácie, napríklad meno volajúceho, a získavať prístup k týmto povoleniam v zariadení <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Vyberte zariadenie, ktoré bude spravovať aplikácia &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Vyberte profil <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, ktorý nastavíte"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Táto aplikácia bude môcť synchronizovať informácie, napríklad meno volajúceho, a získavať prístup k týmto povoleniam v zariadení <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Chcete povoliť aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; spravovať zariadenie &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"okuliare"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Táto aplikácia sa vyžaduje na správu zariadenia <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> bude môcť interagovať s vašimi upozorneniami a získa prístup k povoleniam pre telefón, SMS, kontakty, mikrofón a zariadenia v okolí."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Táto aplikácia bude mať prístup k týmto povoleniam v zariadení <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"zariadenie"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Táto aplikácia bude mať prístup k týmto povoleniam v zariadení <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Povoľte aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; prístup k týmto informáciám z vášho telefónu"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Služby pre viacero zariadení"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> vyžaduje pre zariadenie <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> povolenie streamovať aplikácie medzi vašimi zariadeniami."</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Chcete povoliť zariadeniu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; vykonať túto akciu?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> vyžaduje pre zariadenie <xliff:g id="DEVICE_NAME">%2$s</xliff:g> povolenie streamovať aplikácie a ďalšie systémové funkcie do zariadení v okolí"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"zariadenie"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Táto aplikácia bude môcť synchronizovať informácie, napríklad meno volajúceho, medzi telefónom a zariadením <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Táto aplikácia bude môcť synchronizovať informácie, napríklad meno volajúceho, medzi telefónom a vybraným zariadením"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Povoliť"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nepovoliť"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Zrušiť"</string>
     <string name="consent_back" msgid="2560683030046918882">"Späť"</string>
     <string name="permission_expand" msgid="893185038020887411">"Rozbaliť sekciu <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Zbaliť sekciu <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-sl/strings.xml b/packages/CompanionDeviceManager/res/values-sl/strings.xml
index 562e474..b1b1e91 100644
--- a/packages/CompanionDeviceManager/res/values-sl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sl/strings.xml
@@ -17,15 +17,14 @@
 <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">"Upravitelj spremljevalnih naprav"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Želite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; dovoliti dostop do naprave &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Želite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; dovoliti dostop do naprave &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ura"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Izbira profila »<xliff:g id="PROFILE_NAME">%1$s</xliff:g>«, ki ga bo upravljala aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Ta aplikacija je potrebna za upravljanje naprave »<xliff:g id="DEVICE_NAME">%1$s</xliff:g>«. Aplikaciji <xliff:g id="APP_NAME">%2$s</xliff:g> bodo omogočene sinhronizacija podatkov, na primer imena klicatelja, interakcija z obvestili in uporaba dovoljenj Telefon, SMS, Stiki, Koledar, Dnevniki klicev in Naprave v bližini."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Ta aplikacija bo lahko sinhronizirala podatke, na primer ime klicatelja, in dostopala do teh dovoljenj v napravi »<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>«."</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Izbira naprave, ki jo bo upravljala aplikacija &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Izberite profil naprave »<xliff:g id="PROFILE_NAME">%1$s</xliff:g>« za nastavitev"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Ta aplikacija bo lahko sinhronizirala podatke, na primer ime klicatelja, in dostopala do teh dovoljenj v napravi »<xliff:g id="DEVICE_NAME">%1$s</xliff:g>«."</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Želite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; dovoliti upravljanje naprave &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"očala"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Ta aplikacija je potrebna za upravljanje naprave »<xliff:g id="DEVICE_NAME">%1$s</xliff:g>«. Aplikaciji <xliff:g id="APP_NAME">%2$s</xliff:g> bosta omogočeni interakcija z obvestili in uporaba dovoljenj Telefon, SMS, Stiki, Mikrofon in Naprave v bližini."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Ta aplikacija bo lahko dostopala do teh dovoljenj v napravi »<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>«."</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"naprava"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Ta aplikacija bo lahko dostopala do teh dovoljenj v napravi »<xliff:g id="DEVICE_NAME">%1$s</xliff:g>«."</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Dovolite, da &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; dostopa do teh podatkov v vašem telefonu"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Storitve za zunanje naprave"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> v imenu naprave »<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>« zahteva dovoljenje za pretočno predvajanje aplikacij v vaših napravah."</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Ali napravi &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; dovolite izvedbo tega dejanja?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> v imenu naprave »<xliff:g id="DEVICE_NAME">%2$s</xliff:g>« zahteva dovoljenje za pretočno predvajanje aplikacij in drugih sistemskih funkcij v napravah v bližini."</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"naprava"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Ta aplikacija bo lahko sinhronizirala podatke, na primer ime klicatelja, v telefonu in napravi »<xliff:g id="DEVICE_NAME">%1$s</xliff:g>«."</string>
     <string name="summary_generic" msgid="1761976003668044801">"Ta aplikacija bo lahko sinhronizirala podatke, na primer ime klicatelja, v telefonu in izbrani napravi."</string>
     <string name="consent_yes" msgid="8344487259618762872">"Dovoli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne dovoli"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Prekliči"</string>
     <string name="consent_back" msgid="2560683030046918882">"Nazaj"</string>
     <string name="permission_expand" msgid="893185038020887411">"Razširi dovoljenje »<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>«"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Strni dovoljenje »<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>«"</string>
diff --git a/packages/CompanionDeviceManager/res/values-sq/strings.xml b/packages/CompanionDeviceManager/res/values-sq/strings.xml
index 8e32ec7..037a707 100644
--- a/packages/CompanionDeviceManager/res/values-sq/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sq/strings.xml
@@ -17,15 +17,14 @@
 <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">"Menaxheri i pajisjes shoqëruese"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"T\'i lejohet &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; qasja te &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"T\'i lejohet aplikacionit &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; qasja te &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"ora inteligjente"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Zgjidh që <xliff:g id="PROFILE_NAME">%1$s</xliff:g> të menaxhohet nga &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Ky aplikacion nevojitet për të menaxhuar <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> do të lejohet të sinkronizojë informacione, si p.sh. emrin e dikujt që po telefonon, të ndërveprojë me njoftimet e tua dhe të ketë qasje te lejet e \"Telefonit\", \"SMS-ve\", \"Kontakteve\", \"Kalendarit\", \"Evidencave të telefonatave\" dhe \"Pajisjeve në afërsi\"."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Këtij aplikacioni do t\'i lejohet të sinkronizojë informacione, si p.sh. emrin e dikujt që po telefonon, si dhe të ketë qasje në këto leje në <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Zgjidh një pajisje që do të menaxhohet nga &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Zgjidh një <xliff:g id="PROFILE_NAME">%1$s</xliff:g> për konfigurimin"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Këtij aplikacioni do t\'i lejohet të sinkronizojë informacione, si p.sh. emrin e dikujt që po telefonon, si dhe të ketë qasje në këto leje në <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Të lejohet që &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; të menaxhojë &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"syzet"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Ky aplikacion nevojitet për të menaxhuar <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> do të lejohet të ndërveprojë me njoftimet e tua dhe të ketë qasje te lejet e \"Telefonit\", \"SMS-ve\", \"Kontakteve\", \"Mikrofonit\" dhe të \"Pajisjeve në afërsi\"."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Këtij aplikacioni do t\'i lejohet qasja te këto leje në <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"pajisje"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Këtij aplikacioni do t\'i lejohet qasja te këto leje në <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Lejo që &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; të ketë qasje në këtë informacion nga telefoni yt"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Shërbimet mes pajisjeve"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> po kërkon leje në emër të <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> për të transmetuar aplikacione ndërmjet pajisjeve të tua"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Të lejohet që &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; të ndërmarrë këtë veprim?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> po kërkon leje në emër të (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>) tënde për të transmetuar aplikacione dhe veçori të tjera të sistemit te pajisjet në afërsi"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"pajisja"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Ky aplikacion do të mund të sinkronizojë informacione, si p.sh emrin i dikujt që po telefonon, mes telefonit tënd dhe <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="summary_generic" msgid="1761976003668044801">"Ky aplikacion do të mund të sinkronizojë informacione, si p.sh emrin e dikujt që po telefonon, mes telefonit tënd dhe pajisjes së zgjedhur."</string>
     <string name="consent_yes" msgid="8344487259618762872">"Lejo"</string>
     <string name="consent_no" msgid="2640796915611404382">"Mos lejo"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Anulo"</string>
     <string name="consent_back" msgid="2560683030046918882">"Pas"</string>
     <string name="permission_expand" msgid="893185038020887411">"Zgjero: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Palos: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-sr/strings.xml b/packages/CompanionDeviceManager/res/values-sr/strings.xml
index 34d9ca2..3817d58 100644
--- a/packages/CompanionDeviceManager/res/values-sr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sr/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"Дозволите да &lt;strong&gt;<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;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Дозволите да апликација &lt;strong&gt;<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;"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"сат"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Одаберите <xliff:g id="PROFILE_NAME">%1$s</xliff:g> којим ће управљати апликација &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Ова апликација је потребна за управљање уређајем <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ће добити дозволу за синхронизовање информација, попут особе која упућује позив, за интеракцију са обавештењима и приступ дозволама за телефон, SMS, контакте, календар, евиденције позива и уређаје у близини."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Овој апликацији ће бити дозвољено да синхронизује податке, попут имена особе која упућује позив, и приступа тим дозволама на вашем уређају (<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>)"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Одаберите уређај којим ће управљати апликација &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Одаберите профил <xliff:g id="PROFILE_NAME">%1$s</xliff:g> који желите да подесите"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Овој апликацији ће бити дозвољено да синхронизује податке, попут имена особе која упућује позив, и приступа тим дозволама на вашем уређају (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Желите ли да дозволите да &lt;strong&gt;<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;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"наочаре"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Ова апликација је потребна за управљање уређајем <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> ће добити дозволу за интеракцију са обавештењима и приступ дозволама за телефон, SMS, контакте, микрофон и уређаје у близини."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Овој апликацији ће бити дозвољено да приступа овим дозволама на вашем уређају (<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>)"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"уређај"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Овој апликацији ће бити дозвољено да приступа овим дозволама на вашем уређају (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>)"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Дозволите да &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; приступа овим информацијама са телефона"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Услуге на више уређаја"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> захтева дозволу у име уређаја <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> за стримовање апликација између уређаја"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Желите ли да дозволите да &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; обави ову радњу?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> захтева дозволу у име уређаја <xliff:g id="DEVICE_NAME">%2$s</xliff:g> да стримује апликације и друге системске функције на уређаје у близини"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"уређај"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Ова апликација ће моћи да синхронизује податке, попут имена особе која упућује позив, између телефона и уређаја <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Ова апликација ће моћи да синхронизује податке, попут имена особе која упућује позив, између телефона и одабраног уређаја"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Дозволи"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дозволи"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Откажи"</string>
     <string name="consent_back" msgid="2560683030046918882">"Назад"</string>
     <string name="permission_expand" msgid="893185038020887411">"Прошири <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Скупи <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-sv/strings.xml b/packages/CompanionDeviceManager/res/values-sv/strings.xml
index 54e2d18..f3be3be 100644
--- a/packages/CompanionDeviceManager/res/values-sv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sv/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Vill du tillåta att &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; får åtkomst till &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Vill du tillåta att appen &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; får åtkomst till &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"klocka"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Välj en <xliff:g id="PROFILE_NAME">%1$s</xliff:g> för hantering av &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Appen behövs för att hantera <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tillåtelse att synkronisera information, till exempel namnet på någon som ringer, interagera med dina aviseringar och får åtkomst till behörigheterna Telefon, Sms, Kontakter, Kalender, Samtalsloggar och Enheter i närheten."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Appen får tillåtelse att synkronisera information, till exempel namnet på någon som ringer, och få tillgång till dessa behörigheter på din <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Välj en enhet för hantering av &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Välj en <xliff:g id="PROFILE_NAME">%1$s</xliff:g> för konfigurering"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Appen får tillåtelse att synkronisera information, till exempel namnet på någon som ringer, och få tillgång till dessa behörigheter på din <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Tillåt att &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; hanterar &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"glasögon"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Appen behövs för att hantera <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> får tillåtelse att interagera med dina aviseringar och får åtkomst till behörigheterna Telefon, Sms, Kontakter, Mikrofon och Enheter i närheten."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Appen får tillåtelse att använda dessa behörigheter på din <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"enhet"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Appen får tillåtelse att använda dessa behörigheter på din <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Ge &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; åtkomstbehörighet till denna information på telefonen"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Tjänster för flera enheter"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> begär behörighet att låta <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> streama appar mellan enheter"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Vill du tillåta att &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; utför denna åtgärd?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> begär behörighet att streama appar och andra systemfunktioner till enheter i närheten för din <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"enhet"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Den här appen kommer att kunna synkronisera information mellan telefonen och <xliff:g id="DEVICE_NAME">%1$s</xliff:g>, till exempel namnet på någon som ringer"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Den här appen kommer att kunna synkronisera information mellan telefonen och den valda enheten, till exempel namnet på någon som ringer"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Tillåt"</string>
     <string name="consent_no" msgid="2640796915611404382">"Tillåt inte"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Avbryt"</string>
     <string name="consent_back" msgid="2560683030046918882">"Tillbaka"</string>
     <string name="permission_expand" msgid="893185038020887411">"Utöka <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Komprimera <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml
index 6e8c607..7b94239 100644
--- a/packages/CompanionDeviceManager/res/values-sw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml
@@ -17,15 +17,14 @@
 <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">"Kidhibiti cha Vifaa Visaidizi"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Ungependa kuruhusu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ifikie &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Ungependa kuruhusu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ifikie &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"saa"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Chagua <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ili idhibitiwe na &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Programu hii inahitajika ili udhibiti <xliff:g id="DEVICE_NAME">%1$s</xliff:g> yako. <xliff:g id="APP_NAME">%2$s</xliff:g> itaruhusiwa kusawazisha maelezo, kama vile jina la mtu anayepiga simu, kutumia arifa zako na ruhusa zako za Simu, SMS, Anwani, Maikrofoni na vifaa vilivyo Karibu."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Programu hii itaruhusiwa kusawazisha maelezo, kama vile jina la mtu anayepiga simu na kufikia ruhusa hizi kwenye <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> yako"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Chagua kifaa cha kudhibitiwa na &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Chagua <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ili uweke mipangilio"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Programu hii itaruhusiwa kusawazisha maelezo, kama vile jina la mtu anayepiga simu na kufikia ruhusa hizi kwenye <xliff:g id="DEVICE_NAME">%1$s</xliff:g> yako"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Ungependa kuruhusu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; idhibiti &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"miwani"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Programu hii inahitajika ili udhibiti <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> itaruhusiwa kutumia arifa zako na kufikia ruhusa zako za Simu, SMS, Anwani, Maikrofoni na Vifaa vilivyo Karibu."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Programu hii itaruhusiwa kufikia ruhusa hizi kwenye <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> yako"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"kifaa"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Programu hii itaruhusiwa kufikia ruhusa hizi kwenye <xliff:g id="DEVICE_NAME">%1$s</xliff:g> yako"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Ruhusu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ifikie maelezo haya kutoka kwenye simu yako"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Huduma za kifaa kilichounganishwa kwingine"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Programu ya <xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> yako ili itiririshe programu kati ya vifaa vyako"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Ungependa kuruhusu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; itekeleze kitendo hiki?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_NAME">%2$s</xliff:g> chako ili itiririshe programu na vipengele vingine vya mfumo kwenye vifaa vilivyo karibu"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"kifaa"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Programu hii itaweza kusawazisha maelezo, kama vile jina la mtu anayepiga simu, kati ya simu na <xliff:g id="DEVICE_NAME">%1$s</xliff:g> yako"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Programu hii itaweza kusawazisha maelezo, kama vile jina la mtu anayepiga simu, kati ya simu yako na kifaa ulichochagua"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Ruhusu"</string>
     <string name="consent_no" msgid="2640796915611404382">"Usiruhusu"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Ghairi"</string>
     <string name="consent_back" msgid="2560683030046918882">"Nyuma"</string>
     <string name="permission_expand" msgid="893185038020887411">"Panua <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Kunja <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-ta/strings.xml b/packages/CompanionDeviceManager/res/values-ta/strings.xml
index d049953..c1b8352 100644
--- a/packages/CompanionDeviceManager/res/values-ta/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ta/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; சாதனத்தை அணுக &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ஆப்ஸை அனுமதிக்கவா?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; சாதனத்தை அணுக &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ஆப்ஸை அனுமதிக்கவா?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"வாட்ச்"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ஆப்ஸ் நிர்வகிக்கக்கூடிய <xliff:g id="PROFILE_NAME">%1$s</xliff:g> தேர்ந்தெடுக்கப்பட வேண்டும்"</string>
-    <string name="summary_watch" msgid="898569637110705523">"உங்கள் <xliff:g id="DEVICE_NAME">%1$s</xliff:g> சாதனத்தை நிர்வகிக்க இந்த ஆப்ஸ் தேவை. அழைப்பவரின் பெயர் போன்ற தகவலை ஒத்திசைத்தல், உங்கள் அறிவிப்புகளைப் பார்த்தல், உங்கள் மொபைல், மெசேஜ், தொடர்புகள், கேலெண்டர், அழைப்புப் பதிவுகள், அருகிலுள்ள சாதனங்களை அணுகுதல் ஆகியவற்றுக்கு <xliff:g id="APP_NAME">%2$s</xliff:g> அனுமதிக்கப்படும்."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"அழைப்பவரின் பெயர் போன்ற தகவல்களை ஒத்திசைக்கவும் உங்கள் <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> சாதனத்தில் இந்த அனுமதிகளை அணுகவும் இந்த ஆப்ஸ் அனுமதிக்கப்படும்"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ஆப்ஸால் நிர்வகிக்கப்பட வேண்டிய சாதனத்தைத் தேர்வுசெய்யுங்கள்"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"அமைக்க <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ஐத் தேர்வுசெய்யவும்"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"அழைப்பவரின் பெயர் போன்ற தகவல்களை ஒத்திசைக்கவும் உங்கள் <xliff:g id="DEVICE_NAME">%1$s</xliff:g> சாதனத்தில் இந்த அனுமதிகளை அணுகவும் இந்த ஆப்ஸ் அனுமதிக்கப்படும்"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&amp;gt சாதனத்தை நிர்வகிக்க &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ஆப்ஸை அனுமதிக்கவா?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"கிளாஸஸ்"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> சாதனத்தை நிர்வகிக்க இந்த ஆப்ஸ் தேவை. உங்கள் மொபைல், மெசேஜ், தொடர்புகள், மைக்ரோஃபோன், அருகிலுள்ள சாதனங்கள் ஆகியவற்றுக்கான அணுகலையும் உங்கள் அறிவிப்புகளைப் பார்ப்பதற்கான அனுமதியையும் <xliff:g id="APP_NAME">%2$s</xliff:g> பெறும்."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"உங்கள் <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> சாதனத்தில் இந்த அனுமதிகளை அணுக இந்த ஆப்ஸ் அனுமதிக்கப்படும்"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"சாதனம்"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"உங்கள் <xliff:g id="DEVICE_NAME">%1$s</xliff:g> சாதனத்தில் இந்த அனுமதிகளை அணுக இந்த ஆப்ஸ் அனுமதிக்கப்படும்"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"மொபைலில் உள்ள இந்தத் தகவல்களை அணுக, &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ஆப்ஸை அனுமதிக்கவும்"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"பன்முக சாதன சேவைகள்"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"உங்கள் சாதனங்களுக்கு இடையே ஆப்ஸை ஸ்ட்ரீம் செய்ய உங்கள் <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> சார்பாக <xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் அனுமதியைக் கோருகிறது"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"இந்தச் செயலைச் செய்ய &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ஐ அனுமதிக்கவா?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"அருகிலுள்ள சாதனங்களுக்கு ஆப்ஸையும் பிற சிஸ்டம் அம்சங்களையும் ஸ்ட்ரீம் செய்ய உங்கள் <xliff:g id="DEVICE_NAME">%2$s</xliff:g> சார்பாக <xliff:g id="APP_NAME">%1$s</xliff:g> அனுமதி கோருகிறது"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"சாதனம்"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"அழைப்பவரின் பெயர் போன்ற தகவலை உங்கள் மொபைல் மற்றும் <xliff:g id="DEVICE_NAME">%1$s</xliff:g> சாதனத்திற்கு இடையில் இந்த ஆப்ஸால் ஒத்திசைக்க முடியும்"</string>
     <string name="summary_generic" msgid="1761976003668044801">"அழைப்பவரின் பெயர் போன்ற தகவலை உங்கள் மொபைல் மற்றும் தேர்வுசெய்த சாதனத்திற்கு இடையில் இந்த ஆப்ஸால் ஒத்திசைக்க முடியும்"</string>
     <string name="consent_yes" msgid="8344487259618762872">"அனுமதி"</string>
     <string name="consent_no" msgid="2640796915611404382">"அனுமதிக்க வேண்டாம்"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ரத்துசெய்"</string>
     <string name="consent_back" msgid="2560683030046918882">"பின்செல்"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ஐ விரிவாக்கும்"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> ஐச் சுருக்கும்"</string>
diff --git a/packages/CompanionDeviceManager/res/values-te/strings.xml b/packages/CompanionDeviceManager/res/values-te/strings.xml
index 5e91614..52316cd 100644
--- a/packages/CompanionDeviceManager/res/values-te/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-te/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;‌ను యాక్సెస్ చేయడానికి &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;‌ను అనుమతించాలా?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;‌ను యాక్సెస్ చేయడానికి &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; యాప్‌ను అనుమతించాలా?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"వాచ్"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; ద్వారా మేనేజ్ చేయబడటానికి ఒక <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ను ఎంచుకోండి"</string>
-    <string name="summary_watch" msgid="898569637110705523">"మీ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>‌ను మేనేజ్ చేయడానికి ఈ యాప్ అవసరం. కాల్ చేస్తున్న వారి పేరు వంటి సమాచారాన్ని సింక్ చేయడానికి, మీ నోటిఫికేషన్‌లతో ఇంటరాక్ట్ అవ్వడానికి, అలాగే మీ ఫోన్, SMS, కాంటాక్ట్‌లు, క్యాలెండర్, కాల్ లాగ్‌లు, సమీపంలోని పరికరాల అనుమతులను యాక్సెస్ చేయడానికి <xliff:g id="APP_NAME">%2$s</xliff:g> అనుమతించబడుతుంది."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"కాల్ చేస్తున్న వారి పేరు వంటి సమాచారాన్ని సింక్ చేయడానికి, మీ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>లో ఈ అనుమతులను యాక్సెస్ చేయడానికి ఈ యాప్ అనుమతించబడుతుంది"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ద్వారా మేనేజ్ చేయబడే పరికరాన్ని ఎంచుకోండి"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"సెటప్ చేయడానికి <xliff:g id="PROFILE_NAME">%1$s</xliff:g>‌ను ఎంచుకోండి"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"కాల్ చేస్తున్న వారి పేరు వంటి సమాచారాన్ని సింక్ చేయడానికి, మీ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>లో ఈ అనుమతులను యాక్సెస్ చేయడానికి ఈ యాప్ అనుమతించబడుతుంది"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;‌ను మేనేజ్ చేయడానికి &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;‌ను అనుమతించాలా?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"గ్లాసెస్"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‌ను మేనేజ్ చేయడానికి ఈ యాప్ అవసరం. మీ నోటిఫికేషన్‌లతో ఇంటరాక్ట్ అవ్వడానికి, అలాగే మీ ఫోన్, SMS, కాంటాక్ట్‌లు, మైక్రోఫోన్, సమీపంలోని పరికరాల అనుమతులను యాక్సెస్ చేయడానికి <xliff:g id="APP_NAME">%2$s</xliff:g> అనుమతించబడుతుంది."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"మీ <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>లో ఈ అనుమతులను యాక్సెస్ చేయడానికి ఈ యాప్ అనుమతించబడుతుంది"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"పరికరం"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"మీ <xliff:g id="DEVICE_NAME">%1$s</xliff:g>లో ఈ అనుమతులను యాక్సెస్ చేయడానికి ఈ యాప్ అనుమతించబడుతుంది"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"మీ ఫోన్ నుండి ఈ సమాచారాన్ని యాక్సెస్ చేయడానికి &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; యాప్‌ను అనుమతించండి"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cross-device services"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"మీ పరికరాల మధ్య యాప్‌లను స్ట్రీమ్ చేయడానికి <xliff:g id="APP_NAME">%1$s</xliff:g> మీ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> తరఫున అనుమతిని రిక్వెస్ట్ చేస్తోంది"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"ఈ చర్యను అమలు చేయడానికి &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;‌ను అనుమతించాలా?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"సమీపంలోని పరికరాలకు యాప్‌లను, ఇతర సిస్టమ్ ఫీచర్‌లను స్ట్రీమ్ చేయడానికి <xliff:g id="APP_NAME">%1$s</xliff:g> మీ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> తరఫున అనుమతిని రిక్వెస్ట్ చేస్తోంది"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"పరికరం"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"కాల్ చేస్తున్న వారి పేరు వంటి సమాచారాన్ని ఈ యాప్ మీ ఫోన్‌కి, <xliff:g id="DEVICE_NAME">%1$s</xliff:g>‌కి మధ్య సింక్ చేయగలుగుతుంది"</string>
     <string name="summary_generic" msgid="1761976003668044801">"కాల్ చేస్తున్న వారి పేరు వంటి సమాచారాన్ని ఈ యాప్ మీ ఫోన్ కు, ఎంచుకున్న పరికరానికీ మధ్య సింక్ చేయగలుగుతుంది"</string>
     <string name="consent_yes" msgid="8344487259618762872">"అనుమతించండి"</string>
     <string name="consent_no" msgid="2640796915611404382">"అనుమతించవద్దు"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"రద్దు చేయండి"</string>
     <string name="consent_back" msgid="2560683030046918882">"వెనుకకు"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>‌ను విస్తరించండి"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>‌ను కుదించండి"</string>
diff --git a/packages/CompanionDeviceManager/res/values-th/strings.xml b/packages/CompanionDeviceManager/res/values-th/strings.xml
index 46e631c..cb01f2d 100644
--- a/packages/CompanionDeviceManager/res/values-th/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-th/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"อนุญาตให้ &lt;strong&gt;<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;"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"อนุญาตให้แอป &lt;strong&gt;<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; ใช่ไหม"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"นาฬิกา"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"เลือก<xliff:g id="PROFILE_NAME">%1$s</xliff:g>ที่จะให้มีการจัดการโดย &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"ต้องใช้แอปนี้ในการจัดการ<xliff:g id="DEVICE_NAME">%1$s</xliff:g> <xliff:g id="APP_NAME">%2$s</xliff:g> จะได้รับอนุญาตให้ซิงค์ข้อมูล เช่น ชื่อของบุคคลที่โทรเข้ามา โต้ตอบกับการแจ้งเตือน รวมถึงมีสิทธิ์เข้าถึงโทรศัพท์, SMS, รายชื่อติดต่อ, ปฏิทิน, บันทึกการโทร และอุปกรณ์ที่อยู่ใกล้เคียง"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"แอปนี้จะได้รับอนุญาตให้ซิงค์ข้อมูล เช่น ชื่อของบุคคลที่โทรเข้ามา และมีสิทธิ์เข้าถึงข้อมูลเหล่านี้ใน<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>ของคุณ"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"เลือกอุปกรณ์ที่จะให้มีการจัดการโดย &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"เลือก<xliff:g id="PROFILE_NAME">%1$s</xliff:g>ที่จะตั้งค่า"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"แอปนี้จะได้รับอนุญาตให้ซิงค์ข้อมูล เช่น ชื่อของบุคคลที่โทรเข้ามา และมีสิทธิ์เข้าถึงข้อมูลเหล่านี้ใน<xliff:g id="DEVICE_NAME">%1$s</xliff:g>ของคุณ"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"อนุญาตให้ &lt;strong&gt;<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; ไหม"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"แว่นตา"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"ต้องใช้แอปนี้ในการจัดการ<xliff:g id="DEVICE_NAME">%1$s</xliff:g> <xliff:g id="APP_NAME">%2$s</xliff:g> จะได้รับอนุญาตให้โต้ตอบกับการแจ้งเตือนและมีสิทธิ์เข้าถึงโทรศัพท์, SMS, รายชื่อติดต่อ, ไมโครโฟน และอุปกรณ์ที่อยู่ใกล้เคียง"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"แอปนี้จะได้รับสิทธิ์ดังต่อไปนี้ใน<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>ของคุณ"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"อุปกรณ์"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"แอปนี้จะได้รับสิทธิ์ดังต่อไปนี้ใน<xliff:g id="DEVICE_NAME">%1$s</xliff:g>ของคุณ"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"อนุญาตให้ &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; เข้าถึงข้อมูลนี้จากโทรศัพท์ของคุณ"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"บริการหลายอุปกรณ์"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังขอสิทธิ์ในนามของ <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> เพื่อสตรีมแอประหว่างอุปกรณ์ต่างๆ ของคุณ"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"อนุญาตให้ &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ทำงานนี้ไหม"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังขอสิทธิ์ในนามของ <xliff:g id="DEVICE_NAME">%2$s</xliff:g> เพื่อสตรีมแอปและฟีเจอร์อื่นๆ ของระบบไปยังอุปกรณ์ที่อยู่ใกล้เคียง"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"อุปกรณ์"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"แอปนี้จะสามารถซิงค์ข้อมูล เช่น ชื่อของบุคคลที่โทรเข้ามา ระหว่างโทรศัพท์ของคุณและ<xliff:g id="DEVICE_NAME">%1$s</xliff:g>ได้"</string>
     <string name="summary_generic" msgid="1761976003668044801">"แอปนี้จะสามารถซิงค์ข้อมูล เช่น ชื่อของบุคคลที่โทรเข้ามา ระหว่างโทรศัพท์ของคุณและอุปกรณ์ที่เลือกไว้ได้"</string>
     <string name="consent_yes" msgid="8344487259618762872">"อนุญาต"</string>
     <string name="consent_no" msgid="2640796915611404382">"ไม่อนุญาต"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"ยกเลิก"</string>
     <string name="consent_back" msgid="2560683030046918882">"กลับ"</string>
     <string name="permission_expand" msgid="893185038020887411">"ขยาย <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"ยุบ <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-tl/strings.xml b/packages/CompanionDeviceManager/res/values-tl/strings.xml
index b358870..43d5996 100644
--- a/packages/CompanionDeviceManager/res/values-tl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tl/strings.xml
@@ -17,15 +17,14 @@
 <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">"Kasamang Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Payagan ang &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na i-access ang &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Payagan ang app na &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na i-access ang &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"relo"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Pumili ng <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para pamahalaan ng &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Kailangan ang app na ito para mapamahalaan ang iyong <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Papayagan ang <xliff:g id="APP_NAME">%2$s</xliff:g> na mag-sync ng impormasyon, tulad ng pangalan ng isang taong tumatawag, makipag-ugnayan sa mga notification mo, at ma-access ang iyong mga pahintulot sa Telepono, SMS, Mga Contact, Kalendaryo, Mga log ng tawag, at Mga kalapit na device."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Papayagan ang app na ito na mag-sync ng impormasyon, tulad ng pangalan ng isang taong tumatawag, at i-access ang mga pahintulot na ito sa iyong <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Pumili ng device na papamahalaan ng &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Pumili ng <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para mag-set up"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Papayagan ang app na ito na mag-sync ng impormasyon, tulad ng pangalan ng taong tumatawag, at i-access ang mga pahintulot na ito sa iyong <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Payagan ang &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na pamahalaan ang &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"salamin"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Kailangan ang app na ito para mapamahalaan ang <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. Papayagan ang <xliff:g id="APP_NAME">%2$s</xliff:g> na makipag-ugnayan sa mga notification mo at i-access ang iyong mga pahintulot sa Telepono, SMS, Mga Contact, Mikropono, at Mga kalapit na device."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Papayagan ang app na ito na i-access ang mga pahintulot na ito sa iyong <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"device"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Papayagan ang app na ito na i-access ang mga pahintulot na ito sa iyong <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Payagan ang &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na i-access ang impormasyong ito sa iyong telepono"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Mga cross-device na serbisyo"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Ang <xliff:g id="APP_NAME">%1$s</xliff:g> ay humihiling ng pahintulot sa ngalan ng iyong <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> para mag-stream ng mga app sa pagitan ng mga device mo"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Payagan ang &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; na gawin ang pagkilos na ito?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Humihiling ang <xliff:g id="APP_NAME">%1$s</xliff:g> ng pahintulot sa ngalan ng iyong <xliff:g id="DEVICE_NAME">%2$s</xliff:g> para mag-stream ng mga app at iba pang feature ng system sa mga kalapit na device"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Magagawa ng app na ito na mag-sync ng impormasyon, tulad ng pangalan ng isang taong tumatawag, sa pagitan ng iyong telepono at ng <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Magagawa ng app na ito na mag-sync ng impormasyon, tulad ng pangalan ng isang taong tumatawag, sa pagitan ng iyong telepono at ng napiling device"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Payagan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Huwag payagan"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Kanselahin"</string>
     <string name="consent_back" msgid="2560683030046918882">"Bumalik"</string>
     <string name="permission_expand" msgid="893185038020887411">"I-expand ang <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"I-collapse ang <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-tr/strings.xml b/packages/CompanionDeviceManager/res/values-tr/strings.xml
index c5229ab..1e8e842 100644
--- a/packages/CompanionDeviceManager/res/values-tr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tr/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazına erişmesi için &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; uygulamasına izin verin"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazına erişmesi için &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; uygulamasına izin verin"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"saat"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; tarafından yönetilecek bir <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Bu uygulama, <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazınızın yönetilmesi için gereklidir. <xliff:g id="APP_NAME">%2$s</xliff:g> adlı uygulamanın arayan kişinin adı gibi bilgileri senkronize etmesine, bildirimlerinizle etkileşimde bulunup Telefon, SMS, Kişiler, Takvim, Arama kayıtları ve Yakındaki cihazlar izinlerine erişmesine izin verilir."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Bu uygulamanın arayan kişinin adı gibi bilgileri senkronize etmesine ve <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> cihazınızda aşağıdaki izinlere erişmesine izin verilir"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tarafından yönetilecek bir cihaz seçin"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Ayarlamak için bir <xliff:g id="PROFILE_NAME">%1$s</xliff:g> seçin"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Bu uygulamanın arayan kişinin adı gibi bilgileri senkronize etmesine ve <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazınızda aşağıdaki izinlere erişmesine izin verilir"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; uygulamasına &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazını yönetmesi için izin verilsin mi?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"glasses"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Bu uygulama, <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazının yönetilmesi için gereklidir. <xliff:g id="APP_NAME">%2$s</xliff:g> adlı uygulamanın bildirimlerinizle etkileşimde bulunup Telefon, SMS, Kişiler, Mikrofon ve Yakındaki cihazlar izinlerine erişmesine izin verilir."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Bu uygulamanın <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> cihazınızda şu izinlere erişmesine izin verilecek:"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"Cihaz"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Bu uygulamanın <xliff:g id="DEVICE_NAME">%1$s</xliff:g> cihazınızda şu izinlere erişmesine izin verilecek:"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; uygulamasının, telefonunuzdaki bu bilgilere erişmesine izin verin"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Cihazlar arası hizmetler"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g>, cihazlarınız arasında uygulama akışı gerçekleştirmek için <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> cihazınız adına izin istiyor"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; cihazının bu işlemi yapmasına izin verilsin mi?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulaması <xliff:g id="DEVICE_NAME">%2$s</xliff:g> cihazınız adına uygulamaları ve diğer sistem özelliklerini yakındaki cihazlara aktarmak için izin istiyor"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"cihaz"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Bu uygulama, arayan kişinin adı gibi bilgileri telefonunuz ve <xliff:g id="DEVICE_NAME">%1$s</xliff:g> adlı cihaz arasında senkronize edebilir"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Bu uygulama, arayan kişinin adı gibi bilgileri telefonunuz ve seçili cihaz arasında senkronize edebilir"</string>
     <string name="consent_yes" msgid="8344487259618762872">"İzin ver"</string>
     <string name="consent_no" msgid="2640796915611404382">"İzin verme"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"İptal"</string>
     <string name="consent_back" msgid="2560683030046918882">"Geri"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> panelini genişlet"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> panelini daralt"</string>
diff --git a/packages/CompanionDeviceManager/res/values-uk/strings.xml b/packages/CompanionDeviceManager/res/values-uk/strings.xml
index 221a869..39a2b1b 100644
--- a/packages/CompanionDeviceManager/res/values-uk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uk/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"Надати додатку &lt;strong&gt;<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;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Дозволити додатку &lt;strong&gt;<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;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"годинник"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Виберіть <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, яким керуватиме додаток &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Цей додаток потрібен, щоб керувати пристроєм \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Додаток <xliff:g id="APP_NAME">%2$s</xliff:g> зможе синхронізувати інформацію (наприклад, ім’я абонента, який викликає), взаємодіяти з вашими сповіщеннями й отримає дозволи \"Телефон\", \"SMS\", \"Контакти\", \"Календар\", \"Журнали викликів\" і \"Пристрої поблизу\"."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Цей додаток зможе синхронізувати інформацію (наприклад, ім’я абонента, який викликає) і отримає доступ до перелічених нижче дозволів на вашому <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Виберіть пристрій, яким керуватиме додаток &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Виберіть <xliff:g id="PROFILE_NAME">%1$s</xliff:g> для налаштування"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Цей додаток зможе синхронізувати інформацію (наприклад, ім’я абонента, який викликає) і отримає доступ до перелічених нижче дозволів на вашому <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Дозволити додатку &lt;strong&gt;<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;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"окуляри"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Цей додаток потрібен, щоб керувати пристроєм \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Додаток <xliff:g id="APP_NAME">%2$s</xliff:g> зможе взаємодіяти з вашими сповіщеннями й отримає дозволи \"Телефон\", \"SMS\", \"Контакти\", \"Мікрофон\" і \"Пристрої поблизу\"."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Цей додаток матиме доступ до перелічених нижче дозволів на вашому <xliff:g id="DEVICE_TYPE">%1$s</xliff:g>"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"пристрій"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Цей додаток матиме доступ до перелічених нижче дозволів на вашому <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Надайте додатку &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; доступ до цієї інформації з телефона"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Сервіси для кількох пристроїв"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> від імені вашого пристрою \"<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>\" запитує дозвіл на трансляцію додатків між вашими пристроями"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Дозволити додатку &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; виконувати цю дію?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> від імені вашого пристрою (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>) запитує дозвіл на трансляцію додатків та інших системних функцій на пристрої поблизу"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"пристрій"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Цей додаток зможе синхронізувати інформацію (наприклад, ім’я абонента, який викликає) між телефоном і пристроєм \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\""</string>
     <string name="summary_generic" msgid="1761976003668044801">"Цей додаток зможе синхронізувати інформацію (наприклад, ім’я абонента, який викликає) між телефоном і вибраним пристроєм"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Дозволити"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дозволяти"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Скасувати"</string>
     <string name="consent_back" msgid="2560683030046918882">"Назад"</string>
     <string name="permission_expand" msgid="893185038020887411">"Розгорнути дозвіл \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\""</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Згорнути дозвіл \"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>\""</string>
diff --git a/packages/CompanionDeviceManager/res/values-ur/strings.xml b/packages/CompanionDeviceManager/res/values-ur/strings.xml
index 8acfc29..ce44941 100644
--- a/packages/CompanionDeviceManager/res/values-ur/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ur/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"‏‎&lt;strong&gt;<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;‎ تک رسائی کی اجازت دیں؟"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"‏ایپ ‎&lt;strong&gt;<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;‎ تک رسائی کی اجازت دیں؟"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"دیکھیں"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"‏&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; کے ذریعے نظم کئے جانے کیلئے <xliff:g id="PROFILE_NAME">%1$s</xliff:g> کو منتخب کریں"</string>
-    <string name="summary_watch" msgid="898569637110705523">"‏آپ کے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> کا نظم کرنے کے لیے اس ایپ کی ضرورت ہے۔ <xliff:g id="APP_NAME">%2$s</xliff:g> کو کسی کال کرنے والے کے نام جیسی معلومات کی مطابقت پذیری کرنے، آپ کی اطلاعات کے ساتھ تعامل کرنے، آپ کے فون، SMS، رابطے، کیلنڈر، کال لاگز اور قریبی آلات کی اجازتوں تک رسائی حاصل کرنے کی اجازت ہوگی۔"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"اس ایپ کو آپ کے <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> پر کسی کال کرنے والے کے نام جیسی معلومات کی مطابقت پذیری کرنے اور ان اجازتوں تک رسائی کی اجازت ہوگی"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"‏‎&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;‎ کے ذریعے منتخب کیے جانے کیلئے آلہ منتخب کریں"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"سیٹ اپ کرنے کے لیے <xliff:g id="PROFILE_NAME">%1$s</xliff:g> کا انتخاب کریں"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"اس ایپ کو آپ کے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> پر کسی کال کرنے والے کے نام جیسی معلومات کی مطابقت پذیری کرنے اور ان اجازتوں تک رسائی کی اجازت ہوگی"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"‏&lt;strong&gt;<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; کا نظم کرنے کی اجازت دیں؟"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"گلاسز"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"‏<xliff:g id="DEVICE_NAME">%1$s</xliff:g> کا نظم کرنے کے لیے، اس ایپ کی ضرورت ہے۔ <xliff:g id="APP_NAME">%2$s</xliff:g> کو آپ کی اطلاعات کے ساتھ تعامل کرنے اور آپ کے فون، SMS، رابطوں، مائیکروفون اور قریبی آلات کی اجازتوں تک رسائی کی اجازت ہوگی۔"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"اس ایپ کو آپ کے <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> پر ان اجازتوں تک رسائی کی اجازت ہوگی"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"آلہ"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"اس ایپ کو آپ کے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> پر ان اجازتوں تک رسائی کی اجازت ہوگی"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"‏اپنے فون سے ان معلومات تک رسائی حاصل کرنے کی &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; کو اجازت دیں"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"کراس ڈیوائس سروسز"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> ایپ آپ کے <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> کی جانب سے آپ کے آلات کے درمیان ایپس کی سلسلہ بندی کرنے کی اجازت کی درخواست کر رہی ہے"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"‏&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; کو یہ کارروائی انجام دینے کی اجازت دیں؟"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> آپ کے <xliff:g id="DEVICE_NAME">%2$s</xliff:g> کی جانب سے ایپس اور سسٹم کی دیگر خصوصیات کی سلسلہ بندی قریبی آلات پر کرنے کی اجازت طلب کر رہی ہے"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"آلہ"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"یہ ایپ آپ کے فون اور <xliff:g id="DEVICE_NAME">%1$s</xliff:g> کے درمیان معلومات، جیسے کسی کال کرنے والے کے نام، کی مطابقت پذیری کر سکے گی"</string>
     <string name="summary_generic" msgid="1761976003668044801">"یہ ایپ آپ کے فون اور منتخب کردہ آلے کے درمیان معلومات، جیسے کسی کال کرنے والے کے نام، کی مطابقت پذیری کر سکے گی"</string>
     <string name="consent_yes" msgid="8344487259618762872">"اجازت دیں"</string>
     <string name="consent_no" msgid="2640796915611404382">"اجازت نہ دیں"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"منسوخ کریں"</string>
     <string name="consent_back" msgid="2560683030046918882">"پیچھے"</string>
     <string name="permission_expand" msgid="893185038020887411">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> کو پھیلائیں"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g> کو سکیڑیں"</string>
diff --git a/packages/CompanionDeviceManager/res/values-uz/strings.xml b/packages/CompanionDeviceManager/res/values-uz/strings.xml
index 95d5b1b..82b6937 100644
--- a/packages/CompanionDeviceManager/res/values-uz/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uz/strings.xml
@@ -17,15 +17,14 @@
 <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">"Companion Device Manager"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; qurilmasidan foydalanishga ruxsat berilsinmi?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; qurilmasidan foydalanishga ruxsat berilsinmi?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"soat"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; boshqaradigan <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qurilmasini tanlang"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Bu ilova <xliff:g id="DEVICE_NAME">%1$s</xliff:g> profilini boshqarish uchun kerak. <xliff:g id="APP_NAME">%2$s</xliff:g> ilovasiga chaqiruvchining ismi, bildirishnomalar bilan ishlash va telefon, SMS, kontaktlar, taqvim, chaqiruvlar jurnali va yaqin-atrofdagi qurilmalarni aniqlash kabi maʼlumotlarni sinxronlashga ruxsat beriladi."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Bu ilovaga chaqiruvchining ismi kabi maʼlumotlarni sinxronlash va <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> qurilmasida quyidagi amallarni bajarishga ruxsat beriladi"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; boshqaradigan qurilmani tanlang"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Sozlash uchun <xliff:g id="PROFILE_NAME">%1$s</xliff:g> profilini tanlang"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Bu ilovaga chaqiruvchining ismi kabi maʼlumotlarni sinxronlash va <xliff:g id="DEVICE_NAME">%1$s</xliff:g> qurilmasida quyidagi amallarni bajarishga ruxsat beriladi"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; qurilmasini boshqarish uchun ruxsat berilsinmi?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"koʻzoynak"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Bu ilova <xliff:g id="DEVICE_NAME">%1$s</xliff:g> qurilmasini boshqarish uchun kerak. <xliff:g id="APP_NAME">%2$s</xliff:g> ilovasiga bildirishnomalar bilan ishlash va telefon, SMS, kontaktlar, mikrofon va yaqin-atrofdagi qurilmalarga kirishga ruxsat beriladi."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Bu ilova <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> qurilmasida quyidagi ruxsatlarni oladi"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"qurilma"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Bu ilova <xliff:g id="DEVICE_NAME">%1$s</xliff:g> qurilmasida quyidagi ruxsatlarni oladi"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga telefondagi ushbu maʼlumot uchun ruxsat bering"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Qurilmalararo xizmatlar"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"Qurilamalararo ilovalar strimingi uchun <xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> nomidan ruxsat soʻramoqda"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga bu amalni bajarish uchun ruxsat berilsinmi?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi <xliff:g id="DEVICE_NAME">%2$s</xliff:g> qurilmangizdan nomidan atrofdagi qurilmalarga ilova va boshqa tizim funksiyalarini uzatish uchun ruxsat olmoqchi"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"qurilma"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Bu ilova telefoningiz va <xliff:g id="DEVICE_NAME">%1$s</xliff:g> qurilmasida chaqiruvchining ismi kabi maʼlumotlarni sinxronlay oladi"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Bu ilova telefoningiz va tanlangan qurilmada chaqiruvchining ismi kabi maʼlumotlarni sinxronlay oladi"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Ruxsat"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ruxsat berilmasin"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Bekor qilish"</string>
     <string name="consent_back" msgid="2560683030046918882">"Orqaga"</string>
     <string name="permission_expand" msgid="893185038020887411">"Yoyish: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Yopish: <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-vi/strings.xml b/packages/CompanionDeviceManager/res/values-vi/strings.xml
index 24fdd4a..951f429 100644
--- a/packages/CompanionDeviceManager/res/values-vi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-vi/strings.xml
@@ -17,15 +17,14 @@
 <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">"Trình quản lý thiết bị đồng hành"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Cho phép &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; truy cập vào &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Cho phép ứng dụng &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; truy cập vào &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"đồng hồ"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Chọn một <xliff:g id="PROFILE_NAME">%1$s</xliff:g> sẽ do &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; quản lý"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Cần ứng dụng này để quản lý <xliff:g id="DEVICE_NAME">%1$s</xliff:g> của bạn. <xliff:g id="APP_NAME">%2$s</xliff:g> được phép đồng bộ hoá thông tin (ví dụ: tên người gọi), tương tác với thông báo cũng như có các quyền truy cập Điện thoại, Tin nhắn SMS, Danh bạ, Lịch, Nhật ký cuộc gọi và Thiết bị ở gần."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Ứng dụng này sẽ được phép đồng bộ hoá thông tin (chẳng hạn như tên của người đang gọi điện) và dùng những quyền sau trên <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> của bạn"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Chọn một thiết bị sẽ do &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; quản lý"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Chọn một <xliff:g id="PROFILE_NAME">%1$s</xliff:g> để thiết lập"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Ứng dụng này sẽ được phép đồng bộ hoá thông tin (chẳng hạn như tên của người đang gọi điện) và dùng những quyền sau trên <xliff:g id="DEVICE_NAME">%1$s</xliff:g> của bạn"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Cho phép &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; quản lý &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"kính"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Bạn cần có ứng dụng này để quản lý <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> sẽ được phép tương tác với thông báo của bạn, cũng như sử dụng các quyền đối với Điện thoại, SMS, Danh bạ, Micrô và Thiết bị ở gần."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Ứng dụng này sẽ được phép dùng những quyền sau trên <xliff:g id="DEVICE_TYPE">%1$s</xliff:g> của bạn"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"thiết bị"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Ứng dụng này sẽ được phép dùng những quyền sau trên <xliff:g id="DEVICE_NAME">%1$s</xliff:g> của bạn"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Cho phép &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; truy cập vào thông tin này trên điện thoại của bạn"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Dịch vụ trên nhiều thiết bị"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang yêu cầu quyền thay cho <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> để truyền trực tuyến ứng dụng giữa các thiết bị của bạn"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Cho phép &lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt; thực hiện hành động này?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang thay <xliff:g id="DEVICE_NAME">%2$s</xliff:g> yêu cầu quyền truyền trực tuyến ứng dụng và các tính năng khác của hệ thống đến các thiết bị ở gần"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"thiết bị"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Ứng dụng này sẽ đồng bộ hoá thông tin (ví dụ: tên người gọi) giữa điện thoại của bạn và <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Ứng dụng này sẽ đồng bộ hoá thông tin (ví dụ: tên người gọi) giữa điện thoại của bạn và thiết bị bạn chọn"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Cho phép"</string>
     <string name="consent_no" msgid="2640796915611404382">"Không cho phép"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Huỷ"</string>
     <string name="consent_back" msgid="2560683030046918882">"Quay lại"</string>
     <string name="permission_expand" msgid="893185038020887411">"Mở rộng <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Thu gọn <xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
index 401a263..5edf9c0 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"允许&lt;strong&gt;<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;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"允许应用&lt;strong&gt;<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;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"手表"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"选择要由&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
-    <string name="summary_watch" msgid="898569637110705523">"需要使用此应用才能管理您的设备“<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”。<xliff:g id="APP_NAME">%2$s</xliff:g>将能同步信息(例如来电者的姓名)、与通知交互,并能获得对电话、短信、通讯录、日历、通话记录和附近设备的访问权限。"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"该应用将可以同步信息(例如来电者的姓名),并可以获得您<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>上的以下权限"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"选择要由&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;管理的设备"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"选择 <xliff:g id="PROFILE_NAME">%1$s</xliff:g> 进行设置"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"该应用将可以同步信息(例如来电者的姓名),并可以获得您<xliff:g id="DEVICE_NAME">%1$s</xliff:g>上的以下权限"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"允许&lt;strong&gt;<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;?"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"眼镜"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"需要使用此应用才能管理<xliff:g id="DEVICE_NAME">%1$s</xliff:g>。“<xliff:g id="APP_NAME">%2$s</xliff:g>”将能与通知交互,并可获得电话、短信、通讯录、麦克风和附近设备的访问权限。"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"该应用将可以获得您<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>上的以下权限"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"设备"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"该应用将可以获得您<xliff:g id="DEVICE_NAME">%1$s</xliff:g>上的以下权限"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"允许“<xliff:g id="APP_NAME">%1$s</xliff:g>”&lt;strong&gt;&lt;/strong&gt;访问您手机中的这项信息"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"跨设备服务"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”正代表您的<xliff:g id="DISPLAY_NAME">%2$s</xliff:g>请求在您的设备之间流式传输应用内容"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"允许&lt;strong&gt;<xliff:g id="DEVICE_NAME">%1$s</xliff:g>&lt;/strong&gt;进行此操作?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”正代表您的<xliff:g id="DEVICE_NAME">%2$s</xliff:g>请求将应用和其他系统功能流式传输到附近的设备"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"设备"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"此应用将能在您的手机和“<xliff:g id="DEVICE_NAME">%1$s</xliff:g>”之间同步信息,例如来电者的姓名"</string>
     <string name="summary_generic" msgid="1761976003668044801">"此应用将能在您的手机和所选设备之间同步信息,例如来电者的姓名"</string>
     <string name="consent_yes" msgid="8344487259618762872">"允许"</string>
     <string name="consent_no" msgid="2640796915611404382">"不允许"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"取消"</string>
     <string name="consent_back" msgid="2560683030046918882">"返回"</string>
     <string name="permission_expand" msgid="893185038020887411">"展开<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"收起<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
index 52230cb..0878c23 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"要允許「<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="confirmation_title" msgid="2244241995958340998">"要允許「<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">"選擇由 &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; 管理的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
-    <string name="summary_watch" msgid="898569637110705523">"必須使用此應用程式,才能管理「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」。「<xliff:g id="APP_NAME">%2$s</xliff:g>」將可同步資訊 (例如來電者的名稱)、透過通知與你互動,並存取電話、短訊、通訊錄、日曆、通話記錄和附近的裝置權限。"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"此應用程式將可同步資訊 (例如來電者的名稱),並可在<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>上取得以下權限"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"選擇要讓 &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; 管理的裝置"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"選擇要設定的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"此應用程式將可同步資訊 (例如來電者的名稱),並可在<xliff:g id="DEVICE_NAME">%1$s</xliff:g>上取得以下權限"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"要允許「<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_glasses" msgid="8488394059007275998">"眼鏡"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"必須使用此應用程式,才能管理「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」。「<xliff:g id="APP_NAME">%2$s</xliff:g>」將可透過通知與你互動,並存取電話、短訊、通訊錄、麥克風和附近的裝置權限。"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"此應用程式將可在<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>上取得以下權限"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"裝置"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"此應用程式將可在<xliff:g id="DEVICE_NAME">%1$s</xliff:g>上取得以下權限"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取你手機中的這項資料"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"跨裝置服務"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表 <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> 要求權限,以便在裝置間串流應用程式的內容"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"要允許「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;執行此操作嗎?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」要求權限,才能在附近的裝置上串流播放應用程式和其他系統功能"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"裝置"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"此應用程式將可同步手機和「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」的資訊,例如來電者的名稱"</string>
     <string name="summary_generic" msgid="1761976003668044801">"此應用程式將可同步手機和所選裝置的資訊,例如來電者的名稱"</string>
     <string name="consent_yes" msgid="8344487259618762872">"允許"</string>
     <string name="consent_no" msgid="2640796915611404382">"不允許"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"取消"</string>
     <string name="consent_back" msgid="2560683030046918882">"返回"</string>
     <string name="permission_expand" msgid="893185038020887411">"展開<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"收合<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
index c680157..4f50ef5 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
@@ -17,15 +17,14 @@
 <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="confirmation_title" msgid="4593465730772390351">"要允許「<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="confirmation_title" msgid="2244241995958340998">"要允許「<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>
-    <string name="summary_watch" msgid="898569637110705523">"你必須使用這個應用程式,才能管理<xliff:g id="DEVICE_NAME">%1$s</xliff:g>。「<xliff:g id="APP_NAME">%2$s</xliff:g>」將可同步資訊 (例如來電者名稱)、存取通知及在通知上執行操作,並取得電話、簡訊、聯絡人、日曆、通話記錄、麥克風和鄰近裝置權限。"</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"這個應用程式將可同步處理資訊 (例如來電者名稱)、取得<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>上的這些權限"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"選擇要讓「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;管理的裝置"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"選擇要設定的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"這個應用程式將可同步處理資訊 (例如來電者名稱)、取得<xliff:g id="DEVICE_NAME">%1$s</xliff:g>上的這些權限"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"要允許「<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_glasses" msgid="8488394059007275998">"眼鏡"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"你必須使用這個應用程式,才能管理「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」。「<xliff:g id="APP_NAME">%2$s</xliff:g>」將可存取通知及在通知上執行操作,並取得電話、簡訊、聯絡人、麥克風和鄰近裝置權限。"</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"這個應用程式將可取得<xliff:g id="DEVICE_TYPE">%1$s</xliff:g>上的這些權限"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"裝置"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"這個應用程式將可取得<xliff:g id="DEVICE_NAME">%1$s</xliff:g>上的這些權限"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;存取手機中的這項資訊"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"跨裝置服務"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"為了在裝置間串流傳輸應用程式內容,「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表 <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> 要求相關權限"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"要允許「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」&lt;strong&gt;&lt;/strong&gt;執行這項操作嗎?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在代表「<xliff:g id="DEVICE_NAME">%2$s</xliff:g>」要求必要權限,才能在鄰近裝置上串流播放應用程式和其他系統功能"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"裝置"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"這個應用程式將可在手機和「<xliff:g id="DEVICE_NAME">%1$s</xliff:g>」之間同步資訊,例如來電者名稱"</string>
     <string name="summary_generic" msgid="1761976003668044801">"這個應用程式將可在手機和指定裝置間同步資訊,例如來電者名稱"</string>
     <string name="consent_yes" msgid="8344487259618762872">"允許"</string>
     <string name="consent_no" msgid="2640796915611404382">"不允許"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"取消"</string>
     <string name="consent_back" msgid="2560683030046918882">"返回"</string>
     <string name="permission_expand" msgid="893185038020887411">"展開<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"收合<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values-zu/strings.xml b/packages/CompanionDeviceManager/res/values-zu/strings.xml
index 22495e2..7fb5ece 100644
--- a/packages/CompanionDeviceManager/res/values-zu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zu/strings.xml
@@ -17,15 +17,14 @@
 <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">"Isiphathi sedivayisi esihambisanayo"</string>
-    <string name="confirmation_title" msgid="4593465730772390351">"Vumela &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ukufinyelela &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
+    <string name="confirmation_title" msgid="2244241995958340998">"Vumela i-app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ukufinyelela &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;?"</string>
     <string name="profile_name_watch" msgid="576290739483672360">"buka"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Khetha i-<xliff:g id="PROFILE_NAME">%1$s</xliff:g> ezophathwa yi-&lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="summary_watch" msgid="898569637110705523">"Le app iyadingeka ukuphatha i-<xliff:g id="DEVICE_NAME">%1$s</xliff:g> yakho. I-<xliff:g id="APP_NAME">%2$s</xliff:g> izovunyelwa ukuvumelanisa ulwazi, njengegama lomuntu othile ofonayo, ukusebenzisana nezaziso zakho futhi ufinyelele Ifoni yakho, i-SMS, Oxhumana Nabo, Ikhalenda, Amarekhodi Amakholi nezimvume zamadivayisi aseduze."</string>
-    <string name="summary_watch_single_device" msgid="3173948915947011333">"Le-app izovunyelwa ukuvumelanisa ulwazi, olufana negama lomuntu ofonayo, iphinde ifinyelele lezi zimvume ku-<xliff:g id="DEVICE_TYPE">%1$s</xliff:g> yakho"</string>
+    <string name="chooser_title_non_profile" msgid="6035023914517087400">"Khetha idivayisi engaphathwa nge-&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
+    <string name="chooser_title" msgid="2235819929238267637">"Khetha i-<xliff:g id="PROFILE_NAME">%1$s</xliff:g> ukusetha"</string>
+    <string name="summary_watch" msgid="7962014927042971830">"Le-app izovunyelwa ukuvumelanisa ulwazi, olufana negama lomuntu ofonayo, iphinde ifinyelele lezi zimvume ku-<xliff:g id="DEVICE_NAME">%1$s</xliff:g> yakho"</string>
     <string name="confirmation_title_glasses" msgid="8288346850537727333">"Vumela i-&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ukuthi ifinyelele i-&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_name_glasses" msgid="8488394059007275998">"Izingilazi"</string>
-    <string name="summary_glasses_multi_device" msgid="615259525961937348">"Le app iyadingeka ukuphatha i-<xliff:g id="DEVICE_NAME">%1$s</xliff:g>. I-<xliff:g id="APP_NAME">%2$s</xliff:g> izovunyelwa ukuthi ihlanganyele nezaziso zakho futhi ifinyelele kufoni yakho, i-SMS, Oxhumana nabo, Imakrofoni Nezimvume zamadivayisi aseduze."</string>
-    <string name="summary_glasses_single_device" msgid="3000909894067413398">"Le-app izovunyelwa ukufinyelela lezi zimvume ku-<xliff:g id="DEVICE_TYPE">%1$s</xliff:g> yakho"</string>
+    <string name="profile_name_glasses" msgid="3506504967216601277">"idivayisi"</string>
+    <string name="summary_glasses" msgid="2872254734959842579">"Le-app izovunyelwa ukufinyelela lezi zimvume ku-<xliff:g id="DEVICE_NAME">%1$s</xliff:g> yakho"</string>
     <string name="title_app_streaming" msgid="2270331024626446950">"Vumela i-&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ifinyelele lolu lwazi kusukela efonini yakho"</string>
     <string name="helper_title_app_streaming" msgid="4151687003439969765">"Amasevisi amadivayisi amaningi"</string>
     <string name="helper_summary_app_streaming" msgid="2396773196949578425">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> icela imvume esikhundleni se-<xliff:g id="DISPLAY_NAME">%2$s</xliff:g> yakho ukuze isakaze-bukhoma ama-app phakathi kwamadivayisi akho"</string>
@@ -38,10 +37,10 @@
     <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Vumela i-<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ukwenza lesi senzo?"</string>
     <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> icela imvume esikhundleni se-<xliff:g id="DEVICE_NAME">%2$s</xliff:g> ukusakaza ama-app nezinye izakhi zesistimu kumadivayisi aseduze"</string>
     <string name="profile_name_generic" msgid="6851028682723034988">"idivayisi"</string>
-    <string name="summary_generic_single_device" msgid="4181180669689590417">"Le app izokwazi ukuvumelanisa ulwazi, njengegama lomuntu othile ofonayo, phakathi kwefoni yakho ne-<xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="summary_generic" msgid="1761976003668044801">"Le app izokwazi ukuvumelanisa ulwazi, njengegama lomuntu othile ofonayo, phakathi kwefoni yakho nedivayisi ekhethiwe"</string>
     <string name="consent_yes" msgid="8344487259618762872">"Vumela"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ungavumeli"</string>
+    <string name="consent_cancel" msgid="5655005528379285841">"Khansela"</string>
     <string name="consent_back" msgid="2560683030046918882">"Emuva"</string>
     <string name="permission_expand" msgid="893185038020887411">"Nweba i-<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
     <string name="permission_collapse" msgid="3320833884220844084">"Goqa i-<xliff:g id="PERMISSION_TYPE">%1$s</xliff:g>"</string>
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 2502bbf..7a6fad4 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -20,7 +20,7 @@
     <string name="app_label">Companion Device Manager</string>
 
     <!-- Title of the device association confirmation dialog. -->
-    <string name="confirmation_title">Allow &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g>&lt;/strong&gt;?</string>
+    <string name="confirmation_title">Allow the app &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g>&lt;/strong&gt;?</string>
 
     <!-- ================= DEVICE_PROFILE_WATCH and null profile ================= -->
 
@@ -28,13 +28,13 @@
     <string name="profile_name_watch">watch</string>
 
     <!-- Title of the device selection dialog. -->
-    <string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to be managed by &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%2$s</xliff:g>&lt;/strong&gt;</string>
+    <string name="chooser_title_non_profile">Choose a device to be managed by &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt;</string>
 
-    <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile (type) [CHAR LIMIT=NONE] -->
-    <string name="summary_watch">This app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
+    <!-- Tile of the multiple devices' dialog. -->
+    <string name="chooser_title">Choose a <xliff:g id="profile_name" example="watch">%1$s</xliff:g> to set up</string>
 
-    <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
-    <string name="summary_watch_single_device">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_type" example="phone">%1$s</xliff:g></string>
+    <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile [CHAR LIMIT=NONE] -->
+    <string name="summary_watch">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
 
     <!-- ================= DEVICE_PROFILE_GLASSES ================= -->
 
@@ -42,13 +42,10 @@
     <string name="confirmation_title_glasses">Allow &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to manage &lt;strong&gt;<xliff:g id="device_name" example="Glasses">%2$s</xliff:g>&lt;/strong&gt;?</string>
 
     <!-- The name of the "glasses" device type [CHAR LIMIT=30] -->
-    <string name="profile_name_glasses">glasses</string>
+    <string name="profile_name_glasses">device</string>
 
-    <!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile (type) [CHAR LIMIT=NONE] -->
-    <string name="summary_glasses_multi_device">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
-
-    <!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
-    <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your <xliff:g id="device_type" example="phone">%1$s</xliff:g></string>
+    <!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile [CHAR LIMIT=NONE] -->
+    <string name="summary_glasses">This app will be allowed to access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
 
     <!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
 
@@ -97,9 +94,6 @@
     <string name="profile_name_generic">device</string>
 
     <!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] -->
-    <string name="summary_generic_single_device">This app will be able to sync info, like the name of someone calling, between your phone and <xliff:g id="device_name" example="My Watch">%1$s</xliff:g></string>
-
-    <!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] -->
     <string name="summary_generic">This app will be able to sync info, like the name of someone calling, between your phone and the chosen device</string>
 
     <!-- ================= Buttons ================= -->
@@ -110,6 +104,9 @@
     <!-- Negative button for the device-app association consent dialog [CHAR LIMIT=30] -->
     <string name="consent_no">Don\u2019t allow</string>
 
+    <!-- Cancel button for the device chooser dialog [CHAR LIMIT=30] -->
+    <string name="consent_cancel">Cancel</string>
+
     <!-- Back button for the helper consent dialog [CHAR LIMIT=30] -->
     <string name="consent_back">Back</string>
 
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index e85190b..222877b 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -69,11 +69,13 @@
 
     <style name="PositiveButton"
            parent="@android:style/Widget.Material.Button.Borderless.Colored">
-        <item name="android:layout_width">300dp</item>
-        <item name="android:layout_height">56dp</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_marginBottom">2dp</item>
         <item name="android:textAllCaps">false</item>
         <item name="android:textSize">14sp</item>
+        <item name="android:layout_marginStart">32dp</item>
+        <item name="android:layout_marginEnd">32dp</item>
         <item name="android:textColor">@android:color/system_neutral1_900</item>
         <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
         <item name="android:background">@drawable/btn_positive_bottom</item>
@@ -81,11 +83,13 @@
 
     <style name="NegativeButton"
            parent="@android:style/Widget.Material.Button.Borderless.Colored">
-        <item name="android:layout_width">300dp</item>
-        <item name="android:layout_height">56dp</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_marginTop">2dp</item>
         <item name="android:textAllCaps">false</item>
         <item name="android:textSize">14sp</item>
+        <item name="android:layout_marginStart">32dp</item>
+        <item name="android:layout_marginEnd">32dp</item>
         <item name="android:textColor">@android:color/system_neutral1_900</item>
         <item name="android:layout_marginTop">4dp</item>
         <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 4154029..97016f5 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -27,10 +27,8 @@
 
 import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState;
 import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState.FINISHED_TIMEOUT;
-import static com.android.companiondevicemanager.CompanionDeviceResources.MULTI_DEVICES_SUMMARIES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_TYPES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME;
-import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME_MULTI;
 import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICON;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUMMARIES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES;
@@ -121,6 +119,9 @@
     private IAssociationRequestCallback mAppCallback;
     private ResultReceiver mCdmServiceReceiver;
 
+    // Present for application's name.
+    private CharSequence mAppLabel;
+
     // Always present widgets.
     private TextView mTitle;
     private TextView mSummary;
@@ -165,8 +166,7 @@
     private @Nullable RecyclerView mDeviceListRecyclerView;
     private @Nullable DeviceListAdapter mDeviceAdapter;
 
-
-    // The recycler view is only shown for selfManaged and singleDevice  association request.
+    // The recycler view is shown for non-null profile association request.
     private @Nullable RecyclerView mPermissionListRecyclerView;
     private @Nullable PermissionListAdapter mPermissionListAdapter;
 
@@ -178,8 +178,6 @@
     // onActivityResult() after the association is created.
     private @Nullable DeviceFilterPair<?> mSelectedDevice;
 
-    private @Nullable List<Integer> mPermissionTypes;
-
     private LinearLayoutManager mPermissionsLayoutManager = new LinearLayoutManager(this);
 
     @Override
@@ -302,6 +300,8 @@
 
         setContentView(R.layout.activity_confirmation);
 
+        mAppLabel = appLabel;
+
         mConstraintList = findViewById(R.id.constraint_list);
         mAssociationConfirmationDialog = findViewById(R.id.association_confirmation);
         mVendorHeader = findViewById(R.id.vendor_header);
@@ -322,7 +322,6 @@
 
         mMultipleDeviceSpinner = findViewById(R.id.spinner_multiple_device);
         mSingleDeviceSpinner = findViewById(R.id.spinner_single_device);
-        mDeviceAdapter = new DeviceListAdapter(this, this::onListItemClick);
 
         mPermissionListRecyclerView = findViewById(R.id.permission_list);
         mPermissionListAdapter = new PermissionListAdapter(this);
@@ -468,8 +467,6 @@
             throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
 
-        mPermissionTypes = new ArrayList<>();
-
         try {
             vendorIcon = getVendorHeaderIcon(this, packageName, userId);
             vendorName = getVendorHeaderName(this, packageName, userId);
@@ -486,17 +483,13 @@
         }
 
         title = getHtmlFromResources(this, TITLES.get(deviceProfile), deviceName);
-        mPermissionTypes.addAll(PERMISSION_TYPES.get(deviceProfile));
+        setupPermissionList(deviceProfile);
 
         // Summary is not needed for selfManaged dialog.
         mSummary.setVisibility(View.GONE);
-
-        setupPermissionList();
-
         mTitle.setText(title);
         mVendorHeaderName.setText(vendorName);
         mVendorHeader.setVisibility(View.VISIBLE);
-        mVendorHeader.setVisibility(View.VISIBLE);
         mProfileIcon.setVisibility(View.GONE);
         mDeviceListRecyclerView.setVisibility(View.GONE);
         // Top and bottom borders should be gone for selfManaged dialog.
@@ -509,7 +502,9 @@
 
         final String deviceProfile = mRequest.getDeviceProfile();
 
-        mPermissionTypes = new ArrayList<>();
+        if (!SUPPORTED_PROFILES.contains(deviceProfile)) {
+            throw new RuntimeException("Unsupported profile " + deviceProfile);
+        }
 
         CompanionDeviceDiscoveryService.getScanResult().observe(this,
                 deviceFilterPairs -> updateSingleDeviceUi(
@@ -529,75 +524,40 @@
         if (deviceFilterPairs.isEmpty()) return;
 
         mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
-        // No need to show user consent dialog if it is a singleDevice
-        // and isSkipPrompt(true) AssociationRequest.
-        // See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
-        if (mRequest.isSkipPrompt()) {
-            mSingleDeviceSpinner.setVisibility(View.GONE);
-            onUserSelectedDevice(mSelectedDevice);
-            return;
-        }
 
-        final String deviceName = mSelectedDevice.getDisplayName();
-        final Spanned title;
-        final Spanned summary;
-        final Drawable profileIcon;
+        final Drawable profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
 
-        if (!SUPPORTED_PROFILES.contains(deviceProfile)) {
-            throw new RuntimeException("Unsupported profile " + deviceProfile);
-        }
+        updatePermissionUi();
 
-        if (deviceProfile == null) {
-            summary = getHtmlFromResources(this, SUMMARIES.get(null), deviceName);
-            mConstraintList.setVisibility(View.GONE);
-        } else {
-            summary = getHtmlFromResources(
-                    this, SUMMARIES.get(deviceProfile), getString(R.string.device_type));
-            mPermissionTypes.addAll(PERMISSION_TYPES.get(deviceProfile));
-            setupPermissionList();
-        }
-
-        title = getHtmlFromResources(this, TITLES.get(deviceProfile), appLabel, deviceName);
-        profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
-
-        mTitle.setText(title);
-        mSummary.setText(summary);
         mProfileIcon.setImageDrawable(profileIcon);
-        mSingleDeviceSpinner.setVisibility(View.GONE);
         mAssociationConfirmationDialog.setVisibility(View.VISIBLE);
+        mSingleDeviceSpinner.setVisibility(View.GONE);
     }
 
     private void initUiForMultipleDevices(CharSequence appLabel) {
         if (DEBUG) Log.i(TAG, "initUiFor_MultipleDevices()");
 
-        final String deviceProfile = mRequest.getDeviceProfile();
-
-        final String profileName;
-        final String profileNameMulti;
-        final Spanned summary;
         final Drawable profileIcon;
-        final int summaryResourceId;
+        final Spanned title;
+        final String deviceProfile = mRequest.getDeviceProfile();
 
         if (!SUPPORTED_PROFILES.contains(deviceProfile)) {
             throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
 
-        profileName = getString(PROFILES_NAME.get(deviceProfile));
-        profileNameMulti = getString(PROFILES_NAME_MULTI.get(deviceProfile));
         profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
-        summaryResourceId = MULTI_DEVICES_SUMMARIES.get(deviceProfile);
 
         if (deviceProfile == null) {
-            summary = getHtmlFromResources(this, summaryResourceId);
+            title = getHtmlFromResources(this, R.string.chooser_title_non_profile, appLabel);
+            mButtonNotAllowMultipleDevices.setText(R.string.consent_no);
         } else {
-            summary = getHtmlFromResources(this, summaryResourceId, profileName, appLabel);
+            title = getHtmlFromResources(this,
+                    R.string.chooser_title, getString(PROFILES_NAME.get(deviceProfile)));
         }
 
-        final Spanned title = getHtmlFromResources(
-                this, R.string.chooser_title, profileNameMulti, appLabel);
+        mDeviceAdapter = new DeviceListAdapter(this, this::onDeviceClicked);
 
         mTitle.setText(title);
-        mSummary.setText(summary);
         mProfileIcon.setImageDrawable(profileIcon);
 
         mDeviceListRecyclerView.setAdapter(mDeviceAdapter);
@@ -613,6 +573,7 @@
                     mDeviceAdapter.setDevices(deviceFilterPairs);
                 });
 
+        mSummary.setVisibility(View.GONE);
         // "Remove" consent button: users would need to click on the list item.
         mButtonAllow.setVisibility(View.GONE);
         mButtonNotAllow.setVisibility(View.GONE);
@@ -623,11 +584,9 @@
         mMultipleDeviceSpinner.setVisibility(View.VISIBLE);
     }
 
-    private void onListItemClick(int position) {
-        if (DEBUG) Log.d(TAG, "onListItemClick() " + position);
-
+    private void onDeviceClicked(int position) {
         final DeviceFilterPair<?> selectedDevice = mDeviceAdapter.getItem(position);
-
+        // To prevent double tap on the selected device.
         if (mSelectedDevice != null) {
             if (DEBUG) Log.w(TAG, "Already selected.");
             return;
@@ -637,7 +596,47 @@
 
         mSelectedDevice = requireNonNull(selectedDevice);
 
-        onUserSelectedDevice(selectedDevice);
+        Log.d(TAG, "onDeviceClicked(): " + mSelectedDevice.toShortString());
+
+        updatePermissionUi();
+
+        mSummary.setVisibility(View.VISIBLE);
+        mButtonAllow.setVisibility(View.VISIBLE);
+        mButtonNotAllow.setVisibility(View.VISIBLE);
+        mDeviceListRecyclerView.setVisibility(View.GONE);
+        mNotAllowMultipleDevicesLayout.setVisibility(View.GONE);
+    }
+
+    private void updatePermissionUi() {
+        final String deviceProfile = mRequest.getDeviceProfile();
+        final int summaryResourceId = SUMMARIES.get(deviceProfile);
+        final String remoteDeviceName = mSelectedDevice.getDisplayName();
+        final Spanned title = getHtmlFromResources(
+                this, TITLES.get(deviceProfile), mAppLabel, remoteDeviceName);
+        final Spanned summary;
+
+        // No need to show permission consent dialog if it is a isSkipPrompt(true)
+        // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
+        if (mRequest.isSkipPrompt()) {
+            mSingleDeviceSpinner.setVisibility(View.GONE);
+            onUserSelectedDevice(mSelectedDevice);
+            return;
+        }
+
+        if (deviceProfile == null && mRequest.isSingleDevice()) {
+            summary = getHtmlFromResources(this, summaryResourceId, remoteDeviceName);
+            mConstraintList.setVisibility(View.GONE);
+        } else if (deviceProfile == null) {
+            onUserSelectedDevice(mSelectedDevice);
+            return;
+        } else {
+            summary = getHtmlFromResources(
+                    this, summaryResourceId, getString(R.string.device_type));
+            setupPermissionList(deviceProfile);
+        }
+
+        mTitle.setText(title);
+        mSummary.setText(summary);
     }
 
     private void onPositiveButtonClick(View v) {
@@ -680,8 +679,9 @@
     // initiate the layoutManager for the recyclerview, add listeners for monitoring the scrolling
     // and when mPermissionListRecyclerView is fully populated.
     // Lastly, disable the Allow and Don't allow buttons.
-    private void setupPermissionList() {
-        mPermissionListAdapter.setPermissionType(mPermissionTypes);
+    private void setupPermissionList(String deviceProfile) {
+        final List<Integer> permissionTypes = new ArrayList<>(PERMISSION_TYPES.get(deviceProfile));
+        mPermissionListAdapter.setPermissionType(permissionTypes);
         mPermissionListRecyclerView.setAdapter(mPermissionListAdapter);
         mPermissionListRecyclerView.setLayoutManager(mPermissionsLayoutManager);
 
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index 7aed139..060c032 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -86,21 +86,11 @@
     static final Map<String, Integer> SUMMARIES;
     static {
         final Map<String, Integer> map = new ArrayMap<>();
-        map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch_single_device);
-        map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses_single_device);
-        map.put(null, R.string.summary_generic_single_device);
-
-        SUMMARIES = unmodifiableMap(map);
-    }
-
-    static final Map<String, Integer> MULTI_DEVICES_SUMMARIES;
-    static {
-        final Map<String, Integer> map = new ArrayMap<>();
         map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch);
-        map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses_multi_device);
+        map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses);
         map.put(null, R.string.summary_generic);
 
-        MULTI_DEVICES_SUMMARIES = unmodifiableMap(map);
+        SUMMARIES = unmodifiableMap(map);
     }
 
     static final Map<String, Integer> PROFILES_NAME;
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index b265a42..25ac3c9 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -16,11 +16,10 @@
 
 package com.android.dynsystem;
 
-import static android.os.AsyncTask.Status.FINISHED;
-import static android.os.AsyncTask.Status.PENDING;
 import static android.os.AsyncTask.Status.RUNNING;
 import static android.os.image.DynamicSystemClient.ACTION_HIDE_NOTIFICATION;
 import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_IF_IN_USE;
+import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_KEYGUARD_DISMISSED;
 import static android.os.image.DynamicSystemClient.ACTION_START_INSTALL;
 import static android.os.image.DynamicSystemClient.CAUSE_ERROR_EXCEPTION;
 import static android.os.image.DynamicSystemClient.CAUSE_ERROR_INVALID_URL;
@@ -234,6 +233,8 @@
             executeNotifyIfInUseCommand();
         } else if (ACTION_HIDE_NOTIFICATION.equals(action)) {
             executeHideNotificationCommand();
+        } else if (ACTION_NOTIFY_KEYGUARD_DISMISSED.equals(action)) {
+            executeNotifyKeyguardDismissed();
         }
 
         return Service.START_NOT_STICKY;
@@ -418,12 +419,23 @@
         mDynSystem.remove();
     }
 
+    private boolean isDsuSlotLocked() {
+        // Slot names ending with ".lock" are a customized installation.
+        // We expect the client app to provide custom UI to enter/exit DSU mode.
+        // We will ignore the ACTION_REBOOT_TO_NORMAL command and will not show
+        // notifications in this case.
+        return mDynSystem.getActiveDsuSlot().endsWith(".lock");
+    }
+
     private void executeRebootToNormalCommand() {
         if (!isInDynamicSystem()) {
             Log.e(TAG, "It's already running in normal system.");
             return;
         }
-
+        if (isDsuSlotLocked()) {
+            Log.e(TAG, "Ignore the reboot intent for a locked DSU slot");
+            return;
+        }
         if (!mDynSystem.setEnable(/* enable = */ false, /* oneShot = */ false)) {
             Log.e(TAG, "Failed to disable DynamicSystem.");
 
@@ -445,13 +457,13 @@
     private void executeNotifyIfInUseCommand() {
         switch (getStatus()) {
             case STATUS_IN_USE:
-                if (!mHideNotification) {
+                if (!mHideNotification && !isDsuSlotLocked()) {
                     startForeground(NOTIFICATION_ID,
                             buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
                 }
                 break;
             case STATUS_READY:
-                if (!mHideNotification) {
+                if (!mHideNotification && !isDsuSlotLocked()) {
                     startForeground(NOTIFICATION_ID,
                             buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
                 }
@@ -474,6 +486,10 @@
         }
     }
 
+    private void executeNotifyKeyguardDismissed() {
+        postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED, null);
+    }
+
     private void resetTaskAndStop() {
         resetTaskAndStop(/* removeNotification= */ false);
     }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
index b522729..7401691 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.dynsystem;
 
+import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_KEYGUARD_DISMISSED;
 import static android.os.image.DynamicSystemClient.KEY_KEYGUARD_USE_DEFAULT_STRINGS;
 
 import android.app.Activity;
@@ -83,11 +84,19 @@
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
             startInstallationService();
+        } else {
+            notifyKeyguardDismissed();
         }
 
         finish();
     }
 
+    private void notifyKeyguardDismissed() {
+        Intent intent = new Intent(this, DynamicSystemInstallationService.class);
+        intent.setAction(ACTION_NOTIFY_KEYGUARD_DISMISSED);
+        startServiceAsUser(intent, UserHandle.SYSTEM);
+    }
+
     private void startInstallationService() {
         // retrieve data from calling intent
         Intent callingIntent = getIntent();
diff --git a/packages/EasterEgg/res/values/q_puzzles.xml b/packages/EasterEgg/res/values/q_puzzles.xml
index 7c2eff1..2a477f5 100644
--- a/packages/EasterEgg/res/values/q_puzzles.xml
+++ b/packages/EasterEgg/res/values/q_puzzles.xml
@@ -44,7 +44,6 @@
 
         <item>android:drawable/ic_audio_alarm</item>
         <item>android:drawable/ic_audio_alarm_mute</item>
-        <item>android:drawable/ic_bluetooth_share_icon</item>
         <item>android:drawable/ic_bt_headphones_a2dp</item>
         <item>android:drawable/ic_bt_headset_hfp</item>
         <item>android:drawable/ic_bt_hearing_aid</item>
diff --git a/packages/PackageInstaller/res/values-af/strings.xml b/packages/PackageInstaller/res/values-af/strings.xml
index 3545179..14ed179 100644
--- a/packages/PackageInstaller/res/values-af/strings.xml
+++ b/packages/PackageInstaller/res/values-af/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Program geïnstalleer."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Wil jy hierdie program installeer?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Wil jy hierdie program opdateer?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Dateer hierdie app vanaf <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> op?\n\nHierdie app kry gewoonlik opdaterings vanaf <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. As jy vanaf ’n ander bron opdateer, kan jy in die toekoms dalk opdaterings vanaf enige bron op jou foon kry. Appfunksie kan verander."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Dateer hierdie app vanaf <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> op?\n\nHierdie app kry gewoonlik opdaterings vanaf <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. As jy vanaf ’n ander bron opdateer, kan jy in die toekoms dalk opdaterings vanaf enige bron op jou tablet kry. Appfunksies kan verander."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Dateer hierdie app vanaf <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> op?\n\nHierdie app kry gewoonlik opdaterings vanaf <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. As jy vanaf ’n ander bron opdateer, kan jy in die toekoms dalk opdaterings vanaf enige bron op jou TV kry. Appfunksies kan verander."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Dateer hierdie app vanaf <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> op?\n\nHierdie app kry gewoonlik opdaterings vanaf <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. As jy vanaf ’n ander bron opdateer, kan jy in die toekoms dalk opdaterings vanaf enige bron op jou foon kry. Appfunksie kan verander."</string>
     <string name="install_failed" msgid="5777824004474125469">"Program nie geïnstalleer nie."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Die installering van die pakket is geblokkeer."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Program is nie geïnstalleer nie omdat pakket met \'n bestaande pakket bots."</string>
diff --git a/packages/PackageInstaller/res/values-am/strings.xml b/packages/PackageInstaller/res/values-am/strings.xml
index 3dab467c..1fb5c12 100644
--- a/packages/PackageInstaller/res/values-am/strings.xml
+++ b/packages/PackageInstaller/res/values-am/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"መተግበሪያ ተጭኗል።"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"ይህን መተግበሪያ መጫን ይፈልጋሉ?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ይህን መተግበሪያ ማዘመን ይፈልጋሉ?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"ይህ መተግበሪያ ከ<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ይዘምን?\n\nይህ መተግበሪያ በመደበኛነት ዝማኔዎችን ከ<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> ይቀበላል። ከተለየ ምንጭ በማዘመን በስልክዎ ላይ ካለ ማንኛውም ምንጭ የወደፊት ዝማኔዎችን ሊቀበሉ ይችላሉ። የመተግበሪያ ተግባራዊነት ሊለወጥ ይችላል።"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"ይህ መተግበሪያ ከ<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ይዘምን?\n\nይህ መተግበሪያ በመደበኛነት ዝማኔዎችን ከ<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> ይቀበላል። ከተለየ ምንጭ በማዘመን በጡባዊዎ ላይ ካለ ማንኛውም ምንጭ የወደፊት ዝማኔዎችን ሊቀበሉ ይችላሉ። የመተግበሪያ ተግባራዊነት ሊለወጥ ይችላል።"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"ይህ መተግበሪያ ከ<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ይዘምን?\n\nይህ መተግበሪያ በመደበኛነት ዝማኔዎችን ከ<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> ይቀበላል። ከተለየ ምንጭ በማዘመን በቲቪዎ ላይ ካለ ማንኛውም ምንጭ የወደፊት ዝማኔዎችን ሊቀበሉ ይችላሉ። የመተግበሪያ ተግባራዊነት ሊለወጥ ይችላል።"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"ይህ መተግበሪያ ከ<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ይዘምን?\n\nይህ መተግበሪያ በመደበኛነት ዝማኔዎችን ከ<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> ይቀበላል። ከተለየ ምንጭ በማዘመን በስልክዎ ላይ ካለ ማንኛውም ምንጭ የወደፊት ዝማኔዎችን ሊቀበሉ ይችላሉ። የመተግበሪያ ተግባራዊነት ሊለወጥ ይችላል።"</string>
     <string name="install_failed" msgid="5777824004474125469">"መተግበሪያ አልተጫነም።"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ጥቅሉ እንዳይጫን ታግዷል።"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"እንደ ጥቅል ያልተጫነ መተግበሪያ ከነባር ጥቅል ጋር ይጋጫል።"</string>
diff --git a/packages/PackageInstaller/res/values-ar/strings.xml b/packages/PackageInstaller/res/values-ar/strings.xml
index 170c1fc..a92b016 100644
--- a/packages/PackageInstaller/res/values-ar/strings.xml
+++ b/packages/PackageInstaller/res/values-ar/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"تم تثبيت التطبيق."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"هل تريد تثبيت هذا التطبيق؟"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"هل تريد تحديث هذا التطبيق؟"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"هل تريد تحديث هذا التطبيق من خلال \"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>\"؟\n\nيتلقّى هذا التطبيق التحديثات عادةً من \"<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>\". من خلال إجراء التحديث من مصدر مختلف، قد تتلقّى تحديثات في المستقبل من أي مصدر على هاتفك. قد تتغير وظائف التطبيق."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"هل تريد تحديث هذا التطبيق من خلال \"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>\"؟\n\nيتلقّى هذا التطبيق التحديثات عادةً من \"<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>\". من خلال تحديثه من مصدر مختلف، قد يتلقّى تحديثات في المستقبل من أي مصدر على جهازك اللوحي. قد تتغير وظائف التطبيق."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"هل تريد تحديث هذا التطبيق من خلال \"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>\"؟\n\nيتلقّى هذا التطبيق التحديثات عادةً من \"<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>\". من خلال تحديثه من مصدر مختلف، قد يتلقّى تحديثات في المستقبل من أي مصدر على التلفزيون. قد تتغير وظائف التطبيق."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"هل تريد تحديث هذا التطبيق من خلال \"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>\"؟\n\nيتلقّى هذا التطبيق التحديثات عادةً من \"<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>\". من خلال إجراء التحديث من مصدر مختلف، قد تتلقّى تحديثات في المستقبل من أي مصدر على هاتفك. قد تتغير وظائف التطبيق."</string>
     <string name="install_failed" msgid="5777824004474125469">"التطبيق ليس مثبتًا."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"تم حظر تثبيت الحزمة."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"لم يتم تثبيت التطبيق لأن حزمة التثبيت تتعارض مع حزمة حالية."</string>
diff --git a/packages/PackageInstaller/res/values-as/strings.xml b/packages/PackageInstaller/res/values-as/strings.xml
index 37f6c13..e6b46ae 100644
--- a/packages/PackageInstaller/res/values-as/strings.xml
+++ b/packages/PackageInstaller/res/values-as/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"এপ্ ইনষ্টল কৰা হ’ল।"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"আপুনি এই এপ্‌টো ইনষ্টল কৰিবলৈ বিচাৰেনে?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"আপুনি এই এপ্‌টো আপডে’ট কৰিবলৈ বিচাৰেনে?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"এই এপ্‌টো <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>ৰ পৰা আপডে’ট কৰিবনে?\n\nএই এপ্‌টোৱে সাধাৰণতে <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>ৰ পৰা আপডে’ট লাভ কৰে। অন্য এটা উৎসৰ পৰা আপডে’ট কৰি আপুনি যিকোনো উৎসৰ পৰা আপোনাৰ ফ’নত অনাগত আপডে’টসমূহ পাব পাৰে। এপৰ কাৰ্যক্ষমতা সলনি হ’ব পাৰে।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"এই এপ্‌টো <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>ৰ পৰা আপডে’ট কৰিবনে?\n\nএই এপ্‌টোৱে সাধাৰণতে <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>ৰ পৰা আপডে’ট লাভ কৰে। অন্য এটা উৎসৰ পৰা আপডে’ট কৰি আপুনি যিকোনো উৎসৰ পৰা আপোনাৰ টেবলেটত অনাগত আপডে’টসমূহ পাব পাৰে। এপৰ কাৰ্যক্ষমতা সলনি হ’ব পাৰে।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"এই এপ্‌টো <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>ৰ পৰা আপডে’ট কৰিবনে?\n\nএই এপ্‌টোৱে সাধাৰণতে <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>ৰ পৰা আপডে’ট লাভ কৰে। অন্য এটা উৎসৰ পৰা আপডে’ট কৰি আপুনি যিকোনো উৎসৰ পৰা আপোনাৰ টিভিত অনাগত আপডে’টসমূহ পাব পাৰে। এপৰ কাৰ্যক্ষমতা সলনি হ’ব পাৰে।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"এই এপ্‌টো <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>ৰ পৰা আপডে’ট কৰিবনে?\n\nএই এপ্‌টোৱে সাধাৰণতে <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>ৰ পৰা আপডে’ট লাভ কৰে। অন্য এটা উৎসৰ পৰা আপডে’ট কৰি আপুনি যিকোনো উৎসৰ পৰা আপোনাৰ ফ’নত অনাগত আপডে’টসমূহ পাব পাৰে। এপৰ কাৰ্যক্ষমতা সলনি হ’ব পাৰে।"</string>
     <string name="install_failed" msgid="5777824004474125469">"এপ্ ইনষ্টল কৰা হোৱা নাই।"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"পেকেজটোৰ ইনষ্টল অৱৰোধ কৰা হৈছে।"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"এপ্‌টো ইনষ্টল কৰিব পৰা নগ\'ল কাৰণ ইয়াৰ সৈতে আগৰে পৰা থকা এটা পেকেজৰ সংঘাত হৈছে।"</string>
diff --git a/packages/PackageInstaller/res/values-az/strings.xml b/packages/PackageInstaller/res/values-az/strings.xml
index ae7b2fc..a1b4fca 100644
--- a/packages/PackageInstaller/res/values-az/strings.xml
+++ b/packages/PackageInstaller/res/values-az/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Tətbiq quraşdırılıb."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Bu tətbiqi quraşdırmaq istəyirsiniz?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Bu tətbiqi güncəlləmək istəyirsiniz?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Bu tətbiq <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> mənbəyindən güncəllənsin?\n\nBu tətbiq adətən <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> mənbəyindən güncəllənmələr qəbul edir. Fərqli mənbədən güncəllədikdə telefonda istənilən mənbədən gələcəkdə güncəllənmələr qəbul edə bilərsiniz. Tətbiq funksionallığı dəyişə bilər."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Bu tətbiq <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> mənbəyindən güncəllənsin?\n\nBu tətbiq adətən <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> mənbəyindən güncəllənir. Fərqli mənbədən güncəllədikdə gələcəkdə planşetdə istənilən mənbədən güncəlləyə bilərsiniz. Tətbiq funksionallığı dəyişə bilər."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Bu tətbiq <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> mənbəyindən güncəllənsin?\n\nBu tətbiq adətən <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> mənbəyindən güncəllənir. Fərqli mənbədən güncəllədikdə gələcəkdə TV-də istənilən mənbədən güncəlləyə bilərsiniz. Tətbiq funksionallığı dəyişə bilər."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Bu tətbiq <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> mənbəyindən güncəllənsin?\n\nBu tətbiq adətən <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> mənbəyindən güncəllənmələr qəbul edir. Fərqli mənbədən güncəllədikdə telefonda istənilən mənbədən gələcəkdə güncəllənmələr qəbul edə bilərsiniz. Tətbiq funksionallığı dəyişə bilər."</string>
     <string name="install_failed" msgid="5777824004474125469">"Tətbiq quraşdırılmayıb."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paketin quraşdırılması blok edildi."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Bu paketin mövcud paket ilə ziddiyətinə görə tətbiq quraşdırılmadı."</string>
diff --git a/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml b/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml
index 2b0fa82..953b227 100644
--- a/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml
+++ b/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikacija je instalirana."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Želite da instalirate ovu aplikaciju?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Želite da ažurirate ovu aplikaciju?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Želite da ažurirate ovu aplikaciju iz izvora <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nOva aplikacija se obično ažurira iz izvora <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ako ažurirate iz drugog izvora, možete da primate buduća ažuriranja iz bilo kog izvora na telefonu. Funkcije aplikacije mogu da se promene."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Želite da ažurirate ovu aplikaciju iz izvora <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nOva aplikacija se obično ažurira iz izvora <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ako ažurirate iz drugog izvora, možete da primate buduća ažuriranja iz bilo kog izvora na tabletu. Funkcije aplikacije mogu da se promene."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Želite da ažurirate ovu aplikaciju iz izvora <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nOva aplikacija se obično ažurira iz izvora <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ako ažurirate iz drugog izvora, možete da primate buduća ažuriranja iz bilo kog izvora na TV-u. Funkcije aplikacije mogu da se promene."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Želite da ažurirate ovu aplikaciju iz izvora <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nOva aplikacija se obično ažurira iz izvora <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ako ažurirate iz drugog izvora, možete da primate buduća ažuriranja iz bilo kog izvora na telefonu. Funkcije aplikacije mogu da se promene."</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikacija nije instalirana."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Instaliranje paketa je blokirano."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikacija nije instalirana jer je paket neusaglašen sa postojećim paketom."</string>
diff --git a/packages/PackageInstaller/res/values-be/strings.xml b/packages/PackageInstaller/res/values-be/strings.xml
index d18e009..0e47d72 100644
--- a/packages/PackageInstaller/res/values-be/strings.xml
+++ b/packages/PackageInstaller/res/values-be/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Праграма ўсталявана."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Усталяваць гэту праграму?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Абнавіць гэту праграму?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Абнавіць праграму ад <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nЗвычайна гэтая праграма атрымлівае абнаўленні ад <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Пры абнаўленні з іншай крыніцы вы, магчыма, будзеце атрымліваць будучыя абнаўленні з любой крыніцы на тэлефоне. Функцыі праграмы могуць змяніцца."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Абнавіць праграму з <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nЗвычайна гэтая праграма атрымлівае абнаўленні з <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Калі абнавіць праграму з іншай крыніцы, то ў будучыні вы, магчыма, будзеце атрымліваць абнаўленні з любых крыніц на планшэце. Функцыі праграмы могуць змяніцца."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Абнавіць праграму з <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nЗвычайна гэтая праграма атрымлівае абнаўленні з <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Калі абнавіць праграму з іншай крыніцы, то ў будучыні вы, магчыма, будзеце атрымліваць абнаўленні з любых крыніц на тэлевізары. Функцыі праграмы могуць змяніцца."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Абнавіць праграму ад <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nЗвычайна гэтая праграма атрымлівае абнаўленні ад <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Пры абнаўленні з іншай крыніцы вы, магчыма, будзеце атрымліваць будучыя абнаўленні з любой крыніцы на тэлефоне. Функцыі праграмы могуць змяніцца."</string>
     <string name="install_failed" msgid="5777824004474125469">"Праграма не ўсталявана."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Усталяванне пакета заблакіравана."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Праграма не ўсталявана, таму што пакет канфліктуе з існуючым пакетам."</string>
diff --git a/packages/PackageInstaller/res/values-bg/strings.xml b/packages/PackageInstaller/res/values-bg/strings.xml
index 6dc927f..6a38129 100644
--- a/packages/PackageInstaller/res/values-bg/strings.xml
+++ b/packages/PackageInstaller/res/values-bg/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Приложението бе инсталирано."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Искате ли да инсталирате това приложение?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Искате ли да актуализирате това приложение?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Да се актуализира ли това приложение от <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nТо обикновено получава актуализации от <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ако инсталирате актуализация от друг източник, може да получавате бъдещи актуализации от който и да е източник на телефона си. Функционалността на приложението може да се промени."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Да се актуализира ли това приложение от <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nТо обикновено получава актуализации от <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ако инсталирате актуализация от друг източник, може да получавате бъдещи актуализации от който и да е източник на таблета си. Функционалността на приложението може да се промени."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Да се актуализира ли това приложение от <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nТо обикновено получава актуализации от <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ако инсталирате актуализация от друг източник, може да получавате бъдещи актуализации от който и да е източник на телевизора си. Функционалността на приложението може да се промени."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Да се актуализира ли това приложение от <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nТо обикновено получава актуализации от <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ако инсталирате актуализация от друг източник, може да получавате бъдещи актуализации от който и да е източник на телефона си. Функционалността на приложението може да се промени."</string>
     <string name="install_failed" msgid="5777824004474125469">"Приложението не бе инсталирано."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Инсталирането на пакета бе блокирано."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Приложението не бе инсталирано, тъй като пакетът е в конфликт със съществуващ пакет."</string>
diff --git a/packages/PackageInstaller/res/values-bn/strings.xml b/packages/PackageInstaller/res/values-bn/strings.xml
index 5b5c6dc..95cec04 100644
--- a/packages/PackageInstaller/res/values-bn/strings.xml
+++ b/packages/PackageInstaller/res/values-bn/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"অ্যাপটি ইনস্টল করা হয়ে গেছে।"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"আপনি কি এই অ্যাপটি ইনস্টল করতে চান?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"আপনি কি এই অ্যাপটি আপডেট করতে চান?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> থেকে এই অ্যাপ আপডেট করবেন?\n\nএই অ্যাপ সাধারণত <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> থেকে আপডেট পায়। অন্য কোনও সোর্স থেকে আপডেট করলে, আপনার ফোনে ভবিষ্যতে যেকোনও সোর্স থেকে আপডেট পেতে পারেন। অ্যাপের কার্যকারিতা পরিবর্তন হতে পারে।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-এর থেকে এই অ্যাপ আপডেট করতে চান?\n\nসাধারণত <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-এর থেকেই এই অ্যাপ আপডেট পায়। অন্য কোনও সোর্স থেকে আপডেট করলে, আপনার ট্যাবলেটে ভবিষ্যতে যেকোনও সোর্স থেকে আপডেট পাঠানো হতে পারে। তার ফলে অ্যাপের কার্যকারিতার ক্ষেত্রে পরিবর্তন হতে পারে।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-এর থেকে এই অ্যাপ আপডেট করতে চান?\n\nসাধারণত <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-এর থেকেই এই অ্যাপ আপডেট পায়। অন্য কোনও সোর্স থেকে আপডেট করলে, আপনার টিভিতে ভবিষ্যতে যেকোনও সোর্স থেকে আপডেট পাঠানো হতে পারে। তার ফলে অ্যাপের কার্যকারিতার ক্ষেত্রে পরিবর্তন হতে পারে।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> থেকে এই অ্যাপ আপডেট করবেন?\n\nএই অ্যাপ সাধারণত <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> থেকে আপডেট পায়। অন্য কোনও সোর্স থেকে আপডেট করলে, আপনার ফোনে ভবিষ্যতে যেকোনও সোর্স থেকে আপডেট পেতে পারেন। অ্যাপের কার্যকারিতা পরিবর্তন হতে পারে।"</string>
     <string name="install_failed" msgid="5777824004474125469">"অ্যাপটি ইনস্টল করা হয়নি।"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ইনস্টল হওয়া থেকে প্যাকেজটিকে ব্লক করা হয়েছে।"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"আগে থেকেই থাকা একটি প্যাকেজের সাথে প্যাকেজটির সমস্যা সৃষ্টি হওয়ায় অ্যাপটি ইনস্টল করা যায়নি।"</string>
diff --git a/packages/PackageInstaller/res/values-bs/strings.xml b/packages/PackageInstaller/res/values-bs/strings.xml
index e728937..56de9f2 100644
--- a/packages/PackageInstaller/res/values-bs/strings.xml
+++ b/packages/PackageInstaller/res/values-bs/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikacija je instalirana."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Želite li instalirati ovu aplikaciju?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Želite li ažurirati ovu aplikaciju?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Ažurirati aplikaciju iz izvora <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nOva aplikacija obično prima ažuriranja iz izvora <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ako je ažurirate iz drugog izvora, možda ćete primati buduća ažuriranja iz bilo kojeg izvora na telefonu. Funkcionalnost aplikacije se može promijeniti."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Ažurirati aplikaciju iz izvora <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nOva aplikacija obično prima ažuriranja iz izvora <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ako je ažurirate iz drugog izvora, možda ćete primati buduća ažuriranja iz bilo kojeg izvora na tabletu. Funkcionalnost aplikacije se može promijeniti."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Ažurirati aplikaciju iz izvora <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nOva aplikacija obično prima ažuriranja iz izvora <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ako je ažurirate iz drugog izvora, možda ćete primati buduća ažuriranja iz bilo kojeg izvora na TV-u. Funkcionalnost aplikacije se može promijeniti."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Ažurirati aplikaciju iz izvora <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nOva aplikacija obično prima ažuriranja iz izvora <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ako je ažurirate iz drugog izvora, možda ćete primati buduća ažuriranja iz bilo kojeg izvora na telefonu. Funkcionalnost aplikacije se može promijeniti."</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikacija nije instalirana."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Instaliranje ovog paketa je blokirano."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikacija nije instalirana jer paket nije usaglašen s postojećim paketom."</string>
diff --git a/packages/PackageInstaller/res/values-ca/strings.xml b/packages/PackageInstaller/res/values-ca/strings.xml
index 081d331..3db0267 100644
--- a/packages/PackageInstaller/res/values-ca/strings.xml
+++ b/packages/PackageInstaller/res/values-ca/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"S\'ha instal·lat l\'aplicació."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Vols instal·lar aquesta aplicació?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Vols actualitzar aquesta aplicació?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Vols actualitzar l\'aplicació des de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAquesta aplicació sol rebre actualitzacions a través de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si l\'actualitzes des d\'una font diferent, pot ser que en el futur rebis actualitzacions des de qualsevol font del teu telèfon. És possible que la funcionalitat de l\'aplicació canviï."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Vols actualitzar l\'aplicació de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAquesta aplicació sol rebre actualitzacions de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si l\'actualitzes des d\'una font diferent, pot ser que en el futur rebis actualitzacions des de qualsevol font de la teva tauleta. És possible que la funcionalitat de l\'aplicació canviï."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Vols actualitzar l\'aplicació de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAquesta aplicació sol rebre actualitzacions de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si l\'actualitzes des d\'una font diferent, pot ser que en el futur rebis actualitzacions des de qualsevol font del teu televisor. És possible que la funcionalitat de l\'aplicació canviï."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Vols actualitzar l\'aplicació des de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAquesta aplicació sol rebre actualitzacions a través de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si l\'actualitzes des d\'una font diferent, pot ser que en el futur rebis actualitzacions des de qualsevol font del teu telèfon. És possible que la funcionalitat de l\'aplicació canviï."</string>
     <string name="install_failed" msgid="5777824004474125469">"No s\'ha instal·lat l\'aplicació."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"El paquet s\'ha bloquejat perquè no es pugui instal·lar."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"L\'aplicació no s\'ha instal·lat perquè el paquet entra en conflicte amb un d\'existent."</string>
diff --git a/packages/PackageInstaller/res/values-cs/strings.xml b/packages/PackageInstaller/res/values-cs/strings.xml
index c96d27e..117c6f6 100644
--- a/packages/PackageInstaller/res/values-cs/strings.xml
+++ b/packages/PackageInstaller/res/values-cs/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikace je nainstalována."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Chcete tuto aplikaci nainstalovat?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Chcete tuto aplikaci aktualizovat?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Aktualizovat tuto aplikaci ze zdroje <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nTato aplikace obvykle dostává aktualizace ze zdroje <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Pokud ji aktualizujete z jiného zdroje, budete v budoucnu do telefonu moci dostávat aktualizace z libovolného zdroje. Funkčnost aplikace se může změnit."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Aktualizovat tuto aplikaci ze zdroje <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nTato aplikace obvykle dostává aktualizace ze zdroje <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Pokud ji aktualizujete z jiného zdroje, budete v budoucnu do tabletu moci dostávat aktualizace z libovolného zdroje. Funkčnost aplikace se může změnit."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Aktualizovat tuto aplikaci ze zdroje <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nTato aplikace obvykle dostává aktualizace ze zdroje <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Pokud ji aktualizujete z jiného zdroje, budete v budoucnu do televize moci dostávat aktualizace z libovolného zdroje. Funkčnost aplikace se může změnit."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Aktualizovat tuto aplikaci ze zdroje <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nTato aplikace obvykle dostává aktualizace ze zdroje <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Pokud ji aktualizujete z jiného zdroje, budete v budoucnu do telefonu moci dostávat aktualizace z libovolného zdroje. Funkčnost aplikace se může změnit."</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikaci nelze nainstalovat."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Instalace balíčku byla zablokována."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikaci nelze nainstalovat, protože balíček je v konfliktu se stávajícím balíčkem."</string>
diff --git a/packages/PackageInstaller/res/values-da/strings.xml b/packages/PackageInstaller/res/values-da/strings.xml
index d8759d4..e34d8cf 100644
--- a/packages/PackageInstaller/res/values-da/strings.xml
+++ b/packages/PackageInstaller/res/values-da/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Appen er installeret."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Vil du installere denne app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Vil du opdatere denne app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Vil du opdatere denne app fra <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDenne app plejer at modtage opdateringer fra <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Hvis du opdaterer fra en anden kilde, vil du kunne modtage opdateringer fra en hvilken som helst kilde på din telefon fremover. Appfunktionaliteten kan ændre sig."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Vil du opdatere denne app fra <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDenne app plejer at modtage opdateringer fra <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Hvis du opdaterer fra en anden kilde, vil du kunne modtage opdateringer fra en hvilken som helst kilde på din tablet fremover. Appfunktionaliteten kan ændre sig."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Vil du opdatere denne app fra <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDenne app plejer at modtage opdateringer fra <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Hvis du opdaterer fra en anden kilde, vil du kunne modtage opdateringer fra en hvilken som helst kilde på dit fjernsyn fremover. Appfunktionaliteten kan ændre sig."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Vil du opdatere denne app fra <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDenne app plejer at modtage opdateringer fra <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Hvis du opdaterer fra en anden kilde, vil du kunne modtage opdateringer fra en hvilken som helst kilde på din telefon fremover. Appfunktionaliteten kan ændre sig."</string>
     <string name="install_failed" msgid="5777824004474125469">"Appen blev ikke installeret."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Pakken blev forhindret i at blive installeret."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Appen blev ikke installeret, da pakken er i strid med en eksisterende pakke."</string>
diff --git a/packages/PackageInstaller/res/values-de/strings.xml b/packages/PackageInstaller/res/values-de/strings.xml
index bb55708..dc9c6e6 100644
--- a/packages/PackageInstaller/res/values-de/strings.xml
+++ b/packages/PackageInstaller/res/values-de/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App wurde installiert."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Möchtest du diese App installieren?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Möchtest du diese App aktualisieren?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Diese App mit einem Update von <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> aktualisieren?\n\nSie erhält normalerweise Updates von <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Wenn du ein Update von einer anderen Quelle verwendest, erhältst du möglicherweise zukünftige Updates von beliebigen Quellen auf deinem Smartphone. Die Funktionalität der App kann sich ändern."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Diese App mit einem Update von <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> aktualisieren?\n\nSie erhält normalerweise Updates von <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Wenn du ein Update von einer anderen Quelle verwendest, erhältst du möglicherweise zukünftige Updates von beliebigen Quellen auf deinem Tablet. Die Funktionalität der App kann sich ändern."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Diese App mit einem Update von <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> aktualisieren?\n\nSie erhält normalerweise Updates von <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Wenn du ein Update von einer anderen Quelle verwendest, erhältst du möglicherweise zukünftige Updates von beliebigen Quellen auf deinem Fernseher. Die Funktionalität der App kann sich ändern."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Diese App mit einem Update von <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> aktualisieren?\n\nSie erhält normalerweise Updates von <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Wenn du ein Update von einer anderen Quelle verwendest, erhältst du möglicherweise zukünftige Updates von beliebigen Quellen auf deinem Smartphone. Die Funktionalität der App kann sich ändern."</string>
     <string name="install_failed" msgid="5777824004474125469">"App wurde nicht installiert."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Die Installation des Pakets wurde blockiert."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Die App wurde nicht installiert, da das Paket in Konflikt mit einem bestehenden Paket steht."</string>
diff --git a/packages/PackageInstaller/res/values-el/strings.xml b/packages/PackageInstaller/res/values-el/strings.xml
index 9721a19..092f34c 100644
--- a/packages/PackageInstaller/res/values-el/strings.xml
+++ b/packages/PackageInstaller/res/values-el/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Η εφαρμογή εγκαταστάθηκε."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Θέλετε να εγκαταστήσετε αυτήν την εφαρμογή;"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Θέλετε να ενημερώσετε αυτήν την εφαρμογή;"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Να ενημερωθεί αυτή η εφαρμογή από <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>;\n\nΗ συγκεκριμένη εφαρμογή λαμβάνει συνήθως ενημερώσεις από <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Αν κάνετε την ενημέρωση από διαφορετική πηγή, μπορεί να λαμβάνετε μελλοντικές ενημερώσεις από οποιαδήποτε πηγή στο τηλέφωνό σας. Η λειτουργικότητα της εφαρμογής μπορεί να αλλάξει."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Να ενημερωθεί αυτή η εφαρμογή από <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>;\n\nΗ συγκεκριμένη εφαρμογή λαμβάνει συνήθως ενημερώσεις από <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Αν κάνετε την ενημέρωση από διαφορετική πηγή, μπορεί να λαμβάνετε μελλοντικές ενημερώσεις από οποιαδήποτε πηγή στο tablet σας. Η λειτουργικότητα της εφαρμογής μπορεί να αλλάξει."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Να ενημερωθεί αυτή η εφαρμογή από <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>;\n\nΗ συγκεκριμένη εφαρμογή λαμβάνει συνήθως ενημερώσεις από <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Αν κάνετε την ενημέρωση από διαφορετική πηγή, μπορεί να λαμβάνετε μελλοντικές ενημερώσεις από οποιαδήποτε πηγή στην τηλεόρασή σας. Η λειτουργικότητα της εφαρμογής μπορεί να αλλάξει."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Να ενημερωθεί αυτή η εφαρμογή από <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>;\n\nΗ συγκεκριμένη εφαρμογή λαμβάνει συνήθως ενημερώσεις από <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Αν κάνετε την ενημέρωση από διαφορετική πηγή, μπορεί να λαμβάνετε μελλοντικές ενημερώσεις από οποιαδήποτε πηγή στο τηλέφωνό σας. Η λειτουργικότητα της εφαρμογής μπορεί να αλλάξει."</string>
     <string name="install_failed" msgid="5777824004474125469">"Η εφαρμογή δεν εγκαταστάθηκε."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Η εγκατάσταση του πακέτου αποκλείστηκε."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Η εφαρμογή δεν εγκαταστάθηκε, επειδή το πακέτο είναι σε διένεξη με κάποιο υπάρχον πακέτο."</string>
diff --git a/packages/PackageInstaller/res/values-en-rAU/strings.xml b/packages/PackageInstaller/res/values-en-rAU/strings.xml
index 543dbf6..16e87a2 100644
--- a/packages/PackageInstaller/res/values-en-rAU/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rAU/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App installed."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Do you want to install this app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Do you want to update this app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your tablet. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your TV. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change."</string>
     <string name="install_failed" msgid="5777824004474125469">"App not installed."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"The package was blocked from being installed."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"App not installed as package conflicts with an existing package."</string>
diff --git a/packages/PackageInstaller/res/values-en-rCA/strings.xml b/packages/PackageInstaller/res/values-en-rCA/strings.xml
index f2457f2..03c0c27 100644
--- a/packages/PackageInstaller/res/values-en-rCA/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rCA/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App installed."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Do you want to install this app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Do you want to update this app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your tablet. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your TV. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change."</string>
     <string name="install_failed" msgid="5777824004474125469">"App not installed."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"The package was blocked from being installed."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"App not installed as package conflicts with an existing package."</string>
diff --git a/packages/PackageInstaller/res/values-en-rGB/strings.xml b/packages/PackageInstaller/res/values-en-rGB/strings.xml
index 543dbf6..16e87a2 100644
--- a/packages/PackageInstaller/res/values-en-rGB/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rGB/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App installed."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Do you want to install this app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Do you want to update this app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your tablet. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your TV. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change."</string>
     <string name="install_failed" msgid="5777824004474125469">"App not installed."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"The package was blocked from being installed."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"App not installed as package conflicts with an existing package."</string>
diff --git a/packages/PackageInstaller/res/values-en-rIN/strings.xml b/packages/PackageInstaller/res/values-en-rIN/strings.xml
index 543dbf6..16e87a2 100644
--- a/packages/PackageInstaller/res/values-en-rIN/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rIN/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App installed."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Do you want to install this app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Do you want to update this app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your tablet. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your TV. App functionality may change."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Update this app from <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change."</string>
     <string name="install_failed" msgid="5777824004474125469">"App not installed."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"The package was blocked from being installed."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"App not installed as package conflicts with an existing package."</string>
diff --git a/packages/PackageInstaller/res/values-en-rXC/strings.xml b/packages/PackageInstaller/res/values-en-rXC/strings.xml
index a674c20..c36b3e9 100644
--- a/packages/PackageInstaller/res/values-en-rXC/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rXC/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‎‏‏‏‎‏‏‎‎‎‏‏‎‏‏‏‎‏‎‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‎‎‏‏‎‏‎‏‏‎‎‎‎App installed.‎‏‎‎‏‎"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‎‏‏‎‎‎‎‏‏‎‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎Do you want to install this app?‎‏‎‎‏‎"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎‏‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‎‏‎‏‏‎‏‎‎‎‎Do you want to update this app?‎‏‎‎‏‎"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‎‏‏‎‎‎‎‎‏‎‎‎‏‏‎‏‎‎Update this app from ‎‏‎‎‏‏‎<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎This app normally receives updates from ‎‏‎‎‏‏‎<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>‎‏‎‎‏‏‏‎. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.‎‏‎‎‏‎"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‏‏‏‏‏‎‎‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‎‏‏‎‏‎‎‎‏‎‎‏‎‏‏‎‎‏‎‏‏‏‏‎‎‏‏‎‏‏‎‎Update this app from ‎‏‎‎‏‏‎<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎This app normally receives updates from ‎‏‎‎‏‏‎<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>‎‏‎‎‏‏‏‎. By updating from a different source, you may receive future updates from any source on your tablet. App functionality may change.‎‏‎‎‏‎"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‏‎‏‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‏‏‎‎‏‎‏‎‎‏‏‎‏‎‎‎‎‏‎‏‎‏‎‎‏‎‏‎‎‎‎‏‎‎‏‏‎Update this app from ‎‏‎‎‏‏‎<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎This app normally receives updates from ‎‏‎‎‏‏‎<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>‎‏‎‎‏‏‏‎. By updating from a different source, you may receive future updates from any source on your TV. App functionality may change.‎‏‎‎‏‎"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‎‏‏‎‎‎‎‎‏‎‎‎‏‏‎‏‎‎Update this app from ‎‏‎‎‏‏‎<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎This app normally receives updates from ‎‏‎‎‏‏‎<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>‎‏‎‎‏‏‏‎. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.‎‏‎‎‏‎"</string>
     <string name="install_failed" msgid="5777824004474125469">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‎‏‏‏‏‎‏‎‎‎‏‎‏‎‎‎‏‏‏‏‎‎‎‎‏‎‏‏‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‏‏‎‏‎App not installed.‎‏‎‎‏‎"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‏‎‏‏‎‎‏‎‏‏‏‏‎‎The package was blocked from being installed.‎‏‎‎‏‎"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‏‏‎‏‎‎‏‎‎‏‎‎‎‏‎‎‎‏‎‎‎‏‎‎‎‎‏‎‎‏‏‏‏‎‎‏‏‎‎‏‎‏‎‎‏‎‏‎‏‎‎‏‎‎App not installed as package conflicts with an existing package.‎‏‎‎‏‎"</string>
diff --git a/packages/PackageInstaller/res/values-es-rUS/strings.xml b/packages/PackageInstaller/res/values-es-rUS/strings.xml
index eb3abe9..23d1291 100644
--- a/packages/PackageInstaller/res/values-es-rUS/strings.xml
+++ b/packages/PackageInstaller/res/values-es-rUS/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Se instaló la app."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"¿Deseas instalar esta app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"¿Deseas actualizar esta app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"¿Quieres actualizar esta app a través de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEn general, esta suele recibir actualizaciones de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si actualizas a través de otra fuente, es posible que recibas las próximas actualizaciones de cualquier fuente en el teléfono. Por ende, podría verse afectada la funcionalidad de la app."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"¿Quieres actualizar esta app a través <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nPor lo general, suele recibir actualizaciones de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si actualizas a través de otra fuente, es posible que recibas las próximas actualizaciones de cualquier fuente en la tablet. Por ende, podría verse afectada la funcionalidad de la app."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"¿Quieres actualizar esta app a través <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nPor lo general, suele recibir actualizaciones de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si actualizas a través de otra fuente, es posible que recibas las próximas actualizaciones de cualquier fuente en la TV. Por ende, podría verse afectada la funcionalidad de la app."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"¿Quieres actualizar esta app a través de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEn general, esta suele recibir actualizaciones de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si actualizas a través de otra fuente, es posible que recibas las próximas actualizaciones de cualquier fuente en el teléfono. Por ende, podría verse afectada la funcionalidad de la app."</string>
     <string name="install_failed" msgid="5777824004474125469">"No se instaló la app."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Se bloqueó el paquete para impedir la instalación."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"No se instaló la app debido a un conflicto con un paquete."</string>
diff --git a/packages/PackageInstaller/res/values-es/strings.xml b/packages/PackageInstaller/res/values-es/strings.xml
index 9005718..62e4776 100644
--- a/packages/PackageInstaller/res/values-es/strings.xml
+++ b/packages/PackageInstaller/res/values-es/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplicación instalada."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"¿Quieres instalar esta aplicación?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"¿Quieres actualizar esta aplicación?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"¿Actualizar esta aplicación a través de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEsta aplicación normalmente recibe actualizaciones a través de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si la actualizas usando otra fuente, puede que recibas futuras actualizaciones a través de cualquier fuente en tu teléfono. La funcionalidad de la aplicación puede cambiar."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"¿Actualizar esta aplicación con <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEsta aplicación suele recibir actualizaciones de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si actualizas a través de otra fuente, puede que recibas futuras actualizaciones de cualquier fuente de tu tablet. La funcionalidad de la aplicación puede cambiar."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"¿Actualizar esta aplicación con <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEsta aplicación suele recibir actualizaciones de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si actualizas a través de otra fuente, puede que recibas futuras actualizaciones de cualquier fuente de tu TV. La funcionalidad de la aplicación puede cambiar."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"¿Actualizar esta aplicación a través de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEsta aplicación normalmente recibe actualizaciones a través de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si la actualizas usando otra fuente, puede que recibas futuras actualizaciones a través de cualquier fuente en tu teléfono. La funcionalidad de la aplicación puede cambiar."</string>
     <string name="install_failed" msgid="5777824004474125469">"No se ha instalado la aplicación."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Se ha bloqueado la instalación del paquete."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"La aplicación no se ha instalado debido a un conflicto con un paquete."</string>
diff --git a/packages/PackageInstaller/res/values-et/strings.xml b/packages/PackageInstaller/res/values-et/strings.xml
index cf488a9..f518b50 100644
--- a/packages/PackageInstaller/res/values-et/strings.xml
+++ b/packages/PackageInstaller/res/values-et/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Rakendus on installitud."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Kas soovite selle rakenduse installida?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Kas soovite seda rakendust värskendada?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Kas värskendada seda rakendust allikast <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nSee rakendus saab tavaliselt värskendusi allikast <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Muust allikast värskendamise korral võite edaspidi saada telefonis värskendusi mis tahes allikast. Rakenduse funktsioonid võivad muutuda."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Kas värskendada seda rakendust allikast <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nSee rakendus saab tavaliselt värskendusi allikast <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Muust allikast värskendamise korral võite edaspidi saada tahvelarvutis värskendusi mis tahes allikast. Rakenduse funktsioonid võivad muutuda."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Kas värskendada seda rakendust allikast <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nSee rakendus saab tavaliselt värskendusi allikast <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Muust allikast värskendamise korral võite edaspidi saada teleris värskendusi mis tahes allikast. Rakenduse funktsioonid võivad muutuda."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Kas värskendada seda rakendust allikast <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nSee rakendus saab tavaliselt värskendusi allikast <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Muust allikast värskendamise korral võite edaspidi saada telefonis värskendusi mis tahes allikast. Rakenduse funktsioonid võivad muutuda."</string>
     <string name="install_failed" msgid="5777824004474125469">"Rakendus pole installitud."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paketi installimine blokeeriti."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Rakendust ei installitud, kuna pakett on olemasoleva paketiga vastuolus."</string>
diff --git a/packages/PackageInstaller/res/values-eu/strings.xml b/packages/PackageInstaller/res/values-eu/strings.xml
index 9dadbed..580791c8 100644
--- a/packages/PackageInstaller/res/values-eu/strings.xml
+++ b/packages/PackageInstaller/res/values-eu/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Instalatu da aplikazioa."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Aplikazioa instalatu nahi duzu?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Aplikazioa eguneratu nahi duzu?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Aplikazioa <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> zerbitzutik eguneratu nahi duzu?\n\nAplikazioak <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> zerbitzutik jaso ohi ditu eguneratzeak. Beste iturburu batetik eguneratuz gero, baliteke aurrerantzeko eguneratzeak telefonoko edozein iturburutatik jasotzea. Baliteke aplikazioaren funtzioak aldatzea."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Aplikazioa <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> zerbitzutik eguneratu nahi duzu?\n\nAplikazioak <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> zerbitzutik jaso ohi ditu eguneratzeak. Beste iturburu batetik eguneratuz gero, baliteke aurrerantzeko eguneratzeak tabletako edozein iturburutatik jasotzea. Baliteke aplikazioaren funtzioak aldatzea."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Aplikazioa <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> zerbitzutik eguneratu nahi duzu?\n\nAplikazioak <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> zerbitzutik jaso ohi ditu eguneratzeak. Beste iturburu batetik eguneratuz gero, baliteke aurrerantzeko eguneratzeak telebistako edozein iturburutatik jasotzea. Baliteke aplikazioaren funtzioak aldatzea."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Aplikazioa <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> zerbitzutik eguneratu nahi duzu?\n\nAplikazioak <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> zerbitzutik jaso ohi ditu eguneratzeak. Beste iturburu batetik eguneratuz gero, baliteke aurrerantzeko eguneratzeak telefonoko edozein iturburutatik jasotzea. Baliteke aplikazioaren funtzioak aldatzea."</string>
     <string name="install_failed" msgid="5777824004474125469">"Ez da instalatu aplikazioa."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paketea instalatzeko aukera blokeatu egin da."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Ez da instalatu aplikazioa, gatazka bat sortu delako lehendik dagoen pakete batekin."</string>
diff --git a/packages/PackageInstaller/res/values-fa/strings.xml b/packages/PackageInstaller/res/values-fa/strings.xml
index 73b070d..4bde113 100644
--- a/packages/PackageInstaller/res/values-fa/strings.xml
+++ b/packages/PackageInstaller/res/values-fa/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"برنامه نصب شد."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"می‌خواهید این برنامه را نصب کنید؟"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"می‌خواهید این برنامه را به‌روزرسانی کنید؟"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"این برنامه ازطریق <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> به‌روز شود؟\n\nاین برنامه معمولاً به‌روزرسانی‌ها را از <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> دریافت می‌کند. با به‌روزرسانی از منبعی متفاوت، ممکن است به‌روزرسانی‌های بعدی را از هر منبعی در تلفنتان دریافت کنید. قابلیت‌های برنامه ممکن است تغییر کند."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"این برنامه ازطریق <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> به‌روز شود؟\n\nاین برنامه معمولاً به‌روزرسانی‌ها را از <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> دریافت می‌کند. با به‌روزرسانی از منبعی متفاوت، ممکن است به‌روزرسانی‌های آتی را از هر منبعی در رایانه لوحی‌تان دریافت کنید. قابلیت‌های برنامه ممکن است تغییر کند."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"این برنامه ازطریق <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> به‌روز شود؟\n\nاین برنامه معمولاً به‌روزرسانی‌ها را از <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> دریافت می‌کند. با به‌روزرسانی از منبعی متفاوت، ممکن است به‌روزرسانی‌های آتی را از هر منبعی در تلویزیون دریافت کنید. قابلیت‌های برنامه ممکن است تغییر کند."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"این برنامه ازطریق <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> به‌روز شود؟\n\nاین برنامه معمولاً به‌روزرسانی‌ها را از <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> دریافت می‌کند. با به‌روزرسانی از منبعی متفاوت، ممکن است به‌روزرسانی‌های بعدی را از هر منبعی در تلفنتان دریافت کنید. قابلیت‌های برنامه ممکن است تغییر کند."</string>
     <string name="install_failed" msgid="5777824004474125469">"برنامه نصب نشد."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"از نصب شدن بسته جلوگیری شد."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"برنامه نصب نشد چون بسته با بسته موجود تداخل دارد."</string>
diff --git a/packages/PackageInstaller/res/values-fi/strings.xml b/packages/PackageInstaller/res/values-fi/strings.xml
index ee8910b..e71ebaf 100644
--- a/packages/PackageInstaller/res/values-fi/strings.xml
+++ b/packages/PackageInstaller/res/values-fi/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Sovellus on asennettu."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Haluatko asentaa tämän sovelluksen?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Haluatko päivittää tämän sovelluksen?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Voiko <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> päivittää sovelluksen?\n\nTämän sovelluksen päivitykset tarjoaa yleensä <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Kun päivität uudesta lähteestä, tulevat päivitykset voivat tulla mistä tahansa puhelimella olevasta lähteestä. Sovelluksen toiminnot voivat muuttua."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Voiko <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> päivittää sovelluksen?\n\nTämän sovelluksen päivitykset tarjoaa yleensä <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Kun päivität uudesta lähteestä, tulevat päivitykset voivat tulla mistä tahansa tabletilla olevasta lähteestä. Sovelluksen toiminnot voivat muuttua."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Voiko <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> päivittää sovelluksen?\n\nTämän sovelluksen päivitykset tarjoaa yleensä <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Kun päivität uudesta lähteestä, tulevat päivitykset voivat tulla mistä tahansa televisiolla olevasta lähteestä. Sovelluksen toiminnot voivat muuttua."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Voiko <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> päivittää sovelluksen?\n\nTämän sovelluksen päivitykset tarjoaa yleensä <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Kun päivität uudesta lähteestä, tulevat päivitykset voivat tulla mistä tahansa puhelimella olevasta lähteestä. Sovelluksen toiminnot voivat muuttua."</string>
     <string name="install_failed" msgid="5777824004474125469">"Sovellusta ei asennettu."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paketin asennus estettiin."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Sovellusta ei asennettu, koska paketti on ristiriidassa nykyisen paketin kanssa."</string>
diff --git a/packages/PackageInstaller/res/values-fr-rCA/strings.xml b/packages/PackageInstaller/res/values-fr-rCA/strings.xml
index b971c35..6e48030 100644
--- a/packages/PackageInstaller/res/values-fr-rCA/strings.xml
+++ b/packages/PackageInstaller/res/values-fr-rCA/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Application installée."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Voulez-vous installer cette application?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Voulez-vous mettre à jour cette application?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Mettre à jour cette application à partir de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nCette application reçoit normalement des mises à jour de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. En effectuant une mise à jour à partir d\'une source différente, vous pourriez recevoir des mises à jour futures à partir de n\'importe quelle source sur votre téléphone. Le fonctionnement de l\'application peut en être modifié."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Mettre à jour cette application à partir de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nCette application reçoit normalement des mises à jour de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. En effectuant une mise à jour à partir d\'une source différente, vous pourriez recevoir des mises à jour futures à partir de n\'importe quelle source sur votre tablette. Le fonctionnement de l\'application peut en être modifié."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Mettre à jour cette application à partir de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nCette application reçoit normalement des mises à jour de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. En effectuant une mise à jour à partir d\'une source différente, vous pourriez recevoir des mises à jour futures à partir de n\'importe quelle source sur votre téléviseur. Le fonctionnement de l\'application peut en être modifié."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Mettre à jour cette application à partir de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nCette application reçoit normalement des mises à jour de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. En effectuant une mise à jour à partir d\'une source différente, vous pourriez recevoir des mises à jour futures à partir de n\'importe quelle source sur votre téléphone. Le fonctionnement de l\'application peut en être modifié."</string>
     <string name="install_failed" msgid="5777824004474125469">"Application non installée."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"L\'installation du paquet a été bloquée."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"L\'application n\'a pas été installée, car le paquet entre en conflit avec un paquet existant."</string>
diff --git a/packages/PackageInstaller/res/values-fr/strings.xml b/packages/PackageInstaller/res/values-fr/strings.xml
index 08d37d1..4b52724 100644
--- a/packages/PackageInstaller/res/values-fr/strings.xml
+++ b/packages/PackageInstaller/res/values-fr/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Application installée."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Voulez-vous installer cette appli ?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Voulez-vous mettre à jour cette appli ?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Mettre à jour cette appli à partir de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ?\n\nCette appli reçoit normalement des mises à jour de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si vous effectuez la mise à jour à partir d\'une autre source, vous recevrez peut-être les prochaines mises à jour depuis n\'importe quelle source sur votre téléphone. Le fonctionnement de l\'application est susceptible de changer."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Mettre à jour cette appli à partir de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ?\n\nCette appli reçoit normalement des mises à jour de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si vous effectuez la mise à jour à partir d\'une autre source, vous recevrez peut-être les prochaines mises à jour depuis n\'importe quelle source sur votre tablette. Le fonctionnement de l\'application est susceptible d\'évoluer."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Mettre à jour cette appli à partir de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ?\n\nCette appli reçoit normalement des mises à jour de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si vous effectuez la mise à jour à partir d\'une autre source, vous recevrez peut-être les prochaines mises à jour depuis n\'importe quelle source sur votre téléviseur. Le fonctionnement de l\'application est susceptible d\'évoluer."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Mettre à jour cette appli à partir de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ?\n\nCette appli reçoit normalement des mises à jour de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Si vous effectuez la mise à jour à partir d\'une autre source, vous recevrez peut-être les prochaines mises à jour depuis n\'importe quelle source sur votre téléphone. Le fonctionnement de l\'application est susceptible de changer."</string>
     <string name="install_failed" msgid="5777824004474125469">"Application non installée."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"L\'installation du package a été bloquée."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"L\'application n\'a pas été installée, car le package est en conflit avec un package déjà présent."</string>
diff --git a/packages/PackageInstaller/res/values-gl/strings.xml b/packages/PackageInstaller/res/values-gl/strings.xml
index d6cbf60..5427cc6 100644
--- a/packages/PackageInstaller/res/values-gl/strings.xml
+++ b/packages/PackageInstaller/res/values-gl/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Instalouse a aplicación."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Queres instalar esta aplicación?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Queres actualizar esta aplicación?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Queres actualizar esta aplicación desde <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAs actualizacións desta aplicación adoitan provir de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se actualizas a aplicación desde unha fonte diferente, pode que, a partir de agora, recibas actualizacións de calquera fonte no teléfono. Poderían cambiar as funcións da aplicación."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Queres actualizar esta aplicación desde <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAs actualizacións desta aplicación adoitan provir de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se actualizas a aplicación desde unha fonte diferente, pode que, a partir de agora, recibas actualizacións de calquera fonte na tableta. Poderían cambiar as funcións da aplicación."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Queres actualizar esta aplicación desde <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAs actualizacións desta aplicación adoitan provir de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se actualizas a aplicación desde unha fonte diferente, pode que, a partir de agora, recibas actualizacións de calquera fonte na televisión. Poderían cambiar as funcións da aplicación."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Queres actualizar esta aplicación desde <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAs actualizacións desta aplicación adoitan provir de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se actualizas a aplicación desde unha fonte diferente, pode que, a partir de agora, recibas actualizacións de calquera fonte no teléfono. Poderían cambiar as funcións da aplicación."</string>
     <string name="install_failed" msgid="5777824004474125469">"Non se instalou a aplicación"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Bloqueouse a instalación do paquete."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"A aplicación non se instalou porque o paquete presenta un conflito cun paquete que xa hai."</string>
diff --git a/packages/PackageInstaller/res/values-gu/strings.xml b/packages/PackageInstaller/res/values-gu/strings.xml
index dcaa48f..155a31b 100644
--- a/packages/PackageInstaller/res/values-gu/strings.xml
+++ b/packages/PackageInstaller/res/values-gu/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ઍપ્લિકેશન ઇન્સ્ટૉલ કરી."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"શું તમે આ ઍપ ઇન્સ્ટૉલ કરવા માગો છો?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"શું તમે આ ઍપ અપડેટ કરવા માગો છો?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"આ ઍપને <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>થી અપડેટ કરવી છે?\n\nઆ ઍપ સામાન્ય રીતે <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>થી અપડેટ મેળવે છે. અલગ સૉર્સથી અપડેટ કરીને, તમે તમારા ફોન પર કોઈપણ સૉર્સથી ભાવિ અપડેટ મેળવી શકો છો. ઍપની કાર્યક્ષમતા બદલાઈ શકે છે."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"આ ઍપને <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>થી અપડેટ કરવી છે?\n\nઆ ઍપ સામાન્ય રીતે <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>થી અપડેટ મેળવે છે. અલગ સૉર્સથી અપડેટ કરીને, તમે તમારા ટૅબ્લેટ પર કોઈપણ સૉર્સથી ભાવિ અપડેટ મેળવી શકો છો. ઍપની કાર્યક્ષમતા બદલાઈ શકે છે."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"આ ઍપને <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>થી અપડેટ કરવી છે?\n\nઆ ઍપ સામાન્ય રીતે <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>થી અપડેટ મેળવે છે. અલગ સૉર્સથી અપડેટ કરીને, તમે તમારા ટીવી પર કોઈપણ સૉર્સથી ભાવિ અપડેટ મેળવી શકો છો. ઍપની કાર્યક્ષમતા બદલાઈ શકે છે."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"આ ઍપને <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>થી અપડેટ કરવી છે?\n\nઆ ઍપ સામાન્ય રીતે <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>થી અપડેટ મેળવે છે. અલગ સૉર્સથી અપડેટ કરીને, તમે તમારા ફોન પર કોઈપણ સૉર્સથી ભાવિ અપડેટ મેળવી શકો છો. ઍપની કાર્યક્ષમતા બદલાઈ શકે છે."</string>
     <string name="install_failed" msgid="5777824004474125469">"ઍપ્લિકેશન ઇન્સ્ટૉલ કરી નથી."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"પૅકેજને ઇન્સ્ટૉલ થવાથી બ્લૉક કરવામાં આવ્યું હતું."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"પૅકેજનો અસ્તિત્વમાંના પૅકેજ સાથે વિરોધાભાસ હોવાને કારણે ઍપ્લિકેશન ઇન્સ્ટૉલ થઈ નથી."</string>
diff --git a/packages/PackageInstaller/res/values-hi/strings.xml b/packages/PackageInstaller/res/values-hi/strings.xml
index df3353b..131d9d5 100644
--- a/packages/PackageInstaller/res/values-hi/strings.xml
+++ b/packages/PackageInstaller/res/values-hi/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ऐप्लिकेशन इंस्‍टॉल हो गया."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"क्या आपको यह ऐप्लिकेशन इंस्टॉल करना है?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"क्या आप इस ऐप्लिकेशन को अपडेट करना चाहते हैं?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"क्या इस ऐप्लिकेशन को <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> से अपडेट करना है?\n\nआम तौर पर, इस ऐप्लिकेशन को <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> से अपडेट मिलते हैं. किसी दूसरे सोर्स से अपडेट करने पर, आपको नए अपडेट फ़ोन पर मौजूद किसी भी सोर्स से मिल सकते हैं. इसके साथ ही, ऐप्लिकेशन की मुख्य सुविधाओं और उनके काम करने के तरीके में बदलाव आ सकता है."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"क्या इस ऐप्लिकेशन को <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> से अपडेट करना है?\n\nआम तौर पर, इस ऐप्लिकेशन को <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> से अपडेट मिलते हैं. किसी दूसरे सोर्स से अपडेट करने पर, आपको आगे से अपने टैबलेट पर किसी भी सोर्स से अपडेट मिल सकते हैं. ऐप्लिकेशन की मुख्य सुविधाएं और उसके काम करने का तरीका बदल सकता है."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"क्या इस ऐप्लिकेशन को <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> से अपडेट करना है?\n\nआम तौर पर, इस ऐप्लिकेशन को <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> से अपडेट मिलते हैं. किसी दूसरे सोर्स से अपडेट करने पर, आपको आगे से अपने टीवी पर किसी भी सोर्स से अपडेट मिल सकते हैं. ऐप्लिकेशन की मुख्य सुविधाएं और उसके काम करने का तरीका बदल सकता है."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"क्या इस ऐप्लिकेशन को <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> से अपडेट करना है?\n\nआम तौर पर, इस ऐप्लिकेशन को <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> से अपडेट मिलते हैं. किसी दूसरे सोर्स से अपडेट करने पर, आपको नए अपडेट फ़ोन पर मौजूद किसी भी सोर्स से मिल सकते हैं. इसके साथ ही, ऐप्लिकेशन की मुख्य सुविधाओं और उनके काम करने के तरीके में बदलाव आ सकता है."</string>
     <string name="install_failed" msgid="5777824004474125469">"ऐप्लिकेशन इंस्‍टॉल नहीं हुआ."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"पैकेज को इंस्टॉल होने से ब्लॉक किया हुआ है."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ऐप्लिकेशन इंस्टॉल नहीं हुआ क्योंकि पैकेज का किसी मौजूदा पैकेज से विरोध है."</string>
diff --git a/packages/PackageInstaller/res/values-hr/strings.xml b/packages/PackageInstaller/res/values-hr/strings.xml
index 74c1fa1..7eb2e80 100644
--- a/packages/PackageInstaller/res/values-hr/strings.xml
+++ b/packages/PackageInstaller/res/values-hr/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikacija je instalirana."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Želite li instalirati ovu aplikaciju?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Želite li ažurirati ovu aplikaciju?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Želite li ovu aplikaciju ažurirati s izvora <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nOva aplikacija obično prima ažuriranja s izvora <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ako je ažurirate s nekog drugog izvora, buduća ažuriranja možete primati s bilo kojeg izvora na svojem telefonu. Funkcije aplikacije mogu se promijeniti."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Želite li ažurirati ovu aplikaciju s izvora <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nOva aplikacija obično prima ažuriranja s izvora <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ako je ažurirate s nekog drugog izvora, buduća ažuriranja možete primati s bilo kojeg izvora na svojem tabletu. Funkcije aplikacije mogu se promijeniti."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Želite li ažurirati ovu aplikaciju s izvora <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nOva aplikacija obično prima ažuriranja s izvora <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ako je ažurirate s nekog drugog izvora, buduća ažuriranja možete primati s bilo kojeg izvora na svojem TV-u. Funkcije aplikacije mogu se promijeniti."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Želite li ovu aplikaciju ažurirati s izvora <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nOva aplikacija obično prima ažuriranja s izvora <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ako je ažurirate s nekog drugog izvora, buduća ažuriranja možete primati s bilo kojeg izvora na svojem telefonu. Funkcije aplikacije mogu se promijeniti."</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikacija nije instalirana."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Instaliranje paketa blokirano je."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikacija koja nije instalirana kao paket u sukobu je s postojećim paketom."</string>
diff --git a/packages/PackageInstaller/res/values-hu/strings.xml b/packages/PackageInstaller/res/values-hu/strings.xml
index 823c20c..ac19a48 100644
--- a/packages/PackageInstaller/res/values-hu/strings.xml
+++ b/packages/PackageInstaller/res/values-hu/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Alkalmazás telepítve."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Telepíti ezt az alkalmazást?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Frissíti ezt az alkalmazást?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Frissíti az appot ebből a forrásból: <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEz az app általában a következő forrásból kap frissítéseket: <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ha másik forrásból frissíti, a későbbiekben bármelyik forrásból kaphat frissítéseket a telefonján. Emiatt megváltozhat az app működése."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"A(z) <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> forrásból frissíti ezt az alkalmazást?\n\nEz az alkalmazás általában a következő forrásból kap frissítéseket: <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ha másik forrásból frissíti, a későbbiekben bármelyik forrásból kaphat frissítéseket a táblagépén. Emiatt megváltozhat az alkalmazás működése."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"A(z) <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> forrásból frissíti ezt az alkalmazást?\n\nEz az alkalmazás általában a következő forrásból kap frissítéseket: <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ha másik forrásból frissíti, a későbbiekben bármelyik forrásból kaphat frissítéseket a tévéjén. Emiatt megváltozhat az alkalmazás működése."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Frissíti az appot ebből a forrásból: <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEz az app általában a következő forrásból kap frissítéseket: <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ha másik forrásból frissíti, a későbbiekben bármelyik forrásból kaphat frissítéseket a telefonján. Emiatt megváltozhat az app működése."</string>
     <string name="install_failed" msgid="5777824004474125469">"Az alkalmazás nincs telepítve."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"A csomag telepítését letiltotta a rendszer."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"A nem csomagként telepített alkalmazás ütközik egy már létező csomaggal."</string>
diff --git a/packages/PackageInstaller/res/values-hy/strings.xml b/packages/PackageInstaller/res/values-hy/strings.xml
index 4f56568..fe2f06b 100644
--- a/packages/PackageInstaller/res/values-hy/strings.xml
+++ b/packages/PackageInstaller/res/values-hy/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Հավելվածը տեղադրված է:"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Տեղադրե՞լ այս հավելվածը:"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Թարմացնե՞լ այս հավելվածը։"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Թարմացնե՞լ այս հավելվածը <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-ից։\n\nՍովորաբար այս հավելվածի թարմացումները ստացվում են <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-ից։ Եթե թարմացնեք այլ աղբյուրից, հետագայում կարող եք ձեր հեռախոսում թարմացումներ ստանալ ցանկացած աղբյուրից։ Հավելվածի գործառույթները կարող են փոփոխվել։"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Թարմացնե՞լ այս հավելվածը <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-ից։\n\nՍովորաբար այս հավելվածի թարմացումները ստացվում են <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-ից։ Եթե թարմացնեք այլ աղբյուրից, հետագայում կարող եք ձեր պլանշետում թարմացումներ ստանալ ցանկացած աղբյուրից։ Հավելվածի գործառույթները կարող են փոփոխվել։"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Թարմացնե՞լ այս հավելվածը <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-ից։\n\nՍովորաբար այս հավելվածի թարմացումները ստացվում են <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-ից։ Եթե թարմացնեք այլ աղբյուրից, հետագայում կարող եք ձեր հեռուստացույցում թարմացումներ ստանալ ցանկացած աղբյուրից։ Հավելվածի գործառույթները կարող են փոփոխվել։"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Թարմացնե՞լ այս հավելվածը <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-ից։\n\nՍովորաբար այս հավելվածի թարմացումները ստացվում են <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-ից։ Եթե թարմացնեք այլ աղբյուրից, հետագայում կարող եք ձեր հեռախոսում թարմացումներ ստանալ ցանկացած աղբյուրից։ Հավելվածի գործառույթները կարող են փոփոխվել։"</string>
     <string name="install_failed" msgid="5777824004474125469">"Հավելվածը տեղադրված չէ:"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Փաթեթի տեղադրումն արգելափակվել է:"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Հավելվածը չի տեղադրվել, քանի որ տեղադրման փաթեթն ունի հակասություն առկա փաթեթի հետ:"</string>
diff --git a/packages/PackageInstaller/res/values-in/strings.xml b/packages/PackageInstaller/res/values-in/strings.xml
index 0758a1d..a0c76c6 100644
--- a/packages/PackageInstaller/res/values-in/strings.xml
+++ b/packages/PackageInstaller/res/values-in/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikasi terinstal."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Ingin menginstal aplikasi ini?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Ingin mengupdate aplikasi ini?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Update aplikasi ini dari <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAplikasi ini biasanya menerima update dari <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Dengan mengupdate dari sumber yang berbeda, Anda mungkin menerima update berikutnya dari sumber mana pun di ponsel. Fungsi aplikasi mungkin berubah."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Update aplikasi ini dari <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAplikasi ini biasanya menerima update dari <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Dengan mengupdate dari sumber yang berbeda, Anda mungkin menerima update berikutnya dari sumber mana pun di tablet. Fungsi aplikasi mungkin berubah."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Update aplikasi ini dari <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAplikasi ini biasanya menerima update dari <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Dengan mengupdate dari sumber yang berbeda, Anda mungkin menerima update berikutnya dari sumber mana pun di TV. Fungsi aplikasi mungkin berubah."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Update aplikasi ini dari <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAplikasi ini biasanya menerima update dari <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Dengan mengupdate dari sumber yang berbeda, Anda mungkin menerima update berikutnya dari sumber mana pun di ponsel. Fungsi aplikasi mungkin berubah."</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikasi tidak terinstal."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paket diblokir sehingga tidak dapat diinstal."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikasi tidak diinstal karena paket ini bentrok dengan paket yang sudah ada."</string>
diff --git a/packages/PackageInstaller/res/values-is/strings.xml b/packages/PackageInstaller/res/values-is/strings.xml
index 6cbb2ee..f0dce1e 100644
--- a/packages/PackageInstaller/res/values-is/strings.xml
+++ b/packages/PackageInstaller/res/values-is/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Forritið er uppsett."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Viltu setja upp þetta forrit?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Viltu uppfæra þetta forrit?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Uppfæra þetta forrit frá <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nÞetta forrit fær venjulega uppfærslur frá <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Með því að uppfæra frá öðrum uppruna gætirðu fengið framtíðaruppfærslur frá hvaða uppruna sem er í símanum. Forritseiginleikar kunna að breytast."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Uppfæra þetta forrit frá <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nÞetta forrit fær venjulega uppfærslur frá <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Með því að uppfæra frá öðrum uppruna gætirðu fengið framtíðaruppfærslur frá hvaða uppruna sem er í spjaldtölvunni. Forritseiginleikar kunna að breytast."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Uppfæra þetta forrit frá <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nÞetta forrit fær venjulega uppfærslur frá <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Með því að uppfæra frá öðrum uppruna gætirðu fengið framtíðaruppfærslur frá hvaða uppruna sem er í sjónvarpinu. Forritseiginleikar kunna að breytast."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Uppfæra þetta forrit frá <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nÞetta forrit fær venjulega uppfærslur frá <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Með því að uppfæra frá öðrum uppruna gætirðu fengið framtíðaruppfærslur frá hvaða uppruna sem er í símanum. Forritseiginleikar kunna að breytast."</string>
     <string name="install_failed" msgid="5777824004474125469">"Forritið er ekki uppsett."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Lokað var á uppsetningu pakkans."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Forritið var ekki sett upp vegna árekstra á milli pakkans og annars pakka."</string>
diff --git a/packages/PackageInstaller/res/values-it/strings.xml b/packages/PackageInstaller/res/values-it/strings.xml
index 323db1d..b43570f 100644
--- a/packages/PackageInstaller/res/values-it/strings.xml
+++ b/packages/PackageInstaller/res/values-it/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App installata."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Vuoi installare questa app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Vuoi aggiornare questa app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Vuoi aggiornare questa app tramite <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nGeneralmente l\'app viene aggiornata tramite <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se la aggiorni da un\'origine diversa, in futuro potresti ricevere aggiornamenti da qualsiasi origine sul telefono. La funzionalità dell\'app potrebbe cambiare."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Vuoi aggiornare questa app tramite <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nGeneralmente l\'app viene aggiornata tramite <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se la aggiorni da un\'origine diversa, in futuro potresti ricevere aggiornamenti da qualsiasi origine sul tablet. La funzionalità dell\'app potrebbe cambiare."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Vuoi aggiornare questa app tramite <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nGeneralmente l\'app viene aggiornata tramite <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se la aggiorni da un\'origine diversa, in futuro potresti ricevere aggiornamenti da qualsiasi origine sulla TV. La funzionalità dell\'app potrebbe cambiare."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Vuoi aggiornare questa app tramite <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nGeneralmente l\'app viene aggiornata tramite <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se la aggiorni da un\'origine diversa, in futuro potresti ricevere aggiornamenti da qualsiasi origine sul telefono. La funzionalità dell\'app potrebbe cambiare."</string>
     <string name="install_failed" msgid="5777824004474125469">"App non installata."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"È stata bloccata l\'installazione del pacchetto."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"App non installata poiché il pacchetto è in conflitto con un pacchetto esistente."</string>
diff --git a/packages/PackageInstaller/res/values-iw/strings.xml b/packages/PackageInstaller/res/values-iw/strings.xml
index 24beba6..692ce2d 100644
--- a/packages/PackageInstaller/res/values-iw/strings.xml
+++ b/packages/PackageInstaller/res/values-iw/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"האפליקציה הותקנה."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"האם ברצונך להתקין אפליקציה זו?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"האם ברצונך לעדכן אפליקציה זו?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"לקבל את העדכון לאפליקציה הזו מ-<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nהאפליקציה הזו בדרך כלל מקבלת עדכונים מ: <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. אם בחרת לעדכן ממקור אחר, יכול להיות שבעתיד יתקבלו עדכונים ממקורות אחרים בטלפון. תכונות האפליקציה יכולות להשתנות."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"לקבל את העדכון לאפליקציה הזו מ-<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nהאפליקציה הזו בדרך כלל מקבלת עדכונים מ-<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. אם בחרת לעדכן ממקור אחר, יכול להיות שבעתיד יתקבלו עדכונים ממקורות אחרים בטאבלט. תכונות האפליקציה עשויות להשתנות."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"לקבל את העדכון לאפליקציה הזו מ-<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nהאפליקציה הזו בדרך כלל מקבלת עדכונים מ-<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. אם בחרת לעדכן ממקור אחר, יכול להיות שבעתיד יתקבלו עדכונים ממקורות אחרים בטלוויזיה. תכונות האפליקציה עשויות להשתנות."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"לקבל את העדכון לאפליקציה הזו מ-<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nהאפליקציה הזו בדרך כלל מקבלת עדכונים מ: <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. אם בחרת לעדכן ממקור אחר, יכול להיות שבעתיד יתקבלו עדכונים ממקורות אחרים בטלפון. תכונות האפליקציה יכולות להשתנות."</string>
     <string name="install_failed" msgid="5777824004474125469">"האפליקציה לא הותקנה."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"החבילה נחסמה להתקנה."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"האפליקציה לא הותקנה כי החבילה מתנגשת עם חבילה קיימת."</string>
diff --git a/packages/PackageInstaller/res/values-ja/strings.xml b/packages/PackageInstaller/res/values-ja/strings.xml
index 95b789e..b91312b 100644
--- a/packages/PackageInstaller/res/values-ja/strings.xml
+++ b/packages/PackageInstaller/res/values-ja/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"アプリをインストールしました。"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"このアプリをインストールしますか?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"このアプリを更新しますか?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"このアプリを <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> から更新しますか?\n\nこのアプリは通常、<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> からアップデートを受信しています。別の提供元から更新することにより、お使いのスマートフォンで今後のアップデートを任意の提供元から受け取ることになります。アプリの機能は変更される場合があります。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"このアプリを <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> から更新しますか?\n\nこのアプリは通常、<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> からアップデートを受信しています。別の提供元から更新することにより、お使いのタブレットで今後のアップデートを任意の提供元から受け取ることになります。アプリの機能は変更される場合があります。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"このアプリを <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> から更新しますか?\n\nこのアプリは通常、<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> からアップデートを受信しています。別の提供元から更新することにより、お使いのテレビで今後のアップデートを任意の提供元から受け取ることになります。アプリの機能は変更される場合があります。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"このアプリを <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> から更新しますか?\n\nこのアプリは通常、<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> からアップデートを受信しています。別の提供元から更新することにより、お使いのスマートフォンで今後のアップデートを任意の提供元から受け取ることになります。アプリの機能は変更される場合があります。"</string>
     <string name="install_failed" msgid="5777824004474125469">"アプリはインストールされていません。"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"パッケージのインストールはブロックされています。"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"パッケージが既存のパッケージと競合するため、アプリをインストールできませんでした。"</string>
diff --git a/packages/PackageInstaller/res/values-ka/strings.xml b/packages/PackageInstaller/res/values-ka/strings.xml
index 0699f0b..497f3b0 100644
--- a/packages/PackageInstaller/res/values-ka/strings.xml
+++ b/packages/PackageInstaller/res/values-ka/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"აპი დაინსტალირებულია."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"გნებავთ ამ აპის დაყენება?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"გსურთ ამ აპის განახლება?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"გსურთ განაახლოთ ეს აპი <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-ისგან?\n\nეს აპი, როგორც წესი, განახლებებს იღებს <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-ისგან. აპის სხვა წყაროდან განახლებით შემდგომში განახლებების მიღებას შეძლებთ ნებისმიერი წყაროდან თქვენს ტელეფონზე. აპის ფუნქციები, შესაძლოა, შეიცვალოს."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"გსურთ განაახლოთ ეს აპი <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-ისგან?\n\nეს აპი, როგორც წესი, განახლებებს იღებს <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-ისგან. სხვა წყაროდან განახლებით, შეგიძლიათ მიიღოთ მომავალი განახლებები ტაბლეტზე არსებული ნებისმიერი წყაროდან. აპის ფუნქციები, შესაძლოა, შეიცვალოს."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"გსურთ განაახლოთ ეს აპი <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-ისგან?\n\nეს აპი, როგორც წესი, განახლებებს იღებს <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-ისგან. სხვა წყაროდან განახლებით, შეგიძლიათ მიიღოთ მომავალი განახლებები ტელევიზორზე არსებული ნებისმიერი წყაროდან. აპის ფუნქციები, შესაძლოა, შეიცვალოს."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"გსურთ განაახლოთ ეს აპი <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-ისგან?\n\nეს აპი, როგორც წესი, განახლებებს იღებს <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-ისგან. აპის სხვა წყაროდან განახლებით შემდგომში განახლებების მიღებას შეძლებთ ნებისმიერი წყაროდან თქვენს ტელეფონზე. აპის ფუნქციები, შესაძლოა, შეიცვალოს."</string>
     <string name="install_failed" msgid="5777824004474125469">"აპი დაუინსტალირებელია."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ამ პაკეტის ინსტალაცია დაბლოკილია."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"აპი ვერ დაინსტალირდა, რადგან პაკეტი კონფლიქტშია არსებულ პაკეტთან."</string>
diff --git a/packages/PackageInstaller/res/values-kk/strings.xml b/packages/PackageInstaller/res/values-kk/strings.xml
index 129267f..a7ae943 100644
--- a/packages/PackageInstaller/res/values-kk/strings.xml
+++ b/packages/PackageInstaller/res/values-kk/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Қолданба орнатылды."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Бұл қолданбаны орнатқыңыз келе ме?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Бұл қолданбаны жаңартқыңыз келе ме?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Бұл қолданба <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> арқылы жаңартылсын ба?\n\nБұл қолданба әдетте <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> көмегімен жаңартылады. Басқа дереккөзден жаңартсаңыз, алдағы жаңартулар телефоныңыздағы кез келген дереккөзден келуі мүмкін. Қолданба функциялары өзгеруі мүмкін."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Бұл қолданба <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> арқылы жаңартылсын ба?\n\nБұл қолданба әдетте <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> көмегімен жаңартылады. Басқа дереккөзден жаңартсаңыз, планшетіңіздегі кез келген дереккөзден алдағы жаңартулар берілуі мүмкін. Қолданба функциялары өзгеруі мүмкін."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Бұл қолданба <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> арқылы жаңартылсын ба?\n\nБұл қолданба әдетте <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> көмегімен жаңартылады. Басқа дереккөзден жаңартсаңыз, теледидарыңыздағы кез келген дереккөзден алдағы жаңартулар берілуі мүмкін. Қолданба функциялары өзгеруі мүмкін."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Бұл қолданба <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> арқылы жаңартылсын ба?\n\nБұл қолданба әдетте <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> көмегімен жаңартылады. Басқа дереккөзден жаңартсаңыз, алдағы жаңартулар телефоныңыздағы кез келген дереккөзден келуі мүмкін. Қолданба функциялары өзгеруі мүмкін."</string>
     <string name="install_failed" msgid="5777824004474125469">"Қолданба орнатылмады."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Пакетті орнатуға тыйым салынды."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Жаңа пакет пен бұрыннан бар пакеттің арасында қайшылық туындағандықтан, қолданба орнатылмады."</string>
diff --git a/packages/PackageInstaller/res/values-km/strings.xml b/packages/PackageInstaller/res/values-km/strings.xml
index 04dc574..52bfce3 100644
--- a/packages/PackageInstaller/res/values-km/strings.xml
+++ b/packages/PackageInstaller/res/values-km/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"បាន​ដំឡើង​កម្មវិធី។"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"តើ​អ្នក​ចង់​ដំឡើង​កម្មវិធី​នេះ​ដែរទេ?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"តើអ្នកចង់ដំឡើងកំណែ​កម្មវិធីនេះដែរទេ?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"ដំឡើងកំណែកម្មវិធីនេះពី <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ឬ?\n\nកម្មវិធីនេះជាធម្មតាទទួលបានកំណែថ្មីពី <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>។ តាមរយៈការដំឡើងកំណែពីប្រភពផ្សេង អ្នកអាចនឹងទទួលបានកំណែថ្មីនាពេលអនាគតពីប្រភពណាក៏បាននៅលើទូរសព្ទរបស់អ្នក។ មុខងារ​កម្មវិធីអាចមានការផ្លាស់ប្ដូរ។"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"ដំឡើងកំណែកម្មវិធីនេះពី <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ឬ?\n\nកម្មវិធីនេះជាធម្មតាទទួលបានកំណែថ្មីៗពី <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>។ តាមរយៈការដំឡើងកំណែពីប្រភពផ្សេង អ្នកអាចទទួលបានកំណែថ្មីៗនាពេលអនាគតពីប្រភពណាក៏បាននៅលើថេប្លេតរបស់អ្នក។ មុខងារ​កម្មវិធីអាចមានការផ្លាស់ប្ដូរ។"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"ដំឡើងកំណែកម្មវិធីនេះពី <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ឬ?\n\nកម្មវិធីនេះជាធម្មតាទទួលបានកំណែថ្មីៗពី <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>។ តាមរយៈការដំឡើងកំណែពីប្រភពផ្សេង អ្នកអាចទទួលបានកំណែថ្មីៗនាពេលអនាគតពីប្រភពណាក៏បាននៅលើទូរទស្សន៍របស់អ្នក។ មុខងារ​កម្មវិធីអាចមានការផ្លាស់ប្ដូរ។"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"ដំឡើងកំណែកម្មវិធីនេះពី <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ឬ?\n\nកម្មវិធីនេះជាធម្មតាទទួលបានកំណែថ្មីពី <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>។ តាមរយៈការដំឡើងកំណែពីប្រភពផ្សេង អ្នកអាចនឹងទទួលបានកំណែថ្មីនាពេលអនាគតពីប្រភពណាក៏បាននៅលើទូរសព្ទរបស់អ្នក។ មុខងារ​កម្មវិធីអាចមានការផ្លាស់ប្ដូរ។"</string>
     <string name="install_failed" msgid="5777824004474125469">"មិន​បាន​ដំឡើង​កម្មវិធីទេ។"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"កញ្ចប់ត្រូវបានទប់ស្កាត់​មិន​ឱ្យ​ដំឡើង។"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"កម្មវិធីមិនបានដំឡើងទេ ដោយសារកញ្ចប់កម្មវិធីមិនត្រូវគ្នាជាមួយកញ្ចប់ដែលមានស្រាប់។"</string>
diff --git a/packages/PackageInstaller/res/values-kn/strings.xml b/packages/PackageInstaller/res/values-kn/strings.xml
index c9455ae..1d3cfd3 100644
--- a/packages/PackageInstaller/res/values-kn/strings.xml
+++ b/packages/PackageInstaller/res/values-kn/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ಆ್ಯಪ್‌ ಅನ್ನು ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡಲಾಗಿದೆ."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"ನೀವು ಈ ಆ್ಯಪ್‌ ಅನ್ನು ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡಲು ಬಯಸುವಿರಾ?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ನೀವು ಈ ಆ್ಯಪ್‌ ಅನ್ನು ಅಪ್‌ಡೇಟ್‌ ಮಾಡಲು ಬಯಸುವಿರಾ?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ನಿಂದ ಈ ಆ್ಯಪ್ ಅನ್ನು ಅಪ್‌ಡೇಟ್‌ ಮಾಡಬೇಕೇ?\n\nಈ ಆ್ಯಪ್ ಸಾಮಾನ್ಯವಾಗಿ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> ನಿಂದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸುತ್ತದೆ. ಬೇರೆ ಮೂಲವೊಂದರಿಂದ ಅಪ್‌ಡೇಟ್‌ ಮಾಡುವ ಮೂಲಕ, ನಿಮ್ಮ ಫೋನ್‌ನಲ್ಲಿರುವ ಯಾವುದೇ ಮೂಲದಿಂದ ಭವಿಷ್ಯದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ನೀವು ಸ್ವೀಕರಿಸಬಹುದು. ಆ್ಯಪ್‌ನ ಕಾರ್ಯಚಟುವಟಿಕೆಯು ಬದಲಾಗಬಹುದು."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ನಿಂದ ಈ ಆ್ಯಪ್ ಅನ್ನು ಅಪ್‌ಡೇಟ್‌ ಮಾಡಬೇಕೇ?\n\nಈ ಆ್ಯಪ್ ಸಾಮಾನ್ಯವಾಗಿ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> ನಿಂದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸುತ್ತದೆ. ಬೇರೆ ಮೂಲವೊಂದರಿಂದ ಅಪ್‌ಡೇಟ್‌ ಮಾಡುವ ಮೂಲಕ, ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್‌ನಲ್ಲಿರುವ ಯಾವುದೇ ಮೂಲದಿಂದ ಭವಿಷ್ಯದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ನೀವು ಸ್ವೀಕರಿಸಬಹುದು. ಆ್ಯಪ್‌ನ ಕಾರ್ಯಚಟುವಟಿಕೆಯು ಬದಲಾಗಬಹುದು."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ನಿಂದ ಈ ಆ್ಯಪ್ ಅನ್ನು ಅಪ್‌ಡೇಟ್‌ ಮಾಡಬೇಕೇ?\n\nಈ ಆ್ಯಪ್ ಸಾಮಾನ್ಯವಾಗಿ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> ನಿಂದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸುತ್ತದೆ. ಬೇರೆ ಮೂಲವೊಂದರಿಂದ ಅಪ್‌ಡೇಟ್‌ ಮಾಡುವ ಮೂಲಕ, ನಿಮ್ಮ ಟಿವಿಯಲ್ಲಿರುವ ಯಾವುದೇ ಮೂಲದಿಂದ ಭವಿಷ್ಯದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ನೀವು ಸ್ವೀಕರಿಸಬಹುದು. ಆ್ಯಪ್‌ನ ಕಾರ್ಯಚಟುವಟಿಕೆಯು ಬದಲಾಗಬಹುದು."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ನಿಂದ ಈ ಆ್ಯಪ್ ಅನ್ನು ಅಪ್‌ಡೇಟ್‌ ಮಾಡಬೇಕೇ?\n\nಈ ಆ್ಯಪ್ ಸಾಮಾನ್ಯವಾಗಿ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> ನಿಂದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸುತ್ತದೆ. ಬೇರೆ ಮೂಲವೊಂದರಿಂದ ಅಪ್‌ಡೇಟ್‌ ಮಾಡುವ ಮೂಲಕ, ನಿಮ್ಮ ಫೋನ್‌ನಲ್ಲಿರುವ ಯಾವುದೇ ಮೂಲದಿಂದ ಭವಿಷ್ಯದ ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ನೀವು ಸ್ವೀಕರಿಸಬಹುದು. ಆ್ಯಪ್‌ನ ಕಾರ್ಯಚಟುವಟಿಕೆಯು ಬದಲಾಗಬಹುದು."</string>
     <string name="install_failed" msgid="5777824004474125469">"ಆ್ಯಪ್‌ ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡಲಾಗಿಲ್ಲ."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡುವ ಪ್ಯಾಕೇಜ್‌ ಅನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ಪ್ಯಾಕೇಜ್‌ನಂತೆ ಇನ್‌ಸ್ಟಾಲ್‌ ಮಾಡಲಾಗಿರುವ ಆ್ಯಪ್‌ ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಪ್ಯಾಕೇಜ್ ಜೊತೆಗೆ ಸಂಘರ್ಷವಾಗುತ್ತದೆ."</string>
diff --git a/packages/PackageInstaller/res/values-ko/strings.xml b/packages/PackageInstaller/res/values-ko/strings.xml
index ef39eef..5c22453 100644
--- a/packages/PackageInstaller/res/values-ko/strings.xml
+++ b/packages/PackageInstaller/res/values-ko/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"앱이 설치되었습니다."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"이 앱을 설치하시겠습니까?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"이 앱을 업데이트하시겠습니까?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>에서 이 앱에 대한 업데이트를 받으시겠습니까?\n\n평소에는 <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>에서 앱을 업데이트했습니다. 다른 출처에서 앱을 업데이트하면 향후 휴대전화에 있는 어떤 출처에서든지 업데이트를 받을 수 있습니다. 앱 기능도 변경될 수 있습니다."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>에서 이 앱에 대한 업데이트를 받으시겠습니까?\n\n평소에는 <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>에서 앱을 업데이트했습니다. 다른 출처에서 업데이트를 받으면 향후 태블릿에 있는 어떤 출처에서든지 업데이트를 받을 수 있습니다. 앱 기능도 변경될 수 있습니다."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>에서 이 앱에 대한 업데이트를 받으시겠습니까?\n\n평소에는 <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>에서 앱을 업데이트했습니다. 다른 출처에서 앱을 업데이트하면 향후 TV에 있는 어떤 출처에서든지 업데이트를 받을 수 있습니다. 앱 기능도 변경될 수 있습니다."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>에서 이 앱에 대한 업데이트를 받으시겠습니까?\n\n평소에는 <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>에서 앱을 업데이트했습니다. 다른 출처에서 앱을 업데이트하면 향후 휴대전화에 있는 어떤 출처에서든지 업데이트를 받을 수 있습니다. 앱 기능도 변경될 수 있습니다."</string>
     <string name="install_failed" msgid="5777824004474125469">"앱이 설치되지 않았습니다."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"패키지 설치가 차단되었습니다."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"패키지가 기존 패키지와 충돌하여 앱이 설치되지 않았습니다."</string>
diff --git a/packages/PackageInstaller/res/values-ky/strings.xml b/packages/PackageInstaller/res/values-ky/strings.xml
index 8b166c8..febc30c 100644
--- a/packages/PackageInstaller/res/values-ky/strings.xml
+++ b/packages/PackageInstaller/res/values-ky/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Колдонмо орнотулду."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Бул колдонмону орнотоюн деп жатасызбы?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Бул колдонмону жаңыртайын деп жатасызбы?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Колдонмону <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> аркылуу жаңыртасызбы?\n\nАдатта бул колдонмонун жаңыртууларын <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> жөнөтөт. Эгер колдонмону башка булактан жаңыртсаңыз, эртеңки күнү телефонуңуз ар кайсы булактан жаңырып, колдонмонун функциялары өзгөрүшү мүмкүн."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Колдонмо <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> аркылуу жаңыртылсынбы?\n\nАдатта бул колдонмо <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> жөнөткөн жаңыртууларды алат. Башка булактан жаңыртcаңыз, кийинки жаңыртуулар планшетиңиздеги ар кандай булактардан алынышы мүмкүн. Колдонмонун функциялары өзгөрүшү мүмкүн."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Колдонмо <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> аркылуу жаңыртылсынбы?\n\nАдатта бул колдонмо <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> жөнөткөн жаңыртууларды алат. Башка булактан жаңыртcаңыз, кийинки жаңыртуулар сыналгыңыздагы ар кандай булактардан алынышы мүмкүн. Колдонмонун функциялары өзгөрүшү мүмкүн."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Колдонмону <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> аркылуу жаңыртасызбы?\n\nАдатта бул колдонмонун жаңыртууларын <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> жөнөтөт. Эгер колдонмону башка булактан жаңыртсаңыз, эртеңки күнү телефонуңуз ар кайсы булактан жаңырып, колдонмонун функциялары өзгөрүшү мүмкүн."</string>
     <string name="install_failed" msgid="5777824004474125469">"Колдонмо орнотулган жок."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Топтомду орнотууга болбойт."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Башка топтом менен дал келбегендиктен колдонмо орнотулган жок."</string>
diff --git a/packages/PackageInstaller/res/values-lo/strings.xml b/packages/PackageInstaller/res/values-lo/strings.xml
index f3912cc..667e074 100644
--- a/packages/PackageInstaller/res/values-lo/strings.xml
+++ b/packages/PackageInstaller/res/values-lo/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ຕິດຕັ້ງແອັບແລ້ວ."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"ທ່ານຕ້ອງການຕິດຕັ້ງແອັບນີ້ບໍ່?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ທ່ານຕ້ອງການອັບເດດແອັບນີ້ບໍ່?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"ອັບເດດແອັບນີ້ຈາກ <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ບໍ?\n\nໂດຍທົ່ວໄປແລ້ວແອັບນີ້ຈະໄດ້ຮັບການອັບເດດຈາກ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. ການອັບເດດຈາກແຫຼ່ງທີ່ມາອື່ນອາດເຮັດໃຫ້ໂທລະສັບຂອງທ່ານໄດ້ຮັບການອັບເດດຈາກແຫຼ່ງທີ່ມານັ້ນໃນອະນາຄົດ. ຟັງຊັນການເຮັດວຽກຂອງແອັບອາດມີການປ່ຽນແປງ."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"ອັບເດດແອັບນີ້ຈາກ <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ບໍ?\n\nໂດຍທົ່ວໄປແລ້ວແອັບນີ້ຈະໄດ້ຮັບການອັບເດດຈາກ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. ໂດຍການອັບເດດຈາກແຫຼ່ງອື່ນ, ທ່ານອາດຈະໄດ້ຮັບການອັບເດດໃນອະນາຄົດຈາກແຫຼ່ງທີ່ມາໃດກໍໄດ້ຢູ່ແທັບເລັດຂອງທ່ານ. ຟັງຊັນການນຳໃຊ້ແອັບອາດມີການປ່ຽນແປງ."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"ອັບເດດແອັບນີ້ຈາກ <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ບໍ?\n\nໂດຍທົ່ວໄປແລ້ວແອັບນີ້ຈະໄດ້ຮັບການອັບເດດຈາກ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. ໂດຍການອັບເດດຈາກແຫຼ່ງອື່ນ, ທ່ານອາດຈະໄດ້ຮັບການອັບເດດໃນອະນາຄົດຈາກແຫຼ່ງທີ່ມາໃດກໍໄດ້ຢູ່ໂທລະທັດຂອງທ່ານ. ຟັງຊັນການນຳໃຊ້ແອັບອາດມີການປ່ຽນແປງ."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"ອັບເດດແອັບນີ້ຈາກ <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ບໍ?\n\nໂດຍທົ່ວໄປແລ້ວແອັບນີ້ຈະໄດ້ຮັບການອັບເດດຈາກ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. ການອັບເດດຈາກແຫຼ່ງທີ່ມາອື່ນອາດເຮັດໃຫ້ໂທລະສັບຂອງທ່ານໄດ້ຮັບການອັບເດດຈາກແຫຼ່ງທີ່ມານັ້ນໃນອະນາຄົດ. ຟັງຊັນການເຮັດວຽກຂອງແອັບອາດມີການປ່ຽນແປງ."</string>
     <string name="install_failed" msgid="5777824004474125469">"ບໍ່ໄດ້ຕິດຕັ້ງແອັບເທື່ອ."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ແພັກ​ເກດ​ຖືກບ​ລັອກ​ບໍ່​ໃຫ້​ໄດ້​ຮັບ​ການ​ຕິດ​ຕັ້ງ."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ບໍ່ໄດ້ຕິດຕັ້ງແອັບເນື່ອງຈາກແພັກເກດຂັດແຍ່ງກັບແພັກເກດທີ່ມີຢູ່ກ່ອນແລ້ວ."</string>
diff --git a/packages/PackageInstaller/res/values-lt/strings.xml b/packages/PackageInstaller/res/values-lt/strings.xml
index a7ec560e..1fe963d 100644
--- a/packages/PackageInstaller/res/values-lt/strings.xml
+++ b/packages/PackageInstaller/res/values-lt/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Programa įdiegta."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Ar norite įdiegti šią programą?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Ar norite atnaujinti šią programą?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Atnaujinti šią programą iš <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nŠi programa įprastai gauna naujinius iš <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Atnaujinę iš kito šaltinio, būsimus naujinius galite gauti iš bet kurio šaltinio telefone. Gali būti pakeistos programos funkcijos."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Atnaujinti šią programą iš <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nŠi programa įprastai gauna naujinius iš <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Atnaujinę iš kito šaltinio, būsimus naujinius galite gauti iš bet kurio šaltinio planšetiniame kompiuteryje. Gali būti pakeistos programos funkcijos."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Atnaujinti šią programą iš <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nŠi programa įprastai gauna naujinius iš <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Atnaujinę iš kito šaltinio, būsimus naujinius galite gauti iš bet kurio šaltinio televizoriuje. Gali būti pakeistos programos funkcijos."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Atnaujinti šią programą iš <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nŠi programa įprastai gauna naujinius iš <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Atnaujinę iš kito šaltinio, būsimus naujinius galite gauti iš bet kurio šaltinio telefone. Gali būti pakeistos programos funkcijos."</string>
     <string name="install_failed" msgid="5777824004474125469">"Programa neįdiegta."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paketas užblokuotas ir negali būti įdiegtas."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Programa neįdiegta, nes paketas nesuderinamas su esamu paketu."</string>
diff --git a/packages/PackageInstaller/res/values-lv/strings.xml b/packages/PackageInstaller/res/values-lv/strings.xml
index 17dd542..8676579 100644
--- a/packages/PackageInstaller/res/values-lv/strings.xml
+++ b/packages/PackageInstaller/res/values-lv/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Lietotne ir instalēta."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Vai vēlaties instalēt šo lietotni?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Vai vēlaties atjaunināt šo lietotni?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Vai atjaunināt šo lietotni, izmantojot “<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>”?\n\nŠī lietotne parasti saņem atjauninājumus no “<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>”. Veicot atjaunināšanu no cita avota, iespējams, turpmāk tālrunī saņemsiet atjauninājumus no jebkāda avota. Lietotnes funkcionalitāte var mainīties."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Vai atjaunināt šo lietotni, izmantojot “<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>”?\n\nŠai lietotnei parasti saņemat atjauninājumus no “<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>”. Veicot atjaunināšanu no cita avota, iespējams, turpmāk planšetdatorā saņemsiet atjauninājumus no jebkāda avota. Lietotnes funkcionalitāte var mainīties."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Vai atjaunināt šo lietotni, izmantojot “<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>”?\n\nŠai lietotnei parasti saņemat atjauninājumus no “<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>”. Veicot atjaunināšanu no cita avota, iespējams, turpmāk televizorā saņemsiet atjauninājumus no jebkāda avota. Lietotnes funkcionalitāte var mainīties."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Vai atjaunināt šo lietotni, izmantojot “<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>”?\n\nŠī lietotne parasti saņem atjauninājumus no “<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>”. Veicot atjaunināšanu no cita avota, iespējams, turpmāk tālrunī saņemsiet atjauninājumus no jebkāda avota. Lietotnes funkcionalitāte var mainīties."</string>
     <string name="install_failed" msgid="5777824004474125469">"Lietotne nav instalēta."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Pakotnes instalēšana tika bloķēta."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Lietotne netika instalēta, jo pastāv pakotnes konflikts ar esošu pakotni."</string>
diff --git a/packages/PackageInstaller/res/values-mk/strings.xml b/packages/PackageInstaller/res/values-mk/strings.xml
index 5aaea17..c8f439e 100644
--- a/packages/PackageInstaller/res/values-mk/strings.xml
+++ b/packages/PackageInstaller/res/values-mk/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Апликацијата е инсталирана."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Дали сакате да ја инсталирате апликацијава?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Дали сакате да ја ажурирате апликацијава?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Да се ажурира апликацијава од <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nАпликацијава вообичаено добива ажурирања од<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Со ажурирање од различен извор, може да добивате идни ажурирања од кој било извор на вашиот телефон. Функционалноста на апликацијата може да се промени."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Да се ажурира апликацијава од <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nАпликацијава вообичаено добива ажурирања од<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Со ажурирање од различен извор, може да добивате идни ажурирања од кој било извор на вашиот таблет. Функционалноста на апликацијата може да се промени."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Да се ажурира апликацијава од <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nАпликацијава вообичаено добива ажурирања од<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Со ажурирање од различен извор, може да добивате идни ажурирања од кој било извор на вашиот телевизор. Функционалноста на апликацијата може да се промени."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Да се ажурира апликацијава од <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nАпликацијава вообичаено добива ажурирања од<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Со ажурирање од различен извор, може да добивате идни ажурирања од кој било извор на вашиот телефон. Функционалноста на апликацијата може да се промени."</string>
     <string name="install_failed" msgid="5777824004474125469">"Апликацијата не е инсталирана."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Инсталирањето на пакетот е блокирано."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Апликација што не е инсталирана како пакет е во конфликт со постоечки пакет."</string>
diff --git a/packages/PackageInstaller/res/values-ml/strings.xml b/packages/PackageInstaller/res/values-ml/strings.xml
index 0535843..f051cc1 100644
--- a/packages/PackageInstaller/res/values-ml/strings.xml
+++ b/packages/PackageInstaller/res/values-ml/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ആപ്പ് ഇൻസ്‌റ്റാൾ ചെയ്‌തു."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"ഈ ആപ്പ് ഇൻസ്‌റ്റാൾ ചെയ്യണോ?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ഈ ആപ്പ് അപ്‌ഡേറ്റ് ചെയ്യണോ?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> എന്നതിൽ നിന്ന് ഈ ആപ്പ് അപ്‌ഡേറ്റ് ചെയ്യണോ?\n\nഈ ആപ്പിന് സാധാരണയായി <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> എന്നതിൽ നിന്ന് അപ്‌ഡേറ്റുകൾ ലഭിക്കാറുണ്ട്. മറ്റൊരു ഉറവിടത്തിൽ നിന്ന് അപ്‌ഡേറ്റ് ചെയ്യുന്നത് വഴി, നിങ്ങളുടെ ഫോണിലെ ഏത് ഉറവിടത്തിൽ നിന്നും ഭാവിയിൽ അപ്‌ഡേറ്റുകൾ ലഭിക്കാൻ ഇടയുണ്ട്. ആപ്പ് ഫംഗ്ഷണാലിറ്റിയിൽ വ്യത്യാസം വന്നേക്കാം."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> എന്നതിൽ നിന്ന് ഈ ആപ്പ് അപ്‌ഡേറ്റ് ചെയ്യണോ?\n\nഈ ആപ്പിന് സാധാരണയായി <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> എന്നതിൽ നിന്ന് അപ്‌ഡേറ്റുകൾ ലഭിക്കാറുണ്ട്. മറ്റൊരു ഉറവിടത്തിൽ നിന്ന് അപ്‌ഡേറ്റ് ചെയ്യുന്നതിലൂടെ, നിങ്ങളുടെ ടാബ്‌ലെറ്റിലെ ഏത് ഉറവിടത്തിൽ നിന്നും ഭാവിയിൽ അപ്‌ഡേറ്റുകൾ ലഭിച്ചേക്കാം. ആപ്പ് ഫംഗ്ഷണാലിറ്റിയിൽ വ്യത്യാസം വന്നേക്കാം."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> എന്നതിൽ നിന്ന് ഈ ആപ്പ് അപ്‌ഡേറ്റ് ചെയ്യണോ?\n\nഈ ആപ്പിന് സാധാരണയായി <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> എന്നതിൽ നിന്ന് അപ്‌ഡേറ്റുകൾ ലഭിക്കാറുണ്ട്. മറ്റൊരു ഉറവിടത്തിൽ നിന്ന് അപ്‌ഡേറ്റ് ചെയ്യുന്നതിലൂടെ, നിങ്ങളുടെ ടിവിയിലെ ഏത് ഉറവിടത്തിൽ നിന്നും ഭാവിയിൽ അപ്‌ഡേറ്റുകൾ ലഭിച്ചേക്കാം. ആപ്പ് ഫംഗ്ഷണാലിറ്റിയിൽ വ്യത്യാസം വന്നേക്കാം."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> എന്നതിൽ നിന്ന് ഈ ആപ്പ് അപ്‌ഡേറ്റ് ചെയ്യണോ?\n\nഈ ആപ്പിന് സാധാരണയായി <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> എന്നതിൽ നിന്ന് അപ്‌ഡേറ്റുകൾ ലഭിക്കാറുണ്ട്. മറ്റൊരു ഉറവിടത്തിൽ നിന്ന് അപ്‌ഡേറ്റ് ചെയ്യുന്നത് വഴി, നിങ്ങളുടെ ഫോണിലെ ഏത് ഉറവിടത്തിൽ നിന്നും ഭാവിയിൽ അപ്‌ഡേറ്റുകൾ ലഭിക്കാൻ ഇടയുണ്ട്. ആപ്പ് ഫംഗ്ഷണാലിറ്റിയിൽ വ്യത്യാസം വന്നേക്കാം."</string>
     <string name="install_failed" msgid="5777824004474125469">"ആപ്പ് ഇൻസ്‌റ്റാൾ ചെയ്‌തിട്ടില്ല."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"പാക്കേജ് ഇൻസ്‌റ്റാൾ ചെയ്യുന്നത് ബ്ലോക്ക് ചെയ്‌തു."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"പാക്കേജിന് നിലവിലുള്ള പാക്കേജുമായി പൊരുത്തക്കേടുള്ളതിനാൽ, ആപ്പ് ഇൻസ്‌റ്റാൾ ചെയ്‌തില്ല."</string>
diff --git a/packages/PackageInstaller/res/values-mn/strings.xml b/packages/PackageInstaller/res/values-mn/strings.xml
index 84a3909..6098eb8 100644
--- a/packages/PackageInstaller/res/values-mn/strings.xml
+++ b/packages/PackageInstaller/res/values-mn/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Аппыг суулгасан."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Та энэ аппыг суулгахыг хүсэж байна уу?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Та энэ аппыг шинэчлэхийг хүсэж байна уу?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Аппыг <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-с шинэчлэх үү?\n\nЭнэ апп ихэвчлэн <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-с шинэчлэлт хүлээн авдаг. Өөр эх сурвалжаас шинэчилснээр та ирээдүйн шинэчлэлтийг утсан дээрх аливаа эх сурвалжаас хүлээн авч магадгүй. Аппын ажиллагаа өөрчлөгдөж магадгүй."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Энэ аппыг <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-с шинэчлэх үү?\n\nЭнэ апп ихэвчлэн <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-с шинэчлэлт хүлээн авдаг. Өөр эх сурвалжаас шинэчилснээр та ирээдүйн шинэчлэлтийг таблет дээрх аливаа эх сурвалжаас хүлээн авч магадгүй. Аппын ажиллагаа өөрчлөгдөж магадгүй."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Энэ аппыг <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-с шинэчлэх үү?\n\nЭнэ апп ихэвчлэн <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-с шинэчлэлт хүлээн авдаг. Өөр эх сурвалжаас шинэчилснээр та ирээдүйн шинэчлэлтийг ТВ дээрх аливаа эх сурвалжаас хүлээн авч магадгүй. Аппын ажиллагаа өөрчлөгдөж магадгүй."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Аппыг <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>-с шинэчлэх үү?\n\nЭнэ апп ихэвчлэн <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>-с шинэчлэлт хүлээн авдаг. Өөр эх сурвалжаас шинэчилснээр та ирээдүйн шинэчлэлтийг утсан дээрх аливаа эх сурвалжаас хүлээн авч магадгүй. Аппын ажиллагаа өөрчлөгдөж магадгүй."</string>
     <string name="install_failed" msgid="5777824004474125469">"Аппыг суулгаагүй."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Багц суулгахыг блоклосон байна."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Багц одоо байгаа багцтай тохирохгүй байгаа тул аппыг суулгаж чадсангүй."</string>
diff --git a/packages/PackageInstaller/res/values-mr/strings.xml b/packages/PackageInstaller/res/values-mr/strings.xml
index 367dede..5364fa4 100644
--- a/packages/PackageInstaller/res/values-mr/strings.xml
+++ b/packages/PackageInstaller/res/values-mr/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"अ‍ॅप इंस्टॉल झाले."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"तुम्हाला हे ॲप इंस्टॉल करायचे आहे का?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"तुम्हाला हे ॲप अपडेट करायचे आहे का?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> वरून हे अ‍ॅप अपडेट करायचे आहे का?\n\nया अ‍ॅपला सामान्यतः <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> कडून अपडेट मिळतात. वेगवेगळ्या स्रोताकडून अपडेट करून, तुम्हाला तुमच्या फोनवरील कोणत्याही स्रोताकडून भविष्यातील अपडेट मिळू शकतात. अ‍ॅपची कार्यक्षमता बदलू शकते."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> वरून हे अ‍ॅप अपडेट करायचे आहे का?\n\nया अ‍ॅपला सामान्यतः <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> कडून अपडेट मिळतात. वेगवेगळ्या स्रोताकडून अपडेट करून, तुम्हाला तुमच्या टॅबलेटवरील कोणत्याही स्रोताकडून भविष्यातील अपडेट मिळू शकतात. अ‍ॅपची कार्यक्षमता बदलू शकते."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> वरून हे अ‍ॅप अपडेट करायचे आहे का?\n\nया अ‍ॅपला सामान्यतः <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> कडून अपडेट मिळतात. वेगवेगळ्या स्रोताकडून अपडेट करून, तुम्हाला तुमच्या टीव्हीवरील कोणत्याही स्रोताकडून भविष्यातील अपडेट मिळू शकतात. अ‍ॅपची कार्यक्षमता बदलू शकते."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> वरून हे अ‍ॅप अपडेट करायचे आहे का?\n\nया अ‍ॅपला सामान्यतः <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> कडून अपडेट मिळतात. वेगवेगळ्या स्रोताकडून अपडेट करून, तुम्हाला तुमच्या फोनवरील कोणत्याही स्रोताकडून भविष्यातील अपडेट मिळू शकतात. अ‍ॅपची कार्यक्षमता बदलू शकते."</string>
     <string name="install_failed" msgid="5777824004474125469">"अ‍ॅप इंस्टॉल झाले नाही."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"पॅकेज इंस्टॉल होण्यापासून ब्लॉक केले होते."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"पॅकेजचा विद्यमान पॅकेजशी विरोध असल्याने अ‍ॅप इंस्टॉल झाले नाही."</string>
diff --git a/packages/PackageInstaller/res/values-ms/strings.xml b/packages/PackageInstaller/res/values-ms/strings.xml
index 6ab6622..7f62f5b 100644
--- a/packages/PackageInstaller/res/values-ms/strings.xml
+++ b/packages/PackageInstaller/res/values-ms/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikasi dipasang."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Adakah anda ingin memasang aplikasi ini?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Adakah anda mahu mengemas kini apl ini?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Kemas kinikan apl ini daripada <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nApl ini biasanya menerima kemaskinian daripada <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Dengan membuat kemaskinian daripada sumber yang berbeza, anda mungkin menerima kemaskinian masa hadapan daripada sebarang sumber pada telefon anda. Fungsi apl mungkin berubah."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Kemas kinikan apl ini daripada <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nApl ini biasanya menerima kemaskinian daripada <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Dengan membuat kemaskinian daripada sumber yang berbeza, anda mungkin menerima kemaskinian masa hadapan daripada sebarang sumber pada tablet anda. Fungsi apl mungkin berubah."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Kemas kinikan apl ini daripada <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nApl ini biasanya menerima kemaskinian daripada <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Dengan membuat kemaskinian daripada sumber yang berbeza, anda mungkin menerima kemaskinian masa hadapan daripada sebarang sumber pada TV anda. Fungsi apl mungkin berubah."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Kemas kinikan apl ini daripada <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nApl ini biasanya menerima kemaskinian daripada <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Dengan membuat kemaskinian daripada sumber yang berbeza, anda mungkin menerima kemaskinian masa hadapan daripada sebarang sumber pada telefon anda. Fungsi apl mungkin berubah."</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikasi tidak dipasang."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Pakej ini telah disekat daripada dipasang."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Apl tidak dipasang kerana pakej bercanggah dengan pakej yang sedia ada."</string>
diff --git a/packages/PackageInstaller/res/values-my/strings.xml b/packages/PackageInstaller/res/values-my/strings.xml
index cb05b06..4eafd7a 100644
--- a/packages/PackageInstaller/res/values-my/strings.xml
+++ b/packages/PackageInstaller/res/values-my/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"အက်ပ်ထည့်သွင်းပြီးပါပြီ။"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"ဤအက်ပ်ကို ထည့်သွင်းလိုသလား။"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ဤအက်ပ်ကို အပ်ဒိတ်လုပ်လိုသလား။"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"ဤအက်ပ်ကို <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> မှ အပ်ဒိတ်လုပ်မလား။\n\nဤအက်ပ်သည် ပုံမှန်အားဖြင့် <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> မှ အပ်ဒိတ်များ ရရှိသည်။ မတူညီသောရင်းမြစ်မှ အပ်ဒိတ်လုပ်ခြင်းဖြင့် ဖုန်းပေါ်တွင် နောင်လာမည့်အပ်ဒိတ်များကို မည်သည့်ရင်းမြစ်မှမဆို လက်ခံရယူနိုင်သည်။ အက်ပ်လုပ်ဆောင်ချက် ပြောင်းလဲနိုင်သည်။"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"ဤအက်ပ်ကို <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> မှ အပ်ဒိတ်လုပ်မလား။\n\nဤအက်ပ်သည် ပုံမှန်အားဖြင့် <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> မှ အပ်ဒိတ်များ ရရှိသည်။ မတူညီသောရင်းမြစ်မှ အပ်ဒိတ်လုပ်ခြင်းဖြင့် တက်ဘလက်ပေါ်တွင် နောင်လာမည့်အပ်ဒိတ်များကို မည်သည့်ရင်းမြစ်မဆိုမှ လက်ခံရယူနိုင်သည်။ အက်ပ်လုပ်ဆောင်ချက် ပြောင်းလဲနိုင်သည်။"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"ဤအက်ပ်ကို <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> မှ အပ်ဒိတ်လုပ်မလား။\n\nဤအက်ပ်သည် ပုံမှန်အားဖြင့် <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> မှ အပ်ဒိတ်များ ရရှိသည်။ မတူညီသောရင်းမြစ်မှ အပ်ဒိတ်လုပ်ခြင်းဖြင့် TV တွင် နောင်လာမည့်အပ်ဒိတ်များကို မည်သည့်ရင်းမြစ်မဆိုမှ လက်ခံရယူနိုင်သည်။ အက်ပ်လုပ်ဆောင်ချက် ပြောင်းလဲနိုင်သည်။"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"ဤအက်ပ်ကို <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> မှ အပ်ဒိတ်လုပ်မလား။\n\nဤအက်ပ်သည် ပုံမှန်အားဖြင့် <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> မှ အပ်ဒိတ်များ ရရှိသည်။ မတူညီသောရင်းမြစ်မှ အပ်ဒိတ်လုပ်ခြင်းဖြင့် ဖုန်းပေါ်တွင် နောင်လာမည့်အပ်ဒိတ်များကို မည်သည့်ရင်းမြစ်မှမဆို လက်ခံရယူနိုင်သည်။ အက်ပ်လုပ်ဆောင်ချက် ပြောင်းလဲနိုင်သည်။"</string>
     <string name="install_failed" msgid="5777824004474125469">"အက်ပ်မထည့်သွင်းရသေးပါ"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ပက်ကေ့ဂျ်ထည့်သွင်းခြင်းကို ပိတ်ထားသည်။"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ပက်ကေ့ဂျ်အဖြစ် ထည့်သွင်းမထားသော အက်ပ်သည် လက်ရှိပက်ကေ့ဂျ်နှင့် တိုက်နေသည်။"</string>
diff --git a/packages/PackageInstaller/res/values-nb/strings.xml b/packages/PackageInstaller/res/values-nb/strings.xml
index 7532d7b..7d87473 100644
--- a/packages/PackageInstaller/res/values-nb/strings.xml
+++ b/packages/PackageInstaller/res/values-nb/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Appen er installert."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Vil du installere denne appen?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Vil du oppdatere denne appen?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Vil du oppdatere denne appen fra <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDenne appen mottar vanligvis oppdateringer fra <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Hvis du oppdaterer fra en annen kilde, kan du få fremtidige oppdateringer fra en hvilken som helst kilde på telefonen. Appfunksjonaliteten kan endres."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Vil du oppdatere denne appen fra <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDenne appen får vanligvis oppdateringer fra <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Hvis du oppdaterer fra en annen kilde, kan du få fremtidige oppdateringer fra en hvilken som helst kilde på nettbrettet. Appfunksjonaliteten kan endres."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Vil du oppdatere denne appen fra <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDenne appen får vanligvis oppdateringer fra <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Hvis du oppdaterer fra en annen kilde, kan du få fremtidige oppdateringer fra en hvilken som helst kilde på TV-en. Appfunksjonaliteten kan endres."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Vil du oppdatere denne appen fra <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDenne appen mottar vanligvis oppdateringer fra <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Hvis du oppdaterer fra en annen kilde, kan du få fremtidige oppdateringer fra en hvilken som helst kilde på telefonen. Appfunksjonaliteten kan endres."</string>
     <string name="install_failed" msgid="5777824004474125469">"Appen ble ikke installert."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Pakken er blokkert fra å bli installert."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Appen ble ikke installert fordi pakken er i konflikt med en eksisterende pakke."</string>
diff --git a/packages/PackageInstaller/res/values-ne/strings.xml b/packages/PackageInstaller/res/values-ne/strings.xml
index 0d7068e..ee0782a 100644
--- a/packages/PackageInstaller/res/values-ne/strings.xml
+++ b/packages/PackageInstaller/res/values-ne/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"एप इन्स्टल गरियो।"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"तपाईं यो एप इन्स्टल गर्न चाहनुहुन्छ?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"तपाईं यो एप अपडेट गर्न चाहनुहुन्छ?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"यो एप <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> बाट अपडेट गर्ने हो?\n\nयो एपले सामान्यतया <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> बाट अपडेट प्राप्त गर्छ। तपाईंले कुनै फरक स्रोतबाट अपडेट गर्नुभयो भने तपाईं भविष्यमा आफ्नो फोनमा भएको जुनसुकै स्रोतबाट अपडेटहरू प्राप्त गर्न सक्नुहुन्छ। यसो गर्दा एपका मुख्य सुविधाहरूले काम गर्ने तरिका परिवर्तन हुन सक्छ।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"यो एप <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> बाट अपडेट गर्ने हो?\n\nयो एपले सामान्यतया <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> बाट अपडेट प्राप्त गर्छ। तपाईंले कुनै फरक स्रोतबाट अपडेट गर्नुभयो भने तपाईं भविष्यमा आफ्नो ट्याब्लेटमा भएको जुनसुकै स्रोतबाट अपडेटहरू प्राप्त गर्न सक्नुहुन्छ। यसो गर्दा एपको मुख्य सुविधा परिवर्तन हुन सक्छ।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"यो एप <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> बाट अपडेट गर्ने हो?\n\nयो एपले सामान्यतया <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> बाट अपडेट प्राप्त गर्छ। तपाईंले कुनै फरक स्रोतबाट अपडेट गर्नुभयो भने तपाईं भविष्यमा आफ्नो टिभीमा भएको जुनसुकै स्रोतबाट अपडेटहरू प्राप्त गर्न सक्नुहुन्छ। यसो गर्दा एपको मुख्य सुविधा परिवर्तन हुन सक्छ।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"यो एप <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> बाट अपडेट गर्ने हो?\n\nयो एपले सामान्यतया <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> बाट अपडेट प्राप्त गर्छ। तपाईंले कुनै फरक स्रोतबाट अपडेट गर्नुभयो भने तपाईं भविष्यमा आफ्नो फोनमा भएको जुनसुकै स्रोतबाट अपडेटहरू प्राप्त गर्न सक्नुहुन्छ। यसो गर्दा एपका मुख्य सुविधाहरूले काम गर्ने तरिका परिवर्तन हुन सक्छ।"</string>
     <string name="install_failed" msgid="5777824004474125469">"एप स्थापना गरिएन।"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"यो प्याकेज स्थापना गर्ने क्रममा अवरोध गरियो।"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"प्याकेजका रूपमा स्थापना नगरिएको एप विद्यमान प्याकेजसँग मेल खाँदैन।"</string>
diff --git a/packages/PackageInstaller/res/values-nl/strings.xml b/packages/PackageInstaller/res/values-nl/strings.xml
index fab6d51..47a5ec0 100644
--- a/packages/PackageInstaller/res/values-nl/strings.xml
+++ b/packages/PackageInstaller/res/values-nl/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App geïnstalleerd."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Wil je deze app installeren?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Wil je deze app updaten?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Deze app updaten via <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDeze app krijgt gewoonlijk updates via <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Als je updatet via een andere bron, kun je toekomstige updates via elke bron op je telefoon krijgen. De app-functionaliteit kan veranderen."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Deze app updaten via <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDeze app krijgt gewoonlijk updates via <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Als je updatet via een andere bron, kun je toekomstige updates via elke bron op je tablet krijgen. De app-functionaliteit kan veranderen."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Deze app updaten via <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDeze app krijgt gewoonlijk updates via <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Als je updatet via een andere bron, kun je toekomstige updates via elke bron op je tv krijgen. De app-functionaliteit kan veranderen."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Deze app updaten via <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDeze app krijgt gewoonlijk updates via <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Als je updatet via een andere bron, kun je toekomstige updates via elke bron op je telefoon krijgen. De app-functionaliteit kan veranderen."</string>
     <string name="install_failed" msgid="5777824004474125469">"App niet geïnstalleerd."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"De installatie van het pakket is geblokkeerd."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"App die niet is geïnstalleerd als pakket conflicteert met een bestaand pakket."</string>
diff --git a/packages/PackageInstaller/res/values-or/strings.xml b/packages/PackageInstaller/res/values-or/strings.xml
index b6caebd..7c91ac4 100644
--- a/packages/PackageInstaller/res/values-or/strings.xml
+++ b/packages/PackageInstaller/res/values-or/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ଆପ ଇନଷ୍ଟଲ ହୋଇଗଲା।"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"ଆପଣ ଏହି ଆପକୁ ଇନଷ୍ଟଲ୍ କରିବା ପାଇଁ ଚାହୁଁଛନ୍ତି କି?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ଆପଣ ଏହି ଆପକୁ ଅପଡେଟ୍ କରିବା ପାଇଁ ଚାହୁଁଛନ୍ତି କି?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>ରୁ ଏହି ଆପକୁ ଅପଡେଟ କରିବେ?\n\nଏହି ଆପ ସାଧାରଣତଃ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>ରୁ ଅପଡେଟଗୁଡ଼ିକ ପାଏ। ଏକ ଭିନ୍ନ ସୋର୍ସରୁ ଅପଡେଟ କରି ଆପଣଙ୍କ ଫୋନରେ ଯେ କୌଣସି ସୋର୍ସରୁ ଭବିଷ୍ୟତର ଅପଡେଟଗୁଡ଼ିକ ଆପଣ ପାଇପାରନ୍ତି। ଆପ କାର୍ଯ୍ୟକ୍ଷମତା ପରିବର୍ତ୍ତନ ହୋଇପାରେ।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>ରୁ ଏହି ଆପକୁ ଅପଡେଟ କରିବେ?\n\nଏହି ଆପ ସାଧାରଣତଃ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>ରୁ ଅପଡେଟ ପାଇଥାଏ। ଏକ ଭିନ୍ନ ସୋର୍ସରୁ ଅପଡେଟ କରି ଆପଣ ଆପଣଙ୍କ ଫୋନରେ ଯେ କୌଣସି ସୋର୍ସରୁ ଭବିଷ୍ୟତର ଅପଡେଟଗୁଡ଼ିକ ପାଇପାରନ୍ତି। ଆପ କାର୍ଯ୍ୟକ୍ଷମତା ପରିବର୍ତ୍ତନ ହୋଇପାରେ।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>ରୁ ଏହି ଆପକୁ ଅପଡେଟ କରିବେ?\n\nଏହି ଆପ ସାଧାରଣତଃ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>ରୁ ଅପଡେଟ ପାଇଥାଏ। ଏକ ଭିନ୍ନ ସୋର୍ସରୁ ଅପଡେଟ କରି ଆପଣ ଆପଣଙ୍କ ଫୋନରେ ଯେ କୌଣସି ସୋର୍ସରୁ ଭବିଷ୍ୟତର ଅପଡେଟଗୁଡ଼ିକ ପାଇପାରନ୍ତି। ଆପ କାର୍ଯ୍ୟକ୍ଷମତା ପରିବର୍ତ୍ତନ ହୋଇପାରେ।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>ରୁ ଏହି ଆପକୁ ଅପଡେଟ କରିବେ?\n\nଏହି ଆପ ସାଧାରଣତଃ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>ରୁ ଅପଡେଟଗୁଡ଼ିକ ପାଏ। ଏକ ଭିନ୍ନ ସୋର୍ସରୁ ଅପଡେଟ କରି ଆପଣଙ୍କ ଫୋନରେ ଯେ କୌଣସି ସୋର୍ସରୁ ଭବିଷ୍ୟତର ଅପଡେଟଗୁଡ଼ିକ ଆପଣ ପାଇପାରନ୍ତି। ଆପ କାର୍ଯ୍ୟକ୍ଷମତା ପରିବର୍ତ୍ତନ ହୋଇପାରେ।"</string>
     <string name="install_failed" msgid="5777824004474125469">"ଆପ୍‍ ଇନଷ୍ଟଲ୍‌ ହୋଇନାହିଁ।"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ଏହି ପ୍ୟାକେଜ୍‌କୁ ଇନଷ୍ଟଲ୍‍ କରାଯିବାରୁ ଅବରୋଧ କରାଯାଇଥିଲା।"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ପୂର୍ବରୁ ଥିବା ପ୍ୟାକେଜ୍‍ ସହ ଏହି ପ୍ୟାକେଜ୍‌ର ସମସ୍ୟା ଉପୁଯିବାରୁ ଆପ୍‍ ଇନଷ୍ଟଲ୍‍ ହୋଇପାରିଲା ନାହିଁ।"</string>
diff --git a/packages/PackageInstaller/res/values-pa/strings.xml b/packages/PackageInstaller/res/values-pa/strings.xml
index 1ef4921..40ed479 100644
--- a/packages/PackageInstaller/res/values-pa/strings.xml
+++ b/packages/PackageInstaller/res/values-pa/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ਐਪ ਸਥਾਪਤ ਕੀਤੀ ਗਈ।"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"ਕੀ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਸਥਾਪਤ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ਕੀ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਅੱਪਡੇਟ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"ਕੀ ਇਸ ਐਪ ਨੂੰ <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ਤੋਂ ਅੱਪਡੇਟ ਕਰਨਾ ਹੈ?\n\nਇਸ ਐਪ ਨੂੰ ਆਮ ਤੌਰ \'ਤੇ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> ਤੋਂ ਅੱਪਡੇਟਾਂ ਪ੍ਰਾਪਤ ਹੁੰਦੀਆਂ ਹਨ। ਕਿਸੇ ਵੱਖਰੇ ਸਰੋਤ ਤੋਂ ਅੱਪਡੇਟ ਕਰ ਕੇ, ਤੁਸੀਂ ਆਪਣੇ ਫ਼ੋਨ \'ਤੇ ਕਿਸੇ ਵੀ ਸਰੋਤ ਤੋਂ ਭਵਿੱਖੀ ਅੱਪਡੇਟਾਂ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ। ਐਪ ਪ੍ਰਕਾਰਜਾਤਮਕਤਾ ਬਦਲ ਸਕਦੀ ਹੈ।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"ਕੀ ਇਸ ਐਪ ਨੂੰ <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ਤੋਂ ਅੱਪਡੇਟ ਕਰਨਾ ਹੈ?\n\nਇਸ ਐਪ ਨੂੰ ਆਮ ਤੌਰ \'ਤੇ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> ਤੋਂ ਅੱਪਡੇਟਾਂ ਪ੍ਰਾਪਤ ਹੁੰਦੀਆਂ ਹਨ। ਕਿਸੇ ਵੱਖਰੇ ਸਰੋਤ ਤੋਂ ਅੱਪਡੇਟ ਕਰ ਕੇ, ਤੁਸੀਂ ਆਪਣੇ ਟੈਬਲੈੱਟ \'ਤੇ ਕਿਸੇ ਵੀ ਸਰੋਤ ਤੋਂ ਭਵਿੱਖੀ ਅੱਪਡੇਟਾਂ ਨੂੰ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ। ਐਪ ਪ੍ਰਕਾਰਜਾਤਮਕਤਾ ਬਦਲ ਸਕਦੀ ਹੈ।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"ਕੀ ਇਸ ਐਪ ਨੂੰ <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ਤੋਂ ਅੱਪਡੇਟ ਕਰਨਾ ਹੈ?\n\nਇਸ ਐਪ ਨੂੰ ਆਮ ਤੌਰ \'ਤੇ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> ਤੋਂ ਅੱਪਡੇਟਾਂ ਪ੍ਰਾਪਤ ਹੁੰਦੀਆਂ ਹਨ। ਕਿਸੇ ਵੱਖਰੇ ਸਰੋਤ ਤੋਂ ਅੱਪਡੇਟ ਕਰ ਕੇ, ਤੁਸੀਂ ਆਪਣੇ ਟੀਵੀ \'ਤੇ ਕਿਸੇ ਵੀ ਸਰੋਤ ਤੋਂ ਭਵਿੱਖੀ ਅੱਪਡੇਟਾਂ ਨੂੰ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ। ਐਪ ਪ੍ਰਕਾਰਜਾਤਮਕਤਾ ਬਦਲ ਸਕਦੀ ਹੈ।"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"ਕੀ ਇਸ ਐਪ ਨੂੰ <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ਤੋਂ ਅੱਪਡੇਟ ਕਰਨਾ ਹੈ?\n\nਇਸ ਐਪ ਨੂੰ ਆਮ ਤੌਰ \'ਤੇ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> ਤੋਂ ਅੱਪਡੇਟਾਂ ਪ੍ਰਾਪਤ ਹੁੰਦੀਆਂ ਹਨ। ਕਿਸੇ ਵੱਖਰੇ ਸਰੋਤ ਤੋਂ ਅੱਪਡੇਟ ਕਰ ਕੇ, ਤੁਸੀਂ ਆਪਣੇ ਫ਼ੋਨ \'ਤੇ ਕਿਸੇ ਵੀ ਸਰੋਤ ਤੋਂ ਭਵਿੱਖੀ ਅੱਪਡੇਟਾਂ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ। ਐਪ ਪ੍ਰਕਾਰਜਾਤਮਕਤਾ ਬਦਲ ਸਕਦੀ ਹੈ।"</string>
     <string name="install_failed" msgid="5777824004474125469">"ਐਪ ਸਥਾਪਤ ਨਹੀਂ ਕੀਤੀ ਗਈ।"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ਪੈਕੇਜ ਨੂੰ ਸਥਾਪਤ ਹੋਣ ਤੋਂ ਬਲਾਕ ਕੀਤਾ ਗਿਆ ਸੀ।"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ਪੈਕੇਜ ਦੇ ਇੱਕ ਮੌਜੂਦਾ ਪੈਕੇਜ ਨਾਲ ਵਿਵਾਦ ਹੋਣ ਕਰਕੇ ਐਪ ਸਥਾਪਤ ਨਹੀਂ ਕੀਤੀ ਗਈ।"</string>
diff --git a/packages/PackageInstaller/res/values-pl/strings.xml b/packages/PackageInstaller/res/values-pl/strings.xml
index e44a391..264d247 100644
--- a/packages/PackageInstaller/res/values-pl/strings.xml
+++ b/packages/PackageInstaller/res/values-pl/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikacja została zainstalowana."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Zainstalować tę aplikację?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Zaktualizować tę aplikację?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Zastosować do aplikacji aktualizację pochodzącą z tego źródła (<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>)?\n\nAktualizacje dla tej aplikacji zwykle dostarcza <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Jeśli zastosujesz aplikację pochodzącą z innego źródła, możesz w przyszłości otrzymywać na telefonie aktualizacje z dowolnych źródeł. Funkcje aplikacji mogą się zmienić."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Zastosować do aplikacji aktualizację pochodzącą z tego źródła (<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>)?\n\nAktualizacje dla tej aplikacji zwykle dostarcza <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Jeśli zastosujesz aplikację pochodzącą z innego źródła, możesz w przyszłości otrzymywać na tablecie aktualizacje z dowolnych źródeł. Funkcje aplikacji mogą się zmienić."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Zastosować do aplikacji aktualizację pochodzącą z tego źródła (<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>)?\n\nAktualizacje dla tej aplikacji zwykle dostarcza <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Jeśli zastosujesz aplikację pochodzącą z innego źródła, możesz w przyszłości otrzymywać na telewizorze aktualizacje z dowolnych źródeł. Funkcje aplikacji mogą się zmienić."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Zastosować do aplikacji aktualizację pochodzącą z tego źródła (<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>)?\n\nAktualizacje dla tej aplikacji zwykle dostarcza <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Jeśli zastosujesz aplikację pochodzącą z innego źródła, możesz w przyszłości otrzymywać na telefonie aktualizacje z dowolnych źródeł. Funkcje aplikacji mogą się zmienić."</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikacja nie została zainstalowana."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Instalacja pakietu została zablokowana."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikacja nie została zainstalowana, bo powoduje konflikt z istniejącym pakietem."</string>
diff --git a/packages/PackageInstaller/res/values-pt-rBR/strings.xml b/packages/PackageInstaller/res/values-pt-rBR/strings.xml
index 923c3bd..38ea89e 100644
--- a/packages/PackageInstaller/res/values-pt-rBR/strings.xml
+++ b/packages/PackageInstaller/res/values-pt-rBR/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App instalado."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Quer instalar esse app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Quer atualizar esse app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Atualizar este app com <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEste app normalmente recebe atualizações de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se você usa uma fonte diferente, o smartphone vai aceitar outras fontes para fazer as próximas atualizações. A funcionalidade do app pode mudar."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Atualizar este app com <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAs atualizações dele normalmente são feitas com <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ao usar uma fonte diferente, as próximas atualizações poderão ser feitas com qualquer fonte no tablet. A funcionalidade do app pode mudar."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Atualizar este app com <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAs atualizações dele normalmente são feitas com <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ao usar uma fonte diferente, as próximas atualizações poderão ser feitas com qualquer fonte na TV. A funcionalidade do app pode mudar."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Atualizar este app com <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEste app normalmente recebe atualizações de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se você usa uma fonte diferente, o smartphone vai aceitar outras fontes para fazer as próximas atualizações. A funcionalidade do app pode mudar."</string>
     <string name="install_failed" msgid="5777824004474125469">"O app não foi instalado."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"A instalação do pacote foi bloqueada."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Como o pacote tem um conflito com um pacote já existente, o app não foi instalado."</string>
diff --git a/packages/PackageInstaller/res/values-pt-rPT/strings.xml b/packages/PackageInstaller/res/values-pt-rPT/strings.xml
index ebe5750..49f9f74 100644
--- a/packages/PackageInstaller/res/values-pt-rPT/strings.xml
+++ b/packages/PackageInstaller/res/values-pt-rPT/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App instalada."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Instalar esta app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Quer atualizar esta app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Atualizar esta app a partir de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nNormalmente, esta app recebe atualizações de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se atualizar a partir de uma fonte diferente, poderá receber futuras atualizações de qualquer fonte no seu telemóvel. A funcionalidade da app pode sofrer alterações."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Atualizar esta app a partir de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nNormalmente, esta app recebe atualizações de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se atualizar a partir de uma origem diferente, poderá receber futuras atualizações de qualquer origem no seu tablet. A funcionalidade da app pode sofrer alterações."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Atualizar esta app a partir de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nNormalmente, esta app recebe atualizações de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se atualizar a partir de uma origem diferente, poderá receber futuras atualizações de qualquer origem na sua TV. A funcionalidade da app pode sofrer alterações."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Atualizar esta app a partir de <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nNormalmente, esta app recebe atualizações de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se atualizar a partir de uma fonte diferente, poderá receber futuras atualizações de qualquer fonte no seu telemóvel. A funcionalidade da app pode sofrer alterações."</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplicação não instalada."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Foi bloqueada a instalação do pacote."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"A app não foi instalada porque o pacote entra em conflito com um pacote existente."</string>
diff --git a/packages/PackageInstaller/res/values-pt/strings.xml b/packages/PackageInstaller/res/values-pt/strings.xml
index 923c3bd..38ea89e 100644
--- a/packages/PackageInstaller/res/values-pt/strings.xml
+++ b/packages/PackageInstaller/res/values-pt/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"App instalado."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Quer instalar esse app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Quer atualizar esse app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Atualizar este app com <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEste app normalmente recebe atualizações de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se você usa uma fonte diferente, o smartphone vai aceitar outras fontes para fazer as próximas atualizações. A funcionalidade do app pode mudar."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Atualizar este app com <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAs atualizações dele normalmente são feitas com <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ao usar uma fonte diferente, as próximas atualizações poderão ser feitas com qualquer fonte no tablet. A funcionalidade do app pode mudar."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Atualizar este app com <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAs atualizações dele normalmente são feitas com <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ao usar uma fonte diferente, as próximas atualizações poderão ser feitas com qualquer fonte na TV. A funcionalidade do app pode mudar."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Atualizar este app com <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nEste app normalmente recebe atualizações de <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Se você usa uma fonte diferente, o smartphone vai aceitar outras fontes para fazer as próximas atualizações. A funcionalidade do app pode mudar."</string>
     <string name="install_failed" msgid="5777824004474125469">"O app não foi instalado."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"A instalação do pacote foi bloqueada."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Como o pacote tem um conflito com um pacote já existente, o app não foi instalado."</string>
diff --git a/packages/PackageInstaller/res/values-ro/strings.xml b/packages/PackageInstaller/res/values-ro/strings.xml
index de4dd55..471ff09 100644
--- a/packages/PackageInstaller/res/values-ro/strings.xml
+++ b/packages/PackageInstaller/res/values-ro/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplicație instalată."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Vrei să instalezi această aplicație?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Vrei să actualizezi această aplicație?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Actualizezi aplicația de la <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDe obicei, aplicația primește actualizări de la <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Dacă actualizezi din altă sursă, este posibil să primești actualizări viitoare din orice sursă pe telefon. Funcționalitatea aplicației se poate modifica."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Actualizează aplicația de la <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDe obicei, aplicația primește actualizări de la <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Dacă actualizezi din altă sursă, este posibil să primești actualizări viitoare din orice sursă pe tabletă. Funcționalitatea aplicației se poate modifica."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Actualizează aplicația de la <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDe obicei, aplicația primește actualizări de la <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Dacă actualizezi din altă sursă, este posibil să primești actualizări viitoare din orice sursă pe televizor. Funcționalitatea aplicației se poate modifica."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Actualizezi aplicația de la <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nDe obicei, aplicația primește actualizări de la <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Dacă actualizezi din altă sursă, este posibil să primești actualizări viitoare din orice sursă pe telefon. Funcționalitatea aplicației se poate modifica."</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplicația nu a fost instalată."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Instalarea pachetului a fost blocată."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplicația nu a fost instalată deoarece pachetul intră în conflict cu un pachet existent."</string>
diff --git a/packages/PackageInstaller/res/values-ru/strings.xml b/packages/PackageInstaller/res/values-ru/strings.xml
index d1f56fd..11e2ebf 100644
--- a/packages/PackageInstaller/res/values-ru/strings.xml
+++ b/packages/PackageInstaller/res/values-ru/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Приложение установлено."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Установить приложение?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Обновить приложение?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Обновить приложение отсюда: <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nСтандартный источник обновлений этого приложения – <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Если обновить приложение из другого источника, для последующих обновлений будут использоваться любые источники на телефоне. Функции приложения могут измениться."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Обновить приложение из <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nОбычно это приложение получает обновления из <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Если обновить приложение из другого источника, в будущем для обновления могут использоваться любые источники на планшете. Функции приложения могут измениться."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Обновить приложение из <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nОбычно это приложение получает обновления из <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Если обновить приложение из другого источника, в будущем для обновления могут использоваться любые источники на телевизоре. Функции приложения могут измениться."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Обновить приложение отсюда: <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nСтандартный источник обновлений этого приложения – <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Если обновить приложение из другого источника, для последующих обновлений будут использоваться любые источники на телефоне. Функции приложения могут измениться."</string>
     <string name="install_failed" msgid="5777824004474125469">"Приложение не установлено."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Установка пакета заблокирована."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Приложение не установлено, так как оно конфликтует с другим пакетом."</string>
diff --git a/packages/PackageInstaller/res/values-si/strings.xml b/packages/PackageInstaller/res/values-si/strings.xml
index c300b68..b0392ed 100644
--- a/packages/PackageInstaller/res/values-si/strings.xml
+++ b/packages/PackageInstaller/res/values-si/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"යෙදුම ස්ථාපනය කර ඇත."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"මෙම යෙදුම ස්ථාපනය කිරීමට ඔබට අවශ්‍යද?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"ඔබට මෙම යෙදුම යාවත්කාලීන කිරීමට අවශ්‍යද?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> වෙතින් මෙම යෙදුම යාවත්කාලීන කරන්න ද?\n\nමෙම යෙදුමට සාමාන්‍යයෙන් <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> සිට යාවත්කාලීන ලැබේ. වෙනස් මූලාශ්‍රයකින් යාවත්කාලීන කිරීමෙන්, ඔබට ඔබේ දුරකථනයෙහි ඕනෑම මූලාශ්‍රයකින් අනාගත යාවත්කාලීන ලැබීමට ඉඩ ඇත. යෙදුම් ක්‍රියාකාරිත්වය වෙනස් වීමට ඉඩ ඇත."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> වෙතින් මෙම යෙදුම යාවත්කාලීන කරන්න ද?\n\nමෙම යෙදුමට සාමාන්‍යයෙන් <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> සිට යාවත්කාලීන ලැබේ. වෙනස් මූලාශ්‍රයකින් යාවත්කාලීන කිරීමෙන්, ඔබට ඔබේ ටැබ්ලටයෙහි ඕනෑම මූලාශ්‍රයකින් අනාගත යාවත්කාලීන ලැබීමට ඉඩ ඇත. යෙදුම් ක්‍රියාකාරිත්වය වෙනස් වීමට ඉඩ ඇත."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> වෙතින් මෙම යෙදුම යාවත්කාලීන කරන්න ද?\n\nමෙම යෙදුමට සාමාන්‍යයෙන් <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> සිට යාවත්කාලීන ලැබේ. වෙනස් මූලාශ්‍රයකින් යාවත්කාලීන කිරීමෙන්, ඔබට ඔබේ TV මත ඕනෑම මූලාශ්‍රයකින් අනාගත යාවත්කාලීන ලැබීමට ඉඩ ඇත. යෙදුම් ක්‍රියාකාරිත්වය වෙනස් වීමට ඉඩ ඇත."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> වෙතින් මෙම යෙදුම යාවත්කාලීන කරන්න ද?\n\nමෙම යෙදුමට සාමාන්‍යයෙන් <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> සිට යාවත්කාලීන ලැබේ. වෙනස් මූලාශ්‍රයකින් යාවත්කාලීන කිරීමෙන්, ඔබට ඔබේ දුරකථනයෙහි ඕනෑම මූලාශ්‍රයකින් අනාගත යාවත්කාලීන ලැබීමට ඉඩ ඇත. යෙදුම් ක්‍රියාකාරිත්වය වෙනස් වීමට ඉඩ ඇත."</string>
     <string name="install_failed" msgid="5777824004474125469">"යෙදුම ස්ථාපනය කර නැත."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"මෙම පැකේජය ස්ථාපනය කිරීම අවහිර කරන ලදි."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"පැකේජය දැනට පවතින පැකේජයක් සමග ගැටෙන නිසා යෙදුම ස්ථාපනය නොකරන ලදී."</string>
diff --git a/packages/PackageInstaller/res/values-sk/strings.xml b/packages/PackageInstaller/res/values-sk/strings.xml
index 58a3a35..8e84671 100644
--- a/packages/PackageInstaller/res/values-sk/strings.xml
+++ b/packages/PackageInstaller/res/values-sk/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikácia bola nainštalovaná."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Chcete túto aplikáciu nainštalovať?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Chcete túto aplikáciu aktualizovať?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Chcete aktualizovať túto aplikáciu zo zdroja <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nTúto aplikáciu obvykle aktualizuje <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ak vykonáte aktualizáciu z iného zdroja, aplikácia sa v budúcnosti môže aktualizovať z ľubovoľného zdroja v telefóne. Funkcie aplikácie sa môžu zmeniť."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Chcete aktualizovať túto aplikáciu zo zdroja <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nTáto aplikácia obvykle dostáva aktualizácie zo zdroja <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ak aktualizujete z iného zdroja, môžete v budúcnosti dostávať aktualizácie z ľubovoľného zdroja v tablete. Funkcie aplikácie sa môžu zmeniť."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Chcete aktualizovať túto aplikáciu zo zdroja <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nTáto aplikácia obvykle dostáva aktualizácie zo zdroja <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ak aktualizujete z iného zdroja, môžete v budúcnosti dostávať aktualizácie z ľubovoľného zdroja v televízore. Funkcie aplikácie sa môžu zmeniť."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Chcete aktualizovať túto aplikáciu zo zdroja <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nTúto aplikáciu obvykle aktualizuje <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ak vykonáte aktualizáciu z iného zdroja, aplikácia sa v budúcnosti môže aktualizovať z ľubovoľného zdroja v telefóne. Funkcie aplikácie sa môžu zmeniť."</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikácia nebola nainštalovaná."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Inštalácia balíka bola zablokovaná."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikácia sa nenainštalovala, pretože balík je v konflikte s existujúcim balíkom."</string>
diff --git a/packages/PackageInstaller/res/values-sl/strings.xml b/packages/PackageInstaller/res/values-sl/strings.xml
index 00c3d15..0c67373 100644
--- a/packages/PackageInstaller/res/values-sl/strings.xml
+++ b/packages/PackageInstaller/res/values-sl/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikacija je nameščena."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Ali želite namestiti to aplikacijo?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Ali želite posodobiti to aplikacijo?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Želite to aplikacijo posodobiti iz vira <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nTa aplikacija običajno prejema posodobitve iz vira <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Če jo posodobite iz drugega vira, boste prihodnje posodobitve morda prejemali iz katerega koli vira v telefonu. Funkcija aplikacije se lahko spremeni."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Želite to aplikacijo posodobiti iz vira <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nTa aplikacija običajno prejema posodobitve iz vira <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Če jo posodobite iz drugega vira, boste prihodnje posodobitve morda prejemali iz katerega koli vira v tabličnem računalniku. Funkcija aplikacije se lahko spremeni."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Želite to aplikacijo posodobiti iz vira <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nTa aplikacija običajno prejema posodobitve iz vira <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Če jo posodobite iz drugega vira, boste prihodnje posodobitve morda prejemali iz katerega koli vira v televizorju. Funkcija aplikacije se lahko spremeni."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Želite to aplikacijo posodobiti iz vira <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nTa aplikacija običajno prejema posodobitve iz vira <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Če jo posodobite iz drugega vira, boste prihodnje posodobitve morda prejemali iz katerega koli vira v telefonu. Funkcija aplikacije se lahko spremeni."</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikacija ni nameščena."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Namestitev paketa je bila blokirana."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikacija ni bila nameščena, ker je paket v navzkrižju z obstoječim paketom."</string>
diff --git a/packages/PackageInstaller/res/values-sq/strings.xml b/packages/PackageInstaller/res/values-sq/strings.xml
index 9904bc0..f2cbf31 100644
--- a/packages/PackageInstaller/res/values-sq/strings.xml
+++ b/packages/PackageInstaller/res/values-sq/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Aplikacioni u instalua."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Dëshiron ta instalosh këtë aplikacion?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Dëshiron ta përditësosh këtë aplikacion?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Të përditësohet ky aplikacion nga <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nKy aplikacion zakonisht merr përditësime nga <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Duke përditësuar nga një burim tjetër, mund të marrësh përditësime të ardhshme nga çdo burim në telefonin tënd. Funksionaliteti i aplikacionit mund të ndryshojë."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Të përditësohet ky aplikacion nga <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nKy aplikacion zakonisht merr përditësime nga <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Duke përditësuar nga një burim tjetër, mund të marrësh përditësime të ardhshme nga çdo burim në tabletin tënd. Funksionaliteti i aplikacionit mund të ndryshojë."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Të përditësohet ky aplikacion nga <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nKy aplikacion zakonisht merr përditësime nga <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Duke përditësuar nga një burim tjetër, mund të marrësh përditësime të ardhshme nga çdo burim në televizorin tënd. Funksionaliteti i aplikacionit mund të ndryshojë."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Të përditësohet ky aplikacion nga <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nKy aplikacion zakonisht merr përditësime nga <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Duke përditësuar nga një burim tjetër, mund të marrësh përditësime të ardhshme nga çdo burim në telefonin tënd. Funksionaliteti i aplikacionit mund të ndryshojë."</string>
     <string name="install_failed" msgid="5777824004474125469">"Aplikacioni nuk u instalua."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Instalimi paketës u bllokua."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Aplikacioni nuk u instalua pasi paketa është në konflikt me një paketë ekzistuese."</string>
diff --git a/packages/PackageInstaller/res/values-sr/strings.xml b/packages/PackageInstaller/res/values-sr/strings.xml
index 5a0f52d..867bc84 100644
--- a/packages/PackageInstaller/res/values-sr/strings.xml
+++ b/packages/PackageInstaller/res/values-sr/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Апликација је инсталирана."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Желите да инсталирате ову апликацију?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Желите да ажурирате ову апликацију?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Желите да ажурирате ову апликацију из извора <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nОва апликација се обично ажурира из извора <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ако ажурирате из другог извора, можете да примате будућа ажурирања из било ког извора на телефону. Функције апликације могу да се промене."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Желите да ажурирате ову апликацију из извора <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nОва апликација се обично ажурира из извора <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ако ажурирате из другог извора, можете да примате будућа ажурирања из било ког извора на таблету. Функције апликације могу да се промене."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Желите да ажурирате ову апликацију из извора <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nОва апликација се обично ажурира из извора <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ако ажурирате из другог извора, можете да примате будућа ажурирања из било ког извора на ТВ-у. Функције апликације могу да се промене."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Желите да ажурирате ову апликацију из извора <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nОва апликација се обично ажурира из извора <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ако ажурирате из другог извора, можете да примате будућа ажурирања из било ког извора на телефону. Функције апликације могу да се промене."</string>
     <string name="install_failed" msgid="5777824004474125469">"Апликација није инсталирана."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Инсталирање пакета је блокирано."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Апликација није инсталирана јер је пакет неусаглашен са постојећим пакетом."</string>
diff --git a/packages/PackageInstaller/res/values-sv/strings.xml b/packages/PackageInstaller/res/values-sv/strings.xml
index ec6af2e..8c2b1dd 100644
--- a/packages/PackageInstaller/res/values-sv/strings.xml
+++ b/packages/PackageInstaller/res/values-sv/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Appen har installerats."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Vill du installera den här appen?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Vill du uppdatera den här appen?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Vill du uppdatera den här appen från <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAppen tar vanligtvis emot uppdateringar från <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Genom att uppdatera från en annan källa kan du komma att ta emot framtida uppdateringar från olika källor på telefonen. Appfunktioner kan förändras."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Vill du uppdatera den här appen från <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAppen tar vanligtvis emot uppdateringar från <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Om du uppdaterar från en annan källa kanske du får framtida uppdateringar från olika källor på surfplattan. Appfunktioner kan förändras."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Vill du uppdatera den här appen från <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAppen tar vanligtvis emot uppdateringar från <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Om du uppdaterar från en annan källa kanske du får framtida uppdateringar från olika källor på tv:n. Appfunktioner kan förändras."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Vill du uppdatera den här appen från <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nAppen tar vanligtvis emot uppdateringar från <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Genom att uppdatera från en annan källa kan du komma att ta emot framtida uppdateringar från olika källor på telefonen. Appfunktioner kan förändras."</string>
     <string name="install_failed" msgid="5777824004474125469">"Appen har inte installerats."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paketet har blockerats för installation."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Appen har inte installerats på grund av en konflikt mellan detta paket och ett befintligt paket."</string>
diff --git a/packages/PackageInstaller/res/values-sw/strings.xml b/packages/PackageInstaller/res/values-sw/strings.xml
index 0eb224a..d8cb12a 100644
--- a/packages/PackageInstaller/res/values-sw/strings.xml
+++ b/packages/PackageInstaller/res/values-sw/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Imesakinisha programu."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Ungependa kusakinisha programu hii?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Ungependa kusasisha programu hii?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Ungependa kusasisha programu hii kutoka kwenye <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nProgramu hii kwa kawaida hupokea masasisho kutoka <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Kwa kusasisha kutoka chanzo tofauti, huenda ukapokea masasisho ya siku zijazo kutoka chanzo chochote kwenye simu yako. Utendaji wa programu unaweza kubadilika."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Ungependa kusasisha programu hii kutoka kwa <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nProgramu hii kwa kawaida hupokea masasisho kutoka kwa <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Kwa kusasisha kutoka kwenye chanzo tofauti, unaweza kupokea masasisho ya baadaye kutoka kwenye chanzo chochote katika kishikwambi chako. Utendaji wa programu unaweza kubadilika."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Ungependa kusasisha programu hii kutoka kwa <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nProgramu hii kwa kawaida hupokea masasisho kutoka kwa <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Kwa kusasisha kutoka kwenye chanzo tofauti, unaweza kupokea masasisho ya baadaye kutoka kwenye chanzo chochote katika TV yako. Utendaji wa programu unaweza kubadilika."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Ungependa kusasisha programu hii kutoka kwenye <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nProgramu hii kwa kawaida hupokea masasisho kutoka <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Kwa kusasisha kutoka chanzo tofauti, huenda ukapokea masasisho ya siku zijazo kutoka chanzo chochote kwenye simu yako. Utendaji wa programu unaweza kubadilika."</string>
     <string name="install_failed" msgid="5777824004474125469">"Imeshindwa kusakinisha programu."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Kifurushi kimezuiwa kisisakinishwe."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Programu haikusakinishwa kwa sababu kifurushi kinakinzana na kifurushi kingine kilichopo."</string>
diff --git a/packages/PackageInstaller/res/values-ta/strings.xml b/packages/PackageInstaller/res/values-ta/strings.xml
index c60910c..06870aa 100644
--- a/packages/PackageInstaller/res/values-ta/strings.xml
+++ b/packages/PackageInstaller/res/values-ta/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ஆப்ஸ் நிறுவப்பட்டது."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"இந்த ஆப்ஸை நிறுவ வேண்டுமா?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"இந்த ஆப்ஸைப் புதுப்பிக்க வேண்டுமா?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> இலிருந்து இந்த ஆப்ஸைப் புதுப்பிக்க வேண்டுமா?\n\nபொதுவாக இந்த ஆப்ஸ்<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> இலிருந்து புதுப்பிப்புகளைப் பெறும். வேறொன்றின் மூலம் புதுப்பித்தால் எதிர்காலத்தில் மொபைலில் வேறு இடத்திலிருந்து புதுப்பிப்புகளை நீங்கள் பெறக்கூடும். ஆப்ஸ் செயல்பாடுகள் மாறுபடக்கூடும்."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> இல் இருந்து இந்த ஆப்ஸைப் புதுப்பிக்க வேண்டுமா?\n\nபொதுவாக இந்த ஆப்ஸ் <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> இல் இருந்து புதுப்பிப்புகளைப் பெறும். வேறு உரிமையாளர் மூலம் புதுப்பித்தால் எதிர்காலத்தில் டேப்லெட்டில் இடம்பெற்றுள்ள எந்த உரிமையாளரிடம் இருந்தும் புதுப்பிப்புகளைப் பெறக்கூடும். ஆப்ஸ் செயல்பாடுகள் மாறுபடக்கூடும்."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> இல் இருந்து இந்த ஆப்ஸைப் புதுப்பிக்க வேண்டுமா?\n\nபொதுவாக இந்த ஆப்ஸ் <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> இல் இருந்து புதுப்பிப்புகளைப் பெறும். வேறு உரிமையாளர் மூலம் புதுப்பித்தால் எதிர்காலத்தில் டிவியில் இடம்பெற்றுள்ள எந்த உரிமையாளரிடம் இருந்தும் புதுப்பிப்புகளைப் பெறக்கூடும். ஆப்ஸ் செயல்பாடுகள் மாறுபடக்கூடும்."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> இலிருந்து இந்த ஆப்ஸைப் புதுப்பிக்க வேண்டுமா?\n\nபொதுவாக இந்த ஆப்ஸ்<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> இலிருந்து புதுப்பிப்புகளைப் பெறும். வேறொன்றின் மூலம் புதுப்பித்தால் எதிர்காலத்தில் மொபைலில் வேறு இடத்திலிருந்து புதுப்பிப்புகளை நீங்கள் பெறக்கூடும். ஆப்ஸ் செயல்பாடுகள் மாறுபடக்கூடும்."</string>
     <string name="install_failed" msgid="5777824004474125469">"ஆப்ஸ் நிறுவப்படவில்லை."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"இந்தத் தொகுப்பு நிறுவப்படுவதிலிருந்து தடுக்கப்பட்டது."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"இந்தத் தொகுப்பு ஏற்கனவே உள்ள தொகுப்புடன் முரண்படுவதால் ஆப்ஸ் நிறுவப்படவில்லை."</string>
diff --git a/packages/PackageInstaller/res/values-te/strings.xml b/packages/PackageInstaller/res/values-te/strings.xml
index 25673f3..df871a6 100644
--- a/packages/PackageInstaller/res/values-te/strings.xml
+++ b/packages/PackageInstaller/res/values-te/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"యాప్ ఇన్‌స్టాల్ చేయబడింది."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"మీరు ఈ యాప్‌ను ఇన్‌స్టాల్ చేయాలనుకుంటున్నారా?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"మీరు ఈ యాప్‌ను అప్‌డేట్ చేయాలనుకుంటున్నారా?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ద్వారా ఈ యాప్‌ను అప్‌డేట్ చేయాలా?\n\nఈ యాప్ సాధారణంగా <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> నుండి అప్‌డేట్‌లను అందుకుంటుంది. వేరే సోర్స్ ద్వారా అప్‌డేట్ చేయడం వల్ల, భవిష్యత్తులో మీ ఫోన్‌లోని ఏ సోర్స్ ద్వారా అయినా అప్‌డేట్‌లను పొందవచ్చు. యాప్ ఫంక్షనాలిటీ మారవచ్చు."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> నుండి ఈ యాప్‌ను అప్‌డేట్ చేయాలా?\n\nఈ యాప్ సాధారణంగా <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> నుండి అప్‌డేట్‌లను అందుకుంటుంది. విభిన్నమైన సోర్స్ నుండి అప్‌డేట్ చేయడం ద్వారా, మీరు మీ టాబ్లెట్‌లోని ఏదైనా సోర్స్ నుండి భవిష్యత్తు అప్‌డేట్‌లను పొందవచ్చు. యాప్ ఫంక్షనాలిటీ మారవచ్చు."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> నుండి ఈ యాప్‌ను అప్‌డేట్ చేయాలా?\n\nఈ యాప్ సాధారణంగా <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> నుండి అప్‌డేట్‌లను అందుకుంటుంది. విభిన్నమైన సోర్స్ నుండి అప్‌డేట్ చేయడం ద్వారా, మీరు మీ TVలోని ఏదైనా సోర్స్ నుండి భవిష్యత్తు అప్‌డేట్‌లను పొందవచ్చు. యాప్ ఫంక్షనాలిటీ మారవచ్చు."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ద్వారా ఈ యాప్‌ను అప్‌డేట్ చేయాలా?\n\nఈ యాప్ సాధారణంగా <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> నుండి అప్‌డేట్‌లను అందుకుంటుంది. వేరే సోర్స్ ద్వారా అప్‌డేట్ చేయడం వల్ల, భవిష్యత్తులో మీ ఫోన్‌లోని ఏ సోర్స్ ద్వారా అయినా అప్‌డేట్‌లను పొందవచ్చు. యాప్ ఫంక్షనాలిటీ మారవచ్చు."</string>
     <string name="install_failed" msgid="5777824004474125469">"యాప్ ఇన్‌స్టాల్ చేయబడలేదు."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"ప్యాకేజీ ఇన్‌స్టాల్ కాకుండా బ్లాక్ చేయబడింది."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ప్యాకేజీ, అలాగే ఇప్పటికే ఉన్న ప్యాకేజీ మధ్య వైరుధ్యం ఉన్నందున యాప్ ఇన్‌స్టాల్ చేయబడలేదు."</string>
diff --git a/packages/PackageInstaller/res/values-th/strings.xml b/packages/PackageInstaller/res/values-th/strings.xml
index c8f3275..5a837fc 100644
--- a/packages/PackageInstaller/res/values-th/strings.xml
+++ b/packages/PackageInstaller/res/values-th/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ติดตั้งแอปแล้ว"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"คุณต้องการติดตั้งแอปนี้ไหม"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"คุณต้องการอัปเดตแอปนี้ไหม"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"อัปเดตแอปนี้จาก <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ไหม\n\nโดยปกติแล้ว แอปนี้จะได้รับการอัปเดตจาก <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> การอัปเดตจากแหล่งที่มาอื่นอาจทำให้โทรศัพท์ของคุณได้รับการอัปเดตจากแหล่งที่มาใดก็ได้ในอนาคต ฟังก์ชันการทำงานของแอปอาจมีการเปลี่ยนแปลง"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"อัปเดตแอปนี้จาก <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ไหม\n\nโดยปกติแล้ว แอปนี้จะได้รับการอัปเดตจาก <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> การอัปเดตจากแหล่งที่มาอื่นอาจทำให้แท็บเล็ตของคุณได้รับการอัปเดตจากแหล่งที่มานั้นในอนาคต ฟังก์ชันการทำงานของแอปอาจมีการเปลี่ยนแปลง"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"อัปเดตแอปนี้จาก <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ไหม\n\nโดยปกติแล้ว แอปนี้จะได้รับการอัปเดตจาก <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> การอัปเดตจากแหล่งที่มาอื่นอาจทำให้ทีวีของคุณได้รับการอัปเดตจากแหล่งที่มานั้นในอนาคต ฟังก์ชันการทำงานของแอปอาจมีการเปลี่ยนแปลง"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"อัปเดตแอปนี้จาก <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> ไหม\n\nโดยปกติแล้ว แอปนี้จะได้รับการอัปเดตจาก <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> การอัปเดตจากแหล่งที่มาอื่นอาจทำให้โทรศัพท์ของคุณได้รับการอัปเดตจากแหล่งที่มาใดก็ได้ในอนาคต ฟังก์ชันการทำงานของแอปอาจมีการเปลี่ยนแปลง"</string>
     <string name="install_failed" msgid="5777824004474125469">"ไม่ได้ติดตั้งแอป"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"มีการบล็อกแพ็กเกจไม่ให้ติดตั้ง"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ไม่ได้ติดตั้งแอปเพราะแพ็กเกจขัดแย้งกับแพ็กเกจที่มีอยู่"</string>
diff --git a/packages/PackageInstaller/res/values-tl/strings.xml b/packages/PackageInstaller/res/values-tl/strings.xml
index 4d516b5..af79ffe 100644
--- a/packages/PackageInstaller/res/values-tl/strings.xml
+++ b/packages/PackageInstaller/res/values-tl/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Na-install na ang app."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Gusto mo bang i-install ang app na ito?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Gusto mo bang i-update ang app na ito?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"I-update itong app na mula sa <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nKaraniwang nakakatanggap ang app na ito ng mga update mula sa <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Sa pag-update mula sa ibang pinagmulan, puwede kang makatanggap ng mga update mula sa anumang pinagmulan sa iyong telepono sa hinaharap. Posibleng magbago ang functionality ng app."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"I-update itong app na mula sa <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nKaraniwang nakakatanggap ang app na ito ng mga update mula sa <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Sa pag-update mula sa ibang pinagmulan, puwede kang makatanggap ng mga update mula sa anumang pinagmulan sa iyong tablet sa hinaharap. Posibleng magbago ang functionality ng app."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"I-update itong app na mula sa <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nKaraniwang nakakatanggap ang app na ito ng mga update mula sa <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Sa pag-update mula sa ibang pinagmulan, puwede kang makatanggap ng mga update mula sa anumang pinagmulan sa iyong TV sa hinaharap. Posibleng magbago ang functionality ng app."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"I-update itong app na mula sa <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nKaraniwang nakakatanggap ang app na ito ng mga update mula sa <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Sa pag-update mula sa ibang pinagmulan, puwede kang makatanggap ng mga update mula sa anumang pinagmulan sa iyong telepono sa hinaharap. Posibleng magbago ang functionality ng app."</string>
     <string name="install_failed" msgid="5777824004474125469">"Hindi na-install ang app."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Na-block ang pag-install sa package."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Hindi na-install ang app dahil nagkakaproblema ang package sa isang dati nang package."</string>
diff --git a/packages/PackageInstaller/res/values-tr/strings.xml b/packages/PackageInstaller/res/values-tr/strings.xml
index 050d398..4a0d869 100644
--- a/packages/PackageInstaller/res/values-tr/strings.xml
+++ b/packages/PackageInstaller/res/values-tr/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Uygulama yüklendi."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Bu uygulamayı yüklemek istiyor musunuz?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Bu uygulamayı güncellemek istiyor musunuz?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Bu uygulama <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> kaynağından güncellensin mi?\n\nBu uygulama genellikle <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> kaynağından güncelleme alır. Farklı bir kaynaktan güncellerseniz ileride telefonunuzda herhangi bir kaynaktan güncelleme alabilirsiniz. Uygulama işlevselliği değişebilir."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Bu uygulama <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> kaynağından güncellensin mi?\n\nBu uygulama genellikle <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> kaynağından güncelleme alır. Farklı bir kaynaktan güncellerseniz ileride tabletinizde herhangi bir kaynaktan güncelleme alabilirsiniz. Uygulama işlevselliği değişebilir."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Bu uygulama <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> kaynağından güncellensin mi?\n\nBu uygulama genellikle <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> kaynağından güncelleme alır. Farklı bir kaynaktan güncellerseniz ileride televizyonunuzda herhangi bir kaynaktan güncelleme alabilirsiniz. Uygulama işlevselliği değişebilir."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Bu uygulama <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> kaynağından güncellensin mi?\n\nBu uygulama genellikle <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> kaynağından güncelleme alır. Farklı bir kaynaktan güncellerseniz ileride telefonunuzda herhangi bir kaynaktan güncelleme alabilirsiniz. Uygulama işlevselliği değişebilir."</string>
     <string name="install_failed" msgid="5777824004474125469">"Uygulama yüklenmedi."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paketin yüklemesi engellendi."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Paket, mevcut bir paketle çakıştığından uygulama yüklenemedi."</string>
diff --git a/packages/PackageInstaller/res/values-uk/strings.xml b/packages/PackageInstaller/res/values-uk/strings.xml
index ec10962..a3b8e81 100644
--- a/packages/PackageInstaller/res/values-uk/strings.xml
+++ b/packages/PackageInstaller/res/values-uk/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Програму встановлено."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Установити цей додаток?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Оновити цей додаток?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Оновити цей додаток через <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nЗазвичай цей додаток отримує оновлення в інший спосіб (<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>). Якщо встановити оновлення з іншого джерела, надалі на ваш телефон зможуть надходити оновлення з будь-яких джерел. Це може змінити функції додатка."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Оновити цей додаток від <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nЗазвичай цей додаток отримує оновлення від <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Якщо встановити оновлення з іншого джерела, надалі на ваш планшет зможуть надходити оновлення з будь-яких джерел. Це може змінити функції додатка."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Оновити цей додаток від <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nЗазвичай цей додаток отримує оновлення від <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Якщо встановити оновлення з іншого джерела, надалі на ваш телевізор зможуть надходити оновлення з будь-яких джерел. Це може змінити функції додатка."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Оновити цей додаток через <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nЗазвичай цей додаток отримує оновлення в інший спосіб (<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>). Якщо встановити оновлення з іншого джерела, надалі на ваш телефон зможуть надходити оновлення з будь-яких джерел. Це може змінити функції додатка."</string>
     <string name="install_failed" msgid="5777824004474125469">"Програму не встановлено."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Встановлення пакета заблоковано."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Додаток не встановлено, оскільки пакет конфліктує з наявним пакетом."</string>
diff --git a/packages/PackageInstaller/res/values-ur/strings.xml b/packages/PackageInstaller/res/values-ur/strings.xml
index b3b4c0d..c2a318b 100644
--- a/packages/PackageInstaller/res/values-ur/strings.xml
+++ b/packages/PackageInstaller/res/values-ur/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"ایپ انسٹال ہو گئی۔"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"کیا آپ یہ ایپ انسٹال کرنا چاہتے ہیں؟"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"کیا آپ یہ ایپ اپ ڈیٹ کرنا چاہتے ہیں؟"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"اس ایپ کو <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> سے اپ ڈیٹ کریں؟\n\n اس ایپ کو عام طور پر <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> سے اپ ڈیٹس موصول ہوتی ہیں۔ کسی مختلف ذریعے سے اپ ڈیٹ کر کے، آپ اپنے فون پر کسی بھی ذریعے سے مستقبل کی اپ ڈیٹس حاصل کر سکتے ہیں۔ ایپ کی فعالیت تبدیل ہو سکتی ہے۔"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"اس ایپ کو <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> سے اپ ڈیٹ کریں؟\n\nاس ایپ کو عام طور پر <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> سے اپ ڈیٹس موصول ہوتی ہیں۔ کسی مختلف وسیلے سے اپ ڈیٹ کر کے، آپ اپنے ٹیبلیٹ پر کسی بھی وسیلے سے مستقبل کی اپ ڈیٹس حاصل کر سکتے ہیں۔ ایپ کی فعالیت تبدیل ہو سکتی ہے۔"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"‏اس ایپ کو <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> سے اپ ڈیٹ کریں؟\n\nاس ایپ کو عام طور پر <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> سے اپ ڈیٹس موصول ہوتی ہیں۔ کسی مختلف وسیلے سے اپ ڈیٹ کر کے، آپ اپنے TV پر کسی بھی وسیلے سے مستقبل کی اپ ڈیٹس حاصل کر سکتے ہیں۔ ایپ کی فعالیت تبدیل ہو سکتی ہے۔"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"اس ایپ کو <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> سے اپ ڈیٹ کریں؟\n\n اس ایپ کو عام طور پر <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> سے اپ ڈیٹس موصول ہوتی ہیں۔ کسی مختلف ذریعے سے اپ ڈیٹ کر کے، آپ اپنے فون پر کسی بھی ذریعے سے مستقبل کی اپ ڈیٹس حاصل کر سکتے ہیں۔ ایپ کی فعالیت تبدیل ہو سکتی ہے۔"</string>
     <string name="install_failed" msgid="5777824004474125469">"ایپ انسٹال نہیں ہوئی۔"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"پیکج کو انسٹال ہونے سے مسدود کر دیا گیا تھا۔"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"ایپ انسٹال نہیں ہوئی کیونکہ پیکج ایک موجودہ پیکیج سے متصادم ہے۔"</string>
diff --git a/packages/PackageInstaller/res/values-uz/strings.xml b/packages/PackageInstaller/res/values-uz/strings.xml
index 2993663c..691633a 100644
--- a/packages/PackageInstaller/res/values-uz/strings.xml
+++ b/packages/PackageInstaller/res/values-uz/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Ilova o‘rnatildi."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Bu ilovani oʻrnatmoqchimisiz?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Bu ilova yangilansinmi?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Bu ilova <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> orqali yangilansinmi?\n\nBu ilova odatda <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> orqali yangilanishlar oladi. Boshqa manbadan yangilash orqali siz kelajakdagi yangilanishlarni telefoningizda istalgan manbadan olishingiz mumkin. Ilova funksiyalari oʻzgarishi mumkin."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Bu ilova <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> orqali yangilansinmi?\n\nBu ilova odatda <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> orqali yangilanishlar oladi. Boshqa manbadan yangilash orqali siz kelajakdagi yangilanishlarni planshetingizda istalgan manbadan olishingiz mumkin. Ilova funksiyalari oʻzgarishi mumkin."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Bu ilova <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> orqali yangilansinmi?\n\nBu ilova odatda <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> orqali yangilanishlar oladi. Boshqa manbadan yangilash orqali siz kelajakdagi yangilanishlarni televizoringizda istalgan manbadan olishingiz mumkin. Ilova funksiyalari oʻzgarishi mumkin."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Bu ilova <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g> orqali yangilansinmi?\n\nBu ilova odatda <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g> orqali yangilanishlar oladi. Boshqa manbadan yangilash orqali siz kelajakdagi yangilanishlarni telefoningizda istalgan manbadan olishingiz mumkin. Ilova funksiyalari oʻzgarishi mumkin."</string>
     <string name="install_failed" msgid="5777824004474125469">"Ilova o‘rnatilmadi."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Paket o‘rnatilishga qarshi bloklangan."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Paket mavjud paket bilan zid kelganligi uchun ilovani o‘rnatib bo‘lmadi."</string>
diff --git a/packages/PackageInstaller/res/values-vi/strings.xml b/packages/PackageInstaller/res/values-vi/strings.xml
index f6ffa3a..4976d66 100644
--- a/packages/PackageInstaller/res/values-vi/strings.xml
+++ b/packages/PackageInstaller/res/values-vi/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Ứng dụng đã được cài đặt."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Bạn có muốn cài đặt ứng dụng này không?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Bạn có muốn cập nhật ứng dụng này không?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Cập nhật ứng dụng này của <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nỨng dụng này thường nhận thông tin cập nhật từ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Khi cập nhật từ một nguồn khác, trong tương lai, bạn có thể nhận thông tin cập nhật từ nguồn bất kỳ trên điện thoại của bạn. Chức năng ứng dụng có thể thay đổi."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Cập nhật ứng dụng này từ <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nỨng dụng này thường nhận các bản cập nhật từ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Khi cập nhật từ một nguồn khác, bạn có thể nhận các bản cập nhật trong tương lai từ nguồn bất kỳ trên máy tính bảng của mình. Chức năng ứng dụng có thể thay đổi."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Cập nhật ứng dụng này từ <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nỨng dụng này thường nhận các bản cập nhật từ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Khi cập nhật từ một nguồn khác, bạn có thể nhận các bản cập nhật trong tương lai từ nguồn bất kỳ trên TV của mình. Chức năng ứng dụng có thể thay đổi."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Cập nhật ứng dụng này của <xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nỨng dụng này thường nhận thông tin cập nhật từ <xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Khi cập nhật từ một nguồn khác, trong tương lai, bạn có thể nhận thông tin cập nhật từ nguồn bất kỳ trên điện thoại của bạn. Chức năng ứng dụng có thể thay đổi."</string>
     <string name="install_failed" msgid="5777824004474125469">"Ứng dụng chưa được cài đặt."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Đã chặn cài đặt gói."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Chưa cài đặt được ứng dụng do gói xung đột với một gói hiện có."</string>
diff --git a/packages/PackageInstaller/res/values-zh-rCN/strings.xml b/packages/PackageInstaller/res/values-zh-rCN/strings.xml
index 5c2d11f..075467d 100644
--- a/packages/PackageInstaller/res/values-zh-rCN/strings.xml
+++ b/packages/PackageInstaller/res/values-zh-rCN/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"已安装应用。"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"要安装此应用吗?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"要更新此应用吗?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"要通过<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>更新此应用吗?\n\n此应用通常通过<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>接收更新。如果通过其他来源更新,手机未来可能会收到任何来源的更新。应用功能可能会变化。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"要通过<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>更新此应用?\n\n此应用通常通过<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>接收更新。如果通过其他来源更新,平板电脑未来可能会收到任何来源的更新。应用功能可能会变化。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"要通过<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>更新此应用?\n\n此应用通常通过<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>接收更新。如果通过其他来源更新,电视未来可能会收到任何来源的更新。应用功能可能会变化。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"要通过<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>更新此应用吗?\n\n此应用通常通过<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>接收更新。如果通过其他来源更新,手机未来可能会收到任何来源的更新。应用功能可能会变化。"</string>
     <string name="install_failed" msgid="5777824004474125469">"未安装应用。"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"系统已禁止安装该软件包。"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"应用未安装:软件包与现有软件包存在冲突。"</string>
diff --git a/packages/PackageInstaller/res/values-zh-rHK/strings.xml b/packages/PackageInstaller/res/values-zh-rHK/strings.xml
index b36770d..c971682 100644
--- a/packages/PackageInstaller/res/values-zh-rHK/strings.xml
+++ b/packages/PackageInstaller/res/values-zh-rHK/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"已安裝應用程式。"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"要安裝此應用程式嗎?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"要更新此應用程式嗎?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"要從「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」更新此應用程式嗎?\n\n在正常情況下,系統會透過「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」更新此應用程式。如果透過其他來源更新,手機未來可能會收到任何來源的更新。應用程式功能可能會有變動。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"要從「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」更新此應用程式嗎?\n\n在正常情況下,系統會透過「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」更新此應用程式。如果透過其他來源更新,平板電腦未來可能會收到任何來源的更新。應用程式功能可能會有變動。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"要從「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」更新此應用程式嗎?\n\n在正常情況下,系統會透過「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」更新此應用程式。如果透過其他來源更新,電視未來可能會收到任何來源的更新。應用程式功能可能會有變動。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"要從「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」更新此應用程式嗎?\n\n在正常情況下,系統會透過「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」更新此應用程式。如果透過其他來源更新,手機未來可能會收到任何來源的更新。應用程式功能可能會有變動。"</string>
     <string name="install_failed" msgid="5777824004474125469">"未安裝應用程式。"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"套件已遭封鎖,無法安裝。"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"套件與現有的套件發生衝突,無法安裝應用程式。"</string>
diff --git a/packages/PackageInstaller/res/values-zh-rTW/strings.xml b/packages/PackageInstaller/res/values-zh-rTW/strings.xml
index 2a87eb8..d9027cc 100644
--- a/packages/PackageInstaller/res/values-zh-rTW/strings.xml
+++ b/packages/PackageInstaller/res/values-zh-rTW/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"已安裝應用程式。"</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"要安裝這個應用程式嗎?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"要更新這個應用程式嗎?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"要透過「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」更新這個應用程式嗎?\n\n在正常情況下,系統會透過「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」更新這個應用程式。如果透過其他來源更新,手機未來可能會收到任何來源的更新。應用程式功能可能會有變動。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"要透過「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」更新這個應用程式嗎?\n\n在正常情況下,系統會透過「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」更新這個應用程式。如果透過其他來源更新,平板電腦未來可能會收到任何來源的更新。應用程式功能可能會有變動。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"要透過「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」更新這個應用程式嗎?\n\n在正常情況下,系統會透過「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」更新這個應用程式。如果透過其他來源更新,TV 裝置未來可能會收到任何來源的更新。應用程式功能可能會有變動。"</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"要透過「<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>」更新這個應用程式嗎?\n\n在正常情況下,系統會透過「<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>」更新這個應用程式。如果透過其他來源更新,手機未來可能會收到任何來源的更新。應用程式功能可能會有變動。"</string>
     <string name="install_failed" msgid="5777824004474125469">"未安裝應用程式。"</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"系統已封鎖這個套件,因此無法安裝。"</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"應用程式套件與現有套件衝突,因此未能完成安裝。"</string>
diff --git a/packages/PackageInstaller/res/values-zu/strings.xml b/packages/PackageInstaller/res/values-zu/strings.xml
index ca9c63b..13419d9 100644
--- a/packages/PackageInstaller/res/values-zu/strings.xml
+++ b/packages/PackageInstaller/res/values-zu/strings.xml
@@ -26,7 +26,9 @@
     <string name="install_done" msgid="5987363587661783896">"Uhlelo lokusebenza olufakiwe."</string>
     <string name="install_confirm_question" msgid="7663733664476363311">"Ingabe ufuna ukufaka le app?"</string>
     <string name="install_confirm_question_update" msgid="3348888852318388584">"Ingabe ufuna ukubuyekeza le app?"</string>
-    <string name="install_confirm_question_update_owner_reminder" msgid="3750986542284587290">"Buyekeza le app kusuka ku-<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nNgokuvamile le app ithola izibuyekezo kusuka ku-<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ngokubuyekeza kusuka kumthombo ohlukile, ungase uthole izibuyekezo zesikhathi esizayo kusuka kunoma yimuphi umthombo efonini yakho. Okwenziwa yi-app kungase kushintshe."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet" msgid="738046584021528374">"Buyekeza le app kusuka ku-<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nNgokuvamile le app ithola izibuyekezo kusuka ku-<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ngokubuyekeza kusuka kumthombo ohlukile, ungase uthole izibuyekezo zesikhathi esizayo kusuka kunoma yimuphi umthombo kuthebulethi yakho. Okwenziwa yi-app kungase kushintshe."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tv" msgid="3056133099508550163">"Buyekeza le app kusuka ku-<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nNgokuvamile le app ithola izibuyekezo kusuka ku-<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ngokubuyekeza kusuka kumthombo ohlukile, ungase uthole izibuyekezo zesikhathi esizayo kusuka kunoma yimuphi umthombo ku-TV yakho. Okwenziwa yi-app kungase kushintshe."</string>
+    <string name="install_confirm_question_update_owner_reminder" product="default" msgid="3750986542284587290">"Buyekeza le app kusuka ku-<xliff:g id="NEW_UPDATE_OWNER">%1$s</xliff:g>?\n\nNgokuvamile le app ithola izibuyekezo kusuka ku-<xliff:g id="EXISTING_UPDATE_OWNER">%2$s</xliff:g>. Ngokubuyekeza kusuka kumthombo ohlukile, ungase uthole izibuyekezo zesikhathi esizayo kusuka kunoma yimuphi umthombo efonini yakho. Okwenziwa yi-app kungase kushintshe."</string>
     <string name="install_failed" msgid="5777824004474125469">"Uhlelo lokusebenza alufakiwe."</string>
     <string name="install_failed_blocked" msgid="8512284352994752094">"Iphakheji livinjiwe kusukela ekufakweni."</string>
     <string name="install_failed_conflict" msgid="3493184212162521426">"Uhlelo lokusebenza alufakiwe njengoba ukuphakheja kushayisana nephakheji elikhona."</string>
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index a2118fa..c52fde6 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -38,7 +38,11 @@
     <!-- Message for updating an existing app [CHAR LIMIT=NONE] -->
     <string name="install_confirm_question_update">Do you want to update this app?</string>
     <!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
-    <string name="install_confirm_question_update_owner_reminder">Update this app from <xliff:g id="new_update_owner">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="existing_update_owner">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.</string>
+    <string name="install_confirm_question_update_owner_reminder" product="tablet">Update this app from <xliff:g id="new_update_owner">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="existing_update_owner">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your tablet. App functionality may change.</string>
+    <!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
+    <string name="install_confirm_question_update_owner_reminder" product="tv">Update this app from <xliff:g id="new_update_owner">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="existing_update_owner">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your TV. App functionality may change.</string>
+    <!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
+    <string name="install_confirm_question_update_owner_reminder" product="default">Update this app from <xliff:g id="new_update_owner">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="existing_update_owner">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.</string>
     <!-- [CHAR LIMIT=100] -->
     <string name="install_failed">App not installed.</string>
     <!-- Reason displayed when installation fails because the package was blocked
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
index 7b17cbd..19d74b3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
@@ -16,6 +16,8 @@
 
 package com.android.packageinstaller;
 
+import static android.content.Intent.CATEGORY_LAUNCHER;
+
 import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
 
 import android.app.Activity;
@@ -45,6 +47,9 @@
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         setResult(resultCode, data);
         finish();
+        if (data != null && data.hasCategory(CATEGORY_LAUNCHER)) {
+            startActivity(data);
+        }
     }
 
     @Override
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index 229b7a7..15446b6 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -279,9 +279,16 @@
     }
 
     private boolean isCallerSessionOwner(int originatingUid, int sessionId) {
+        if (originatingUid == Process.ROOT_UID) {
+            return true;
+        }
         PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
-        int installerUid = packageInstaller.getSessionInfo(sessionId).getInstallerUid();
-        return (originatingUid == Process.ROOT_UID) || (originatingUid == installerUid);
+        PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
+        if (sessionInfo == null) {
+            return false;
+        }
+        int installerUid = sessionInfo.getInstallerUid();
+        return originatingUid == installerUid;
     }
 
     private void checkDevicePolicyRestriction() {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
index 73c03a5..ff991d2 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
@@ -17,7 +17,6 @@
 package com.android.packageinstaller;
 
 import android.app.Activity;
-import android.content.ActivityNotFoundException;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -123,11 +122,7 @@
         Button launchButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
         if (enabled) {
             launchButton.setOnClickListener(view -> {
-                try {
-                    startActivity(mLaunchIntent);
-                } catch (ActivityNotFoundException | SecurityException e) {
-                    Log.e(LOG_TAG, "Could not start activity", e);
-                }
+                setResult(Activity.RESULT_OK, mLaunchIntent);
                 finish();
             });
         } else {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index d154156..e071c11 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -16,8 +16,9 @@
 */
 package com.android.packageinstaller;
 
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY;
-import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
+import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 import android.Manifest;
@@ -789,7 +790,8 @@
             }
             new Handler(Looper.getMainLooper()).postDelayed(() -> {
                 if (!isDestroyed()) {
-                    startActivity(getIntent().addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT));
+                    startActivity(getIntent().addFlags(
+                            FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP));
                 }
             }, 500);
 
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index 0c4cb8e..74acf67 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.app.LoaderManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -714,8 +715,13 @@
 
                     try {
                         mPrinterForInfoIntent = printer;
+                        Bundle options = ActivityOptions.makeBasic()
+                                .setPendingIntentBackgroundActivityStartMode(
+                                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+                                .toBundle();
                         startIntentSenderForResult(printer.getInfoIntent().getIntentSender(),
-                                INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0);
+                                INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0,
+                                options);
                     } catch (SendIntentException e) {
                         mPrinterForInfoIntent = null;
                         Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
deleted file mode 100644
index e68ef85..0000000
--- a/packages/SettingsLib/Spa/build.gradle
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 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.
- */
-
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-
-buildscript {
-    ext {
-        BUILD_TOOLS_VERSION = "30.0.3"
-        MIN_SDK = 21
-        TARGET_SDK = 33
-        jetpack_compose_version = '1.4.0-beta01'
-        jetpack_compose_compiler_version = '1.4.4'
-    }
-}
-plugins {
-    id 'com.android.application' version '8.0.0' apply false
-    id 'com.android.library' version '8.0.0' apply false
-    id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
-}
-subprojects {
-    tasks.withType(KotlinCompile).configureEach {
-        kotlinOptions {
-            jvmTarget = "17"
-            freeCompilerArgs = ["-Xjvm-default=all"]
-        }
-    }
-}
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
new file mode 100644
index 0000000..e76139f
--- /dev/null
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -0,0 +1,66 @@
+/*
+ * Copyright 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.
+ */
+
+import com.android.build.gradle.BaseExtension
+import com.android.build.gradle.api.AndroidBasePlugin
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+    alias(libs.plugins.android.application) apply false
+    alias(libs.plugins.android.library) apply false
+    alias(libs.plugins.kotlin.android) apply false
+}
+
+allprojects {
+    extra["jetpackComposeVersion"] = "1.6.0-alpha01"
+}
+
+subprojects {
+    plugins.withType<AndroidBasePlugin> {
+        configure<BaseExtension> {
+            compileSdkVersion(34)
+
+            defaultConfig {
+                minSdk = 21
+                targetSdk = 34
+            }
+
+            compileOptions {
+                sourceCompatibility = JavaVersion.VERSION_17
+                targetCompatibility = JavaVersion.VERSION_17
+            }
+        }
+    }
+
+    afterEvaluate {
+        plugins.withType<AndroidBasePlugin> {
+            configure<BaseExtension> {
+                if (buildFeatures.compose == true) {
+                    composeOptions {
+                        kotlinCompilerExtensionVersion = "1.4.4"
+                    }
+                }
+            }
+        }
+    }
+
+    tasks.withType<KotlinCompile> {
+        kotlinOptions {
+            jvmTarget = "17"
+            freeCompilerArgs = listOf("-Xjvm-default=all")
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/Android.bp b/packages/SettingsLib/Spa/gallery/Android.bp
index bc083c9..e59e982 100644
--- a/packages/SettingsLib/Spa/gallery/Android.bp
+++ b/packages/SettingsLib/Spa/gallery/Android.bp
@@ -28,6 +28,6 @@
         "androidx.compose.runtime_runtime",
     ],
     kotlincflags: ["-Xjvm-default=all"],
-    platform_apis: true,
+    sdk_version: "current",
     min_sdk_version: "31",
 }
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
deleted file mode 100644
index 212aa7b..0000000
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 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.
- */
-
-plugins {
-    id 'com.android.application'
-    id 'kotlin-android'
-}
-
-android {
-    namespace 'com.android.settingslib.spa.gallery'
-    compileSdk TARGET_SDK
-    buildToolsVersion = BUILD_TOOLS_VERSION
-
-    defaultConfig {
-        applicationId "com.android.settingslib.spa.gallery"
-        minSdk MIN_SDK
-        targetSdk TARGET_SDK
-        versionCode 1
-        versionName "1.0"
-    }
-
-    sourceSets {
-        main {
-            kotlin {
-                srcDir "src"
-            }
-            res.srcDirs = ["res"]
-            manifest.srcFile "AndroidManifest.xml"
-        }
-    }
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_17
-        targetCompatibility JavaVersion.VERSION_17
-    }
-    buildFeatures {
-        compose true
-    }
-    composeOptions {
-        kotlinCompilerExtensionVersion jetpack_compose_compiler_version
-    }
-}
-
-dependencies {
-    implementation project(":spa")
-}
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle.kts b/packages/SettingsLib/Spa/gallery/build.gradle.kts
new file mode 100644
index 0000000..a1151a5
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/build.gradle.kts
@@ -0,0 +1,45 @@
+/*
+ * Copyright 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.
+ */
+
+plugins {
+    alias(libs.plugins.android.application)
+    alias(libs.plugins.kotlin.android)
+}
+
+android {
+    namespace = "com.android.settingslib.spa.gallery"
+
+    defaultConfig {
+        applicationId = "com.android.settingslib.spa.gallery"
+        versionCode = 1
+        versionName = "1.0"
+    }
+
+    sourceSets {
+        sourceSets.getByName("main") {
+            kotlin.setSrcDirs(listOf("src"))
+            res.setSrcDirs(listOf("res"))
+            manifest.srcFile("AndroidManifest.xml")
+        }
+    }
+    buildFeatures {
+        compose = true
+    }
+}
+
+dependencies {
+    implementation(project(":spa"))
+}
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
new file mode 100644
index 0000000..9a16df8
--- /dev/null
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -0,0 +1,30 @@
+#
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+[versions]
+agp = "8.0.2"
+dexmaker-mockito = "2.28.3"
+kotlin = "1.8.10"
+truth = "1.1"
+
+[libraries]
+dexmaker-mockito = { module = "com.linkedin.dexmaker:dexmaker-mockito", version.ref = "dexmaker-mockito" }
+truth = { module = "com.google.truth:truth", version.ref = "truth" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+android-library = { id = "com.android.library", version.ref = "agp" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index ed85e33..33f49e3 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -14,9 +14,8 @@
 # limitations under the License.
 #
 
-#Thu Jul 14 10:36:06 CST 2022
 distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
 distributionPath=wrapper/dists
 zipStorePath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
diff --git a/packages/SettingsLib/Spa/settings.gradle b/packages/SettingsLib/Spa/settings.gradle
deleted file mode 100644
index 1c5a1ce..0000000
--- a/packages/SettingsLib/Spa/settings.gradle
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 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.
- */
-
-pluginManagement {
-    repositories {
-        gradlePluginPortal()
-        google()
-        mavenCentral()
-    }
-}
-dependencyResolutionManagement {
-    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
-    repositories {
-        google()
-        mavenCentral()
-        maven { url "https://jitpack.io"}
-    }
-}
-rootProject.name = "SpaLib"
-include ':spa'
-include ':gallery'
-include ':testutils'
diff --git a/packages/SettingsLib/Spa/settings.gradle.kts b/packages/SettingsLib/Spa/settings.gradle.kts
new file mode 100644
index 0000000..9909781
--- /dev/null
+++ b/packages/SettingsLib/Spa/settings.gradle.kts
@@ -0,0 +1,42 @@
+/*
+ * Copyright 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.
+ */
+
+pluginManagement {
+    repositories {
+        google()
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    rulesMode.set(RulesMode.FAIL_ON_PROJECT_RULES)
+
+    repositories {
+        google()
+        mavenCentral()
+        maven {
+            url = uri("https://jitpack.io")
+            content {
+                includeGroup("com.github.PhilJay")
+            }
+        }
+    }
+}
+rootProject.name = "SpaLib"
+include(":spa")
+include(":gallery")
+include(":testutils")
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 139f3e1..79f8c46 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -43,6 +43,7 @@
     kotlincflags: [
         "-Xjvm-default=all",
     ],
+    sdk_version: "current",
     min_sdk_version: "31",
 }
 
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
deleted file mode 100644
index a591366..0000000
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright 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.
- */
-
-plugins {
-    id 'com.android.library'
-    id 'kotlin-android'
-}
-
-android {
-    namespace 'com.android.settingslib.spa'
-    compileSdk TARGET_SDK
-    buildToolsVersion = BUILD_TOOLS_VERSION
-
-    defaultConfig {
-        minSdk MIN_SDK
-        targetSdk TARGET_SDK
-
-        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-    }
-
-    sourceSets {
-        main {
-            kotlin {
-                srcDir "src"
-            }
-            res.srcDirs = ["res"]
-            manifest.srcFile "AndroidManifest.xml"
-        }
-        androidTest {
-            kotlin {
-                srcDir "../tests/src"
-            }
-            res.srcDirs = ["../tests/res"]
-            manifest.srcFile "../tests/AndroidManifest.xml"
-        }
-    }
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_17
-        targetCompatibility JavaVersion.VERSION_17
-    }
-    buildFeatures {
-        compose true
-    }
-    composeOptions {
-        kotlinCompilerExtensionVersion jetpack_compose_compiler_version
-    }
-    buildTypes {
-        debug {
-            testCoverageEnabled = true
-        }
-    }
-}
-
-dependencies {
-    api "androidx.appcompat:appcompat:1.7.0-alpha02"
-    api "androidx.slice:slice-builders:1.1.0-alpha02"
-    api "androidx.slice:slice-core:1.1.0-alpha02"
-    api "androidx.slice:slice-view:1.1.0-alpha02"
-    api "androidx.compose.material3:material3:1.1.0-alpha06"
-    api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
-    api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
-    api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
-    api "androidx.lifecycle:lifecycle-livedata-ktx"
-    api "androidx.lifecycle:lifecycle-runtime-compose"
-    api "androidx.navigation:navigation-compose:2.6.0-alpha08"
-    api "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
-    api "com.google.android.material:material:1.7.0-alpha03"
-    debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
-    implementation "com.airbnb.android:lottie-compose:5.2.0"
-
-    androidTestImplementation project(":testutils")
-    androidTestImplementation 'androidx.lifecycle:lifecycle-runtime-testing'
-    androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:2.28.3"
-}
-
-task coverageReport(type: JacocoReport, dependsOn: "connectedDebugAndroidTest") {
-    group = "Reporting"
-    description = "Generate Jacoco coverage reports after running tests."
-
-    sourceDirectories.from = files("src")
-    classDirectories.from = fileTree(
-            dir: "$buildDir/tmp/kotlin-classes/debug",
-            excludes: [
-                    "com/android/settingslib/spa/debug/**",
-
-                    // Excludes files forked from AndroidX.
-                    "com/android/settingslib/spa/widget/scaffold/CustomizedAppBar*",
-                    "com/android/settingslib/spa/widget/scaffold/TopAppBarColors*",
-
-                    // Excludes files forked from Accompanist.
-                    "com/android/settingslib/spa/framework/compose/DrawablePainter*",
-
-                    // Excludes inline functions, which is not covered in Jacoco reports.
-                    "com/android/settingslib/spa/framework/util/Collections*",
-                    "com/android/settingslib/spa/framework/util/Flows*",
-
-                    // Excludes debug functions
-                    "com/android/settingslib/spa/framework/compose/TimeMeasurer*",
-
-                    // Excludes slice demo presenter & provider
-                    "com/android/settingslib/spa/slice/presenter/Demo*",
-                    "com/android/settingslib/spa/slice/provider/Demo*",
-            ],
-    )
-    executionData.from = fileTree(dir: "$buildDir/outputs/code_coverage/debugAndroidTest/connected")
-}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
new file mode 100644
index 0000000..188e7f6
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -0,0 +1,110 @@
+/*
+ * Copyright 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.
+ */
+
+plugins {
+    alias(libs.plugins.android.library)
+    alias(libs.plugins.kotlin.android)
+    jacoco
+}
+
+val jetpackComposeVersion: String? by extra
+
+android {
+    namespace = "com.android.settingslib.spa"
+
+    defaultConfig {
+        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    sourceSets {
+        sourceSets.getByName("main") {
+            kotlin.setSrcDirs(listOf("src"))
+            res.setSrcDirs(listOf("res"))
+            manifest.srcFile("AndroidManifest.xml")
+        }
+        sourceSets.getByName("androidTest") {
+            kotlin.setSrcDirs(listOf("../tests/src"))
+            res.setSrcDirs(listOf("../tests/res"))
+            manifest.srcFile("../tests/AndroidManifest.xml")
+        }
+    }
+    buildFeatures {
+        compose = true
+    }
+    buildTypes {
+        getByName("debug") {
+            enableAndroidTestCoverage = true
+        }
+    }
+}
+
+dependencies {
+    api("androidx.appcompat:appcompat:1.7.0-alpha02")
+    api("androidx.slice:slice-builders:1.1.0-alpha02")
+    api("androidx.slice:slice-core:1.1.0-alpha02")
+    api("androidx.slice:slice-view:1.1.0-alpha02")
+    api("androidx.compose.material3:material3:1.2.0-alpha03")
+    api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
+    api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
+    api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
+    api("androidx.lifecycle:lifecycle-livedata-ktx")
+    api("androidx.lifecycle:lifecycle-runtime-compose")
+    api("androidx.navigation:navigation-compose:2.7.0-beta01")
+    api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
+    api("com.google.android.material:material:1.7.0-alpha03")
+    debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
+    implementation("com.airbnb.android:lottie-compose:5.2.0")
+
+    androidTestImplementation(project(":testutils"))
+    androidTestImplementation(libs.dexmaker.mockito)
+}
+
+tasks.register<JacocoReport>("coverageReport") {
+    group = "Reporting"
+    description = "Generate Jacoco coverage reports after running tests."
+    dependsOn("connectedDebugAndroidTest")
+    sourceDirectories.setFrom(files("src"))
+    classDirectories.setFrom(
+        fileTree(layout.buildDirectory.dir("tmp/kotlin-classes/debug")) {
+            setExcludes(
+                listOf(
+                    "com/android/settingslib/spa/debug/**",
+
+                    // Excludes files forked from AndroidX.
+                    "com/android/settingslib/spa/widget/scaffold/CustomizedAppBar*",
+                    "com/android/settingslib/spa/widget/scaffold/TopAppBarColors*",
+
+                    // Excludes files forked from Accompanist.
+                    "com/android/settingslib/spa/framework/compose/DrawablePainter*",
+
+                    // Excludes inline functions, which is not covered in Jacoco reports.
+                    "com/android/settingslib/spa/framework/util/Collections*",
+                    "com/android/settingslib/spa/framework/util/Flows*",
+
+                    // Excludes debug functions
+                    "com/android/settingslib/spa/framework/compose/TimeMeasurer*",
+
+                    // Excludes slice demo presenter & provider
+                    "com/android/settingslib/spa/slice/presenter/Demo*",
+                    "com/android/settingslib/spa/slice/provider/Demo*",
+                )
+            )
+        }
+    )
+    executionData.setFrom(
+        fileTree(layout.buildDirectory.dir("outputs/code_coverage/debugAndroidTest/connected"))
+    )
+}
diff --git a/packages/SettingsLib/Spa/spa/res/values/colors.xml b/packages/SettingsLib/Spa/spa/res/values/colors.xml
new file mode 100644
index 0000000..ca4a0b2
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/res/values/colors.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <color name="settingslib_protection_color">@android:color/white</color>
+
+    <!-- Dynamic colors-->
+    <color name="settingslib_color_blue600">#1a73e8</color>
+    <color name="settingslib_color_blue400">#669df6</color>
+    <color name="settingslib_color_blue300">#8ab4f8</color>
+    <color name="settingslib_color_blue100">#d2e3fc</color>
+    <color name="settingslib_color_blue50">#e8f0fe</color>
+    <color name="settingslib_color_green600">#1e8e3e</color>
+    <color name="settingslib_color_green500">#34A853</color>
+    <color name="settingslib_color_green400">#5bb974</color>
+    <color name="settingslib_color_green100">#ceead6</color>
+    <color name="settingslib_color_green50">#e6f4ea</color>
+    <color name="settingslib_color_red600">#d93025</color>
+    <color name="settingslib_color_red500">#B3261E</color>
+    <color name="settingslib_color_red400">#ee675c</color>
+    <color name="settingslib_color_red100">#fad2cf</color>
+    <color name="settingslib_color_red50">#fce8e6</color>
+    <color name="settingslib_color_yellow600">#f9ab00</color>
+    <color name="settingslib_color_yellow400">#fcc934</color>
+    <color name="settingslib_color_yellow100">#feefc3</color>
+    <color name="settingslib_color_yellow50">#fef7e0</color>
+    <color name="settingslib_color_grey900">#202124</color>
+    <color name="settingslib_color_grey800">#3c4043</color>
+    <color name="settingslib_color_grey700">#5f6368</color>
+    <color name="settingslib_color_grey600">#80868b</color>
+    <color name="settingslib_color_grey500">#9AA0A6</color>
+    <color name="settingslib_color_grey400">#bdc1c6</color>
+    <color name="settingslib_color_grey300">#dadce0</color>
+    <color name="settingslib_color_grey200">#e8eaed</color>
+    <color name="settingslib_color_grey100">#f1f3f4</color>
+    <color name="settingslib_color_grey50">#f8f9fa</color>
+    <color name="settingslib_color_orange600">#e8710a</color>
+    <color name="settingslib_color_orange400">#fa903e</color>
+    <color name="settingslib_color_orange300">#fcad70</color>
+    <color name="settingslib_color_orange100">#fedfc8</color>
+    <color name="settingslib_color_pink600">#e52592</color>
+    <color name="settingslib_color_pink400">#ff63b8</color>
+    <color name="settingslib_color_pink300">#ff8bcb</color>
+    <color name="settingslib_color_pink100">#fdcfe8</color>
+    <color name="settingslib_color_purple600">#9334e6</color>
+    <color name="settingslib_color_purple400">#af5cf7</color>
+    <color name="settingslib_color_purple300">#c58af9</color>
+    <color name="settingslib_color_purple100">#e9d2fd</color>
+    <color name="settingslib_color_cyan600">#12b5c8</color>
+    <color name="settingslib_color_cyan400">#4ecde6</color>
+    <color name="settingslib_color_cyan300">#78d9ec</color>
+    <color name="settingslib_color_cyan100">#cbf0f8</color>
+</resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 0f5862a..08e3a27 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalAnimationApi::class)
-
 package com.android.settingslib.spa.framework
 
 import android.content.Intent
@@ -24,20 +22,17 @@
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.annotation.VisibleForTesting
-import androidx.compose.animation.AnimatedContentScope
-import androidx.compose.animation.ExperimentalAnimationApi
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.ui.unit.IntOffset
 import androidx.core.view.WindowCompat
 import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
 import com.android.settingslib.spa.R
 import com.android.settingslib.spa.framework.common.LogCategory
 import com.android.settingslib.spa.framework.common.NullPageProvider
@@ -46,12 +41,10 @@
 import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.compose.AnimatedNavHost
 import com.android.settingslib.spa.framework.compose.LocalNavController
 import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
-import com.android.settingslib.spa.framework.compose.composable
+import com.android.settingslib.spa.framework.compose.animatedComposable
 import com.android.settingslib.spa.framework.compose.localNavController
-import com.android.settingslib.spa.framework.compose.rememberAnimatedNavController
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.framework.util.PageLogger
 import com.android.settingslib.spa.framework.util.getDestination
@@ -108,7 +101,7 @@
     isPageEnabled: (SettingsPage) -> Boolean,
     initialIntent: Intent?,
 ) {
-    val navController = rememberAnimatedNavController()
+    val navController = rememberNavController()
     CompositionLocalProvider(navController.localNavController()) {
         val controller = LocalNavController.current as NavControllerWrapperImpl
         controller.NavContent(sppRepository.getAllProviders()) { page ->
@@ -133,37 +126,15 @@
     allProvider: Collection<SettingsPageProvider>,
     content: @Composable (SettingsPage) -> Unit,
 ) {
-    AnimatedNavHost(
+    NavHost(
         navController = navController,
         startDestination = NullPageProvider.name,
     ) {
-        val slideEffect = tween<IntOffset>(durationMillis = 300)
-        val fadeEffect = tween<Float>(durationMillis = 300)
         composable(NullPageProvider.name) {}
         for (spp in allProvider) {
-            composable(
+            animatedComposable(
                 route = spp.name + spp.parameter.navRoute(),
                 arguments = spp.parameter,
-                enterTransition = {
-                    slideIntoContainer(
-                        AnimatedContentScope.SlideDirection.Start, animationSpec = slideEffect
-                    ) + fadeIn(animationSpec = fadeEffect)
-                },
-                exitTransition = {
-                    slideOutOfContainer(
-                        AnimatedContentScope.SlideDirection.Start, animationSpec = slideEffect
-                    ) + fadeOut(animationSpec = fadeEffect)
-                },
-                popEnterTransition = {
-                    slideIntoContainer(
-                        AnimatedContentScope.SlideDirection.End, animationSpec = slideEffect
-                    ) + fadeIn(animationSpec = fadeEffect)
-                },
-                popExitTransition = {
-                    slideOutOfContainer(
-                        AnimatedContentScope.SlideDirection.End, animationSpec = slideEffect
-                    ) + fadeOut(animationSpec = fadeEffect)
-                },
             ) { navBackStackEntry ->
                 val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) }
                 content(page)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt
deleted file mode 100644
index 930a83f..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.framework.compose
-
-import androidx.compose.animation.AnimatedVisibilityScope
-import androidx.compose.animation.ExperimentalAnimationApi
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateOf
-import androidx.navigation.NavBackStackEntry
-import androidx.navigation.NavDestination
-import androidx.navigation.NavOptions
-import androidx.navigation.Navigator
-
-/**
- * Navigator that navigates through [Composable]s. Every destination using this Navigator must
- * set a valid [Composable] by setting it directly on an instantiated [Destination] or calling
- * [composable].
- */
-@ExperimentalAnimationApi
-@Navigator.Name("animatedComposable")
-public class AnimatedComposeNavigator : Navigator<AnimatedComposeNavigator.Destination>() {
-    internal val transitionsInProgress get() = state.transitionsInProgress
-
-    internal val backStack get() = state.backStack
-
-    internal val isPop = mutableStateOf(false)
-
-    override fun navigate(
-        entries: List<NavBackStackEntry>,
-        navOptions: NavOptions?,
-        navigatorExtras: Extras?
-    ) {
-        entries.forEach { entry ->
-            state.pushWithTransition(entry)
-        }
-        isPop.value = false
-    }
-
-    override fun createDestination(): Destination {
-        return Destination(this, content = { })
-    }
-
-    override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) {
-        state.popWithTransition(popUpTo, savedState)
-        isPop.value = true
-    }
-
-    internal fun markTransitionComplete(entry: NavBackStackEntry) {
-        state.markTransitionComplete(entry)
-    }
-
-    /**
-     * NavDestination specific to [AnimatedComposeNavigator]
-     */
-    @ExperimentalAnimationApi
-    @NavDestination.ClassType(Composable::class)
-    public class Destination(
-        navigator: AnimatedComposeNavigator,
-        internal val content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit
-    ) : NavDestination(navigator)
-
-    internal companion object {
-        internal const val NAME = "animatedComposable"
-    }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt
new file mode 100644
index 0000000..192b125
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.AnimatedContentTransitionScope
+import androidx.compose.animation.core.FastOutLinearInEasing
+import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.IntOffset
+import androidx.navigation.NamedNavArgument
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavDeepLink
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.compose.composable
+
+/**
+ * Add the [Composable] to the [NavGraphBuilder] with animation
+ *
+ * @param route route for the destination
+ * @param arguments list of arguments to associate with destination
+ * @param deepLinks list of deep links to associate with the destinations
+ * @param content composable for the destination
+ */
+internal fun NavGraphBuilder.animatedComposable(
+    route: String,
+    arguments: List<NamedNavArgument> = emptyList(),
+    deepLinks: List<NavDeepLink> = emptyList(),
+    content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
+) = composable(
+    route = route,
+    arguments = arguments,
+    deepLinks = deepLinks,
+    enterTransition = {
+        slideIntoContainer(
+            towards = AnimatedContentTransitionScope.SlideDirection.Start,
+            animationSpec = slideInEffect,
+            initialOffset = offsetFunc,
+        ) + fadeIn(animationSpec = fadeInEffect)
+    },
+    exitTransition = {
+        slideOutOfContainer(
+            towards = AnimatedContentTransitionScope.SlideDirection.Start,
+            animationSpec = slideOutEffect,
+            targetOffset = offsetFunc,
+        ) + fadeOut(animationSpec = fadeOutEffect)
+    },
+    popEnterTransition = {
+        slideIntoContainer(
+            towards = AnimatedContentTransitionScope.SlideDirection.End,
+            animationSpec = slideInEffect,
+            initialOffset = offsetFunc,
+        ) + fadeIn(animationSpec = fadeInEffect)
+    },
+    popExitTransition = {
+        slideOutOfContainer(
+            towards = AnimatedContentTransitionScope.SlideDirection.End,
+            animationSpec = slideOutEffect,
+            targetOffset = offsetFunc,
+        ) + fadeOut(animationSpec = fadeOutEffect)
+    },
+    content = content,
+)
+
+private const val FADE_OUT_MILLIS = 75
+private const val FADE_IN_MILLIS = 300
+
+private val slideInEffect = tween<IntOffset>(
+    durationMillis = FADE_IN_MILLIS,
+    delayMillis = FADE_OUT_MILLIS,
+    easing = LinearOutSlowInEasing,
+)
+private val slideOutEffect = tween<IntOffset>(durationMillis = FADE_IN_MILLIS)
+private val fadeOutEffect = tween<Float>(
+    durationMillis = FADE_OUT_MILLIS,
+    easing = FastOutLinearInEasing,
+)
+private val fadeInEffect = tween<Float>(
+    durationMillis = FADE_IN_MILLIS,
+    delayMillis = FADE_OUT_MILLIS,
+    easing = LinearOutSlowInEasing,
+)
+private val offsetFunc: (offsetForFullSlide: Int) -> Int = { it.div(5) }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt
deleted file mode 100644
index 57bb838..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.framework.compose
-
-import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
-import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.AnimatedContentScope
-import androidx.compose.animation.ContentTransform
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.ExperimentalAnimationApi
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.core.updateTransition
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.with
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.rememberSaveableStateHolder
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
-import androidx.navigation.NavBackStackEntry
-import androidx.navigation.NavDestination
-import androidx.navigation.NavDestination.Companion.hierarchy
-import androidx.navigation.NavGraph
-import androidx.navigation.NavGraphBuilder
-import androidx.navigation.NavHostController
-import androidx.navigation.Navigator
-import androidx.navigation.compose.DialogHost
-import androidx.navigation.compose.DialogNavigator
-import androidx.navigation.compose.LocalOwnersProvider
-import androidx.navigation.createGraph
-import androidx.navigation.get
-import kotlinx.coroutines.flow.map
-
-/**
- * Provides in place in the Compose hierarchy for self contained navigation to occur.
- *
- * Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
- * the provided [navController].
- *
- * The builder passed into this method is [remember]ed. This means that for this NavHost, the
- * contents of the builder cannot be changed.
- *
- * @param navController the navController for this host
- * @param startDestination the route for the start destination
- * @param modifier The modifier to be applied to the layout.
- * @param route the route for the graph
- * @param enterTransition callback to define enter transitions for destination in this host
- * @param exitTransition callback to define exit transitions for destination in this host
- * @param popEnterTransition callback to define popEnter transitions for destination in this host
- * @param popExitTransition callback to define popExit transitions for destination in this host
- * @param builder the builder used to construct the graph
- */
-@Composable
-@ExperimentalAnimationApi
-public fun AnimatedNavHost(
-    navController: NavHostController,
-    startDestination: String,
-    modifier: Modifier = Modifier,
-    contentAlignment: Alignment = Alignment.Center,
-    route: String? = null,
-    enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
-        { fadeIn(animationSpec = tween(700)) },
-    exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
-        { fadeOut(animationSpec = tween(700)) },
-    popEnterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
-        enterTransition,
-    popExitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
-        exitTransition,
-    builder: NavGraphBuilder.() -> Unit
-) {
-    AnimatedNavHost(
-        navController,
-        remember(route, startDestination, builder) {
-            navController.createGraph(startDestination, route, builder)
-        },
-        modifier,
-        contentAlignment,
-        enterTransition,
-        exitTransition,
-        popEnterTransition,
-        popExitTransition
-    )
-}
-
-/**
- * Provides in place in the Compose hierarchy for self contained navigation to occur.
- *
- * Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
- * the provided [navController].
- *
- * @param navController the navController for this host
- * @param graph the graph for this host
- * @param modifier The modifier to be applied to the layout.
- * @param enterTransition callback to define enter transitions for destination in this host
- * @param exitTransition callback to define exit transitions for destination in this host
- * @param popEnterTransition callback to define popEnter transitions for destination in this host
- * @param popExitTransition callback to define popExit transitions for destination in this host
- */
-@ExperimentalAnimationApi
-@Composable
-public fun AnimatedNavHost(
-    navController: NavHostController,
-    graph: NavGraph,
-    modifier: Modifier = Modifier,
-    contentAlignment: Alignment = Alignment.Center,
-    enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
-        { fadeIn(animationSpec = tween(700)) },
-    exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
-        { fadeOut(animationSpec = tween(700)) },
-    popEnterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
-        enterTransition,
-    popExitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
-        exitTransition,
-) {
-
-    val lifecycleOwner = LocalLifecycleOwner.current
-    val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
-        "NavHost requires a ViewModelStoreOwner to be provided via LocalViewModelStoreOwner"
-    }
-    val onBackPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
-    val onBackPressedDispatcher = onBackPressedDispatcherOwner?.onBackPressedDispatcher
-
-    // on successful recompose we setup the navController with proper inputs
-    // after the first time, this will only happen again if one of the inputs changes
-    navController.setLifecycleOwner(lifecycleOwner)
-    navController.setViewModelStore(viewModelStoreOwner.viewModelStore)
-    if (onBackPressedDispatcher != null) {
-        navController.setOnBackPressedDispatcher(onBackPressedDispatcher)
-    }
-
-    navController.graph = graph
-
-    val saveableStateHolder = rememberSaveableStateHolder()
-
-    // Find the ComposeNavigator, returning early if it isn't found
-    // (such as is the case when using TestNavHostController)
-    val composeNavigator = navController.navigatorProvider.get<Navigator<out NavDestination>>(
-        AnimatedComposeNavigator.NAME
-    ) as? AnimatedComposeNavigator ?: return
-    val visibleEntries by remember(navController.visibleEntries) {
-        navController.visibleEntries.map {
-            it.filter { entry ->
-                entry.destination.navigatorName == AnimatedComposeNavigator.NAME
-            }
-        }
-    }.collectAsState(emptyList())
-
-    val backStackEntry = visibleEntries.lastOrNull()
-
-    if (backStackEntry != null) {
-        val finalEnter: AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition = {
-            val targetDestination = targetState.destination as AnimatedComposeNavigator.Destination
-
-            if (composeNavigator.isPop.value) {
-                targetDestination.hierarchy.firstNotNullOfOrNull { destination ->
-                    popEnterTransitions[destination.route]?.invoke(this)
-                } ?: popEnterTransition.invoke(this)
-            } else {
-                targetDestination.hierarchy.firstNotNullOfOrNull { destination ->
-                    enterTransitions[destination.route]?.invoke(this)
-                } ?: enterTransition.invoke(this)
-            }
-        }
-
-        val finalExit: AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition = {
-            val initialDestination =
-                initialState.destination as AnimatedComposeNavigator.Destination
-
-            if (composeNavigator.isPop.value) {
-                initialDestination.hierarchy.firstNotNullOfOrNull { destination ->
-                    popExitTransitions[destination.route]?.invoke(this)
-                } ?: popExitTransition.invoke(this)
-            } else {
-                initialDestination.hierarchy.firstNotNullOfOrNull { destination ->
-                    exitTransitions[destination.route]?.invoke(this)
-                } ?: exitTransition.invoke(this)
-            }
-        }
-
-        val transition = updateTransition(backStackEntry, label = "entry")
-        transition.AnimatedContent(
-            modifier,
-            transitionSpec = {
-                val zIndex = composeNavigator.backStack.value.size.toFloat()
-                // If the initialState of the AnimatedContent is not in visibleEntries, we are in
-                // a case where visible has cleared the old state for some reason, so instead of
-                // attempting to animate away from the initialState, we skip the animation.
-                if (initialState in visibleEntries) {
-                    ContentTransform(finalEnter(this), finalExit(this), zIndex)
-                } else {
-                    EnterTransition.None with ExitTransition.None
-                }
-            },
-            contentAlignment,
-            contentKey = { it.id }
-        ) {
-            // In some specific cases, such as clearing your back stack by changing your
-            // start destination, AnimatedContent can contain an entry that is no longer
-            // part of visible entries since it was cleared from the back stack and is not
-            // animating. In these cases the currentEntry will be null, and in those cases,
-            // AnimatedContent will just skip attempting to transition the old entry.
-            // See https://issuetracker.google.com/238686802
-            val currentEntry = visibleEntries.lastOrNull { entry ->
-                it == entry
-            }
-            // while in the scope of the composable, we provide the navBackStackEntry as the
-            // ViewModelStoreOwner and LifecycleOwner
-            currentEntry?.LocalOwnersProvider(saveableStateHolder) {
-                (currentEntry.destination as AnimatedComposeNavigator.Destination)
-                    .content(this, currentEntry)
-            }
-        }
-        if (transition.currentState == transition.targetState) {
-            visibleEntries.forEach { entry ->
-                composeNavigator.markTransitionComplete(entry)
-            }
-        }
-    }
-
-    val dialogNavigator = navController.navigatorProvider.get<Navigator<out NavDestination>>(
-        "dialog"
-    ) as? DialogNavigator ?: return
-
-    // Show any dialog destinations
-    DialogHost(dialogNavigator)
-}
-
-@ExperimentalAnimationApi
-internal val enterTransitions =
-    mutableMapOf<String?,
-        (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)?>()
-
-@ExperimentalAnimationApi
-internal val exitTransitions =
-    mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)?>()
-
-@ExperimentalAnimationApi
-internal val popEnterTransitions =
-    mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)?>()
-
-@ExperimentalAnimationApi
-internal val popExitTransitions =
-    mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)?>()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt
deleted file mode 100644
index 9e58603..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.framework.compose
-
-import androidx.compose.animation.AnimatedContentScope
-import androidx.compose.animation.AnimatedVisibilityScope
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.ExperimentalAnimationApi
-import androidx.compose.runtime.Composable
-import androidx.navigation.NamedNavArgument
-import androidx.navigation.NavBackStackEntry
-import androidx.navigation.NavDeepLink
-import androidx.navigation.NavGraph
-import androidx.navigation.NavGraphBuilder
-import androidx.navigation.compose.navigation
-import androidx.navigation.get
-
-/**
- * Add the [Composable] to the [NavGraphBuilder]
- *
- * @param route route for the destination
- * @param arguments list of arguments to associate with destination
- * @param deepLinks list of deep links to associate with the destinations
- * @param enterTransition callback to determine the destination's enter transition
- * @param exitTransition callback to determine the destination's exit transition
- * @param popEnterTransition callback to determine the destination's popEnter transition
- * @param popExitTransition callback to determine the destination's popExit transition
- * @param content composable for the destination
- */
-@ExperimentalAnimationApi
-public fun NavGraphBuilder.composable(
-    route: String,
-    arguments: List<NamedNavArgument> = emptyList(),
-    deepLinks: List<NavDeepLink> = emptyList(),
-    enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)? = null,
-    exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)? = null,
-    popEnterTransition: (
-    AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?
-    )? = enterTransition,
-    popExitTransition: (
-    AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?
-    )? = exitTransition,
-    content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit
-) {
-    addDestination(
-        AnimatedComposeNavigator.Destination(
-            provider[AnimatedComposeNavigator::class],
-            content
-        ).apply {
-            this.route = route
-            arguments.forEach { (argumentName, argument) ->
-                addArgument(argumentName, argument)
-            }
-            deepLinks.forEach { deepLink ->
-                addDeepLink(deepLink)
-            }
-            enterTransition?.let { enterTransitions[route] = enterTransition }
-            exitTransition?.let { exitTransitions[route] = exitTransition }
-            popEnterTransition?.let { popEnterTransitions[route] = popEnterTransition }
-            popExitTransition?.let { popExitTransitions[route] = popExitTransition }
-        }
-    )
-}
-
-/**
- * Construct a nested [NavGraph]
- *
- * @param startDestination the starting destination's route for this NavGraph
- * @param route the destination's unique route
- * @param arguments list of arguments to associate with destination
- * @param deepLinks list of deep links to associate with the destinations
- * @param enterTransition callback to define enter transitions for destination in this NavGraph
- * @param exitTransition callback to define exit transitions for destination in this NavGraph
- * @param popEnterTransition callback to define pop enter transitions for destination in this
- * NavGraph
- * @param popExitTransition callback to define pop exit transitions for destination in this NavGraph
- * @param builder the builder used to construct the graph
- *
- * @return the newly constructed nested NavGraph
- */
-@ExperimentalAnimationApi
-public fun NavGraphBuilder.navigation(
-    startDestination: String,
-    route: String,
-    arguments: List<NamedNavArgument> = emptyList(),
-    deepLinks: List<NavDeepLink> = emptyList(),
-    enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)? = null,
-    exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)? = null,
-    popEnterTransition: (
-    AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?
-    )? = enterTransition,
-    popExitTransition: (
-    AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?
-    )? = exitTransition,
-    builder: NavGraphBuilder.() -> Unit
-) {
-    navigation(startDestination, route, arguments, deepLinks, builder).apply {
-        enterTransition?.let { enterTransitions[route] = enterTransition }
-        exitTransition?.let { exitTransitions[route] = exitTransition }
-        popEnterTransition?.let { popEnterTransitions[route] = popEnterTransition }
-        popExitTransition?.let { popExitTransitions[route] = popExitTransition }
-    }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt
deleted file mode 100644
index a8ac86c..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.framework.compose
-
-import androidx.compose.animation.ExperimentalAnimationApi
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.navigation.NavDestination
-import androidx.navigation.NavHostController
-import androidx.navigation.Navigator
-import androidx.navigation.compose.rememberNavController
-
-/**
- * Creates a NavHostController that handles the adding of the [ComposeNavigator], [DialogNavigator]
- * and [AnimatedComposeNavigator]. Additional [androidx.navigation.Navigator] instances should be
- * added in a [androidx.compose.runtime.SideEffect] block.
- *
- * @see AnimatedNavHost
- */
-@ExperimentalAnimationApi
-@Composable
-fun rememberAnimatedNavController(
-    vararg navigators: Navigator<out NavDestination>
-): NavHostController {
-    val animatedNavigator = remember { AnimatedComposeNavigator() }
-    return rememberNavController(animatedNavigator, *navigators)
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
index 7cc9bf7..6a2163c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
@@ -22,11 +22,11 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.widget.ui.ImageBox
 import com.android.settingslib.spa.widget.ui.Lottie
@@ -81,7 +81,7 @@
                 maxHeight = SettingsDimension.illustrationMaxHeight,
             )
             .clip(RoundedCornerShape(SettingsDimension.illustrationCornerRadius))
-            .background(color = MaterialTheme.colorScheme.surface)
+            .background(color = Color.Transparent)
 
         when (resourceType) {
             ResourceType.LOTTIE -> {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index e07a629..90a723f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -50,7 +50,7 @@
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Alignment
@@ -286,21 +286,22 @@
         )
     }
     val pinnedHeightPx: Float
-    val density = LocalDensity.current
-    val maxHeightPx = density.run {
-        remember { mutableStateOf((MaxHeightWithoutTitle + DefaultTitleHeight).toPx()) }
-    }
     val titleBottomPaddingPx: Int
+    val defaultMaxHeightPx: Float
+    val density = LocalDensity.current
     density.run {
         pinnedHeightPx = pinnedHeight.toPx()
         titleBottomPaddingPx = titleBottomPadding.roundToPx()
+        defaultMaxHeightPx = (MaxHeightWithoutTitle + DefaultTitleHeight).toPx()
     }
 
+    val maxHeightPx = remember(density) { mutableFloatStateOf(defaultMaxHeightPx) }
+
     // Sets the app bar's height offset limit to hide just the bottom title area and keep top title
     // visible when collapsed.
     SideEffect {
-        if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx.value) {
-            scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.value
+        if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx.floatValue) {
+            scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.floatValue
         }
     }
 
@@ -370,16 +371,20 @@
             )
             TopAppBarLayout(
                 modifier = Modifier.clipToBounds(),
-                heightPx = maxHeightPx.value - pinnedHeightPx +
+                heightPx = maxHeightPx.floatValue - pinnedHeightPx +
                     (scrollBehavior?.state?.heightOffset ?: 0f),
                 navigationIconContentColor = colors.navigationIconContentColor,
                 titleContentColor = colors.titleContentColor,
                 actionIconContentColor = colors.actionIconContentColor,
                 title = {
                     Box(modifier = Modifier.onGloballyPositioned { coordinates ->
-                        density.run {
-                            maxHeightPx.value =
-                                MaxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat()
+                        val measuredMaxHeightPx = density.run {
+                            MaxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat()
+                        }
+                        // Allow larger max height for multi-line title, but do not reduce
+                        // max height to prevent flaky.
+                        if (measuredMaxHeightPx > defaultMaxHeightPx) {
+                            maxHeightPx.floatValue = measuredMaxHeightPx
                         }
                     }) { title() }
                 },
@@ -506,7 +511,7 @@
                 0
             }
 
-        val layoutHeight = heightPx.roundToInt()
+        val layoutHeight = if (heightPx.isNaN()) 0 else heightPx.roundToInt()
 
         layout(constraints.maxWidth, layoutHeight) {
             // Navigation icon
@@ -612,9 +617,9 @@
 // Medium or Large app bar.
 private val TopTitleAlphaEasing = CubicBezierEasing(.8f, 0f, .8f, .15f)
 
-private val MaxHeightWithoutTitle = 124.dp
-private val DefaultTitleHeight = 52.dp
-private val ContainerHeight = 56.dp
+internal val MaxHeightWithoutTitle = 124.dp
+internal val DefaultTitleHeight = 52.dp
+internal val ContainerHeight = 56.dp
 private val LargeTitleBottomPadding = 28.dp
 private val TopAppBarHorizontalPadding = 4.dp
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index 67c4cdc..67f4418 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -163,7 +163,6 @@
     BackHandler { onClose() }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun SearchBox(query: TextFieldValue, onQueryChange: (TextFieldValue) -> Unit) {
     val focusRequester = remember { FocusRequester() }
@@ -186,8 +185,9 @@
         keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
         keyboardActions = KeyboardActions(onSearch = { hideKeyboardAction() }),
         singleLine = true,
-        colors = TextFieldDefaults.textFieldColors(
-            containerColor = Color.Transparent,
+        colors = TextFieldDefaults.colors(
+            focusedContainerColor = Color.Transparent,
+            unfocusedContainerColor = Color.Transparent,
             focusedIndicatorColor = Color.Transparent,
             unfocusedIndicatorColor = Color.Transparent,
         ),
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
index 79635a0..aa148b0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
@@ -20,20 +20,12 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.pager.HorizontalPager
-import androidx.compose.foundation.pager.PagerState
 import androidx.compose.foundation.pager.rememberPagerState
 import androidx.compose.material3.TabRow
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalConfiguration
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import kotlin.math.absoluteValue
 import kotlinx.coroutines.launch
@@ -49,7 +41,7 @@
 
     Column {
         val coroutineScope = rememberCoroutineScope()
-        val pagerState = rememberPageStateFixed()
+        val pagerState = rememberPagerState { titles.size }
 
         TabRow(
             selectedTabIndex = pagerState.currentPage,
@@ -72,46 +64,8 @@
             }
         }
 
-        HorizontalPager(pageCount = titles.size, state = pagerState) { page ->
+        HorizontalPager(state = pagerState) { page ->
             content(page)
         }
     }
 }
-
-/**
- * Gets the state of [PagerState].
- *
- * This is a work around.
- *
- * TODO: Remove this and replace with rememberPageState() after the Compose Foundation 1.5.0-alpha04
- *       updated in the platform.
- */
-@Composable
-@OptIn(ExperimentalFoundationApi::class)
-private fun rememberPageStateFixed(): PagerState {
-    var currentPage by rememberSaveable { mutableStateOf(0) }
-    var targetPage by rememberSaveable { mutableStateOf(-1) }
-    val pagerState = rememberPagerState()
-    val configuration = LocalConfiguration.current
-    var lastScreenWidthDp by rememberSaveable { mutableStateOf(-1) }
-    val screenWidthDp = configuration.screenWidthDp
-    LaunchedEffect(screenWidthDp) {
-        // Reset pager state to fix an issue after configuration change.
-        // When we declare android:configChanges in the manifest, the pager state is in a weird
-        // state after configuration change.
-        targetPage = currentPage
-        lastScreenWidthDp = screenWidthDp
-    }
-    LaunchedEffect(targetPage) {
-        if (targetPage != -1) {
-            pagerState.scrollToPage(targetPage)
-            targetPage = -1
-        }
-    }
-    SideEffect {
-        if (lastScreenWidthDp == screenWidthDp) {
-            currentPage = pagerState.currentPage
-        }
-    }
-    return pagerState
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
index 915c6e2..5f7fe85 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
@@ -16,15 +16,24 @@
 
 package com.android.settingslib.spa.widget.ui
 
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.res.colorResource
+import com.airbnb.lottie.LottieProperty
 import com.airbnb.lottie.compose.LottieAnimation
 import com.airbnb.lottie.compose.LottieCompositionSpec
 import com.airbnb.lottie.compose.LottieConstants
 import com.airbnb.lottie.compose.animateLottieCompositionAsState
 import com.airbnb.lottie.compose.rememberLottieComposition
+import com.airbnb.lottie.compose.rememberLottieDynamicProperties
+import com.airbnb.lottie.compose.rememberLottieDynamicProperty
+import com.android.settingslib.spa.R
 
 @Composable
 fun Lottie(
@@ -38,6 +47,34 @@
     }
 }
 
+object LottieColorUtils {
+    private val DARK_TO_LIGHT_THEME_COLOR_MAP = mapOf(
+        ".grey600" to R.color.settingslib_color_grey400,
+        ".grey800" to R.color.settingslib_color_grey300,
+        ".grey900" to R.color.settingslib_color_grey50,
+        ".red400" to R.color.settingslib_color_red600,
+        ".black" to android.R.color.white,
+        ".blue400" to R.color.settingslib_color_blue600,
+        ".green400" to R.color.settingslib_color_green600,
+        ".green200" to R.color.settingslib_color_green500,
+        ".red200" to R.color.settingslib_color_red500,
+    )
+
+    @Composable
+    private fun getDefaultPropertiesList() =
+        DARK_TO_LIGHT_THEME_COLOR_MAP.map { (key, colorRes) ->
+            val color = colorResource(colorRes).toArgb()
+            rememberLottieDynamicProperty(
+                property = LottieProperty.COLOR_FILTER,
+                keyPath = arrayOf("**", key, "**")
+            ){ PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP) }
+        }
+
+    @Composable
+    fun getDefaultDynamicProperties() =
+        rememberLottieDynamicProperties(*getDefaultPropertiesList().toTypedArray())
+}
+
 @Composable
 private fun BaseLottie(resId: Int) {
     val composition by rememberLottieComposition(
@@ -47,8 +84,10 @@
         composition,
         iterations = LottieConstants.IterateForever,
     )
+    val isLightMode = !isSystemInDarkTheme()
     LottieAnimation(
         composition = composition,
+        dynamicProperties = LottieColorUtils.getDefaultDynamicProperties().takeIf { isLightMode },
         progress = { progress },
     )
 }
diff --git a/packages/SettingsLib/Spa/tests/Android.bp b/packages/SettingsLib/Spa/tests/Android.bp
index f9e64ae..0d9ba54 100644
--- a/packages/SettingsLib/Spa/tests/Android.bp
+++ b/packages/SettingsLib/Spa/tests/Android.bp
@@ -36,5 +36,6 @@
         "mockito-target-minus-junit4",
     ],
     kotlincflags: ["-Xjvm-default=all"],
+    sdk_version: "current",
     min_sdk_version: "31",
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
new file mode 100644
index 0000000..a6a5ed22
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
@@ -0,0 +1,457 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.height
+import androidx.compose.ui.unit.width
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.rootWidth
+import com.android.settingslib.spa.testutils.setContentForSizeAssertions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalMaterial3Api::class)
+@RunWith(AndroidJUnit4::class)
+class CustomizedAppBarTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun smallTopAppBar_expandsToScreen() {
+        rule
+            .setContentForSizeAssertions {
+                CustomizedTopAppBar(title = { Text("Title") })
+            }
+            .assertHeightIsEqualTo(ContainerHeight)
+            .assertWidthIsEqualTo(rule.rootWidth())
+    }
+
+    @Test
+    fun smallTopAppBar_withTitle() {
+        val title = "Title"
+        rule.setContent {
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                CustomizedTopAppBar(title = { Text(title) })
+            }
+        }
+        rule.onNodeWithText(title).assertIsDisplayed()
+    }
+
+    @Test
+    fun smallTopAppBar_default_positioning() {
+        rule.setContent {
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                CustomizedTopAppBar(
+                    navigationIcon = {
+                        FakeIcon(Modifier.testTag(NavigationIconTestTag))
+                    },
+                    title = {
+                        Text("Title", Modifier.testTag(TitleTestTag))
+                    },
+                    actions = {
+                        FakeIcon(Modifier.testTag(ActionsTestTag))
+                    }
+                )
+            }
+        }
+        assertSmallDefaultPositioning()
+    }
+
+    @Test
+    fun smallTopAppBar_noNavigationIcon_positioning() {
+        rule.setContent {
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                CustomizedTopAppBar(
+                    title = {
+                        Text("Title", Modifier.testTag(TitleTestTag))
+                    },
+                    actions = {
+                        FakeIcon(Modifier.testTag(ActionsTestTag))
+                    }
+                )
+            }
+        }
+        assertSmallPositioningWithoutNavigation()
+    }
+
+    @Test
+    fun smallTopAppBar_titleDefaultStyle() {
+        var textStyle: TextStyle? = null
+        var expectedTextStyle: TextStyle? = null
+        rule.setContent {
+            CustomizedTopAppBar(
+                title = {
+                    Text("Title")
+                    textStyle = LocalTextStyle.current
+                    expectedTextStyle = MaterialTheme.typography.titleMedium
+                },
+            )
+        }
+        assertThat(textStyle).isNotNull()
+        assertThat(textStyle).isEqualTo(expectedTextStyle)
+    }
+
+    @Test
+    fun smallTopAppBar_contentColor() {
+        var titleColor: Color = Color.Unspecified
+        var navigationIconColor: Color = Color.Unspecified
+        var actionsColor: Color = Color.Unspecified
+        var expectedTitleColor: Color = Color.Unspecified
+        var expectedNavigationIconColor: Color = Color.Unspecified
+        var expectedActionsColor: Color = Color.Unspecified
+
+        rule.setContent {
+            CustomizedTopAppBar(
+                navigationIcon = {
+                    FakeIcon(Modifier.testTag(NavigationIconTestTag))
+                    navigationIconColor = LocalContentColor.current
+                    expectedNavigationIconColor =
+                        TopAppBarDefaults.topAppBarColors().navigationIconContentColor
+                    // fraction = 0f to indicate no scroll.
+                },
+                title = {
+                    Text("Title", Modifier.testTag(TitleTestTag))
+                    titleColor = LocalContentColor.current
+                    expectedTitleColor = TopAppBarDefaults.topAppBarColors().titleContentColor
+                },
+                actions = {
+                    FakeIcon(Modifier.testTag(ActionsTestTag))
+                    actionsColor = LocalContentColor.current
+                    expectedActionsColor =
+                        TopAppBarDefaults.topAppBarColors().actionIconContentColor
+                }
+            )
+        }
+        assertThat(navigationIconColor).isNotNull()
+        assertThat(titleColor).isNotNull()
+        assertThat(actionsColor).isNotNull()
+        assertThat(navigationIconColor).isEqualTo(expectedNavigationIconColor)
+        assertThat(titleColor).isEqualTo(expectedTitleColor)
+        assertThat(actionsColor).isEqualTo(expectedActionsColor)
+    }
+
+    @Test
+    fun largeTopAppBar_scrolled_positioning() {
+        val content = @Composable { scrollBehavior: TopAppBarScrollBehavior? ->
+            Box(Modifier.testTag(TopAppBarTestTag)) {
+                CustomizedLargeTopAppBar(
+                    navigationIcon = {
+                        FakeIcon(Modifier.testTag(NavigationIconTestTag))
+                    },
+                    title = "Title",
+                    actions = {
+                        FakeIcon(Modifier.testTag(ActionsTestTag))
+                    },
+                    scrollBehavior = scrollBehavior,
+                )
+            }
+        }
+        assertLargeScrolledHeight(
+            MaxHeightWithoutTitle + DefaultTitleHeight,
+            MaxHeightWithoutTitle + DefaultTitleHeight,
+            content,
+        )
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun topAppBar_enterAlways_allowHorizontalScroll() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            state = rememberLazyListState()
+            MultiPageContent(TopAppBarDefaults.enterAlwaysScrollBehavior(), state)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun topAppBar_exitUntilCollapsed_allowHorizontalScroll() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            state = rememberLazyListState()
+            MultiPageContent(TopAppBarDefaults.exitUntilCollapsedScrollBehavior(), state)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun topAppBar_pinned_allowHorizontalScroll() {
+        lateinit var state: LazyListState
+        rule.setContent {
+            state = rememberLazyListState()
+            MultiPageContent(
+                TopAppBarDefaults.pinnedScrollBehavior(),
+                state
+            )
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+        }
+
+        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
+        rule.runOnIdle {
+            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Composable
+    private fun MultiPageContent(scrollBehavior: TopAppBarScrollBehavior, state: LazyListState) {
+        Scaffold(
+            modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+            topBar = {
+                CustomizedTopAppBar(
+                    title = { Text(text = "Title") },
+                )
+            }
+        ) { contentPadding ->
+            LazyRow(
+                Modifier
+                    .fillMaxSize()
+                    .testTag(LazyListTag), state
+            ) {
+                items(2) { page ->
+                    LazyColumn(
+                        modifier = Modifier.fillParentMaxSize(),
+                        contentPadding = contentPadding
+                    ) {
+                        items(50) {
+                            Text(
+                                modifier = Modifier.fillParentMaxWidth(),
+                                text = "Item #$page x $it"
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks the app bar's components positioning when it's a [CustomizedTopAppBar], a
+     * [CenterAlignedTopAppBar], or a larger app bar that is scrolled up and collapsed into a small
+     * configuration and there is no navigation icon.
+     */
+    private fun assertSmallPositioningWithoutNavigation(isCenteredTitle: Boolean = false) {
+        val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot()
+        val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot()
+
+        val titleNode = rule.onNodeWithTag(TitleTestTag)
+        // Title should be vertically centered
+        titleNode.assertTopPositionInRootIsEqualTo((appBarBounds.height - titleBounds.height) / 2)
+        if (isCenteredTitle) {
+            // Title should be horizontally centered
+            titleNode.assertLeftPositionInRootIsEqualTo(
+                (appBarBounds.width - titleBounds.width) / 2
+            )
+        } else {
+            // Title should now be placed 16.dp from the start, as there is no navigation icon
+            // 4.dp padding for the whole app bar + 12.dp inset
+            titleNode.assertLeftPositionInRootIsEqualTo(4.dp + 12.dp)
+        }
+
+        rule.onNodeWithTag(ActionsTestTag)
+            // Action should still be placed at the end
+            .assertLeftPositionInRootIsEqualTo(expectedActionPosition(appBarBounds.width))
+    }
+
+    /**
+     * Checks the app bar's components positioning when it's a [CustomizedTopAppBar] or a
+     * [CenterAlignedTopAppBar].
+     */
+    private fun assertSmallDefaultPositioning(isCenteredTitle: Boolean = false) {
+        val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot()
+        val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot()
+        val appBarBottomEdgeY = appBarBounds.top + appBarBounds.height
+
+        rule.onNodeWithTag(NavigationIconTestTag)
+            // Navigation icon should be 4.dp from the start
+            .assertLeftPositionInRootIsEqualTo(AppBarStartAndEndPadding)
+            // Navigation icon should be centered within the height of the app bar.
+            .assertTopPositionInRootIsEqualTo(
+                appBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize
+            )
+
+        val titleNode = rule.onNodeWithTag(TitleTestTag)
+        // Title should be vertically centered
+        titleNode.assertTopPositionInRootIsEqualTo((appBarBounds.height - titleBounds.height) / 2)
+        if (isCenteredTitle) {
+            // Title should be horizontally centered
+            titleNode.assertLeftPositionInRootIsEqualTo(
+                (appBarBounds.width - titleBounds.width) / 2
+            )
+        } else {
+            // Title should be 56.dp from the start
+            // 4.dp padding for the whole app bar + 48.dp icon size + 4.dp title padding.
+            titleNode.assertLeftPositionInRootIsEqualTo(4.dp + FakeIconSize + 4.dp)
+        }
+
+        rule.onNodeWithTag(ActionsTestTag)
+            // Action should be placed at the end
+            .assertLeftPositionInRootIsEqualTo(expectedActionPosition(appBarBounds.width))
+            // Action should be 8.dp from the top
+            .assertTopPositionInRootIsEqualTo(
+                appBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize
+            )
+    }
+
+    /**
+     * Checks that changing values at a [CustomizedLargeTopAppBar] scroll behavior
+     * affects the height of the app bar.
+     *
+     * This check partially and fully collapses the app bar to test its height.
+     *
+     * @param appBarMaxHeight the max height of the app bar [content]
+     * @param appBarMinHeight the min height of the app bar [content]
+     * @param content a Composable that adds a CustomizedLargeTopAppBar
+     */
+    @OptIn(ExperimentalMaterial3Api::class)
+    private fun assertLargeScrolledHeight(
+        appBarMaxHeight: Dp,
+        appBarMinHeight: Dp,
+        content: @Composable (TopAppBarScrollBehavior?) -> Unit
+    ) {
+        val fullyCollapsedOffsetDp = appBarMaxHeight - appBarMinHeight
+        val partiallyCollapsedOffsetDp = fullyCollapsedOffsetDp / 3
+        var partiallyCollapsedHeightOffsetPx = 0f
+        var fullyCollapsedHeightOffsetPx = 0f
+        lateinit var scrollBehavior: TopAppBarScrollBehavior
+        rule.setContent {
+            scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
+            with(LocalDensity.current) {
+                partiallyCollapsedHeightOffsetPx = partiallyCollapsedOffsetDp.toPx()
+                fullyCollapsedHeightOffsetPx = fullyCollapsedOffsetDp.toPx()
+            }
+
+            content(scrollBehavior)
+        }
+
+        // Simulate a partially collapsed app bar.
+        rule.runOnIdle {
+            scrollBehavior.state.heightOffset = -partiallyCollapsedHeightOffsetPx
+            scrollBehavior.state.contentOffset = -partiallyCollapsedHeightOffsetPx
+        }
+        rule.waitForIdle()
+        rule.onNodeWithTag(TopAppBarTestTag)
+            .assertHeightIsEqualTo(
+                appBarMaxHeight - partiallyCollapsedOffsetDp
+            )
+
+        // Simulate a fully collapsed app bar.
+        rule.runOnIdle {
+            scrollBehavior.state.heightOffset = -fullyCollapsedHeightOffsetPx
+            // Simulate additional content scroll beyond the max offset scroll.
+            scrollBehavior.state.contentOffset =
+                -fullyCollapsedHeightOffsetPx - partiallyCollapsedHeightOffsetPx
+        }
+        rule.waitForIdle()
+        // Check that the app bar collapsed to its min height.
+        rule.onNodeWithTag(TopAppBarTestTag).assertHeightIsEqualTo(appBarMinHeight)
+    }
+
+    /**
+     * An [IconButton] with an [Icon] inside for testing positions.
+     *
+     * An [IconButton] is defaulted to be 48X48dp, while its child [Icon] is defaulted to 24x24dp.
+     */
+    private val FakeIcon = @Composable { modifier: Modifier ->
+        IconButton(
+            onClick = { /* doSomething() */ },
+            modifier = modifier.semantics(mergeDescendants = true) {}
+        ) {
+            Icon(ColorPainter(Color.Red), null)
+        }
+    }
+
+    private fun expectedActionPosition(appBarWidth: Dp): Dp =
+        appBarWidth - AppBarStartAndEndPadding - FakeIconSize
+
+    private val FakeIconSize = 48.dp
+    private val AppBarStartAndEndPadding = 4.dp
+    private val AppBarTopAndBottomPadding = (ContainerHeight - FakeIconSize) / 2
+
+    private val LazyListTag = "lazyList"
+    private val TopAppBarTestTag = "bar"
+    private val NavigationIconTestTag = "navigationIcon"
+    private val TitleTestTag = "title"
+    private val ActionsTestTag = "actions"
+}
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
deleted file mode 100644
index 23a9add..0000000
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 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.
- */
-
-plugins {
-    id 'com.android.library'
-    id 'kotlin-android'
-}
-
-android {
-    namespace 'com.android.settingslib.spa.testutils'
-    compileSdk TARGET_SDK
-    buildToolsVersion = BUILD_TOOLS_VERSION
-
-    defaultConfig {
-        minSdk MIN_SDK
-        targetSdk TARGET_SDK
-    }
-
-    sourceSets {
-        main {
-            kotlin {
-                srcDir "src"
-            }
-            manifest.srcFile "AndroidManifest.xml"
-        }
-    }
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_17
-        targetCompatibility JavaVersion.VERSION_17
-    }
-    buildFeatures {
-        compose true
-    }
-    composeOptions {
-        kotlinCompilerExtensionVersion jetpack_compose_compiler_version
-    }
-}
-
-dependencies {
-    api project(":spa")
-
-    api "androidx.arch.core:core-testing:2.2.0-alpha01"
-    api "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
-    api "com.google.truth:truth:1.1.3"
-    api "org.mockito:mockito-core:2.21.0"
-    debugApi "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
-}
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle.kts b/packages/SettingsLib/Spa/testutils/build.gradle.kts
new file mode 100644
index 0000000..f5a22c9
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/build.gradle.kts
@@ -0,0 +1,47 @@
+/*
+ * Copyright 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.
+ */
+
+plugins {
+    alias(libs.plugins.android.library)
+    alias(libs.plugins.kotlin.android)
+}
+
+val jetpackComposeVersion: String? by extra
+
+android {
+    namespace = "com.android.settingslib.spa.testutils"
+
+    sourceSets {
+        sourceSets.getByName("main") {
+            kotlin.setSrcDirs(listOf("src"))
+            manifest.srcFile("AndroidManifest.xml")
+        }
+    }
+    buildFeatures {
+        compose = true
+    }
+}
+
+dependencies {
+    api(project(":spa"))
+
+    api("androidx.arch.core:core-testing:2.2.0-alpha01")
+    api("androidx.compose.ui:ui-test-junit4:$jetpackComposeVersion")
+    api("androidx.lifecycle:lifecycle-runtime-testing")
+    api(libs.truth)
+    api("org.mockito:mockito-core:2.21.0")
+    debugApi("androidx.compose.ui:ui-test-manifest:$jetpackComposeVersion")
+}
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt
index a5d1f40..0436fc2 100644
--- a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ComposeContentTestRuleExt.kt
@@ -16,13 +16,28 @@
 
 package com.android.settingslib.spa.testutils
 
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ComposeTimeoutException
 import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
 import androidx.compose.ui.test.hasAnyAncestor
 import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.isDialog
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.ComposeTestRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.height
+import androidx.compose.ui.unit.width
+import com.android.settingslib.spa.framework.theme.SettingsTheme
 
 /** Blocks until the found a semantics node that match the given condition. */
 fun ComposeContentTestRule.waitUntilExists(matcher: SemanticsMatcher) = waitUntil {
@@ -39,3 +54,39 @@
 /** Finds a text node that within dialog. */
 fun ComposeContentTestRule.onDialogText(text: String): SemanticsNodeInteraction =
     onNode(hasAnyAncestor(isDialog()) and hasText(text))
+
+fun ComposeTestRule.rootWidth(): Dp = onRoot().getUnclippedBoundsInRoot().width
+
+fun ComposeTestRule.rootHeight(): Dp = onRoot().getUnclippedBoundsInRoot().height
+
+/**
+ * Constant to emulate very big but finite constraints
+ */
+private val sizeAssertionMaxSize = 5000.dp
+
+private const val SIZE_ASSERTION_TAG = "containerForSizeAssertion"
+
+fun ComposeContentTestRule.setContentForSizeAssertions(
+    parentMaxWidth: Dp = sizeAssertionMaxSize,
+    parentMaxHeight: Dp = sizeAssertionMaxSize,
+    // TODO : figure out better way to make it flexible
+    content: @Composable () -> Unit
+): SemanticsNodeInteraction {
+    setContent {
+        SettingsTheme {
+            Surface {
+                Box {
+                    Box(
+                        Modifier
+                            .sizeIn(maxWidth = parentMaxWidth, maxHeight = parentMaxHeight)
+                            .testTag(SIZE_ASSERTION_TAG)
+                    ) {
+                        content()
+                    }
+                }
+            }
+        }
+    }
+
+    return onNodeWithTag(SIZE_ASSERTION_TAG)
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index 2b38b4c..8e0cf89 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -34,11 +34,11 @@
 /**
  * The repository to load the App List data.
  */
-internal interface AppListRepository {
+interface AppListRepository {
     /** Loads the list of [ApplicationInfo]. */
     suspend fun loadApps(
         userId: Int,
-        showInstantApps: Boolean = false,
+        loadInstantApps: Boolean = false,
         matchAnyUserForAdmin: Boolean = false,
     ): List<ApplicationInfo>
 
@@ -50,6 +50,9 @@
 
     /** Gets the system app package names. */
     fun getSystemPackageNamesBlocking(userId: Int): Set<String>
+
+    /** Loads the list of [ApplicationInfo], and filter base on `isSystemApp`. */
+    suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean): List<ApplicationInfo>
 }
 
 /**
@@ -62,13 +65,13 @@
         AppListRepositoryImpl(context).getSystemPackageNamesBlocking(userId)
 }
 
-internal class AppListRepositoryImpl(private val context: Context) : AppListRepository {
+class AppListRepositoryImpl(private val context: Context) : AppListRepository {
     private val packageManager = context.packageManager
     private val userManager = context.userManager
 
     override suspend fun loadApps(
         userId: Int,
-        showInstantApps: Boolean,
+        loadInstantApps: Boolean,
         matchAnyUserForAdmin: Boolean,
     ): List<ApplicationInfo> = coroutineScope {
         val hiddenSystemModulesDeferred = async {
@@ -86,7 +89,7 @@
         val hiddenSystemModules = hiddenSystemModulesDeferred.await()
         val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await()
         installedApplicationsAsUser.filter { app ->
-            app.isInAppList(showInstantApps, hiddenSystemModules, hideWhenDisabledPackages)
+            app.isInAppList(loadInstantApps, hiddenSystemModules, hideWhenDisabledPackages)
         }
     }
 
@@ -136,17 +139,17 @@
     ): Flow<(app: ApplicationInfo) -> Boolean> =
         userIdFlow.combine(showSystemFlow, ::showSystemPredicate)
 
-    override fun getSystemPackageNamesBlocking(userId: Int) =
-        runBlocking { getSystemPackageNames(userId) }
+    override fun getSystemPackageNamesBlocking(userId: Int) = runBlocking {
+        loadAndFilterApps(userId = userId, isSystemApp = true).map { it.packageName }.toSet()
+    }
 
-    private suspend fun getSystemPackageNames(userId: Int): Set<String> =
-        coroutineScope {
-            val loadAppsDeferred = async { loadApps(userId) }
-            val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId)
-            val showSystemPredicate =
-                { app: ApplicationInfo -> isSystemApp(app, homeOrLauncherPackages) }
-            loadAppsDeferred.await().filter(showSystemPredicate).map { it.packageName }.toSet()
+    override suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean) = coroutineScope {
+        val loadAppsDeferred = async { loadApps(userId) }
+        val homeOrLauncherPackages = loadHomeOrLauncherPackages(userId)
+        loadAppsDeferred.await().filter { app ->
+            isSystemApp(app, homeOrLauncherPackages) == isSystemApp
         }
+    }
 
     private suspend fun showSystemPredicate(
         userId: Int,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 030b70a..07e4235 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -42,7 +42,6 @@
     showInstantApps: Boolean = false,
     noMoreOptions: Boolean = false,
     matchAnyUserForAdmin: Boolean = false,
-    primaryUserOnly: Boolean = false,
     noItemMessage: String? = null,
     moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
     header: @Composable () -> Unit = {},
@@ -60,7 +59,7 @@
             }
         },
     ) { bottomPadding, searchQuery ->
-        UserProfilePager(primaryUserOnly) { userGroup ->
+        UserProfilePager { userGroup ->
             val appListInput = AppListInput(
                 config = AppListConfig(
                     userIds = userGroup.userInfos.map { it.id },
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
index b5a4929..6a76c93 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
@@ -38,14 +38,9 @@
 )
 
 @Composable
-fun UserProfilePager(
-    primaryUserOnly: Boolean = false,
-    content: @Composable (userGroup: UserGroup) -> Unit,
-) {
+fun UserProfilePager(content: @Composable (userGroup: UserGroup) -> Unit) {
     val context = LocalContext.current
-    val userGroups = remember {
-        context.userManager.getUserGroups(primaryUserOnly)
-    }
+    val userGroups = remember { context.userManager.getUserGroups() }
     val titles = remember {
         val enterpriseRepository = EnterpriseRepository(context)
         userGroups.map { userGroup ->
@@ -60,10 +55,9 @@
     }
 }
 
-private fun UserManager.getUserGroups(primaryUserOnly: Boolean): List<UserGroup> {
+private fun UserManager.getUserGroups(): List<UserGroup> {
     val userGroupList = mutableListOf<UserGroup>()
     val profileToShowInSettingsList = getProfiles(UserHandle.myUserId())
-        .filter { userInfo -> !primaryUserOnly || userInfo.isPrimary }
         .map { userInfo -> userInfo to getUserProperties(userInfo.userHandle).showInSettings }
 
     profileToShowInSettingsList.filter { it.second == UserProperties.SHOW_IN_SETTINGS_WITH_PARENT }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index 302f780..375ed60 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -108,7 +108,7 @@
 
         val appList = repository.loadApps(
             userId = ADMIN_USER_ID,
-            showInstantApps = false,
+            loadInstantApps = false,
         )
 
         assertThat(appList).containsExactly(NORMAL_APP)
@@ -120,7 +120,7 @@
 
         val appList = repository.loadApps(
             userId = ADMIN_USER_ID,
-            showInstantApps = true,
+            loadInstantApps = true,
         )
 
         assertThat(appList).containsExactly(NORMAL_APP, INSTANT_APP)
@@ -325,6 +325,21 @@
         assertThat(systemPackageNames).containsExactly(SYSTEM_APP.packageName)
     }
 
+    @Test
+    fun loadAndFilterApps_loadNonSystemApp_returnExpectedValues() = runTest {
+        mockInstalledApplications(
+            apps = listOf(
+                NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP
+            ),
+            userId = ADMIN_USER_ID,
+        )
+
+        val appList = repository.loadAndFilterApps(userId = ADMIN_USER_ID, isSystemApp = false)
+
+        assertThat(appList)
+            .containsExactly(NORMAL_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP)
+    }
+
     private suspend fun getShowSystemPredicate(showSystem: Boolean) =
         repository.showSystemPredicate(
             userIdFlow = flowOf(ADMIN_USER_ID),
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index 6889e5d..9b22497 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -87,7 +87,7 @@
     private object FakeAppListRepository : AppListRepository {
         override suspend fun loadApps(
             userId: Int,
-            showInstantApps: Boolean,
+            loadInstantApps: Boolean,
             matchAnyUserForAdmin: Boolean,
         ) = listOf(APP)
 
@@ -97,6 +97,9 @@
         ): Flow<(app: ApplicationInfo) -> Boolean> = flowOf { true }
 
         override fun getSystemPackageNamesBlocking(userId: Int): Set<String> = emptySet()
+
+        override suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean) =
+            emptyList<ApplicationInfo>()
     }
 
     private object FakeAppRepository : AppRepository {
diff --git a/packages/SettingsLib/res/drawable/dialog_btn_filled.xml b/packages/SettingsLib/res/drawable/dialog_btn_filled.xml
index 14cb1de9..0614f9d 100644
--- a/packages/SettingsLib/res/drawable/dialog_btn_filled.xml
+++ b/packages/SettingsLib/res/drawable/dialog_btn_filled.xml
@@ -22,12 +22,12 @@
         <item android:id="@android:id/mask">
             <shape android:shape="rectangle">
                 <solid android:color="@android:color/white"/>
-                <corners android:radius="?android:attr/buttonCornerRadius"/>
+                <corners android:radius="@dimen/button_corner_radius"/>
             </shape>
         </item>
         <item>
             <shape android:shape="rectangle">
-                <corners android:radius="?android:attr/buttonCornerRadius"/>
+                <corners android:radius="@dimen/button_corner_radius"/>
                 <solid android:color="?androidprv:attr/colorAccentPrimary"/>
                 <padding android:left="@dimen/dialog_button_horizontal_padding"
                          android:top="@dimen/dialog_button_vertical_padding"
diff --git a/packages/SettingsLib/res/drawable/dialog_btn_outline.xml b/packages/SettingsLib/res/drawable/dialog_btn_outline.xml
index 1e77759..a920b50 100644
--- a/packages/SettingsLib/res/drawable/dialog_btn_outline.xml
+++ b/packages/SettingsLib/res/drawable/dialog_btn_outline.xml
@@ -22,16 +22,15 @@
         <item android:id="@android:id/mask">
             <shape android:shape="rectangle">
                 <solid android:color="@android:color/white"/>
-                <corners android:radius="?android:attr/buttonCornerRadius"/>
+                <corners android:radius="@dimen/button_corner_radius"/>
             </shape>
         </item>
         <item>
             <shape android:shape="rectangle">
-                <corners android:radius="?android:attr/buttonCornerRadius"/>
+                <corners android:radius="@dimen/button_corner_radius"/>
                 <solid android:color="@android:color/transparent"/>
                 <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
-                        android:width="1dp"
-                />
+                        android:width="1dp"/>
                 <padding android:left="@dimen/dialog_button_horizontal_padding"
                          android:top="@dimen/dialog_button_vertical_padding"
                          android:right="@dimen/dialog_button_horizontal_padding"
diff --git a/packages/SettingsLib/res/drawable/ic_hotspot_auto.xml b/packages/SettingsLib/res/drawable/ic_hotspot_auto.xml
new file mode 100644
index 0000000..ddd526a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hotspot_auto.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M240,760L240,800Q240,817 228.5,828.5Q217,840 200,840L160,840Q143,840 131.5,828.5Q120,817 120,800L120,480L204,240Q210,222 225.5,211Q241,200 260,200L700,200Q719,200 734.5,211Q750,222 756,240L840,480L840,800Q840,817 828.5,828.5Q817,840 800,840L760,840Q743,840 731.5,828.5Q720,817 720,800L720,760L240,760ZM232,400L728,400L686,280L274,280L232,400ZM200,480L200,480L200,680L200,680L200,480ZM300,640Q325,640 342.5,622.5Q360,605 360,580Q360,555 342.5,537.5Q325,520 300,520Q275,520 257.5,537.5Q240,555 240,580Q240,605 257.5,622.5Q275,640 300,640ZM660,640Q685,640 702.5,622.5Q720,605 720,580Q720,555 702.5,537.5Q685,520 660,520Q635,520 617.5,537.5Q600,555 600,580Q600,605 617.5,622.5Q635,640 660,640ZM200,680L760,680L760,480L200,480L200,680Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_hotspot_laptop.xml b/packages/SettingsLib/res/drawable/ic_hotspot_laptop.xml
new file mode 100644
index 0000000..5e1b184
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hotspot_laptop.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M0,800L0,720L80,720L80,120L880,120L880,720L960,720L960,800L0,800ZM400,720L560,720L560,680L400,680L400,720ZM160,600L800,600L800,200L160,200L160,600ZM160,600L160,200L160,200L160,600L160,600Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_hotspot_phone.xml b/packages/SettingsLib/res/drawable/ic_hotspot_phone.xml
new file mode 100644
index 0000000..baa793c
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hotspot_phone.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M280,920Q247,920 223.5,896.5Q200,873 200,840L200,120Q200,87 223.5,63.5Q247,40 280,40L680,40Q713,40 736.5,63.5Q760,87 760,120L760,840Q760,873 736.5,896.5Q713,920 680,920L280,920ZM280,800L280,840Q280,840 280,840Q280,840 280,840L680,840Q680,840 680,840Q680,840 680,840L680,800L280,800ZM280,720L680,720L680,240L280,240L280,720ZM280,160L680,160L680,120Q680,120 680,120Q680,120 680,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,160L280,120Q280,120 280,120Q280,120 280,120L280,120Q280,120 280,120Q280,120 280,120L280,160L280,160ZM280,800L280,800L280,840Q280,840 280,840Q280,840 280,840L280,840Q280,840 280,840Q280,840 280,840L280,800Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_hotspot_tablet.xml b/packages/SettingsLib/res/drawable/ic_hotspot_tablet.xml
new file mode 100644
index 0000000..cf67cd9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hotspot_tablet.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M120,800Q87,800 63.5,776.5Q40,753 40,720L40,240Q40,207 63.5,183.5Q87,160 120,160L840,160Q873,160 896.5,183.5Q920,207 920,240L920,720Q920,753 896.5,776.5Q873,800 840,800L120,800ZM160,240L120,240Q120,240 120,240Q120,240 120,240L120,720Q120,720 120,720Q120,720 120,720L160,720L160,240ZM240,720L720,720L720,240L240,240L240,720ZM800,240L800,720L840,720Q840,720 840,720Q840,720 840,720L840,240Q840,240 840,240Q840,240 840,240L800,240ZM800,240L840,240Q840,240 840,240Q840,240 840,240L840,240Q840,240 840,240Q840,240 840,240L800,240L800,240ZM160,240L160,240L120,240Q120,240 120,240Q120,240 120,240L120,240Q120,240 120,240Q120,240 120,240L160,240Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_hotspot_watch.xml b/packages/SettingsLib/res/drawable/ic_hotspot_watch.xml
new file mode 100644
index 0000000..252a0db
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_hotspot_watch.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M360,880L306,698Q258,660 229,603Q200,546 200,480Q200,414 229,357Q258,300 306,262L360,80L600,80L654,262Q702,300 731,357Q760,414 760,480Q760,546 731,603Q702,660 654,698L600,880L360,880ZM480,680Q563,680 621.5,621.5Q680,563 680,480Q680,397 621.5,338.5Q563,280 480,280Q397,280 338.5,338.5Q280,397 280,480Q280,563 338.5,621.5Q397,680 480,680ZM404,210Q424,205 442.5,202Q461,199 480,199Q499,199 517.5,202Q536,205 556,210L540,160L420,160L404,210ZM420,800L540,800L556,750Q536,755 517.5,757.5Q499,760 480,760Q461,760 442.5,757.5Q424,755 404,750L420,800ZM404,160L420,160L540,160L556,160Q536,160 517.5,160Q499,160 480,160Q461,160 442.5,160Q424,160 404,160ZM420,800L404,800Q424,800 442.5,800Q461,800 480,800Q499,800 517.5,800Q536,800 556,800L540,800L420,800Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/layout/dialog_with_icon.xml b/packages/SettingsLib/res/layout/dialog_with_icon.xml
index 54f8096..3586dcb 100644
--- a/packages/SettingsLib/res/layout/dialog_with_icon.xml
+++ b/packages/SettingsLib/res/layout/dialog_with_icon.xml
@@ -33,15 +33,14 @@
             android:importantForAccessibility="no"/>
         <TextView
             android:id="@+id/dialog_with_icon_title"
-            android:layout_width="wrap_content"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:gravity="center"
             style="@style/DialogWithIconTitle"/>
         <TextView
             android:id="@+id/dialog_with_icon_message"
-            android:layout_width="wrap_content"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:padding="10dp"
             android:gravity="center"
             style="@style/TextAppearanceSmall"/>
 
@@ -64,7 +63,6 @@
                 android:id="@+id/button_cancel"
                 style="@style/DialogButtonNegative"
                 android:layout_width="wrap_content"
-                android:buttonCornerRadius="28dp"
                 android:layout_height="wrap_content"
                 android:visibility="gone"/>
 
@@ -79,7 +77,6 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 style="@style/DialogButtonNegative"
-                android:buttonCornerRadius="40dp"
                 android:visibility="gone"/>
 
             <Space
@@ -93,8 +90,7 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 style="@style/DialogButtonPositive"
-                android:visibility="gone"
-            />
+                android:visibility="gone"/>
         </LinearLayout>
     </LinearLayout>
 </ScrollView>
diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
index 4ffaf1b..2ded3c6 100644
--- a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
+++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
@@ -14,62 +14,48 @@
      limitations under the License.
 -->
 
-<ScrollView
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/user_info_editor"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:id="@+id/user_info_scroll"
-    android:padding="16dp">
-
-    <LinearLayout
-        android:layout_width="match_parent"
+    android:baselineAligned="false"
+    android:orientation="vertical">
+    <FrameLayout
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:baselineAligned="false"
-        android:orientation="vertical">
-        <TextView
-            android:id="@+id/user_info_title"
-            android:gravity="center"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="@style/EditUserDialogTitle"
-            android:text="@string/user_info_settings_title"
-            android:textDirection="locale"/>
-        <FrameLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center">
-            <ImageView
-                android:id="@+id/user_photo"
-                android:layout_width="@dimen/user_photo_size_in_user_info_dialog"
-                android:layout_height="@dimen/user_photo_size_in_user_info_dialog"
-                android:contentDescription="@string/user_image_photo_selector"
-                android:scaleType="fitCenter"/>
-            <ImageView
-                android:id="@+id/add_a_photo_icon"
-                android:layout_width="@dimen/add_a_photo_icon_size_in_user_info_dialog"
-                android:layout_height="@dimen/add_a_photo_icon_size_in_user_info_dialog"
-                android:src="@drawable/add_a_photo_circled"
-                android:layout_gravity="bottom|right"/>
-        </FrameLayout>
+        android:layout_gravity="center">
+        <ImageView
+            android:id="@+id/user_photo"
+            android:layout_width="@dimen/user_photo_size_in_user_info_dialog"
+            android:layout_height="@dimen/user_photo_size_in_user_info_dialog"
+            android:contentDescription="@string/user_image_photo_selector"
+            android:scaleType="fitCenter"/>
+        <ImageView
+            android:id="@+id/add_a_photo_icon"
+            android:layout_width="@dimen/add_a_photo_icon_size_in_user_info_dialog"
+            android:layout_height="@dimen/add_a_photo_icon_size_in_user_info_dialog"
+            android:src="@drawable/add_a_photo_circled"
+            android:layout_gravity="bottom|right"/>
+    </FrameLayout>
 
-        <EditText
-            android:id="@+id/user_name"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/user_name_height_in_user_info_dialog"
-            android:layout_gravity="center"
-            android:minWidth="200dp"
-            android:layout_marginStart="6dp"
-            android:minHeight="@dimen/min_tap_target_size"
-            android:ellipsize="end"
-            android:singleLine="true"
-            android:textAppearance="?android:attr/textAppearanceMedium"
-            android:textAlignment="viewStart"
-            android:inputType="text|textCapWords"
-            android:selectAllOnFocus="true"
-            android:hint="@string/user_nickname"
-            android:maxLength="100"/>
+    <EditText
+        android:id="@+id/user_name"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/user_name_height_in_user_info_dialog"
+        android:layout_gravity="center"
+        android:minWidth="200dp"
+        android:layout_marginStart="6dp"
+        android:minHeight="@dimen/min_tap_target_size"
+        android:ellipsize="end"
+        android:singleLine="true"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textAlignment="viewStart"
+        android:inputType="text|textCapWords"
+        android:selectAllOnFocus="true"
+        android:hint="@string/user_nickname"
+        android:maxLength="100"/>
 
-    </LinearLayout>
+</LinearLayout>
 
-</ScrollView>
 
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 98ad6e3..a005693 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM-toegang"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-oudio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD oudio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Gehoortoestelle"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Gehoortoestelle"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE-oudio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Gekoppel aan gehoortoestelle"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Gekoppel aan gehoortoestelle"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Gekoppel aan LE-oudio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Gekoppel aan media-oudio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Gekoppel aan foonoudio"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Gebruik vir foonoudio"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Gebruik vir lêeroordrag"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Gebruik vir invoer"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Gebruik vir gehoortoestelle"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Gebruik vir gehoortoestelle"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Gebruik vir LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Bind saam"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"BIND SAAM"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Skermlaag wys huidige raakdata"</string>
     <string name="show_touches" msgid="8437666942161289025">"Wys tikke"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Wys visuele terugvoer vir tikke"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Wys sleuteldrukke"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Wys visuele terugvoer vir fisieke sleuteldrukke"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Wys oppervlakopdaterings"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Flits totale vensteroppervlakke wanneer dit opdateer"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Wys aansigopdaterings"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index a05df64..f30d606 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"የሲም መዳረሻ"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"ኤችዲ ኦዲዮ፦ <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"ኤችዲ ኦዲዮ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"አጋዥ መስሚያዎች"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"መስሚያ አጋዥ መሣሪያዎች"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ኦዲዮ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ከአጋዥ መስሚያዎች ጋር ተገናኝቷል"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"ከመስሚያ አጋዥ መሣሪያዎች ጋር ተገናኝቷል"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"ከLE ኦዲዮ ጋር ተገናኝቷል"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"ወደ ማህደረ  መረጃ  አውዲዮ ተያይዟል"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ወደ ስልክ አውዲዮ ተያይዟል"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ለስልክ ድምፅ ተጠቀም"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ለፋይል ዝውውር ተጠቀም"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ለውፅአት ተጠቀም"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ለአጋዥ መስሚያዎች ይጠቀሙ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ለመስሚያ አጋዥ መሣሪያዎች ይጠቀሙ"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"ለLE_AUDIO ይጠቀሙ"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"አጣምር"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"አጣምር"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"የማያ ተደራቢ የአሁኑን የCPU አጠቃቀም  እያሳየ ነው።"</string>
     <string name="show_touches" msgid="8437666942161289025">"ነካ ማድረጎችን አሳይ"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ለነካ ማድረጎች ምስላዊ ግብረመልስን አሳይ"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"የቁልፍ ጭነቶችን አሳይ"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ለአካላዊ የቁልፍ ጭነቶች የሚታይ ግብረመልስን አሳይ"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"የወለል ዝማኔዎችን አሳይ"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"የመስኮት ወለሎች ሲዘምኑ መላ መስኮቱን አብለጭልጭ"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"የእይታ ዝማኔዎችን አሳይ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 39cb7d7..698e4b6 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"‏الوصول إلى شريحة SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"صوت عالي الدقة: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"صوت عالي الدقة"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"سماعات الأذن الطبية"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"سماعات الأذن الطبية"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"تمّ التوصيل بسماعات الأذن الطبية"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"تم توصيل سماعات الأذن الطبية"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"‏متصل بـ LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"متصل بالإعدادات الصوتية للوسائط"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"متصل بالإعدادات الصوتية للهاتف"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"الاستخدام لإعدادات الهاتف الصوتية"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"استخدامه لنقل الملفات"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"استخدام للإدخال"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"استخدام سماعات الأذن الطبية"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"استخدام سماعات الأذن الطبية"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"‏الاستخدام لـ LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"إقران"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"إقران"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"عرض بيانات اللمس الحالية فوق المحتوى على الشاشة"</string>
     <string name="show_touches" msgid="8437666942161289025">"عرض النقرات"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"عرض التعليقات المرئية للنقرات"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"عرض الضغطات على المفاتيح"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"عرض الملاحظات المرئية للضغطات الفعلية على المفاتيح"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"عرض تحديثات السطح"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"وميض أسطح النوافذ بالكامل عندما يتم تحديثها"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"إظهار تحديثات العرض"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 725a057..c1128ae 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ছিমৰ এক্সেছ"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"এইচ্ছডি অডি\'অ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"এইচ্ছডি অডিঅ’"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"শ্ৰৱণ যন্ত্ৰ"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"শ্ৰৱণ যন্ত্ৰ"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE অডিঅ’"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"শ্ৰৱণ যন্ত্ৰলৈ সংযোগ কৰা হৈছে"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"শ্ৰৱণ যন্ত্ৰৰ সৈতে সংযোগ কৰা হৈছে"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE অডিঅ’ৰ সৈতে সংযোগ কৰক"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"মিডিয়া অডিঅ’লৈ সংযোগ হৈছে"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ফ’ন অডিঅ\'ৰ লগত সংযোগ কৰা হ’ল"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ফ\'ন অডিঅ\'ৰ বাবে ব্যৱহাৰ কৰক"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ফাইল স্থানান্তৰ কৰিবলৈ ব্যৱহাৰ কৰক"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ইনপুটৰ বাবে ব্যৱহাৰ কৰক"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"শ্ৰৱণ যন্ত্ৰৰ বাবে ব্যৱহাৰ কৰক"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"শ্ৰৱণ যন্ত্ৰৰ বাবে ব্যৱহাৰ কৰক"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIOৰ বাবে ব্যৱহাৰ কৰক"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"পেয়াৰ কৰক"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"পেয়াৰ কৰক"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"চলিত স্পৰ্শ-বিষয়ক তথ্যসহ স্ক্ৰীন অভাৰলে’"</string>
     <string name="show_touches" msgid="8437666942161289025">"টেপসমূহ দেখুৱাওক"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"টিপিলে দৃশ্যায়িত ফীডবেক দিয়ক"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"কী টিপা দেখুৱাওক"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ভৌতিক কী টিপাৰ ভিজুৱেল প্ৰতিক্ৰিয়া দেখুৱাওক"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"পৃষ্ঠভাগৰ আপডে’ট দেখুৱাওক"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"আপডে’ট হওতে গোটেই ৱিণ্ড পৃষ্ঠসমূহ ফ্লাশ্ব কৰক"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"ভিউৰ আপডে’ট দেখুৱাওক"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index ec1b8cf..57b4694 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM-karta giriş"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Eşitmə cihazları"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Eşitmə aparatları"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Eşitmə Aparatlarına qoşuldu"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Eşitmə aparatlarına qoşuldu"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE audiosuna qoşulub"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Media audioya birləşdirilib"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Telefon audiosuna qoşulu"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Telefon audiosu istifadə edin"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Fayl transferi üçün istifadə edin"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Daxiletmə üçün istifadə edin"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Eşitmə Aparatları üçün istifadə edin"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Eşitmə aparatları üçün istifadə edin"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO üçün istifadə edin"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Qoşulsun"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"QOŞULSUN"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Toxunuş və jest datası göstərilsin"</string>
     <string name="show_touches" msgid="8437666942161289025">"Vizual reaksiya"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Toxunuşa vizual reaksiya verilsin"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Düyməyə basma göstərilsin"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Fiziki düymə basılmaları üçün vizual rəy göstərin"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Səth yenilənməsi göstərilsin"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Pəncərə səthi təzələnəndə işıqlansın"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Baxış yenilənməsi göstərilsin"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 91ca8a6..e9e0e90 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Pristup SIM kartici"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD zvuk: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD zvuk"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Slušni aparati"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Slušni aparati"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Povezano sa slušnim aparatima"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Povezano sa slušnim aparatima"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Povezano sa LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Povezano sa zvukom medija"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Povezano sa zvukom telefona"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Korišćenje za audio telefona"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Korišćenje za prenos datoteka"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Koristi za ulaz"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Koristi za slušne aparate"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Koristi za slušne aparate"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Koristite za LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Upari"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"UPARI"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Preklopni element sa trenutnim podacima o dodiru"</string>
     <string name="show_touches" msgid="8437666942161289025">"Prikazuj dodire"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Prikazuje vizuelne povratne informacije za dodire"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Prikazuj pritiske tastera"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Prikazuje povratne informacije za pritiske tastera"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Prikaži ažuriranja površine"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Osvetljava sve površine prozora kada se ažuriraju"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Prikaži ažuriranja prikaza"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 71aac42..0f6112c 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Доступ да SIM-карты"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Аўдыя ў HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Аўдыя ў HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слыхавыя апараты"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Слыхавыя апараты"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Падключана да слыхавых апаратаў"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Слыхавыя апараты падключаны"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Падключана да LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Падключана да аўдыё медыа"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Падключана да аўдыё тэлефона"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Выкарыстоўваць для аўдыё тэлефона"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Выкарыстоўваць для перадачы файлаў"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Выкарыстоўваць для ўводу"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Выкарыстоўваць для слыхавых апаратаў"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Выкарыстоўваць для слыхавых апаратаў"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Выкарыстоўваць для LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Спалучыць"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"СПАЛУЧЫЦЬ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Паказваць на экране націсканні і жэсты"</string>
     <string name="show_touches" msgid="8437666942161289025">"Паказваць дакрананні"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Паказваць візуалізацыю дакрананняў"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Паказваць націсканні"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Паказваць візуальны водгук пры націсканні клавіш"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Абнаўленне паверхні"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Падсвяціць паверхню акна пры абнаўленні"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Паказаць абнаўленні"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 2779689..abd90d0 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Достъп до SIM картата"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Висококачествено аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Висококачествено аудио"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слухови апарати"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Слухови апарати"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Установена е връзка със слухов апарат"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Установена е връзка със слухов апарат"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Свързано с LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Установена е връзка с медийно аудио"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Връзка със звука на телефона"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Използване на телефон за аудио"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Използване на за пренос на файлове"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Да се използва за въвеждане"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Използване за слухови апарати"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Използване за слухови апарати"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Използване за LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Сдвояване"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"СДВОЯВАНЕ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Насл. на екран показва текущи данни при докосване"</string>
     <string name="show_touches" msgid="8437666942161289025">"Показване на докосванията"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Показване на визуална обр. връзка за докосванията"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Видими натиск. на клавиши"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Визуална обратна връзка при натискане на клавиши"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Актуализации на повърхн."</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Примигв. на целите повърхности на прозорците при актуализирането им"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Актуализации на изгледите"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 68283ca..b037b13 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"সিম অ্যাক্সেস"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD অডিও: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD অডিও"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"হিয়ারিং এড"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"হিয়ারিং এড"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE অডিও"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"হিয়ারিং এডের সাথে কানেক্ট করা হয়েছে"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"হিয়ারিং এডের সাথে কানেক্ট করা হয়েছে"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE অডিও কানেক্ট করা হয়েছে"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"মিডিয়া অডিওতে কানেক্ট রয়েছে"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ফোন অডিওতে কানেক্ট"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ফোন অডিওয়ের জন্য ব্যবহার করুন"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ফাইল স্থানান্তরের জন্য ব্যবহার করুন"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ইনপুটের জন্য ব্যবহার করুন"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"হিয়ারিং এডের জন্য ব্যবহার করুন"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"হিয়ারিং এডের জন্য ব্যবহার করুন"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO-এর জন্য ব্যবহার করুন"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"যুক্ত করুন"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"যুক্ত করুন"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"স্ক্রিন ওভারলে বর্তমান স্পর্শ ডেটা দেখাচ্ছে"</string>
     <string name="show_touches" msgid="8437666942161289025">"আলতো চাপ দেখান"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"আলতো চাপ দিলে ভিজ্যুয়াল প্রতিক্রিয়া দেখান"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"প্রেস করা কী দেখুন"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"প্রেস করা ফিজিকাল কীয়ের জন্য ভিস্যুয়াল মতামত দেখুন"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"সারফেস আপডেটগুলি দেখান"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"সম্পূর্ণ উইন্ডোর সারফেস আপডেট হয়ে গেলে সেটিকে ফ্ল্যাশ করুন"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"ভিউয়ের আপডেট দেখুন"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 8e589f6..1a37a3f 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Pristup SIM-u"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Slušni aparati"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Slušni aparati"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Povezan na slušne aparate"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Povezano je sa slušnim aparatima"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Povezano s LE zvukom"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Povezano sa zvukom medija"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Povezano na zvuk telefona"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Koristi za zvuk telefona"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Koristi za prijenos fajlova"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Koristi kao ulaz"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Korištenje za slušne aparate"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Korištenje za slušne aparate"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Koristi za: LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Upari"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"UPARI"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Preklapanje ekrana s trenutnim podacima o dodiru"</string>
     <string name="show_touches" msgid="8437666942161289025">"Prikaži dodire"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Prikaz vizuelnih povratnih informacija za dodire"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Prikaži pritiskanja tipki"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Prikaži vizuelne povr. inform. za pritiske tipki"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Prikaži ažuriranja za površinu"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Osvjetljava sve površine prozora kada se ažuriraju"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Prikaži ažuriranja prikaza"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index f22b489b8..233b3e1 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Accés a la SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Àudio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Àudio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Audiòfons"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Audiòfons"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"S\'ha connectat als audiòfons"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"S\'ha connectat als audiòfons"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connectat a LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connectat a l\'àudio del mitjà"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connectat a àudio del telèfon"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Utilitza-ho per a l\'àudio del telèfon"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Utilitza per a la transferència de fitxers"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Utilitza per a entrada"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Utilitza per als audiòfons"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Utilitza per als audiòfons"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Utilitza per a LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Vincula"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"VINCULA"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Superposa les dades dels tocs a la pantalla"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostra els tocs"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Mostra la ubicació visual dels tocs"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Mostra les tecles premudes"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Mostra avisos visuals en prémer tecles físiques"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Canvis de superfície"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Il·lumina superfícies de finestres en actualitzar-se"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Actualitzacions de visualitzacions"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 363c22e..41d3d52 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Přístup k SIM kartě"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD zvuk: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD zvuk"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Naslouchátka"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Naslouchátka"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Připojeno k naslouchátkům"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Připojeno k naslouchátkům"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Připojeno k LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Připojeno ke zvukovému médiu"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Připojeno k náhlavní soupravě"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Umožňuje připojení náhlavní soupravy"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Použít pro přenos souborů"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Použít pro vstup"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Použít pro naslouchátka"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Použít pro naslouchátka"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Používat pro LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Spárovat"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SPÁROVAT"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Překryvná vrstva zobrazuje aktuální data o dotycích"</string>
     <string name="show_touches" msgid="8437666942161289025">"Zobrazovat klepnutí"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Zobrazovat vizuální zpětnou vazbu pro klepnutí"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Zobrazit stisknutí klávesy"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Zobrazit vizuální odezvu při stisknutí fyzické klávesy"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Zobrazit obnovení obsahu"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Rozbliká obsah okna při aktualizaci"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ukazovat aktualizace zobrazení"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 52d7040..43a1d98 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Adgang til SIM-kort"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-lyd: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-lyd"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Høreapparater"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Høreapparater"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE-lyd"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Forbundet til høreapparater"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Forbundet til høreapparater"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Forbundet med LE-lyd"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Forbundet til medielyd"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Forbundet til telefonlyd"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Brug til telefonlyd"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Brug til filoverførsel"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Brug til input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Brug til høreapparater"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Brug til høreapparater"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Brug med LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Par"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ACCEPTÉR PARRING"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Skærmoverlejringen viser de aktuelle berøringsdata"</string>
     <string name="show_touches" msgid="8437666942161289025">"Vis tryk"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Vis visuel feedback ved tryk"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Vis tastetryk"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Vis visuel feedback ved fysiske tastetryk"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Vis overfladeopdateringer"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Fremhæv hele vinduesoverflader, når de opdateres"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Se visningsopdateringer"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index bf5a8b7..e9d885a 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Zugriff auf SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-Audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-Audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hörgeräte"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hörgerät"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Mit Hörgeräten verbunden"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Mit Hörgerät verbunden"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Mit LE Audio verbunden"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Verbunden mit Medien-Audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Verbunden mit Telefon-Audio"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Für Telefon-Audio verwenden"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Für Dateiübertragung verwenden"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Für Eingabe verwenden"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Für Hörgeräte verwenden"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Für Hörgerät verwenden"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Für LE_AUDIO verwenden"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Koppeln"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"KOPPELN"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Overlay mit aktuellen Daten zu Tippaktionen anzeigen"</string>
     <string name="show_touches" msgid="8437666942161289025">"Fingertippen visualisieren"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Bei Fingertippen visuelles Feedback anzeigen"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Tastatureingaben anzeigen"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Visuelles Feedback für Tastatureingaben anzeigen"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Oberflächenaktualisierungen hervorheben"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Gesamte Fensteroberflächen blinken bei Aktualisierung"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Aktualisierungen von Ansichten hervorheben"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index abae2ad..114742a 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Πρόσβαση SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Ήχος HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Ήχος HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Βοηθήματα ακοής"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Βοηθήματα ακοής"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Έγινε σύνδεση σε βοηθήματα ακοής"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Σε σύνδεση με βοηθήματα ακοής"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Συνδέθηκε σε LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Συνδέθηκε σε ήχο πολυμέσων"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Συνδεδεμένο στον ήχο τηλεφώνου"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Χρήση για ήχο τηλεφώνου"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Χρήση για τη μεταφορά αρχείων"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Χρήση για είσοδο"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Χρήση για βοηθήματα ακοής"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Χρήση για βοηθήματα ακοής"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Χρήση για LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Σύζευξη"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ΣΥΖΕΥΞΗ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Επικάλ.οθόνης για προβολή τρεχόντων δεδ/νων αφής"</string>
     <string name="show_touches" msgid="8437666942161289025">"Εμφάνιση πατημάτων"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Εμφάνιση οπτικών σχολίων για πατήματα"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Εμφάν. πατημάτων πλήκτρων"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Εμφ. οπτικής ανάδρασης για πατήμ. φυσικών πλήκτρων"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Εμφάνιση ενημερώσεων επιφάνειας"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Προβολή Flash ολόκλ. των επιφ παραθ. όταν ενημερ."</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Εμφάνιση ενημερ. προβολής"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 3a50fa3..58ea681 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM access"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hearing aids"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connected to Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Connected to hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Use for phone audio"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Use for file transfer"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Use for input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Use for Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Use for hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Use for LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Pair"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PAIR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Screen overlay showing current touch data"</string>
     <string name="show_touches" msgid="8437666942161289025">"Show taps"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Show visual feedback for taps"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Show key presses"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Show visual feedback for physical key presses"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Show surface updates"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Flash entire window surfaces when they update"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Show view updates"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index b5aca9a..6813500 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM Access"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hearing aids"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connected to Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Connected to hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Use for phone audio"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Use for file transfer"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Use for input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Use for Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Use for hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Use for LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Pair"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PAIR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Screen overlay showing current touch data"</string>
     <string name="show_touches" msgid="8437666942161289025">"Show taps"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Show visual feedback for taps"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Show key presses"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Show visual feedback for physical key presses"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Show surface updates"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Flash entire window surfaces when they update"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Show view updates"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 3a50fa3..58ea681 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM access"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hearing aids"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connected to Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Connected to hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Use for phone audio"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Use for file transfer"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Use for input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Use for Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Use for hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Use for LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Pair"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PAIR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Screen overlay showing current touch data"</string>
     <string name="show_touches" msgid="8437666942161289025">"Show taps"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Show visual feedback for taps"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Show key presses"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Show visual feedback for physical key presses"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Show surface updates"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Flash entire window surfaces when they update"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Show view updates"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 3a50fa3..58ea681 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM access"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hearing aids"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connected to Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Connected to hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Use for phone audio"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Use for file transfer"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Use for input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Use for Hearing Aids"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Use for hearing aids"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Use for LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Pair"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PAIR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Screen overlay showing current touch data"</string>
     <string name="show_touches" msgid="8437666942161289025">"Show taps"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Show visual feedback for taps"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Show key presses"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Show visual feedback for physical key presses"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Show surface updates"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Flash entire window surfaces when they update"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Show view updates"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 9ad8a39..d69cda5 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‎‏‎‎SIM Access‎‏‎‎‏‎"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‎‎‎‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎HD audio: ‎‏‎‎‏‏‎<xliff:g id="CODEC_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎‎‏‏‎‏‎‏‏‎‎‏‎‎‏‎‎‏‎‏‎‎‏‏‎‏‏‎‎‎‎‎‏‏‎‎HD audio‎‏‎‎‏‎"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‎‏‎‎‏‏‎‏‏‎‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‎‏‎‎‏‏‎‏‎‎‏‎‎‏‎‎Hearing Aids‎‏‎‎‏‎"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‎‎‎‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‏‎‏‏‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‏‎‎‎Hearing aids‎‏‎‎‏‎"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‎‏‎‎‎‏‎‎‎‏‏‏‎‎‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‎LE Audio‎‏‎‎‏‎"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‏‏‎‏‎‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‎‏‏‎‎‏‏‏‏‏‏‏‏‎‎Connected to Hearing Aids‎‏‎‎‏‎"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‏‏‎‎‏‎‎‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‎‏‏‎‎‎Connected to hearing aids‎‏‎‎‏‎"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‏‏‏‏‎‎‏‏‎‎‏‎‏‎‎Connected to LE audio‎‏‎‎‏‎"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‏‏‏‏‏‎‏‎‎‎‏‎‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‏‎‎‏‎Connected to media audio‎‏‎‎‏‎"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‏‏‎‎‏‏‏‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎Connected to phone audio‎‏‎‎‏‎"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‏‎‎‏‎‎‎‏‏‏‎‎‎‎‏‎‏‏‎‏‏‎‏‎‏‎‏‎‎Use for phone audio‎‏‎‎‏‎"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‏‎‏‏‎‏‎‎‏‏‎‎‎‏‎‎‎‎‎‏‎‏‎‎‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‏‎Use for file transfer‎‏‎‎‏‎"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‎‏‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‎‎‏‏‏‎‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎Use for input‎‏‎‎‏‎"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‏‏‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‎‎‎‎‎‎‎‎‎‏‎‎‏‏‎‏‎‏‎‎‎‏‏‎Use for Hearing Aids‎‏‎‎‏‎"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‏‎‏‏‏‎‎‏‎‎‏‎‏‎‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎Use for hearing aids‎‏‎‎‏‎"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‎‏‏‏‎‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‎‎‎Use for LE_AUDIO‎‏‎‎‏‎"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‎‎‏‎‎‎‎‏‏‎‎‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‏‎‏‎‎‎Pair‎‏‎‎‏‎"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‎‎‎PAIR‎‏‎‎‏‎"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‏‏‎‎‏‎‎‏‎‎‎‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‎Screen overlay showing current touch data‎‏‎‎‏‎"</string>
     <string name="show_touches" msgid="8437666942161289025">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‎‏‎‎‎‏‎‏‎‎‏‎‎‏‎‏‎‎‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎Show taps‎‏‎‎‏‎"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‏‏‎‎‏‎‎‎‎‏‎‎‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎Show visual feedback for taps‎‏‎‎‏‎"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‎‎‎‎‏‎‎‎‏‏‎‏‎‎‎‎‎‏‏‏‎‏‏‎‎‎‏‏‎‏‏‎‎Show key presses‎‏‎‎‏‎"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‎‎‏‏‎‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‎‎‏‎‎‏‏‏‏‏‎‎‎‎‎‏‎‏‏‏‏‏‏‎‎‎‎‎Show visual feedback for physical key presses‎‏‎‎‏‎"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‎‎‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎‏‎‏‏‎‏‏‎‎‎‎‏‎‎‎‎‎‎‏‏‎‎Show surface updates‎‏‎‎‏‎"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‏‏‎‏‎‏‏‎‏‏‏‏‎‏‏‏‎‎Flash entire window surfaces when they update‎‏‎‎‏‎"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‏‎‎‎‎‏‏‎‏‎‎‎‏‏‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‏‎‏‏‎‎‏‎‎Show view updates‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 7ac4ab0..39fd341 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Acceso a SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio en HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio en HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Audífonos"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Audífonos"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"audio de bajo consumo"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectado a audífonos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Conectado a audífonos"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Conectado a audio de bajo consumo"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectado al audio multimedia"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectado al audio del dispositivo"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Utilizar para el audio del dispositivo"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Utilizar para la transferencia de archivos"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Utilizar para entrada"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Usar para audífonos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Usar para audífonos"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Usar para LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Vincular"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SINCRONIZAR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Muestra los datos táctiles en la pantalla"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostrar presiones"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Muestra la ubicación de las presiones en la pantalla"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Ver pulsaciones de teclas"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Ver coment. visual para pulsac. de teclas físicas"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Ver actualiz. de superficie"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Destello en superficie por actualización"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Mostrar cambios de vista"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 6ffed25..df256e5 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Acceso a tarjeta SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Audífonos"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Audífonos"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectado a audífonos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Conectado a audífonos"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Conectado a LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectado al audio del medio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectado al audio del teléfono"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Utilizar para audio del teléfono"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Uso de la transferencia de archivos"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Usar para entrada"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Usar con audífonos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Usar con audífonos"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Usar en LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Emparejar"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"EMPAREJAR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Superpone los datos de las pulsaciones en la pantalla"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostrar toques"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Muestra la ubicación de los toques en la pantalla"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Ver pulsación de teclas"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Ver respuestas visuales al pulsar teclas físicas"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Mostrar cambios de superficies"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Hace parpadear todas las superficies de la ventana cuando se actualizan"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ver actualizaciones de vista"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 434e7d7..be847b3 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Juurdepääs SIM-ile"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-heli: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-heli"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Kuuldeaparaadid"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Kuuldeaparaadid"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Kuuldeaparaatidega ühendatud"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Kuuldeaparaatidega ühendatud"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Ühendatud üksusega LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Ühendatud meediumiheliga"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Ühendatud telefoniheliga"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Kasuta telefoniheli jaoks"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Kasutage failide edastamiseks"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Kasutage sisendi jaoks"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Kasuta kuulmisaparaatide puhul"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Kasutatakse kuuldeaparaatide puhul"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Üksuse LE_AUDIO kasutamine"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Seo"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SEO"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Praegusi puuteandmeid kuvav ekraani ülekate"</string>
     <string name="show_touches" msgid="8437666942161289025">"Kuva puudutused"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Kuvab puudutuste visuaalse tagasiside"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Kuva klahvivajutused"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Kuva visuaalset tagasisidet füüsiliste klahvivajutuste kohta"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Näita pinna värskendusi"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Akna pinna värskendamiseks kirjuta kogu akna pind üle"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Kuva ekraanikuva värskendusi"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 591d3cf..93abed2 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIMerako sarbidea"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Kalitate handiko audioa: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Kalitate handiko audioa"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Audifonoak"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Audifonoak"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"Kontsumo txikiko Bluetooth bidezko audioa"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Audifonoetara konektatuta"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Audifonoetara konektatuta"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE audio-ra konektatuta"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Euskarriaren audiora konektatuta"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Telefonoaren audiora konektatuta"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Erabili telefonoaren audiorako"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Erabili fitxategi-transferentziarako"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Erabili idazketarako"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Erabili audifonoak"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Erabili audifonoekin"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Erabili LE_AUDIO-rako"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Parekatu"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PAREKATU"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Ukipen-datuak erakusteko pantaila-gainjartzea"</string>
     <string name="show_touches" msgid="8437666942161289025">"Erakutsi sakatutakoa"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Erakutsi sakatutako elementuak"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Erakutsi tekla-sakatzeak"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Erakutsi sakatutako tekla fisikoak"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Erakutsi azaleko aldaketak"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Distirarazi leiho osoen azalak haiek eguneratzean"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Erakutsi ikuspegi-aldaketak"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 15e0968..9f33fa4 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"دسترسی سیم‌کارت"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"‏صدای HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"‏صدای HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"سمعک"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"سمعک"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"صدای کم‌مصرف"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"به سمعک متصل شد"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"متصل به سمعک"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"به «صدای کم‌مصرف» وصل است"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"به رسانه صوتی متصل شد"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"به تلفن صوتی متصل شد"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"استفاده برای تلفن صوتی"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"استفاده برای انتقال فایل"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"استفاده برای چاپ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"استفاده کردن برای سمعک"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"استفاده برای سمعک"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"‏استفاده برای LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"مرتبط‌سازی"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"مرتبط‌سازی"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"هم‌پوشانی صفحه‌نمایش با نمایش داده لمسی فعلی"</string>
     <string name="show_touches" msgid="8437666942161289025">"نمایش ضربه‌ها"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"نمایش بازخورد تصویری برای ضربه‌ها"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"نمایش فشار کلیدها"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"نمایش بازخورد بصری برای فشار دادن کلیدهای فیزیکی"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"نمایش به‌روزرسانی سطح"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"هنگام به‌روزرسانی سطوح پنجره همه فلش شوند"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"نمایش به‌روزرسانی‌های نما"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index b3a7560..6ac22b2 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM-kortin käyttö"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-ääni: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-ääni"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Kuulolaitteet"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Kuulolaitteet"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Yhdistetty kuulolaitteisiin"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Yhdistetty kuulolaitteisiin"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE Audio yhdistetty"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Yhdistetty median ääneen"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Yhdistetty puhelimen ääneen"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Käytä puhelimen äänille"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Käytä tiedostojen siirtoon"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Käytä syöttöön"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Käytä kuulolaitteilla"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Käytä kuulolaitteilla"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Käyttö: LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Muodosta laitepari"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"MUODOSTA LAITEPARI"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Näytön peittokuva näyttää nykyiset kosketustiedot"</string>
     <string name="show_touches" msgid="8437666942161289025">"Näytä kosketus"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Anna visuaalista palautetta kosketuksesta"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Näytä näppäinpainallukset"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Näytä visuaalista palautetta näppäinpainalluksista"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Näytä pintapäivitykset"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Väläytä koko ikkunoiden pinnat päivitettäessä"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Näytä näyttöpäivitykset"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 930a373..11afccb 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Accès à la carte SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD : <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Prothèses auditives"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Prothèses auditives"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connecté aux prothèses auditives"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Connecté aux prothèses auditives"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connecté par LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connecté aux paramètres audio du média"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connecté à l\'audio du téléphone"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Utiliser pour les paramètres audio du téléphone"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Utiliser pour le transfert de fichiers"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Utiliser comme entrée"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Utiliser avec les prothèses auditives"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Utiliser avec les prothèses auditives"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Utiliser pour LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Associer"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ASSOCIER"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Superposition écran indiquant données actuelles"</string>
     <string name="show_touches" msgid="8437666942161289025">"Afficher éléments sélect."</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Afficher repère visuel pour éléments sélectionnés"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Afficher press. de touches"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Afficher retour visuel pour press. de touches phys."</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Afficher mises à jour surface"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Faire clignoter les surfaces à chaque mise à jour"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Afficher m. à j. affichage"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 56afbec..c54e8a9 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Accès à la SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD : <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Appareils auditifs"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Appareils auditifs"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connexion établie avec les appareils auditifs"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Connexion établie avec les appareils auditifs"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connecté à LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connecté aux paramètres audio du média"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connecté aux paramètres audio du téléphone"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Utiliser pour les paramètres audio du téléphone"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Utiliser pour le transfert de fichiers"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Utiliser comme entrée"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Utiliser pour les appareils auditifs"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Utiliser pour les appareils auditifs"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Utiliser pour LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Associer"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ASSOCIER"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Superposition à l\'écran indiquant l\'emplacement actuel du curseur"</string>
     <string name="show_touches" msgid="8437666942161289025">"Indicateurs visuels"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Afficher un indicateur visuel là où l\'utilisateur appuie sur l\'écran"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Afficher appuis touche"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Afficher retour visuel pour appuis de touches"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Mises à jour de la surface"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Faire clignoter les endroits où des mises à jour sont effectuées"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Mises à jour de fenêtres"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index dbc0ba61..0661d7a 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Acceso á SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio en HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio en HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Audiófonos"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Audiófonos"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"Audio de baixo consumo"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectado a audiófonos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Conectado a audiófonos"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Estableceuse conexión co audio de baixo consumo"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectado ao audio multimedia"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectado ao audio do teléfono"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Utilízase para o audio do teléfono"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Utilízase para a transferencia de ficheiros"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Utilízase para a entrada"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Utilizar para audiófonos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Úsase para audiófonos"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Usa esta opción para LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Vincular"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"VINCULAR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Superpón os datos dos toques na pantalla"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostrar toques"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Mostra a localización dos toques na pantalla"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Mostrar teclas premidas"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Mostra información das teclas físicas premidas"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Mostrar cambios de superficie"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Ilumina as superficies de ventás ao actualizarse"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Mostrar actualizacións"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index ee44c0a..b92d2f2 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"સિમ ઍક્સેસ"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ઑડિયો: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ઑડિયો"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"શ્રવણ યંત્રો"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"સાંભળવામાં મદદ આપતા યંત્રો"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ઑડિયો"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"શ્રવણ યંત્રો સાથે કનેક્ટ કરેલું છે"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"સાંભળવામાં મદદ આપતા યંત્રો સાથે કનેક્ટ કરેલું છે"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ઑડિયોથી કનેક્ટેડ"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"મીડિયા ઑડિઓ સાથે કનેક્ટ કર્યુ"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ફોન ઑડિઓ સાથે કનેક્ટ થયાં"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ફોન ઑડિઓ માટે ઉપયોગ કરો"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ફાઇલ સ્થાનાંતર માટે ઉપયોગ કરો"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ઇનપુટ માટે ઉપયોગ કરો"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"શ્રવણ યંત્રો માટે ઉપયોગ કરો"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"સાંભળવામાં મદદ આપતા યંત્રો માટે ઉપયોગ કરો"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO માટે ઉપયોગ કરો"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"જોડી કરો"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"જોડી કરો"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"વર્તમાન ટચ ડેટા દર્શાવતું સ્ક્રીન ઓવરલે"</string>
     <string name="show_touches" msgid="8437666942161289025">"ટૅપ બતાવો"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ટૅપ માટે વિઝ્યુઅલ પ્રતિસાદ બતાવો"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"કી દબાવવાની ઘટના બતાવો"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"વાસ્તિવક રીતે કી દબાવવાના વિઝ્યુઅલ પ્રતિસાદ બતાવો"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"સપાટી અપડેટ બતાવો"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"તે અપડેટ થાય ત્યારે સમગ્ર વિન્ડો સપાટી ફ્લેશ કરો"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"વ્યૂના અપડેટ બતાવો"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index b97837b..bd508d9 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"सिम ऐक्सेस"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"एचडी ऑडियो: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"एचडी ऑडियो"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"कान की मशीन"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"कान की मशीनें"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"सुनने में मदद करने वाले डिवाइस से कनेक्ट है"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"कान की मशीनों के साथ कनेक्ट किया गया"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE Audio से कनेक्ट किया गया"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"मीडिया ऑडियो से कनेक्‍ट किया गया"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"फ़ोन ऑडियो से कनेक्‍ट किया गया"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"फ़ोन ऑडियो के लिए उपयोग करें"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"फ़ाइल स्‍थानांतरण के लिए उपयोग करें"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"इनपुट के लिए उपयोग करें"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"सुनने में मदद करने वाले डिवाइस के लिए इस्तेमाल करें"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"कान की मशीनों के लिए इस्तेमाल करें"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO के लिए इस्तेमाल करें"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"जोड़ें"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"जोड़ें"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"मौजूदा टच डेटा दिखाने वाला स्‍क्रीन ओवरले"</string>
     <string name="show_touches" msgid="8437666942161289025">"टैप दिखाएं"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"टैप के लिए विज़ुअल फ़ीडबैक दिखाएं"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"दबाए गए बटन दिखाएं"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"दबाए गए बटन के लिए विज़ुअल फ़ीडबैक दिखाएं"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"सर्फ़ेस अपडेट दिखाएं"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"अपडेट होने पर पूरे विंडो सर्फ़ेस फ़्लैश करें"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"जीपीयू व्यू के अपडेट दिखाएं"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 6acfb95..7a8507d 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Pristup SIM-u"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Slušni aparati"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Slušna pomagala"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Povezano sa Slušnim aparatima"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Povezano sa slušnim pomagalima"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Povezano s profilom LE_AUDIO"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Povezano s medijskim zvukom"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Povezano sa telefonskim zvukom"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Koristi za telefonski zvuk"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Koristi za prijenos datoteke"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Upotrijebi za ulaz"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Upotrijebi za Slušne aparate"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Upotrijebi za slušna pomagala"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Upotrebljavajte za LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Upari"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"UPARI"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Na zaslonu se prikazuju podaci o dodirima"</string>
     <string name="show_touches" msgid="8437666942161289025">"Prikaži dodire"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Prikazuju se vizualne povratne informacije za dodire"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Prikaži pritiske na tipke"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Prikaži vizualne povratne informacije za pritiske na tipke"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Prikaži ažuriranja površine"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Sve površine prozora bljeskaju pri ažuriranju"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Prikaži ažuriranja prikaza"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 14ecad0..a67748f 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM-elérés"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hallókészülékek"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hallókészülék"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"Alacsony energiaszintű hangátvitel"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Hallókészülékhez csatlakoztatva"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Hallókészülékhez csatlakoztatva"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Csatlakoztatva az alacsony energiaszintű hangátvitelhez"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Csatlakoztatva az eszköz hangjához"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Csatlakoztatva a telefon hangjához"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Felhasználás a telefon hangjához"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Felhasználás fájlátvitelre"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Használat beviteli eszközként"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Hallókészülékkel való használat"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Hallókészülékkel való használat"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Használat ehhez: LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Párosítás"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PÁROSÍTÁS"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"A fedvény mutatja az aktuális érintési adatokat"</string>
     <string name="show_touches" msgid="8437666942161289025">"Koppintások megjelenítése"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Koppintások vizuális visszajelzésének megjelenítése"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Gomblenyomások mutatása"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Fizikai gomblenyomások vizuális visszajelzéseinek mutatása"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Felületfrissítések megj."</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"A teljes ablakfelület villogjon frissítéskor."</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Frissítések megjelenítése"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 5083165..b72b094 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM քարտի հասանելիություն"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD աուդիո՝ <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD աուդիո"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Լսողական ապարատ"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Լսողական սարք"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Լսողական ապարատը միացված է"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Միացված է լսողական սարքին"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Միացած է LE audio-ին"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Միացված է մեդիա աուդիոյին"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Միացված է հեռախոսի ձայնային տվյալներին"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Օգտագործել հեռախոսի աուդիոյի համար"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Օգտագործել ֆայլի փոխանցման համար"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Օգտագործել ներմուծման համար"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Օգտագործել լսողական ապարատի համար"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Օգտագործել լսողական սարքի համար"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Օգտագործել LE_AUDIO-ի համար"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Զուգակցել"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"Զուգակցել"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Էկրանի վերադրումը ցույց է տալիս ընթացիկ հպման տվյալները"</string>
     <string name="show_touches" msgid="8437666942161289025">"Ցույց տալ հպումները"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Ցույց տալ հպումների տեսանելի արձագանքը"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Ցույց տալ սեղմումները"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Տեսողական արձագանք ստեղների սեղմումների համար"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Ցույց տալ մակերեսի թարմացումները"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Թարմացվելիս ընդգծել սարքաշարի ծածկույթները կանաչ գույնով"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ցուցադրել թարմացումները"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index c5e6912..07dd53a 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Akses SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Alat Bantu Dengar"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Alat bantu dengar"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Terhubung ke Alat Bantu Dengar"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Terhubung ke alat bantu dengar"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Terhubung ke LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Terhubung ke media audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Terhubung ke audio ponsel"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Gunakan untuk audio ponsel"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Gunakan untuk transfer file"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Gunakan untuk masukan"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Gunakan untuk Alat Bantu Dengar"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Gunakan untuk alat bantu dengar"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Digunakan untuk LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Sambungkan"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SAMBUNGKAN"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Overlay layar menampilkan data sentuhan saat ini"</string>
     <string name="show_touches" msgid="8437666942161289025">"Tampilkan ketukan"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Tampilkan efek visual untuk ketukan"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Tampilkan penekanan tombol"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Tampilkan respons visual untuk penekanan tombol fisik"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Tampilkan pembaruan permukaan"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Buat seluruh permukaan jendela berkedip saat diperbarui"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Tampilkan pembaruan tampilan"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index 5d18930..bb7bf70 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Aðgangur að SIM-korti"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-hljóð: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-hljóð"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Heyrnartæki"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Heyrnartæki"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE-hljóð"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Tengt við heyrnartæki"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Tengt við heyrnartæki"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Tengt við LE-hljóð"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Tengt við hljóðspilun efnis"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Tengt við hljóð símans"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Nota fyrir hljóð símans"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Nota við skráaflutning"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Nota fyrir inntak"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Nota fyrir heyrnartæki"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Nota fyrir heyrnartæki"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Nota fyrir LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Para"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PARA"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Skjáyfirlögn sem sýnir rauntímagögn um snertingar"</string>
     <string name="show_touches" msgid="8437666942161289025">"Sýna snertingar"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Sýna snertingar myndrænt"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Sýna „Ýtt á lykil“"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Sýna myndsvörun fyrir „Ýtt á raunverulegan lykil“"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Sýna yfirborðsuppfærslur"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Láta allt yfirborð glugga blikka við uppfærslu"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Sýna uppfærslur yfirlits"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 8fe33d4..79151a3 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Accesso alla SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Apparecchi acustici"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Apparecchi acustici"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Connessione con gli apparecchi acustici stabilita"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Connessione con gli apparecchi acustici stabilita"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connesso a LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Collegato ad audio media"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Collegato ad audio telefono"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Usa per audio telefono"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Usa per trasferimento file"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Utilizza per l\'input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Utilizza per gli apparecchi acustici"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Utilizza per gli apparecchi acustici"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Usa per LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Accoppia"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ACCOPPIA"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Overlay schermo che mostra i dati touch correnti"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostra tocchi"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Mostra feedback visivi per i tocchi"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Mostra pressioni tasti"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Mostra feedback visivo per pressioni tasti fisici"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Aggiornamenti superficie"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Fai lampeggiare le superfici delle finestre quando si aggiornano"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Aggiornam. visualizzazione"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 86ca7a1..ee5b81d 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"‏גישה ל-SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"‏אודיו באיכות HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"‏אודיו באיכות HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"מכשירי שמיעה"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"מכשירי שמיעה"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"מחובר אל מכשירי שמיעה"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"מחובר למכשירי השמיעה"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"‏מחובר אל LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"מחובר לאודיו של מדיה"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"מחובר לאודיו של הטלפון"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"השתמש עבור האודיו של הטלפון"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"לצורך העברת קבצים"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"שימוש כקלט"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"שימוש בשביל מכשירי שמיעה"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"שימוש למכשירי שמיעה"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"‏לשימוש עבור LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"התאמה"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"התאמה"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"שכבת-על של המסך המציגה את נתוני המגע הנוכחיים"</string>
     <string name="show_touches" msgid="8437666942161289025">"הצגת הקשות"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"הצגת משוב ויזואלי להקשות"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"הצגת לחיצות המקשים"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"הצגת משוב חזותי עבור לחיצות פיזיות על מקשים"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"הצגת עדכונים על פני השטח"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"הבהוב של כל שטחי החלון כשהם מתעדכנים"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"הצגת עדכונים של התצוגה"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 0032351..d15b4ef 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIMアクセス"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD オーディオ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD オーディオ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"補聴器"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"補聴器"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"補聴器に接続"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"補聴器に接続"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE Audio に接続"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"メディアの音声に接続"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"携帯電話の音声に接続"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"携帯電話の音声に使用"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ファイル転送に使用"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"入力に使用"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"補聴器に使用"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"補聴器に使用"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO の使用"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ペア設定する"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ペア設定する"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"現在のタップデータをオーバーレイ表示する"</string>
     <string name="show_touches" msgid="8437666942161289025">"タップを表示"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"タップを視覚表示する"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"キーの押下を表示"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"物理キーの押下に関する視覚的フィードバックを表示"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"表示面の更新を通知"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"更新時にウィンドウの表示面全体を点滅させる"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"画面の更新を表示"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 147512b..f63d4c2 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM წვდომა"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD აუდიო: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD აუდიო"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"სმენის მოწყობილობები"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"სმენის მოწყობილობები"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE-აუდიო"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"დაკავშირებულია სმენის მოწყობილობებთან"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"დაკავშირებულია სმენის მოწყობილობებთან"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"დაკავშირებულია LE აუდიოსთან"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"დაკავშირებულია აუდიო მულტიმედიურ სისტემასთან"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"დაკავშირებულია ტელეფონის აუდიო მოწყობილობასთან"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"გამოიყენეთ ტელეფონის აუდიომოწყობილობაში"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ფაილების ტრანსფერისათვის გამოყენება"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"შეტანისთვის გამოყენება"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"გამოყენება სმენის მოწყობილობებისთვის"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"გამოყენება სმენის მოწყობილობებისთვის"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"გამოიყენება შემდეგისთვის: LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"დაწყვილება"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"დაწყვილება"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"ეკრანის გადაფარვა შეხების მონაცემების ჩვენებით"</string>
     <string name="show_touches" msgid="8437666942161289025">"შეხებების ჩვენება"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"შეხებებისთვის ვიზუალური უკუკავშირის ჩვენება"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"კლავიშების დაჭერის ჩვენება"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ვიზუალური გამოხმაურების ჩვენება კლავიშის დაჭერაზე"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"ზედაპირის განახლებების ჩვენება"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"ფანჯრის მთელი ზედაპირის აციმციმება მისი განახლებისას"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"განახლებების ჩვენება"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 7df9fb3..883dea6 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM картасына кіру"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD форматты аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD форматты аудио"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Есту аппараттары"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Есту аппараттары"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Есту аппараттарына жалғанған"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Есту аппараттарына жалғанған"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE Audio-ға жалғанды."</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Медиа аудиосына жалғанған"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Телефон аудиосына қосылған"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Телефон аудиосы үшін қолдану"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Файлды жіберу үшін қолдану"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Кіріс үшін қолдану"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Есту аппараттары үшін пайдалану"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Есту аппараттары үшін пайдалану"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO үшін пайдалану"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Жұптау"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ЖҰПТАУ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Экран бетіне түртілген элемент дерегі көрсетіледі"</string>
     <string name="show_touches" msgid="8437666942161289025">"Түрту қимылын көрсету"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Түрту қимылын экраннан көрсету"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Түйменің басылуын көрсету"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Түйменің басылуын экраннан көрсету"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Бедердің жаңарғанын көрсету"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Бедері жаңарғанда, терезені түгелдей жыпылықтату"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Көріністің жаңарғанын көрсету"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 81d2669..f7a8bfd 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ការចូលដំណើរការស៊ីម"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"សំឡេងកម្រិត HD៖ <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"សំឡេងកម្រិត HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ឧបករណ៍​ជំនួយការ​ស្ដាប់"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"ឧបករណ៍ជំនួយការស្ដាប់"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"បាន​ភ្ជាប់ទៅ​ឧបករណ៍​ជំនួយការ​ស្ដាប់"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"បានភ្ជាប់ទៅ​ឧបករណ៍​ជំនួយ​ការស្ដាប់"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"បានភ្ជាប់​ទៅ LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"បា​ន​ភ្ជាប់​ទៅ​អូឌីយ៉ូ​មេឌៀ"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"តភ្ជាប់​ទៅ​អូឌីយ៉ូ​ទូរស័ព្ទ"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ប្រើ​សម្រាប់​​អូឌីយ៉ូ​ទូរស័ព្ទ"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ប្រើ​សម្រាប់​ផ្ទេរ​ឯកសារ"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ប្រើ​សម្រាប់​បញ្ចូល"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ប្រើ​សម្រាប់​ឧបករណ៍​ជំនួយការ​ស្តាប់"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ប្រើ​សម្រាប់​ឧបករណ៍​ជំនួយការ​ស្ដាប់"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"ប្រើ​សម្រាប់ LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ផ្គូផ្គង"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ផ្គូផ្គង"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"អេក្រង់​ត្រួត​គ្នា​បង្ហាញ​ទិន្នន័យ​ប៉ះ​បច្ចុប្បន្ន"</string>
     <string name="show_touches" msgid="8437666942161289025">"បង្ហាញការចុច"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"បង្ហាញដានចុច នៅពេលចុច"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"បង្ហាញការចុចគ្រាប់ចុច"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"បង្ហាញព័ត៌មានឆ្លើយតបជារូបភាពសម្រាប់ការចុចគ្រាប់ចុចរូបវន្ត"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"បង្ហាញ​បច្ចុប្បន្នភាព​ផ្ទៃ"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"ផ្ទៃ​វីនដូទាំង​មូល​បញ្ចេញពន្លឺ​នៅពេល​ធ្វើ​បច្ចុប្បន្នភាព"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"បង្ហាញ​បច្ចុប្បន្នភាពទិដ្ឋភាព"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 7a80656..818af41 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ಸಿಮ್ ಆ್ಯಕ್ಸೆಸ್"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ಆಡಿಯೋ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ಆಡಿಯೋ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ಶ್ರವಣ ಸಾಧನಗಳು"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"ಶ್ರವಣ ಸಾಧನಗಳು"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ಆಡಿಯೋ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ಶ್ರವಣ ಸಾಧನಗಳಿಗೆ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"ಶ್ರವಣ ಸಾಧನಗಳಿಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ಆಡಿಯೋಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"ಮಾಧ್ಯಮ ಆಡಿಯೋಗೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ಫೋನ್ ಆಡಿಯೋಗೆ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ಫೋನ್‌ ಆಡಿಯೋಗಾಗಿ ಬಳಕೆ"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ಫೈಲ್‌ ವರ್ಗಾವಣೆಗಾಗಿ ಬಳಸು"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ಇನ್‌ಪುಟ್‌ಗಾಗಿ ಬಳಸು"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ಶ್ರವಣ ಸಾಧನಗಳಿಗಾಗಿ ಬಳಸಿ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ಶ್ರವಣ ಸಾಧನಗಳಿಗಾಗಿ ಬಳಸಿ"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO ಗೆ ಬಳಸಿ"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ಜೋಡಿಸಿ"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ಜೋಡಿ ಮಾಡು"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"ಪ್ರಸ್ತುತ ಸ್ಪರ್ಶ ಡೇಟಾ ತೋರಿಸುವ ಪರದೆಯ ಓವರ್‌ಲೇ"</string>
     <string name="show_touches" msgid="8437666942161289025">"ಟ್ಯಾಪ್‌ಗಳನ್ನು ತೋರಿಸಿ"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ಟ್ಯಾಪ್‌ಗಳಿಗೆ ದೃಶ್ಯ ಪ್ರತಿಕ್ರಿಯೆ ತೋರಿಸು"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"ಕೀ ಪ್ರೆಸ್‌ಗಳನ್ನು ತೋರಿಸಿ"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ಭೌತಿಕ ಕೀ ಪ್ರೆಸ್‌ಗಳ ವಿಷುವಲ್ ಪ್ರತಿಕ್ರಿಯೆಗಾಗಿ ನೋಡಿ"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"ಸರ್ಫೇಸ್‌‌ ಅಪ್‌ಡೇಟ್ ತೋರಿಸಿ‌"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"ಅಪ್‌ಡೇಟ್‌ ಆಗುವಾಗ ವಿಂಡೋದ ಸರ್ಫೇಸ್‌ ಫ್ಲ್ಯಾಶ್ ಆಗುತ್ತದೆ"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"\'ಅಪ್‌ಡೇಟ್‌ಗಳನ್ನು ವೀಕ್ಷಿಸಿ\' ತೋರಿಸಿ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index ffa05cc..653ac23 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM 액세스"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD 오디오: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD 오디오"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"보청기"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"보청기"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE 오디오"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"보청기에 연결됨"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"보청기에 연결됨"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE 오디오에 연결됨"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"미디어 오디오에 연결됨"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"휴대전화 오디오에 연결됨"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"휴대전화 오디오에 사용"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"파일 전송에 사용"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"입력에 사용"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"보청기로 사용"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"보청기로 사용"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO에 사용"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"페어링"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"페어링"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"현재 터치 데이터 오버레이 표시"</string>
     <string name="show_touches" msgid="8437666942161289025">"탭한 항목 표시"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"탭한 항목에 대해 시각적인 피드백 표시"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"키 누름 표시"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"물리적 키 누름에 관한 시각적 피드백을 표시합니다."</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"표면 업데이트 표시"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"전체 창 화면이 업데이트되었을 때 플래시 처리"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"보기 업데이트 표시"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 7e7d382..c2aa652 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM картаны пайдалануу мүмкүнчүлүгү"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD форматындагы аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD форматындагы аудио"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Угуу аппараттары"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Угуу аппараттары"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Угуу аппараттарына туташып турат"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Угуу аппараттарына туташып турат"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE аудио менен туташты"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Медиа аудиого туташты"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Телефон аудиосуна туташты"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Телефон аудиосу үчүн колдонулсун"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Файл өткөрүү үчүн колдонулсун"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Киргизүү үчүн колдонулсун"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Угуу аппараттары үчүн колдонуу"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Угуу аппараттары үчүн колдонуу"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO үчүн колдонуу"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Байланыштыруу"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ЖУПТАШТЫРУУ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Басылган жерлер жана жаңсоолор экранда көрүнүп турат"</string>
     <string name="show_touches" msgid="8437666942161289025">"Басылган жерлерди көрсөтүү"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Экранда басылган жерлер көрүнүп турат"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Баскычтардын басылганын көрсөтүү"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Баскычтар басылганда визуалдык сигнал көрүнөт"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Экран жаңыруусун көрсөтүү"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Экран жаңырганда анын үстү жарык болот"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Жаңыртууларды көрсөтүү"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 28fd87e..0a429fd 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ການ​ເຂົ້າ​ເຖິງ SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"ສຽງ HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"ສຽງ HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ອຸປະກອນຊ່ວຍຟັງ"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"ເຄື່ອງຊ່ວຍຟັງ"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"ສຽງ LE"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ເຊື່ອມຕໍ່ຫາອຸປະກອນຊ່ວຍຟັງແລ້ວ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"ເຊື່ອມຕໍ່ກັບເຄື່ອງຊ່ວຍຟັງແລ້ວ"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"ເຊື່ອມຕໍ່ຫາສຽງ LE ແລ້ວ"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"ເຊື່ອມຕໍ່ກັບສື່ດ້ານສຽງແລ້ວ"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ເຊື່ອມຕໍ່ກັບສຽງໂທລະສັບແລ້ວ"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ໃຊ້ສຳລັບລະບົບສຽງຂອງໂທລະສັບ"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ໃຊ້ເພື່ອໂອນຍ້າຍໄຟລ໌"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ໃຊ້ສຳລັບການປ້ອນຂໍ້ມູນ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ໃຊ້ສຳລັບອຸປະກອນຊ່ວຍຟັງ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ໃຊ້ສຳລັບເຄື່ອງຊ່ວຍຟັງ"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"ໃຊ້ສຳລັບ LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ຈັບຄູ່"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ຈັບຄູ່"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"ການວາງຊ້ອນໜ້າຈໍກຳລັງສະແດງຂໍ້ມູນການສຳຜັດໃນປັດຈຸບັນ"</string>
     <string name="show_touches" msgid="8437666942161289025">"ສະແດງການແຕະ"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ສະແດງຄໍາຕິຊົມທາງຮູບພາບສຳລັບການແຕະ"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"ສະແດງການກົດປຸ່ມ"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ສະແດງຄຳຕິຊົມພາບສຳລັບການກົດປຸ່ມຈິງ"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"ສະແດງການອັບເດດພື້ນຜິວ"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"ກະພິບໜ້າຈໍທັງໜ້າເມື່ອມີການອັບເດດ"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"ສະແດງອັບເດດມຸມມອງ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 8246c6b..78246dc 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM prieiga"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD garsas: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD garsas"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Klausos aparatai"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Klausos aparatai"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Prisijungta prie klausos aparatų"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Prisijungta prie klausos aparatų"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Prisijungta prie „LE Audio“"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Prijungta prie medijos garso įrašo"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Prijungta prie telefono garso"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Naudoti telefono garso įrašui"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Naudoti failų perkėlimui"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Naudoti įvedant"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Naudoti su klausos aparatais"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Naudoti su klausos aparatais"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Naudoti LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Susieti"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SUSIETI"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Ekrano perdanga rodo dabartinius lietimo duomenis"</string>
     <string name="show_touches" msgid="8437666942161289025">"Rodyti palietimus"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Rodyti vaizdinius palietimų atsiliepimus"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Rodyt klavišų paspaudimus"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Rodyti fizinių klavišų paspaudimų vaizdinį atsiliepimą"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Rodyti paviršiaus naujin."</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Naujinant mirginti visus langų paviršius"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Rodyti rodinių naujinius"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 807df3a..e3fbcdf 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Piekļuve SIM kartei"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Dzirdes aparāti"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Dzirdes aparāti"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Izveidots savienojums ar dzirdes aparātiem"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Izveidots savienojums ar dzirdes aparātiem"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Izveidots savienojums ar LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Savienots ar multivides audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Savienots ar tālruņa audio"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Izmantot tālruņa skaņai"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Izmantot faila pārsūtīšanai"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Izmantot ievadei"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Izmantot dzirdes aparātiem"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Izmantot dzirdes aparātiem"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Izmantot LE_AUDIO profilam"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Izveidot pāri"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SAVIENOT PĀRĪ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Ekrāna pārklājums ar aktuāliem pieskāriena datiem"</string>
     <string name="show_touches" msgid="8437666942161289025">"Rādīt pieskārienus"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Rādīt vizuālo reakciju pēc pieskārieniem"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Rādīt taustiņu nospiešanu"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Vizuāla reakcija uz fizisku taustiņu nospiešanu"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Rādīt virsmas atjauninājumus WL: 294"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Atjaunināt visa loga virsmas, kad tās tiek atjauninātas WL: 294"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Rādīt skat. atjaunin."</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 583b069..6ba8d5c 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Пристап до SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-аудио"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слушни помагала"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Слушни помагала"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE-аудио"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Поврзано со слушни помагала"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Поврзано со слушни помагала"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Поврзано на LE-аудио"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Поврзан со аудио на медиуми"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Поврзан со аудио на телефон"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Користи за аудио на телефон"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Користи за пренос на датотеки"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Користи за внес"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Користи за слушни помагала"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Користам слушни помагала"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Користи за LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Спари"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"СПАРИ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Прекривката на екран ги покажува тековните податоци на допир"</string>
     <string name="show_touches" msgid="8437666942161289025">"Прикажувај допири"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Прикажувај визуелни повратни информации за допири"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Прикаж. притисн. копчиња"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Визуелно го прикажува притискањето на копчињата"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Прикажи ажурир. површина"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Осветли површ. на прозорци при нивно ажурирање"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Покажувај ажурирања на приказот"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 610f365..0f4d30d 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"സിം ആക്സസ്"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ഓഡിയോ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ഓഡിയോ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ശ്രവണ സഹായികൾ"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"ശ്രവണ സഹായികൾ"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ഓഡിയോ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ശ്രവണ സഹായികളിലേക്ക് കണക്‌റ്റ് ചെയ്‌തു"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"ശ്രവണ സഹായികളിലേക്ക് കണക്‌റ്റ് ചെയ്‌തു"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ഓഡിയോയിലേക്ക് കണക്‌റ്റ് ചെയ്‌തു"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"മീഡിയ ഓഡിയോയിലേക്ക് കണ‌ക്റ്റുചെയ്‌തു"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ഫോൺ ഓഡിയോയിൽ കണ‌ക്റ്റുചെ‌യ്‌തു"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ഫോൺ ഓഡിയോയ്ക്കായി ഉപയോഗിക്കുക"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ഫയൽ കൈമാറ്റത്തിനായി ഉപയോഗിക്കുന്നു"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ഇൻപുട്ടിനായി ഉപയോഗിക്കുക"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ശ്രവണ സഹായികൾക്കായി ഉപയോഗിക്കുക"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ശ്രവണ സഹായികൾക്കായി ഉപയോഗിക്കുക"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO ഉപയോഗിക്കുക"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ജോടിയാക്കുക"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ജോടിയാക്കുക"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"സ്‌ക്രീൻ ഓവർലേ നിലവിലെ ടച്ച് ഡാറ്റ ദൃശ്യമാക്കുന്നു"</string>
     <string name="show_touches" msgid="8437666942161289025">"ടാപ്പുകൾ കാണിക്കുക"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ടാപ്പുകൾക്ക് ദൃശ്യ ഫീഡ്ബാക്ക് കാണിക്കുക"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"കീ അമർത്തലുകൾ കാണിക്കുക"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ഫിസിക്കൽ കീ അമർത്തൽ വിഷ്വൽ ഫീഡ്‌ബാക്ക് കാണിക്കൂ"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"സർഫേസ് അപ്‌ഡേറ്റ് കാണിക്കൂ"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"അപ്‍ഡേറ്റ് പൂർത്തിയാകുമ്പോൾ മുഴുവൻ വിൻഡോ സർഫേസുകളും ഫ്ലാഷ് ചെയ്യുക"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"\'അപ്‌ഡേറ്റുകൾ കാണുക\' കാണിക്കുക"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 040f2c8..29fb6d4 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM Хандалт"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD аудио: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD аудио"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Сонсголын төхөөрөмж"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Сонсголын төхөөрөмж"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Аудио"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Сонсголын төхөөрөмжтэй холбосон"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Сонсголын төхөөрөмжтэй холбосон"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE аудионд холбогдсон"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Медиа аудиод холбогдсон"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Утасны аудид холбогдсон"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Утасны аудиод ашиглах"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Файл дамжуулахад ашиглах"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Оруулахад ашиглах"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Сонсголын төхөөрөмжид ашиглах"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Сонсголын төхөөрөмж болгон ашиглах"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_АУДИОНД ашиглах"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Хослуулах"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ХОСЛУУЛАХ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Дэлгэцийн давхаргаар одоогийн хүрэлтийн өгөгдлийг харуулж байна"</string>
     <string name="show_touches" msgid="8437666942161289025">"Товшилтыг харуулах"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Товшилтын визуал хариу үйлдлийг харуулах"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Түлхүүрийн даралт харуул"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Биет түлхүүрийн даралтын визуал санал хүсэлт харуул"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Гадаргын шинэчлэлтүүдийг харуулах"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Шинэчлэгдэх үед цонхны гадаргыг бүхэлд нь анивчуулах"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Шинэчлэлт харахыг харуулах"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 932ef6c..e9e0ee4 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"सिम अ‍ॅक्सेस"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ऑडिओ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ऑडिओ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"श्रवणयंत्रे"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"श्रवणयंत्रे"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ऑडिओ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"श्रवण यंत्रांशी कनेक्ट केले आहे"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"श्रवणयंत्रांशी कनेक्ट केले आहे"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ऑडिओशी कनेक्ट केले आहे"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"मीडिया ऑडिओवर कनेक्ट केले"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"फोन ऑडिओ वर कनेक्ट केले"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"फोन ऑडिओसाठी वापरा"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"फाइल स्थानांतरणासाठी वापरा"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"इनपुट साठी वापरा"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"श्रवण यंत्रांसाठी वापरा"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"श्रवणयंत्रांसाठी वापरा"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO साठी वापरले आहे"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"पेअर करा"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"पेअर करा"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"वर्तमान स्पर्श डेटा दर्शविणारे स्क्रीन ओव्हरले"</string>
     <string name="show_touches" msgid="8437666942161289025">"टॅप दाखवा"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"टॅपसाठी व्हिज्युअल फीडबॅक दाखवा"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"की प्रेस दाखवा"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"प्रत्यक्ष की प्रेससाठी व्हिज्युअल फीडबॅक दाखवा"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"सर्फेस अपडेट दाखवा"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"संपूर्ण विंडो सर्फेस अपडेट होतात तेव्हा ते फ्‍लॅश करा"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"व्‍ह्यू अपडेट दाखवा"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 460eb56..0a41c0f 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Akses SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Alat Bantu Dengar"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Alat bantu pendengaran"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Disambungkan pada Alat Bantu Dengar"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Disambungkan kepada alat bantu pendengaran"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Disambungkan kepada LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Disambungkan ke audio media"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Disambungkan ke audio telefon"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Gunakan untuk audio telefon"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Gunakan untuk pemindahan fail"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Gunakan untuk input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Gunakan untuk Alat Bantu Dengar"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Gunakan untuk alat bantu pendengaran"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Gunakan untuk LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Gandingkan"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"JADIKAN PASANGAN"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Tindihan skrin menunjukkan data sentuh semasa"</string>
     <string name="show_touches" msgid="8437666942161289025">"Tunjukkan ketikan"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Tunjukkan maklum balas visual untuk ketikan"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Tunjukkan tekanan kekunci"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Tunjukkan maklum balas visual untuk tekanan kekunci fizikal"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Tunjuk kemaskinian permukaan"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Denyar permukaan tetingkap apabila dikemas kini"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Tunjuk kemaskinian paparan"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 8458a62..7346d91 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM အသုံးပြုခြင်း"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD အသံ- <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD အသံ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"နားကြားကိရိယာ"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"နားကြားကိရိယာ"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"နားကြားကိရိယာနှင့် ချိတ်ဆက်ပြီးပါပြီ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"နားကြားကိရိယာနှင့် ချိတ်ဆက်ပြီးပါပြီ"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE အသံနှင့် ချိတ်ဆက်ထားသည်"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"မီဒီယာအသံအား ချိတ်ဆက်ရန်"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ဖုန်းအသံအား ချိတ်ဆက်ရန်"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ဖုန်းအသံအားအသုံးပြုရန်"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ဖိုင်လွဲပြောင်းရန်အတွက်အသုံးပြုရန်"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ထည့်သွင်းရန်အသုံးပြုသည်"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"နားကြားကိရိယာအတွက် အသုံးပြုသည်"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"နားကြားကိရိယာအတွက် အသုံးပြုသည်"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO အတွက် သုံးသည်"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"တွဲချိတ်ရန်"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"တွဲချိတ်ရန်"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"လက်ရှိထိတွေ့မှုဒေတာကို ဖန်သားပေါ်တွင်ထပ်၍ ပြသသည်"</string>
     <string name="show_touches" msgid="8437666942161289025">"တို့ခြင်းများကို ပြပါ"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"တို့ခြင်းများအတွက် အမြင်ဖြင့် တုံ့ပြန်မှုပြသည်"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"ကီးနှိပ်မှုများ ပြပါ"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ပကတိကီးနှိပ်မှုများအတွက် ရုပ်မြင်အကြံပြုချက် ပြပါ"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"မျက်နှာပြင်အပ်ဒိတ်ပြရန်"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"အပ်ဒိတ်လုပ်စဉ် ဝင်းဒိုးမျက်နှာပြင်တွင် အချက်ပြရန်"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"မြင်ကွင်းအပ်ဒိတ်များ ပြခြင်း"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 12a7264..598304b 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Tilgang til SIM-kortet"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-lyd: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-lyd"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Høreapparater"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Høreapparater"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE-lyd"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Koblet til høreapparater"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Koblet til høreapparater"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Koblet til LE-lyd"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Koblet til medielyd"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Koblet til telefonlyd"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Bruk for telefonlyd"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Bruk til filoverføring"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Bruk for inndata"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Bruk for høreapparater"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Bruk for høreapparater"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Bruk for LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Koble til"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"KOBLE TIL"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Skjermoverlegg viser aktuelle berøringsdata"</string>
     <string name="show_touches" msgid="8437666942161289025">"Vis trykk"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Vis visuell tilbakemelding for trykk"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Vis tastetrykk"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Vis visuell tilbakemelding for fysiske tastetrykk"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Vis overflateoppdateringer"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Fremhev hele vindusoverflater når de oppdateres"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Vis visningsoppdateringer"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 349b9d3..5d61132 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM एक्सेस"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD अडियो: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD अडियो"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"श्रवण यन्त्रहरू"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"श्रवण यन्त्रहरू"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE अडियो"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"श्रवण यन्त्रहरूमा जडान गरियो"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"श्रवण यन्त्रहरूमा जडान गरियो"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE अडियोमा कनेक्ट गरिएको छ"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"मिडिया अडियोसँग जडित"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"फोन अडियोमा जडान गरियो"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"फोन अडियोको लागि प्रयोग गर्नुहोस्"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"फाइल ट्रान्सफरका लागि प्रयोग गर्नुहोस्"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"इनपुटको लागि प्रयोग गर्नुहोस्"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"श्रवण यन्त्रहरूका लागि प्रयोग गर्नुहोस्"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"श्रवण यन्त्रहरूका लागि प्रयोग गर्नुहोस्"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO मा प्रयोग गर्नुहोस्"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"कनेक्ट गर्नुहोस्"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"जोडी"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"स्क्रिन ओभरलेले हालको टच डेटा देखाउँदै छ"</string>
     <string name="show_touches" msgid="8437666942161289025">"ट्याप देखाइयोस्"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ट्यापका लागि भिजुअल प्रतिक्रिया देखाइयोस्"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"थिचिएका कीहरू देखाइयून्"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"थिचिएका भौतिक कीसम्बन्धी भिजुअल प्रतिक्रिया देखाइयोस्"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"सर्फेस अपडेट देखाइयोस्"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"अपडेट हुँदा विन्डोका पूरै सतहमा देखाइयोस्"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"GPU भ्युको अपडेट देखाइयोस्"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 1b456b9..51fc99a2 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Sim-toegang"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hoortoestellen"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hoortoestellen"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Verbonden met hoortoestellen"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Verbonden met hoortoestellen"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Verbonden met LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Verbonden met audio van medium"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Verbonden met audio van telefoon"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Gebruiken voor audio van telefoon"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Gebruiken voor bestandsoverdracht"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Gebruiken voor invoer"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Gebruiken voor hoortoestellen"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Gebruiken voor hoortoestellen"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Gebruiken voor LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Koppelen"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"KOPPELEN"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Schermoverlay met huidige aanraakgegevens"</string>
     <string name="show_touches" msgid="8437666942161289025">"Tikken tonen"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Toon visuele feedback voor tikken"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Toetsaanslagen tonen"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Visuele feedback tonen voor fysieke toetsaanslagen"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Oppervlakupdates tonen"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Flash volledige vensteroppervlakken bij updates"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Weergave-updates tonen"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 048db0e..80945c7 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM ଆକ୍ସେସ"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ଅଡିଓ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ଅଡିଓ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ଅଡିଓ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ଶ୍ରବଣ ଯନ୍ତ୍ରକୁ ସଂଯୋଗ ହୋଇଛି"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"ଶ୍ରବଣ ଯନ୍ତ୍ର ସହ କନେକ୍ଟ କରାଯାଇଛି"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ଅଡିଓ ସହ କନେକ୍ଟ କରାଯାଇଛି"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"ମିଡିଆ ଅଡିଓ ସହ ସଂଯୁକ୍ତ"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ଫୋନ୍‌ ଅଡିଓ ସହିତ ସଂଯୁକ୍ତ"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ଫୋନ୍‌ ଅଡିଓ ପାଇଁ ବ୍ୟବହାର କର"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ଫାଇଲ୍‌ ଟ୍ରାନ୍ସଫର୍‌ ପାଇଁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ଇନ୍‌ପୁଟ୍‌ ପାଇଁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ଶ୍ରବଣ ଯନ୍ତ୍ର ପାଇଁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ଶ୍ରବଣ ଯନ୍ତ୍ର ପାଇଁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO ପାଇଁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ପେୟାର୍‌"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ପେୟାର୍‌"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"ଏବେର ଟଚ୍‌ ଡାଟା ଦେଖାଉଥିବା ସ୍କ୍ରୀନ୍‌ ଓଭର୍‌ଲେ"</string>
     <string name="show_touches" msgid="8437666942161289025">"ଟାପ୍‌ ଦେଖାନ୍ତୁ"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ଟାପ୍ ପାଇଁ ଭିଜୁଆଲ୍ ମତାମତ ଦେଖାନ୍ତୁ"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"ଦବାଯାଇଥିବା କୀ ଦେଖାନ୍ତୁ"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ଦବାଯାଇଥିବା ଫିଜିକାଲ କୀ ପାଇଁ ଭିଜୁଆଲ ମତାମତ ଦେଖାନ୍ତୁ"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"ସର୍ଫେସ୍‌ ଅପଡେଟ୍‌ ଦେଖାନ୍ତୁ"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"ସମଗ୍ର ୱିଣ୍ଡୋ ପୃଷ୍ଠ ଅପଡେଟ୍‌ ହେବା ବେଳେ ସେଗୁଡ଼ିକ ଫ୍ଲାସ୍‌ କରନ୍ତୁ"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"ଭ୍ୟୁ ଅପଡେଟ୍‌ଗୁଡ଼ିକୁ ଦେଖାନ୍ତୁ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 3e20201..3c4c4bc 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"ਸਿਮ ਪਹੁੰਚ"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ਆਡੀਓ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ਆਡੀਓ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ਸੁਣਨ ਦੇ ਸਾਧਨ"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"ਸੁਣਨ ਦੇ ਸਾਧਨ"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ਆਡੀਓ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ਸੁਣਨ ਦੇ ਸਾਧਨਾਂ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"ਸੁਣਨ ਦੇ ਸਾਧਨਾਂ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ਆਡੀਓ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"ਮੀਡੀਆ  ਆਡੀਓ  ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ਫ਼ੋਨ ਔਡੀਓ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ਫ਼ੋਨ ਔਡੀਓ ਲਈ ਵਰਤੋ"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ਫਾਈਲ ਟ੍ਰਾਂਸਫਰ ਲਈ ਵਰਤੋ"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ਇਨਪੁਟ ਲਈ ਵਰਤੋ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ਸੁਣਨ ਦੇ ਸਾਧਨਾਂ ਲਈ ਵਰਤੋ"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ਸੁਣਨ ਦੇ ਸਾਧਨਾਂ ਲਈ ਵਰਤੋ"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO ਲਈ ਵਰਤੋ"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"ਜੋੜਾਬੱਧ ਕਰੋ"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ਜੋੜਾਬੱਧ ਕਰੋ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"ਸਕ੍ਰੀਨ ਓਵਰਲੇ ਮੌਜੂਦਾ ਸਪਰਸ਼ ਡਾਟਾ ਦਿਖਾ ਰਿਹਾ ਹੈ"</string>
     <string name="show_touches" msgid="8437666942161289025">"ਟੈਪਾਂ ਦਿਖਾਓ"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"ਟੈਪਾਂ ਲਈ ਦ੍ਰਿਸ਼ਟੀਗਤ ਪ੍ਰਤੀਕਰਮ ਦਿਖਾਓ"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"ਕੁੰਜੀ ਦਬਾਉਣ ਸੰਬੰਧੀ ਜਾਣਕਾਰੀ ਦਿਖਾਓ"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"ਭੌਤਿਕ ਕੁੰਜੀ ਦਬਾਉਣ ਸੰਬੰਧੀ ਦ੍ਰਿਸ਼ਟੀਗਤ ਵਿਚਾਰ ਦਿਖਾਓ"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"ਸਰਫ਼ੇਸ ਅੱਪਡੇਟ ਦਿਖਾਓ"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"ਅੱਪਡੇਟ ਹੋਣ \'ਤੇ, ਸਮੁੱਚੀਆਂ ਵਿੰਡੋ ਸਰਫ਼ੇਸਾਂ ਫਲੈਸ਼ ਕਰੋ"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"\'ਅੱਪਡੇਟ ਦੇਖੋ\' ਨੂੰ ਦਿਖਾਓ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 58a89d9..62f7e81 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Dostęp do karty SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Dźwięk HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Dźwięk HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparaty słuchowe"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Aparaty słuchowe"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Połączono z aparatami słuchowymi"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Połączono z aparatami słuchowymi"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Połączono z LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Połączono z funkcją audio multimediów"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Połączono z funkcją audio telefonu"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Użyj dla funkcji audio telefonu"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Użyj do transferu plików"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Użyj do wprowadzania"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Użyj z aparatami słuchowymi"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Używaj w przypadku aparatów słuchowych"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Używaj dla LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Sparuj"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SPARUJ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Nakładka pokazująca dane o dotknięciach ekranu"</string>
     <string name="show_touches" msgid="8437666942161289025">"Pokazuj dotknięcia"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Pokazuj potwierdzenie wizualne po dotknięciu"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Wyświetl naciśnięcia klawiszy"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Wyświetl opinie wizualne dla naciśnięć fizycznego klucza"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Pokazuj zmiany powierzchni"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Podświetlaj całe aktualizowane powierzchnie okien"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Pokazuj aktualizacje widoku"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 678c076..6880511 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Acesso ao chip"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Áudio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Áudio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparelhos auditivos"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Aparelhos auditivos"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectado a aparelhos auditivos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Conectado a aparelhos auditivos"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Conectado ao perfil Áudio de baixa energia"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectado ao áudio da mídia"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectado ao áudio do smartphone"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Usar para áudio do smartphone"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Usado para transferência de arquivo"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Usar para entrada"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Usar para aparelhos auditivos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Usar para aparelhos auditivos"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Usar para LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Parear"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PAREAR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Exibir dados de toque"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostrar toques"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Mostrar feedback visual para toques"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Mostrar teclas pressionadas"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Mostrar feedback visual de teclas pressionadas"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Mostrar superfície atualizada"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Piscar superfícies de toda a janela ao atualizar"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ver atualizações de exibição"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 807b80d..48aa735 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Acesso ao SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Áudio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Áudio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparelhos auditivos"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Aparelhos auditivos"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Ligado a aparelhos auditivos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Ligado a aparelhos auditivos"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Ligado a LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Ligado ao áudio de multimédia"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Ligado ao áudio do telefone"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Usar para áudio do telefone"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Usar para transferência de ficheiros"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Usar para entrada"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Usar para aparelhos auditivos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Usar para aparelhos auditivos"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Usar para LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Sincr."</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SINCRONIZAR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Apresentar dados atuais de toque"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostrar toques"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Mostrar feedback visual para toques"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Mostrar toques em teclas"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Mostrar feedback visual (toques em teclas físicas)"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Mostrar superfície atualizada"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Destacar a superfície da janela ao atualizar"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ver atualizações de vistas"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 678c076..6880511 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Acesso ao chip"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Áudio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Áudio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparelhos auditivos"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Aparelhos auditivos"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectado a aparelhos auditivos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Conectado a aparelhos auditivos"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Conectado ao perfil Áudio de baixa energia"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectado ao áudio da mídia"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectado ao áudio do smartphone"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Usar para áudio do smartphone"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Usado para transferência de arquivo"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Usar para entrada"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Usar para aparelhos auditivos"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Usar para aparelhos auditivos"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Usar para LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Parear"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PAREAR"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Exibir dados de toque"</string>
     <string name="show_touches" msgid="8437666942161289025">"Mostrar toques"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Mostrar feedback visual para toques"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Mostrar teclas pressionadas"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Mostrar feedback visual de teclas pressionadas"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Mostrar superfície atualizada"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Piscar superfícies de toda a janela ao atualizar"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ver atualizações de exibição"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 7fa228b..1dce0e1 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Acces la SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparate auditive"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Aparate auditive"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Conectat la aparatul auditiv"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Conectat la aparatul auditiv"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Conectat la LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Conectat la profilul pentru conținut media audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Conectat la componenta audio a telefonului"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Folosește pentru componenta audio a telefonului"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Folosește pentru transferul de fișiere"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Folosește pentru introducere date"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Folosește pentru aparatele auditive"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Folosește pentru aparatele auditive"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Folosește pentru LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Asociază"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"CONECTEAZĂ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Suprapunere care indică date curente pt. atingeri"</string>
     <string name="show_touches" msgid="8437666942161289025">"Afișează atingerile"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Afișează feedbackul vizual pentru atingeri"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Arată apăsările pe taste"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Arată feedback vizual pentru apăsările pe taste"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Actualizări suprafețe"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Iluminarea întregii fereastre la actualizare"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Afiș. actualizări ecran"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index a53b67a..79db17c 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Доступ к SIM-карте"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD Audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD Audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слуховые аппараты"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Слуховые аппараты"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Слуховой аппарат подключен"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Слуховой аппарат подключен"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Подключено к LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Подключено к мультимедийному аудиоустройству"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Подключено к аудиоустройству телефона"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Использовать для аудиоустройства телефона"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Используется для передачи файлов"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Использовать для ввода"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Использовать для слухового аппарата"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Использовать для слухового аппарата"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Использовать для LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Добавить"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ДОБАВИТЬ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Показывать данные касаний и жестов"</string>
     <string name="show_touches" msgid="8437666942161289025">"Показывать нажатия"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Визуальный отклик при нажатии"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Показывать нажатия клавиш"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Показывать визуальный отклик при нажатии клавиш"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Показ. обнов. поверхности"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Подсвечивать поверхности окон при обновлении"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Показывать обнов. экрана"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index ab5d3ce..c43f5dd 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM ප්‍රවේශය"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ශ්‍රව්‍යය: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ශ්‍රව්‍යය"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"ශ්‍රවණාධාරක"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"ශ්‍රවණාධාරක"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ශ්‍රව්‍ය"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"ශ්‍රවණාධාරක වෙත සම්බන්ධ කළා"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"ශ්‍රවණාධාරක වෙත සම්බන්ධ කළා"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ශ්‍රව්‍ය වෙත සම්බන්ධ විය"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"මාධ්‍ය ශ්‍රව්‍යට සම්බන්ධ විය"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"දුරකතනයේ ශ්‍රව්‍යට සම්බන්ධ විය"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"දුරකථන ශ්‍රව්‍ය සඳහා භාවිතා කෙරේ"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ගොනු හුවමාරුව සඳහා භාවිතා කරන්න"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ආදානය සඳහා භාවිතා කරන්න"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ශ්‍රවණාධාර සඳහා භාවිත කරන්න"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ශ්‍රවණාධාර සඳහා භාවිත කරන්න"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO සඳහා භාවිත කරන්න"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"යුගල කරන්න"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"යුගල කරන්න"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"තිර උඩැතිරිය වර්තමාන ස්පර්ශ දත්ත පෙන්වයි"</string>
     <string name="show_touches" msgid="8437666942161289025">"තට්ටු කිරීම් පෙන්වන්න"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"තට්ටු කිරීම් සඳහා දෘශ්‍ය ප්‍රතිපෝෂණ පෙන්වන්න"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"යතුරු එබීම් පෙන්වන්න"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"භෞතික යතුරු එබීම සඳහා දෘශ්‍ය ප්‍රතිපෝෂණය පෙන්වන්න"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"පෘෂ්ඨ යාවත්කාලීන පෙන්වන්න"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"යාවත්කාලින වනවිට මුළු කවුළු තලයම දැල්වෙන්න"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"නැරඹීම් යාවත්කාලීන පෙන්වන්න"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index daf9492..ec383f9 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Prístup k SIM karte"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD zvuk: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD zvuk"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Načúvadlá"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Načúvadlá"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Pripojené k načúvadlám"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Pripojené k načúvadlám"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Pripojené k systému LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Pripojené ku zvukovému médiu"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Pripojené ku zvuku telefónu"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Použiť pre zvuk telefónu"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Použiť na prenos súborov"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Použiť pre vstup"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Použiť pre načúvadlá"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Používať pre načúvadlá"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Používať s profilom LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Párovať"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PÁROVAŤ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Zobraziť prekryvnú vrstvu s aktuálnymi údajmi o klepnutiach"</string>
     <string name="show_touches" msgid="8437666942161289025">"Zobrazovať klepnutia"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Vizuálne znázorňovať klepnutia"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Zobrazovať stlač. kláves."</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Zobr. vizuálnu spätnú väzbu fyz. stlač. klávesov"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Ukazovať obnovenia obsahu"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Rozblikať obsah okna pri aktualizácii"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ukazovať obnovenia zobrazenia"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 7164da6..4d0e7a8 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Dostop do kartice SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Zvok visoke kakovosti: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Zvok visoke kakovosti"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Slušni pripomočki"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Slušni aparati"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE zvok"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Povezava s slušnimi pripomočki je vzpostavljena"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Povezava s slušnimi aparati je vzpostavljena."</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Povezano s profilom LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Povezan s profilom za predstavnostni zvok"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Povezava s profilom za zvok telefona vzpostavljena"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Uporabi za zvok telefona"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Uporabi za prenos datotek"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Uporabi za vnos"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Uporabi za slušne pripomočke"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Uporabi za slušne aparate"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Uporaba za profil LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Seznani"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SEZNANI"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Prekrivanje zaslona prikazuje trenutni dotik."</string>
     <string name="show_touches" msgid="8437666942161289025">"Prikaži dotike"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Prikaži vizualne povratne informacije za dotike."</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Pokaži pritiske tipk"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Pokaži vizualne povratne informacije za pritiske fizičnih tipk"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Pokaži posodob. površine"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Ob posodobitvi osveži celotne površine oken."</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Prikaži posodob. pogleda"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 7fd6ec9..2fdac37 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Qasje në kartën SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Audio HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Audio HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Aparatet e dëgjimit"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Aparatet e dëgjimit"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Lidhur me aparatet e dëgjimit"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Lidhur me aparatet e dëgjimit"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"U lidh me audion LE"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"U lidh me audion e medias"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"U lidh me audion e telefonit"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Përdor për audion e telefonit"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Përdor për transferimin e skedarëve"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Përdore për hyrjen"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Përdore për aparatet e dëgjimit"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Përdore për aparatet e dëgjimit"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Përdor për LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Çifto"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ÇIFTO"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Mbivendosja e ekranit tregon të dhënat e prekjes"</string>
     <string name="show_touches" msgid="8437666942161289025">"Shfaq trokitjet"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Shfaq reagimet vizuale për trokitjet"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Shfaq shtypjet e tasteve"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Shfaq reagime vizuale për shtypjen e tasteve fizike"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Shfaq përditësimet e sipërfaqes"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Ndriço të gjitha sipërfaqet e dritares kur ato të përditësohen"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Shfaq përditësimet e pamjes"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 86675cf..dc698ce 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Приступ SIM картици"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD звук: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD звук"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слушни апарати"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Слушни апарати"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Повезано са слушним апаратима"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Повезано са слушним апаратима"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Повезано са LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Повезано са звуком медија"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Повезано са звуком телефона"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Коришћење за аудио телефона"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Коришћење за пренос датотека"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Користи за улаз"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Користи за слушне апарате"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Користи за слушне апарате"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Користите за LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Упари"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"УПАРИ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Преклопни елемент са тренутним подацима о додиру"</string>
     <string name="show_touches" msgid="8437666942161289025">"Приказуј додире"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Приказује визуелне повратне информације за додире"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Приказуј притиске тастера"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Приказује повратне информације за притиске тастера"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Прикажи ажурирања површине"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Осветљава све површине прозора када се ажурирају"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Прикажи ажурирања приказа"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 0d71afd..64412dd5 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM-åtkomst"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-ljud: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-ljud"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hörapparater"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Hörapparater"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Ansluten till hörapparater"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Ansluten till hörapparater"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Ansluten till LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Ansluten till medialjud"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Ansluten till telefonens ljud"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Använd för telefonens ljud"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Använd för filöverföring"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Använd för inmatning"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Använd med hörapparater"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Använd med hörapparater"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Använd för LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Parkoppla"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"PARKOPPLA"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Överlägg på skärmen med aktuella skärmtryck"</string>
     <string name="show_touches" msgid="8437666942161289025">"Visa tryck"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Visa visuell feedback för tryck"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Visa tangenttryckningar"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Visa visuell feedback för tangenttryckningar"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Visa ytuppdateringar"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Hela fönstret blinkar vid uppdatering"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Visa visningsuppdatering"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 303131e..4893fe83 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Ufikiaji wa SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Sauti ya HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Sauti ya HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Vifaa vya Kusaidia Kusikia"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Visaidizi vya kusikia"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Imeunganishwa kwenye Vifaa vya Kusaidia Kusikia"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Imeunganishwa kwenye visaidizi vya kusikia"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Imeunganishwa kwenye LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Imeunganishwa kwenye sikika ya njia ya mawasiliano"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Imeunganishwa kwenye sauti ya simu"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Tumia kwa sauti ya simu"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Tumia kwa hali faili"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Tumia kwa kuingiza"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Tumia kwenye Vifaa vya Kusaidia Kusikia"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Tumia kwa visaidizi vya kusikia"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Tumia kwa ajili ya LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Oanisha"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"OANISHA"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Kuegeshwa kwa skrini ikionyesha data ya mguso ya sasa"</string>
     <string name="show_touches" msgid="8437666942161289025">"Onyesha unapogusa"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Onyesha ishara za kuthibitisha unapogusa"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Onyesha mibofyo ya vitufe"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Onyesha muhtasari wa mibofyo halisi ya vitufe"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Onyesha masasisho ya sehemu"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Angaza dirisha lote zitakaposasisha"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Onyesha taarifa za kuonekana"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 83aa2af..b72c533 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"சிம் அணுகல்"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ஆடியோ: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ஆடியோ"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"செவித்துணை கருவிகள்"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"செவித்துணைக் கருவிகள்"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ஆடியோ"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"செவித்துணை கருவிகளுடன் இணைக்கப்பட்டது"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"செவித்துணைக் கருவிகளுடன் இணைக்கப்பட்டது"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ஆடியோவுடன் இணைக்கப்பட்டுள்ளது"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"மீடியா ஆடியோவுடன் இணைக்கப்பட்டது"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"மொபைல் ஆடியோவுடன் இணைக்கப்பட்டது"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"மொபைல் ஆடியோவைப் பயன்படுத்து"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ஃபைல் பரிமாற்றத்திற்காகப் பயன்படுத்து"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"உள்ளீட்டுக்குப் பயன்படுத்து"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"செவித்துணை கருவிகளுக்குப் பயன்படுத்தவும்"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"செவித்துணைக் கருவிகளுக்காகப் பயன்படுத்தப்படும்"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIOவிற்குப் பயன்படுத்தும்"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"இணை"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"இணை"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"திரையின் மேல் அடுக்கானது தற்போது தொடப்பட்டிருக்கும் தரவைக் காண்பிக்கிறது"</string>
     <string name="show_touches" msgid="8437666942161289025">"தட்டல்களைக் காட்டு"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"தட்டல்களின் போது காட்சி அறிகுறிகளைக் காட்டும்"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"பட்டன் அழுத்தத்தை காட்டவா"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"பட்டன் அழுத்தங்களைக் காட்சி மூலம் உறுதிப்படுத்தும்"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"மேலோட்ட புதுப்பிப்புகளைக் காட்டு"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"சாளரத்தின் பரப்புநிலைகள் புதுப்பிக்கப்படும்போது, அவற்றை முழுவதுமாகக் காட்டும்"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"வியூ அப்டேட்ஸைக் காட்டு"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index a216c81..ae1b251 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM యాక్సెస్"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ఆడియో: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ఆడియో"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"వినికిడి మద్దతు ఉపకరణాలు"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"వినికిడి పరికరాలు"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE ఆడియో"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"వినికిడి మద్దతు ఉపకరణాలకు కనెక్ట్ చేయబడింది"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"వినికిడి పరికరాలకు కనెక్ట్ చేయబడింది"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE ఆడియోకు కనెక్ట్ చేయబడింది"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"మీడియా ఆడియోకు కనెక్ట్ చేయబడింది"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"ఫోన్ ఆడియోకు కనెక్ట్ చేయబడింది"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ఫోన్ ఆడియో కోసం ఉపయోగించండి"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ఫైల్ బదిలీ కోసం ఉపయోగించండి"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ఇన్‌పుట్ కోసం ఉపయోగించండి"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"వినికిడి మద్దతు ఉపకరణాలకు ఉపయోగించండి"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"వినికిడి పరికరాల కోసం ఉపయోగించండి"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO కోసం ఉపయోగించండి"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"జత చేయి"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"జత చేయి"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"ప్రస్తుత టచ్ డేటాను చూపుతోన్న స్క్రీన్"</string>
     <string name="show_touches" msgid="8437666942161289025">"నొక్కినవి చూపు"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"నొక్కినప్పుడు దృశ్యపరమైన ప్రతిస్పందన చూపు"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"కీ ప్రెస్‌లను చూడండి"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"భౌతిక కీ ప్రెస్‌ల విజువల్ ఫీడ్‌బ్యాక్‌ను చూడండి"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"సర్ఫేస్‌ అప్‌డేట్లను చూపు"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"విండో సర్‌ఫేస్‌లన్నీ అప్‌డేట్‌ అయితే ఫ్లాష్ చేయి"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"వీక్షణ అప్‌డేట్‌లను చూపు"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 32629c3..14ef3a3 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"การเข้าถึงซิม"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"เสียง HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"เสียง HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"เครื่องช่วยฟัง"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"เครื่องช่วยฟัง"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"เชื่อมต่อกับเครื่องช่วยฟังแล้ว"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"เชื่อมต่อกับเครื่องช่วยฟังแล้ว"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"เชื่อมต่อกับ LE Audio แล้ว"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"เชื่อมต่อกับระบบเสียงของสื่อแล้ว"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"เชื่อมต่อกับระบบเสียงของโทรศัพท์แล้ว"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"ใช้สำหรับระบบเสียงของโทรศัพท์"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"ใช้สำหรับการโอนไฟล์"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ใช้สำหรับการป้อนข้อมูล"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"ใช้สำหรับเครื่องช่วยฟัง"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"ใช้สำหรับเครื่องช่วยฟัง"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"ใช้สำหรับ LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"จับคู่"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"จับคู่อุปกรณ์"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"การวางซ้อนหน้าจอที่แสดงข้อมูลการแตะในปัจจุบัน"</string>
     <string name="show_touches" msgid="8437666942161289025">"แสดงการแตะ"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"แสดงผลตอบสนองแบบภาพเมื่อแตะ"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"แสดงการกดแป้น"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"แสดงฟีดแบ็กที่เป็นภาพสำหรับการกดแป้นพิมพ์"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"แสดงการอัปเดตพื้นผิว"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"กะพริบหน้าต่างทั้งหมดเมื่อมีการอัปเดต"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"แสดงการอัปเดตมุมมอง"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index e60faf6..ab4acb6 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Access sa SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Mga Hearing Aid"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Mga hearing aid"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Nakakonekta sa Mga Hearing Aid"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Nakakonekta sa mga hearing aid"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Nakakonekta sa LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Konektado sa media audio"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Nakakonekta sa audio ng telepono"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Ginagamit para sa audio ng telepono"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Ginagamit para sa paglilipat ng file"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Gamitin para sa input"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Gamitin para sa Mga Hearing Aid"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Gamitin para sa mga hearing aid"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Gamitin para sa LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Ipares"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"IPARES"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Overlay ng screen na nagpapakita ng touch data"</string>
     <string name="show_touches" msgid="8437666942161289025">"Ipakita ang mga pag-tap"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Ipakita ang visual na feedback para sa mga pag-tap"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Ipakita ang mga key press"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Ipakita: visual feedback ng mga physical key press"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Ipakita update sa surface"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"I-flash ang buong window surface kapag nag-update"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Ipakita update ng view"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 3ea12ad..abbef53 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM Erişimi"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ses: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ses"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"İşitme Cihazları"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"İşitme cihazları"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"İşitme Cihazlarına Bağlandı"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"İşitme cihazlarına bağlandı"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE Audio\'ya bağlandı"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Medya sesine bağlanıldı"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Telefon sesine bağlandı"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Telefon sesi için kullan"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Dosya aktarımı için kullan"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Giriş için kullan"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"İşitme Cihazları için kullan"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"İşitme cihazları için kullan"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO kullan"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Eşle"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"EŞLE"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Mevcut dokunmatik verilerini gösteren yer paylaşımı"</string>
     <string name="show_touches" msgid="8437666942161289025">"Dokunmayı göster"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Dokunmalarda görsel geri bildirim göster"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Basılan tuşları göster"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Fiziksel tuşlara basınca görsel geri bildirim ver"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Yüzey güncellemelerini göster"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Güncellenirken tüm pencere yüzeylerini yakıp söndür"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Görünüm güncellemelerini göster"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 95ece26..bf297e6 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Доступ до SIM-карти"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD-аудіо: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD-аудіо"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Слухові апарати"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Слухові апарати"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Підключено до слухових апаратів"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Підключено до слухових апаратів"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Підключено до LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Підключено до аудіоджерела"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Підключено до звуку телеф."</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Викор. для звуку тел."</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Викор. для перед. файлів"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Викор. для введ."</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Використовувати для слухових апаратів"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Використовувати для слухових апаратів"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Використовувати для LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Підключити"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ПІДКЛЮЧИТИСЯ"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Показувати на екрані жести й натискання"</string>
     <string name="show_touches" msgid="8437666942161289025">"Показувати дотики"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Показувати візуальну реакцію на торкання"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Показувати натиск. клавіш"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Показувати візуальний відгук на натискання клавіш"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Показ. оновлення поверхні"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Підсвічувати вікна повністю під час оновлення"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Показувати оновлення областей"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 70aa9a3..db2bb26 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"‏SIM رسائی"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"‏HD آڈیو: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"‏HD آڈیو"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"سماعتی آلات"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"سماعتی آلات"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"‏LE آڈیو"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"سماعتی آلات سے منسلک ہے"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"سماعتی آلات سے منسلک ہے"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"‏LE آڈیو سے منسلک ہے"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"میڈیا آڈیو سے مربوط"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"فون آڈیو سے مربوط"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"فون آڈیو کیلئے استعمال کریں"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"فائل منتقل کرنے کیلئے استعمال کریں"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ان پٹ کیلئے استعمال"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"سماعتی آلات کے لیے استعمال کریں"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"سماعتی آلات کیلئے استعمال کریں"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"‏LE_AUDIO کے لیے استعمال کریں"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"جوڑا بنائیں"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"جوڑا بنائیں"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"موجودہ ٹچ ڈیٹا دکھانے والا اسکرین اوور لے"</string>
     <string name="show_touches" msgid="8437666942161289025">"تھپتھپاہٹیں دکھائیں"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"تھپتھپاہٹوں کیلئے بصری تاثرات دکھائیں"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"\'کلید کو دبانا\' دکھائیں"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"\'طبعی کلید کو دبانا\' کیلئے ویژوئل تاثرات دکھائیں"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"سطح کے اپ ڈیٹس دکھائیں"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"اپ ڈیٹ ہونے پر ونڈو کی پوری سطحیں جھلملائیں"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"منظر کے اپ ڈیٹس دکھائیں"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 5f42969..96e5d6a 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM kartaga kirish"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Eshitish apparatlari"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Eshitish moslamalari"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Eshitish apparatlariga ulangan"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Eshitish moslamalariga ulangan"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"LE audioga ulandi"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Audio qurilmasiga ulangan"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Telefon karnayiga ulanildi"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Dok’dan karnay sifatida foydalanish"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Fayl almashinish uchun foydalanish"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Kiritish qurilmasi sifatida foydalanish"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Eshitish apparatlari uchun foydalanish"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Eshitish moslamalari uchun foydalanish"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO uchun foydalanish"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"OK"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"ULANISH"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Ekranda bosilgan va tegilgan joylarni vizuallashtirish"</string>
     <string name="show_touches" msgid="8437666942161289025">"Bosishlarni ko‘rsatish"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Ekranda bosilgan joylardagi nuqtalarni ko‘rsatish"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Tugma bosishlarini chiqarish"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Jismoniy tugmani bosishlar uchun vizual fikr-mulohazani chiqarish"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Yuza yangilanishlarini ko‘rsatish"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Yangilangandan so‘ng to‘liq oyna sirtlarini miltillatish"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Yangilash oynasini ochish"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index d75f1b2..7035077 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Truy cập SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Âm thanh HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Âm thanh HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Thiết bị trợ thính"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Thiết bị trợ thính"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"Âm thanh năng lượng thấp"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Đã kết nối với Thiết bị trợ thính"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Đã kết nối với thiết bị trợ thính"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Đã kết nối với âm thanh LE"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Đã kết nối với âm thanh nội dung nghe nhìn"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Đã kết nối với âm thanh điện thoại"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Sử dụng cho âm thanh điện thoại"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Sử dụng để chuyển tệp"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Sử dụng để nhập"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Dùng cho Thiết bị trợ thính"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Dùng cho thiết bị trợ thính"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Dùng cho LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Ghép nối"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"GHÉP NỐI"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Lớp phủ màn hình hiển thị dữ liệu chạm hiện tại"</string>
     <string name="show_touches" msgid="8437666942161289025">"Hiện số lần nhấn"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Hiển thị phản hồi trực quan cho các lần nhấn"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Hiện thao tác nhấn phím"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Hiện phản hồi trực quan cho thao tác nhấn phím vật lý"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Hiện bản cập nhật giao diện"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Chuyển nhanh toàn bộ các giao diện cửa sổ khi các giao diện này cập nhật"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Hiện bản cập nhật chế độ xem"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index cc3d1da..ab16a73 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM 卡访问权限"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD 音频:<xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD 音频"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"助听器"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"助听器"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE 音频"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"已连接到助听器"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"已连接到助听器"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"已连接到 LE 音频"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"已连接到媒体音频"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"已连接到手机音频"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"用于手机音频"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"用于文件传输"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"用于输入"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"用于助听器"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"用于助听器"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"用于 LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"配对"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"配对"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"屏幕叠加层显示当前触摸数据"</string>
     <string name="show_touches" msgid="8437666942161289025">"显示点按操作反馈"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"显示点按操作的视觉反馈"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"显示按键操作"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"显示实体按键操作的视觉反馈"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"显示面 (surface) 更新"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"窗口中的面 (surface) 更新时全部闪烁"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"显示视图更新"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index e6da4bc..9ec57a3 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM 卡存取"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"高清音訊:<xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"高清音訊"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"助聽器"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"助聽器"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"已連接助聽器"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"已連接助聽器"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"已連接 LE Audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"已連接媒體音頻裝置"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"已連接手機耳機"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"用於手機音效"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"用於傳輸檔案"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"用於輸入"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"用於助聽器"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"用於助聽器"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"用於 LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"配對"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"配對"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"在螢幕上重疊顯示目前的觸控資料"</string>
     <string name="show_touches" msgid="8437666942161289025">"顯示輕按回應"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"顯示輕按位置的視覺回應"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"顯示按鍵操作"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"顯示實際按鍵操作的視覺回應"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"顯示表層更新"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"更新表層時閃動整個視窗表層"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"顯示畫面更新"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index e33934c..59e66aa 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM 卡存取權"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD 高解析音訊:<xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD 高解析音訊"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"助聽器"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"助聽器"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"LE Audio"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"已連接到助聽器"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"已連上助聽器"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"已連上 LE audio"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"連接至媒體音訊"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"連接至電話音訊"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"用於電話音訊"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"用於傳輸檔案"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"用於輸入"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"用於助聽器"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"用於助聽器"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"用於 LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"配對"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"配對"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"在螢幕圖層上顯示目前的觸控資料"</string>
     <string name="show_touches" msgid="8437666942161289025">"顯示觸控回應"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"顯示觸控位置的視覺回應"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"顯示按鍵操作"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"顯示實際按鍵操作的視覺回饋"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"顯示表層更新"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"更新表層時閃爍顯示整個視窗表層"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"顯示畫面更新"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 5c7a7b5..d292bd5 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -114,9 +114,9 @@
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"Ukufinyelela kwe-SIM"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"Umsindo we-HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"Umsindo we-HD"</string>
-    <string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Izinsiza zokuzwa"</string>
+    <string name="bluetooth_profile_hearing_aid" msgid="2607867572569689732">"Imishini yendlebe"</string>
     <string name="bluetooth_profile_le_audio" msgid="1725521360076451751">"Umsindo we-LE"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"Kuxhumeke kwizinsiza zokuzwa"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_connected" msgid="5757754050938807276">"Kuxhume emishinini yendlebe"</string>
     <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Kuxhunywe kumsindo we-LE"</string>
     <string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Ixhume emsindweni wemidiya"</string>
     <string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Ixhunywe kumsindo wefoni"</string>
@@ -134,7 +134,7 @@
     <string name="bluetooth_headset_profile_summary_use_for" msgid="808970643123744170">"Sebenziselwa umsindo wefoni"</string>
     <string name="bluetooth_opp_profile_summary_use_for" msgid="461981154387015457">"Sebenziselwa ukudlulisa ifayela"</string>
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Isetshenziselwa okufakwayo"</string>
-    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"Sebenzisa izinsiza zokuzwa"</string>
+    <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Sebenzisa imishini yendlebe"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Sebenzisela i-LE_AUDIO"</string>
     <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Bhangqa"</string>
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"BHANGQA"</string>
@@ -355,6 +355,8 @@
     <string name="pointer_location_summary" msgid="957120116989798464">"Imbondela yesikrini ibonisa idatha yokuthinta yamanje"</string>
     <string name="show_touches" msgid="8437666942161289025">"Bonisa amathebhu"</string>
     <string name="show_touches_summary" msgid="3692861665994502193">"Bonisa izmpendulo ebukekayo ngamathebhu"</string>
+    <string name="show_key_presses" msgid="6360141722735900214">"Bonisa ukucindezela ukhiye"</string>
+    <string name="show_key_presses_summary" msgid="725387457373015024">"Bonisa impendulo ebonakalayo yokucindezela ukhiye obonakalayo"</string>
     <string name="show_screen_updates" msgid="2078782895825535494">"Buka izibuyekezo ezibonakalayo"</string>
     <string name="show_screen_updates_summary" msgid="2126932969682087406">"Khanyisa ukubonakala kwalo lonke iwindi uma libuyekezwa"</string>
     <string name="show_hw_screen_updates" msgid="2021286231267747506">"Bonisa izibuyekezo zokubuka"</string>
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index 91549d7..07854bd 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -124,4 +124,5 @@
     <dimen name="dialog_bottom_padding">18dp</dimen>
     <dimen name="dialog_side_padding">24dp</dimen>
     <dimen name="dialog_button_bar_top_padding">32dp</dimen>
+    <dimen name="button_corner_radius">28dp</dimen>
 </resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index b44c0e0..60bc226 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -241,12 +241,12 @@
     <!-- Bluetooth settings. Similar to bluetooth_profile_a2dp_high_quality, but used when the device supports high quality audio but we don't know which codec that will be used. -->
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec">HD audio</string>
 
-    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the Hearing Aid profile. -->
-    <string name="bluetooth_profile_hearing_aid">Hearing Aids</string>
+    <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the Hearing aid profile. -->
+    <string name="bluetooth_profile_hearing_aid">Hearing aids</string>
     <!-- Bluetooth settings.  The user-visible string that is used whenever referring to the LE audio profile. -->
     <string name="bluetooth_profile_le_audio">LE Audio</string>
-    <!-- Bluetooth settings.  Connection options screen.  The summary for the Hearing Aid checkbox preference when Hearing Aid is connected. -->
-    <string name="bluetooth_hearing_aid_profile_summary_connected">Connected to Hearing Aids</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the Hearing aid checkbox preference when hearing aid is connected. -->
+    <string name="bluetooth_hearing_aid_profile_summary_connected">Connected to hearing aids</string>
     <!-- Bluetooth settings.  Connection options screen.  The summary for the LE audio checkbox preference when LE audio is connected. -->
     <string name="bluetooth_le_audio_profile_summary_connected">Connected to LE audio</string>
 
@@ -287,8 +287,8 @@
          for the HID checkbox preference that describes how checking it
          will set the HID profile as preferred. -->
     <string name="bluetooth_hid_profile_summary_use_for">Use for input</string>
-    <!-- Bluetooth settings.  Connection options screen.  The summary for the Hearing Aid checkbox preference that describes how checking it will set the Hearing Aid profile as preferred. -->
-    <string name="bluetooth_hearing_aid_profile_summary_use_for">Use for Hearing Aids</string>
+    <!-- Bluetooth settings.  Connection options screen.  The summary for the Hearing aid checkbox preference that describes how checking it will set the hearing aid related profile as preferred. -->
+    <string name="bluetooth_hearing_aid_profile_summary_use_for">Use for hearing aids</string>
     <!-- Bluetooth settings.  Connection options screen.  The summary for the LE_AUDIO checkbox preference that describes how checking it will set the LE_AUDIO profile as preferred. -->
     <string name="bluetooth_le_audio_profile_summary_use_for">Use for LE_AUDIO</string>
 
@@ -827,6 +827,11 @@
     <!-- UI debug setting: show touches location summary [CHAR LIMIT=50] -->
     <string name="show_touches_summary">Show visual feedback for taps</string>
 
+    <!-- UI debug setting: show key presses? [CHAR LIMIT=50] -->
+    <string name="show_key_presses">Show key presses</string>
+    <!-- UI debug setting: show physical key presses summary [CHAR LIMIT=150] -->
+    <string name="show_key_presses_summary">Show visual feedback for physical key presses</string>
+
     <!-- UI debug setting: show where surface updates happen? [CHAR LIMIT=25] -->
     <string name="show_screen_updates">Show surface updates</string>
     <!-- UI debug setting: show surface updates summary [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml
index 4ac4aae..1249c6b 100644
--- a/packages/SettingsLib/res/values/styles.xml
+++ b/packages/SettingsLib/res/values/styles.xml
@@ -93,7 +93,6 @@
     </style>
 
     <style name="DialogButtonPositive">
-        <item name="android:buttonCornerRadius">0dp</item>
         <item name="android:background">@drawable/dialog_btn_filled</item>
         <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
         <item name="android:textSize">14sp</item>
@@ -104,7 +103,6 @@
     </style>
 
     <style name="DialogButtonNegative">
-        <item name="android:buttonCornerRadius">28dp</item>
         <item name="android:background">@drawable/dialog_btn_outline</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textSize">14sp</item>
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index cfff519..918d696 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -7,8 +7,18 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+java_library {
+    name: "SettingsLib-search-interface",
+    visibility: ["//visibility:private"],
+    srcs: ["interface-src/**/*.java"],
+    host_supported: true,
+}
+
 android_library {
     name: "SettingsLib-search",
+    static_libs: [
+        "SettingsLib-search-interface",
+    ],
     srcs: ["src/**/*.java"],
 
     sdk_version: "system_current",
@@ -19,12 +29,10 @@
     name: "SettingsLib-annotation-processor",
     processor_class: "com.android.settingslib.search.IndexableProcessor",
     static_libs: [
+        "SettingsLib-search-interface",
         "javapoet",
     ],
-    srcs: [
-        "processor-src/**/*.java",
-        "src/com/android/settingslib/search/SearchIndexable.java",
-    ],
+    srcs: ["processor-src/**/*.java"],
     java_resource_dirs: ["resources"],
 }
 
diff --git a/packages/SettingsLib/search/common.mk b/packages/SettingsLib/search/common.mk
deleted file mode 100644
index 05226db..0000000
--- a/packages/SettingsLib/search/common.mk
+++ /dev/null
@@ -1,10 +0,0 @@
-# Include this file to generate SearchIndexableResourcesImpl
-
-LOCAL_ANNOTATION_PROCESSORS += \
-    SettingsLib-annotation-processor
-
-LOCAL_ANNOTATION_PROCESSOR_CLASSES += \
-    com.android.settingslib.search.IndexableProcessor
-
-LOCAL_STATIC_JAVA_LIBRARIES += \
-    SettingsLib-search
diff --git a/packages/SettingsLib/search/interface-src/com/android/settingslib/search/SearchIndexable.java b/packages/SettingsLib/search/interface-src/com/android/settingslib/search/SearchIndexable.java
new file mode 100644
index 0000000..174f337
--- /dev/null
+++ b/packages/SettingsLib/search/interface-src/com/android/settingslib/search/SearchIndexable.java
@@ -0,0 +1,65 @@
+/*
+ * 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.settingslib.search;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Denotes that the class should participate in search indexing.
+ */
+@Retention(RetentionPolicy.SOURCE)
+public @interface SearchIndexable {
+    /**
+     * Bitfield for the form factors this class should be considered indexable for.
+     * Default is {@link #ALL}.
+     * <p>
+     * TODO: actually use this value somehow
+     */
+    int forTarget() default ALL;
+
+    /**
+     * Indicates that the class should be considered indexable for Mobile.
+     */
+    int MOBILE = 1 << 0;
+
+    /**
+     * Indicates that the class should be considered indexable for TV.
+     */
+    int TV = 1 << 1;
+
+    /**
+     * Indicates that the class should be considered indexable for Wear.
+     */
+    int WEAR = 1 << 2;
+
+    /**
+     * Indicates that the class should be considered indexable for Auto.
+     */
+    int AUTO = 1 << 3;
+
+    /**
+     * Indicates that the class should be considered indexable for ARC++.
+     */
+    int ARC = 1 << 4;
+
+    /**
+     * Indicates that the class should be considered indexable for all targets.
+     */
+    int ALL = MOBILE | TV | WEAR | AUTO | ARC;
+
+}
diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
index e92157e..fa43915 100644
--- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
+++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
@@ -34,6 +34,7 @@
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.annotation.processing.RoundEnvironment;
 import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedOptions;
 import javax.annotation.processing.SupportedSourceVersion;
 import javax.lang.model.SourceVersion;
 import javax.lang.model.element.Element;
@@ -48,10 +49,11 @@
  * subclasses.
  */
 @SupportedSourceVersion(SourceVersion.RELEASE_17)
+@SupportedOptions(IndexableProcessor.PACKAGE_KEY)
 @SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"})
 public class IndexableProcessor extends AbstractProcessor {
 
-    private static final String PACKAGE = "com.android.settingslib.search";
+    private static final String SETTINGSLIB_SEARCH_PACKAGE = "com.android.settingslib.search";
     private static final String CLASS_BASE = "SearchIndexableResourcesBase";
     private static final String CLASS_MOBILE = "SearchIndexableResourcesMobile";
     private static final String CLASS_TV = "SearchIndexableResourcesTv";
@@ -59,6 +61,9 @@
     private static final String CLASS_AUTO = "SearchIndexableResourcesAuto";
     private static final String CLASS_ARC = "SearchIndexableResourcesArc";
 
+    static final String PACKAGE_KEY = "com.android.settingslib.search.processor.package";
+
+    private String mPackage;
     private Filer mFiler;
     private Messager mMessager;
     private boolean mRanOnce;
@@ -72,7 +77,8 @@
         }
         mRanOnce = true;
 
-        final ClassName searchIndexableData = ClassName.get(PACKAGE, "SearchIndexableData");
+        final ClassName searchIndexableData =
+                ClassName.get(SETTINGSLIB_SEARCH_PACKAGE, "SearchIndexableData");
 
         final FieldSpec providers = FieldSpec.builder(
                 ParameterizedTypeName.get(
@@ -130,7 +136,7 @@
                         builder = arcConstructorBuilder;
                     }
                     builder.addCode(
-                            "$N(new SearchIndexableData($L.class, $L"
+                            "$N(new com.android.settingslib.search.SearchIndexableData($L.class, $L"
                                     + ".SEARCH_INDEX_DATA_PROVIDER));\n",
                             addIndex, className, className);
                 } else {
@@ -150,50 +156,51 @@
 
         final TypeSpec baseClass = TypeSpec.classBuilder(CLASS_BASE)
                 .addModifiers(Modifier.PUBLIC)
-                .addSuperinterface(ClassName.get(PACKAGE, "SearchIndexableResources"))
+                .addSuperinterface(
+                        ClassName.get(SETTINGSLIB_SEARCH_PACKAGE, "SearchIndexableResources"))
                 .addField(providers)
                 .addMethod(baseConstructorBuilder.build())
                 .addMethod(addIndex)
                 .addMethod(getProviderValues)
                 .build();
-        final JavaFile searchIndexableResourcesBase = JavaFile.builder(PACKAGE, baseClass).build();
+        final JavaFile searchIndexableResourcesBase = JavaFile.builder(mPackage, baseClass).build();
 
-        final JavaFile searchIndexableResourcesMobile = JavaFile.builder(PACKAGE,
+        final JavaFile searchIndexableResourcesMobile = JavaFile.builder(mPackage,
                 TypeSpec.classBuilder(CLASS_MOBILE)
                         .addModifiers(Modifier.PUBLIC)
-                        .superclass(ClassName.get(PACKAGE, baseClass.name))
+                        .superclass(ClassName.get(mPackage, baseClass.name))
                         .addMethod(mobileConstructorBuilder.build())
                         .build())
                 .build();
 
-        final JavaFile searchIndexableResourcesTv = JavaFile.builder(PACKAGE,
+        final JavaFile searchIndexableResourcesTv = JavaFile.builder(mPackage,
                 TypeSpec.classBuilder(CLASS_TV)
                         .addModifiers(Modifier.PUBLIC)
-                        .superclass(ClassName.get(PACKAGE, baseClass.name))
+                        .superclass(ClassName.get(mPackage, baseClass.name))
                         .addMethod(tvConstructorBuilder.build())
                         .build())
                 .build();
 
-        final JavaFile searchIndexableResourcesWear = JavaFile.builder(PACKAGE,
+        final JavaFile searchIndexableResourcesWear = JavaFile.builder(mPackage,
                 TypeSpec.classBuilder(CLASS_WEAR)
                         .addModifiers(Modifier.PUBLIC)
-                        .superclass(ClassName.get(PACKAGE, baseClass.name))
+                        .superclass(ClassName.get(mPackage, baseClass.name))
                         .addMethod(wearConstructorBuilder.build())
                         .build())
                 .build();
 
-        final JavaFile searchIndexableResourcesAuto = JavaFile.builder(PACKAGE,
+        final JavaFile searchIndexableResourcesAuto = JavaFile.builder(mPackage,
                 TypeSpec.classBuilder(CLASS_AUTO)
                         .addModifiers(Modifier.PUBLIC)
-                        .superclass(ClassName.get(PACKAGE, baseClass.name))
+                        .superclass(ClassName.get(mPackage, baseClass.name))
                         .addMethod(autoConstructorBuilder.build())
                         .build())
                 .build();
 
-        final JavaFile searchIndexableResourcesArc = JavaFile.builder(PACKAGE,
+        final JavaFile searchIndexableResourcesArc = JavaFile.builder(mPackage,
                 TypeSpec.classBuilder(CLASS_ARC)
                         .addModifiers(Modifier.PUBLIC)
-                        .superclass(ClassName.get(PACKAGE, baseClass.name))
+                        .superclass(ClassName.get(mPackage, baseClass.name))
                         .addMethod(arcConstructorBuilder.build())
                         .build())
                 .build();
@@ -214,6 +221,8 @@
     @Override
     public synchronized void init(ProcessingEnvironment processingEnvironment) {
         super.init(processingEnvironment);
+        mPackage = processingEnvironment.getOptions()
+                .getOrDefault(PACKAGE_KEY, SETTINGSLIB_SEARCH_PACKAGE);
         mFiler = processingEnvironment.getFiler();
         mMessager = processingEnvironment.getMessager();
     }
diff --git a/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexable.java b/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexable.java
deleted file mode 100644
index 638fa3e9..0000000
--- a/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexable.java
+++ /dev/null
@@ -1,65 +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.settingslib.search;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Denotes that the class should participate in search indexing.
- */
-@Retention(RetentionPolicy.SOURCE)
-public @interface SearchIndexable {
-    /**
-     * Bitfield for the form factors this class should be considered indexable for.
-     * Default is {@link #ALL}.
-     *
-     * TODO: actually use this value somehow
-     */
-    int forTarget() default ALL;
-
-    /**
-     * Indicates that the class should be considered indexable for Mobile.
-     */
-    int MOBILE = 1<<0;
-
-    /**
-     * Indicates that the class should be considered indexable for TV.
-     */
-    int TV = 1<<1;
-
-    /**
-     * Indicates that the class should be considered indexable for Wear.
-     */
-    int WEAR = 1<<2;
-
-    /**
-     * Indicates that the class should be considered indexable for Auto.
-     */
-    int AUTO = 1<<3;
-
-    /**
-     * Indicates that the class should be considered indexable for ARC++.
-     */
-    int ARC = 1<<4;
-
-    /**
-     * Indicates that the class should be considered indexable for all targets.
-     */
-    int ALL = MOBILE | TV | WEAR | AUTO | ARC;
-
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
index 64a0781..5d520ce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
@@ -28,11 +28,11 @@
 public class InterestingConfigChanges {
     private final Configuration mLastConfiguration = new Configuration();
     private final int mFlags;
-    private int mLastDensity;
 
     public InterestingConfigChanges() {
         this(ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_LAYOUT_DIRECTION
-                | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_ASSETS_PATHS);
+                | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_ASSETS_PATHS
+                | ActivityInfo.CONFIG_DENSITY);
     }
 
     public InterestingConfigChanges(int flags) {
@@ -50,11 +50,6 @@
     public boolean applyNewConfig(Resources res) {
         int configChanges = mLastConfiguration.updateFrom(
                 Configuration.generateDelta(mLastConfiguration, res.getConfiguration()));
-        boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
-        if (densityChanged || (configChanges & (mFlags)) != 0) {
-            mLastDensity = res.getDisplayMetrics().densityDpi;
-            return true;
-        }
-        return false;
+        return (configChanges & (mFlags)) != 0;
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index f522fd1..f03ff00 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -356,11 +356,7 @@
         connectDevice();
     }
 
-    public HearingAidInfo getHearingAidInfo() {
-        return mHearingAidInfo;
-    }
-
-    public void setHearingAidInfo(HearingAidInfo hearingAidInfo) {
+    void setHearingAidInfo(HearingAidInfo hearingAidInfo) {
         mHearingAidInfo = hearingAidInfo;
     }
 
@@ -1522,10 +1518,15 @@
      *                      list.
      */
     public void switchMemberDeviceContent(CachedBluetoothDevice newMainDevice) {
-        // Backup from main device
+        // Remove the sub device from mMemberDevices first to prevent hash mismatch problem due
+        // to mDevice switch
+        removeMemberDevice(newMainDevice);
+
+        // Backup from current main device
         final BluetoothDevice tmpDevice = mDevice;
         final short tmpRssi = mRssi;
         final boolean tmpJustDiscovered = mJustDiscovered;
+
         // Set main device from sub device
         release();
         mDevice = newMainDevice.mDevice;
@@ -1539,6 +1540,9 @@
         newMainDevice.mRssi = tmpRssi;
         newMainDevice.mJustDiscovered = tmpJustDiscovered;
         newMainDevice.fillData();
+
+        // Add the sub device back into mMemberDevices with correct hash
+        addMemberDevice(newMainDevice);
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 0c1b793..a6536a8c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -20,6 +20,7 @@
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.ScanFilter;
 import android.content.Context;
 import android.util.Log;
 
@@ -28,6 +29,7 @@
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -114,10 +116,21 @@
     /**
      * Create and return a new {@link CachedBluetoothDevice}. This assumes
      * that {@link #findDevice} has already been called and returned null.
-     * @param device the address of the new Bluetooth device
+     * @param device the new Bluetooth device
      * @return the newly created CachedBluetoothDevice object
      */
     public CachedBluetoothDevice addDevice(BluetoothDevice device) {
+        return addDevice(device, /*leScanFilters=*/null);
+    }
+
+    /**
+     * Create and return a new {@link CachedBluetoothDevice}. This assumes
+     * that {@link #findDevice} has already been called and returned null.
+     * @param device the new Bluetooth device
+     * @param leScanFilters the BLE scan filters which the device matched
+     * @return the newly created CachedBluetoothDevice object
+     */
+    public CachedBluetoothDevice addDevice(BluetoothDevice device, List<ScanFilter> leScanFilters) {
         CachedBluetoothDevice newDevice;
         final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
         synchronized (this) {
@@ -125,7 +138,7 @@
             if (newDevice == null) {
                 newDevice = new CachedBluetoothDevice(mContext, profileManager, device);
                 mCsipDeviceManager.initCsipDeviceIfNeeded(newDevice);
-                mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice);
+                mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice, leScanFilters);
                 if (!mCsipDeviceManager.setMemberDeviceIfNeeded(newDevice)
                         && !mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) {
                     mCachedDevices.add(newDevice);
@@ -353,16 +366,17 @@
     public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) {
         device.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
         CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(device);
-        final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
+        // Should iterate through the cloned set to avoid ConcurrentModificationException
+        final Set<CachedBluetoothDevice> memberDevices = new HashSet<>(device.getMemberDevice());
         if (!memberDevices.isEmpty()) {
-            // Main device is unpaired, to unpair the member device
+            // Main device is unpaired, also unpair the member devices
             for (CachedBluetoothDevice memberDevice : memberDevices) {
                 memberDevice.unpair();
                 memberDevice.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
                 device.removeMemberDevice(memberDevice);
             }
         } else if (mainDevice != null) {
-            // the member device unpaired, to unpair main device
+            // Member device is unpaired, also unpair the main device
             mainDevice.unpair();
         }
         mainDevice = mHearingAidDeviceManager.findMainDevice(device);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index e5e5782..111a2c1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -18,10 +18,13 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.le.ScanFilter;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.media.AudioDeviceAttributes;
 import android.media.audiopolicy.AudioProductStrategy;
+import android.os.ParcelUuid;
 import android.provider.Settings;
 import android.util.Log;
 
@@ -59,7 +62,8 @@
         mRoutingHelper = routingHelper;
     }
 
-    void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) {
+    void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice,
+            List<ScanFilter> leScanFilters) {
         long hiSyncId = getHiSyncId(newDevice.getDevice());
         if (isValidHiSyncId(hiSyncId)) {
             // Once hiSyncId is valid, assign hearing aid info
@@ -68,6 +72,21 @@
                     .setAshaDeviceMode(getDeviceMode(newDevice.getDevice()))
                     .setHiSyncId(hiSyncId);
             newDevice.setHearingAidInfo(infoBuilder.build());
+        } else if (leScanFilters != null && !newDevice.isHearingAidDevice()) {
+            // If the device is added with hearing aid scan filter during pairing, set an empty
+            // hearing aid info to indicate it's a hearing aid device. The info will be updated
+            // when corresponding profiles connected.
+            for (ScanFilter leScanFilter: leScanFilters) {
+                final ParcelUuid serviceUuid = leScanFilter.getServiceUuid();
+                final ParcelUuid serviceDataUuid = leScanFilter.getServiceDataUuid();
+                if (BluetoothUuid.HEARING_AID.equals(serviceUuid)
+                        || BluetoothUuid.HAS.equals(serviceUuid)
+                        || BluetoothUuid.HEARING_AID.equals(serviceDataUuid)
+                        || BluetoothUuid.HAS.equals(serviceDataUuid)) {
+                    newDevice.setHearingAidInfo(new HearingAidInfo.Builder().build());
+                    break;
+                }
+            }
         }
     }
 
@@ -167,6 +186,14 @@
             if (cachedDevice.getHiSyncId() != hiSyncId) {
                 continue;
             }
+
+            // The remote device supports CSIP, the other ear should be processed as a member
+            // device. Ignore hiSyncId grouping from ASHA here.
+            if (cachedDevice.getProfiles().stream().anyMatch(
+                    profile -> profile instanceof CsipSetCoordinatorProfile)) {
+                continue;
+            }
+
             if (firstMatchedIndex == -1) {
                 // Found the first one
                 firstMatchedIndex = i;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 79fb566..69b61c7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -25,10 +25,13 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeAudioContentMetadata;
 import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
 import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.bluetooth.BluetoothLeBroadcastSubgroup;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.bluetooth.BluetoothStatusCodes;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -39,6 +42,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 
 import androidx.annotation.RequiresApi;
 
@@ -76,14 +80,16 @@
             Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME),
     };
 
-    private BluetoothLeBroadcast mService;
+    private BluetoothLeBroadcast mServiceBroadcast;
+    private BluetoothLeBroadcastAssistant mServiceBroadcastAssistant;
     private BluetoothLeAudioContentMetadata mBluetoothLeAudioContentMetadata;
     private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata;
     private BluetoothLeAudioContentMetadata.Builder mBuilder;
     private int mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER;
     private String mAppSourceName = "";
     private String mNewAppSourceName = "";
-    private boolean mIsProfileReady;
+    private boolean mIsBroadcastProfileReady = false;
+    private boolean mIsBroadcastAssistantProfileReady = false;
     private String mProgramInfo;
     private byte[] mBroadcastCode;
     private Executor mExecutor;
@@ -94,17 +100,22 @@
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
             if (DEBUG) {
-                Log.d(TAG, "Bluetooth service connected");
+                Log.d(TAG, "Bluetooth service connected: " + profile);
             }
-            if(!mIsProfileReady) {
-                mService = (BluetoothLeBroadcast) proxy;
-                mIsProfileReady = true;
+            if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) && !mIsBroadcastProfileReady) {
+                mServiceBroadcast = (BluetoothLeBroadcast) proxy;
+                mIsBroadcastProfileReady = true;
                 registerServiceCallBack(mExecutor, mBroadcastCallback);
                 List<BluetoothLeBroadcastMetadata> metadata = getAllBroadcastMetadata();
                 if (!metadata.isEmpty()) {
                     updateBroadcastInfoFromBroadcastMetadata(metadata.get(0));
                 }
                 registerContentObserver();
+            } else if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+                    && !mIsBroadcastAssistantProfileReady) {
+                mIsBroadcastAssistantProfileReady = true;
+                mServiceBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
+                registerBroadcastAssistantCallback(mExecutor, mBroadcastAssistantCallback);
             }
         }
 
@@ -113,9 +124,17 @@
             if (DEBUG) {
                 Log.d(TAG, "Bluetooth service disconnected");
             }
-            if(mIsProfileReady) {
-                mIsProfileReady = false;
+            if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) && mIsBroadcastProfileReady) {
+                mIsBroadcastProfileReady = false;
                 unregisterServiceCallBack(mBroadcastCallback);
+            }
+            if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+                    && mIsBroadcastAssistantProfileReady) {
+                mIsBroadcastAssistantProfileReady = false;
+                unregisterBroadcastAssistantCallback(mBroadcastAssistantCallback);
+            }
+
+            if (!mIsBroadcastAssistantProfileReady && !mIsBroadcastProfileReady) {
                 unregisterContentObserver();
             }
         }
@@ -157,6 +176,8 @@
                                 "onBroadcastStopped(), reason = " + reason + ", broadcastId = "
                                         + broadcastId);
                     }
+
+                    stopLocalSourceReceivers();
                     resetCacheInfo();
                 }
 
@@ -196,6 +217,61 @@
                 }
             };
 
+    private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
+            new BluetoothLeBroadcastAssistant.Callback() {
+                @Override
+                public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId,
+                        int reason) {}
+                @Override
+                public void onSearchStarted(int reason) {}
+
+                @Override
+                public void onSearchStartFailed(int reason) {}
+
+                @Override
+                public void onSearchStopped(int reason) {}
+
+                @Override
+                public void onSearchStopFailed(int reason) {}
+
+                @Override
+                public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
+
+                @Override
+                public void onSourceAddFailed(@NonNull BluetoothDevice sink,
+                        @NonNull BluetoothLeBroadcastMetadata source, int reason) {}
+
+                @Override
+                public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId,
+                        int reason) {}
+
+                @Override
+                public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId,
+                        int reason) {}
+
+                @Override
+                public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId,
+                        int reason) {
+                    if (DEBUG) {
+                        Log.d(TAG, "onSourceRemoved(), sink = " + sink + ", reason = "
+                                + reason + ", sourceId = " + sourceId);
+                    }
+                }
+
+                @Override
+                public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId,
+                        int reason) {
+                    if (DEBUG) {
+                        Log.d(TAG, "onSourceRemoveFailed(), sink = " + sink + ", reason = "
+                                + reason + ", sourceId = " + sourceId);
+                    }
+                }
+
+                @Override
+                public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId,
+                        @NonNull BluetoothLeBroadcastReceiveState state) {}
+            };
+
     private class BroadcastSettingsObserver extends ContentObserver {
         BroadcastSettingsObserver(Handler h) {
             super(h);
@@ -219,6 +295,9 @@
         // Before registering callback, the constructor should finish creating the all of variables.
         BluetoothAdapter.getDefaultAdapter()
                 .getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
+        BluetoothAdapter.getDefaultAdapter()
+                .getProfileProxy(context, mServiceListener,
+                BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
     }
 
     /**
@@ -227,7 +306,7 @@
      */
     public void startBroadcast(String appSourceName, String language) {
         mNewAppSourceName = appSourceName;
-        if (mService == null) {
+        if (mServiceBroadcast == null) {
             Log.d(TAG, "The BluetoothLeBroadcast is null when starting the broadcast.");
             return;
         }
@@ -237,7 +316,8 @@
                     "startBroadcast: language = " + language + " ,programInfo = " + programInfo);
         }
         buildContentMetadata(language, programInfo);
-        mService.startBroadcast(mBluetoothLeAudioContentMetadata, mBroadcastCode);
+        mServiceBroadcast.startBroadcast(mBluetoothLeAudioContentMetadata,
+                (mBroadcastCode != null && mBroadcastCode.length > 0) ? mBroadcastCode : null);
     }
 
     public String getProgramInfo() {
@@ -340,13 +420,13 @@
     }
 
     public BluetoothLeBroadcastMetadata getLatestBluetoothLeBroadcastMetadata() {
-        if (mService == null) {
+        if (mServiceBroadcast == null) {
             Log.d(TAG, "The BluetoothLeBroadcast is null");
             return null;
         }
         if (mBluetoothLeBroadcastMetadata == null) {
             final List<BluetoothLeBroadcastMetadata> metadataList =
-                    mService.getAllBroadcastMetadata();
+                    mServiceBroadcast.getAllBroadcastMetadata();
             mBluetoothLeBroadcastMetadata = metadataList.stream()
                     .filter(i -> i.getBroadcastId() == mBroadcastId)
                     .findFirst()
@@ -410,14 +490,14 @@
      * corresponding callback {@link BluetoothLeBroadcast.Callback}.
      */
     public void stopBroadcast(int broadcastId) {
-        if (mService == null) {
+        if (mServiceBroadcast == null) {
             Log.d(TAG, "The BluetoothLeBroadcast is null when stopping the broadcast.");
             return;
         }
         if (DEBUG) {
             Log.d(TAG, "stopBroadcast()");
         }
-        mService.stopBroadcast(broadcastId);
+        mServiceBroadcast.stopBroadcast(broadcastId);
     }
 
     /**
@@ -425,7 +505,7 @@
      * corresponding callback {@link BluetoothLeBroadcast.Callback}.
      */
     public void updateBroadcast(String appSourceName, String language) {
-        if (mService == null) {
+        if (mServiceBroadcast == null) {
             Log.d(TAG, "The BluetoothLeBroadcast is null when updating the broadcast.");
             return;
         }
@@ -436,26 +516,57 @@
         }
         mNewAppSourceName = appSourceName;
         mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(programInfo).build();
-        mService.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata);
+        mServiceBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata);
     }
 
     public void registerServiceCallBack(@NonNull @CallbackExecutor Executor executor,
             @NonNull BluetoothLeBroadcast.Callback callback) {
-        if (mService == null) {
+        if (mServiceBroadcast == null) {
             Log.d(TAG, "The BluetoothLeBroadcast is null.");
             return;
         }
 
-        mService.registerCallback(executor, callback);
+        mServiceBroadcast.registerCallback(executor, callback);
+    }
+
+    /**
+     * Register Broadcast Assistant Callbacks to track it's state and receivers
+     *
+     * @param executor Executor object for callback
+     * @param callback Callback object to be registered
+     */
+    public void registerBroadcastAssistantCallback(@NonNull @CallbackExecutor Executor executor,
+            @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
+        if (mServiceBroadcastAssistant == null) {
+            Log.d(TAG, "The BluetoothLeBroadcastAssisntant is null.");
+            return;
+        }
+
+        mServiceBroadcastAssistant.registerCallback(executor, callback);
     }
 
     public void unregisterServiceCallBack(@NonNull BluetoothLeBroadcast.Callback callback) {
-        if (mService == null) {
+        if (mServiceBroadcast == null) {
             Log.d(TAG, "The BluetoothLeBroadcast is null.");
             return;
         }
 
-        mService.unregisterCallback(callback);
+        mServiceBroadcast.unregisterCallback(callback);
+    }
+
+    /**
+     * Unregister previousely registered Broadcast Assistant Callbacks
+     *
+     * @param callback Callback object to be unregistered
+     */
+    public void unregisterBroadcastAssistantCallback(
+            @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
+        if (mServiceBroadcastAssistant == null) {
+            Log.d(TAG, "The BluetoothLeBroadcastAssisntant is null.");
+            return;
+        }
+
+        mServiceBroadcastAssistant.unregisterCallback(callback);
     }
 
     private void buildContentMetadata(String language, String programInfo) {
@@ -473,7 +584,7 @@
     }
 
     public boolean isProfileReady() {
-        return mIsProfileReady;
+        return mIsBroadcastProfileReady;
     }
 
     @Override
@@ -493,40 +604,40 @@
      * Not supported since LE Audio Broadcasts do not establish a connection.
      */
     public int getConnectionStatus(BluetoothDevice device) {
-        if (mService == null) {
+        if (mServiceBroadcast == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
         }
         // LE Audio Broadcasts are not connection-oriented.
-        return mService.getConnectionState(device);
+        return mServiceBroadcast.getConnectionState(device);
     }
 
     /**
      * Not supported since LE Audio Broadcasts do not establish a connection.
      */
     public List<BluetoothDevice> getConnectedDevices() {
-        if (mService == null) {
+        if (mServiceBroadcast == null) {
             return new ArrayList<BluetoothDevice>(0);
         }
         // LE Audio Broadcasts are not connection-oriented.
-        return mService.getConnectedDevices();
+        return mServiceBroadcast.getConnectedDevices();
     }
 
     public @NonNull
     List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
-        if (mService == null) {
+        if (mServiceBroadcast == null) {
             Log.d(TAG, "The BluetoothLeBroadcast is null.");
             return Collections.emptyList();
         }
 
-        return mService.getAllBroadcastMetadata();
+        return mServiceBroadcast.getAllBroadcastMetadata();
     }
 
     public boolean isEnabled(BluetoothDevice device) {
-        if (mService == null) {
+        if (mServiceBroadcast == null) {
             return false;
         }
 
-        return !mService.getAllBroadcastMetadata().isEmpty();
+        return !mServiceBroadcast.getAllBroadcastMetadata().isEmpty();
     }
 
     /**
@@ -570,12 +681,12 @@
         if (DEBUG) {
             Log.d(TAG, "finalize()");
         }
-        if (mService != null) {
+        if (mServiceBroadcast != null) {
             try {
                 BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
                         BluetoothProfile.LE_AUDIO_BROADCAST,
-                        mService);
-                mService = null;
+                        mServiceBroadcast);
+                mServiceBroadcast = null;
             } catch (Throwable t) {
                 Log.w(TAG, "Error cleaning up LeAudio proxy", t);
             }
@@ -625,4 +736,21 @@
         }
         mContentResolver.unregisterContentObserver(mSettingsObserver);
     }
+
+    private void stopLocalSourceReceivers() {
+        if (DEBUG) {
+            Log.d(TAG, "stopLocalSourceReceivers()");
+        }
+        for (BluetoothDevice device : mServiceBroadcastAssistant.getConnectedDevices()) {
+            for (BluetoothLeBroadcastReceiveState receiveState :
+                    mServiceBroadcastAssistant.getAllSources(device)) {
+                /* Check if local/last broadcast is the synced one */
+                int localBroadcastId = getLatestBroadcastId();
+                if (receiveState.getBroadcastId() != localBroadcastId) continue;
+
+                mServiceBroadcastAssistant.removeSource(device, receiveState.getSourceId());
+            }
+        }
+    }
+
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index b0392be..bb103b8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -221,6 +221,27 @@
     }
 
     /**
+     * Stops an ongoing search for nearby Broadcast Sources.
+     *
+     * On success, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopped(int)} will be
+     * called with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}.
+     * On failure, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopFailed(int)} will be
+     * called with reason code
+     *
+     * @throws IllegalStateException if callback was not registered
+     */
+    public void stopSearchingForSources() {
+        if (DEBUG) {
+            Log.d(TAG, "stopSearchingForSources()");
+        }
+        if (mService == null) {
+            Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+            return;
+        }
+        mService.stopSearchingForSources();
+    }
+
+    /**
      * Get information about all Broadcast Sources that a Broadcast Sink knows about.
      *
      * @param sink Broadcast Sink from which to get all Broadcast Sources
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
index 9567a3b..ea65ade 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.settingslib.core;
 
 import android.app.admin.DevicePolicyManager;
@@ -6,8 +22,11 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.EmptySuper;
+import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 import androidx.core.os.BuildCompat;
+import androidx.lifecycle.LifecycleOwner;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceGroup;
 import androidx.preference.PreferenceScreen;
@@ -52,6 +71,13 @@
     }
 
     /**
+     * Called on view created.
+     */
+    @EmptySuper
+    public void onViewCreated(@NonNull LifecycleOwner viewLifecycleOwner) {
+    }
+
+    /**
      * Updates the current status of preference (summary, switch state, etc)
      */
     public void updateState(Preference preference) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index f911d35..a617bf3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -28,6 +28,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
@@ -35,6 +36,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -114,6 +116,26 @@
     private static final int SCREENSAVER_HOME_CONTROLS_ENABLED_DEFAULT = 1;
     private static final int LOCKSCREEN_SHOW_CONTROLS_DEFAULT = 0;
 
+    private static final int DS_TYPE_ENABLED = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_ENABLED;
+    private static final int DS_TYPE_WHEN_TO_DREAM = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_WHEN_TO_DREAM;
+    private static final int DS_TYPE_DREAM_COMPONENT = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_DREAM_COMPONENT;
+    private static final int DS_TYPE_SHOW_ADDITIONAL_INFO = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_SHOW_ADDITIONAL_INFO;
+    private static final int DS_TYPE_SHOW_HOME_CONTROLS = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_SHOW_HOME_CONTROLS;
+
+    private static final int WHEN_TO_DREAM_UNSPECIFIED = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_UNSPECIFIED;
+    private static final int WHEN_TO_DREAM_CHARGING = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_WHILE_CHARGING_ONLY;
+    private static final int WHEN_TO_DREAM_DOCKED = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_WHILE_DOCKED_ONLY;
+    private static final int WHEN_TO_DREAM_CHARGING_OR_DOCKED = FrameworkStatsLog
+            .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_EITHER_CHARGING_OR_DOCKED;
+
     private final Context mContext;
     private final IDreamManager mDreamManager;
     private final DreamInfoComparator mComparator;
@@ -121,6 +143,7 @@
     private final boolean mDreamsActivatedOnSleepByDefault;
     private final boolean mDreamsActivatedOnDockByDefault;
     private final Set<ComponentName> mDisabledDreams;
+    private final List<String> mLoggableDreamPrefixes;
     private Set<Integer> mSupportedComplications;
     private static DreamBackend sInstance;
 
@@ -148,6 +171,8 @@
                         com.android.internal.R.array.config_disabledDreamComponents))
                 .map(ComponentName::unflattenFromString)
                 .collect(Collectors.toSet());
+        mLoggableDreamPrefixes = Arrays.stream(resources.getStringArray(
+                com.android.internal.R.array.config_loggable_dream_prefixes)).toList();
 
         mSupportedComplications = Arrays.stream(resources.getIntArray(
                         com.android.internal.R.array.config_supportedDreamComplications))
@@ -282,6 +307,8 @@
             default:
                 break;
         }
+
+        logDreamSettingChangeToStatsd(DS_TYPE_WHEN_TO_DREAM);
     }
 
     /** Gets all complications which have been enabled by the user. */
@@ -304,12 +331,14 @@
     public void setComplicationsEnabled(boolean enabled) {
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, enabled ? 1 : 0);
+        logDreamSettingChangeToStatsd(DS_TYPE_SHOW_ADDITIONAL_INFO);
     }
 
     /** Sets whether home controls are enabled by the user on the dream */
     public void setHomeControlsEnabled(boolean enabled) {
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, enabled ? 1 : 0);
+        logDreamSettingChangeToStatsd(DS_TYPE_SHOW_HOME_CONTROLS);
     }
 
     /** Gets whether home controls button is enabled on the dream */
@@ -353,6 +382,7 @@
     public void setEnabled(boolean value) {
         logd("setEnabled(%s)", value);
         setBoolean(Settings.Secure.SCREENSAVER_ENABLED, value);
+        logDreamSettingChangeToStatsd(DS_TYPE_ENABLED);
     }
 
     public boolean isActivatedOnDock() {
@@ -391,6 +421,7 @@
         try {
             ComponentName[] dreams = {dream};
             mDreamManager.setDreamComponents(dream == null ? null : dreams);
+            logDreamSettingChangeToStatsd(DS_TYPE_DREAM_COMPONENT);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to set active dream to " + dream, e);
         }
@@ -461,6 +492,68 @@
         }
     }
 
+    private void logDreamSettingChangeToStatsd(int dreamSettingType) {
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.DREAM_SETTING_CHANGED, /*atom_tag*/
+                UserHandle.myUserId(), /*uid*/
+                isEnabled(), /*enabled*/
+                getActiveDreamComponentForStatsd(), /*dream_component*/
+                getWhenToDreamForStatsd(), /*when_to_dream*/
+                getComplicationsEnabled(), /*show_additional_info*/
+                getHomeControlsEnabled(), /*show_home_controls*/
+                dreamSettingType /*dream_setting_type*/
+        );
+    }
+
+    /**
+     * Returns the user selected dream component in string format for stats logging. If the dream
+     * component is not loggable, returns "other".
+     */
+    private String getActiveDreamComponentForStatsd() {
+        final ComponentName activeDream = getActiveDream();
+        if (activeDream == null) {
+            return "";
+        }
+
+        final String component = activeDream.flattenToShortString();
+        if (isLoggableDreamComponentForStatsd(component)) {
+            return component;
+        } else {
+            return "other";
+        }
+    }
+
+    /**
+     * Whether the dream component is loggable. Only components from the predefined packages are
+     * allowed to be logged for privacy.
+     */
+    private boolean isLoggableDreamComponentForStatsd(String component) {
+        for (int i = 0; i < mLoggableDreamPrefixes.size(); i++) {
+            if (component.startsWith(mLoggableDreamPrefixes.get(i))) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the enum of "when to dream" setting for statsd logging.
+     */
+    private int getWhenToDreamForStatsd() {
+        switch (getWhenToDreamSetting()) {
+            case WHILE_CHARGING:
+                return WHEN_TO_DREAM_CHARGING;
+            case WHILE_DOCKED:
+                return WHEN_TO_DREAM_DOCKED;
+            case EITHER:
+                return WHEN_TO_DREAM_CHARGING_OR_DOCKED;
+            case NEVER:
+            default:
+                return WHEN_TO_DREAM_UNSPECIFIED;
+        }
+    }
+
     private static class DreamInfoComparator implements Comparator<DreamInfo> {
         private final ComponentName mDefaultDream;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 1251b0d..9ab84d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -26,6 +26,7 @@
 import static android.os.BatteryManager.EXTRA_PLUGGED;
 import static android.os.BatteryManager.EXTRA_PRESENT;
 import static android.os.BatteryManager.EXTRA_STATUS;
+import static android.os.OsProtoEnums.BATTERY_PLUGGED_NONE;
 
 import android.content.Context;
 import android.content.Intent;
@@ -40,6 +41,8 @@
  */
 public class BatteryStatus {
     private static final int LOW_BATTERY_THRESHOLD = 20;
+    private static final int SEVERE_LOW_BATTERY_THRESHOLD = 10;
+    private static final int EXTREME_LOW_BATTERY_THRESHOLD = 3;
     private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
 
     public static final int CHARGING_UNKNOWN = -1;
@@ -90,21 +93,7 @@
         present = batteryChangedIntent.getBooleanExtra(EXTRA_PRESENT, true);
         this.incompatibleCharger = incompatibleCharger;
 
-        final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT,
-                -1);
-        int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
-
-        if (maxChargingMicroVolt <= 0) {
-            maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
-        }
-        if (maxChargingMicroAmp > 0) {
-            // Calculating muW = muA * muV / (10^6 mu^2 / mu); splitting up the divisor
-            // to maintain precision equally on both factors.
-            maxChargingWattage = (maxChargingMicroAmp / 1000)
-                    * (maxChargingMicroVolt / 1000);
-        } else {
-            maxChargingWattage = -1;
-        }
+        maxChargingWattage = calculateMaxChargingMicroWatt(batteryChangedIntent);
     }
 
     /** Determine whether the device is plugged. */
@@ -126,7 +115,7 @@
 
     /** Determine whether the device is plugged in dock. */
     public boolean isPluggedInDock() {
-        return plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
+        return isPluggedInDock(plugged);
     }
 
     /**
@@ -140,15 +129,15 @@
 
     /** Whether battery is low and needs to be charged. */
     public boolean isBatteryLow() {
-        return level < LOW_BATTERY_THRESHOLD;
+        return isLowBattery(level);
     }
 
     /** Whether battery defender is enabled. */
     public boolean isBatteryDefender() {
-        return chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+        return isBatteryDefender(chargingStatus);
     }
 
-    /** Return current chargin speed is fast, slow or normal. */
+    /** Return current charging speed is fast, slow or normal. */
     public final int getChargingSpeed(Context context) {
         final int slowThreshold = context.getResources().getInteger(
                 R.integer.config_chargingSlowlyThreshold);
@@ -218,4 +207,126 @@
                 || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS
                 || plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
     }
+
+    /** Determine whether the device is plugged in dock. */
+    public static boolean isPluggedInDock(Intent batteryChangedIntent) {
+        return isPluggedInDock(
+                batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, BATTERY_PLUGGED_NONE));
+    }
+
+    /** Determine whether the device is plugged in dock. */
+    public static boolean isPluggedInDock(int plugged) {
+        return plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
+    }
+
+    /**
+     * Whether the battery is low or not.
+     *
+     * @param batteryChangedIntent the {@link ACTION_BATTERY_CHANGED} intent
+     * @return {@code true} if the battery level is less or equal to {@link LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isLowBattery(Intent batteryChangedIntent) {
+        int level = getBatteryLevel(batteryChangedIntent);
+        return isLowBattery(level);
+    }
+
+    /**
+     * Whether the battery is low or not.
+     *
+     * @param batteryLevel the battery level
+     * @return {@code true} if the battery level is less or equal to {@link LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isLowBattery(int batteryLevel) {
+        return batteryLevel <= LOW_BATTERY_THRESHOLD;
+    }
+
+    /**
+     * Whether the battery is severe low or not.
+     *
+     * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
+     * @return {@code true} if the battery level is less or equal to {@link
+     *     SEVERE_LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isSevereLowBattery(Intent batteryChangedIntent) {
+        int level = getBatteryLevel(batteryChangedIntent);
+        return level <= SEVERE_LOW_BATTERY_THRESHOLD;
+    }
+
+    /**
+     * Whether the battery is extreme low or not.
+     *
+     * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
+     * @return {@code true} if the battery level is less or equal to {@link
+     *     EXTREME_LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isExtremeLowBattery(Intent batteryChangedIntent) {
+        int level = getBatteryLevel(batteryChangedIntent);
+        return level <= EXTREME_LOW_BATTERY_THRESHOLD;
+    }
+
+    /**
+     * Whether the battery defender is enabled or not.
+     *
+     * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
+     * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
+     *     defend, or temp defend
+     */
+    public static boolean isBatteryDefender(Intent batteryChangedIntent) {
+        int chargingStatus =
+                batteryChangedIntent.getIntExtra(EXTRA_CHARGING_STATUS, CHARGING_POLICY_DEFAULT);
+        return isBatteryDefender(chargingStatus);
+    }
+
+    /**
+     * Whether the battery defender is enabled or not.
+     *
+     * @param chargingStatus for {@link EXTRA_CHARGING_STATUS} field in the ACTION_BATTERY_CHANGED
+     *     intent
+     * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
+     *     defend, or temp defend
+     */
+    public static boolean isBatteryDefender(int chargingStatus) {
+        return chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+    }
+
+    /**
+     * Gets the max charging current and max charging voltage form {@link
+     * Intent.ACTION_BATTERY_CHANGED} and calculates the charging speed based on the {@link
+     * R.integer.config_chargingSlowlyThreshold} and {@link R.integer.config_chargingFastThreshold}.
+     *
+     * @param context the application context
+     * @param batteryChangedIntent the intent from {@link Intent.ACTION_BATTERY_CHANGED}
+     * @return the charging speed. {@link CHARGING_REGULAR}, {@link CHARGING_FAST}, {@link
+     *     CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN}
+     */
+    public static int getChargingSpeed(Context context, Intent batteryChangedIntent) {
+        final int maxChargingMicroWatt = calculateMaxChargingMicroWatt(batteryChangedIntent);
+        if (maxChargingMicroWatt <= 0) {
+            return CHARGING_UNKNOWN;
+        } else if (maxChargingMicroWatt
+                < context.getResources().getInteger(R.integer.config_chargingSlowlyThreshold)) {
+            return CHARGING_SLOWLY;
+        } else if (maxChargingMicroWatt
+                > context.getResources().getInteger(R.integer.config_chargingFastThreshold)) {
+            return CHARGING_FAST;
+        } else {
+            return CHARGING_REGULAR;
+        }
+    }
+
+    private static int calculateMaxChargingMicroWatt(Intent batteryChangedIntent) {
+        final int maxChargingMicroAmp =
+                batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
+        int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
+        if (maxChargingMicroVolt <= 0) {
+            maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
+        }
+
+        if (maxChargingMicroAmp > 0) {
+            // Calculating µW = mA * mV
+            return (int) Math.round(maxChargingMicroAmp * 0.001 * maxChargingMicroVolt * 0.001);
+        } else {
+            return -1;
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
index a03acc3..73f6db6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
@@ -325,7 +325,7 @@
         return batteryLevel
     }
 
-    override fun onBoundsChange(bounds: Rect?) {
+    override fun onBoundsChange(bounds: Rect) {
         super.onBoundsChange(bounds)
         updateSize()
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index a9d15f3..ac93019 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -22,6 +22,7 @@
 import static android.media.MediaRoute2Info.TYPE_GROUP;
 import static android.media.MediaRoute2Info.TYPE_HDMI;
 import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
 import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
 import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
 import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
@@ -83,7 +84,8 @@
             MediaDeviceType.TYPE_FAST_PAIR_BLUETOOTH_DEVICE,
             MediaDeviceType.TYPE_BLUETOOTH_DEVICE,
             MediaDeviceType.TYPE_CAST_DEVICE,
-            MediaDeviceType.TYPE_CAST_GROUP_DEVICE})
+            MediaDeviceType.TYPE_CAST_GROUP_DEVICE,
+            MediaDeviceType.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER})
     public @interface MediaDeviceType {
         int TYPE_UNKNOWN = 0;
         int TYPE_PHONE_DEVICE = 1;
@@ -93,6 +95,7 @@
         int TYPE_BLUETOOTH_DEVICE = 5;
         int TYPE_CAST_DEVICE = 6;
         int TYPE_CAST_GROUP_DEVICE = 7;
+        int TYPE_REMOTE_AUDIO_VIDEO_RECEIVER = 8;
     }
 
     @Retention(RetentionPolicy.SOURCE)
@@ -161,6 +164,9 @@
             case TYPE_BLE_HEADSET:
                 mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
                 break;
+            case TYPE_REMOTE_AUDIO_VIDEO_RECEIVER:
+                mType = MediaDeviceType.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
+                break;
             case TYPE_UNKNOWN:
             case TYPE_REMOTE_TV:
             case TYPE_REMOTE_SPEAKER:
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index e61c8f5..3011d31 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -59,6 +59,7 @@
     private static final String KEY_IS_ADMIN = "admin_status";
     private static final String KEY_ADD_USER_LONG_MESSAGE_DISPLAYED =
             "key_add_user_long_message_displayed";
+    public static final int MESSAGE_PADDING = 10;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({EXIT_DIALOG, INITIAL_DIALOG, GRANT_ADMIN_DIALOG,
@@ -79,6 +80,8 @@
     private Bitmap mSavedPhoto;
     private String mSavedName;
     private Drawable mSavedDrawable;
+    private String mUserName;
+    private Drawable mNewUserIcon;
     private Boolean mIsAdmin;
     private Dialog mUserCreationDialog;
     private View mGrantAdminView;
@@ -88,6 +91,7 @@
     private ActivityStarter mActivityStarter;
     private boolean mWaitingForActivityResult;
     private NewUserData mSuccessCallback;
+    private Runnable mCancelCallback;
 
     private final String mFileAuthority;
 
@@ -112,6 +116,7 @@
         mEditUserInfoView = null;
         mUserNameView = null;
         mSuccessCallback = null;
+        mCancelCallback = null;
         mCurrentState = INITIAL_DIALOG;
     }
 
@@ -183,14 +188,13 @@
         mActivity = activity;
         mCustomDialogHelper = new CustomDialogHelper(activity);
         mSuccessCallback = successCallback;
+        mCancelCallback = cancelCallback;
         mActivityStarter = activityStarter;
         addCustomViews(isMultipleAdminEnabled);
         mUserCreationDialog = mCustomDialogHelper.getDialog();
         updateLayout();
-        mUserCreationDialog.setOnDismissListener(view -> {
-            cancelCallback.run();
-            clear();
-        });
+        mUserCreationDialog.setOnDismissListener(view -> finish());
+        mCustomDialogHelper.setMessagePadding(MESSAGE_PADDING);
         mUserCreationDialog.setCanceledOnTouchOutside(true);
         return mUserCreationDialog;
     }
@@ -212,7 +216,6 @@
             }
             updateLayout();
         });
-        return;
     }
 
     private void updateLayout() {
@@ -234,7 +237,6 @@
                 }
                 Drawable icon = mActivity.getDrawable(R.drawable.ic_person_add);
                 mCustomDialogHelper.setVisibility(mCustomDialogHelper.ICON, true)
-                        .setVisibility(mCustomDialogHelper.TITLE, true)
                         .setVisibility(mCustomDialogHelper.MESSAGE, true)
                         .setIcon(icon)
                         .setButtonEnabled(true)
@@ -248,7 +250,6 @@
                 mGrantAdminView.setVisibility(View.VISIBLE);
                 mCustomDialogHelper
                         .setVisibility(mCustomDialogHelper.ICON, true)
-                        .setVisibility(mCustomDialogHelper.TITLE, true)
                         .setVisibility(mCustomDialogHelper.MESSAGE, true)
                         .setIcon(mActivity.getDrawable(R.drawable.ic_admin_panel_settings))
                         .setTitle(R.string.user_grant_admin_title)
@@ -262,28 +263,22 @@
             case EDIT_NAME_DIALOG:
                 mCustomDialogHelper
                         .setVisibility(mCustomDialogHelper.ICON, false)
-                        .setVisibility(mCustomDialogHelper.TITLE, false)
                         .setVisibility(mCustomDialogHelper.MESSAGE, false)
+                        .setTitle(R.string.user_info_settings_title)
                         .setNegativeButtonText(R.string.back)
                         .setPositiveButtonText(R.string.done);
                 mEditUserInfoView.setVisibility(View.VISIBLE);
                 mGrantAdminView.setVisibility(View.GONE);
                 break;
             case CREATE_USER_AND_CLOSE:
-                Drawable newUserIcon = mEditUserPhotoController != null
+                mNewUserIcon = mEditUserPhotoController != null
                         ? mEditUserPhotoController.getNewUserPhotoDrawable()
                         : null;
 
                 String newName = mUserNameView.getText().toString().trim();
                 String defaultName = mActivity.getString(R.string.user_new_user_name);
-                String userName = !newName.isEmpty() ? newName : defaultName;
-
-                if (mSuccessCallback != null) {
-                    mSuccessCallback.onSuccess(userName, newUserIcon,
-                            Boolean.TRUE.equals(mIsAdmin));
-                }
+                mUserName = !newName.isEmpty() ? newName : defaultName;
                 mCustomDialogHelper.getDialog().dismiss();
-                clear();
                 break;
             case EXIT_DIALOG:
                 mCustomDialogHelper.getDialog().dismiss();
@@ -385,4 +380,20 @@
     public boolean isActive() {
         return mCustomDialogHelper != null && mCustomDialogHelper.getDialog() != null;
     }
+
+    /**
+     * Runs callback and clears saved values after dialog is dismissed.
+     */
+    public void finish() {
+        if (mCurrentState == CREATE_USER_AND_CLOSE) {
+            if (mSuccessCallback != null) {
+                mSuccessCallback.onSuccess(mUserName, mNewUserIcon, Boolean.TRUE.equals(mIsAdmin));
+            }
+        } else {
+            if (mCancelCallback != null) {
+                mCancelCallback.run();
+            }
+        }
+        clear();
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index e55d7ea..cd5f597 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -17,7 +17,6 @@
 package com.android.settingslib.users;
 
 import android.app.Activity;
-import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.Intent;
@@ -31,7 +30,6 @@
 import android.view.WindowManager;
 import android.widget.EditText;
 import android.widget.ImageView;
-import android.widget.ScrollView;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
@@ -41,6 +39,7 @@
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.settingslib.utils.CustomDialogHelper;
 
 import java.io.File;
 import java.util.function.BiConsumer;
@@ -128,7 +127,7 @@
      *                        codes to take photo/choose photo/crop photo.
      */
     public Dialog createDialog(Activity activity, ActivityStarter activityStarter,
-            @Nullable Drawable oldUserIcon, String defaultUserName, String title,
+            @Nullable Drawable oldUserIcon, String defaultUserName,
             BiConsumer<String, Drawable> successCallback, Runnable cancelCallback) {
         LayoutInflater inflater = LayoutInflater.from(activity);
         View content = inflater.inflate(R.layout.edit_user_info_dialog_content, null);
@@ -160,10 +159,8 @@
                         userPhotoView);
             }
         }
-        ScrollView scrollView = content.findViewById(R.id.user_info_scroll);
-        scrollView.setClipToOutline(true);
         mEditUserInfoDialog = buildDialog(activity, content, userNameView, oldUserIcon,
-                defaultUserName, title, successCallback, cancelCallback);
+                defaultUserName, successCallback, cancelCallback);
 
         // Make sure the IME is up.
         mEditUserInfoDialog.getWindow()
@@ -181,12 +178,13 @@
     }
 
     private Dialog buildDialog(Activity activity, View content, EditText userNameView,
-            @Nullable Drawable oldUserIcon, String defaultUserName, String title,
+            @Nullable Drawable oldUserIcon, String defaultUserName,
             BiConsumer<String, Drawable> successCallback, Runnable cancelCallback) {
-        return new AlertDialog.Builder(activity)
-                .setView(content)
-                .setCancelable(true)
-                .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+        CustomDialogHelper dialogHelper = new CustomDialogHelper(activity);
+        dialogHelper
+                .setTitle(R.string.user_info_settings_title)
+                .addCustomView(content)
+                .setPositiveButton(android.R.string.ok, view -> {
                     Drawable newUserIcon = mEditUserPhotoController != null
                             ? mEditUserPhotoController.getNewUserPhotoDrawable()
                             : null;
@@ -201,20 +199,23 @@
                     if (successCallback != null) {
                         successCallback.accept(userName, userIcon);
                     }
+                    dialogHelper.getDialog().dismiss();
                 })
-                .setNegativeButton(android.R.string.cancel, (dialog, which) -> {
+                .setBackButton(android.R.string.cancel, view -> {
                     clear();
                     if (cancelCallback != null) {
                         cancelCallback.run();
                     }
-                })
-                .setOnCancelListener(dialog -> {
-                    clear();
-                    if (cancelCallback != null) {
-                        cancelCallback.run();
-                    }
-                })
-                .create();
+                    dialogHelper.getDialog().dismiss();
+                });
+        dialogHelper.getDialog().setOnCancelListener(dialog -> {
+            clear();
+            if (cancelCallback != null) {
+                cancelCallback.run();
+            }
+            dialogHelper.getDialog().dismiss();
+        });
+        return dialogHelper.getDialog();
     }
 
     @VisibleForTesting
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
index de48814..5201b3d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
@@ -193,6 +193,14 @@
     }
 
     /**
+     * Sets message padding of the dialog.
+     */
+    public CustomDialogHelper setMessagePadding(int dp) {
+        mDialogMessage.setPadding(dp, dp, dp, dp);
+        return this;
+    }
+
+    /**
      * Sets icon of the dialog.
      */
     public CustomDialogHelper setIcon(Drawable icon) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index afab046..b9a4647 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -27,6 +27,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiInfo;
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.util.Log;
@@ -331,6 +332,22 @@
     }
 
     /**
+     * Returns the Hotspot network icon resource.
+     *
+     * @param deviceType The device type of Hotspot network
+     */
+    public static int getHotspotIconResource(int deviceType) {
+        return switch (deviceType) {
+            case NetworkProviderInfo.DEVICE_TYPE_PHONE -> R.drawable.ic_hotspot_phone;
+            case NetworkProviderInfo.DEVICE_TYPE_TABLET -> R.drawable.ic_hotspot_tablet;
+            case NetworkProviderInfo.DEVICE_TYPE_LAPTOP -> R.drawable.ic_hotspot_laptop;
+            case NetworkProviderInfo.DEVICE_TYPE_WATCH -> R.drawable.ic_hotspot_watch;
+            case NetworkProviderInfo.DEVICE_TYPE_AUTO -> R.drawable.ic_hotspot_auto;
+            default -> R.drawable.ic_hotspot_phone;  // Return phone icon as default.
+        };
+    }
+
+    /**
      * Wrapper the {@link #getInternetIconResource} for testing compatibility.
      */
     public static class InternetIconInjector {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 0d5de88..56c8c5a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -33,6 +33,8 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.le.ScanFilter;
 import android.content.Context;
 import android.media.AudioAttributes;
 import android.media.AudioDeviceAttributes;
@@ -147,7 +149,7 @@
                 HearingAidProfile.DeviceSide.SIDE_RIGHT);
 
         assertThat(mCachedDevice1.getHiSyncId()).isNotEqualTo(HISYNCID1);
-        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1);
+        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
 
         assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
         assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
@@ -164,12 +166,43 @@
         when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
                 BluetoothHearingAid.HI_SYNC_ID_INVALID);
 
-        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1);
+        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
 
         verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
     }
 
     /**
+     * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and hearing aid scan filter, set an
+     * empty hearing aid info on the device.
+     */
+    @Test
+    public void initHearingAidDeviceIfNeeded_hearingAidScanFilter_setHearingAidInfo() {
+        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
+                BluetoothHearingAid.HI_SYNC_ID_INVALID);
+        final ScanFilter scanFilter = new ScanFilter.Builder()
+                .setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}).build();
+
+        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
+
+        assertThat(mCachedDevice1.isHearingAidDevice()).isTrue();
+    }
+
+    /**
+     * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and random scan filter, not to set
+     * hearing aid info on the device.
+     */
+    @Test
+    public void initHearingAidDeviceIfNeeded_randomScanFilter_notToSetHearingAidInfo() {
+        when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
+                BluetoothHearingAid.HI_SYNC_ID_INVALID);
+        final ScanFilter scanFilter = new ScanFilter.Builder().build();
+
+        mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
+
+        assertThat(mCachedDevice1.isHearingAidDevice()).isFalse();
+    }
+
+    /**
      * Test setSubDeviceIfNeeded, a device with same HiSyncId will be set as sub device
      */
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
index 2edf403..00ae96c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/dream/DreamBackendTest.java
@@ -75,6 +75,9 @@
         when(res.getStringArray(
                 com.android.internal.R.array.config_disabledDreamComponents)).thenReturn(
                 new String[]{});
+        when(res.getStringArray(
+                com.android.internal.R.array.config_loggable_dream_prefixes)).thenReturn(
+                new String[]{});
         mBackend = new DreamBackend(mContext);
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
index e989ed2..66a2ea6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
@@ -111,13 +111,13 @@
                 mActivityStarter, true, null,
                 cancelCallback);
         dialog.show();
-        assertThat(dialog.findViewById(R.id.user_info_scroll).getVisibility()).isEqualTo(View.GONE);
+        assertThat(dialog.findViewById(R.id.user_info_editor).getVisibility()).isEqualTo(View.GONE);
         Button next = dialog.findViewById(R.id.button_ok);
         next.performClick();
         ((RadioButton) dialog.findViewById(R.id.grant_admin_yes)).setChecked(true);
-        assertThat(dialog.findViewById(R.id.user_info_scroll).getVisibility()).isEqualTo(View.GONE);
+        assertThat(dialog.findViewById(R.id.user_info_editor).getVisibility()).isEqualTo(View.GONE);
         next.performClick();
-        assertThat(dialog.findViewById(R.id.user_info_scroll).getVisibility())
+        assertThat(dialog.findViewById(R.id.user_info_editor).getVisibility())
                 .isEqualTo(View.VISIBLE);
         dialog.dismiss();
     }
@@ -212,6 +212,7 @@
         next.performClick();
         verify(successCallback, times(1))
                 .onSuccess(expectedNewName, null, true);
+        verifyNoInteractions(cancelCallback);
     }
 
     @Test
@@ -233,6 +234,7 @@
         next.performClick();
         verify(successCallback, times(1))
                 .onSuccess(expectedNewName, null, false);
+        verifyNoInteractions(cancelCallback);
     }
 
     private class TestCreateUserDialogController extends CreateUserDialogController {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
index f760032..f595cd3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
@@ -27,7 +27,6 @@
 
 import android.app.Activity;
 import android.app.AlertDialog;
-import android.app.Dialog;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
@@ -108,7 +107,7 @@
     @Test
     public void photoControllerOnActivityResult_whenWaiting_isCalled() {
         mController.createDialog(mActivity, mActivityStarter, mCurrentIcon, "test user",
-                "title", null, null);
+                null, null);
         mController.startingActivityForResult();
         Intent resultData = new Intent();
         mController.onActivityResult(0, 0, resultData);
@@ -126,9 +125,7 @@
                 () -> String.valueOf('A')).limit(200).collect(Collectors.joining());
 
         final AlertDialog dialog = (AlertDialog) mController.createDialog(mActivity,
-                mActivityStarter, mCurrentIcon,
-                "test user", "title", null,
-                null);
+                mActivityStarter, mCurrentIcon, "test user", null, null);
         dialog.show();
         final EditText userNameEditText = dialog.findViewById(R.id.user_name);
         userNameEditText.setText(longName);
@@ -143,7 +140,7 @@
 
         AlertDialog dialog = (AlertDialog) mController.createDialog(
                 mActivity, mActivityStarter, mCurrentIcon, "test",
-                "title", successCallback, cancelCallback);
+                successCallback, cancelCallback);
         dialog.show();
         dialog.cancel();
 
@@ -159,9 +156,9 @@
 
         AlertDialog dialog = (AlertDialog) mController.createDialog(
                 mActivity, mActivityStarter, mCurrentIcon, "test",
-                "title", successCallback, cancelCallback);
+                successCallback, cancelCallback);
         dialog.show();
-        dialog.getButton(Dialog.BUTTON_NEGATIVE).performClick();
+        dialog.findViewById(R.id.button_back).performClick();
 
         verifyNoInteractions(successCallback);
         verify(cancelCallback, times(1))
@@ -176,11 +173,11 @@
         Drawable oldUserIcon = mCurrentIcon;
         AlertDialog dialog = (AlertDialog) mController.createDialog(
                 mActivity, mActivityStarter, oldUserIcon, "test",
-                "title", successCallback, cancelCallback);
+                successCallback, cancelCallback);
         // No change to the photo.
         when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(null);
         dialog.show();
-        dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+        dialog.findViewById(R.id.button_ok).performClick();
 
         verify(successCallback, times(1))
                 .accept("test", oldUserIcon);
@@ -194,11 +191,11 @@
 
         AlertDialog dialog = (AlertDialog) mController.createDialog(
                 mActivity, mActivityStarter, null, "test",
-                "title", successCallback, cancelCallback);
+                successCallback, cancelCallback);
         // No change to the photo.
         when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(null);
         dialog.show();
-        dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+        dialog.findViewById(R.id.button_ok).performClick();
 
         verify(successCallback, times(1))
                 .accept("test", null);
@@ -212,14 +209,14 @@
 
         AlertDialog dialog = (AlertDialog) mController.createDialog(
                 mActivity, mActivityStarter, mCurrentIcon, "test",
-                "title", successCallback, cancelCallback);
+                successCallback, cancelCallback);
         // No change to the photo.
         when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(null);
         dialog.show();
         String expectedNewName = "new test user";
         EditText editText = (EditText) dialog.findViewById(R.id.user_name);
         editText.setText(expectedNewName);
-        dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+        dialog.findViewById(R.id.button_ok).performClick();
 
         verify(successCallback, times(1))
                 .accept(expectedNewName, mCurrentIcon);
@@ -233,12 +230,12 @@
 
         AlertDialog dialog = (AlertDialog) mController.createDialog(
                 mActivity, mActivityStarter, mCurrentIcon, "test",
-                "title", successCallback, cancelCallback);
+                successCallback, cancelCallback);
         // A different drawable.
         Drawable newPhoto = mock(Drawable.class);
         when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(newPhoto);
         dialog.show();
-        dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+        dialog.findViewById(R.id.button_ok).performClick();
 
         verify(successCallback, times(1))
                 .accept("test", newPhoto);
@@ -252,12 +249,12 @@
 
         AlertDialog dialog = (AlertDialog) mController.createDialog(
                 mActivity, mActivityStarter, null, "test",
-                "title", successCallback, cancelCallback);
+                 successCallback, cancelCallback);
         // A different drawable.
         Drawable newPhoto = mock(Drawable.class);
         when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(newPhoto);
         dialog.show();
-        dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+        dialog.findViewById(R.id.button_ok).performClick();
 
         verify(successCallback, times(1))
                 .accept("test", newPhoto);
@@ -269,7 +266,7 @@
         mPhotoRestrictedByBase = true;
 
         mController.createDialog(mActivity, mActivityStarter, mCurrentIcon,
-                "test", "title", null, null);
+                "test", null, null);
 
         assertThat(mController.mPhotoController).isNull();
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
index b60dc6a..5293011 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -35,6 +35,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkScoreCache;
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -201,6 +202,25 @@
     }
 
     @Test
+    public void getHotspotIconResource_deviceTypeUnknown_shouldNotCrash() {
+        WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_UNKNOWN);
+    }
+
+    @Test
+    public void getHotspotIconResource_deviceTypeExists_shouldNotNull() {
+        assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_PHONE))
+                .isNotNull();
+        assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_TABLET))
+                .isNotNull();
+        assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_LAPTOP))
+                .isNotNull();
+        assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_WATCH))
+                .isNotNull();
+        assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_AUTO))
+                .isNotNull();
+    }
+
+    @Test
     public void testInternetIconInjector_getIcon_returnsCorrectValues() {
         WifiUtils.InternetIconInjector iconInjector = new WifiUtils.InternetIconInjector(mContext);
 
diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
new file mode 100644
index 0000000..6c0c1a7
--- /dev/null
+++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.fuelgague
+
+import android.content.Context
+import android.content.Intent
+import android.os.BatteryManager
+import android.os.BatteryManager.BATTERY_PLUGGED_AC
+import android.os.BatteryManager.BATTERY_PLUGGED_DOCK
+import android.os.BatteryManager.BATTERY_PLUGGED_USB
+import android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS
+import android.os.BatteryManager.BATTERY_STATUS_FULL
+import android.os.BatteryManager.BATTERY_STATUS_UNKNOWN
+import android.os.BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE
+import android.os.BatteryManager.CHARGING_POLICY_DEFAULT
+import android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT
+import android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE
+import android.os.OsProtoEnums.BATTERY_PLUGGED_NONE
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.fuelgauge.BatteryStatus
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_FAST
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_REGULAR
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_SLOWLY
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_UNKNOWN
+import com.android.settingslib.fuelgauge.BatteryStatus.isBatteryDefender
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import java.util.Optional
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Suite
+import org.junit.runners.Suite.SuiteClasses
+
+@RunWith(Suite::class)
+@SuiteClasses(
+    BatteryStatusTest.NonParameterizedTest::class,
+    BatteryStatusTest.IsPluggedInTest::class,
+    BatteryStatusTest.IsChargedTest::class,
+    BatteryStatusTest.GetChargingSpeedTest::class,
+    BatteryStatusTest.IsPluggedInDockTest::class,
+)
+open class BatteryStatusTest {
+
+    @RunWith(AndroidJUnit4::class)
+    class NonParameterizedTest : BatteryStatusTest() {
+        @Test
+        fun isLowBattery_20Percent_returnsTrue() {
+            val level = 20
+            val intent = createIntent(batteryLevel = level)
+
+            assertWithMessage("failed by isLowBattery(Intent), level=$level")
+                .that(BatteryStatus.isLowBattery(intent))
+                .isTrue()
+            assertWithMessage("failed by isLowBattery($level)")
+                .that(BatteryStatus.isLowBattery(level))
+                .isTrue()
+        }
+
+        @Test
+        fun isLowBattery_21Percent_returnsFalse() {
+            val level = 21
+            val intent = createIntent(batteryLevel = level)
+
+            assertWithMessage("failed by isLowBattery(intent), level=$level")
+                .that(BatteryStatus.isLowBattery(intent))
+                .isFalse()
+            assertWithMessage("failed by isLowBattery($level)")
+                .that(BatteryStatus.isLowBattery(intent))
+                .isFalse()
+        }
+
+        @Test
+        fun isSevereLowBattery_10Percent_returnsTrue() {
+            val batteryChangedIntent = createIntent(batteryLevel = 10)
+
+            assertThat(BatteryStatus.isSevereLowBattery(batteryChangedIntent)).isTrue()
+        }
+
+        @Test
+        fun isSevereLowBattery_11Percent_returnFalse() {
+            val batteryChangedIntent = createIntent(batteryLevel = 11)
+
+            assertThat(BatteryStatus.isSevereLowBattery(batteryChangedIntent)).isFalse()
+        }
+
+        @Test
+        fun isExtremeLowBattery_3Percent_returnsTrue() {
+            val batteryChangedIntent = createIntent(batteryLevel = 3)
+
+            assertThat(BatteryStatus.isExtremeLowBattery(batteryChangedIntent)).isTrue()
+        }
+
+        @Test
+        fun isExtremeLowBattery_4Percent_returnsFalse() {
+            val batteryChangedIntent = createIntent(batteryLevel = 4)
+
+            assertThat(BatteryStatus.isExtremeLowBattery(batteryChangedIntent)).isFalse()
+        }
+
+        @Test
+        fun isBatteryDefender_chargingLongLife_returnsTrue() {
+            val chargingStatus = CHARGING_POLICY_ADAPTIVE_LONGLIFE
+            val batteryChangedIntent = createIntent(chargingStatus = chargingStatus)
+
+            assertIsBatteryDefender(chargingStatus, batteryChangedIntent).isTrue()
+        }
+
+        @Test
+        fun isBatteryDefender_nonChargingLongLife_returnsFalse() {
+            val chargingStatus = CHARGING_POLICY_DEFAULT
+            val batteryChangedIntent = createIntent(chargingStatus = chargingStatus)
+
+            assertIsBatteryDefender(chargingStatus, batteryChangedIntent).isFalse()
+        }
+
+        private fun assertIsBatteryDefender(chargingStatus: Int, batteryChangedIntent: Intent) =
+            object {
+                val assertions =
+                    listOf(
+                        "failed by isBatteryDefender(Intent), chargingStatus=$chargingStatus".let {
+                            assertWithMessage(it).that(isBatteryDefender(batteryChangedIntent))
+                        },
+                        "failed by isBatteryDefender($chargingStatus)".let {
+                            assertWithMessage(it).that(isBatteryDefender(chargingStatus))
+                        },
+                    )
+
+                fun isTrue() = assertions.forEach { it.isTrue() }
+
+                fun isFalse() = assertions.forEach { it.isFalse() }
+            }
+    }
+
+    @RunWith(Parameterized::class)
+    class IsPluggedInTest(
+        private val name: String,
+        private val plugged: Int,
+        val expected: Boolean
+    ) : BatteryStatusTest() {
+
+        @Test
+        fun isPluggedIn_() {
+            val batteryChangedIntent = createIntent(plugged = plugged)
+
+            assertWithMessage("failed by isPluggedIn(plugged=$plugged)")
+                .that(BatteryStatus.isPluggedIn(plugged))
+                .isEqualTo(expected)
+            assertWithMessage("failed by isPlugged(Intent), which plugged=$plugged")
+                .that(BatteryStatus.isPluggedIn(batteryChangedIntent))
+                .isEqualTo(expected)
+        }
+
+        companion object {
+            @Parameterized.Parameters(name = "{0}")
+            @JvmStatic
+            fun parameters() =
+                arrayListOf(
+                    arrayOf("withAC_returnsTrue", BATTERY_PLUGGED_AC, true),
+                    arrayOf("withDock_returnsTrue", BATTERY_PLUGGED_DOCK, true),
+                    arrayOf("withUSB_returnsTrue", BATTERY_PLUGGED_USB, true),
+                    arrayOf("withWireless_returnsTrue", BATTERY_PLUGGED_WIRELESS, true),
+                    arrayOf("pluggedNone_returnsTrue", BATTERY_PLUGGED_NONE, false),
+                )
+        }
+    }
+
+    @RunWith(Parameterized::class)
+    class IsPluggedInDockTest(
+        private val name: String,
+        private val plugged: Int,
+        val expected: Boolean
+    ) : BatteryStatusTest() {
+
+        @Test
+        fun isPluggedDockIn_() {
+            val batteryChangedIntent = createIntent(plugged = plugged)
+
+            assertWithMessage("failed by isPluggedInDock(plugged=$plugged)")
+                .that(BatteryStatus.isPluggedInDock(plugged))
+                .isEqualTo(expected)
+            assertWithMessage("failed by isPluggedInDock(Intent), which plugged=$plugged")
+                .that(BatteryStatus.isPluggedInDock(batteryChangedIntent))
+                .isEqualTo(expected)
+        }
+
+        companion object {
+            @Parameterized.Parameters(name = "{0}")
+            @JvmStatic
+            fun parameters() =
+                arrayListOf(
+                    arrayOf("withAC_returnsTrue", BATTERY_PLUGGED_AC, false),
+                    arrayOf("withDock_returnsTrue", BATTERY_PLUGGED_DOCK, true),
+                    arrayOf("withUSB_returnsTrue", BATTERY_PLUGGED_USB, false),
+                    arrayOf("withWireless_returnsTrue", BATTERY_PLUGGED_WIRELESS, false),
+                    arrayOf("pluggedNone_returnsTrue", BATTERY_PLUGGED_NONE, false),
+                )
+        }
+    }
+
+    @RunWith(Parameterized::class)
+    class IsChargedTest(
+        private val status: Int,
+        private val batteryLevel: Int,
+        private val expected: Boolean
+    ) : BatteryStatusTest() {
+
+        @Test
+        fun isCharged_() {
+            val batteryChangedIntent = createIntent(batteryLevel = batteryLevel, status = status)
+
+            assertWithMessage(
+                    "failed by isCharged(Intent), status=$status, batteryLevel=$batteryLevel"
+                )
+                .that(BatteryStatus.isCharged(batteryChangedIntent))
+                .isEqualTo(expected)
+            assertWithMessage("failed by isCharged($status, $batteryLevel)")
+                .that(BatteryStatus.isCharged(status, batteryLevel))
+                .isEqualTo(expected)
+        }
+
+        companion object {
+            @Parameterized.Parameters(name = "status{0}_level{1}_returns-{2}")
+            @JvmStatic
+            fun parameters() =
+                arrayListOf(
+                    arrayOf(BATTERY_STATUS_FULL, 99, true),
+                    arrayOf(BATTERY_STATUS_UNKNOWN, 100, true),
+                    arrayOf(BATTERY_STATUS_FULL, 100, true),
+                    arrayOf(BATTERY_STATUS_UNKNOWN, 99, false),
+                )
+        }
+    }
+
+    @RunWith(Parameterized::class)
+    class GetChargingSpeedTest(
+        private val name: String,
+        private val maxChargingCurrent: Optional<Int>,
+        private val maxChargingVoltage: Optional<Int>,
+        private val expectedChargingSpeed: Int,
+    ) {
+
+        val context: Context = ApplicationProvider.getApplicationContext()
+
+        @Test
+        fun getChargingSpeed_() {
+            val batteryChangedIntent =
+                Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+                    maxChargingCurrent.ifPresent { putExtra(EXTRA_MAX_CHARGING_CURRENT, it) }
+                    maxChargingVoltage.ifPresent { putExtra(EXTRA_MAX_CHARGING_VOLTAGE, it) }
+                }
+
+            assertThat(BatteryStatus.getChargingSpeed(context, batteryChangedIntent))
+                .isEqualTo(expectedChargingSpeed)
+        }
+
+        companion object {
+            @Parameterized.Parameters(name = "{0}")
+            @JvmStatic
+            fun parameters() =
+                arrayListOf(
+                    arrayOf(
+                        "maxCurrent=n/a, maxVoltage=n/a -> UNKNOWN",
+                        Optional.empty<Int>(),
+                        Optional.empty<Int>(),
+                        CHARGING_UNKNOWN
+                    ),
+                    arrayOf(
+                        "maxCurrent=0, maxVoltage=9000000 -> UNKNOWN",
+                        Optional.of(0),
+                        Optional.of(0),
+                        CHARGING_UNKNOWN
+                    ),
+                    arrayOf(
+                        "maxCurrent=1500000, maxVoltage=5000000 -> CHARGING_REGULAR",
+                        Optional.of(1500000),
+                        Optional.of(5000000),
+                        CHARGING_REGULAR
+                    ),
+                    arrayOf(
+                        "maxCurrent=1000000, maxVoltage=5000000 -> CHARGING_REGULAR",
+                        Optional.of(1000000),
+                        Optional.of(5000000),
+                        CHARGING_REGULAR
+                    ),
+                    arrayOf(
+                        "maxCurrent=1500001, maxVoltage=5000000 -> CHARGING_FAST",
+                        Optional.of(1501000),
+                        Optional.of(5000000),
+                        CHARGING_FAST
+                    ),
+                    arrayOf(
+                        "maxCurrent=999999, maxVoltage=5000000 -> CHARGING_SLOWLY",
+                        Optional.of(999999),
+                        Optional.of(5000000),
+                        CHARGING_SLOWLY
+                    ),
+                )
+        }
+    }
+
+    protected fun createIntent(
+        batteryLevel: Int = 50,
+        chargingStatus: Int = CHARGING_POLICY_DEFAULT,
+        plugged: Int = BATTERY_PLUGGED_NONE,
+        status: Int = BatteryManager.BATTERY_STATUS_CHARGING,
+    ): Intent =
+        Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+            putExtra(BatteryManager.EXTRA_STATUS, status)
+            putExtra(BatteryManager.EXTRA_LEVEL, batteryLevel)
+            putExtra(BatteryManager.EXTRA_SCALE, 100)
+            putExtra(BatteryManager.EXTRA_CHARGING_STATUS, chargingStatus)
+            putExtra(BatteryManager.EXTRA_PLUGGED, plugged)
+        }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 6a5535d..6b0a906 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -61,6 +61,7 @@
         Settings.System.TTY_MODE,
         Settings.System.MASTER_MONO,
         Settings.System.MASTER_BALANCE,
+        Settings.System.STAY_AWAKE_ON_FOLD,
         Settings.System.SOUND_EFFECTS_ENABLED,
         Settings.System.HAPTIC_FEEDBACK_ENABLED,
         Settings.System.POWER_SOUNDS_ENABLED,       // moved to global
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index f6c2f69..e5f7f88 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -259,6 +259,8 @@
         VALIDATORS.put(Secure.NAV_BAR_KIDS_MODE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 Secure.NAVIGATION_MODE, new DiscreteValueValidator(new String[] {"0", "1", "2"}));
+        VALIDATORS.put(Secure.NAVIGATION_MODE_RESTORE,
+                new DiscreteValueValidator(new String[] {"-1", "0", "1", "2"}));
         VALIDATORS.put(Secure.BACK_GESTURE_INSET_SCALE_LEFT,
                 new InclusiveFloatRangeValidator(0.0f, Float.MAX_VALUE));
         VALIDATORS.put(Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 753c860..a08d07e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -218,6 +218,7 @@
         VALIDATORS.put(System.WIFI_STATIC_DNS1, LENIENT_IP_ADDRESS_VALIDATOR);
         VALIDATORS.put(System.WIFI_STATIC_DNS2, LENIENT_IP_ADDRESS_VALIDATOR);
         VALIDATORS.put(System.SHOW_BATTERY_PERCENT, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.STAY_AWAKE_ON_FOLD, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.WEAR_ACCESSIBILITY_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.CLOCKWORK_BLUETOOTH_SETTINGS_PREF, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 11154d1..056be2e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -813,7 +813,11 @@
                 continue;
             }
 
-            if (settingsToPreserve.contains(getQualifiedKeyForSetting(key, contentUri))) {
+            // Filter out Settings.Secure.NAVIGATION_MODE from modified preserve settings.
+            // Let it take part in restore process. See also b/244532342.
+            boolean isSettingPreserved = settingsToPreserve.contains(
+                    getQualifiedKeyForSetting(key, contentUri));
+            if (isSettingPreserved && !Settings.Secure.NAVIGATION_MODE.equals(key)) {
                 Log.i(TAG, "Skipping restore for setting " + key + " as it is marked as "
                         + "preserved");
                 continue;
@@ -874,6 +878,23 @@
             } else {
                 destination = contentUri;
             }
+
+            // Value is written to NAVIGATION_MODE_RESTORE to mark navigation mode
+            // has been set before on source device.
+            // See also: b/244532342.
+            if (Settings.Secure.NAVIGATION_MODE.equals(key)) {
+                contentValues.clear();
+                contentValues.put(Settings.NameValueTable.NAME,
+                        Settings.Secure.NAVIGATION_MODE_RESTORE);
+                contentValues.put(Settings.NameValueTable.VALUE, value);
+                cr.insert(destination, contentValues);
+                // Avoid restore original setting if it has been preserved.
+                if (isSettingPreserved) {
+                    Log.i(TAG, "Skipping restore for setting navigation_mode "
+                        + "as it is marked as preserved");
+                    continue;
+                }
+            }
             settingsHelper.restoreValue(this, cr, contentValues, destination, key, value,
                     mRestoredFromSdkInt);
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 9da1ab8..27a45df 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -44,6 +44,7 @@
 import com.android.internal.app.LocalePicker;
 import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
 
+import java.io.FileNotFoundException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Locale;
@@ -332,21 +333,30 @@
      * @param value can be a canonicalized uri or "_silent" to indicate a silent (null) ringtone.
      */
     private void setRingtone(String name, String value) {
-        // If it's null, don't change the default
-        if (value == null) return;
-        final Uri ringtoneUri;
-        if (SILENT_RINGTONE.equals(value)) {
-            ringtoneUri = null;
-        } else {
-            Uri canonicalUri = Uri.parse(value);
-            ringtoneUri = mContext.getContentResolver().uncanonicalize(canonicalUri);
-            if (ringtoneUri == null) {
-                // Unrecognized or invalid Uri, don't restore
-                return;
-            }
-        }
-        final int ringtoneType = getRingtoneType(name);
+        Log.v(TAG, "Set ringtone for name: " + name + " value: " + value);
 
+        // If it's null, don't change the default.
+        if (value == null) return;
+        final int ringtoneType = getRingtoneType(name);
+        if (SILENT_RINGTONE.equals(value)) {
+            // SILENT_RINGTONE is a special constant generated by onBackupValue in the source
+            // device.
+            RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, null);
+            return;
+        }
+
+        Uri ringtoneUri = null;
+        try {
+            ringtoneUri =
+                    RingtoneManager.getRingtoneUriForRestore(
+                            mContext.getContentResolver(), value, ringtoneType);
+        } catch (FileNotFoundException | IllegalArgumentException e) {
+            Log.w(TAG, "Failed to resolve " + value + ": " + e);
+            // Unrecognized or invalid Uri, don't restore
+            return;
+        }
+
+        Log.v(TAG, "setActualDefaultRingtoneUri type: " + ringtoneType + ", uri: " + ringtoneUri);
         RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, ringtoneUri);
     }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 23b6308..f60f8db 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3227,6 +3227,15 @@
             return settingsState.getSettingLocked(name);
         }
 
+        private static boolean shouldExcludeSettingFromReset(Setting setting, String prefix) {
+            // If a prefix was specified, exclude settings whose names don't start with it.
+            if (prefix != null && !setting.getName().startsWith(prefix)) {
+                return true;
+            }
+            // Never reset SECURE_FRP_MODE, as it could be abused to bypass FRP via RescueParty.
+            return Global.SECURE_FRP_MODE.equals(setting.getName());
+        }
+
         public void resetSettingsLocked(int type, int userId, String packageName, int mode,
                 String tag) {
             resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/
@@ -3249,7 +3258,7 @@
                         Setting setting = settingsState.getSettingLocked(name);
                         if (packageName.equals(setting.getPackageName())) {
                             if ((tag != null && !tag.equals(setting.getTag()))
-                                    || (prefix != null && !setting.getName().startsWith(prefix))) {
+                                    || shouldExcludeSettingFromReset(setting, prefix)) {
                                 continue;
                             }
                             if (settingsState.resetSettingLocked(name)) {
@@ -3270,7 +3279,7 @@
                         Setting setting = settingsState.getSettingLocked(name);
                         if (!SettingsState.isSystemPackage(getContext(),
                                 setting.getPackageName())) {
-                            if (prefix != null && !setting.getName().startsWith(prefix)) {
+                            if (shouldExcludeSettingFromReset(setting, prefix)) {
                                 continue;
                             }
                             if (settingsState.resetSettingLocked(name)) {
@@ -3291,7 +3300,7 @@
                         Setting setting = settingsState.getSettingLocked(name);
                         if (!SettingsState.isSystemPackage(getContext(),
                                 setting.getPackageName())) {
-                            if (prefix != null && !setting.getName().startsWith(prefix)) {
+                            if (shouldExcludeSettingFromReset(setting, prefix)) {
                                 continue;
                             }
                             if (setting.isDefaultFromSystem()) {
@@ -3316,7 +3325,7 @@
                     for (String name : settingsState.getSettingNamesLocked()) {
                         Setting setting = settingsState.getSettingLocked(name);
                         boolean someSettingChanged = false;
-                        if (prefix != null && !setting.getName().startsWith(prefix)) {
+                        if (shouldExcludeSettingFromReset(setting, prefix)) {
                             continue;
                         }
                         if (setting.isDefaultFromSystem()) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 873b434..9b8e473 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -345,6 +345,8 @@
                     Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS,
                     Settings.Global.MOBILE_DATA, // Candidate for backup?
                     Settings.Global.MOBILE_DATA_ALWAYS_ON,
+                    Settings.Global.DSRM_DURATION_MILLIS,
+                    Settings.Global.DSRM_ENABLED_ACTIONS,
                     Settings.Global.MODE_RINGER,
                     Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
                     Settings.Global.MULTI_SIM_SMS_PROMPT,
@@ -590,6 +592,7 @@
                     Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS,
                     Settings.Global.AUTO_REVOKE_PARAMETERS,
                     Settings.Global.ENABLE_RADIO_BUG_DETECTION,
+                    Settings.Global.REPAIR_MODE_ACTIVE,
                     Settings.Global.RADIO_BUG_WAKELOCK_TIMEOUT_COUNT_THRESHOLD,
                     Settings.Global.RADIO_BUG_SYSTEM_ERROR_COUNT_THRESHOLD,
                     Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT,
@@ -700,6 +703,7 @@
                  Settings.Secure.ASSIST_SCREENSHOT_ENABLED,
                  Settings.Secure.ASSIST_STRUCTURE_ENABLED,
                  Settings.Secure.ATTENTIVE_TIMEOUT,
+                 Settings.Secure.AUDIO_DEVICE_INVENTORY, // setting not controllable by user
                  Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION,
                  Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT,
                  Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE,
@@ -724,6 +728,7 @@
                  Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
                  Settings.Secure.CONTENT_CAPTURE_ENABLED,
                  Settings.Secure.DEFAULT_INPUT_METHOD,
+                 Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
                  Settings.Secure.DEVICE_PAIRED,
                  Settings.Secure.DIALER_DEFAULT_APPLICATION,
                  Settings.Secure.DISABLED_PRINT_SERVICES,
@@ -843,7 +848,8 @@
                  Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
                  Settings.Secure.UI_TRANSLATION_ENABLED,
                  Settings.Secure.CREDENTIAL_SERVICE,
-                 Settings.Secure.CREDENTIAL_SERVICE_PRIMARY);
+                 Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
+                 Settings.Secure.NAVIGATION_MODE_RESTORE);
 
     @Test
     public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
index bc81c44..ef062df 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -26,15 +26,24 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.MatrixCursor;
 import android.media.AudioManager;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.LocaleList;
+import android.provider.BaseColumns;
+import android.provider.MediaStore;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -57,6 +66,13 @@
     private static final String SETTING_VALUE = "setting_value";
     private static final String SETTING_REAL_VALUE = "setting_real_value";
 
+    private static final String DEFAULT_RINGTONE_VALUE =
+            "content://media/internal/audio/media/10?title=DefaultRingtone&canonical=1";
+    private static final String DEFAULT_NOTIFICATION_VALUE =
+            "content://media/internal/audio/media/20?title=DefaultNotification&canonical=1";
+    private static final String DEFAULT_ALARM_VALUE =
+            "content://media/internal/audio/media/30?title=DefaultAlarm&canonical=1";
+
     private SettingsHelper mSettingsHelper;
 
     @Mock private Context mContext;
@@ -74,6 +90,7 @@
                 mTelephonyManager);
         when(mContext.getResources()).thenReturn(mResources);
         when(mContext.getApplicationContext()).thenReturn(mContext);
+        when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
         when(mContext.getContentResolver()).thenReturn(getContentResolver());
 
         mSettingsHelper = spy(new SettingsHelper(mContext));
@@ -338,6 +355,377 @@
     }
 
     @Test
+    public void testRestoreValue_customRingtone_regularUncanonicalize_Success() {
+        final String sourceRingtoneValue =
+                "content://media/internal/audio/media/1?title=Song&canonical=1";
+        final String newRingtoneValueUncanonicalized =
+                "content://media/internal/audio/media/100";
+        final String newRingtoneValueCanonicalized =
+                "content://media/internal/audio/media/100?title=Song&canonical=1";
+
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        assertThat(url).isEqualTo(Uri.parse(sourceRingtoneValue));
+                        return Uri.parse(newRingtoneValueUncanonicalized);
+                    }
+
+                    @Override
+                    public Uri canonicalize(Uri url) {
+                        assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+                        return Uri.parse(newRingtoneValueCanonicalized);
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+                .isEqualTo(DEFAULT_RINGTONE_VALUE);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.RINGTONE,
+                sourceRingtoneValue,
+                0);
+
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+                .isEqualTo(newRingtoneValueCanonicalized);
+    }
+
+    @Test
+    public void testRestoreValue_customRingtone_useCustomLookup_success() {
+        final String sourceRingtoneValue =
+                "content://0@media/external/audio/media/1?title=Song&canonical=1";
+        final String newRingtoneValueUncanonicalized =
+                "content://0@media/external/audio/media/100";
+        final String newRingtoneValueCanonicalized =
+                "content://0@media/external/audio/media/100?title=Song&canonical=1";
+
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+        cursor.addRow(new Object[] {100L});
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        // mock the lookup failure in regular MediaProvider.uncanonicalize.
+                        return null;
+                    }
+
+                    @Override
+                    public Uri canonicalize(Uri url) {
+                        assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+                        return Uri.parse(newRingtoneValueCanonicalized);
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+
+                    @Override
+                    public Cursor query(
+                            Uri uri,
+                            String[] projection,
+                            String selection,
+                            String[] selectionArgs,
+                            String sortOrder) {
+                        assertThat(uri)
+                                .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+                        assertThat(projection).isEqualTo(new String[] {"_id"});
+                        assertThat(selection).isEqualTo("is_ringtone=1 AND title=?");
+                        assertThat(selectionArgs).isEqualTo(new String[] {"Song"});
+                        return cursor;
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.RINGTONE,
+                sourceRingtoneValue,
+                0);
+
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+                .isEqualTo(newRingtoneValueCanonicalized);
+    }
+
+    @Test
+    public void testRestoreValue_customRingtone_notificationSound_useCustomLookup_success() {
+        final String sourceRingtoneValue =
+                "content://0@media/external/audio/media/2?title=notificationPing&canonical=1";
+        final String newRingtoneValueUncanonicalized =
+                "content://0@media/external/audio/media/200";
+        final String newRingtoneValueCanonicalized =
+                "content://0@media/external/audio/media/200?title=notificationPing&canonicalize=1";
+
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+        cursor.addRow(new Object[] {200L});
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        // mock the lookup failure in regular MediaProvider.uncanonicalize.
+                        return null;
+                    }
+
+                    @Override
+                    public Uri canonicalize(Uri url) {
+                        assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+                        return Uri.parse(newRingtoneValueCanonicalized);
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+
+                    @Override
+                    public Cursor query(
+                            Uri uri,
+                            String[] projection,
+                            String selection,
+                            String[] selectionArgs,
+                            String sortOrder) {
+                        assertThat(uri)
+                                .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+                        assertThat(projection).isEqualTo(new String[] {"_id"});
+                        assertThat(selection).isEqualTo("is_notification=1 AND title=?");
+                        assertThat(selectionArgs).isEqualTo(new String[] {"notificationPing"});
+                        return cursor;
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.NOTIFICATION_SOUND,
+                sourceRingtoneValue,
+                0);
+
+        assertThat(
+                        Settings.System.getString(
+                                mMockContentResolver, Settings.System.NOTIFICATION_SOUND))
+                .isEqualTo(newRingtoneValueCanonicalized);
+    }
+
+    @Test
+    public void testRestoreValue_customRingtone_alarmSound_useCustomLookup_success() {
+        final String sourceRingtoneValue =
+                "content://0@media/external/audio/media/3?title=alarmSound&canonical=1";
+        final String newRingtoneValueUncanonicalized =
+                "content://0@media/external/audio/media/300";
+        final String newRingtoneValueCanonicalized =
+                "content://0@media/external/audio/media/300?title=alarmSound&canonical=1";
+
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+        cursor.addRow(new Object[] {300L});
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        // mock the lookup failure in regular MediaProvider.uncanonicalize.
+                        return null;
+                    }
+
+                    @Override
+                    public Uri canonicalize(Uri url) {
+                        assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+                        return Uri.parse(newRingtoneValueCanonicalized);
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+
+                    @Override
+                    public Cursor query(
+                            Uri uri,
+                            String[] projection,
+                            String selection,
+                            String[] selectionArgs,
+                            String sortOrder) {
+                        assertThat(uri)
+                                .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+                        assertThat(projection).isEqualTo(new String[] {"_id"});
+                        assertThat(selection).isEqualTo("is_alarm=1 AND title=?");
+                        assertThat(selectionArgs).isEqualTo(new String[] {"alarmSound"});
+                        return cursor;
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.ALARM_ALERT,
+                sourceRingtoneValue,
+                0);
+
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.ALARM_ALERT))
+                .isEqualTo(newRingtoneValueCanonicalized);
+    }
+
+    @Test
+    public void testRestoreValue_customRingtone_useCustomLookup_multipleResults_notRestore() {
+        final String sourceRingtoneValue =
+                "content://0@media/external/audio/media/1?title=Song&canonical=1";
+
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        // This is to mock the case that there are multiple results by querying title +
+        // ringtone_type.
+        MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+        cursor.addRow(new Object[] {100L});
+        cursor.addRow(new Object[] {110L});
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        // mock the lookup failure in regular MediaProvider.uncanonicalize.
+                        return null;
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.RINGTONE,
+                sourceRingtoneValue,
+                0);
+
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+                .isEqualTo(DEFAULT_RINGTONE_VALUE);
+    }
+
+    @Test
+    public void testRestoreValue_customRingtone_restoreSilentValue() {
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        // mock the lookup failure in regular MediaProvider.uncanonicalize.
+                        return null;
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.RINGTONE,
+                "_silent",
+                0);
+
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+                .isEqualTo(null);
+    }
+
+    public static class MockSettingsProvider extends MockContentProvider {
+        ContentResolver mBaseContentResolver;
+
+        public MockSettingsProvider(Context context, ContentResolver baseContentResolver) {
+            super(context);
+            this.mBaseContentResolver = baseContentResolver;
+        }
+
+        @Override
+        public Bundle call(String method, String request, Bundle args) {
+            return mBaseContentResolver.call(Settings.AUTHORITY, method, request, args);
+        }
+    }
+
+    @Test
     public void restoreValue_autoRotation_deviceStateAutoRotationDisabled_restoresValue() {
         when(mResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults))
                 .thenReturn(new String[]{});
@@ -400,4 +788,20 @@
         Settings.Global.putString(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, null);
         Settings.Global.putString(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP, null);
     }
+
+    private void resetRingtoneSettingsToDefault(ContentResolver contentResolver) {
+        Settings.System.putString(
+                contentResolver, Settings.System.RINGTONE, DEFAULT_RINGTONE_VALUE);
+        Settings.System.putString(
+                contentResolver, Settings.System.NOTIFICATION_SOUND, DEFAULT_NOTIFICATION_VALUE);
+        Settings.System.putString(
+                contentResolver, Settings.System.ALARM_ALERT, DEFAULT_ALARM_VALUE);
+
+        assertThat(Settings.System.getString(contentResolver, Settings.System.RINGTONE))
+                .isEqualTo(DEFAULT_RINGTONE_VALUE);
+        assertThat(Settings.System.getString(contentResolver, Settings.System.NOTIFICATION_SOUND))
+                .isEqualTo(DEFAULT_NOTIFICATION_VALUE);
+        assertThat(Settings.System.getString(contentResolver, Settings.System.ALARM_ALERT))
+                .isEqualTo(DEFAULT_ALARM_VALUE);
+    }
 }
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
index eaf0dcb..a945c33 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
@@ -464,6 +464,31 @@
         }
     }
 
+    // To prevent FRP bypasses, the SECURE_FRP_MODE setting should not be reset when all other
+    // settings are reset.  But it should still be possible to explicitly set its value.
+    @Test
+    public void testSecureFrpModeSettingCannotBeReset() throws Exception {
+        final String name = Settings.Global.SECURE_FRP_MODE;
+        final String origValue = getSetting(SETTING_TYPE_GLOBAL, name);
+        setSettingViaShell(SETTING_TYPE_GLOBAL, name, "1", false);
+        try {
+            assertEquals("1", getSetting(SETTING_TYPE_GLOBAL, name));
+            for (int type : new int[] { SETTING_TYPE_GLOBAL, SETTING_TYPE_SECURE }) {
+                resetSettingsViaShell(type, Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
+                resetSettingsViaShell(type, Settings.RESET_MODE_UNTRUSTED_CHANGES);
+                resetSettingsViaShell(type, Settings.RESET_MODE_TRUSTED_DEFAULTS);
+            }
+            // The value should still be "1".  It should not have been reset to null.
+            assertEquals("1", getSetting(SETTING_TYPE_GLOBAL, name));
+            // It should still be possible to explicitly set the value to "0".
+            setSettingViaShell(SETTING_TYPE_GLOBAL, name, "0", false);
+            assertEquals("0", getSetting(SETTING_TYPE_GLOBAL, name));
+        } finally {
+            setSettingViaShell(SETTING_TYPE_GLOBAL, name, origValue, false);
+            assertEquals(origValue, getSetting(SETTING_TYPE_GLOBAL, name));
+        }
+    }
+
     private void doTestQueryStringInBracketsViaProviderApiForType(int type) {
         // Make sure we have a clean slate.
         deleteStringViaProviderApi(type, FAKE_SETTING_NAME);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7df3dfb..07f7ecd 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -65,6 +65,7 @@
                 "androidx.compose.runtime_runtime",
                 "androidx.compose.material3_material3",
                 "androidx.activity_activity-compose",
+                "androidx.compose.animation_animation-graphics",
             ],
 
             // By default, Compose is disabled and we compile the ComposeFacade
@@ -122,6 +123,7 @@
     ],
     static_libs: [
         "SystemUISharedLib",
+        "SystemUICustomizationLib",
         "SettingsLib",
         "androidx.leanback_leanback",
         "androidx.slice_slice-core",
@@ -191,6 +193,7 @@
         "SystemUI-tags",
         "SystemUI-proto",
         "monet",
+        "libmonet",
         "dagger2",
         "jsr305",
         "jsr330",
@@ -226,6 +229,17 @@
 }
 
 filegroup {
+    name: "SystemUI-test-fakes",
+    srcs: [
+        /* Status bar fakes */
+        "tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt",
+        "tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt",
+        "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt",
+    ],
+    path: "tests/src",
+}
+
+filegroup {
     name: "SystemUI-tests-robolectric-pilots",
     srcs: [
         /* Keyguard converted tests */
@@ -250,17 +264,17 @@
         "tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt",
         "tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt",
         // domain
-        "tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt",
+        "tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt",
+        "tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt",
+        "tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt",
-        "tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt",
-        "tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt",
         // ui
+        "tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt",
         "tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt",
-        "tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt",
         "tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt",
         "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt",
         "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt",
@@ -271,7 +285,6 @@
         "tests/src/com/android/systemui/dock/DockManagerFake.java",
         "tests/src/com/android/systemui/dump/LogBufferHelper.kt",
         "tests/src/com/android/systemui/statusbar/phone/FakeKeyguardStateController.java",
-        "tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt",
 
         /* Biometric converted tests */
         "tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt",
@@ -290,6 +303,13 @@
         "tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt",
         "tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt",
         "tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt",
+
+        /* Status bar wifi converted tests */
+        "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt",
+        "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt",
+        "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt",
+        "tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt",
+        "tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt",
     ],
     path: "tests/src",
 }
@@ -355,6 +375,7 @@
         "testables",
         "truth-prebuilt",
         "monet",
+        "libmonet",
         "dagger2",
         "jsr330",
         "WindowManager-Shell",
@@ -384,9 +405,11 @@
     static_libs: [
         "SystemUI-tests-base",
         "androidx.test.uiautomator_uiautomator",
+        "androidx.core_core-animation-testing",
         "mockito-target-extended-minus-junit4",
         "androidx.test.ext.junit",
         "androidx.test.ext.truth",
+        "kotlin-test",
     ],
     libs: [
         "android.test.runner",
@@ -448,10 +471,12 @@
         "tests/robolectric/src/**/*.kt",
         "tests/robolectric/src/**/*.java",
         ":SystemUI-tests-utils",
+        ":SystemUI-test-fakes",
         ":SystemUI-tests-robolectric-pilots",
     ],
     static_libs: [
         "androidx.test.uiautomator_uiautomator",
+        "androidx.core_core-animation-testing",
         "androidx.test.ext.junit",
         "inline-mockito-robolectric-prebuilt",
     ],
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5c03698..4fd4723 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -176,6 +176,7 @@
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
     <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"/>
+    <uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
 
     <!-- Assist -->
     <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
@@ -369,7 +370,7 @@
         android:defaultToDeviceProtectedStorage="true"
         android:directBootAware="true"
         tools:replace="android:appComponentFactory"
-        android:appComponentFactory=".SystemUIAppComponentFactory">
+        android:appComponentFactory=".PhoneSystemUIAppComponentFactory">
         <!-- Keep theme in sync with SystemUIApplication.onCreate().
              Setting the theme on the application does not affect views inflated by services.
              The application theme is set again from onCreate to take effect for those views. -->
@@ -451,12 +452,14 @@
             android:noHistory="true" />
 
         <service android:name=".screenshot.appclips.AppClipsScreenshotHelperService"
-            android:permission="com.android.systemui.permission.SELF"
-            android:exported="false" />
+            android:exported="false"
+            android:singleUser="true"
+            android:permission="com.android.systemui.permission.SELF" />
 
         <service android:name=".screenshot.appclips.AppClipsService"
-            android:permission="android.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE"
-            android:exported="true" />
+            android:exported="true"
+            android:singleUser="true"
+            android:permission="android.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE" />
 
         <service android:name=".screenrecord.RecordingService"
                  android:foregroundServiceType="systemExempted"/>
@@ -814,7 +817,8 @@
         <service
             android:name=".dreams.DreamOverlayService"
             android:enabled="false"
-            android:exported="true" />
+            android:exported="true"
+            android:singleUser="true" />
 
         <activity android:name=".keyguard.WorkLockActivity"
                   android:label="@string/accessibility_desc_work_lock"
@@ -861,7 +865,7 @@
         <activity
             android:name=".settings.brightness.BrightnessDialog"
             android:label="@string/quick_settings_brightness_dialog_title"
-            android:theme="@style/Theme.SystemUI.QuickSettings.BrightnessDialog"
+            android:theme="@style/BrightnessDialog"
             android:finishOnCloseSystemDialogs="true"
             android:launchMode="singleInstance"
             android:excludeFromRecents="true"
@@ -992,6 +996,11 @@
 
         <service android:name=".notetask.NoteTaskControllerUpdateService" />
 
+        <service android:name=".notetask.NoteTaskBubblesController$NoteTaskBubblesService"
+            android:exported="false"
+            android:singleUser="true"
+            android:permission="com.android.systemui.permission.SELF" />
+
         <activity
             android:name=".notetask.shortcut.LaunchNoteTaskActivity"
             android:exported="true"
@@ -1004,20 +1013,10 @@
             </intent-filter>
         </activity>
 
-        <!-- LaunchNoteTaskManagedProfileProxyActivity MUST NOT be exported because it allows caller
-             to specify an Android user when launching the default notes app. -->
-        <activity
-            android:name=".notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity"
-            android:exported="false"
-            android:enabled="true"
-            android:excludeFromRecents="true"
-            android:theme="@android:style/Theme.NoDisplay" />
-
         <activity
             android:name=".notetask.LaunchNotesRoleSettingsTrampolineActivity"
             android:exported="true"
             android:excludeFromRecents="true"
-            android:resizeableActivity="false"
             android:theme="@android:style/Theme.NoDisplay" >
             <intent-filter>
                 <action android:name="com.android.systemui.action.MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE" />
@@ -1069,15 +1068,6 @@
                   android:exported="true">
         </provider>
 
-        <!-- Provides list and realistic previews of clock faces for the picker app. -->
-        <provider
-            android:name="com.android.keyguard.clock.ClockOptionsProvider"
-            android:authorities="com.android.keyguard.clock"
-            android:enabled="false"
-            android:exported="false"
-            android:grantUriPermissions="true">
-        </provider>
-
         <receiver
             android:name=".statusbar.KeyboardShortcutsReceiver"
             android:exported="true">
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 1ce3472..b708fc2 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -88,7 +88,6 @@
 tracyzhou@google.com
 tsuji@google.com
 twickham@google.com
-vadimt@google.com
 victortulias@google.com
 winsonc@google.com
 wleshner@google.com
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 48dd08f..187d073 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -32,10 +32,10 @@
 import android.view.WindowInsets
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
-import android.widget.FrameLayout
 import com.android.app.animation.Interpolators
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.jank.InteractionJankMonitor.CujType
+import com.android.systemui.util.maybeForceFullscreen
 import com.android.systemui.util.registerAnimationOnBackInvoked
 import kotlin.math.roundToInt
 
@@ -249,7 +249,7 @@
         // intent is to launch a dialog from another dialog.
         val animatedParent =
             openedDialogs.firstOrNull {
-                it.dialog.window.decorView.viewRootImpl == controller.viewRoot
+                it.dialog.window?.decorView?.viewRootImpl == controller.viewRoot
             }
         val controller =
             animatedParent?.dialogContentWithBackground?.let {
@@ -336,7 +336,7 @@
     ): ActivityLaunchAnimator.Controller? {
         val animatedDialog =
             openedDialogs.firstOrNull {
-                it.dialog.window.decorView.viewRootImpl == view.viewRootImpl
+                it.dialog.window?.decorView?.viewRootImpl == view.viewRootImpl
             }
                 ?: return null
         return createActivityLaunchController(animatedDialog, cujType)
@@ -417,7 +417,7 @@
                 animatedDialog.prepareForStackDismiss()
 
                 // Remove the dim.
-                dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+                dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
             }
 
             override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
@@ -621,96 +621,12 @@
 
                 viewGroupWithBackground
             } else {
-                // We will make the dialog window (and therefore its DecorView) fullscreen to make
-                // it possible to animate outside its bounds.
-                //
-                // Before that, we add a new View as a child of the DecorView with the same size and
-                // gravity as that DecorView, then we add all original children of the DecorView to
-                // that new View. Finally we remove the background of the DecorView and add it to
-                // the new View, then we make the DecorView fullscreen. This new View now acts as a
-                // fake (non fullscreen) window.
-                //
-                // On top of that, we also add a fullscreen transparent background between the
-                // DecorView and the view that we added so that we can dismiss the dialog when this
-                // view is clicked. This is necessary because DecorView overrides onTouchEvent and
-                // therefore we can't set the click listener directly on the (now fullscreen)
-                // DecorView.
-                val fullscreenTransparentBackground = FrameLayout(dialog.context)
-                decorView.addView(
-                    fullscreenTransparentBackground,
-                    0 /* index */,
-                    FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
-                )
-
-                val dialogContentWithBackground = LaunchableFrameLayout(dialog.context)
-                dialogContentWithBackground.background = decorView.background
-
-                // Make the window background transparent. Note that setting the window (or
-                // DecorView) background drawable to null leads to issues with background color (not
-                // being transparent) or with insets that are not refreshed. Therefore we need to
-                // set it to something not null, hence we are using android.R.color.transparent
-                // here.
-                window.setBackgroundDrawableResource(android.R.color.transparent)
-
-                // Close the dialog when clicking outside of it.
-                fullscreenTransparentBackground.setOnClickListener { dialog.dismiss() }
-                dialogContentWithBackground.isClickable = true
-
-                // Make sure the transparent and dialog backgrounds are not focusable by
-                // accessibility
-                // features.
-                fullscreenTransparentBackground.importantForAccessibility =
-                    View.IMPORTANT_FOR_ACCESSIBILITY_NO
-                dialogContentWithBackground.importantForAccessibility =
-                    View.IMPORTANT_FOR_ACCESSIBILITY_NO
-
-                fullscreenTransparentBackground.addView(
-                    dialogContentWithBackground,
-                    FrameLayout.LayoutParams(
-                        window.attributes.width,
-                        window.attributes.height,
-                        window.attributes.gravity
-                    )
-                )
-
-                // Move all original children of the DecorView to the new View we just added.
-                for (i in 1 until decorView.childCount) {
-                    val view = decorView.getChildAt(1)
-                    decorView.removeViewAt(1)
-                    dialogContentWithBackground.addView(view)
-                }
-
-                // Make the window fullscreen and add a layout listener to ensure it stays
-                // fullscreen.
-                window.setLayout(MATCH_PARENT, MATCH_PARENT)
-                decorViewLayoutListener =
-                    View.OnLayoutChangeListener {
-                        v,
-                        left,
-                        top,
-                        right,
-                        bottom,
-                        oldLeft,
-                        oldTop,
-                        oldRight,
-                        oldBottom ->
-                        if (
-                            window.attributes.width != MATCH_PARENT ||
-                                window.attributes.height != MATCH_PARENT
-                        ) {
-                            // The dialog size changed, copy its size to dialogContentWithBackground
-                            // and make the dialog window full screen again.
-                            val layoutParams = dialogContentWithBackground.layoutParams
-                            layoutParams.width = window.attributes.width
-                            layoutParams.height = window.attributes.height
-                            dialogContentWithBackground.layoutParams = layoutParams
-                            window.setLayout(MATCH_PARENT, MATCH_PARENT)
-                        }
-                    }
-                decorView.addOnLayoutChangeListener(decorViewLayoutListener)
-
+                val (dialogContentWithBackground, decorViewLayoutListener) =
+                    dialog.maybeForceFullscreen()!!
+                this.decorViewLayoutListener = decorViewLayoutListener
                 dialogContentWithBackground
             }
+
         this.dialogContentWithBackground = dialogContentWithBackground
         dialogContentWithBackground.setTag(R.id.tag_dialog_background, true)
 
@@ -867,7 +783,7 @@
         }
 
         // Show the background dim.
-        dialog.window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+        dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
 
         startAnimation(
             isLaunching = true,
@@ -947,7 +863,7 @@
             isLaunching = false,
             onLaunchAnimationStart = {
                 // Remove the dim background as soon as we start the animation.
-                dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+                dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
             },
             onLaunchAnimationEnd = {
                 val dialogContentWithBackground = this.dialogContentWithBackground!!
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 1a03ede..6c4b695 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -206,8 +206,9 @@
             return
         }
 
-        backgroundView = FrameLayout(launchContainer.context)
-        launchContainerOverlay.add(backgroundView)
+        backgroundView = FrameLayout(launchContainer.context).also {
+            launchContainerOverlay.add(it)
+        }
 
         // We wrap the ghosted view background and use it to draw the expandable background. Its
         // alpha will be set to 0 as soon as we start drawing the expanding background.
@@ -319,7 +320,7 @@
         backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
 
         GhostView.removeGhost(ghostedView)
-        launchContainerOverlay.remove(backgroundView)
+        backgroundView?.let { launchContainerOverlay.remove(it) }
 
         if (ghostedView is LaunchableView) {
             // Restore the ghosted view visibility.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
index 142fd21..d6eba2e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
@@ -283,7 +283,7 @@
 
         animator.addListener(
             object : AnimatorListenerAdapter() {
-                override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
+                override fun onAnimationStart(animation: Animator, isReverse: Boolean) {
                     if (DEBUG) {
                         Log.d(TAG, "Animation started")
                     }
@@ -295,7 +295,7 @@
                     launchContainerOverlay.add(windowBackgroundLayer)
                 }
 
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     if (DEBUG) {
                         Log.d(TAG, "Animation ended")
                     }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt
deleted file mode 100644
index 2eb503b..0000000
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt
+++ /dev/null
@@ -1,53 +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.animation
-
-import android.content.Context
-import android.util.AttributeSet
-import android.widget.FrameLayout
-
-/** A [FrameLayout] that also implements [LaunchableView]. */
-open class LaunchableFrameLayout : FrameLayout, LaunchableView {
-    private val delegate =
-        LaunchableViewDelegate(
-            this,
-            superSetVisibility = { super.setVisibility(it) },
-        )
-
-    constructor(context: Context) : super(context)
-    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
-    constructor(
-        context: Context,
-        attrs: AttributeSet?,
-        defStyleAttr: Int
-    ) : super(context, attrs, defStyleAttr)
-
-    constructor(
-        context: Context,
-        attrs: AttributeSet?,
-        defStyleAttr: Int,
-        defStyleRes: Int
-    ) : super(context, attrs, defStyleAttr, defStyleRes)
-
-    override fun setShouldBlockVisibilityChanges(block: Boolean) {
-        delegate.setShouldBlockVisibilityChanges(block)
-    }
-
-    override fun setVisibility(visibility: Int) {
-        delegate.setVisibility(visibility)
-    }
-}
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 b555fa5..8dc7495 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -42,7 +42,9 @@
                 return baseTypeface
             }
 
-            val axes = FontVariationAxis.fromFontVariationSettings(fVar).toMutableList()
+            val axes = FontVariationAxis.fromFontVariationSettings(fVar)
+                ?.toMutableList()
+                ?: mutableListOf()
             axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) }
             if (axes.isEmpty()) {
                 return baseTypeface
@@ -120,8 +122,8 @@
             }
             addListener(
                 object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) = textInterpolator.rebase()
-                    override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
+                    override fun onAnimationEnd(animation: Animator) = textInterpolator.rebase()
+                    override fun onAnimationCancel(animation: Animator) = textInterpolator.rebase()
                 }
             )
         }
@@ -302,11 +304,11 @@
             if (onAnimationEnd != null) {
                 val listener =
                     object : AnimatorListenerAdapter() {
-                        override fun onAnimationEnd(animation: Animator?) {
+                        override fun onAnimationEnd(animation: Animator) {
                             onAnimationEnd.run()
                             animator.removeListener(this)
                         }
-                        override fun onAnimationCancel(animation: Animator?) {
+                        override fun onAnimationCancel(animation: Animator) {
                             animator.removeListener(this)
                         }
                     }
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 8e79e3c..bd3706e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -70,6 +70,9 @@
          * If a new layout change happens while an animation is already in progress, the animation
          * is updated to continue from the current values to the new end state.
          *
+         * A set of [excludedViews] can be passed. If any dependent view from [rootView] matches an
+         * entry in this set, changes to that view will not be animated.
+         *
          * The animator continues to respond to layout changes until [stopAnimating] is called.
          *
          * Successive calls to this method override the previous settings ([interpolator] and
@@ -82,9 +85,16 @@
         fun animate(
             rootView: View,
             interpolator: Interpolator = DEFAULT_INTERPOLATOR,
-            duration: Long = DEFAULT_DURATION
+            duration: Long = DEFAULT_DURATION,
+            excludedViews: Set<View> = emptySet()
         ): Boolean {
-            return animate(rootView, interpolator, duration, ephemeral = false)
+            return animate(
+                rootView,
+                interpolator,
+                duration,
+                ephemeral = false,
+                excludedViews = excludedViews
+            )
         }
 
         /**
@@ -95,16 +105,24 @@
         fun animateNextUpdate(
             rootView: View,
             interpolator: Interpolator = DEFAULT_INTERPOLATOR,
-            duration: Long = DEFAULT_DURATION
+            duration: Long = DEFAULT_DURATION,
+            excludedViews: Set<View> = emptySet()
         ): Boolean {
-            return animate(rootView, interpolator, duration, ephemeral = true)
+            return animate(
+                rootView,
+                interpolator,
+                duration,
+                ephemeral = true,
+                excludedViews = excludedViews
+            )
         }
 
         private fun animate(
             rootView: View,
             interpolator: Interpolator,
             duration: Long,
-            ephemeral: Boolean
+            ephemeral: Boolean,
+            excludedViews: Set<View> = emptySet()
         ): Boolean {
             if (
                 !occupiesSpace(
@@ -119,7 +137,7 @@
             }
 
             val listener = createUpdateListener(interpolator, duration, ephemeral)
-            addListener(rootView, listener, recursive = true)
+            addListener(rootView, listener, recursive = true, excludedViews = excludedViews)
             return true
         }
 
@@ -921,8 +939,11 @@
         private fun addListener(
             view: View,
             listener: View.OnLayoutChangeListener,
-            recursive: Boolean = false
+            recursive: Boolean = false,
+            excludedViews: Set<View> = emptySet()
         ) {
+            if (excludedViews.contains(view)) return
+
             // Make sure that only one listener is active at a time.
             val previousListener = view.getTag(R.id.tag_layout_listener)
             if (previousListener != null && previousListener is View.OnLayoutChangeListener) {
@@ -933,7 +954,12 @@
             view.setTag(R.id.tag_layout_listener, listener)
             if (view is ViewGroup && recursive) {
                 for (i in 0 until view.childCount) {
-                    addListener(view.getChildAt(i), listener, recursive = true)
+                    addListener(
+                        view.getChildAt(i),
+                        listener,
+                        recursive = true,
+                        excludedViews = excludedViews
+                    )
                 }
             }
         }
@@ -1020,7 +1046,7 @@
                         }
                     }
 
-                    override fun onAnimationCancel(animation: Animator?) {
+                    override fun onAnimationCancel(animation: Animator) {
                         cancelled = true
                     }
                 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.kt
new file mode 100644
index 0000000..7538f18
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableFrameLayout.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.animation.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
+
+/** A [FrameLayout] that also implements [LaunchableView]. */
+open class LaunchableFrameLayout : FrameLayout, LaunchableView {
+    private val delegate =
+        LaunchableViewDelegate(
+            this,
+            superSetVisibility = { super.setVisibility(it) },
+        )
+
+    constructor(context: Context) : super(context)
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int
+    ) : super(context, attrs, defStyleAttr)
+
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int,
+        defStyleRes: Int
+    ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+    override fun setShouldBlockVisibilityChanges(block: Boolean) {
+        delegate.setShouldBlockVisibilityChanges(block)
+    }
+
+    override fun setVisibility(visibility: Int) {
+        delegate.setVisibility(visibility)
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt
new file mode 100644
index 0000000..c94fad7
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.shaders
+
+import android.graphics.RuntimeShader
+
+/** Simply renders a solid color. */
+class SolidColorShader(color: Int) : RuntimeShader(SHADER) {
+    // language=AGSL
+    private companion object {
+        private const val SHADER =
+            """
+                layout(color) uniform vec4 in_color;
+                vec4 main(vec2 p) {
+                    return in_color;
+                }
+            """
+    }
+
+    init {
+        setColorUniform("in_color", color)
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt
new file mode 100644
index 0000000..df07856
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.shaders
+
+import android.graphics.Color
+import android.graphics.RuntimeShader
+import android.graphics.Shader
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
+
+/**
+ * Renders sparkles based on the luma matte.
+ *
+ * For example, you can pass in simplex noise as the luma matte and have a cloud looking sparkles.
+ *
+ * You may want to utilize this shader by: (Preferred) 1. Create a RuntimeShaderEffect and set the
+ * [RenderEffect] to the target [View].
+ * 2. Create a custom [View], set the shader to the [Paint] and use [Canvas.drawPaint] in [onDraw].
+ */
+class SparkleShader : RuntimeShader(SPARKLE_SHADER) {
+    // language=AGSL
+    companion object {
+        private const val UNIFORMS =
+            """
+            // Used it for RenderEffect. For example:
+            // myView.setRenderEffect(
+            //     RenderEffect.createRuntimeShaderEffect(SparkleShader(), "in_src")
+            // )
+            uniform shader in_src;
+            uniform half in_time;
+            uniform half in_pixelate;
+            uniform shader in_lumaMatte;
+            layout(color) uniform vec4 in_color;
+        """
+        private const val MAIN_SHADER =
+            """vec4 main(vec2 p) {
+            half3 src = in_src.eval(p).rgb;
+            half luma = getLuminosity(in_lumaMatte.eval(p).rgb);
+            half sparkle = sparkles(p - mod(p, in_pixelate), in_time);
+            half3 mask = maskLuminosity(in_color.rgb * sparkle, luma);
+
+            return vec4(src * mask * in_color.a, in_color.a);
+        }
+        """
+        private const val SPARKLE_SHADER = UNIFORMS + ShaderUtilLibrary.SHADER_LIB + MAIN_SHADER
+
+        /** Highly recommended to use this value unless specified by design spec. */
+        const val DEFAULT_SPARKLE_PIXELATE_AMOUNT = 0.8f
+    }
+
+    init {
+        // Initializes the src and luma matte to be white.
+        setInputShader("in_src", SolidColorShader(Color.WHITE))
+        setLumaMatteColor(Color.WHITE)
+    }
+
+    /**
+     * Sets the time of the sparkle animation.
+     *
+     * This is used for animating sparkles. Note that this only makes the sparkles sparkle in place.
+     * In order to move the sparkles in x, y directions, move the luma matte input instead.
+     */
+    fun setTime(time: Float) {
+        setFloatUniform("in_time", time)
+    }
+
+    /**
+     * Sets pixelated amount of the sparkle.
+     *
+     * This value *must* be based on [resources.displayMetrics.density]. Otherwise, this will result
+     * in having different sparkle sizes on different screens.
+     *
+     * Expected to be used as follows:
+     * <pre>
+     *     {@code
+     *     val pixelDensity = context.resources.displayMetrics.density
+     *     // Sparkles will be 0.8 of the pixel size.
+     *     val sparkleShader = SparkleShader().apply { setPixelateAmount(pixelDensity * 0.8f) }
+     *     }
+     * </pre>
+     */
+    fun setPixelateAmount(pixelateAmount: Float) {
+        setFloatUniform("in_pixelate", pixelateAmount)
+    }
+
+    /**
+     * Sets the luma matte for the sparkles. The luminosity determines the sparkle's visibility.
+     * Useful for setting a complex mask (e.g. simplex noise, texture, etc.)
+     */
+    fun setLumaMatte(lumaMatte: Shader) {
+        setInputShader("in_lumaMatte", lumaMatte)
+    }
+
+    /** Sets the luma matte for the sparkles. Useful for setting a solid color. */
+    fun setLumaMatteColor(color: Int) {
+        setInputShader("in_lumaMatte", SolidColorShader(color))
+    }
+
+    /** Sets the color of the sparkles. Expect to have the alpha value encoded. */
+    fun setColor(color: Int) {
+        setColorUniform("in_color", color)
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
index 428856d..0f63548 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
@@ -18,6 +18,9 @@
 
 import android.app.Dialog
 import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.widget.FrameLayout
 import android.window.OnBackInvokedDispatcher
 import com.android.systemui.animation.back.BackAnimationSpec
 import com.android.systemui.animation.back.BackTransformation
@@ -25,6 +28,7 @@
 import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi
 import com.android.systemui.animation.back.onBackAnimationCallbackFrom
 import com.android.systemui.animation.back.registerOnBackInvokedCallbackOnViewAttached
+import com.android.systemui.animation.view.LaunchableFrameLayout
 
 /**
  * Register on the Dialog's [OnBackInvokedDispatcher] an animation using the [BackAnimationSpec].
@@ -49,3 +53,110 @@
             ),
     )
 }
+
+/**
+ * Make the dialog window (and therefore its DecorView) fullscreen to make it possible to animate
+ * outside its bounds. No-op if the dialog is already fullscreen.
+ *
+ * <p>Returns null if the dialog is already fullscreen. Otherwise, returns a pair containing a view
+ * and a layout listener. The new view matches the original dialog DecorView in size, position, and
+ * background. This new view will be a child of the modified, transparent, fullscreen DecorView. The
+ * layout listener is listening to changes to the modified DecorView. It is the responsibility of
+ * the caller to deregister the listener when the dialog is dismissed.
+ */
+fun Dialog.maybeForceFullscreen(): Pair<LaunchableFrameLayout, View.OnLayoutChangeListener>? {
+    // Create the dialog so that its onCreate() method is called, which usually sets the dialog
+    // content.
+    create()
+
+    val window = window!!
+    val decorView = window.decorView as ViewGroup
+
+    val isWindowFullscreen =
+        window.attributes.width == MATCH_PARENT && window.attributes.height == MATCH_PARENT
+    if (isWindowFullscreen) {
+        return null
+    }
+
+    // We will make the dialog window (and therefore its DecorView) fullscreen to make it possible
+    // to animate outside its bounds.
+    //
+    // Before that, we add a new View as a child of the DecorView with the same size and gravity as
+    // that DecorView, then we add all original children of the DecorView to that new View. Finally
+    // we remove the background of the DecorView and add it to the new View, then we make the
+    // DecorView fullscreen. This new View now acts as a fake (non fullscreen) window.
+    //
+    // On top of that, we also add a fullscreen transparent background between the DecorView and the
+    // view that we added so that we can dismiss the dialog when this view is clicked. This is
+    // necessary because DecorView overrides onTouchEvent and therefore we can't set the click
+    // listener directly on the (now fullscreen) DecorView.
+    val fullscreenTransparentBackground = FrameLayout(context)
+    decorView.addView(
+        fullscreenTransparentBackground,
+        0 /* index */,
+        FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+    )
+
+    val dialogContentWithBackground = LaunchableFrameLayout(context)
+    dialogContentWithBackground.background = decorView.background
+
+    // Make the window background transparent. Note that setting the window (or DecorView)
+    // background drawable to null leads to issues with background color (not being transparent) or
+    // with insets that are not refreshed. Therefore we need to set it to something not null, hence
+    // we are using android.R.color.transparent here.
+    window.setBackgroundDrawableResource(android.R.color.transparent)
+
+    // Close the dialog when clicking outside of it.
+    fullscreenTransparentBackground.setOnClickListener { dismiss() }
+    dialogContentWithBackground.isClickable = true
+
+    // Make sure the transparent and dialog backgrounds are not focusable by accessibility
+    // features.
+    fullscreenTransparentBackground.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+    dialogContentWithBackground.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+
+    fullscreenTransparentBackground.addView(
+        dialogContentWithBackground,
+        FrameLayout.LayoutParams(
+            window.attributes.width,
+            window.attributes.height,
+            window.attributes.gravity
+        )
+    )
+
+    // Move all original children of the DecorView to the new View we just added.
+    for (i in 1 until decorView.childCount) {
+        val view = decorView.getChildAt(1)
+        decorView.removeViewAt(1)
+        dialogContentWithBackground.addView(view)
+    }
+
+    // Make the window fullscreen and add a layout listener to ensure it stays fullscreen.
+    window.setLayout(MATCH_PARENT, MATCH_PARENT)
+    val decorViewLayoutListener =
+        View.OnLayoutChangeListener {
+            v,
+            left,
+            top,
+            right,
+            bottom,
+            oldLeft,
+            oldTop,
+            oldRight,
+            oldBottom ->
+            if (
+                window.attributes.width != MATCH_PARENT || window.attributes.height != MATCH_PARENT
+            ) {
+                // The dialog size changed, copy its size to dialogContentWithBackground and make
+                // the dialog window full screen again.
+                val layoutParams = dialogContentWithBackground.layoutParams
+                layoutParams.width = window.attributes.width
+                layoutParams.height = window.attributes.height
+                dialogContentWithBackground.layoutParams = layoutParams
+                window.setLayout(MATCH_PARENT, MATCH_PARENT)
+            }
+        }
+    decorView.addOnLayoutChangeListener(decorViewLayoutListener)
+
+    return dialogContentWithBackground to decorViewLayoutListener
+}
diff --git a/packages/SystemUI/compose/core/OWNERS b/packages/SystemUI/compose/core/OWNERS
new file mode 100644
index 0000000..7e37f4f
--- /dev/null
+++ b/packages/SystemUI/compose/core/OWNERS
@@ -0,0 +1,12 @@
+set noparent
+
+# Bug component: 1184816
+
+jdemeulenaere@google.com
+nijamkin@google.com
+
+# Don't send reviews here.
+dsandler@android.com
+cinek@google.com
+juliacr@google.com
+pixel@google.com
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/activity/EdgeToEdgeActivitContent.kt b/packages/SystemUI/compose/core/src/com/android/compose/activity/EdgeToEdgeActivitContent.kt
new file mode 100644
index 0000000..97c8076
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/activity/EdgeToEdgeActivitContent.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.activity
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.contentColorFor
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.android.compose.rememberSystemUiController
+import com.android.compose.theme.PlatformTheme
+
+/** Scaffolding for an edge-to-edge activity content. */
+@Composable
+fun EdgeToEdgeActivityContent(
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit,
+) {
+    // Make the status and navigation bars transparent, ensuring that the status bar icons are dark
+    // when the theme is light and vice-versa.
+    val systemUiController = rememberSystemUiController()
+    val isDarkTheme = isSystemInDarkTheme()
+    val useDarkIcons = !isDarkTheme
+    DisposableEffect(systemUiController, useDarkIcons) {
+        systemUiController.setSystemBarsColor(
+            color = Color.Transparent,
+            darkIcons = useDarkIcons,
+        )
+        onDispose {}
+    }
+
+    PlatformTheme(isDarkTheme) {
+        val backgroundColor = MaterialTheme.colorScheme.background
+        Box(modifier.fillMaxSize().background(backgroundColor)) {
+            CompositionLocalProvider(LocalContentColor provides contentColorFor(backgroundColor)) {
+                content()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt
index 8fe1f48..4fe9f89 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Easings.kt
@@ -27,37 +27,52 @@
 object Easings {
 
     /** The standard interpolator that should be used on every normal animation */
-    val StandardEasing = fromInterpolator(InterpolatorsAndroidX.STANDARD)
+    val Standard = fromInterpolator(InterpolatorsAndroidX.STANDARD)
 
     /**
      * The standard accelerating interpolator that should be used on every regular movement of
      * content that is disappearing e.g. when moving off screen.
      */
-    val StandardAccelerateEasing = fromInterpolator(InterpolatorsAndroidX.STANDARD_ACCELERATE)
+    val StandardAccelerate = fromInterpolator(InterpolatorsAndroidX.STANDARD_ACCELERATE)
 
     /**
      * The standard decelerating interpolator that should be used on every regular movement of
      * content that is appearing e.g. when coming from off screen.
      */
-    val StandardDecelerateEasing = fromInterpolator(InterpolatorsAndroidX.STANDARD_DECELERATE)
+    val StandardDecelerate = fromInterpolator(InterpolatorsAndroidX.STANDARD_DECELERATE)
 
     /** The default emphasized interpolator. Used for hero / emphasized movement of content. */
-    val EmphasizedEasing = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED)
+    val Emphasized = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED)
 
     /**
      * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
      * is disappearing e.g. when moving off screen.
      */
-    val EmphasizedAccelerateEasing = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_ACCELERATE)
+    val EmphasizedAccelerate = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_ACCELERATE)
 
     /**
      * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
      * is appearing e.g. when coming from off screen
      */
-    val EmphasizedDecelerateEasing = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_DECELERATE)
+    val EmphasizedDecelerate = fromInterpolator(InterpolatorsAndroidX.EMPHASIZED_DECELERATE)
 
     /** The linear interpolator. */
-    val LinearEasing = fromInterpolator(InterpolatorsAndroidX.LINEAR)
+    val Linear = fromInterpolator(InterpolatorsAndroidX.LINEAR)
+
+    /** The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN. */
+    val Legacy = fromInterpolator(InterpolatorsAndroidX.LEGACY)
+
+    /**
+     * The default legacy accelerating interpolator as defined in Material 1. Also known as
+     * FAST_OUT_LINEAR_IN.
+     */
+    val LegacyAccelerate = fromInterpolator(InterpolatorsAndroidX.LEGACY_ACCELERATE)
+
+    /**
+     * T The default legacy decelerating interpolator as defined in Material 1. Also known as
+     * LINEAR_OUT_SLOW_IN.
+     */
+    val LegacyDecelerate = fromInterpolator(InterpolatorsAndroidX.LEGACY_DECELERATE)
 
     private fun fromInterpolator(source: Interpolator) = Easing { x -> source.getInterpolation(x) }
 }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index d4a81f9..ac1ef15 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -70,8 +70,10 @@
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.dp
-import androidx.lifecycle.ViewTreeLifecycleOwner
-import androidx.lifecycle.ViewTreeViewModelStoreOwner
+import androidx.lifecycle.findViewTreeLifecycleOwner
+import androidx.lifecycle.findViewTreeViewModelStoreOwner
+import androidx.lifecycle.setViewTreeLifecycleOwner
+import androidx.lifecycle.setViewTreeViewModelStoreOwner
 import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.LaunchAnimator
 import kotlin.math.max
@@ -368,13 +370,10 @@
                     context,
                     overlay,
                 )
-            ViewTreeLifecycleOwner.set(
-                overlayViewGroup,
-                ViewTreeLifecycleOwner.get(composeViewRoot),
-            )
-            ViewTreeViewModelStoreOwner.set(
-                overlayViewGroup,
-                ViewTreeViewModelStoreOwner.get(composeViewRoot),
+
+            overlayViewGroup.setViewTreeLifecycleOwner(composeViewRoot.findViewTreeLifecycleOwner())
+            overlayViewGroup.setViewTreeViewModelStoreOwner(
+                composeViewRoot.findViewTreeViewModelStoreOwner()
             )
             ViewTreeSavedStateRegistryOwner.set(
                 overlayViewGroup,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
new file mode 100644
index 0000000..566967f
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.DisposableEffectResult
+import androidx.compose.runtime.DisposableEffectScope
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.lerp
+import com.android.compose.ui.util.lerp
+
+/**
+ * Animate a shared Int value.
+ *
+ * @see SceneScope.animateSharedValueAsState
+ */
+@Composable
+fun SceneScope.animateSharedIntAsState(
+    value: Int,
+    key: ValueKey,
+    element: ElementKey,
+    canOverflow: Boolean = true,
+): State<Int> {
+    return animateSharedValueAsState(value, key, element, ::lerp, canOverflow)
+}
+
+/**
+ * Animate a shared Float value.
+ *
+ * @see SceneScope.animateSharedValueAsState
+ */
+@Composable
+fun SceneScope.animateSharedFloatAsState(
+    value: Float,
+    key: ValueKey,
+    element: ElementKey,
+    canOverflow: Boolean = true,
+): State<Float> {
+    return animateSharedValueAsState(value, key, element, ::lerp, canOverflow)
+}
+
+/**
+ * Animate a shared Dp value.
+ *
+ * @see SceneScope.animateSharedValueAsState
+ */
+@Composable
+fun SceneScope.animateSharedDpAsState(
+    value: Dp,
+    key: ValueKey,
+    element: ElementKey,
+    canOverflow: Boolean = true,
+): State<Dp> {
+    return animateSharedValueAsState(value, key, element, ::lerp, canOverflow)
+}
+
+/**
+ * Animate a shared Color value.
+ *
+ * @see SceneScope.animateSharedValueAsState
+ */
+@Composable
+fun SceneScope.animateSharedColorAsState(
+    value: Color,
+    key: ValueKey,
+    element: ElementKey,
+): State<Color> {
+    return animateSharedValueAsState(value, key, element, ::lerp, canOverflow = false)
+}
+
+@Composable
+internal fun <T> animateSharedValueAsState(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    element: Element,
+    key: ValueKey,
+    value: T,
+    lerp: (T, T, Float) -> T,
+    canOverflow: Boolean,
+): State<T> {
+    val sharedValue = remember(key) { Element.SharedValue(key, value) }
+    if (value != sharedValue.value) {
+        sharedValue.value = value
+    }
+
+    DisposableEffect(element, scene, sharedValue) {
+        addSharedValueToElement(element, scene, sharedValue)
+    }
+
+    return remember(layoutImpl, element, sharedValue, lerp, canOverflow) {
+        derivedStateOf { computeValue(layoutImpl, element, sharedValue, lerp, canOverflow) }
+    }
+}
+
+private fun <T> DisposableEffectScope.addSharedValueToElement(
+    element: Element,
+    scene: Scene,
+    sharedValue: Element.SharedValue<T>,
+): DisposableEffectResult {
+    val sceneValues =
+        element.sceneValues[scene.key] ?: error("Element $element is not present in $scene")
+    val sharedValues = sceneValues.sharedValues
+
+    sharedValues[sharedValue.key] = sharedValue
+    return onDispose { sharedValues.remove(sharedValue.key) }
+}
+
+private fun <T> computeValue(
+    layoutImpl: SceneTransitionLayoutImpl,
+    element: Element,
+    sharedValue: Element.SharedValue<T>,
+    lerp: (T, T, Float) -> T,
+    canOverflow: Boolean,
+): T {
+    val state = layoutImpl.state.transitionState
+    if (
+        state !is TransitionState.Transition ||
+            state.fromScene == state.toScene ||
+            !layoutImpl.isTransitionReady(state)
+    ) {
+        return sharedValue.value
+    }
+
+    fun sceneValue(scene: SceneKey): Element.SharedValue<T>? {
+        val sceneValues = element.sceneValues[scene] ?: return null
+        val value = sceneValues.sharedValues[sharedValue.key] ?: return null
+        return value as Element.SharedValue<T>
+    }
+
+    val fromValue = sceneValue(state.fromScene)
+    val toValue = sceneValue(state.toScene)
+    return if (fromValue != null && toValue != null) {
+        val progress = if (canOverflow) state.progress else state.progress.coerceIn(0f, 1f)
+        lerp(fromValue.value, toValue.value, progress)
+    } else if (fromValue != null) {
+        fromValue.value
+    } else if (toValue != null) {
+        toValue.value
+    } else {
+        sharedValue.value
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt
new file mode 100644
index 0000000..7536728
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.SpringSpec
+import kotlin.math.absoluteValue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Transition to [target] using a canned animation. This function will try to be smart and take over
+ * the currently running transition, if there is one.
+ */
+internal fun CoroutineScope.animateToScene(
+    layoutImpl: SceneTransitionLayoutImpl,
+    target: SceneKey,
+) {
+    val state = layoutImpl.state.transitionState
+    if (state.currentScene == target) {
+        // This can happen in 3 different situations, for which there isn't anything else to do:
+        //  1. There is no ongoing transition and [target] is already the current scene.
+        //  2. The user is swiping to [target] from another scene and released their pointer such
+        //     that the gesture was committed and the transition is animating to [scene] already.
+        //  3. The user is swiping from [target] to another scene and either:
+        //     a. didn't release their pointer yet.
+        //     b. released their pointer such that the swipe gesture was cancelled and the
+        //        transition is currently animating back to [target].
+        return
+    }
+
+    when (state) {
+        is TransitionState.Idle -> animate(layoutImpl, target)
+        is TransitionState.Transition -> {
+            if (state.toScene == state.fromScene) {
+                // Same as idle.
+                animate(layoutImpl, target)
+                return
+            }
+
+            // A transition is currently running: first check whether `transition.toScene` or
+            // `transition.fromScene` is the same as our target scene, in which case the transition
+            // can be accelerated or reversed to end up in the target state.
+
+            if (state.toScene == target) {
+                // The user is currently swiping to [target] but didn't release their pointer yet:
+                // animate the progress to `1`.
+
+                check(state.fromScene == state.currentScene)
+                val progress = state.progress
+                if ((1f - progress).absoluteValue < ProgressVisibilityThreshold) {
+                    // The transition is already finished (progress ~= 1): no need to animate.
+                    layoutImpl.state.transitionState = TransitionState.Idle(state.currentScene)
+                } else {
+                    // The transition is in progress: start the canned animation at the same
+                    // progress as it was in.
+                    // TODO(b/290184746): Also take the current velocity into account.
+                    animate(layoutImpl, target, startProgress = progress)
+                }
+
+                return
+            }
+
+            if (state.fromScene == target) {
+                // There is a transition from [target] to another scene: simply animate the same
+                // transition progress to `0`.
+
+                check(state.toScene == state.currentScene)
+                val progress = state.progress
+                if (progress.absoluteValue < ProgressVisibilityThreshold) {
+                    // The transition is at progress ~= 0: no need to animate.
+                    layoutImpl.state.transitionState = TransitionState.Idle(state.currentScene)
+                } else {
+                    // TODO(b/290184746): Also take the current velocity into account.
+                    animate(layoutImpl, target, startProgress = progress, reversed = true)
+                }
+
+                return
+            }
+
+            // Generic interruption; the current transition is neither from or to [target].
+            // TODO(b/290930950): Better handle interruptions here.
+            animate(layoutImpl, target)
+        }
+    }
+}
+
+private fun CoroutineScope.animate(
+    layoutImpl: SceneTransitionLayoutImpl,
+    target: SceneKey,
+    startProgress: Float = 0f,
+    reversed: Boolean = false,
+) {
+    val fromScene = layoutImpl.state.transitionState.currentScene
+
+    val animationSpec = layoutImpl.transitions.transitionSpec(fromScene, target).spec
+    val visibilityThreshold =
+        (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
+    val animatable = Animatable(startProgress, visibilityThreshold = visibilityThreshold)
+
+    val targetProgress = if (reversed) 0f else 1f
+    val transition =
+        if (reversed) {
+            OneOffTransition(target, fromScene, currentScene = target, animatable)
+        } else {
+            OneOffTransition(fromScene, target, currentScene = target, animatable)
+        }
+
+    // Change the current layout state to use this new transition.
+    layoutImpl.state.transitionState = transition
+
+    // Animate the progress to its target value.
+    launch {
+        animatable.animateTo(targetProgress, animationSpec)
+
+        // Unless some other external state change happened, the state should now be idle.
+        if (layoutImpl.state.transitionState == transition) {
+            layoutImpl.state.transitionState = TransitionState.Idle(target)
+        }
+    }
+}
+
+private class OneOffTransition(
+    override val fromScene: SceneKey,
+    override val toScene: SceneKey,
+    override val currentScene: SceneKey,
+    private val animatable: Animatable<Float, AnimationVector1D>,
+) : TransitionState.Transition {
+    override val progress: Float
+        get() = animatable.value
+}
+
+// TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size
+// and screen density.
+private const val ProgressVisibilityThreshold = 1e-3f
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
new file mode 100644
index 0000000..0cc259a
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt
@@ -0,0 +1,449 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.runtime.snapshots.SnapshotStateMap
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.isSpecified
+import androidx.compose.ui.geometry.lerp
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.IntermediateMeasureScope
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.round
+import com.android.compose.animation.scene.transformation.PropertyTransformation
+import com.android.compose.modifiers.thenIf
+import com.android.compose.ui.util.lerp
+
+/** An element on screen, that can be composed in one or more scenes. */
+internal class Element(val key: ElementKey) {
+    /**
+     * The last offset assigned to this element, relative to the SceneTransitionLayout containing
+     * it.
+     */
+    var lastOffset = Offset.Unspecified
+
+    /** The last size assigned to this element. */
+    var lastSize = SizeUnspecified
+
+    /** The last alpha assigned to this element. */
+    var lastAlpha = 1f
+
+    /** The mapping between a scene and the values/state this element has in that scene, if any. */
+    val sceneValues = SnapshotStateMap<SceneKey, SceneValues>()
+
+    override fun toString(): String {
+        return "Element(key=$key)"
+    }
+
+    /** The target values of this element in a given scene. */
+    class SceneValues {
+        var size by mutableStateOf(SizeUnspecified)
+        var offset by mutableStateOf(Offset.Unspecified)
+        val sharedValues = SnapshotStateMap<ValueKey, SharedValue<*>>()
+    }
+
+    /** A shared value of this element. */
+    class SharedValue<T>(val key: ValueKey, initialValue: T) {
+        var value by mutableStateOf(initialValue)
+    }
+
+    companion object {
+        val SizeUnspecified = IntSize(Int.MAX_VALUE, Int.MAX_VALUE)
+    }
+}
+
+/** The implementation of [SceneScope.element]. */
+@Composable
+@OptIn(ExperimentalComposeUiApi::class)
+internal fun Modifier.element(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    key: ElementKey,
+): Modifier {
+    val sceneValues = remember(scene, key) { Element.SceneValues() }
+    val element =
+        // Get the element associated to [key] if it was already composed in another scene,
+        // otherwise create it and add it to our Map<ElementKey, Element>. This is done inside a
+        // withoutReadObservation() because there is no need to recompose when that map is mutated.
+        Snapshot.withoutReadObservation {
+            val element =
+                layoutImpl.elements[key] ?: Element(key).also { layoutImpl.elements[key] = it }
+            val previousValues = element.sceneValues[scene.key]
+            if (previousValues == null) {
+                element.sceneValues[scene.key] = sceneValues
+            } else if (previousValues != sceneValues) {
+                error("$key was composed multiple times in $scene")
+            }
+
+            element
+        }
+
+    DisposableEffect(scene, sceneValues, element) {
+        onDispose {
+            element.sceneValues.remove(scene.key)
+
+            // This was the last scene this element was in, so remove it from the map.
+            if (element.sceneValues.isEmpty()) {
+                layoutImpl.elements.remove(element.key)
+            }
+        }
+    }
+
+    val alpha =
+        remember(layoutImpl, element, scene) {
+            derivedStateOf { elementAlpha(layoutImpl, element, scene) }
+        }
+    val isOpaque by remember(alpha) { derivedStateOf { alpha.value == 1f } }
+    SideEffect {
+        if (isOpaque && element.lastAlpha != 1f) {
+            element.lastAlpha = 1f
+        }
+    }
+
+    return drawWithContent {
+            if (shouldDrawElement(layoutImpl, scene, element)) {
+                drawContent()
+            }
+        }
+        .modifierTransformations(layoutImpl, scene, element, sceneValues)
+        .intermediateLayout { measurable, constraints ->
+            val placeable =
+                measure(layoutImpl, scene, element, sceneValues, measurable, constraints)
+            layout(placeable.width, placeable.height) {
+                place(layoutImpl, scene, element, sceneValues, placeable, placementScope = this)
+            }
+        }
+        .thenIf(!isOpaque) {
+            Modifier.graphicsLayer {
+                val alpha = alpha.value
+                this.alpha = alpha
+                element.lastAlpha = alpha
+            }
+        }
+        .testTag(key.name)
+}
+
+private fun shouldDrawElement(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    element: Element,
+): Boolean {
+    val state = layoutImpl.state.transitionState
+
+    // Always draw the element if there is no ongoing transition or if the element is not shared.
+    if (
+        state !is TransitionState.Transition ||
+            state.fromScene == state.toScene ||
+            !layoutImpl.isTransitionReady(state) ||
+            state.fromScene !in element.sceneValues ||
+            state.toScene !in element.sceneValues
+    ) {
+        return true
+    }
+
+    val otherScene =
+        layoutImpl.scenes.getValue(
+            if (scene.key == state.fromScene) {
+                state.toScene
+            } else {
+                state.fromScene
+            }
+        )
+
+    // When the element is shared, draw the one in the highest scene unless it is a background, i.e.
+    // it is usually drawn below everything else.
+    val isHighestScene = scene.zIndex > otherScene.zIndex
+    return if (element.key.isBackground) {
+        !isHighestScene
+    } else {
+        isHighestScene
+    }
+}
+
+/**
+ * Chain the [com.android.compose.animation.scene.transformation.ModifierTransformation] applied
+ * throughout the current transition, if any.
+ */
+private fun Modifier.modifierTransformations(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    element: Element,
+    sceneValues: Element.SceneValues,
+): Modifier {
+    when (val state = layoutImpl.state.transitionState) {
+        is TransitionState.Idle -> return this
+        is TransitionState.Transition -> {
+            val fromScene = state.fromScene
+            val toScene = state.toScene
+            if (fromScene == toScene) {
+                // Same as idle.
+                return this
+            }
+
+            return layoutImpl.transitions
+                .transitionSpec(fromScene, state.toScene)
+                .transformations(element.key)
+                .modifier
+                .fold(this) { modifier, transformation ->
+                    with(transformation) {
+                        modifier.transform(layoutImpl, scene, element, sceneValues)
+                    }
+                }
+        }
+    }
+}
+
+private fun elementAlpha(
+    layoutImpl: SceneTransitionLayoutImpl,
+    element: Element,
+    scene: Scene
+): Float {
+    return computeValue(
+            layoutImpl,
+            scene,
+            element,
+            sceneValue = { 1f },
+            transformation = { it.alpha },
+            idleValue = 1f,
+            currentValue = { 1f },
+            lastValue = { element.lastAlpha },
+            ::lerp,
+        )
+        .coerceIn(0f, 1f)
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+private fun IntermediateMeasureScope.measure(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    element: Element,
+    sceneValues: Element.SceneValues,
+    measurable: Measurable,
+    constraints: Constraints,
+): Placeable {
+    // Update the size this element has in this scene when idle.
+    val targetSizeInScene = lookaheadSize
+    if (targetSizeInScene != sceneValues.size) {
+        // TODO(b/290930950): Better handle when this changes to avoid instant size jumps.
+        sceneValues.size = targetSizeInScene
+    }
+
+    // Some lambdas called (max once) by computeValue() will need to measure [measurable], in which
+    // case we store the resulting placeable here to make sure the element is not measured more than
+    // once.
+    var maybePlaceable: Placeable? = null
+
+    fun Placeable.size() = IntSize(width, height)
+
+    val targetSize =
+        computeValue(
+            layoutImpl,
+            scene,
+            element,
+            sceneValue = { it.size },
+            transformation = { it.size },
+            idleValue = lookaheadSize,
+            currentValue = { measurable.measure(constraints).also { maybePlaceable = it }.size() },
+            lastValue = {
+                val lastSize = element.lastSize
+                if (lastSize != Element.SizeUnspecified) {
+                    lastSize
+                } else {
+                    measurable.measure(constraints).also { maybePlaceable = it }.size()
+                }
+            },
+            ::lerp,
+        )
+
+    val placeable =
+        maybePlaceable
+            ?: measurable.measure(
+                Constraints.fixed(
+                    targetSize.width.coerceAtLeast(0),
+                    targetSize.height.coerceAtLeast(0),
+                )
+            )
+
+    element.lastSize = placeable.size()
+    return placeable
+}
+
+@OptIn(ExperimentalComposeUiApi::class)
+private fun IntermediateMeasureScope.place(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    element: Element,
+    sceneValues: Element.SceneValues,
+    placeable: Placeable,
+    placementScope: Placeable.PlacementScope,
+) {
+    with(placementScope) {
+        // Update the offset (relative to the SceneTransitionLayout) this element has in this scene
+        // when idle.
+        val coords = coordinates!!
+        val targetOffsetInScene = lookaheadScopeCoordinates.localLookaheadPositionOf(coords)
+        if (targetOffsetInScene != sceneValues.offset) {
+            // TODO(b/290930950): Better handle when this changes to avoid instant offset jumps.
+            sceneValues.offset = targetOffsetInScene
+        }
+
+        val currentOffset = lookaheadScopeCoordinates.localPositionOf(coords, Offset.Zero)
+        val targetOffset =
+            computeValue(
+                layoutImpl,
+                scene,
+                element,
+                sceneValue = { it.offset },
+                transformation = { it.offset },
+                idleValue = targetOffsetInScene,
+                currentValue = { currentOffset },
+                lastValue = {
+                    val lastValue = element.lastOffset
+                    if (lastValue.isSpecified) {
+                        lastValue
+                    } else {
+                        currentOffset
+                    }
+                },
+                ::lerp,
+            )
+
+        element.lastOffset = targetOffset
+        placeable.place((targetOffset - currentOffset).round())
+    }
+}
+
+/**
+ * Return the value that should be used depending on the current layout state and transition.
+ *
+ * Important: This function must remain inline because of all the lambda parameters. These lambdas
+ * are necessary because getting some of them might require some computation, like measuring a
+ * Measurable.
+ *
+ * @param layoutImpl the [SceneTransitionLayoutImpl] associated to [element].
+ * @param scene the scene containing [element].
+ * @param element the element being animated.
+ * @param sceneValue the value being animated.
+ * @param transformation the transformation associated to the value being animated.
+ * @param idleValue the value when idle, i.e. when there is no transition happening.
+ * @param currentValue the value that would be used if it is not transformed. Note that this is
+ *   different than [idleValue] even if the value is not transformed directly because it could be
+ *   impacted by the transformations on other elements, like a parent that is being translated or
+ *   resized.
+ * @param lastValue the last value that was used. This should be equal to [currentValue] if this is
+ *   the first time the value is set.
+ * @param lerp the linear interpolation function used to interpolate between two values of this
+ *   value type.
+ */
+private inline fun <T> computeValue(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    element: Element,
+    sceneValue: (Element.SceneValues) -> T,
+    transformation: (ElementTransformations) -> PropertyTransformation<T>?,
+    idleValue: T,
+    currentValue: () -> T,
+    lastValue: () -> T,
+    lerp: (T, T, Float) -> T,
+): T {
+    val state = layoutImpl.state.transitionState
+
+    // There is no ongoing transition.
+    if (state !is TransitionState.Transition || state.fromScene == state.toScene) {
+        return idleValue
+    }
+
+    // A transition was started but it's not ready yet (not all elements have been composed/laid
+    // out yet). Use the last value that was set, to make sure elements don't unexpectedly jump.
+    if (!layoutImpl.isTransitionReady(state)) {
+        return lastValue()
+    }
+
+    val fromScene = state.fromScene
+    val toScene = state.toScene
+    val fromValues = element.sceneValues[fromScene]
+    val toValues = element.sceneValues[toScene]
+
+    if (fromValues == null && toValues == null) {
+        error("This should not happen, element $element is neither in $fromScene or $toScene")
+    }
+
+    // TODO(b/291053278): Handle overscroll correctly. We should probably coerce between [0f, 1f]
+    // here and consume overflows at drawing time, somehow reusing Compose OverflowEffect or some
+    // similar mechanism.
+    val transitionProgress = state.progress
+
+    // The element is shared: interpolate between the value in fromScene and the value in toScene.
+    // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared
+    // elements follow the finger direction.
+    if (fromValues != null && toValues != null) {
+        return lerp(
+            sceneValue(fromValues),
+            sceneValue(toValues),
+            transitionProgress,
+        )
+    }
+
+    val transformation =
+        transformation(
+            layoutImpl.transitions.transitionSpec(fromScene, toScene).transformations(element.key)
+        )
+        // If there is no transformation explicitly associated to this element value, let's use
+        // the value given by the system (like the current position and size given by the layout
+        // pass).
+        ?: return currentValue()
+
+    // Get the transformed value, i.e. the target value at the beginning (for entering elements) or
+    // end (for leaving elements) of the transition.
+    val targetValue =
+        transformation.transform(
+            layoutImpl,
+            scene,
+            element,
+            fromValues ?: toValues!!,
+            state,
+            idleValue,
+        )
+
+    // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range.
+    val rangeProgress = transformation.range?.progress(transitionProgress) ?: transitionProgress
+
+    // Interpolate between the value at rest and the value before entering/after leaving.
+    val isEntering = fromValues == null
+    return if (isEntering) {
+        lerp(targetValue, idleValue, rangeProgress)
+    } else {
+        lerp(idleValue, targetValue, rangeProgress)
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
new file mode 100644
index 0000000..f7ebe2f
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+/**
+ * A base class to create unique keys, associated to an [identity] that is used to check the
+ * equality of two key instances.
+ */
+sealed class Key(val name: String, val identity: Any) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (this.javaClass != other?.javaClass) return false
+        return identity == (other as? Key)?.identity
+    }
+
+    override fun hashCode(): Int {
+        return identity.hashCode()
+    }
+
+    override fun toString(): String {
+        return "Key(name=$name)"
+    }
+}
+
+/** Key for a scene. */
+class SceneKey(
+    name: String,
+    identity: Any = Object(),
+) : Key(name, identity) {
+
+    /** The unique [ElementKey] identifying this scene's root element. */
+    val rootElementKey = ElementKey(name, identity)
+
+    override fun toString(): String {
+        return "SceneKey(name=$name)"
+    }
+}
+
+/** Key for an element. */
+class ElementKey(
+    name: String,
+    identity: Any = Object(),
+
+    /**
+     * Whether this element is a background and usually drawn below other elements. This should be
+     * set to true to make sure that shared backgrounds are drawn below elements of other scenes.
+     */
+    val isBackground: Boolean = false,
+) : Key(name, identity), ElementMatcher {
+    override fun matches(key: ElementKey): Boolean {
+        return key == this
+    }
+
+    override fun toString(): String {
+        return "ElementKey(name=$name)"
+    }
+
+    companion object {
+        /** Matches any element whose [key identity][ElementKey.identity] matches [predicate]. */
+        fun withIdentity(predicate: (Any) -> Boolean): ElementMatcher {
+            return object : ElementMatcher {
+                override fun matches(key: ElementKey): Boolean = predicate(key.identity)
+            }
+        }
+    }
+}
+
+/** Key for a shared value of an element. */
+class ValueKey(name: String, identity: Any = Object()) : Key(name, identity) {
+    override fun toString(): String {
+        return "ValueKey(name=$name)"
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt
new file mode 100644
index 0000000..a625250
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.runtime.snapshotFlow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+
+/**
+ * A scene transition state.
+ *
+ * This models the same thing as [TransitionState], with the following distinctions:
+ * 1. [TransitionState] values are backed by the Snapshot system (Compose State objects) and can be
+ *    used by callers tracking State reads, for instance in Compose code during the composition,
+ *    layout or Compose drawing phases.
+ * 2. [ObservableTransitionState] values are backed by Kotlin [Flow]s and can be collected by
+ *    non-Compose code to observe value changes.
+ * 3. [ObservableTransitionState.Transition.fromScene] and
+ *    [ObservableTransitionState.Transition.toScene] will never be equal, while
+ *    [TransitionState.Transition.fromScene] and [TransitionState.Transition.toScene] can be equal.
+ */
+sealed class ObservableTransitionState {
+    /** No transition/animation is currently running. */
+    data class Idle(val scene: SceneKey) : ObservableTransitionState()
+
+    /** There is a transition animating between two scenes. */
+    data class Transition(
+        val fromScene: SceneKey,
+        val toScene: SceneKey,
+        val progress: Flow<Float>,
+    ) : ObservableTransitionState()
+}
+
+/**
+ * The current [ObservableTransitionState]. This models the same thing as
+ * [SceneTransitionLayoutState.transitionState], except that it is backed by Flows and can be used
+ * by non-Compose code to observe state changes.
+ */
+fun SceneTransitionLayoutState.observableTransitionState(): Flow<ObservableTransitionState> {
+    return snapshotFlow {
+            when (val state = transitionState) {
+                is TransitionState.Idle -> ObservableTransitionState.Idle(state.currentScene)
+                is TransitionState.Transition -> {
+                    if (state.fromScene == state.toScene) {
+                        ObservableTransitionState.Idle(state.currentScene)
+                    } else {
+                        ObservableTransitionState.Transition(
+                            fromScene = state.fromScene,
+                            toScene = state.toScene,
+                            progress = snapshotFlow { state.progress },
+                        )
+                    }
+                }
+            }
+        }
+        .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
new file mode 100644
index 0000000..b44c8ef
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.zIndex
+
+/** A scene in a [SceneTransitionLayout]. */
+internal class Scene(
+    val key: SceneKey,
+    layoutImpl: SceneTransitionLayoutImpl,
+    content: @Composable SceneScope.() -> Unit,
+    actions: Map<UserAction, SceneKey>,
+    zIndex: Float,
+) {
+    private val scope = SceneScopeImpl(layoutImpl, this)
+
+    var content by mutableStateOf(content)
+    var userActions by mutableStateOf(actions)
+    var zIndex by mutableFloatStateOf(zIndex)
+    var size by mutableStateOf(IntSize.Zero)
+
+    @Composable
+    fun Content(modifier: Modifier = Modifier) {
+        Box(modifier.zIndex(zIndex).onPlaced { size = it.size }) { scope.content() }
+    }
+
+    override fun toString(): String {
+        return "Scene(key=$key)"
+    }
+}
+
+private class SceneScopeImpl(
+    private val layoutImpl: SceneTransitionLayoutImpl,
+    private val scene: Scene,
+) : SceneScope {
+    @Composable
+    override fun Modifier.element(key: ElementKey): Modifier {
+        return element(layoutImpl, scene, key)
+    }
+
+    @Composable
+    override fun <T> animateSharedValueAsState(
+        value: T,
+        key: ValueKey,
+        element: ElementKey,
+        lerp: (T, T, Float) -> T,
+        canOverflow: Boolean
+    ): State<T> {
+        val element =
+            layoutImpl.elements[element]
+                ?: error(
+                    "Element $element is not composed. Make sure to call animateSharedXAsState " +
+                        "*after* Modifier.element(key)."
+                )
+
+        return animateSharedValueAsState(
+            layoutImpl,
+            scene,
+            element,
+            key,
+            value,
+            lerp,
+            canOverflow,
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
new file mode 100644
index 0000000..39173d9
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
+
+/**
+ * [SceneTransitionLayout] is a container that automatically animates its content whenever
+ * [currentScene] changes, using the transitions defined in [transitions].
+ *
+ * Note: You should use [androidx.compose.animation.AnimatedContent] instead of
+ * [SceneTransitionLayout] if it fits your need. Use [SceneTransitionLayout] over AnimatedContent if
+ * you need support for swipe gestures, shared elements or transitions defined declaratively outside
+ * UI code.
+ *
+ * @param currentScene the current scene
+ * @param onChangeScene a mutator that should set [currentScene] to the given scene when called.
+ *   This is called when the user commits a transition to a new scene because of a [UserAction], for
+ *   instance by triggering back navigation or by swiping to a new scene.
+ * @param transitions the definition of the transitions used to animate a change of scene.
+ * @param state the observable state of this layout.
+ * @param scenes the configuration of the different scenes of this layout.
+ */
+@Composable
+fun SceneTransitionLayout(
+    currentScene: SceneKey,
+    onChangeScene: (SceneKey) -> Unit,
+    transitions: SceneTransitions,
+    modifier: Modifier = Modifier,
+    state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) },
+    scenes: SceneTransitionLayoutScope.() -> Unit,
+) {
+    val density = LocalDensity.current
+    val layoutImpl = remember {
+        SceneTransitionLayoutImpl(
+            onChangeScene,
+            scenes,
+            transitions,
+            state,
+            density,
+        )
+    }
+
+    layoutImpl.onChangeScene = onChangeScene
+    layoutImpl.transitions = transitions
+    layoutImpl.density = density
+    layoutImpl.setScenes(scenes)
+    layoutImpl.setCurrentScene(currentScene)
+
+    layoutImpl.Content(modifier)
+}
+
+interface SceneTransitionLayoutScope {
+    /**
+     * Add a scene to this layout, identified by [key].
+     *
+     * You can configure [userActions] so that swiping on this layout or navigating back will
+     * transition to a different scene.
+     *
+     * Important: scene order along the z-axis follows call order. Calling scene(A) followed by
+     * scene(B) will mean that scene B renders after/above scene A.
+     */
+    fun scene(
+        key: SceneKey,
+        userActions: Map<UserAction, SceneKey> = emptyMap(),
+        content: @Composable SceneScope.() -> Unit,
+    )
+}
+
+interface SceneScope {
+    /**
+     * Tag an element identified by [key].
+     *
+     * Tagging an element will allow you to reference that element when defining transitions, so
+     * that the element can be transformed and animated when the scene transitions in or out.
+     *
+     * Additionally, this [key] will be used to detect elements that are shared between scenes to
+     * automatically interpolate their size, offset and [shared values][animateSharedValueAsState].
+     *
+     * TODO(b/291566282): Migrate this to the new Modifier Node API and remove the @Composable
+     *   constraint.
+     */
+    @Composable fun Modifier.element(key: ElementKey): Modifier
+
+    /**
+     * Animate some value of a shared element.
+     *
+     * @param value the value of this shared value in the current scene.
+     * @param key the key of this shared value.
+     * @param element the element associated with this value.
+     * @param lerp the *linear* interpolation function that should be used to interpolate between
+     *   two different values. Note that it has to be linear because the [fraction] passed to this
+     *   interpolator is already interpolated.
+     * @param canOverflow whether this value can overflow past the values it is interpolated
+     *   between, for instance because the transition is animated using a bouncy spring.
+     * @see animateSharedIntAsState
+     * @see animateSharedFloatAsState
+     * @see animateSharedDpAsState
+     * @see animateSharedColorAsState
+     */
+    @Composable
+    fun <T> animateSharedValueAsState(
+        value: T,
+        key: ValueKey,
+        element: ElementKey,
+        lerp: (start: T, stop: T, fraction: Float) -> T,
+        canOverflow: Boolean,
+    ): State<T>
+}
+
+/** An action performed by the user. */
+sealed interface UserAction
+
+/** The user navigated back, either using a gesture or by triggering a KEYCODE_BACK event. */
+object Back : UserAction
+
+/** The user swiped on the container. */
+enum class Swipe : UserAction {
+    Up,
+    Down,
+    Left,
+    Right,
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
new file mode 100644
index 0000000..350b9c2
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.SnapshotStateMap
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.layout.LookaheadScope
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import com.android.compose.ui.util.fastForEach
+import kotlinx.coroutines.channels.Channel
+
+internal class SceneTransitionLayoutImpl(
+    onChangeScene: (SceneKey) -> Unit,
+    builder: SceneTransitionLayoutScope.() -> Unit,
+    transitions: SceneTransitions,
+    internal val state: SceneTransitionLayoutState,
+    density: Density,
+) {
+    internal val scenes = SnapshotStateMap<SceneKey, Scene>()
+    internal val elements = SnapshotStateMap<ElementKey, Element>()
+
+    /** The scenes that are "ready", i.e. they were composed and fully laid-out at least once. */
+    private val readyScenes = SnapshotStateMap<SceneKey, Boolean>()
+
+    internal var onChangeScene by mutableStateOf(onChangeScene)
+    internal var transitions by mutableStateOf(transitions)
+    internal var density: Density by mutableStateOf(density)
+
+    /**
+     * The size of this layout. Note that this could be [IntSize.Zero] if this layour does not have
+     * any scene configured or right before the first measure pass of the layout.
+     */
+    internal var size by mutableStateOf(IntSize.Zero)
+
+    init {
+        setScenes(builder)
+    }
+
+    internal fun scene(key: SceneKey): Scene {
+        return scenes[key] ?: error("Scene $key is not configured")
+    }
+
+    internal fun setScenes(builder: SceneTransitionLayoutScope.() -> Unit) {
+        // Keep a reference of the current scenes. After processing [builder], the scenes that were
+        // not configured will be removed.
+        val scenesToRemove = scenes.keys.toMutableSet()
+
+        // The incrementing zIndex of each scene.
+        var zIndex = 0f
+
+        object : SceneTransitionLayoutScope {
+                override fun scene(
+                    key: SceneKey,
+                    userActions: Map<UserAction, SceneKey>,
+                    content: @Composable SceneScope.() -> Unit,
+                ) {
+                    scenesToRemove.remove(key)
+
+                    val scene = scenes[key]
+                    if (scene != null) {
+                        // Update an existing scene.
+                        scene.content = content
+                        scene.userActions = userActions
+                        scene.zIndex = zIndex
+                    } else {
+                        // New scene.
+                        scenes[key] =
+                            Scene(
+                                key,
+                                this@SceneTransitionLayoutImpl,
+                                content,
+                                userActions,
+                                zIndex,
+                            )
+                    }
+
+                    zIndex++
+                }
+            }
+            .builder()
+
+        scenesToRemove.forEach { scenes.remove(it) }
+    }
+
+    @Composable
+    internal fun setCurrentScene(key: SceneKey) {
+        val channel = remember { Channel<SceneKey>(Channel.CONFLATED) }
+        SideEffect { channel.trySend(key) }
+        LaunchedEffect(channel) {
+            for (newKey in channel) {
+                // Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame
+                // late.
+                val newKey = channel.tryReceive().getOrNull() ?: newKey
+                animateToScene(this@SceneTransitionLayoutImpl, newKey)
+            }
+        }
+    }
+
+    @Composable
+    @OptIn(ExperimentalComposeUiApi::class)
+    internal fun Content(modifier: Modifier) {
+        Box(
+            modifier
+                // Handle horizontal and vertical swipes on this layout.
+                // Note: order here is important and will give a slight priority to the vertical
+                // swipes.
+                .swipeToScene(layoutImpl = this, Orientation.Horizontal)
+                .swipeToScene(layoutImpl = this, Orientation.Vertical)
+                .onSizeChanged { size = it }
+        ) {
+            LookaheadScope {
+                val scenesToCompose =
+                    when (val state = state.transitionState) {
+                        is TransitionState.Idle -> listOf(scene(state.currentScene))
+                        is TransitionState.Transition -> {
+                            if (state.toScene != state.fromScene) {
+                                listOf(scene(state.toScene), scene(state.fromScene))
+                            } else {
+                                listOf(scene(state.fromScene))
+                            }
+                        }
+                    }
+
+                // Handle back events.
+                // TODO(b/290184746): Make sure that this works with SystemUI once we use
+                // SceneTransitionLayout in Flexiglass.
+                scene(state.transitionState.currentScene).userActions[Back]?.let { backScene ->
+                    BackHandler { onChangeScene(backScene) }
+                }
+
+                Box(
+                    Modifier.drawWithContent {
+                        drawContent()
+
+                        // At this point, all scenes in scenesToCompose are fully laid out so they
+                        // are marked as ready. This is necessary because the animation code needs
+                        // to know the position and size of the elements in each scenes they are in,
+                        // so [readyScenes] will be used to decide whether the transition is ready
+                        // (see isTransitionReady() below).
+                        //
+                        // We can't do that in a DisposableEffect or SideEffect because those are
+                        // run between composition and layout. LaunchedEffect could work and might
+                        // be better, but it looks like launched effects run a frame later than this
+                        // code so doing this here seems better for performance.
+                        scenesToCompose.fastForEach { readyScenes[it.key] = true }
+                    }
+                ) {
+                    scenesToCompose.fastForEach { scene ->
+                        val key = scene.key
+                        key(key) {
+                            DisposableEffect(key) { onDispose { readyScenes.remove(key) } }
+
+                            scene.Content(
+                                Modifier.drawWithContent {
+                                    when (val state = state.transitionState) {
+                                        is TransitionState.Idle -> drawContent()
+                                        is TransitionState.Transition -> {
+                                            // Don't draw scenes that are not ready yet.
+                                            if (
+                                                readyScenes.containsKey(key) ||
+                                                    state.fromScene == state.toScene
+                                            ) {
+                                                drawContent()
+                                            }
+                                        }
+                                    }
+                                }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Return whether [transition] is ready, i.e. the elements of both scenes of the transition were
+     * laid out at least once.
+     */
+    internal fun isTransitionReady(transition: TransitionState.Transition): Boolean {
+        return readyScenes.containsKey(transition.fromScene) &&
+            readyScenes.containsKey(transition.toScene)
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
new file mode 100644
index 0000000..47e3d5a
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+
+/** The state of a [SceneTransitionLayout]. */
+class SceneTransitionLayoutState(initialScene: SceneKey) {
+    /**
+     * The current [TransitionState]. All values read here are backed by the Snapshot system.
+     *
+     * To observe those values outside of Compose/the Snapshot system, use
+     * [SceneTransitionLayoutState.observableTransitionState] instead.
+     */
+    var transitionState: TransitionState by mutableStateOf(TransitionState.Idle(initialScene))
+        internal set
+}
+
+sealed interface TransitionState {
+    /**
+     * The current effective scene. If a new transition was triggered, it would start from this
+     * scene.
+     *
+     * For instance, when swiping from scene A to scene B, the [currentScene] is A when the swipe
+     * gesture starts, but then if the user flings their finger and commits the transition to scene
+     * B, then [currentScene] becomes scene B even if the transition is not finished yet and is
+     * still animating to settle to scene B.
+     */
+    val currentScene: SceneKey
+
+    /** No transition/animation is currently running. */
+    data class Idle(override val currentScene: SceneKey) : TransitionState
+
+    /**
+     * There is a transition animating between two scenes.
+     *
+     * Important note: [fromScene] and [toScene] might be the same, in which case this [Transition]
+     * should be treated the same as [Idle]. This is designed on purpose so that a [Transition] can
+     * be started without knowing in advance where it is transitioning to, making the logic of
+     * [swipeToScene] easier to reason about.
+     */
+    interface Transition : TransitionState {
+        /** The scene this transition is starting from. */
+        val fromScene: SceneKey
+
+        /** The scene this transition is going to. */
+        val toScene: SceneKey
+
+        /**
+         * The progress of the transition. This is usually in the `[0; 1]` range, but it can also be
+         * less than `0` or greater than `1` when using transitions with a spring AnimationSpec or
+         * when flinging quickly during a swipe gesture.
+         */
+        val progress: Float
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
new file mode 100644
index 0000000..f4e3902
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.snap
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.transformation.AnchoredSize
+import com.android.compose.animation.scene.transformation.AnchoredTranslate
+import com.android.compose.animation.scene.transformation.EdgeTranslate
+import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.ModifierTransformation
+import com.android.compose.animation.scene.transformation.PropertyTransformation
+import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
+import com.android.compose.animation.scene.transformation.ScaleSize
+import com.android.compose.animation.scene.transformation.Transformation
+import com.android.compose.animation.scene.transformation.Translate
+import com.android.compose.ui.util.fastForEach
+import com.android.compose.ui.util.fastMap
+
+/** The transitions configuration of a [SceneTransitionLayout]. */
+class SceneTransitions(
+    private val transitionSpecs: List<TransitionSpec>,
+) {
+    private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>()
+
+    internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec {
+        return cache.getOrPut(from) { mutableMapOf() }.getOrPut(to) { findSpec(from, to) }
+    }
+
+    private fun findSpec(from: SceneKey, to: SceneKey): TransitionSpec {
+        val spec = transition(from, to) { it.from == from && it.to == to }
+        if (spec != null) {
+            return spec
+        }
+
+        val reversed = transition(from, to) { it.from == to && it.to == from }
+        if (reversed != null) {
+            return reversed.reverse()
+        }
+
+        val relaxedSpec =
+            transition(from, to) {
+                (it.from == from && it.to == null) || (it.to == to && it.from == null)
+            }
+        if (relaxedSpec != null) {
+            return relaxedSpec
+        }
+
+        return transition(from, to) {
+                (it.from == to && it.to == null) || (it.to == from && it.from == null)
+            }
+            ?.reverse()
+            ?: defaultTransition(from, to)
+    }
+
+    private fun transition(
+        from: SceneKey,
+        to: SceneKey,
+        filter: (TransitionSpec) -> Boolean,
+    ): TransitionSpec? {
+        var match: TransitionSpec? = null
+        transitionSpecs.fastForEach { spec ->
+            if (filter(spec)) {
+                if (match != null) {
+                    error("Found multiple transition specs for transition $from => $to")
+                }
+                match = spec
+            }
+        }
+        return match
+    }
+
+    private fun defaultTransition(from: SceneKey, to: SceneKey) =
+        TransitionSpec(from, to, emptyList(), snap())
+}
+
+/** The definition of a transition between [from] and [to]. */
+data class TransitionSpec(
+    val from: SceneKey?,
+    val to: SceneKey?,
+    val transformations: List<Transformation>,
+    val spec: AnimationSpec<Float>,
+) {
+    private val cache = mutableMapOf<ElementKey, ElementTransformations>()
+
+    internal fun reverse(): TransitionSpec {
+        return copy(
+            from = to,
+            to = from,
+            transformations = transformations.fastMap { it.reverse() },
+        )
+    }
+
+    internal fun transformations(element: ElementKey): ElementTransformations {
+        return cache.getOrPut(element) { computeTransformations(element) }
+    }
+
+    /** Filter [transformations] to compute the [ElementTransformations] of [element]. */
+    private fun computeTransformations(element: ElementKey): ElementTransformations {
+        val modifier = mutableListOf<ModifierTransformation>()
+        var offset: PropertyTransformation<Offset>? = null
+        var size: PropertyTransformation<IntSize>? = null
+        var alpha: PropertyTransformation<Float>? = null
+
+        fun <T> onPropertyTransformation(
+            root: PropertyTransformation<T>,
+            current: PropertyTransformation<T> = root,
+        ) {
+            when (current) {
+                is Translate,
+                is EdgeTranslate,
+                is AnchoredTranslate -> {
+                    throwIfNotNull(offset, element, property = "offset")
+                    offset = root as PropertyTransformation<Offset>
+                }
+                is ScaleSize,
+                is AnchoredSize -> {
+                    throwIfNotNull(size, element, property = "size")
+                    size = root as PropertyTransformation<IntSize>
+                }
+                is Fade -> {
+                    throwIfNotNull(alpha, element, property = "alpha")
+                    alpha = root as PropertyTransformation<Float>
+                }
+                is RangedPropertyTransformation -> onPropertyTransformation(root, current.delegate)
+            }
+        }
+
+        transformations.fastForEach { transformation ->
+            if (!transformation.matcher.matches(element)) {
+                return@fastForEach
+            }
+
+            when (transformation) {
+                is ModifierTransformation -> modifier.add(transformation)
+                is PropertyTransformation<*> -> onPropertyTransformation(transformation)
+            }
+        }
+
+        return ElementTransformations(modifier, offset, size, alpha)
+    }
+
+    private fun throwIfNotNull(
+        previous: PropertyTransformation<*>?,
+        element: ElementKey,
+        property: String,
+    ) {
+        if (previous != null) {
+            error("$element has multiple transformations for its $property property")
+        }
+    }
+}
+
+/** The transformations of an element during a transition. */
+internal class ElementTransformations(
+    val modifier: List<ModifierTransformation>,
+    val offset: PropertyTransformation<Offset>?,
+    val size: PropertyTransformation<IntSize>?,
+    val alpha: PropertyTransformation<Float>?,
+)
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
new file mode 100644
index 0000000..d9a45cd
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -0,0 +1,419 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.gestures.rememberDraggableState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import kotlin.math.absoluteValue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+/**
+ * Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state.
+ */
+@Composable
+internal fun Modifier.swipeToScene(
+    layoutImpl: SceneTransitionLayoutImpl,
+    orientation: Orientation,
+): Modifier {
+    val state = layoutImpl.state.transitionState
+    val currentScene = layoutImpl.scene(state.currentScene)
+    val transition = remember {
+        // Note that the currentScene here does not matter, it's only used for initializing the
+        // transition and will be replaced when a drag event starts.
+        SwipeTransition(initialScene = currentScene)
+    }
+
+    val enabled = state == transition || currentScene.shouldEnableSwipes(orientation)
+
+    // Immediately start the drag if this our [transition] is currently animating to a scene (i.e.
+    // the user released their input pointer after swiping in this orientation) and the user can't
+    // swipe in the other direction.
+    val startDragImmediately =
+        state == transition &&
+            transition.isAnimatingOffset &&
+            !currentScene.shouldEnableSwipes(orientation.opposite())
+
+    // The velocity threshold at which the intent of the user is to swipe up or down. It is the same
+    // as SwipeableV2Defaults.VelocityThreshold.
+    val velocityThreshold = with(LocalDensity.current) { 125.dp.toPx() }
+
+    // The positional threshold at which the intent of the user is to swipe to the next scene. It is
+    // the same as SwipeableV2Defaults.PositionalThreshold.
+    val positionalThreshold = with(LocalDensity.current) { 56.dp.toPx() }
+
+    return draggable(
+        orientation = orientation,
+        enabled = enabled,
+        startDragImmediately = startDragImmediately,
+        onDragStarted = { onDragStarted(layoutImpl, transition, orientation) },
+        state =
+            rememberDraggableState { delta -> onDrag(layoutImpl, transition, orientation, delta) },
+        onDragStopped = { velocity ->
+            onDragStopped(
+                layoutImpl,
+                transition,
+                velocity,
+                velocityThreshold,
+                positionalThreshold,
+            )
+        },
+    )
+}
+
+private class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
+    var _currentScene by mutableStateOf(initialScene)
+    override val currentScene: SceneKey
+        get() = _currentScene.key
+
+    var _fromScene by mutableStateOf(initialScene)
+    override val fromScene: SceneKey
+        get() = _fromScene.key
+
+    var _toScene by mutableStateOf(initialScene)
+    override val toScene: SceneKey
+        get() = _toScene.key
+
+    override val progress: Float
+        get() {
+            val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
+            if (distance == 0f) {
+                // This can happen only if fromScene == toScene.
+                error(
+                    "Transition.progress should be called only when Transition.fromScene != " +
+                        "Transition.toScene"
+                )
+            }
+            return offset / distance
+        }
+
+    /** The current offset caused by the drag gesture. */
+    var dragOffset by mutableFloatStateOf(0f)
+
+    /**
+     * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture.
+     */
+    var isAnimatingOffset by mutableStateOf(false)
+
+    /** The animatable used to animate the offset once the user lifted its finger. */
+    val offsetAnimatable = Animatable(0f, visibilityThreshold = OffsetVisibilityThreshold)
+
+    /**
+     * The job currently animating [offsetAnimatable], if it is animating. Note that setting this to
+     * a new job will automatically cancel the previous one.
+     */
+    var offsetAnimationJob: Job? = null
+        set(value) {
+            field?.cancel()
+            field = value
+        }
+
+    /** The absolute distance between [fromScene] and [toScene]. */
+    var absoluteDistance = 0f
+
+    /**
+     * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
+     * or to the left of [toScene].
+     */
+    var _distance by mutableFloatStateOf(0f)
+    val distance: Float
+        get() = _distance
+}
+
+/** The destination scene when swiping up or left from [this@upOrLeft]. */
+private fun Scene.upOrLeft(orientation: Orientation): SceneKey? {
+    return when (orientation) {
+        Orientation.Vertical -> userActions[Swipe.Up]
+        Orientation.Horizontal -> userActions[Swipe.Left]
+    }
+}
+
+/** The destination scene when swiping down or right from [this@downOrRight]. */
+private fun Scene.downOrRight(orientation: Orientation): SceneKey? {
+    return when (orientation) {
+        Orientation.Vertical -> userActions[Swipe.Down]
+        Orientation.Horizontal -> userActions[Swipe.Right]
+    }
+}
+
+/** Whether swipe should be enabled in the given [orientation]. */
+private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
+    return upOrLeft(orientation) != null || downOrRight(orientation) != null
+}
+
+private fun Orientation.opposite(): Orientation {
+    return when (this) {
+        Orientation.Vertical -> Orientation.Horizontal
+        Orientation.Horizontal -> Orientation.Vertical
+    }
+}
+
+private fun onDragStarted(
+    layoutImpl: SceneTransitionLayoutImpl,
+    transition: SwipeTransition,
+    orientation: Orientation,
+) {
+    if (layoutImpl.state.transitionState == transition) {
+        // This [transition] was already driving the animation: simply take over it.
+        if (transition.isAnimatingOffset) {
+            // Stop animating and start from where the current offset. Setting the animation job to
+            // `null` will effectively cancel the animation.
+            transition.isAnimatingOffset = false
+            transition.offsetAnimationJob = null
+            transition.dragOffset = transition.offsetAnimatable.value
+        }
+
+        return
+    }
+
+    // TODO(b/290184746): Better handle interruptions here if state != idle.
+
+    val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
+
+    transition._currentScene = fromScene
+    transition._fromScene = fromScene
+
+    // We don't know where we are transitioning to yet given that the drag just started, so set it
+    // to fromScene, which will effectively be treated the same as Idle(fromScene).
+    transition._toScene = fromScene
+
+    transition.dragOffset = 0f
+    transition.isAnimatingOffset = false
+    transition.offsetAnimationJob = null
+
+    // Use the layout size in the swipe orientation for swipe distance.
+    // TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, we
+    // will also have to make sure that we correctly handle overscroll.
+    transition.absoluteDistance =
+        when (orientation) {
+            Orientation.Horizontal -> layoutImpl.size.width
+            Orientation.Vertical -> layoutImpl.size.height
+        }.toFloat()
+
+    if (transition.absoluteDistance > 0f) {
+        layoutImpl.state.transitionState = transition
+    }
+}
+
+private fun onDrag(
+    layoutImpl: SceneTransitionLayoutImpl,
+    transition: SwipeTransition,
+    orientation: Orientation,
+    delta: Float,
+) {
+    transition.dragOffset += delta
+
+    // First check transition.fromScene should be changed for the case where the user quickly swiped
+    // twice in a row to accelerate the transition and go from A => B then B => C really fast.
+    maybeHandleAcceleratedSwipe(transition, orientation)
+
+    val fromScene = transition._fromScene
+    val upOrLeft = fromScene.upOrLeft(orientation)
+    val downOrRight = fromScene.downOrRight(orientation)
+    val offset = transition.dragOffset
+
+    // Compute the target scene depending on the current offset.
+    val targetSceneKey: SceneKey
+    val signedDistance: Float
+    when {
+        offset < 0f && upOrLeft != null -> {
+            targetSceneKey = upOrLeft
+            signedDistance = -transition.absoluteDistance
+        }
+        offset > 0f && downOrRight != null -> {
+            targetSceneKey = downOrRight
+            signedDistance = transition.absoluteDistance
+        }
+        else -> {
+            targetSceneKey = fromScene.key
+            signedDistance = 0f
+        }
+    }
+
+    if (transition._toScene.key != targetSceneKey) {
+        transition._toScene = layoutImpl.scenes.getValue(targetSceneKey)
+    }
+
+    if (transition._distance != signedDistance) {
+        transition._distance = signedDistance
+    }
+}
+
+/**
+ * Change fromScene in the case where the user quickly swiped multiple times in the same direction
+ * to accelerate the transition from A => B then B => C.
+ */
+private fun maybeHandleAcceleratedSwipe(
+    transition: SwipeTransition,
+    orientation: Orientation,
+) {
+    val toScene = transition._toScene
+    val fromScene = transition._fromScene
+
+    // If the swipe was not committed, don't do anything.
+    if (fromScene == toScene || transition._currentScene != toScene) {
+        return
+    }
+
+    // If the offset is past the distance then let's change fromScene so that the user can swipe to
+    // the next screen or go back to the previous one.
+    val offset = transition.dragOffset
+    val absoluteDistance = transition.absoluteDistance
+    if (offset <= -absoluteDistance && fromScene.upOrLeft(orientation) == toScene.key) {
+        transition.dragOffset += absoluteDistance
+        transition._fromScene = toScene
+    } else if (offset >= absoluteDistance && fromScene.downOrRight(orientation) == toScene.key) {
+        transition.dragOffset -= absoluteDistance
+        transition._fromScene = toScene
+    }
+
+    // Important note: toScene and distance will be updated right after this function is called,
+    // using fromScene and dragOffset.
+}
+
+private fun CoroutineScope.onDragStopped(
+    layoutImpl: SceneTransitionLayoutImpl,
+    transition: SwipeTransition,
+    velocity: Float,
+    velocityThreshold: Float,
+    positionalThreshold: Float,
+) {
+    // The state was changed since the drag started; don't do anything.
+    if (layoutImpl.state.transitionState != transition) {
+        return
+    }
+
+    // We were not animating.
+    if (transition._fromScene == transition._toScene) {
+        layoutImpl.state.transitionState = TransitionState.Idle(transition._fromScene.key)
+        return
+    }
+
+    // Compute the destination scene (and therefore offset) to settle in.
+    val targetScene: Scene
+    val targetOffset: Float
+    val offset = transition.dragOffset
+    val distance = transition.distance
+    if (
+        shouldCommitSwipe(
+            offset,
+            distance,
+            velocity,
+            velocityThreshold,
+            positionalThreshold,
+            wasCommitted = transition._currentScene == transition._toScene,
+        )
+    ) {
+        targetOffset = distance
+        targetScene = transition._toScene
+    } else {
+        targetOffset = 0f
+        targetScene = transition._fromScene
+    }
+
+    // If the effective current scene changed, it should be reflected right now in the current scene
+    // state, even before the settle animation is ongoing. That way all the swipeables and back
+    // handlers will be refreshed and the user can for instance quickly swipe vertically from A => B
+    // then horizontally from B => C, or swipe from A => B then immediately go back B => A.
+    if (targetScene != transition._currentScene) {
+        transition._currentScene = targetScene
+        layoutImpl.onChangeScene(targetScene.key)
+    }
+
+    // Animate the offset.
+    transition.offsetAnimationJob = launch {
+        transition.offsetAnimatable.snapTo(offset)
+        transition.isAnimatingOffset = true
+
+        transition.offsetAnimatable.animateTo(
+            targetOffset,
+            // TODO(b/290184746): Make this spring spec configurable.
+            spring(
+                stiffness = Spring.StiffnessMediumLow,
+                visibilityThreshold = OffsetVisibilityThreshold
+            ),
+            initialVelocity = velocity,
+        )
+
+        // Now that the animation is done, the state should be idle. Note that if the state was
+        // changed since this animation started, some external code changed it and we shouldn't do
+        // anything here. Note also that this job will be cancelled in the case where the user
+        // intercepts this swipe.
+        if (layoutImpl.state.transitionState == transition) {
+            layoutImpl.state.transitionState = TransitionState.Idle(targetScene.key)
+        }
+
+        transition.offsetAnimationJob = null
+    }
+}
+
+/**
+ * Whether the swipe to the target scene should be committed or not. This is inspired by
+ * SwipeableV2.computeTarget().
+ */
+private fun shouldCommitSwipe(
+    offset: Float,
+    distance: Float,
+    velocity: Float,
+    velocityThreshold: Float,
+    positionalThreshold: Float,
+    wasCommitted: Boolean,
+): Boolean {
+    fun isCloserToTarget(): Boolean {
+        return (offset - distance).absoluteValue < offset.absoluteValue
+    }
+
+    // Swiping up or left.
+    if (distance < 0f) {
+        return if (offset > 0f || velocity >= velocityThreshold) {
+            false
+        } else {
+            velocity <= -velocityThreshold ||
+                (offset <= -positionalThreshold && !wasCommitted) ||
+                isCloserToTarget()
+        }
+    }
+
+    // Swiping down or right.
+    return if (offset < 0f || velocity <= -velocityThreshold) {
+        false
+    } else {
+        velocity >= velocityThreshold ||
+            (offset >= positionalThreshold && !wasCommitted) ||
+            isCloserToTarget()
+    }
+}
+
+/**
+ * The number of pixels below which there won't be a visible difference in the transition and from
+ * which the animation can stop.
+ */
+private const val OffsetVisibilityThreshold = 0.5f
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
new file mode 100644
index 0000000..fb12b90
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+/** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */
+fun transitions(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
+    return transitionsImpl(builder)
+}
+
+@DslMarker annotation class TransitionDsl
+
+@TransitionDsl
+interface SceneTransitionsBuilder {
+    /**
+     * Define the default animation to be played when transitioning [to] the specified scene, from
+     * any scene. For the animation specification to apply only when transitioning between two
+     * specific scenes, use [from] instead.
+     *
+     * @see from
+     */
+    fun to(
+        to: SceneKey,
+        builder: TransitionBuilder.() -> Unit = {},
+    ): TransitionSpec
+
+    /**
+     * Define the animation to be played when transitioning [from] the specified scene. For the
+     * animation specification to apply only when transitioning between two specific scenes, pass
+     * the destination scene via the [to] argument.
+     *
+     * When looking up which transition should be used when animating from scene A to scene B, we
+     * pick the single transition matching one of these predicates (in order of importance):
+     * 1. from == A && to == B
+     * 2. to == A && from == B, which is then treated in reverse.
+     * 3. (from == A && to == null) || (from == null && to == B)
+     * 4. (from == B && to == null) || (from == null && to == A), which is then treated in reverse.
+     */
+    fun from(
+        from: SceneKey,
+        to: SceneKey? = null,
+        builder: TransitionBuilder.() -> Unit = {},
+    ): TransitionSpec
+}
+
+@TransitionDsl
+interface TransitionBuilder : PropertyTransformationBuilder {
+    /**
+     * The [AnimationSpec] used to animate the progress of this transition from `0` to `1` when
+     * performing programmatic (not input pointer tracking) animations.
+     */
+    var spec: AnimationSpec<Float>
+
+    /**
+     * Define a progress-based range for the transformations inside [builder].
+     *
+     * For instance, the following will fade `Foo` during the first half of the transition then it
+     * will translate it by 100.dp during the second half.
+     *
+     * ```
+     * fractionRange(end = 0.5f) { fade(Foo) }
+     * fractionRange(start = 0.5f) { translate(Foo, x = 100.dp) }
+     * ```
+     *
+     * @param start the start of the range, in the [0; 1] range.
+     * @param end the end of the range, in the [0; 1] range.
+     */
+    fun fractionRange(
+        start: Float? = null,
+        end: Float? = null,
+        builder: PropertyTransformationBuilder.() -> Unit,
+    )
+
+    /**
+     * Define a timestamp-based range for the transformations inside [builder].
+     *
+     * For instance, the following will fade `Foo` during the first half of the transition then it
+     * will translate it by 100.dp during the second half.
+     *
+     * ```
+     * spec = tween(500)
+     * timestampRange(end = 250) { fade(Foo) }
+     * timestampRange(start = 250) { translate(Foo, x = 100.dp) }
+     * ```
+     *
+     * Important: [spec] must be a [androidx.compose.animation.core.DurationBasedAnimationSpec] if
+     * you call [timestampRange], otherwise this will throw. The spec duration will be used to
+     * transform this range into a [fractionRange].
+     *
+     * @param startMillis the start of the range, in the [0; spec.duration] range.
+     * @param endMillis the end of the range, in the [0; spec.duration] range.
+     */
+    fun timestampRange(
+        startMillis: Int? = null,
+        endMillis: Int? = null,
+        builder: PropertyTransformationBuilder.() -> Unit,
+    )
+
+    /**
+     * Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and
+     * using the given [shape].
+     *
+     * Punching a hole in an element will "remove" any pixel drawn by that element in the hole area.
+     * This can be used to make content drawn below an opaque element visible. For example, if we
+     * have [this lockscreen scene](http://shortn/_VYySFnJDhN) drawn below
+     * [this shade scene](http://shortn/_fpxGUk0Rg7) and punch a hole in the latter using the big
+     * clock time bounds and a RoundedCornerShape(10dp), [this](http://shortn/_qt80IvORFj) would be
+     * the result.
+     */
+    fun punchHole(matcher: ElementMatcher, bounds: ElementKey, shape: Shape = RectangleShape)
+}
+
+@TransitionDsl
+interface PropertyTransformationBuilder {
+    /**
+     * Fade the element(s) matching [matcher]. This will automatically fade in or fade out if the
+     * element is entering or leaving the scene, respectively.
+     */
+    fun fade(matcher: ElementMatcher)
+
+    /** Translate the element(s) matching [matcher] by ([x], [y]) dp. */
+    fun translate(matcher: ElementMatcher, x: Dp = 0.dp, y: Dp = 0.dp)
+
+    /**
+     * Translate the element(s) matching [matcher] from/to the [edge] of the [SceneTransitionLayout]
+     * animating it.
+     *
+     * If [startsOutsideLayoutBounds] is `true`, then the element will start completely outside of
+     * the layout bounds (i.e. none of it will be visible at progress = 0f if the layout clips its
+     * content). If it is `false`, then the element will start aligned with the edge of the layout
+     * (i.e. it will be completely visible at progress = 0f).
+     */
+    fun translate(matcher: ElementMatcher, edge: Edge, startsOutsideLayoutBounds: Boolean = true)
+
+    /**
+     * Translate the element(s) matching [matcher] by the same amount that [anchor] is translated
+     * during this transition.
+     *
+     * Note: This currently only works if [anchor] is a shared element of this transition.
+     *
+     * TODO(b/290184746): Also support anchors that are not shared but translated because of other
+     *   transformations, like an edge translation.
+     */
+    fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey)
+
+    /**
+     * Scale the [width] and [height] of the element(s) matching [matcher]. Note that this scaling
+     * is done during layout, so it will potentially impact the size and position of other elements.
+     *
+     * TODO(b/290184746): Also provide a scaleDrawing() to scale an element at drawing time.
+     */
+    fun scaleSize(matcher: ElementMatcher, width: Float = 1f, height: Float = 1f)
+
+    /**
+     * Scale the element(s) matching [matcher] so that it grows/shrinks to the same size as [anchor]
+     * .
+     *
+     * Note: This currently only works if [anchor] is a shared element of this transition.
+     */
+    fun anchoredSize(matcher: ElementMatcher, anchor: ElementKey)
+}
+
+/** An interface to match one or more elements. */
+interface ElementMatcher {
+    /** Whether the element with key [key] matches this matcher. */
+    fun matches(key: ElementKey): Boolean
+}
+
+/** The edge of a [SceneTransitionLayout]. */
+enum class Edge {
+    Left,
+    Right,
+    Top,
+    Bottom,
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
new file mode 100644
index 0000000..48d5638e8b
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.DurationBasedAnimationSpec
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.spring
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Dp
+import com.android.compose.animation.scene.transformation.AnchoredSize
+import com.android.compose.animation.scene.transformation.AnchoredTranslate
+import com.android.compose.animation.scene.transformation.EdgeTranslate
+import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.PropertyTransformation
+import com.android.compose.animation.scene.transformation.PunchHole
+import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
+import com.android.compose.animation.scene.transformation.ScaleSize
+import com.android.compose.animation.scene.transformation.Transformation
+import com.android.compose.animation.scene.transformation.TransformationRange
+import com.android.compose.animation.scene.transformation.Translate
+
+internal fun transitionsImpl(
+    builder: SceneTransitionsBuilder.() -> Unit,
+): SceneTransitions {
+    val impl = SceneTransitionsBuilderImpl().apply(builder)
+    return SceneTransitions(impl.transitionSpecs)
+}
+
+private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
+    val transitionSpecs = mutableListOf<TransitionSpec>()
+
+    override fun to(to: SceneKey, builder: TransitionBuilder.() -> Unit): TransitionSpec {
+        return transition(from = null, to = to, builder)
+    }
+
+    override fun from(
+        from: SceneKey,
+        to: SceneKey?,
+        builder: TransitionBuilder.() -> Unit
+    ): TransitionSpec {
+        return transition(from = from, to = to, builder)
+    }
+
+    private fun transition(
+        from: SceneKey?,
+        to: SceneKey?,
+        builder: TransitionBuilder.() -> Unit,
+    ): TransitionSpec {
+        val impl = TransitionBuilderImpl().apply(builder)
+        val spec =
+            TransitionSpec(
+                from,
+                to,
+                impl.transformations,
+                impl.spec,
+            )
+        transitionSpecs.add(spec)
+        return spec
+    }
+}
+
+internal class TransitionBuilderImpl : TransitionBuilder {
+    val transformations = mutableListOf<Transformation>()
+    override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
+
+    private var range: TransformationRange? = null
+    private val durationMillis: Int by lazy {
+        val spec = spec
+        if (spec !is DurationBasedAnimationSpec) {
+            error("timestampRange {} can only be used with a DurationBasedAnimationSpec")
+        }
+
+        spec.vectorize(Float.VectorConverter).durationMillis
+    }
+
+    override fun punchHole(matcher: ElementMatcher, bounds: ElementKey, shape: Shape) {
+        transformations.add(PunchHole(matcher, bounds, shape))
+    }
+
+    override fun fractionRange(
+        start: Float?,
+        end: Float?,
+        builder: PropertyTransformationBuilder.() -> Unit
+    ) {
+        range = TransformationRange(start, end)
+        builder()
+        range = null
+    }
+
+    override fun timestampRange(
+        startMillis: Int?,
+        endMillis: Int?,
+        builder: PropertyTransformationBuilder.() -> Unit
+    ) {
+        if (startMillis != null && (startMillis < 0 || startMillis > durationMillis)) {
+            error("invalid start value: startMillis=$startMillis durationMillis=$durationMillis")
+        }
+
+        if (endMillis != null && (endMillis < 0 || endMillis > durationMillis)) {
+            error("invalid end value: endMillis=$startMillis durationMillis=$durationMillis")
+        }
+
+        val start = startMillis?.let { it.toFloat() / durationMillis }
+        val end = endMillis?.let { it.toFloat() / durationMillis }
+        fractionRange(start, end, builder)
+    }
+
+    private fun transformation(transformation: PropertyTransformation<*>) {
+        if (range != null) {
+            transformations.add(RangedPropertyTransformation(transformation, range!!))
+        } else {
+            transformations.add(transformation)
+        }
+    }
+
+    override fun fade(matcher: ElementMatcher) {
+        transformation(Fade(matcher))
+    }
+
+    override fun translate(matcher: ElementMatcher, x: Dp, y: Dp) {
+        transformation(Translate(matcher, x, y))
+    }
+
+    override fun translate(
+        matcher: ElementMatcher,
+        edge: Edge,
+        startsOutsideLayoutBounds: Boolean
+    ) {
+        transformation(EdgeTranslate(matcher, edge, startsOutsideLayoutBounds))
+    }
+
+    override fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey) {
+        transformation(AnchoredTranslate(matcher, anchor))
+    }
+
+    override fun scaleSize(matcher: ElementMatcher, width: Float, height: Float) {
+        transformation(ScaleSize(matcher, width, height))
+    }
+
+    override fun anchoredSize(matcher: ElementMatcher, anchor: ElementKey) {
+        transformation(AnchoredSize(matcher, anchor))
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
new file mode 100644
index 0000000..d4ed697
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransitionState
+
+/** Anchor the size of an element to the size of another element. */
+internal class AnchoredSize(
+    override val matcher: ElementMatcher,
+    private val anchor: ElementKey,
+) : PropertyTransformation<IntSize> {
+    override fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+        transition: TransitionState.Transition,
+        value: IntSize,
+    ): IntSize {
+        fun anchorSizeIn(scene: SceneKey): IntSize {
+            val size = layoutImpl.elements[anchor]?.sceneValues?.get(scene)?.size
+            return if (size != null && size != Element.SizeUnspecified) {
+                size
+            } else {
+                value
+            }
+        }
+
+        // This simple implementation assumes that the size of [element] is the same as the size of
+        // the [anchor] in [scene], so simply transform to the size of the anchor in the other
+        // scene.
+        return if (scene.key == transition.fromScene) {
+            anchorSizeIn(transition.toScene)
+        } else {
+            anchorSizeIn(transition.fromScene)
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
new file mode 100644
index 0000000..8a5bd74
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.isSpecified
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransitionState
+
+/** Anchor the translation of an element to another element. */
+internal class AnchoredTranslate(
+    override val matcher: ElementMatcher,
+    private val anchor: ElementKey,
+) : PropertyTransformation<Offset> {
+    override fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+        transition: TransitionState.Transition,
+        value: Offset,
+    ): Offset {
+        val anchor = layoutImpl.elements[anchor] ?: return value
+        fun anchorOffsetIn(scene: SceneKey): Offset? {
+            return anchor.sceneValues[scene]?.offset?.takeIf { it.isSpecified }
+        }
+
+        // [element] will move the same amount as [anchor] does.
+        // TODO(b/290184746): Also support anchors that are not shared but translated because of
+        // other transformations, like an edge translation.
+        val anchorFromOffset = anchorOffsetIn(transition.fromScene) ?: return value
+        val anchorToOffset = anchorOffsetIn(transition.toScene) ?: return value
+        val offset = anchorToOffset - anchorFromOffset
+
+        return if (scene.key == transition.toScene) {
+            Offset(
+                value.x - offset.x,
+                value.y - offset.y,
+            )
+        } else {
+            Offset(
+                value.x + offset.x,
+                value.y + offset.y,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
new file mode 100644
index 0000000..5cdce94
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.ui.geometry.Offset
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransitionState
+
+/** Translate an element from an edge of the layout. */
+internal class EdgeTranslate(
+    override val matcher: ElementMatcher,
+    private val edge: Edge,
+    private val startsOutsideLayoutBounds: Boolean = true,
+) : PropertyTransformation<Offset> {
+    override fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+        transition: TransitionState.Transition,
+        value: Offset
+    ): Offset {
+        val sceneSize = scene.size
+        val elementSize = sceneValues.size
+        if (elementSize == Element.SizeUnspecified) {
+            return value
+        }
+
+        return when (edge) {
+            Edge.Top ->
+                if (startsOutsideLayoutBounds) {
+                    Offset(value.x, -elementSize.height.toFloat())
+                } else {
+                    Offset(value.x, 0f)
+                }
+            Edge.Left ->
+                if (startsOutsideLayoutBounds) {
+                    Offset(-elementSize.width.toFloat(), value.y)
+                } else {
+                    Offset(0f, value.y)
+                }
+            Edge.Bottom ->
+                if (startsOutsideLayoutBounds) {
+                    Offset(value.x, sceneSize.height.toFloat())
+                } else {
+                    Offset(value.x, (sceneSize.height - elementSize.height).toFloat())
+                }
+            Edge.Right ->
+                if (startsOutsideLayoutBounds) {
+                    Offset(sceneSize.width.toFloat(), value.y)
+                } else {
+                    Offset((sceneSize.width - elementSize.width).toFloat(), value.y)
+                }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt
new file mode 100644
index 0000000..0a5ac54
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransitionState
+
+/** Fade an element in or out. */
+internal class Fade(
+    override val matcher: ElementMatcher,
+) : PropertyTransformation<Float> {
+    override fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+        transition: TransitionState.Transition,
+        value: Float
+    ): Float {
+        // Return the alpha value of [element] either when it starts fading in or when it finished
+        // fading out, which is `0` in both cases.
+        return 0f
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt
new file mode 100644
index 0000000..31e7d7c
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.toRect
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Paint
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.drawOutline
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.graphics.withSaveLayer
+import androidx.compose.ui.unit.toSize
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+
+/** Punch a hole in an element using the bounds of another element and a given [shape]. */
+internal class PunchHole(
+    override val matcher: ElementMatcher,
+    private val bounds: ElementKey,
+    private val shape: Shape,
+) : ModifierTransformation {
+    override fun Modifier.transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+    ): Modifier {
+        return drawWithContent {
+            val bounds = layoutImpl.elements[bounds]
+            if (
+                bounds == null ||
+                    bounds.lastSize == Element.SizeUnspecified ||
+                    bounds.lastOffset == Offset.Unspecified
+            ) {
+                drawContent()
+                return@drawWithContent
+            }
+
+            drawIntoCanvas { canvas ->
+                canvas.withSaveLayer(size.toRect(), Paint()) {
+                    drawContent()
+
+                    val offset = bounds.lastOffset - element.lastOffset
+                    translate(offset.x, offset.y) { drawHole(bounds) }
+                }
+            }
+        }
+    }
+
+    private fun DrawScope.drawHole(bounds: Element) {
+        if (shape == RectangleShape) {
+            drawRect(Color.Black, blendMode = BlendMode.DstOut)
+            return
+        }
+
+        // TODO(b/290184746): Cache outline if the size of bounds does not change.
+        drawOutline(
+            shape.createOutline(
+                bounds.lastSize.toSize(),
+                layoutDirection,
+                this,
+            ),
+            Color.Black,
+            blendMode = BlendMode.DstOut,
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
new file mode 100644
index 0000000..ce754dc
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransitionState
+import kotlin.math.roundToInt
+
+/**
+ * Scales the size of an element. Note that this makes the element resize every frame and will
+ * therefore impact the layout of other elements.
+ */
+internal class ScaleSize(
+    override val matcher: ElementMatcher,
+    private val width: Float = 1f,
+    private val height: Float = 1f,
+) : PropertyTransformation<IntSize> {
+    override fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+        transition: TransitionState.Transition,
+        value: IntSize,
+    ): IntSize {
+        return IntSize(
+            width = (value.width * width).roundToInt(),
+            height = (value.height * height).roundToInt(),
+        )
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
new file mode 100644
index 0000000..ce6749d
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransitionState
+
+/** A transformation applied to one or more elements during a transition. */
+sealed interface Transformation {
+    /**
+     * The matcher that should match the element(s) to which this transformation should be applied.
+     */
+    val matcher: ElementMatcher
+
+    /*
+     * Reverse this transformation. This is called when we use Transition(from = A, to = B) when
+     * animating from B to A and there is no Transition(from = B, to = A) defined.
+     */
+    fun reverse(): Transformation = this
+}
+
+/** A transformation that is applied on the element during the whole transition. */
+internal interface ModifierTransformation : Transformation {
+    /** Apply the transformation to [element]. */
+    // TODO(b/290184746): Figure out a public API for custom transformations that don't have access
+    // to these internal classes.
+    fun Modifier.transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+    ): Modifier
+}
+
+/** A transformation that changes the value of an element property, like its size or offset. */
+internal sealed interface PropertyTransformation<T> : Transformation {
+    /**
+     * The range during which the transformation is applied. If it is `null`, then the
+     * transformation will be applied throughout the whole scene transition.
+     */
+    val range: TransformationRange?
+        get() = null
+
+    /**
+     * Transform [value], i.e. the value of the transformed property without this transformation.
+     */
+    // TODO(b/290184746): Figure out a public API for custom transformations that don't have access
+    // to these internal classes.
+    fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+        transition: TransitionState.Transition,
+        value: T,
+    ): T
+}
+
+/**
+ * A [PropertyTransformation] associated to a range. This is a helper class so that normal
+ * implementations of [PropertyTransformation] don't have to take care of reversing their range when
+ * they are reversed.
+ */
+internal class RangedPropertyTransformation<T>(
+    val delegate: PropertyTransformation<T>,
+    override val range: TransformationRange,
+) : PropertyTransformation<T> by delegate {
+    override fun reverse(): Transformation {
+        return RangedPropertyTransformation(
+            delegate.reverse() as PropertyTransformation<T>,
+            range.reverse()
+        )
+    }
+}
+
+/** The progress-based range of a [PropertyTransformation]. */
+data class TransformationRange
+private constructor(
+    val start: Float,
+    val end: Float,
+) {
+    constructor(
+        start: Float? = null,
+        end: Float? = null
+    ) : this(start ?: BoundUnspecified, end ?: BoundUnspecified)
+
+    init {
+        require(!start.isSpecified() || (start in 0f..1f))
+        require(!end.isSpecified() || (end in 0f..1f))
+        require(!start.isSpecified() || !end.isSpecified() || start <= end)
+    }
+
+    /** Reverse this range. */
+    fun reverse() = TransformationRange(start = reverseBound(end), end = reverseBound(start))
+
+    /** Get the progress of this range given the global [transitionProgress]. */
+    fun progress(transitionProgress: Float): Float {
+        return when {
+            start.isSpecified() && end.isSpecified() ->
+                ((transitionProgress - start) / (end - start)).coerceIn(0f, 1f)
+            !start.isSpecified() && !end.isSpecified() -> transitionProgress
+            end.isSpecified() -> (transitionProgress / end).coerceAtMost(1f)
+            else -> ((transitionProgress - start) / (1f - start)).coerceAtLeast(0f)
+        }
+    }
+
+    private fun Float.isSpecified() = this != BoundUnspecified
+
+    private fun reverseBound(bound: Float): Float {
+        return if (bound.isSpecified()) {
+            1f - bound
+        } else {
+            BoundUnspecified
+        }
+    }
+
+    companion object {
+        private const val BoundUnspecified = Float.MIN_VALUE
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt
new file mode 100644
index 0000000..8abca61
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.Element
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.Scene
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransitionState
+
+/** Translate an element by a fixed amount of density-independent pixels. */
+internal class Translate(
+    override val matcher: ElementMatcher,
+    private val x: Dp = 0.dp,
+    private val y: Dp = 0.dp,
+) : PropertyTransformation<Offset> {
+    override fun transform(
+        layoutImpl: SceneTransitionLayoutImpl,
+        scene: Scene,
+        element: Element,
+        sceneValues: Element.SceneValues,
+        transition: TransitionState.Transition,
+        value: Offset,
+    ): Offset {
+        return with(layoutImpl.density) {
+            Offset(
+                value.x + x.toPx(),
+                value.y + y.toPx(),
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt b/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt
index 5224c51..27f0948 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt
@@ -22,7 +22,6 @@
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.constrainWidth
 import androidx.compose.ui.unit.dp
 import kotlin.math.ceil
 import kotlin.math.max
@@ -126,18 +125,20 @@
             ((columns - 1) * horizontalSpacing.toPx()).roundToInt()
         val totalVerticalSpacingBetweenChildren = ((rows - 1) * verticalSpacing.toPx()).roundToInt()
         val childConstraints =
-            Constraints().apply {
-                if (constraints.maxWidth != Constraints.Infinity) {
-                    constrainWidth(
+            Constraints(
+                maxWidth =
+                    if (constraints.maxWidth != Constraints.Infinity) {
                         (constraints.maxWidth - totalHorizontalSpacingBetweenChildren) / columns
-                    )
-                }
-                if (constraints.maxHeight != Constraints.Infinity) {
-                    constrainWidth(
+                    } else {
+                        Constraints.Infinity
+                    },
+                maxHeight =
+                    if (constraints.maxHeight != Constraints.Infinity) {
                         (constraints.maxHeight - totalVerticalSpacingBetweenChildren) / rows
-                    )
-                }
-            }
+                    } else {
+                        Constraints.Infinity
+                    }
+            )
 
         val placeables = buildList {
             for (cellIndex in measurables.indices) {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt
new file mode 100644
index 0000000..135a6e4
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.modifiers
+
+import androidx.compose.ui.Modifier
+
+/**
+ * Concatenates this modifier with another if `condition` is true.
+ *
+ * @param condition Whether or not to apply the modifiers.
+ * @param factory Creates the modifier to concatenate with the current one.
+ * @return a Modifier representing this modifier followed by other in sequence.
+ * @see Modifier.then
+ *
+ * This method allows inline conditional addition of modifiers to a modifier chain. Instead of
+ * writing
+ *
+ * ```
+ * val aModifier = Modifier.a()
+ * val bModifier = if(condition) aModifier.b() else aModifier
+ * Composable(modifier = bModifier)
+ * ```
+ *
+ * You can instead write
+ *
+ * ```
+ * Composable(modifier = Modifier.a().thenIf(condition){
+ *  Modifier.b()
+ * }
+ * ```
+ *
+ * This makes the modifier chain easier to read.
+ *
+ * Note that unlike the non-factory version, the conditional modifier is recreated each time, and
+ * may never be created at all.
+ */
+inline fun Modifier.thenIf(condition: Boolean, crossinline factory: () -> Modifier): Modifier =
+    if (condition) this.then(factory()) else this
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/pager/Pager.kt b/packages/SystemUI/compose/core/src/com/android/compose/pager/Pager.kt
deleted file mode 100644
index a80a1f9..0000000
--- a/packages/SystemUI/compose/core/src/com/android/compose/pager/Pager.kt
+++ /dev/null
@@ -1,357 +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.compose.pager
-
-import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.DecayAnimationSpec
-import androidx.compose.animation.rememberSplineBasedDecay
-import androidx.compose.foundation.gestures.FlingBehavior
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import androidx.compose.ui.input.nestedscroll.NestedScrollSource
-import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.Velocity
-import androidx.compose.ui.unit.dp
-import kotlinx.coroutines.flow.filter
-
-/** Library-wide switch to turn on debug logging. */
-internal const val DebugLog = false
-
-@RequiresOptIn(message = "Accompanist Pager is experimental. The API may be changed in the future.")
-@Retention(AnnotationRetention.BINARY)
-annotation class ExperimentalPagerApi
-
-/** Contains the default values used by [HorizontalPager] and [VerticalPager]. */
-@ExperimentalPagerApi
-object PagerDefaults {
-    /**
-     * Remember the default [FlingBehavior] that represents the scroll curve.
-     *
-     * @param state The [PagerState] to update.
-     * @param decayAnimationSpec The decay animation spec to use for decayed flings.
-     * @param snapAnimationSpec The animation spec to use when snapping.
-     */
-    @Composable
-    fun flingBehavior(
-        state: PagerState,
-        decayAnimationSpec: DecayAnimationSpec<Float> = rememberSplineBasedDecay(),
-        snapAnimationSpec: AnimationSpec<Float> = SnappingFlingBehaviorDefaults.snapAnimationSpec,
-    ): FlingBehavior =
-        rememberSnappingFlingBehavior(
-            lazyListState = state.lazyListState,
-            decayAnimationSpec = decayAnimationSpec,
-            snapAnimationSpec = snapAnimationSpec,
-        )
-
-    @Deprecated(
-        "Replaced with PagerDefaults.flingBehavior()",
-        ReplaceWith("PagerDefaults.flingBehavior(state, decayAnimationSpec, snapAnimationSpec)")
-    )
-    @Composable
-    fun rememberPagerFlingConfig(
-        state: PagerState,
-        decayAnimationSpec: DecayAnimationSpec<Float> = rememberSplineBasedDecay(),
-        snapAnimationSpec: AnimationSpec<Float> = SnappingFlingBehaviorDefaults.snapAnimationSpec,
-    ): FlingBehavior = flingBehavior(state, decayAnimationSpec, snapAnimationSpec)
-}
-
-/**
- * A horizontally scrolling layout that allows users to flip between items to the left and right.
- *
- * @param count the number of pages.
- * @param modifier the modifier to apply to this layout.
- * @param state the state object to be used to control or observe the pager's state.
- * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be
- *   composed from the end to the start and [PagerState.currentPage] == 0 will mean the first item
- *   is located at the end.
- * @param itemSpacing horizontal spacing to add between items.
- * @param flingBehavior logic describing fling behavior.
- * @param key the scroll position will be maintained based on the key, which means if you add/remove
- *   items before the current visible item the item with the given key will be kept as the first
- *   visible one.
- * @param content a block which describes the content. Inside this block you can reference
- *   [PagerScope.currentPage] and other properties in [PagerScope].
- * @sample com.google.accompanist.sample.pager.HorizontalPagerSample
- */
-@ExperimentalPagerApi
-@Composable
-fun HorizontalPager(
-    count: Int,
-    modifier: Modifier = Modifier,
-    state: PagerState = rememberPagerState(),
-    reverseLayout: Boolean = false,
-    itemSpacing: Dp = 0.dp,
-    flingBehavior: FlingBehavior = PagerDefaults.flingBehavior(state),
-    verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
-    key: ((page: Int) -> Any)? = null,
-    contentPadding: PaddingValues = PaddingValues(0.dp),
-    content: @Composable PagerScope.(page: Int) -> Unit,
-) {
-    Pager(
-        count = count,
-        state = state,
-        modifier = modifier,
-        isVertical = false,
-        reverseLayout = reverseLayout,
-        itemSpacing = itemSpacing,
-        verticalAlignment = verticalAlignment,
-        flingBehavior = flingBehavior,
-        key = key,
-        contentPadding = contentPadding,
-        content = content
-    )
-}
-
-/**
- * A vertically scrolling layout that allows users to flip between items to the top and bottom.
- *
- * @param count the number of pages.
- * @param modifier the modifier to apply to this layout.
- * @param state the state object to be used to control or observe the pager's state.
- * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be
- *   composed from the bottom to the top and [PagerState.currentPage] == 0 will mean the first item
- *   is located at the bottom.
- * @param itemSpacing vertical spacing to add between items.
- * @param flingBehavior logic describing fling behavior.
- * @param key the scroll position will be maintained based on the key, which means if you add/remove
- *   items before the current visible item the item with the given key will be kept as the first
- *   visible one.
- * @param content a block which describes the content. Inside this block you can reference
- *   [PagerScope.currentPage] and other properties in [PagerScope].
- * @sample com.google.accompanist.sample.pager.VerticalPagerSample
- */
-@ExperimentalPagerApi
-@Composable
-fun VerticalPager(
-    count: Int,
-    modifier: Modifier = Modifier,
-    state: PagerState = rememberPagerState(),
-    reverseLayout: Boolean = false,
-    itemSpacing: Dp = 0.dp,
-    flingBehavior: FlingBehavior = PagerDefaults.flingBehavior(state),
-    horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
-    key: ((page: Int) -> Any)? = null,
-    contentPadding: PaddingValues = PaddingValues(0.dp),
-    content: @Composable PagerScope.(page: Int) -> Unit,
-) {
-    Pager(
-        count = count,
-        state = state,
-        modifier = modifier,
-        isVertical = true,
-        reverseLayout = reverseLayout,
-        itemSpacing = itemSpacing,
-        horizontalAlignment = horizontalAlignment,
-        flingBehavior = flingBehavior,
-        key = key,
-        contentPadding = contentPadding,
-        content = content
-    )
-}
-
-@ExperimentalPagerApi
-@Composable
-internal fun Pager(
-    count: Int,
-    modifier: Modifier,
-    state: PagerState,
-    reverseLayout: Boolean,
-    itemSpacing: Dp,
-    isVertical: Boolean,
-    flingBehavior: FlingBehavior,
-    key: ((page: Int) -> Any)?,
-    contentPadding: PaddingValues,
-    verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
-    horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
-    content: @Composable PagerScope.(page: Int) -> Unit,
-) {
-    require(count >= 0) { "pageCount must be >= 0" }
-
-    // Provide our PagerState with access to the SnappingFlingBehavior animation target
-    // TODO: can this be done in a better way?
-    state.flingAnimationTarget = { (flingBehavior as? SnappingFlingBehavior)?.animationTarget }
-
-    LaunchedEffect(count) {
-        state.currentPage = minOf(count - 1, state.currentPage).coerceAtLeast(0)
-    }
-
-    // Once a fling (scroll) has finished, notify the state
-    LaunchedEffect(state) {
-        // When a 'scroll' has finished, notify the state
-        snapshotFlow { state.isScrollInProgress }
-            .filter { !it }
-            .collect { state.onScrollFinished() }
-    }
-
-    val pagerScope = remember(state) { PagerScopeImpl(state) }
-
-    // We only consume nested flings in the main-axis, allowing cross-axis flings to propagate
-    // as normal
-    val consumeFlingNestedScrollConnection =
-        ConsumeFlingNestedScrollConnection(
-            consumeHorizontal = !isVertical,
-            consumeVertical = isVertical,
-        )
-
-    if (isVertical) {
-        LazyColumn(
-            state = state.lazyListState,
-            verticalArrangement = Arrangement.spacedBy(itemSpacing, verticalAlignment),
-            horizontalAlignment = horizontalAlignment,
-            flingBehavior = flingBehavior,
-            reverseLayout = reverseLayout,
-            contentPadding = contentPadding,
-            modifier = modifier,
-        ) {
-            items(
-                count = count,
-                key = key,
-            ) { page ->
-                Box(
-                    Modifier
-                        // We don't any nested flings to continue in the pager, so we add a
-                        // connection which consumes them.
-                        // See: https://github.com/google/accompanist/issues/347
-                        .nestedScroll(connection = consumeFlingNestedScrollConnection)
-                        // Constraint the content to be <= than the size of the pager.
-                        .fillParentMaxHeight()
-                        .wrapContentSize()
-                ) {
-                    pagerScope.content(page)
-                }
-            }
-        }
-    } else {
-        LazyRow(
-            state = state.lazyListState,
-            verticalAlignment = verticalAlignment,
-            horizontalArrangement = Arrangement.spacedBy(itemSpacing, horizontalAlignment),
-            flingBehavior = flingBehavior,
-            reverseLayout = reverseLayout,
-            contentPadding = contentPadding,
-            modifier = modifier,
-        ) {
-            items(
-                count = count,
-                key = key,
-            ) { page ->
-                Box(
-                    Modifier
-                        // We don't any nested flings to continue in the pager, so we add a
-                        // connection which consumes them.
-                        // See: https://github.com/google/accompanist/issues/347
-                        .nestedScroll(connection = consumeFlingNestedScrollConnection)
-                        // Constraint the content to be <= than the size of the pager.
-                        .fillParentMaxWidth()
-                        .wrapContentSize()
-                ) {
-                    pagerScope.content(page)
-                }
-            }
-        }
-    }
-}
-
-private class ConsumeFlingNestedScrollConnection(
-    private val consumeHorizontal: Boolean,
-    private val consumeVertical: Boolean,
-) : NestedScrollConnection {
-    override fun onPostScroll(
-        consumed: Offset,
-        available: Offset,
-        source: NestedScrollSource
-    ): Offset =
-        when (source) {
-            // We can consume all resting fling scrolls so that they don't propagate up to the
-            // Pager
-            NestedScrollSource.Fling -> available.consume(consumeHorizontal, consumeVertical)
-            else -> Offset.Zero
-        }
-
-    override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
-        // We can consume all post fling velocity on the main-axis
-        // so that it doesn't propagate up to the Pager
-        return available.consume(consumeHorizontal, consumeVertical)
-    }
-}
-
-private fun Offset.consume(
-    consumeHorizontal: Boolean,
-    consumeVertical: Boolean,
-): Offset =
-    Offset(
-        x = if (consumeHorizontal) this.x else 0f,
-        y = if (consumeVertical) this.y else 0f,
-    )
-
-private fun Velocity.consume(
-    consumeHorizontal: Boolean,
-    consumeVertical: Boolean,
-): Velocity =
-    Velocity(
-        x = if (consumeHorizontal) this.x else 0f,
-        y = if (consumeVertical) this.y else 0f,
-    )
-
-/** Scope for [HorizontalPager] content. */
-@ExperimentalPagerApi
-@Stable
-interface PagerScope {
-    /** Returns the current selected page */
-    val currentPage: Int
-
-    /** The current offset from the start of [currentPage], as a ratio of the page width. */
-    val currentPageOffset: Float
-}
-
-@ExperimentalPagerApi
-private class PagerScopeImpl(
-    private val state: PagerState,
-) : PagerScope {
-    override val currentPage: Int
-        get() = state.currentPage
-    override val currentPageOffset: Float
-        get() = state.currentPageOffset
-}
-
-/**
- * Calculate the offset for the given [page] from the current scroll position. This is useful when
- * using the scroll position to apply effects or animations to items.
- *
- * The returned offset can positive or negative, depending on whether which direction the [page] is
- * compared to the current scroll position.
- *
- * @sample com.google.accompanist.sample.pager.HorizontalPagerWithOffsetTransition
- */
-@ExperimentalPagerApi
-fun PagerScope.calculateCurrentOffsetForPage(page: Int): Float {
-    return (currentPage + currentPageOffset) - page
-}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/pager/PagerState.kt b/packages/SystemUI/compose/core/src/com/android/compose/pager/PagerState.kt
deleted file mode 100644
index 1822a68..0000000
--- a/packages/SystemUI/compose/core/src/com/android/compose/pager/PagerState.kt
+++ /dev/null
@@ -1,348 +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.compose.pager
-
-import androidx.annotation.FloatRange
-import androidx.annotation.IntRange
-import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.spring
-import androidx.compose.foundation.MutatePriority
-import androidx.compose.foundation.gestures.ScrollScope
-import androidx.compose.foundation.gestures.ScrollableState
-import androidx.compose.foundation.interaction.InteractionSource
-import androidx.compose.foundation.lazy.LazyListItemInfo
-import androidx.compose.foundation.lazy.LazyListState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.Saver
-import androidx.compose.runtime.saveable.listSaver
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import kotlin.math.absoluteValue
-import kotlin.math.roundToInt
-
-@Deprecated(
-    "Replaced with rememberPagerState(initialPage) and count parameter on Pager composables",
-    ReplaceWith("rememberPagerState(initialPage)"),
-    level = DeprecationLevel.ERROR,
-)
-@Suppress("UNUSED_PARAMETER", "NOTHING_TO_INLINE")
-@ExperimentalPagerApi
-@Composable
-inline fun rememberPagerState(
-    @IntRange(from = 0) pageCount: Int,
-    @IntRange(from = 0) initialPage: Int = 0,
-    @FloatRange(from = 0.0, to = 1.0) initialPageOffset: Float = 0f,
-    @IntRange(from = 1) initialOffscreenLimit: Int = 1,
-    infiniteLoop: Boolean = false
-): PagerState {
-    return rememberPagerState(initialPage = initialPage)
-}
-
-/**
- * Creates a [PagerState] that is remembered across compositions.
- *
- * Changes to the provided values for [initialPage] will **not** result in the state being recreated
- * or changed in any way if it has already been created.
- *
- * @param initialPage the initial value for [PagerState.currentPage]
- */
-@ExperimentalPagerApi
-@Composable
-fun rememberPagerState(
-    @IntRange(from = 0) initialPage: Int = 0,
-): PagerState =
-    rememberSaveable(saver = PagerState.Saver) {
-        PagerState(
-            currentPage = initialPage,
-        )
-    }
-
-/**
- * A state object that can be hoisted to control and observe scrolling for [HorizontalPager].
- *
- * In most cases, this will be created via [rememberPagerState].
- *
- * @param currentPage the initial value for [PagerState.currentPage]
- */
-@ExperimentalPagerApi
-@Stable
-class PagerState(
-    @IntRange(from = 0) currentPage: Int = 0,
-) : ScrollableState {
-    // Should this be public?
-    internal val lazyListState = LazyListState(firstVisibleItemIndex = currentPage)
-
-    private var _currentPage by mutableStateOf(currentPage)
-
-    private val currentLayoutPageInfo: LazyListItemInfo?
-        get() =
-            lazyListState.layoutInfo.visibleItemsInfo
-                .asSequence()
-                .filter { it.offset <= 0 && it.offset + it.size > 0 }
-                .lastOrNull()
-
-    private val currentLayoutPageOffset: Float
-        get() =
-            currentLayoutPageInfo?.let { current ->
-                // We coerce since itemSpacing can make the offset > 1f.
-                // We don't want to count spacing in the offset so cap it to 1f
-                (-current.offset / current.size.toFloat()).coerceIn(0f, 1f)
-            }
-                ?: 0f
-
-    /**
-     * [InteractionSource] that will be used to dispatch drag events when this list is being
-     * dragged. If you want to know whether the fling (or animated scroll) is in progress, use
-     * [isScrollInProgress].
-     */
-    val interactionSource: InteractionSource
-        get() = lazyListState.interactionSource
-
-    /** The number of pages to display. */
-    @get:IntRange(from = 0)
-    val pageCount: Int by derivedStateOf { lazyListState.layoutInfo.totalItemsCount }
-
-    /**
-     * The index of the currently selected page. This may not be the page which is currently
-     * displayed on screen.
-     *
-     * To update the scroll position, use [scrollToPage] or [animateScrollToPage].
-     */
-    @get:IntRange(from = 0)
-    var currentPage: Int
-        get() = _currentPage
-        internal set(value) {
-            if (value != _currentPage) {
-                _currentPage = value
-            }
-        }
-
-    /**
-     * The current offset from the start of [currentPage], as a ratio of the page width.
-     *
-     * To update the scroll position, use [scrollToPage] or [animateScrollToPage].
-     */
-    val currentPageOffset: Float by derivedStateOf {
-        currentLayoutPageInfo?.let {
-            // The current page offset is the current layout page delta from `currentPage`
-            // (which is only updated after a scroll/animation).
-            // We calculate this by looking at the current layout page + it's offset,
-            // then subtracting the 'current page'.
-            it.index + currentLayoutPageOffset - _currentPage
-        }
-            ?: 0f
-    }
-
-    /** The target page for any on-going animations. */
-    private var animationTargetPage: Int? by mutableStateOf(null)
-
-    internal var flingAnimationTarget: (() -> Int?)? by mutableStateOf(null)
-
-    /**
-     * The target page for any on-going animations or scrolls by the user. Returns the current page
-     * if a scroll or animation is not currently in progress.
-     */
-    val targetPage: Int
-        get() =
-            animationTargetPage
-                ?: flingAnimationTarget?.invoke()
-                    ?: when {
-                    // If a scroll isn't in progress, return the current page
-                    !isScrollInProgress -> currentPage
-                    // If the offset is 0f (or very close), return the current page
-                    currentPageOffset.absoluteValue < 0.001f -> currentPage
-                    // If we're offset towards the start, guess the previous page
-                    currentPageOffset < -0.5f -> (currentPage - 1).coerceAtLeast(0)
-                    // If we're offset towards the end, guess the next page
-                    else -> (currentPage + 1).coerceAtMost(pageCount - 1)
-                }
-
-    @Deprecated(
-        "Replaced with animateScrollToPage(page, pageOffset)",
-        ReplaceWith("animateScrollToPage(page = page, pageOffset = pageOffset)")
-    )
-    @Suppress("UNUSED_PARAMETER")
-    suspend fun animateScrollToPage(
-        @IntRange(from = 0) page: Int,
-        @FloatRange(from = 0.0, to = 1.0) pageOffset: Float = 0f,
-        animationSpec: AnimationSpec<Float> = spring(),
-        initialVelocity: Float = 0f,
-        skipPages: Boolean = true,
-    ) {
-        animateScrollToPage(page = page, pageOffset = pageOffset)
-    }
-
-    /**
-     * Animate (smooth scroll) to the given page to the middle of the viewport.
-     *
-     * Cancels the currently running scroll, if any, and suspends until the cancellation is
-     * complete.
-     *
-     * @param page the page to animate to. Must be between 0 and [pageCount] (inclusive).
-     * @param pageOffset the percentage of the page width to offset, from the start of [page]. Must
-     *   be in the range 0f..1f.
-     */
-    suspend fun animateScrollToPage(
-        @IntRange(from = 0) page: Int,
-        @FloatRange(from = 0.0, to = 1.0) pageOffset: Float = 0f,
-    ) {
-        requireCurrentPage(page, "page")
-        requireCurrentPageOffset(pageOffset, "pageOffset")
-        try {
-            animationTargetPage = page
-
-            if (pageOffset <= 0.005f) {
-                // If the offset is (close to) zero, just call animateScrollToItem and we're done
-                lazyListState.animateScrollToItem(index = page)
-            } else {
-                // Else we need to figure out what the offset is in pixels...
-
-                var target =
-                    lazyListState.layoutInfo.visibleItemsInfo.firstOrNull { it.index == page }
-
-                if (target != null) {
-                    // If we have access to the target page layout, we can calculate the pixel
-                    // offset from the size
-                    lazyListState.animateScrollToItem(
-                        index = page,
-                        scrollOffset = (target.size * pageOffset).roundToInt()
-                    )
-                } else {
-                    // If we don't, we use the current page size as a guide
-                    val currentSize = currentLayoutPageInfo!!.size
-                    lazyListState.animateScrollToItem(
-                        index = page,
-                        scrollOffset = (currentSize * pageOffset).roundToInt()
-                    )
-
-                    // The target should be visible now
-                    target = lazyListState.layoutInfo.visibleItemsInfo.first { it.index == page }
-
-                    if (target.size != currentSize) {
-                        // If the size we used for calculating the offset differs from the actual
-                        // target page size, we need to scroll again. This doesn't look great,
-                        // but there's not much else we can do.
-                        lazyListState.animateScrollToItem(
-                            index = page,
-                            scrollOffset = (target.size * pageOffset).roundToInt()
-                        )
-                    }
-                }
-            }
-        } finally {
-            // We need to manually call this, as the `animateScrollToItem` call above will happen
-            // in 1 frame, which is usually too fast for the LaunchedEffect in Pager to detect
-            // the change. This is especially true when running unit tests.
-            onScrollFinished()
-        }
-    }
-
-    /**
-     * Instantly brings the item at [page] to the middle of the viewport.
-     *
-     * Cancels the currently running scroll, if any, and suspends until the cancellation is
-     * complete.
-     *
-     * @param page the page to snap to. Must be between 0 and [pageCount] (inclusive).
-     */
-    suspend fun scrollToPage(
-        @IntRange(from = 0) page: Int,
-        @FloatRange(from = 0.0, to = 1.0) pageOffset: Float = 0f,
-    ) {
-        requireCurrentPage(page, "page")
-        requireCurrentPageOffset(pageOffset, "pageOffset")
-        try {
-            animationTargetPage = page
-
-            // First scroll to the given page. It will now be laid out at offset 0
-            lazyListState.scrollToItem(index = page)
-
-            // If we have a start spacing, we need to offset (scroll) by that too
-            if (pageOffset > 0.0001f) {
-                scroll { currentLayoutPageInfo?.let { scrollBy(it.size * pageOffset) } }
-            }
-        } finally {
-            // We need to manually call this, as the `scroll` call above will happen in 1 frame,
-            // which is usually too fast for the LaunchedEffect in Pager to detect the change.
-            // This is especially true when running unit tests.
-            onScrollFinished()
-        }
-    }
-
-    internal fun onScrollFinished() {
-        // Then update the current page to our layout page
-        currentPage = currentLayoutPageInfo?.index ?: 0
-        // Clear the animation target page
-        animationTargetPage = null
-    }
-
-    override suspend fun scroll(
-        scrollPriority: MutatePriority,
-        block: suspend ScrollScope.() -> Unit
-    ) = lazyListState.scroll(scrollPriority, block)
-
-    override fun dispatchRawDelta(delta: Float): Float {
-        return lazyListState.dispatchRawDelta(delta)
-    }
-
-    override val isScrollInProgress: Boolean
-        get() = lazyListState.isScrollInProgress
-
-    override fun toString(): String =
-        "PagerState(" +
-            "pageCount=$pageCount, " +
-            "currentPage=$currentPage, " +
-            "currentPageOffset=$currentPageOffset" +
-            ")"
-
-    private fun requireCurrentPage(value: Int, name: String) {
-        if (pageCount == 0) {
-            require(value == 0) { "$name must be 0 when pageCount is 0" }
-        } else {
-            require(value in 0 until pageCount) { "$name[$value] must be >= 0 and < pageCount" }
-        }
-    }
-
-    private fun requireCurrentPageOffset(value: Float, name: String) {
-        if (pageCount == 0) {
-            require(value == 0f) { "$name must be 0f when pageCount is 0" }
-        } else {
-            require(value in 0f..1f) { "$name must be >= 0 and <= 1" }
-        }
-    }
-
-    companion object {
-        /** The default [Saver] implementation for [PagerState]. */
-        val Saver: Saver<PagerState, *> =
-            listSaver(
-                save = {
-                    listOf<Any>(
-                        it.currentPage,
-                    )
-                },
-                restore = {
-                    PagerState(
-                        currentPage = it[0] as Int,
-                    )
-                }
-            )
-    }
-}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/pager/SnappingFlingBehavior.kt b/packages/SystemUI/compose/core/src/com/android/compose/pager/SnappingFlingBehavior.kt
deleted file mode 100644
index 9814029..0000000
--- a/packages/SystemUI/compose/core/src/com/android/compose/pager/SnappingFlingBehavior.kt
+++ /dev/null
@@ -1,270 +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.compose.pager
-
-import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.AnimationState
-import androidx.compose.animation.core.DecayAnimationSpec
-import androidx.compose.animation.core.animateDecay
-import androidx.compose.animation.core.animateTo
-import androidx.compose.animation.core.calculateTargetValue
-import androidx.compose.animation.core.spring
-import androidx.compose.animation.rememberSplineBasedDecay
-import androidx.compose.foundation.gestures.FlingBehavior
-import androidx.compose.foundation.gestures.ScrollScope
-import androidx.compose.foundation.lazy.LazyListItemInfo
-import androidx.compose.foundation.lazy.LazyListState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import kotlin.math.abs
-
-/** Default values used for [SnappingFlingBehavior] & [rememberSnappingFlingBehavior]. */
-internal object SnappingFlingBehaviorDefaults {
-    /** TODO */
-    val snapAnimationSpec: AnimationSpec<Float> = spring(stiffness = 600f)
-}
-
-/**
- * Create and remember a snapping [FlingBehavior] to be used with [LazyListState].
- *
- * @param lazyListState The [LazyListState] to update.
- * @param decayAnimationSpec The decay animation spec to use for decayed flings.
- * @param snapAnimationSpec The animation spec to use when snapping.
- *
- * TODO: move this to a new module and make it public
- */
-@Composable
-internal fun rememberSnappingFlingBehavior(
-    lazyListState: LazyListState,
-    decayAnimationSpec: DecayAnimationSpec<Float> = rememberSplineBasedDecay(),
-    snapAnimationSpec: AnimationSpec<Float> = SnappingFlingBehaviorDefaults.snapAnimationSpec,
-): SnappingFlingBehavior =
-    remember(lazyListState, decayAnimationSpec, snapAnimationSpec) {
-        SnappingFlingBehavior(
-            lazyListState = lazyListState,
-            decayAnimationSpec = decayAnimationSpec,
-            snapAnimationSpec = snapAnimationSpec,
-        )
-    }
-
-/**
- * A snapping [FlingBehavior] for [LazyListState]. Typically this would be created via
- * [rememberSnappingFlingBehavior].
- *
- * @param lazyListState The [LazyListState] to update.
- * @param decayAnimationSpec The decay animation spec to use for decayed flings.
- * @param snapAnimationSpec The animation spec to use when snapping.
- */
-internal class SnappingFlingBehavior(
-    private val lazyListState: LazyListState,
-    private val decayAnimationSpec: DecayAnimationSpec<Float>,
-    private val snapAnimationSpec: AnimationSpec<Float>,
-) : FlingBehavior {
-    /** The target item index for any on-going animations. */
-    var animationTarget: Int? by mutableStateOf(null)
-        private set
-
-    override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
-        val itemInfo = currentItemInfo ?: return initialVelocity
-
-        // If the decay fling can scroll past the current item, fling with decay
-        return if (decayAnimationSpec.canFlingPastCurrentItem(itemInfo, initialVelocity)) {
-            performDecayFling(initialVelocity, itemInfo)
-        } else {
-            // Otherwise we 'spring' to current/next item
-            performSpringFling(
-                index =
-                    when {
-                        // If the velocity is greater than 1 item per second (velocity is px/s),
-                        // spring
-                        // in the relevant direction
-                        initialVelocity > itemInfo.size -> {
-                            (itemInfo.index + 1).coerceAtMost(
-                                lazyListState.layoutInfo.totalItemsCount - 1
-                            )
-                        }
-                        initialVelocity < -itemInfo.size -> itemInfo.index
-                        // If the velocity is 0 (or less than the size of the item), spring to
-                        // whichever item is closest to the snap point
-                        itemInfo.offset < -itemInfo.size / 2 -> itemInfo.index + 1
-                        else -> itemInfo.index
-                    },
-                initialVelocity = initialVelocity,
-            )
-        }
-    }
-
-    private suspend fun ScrollScope.performDecayFling(
-        initialVelocity: Float,
-        startItem: LazyListItemInfo,
-    ): Float {
-        val index =
-            when {
-                initialVelocity > 0 -> startItem.index + 1
-                else -> startItem.index
-            }
-        val forward = index > (currentItemInfo?.index ?: return initialVelocity)
-
-        // Update the animationTarget
-        animationTarget = index
-
-        var velocityLeft = initialVelocity
-        var lastValue = 0f
-        AnimationState(
-                initialValue = 0f,
-                initialVelocity = initialVelocity,
-            )
-            .animateDecay(decayAnimationSpec) {
-                val delta = value - lastValue
-                val consumed = scrollBy(delta)
-                lastValue = value
-                velocityLeft = this.velocity
-
-                val current = currentItemInfo
-                if (current == null) {
-                    cancelAnimation()
-                    return@animateDecay
-                }
-
-                if (
-                    !forward &&
-                        (current.index < index || current.index == index && current.offset >= 0)
-                ) {
-                    // 'snap back' to the item as we may have scrolled past it
-                    scrollBy(lazyListState.calculateScrollOffsetToItem(index).toFloat())
-                    cancelAnimation()
-                } else if (
-                    forward &&
-                        (current.index > index || current.index == index && current.offset <= 0)
-                ) {
-                    // 'snap back' to the item as we may have scrolled past it
-                    scrollBy(lazyListState.calculateScrollOffsetToItem(index).toFloat())
-                    cancelAnimation()
-                } else if (abs(delta - consumed) > 0.5f) {
-                    // avoid rounding errors and stop if anything is unconsumed
-                    cancelAnimation()
-                }
-            }
-        animationTarget = null
-        return velocityLeft
-    }
-
-    private suspend fun ScrollScope.performSpringFling(
-        index: Int,
-        scrollOffset: Int = 0,
-        initialVelocity: Float = 0f,
-    ): Float {
-        // If we don't have a current layout, we can't snap
-        val initialItem = currentItemInfo ?: return initialVelocity
-
-        val forward = index > initialItem.index
-        // We add 10% on to the size of the current item, to compensate for any item spacing, etc
-        val target = (if (forward) initialItem.size else -initialItem.size) * 1.1f
-
-        // Update the animationTarget
-        animationTarget = index
-
-        var velocityLeft = initialVelocity
-        var lastValue = 0f
-        AnimationState(
-                initialValue = 0f,
-                initialVelocity = initialVelocity,
-            )
-            .animateTo(
-                targetValue = target,
-                animationSpec = snapAnimationSpec,
-            ) {
-                // Springs can overshoot their target, clamp to the desired range
-                val coercedValue =
-                    if (forward) {
-                        value.coerceAtMost(target)
-                    } else {
-                        value.coerceAtLeast(target)
-                    }
-                val delta = coercedValue - lastValue
-                val consumed = scrollBy(delta)
-                lastValue = coercedValue
-                velocityLeft = this.velocity
-
-                val current = currentItemInfo
-                if (current == null) {
-                    cancelAnimation()
-                    return@animateTo
-                }
-
-                if (scrolledPastItem(initialVelocity, current, index, scrollOffset)) {
-                    // If we've scrolled to/past the item, stop the animation. We may also need to
-                    // 'snap back' to the item as we may have scrolled past it
-                    scrollBy(lazyListState.calculateScrollOffsetToItem(index).toFloat())
-                    cancelAnimation()
-                } else if (abs(delta - consumed) > 0.5f) {
-                    // avoid rounding errors and stop if anything is unconsumed
-                    cancelAnimation()
-                }
-            }
-        animationTarget = null
-        return velocityLeft
-    }
-
-    private fun LazyListState.calculateScrollOffsetToItem(index: Int): Int {
-        return layoutInfo.visibleItemsInfo.firstOrNull { it.index == index }?.offset ?: 0
-    }
-
-    private val currentItemInfo: LazyListItemInfo?
-        get() =
-            lazyListState.layoutInfo.visibleItemsInfo
-                .asSequence()
-                .filter { it.offset <= 0 && it.offset + it.size > 0 }
-                .lastOrNull()
-}
-
-private fun scrolledPastItem(
-    initialVelocity: Float,
-    currentItem: LazyListItemInfo,
-    targetIndex: Int,
-    targetScrollOffset: Int = 0,
-): Boolean {
-    return if (initialVelocity > 0) {
-        // forward
-        currentItem.index > targetIndex ||
-            (currentItem.index == targetIndex && currentItem.offset <= targetScrollOffset)
-    } else {
-        // backwards
-        currentItem.index < targetIndex ||
-            (currentItem.index == targetIndex && currentItem.offset >= targetScrollOffset)
-    }
-}
-
-private fun DecayAnimationSpec<Float>.canFlingPastCurrentItem(
-    currentItem: LazyListItemInfo,
-    initialVelocity: Float,
-): Boolean {
-    val targetValue =
-        calculateTargetValue(
-            initialValue = currentItem.offset.toFloat(),
-            initialVelocity = initialVelocity,
-        )
-    return when {
-        // forward. We add 10% onto the size to cater for any item spacing
-        initialVelocity > 0 -> targetValue <= -(currentItem.size * 1.1f)
-        // backwards. We add 10% onto the size to cater for any item spacing
-        else -> targetValue >= (currentItem.size * 0.1f)
-    }
-}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt b/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt
deleted file mode 100644
index 946e779..0000000
--- a/packages/SystemUI/compose/core/src/com/android/compose/swipeable/Swipeable.kt
+++ /dev/null
@@ -1,849 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.compose.swipeable
-
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.SpringSpec
-import androidx.compose.foundation.gestures.DraggableState
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.draggable
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.Saver
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import androidx.compose.ui.input.nestedscroll.NestedScrollSource
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.Velocity
-import androidx.compose.ui.unit.dp
-import com.android.compose.swipeable.SwipeableDefaults.AnimationSpec
-import com.android.compose.swipeable.SwipeableDefaults.StandardResistanceFactor
-import com.android.compose.swipeable.SwipeableDefaults.VelocityThreshold
-import com.android.compose.swipeable.SwipeableDefaults.resistanceConfig
-import com.android.compose.ui.util.lerp
-import kotlin.math.PI
-import kotlin.math.abs
-import kotlin.math.sign
-import kotlin.math.sin
-import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.take
-import kotlinx.coroutines.launch
-
-/**
- * State of the [swipeable] modifier.
- *
- * This contains necessary information about any ongoing swipe or animation and provides methods to
- * change the state either immediately or by starting an animation. To create and remember a
- * [SwipeableState] with the default animation clock, use [rememberSwipeableState].
- *
- * @param initialValue The initial value of the state.
- * @param animationSpec The default animation that will be used to animate to a new state.
- * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
- *
- * TODO(b/272311106): this is a fork from material. Unfork it when Swipeable.kt reaches material3.
- */
-@Stable
-open class SwipeableState<T>(
-    initialValue: T,
-    internal val animationSpec: AnimationSpec<Float> = AnimationSpec,
-    internal val confirmStateChange: (newValue: T) -> Boolean = { true }
-) {
-    /**
-     * The current value of the state.
-     *
-     * If no swipe or animation is in progress, this corresponds to the anchor at which the
-     * [swipeable] is currently settled. If a swipe or animation is in progress, this corresponds
-     * the last anchor at which the [swipeable] was settled before the swipe or animation started.
-     */
-    var currentValue: T by mutableStateOf(initialValue)
-        private set
-
-    /** Whether the state is currently animating. */
-    var isAnimationRunning: Boolean by mutableStateOf(false)
-        private set
-
-    /**
-     * The current position (in pixels) of the [swipeable].
-     *
-     * You should use this state to offset your content accordingly. The recommended way is to use
-     * `Modifier.offsetPx`. This includes the resistance by default, if resistance is enabled.
-     */
-    val offset: State<Float>
-        get() = offsetState
-
-    /** The amount by which the [swipeable] has been swiped past its bounds. */
-    val overflow: State<Float>
-        get() = overflowState
-
-    // Use `Float.NaN` as a placeholder while the state is uninitialised.
-    private val offsetState = mutableStateOf(0f)
-    private val overflowState = mutableStateOf(0f)
-
-    // the source of truth for the "real"(non ui) position
-    // basically position in bounds + overflow
-    private val absoluteOffset = mutableStateOf(0f)
-
-    // current animation target, if animating, otherwise null
-    private val animationTarget = mutableStateOf<Float?>(null)
-
-    internal var anchors by mutableStateOf(emptyMap<Float, T>())
-
-    private val latestNonEmptyAnchorsFlow: Flow<Map<Float, T>> =
-        snapshotFlow { anchors }.filter { it.isNotEmpty() }.take(1)
-
-    internal var minBound = Float.NEGATIVE_INFINITY
-    internal var maxBound = Float.POSITIVE_INFINITY
-
-    internal fun ensureInit(newAnchors: Map<Float, T>) {
-        if (anchors.isEmpty()) {
-            // need to do initial synchronization synchronously :(
-            val initialOffset = newAnchors.getOffset(currentValue)
-            requireNotNull(initialOffset) { "The initial value must have an associated anchor." }
-            offsetState.value = initialOffset
-            absoluteOffset.value = initialOffset
-        }
-    }
-
-    internal suspend fun processNewAnchors(oldAnchors: Map<Float, T>, newAnchors: Map<Float, T>) {
-        if (oldAnchors.isEmpty()) {
-            // If this is the first time that we receive anchors, then we need to initialise
-            // the state so we snap to the offset associated to the initial value.
-            minBound = newAnchors.keys.minOrNull()!!
-            maxBound = newAnchors.keys.maxOrNull()!!
-            val initialOffset = newAnchors.getOffset(currentValue)
-            requireNotNull(initialOffset) { "The initial value must have an associated anchor." }
-            snapInternalToOffset(initialOffset)
-        } else if (newAnchors != oldAnchors) {
-            // If we have received new anchors, then the offset of the current value might
-            // have changed, so we need to animate to the new offset. If the current value
-            // has been removed from the anchors then we animate to the closest anchor
-            // instead. Note that this stops any ongoing animation.
-            minBound = Float.NEGATIVE_INFINITY
-            maxBound = Float.POSITIVE_INFINITY
-            val animationTargetValue = animationTarget.value
-            // if we're in the animation already, let's find it a new home
-            val targetOffset =
-                if (animationTargetValue != null) {
-                    // first, try to map old state to the new state
-                    val oldState = oldAnchors[animationTargetValue]
-                    val newState = newAnchors.getOffset(oldState)
-                    // return new state if exists, or find the closes one among new anchors
-                    newState ?: newAnchors.keys.minByOrNull { abs(it - animationTargetValue) }!!
-                } else {
-                    // we're not animating, proceed by finding the new anchors for an old value
-                    val actualOldValue = oldAnchors[offset.value]
-                    val value = if (actualOldValue == currentValue) currentValue else actualOldValue
-                    newAnchors.getOffset(value)
-                        ?: newAnchors.keys.minByOrNull { abs(it - offset.value) }!!
-                }
-            try {
-                animateInternalToOffset(targetOffset, animationSpec)
-            } catch (c: CancellationException) {
-                // If the animation was interrupted for any reason, snap as a last resort.
-                snapInternalToOffset(targetOffset)
-            } finally {
-                currentValue = newAnchors.getValue(targetOffset)
-                minBound = newAnchors.keys.minOrNull()!!
-                maxBound = newAnchors.keys.maxOrNull()!!
-            }
-        }
-    }
-
-    internal var thresholds: (Float, Float) -> Float by mutableStateOf({ _, _ -> 0f })
-
-    internal var velocityThreshold by mutableStateOf(0f)
-
-    internal var resistance: ResistanceConfig? by mutableStateOf(null)
-
-    internal val draggableState = DraggableState {
-        val newAbsolute = absoluteOffset.value + it
-        val clamped = newAbsolute.coerceIn(minBound, maxBound)
-        val overflow = newAbsolute - clamped
-        val resistanceDelta = resistance?.computeResistance(overflow) ?: 0f
-        offsetState.value = clamped + resistanceDelta
-        overflowState.value = overflow
-        absoluteOffset.value = newAbsolute
-    }
-
-    private suspend fun snapInternalToOffset(target: Float) {
-        draggableState.drag { dragBy(target - absoluteOffset.value) }
-    }
-
-    private suspend fun animateInternalToOffset(target: Float, spec: AnimationSpec<Float>) {
-        draggableState.drag {
-            var prevValue = absoluteOffset.value
-            animationTarget.value = target
-            isAnimationRunning = true
-            try {
-                Animatable(prevValue).animateTo(target, spec) {
-                    dragBy(this.value - prevValue)
-                    prevValue = this.value
-                }
-            } finally {
-                animationTarget.value = null
-                isAnimationRunning = false
-            }
-        }
-    }
-
-    /**
-     * The target value of the state.
-     *
-     * If a swipe is in progress, this is the value that the [swipeable] would animate to if the
-     * swipe finished. If an animation is running, this is the target value of that animation.
-     * Finally, if no swipe or animation is in progress, this is the same as the [currentValue].
-     */
-    val targetValue: T
-        get() {
-            // TODO(calintat): Track current velocity (b/149549482) and use that here.
-            val target =
-                animationTarget.value
-                    ?: computeTarget(
-                        offset = offset.value,
-                        lastValue = anchors.getOffset(currentValue) ?: offset.value,
-                        anchors = anchors.keys,
-                        thresholds = thresholds,
-                        velocity = 0f,
-                        velocityThreshold = Float.POSITIVE_INFINITY
-                    )
-            return anchors[target] ?: currentValue
-        }
-
-    /**
-     * Information about the ongoing swipe or animation, if any. See [SwipeProgress] for details.
-     *
-     * If no swipe or animation is in progress, this returns `SwipeProgress(value, value, 1f)`.
-     */
-    val progress: SwipeProgress<T>
-        get() {
-            val bounds = findBounds(offset.value, anchors.keys)
-            val from: T
-            val to: T
-            val fraction: Float
-            when (bounds.size) {
-                0 -> {
-                    from = currentValue
-                    to = currentValue
-                    fraction = 1f
-                }
-                1 -> {
-                    from = anchors.getValue(bounds[0])
-                    to = anchors.getValue(bounds[0])
-                    fraction = 1f
-                }
-                else -> {
-                    val (a, b) =
-                        if (direction > 0f) {
-                            bounds[0] to bounds[1]
-                        } else {
-                            bounds[1] to bounds[0]
-                        }
-                    from = anchors.getValue(a)
-                    to = anchors.getValue(b)
-                    fraction = (offset.value - a) / (b - a)
-                }
-            }
-            return SwipeProgress(from, to, fraction)
-        }
-
-    /**
-     * The direction in which the [swipeable] is moving, relative to the current [currentValue].
-     *
-     * This will be either 1f if it is is moving from left to right or top to bottom, -1f if it is
-     * moving from right to left or bottom to top, or 0f if no swipe or animation is in progress.
-     */
-    val direction: Float
-        get() = anchors.getOffset(currentValue)?.let { sign(offset.value - it) } ?: 0f
-
-    /**
-     * Set the state without any animation and suspend until it's set
-     *
-     * @param targetValue The new target value to set [currentValue] to.
-     */
-    suspend fun snapTo(targetValue: T) {
-        latestNonEmptyAnchorsFlow.collect { anchors ->
-            val targetOffset = anchors.getOffset(targetValue)
-            requireNotNull(targetOffset) { "The target value must have an associated anchor." }
-            snapInternalToOffset(targetOffset)
-            currentValue = targetValue
-        }
-    }
-
-    /**
-     * Set the state to the target value by starting an animation.
-     *
-     * @param targetValue The new value to animate to.
-     * @param anim The animation that will be used to animate to the new value.
-     */
-    suspend fun animateTo(targetValue: T, anim: AnimationSpec<Float> = animationSpec) {
-        latestNonEmptyAnchorsFlow.collect { anchors ->
-            try {
-                val targetOffset = anchors.getOffset(targetValue)
-                requireNotNull(targetOffset) { "The target value must have an associated anchor." }
-                animateInternalToOffset(targetOffset, anim)
-            } finally {
-                val endOffset = absoluteOffset.value
-                val endValue =
-                    anchors
-                        // fighting rounding error once again, anchor should be as close as 0.5
-                        // pixels
-                        .filterKeys { anchorOffset -> abs(anchorOffset - endOffset) < 0.5f }
-                        .values
-                        .firstOrNull()
-                        ?: currentValue
-                currentValue = endValue
-            }
-        }
-    }
-
-    /**
-     * Perform fling with settling to one of the anchors which is determined by the given
-     * [velocity]. Fling with settling [swipeable] will always consume all the velocity provided
-     * since it will settle at the anchor.
-     *
-     * In general cases, [swipeable] flings by itself when being swiped. This method is to be used
-     * for nested scroll logic that wraps the [swipeable]. In nested scroll developer may want to
-     * trigger settling fling when the child scroll container reaches the bound.
-     *
-     * @param velocity velocity to fling and settle with
-     * @return the reason fling ended
-     */
-    suspend fun performFling(velocity: Float) {
-        latestNonEmptyAnchorsFlow.collect { anchors ->
-            val lastAnchor = anchors.getOffset(currentValue)!!
-            val targetValue =
-                computeTarget(
-                    offset = offset.value,
-                    lastValue = lastAnchor,
-                    anchors = anchors.keys,
-                    thresholds = thresholds,
-                    velocity = velocity,
-                    velocityThreshold = velocityThreshold
-                )
-            val targetState = anchors[targetValue]
-            if (targetState != null && confirmStateChange(targetState)) animateTo(targetState)
-            // If the user vetoed the state change, rollback to the previous state.
-            else animateInternalToOffset(lastAnchor, animationSpec)
-        }
-    }
-
-    /**
-     * Force [swipeable] to consume drag delta provided from outside of the regular [swipeable]
-     * gesture flow.
-     *
-     * Note: This method performs generic drag and it won't settle to any particular anchor, *
-     * leaving swipeable in between anchors. When done dragging, [performFling] must be called as
-     * well to ensure swipeable will settle at the anchor.
-     *
-     * In general cases, [swipeable] drags by itself when being swiped. This method is to be used
-     * for nested scroll logic that wraps the [swipeable]. In nested scroll developer may want to
-     * force drag when the child scroll container reaches the bound.
-     *
-     * @param delta delta in pixels to drag by
-     * @return the amount of [delta] consumed
-     */
-    fun performDrag(delta: Float): Float {
-        val potentiallyConsumed = absoluteOffset.value + delta
-        val clamped = potentiallyConsumed.coerceIn(minBound, maxBound)
-        val deltaToConsume = clamped - absoluteOffset.value
-        if (abs(deltaToConsume) > 0) {
-            draggableState.dispatchRawDelta(deltaToConsume)
-        }
-        return deltaToConsume
-    }
-
-    companion object {
-        /** The default [Saver] implementation for [SwipeableState]. */
-        fun <T : Any> Saver(
-            animationSpec: AnimationSpec<Float>,
-            confirmStateChange: (T) -> Boolean
-        ) =
-            Saver<SwipeableState<T>, T>(
-                save = { it.currentValue },
-                restore = { SwipeableState(it, animationSpec, confirmStateChange) }
-            )
-    }
-}
-
-/**
- * Collects information about the ongoing swipe or animation in [swipeable].
- *
- * To access this information, use [SwipeableState.progress].
- *
- * @param from The state corresponding to the anchor we are moving away from.
- * @param to The state corresponding to the anchor we are moving towards.
- * @param fraction The fraction that the current position represents between [from] and [to]. Must
- *   be between `0` and `1`.
- */
-@Immutable
-class SwipeProgress<T>(
-    val from: T,
-    val to: T,
-    /*@FloatRange(from = 0.0, to = 1.0)*/
-    val fraction: Float
-) {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is SwipeProgress<*>) return false
-
-        if (from != other.from) return false
-        if (to != other.to) return false
-        if (fraction != other.fraction) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = from?.hashCode() ?: 0
-        result = 31 * result + (to?.hashCode() ?: 0)
-        result = 31 * result + fraction.hashCode()
-        return result
-    }
-
-    override fun toString(): String {
-        return "SwipeProgress(from=$from, to=$to, fraction=$fraction)"
-    }
-}
-
-/**
- * Create and [remember] a [SwipeableState] with the default animation clock.
- *
- * @param initialValue The initial value of the state.
- * @param animationSpec The default animation that will be used to animate to a new state.
- * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
- */
-@Composable
-fun <T : Any> rememberSwipeableState(
-    initialValue: T,
-    animationSpec: AnimationSpec<Float> = AnimationSpec,
-    confirmStateChange: (newValue: T) -> Boolean = { true }
-): SwipeableState<T> {
-    return rememberSaveable(
-        saver =
-            SwipeableState.Saver(
-                animationSpec = animationSpec,
-                confirmStateChange = confirmStateChange
-            )
-    ) {
-        SwipeableState(
-            initialValue = initialValue,
-            animationSpec = animationSpec,
-            confirmStateChange = confirmStateChange
-        )
-    }
-}
-
-/**
- * Create and [remember] a [SwipeableState] which is kept in sync with another state, i.e.:
- * 1. Whenever the [value] changes, the [SwipeableState] will be animated to that new value.
- * 2. Whenever the value of the [SwipeableState] changes (e.g. after a swipe), the owner of the
- *    [value] will be notified to update their state to the new value of the [SwipeableState] by
- *    invoking [onValueChange]. If the owner does not update their state to the provided value for
- *    some reason, then the [SwipeableState] will perform a rollback to the previous, correct value.
- */
-@Composable
-internal fun <T : Any> rememberSwipeableStateFor(
-    value: T,
-    onValueChange: (T) -> Unit,
-    animationSpec: AnimationSpec<Float> = AnimationSpec
-): SwipeableState<T> {
-    val swipeableState = remember {
-        SwipeableState(
-            initialValue = value,
-            animationSpec = animationSpec,
-            confirmStateChange = { true }
-        )
-    }
-    val forceAnimationCheck = remember { mutableStateOf(false) }
-    LaunchedEffect(value, forceAnimationCheck.value) {
-        if (value != swipeableState.currentValue) {
-            swipeableState.animateTo(value)
-        }
-    }
-    DisposableEffect(swipeableState.currentValue) {
-        if (value != swipeableState.currentValue) {
-            onValueChange(swipeableState.currentValue)
-            forceAnimationCheck.value = !forceAnimationCheck.value
-        }
-        onDispose {}
-    }
-    return swipeableState
-}
-
-/**
- * Enable swipe gestures between a set of predefined states.
- *
- * To use this, you must provide a map of anchors (in pixels) to states (of type [T]). Note that
- * this map cannot be empty and cannot have two anchors mapped to the same state.
- *
- * When a swipe is detected, the offset of the [SwipeableState] will be updated with the swipe
- * delta. You should use this offset to move your content accordingly (see `Modifier.offsetPx`).
- * When the swipe ends, the offset will be animated to one of the anchors and when that anchor is
- * reached, the value of the [SwipeableState] will also be updated to the state corresponding to the
- * new anchor. The target anchor is calculated based on the provided positional [thresholds].
- *
- * Swiping is constrained between the minimum and maximum anchors. If the user attempts to swipe
- * past these bounds, a resistance effect will be applied by default. The amount of resistance at
- * each edge is specified by the [resistance] config. To disable all resistance, set it to `null`.
- *
- * For an example of a [swipeable] with three states, see:
- *
- * @param T The type of the state.
- * @param state The state of the [swipeable].
- * @param anchors Pairs of anchors and states, used to map anchors to states and vice versa.
- * @param thresholds Specifies where the thresholds between the states are. The thresholds will be
- *   used to determine which state to animate to when swiping stops. This is represented as a lambda
- *   that takes two states and returns the threshold between them in the form of a
- *   [ThresholdConfig]. Note that the order of the states corresponds to the swipe direction.
- * @param orientation The orientation in which the [swipeable] can be swiped.
- * @param enabled Whether this [swipeable] is enabled and should react to the user's input.
- * @param reverseDirection Whether to reverse the direction of the swipe, so a top to bottom swipe
- *   will behave like bottom to top, and a left to right swipe will behave like right to left.
- * @param interactionSource Optional [MutableInteractionSource] that will passed on to the internal
- *   [Modifier.draggable].
- * @param resistance Controls how much resistance will be applied when swiping past the bounds.
- * @param velocityThreshold The threshold (in dp per second) that the end velocity has to exceed in
- *   order to animate to the next state, even if the positional [thresholds] have not been reached.
- * @sample androidx.compose.material.samples.SwipeableSample
- */
-fun <T> Modifier.swipeable(
-    state: SwipeableState<T>,
-    anchors: Map<Float, T>,
-    orientation: Orientation,
-    enabled: Boolean = true,
-    reverseDirection: Boolean = false,
-    interactionSource: MutableInteractionSource? = null,
-    thresholds: (from: T, to: T) -> ThresholdConfig = { _, _ -> FixedThreshold(56.dp) },
-    resistance: ResistanceConfig? = resistanceConfig(anchors.keys),
-    velocityThreshold: Dp = VelocityThreshold
-) =
-    composed(
-        inspectorInfo =
-            debugInspectorInfo {
-                name = "swipeable"
-                properties["state"] = state
-                properties["anchors"] = anchors
-                properties["orientation"] = orientation
-                properties["enabled"] = enabled
-                properties["reverseDirection"] = reverseDirection
-                properties["interactionSource"] = interactionSource
-                properties["thresholds"] = thresholds
-                properties["resistance"] = resistance
-                properties["velocityThreshold"] = velocityThreshold
-            }
-    ) {
-        require(anchors.isNotEmpty()) { "You must have at least one anchor." }
-        require(anchors.values.distinct().count() == anchors.size) {
-            "You cannot have two anchors mapped to the same state."
-        }
-        val density = LocalDensity.current
-        state.ensureInit(anchors)
-        LaunchedEffect(anchors, state) {
-            val oldAnchors = state.anchors
-            state.anchors = anchors
-            state.resistance = resistance
-            state.thresholds = { a, b ->
-                val from = anchors.getValue(a)
-                val to = anchors.getValue(b)
-                with(thresholds(from, to)) { density.computeThreshold(a, b) }
-            }
-            with(density) { state.velocityThreshold = velocityThreshold.toPx() }
-            state.processNewAnchors(oldAnchors, anchors)
-        }
-
-        Modifier.draggable(
-            orientation = orientation,
-            enabled = enabled,
-            reverseDirection = reverseDirection,
-            interactionSource = interactionSource,
-            startDragImmediately = state.isAnimationRunning,
-            onDragStopped = { velocity -> launch { state.performFling(velocity) } },
-            state = state.draggableState
-        )
-    }
-
-/**
- * Interface to compute a threshold between two anchors/states in a [swipeable].
- *
- * To define a [ThresholdConfig], consider using [FixedThreshold] and [FractionalThreshold].
- */
-@Stable
-interface ThresholdConfig {
-    /** Compute the value of the threshold (in pixels), once the values of the anchors are known. */
-    fun Density.computeThreshold(fromValue: Float, toValue: Float): Float
-}
-
-/**
- * A fixed threshold will be at an [offset] away from the first anchor.
- *
- * @param offset The offset (in dp) that the threshold will be at.
- */
-@Immutable
-data class FixedThreshold(private val offset: Dp) : ThresholdConfig {
-    override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
-        return fromValue + offset.toPx() * sign(toValue - fromValue)
-    }
-}
-
-/**
- * A fractional threshold will be at a [fraction] of the way between the two anchors.
- *
- * @param fraction The fraction (between 0 and 1) that the threshold will be at.
- */
-@Immutable
-data class FractionalThreshold(
-    /*@FloatRange(from = 0.0, to = 1.0)*/
-    private val fraction: Float
-) : ThresholdConfig {
-    override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
-        return lerp(fromValue, toValue, fraction)
-    }
-}
-
-/**
- * Specifies how resistance is calculated in [swipeable].
- *
- * There are two things needed to calculate resistance: the resistance basis determines how much
- * overflow will be consumed to achieve maximum resistance, and the resistance factor determines the
- * amount of resistance (the larger the resistance factor, the stronger the resistance).
- *
- * The resistance basis is usually either the size of the component which [swipeable] is applied to,
- * or the distance between the minimum and maximum anchors. For a constructor in which the
- * resistance basis defaults to the latter, consider using [resistanceConfig].
- *
- * You may specify different resistance factors for each bound. Consider using one of the default
- * resistance factors in [SwipeableDefaults]: `StandardResistanceFactor` to convey that the user has
- * run out of things to see, and `StiffResistanceFactor` to convey that the user cannot swipe this
- * right now. Also, you can set either factor to 0 to disable resistance at that bound.
- *
- * @param basis Specifies the maximum amount of overflow that will be consumed. Must be positive.
- * @param factorAtMin The factor by which to scale the resistance at the minimum bound. Must not be
- *   negative.
- * @param factorAtMax The factor by which to scale the resistance at the maximum bound. Must not be
- *   negative.
- */
-@Immutable
-class ResistanceConfig(
-    /*@FloatRange(from = 0.0, fromInclusive = false)*/
-    val basis: Float,
-    /*@FloatRange(from = 0.0)*/
-    val factorAtMin: Float = StandardResistanceFactor,
-    /*@FloatRange(from = 0.0)*/
-    val factorAtMax: Float = StandardResistanceFactor
-) {
-    fun computeResistance(overflow: Float): Float {
-        val factor = if (overflow < 0) factorAtMin else factorAtMax
-        if (factor == 0f) return 0f
-        val progress = (overflow / basis).coerceIn(-1f, 1f)
-        return basis / factor * sin(progress * PI.toFloat() / 2)
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is ResistanceConfig) return false
-
-        if (basis != other.basis) return false
-        if (factorAtMin != other.factorAtMin) return false
-        if (factorAtMax != other.factorAtMax) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = basis.hashCode()
-        result = 31 * result + factorAtMin.hashCode()
-        result = 31 * result + factorAtMax.hashCode()
-        return result
-    }
-
-    override fun toString(): String {
-        return "ResistanceConfig(basis=$basis, factorAtMin=$factorAtMin, factorAtMax=$factorAtMax)"
-    }
-}
-
-/**
- * Given an offset x and a set of anchors, return a list of anchors:
- * 1. [ ] if the set of anchors is empty,
- * 2. [ x' ] if x is equal to one of the anchors, accounting for a small rounding error, where x' is
- *    x rounded to the exact value of the matching anchor,
- * 3. [ min ] if min is the minimum anchor and x < min,
- * 4. [ max ] if max is the maximum anchor and x > max, or
- * 5. [ a , b ] if a and b are anchors such that a < x < b and b - a is minimal.
- */
-private fun findBounds(offset: Float, anchors: Set<Float>): List<Float> {
-    // Find the anchors the target lies between with a little bit of rounding error.
-    val a = anchors.filter { it <= offset + 0.001 }.maxOrNull()
-    val b = anchors.filter { it >= offset - 0.001 }.minOrNull()
-
-    return when {
-        a == null ->
-            // case 1 or 3
-            listOfNotNull(b)
-        b == null ->
-            // case 4
-            listOf(a)
-        a == b ->
-            // case 2
-            // Can't return offset itself here since it might not be exactly equal
-            // to the anchor, despite being considered an exact match.
-            listOf(a)
-        else ->
-            // case 5
-            listOf(a, b)
-    }
-}
-
-private fun computeTarget(
-    offset: Float,
-    lastValue: Float,
-    anchors: Set<Float>,
-    thresholds: (Float, Float) -> Float,
-    velocity: Float,
-    velocityThreshold: Float
-): Float {
-    val bounds = findBounds(offset, anchors)
-    return when (bounds.size) {
-        0 -> lastValue
-        1 -> bounds[0]
-        else -> {
-            val lower = bounds[0]
-            val upper = bounds[1]
-            if (lastValue <= offset) {
-                // Swiping from lower to upper (positive).
-                if (velocity >= velocityThreshold) {
-                    return upper
-                } else {
-                    val threshold = thresholds(lower, upper)
-                    if (offset < threshold) lower else upper
-                }
-            } else {
-                // Swiping from upper to lower (negative).
-                if (velocity <= -velocityThreshold) {
-                    return lower
-                } else {
-                    val threshold = thresholds(upper, lower)
-                    if (offset > threshold) upper else lower
-                }
-            }
-        }
-    }
-}
-
-private fun <T> Map<Float, T>.getOffset(state: T): Float? {
-    return entries.firstOrNull { it.value == state }?.key
-}
-
-/** Contains useful defaults for [swipeable] and [SwipeableState]. */
-object SwipeableDefaults {
-    /** The default animation used by [SwipeableState]. */
-    val AnimationSpec = SpringSpec<Float>()
-
-    /** The default velocity threshold (1.8 dp per millisecond) used by [swipeable]. */
-    val VelocityThreshold = 125.dp
-
-    /** A stiff resistance factor which indicates that swiping isn't available right now. */
-    const val StiffResistanceFactor = 20f
-
-    /** A standard resistance factor which indicates that the user has run out of things to see. */
-    const val StandardResistanceFactor = 10f
-
-    /**
-     * The default resistance config used by [swipeable].
-     *
-     * This returns `null` if there is one anchor. If there are at least two anchors, it returns a
-     * [ResistanceConfig] with the resistance basis equal to the distance between the two bounds.
-     */
-    fun resistanceConfig(
-        anchors: Set<Float>,
-        factorAtMin: Float = StandardResistanceFactor,
-        factorAtMax: Float = StandardResistanceFactor
-    ): ResistanceConfig? {
-        return if (anchors.size <= 1) {
-            null
-        } else {
-            val basis = anchors.maxOrNull()!! - anchors.minOrNull()!!
-            ResistanceConfig(basis, factorAtMin, factorAtMax)
-        }
-    }
-}
-
-// temp default nested scroll connection for swipeables which desire as an opt in
-// revisit in b/174756744 as all types will have their own specific connection probably
-internal val <T> SwipeableState<T>.PreUpPostDownNestedScrollConnection: NestedScrollConnection
-    get() =
-        object : NestedScrollConnection {
-            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
-                val delta = available.toFloat()
-                return if (delta < 0 && source == NestedScrollSource.Drag) {
-                    performDrag(delta).toOffset()
-                } else {
-                    Offset.Zero
-                }
-            }
-
-            override fun onPostScroll(
-                consumed: Offset,
-                available: Offset,
-                source: NestedScrollSource
-            ): Offset {
-                return if (source == NestedScrollSource.Drag) {
-                    performDrag(available.toFloat()).toOffset()
-                } else {
-                    Offset.Zero
-                }
-            }
-
-            override suspend fun onPreFling(available: Velocity): Velocity {
-                val toFling = Offset(available.x, available.y).toFloat()
-                return if (toFling < 0 && offset.value > minBound) {
-                    performFling(velocity = toFling)
-                    // since we go to the anchor with tween settling, consume all for the best UX
-                    available
-                } else {
-                    Velocity.Zero
-                }
-            }
-
-            override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
-                performFling(velocity = Offset(available.x, available.y).toFloat())
-                return available
-            }
-
-            private fun Float.toOffset(): Offset = Offset(0f, this)
-
-            private fun Offset.toFloat(): Float = this.y
-        }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt
new file mode 100644
index 0000000..741f00d
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.ui.util
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+/**
+ * Iterates through a [List] using the index and calls [action] for each item. This does not
+ * allocate an iterator like [Iterable.forEach].
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
+ */
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T> List<T>.fastForEach(action: (T) -> Unit) {
+    contract { callsInPlace(action) }
+    for (index in indices) {
+        val item = get(index)
+        action(item)
+    }
+}
+
+/**
+ * Returns a list containing the results of applying the given [transform] function to each element
+ * in the original collection.
+ *
+ * **Do not use for collections that come from public APIs**, since they may not support random
+ * access in an efficient way, and this method may actually be a lot slower. Only use for
+ * collections that are created by code we control and are known to support random access.
+ */
+@Suppress("BanInlineOptIn")
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T, R> List<T>.fastMap(transform: (T) -> R): List<R> {
+    contract { callsInPlace(transform) }
+    val target = ArrayList<R>(size)
+    fastForEach { target += transform(it) }
+    return target
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
index c1defb7..eb1a634 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt
@@ -17,11 +17,10 @@
 
 package com.android.compose.ui.util
 
+import androidx.compose.ui.unit.IntSize
 import kotlin.math.roundToInt
 import kotlin.math.roundToLong
 
-// TODO(b/272311106): this is a fork from material. Unfork it when MathHelpers.kt reaches material3.
-
 /** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
 fun lerp(start: Float, stop: Float, fraction: Float): Float {
     return (1 - fraction) * start + fraction * stop
@@ -36,3 +35,11 @@
 fun lerp(start: Long, stop: Long, fraction: Float): Long {
     return start + ((stop - start) * fraction.toDouble()).roundToLong()
 }
+
+/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
+fun lerp(start: IntSize, stop: IntSize, fraction: Float): IntSize {
+    return IntSize(
+        lerp(start.width, stop.width, fraction),
+        lerp(start.height, stop.height, fraction)
+    )
+}
diff --git a/packages/SystemUI/compose/core/tests/Android.bp b/packages/SystemUI/compose/core/tests/Android.bp
index 6119e96..eb80da7 100644
--- a/packages/SystemUI/compose/core/tests/Android.bp
+++ b/packages/SystemUI/compose/core/tests/Android.bp
@@ -42,6 +42,8 @@
         "androidx.compose.runtime_runtime",
         "androidx.compose.ui_ui-test-junit4",
         "androidx.compose.ui_ui-test-manifest",
+
+        "truth-prebuilt",
     ],
 
     kotlincflags: ["-Xjvm-default=enable"],
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
new file mode 100644
index 0000000..04b3f8a
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ObservableTransitionStateTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun testObservableTransitionState() = runTest {
+        val state = SceneTransitionLayoutState(TestScenes.SceneA)
+
+        // Collect the current observable state into [observableState].
+        // TODO(b/290184746): Use collectValues {} once it is extracted into a library that can be
+        // reused by non-SystemUI testing code.
+        var observableState: ObservableTransitionState? = null
+        backgroundScope.launch {
+            state.observableTransitionState().collect { observableState = it }
+        }
+
+        fun observableState(): ObservableTransitionState {
+            runCurrent()
+            return observableState!!
+        }
+
+        fun ObservableTransitionState.Transition.progress(): Float {
+            var lastProgress = -1f
+            backgroundScope.launch { progress.collect { lastProgress = it } }
+            runCurrent()
+            return lastProgress
+        }
+
+        rule.testTransition(
+            from = TestScenes.SceneA,
+            to = TestScenes.SceneB,
+            transitionLayout = { currentScene, onChangeScene ->
+                SceneTransitionLayout(
+                    currentScene,
+                    onChangeScene,
+                    EmptyTestTransitions,
+                    state = state,
+                ) {
+                    scene(TestScenes.SceneA) {}
+                    scene(TestScenes.SceneB) {}
+                }
+            }
+        ) {
+            before {
+                assertThat(observableState())
+                    .isEqualTo(ObservableTransitionState.Idle(TestScenes.SceneA))
+            }
+            at(0) {
+                val state = observableState()
+                assertThat(state).isInstanceOf(ObservableTransitionState.Transition::class.java)
+                assertThat((state as ObservableTransitionState.Transition).fromScene)
+                    .isEqualTo(TestScenes.SceneA)
+                assertThat(state.toScene).isEqualTo(TestScenes.SceneB)
+                assertThat(state.progress()).isEqualTo(0f)
+            }
+            at(TestTransitionDuration / 2) {
+                val state = observableState()
+                assertThat((state as ObservableTransitionState.Transition).fromScene)
+                    .isEqualTo(TestScenes.SceneA)
+                assertThat(state.toScene).isEqualTo(TestScenes.SceneB)
+                assertThat(state.progress()).isEqualTo(0.5f)
+            }
+            after {
+                assertThat(observableState())
+                    .isEqualTo(ObservableTransitionState.Idle(TestScenes.SceneB))
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
new file mode 100644
index 0000000..8bd6545
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.activity.ComponentActivity
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onAllNodesWithTag
+import androidx.compose.ui.test.onChild
+import androidx.compose.ui.test.onFirst
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.test.subjects.DpOffsetSubject
+import com.android.compose.test.subjects.assertThat
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SceneTransitionLayoutTest {
+    companion object {
+        private val LayoutSize = 300.dp
+    }
+
+    private var currentScene by mutableStateOf(TestScenes.SceneA)
+    private val layoutState = SceneTransitionLayoutState(currentScene)
+
+    // We use createAndroidComposeRule() here and not createComposeRule() because we need an
+    // activity for testBack().
+    @get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
+
+    /** The content under test. */
+    @Composable
+    private fun TestContent() {
+        SceneTransitionLayout(
+            currentScene,
+            { currentScene = it },
+            EmptyTestTransitions,
+            state = layoutState,
+            modifier = Modifier.size(LayoutSize),
+        ) {
+            scene(
+                TestScenes.SceneA,
+                userActions = mapOf(Back to TestScenes.SceneB),
+            ) {
+                Box(Modifier.fillMaxSize()) {
+                    SharedFoo(size = 50.dp, childOffset = 0.dp, Modifier.align(Alignment.TopEnd))
+                    Text("SceneA")
+                }
+            }
+            scene(TestScenes.SceneB) {
+                Box(Modifier.fillMaxSize()) {
+                    SharedFoo(
+                        size = 100.dp,
+                        childOffset = 50.dp,
+                        Modifier.align(Alignment.TopStart),
+                    )
+                    Text("SceneB")
+                }
+            }
+            scene(TestScenes.SceneC) {
+                Box(Modifier.fillMaxSize()) {
+                    SharedFoo(
+                        size = 150.dp,
+                        childOffset = 100.dp,
+                        Modifier.align(Alignment.BottomStart),
+                    )
+                    Text("SceneC")
+                }
+            }
+        }
+    }
+
+    @Composable
+    private fun SceneScope.SharedFoo(size: Dp, childOffset: Dp, modifier: Modifier = Modifier) {
+        Box(
+            modifier
+                .size(size)
+                .background(Color.Red)
+                .element(TestElements.Foo)
+                .testTag(TestElements.Foo.name)
+        ) {
+            // Offset the single child of Foo by some animated shared offset.
+            val offset by animateSharedDpAsState(childOffset, TestValues.Value1, TestElements.Foo)
+
+            Box(
+                Modifier.offset {
+                        val pxOffset = offset.roundToPx()
+                        IntOffset(pxOffset, pxOffset)
+                    }
+                    .size(30.dp)
+                    .background(Color.Blue)
+                    .testTag(TestElements.Bar.name)
+            )
+        }
+    }
+
+    @Test
+    fun testOnlyCurrentSceneIsDisplayed() {
+        rule.setContent { TestContent() }
+
+        // Only scene A is displayed.
+        rule.onNodeWithText("SceneA").assertIsDisplayed()
+        rule.onNodeWithText("SceneB").assertDoesNotExist()
+        rule.onNodeWithText("SceneC").assertDoesNotExist()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        // Change to scene B. Only that scene is displayed.
+        currentScene = TestScenes.SceneB
+        rule.onNodeWithText("SceneA").assertDoesNotExist()
+        rule.onNodeWithText("SceneB").assertIsDisplayed()
+        rule.onNodeWithText("SceneC").assertDoesNotExist()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+    }
+
+    @Test
+    fun testBack() {
+        rule.setContent { TestContent() }
+
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        rule.activity.onBackPressed()
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+    }
+
+    @Test
+    fun testTransitionState() {
+        rule.setContent { TestContent() }
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        // We will advance the clock manually.
+        rule.mainClock.autoAdvance = false
+
+        // Change the current scene. Until composition is triggered, this won't change the layout
+        // state.
+        currentScene = TestScenes.SceneB
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        // On the next frame, we will recompose because currentScene changed, which will start the
+        // transition (i.e. it will change the transitionState to be a Transition) in a
+        // LaunchedEffect.
+        rule.mainClock.advanceTimeByFrame()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
+        val transition = layoutState.transitionState as TransitionState.Transition
+        assertThat(transition.fromScene).isEqualTo(TestScenes.SceneA)
+        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+        assertThat(transition.progress).isEqualTo(0f)
+
+        // Then, on the next frame, the animator we started gets its initial value and clock
+        // starting time. We are now at progress = 0f.
+        rule.mainClock.advanceTimeByFrame()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((layoutState.transitionState as TransitionState.Transition).progress)
+            .isEqualTo(0f)
+
+        // The test transition lasts 480ms. 240ms after the start of the transition, we are at
+        // progress = 0.5f.
+        rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((layoutState.transitionState as TransitionState.Transition).progress)
+            .isEqualTo(0.5f)
+
+        // (240-16) ms later, i.e. one frame before the transition is finished, we are at
+        // progress=(480-16)/480.
+        rule.mainClock.advanceTimeBy(TestTransitionDuration / 2 - 16)
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((layoutState.transitionState as TransitionState.Transition).progress)
+            .isEqualTo((TestTransitionDuration - 16) / 480f)
+
+        // one frame (16ms) later, the transition is finished and we are in the idle state in scene
+        // B.
+        rule.mainClock.advanceTimeByFrame()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+    }
+
+    @Test
+    fun testSharedElement() {
+        rule.setContent { TestContent() }
+
+        // In scene A, the shared element SharedFoo() is at the top end of the layout and has a size
+        // of 50.dp.
+        var sharedFoo = rule.onNodeWithTag(TestElements.Foo.name, useUnmergedTree = true)
+        sharedFoo.assertWidthIsEqualTo(50.dp)
+        sharedFoo.assertHeightIsEqualTo(50.dp)
+        sharedFoo.assertPositionInRootIsEqualTo(
+            expectedTop = 0.dp,
+            expectedLeft = LayoutSize - 50.dp,
+        )
+
+        // The shared offset of the single child of SharedFoo() is 0dp in scene A.
+        assertThat(sharedFoo.onChild().offsetRelativeTo(sharedFoo)).isEqualTo(DpOffset(0.dp, 0.dp))
+
+        // Pause animations to test the state mid-transition.
+        rule.mainClock.autoAdvance = false
+
+        // Go to scene B and let the animation start. See [testLayoutState()] and
+        // [androidx.compose.ui.test.MainTestClock] to understand why we need to advance the clock
+        // by 2 frames to be at the start of the animation.
+        currentScene = TestScenes.SceneB
+        rule.mainClock.advanceTimeByFrame()
+        rule.mainClock.advanceTimeByFrame()
+
+        // Advance to the middle of the animation.
+        rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
+
+        // We need to use onAllNodesWithTag().onFirst() here given that shared elements are
+        // composed and laid out in both scenes (but drawn only in one).
+        sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.name).onFirst()
+
+        // In scene B, foo is at the top start (x = 0, y = 0) of the layout and has a size of
+        // 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we
+        // use a linear interpolator. Foo was at (x = layoutSize - 50dp, y = 0) in SceneA and is
+        // going to (x = 0, y = 0), so the offset should now be half what it was.
+        assertThat((layoutState.transitionState as TransitionState.Transition).progress)
+            .isEqualTo(0.5f)
+        sharedFoo.assertWidthIsEqualTo(75.dp)
+        sharedFoo.assertHeightIsEqualTo(75.dp)
+        sharedFoo.assertPositionInRootIsEqualTo(
+            expectedTop = 0.dp,
+            expectedLeft = (LayoutSize - 50.dp) / 2
+        )
+
+        // The shared offset of the single child of SharedFoo() is 50dp in scene B and 0dp in Scene
+        // A, so it should be 25dp now.
+        assertThat(sharedFoo.onChild().offsetRelativeTo(sharedFoo))
+            .isWithin(DpOffsetSubject.DefaultTolerance)
+            .of(DpOffset(25.dp, 25.dp))
+
+        // Animate to scene C, let the animation start then go to the middle of the transition.
+        currentScene = TestScenes.SceneC
+        rule.mainClock.advanceTimeByFrame()
+        rule.mainClock.advanceTimeByFrame()
+        rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
+
+        // In Scene C, foo is at the bottom start of the layout and has a size of 150.dp. The
+        // transition scene B => scene C is using a FastOutSlowIn interpolator.
+        val interpolatedProgress = FastOutSlowInEasing.transform(0.5f)
+        val expectedTop = (LayoutSize - 150.dp) * interpolatedProgress
+        val expectedLeft = 0.dp
+        val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress
+
+        sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.name).onFirst()
+        assertThat((layoutState.transitionState as TransitionState.Transition).progress)
+            .isEqualTo(interpolatedProgress)
+        sharedFoo.assertWidthIsEqualTo(expectedSize)
+        sharedFoo.assertHeightIsEqualTo(expectedSize)
+        sharedFoo.assertPositionInRootIsEqualTo(expectedLeft, expectedTop)
+
+        // The shared offset of the single child of SharedFoo() is 50dp in scene B and 100dp in
+        // Scene C.
+        val expectedOffset = 50.dp + (100.dp - 50.dp) * interpolatedProgress
+        assertThat(sharedFoo.onChild().offsetRelativeTo(sharedFoo))
+            .isWithin(DpOffsetSubject.DefaultTolerance)
+            .of(DpOffset(expectedOffset, expectedOffset))
+
+        // Go back to scene A. This should happen instantly (once the animation started, i.e. after
+        // 2 frames) given that we use a snap() animation spec.
+        currentScene = TestScenes.SceneA
+        rule.mainClock.advanceTimeByFrame()
+        rule.mainClock.advanceTimeByFrame()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+    }
+
+    private fun SemanticsNodeInteraction.offsetRelativeTo(
+        other: SemanticsNodeInteraction,
+    ): DpOffset {
+        val node = fetchSemanticsNode()
+        val bounds = node.boundsInRoot
+        val otherBounds = other.fetchSemanticsNode().boundsInRoot
+        return with(node.layoutInfo.density) {
+            DpOffset(
+                x = (bounds.left - otherBounds.left).toDp(),
+                y = (bounds.top - otherBounds.top).toDp(),
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
new file mode 100644
index 0000000..cb2607a
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeWithVelocity
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SwipeToSceneTest {
+    companion object {
+        private val LayoutWidth = 200.dp
+        private val LayoutHeight = 400.dp
+
+        /** The middle of the layout, in pixels. */
+        private val Density.middle: Offset
+            get() = Offset((LayoutWidth / 2).toPx(), (LayoutHeight / 2).toPx())
+    }
+
+    private var currentScene by mutableStateOf(TestScenes.SceneA)
+    private val layoutState = SceneTransitionLayoutState(currentScene)
+
+    @get:Rule val rule = createComposeRule()
+
+    /** The content under test. */
+    @Composable
+    private fun TestContent() {
+        SceneTransitionLayout(
+            currentScene,
+            { currentScene = it },
+            EmptyTestTransitions,
+            state = layoutState,
+            modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.name),
+        ) {
+            scene(
+                TestScenes.SceneA,
+                userActions =
+                    mapOf(
+                        Swipe.Left to TestScenes.SceneB,
+                        Swipe.Down to TestScenes.SceneC,
+                    ),
+            ) {
+                Box(Modifier.fillMaxSize())
+            }
+            scene(
+                TestScenes.SceneB,
+                userActions = mapOf(Swipe.Right to TestScenes.SceneA),
+            ) {
+                Box(Modifier.fillMaxSize())
+            }
+            scene(
+                TestScenes.SceneC,
+                userActions = mapOf(Swipe.Down to TestScenes.SceneA),
+            ) {
+                Box(Modifier.fillMaxSize())
+            }
+        }
+    }
+
+    @Test
+    fun testDragWithPositionalThreshold() {
+        // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+        // detected as a drag event.
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            TestContent()
+        }
+
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        // Drag left (i.e. from right to left) by 55dp. We pick 55dp here because 56dp is the
+        // positional threshold from which we commit the gesture.
+        rule.onRoot().performTouchInput {
+            down(middle)
+
+            // We use a high delay so that the velocity of the gesture is slow (otherwise it would
+            // commit the gesture, even if we are below the positional threshold).
+            moveBy(Offset(-55.dp.toPx() - touchSlop, 0f), delayMillis = 1_000)
+        }
+
+        // We should be at a progress = 55dp / LayoutWidth given that we use the layout size in
+        // the gesture axis as swipe distance.
+        var transition = layoutState.transitionState
+        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((transition as TransitionState.Transition).fromScene)
+            .isEqualTo(TestScenes.SceneA)
+        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth)
+
+        // Release the finger. We should now be animating back to A (currentScene = SceneA) given
+        // that 55dp < positional threshold.
+        rule.onRoot().performTouchInput { up() }
+        transition = layoutState.transitionState
+        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((transition as TransitionState.Transition).fromScene)
+            .isEqualTo(TestScenes.SceneA)
+        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth)
+
+        // Wait for the animation to finish. We should now be in scene A.
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        // Now we do the same but vertically and with a drag distance of 56dp, which is >=
+        // positional threshold.
+        rule.onRoot().performTouchInput {
+            down(middle)
+            moveBy(Offset(0f, 56.dp.toPx() + touchSlop), delayMillis = 1_000)
+        }
+
+        // Drag is in progress, so currentScene = SceneA and progress = 56dp / LayoutHeight
+        transition = layoutState.transitionState
+        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((transition as TransitionState.Transition).fromScene)
+            .isEqualTo(TestScenes.SceneA)
+        assertThat(transition.toScene).isEqualTo(TestScenes.SceneC)
+        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight)
+
+        // Release the finger. We should now be animating to C (currentScene = SceneC) given
+        // that 56dp >= positional threshold.
+        rule.onRoot().performTouchInput { up() }
+        transition = layoutState.transitionState
+        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((transition as TransitionState.Transition).fromScene)
+            .isEqualTo(TestScenes.SceneA)
+        assertThat(transition.toScene).isEqualTo(TestScenes.SceneC)
+        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneC)
+        assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight)
+
+        // Wait for the animation to finish. We should now be in scene C.
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+    }
+
+    @Test
+    fun testSwipeWithVelocityThreshold() {
+        // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+        // detected as a drag event.
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            TestContent()
+        }
+
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        // Swipe left (i.e. from right to left) using a velocity of 124 dp/s. We pick 124 dp/s here
+        // because 125 dp/s is the velocity threshold from which we commit the gesture. We also use
+        // a swipe distance < 56dp, the positional threshold, to make sure that we don't commit
+        // the gesture because of a large enough swipe distance.
+        rule.onRoot().performTouchInput {
+            swipeWithVelocity(
+                start = middle,
+                end = middle - Offset(55.dp.toPx() + touchSlop, 0f),
+                endVelocity = 124.dp.toPx(),
+            )
+        }
+
+        // We should be animating back to A (currentScene = SceneA) given that 124 dp/s < velocity
+        // threshold.
+        var transition = layoutState.transitionState
+        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((transition as TransitionState.Transition).fromScene)
+            .isEqualTo(TestScenes.SceneA)
+        assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth)
+
+        // Wait for the animation to finish. We should now be in scene A.
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+
+        // Now we do the same but vertically and with a swipe velocity of 126dp, which is >
+        // velocity threshold. Note that in theory we could have used 125 dp (= velocity threshold)
+        // but it doesn't work reliably with how swipeWithVelocity() computes move events to get to
+        // the target velocity, probably because of float rounding errors.
+        rule.onRoot().performTouchInput {
+            swipeWithVelocity(
+                start = middle,
+                end = middle + Offset(0f, 55.dp.toPx() + touchSlop),
+                endVelocity = 126.dp.toPx(),
+            )
+        }
+
+        // We should be animating to C (currentScene = SceneC).
+        transition = layoutState.transitionState
+        assertThat(transition).isInstanceOf(TransitionState.Transition::class.java)
+        assertThat((transition as TransitionState.Transition).fromScene)
+            .isEqualTo(TestScenes.SceneA)
+        assertThat(transition.toScene).isEqualTo(TestScenes.SceneC)
+        assertThat(transition.currentScene).isEqualTo(TestScenes.SceneC)
+        assertThat(transition.progress).isEqualTo(55.dp / LayoutHeight)
+
+        // Wait for the animation to finish. We should now be in scene C.
+        rule.waitForIdle()
+        assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
+        assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
new file mode 100644
index 0000000..275149a0
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.onNodeWithTag
+
+@DslMarker annotation class TransitionTestDsl
+
+@TransitionTestDsl
+interface TransitionTestBuilder {
+    /**
+     * Assert on the state of the layout before the transition starts.
+     *
+     * This should be called maximum once, before [at] or [after] is called.
+     */
+    fun before(builder: TransitionTestAssertionScope.() -> Unit)
+
+    /**
+     * Assert on the state of the layout during the transition at [timestamp].
+     *
+     * This should be called after [before] is called and before [after] is called. Successive calls
+     * to [at] must be called with increasing [timestamp].
+     *
+     * Important: [timestamp] must be a multiple of 16 (the duration of a frame on the JVM/Android).
+     * There is no intermediary state between `t` and `t + 16` , so testing transitions outside of
+     * `t = 0`, `t = 16`, `t = 32`, etc does not make sense.
+     */
+    fun at(timestamp: Long, builder: TransitionTestAssertionScope.() -> Unit)
+
+    /**
+     * Assert on the state of the layout after the transition finished.
+     *
+     * This should be called maximum once, after [before] or [at] is called.
+     */
+    fun after(builder: TransitionTestAssertionScope.() -> Unit)
+}
+
+@TransitionTestDsl
+interface TransitionTestAssertionScope {
+    /** Assert on [element]. */
+    fun onElement(element: ElementKey): SemanticsNodeInteraction
+}
+
+/**
+ * Test the transition between [fromSceneContent] and [toSceneContent] at different points in time.
+ *
+ * @sample com.android.compose.animation.scene.transformation.TranslateTest
+ */
+fun ComposeContentTestRule.testTransition(
+    fromSceneContent: @Composable SceneScope.() -> Unit,
+    toSceneContent: @Composable SceneScope.() -> Unit,
+    transition: TransitionBuilder.() -> Unit,
+    layoutModifier: Modifier = Modifier,
+    builder: TransitionTestBuilder.() -> Unit,
+) {
+    testTransition(
+        from = TestScenes.SceneA,
+        to = TestScenes.SceneB,
+        transitionLayout = { currentScene, onChangeScene ->
+            SceneTransitionLayout(
+                currentScene,
+                onChangeScene,
+                transitions { from(TestScenes.SceneA, to = TestScenes.SceneB, transition) },
+                layoutModifier.fillMaxSize(),
+            ) {
+                scene(TestScenes.SceneA, content = fromSceneContent)
+                scene(TestScenes.SceneB, content = toSceneContent)
+            }
+        },
+        builder,
+    )
+}
+
+/**
+ * Test the transition between two scenes of [transitionLayout][SceneTransitionLayout] at different
+ * points in time.
+ */
+fun ComposeContentTestRule.testTransition(
+    from: SceneKey,
+    to: SceneKey,
+    transitionLayout:
+        @Composable
+        (
+            currentScene: SceneKey,
+            onChangeScene: (SceneKey) -> Unit,
+        ) -> Unit,
+    builder: TransitionTestBuilder.() -> Unit,
+) {
+    val test = transitionTest(builder)
+    val assertionScope =
+        object : TransitionTestAssertionScope {
+            override fun onElement(element: ElementKey): SemanticsNodeInteraction {
+                return this@testTransition.onNodeWithTag(element.name)
+            }
+        }
+
+    var currentScene by mutableStateOf(from)
+    setContent { transitionLayout(currentScene, { currentScene = it }) }
+
+    // Wait for the UI to be idle then test the before state.
+    waitForIdle()
+    test.before(assertionScope)
+
+    // Manually advance the clock to the start of the animation.
+    mainClock.autoAdvance = false
+
+    // Change the current scene.
+    currentScene = to
+
+    // Advance by a frame to trigger recomposition, which will start the transition (i.e. it will
+    // change the transitionState to be a Transition) in a LaunchedEffect.
+    mainClock.advanceTimeByFrame()
+
+    // Advance by another frame so that the animator we started gets its initial value and clock
+    // starting time. We are now at progress = 0f.
+    mainClock.advanceTimeByFrame()
+    waitForIdle()
+
+    // Test the assertions at specific points in time.
+    test.timestamps.forEach { tsAssertion ->
+        if (tsAssertion.timestampDelta > 0L) {
+            mainClock.advanceTimeBy(tsAssertion.timestampDelta)
+            waitForIdle()
+        }
+
+        tsAssertion.assertion(assertionScope)
+    }
+
+    // Go to the end state and test it.
+    mainClock.autoAdvance = true
+    waitForIdle()
+    test.after(assertionScope)
+}
+
+private fun transitionTest(builder: TransitionTestBuilder.() -> Unit): TransitionTest {
+    // Collect the assertion lambdas in [TransitionTest]. Note that the ordering is forced by the
+    // builder, e.g. `before {}` must be called before everything else, then `at {}` (in increasing
+    // order of timestamp), then `after {}`. That way the test code is run with the same order as it
+    // is written, to avoid confusion.
+
+    val impl =
+        object : TransitionTestBuilder {
+                var before: (TransitionTestAssertionScope.() -> Unit)? = null
+                var after: (TransitionTestAssertionScope.() -> Unit)? = null
+                val timestamps = mutableListOf<TimestampAssertion>()
+
+                private var currentTimestamp = 0L
+
+                override fun before(builder: TransitionTestAssertionScope.() -> Unit) {
+                    check(before == null) { "before {} must be called maximum once" }
+                    check(after == null) { "before {} must be called before after {}" }
+                    check(timestamps.isEmpty()) { "before {} must be called before at(...) {}" }
+
+                    before = builder
+                }
+
+                override fun at(timestamp: Long, builder: TransitionTestAssertionScope.() -> Unit) {
+                    check(after == null) { "at(...) {} must be called before after {}" }
+                    check(timestamp >= currentTimestamp) {
+                        "at(...) must be called with timestamps in increasing order"
+                    }
+                    check(timestamp % 16 == 0L) {
+                        "timestamp must be a multiple of the frame time (16ms)"
+                    }
+
+                    val delta = timestamp - currentTimestamp
+                    currentTimestamp = timestamp
+
+                    timestamps.add(TimestampAssertion(delta, builder))
+                }
+
+                override fun after(builder: TransitionTestAssertionScope.() -> Unit) {
+                    check(after == null) { "after {} must be called maximum once" }
+                    after = builder
+                }
+            }
+            .apply(builder)
+
+    return TransitionTest(
+        before = impl.before ?: {},
+        timestamps = impl.timestamps,
+        after = impl.after ?: {},
+    )
+}
+
+private class TransitionTest(
+    val before: TransitionTestAssertionScope.() -> Unit,
+    val after: TransitionTestAssertionScope.() -> Unit,
+    val timestamps: List<TimestampAssertion>,
+)
+
+private class TimestampAssertion(
+    val timestampDelta: Long,
+    val assertion: TransitionTestAssertionScope.() -> Unit,
+)
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt
new file mode 100644
index 0000000..8357262
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.snap
+import androidx.compose.animation.core.tween
+
+/** Scenes keys that can be reused by tests. */
+object TestScenes {
+    val SceneA = SceneKey("SceneA")
+    val SceneB = SceneKey("SceneB")
+    val SceneC = SceneKey("SceneC")
+}
+
+/** Element keys that can be reused by tests. */
+object TestElements {
+    val Foo = ElementKey("Foo")
+    val Bar = ElementKey("Bar")
+}
+
+/** Value keys that can be reused by tests. */
+object TestValues {
+    val Value1 = ValueKey("Value1")
+}
+
+// We use a transition duration of 480ms here because it is a multiple of 16, the time of a frame in
+// C JVM/Android. Doing so allows us for instance to test the state at progress = 0.5f given that t
+// = 240ms is also a multiple of 16.
+val TestTransitionDuration = 480L
+
+/** A definition of empty transitions between [TestScenes], using different animation specs. */
+val EmptyTestTransitions = transitions {
+    from(TestScenes.SceneA, to = TestScenes.SceneB) {
+        spec = tween(durationMillis = TestTransitionDuration.toInt(), easing = LinearEasing)
+    }
+
+    from(TestScenes.SceneB, to = TestScenes.SceneC) {
+        spec = tween(durationMillis = TestTransitionDuration.toInt(), easing = FastOutSlowInEasing)
+    }
+
+    from(TestScenes.SceneC, to = TestScenes.SceneA) { spec = snap() }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
new file mode 100644
index 0000000..8ef6757
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.testTransition
+import com.android.compose.test.assertSizeIsEqualTo
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AnchoredSizeTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun testAnchoredSizeEnter() {
+        rule.testTransition(
+            fromSceneContent = { Box(Modifier.size(100.dp, 100.dp).element(TestElements.Foo)) },
+            toSceneContent = {
+                Box(Modifier.size(50.dp, 50.dp).element(TestElements.Foo))
+                Box(Modifier.size(200.dp, 60.dp).element(TestElements.Bar))
+            },
+            transition = {
+                // Scale during 4 frames.
+                spec = tween(16 * 4, easing = LinearEasing)
+                anchoredSize(TestElements.Bar, TestElements.Foo)
+            },
+        ) {
+            // Bar is entering. It starts at the same size as Foo in scene A in and scales to its
+            // final size in scene B.
+            before { onElement(TestElements.Bar).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 100.dp) }
+            at(16) { onElement(TestElements.Bar).assertSizeIsEqualTo(125.dp, 90.dp) }
+            at(32) { onElement(TestElements.Bar).assertSizeIsEqualTo(150.dp, 80.dp) }
+            at(48) { onElement(TestElements.Bar).assertSizeIsEqualTo(175.dp, 70.dp) }
+            at(64) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) }
+            after { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) }
+        }
+    }
+
+    @Test
+    fun testAnchoredSizeExit() {
+        rule.testTransition(
+            fromSceneContent = {
+                Box(Modifier.size(100.dp, 100.dp).element(TestElements.Foo))
+                Box(Modifier.size(100.dp, 100.dp).element(TestElements.Bar))
+            },
+            toSceneContent = { Box(Modifier.size(200.dp, 60.dp).element(TestElements.Foo)) },
+            transition = {
+                // Scale during 4 frames.
+                spec = tween(16 * 4, easing = LinearEasing)
+                anchoredSize(TestElements.Bar, TestElements.Foo)
+            },
+        ) {
+            // Bar is leaving. It starts at 100dp x 100dp in scene A and is scaled to 200dp x 60dp,
+            // the size of Foo in scene B.
+            before { onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 100.dp) }
+            at(0) { onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 100.dp) }
+            at(16) { onElement(TestElements.Bar).assertSizeIsEqualTo(125.dp, 90.dp) }
+            at(32) { onElement(TestElements.Bar).assertSizeIsEqualTo(150.dp, 80.dp) }
+            at(48) { onElement(TestElements.Bar).assertSizeIsEqualTo(175.dp, 70.dp) }
+            after { onElement(TestElements.Bar).assertDoesNotExist() }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
new file mode 100644
index 0000000..d1205e7
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.offset
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.testTransition
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AnchoredTranslateTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun testAnchoredTranslateExit() {
+        rule.testTransition(
+            fromSceneContent = {
+                Box(Modifier.offset(10.dp, 50.dp).element(TestElements.Foo))
+                Box(Modifier.offset(20.dp, 40.dp).element(TestElements.Bar))
+            },
+            toSceneContent = { Box(Modifier.offset(30.dp, 10.dp).element(TestElements.Foo)) },
+            transition = {
+                // Anchor Bar to Foo, which is moving from (10dp, 50dp) to (30dp, 10dp).
+                spec = tween(16 * 4, easing = LinearEasing)
+                anchoredTranslate(TestElements.Bar, TestElements.Foo)
+            },
+        ) {
+            // Bar moves by (20dp, -40dp), like Foo.
+            before { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(20.dp, 40.dp) }
+            at(0) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(20.dp, 40.dp) }
+            at(16) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(25.dp, 30.dp) }
+            at(32) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(30.dp, 20.dp) }
+            at(48) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(35.dp, 10.dp) }
+            after { onElement(TestElements.Bar).assertDoesNotExist() }
+        }
+    }
+
+    @Test
+    fun testAnchoredTranslateEnter() {
+        rule.testTransition(
+            fromSceneContent = { Box(Modifier.offset(10.dp, 50.dp).element(TestElements.Foo)) },
+            toSceneContent = {
+                Box(Modifier.offset(30.dp, 10.dp).element(TestElements.Foo))
+                Box(Modifier.offset(20.dp, 40.dp).element(TestElements.Bar))
+            },
+            transition = {
+                // Anchor Bar to Foo, which is moving from (10dp, 50dp) to (30dp, 10dp).
+                spec = tween(16 * 4, easing = LinearEasing)
+                anchoredTranslate(TestElements.Bar, TestElements.Foo)
+            },
+        ) {
+            // Bar moves by (20dp, -40dp), like Foo.
+            before { onElement(TestElements.Bar).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(0.dp, 80.dp) }
+            at(16) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(5.dp, 70.dp) }
+            at(32) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(10.dp, 60.dp) }
+            at(48) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(15.dp, 50.dp) }
+            at(64) { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(20.dp, 40.dp) }
+            after { onElement(TestElements.Bar).assertPositionInRootIsEqualTo(20.dp, 40.dp) }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
new file mode 100644
index 0000000..2a27763
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.TransitionTestBuilder
+import com.android.compose.animation.scene.testTransition
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class EdgeTranslateTest {
+
+    @get:Rule val rule = createComposeRule()
+
+    private fun testEdgeTranslate(
+        edge: Edge,
+        startsOutsideLayoutBounds: Boolean,
+        builder: TransitionTestBuilder.() -> Unit,
+    ) {
+        rule.testTransition(
+            // The layout under test is 300dp x 300dp.
+            layoutModifier = Modifier.size(300.dp),
+            fromSceneContent = {},
+            toSceneContent = {
+                // Foo is 100dp x 100dp in the center of the layout, so at offset = (100dp, 100dp)
+                Box(Modifier.fillMaxSize()) {
+                    Box(Modifier.size(100.dp).element(TestElements.Foo).align(Alignment.Center))
+                }
+            },
+            transition = {
+                spec = tween(16 * 4, easing = LinearEasing)
+                translate(TestElements.Foo, edge, startsOutsideLayoutBounds)
+            },
+            builder = builder,
+        )
+    }
+
+    @Test
+    fun testEntersFromTop_startsOutsideLayoutBounds() {
+        testEdgeTranslate(Edge.Top, startsOutsideLayoutBounds = true) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, (-100).dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 0.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+
+    @Test
+    fun testEntersFromTop_startsInsideLayoutBounds() {
+        testEdgeTranslate(Edge.Top, startsOutsideLayoutBounds = false) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 0.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 50.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+
+    @Test
+    fun testEntersFromBottom_startsOutsideLayoutBounds() {
+        testEdgeTranslate(Edge.Bottom, startsOutsideLayoutBounds = true) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 300.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 200.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+
+    @Test
+    fun testEntersFromBottom_startsInsideLayoutBounds() {
+        testEdgeTranslate(Edge.Bottom, startsOutsideLayoutBounds = false) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 200.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 150.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+
+    @Test
+    fun testEntersFromLeft_startsOutsideLayoutBounds() {
+        testEdgeTranslate(Edge.Left, startsOutsideLayoutBounds = true) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo((-100).dp, 100.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(0.dp, 100.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+
+    @Test
+    fun testEntersFromLeft_startsInsideLayoutBounds() {
+        testEdgeTranslate(Edge.Left, startsOutsideLayoutBounds = false) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(0.dp, 100.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 100.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+
+    @Test
+    fun testEntersFromRight_startsOutsideLayoutBounds() {
+        testEdgeTranslate(Edge.Right, startsOutsideLayoutBounds = true) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(300.dp, 100.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(200.dp, 100.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+
+    @Test
+    fun testEntersFromRight_startsInsideLayoutBounds() {
+        testEdgeTranslate(Edge.Right, startsOutsideLayoutBounds = false) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(200.dp, 100.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(150.dp, 100.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
new file mode 100644
index 0000000..384355c
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.testTransition
+import com.android.compose.test.assertSizeIsEqualTo
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ScaleSizeTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun testScaleSize() {
+        rule.testTransition(
+            fromSceneContent = {},
+            toSceneContent = { Box(Modifier.size(100.dp).element(TestElements.Foo)) },
+            transition = {
+                // Scale during 4 frames.
+                spec = tween(16 * 4, easing = LinearEasing)
+                scaleSize(TestElements.Foo, width = 2f, height = 0.5f)
+            },
+        ) {
+            // Foo is entering, is 100dp x 100dp at rest and is scaled by (2, 0.5) during the
+            // transition so it starts at 200dp x 50dp.
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertSizeIsEqualTo(200.dp, 50.dp) }
+            at(16) { onElement(TestElements.Foo).assertSizeIsEqualTo(175.dp, 62.5.dp) }
+            at(32) { onElement(TestElements.Foo).assertSizeIsEqualTo(150.dp, 75.dp) }
+            at(48) { onElement(TestElements.Foo).assertSizeIsEqualTo(125.dp, 87.5.dp) }
+            at(64) { onElement(TestElements.Foo).assertSizeIsEqualTo(100.dp, 100.dp) }
+            after { onElement(TestElements.Foo).assertSizeIsEqualTo(100.dp, 100.dp) }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
new file mode 100644
index 0000000..1d559fd
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene.transformation
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.offset
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.testTransition
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TranslateTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun testTranslateExit() {
+        rule.testTransition(
+            fromSceneContent = {
+                // Foo is at (10dp, 50dp) and is exiting.
+                Box(Modifier.offset(10.dp, 50.dp).element(TestElements.Foo))
+            },
+            toSceneContent = {},
+            transition = {
+                // Foo is translated by (20dp, -40dp) during 4 frames.
+                spec = tween(16 * 4, easing = LinearEasing)
+                translate(TestElements.Foo, x = 20.dp, y = (-40).dp)
+            },
+        ) {
+            before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
+            at(16) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(15.dp, 40.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(20.dp, 30.dp) }
+            at(48) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(25.dp, 20.dp) }
+            after { onElement(TestElements.Foo).assertDoesNotExist() }
+        }
+    }
+
+    @Test
+    fun testTranslateEnter() {
+        rule.testTransition(
+            fromSceneContent = {},
+            toSceneContent = {
+                // Foo is entering to (10dp, 50dp)
+                Box(Modifier.offset(10.dp, 50.dp).element(TestElements.Foo))
+            },
+            transition = {
+                // Foo is translated from (10dp, 50) + (20dp, -40dp) during 4 frames.
+                spec = tween(16 * 4, easing = LinearEasing)
+                translate(TestElements.Foo, x = 20.dp, y = (-40).dp)
+            },
+        ) {
+            before { onElement(TestElements.Foo).assertDoesNotExist() }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(30.dp, 10.dp) }
+            at(16) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(25.dp, 20.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(20.dp, 30.dp) }
+            at(48) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(15.dp, 40.dp) }
+            at(64) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
+            after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt
new file mode 100644
index 0000000..fbd1b51
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt
@@ -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.compose.test
+
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.unit.Dp
+
+fun SemanticsNodeInteraction.assertSizeIsEqualTo(expectedWidth: Dp, expectedHeight: Dp) {
+    assertWidthIsEqualTo(expectedWidth)
+    assertHeightIsEqualTo(expectedHeight)
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
new file mode 100644
index 0000000..bf7bf98
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.compose.test.subjects
+
+import androidx.compose.ui.test.assertIsEqualTo
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpOffset
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth.assertAbout
+
+/** Assert on a [DpOffset]. */
+fun assertThat(dpOffset: DpOffset): DpOffsetSubject {
+    return assertAbout(DpOffsetSubject.dpOffsets()).that(dpOffset)
+}
+
+/** A Truth subject to assert on [DpOffset] with some tolerance. Inspired by FloatSubject. */
+class DpOffsetSubject(
+    metadata: FailureMetadata,
+    private val actual: DpOffset,
+) : Subject(metadata, actual) {
+    fun isWithin(tolerance: Dp): TolerantDpOffsetComparison {
+        return object : TolerantDpOffsetComparison {
+            override fun of(expected: DpOffset) {
+                actual.x.assertIsEqualTo(expected.x, "offset.x", tolerance)
+                actual.y.assertIsEqualTo(expected.y, "offset.y", tolerance)
+            }
+        }
+    }
+
+    interface TolerantDpOffsetComparison {
+        fun of(expected: DpOffset)
+    }
+
+    companion object {
+        val DefaultTolerance = Dp(.5f)
+
+        fun dpOffsets() =
+            Factory<DpOffsetSubject, DpOffset> { metadata, actual ->
+                DpOffsetSubject(metadata, actual)
+            }
+    }
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index cc33745..609ea90 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -21,10 +21,11 @@
 import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
-import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.util.time.SystemClock
+import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 
 /** The Compose facade, when Compose is *not* available. */
 object ComposeFacade : BaseComposeFacade {
@@ -50,10 +51,10 @@
         throwComposeUnavailableError()
     }
 
-    override fun createMultiShadeView(
+    override fun createSceneContainerView(
         context: Context,
-        viewModel: MultiShadeViewModel,
-        clock: SystemClock,
+        viewModel: SceneContainerViewModel,
+        sceneByKey: Map<SceneKey, Scene>,
     ): View {
         throwComposeUnavailableError()
     }
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
index 5413f90..24064b1 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
@@ -17,12 +17,10 @@
 package com.android.systemui.scene.ui.composable
 
 import com.android.systemui.scene.shared.model.Scene
-import com.android.systemui.scene.shared.model.SceneContainerNames
 import dagger.Module
 import dagger.multibindings.Multibinds
-import javax.inject.Named
 
 @Module
 interface SceneModule {
-    @Multibinds @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) fun scenes(): Set<Scene>
+    @Multibinds fun scenes(): Set<Scene>
 }
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 0e79b18..0ee88b9 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -23,13 +23,15 @@
 import androidx.compose.ui.platform.ComposeView
 import androidx.lifecycle.LifecycleOwner
 import com.android.compose.theme.PlatformTheme
-import com.android.systemui.multishade.ui.composable.MultiShade
-import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 import com.android.systemui.people.ui.compose.PeopleScreen
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.compose.FooterActions
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.util.time.SystemClock
+import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.scene.ui.composable.SceneContainer
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 
 /** The Compose facade, when Compose is available. */
 object ComposeFacade : BaseComposeFacade {
@@ -55,17 +57,18 @@
         }
     }
 
-    override fun createMultiShadeView(
+    override fun createSceneContainerView(
         context: Context,
-        viewModel: MultiShadeViewModel,
-        clock: SystemClock,
+        viewModel: SceneContainerViewModel,
+        sceneByKey: Map<SceneKey, Scene>,
     ): View {
         return ComposeView(context).apply {
             setContent {
                 PlatformTheme {
-                    MultiShade(
+                    SceneContainer(
                         viewModel = viewModel,
-                        clock = clock,
+                        sceneByKey =
+                            sceneByKey.mapValues { (_, scene) -> scene as ComposableScene },
                     )
                 }
             }
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
index fbd7f83..1674591 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeInitializerImpl.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.compose
 
 import android.view.View
+import androidx.lifecycle.findViewTreeLifecycleOwner
+import androidx.lifecycle.setViewTreeLifecycleOwner
 import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.ViewTreeLifecycleOwner
-import androidx.savedstate.SavedStateRegistry
 import androidx.savedstate.SavedStateRegistryController
 import androidx.savedstate.SavedStateRegistryOwner
 import com.android.compose.animation.ViewTreeSavedStateRegistryOwner
@@ -27,7 +27,7 @@
 
 internal object ComposeInitializerImpl : ComposeInitializer {
     override fun onAttachedToWindow(root: View) {
-        if (ViewTreeLifecycleOwner.get(root) != null) {
+        if (root.findViewTreeLifecycleOwner() != null) {
             error("root $root already has a LifecycleOwner")
         }
 
@@ -54,7 +54,8 @@
 
                 override val savedStateRegistry = savedStateRegistryController.savedStateRegistry
 
-                override fun getLifecycle(): Lifecycle = lifecycleOwner.lifecycle
+                override val lifecycle: Lifecycle
+                    get() = lifecycleOwner.lifecycle
             }
 
         // We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner]
@@ -64,13 +65,13 @@
 
         // Set the owners on the root. They will be reused by any ComposeView inside the root
         // hierarchy.
-        ViewTreeLifecycleOwner.set(root, lifecycleOwner)
+        root.setViewTreeLifecycleOwner(lifecycleOwner)
         ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner)
     }
 
     override fun onDetachedFromWindow(root: View) {
-        (ViewTreeLifecycleOwner.get(root) as ViewLifecycleOwner).onDestroy()
-        ViewTreeLifecycleOwner.set(root, null)
+        (root.findViewTreeLifecycleOwner() as ViewLifecycleOwner).onDestroy()
+        root.setViewTreeLifecycleOwner(null)
         ViewTreeSavedStateRegistryOwner.set(root, null)
     }
 }
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
index d364374..3e9b397 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
@@ -16,35 +16,29 @@
 
 package com.android.systemui.scene.ui.composable
 
+import android.app.AlertDialog
 import android.content.Context
 import com.android.systemui.bouncer.ui.composable.BouncerScene
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.composable.BouncerSceneDialogFactory
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.ui.composable.LockscreenScene
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
 import com.android.systemui.qs.ui.composable.QuickSettingsScene
-import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
 import com.android.systemui.scene.shared.model.Scene
-import com.android.systemui.scene.shared.model.SceneContainerNames
 import com.android.systemui.shade.ui.composable.ShadeScene
-import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import dagger.Module
 import dagger.Provides
-import javax.inject.Named
-import kotlinx.coroutines.CoroutineScope
 
 @Module
 object SceneModule {
     @Provides
-    @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
     fun scenes(
-        @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) bouncer: BouncerScene,
-        @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) gone: GoneScene,
-        @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) lockScreen: LockscreenScene,
-        @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) qs: QuickSettingsScene,
-        @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) shade: ShadeScene,
+        bouncer: BouncerScene,
+        gone: GoneScene,
+        lockScreen: LockscreenScene,
+        qs: QuickSettingsScene,
+        shade: ShadeScene,
     ): Set<Scene> {
         return setOf(
             bouncer,
@@ -57,70 +51,11 @@
 
     @Provides
     @SysUISingleton
-    @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
-    fun bouncerScene(
-        @Application context: Context,
-        viewModelFactory: BouncerViewModel.Factory,
-    ): BouncerScene {
-        return BouncerScene(
-            viewModel =
-                viewModelFactory.create(
-                    containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
-                ),
-            dialogFactory = { SystemUIDialog(context) },
-        )
-    }
-
-    @Provides
-    @SysUISingleton
-    @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
-    fun goneScene(): GoneScene {
-        return GoneScene()
-    }
-
-    @Provides
-    @SysUISingleton
-    @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
-    fun lockscreenScene(
-        @Application applicationScope: CoroutineScope,
-        viewModelFactory: LockscreenSceneViewModel.Factory,
-    ): LockscreenScene {
-        return LockscreenScene(
-            applicationScope = applicationScope,
-            viewModel =
-                viewModelFactory.create(
-                    containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
-                ),
-        )
-    }
-
-    @Provides
-    @SysUISingleton
-    @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
-    fun quickSettingsScene(
-        viewModelFactory: QuickSettingsSceneViewModel.Factory,
-    ): QuickSettingsScene {
-        return QuickSettingsScene(
-            viewModel =
-                viewModelFactory.create(
-                    containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
-                ),
-        )
-    }
-
-    @Provides
-    @SysUISingleton
-    @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
-    fun shadeScene(
-        @Application applicationScope: CoroutineScope,
-        viewModelFactory: ShadeSceneViewModel.Factory,
-    ): ShadeScene {
-        return ShadeScene(
-            applicationScope = applicationScope,
-            viewModel =
-                viewModelFactory.create(
-                    containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
-                ),
-        )
+    fun bouncerSceneDialogFactory(@Application context: Context): BouncerSceneDialogFactory {
+        return object : BouncerSceneDialogFactory {
+            override fun invoke(): AlertDialog {
+                return SystemUIDialog(context)
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 8f8bb1b..c6438c9 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -34,6 +34,7 @@
         "PlatformComposeCore",
 
         "androidx.compose.runtime_runtime",
+        "androidx.compose.animation_animation-graphics",
         "androidx.compose.material3_material3",
         "androidx.activity_activity-compose",
     ],
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
deleted file mode 100644
index c58c162..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
+++ /dev/null
@@ -1,94 +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
-
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.BoxWithConstraints
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import kotlin.math.roundToInt
-
-/**
- * This is an example Compose feature, which shows a text and a count that is incremented when
- * clicked. We also show the max width available to this component, which is displayed either next
- * to or below the text depending on that max width.
- */
-@Composable
-fun ExampleFeature(text: String, modifier: Modifier = Modifier) {
-    BoxWithConstraints(modifier) {
-        val maxWidth = maxWidth
-        if (maxWidth < 600.dp) {
-            Column {
-                CounterTile(text)
-                Spacer(Modifier.size(16.dp))
-                MaxWidthTile(maxWidth)
-            }
-        } else {
-            Row {
-                CounterTile(text)
-                Spacer(Modifier.size(16.dp))
-                MaxWidthTile(maxWidth)
-            }
-        }
-    }
-}
-
-@Composable
-private fun CounterTile(text: String, modifier: Modifier = Modifier) {
-    Surface(
-        modifier,
-        color = MaterialTheme.colorScheme.primaryContainer,
-        shape = RoundedCornerShape(28.dp),
-    ) {
-        var count by remember { mutableStateOf(0) }
-        Column(
-            Modifier.clickable { count++ }.padding(16.dp),
-        ) {
-            Text(text)
-            Text("I was clicked $count times.")
-        }
-    }
-}
-
-@Composable
-private fun MaxWidthTile(maxWidth: Dp, modifier: Modifier = Modifier) {
-    Surface(
-        modifier,
-        color = MaterialTheme.colorScheme.tertiaryContainer,
-        shape = RoundedCornerShape(28.dp),
-    ) {
-        Text(
-            "The max available width to me is: ${maxWidth.value.roundToInt()}dp",
-            Modifier.padding(16.dp)
-        )
-    }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 240bace..81b9eb0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -24,7 +24,7 @@
 import androidx.compose.animation.Crossfade
 import androidx.compose.animation.core.snap
 import androidx.compose.animation.core.tween
-import androidx.compose.foundation.background
+import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -46,30 +46,42 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.R
 import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.scene.shared.model.UserAction
 import com.android.systemui.scene.ui.composable.ComposableScene
+import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
+object Bouncer {
+    object Elements {
+        val Background = ElementKey("BouncerBackground")
+        val Content = ElementKey("BouncerContent")
+    }
+}
+
 /** The bouncer scene displays authentication challenges like PIN, password, or pattern. */
-class BouncerScene(
+@SysUISingleton
+class BouncerScene
+@Inject
+constructor(
     private val viewModel: BouncerViewModel,
-    private val dialogFactory: () -> AlertDialog,
+    private val dialogFactory: BouncerSceneDialogFactory,
 ) : ComposableScene {
     override val key = SceneKey.Bouncer
 
-    override fun destinationScenes(
-        containerName: String,
-    ): StateFlow<Map<UserAction, SceneModel>> =
+    override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
         MutableStateFlow<Map<UserAction, SceneModel>>(
                 mapOf(
                     UserAction.Back to SceneModel(SceneKey.Lockscreen),
@@ -78,97 +90,109 @@
             .asStateFlow()
 
     @Composable
-    override fun Content(
-        containerName: String,
+    override fun SceneScope.Content(
         modifier: Modifier,
     ) = BouncerScene(viewModel, dialogFactory, modifier)
 }
 
 @Composable
-private fun BouncerScene(
+private fun SceneScope.BouncerScene(
     viewModel: BouncerViewModel,
-    dialogFactory: () -> AlertDialog,
+    dialogFactory: BouncerSceneDialogFactory,
     modifier: Modifier = Modifier,
 ) {
     val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
     val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethod.collectAsState()
     val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
     var dialog: Dialog? by remember { mutableStateOf(null) }
+    val backgroundColor = MaterialTheme.colorScheme.surface
 
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        verticalArrangement = Arrangement.spacedBy(60.dp),
-        modifier =
-            modifier.background(MaterialTheme.colorScheme.surface).fillMaxSize().padding(32.dp)
-    ) {
-        Crossfade(
-            targetState = message,
-            label = "Bouncer message",
-            animationSpec = if (message.isUpdateAnimated) tween() else snap(),
-        ) { message ->
-            Text(
-                text = message.text,
-                color = MaterialTheme.colorScheme.onSurface,
-                style = MaterialTheme.typography.bodyLarge,
-            )
+    Box(modifier) {
+        Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) {
+            drawRect(color = backgroundColor)
         }
 
-        Box(Modifier.weight(1f)) {
-            when (val nonNullViewModel = authMethodViewModel) {
-                is PinBouncerViewModel ->
-                    PinBouncer(
-                        viewModel = nonNullViewModel,
-                        modifier = Modifier.align(Alignment.Center),
-                    )
-                is PasswordBouncerViewModel ->
-                    PasswordBouncer(
-                        viewModel = nonNullViewModel,
-                        modifier = Modifier.align(Alignment.Center),
-                    )
-                is PatternBouncerViewModel ->
-                    PatternBouncer(
-                        viewModel = nonNullViewModel,
-                        modifier =
-                            Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
-                                .align(Alignment.BottomCenter),
-                    )
-                else -> Unit
-            }
-        }
-
-        Button(
-            onClick = viewModel::onEmergencyServicesButtonClicked,
-            colors =
-                ButtonDefaults.buttonColors(
-                    containerColor = MaterialTheme.colorScheme.tertiaryContainer,
-                    contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
-                ),
+        Column(
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.spacedBy(60.dp),
+            modifier =
+                Modifier.element(Bouncer.Elements.Content)
+                    .fillMaxSize()
+                    .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
         ) {
-            Text(
-                text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
-                style = MaterialTheme.typography.bodyMedium,
-            )
-        }
-
-        if (dialogMessage != null) {
-            if (dialog == null) {
-                dialog =
-                    dialogFactory().apply {
-                        setMessage(dialogMessage)
-                        setButton(
-                            DialogInterface.BUTTON_NEUTRAL,
-                            context.getString(R.string.ok),
-                        ) { _, _ ->
-                            viewModel.onThrottlingDialogDismissed()
-                        }
-                        setCancelable(false)
-                        setCanceledOnTouchOutside(false)
-                        show()
-                    }
+            Crossfade(
+                targetState = message,
+                label = "Bouncer message",
+                animationSpec = if (message.isUpdateAnimated) tween() else snap(),
+            ) { message ->
+                Text(
+                    text = message.text,
+                    color = MaterialTheme.colorScheme.onSurface,
+                    style = MaterialTheme.typography.bodyLarge,
+                )
             }
-        } else {
-            dialog?.dismiss()
-            dialog = null
+
+            Box(Modifier.weight(1f)) {
+                when (val nonNullViewModel = authMethodViewModel) {
+                    is PinBouncerViewModel ->
+                        PinBouncer(
+                            viewModel = nonNullViewModel,
+                            modifier = Modifier.align(Alignment.Center),
+                        )
+                    is PasswordBouncerViewModel ->
+                        PasswordBouncer(
+                            viewModel = nonNullViewModel,
+                            modifier = Modifier.align(Alignment.Center),
+                        )
+                    is PatternBouncerViewModel ->
+                        PatternBouncer(
+                            viewModel = nonNullViewModel,
+                            modifier =
+                                Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
+                                    .align(Alignment.BottomCenter),
+                        )
+                    else -> Unit
+                }
+            }
+
+            Button(
+                onClick = viewModel::onEmergencyServicesButtonClicked,
+                colors =
+                    ButtonDefaults.buttonColors(
+                        containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+                        contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+                    ),
+            ) {
+                Text(
+                    text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
+                    style = MaterialTheme.typography.bodyMedium,
+                )
+            }
+
+            if (dialogMessage != null) {
+                if (dialog == null) {
+                    dialog =
+                        dialogFactory().apply {
+                            setMessage(dialogMessage)
+                            setButton(
+                                DialogInterface.BUTTON_NEUTRAL,
+                                context.getString(R.string.ok),
+                            ) { _, _ ->
+                                viewModel.onThrottlingDialogDismissed()
+                            }
+                            setCancelable(false)
+                            setCanceledOnTouchOutside(false)
+                            show()
+                        }
+                }
+            } else {
+                dialog?.dismiss()
+                dialog = null
+            }
         }
     }
 }
+
+interface BouncerSceneDialogFactory {
+    operator fun invoke(): AlertDialog
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 8844114..64227b8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -43,10 +43,10 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.Easings
+import com.android.compose.modifiers.thenIf
 import com.android.internal.R
 import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel
-import com.android.systemui.compose.modifiers.thenIf
 import kotlin.math.min
 import kotlin.math.pow
 import kotlin.math.sqrt
@@ -131,7 +131,7 @@
                         animationSpec =
                             tween(
                                 durationMillis = SELECTED_DOT_REACTION_ANIMATION_DURATION_MS,
-                                easing = Easings.StandardAccelerateEasing,
+                                easing = Easings.StandardAccelerate,
                             ),
                     )
                 } else {
@@ -140,7 +140,7 @@
                         animationSpec =
                             tween(
                                 durationMillis = SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS,
-                                easing = Easings.StandardDecelerateEasing,
+                                easing = Easings.StandardDecelerate,
                             ),
                     )
                 }
@@ -228,43 +228,45 @@
                 }
             }
     ) {
-        // Draw lines between dots.
-        selectedDots.forEachIndexed { index, dot ->
-            if (index > 0) {
-                val previousDot = selectedDots[index - 1]
-                val lineFadeOutAnimationProgress = lineFadeOutAnimatables[previousDot]!!.value
-                val startLerp = 1 - lineFadeOutAnimationProgress
-                val from = pixelOffset(previousDot, spacing, verticalOffset)
-                val to = pixelOffset(dot, spacing, verticalOffset)
-                val lerpedFrom =
-                    Offset(
-                        x = from.x + (to.x - from.x) * startLerp,
-                        y = from.y + (to.y - from.y) * startLerp,
+        if (isAnimationEnabled) {
+            // Draw lines between dots.
+            selectedDots.forEachIndexed { index, dot ->
+                if (index > 0) {
+                    val previousDot = selectedDots[index - 1]
+                    val lineFadeOutAnimationProgress = lineFadeOutAnimatables[previousDot]!!.value
+                    val startLerp = 1 - lineFadeOutAnimationProgress
+                    val from = pixelOffset(previousDot, spacing, verticalOffset)
+                    val to = pixelOffset(dot, spacing, verticalOffset)
+                    val lerpedFrom =
+                        Offset(
+                            x = from.x + (to.x - from.x) * startLerp,
+                            y = from.y + (to.y - from.y) * startLerp,
+                        )
+                    drawLine(
+                        start = lerpedFrom,
+                        end = to,
+                        cap = StrokeCap.Round,
+                        alpha = lineFadeOutAnimationProgress * lineAlpha(spacing),
+                        color = lineColor,
+                        strokeWidth = lineStrokeWidth,
                     )
-                drawLine(
-                    start = lerpedFrom,
-                    end = to,
-                    cap = StrokeCap.Round,
-                    alpha = lineFadeOutAnimationProgress * lineAlpha(spacing),
-                    color = lineColor,
-                    strokeWidth = lineStrokeWidth,
-                )
+                }
             }
-        }
 
-        // Draw the line between the most recently-selected dot and the input pointer position.
-        inputPosition?.let { lineEnd ->
-            currentDot?.let { dot ->
-                val from = pixelOffset(dot, spacing, verticalOffset)
-                val lineLength = sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2))
-                drawLine(
-                    start = from,
-                    end = lineEnd,
-                    cap = StrokeCap.Round,
-                    alpha = lineAlpha(spacing, lineLength),
-                    color = lineColor,
-                    strokeWidth = lineStrokeWidth,
-                )
+            // Draw the line between the most recently-selected dot and the input pointer position.
+            inputPosition?.let { lineEnd ->
+                currentDot?.let { dot ->
+                    val from = pixelOffset(dot, spacing, verticalOffset)
+                    val lineLength = sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2))
+                    drawLine(
+                        start = from,
+                        end = lineEnd,
+                        cap = StrokeCap.Round,
+                        alpha = lineAlpha(spacing, lineLength),
+                        color = lineColor,
+                        strokeWidth = lineStrokeWidth,
+                    )
+                }
             }
         }
 
@@ -333,7 +335,7 @@
                                         FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS,
                                     delayMillis =
                                         rowIndex * FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS,
-                                    easing = Easings.LinearEasing,
+                                    easing = Easings.Linear,
                                 ),
                         )
 
@@ -343,7 +345,7 @@
                                 tween(
                                     durationMillis =
                                         FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION,
-                                    easing = Easings.StandardEasing,
+                                    easing = Easings.Standard,
                                 ),
                         )
                     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 968e5ab..ec6e5ed 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -14,36 +14,22 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalAnimationApi::class)
-
 package com.android.systemui.bouncer.ui.composable
 
 import android.view.HapticFeedbackConstants
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.animateColorAsState
-import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.scaleIn
-import androidx.compose.animation.scaleOut
-import androidx.compose.animation.slideInHorizontally
-import androidx.compose.animation.slideOutHorizontally
-import androidx.compose.foundation.background
 import androidx.compose.foundation.gestures.detectTapGestures
-import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -59,22 +45,24 @@
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.Easings
 import com.android.compose.grid.VerticalGrid
+import com.android.compose.modifiers.thenIf
 import com.android.systemui.R
+import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
-import com.android.systemui.compose.modifiers.thenIf
-import kotlin.math.max
 import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.DurationUnit
 import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 
@@ -86,139 +74,164 @@
     // Report that the UI is shown to let the view-model run some logic.
     LaunchedEffect(Unit) { viewModel.onShown() }
 
-    // The length of the PIN input received so far, so we know how many dots to render.
-    val pinLength: Pair<Int, Int> by viewModel.pinLengths.collectAsState()
-    val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
-    val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
-
-    // Show the failure animation if the user entered the wrong input.
-    LaunchedEffect(animateFailure) {
-        if (animateFailure) {
-            showFailureAnimation()
-            viewModel.onFailureAnimationShown()
-        }
-    }
-
     Column(
         horizontalAlignment = Alignment.CenterHorizontally,
         modifier = modifier,
     ) {
-        Row(
-            horizontalArrangement = Arrangement.spacedBy(12.dp),
-            modifier = Modifier.heightIn(min = 16.dp).animateContentSize(),
-        ) {
-            // TODO(b/281871687): add support for dot shapes.
-            val (previousPinLength, currentPinLength) = pinLength
-            val dotCount = max(previousPinLength, currentPinLength) + 1
-            repeat(dotCount) { index ->
-                AnimatedVisibility(
-                    visible = index < currentPinLength,
-                    enter = fadeIn() + scaleIn() + slideInHorizontally(),
-                    exit = fadeOut() + scaleOut() + slideOutHorizontally(),
-                ) {
-                    Box(
-                        modifier =
-                            Modifier.size(16.dp)
-                                .background(
-                                    MaterialTheme.colorScheme.onSurfaceVariant,
-                                    CircleShape,
-                                )
-                    )
-                }
-            }
-        }
-
+        PinInputDisplay(viewModel)
         Spacer(Modifier.height(100.dp))
-
-        VerticalGrid(
-            columns = 3,
-            verticalSpacing = 12.dp,
-            horizontalSpacing = 20.dp,
-        ) {
-            repeat(9) { index ->
-                val digit = index + 1
-                PinButton(
-                    onClicked = { viewModel.onPinButtonClicked(digit) },
-                    isEnabled = isInputEnabled,
-                ) { contentColor ->
-                    PinDigit(digit, contentColor)
-                }
-            }
-
-            PinButton(
-                onClicked = { viewModel.onBackspaceButtonClicked() },
-                onLongPressed = { viewModel.onBackspaceButtonLongPressed() },
-                isEnabled = isInputEnabled,
-                isIconButton = true,
-            ) { contentColor ->
-                PinIcon(
-                    Icon.Resource(
-                        res = R.drawable.ic_backspace_24dp,
-                        contentDescription =
-                            ContentDescription.Resource(R.string.keyboardview_keycode_delete),
-                    ),
-                    contentColor,
-                )
-            }
-
-            PinButton(
-                onClicked = { viewModel.onPinButtonClicked(0) },
-                isEnabled = isInputEnabled,
-            ) { contentColor ->
-                PinDigit(0, contentColor)
-            }
-
-            PinButton(
-                onClicked = { viewModel.onAuthenticateButtonClicked() },
-                isEnabled = isInputEnabled,
-                isIconButton = true,
-            ) { contentColor ->
-                PinIcon(
-                    Icon.Resource(
-                        res = R.drawable.ic_keyboard_tab_36dp,
-                        contentDescription =
-                            ContentDescription.Resource(R.string.keyboardview_keycode_enter),
-                    ),
-                    contentColor,
-                )
-            }
-        }
+        PinPad(viewModel)
     }
 }
 
 @Composable
-private fun PinDigit(
+private fun PinPad(viewModel: PinBouncerViewModel) {
+    val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
+    val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState()
+    val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsState()
+    val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
+
+    val buttonScaleAnimatables = remember { List(12) { Animatable(1f) } }
+    LaunchedEffect(animateFailure) {
+        // Show the failure animation if the user entered the wrong input.
+        if (animateFailure) {
+            showFailureAnimation(buttonScaleAnimatables)
+            viewModel.onFailureAnimationShown()
+        }
+    }
+
+    VerticalGrid(
+        columns = 3,
+        verticalSpacing = 12.dp,
+        horizontalSpacing = 20.dp,
+    ) {
+        repeat(9) { index ->
+            DigitButton(
+                index + 1,
+                isInputEnabled,
+                viewModel::onPinButtonClicked,
+                buttonScaleAnimatables[index]::value,
+            )
+        }
+
+        ActionButton(
+            icon =
+                Icon.Resource(
+                    res = R.drawable.ic_backspace_24dp,
+                    contentDescription =
+                        ContentDescription.Resource(R.string.keyboardview_keycode_delete),
+                ),
+            isInputEnabled = isInputEnabled,
+            onClicked = viewModel::onBackspaceButtonClicked,
+            onLongPressed = viewModel::onBackspaceButtonLongPressed,
+            appearance = backspaceButtonAppearance,
+            scaling = buttonScaleAnimatables[9]::value,
+        )
+
+        DigitButton(
+            0,
+            isInputEnabled,
+            viewModel::onPinButtonClicked,
+            buttonScaleAnimatables[10]::value,
+        )
+
+        ActionButton(
+            icon =
+                Icon.Resource(
+                    res = R.drawable.ic_keyboard_tab_36dp,
+                    contentDescription =
+                        ContentDescription.Resource(R.string.keyboardview_keycode_enter),
+                ),
+            isInputEnabled = isInputEnabled,
+            onClicked = viewModel::onAuthenticateButtonClicked,
+            appearance = confirmButtonAppearance,
+            scaling = buttonScaleAnimatables[11]::value,
+        )
+    }
+}
+
+@Composable
+private fun DigitButton(
     digit: Int,
-    contentColor: Color,
+    isInputEnabled: Boolean,
+    onClicked: (Int) -> Unit,
+    scaling: () -> Float,
 ) {
-    // TODO(b/281878426): once "color: () -> Color" (added to BasicText in aosp/2568972) makes it
-    //  into Text, use that here, to animate more efficiently.
-    Text(
-        text = digit.toString(),
-        style = MaterialTheme.typography.headlineLarge,
-        color = contentColor,
-    )
+    PinPadButton(
+        onClicked = { onClicked(digit) },
+        isEnabled = isInputEnabled,
+        backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
+        foregroundColor = MaterialTheme.colorScheme.onSurfaceVariant,
+        modifier =
+            Modifier.graphicsLayer {
+                val scale = scaling()
+                scaleX = scale
+                scaleY = scale
+            }
+    ) { contentColor ->
+        // TODO(b/281878426): once "color: () -> Color" (added to BasicText in aosp/2568972) makes
+        // it into Text, use that here, to animate more efficiently.
+        Text(
+            text = digit.toString(),
+            style = MaterialTheme.typography.headlineLarge,
+            color = contentColor(),
+        )
+    }
 }
 
 @Composable
-private fun PinIcon(
+private fun ActionButton(
     icon: Icon,
-    contentColor: Color,
+    isInputEnabled: Boolean,
+    onClicked: () -> Unit,
+    onLongPressed: (() -> Unit)? = null,
+    appearance: ActionButtonAppearance,
+    scaling: () -> Float,
 ) {
-    Icon(
-        icon = icon,
-        tint = contentColor,
-    )
+    val isHidden = appearance == ActionButtonAppearance.Hidden
+    val hiddenAlpha by animateFloatAsState(if (isHidden) 0f else 1f, label = "Action button alpha")
+
+    val foregroundColor =
+        when (appearance) {
+            ActionButtonAppearance.Shown -> MaterialTheme.colorScheme.onSecondaryContainer
+            else -> MaterialTheme.colorScheme.onSurface
+        }
+    val backgroundColor =
+        when (appearance) {
+            ActionButtonAppearance.Shown -> MaterialTheme.colorScheme.secondaryContainer
+            else -> MaterialTheme.colorScheme.surface
+        }
+
+    PinPadButton(
+        onClicked = onClicked,
+        onLongPressed = onLongPressed,
+        isEnabled = isInputEnabled && !isHidden,
+        backgroundColor = backgroundColor,
+        foregroundColor = foregroundColor,
+        modifier =
+            Modifier.graphicsLayer {
+                alpha = hiddenAlpha
+                val scale = scaling()
+                scaleX = scale
+                scaleY = scale
+            }
+    ) { contentColor ->
+        Icon(
+            icon = icon,
+            tint = contentColor(),
+        )
+    }
 }
 
 @Composable
-private fun PinButton(
+private fun PinPadButton(
     onClicked: () -> Unit,
     isEnabled: Boolean,
+    backgroundColor: Color,
+    foregroundColor: Color,
     modifier: Modifier = Modifier,
     onLongPressed: (() -> Unit)? = null,
-    isIconButton: Boolean = false,
-    content: @Composable (contentColor: Color) -> Unit,
+    content: @Composable (contentColor: () -> Color) -> Unit,
 ) {
     var isPressed: Boolean by remember { mutableStateOf(false) }
 
@@ -252,18 +265,16 @@
         animateColorAsState(
             when {
                 isPressed -> MaterialTheme.colorScheme.primary
-                isIconButton -> MaterialTheme.colorScheme.secondaryContainer
-                else -> MaterialTheme.colorScheme.surfaceVariant
+                else -> backgroundColor
             },
             label = "Pin button container color",
             animationSpec = colorAnimationSpec
         )
-    val contentColor: Color by
+    val contentColor =
         animateColorAsState(
             when {
                 isPressed -> MaterialTheme.colorScheme.onPrimary
-                isIconButton -> MaterialTheme.colorScheme.onSecondaryContainer
-                else -> MaterialTheme.colorScheme.onSurfaceVariant
+                else -> foregroundColor
             },
             label = "Pin button container color",
             animationSpec = colorAnimationSpec
@@ -302,19 +313,48 @@
                     }
                 },
     ) {
-        content(contentColor)
+        content(contentColor::value)
     }
 }
 
-private fun showFailureAnimation() {
-    // TODO(b/282730134): implement.
+private suspend fun showFailureAnimation(
+    buttonScaleAnimatables: List<Animatable<Float, AnimationVector1D>>
+) {
+    coroutineScope {
+        buttonScaleAnimatables.forEachIndexed { index, animatable ->
+            launch {
+                animatable.animateTo(
+                    targetValue = pinButtonErrorShrinkFactor,
+                    animationSpec =
+                        tween(
+                            durationMillis = pinButtonErrorShrinkMs,
+                            delayMillis = index * pinButtonErrorStaggerDelayMs,
+                            easing = Easings.Linear,
+                        ),
+                )
+
+                animatable.animateTo(
+                    targetValue = 1f,
+                    animationSpec =
+                        tween(
+                            durationMillis = pinButtonErrorRevertMs,
+                            easing = Easings.Legacy,
+                        ),
+                )
+            }
+        }
+    }
 }
 
 private val pinButtonSize = 84.dp
+private val pinButtonErrorShrinkFactor = 67.dp / pinButtonSize
+private const val pinButtonErrorShrinkMs = 50
+private const val pinButtonErrorStaggerDelayMs = 33
+private const val pinButtonErrorRevertMs = 617
 
 // Pin button motion spec: http://shortn/_9TTIG6SoEa
 private val pinButtonPressedDuration = 100.milliseconds
-private val pinButtonPressedEasing = LinearEasing
+private val pinButtonPressedEasing = Easings.Linear
 private val pinButtonHoldTime = 33.milliseconds
 private val pinButtonReleasedDuration = 420.milliseconds
-private val pinButtonReleasedEasing = Easings.StandardEasing
+private val pinButtonReleasedEasing = Easings.Standard
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
new file mode 100644
index 0000000..77065cf
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalAnimationGraphicsApi::class)
+
+package com.android.systemui.bouncer.ui.composable
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.runtime.toMutableStateList
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.Easings
+import com.android.keyguard.PinShapeAdapter
+import com.android.systemui.R
+import com.android.systemui.bouncer.ui.viewmodel.EntryToken.Digit
+import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.PinInputViewModel
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.joinAll
+import kotlinx.coroutines.launch
+
+@Composable
+fun PinInputDisplay(viewModel: PinBouncerViewModel) {
+    val hintedPinLength: Int? by viewModel.hintedPinLength.collectAsState()
+    val shapeAnimations = rememberShapeAnimations(viewModel.pinShapes)
+
+    // The display comes in two different flavors:
+    // 1) hinting: shows a circle (◦) per expected pin input, and dot (●) per entered digit.
+    //    This has a fixed width, and uses two distinct types of AVDs to animate the addition and
+    //    removal of digits.
+    // 2) regular, shows a dot (●) per entered digit.
+    //    This grows/shrinks as digits are added deleted. Uses the same type of AVDs to animate the
+    //    addition of digits, but simply center-shrinks the dot (●) shape to zero to animate the
+    //    removal.
+    // Because of all these differences, there are two separate implementations, rather than
+    // unifying into a single, more complex implementation.
+
+    when (val length = hintedPinLength) {
+        null -> RegularPinInputDisplay(viewModel, shapeAnimations)
+        else -> HintingPinInputDisplay(viewModel, shapeAnimations, length)
+    }
+}
+
+/**
+ * A pin input display that shows a placeholder circle (◦) for every digit in the pin not yet
+ * entered.
+ *
+ * Used for auto-confirmed pins of a specific length, see design: http://shortn/_jS8kPzQ7QV
+ */
+@Composable
+private fun HintingPinInputDisplay(
+    viewModel: PinBouncerViewModel,
+    shapeAnimations: ShapeAnimations,
+    hintedPinLength: Int,
+) {
+    val pinInput: PinInputViewModel by viewModel.pinInput.collectAsState()
+    // [ClearAll] marker pointing at the beginning of the current pin input.
+    // When a new [ClearAll] token is added to the [pinInput], the clear-all animation is played
+    // and the marker is advanced manually to the most recent marker. See LaunchedEffect below.
+    var currentClearAll by remember { mutableStateOf(pinInput.mostRecentClearAll()) }
+    // The length of the pin currently entered by the user.
+    val currentPinLength = pinInput.getDigits(currentClearAll).size
+
+    // The animated vector drawables for each of the [hintedPinLength] slots.
+    // The first [currentPinLength] drawables end in a dot (●) shape, the remaining drawables up to
+    // [hintedPinLength] end in the circle (◦) shape.
+    // This list is re-generated upon each pin entry, it is modelled as a  [MutableStateList] to
+    // allow the clear-all animation to replace the shapes asynchronously, see LaunchedEffect below.
+    // Note that when a [ClearAll] token is added to the input (and the clear-all animation plays)
+    // the [currentPinLength] does not change; the [pinEntryDrawable] is remembered until the
+    // clear-all animation finishes and the [currentClearAll] state is manually advanced.
+    val pinEntryDrawable =
+        remember(currentPinLength) {
+            buildList {
+                    repeat(currentPinLength) { add(shapeAnimations.getShapeToDot(it)) }
+                    repeat(hintedPinLength - currentPinLength) { add(shapeAnimations.dotToCircle) }
+                }
+                .toMutableStateList()
+        }
+
+    val mostRecentClearAll = pinInput.mostRecentClearAll()
+    // Whenever a new [ClearAll] marker is added to the input, the clear-all animation needs to
+    // be played.
+    LaunchedEffect(mostRecentClearAll) {
+        if (currentClearAll == mostRecentClearAll) {
+            // Except during the initial composition.
+            return@LaunchedEffect
+        }
+
+        // Staggered replace of all dot (●) shapes with an animation from dot (●) to circle (◦).
+        for (index in 0 until hintedPinLength) {
+            if (!shapeAnimations.isDotShape(pinEntryDrawable[index])) break
+
+            pinEntryDrawable[index] = shapeAnimations.dotToCircle
+            delay(shapeAnimations.dismissStaggerDelay)
+        }
+
+        // Once the animation is done, start processing the next pin input again.
+        currentClearAll = mostRecentClearAll
+    }
+
+    // During the initial composition, do not play the [pinEntryDrawable] animations. This prevents
+    // the dot (●) to circle (◦) animation when the empty display becomes first visible, and a
+    // superfluous shape to dot (●)  animation after for example device rotation.
+    var playAnimation by remember { mutableStateOf(false) }
+    LaunchedEffect(Unit) { playAnimation = true }
+
+    val dotColor = MaterialTheme.colorScheme.onSurfaceVariant
+    Row(modifier = Modifier.heightIn(min = shapeAnimations.shapeSize)) {
+        pinEntryDrawable.forEachIndexed { index, drawable ->
+            // Key the loop by [index] and [drawable], so that updating a shape drawable at the same
+            // index will play the new animation (by remembering a new [atEnd]).
+            key(index, drawable) {
+                // [rememberAnimatedVectorPainter] requires a `atEnd` boolean to switch from `false`
+                // to `true` for the animation to play. This animation is suppressed when
+                // playAnimation is false, always rendering the end-state of the animation.
+                var atEnd by remember { mutableStateOf(!playAnimation) }
+                LaunchedEffect(Unit) { atEnd = true }
+
+                Image(
+                    painter = rememberAnimatedVectorPainter(drawable, atEnd),
+                    contentDescription = null,
+                    contentScale = ContentScale.Crop,
+                    colorFilter = ColorFilter.tint(dotColor),
+                )
+            }
+        }
+    }
+}
+
+/**
+ * A pin input that shows a dot (●) for each entered pin, horizontally centered and growing /
+ * shrinking as more digits are entered and deleted.
+ *
+ * Used for pin input when the pin length is not hinted, see design http://shortn/_wNP7SrBD78
+ */
+@Composable
+private fun RegularPinInputDisplay(
+    viewModel: PinBouncerViewModel,
+    shapeAnimations: ShapeAnimations,
+) {
+    // Holds all currently [VisiblePinEntry] composables. This cannot be simply derived from
+    // `viewModel.pinInput` at composition, since deleting a pin entry needs to play a remove
+    // animation, thus the composable to be removed has to remain in the composition until fully
+    // disappeared (see `prune` launched effect below)
+    val pinInputRow = remember(shapeAnimations) { PinInputRow(shapeAnimations) }
+
+    // Processed `viewModel.pinInput` updates and applies them to [pinDigitShapes]
+    LaunchedEffect(viewModel.pinInput, pinInputRow) {
+        // Initial setup: capture the most recent [ClearAll] marker and create the visuals for the
+        // existing digits (if any) without animation..
+        var currentClearAll =
+            with(viewModel.pinInput.value) {
+                val initialClearAll = mostRecentClearAll()
+                pinInputRow.setDigits(getDigits(initialClearAll))
+                initialClearAll
+            }
+
+        viewModel.pinInput.collect { input ->
+            // Process additions and removals of pins within the current input block.
+            pinInputRow.updateDigits(input.getDigits(currentClearAll), scope = this@LaunchedEffect)
+
+            val mostRecentClearAll = input.mostRecentClearAll()
+            if (currentClearAll != mostRecentClearAll) {
+                // A new [ClearAll] token is added to the [input], play the clear-all animation
+                pinInputRow.playClearAllAnimation()
+
+                // Animation finished, advance manually to the next marker.
+                currentClearAll = mostRecentClearAll
+            }
+        }
+    }
+
+    LaunchedEffect(pinInputRow) {
+        // Prunes unused VisiblePinEntries once they are no longer visible.
+        snapshotFlow { pinInputRow.hasUnusedEntries() }
+            .collect { hasUnusedEntries ->
+                if (hasUnusedEntries) {
+                    pinInputRow.prune()
+                }
+            }
+    }
+
+    pinInputRow.Content()
+}
+
+private class PinInputRow(
+    val shapeAnimations: ShapeAnimations,
+) {
+    private val entries = mutableStateListOf<PinInputEntry>()
+
+    @Composable
+    fun Content() {
+        Row(
+            modifier =
+                Modifier.heightIn(min = shapeAnimations.shapeSize)
+                    // Pins overflowing horizontally should still be shown as scrolling.
+                    .wrapContentSize(unbounded = true),
+        ) {
+            entries.forEach { entry -> key(entry.digit) { entry.Content() } }
+        }
+    }
+
+    /**
+     * Replaces all current [PinInputEntry] composables with new instances for each digit.
+     *
+     * Does not play the entry expansion animation.
+     */
+    fun setDigits(digits: List<Digit>) {
+        entries.clear()
+        entries.addAll(digits.map { PinInputEntry(it, shapeAnimations) })
+    }
+
+    /**
+     * Adds [PinInputEntry] composables for new digits and plays an entry animation, and starts the
+     * exit animation for digits not in [updated] anymore.
+     *
+     * The function return immediately, playing the animations in the background.
+     *
+     * Removed entries have to be [prune]d once the exit animation completes, [hasUnusedEntries] can
+     * be used in a [SnapshotFlow] to discover when its time to do so.
+     */
+    fun updateDigits(updated: List<Digit>, scope: CoroutineScope) {
+        val incoming = updated.minus(entries.map { it.digit }.toSet()).toList()
+        val outgoing = entries.filterNot { entry -> updated.any { entry.digit == it } }.toList()
+
+        entries.addAll(
+            incoming.map {
+                PinInputEntry(it, shapeAnimations).apply { scope.launch { animateAppearance() } }
+            }
+        )
+
+        outgoing.forEach { entry -> scope.launch { entry.animateRemoval() } }
+
+        entries.sortWith(compareBy { it.digit })
+    }
+
+    /**
+     * Plays a staggered remove animation, and upon completion removes the [PinInputEntry]
+     * composables.
+     *
+     * This function returns once the animation finished playing and the entries are removed.
+     */
+    suspend fun playClearAllAnimation() = coroutineScope {
+        val entriesToRemove = entries.toList()
+        entriesToRemove
+            .mapIndexed { index, entry ->
+                launch {
+                    delay(shapeAnimations.dismissStaggerDelay * index)
+                    entry.animateClearAllCollapse()
+                }
+            }
+            .joinAll()
+
+        // Remove all [PinInputEntry] composables for which the staggered remove animation was
+        // played. Note that up to now, each PinInputEntry still occupied the full width.
+        entries.removeAll(entriesToRemove)
+    }
+
+    /**
+     * Whether there are [PinInputEntry] that can be removed from the composition since they were
+     * fully animated out.
+     */
+    fun hasUnusedEntries(): Boolean {
+        return entries.any { it.isUnused }
+    }
+
+    /** Remove all no longer visible [PinInputEntry]s from the composition. */
+    fun prune() {
+        entries.removeAll { it.isUnused }
+    }
+}
+
+private class PinInputEntry(
+    val digit: Digit,
+    val shapeAnimations: ShapeAnimations,
+) {
+    private val shape = shapeAnimations.getShapeToDot(digit.sequenceNumber)
+    // horizontal space occupied, used to shift contents as individual digits are animated in/out
+    private val entryWidth =
+        Animatable(shapeAnimations.shapeSize, Dp.VectorConverter, label = "Width of pin ($digit)")
+    // intrinsic width and height of the shape, used to collapse the shape during exit animations.
+    private val shapeSize =
+        Animatable(shapeAnimations.shapeSize, Dp.VectorConverter, label = "Size of pin ($digit)")
+
+    /**
+     * Whether the is fully animated out. When `true`, removing this from the composable won't have
+     * visual effects.
+     */
+    val isUnused: Boolean
+        get() {
+            return entryWidth.targetValue == 0.dp && !entryWidth.isRunning
+        }
+
+    /** Animate the shape appearance by growing the entry width from 0.dp to the intrinsic width. */
+    suspend fun animateAppearance() = coroutineScope {
+        entryWidth.snapTo(0.dp)
+        entryWidth.animateTo(shapeAnimations.shapeSize, shapeAnimations.inputShiftAnimationSpec)
+    }
+
+    /**
+     * Animates shape disappearance by collapsing the shape and occupied horizontal space.
+     *
+     * Once complete, [isUnused] will return `true`.
+     */
+    suspend fun animateRemoval() = coroutineScope {
+        awaitAll(
+            async { entryWidth.animateTo(0.dp, shapeAnimations.inputShiftAnimationSpec) },
+            async { shapeSize.animateTo(0.dp, shapeAnimations.deleteShapeSizeAnimationSpec) }
+        )
+    }
+
+    /** Collapses the shape in place, while still holding on to the horizontal space. */
+    suspend fun animateClearAllCollapse() = coroutineScope {
+        shapeSize.animateTo(0.dp, shapeAnimations.clearAllShapeSizeAnimationSpec)
+    }
+
+    @Composable
+    fun Content() {
+        val animatedShapeSize by shapeSize.asState()
+        val animatedEntryWidth by entryWidth.asState()
+
+        val dotColor = MaterialTheme.colorScheme.onSurfaceVariant
+        val shapeHeight = shapeAnimations.shapeSize
+        var atEnd by remember { mutableStateOf(false) }
+        LaunchedEffect(Unit) { atEnd = true }
+        Image(
+            painter = rememberAnimatedVectorPainter(shape, atEnd),
+            contentDescription = null,
+            contentScale = ContentScale.Crop,
+            colorFilter = ColorFilter.tint(dotColor),
+            modifier =
+                Modifier.layout { measurable, _ ->
+                    val shapeSizePx = animatedShapeSize.roundToPx()
+                    val placeable = measurable.measure(Constraints.fixed(shapeSizePx, shapeSizePx))
+
+                    layout(animatedEntryWidth.roundToPx(), shapeHeight.roundToPx()) {
+                        placeable.place(
+                            ((animatedEntryWidth - animatedShapeSize) / 2f).roundToPx(),
+                            ((shapeHeight - animatedShapeSize) / 2f).roundToPx()
+                        )
+                    }
+                },
+        )
+    }
+}
+
+/** Animated Vector Drawables used to render the pin input. */
+private class ShapeAnimations(
+    /** Width and height for all the animation images listed here. */
+    val shapeSize: Dp,
+    /** Transitions from the dot (●) to the circle (◦). Used for the hinting pin input only. */
+    val dotToCircle: AnimatedImageVector,
+    /** Each of the animations transition from nothing via a shape to the dot (●). */
+    private val shapesToDot: List<AnimatedImageVector>,
+) {
+    /**
+     * Returns a transition from nothing via shape to the dot (●)., specific to the input position.
+     */
+    fun getShapeToDot(position: Int): AnimatedImageVector {
+        return shapesToDot[position.mod(shapesToDot.size)]
+    }
+
+    /**
+     * Whether the [shapeAnimation] is a image returned by [getShapeToDot], and thus is ending in
+     * the dot (●) shape.
+     *
+     * `false` if the shape's end state is the circle (◦).
+     */
+    fun isDotShape(shapeAnimation: AnimatedImageVector): Boolean {
+        return shapeAnimation != dotToCircle
+    }
+
+    // spec: http://shortn/_DEhE3Xl2bi
+    val dismissStaggerDelay = 33.milliseconds
+    val inputShiftAnimationSpec = tween<Dp>(durationMillis = 250, easing = Easings.Standard)
+    val deleteShapeSizeAnimationSpec =
+        tween<Dp>(durationMillis = 200, easing = Easings.StandardDecelerate)
+    val clearAllShapeSizeAnimationSpec = tween<Dp>(durationMillis = 450, easing = Easings.Legacy)
+}
+
+@Composable
+private fun rememberShapeAnimations(pinShapes: PinShapeAdapter): ShapeAnimations {
+    // NOTE: `animatedVectorResource` does remember the returned AnimatedImageVector.
+    val dotToCircle = AnimatedImageVector.animatedVectorResource(R.drawable.pin_dot_delete_avd)
+    val shapesToDot = pinShapes.shapes.map { AnimatedImageVector.animatedVectorResource(it) }
+    val shapeSize = dimensionResource(R.dimen.password_shape_size)
+
+    return remember(dotToCircle, shapesToDot, shapeSize) {
+        ShapeAnimations(shapeSize, dotToCircle, shapesToDot)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt
deleted file mode 100644
index 83071d7..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/ConditionalModifiers.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.compose.modifiers
-
-import androidx.compose.ui.Modifier
-
-/**
- * Concatenates this modifier with another if `condition` is true.
- *
- * @param condition Whether or not to apply the modifiers.
- * @param factory Creates the modifier to concatenate with the current one.
- * @return a Modifier representing this modifier followed by other in sequence.
- * @see Modifier.then
- *
- * This method allows inline conditional addition of modifiers to a modifier chain. Instead of
- * writing
- *
- * ```
- * val aModifier = Modifier.a()
- * val bModifier = if(condition) aModifier.b() else aModifier
- * Composable(modifier = bModifier)
- * ```
- *
- * You can instead write
- *
- * ```
- * Composable(modifier = Modifier.a().thenIf(condition){
- *  Modifier.b()
- * }
- * ```
- *
- * This makes the modifier chain easier to read.
- *
- * Note that unlike the non-factory version, the conditional modifier is recreated each time, and
- * may never be created at all.
- */
-inline fun Modifier.thenIf(condition: Boolean, crossinline factory: () -> Modifier): Modifier =
-    if (condition) this.then(factory()) else this
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 1065267..da48762 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -29,8 +29,10 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
 import com.android.systemui.scene.shared.model.Direction
@@ -38,6 +40,7 @@
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.scene.shared.model.UserAction
 import com.android.systemui.scene.ui.composable.ComposableScene
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -45,26 +48,26 @@
 import kotlinx.coroutines.flow.stateIn
 
 /** The lock screen scene shows when the device is locked. */
-class LockscreenScene(
+@SysUISingleton
+class LockscreenScene
+@Inject
+constructor(
     @Application private val applicationScope: CoroutineScope,
     private val viewModel: LockscreenSceneViewModel,
 ) : ComposableScene {
     override val key = SceneKey.Lockscreen
 
-    override fun destinationScenes(
-        containerName: String,
-    ): StateFlow<Map<UserAction, SceneModel>> =
+    override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
         viewModel.upDestinationSceneKey
             .map { pageKey -> destinationScenes(up = pageKey) }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value)
+                initialValue = destinationScenes(up = null)
             )
 
     @Composable
-    override fun Content(
-        containerName: String,
+    override fun SceneScope.Content(
         modifier: Modifier,
     ) {
         LockscreenScene(
@@ -74,12 +77,12 @@
     }
 
     private fun destinationScenes(
-        up: SceneKey,
+        up: SceneKey?,
     ): Map<UserAction, SceneModel> {
-        return mapOf(
-            UserAction.Swipe(Direction.UP) to SceneModel(up),
-            UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade)
-        )
+        return buildMap {
+            up?.let { this[UserAction.Swipe(Direction.UP)] = SceneModel(up) }
+            this[UserAction.Swipe(Direction.DOWN)] = SceneModel(SceneKey.Shade)
+        }
     }
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt
deleted file mode 100644
index 99fe26c..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.ui.composable
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.gestures.detectTapGestures
-import androidx.compose.foundation.gestures.detectVerticalDragGestures
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxWithConstraints
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.colorResource
-import androidx.compose.ui.unit.IntSize
-import com.android.systemui.R
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
-import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.qs.footer.ui.compose.QuickSettings
-import com.android.systemui.statusbar.ui.composable.StatusBar
-import com.android.systemui.util.time.SystemClock
-
-@Composable
-fun MultiShade(
-    viewModel: MultiShadeViewModel,
-    clock: SystemClock,
-    modifier: Modifier = Modifier,
-) {
-    val isScrimEnabled: Boolean by viewModel.isScrimEnabled.collectAsState()
-    val scrimAlpha: Float by viewModel.scrimAlpha.collectAsState()
-
-    // TODO(b/273298030): find a different way to get the height constraint from its parent.
-    BoxWithConstraints(modifier = modifier) {
-        val maxHeightPx = with(LocalDensity.current) { maxHeight.toPx() }
-
-        Scrim(
-            modifier = Modifier.fillMaxSize(),
-            remoteTouch = viewModel::onScrimTouched,
-            alpha = { scrimAlpha },
-            isScrimEnabled = isScrimEnabled,
-        )
-        Shade(
-            viewModel = viewModel.leftShade,
-            currentTimeMillis = clock::elapsedRealtime,
-            containerHeightPx = maxHeightPx,
-            modifier = Modifier.align(Alignment.TopStart),
-        ) {
-            Column {
-                StatusBar()
-                Notifications()
-            }
-        }
-        Shade(
-            viewModel = viewModel.rightShade,
-            currentTimeMillis = clock::elapsedRealtime,
-            containerHeightPx = maxHeightPx,
-            modifier = Modifier.align(Alignment.TopEnd),
-        ) {
-            Column {
-                StatusBar()
-                QuickSettings()
-            }
-        }
-        Shade(
-            viewModel = viewModel.singleShade,
-            currentTimeMillis = clock::elapsedRealtime,
-            containerHeightPx = maxHeightPx,
-            modifier = Modifier,
-        ) {
-            Column {
-                StatusBar()
-                Notifications()
-                QuickSettings()
-            }
-        }
-    }
-}
-
-@Composable
-private fun Scrim(
-    remoteTouch: (ProxiedInputModel) -> Unit,
-    alpha: () -> Float,
-    isScrimEnabled: Boolean,
-    modifier: Modifier = Modifier,
-) {
-    var size by remember { mutableStateOf(IntSize.Zero) }
-
-    Box(
-        modifier =
-            modifier
-                .graphicsLayer { this.alpha = alpha() }
-                .background(colorResource(R.color.opaque_scrim))
-                .fillMaxSize()
-                .onSizeChanged { size = it }
-                .then(
-                    if (isScrimEnabled) {
-                        Modifier.pointerInput(Unit) {
-                                detectTapGestures(onTap = { remoteTouch(ProxiedInputModel.OnTap) })
-                            }
-                            .pointerInput(Unit) {
-                                detectVerticalDragGestures(
-                                    onVerticalDrag = { change, dragAmount ->
-                                        remoteTouch(
-                                            ProxiedInputModel.OnDrag(
-                                                xFraction = change.position.x / size.width,
-                                                yDragAmountPx = dragAmount,
-                                            )
-                                        )
-                                    },
-                                    onDragEnd = { remoteTouch(ProxiedInputModel.OnDragEnd) },
-                                    onDragCancel = { remoteTouch(ProxiedInputModel.OnDragCancel) }
-                                )
-                            }
-                    } else {
-                        Modifier
-                    }
-                )
-    )
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt
deleted file mode 100644
index cfcc2fb..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/Shade.kt
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.ui.composable
-
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.interaction.DragInteraction
-import androidx.compose.foundation.interaction.InteractionSource
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.Surface
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.input.pointer.util.VelocityTracker
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import com.android.compose.modifiers.height
-import com.android.compose.modifiers.padding
-import com.android.compose.swipeable.FixedThreshold
-import com.android.compose.swipeable.SwipeableState
-import com.android.compose.swipeable.ThresholdConfig
-import com.android.compose.swipeable.rememberSwipeableState
-import com.android.compose.swipeable.swipeable
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.ui.viewmodel.ShadeViewModel
-import kotlin.math.min
-import kotlin.math.roundToInt
-import kotlinx.coroutines.launch
-
-/**
- * Renders a shade (container and content).
- *
- * This should be allowed to grow to fill the width and height of its container.
- *
- * @param viewModel The view-model for this shade.
- * @param currentTimeMillis A provider for the current time, in milliseconds.
- * @param containerHeightPx The height of the container that this shade is being shown in, in
- *   pixels.
- * @param modifier The Modifier.
- * @param content The content of the shade.
- */
-@Composable
-fun Shade(
-    viewModel: ShadeViewModel,
-    currentTimeMillis: () -> Long,
-    containerHeightPx: Float,
-    modifier: Modifier = Modifier,
-    content: @Composable () -> Unit = {},
-) {
-    val isVisible: Boolean by viewModel.isVisible.collectAsState()
-    if (!isVisible) {
-        return
-    }
-
-    val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
-    ReportNonProxiedInput(viewModel, interactionSource)
-
-    val swipeableState = rememberSwipeableState(initialValue = ShadeState.FullyCollapsed)
-    HandleForcedCollapse(viewModel, swipeableState)
-    HandleProxiedInput(viewModel, swipeableState, currentTimeMillis)
-    ReportShadeExpansion(viewModel, swipeableState, containerHeightPx)
-
-    val isSwipingEnabled: Boolean by viewModel.isSwipingEnabled.collectAsState()
-    val collapseThreshold: Float by viewModel.swipeCollapseThreshold.collectAsState()
-    val expandThreshold: Float by viewModel.swipeExpandThreshold.collectAsState()
-
-    val width: ShadeViewModel.Size by viewModel.width.collectAsState()
-    val density = LocalDensity.current
-
-    val anchors: Map<Float, ShadeState> =
-        remember(containerHeightPx) { swipeableAnchors(containerHeightPx) }
-
-    ShadeContent(
-        shadeHeightPx = { swipeableState.offset.value },
-        overstretch = { swipeableState.overflow.value / containerHeightPx },
-        isSwipingEnabled = isSwipingEnabled,
-        swipeableState = swipeableState,
-        interactionSource = interactionSource,
-        anchors = anchors,
-        thresholds = { _, to ->
-            swipeableThresholds(
-                to = to,
-                swipeCollapseThreshold = collapseThreshold.fractionToDp(density, containerHeightPx),
-                swipeExpandThreshold = expandThreshold.fractionToDp(density, containerHeightPx),
-            )
-        },
-        modifier = modifier.shadeWidth(width, density),
-        content = content,
-    )
-}
-
-/**
- * Draws the content of the shade.
- *
- * @param shadeHeightPx Provider for the current expansion of the shade, in pixels, where `0` is
- *   fully collapsed.
- * @param overstretch Provider for the current amount of vertical "overstretch" that the shade
- *   should be rendered with. This is `0` or a positive number that is a percentage of the total
- *   height of the shade when fully expanded. A value of `0` means that the shade is not stretched
- *   at all.
- * @param isSwipingEnabled Whether swiping inside the shade is enabled or not.
- * @param swipeableState The state to use for the [swipeable] modifier, allowing external control in
- *   addition to direct control (proxied user input in addition to non-proxied/direct user input).
- * @param anchors A map of [ShadeState] keyed by the vertical position, in pixels, where that state
- *   occurs; this is used to configure the [swipeable] modifier.
- * @param thresholds Function that returns the [ThresholdConfig] for going from one [ShadeState] to
- *   another. This controls how the [swipeable] decides which [ShadeState] to animate to once the
- *   user lets go of the shade; e.g. does it animate to fully collapsed or fully expanded.
- * @param content The content to render inside the shade.
- * @param modifier The [Modifier].
- */
-@Composable
-private fun ShadeContent(
-    shadeHeightPx: () -> Float,
-    overstretch: () -> Float,
-    isSwipingEnabled: Boolean,
-    swipeableState: SwipeableState<ShadeState>,
-    interactionSource: MutableInteractionSource,
-    anchors: Map<Float, ShadeState>,
-    thresholds: (from: ShadeState, to: ShadeState) -> ThresholdConfig,
-    modifier: Modifier = Modifier,
-    content: @Composable () -> Unit = {},
-) {
-    /**
-     * Returns a function that takes in [Density] and returns the current padding around the shade
-     * content.
-     */
-    fun padding(
-        shadeHeightPx: () -> Float,
-    ): Density.() -> Int {
-        return {
-            min(
-                12.dp.toPx().roundToInt(),
-                shadeHeightPx().roundToInt(),
-            )
-        }
-    }
-
-    Surface(
-        shape = RoundedCornerShape(32.dp),
-        modifier =
-            modifier
-                .fillMaxWidth()
-                .height { shadeHeightPx().roundToInt() }
-                .padding(
-                    horizontal = padding(shadeHeightPx),
-                    vertical = padding(shadeHeightPx),
-                )
-                .graphicsLayer {
-                    // Applies the vertical over-stretching of the shade content that may happen if
-                    // the user keep dragging down when the shade is already fully-expanded.
-                    transformOrigin = transformOrigin.copy(pivotFractionY = 0f)
-                    this.scaleY = 1 + overstretch().coerceAtLeast(0f)
-                }
-                .swipeable(
-                    enabled = isSwipingEnabled,
-                    state = swipeableState,
-                    interactionSource = interactionSource,
-                    anchors = anchors,
-                    thresholds = thresholds,
-                    orientation = Orientation.Vertical,
-                ),
-        content = content,
-    )
-}
-
-/** Funnels current shade expansion values into the view-model. */
-@Composable
-private fun ReportShadeExpansion(
-    viewModel: ShadeViewModel,
-    swipeableState: SwipeableState<ShadeState>,
-    containerHeightPx: Float,
-) {
-    LaunchedEffect(swipeableState.offset, containerHeightPx) {
-        snapshotFlow { swipeableState.offset.value / containerHeightPx }
-            .collect { expansion -> viewModel.onExpansionChanged(expansion) }
-    }
-}
-
-/** Funnels drag gesture start and end events into the view-model. */
-@Composable
-private fun ReportNonProxiedInput(
-    viewModel: ShadeViewModel,
-    interactionSource: InteractionSource,
-) {
-    LaunchedEffect(interactionSource) {
-        interactionSource.interactions.collect {
-            when (it) {
-                is DragInteraction.Start -> {
-                    viewModel.onDragStarted()
-                }
-                is DragInteraction.Stop -> {
-                    viewModel.onDragEnded()
-                }
-            }
-        }
-    }
-}
-
-/** When told to force collapse, collapses the shade. */
-@Composable
-private fun HandleForcedCollapse(
-    viewModel: ShadeViewModel,
-    swipeableState: SwipeableState<ShadeState>,
-) {
-    LaunchedEffect(viewModel) {
-        viewModel.isForceCollapsed.collect {
-            launch { swipeableState.animateTo(ShadeState.FullyCollapsed) }
-        }
-    }
-}
-
-/**
- * Handles proxied input (input originating outside of the UI of the shade) by driving the
- * [SwipeableState] accordingly.
- */
-@Composable
-private fun HandleProxiedInput(
-    viewModel: ShadeViewModel,
-    swipeableState: SwipeableState<ShadeState>,
-    currentTimeMillis: () -> Long,
-) {
-    val velocityTracker: VelocityTracker = remember { VelocityTracker() }
-    LaunchedEffect(viewModel) {
-        viewModel.proxiedInput.collect {
-            when (it) {
-                is ProxiedInputModel.OnDrag -> {
-                    velocityTracker.addPosition(
-                        timeMillis = currentTimeMillis.invoke(),
-                        position = Offset(0f, it.yDragAmountPx),
-                    )
-                    swipeableState.performDrag(it.yDragAmountPx)
-                }
-                is ProxiedInputModel.OnDragEnd -> {
-                    launch {
-                        val velocity = velocityTracker.calculateVelocity().y
-                        velocityTracker.resetTracking()
-                        // We use a VelocityTracker to keep a record of how fast the pointer was
-                        // moving such that we know how far to fling the shade when the gesture
-                        // ends. Flinging the SwipeableState using performFling is required after
-                        // one or more calls to performDrag such that the swipeable settles into one
-                        // of the states. Without doing that, the shade would remain unmoving in an
-                        // in-between state on the screen.
-                        swipeableState.performFling(velocity)
-                    }
-                }
-                is ProxiedInputModel.OnDragCancel -> {
-                    launch {
-                        velocityTracker.resetTracking()
-                        swipeableState.animateTo(swipeableState.progress.from)
-                    }
-                }
-                else -> Unit
-            }
-        }
-    }
-}
-
-/**
- * Converts the [Float] (which is assumed to be a fraction between `0` and `1`) to a value in dp.
- *
- * @param density The [Density] of the display.
- * @param wholePx The whole amount that the given [Float] is a fraction of.
- * @return The dp size that's a fraction of the whole amount.
- */
-private fun Float.fractionToDp(density: Density, wholePx: Float): Dp {
-    return with(density) { (this@fractionToDp * wholePx).toDp() }
-}
-
-private fun Modifier.shadeWidth(
-    size: ShadeViewModel.Size,
-    density: Density,
-): Modifier {
-    return then(
-        when (size) {
-            is ShadeViewModel.Size.Fraction -> Modifier.fillMaxWidth(size.fraction)
-            is ShadeViewModel.Size.Pixels -> Modifier.width(with(density) { size.pixels.toDp() })
-        }
-    )
-}
-
-/** Returns the pixel positions for each of the supported shade states. */
-private fun swipeableAnchors(containerHeightPx: Float): Map<Float, ShadeState> {
-    return mapOf(
-        0f to ShadeState.FullyCollapsed,
-        containerHeightPx to ShadeState.FullyExpanded,
-    )
-}
-
-/**
- * Returns the [ThresholdConfig] for how far the shade should be expanded or collapsed such that it
- * actually completes the expansion or collapse after the user lifts their pointer.
- */
-private fun swipeableThresholds(
-    to: ShadeState,
-    swipeExpandThreshold: Dp,
-    swipeCollapseThreshold: Dp,
-): ThresholdConfig {
-    return FixedThreshold(
-        when (to) {
-            ShadeState.FullyExpanded -> swipeExpandThreshold
-            ShadeState.FullyCollapsed -> swipeCollapseThreshold
-        }
-    )
-}
-
-/** Enumerates the shade UI states for [SwipeableState]. */
-private enum class ShadeState {
-    FullyCollapsed,
-    FullyExpanded,
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index ca91b8a..dd71dfa 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -17,27 +17,56 @@
 
 package com.android.systemui.notifications.ui.composable
 
+import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+
+object Notifications {
+    object Elements {
+        val Notifications = ElementKey("Notifications")
+    }
+}
 
 @Composable
-fun Notifications(
+fun SceneScope.Notifications(
     modifier: Modifier = Modifier,
 ) {
     // TODO(b/272779828): implement.
     Column(
-        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+        modifier =
+            modifier
+                .element(key = Notifications.Elements.Notifications)
+                .fillMaxWidth()
+                .defaultMinSize(minHeight = 300.dp)
+                .clip(RoundedCornerShape(32.dp))
+                .background(MaterialTheme.colorScheme.surface)
+                .padding(16.dp),
     ) {
-        Text("Notifications", modifier = Modifier.align(Alignment.CenterHorizontally))
+        Text(
+            text = "Notifications",
+            modifier = Modifier.align(Alignment.CenterHorizontally),
+            style = MaterialTheme.typography.titleLarge,
+            color = MaterialTheme.colorScheme.onSurface,
+        )
         Spacer(modifier = Modifier.weight(1f))
-        Text("Shelf", modifier = Modifier.align(Alignment.CenterHorizontally))
+        Text(
+            text = "Shelf",
+            modifier = Modifier.align(Alignment.CenterHorizontally),
+            style = MaterialTheme.typography.titleSmall,
+            color = MaterialTheme.colorScheme.onSurface,
+        )
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
index f88fc21..68f010e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -20,7 +20,6 @@
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
@@ -28,9 +27,9 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.Divider
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
@@ -39,16 +38,14 @@
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.R
 import com.android.systemui.compose.modifiers.sysuiResTag
@@ -70,15 +67,6 @@
     val priorityTiles by viewModel.priorityTiles.collectAsState()
     val recentTiles by viewModel.recentTiles.collectAsState()
 
-    // Make sure to refresh the tiles/conversations when the lifecycle is resumed, so that it
-    // updates them when going back to the Activity after leaving it.
-    val lifecycleOwner = LocalLifecycleOwner.current
-    LaunchedEffect(lifecycleOwner, viewModel) {
-        lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
-            viewModel.onTileRefreshRequested()
-        }
-    }
-
     // Call [onResult] this activity when the ViewModel tells us so.
     LaunchedEffect(viewModel.result) {
         viewModel.result.collect { result ->
@@ -134,10 +122,11 @@
             )
         }
 
-        LazyColumn(
-            Modifier.fillMaxWidth().sysuiResTag("scroll_view"),
-            contentPadding =
-                PaddingValues(
+        Column(
+            Modifier.fillMaxWidth()
+                .sysuiResTag("scroll_view")
+                .verticalScroll(rememberScrollState())
+                .padding(
                     top = 16.dp,
                     bottom = PeopleSpacePadding,
                     start = 8.dp,
@@ -151,7 +140,7 @@
 
             if (recentTiles.isNotEmpty()) {
                 if (hasPriorityConversations) {
-                    item { Spacer(Modifier.height(35.dp)) }
+                    Spacer(Modifier.height(35.dp))
                 }
 
                 ConversationList(R.string.recent_conversations, recentTiles, onTileClicked)
@@ -160,33 +149,30 @@
     }
 }
 
-private fun LazyListScope.ConversationList(
+@Composable
+private fun ConversationList(
     @StringRes headerTextResource: Int,
     tiles: List<PeopleTileViewModel>,
     onTileClicked: (PeopleTileViewModel) -> Unit
 ) {
-    item {
-        Text(
-            stringResource(headerTextResource),
-            Modifier.padding(start = 16.dp),
-            style = MaterialTheme.typography.labelLarge,
-            color = LocalAndroidColorScheme.current.deprecated.colorAccentPrimaryVariant,
-        )
+    Text(
+        stringResource(headerTextResource),
+        Modifier.padding(start = 16.dp),
+        style = MaterialTheme.typography.labelLarge,
+        color = LocalAndroidColorScheme.current.deprecated.colorAccentPrimaryVariant,
+    )
 
-        Spacer(Modifier.height(10.dp))
-    }
+    Spacer(Modifier.height(10.dp))
 
     tiles.forEachIndexed { index, tile ->
         if (index > 0) {
-            item {
-                Divider(
-                    color = LocalAndroidColorScheme.current.deprecated.colorBackground,
-                    thickness = 2.dp,
-                )
-            }
+            Divider(
+                color = LocalAndroidColorScheme.current.deprecated.colorBackground,
+                thickness = 2.dp,
+            )
         }
 
-        item(tile.key.toString()) {
+        key(tile.key.toString()) {
             Tile(
                 tile,
                 onTileClicked,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 75bf281..13acde2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -121,8 +121,8 @@
         }
     }
 
-    val backgroundColor = colorAttr(R.attr.underSurfaceColor)
-    val contentColor = LocalAndroidColorScheme.current.deprecated.textColorPrimary
+    val backgroundColor = colorAttr(R.attr.underSurface)
+    val contentColor = LocalAndroidColorScheme.current.onSurface
     val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius)
     val backgroundModifier =
         remember(
@@ -268,7 +268,7 @@
     val interactionSource = remember { MutableInteractionSource() }
 
     Expandable(
-        color = colorAttr(R.attr.offStateColor),
+        color = colorAttr(R.attr.shadeInactive),
         shape = CircleShape,
         onClick = onClick,
         interactionSource = interactionSource,
@@ -287,7 +287,7 @@
                     number.toString(),
                     modifier = Modifier.align(Alignment.Center),
                     style = MaterialTheme.typography.bodyLarge,
-                    color = LocalAndroidColorScheme.current.deprecated.textColorPrimary,
+                    color = colorAttr(R.attr.onShadeInactiveVariant),
                     // TODO(b/242040009): This should only use a standard text style instead and
                     // should not override the text size.
                     fontSize = 18.sp,
@@ -305,7 +305,7 @@
 @Composable
 private fun NewChangesDot(modifier: Modifier = Modifier) {
     val contentDescription = stringResource(R.string.fgs_dot_content_description)
-    val color = LocalAndroidColorScheme.current.deprecated.colorAccentTertiary
+    val color = LocalAndroidColorScheme.current.tertiary
 
     Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) {
         drawCircle(color)
@@ -323,10 +323,9 @@
 ) {
     Expandable(
         shape = CircleShape,
-        color = colorAttr(R.attr.underSurfaceColor),
-        contentColor = LocalAndroidColorScheme.current.deprecated.textColorSecondary,
-        borderStroke =
-            BorderStroke(1.dp, LocalAndroidColorScheme.current.deprecated.colorBackground),
+        color = colorAttr(R.attr.underSurface),
+        contentColor = LocalAndroidColorScheme.current.onSurfaceVariant,
+        borderStroke = BorderStroke(1.dp, colorAttr(R.attr.onShadeActive)),
         modifier = modifier.padding(horizontal = 4.dp),
         onClick = onClick,
     ) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
index 665d6dd..c84a5e9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
@@ -17,27 +17,63 @@
 
 package com.android.systemui.qs.footer.ui.compose
 
+import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+
+object QuickSettings {
+    object Elements {
+        // TODO RENAME
+        val Content = ElementKey("QuickSettingsContent")
+        val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid")
+        val FooterActions = ElementKey("QuickSettingsFooterActions")
+    }
+}
 
 @Composable
-fun QuickSettings(
+fun SceneScope.QuickSettings(
     modifier: Modifier = Modifier,
 ) {
     // TODO(b/272780058): implement.
     Column(
-        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+        modifier =
+            modifier
+                .element(QuickSettings.Elements.Content)
+                .fillMaxWidth()
+                .defaultMinSize(minHeight = 300.dp)
+                .clip(RoundedCornerShape(32.dp))
+                .background(MaterialTheme.colorScheme.primary)
+                .padding(16.dp),
     ) {
-        Text("Quick settings", modifier = Modifier.align(Alignment.CenterHorizontally))
+        Text(
+            text = "Quick settings grid",
+            modifier =
+                Modifier.element(QuickSettings.Elements.CollapsedGrid)
+                    .align(Alignment.CenterHorizontally),
+            style = MaterialTheme.typography.titleLarge,
+            color = MaterialTheme.colorScheme.onPrimary,
+        )
         Spacer(modifier = Modifier.weight(1f))
-        Text("QS footer actions", modifier = Modifier.align(Alignment.CenterHorizontally))
+        Text(
+            text = "QS footer actions",
+            modifier =
+                Modifier.element(QuickSettings.Elements.FooterActions)
+                    .align(Alignment.CenterHorizontally),
+            style = MaterialTheme.typography.titleSmall,
+            color = MaterialTheme.colorScheme.onPrimary,
+        )
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 30b80ca..e5cd439 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -16,36 +16,38 @@
 
 package com.android.systemui.qs.ui.composable
 
-import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.material3.Button
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.scene.shared.model.UserAction
 import com.android.systemui.scene.ui.composable.ComposableScene
+import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 /** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
-class QuickSettingsScene(
+@SysUISingleton
+class QuickSettingsScene
+@Inject
+constructor(
     private val viewModel: QuickSettingsSceneViewModel,
 ) : ComposableScene {
     override val key = SceneKey.QuickSettings
 
-    override fun destinationScenes(
-        containerName: String,
-    ): StateFlow<Map<UserAction, SceneModel>> =
+    override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
         MutableStateFlow<Map<UserAction, SceneModel>>(
                 mapOf(
                     UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade),
@@ -54,8 +56,7 @@
             .asStateFlow()
 
     @Composable
-    override fun Content(
-        containerName: String,
+    override fun SceneScope.Content(
         modifier: Modifier,
     ) {
         QuickSettingsScene(
@@ -66,23 +67,18 @@
 }
 
 @Composable
-private fun QuickSettingsScene(
+private fun SceneScope.QuickSettingsScene(
     viewModel: QuickSettingsSceneViewModel,
     modifier: Modifier = Modifier,
 ) {
     // TODO(b/280887232): implement the real UI.
 
-    Box(modifier = modifier) {
-        Column(
-            horizontalAlignment = Alignment.CenterHorizontally,
-            modifier = Modifier.align(Alignment.Center)
-        ) {
-            Text("Quick settings", style = MaterialTheme.typography.headlineMedium)
-            Row(
-                horizontalArrangement = Arrangement.spacedBy(8.dp),
-            ) {
-                Button(onClick = { viewModel.onContentClicked() }) { Text("Open some content") }
-            }
-        }
+    Box(
+        modifier
+            .fillMaxSize()
+            .clickable(onClick = { viewModel.onContentClicked() })
+            .padding(horizontal = 16.dp, vertical = 48.dp)
+    ) {
+        QuickSettings(modifier = Modifier.fillMaxHeight())
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt
index 6f3363e..3da6a02 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt
@@ -18,9 +18,10 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.scene.shared.model.Scene
 
 /** Compose-capable extension of [Scene]. */
 interface ComposableScene : Scene {
-    @Composable fun Content(containerName: String, modifier: Modifier)
+    @Composable fun SceneScope.Content(modifier: Modifier)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 0a4da1d..774c409 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -23,10 +23,13 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.scene.shared.model.UserAction
+import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -35,12 +38,11 @@
  * "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
  * content from the scene framework.
  */
-class GoneScene : ComposableScene {
+@SysUISingleton
+class GoneScene @Inject constructor() : ComposableScene {
     override val key = SceneKey.Gone
 
-    override fun destinationScenes(
-        containerName: String,
-    ): StateFlow<Map<UserAction, SceneModel>> =
+    override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
         MutableStateFlow<Map<UserAction, SceneModel>>(
                 mapOf(
                     UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade),
@@ -49,8 +51,7 @@
             .asStateFlow()
 
     @Composable
-    override fun Content(
-        containerName: String,
+    override fun SceneScope.Content(
         modifier: Modifier,
     ) {
         /*
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 5e07610..c865070 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -14,32 +14,30 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalAnimationApi::class)
-
 package com.android.systemui.scene.ui.composable
 
-import androidx.activity.compose.BackHandler
-import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.ExperimentalAnimationApi
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Button
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.ObservableTransitionState as SceneTransitionObservableTransitionState
+import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.SceneTransitionLayoutState
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.UserAction as SceneTransitionUserAction
+import com.android.compose.animation.scene.observableTransitionState
 import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.scene.shared.model.UserAction
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import java.util.Locale
+import kotlinx.coroutines.flow.map
 
 /**
  * Renders a container of a collection of "scenes" that the user can switch between using certain
@@ -64,81 +62,92 @@
     sceneByKey: Map<SceneKey, ComposableScene>,
     modifier: Modifier = Modifier,
 ) {
-    val currentScene: SceneModel by viewModel.currentScene.collectAsState()
+    val currentSceneModel: SceneModel by viewModel.currentScene.collectAsState()
+    val currentSceneKey = currentSceneModel.key
+    val currentScene = checkNotNull(sceneByKey[currentSceneKey])
+    val currentDestinations: Map<UserAction, SceneModel> by
+        currentScene.destinationScenes().collectAsState()
+    val state = remember { SceneTransitionLayoutState(currentSceneKey.toTransitionSceneKey()) }
 
-    AnimatedContent(
-        targetState = currentScene.key,
-        label = "scene container",
-        modifier = modifier,
-    ) { currentSceneKey ->
-        sceneByKey.forEach { (key, composableScene) ->
-            if (key == currentSceneKey) {
-                Scene(
-                    scene = composableScene,
-                    containerName = viewModel.containerName,
-                    onSceneChanged = viewModel::setCurrentScene,
-                    modifier = Modifier.fillMaxSize(),
-                )
-            }
-        }
+    DisposableEffect(viewModel, state) {
+        viewModel.setTransitionState(state.observableTransitionState().map { it.toModel() })
+        onDispose { viewModel.setTransitionState(null) }
     }
-}
 
-/** Renders the given [ComposableScene]. */
-@Composable
-private fun Scene(
-    scene: ComposableScene,
-    containerName: String,
-    onSceneChanged: (SceneModel) -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    // TODO(b/280880714): replace with the real UI and make sure to call onTransitionProgress.
-    Box(modifier) {
-        Column(
-            horizontalAlignment = Alignment.CenterHorizontally,
-            modifier = Modifier.align(Alignment.Center),
-        ) {
-            scene.Content(
-                containerName = containerName,
-                modifier = Modifier,
-            )
-
-            val destinationScenes: Map<UserAction, SceneModel> by
-                scene.destinationScenes(containerName).collectAsState()
-            val swipeLeftDestinationScene = destinationScenes[UserAction.Swipe(Direction.LEFT)]
-            val swipeUpDestinationScene = destinationScenes[UserAction.Swipe(Direction.UP)]
-            val swipeRightDestinationScene = destinationScenes[UserAction.Swipe(Direction.RIGHT)]
-            val swipeDownDestinationScene = destinationScenes[UserAction.Swipe(Direction.DOWN)]
-            val backDestinationScene = destinationScenes[UserAction.Back]
-
-            Row(
-                horizontalArrangement = Arrangement.spacedBy(8.dp),
-            ) {
-                DirectionalButton(Direction.LEFT, swipeLeftDestinationScene, onSceneChanged)
-                DirectionalButton(Direction.UP, swipeUpDestinationScene, onSceneChanged)
-                DirectionalButton(Direction.RIGHT, swipeRightDestinationScene, onSceneChanged)
-                DirectionalButton(Direction.DOWN, swipeDownDestinationScene, onSceneChanged)
-            }
-
-            if (backDestinationScene != null) {
-                BackHandler { onSceneChanged.invoke(backDestinationScene) }
-            }
-        }
-    }
-}
-
-@Composable
-private fun DirectionalButton(
-    direction: Direction,
-    destinationScene: SceneModel?,
-    onSceneChanged: (SceneModel) -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    Button(
-        onClick = { destinationScene?.let { onSceneChanged.invoke(it) } },
-        enabled = destinationScene != null,
-        modifier = modifier,
+    SceneTransitionLayout(
+        currentScene = currentSceneKey.toTransitionSceneKey(),
+        onChangeScene = viewModel::onSceneChanged,
+        transitions = SceneContainerTransitions,
+        state = state,
+        modifier = modifier.fillMaxSize(),
     ) {
-        Text(direction.name.lowercase(Locale.getDefault()))
+        sceneByKey.forEach { (sceneKey, composableScene) ->
+            scene(
+                key = sceneKey.toTransitionSceneKey(),
+                userActions =
+                    if (sceneKey == currentSceneKey) {
+                            currentDestinations
+                        } else {
+                            composableScene.destinationScenes().value
+                        }
+                        .map { (userAction, destinationSceneModel) ->
+                            toTransitionModels(userAction, destinationSceneModel)
+                        }
+                        .toMap(),
+            ) {
+                with(composableScene) {
+                    this@scene.Content(
+                        modifier =
+                            Modifier.element(sceneKey.toTransitionSceneKey().rootElementKey)
+                                .fillMaxSize(),
+                    )
+                }
+            }
+        }
     }
 }
+
+// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
+private fun SceneTransitionObservableTransitionState.toModel(): ObservableTransitionState {
+    return when (this) {
+        is SceneTransitionObservableTransitionState.Idle ->
+            ObservableTransitionState.Idle(scene.toModel().key)
+        is SceneTransitionObservableTransitionState.Transition ->
+            ObservableTransitionState.Transition(
+                fromScene = fromScene.toModel().key,
+                toScene = toScene.toModel().key,
+                progress = progress,
+            )
+    }
+}
+
+// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
+private fun toTransitionModels(
+    userAction: UserAction,
+    sceneModel: SceneModel,
+): Pair<SceneTransitionUserAction, SceneTransitionSceneKey> {
+    return userAction.toTransitionUserAction() to sceneModel.key.toTransitionSceneKey()
+}
+
+// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
+private fun SceneTransitionSceneKey.toModel(): SceneModel {
+    return SceneModel(key = identity as SceneKey)
+}
+
+// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
+private fun UserAction.toTransitionUserAction(): SceneTransitionUserAction {
+    return when (this) {
+        is UserAction.Swipe ->
+            when (this.direction) {
+                Direction.LEFT -> Swipe.Left
+                Direction.UP -> Swipe.Up
+                Direction.RIGHT -> Swipe.Right
+                Direction.DOWN -> Swipe.Down
+            }
+        is UserAction.Back -> Back
+    }
+}
+
+private fun SceneContainerViewModel.onSceneChanged(sceneKey: SceneTransitionSceneKey) {
+    onSceneChanged(sceneKey.toModel())
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
new file mode 100644
index 0000000..404bf81
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -0,0 +1,34 @@
+package com.android.systemui.scene.ui.composable
+
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition
+import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
+import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToBouncerTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition
+
+/**
+ * Comprehensive definition of all transitions between scenes in [SceneContainer].
+ *
+ * Transitions are automatically reversible, so define only one transition per scene pair. By
+ * convention, use the more common transition direction when defining the pair order, e.g.
+ * Lockscreen to Bouncer rather than Bouncer to Lockscreen.
+ *
+ * The actual transition DSL must be placed in a separate file under the package
+ * [com.android.systemui.scene.ui.composable.transitions].
+ *
+ * Please keep the list sorted alphabetically.
+ */
+val SceneContainerTransitions = transitions {
+    from(Bouncer, to = Gone) { bouncerToGoneTransition() }
+    from(Gone, to = Shade) { goneToShadeTransition() }
+    from(Gone, to = QuickSettings) { goneToQuickSettingsTransition() }
+    from(Lockscreen, to = Bouncer) { lockscreenToBouncerTransition() }
+    from(Lockscreen, to = Shade) { lockscreenToShadeTransition() }
+    from(Lockscreen, to = QuickSettings) { lockscreenToQuickSettingsTransition() }
+    from(Lockscreen, to = Gone) { lockscreenToGoneTransition() }
+    from(Shade, to = QuickSettings) { shadeToQuickSettingsTransition() }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt
new file mode 100644
index 0000000..8d0d705
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.scene.ui.composable
+
+import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey
+import com.android.systemui.scene.shared.model.SceneKey
+
+val Lockscreen = SceneKey.Lockscreen.toTransitionSceneKey()
+val Bouncer = SceneKey.Bouncer.toTransitionSceneKey()
+val Shade = SceneKey.Shade.toTransitionSceneKey()
+val QuickSettings = SceneKey.QuickSettings.toTransitionSceneKey()
+val Gone = SceneKey.Gone.toTransitionSceneKey()
+
+// TODO(b/293899074): Remove this file once we can use the scene keys from SceneTransitionLayout.
+fun SceneKey.toTransitionSceneKey(): SceneTransitionSceneKey {
+    return SceneTransitionSceneKey(name = toString(), identity = this)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt
new file mode 100644
index 0000000..1a9face
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.Bouncer
+
+fun TransitionBuilder.bouncerToGoneTransition() {
+    spec = tween(durationMillis = 500)
+
+    fade(Bouncer.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
new file mode 100644
index 0000000..291617f
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
@@ -0,0 +1,12 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.QuickSettings
+
+fun TransitionBuilder.goneToQuickSettingsTransition() {
+    spec = tween(durationMillis = 500)
+
+    translate(QuickSettings.rootElementKey, Edge.Top, true)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
new file mode 100644
index 0000000..45df2b1
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -0,0 +1,12 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.Shade
+
+fun TransitionBuilder.goneToShadeTransition() {
+    spec = tween(durationMillis = 500)
+
+    translate(Shade.rootElementKey, Edge.Top, true)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
new file mode 100644
index 0000000..1fee874
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
@@ -0,0 +1,14 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.bouncer.ui.composable.Bouncer
+
+fun TransitionBuilder.lockscreenToBouncerTransition() {
+    spec = tween(durationMillis = 500)
+
+    translate(Bouncer.Elements.Content, y = 300.dp)
+    fractionRange(end = 0.5f) { fade(Bouncer.Elements.Background) }
+    fractionRange(start = 0.5f) { fade(Bouncer.Elements.Content) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt
new file mode 100644
index 0000000..da6306d
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.Lockscreen
+
+fun TransitionBuilder.lockscreenToGoneTransition() {
+    spec = tween(durationMillis = 500)
+
+    fade(Lockscreen.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
new file mode 100644
index 0000000..e63bc4e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
@@ -0,0 +1,12 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.QuickSettings
+
+fun TransitionBuilder.lockscreenToQuickSettingsTransition() {
+    spec = tween(durationMillis = 500)
+
+    translate(QuickSettings.rootElementKey, Edge.Top, true)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
new file mode 100644
index 0000000..7ecfb62
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
@@ -0,0 +1,24 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.shade.ui.composable.Shade
+
+fun TransitionBuilder.lockscreenToShadeTransition() {
+    spec = tween(durationMillis = 500)
+
+    punchHole(Shade.Elements.QuickSettings, bounds = Shade.Elements.Scrim, Shade.Shapes.Scrim)
+    translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
+    fractionRange(end = 0.5f) {
+        fade(Shade.Elements.ScrimBackground)
+        translate(
+            QuickSettings.Elements.CollapsedGrid,
+            Edge.Top,
+            startsOutsideLayoutBounds = false,
+        )
+    }
+    fractionRange(start = 0.5f) { fade(Notifications.Elements.Notifications) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
new file mode 100644
index 0000000..21a10b1
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -0,0 +1,14 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
+
+fun TransitionBuilder.shadeToQuickSettingsTransition() {
+    spec = tween(durationMillis = 500)
+
+    translate(Notifications.Elements.Notifications, Edge.Bottom)
+    timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 20e1751..f985aa2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -16,40 +16,71 @@
 
 package com.android.systemui.shade.ui.composable
 
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.material3.Button
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.scene.shared.model.UserAction
 import com.android.systemui.scene.ui.composable.ComposableScene
 import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
+object Shade {
+    object Elements {
+        val QuickSettings = ElementKey("ShadeQuickSettings")
+        val Scrim = ElementKey("ShadeScrim")
+        val ScrimBackground = ElementKey("ShadeScrimBackground")
+    }
+
+    object Dimensions {
+        val ScrimCornerSize = 32.dp
+    }
+
+    object Shapes {
+        val Scrim =
+            RoundedCornerShape(
+                topStart = Dimensions.ScrimCornerSize,
+                topEnd = Dimensions.ScrimCornerSize,
+            )
+    }
+}
+
 /** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
-class ShadeScene(
+@SysUISingleton
+class ShadeScene
+@Inject
+constructor(
     @Application private val applicationScope: CoroutineScope,
     private val viewModel: ShadeSceneViewModel,
 ) : ComposableScene {
     override val key = SceneKey.Shade
 
-    override fun destinationScenes(
-        containerName: String,
-    ): StateFlow<Map<UserAction, SceneModel>> =
+    override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
         viewModel.upDestinationSceneKey
             .map { sceneKey -> destinationScenes(up = sceneKey) }
             .stateIn(
@@ -59,15 +90,9 @@
             )
 
     @Composable
-    override fun Content(
-        containerName: String,
+    override fun SceneScope.Content(
         modifier: Modifier,
-    ) {
-        ShadeScene(
-            viewModel = viewModel,
-            modifier = modifier,
-        )
-    }
+    ) = ShadeScene(viewModel, modifier)
 
     private fun destinationScenes(
         up: SceneKey,
@@ -80,27 +105,28 @@
 }
 
 @Composable
-private fun ShadeScene(
+private fun SceneScope.ShadeScene(
     viewModel: ShadeSceneViewModel,
     modifier: Modifier = Modifier,
 ) {
-    // TODO(b/280887022): implement the real UI.
+    Box(modifier.element(Shade.Elements.Scrim)) {
+        Spacer(
+            modifier =
+                Modifier.element(Shade.Elements.ScrimBackground)
+                    .fillMaxSize()
+                    .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
+        )
 
-    Box(modifier = modifier) {
         Column(
             horizontalAlignment = Alignment.CenterHorizontally,
-            modifier = Modifier.align(Alignment.Center)
+            verticalArrangement = Arrangement.spacedBy(16.dp),
+            modifier =
+                Modifier.fillMaxSize()
+                    .clickable(onClick = { viewModel.onContentClicked() })
+                    .padding(horizontal = 16.dp, vertical = 48.dp)
         ) {
-            Text("Shade", style = MaterialTheme.typography.headlineMedium)
-            Row(
-                horizontalArrangement = Arrangement.spacedBy(8.dp),
-            ) {
-                Button(
-                    onClick = { viewModel.onContentClicked() },
-                ) {
-                    Text("Open some content")
-                }
-            }
+            QuickSettings(modifier = Modifier.height(160.dp))
+            Notifications(modifier = Modifier.weight(1f))
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/tests/AndroidManifest.xml b/packages/SystemUI/compose/features/tests/AndroidManifest.xml
index 2f41ea9..8fe9656 100644
--- a/packages/SystemUI/compose/features/tests/AndroidManifest.xml
+++ b/packages/SystemUI/compose/features/tests/AndroidManifest.xml
@@ -40,11 +40,6 @@
             android:enabled="false"
             tools:replace="android:authorities"
             tools:node="remove" />
-        <provider android:name="com.android.keyguard.clock.ClockOptionsProvider"
-            android:authorities="com.android.systemui.test.keyguard.clock.disabled"
-            android:enabled="false"
-            tools:replace="android:authorities"
-            tools:node="remove" />
         <provider android:name="com.android.systemui.people.PeopleProvider"
             android:authorities="com.android.systemui.test.people.disabled"
             android:enabled="false"
diff --git a/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt b/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt
deleted file mode 100644
index 1c2e8fa..0000000
--- a/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt
+++ /dev/null
@@ -1,46 +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
-
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithText
-import androidx.compose.ui.test.performClick
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class ExampleFeatureTest {
-    @get:Rule val composeRule = createComposeRule()
-
-    @Test
-    fun testProvidedTextIsDisplayed() {
-        composeRule.setContent { ExampleFeature("foo") }
-
-        composeRule.onNodeWithText("foo").assertIsDisplayed()
-    }
-
-    @Test
-    fun testCountIsIncreasedWhenClicking() {
-        composeRule.setContent { ExampleFeature("foo") }
-
-        composeRule.onNodeWithText("I was clicked 0 times.").assertIsDisplayed().performClick()
-        composeRule.onNodeWithText("I was clicked 1 times.").assertIsDisplayed()
-    }
-}
diff --git a/packages/SystemUI/compose/testing/Android.bp b/packages/SystemUI/compose/testing/Android.bp
deleted file mode 100644
index 555f42e..0000000
--- a/packages/SystemUI/compose/testing/Android.bp
+++ /dev/null
@@ -1,43 +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 {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_library {
-    name: "SystemUIComposeTesting",
-    manifest: "AndroidManifest.xml",
-
-    srcs: [
-        "src/**/*.kt",
-    ],
-
-    static_libs: [
-        "PlatformComposeCore",
-        "SystemUIScreenshotLib",
-
-        "androidx.compose.runtime_runtime",
-        "androidx.compose.material3_material3",
-        "androidx.compose.ui_ui-test-junit4",
-        "androidx.compose.ui_ui-test-manifest",
-    ],
-
-    kotlincflags: ["-Xjvm-default=all"],
-}
diff --git a/packages/SystemUI/compose/testing/AndroidManifest.xml b/packages/SystemUI/compose/testing/AndroidManifest.xml
deleted file mode 100644
index b1f7c3b..0000000
--- a/packages/SystemUI/compose/testing/AndroidManifest.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.systemui.testing.compose">
-    <application
-        android:appComponentFactory="androidx.core.app.AppComponentFactory"
-        tools:replace="android:appComponentFactory">
-    </application>
-</manifest>
diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
deleted file mode 100644
index cb9e53c..0000000
--- a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
+++ /dev/null
@@ -1,95 +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.testing.compose
-
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.ViewRootForTest
-import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.compose.ui.test.onRoot
-import com.android.compose.theme.PlatformTheme
-import com.android.systemui.testing.screenshot.ScreenshotActivity
-import com.android.systemui.testing.screenshot.SystemUIGoldenImagePathManager
-import com.android.systemui.testing.screenshot.UnitTestBitmapMatcher
-import com.android.systemui.testing.screenshot.drawIntoBitmap
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import platform.test.screenshot.DeviceEmulationRule
-import platform.test.screenshot.DeviceEmulationSpec
-import platform.test.screenshot.MaterialYouColorsRule
-import platform.test.screenshot.ScreenshotTestRule
-import platform.test.screenshot.getEmulatedDevicePathConfig
-
-/** A rule for Compose screenshot diff tests. */
-class ComposeScreenshotTestRule(
-    emulationSpec: DeviceEmulationSpec,
-    assetPathRelativeToBuildRoot: String
-) : TestRule {
-    private val colorsRule = MaterialYouColorsRule()
-    private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
-    private val screenshotRule =
-        ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(
-                getEmulatedDevicePathConfig(emulationSpec),
-                assetPathRelativeToBuildRoot
-            )
-        )
-    private val composeRule = createAndroidComposeRule<ScreenshotActivity>()
-    private val delegateRule =
-        RuleChain.outerRule(colorsRule)
-            .around(deviceEmulationRule)
-            .around(screenshotRule)
-            .around(composeRule)
-    private val matcher = UnitTestBitmapMatcher
-
-    override fun apply(base: Statement, description: Description): Statement {
-        return delegateRule.apply(base, description)
-    }
-
-    /**
-     * Compare [content] with the golden image identified by [goldenIdentifier] in the context of
-     * [testSpec].
-     */
-    fun screenshotTest(
-        goldenIdentifier: String,
-        content: @Composable () -> Unit,
-    ) {
-        // Make sure that the activity draws full screen and fits the whole display instead of the
-        // system bars.
-        val activity = composeRule.activity
-        activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) }
-
-        // Set the content using the AndroidComposeRule to make sure that the Activity is set up
-        // correctly.
-        composeRule.setContent {
-            PlatformTheme {
-                Surface(
-                    color = MaterialTheme.colorScheme.background,
-                ) {
-                    content()
-                }
-            }
-        }
-        composeRule.waitForIdle()
-
-        val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
-        screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
-    }
-}
diff --git a/packages/SystemUI/customization/res/values-h800dp/dimens.xml b/packages/SystemUI/customization/res/values-h800dp/dimens.xml
index 60afc8a..cb49945 100644
--- a/packages/SystemUI/customization/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/customization/res/values-h800dp/dimens.xml
@@ -17,4 +17,7 @@
 <resources>
     <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
     <dimen name="large_clock_text_size">200dp</dimen>
+
+    <!-- With the large clock, move up slightly from the center -->
+    <dimen name="keyguard_large_clock_top_margin">-112dp</dimen>
 </resources>
diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
index ba8f284..8eb8132 100644
--- a/packages/SystemUI/customization/res/values/dimens.xml
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -24,4 +24,12 @@
     <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
     <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
     <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
+
+    <!-- With the large clock, move up slightly from the center -->
+    <dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
+
+    <!-- additional offset for clock switch area items -->
+    <dimen name="small_clock_height">114dp</dimen>
+    <dimen name="small_clock_padding_top">28dp</dimen>
+    <dimen name="clock_padding_start">28dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 648ef03..b9d6643 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -33,8 +33,8 @@
 import com.android.systemui.animation.GlyphCallback
 import com.android.systemui.animation.TextAnimator
 import com.android.systemui.customization.R
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
 import java.io.PrintWriter
 import java.util.Calendar
 import java.util.Locale
@@ -51,7 +51,12 @@
     defStyleAttr: Int = 0,
     defStyleRes: Int = 0
 ) : TextView(context, attrs, defStyleAttr, defStyleRes) {
-    var logBuffer: LogBuffer? = null
+    var messageBuffer: MessageBuffer? = null
+        set(value) {
+            logger = if (value != null) Logger(value, TAG) else null
+        }
+
+    private var logger: Logger? = null
 
     private val time = Calendar.getInstance()
 
@@ -129,7 +134,7 @@
 
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
-        logBuffer?.log(TAG, DEBUG, "onAttachedToWindow")
+        logger?.d("onAttachedToWindow")
         refreshFormat()
     }
 
@@ -145,39 +150,32 @@
         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" }
-        )
+        logger?.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
         // 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" }
-            )
+            logger?.d({ "refreshTime: done setting new time text to: $str1" }) {
+                str1 = formattedText?.toString()
+            }
             // 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")
+                logger?.d("refreshTime: done updating textAnimator layout")
             }
             requestLayout()
-            logBuffer?.log(TAG, DEBUG, "refreshTime: after requestLayout")
+            logger?.d("refreshTime: after requestLayout")
         }
     }
 
     fun onTimeZoneChanged(timeZone: TimeZone?) {
         time.timeZone = timeZone
         refreshFormat()
-        logBuffer?.log(TAG, DEBUG,
-                { str1 = timeZone?.toString() },
-                { "onTimeZoneChanged newTimeZone=$str1" }
-        )
+        logger?.d({ "onTimeZoneChanged newTimeZone=$str1" }) { str1 = timeZone?.toString() }
     }
 
     @SuppressLint("DrawAllocation")
@@ -191,7 +189,7 @@
         } else {
             animator.updateLayout(layout)
         }
-        logBuffer?.log(TAG, DEBUG, "onMeasure")
+        logger?.d("onMeasure")
     }
 
     override fun onDraw(canvas: Canvas) {
@@ -203,12 +201,12 @@
         } else {
             super.onDraw(canvas)
         }
-        logBuffer?.log(TAG, DEBUG, "onDraw")
+        logger?.d("onDraw")
     }
 
     override fun invalidate() {
         super.invalidate()
-        logBuffer?.log(TAG, DEBUG, "invalidate")
+        logger?.d("invalidate")
     }
 
     override fun onTextChanged(
@@ -218,10 +216,7 @@
             lengthAfter: Int
     ) {
         super.onTextChanged(text, start, lengthBefore, lengthAfter)
-        logBuffer?.log(TAG, DEBUG,
-                { str1 = text.toString() },
-                { "onTextChanged text=$str1" }
-        )
+        logger?.d({ "onTextChanged text=$str1" }) { str1 = text.toString() }
     }
 
     fun setLineSpacingScale(scale: Float) {
@@ -235,7 +230,7 @@
     }
 
     fun animateColorChange() {
-        logBuffer?.log(TAG, DEBUG, "animateColorChange")
+        logger?.d("animateColorChange")
         setTextStyle(
             weight = lockScreenWeight,
             textSize = -1f,
@@ -257,7 +252,7 @@
     }
 
     fun animateAppearOnLockscreen() {
-        logBuffer?.log(TAG, DEBUG, "animateAppearOnLockscreen")
+        logger?.d("animateAppearOnLockscreen")
         setTextStyle(
             weight = dozingWeight,
             textSize = -1f,
@@ -283,7 +278,7 @@
         if (isAnimationEnabled && textAnimator == null) {
             return
         }
-        logBuffer?.log(TAG, DEBUG, "animateFoldAppear")
+        logger?.d("animateFoldAppear")
         setTextStyle(
             weight = lockScreenWeightInternal,
             textSize = -1f,
@@ -310,7 +305,7 @@
             // Skip charge animation if dozing animation is already playing.
             return
         }
-        logBuffer?.log(TAG, DEBUG, "animateCharge")
+        logger?.d("animateCharge")
         val startAnimPhase2 = Runnable {
             setTextStyle(
                 weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -334,7 +329,7 @@
     }
 
     fun animateDoze(isDozing: Boolean, animate: Boolean) {
-        logBuffer?.log(TAG, DEBUG, "animateDoze")
+        logger?.d("animateDoze")
         setTextStyle(
             weight = if (isDozing) dozingWeight else lockScreenWeight,
             textSize = -1f,
@@ -453,10 +448,7 @@
             isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
             else -> DOUBLE_LINE_FORMAT_12_HOUR
         }
-        logBuffer?.log(TAG, DEBUG,
-                { str1 = format?.toString() },
-                { "refreshFormat format=$str1" }
-        )
+        logger?.d({ "refreshFormat format=$str1" }) { str1 = format?.toString() }
 
         descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
         refreshTime()
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 702cc05..d65edae 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -23,12 +23,13 @@
 import android.provider.Settings
 import android.util.Log
 import androidx.annotation.OpenForTesting
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogMessage
 import com.android.systemui.log.LogMessageImpl
-import com.android.systemui.log.MessageInitializer
-import com.android.systemui.log.MessagePrinter
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.log.core.MessageInitializer
+import com.android.systemui.log.core.MessagePrinter
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockId
 import com.android.systemui.plugins.ClockMetadata
@@ -56,6 +57,7 @@
         "com.android.systemui.falcon.four" to listOf(ClockMetadata("DIGITAL_CLOCK_GROWTH")),
         "com.android.systemui.falcon.five" to listOf(ClockMetadata("DIGITAL_CLOCK_HANDWRITTEN")),
         "com.android.systemui.falcon.six" to listOf(ClockMetadata("DIGITAL_CLOCK_INFLATE")),
+        "com.android.systemui.falcon.seven" to listOf(ClockMetadata("DIGITAL_CLOCK_METRO")),
         "com.android.systemui.falcon.eight" to listOf(ClockMetadata("DIGITAL_CLOCK_NUMBEROVERLAP")),
         "com.android.systemui.falcon.nine" to listOf(ClockMetadata("DIGITAL_CLOCK_WEATHER")),
     )
@@ -74,7 +76,7 @@
 
 private val TMP_MESSAGE: LogMessage by lazy { LogMessageImpl.Factory.create() }
 
-private inline fun LogBuffer?.tryLog(
+private inline fun Logger?.tryLog(
     tag: String,
     level: LogLevel,
     messageInitializer: MessageInitializer,
@@ -83,7 +85,7 @@
 ) {
     if (this != null) {
         // Wrap messagePrinter to convert it from crossinline to noinline
-        this.log(tag, level, messageInitializer, messagePrinter, ex)
+        this.log(level, messagePrinter, ex, messageInitializer)
     } else {
         messageInitializer(TMP_MESSAGE)
         val msg = messagePrinter(TMP_MESSAGE)
@@ -109,9 +111,10 @@
     val handleAllUsers: Boolean,
     defaultClockProvider: ClockProvider,
     val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
-    val logBuffer: LogBuffer? = null,
+    messageBuffer: MessageBuffer? = null,
     val keepAllLoaded: Boolean,
     subTag: String,
+    var isTransitClockEnabled: Boolean = false,
 ) {
     private val TAG = "${ClockRegistry::class.simpleName} ($subTag)"
     interface ClockChangeListener {
@@ -122,6 +125,7 @@
         fun onAvailableClocksChanged() {}
     }
 
+    private val logger: Logger? = if (messageBuffer != null) Logger(messageBuffer, TAG) else null
     private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>()
     private val clockChangeListeners = mutableListOf<ClockChangeListener>()
     private val settingObserver =
@@ -148,7 +152,7 @@
 
                 val knownClocks = KNOWN_PLUGINS.get(manager.getPackage())
                 if (knownClocks == null) {
-                    logBuffer.tryLog(
+                    logger.tryLog(
                         TAG,
                         LogLevel.WARNING,
                         { str1 = manager.getPackage() },
@@ -157,7 +161,7 @@
                     return true
                 }
 
-                logBuffer.tryLog(
+                logger.tryLog(
                     TAG,
                     LogLevel.INFO,
                     { str1 = manager.getPackage() },
@@ -174,7 +178,7 @@
                         }
 
                     if (manager != info.manager) {
-                        logBuffer.tryLog(
+                        logger.tryLog(
                             TAG,
                             LogLevel.ERROR,
                             { str1 = id },
@@ -203,6 +207,10 @@
                 var isClockListChanged = false
                 for (clock in plugin.getClocks()) {
                     val id = clock.clockId
+                    if (!isTransitClockEnabled && id == "DIGITAL_CLOCK_METRO") {
+                        continue
+                    }
+
                     val info =
                         availableClocks.concurrentGetOrPut(id, ClockInfo(clock, plugin, manager)) {
                             isClockListChanged = true
@@ -210,7 +218,7 @@
                         }
 
                     if (manager != info.manager) {
-                        logBuffer.tryLog(
+                        logger.tryLog(
                             TAG,
                             LogLevel.ERROR,
                             { str1 = id },
@@ -238,7 +246,7 @@
                     val id = clock.clockId
                     val info = availableClocks[id]
                     if (info?.manager != manager) {
-                        logBuffer.tryLog(
+                        logger.tryLog(
                             TAG,
                             LogLevel.ERROR,
                             { str1 = id },
@@ -313,7 +321,7 @@
 
                 ClockSettings.deserialize(json)
             } catch (ex: Exception) {
-                logBuffer.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex)
+                logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex)
                 null
             }
         settings = result
@@ -342,7 +350,7 @@
                 )
             }
         } catch (ex: Exception) {
-            logBuffer.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex)
+            logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex)
         }
         settings = value
     }
@@ -502,9 +510,9 @@
     }
 
     private fun onConnected(clockId: ClockId) {
-        logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" })
+        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" })
         if (currentClockId == clockId) {
-            logBuffer.tryLog(
+            logger.tryLog(
                 TAG,
                 LogLevel.INFO,
                 { str1 = clockId },
@@ -514,10 +522,10 @@
     }
 
     private fun onLoaded(clockId: ClockId) {
-        logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" })
+        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" })
 
         if (currentClockId == clockId) {
-            logBuffer.tryLog(
+            logger.tryLog(
                 TAG,
                 LogLevel.INFO,
                 { str1 = clockId },
@@ -528,10 +536,10 @@
     }
 
     private fun onUnloaded(clockId: ClockId) {
-        logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" })
+        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" })
 
         if (currentClockId == clockId) {
-            logBuffer.tryLog(
+            logger.tryLog(
                 TAG,
                 LogLevel.WARNING,
                 { str1 = clockId },
@@ -542,10 +550,10 @@
     }
 
     private fun onDisconnected(clockId: ClockId) {
-        logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" })
+        logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" })
 
         if (currentClockId == clockId) {
-            logBuffer.tryLog(
+            logger.tryLog(
                 TAG,
                 LogLevel.WARNING,
                 { str1 = clockId },
@@ -591,22 +599,17 @@
         if (isEnabled && clockId.isNotEmpty()) {
             val clock = createClock(clockId)
             if (clock != null) {
-                logBuffer.tryLog(
-                    TAG,
-                    LogLevel.INFO,
-                    { str1 = clockId },
-                    { "Rendering clock $str1" }
-                )
+                logger.tryLog(TAG, LogLevel.INFO, { str1 = clockId }, { "Rendering clock $str1" })
                 return clock
             } else if (availableClocks.containsKey(clockId)) {
-                logBuffer.tryLog(
+                logger.tryLog(
                     TAG,
                     LogLevel.WARNING,
                     { str1 = clockId },
                     { "Clock $str1 not loaded; using default" }
                 )
             } else {
-                logBuffer.tryLog(
+                logger.tryLog(
                     TAG,
                     LogLevel.ERROR,
                     { str1 = clockId },
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index e557c8e..e7a53e5b 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -24,7 +24,7 @@
 import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.customization.R
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.plugins.ClockAnimations
 import com.android.systemui.plugins.ClockConfig
 import com.android.systemui.plugins.ClockController
@@ -62,6 +62,7 @@
     private val burmeseLineSpacing =
         resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
     private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
+    protected var onSecondaryDisplay: Boolean = false
 
     override val events: DefaultClockEvents
     override val config = ClockConfig()
@@ -108,10 +109,10 @@
 
         override val config = ClockFaceConfig()
 
-        override var logBuffer: LogBuffer?
-            get() = view.logBuffer
+        override var messageBuffer: MessageBuffer?
+            get() = view.messageBuffer
             set(value) {
-                view.logBuffer = value
+                view.messageBuffer = value
             }
 
         override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f)
@@ -142,6 +143,11 @@
                     view.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSizePx)
                     recomputePadding(targetRegion)
                 }
+
+                override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {
+                    this@DefaultClockController.onSecondaryDisplay = onSecondaryDisplay
+                    recomputePadding(null)
+                }
             }
 
         open fun recomputePadding(targetRegion: Rect?) {}
@@ -182,13 +188,19 @@
         override fun recomputePadding(targetRegion: Rect?) {
             // We center the view within the targetRegion instead of within the parent
             // view by computing the difference and adding that to the padding.
-            val parent = view.parent
-            val yDiff =
-                if (targetRegion != null && parent is View && parent.isLaidOut())
-                    targetRegion.centerY() - parent.height / 2f
-                else 0f
             val lp = view.getLayoutParams() as FrameLayout.LayoutParams
-            lp.topMargin = (-0.5f * view.bottom + yDiff).toInt()
+            lp.topMargin =
+                if (onSecondaryDisplay) {
+                    // On the secondary display we don't want any additional top/bottom margin.
+                    0
+                } else {
+                    val parent = view.parent
+                    val yDiff =
+                        if (targetRegion != null && parent is View && parent.isLaidOut())
+                            targetRegion.centerY() - parent.height / 2f
+                        else 0f
+                    (-0.5f * view.bottom + yDiff).toInt()
+                }
             view.setLayoutParams(lp)
         }
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index b6d5ef3..92d2bd2 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -184,6 +184,19 @@
         /** Flag denoting AI Wallpapers are enabled in wallpaper picker. */
         const val FLAG_NAME_WALLPAPER_PICKER_UI_FOR_AIWP = "wallpaper_picker_ui_for_aiwp"
 
+        /** Flag denoting transit clock are enabled in wallpaper picker. */
+        const val FLAG_NAME_TRANSIT_CLOCK = "lockscreen_custom_transit_clock"
+
+        /** Flag denoting transit clock are enabled in wallpaper picker. */
+        const val FLAG_NAME_PAGE_TRANSITIONS = "wallpaper_picker_page_transitions"
+
+        /** Flag denoting adding apply button to wallpaper picker's grid preview page. */
+        const val FLAG_NAME_GRID_APPLY_BUTTON = "wallpaper_picker_grid_apply_button"
+
+        /** Flag denoting whether preview loading animation is enabled. */
+        const val FLAG_NAME_WALLPAPER_PICKER_PREVIEW_ANIMATION =
+            "wallpaper_picker_preview_animation"
+
         object Columns {
             /** String. Unique ID for the flag. */
             const val NAME = "name"
diff --git a/packages/SystemUI/docs/clock-plugins.md b/packages/SystemUI/docs/clock-plugins.md
index 2226d79..9cb115a 100644
--- a/packages/SystemUI/docs/clock-plugins.md
+++ b/packages/SystemUI/docs/clock-plugins.md
@@ -1,7 +1,7 @@
 # Clock Plugins
 
-The clock appearing on the lock screen and always on display (AOD) can be
-customized via the ClockProviderPlugin plugin interface.
+The clock appearing on the lock screen and always on display (AOD) can be customized via the
+ClockProviderPlugin plugin interface. The ClockPlugin interface has been removed.
 
 ## Lock screen integration
 The lockscreen code has two main components, a [clock customization library](../customization), and
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 18753fd..006fc09 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -422,9 +422,6 @@
 -packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallFlags.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/ConnectivitySlots.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -696,7 +693,6 @@
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt
index 6fc52536..a4f4e13 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.log
 
+import com.android.systemui.log.core.LogLevel
 import com.google.errorprone.annotations.CompileTimeConstant
 
 class ConstantStringsLoggerImpl(val buffer: LogBuffer, val tag: String) : ConstantStringsLogger {
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
index 2007e76..e0051f5 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
@@ -19,6 +19,11 @@
 import android.os.Trace
 import android.util.Log
 import com.android.systemui.common.buffer.RingBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.log.core.MessageInitializer
+import com.android.systemui.log.core.MessagePrinter
 import com.google.errorprone.annotations.CompileTimeConstant
 import java.io.PrintWriter
 import java.util.concurrent.ArrayBlockingQueue
@@ -73,7 +78,7 @@
     private val maxSize: Int,
     private val logcatEchoTracker: LogcatEchoTracker,
     private val systrace: Boolean = true,
-) {
+) : MessageBuffer {
     private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() }
 
     private val echoMessageQueue: BlockingQueue<LogMessage>? =
@@ -174,11 +179,11 @@
      * store any relevant data on the message and then call [commit].
      */
     @Synchronized
-    fun obtain(
+    override fun obtain(
         tag: String,
         level: LogLevel,
         messagePrinter: MessagePrinter,
-        exception: Throwable? = null,
+        exception: Throwable?,
     ): LogMessage {
         if (!mutable) {
             return FROZEN_MESSAGE
@@ -195,7 +200,7 @@
      * have finished filling in its data fields. The message will be echoed to logcat if necessary.
      */
     @Synchronized
-    fun commit(message: LogMessage) {
+    override fun commit(message: LogMessage) {
         if (!mutable) {
             return
         }
@@ -292,11 +297,5 @@
     }
 }
 
-/**
- * A function that will be called immediately to store relevant data on the log message. The value
- * of `this` will be the LogMessage to be initialized.
- */
-typealias MessageInitializer = LogMessage.() -> Unit
-
 private const val TAG = "LogBuffer"
 private val FROZEN_MESSAGE = LogMessageImpl.create()
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt
deleted file mode 100644
index 7d9647a..0000000
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt
+++ /dev/null
@@ -1,29 +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.log
-
-import android.util.Log
-
-/** 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"),
-    WARNING(Log.WARN, "W"),
-    ERROR(Log.ERROR, "E"),
-    WTF(Log.ASSERT, "WTF")
-}
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt
deleted file mode 100644
index 8c3988b..0000000
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt
+++ /dev/null
@@ -1,95 +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.log
-
-import android.icu.text.SimpleDateFormat
-import java.io.PrintWriter
-import java.util.Locale
-
-/**
- * Generic data class for storing messages logged to a [LogBuffer]
- *
- * Each LogMessage has a few standard fields ([level], [tag], and [timestamp]). The rest are generic
- * data slots that may or may not be used, depending on the nature of the specific message being
- * logged.
- *
- * 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.
- *
- * See [LogBuffer.log] for more information.
- */
-interface LogMessage {
-    val level: LogLevel
-    val tag: String
-    val timestamp: Long
-    val messagePrinter: MessagePrinter
-    val exception: Throwable?
-
-    var str1: String?
-    var str2: String?
-    var str3: String?
-    var int1: Int
-    var int2: Int
-    var long1: Long
-    var long2: Long
-    var double1: Double
-    var bool1: Boolean
-    var bool2: Boolean
-    var bool3: Boolean
-    var bool4: Boolean
-
-    /** Function that dumps the [LogMessage] to the provided [writer]. */
-    fun dump(writer: PrintWriter) {
-        val formattedTimestamp = DATE_FORMAT.format(timestamp)
-        val shortLevel = level.shortString
-        val messageToPrint = messagePrinter(this)
-        printLikeLogcat(writer, formattedTimestamp, shortLevel, tag, messageToPrint)
-        exception?.printStackTrace(writer)
-    }
-}
-
-/**
- * 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
-
-private fun printLikeLogcat(
-    pw: PrintWriter,
-    formattedTimestamp: String,
-    shortLogLevel: String,
-    tag: String,
-    message: String
-) {
-    pw.print(formattedTimestamp)
-    pw.print(" ")
-    pw.print(shortLogLevel)
-    pw.print(" ")
-    pw.print(tag)
-    pw.print(": ")
-    pw.println(message)
-}
-
-private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt
index 5e10f78..33cc199 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt
@@ -16,6 +16,10 @@
 
 package com.android.systemui.log
 
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.core.MessagePrinter
+
 /** Recyclable implementation of [LogMessage]. */
 data class LogMessageImpl(
     override var level: LogLevel,
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt
index 55f3a73..ae717df 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.log
 
+import com.android.systemui.log.core.LogLevel
+
 /** Keeps track of which [LogBuffer] messages should also appear in logcat. */
 interface LogcatEchoTracker {
     /** Whether [bufferName] should echo messages of [level] or higher to logcat. */
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
index d0ad28f..9ff48ca 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt
@@ -23,6 +23,7 @@
 import android.os.Looper
 import android.os.Trace
 import android.provider.Settings
+import com.android.systemui.log.core.LogLevel
 
 /**
  * Version of [LogcatEchoTracker] for debuggable builds
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
index 56966773..044d97f 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.log
 
+import com.android.systemui.log.core.LogLevel
+
 /** Production version of [LogcatEchoTracker] that isn't configurable. */
 class LogcatEchoTrackerProd : LogcatEchoTracker {
     override val logInBackgroundThread = false
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/LogLevel.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/LogLevel.kt
new file mode 100644
index 0000000..d30d8e9
--- /dev/null
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/LogLevel.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.log.core
+
+import android.util.Log
+
+/** 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"),
+    WARNING(Log.WARN, "W"),
+    ERROR(Log.ERROR, "E"),
+    WTF(Log.ASSERT, "WTF")
+}
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/LogMessage.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/LogMessage.kt
new file mode 100644
index 0000000..3bd6473
--- /dev/null
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/LogMessage.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.log.core
+
+import android.icu.text.SimpleDateFormat
+import java.io.PrintWriter
+import java.util.Locale
+
+/**
+ * Generic data class for storing messages logged to a [LogBuffer]
+ *
+ * Each LogMessage has a few standard fields ([level], [tag], and [timestamp]). The rest are generic
+ * data slots that may or may not be used, depending on the nature of the specific message being
+ * logged.
+ *
+ * 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.
+ *
+ * See [LogBuffer.log] for more information.
+ */
+interface LogMessage {
+    val level: LogLevel
+    val tag: String
+    val timestamp: Long
+    val messagePrinter: MessagePrinter
+    val exception: Throwable?
+
+    var str1: String?
+    var str2: String?
+    var str3: String?
+    var int1: Int
+    var int2: Int
+    var long1: Long
+    var long2: Long
+    var double1: Double
+    var bool1: Boolean
+    var bool2: Boolean
+    var bool3: Boolean
+    var bool4: Boolean
+
+    /** Function that dumps the [LogMessage] to the provided [writer]. */
+    fun dump(writer: PrintWriter) {
+        val formattedTimestamp = DATE_FORMAT.format(timestamp)
+        val shortLevel = level.shortString
+        val messageToPrint = messagePrinter(this)
+        printLikeLogcat(writer, formattedTimestamp, shortLevel, tag, messageToPrint)
+        exception?.printStackTrace(writer)
+    }
+}
+
+/**
+ * A function that will be called immediately to store relevant data on the log message. The value
+ * of `this` will be the LogMessage to be initialized.
+ */
+typealias MessageInitializer = LogMessage.() -> Unit
+
+/**
+ * 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
+
+private fun printLikeLogcat(
+    pw: PrintWriter,
+    formattedTimestamp: String,
+    shortLogLevel: String,
+    tag: String,
+    message: String
+) {
+    pw.print(formattedTimestamp)
+    pw.print(" ")
+    pw.print(shortLogLevel)
+    pw.print(" ")
+    pw.print(tag)
+    pw.print(": ")
+    pw.println(message)
+}
+
+private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt
new file mode 100644
index 0000000..5729ab2
--- /dev/null
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.core
+
+import com.google.errorprone.annotations.CompileTimeConstant
+
+/** Logs messages to the [MessageBuffer] with [tag]. */
+open class Logger(val buffer: MessageBuffer, val tag: String) {
+    /**
+     * Logs a message to the buffer.
+     *
+     * The actual string of the log message is not constructed until it is needed. To accomplish
+     * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is
+     * obtained and is passed to the [messageInitializer]. The initializer stores any relevant data
+     * on the message's fields. The message is then inserted into the buffer where it waits until it
+     * is either pushed out by newer messages or it needs to printed. If and when this latter moment
+     * occurs, the [messagePrinter] function is called on the message. It reads whatever data the
+     * initializer stored and converts it to a human-readable log message.
+     *
+     * @param level Which level to log the message at, both to the buffer and to logcat if it's
+     *   echoed. In general, a module should split most of its logs into either INFO or DEBUG level.
+     *   INFO level should be reserved for information that other parts of the system might care
+     *   about, leaving the specifics of code's day-to-day operations to DEBUG.
+     * @param messagePrinter 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.
+     * @param exception Provide any exception that need to be logged. This is saved as
+     *   [LogMessage.exception]
+     * @param messageInitializer A function that will be called immediately to store relevant data
+     *   on the log message. The value of `this` will be the [LogMessage] to be initialized.
+     */
+    @JvmOverloads
+    inline fun log(
+        level: LogLevel,
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) {
+        val message = buffer.obtain(tag, level, messagePrinter, exception)
+        messageInitializer(message)
+        buffer.commit(message)
+    }
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer. Use sparingly.
+     *
+     * This is for simpler use-cases where [message] is a compile time string constant. For
+     * use-cases where the log message is built during runtime, use the [log] overloaded method that
+     * takes in an initializer and a message printer.
+     *
+     * Buffers are limited by the number of entries, so logging more frequently will limit the time
+     * window that the [MessageBuffer] 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 details
+     * to every log may do more to improve overall logging than adding more logs with this method.
+     */
+    @JvmOverloads
+    fun log(
+        level: LogLevel,
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(level, { str1!! }, exception) { str1 = message }
+
+    /**
+     * Logs a message to the buffer at [LogLevel.VERBOSE].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun v(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.VERBOSE, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.VERBOSE]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun v(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.VERBOSE, message, exception)
+
+    /**
+     * Logs a message to the buffer at [LogLevel.DEBUG].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun d(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.DEBUG, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.DEBUG]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun d(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.DEBUG, message, exception)
+
+    /**
+     * Logs a message to the buffer at [LogLevel.INFO].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun i(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.INFO, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.INFO]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun i(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.INFO, message, exception)
+
+    /**
+     * Logs a message to the buffer at [LogLevel.WARNING].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun w(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.WARNING, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.WARNING]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun w(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.WARNING, message, exception)
+
+    /**
+     * Logs a message to the buffer at [LogLevel.ERROR].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun e(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.ERROR, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.ERROR]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun e(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.ERROR, message, exception)
+
+    /**
+     * Logs a message to the buffer at [LogLevel.WTF].
+     *
+     * @see log
+     */
+    @JvmOverloads
+    inline fun wtf(
+        noinline messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+        messageInitializer: MessageInitializer,
+    ) = log(LogLevel.WTF, messagePrinter, exception, messageInitializer)
+
+    /**
+     * Logs a compile-time string constant [message] to the log buffer at [LogLevel.WTF]. Use
+     * sparingly.
+     *
+     * @see log
+     */
+    @JvmOverloads
+    fun wtf(
+        @CompileTimeConstant message: String,
+        exception: Throwable? = null,
+    ) = log(LogLevel.WTF, message, exception)
+}
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt
new file mode 100644
index 0000000..bb91633
--- /dev/null
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.core
+
+/**
+ * [MessageBuffer] is an interface that represents a buffer of log messages, and provides methods to
+ * [obtain] a log message and [commit] it to the buffer.
+ */
+interface MessageBuffer {
+    /**
+     * Obtains the next [LogMessage] from the buffer.
+     *
+     * After calling [obtain], the caller must store any relevant data on the message and then call
+     * [commit].
+     */
+    fun obtain(
+        tag: String,
+        level: LogLevel,
+        messagePrinter: MessagePrinter,
+        exception: Throwable? = null,
+    ): LogMessage
+
+    /**
+     * After acquiring a log message via [obtain], call this method to signal to the buffer that
+     * data fields have been filled.
+     */
+    fun commit(message: LogMessage)
+}
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 450c616..46a90f6 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -377,6 +377,8 @@
     }
 }
 
+@Deprecated("Please use com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors " +
+        "instead")
 class ColorScheme(
     @ColorInt val seed: Int,
     val darkTheme: Boolean,
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/README.md b/packages/SystemUI/monet/src/com/android/systemui/monet/README.md
deleted file mode 100644
index e4818f9..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-Subpackages, i.e. folders, in this directory have their source of truth in the internal Google
-source code repository.
-
-The code is manually copied because:
-
-- Alternatives are automatically syncing Material Color Utilities from the internal Google source
-  repository, or GitHub.
-- Having Android's core automatically taking updates from code that changes regularly, and can
-  easily affect design, is undesirable.
-
-To update the code:
-
-1. Copy Material Color Utilities (libmonet internally) from the internal Google source repository to
-   this directory.
-2. Replace package definitions. Use Find and Replace in directory, replace
-   com.google.ux.material.libmonet with com.android.systemui.monet.
-3. Remove @CanIgnoreReturnValue and @CheckReturnValue, both usage and imports. Currently only in
-   Scheme.java. Chose removal because only Android /external libraries depend on the library with
-   these annotations.
-4. Copy tests for Material Color Utilities to
-   frameworks/base/packages/SystemUI/tests/src/com/android/systemui/monet/
-5. Remove HctRoundTripTest (~3 minutes to run) and QuantizerCelebiTest (depends on internal source
-   repository code for handling images)
-6. Reformat regular and test code to Android bracing/spacing style.
-   In Android Studio, right click on the package, and choose Reformat Code.
-7. Change tests to subclass SysuiTestCase and use an annotation such as @SmallTest / @MediumTest.
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/blend/Blend.java b/packages/SystemUI/monet/src/com/android/systemui/monet/blend/Blend.java
deleted file mode 100644
index 927417a..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/blend/Blend.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.blend;
-
-import com.android.systemui.monet.hct.Cam16;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.utils.ColorUtils;
-import com.android.systemui.monet.utils.MathUtils;
-
-/** Functions for blending in HCT and CAM16. */
-public class Blend {
-    private Blend() {
-    }
-
-    /**
-     * Blend the design color's HCT hue towards the key color's HCT hue, in a way that leaves the
-     * original color recognizable and recognizably shifted towards the key color.
-     *
-     * @param designColor ARGB representation of an arbitrary color.
-     * @param sourceColor ARGB representation of the main theme color.
-     * @return The design color with a hue shifted towards the system's color, a slightly
-     * warmer/cooler variant of the design color's hue.
-     */
-    public static int harmonize(int designColor, int sourceColor) {
-        Hct fromHct = Hct.fromInt(designColor);
-        Hct toHct = Hct.fromInt(sourceColor);
-        double differenceDegrees = MathUtils.differenceDegrees(fromHct.getHue(), toHct.getHue());
-        double rotationDegrees = Math.min(differenceDegrees * 0.5, 15.0);
-        double outputHue =
-                MathUtils.sanitizeDegreesDouble(
-                        fromHct.getHue()
-                                + rotationDegrees * MathUtils.rotationDirection(fromHct.getHue(),
-                                toHct.getHue()));
-        return Hct.from(outputHue, fromHct.getChroma(), fromHct.getTone()).toInt();
-    }
-
-    /**
-     * Blends hue from one color into another. The chroma and tone of the original color are
-     * maintained.
-     *
-     * @param from   ARGB representation of color
-     * @param to     ARGB representation of color
-     * @param amount how much blending to perform; 0.0 >= and <= 1.0
-     * @return from, with a hue blended towards to. Chroma and tone are constant.
-     */
-    public static int hctHue(int from, int to, double amount) {
-        int ucs = cam16Ucs(from, to, amount);
-        Cam16 ucsCam = Cam16.fromInt(ucs);
-        Cam16 fromCam = Cam16.fromInt(from);
-        Hct blended = Hct.from(ucsCam.getHue(), fromCam.getChroma(),
-                ColorUtils.lstarFromArgb(from));
-        return blended.toInt();
-    }
-
-    /**
-     * Blend in CAM16-UCS space.
-     *
-     * @param from   ARGB representation of color
-     * @param to     ARGB representation of color
-     * @param amount how much blending to perform; 0.0 >= and <= 1.0
-     * @return from, blended towards to. Hue, chroma, and tone will change.
-     */
-    public static int cam16Ucs(int from, int to, double amount) {
-        Cam16 fromCam = Cam16.fromInt(from);
-        Cam16 toCam = Cam16.fromInt(to);
-        double fromJ = fromCam.getJstar();
-        double fromA = fromCam.getAstar();
-        double fromB = fromCam.getBstar();
-        double toJ = toCam.getJstar();
-        double toA = toCam.getAstar();
-        double toB = toCam.getBstar();
-        double jstar = fromJ + (toJ - fromJ) * amount;
-        double astar = fromA + (toA - fromA) * amount;
-        double bstar = fromB + (toB - fromB) * amount;
-        return Cam16.fromUcs(jstar, astar, bstar).toInt();
-    }
-}
-
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/contrast/Contrast.java b/packages/SystemUI/monet/src/com/android/systemui/monet/contrast/Contrast.java
deleted file mode 100644
index ada8ced..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/contrast/Contrast.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.contrast;
-
-
-import static java.lang.Math.max;
-
-import com.android.systemui.monet.utils.ColorUtils;
-
-/**
- * Color science for contrast utilities.
- *
- * <p>Utility methods for calculating contrast given two colors, or calculating a color given one
- * color and a contrast ratio.
- *
- * <p>Contrast ratio is calculated using XYZ's Y. When linearized to match human perception, Y
- * becomes HCT's tone and L*a*b*'s' L*.
- */
-public final class Contrast {
-    // The minimum contrast ratio of two colors.
-    // Contrast ratio equation = lighter + 5 / darker + 5, if lighter == darker, ratio == 1.
-    public static final double RATIO_MIN = 1.0;
-
-    // The maximum contrast ratio of two colors.
-    // Contrast ratio equation = lighter + 5 / darker + 5. Lighter and darker scale from 0 to 100.
-    // If lighter == 100, darker = 0, ratio == 21.
-    public static final double RATIO_MAX = 21.0;
-    public static final double RATIO_30 = 3.0;
-    public static final double RATIO_45 = 4.5;
-    public static final double RATIO_70 = 7.0;
-
-    // Given a color and a contrast ratio to reach, the luminance of a color that reaches that ratio
-    // with the color can be calculated. However, that luminance may not contrast as desired, i.e.
-    // the contrast ratio of the input color and the returned luminance may not reach the contrast
-    // ratio asked for.
-    //
-    // When the desired contrast ratio and the result contrast ratio differ by more than this
-    // amount, an error value should be returned, or the method should be documented as 'unsafe',
-    // meaning, it will return a valid luminance but that luminance may not meet the requested
-    // contrast ratio.
-    //
-    // 0.04 selected because it ensures the resulting ratio rounds to the same tenth.
-    private static final double CONTRAST_RATIO_EPSILON = 0.04;
-
-    // Color spaces that measure luminance, such as Y in XYZ, L* in L*a*b*, or T in HCT, are
-    // known as perceptually accurate color spaces.
-    //
-    // To be displayed, they must gamut map to a "display space", one that has a defined limit on
-    // the number of colors. Display spaces include sRGB, more commonly understood as RGB/HSL/HSV.
-    //
-    // Gamut mapping is undefined and not defined by the color space. Any gamut mapping algorithm
-    // must choose how to sacrifice accuracy in hue, saturation, and/or lightness.
-    //
-    // A principled solution is to maintain lightness, thus maintaining contrast/a11y, maintain hue,
-    // thus maintaining aesthetic intent, and reduce chroma until the color is in gamut.
-    //
-    // HCT chooses this solution, but, that doesn't mean it will _exactly_ matched desired
-    // lightness, if only because RGB is quantized: RGB is expressed as a set of integers: there may
-    // be an RGB color with, for example, 47.892 lightness, but not 47.891.
-    //
-    // To allow for this inherent incompatibility between perceptually accurate color spaces and
-    // display color spaces, methods that take a contrast ratio and luminance, and return a
-    // luminance that reaches that contrast ratio for the input luminance, purposefully
-    // darken/lighten their result such that the desired contrast ratio will be reached even if
-    // inaccuracy is introduced.
-    //
-    // 0.4 is generous, ex. HCT requires much less delta. It was chosen because it provides a rough
-    // guarantee that as long as a percetual color space gamut maps lightness such that the
-    // resulting lightness rounds to the same as the requested, the desired contrast ratio will be
-    // reached.
-    private static final double LUMINANCE_GAMUT_MAP_TOLERANCE = 0.4;
-
-    private Contrast() {
-    }
-
-    /**
-     * Contrast ratio is a measure of legibility, its used to compare the lightness of two colors.
-     * This method is used commonly in industry due to its use by WCAG.
-     *
-     * <p>To compare lightness, the colors are expressed in the XYZ color space, where Y is
-     * lightness,
-     * also known as relative luminance.
-     *
-     * <p>The equation is ratio = lighter Y + 5 / darker Y + 5.
-     */
-    public static double ratioOfYs(double y1, double y2) {
-        final double lighter = max(y1, y2);
-        final double darker = (lighter == y2) ? y1 : y2;
-        return (lighter + 5.0) / (darker + 5.0);
-    }
-
-    /**
-     * Contrast ratio of two tones. T in HCT, L* in L*a*b*. Also known as luminance or perpectual
-     * luminance.
-     *
-     * <p>Contrast ratio is defined using Y in XYZ, relative luminance. However, relative luminance
-     * is linear to number of photons, not to perception of lightness. Perceptual luminance, L* in
-     * L*a*b*, T in HCT, is. Designers prefer color spaces with perceptual luminance since they're
-     * accurate to the eye.
-     *
-     * <p>Y and L* are pure functions of each other, so it possible to use perceptually accurate
-     * color spaces, and measure contrast, and measure contrast in a much more understandable way:
-     * instead of a ratio, a linear difference. This allows a designer to determine what they need
-     * to adjust a color's lightness to in order to reach their desired contrast, instead of
-     * guessing & checking with hex codes.
-     */
-    public static double ratioOfTones(double t1, double t2) {
-        return ratioOfYs(ColorUtils.yFromLstar(t1), ColorUtils.yFromLstar(t2));
-    }
-
-    /**
-     * Returns T in HCT, L* in L*a*b* >= tone parameter that ensures ratio with input T/L*. Returns
-     * -1 if ratio cannot be achieved.
-     *
-     * @param tone  Tone return value must contrast with.
-     * @param ratio Desired contrast ratio of return value and tone parameter.
-     */
-    public static double lighter(double tone, double ratio) {
-        if (tone < 0.0 || tone > 100.0) {
-            return -1.0;
-        }
-        // Invert the contrast ratio equation to determine lighter Y given a ratio and darker Y.
-        final double darkY = ColorUtils.yFromLstar(tone);
-        final double lightY = ratio * (darkY + 5.0) - 5.0;
-        if (lightY < 0.0 || lightY > 100.0) {
-            return -1.0;
-        }
-        final double realContrast = ratioOfYs(lightY, darkY);
-        final double delta = Math.abs(realContrast - ratio);
-        if (realContrast < ratio && delta > CONTRAST_RATIO_EPSILON) {
-            return -1.0;
-        }
-
-        final double returnValue = ColorUtils.lstarFromY(lightY) + LUMINANCE_GAMUT_MAP_TOLERANCE;
-        // NOMUTANTS--important validation step; functions it is calling may change implementation.
-        if (returnValue < 0 || returnValue > 100) {
-            return -1.0;
-        }
-        return returnValue;
-    }
-
-    /**
-     * Tone >= tone parameter that ensures ratio. 100 if ratio cannot be achieved.
-     *
-     * <p>This method is unsafe because the returned value is guaranteed to be in bounds, but, the
-     * in bounds return value may not reach the desired ratio.
-     *
-     * @param tone  Tone return value must contrast with.
-     * @param ratio Desired contrast ratio of return value and tone parameter.
-     */
-    public static double lighterUnsafe(double tone, double ratio) {
-        double lighterSafe = lighter(tone, ratio);
-        return lighterSafe < 0.0 ? 100.0 : lighterSafe;
-    }
-
-    /**
-     * Returns T in HCT, L* in L*a*b* <= tone parameter that ensures ratio with input T/L*. Returns
-     * -1 if ratio cannot be achieved.
-     *
-     * @param tone  Tone return value must contrast with.
-     * @param ratio Desired contrast ratio of return value and tone parameter.
-     */
-    public static double darker(double tone, double ratio) {
-        if (tone < 0.0 || tone > 100.0) {
-            return -1.0;
-        }
-        // Invert the contrast ratio equation to determine darker Y given a ratio and lighter Y.
-        final double lightY = ColorUtils.yFromLstar(tone);
-        final double darkY = ((lightY + 5.0) / ratio) - 5.0;
-        if (darkY < 0.0 || darkY > 100.0) {
-            return -1.0;
-        }
-        final double realContrast = ratioOfYs(lightY, darkY);
-        final double delta = Math.abs(realContrast - ratio);
-        if (realContrast < ratio && delta > CONTRAST_RATIO_EPSILON) {
-            return -1.0;
-        }
-
-        // For information on 0.4 constant, see comment in lighter(tone, ratio).
-        final double returnValue = ColorUtils.lstarFromY(darkY) - LUMINANCE_GAMUT_MAP_TOLERANCE;
-        // NOMUTANTS--important validation step; functions it is calling may change implementation.
-        if (returnValue < 0 || returnValue > 100) {
-            return -1.0;
-        }
-        return returnValue;
-    }
-
-    /**
-     * Tone <= tone parameter that ensures ratio. 0 if ratio cannot be achieved.
-     *
-     * <p>This method is unsafe because the returned value is guaranteed to be in bounds, but, the
-     * in bounds return value may not reach the desired ratio.
-     *
-     * @param tone  Tone return value must contrast with.
-     * @param ratio Desired contrast ratio of return value and tone parameter.
-     */
-    public static double darkerUnsafe(double tone, double ratio) {
-        double darkerSafe = darker(tone, ratio);
-        return max(0.0, darkerSafe);
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/dislike/DislikeAnalyzer.java b/packages/SystemUI/monet/src/com/android/systemui/monet/dislike/DislikeAnalyzer.java
deleted file mode 100644
index 31feee3..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/dislike/DislikeAnalyzer.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.dislike;
-
-import com.android.systemui.monet.hct.Hct;
-
-/**
- * Check and/or fix universally disliked colors.
- *
- * <p>Color science studies of color preference indicate universal distaste for dark yellow-greens,
- * and also show this is correlated to distate for biological waste and rotting food.
- *
- * <p>See Palmer and Schloss, 2010 or Schloss and Palmer's Chapter 21 in Handbook of Color
- * Psychology (2015).
- */
-public final class DislikeAnalyzer {
-
-    private DislikeAnalyzer() {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Returns true if color is disliked.
-     *
-     * <p>Disliked is defined as a dark yellow-green that is not neutral.
-     */
-    public static boolean isDisliked(Hct hct) {
-        final boolean huePasses = Math.round(hct.getHue()) >= 90.0 && Math.round(hct.getHue())
-                <= 111.0;
-        final boolean chromaPasses = Math.round(hct.getChroma()) > 16.0;
-        final boolean tonePasses = Math.round(hct.getTone()) < 70.0;
-
-        return huePasses && chromaPasses && tonePasses;
-    }
-
-    /** If color is disliked, lighten it to make it likable. */
-    public static Hct fixIfDisliked(Hct hct) {
-        if (isDisliked(hct)) {
-            return Hct.from(hct.getHue(), hct.getChroma(), 70.0);
-        }
-
-        return hct;
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/DynamicColor.java b/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/DynamicColor.java
deleted file mode 100644
index e839d9b..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/DynamicColor.java
+++ /dev/null
@@ -1,653 +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.monet.dynamiccolor;
-
-import static java.lang.Math.max;
-import static java.lang.Math.min;
-
-import com.android.systemui.monet.contrast.Contrast;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.palettes.TonalPalette;
-import com.android.systemui.monet.scheme.DynamicScheme;
-import com.android.systemui.monet.utils.MathUtils;
-
-import java.util.HashMap;
-import java.util.function.BiFunction;
-import java.util.function.Function;
-
-/**
- * A color that adjusts itself based on UI state, represented by DynamicScheme.
- *
- * <p>This color automatically adjusts to accommodate a desired contrast level, or other adjustments
- * such as differing in light mode versus dark mode, or what the theme is, or what the color that
- * produced the theme is, etc.
- *
- * <p>Colors without backgrounds do not change tone when contrast changes. Colors with backgrounds
- * become closer to their background as contrast lowers, and further when contrast increases.
- *
- * <p>Prefer the static constructors. They provide a much more simple interface, such as requiring
- * just a hexcode, or just a hexcode and a background.
- *
- * <p>Ultimately, each component necessary for calculating a color, adjusting it for a desired
- * contrast level, and ensuring it has a certain lightness/tone difference from another color, is
- * provided by a function that takes a DynamicScheme and returns a value. This ensures ultimate
- * flexibility, any desired behavior of a color for any design system, but it usually unnecessary.
- * See the default constructor for more information.
- */
-// Prevent lint for Function.apply not being available on Android before API level 14 (4.0.1).
-// "AndroidJdkLibsChecker" for Function, "NewApi" for Function.apply().
-// A java_library Bazel rule with an Android constraint cannot skip these warnings without this
-// annotation; another solution would be to create an android_library rule and supply
-// AndroidManifest with an SDK set higher than 14.
-@SuppressWarnings({"AndroidJdkLibsChecker", "NewApi"})
-public final class DynamicColor {
-    public final Function<DynamicScheme, Double> hue;
-    public final Function<DynamicScheme, Double> chroma;
-    public final Function<DynamicScheme, Double> tone;
-    public final Function<DynamicScheme, Double> opacity;
-
-    public final Function<DynamicScheme, DynamicColor> background;
-    public final Function<DynamicScheme, Double> toneMinContrast;
-    public final Function<DynamicScheme, Double> toneMaxContrast;
-    public final Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint;
-
-    private final HashMap<DynamicScheme, Hct> hctCache = new HashMap<>();
-
-    /**
-     * The base constructor for DynamicColor.
-     *
-     * <p>Functional arguments allow overriding without risks that come with subclasses. _Strongly_
-     * prefer using one of the static convenience constructors. This class is arguably too
-     * flexible to
-     * ensure it can support any scenario.
-     *
-     * <p>For example, the default behavior of adjust tone at max contrast to be at a 7.0 ratio with
-     * its background is principled and matches a11y guidance. That does not mean it's the desired
-     * approach for _every_ design system, and every color pairing, always, in every case.
-     *
-     * @param hue                 given DynamicScheme, return the hue in HCT of the output color.
-     * @param chroma              given DynamicScheme, return chroma in HCT of the output color.
-     * @param tone                given DynamicScheme, return tone in HCT of the output color.
-     * @param background          given DynamicScheme, return the DynamicColor that is the
-     *                            background of this
-     *                            DynamicColor. When this is provided, automated adjustments to
-     *                            lower and raise contrast are
-     *                            made.
-     * @param toneMinContrast     given DynamicScheme, return tone in HCT/L* in L*a*b* this color
-     *                           should
-     *                            be at minimum contrast. See toneMinContrastDefault for the
-     *                            default behavior, and strongly
-     *                            consider using it unless you have strong opinions on a11y. The
-     *                            static constructors use it.
-     * @param toneMaxContrast     given DynamicScheme, return tone in HCT/L* in L*a*b* this color
-     *                           should
-     *                            be at maximum contrast. See toneMaxContrastDefault for the
-     *                            default behavior, and strongly
-     *                            consider using it unless you have strong opinions on a11y. The
-     *                            static constructors use it.
-     * @param toneDeltaConstraint given DynamicScheme, return a ToneDeltaConstraint instance that
-     *                            describes a requirement that this DynamicColor must always have
-     *                            some difference in tone/L*
-     *                            from another DynamicColor.<br>
-     *                            Unlikely to be useful unless a design system has some
-     *                            distortions where colors that don't
-     *                            have a background/foreground relationship must have _some_
-     *                            difference in tone, yet, not
-     *                            enough difference to create meaningful contrast.
-     */
-    public DynamicColor(
-            Function<DynamicScheme, Double> hue,
-            Function<DynamicScheme, Double> chroma,
-            Function<DynamicScheme, Double> tone,
-            Function<DynamicScheme, Double> opacity,
-            Function<DynamicScheme, DynamicColor> background,
-            Function<DynamicScheme, Double> toneMinContrast,
-            Function<DynamicScheme, Double> toneMaxContrast,
-            Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint) {
-        this.hue = hue;
-        this.chroma = chroma;
-        this.tone = tone;
-        this.opacity = opacity;
-        this.background = background;
-        this.toneMinContrast = toneMinContrast;
-        this.toneMaxContrast = toneMaxContrast;
-        this.toneDeltaConstraint = toneDeltaConstraint;
-    }
-
-    /**
-     * Create a DynamicColor from a hex code.
-     *
-     * <p>Result has no background; thus no support for increasing/decreasing contrast for a11y.
-     */
-    public static DynamicColor fromArgb(int argb) {
-        final Hct hct = Hct.fromInt(argb);
-        final TonalPalette palette = TonalPalette.fromInt(argb);
-        return DynamicColor.fromPalette((s) -> palette, (s) -> hct.getTone());
-    }
-
-    /**
-     * Create a DynamicColor from just a hex code.
-     *
-     * <p>Result has no background; thus cannot support increasing/decreasing contrast for a11y.
-     *
-     * @param argb A hex code.
-     * @param tone Function that provides a tone given DynamicScheme. Useful for adjusting for dark
-     *             vs. light mode.
-     */
-    public static DynamicColor fromArgb(int argb, Function<DynamicScheme, Double> tone) {
-        return DynamicColor.fromPalette((s) -> TonalPalette.fromInt(argb), tone);
-    }
-
-    /**
-     * Create a DynamicColor.
-     *
-     * <p>If you don't understand HCT fully, or your design team doesn't, but wants support for
-     * automated contrast adjustment, this method is _extremely_ useful: you can take a standard
-     * design system expressed as hex codes, create DynamicColors corresponding to each color, and
-     * then wire up backgrounds.
-     *
-     * <p>If the design system uses the same hex code on multiple backgrounds, define that in
-     * multiple
-     * DynamicColors so that the background is accurate for each one. If you define a DynamicColor
-     * with one background, and actually use it on another, DynamicColor can't guarantee contrast
-     * . For
-     * example, if you use a color on both black and white, increasing the contrast on one
-     * necessarily
-     * decreases contrast of the other.
-     *
-     * @param argb       A hex code.
-     * @param tone       Function that provides a tone given DynamicScheme. (useful for dark vs.
-     *                   light mode)
-     * @param background Function that provides background DynamicColor given DynamicScheme. Useful
-     *                   for contrast, given a background, colors can adjust to increase/decrease
-     *                   contrast.
-     */
-    public static DynamicColor fromArgb(
-            int argb,
-            Function<DynamicScheme, Double> tone,
-            Function<DynamicScheme, DynamicColor> background) {
-        return DynamicColor.fromPalette((s) -> TonalPalette.fromInt(argb), tone, background);
-    }
-
-    /**
-     * Create a DynamicColor from:
-     *
-     * @param argb                A hex code.
-     * @param tone                Function that provides a tone given DynamicScheme. (useful for
-     *                            dark vs. light mode)
-     * @param background          Function that provides background DynamicColor given
-     *                            DynamicScheme. Useful
-     *                            for contrast, given a background, colors can adjust to
-     *                            increase/decrease contrast.
-     * @param toneDeltaConstraint Function that provides a ToneDeltaConstraint given DynamicScheme.
-     *                            Useful for ensuring lightness difference between colors that
-     *                            don't _require_ contrast or
-     *                            have a formal background/foreground relationship.
-     */
-    public static DynamicColor fromArgb(
-            int argb,
-            Function<DynamicScheme, Double> tone,
-            Function<DynamicScheme, DynamicColor> background,
-            Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint) {
-        return DynamicColor.fromPalette(
-                (s) -> TonalPalette.fromInt(argb), tone, background, toneDeltaConstraint);
-    }
-
-    /**
-     * Create a DynamicColor.
-     *
-     * @param palette Function that provides a TonalPalette given DynamicScheme. A TonalPalette is
-     *                defined by a hue and chroma, so this replaces the need to specify
-     *                hue/chroma. By providing
-     *                a tonal palette, when contrast adjustments are made, intended chroma can be
-     *                preserved. For
-     *                example, at T/L* 90, there is a significant limit to the amount of chroma.
-     *                There is no
-     *                colorful red, a red that light is pink. By preserving the _intended_ chroma
-     *                if lightness
-     *                lowers for contrast adjustments, the intended chroma is restored.
-     * @param tone    Function that provides a tone given DynamicScheme. (useful for dark vs.
-     *                light mode)
-     */
-    public static DynamicColor fromPalette(
-            Function<DynamicScheme, TonalPalette> palette, Function<DynamicScheme, Double> tone) {
-        return fromPalette(palette, tone, null, null);
-    }
-
-    /**
-     * Create a DynamicColor.
-     *
-     * @param palette    Function that provides a TonalPalette given DynamicScheme. A
-     *                   TonalPalette is
-     *                   defined by a hue and chroma, so this replaces the need to specify
-     *                   hue/chroma. By providing
-     *                   a tonal palette, when contrast adjustments are made, intended chroma can
-     *                   be preserved. For
-     *                   example, at T/L* 90, there is a significant limit to the amount of
-     *                   chroma. There is no
-     *                   colorful red, a red that light is pink. By preserving the _intended_
-     *                   chroma if lightness
-     *                   lowers for contrast adjustments, the intended chroma is restored.
-     * @param tone       Function that provides a tone given DynamicScheme. (useful for dark vs.
-     *                   light mode)
-     * @param background Function that provides background DynamicColor given DynamicScheme. Useful
-     *                   for contrast, given a background, colors can adjust to increase/decrease
-     *                   contrast.
-     */
-    public static DynamicColor fromPalette(
-            Function<DynamicScheme, TonalPalette> palette,
-            Function<DynamicScheme, Double> tone,
-            Function<DynamicScheme, DynamicColor> background) {
-        return fromPalette(palette, tone, background, null);
-    }
-
-    /**
-     * Create a DynamicColor.
-     *
-     * @param palette             Function that provides a TonalPalette given DynamicScheme. A
-     *                            TonalPalette is
-     *                            defined by a hue and chroma, so this replaces the need to
-     *                            specify hue/chroma. By providing
-     *                            a tonal palette, when contrast adjustments are made, intended
-     *                            chroma can be preserved. For
-     *                            example, at T/L* 90, there is a significant limit to the amount
-     *                            of chroma. There is no
-     *                            colorful red, a red that light is pink. By preserving the
-     *                            _intended_ chroma if lightness
-     *                            lowers for contrast adjustments, the intended chroma is restored.
-     * @param tone                Function that provides a tone given DynamicScheme. (useful for
-     *                            dark vs. light mode)
-     * @param background          Function that provides background DynamicColor given
-     *                            DynamicScheme. Useful
-     *                            for contrast, given a background, colors can adjust to
-     *                            increase/decrease contrast.
-     * @param toneDeltaConstraint Function that provides a ToneDeltaConstraint given DynamicScheme.
-     *                            Useful for ensuring lightness difference between colors that
-     *                            don't _require_ contrast or
-     *                            have a formal background/foreground relationship.
-     */
-    public static DynamicColor fromPalette(
-            Function<DynamicScheme, TonalPalette> palette,
-            Function<DynamicScheme, Double> tone,
-            Function<DynamicScheme, DynamicColor> background,
-            Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint) {
-        return new DynamicColor(
-                scheme -> palette.apply(scheme).getHue(),
-                scheme -> palette.apply(scheme).getChroma(),
-                tone,
-                null,
-                background,
-                scheme -> toneMinContrastDefault(tone, background, scheme, toneDeltaConstraint),
-                scheme -> toneMaxContrastDefault(tone, background, scheme, toneDeltaConstraint),
-                toneDeltaConstraint);
-    }
-
-    /**
-     * The default algorithm for calculating the tone of a color at minimum contrast.<br>
-     * If the original contrast ratio was >= 7.0, reach contrast 4.5.<br>
-     * If the original contrast ratio was >= 3.0, reach contrast 3.0.<br>
-     * If the original contrast ratio was < 3.0, reach that ratio.
-     */
-    public static double toneMinContrastDefault(
-            Function<DynamicScheme, Double> tone,
-            Function<DynamicScheme, DynamicColor> background,
-            DynamicScheme scheme,
-            Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint) {
-        return DynamicColor.calculateDynamicTone(
-                scheme,
-                tone,
-                c -> c.toneMinContrast.apply(scheme),
-                (stdRatio, bgTone) -> {
-                    double answer = tone.apply(scheme);
-                    if (stdRatio >= Contrast.RATIO_70) {
-                        answer = contrastingTone(bgTone, Contrast.RATIO_45);
-                    } else if (stdRatio >= Contrast.RATIO_30) {
-                        answer = contrastingTone(bgTone, Contrast.RATIO_30);
-                    } else {
-                        final boolean backgroundHasBackground =
-                                background != null
-                                        && background.apply(scheme) != null
-                                        && background.apply(scheme).background != null
-                                        && background.apply(scheme).background.apply(scheme)
-                                        != null;
-                        if (backgroundHasBackground) {
-                            answer = contrastingTone(bgTone, stdRatio);
-                        }
-                    }
-                    return answer;
-                },
-                background,
-                toneDeltaConstraint,
-                null,
-                standardRatio -> standardRatio);
-    }
-
-    /**
-     * The default algorithm for calculating the tone of a color at maximum contrast.<br>
-     * If the color's background has a background, reach contrast 7.0.<br>
-     * If it doesn't, maintain the original contrast ratio.<br>
-     *
-     * <p>This ensures text on surfaces maintains its original, often detrimentally excessive,
-     * contrast ratio. But, text on buttons can soften to not have excessive contrast.
-     *
-     * <p>Historically, digital design uses pure whites and black for text and surfaces. It's too
-     * much
-     * of a jump at this point in history to introduce a dynamic contrast system _and_ insist that
-     * text always had excessive contrast and should reach 7.0, it would deterimentally affect
-     * desire
-     * to understand and use dynamic contrast.
-     */
-    public static double toneMaxContrastDefault(
-            Function<DynamicScheme, Double> tone,
-            Function<DynamicScheme, DynamicColor> background,
-            DynamicScheme scheme,
-            Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint) {
-        return DynamicColor.calculateDynamicTone(
-                scheme,
-                tone,
-                c -> c.toneMaxContrast.apply(scheme),
-                (stdRatio, bgTone) -> {
-                    final boolean backgroundHasBackground =
-                            background != null
-                                    && background.apply(scheme) != null
-                                    && background.apply(scheme).background != null
-                                    && background.apply(scheme).background.apply(scheme) != null;
-                    if (backgroundHasBackground) {
-                        return contrastingTone(bgTone, Contrast.RATIO_70);
-                    } else {
-                        return contrastingTone(bgTone, max(Contrast.RATIO_70, stdRatio));
-                    }
-                },
-                background,
-                toneDeltaConstraint,
-                null,
-                null);
-    }
-
-    /**
-     * Core method for calculating a tone for under dynamic contrast.
-     *
-     * <p>It enforces important properties:<br>
-     * #1. Desired contrast ratio is reached.<br>
-     * As contrast increases from standard to max, the tones involved should always be at least the
-     * standard ratio. For example, if a button is T90, and button text is T0, and the button is
-     * T0 at
-     * max contrast, the button text cannot simply linearly interpolate from T0 to T100, or at some
-     * point they'll both be at the same tone.
-     *
-     * <p>#2. Enable light foregrounds on midtones.<br>
-     * The eye prefers light foregrounds on T50 to T60, possibly up to T70, but, contrast ratio 4.5
-     * can't be reached with T100 unless the foreground is T50. Contrast ratio 4.5 is crucial, it
-     * represents 'readable text', i.e. text smaller than ~40 dp / 1/4". So, if a tone is between
-     * T50
-     * and T60, it is proactively changed to T49 to enable light foregrounds.
-     *
-     * <p>#3. Ensure tone delta with another color.<br>
-     * In design systems, there may be colors that don't have a pure background/foreground
-     * relationship, but, do require different tones for visual differentiation. ToneDeltaConstraint
-     * models this requirement, and DynamicColor enforces it.
-     */
-    public static double calculateDynamicTone(
-            DynamicScheme scheme,
-            Function<DynamicScheme, Double> toneStandard,
-            Function<DynamicColor, Double> toneToJudge,
-            BiFunction<Double, Double, Double> desiredTone,
-            Function<DynamicScheme, DynamicColor> background,
-            Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint,
-            Function<Double, Double> minRatio,
-            Function<Double, Double> maxRatio) {
-        // Start with the tone with no adjustment for contrast.
-        // If there is no background, don't perform any adjustment, return immediately.
-        final double toneStd = toneStandard.apply(scheme);
-        double answer = toneStd;
-        final DynamicColor bgDynamic = background == null ? null : background.apply(scheme);
-        if (bgDynamic == null) {
-            return answer;
-        }
-        final double bgToneStd = bgDynamic.tone.apply(scheme);
-        final double stdRatio = Contrast.ratioOfTones(toneStd, bgToneStd);
-
-        // If there is a background, determine its tone after contrast adjustment.
-        // Then, calculate the foreground tone that ensures the caller's desired contrast ratio
-        // is met.
-        final double bgTone = toneToJudge.apply(bgDynamic);
-        final double myDesiredTone = desiredTone.apply(stdRatio, bgTone);
-        final double currentRatio = Contrast.ratioOfTones(bgTone, myDesiredTone);
-        final double minRatioRealized =
-                minRatio == null
-                        ? Contrast.RATIO_MIN
-                        : minRatio.apply(stdRatio) == null ? Contrast.RATIO_MIN : minRatio.apply(
-                                stdRatio);
-        final double maxRatioRealized =
-                maxRatio == null
-                        ? Contrast.RATIO_MAX
-                        : maxRatio.apply(stdRatio) == null ? Contrast.RATIO_MAX : maxRatio.apply(
-                                stdRatio);
-        final double desiredRatio =
-                MathUtils.clampDouble(minRatioRealized, maxRatioRealized, currentRatio);
-        if (desiredRatio == currentRatio) {
-            answer = myDesiredTone;
-        } else {
-            answer = DynamicColor.contrastingTone(bgTone, desiredRatio);
-        }
-
-        // If the background has no background,  adjust the foreground tone to ensure that
-        // it is dark enough to have a light foreground.
-        if (bgDynamic.background == null || bgDynamic.background.apply(scheme) == null) {
-            answer = DynamicColor.enableLightForeground(answer);
-        }
-
-        // If the caller has specified a constraint where it must have a certain  tone distance from
-        // another color, enforce that constraint.
-        answer = ensureToneDelta(answer, toneStd, scheme, toneDeltaConstraint, toneToJudge);
-
-        return answer;
-    }
-
-    static double ensureToneDelta(
-            double tone,
-            double toneStandard,
-            DynamicScheme scheme,
-            Function<DynamicScheme, ToneDeltaConstraint> toneDeltaConstraint,
-            Function<DynamicColor, Double> toneToDistanceFrom) {
-        final ToneDeltaConstraint constraint =
-                (toneDeltaConstraint == null ? null : toneDeltaConstraint.apply(scheme));
-        if (constraint == null) {
-            return tone;
-        }
-
-        final double requiredDelta = constraint.delta;
-        final double keepAwayTone = toneToDistanceFrom.apply(constraint.keepAway);
-        final double delta = Math.abs(tone - keepAwayTone);
-        if (delta >= requiredDelta) {
-            return tone;
-        }
-        switch (constraint.keepAwayPolarity) {
-            case DARKER:
-                return MathUtils.clampDouble(0, 100, keepAwayTone + requiredDelta);
-            case LIGHTER:
-                return MathUtils.clampDouble(0, 100, keepAwayTone - requiredDelta);
-            case NO_PREFERENCE:
-                final double keepAwayToneStandard = constraint.keepAway.tone.apply(scheme);
-                final boolean preferLighten = toneStandard > keepAwayToneStandard;
-                final double alterAmount = Math.abs(delta - requiredDelta);
-                final boolean lighten =
-                        preferLighten ? (tone + alterAmount <= 100.0) : tone < alterAmount;
-                return lighten ? tone + alterAmount : tone - alterAmount;
-        }
-        return tone;
-    }
-
-    /**
-     * Given a background tone, find a foreground tone, while ensuring they reach a contrast ratio
-     * that is as close to ratio as possible.
-     */
-    public static double contrastingTone(double bgTone, double ratio) {
-        final double lighterTone = Contrast.lighterUnsafe(bgTone, ratio);
-        final double darkerTone = Contrast.darkerUnsafe(bgTone, ratio);
-        final double lighterRatio = Contrast.ratioOfTones(lighterTone, bgTone);
-        final double darkerRatio = Contrast.ratioOfTones(darkerTone, bgTone);
-        final boolean preferLighter = tonePrefersLightForeground(bgTone);
-
-        if (preferLighter) {
-            // "Neglible difference" handles an edge case where the initial contrast ratio is high
-            // (ex. 13.0), and the ratio passed to the function is that high ratio, and both the
-            // lighter
-            // and darker ratio fails to pass that ratio.
-            //
-            // This was observed with Tonal Spot's On Primary Container turning black momentarily
-            // between
-            // high and max contrast in light mode. PC's standard tone was T90, OPC's was T10, it
-            // was
-            // light mode, and the contrast level was 0.6568521221032331.
-            final boolean negligibleDifference =
-                    Math.abs(lighterRatio - darkerRatio) < 0.1 && lighterRatio < ratio
-                            && darkerRatio < ratio;
-            if (lighterRatio >= ratio || lighterRatio >= darkerRatio || negligibleDifference) {
-                return lighterTone;
-            } else {
-                return darkerTone;
-            }
-        } else {
-            return darkerRatio >= ratio || darkerRatio >= lighterRatio ? darkerTone : lighterTone;
-        }
-    }
-
-    /**
-     * Adjust a tone down such that white has 4.5 contrast, if the tone is reasonably close to
-     * supporting it.
-     */
-    public static double enableLightForeground(double tone) {
-        if (tonePrefersLightForeground(tone) && !toneAllowsLightForeground(tone)) {
-            return 49.0;
-        }
-        return tone;
-    }
-
-    /**
-     * People prefer white foregrounds on ~T60-70. Observed over time, and also by Andrew Somers
-     * during research for APCA.
-     *
-     * <p>T60 used as to create the smallest discontinuity possible when skipping down to T49 in
-     * order
-     * to ensure light foregrounds.
-     *
-     * <p>Since `tertiaryContainer` in dark monochrome scheme requires a tone of 60, it should
-     * not be
-     * adjusted. Therefore, 60 is excluded here.
-     */
-    public static boolean tonePrefersLightForeground(double tone) {
-        return Math.round(tone) < 60;
-    }
-
-    /**
-     * Tones less than ~T50 always permit white at 4.5 contrast.
-     */
-    public static boolean toneAllowsLightForeground(double tone) {
-        return Math.round(tone) <= 49;
-    }
-
-    public int getArgb(DynamicScheme scheme) {
-        final int argb = getHct(scheme).toInt();
-        if (opacity == null) {
-            return argb;
-        }
-        final double percentage = opacity.apply(scheme);
-        final int alpha = MathUtils.clampInt(0, 255, (int) Math.round(percentage * 255));
-        return (argb & 0x00ffffff) | (alpha << 24);
-    }
-
-    public Hct getHct(DynamicScheme scheme) {
-        final Hct cachedAnswer = hctCache.get(scheme);
-        if (cachedAnswer != null) {
-            return cachedAnswer;
-        }
-        // This is crucial for aesthetics: we aren't simply the taking the standard color
-        // and changing its tone for contrast. Rather, we find the tone for contrast, then
-        // use the specified chroma from the palette to construct a new color.
-        //
-        // For example, this enables colors with standard tone of T90, which has limited chroma, to
-        // "recover" intended chroma as contrast increases.
-        final Hct answer = Hct.from(hue.apply(scheme), chroma.apply(scheme), getTone(scheme));
-        // NOMUTANTS--trivial test with onerous dependency injection requirement.
-        if (hctCache.size() > 4) {
-            hctCache.clear();
-        }
-        // NOMUTANTS--trivial test with onerous dependency injection requirement.
-        hctCache.put(scheme, answer);
-        return answer;
-    }
-
-    /**
-     * Returns the tone in HCT, ranging from 0 to 100, of the resolved color given scheme.
-     */
-    public double getTone(DynamicScheme scheme) {
-        double answer = tone.apply(scheme);
-
-        final boolean decreasingContrast = scheme.contrastLevel < 0.0;
-        if (scheme.contrastLevel != 0.0) {
-            final double startTone = tone.apply(scheme);
-            final double endTone =
-                    decreasingContrast ? toneMinContrast.apply(scheme) : toneMaxContrast.apply(
-                            scheme);
-            final double delta = (endTone - startTone) * Math.abs(scheme.contrastLevel);
-            answer = delta + startTone;
-        }
-
-        final DynamicColor bgDynamicColor = background == null ? null : background.apply(scheme);
-        double minRatio = Contrast.RATIO_MIN;
-        double maxRatio = Contrast.RATIO_MAX;
-        if (bgDynamicColor != null) {
-            final boolean bgHasBg =
-                    bgDynamicColor.background != null && bgDynamicColor.background.apply(scheme)
-                            != null;
-            final double standardRatio =
-                    Contrast.ratioOfTones(tone.apply(scheme), bgDynamicColor.tone.apply(scheme));
-            if (decreasingContrast) {
-                final double minContrastRatio =
-                        Contrast.ratioOfTones(
-                                toneMinContrast.apply(scheme),
-                                bgDynamicColor.toneMinContrast.apply(scheme));
-                minRatio = bgHasBg ? minContrastRatio : 1.0;
-                maxRatio = standardRatio;
-            } else {
-                final double maxContrastRatio =
-                        Contrast.ratioOfTones(
-                                toneMaxContrast.apply(scheme),
-                                bgDynamicColor.toneMaxContrast.apply(scheme));
-                minRatio = bgHasBg ? min(maxContrastRatio, standardRatio) : 1.0;
-                maxRatio = bgHasBg ? max(maxContrastRatio, standardRatio) : 21.0;
-            }
-        }
-
-        final double finalMinRatio = minRatio;
-        final double finalMaxRatio = maxRatio;
-        final double finalAnswer = answer;
-        answer =
-                calculateDynamicTone(
-                        scheme,
-                        this.tone,
-                        (dynamicColor) -> dynamicColor.getTone(scheme),
-                        (a, b) -> finalAnswer,
-                        (s) -> bgDynamicColor,
-                        toneDeltaConstraint,
-                        (s) -> finalMinRatio,
-                        (s) -> finalMaxRatio);
-
-        return answer;
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/MaterialDynamicColors.java b/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/MaterialDynamicColors.java
deleted file mode 100644
index 9f075e5..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/MaterialDynamicColors.java
+++ /dev/null
@@ -1,640 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.dynamiccolor;
-
-import android.annotation.NonNull;
-
-import com.android.systemui.monet.dislike.DislikeAnalyzer;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.hct.ViewingConditions;
-import com.android.systemui.monet.scheme.DynamicScheme;
-import com.android.systemui.monet.scheme.Variant;
-
-/** Named colors, otherwise known as tokens, or roles, in the Material Design system. */
-// Prevent lint for Function.apply not being available on Android before API level 14 (4.0.1).
-// "AndroidJdkLibsChecker" for Function, "NewApi" for Function.apply().
-// A java_library Bazel rule with an Android constraint cannot skip these warnings without this
-// annotation; another solution would be to create an android_library rule and supply
-// AndroidManifest with an SDK set higher than 14.
-@SuppressWarnings({"AndroidJdkLibsChecker", "NewApi"})
-public final class MaterialDynamicColors {
-    private static final double CONTAINER_ACCENT_TONE_DELTA = 15.0;
-
-    public MaterialDynamicColors() {}
-
-    // Compatibility Keys Colors for Android
-    public DynamicColor primaryPaletteKeyColor() {
-        return DynamicColor.fromPalette(
-                (s) -> s.primaryPalette, (s) -> s.primaryPalette.getKeyColor().getTone());
-    }
-
-    public DynamicColor secondaryPaletteKeyColor() {
-        return DynamicColor.fromPalette(
-                (s) -> s.secondaryPalette, (s) -> s.secondaryPalette.getKeyColor().getTone());
-    }
-
-    public DynamicColor tertiaryPaletteKeyColor() {
-        return DynamicColor.fromPalette(
-                (s) -> s.tertiaryPalette, (s) -> s.tertiaryPalette.getKeyColor().getTone());
-    }
-
-    public DynamicColor neutralPaletteKeyColor() {
-        return DynamicColor.fromPalette(
-                (s) -> s.neutralPalette, (s) -> s.neutralPalette.getKeyColor().getTone());
-    }
-
-    public DynamicColor neutralVariantPaletteKeyColor() {
-        return DynamicColor.fromPalette(
-                (s) -> s.neutralVariantPalette,
-                (s) -> s.neutralVariantPalette.getKeyColor().getTone());
-    }
-
-    @NonNull
-    public DynamicColor highestSurface(@NonNull DynamicScheme s) {
-        return s.isDark ? surfaceBright() : surfaceDim();
-    }
-
-    @NonNull
-    public DynamicColor background() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 6.0 : 98.0);
-    }
-
-    @NonNull
-    public DynamicColor onBackground() {
-        return DynamicColor.fromPalette(
-                (s) -> s.neutralPalette, (s) -> s.isDark ? 90.0 : 10.0, (s) -> background());
-    }
-
-    @NonNull
-    public DynamicColor surface() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 6.0 : 98.0);
-    }
-
-    @NonNull
-    public DynamicColor inverseSurface() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 90.0 : 20.0);
-    }
-
-    @NonNull
-    public DynamicColor surfaceBright() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 24.0 : 98.0);
-    }
-
-    @NonNull
-    public DynamicColor surfaceDim() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 6.0 : 87.0);
-    }
-
-    @NonNull
-    public DynamicColor surfaceContainerLowest() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 4.0 : 100.0);
-    }
-
-    @NonNull
-    public DynamicColor surfaceContainerLow() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 96.0);
-    }
-
-    @NonNull
-    public DynamicColor surfaceContainer() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 12.0 : 94.0);
-    }
-
-    @NonNull
-    public DynamicColor surfaceContainerHigh() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 17.0 : 92.0);
-    }
-
-    @NonNull
-    public DynamicColor surfaceContainerHighest() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 22.0 : 90.0);
-    }
-
-    @NonNull
-    public DynamicColor onSurface() {
-        return DynamicColor.fromPalette(
-                (s) -> s.neutralPalette, (s) -> s.isDark ? 90.0 : 10.0, this::highestSurface);
-    }
-
-    @NonNull
-    public DynamicColor inverseOnSurface() {
-        return DynamicColor.fromPalette(
-                (s) -> s.neutralPalette, (s) -> s.isDark ? 20.0 : 95.0, (s) -> inverseSurface());
-    }
-
-    @NonNull
-    public DynamicColor surfaceVariant() {
-        return DynamicColor.fromPalette((s) -> s.neutralVariantPalette, (s) -> s.isDark ? 30.0 : 90.0);
-    }
-
-    @NonNull
-    public DynamicColor onSurfaceVariant() {
-        return DynamicColor.fromPalette(
-                (s) -> s.neutralVariantPalette, (s) -> s.isDark ? 80.0 : 30.0, (s) -> surfaceVariant());
-    }
-
-    @NonNull
-    public DynamicColor outline() {
-        return DynamicColor.fromPalette(
-                (s) -> s.neutralVariantPalette, (s) -> s.isDark ? 60.0 : 50.0, this::highestSurface);
-    }
-
-    @NonNull
-    public DynamicColor outlineVariant() {
-        return DynamicColor.fromPalette(
-                (s) -> s.neutralVariantPalette, (s) -> s.isDark ? 30.0 : 80.0, this::highestSurface);
-    }
-
-    @NonNull
-    public DynamicColor shadow() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> 0.0);
-    }
-
-    @NonNull
-    public DynamicColor scrim() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> 0.0);
-    }
-
-    @NonNull
-    public DynamicColor surfaceTint() {
-        return DynamicColor.fromPalette((s) -> s.primaryPalette, (s) -> s.isDark ? 80.0 : 40.0);
-    }
-
-    @NonNull
-    public DynamicColor primaryContainer() {
-        return DynamicColor.fromPalette(
-                (s) -> s.primaryPalette,
-                (s) -> {
-                    if (isFidelity(s)) {
-                        return performAlbers(s.sourceColorHct, s);
-                    }
-                    if (isMonochrome(s)) {
-                        return s.isDark ? 85.0 : 25.0;
-                    }
-                    return s.isDark ? 30.0 : 90.0;
-                },
-                this::highestSurface);
-    }
-
-    @NonNull
-    public DynamicColor onPrimaryContainer() {
-        return DynamicColor.fromPalette(
-                (s) -> s.primaryPalette,
-                (s) -> {
-                    if (isFidelity(s)) {
-                        return DynamicColor.contrastingTone(primaryContainer().tone.apply(s), 4.5);
-                    }
-                    if (isMonochrome(s)) {
-                        return s.isDark ? 0.0 : 100.0;
-                    }
-                    return s.isDark ? 90.0 : 10.0;
-                },
-                (s) -> primaryContainer(),
-                null);
-    }
-
-    @NonNull
-    public DynamicColor primary() {
-        return DynamicColor.fromPalette(
-                (s) -> s.primaryPalette,
-                (s) -> {
-                    if (isMonochrome(s)) {
-                        return s.isDark ? 100.0 : 0.0;
-                    }
-                    return s.isDark ? 80.0 : 40.0;
-                },
-                this::highestSurface,
-                (s) ->
-                        new ToneDeltaConstraint(
-                                CONTAINER_ACCENT_TONE_DELTA,
-                                primaryContainer(),
-                                s.isDark ? TonePolarity.DARKER : TonePolarity.LIGHTER));
-    }
-
-    @NonNull
-    public DynamicColor inversePrimary() {
-        return DynamicColor.fromPalette(
-                (s) -> s.primaryPalette, (s) -> s.isDark ? 40.0 : 80.0, (s) -> inverseSurface());
-    }
-
-    @NonNull
-    public DynamicColor onPrimary() {
-        return DynamicColor.fromPalette(
-                (s) -> s.primaryPalette,
-                (s) -> {
-                    if (isMonochrome(s)) {
-                        return s.isDark ? 10.0 : 90.0;
-                    }
-                    return s.isDark ? 20.0 : 100.0;
-                },
-                (s) -> primary());
-    }
-
-    @NonNull
-    public DynamicColor secondaryContainer() {
-        return DynamicColor.fromPalette(
-                (s) -> s.secondaryPalette,
-                (s) -> {
-                    if (isMonochrome(s)) {
-                        return s.isDark ? 30.0 : 85.0;
-                    }
-                    final double initialTone = s.isDark ? 30.0 : 90.0;
-                    if (!isFidelity(s)) {
-                        return initialTone;
-                    }
-                    double answer =
-                            findDesiredChromaByTone(
-                                    s.secondaryPalette.getHue(),
-                                    s.secondaryPalette.getChroma(),
-                                    initialTone,
-                                    !s.isDark);
-                    answer = performAlbers(s.secondaryPalette.getHct(answer), s);
-                    return answer;
-                },
-                this::highestSurface);
-    }
-
-    @NonNull
-    public DynamicColor onSecondaryContainer() {
-        return DynamicColor.fromPalette(
-                (s) -> s.secondaryPalette,
-                (s) -> {
-                    if (!isFidelity(s)) {
-                        return s.isDark ? 90.0 : 10.0;
-                    }
-                    return DynamicColor.contrastingTone(secondaryContainer().tone.apply(s), 4.5);
-                },
-                (s) -> secondaryContainer());
-    }
-
-    @NonNull
-    public DynamicColor secondary() {
-        return DynamicColor.fromPalette(
-                (s) -> s.secondaryPalette,
-                (s) -> s.isDark ? 80.0 : 40.0,
-                this::highestSurface,
-                (s) ->
-                        new ToneDeltaConstraint(
-                                CONTAINER_ACCENT_TONE_DELTA,
-                                secondaryContainer(),
-                                s.isDark ? TonePolarity.DARKER : TonePolarity.LIGHTER));
-    }
-
-    @NonNull
-    public DynamicColor onSecondary() {
-        return DynamicColor.fromPalette(
-                (s) -> s.secondaryPalette,
-                (s) -> {
-                    if (isMonochrome(s)) {
-                        return s.isDark ? 10.0 : 100.0;
-                    }
-                    return s.isDark ? 20.0 : 100.0;
-                },
-                (s) -> secondary());
-    }
-
-    @NonNull
-    public DynamicColor tertiaryContainer() {
-        return DynamicColor.fromPalette(
-                (s) -> s.tertiaryPalette,
-                (s) -> {
-                    if (isMonochrome(s)) {
-                        return s.isDark ? 60.0 : 49.0;
-                    }
-                    if (!isFidelity(s)) {
-                        return s.isDark ? 30.0 : 90.0;
-                    }
-                    final double albersTone =
-                            performAlbers(s.tertiaryPalette.getHct(s.sourceColorHct.getTone()), s);
-                    final Hct proposedHct = s.tertiaryPalette.getHct(albersTone);
-                    return DislikeAnalyzer.fixIfDisliked(proposedHct).getTone();
-                },
-                this::highestSurface);
-    }
-
-    @NonNull
-    public DynamicColor onTertiaryContainer() {
-        return DynamicColor.fromPalette(
-                (s) -> s.tertiaryPalette,
-                (s) -> {
-                    if (isMonochrome(s)) {
-                        return s.isDark ? 0.0 : 100.0;
-                    }
-                    if (!isFidelity(s)) {
-                        return s.isDark ? 90.0 : 10.0;
-                    }
-                    return DynamicColor.contrastingTone(tertiaryContainer().tone.apply(s), 4.5);
-                },
-                (s) -> tertiaryContainer());
-    }
-
-    @NonNull
-    public DynamicColor tertiary() {
-        return DynamicColor.fromPalette(
-                (s) -> s.tertiaryPalette,
-                (s) -> {
-                    if (isMonochrome(s)) {
-                        return s.isDark ? 90.0 : 25.0;
-                    }
-                    return s.isDark ? 80.0 : 40.0;
-                },
-                this::highestSurface,
-                (s) ->
-                        new ToneDeltaConstraint(
-                                CONTAINER_ACCENT_TONE_DELTA,
-                                tertiaryContainer(),
-                                s.isDark ? TonePolarity.DARKER : TonePolarity.LIGHTER));
-    }
-
-    @NonNull
-    public DynamicColor onTertiary() {
-        return DynamicColor.fromPalette(
-                (s) -> s.tertiaryPalette,
-                (s) -> {
-                    if (isMonochrome(s)) {
-                        return s.isDark ? 10.0 : 90.0;
-                    }
-                    return s.isDark ? 20.0 : 100.0;
-                },
-                (s) -> tertiary());
-    }
-
-    @NonNull
-    public DynamicColor errorContainer() {
-        return DynamicColor.fromPalette(
-                (s) -> s.errorPalette, (s) -> s.isDark ? 30.0 : 90.0, this::highestSurface);
-    }
-
-    @NonNull
-    public DynamicColor onErrorContainer() {
-        return DynamicColor.fromPalette(
-                (s) -> s.errorPalette, (s) -> s.isDark ? 90.0 : 10.0, (s) -> errorContainer());
-    }
-
-    @NonNull
-    public DynamicColor error() {
-        return DynamicColor.fromPalette(
-                (s) -> s.errorPalette,
-                (s) -> s.isDark ? 80.0 : 40.0,
-                this::highestSurface,
-                (s) ->
-                        new ToneDeltaConstraint(
-                                CONTAINER_ACCENT_TONE_DELTA,
-                                errorContainer(),
-                                s.isDark ? TonePolarity.DARKER : TonePolarity.LIGHTER));
-    }
-
-    @NonNull
-    public DynamicColor onError() {
-        return DynamicColor.fromPalette(
-                (s) -> s.errorPalette, (s) -> s.isDark ? 20.0 : 100.0, (s) -> error());
-    }
-
-    @NonNull
-    public DynamicColor primaryFixed() {
-        return DynamicColor.fromPalette(
-                (s) -> s.primaryPalette,
-                (s) -> {
-                    if (isMonochrome(s)) {
-                        return 40.0;
-                    }
-                    return 90.0;
-                },
-                this::highestSurface);
-    }
-
-    @NonNull
-    public DynamicColor primaryFixedDim() {
-        return DynamicColor.fromPalette(
-                (s) -> s.primaryPalette,
-                (s) -> {
-                    if (isMonochrome(s)) {
-                        return 30.0;
-                    }
-                    return 80.0;
-                },
-                this::highestSurface);
-    }
-
-    @NonNull
-    public DynamicColor onPrimaryFixed() {
-        return DynamicColor.fromPalette(
-                (s) -> s.primaryPalette,
-                (s) -> {
-                    if (isMonochrome(s)) {
-                        return 100.0;
-                    }
-                    return 10.0;
-                },
-                (s) -> primaryFixedDim());
-    }
-
-    @NonNull
-    public DynamicColor onPrimaryFixedVariant() {
-        return DynamicColor.fromPalette(
-                (s) -> s.primaryPalette,
-                (s) -> {
-                    if (isMonochrome(s)) {
-                        return 90.0;
-                    }
-                    return 30.0;
-                },
-                (s) -> primaryFixedDim());
-    }
-
-    @NonNull
-    public DynamicColor secondaryFixed() {
-        return DynamicColor.fromPalette(
-                (s) -> s.secondaryPalette, (s) -> isMonochrome(s) ? 80.0 : 90.0, this::highestSurface);
-    }
-
-    @NonNull
-    public DynamicColor secondaryFixedDim() {
-        return DynamicColor.fromPalette(
-                (s) -> s.secondaryPalette, (s) -> isMonochrome(s) ? 70.0 : 80.0, this::highestSurface);
-    }
-
-    @NonNull
-    public DynamicColor onSecondaryFixed() {
-        return DynamicColor.fromPalette(
-                (s) -> s.secondaryPalette, (s) -> 10.0, (s) -> secondaryFixedDim());
-    }
-
-    @NonNull
-    public DynamicColor onSecondaryFixedVariant() {
-        return DynamicColor.fromPalette(
-                (s) -> s.secondaryPalette,
-                (s) -> isMonochrome(s) ? 25.0 : 30.0,
-                (s) -> secondaryFixedDim());
-    }
-
-    @NonNull
-    public DynamicColor tertiaryFixed() {
-        return DynamicColor.fromPalette(
-                (s) -> s.tertiaryPalette, (s) -> isMonochrome(s) ? 40.0 : 90.0, this::highestSurface);
-    }
-
-    @NonNull
-    public DynamicColor tertiaryFixedDim() {
-        return DynamicColor.fromPalette(
-                (s) -> s.tertiaryPalette, (s) -> isMonochrome(s) ? 30.0 : 80.0, this::highestSurface);
-    }
-
-    @NonNull
-    public DynamicColor onTertiaryFixed() {
-        return DynamicColor.fromPalette(
-                (s) -> s.tertiaryPalette, (s) -> isMonochrome(s) ? 100.0 : 10.0, (s) -> tertiaryFixedDim());
-    }
-
-    @NonNull
-    public DynamicColor onTertiaryFixedVariant() {
-        return DynamicColor.fromPalette(
-                (s) -> s.tertiaryPalette, (s) -> isMonochrome(s) ? 90.0 : 30.0, (s) -> tertiaryFixedDim());
-    }
-
-    /**
-     * These colors were present in Android framework before Android U, and used by MDC controls. They
-     * should be avoided, if possible. It's unclear if they're used on multiple backgrounds, and if
-     * they are, they can't be adjusted for contrast.* For now, they will be set with no background,
-     * and those won't adjust for contrast, avoiding issues.
-     *
-     * <p>* For example, if the same color is on a white background _and_ black background, there's no
-     * way to increase contrast with either without losing contrast with the other.
-     */
-    // colorControlActivated documented as colorAccent in M3 & GM3.
-    // colorAccent documented as colorSecondary in M3 and colorPrimary in GM3.
-    // Android used Material's Container as Primary/Secondary/Tertiary at launch.
-    // Therefore, this is a duplicated version of Primary Container.
-    @NonNull
-    public DynamicColor controlActivated() {
-        return DynamicColor.fromPalette((s) -> s.primaryPalette, (s) -> s.isDark ? 30.0 : 90.0, null);
-    }
-
-    // colorControlNormal documented as textColorSecondary in M3 & GM3.
-    // In Material, textColorSecondary points to onSurfaceVariant in the non-disabled state,
-    // which is Neutral Variant T30/80 in light/dark.
-    @NonNull
-    public DynamicColor controlNormal() {
-        return DynamicColor.fromPalette((s) -> s.neutralVariantPalette, (s) -> s.isDark ? 80.0 : 30.0);
-    }
-
-    // colorControlHighlight documented, in both M3 & GM3:
-    // Light mode: #1f000000 dark mode: #33ffffff.
-    // These are black and white with some alpha.
-    // 1F hex = 31 decimal; 31 / 255 = 12% alpha.
-    // 33 hex = 51 decimal; 51 / 255 = 20% alpha.
-    // DynamicColors do not support alpha currently, and _may_ not need it for this use case,
-    // depending on how MDC resolved alpha for the other cases.
-    // Returning black in dark mode, white in light mode.
-    @NonNull
-    public DynamicColor controlHighlight() {
-        return new DynamicColor(
-                s -> 0.0,
-                s -> 0.0,
-                s -> s.isDark ? 100.0 : 0.0,
-                s -> s.isDark ? 0.20 : 0.12,
-                null,
-                scheme ->
-                        DynamicColor.toneMinContrastDefault((s) -> s.isDark ? 100.0 : 0.0, null, scheme, null),
-                scheme ->
-                        DynamicColor.toneMaxContrastDefault((s) -> s.isDark ? 100.0 : 0.0, null, scheme, null),
-                null);
-    }
-
-    // textColorPrimaryInverse documented, in both M3 & GM3, documented as N10/N90.
-    @NonNull
-    public DynamicColor textPrimaryInverse() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 90.0);
-    }
-
-    // textColorSecondaryInverse and textColorTertiaryInverse both documented, in both M3 & GM3, as
-    // NV30/NV80
-    @NonNull
-    public DynamicColor textSecondaryAndTertiaryInverse() {
-        return DynamicColor.fromPalette((s) -> s.neutralVariantPalette, (s) -> s.isDark ? 30.0 : 80.0);
-    }
-
-    // textColorPrimaryInverseDisableOnly documented, in both M3 & GM3, as N10/N90
-    @NonNull
-    public DynamicColor textPrimaryInverseDisableOnly() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 90.0);
-    }
-
-    // textColorSecondaryInverse and textColorTertiaryInverse in disabled state both documented,
-    // in both M3 & GM3, as N10/N90
-    @NonNull
-    public DynamicColor textSecondaryAndTertiaryInverseDisabled() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 90.0);
-    }
-
-    // textColorHintInverse documented, in both M3 & GM3, as N10/N90
-    @NonNull
-    public DynamicColor textHintInverse() {
-        return DynamicColor.fromPalette((s) -> s.neutralPalette, (s) -> s.isDark ? 10.0 : 90.0);
-    }
-
-    private static ViewingConditions viewingConditionsForAlbers(DynamicScheme scheme) {
-        return ViewingConditions.defaultWithBackgroundLstar(scheme.isDark ? 30.0 : 80.0);
-    }
-
-    private static boolean isFidelity(DynamicScheme scheme) {
-        return scheme.variant == Variant.FIDELITY || scheme.variant == Variant.CONTENT;
-    }
-
-    private static boolean isMonochrome(DynamicScheme scheme) {
-        return scheme.variant == Variant.MONOCHROME;
-    }
-
-    static double findDesiredChromaByTone(
-            double hue, double chroma, double tone, boolean byDecreasingTone) {
-        double answer = tone;
-
-        Hct closestToChroma = Hct.from(hue, chroma, tone);
-        if (closestToChroma.getChroma() < chroma) {
-            double chromaPeak = closestToChroma.getChroma();
-            while (closestToChroma.getChroma() < chroma) {
-                answer += byDecreasingTone ? -1.0 : 1.0;
-                Hct potentialSolution = Hct.from(hue, chroma, answer);
-                if (chromaPeak > potentialSolution.getChroma()) {
-                    break;
-                }
-                if (Math.abs(potentialSolution.getChroma() - chroma) < 0.4) {
-                    break;
-                }
-
-                double potentialDelta = Math.abs(potentialSolution.getChroma() - chroma);
-                double currentDelta = Math.abs(closestToChroma.getChroma() - chroma);
-                if (potentialDelta < currentDelta) {
-                    closestToChroma = potentialSolution;
-                }
-                chromaPeak = Math.max(chromaPeak, potentialSolution.getChroma());
-            }
-        }
-
-        return answer;
-    }
-
-    static double performAlbers(Hct prealbers, DynamicScheme scheme) {
-        final Hct albersd = prealbers.inViewingConditions(viewingConditionsForAlbers(scheme));
-        if (DynamicColor.tonePrefersLightForeground(prealbers.getTone())
-                && !DynamicColor.toneAllowsLightForeground(albersd.getTone())) {
-            return DynamicColor.enableLightForeground(prealbers.getTone());
-        } else {
-            return DynamicColor.enableLightForeground(albersd.getTone());
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/ToneDeltaConstraint.java b/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/ToneDeltaConstraint.java
deleted file mode 100644
index 5fd1210..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/ToneDeltaConstraint.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.dynamiccolor;
-
-/**
- * Documents a constraint between two DynamicColors, in which their tones must have a certain
- * distance from each other.
- */
-public final class ToneDeltaConstraint {
-    public final double delta;
-    public final DynamicColor keepAway;
-    public final TonePolarity keepAwayPolarity;
-
-    /**
-     * @param delta            the difference in tone required
-     * @param keepAway         the color to distance in tone from
-     * @param keepAwayPolarity whether the color to keep away from must be lighter, darker, or no
-     *                         preference, in which case it should
-     */
-    public ToneDeltaConstraint(double delta, DynamicColor keepAway, TonePolarity keepAwayPolarity) {
-        this.delta = delta;
-        this.keepAway = keepAway;
-        this.keepAwayPolarity = keepAwayPolarity;
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/TonePolarity.java b/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/TonePolarity.java
deleted file mode 100644
index bb5649c..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/dynamiccolor/TonePolarity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.dynamiccolor;
-
-/** Describes the relationship in lightness between two colors. */
-public enum TonePolarity {
-    DARKER,
-    LIGHTER,
-    NO_PREFERENCE;
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/hct/Cam16.java b/packages/SystemUI/monet/src/com/android/systemui/monet/hct/Cam16.java
deleted file mode 100644
index 9ccc503..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/hct/Cam16.java
+++ /dev/null
@@ -1,454 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.hct;
-
-import static java.lang.Math.max;
-
-import com.android.systemui.monet.utils.ColorUtils;
-
-/**
- * CAM16, a color appearance model. Colors are not just defined by their hex code, but rather, a hex
- * code and viewing conditions.
- *
- * <p>CAM16 instances also have coordinates in the CAM16-UCS space, called J*, a*, b*, or jstar,
- * astar, bstar in code. CAM16-UCS is included in the CAM16 specification, and should be used when
- * measuring distances between colors.
- *
- * <p>In traditional color spaces, a color can be identified solely by the observer's measurement of
- * the color. Color appearance models such as CAM16 also use information about the environment where
- * the color was observed, known as the viewing conditions.
- *
- * <p>For example, white under the traditional assumption of a midday sun white point is accurately
- * measured as a slightly chromatic blue by CAM16. (roughly, hue 203, chroma 3, lightness 100)
- */
-public final class Cam16 {
-    // Transforms XYZ color space coordinates to 'cone'/'RGB' responses in CAM16.
-    static final double[][] XYZ_TO_CAM16RGB = {
-            {0.401288, 0.650173, -0.051461},
-            {-0.250268, 1.204414, 0.045854},
-            {-0.002079, 0.048952, 0.953127}
-    };
-
-    // Transforms 'cone'/'RGB' responses in CAM16 to XYZ color space coordinates.
-    static final double[][] CAM16RGB_TO_XYZ = {
-            {1.8620678, -1.0112547, 0.14918678},
-            {0.38752654, 0.62144744, -0.00897398},
-            {-0.01584150, -0.03412294, 1.0499644}
-    };
-
-    // CAM16 color dimensions, see getters for documentation.
-    private final double hue;
-    private final double chroma;
-    private final double j;
-    private final double q;
-    private final double m;
-    private final double s;
-
-    // Coordinates in UCS space. Used to determine color distance, like delta E equations in L*a*b*.
-    private final double jstar;
-    private final double astar;
-    private final double bstar;
-
-    // Avoid allocations during conversion by pre-allocating an array.
-    private final double[] tempArray = new double[]{0.0, 0.0, 0.0};
-
-    /**
-     * CAM16 instances also have coordinates in the CAM16-UCS space, called J*, a*, b*, or jstar,
-     * astar, bstar in code. CAM16-UCS is included in the CAM16 specification, and is used to
-     * measure
-     * distances between colors.
-     */
-    double distance(Cam16 other) {
-        double dJ = getJstar() - other.getJstar();
-        double dA = getAstar() - other.getAstar();
-        double dB = getBstar() - other.getBstar();
-        double dEPrime = Math.sqrt(dJ * dJ + dA * dA + dB * dB);
-        double dE = 1.41 * Math.pow(dEPrime, 0.63);
-        return dE;
-    }
-
-    /** Hue in CAM16 */
-    public double getHue() {
-        return hue;
-    }
-
-    /** Chroma in CAM16 */
-    public double getChroma() {
-        return chroma;
-    }
-
-    /** Lightness in CAM16 */
-    public double getJ() {
-        return j;
-    }
-
-    /**
-     * Brightness in CAM16.
-     *
-     * <p>Prefer lightness, brightness is an absolute quantity. For example, a sheet of white paper
-     * is
-     * much brighter viewed in sunlight than in indoor light, but it is the lightest object under
-     * any
-     * lighting.
-     */
-    public double getQ() {
-        return q;
-    }
-
-    /**
-     * Colorfulness in CAM16.
-     *
-     * <p>Prefer chroma, colorfulness is an absolute quantity. For example, a yellow toy car is much
-     * more colorful outside than inside, but it has the same chroma in both environments.
-     */
-    public double getM() {
-        return m;
-    }
-
-    /**
-     * Saturation in CAM16.
-     *
-     * <p>Colorfulness in proportion to brightness. Prefer chroma, saturation measures colorfulness
-     * relative to the color's own brightness, where chroma is colorfulness relative to white.
-     */
-    public double getS() {
-        return s;
-    }
-
-    /** Lightness coordinate in CAM16-UCS */
-    public double getJstar() {
-        return jstar;
-    }
-
-    /** a* coordinate in CAM16-UCS */
-    public double getAstar() {
-        return astar;
-    }
-
-    /** b* coordinate in CAM16-UCS */
-    public double getBstar() {
-        return bstar;
-    }
-
-    /**
-     * All of the CAM16 dimensions can be calculated from 3 of the dimensions, in the following
-     * combinations: - {j or q} and {c, m, or s} and hue - jstar, astar, bstar Prefer using a static
-     * method that constructs from 3 of those dimensions. This constructor is intended for those
-     * methods to use to return all possible dimensions.
-     *
-     * @param hue    for example, red, orange, yellow, green, etc.
-     * @param chroma informally, colorfulness / color intensity. like saturation in HSL, except
-     *               perceptually accurate.
-     * @param j      lightness
-     * @param q      brightness; ratio of lightness to white point's lightness
-     * @param m      colorfulness
-     * @param s      saturation; ratio of chroma to white point's chroma
-     * @param jstar  CAM16-UCS J coordinate
-     * @param astar  CAM16-UCS a coordinate
-     * @param bstar  CAM16-UCS b coordinate
-     */
-    private Cam16(
-            double hue,
-            double chroma,
-            double j,
-            double q,
-            double m,
-            double s,
-            double jstar,
-            double astar,
-            double bstar) {
-        this.hue = hue;
-        this.chroma = chroma;
-        this.j = j;
-        this.q = q;
-        this.m = m;
-        this.s = s;
-        this.jstar = jstar;
-        this.astar = astar;
-        this.bstar = bstar;
-    }
-
-    /**
-     * Create a CAM16 color from a color, assuming the color was viewed in default viewing
-     * conditions.
-     *
-     * @param argb ARGB representation of a color.
-     */
-    public static Cam16 fromInt(int argb) {
-        return fromIntInViewingConditions(argb, ViewingConditions.DEFAULT);
-    }
-
-    /**
-     * Create a CAM16 color from a color in defined viewing conditions.
-     *
-     * @param argb              ARGB representation of a color.
-     * @param viewingConditions Information about the environment where the color was observed.
-     */
-    // The RGB => XYZ conversion matrix elements are derived scientific constants. While the values
-    // may differ at runtime due to floating point imprecision, keeping the values the same, and
-    // accurate, across implementations takes precedence.
-    @SuppressWarnings("FloatingPointLiteralPrecision")
-    static Cam16 fromIntInViewingConditions(int argb, ViewingConditions viewingConditions) {
-        // Transform ARGB int to XYZ
-        int red = (argb & 0x00ff0000) >> 16;
-        int green = (argb & 0x0000ff00) >> 8;
-        int blue = (argb & 0x000000ff);
-        double redL = ColorUtils.linearized(red);
-        double greenL = ColorUtils.linearized(green);
-        double blueL = ColorUtils.linearized(blue);
-        double x = 0.41233895 * redL + 0.35762064 * greenL + 0.18051042 * blueL;
-        double y = 0.2126 * redL + 0.7152 * greenL + 0.0722 * blueL;
-        double z = 0.01932141 * redL + 0.11916382 * greenL + 0.95034478 * blueL;
-
-        return fromXyzInViewingConditions(x, y, z, viewingConditions);
-    }
-
-    static Cam16 fromXyzInViewingConditions(
-            double x, double y, double z, ViewingConditions viewingConditions) {
-        // Transform XYZ to 'cone'/'rgb' responses
-        double[][] matrix = XYZ_TO_CAM16RGB;
-        double rT = (x * matrix[0][0]) + (y * matrix[0][1]) + (z * matrix[0][2]);
-        double gT = (x * matrix[1][0]) + (y * matrix[1][1]) + (z * matrix[1][2]);
-        double bT = (x * matrix[2][0]) + (y * matrix[2][1]) + (z * matrix[2][2]);
-
-        // Discount illuminant
-        double rD = viewingConditions.getRgbD()[0] * rT;
-        double gD = viewingConditions.getRgbD()[1] * gT;
-        double bD = viewingConditions.getRgbD()[2] * bT;
-
-        // Chromatic adaptation
-        double rAF = Math.pow(viewingConditions.getFl() * Math.abs(rD) / 100.0, 0.42);
-        double gAF = Math.pow(viewingConditions.getFl() * Math.abs(gD) / 100.0, 0.42);
-        double bAF = Math.pow(viewingConditions.getFl() * Math.abs(bD) / 100.0, 0.42);
-        double rA = Math.signum(rD) * 400.0 * rAF / (rAF + 27.13);
-        double gA = Math.signum(gD) * 400.0 * gAF / (gAF + 27.13);
-        double bA = Math.signum(bD) * 400.0 * bAF / (bAF + 27.13);
-
-        // redness-greenness
-        double a = (11.0 * rA + -12.0 * gA + bA) / 11.0;
-        // yellowness-blueness
-        double b = (rA + gA - 2.0 * bA) / 9.0;
-
-        // auxiliary components
-        double u = (20.0 * rA + 20.0 * gA + 21.0 * bA) / 20.0;
-        double p2 = (40.0 * rA + 20.0 * gA + bA) / 20.0;
-
-        // hue
-        double atan2 = Math.atan2(b, a);
-        double atanDegrees = Math.toDegrees(atan2);
-        double hue =
-                atanDegrees < 0
-                        ? atanDegrees + 360.0
-                        : atanDegrees >= 360 ? atanDegrees - 360.0 : atanDegrees;
-        double hueRadians = Math.toRadians(hue);
-
-        // achromatic response to color
-        double ac = p2 * viewingConditions.getNbb();
-
-        // CAM16 lightness and brightness
-        double j =
-                100.0
-                        * Math.pow(
-                        ac / viewingConditions.getAw(),
-                        viewingConditions.getC() * viewingConditions.getZ());
-        double q =
-                4.0
-                        / viewingConditions.getC()
-                        * Math.sqrt(j / 100.0)
-                        * (viewingConditions.getAw() + 4.0)
-                        * viewingConditions.getFlRoot();
-
-        // CAM16 chroma, colorfulness, and saturation.
-        double huePrime = (hue < 20.14) ? hue + 360 : hue;
-        double eHue = 0.25 * (Math.cos(Math.toRadians(huePrime) + 2.0) + 3.8);
-        double p1 = 50000.0 / 13.0 * eHue * viewingConditions.getNc() * viewingConditions.getNcb();
-        double t = p1 * Math.hypot(a, b) / (u + 0.305);
-        double alpha =
-                Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73) * Math.pow(t, 0.9);
-        // CAM16 chroma, colorfulness, saturation
-        double c = alpha * Math.sqrt(j / 100.0);
-        double m = c * viewingConditions.getFlRoot();
-        double s =
-                50.0 * Math.sqrt(
-                        (alpha * viewingConditions.getC()) / (viewingConditions.getAw() + 4.0));
-
-        // CAM16-UCS components
-        double jstar = (1.0 + 100.0 * 0.007) * j / (1.0 + 0.007 * j);
-        double mstar = 1.0 / 0.0228 * Math.log1p(0.0228 * m);
-        double astar = mstar * Math.cos(hueRadians);
-        double bstar = mstar * Math.sin(hueRadians);
-
-        return new Cam16(hue, c, j, q, m, s, jstar, astar, bstar);
-    }
-
-    /**
-     * @param j CAM16 lightness
-     * @param c CAM16 chroma
-     * @param h CAM16 hue
-     */
-    static Cam16 fromJch(double j, double c, double h) {
-        return fromJchInViewingConditions(j, c, h, ViewingConditions.DEFAULT);
-    }
-
-    /**
-     * @param j                 CAM16 lightness
-     * @param c                 CAM16 chroma
-     * @param h                 CAM16 hue
-     * @param viewingConditions Information about the environment where the color was observed.
-     */
-    private static Cam16 fromJchInViewingConditions(
-            double j, double c, double h, ViewingConditions viewingConditions) {
-        double q =
-                4.0
-                        / viewingConditions.getC()
-                        * Math.sqrt(j / 100.0)
-                        * (viewingConditions.getAw() + 4.0)
-                        * viewingConditions.getFlRoot();
-        double m = c * viewingConditions.getFlRoot();
-        double alpha = c / Math.sqrt(j / 100.0);
-        double s =
-                50.0 * Math.sqrt(
-                        (alpha * viewingConditions.getC()) / (viewingConditions.getAw() + 4.0));
-
-        double hueRadians = Math.toRadians(h);
-        double jstar = (1.0 + 100.0 * 0.007) * j / (1.0 + 0.007 * j);
-        double mstar = 1.0 / 0.0228 * Math.log1p(0.0228 * m);
-        double astar = mstar * Math.cos(hueRadians);
-        double bstar = mstar * Math.sin(hueRadians);
-        return new Cam16(h, c, j, q, m, s, jstar, astar, bstar);
-    }
-
-    /**
-     * Create a CAM16 color from CAM16-UCS coordinates.
-     *
-     * @param jstar CAM16-UCS lightness.
-     * @param astar CAM16-UCS a dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the Y
-     *              axis.
-     * @param bstar CAM16-UCS b dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the X
-     *              axis.
-     */
-    public static Cam16 fromUcs(double jstar, double astar, double bstar) {
-
-        return fromUcsInViewingConditions(jstar, astar, bstar, ViewingConditions.DEFAULT);
-    }
-
-    /**
-     * Create a CAM16 color from CAM16-UCS coordinates in defined viewing conditions.
-     *
-     * @param jstar             CAM16-UCS lightness.
-     * @param astar             CAM16-UCS a dimension. Like a* in L*a*b*, it is a Cartesian
-     *                          coordinate on the Y
-     *                          axis.
-     * @param bstar             CAM16-UCS b dimension. Like a* in L*a*b*, it is a Cartesian
-     *                          coordinate on the X
-     *                          axis.
-     * @param viewingConditions Information about the environment where the color was observed.
-     */
-    public static Cam16 fromUcsInViewingConditions(
-            double jstar, double astar, double bstar, ViewingConditions viewingConditions) {
-
-        double m = Math.hypot(astar, bstar);
-        double m2 = Math.expm1(m * 0.0228) / 0.0228;
-        double c = m2 / viewingConditions.getFlRoot();
-        double h = Math.atan2(bstar, astar) * (180.0 / Math.PI);
-        if (h < 0.0) {
-            h += 360.0;
-        }
-        double j = jstar / (1. - (jstar - 100.) * 0.007);
-        return fromJchInViewingConditions(j, c, h, viewingConditions);
-    }
-
-    /**
-     * ARGB representation of the color. Assumes the color was viewed in default viewing conditions,
-     * which are near-identical to the default viewing conditions for sRGB.
-     */
-    public int toInt() {
-        return viewed(ViewingConditions.DEFAULT);
-    }
-
-    /**
-     * ARGB representation of the color, in defined viewing conditions.
-     *
-     * @param viewingConditions Information about the environment where the color will be viewed.
-     * @return ARGB representation of color
-     */
-    int viewed(ViewingConditions viewingConditions) {
-        double[] xyz = xyzInViewingConditions(viewingConditions, tempArray);
-        return ColorUtils.argbFromXyz(xyz[0], xyz[1], xyz[2]);
-    }
-
-    double[] xyzInViewingConditions(ViewingConditions viewingConditions, double[] returnArray) {
-        double alpha =
-                (getChroma() == 0.0 || getJ() == 0.0) ? 0.0 : getChroma() / Math.sqrt(
-                        getJ() / 100.0);
-
-        double t =
-                Math.pow(
-                        alpha / Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73),
-                        1.0 / 0.9);
-        double hRad = Math.toRadians(getHue());
-
-        double eHue = 0.25 * (Math.cos(hRad + 2.0) + 3.8);
-        double ac =
-                viewingConditions.getAw()
-                        * Math.pow(getJ() / 100.0,
-                        1.0 / viewingConditions.getC() / viewingConditions.getZ());
-        double p1 =
-                eHue * (50000.0 / 13.0) * viewingConditions.getNc() * viewingConditions.getNcb();
-        double p2 = (ac / viewingConditions.getNbb());
-
-        double hSin = Math.sin(hRad);
-        double hCos = Math.cos(hRad);
-
-        double gamma = 23.0 * (p2 + 0.305) * t / (23.0 * p1 + 11.0 * t * hCos + 108.0 * t * hSin);
-        double a = gamma * hCos;
-        double b = gamma * hSin;
-        double rA = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0;
-        double gA = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0;
-        double bA = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0;
-
-        double rCBase = max(0, (27.13 * Math.abs(rA)) / (400.0 - Math.abs(rA)));
-        double rC =
-                Math.signum(rA) * (100.0 / viewingConditions.getFl()) * Math.pow(rCBase,
-                        1.0 / 0.42);
-        double gCBase = max(0, (27.13 * Math.abs(gA)) / (400.0 - Math.abs(gA)));
-        double gC =
-                Math.signum(gA) * (100.0 / viewingConditions.getFl()) * Math.pow(gCBase,
-                        1.0 / 0.42);
-        double bCBase = max(0, (27.13 * Math.abs(bA)) / (400.0 - Math.abs(bA)));
-        double bC =
-                Math.signum(bA) * (100.0 / viewingConditions.getFl()) * Math.pow(bCBase,
-                        1.0 / 0.42);
-        double rF = rC / viewingConditions.getRgbD()[0];
-        double gF = gC / viewingConditions.getRgbD()[1];
-        double bF = bC / viewingConditions.getRgbD()[2];
-
-        double[][] matrix = CAM16RGB_TO_XYZ;
-        double x = (rF * matrix[0][0]) + (gF * matrix[0][1]) + (bF * matrix[0][2]);
-        double y = (rF * matrix[1][0]) + (gF * matrix[1][1]) + (bF * matrix[1][2]);
-        double z = (rF * matrix[2][0]) + (gF * matrix[2][1]) + (bF * matrix[2][2]);
-
-        if (returnArray != null) {
-            returnArray[0] = x;
-            returnArray[1] = y;
-            returnArray[2] = z;
-            return returnArray;
-        } else {
-            return new double[]{x, y, z};
-        }
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/hct/Hct.java b/packages/SystemUI/monet/src/com/android/systemui/monet/hct/Hct.java
deleted file mode 100644
index 6cff99e..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/hct/Hct.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.hct;
-
-import com.android.systemui.monet.utils.ColorUtils;
-
-/**
- * A color system built using CAM16 hue and chroma, and L* from L*a*b*.
- *
- * <p>Using L* creates a link between the color system, contrast, and thus accessibility. Contrast
- * ratio depends on relative luminance, or Y in the XYZ color space. L*, or perceptual luminance can
- * be calculated from Y.
- *
- * <p>Unlike Y, L* is linear to human perception, allowing trivial creation of accurate color tones.
- *
- * <p>Unlike contrast ratio, measuring contrast in L* is linear, and simple to calculate. A
- * difference of 40 in HCT tone guarantees a contrast ratio >= 3.0, and a difference of 50
- * guarantees a contrast ratio >= 4.5.
- */
-
-/**
- * HCT, hue, chroma, and tone. A color system that provides a perceptually accurate color
- * measurement system that can also accurately render what colors will appear as in different
- * lighting environments.
- */
-public final class Hct {
-    private double hue;
-    private double chroma;
-    private double tone;
-    private int argb;
-
-    /**
-     * Create an HCT color from hue, chroma, and tone.
-     *
-     * @param hue    0 <= hue < 360; invalid values are corrected.
-     * @param chroma 0 <= chroma < ?; Informally, colorfulness. The color returned may be lower
-     *               than
-     *               the requested chroma. Chroma has a different maximum for any given hue and
-     *               tone.
-     * @param tone   0 <= tone <= 100; invalid values are corrected.
-     * @return HCT representation of a color in default viewing conditions.
-     */
-    public static Hct from(double hue, double chroma, double tone) {
-        int argb = HctSolver.solveToInt(hue, chroma, tone);
-        return new Hct(argb);
-    }
-
-    /**
-     * Create an HCT color from a color.
-     *
-     * @param argb ARGB representation of a color.
-     * @return HCT representation of a color in default viewing conditions
-     */
-    public static Hct fromInt(int argb) {
-        return new Hct(argb);
-    }
-
-    private Hct(int argb) {
-        setInternalState(argb);
-    }
-
-    public double getHue() {
-        return hue;
-    }
-
-    public double getChroma() {
-        return chroma;
-    }
-
-    public double getTone() {
-        return tone;
-    }
-
-    /** This color converted to ARGB, i.e. a hex code. */
-    public int toInt() {
-        return argb;
-    }
-
-    /**
-     * Set the hue of this color. Chroma may decrease because chroma has a different maximum for any
-     * given hue and tone.
-     *
-     * @param newHue 0 <= newHue < 360; invalid values are corrected.
-     */
-    public void setHue(double newHue) {
-        setInternalState(HctSolver.solveToInt(newHue, chroma, tone));
-    }
-
-    /**
-     * Set the chroma of this color. Chroma may decrease because chroma has a different maximum for
-     * any given hue and tone.
-     *
-     * @param newChroma 0 <= newChroma < ?
-     */
-    public void setChroma(double newChroma) {
-        setInternalState(HctSolver.solveToInt(hue, newChroma, tone));
-    }
-
-    /**
-     * Set the tone of this color. Chroma may decrease because chroma has a different maximum for
-     * any
-     * given hue and tone.
-     *
-     * @param newTone 0 <= newTone <= 100; invalid valids are corrected.
-     */
-    public void setTone(double newTone) {
-        setInternalState(HctSolver.solveToInt(hue, chroma, newTone));
-    }
-
-    /**
-     * Translate a color into different ViewingConditions.
-     *
-     * <p>Colors change appearance. They look different with lights on versus off, the same color,
-     * as
-     * in hex code, on white looks different when on black. This is called color relativity, most
-     * famously explicated by Josef Albers in Interaction of Color.
-     *
-     * <p>In color science, color appearance models can account for this and calculate the
-     * appearance
-     * of a color in different settings. HCT is based on CAM16, a color appearance model, and uses
-     * it
-     * to make these calculations.
-     *
-     * <p>See ViewingConditions.make for parameters affecting color appearance.
-     */
-    public Hct inViewingConditions(ViewingConditions vc) {
-        // 1. Use CAM16 to find XYZ coordinates of color in specified VC.
-        Cam16 cam16 = Cam16.fromInt(toInt());
-        double[] viewedInVc = cam16.xyzInViewingConditions(vc, null);
-
-        // 2. Create CAM16 of those XYZ coordinates in default VC.
-        Cam16 recastInVc =
-                Cam16.fromXyzInViewingConditions(
-                        viewedInVc[0], viewedInVc[1], viewedInVc[2], ViewingConditions.DEFAULT);
-
-        // 3. Create HCT from:
-        // - CAM16 using default VC with XYZ coordinates in specified VC.
-        // - L* converted from Y in XYZ coordinates in specified VC.
-        return Hct.from(
-                recastInVc.getHue(), recastInVc.getChroma(), ColorUtils.lstarFromY(viewedInVc[1]));
-    }
-
-    private void setInternalState(int argb) {
-        this.argb = argb;
-        Cam16 cam = Cam16.fromInt(argb);
-        hue = cam.getHue();
-        chroma = cam.getChroma();
-        this.tone = ColorUtils.lstarFromArgb(argb);
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/hct/HctSolver.java b/packages/SystemUI/monet/src/com/android/systemui/monet/hct/HctSolver.java
deleted file mode 100644
index 7083222..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/hct/HctSolver.java
+++ /dev/null
@@ -1,677 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.hct;
-
-import com.android.systemui.monet.utils.ColorUtils;
-import com.android.systemui.monet.utils.MathUtils;
-
-/** A class that solves the HCT equation. */
-public class HctSolver {
-    private HctSolver() {
-    }
-
-    static final double[][] SCALED_DISCOUNT_FROM_LINRGB =
-            new double[][]{
-                    new double[]{
-                            0.001200833568784504, 0.002389694492170889, 0.0002795742885861124,
-                    },
-                    new double[]{
-                            0.0005891086651375999, 0.0029785502573438758, 0.0003270666104008398,
-                    },
-                    new double[]{
-                            0.00010146692491640572, 0.0005364214359186694, 0.0032979401770712076,
-                    },
-            };
-
-    static final double[][] LINRGB_FROM_SCALED_DISCOUNT =
-            new double[][]{
-                    new double[]{
-                            1373.2198709594231, -1100.4251190754821, -7.278681089101213,
-                    },
-                    new double[]{
-                            -271.815969077903, 559.6580465940733, -32.46047482791194,
-                    },
-                    new double[]{
-                            1.9622899599665666, -57.173814538844006, 308.7233197812385,
-                    },
-            };
-
-    static final double[] Y_FROM_LINRGB = new double[]{0.2126, 0.7152, 0.0722};
-
-    static final double[] CRITICAL_PLANES =
-            new double[]{
-                    0.015176349177441876,
-                    0.045529047532325624,
-                    0.07588174588720938,
-                    0.10623444424209313,
-                    0.13658714259697685,
-                    0.16693984095186062,
-                    0.19729253930674434,
-                    0.2276452376616281,
-                    0.2579979360165119,
-                    0.28835063437139563,
-                    0.3188300904430532,
-                    0.350925934958123,
-                    0.3848314933096426,
-                    0.42057480301049466,
-                    0.458183274052838,
-                    0.4976837250274023,
-                    0.5391024159806381,
-                    0.5824650784040898,
-                    0.6277969426914107,
-                    0.6751227633498623,
-                    0.7244668422128921,
-                    0.775853049866786,
-                    0.829304845476233,
-                    0.8848452951698498,
-                    0.942497089126609,
-                    1.0022825574869039,
-                    1.0642236851973577,
-                    1.1283421258858297,
-                    1.1946592148522128,
-                    1.2631959812511864,
-                    1.3339731595349034,
-                    1.407011200216447,
-                    1.4823302800086415,
-                    1.5599503113873272,
-                    1.6398909516233677,
-                    1.7221716113234105,
-                    1.8068114625156377,
-                    1.8938294463134073,
-                    1.9832442801866852,
-                    2.075074464868551,
-                    2.1693382909216234,
-                    2.2660538449872063,
-                    2.36523901573795,
-                    2.4669114995532007,
-                    2.5710888059345764,
-                    2.6777882626779785,
-                    2.7870270208169257,
-                    2.898822059350997,
-                    3.0131901897720907,
-                    3.1301480604002863,
-                    3.2497121605402226,
-                    3.3718988244681087,
-                    3.4967242352587946,
-                    3.624204428461639,
-                    3.754355295633311,
-                    3.887192587735158,
-                    4.022731918402185,
-                    4.160988767090289,
-                    4.301978482107941,
-                    4.445716283538092,
-                    4.592217266055746,
-                    4.741496401646282,
-                    4.893568542229298,
-                    5.048448422192488,
-                    5.20615066083972,
-                    5.3666897647573375,
-                    5.5300801301023865,
-                    5.696336044816294,
-                    5.865471690767354,
-                    6.037501145825082,
-                    6.212438385869475,
-                    6.390297286737924,
-                    6.571091626112461,
-                    6.7548350853498045,
-                    6.941541251256611,
-                    7.131223617812143,
-                    7.323895587840543,
-                    7.5195704746346665,
-                    7.7182615035334345,
-                    7.919981813454504,
-                    8.124744458384042,
-                    8.332562408825165,
-                    8.543448553206703,
-                    8.757415699253682,
-                    8.974476575321063,
-                    9.194643831691977,
-                    9.417930041841839,
-                    9.644347703669503,
-                    9.873909240696694,
-                    10.106627003236781,
-                    10.342513269534024,
-                    10.58158024687427,
-                    10.8238400726681,
-                    11.069304815507364,
-                    11.317986476196008,
-                    11.569896988756009,
-                    11.825048221409341,
-                    12.083451977536606,
-                    12.345119996613247,
-                    12.610063955123938,
-                    12.878295467455942,
-                    13.149826086772048,
-                    13.42466730586372,
-                    13.702830557985108,
-                    13.984327217668513,
-                    14.269168601521828,
-                    14.55736596900856,
-                    14.848930523210871,
-                    15.143873411576273,
-                    15.44220572664832,
-                    15.743938506781891,
-                    16.04908273684337,
-                    16.35764934889634,
-                    16.66964922287304,
-                    16.985093187232053,
-                    17.30399201960269,
-                    17.62635644741625,
-                    17.95219714852476,
-                    18.281524751807332,
-                    18.614349837764564,
-                    18.95068293910138,
-                    19.290534541298456,
-                    19.633915083172692,
-                    19.98083495742689,
-                    20.331304511189067,
-                    20.685334046541502,
-                    21.042933821039977,
-                    21.404114048223256,
-                    21.76888489811322,
-                    22.137256497705877,
-                    22.50923893145328,
-                    22.884842241736916,
-                    23.264076429332462,
-                    23.6469514538663,
-                    24.033477234264016,
-                    24.42366364919083,
-                    24.817520537484558,
-                    25.21505769858089,
-                    25.61628489293138,
-                    26.021211842414342,
-                    26.429848230738664,
-                    26.842203703840827,
-                    27.258287870275353,
-                    27.678110301598522,
-                    28.10168053274597,
-                    28.529008062403893,
-                    28.96010235337422,
-                    29.39497283293396,
-                    29.83362889318845,
-                    30.276079891419332,
-                    30.722335150426627,
-                    31.172403958865512,
-                    31.62629557157785,
-                    32.08401920991837,
-                    32.54558406207592,
-                    33.010999283389665,
-                    33.4802739966603,
-                    33.953417292456834,
-                    34.430438229418264,
-                    34.911345834551085,
-                    35.39614910352207,
-                    35.88485700094671,
-                    36.37747846067349,
-                    36.87402238606382,
-                    37.37449765026789,
-                    37.87891309649659,
-                    38.38727753828926,
-                    38.89959975977785,
-                    39.41588851594697,
-                    39.93615253289054,
-                    40.460400508064545,
-                    40.98864111053629,
-                    41.520882981230194,
-                    42.05713473317016,
-                    42.597404951718396,
-                    43.141702194811224,
-                    43.6900349931913,
-                    44.24241185063697,
-                    44.798841244188324,
-                    45.35933162437017,
-                    45.92389141541209,
-                    46.49252901546552,
-                    47.065252796817916,
-                    47.64207110610409,
-                    48.22299226451468,
-                    48.808024568002054,
-                    49.3971762874833,
-                    49.9904556690408,
-                    50.587870934119984,
-                    51.189430279724725,
-                    51.79514187861014,
-                    52.40501387947288,
-                    53.0190544071392,
-                    53.637271562750364,
-                    54.259673423945976,
-                    54.88626804504493,
-                    55.517063457223934,
-                    56.15206766869424,
-                    56.79128866487574,
-                    57.43473440856916,
-                    58.08241284012621,
-                    58.734331877617365,
-                    59.39049941699807,
-                    60.05092333227251,
-                    60.715611475655585,
-                    61.38457167773311,
-                    62.057811747619894,
-                    62.7353394731159,
-                    63.417162620860914,
-                    64.10328893648692,
-                    64.79372614476921,
-                    65.48848194977529,
-                    66.18756403501224,
-                    66.89098006357258,
-                    67.59873767827808,
-                    68.31084450182222,
-                    69.02730813691093,
-                    69.74813616640164,
-                    70.47333615344107,
-                    71.20291564160104,
-                    71.93688215501312,
-                    72.67524319850172,
-                    73.41800625771542,
-                    74.16517879925733,
-                    74.9167682708136,
-                    75.67278210128072,
-                    76.43322770089146,
-                    77.1981124613393,
-                    77.96744375590167,
-                    78.74122893956174,
-                    79.51947534912904,
-                    80.30219030335869,
-                    81.08938110306934,
-                    81.88105503125999,
-                    82.67721935322541,
-                    83.4778813166706,
-                    84.28304815182372,
-                    85.09272707154808,
-                    85.90692527145302,
-                    86.72564993000343,
-                    87.54890820862819,
-                    88.3767072518277,
-                    89.2090541872801,
-                    90.04595612594655,
-                    90.88742016217518,
-                    91.73345337380438,
-                    92.58406282226491,
-                    93.43925555268066,
-                    94.29903859396902,
-                    95.16341895893969,
-                    96.03240364439274,
-                    96.9059996312159,
-                    97.78421388448044,
-                    98.6670533535366,
-                    99.55452497210776,
-            };
-
-    /**
-     * Sanitizes a small enough angle in radians.
-     *
-     * @param angle An angle in radians; must not deviate too much from 0.
-     * @return A coterminal angle between 0 and 2pi.
-     */
-    static double sanitizeRadians(double angle) {
-        return (angle + Math.PI * 8) % (Math.PI * 2);
-    }
-
-    /**
-     * Delinearizes an RGB component, returning a floating-point number.
-     *
-     * @param rgbComponent 0.0 <= rgb_component <= 100.0, represents linear R/G/B channel
-     * @return 0.0 <= output <= 255.0, color channel converted to regular RGB space
-     */
-    static double trueDelinearized(double rgbComponent) {
-        double normalized = rgbComponent / 100.0;
-        double delinearized = 0.0;
-        if (normalized <= 0.0031308) {
-            delinearized = normalized * 12.92;
-        } else {
-            delinearized = 1.055 * Math.pow(normalized, 1.0 / 2.4) - 0.055;
-        }
-        return delinearized * 255.0;
-    }
-
-    static double chromaticAdaptation(double component) {
-        double af = Math.pow(Math.abs(component), 0.42);
-        return MathUtils.signum(component) * 400.0 * af / (af + 27.13);
-    }
-
-    /**
-     * Returns the hue of a linear RGB color in CAM16.
-     *
-     * @param linrgb The linear RGB coordinates of a color.
-     * @return The hue of the color in CAM16, in radians.
-     */
-    static double hueOf(double[] linrgb) {
-        double[] scaledDiscount = MathUtils.matrixMultiply(linrgb, SCALED_DISCOUNT_FROM_LINRGB);
-        double rA = chromaticAdaptation(scaledDiscount[0]);
-        double gA = chromaticAdaptation(scaledDiscount[1]);
-        double bA = chromaticAdaptation(scaledDiscount[2]);
-        // redness-greenness
-        double a = (11.0 * rA + -12.0 * gA + bA) / 11.0;
-        // yellowness-blueness
-        double b = (rA + gA - 2.0 * bA) / 9.0;
-        return Math.atan2(b, a);
-    }
-
-    static boolean areInCyclicOrder(double a, double b, double c) {
-        double deltaAB = sanitizeRadians(b - a);
-        double deltaAC = sanitizeRadians(c - a);
-        return deltaAB < deltaAC;
-    }
-
-    /**
-     * Solves the lerp equation.
-     *
-     * @param source The starting number.
-     * @param mid    The number in the middle.
-     * @param target The ending number.
-     * @return A number t such that lerp(source, target, t) = mid.
-     */
-    static double intercept(double source, double mid, double target) {
-        return (mid - source) / (target - source);
-    }
-
-    static double[] lerpPoint(double[] source, double t, double[] target) {
-        return new double[]{
-                source[0] + (target[0] - source[0]) * t,
-                source[1] + (target[1] - source[1]) * t,
-                source[2] + (target[2] - source[2]) * t,
-        };
-    }
-
-    /**
-     * Intersects a segment with a plane.
-     *
-     * @param source     The coordinates of point A.
-     * @param coordinate The R-, G-, or B-coordinate of the plane.
-     * @param target     The coordinates of point B.
-     * @param axis       The axis the plane is perpendicular with. (0: R, 1: G, 2: B)
-     * @return The intersection point of the segment AB with the plane R=coordinate,
-     * G=coordinate, or
-     * B=coordinate
-     */
-    static double[] setCoordinate(double[] source, double coordinate, double[] target, int axis) {
-        double t = intercept(source[axis], coordinate, target[axis]);
-        return lerpPoint(source, t, target);
-    }
-
-    static boolean isBounded(double x) {
-        return 0.0 <= x && x <= 100.0;
-    }
-
-    /**
-     * Returns the nth possible vertex of the polygonal intersection.
-     *
-     * @param y The Y value of the plane.
-     * @param n The zero-based index of the point. 0 <= n <= 11.
-     * @return The nth possible vertex of the polygonal intersection of the y plane and the RGB
-     * cube,
-     * in linear RGB coordinates, if it exists. If this possible vertex lies outside of the cube,
-     * [-1.0, -1.0, -1.0] is returned.
-     */
-    static double[] nthVertex(double y, int n) {
-        double kR = Y_FROM_LINRGB[0];
-        double kG = Y_FROM_LINRGB[1];
-        double kB = Y_FROM_LINRGB[2];
-        double coordA = n % 4 <= 1 ? 0.0 : 100.0;
-        double coordB = n % 2 == 0 ? 0.0 : 100.0;
-        if (n < 4) {
-            double g = coordA;
-            double b = coordB;
-            double r = (y - g * kG - b * kB) / kR;
-            if (isBounded(r)) {
-                return new double[]{r, g, b};
-            } else {
-                return new double[]{-1.0, -1.0, -1.0};
-            }
-        } else if (n < 8) {
-            double b = coordA;
-            double r = coordB;
-            double g = (y - r * kR - b * kB) / kG;
-            if (isBounded(g)) {
-                return new double[]{r, g, b};
-            } else {
-                return new double[]{-1.0, -1.0, -1.0};
-            }
-        } else {
-            double r = coordA;
-            double g = coordB;
-            double b = (y - r * kR - g * kG) / kB;
-            if (isBounded(b)) {
-                return new double[]{r, g, b};
-            } else {
-                return new double[]{-1.0, -1.0, -1.0};
-            }
-        }
-    }
-
-    /**
-     * Finds the segment containing the desired color.
-     *
-     * @param y         The Y value of the color.
-     * @param targetHue The hue of the color.
-     * @return A list of two sets of linear RGB coordinates, each corresponding to an endpoint of
-     * the
-     * segment containing the desired color.
-     */
-    static double[][] bisectToSegment(double y, double targetHue) {
-        double[] left = new double[]{-1.0, -1.0, -1.0};
-        double[] right = left;
-        double leftHue = 0.0;
-        double rightHue = 0.0;
-        boolean initialized = false;
-        boolean uncut = true;
-        for (int n = 0; n < 12; n++) {
-            double[] mid = nthVertex(y, n);
-            if (mid[0] < 0) {
-                continue;
-            }
-            double midHue = hueOf(mid);
-            if (!initialized) {
-                left = mid;
-                right = mid;
-                leftHue = midHue;
-                rightHue = midHue;
-                initialized = true;
-                continue;
-            }
-            if (uncut || areInCyclicOrder(leftHue, midHue, rightHue)) {
-                uncut = false;
-                if (areInCyclicOrder(leftHue, targetHue, midHue)) {
-                    right = mid;
-                    rightHue = midHue;
-                } else {
-                    left = mid;
-                    leftHue = midHue;
-                }
-            }
-        }
-        return new double[][]{left, right};
-    }
-
-    static double[] midpoint(double[] a, double[] b) {
-        return new double[]{
-                (a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2,
-        };
-    }
-
-    static int criticalPlaneBelow(double x) {
-        return (int) Math.floor(x - 0.5);
-    }
-
-    static int criticalPlaneAbove(double x) {
-        return (int) Math.ceil(x - 0.5);
-    }
-
-    /**
-     * Finds a color with the given Y and hue on the boundary of the cube.
-     *
-     * @param y         The Y value of the color.
-     * @param targetHue The hue of the color.
-     * @return The desired color, in linear RGB coordinates.
-     */
-    static double[] bisectToLimit(double y, double targetHue) {
-        double[][] segment = bisectToSegment(y, targetHue);
-        double[] left = segment[0];
-        double leftHue = hueOf(left);
-        double[] right = segment[1];
-        for (int axis = 0; axis < 3; axis++) {
-            if (left[axis] != right[axis]) {
-                int lPlane = -1;
-                int rPlane = 255;
-                if (left[axis] < right[axis]) {
-                    lPlane = criticalPlaneBelow(trueDelinearized(left[axis]));
-                    rPlane = criticalPlaneAbove(trueDelinearized(right[axis]));
-                } else {
-                    lPlane = criticalPlaneAbove(trueDelinearized(left[axis]));
-                    rPlane = criticalPlaneBelow(trueDelinearized(right[axis]));
-                }
-                for (int i = 0; i < 8; i++) {
-                    if (Math.abs(rPlane - lPlane) <= 1) {
-                        break;
-                    } else {
-                        int mPlane = (int) Math.floor((lPlane + rPlane) / 2.0);
-                        double midPlaneCoordinate = CRITICAL_PLANES[mPlane];
-                        double[] mid = setCoordinate(left, midPlaneCoordinate, right, axis);
-                        double midHue = hueOf(mid);
-                        if (areInCyclicOrder(leftHue, targetHue, midHue)) {
-                            right = mid;
-                            rPlane = mPlane;
-                        } else {
-                            left = mid;
-                            leftHue = midHue;
-                            lPlane = mPlane;
-                        }
-                    }
-                }
-            }
-        }
-        return midpoint(left, right);
-    }
-
-    static double inverseChromaticAdaptation(double adapted) {
-        double adaptedAbs = Math.abs(adapted);
-        double base = Math.max(0, 27.13 * adaptedAbs / (400.0 - adaptedAbs));
-        return MathUtils.signum(adapted) * Math.pow(base, 1.0 / 0.42);
-    }
-
-    /**
-     * Finds a color with the given hue, chroma, and Y.
-     *
-     * @param hueRadians The desired hue in radians.
-     * @param chroma     The desired chroma.
-     * @param y          The desired Y.
-     * @return The desired color as a hexadecimal integer, if found; 0 otherwise.
-     */
-    static int findResultByJ(double hueRadians, double chroma, double y) {
-        // Initial estimate of j.
-        double j = Math.sqrt(y) * 11.0;
-        // ===========================================================
-        // Operations inlined from Cam16 to avoid repeated calculation
-        // ===========================================================
-        ViewingConditions viewingConditions = ViewingConditions.DEFAULT;
-        double tInnerCoeff = 1 / Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73);
-        double eHue = 0.25 * (Math.cos(hueRadians + 2.0) + 3.8);
-        double p1 =
-                eHue * (50000.0 / 13.0) * viewingConditions.getNc() * viewingConditions.getNcb();
-        double hSin = Math.sin(hueRadians);
-        double hCos = Math.cos(hueRadians);
-        for (int iterationRound = 0; iterationRound < 5; iterationRound++) {
-            // ===========================================================
-            // Operations inlined from Cam16 to avoid repeated calculation
-            // ===========================================================
-            double jNormalized = j / 100.0;
-            double alpha = chroma == 0.0 || j == 0.0 ? 0.0 : chroma / Math.sqrt(jNormalized);
-            double t = Math.pow(alpha * tInnerCoeff, 1.0 / 0.9);
-            double ac =
-                    viewingConditions.getAw()
-                            * Math.pow(jNormalized,
-                            1.0 / viewingConditions.getC() / viewingConditions.getZ());
-            double p2 = ac / viewingConditions.getNbb();
-            double gamma = 23.0 * (p2 + 0.305) * t / (23.0 * p1 + 11 * t * hCos + 108.0 * t * hSin);
-            double a = gamma * hCos;
-            double b = gamma * hSin;
-            double rA = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0;
-            double gA = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0;
-            double bA = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0;
-            double rCScaled = inverseChromaticAdaptation(rA);
-            double gCScaled = inverseChromaticAdaptation(gA);
-            double bCScaled = inverseChromaticAdaptation(bA);
-            double[] linrgb =
-                    MathUtils.matrixMultiply(
-                            new double[]{rCScaled, gCScaled, bCScaled},
-                            LINRGB_FROM_SCALED_DISCOUNT);
-            // ===========================================================
-            // Operations inlined from Cam16 to avoid repeated calculation
-            // ===========================================================
-            if (linrgb[0] < 0 || linrgb[1] < 0 || linrgb[2] < 0) {
-                return 0;
-            }
-            double kR = Y_FROM_LINRGB[0];
-            double kG = Y_FROM_LINRGB[1];
-            double kB = Y_FROM_LINRGB[2];
-            double fnj = kR * linrgb[0] + kG * linrgb[1] + kB * linrgb[2];
-            if (fnj <= 0) {
-                return 0;
-            }
-            if (iterationRound == 4 || Math.abs(fnj - y) < 0.002) {
-                if (linrgb[0] > 100.01 || linrgb[1] > 100.01 || linrgb[2] > 100.01) {
-                    return 0;
-                }
-                return ColorUtils.argbFromLinrgb(linrgb);
-            }
-            // Iterates with Newton method,
-            // Using 2 * fn(j) / j as the approximation of fn'(j)
-            j = j - (fnj - y) * j / (2 * fnj);
-        }
-        return 0;
-    }
-
-    /**
-     * Finds an sRGB color with the given hue, chroma, and L*, if possible.
-     *
-     * @param hueDegrees The desired hue, in degrees.
-     * @param chroma     The desired chroma.
-     * @param lstar      The desired L*.
-     * @return A hexadecimal representing the sRGB color. The color has sufficiently close hue,
-     * chroma, and L* to the desired values, if possible; otherwise, the hue and L* will be
-     * sufficiently close, and chroma will be maximized.
-     */
-    public static int solveToInt(double hueDegrees, double chroma, double lstar) {
-        if (chroma < 0.0001 || lstar < 0.0001 || lstar > 99.9999) {
-            return ColorUtils.argbFromLstar(lstar);
-        }
-        hueDegrees = MathUtils.sanitizeDegreesDouble(hueDegrees);
-        double hueRadians = hueDegrees / 180 * Math.PI;
-        double y = ColorUtils.yFromLstar(lstar);
-        int exactAnswer = findResultByJ(hueRadians, chroma, y);
-        if (exactAnswer != 0) {
-            return exactAnswer;
-        }
-        double[] linrgb = bisectToLimit(y, hueRadians);
-        return ColorUtils.argbFromLinrgb(linrgb);
-    }
-
-    /**
-     * Finds an sRGB color with the given hue, chroma, and L*, if possible.
-     *
-     * @param hueDegrees The desired hue, in degrees.
-     * @param chroma     The desired chroma.
-     * @param lstar      The desired L*.
-     * @return A CAM16 object representing the sRGB color. The color has sufficiently close hue,
-     * chroma, and L* to the desired values, if possible; otherwise, the hue and L* will be
-     * sufficiently close, and chroma will be maximized.
-     */
-    public static Cam16 solveToCam(double hueDegrees, double chroma, double lstar) {
-        return Cam16.fromInt(solveToInt(hueDegrees, chroma, lstar));
-    }
-}
-
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/hct/ViewingConditions.java b/packages/SystemUI/monet/src/com/android/systemui/monet/hct/ViewingConditions.java
deleted file mode 100644
index a0ba7a6..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/hct/ViewingConditions.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.hct;
-
-import com.android.systemui.monet.utils.ColorUtils;
-import com.android.systemui.monet.utils.MathUtils;
-
-/**
- * In traditional color spaces, a color can be identified solely by the observer's measurement of
- * the color. Color appearance models such as CAM16 also use information about the environment where
- * the color was observed, known as the viewing conditions.
- *
- * <p>For example, white under the traditional assumption of a midday sun white point is accurately
- * measured as a slightly chromatic blue by CAM16. (roughly, hue 203, chroma 3, lightness 100)
- *
- * <p>This class caches intermediate values of the CAM16 conversion process that depend only on
- * viewing conditions, enabling speed ups.
- */
-public final class ViewingConditions {
-    /** sRGB-like viewing conditions. */
-    public static final ViewingConditions DEFAULT =
-            ViewingConditions.defaultWithBackgroundLstar(50.0);
-
-    private final double aw;
-    private final double nbb;
-    private final double ncb;
-    private final double c;
-    private final double nc;
-    private final double n;
-    private final double[] rgbD;
-    private final double fl;
-    private final double flRoot;
-    private final double z;
-
-    public double getAw() {
-        return aw;
-    }
-
-    public double getN() {
-        return n;
-    }
-
-    public double getNbb() {
-        return nbb;
-    }
-
-    double getNcb() {
-        return ncb;
-    }
-
-    double getC() {
-        return c;
-    }
-
-    double getNc() {
-        return nc;
-    }
-
-    public double[] getRgbD() {
-        return rgbD;
-    }
-
-    double getFl() {
-        return fl;
-    }
-
-    public double getFlRoot() {
-        return flRoot;
-    }
-
-    double getZ() {
-        return z;
-    }
-
-    /**
-     * Create ViewingConditions from a simple, physically relevant, set of parameters.
-     *
-     * @param whitePoint            White point, measured in the XYZ color space. default = D65, or
-     *                              sunny day
-     *                              afternoon
-     * @param adaptingLuminance     The luminance of the adapting field. Informally, how bright it
-     *                              is in
-     *                              the room where the color is viewed. Can be calculated from lux
-     *                              by multiplying lux by
-     *                              0.0586. default = 11.72, or 200 lux.
-     * @param backgroundLstar       The lightness of the area surrounding the color. measured by L*
-     *                              in
-     *                              L*a*b*. default = 50.0
-     * @param surround              A general description of the lighting surrounding the color. 0
-     *                              is pitch dark,
-     *                              like watching a movie in a theater. 1.0 is a dimly light room,
-     *                              like watching TV at home at
-     *                              night. 2.0 means there is no difference between the lighting on
-     *                              the color and around it.
-     *                              default = 2.0
-     * @param discountingIlluminant Whether the eye accounts for the tint of the ambient lighting,
-     *                              such as knowing an apple is still red in green light. default =
-     *                              false, the eye does not
-     *                              perform this process on self-luminous objects like displays.
-     */
-    public static ViewingConditions make(
-            double[] whitePoint,
-            double adaptingLuminance,
-            double backgroundLstar,
-            double surround,
-            boolean discountingIlluminant) {
-        // A background of pure black is non-physical and leads to infinities that represent the
-        // idea
-        // that any color viewed in pure black can't be seen.
-        backgroundLstar = Math.max(0.1, backgroundLstar);
-        // Transform white point XYZ to 'cone'/'rgb' responses
-        double[][] matrix = Cam16.XYZ_TO_CAM16RGB;
-        double[] xyz = whitePoint;
-        double rW = (xyz[0] * matrix[0][0]) + (xyz[1] * matrix[0][1]) + (xyz[2] * matrix[0][2]);
-        double gW = (xyz[0] * matrix[1][0]) + (xyz[1] * matrix[1][1]) + (xyz[2] * matrix[1][2]);
-        double bW = (xyz[0] * matrix[2][0]) + (xyz[1] * matrix[2][1]) + (xyz[2] * matrix[2][2]);
-        double f = 0.8 + (surround / 10.0);
-        double c =
-                (f >= 0.9)
-                        ? MathUtils.lerp(0.59, 0.69, ((f - 0.9) * 10.0))
-                        : MathUtils.lerp(0.525, 0.59, ((f - 0.8) * 10.0));
-        double d =
-                discountingIlluminant
-                        ? 1.0
-                        : f * (1.0 - ((1.0 / 3.6) * Math.exp((-adaptingLuminance - 42.0) / 92.0)));
-        d = MathUtils.clampDouble(0.0, 1.0, d);
-        double nc = f;
-        double[] rgbD =
-                new double[]{
-                        d * (100.0 / rW) + 1.0 - d, d * (100.0 / gW) + 1.0 - d,
-                        d * (100.0 / bW) + 1.0 - d
-                };
-        double k = 1.0 / (5.0 * adaptingLuminance + 1.0);
-        double k4 = k * k * k * k;
-        double k4F = 1.0 - k4;
-        double fl = (k4 * adaptingLuminance) + (0.1 * k4F * k4F * Math.cbrt(
-                5.0 * adaptingLuminance));
-        double n = (ColorUtils.yFromLstar(backgroundLstar) / whitePoint[1]);
-        double z = 1.48 + Math.sqrt(n);
-        double nbb = 0.725 / Math.pow(n, 0.2);
-        double ncb = nbb;
-        double[] rgbAFactors =
-                new double[]{
-                        Math.pow(fl * rgbD[0] * rW / 100.0, 0.42),
-                        Math.pow(fl * rgbD[1] * gW / 100.0, 0.42),
-                        Math.pow(fl * rgbD[2] * bW / 100.0, 0.42)
-                };
-
-        double[] rgbA =
-                new double[]{
-                        (400.0 * rgbAFactors[0]) / (rgbAFactors[0] + 27.13),
-                        (400.0 * rgbAFactors[1]) / (rgbAFactors[1] + 27.13),
-                        (400.0 * rgbAFactors[2]) / (rgbAFactors[2] + 27.13)
-                };
-
-        double aw = ((2.0 * rgbA[0]) + rgbA[1] + (0.05 * rgbA[2])) * nbb;
-        return new ViewingConditions(n, aw, nbb, ncb, c, nc, rgbD, fl, Math.pow(fl, 0.25), z);
-    }
-
-    /**
-     * Create sRGB-like viewing conditions with a custom background lstar.
-     *
-     * <p>Default viewing conditions have a lstar of 50, midgray.
-     */
-    public static ViewingConditions defaultWithBackgroundLstar(double lstar) {
-        return ViewingConditions.make(
-                ColorUtils.whitePointD65(),
-                (200.0 / Math.PI * ColorUtils.yFromLstar(50.0) / 100.f),
-                lstar,
-                2.0,
-                false);
-    }
-
-    /**
-     * Parameters are intermediate values of the CAM16 conversion process. Their names are
-     * shorthand
-     * for technical color science terminology, this class would not benefit from documenting them
-     * individually. A brief overview is available in the CAM16 specification, and a complete
-     * overview
-     * requires a color science textbook, such as Fairchild's Color Appearance Models.
-     */
-    private ViewingConditions(
-            double n,
-            double aw,
-            double nbb,
-            double ncb,
-            double c,
-            double nc,
-            double[] rgbD,
-            double fl,
-            double flRoot,
-            double z) {
-        this.n = n;
-        this.aw = aw;
-        this.nbb = nbb;
-        this.ncb = ncb;
-        this.c = c;
-        this.nc = nc;
-        this.rgbD = rgbD;
-        this.fl = fl;
-        this.flRoot = flRoot;
-        this.z = z;
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/palettes/CorePalette.java b/packages/SystemUI/monet/src/com/android/systemui/monet/palettes/CorePalette.java
deleted file mode 100644
index 37f6042..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/palettes/CorePalette.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.palettes;
-
-import static java.lang.Math.max;
-import static java.lang.Math.min;
-
-import com.android.systemui.monet.hct.Hct;
-
-/**
- * An intermediate concept between the key color for a UI theme, and a full color scheme. 5 sets of
- * tones are generated, all except one use the same hue as the key color, and all vary in chroma.
- */
-public final class CorePalette {
-    public TonalPalette a1;
-    public TonalPalette a2;
-    public TonalPalette a3;
-    public TonalPalette n1;
-    public TonalPalette n2;
-    public TonalPalette error;
-
-    /**
-     * Create key tones from a color.
-     *
-     * @param argb ARGB representation of a color
-     */
-    public static CorePalette of(int argb) {
-        return new CorePalette(argb, false);
-    }
-
-    /**
-     * Create content key tones from a color.
-     *
-     * @param argb ARGB representation of a color
-     */
-    public static CorePalette contentOf(int argb) {
-        return new CorePalette(argb, true);
-    }
-
-    private CorePalette(int argb, boolean isContent) {
-        Hct hct = Hct.fromInt(argb);
-        double hue = hct.getHue();
-        double chroma = hct.getChroma();
-        if (isContent) {
-            this.a1 = TonalPalette.fromHueAndChroma(hue, chroma);
-            this.a2 = TonalPalette.fromHueAndChroma(hue, chroma / 3.);
-            this.a3 = TonalPalette.fromHueAndChroma(hue + 60., chroma / 2.);
-            this.n1 = TonalPalette.fromHueAndChroma(hue, min(chroma / 12., 4.));
-            this.n2 = TonalPalette.fromHueAndChroma(hue, min(chroma / 6., 8.));
-        } else {
-            this.a1 = TonalPalette.fromHueAndChroma(hue, max(48., chroma));
-            this.a2 = TonalPalette.fromHueAndChroma(hue, 16.);
-            this.a3 = TonalPalette.fromHueAndChroma(hue + 60., 24.);
-            this.n1 = TonalPalette.fromHueAndChroma(hue, 4.);
-            this.n2 = TonalPalette.fromHueAndChroma(hue, 8.);
-        }
-        this.error = TonalPalette.fromHueAndChroma(25, 84.);
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/palettes/TonalPalette.java b/packages/SystemUI/monet/src/com/android/systemui/monet/palettes/TonalPalette.java
deleted file mode 100644
index 8e905ea..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/palettes/TonalPalette.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.palettes;
-
-import com.android.systemui.monet.hct.Hct;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * A convenience class for retrieving colors that are constant in hue and chroma, but vary in tone.
- */
-public final class TonalPalette {
-    Map<Integer, Integer> cache;
-    Hct keyColor;
-    double hue;
-    double chroma;
-
-    /**
-     * Create tones using the HCT hue and chroma from a color.
-     *
-     * @param argb ARGB representation of a color
-     * @return Tones matching that color's hue and chroma.
-     */
-    public static TonalPalette fromInt(int argb) {
-        return fromHct(Hct.fromInt(argb));
-    }
-
-    /**
-     * Create tones using a HCT color.
-     *
-     * @param hct HCT representation of a color.
-     * @return Tones matching that color's hue and chroma.
-     */
-    public static TonalPalette fromHct(Hct hct) {
-        return new TonalPalette(hct.getHue(), hct.getChroma(), hct);
-    }
-
-    /**
-     * Create tones from a defined HCT hue and chroma.
-     *
-     * @param hue    HCT hue
-     * @param chroma HCT chroma
-     * @return Tones matching hue and chroma.
-     */
-    public static TonalPalette fromHueAndChroma(double hue, double chroma) {
-        return new TonalPalette(hue, chroma, createKeyColor(hue, chroma));
-    }
-
-    private TonalPalette(double hue, double chroma, Hct keyColor) {
-        cache = new HashMap<>();
-        this.hue = hue;
-        this.chroma = chroma;
-        this.keyColor = keyColor;
-    }
-
-    /** The key color is the first tone, starting from T50, matching the given hue and chroma. */
-    private static Hct createKeyColor(double hue, double chroma) {
-        double startTone = 50.0;
-        Hct smallestDeltaHct = Hct.from(hue, chroma, startTone);
-        double smallestDelta = Math.abs(smallestDeltaHct.getChroma() - chroma);
-        // Starting from T50, check T+/-delta to see if they match the requested
-        // chroma.
-        //
-        // Starts from T50 because T50 has the most chroma available, on
-        // average. Thus it is most likely to have a direct answer and minimize
-        // iteration.
-        for (double delta = 1.0; delta < 50.0; delta += 1.0) {
-            // Termination condition rounding instead of minimizing delta to avoid
-            // case where requested chroma is 16.51, and the closest chroma is 16.49.
-            // Error is minimized, but when rounded and displayed, requested chroma
-            // is 17, key color's chroma is 16.
-            if (Math.round(chroma) == Math.round(smallestDeltaHct.getChroma())) {
-                return smallestDeltaHct;
-            }
-
-            final Hct hctAdd = Hct.from(hue, chroma, startTone + delta);
-            final double hctAddDelta = Math.abs(hctAdd.getChroma() - chroma);
-            if (hctAddDelta < smallestDelta) {
-                smallestDelta = hctAddDelta;
-                smallestDeltaHct = hctAdd;
-            }
-
-            final Hct hctSubtract = Hct.from(hue, chroma, startTone - delta);
-            final double hctSubtractDelta = Math.abs(hctSubtract.getChroma() - chroma);
-            if (hctSubtractDelta < smallestDelta) {
-                smallestDelta = hctSubtractDelta;
-                smallestDeltaHct = hctSubtract;
-            }
-        }
-
-        return smallestDeltaHct;
-    }
-
-    /**
-     * Create an ARGB color with HCT hue and chroma of this Tones instance, and the provided HCT
-     * tone.
-     *
-     * @param tone HCT tone, measured from 0 to 100.
-     * @return ARGB representation of a color with that tone.
-     */
-    // AndroidJdkLibsChecker is higher priority than ComputeIfAbsentUseValue (b/119581923)
-    @SuppressWarnings("ComputeIfAbsentUseValue")
-    public int tone(int tone) {
-        Integer color = cache.get(tone);
-        if (color == null) {
-            color = Hct.from(this.hue, this.chroma, tone).toInt();
-            cache.put(tone, color);
-        }
-        return color;
-    }
-
-    /** Given a tone, use hue and chroma of palette to create a color, and return it as HCT. */
-    public Hct getHct(double tone) {
-        return Hct.from(this.hue, this.chroma, tone);
-    }
-
-    /** The chroma of the Tonal Palette, in HCT. Ranges from 0 to ~130 (for sRGB gamut). */
-    public double getChroma() {
-        return this.chroma;
-    }
-
-    /** The hue of the Tonal Palette, in HCT. Ranges from 0 to 360. */
-    public double getHue() {
-        return this.hue;
-    }
-
-    /** The key color is the first tone, starting from T50, that matches the palette's chroma. */
-    public Hct getKeyColor() {
-        return this.keyColor;
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/PointProvider.java b/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/PointProvider.java
deleted file mode 100644
index 99ee0d4..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/PointProvider.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.quantize;
-
-/** An interface to allow use of different color spaces by quantizers. */
-public interface PointProvider {
-    /** The three components in the color space of an sRGB color. */
-    double[] fromInt(int argb);
-
-    /** The ARGB (i.e. hex code) representation of this color. */
-    int toInt(double[] point);
-
-    /**
-     * Squared distance between two colors.
-     * Distance is defined by scientific color spaces and referred to as delta E.
-     */
-    double distance(double[] a, double[] b);
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/PointProviderLab.java b/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/PointProviderLab.java
deleted file mode 100644
index e852d5a..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/PointProviderLab.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.quantize;
-
-import com.android.systemui.monet.utils.ColorUtils;
-
-/**
- * Provides conversions needed for K-Means quantization. Converting input to points, and converting
- * the final state of the K-Means algorithm to colors.
- */
-public final class PointProviderLab implements PointProvider {
-    /**
-     * Convert a color represented in ARGB to a 3-element array of L*a*b* coordinates of the color.
-     */
-    @Override
-    public double[] fromInt(int argb) {
-        double[] lab = ColorUtils.labFromArgb(argb);
-        return new double[]{lab[0], lab[1], lab[2]};
-    }
-
-    /** Convert a 3-element array to a color represented in ARGB. */
-    @Override
-    public int toInt(double[] lab) {
-        return ColorUtils.argbFromLab(lab[0], lab[1], lab[2]);
-    }
-
-    /**
-     * Standard CIE 1976 delta E formula also takes the square root, unneeded here. This method is
-     * used by quantization algorithms to compare distance, and the relative ordering is the same,
-     * with or without a square root.
-     *
-     * <p>This relatively minor optimization is helpful because this method is called at least once
-     * for each pixel in an image.
-     */
-    @Override
-    public double distance(double[] one, double[] two) {
-        double dL = (one[0] - two[0]);
-        double dA = (one[1] - two[1]);
-        double dB = (one[2] - two[2]);
-        return (dL * dL + dA * dA + dB * dB);
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/Quantizer.java b/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/Quantizer.java
deleted file mode 100644
index 136e5a5..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/Quantizer.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.quantize;
-
-interface Quantizer {
-    QuantizerResult quantize(int[] pixels, int maxColors);
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/QuantizerCelebi.java b/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/QuantizerCelebi.java
deleted file mode 100644
index d448dd0..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/QuantizerCelebi.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.quantize;
-
-import java.util.Map;
-import java.util.Set;
-
-/**
- * An image quantizer that improves on the quality of a standard K-Means algorithm by setting the
- * K-Means initial state to the output of a Wu quantizer, instead of random centroids. Improves on
- * speed by several optimizations, as implemented in Wsmeans, or Weighted Square Means, K-Means with
- * those optimizations.
- *
- * <p>This algorithm was designed by M. Emre Celebi, and was found in their 2011 paper, Improving
- * the Performance of K-Means for Color Quantization. https://arxiv.org/abs/1101.0395
- */
-public final class QuantizerCelebi {
-    private QuantizerCelebi() {
-    }
-
-    /**
-     * Reduce the number of colors needed to represented the input, minimizing the difference
-     * between
-     * the original image and the recolored image.
-     *
-     * @param pixels    Colors in ARGB format.
-     * @param maxColors The number of colors to divide the image into. A lower number of colors may
-     *                  be
-     *                  returned.
-     * @return Map with keys of colors in ARGB format, and values of number of pixels in the
-     * original
-     * image that correspond to the color in the quantized image.
-     */
-    public static Map<Integer, Integer> quantize(int[] pixels, int maxColors) {
-        QuantizerWu wu = new QuantizerWu();
-        QuantizerResult wuResult = wu.quantize(pixels, maxColors);
-
-        Set<Integer> wuClustersAsObjects = wuResult.colorToCount.keySet();
-        int index = 0;
-        int[] wuClusters = new int[wuClustersAsObjects.size()];
-        for (Integer argb : wuClustersAsObjects) {
-            wuClusters[index++] = argb;
-        }
-
-        return QuantizerWsmeans.quantize(pixels, wuClusters, maxColors);
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/QuantizerMap.java b/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/QuantizerMap.java
deleted file mode 100644
index 9dae8ff..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/QuantizerMap.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.quantize;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/** Creates a dictionary with keys of colors, and values of count of the color */
-public final class QuantizerMap implements Quantizer {
-    Map<Integer, Integer> colorToCount;
-
-    @Override
-    public QuantizerResult quantize(int[] pixels, int colorCount) {
-        final Map<Integer, Integer> pixelByCount = new LinkedHashMap<>();
-        for (int pixel : pixels) {
-            final Integer currentPixelCount = pixelByCount.get(pixel);
-            final int newPixelCount = currentPixelCount == null ? 1 : currentPixelCount + 1;
-            pixelByCount.put(pixel, newPixelCount);
-        }
-        colorToCount = pixelByCount;
-        return new QuantizerResult(pixelByCount);
-    }
-
-    public Map<Integer, Integer> getColorToCount() {
-        return colorToCount;
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/QuantizerResult.java b/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/QuantizerResult.java
deleted file mode 100644
index a2d06af..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/QuantizerResult.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.quantize;
-
-import java.util.Map;
-
-/** Represents result of a quantizer run */
-public final class QuantizerResult {
-    public final Map<Integer, Integer> colorToCount;
-
-    QuantizerResult(Map<Integer, Integer> colorToCount) {
-        this.colorToCount = colorToCount;
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/QuantizerWsmeans.java b/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/QuantizerWsmeans.java
deleted file mode 100644
index 75dd75b..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/QuantizerWsmeans.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.quantize;
-
-import static java.lang.Math.min;
-
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Random;
-
-/**
- * An image quantizer that improves on the speed of a standard K-Means algorithm by implementing
- * several optimizations, including deduping identical pixels and a triangle inequality rule that
- * reduces the number of comparisons needed to identify which cluster a point should be moved to.
- *
- * <p>Wsmeans stands for Weighted Square Means.
- *
- * <p>This algorithm was designed by M. Emre Celebi, and was found in their 2011 paper, Improving
- * the Performance of K-Means for Color Quantization. https://arxiv.org/abs/1101.0395
- */
-public final class QuantizerWsmeans {
-    private QuantizerWsmeans() {
-    }
-
-    private static final class Distance implements Comparable<Distance> {
-        int index;
-        double distance;
-
-        Distance() {
-            this.index = -1;
-            this.distance = -1;
-        }
-
-        @Override
-        public int compareTo(Distance other) {
-            return ((Double) this.distance).compareTo(other.distance);
-        }
-    }
-
-    private static final int MAX_ITERATIONS = 10;
-    private static final double MIN_MOVEMENT_DISTANCE = 3.0;
-
-    /**
-     * Reduce the number of colors needed to represented the input, minimizing the difference
-     * between
-     * the original image and the recolored image.
-     *
-     * @param inputPixels      Colors in ARGB format.
-     * @param startingClusters Defines the initial state of the quantizer. Passing an empty array
-     *                         is
-     *                         fine, the implementation will create its own initial state that leads
-     *                         to reproducible
-     *                         results for the same inputs. Passing an array that is the result of
-     *                         Wu quantization leads
-     *                         to higher quality results.
-     * @param maxColors        The number of colors to divide the image into. A lower number of
-     *                         colors may be
-     *                         returned.
-     * @return Map with keys of colors in ARGB format, values of how many of the input pixels belong
-     * to the color.
-     */
-    public static Map<Integer, Integer> quantize(
-            int[] inputPixels, int[] startingClusters, int maxColors) {
-        // Uses a seeded random number generator to ensure consistent results.
-        Random random = new Random(0x42688);
-
-        Map<Integer, Integer> pixelToCount = new LinkedHashMap<>();
-        double[][] points = new double[inputPixels.length][];
-        int[] pixels = new int[inputPixels.length];
-        PointProvider pointProvider = new PointProviderLab();
-
-        int pointCount = 0;
-        for (int i = 0; i < inputPixels.length; i++) {
-            int inputPixel = inputPixels[i];
-            Integer pixelCount = pixelToCount.get(inputPixel);
-            if (pixelCount == null) {
-                points[pointCount] = pointProvider.fromInt(inputPixel);
-                pixels[pointCount] = inputPixel;
-                pointCount++;
-
-                pixelToCount.put(inputPixel, 1);
-            } else {
-                pixelToCount.put(inputPixel, pixelCount + 1);
-            }
-        }
-
-        int[] counts = new int[pointCount];
-        for (int i = 0; i < pointCount; i++) {
-            int pixel = pixels[i];
-            int count = pixelToCount.get(pixel);
-            counts[i] = count;
-        }
-
-        int clusterCount = min(maxColors, pointCount);
-        if (startingClusters.length != 0) {
-            clusterCount = min(clusterCount, startingClusters.length);
-        }
-
-        double[][] clusters = new double[clusterCount][];
-        for (int i = 0; i < startingClusters.length; i++) {
-            clusters[i] = pointProvider.fromInt(startingClusters[i]);
-        }
-
-        int[] clusterIndices = new int[pointCount];
-        for (int i = 0; i < pointCount; i++) {
-            clusterIndices[i] = random.nextInt(clusterCount);
-        }
-
-        int[][] indexMatrix = new int[clusterCount][];
-        for (int i = 0; i < clusterCount; i++) {
-            indexMatrix[i] = new int[clusterCount];
-        }
-
-        Distance[][] distanceToIndexMatrix = new Distance[clusterCount][];
-        for (int i = 0; i < clusterCount; i++) {
-            distanceToIndexMatrix[i] = new Distance[clusterCount];
-            for (int j = 0; j < clusterCount; j++) {
-                distanceToIndexMatrix[i][j] = new Distance();
-            }
-        }
-
-        int[] pixelCountSums = new int[clusterCount];
-        for (int iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
-            for (int i = 0; i < clusterCount; i++) {
-                for (int j = i + 1; j < clusterCount; j++) {
-                    double distance = pointProvider.distance(clusters[i], clusters[j]);
-                    distanceToIndexMatrix[j][i].distance = distance;
-                    distanceToIndexMatrix[j][i].index = i;
-                    distanceToIndexMatrix[i][j].distance = distance;
-                    distanceToIndexMatrix[i][j].index = j;
-                }
-                Arrays.sort(distanceToIndexMatrix[i]);
-                for (int j = 0; j < clusterCount; j++) {
-                    indexMatrix[i][j] = distanceToIndexMatrix[i][j].index;
-                }
-            }
-
-            int pointsMoved = 0;
-            for (int i = 0; i < pointCount; i++) {
-                double[] point = points[i];
-                int previousClusterIndex = clusterIndices[i];
-                double[] previousCluster = clusters[previousClusterIndex];
-                double previousDistance = pointProvider.distance(point, previousCluster);
-
-                double minimumDistance = previousDistance;
-                int newClusterIndex = -1;
-                for (int j = 0; j < clusterCount; j++) {
-                    if (distanceToIndexMatrix[previousClusterIndex][j].distance
-                            >= 4 * previousDistance) {
-                        continue;
-                    }
-                    double distance = pointProvider.distance(point, clusters[j]);
-                    if (distance < minimumDistance) {
-                        minimumDistance = distance;
-                        newClusterIndex = j;
-                    }
-                }
-                if (newClusterIndex != -1) {
-                    double distanceChange =
-                            Math.abs(Math.sqrt(minimumDistance) - Math.sqrt(previousDistance));
-                    if (distanceChange > MIN_MOVEMENT_DISTANCE) {
-                        pointsMoved++;
-                        clusterIndices[i] = newClusterIndex;
-                    }
-                }
-            }
-
-            if (pointsMoved == 0 && iteration != 0) {
-                break;
-            }
-
-            double[] componentASums = new double[clusterCount];
-            double[] componentBSums = new double[clusterCount];
-            double[] componentCSums = new double[clusterCount];
-            Arrays.fill(pixelCountSums, 0);
-            for (int i = 0; i < pointCount; i++) {
-                int clusterIndex = clusterIndices[i];
-                double[] point = points[i];
-                int count = counts[i];
-                pixelCountSums[clusterIndex] += count;
-                componentASums[clusterIndex] += (point[0] * count);
-                componentBSums[clusterIndex] += (point[1] * count);
-                componentCSums[clusterIndex] += (point[2] * count);
-            }
-
-            for (int i = 0; i < clusterCount; i++) {
-                int count = pixelCountSums[i];
-                if (count == 0) {
-                    clusters[i] = new double[]{0., 0., 0.};
-                    continue;
-                }
-                double a = componentASums[i] / count;
-                double b = componentBSums[i] / count;
-                double c = componentCSums[i] / count;
-                clusters[i][0] = a;
-                clusters[i][1] = b;
-                clusters[i][2] = c;
-            }
-        }
-
-        Map<Integer, Integer> argbToPopulation = new LinkedHashMap<>();
-        for (int i = 0; i < clusterCount; i++) {
-            int count = pixelCountSums[i];
-            if (count == 0) {
-                continue;
-            }
-
-            int possibleNewCluster = pointProvider.toInt(clusters[i]);
-            if (argbToPopulation.containsKey(possibleNewCluster)) {
-                continue;
-            }
-
-            argbToPopulation.put(possibleNewCluster, count);
-        }
-
-        return argbToPopulation;
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/QuantizerWu.java b/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/QuantizerWu.java
deleted file mode 100644
index f788d45..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/quantize/QuantizerWu.java
+++ /dev/null
@@ -1,407 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.quantize;
-
-import com.android.systemui.monet.utils.ColorUtils;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * An image quantizer that divides the image's pixels into clusters by recursively cutting an RGB
- * cube, based on the weight of pixels in each area of the cube.
- *
- * <p>The algorithm was described by Xiaolin Wu in Graphic Gems II, published in 1991.
- */
-public final class QuantizerWu implements Quantizer {
-    int[] weights;
-    int[] momentsR;
-    int[] momentsG;
-    int[] momentsB;
-    double[] moments;
-    Box[] cubes;
-
-    // A histogram of all the input colors is constructed. It has the shape of a
-    // cube. The cube would be too large if it contained all 16 million colors:
-    // historical best practice is to use 5 bits  of the 8 in each channel,
-    // reducing the histogram to a volume of ~32,000.
-    private static final int INDEX_BITS = 5;
-    private static final int INDEX_COUNT = 33; // ((1 << INDEX_BITS) + 1)
-    private static final int TOTAL_SIZE = 35937; // INDEX_COUNT * INDEX_COUNT * INDEX_COUNT
-
-    @Override
-    public QuantizerResult quantize(int[] pixels, int colorCount) {
-        QuantizerResult mapResult = new QuantizerMap().quantize(pixels, colorCount);
-        constructHistogram(mapResult.colorToCount);
-        createMoments();
-        CreateBoxesResult createBoxesResult = createBoxes(colorCount);
-        List<Integer> colors = createResult(createBoxesResult.resultCount);
-        Map<Integer, Integer> resultMap = new LinkedHashMap<>();
-        for (int color : colors) {
-            resultMap.put(color, 0);
-        }
-        return new QuantizerResult(resultMap);
-    }
-
-    static int getIndex(int r, int g, int b) {
-        return (r << (INDEX_BITS * 2)) + (r << (INDEX_BITS + 1)) + r + (g << INDEX_BITS) + g + b;
-    }
-
-    void constructHistogram(Map<Integer, Integer> pixels) {
-        weights = new int[TOTAL_SIZE];
-        momentsR = new int[TOTAL_SIZE];
-        momentsG = new int[TOTAL_SIZE];
-        momentsB = new int[TOTAL_SIZE];
-        moments = new double[TOTAL_SIZE];
-
-        for (Map.Entry<Integer, Integer> pair : pixels.entrySet()) {
-            int pixel = pair.getKey();
-            int count = pair.getValue();
-            int red = ColorUtils.redFromArgb(pixel);
-            int green = ColorUtils.greenFromArgb(pixel);
-            int blue = ColorUtils.blueFromArgb(pixel);
-            int bitsToRemove = 8 - INDEX_BITS;
-            int iR = (red >> bitsToRemove) + 1;
-            int iG = (green >> bitsToRemove) + 1;
-            int iB = (blue >> bitsToRemove) + 1;
-            int index = getIndex(iR, iG, iB);
-            weights[index] += count;
-            momentsR[index] += (red * count);
-            momentsG[index] += (green * count);
-            momentsB[index] += (blue * count);
-            moments[index] += (count * ((red * red) + (green * green) + (blue * blue)));
-        }
-    }
-
-    void createMoments() {
-        for (int r = 1; r < INDEX_COUNT; ++r) {
-            int[] area = new int[INDEX_COUNT];
-            int[] areaR = new int[INDEX_COUNT];
-            int[] areaG = new int[INDEX_COUNT];
-            int[] areaB = new int[INDEX_COUNT];
-            double[] area2 = new double[INDEX_COUNT];
-
-            for (int g = 1; g < INDEX_COUNT; ++g) {
-                int line = 0;
-                int lineR = 0;
-                int lineG = 0;
-                int lineB = 0;
-                double line2 = 0.0;
-                for (int b = 1; b < INDEX_COUNT; ++b) {
-                    int index = getIndex(r, g, b);
-                    line += weights[index];
-                    lineR += momentsR[index];
-                    lineG += momentsG[index];
-                    lineB += momentsB[index];
-                    line2 += moments[index];
-
-                    area[b] += line;
-                    areaR[b] += lineR;
-                    areaG[b] += lineG;
-                    areaB[b] += lineB;
-                    area2[b] += line2;
-
-                    int previousIndex = getIndex(r - 1, g, b);
-                    weights[index] = weights[previousIndex] + area[b];
-                    momentsR[index] = momentsR[previousIndex] + areaR[b];
-                    momentsG[index] = momentsG[previousIndex] + areaG[b];
-                    momentsB[index] = momentsB[previousIndex] + areaB[b];
-                    moments[index] = moments[previousIndex] + area2[b];
-                }
-            }
-        }
-    }
-
-    CreateBoxesResult createBoxes(int maxColorCount) {
-        cubes = new Box[maxColorCount];
-        for (int i = 0; i < maxColorCount; i++) {
-            cubes[i] = new Box();
-        }
-        double[] volumeVariance = new double[maxColorCount];
-        Box firstBox = cubes[0];
-        firstBox.r1 = INDEX_COUNT - 1;
-        firstBox.g1 = INDEX_COUNT - 1;
-        firstBox.b1 = INDEX_COUNT - 1;
-
-        int generatedColorCount = maxColorCount;
-        int next = 0;
-        for (int i = 1; i < maxColorCount; i++) {
-            if (cut(cubes[next], cubes[i])) {
-                volumeVariance[next] = (cubes[next].vol > 1) ? variance(cubes[next]) : 0.0;
-                volumeVariance[i] = (cubes[i].vol > 1) ? variance(cubes[i]) : 0.0;
-            } else {
-                volumeVariance[next] = 0.0;
-                i--;
-            }
-
-            next = 0;
-
-            double temp = volumeVariance[0];
-            for (int j = 1; j <= i; j++) {
-                if (volumeVariance[j] > temp) {
-                    temp = volumeVariance[j];
-                    next = j;
-                }
-            }
-            if (temp <= 0.0) {
-                generatedColorCount = i + 1;
-                break;
-            }
-        }
-
-        return new CreateBoxesResult(maxColorCount, generatedColorCount);
-    }
-
-    List<Integer> createResult(int colorCount) {
-        List<Integer> colors = new ArrayList<>();
-        for (int i = 0; i < colorCount; ++i) {
-            Box cube = cubes[i];
-            int weight = volume(cube, weights);
-            if (weight > 0) {
-                int r = volume(cube, momentsR) / weight;
-                int g = volume(cube, momentsG) / weight;
-                int b = volume(cube, momentsB) / weight;
-                int color = (255 << 24) | ((r & 0x0ff) << 16) | ((g & 0x0ff) << 8) | (b & 0x0ff);
-                colors.add(color);
-            }
-        }
-        return colors;
-    }
-
-    double variance(Box cube) {
-        int dr = volume(cube, momentsR);
-        int dg = volume(cube, momentsG);
-        int db = volume(cube, momentsB);
-        double xx =
-                moments[getIndex(cube.r1, cube.g1, cube.b1)]
-                        - moments[getIndex(cube.r1, cube.g1, cube.b0)]
-                        - moments[getIndex(cube.r1, cube.g0, cube.b1)]
-                        + moments[getIndex(cube.r1, cube.g0, cube.b0)]
-                        - moments[getIndex(cube.r0, cube.g1, cube.b1)]
-                        + moments[getIndex(cube.r0, cube.g1, cube.b0)]
-                        + moments[getIndex(cube.r0, cube.g0, cube.b1)]
-                        - moments[getIndex(cube.r0, cube.g0, cube.b0)];
-
-        int hypotenuse = dr * dr + dg * dg + db * db;
-        int volume = volume(cube, weights);
-        return xx - hypotenuse / ((double) volume);
-    }
-
-    Boolean cut(Box one, Box two) {
-        int wholeR = volume(one, momentsR);
-        int wholeG = volume(one, momentsG);
-        int wholeB = volume(one, momentsB);
-        int wholeW = volume(one, weights);
-
-        MaximizeResult maxRResult =
-                maximize(one, Direction.RED, one.r0 + 1, one.r1, wholeR, wholeG, wholeB, wholeW);
-        MaximizeResult maxGResult =
-                maximize(one, Direction.GREEN, one.g0 + 1, one.g1, wholeR, wholeG, wholeB, wholeW);
-        MaximizeResult maxBResult =
-                maximize(one, Direction.BLUE, one.b0 + 1, one.b1, wholeR, wholeG, wholeB, wholeW);
-        Direction cutDirection;
-        double maxR = maxRResult.maximum;
-        double maxG = maxGResult.maximum;
-        double maxB = maxBResult.maximum;
-        if (maxR >= maxG && maxR >= maxB) {
-            if (maxRResult.cutLocation < 0) {
-                return false;
-            }
-            cutDirection = Direction.RED;
-        } else if (maxG >= maxR && maxG >= maxB) {
-            cutDirection = Direction.GREEN;
-        } else {
-            cutDirection = Direction.BLUE;
-        }
-
-        two.r1 = one.r1;
-        two.g1 = one.g1;
-        two.b1 = one.b1;
-
-        switch (cutDirection) {
-            case RED:
-                one.r1 = maxRResult.cutLocation;
-                two.r0 = one.r1;
-                two.g0 = one.g0;
-                two.b0 = one.b0;
-                break;
-            case GREEN:
-                one.g1 = maxGResult.cutLocation;
-                two.r0 = one.r0;
-                two.g0 = one.g1;
-                two.b0 = one.b0;
-                break;
-            case BLUE:
-                one.b1 = maxBResult.cutLocation;
-                two.r0 = one.r0;
-                two.g0 = one.g0;
-                two.b0 = one.b1;
-                break;
-        }
-
-        one.vol = (one.r1 - one.r0) * (one.g1 - one.g0) * (one.b1 - one.b0);
-        two.vol = (two.r1 - two.r0) * (two.g1 - two.g0) * (two.b1 - two.b0);
-
-        return true;
-    }
-
-    MaximizeResult maximize(
-            Box cube,
-            Direction direction,
-            int first,
-            int last,
-            int wholeR,
-            int wholeG,
-            int wholeB,
-            int wholeW) {
-        int bottomR = bottom(cube, direction, momentsR);
-        int bottomG = bottom(cube, direction, momentsG);
-        int bottomB = bottom(cube, direction, momentsB);
-        int bottomW = bottom(cube, direction, weights);
-
-        double max = 0.0;
-        int cut = -1;
-
-        int halfR = 0;
-        int halfG = 0;
-        int halfB = 0;
-        int halfW = 0;
-        for (int i = first; i < last; i++) {
-            halfR = bottomR + top(cube, direction, i, momentsR);
-            halfG = bottomG + top(cube, direction, i, momentsG);
-            halfB = bottomB + top(cube, direction, i, momentsB);
-            halfW = bottomW + top(cube, direction, i, weights);
-            if (halfW == 0) {
-                continue;
-            }
-
-            double tempNumerator = halfR * halfR + halfG * halfG + halfB * halfB;
-            double tempDenominator = halfW;
-            double temp = tempNumerator / tempDenominator;
-
-            halfR = wholeR - halfR;
-            halfG = wholeG - halfG;
-            halfB = wholeB - halfB;
-            halfW = wholeW - halfW;
-            if (halfW == 0) {
-                continue;
-            }
-
-            tempNumerator = halfR * halfR + halfG * halfG + halfB * halfB;
-            tempDenominator = halfW;
-            temp += (tempNumerator / tempDenominator);
-
-            if (temp > max) {
-                max = temp;
-                cut = i;
-            }
-        }
-        return new MaximizeResult(cut, max);
-    }
-
-    static int volume(Box cube, int[] moment) {
-        return (moment[getIndex(cube.r1, cube.g1, cube.b1)]
-                - moment[getIndex(cube.r1, cube.g1, cube.b0)]
-                - moment[getIndex(cube.r1, cube.g0, cube.b1)]
-                + moment[getIndex(cube.r1, cube.g0, cube.b0)]
-                - moment[getIndex(cube.r0, cube.g1, cube.b1)]
-                + moment[getIndex(cube.r0, cube.g1, cube.b0)]
-                + moment[getIndex(cube.r0, cube.g0, cube.b1)]
-                - moment[getIndex(cube.r0, cube.g0, cube.b0)]);
-    }
-
-    static int bottom(Box cube, Direction direction, int[] moment) {
-        switch (direction) {
-            case RED:
-                return -moment[getIndex(cube.r0, cube.g1, cube.b1)]
-                        + moment[getIndex(cube.r0, cube.g1, cube.b0)]
-                        + moment[getIndex(cube.r0, cube.g0, cube.b1)]
-                        - moment[getIndex(cube.r0, cube.g0, cube.b0)];
-            case GREEN:
-                return -moment[getIndex(cube.r1, cube.g0, cube.b1)]
-                        + moment[getIndex(cube.r1, cube.g0, cube.b0)]
-                        + moment[getIndex(cube.r0, cube.g0, cube.b1)]
-                        - moment[getIndex(cube.r0, cube.g0, cube.b0)];
-            case BLUE:
-                return -moment[getIndex(cube.r1, cube.g1, cube.b0)]
-                        + moment[getIndex(cube.r1, cube.g0, cube.b0)]
-                        + moment[getIndex(cube.r0, cube.g1, cube.b0)]
-                        - moment[getIndex(cube.r0, cube.g0, cube.b0)];
-        }
-        throw new IllegalArgumentException("unexpected direction " + direction);
-    }
-
-    static int top(Box cube, Direction direction, int position, int[] moment) {
-        switch (direction) {
-            case RED:
-                return (moment[getIndex(position, cube.g1, cube.b1)]
-                        - moment[getIndex(position, cube.g1, cube.b0)]
-                        - moment[getIndex(position, cube.g0, cube.b1)]
-                        + moment[getIndex(position, cube.g0, cube.b0)]);
-            case GREEN:
-                return (moment[getIndex(cube.r1, position, cube.b1)]
-                        - moment[getIndex(cube.r1, position, cube.b0)]
-                        - moment[getIndex(cube.r0, position, cube.b1)]
-                        + moment[getIndex(cube.r0, position, cube.b0)]);
-            case BLUE:
-                return (moment[getIndex(cube.r1, cube.g1, position)]
-                        - moment[getIndex(cube.r1, cube.g0, position)]
-                        - moment[getIndex(cube.r0, cube.g1, position)]
-                        + moment[getIndex(cube.r0, cube.g0, position)]);
-        }
-        throw new IllegalArgumentException("unexpected direction " + direction);
-    }
-
-    private enum Direction {
-        RED,
-        GREEN,
-        BLUE
-    }
-
-    private static final class MaximizeResult {
-        // < 0 if cut impossible
-        int cutLocation;
-        double maximum;
-
-        MaximizeResult(int cut, double max) {
-            this.cutLocation = cut;
-            this.maximum = max;
-        }
-    }
-
-    private static final class CreateBoxesResult {
-        int requestedCount;
-        int resultCount;
-
-        CreateBoxesResult(int requestedCount, int resultCount) {
-            this.requestedCount = requestedCount;
-            this.resultCount = resultCount;
-        }
-    }
-
-    private static final class Box {
-        int r0 = 0;
-        int r1 = 0;
-        int g0 = 0;
-        int g1 = 0;
-        int b0 = 0;
-        int b1 = 0;
-        int vol = 0;
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/DynamicScheme.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/DynamicScheme.java
deleted file mode 100644
index e1cbfb1..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/DynamicScheme.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.scheme;
-
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.palettes.TonalPalette;
-import com.android.systemui.monet.utils.MathUtils;
-
-/**
- * Provides important settings for creating colors dynamically, and 6 color palettes. Requires: 1. A
- * color. (source color) 2. A theme. (Variant) 3. Whether or not its dark mode. 4. Contrast level.
- * (-1 to 1, currently contrast ratio 3.0 and 7.0)
- */
-public class DynamicScheme {
-    public final int sourceColorArgb;
-    public final Hct sourceColorHct;
-    public final Variant variant;
-    public final boolean isDark;
-    public final double contrastLevel;
-
-    public final TonalPalette primaryPalette;
-    public final TonalPalette secondaryPalette;
-    public final TonalPalette tertiaryPalette;
-    public final TonalPalette neutralPalette;
-    public final TonalPalette neutralVariantPalette;
-    public final TonalPalette errorPalette;
-
-    public DynamicScheme(
-            Hct sourceColorHct,
-            Variant variant,
-            boolean isDark,
-            double contrastLevel,
-            TonalPalette primaryPalette,
-            TonalPalette secondaryPalette,
-            TonalPalette tertiaryPalette,
-            TonalPalette neutralPalette,
-            TonalPalette neutralVariantPalette) {
-        this.sourceColorArgb = sourceColorHct.toInt();
-        this.sourceColorHct = sourceColorHct;
-        this.variant = variant;
-        this.isDark = isDark;
-        this.contrastLevel = contrastLevel;
-
-        this.primaryPalette = primaryPalette;
-        this.secondaryPalette = secondaryPalette;
-        this.tertiaryPalette = tertiaryPalette;
-        this.neutralPalette = neutralPalette;
-        this.neutralVariantPalette = neutralVariantPalette;
-        this.errorPalette = TonalPalette.fromHueAndChroma(25.0, 84.0);
-    }
-
-    /**
-     * Given a set of hues and set of hue rotations, locate which hues the source color's hue is
-     * between, apply the rotation at the same index as the first hue in the range, and return the
-     * rotated hue.
-     *
-     * @param sourceColorHct The color whose hue should be rotated.
-     * @param hues A set of hues.
-     * @param rotations A set of hue rotations.
-     * @return Color's hue with a rotation applied.
-     */
-    public static double getRotatedHue(Hct sourceColorHct, double[] hues, double[] rotations) {
-        final double sourceHue = sourceColorHct.getHue();
-        if (rotations.length == 1) {
-            return MathUtils.sanitizeDegreesDouble(sourceHue + rotations[0]);
-        }
-        final int size = hues.length;
-        for (int i = 0; i <= (size - 2); i++) {
-            final double thisHue = hues[i];
-            final double nextHue = hues[i + 1];
-            if (thisHue < sourceHue && sourceHue < nextHue) {
-                return MathUtils.sanitizeDegreesDouble(sourceHue + rotations[i]);
-            }
-        }
-        // If this statement executes, something is wrong, there should have been a rotation
-        // found using the arrays.
-        return sourceHue;
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/Scheme.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/Scheme.java
deleted file mode 100644
index fa736cc..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/Scheme.java
+++ /dev/null
@@ -1,784 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.scheme;
-
-import com.android.systemui.monet.palettes.CorePalette;
-
-/** Represents a Material color scheme, a mapping of color roles to colors. */
-public class Scheme {
-    private int primary;
-    private int onPrimary;
-    private int primaryContainer;
-    private int onPrimaryContainer;
-    private int secondary;
-    private int onSecondary;
-    private int secondaryContainer;
-    private int onSecondaryContainer;
-    private int tertiary;
-    private int onTertiary;
-    private int tertiaryContainer;
-    private int onTertiaryContainer;
-    private int error;
-    private int onError;
-    private int errorContainer;
-    private int onErrorContainer;
-    private int background;
-    private int onBackground;
-    private int surface;
-    private int onSurface;
-    private int surfaceVariant;
-    private int onSurfaceVariant;
-    private int outline;
-    private int outlineVariant;
-    private int shadow;
-    private int scrim;
-    private int inverseSurface;
-    private int inverseOnSurface;
-    private int inversePrimary;
-
-    public Scheme() {
-    }
-
-    public Scheme(
-            int primary,
-            int onPrimary,
-            int primaryContainer,
-            int onPrimaryContainer,
-            int secondary,
-            int onSecondary,
-            int secondaryContainer,
-            int onSecondaryContainer,
-            int tertiary,
-            int onTertiary,
-            int tertiaryContainer,
-            int onTertiaryContainer,
-            int error,
-            int onError,
-            int errorContainer,
-            int onErrorContainer,
-            int background,
-            int onBackground,
-            int surface,
-            int onSurface,
-            int surfaceVariant,
-            int onSurfaceVariant,
-            int outline,
-            int outlineVariant,
-            int shadow,
-            int scrim,
-            int inverseSurface,
-            int inverseOnSurface,
-            int inversePrimary) {
-        super();
-        this.primary = primary;
-        this.onPrimary = onPrimary;
-        this.primaryContainer = primaryContainer;
-        this.onPrimaryContainer = onPrimaryContainer;
-        this.secondary = secondary;
-        this.onSecondary = onSecondary;
-        this.secondaryContainer = secondaryContainer;
-        this.onSecondaryContainer = onSecondaryContainer;
-        this.tertiary = tertiary;
-        this.onTertiary = onTertiary;
-        this.tertiaryContainer = tertiaryContainer;
-        this.onTertiaryContainer = onTertiaryContainer;
-        this.error = error;
-        this.onError = onError;
-        this.errorContainer = errorContainer;
-        this.onErrorContainer = onErrorContainer;
-        this.background = background;
-        this.onBackground = onBackground;
-        this.surface = surface;
-        this.onSurface = onSurface;
-        this.surfaceVariant = surfaceVariant;
-        this.onSurfaceVariant = onSurfaceVariant;
-        this.outline = outline;
-        this.outlineVariant = outlineVariant;
-        this.shadow = shadow;
-        this.scrim = scrim;
-        this.inverseSurface = inverseSurface;
-        this.inverseOnSurface = inverseOnSurface;
-        this.inversePrimary = inversePrimary;
-    }
-
-    /** Create a light theme Scheme from a source color in ARGB, i.e. a hex code. */
-    public static Scheme light(int argb) {
-        return lightFromCorePalette(CorePalette.of(argb));
-    }
-
-    /** Create a dark theme Scheme from a source color in ARGB, i.e. a hex code. */
-    public static Scheme dark(int argb) {
-        return darkFromCorePalette(CorePalette.of(argb));
-    }
-
-    /** Create a light theme content-based Scheme from a source color in ARGB, i.e. a hex code. */
-    public static Scheme lightContent(int argb) {
-        return lightFromCorePalette(CorePalette.contentOf(argb));
-    }
-
-    /** Create a dark theme content-based Scheme from a source color in ARGB, i.e. a hex code. */
-    public static Scheme darkContent(int argb) {
-        return darkFromCorePalette(CorePalette.contentOf(argb));
-    }
-
-    private static Scheme lightFromCorePalette(CorePalette core) {
-        return new Scheme()
-                .withPrimary(core.a1.tone(40))
-                .withOnPrimary(core.a1.tone(100))
-                .withPrimaryContainer(core.a1.tone(90))
-                .withOnPrimaryContainer(core.a1.tone(10))
-                .withSecondary(core.a2.tone(40))
-                .withOnSecondary(core.a2.tone(100))
-                .withSecondaryContainer(core.a2.tone(90))
-                .withOnSecondaryContainer(core.a2.tone(10))
-                .withTertiary(core.a3.tone(40))
-                .withOnTertiary(core.a3.tone(100))
-                .withTertiaryContainer(core.a3.tone(90))
-                .withOnTertiaryContainer(core.a3.tone(10))
-                .withError(core.error.tone(40))
-                .withOnError(core.error.tone(100))
-                .withErrorContainer(core.error.tone(90))
-                .withOnErrorContainer(core.error.tone(10))
-                .withBackground(core.n1.tone(99))
-                .withOnBackground(core.n1.tone(10))
-                .withSurface(core.n1.tone(99))
-                .withOnSurface(core.n1.tone(10))
-                .withSurfaceVariant(core.n2.tone(90))
-                .withOnSurfaceVariant(core.n2.tone(30))
-                .withOutline(core.n2.tone(50))
-                .withOutlineVariant(core.n2.tone(80))
-                .withShadow(core.n1.tone(0))
-                .withScrim(core.n1.tone(0))
-                .withInverseSurface(core.n1.tone(20))
-                .withInverseOnSurface(core.n1.tone(95))
-                .withInversePrimary(core.a1.tone(80));
-    }
-
-    private static Scheme darkFromCorePalette(CorePalette core) {
-        return new Scheme()
-                .withPrimary(core.a1.tone(80))
-                .withOnPrimary(core.a1.tone(20))
-                .withPrimaryContainer(core.a1.tone(30))
-                .withOnPrimaryContainer(core.a1.tone(90))
-                .withSecondary(core.a2.tone(80))
-                .withOnSecondary(core.a2.tone(20))
-                .withSecondaryContainer(core.a2.tone(30))
-                .withOnSecondaryContainer(core.a2.tone(90))
-                .withTertiary(core.a3.tone(80))
-                .withOnTertiary(core.a3.tone(20))
-                .withTertiaryContainer(core.a3.tone(30))
-                .withOnTertiaryContainer(core.a3.tone(90))
-                .withError(core.error.tone(80))
-                .withOnError(core.error.tone(20))
-                .withErrorContainer(core.error.tone(30))
-                .withOnErrorContainer(core.error.tone(80))
-                .withBackground(core.n1.tone(10))
-                .withOnBackground(core.n1.tone(90))
-                .withSurface(core.n1.tone(10))
-                .withOnSurface(core.n1.tone(90))
-                .withSurfaceVariant(core.n2.tone(30))
-                .withOnSurfaceVariant(core.n2.tone(80))
-                .withOutline(core.n2.tone(60))
-                .withOutlineVariant(core.n2.tone(30))
-                .withShadow(core.n1.tone(0))
-                .withScrim(core.n1.tone(0))
-                .withInverseSurface(core.n1.tone(90))
-                .withInverseOnSurface(core.n1.tone(20))
-                .withInversePrimary(core.a1.tone(40));
-    }
-
-    public int getPrimary() {
-        return primary;
-    }
-
-    public void setPrimary(int primary) {
-        this.primary = primary;
-    }
-
-    public Scheme withPrimary(int primary) {
-        this.primary = primary;
-        return this;
-    }
-
-    public int getOnPrimary() {
-        return onPrimary;
-    }
-
-    public void setOnPrimary(int onPrimary) {
-        this.onPrimary = onPrimary;
-    }
-
-    public Scheme withOnPrimary(int onPrimary) {
-        this.onPrimary = onPrimary;
-        return this;
-    }
-
-    public int getPrimaryContainer() {
-        return primaryContainer;
-    }
-
-    public void setPrimaryContainer(int primaryContainer) {
-        this.primaryContainer = primaryContainer;
-    }
-
-    public Scheme withPrimaryContainer(int primaryContainer) {
-        this.primaryContainer = primaryContainer;
-        return this;
-    }
-
-    public int getOnPrimaryContainer() {
-        return onPrimaryContainer;
-    }
-
-    public void setOnPrimaryContainer(int onPrimaryContainer) {
-        this.onPrimaryContainer = onPrimaryContainer;
-    }
-
-    public Scheme withOnPrimaryContainer(int onPrimaryContainer) {
-        this.onPrimaryContainer = onPrimaryContainer;
-        return this;
-    }
-
-    public int getSecondary() {
-        return secondary;
-    }
-
-    public void setSecondary(int secondary) {
-        this.secondary = secondary;
-    }
-
-    public Scheme withSecondary(int secondary) {
-        this.secondary = secondary;
-        return this;
-    }
-
-    public int getOnSecondary() {
-        return onSecondary;
-    }
-
-    public void setOnSecondary(int onSecondary) {
-        this.onSecondary = onSecondary;
-    }
-
-    public Scheme withOnSecondary(int onSecondary) {
-        this.onSecondary = onSecondary;
-        return this;
-    }
-
-    public int getSecondaryContainer() {
-        return secondaryContainer;
-    }
-
-    public void setSecondaryContainer(int secondaryContainer) {
-        this.secondaryContainer = secondaryContainer;
-    }
-
-    public Scheme withSecondaryContainer(int secondaryContainer) {
-        this.secondaryContainer = secondaryContainer;
-        return this;
-    }
-
-    public int getOnSecondaryContainer() {
-        return onSecondaryContainer;
-    }
-
-    public void setOnSecondaryContainer(int onSecondaryContainer) {
-        this.onSecondaryContainer = onSecondaryContainer;
-    }
-
-    public Scheme withOnSecondaryContainer(int onSecondaryContainer) {
-        this.onSecondaryContainer = onSecondaryContainer;
-        return this;
-    }
-
-    public int getTertiary() {
-        return tertiary;
-    }
-
-    public void setTertiary(int tertiary) {
-        this.tertiary = tertiary;
-    }
-
-    public Scheme withTertiary(int tertiary) {
-        this.tertiary = tertiary;
-        return this;
-    }
-
-    public int getOnTertiary() {
-        return onTertiary;
-    }
-
-    public void setOnTertiary(int onTertiary) {
-        this.onTertiary = onTertiary;
-    }
-
-    public Scheme withOnTertiary(int onTertiary) {
-        this.onTertiary = onTertiary;
-        return this;
-    }
-
-    public int getTertiaryContainer() {
-        return tertiaryContainer;
-    }
-
-    public void setTertiaryContainer(int tertiaryContainer) {
-        this.tertiaryContainer = tertiaryContainer;
-    }
-
-    public Scheme withTertiaryContainer(int tertiaryContainer) {
-        this.tertiaryContainer = tertiaryContainer;
-        return this;
-    }
-
-    public int getOnTertiaryContainer() {
-        return onTertiaryContainer;
-    }
-
-    public void setOnTertiaryContainer(int onTertiaryContainer) {
-        this.onTertiaryContainer = onTertiaryContainer;
-    }
-
-    public Scheme withOnTertiaryContainer(int onTertiaryContainer) {
-        this.onTertiaryContainer = onTertiaryContainer;
-        return this;
-    }
-
-    public int getError() {
-        return error;
-    }
-
-    public void setError(int error) {
-        this.error = error;
-    }
-
-    public Scheme withError(int error) {
-        this.error = error;
-        return this;
-    }
-
-    public int getOnError() {
-        return onError;
-    }
-
-    public void setOnError(int onError) {
-        this.onError = onError;
-    }
-
-    public Scheme withOnError(int onError) {
-        this.onError = onError;
-        return this;
-    }
-
-    public int getErrorContainer() {
-        return errorContainer;
-    }
-
-    public void setErrorContainer(int errorContainer) {
-        this.errorContainer = errorContainer;
-    }
-
-    public Scheme withErrorContainer(int errorContainer) {
-        this.errorContainer = errorContainer;
-        return this;
-    }
-
-    public int getOnErrorContainer() {
-        return onErrorContainer;
-    }
-
-    public void setOnErrorContainer(int onErrorContainer) {
-        this.onErrorContainer = onErrorContainer;
-    }
-
-    public Scheme withOnErrorContainer(int onErrorContainer) {
-        this.onErrorContainer = onErrorContainer;
-        return this;
-    }
-
-    public int getBackground() {
-        return background;
-    }
-
-    public void setBackground(int background) {
-        this.background = background;
-    }
-
-    public Scheme withBackground(int background) {
-        this.background = background;
-        return this;
-    }
-
-    public int getOnBackground() {
-        return onBackground;
-    }
-
-    public void setOnBackground(int onBackground) {
-        this.onBackground = onBackground;
-    }
-
-    public Scheme withOnBackground(int onBackground) {
-        this.onBackground = onBackground;
-        return this;
-    }
-
-    public int getSurface() {
-        return surface;
-    }
-
-    public void setSurface(int surface) {
-        this.surface = surface;
-    }
-
-    public Scheme withSurface(int surface) {
-        this.surface = surface;
-        return this;
-    }
-
-    public int getOnSurface() {
-        return onSurface;
-    }
-
-    public void setOnSurface(int onSurface) {
-        this.onSurface = onSurface;
-    }
-
-    public Scheme withOnSurface(int onSurface) {
-        this.onSurface = onSurface;
-        return this;
-    }
-
-    public int getSurfaceVariant() {
-        return surfaceVariant;
-    }
-
-    public void setSurfaceVariant(int surfaceVariant) {
-        this.surfaceVariant = surfaceVariant;
-    }
-
-    public Scheme withSurfaceVariant(int surfaceVariant) {
-        this.surfaceVariant = surfaceVariant;
-        return this;
-    }
-
-    public int getOnSurfaceVariant() {
-        return onSurfaceVariant;
-    }
-
-    public void setOnSurfaceVariant(int onSurfaceVariant) {
-        this.onSurfaceVariant = onSurfaceVariant;
-    }
-
-    public Scheme withOnSurfaceVariant(int onSurfaceVariant) {
-        this.onSurfaceVariant = onSurfaceVariant;
-        return this;
-    }
-
-    public int getOutline() {
-        return outline;
-    }
-
-    public void setOutline(int outline) {
-        this.outline = outline;
-    }
-
-    public Scheme withOutline(int outline) {
-        this.outline = outline;
-        return this;
-    }
-
-    public int getOutlineVariant() {
-        return outlineVariant;
-    }
-
-    public void setOutlineVariant(int outlineVariant) {
-        this.outlineVariant = outlineVariant;
-    }
-
-    public Scheme withOutlineVariant(int outlineVariant) {
-        this.outlineVariant = outlineVariant;
-        return this;
-    }
-
-    public int getShadow() {
-        return shadow;
-    }
-
-    public void setShadow(int shadow) {
-        this.shadow = shadow;
-    }
-
-    public Scheme withShadow(int shadow) {
-        this.shadow = shadow;
-        return this;
-    }
-
-    public int getScrim() {
-        return scrim;
-    }
-
-    public void setScrim(int scrim) {
-        this.scrim = scrim;
-    }
-
-    public Scheme withScrim(int scrim) {
-        this.scrim = scrim;
-        return this;
-    }
-
-    public int getInverseSurface() {
-        return inverseSurface;
-    }
-
-    public void setInverseSurface(int inverseSurface) {
-        this.inverseSurface = inverseSurface;
-    }
-
-    public Scheme withInverseSurface(int inverseSurface) {
-        this.inverseSurface = inverseSurface;
-        return this;
-    }
-
-    public int getInverseOnSurface() {
-        return inverseOnSurface;
-    }
-
-    public void setInverseOnSurface(int inverseOnSurface) {
-        this.inverseOnSurface = inverseOnSurface;
-    }
-
-    public Scheme withInverseOnSurface(int inverseOnSurface) {
-        this.inverseOnSurface = inverseOnSurface;
-        return this;
-    }
-
-    public int getInversePrimary() {
-        return inversePrimary;
-    }
-
-    public void setInversePrimary(int inversePrimary) {
-        this.inversePrimary = inversePrimary;
-    }
-
-    public Scheme withInversePrimary(int inversePrimary) {
-        this.inversePrimary = inversePrimary;
-        return this;
-    }
-
-    @Override
-    public String toString() {
-        return "Scheme{"
-                + "primary="
-                + primary
-                + ", onPrimary="
-                + onPrimary
-                + ", primaryContainer="
-                + primaryContainer
-                + ", onPrimaryContainer="
-                + onPrimaryContainer
-                + ", secondary="
-                + secondary
-                + ", onSecondary="
-                + onSecondary
-                + ", secondaryContainer="
-                + secondaryContainer
-                + ", onSecondaryContainer="
-                + onSecondaryContainer
-                + ", tertiary="
-                + tertiary
-                + ", onTertiary="
-                + onTertiary
-                + ", tertiaryContainer="
-                + tertiaryContainer
-                + ", onTertiaryContainer="
-                + onTertiaryContainer
-                + ", error="
-                + error
-                + ", onError="
-                + onError
-                + ", errorContainer="
-                + errorContainer
-                + ", onErrorContainer="
-                + onErrorContainer
-                + ", background="
-                + background
-                + ", onBackground="
-                + onBackground
-                + ", surface="
-                + surface
-                + ", onSurface="
-                + onSurface
-                + ", surfaceVariant="
-                + surfaceVariant
-                + ", onSurfaceVariant="
-                + onSurfaceVariant
-                + ", outline="
-                + outline
-                + ", outlineVariant="
-                + outlineVariant
-                + ", shadow="
-                + shadow
-                + ", scrim="
-                + scrim
-                + ", inverseSurface="
-                + inverseSurface
-                + ", inverseOnSurface="
-                + inverseOnSurface
-                + ", inversePrimary="
-                + inversePrimary
-                + '}';
-    }
-
-    @Override
-    public boolean equals(Object object) {
-        if (this == object) {
-            return true;
-        }
-        if (!(object instanceof Scheme)) {
-            return false;
-        }
-        if (!super.equals(object)) {
-            return false;
-        }
-
-        Scheme scheme = (Scheme) object;
-
-        if (primary != scheme.primary) {
-            return false;
-        }
-        if (onPrimary != scheme.onPrimary) {
-            return false;
-        }
-        if (primaryContainer != scheme.primaryContainer) {
-            return false;
-        }
-        if (onPrimaryContainer != scheme.onPrimaryContainer) {
-            return false;
-        }
-        if (secondary != scheme.secondary) {
-            return false;
-        }
-        if (onSecondary != scheme.onSecondary) {
-            return false;
-        }
-        if (secondaryContainer != scheme.secondaryContainer) {
-            return false;
-        }
-        if (onSecondaryContainer != scheme.onSecondaryContainer) {
-            return false;
-        }
-        if (tertiary != scheme.tertiary) {
-            return false;
-        }
-        if (onTertiary != scheme.onTertiary) {
-            return false;
-        }
-        if (tertiaryContainer != scheme.tertiaryContainer) {
-            return false;
-        }
-        if (onTertiaryContainer != scheme.onTertiaryContainer) {
-            return false;
-        }
-        if (error != scheme.error) {
-            return false;
-        }
-        if (onError != scheme.onError) {
-            return false;
-        }
-        if (errorContainer != scheme.errorContainer) {
-            return false;
-        }
-        if (onErrorContainer != scheme.onErrorContainer) {
-            return false;
-        }
-        if (background != scheme.background) {
-            return false;
-        }
-        if (onBackground != scheme.onBackground) {
-            return false;
-        }
-        if (surface != scheme.surface) {
-            return false;
-        }
-        if (onSurface != scheme.onSurface) {
-            return false;
-        }
-        if (surfaceVariant != scheme.surfaceVariant) {
-            return false;
-        }
-        if (onSurfaceVariant != scheme.onSurfaceVariant) {
-            return false;
-        }
-        if (outline != scheme.outline) {
-            return false;
-        }
-        if (outlineVariant != scheme.outlineVariant) {
-            return false;
-        }
-        if (shadow != scheme.shadow) {
-            return false;
-        }
-        if (scrim != scheme.scrim) {
-            return false;
-        }
-        if (inverseSurface != scheme.inverseSurface) {
-            return false;
-        }
-        if (inverseOnSurface != scheme.inverseOnSurface) {
-            return false;
-        }
-        if (inversePrimary != scheme.inversePrimary) {
-            return false;
-        }
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = super.hashCode();
-        result = 31 * result + primary;
-        result = 31 * result + onPrimary;
-        result = 31 * result + primaryContainer;
-        result = 31 * result + onPrimaryContainer;
-        result = 31 * result + secondary;
-        result = 31 * result + onSecondary;
-        result = 31 * result + secondaryContainer;
-        result = 31 * result + onSecondaryContainer;
-        result = 31 * result + tertiary;
-        result = 31 * result + onTertiary;
-        result = 31 * result + tertiaryContainer;
-        result = 31 * result + onTertiaryContainer;
-        result = 31 * result + error;
-        result = 31 * result + onError;
-        result = 31 * result + errorContainer;
-        result = 31 * result + onErrorContainer;
-        result = 31 * result + background;
-        result = 31 * result + onBackground;
-        result = 31 * result + surface;
-        result = 31 * result + onSurface;
-        result = 31 * result + surfaceVariant;
-        result = 31 * result + onSurfaceVariant;
-        result = 31 * result + outline;
-        result = 31 * result + outlineVariant;
-        result = 31 * result + shadow;
-        result = 31 * result + scrim;
-        result = 31 * result + inverseSurface;
-        result = 31 * result + inverseOnSurface;
-        result = 31 * result + inversePrimary;
-        return result;
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeContent.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeContent.java
deleted file mode 100644
index 4c3c07c..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeContent.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.scheme;
-
-import static java.lang.Math.max;
-
-import com.android.systemui.monet.dislike.DislikeAnalyzer;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.palettes.TonalPalette;
-import com.android.systemui.monet.temperature.TemperatureCache;
-
-/**
- * A scheme that places the source color in Scheme.primaryContainer.
- *
- * <p>Primary Container is the source color, adjusted for color relativity. It maintains constant
- * appearance in light mode and dark mode. This adds ~5 tone in light mode, and subtracts ~5 tone in
- * dark mode.
- *
- * <p>Tertiary Container is an analogous color, specifically, the analog of a color wheel divided
- * into 6, and the precise analog is the one found by increasing hue. This is a scientifically
- * grounded equivalent to rotating hue clockwise by 60 degrees. It also maintains constant
- * appearance.
- */
-public class SchemeContent extends DynamicScheme {
-    public SchemeContent(Hct sourceColorHct, boolean isDark, double contrastLevel) {
-        super(
-                sourceColorHct,
-                Variant.CONTENT,
-                isDark,
-                contrastLevel,
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), sourceColorHct.getChroma()),
-                TonalPalette.fromHueAndChroma(
-                        sourceColorHct.getHue(),
-                        max(sourceColorHct.getChroma() - 32.0, sourceColorHct.getChroma() * 0.5)),
-                TonalPalette.fromHct(
-                        DislikeAnalyzer.fixIfDisliked(
-                                new TemperatureCache(sourceColorHct)
-                                        .getAnalogousColors(/* count= */ 3, /* divisions= */ 6)
-                                        .get(2))),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(),
-                        sourceColorHct.getChroma() / 8.0),
-                TonalPalette.fromHueAndChroma(
-                        sourceColorHct.getHue(), (sourceColorHct.getChroma() / 8.0) + 4.0));
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeExpressive.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeExpressive.java
deleted file mode 100644
index 234826e..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeExpressive.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.scheme;
-
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.palettes.TonalPalette;
-import com.android.systemui.monet.utils.MathUtils;
-
-/** A playful theme - the source color's hue does not appear in the theme. */
-public class SchemeExpressive extends DynamicScheme {
-    // NOMUTANTS--arbitrary increments/decrements, correctly, still passes tests.
-    private static final double[] HUES = {0, 21, 51, 121, 151, 191, 271, 321, 360};
-    private static final double[] SECONDARY_ROTATIONS = {45, 95, 45, 20, 45, 90, 45, 45, 45};
-    private static final double[] TERTIARY_ROTATIONS = {120, 120, 20, 45, 20, 15, 20, 120, 120};
-
-    public SchemeExpressive(Hct sourceColorHct, boolean isDark, double contrastLevel) {
-        super(
-                sourceColorHct,
-                Variant.EXPRESSIVE,
-                isDark,
-                contrastLevel,
-                TonalPalette.fromHueAndChroma(
-                        MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 240.0), 40.0),
-                TonalPalette.fromHueAndChroma(
-                        DynamicScheme.getRotatedHue(sourceColorHct, HUES, SECONDARY_ROTATIONS),
-                        24.0),
-                TonalPalette.fromHueAndChroma(
-                        DynamicScheme.getRotatedHue(sourceColorHct, HUES, TERTIARY_ROTATIONS),
-                        32.0),
-                TonalPalette.fromHueAndChroma(
-                        MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 15.0), 8.0),
-                TonalPalette.fromHueAndChroma(
-                        MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 15.0), 12.0));
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeFidelity.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeFidelity.java
deleted file mode 100644
index dfbf56d..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeFidelity.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.scheme;
-
-import com.android.systemui.monet.dislike.DislikeAnalyzer;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.palettes.TonalPalette;
-import com.android.systemui.monet.temperature.TemperatureCache;
-
-/**
- * A scheme that places the source color in Scheme.primaryContainer.
- *
- * <p>Primary Container is the source color, adjusted for color relativity. It maintains constant
- * appearance in light mode and dark mode. This adds ~5 tone in light mode, and subtracts ~5 tone in
- * dark mode.
- *
- * <p>Tertiary Container is the complement to the source color, using TemperatureCache. It also
- * maintains constant appearance.
- */
-public class SchemeFidelity extends DynamicScheme {
-    public SchemeFidelity(Hct sourceColorHct, boolean isDark, double contrastLevel) {
-        super(
-                sourceColorHct,
-                Variant.FIDELITY,
-                isDark,
-                contrastLevel,
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), sourceColorHct.getChroma()),
-                TonalPalette.fromHueAndChroma(
-                        sourceColorHct.getHue(),
-                        Math.max(sourceColorHct.getChroma() - 32.0,
-                                sourceColorHct.getChroma() * 0.5)),
-                TonalPalette.fromHct(
-                        DislikeAnalyzer.fixIfDisliked(
-                                new TemperatureCache(sourceColorHct).getComplement())),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(),
-                        sourceColorHct.getChroma() / 8.0),
-                TonalPalette.fromHueAndChroma(
-                        sourceColorHct.getHue(), (sourceColorHct.getChroma() / 8.0) + 4.0));
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeFruitSalad.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeFruitSalad.java
deleted file mode 100644
index aa2c979..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeFruitSalad.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.scheme;
-
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.palettes.TonalPalette;
-import com.android.systemui.monet.utils.MathUtils;
-
-/** A playful theme - the source color's hue does not appear in the theme. */
-public class SchemeFruitSalad extends DynamicScheme {
-    public SchemeFruitSalad(Hct sourceColorHct, boolean isDark, double contrastLevel) {
-        super(
-                sourceColorHct,
-                Variant.FRUIT_SALAD,
-                isDark,
-                contrastLevel,
-                TonalPalette.fromHueAndChroma(
-                        MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() - 50.0), 48.0),
-                TonalPalette.fromHueAndChroma(
-                        MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() - 50.0), 36.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 36.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 10.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 16.0));
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeMonochrome.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeMonochrome.java
deleted file mode 100644
index 509ea2a..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeMonochrome.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.scheme;
-
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.palettes.TonalPalette;
-
-/** A monochrome theme, colors are purely black / white / gray. */
-public class SchemeMonochrome extends DynamicScheme {
-    public SchemeMonochrome(Hct sourceColorHct, boolean isDark, double contrastLevel) {
-        super(
-                sourceColorHct,
-                Variant.MONOCHROME,
-                isDark,
-                contrastLevel,
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0));
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeNeutral.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeNeutral.java
deleted file mode 100644
index fdbde9de..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeNeutral.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.scheme;
-
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.palettes.TonalPalette;
-
-/** A theme that's slightly more chromatic than monochrome, which is purely black / white / gray. */
-public class SchemeNeutral extends DynamicScheme {
-    public SchemeNeutral(Hct sourceColorHct, boolean isDark, double contrastLevel) {
-        super(
-                sourceColorHct,
-                Variant.NEUTRAL,
-                isDark,
-                contrastLevel,
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 12.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 16.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 2.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 2.0));
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeRainbow.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeRainbow.java
deleted file mode 100644
index 92eb27f..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeRainbow.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.scheme;
-
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.palettes.TonalPalette;
-import com.android.systemui.monet.utils.MathUtils;
-
-/** A playful theme - the source color's hue does not appear in the theme. */
-public class SchemeRainbow extends DynamicScheme {
-    public SchemeRainbow(Hct sourceColorHct, boolean isDark, double contrastLevel) {
-        super(
-                sourceColorHct,
-                Variant.RAINBOW,
-                isDark,
-                contrastLevel,
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 48.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 16.0),
-                TonalPalette.fromHueAndChroma(
-                        MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 60.0), 24.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 0.0));
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeTonalSpot.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeTonalSpot.java
deleted file mode 100644
index cc6b492..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeTonalSpot.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.scheme;
-
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.palettes.TonalPalette;
-import com.android.systemui.monet.utils.MathUtils;
-
-/** A calm theme, sedated colors that aren't particularly chromatic. */
-public class SchemeTonalSpot extends DynamicScheme {
-    public SchemeTonalSpot(Hct sourceColorHct, boolean isDark, double contrastLevel) {
-        super(
-                sourceColorHct,
-                Variant.TONAL_SPOT,
-                isDark,
-                contrastLevel,
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 36.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 16.0),
-                TonalPalette.fromHueAndChroma(
-                        MathUtils.sanitizeDegreesDouble(sourceColorHct.getHue() + 60.0), 24.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 6.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 8.0));
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeVibrant.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeVibrant.java
deleted file mode 100644
index 160fab7..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/SchemeVibrant.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.scheme;
-
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.palettes.TonalPalette;
-
-/** A loud theme, colorfulness is maximum for Primary palette, increased for others. */
-public class SchemeVibrant extends DynamicScheme {
-    private static final double[] HUES = {0, 41, 61, 101, 131, 181, 251, 301, 360};
-    private static final double[] SECONDARY_ROTATIONS = {18, 15, 10, 12, 15, 18, 15, 12, 12};
-    private static final double[] TERTIARY_ROTATIONS = {35, 30, 20, 25, 30, 35, 30, 25, 25};
-
-    public SchemeVibrant(Hct sourceColorHct, boolean isDark, double contrastLevel) {
-        super(
-                sourceColorHct,
-                Variant.VIBRANT,
-                isDark,
-                contrastLevel,
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 200.0),
-                TonalPalette.fromHueAndChroma(
-                        DynamicScheme.getRotatedHue(sourceColorHct, HUES, SECONDARY_ROTATIONS),
-                        24.0),
-                TonalPalette.fromHueAndChroma(
-                        DynamicScheme.getRotatedHue(sourceColorHct, HUES, TERTIARY_ROTATIONS),
-                        32.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 10.0),
-                TonalPalette.fromHueAndChroma(sourceColorHct.getHue(), 12.0));
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/Variant.java b/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/Variant.java
deleted file mode 100644
index 99aa2de..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/scheme/Variant.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.scheme;
-
-/** Themes for Dynamic Color. */
-public enum Variant {
-    MONOCHROME,
-    NEUTRAL,
-    TONAL_SPOT,
-    VIBRANT,
-    EXPRESSIVE,
-    FIDELITY,
-    CONTENT,
-    RAINBOW,
-    FRUIT_SALAD
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/score/Score.java b/packages/SystemUI/monet/src/com/android/systemui/monet/score/Score.java
deleted file mode 100644
index 09ea7a3..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/score/Score.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.score;
-
-import com.android.systemui.monet.hct.Cam16;
-import com.android.systemui.monet.utils.ColorUtils;
-import com.android.systemui.monet.utils.MathUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Given a large set of colors, remove colors that are unsuitable for a UI theme, and rank the rest
- * based on suitability.
- *
- * <p>Enables use of a high cluster count for image quantization, thus ensuring colors aren't
- * muddied, while curating the high cluster count to a much smaller number of appropriate choices.
- */
-public final class Score {
-    private static final double CUTOFF_CHROMA = 15.;
-    private static final double CUTOFF_EXCITED_PROPORTION = 0.01;
-    private static final double CUTOFF_TONE = 10.;
-    private static final double TARGET_CHROMA = 48.;
-    private static final double WEIGHT_PROPORTION = 0.7;
-    private static final double WEIGHT_CHROMA_ABOVE = 0.3;
-    private static final double WEIGHT_CHROMA_BELOW = 0.1;
-
-    private Score() {
-    }
-
-    /**
-     * Given a map with keys of colors and values of how often the color appears, rank the colors
-     * based on suitability for being used for a UI theme.
-     *
-     * @param colorsToPopulation map with keys of colors and values of how often the color appears,
-     *                           usually from a source image.
-     * @return Colors sorted by suitability for a UI theme. The most suitable color is the first
-     * item,
-     * the least suitable is the last. There will always be at least one color returned. If all
-     * the input colors were not suitable for a theme, a default fallback color will be provided.
-     */
-    public static List<Integer> score(Map<Integer, Integer> colorsToPopulation) {
-        // Determine the total count of all colors.
-        double populationSum = 0.;
-        for (Map.Entry<Integer, Integer> entry : colorsToPopulation.entrySet()) {
-            populationSum += entry.getValue();
-        }
-
-        // Turn the count of each color into a proportion by dividing by the total
-        // count. Also, fill a cache of CAM16 colors representing each color, and
-        // record the proportion of colors for each CAM16 hue.
-        Map<Integer, Cam16> colorsToCam = new HashMap<>();
-        double[] hueProportions = new double[361];
-        for (Map.Entry<Integer, Integer> entry : colorsToPopulation.entrySet()) {
-            int color = entry.getKey();
-            double population = entry.getValue();
-            double proportion = population / populationSum;
-
-            Cam16 cam = Cam16.fromInt(color);
-            colorsToCam.put(color, cam);
-
-            int hue = (int) Math.round(cam.getHue());
-            hueProportions[hue] += proportion;
-        }
-
-        // Determine the proportion of the colors around each color, by summing the
-        // proportions around each color's hue.
-        Map<Integer, Double> colorsToExcitedProportion = new HashMap<>();
-        for (Map.Entry<Integer, Cam16> entry : colorsToCam.entrySet()) {
-            int color = entry.getKey();
-            Cam16 cam = entry.getValue();
-            int hue = (int) Math.round(cam.getHue());
-
-            double excitedProportion = 0.;
-            for (int j = (hue - 15); j < (hue + 15); j++) {
-                int neighborHue = MathUtils.sanitizeDegreesInt(j);
-                excitedProportion += hueProportions[neighborHue];
-            }
-
-            colorsToExcitedProportion.put(color, excitedProportion);
-        }
-
-        // Score the colors by their proportion, as well as how chromatic they are.
-        Map<Integer, Double> colorsToScore = new HashMap<>();
-        for (Map.Entry<Integer, Cam16> entry : colorsToCam.entrySet()) {
-            int color = entry.getKey();
-            Cam16 cam = entry.getValue();
-
-            double proportion = colorsToExcitedProportion.get(color);
-            double proportionScore = proportion * 100.0 * WEIGHT_PROPORTION;
-
-            double chromaWeight =
-                    cam.getChroma() < TARGET_CHROMA ? WEIGHT_CHROMA_BELOW : WEIGHT_CHROMA_ABOVE;
-            double chromaScore = (cam.getChroma() - TARGET_CHROMA) * chromaWeight;
-
-            double score = proportionScore + chromaScore;
-            colorsToScore.put(color, score);
-        }
-
-        // Remove colors that are unsuitable, ex. very dark or unchromatic colors.
-        // Also, remove colors that are very similar in hue.
-        List<Integer> filteredColors = filter(colorsToExcitedProportion, colorsToCam);
-        Map<Integer, Double> filteredColorsToScore = new HashMap<>();
-        for (int color : filteredColors) {
-            filteredColorsToScore.put(color, colorsToScore.get(color));
-        }
-
-        // Ensure the list of colors returned is sorted such that the first in the
-        // list is the most suitable, and the last is the least suitable.
-        List<Map.Entry<Integer, Double>> entryList = new ArrayList<>(
-                filteredColorsToScore.entrySet());
-        Collections.sort(entryList, new ScoredComparator());
-        List<Integer> colorsByScoreDescending = new ArrayList<>();
-        for (Map.Entry<Integer, Double> entry : entryList) {
-            int color = entry.getKey();
-            Cam16 cam = colorsToCam.get(color);
-            boolean duplicateHue = false;
-
-            for (Integer alreadyChosenColor : colorsByScoreDescending) {
-                Cam16 alreadyChosenCam = colorsToCam.get(alreadyChosenColor);
-                if (MathUtils.differenceDegrees(cam.getHue(), alreadyChosenCam.getHue()) < 15) {
-                    duplicateHue = true;
-                    break;
-                }
-            }
-
-            if (duplicateHue) {
-                continue;
-            }
-            colorsByScoreDescending.add(entry.getKey());
-        }
-
-        // Ensure that at least one color is returned.
-        if (colorsByScoreDescending.isEmpty()) {
-            colorsByScoreDescending.add(0xff4285F4);
-        }
-        return colorsByScoreDescending;
-    }
-
-    private static List<Integer> filter(
-            Map<Integer, Double> colorsToExcitedProportion, Map<Integer, Cam16> colorsToCam) {
-        List<Integer> filtered = new ArrayList<>();
-        for (Map.Entry<Integer, Cam16> entry : colorsToCam.entrySet()) {
-            int color = entry.getKey();
-            Cam16 cam = entry.getValue();
-            double proportion = colorsToExcitedProportion.get(color);
-
-            if (cam.getChroma() >= CUTOFF_CHROMA
-                    && ColorUtils.lstarFromArgb(color) >= CUTOFF_TONE
-                    && proportion >= CUTOFF_EXCITED_PROPORTION) {
-                filtered.add(color);
-            }
-        }
-        return filtered;
-    }
-
-    static class ScoredComparator implements Comparator<Map.Entry<Integer, Double>> {
-        ScoredComparator() {
-        }
-
-        @Override
-        public int compare(Map.Entry<Integer, Double> entry1, Map.Entry<Integer, Double> entry2) {
-            return -entry1.getValue().compareTo(entry2.getValue());
-        }
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/temperature/TemperatureCache.java b/packages/SystemUI/monet/src/com/android/systemui/monet/temperature/TemperatureCache.java
deleted file mode 100644
index 2f2022b..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/temperature/TemperatureCache.java
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.temperature;
-
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.utils.ColorUtils;
-import com.android.systemui.monet.utils.MathUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Design utilities using color temperature theory.
- *
- * <p>Analogous colors, complementary color, and cache to efficiently, lazily, generate data for
- * calculations when needed.
- */
-public final class TemperatureCache {
-    private final Hct input;
-
-    private Hct precomputedComplement;
-    private List<Hct> precomputedHctsByTemp;
-    private List<Hct> precomputedHctsByHue;
-    private Map<Hct, Double> precomputedTempsByHct;
-
-    private TemperatureCache() {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Create a cache that allows calculation of ex. complementary and analogous colors.
-     *
-     * @param input Color to find complement/analogous colors of. Any colors will have the same
-     *              tone,
-     *              and chroma as the input color, modulo any restrictions due to the other hues
-     *              having lower
-     *              limits on chroma.
-     */
-    public TemperatureCache(Hct input) {
-        this.input = input;
-    }
-
-    /**
-     * A color that complements the input color aesthetically.
-     *
-     * <p>In art, this is usually described as being across the color wheel. History of this shows
-     * intent as a color that is just as cool-warm as the input color is warm-cool.
-     */
-    public Hct getComplement() {
-        if (precomputedComplement != null) {
-            return precomputedComplement;
-        }
-
-        double coldestHue = getColdest().getHue();
-        double coldestTemp = getTempsByHct().get(getColdest());
-
-        double warmestHue = getWarmest().getHue();
-        double warmestTemp = getTempsByHct().get(getWarmest());
-        double range = warmestTemp - coldestTemp;
-        boolean startHueIsColdestToWarmest = isBetween(input.getHue(), coldestHue, warmestHue);
-        double startHue = startHueIsColdestToWarmest ? warmestHue : coldestHue;
-        double endHue = startHueIsColdestToWarmest ? coldestHue : warmestHue;
-        double directionOfRotation = 1.;
-        double smallestError = 1000.;
-        Hct answer = getHctsByHue().get((int) Math.round(input.getHue()));
-
-        double complementRelativeTemp = (1. - getRelativeTemperature(input));
-        // Find the color in the other section, closest to the inverse percentile
-        // of the input color. This is the complement.
-        for (double hueAddend = 0.; hueAddend <= 360.; hueAddend += 1.) {
-            double hue = MathUtils.sanitizeDegreesDouble(
-                    startHue + directionOfRotation * hueAddend);
-            if (!isBetween(hue, startHue, endHue)) {
-                continue;
-            }
-            Hct possibleAnswer = getHctsByHue().get((int) Math.round(hue));
-            double relativeTemp =
-                    (getTempsByHct().get(possibleAnswer) - coldestTemp) / range;
-            double error = Math.abs(complementRelativeTemp - relativeTemp);
-            if (error < smallestError) {
-                smallestError = error;
-                answer = possibleAnswer;
-            }
-        }
-        precomputedComplement = answer;
-        return precomputedComplement;
-    }
-
-    /**
-     * 5 colors that pair well with the input color.
-     *
-     * <p>The colors are equidistant in temperature and adjacent in hue.
-     */
-    public List<Hct> getAnalogousColors() {
-        return getAnalogousColors(5, 12);
-    }
-
-    /**
-     * A set of colors with differing hues, equidistant in temperature.
-     *
-     * <p>In art, this is usually described as a set of 5 colors on a color wheel divided into 12
-     * sections. This method allows provision of either of those values.
-     *
-     * <p>Behavior is undefined when count or divisions is 0. When divisions < count, colors repeat.
-     *
-     * @param count     The number of colors to return, includes the input color.
-     * @param divisions The number of divisions on the color wheel.
-     */
-    public List<Hct> getAnalogousColors(int count, int divisions) {
-        // The starting hue is the hue of the input color.
-        int startHue = (int) Math.round(input.getHue());
-        Hct startHct = getHctsByHue().get(startHue);
-        double lastTemp = getRelativeTemperature(startHct);
-
-        List<Hct> allColors = new ArrayList<>();
-        allColors.add(startHct);
-
-        double absoluteTotalTempDelta = 0.f;
-        for (int i = 0; i < 360; i++) {
-            int hue = MathUtils.sanitizeDegreesInt(startHue + i);
-            Hct hct = getHctsByHue().get(hue);
-            double temp = getRelativeTemperature(hct);
-            double tempDelta = Math.abs(temp - lastTemp);
-            lastTemp = temp;
-            absoluteTotalTempDelta += tempDelta;
-        }
-
-        int hueAddend = 1;
-        double tempStep = absoluteTotalTempDelta / (double) divisions;
-        double totalTempDelta = 0.0;
-        lastTemp = getRelativeTemperature(startHct);
-        while (allColors.size() < divisions) {
-            int hue = MathUtils.sanitizeDegreesInt(startHue + hueAddend);
-            Hct hct = getHctsByHue().get(hue);
-            double temp = getRelativeTemperature(hct);
-            double tempDelta = Math.abs(temp - lastTemp);
-            totalTempDelta += tempDelta;
-
-            double desiredTotalTempDeltaForIndex = (allColors.size() * tempStep);
-            boolean indexSatisfied = totalTempDelta >= desiredTotalTempDeltaForIndex;
-            int indexAddend = 1;
-            // Keep adding this hue to the answers until its temperature is
-            // insufficient. This ensures consistent behavior when there aren't
-            // `divisions` discrete steps between 0 and 360 in hue with `tempStep`
-            // delta in temperature between them.
-            //
-            // For example, white and black have no analogues: there are no other
-            // colors at T100/T0. Therefore, they should just be added to the array
-            // as answers.
-            while (indexSatisfied && allColors.size() < divisions) {
-                allColors.add(hct);
-                desiredTotalTempDeltaForIndex = ((allColors.size() + indexAddend) * tempStep);
-                indexSatisfied = totalTempDelta >= desiredTotalTempDeltaForIndex;
-                indexAddend++;
-            }
-            lastTemp = temp;
-            hueAddend++;
-
-            if (hueAddend > 360) {
-                while (allColors.size() < divisions) {
-                    allColors.add(hct);
-                }
-                break;
-            }
-        }
-
-        List<Hct> answers = new ArrayList<>();
-        answers.add(input);
-
-        int ccwCount = (int) Math.floor(((double) count - 1.0) / 2.0);
-        for (int i = 1; i < (ccwCount + 1); i++) {
-            int index = 0 - i;
-            while (index < 0) {
-                index = allColors.size() + index;
-            }
-            if (index >= allColors.size()) {
-                index = index % allColors.size();
-            }
-            answers.add(0, allColors.get(index));
-        }
-
-        int cwCount = count - ccwCount - 1;
-        for (int i = 1; i < (cwCount + 1); i++) {
-            int index = i;
-            while (index < 0) {
-                index = allColors.size() + index;
-            }
-            if (index >= allColors.size()) {
-                index = index % allColors.size();
-            }
-            answers.add(allColors.get(index));
-        }
-
-        return answers;
-    }
-
-    /**
-     * Temperature relative to all colors with the same chroma and tone.
-     *
-     * @param hct HCT to find the relative temperature of.
-     * @return Value on a scale from 0 to 1.
-     */
-    public double getRelativeTemperature(Hct hct) {
-        double range = getTempsByHct().get(getWarmest()) - getTempsByHct().get(getColdest());
-        double differenceFromColdest =
-                getTempsByHct().get(hct) - getTempsByHct().get(getColdest());
-        // Handle when there's no difference in temperature between warmest and
-        // coldest: for example, at T100, only one color is available, white.
-        if (range == 0.) {
-            return 0.5;
-        }
-        return differenceFromColdest / range;
-    }
-
-    /**
-     * Value representing cool-warm factor of a color. Values below 0 are considered cool, above,
-     * warm.
-     *
-     * <p>Color science has researched emotion and harmony, which art uses to select colors.
-     * Warm-cool
-     * is the foundation of analogous and complementary colors. See: - Li-Chen Ou's Chapter 19 in
-     * Handbook of Color Psychology (2015). - Josef Albers' Interaction of Color chapters 19 and
-     * 21.
-     *
-     * <p>Implementation of Ou, Woodcock and Wright's algorithm, which uses Lab/LCH color space.
-     * Return value has these properties:<br>
-     * - Values below 0 are cool, above 0 are warm.<br>
-     * - Lower bound: -9.66. Chroma is infinite. Assuming max of Lab chroma 130.<br>
-     * - Upper bound: 8.61. Chroma is infinite. Assuming max of Lab chroma 130.
-     */
-    public static double rawTemperature(Hct color) {
-        double[] lab = ColorUtils.labFromArgb(color.toInt());
-        double hue = MathUtils.sanitizeDegreesDouble(Math.toDegrees(Math.atan2(lab[2], lab[1])));
-        double chroma = Math.hypot(lab[1], lab[2]);
-        return -0.5
-                + 0.02
-                * Math.pow(chroma, 1.07)
-                * Math.cos(Math.toRadians(MathUtils.sanitizeDegreesDouble(hue - 50.)));
-    }
-
-    /** Coldest color with same chroma and tone as input. */
-    private Hct getColdest() {
-        return getHctsByTemp().get(0);
-    }
-
-    /**
-     * HCTs for all colors with the same chroma/tone as the input.
-     *
-     * <p>Sorted by hue, ex. index 0 is hue 0.
-     */
-    private List<Hct> getHctsByHue() {
-        if (precomputedHctsByHue != null) {
-            return precomputedHctsByHue;
-        }
-        List<Hct> hcts = new ArrayList<>();
-        for (double hue = 0.; hue <= 360.; hue += 1.) {
-            Hct colorAtHue = Hct.from(hue, input.getChroma(), input.getTone());
-            hcts.add(colorAtHue);
-        }
-        precomputedHctsByHue = Collections.unmodifiableList(hcts);
-        return precomputedHctsByHue;
-    }
-
-    /**
-     * HCTs for all colors with the same chroma/tone as the input.
-     *
-     * <p>Sorted from coldest first to warmest last.
-     */
-    // Prevent lint for Comparator not being available on Android before API level 24, 7.0, 2016.
-    // "AndroidJdkLibsChecker" for one linter, "NewApi" for another.
-    // A java_library Bazel rule with an Android constraint cannot skip these warnings without this
-    // annotation; another solution would be to create an android_library rule and supply
-    // AndroidManifest with an SDK set higher than 23.
-    @SuppressWarnings({"AndroidJdkLibsChecker", "NewApi"})
-    private List<Hct> getHctsByTemp() {
-        if (precomputedHctsByTemp != null) {
-            return precomputedHctsByTemp;
-        }
-
-        List<Hct> hcts = new ArrayList<>(getHctsByHue());
-        hcts.add(input);
-        Comparator<Hct> temperaturesComparator =
-                Comparator.comparing((Hct arg) -> getTempsByHct().get(arg), Double::compareTo);
-        Collections.sort(hcts, temperaturesComparator);
-        precomputedHctsByTemp = hcts;
-        return precomputedHctsByTemp;
-    }
-
-    /** Keys of HCTs in getHctsByTemp, values of raw temperature. */
-    private Map<Hct, Double> getTempsByHct() {
-        if (precomputedTempsByHct != null) {
-            return precomputedTempsByHct;
-        }
-
-        List<Hct> allHcts = new ArrayList<>(getHctsByHue());
-        allHcts.add(input);
-
-        Map<Hct, Double> temperaturesByHct = new HashMap<>();
-        for (Hct hct : allHcts) {
-            temperaturesByHct.put(hct, rawTemperature(hct));
-        }
-
-        precomputedTempsByHct = temperaturesByHct;
-        return precomputedTempsByHct;
-    }
-
-    /** Warmest color with same chroma and tone as input. */
-    private Hct getWarmest() {
-        return getHctsByTemp().get(getHctsByTemp().size() - 1);
-    }
-
-    /** Determines if an angle is between two other angles, rotating clockwise. */
-    private static boolean isBetween(double angle, double a, double b) {
-        if (a < b) {
-            return a <= angle && angle <= b;
-        }
-        return a <= angle || angle <= b;
-    }
-}
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/utils/ColorUtils.java b/packages/SystemUI/monet/src/com/android/systemui/monet/utils/ColorUtils.java
deleted file mode 100644
index 5a4ced0..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/utils/ColorUtils.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.utils;
-
-/**
- * Color science utilities.
- *
- * <p>Utility methods for color science constants and color space conversions that aren't HCT or
- * CAM16.
- */
-public class ColorUtils {
-    private ColorUtils() {
-    }
-
-    static final double[][] SRGB_TO_XYZ =
-            new double[][]{
-                    new double[]{0.41233895, 0.35762064, 0.18051042},
-                    new double[]{0.2126, 0.7152, 0.0722},
-                    new double[]{0.01932141, 0.11916382, 0.95034478},
-            };
-
-    static final double[][] XYZ_TO_SRGB =
-            new double[][]{
-                    new double[]{
-                            3.2413774792388685, -1.5376652402851851, -0.49885366846268053,
-                    },
-                    new double[]{
-                            -0.9691452513005321, 1.8758853451067872, 0.04156585616912061,
-                    },
-                    new double[]{
-                            0.05562093689691305, -0.20395524564742123, 1.0571799111220335,
-                    },
-            };
-
-    static final double[] WHITE_POINT_D65 = new double[]{95.047, 100.0, 108.883};
-
-    /** Converts a color from RGB components to ARGB format. */
-    public static int argbFromRgb(int red, int green, int blue) {
-        return (255 << 24) | ((red & 255) << 16) | ((green & 255) << 8) | (blue & 255);
-    }
-
-    /** Converts a color from linear RGB components to ARGB format. */
-    public static int argbFromLinrgb(double[] linrgb) {
-        int r = delinearized(linrgb[0]);
-        int g = delinearized(linrgb[1]);
-        int b = delinearized(linrgb[2]);
-        return argbFromRgb(r, g, b);
-    }
-
-    /** Returns the alpha component of a color in ARGB format. */
-    public static int alphaFromArgb(int argb) {
-        return (argb >> 24) & 255;
-    }
-
-    /** Returns the red component of a color in ARGB format. */
-    public static int redFromArgb(int argb) {
-        return (argb >> 16) & 255;
-    }
-
-    /** Returns the green component of a color in ARGB format. */
-    public static int greenFromArgb(int argb) {
-        return (argb >> 8) & 255;
-    }
-
-    /** Returns the blue component of a color in ARGB format. */
-    public static int blueFromArgb(int argb) {
-        return argb & 255;
-    }
-
-    /** Returns whether a color in ARGB format is opaque. */
-    public static boolean isOpaque(int argb) {
-        return alphaFromArgb(argb) >= 255;
-    }
-
-    /** Converts a color from ARGB to XYZ. */
-    public static int argbFromXyz(double x, double y, double z) {
-        double[][] matrix = XYZ_TO_SRGB;
-        double linearR = matrix[0][0] * x + matrix[0][1] * y + matrix[0][2] * z;
-        double linearG = matrix[1][0] * x + matrix[1][1] * y + matrix[1][2] * z;
-        double linearB = matrix[2][0] * x + matrix[2][1] * y + matrix[2][2] * z;
-        int r = delinearized(linearR);
-        int g = delinearized(linearG);
-        int b = delinearized(linearB);
-        return argbFromRgb(r, g, b);
-    }
-
-    /** Converts a color from XYZ to ARGB. */
-    public static double[] xyzFromArgb(int argb) {
-        double r = linearized(redFromArgb(argb));
-        double g = linearized(greenFromArgb(argb));
-        double b = linearized(blueFromArgb(argb));
-        return MathUtils.matrixMultiply(new double[]{r, g, b}, SRGB_TO_XYZ);
-    }
-
-    /** Converts a color represented in Lab color space into an ARGB integer. */
-    public static int argbFromLab(double l, double a, double b) {
-        double[] whitePoint = WHITE_POINT_D65;
-        double fy = (l + 16.0) / 116.0;
-        double fx = a / 500.0 + fy;
-        double fz = fy - b / 200.0;
-        double xNormalized = labInvf(fx);
-        double yNormalized = labInvf(fy);
-        double zNormalized = labInvf(fz);
-        double x = xNormalized * whitePoint[0];
-        double y = yNormalized * whitePoint[1];
-        double z = zNormalized * whitePoint[2];
-        return argbFromXyz(x, y, z);
-    }
-
-    /**
-     * Converts a color from ARGB representation to L*a*b* representation.
-     *
-     * @param argb the ARGB representation of a color
-     * @return a Lab object representing the color
-     */
-    public static double[] labFromArgb(int argb) {
-        double linearR = linearized(redFromArgb(argb));
-        double linearG = linearized(greenFromArgb(argb));
-        double linearB = linearized(blueFromArgb(argb));
-        double[][] matrix = SRGB_TO_XYZ;
-        double x = matrix[0][0] * linearR + matrix[0][1] * linearG + matrix[0][2] * linearB;
-        double y = matrix[1][0] * linearR + matrix[1][1] * linearG + matrix[1][2] * linearB;
-        double z = matrix[2][0] * linearR + matrix[2][1] * linearG + matrix[2][2] * linearB;
-        double[] whitePoint = WHITE_POINT_D65;
-        double xNormalized = x / whitePoint[0];
-        double yNormalized = y / whitePoint[1];
-        double zNormalized = z / whitePoint[2];
-        double fx = labF(xNormalized);
-        double fy = labF(yNormalized);
-        double fz = labF(zNormalized);
-        double l = 116.0 * fy - 16;
-        double a = 500.0 * (fx - fy);
-        double b = 200.0 * (fy - fz);
-        return new double[]{l, a, b};
-    }
-
-    /**
-     * Converts an L* value to an ARGB representation.
-     *
-     * @param lstar L* in L*a*b*
-     * @return ARGB representation of grayscale color with lightness matching L*
-     */
-    public static int argbFromLstar(double lstar) {
-        double y = yFromLstar(lstar);
-        int component = delinearized(y);
-        return argbFromRgb(component, component, component);
-    }
-
-    /**
-     * Computes the L* value of a color in ARGB representation.
-     *
-     * @param argb ARGB representation of a color
-     * @return L*, from L*a*b*, coordinate of the color
-     */
-    public static double lstarFromArgb(int argb) {
-        double y = xyzFromArgb(argb)[1];
-        return 116.0 * labF(y / 100.0) - 16.0;
-    }
-
-    /**
-     * Converts an L* value to a Y value.
-     *
-     * <p>L* in L*a*b* and Y in XYZ measure the same quantity, luminance.
-     *
-     * <p>L* measures perceptual luminance, a linear scale. Y in XYZ measures relative luminance, a
-     * logarithmic scale.
-     *
-     * @param lstar L* in L*a*b*
-     * @return Y in XYZ
-     */
-    public static double yFromLstar(double lstar) {
-        return 100.0 * labInvf((lstar + 16.0) / 116.0);
-    }
-
-    /**
-     * Converts a Y value to an L* value.
-     *
-     * <p>L* in L*a*b* and Y in XYZ measure the same quantity, luminance.
-     *
-     * <p>L* measures perceptual luminance, a linear scale. Y in XYZ measures relative luminance, a
-     * logarithmic scale.
-     *
-     * @param y Y in XYZ
-     * @return L* in L*a*b*
-     */
-    public static double lstarFromY(double y) {
-        return labF(y / 100.0) * 116.0 - 16.0;
-    }
-
-    /**
-     * Linearizes an RGB component.
-     *
-     * @param rgbComponent 0 <= rgb_component <= 255, represents R/G/B channel
-     * @return 0.0 <= output <= 100.0, color channel converted to linear RGB space
-     */
-    public static double linearized(int rgbComponent) {
-        double normalized = rgbComponent / 255.0;
-        if (normalized <= 0.040449936) {
-            return normalized / 12.92 * 100.0;
-        } else {
-            return Math.pow((normalized + 0.055) / 1.055, 2.4) * 100.0;
-        }
-    }
-
-    /**
-     * Delinearizes an RGB component.
-     *
-     * @param rgbComponent 0.0 <= rgb_component <= 100.0, represents linear R/G/B channel
-     * @return 0 <= output <= 255, color channel converted to regular RGB space
-     */
-    public static int delinearized(double rgbComponent) {
-        double normalized = rgbComponent / 100.0;
-        double delinearized = 0.0;
-        if (normalized <= 0.0031308) {
-            delinearized = normalized * 12.92;
-        } else {
-            delinearized = 1.055 * Math.pow(normalized, 1.0 / 2.4) - 0.055;
-        }
-        return MathUtils.clampInt(0, 255, (int) Math.round(delinearized * 255.0));
-    }
-
-    /**
-     * Returns the standard white point; white on a sunny day.
-     *
-     * @return The white point
-     */
-    public static double[] whitePointD65() {
-        return WHITE_POINT_D65;
-    }
-
-    static double labF(double t) {
-        double e = 216.0 / 24389.0;
-        double kappa = 24389.0 / 27.0;
-        if (t > e) {
-            return Math.pow(t, 1.0 / 3.0);
-        } else {
-            return (kappa * t + 16) / 116;
-        }
-    }
-
-    static double labInvf(double ft) {
-        double e = 216.0 / 24389.0;
-        double kappa = 24389.0 / 27.0;
-        double ft3 = ft * ft * ft;
-        if (ft3 > e) {
-            return ft3;
-        } else {
-            return (116 * ft - 16) / kappa;
-        }
-    }
-}
-
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/utils/MathUtils.java b/packages/SystemUI/monet/src/com/android/systemui/monet/utils/MathUtils.java
deleted file mode 100644
index a7622ef..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/utils/MathUtils.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.utils;
-
-/** Utility methods for mathematical operations. */
-public class MathUtils {
-    private MathUtils() {
-    }
-
-    /**
-     * The signum function.
-     *
-     * @return 1 if num > 0, -1 if num < 0, and 0 if num = 0
-     */
-    public static int signum(double num) {
-        if (num < 0) {
-            return -1;
-        } else if (num == 0) {
-            return 0;
-        } else {
-            return 1;
-        }
-    }
-
-    /**
-     * The linear interpolation function.
-     *
-     * @return start if amount = 0 and stop if amount = 1
-     */
-    public static double lerp(double start, double stop, double amount) {
-        return (1.0 - amount) * start + amount * stop;
-    }
-
-    /**
-     * Clamps an integer between two integers.
-     *
-     * @return input when min <= input <= max, and either min or max otherwise.
-     */
-    public static int clampInt(int min, int max, int input) {
-        if (input < min) {
-            return min;
-        } else if (input > max) {
-            return max;
-        }
-
-        return input;
-    }
-
-    /**
-     * Clamps an integer between two floating-point numbers.
-     *
-     * @return input when min <= input <= max, and either min or max otherwise.
-     */
-    public static double clampDouble(double min, double max, double input) {
-        if (input < min) {
-            return min;
-        } else if (input > max) {
-            return max;
-        }
-
-        return input;
-    }
-
-    /**
-     * Sanitizes a degree measure as an integer.
-     *
-     * @return a degree measure between 0 (inclusive) and 360 (exclusive).
-     */
-    public static int sanitizeDegreesInt(int degrees) {
-        degrees = degrees % 360;
-        if (degrees < 0) {
-            degrees = degrees + 360;
-        }
-        return degrees;
-    }
-
-    /**
-     * Sanitizes a degree measure as a floating-point number.
-     *
-     * @return a degree measure between 0.0 (inclusive) and 360.0 (exclusive).
-     */
-    public static double sanitizeDegreesDouble(double degrees) {
-        degrees = degrees % 360.0;
-        if (degrees < 0) {
-            degrees = degrees + 360.0;
-        }
-        return degrees;
-    }
-
-    /**
-     * Sign of direction change needed to travel from one angle to another.
-     *
-     * <p>For angles that are 180 degrees apart from each other, both directions have the same
-     * travel
-     * distance, so either direction is shortest. The value 1.0 is returned in this case.
-     *
-     * @param from The angle travel starts from, in degrees.
-     * @param to   The angle travel ends at, in degrees.
-     * @return -1 if decreasing from leads to the shortest travel distance, 1 if increasing from
-     * leads
-     * to the shortest travel distance.
-     */
-    public static double rotationDirection(double from, double to) {
-        double increasingDifference = sanitizeDegreesDouble(to - from);
-        return increasingDifference <= 180.0 ? 1.0 : -1.0;
-    }
-
-    /** Distance of two points on a circle, represented using degrees. */
-    public static double differenceDegrees(double a, double b) {
-        return 180.0 - Math.abs(Math.abs(a - b) - 180.0);
-    }
-
-    /** Multiplies a 1x3 row vector with a 3x3 matrix. */
-    public static double[] matrixMultiply(double[] row, double[][] matrix) {
-        double a = row[0] * matrix[0][0] + row[1] * matrix[0][1] + row[2] * matrix[0][2];
-        double b = row[0] * matrix[1][0] + row[1] * matrix[1][1] + row[2] * matrix[1][2];
-        double c = row[0] * matrix[2][0] + row[1] * matrix[2][1] + row[2] * matrix[2][2];
-        return new double[]{a, b, c};
-    }
-}
-
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/utils/StringUtils.java b/packages/SystemUI/monet/src/com/android/systemui/monet/utils/StringUtils.java
deleted file mode 100644
index cb2b694..0000000
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/utils/StringUtils.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.utils;
-
-/** Utility methods for string representations of colors. */
-final class StringUtils {
-    private StringUtils() {
-    }
-
-    /**
-     * Hex string representing color, ex. #ff0000 for red.
-     *
-     * @param argb ARGB representation of a color.
-     */
-    public static String hexFromArgb(int argb) {
-        int red = ColorUtils.redFromArgb(argb);
-        int blue = ColorUtils.blueFromArgb(argb);
-        int green = ColorUtils.greenFromArgb(argb);
-        return String.format("#%02x%02x%02x", red, green, blue);
-    }
-}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 2baeaf6..9cc87fd 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -133,6 +133,9 @@
             boolean willAnimateOnKeyguard,
             @Nullable String customMessage);
 
+    /** Whether we should animate an activity launch. */
+    boolean shouldAnimateLaunch(boolean isActivityIntent);
+
     interface Callback {
         void onActivityStarted(int resultCode);
     }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
deleted file mode 100644
index bef61b8..0000000
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
+++ /dev/null
@@ -1,145 +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.plugins;
-
-import android.graphics.Bitmap;
-import android.graphics.Paint.Style;
-import android.view.View;
-
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
-import java.util.TimeZone;
-
-/**
- * Plugin used to replace main clock in keyguard.
- * @deprecated Migrating to ClockProviderPlugin
- */
-@Deprecated
-@ProvidesInterface(action = ClockPlugin.ACTION, version = ClockPlugin.VERSION)
-public interface ClockPlugin extends Plugin {
-
-    String ACTION = "com.android.systemui.action.PLUGIN_CLOCK";
-    int VERSION = 5;
-
-    /**
-     * Get the name of the clock face.
-     *
-     * This name should not be translated.
-     */
-    String getName();
-
-    /**
-     * Get the title of the clock face to be shown in the picker app.
-     */
-    String getTitle();
-
-    /**
-     * Get thumbnail of clock face to be shown in the picker app.
-     */
-    Bitmap getThumbnail();
-
-    /**
-     * Get preview images of clock face to be shown in the picker app.
-     *
-     * Preview image should be realistic and show what the clock face will look like on AOD and lock
-     * screen.
-     *
-     * @param width width of the preview image, should be the same as device width in pixels.
-     * @param height height of the preview image, should be the same as device height in pixels.
-     */
-    Bitmap getPreview(int width, int height);
-
-    /**
-     * Get clock view.
-     * @return clock view from plugin.
-     */
-    View getView();
-
-    /**
-     * Get clock view for a large clock that appears behind NSSL.
-     */
-    default View getBigClockView() {
-        return null;
-    }
-
-    /**
-     * Returns the preferred Y position of the clock.
-     *
-     * @param totalHeight Height of the parent container.
-     * @return preferred Y position.
-     */
-    int getPreferredY(int totalHeight);
-
-    /**
-     * Allows the plugin to clean up resources when no longer needed.
-     *
-     * Called when the view previously created by {@link ClockPlugin#getView()} has been detached
-     * from the view hierarchy.
-     */
-    void onDestroyView();
-
-    /**
-     * Set clock paint style.
-     * @param style The new style to set in the paint.
-     */
-    void setStyle(Style style);
-
-    /**
-     * Set clock text color.
-     * @param color A color value.
-     */
-    void setTextColor(int color);
-
-    /**
-     * Sets the color palette for the clock face.
-     * @param supportsDarkText Whether dark text can be displayed.
-     * @param colors Colors that should be used on the clock face, ordered from darker to lighter.
-     */
-    default void setColorPalette(boolean supportsDarkText, int[] colors) {}
-
-    /**
-     * Set the amount (ratio) that the device has transitioned to doze.
-     * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
-     */
-    default void setDarkAmount(float darkAmount) {}
-
-    /**
-     * Notifies that time tick alarm from doze service fired.
-     *
-     * Implement this method instead of registering a broadcast listener for TIME_TICK.
-     */
-    default void onTimeTick() {}
-
-    /**
-     * Notifies that the time zone has changed.
-     *
-     * Implement this method instead of registering a broadcast listener for TIME_ZONE_CHANGED.
-     */
-    default void onTimeZoneChanged(TimeZone timeZone) {}
-
-    /**
-     * Notifies that the time format has changed.
-     *
-     * @param timeFormat "12" for 12-hour format, "24" for 24-hour format
-     */
-    default void onTimeFormatChanged(String timeFormat) {}
-
-    /**
-     * Indicates whether the keyguard status area (date) should be shown below
-     * the clock.
-     */
-    default boolean shouldShowStatusArea() {
-        return true;
-    }
-}
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 537b7a4..527f8007 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -18,7 +18,7 @@
 import android.graphics.drawable.Drawable
 import android.view.View
 import com.android.internal.annotations.Keep
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.plugins.annotations.ProvidesInterface
 import java.io.PrintWriter
 import java.util.Locale
@@ -95,7 +95,7 @@
     val animations: ClockAnimations
 
     /** Some clocks may log debug information */
-    var logBuffer: LogBuffer?
+    var messageBuffer: MessageBuffer?
 }
 
 /** Events that should call when various rendering parameters change */
@@ -177,6 +177,9 @@
      * targetRegion is relative to the parent view.
      */
     fun onTargetRegionChanged(targetRegion: Rect?)
+
+    /** Called to notify the clock about its display. */
+    fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean)
 }
 
 /** Tick rates for clocks */
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
index b33c544..a5e5aaa 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
@@ -26,6 +26,7 @@
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 
 import java.util.ArrayList;
+import java.util.Collection;
 
 /**
  * Dispatches events to {@link DarkReceiver}s about changes in darkness, tint area and dark
@@ -78,7 +79,7 @@
      * @return the tint to apply to view depending on the desired tint color and
      *         the screen tintArea in which to apply that tint
      */
-    static int getTint(ArrayList<Rect> tintAreas, View view, int color) {
+    static int getTint(Collection<Rect> tintAreas, View view, int color) {
         if (isInAreas(tintAreas, view)) {
             return color;
         } else {
@@ -90,7 +91,7 @@
      * @return true if more than half of the view area are in any of the given
      *         areas, false otherwise
      */
-    static boolean isInAreas(ArrayList<Rect> areas, View view) {
+    static boolean isInAreas(Collection<Rect> areas, View view) {
         if (areas.isEmpty()) {
             return true;
         }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
index cf7d2c5..3d9645a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@@ -58,9 +58,26 @@
     void userActivity();
     void getState();
 
-    boolean areCaptionsEnabled();
-    void setCaptionsEnabled(boolean isEnabled);
+    /**
+     * Get Captions enabled state
+     *
+     * @param checkForSwitchState set true when we'd like to switch captions enabled state after
+     *                            getting the latest captions state.
+     */
+    void getCaptionsEnabledState(boolean checkForSwitchState);
 
+    /**
+     * Set Captions enabled state
+     *
+     * @param enabled the captions enabled state we'd like to update.
+     */
+    void setCaptionsEnabledState(boolean enabled);
+
+    /**
+     * Get Captions component state
+     *
+     * @param fromTooltip if it's triggered from tooltip.
+     */
     void getCaptionsComponentState(boolean fromTooltip);
 
     @ProvidesInterface(version = StreamState.VERSION)
@@ -192,7 +209,22 @@
         void onScreenOff();
         void onShowSafetyWarning(int flags);
         void onAccessibilityModeChanged(Boolean showA11yStream);
+
+        /**
+         * Callback function for captions component state changed event
+         *
+         * @param isComponentEnabled the lateset captions component state.
+         * @param fromTooltip if it's triggered from tooltip.
+         */
         void onCaptionComponentStateChanged(Boolean isComponentEnabled, Boolean fromTooltip);
+
+        /**
+         * Callback function for captions enabled state changed event
+         *
+         * @param isEnabled the lateset captions enabled state.
+         * @param checkBeforeSwitch intend to switch captions enabled state after the callback.
+         */
+        void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkBeforeSwitch);
         // requires version 2
         void onShowCsdWarning(@AudioManager.CsdWarning int csdWarning, int durationMs);
     }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
index a4b1cee..affb76b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
@@ -2,14 +2,18 @@
 
 import android.os.Bundle
 import android.util.Log
+import android.view.View
 import androidx.annotation.VisibleForTesting
 
+typealias WeatherTouchAction = (View) -> Unit
+
 class WeatherData
 constructor(
     val description: String,
     val state: WeatherStateIcon,
     val useCelsius: Boolean,
     val temperature: Int,
+    val touchAction: WeatherTouchAction? = null,
 ) {
     companion object {
         const val DEBUG = true
@@ -20,7 +24,7 @@
         @VisibleForTesting const val TEMPERATURE_KEY = "temperature"
         private const val INVALID_WEATHER_ICON_STATE = -1
 
-        fun fromBundle(extras: Bundle): WeatherData? {
+        fun fromBundle(extras: Bundle, touchAction: WeatherTouchAction? = null): WeatherData? {
             val description = extras.getString(DESCRIPTION_KEY)
             val state =
                 WeatherStateIcon.fromInt(extras.getInt(STATE_KEY, INVALID_WEATHER_ICON_STATE))
@@ -41,7 +45,8 @@
                         description = description,
                         state = state,
                         useCelsius = extras.getBoolean(USE_CELSIUS_KEY),
-                        temperature = temperature
+                        temperature = temperature,
+                        touchAction = touchAction
                     )
                 if (DEBUG) {
                     Log.i(TAG, "Weather data parsed $result from $extras")
@@ -52,7 +57,7 @@
 
         private fun readIntFromBundle(extras: Bundle, key: String): Int? =
             try {
-                extras.getString(key).toInt()
+                extras.getString(key)?.toInt()
             } catch (e: Exception) {
                 null
             }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index c0b69c1..25f77ea 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -172,7 +172,6 @@
         public String expandedAccessibilityClassName;
         public SlashState slash;
         public boolean handlesLongClick = true;
-        public boolean showRippleEffect = true;
         @Nullable
         public Drawable sideViewCustomDrawable;
         public String spec;
@@ -217,7 +216,6 @@
                     || !Objects.equals(other.dualTarget, dualTarget)
                     || !Objects.equals(other.slash, slash)
                     || !Objects.equals(other.handlesLongClick, handlesLongClick)
-                    || !Objects.equals(other.showRippleEffect, showRippleEffect)
                     || !Objects.equals(other.sideViewCustomDrawable, sideViewCustomDrawable);
             other.spec = spec;
             other.icon = icon;
@@ -234,7 +232,6 @@
             other.isTransient = isTransient;
             other.slash = slash != null ? slash.copy() : null;
             other.handlesLongClick = handlesLongClick;
-            other.showRippleEffect = showRippleEffect;
             other.sideViewCustomDrawable = sideViewCustomDrawable;
             return changed;
         }
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index a8ed843..b534fcec 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -1,13 +1,7 @@
 -include proguard_common.flags
 
--keep class com.android.systemui.statusbar.tv.TvStatusBar
 -keep class com.android.systemui.SystemUIInitializerImpl {
     *;
 }
 
--keep class com.android.systemui.tv.TvSystemUIInitializer {
-    *;
-}
-
--keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.DaggerReferenceGlobalRootComponent** { !synthetic *; }
--keep,allowoptimization,allowaccessmodification class com.android.systemui.tv.DaggerTvGlobalRootComponent** { !synthetic *; }
\ No newline at end of file
+-keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.DaggerReferenceGlobalRootComponent** { !synthetic *; }
\ No newline at end of file
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index 1f47e72..9bd26ab1 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -65,15 +65,7 @@
 # The plugins, log & common subpackages act as shared libraries that might be referenced in
 # dynamically-loaded plugin APKs.
 -keep class com.android.systemui.plugins.** { *; }
--keep class com.android.systemui.log.ConstantStringsLoggerImpl { *; }
--keep class com.android.systemui.log.ConstantStringsLogger { *; }
--keep class com.android.systemui.log.LogBuffer { *; }
--keep class com.android.systemui.log.LogcatEchoTrackerDebug { *; }
--keep class com.android.systemui.log.LogcatEchoTracker { *; }
--keep class com.android.systemui.log.LogcatEchoTrackerProd { *; }
--keep class com.android.systemui.log.LogLevel { *; }
--keep class com.android.systemui.log.LogMessageImpl { *; }
--keep class com.android.systemui.log.LogMessage { *; }
+-keep class com.android.systemui.log.core.** { *; }
 -keep class com.android.systemui.fragments.FragmentService$FragmentCreator {
     *;
 }
diff --git a/packages/SystemUI/res-keyguard/color/shade_disabled.xml b/packages/SystemUI/res-keyguard/color/shade_disabled.xml
new file mode 100644
index 0000000..241f203
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/color/shade_disabled.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral1_500" android:lStar="4" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/controls_list_divider.xml b/packages/SystemUI/res-keyguard/drawable/controls_list_divider.xml
new file mode 100644
index 0000000..2fb7222
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/controls_list_divider.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/transparent" />
+    <size
+        android:width="@dimen/control_list_horizontal_spacing"
+        android:height="@dimen/control_list_vertical_spacing" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_lock_locked.xml b/packages/SystemUI/res-keyguard/drawable/ic_lock_locked.xml
new file mode 100644
index 0000000..e572985
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_lock_locked.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M17,8H18C19.1,8 20,8.9 20,10V20C20,21.1 19.1,22 18,22H6C4.9,22 4,21.1 4,20V10C4,8.9 4.9,8 6,8H7V6C7,3.24 9.24,1 12,1C14.76,1 17,3.24 17,6V8ZM12,3C10.34,3 9,4.34 9,6V8H15V6C15,4.34 13.66,3 12,3ZM6,20V10H18V20H6ZM14,15C14,16.1 13.1,17 12,17C10.9,17 10,16.1 10,15C10,13.9 10.9,13 12,13C13.1,13 14,13.9 14,15Z"
+      android:fillColor="#5F6368"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
new file mode 100644
index 0000000..cd7ab98
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <include layout="@layout/keyguard_pin_view_landscape" />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml
new file mode 100644
index 0000000..80cc8c0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <include layout="@layout/keyguard_pin_view_portrait" />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml
index a7ffe9c..c09607d 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml
@@ -26,7 +26,7 @@
         android:layout_height="wrap_content"
         android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
         android:layout_gravity="center"
-        android:textColor="?android:attr/textColorPrimary"
+        android:textColor="?attr/onShadeInactiveVariant"
         android:textSize="18sp"/>
     <ImageView
         android:id="@+id/new_dot"
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
index 6fe7d39..1c31f1d 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
@@ -33,7 +33,7 @@
         android:layout_marginEnd="12dp"
         android:contentDescription="@null"
         android:src="@drawable/ic_info_outline"
-        android:tint="?android:attr/textColorSecondary" />
+        android:tint="?attr/onSurfaceVariant" />
 
     <TextView
         android:id="@+id/text"
@@ -43,7 +43,7 @@
         android:maxLines="1"
         android:ellipsize="end"
         android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
-        android:textColor="?android:attr/textColorSecondary"/>
+        android:textColor="?attr/onSurfaceVariant"/>
 
     <ImageView
         android:id="@+id/new_dot"
@@ -62,5 +62,5 @@
         android:contentDescription="@null"
         android:src="@*android:drawable/ic_chevron_end"
         android:autoMirrored="true"
-        android:tint="?android:attr/textColorSecondary" />
+        android:tint="?attr/onSurfaceVariant" />
 </com.android.systemui.animation.view.LaunchableLinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_presentation.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_presentation.xml
new file mode 100644
index 0000000..593f507f
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_presentation.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/presentation"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <com.android.keyguard.KeyguardStatusView
+        android:id="@+id/clock"
+        android:layout_width="410dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:orientation="vertical">
+
+        <include
+            android:id="@+id/keyguard_clock_container"
+            layout="@layout/keyguard_clock_switch"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </com.android.keyguard.KeyguardStatusView>
+
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 3412a30..2fc1d2e 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -29,7 +29,7 @@
     >
     <include layout="@layout/keyguard_bouncer_message_area"/>
 
-    <com.android.systemui.keyguard.bouncer.ui.BouncerMessageView
+    <com.android.systemui.bouncer.ui.BouncerMessageView
         android:id="@+id/bouncer_message_view"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index 78a7c17..d9011c2 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -33,7 +33,7 @@
     android:clipToPadding="false">
     <include layout="@layout/keyguard_bouncer_message_area"/>
 
-    <com.android.systemui.keyguard.bouncer.ui.BouncerMessageView
+    <com.android.systemui.bouncer.ui.BouncerMessageView
         android:id="@+id/bouncer_message_view"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index 01c5443..29e14c5 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -16,202 +16,10 @@
 ** limitations under the License.
 */
 -->
-
-<com.android.keyguard.KeyguardPINView xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/keyguard_pin_view"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layout_gravity="center_horizontal|bottom"
-    android:clipChildren="false"
-    android:clipToPadding="false"
-    android:orientation="vertical"
-    androidprv:layout_maxWidth="@dimen/keyguard_security_width">
-<include layout="@layout/keyguard_bouncer_message_area"/>
+    android:layout_height="match_parent">
 
-<com.android.systemui.keyguard.bouncer.ui.BouncerMessageView
-    android:id="@+id/bouncer_message_view"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical" />
+    <include layout="@layout/keyguard_pin_view_portrait" />
 
-<androidx.constraintlayout.widget.ConstraintLayout
-        android:id="@+id/pin_container"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_marginBottom="8dp"
-        android:clipChildren="false"
-        android:clipToPadding="false"
-        android:layout_weight="1"
-        android:layoutDirection="ltr"
-        android:orientation="vertical">
-
-        <!-- Set this to be just above key1. It would be better to introduce a barrier above
-             key1/key2/key3, then place this View above that. Sadly, that doesn't work (the Barrier
-             drops to the bottom of the page, and key1/2/3 all shoot up to the top-left). In any
-             case, the Flow should ensure that key1/2/3 all have the same top, so this should be
-             fine. -->
-        <com.android.keyguard.AlphaOptimizedRelativeLayout
-            android:id="@+id/row0"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
-            androidprv:layout_constraintTop_toTopOf="parent"
-            androidprv:layout_constraintEnd_toEndOf="parent"
-            androidprv:layout_constraintStart_toStartOf="parent"
-            androidprv:layout_constraintBottom_toTopOf="@id/key1"
-            androidprv:layout_constraintVertical_bias="0.5">
-
-            <com.android.keyguard.PasswordTextView
-                android:id="@+id/pinEntry"
-                style="@style/Widget.TextView.Password"
-                android:layout_width="@dimen/keyguard_security_width"
-                android:layout_height="@dimen/keyguard_password_height"
-                android:layout_centerHorizontal="true"
-                android:layout_marginRight="72dp"
-                android:contentDescription="@string/keyguard_accessibility_pin_area"
-                androidprv:scaledTextSize="@integer/scaled_password_text_size" />
-        </com.android.keyguard.AlphaOptimizedRelativeLayout>
-
-        <!-- Guideline used to place the top row of keys relative to the screen height. This will be
-             updated in KeyguardPINView to reduce the height of the PIN pad. -->
-        <androidx.constraintlayout.widget.Guideline
-            android:id="@+id/pin_pad_top_guideline"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            androidprv:layout_constraintGuide_percent="0"
-            android:orientation="horizontal" />
-
-        <com.android.keyguard.KeyguardPinFlowView
-            android:id="@+id/flow1"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:orientation="horizontal"
-            android:clipChildren="false"
-            android:clipToPadding="false"
-
-            androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
-
-            androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
-
-            androidprv:flow_horizontalStyle="packed"
-            androidprv:flow_maxElementsWrap="3"
-
-            androidprv:flow_verticalBias="1.0"
-            androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
-            androidprv:flow_verticalStyle="packed"
-
-            androidprv:flow_wrapMode="aligned"
-            androidprv:layout_constraintBottom_toBottomOf="parent"
-            androidprv:layout_constraintEnd_toEndOf="parent"
-            androidprv:layout_constraintStart_toStartOf="parent"
-            androidprv:layout_constraintTop_toBottomOf="@id/pin_pad_top_guideline" />
-
-        <com.android.keyguard.NumPadKey
-            android:id="@+id/key1"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:accessibilityTraversalBefore="@id/key2"
-            androidprv:digit="1"
-            androidprv:textView="@+id/pinEntry" />
-
-        <com.android.keyguard.NumPadKey
-            android:id="@+id/key2"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:accessibilityTraversalBefore="@id/key3"
-            androidprv:digit="2"
-            androidprv:textView="@+id/pinEntry" />
-
-        <com.android.keyguard.NumPadKey
-            android:id="@+id/key3"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:accessibilityTraversalBefore="@id/key4"
-            androidprv:digit="3"
-            androidprv:textView="@+id/pinEntry" />
-
-        <com.android.keyguard.NumPadKey
-            android:id="@+id/key4"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:accessibilityTraversalBefore="@id/key5"
-            androidprv:digit="4"
-            androidprv:textView="@+id/pinEntry" />
-
-        <com.android.keyguard.NumPadKey
-            android:id="@+id/key5"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:accessibilityTraversalBefore="@id/key6"
-            androidprv:digit="5"
-            androidprv:textView="@+id/pinEntry" />
-
-        <com.android.keyguard.NumPadKey
-            android:id="@+id/key6"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:accessibilityTraversalBefore="@id/key7"
-            androidprv:digit="6"
-            androidprv:textView="@+id/pinEntry" />
-
-        <com.android.keyguard.NumPadKey
-            android:id="@+id/key7"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:accessibilityTraversalBefore="@id/key8"
-            androidprv:digit="7"
-            androidprv:textView="@+id/pinEntry" />
-
-
-        <com.android.keyguard.NumPadKey
-            android:id="@+id/key8"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:accessibilityTraversalBefore="@id/key9"
-            androidprv:digit="8"
-            androidprv:textView="@+id/pinEntry" />
-
-        <com.android.keyguard.NumPadKey
-            android:id="@+id/key9"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:accessibilityTraversalBefore="@id/delete_button"
-            androidprv:digit="9"
-            androidprv:textView="@+id/pinEntry" />
-
-        <com.android.keyguard.NumPadButton
-            android:id="@+id/delete_button"
-            style="@style/NumPadKey.Delete"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:accessibilityTraversalBefore="@id/key0"
-            android:contentDescription="@string/keyboardview_keycode_delete" />
-
-        <com.android.keyguard.NumPadKey
-            android:id="@+id/key0"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:accessibilityTraversalBefore="@id/key_enter"
-            androidprv:digit="0"
-            androidprv:textView="@+id/pinEntry" />
-
-        <com.android.keyguard.NumPadButton
-            android:id="@+id/key_enter"
-            style="@style/NumPadKey.Enter"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:contentDescription="@string/keyboardview_keycode_enter" />
-</androidx.constraintlayout.widget.ConstraintLayout>
-
-    <include layout="@layout/keyguard_eca"
-             android:id="@+id/keyguard_selector_fade_container"
-             android:layout_width="match_parent"
-             android:layout_height="wrap_content"
-             android:orientation="vertical"
-             android:layout_gravity="bottom|center_horizontal"
-             android:layout_marginTop="@dimen/keyguard_eca_top_margin"
-             android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
-             android:gravity="center_horizontal"/>
-
-</com.android.keyguard.KeyguardPINView>
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml
new file mode 100644
index 0000000..e00742d
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<com.android.keyguard.KeyguardPINView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/keyguard_pin_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_horizontal|bottom"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:orientation="horizontal">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="2"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layoutDirection="ltr"
+        android:orientation="vertical">
+
+        <include layout="@layout/keyguard_bouncer_message_area" />
+
+        <com.android.systemui.bouncer.ui.BouncerMessageView
+            android:id="@+id/bouncer_message_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            androidprv:layout_constraintBottom_toTopOf="@+id/row0"
+            androidprv:layout_constraintTop_toTopOf="parent"
+            androidprv:layout_constraintVertical_chainStyle="packed" />
+
+        <!-- Set this to be just above key1. It would be better to introduce a barrier above
+         key1/key2/key3, then place this View above that. Sadly, that doesn't work (the Barrier
+         drops to the bottom of the page, and key1/2/3 all shoot up to the top-left). In any
+         case, the Flow should ensure that key1/2/3 all have the same top, so this should be
+         fine. -->
+        <com.android.keyguard.AlphaOptimizedRelativeLayout
+            android:id="@+id/row0"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container"
+            androidprv:layout_constraintTop_toBottomOf="@+id/bouncer_message_view"
+            tools:layout_editor_absoluteX="-16dp">
+
+            <com.android.keyguard.PasswordTextView
+                android:id="@+id/pinEntry"
+                style="@style/Widget.TextView.Password"
+                android:layout_width="@dimen/keyguard_security_width"
+                android:layout_height="@dimen/keyguard_password_height"
+                android:layout_centerHorizontal="true"
+                android:layout_marginRight="72dp"
+                android:contentDescription="@string/keyguard_accessibility_pin_area"
+                androidprv:scaledTextSize="@integer/scaled_password_text_size" />
+        </com.android.keyguard.AlphaOptimizedRelativeLayout>
+
+        <include
+            android:id="@+id/keyguard_selector_fade_container"
+            layout="@layout/keyguard_eca"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom|center_horizontal"
+            android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+            android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+            android:gravity="center_horizontal"
+            android:orientation="vertical"
+            androidprv:layout_constraintBottom_toBottomOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/pin_container"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="3"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layoutDirection="ltr"
+        android:orientation="vertical">
+
+        <!-- Guideline used to place the top row of keys relative to the screen height. This will be
+        updated in KeyguardPINView to reduce the height of the PIN pad. -->
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/pin_pad_top_guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            androidprv:layout_constraintGuide_percent="0" />
+
+        <com.android.keyguard.KeyguardPinFlowView
+            android:id="@+id/flow1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:orientation="horizontal"
+
+            androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+
+            androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+
+            androidprv:flow_horizontalStyle="packed"
+            androidprv:flow_maxElementsWrap="3"
+
+            androidprv:flow_verticalBias="0.5"
+            androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:flow_verticalStyle="packed"
+
+            androidprv:flow_wrapMode="aligned"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toBottomOf="@id/pin_pad_top_guideline" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/delete_button"
+            style="@style/NumPadKey.Delete"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key0"
+            android:contentDescription="@string/keyboardview_keycode_delete" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/key_enter"
+            style="@style/NumPadKey.Enter"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:contentDescription="@string/keyboardview_keycode_enter" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key2"
+            androidprv:digit="1"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key2"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key3"
+            androidprv:digit="2"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key3"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key4"
+            androidprv:digit="3"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key4"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key5"
+            androidprv:digit="4"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key5"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key6"
+            androidprv:digit="5"
+            androidprv:textView="@+id/pinEntry" />
+
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key6"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key7"
+            androidprv:digit="6"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key7"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key8"
+            androidprv:digit="7"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key8"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key9"
+            androidprv:digit="8"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key9"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/delete_button"
+            androidprv:digit="9"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key0"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key_enter"
+            androidprv:digit="0"
+            androidprv:textView="@+id/pinEntry" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.android.keyguard.KeyguardPINView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_portrait.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_portrait.xml
new file mode 100644
index 0000000..f3cd9e4
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_portrait.xml
@@ -0,0 +1,218 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<com.android.keyguard.KeyguardPINView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/keyguard_pin_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_horizontal|bottom"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:orientation="vertical"
+    androidprv:layout_maxWidth="@dimen/keyguard_security_width">
+
+    <include layout="@layout/keyguard_bouncer_message_area"/>
+
+    <com.android.systemui.bouncer.ui.BouncerMessageView
+        android:id="@+id/bouncer_message_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/pin_container"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_marginBottom="8dp"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layout_weight="1"
+        android:layoutDirection="ltr"
+        android:orientation="vertical">
+
+        <!-- Set this to be just above key1. It would be better to introduce a barrier above
+             key1/key2/key3, then place this View above that. Sadly, that doesn't work (the Barrier
+             drops to the bottom of the page, and key1/2/3 all shoot up to the top-left). In any
+             case, the Flow should ensure that key1/2/3 all have the same top, so this should be
+             fine. -->
+        <com.android.keyguard.AlphaOptimizedRelativeLayout
+            android:id="@+id/row0"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:layout_constraintTop_toTopOf="parent"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintBottom_toTopOf="@id/key1"
+            androidprv:layout_constraintVertical_bias="0.5">
+
+            <com.android.keyguard.PasswordTextView
+                android:id="@+id/pinEntry"
+                style="@style/Widget.TextView.Password"
+                android:layout_width="@dimen/keyguard_security_width"
+                android:layout_height="@dimen/keyguard_password_height"
+                android:layout_centerHorizontal="true"
+                android:layout_marginRight="72dp"
+                android:contentDescription="@string/keyguard_accessibility_pin_area"
+                androidprv:scaledTextSize="@integer/scaled_password_text_size" />
+        </com.android.keyguard.AlphaOptimizedRelativeLayout>
+
+        <!-- Guideline used to place the top row of keys relative to the screen height. This will be
+             updated in KeyguardPINView to reduce the height of the PIN pad. -->
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/pin_pad_top_guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            androidprv:layout_constraintGuide_percent="0"
+            android:orientation="horizontal" />
+
+        <com.android.keyguard.KeyguardPinFlowView
+            android:id="@+id/flow1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:orientation="horizontal"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+
+            androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+
+            androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+
+            androidprv:flow_horizontalStyle="packed"
+            androidprv:flow_maxElementsWrap="3"
+
+            androidprv:flow_verticalBias="1.0"
+            androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:flow_verticalStyle="packed"
+
+            androidprv:flow_wrapMode="aligned"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toBottomOf="@id/pin_pad_top_guideline" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key2"
+            androidprv:digit="1"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key2"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key3"
+            androidprv:digit="2"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key3"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key4"
+            androidprv:digit="3"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key4"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key5"
+            androidprv:digit="4"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key5"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key6"
+            androidprv:digit="5"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key6"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key7"
+            androidprv:digit="6"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key7"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key8"
+            androidprv:digit="7"
+            androidprv:textView="@+id/pinEntry" />
+
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key8"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key9"
+            androidprv:digit="8"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key9"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/delete_button"
+            androidprv:digit="9"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/delete_button"
+            style="@style/NumPadKey.Delete"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key0"
+            android:contentDescription="@string/keyboardview_keycode_delete" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key0"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key_enter"
+            androidprv:digit="0"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/key_enter"
+            style="@style/NumPadKey.Enter"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:contentDescription="@string/keyboardview_keycode_enter" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <include layout="@layout/keyguard_eca"
+        android:id="@+id/keyguard_selector_fade_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:layout_gravity="bottom|center_horizontal"
+        android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+        android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+        android:gravity="center_horizontal"/>
+
+</com.android.keyguard.KeyguardPINView>
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
index c85449d0..a1e2dc3 100644
--- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -30,12 +30,13 @@
 
         <FrameLayout
             android:id="@+id/inout_container"
-            android:layout_height="17dp"
+            android:layout_height="@dimen/status_bar_mobile_inout_container_size"
             android:layout_width="wrap_content"
             android:layout_gravity="center_vertical">
             <ImageView
                 android:id="@+id/mobile_in"
-                android:layout_height="wrap_content"
+                android:layout_height="@dimen/status_bar_mobile_signal_size"
+                android:adjustViewBounds="true"
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_down"
                 android:visibility="gone"
@@ -43,7 +44,8 @@
                 />
             <ImageView
                 android:id="@+id/mobile_out"
-                android:layout_height="wrap_content"
+                android:layout_height="@dimen/status_bar_mobile_signal_size"
+                android:adjustViewBounds="true"
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_up"
                 android:paddingEnd="2dp"
@@ -52,11 +54,12 @@
         </FrameLayout>
         <ImageView
             android:id="@+id/mobile_type"
-            android:layout_height="wrap_content"
+            android:layout_height="@dimen/status_bar_mobile_type_size"
             android:layout_width="wrap_content"
             android:layout_gravity="center_vertical"
-            android:paddingStart="2.5dp"
-            android:paddingEnd="1dp"
+            android:adjustViewBounds="true"
+            android:paddingStart="2.5sp"
+            android:paddingEnd="1sp"
             android:visibility="gone" />
         <Space
             android:id="@+id/mobile_roaming_space"
@@ -70,14 +73,16 @@
             android:layout_gravity="center_vertical">
             <com.android.systemui.statusbar.AnimatedImageView
                 android:id="@+id/mobile_signal"
-                android:layout_height="wrap_content"
+                android:layout_height="@dimen/status_bar_mobile_signal_size"
                 android:layout_width="wrap_content"
+                android:adjustViewBounds="true"
                 systemui:hasOverlappingRendering="false"
                 />
             <ImageView
                 android:id="@+id/mobile_roaming"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
+                android:layout_height="@dimen/status_bar_mobile_roam_size"
+                android:adjustViewBounds="true"
                 android:layout_gravity="top|start"
                 android:src="@drawable/stat_sys_roaming"
                 android:contentDescription="@string/data_connection_roaming"
diff --git a/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml
new file mode 100644
index 0000000..360ef26
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<com.android.systemui.biometrics.UdfpsKeyguardView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/udfps_animation_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <!-- Add fingerprint views here. See udfps_keyguard_view_internal.xml. -->
+
+</com.android.systemui.biometrics.UdfpsKeyguardView>
diff --git a/packages/SystemUI/res-keyguard/values-af/strings.xml b/packages/SystemUI/res-keyguard/values-af/strings.xml
index 4bd19465..2eb1bb5 100644
--- a/packages/SystemUI/res-keyguard/values-af/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-af/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN word vereis ná vassluit"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Wagwoord word vereis ná vassluit"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Patroon word vereis ná vassluit"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Opdatering word tydens onaktiewe ure geïnstalleer"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Die opdatering sal installeer wanneer die toestel nie gebruik word nie"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Meer sekuriteit vereis. PIN ruk lank nie gebruik nie."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Meer sekuriteit vereis. Wagwoord ruk lank nie gebruik nie."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Meer sekuriteit vereis. Patroon ruk lank nie gebruik nie."</string>
diff --git a/packages/SystemUI/res-keyguard/values-am/strings.xml b/packages/SystemUI/res-keyguard/values-am/strings.xml
index daa25e7..5fd946b 100644
--- a/packages/SystemUI/res-keyguard/values-am/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-am/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"ከመቆለፊያ በኋላ ፒን ያስፈልጋል"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"ከመቆለፊያ በኋላ የይለፍ ቃል ያስፈልጋል"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"ከመቆለፊያ በኋላ ስርዓተ ጥለት ያስፈልጋል"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"ዝማኔ በቦዘኑ ሰዓታት ወቅት ይጭናል"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"መሣሪያው ጥቅም ላይ በማይውልበት ጊዜ ዝማኔ ይጫናል"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"ተጨማሪ የደህንነት ጥበቃ ያስፈልጋል። ፒን ለተወሰነ ጊዜ ጥቅም ላይ አልዋለም።"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"ተጨማሪ የደህንነት ጥበቃ ያስፈልጋል። የይለፍ ቃል ለተወሰነ ጊዜ ጥቅም ላይ አልዋለም።"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"ተጨማሪ የደህንነት ጥበቃ ያስፈልጋል። ስርዓተ ጥለት ለተወሰነ ጊዜ ጥቅም ላይ አልዋለም።"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index 1215c34..b6479f4 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"يجب إدخال رقم التعريف الشخصي بعد إلغاء التأمين."</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"يجب إدخال كلمة المرور بعد إلغاء التأمين."</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"يجب رسم النقش بعد إلغاء التأمين."</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"سيتم تثبيت التحديث خلال ساعات عدم النشاط."</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"سيتم تثبيت التحديث عندما لا يكون الجهاز قيد الاستخدام."</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"‏يجب تعزيز الأمان. لم يُستخدَم رقم PIN لبعض الوقت."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"يجب تعزيز الأمان. لم تستخدَم كلمة المرور لبعض الوقت."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"يجب تعزيز الأمان. لم يُستخدَم النقش لبعض الوقت."</string>
diff --git a/packages/SystemUI/res-keyguard/values-as/strings.xml b/packages/SystemUI/res-keyguard/values-as/strings.xml
index 7e43a29..a41a704 100644
--- a/packages/SystemUI/res-keyguard/values-as/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-as/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"লকডাউনৰ পাছত পিন দিয়াৰ আৱশ্যক"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"লকডাউনৰ পাছত পাছৱৰ্ড দিয়াৰ আৱশ্যক"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"লকডাউনৰ পাছত আৰ্হি দিয়াৰ আৱশ্যক"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"নিষ্ক্ৰিয় হৈ থকাৰ সময়ত আপডে’ট ইনষ্টল কৰা হ’ব"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"ডিভাইচটো ব্যৱহাৰ হৈ নাথাকোঁতে আপডে’ট ইনষ্টল হ’ব"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"অতিৰিক্ত সুৰক্ষাৰ আৱশ্যক। কিছু সময় ধৰি আৰ্হি ব্যৱহাৰ কৰা হোৱা নাই।"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"অতিৰিক্ত সুৰক্ষাৰ আৱশ্যক। কিছু সময় ধৰি পাছৱৰ্ড ব্যৱহাৰ কৰা হোৱা নাই।"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"অতিৰিক্ত সুৰক্ষাৰ আৱশ্যক। কিছু সময় ধৰি আৰ্হি ব্যৱহাৰ কৰা হোৱা নাই।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-az/strings.xml b/packages/SystemUI/res-keyguard/values-az/strings.xml
index 37d9f0e..f66fb3c 100644
--- a/packages/SystemUI/res-keyguard/values-az/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-az/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Kilidləmədən sonra PIN tələb edilir"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Kilidləmədən sonra parol tələb edilir"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Kilidləmədən sonra model tələb edilir"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Güncəllənmə qeyri-işlək saatlarda quraşdırılacaq"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Cihaz istifadə edilmədikdə güncəllənmə quraşdırılacaq"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Qoruma lazımdır. PIN bir müddət işlənməyib."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Qoruma lazımdır. Parol bir müddət işlənməyib."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Qoruma lazımdır. Model bir müddət işlənməyib."</string>
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 d8de1ef..b0a6471 100644
--- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN je obavezan posle zaključavanja"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Lozinka je obavezna posle zaključavanja"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Šablon je obavezan posle zaključavanja"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Ažuriranje se instalira tokom neaktivnosti"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Ažuriranje će se instalirati kada se uređaj ne koristi"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Potrebna je dodatna zaštita. PIN dugo nije korišćen."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Potrebna je dodatna zaštita. Lozinka dugo nije korišćena."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Potrebna je dodatna zaštita. Šablon dugo nije korišćen."</string>
diff --git a/packages/SystemUI/res-keyguard/values-be/strings.xml b/packages/SystemUI/res-keyguard/values-be/strings.xml
index f66ccc7..11cc77d 100644
--- a/packages/SystemUI/res-keyguard/values-be/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-be/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Пасля блакіроўкі неабходна ўвесці PIN-код"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Пасля блакіроўкі неабходна ўвесці пароль"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Пасля блакіроўкі неабходна ўвесці ўзор разблакіроўкі"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Абнаўленне ўсталюецца, калі прылада будзе неактыўная"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Абнаўленне будзе ўсталявана, калі прылада не выкарыстоўваецца"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Патрабуецца дадатковая праверка. Даўно не выкарыстоўваўся PIN-код."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Патрабуецца дадатковая праверка. Даўно не выкарыстоўваўся пароль."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Патрабуецца дадатковая праверка. Даўно не выкарыстоўваўся ўзор разблакіроўкі."</string>
diff --git a/packages/SystemUI/res-keyguard/values-bg/strings.xml b/packages/SystemUI/res-keyguard/values-bg/strings.xml
index 646f7f9..c554a27 100644
--- a/packages/SystemUI/res-keyguard/values-bg/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bg/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"След заключването се изисква ПИН код"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"След заключването се изисква парола"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"След заключването се изисква фигура"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Актуализацията ще се инсталира при неактивност"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Актуализацията ще се инсталира, когато устройството не се използва"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Изисква се допъл. защита. ПИН кодът не е ползван скоро."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Изисква се допъл. защита. Паролата не е ползвана скоро."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Изисква се допъл. защита. Фигурата не е ползвана скоро."</string>
diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml
index 23eb418..69f533c 100644
--- a/packages/SystemUI/res-keyguard/values-bn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"লকডাউন হওয়ার পরে পিন দিতে হবে"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"লকডাউন হওয়ার পরে পাসওয়ার্ড দিতে হবে"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"লকডাউন হওয়ার পরে প্যাটার্ন দিতে হবে"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"ডিভাইস অ্যাক্টিভ না থাকাকালীন আপডেট ইনস্টল হবে"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"ডিভাইস ব্যবহার না করাকালীন আপডেট ইনস্টল করা হবে"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"অতিরিক্ত সুরক্ষা দরকার। পিন কিছুক্ষণ ব্যবহার করা হয়নি।"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"অতিরিক্ত সুরক্ষা দরকার। পাসওয়ার্ড কিছুক্ষণ ব্যবহার করা হয়নি।"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"অতিরিক্ত সুরক্ষা দরকার। প্যাটার্ন কিছুক্ষণ ব্যবহার করা হয়নি।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml
index 3ed0958..4c519c8 100644
--- a/packages/SystemUI/res-keyguard/values-bs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN je potreban nakon zaključavanja"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Lozinka je potrebna nakon zaključavanja"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Uzorak je potreban nakon zaključavanja"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Ažuriranje će se instalirati u periodu neaktivnosti"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Ažuriranje će se instalirati dok se uređaj ne koristi"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Potrebna je dodatna zaštita. PIN dugo nije unošen."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Potrebna je dodatna zaštita. Lozinka dugo nije unošena."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Potrebna je dodatna zaštita. Uzorak dugo nije unošen."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml
index bd1e4a1..3bd6508 100644
--- a/packages/SystemUI/res-keyguard/values-ca/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Cal el PIN després del bloqueig de seguretat"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Cal la contrasenya després del bloqueig de seguretat"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Cal el patró després del bloqueig de seguretat"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"S\'actualitzarà durant les hores d\'inactivitat"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"L\'actualització s\'instal·larà quan el dispositiu no estigui en ús"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Cal més seguretat. Fa temps que no utilitzes el PIN."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Cal més seguretat. Contrasenya no utilitzada fa temps."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Cal més seguretat. Fa temps que no utilitzes el patró."</string>
diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml
index 88890bb..e075d85 100644
--- a/packages/SystemUI/res-keyguard/values-cs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Po uzamčení je třeba zadat PIN"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Po uzamčení je třeba zadat heslo"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Po uzamčení je třeba zadat gesto"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Aktualizace se nainstaluje v období neaktivity"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Aktualizace se nainstaluje, až zařízení nebudete používat"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Je potřeba další krok. PIN dlouho nepoužit."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Je potřeba další krok. Heslo dlouho nepoužito."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Je potřeba další krok. Gesto dlouho nepoužito."</string>
diff --git a/packages/SystemUI/res-keyguard/values-da/strings.xml b/packages/SystemUI/res-keyguard/values-da/strings.xml
index e9b3b90c..027166f 100644
--- a/packages/SystemUI/res-keyguard/values-da/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-da/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Pinkode er påkrævet efter brug af låsning"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Adgangskode er påkrævet efter brug af låsning"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Mønster er påkrævet efter brug af låsning"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Opdateringen installeres under inaktivitet"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Opdateringen installeres, når enheden ikke er i brug"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Mere sikkerhed er påkrævet. Pinkoden er ikke blevet brugt i et stykke tid."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Mere sikkerhed er påkrævet. Adgangskoden er ikke blevet brugt i et stykke tid."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Mere sikkerhed er påkrævet. Mønsteret er ikke blevet brugt i et stykke tid."</string>
diff --git a/packages/SystemUI/res-keyguard/values-de/strings.xml b/packages/SystemUI/res-keyguard/values-de/strings.xml
index 223e74c7..117f7a9 100644
--- a/packages/SystemUI/res-keyguard/values-de/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-de/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Nach einer Sperre muss die PIN eingegeben werden"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Nach einer Sperre muss das Passwort eingegeben werden"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Nach einer Sperre muss das Muster gezeichnet werden"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Update wird installiert, wenn das Gerät inaktiv ist"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Update wird installiert, wenn das Gerät nicht verwendet wird"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Zusätzliche Sicherheitsmaßnahme erforderlich. Die PIN wurde länger nicht genutzt."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Zusätzliche Sicherheitsmaßnahme erforderlich. Passwort wurde länger nicht genutzt."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Zusätzliche Sicherheitsmaßnahme erforderlich. Muster wurde länger nicht genutzt."</string>
diff --git a/packages/SystemUI/res-keyguard/values-el/strings.xml b/packages/SystemUI/res-keyguard/values-el/strings.xml
index 6ee323a..cd7637c 100644
--- a/packages/SystemUI/res-keyguard/values-el/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-el/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Απαιτείται PIN μετά από κλείδωμα"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Απαιτείται κωδικός πρόσβασης μετά από κλείδωμα"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Απαιτείται μοτίβο μετά από κλείδωμα"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Η ενημέρωση θα εγκατασταθεί κατά τις ανενεργές ώρες"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Η ενημέρωση θα εγκατασταθεί όταν δεν χρησιμοποιείται η συσκευή"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Απαιτ. πρόσθ. ασφάλ. Το PIN έχει καιρό να χρησιμοπ."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Απαιτ. πρόσθ. ασφάλ. Ο κωδ. πρ. έχει καιρό να χρησ."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Απαιτ. πρόσθ. ασφάλ. Το μοτίβο έχει καιρό να χρησιμ."</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
index 5130670..0ace8a7 100644
--- a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN is required after lockdown"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Password is required after lockdown"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Pattern is required after lockdown"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Update will be installed during inactive hours"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Update will be installed when the device is not in use"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Added security required. PIN not used for a while."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Added security required. Password not used for a while."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Added security required. Pattern not used for a while."</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
index 9457489..480bcbb 100644
--- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN is required after lockdown"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Password is required after lockdown"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Pattern is required after lockdown"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Update will install during inactive hours"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Update will install when device not in use"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Added security required. PIN not used for a while."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Added security required. Password not used for a while."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Added security required. Pattern not used for a while."</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
index 5130670..0ace8a7 100644
--- a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN is required after lockdown"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Password is required after lockdown"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Pattern is required after lockdown"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Update will be installed during inactive hours"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Update will be installed when the device is not in use"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Added security required. PIN not used for a while."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Added security required. Password not used for a while."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Added security required. Pattern not used for a while."</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
index 5130670..0ace8a7 100644
--- a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN is required after lockdown"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Password is required after lockdown"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Pattern is required after lockdown"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Update will be installed during inactive hours"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Update will be installed when the device is not in use"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Added security required. PIN not used for a while."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Added security required. Password not used for a while."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Added security required. Pattern not used for a while."</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
index b2520d79..b8e89f4 100644
--- a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‎‏‎PIN is required after lockdown‎‏‎‎‏‎"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‎‏‏‎‏‏‏‎‎‎‎‏‏‎Password is required after lockdown‎‏‎‎‏‎"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‏‏‏‏‏‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‏‎‎‏‏‎‎‏‎‏‏‏‎‎‏‎‏‏‏‏‎‎‎‎‏‏‎‏‎‎‏‎‎Pattern is required after lockdown‎‏‎‎‏‎"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‎‎‏‏‏‏‎‎‏‎‏‎‏‎‎‎‎‏‏‎‎‏‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‏‎‎Update will install during inactive hours‎‏‎‎‏‎"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‏‎‎‏‎‏‏‎‏‎‎‎‎‎‎‏‎‏‎‎‏‎‏‎‏‏‎‏‏‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‏‎‎‎‎‏‎‎‎Update will install when device not in use‎‏‎‎‏‎"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‎‏‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‏‎‏‎‏‏‎‏‎Added security required. PIN not used for a while.‎‏‎‎‏‎"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‎‎‎‎‏‏‎‏‏‎‎‏‎‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‏‏‏‎‎‏‏‏‎Added security required. Password not used for a while.‎‏‎‎‏‎"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‎‎‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‎Added security required. Pattern not used for a while.‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
index 8c5b7bc..debbeb1 100644
--- a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Se requiere el PIN después del bloqueo"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Se requiere la contraseña después del bloqueo"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Se requiere el patrón después del bloqueo"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"La actualización se instala en horas de inactividad"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Se instalará la actualización cuando el dispositivo esté en desuso"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Reforzar seguridad. PIN sin uso mucho tiempo."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Reforzar seguridad. Contraseña sin uso mucho tiempo."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Reforzar seguridad. Patrón sin uso mucho tiempo."</string>
diff --git a/packages/SystemUI/res-keyguard/values-es/strings.xml b/packages/SystemUI/res-keyguard/values-es/strings.xml
index 811e33a..0ea98a8 100644
--- a/packages/SystemUI/res-keyguard/values-es/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Se necesita el PIN después del bloqueo"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Se necesita la contraseña después del bloqueo"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Se necesita el patrón después del bloqueo"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"La actualización se instalará en horas de inactividad"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"La actualización se instalará cuando el dispositivo no esté en uso"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Se debe reforzar la seguridad. PIN no usado en mucho tiempo."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Se debe reforzar la seguridad. Contraseña no usada en mucho tiempo."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Se debe reforzar la seguridad. Patrón no usado en mucho tiempo."</string>
diff --git a/packages/SystemUI/res-keyguard/values-et/strings.xml b/packages/SystemUI/res-keyguard/values-et/strings.xml
index 4846412..722a022 100644
--- a/packages/SystemUI/res-keyguard/values-et/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-et/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Pärast lukustamist on PIN-kood nõutav"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Pärast lukustamist on parool nõutav"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Pärast lukustamist on muster nõutav"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Värskendus installitakse mitteaktiivsete tundide ajal"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Värskendus installitakse, kui seadet ei kasutata"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Turvalisuse suurendamine on nõutav. PIN-koodi ei ole mõnda aega kasutatud."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Turvalisuse suurendamine on nõutav. Parooli ei ole mõnda aega kasutatud."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Turvalisuse suurendamine on nõutav. Mustrit ei ole mõnda aega kasutatud."</string>
diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml
index 351bada..d329369 100644
--- a/packages/SystemUI/res-keyguard/values-eu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PINa behar da blokeoa desgaitu ostean"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Pasahitza behar da blokeoa desgaitu ostean"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Eredua behar da blokeoa desgaitu ostean"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Inaktibo egon ohi den tarte batean instalatuko da eguneratzea"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Gailua erabiltzen ari ez zarenean instalatuko da eguneratzea"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Segurtasuna areagotu behar da. PINa ez da erabili aldi batez."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Segurtasuna areagotu behar da. Pasahitza ez da erabili aldi batez."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Segurtasuna areagotu behar da. Eredua ez da erabili aldi batez."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml
index 8063873..4815815 100644
--- a/packages/SystemUI/res-keyguard/values-fa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"بعداز قفل همه باید از پین استفاده کرد"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"بعداز قفل همه باید از گذرواژه استفاده کرد"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"بعداز قفل همه باید از الگو استفاده کرد"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"به‌روزرسانی درطول ساعات غیرفعال نصب خواهد شد"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"به‌روزرسانی وقتی دستگاه درحال استفاده نیست نصب خواهد شد"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"امنیت بیشتر لازم است. مدتی از پین استفاده نشده است."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"امنیت بیشتر لازم است. گذرواژه مدتی استفاده نشده است."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"امنیت بیشتر لازم است. مدتی از الگو استفاده نشده است."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fi/strings.xml b/packages/SystemUI/res-keyguard/values-fi/strings.xml
index f96f61f..02d41d8 100644
--- a/packages/SystemUI/res-keyguard/values-fi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fi/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN-koodi tarvitaan lukitustilan jälkeen"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Salasana tarvitaan lukitustilan jälkeen"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Kuvio tarvitaan lukitustilan jälkeen"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Päivitys asennetaan käyttöajan ulkopuolella"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Päivitys asennetaan, kun laite ei ole käytössä"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Lisäsuojausta tarvitaan. PIN-koodia ei ole käytetty."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Lisäsuojausta tarvitaan. Salasanaa ei ole käytetty."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Lisäsuojausta tarvitaan. Kuviota ei ole käytetty."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
index e880c1e..fa1a191 100644
--- a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Le NIP est requis après le verrouillage"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Le mot de passe est requis après le verrouillage"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Le schéma est requis après le verrouillage"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"La MAJ sera installée durant les heures d\'inactivité"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"La mise à jour sera installée lorsque l\'appareil n\'est pas utilisé"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Plus de sécurité requise. NIP non utilisé pour un temps."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Plus de sécurité requise. MDP non utilisé pour un temps."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Plus de sécurité requise. Schéma non utilisé pour un temps."</string>
diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml
index f2f037f..d687a1d 100644
--- a/packages/SystemUI/res-keyguard/values-fr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Code requis après un blocage"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Mot de passe requis après un blocage"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Schéma requis après un blocage"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"La mise à jour sera installée pendant les heures d\'inactivité"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"La mise à jour aura lieu lorsque votre appareil n\'est pas utilisé"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Code inutilisé depuis un moment. Renforcez la sécurité."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Mot de passe inutilisé depuis un moment. Renforcez la sécurité."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Schéma inutilisé depuis un moment. Renforcez la sécurité."</string>
diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml
index 2cc2ec2..3faa7ca 100644
--- a/packages/SystemUI/res-keyguard/values-gl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Requírese o PIN tras o bloqueo"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Requírese o contrasinal tras o bloqueo"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Requírese o padrón tras o bloqueo"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"A actualización instalarase durante a inactividade"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"A actualización instalarase cando o dispositivo non se estea usando"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Requírese seguranza adicional. O PIN non se usou desde hai tempo."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Requírese seguranza adicional. O contrasinal non se usou desde hai tempo."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Requírese seguranza adicional. O padrón non se usou desde hai tempo."</string>
diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml
index c574e4f..99c9883 100644
--- a/packages/SystemUI/res-keyguard/values-gu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"પિન પછી પાસવર્ડ આવશ્યક છે"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"લૉકડાઉન પછી પાસવર્ડ આવશ્યક છે"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"પૅટર્ન પછી પાસવર્ડ આવશ્યક છે"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"નિષ્ક્રિયતાના સમય દરમિયાન અપડેટ ઇન્સ્ટૉલ થશે"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"જ્યારે ડિવાઇસ ઉપયોગમાં ન હોય ત્યારે અપડેટ ઇન્સ્ટૉલ કરવામાં આવશે"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"વધારાની સુરક્ષા આવશ્યક છે. થોડા સમય માટે પિનનો ઉપયોગ થયો નથી."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"વધારાની સુરક્ષા આવશ્યક છે. થોડા સમય માટે પાસવર્ડનો ઉપયોગ થયો નથી."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"વધારાની સુરક્ષા આવશ્યક છે. થોડા સમય માટે પૅટર્નનો ઉપયોગ થયો નથી."</string>
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index 5d43c32..9d32f04 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"लॉकडाउन के बाद, पिन डालना ज़रूरी है"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"लॉकडाउन के बाद, पासवर्ड डालना ज़रूरी है"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"लॉकडाउन के बाद, पैटर्न ड्रॉ करना ज़रूरी है"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"इनऐक्टिव रहने के दौरान, अपडेट इंस्टॉल किया जाएगा"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"अपडेट तब इंस्टॉल किया जाएगा, जब डिवाइस का इस्तेमाल न किया जा रहा हो"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"अतिरिक्त सुरक्षा ज़रूरी है. कुछ समय से पिन नहीं डाला गया."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"अतिरिक्त सुरक्षा ज़रूरी है. कुछ समय से पासवर्ड नहीं डाला गया."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"अतिरिक्त सुरक्षा ज़रूरी है. कुछ समय से पैटर्न ड्रॉ नहीं किया गया."</string>
diff --git a/packages/SystemUI/res-keyguard/values-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml
index 6d65df6..b4224bf 100644
--- a/packages/SystemUI/res-keyguard/values-hr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN je obavezan nakon zaključavanja"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Zaporka je obavezna nakon zaključavanja"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Uzorak je obavezan nakon zaključavanja"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Ažuriranje će se instalirati tijekom neaktivnosti"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Ažuriranje će se instalirati kad uređaj nije u upotrebi"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Potrebna je dodatna sigurnost. PIN nije upotrijebljen duže vrijeme."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Potrebna je dodatna sigurnost. Zaporka nije upotrijebljena duže vrijeme."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Potrebna je dodatna sigurnost. Uzorak nije upotrijebljen duže vrijeme."</string>
diff --git a/packages/SystemUI/res-keyguard/values-hu/strings.xml b/packages/SystemUI/res-keyguard/values-hu/strings.xml
index f1183af..bc712c7 100644
--- a/packages/SystemUI/res-keyguard/values-hu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hu/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN-kód megadása szükséges a zárolás után"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Jelszó megadása szükséges a zárolás után"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Minta megadása szükséges a zárolás után"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"A frissítést inaktív időben telepíti a rendszer"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"A frissítést telepíti a rendszer, amikor nincs használatban az eszköz"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Fokozott biztonság szükséges. Régóta nem használta a PIN-t."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Fokozott biztonság szükséges. Régóta nem használta jelszavát."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Fokozott biztonság szükséges. Régóta nem használta a mintát."</string>
diff --git a/packages/SystemUI/res-keyguard/values-hy/strings.xml b/packages/SystemUI/res-keyguard/values-hy/strings.xml
index bbff378..4d7bbbe 100644
--- a/packages/SystemUI/res-keyguard/values-hy/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hy/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Արգելափակումից հետո հարկավոր է մուտքագրել PIN կոդը"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Արգելափակումից հետո հարկավոր է մուտքագրել գաղտնաբառը"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Արգելափակումից հետո հարկավոր է գծել նախշը"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Թարմացումը կտեղադրվի ոչ ակտիվ ժամերին"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Թարմացումը կտեղադրվի, երբ սարքն օգտագործվելիս չլինի"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"PIN կոդը որոշ ժամանակ չի օգտագործվել։"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Գաղտնաբառը որոշ ժամանակ չի օգտագործվել։"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Նախշը որոշ ժամանակ չի օգտագործվել։"</string>
diff --git a/packages/SystemUI/res-keyguard/values-in/strings.xml b/packages/SystemUI/res-keyguard/values-in/strings.xml
index 46390bf..aa766e9 100644
--- a/packages/SystemUI/res-keyguard/values-in/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-in/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN diperlukan setelah kunci total"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Sandi diperlukan setelah kunci total"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Pola diperlukan setelah kunci total"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Update akan diinstal selama jam tidak aktif"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Update akan diinstal saat perangkat sedang tidak digunakan"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Perlu keamanan tambahan. PIN tidak digunakan selama beberapa waktu."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Perlu keamanan tambahan. Sandi tidak digunakan selama beberapa waktu."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Perlu keamanan tambahan. Pola tidak digunakan selama beberapa waktu."</string>
diff --git a/packages/SystemUI/res-keyguard/values-is/strings.xml b/packages/SystemUI/res-keyguard/values-is/strings.xml
index 4099241..99f1779 100644
--- a/packages/SystemUI/res-keyguard/values-is/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-is/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN-númers er krafist eftir læsingu"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Aðgangsorðs er krafist eftir læsingu"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Mynsturs er krafist eftir læsingu"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Uppfærslan verður sett upp í aðgerðaleysi"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Uppfærslan verður sett upp þegar tækið er ekki í notkun"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Viðbótaröryggis krafist. PIN-númer var ekki notað um hríð."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Viðbótaröryggis krafist. Aðgangsorð var ekki notað um hríð."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Viðbótaröryggis krafist. Mynstur var ekki notað um hríð."</string>
diff --git a/packages/SystemUI/res-keyguard/values-it/strings.xml b/packages/SystemUI/res-keyguard/values-it/strings.xml
index bc1922f..e5079c7 100644
--- a/packages/SystemUI/res-keyguard/values-it/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-it/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN richiesto dopo il blocco"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Password richiesta dopo il blocco"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Sequenza richiesta dopo il blocco"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Aggiornamento installato durante ore di inattività"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"L\'aggiornamento verrà installato quando il dispositivo non sarà in uso"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Occorre maggiore sicurezza. PIN non usato per un po\' di tempo."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Occorre maggiore sicurezza. Password non usata per un po\' di tempo."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Occorre maggiore sicurezza. Sequenza non usata per un po\' di tempo."</string>
diff --git a/packages/SystemUI/res-keyguard/values-iw/strings.xml b/packages/SystemUI/res-keyguard/values-iw/strings.xml
index 7b641dc..bc66355 100644
--- a/packages/SystemUI/res-keyguard/values-iw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-iw/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"נדרש קוד אימות לאחר הפעלת \'ללא \'ביטול נעילה בטביעת אצבע\'\'"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"נדרשת סיסמה לאחר הפעלת \'ללא \'ביטול נעילה בטביעת אצבע\'\'"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"נדרש קו ביטול נעילה לאחר הפעלת \'ללא \'ביטול נעילה בטביעת אצבע\'\'"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"העדכון יותקן במהלך השעות שבהן אתם לא משתמשים במכשיר"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"העדכון יותקן כשהמכשיר לא יהיה בשימוש"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"נדרשת אבטחה מוגברת. לא השתמשת בקוד אימות זמן מה."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"נדרשת אבטחה מוגברת. לא השתמשת בסיסמה זמן מה."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"נדרשת אבטחה מוגברת. לא השתמשת בקו ביטול נעילה זמן מה."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ja/strings.xml b/packages/SystemUI/res-keyguard/values-ja/strings.xml
index 6babdd7..1d59a63 100644
--- a/packages/SystemUI/res-keyguard/values-ja/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ja/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"ロックダウン後は PIN の入力が必要になります"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"ロックダウン後はパスワードの入力が必要になります"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"ロックダウン後はパターンの入力が必要になります"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"アップデートはアクティブでない時間帯にインストールされます"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"アップデートは、デバイスを使用していないときにインストールされます"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"セキュリティ強化が必要: PIN がしばらく未使用です。"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"セキュリティ強化が必要: パスワードがしばらく未使用です。"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"セキュリティ強化が必要: パターンがしばらく未使用です。"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ka/strings.xml b/packages/SystemUI/res-keyguard/values-ka/strings.xml
index c2d22f7..5bd6b2e 100644
--- a/packages/SystemUI/res-keyguard/values-ka/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ka/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"დაბლოკვის შემდეგ საჭიროა PIN-კოდი"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"დაბლოკვის შემდეგ საჭიროა პაროლი"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"დაბლოკვის შემდეგ საჭიროა ნიმუში"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"განახლება დაყენდება არასამუშაო საათებში"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"განახლება დაინსტალირდება, როცა მოწყობილობა არ გამოიყენება"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"საჭიროა დამ. უსაფრთ. PIN-კოდი ერთხანს არ გამოიყენ."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"საჭ. დამ. უსაფრთ. პაროლი ერთხანს არ გამოიყენება."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"საჭიროა დამ. უსაფრთ. ნიმუში ერთხანს არ გამოიყენება."</string>
diff --git a/packages/SystemUI/res-keyguard/values-kk/strings.xml b/packages/SystemUI/res-keyguard/values-kk/strings.xml
index d922b23..83d270d 100644
--- a/packages/SystemUI/res-keyguard/values-kk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kk/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Құлыпталғаннан кейін PIN кодын енгізу қажет."</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Құлыпталғаннан кейін құпия сөз енгізу қажет."</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Құлыпталғаннан кейін өрнек енгізу қажет."</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Пайдаланылмаған кезде жаңартылады."</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Құрылғы қолданылмағанда жаңартылады."</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Қауіпсіздікті күшейту қажет. PIN коды біраз уақыт қолданылмаған."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Қауіпсіздікті күшейту қажет. Құпия сөз біраз уақыт қолданылмаған."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Қауіпсіздікті күшейту қажет. Өрнек біраз уақыт қолданылмаған."</string>
diff --git a/packages/SystemUI/res-keyguard/values-km/strings.xml b/packages/SystemUI/res-keyguard/values-km/strings.xml
index dac9002..5306cb1 100644
--- a/packages/SystemUI/res-keyguard/values-km/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-km/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"ត្រូវការកូដ PIN បន្ទាប់ពីការចាក់សោ"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"ត្រូវការពាក្យសម្ងាត់បន្ទាប់ពីការចាក់សោ"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"ត្រូវការលំនាំបន្ទាប់ពីការចាក់សោ"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"នឹងដំឡើងកំណែថ្មីអំឡុងម៉ោងអសកម្ម"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"កំណែថ្មីនឹងត្រូវបានដំឡើង នៅពេលឧបករណ៍មិនជាប់ប្រើ"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"ត្រូវការសុវត្ថិភាពបន្ថែម។ មិនបានប្រើកូដ PIN មួយរយៈ។"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"ត្រូវការសុវត្ថិភាពបន្ថែម។ មិនបានប្រើពាក្យសម្ងាត់មួយរយៈ។"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"ត្រូវការសុវត្ថិភាពបន្ថែម។ មិនបានប្រើលំនាំមួយរយៈ។"</string>
diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml
index 7303585..d609a23 100644
--- a/packages/SystemUI/res-keyguard/values-kn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"ಲಾಕ್‌ಡೌನ್ ಮಾಡಿದ ನಂತರ ಪಿನ್ ಬಳಸುವ ಅಗತ್ಯವಿದೆ"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"ಲಾಕ್‌ಡೌನ್‌ನ ನಂತರ ಪಾಸ್‌ವರ್ಡ್‌ನ ಅಗತ್ಯವಿದೆ"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"ಲಾಕ್‌ಡೌನ್ ಮಾಡಿದ ನಂತರ ಪ್ಯಾಟರ್ನ್‌ ಬಳಸುವ ಅಗತ್ಯವಿದೆ"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"ನಿಷ್ಕ್ರಿಯ ಸಮಯದಲ್ಲಿ ಅಪ್‌ಡೇಟ್‌ ಇನ್‌ಸ್ಟಾಲ್ ಆಗುತ್ತದೆ"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"ಸಾಧನವನ್ನು ಬಳಕೆ ಮಾಡದ ಸಮಯದಲ್ಲಿ ಅಪ್‌ಡೇಟ್ ಅನ್ನು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಲಾಗುತ್ತದೆ"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"ಹೆಚ್ಚುವರಿ ಸುರಕ್ಷತೆಯ ಅಗತ್ಯವಿದೆ. ಪಿನ್ ಅನ್ನು ಸ್ವಲ್ಪ ಕಾಲದಿಂದ ಬಳಸಿಲ್ಲ."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"ಹೆಚ್ಚುವರಿ ಸುರಕ್ಷತೆಯ ಅಗತ್ಯವಿದೆ. ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಸ್ವಲ್ಪ ಕಾಲದಿಂದ ಬಳಸಿಲ್ಲ."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"ಹೆಚ್ಚುವರಿ ಸುರಕ್ಷತೆಯ ಅಗತ್ಯವಿದೆ. ಪ್ಯಾಟರ್ನ್ ಅನ್ನು ಸ್ವಲ್ಪ ಕಾಲದಿಂದ ಬಳಸಿಲ್ಲ."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml
index 5787258..532253e 100644
--- a/packages/SystemUI/res-keyguard/values-ko/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"기기가 잠겨 PIN을 입력해야 합니다."</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"기기가 잠겨 비밀번호를 입력해야 합니다."</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"기기가 잠겨 패턴을 입력해야 합니다."</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"미사용 시간에 업데이트가 설치됩니다."</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"기기를 사용하지 않을 때 업데이트가 설치됩니다."</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"보안을 강화해야 합니다. 한동안 PIN이 사용되지 않았습니다."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"보안을 강화해야 합니다. 한동안 비밀번호가 사용되지 않았습니다."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"보안을 강화해야 합니다. 한동안 패턴이 사용되지 않았습니다."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml
index 9e5c7ab..9ad9d56 100644
--- a/packages/SystemUI/res-keyguard/values-ky/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Бекем кулпулангандан кийин PIN код талап кылынат"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Бекем кулпулангандан кийин сырсөз талап кылынат"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Бекем кулпулангандан кийн грфикалык ачкыч талп клынт"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Жигердүү эмес сааттарда жаңыртылат"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Жаңыртуу түзмөк колдонулбай турганда орнотулат"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Кошмча кпсуздук тлап клнат. PIN код бир нче убкыт бою клднулгн эмeс."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Кошмча кпсуздук тлап клнат. Сырсз бир нче убкыт бою клднулгн эмeс."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Кошмча кпсуздук тлап клнат. Грфиклык ачкч бир нче убкыт бою клднулгн эмeс."</string>
diff --git a/packages/SystemUI/res-keyguard/values-lo/strings.xml b/packages/SystemUI/res-keyguard/values-lo/strings.xml
index ac766e5..0059d7f 100644
--- a/packages/SystemUI/res-keyguard/values-lo/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lo/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"ຕ້ອງໃສ່ PIN ຫຼັງຈາກທີ່ລັອກໄວ້"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"ຕ້ອງໃສ່ລະຫັດຜ່ານຫຼັງຈາກທີ່ລັອກໄວ້"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"ຕ້ອງແຕ້ມຮູບແບບຫຼັງຈາກທີ່ລັອກໄວ້"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"ການອັບເດດຈະຕິດຕັ້ງໃນລະຫວ່າງຊົ່ວໂມງທີ່ບໍ່ມີການນຳໃຊ້"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"ລະບົບຈະຕິດຕັ້ງການອັບເດດເມື່ອບໍ່ມີການນຳໃຊ້ອຸປະກອນ"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"ຕ້ອງເພີ່ມຄວາມປອດໄພ. ບໍ່ໄດ້ໃຊ້ PIN ມາໄລຍະໜຶ່ງແລ້ວ."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"ຕ້ອງເພີ່ມຄວາມປອດໄພ. ບໍ່ໄດ້ໃຊ້ລະຫັດຜ່ານມາໄລຍະໜຶ່ງແລ້ວ."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"ຕ້ອງເພີ່ມຄວາມປອດໄພ. ບໍ່ໄດ້ໃຊ້ຮູບແບບມາໄລຍະໜຶ່ງແລ້ວ."</string>
diff --git a/packages/SystemUI/res-keyguard/values-lt/strings.xml b/packages/SystemUI/res-keyguard/values-lt/strings.xml
index 5e47622..01e2f88 100644
--- a/packages/SystemUI/res-keyguard/values-lt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lt/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Po užrakinimo reikalingas PIN kodas"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Po užrakinimo reikalingas slaptažodis"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Po užrakinimo reikalingas atrakinimo piešinys"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Naujinys bus įdiegtas neaktyvumo valandomis"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Naujinys bus įdiegtas, kai įrenginys nebus naudojamas"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Reikalinga papildoma sauga. PIN kodas nebuvo naudojamas kurį laiką."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Reikalinga papildoma sauga. Slaptažodis nebuvo naudojamas kurį laiką."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Reikalinga papildoma sauga. Atrakinimo piešinys nebuvo naudojamas kurį laiką."</string>
diff --git a/packages/SystemUI/res-keyguard/values-lv/strings.xml b/packages/SystemUI/res-keyguard/values-lv/strings.xml
index 5277671..2133694 100644
--- a/packages/SystemUI/res-keyguard/values-lv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lv/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Pēc bloķēšanas ir jāievada PIN"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Pēc bloķēšanas ir jāievada parole"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Pēc bloķēšanas ir jāzīmē kombinācija"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Atjauninājums tiks instalēts neaktīvajā laikā"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Atjauninājums tiks instalēts, kad ierīce netiks izmantota."</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Jāveic papildu drošības darbība. PIN ilgu laiku nav lietots."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Jāveic papildu drošības darbība. Parole ilgu laiku nav lietota."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Jāveic papildu drošības darbība. Kombinācija ilgu laiku nav lietota."</string>
diff --git a/packages/SystemUI/res-keyguard/values-mk/strings.xml b/packages/SystemUI/res-keyguard/values-mk/strings.xml
index 5a35808..2771c7f 100644
--- a/packages/SystemUI/res-keyguard/values-mk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mk/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Потребен е PIN-код по заклучување"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Потребна е лозинка по заклучување"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Потребна е шема по заклучување"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Ажурирањето ќе се инсталира за време на неактивни часови"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Ажурирањето ќе се инсталира кога нема да се користи уредот"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Потребна е дополнителна безбедност. PIN-кодот не бил користен некое време."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Потребна е дополнителна безбедност. Лозинката не била користена некое време."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Потребна е дополнителна безбедност. Шемата не била користена некое време."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml
index 20f5b4d..02ee66f 100644
--- a/packages/SystemUI/res-keyguard/values-ml/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"ലോക്ക്‌ഡൗണിന് ശേഷം പിൻ നൽകേണ്ടതുണ്ട്"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"ലോക്ക്‌ഡൗണിന് ശേഷം പാസ്‌വേഡ് നൽകേണ്ടതുണ്ട്"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"ലോക്ക്‌ഡൗണിന് ശേഷം പാറ്റേൺ വരയ്‌ക്കേണ്ടതുണ്ട്"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"സജീവമല്ലാത്ത സമയത്ത് അപ്ഡേറ്റ് ഇൻസ്റ്റാൾ ചെയ്യും"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"ഉപകരണം ഉപയോഗിക്കാതിരിക്കുമ്പോൾ അപ്ഡേറ്റ് ഇൻസ്റ്റാൾ ആകും"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"അധിക സുരക്ഷ വേണം. അൽപകാലം പിൻ ഉപയോഗിച്ചില്ല."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"അധിക സുരക്ഷ വേണം. അൽപകാലം പാസ്‌വേഡ് ഉപയോഗിച്ചില്ല."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"അധിക സുരക്ഷ വേണം. അൽപകാലം പാറ്റേൺ ഉപയോഗിച്ചില്ല."</string>
diff --git a/packages/SystemUI/res-keyguard/values-mn/strings.xml b/packages/SystemUI/res-keyguard/values-mn/strings.xml
index b326d3ed..2b9f81e 100644
--- a/packages/SystemUI/res-keyguard/values-mn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mn/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Түгжсэний дараа ПИН шаардлагатай"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Түгжсэний дараа нууц үг шаардлагатай"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Түгжсэний дараа хээ шаардлагатай"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Шинэчлэлтийг идэвхгүй цагуудаар суулгана"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Төхөөрөмжийг ашиглаагүй үед шинэчлэлтийг суулгана"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Нэмэлт аюулгүй байдал шаардлагатай. ПИН-г хэсэг хугацаанд ашиглаагүй."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Нэмэлт аюулгүй байдал шаардлагатай. Нууц үгийг хэсэг хугацаанд ашиглаагүй."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Нэмэлт аюулгүй байдал шаардлагатай. Хээг хэсэг хугацаанд ашиглаагүй."</string>
diff --git a/packages/SystemUI/res-keyguard/values-mr/strings.xml b/packages/SystemUI/res-keyguard/values-mr/strings.xml
index 1e25e17..7aa7bdd 100644
--- a/packages/SystemUI/res-keyguard/values-mr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mr/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"लॉकडाउननंतर पिन आवश्यक आहे"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"लॉकडाउननंतर पासवर्ड आवश्यक आहे"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"लॉकडाउननंतर पॅटर्न आवश्यक आहे"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"अपडेट हे इनॅक्टिव्ह तासांदरम्यान इंस्टॉल होईल"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"डिव्हाइस वापरात नसताना अपडेट इंस्टॉल केले जाईल"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"अतिरिक्त सुरक्षा आवश्‍यक आहे. काही वेळेसाठी पिन अनलॉक केला गेला नव्हता."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"अतिरिक्त सुरक्षा आवश्‍यक आहे. काही वेळेसाठी पासवर्ड अनलॉक केला गेला नव्हता."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"अतिरिक्त सुरक्षा आवश्‍यक आहे. काही वेळेसाठी पॅटर्न अनलॉक केला गेला नव्हता."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ms/strings.xml b/packages/SystemUI/res-keyguard/values-ms/strings.xml
index 3942dbb..bdfa4a7 100644
--- a/packages/SystemUI/res-keyguard/values-ms/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ms/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN diperlukan selepas kunci semua"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Kata laluan diperlukan selepas kunci semua"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Corak diperlukan selepas kunci semua"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Kemaskinian akan dipasang semasa waktu tidak aktif"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Kemaskinian akan dipasang apabila peranti tidak digunakan"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Keselamatan tambahan diperlukan. PIN tidak digunakan."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Keselamatan tambahan diperlukan. Kata laluan tidak digunakan."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Keselamatan tambahan diperlukan. Corak tidak digunakan."</string>
diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml
index 311e072..e85cf8a 100644
--- a/packages/SystemUI/res-keyguard/values-my/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-my/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"လော့ခ်ဒေါင်းလုပ်ပြီးနောက် ပင်နံပါတ်လိုအပ်သည်"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"လော့ခ်ဒေါင်းလုပ်ပြီးနောက် စကားဝှက်လိုအပ်သည်"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"လော့ခ်ဒေါင်းလုပ်ပြီးနောက် ပုံဖော်ခြင်းလိုအပ်သည်"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"မသုံးသည့်အချိန်အတွင်း အပ်ဒိတ်ထည့်သွင်းမည်"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"စက်သုံးနေခြင်း မရှိချိန်တွင် အပ်ဒိတ် ထည့်သွင်းပါမည်"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"ထပ်ဆောင်းလုံခြုံရေး လိုအပ်သည်။ ပင်နံပါတ်မသုံးသည်မှာ အနည်းငယ်ကြာပြီ။"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"ထပ်ဆောင်းလုံခြုံရေး လိုအပ်သည်။ စကားဝှက်မသုံးသည်မှာ အနည်းငယ်ကြာပြီ။"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"ထပ်ဆောင်းလုံခြုံရေး လိုအပ်သည်။ ပုံဖော်ခြင်းမသုံးသည်မှာ အနည်းငယ်ကြာပြီ။"</string>
diff --git a/packages/SystemUI/res-keyguard/values-nb/strings.xml b/packages/SystemUI/res-keyguard/values-nb/strings.xml
index 94a6340..455d086 100644
--- a/packages/SystemUI/res-keyguard/values-nb/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nb/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN-koden kreves etter låsing"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Passordet kreves etter låsing"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Mønsteret kreves etter låsing"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Oppdateringen installeres når enheten er inaktiv"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Oppdateringen installeres når enheten ikke brukes"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Økt sikkerhet kreves. Har ikke brukt PIN-koden nylig"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Økt sikkerhet kreves. Har ikke brukt passordet nylig"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Økt sikkerhet kreves. Har ikke brukt mønsteret nylig"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml
index 0b4a4a5..f0094a3 100644
--- a/packages/SystemUI/res-keyguard/values-ne/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"लकडाउन गरेपछि PIN हाल्नु पर्ने हुन्छ"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"लकडाउन गरेपछि पासवर्ड हाल्नु पर्ने हुन्छ"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"लकडाउन गरेपछि प्याटर्न कोर्नु पर्ने हुन्छ"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"डिभाइस प्रयोग नभएका बेला अपडेट इन्स्टल हुने छ"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"डिभाइस नचलाइएका बेला अपडेट इन्स्टल हुने छ"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"थप सुरक्षित बनाउनु पर्ने हुन्छ। केही समयदेखि PIN प्रयोग गरिएको छैन।"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"थप सुरक्षित बनाउनु पर्ने हुन्छ। केही समयदेखि पासवर्ड प्रयोग गरिएको छैन।"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"थप सुरक्षित बनाउनु पर्ने हुन्छ। केही समयदेखि प्याटर्न प्रयोग गरिएको छैन।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index 366ee57..a236639 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Na lockdown is de pincode vereist"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Na lockdown is het wachtwoord vereist"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Na lockdown is het patroon vereist"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Update wordt geïnstalleerd tijdens inactieve uren"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Update wordt geïnstalleerd als het apparaat niet wordt gebruikt"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Extra beveiliging. Pincode is lang niet gebruikt."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Extra beveiliging. Wachtwoord is lang niet gebruikt."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Extra beveiliging. Patroon is lang niet gebruikt."</string>
diff --git a/packages/SystemUI/res-keyguard/values-or/strings.xml b/packages/SystemUI/res-keyguard/values-or/strings.xml
index 666dab5..b31c9c0 100644
--- a/packages/SystemUI/res-keyguard/values-or/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-or/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"ଲକଡାଉନ ହେବା ପରେ PIN ଆବଶ୍ୟକ"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"ଲକଡାଉନ ହେବା ପରେ ପାସୱାର୍ଡ ଆବଶ୍ୟକ"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"ଲକଡାଉନ ହେବା ପରେ ପାଟର୍ନ ଆବଶ୍ୟକ"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"ନିଷ୍କ୍ରିୟ ସମୟରେ ଅପଡେଟ ଇନଷ୍ଟଲ ହେବ"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"ଡିଭାଇସ ବ୍ୟବହାର କରାଯାଉନଥିବା ବେଳେ ଅପଡେଟ ଇନଷ୍ଟଲ ହେବ"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ଆବଶ୍ୟକ। କିଛି ସମୟ ପାଇଁ PIN ବ୍ୟବହାର କରାଯାଇନାହିଁ।"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ଆବଶ୍ୟକ। କିଛି ସମୟ ପାଇଁ ପାସୱାର୍ଡ ବ୍ୟବହାର କରାଯାଇନାହିଁ।"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ଆବଶ୍ୟକ। କିଛି ସମୟ ପାଇଁ ପାଟର୍ନ ବ୍ୟବହାର କରାଯାଇନାହିଁ।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml
index 321c99f..209b63f 100644
--- a/packages/SystemUI/res-keyguard/values-pa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"ਲਾਕਡਾਊਨ ਤੋਂ ਬਾਅਦ ਪਿੰਨ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"ਲਾਕਡਾਊਨ ਤੋਂ ਬਾਅਦ ਪਾਸਵਰਡ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"ਲਾਕਡਾਊਨ ਤੋਂ ਬਾਅਦ ਪੈਟਰਨ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"ਅੱਪਡੇਟ ਅਕਿਰਿਆਸ਼ੀਲ ਘੰਟਿਆਂ ਦੌਰਾਨ ਸਥਾਪਤ ਹੋਵੇਗਾ"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"ਡੀਵਾਈਸ ਵਰਤੋਂ ਵਿੱਚ ਨਾ ਹੋਣ \'ਤੇ ਅੱਪਡੇਟ ਸਥਾਪਤ ਹੋ ਜਾਵੇਗਾ"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"ਵਾਧੂ ਸੁਰੱਖਿਆ ਦੀ ਲੋੜ ਹੈ। ਪਿੰਨ ਨੂੰ ਕੁਝ ਸਮੇਂ ਲਈ ਵਰਤਿਆ ਨਹੀਂ ਗਿਆ।"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"ਵਾਧੂ ਸੁਰੱਖਿਆ ਦੀ ਲੋੜ ਹੈ। ਪਾਸਵਰਡ ਨੂੰ ਕੁਝ ਸਮੇਂ ਲਈ ਵਰਤਿਆ ਨਹੀਂ ਗਿਆ।"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"ਵਾਧੂ ਸੁਰੱਖਿਆ ਦੀ ਲੋੜ ਹੈ। ਪੈਟਰਨ ਨੂੰ ਕੁਝ ਸਮੇਂ ਲਈ ਵਰਤਿਆ ਨਹੀਂ ਗਿਆ।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pl/strings.xml b/packages/SystemUI/res-keyguard/values-pl/strings.xml
index 1a38896..7ec988e 100644
--- a/packages/SystemUI/res-keyguard/values-pl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pl/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Po zablokowaniu wymagany jest kod PIN"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Po zablokowaniu wymagane jest hasło"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Po zablokowaniu wymagany jest wzór"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Aktualizacja zainstaluje się w czasie bezczynności"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Instalacja aktualizacji nastąpi, gdy urządzenie nie będzie w użyciu"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Wzmocnij ochronę. Od dawna nie używano kodu PIN."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Wzmocnij ochronę. Od dawna nie używano hasła."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Wzmocnij ochronę. Od dawna nie używano wzoru."</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
index 962f1e6..78a8091 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"O PIN é obrigatório após o Bloqueio total"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"A senha é obrigatória após o Bloqueio total"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"O padrão é obrigatório após o Bloqueio total"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"A atualização será feita no período de inatividade"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"A atualização será instalada quando o dispositivo não estiver em uso"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Segurança necessária. PIN não usado há um tempo."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Segurança necessária. Senha não usada há um tempo."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Segurança necessária. Padrão não usado há um tempo."</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
index afec176..2dc7d27 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"O PIN é necessário após o bloqueio"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"A palavra-passe é necessária após o bloqueio"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"O padrão é necessário após o bloqueio"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"A atualização vai ser instalada nas horas inativas"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"A atualização é instalada quando o dispositivo não estiver a ser usado"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Mais segurança necessária. PIN não usado há muito."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Mais segurança necessária. Não usada há muito."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"+ segurança necessária. Padrão não usado há muito."</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt/strings.xml b/packages/SystemUI/res-keyguard/values-pt/strings.xml
index 962f1e6..78a8091 100644
--- a/packages/SystemUI/res-keyguard/values-pt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"O PIN é obrigatório após o Bloqueio total"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"A senha é obrigatória após o Bloqueio total"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"O padrão é obrigatório após o Bloqueio total"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"A atualização será feita no período de inatividade"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"A atualização será instalada quando o dispositivo não estiver em uso"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Segurança necessária. PIN não usado há um tempo."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Segurança necessária. Senha não usada há um tempo."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Segurança necessária. Padrão não usado há um tempo."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index caa3d83..e5be788 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Codul PIN este solicitat după blocarea strictă"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Parola este solicitată după blocarea strictă"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Modelul este solicitat după blocarea strictă"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Actualizarea se va instala în perioada de inactivitate"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Actualizarea se va instala când dispozitivul nu este folosit"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Mai multă securitate necesară. PIN-ul nu a fost folosit de ceva timp."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Mai multă securitate necesară. Parola nu a fost folosită de ceva timp."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Mai multă securitate necesară. Modelul nu a fost folosit de ceva timp."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index bd52b7d..45149a5 100644
--- a/packages/SystemUI/res-keyguard/values-ru/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"После блокировки необходимо ввести PIN-код."</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"После блокировки необходимо ввести пароль."</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"После блокировки необходимо ввести графический ключ."</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Обновление установится, когда устройство неактивно."</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Обновление будет установлено, когда устройство не используется."</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"PIN-код давно не использовался. Усильте защиту."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Пароль давно не использовался. Усильте защиту."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Граф. ключ давно не использовался. Усильте защиту."</string>
diff --git a/packages/SystemUI/res-keyguard/values-si/strings.xml b/packages/SystemUI/res-keyguard/values-si/strings.xml
index 0dfc8d9..17ced75 100644
--- a/packages/SystemUI/res-keyguard/values-si/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-si/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"අගුළු දැමීමෙන් පසු PIN අවශ්‍ය වේ"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"අගුළු දැමීමෙන් පසු මුරපදය අවශ්‍ය වේ"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"අගුළු දැමීමෙන් පසු රටාව අවශ්‍ය වේ"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"යාවත්කාලීනය අක්‍රිය පැය තුළ ස්ථාපනය වනු ඇත"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"උපාංගය භාවිතයේ නොමැති විට යාවත්කාලීනය ස්ථාපනය වනු ඇත"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"අමතර ආරක්ෂාවක් අවශ්‍යයි. PIN ටික කලකට භාවිතා කර නැත."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"අමතර ආරක්ෂාවක් අවශ්‍යයි. මුරපදය ටික කලකට භාවිතා කර නැත."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"අමතර ආරක්ෂාවක් අවශ්‍යයි. රටාව ටික කලකට භාවිතා කර නැත."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml
index 5f10287..ef08a6c 100644
--- a/packages/SystemUI/res-keyguard/values-sk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Po silnej zámke sa vyžaduje PIN"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Po silnej zámke sa vyžaduje heslo"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Po silnej zámke sa vyžaduje vzor"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Aktualizácia sa nainštaluje počas nečinnosti"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Aktualizácia sa nainštaluje, keď zariadenie nebudete používať"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Treba lepšie zabezp. Kód PIN nebol dlhšie použitý."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Treba lepšie zabezp. Heslo nebolo dlhšie použité."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Treba lepšie zabezp. Vzor nebol dlhšie použitý."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sl/strings.xml b/packages/SystemUI/res-keyguard/values-sl/strings.xml
index 26d4951..a42989c 100644
--- a/packages/SystemUI/res-keyguard/values-sl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sl/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Po zaklepu se zahteva vnos kode PIN"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Po zaklepu se zahteva vnos gesla"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Po zaklepu se zahteva vnos vzorca"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Posodobitev bo nameščena v času nedejavnosti"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Posodobitev bo nameščena, ko naprava ne bo v uporabi"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Zahtevana je dodatna varnost. Koda PIN nekaj časa ni bila uporabljena."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Zahtevana je dodatna varnost. Geslo nekaj časa ni bilo uporabljeno."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Zahtevana je dodatna varnost. Vzorec nekaj časa ni bil uporabljen."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sq/strings.xml b/packages/SystemUI/res-keyguard/values-sq/strings.xml
index ef4e566..84f7bb5 100644
--- a/packages/SystemUI/res-keyguard/values-sq/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sq/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Pas bllokimit kërkohet kodi PIN"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Pas bllokimit kërkohet fjalëkalimi"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Pas bllokimit kërkohet motivi"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Përditësimi do të instalohet gjatë kohës joaktive"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Përditësimi do të instalohet kur pajisja të mos përdoret"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Kërkohet një siguri më e lartë. Kodi PIN nuk është përdorur për njëfarë kohe."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Kërkohet një siguri më e lartë. Fjalëkalimi nuk është përdorur për njëfarë kohe."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Kërkohet një siguri më e lartë. Motivi nuk është përdorur për njëfarë kohe."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml
index 5ed152f..437018d 100644
--- a/packages/SystemUI/res-keyguard/values-sr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN је обавезан после закључавања"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Лозинка је обавезна после закључавања"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Шаблон је обавезан после закључавања"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Ажурирање се инсталира током неактивности"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Ажурирање ће се инсталирати када се уређај не користи"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Потребна је додатна заштита. PIN дуго није коришћен."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Потребна је додатна заштита. Лозинка дуго није коришћена."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Потребна је додатна заштита. Шаблон дуго није коришћен."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sv/strings.xml b/packages/SystemUI/res-keyguard/values-sv/strings.xml
index abd677a..b4b1996 100644
--- a/packages/SystemUI/res-keyguard/values-sv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sv/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Pinkod krävs efter låsning"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Lösenord krävs efter låsning"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Mönster krävs efter låsning"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Uppdateringen installeras under inaktiva timmar"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Uppdateringen installeras när enheten inte används"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Ökad säkerhet krävs. Pinkoden har inte använts på länge."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Ökad säkerhet krävs. Lösenordet har inte använts på länge."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Ökad säkerhet krävs. Mönstret har inte använts på länge."</string>
diff --git a/packages/SystemUI/res-keyguard/values-sw/strings.xml b/packages/SystemUI/res-keyguard/values-sw/strings.xml
index 7516d90..8ca9046 100644
--- a/packages/SystemUI/res-keyguard/values-sw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sw/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"PIN inahitajika baada ya kufunga"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Nenosiri linahitajika baada ya kufunga"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Mchoro unahitajika baada ya kufunga"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Sasisho litasakinishwa wakati kifaa hakitumiki"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Sasisho litasakinishwa wakati hutumii kifaa"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Usalama wa ziada unahitajika. PIN haikutumika kwa muda."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Usalama wa ziada unahitajika. Nenosiri halikutumika kwa muda."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Usalama wa ziada unahitajika. Mchoro haukutumika kwa muda."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml
index 92dfc18..7671194 100644
--- a/packages/SystemUI/res-keyguard/values-ta/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"முழுப் பூட்டு காரணமாகப் பின் தேவை"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"முழுப் பூட்டு காரணமாகக் கடவுச்சொல் தேவை"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"முழுப் பூட்டு காரணமாகப் பேட்டர்ன் தேவை"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"செயல்பாடில்லாத நேரத்தில் புதுப்பிப்பு நிறுவப்படும்"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"சாதனம் உபயோகத்தில் இல்லாதபோது புதுப்பிப்பு நிறுவப்படும்"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"சேர்த்த பாதுகாப்பு தேவை. பின் உபயோகிக்கவில்லை."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"சேர்த்த பாதுகாப்பு தேவை. கடவுச்சொல் உபயோகிக்கவில்லை."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"சேர்த்த பாதுகாப்பு தேவை. பேட்டர்ன் உபயோகிக்கவில்லை."</string>
diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml
index a12d6ec..623b589 100644
--- a/packages/SystemUI/res-keyguard/values-te/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-te/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"లాక్‌డౌన్ తర్వాత PIN అవసరం"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"లాక్‌డౌన్ తర్వాత పాస్‌వర్డ్ అవసరం"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"లాక్‌డౌన్ తర్వాత ఆకృతి అవసరం"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"ఇన్‌యాక్టివ్ వేళల్లో అప్‌డేట్ ఇన్‌స్టాల్ చేయబడుతుంది"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"పరికరం ఉపయోగంలో లేనప్పుడు అప్‌డేట్ ఇన్‌స్టాల్ చేయబడుతుంది"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"మరింత సెక్యూరిటీ యాడ్ చెయ్యాలి. PINని ఈమధ్య వాడలేదు."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"యాడెడ్ సెక్యూరిటీ కావాలి. పాస్‌వర్డ్ ఈ మధ్య వాడలేదు."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"మరింత సెక్యూరిటీ కావాలి. ఆకృతిని ఈ మధ్య వాడలేదు."</string>
diff --git a/packages/SystemUI/res-keyguard/values-th/strings.xml b/packages/SystemUI/res-keyguard/values-th/strings.xml
index bcd097d..c244107 100644
--- a/packages/SystemUI/res-keyguard/values-th/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-th/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"ต้องป้อน PIN หลังจากการปิดล็อก"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"ต้องป้อนรหัสผ่านหลังจากการปิดล็อก"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"ต้องวาดรูปแบบหลังจากการปิดล็อก"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"การอัปเดตจะติดตั้งในระหว่างชั่วโมงที่ไม่ได้ใช้งาน"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"ระบบจะติดตั้งการอัปเดตเมื่อไม่มีการใช้งานอุปกรณ์"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"ต้องเพิ่มความปลอดภัย ไม่ได้ใช้ PIN มาระยะหนึ่ง"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"ต้องเพิ่มความปลอดภัย ไม่ได้ใช้รหัสผ่านมาระยะหนึ่ง"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"ต้องเพิ่มความปลอดภัย ไม่ได้ใช้รูปแบบมาระยะหนึ่ง"</string>
diff --git a/packages/SystemUI/res-keyguard/values-tl/strings.xml b/packages/SystemUI/res-keyguard/values-tl/strings.xml
index a968cb0..cd8f810 100644
--- a/packages/SystemUI/res-keyguard/values-tl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tl/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Kailangan ang PIN pagkatapos ng lockdown"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Kailangan ang password pagkatapos ng lockdown"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Kailangan ang pattern pagkatapos ng lockdown"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Mag-i-install ang update sa mga hindi aktibong oras"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Ii-install ang update kapag hindi ginagamit ang device"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Kailangan pa ng seguridad. Matagal na hindi ginamit ang PIN."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Kailangan pa ng seguridad. Matagal na hindi ginamit ang password."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Kailangan pa ng seguridad. Matagal na hindi ginamit ang pattern."</string>
diff --git a/packages/SystemUI/res-keyguard/values-tr/strings.xml b/packages/SystemUI/res-keyguard/values-tr/strings.xml
index 1aad78c..ddeba67 100644
--- a/packages/SystemUI/res-keyguard/values-tr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tr/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Tam kilitlemenin ardından PIN gerekli"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Tam kilitlemenin ardından şifre gerekli"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Tam kilitlemenin ardından desen gerekli"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Güncelleme, etkin olmayan saatlerde yüklenecek"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Güncelleme, cihazın kullanılmadığı bir sırada yüklenecek"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Daha fazla güvenlik gerekli. PIN bir süredir kullanılmamış."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Daha fazla güvenlik gerekli. Şifre bir süredir kullanılmamış."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Daha fazla güvenlik gerekli. Desen bir süredir kullanılmamış."</string>
diff --git a/packages/SystemUI/res-keyguard/values-uk/strings.xml b/packages/SystemUI/res-keyguard/values-uk/strings.xml
index 546f31b..f06d17d 100644
--- a/packages/SystemUI/res-keyguard/values-uk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uk/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Після блокування входу потрібно ввести PIN-код"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Після блокування входу потрібно ввести пароль"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Після блокування входу потрібно намалювати ключ"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Оновлення встановиться під час годин неактивності"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Оновлення встановиться, коли пристрій не використовуватиметься"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Потрібен додатковий захист. PIN-код довго не використовувався."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Потрібен додатковий захист. Пароль довго не використовувався."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Потрібен додатковий захист. Ключ довго не використовувався."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml
index 5c9e6b0..8adbaca 100644
--- a/packages/SystemUI/res-keyguard/values-ur/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"‏لاک ڈاؤن کے بعد PIN کی ضرورت ہوتی ہے"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"لاک ڈاؤن کے بعد پاس ورڈ کی ضرورت ہوتی ہے"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"لاک ڈاؤن کے بعد پیٹرن کی ضرورت ہوتی ہے"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"اپ ڈیٹ غیر فعال اوقات کے دوران انسٹال ہوگی"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"آلہ استعمال میں نہ ہونے پر اپ ڈیٹ انسٹال ہو جائے گی"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"‏مزید سیکیورٹی چاہیے۔ PIN کچھ عرصے اسے استعمال نہیں ہوا ہے۔"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"مزید سیکیورٹی چاہیے۔ پاس ورڈ کچھ عرصے سے استعمال نہیں ہوا ہے۔"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"مزید سیکیورٹی چاہیے۔ پیٹرن کچھ عرصے سے استعمال نہیں ہوا ہے۔"</string>
diff --git a/packages/SystemUI/res-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml
index 53c2a2c..96dfa05 100644
--- a/packages/SystemUI/res-keyguard/values-uz/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Bloklangandan keyin PIN kodni kiritish kerak"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Bloklangandan keyin parolni kiritish kerak"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Bloklangandan keyin grafik kalitni chizish kerak"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Yangilanish qurilma nofaol boʻlganda oʻrnatiladi"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Yangilanish qurilma ishlatilmaganda oʻrnatiladi"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Xavfsizlikni oshiring. PIN kod ancha vaqt ishlatilmadi."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Xavfsizlikni oshiring. Parol ancha vaqt ishlatilmadi."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Xavfsizlikni oshiring. Grafik kalit ancha vaqt chizilmadi"</string>
diff --git a/packages/SystemUI/res-keyguard/values-vi/strings.xml b/packages/SystemUI/res-keyguard/values-vi/strings.xml
index b81e147..94d4fe2 100644
--- a/packages/SystemUI/res-keyguard/values-vi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-vi/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Cần nhập mã PIN sau khi hết thời gian khoá"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Cần nhập mật khẩu sau khi hết thời gian khoá"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Cần vẽ hình mở khoá sau khi hết thời gian khoá"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Bản cập nhật sẽ cài đặt vào các giờ không hoạt động"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Bản cập nhật sẽ được cài đặt khi bạn không sử dụng thiết bị"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Cần tăng cường bảo mật. Đã lâu chưa dùng mã PIN."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Cần tăng cường bảo mật. Đã lâu chưa dùng mật khẩu"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Cần tăng cường bảo mật. Đã lâu chưa dùng hình mở khoá."</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
index 0f0feb5..59261a3 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"一旦设备被锁定,必须输入 PIN 码才能解锁"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"一旦设备被锁定,必须输入密码才能解锁"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"一旦设备被锁定,必须绘制图案才能解锁"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"可用更新会在设备闲置期间安装"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"更新会在设备处于未使用状态时安装"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"需要锁定设备以提高安全性。已有一段时间未使用 PIN 码了。"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"需要锁定设备以提高安全性。已有一段时间未使用密码了。"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"需要锁定设备以提高安全性。已有一段时间未使用图案了。"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
index 701100f..dad6f31 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"需要輸入 PIN 才能解除鎖定"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"需要輸入密碼解才能解除鎖定"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"需要畫出解鎖圖案才能解除鎖定"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"裝置會在閒置時安裝更新"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"系統會在裝置未使用時安裝更新"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"需要加強安全設定:已有一段時間沒有使用 PIN。"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"需要加強安全設定:已有一段時間沒有使用密碼。"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"需要加強安全設定:已有一段時間沒有使用解鎖圖案。"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
index 4ac27f2..88b7e43 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"裝置鎖定後,必須輸入 PIN 碼才能解鎖"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"裝置鎖定後,必須輸入密碼才能解鎖"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"裝置鎖定後,必須畫出解鎖圖案才能解鎖"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"裝置會在閒置時安裝更新"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"系統會在裝置處於未使用狀態時安裝更新"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"裝置已有一段時間未鎖定,請使用 PIN 碼鎖定裝置以策安全。"</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"裝置已有一段時間未鎖定,請使用密碼鎖定裝置以策安全。"</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"裝置已有一段時間未鎖定,請使用解鎖圖案鎖定裝置以策安全。"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zu/strings.xml b/packages/SystemUI/res-keyguard/values-zu/strings.xml
index e4a4503..c5e99ab 100644
--- a/packages/SystemUI/res-keyguard/values-zu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zu/strings.xml
@@ -76,7 +76,7 @@
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"Iphinikhodi iyadingeka ngemva kokukhiya"</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"Iphasiwedi iyadingeka ngemuva kokukhiya"</string>
     <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"Iphethini iyadingeka ngemva kokukhiya"</string>
-    <string name="kg_prompt_unattended_update" msgid="8223448855578632202">"Isibuyekezo sizongena phakathi namahora okungasebenzi"</string>
+    <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"Isibuyekezo sizofakwa lapho idivayisi ingasetshenziswa"</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"Ukuphepha okwengeziwe kuyadingeka. Iphinikhodi ayisetshenziswanga isikhathi eside."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"Ukuphepha okwengeziwe kuyadingeka. Iphasiwedi ayisetshenziswanga isikhathi eside."</string>
     <string name="kg_prompt_pattern_auth_timeout" msgid="1860605401869262178">"Ukuphepha okwengeziwe kuyadingeka. Iphethini ayisetshenziswanga isikhathi eside."</string>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 39dd90e..8c81733 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -95,9 +95,6 @@
     <dimen name="num_pad_key_margin_end">12dp</dimen>
 
     <!-- additional offset for clock switch area items -->
-    <dimen name="small_clock_height">114dp</dimen>
-    <dimen name="small_clock_padding_top">28dp</dimen>
-    <dimen name="clock_padding_start">28dp</dimen>
     <dimen name="below_clock_padding_start">32dp</dimen>
     <dimen name="below_clock_padding_end">16dp</dimen>
     <dimen name="below_clock_padding_start_icons">28dp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index badad58..28b5870 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -192,7 +192,7 @@
     <string name="kg_prompt_after_user_lockdown_pattern">Pattern is required after lockdown</string>
 
     <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update.  [CHAR LIMIT=70] -->
-    <string name="kg_prompt_unattended_update">Update will install during inactive hours</string>
+    <string name="kg_prompt_unattended_update">Update will install when device not in use</string>
 
     <!-- Message shown when primary authentication hasn't been used for some time.  [CHAR LIMIT=70] -->
     <string name="kg_prompt_pin_auth_timeout">Added security required. PIN not used for a while.</string>
diff --git a/packages/SystemUI/res/color/brightness_slider_overlay_color.xml b/packages/SystemUI/res/color/brightness_slider_overlay_color.xml
new file mode 100644
index 0000000..a8abd79
--- /dev/null
+++ b/packages/SystemUI/res/color/brightness_slider_overlay_color.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:color="?attr/onShadeActive" android:alpha="0.12" />
+    <item android:state_hovered="true" android:color="?attr/onShadeActive" android:alpha="0.09" />
+    <item android:color="@color/transparent" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/qs_dialog_btn_filled_background.xml b/packages/SystemUI/res/color/qs_dialog_btn_filled_background.xml
new file mode 100644
index 0000000..40bab5e
--- /dev/null
+++ b/packages/SystemUI/res/color/qs_dialog_btn_filled_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+          android:color="?androidprv:attr/materialColorPrimary"
+          android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorPrimary"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/qs_dialog_btn_filled_large_background.xml b/packages/SystemUI/res/color/qs_dialog_btn_filled_large_background.xml
new file mode 100644
index 0000000..f8d4af5
--- /dev/null
+++ b/packages/SystemUI/res/color/qs_dialog_btn_filled_large_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+        android:color="?androidprv:attr/materialColorPrimaryFixed"
+        android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorPrimaryFixed"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/qs_dialog_btn_filled_large_text.xml b/packages/SystemUI/res/color/qs_dialog_btn_filled_large_text.xml
new file mode 100644
index 0000000..faba8fc
--- /dev/null
+++ b/packages/SystemUI/res/color/qs_dialog_btn_filled_large_text.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+        android:color="?androidprv:attr/materialColorOnPrimaryFixed"
+        android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorOnPrimaryFixed"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/qs_dialog_btn_filled_text_color.xml b/packages/SystemUI/res/color/qs_dialog_btn_filled_text_color.xml
new file mode 100644
index 0000000..e76ad99
--- /dev/null
+++ b/packages/SystemUI/res/color/qs_dialog_btn_filled_text_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+          android:color="?androidprv:attr/materialColorOnPrimary"
+          android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorOnPrimary"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/qs_dialog_btn_outline.xml b/packages/SystemUI/res/color/qs_dialog_btn_outline.xml
new file mode 100644
index 0000000..1adfe5b
--- /dev/null
+++ b/packages/SystemUI/res/color/qs_dialog_btn_outline.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+        android:color="?androidprv:attr/materialColorPrimary"
+        android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorPrimary"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/qs_dialog_btn_outline_text.xml b/packages/SystemUI/res/color/qs_dialog_btn_outline_text.xml
new file mode 100644
index 0000000..5dc994f23
--- /dev/null
+++ b/packages/SystemUI/res/color/qs_dialog_btn_outline_text.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:state_enabled="false"
+        android:color="?androidprv:attr/materialColorOnSurface"
+        android:alpha="0.30"/>
+    <item android:color="?androidprv:attr/materialColorOnSurface"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_down.xml b/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_down.xml
new file mode 100644
index 0000000..16076b1
--- /dev/null
+++ b/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_down.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- TODO(b/273761935): This drawable night variant is identical to the standard drawable. Delete once the drawable cache correctly invalidates for attributes that reference colors that change when the UI mode changes. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M0,12C0,5.373 5.373,0 12,0C18.627,0 24,5.373 24,12C24,18.627 18.627,24 12,24C5.373,24 0,18.627 0,12Z"
+      android:fillColor="?androidprv:attr/materialColorSurfaceContainerHigh"/>
+  <path
+      android:pathData="M7.607,9.059L6.667,9.999L12,15.332L17.333,9.999L16.393,9.059L12,13.445"
+      android:fillColor="?androidprv:attr/materialColorOnSurface"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_up.xml b/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_up.xml
new file mode 100644
index 0000000..309770d
--- /dev/null
+++ b/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_up.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- TODO(b/273761935): This drawable night variant is identical to the standard drawable. Delete once the drawable cache correctly invalidates for attributes that reference colors that change when the UI mode changes. -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M0,12C0,5.3726 5.3726,0 12,0C18.6274,0 24,5.3726 24,12C24,18.6274 18.6274,24 12,24C5.3726,24 0,18.6274 0,12Z"
+      android:fillColor="?androidprv:attr/materialColorSurfaceContainerHigh"/>
+  <path
+      android:pathData="M16.3934,14.9393L17.3334,13.9993L12.0001,8.666L6.6667,13.9993L7.6068,14.9393L12.0001,10.5527"
+      android:fillColor="?androidprv:attr/materialColorOnSurface"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/brightness_mirror_background.xml b/packages/SystemUI/res/drawable/brightness_mirror_background.xml
index 2095103..b5c181b 100644
--- a/packages/SystemUI/res/drawable/brightness_mirror_background.xml
+++ b/packages/SystemUI/res/drawable/brightness_mirror_background.xml
@@ -15,6 +15,6 @@
   ~ limitations under the License
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="?attr/underSurfaceColor" />
+    <solid android:color="?attr/underSurface" />
     <corners android:radius="@dimen/rounded_slider_background_rounded_corner" />
 </shape>
diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
index 569ee76..95c7778c 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
@@ -24,7 +24,7 @@
             <shape>
                 <size android:height="@dimen/rounded_slider_track_width" />
                 <corners android:radius="@dimen/rounded_slider_track_corner_radius" />
-                <solid android:color="?attr/offStateColor" />
+                <solid android:color="?attr/shadeInactive" />
             </shape>
         </inset>
     </item>
diff --git a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
index 4d9188c..a9e7adf 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
@@ -22,10 +22,17 @@
         android:height="@dimen/rounded_slider_height">
         <shape>
             <size android:height="@dimen/rounded_slider_height" />
-            <solid android:color="?priv-android:attr/colorAccentPrimary" />
+            <solid android:color="?attr/shadeActive" />
             <corners android:radius="@dimen/rounded_slider_corner_radius"/>
         </shape>
     </item>
+    <item>
+        <shape>
+            <corners android:radius="@dimen/rounded_slider_corner_radius" />
+            <size android:height="@dimen/rounded_slider_height" />
+            <solid android:color="@color/brightness_slider_overlay_color" />
+        </shape>
+    </item>
     <item
         android:id="@+id/slider_icon"
         android:gravity="center_vertical|right"
@@ -34,7 +41,7 @@
         android:right="@dimen/rounded_slider_icon_inset">
         <com.android.systemui.util.AlphaTintDrawableWrapper
             android:drawable="@drawable/ic_brightness"
-            android:tint="?android:attr/textColorPrimaryInverse"
+            android:tint="?attr/onShadeActive"
         />
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/controls_list_divider_inset.xml b/packages/SystemUI/res/drawable/controls_list_divider_inset.xml
deleted file mode 100644
index ddfa18c..0000000
--- a/packages/SystemUI/res/drawable/controls_list_divider_inset.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<inset
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:drawable="@drawable/controls_list_divider"
-    android:insetRight="@dimen/control_spinner_padding_horizontal"
-    android:insetLeft="@dimen/control_spinner_padding_horizontal" />
diff --git a/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml b/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml
index 54bdf18..bc1775e 100644
--- a/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml
+++ b/packages/SystemUI/res/drawable/dream_overlay_assistant_attention_indicator.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2023 The Android Open Source Project
   ~
@@ -13,30 +14,22 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="56dp"
-    android:height="24dp"
-    android:viewportWidth="56"
-    android:viewportHeight="24">
-    <path
-        android:pathData="M12,0L44,0A12,12 0,0 1,56 12L56,12A12,12 0,0 1,44 24L12,24A12,12 0,0 1,0 12L0,12A12,12 0,0 1,12 0z"
-        android:fillColor="#ffffff"/>
-    <group
-        android:scaleX="0.8"
-        android:scaleY="0.8"
-        android:translateY="2"
-        android:translateX="18">
-        <path
-            android:pathData="M21.5,9C22.3284,9 23,8.3284 23,7.5C23,6.6716 22.3284,6 21.5,6C20.6716,6 20,6.6716 20,7.5C20,8.3284 20.6716,9 21.5,9Z"
-            android:fillColor="#000000"/>
-        <path
-            android:pathData="M17,14C18.6569,14 20,12.6569 20,11C20,9.3432 18.6569,8 17,8C15.3431,8 14,9.3432 14,11C14,12.6569 15.3431,14 17,14Z"
-            android:fillColor="#000000"/>
-        <path
-            android:pathData="M17,22C18.933,22 20.5,20.433 20.5,18.5C20.5,16.567 18.933,15 17,15C15.067,15 13.5,16.567 13.5,18.5C13.5,20.433 15.067,22 17,22Z"
-            android:fillColor="#000000"/>
-        <path
-            android:pathData="M7,14C10.3137,14 13,11.3137 13,8C13,4.6863 10.3137,2 7,2C3.6863,2 1,4.6863 1,8C1,11.3137 3.6863,14 7,14Z"
-            android:fillColor="#000000"/>
-    </group>
-</vector>
\ No newline at end of file
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@id/background"
+        android:gravity="center">
+        <shape android:shape="oval">
+            <size
+                android:height="24px"
+                android:width="24px"
+                />
+            <solid android:color="#FFFFFFFF" />
+        </shape>
+    </item>
+    <item android:id="@id/icon"
+        android:gravity="center"
+        android:width="20px"
+        android:height="20px"
+        android:drawable="@drawable/ic_person_outline"
+        />
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fgs_dot.xml b/packages/SystemUI/res/drawable/fgs_dot.xml
index 3669e1d..0881d7c 100644
--- a/packages/SystemUI/res/drawable/fgs_dot.xml
+++ b/packages/SystemUI/res/drawable/fgs_dot.xml
@@ -19,5 +19,5 @@
     android:shape="oval"
     android:width="12dp"
     android:height="12dp">
-    <solid android:color="?androidprv:attr/colorAccentTertiary" />
+    <solid android:color="?attr/tertiary" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/controls_list_divider.xml b/packages/SystemUI/res/drawable/global_actions_list_divider.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/controls_list_divider.xml
rename to packages/SystemUI/res/drawable/global_actions_list_divider.xml
diff --git a/packages/SystemUI/res/drawable/global_actions_list_divider_inset.xml b/packages/SystemUI/res/drawable/global_actions_list_divider_inset.xml
new file mode 100644
index 0000000..170bc0d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/global_actions_list_divider_inset.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<inset
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="@drawable/global_actions_list_divider"
+    android:insetRight="@dimen/control_spinner_padding_horizontal"
+    android:insetLeft="@dimen/control_spinner_padding_horizontal" />
diff --git a/packages/SystemUI/res/drawable/ic_expand_more_48dp.xml b/packages/SystemUI/res/drawable/ic_expand_more_48dp.xml
new file mode 100644
index 0000000..c61a300
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_expand_more_48dp.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2023 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48.0dp"
+        android:height="48.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M33.17,17.17L24.0,26.34l-9.17,-9.17L12.0,20.0l12.0,12.0 12.0,-12.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_person_outline.xml b/packages/SystemUI/res/drawable/ic_person_outline.xml
new file mode 100644
index 0000000..d94714e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_person_outline.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/black"
+        android:pathData="M480,480Q414,480 367,433Q320,386 320,320Q320,254 367,207Q414,160 480,160Q546,160 593,207Q640,254 640,320Q640,386 593,433Q546,480 480,480ZM160,800L160,688Q160,654 177.5,625.5Q195,597 224,582Q286,551 350,535.5Q414,520 480,520Q546,520 610,535.5Q674,551 736,582Q765,597 782.5,625.5Q800,654 800,688L800,800L160,800ZM240,720L720,720L720,688Q720,677 714.5,668Q709,659 700,654Q646,627 591,613.5Q536,600 480,600Q424,600 369,613.5Q314,627 260,654Q251,659 245.5,668Q240,677 240,688L240,720ZM480,400Q513,400 536.5,376.5Q560,353 560,320Q560,287 536.5,263.5Q513,240 480,240Q447,240 423.5,263.5Q400,287 400,320Q400,353 423.5,376.5Q447,400 480,400ZM480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320ZM480,720L480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720Q480,720 480,720L480,720L480,720Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml b/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml
new file mode 100644
index 0000000..32e88ab
--- /dev/null
+++ b/packages/SystemUI/res/drawable/immersive_cling_bg_circ.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval" >
+
+    <solid android:color="?android:attr/colorBackground" />
+
+    <size
+        android:height="56dp"
+        android:width="56dp" />
+
+</shape>
diff --git a/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml b/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml
new file mode 100644
index 0000000..12c3e23
--- /dev/null
+++ b/packages/SystemUI/res/drawable/immersive_cling_light_bg_circ.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval" >
+
+    <solid android:color="?android:attr/colorBackground" />
+
+    <size
+        android:height="76dp"
+        android:width="76dp" />
+
+</shape>
diff --git a/packages/SystemUI/res/drawable/media_output_icon_volume.xml b/packages/SystemUI/res/drawable/media_output_icon_volume.xml
index fce4e00..85d608f 100644
--- a/packages/SystemUI/res/drawable/media_output_icon_volume.xml
+++ b/packages/SystemUI/res/drawable/media_output_icon_volume.xml
@@ -3,7 +3,8 @@
     android:height="24dp"
     android:viewportWidth="24"
     android:viewportHeight="24"
-    android:tint="?attr/colorControlNormal">
+    android:tint="?attr/colorControlNormal"
+    android:autoMirrored="true">
   <path
       android:fillColor="@color/media_dialog_item_main_content"
       android:pathData="M14,20.725V18.675Q16.25,18.025 17.625,16.175Q19,14.325 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,15.15 19.05,17.587Q17.1,20.025 14,20.725ZM3,15V9H7L12,4V20L7,15ZM14,16V7.95Q15.125,8.475 15.812,9.575Q16.5,10.675 16.5,12Q16.5,13.325 15.812,14.4Q15.125,15.475 14,16ZM10,8.85 L7.85,11H5V13H7.85L10,15.15ZM7.5,12Z"/>
diff --git a/packages/SystemUI/res/drawable/media_output_title_icon_area.xml b/packages/SystemUI/res/drawable/media_output_title_icon_area.xml
index b937937..a877900 100644
--- a/packages/SystemUI/res/drawable/media_output_title_icon_area.xml
+++ b/packages/SystemUI/res/drawable/media_output_title_icon_area.xml
@@ -17,9 +17,9 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
     <corners
-        android:bottomLeftRadius="28dp"
-        android:topLeftRadius="28dp"
-        android:bottomRightRadius="0dp"
-        android:topRightRadius="0dp"/>
+        android:bottomLeftRadius="@dimen/media_output_dialog_icon_left_radius"
+        android:topLeftRadius="@dimen/media_output_dialog_icon_left_radius"
+        android:bottomRightRadius="@dimen/media_output_dialog_icon_right_radius"
+        android:topRightRadius="@dimen/media_output_dialog_icon_right_radius"/>
     <solid android:color="@color/media_dialog_item_background" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_avd.xml b/packages/SystemUI/res/drawable/pin_dot_avd.xml
index 1c16251..710ba83 100644
--- a/packages/SystemUI/res/drawable/pin_dot_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_avd.xml
@@ -1 +1,40 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_0_G" android:translateX="28.237000000000002" android:translateY="23.112000000000002"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M-13.24 -12.11 C-11.03,-12.11 -9.24,-10.32 -9.24,-8.11 C-9.24,-5.9 -11.03,-4.11 -13.24,-4.11 C-15.44,-4.11 -17.24,-5.9 -17.24,-8.11 C-17.24,-10.32 -15.44,-12.11 -13.24,-12.11c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:height="30dp"
+            android:width="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="28.237000000000002"
+                    android:translateY="23.112000000000002">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:strokeColor="#ffffff"
+                        android:strokeLineCap="round"
+                        android:strokeLineJoin="round"
+                        android:strokeWidth="2"
+                        android:strokeAlpha="1"
+                        android:pathData=" M-13.24 -12.11 C-11.03,-12.11 -9.24,-10.32 -9.24,-8.11 C-9.24,-5.9 -11.03,-4.11 -13.24,-4.11 C-15.44,-4.11 -17.24,-5.9 -17.24,-8.11 C-17.24,-10.32 -15.44,-12.11 -13.24,-12.11c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="translateX"
+                    android:duration="500"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml b/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml
index 0f8703f..72a03bf 100644
--- a/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml
@@ -1 +1,130 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_0_G" android:translateX="28.54" android:translateY="23.54"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0" android:strokeAlpha="1" android:pathData=" M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="150" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="200" android:startOffset="150" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="150" android:startOffset="0" android:valueFrom="M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c " android:valueTo="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="200" android:startOffset="150" android:valueFrom="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueTo="M-13.54 -12.54 C-11.33,-12.54 -9.54,-10.75 -9.54,-8.54 C-9.54,-6.33 -11.33,-4.54 -13.54,-4.54 C-15.75,-4.54 -17.54,-6.33 -17.54,-8.54 C-17.54,-10.75 -15.75,-12.54 -13.54,-12.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="150" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeWidth" android:duration="50" android:startOffset="150" android:valueFrom="0" android:valueTo="2" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="150" android:startOffset="0" android:valueFrom="M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c " android:valueTo="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="200" android:startOffset="150" android:valueFrom="M-13.54 -11.54 C-11.88,-11.54 -10.54,-10.2 -10.54,-8.54 C-10.54,-6.88 -11.88,-5.54 -13.54,-5.54 C-15.2,-5.54 -16.54,-6.88 -16.54,-8.54 C-16.54,-10.2 -15.2,-11.54 -13.54,-11.54c " android:valueTo="M-13.54 -12.54 C-11.33,-12.54 -9.54,-10.75 -9.54,-8.54 C-9.54,-6.33 -11.33,-4.54 -13.54,-4.54 C-15.75,-4.54 -17.54,-6.33 -17.54,-8.54 C-17.54,-10.75 -15.75,-12.54 -13.54,-12.54c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="150"
+                    android:propertyName="scaleX"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="0.43"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="150"
+                    android:propertyName="scaleY"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="0.43"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="fillAlpha"
+                    android:duration="200"
+                    android:startOffset="150"
+                    android:valueFrom="1"
+                    android:valueTo="0"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="200"
+                    android:propertyName="scaleX"
+                    android:startOffset="150"
+                    android:valueFrom="0.65"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="200"
+                    android:propertyName="scaleY"
+                    android:startOffset="150"
+                    android:valueFrom="0.65"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="500"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="30dp"
+            android:height="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:pivotX="-13.54"
+                    android:pivotY="-8.54"
+                    android:scaleX="1"
+                    android:scaleY="1"
+                    android:translateX="28.54"
+                    android:translateY="23.54">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M-13.54 -16.54 C-9.12,-16.54 -5.54,-12.96 -5.54,-8.54 C-5.54,-4.12 -9.12,-0.54 -13.54,-0.54 C-17.96,-0.54 -21.54,-4.12 -21.54,-8.54 C-21.54,-12.96 -17.96,-16.54 -13.54,-16.54c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:pivotX="317.509"
+                    android:pivotY="-826.986"
+                    android:scaleX="0.65"
+                    android:scaleY="0.65"
+                    android:translateX="-302.509"
+                    android:translateY="841.986">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M317.51 -821.99 C314.75,-821.99 312.51,-824.23 312.51,-826.99 C312.51,-829.74 314.75,-831.99 317.51,-831.99 C320.27,-831.99 322.51,-829.74 322.51,-826.99 C322.51,-824.23 320.27,-821.99 317.51,-821.99z  M317.51 -829.99 C315.86,-829.99 314.51,-828.64 314.51,-826.99 C314.51,-825.33 315.86,-823.99 317.51,-823.99 C319.16,-823.99 320.51,-825.33 320.51,-826.99 C320.51,-828.64 319.16,-829.99 317.51,-829.99z " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml
index f1fb2aa..02abb99 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml
@@ -1 +1,141 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.441" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-13.65 -3.95 C-16.31,-10.09 -10.09,-16.31 -3.95,-13.65 C-3.95,-13.65 -2.94,-13.21 -2.94,-13.21 C-1.06,-12.39 1.06,-12.39 2.94,-13.21 C2.94,-13.21 3.95,-13.65 3.95,-13.65 C10.09,-16.31 16.31,-10.09 13.65,-3.95 C13.65,-3.95 13.21,-2.94 13.21,-2.94 C12.39,-1.06 12.39,1.06 13.21,2.94 C13.21,2.94 13.65,3.95 13.65,3.95 C16.31,10.09 10.09,16.31 3.95,13.65 C3.95,13.65 2.94,13.21 2.94,13.21 C1.06,12.39 -1.06,12.39 -2.94,13.21 C-2.94,13.21 -3.95,13.65 -3.95,13.65 C-10.09,16.31 -16.31,10.09 -13.65,3.95 C-13.65,3.95 -13.21,2.94 -13.21,2.94 C-12.39,1.06 -12.39,-1.06 -13.21,-2.94 C-13.21,-2.94 -13.65,-3.95 -13.65,-3.95c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " android:valueTo="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.3" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.3" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "
+                    android:valueTo="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="283"
+                    android:propertyName="pathData"
+                    android:startOffset="67"
+                    android:valueFrom="M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c "
+                    android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="0"
+                    android:propertyName="scaleY"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="scaleX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="scaleY"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="283"
+                    android:propertyName="scaleX"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.3"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="283"
+                    android:propertyName="scaleY"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.3"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="500"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="30dp"
+            android:height="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:scaleY="0"
+                    android:translateX="15.441"
+                    android:translateY="15.691">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M-0.44 -12.06 C13.5,-14.63 13.5,-14.63 10.9,-0.69 C13.5,13.25 13.5,13.25 -0.44,10.53 C-14.38,13.25 -14.38,13.25 -11.86,-0.69 C-14.38,-14.63 -14.38,-14.63 -0.44,-12.06c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="15">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M-13.65 -3.95 C-16.31,-10.09 -10.09,-16.31 -3.95,-13.65 C-3.95,-13.65 -2.94,-13.21 -2.94,-13.21 C-1.06,-12.39 1.06,-12.39 2.94,-13.21 C2.94,-13.21 3.95,-13.65 3.95,-13.65 C10.09,-16.31 16.31,-10.09 13.65,-3.95 C13.65,-3.95 13.21,-2.94 13.21,-2.94 C12.39,-1.06 12.39,1.06 13.21,2.94 C13.21,2.94 13.65,3.95 13.65,3.95 C16.31,10.09 10.09,16.31 3.95,13.65 C3.95,13.65 2.94,13.21 2.94,13.21 C1.06,12.39 -1.06,12.39 -2.94,13.21 C-2.94,13.21 -3.95,13.65 -3.95,13.65 C-10.09,16.31 -16.31,10.09 -13.65,3.95 C-13.65,3.95 -13.21,2.94 -13.21,2.94 C-12.39,1.06 -12.39,-1.06 -13.21,-2.94 C-13.21,-2.94 -13.65,-3.95 -13.65,-3.95c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml
index 3717db8..1607327 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml
@@ -1 +1,148 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.397" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="13.205" android:pivotY="1.795" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " android:valueTo="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " android:valueTo="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " android:valueTo="M6.12 0.21 C6.12,0.21 2.27,-4.35 2.27,-4.35 C1.34,-6.2 -1.3,-6.2 -2.23,-4.35 C-2.23,-4.35 -6.06,0.21 -6.06,0.21 C-7.12,2.33 -5.46,5.79 -4.03,6.54 C-2.28,7.45 -1.01,7.48 -1.01,7.48 C-1.01,7.48 1.05,7.48 1.05,7.48 C1.05,7.48 3.28,7.36 4.23,6.66 C4.92,6.15 7.18,2.33 6.12,0.21c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:height="30dp"
+            android:width="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="15.397"
+                    android:translateY="15.691"
+                    android:scaleY="0">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="13.205"
+                    android:pivotY="1.795">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "
+                    android:valueTo="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M-0.56 -14.03 C3.65,-13.99 14.58,7.64 11.51,10.42 C8.45,13.2 5.92,9.56 -0.46,9.61 C-6.85,9.65 -9.27,12.76 -12.33,10.46 C-15.39,8.15 -4.77,-14.07 -0.56,-14.03c "
+                    android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="0"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "
+                    android:valueTo="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M12.78 7.57 C12.78,7.57 4.72,-8.55 4.72,-8.55 C2.77,-12.44 -2.77,-12.44 -4.72,-8.55 C-4.72,-8.55 -12.78,7.57 -12.78,7.57 C-15,12.01 -10.42,16.78 -5.89,14.74 C-5.89,14.74 -2.17,13.07 -2.17,13.07 C-0.79,12.45 0.79,12.45 2.17,13.07 C2.17,13.07 5.9,14.74 5.9,14.74 C10.42,16.78 15,12.01 12.78,7.57c "
+                    android:valueTo="M6.12 0.21 C6.12,0.21 2.27,-4.35 2.27,-4.35 C1.34,-6.2 -1.3,-6.2 -2.23,-4.35 C-2.23,-4.35 -6.06,0.21 -6.06,0.21 C-7.12,2.33 -5.46,5.79 -4.03,6.54 C-2.28,7.45 -1.01,7.48 -1.01,7.48 C-1.01,7.48 1.05,7.48 1.05,7.48 C1.05,7.48 3.28,7.36 4.23,6.66 C4.92,6.15 7.18,2.33 6.12,0.21c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.833,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="translateX"
+                    android:duration="500"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml
index 95b8044..78e2249 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml
@@ -1 +1,141 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.441" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M10.71 10.71 C5.92,15.5 -1.85,15.5 -6.64,10.71 C-6.64,10.71 -10.71,6.64 -10.71,6.64 C-15.5,1.85 -15.5,-5.92 -10.71,-10.71 C-5.92,-15.5 1.85,-15.5 6.64,-10.71 C6.64,-10.71 10.71,-6.64 10.71,-6.64 C15.5,-1.85 15.5,5.92 10.71,10.71c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " android:valueTo="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.4" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.4" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:height="30dp"
+            android:width="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="15.441"
+                    android:translateY="15.691"
+                    android:scaleY="0">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="15">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M10.71 10.71 C5.92,15.5 -1.85,15.5 -6.64,10.71 C-6.64,10.71 -10.71,6.64 -10.71,6.64 C-15.5,1.85 -15.5,-5.92 -10.71,-10.71 C-5.92,-15.5 1.85,-15.5 6.64,-10.71 C6.64,-10.71 10.71,-6.64 10.71,-6.64 C15.5,-1.85 15.5,5.92 10.71,10.71c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "
+                    android:valueTo="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M-10.2 -11.16 C-1.58,-18.94 4.25,-12.72 8.06,-8.64 C11.88,-4.57 17.93,1.89 9.39,9.74 C0.85,17.6 -5.06,11.3 -8.87,7.22 C-12.69,3.14 -18.81,-3.39 -10.2,-11.16c "
+                    android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="0"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.4"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.4"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="translateX"
+                    android:duration="500"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml
index 8ea8f85..35c7210 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml
@@ -1 +1,119 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.441" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.44 -8.69 C3.97,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.72 3.97,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.72 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c " android:valueTo="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c " android:valueTo="M7.73 0 C7.73,-1.3 7.71,-1.88 7.16,-2.92 C6.62,-3.95 6.39,-4.63 5.47,-5.47 C4.55,-6.31 4.06,-6.74 3.04,-7.11 C2.02,-7.48 1.35,-7.73 0,-7.73 C-1.34,-7.73 -2.1,-7.52 -3.02,-7.12 C-3.93,-6.73 -4.52,-6.41 -5.47,-5.47 C-6.42,-4.53 -6.75,-3.96 -7.12,-3.02 C-7.52,-2.01 -7.73,-1.35 -7.73,0 C-7.73,1.35 -7.6,1.91 -7.22,2.77 C-6.85,3.63 -6.46,4.51 -5.47,5.47 C-4.48,6.43 -3.87,6.73 -3.02,7.12 C-2.17,7.52 -1.28,7.73 0,7.73 C1.28,7.73 1.8,7.62 2.81,7.21 C3.81,6.8 4.69,6.41 5.47,5.47 C6.25,4.53 6.55,4.3 7.05,3.19 C7.55,2.07 7.73,1.3 7.73,0c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="0"
+                    android:propertyName="scaleY"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="pathData"
+                    android:startOffset="0"
+                    android:valueFrom="M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c "
+                    android:valueTo="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.833,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="283"
+                    android:propertyName="pathData"
+                    android:startOffset="67"
+                    android:valueFrom="M15 0 C15,-2.52 11.99,-2.3 10.94,-4.32 C9.89,-6.33 12.4,-8.98 10.61,-10.61 C8.82,-12.23 6.44,-10.15 4.46,-10.86 C2.48,-11.58 2.61,-15 0,-15 C-2.61,-15 -2.68,-11.61 -4.46,-10.84 C-6.23,-10.08 -8.76,-12.44 -10.61,-10.61 C-12.45,-8.78 -10.31,-6.69 -10.87,-4.64 C-11.43,-2.61 -15,-2.61 -15,0 C-15,2.62 -11.67,2.75 -10.94,4.42 C-10.21,6.08 -12.53,8.74 -10.61,10.61 C-8.68,12.47 -6.18,10.19 -4.54,10.96 C-2.89,11.72 -2.48,15 0,15 C2.48,15 2.49,11.78 4.45,10.99 C6.4,10.19 9.09,12.43 10.61,10.61 C12.12,8.79 10,6.52 10.97,4.36 C11.94,2.2 15,2.52 15,0c "
+                    android:valueTo="M7.73 0 C7.73,-1.3 7.71,-1.88 7.16,-2.92 C6.62,-3.95 6.39,-4.63 5.47,-5.47 C4.55,-6.31 4.06,-6.74 3.04,-7.11 C2.02,-7.48 1.35,-7.73 0,-7.73 C-1.34,-7.73 -2.1,-7.52 -3.02,-7.12 C-3.93,-6.73 -4.52,-6.41 -5.47,-5.47 C-6.42,-4.53 -6.75,-3.96 -7.12,-3.02 C-7.52,-2.01 -7.73,-1.35 -7.73,0 C-7.73,1.35 -7.6,1.91 -7.22,2.77 C-6.85,3.63 -6.46,4.51 -5.47,5.47 C-4.48,6.43 -3.87,6.73 -3.02,7.12 C-2.17,7.52 -1.28,7.73 0,7.73 C1.28,7.73 1.8,7.62 2.81,7.21 C3.81,6.8 4.69,6.41 5.47,5.47 C6.25,4.53 6.55,4.3 7.05,3.19 C7.55,2.07 7.73,1.3 7.73,0c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="scaleX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="67"
+                    android:propertyName="scaleY"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="500"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="30dp"
+            android:height="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:scaleY="0"
+                    android:translateX="15.441"
+                    android:translateY="15.691">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M-0.44 -8.69 C3.97,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.72 3.97,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.72 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="15">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M8 0 C8,-1.35 7.97,-1.94 7.41,-3.02 C6.85,-4.09 6.61,-4.79 5.66,-5.66 C4.7,-6.52 4.2,-6.97 3.15,-7.36 C2.09,-7.74 1.39,-8 0,-8 C-1.39,-8 -2.18,-7.78 -3.12,-7.37 C-4.07,-6.96 -4.67,-6.63 -5.66,-5.66 C-6.64,-4.68 -6.98,-4.1 -7.37,-3.13 C-7.78,-2.08 -8,-1.39 -8,0 C-8,1.4 -7.86,1.98 -7.47,2.87 C-7.08,3.76 -6.68,4.66 -5.66,5.66 C-4.63,6.65 -4,6.96 -3.12,7.37 C-2.25,7.78 -1.32,8 0,8 C1.32,8 1.86,7.88 2.9,7.46 C3.95,7.03 4.85,6.63 5.66,5.66 C6.46,4.69 6.78,4.45 7.29,3.29 C7.81,2.14 8,1.35 8,0c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml
index 3779c80..9458b2e 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml
@@ -1 +1,148 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.397" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="17.64" android:pivotY="-2.64" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " android:valueTo="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " android:valueTo="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " android:valueTo="M-2.99 -8.52 C-1.33,-9.83 1.52,-9.34 3.25,-8.28 C4.98,-7.22 6.77,-5.88 6.7,-2.4 C6.64,1.08 4.48,2.26 3.2,3.05 C1.92,3.83 -1.1,3.72 -3.03,2.76 C-4.97,1.79 -6.38,0.3 -6.37,-2.59 C-6.36,-5.49 -4.65,-7.2 -2.99,-8.52c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:height="30dp"
+            android:width="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="15.397"
+                    android:translateY="15.691"
+                    android:scaleY="0">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="17.64"
+                    android:pivotY="-2.64">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "
+                    android:valueTo="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M-0.48 -12.86 C2.96,-12.87 14.59,5.93 11.98,9.12 C9.42,12.26 5.76,11.36 -0.48,11.41 C-6.72,11.45 -10.24,11.91 -12.78,9.16 C-15.4,6.32 -3.91,-12.85 -0.48,-12.86c "
+                    android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="0"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "
+                    android:valueTo="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M-4.68 -13.34 C-2.59,-17.01 2.64,-17 4.72,-13.33 C4.72,-13.33 13.65,2.45 13.65,2.45 C15.72,6.11 13.11,10.66 8.94,10.65 C8.94,10.65 -8.98,10.62 -8.98,10.62 C-13.15,10.61 -15.75,6.05 -13.67,2.4 C-13.67,2.4 -4.68,-13.34 -4.68,-13.34c "
+                    android:valueTo="M-2.99 -8.52 C-1.33,-9.83 1.52,-9.34 3.25,-8.28 C4.98,-7.22 6.77,-5.88 6.7,-2.4 C6.64,1.08 4.48,2.26 3.2,3.05 C1.92,3.83 -1.1,3.72 -3.03,2.76 C-4.97,1.79 -6.38,0.3 -6.37,-2.59 C-6.36,-5.49 -4.65,-7.2 -2.99,-8.52c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.7,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="translateX"
+                    android:duration="500"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml
index ddda94a..06e27df 100644
--- a/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml
+++ b/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml
@@ -1 +1,142 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_1_G" android:translateX="15.397" android:translateY="15.691" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "/></group><group android:name="_R_G_L_0_G" android:translateX="15" android:translateY="15.859" android:pivotY="-0.859" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-2.82 -14.07 C-1.14,-15.29 1.14,-15.29 2.82,-14.07 C2.82,-14.07 7.73,-10.53 7.73,-10.53 C7.73,-10.53 12.58,-7 12.58,-7 C14.29,-5.76 15,-3.55 14.35,-1.54 C14.35,-1.54 12.51,4.14 12.51,4.14 C12.51,4.14 10.64,9.85 10.64,9.85 C9.99,11.84 8.14,13.19 6.05,13.19 C6.05,13.19 0,13.2 0,13.2 C0,13.2 -6.05,13.19 -6.05,13.19 C-8.14,13.19 -9.98,11.84 -10.64,9.85 C-10.64,9.85 -12.51,4.14 -12.51,4.14 C-12.51,4.14 -14.35,-1.54 -14.35,-1.54 C-15,-3.55 -14.29,-5.76 -12.58,-7 C-12.58,-7 -7.73,-10.53 -7.73,-10.53 C-7.73,-10.53 -2.82,-14.07 -2.82,-14.07c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="67" android:startOffset="0" android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " android:valueTo="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="283" android:startOffset="67" android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.35000000000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="283" android:startOffset="67" android:valueFrom="1" android:valueTo="0.35000000000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="367" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
\ No newline at end of file
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector
+            android:height="30dp"
+            android:width="30dp"
+            android:viewportHeight="30"
+            android:viewportWidth="30">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_1_G"
+                    android:translateX="15.397"
+                    android:translateY="15.691"
+                    android:scaleY="0">
+                    <path
+                        android:name="_R_G_L_1_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c " />
+                </group>
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="15"
+                    android:translateY="15.859"
+                    android:pivotY="-0.859">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillColor="#ffffff"
+                        android:fillAlpha="1"
+                        android:fillType="nonZero"
+                        android:pathData=" M-2.82 -14.07 C-1.14,-15.29 1.14,-15.29 2.82,-14.07 C2.82,-14.07 7.73,-10.53 7.73,-10.53 C7.73,-10.53 12.58,-7 12.58,-7 C14.29,-5.76 15,-3.55 14.35,-1.54 C14.35,-1.54 12.51,4.14 12.51,4.14 C12.51,4.14 10.64,9.85 10.64,9.85 C9.99,11.84 8.14,13.19 6.05,13.19 C6.05,13.19 0,13.2 0,13.2 C0,13.2 -6.05,13.19 -6.05,13.19 C-8.14,13.19 -9.98,11.84 -10.64,9.85 C-10.64,9.85 -12.51,4.14 -12.51,4.14 C-12.51,4.14 -14.35,-1.54 -14.35,-1.54 C-15,-3.55 -14.29,-5.76 -12.58,-7 C-12.58,-7 -7.73,-10.53 -7.73,-10.53 C-7.73,-10.53 -2.82,-14.07 -2.82,-14.07c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "
+                    android:valueTo="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="pathData"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="M-0.46 -13.65 C3.05,-13.65 12.63,-6.57 12.9,-3.63 C12.98,-2.7 12.65,12.85 -0.46,12.85 C-13.57,12.85 -13.77,-2.84 -13.76,-3.63 C-13.72,-6.87 -3.96,-13.65 -0.46,-13.65c "
+                    android:valueTo="M-0.44 -8.69 C3.98,-8.69 7.56,-5.11 7.56,-0.69 C7.56,3.73 3.98,7.31 -0.44,7.31 C-4.86,7.31 -8.44,3.73 -8.44,-0.69 C-8.44,-5.11 -4.86,-8.69 -0.44,-8.69c "
+                    android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.8,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="0"
+                    android:startOffset="67"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="67"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleX"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.35000000000000003"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:propertyName="scaleY"
+                    android:duration="283"
+                    android:startOffset="67"
+                    android:valueFrom="1"
+                    android:valueTo="0.35000000000000003"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.7,0 0.6,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:propertyName="translateX"
+                    android:duration="500"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_background_circle.xml b/packages/SystemUI/res/drawable/privacy_dialog_background_circle.xml
new file mode 100644
index 0000000..f63c2ff
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_background_circle.xml
@@ -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.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:colorControlHighlight">
+    <item android:id="@android:id/background">
+        <shape
+            android:shape="oval"
+            android:id="@id/background"
+            android:gravity="center">
+            <size
+                android:height="24dp"
+                android:width="24dp"/>
+            <solid android:color="@android:color/white"/>
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_large_bottom.xml b/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_large_bottom.xml
new file mode 100644
index 0000000..5d5529f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_large_bottom.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_large" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_large" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_small_bottom.xml b/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_small_bottom.xml
new file mode 100644
index 0000000..310b0be
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_small_bottom.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_large" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_large" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_large_bottom.xml b/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_large_bottom.xml
new file mode 100644
index 0000000..e89bdd3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_large_bottom.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_small" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_large"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_large"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_small" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_small_bottom.xml b/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_small_bottom.xml
new file mode 100644
index 0000000..fcf0b1c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_small_bottom.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="?android:colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_small" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <corners
+                android:bottomLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:bottomRightRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topLeftRadius="@dimen/privacy_dialog_background_radius_small"
+                android:topRightRadius="@dimen/privacy_dialog_background_radius_small" />
+            <solid android:color="@android:color/white" />
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_check_icon.xml b/packages/SystemUI/res/drawable/privacy_dialog_check_icon.xml
new file mode 100644
index 0000000..b9f5d60
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_check_icon.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M9.55,18l-5.7,-5.7 1.425,-1.425L9.55,15.15l9.175,-9.175L20.15,7.4z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_default_permission_icon.xml b/packages/SystemUI/res/drawable/privacy_dialog_default_permission_icon.xml
new file mode 100644
index 0000000..ea8f9c2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_default_permission_icon.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M26.0,14.0l-4.0,0.0l0.0,4.0l4.0,0.0l0.0,-4.0zm0.0,8.0l-4.0,0.0l0.0,12.0l4.0,0.0L26.0,22.0zm8.0,-19.98L14.0,2.0c-2.21,0.0 -4.0,1.79 -4.0,4.0l0.0,36.0c0.0,2.21 1.79,4.0 4.0,4.0l20.0,0.0c2.21,0.0 4.0,-1.79 4.0,-4.0L38.0,6.0c0.0,-2.21 -1.79,-3.98 -4.0,-3.98zM34.0,38.0L14.0,38.0L14.0,10.0l20.0,0.0l0.0,28.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_down.xml b/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_down.xml
new file mode 100644
index 0000000..f8b99f4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_down.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M0,12C0,5.373 5.373,0 12,0C18.627,0 24,5.373 24,12C24,18.627 18.627,24 12,24C5.373,24 0,18.627 0,12Z"
+      android:fillColor="?androidprv:attr/materialColorSurfaceContainerHigh"/>
+  <path
+      android:pathData="M7.607,9.059L6.667,9.999L12,15.332L17.333,9.999L16.393,9.059L12,13.445"
+      android:fillColor="?androidprv:attr/materialColorOnSurface"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_up.xml b/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_up.xml
new file mode 100644
index 0000000..ae60d51
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_up.xml
@@ -0,0 +1,30 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M0,12C0,5.3726 5.3726,0 12,0C18.6274,0 24,5.3726 24,12C24,18.6274 18.6274,24 12,24C5.3726,24 0,18.6274 0,12Z"
+      android:fillColor="?androidprv:attr/materialColorSurfaceContainerHigh"/>
+  <path
+      android:pathData="M16.3934,14.9393L17.3334,13.9993L12.0001,8.666L6.6667,13.9993L7.6068,14.9393L12.0001,10.5527"
+      android:fillColor="?androidprv:attr/materialColorOnSurface"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml b/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml
index ea0aafd..e138d09 100644
--- a/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml
+++ b/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml
@@ -15,7 +15,7 @@
 -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android">
     <shape>
-        <solid android:color="?attr/underSurfaceColor"/>
+        <solid android:color="?attr/underSurface"/>
         <corners android:radius="?android:attr/dialogCornerRadius" />
     </shape>
 </inset>
diff --git a/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml b/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml
index ef950fe..f1a24aa 100644
--- a/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml
+++ b/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml
@@ -15,6 +15,6 @@
 -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android">
     <shape>
-        <solid android:color="?attr/underSurfaceColor"/>
+        <solid android:color="?attr/underSurface"/>
     </shape>
 </inset>
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
index 14cb1de9..9bc8b53 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
@@ -15,7 +15,6 @@
   ~ limitations under the License.
   -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
        android:insetTop="@dimen/dialog_button_vertical_inset"
        android:insetBottom="@dimen/dialog_button_vertical_inset">
     <ripple android:color="?android:attr/colorControlHighlight">
@@ -28,7 +27,7 @@
         <item>
             <shape android:shape="rectangle">
                 <corners android:radius="?android:attr/buttonCornerRadius"/>
-                <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+                <solid android:color="@color/qs_dialog_btn_filled_background"/>
                 <padding android:left="@dimen/dialog_button_horizontal_padding"
                          android:top="@dimen/dialog_button_vertical_padding"
                          android:right="@dimen/dialog_button_horizontal_padding"
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
index 0544b871..50405ca 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml
@@ -26,7 +26,7 @@
     <item>
         <shape android:shape="rectangle">
             <corners android:radius="18dp"/>
-            <solid android:color="?androidprv:attr/colorAccentPrimary"/>
+            <solid android:color="@color/qs_dialog_btn_filled_large_background"/>
         </shape>
     </item>
 </ripple>
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
index a47299d..9e9533a 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
@@ -15,7 +15,6 @@
   ~ limitations under the License.
   -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
        android:insetTop="@dimen/dialog_button_vertical_inset"
        android:insetBottom="@dimen/dialog_button_vertical_inset">
     <ripple android:color="?android:attr/colorControlHighlight">
@@ -29,7 +28,7 @@
             <shape android:shape="rectangle">
                 <corners android:radius="?android:attr/buttonCornerRadius"/>
                 <solid android:color="@android:color/transparent"/>
-                <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
+                <stroke android:color="@color/qs_dialog_btn_outline"
                         android:width="1dp"
                 />
                 <padding android:left="@dimen/dialog_button_horizontal_padding"
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
index c8c36b0..4a5d4af 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
@@ -28,7 +28,7 @@
         </item>
         <item>
             <shape android:shape="rectangle">
-                <solid android:color="?attr/offStateColor"/>
+                <solid android:color="?attr/shadeInactive"/>
                 <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
index 6a365000..a8c0349 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
@@ -28,7 +28,7 @@
         </item>
         <item>
             <shape android:shape="rectangle">
-                <solid android:color="?android:attr/colorAccent"/>
+                <solid android:color="?attr/shadeActive"/>
                 <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
             </shape>
         </item>
diff --git a/packages/SystemUI/res/drawable/qs_footer_actions_background.xml b/packages/SystemUI/res/drawable/qs_footer_actions_background.xml
index c9517cd9..a7e8762 100644
--- a/packages/SystemUI/res/drawable/qs_footer_actions_background.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_actions_background.xml
@@ -15,7 +15,7 @@
 -->
 <inset xmlns:android="http://schemas.android.com/apk/res/android">
     <shape>
-        <solid android:color="?attr/underSurfaceColor"/>
+        <solid android:color="?attr/underSurface"/>
         <corners android:topLeftRadius="@dimen/qs_corner_radius"
                  android:topRightRadius="@dimen/qs_corner_radius"/>
     </shape>
diff --git a/packages/SystemUI/res/drawable/qs_security_footer_background.xml b/packages/SystemUI/res/drawable/qs_security_footer_background.xml
index 381af50..0b0055b 100644
--- a/packages/SystemUI/res/drawable/qs_security_footer_background.xml
+++ b/packages/SystemUI/res/drawable/qs_security_footer_background.xml
@@ -29,7 +29,7 @@
         <item>
             <shape android:shape="rectangle">
                 <stroke android:width="1dp"
-                        android:color="?android:attr/colorBackground"/>
+                        android:color="?attr/shadeInactive"/>
                 <corners android:radius="@dimen/qs_security_footer_corner_radius"/>
             </shape>
         </item>
diff --git a/packages/SystemUI/res/drawable/stat_sys_connected_display.xml b/packages/SystemUI/res/drawable/stat_sys_connected_display.xml
new file mode 100644
index 0000000..3f3d6f5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_connected_display.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2023 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:fillColor="@android:color/white"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M320,840L320,760L400,760L400,680L160,680Q127,680 103.5,656.5Q80,633 80,600L80,200Q80,167 103.5,143.5Q127,120 160,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,600Q880,633 856.5,656.5Q833,680 800,680L560,680L560,760L640,760L640,840L320,840ZM160,600L800,600Q800,600 800,600Q800,600 800,600L800,200Q800,200 800,200Q800,200 800,200L160,200Q160,200 160,200Q160,200 160,200L160,600Q160,600 160,600Q160,600 160,600ZM160,600Q160,600 160,600Q160,600 160,600L160,200Q160,200 160,200Q160,200 160,200L160,200Q160,200 160,200Q160,200 160,200L160,600Q160,600 160,600Q160,600 160,600L160,600Z" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml
index 88f13b4..ca7df86 100644
--- a/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml
+++ b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml
@@ -42,7 +42,7 @@
             android:layout_marginBottom="16dp"
             android:scaleType="fitCenter"
             android:src="@null"
-            android:tint="?androidprv:attr/colorAccentPrimaryVariant"
+            android:tint="?androidprv:attr/materialColorPrimary"
             />
 
         <TextView
diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml
index b9b1bb1..82facd0 100644
--- a/packages/SystemUI/res/layout/battery_percentage_view.xml
+++ b/packages/SystemUI/res/layout/battery_percentage_view.xml
@@ -20,7 +20,7 @@
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/battery_percentage_view"
         android:layout_width="wrap_content"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
         android:singleLine="true"
         android:textAppearance="@style/TextAppearance.StatusBar.Clock"
         android:textColor="?android:attr/textColorPrimary"
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index 386c9d6..3a15ae4 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -74,7 +74,7 @@
         android:layout_height="wrap_content"
         android:id="@+id/barrier"
         app:barrierDirection="start"
-        app:constraint_referenced_ids="statusIcons,privacy_container" />
+        app:constraint_referenced_ids="shade_header_system_icons,privacy_container" />
 
     <com.android.systemui.statusbar.policy.Clock
         android:id="@+id/clock"
@@ -108,46 +108,44 @@
     <include
         android:id="@+id/carrier_group"
         layout="@layout/shade_carrier_group"
-        app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
-        android:minHeight="@dimen/large_screen_shade_header_min_height"
-        app:layout_constraintWidth_min="48dp"
         android:layout_width="0dp"
         android:layout_height="0dp"
-        app:layout_constrainedWidth="true"
         android:layout_gravity="end|center_vertical"
         android:layout_marginStart="8dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/shade_header_system_icons"
+        app:layout_constraintHorizontal_bias="1"
         app:layout_constraintStart_toEndOf="@id/date"
-        app:layout_constraintEnd_toStartOf="@id/statusIcons"
-        app:layout_constraintTop_toTopOf="@id/clock"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintHorizontal_bias="1"
-    />
+        app:layout_constraintTop_toTopOf="@id/clock" />
 
-    <com.android.systemui.statusbar.phone.StatusIconContainer
-        android:id="@+id/statusIcons"
-        app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
-        android:paddingEnd="@dimen/signal_cluster_battery_padding"
+    <LinearLayout
+        android:id="@+id/shade_header_system_icons"
         android:layout_width="wrap_content"
-        android:layout_height="@dimen/large_screen_shade_header_min_height"
-        app:layout_constraintStart_toEndOf="@id/carrier_group"
-        app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
-        app:layout_constraintTop_toTopOf="@id/clock"
+        android:layout_height="@dimen/shade_header_system_icons_height"
+        android:clickable="true"
+        android:orientation="horizontal"
+        android:gravity="center_vertical"
+        android:paddingStart="@dimen/shade_header_system_icons_padding_start"
+        android:paddingEnd="@dimen/shade_header_system_icons_padding_end"
+        android:paddingTop="@dimen/shade_header_system_icons_padding_top"
+        android:paddingBottom="@dimen/shade_header_system_icons_padding_bottom"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintHorizontal_bias="1"
-    />
+        app:layout_constraintEnd_toEndOf="@id/privacy_container"
+        app:layout_constraintTop_toTopOf="@id/clock">
 
-    <com.android.systemui.battery.BatteryMeterView
-        android:id="@+id/batteryRemainingIcon"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/large_screen_shade_header_min_height"
-        app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
-        app:layout_constrainedWidth="true"
-        app:textAppearance="@style/TextAppearance.QS.Status"
-        app:layout_constraintStart_toEndOf="@id/statusIcons"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="@id/clock"
-        app:layout_constraintBottom_toBottomOf="parent"
-    />
+        <com.android.systemui.statusbar.phone.StatusIconContainer
+            android:id="@+id/statusIcons"
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:layout_height="wrap_content"
+            android:paddingEnd="@dimen/signal_cluster_battery_padding" />
+
+        <com.android.systemui.battery.BatteryMeterView
+            android:id="@+id/batteryRemainingIcon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:textAppearance="@style/TextAppearance.QS.Status" />
+    </LinearLayout>
 
     <FrameLayout
         android:id="@+id/privacy_container"
diff --git a/packages/SystemUI/res/layout/connected_display_chip.xml b/packages/SystemUI/res/layout/connected_display_chip.xml
new file mode 100644
index 0000000..d9df91e
--- /dev/null
+++ b/packages/SystemUI/res/layout/connected_display_chip.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+     Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/connected_display_chip"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_vertical|end"
+    tools:parentTag="com.android.systemui.statusbar.ConnectedDisplayChip">
+
+
+    <FrameLayout
+        android:id="@+id/icons_rounded_container"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/ongoing_appops_chip_height"
+        android:layout_gravity="center"
+        android:background="@drawable/statusbar_chip_bg"
+        android:clipToOutline="true"
+        android:clipToPadding="false"
+        android:gravity="center"
+        android:maxWidth="@dimen/ongoing_appops_chip_max_width"
+        android:minWidth="@dimen/ongoing_appops_chip_min_width">
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginHorizontal="10dp"
+            android:scaleType="centerInside"
+            android:src="@drawable/stat_sys_connected_display"
+            android:tint="@android:color/black" />
+    </FrameLayout>
+</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml
index e1dbe69..f1939bb 100644
--- a/packages/SystemUI/res/layout/controls_base_item.xml
+++ b/packages/SystemUI/res/layout/controls_base_item.xml
@@ -25,7 +25,6 @@
     android:focusable="true"
     android:screenReaderFocusable="true"
     android:stateListAnimator="@anim/control_state_list_animator"
-    android:layout_marginStart="@dimen/control_spacing"
     android:background="@drawable/control_background">
 
     <ImageView
diff --git a/packages/SystemUI/res/layout/controls_list_view.xml b/packages/SystemUI/res/layout/controls_list_view.xml
new file mode 100644
index 0000000..2831cbf
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_list_view.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/controls_list"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:divider="@drawable/controls_list_divider"
+    android:orientation="vertical"
+    android:showDividers="middle" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_row.xml b/packages/SystemUI/res/layout/controls_row.xml
index 4cc461a..4923b05 100644
--- a/packages/SystemUI/res/layout/controls_row.xml
+++ b/packages/SystemUI/res/layout/controls_row.xml
@@ -14,9 +14,9 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="horizontal"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:layout_marginBottom="@dimen/control_spacing" />
+    android:divider="@drawable/controls_list_divider"
+    android:orientation="horizontal"
+    android:showDividers="middle" />
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index b1259e4..c60fb26 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -20,7 +20,7 @@
 
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
+        android:layout_height="@dimen/controls_header_menu_size"
         android:paddingHorizontal="@dimen/controls_header_horizontal_padding"
         android:layout_marginBottom="@dimen/controls_header_bottom_margin"
         android:orientation="horizontal">
@@ -85,10 +85,11 @@
         android:layout_weight="1"
         android:clipChildren="true"
         android:orientation="vertical"
-        android:paddingHorizontal="16dp"
+        android:padding="@dimen/controls_content_padding"
+        android:background="@drawable/controls_panel_background"
         android:scrollbars="none">
 
-        <include layout="@layout/global_actions_controls_list_view" />
+        <include layout="@layout/controls_list_view" />
 
     </ScrollView>
 
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index 8bff1a1..6de10b4 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -14,34 +14,27 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
 -->
-<FrameLayout
+<com.android.systemui.shared.shadow.DoubleShadowTextClock
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="wrap_content"
-    android:layout_height="wrap_content">
-
-    <com.android.systemui.shared.shadow.DoubleShadowTextClock
-        android:id="@+id/time_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:fontFamily="@*android:string/config_clockFontFamily"
-        android:textColor="@android:color/white"
-        android:format12Hour="@string/dream_time_complication_12_hr_time_format"
-        android:format24Hour="@string/dream_time_complication_24_hr_time_format"
-        android:fontFeatureSettings="pnum, lnum"
-        android:includeFontPadding="false"
-        android:letterSpacing="0.02"
-        android:maxLines="1"
-        android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"
-        app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius"
-        app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx"
-        app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy"
-        app:keyShadowAlpha="0.3"
-        app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius"
-        app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx"
-        app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy"
-        app:ambientShadowAlpha="0.3"
-        app:removeTextDescent="true"
-        app:textDescentExtraPadding="@dimen/dream_overlay_clock_text_descent_extra_padding" />
-
-</FrameLayout>
+    android:layout_height="wrap_content"
+    android:fontFamily="@*android:string/config_clockFontFamily"
+    android:textColor="@android:color/white"
+    android:format12Hour="@string/dream_time_complication_12_hr_time_format"
+    android:format24Hour="@string/dream_time_complication_24_hr_time_format"
+    android:fontFeatureSettings="pnum, lnum"
+    android:includeFontPadding="false"
+    android:letterSpacing="0.02"
+    android:maxLines="1"
+    android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"
+    app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius"
+    app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx"
+    app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy"
+    app:keyShadowAlpha="0.3"
+    app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius"
+    app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx"
+    app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy"
+    app:ambientShadowAlpha="0.3"
+    app:removeTextDescent="true"
+    app:textDescentExtraPadding="@dimen/dream_overlay_clock_text_descent_extra_padding" />
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index bb32022..8241070 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -110,7 +110,7 @@
 
         <ImageView
             android:id="@+id/dream_overlay_assistant_attention_indicator"
-            android:layout_width="@dimen/dream_overlay_grey_chip_width"
+            android:layout_width="@dimen/dream_overlay_status_bar_icon_size"
             android:layout_height="match_parent"
             android:layout_marginStart="@dimen/dream_overlay_status_icon_margin"
             android:src="@drawable/dream_overlay_assistant_attention_indicator"
diff --git a/packages/SystemUI/res/layout/global_actions_controls_list_view.xml b/packages/SystemUI/res/layout/global_actions_controls_list_view.xml
deleted file mode 100644
index e1c2611..0000000
--- a/packages/SystemUI/res/layout/global_actions_controls_list_view.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/global_actions_controls_list"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical"
-    android:layout_marginLeft="@dimen/global_actions_side_margin"
-    android:layout_marginRight="@dimen/global_actions_side_margin" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_actions_power_dialog.xml b/packages/SystemUI/res/layout/global_actions_power_dialog.xml
index ff3f0fb..3456515 100644
--- a/packages/SystemUI/res/layout/global_actions_power_dialog.xml
+++ b/packages/SystemUI/res/layout/global_actions_power_dialog.xml
@@ -18,7 +18,7 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:orientation="vertical"
-    android:divider="@drawable/controls_list_divider"
+    android:divider="@drawable/global_actions_list_divider"
     android:showDividers="middle"
 />
 
diff --git a/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml b/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml
index bacb5c1..49744e7 100644
--- a/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml
+++ b/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml
@@ -27,8 +27,8 @@
          view. -->
     <Space
         android:id="@+id/icon_placeholder"
-        android:layout_width="@dimen/status_bar_icon_drawing_size"
-        android:layout_height="@dimen/status_bar_icon_drawing_size"
+        android:layout_width="@dimen/status_bar_icon_size_sp"
+        android:layout_height="@dimen/status_bar_icon_size_sp"
         android:layout_gravity="center_vertical"
     />
     <TextView
diff --git a/packages/SystemUI/res/layout/immersive_mode_cling.xml b/packages/SystemUI/res/layout/immersive_mode_cling.xml
new file mode 100644
index 0000000..e6529b9
--- /dev/null
+++ b/packages/SystemUI/res/layout/immersive_mode_cling.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical"
+        android:paddingBottom="24dp">
+
+    <FrameLayout
+            android:id="@+id/immersive_cling_chevron"
+            android:layout_width="76dp"
+            android:layout_height="76dp"
+            android:layout_marginTop="-24dp"
+            android:layout_centerHorizontal="true">
+
+        <ImageView
+                android:id="@+id/immersive_cling_back_bg_light"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:scaleType="center"
+                android:src="@drawable/immersive_cling_light_bg_circ" />
+
+        <ImageView
+                android:id="@+id/immersive_cling_back_bg"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:scaleType="center"
+                android:src="@drawable/immersive_cling_bg_circ" />
+
+        <ImageView
+                android:id="@+id/immersive_cling_ic_expand_more"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:paddingTop="8dp"
+                android:scaleType="center"
+                android:src="@drawable/ic_expand_more_48dp"/>
+    </FrameLayout>
+
+    <TextView
+            android:id="@+id/immersive_cling_title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/immersive_cling_chevron"
+            android:paddingEnd="48dp"
+            android:paddingStart="48dp"
+            android:paddingTop="40dp"
+            android:text="@string/immersive_cling_title"
+            android:textColor="?android:attr/textColorPrimaryInverse"
+            android:textSize="24sp" />
+
+    <TextView
+            android:id="@+id/immersive_cling_description"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/immersive_cling_title"
+            android:paddingEnd="48dp"
+            android:paddingStart="48dp"
+            android:paddingTop="12.6dp"
+            android:text="@string/immersive_cling_description"
+            android:textColor="?android:attr/textColorPrimaryInverse"
+            android:textSize="16sp" />
+
+    <Button
+            android:id="@+id/ok"
+            style="@android:style/Widget.Material.Button.Borderless"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentEnd="true"
+            android:layout_below="@+id/immersive_cling_description"
+            android:layout_marginEnd="40dp"
+            android:layout_marginTop="18dp"
+            android:paddingEnd="8dp"
+            android:paddingStart="8dp"
+            android:text="@string/immersive_cling_positive"
+            android:textColor="?android:attr/textColorPrimaryInverse"
+            android:textSize="14sp" />
+
+</RelativeLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index a3a7135..66c57fc 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -23,7 +23,7 @@
     android:outlineProvider="none" >
 
     <LinearLayout
-        android:id="@+id/keyguard_indication_area"
+        android:id="@id/keyguard_indication_area"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginBottom="@dimen/keyguard_indication_margin_bottom"
@@ -31,7 +31,7 @@
         android:orientation="vertical">
 
         <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
-            android:id="@+id/keyguard_indication_text"
+            android:id="@id/keyguard_indication_text"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:gravity="center"
@@ -41,13 +41,12 @@
             android:accessibilityLiveRegion="polite"/>
 
         <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
-            android:id="@+id/keyguard_indication_text_bottom"
+            android:id="@id/keyguard_indication_text_bottom"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:gravity="center"
-            android:minHeight="48dp"
+            android:minHeight="@dimen/keyguard_indication_text_min_height"
             android:layout_gravity="center_horizontal"
-            android:layout_centerHorizontal="true"
             android:paddingStart="@dimen/keyguard_indication_text_padding"
             android:paddingEnd="@dimen/keyguard_indication_text_padding"
             android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
index 07c428b..6194311 100644
--- a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
+++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
@@ -24,7 +24,7 @@
     android:layout_gravity="end">
     <!-- We add a background behind the UserAvatarView with the same color and with a circular shape
          so that this view can be expanded into a Dialog or an Activity. -->
-    <com.android.systemui.animation.LaunchableFrameLayout
+    <com.android.systemui.animation.view.LaunchableFrameLayout
         android:id="@+id/kg_multi_user_avatar_with_background"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
@@ -42,5 +42,5 @@
             systemui:framePadding="0dp"
             systemui:frameWidth="0dp">
         </com.android.systemui.statusbar.phone.UserAvatarView>
-    </com.android.systemui.animation.LaunchableFrameLayout>
+    </com.android.systemui.animation.view.LaunchableFrameLayout>
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
index 65ee8b3..636f479 100644
--- a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
+++ b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
@@ -25,13 +25,13 @@
     android:orientation="horizontal"
     android:gravity="center_vertical"
     android:background="@drawable/keyguard_settings_popup_menu_background"
-    android:padding="12dp">
+    android:padding="@dimen/keyguard_settings_popup_menu_padding">
 
     <ImageView
         android:id="@+id/icon"
-        android:layout_width="24dp"
-        android:layout_height="24dp"
-        android:layout_marginEnd="8dp"
+        android:layout_width="@dimen/keyguard_settings_popup_menu_icon_height"
+        android:layout_height="@dimen/keyguard_settings_popup_menu_icon_width"
+        android:layout_marginEnd="@dimen/keyguard_settings_popup_menu_icon_end_margin"
         android:tint="?androidprv:attr/materialColorOnSecondaryFixed"
         android:importantForAccessibility="no"
         tools:ignore="UseAppTint" />
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index 64c4eff..fc0bf24 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -47,7 +47,10 @@
             android:layout_height="match_parent"
             android:layout_marginEnd="@dimen/status_bar_padding_end"
             android:gravity="center_vertical|end">
-            <include layout="@layout/system_icons" />
+            <include layout="@layout/system_icons"
+                android:layout_gravity="center_vertical"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
         </FrameLayout>
 
         <ImageView android:id="@+id/multi_user_avatar"
diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
deleted file mode 100644
index 9304ff7..0000000
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ /dev/null
@@ -1,136 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<!-- Layout for media recommendations inside QSPanel carousel -->
-<!-- See media_recommendation_expanded.xml and media_recommendation_collapsed.xml for the
-     constraints. -->
-<com.android.systemui.util.animation.TransitionLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/media_recommendations"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:clipChildren="false"
-    android:clipToPadding="false"
-    android:forceHasOverlappingRendering="false"
-    android:background="@drawable/qs_media_background"
-    android:theme="@style/MediaPlayer">
-
-    <!-- This view just ensures the full media player is a certain height. -->
-    <View
-        android:id="@+id/sizing_view"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/qs_media_session_height_expanded" />
-
-    <com.android.internal.widget.CachingIconView
-        android:id="@+id/recommendation_card_icon"
-        android:layout_width="@dimen/qs_media_app_icon_size"
-        android:layout_height="@dimen/qs_media_app_icon_size"
-        android:minWidth="@dimen/qs_media_app_icon_size"
-        android:minHeight="@dimen/qs_media_app_icon_size"
-        android:layout_marginStart="@dimen/qs_media_padding"
-        android:layout_marginTop="@dimen/qs_media_rec_icon_top_margin"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-
-    <FrameLayout
-        android:id="@+id/media_cover1_container"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        >
-        <ImageView
-            android:id="@+id/media_cover1"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:minWidth="@dimen/qs_media_rec_album_size"
-            android:minHeight="@dimen/qs_media_rec_album_size"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            android:adjustViewBounds="true"
-            android:background="@drawable/bg_smartspace_media_item"
-            style="@style/MediaPlayer.Recommendation.Album"
-            android:clipToOutline="true"
-            android:scaleType="centerCrop"/>
-    </FrameLayout>
-
-    <TextView
-        android:id="@+id/media_title1"
-        style="@style/MediaPlayer.Recommendation.Text.Title"
-        />
-
-    <TextView
-        android:id="@+id/media_subtitle1"
-        style="@style/MediaPlayer.Recommendation.Text.Subtitle"
-        />
-
-    <FrameLayout
-        android:id="@+id/media_cover2_container"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        >
-        <ImageView
-            android:id="@+id/media_cover2"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:minWidth="@dimen/qs_media_rec_album_size"
-            android:minHeight="@dimen/qs_media_rec_album_size"
-            android:adjustViewBounds="true"
-            android:background="@drawable/bg_smartspace_media_item"
-            style="@style/MediaPlayer.Recommendation.Album"
-            android:clipToOutline="true"
-            android:scaleType="centerCrop"/>
-    </FrameLayout>
-
-    <TextView
-        android:id="@+id/media_title2"
-        style="@style/MediaPlayer.Recommendation.Text.Title"
-        />
-
-    <TextView
-        android:id="@+id/media_subtitle2"
-        style="@style/MediaPlayer.Recommendation.Text.Subtitle"
-        />
-
-    <FrameLayout
-        android:id="@+id/media_cover3_container"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        >
-        <ImageView
-            android:id="@+id/media_cover3"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:minWidth="@dimen/qs_media_rec_album_size"
-            android:minHeight="@dimen/qs_media_rec_album_size"
-            android:adjustViewBounds="true"
-            android:background="@drawable/bg_smartspace_media_item"
-            style="@style/MediaPlayer.Recommendation.Album"
-            android:clipToOutline="true"
-            android:scaleType="centerCrop"/>
-    </FrameLayout>
-
-    <TextView
-        android:id="@+id/media_title3"
-        style="@style/MediaPlayer.Recommendation.Text.Title"
-        />
-
-    <TextView
-        android:id="@+id/media_subtitle3"
-        style="@style/MediaPlayer.Recommendation.Text.Subtitle"
-        />
-
-    <include
-        layout="@layout/media_long_press_menu" />
-
-</com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/layout/privacy_dialog_card_button.xml b/packages/SystemUI/res/layout/privacy_dialog_card_button.xml
new file mode 100644
index 0000000..e297b93
--- /dev/null
+++ b/packages/SystemUI/res/layout/privacy_dialog_card_button.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<Button
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="56dp"
+    android:layout_marginBottom="4dp"
+    android:ellipsize="end"
+    android:maxLines="1"
+    android:clickable="true"
+    android:focusable="true"
+    android:gravity="center"
+    style="@style/Widget.Dialog.Button.BorderButton"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dialog_item_v2.xml b/packages/SystemUI/res/layout/privacy_dialog_item_v2.xml
new file mode 100644
index 0000000..b84f3a9
--- /dev/null
+++ b/packages/SystemUI/res/layout/privacy_dialog_item_v2.xml
@@ -0,0 +1,89 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<androidx.cardview.widget.CardView
+    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/privacy_dialog_item_card"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="4dp"
+    android:layout_marginBottom="4dp"
+    android:foreground="?android:attr/selectableItemBackground"
+    app:cardCornerRadius="28dp"
+    app:cardElevation="0dp"
+    app:cardBackgroundColor="?androidprv:attr/materialColorSurfaceBright">
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <LinearLayout
+            android:id="@+id/privacy_dialog_item_header"
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="20dp"
+            android:paddingBottom="20dp"
+            android:paddingStart="24dp"
+            android:paddingEnd="24dp">
+            <ImageView
+                android:id="@+id/privacy_dialog_item_header_icon"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_centerVertical="true"
+                android:importantForAccessibility="no" />
+            <LinearLayout
+                android:orientation="vertical"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:layout_height="match_parent"
+                android:layout_marginStart="16dp"
+                android:layout_marginEnd="16dp"
+                android:layout_centerVertical="true">
+                <TextView
+                    android:id="@+id/privacy_dialog_item_header_title"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginBottom="2dp"
+                    android:hyphenationFrequency="normalFast"
+                    android:textAlignment="viewStart"
+                    android:textAppearance="@style/TextAppearance.PrivacyDialog.Item.Title" />
+                <TextView
+                    android:id="@+id/privacy_dialog_item_header_summary"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:textAlignment="viewStart"
+                    android:textAppearance="@style/TextAppearance.PrivacyDialog.Item.Summary" />
+            </LinearLayout>
+            <ImageView
+                android:id="@+id/privacy_dialog_item_header_expand_toggle"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_centerVertical="true"
+                android:visibility="gone" />
+        </LinearLayout>
+        <LinearLayout
+            android:id="@+id/privacy_dialog_item_header_expanded_layout"
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="16dp"
+            android:paddingStart="24dp"
+            android:paddingEnd="24dp"
+            android:visibility="gone">
+        </LinearLayout>
+    </LinearLayout>
+</androidx.cardview.widget.CardView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dialog_v2.xml b/packages/SystemUI/res/layout/privacy_dialog_v2.xml
new file mode 100644
index 0000000..843dad0
--- /dev/null
+++ b/packages/SystemUI/res/layout/privacy_dialog_v2.xml
@@ -0,0 +1,109 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<androidx.core.widget.NestedScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:layout_width="@dimen/large_dialog_width"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="24dp"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="16dp"
+        android:orientation="vertical">
+
+        <!-- Header -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical"
+            android:gravity="center">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="?android:attr/textAppearanceLarge"
+                android:fontFamily="@*android:string/config_headlineFontFamily"
+                android:text="@string/privacy_dialog_title"
+                android:layout_marginBottom="12dp"/>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/privacy_dialog_summary"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+                android:gravity="center"
+                android:layout_marginBottom="20dp"/>
+        </LinearLayout>
+
+        <!-- Items -->
+        <LinearLayout
+            android:id="@+id/privacy_dialog_items_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="20dp"
+            android:orientation="vertical"
+        />
+
+        <!-- Buttons -->
+        <LinearLayout
+            android:id="@+id/button_layout"
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="18dp"
+            android:clickable="false"
+            android:focusable="false">
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:layout_gravity="start|center_vertical"
+                android:orientation="vertical">
+                <Button
+                    android:id="@+id/privacy_dialog_more_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/privacy_dialog_more_button"
+                    android:ellipsize="end"
+                    android:maxLines="1"
+                    style="@style/Widget.Dialog.Button.BorderButton"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:gravity="center"/>
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="end|center_vertical">
+                <Button
+                    android:id="@+id/privacy_dialog_close_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/privacy_dialog_done_button"
+                    android:ellipsize="end"
+                    android:maxLines="1"
+                    style="@style/Widget.Dialog.Button.BorderButton"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:gravity="center"/>
+            </LinearLayout>
+        </LinearLayout>
+    </LinearLayout>
+</androidx.core.widget.NestedScrollView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 745cfc6..b8f4c0f 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -53,6 +53,7 @@
                 android:layout_width="wrap_content"
                 android:layout_height="match_parent"
                 android:layout_gravity="center_vertical"
+                android:tint="?attr/shadeActive"
                 android:visibility="gone" />
 
             <FrameLayout
@@ -70,7 +71,7 @@
                     android:focusable="true"
                     android:padding="@dimen/qs_footer_icon_padding"
                     android:src="@*android:drawable/ic_mode_edit"
-                    android:tint="?android:attr/textColorPrimary" />
+                    android:tint="?attr/onSurfaceVariant" />
             </FrameLayout>
 
         </LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index c124aea..974cad3 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -54,6 +54,6 @@
         android:focusable="false"
         android:importantForAccessibility="no"
         android:textAppearance="@style/TextAppearance.QS.TileLabel.Secondary"
-        android:textColor="?android:attr/textColorSecondary"/>
+        android:textColor="?attr/onShadeInactive"/>
 
 </com.android.systemui.qs.tileimpl.IgnorableChildLinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_tile_side_icon.xml b/packages/SystemUI/res/layout/qs_tile_side_icon.xml
index f1b7259..fbcead1 100644
--- a/packages/SystemUI/res/layout/qs_tile_side_icon.xml
+++ b/packages/SystemUI/res/layout/qs_tile_side_icon.xml
@@ -30,12 +30,11 @@
         android:visibility="gone"
     />
 
-    <ImageView
+    <com.android.systemui.qs.tileimpl.ChevronImageView
         android:id="@+id/chevron"
         android:layout_width="@dimen/qs_icon_size"
         android:layout_height="@dimen/qs_icon_size"
         android:src="@*android:drawable/ic_chevron_end"
-        android:autoMirrored="true"
         android:visibility="gone"
         android:importantForAccessibility="no"
     />
diff --git a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
index e95c6a7..91550b3 100644
--- a/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
+++ b/packages/SystemUI/res/layout/quick_settings_brightness_dialog.xml
@@ -34,5 +34,6 @@
             android:paddingEnd="0dp"
             android:progressDrawable="@drawable/brightness_progress_drawable"
             android:splitTrack="false"
+            android:clickable="true"
         />
     </com.android.systemui.settings.brightness.BrightnessSliderView>
diff --git a/packages/SystemUI/res/layout/scene_window_root.xml b/packages/SystemUI/res/layout/scene_window_root.xml
new file mode 100644
index 0000000..0dcd15b
--- /dev/null
+++ b/packages/SystemUI/res/layout/scene_window_root.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- The root view of the scene window. -->
+<com.android.systemui.scene.ui.view.SceneWindowRootView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:sysui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/scene_window_root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+
+    <include layout="@layout/super_notification_shade"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+</com.android.systemui.scene.ui.view.SceneWindowRootView>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index bbf3adf..f6ce70d 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -15,6 +15,7 @@
   limitations under the License.
   -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical">
@@ -47,7 +48,7 @@
                 <TextView
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
-                    android:textAppearance="?android:attr/textAppearanceLarge"
+                    android:textAppearance="@style/TextAppearance.Dialog.Title"
                     android:fontFamily="@*android:string/config_headlineFontFamily"
                     android:text="@string/screenrecord_permission_dialog_title"
                     android:layout_marginTop="22dp"
@@ -56,8 +57,7 @@
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:text="@string/screenrecord_permission_dialog_warning_entire_screen"
-                    android:textAppearance="?android:attr/textAppearanceSmall"
-                    android:textColor="?android:textColorSecondary"
+                    android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
                     android:gravity="center"
                     android:layout_marginBottom="20dp"/>
 
@@ -81,6 +81,7 @@
                         android:minHeight="48dp"
                         android:layout_weight="1"
                         android:popupBackground="@drawable/screenrecord_spinner_background"
+                        android:textColor="?androidprv:attr/materialColorOnSurface"
                         android:dropDownWidth="274dp"
                         android:prompt="@string/screenrecord_audio_label"/>
                     <Switch
@@ -117,7 +118,7 @@
                         android:text="@string/screenrecord_taps_label"
                         android:textAppearance="?android:attr/textAppearanceMedium"
                         android:fontFamily="@*android:string/config_headlineFontFamily"
-                        android:textColor="?android:attr/textColorPrimary"
+                        android:textColor="?androidprv:attr/materialColorOnSurface"
                         android:importantForAccessibility="no"/>
                     <Switch
                         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/screen_record_options.xml b/packages/SystemUI/res/layout/screen_record_options.xml
index 6cc72dd..d9f4b79 100644
--- a/packages/SystemUI/res/layout/screen_record_options.xml
+++ b/packages/SystemUI/res/layout/screen_record_options.xml
@@ -55,14 +55,13 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
+        android:gravity="center_vertical"
         android:layout_marginTop="@dimen/screenrecord_option_padding">
         <ImageView
             android:layout_width="@dimen/screenrecord_option_icon_size"
             android:layout_height="@dimen/screenrecord_option_icon_size"
-            android:layout_weight="0"
             android:src="@drawable/ic_touch"
             android:tint="?android:attr/textColorSecondary"
-            android:layout_gravity="center_vertical"
             android:layout_marginRight="@dimen/screenrecord_option_padding"
             android:importantForAccessibility="no"/>
         <TextView
@@ -70,7 +69,6 @@
             android:layout_height="wrap_content"
             android:minHeight="48dp"
             android:layout_weight="1"
-            android:gravity="center_vertical"
             android:text="@string/screenrecord_taps_label"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:fontFamily="@*android:string/config_bodyFontFamily"
@@ -80,7 +78,6 @@
             android:layout_width="wrap_content"
             android:minWidth="48dp"
             android:layout_height="48dp"
-            android:layout_weight="0"
             android:id="@+id/screenrecord_taps_switch"
             style="@style/ScreenRecord.Switch"
             android:importantForAccessibility="yes"/>
diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml
index ab522a3..3796415 100644
--- a/packages/SystemUI/res/layout/screen_share_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_share_dialog.xml
@@ -36,14 +36,13 @@
             android:layout_width="@dimen/screenrecord_logo_size"
             android:layout_height="@dimen/screenrecord_logo_size"
             android:src="@drawable/ic_media_projection_permission"
-            android:tint="?androidprv:attr/colorAccentPrimaryVariant"
+            android:tint="?androidprv:attr/materialColorPrimary"
             android:importantForAccessibility="no"/>
         <TextView
             android:id="@+id/screen_share_dialog_title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textAppearance="?android:attr/textAppearanceLarge"
-            android:fontFamily="@*android:string/config_headlineFontFamily"
+            android:textAppearance="@style/TextAppearance.Dialog.Title"
             android:layout_marginTop="@dimen/screenrecord_title_margin_top"
             android:gravity="center"/>
         <Spinner
@@ -64,10 +63,8 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="@string/screenrecord_permission_dialog_warning_entire_screen"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textColor="?android:textColorSecondary"
-            android:gravity="start"
-            android:lineHeight="@dimen/screenrecord_warning_line_height"/>
+            style="@style/TextAppearance.Dialog.Body.Message"
+            android:gravity="start"/>
 
         <!-- Buttons -->
         <LinearLayout
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 331307a0..5132e57 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -32,7 +32,7 @@
 
     <ImageView
         android:id="@+id/notification_lights_out"
-        android:layout_width="@dimen/status_bar_icon_size"
+        android:layout_width="@dimen/status_bar_icon_size_sp"
         android:layout_height="match_parent"
         android:paddingStart="@dimen/status_bar_padding_start"
         android:paddingBottom="2dip"
@@ -67,6 +67,7 @@
                 android:id="@+id/status_bar_start_side_content"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical|start"
                 android:clipChildren="false">
 
                 <include layout="@layout/heads_up_status_bar_layout" />
@@ -88,7 +89,8 @@
                     <com.android.systemui.statusbar.policy.Clock
                         android:id="@+id/clock"
                         android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
+                        android:layout_height="@dimen/status_bar_system_icons_height"
+                        android:layout_gravity="center_vertical"
                         android:textAppearance="@style/TextAppearance.StatusBar.Clock"
                         android:singleLine="true"
                         android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
@@ -146,7 +148,10 @@
                     android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
                     layout="@layout/status_bar_user_chip_container" />
 
-                <include layout="@layout/system_icons" />
+                <include layout="@layout/system_icons"
+                     android:layout_gravity="center_vertical"
+                     android:layout_width="wrap_content"
+                     android:layout_height="@dimen/status_bar_system_icons_height" />
             </com.android.keyguard.AlphaOptimizedLinearLayout>
         </FrameLayout>
     </LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index d710676..e214666 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -132,21 +132,6 @@
         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:src="@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>
 
     <include
diff --git a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
deleted file mode 100644
index d6c63eb..0000000
--- a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 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.
-*/
--->
-<com.android.systemui.statusbar.StatusBarMobileView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/mobile_combo"
-    android:layout_width="wrap_content"
-    android:layout_height="match_parent"
-    android:gravity="center_vertical" >
-
-    <include layout="@layout/status_bar_mobile_signal_group_inner" />
-
-</com.android.systemui.statusbar.StatusBarMobileView>
-
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index db94c92..909048e 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -17,10 +17,9 @@
 <!-- Extends Framelayout -->
 <com.android.systemui.statusbar.notification.row.FooterView
         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="wrap_content"
-        android:paddingStart="16dp"
-        android:paddingEnd="16dp"
         android:visibility="gone">
     <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
         android:id="@+id/content"
@@ -37,31 +36,45 @@
             android:visibility="gone"
             android:textAppearance="?android:attr/textAppearanceButton"
             android:text="@string/unlock_to_see_notif_text"/>
-        <com.android.systemui.statusbar.notification.row.FooterViewButton
-            style="@style/TextAppearance.NotificationSectionHeaderButton"
-            android:id="@+id/manage_text"
-            android:layout_width="wrap_content"
-            android:layout_height="48dp"
-            android:layout_marginTop="12dp"
-            android:layout_gravity="start"
-            android:background="@drawable/notif_footer_btn_background"
-            android:focusable="true"
-            android:textColor="@color/notif_pill_text"
-            android:contentDescription="@string/manage_notifications_history_text"
-            android:text="@string/manage_notifications_history_text"
-        />
-        <com.android.systemui.statusbar.notification.row.FooterViewButton
-            style="@style/TextAppearance.NotificationSectionHeaderButton"
-            android:id="@+id/dismiss_text"
-            android:layout_width="wrap_content"
-            android:layout_height="48dp"
-            android:layout_marginTop="12dp"
-            android:layout_gravity="end"
-            android:background="@drawable/notif_footer_btn_background"
-            android:focusable="true"
-            android:textColor="@color/notif_pill_text"
-            android:contentDescription="@string/accessibility_clear_all"
-            android:text="@string/clear_all_notifications_text"
-        />
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            >
+            <com.android.systemui.statusbar.notification.row.FooterViewButton
+                style="@style/TextAppearance.NotificationSectionHeaderButton"
+                android:id="@+id/manage_text"
+                android:layout_width="wrap_content"
+                android:layout_height="48dp"
+                android:layout_marginTop="12dp"
+                android:layout_marginStart="16dp"
+                app:layout_constraintVertical_bias="0.0"
+                app:layout_constraintHorizontal_chainStyle="spread_inside"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintEnd_toStartOf="@id/dismiss_text"
+                android:background="@drawable/notif_footer_btn_background"
+                android:focusable="true"
+                android:textColor="@color/notif_pill_text"
+                android:contentDescription="@string/manage_notifications_history_text"
+                android:text="@string/manage_notifications_history_text"
+                />
+            <com.android.systemui.statusbar.notification.row.FooterViewButton
+                style="@style/TextAppearance.NotificationSectionHeaderButton"
+                android:id="@+id/dismiss_text"
+                android:layout_width="wrap_content"
+                android:layout_height="48dp"
+                android:layout_marginTop="12dp"
+                android:layout_marginEnd="16dp"
+                app:layout_constraintVertical_bias="1.0"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintStart_toEndOf="@id/manage_text"
+                android:background="@drawable/notif_footer_btn_background"
+                android:focusable="true"
+                android:textColor="@color/notif_pill_text"
+                android:contentDescription="@string/accessibility_clear_all"
+                android:text="@string/clear_all_notifications_text"
+                />
+        </androidx.constraintlayout.widget.ConstraintLayout>
     </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
 </com.android.systemui.statusbar.notification.row.FooterView>
diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group.xml b/packages/SystemUI/res/layout/status_bar_wifi_group.xml
deleted file mode 100644
index 6cb6993..0000000
--- a/packages/SystemUI/res/layout/status_bar_wifi_group.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 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.
-*/
--->
-<com.android.systemui.statusbar.StatusBarWifiView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/wifi_combo"
-    android:layout_width="wrap_content"
-    android:layout_height="match_parent"
-    android:gravity="center_vertical" >
-
-    <include layout="@layout/status_bar_wifi_group_inner" />
-
-</com.android.systemui.statusbar.StatusBarWifiView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
index 0ea0653..4c5cd7d 100644
--- a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
+++ b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
@@ -24,16 +24,17 @@
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:gravity="center_vertical"
-        android:layout_marginStart="2.5dp"
+        android:layout_marginStart="2.5sp"
     >
         <FrameLayout
                 android:id="@+id/inout_container"
-                android:layout_height="17dp"
+                android:layout_height="@dimen/status_bar_wifi_inout_container_size"
                 android:layout_width="wrap_content"
                 android:gravity="center_vertical" >
             <ImageView
                 android:id="@+id/wifi_in"
-                android:layout_height="wrap_content"
+                android:layout_height="@dimen/status_bar_wifi_signal_size"
+                android:adjustViewBounds="true"
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_down"
                 android:visibility="gone"
@@ -41,7 +42,8 @@
             />
             <ImageView
                 android:id="@+id/wifi_out"
-                android:layout_height="wrap_content"
+                android:layout_height="@dimen/status_bar_wifi_signal_size"
+                android:adjustViewBounds="true"
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_up"
                 android:paddingEnd="2dp"
@@ -75,7 +77,7 @@
         <View
             android:id="@+id/wifi_airplane_spacer"
             android:layout_width="@dimen/status_bar_airplane_spacer_width"
-            android:layout_height="4dp"
+            android:layout_height="wrap_content"
             android:visibility="gone"
         />
     </com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index d9fe949..d4b73a4 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -21,6 +21,7 @@
 <com.android.systemui.shade.NotificationShadeWindowView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:sysui="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/legacy_window_root"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:fitsSystemWindows="true">
@@ -69,6 +70,19 @@
              android:layout_height="match_parent"
              android:visibility="invisible" />
 
+    <!-- Root for all keyguard content. It was previously located within the shade. -->
+    <com.android.systemui.keyguard.ui.view.KeyguardRootView
+        android:id="@id/keyguard_root_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <!-- Shared container for the notification stack. Can be positioned by either
+         the keyguard_root_view or notification_panel -->
+    <com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+        android:id="@+id/shared_notification_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
     <include layout="@layout/brightness_mirror_container" />
 
     <com.android.systemui.scrim.ScrimView
diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml
index 816dfd3..6a14c21 100644
--- a/packages/SystemUI/res/layout/system_icons.xml
+++ b/packages/SystemUI/res/layout/system_icons.xml
@@ -18,20 +18,23 @@
               xmlns:systemui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/system_icons"
     android:layout_width="wrap_content"
-    android:layout_height="match_parent"
-    android:layout_gravity="center_vertical|end"
+    android:layout_height="wrap_content"
+    android:paddingBottom="@dimen/status_bar_icons_padding_bottom"
+    android:paddingTop="@dimen/status_bar_icons_padding_top"
+    android:paddingStart="@dimen/status_bar_icons_padding_start"
+    android:paddingEnd="@dimen/status_bar_icons_padding_end"
     android:gravity="center_vertical">
 
     <com.android.systemui.statusbar.phone.StatusIconContainer android:id="@+id/statusIcons"
         android:layout_width="0dp"
         android:layout_weight="1"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
         android:paddingEnd="@dimen/signal_cluster_battery_padding"
         android:gravity="center_vertical"
         android:orientation="horizontal"/>
 
     <com.android.systemui.battery.BatteryMeterView android:id="@+id/battery"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         android:clipToPadding="false"
         android:clipChildren="false"
diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
index 00af7f4..530d752 100644
--- a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
+++ b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml
@@ -16,8 +16,7 @@
   -->
 <com.android.systemui.biometrics.UdfpsKeyguardViewLegacy
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/udfps_animation_view"
+    android:id="@+id/udfps_animation_view_legacy"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index a8febe7..efdb0a3 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -147,10 +147,12 @@
         android:id="@+id/magnifier_zoom_slider"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        app:max="6"
         app:progress="0"
         app:iconStartContentDescription="@string/accessibility_control_zoom_out"
-        app:iconEndContentDescription="@string/accessibility_control_zoom_in"/>
+        app:iconEndContentDescription="@string/accessibility_control_zoom_in"
+        app:tickMark="@android:color/transparent"
+        app:seekBarChangeMagnitude="10"
+    />
 
     <Button
         android:id="@+id/magnifier_done_button"
diff --git a/packages/SystemUI/res/layout/zen_mode_condition.xml b/packages/SystemUI/res/layout/zen_mode_condition.xml
index ab52465..3baae33 100644
--- a/packages/SystemUI/res/layout/zen_mode_condition.xml
+++ b/packages/SystemUI/res/layout/zen_mode_condition.xml
@@ -15,6 +15,7 @@
      limitations under the License.
 -->
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:theme="@style/Theme.SystemUI.QuickSettings"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:clipChildren="false"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 337fcfb..fe80355 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tik om te bekyk"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Kon nie skermopname stoor nie"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Kon nie skermopname begin nie"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Bekyk tans volskerm"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Swiep van bo af as jy wil uitgaan."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Het dit"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Terug"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Tuis"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Kieslys"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Oudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Kopstuk"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Invoer"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Gehoortoestelle"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Gehoortoestelle"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Skakel tans aan …"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Outo-draai"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Outodraai skerm"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g>-limiet"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"<xliff:g id="DATA_LIMIT">%s</xliff:g> waarskuwing"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Werkprogramme"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Onderbreek"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Aandbeligting"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Aan by sonsondergang"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Tot sonsopkoms"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Wanneer jy deel, opneem of uitsaai, het Android toegang tot enigiets wat op jou skerm sigbaar is of op jou toestel gespeel word. Wees dus versigtig met dinge soos wagwoorde, betalingbesonderhede, boodskappe, foto’s, en oudio en video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Wanneer jy ’n app deel, opneem of uitsaai, het Android toegang tot enigiets wat in daardie app gewys of gespeel word. Wees dus versigtig met dinge soos wagwoorde, betalingbesonderhede, boodskappe, foto’s, en oudio en video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Begin"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Deling word onderbreek wanneer jy apps wissel"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Deel eerder hierdie app"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Skakel terug"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Appwisselaar"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Deur jou IT-admin geblokkeer"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skermskote is deur toestelbeleid gedeaktiveer"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Vee alles uit"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Vergroot die hele skerm"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Vergroot \'n deel van die skerm"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Maak vergrotinginstellings oop"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Maak vergrotinginstellings toe"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Sleep hoek om grootte te verander"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Laat diagonale rollees toe"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Verander grootte"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Kon nie jou batterymeter lees nie"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tik vir meer inligting"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Geen wekker nie"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"voer skermslot in"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Vingerafdruksensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"staaf"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"gaan by toestel in"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistent luister"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# kennisgewing}other{# kennisgewings}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Neem notas"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Maak notas"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Maak notas, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Uitsaai"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Hou op om <xliff:g id="APP_NAME">%1$s</xliff:g> uit te saai?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"As jy <xliff:g id="SWITCHAPP">%1$s</xliff:g> uitsaai of die uitvoer verander, sal jou huidige uitsending stop"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Maak <xliff:g id="APPNAME">%1$s</xliff:g> oop"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Om die Wallet-app as ’n kortpad by te voeg, moet jy seker maak dat die app geïnstalleer is"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Om die Wallet-app as ’n kortpad by te voeg, moet jy seker maak dat minstens een kaart bygevoeg is"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Om die QR-kodeskandeerder as ’n kortpad by te voeg, moet jy seker maak dat ’n kamera-app geïnstalleer is"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Om die Home-app as ’n kortpad by te voeg, moet jy seker maak dat die app geïnstalleer is"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Minstens een toestel beskikbaar is"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Kies ’n versteknotasapp om die notaneemkortpad te gebruik"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistent-aandag is aan"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Stel versteknotasapp in Instellings"</string>
     <string name="install_app" msgid="5066668100199613936">"Installeer app"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofoon en kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Onlangse appgebruik"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Sien onlangse toegang"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Klaar"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Vou uit en wys opsies"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Vou in"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Maak hierdie app toe"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> is toegemaak"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Bestuur diens"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Bestuur toegang"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Word gebruik deur foonoproep"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Onlangs gebruik in foonoproep"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Word gebruik deur <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Onlangs gebruik deur <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Word gebruik deur <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Onlangs gebruik deur <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Word gebruik deur <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Onlangs gebruik deur <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 5ef2a15..63f1765 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"ለመመልከት መታ ያድርጉ"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"የማያ ገጽ ቀረጻን ማስቀመጥ ላይ ስህተት"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"የማያ ገፅ ቀረጻን መጀመር ላይ ስህተት"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"በሙሉ ገጽ ዕይታ በማየት ላይ"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ለመውጣት፣ ከላይ ወደታች ያንሸራትቱ።"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"ገባኝ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ተመለስ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"መነሻ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"ምናሌ"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ኦዲዮ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ማዳመጫ"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ግቤት"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"አጋዥ መስሚያዎች"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"መስሚያ አጋዥ መሣሪያዎች"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"በማብራት ላይ..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"በራስ ሰር አሽከርክር"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ማያ ገጽን በራስ-አሽከርክር"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> ገደብ"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"ባለበት ቆሟል"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"እርስዎ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ Android በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ መልዕክቶች፣ ፎቶዎች እና ኦዲዮ እና ቪድዮ ላሉ ነገሮች ጥንቃቄ ያድርጉ።"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"እርስዎ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ Android በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ መልዕክቶች፣ ፎቶዎች እና ኦዲዮ እና ቪድዮ ላሉ ነገሮች ጥንቃቄ ያድርጉ።"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ጀምር"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"መተግበሪያዎችን በሚቀያይሩበት ጊዜ ለአፍታ ማቆሞችን ማጋራት"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"በምትኩ ይህን መተግበሪያ ያጋሩ"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"ተመልሰው ይቀይሩ"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"የመተግበሪያ መቀየሪያ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"በእርስዎ የአይቲ አስተዳዳሪ ታግዷል"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"የማያ ገፅ ቀረጻ በመሣሪያ መመሪያ ተሰናክሏል"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ሁሉንም አጽዳ"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ሙሉ ገፅ እይታን ያጉሉ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"የማያ ገጹን ክፍል አጉላ"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"የማጉያ ቅንብሮችን ክፈት"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"የማጉላት ቅንብሮችን ዝጋ"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"መጠን ለመቀየር ጠርዙን ይዘው ይጎትቱ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ሰያፍ ሽብለላን ፍቀድ"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"መጠን ቀይር"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"የባትሪ መለኪያዎን የማንበብ ችግር"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ለበለጠ መረጃ መታ ያድርጉ"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ምንም ማንቂያ አልተቀናበረም"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ማያ ገጽ መቆለፊያ ያስገቡ"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"የጣት አሻራ ዳሳሽ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ያረጋግጡ"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"መሣሪያን ያስገቡ"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"ረዳት በማዳመጥ ላይ ነው"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ማሳወቂያ}one{# ማሳወቂያዎች}other{# ማሳወቂያዎች}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>፣ <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetaking"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"የማስታወሻ አያያዝ"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"የማስታወሻ አያያዝ፣ <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"በማሰራጨት ላይ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g>ን ማሰራጨት ይቁም?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>ን ካሰራጩ ወይም ውፅዓትን ከቀየሩ የአሁኑ ስርጭትዎ ይቆማል"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ይክፈቱ"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"የWallet መተግበሪያን እንደ አቋራጭ ለማከል መተግበሪያው መጫኑን ያረጋግጡ"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"የWallet መተግበሪያን እንደ አቋራጭ ለማከል ቢያንስ አንድ ካርድ መታከሉን ያረጋግጡ"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"የQR ኮድ መቃኛውን እንደ አቋራጭ ለማከል የካሜራ መተግበሪያ መጫኑን ያረጋግጡ"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"የHome መተግበሪያውን እንደ አቋራጭ ለማከል መተግበሪያው እንደተጫነ ያረጋግጡ"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• ቢያንስ አንድ መሣሪያ ይገኛል"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"የማስታወሻ አያያዝ አቋራጭን ለመጠቀም ነባሪ የማስታወሻ መተግበሪያ ይምረጡ"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"የረዳት ትኩረት በርቷል"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"በቅንብሮች ውስጥ ነባሪ የማስታወሻዎች መተግበሪያን ያቀናብሩ"</string>
     <string name="install_app" msgid="5066668100199613936">"መተግበሪያን ጫን"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"ማይክሮፎን እና ካሜራ"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"የቅርብ ጊዜ የመተግበሪያ አጠቃቀም"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"የቅርብ ጊዜ መዳረሻን አሳይ"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"ተከናውኗል"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"ዘርጋ እና አማራጮችን አሳይ"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"ሰብስብ"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"ይህን መተግበሪያ ዝጋ"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> ተዘግቷል"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"አገልግሎትን አስተዳድር"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"መዳረሻን አስተዳድር"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"በስልክ ጥሪ ሥራ ላይ እየዋለ ነው"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"በቅርብ ጊዜ በስልክ ጥሪ ውስጥ ጥቅም ላይ ውሏል"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> ጥቅም ላይ እየዋለ ነው"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> ጥቅም ላይ ውሏል"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ጥቅም ላይ እየዋለ ነው"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ጥቅም ላይ ውሏል"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ጥቅም ላይ እየዋለ ነው"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ጥቅም ላይ ውሏል"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 527c0e8..8128fae 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"انقر لعرض التسجيل."</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"حدث خطأ أثناء حفظ تسجيل محتوى الشاشة."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"حدث خطأ في بدء تسجيل الشاشة"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"جارٍ العرض بملء الشاشة"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"للخروج، مرِّر سريعًا من أعلى الشاشة لأسفلها."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"حسنًا"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"رجوع"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"الرئيسية"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"القائمة"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"صوت"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"سماعة الرأس"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"الإدخال"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"سماعات الأذن الطبية"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"سماعات الأذن الطبية"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"جارٍ التفعيل…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"التدوير التلقائي"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"التدوير التلقائي للشاشة"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"قيد <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"متوقّف مؤقَّتًا"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"‏أثناء المشاركة أو التسجيل أو البثّ، يمكن لنظام Android الوصول إلى كل المحتوى المعروض على شاشتك أو الذي يتم تشغيله على جهازك، لذا يُرجى توخي الحذر بشأن المعلومات، مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"‏أثناء مشاركة محتوى تطبيق أو تسجيله أو بثّه، يمكن لنظام Android الوصول إلى كل المحتوى المعروض أو الذي يتم تشغيله في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن المعلومات مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"بدء"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"تتوقف المشاركة مؤقتًا عند التبديل بين التطبيقات."</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"مشاركة هذا التطبيق بدلاً من ذلك"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"الرجوع"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"التبديل بين التطبيقات"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"حظر مشرف تكنولوجيا المعلومات هذه الميزة"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ميزة \"تصوير الشاشة\" غير مفعَّلة بسبب سياسة الجهاز."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"محو الكل"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"تكبير الشاشة كلها"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"تكبير جزء من الشاشة"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"فتح إعدادات التكبير"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"إغلاق إعدادات التكبير"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"اسحب الزاوية لتغيير الحجم."</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"السماح بالتمرير القطري"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"تغيير الحجم"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"حدثت مشكلة أثناء قراءة مقياس مستوى شحن البطارية."</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"انقر للحصول على مزيد من المعلومات."</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"لم يتم ضبط منبّه."</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"إدخال الرمز لفتح القفل"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"مستشعر بصمات الإصبع"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"المصادقة"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"الدخول إلى الجهاز"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"‏يستمع \"مساعد Google\" إليك الآن."</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{إشعار واحد}zero{# إشعار}two{إشعاران}few{# إشعارات}many{# إشعارًا}other{# إشعار}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>، <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"تدوين الملاحظات"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"تدوين الملاحظات"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"تدوين الملاحظات في \"<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>\""</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"البث"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"هل تريد إيقاف بث تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g>؟"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"إذا أجريت بث تطبيق <xliff:g id="SWITCHAPP">%1$s</xliff:g> أو غيَّرت جهاز الإخراج، سيتوقَف البث الحالي."</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"فتح \"<xliff:g id="APPNAME">%1$s</xliff:g>\""</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"‏لإضافة تطبيق \"محفظة Google\" كاختصار، تأكَّد من أنّ التطبيق مثبَّت."</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"‏لإضافة تطبيق \"محفظة Google\" كاختصار، تأكَّد من إضافة بطاقة واحدة على الأقل."</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"لإضافة تطبيق الماسح الضوئي لرمز الاستجابة السريعة كاختصار، تأكَّد من أنّ تطبيق الكاميرا مثبَّت."</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"‏لإضافة تطبيق Home كاختصار، تأكَّد من أنّ التطبيق مثبَّت."</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• توفُّر جهاز واحد على الأقل"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"اختَر تطبيقًا تلقائيًا لتدوين الملاحظات لاستخدام اختصار تدوين الملاحظات."</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"‏ميزة لفت انتباه \"مساعد Google\" مفعّلة."</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"يمكنك ضبط تطبيق تدوين الملاحظات التلقائي في \"الإعدادات\"."</string>
     <string name="install_app" msgid="5066668100199613936">"تثبيت التطبيق"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"الميكروفون والكاميرا"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"آخر استخدام في التطبيقات"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"عرض آخر استخدام في التطبيقات"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"تم"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"توسيع الخيارات وعرضها"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"تصغير"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"إغلاق هذا التطبيق"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"تم إغلاق <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"إدارة الخدمة"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"إدارة أذونات الوصول"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"قيد الاستخدام في المكالمة الهاتفية"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"تم الاستخدام مؤخرًا في المكالمة الهاتفية"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"قيد الاستخدام في <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"تم الاستخدام مؤخرًا في <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"قيد الاستخدام في <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"تم الاستخدام مؤخرًا في <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"قيد الاستخدام في <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"تم الاستخدام مؤخرًا في <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 08c3d87..4915257 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"চাবলৈ টিপক"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ৰেকৰ্ড কৰা স্ক্ৰীন ছেভ কৰোঁতে আসোঁৱাহ হৈছে"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"স্ক্রীন ৰেকৰ্ড কৰা আৰম্ভ কৰোঁতে আসোঁৱাহ হৈছে"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"পূৰ্ণ স্ক্ৰীনত চাই আছে"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"বাহিৰ হ’বলৈ ওপৰৰ পৰা তললৈ ছোৱাইপ কৰক।"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"বুজি পালোঁ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"উভতি যাওক"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"গৃহ পৃষ্ঠাৰ বুটাম"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"মেনু"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"অডিঅ’"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"হেডছেট"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ইনপুট"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"শ্ৰৱণ যন্ত্ৰ"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"শ্ৰৱণ যন্ত্ৰ"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"অন কৰি থকা হৈছে…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"স্বয়ং-ঘূৰ্ণন"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"স্বয়ং-ঘূৰ্ণন স্ক্ৰীন"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> সীমা"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"পজ হৈ আছে"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, আপোনাৰ স্ক্ৰীনখনত দৃশ্যমান হোৱা যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা, ফট’ আৰু অডিঅ’ আৰু ভিডিঅ’ৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা, ফট’ আৰু অডিঅ’ আৰু ভিডিঅ’ৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"আৰম্ভ কৰক"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"আপুনি এপ্‌ সলনি কৰিলে শ্বেয়াৰ কৰাটো পজ হয়"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"তাৰ সলনি এই এপ্‌টো শ্বেয়াৰ কৰক"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"উভতি যাওক"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"এপ্‌ সলনি কৰা"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"আপোনাৰ আইটি প্ৰশাসকে অৱৰোধ কৰিছে"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ডিভাইচ সম্পৰ্কীয় নীতিয়ে স্ক্ৰীন কেপশ্বাৰ কৰাটো অক্ষম কৰিছে"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"আটাইবোৰ মচক"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"পূৰ্ণ স্ক্ৰীন বিবৰ্ধন কৰক"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"স্ক্ৰীনৰ কিছু অংশ বিবৰ্ধন কৰক"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"বিবৰ্ধন কৰাৰ ছেটিং খোলক"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"বিবৰ্ধনৰ ছেটিং বন্ধ কৰক"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"আকাৰ সলনি কৰিবলৈ চুককেইটা টানি আনি এৰক"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"কোণীয়াকৈ স্ক্ৰ’ল কৰাৰ অনুমতি দিয়ক"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"আকাৰ সলনি কৰক"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"আপোনাৰ বেটাৰী মিটাৰ পঢ়োঁতে সমস্যা হৈছে"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"অধিক তথ্যৰ বাবে টিপক"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"কোনো এলাৰ্ম ছেট কৰা হোৱা নাই"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"স্ক্ৰীন লকটো দিয়ক"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ফিংগাৰপ্ৰিণ্ট ছেন্সৰ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"বিশ্বাসযোগ্যতা প্ৰমাণ কৰক"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ডিভাইচ আনলক কৰক"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistantএ শুনি আছে"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# টা জাননী}one{# টা জাননী}other{# টা জাননী}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"টোকাগ্ৰহণ"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"টোকা গ্ৰহণ কৰা"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"টোকা গ্ৰহণ কৰা, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"সম্প্ৰচাৰণ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g>ৰ সম্প্ৰচাৰ কৰা বন্ধ কৰিবনে?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"যদি আপুনি <xliff:g id="SWITCHAPP">%1$s</xliff:g>ৰ সম্প্ৰচাৰ কৰে অথবা আউটপুট সলনি কৰে, তেন্তে, আপোনাৰ বৰ্তমানৰ সম্প্ৰচাৰ বন্ধ হৈ যাব"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> খোলক"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Wallet এপ্‌টোক এটা শ্বৰ্টকাট হিচাপে যোগ দিবলৈ, এপ্‌টো ইনষ্টল কৰি থোৱাটো নিশ্চিত কৰক"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Wallet এপ্‌টোক এটা শ্বৰ্টকাট হিচাপে যোগ দিবলৈ, কমেও এখন কাৰ্ড যোগ দিয়াটো নিশ্চিত কৰক"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"কিউআৰ ক’ড স্কেনাৰক এটা শ্বৰ্টকাট হিচাপে যোগ দিবলৈ, কেমেৰা এপ্‌টো ইনষ্টল কৰি থোৱাটো নিশ্চিত কৰক"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home এপ্‌টোক এটা শ্বৰ্টকাট হিচাপে যোগ দিবলৈ, এপ্‌টো ইনষ্টল কৰি থোৱাটো নিশ্চিত কৰক"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• অতি কমেও এটা ডিভাইচ উপলব্ধ"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"টোকা গ্ৰহণৰ শ্বৰ্টকাটটো ব্যৱহাৰ কৰিবলৈ এটা ডিফ’ল্ট টোকা গ্ৰহণৰ এপ্‌ বাছনি কৰক"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistantএ আপোনাৰ কথা শুনি আছে"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ছেটিঙত টোকাৰ ডিফ’ল্ট এপ্ ছেট কৰক"</string>
     <string name="install_app" msgid="5066668100199613936">"এপ্‌টো ইনষ্টল কৰক"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"মাইক্ৰ’ফ’ন আৰু কেমেৰা"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"শেহতীয়া এপৰ ব্যৱহাৰ"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"শেহতীয়া এক্সেছ চাওক"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"কৰা হ’ল"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"বিস্তাৰ কৰক আৰু বিকল্পসমূহ দেখুৱাওক"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"সংকোচন কৰক"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"এই এপ্‌টো বন্ধ কৰক"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> বন্ধ কৰা হৈছে"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"সেৱা পৰিচালনা কৰক"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"এক্সেছ পৰিচালনা কৰক"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"ফ’ন কলত ব্যৱহাৰ কৰি থকা হৈছে"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"শেহতীয়াকৈ ফ’ন কলত ব্যৱহাৰ কৰা হৈছে"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ ব্যৱহাৰ কৰি আছে"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"শেহতীয়াকৈ <xliff:g id="APP_NAME">%1$s</xliff:g>এ ব্যৱহাৰ কৰিছে"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)এ ব্যৱহাৰ কৰি আছে"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"শেহতীয়াকৈ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)এ ব্যৱহাৰ কৰিছে"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)এ ব্যৱহাৰ কৰি আছে"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"শেহতীয়াকৈ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)এ ব্যৱহাৰ কৰিছে"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index a29b4fa..8d9fcbd 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Baxmaq üçün toxunun"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Ekran çəkimini yadda saxlayarkən xəta oldu"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Ekranın yazılması ilə bağlı xəta"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Tam ekran rejimi"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Çıxmaq üçün yuxarıdan aşağı sürüşdürün."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Anladım"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Geri"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Ana səhifə"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menyu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Qulaqlıq"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Giriş"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Eşitmə cihazları"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Eşitmə aparatları"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiv edilir..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Avtodönüş"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranın avtomatik dönməsi"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> limit"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"<xliff:g id="DATA_LIMIT">%s</xliff:g> xəbərdarlığı"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"İş tətbiqləri"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Durdurulub"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Gecə işığı"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Qürubda aktiv ediləcək"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Şəfəq vaxtına qədər"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Paylaşım, qeydəalma və ya yayım zamanı Android-in ekranda görünən, yaxud cihazda oxudulan məlumatlara girişi olur. Parol, ödəniş detalları, mesaj, foto, habelə audio və video kimi məlumatlarla bağlı diqqətli olun."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Tətbiq paylaşdıqda, qeydə aldıqda və ya yayımladıqda Android-in həmin tətbiqdə göstərilən, yaxud oxudulan məlumatlara girişi olur. Parol, ödəniş detalları, mesaj, foto, habelə audio və video kimi məlumatlarla bağlı diqqətli olun."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Başlayın"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Tətbiqləri dəyişdikdə paylaşım durdurulur"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Bu tətbiqi paylaşın"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Geri dəyişin"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Tətbiq keçirici"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"İT admininiz tərəfindən bloklanıb"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekran çəkimi cihaz siyasəti ilə deaktiv edilib"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hamısını silin"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Tam ekranı böyüdün"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekran hissəsinin böyüdülməsi"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Böyütmə ayarlarını açın"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Böyütmə ayarlarını bağlayın"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Ölçüsünü dəyişmək üçün küncündən sürüşdürün"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Diaqonal sürüşdürməyə icazə verin"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ölçüsünü dəyişin"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Batareya ölçüsünü oxuyarkən problem yarandı"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Ətraflı məlumat üçün toxunun"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Siqnal ayarlanmayıb"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ekran kilidi daxil edin"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Barmaq izi sensoru"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"doğrulayın"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"cihaz daxil edin"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistent dinləyir"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# bildiriş}other{# bildiriş}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Qeyd tutma"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Qeydgötürmə"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Qeydgötürmə, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Yayım"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqinin yayımlanması dayandırılsın?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> tətbiqini yayımlasanız və ya nəticəni dəyişsəniz, cari yayımınız dayandırılacaq"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> tətbiqini açın"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Pulqabı tətbiqini qısayol kimi əlavə etmək üçün tətbiq quraşdırılmalıdır"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Pulqabı tətbiqini qısayol kimi əlavə etmək üçün kart əlavə edilməlidir"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR kod skanerini qısayol kimi əlavə etmək üçün kamera tətbiqi quraşdırılmalıdır"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home tətbiqini qısayol kimi əlavə etmək üçün tətbiq quraşdırılmalıdır"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Ən azı bir cihaz əlçatandır"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Qeydgötürmə qısayolu üçün defolt qeyd tətbiqi seçin"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistent aktivdir"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlarda defolt qeydlər tətbiqi ayarlayın"</string>
     <string name="install_app" msgid="5066668100199613936">"Tətbiqi quraşdırın"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon və kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Son tətbiq istifadəsi"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Son girişə baxın"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Hazırdır"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Genişləndirin və seçimləri göstərin"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Yığcamlaşdırın"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Bu tətbiqi bağlayın"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> bağlandı"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Xidməti idarə edin"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Girişi idarə edin"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Telefon zəngində istifadə edilir"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Bu yaxınlarda telefon zəngində istifadə edilib"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> istifadə edir"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Bu yaxınlarda <xliff:g id="APP_NAME">%1$s</xliff:g> istifadə edib"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) istifadə edir"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Bu yaxınlarda <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) istifadə edib"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) istifadə edir"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Bu yaxınlarda <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) istifadə edib"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 2908be6..e1bd89d 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Dodirnite da biste pregledali"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Greška pri čuvanju snimka ekrana"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Greška pri pokretanju snimanja ekrana"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Prikazuje se ceo ekran"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Da biste izašli, prevucite nadole odozgo."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Važi"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Nazad"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Početna"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Meni"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Unos"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Slušni aparati"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušni aparati"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključuje se..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatska rotacija"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko rotiranje ekrana"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Ograničenje od <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Upozorenje za <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Poslovne aplikacije"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Pauzirano"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Noćno svetlo"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Uključuje se po zalasku sunca"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Do izlaska sunca"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada delite, snimate ili prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada delite, snimate ili prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Deljenje se zaustavlja kada menjate aplikacije"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Deli ovu aplikaciju"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Vrati"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Promena aplikacije"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokira IT administrator"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje ekrana je onemogućeno smernicama za uređaj"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Obriši sve"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Uvećajte ceo ekran"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Uvećajte deo ekrana"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Otvori podešavanja uvećanja"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Zatvori podešavanja uvećanja"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"Zatvori režim izmene"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Prevucite ugao da biste promenili veličinu"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Dozvoli dijagonalno skrolovanje"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Promeni veličinu"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem sa očitavanjem merača baterije"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Dodirnite za više informacija"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nije podešen"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"unesite zaključavanje ekrana"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor za otisak prsta"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"potvrdite identitet"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"unesite uređaj"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Pomoćnik sluša"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# obaveštenje}one{# obaveštenje}few{# obaveštenja}other{# obaveštenja}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Pravljenje beležaka"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Pravljenje beležaka"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pravljenje beležaka, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Emitovanje"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Želite da zaustavite emitovanje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ako emitujete aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promenite izlaz, aktuelno emitovanje će se zaustaviti"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Otvorite: <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Da biste dodali aplikaciju Novčanik kao prečicu, uverite se da je aplikacija instalirana"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Da biste dodali aplikaciju Novčanik kao prečicu, uverite se da je dodata bar jedna kartica"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Da biste dodali Skener QR koda kao prečicu, uverite se da je aplikacija za kameru instalirana"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Da biste dodali aplikaciju Home kao prečicu, uverite se da je aplikacija instalirana"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Dostupan je bar jedan uređaj"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Izaberite podrazumevanu aplikaciju za beleške da biste koristili prečicu za pravljenje beleški"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Pomoćnik je u aktivnom stanju"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Podesite podrazumevanu aplikaciju za beleške u Podešavanjima"</string>
     <string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavno koristila aplikacija"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Prikaži nedavni pristup"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Gotovo"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Proširi i prikaži opcije"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Skupi"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Zatvori ovu aplikaciju"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Zatvoreno: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Upravljaj uslugom"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Upravljaj pristupom"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Koristi telefonski poziv"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Nedavno korišćeno u telefonskom pozivu"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Koristi <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Nedavno koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Koristi <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nedavno koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Koristi <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nedavno koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 999600a..5d1b65c 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Націсніце для прагляду"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Памылка захавання запісу экрана"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Памылка пачатку запісу экрана"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Прагляд у поўнаэкранным рэжыме"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Каб выйсці, правядзіце пальцам па экране зверху ўніз."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Зразумела"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"На Галоўную старонку"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Гук"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнітура"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Увод"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Слыхавыя апараты"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слыхавыя апараты"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Уключэнне…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Аўтапаварот"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Аўтаматычны паварот экрана"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Ліміт <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"Профіль прыпынены"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Калі адбываецца абагульванне, запіс ці трансляцыя, Android мае доступ да ўсяго змесціва, якое паказваецца на экране ці прайграецца на прыладзе. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў, фота, відэа і аўдыя."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Калі адбываецца абагульванне, запіс ці трансляцыя змесціва праграмы, Android мае доступ да ўсяго змесціва, якое паказваецца ці прайграецца ў праграме. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў, фота, відэа і аўдыя."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Пачаць"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Пры пераключэнні праграм абагульванне прыпыняецца"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Абагуліць гэту праграму"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Пераключыцца назад"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Пераключальнік праграм"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблакіравана вашым ІТ-адміністратарам"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Здыманне экрана адключана згодна з палітыкай прылады"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Ачысціць усё"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Павялічыць увесь экран"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Павялічыць частку экрана"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Адкрыць налады павелічэння"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Закрыць налады павелічэння"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Каб змяніць памер, перацягніце вугал"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Дазволіць прагортванне па дыяганалі"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Змяніць памер"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Праблема з чытаннем індыкатара зараду акумулятара"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Націсніце, каб убачыць больш"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Няма будзільнікаў"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"прыступіць да разблакіроўкі экрана"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сканер адбіткаў пальцаў"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"правесці аўтэнтыфікацыю"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"адкрыць галоўны экран прылады"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Памочнік слухае"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# апавяшчэнне}one{# апавяшчэнне}few{# апавяшчэнні}many{# апавяшчэнняў}other{# апавяшчэння}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Стварэнне нататак"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Стварэнне нататак"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Стварэнне нататак, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Перадача даных"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Спыніць трансляцыю праграмы \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Пры пераключэнні на праграму \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\" ці змяненні вываду бягучая трансляцыя спыняецца"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Адкрыць праграму \"<xliff:g id="APPNAME">%1$s</xliff:g>\""</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Вы можаце дадаць ярлык праграмы \"Кашалёк\", толькі калі яна ўсталявана"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Вы можаце дадаць ярлык праграмы \"Кашалёк\", толькі калі дададзена хаця б адна картка"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Вы можаце дадаць ярлык сканера QR-кодаў, толькі калі ўсталявана праграма камеры"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Вы можаце дадаць ярлык праграмы Home, толькі калі яна ўсталявана"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Даступная хаця б адна прылада"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Выберыце стандартную праграму для нататак, якая будзе адкрывацца пры націсканні на ярлык"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Памочнік гатовы выконваць каманды"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайце ў Наладах стандартную праграму для нататак"</string>
     <string name="install_app" msgid="5066668100199613936">"Усталяваць праграму"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Мікрафон і камера"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Нядаўна выкарыстоўваліся праграмамі"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Паглядзець нядаўні доступ"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Гатова"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Разгарнуць і паказаць варыянты дзеянняў"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Згарнуць"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Закрыць гэту праграму"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" закрыта"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Кіраваць сэрвісам"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Кіраваць доступам"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Зараз выкарыстоўваецца для тэлефоннага выкліку"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Нядаўна выкарыстоўваўся для тэлефоннага выкліку"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Зараз выкарыстоўваецца праграмай \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Нядаўна выкарыстоўваўся праграмай \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Зараз выкарыстоўваецца праграмай \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Нядаўна выкарыстоўваўся праграмай \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Зараз выкарыстоўваецца праграмай \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Нядаўна выкарыстоўваўся праграмай \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 1042d43..94ab2c9 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Докоснете за преглед"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Грешка при запазването на записа на екрана"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"При стартирането на записа на екрана възникна грешка"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Изглед на цял екран"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"За изход прекарайте пръст надолу от горната част."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Разбрах"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Начало"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалки"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Вход"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Слухови апарати"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слухови апарати"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Включва се..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Авт. ориентация"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматично завъртане на екрана"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Ограничение от <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"На пауза"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Когато споделяте, записвате или предавате, Android има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви. Затова бъдете внимателни с неща като пароли, подробности за начини на плащане, съобщения, снимки, аудио и видео."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Когато споделяте, записвате или предавате дадено приложение, Android има достъп до всичко, което се показва или възпроизвежда в него. Затова бъдете внимателни с неща като пароли, подробности за начини на плащане, съобщения, снимки, аудио и видео."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Стартиране"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Споделянето се поставя на пауза, когато превключвате приложенията"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Вместо това споделете приложението"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Превключване обратно"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Превключване на приложението"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокирано от системния ви администратор"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Заснемането на екрана е деактивирано от правило за устройството"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Изчистване на всички"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Увеличаване на целия екран"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Увеличаване на част от екрана"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Отваряне на настройките за увеличението"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Затваряне на настройките за увеличение"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"Изход от режима на редактиране"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Плъзнете ъгъла за преоразмеряване"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Разрешаване на диагонално превъртане"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Преоразмеряване"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Възникна проблем при четенето на данните за нивото на батерията"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Докоснете за още информация"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Няма зададен будилник"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"въведете опция за заключване на екрана"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сензор за отпечатъци"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"удостоверяване"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"вход в устройството"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Асистент слуша"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# известие}other{# известия}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Водене на бележки"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Водене на бележки"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Водене на бележки, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Излъчване"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Да се спре ли предаването на <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ако предавате <xliff:g id="SWITCHAPP">%1$s</xliff:g> или промените изхода, текущото ви предаване ще бъде прекратено"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Отваряне на <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"За да добавите пряк път към приложението Wallet, уверете се, че то е инсталирано"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"За да добавите пряк път към приложението Wallet, уверете се, че е добавена поне една карта"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"За да добавите пряк път към скенера за QR кодове, уверете се, че е инсталирано приложение за камера"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"За да добавите пряк път към приложението Home, уверете се, че то е инсталирано"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Налице е поне едно устройство."</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Изберете стандартно приложение за бележки, за да използвате прекия път за водене на бележки"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Функцията за активиране на Асистент е включена"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайте стандартно приложение за бележки от настройките"</string>
     <string name="install_app" msgid="5066668100199613936">"Инсталиране на приложението"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Скорошно използване на приложението"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Вижте скорошния достъп"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Готово"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Разгъване и показване на опциите"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Свиване"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Затваряне на това приложение"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Затворихте <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Управление на услугата"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Управление на достъпа"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Използва се от телефонно обаждане"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Наскоро използвано в телефонно обаждане"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Използва се от <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Наскоро използвано от <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Използва се от <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Наскоро използвано от <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Използва се от <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Наскоро използвано от <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 749b05a..f80c301 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"দেখতে ট্যাপ করুন"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"স্ক্রিন রেকর্ডিং সেভ করার সময় কোনও সমস্যা হয়েছে"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"স্ক্রিন রেকর্ডিং শুরু করার সময় সমস্যা হয়েছে"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"ফুল-স্ক্রিনে দেখা হচ্ছে"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"বেরিয়ে যেতে উপর থেকে নিচের দিকে সোয়াইপ করুন।"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"বুঝেছি"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ফিরুন"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"হোম"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"মেনু"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"অডিও"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"হেডসেট"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ইনপুট"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"হিয়ারিং এড"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"হিয়ারিং এড"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"চালু করা হচ্ছে…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"নিজে থেকে ঘুরবে"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"অটো-রোটেট স্ক্রিন"</string>
@@ -292,6 +295,8 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"সীমা <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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>
+    <!-- no translation found for quick_settings_work_mode_paused_state (6681788236383735976) -->
+    <skip />
     <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>
@@ -415,6 +420,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"আপনি শেয়ার, রেকর্ড বা কাস্ট করার সময়, স্ক্রিনে দৃশ্যমান বা ডিভাইসে চালানো সব কিছুই Android অ্যাক্সেস করতে পারবে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ে সতর্ক থাকুন।"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"আপনি কোনও অ্যাপ শেয়ার, রেকর্ড বা কাস্ট করার সময়, সেই অ্যাপে দেখা যায় বা চালানো হয় এমন সব কিছু Android অ্যাক্সেস করতে পারবে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ে সতর্ক থাকুন।"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"শুরু করুন"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"আপনি অ্যাপ পাল্টানোর সময় শেয়ারিং পজ করা হয়"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"এর পরিবর্তে এই অ্যাপ শেয়ার করুন"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"আবার পাল্টান"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"অ্যাপ পাল্টান"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"আপনার আইটি অ্যাডমিন ব্লক করেছেন"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ডিভাইস নীতির কারণে স্ক্রিন ক্যাপচার করার প্রসেস বন্ধ করা আছে"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"সব মুছে দিন"</string>
@@ -856,6 +865,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"সম্পূর্ণ স্ক্রিন বড় করে দেখা"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"স্ক্রিনের কিছুটা অংশ বড় করুন"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"বড় করে দেখার সেটিংস খুলুন"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"\'বড় করে দেখা\' সেটিংস বন্ধ করুন"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ছোট বড় করার জন্য কোণ টেনে আনুন"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"কোনাকুনি স্ক্রল করার অনুমতি দেওয়া"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ছোট বড় করা"</string>
@@ -1043,6 +1055,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"ব্যাটারির মিটারের রিডিং নেওয়ার সময় সমস্যা হয়েছে"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"আরও তথ্যের জন্য ট্যাপ করুন"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"কোনও অ্যালার্ম সেট করা নেই"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"স্ক্রিন লক খুলুন"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ফিঙ্গারপ্রিন্ট সেন্সর"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"যাচাই করিয়ে নিন"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ডিভাইস আনলক করুন"</string>
@@ -1105,7 +1118,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant শুনছে"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{#টি বিজ্ঞপ্তি}one{#টি বিজ্ঞপ্তি}other{#টি বিজ্ঞপ্তি}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetaking"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"নোট নেওয়া"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"নোট নেওয়া, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ব্রডকাস্ট করা হচ্ছে"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> সম্প্রচার বন্ধ করবেন?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"আপনি <xliff:g id="SWITCHAPP">%1$s</xliff:g> সম্প্রচার করলে বা আউটপুট পরিবর্তন করলে, আপনার বর্তমান সম্প্রচার বন্ধ হয়ে যাবে"</string>
@@ -1123,7 +1137,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> খুলুন"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Wallet অ্যাপ, শর্টকাট হিসেবে যোগ করতে, অ্যাপ ইনস্টল করা আছে কিনা তা ভালভাবে দেখে নিন"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Wallet অ্যাপ, শর্টকাট হিসেবে যোগ করতে, অন্তত একটি কার্ড যোগ করা হয়েছে কিনা তা ভালভাবে দেখে নিন"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR কোড স্ক্যানার, শর্টকাট হিসেবে যোগ করতে, ক্যামেরা অ্যাপ ইনস্টল করা আছে কিনা তা ভালভাবে দেখে নিন"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home অ্যাপ, শর্টকাট হিসেবে যোগ করতে, অ্যাপ ইনস্টল করা আছে কিনা তা ভালভাবে দেখে নিন"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• অন্তত একটি ডিভাইস উপলভ্য আছে"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"নোট নেওয়ার শর্টকাট ব্যবহার করতে, ডিফল্ট কোনও নোট অ্যাপ বেছে নিন"</string>
@@ -1156,4 +1169,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"অ্যাসিস্ট্যান্ট আপনার কথা শোনার জন্য চালু করা আছে"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"\'সেটিংস\' থেকে ডিফল্ট নোট নেওয়ার অ্যাপ সেট করুন"</string>
     <string name="install_app" msgid="5066668100199613936">"অ্যাপ ইনস্টল করুন"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"মাইক্রোফোন ও ক্যামেরা"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"সম্প্রতি ব্যবহার করা অ্যাপ"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"সাম্প্রতিক অ্যাক্সেস দেখুন"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"হয়ে গেছে"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"বড় করুন ও বিকল্প দেখান"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"আড়াল করুন"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"এই অ্যাপ বন্ধ করুন"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপ বন্ধ করা আছে"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"পরিষেবা ম্যানেজ করুন"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"অ্যাক্সেস ম্যানেজ করুন"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"ফোন কলে ব্যবহার করা হচ্ছে"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"সম্প্রতি ফোন কলে ব্যবহার করা হয়েছে"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপে ব্যবহার করা হচ্ছে"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"সম্প্রতি <xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপে ব্যবহার করা হয়েছে"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপে (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ব্যবহার করা হচ্ছে"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"সম্প্রতি <xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপে (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ব্যবহার করা হয়েছে"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপে (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ব্যবহার করা হচ্ছে"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"সম্প্রতি <xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপে (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ব্যবহার করা হয়েছে"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 73e7f4a..364e847 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Dodirnite da vidite"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Greška prilikom pohranjivanja snimka ekrana"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Greška pri pokretanju snimanja ekrana"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Prikazivanje preko cijelog ekrana"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Da izađete, prevucite s vrha nadolje."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Razumijem"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Nazad"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Dugme za početnu stranicu"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Dugme Meni"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ulaz"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Slušni aparat"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušni aparati"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključivanje…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatsko rotiranje"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko rotiranje ekrana"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Ograničenje <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Upozorenje <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Poslovne aplikacije"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Pauzirano"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Noćno svjetlo"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Uključuje se u sumrak"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Do svitanja"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada dijelite, snimate ili emitirate, Android ima pristup svemu što je vidljivo na ekranu ili što se reproducira na uređaju. Stoga budite oprezni s informacijama kao što su lozinke, podaci o plaćanju, poruke, fotografije, zvukovi i videozapisi."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada dijelite, snimate ili emitirate aplikaciju, Android ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga budite oprezni s informacijama kao što su lozinke, podaci o plaćanju, poruke, fotografije, zvukovi i videozapisi."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Dijeljenje se pauzira kada promijenite aplikaciju"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Umjesto toga, dijeli aplikaciju"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Vrati se"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Promjena aplikacije"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokirao je vaš IT administrator"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje ekrana je onemogućeno pravilima uređaja"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Očisti sve"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Uvećavanje prikaza preko cijelog ekrana"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Uvećavanje dijela ekrana"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Otvori postavke uvećavanja"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Zatvori postavke uvećavanja"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"Izađi iz načina rada za uređivanje"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Prevucite ugao da promijenite veličinu"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Dozvoli dijagonalno klizanje"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Promijeni veličinu"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Došlo je do problema prilikom očitavanja mjerača stanja baterije"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Dodirnite za više informacija"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nema nijednog alarma"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"unos zaključavanja ekrana"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor za otisak prsta"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentificiranje"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"pristup uređaju"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistent sluša"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# obavještenje}one{# obavještenje}few{# obavještenja}other{# obavještenja}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Pisanje bilješki"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Pisanje bilješki"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pisanje bilješki, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Emitiranje"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Zaustaviti emitiranje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ako emitirate aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promijenite izlaz, trenutno emitiranje će se zaustaviti"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Otvori aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Da dodate aplikaciju Novčanik kao prečicu, instalirajte aplikaciju"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Da dodate aplikaciju Novčanik kao prečicu, dodajte barem jednu karticu"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Da dodate skener QR koda kao prečicu, instalirajte aplikaciju kamere"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Da dodate aplikaciju Home kao prečicu, instalirajte aplikaciju"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Dostupan je najmanje jedan uređaj"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Odaberite zadanu aplikaciju za bilješke da koristite prečicu za zapisivanje bilješki"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Pažnja Asistenta je uključena"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u Postavkama"</string>
     <string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavno koristila aplikacija"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Prikaži nedavni pristup"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Gotovo"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Proširite i prikažite opcije"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Sužavanje"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Zatvori ovu aplikaciju"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> je zatvorena"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Upravljajte uslugom"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Upravljajte pristupom"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Koristi telefonski poziv"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Nedavno je korišteno u telefonskom pozivu"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Koristi aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Nedavno je koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Koristi aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nedavno je koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Koristi aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nedavno je koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 65196a6..557a7e6 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Toca per veure-la"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"S\'ha produït un error en desar la gravació de la pantalla"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"S\'ha produït un error en iniciar la gravació de pantalla"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visualització en pantalla completa"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Per sortir, llisca cap avall des de la part superior."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Entesos"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Enrere"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Inici"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Àudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculars"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Audiòfons"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audiòfons"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"S\'està activant…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Gira automàticament"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Gira la pantalla automàticament"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Límit: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Advertiment: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Aplicacions de treball"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"En pausa"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Llum nocturna"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Al vespre"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Fins a l\'alba"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quan comparteixes, graves o emets contingut, 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, les fotos i l\'àudio i el vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quan comparteixes, graves o emets contingut, 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, les fotos i l\'àudio i el vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Inicia"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"La compartició es posa en pausa quan canvies d\'aplicació"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Comparteix aquesta aplicació"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Torna a canviar"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Canvi d\'aplicació"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquejat per l\'administrador de TI"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Les captures de pantalla estan desactivades per la política de dispositius"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Esborra-ho tot"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Amplia la pantalla completa"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Amplia una part de la pantalla"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Obre la configuració de l\'ampliació"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Tanca la configuració de l\'ampliació"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arrossega el cantó per canviar la mida"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permet el desplaçament en diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Canvia la mida"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Hi ha hagut un problema en llegir el mesurador de la bateria"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toca per obtenir més informació"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Cap alarma definida"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"utilitza el bloqueig de pantalla"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor d\'empremtes digitals"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"accedir al dispositiu"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"L\'assistent t\'escolta"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificació}many{# notificacions}other{# notificacions}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Presa de notes"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Presa de notes"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Presa de notes, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"S\'està emetent"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vols deixar d\'emetre <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si emets <xliff:g id="SWITCHAPP">%1$s</xliff:g> o canvies la sortida, l\'emissió actual s\'aturarà"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Obre <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Per afegir l\'aplicació Wallet com a drecera, assegura\'t que estigui instal·lada"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Per afegir l\'aplicació Wallet com a drecera, assegura\'t que s\'hagi afegit almenys una targeta"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Per afegir l\'escàner de codis QR com a drecera, assegura\'t que hi hagi una aplicació de càmera instal·lada"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Per afegir l\'aplicació Home com a drecera, assegura\'t que estigui instal·lada"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Almenys un dispositiu està disponible."</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Selecciona una aplicació de notes predeterminada per utilitzar la drecera de presa de notes"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"L\'Assistent està activat"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defineix l\'aplicació de notes predeterminada a Configuració"</string>
     <string name="install_app" msgid="5066668100199613936">"Instal·la l\'aplicació"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Micròfon i càmera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Ús recent de l\'aplicació"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Mostra l\'accés recent"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Fet"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Desplega i mostra opcions"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Replega"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Tanca aquesta aplicació"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"S\'ha tancat <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Gestiona el servei"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Gestiona l\'accés"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"En ús per una trucada"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Utilitzat recentment en una trucada"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"En ús per <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Utilitzat recentment per <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"En ús per <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Utilitzat recentment per <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"En ús per <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Utilitzat recentment per <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 814345e..0a83c86 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Klepnutím nahrávku zobrazíte"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Při ukládání záznamu obrazovky došlo k chybě"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Při spouštění nahrávání obrazovky došlo k chybě"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Zobrazování přes celou obrazovku"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Režim ukončíte přejetím prstem shora dolů."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Rozumím"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Zpět"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Domů"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Sluchátka"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vstup"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Naslouchátka"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Naslouchátka"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Zapínání…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatické otáčení"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatické otáčení obrazovky"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Limit: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Upozornění při <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Pracovní aplikace"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Pozastaveno"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Noční režim"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Při soumraku"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Do svítání"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Během sdílení, nahrávání nebo odesílání má Android přístup k veškerému obsahu, který je viditelný na obrazovce nebo se přehrává v zařízení. Buďte proto opatrní s věcmi, jako jsou hesla, platební údaje, zprávy, fotografie, zvuk a video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Během sdílení, nahrávání nebo odesílání aplikace má Android přístup k veškerému obsahu, který je v dané aplikaci zobrazen nebo přehráván. Buďte proto opatrní s věcmi, jako jsou hesla, platební údaje, zprávy, fotografie, zvuk a video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Začít"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Když přepnete aplikace, sdílení se pozastaví"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Sdílet aplikaci"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Přepnout zpět"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Přepnutí aplikace"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokováno administrátorem IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Záznam obrazovky je zakázán zásadami zařízení"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Smazat vše"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Zvětšit celou obrazovku"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Zvětšit část obrazovky"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Otevřít nastavení zvětšení"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Zavřít nastavení zvětšení"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"Ukončit režim úprav"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Velikost změníte přetažením rohu"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Povolit diagonální posouvání"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Změnit velikost"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problém s načtením měřiče baterie"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Klepnutím zobrazíte další informace"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Budík nenastaven"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"zadejte zámek obrazovky"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Snímač otisků prstů"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ověříte"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"zadáte zařízení"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistent poslouchá"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# oznámení}few{# oznámení}many{# oznámení}other{# oznámení}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g> <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Poznámky"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Psaní poznámek"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Psaní poznámek, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Vysílání"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Zastavit vysílání v aplikaci <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Pokud budete vysílat v aplikaci <xliff:g id="SWITCHAPP">%1$s</xliff:g> nebo změníte výstup, aktuální vysílání se zastaví"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Otevřít <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Pokud chcete přidat aplikaci Peněženka jako zkratku, nainstalujte ji"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Pokud chcete přidat aplikaci Peněženka jako zkratku, přidejte alespoň jednu kartu"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Pokud chcete přidat skener QR kódů jako zkratku, ujistěte se, že je nainstalována aplikace k focení a natáčení"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Pokud chcete přidat aplikaci Home jako zkratku, nainstalujte ji"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Je k dispozici alespoň jedno zařízení"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Vyberte výchozí aplikaci k psaní poznámek, ke které přidružíte zkratku pro poznámky"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Pozornost Asistenta je zapnutá"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Výchozí aplikaci pro poznámky nastavíte v Nastavení"</string>
     <string name="install_app" msgid="5066668100199613936">"Nainstalovat aplikaci"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon a fotoaparát"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedávné použití aplikacemi"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Zobrazit nedávný přístup"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Hotovo"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Rozbalit a zobrazit možnosti"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Sbalit"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Zavřít tuto aplikaci"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> byla zavřena"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Spravovat službu"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Spravovat přístup"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Právě používán telefonním hovorem"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Nedávno použito při telefonním hovoru"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Právě používán aplikací <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Nedávno použila aplikace <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Právě používán aplikací <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nedávno použila aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Právě používán aplikací <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nedávno použila aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 8f735c4..9ebbebb 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tryk for at se"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Skærmoptagelsen kunne ikke gemmes"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Skærmoptagelsen kunne ikke startes"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visning i fuld skærm"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Stryg ned fra toppen for at afslutte."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Tilbage"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Hjem"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Høreapparater"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Høreapparater"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiverer…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Roter automatisk"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Roter skærmen automatisk"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Grænse: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Advarsel ved <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Arbejdsapps"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Sat på pause"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Nattelys"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Tænd ved solnedgang"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Indtil solopgang"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Når du deler, optager eller caster, har Android adgang til alt, der er synligt på din skærm eller afspilles på din enhed. Vær derfor forsigtig med ting såsom adgangskoder, betalingsoplysninger, beskeder, billeder, lyd og video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Når du deler, optager eller caster en app, har Android adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med ting såsom adgangskoder, betalingsoplysninger, beskeder, billeder, lyd og video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Delingen sættes på pause, når du skifter apps"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Del denne app i stedet"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Skift tilbage"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Skift app"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokeret af din it-administrator"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screenshots er deaktiveret af enhedspolitikken"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Forstør hele skærmen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Forstør en del af skærmen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Åbn indstillinger for forstørrelse"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Luk indstillinger for forstørrelse"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Træk i hjørnet for at justere størrelsen"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Tillad diagonal rulning"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Juster"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Der er problemer med at læse dit batteriniveau"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tryk for at få flere oplysninger"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ingen alarm er indstillet"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"angiv skærmlås"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingeraftrykssensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"godkende"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"få adgang til enheden"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Google Assistent lytter med"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notifikation}one{# notifikation}other{# notifikationer}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetagning"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Notetagning"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Notetagning, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Udsender"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Stop udsendelsen <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Hvis du udsender <xliff:g id="SWITCHAPP">%1$s</xliff:g> eller skifter output, stopper din aktuelle udsendelse"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Åbn <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Hvis du vil tilføje en genvej til Wallet-appen, skal du sørge for, at appen er installeret"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Hvis du vil tilføje en genvej til Wallet-appen, skal du sørge for, at du har tilføjet mindst ét kort"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Hvis du vil tilføje en genvej til QR-kodescanneren, skal du sørge for, at kameraappen er installeret"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Hvis du vil tilføje en genvej til Home-appen, skal du sørge for, at appen er installeret"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Mindst én enhed er tilgængelig"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Vælg en standardapp til noter for at bruge genvejen til notetagning"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistent lytter"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Angiv standardapp til noter i Indstillinger"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer app"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon og kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Seneste brug af apps"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Se seneste adgang"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Udfør"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Udvid og vis mulighederne"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Skjul"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Luk denne app"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> er lukket"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Administrer tjeneste"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Administrer adgang"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Bruges af telefonopkald"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Brugt for nylig af telefonopkald"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Bruges af <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Brugt for nylig af <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Bruges af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Brugt for nylig af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Bruges af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Brugt for nylig af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 94b07f2..206e832 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Zum Ansehen tippen"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Fehler beim Speichern der Bildschirmaufzeichnung"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Fehler beim Start der Bildschirmaufzeichnung"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Vollbildmodus wird aktiviert"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Zum Beenden von oben nach unten wischen."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Ok"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Zurück"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Startbildschirm"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menü"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Eingabe"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hörgeräte"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hörgerät"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Wird aktiviert…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. drehen"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Bildschirm automatisch drehen"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> Datenlimit"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Warnung für <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Geschäftliche Apps"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Pausiert"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Nachtlicht"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"An bei Sonnenuntergang"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Bis Sonnenaufgang"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Beim Teilen, Aufnehmen oder Streamen hat Android Zugriff auf alle Inhalte, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden. Sei also vorsichtig mit Informationen wie Passwörtern, Zahlungsdetails, Nachrichten, Fotos sowie Audio- und Videoinhalten."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Beim Teilen, Aufnehmen oder Streamen einer App hat Android Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder von ihr wiedergegeben werden. Sei also vorsichtig mit Informationen wie Passwörtern, Zahlungsdetails, Nachrichten, Fotos sowie Audio- und Videoinhalten."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Starten"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Das Teilen wird pausiert, wenn du zwischen Apps wechselst"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Stattdessen diese App teilen"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Zurückwechseln"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"App wechseln"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Vom IT-Administrator blockiert"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Bildschirmaufnahme ist durch die Geräterichtlinien deaktiviert"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Alle löschen"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ganzen Bildschirm vergrößern"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Teil des Bildschirms vergrößern"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Vergrößerungseinstellungen öffnen"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Vergrößerungseinstellungen schließen"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Zum Anpassen der Größe Ecke ziehen"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Diagonales Scrollen erlauben"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Größe ändern"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem beim Lesen des Akkustands"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Für weitere Informationen tippen"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Kein Wecker gestellt"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"Displaysperre-Anmeldedaten eingeben"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerabdrucksensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Authentifizieren"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"Eingeben des Geräts"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant hört zu"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# Benachrichtigung}other{# Benachrichtigungen}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notizen machen"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Notizen"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Notizen, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Übertragung läuft"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> nicht mehr streamen?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Wenn du <xliff:g id="SWITCHAPP">%1$s</xliff:g> streamst oder die Ausgabe änderst, wird dein aktueller Stream beendet"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> öffnen"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Damit du die Wallet App als Verknüpfung hinzufügen kannst, muss die App installiert sein"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Damit du die Wallet App als Verknüpfung hinzufügen kannst, muss mindestens eine Karte hinzugefügt sein"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Damit du den QR-Code-Scanner als Verknüpfung hinzufügen kannst, muss eine Kamera-App installiert sein"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Damit du die Home App als Verknüpfung hinzufügen kannst, muss die App installiert sein"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Mindestens ein Gerät ist verfügbar"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Wähle eine Standard-App für Notizen aus, die du für die Verknüpfung verwenden möchtest"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant-Aktivierung an"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standard-Notizen-App in den Einstellungen einrichten"</string>
     <string name="install_app" msgid="5066668100199613936">"App installieren"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon &amp; Kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Kürzliche App-Nutzung"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Kürzliche Zugriffe ansehen"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Fertig"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Maximieren und Optionen einblenden"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Minimieren"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Diese App schließen"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> geschlossen"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Dienst verwalten"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Zugriff verwalten"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Verwendet in Telefonanruf"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Kürzlich in einem Telefonanruf verwendet"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Verwendet von <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Kürzlich von <xliff:g id="APP_NAME">%1$s</xliff:g> verwendet"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Verwendet von <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Kürzlich von <xliff:g id="APP_NAME">%1$s</xliff:g> verwendet (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Verwendet von <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Kürzlich von <xliff:g id="APP_NAME">%1$s</xliff:g> verwendet (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 5313921..c32cd17 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Πατήστε για προβολή"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Σφάλμα κατά την αποθήκευση της εγγραφής οθόνης"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Σφάλμα κατά την έναρξη της εγγραφής οθόνης"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Προβολή σε πλήρη οθόνη"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Για έξοδο, σύρετε προς τα κάτω από το επάνω μέρος."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Το κατάλαβα"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Πίσω"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Αρχική οθόνη"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Μενού"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ήχος"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ακουστικά"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Είσοδος"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Βοηθήματα ακρόασης"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Βοηθήματα ακοής"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ενεργοποίηση…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Αυτόματη περιστροφή"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Αυτόματη περιστροφή οθόνης"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Όριο <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"Σε παύση"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Όταν κάνετε κοινή χρήση, εγγραφή ή μετάδοση, το Android έχει πρόσβαση σε οτιδήποτε είναι ορατό στην οθόνη σας ή αναπαράγεται στη συσκευή σας. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα, τις φωτογραφίες, τον ήχο και το βίντεο."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Όταν κάνετε κοινή χρήση, εγγραφή ή μετάδοση μιας εφαρμογής, το Android έχει πρόσβαση σε οτιδήποτε είναι ορατό ή αναπαράγεται στη συγκεκριμένη εφαρμογή. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα, τις φωτογραφίες, τον ήχο και το βίντεο."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Έναρξη"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Η κοινοποίηση τίθεται σε παύση κατά την εναλλαγή μεταξύ εφαρμογών"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Εναλλακτικά, κοινοποιήστε την εφαρμογή"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Επιστροφή"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Εναλλαγή μεταξύ εφαρμογών"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Αποκλείστηκε από τον διαχειριστή IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Η καταγραφή οθόνης έχει απενεργοποιηθεί από την πολιτική χρήσης συσκευής."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Διαγραφή όλων"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Μεγέθυνση πλήρους οθόνης"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Μεγέθυνση μέρους της οθόνης"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Άνοιγμα ρυθμίσεων μεγιστοποίησης"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Κλείσιμο ρυθμίσεων μεγιστοποίησης"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Σύρετε τη γωνία για αλλαγή μεγέθους"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Να επιτρέπεται η διαγώνια κύλιση"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Αλλαγή μεγέθους"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Υπάρχει κάποιο πρόβλημα με την ανάγνωση του μετρητή μπαταρίας"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Πατήστε για περισσότερες πληροφορίες."</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Δεν ορίστηκε ξυπνητ."</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"εισαγωγή κλειδώματος οθόνης"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Αισθητήρας δακτυλικών αποτυπωμάτων"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"έλεγχος ταυτότητας"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"εισαγωγή συσκευής"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Ο Βοηθός ακούει"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ειδοποίηση}other{# ειδοποιήσεις}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Δημιουργία σημειώσεων"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Δημιουργία σημειώσεων"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Δημιουργία σημειώσεων, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Μετάδοση"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Διακοπή μετάδοσης με την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>;"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Εάν κάνετε μετάδοση με την εφαρμογή <xliff:g id="SWITCHAPP">%1$s</xliff:g> ή αλλάξετε την έξοδο, η τρέχουσα μετάδοση θα σταματήσει"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Άνοιγμα <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Για να προσθέσετε ως συντόμευση την εφαρμογή Πορτοφόλι, βεβαιωθείτε ότι έχει εγκατασταθεί η εφαρμογή"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Για να προσθέσετε ως συντόμευση την εφαρμογή Πορτοφόλι, βεβαιωθείτε ότι έχει προστεθεί τουλάχιστον μία κάρτα"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Για να προσθέσετε ως συντόμευση τη Σάρωση κωδικών QR, βεβαιωθείτε ότι η εφαρμογή κάμερας είναι εγκατεστημένη"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Για να προσθέσετε την εφαρμογή Home ως συντόμευση, βεβαιωθείτε ότι η εφαρμογή είναι εγκατεστημένη"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Είναι διαθέσιμη τουλάχιστον μία συσκευή"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Επιλέξτε μια προεπιλεγμένη εφαρμογή σημειώσεων για να χρησιμοποιήσετε τη συντόμευση δημιουργίας σημειώσεων"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Ο Βοηθός βρίσκεται σε αναμονή"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ορίστε την προεπιλεγμένη εφαρμογή σημειώσεων στις Ρυθμίσεις"</string>
     <string name="install_app" msgid="5066668100199613936">"Εγκατάσταση εφαρμογής"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Μικρόφωνο και Κάμερα"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Πρόσφατη χρήση εφαρμογής"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Εμφάνιση πρόσφατης πρόσβασης"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Τέλος"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Ανάπτυξη και εμφάνιση επιλογών"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Σύμπτυξη"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Κλείσιμο αυτής της εφαρμογής"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> έκλεισε"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Διαχείριση υπηρεσίας"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Διαχείριση πρόσβασης"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Χρησιμοποιείται από την τηλεφωνική κλήση"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Χρησιμοποιήθηκε πρόσφατα στην τηλεφωνική κλήση"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Χρησιμοποιείται από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Χρησιμοποιήθηκε πρόσφατα από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Χρησιμοποιείται από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Χρησιμοποιήθηκε πρόσφατα από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Χρησιμοποιείται από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Χρησιμοποιήθηκε πρόσφατα από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 16164f4..313f617 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hearing aids"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
@@ -292,6 +295,8 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> limit"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"<xliff:g id="DATA_LIMIT">%s</xliff:g> warning"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Work apps"</string>
+    <!-- no translation found for quick_settings_work_mode_paused_state (6681788236383735976) -->
+    <skip />
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Night Light"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"On at sunset"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Until sunrise"</string>
@@ -415,6 +420,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Sharing pauses when you switch apps"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Share this app instead"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Switch back"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"App switch"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
@@ -856,6 +865,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Magnify full screen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Open magnification settings"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Close magnification settings"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Drag corner to resize"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Allow diagonal scrolling"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Resize"</string>
@@ -1043,6 +1055,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem reading your battery meter"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tap for more information"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No alarm set"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"enter screen lock"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerprint sensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Authenticate"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"enter device"</string>
@@ -1105,7 +1118,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant is listening"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}other{# notifications}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetaking"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Note-taking"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Note-taking, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Broadcasting"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Stop broadcasting <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
@@ -1123,7 +1137,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Open <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"To add the Wallet app as a shortcut, make sure that the app is installed"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"To add the Wallet app as a shortcut, make sure that at least one card has been added"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"To add the QR code scanner as a shortcut, make sure that a camera app is installed"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"To add the Home app as a shortcut, make sure that the app is installed"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• At least one device is available"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Select a default notes app to use the note-taking shortcut"</string>
@@ -1156,4 +1169,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant attention on"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
     <string name="install_app" msgid="5066668100199613936">"Install app"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone and Camera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app use"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"See recent access"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Done"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Expand and show options"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Collapse"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Close this app"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> closed"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Manage service"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Manage access"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"In use by phone call"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Recently used in phone call"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"In use by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"In use by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"In use by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index a3bb5c8..157570a 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hearing Aids"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> limit"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"<xliff:g id="DATA_LIMIT">%s</xliff:g> warning"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Work apps"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Paused"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Night Light"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"On at sunset"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Until sunrise"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording, or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording, or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Sharing pauses when you switch apps"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Share this app instead"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Switch back"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"App switch"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Magnify full screen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Open magnification settings"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Close magnification settings"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"Exit edit mode"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Drag corner to resize"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Allow diagonal scrolling"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Resize"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem reading your battery meter"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tap for more information"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No alarm set"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"enter screen lock"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerprint sensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"authenticate"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"enter device"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant is listening"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}other{# notifications}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetaking"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Note-taking"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Note-taking, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Broadcasting"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Stop broadcasting <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Open <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"To add the Wallet app as a shortcut, make sure the app is installed"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"To add the Wallet app as a shortcut, make sure at least one card has been added"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"To add the QR code scanner as a shortcut, make sure a camera app is installed"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"To add the Home app as a shortcut, make sure the app is installed"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• At least one device is available"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Select a default notes app to use the notetaking shortcut"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant attention on"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
     <string name="install_app" msgid="5066668100199613936">"Install app"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone &amp; Camera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app use"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"See recent access"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Done"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Expand and show options"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Collapse"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Close this app"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> closed"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Manage service"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Manage access"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"In use by phone call"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Recently used in phone call"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"In use by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"In use by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"In use by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 16164f4..313f617 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hearing aids"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
@@ -292,6 +295,8 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> limit"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"<xliff:g id="DATA_LIMIT">%s</xliff:g> warning"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Work apps"</string>
+    <!-- no translation found for quick_settings_work_mode_paused_state (6681788236383735976) -->
+    <skip />
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Night Light"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"On at sunset"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Until sunrise"</string>
@@ -415,6 +420,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Sharing pauses when you switch apps"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Share this app instead"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Switch back"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"App switch"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
@@ -856,6 +865,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Magnify full screen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Open magnification settings"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Close magnification settings"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Drag corner to resize"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Allow diagonal scrolling"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Resize"</string>
@@ -1043,6 +1055,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem reading your battery meter"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tap for more information"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No alarm set"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"enter screen lock"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerprint sensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Authenticate"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"enter device"</string>
@@ -1105,7 +1118,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant is listening"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}other{# notifications}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetaking"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Note-taking"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Note-taking, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Broadcasting"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Stop broadcasting <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
@@ -1123,7 +1137,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Open <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"To add the Wallet app as a shortcut, make sure that the app is installed"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"To add the Wallet app as a shortcut, make sure that at least one card has been added"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"To add the QR code scanner as a shortcut, make sure that a camera app is installed"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"To add the Home app as a shortcut, make sure that the app is installed"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• At least one device is available"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Select a default notes app to use the note-taking shortcut"</string>
@@ -1156,4 +1169,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant attention on"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
     <string name="install_app" msgid="5066668100199613936">"Install app"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone and Camera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app use"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"See recent access"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Done"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Expand and show options"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Collapse"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Close this app"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> closed"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Manage service"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Manage access"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"In use by phone call"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Recently used in phone call"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"In use by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"In use by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"In use by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 16164f4..313f617 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hearing aids"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hearing aids"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Turning on…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
@@ -292,6 +295,8 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> limit"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"<xliff:g id="DATA_LIMIT">%s</xliff:g> warning"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Work apps"</string>
+    <!-- no translation found for quick_settings_work_mode_paused_state (6681788236383735976) -->
+    <skip />
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Night Light"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"On at sunset"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Until sunrise"</string>
@@ -415,6 +420,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Sharing pauses when you switch apps"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Share this app instead"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Switch back"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"App switch"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
@@ -856,6 +865,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Magnify full screen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Open magnification settings"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Close magnification settings"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Drag corner to resize"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Allow diagonal scrolling"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Resize"</string>
@@ -1043,6 +1055,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem reading your battery meter"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tap for more information"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No alarm set"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"enter screen lock"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerprint sensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Authenticate"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"enter device"</string>
@@ -1105,7 +1118,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant is listening"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}other{# notifications}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetaking"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Note-taking"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Note-taking, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Broadcasting"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Stop broadcasting <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"If you broadcast <xliff:g id="SWITCHAPP">%1$s</xliff:g> or change the output, your current broadcast will stop"</string>
@@ -1123,7 +1137,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Open <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"To add the Wallet app as a shortcut, make sure that the app is installed"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"To add the Wallet app as a shortcut, make sure that at least one card has been added"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"To add the QR code scanner as a shortcut, make sure that a camera app is installed"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"To add the Home app as a shortcut, make sure that the app is installed"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• At least one device is available"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Select a default notes app to use the note-taking shortcut"</string>
@@ -1156,4 +1169,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant attention on"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
     <string name="install_app" msgid="5066668100199613936">"Install app"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone and Camera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app use"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"See recent access"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Done"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Expand and show options"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Collapse"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Close this app"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> closed"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Manage service"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Manage access"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"In use by phone call"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Recently used in phone call"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"In use by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"In use by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"In use by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Recently used by <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 5aa9823..db7133c 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‎‎‏‎‎‎‎‎‎‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‏‎‎‎‏‏‏‏‏‏‎‎‎‏‏‎Tap to view‎‏‎‎‏‎"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‏‏‎‎‎‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‎‎‎‎‎‏‎‎‎‎‎‏‏‏‏‏‎Error saving screen recording‎‏‎‎‏‎"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‎‎‎‎‎‎‎Error starting screen recording‎‏‎‎‏‎"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎‏‎‎‏‎‏‏‎‎‏‏‏‎‎‏‎‏‎‏‎Viewing full screen‎‏‎‎‏‎"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‎‎‎‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‏‏‏‎To exit, swipe down from the top.‎‏‎‎‏‎"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‎‏‏‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‎‎‎‏‎‎‎‎Got it‎‏‎‎‏‎"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‏‏‎‏‏‎‏‎‏‏‎‏‎‎‎‏‏‎Back‎‏‎‎‏‎"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎Home‎‏‎‎‏‎"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‎‎‏‏‎‎‎‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‏‎‎‎‎‎Menu‎‏‎‎‏‎"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‎‏‎Audio‎‏‎‎‏‎"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‎Headset‎‏‎‎‏‎"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‎‏‏‎‏‎‏‏‏‎‎‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‏‎‏‎‎‎‏‏‏‏‎‎‎‏‏‏‎‎‏‎‎‎Input‎‏‎‎‏‎"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‏‏‎‎‎‎‎‎‎‎‎‏‎‎‎‎‏‎‎‏‏‎‎‏‏‎‎‏‎‎‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‎‏‎Hearing Aids‎‏‎‎‏‎"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‎‏‏‏‎Hearing aids‎‏‎‎‏‎"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‏‏‎‎‏‏‎‏‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‎Turning on…‎‏‎‎‏‎"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‏‎‏‎‎‎‎‎‏‎‎‎‎‏‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‎‏‏‏‏‎‎‎‎‎‎‎‎Auto-rotate‎‏‎‎‏‎"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‎‏‏‏‎‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‎‎‎‎‏‏‎‎‏‏‎‏‏‎‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‎‎Auto-rotate screen‎‏‎‎‏‎"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‎‏‏‏‎‎‎‏‎‎‏‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‏‎‏‏‎‎‏‎‏‎‎‏‎‏‏‎‏‏‎‎‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="DATA_LIMIT">%s</xliff:g>‎‏‎‎‏‏‏‎ limit‎‏‎‎‏‎"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‏‏‏‎‏‏‎‎‎‎‎‏‎‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‏‎‎‏‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="DATA_LIMIT">%s</xliff:g>‎‏‎‎‏‏‏‎ warning‎‏‎‎‏‎"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‎‎‏‎‏‎‎‎‎‎‏‎‎‎‏‎‎‏‎‎‎‏‏‎‎‎‎‏‎Work apps‎‏‎‎‏‎"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‏‎‎‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‏‎‏‎‎‎‎‏‎‏‎‏‎‎‎‎Paused‎‏‎‎‏‎"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‏‎‏‎‏‎‎‏‏‏‎‏‎‎‎‏‎‏‏‎‏‏‏‏‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‎‎‎Night Light‎‏‎‎‏‎"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‎‎‎‏‎‏‏‎‎‏‏‎‎‎‎‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‏‏‏‎‏‎‏‏‏‎‎‎‎‎‏‎‎On at sunset‎‏‎‎‏‎"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‏‎‎‎‏‎‎‎‏‎‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‏‏‎‎‎‎‎‎‎‎‎‎‏‎‎‎‎‎‏‏‎‏‎‏‎‏‎Until sunrise‎‏‎‎‏‎"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‎‎‏‎‎‎‏‎‏‎‏‎‎‎‏‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎When you’re sharing, recording, or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, and audio and video.‎‏‎‎‏‎"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‏‏‎‎‏‏‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎When you’re sharing, recording, or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video.‎‏‎‎‏‎"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‏‎‏‏‎‏‎‏‎‏‎‏‏‏‎‎‎Start‎‏‎‎‏‎"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎Sharing pauses when you switch apps‎‏‎‎‏‎"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‎Share this app instead‎‏‎‎‏‎"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‏‏‏‎‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‏‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎Switch back‎‏‎‎‏‎"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‏‏‏‎‎‎‏‎‏‎‏‏‎‎‏‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎‎‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‎‏‎App switch‎‏‎‎‏‎"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‎Blocked by your IT admin‎‏‎‎‏‎"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‎Screen capturing is disabled by device policy‎‏‎‎‏‎"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‎‏‏‏‎‏‎‎‎‏‎‎‎‎‎‏‎‏‎‏‏‏‏‎‏‏‎‎‏‎‎‏‏‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‎Clear all‎‏‎‎‏‎"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‎‏‏‎‎‎‏‎‏‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‎Magnify full screen‎‏‎‎‏‎"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‏‎Magnify part of screen‎‏‎‎‏‎"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎Open magnification settings‎‏‎‎‏‎"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‎‏‏‎‏‎‏‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎Close magnification settings‎‏‎‎‏‎"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‎‏‎‏‏‏‎‏‎‎‏‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‎‏‎Exit edit mode‎‏‎‎‏‎"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‏‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‎‎‏‎‎Drag corner to resize‎‏‎‎‏‎"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‎‏‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‎‏‏‏‎‎Allow diagonal scrolling‎‏‎‎‏‎"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‎‎‏‎‏‏‏‎‏‎‏‏‎‏‏‎‏‎‏‏‎‏‏‏‏‏‎Resize‎‏‎‎‏‎"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‏‎‏‏‎‎‎‏‏‏‎‎‏‏‏‏‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‏‎‎Problem reading your battery meter‎‏‎‎‏‎"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‎‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‎‎‏‏‏‏‎‏‏‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‎‎‏‎‎‎‏‎‎‎‏‏‎Tap for more information‎‏‎‎‏‎"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‎‏‏‎No alarm set‎‏‎‎‏‎"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‏‎‏‏‎‎‎‎‏‎‏‎‎‏‏‎‏‏‎‎‏‎‏‎‏‎‎‎‎‎‎‏‎‎‏‎‏‎‎‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎enter screen lock‎‏‎‎‏‎"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‏‎‎‎‎‎‎‎‏‏‎‏‏‏‎‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‏‎‎‎Fingerprint sensor‎‏‎‎‏‎"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‎authenticate‎‏‎‎‏‎"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‏‎‎‏‏‏‎‏‎‎‎‎‎‎‎‏‎‎enter device‎‏‎‎‏‎"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‎‏‏‎‎‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‎‏‎‎‏‎‏‏‎‎‎‎‎‎‎‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‎‎Assistant is listening‎‏‎‎‏‎"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‏‎‎‏‏‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‎# notification‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‏‎‎‏‏‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‎# notifications‎‏‎‎‏‎}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎‎‏‏‏‎‎‏‏‎‏‏‎‏‏‎‎‏‎‏‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>‎‏‎‎‏‏‏‎, ‎‏‎‎‏‏‎<xliff:g id="TEMPERATURE">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‏‏‎‏‏‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‏‎‎‎‎‎‎Notetaking‎‏‎‎‏‎"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‎‏‏‎‎‎‏‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‎‎‎Note-taking‎‏‎‎‏‎"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‏‎‎‎‎‎‏‎‏‎‎‎‎‏‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‎‎‎‏‎‎‎‎‏‏‏‎‎‎‏‎Note-taking, ‎‏‎‎‏‏‎<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‎‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‎‏‏‎‏‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‎‎Broadcasting‎‏‎‎‏‎"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎‏‎‎‎‏‏‏‎‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‏‏‎‎‏‏‎Stop broadcasting ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‎‎‎‎‎‎‏‎If you broadcast ‎‏‎‎‏‏‎<xliff:g id="SWITCHAPP">%1$s</xliff:g>‎‏‎‎‏‏‏‎ or change the output, your current broadcast will stop‎‏‎‎‏‎"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‏‏‎‏‏‎‎‏‏‎‏‏‏‏‎‏‏‏‏‎‎‏‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‎Open ‎‏‎‎‏‏‎<xliff:g id="APPNAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‏‎‎‏‏‎‏‎‏‎‏‏‎‎‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‎To add the Wallet app as a shortcut, make sure the app is installed‎‏‎‎‏‎"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‏‎‎‏‎‏‏‎‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‎‏‎‎‏‏‏‎‏‎‎‏‏‎‏‎‎‏‏‎‏‎‏‎‎To add the Wallet app as a shortcut, make sure at least one card has been added‎‏‎‎‏‎"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‎‏‎‎‏‎‎‏‎‏‎‏‏‏‏‎‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎To add the QR code scanner as a shortcut, make sure a camera app is installed‎‏‎‎‏‎"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‏‎‏‏‎‏‏‎‎‏‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‎‏‎‏‏‎‎‎‎To add the Home app as a shortcut, make sure the app is installed‎‏‎‎‏‎"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‎‎‎‏‏‎‎‏‎‎‏‎‏‎‏‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‏‏‏‎• At least one device is available‎‏‎‎‏‎"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‎‏‎‎‏‏‎‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‏‏‏‎‎‏‎‏‏‎‎‎‏‎‎Select a default notes app to use the notetaking shortcut‎‏‎‎‏‎"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‎‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‎‏‏‎‏‎‏‏‎‎‎‎‎‏‏‏‎‏‏‎Assistant attention on‎‏‎‎‏‎"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎‏‏‏‏‏‎‎‎‏‏‎‏‎‏‎‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‎‎‎‏‎Set default notes app in Settings‎‏‎‎‏‎"</string>
     <string name="install_app" msgid="5066668100199613936">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‎‎‎‎‎Install app‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‏‏‏‏‎‏‎‏‎‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‎‏‎‏‎‎‎‏‏‏‎Microphone &amp; Camera‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‏‏‎‏‏‏‏‎‏‏‎‎‎‏‎‎‎‏‏‎‎‏‎‎‎‏‎Recent app use‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‏‏‏‏‎‎‏‎‎‏‏‎‎‎‎‎‏‎‏‏‎‏‎‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‏‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎See recent access‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‎‏‎‎‏‏‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‏‎‎‎‏‏‏‎‎‏‏‎‎‏‎‏‎‎‏‏‎‎‎‏‎‏‏‏‎Done‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‏‏‎‎‎‏‎Expand and show options‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‏‎‏‏‎‎‏‏‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‏‎‏‎‏‏‎Collapse‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‏‎‎‏‎‎‎‏‎‎‏‏‏‎‎Close this app‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‏‎‏‎‎‏‎‏‎‏‏‎‎‎‏‏‏‏‎‎‎‎‏‎‎‎‏‎‏‎‎‏‏‎‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ closed‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‎‎‎‏‎‏‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‎‎‏‎‏‏‎‎‏‏‏‎‏‎‎‎Manage service‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‏‏‏‏‎‏‏‏‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‎Manage access‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‏‏‎‏‏‏‏‎‏‎‎In use by phone call‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‏‏‎‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‏‏‎‏‎‎‏‎‎‏‎‏‏‎‏‏‏‎‎‎‏‏‎‎‎‎‎Recently used in phone call‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‏‎‏‎‏‎‎‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‎‎‏‎‏‏‎‎‏‏‏‏‎‎‏‎‏‏‎‏‎‎‎‎‏‏‎‎‎‎In use by ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‏‎‏‎‏‏‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‎‏‎‏‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎Recently used by ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‏‎‏‏‎‏‏‎‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‏‎‎‎‎‎‎‏‎‏‏‏‏‎In use by ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ (‎‏‎‎‏‏‎<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>‎‏‎‎‏‏‏‎)‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‏‏‎‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‏‏‏‎‏‎Recently used by ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ (‎‏‎‎‏‏‎<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>‎‏‎‎‏‏‏‎)‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‏‏‏‎‏‎‎‎‏‎‎‏‏‏‎‏‎‎‏‎‎‏‎‎‏‏‎‎‎‎‏‎‎‎‎‏‎‎‏‏‏‎‎‏‏‎‏‎‏‏‎‎‏‏‏‎In use by ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ (‎‏‎‎‏‏‎<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>‎‏‎‎‏‏‏‎ • ‎‏‎‎‏‏‎<xliff:g id="PROXY_LABEL">%3$s</xliff:g>‎‏‎‎‏‏‏‎)‎‏‎‎‏‎"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‎‏‏‎‎‎‏‎‏‎‏‎‏‎‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‎Recently used by ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ (‎‏‎‎‏‏‎<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>‎‏‎‎‏‏‏‎ • ‎‏‎‎‏‏‎<xliff:g id="PROXY_LABEL">%3$s</xliff:g>‎‏‎‎‏‏‏‎)‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 8fc4dae..23d2edc 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Presiona para ver"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Se produjo un error al guardar la grabación de pantalla"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Error al iniciar la grabación de pantalla"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visualización en pantalla completa"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Para salir, desliza el dedo hacia abajo desde la parte superior."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendido"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Atrás"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Página principal"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Audífonos"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audífonos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activando…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Girar la pantalla automáticamente"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Límite de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Advertencia de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Apps de trabajo"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"En pausa"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Luz nocturna"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Al atardecer"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Hasta el amanecer"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cuando compartas, grabes o transmitas contenido, Android podrá acceder a todo lo que sea visible en la pantalla o que reproduzcas en el dispositivo. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cuando compartas, grabes o transmitas una app, Android podrá acceder a todo el contenido que se muestre o que reproduzcas en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Iniciar"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"El uso compartido se detiene al cambiar de app"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Compartir esta app en su lugar"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Cambiar de cuenta"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Cambio de app"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueada por tu administrador de TI"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La captura de pantalla está inhabilitada debido a la política del dispositivo"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Cerrar todo"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar pantalla completa"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte de la pantalla"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Abrir la configuración de ampliación"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Cerrar configuración de ampliación"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"Salir del modo de edición"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arrastra la esquina para cambiar el tamaño"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Desplazamiento en diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Cambiar tamaño"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problema al leer el medidor de batería"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Presiona para obtener más información"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No establecida"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ingresa el bloqueo pantalla"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de huellas dactilares"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ingresar al dispositivo"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistente está escuchando"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificación}many{# notificaciones}other{# notificaciones}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Tomar notas"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Tomar notas"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Tomar notas, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transmitiendo"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"¿Quieres dejar de transmitir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si transmites <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambias la salida, tu transmisión actual se detendrá"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Abrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Para agregar la app de Billetera como acceso directo, asegúrate de haber instalado la app"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Para agregar la app de Billetera como acceso directo, asegúrate de haber agregado al menos una tarjeta"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Para agregar el escáner de código QR como acceso directo, asegúrate de haber instalado la app de la cámara"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Para agregar la app de Home como acceso directo, asegúrate de haber instalado la app"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Hay al menos un dispositivo disponible"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Selecciona una app de notas predeterminada para usar el acceso directo de toma de notas"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Asistente está prestando atención"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la app de notas predeterminada en Configuración"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar app"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrófono y cámara"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso reciente de una app"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver accesos recientes"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Listo"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Expandir y mostrar opciones"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Contraer"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Cerrar esta app"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> cerrada"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Administrar servicio"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Administrar acceso"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"En uso por una llamada telefónica"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Uso reciente en una llamada telefónica"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"En uso por <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Uso reciente en <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"En uso por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Uso reciente en <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"En uso por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Uso reciente en <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 456d4a8..1eb0abc 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Toca para verla"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"No se ha podido guardar la grabación de pantalla"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"No se ha podido empezar a grabar la pantalla"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visualización en pantalla completa"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Para salir, desliza de arriba abajo."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendido"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Atrás"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Inicio"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Audífonos"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audífonos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activando…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Girar pantalla automáticamente"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Límite de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Advertencia de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Aplicaciones de trabajo"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"En pausa"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Luz nocturna"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Al atardecer"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Hasta el amanecer"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cuando compartes, grabas o envías contenido, Android puede acceder a todo lo que se muestre en la pantalla o se reproduzca en tu dispositivo. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cuando compartes, grabas o envías una aplicación, Android puede acceder a todo lo que se muestre o se reproduzca en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Empezar"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"El uso compartido se detiene al cambiar de aplicación"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Compartir esta aplicación"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Volver"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Cambio de aplicación"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueado por tu administrador de TI"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Las capturas de pantalla están inhabilitadas debido a la política de dispositivos"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar pantalla completa"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte de la pantalla"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Abrir ajustes de ampliación"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Cerrar ajustes de ampliación"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arrastra la esquina para cambiar el tamaño"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir ir en diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Cambiar tamaño"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"No se ha podido leer el indicador de batería"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toca la pantalla para consultar más información"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ninguna alarma puesta"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"Poner bloqueo de pantalla"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de huellas digitales"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticarte"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"acceder al dispositivo"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"El Asistente está escuchando"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificación}many{# notificaciones}other{# notificaciones}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Tomar notas"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Toma de notas"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Toma de notas, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Emitiendo"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"¿Dejar de emitir <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si emites <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambias la salida, tu emisión actual se detendrá"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Abrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Para añadir la aplicación Wallet como acceso directo, asegúrate de que está instalada"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Para añadir la aplicación Wallet como acceso directo, asegúrate de haber añadido al menos una tarjeta"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Para añadir el escáner de códigos QR como acceso directo, asegúrate de que hay instalada una aplicación de cámara"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Para añadir la aplicación Home como acceso directo, asegúrate de que está instalada"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Al menos un dispositivo debe estar disponible"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Selecciona una aplicación de notas predeterminada para usar el acceso directo de toma de notas"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"El Asistente está activado"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la aplicación de notas predeterminada en Ajustes"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrófono y cámara"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso reciente en aplicaciones"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver acceso reciente"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Hecho"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Desplegar y mostrar opciones"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Ocultar"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Cerrar esta aplicación"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Se ha cerrado <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Gestionar servicio"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Gestionar acceso"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"En uso por llamada telefónica"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Usado recientemente en llamada telefónica"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"En uso por <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Usado recientemente por <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"En uso por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Usado recientemente por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"En uso por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Usado recientemente por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 795b9f9..3881635 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Puudutage kuvamiseks"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Viga ekraanisalvestise salvestamisel"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Viga ekraanikuva salvestamise alustamisel"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Kuvamine täisekraanil"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Väljumiseks pühkige ülevalt alla."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Selge"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Tagasi"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Kodu"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menüü"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Heli"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Peakomplekt"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Sisend"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Kuuldeaparaadid"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Kuuldeaparaadid"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Sisselülitamine …"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. pööramine"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Kuva automaatne pööramine"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Limiit: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"<xliff:g id="DATA_LIMIT">%s</xliff:g> hoiatus"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Töörakendused"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Peatatud"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Öövalgus"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Sissel. päikeselooj."</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Kuni päikesetõusuni"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kui jagate, salvestate või kannate üle, on Androidil juurdepääs kõigele, mis on teie ekraanikuval nähtaval või mida teie seadmes esitatakse. Seega olge ettevaatlik selliste andmetega nagu paroolid, makseteave, sõnumid, fotod ning heli ja video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kui jagate, salvestate või kannate rakendust üle, on Androidil juurdepääs kõigele, mida selles rakenduses kuvatakse või esitatakse. Seega olge paroolide, makseteabe, sõnumite, fotode, heli ja videoga ettevaatlik."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Alusta"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Rakenduste vahetamisel jagamine peatatakse"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Jaga hoopis seda rakendust"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Lülitu tagasi"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Rakenduse vahetamine"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokeeris teie IT-administraator"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekraanikuva jäädvustamine on seadmereeglitega keelatud"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tühjenda kõik"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Täisekraani suurendamine"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekraanikuva osa suurendamine"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Ava suurendamisseaded"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Sule suurendamisseaded"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Suuruse muutmiseks lohistage nurka"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Luba diagonaalne kerimine"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Muuda suurust"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Probleem akumõõdiku lugemisel"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Puudutage lisateabe saamiseks"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Äratust pole"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"sisesta ekraanilukk"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sõrmejäljeandur"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentimiseks"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"seadmesse sisenemiseks"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistent kuulab"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# märguanne}other{# märguannet}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Märkmete tegemine"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Märkmete tegemine"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Märkmete tegemine <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Edastamine"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Kas peatada rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> ülekandmine?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Kui kannate rakendust <xliff:g id="SWITCHAPP">%1$s</xliff:g> üle või muudate väljundit, peatatakse teie praegune ülekanne"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Ava <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Rahakotirakenduse otsetee lisamiseks veenduge, et rakendus oleks installitud"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Rahakotirakenduse otsetee lisamiseks veenduge, et vähemalt üks kaart oleks lisatud"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR-koodi skanneri otsetee lisamiseks veenduge, et kaamerarakendus oleks installitud"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Rakenduse Home otsetee lisamiseks veenduge, et rakendus oleks installitud"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Vähemalt üks seade on saadaval"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Valige märkmete tegemise vaikerakendus, et kasutada märkmete tegemise otseteed"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistent on aktiveeritud"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Määrake seadetes märkmete vaikerakendus."</string>
     <string name="install_app" msgid="5066668100199613936">"Installi rakendus"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon ja kaamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Rakenduste hiljutine kasutamine"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Kuva hiljutine juurdepääs"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Valmis"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Laiendamine ja valikute kuvamine"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Ahendamine"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Sule see rakendus"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> on suletud"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Teenuse haldamine"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Juurdepääsu haldamine"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Seda kasutab telefonikõne"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Kasutati hiljuti telefonikõne jaoks"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Seda kasutab <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Kasutas hiljuti rakendus <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Seda kasutab <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Kasutas hiljuti rakendus <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Seda kasutab <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Kasutas hiljuti rakendus <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 44e82ab..0121abc 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Sakatu ikusteko"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Errore bat gertatu da pantaila-grabaketa gordetzean"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Errore bat gertatu da pantaila grabatzen hastean"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Pantaila osoa ikusgai"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Irteteko, pasatu hatza goitik behera."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Ados"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Atzera"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Hasiera"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menua"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audioa"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Entzungailua"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Sarrera"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Audifonoak"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audifonoak"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktibatzen…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Biratze automatikoa"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Biratu pantaila automatikoki"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Muga: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Abisua: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Laneko aplikazioak"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Pausatuta"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Gaueko argia"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Ilunabarrean"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Ilunabarrera arte"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Edukia partekatzen, grabatzen edo igortzen ari zarenean, pantailan ikusgai dagoen edo gailuan erreproduzitzen ari den guztia atzi dezake Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin, argazkiekin, audioekin eta bideoekin, besteak beste."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Aplikazio bat partekatzen, grabatzen edo igortzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztia atzi dezake Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin, argazkiekin, audioekin eta bideoekin, besteak beste."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Hasi"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Partekatzea pausatu egiten da aplikazioz aldatzean"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Partekatu aplikazioa"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Itzuli"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Aplikazioz aldatzeko aukera"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IKT saileko administratzaileak blokeatu du"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Pantaila-kapturak egiteko aukera desgaituta dago, gailu-gidalerroei jarraikiz"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Garbitu guztiak"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Handitu pantaila osoa"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Handitu pantailaren zati bat"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Ireki luparen ezarpenak"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Itxi luparen ezarpenak"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arrastatu izkina bat tamaina aldatzeko"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Eman diagonalki gora eta behera egiteko aukera erabiltzeko baimena"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Aldatu tamaina"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Arazo bat izan da bateria-neurgailua irakurtzean"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Informazio gehiago lortzeko, sakatu hau"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ez da ezarri alarmarik"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"erabili pantailaren blokeoa"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Hatz-marken sentsorea"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentifikatu"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"sartu gailuan"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Laguntzailea entzuten ari da"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# jakinarazpen}other{# jakinarazpen}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Oharrak idaztea"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Oharrak idaztea"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Oharrak idaztea, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Igortzen"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioaren audioa igortzeari utzi nahi diozu?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> aplikazioaren audioa igortzen baduzu, edo audio-irteera aldatzen baduzu, une hartako igorpena eten egingo da"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Ireki <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Diru-zorroa aplikazioa lasterbide gisa gehitzeko, ziurtatu aplikazioa instalatuta dagoela."</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Diru-zorroa aplikazioa lasterbide gisa gehitzeko, ziurtatu gutxienez txartel bat gehitu dela."</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR kodeen eskanerra lasterbide gisa gehitzeko, ziurtatu kameraren aplikazioa instalatuta dagoela."</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home aplikazioa lasterbide gisa gehitzeko, ziurtatu aplikazioa instalatuta dagoela."</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Gutxienez gailu bat erabilgarri dago."</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Oharrak idazteko lasterbidea erabiltzeko, hautatu oharretarako aplikazio lehenetsia."</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Laguntzailea zerbitzuak arreta jarrita dauka"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ezarri oharren aplikazio lehenetsia ezarpenetan"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalatu aplikazioa"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofonoa eta kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Aplikazioen azken erabilera"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ikusi azkenaldiko sarbidea"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Eginda"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Zabaldu eta erakutsi aukerak"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Tolestu"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Itxi aplikazioa"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Itxi da <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Kudeatu zerbitzua"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Kudeatu sarbidea"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Telefono-dei batek darabil"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Telefono-dei batean erabili da duela gutxi"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak darabil"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak erabili du duela gutxi"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak darabil (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) aplikazioak erabili du duela gutxi"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak darabil (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) aplikazioak erabili du duela gutxi"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 985860a..a22f6da 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"برای مشاهده ضربه بزنید"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"خطا در ذخیره‌سازی ضبط صفحه‌نمایش"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"خطا هنگام شروع ضبط صفحه‌نمایش"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"درحال مشاهده در حالت تمام‌صفحه"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"برای خروج، از بالای صفحه تند به‌پایین بکشید."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"متوجه‌ام"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"برگشت"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"صفحهٔ اصلی"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"منو"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"صوت"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"هدست"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ورودی"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"سمعک"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"سمعک"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"روشن کردن…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"چرخش خودکار"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"چرخش خودکار صفحه‌نمایش"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> محدودیت"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"موقتاً متوقف شد"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"‏وقتی درحال هم‌رسانی، ضبط، یا پخش محتوا هستید، Android به همه محتوایی که در صفحه‌تان نمایان است یا در دستگاهتان پخش می‌شود دسترسی دارد. درنتیجه مراقب چیزهایی مثل گذرواژه‌ها، جزئیات پرداخت، پیام‌ها، عکس‌ها، و صدا و تصویر باشید."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"‏وقتی درحال هم‌رسانی، ضبط، یا پخش محتوای برنامه‌ای هستید، Android به همه محتوایی که در آن برنامه نمایان است یا پخش می‌شود دسترسی دارد. درنتیجه مراقب چیزهایی مثل گذرواژه‌ها، جزئیات پرداخت، پیام‌ها، عکس‌ها، و صدا و تصویر باشید."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"شروع"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"وقتی برنامه‌ها را تغییر می‌دهید، هم‌رسانی متوقف می‌شود"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"درعوض هم‌رسانی این برنامه"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"تغییر به حالت قبل"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"تغییر برنامه"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"سرپرست فناوری اطلاعات آن را مسدود کرده است"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"«ضبط صفحه‌نمایش» به‌دلیل خط‌مشی دستگاه غیرفعال است"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"پاک کردن همه موارد"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"درشت‌نمایی تمام‌صفحه"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"درشت‌نمایی بخشی از صفحه"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"باز کردن تنظیمات درشت‌نمایی"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"بستن تنظیمات درشت‌نمایی"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"برای تغییر اندازه، گوشه را بکشید"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"اجازه دادن برای پیمایش قطری"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"تغییر اندازه"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"مشکلی در خواندن میزان باتری وجود دارد"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"برای اطلاعات بیشتر ضربه بزنید"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"هشداری تنظیم نشده"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"وارد کردن قفل صفحه"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"حسگر اثرانگشت"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"اصالت‌سنجی کردن"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"وارد شدن به دستگاه"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"«دستیار» درحال گوش کردن است"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# اعلان}one{# اعلان}other{# اعلان}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>، <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"یادداشت‌برداری"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"یادداشت‌برداری"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"یادداشت‌برداری، <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"همه‌فرستی"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"همه‌فرستی <xliff:g id="APP_NAME">%1$s</xliff:g> متوقف شود؟"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"اگر <xliff:g id="SWITCHAPP">%1$s</xliff:g> را همه‌فرستی کنید یا خروجی را تغییر دهید، همه‌فرستی کنونی متوقف خواهد شد"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"باز کردن <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"برای افزودن برنامه «کیف پول» به‌عنوان میان‌بر، مطمئن شوید این برنامه نصب شده باشد"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"برای افزودن برنامه «کیف پول» به‌عنوان میان‌بر، مطمئن شوید حداقل یک کارت اضافه شده باشد"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"برای افزودن «کدخوان پاسخ‌سریع» به‌عنوان میان‌بر، مطمئن شوید برنامه دوربین نصب شده باشد"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"‏برای افزودن برنامه Home به‌عنوان میان‌بر، مطمئن شوید این برنامه نصب شده باشد"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• حداقل یک دستگاه دردسترس باشد"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"برای استفاده از میان‌بر یادداشت‌برداری، برنامه یادداشت پیش‌فرضی را انتخاب کنید"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"توجه «دستیار» روشن است"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"برنامه پیش‌فرض یادداشت را در «تنظیمات» تنظیم کنید"</string>
     <string name="install_app" msgid="5066668100199613936">"نصب برنامه"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"میکروفون و دوربین"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"استفاده اخیر برنامه‌ها"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"دیدن دسترسی اخیر"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"تمام"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"ازهم بازکردن و نمایش گزینه‌ها"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"جمع کردن"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"بستن این برنامه"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> بسته شد"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"مدیریت سرویس"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"مدیریت دسترسی"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"تماس تلفنی از آن استفاده می‌کند"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"اخیراً تماس تلفنی از آن استفاده کرده است"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> از آن استفاده می‌کند"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"اخیراً <xliff:g id="APP_NAME">%1$s</xliff:g> از آن استفاده کرده است"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> از آن استفاده می‌کند (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"اخیراً <xliff:g id="APP_NAME">%1$s</xliff:g> از آن استفاده کرده است (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> از آن استفاده می‌کند (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"اخیراً <xliff:g id="APP_NAME">%1$s</xliff:g> از آن استفاده کرده است (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 9128824..d87f2f6 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Napauta näyttääksesi"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Virhe näyttötallenteen tallentamisessa"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Virhe näytön tallennuksen aloituksessa"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Koko näytön tilassa"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Sulje palkki pyyhkäisemällä alas ruudun ylälaidasta."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Selvä"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Takaisin"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Aloitus"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Valikko"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ääni"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Syöttölaite"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Kuulolaitteet"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Kuulolaitteet"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Otetaan käyttöön…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automaattinen kääntö"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Käännä näyttöä automaattisesti."</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"kiintiö <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"<xliff:g id="DATA_LIMIT">%s</xliff:g> – varoitus"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Työsovellukset"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Keskeytetty"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Yövalo"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Auringon laskiessa"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Auringonnousuun"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kun jaat, tallennat tai striimaat, 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ä, kuvia, audiota tai videoita."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kun jaat, tallennat tai striimaat, Android saa pääsyn kaikkeen sovelluksella näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä, kuvia, audiota tai videoita."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Aloita"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Jakaminen keskeytyy, kun vaihdat sovelluksia"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Jaa tämä sovellus"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Vaihda takaisin"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Sovelluksen vaihto"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT-järjestelmänvalvojasi estämä"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Kuvakaappaus on poistettu käytöstä laitekäytännön perusteella"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tyhjennä kaikki"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Koko näytön suurennus"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Suurenna osa näytöstä"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Avaa suurennusasetukset"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Sulje suurennusasetukset"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Muuta kokoa vetämällä kulmaa"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Salli diagonaalinen vierittäminen"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Muuta kokoa"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Ongelma akkumittarin lukemisessa"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Saat lisätietoja napauttamalla"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ei herätyksiä"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"käytä näytön lukitustapaa"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sormenjälkitunnistin"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"todentaaksesi"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"avataksesi laitteen"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant kuuntelee"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ilmoitus}other{# ilmoitusta}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Muistiinpanojen tekeminen"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Muistiinpanojen tekeminen"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Muistiinpanot, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Lähettää"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Lopetetaanko <xliff:g id="APP_NAME">%1$s</xliff:g>-sovelluksen lähettäminen?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jos lähetät <xliff:g id="SWITCHAPP">%1$s</xliff:g>-sovellusta tai muutat ulostuloa, nykyinen lähetyksesi loppuu"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Avaa <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Jos haluat lisätä Wallet-sovelluksen pikakuvakkeena, varmista, että se on asennettuna"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Jos haluat lisätä Wallet-sovelluksen pikakuvakkeena, varmista, että lisättynä on vähintään yksi kortti"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Jos haluat lisätä QR-koodiskannerin pikakuvakkeena, varmista, että kamerasovellus on asennettuna"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Jos haluat lisätä Home-sovelluksen pikakuvakkeena, varmista, että se on asennettuna"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Ainakin yksi laite on käytettävissä"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Valitse muistiinpanojen tekemisen oletussovellus, jota käytetään pikakuvakkeen avulla"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant on aktiivinen"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Aseta oletusmuistiinpanosovellus Asetuksista"</string>
     <string name="install_app" msgid="5066668100199613936">"Asenna sovellus"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofoni ja kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Sovellusten viimeaikainen käyttö"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Katso viimeaikainen käyttö"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Valmis"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Laajenna ja näytä vaihtoehdot"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Tiivistä"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Sulje tämä sovellus"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> suljettu"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Ylläpidä palvelua"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Hallinnoi pääsyä"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Puhelun käytössä"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Käytetty äskettäin puhelussa"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Tämän käytössä: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"<xliff:g id="APP_NAME">%1$s</xliff:g> käytti tätä äskettäin"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Tämän käytössä: <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> käytti tätä äskettäin (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Tämän käytössä: <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> käytti tätä äskettäin (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index c118026..1c6b508 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Touchez pour afficher"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Erreur d\'enregistrement de l\'écran"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Une erreur s\'est produite lors du démarrage de l\'enregistrement d\'écran"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Affichage plein écran"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Pour quitter, balayez du haut vers le bas."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Précédent"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Domicile"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Écouteurs"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrée"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Prothèses auditives"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Prothèses auditives"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activation en cours…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotation automatique"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotation automatique de l\'écran"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Limite : <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Avertissement : <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Applications professionnelles"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"En pause"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Éclairage nocturne"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Activé la nuit"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Jusqu\'à l\'aube"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Lorsque vous partagez, enregistrez ou diffusez, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages, les photos et les contenus audio et vidéo."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Lorsque vous partagez, enregistrez ou diffusez une application, Android a accès à tout ce qui est visible sur votre écran ou lu sur cette application. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages, les photos et les contenus audio et vidéo."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Commencer"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Le partage s\'interrompt lorsque vous changez d\'application"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Partager plutôt cette application"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Revenir en arrière"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Commutateur d\'application"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquée par votre administrateur informatique"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La fonctionnalité de capture d\'écran est désactivée par l\'application Device Policy"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Agrandir la totalité de l\'écran"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Agrandir une partie de l\'écran"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Ouvrir les paramètres d\'agrandissement"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Fermer les paramètres d\'agrandissement"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Faire glisser le coin pour redimensionner"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Autoriser défilement diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionner"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Un problème est survenu lors de la lecture du niveau de charge de la pile"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Touchez pour en savoir plus"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Aucune alarme définie"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"entrer verrouillage de l\'écran"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Capteur d\'empreintes digitales"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"authentifier"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"accéder à l\'appareil"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"L\'Assistant est à l\'écoute"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}one{# notification}many{# de notifications}other{# notifications}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Prise de note"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Prise de note"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Prise de note, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Diffusion en cours…"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Arrêter la diffusion de <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si vous diffusez <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou changez la sortie, votre diffusion actuelle s\'arrêtera"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Ouvrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Pour ajouter l\'application portefeuille sous forme de raccourci, assurez-vous que l\'application est installée"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Pour ajouter l\'application Portefeuille sous forme de raccourci, assurez-vous qu\'au moins une carte a été ajoutée"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Pour ajouter le numériseur de code QR sous forme de raccourci, assurez-vous qu\'une application d\'appareil photo est installée"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Pour ajouter l\'application Home sous forme de raccourci, assurez-vous que l\'application est installée"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• qu\'au moins un appareil est utilisable;"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Sélectionnez une application de prise de notes par défaut pour utiliser le raccourci de prise de notes"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant à l\'écoute"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir l\'application de prise de notes par défaut dans les Paramètres"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer l\'application"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Microphone et appareil photo"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilisation récente d\'application"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Afficher l\'accès récent"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"OK"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Développer et afficher les options"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Réduire"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Fermer cette application"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> fermé"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Gérer le service"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Gérer l\'accès"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Utilisé par un appel téléphonique"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Récemment utilisé par un appel téléphonique"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Récemment utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Récemment utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Récemment utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index e0d94ab..420fd85 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Appuyez pour afficher"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Erreur lors de l\'enregistrement de l\'écran"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Erreur lors du démarrage de l\'enregistrement de l\'écran"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Affichage en plein écran"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Pour quitter, balayez l\'écran du haut vers le bas."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Retour"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Accueil"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Casque"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrée"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Appareils auditifs"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Appareils auditifs"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activation…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotation automatique"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotation automatique de l\'écran"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> au maximum"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Avertissement : <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Applis pro"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"En pause"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Éclairage nocturne"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Activé la nuit"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Jusqu\'à l\'aube"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Lorsque vous partagez, enregistrez ou castez, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention aux éléments tels que les mots de passe, détails de mode de paiement, messages, photos et contenus audio et vidéo."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Lorsque vous partagez, enregistrez ou castez une appli, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention aux éléments tels que les mots de passe, détails de mode de paiement, messages et contenus audio et vidéo."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Commencer"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Le partage est suspendu pendant le changement d\'application"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Partager cette application à la place"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Revenir"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Changement d\'application"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqué par votre administrateur informatique"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La capture d\'écran est désactivée conformément aux règles relatives à l\'appareil"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Agrandir tout l\'écran"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Agrandir une partie de l\'écran"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Ouvrir les paramètres d\'agrandissement"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Fermer les paramètres d\'agrandissement"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Faire glisser le coin pour redimensionner"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Autoriser le défilement diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionner"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Un problème est survenu au niveau de la lecture de votre outil de mesure de batterie"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Appuyer pour en savoir plus"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Pas d\'alarme définie"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"accéder au verrouillage de l\'écran"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Lecteur d\'empreinte digitale"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"s\'authentifier"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"accéder à l\'appareil"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"L\'Assistant écoute"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}one{# notification}many{# notifications}other{# notifications}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Prise de notes"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Prise de notes"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Prise de notes, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Diffusion…"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Arrêter la diffusion de <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Si vous diffusez <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou que vous modifiez le résultat, votre annonce actuelle s\'arrêtera"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Ouvrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Pour ajouter l\'appli Wallet comme raccourci, vérifiez que l\'appli est installée"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Pour ajouter l\'appli Wallet comme raccourci, assurez-vous qu\'au moins une carte a été ajoutée"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Pour ajouter le lecteur de code QR comme raccourci, assurez-vous qu\'une appli d\'appareil photo est installée"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Pour ajouter l\'application Home comme raccourci, vérifiez que l\'appli est installée"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Au moins un appareil est disponible"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Sélectionnez une appli de notes par défaut pour utiliser le raccourci de prise de notes"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant à l\'écoute"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir une appli de notes par défaut dans les paramètres"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer l\'appli"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Micro et caméra"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilisation récente par une appli"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Consulter les accès récents"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"OK"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Développer et afficher les options"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Réduire"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Fermer cette appli"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> fermé"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Gérer le service"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Gérer l\'accès"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"En cours d\'utilisation par l\'appel téléphonique"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Récemment utilisé lors d\'un appel téléphonique"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"En cours d\'utilisation par <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Récemment utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"En cours d\'utilisation par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Récemment utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"En cours d\'utilisation par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Récemment utilisé par <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 01c9850..3389dd8 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Toca para ver o contido"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Produciuse un erro ao gardar a gravación da pantalla"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Produciuse un erro ao iniciar a gravación da pantalla"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Vendo pantalla completa"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Para saír, pasa o dedo cara abaixo desde a parte superior."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendido"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Volver"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Inicio"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auriculares"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Audiófonos"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Audiófonos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Activando…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Xirar automaticamente"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Xirar pantalla automaticamente"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Límite de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Advertencia <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Aplicacións do traballo"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"En pausa"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Luz nocturna"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Activación ao solpor"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Ata o amencer"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cando compartes, gravas ou emites contido, Android ten acceso a todo o que se vexa na pantalla ou se reproduza no teu dispositivo. Polo tanto, debes ter coidado con determinada información, como os contrasinais, os detalles de pago, as mensaxes e as fotos, así como o contido de audio e de vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cando compartes, gravas ou emites unha aplicación, Android ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado con determinada información, como os contrasinais, os detalles de pago, as mensaxes e as fotos, así como o contido de audio e de vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Iniciar"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Cando cambias de aplicación, ponse en pausa o uso compartido"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Compartir esta aplicación"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Volver"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Cambio de aplicación"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"O teu administrador de TI bloqueou esta aplicación"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A política do dispositivo desactivou a opción de capturar a pantalla"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todo"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar pantalla completa"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Amplía parte da pantalla"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Abrir configuración da ampliación"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Pechar configuración de ampliación"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arrastrar a esquina para cambiar o tamaño"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir desprazamento diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Cambiar tamaño"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Produciuse un problema ao ler o medidor da batería"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toca para obter máis información"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Sen alarmas postas"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"introducir o bloqueo de pantalla"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de impresión dixital"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"poñer o dispositivo"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"O Asistente está escoitando"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificación}other{# notificacións}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Toma de notas"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Toma de notas"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Toma de notas (<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>)"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Difusión"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Queres deixar de emitir contido a través de <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se emites contido a través de <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou cambias de saída, a emisión en curso deterase"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Abrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Para engadir a aplicación Wallet como atallo, asegúrate de que estea instalada"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Para engadir a aplicación Wallet como atallo, asegúrate de que se incluíse polo menos unha tarxeta"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Para engadir o escáner de códigos QR como atallo, asegúrate de que a aplicación de cámara estea instalada"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Para engadir a aplicación Google Home como atallo, asegúrate de que estea instalada"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Ten que haber polo menos un dispositivo dispoñible"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Selecciona unha aplicación de notas predeterminada para usar o atallo de tomar notas"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"A atención do Asistente está activada"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Establece a aplicación de notas predeterminada en Configuración"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrófono e cámara"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente por parte de aplicacións"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver acceso recente"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Feito"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Despregar e mostrar opcións"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Contraer"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Pechar esta aplicación"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Pechouse <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Xestionar servizo"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Xestionar acceso"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"En uso por unha chamada telefónica"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"En uso recentemente por unha chamada telefónica"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"En uso por <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"En uso recentemente por <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"En uso por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"En uso recentemente por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"En uso por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"En uso recentemente por <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index e20d672..92ab77f 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"જોવા માટે ટૅપ કરો"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"સ્ક્રીન રેકોર્ડિંગ સાચવવામાં ભૂલ આવી"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"સ્ક્રીનને રેકૉર્ડ કરવાનું શરૂ કરવામાં ભૂલ"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"પૂર્ણ સ્ક્રીન જોઈ રહ્યાં છે"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"બહાર નીકળવા માટે, સૌથી ઉપરથી નીચેની તરફ સ્વાઇપ કરો."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"સમજાઈ ગયું"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"પાછળ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"હોમ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"મેનુ"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ઑડિયો"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"હૅડસેટ"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ઇનપુટ"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"શ્રવણ યંત્રો"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"સાંભળવામાં મદદ આપતા યંત્રો"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ચાલુ કરી રહ્યાં છીએ…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ઑટો રોટેટ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ઑટો રોટેટ સ્ક્રીન"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> મર્યાદા"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"થોભાવ્યું"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"જ્યારે તમે શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તમારી સ્ક્રીન પર દેખાતી હોય કે તમારા ડિવાઇસ પર ચલાવવામાં આવતી હોય તેવી બધી વસ્તુનો ઍક્સેસ Android પાસે હોય છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ, ફોટા અને ડિવાઇસ પર વાગી રહેલા ઑડિયો તથા વીડિયો જેવી બાબતોને લઈને સાવચેત રહો."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"જ્યારે તમે કોઈ ઍપ શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તે ઍપ પર બતાવવામાં કે ચલાવવામાં આવતી હોય તેવી બધી વસ્તુનો ઍક્સેસ Android પાસે હોય છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ, ફોટા અને ડિવાઇસ પર વાગી રહેલા ઑડિયો તથા વીડિયો જેવી બાબતોને લઈને સાવચેત રહો."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"શરૂ કરો"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"તમે ઍપ સ્વિચ કરો ત્યારે શેરિંગ થોભી જાય છે"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"તેના બદલે આ ઍપ શેર કરો"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"પાછળ સ્વિચ કરો"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"ઍપ સ્વિચ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"તમારા IT ઍડમિન દ્વારા બ્લૉક કરાયેલી"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ડિવાઇસ પૉલિસી અનુસાર સ્ક્રીન કૅપ્ચર કરવાની સુવિધા બંધ કરવામાં આવી છે"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"બધુ સાફ કરો"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"પૂર્ણ સ્ક્રીનને મોટી કરો"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"સ્ક્રીનનો કોઈ ભાગ મોટો કરો"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"મોટા કરવાના સેટિંગ ખોલો"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"મોટા કરવાના સેટિંગ બંધ કરો"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"કદ બદલવા માટે ખૂણો ખેંચો"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ડાયગોનલ સ્ક્રોલિંગને મંજૂરી આપો"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"કદ બદલો"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"તમારું બૅટરી મીટર વાંચવામાં સમસ્યા આવી"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"વધુ માહિતી માટે ટૅપ કરો"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"કોઈ અલાર્મ સેટ નથી"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"સ્ક્રીન લૉક દાખલ કરો"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ફિંગરપ્રિન્ટ સેન્સર"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ખાતરી કરો"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ડિવાઇસ અનલૉક કરો"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant સાંભળી રહ્યું છે"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# નોટિફિકેશન}one{# નોટિફિકેશન}other{# નોટિફિકેશન}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"નોંધ લેવી"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"નોંધ લેવી"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"નોંધ લેવી, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"બ્રૉડકાસ્ટ કરી રહ્યાં છે"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> બ્રોડકાસ્ટ કરવાનું રોકીએ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"જો તમે <xliff:g id="SWITCHAPP">%1$s</xliff:g> બ્રોડકાસ્ટ કરો અથવા આઉટપુટ બદલો, તો તમારું હાલનું બ્રોડકાસ્ટ બંધ થઈ જશે"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ખોલો"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Wallet ઍપને શૉર્ટકટ તરીકે ઉમેરવા માટે, ખાતરી કરો કે ઍપ ઇન્સ્ટૉલ કરેલી હોય"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Wallet ઍપને શૉર્ટકટ તરીકે ઉમેરવા માટે, ખાતરી કરો કે ઓછામાં ઓછું એક કાર્ડ ઉમેરવામાં આવ્યું હોય"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR કોડ સ્કૅનરને શૉર્ટકટ તરીકે ઉમેરવા માટે, ખાતરી કરો કે કૅમેરા ઍપ ઇન્સ્ટૉલ કરેલી હોય"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home ઍપને શૉર્ટકટ તરીકે ઉમેરવા માટે, ખાતરી કરો કે ઍપ ઇન્સ્ટૉલ કરેલી હોય"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• ઓછામાં ઓછું એક ડિવાઇસ ઉપલબ્ધ છે"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"નોંધ લેવાના શૉર્ટકટનો ઉપયોગ કરવા માટે, નોંધ માટેની ડિફૉલ્ટ ઍપ પસંદ કરો"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant સક્રિય છે"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"સેટિંગમાં નોંધની ડિફૉલ્ટ ઍપ સેટ કરો"</string>
     <string name="install_app" msgid="5066668100199613936">"ઍપ ઇન્સ્ટૉલ કરો"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"માઇક્રોફોન અને કૅમેરા"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"તાજેતરનો ઍપનો વપરાશ"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"તાજેતરનો ઍક્સેસ મેનેજ કરો"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"થઈ ગયું"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"મોટું કરો અને વિકલ્પો બતાવો"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"નાનું કરો"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"આ ઍપ બંધ કરો"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> બંધ છે"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"સેવા મેનેજ કરો"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"ઍક્સેસ મેનેજ કરો"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"ફોન કૉલ દ્વારા ઉપયોગ ચાલુ છે"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"તાજેતરમાં ફોન કૉલમાં ઉપયોગ કરવામાં આવ્યો"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> દ્વારા ઉપયોગ ચાલુ છે"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"<xliff:g id="APP_NAME">%1$s</xliff:g> દ્વારા તાજેતરમાં ઉપયોગ કરવામાં આવ્યો"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) દ્વારા ઉપયોગ ચાલુ છે"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) દ્વારા તાજેતરમાં ઉપયોગ કરવામાં આવ્યો"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) દ્વારા ઉપયોગ ચાલુ છે"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) દ્વારા તાજેતરમાં ઉપયોગ કરવામાં આવ્યો"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index 3a71994..829ef98 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -15,9 +15,6 @@
   -->
 
 <resources>
-    <!-- With the large clock, move up slightly from the center -->
-    <dimen name="keyguard_large_clock_top_margin">-112dp</dimen>
-
     <!-- Margin above the ambient indication container -->
     <dimen name="ambient_indication_container_margin_top">20dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 87211f1d..29f80aa 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"देखने के लिए टैप करें"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"स्क्रीन रिकॉर्डिंग सेव करते समय गड़बड़ी हुई"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रीन को रिकॉर्ड करने में गड़बड़ी आ रही है"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"फ़ुल स्क्रीन मोड पर देखा जा रहा है"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"बाहर निकलने के लिए, सबसे ऊपर से नीचे की ओर स्वाइप करें."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"ठीक है"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"वापस जाएं"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"होम"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"मेन्यू"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ऑडियो"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"इनपुट"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"कान की मशीन"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"कान की मशीनें"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ब्लूटूथ चालू हो रहा है…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ऑटो-रोटेट"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"स्क्रीन का अपने-आप दिशा बदलना (ऑटो-रोटेट)"</string>
@@ -292,6 +295,8 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> सीमा"</string>
     <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>
+    <!-- no translation found for quick_settings_work_mode_paused_state (6681788236383735976) -->
+    <skip />
     <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>
@@ -415,6 +420,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"शेयर, रिकॉर्ड या कास्ट करते समय, Android के पास स्क्रीन पर दिख रहे कॉन्टेंट या डिवाइस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज, फ़ोटो, और डिवाइस पर चल रहे ऑडियो और वीडियो को लेकर सावधानी बरतें."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"किसी ऐप्लिकेशन को शेयर, रिकॉर्ड या कास्ट करते समय, Android के पास उस ऐप्लिकेशन पर दिख रहे कॉन्टेंट या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज, फ़ोटो, और डिवाइस पर चल रहे ऑडियो और वीडियो को लेकर सावधानी बरतें."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"शुरू करें"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"ऐप्लिकेशन स्विच करते समय शेयर करने की प्रोसेस रुक जाती है"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"इसके बजाय, यह ऐप्लिकेशन शेयर करें"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"वापस जाएं"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"ऐप्लिकेशन स्विच करें"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"आपके आईटी एडमिन ने स्क्रीन कैप्चर करने की सुविधा पर रोक लगाई है"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिवाइस से जुड़ी नीति के तहत स्क्रीन कैप्चर करने की सुविधा बंद है"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सभी को हटाएं"</string>
@@ -856,6 +865,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"फ़ुल स्क्रीन को ज़ूम करें"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"स्क्रीन के किसी हिस्से को ज़ूम करें"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"ज़ूम करने की सुविधा वाली सेटिंग खोलें"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"ज़ूम करने की सुविधा वाली सेटिंग को बंद करें"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"साइज़ बदलने के लिए, कोने को खींचें और छोड़ें"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"तिरछी दिशा में स्क्रोल करने की अनुमति दें"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"साइज़ बदलें"</string>
@@ -1043,6 +1055,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"आपके डिवाइस के बैटरी मीटर की रीडिंग लेने में समस्या आ रही है"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ज़्यादा जानकारी के लिए टैप करें"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"कोई अलार्म सेट नहीं है"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"स्क्रीन लॉक डालें"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"फ़िंगरप्रिंट सेंसर"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"पुष्टि करें"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"डिवाइस की होम स्क्रीन पर जाएं"</string>
@@ -1105,7 +1118,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant सुन रही है"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# सूचना}one{# सूचना}other{# सूचनाएं}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"नोट बनाएं"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"नोट बनाएं"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"नोट लेने के लिए, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ब्रॉडकास्ट ऐप्लिकेशन"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> पर ब्रॉडकास्ट करना रोकें?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> पर ब्रॉडकास्ट शुरू करने पर या आउटपुट बदलने पर, आपका मौजूदा ब्रॉडकास्ट बंद हो जाएगा"</string>
@@ -1123,7 +1137,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> खोलें"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Wallet ऐप्लिकेशन को शॉर्टकट के तौर पर जोड़ने के लिए, पक्का करें कि ऐप्लिकेशन इंस्टॉल किया गया हो"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Wallet ऐप्लिकेशन को शॉर्टकट के तौर पर जोड़ने के लिए, पक्का करें कि कम से कम एक कार्ड जोड़ा गया हो"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"क्यूआर कोड स्कैनर को शॉर्टकट के तौर पर जोड़ने के लिए, पक्का करें कि कैमरा ऐप्लिकेशन इंस्टॉल किया गया हो"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home ऐप्लिकेशन को शॉर्टकट के तौर पर जोड़ने के लिए, पक्का करें कि ऐप्लिकेशन इंस्टॉल किया गया हो"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• कम से कम एक डिवाइस उपलब्ध है"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"नोट लेने से जुड़ा शॉर्टकट इस्तेमाल करने के लिए, नोट लेने का डिफ़ॉल्ट ऐप्लिकेशन चुनें"</string>
@@ -1156,4 +1169,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant आपकी बातें सुन रही है"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिंग में जाकर, नोट लेने की सुविधा देने वाले ऐप्लिकेशन को डिफ़ॉल्ट के तौर पर सेट करें"</string>
     <string name="install_app" msgid="5066668100199613936">"ऐप्लिकेशन इंस्टॉल करें"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"माइक्रोफ़ोन और कैमरा"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"हाल ही में इस्तेमाल करने वाला ऐप्लिकेशन"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"हाल में ऐक्सेस करने वाले ऐप"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"हो गया"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"बड़ा करें और विकल्प दिखाएं"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"छोटा करें"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"इस ऐप्लिकेशन को बंद करें"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> बंद हो गया है"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"सेवा मैनेज करें"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"ऐक्सेस मैनेज करें"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"फ़ोन कॉल पर इस्तेमाल किया जा रहा है"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"हाल ही में, फ़ोन कॉल में इस्तेमाल किया गया"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> पर इस्तेमाल किया जा रहा है"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"हाल ही में <xliff:g id="APP_NAME">%1$s</xliff:g> पर इस्तेमाल किया गया"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) पर इस्तेमाल किया जा रहा है"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"हाल ही में, <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ने इस्तेमाल किया"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) पर इस्तेमाल किया जा रहा है"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"हाल ही में, <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ने इस्तेमाल किया"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index a1832d3..e58bc76 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Dodirnite za prikaz"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Pogreška prilikom spremanja snimke zaslona"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Pogreška prilikom pokretanja snimanja zaslona"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Gledanje preko cijelog zaslona"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Za izlaz prijeđite prstom od vrha prema dolje."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Shvaćam"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Natrag"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Početna"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Izbornik"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalice"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Unos"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Slušni aparati"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušna pomagala"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Uključivanje…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autom. zakretanje"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatsko zakretanje zaslona"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Ograničenje od <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Upozorenje <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Poslovne aplikacije"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Pauzirano"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Noćno svjetlo"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Uključuje se u suton"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Do izlaska sunca"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kad dijelite, snimate ili emitirate, Android ima pristup svemu što je vidljivo na vašem zaslonu ili se reproducira na vašem uređaju. Stoga pazite na stvari kao što su zaporke, podaci o plaćanju, poruke, fotografije te audio i videozapisi."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kad dijelite, snimate ili emitirate aplikaciju, Android ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na stvari kao što su zaporke, podaci o plaćanju, poruke, fotografije te audio i videozapisi."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Dijeljenje pauza tijekom prebacivanja aplikacija"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Umjesto toga podijelite ovu aplikaciju"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Promjena računa"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Prekidač aplikacije"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokirao vaš IT administrator"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje zaslona onemogućeno je u skladu s pravilima za uređaje"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši sve"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Povećajte cijeli zaslon"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Povećaj dio zaslona"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Otvori postavke povećavanja"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Zatvori postavke povećavanja"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"Zatvaranje načina uređivanja"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Povucite kut da biste promijenili veličinu"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Dopusti dijagonalno pomicanje"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Promijeni veličinu"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem s očitavanjem mjerača baterije"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Dodirnite za više informacija"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nema nijednog alarma"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"unesite zaključavanje zaslona"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor otiska prsta"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentificirali"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"pristupili uređaju"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistent sluša"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# obavijest}one{# obavijest}few{# obavijesti}other{# obavijesti}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Pisanje bilježaka"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Pisanje bilježaka"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pisanje bilježaka, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Emitiranje"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Zaustaviti emitiranje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ako emitirate aplikaciju <xliff:g id="SWITCHAPP">%1$s</xliff:g> ili promijenite izlaz, vaše će se trenutačno emitiranje zaustaviti"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Otvorite <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Za dodavanje aplikacije Wallet kao prečaca provjerite je li instalirana"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Provjerite je li dodana barem jedna kartica kako biste dodali aplikaciju Wallet kao prečac"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Provjerite je li instalirana aplikacija kamere kako biste dodali čitač QR koda kao prečac"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Provjerite je li aplikacija Home instalirana kako biste je dodali kao prečac"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Dostupan je najmanje jedan uređaj"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Odaberite zadanu aplikaciju za bilješke da biste koristili prečac za pisanje bilježaka"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Pažnja Asistenta je aktivirana"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u postavkama"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalacija"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavna upotreba aplikacije"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Pogledajte nedavni pristup"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Gotovo"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Opcije proširivanja i prikazivanja"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Sažimanje"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Zatvori ovu aplikaciju"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Zatvoreno: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Upravljajte uslugama"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Upravljajte pristupom"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Koristi telefonski poziv"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Nedavno korišteno tijekom telefonskog poziva"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Koristi: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Nedavno koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Koristi: <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nedavno koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Koristi: <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nedavno koristila aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index b96d09e..49ba82b 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Koppintson a megtekintéshez"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Hiba történt a képernyőrögzítés mentése során"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Hiba a képernyőrögzítés indításakor"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Megtekintése teljes képernyőn"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Kilépéshez csúsztassa ujját fentről lefelé."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Értem"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Vissza"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Főoldal"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menü"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Hang"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Bevitel"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hallókészülékek"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hallókészülék"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Bekapcsolás…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatikus elforgatás"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatikus képernyőforgatás"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> korlát"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Figyelem! <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Munkahelyi alkalmazások"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Szüneteltetve"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Éjszakai fény"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Be: naplemente"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Napfelkeltéig"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Amikor Ön megosztást, rögzítést vagy átküldést végez, az Android a képernyőn látható vagy az eszközön lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel, a fotókkal, valamint a hang- és videófelvételekkel."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Amikor Ön megoszt, rögzít vagy átküld egy alkalmazást, az Android az adott alkalmazásban látható vagy lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel, a fotókkal, valamint a hang- és videófelvételekkel."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Indítás"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"A megosztás szünetel alkalmazásváltáskor"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Inkább ennek az appnak a megosztása"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Visszaváltás"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Alkalmazásváltás"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Rendszergazda által letiltva"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A képernyőfelvételt eszközszabályzat tiltja"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Az összes törlése"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"A teljes képernyő felnagyítása"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Képernyő bizonyos részének nagyítása"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Nagyítási beállítások megnyitása"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Nagyítási beállítások bezárása"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Az átméretezéshez húzza a kívánt sarkot"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Átlós görgetés engedélyezése"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Átméretezés"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Probléma merült fel az akkumulátor-töltésmérő olvasásakor"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Koppintással további információkat érhet el."</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nincs ébresztés"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"képernyőzár megadása"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Ujjlenyomat-érzékelő"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"a hitelesítéshez"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"eszköz megadásához"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"A Segéd figyel"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# értesítés}other{# értesítés}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Jegyzetelés"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Jegyzetelés"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Jegyzetelés, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Sugárzás"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Leállítja a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> közvetítését?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"A(z) <xliff:g id="SWITCHAPP">%1$s</xliff:g> közvetítése vagy a kimenet módosítása esetén a jelenlegi közvetítés leáll"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"A(z) <xliff:g id="APPNAME">%1$s</xliff:g> megnyitása"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Ha szeretné felvenni Wallet alkalmazást gyorsparancsként, gondoskodjon az app telepítéséről"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Ha szeretné felvenni Wallet alkalmazást gyorsparancsként, győződjön meg róla, hogy hozzáadott legalább egy kártyát a szolgáltatáshoz"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Ha szeretné felvenni a QR-kód-szkennelőt gyorsparancsként, győződjön meg róla, hogy van az eszközre telepítve kameraalkalmazás"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Ha szeretné felvenni Home appot gyorsparancsként, gondoskodjon az alkalmazás telepítéséről"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Legalább egy eszköz rendelkezésre áll"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Válassza ki az alapértelmezett jegyzetkészítő alkalmazást, amelyet a jegyzetelési gyorsparancshoz szeretne használni"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"A Segéd figyel"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Állítson be alapértelmezett jegyzetkészítő alkalmazást a Beállításokban"</string>
     <string name="install_app" msgid="5066668100199613936">"Alkalmazás telepítése"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon és kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Legutóbbi alkalmazáshasználat"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Legutóbbi hozzáférés"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Kész"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Kibontás és lehetőségek megjelenítése"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Összecsukás"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Az alkalmazás bezárása"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> lezárva"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Szolgáltatás kezelése"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Hozzáférés kezelése"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Telefonhívás által használatban"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Legutóbb telefonhívás során volt használva"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Használatban a következő által: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Legutóbb használta: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Használatban a következő által: <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Legutóbb használta: <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Használatban a következő által: <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Legutóbb használta: <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index e241eae..7b72456 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Հպեք՝ դիտելու համար"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Չհաջողվեց պահել էկրանի տեսագրությունը"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Չհաջողվեց սկսել տեսագրումը"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Լիաէկրան դիտակերպ"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Դուրս գալու համար վերևից մատը սահեցրեք դեպի ներքև։"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Եղավ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Հետ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Տուն"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Ցանկ"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Աուդիո"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ականջակալ"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Մուտքագրում"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Լսողական ապարատ"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Լսողական սարք"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Միացում…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Ինքնապտտում"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ավտոմատ պտտել էկրանը"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Սահմանաչափ՝ <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"Դադարեցված է"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք էկրանը, Android-ին հասանելի է լինում այն ամենը, ինչ տեսանելի է ձեր էկրանին և նվագարկվում է ձեր սարքում։ Ուստի ուշադիր եղեք այնպիսի բաների հետ, ինչպիսիք են գաղտնաբառերը, վճարային տվյալները, հաղորդագրությունները, լուսանկարները, աուդիո և վիդեո բովանդակությունը։"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք որևէ հավելվածի էկրանը, Android-ին հասանելի է լինում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Ուստի ուշադիր եղեք այնպիսի բաների հետ, ինչպիսիք են գաղտնաբառերը, վճարային տվյալները, հաղորդագրությունները, լուսանկարները, աուդիո և վիդեո բովանդակությունը։"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Սկսել"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Երբ անցում եք կատարում մեկ այլ հավելվածի, ընթացիկ հավելվածի համատեղ օգտագործումը դադարեցվում է"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Փոխարենը կիսվել այս հավելվածով"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Վերադառնալ"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Հավելվածների փոխանջատիչ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Արգելափակվել է ձեր ՏՏ ադմինիստրատորի կողմից"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Էկրանի տեսագրումն անջատված է սարքի կանոնների համաձայն"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Մաքրել բոլորը"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Խոշորացնել ամբողջ էկրանը"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Խոշորացնել էկրանի որոշակի հատվածը"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Բացել խոշորացման կարգավորումները"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Փակել խոշորացման կարգավորումները"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Քաշեք անկյունը՝ չափը փոխելու համար"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Թույլատրել անկյունագծով ոլորումը"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Փոխել չափը"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Մարտկոցի ցուցիչի ցուցմունքը կարդալու հետ կապված խնդիր կա"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Հպեք՝ ավելին իմանալու համար"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Զարթուցիչ դրված չէ"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ապակողպել էկրանը"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Մատնահետքի սկաներ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"նույնականացնել"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"նշել սարքը"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Օգնականը լսում է"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ծանուցում}one{# ծանուցում}other{# ծանուցում}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Նշումների ստեղծում"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Նշումների ստեղծում"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Նշումների ստեղծում, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Հեռարձակում"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Կանգնեցնել <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի հեռարձակումը"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Եթե հեռարձակեք <xliff:g id="SWITCHAPP">%1$s</xliff:g> հավելվածը կամ փոխեք աուդիո ելքը, ձեր ընթացիկ հեռարձակումը կկանգնեցվի։"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Բացել <xliff:g id="APPNAME">%1$s</xliff:g> հավելվածը"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Wallet հավելվածի դյուրանցումն ավելացնելու համար համոզվեք, որ հավելվածը տեղադրված է"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Wallet հավելվածի դյուրանցումն ավելացնելու համար համոզվեք, որ առնվազն մեկ քարտ ավելացված է"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR կոդերի սկաների դյուրանցումն ավելացնելու համար համոզվեք, որ տեսախցիկի հավելվածը տեղադրված է"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home հավելվածի դյուրանցումն ավելացնելու համար համոզվեք, որ հավելվածը տեղադրված է"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Հասանելի է առնվազն մեկ սարք"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Ընտրեք նշումների կանխադրված հավելված՝ նշումների ստեղծման դյուրանցումն օգտագործելու համար"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Օգնականը լսում է"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Կարգավորեք նշումների կանխադրված հավելված Կարգավորումներում"</string>
     <string name="install_app" msgid="5066668100199613936">"Տեղադրել հավելվածը"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Խոսափող և տեսախցիկ"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Հավելվածի վերջին օգտագործումը"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Տեսնել վերջին օգտագործումը"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Պատրաստ է"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Ծավալել և ցույց տալ տարբերակները"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Ծալել"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Փակել այս հավելվածը"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը փակվեց"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Կառավարել ծառայությունը"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Կառավարել հասանելիությունը"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Օգտագործվում է հեռախոսազանգի կողմից"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Վերջերս օգտագործվել է հեռախոսազանգի ժամանակ"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Օգտագործվում է <xliff:g id="APP_NAME">%1$s</xliff:g>-ի կողմից"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Վերջերս օգտագործվել է <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի կողմից"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Օգտագործվում է <xliff:g id="APP_NAME">%1$s</xliff:g>-ի կողմից (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Վերջերս օգտագործվել է <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի կողմից (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Օգտագործվում է <xliff:g id="APP_NAME">%1$s</xliff:g>-ի կողմից (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Վերջերս օգտագործվել է <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածի կողմից (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index ff30cd6..fc12a14 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Ketuk untuk melihat"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Terjadi error saat menyimpan rekaman layar"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Terjadi error saat memulai perekaman layar"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Melihat layar penuh"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Untuk keluar, geser layar ke bawah dari atas."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Oke"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Kembali"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Utama"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Alat Bantu Dengar"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Alat bantu dengar"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Mengaktifkan…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Putar Otomatis"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Putar layar otomatis"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Batas <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Peri­ngatan <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Aplikasi kerja"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Dijeda"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Cahaya Malam"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Aktif saat malam"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Sampai pagi"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Jika Anda membagikan, merekam, atau mentransmisikan, 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, foto, audio, dan video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Jika Anda membagikan, merekam, atau mentransmisikan suatu 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, foto, audio, dan video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Mulai"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Berbagi akan dijeda saat Anda beralih aplikasi"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Bagikan aplikasi ini"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Beralih kembali"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Beralih aplikasi"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Diblokir oleh admin IT Anda"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Pengambilan screenshot dinonaktifkan oleh kebijakan perangkat"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hapus semua"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Memperbesar tampilan layar penuh"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Perbesar sebagian layar"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Buka setelan pembesaran"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Tutup setelan pembesaran"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Tarik pojok persegi untuk mengubah ukuran"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Izinkan scrolling diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ubah ukuran"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Terjadi error saat membaca indikator baterai"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Ketuk untuk informasi selengkapnya"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Alarm tidak disetel"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"masukkan kunci layar"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor sidik jari"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentikasi"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"masukkan perangkat"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant sedang mendengarkan"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notifikasi}other{# notifikasi}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Pembuatan catatan"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Pembuatan catatan"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pembuatan catatan, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Menyiarkan"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Hentikan siaran <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jika Anda menyiarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g> atau mengubah output, siaran saat ini akan dihentikan"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Buka <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Untuk menambahkan aplikasi Wallet sebagai pintasan, pastikan aplikasi sudah diinstal"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Untuk menambahkan aplikasi Wallet sebagai pintasan, pastikan minimal satu kartu telah ditambahkan"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Untuk menambahkan pemindai kode QR sebagai pintasan, pastikan aplikasi kamera sudah diinstal"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Untuk menambahkan aplikasi Home sebagai pintasan, pastikan aplikasi sudah diinstal"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Tersedia minimal satu perangkat"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Pilih aplikasi catatan default untuk menggunakan pintasan pembuatan catatan"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Asisten sedang memerhatikan"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setel aplikasi catatan default di Setelan"</string>
     <string name="install_app" msgid="5066668100199613936">"Instal aplikasi"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon &amp; Kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Penggunaan aplikasi baru-baru ini"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Lihat akses terbaru"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Selesai"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Luaskan dan tampilkan opsi"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Ciutkan"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Tutup aplikasi ini"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> ditutup"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Kelola layanan"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Kelola akses"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Sedang digunakan untuk panggilan telepon"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Baru saja digunakan untuk panggilan telepon"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Sedang digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Baru saja digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Sedang digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Baru saja digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Sedang digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Baru saja digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index c1030d0..2c784a6 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Ýttu til að skoða"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Villa við að vista skjáupptöku"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Villa við að hefja upptöku skjás"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Notar allan skjáinn"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Strjúktu niður frá efsta hluta skjásins til að hætta."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Ég skil"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Til baka"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Heim"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Valmynd"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Hljóð"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Höfuðtól"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Inntak"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Heyrnartæki"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Heyrnartæki"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Kveikir…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Sjálfvirkur snúningur"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Snúa skjá sjálfkrafa"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> hámark"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"<xliff:g id="DATA_LIMIT">%s</xliff:g> viðvörun"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Vinnuforrit"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Hlé"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Næturljós"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Kveikt við sólsetur"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Til sólarupprásar"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Þegar þú deilir, tekur upp eða varpar 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ð, myndir, hljóð og myndskeið."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Þegar þú deilir, tekur upp eða varpar forriti 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ð, myndir, hljóð og myndskeið."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Byrja"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Hlé er gert á samnýtingu þegar þú skiptir um forrit"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Deila frekar þessu forriti"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Skipta til baka"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Skipta um forrit"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Útilokað af kerfisstjóra"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Slökkt er á skjáupptöku í tækjareglum"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Stækka allan skjáinn"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Stækka hluta skjásins"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Opna stillingar stækkunar"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Loka stillingum stækkunar"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Dragðu horn til að breyta stærð"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Leyfa skáflettingu"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Breyta stærð"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Vandamál við að lesa stöðu rafhlöðu"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Ýttu til að fá frekari upplýsingar"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Enginn vekjari"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"sláðu inn skjálás"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingrafaralesari"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"auðkenna"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"opna tæki"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Hjálparinn er að hlusta"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# tilkynning}one{# tilkynning}other{# tilkynningar}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Glósugerð"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Glósugerð"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Glósugerð, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Útsending í gangi"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Hætta að senda út <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ef þú sendir út <xliff:g id="SWITCHAPP">%1$s</xliff:g> eða skiptir um úttak lýkur yfirstandandi útsendingu"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Opna <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Til að bæta Veskisforritinu við sem flýtileið skaltu ganga úr skugga um að forritið sé uppsett"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Til að bæta Veskisforritinu við sem flýtileið skaltu ganga úr skugga um að hafa bætt að minnsta kosti einu korti við"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Til að bæta QR-kóðaskanna við sem flýtileið skaltu ganga úr skugga um að myndavélarforrit sé uppsett"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Til að bæta Home-forritinu við sem flýtileið skaltu ganga úr skugga um að forritið sé uppsett"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Að minnsta kosti eitt tæki er tiltækt"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Veldu sjálfgefið glósuforrit til að nota flýtileið fyrir glósugerð"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Hjálparinn er að hlusta"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Stilltu sjálfgefið glósuforrit í stillingunum"</string>
     <string name="install_app" msgid="5066668100199613936">"Setja upp forrit"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Hljóðnemi og myndavél"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nýlega notað af forriti"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Sjá nýlegan aðgang"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Lokið"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Stækka og sýna valkosti"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Minnka"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Loka þessu forriti"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> er lokað"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Stjórna þjónustu"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Stjórna aðgangi"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Í notkun í símtali"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Nýlega notað í símtali"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Í notkun í <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Nýlega notað af <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Í notkun í <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nýlega notað af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Í notkun í <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nýlega notað af <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index a194802..7c22297 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tocca per visualizzare"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Errore durante il salvataggio della registrazione dello schermo"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Errore durante l\'avvio della registrazione dello schermo"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visualizzazione a schermo intero"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Per uscire, scorri dall\'alto verso il basso."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Indietro"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Auricolare"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ingresso"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Apparecchi acustici"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Apparecchi acustici"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Attivazione…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotazione automatica"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotazione automatica dello schermo"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Limite di <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Avviso <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"App di lavoro"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"In pausa"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Luminosità notturna"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Attivata al tramonto"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Fino all\'alba"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando condividi, registri o trasmetti, Android ha accesso a qualsiasi elemento visibile sul tuo schermo o in riproduzione sul tuo dispositivo. Presta quindi attenzione a password, dettagli sui pagamenti, messaggi, foto, audio e video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando condividi, registri o trasmetti un\'app, Android ha accesso a qualsiasi elemento visualizzato o riprodotto sull\'app. Presta quindi attenzione a password, dettagli sui pagamenti, messaggi, foto, audio e video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Inizia"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"La condivisione viene messa in pausa quando cambi app"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Condividi invece questa app"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Torna indietro"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Cambio app"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloccata dall\'amministratore IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"L\'acquisizione schermo è disattivata dai criteri relativi ai dispositivi"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Cancella tutto"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ingrandisci l\'intero schermo"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ingrandisci parte dello schermo"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Apri le impostazioni di ingrandimento"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Chiudi impostazioni di ingrandimento"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Trascina l\'angolo per ridimensionare"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Scorrimento diagonale"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ridimensiona"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problema durante la lettura dell\'indicatore di livello della batteria"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tocca per ulteriori informazioni"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nessuna"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"inserisci blocco schermo"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensore di impronte digitali"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"effettuare l\'autenticazione"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"accedere al dispositivo"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"L\'assistente è in ascolto"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notifica}many{# notifiche}other{# notifiche}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Aggiunta di note"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Aggiunta di note"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Aggiunta di note, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Trasmissione in corso…"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vuoi interrompere la trasmissione dell\'app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se trasmetti l\'app <xliff:g id="SWITCHAPP">%1$s</xliff:g> o cambi l\'uscita, la trasmissione attuale viene interrotta"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Apri <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Per aggiungere l\'app Wallet come scorciatoia, assicurati che l\'app sia installata"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Per aggiungere l\'app Wallet come scorciatoia, assicurati che sia stata aggiunta almeno una carta"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Per aggiungere lo scanner di codici QR come scorciatoia, assicurati che ci sia un\'app fotocamera installata"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Per aggiungere l\'app Home come scorciatoia, assicurati che l\'app sia installata"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Ci sia almeno un dispositivo disponibile"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Seleziona un\'app per le note predefinita per usare la scorciatoia per l\'aggiunta di note"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"L\'assistente è attivo"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Imposta l\'app per le note predefinita nelle Impostazioni"</string>
     <string name="install_app" msgid="5066668100199613936">"Installa app"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfono e videocamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente da app"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Vedi accesso recente"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Fine"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Espandi e mostra le opzioni"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Comprimi"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Chiudi l\'app"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"App <xliff:g id="APP_NAME">%1$s</xliff:g> chiusa"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Gestisci servizio"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Gestisci accesso"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"In uso nella telefonata"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Recentemente in uso nella telefonata"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"In uso da <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Recentemente in uso da <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"In uso da <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Recentemente in uso da <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"In uso da <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Recentemente in uso da <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index d5466b0..f1dd497 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"יש להקיש כדי להציג"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"שגיאה בשמירה של הקלטת המסך"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"שגיאה בהפעלה של הקלטת המסך"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"צפייה במסך מלא"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"כדי לצאת, מחליקים למטה מהחלק העליון של המסך."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"הבנתי"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"חזרה"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"בית"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"תפריט"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"אודיו"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"אוזניות"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"קלט"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"מכשירי שמיעה"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"מכשירי שמיעה"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ההפעלה מתבצעת…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"סיבוב אוטומטי"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"סיבוב אוטומטי של המסך"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"הגבלה של <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"בהשהיה"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"‏בזמן שיתוף, הקלטה או העברה (cast) תהיה ל-Android גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"‏בזמן שיתוף, הקלטה או העברה (cast) של אפליקציה, תהיה ל-Android גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"התחלה"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"השיתוף מושהה כשמחליפים אפליקציות"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"שיתוף של האפליקציה הזו במקום"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"חזרה למשימה הקודמת"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"החלפת אפליקציה"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"‏האפשרות נחסמה על ידי אדמין ב-IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"צילום המסך מושבת בגלל מדיניות המכשיר"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ניקוי הכול"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"הגדלה של המסך המלא"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"הגדלת חלק מהמסך"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"פתיחת הגדרות ההגדלה"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"סגירת הגדרות ההגדלה"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"צריך לגרור את הפינה כדי לשנות את הגודל"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"הפעלת גלילה באלכסון"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"שינוי גודל"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"בעיה בקריאת מדדי הסוללה"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"יש להקיש כדי להציג מידע נוסף"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"לא הוגדרה"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"הזנת קוד נעילת המסך"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"חיישן טביעות אצבע"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"אימות"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"הזנת מכשיר"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"‏Assistant מאזינה"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{התראה אחת}one{# התראות}two{# התראות}other{# התראות}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"כתיבת הערות"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"כתיבת הערות"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"כתיבת הערות, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"שידור"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"האם להפסיק לשדר את התוכן מאפליקציית <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"אם משדרים את התוכן מאפליקציית <xliff:g id="SWITCHAPP">%1$s</xliff:g> או משנים את הפלט, השידור הנוכחי יפסיק לפעול"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"פתיחת <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"‏כדי להוסיף את אפליקציית Wallet כקיצור דרך, צריך לוודא שהאפליקציה מותקנת"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"‏כדי להוסיף את אפליקציית Wallet כקיצור דרך, צריך לוודא שנוסף לפחות כרטיס אחד"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"‏כדי להוסיף את סורק קודי ה-QR כקיצור דרך, צריך לוודא שמותקנת אפליקציית מצלמה"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"‏כדי להוסיף את אפליקציית Home כקיצור דרך, צריך לוודא שהאפליקציה מותקנת"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• יש לפחות מכשיר אחד זמין"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"צריך לבחור אפליקציית פתקים שתיפתח כברירת מחדל כשייעשה שימוש במקש הקיצור לכתיבת פתקים"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"‏Assistant מאזינה"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"צריך להגדיר את אפליקציית ברירת המחדל לפתקים ב\'הגדרות\'"</string>
     <string name="install_app" msgid="5066668100199613936">"התקנת האפליקציה"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"מיקרופון ומצלמה"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"נעשה שימוש לאחרונה באפליקציה"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"צפייה בהרשאות הגישה האחרונות"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"סיום"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"הרחבה והצגת האפשרויות"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"כיווץ"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"סגירת האפליקציה הזו"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"אפליקציית <xliff:g id="APP_NAME">%1$s</xliff:g> סגורה"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"ניהול השירות"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"ניהול הרשאות הגישה"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"בשימוש על ידי שיחת טלפון"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"נעשה שימוש לאחרונה על ידי שיחת טלפון"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"בשימוש על ידי <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"נעשה שימוש לאחרונה על ידי <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"בשימוש על ידי <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"נעשה שימוש לאחרונה על ידי <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"בשימוש על ידי <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"נעשה שימוש לאחרונה על ידי <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index ef3f631..5d3919e 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"タップすると表示されます"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"画面の録画の保存中にエラーが発生しました"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"画面の録画中にエラーが発生しました"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"全画面表示"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"終了するには、上から下にスワイプします。"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"戻る"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ホーム"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"メニュー"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"オーディオ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ヘッドセット"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"入力"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"補聴器"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"補聴器"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ON にしています…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自動回転"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"画面を自動回転します"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"上限: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"一時停止中"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"夜間モード"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"日の入りに ON"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"日の出まで"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"共有、録画、キャスト中は、画面に表示される内容やデバイスで再生される内容に Android がアクセスできるため、パスワード、お支払いの詳細、メッセージ、写真、音声、動画などの情報にご注意ください。"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"アプリの共有、録画、キャスト中は、そのアプリで表示または再生される内容に Android がアクセスできるため、パスワード、お支払いの詳細、メッセージ、写真、音声、動画などの情報にご注意ください。"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"開始"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"アプリを切り替えるときに共有を一時停止します"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"代わりにこのアプリを共有する"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"切り替える"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"アプリの切り替え"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 管理者によりブロックされました"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"デバイス ポリシーに基づき、画面のキャプチャが無効になりました"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"すべて消去"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"画面全体を拡大します"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"画面の一部を拡大します"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"画面の拡大設定を開く"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"拡大の設定を閉じる"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"編集モードを終了"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"サイズを変更するには角をドラッグ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"斜めスクロールを許可"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"サイズ変更"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"バッテリー残量の読み込み中に問題が発生しました"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"タップすると詳細が表示されます"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"アラーム未設定"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"画面ロックを設定"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"指紋認証センサー"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"認証"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"デバイスを入力"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"アシスタントが音声認識中です"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# 件の通知}other{# 件の通知}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>、<xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"メモ"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"メモ"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"メモ、<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ブロードキャスト"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> のブロードキャストを停止しますか?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> をブロードキャストしたり、出力を変更したりすると、現在のブロードキャストが停止します。"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> を開く"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"ウォレット アプリをショートカットとして追加するには、アプリがインストールされていることを確認してください"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"ウォレット アプリをショートカットとして追加するには、カードが 1 枚以上追加されていることを確認してください"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR コードスキャナをショートカットとして追加するには、カメラアプリがインストールされていることを確認してください"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Google Home アプリをショートカットとして追加するには、アプリがインストールされていることを確認してください"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• 利用できるデバイスが 1 台以上ある"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"メモのショートカットを使用するデフォルトのメモアプリを選択してください"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"アシスタントは起動済みです"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"[設定] でデフォルトのメモアプリを設定してください"</string>
     <string name="install_app" msgid="5066668100199613936">"アプリをインストール"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"マイクとカメラ"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"最近のアプリの使用状況"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"最近のアクセスを表示"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"完了"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"オプションを開いて表示する"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"閉じる"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"このアプリを閉じる"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> を閉じました"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"サービスを管理"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"アクセスを管理"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"通話で使用中"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"通話で最近使用"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> が使用中"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"<xliff:g id="APP_NAME">%1$s</xliff:g> が最近使用"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> が使用中(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> が最近使用(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> が使用中(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> が最近使用(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index d6a0c50..daa0ae7 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"შეეხეთ სანახავად"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ეკრანის ჩანაწერის შენახვისას შეცდომა მოხდა"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ეკრანის ჩაწერის დაწყებისას წარმოიქმნა შეცდომა"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"მიმდინარეობს სრულ ეკრანზე ნახვა"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"გასვლისთვის გადაფურცლეთ ზემოდან ქვემოთ."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"გასაგებია"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"უკან"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"საწყისი"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"მენიუ"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"აუდიო"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ყურსაცვამი"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"შეყვანა"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"სმენის მოწყობილობები"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"სმენის მოწყობილობები"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ირთვება…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ავტოროტაცია"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ეკრანის ავტომატური შეტრიალება"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"ლიმიტი: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"დაპაუზებულია"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"გაზიარებისას, ჩაწერისას ან ტრანსლირებისას, Android-ს წვდომა აქვს ყველაფერზე, რაც ჩანს თქვენს ეკრანზე ან უკრავს თქვენს მოწყობილობაზე. ამიტომ იყავით ფრთხილად ისეთ ინფორმაციასთან, როგორიცაა პაროლები, გადახდის დეტალები, შეტყობინებები, ფოტოები, აუდიო და ვიდეო."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"აპის გაზიარებისას, ჩაწერისას ან ტრანსლირებისას, Android-ს წვდომა აქვს ყველაფერზე, რაც ჩანს ან იკვრება აპში. ამიტომ იყავით ფრთხილად ისეთ ინფორმაციასთან, როგორიცაა პაროლები, გადახდის დეტალები, შეტყობინებები, ფოტოები, აუდიო და ვიდეო."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"დაწყება"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"აპების გადართვისას გაზიარება პაუზდება"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"სანაცვლოდ ამ აპის გაზიარება"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"უკან გადართვა"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"აპის გადამრთველი"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"დაბლოკილია თქვენი IT-ადმინისტრატორის მიერ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ეკრანის აღბეჭდვა გამორთულია მოწყობილობის წესების თანახმად"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ყველას გასუფთავება"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"გაადიდეთ სრულ ეკრანზე"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ეკრანის ნაწილის გადიდება"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"გახსენით გადიდების პარამეტრები"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"გადიდების პარამეტრების დახურვა"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"რედაქტირების რეჟიმიდან გასვლა"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ჩავლებით გადაიტანეთ კუთხე ზომის შესაცვლელად"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"დიაგონალური გადაადგილების დაშვება"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ზომის შეცვლა"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"თქვენი ბატარეის მზომის წაკითხვასთან დაკავშირებით პრობლემა დაფიქსირდა"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"შეეხეთ მეტი ინფორმაციისთვის"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"მაღვიძარა არ არის"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ეკრანის დაბლოკვის შეყვანა"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"თითის ანაბეჭდის სენსორი"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ავტორიზაცია"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"მოწყობილობის შეყვანა"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"ასისტენტი უსმენს"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# შეტყობინება}other{# შეტყობინება}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"შენიშვნების ჩაწერა"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"ჩანიშვნა"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ჩანიშვნა, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"იწყებთ მაუწყებლობას"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"გსურთ <xliff:g id="APP_NAME">%1$s</xliff:g>-ის ტრანსლაციის შეჩერება?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g>-ის ტრანსლაციის შემთხვევაში ან აუდიოს გამოსასვლელის შეცვლისას, მიმდინარე ტრანსლაცია შეჩერდება"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> აპის გახსნა"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"საფულის აპის მალსახმობის დასამატებლად დარწმუნდით, რომ აპი დაინსტალირებულია"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"საფულის აპის მალსახმობის დასამატებლად დარწმუნდით, რომ დამატებულია მინიმუმ ერთი ბარათი"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR კოდის სკანერის მალსახმობის დასამატებლად დარწმუნდით, რომ დაინსტალირებულია კამერის აპი"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"მთავარი აპის მალსახმობის დასამატებლად დარწმუნდით, რომ აპი დაინსტალირებულია"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• ხელმისაწვდომია მინიმუმ ერთი მოწყობილობა"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"აირჩიეთ ჩანიშვნების ნაგულისხმევი აპი, რათა ჩანიშვნების შექმნის მალსახმობი გამოიყენოთ"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"ასისტენტის ყურადღების ფუნქცია ჩართულია"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"დააყენეთ ნაგულისხმევი შენიშვნების აპი პარამეტრებში"</string>
     <string name="install_app" msgid="5066668100199613936">"აპის ინსტალაცია"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"მიკროფონი და კამერა"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"აპის ბოლოდროინდელი გამოყენება"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ბოლოდროინდელი წვდომის ნახვა"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"მზადაა"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"გაფართოება და ვარიანტების ჩვენება"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"ჩაკეცვა"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"ამ აპის დახურვა"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> დახურულია"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"სერვისის მართვა"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"წვდომის მართვა"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"გამოიყენება სატელეფონო ზარის მიერ"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"ახლახან გამოყენებულია სატელეფონო ზარის მიერ"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"გამოიყენება <xliff:g id="APP_NAME">%1$s</xliff:g>-ის მიერ"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"ახლახან გამოყენებულია <xliff:g id="APP_NAME">%1$s</xliff:g>-ის მიერ"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"გამოიყენება <xliff:g id="APP_NAME">%1$s</xliff:g>-ის მიერ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"ახლახან გამოყენებულია <xliff:g id="APP_NAME">%1$s</xliff:g>-ის მიერ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"გამოიყენება <xliff:g id="APP_NAME">%1$s</xliff:g>-ის მიერ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"ახლახან გამოყენებულია <xliff:g id="APP_NAME">%1$s</xliff:g>-ის მიერ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index ac3a266..18f7855 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Көру үшін түртіңіз."</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Экран жазбасын сақтау кезінде қате шықты."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Экрандағы бейнені жазу кезінде қате шықты."</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Толық экранда көру"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Шығу үшін жоғарыдан төмен қарай сырғытыңыз."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Түсінікті"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Артқа"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Үй"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Mәзір"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Aудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Кіріс"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Есту аппараттары"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Есту аппараттары"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Қосылуда…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоматты түрде бұру"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматты айналатын экран"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> шегі"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"Кідіртілген"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Бөлісу, жазу не трансляциялау кезінде Android жүйесі экраныңызда көрінетін не құрылғыңызда ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізген кезде сақ болыңыз."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Қолданба экранын бөлісу, жазу не трансляциялау кезінде Android жүйесі онда көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізген кезде сақ болыңыз."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Бастау"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Қолданба ауыстырғанда, бөлісу кідіртіледі."</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Орнына осы қолданбаны бөлісу"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Қайта ауысу"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Қолданба ауыстыру"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Әкімшіңіз бөгеген"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Құрылғы саясатына байланысты экранды түсіру өшірілді."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Барлығын тазарту"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Толық экранды ұлғайту"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Экранның бөлігін ұлғайту"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Ұлғайту параметрлерін ашу"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Ұлғайту параметрлерін жабу"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Өлшемін өзгерту үшін бұрышынан сүйреңіз."</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Диагональ бойынша айналдыруға рұқсат беру"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Өлшемін өзгерту"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Батарея зарядының дерегі алынбай жатыр"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Толығырақ ақпарат алу үшін түртіңіз."</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Оятқыш орнатылмаған."</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"экран құлпын енгізу"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Саусақ ізін оқу сканері"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"аутентификациялау"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"құрылғыны енгізу"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant тыңдап тұр."</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# хабарландыру}other{# хабарландыру}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Ескертпе жазу"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Ескертпе жазу"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Ескертпе жазу, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Таратуда"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасын таратуды тоқтатасыз ба?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> қолданбасын таратсаңыз немесе аудио шығысын өзгертсеңіз, қазіргі тарату сеансы тоқтайды."</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ашу"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Wallet қолданбасын таңбаша ретінде қосу үшін қолданбаның орнатылғанын тексеріңіз."</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Wallet қолданбасын таңбаша ретінде қосу үшін кемінде бір картаның қосылғанын тексеріңіз."</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR кодының сканерін таңбаша ретінде қосу үшін камера қолданбасының орнатылғанын тексеріңіз."</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home қолданбасын таңбаша ретінде қосу үшін қолданбаның орнатылғанын тексеріңіз."</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Кемінде бір құрылғы қолжетімді."</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Жазба жазу таңбашасын пайдалану үшін әдепкі жазба қолданбаны таңдаңыз."</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant қосулы."</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Параметрлерден әдепкі жазба қолданбасын орнатыңыз."</string>
     <string name="install_app" msgid="5066668100199613936">"Қолданбаны орнату"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон және камера"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Соңғы рет қолданбаның датчикті пайдалануы"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Соңғы рет пайдаланғандар"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Дайын"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Опцияларды көрсету және жаю"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Жию"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Осы қолданбаны жабу"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы жабылды."</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Қызметті басқару"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Пайдалану рұқсатын басқару"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Телефон қоңырауы үшін пайдаланылып жатыр"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Соңғы рет телефон қоңырауы үшін пайдаланылды."</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы пайдаланып жатыр"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Соңғы рет <xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы пайдаланды."</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) қолданбасы пайдаланып жатыр"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Соңғы рет <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) қолданбасы пайдаланды."</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) қолданбасы пайдаланып жатыр"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Соңғы рет <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) қолданбасы пайдаланды."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 382c6ef..d5ef4b5 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"ចុចដើម្បីមើល"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"មានបញ្ហាក្នុងការរក្សាទុក​ការថតវីដេអូអេក្រង់"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"មានបញ្ហា​ក្នុងការ​ចាប់ផ្ដើម​ថត​អេក្រង់"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"កំពុងមើលពេញអេក្រង់"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ដើម្បីចាកចេញ សូមអូសពីលើចុះក្រោម។"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"យល់ហើយ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ថយក្រោយ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"គេហ​ទំព័រ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"ម៉ឺនុយ"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"សំឡេង"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"កាស"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"បញ្ចូល"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"ឧបករណ៍​ជំនួយការ​ស្ដាប់"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ឧបករណ៍ជំនួយការស្ដាប់"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"កំពុង​បើក..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"បង្វិល​ស្វ័យ​ប្រវត្តិ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"បង្វិលអេក្រង់ស្វ័យប្រវត្តិ"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"ដែន​កំណត់ <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"បានផ្អាក"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬភ្ជាប់, Android មានសិទ្ធិចូលប្រើអ្វីៗដែលអាចមើលឃើញនៅលើអេក្រង់របស់អ្នក ឬចាក់នៅលើឧបករណ៍របស់អ្នក។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះអ្វីៗដូចជា ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ រូបថត ព្រមទាំងសំឡេង និងវីដេអូ។"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬភ្ជាប់កម្មវិធី, Android មានសិទ្ធិចូលប្រើអ្វីៗដែលបង្ហាញ ឬចាក់នៅលើកម្មវិធីនោះ។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះអ្វីៗដូចជា ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ រូបថត ព្រមទាំងសំឡេង និងវីដេអូ។"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ចាប់ផ្ដើម"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"ការចែករំលែក​ផ្អាក នៅពេលដែល​អ្នកប្ដូរ​កម្មវិធី"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"ចែករំលែក​កម្មវិធីនេះ​ជំនួសវិញ"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"ប្ដូរទៅវិញ"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"ការប្ដូរ​កម្មវិធី"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"បានទប់ស្កាត់ដោយអ្នកគ្រប់គ្រង​ផ្នែកព័ត៌មានវិទ្យា​របស់អ្នក"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ការថតអេក្រង់ត្រូវបានបិទ​ដោយគោលការណ៍ឧបករណ៍"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"សម្អាត​ទាំងអស់"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ពង្រីក​ពេញអេក្រង់"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ពង្រីក​ផ្នែកនៃ​អេក្រង់"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"បើកការកំណត់​ការពង្រីក"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"បិទការកំណត់ការពង្រីក"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"អូសជ្រុងដើម្បីប្ដូរទំហំ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"អនុញ្ញាត​ការរំកិលបញ្ឆិត"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ប្ដូរ​ទំហំ"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"មានបញ្ហាក្នុង​ការអាន​ឧបករណ៍រង្វាស់កម្រិតថ្មរបស់អ្នក"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ចុចដើម្បីទទួលបាន​ព័ត៌មានបន្ថែម"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"មិនបាន​កំណត់​ម៉ោងរោទ៍​ទេ"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"បញ្ចូលព័ត៌មានផ្ទៀងផ្ទាត់សម្រាប់ការចាក់សោអេក្រង់"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ឧបករណ៍​ចាប់ស្នាមម្រាមដៃ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ផ្ទៀងផ្ទាត់"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"បញ្ចូល​ឧបករណ៍"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Google Assistant កំពុងស្តាប់"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{ការ​ជូន​ដំណឹង #}other{ការ​ជូនដំណឹង #}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"ការកត់ត្រា​"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"ការកត់ត្រា"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ការកត់ត្រា, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ការផ្សាយ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"បញ្ឈប់ការផ្សាយ <xliff:g id="APP_NAME">%1$s</xliff:g> ឬ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ប្រសិនបើអ្នក​ផ្សាយ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ឬប្ដូរឧបករណ៍បញ្ចេញសំឡេង ការផ្សាយបច្ចុប្បន្នរបស់អ្នកនឹង​បញ្ឈប់"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"បើក <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"ដើម្បីបញ្ចូល​កម្មវិធី Wallet ជាផ្លូវកាត់ សូមប្រាកដថា​កម្មវិធីត្រូវបានដំឡើង"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"ដើម្បីបញ្ចូល​កម្មវិធី Wallet ជាផ្លូវកាត់ សូមប្រាកដថា​បានបញ្ចូលកាត​យ៉ាងហោចណាស់មួយ"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"ដើម្បីបញ្ចូលកម្មវិធី​ស្កេនកូដ QR ជាផ្លូវកាត់ សូមប្រាកដថា​កម្មវិធីកាមេរ៉ាត្រូវបានដំឡើង"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"ដើម្បីបញ្ចូល​កម្មវិធី Home ជាផ្លូវកាត់ សូមប្រាកដថា​កម្មវិធីត្រូវបានដំឡើង"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• ឧបករណ៍យ៉ាងតិចមួយអាចប្រើបាន"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"ជ្រើសរើសកម្មវិធីកំណត់ចំណាំលំនាំដើម ដើម្បីប្រើផ្លូវកាត់សម្រាប់ការកត់ចំណាំ"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"ភាពប្រុងប្រៀប​របស់ Google Assistant ត្រូវបានបើក"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"កំណត់កម្មវិធីកំណត់ចំណាំលំនាំដើមនៅក្នុងការកំណត់"</string>
     <string name="install_app" msgid="5066668100199613936">"ដំឡើង​កម្មវិធី"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"មីក្រូហ្វូន និងកាមេរ៉ា"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"ការប្រើប្រាស់កម្មវិធីថ្មីៗនេះ"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"មើលការចូលប្រើនាពេលថ្មីៗនេះ"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"រួចរាល់"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"ពង្រីក និង​បង្ហាញ​ជម្រើស​នានា"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"បង្រួម"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"បិទកម្មវិធីនេះ"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> ត្រូវ​បាន​បិទ"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"គ្រប់គ្រង​សេវាកម្ម"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"គ្រប់គ្រង​សិទ្ធិចូលប្រើ"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"កំពុងប្រើដោយការហៅទូរសព្ទ"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"បានប្រើនាពេលថ្មីៗនេះនៅក្នុងការហៅទូរសព្ទ"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"កំពុងប្រើដោយ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"បានប្រើនាពេលថ្មីៗនេះដោយ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"កំពុងប្រើដោយ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"បានប្រើនាពេលថ្មីៗនេះដោយ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"កំពុងប្រើដោយ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"បានប្រើនាពេលថ្មីៗនេះដោយ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 6eead02..0b82b55 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"ವೀಕ್ಷಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೇವ್‌ ಮಾಡುವಾಗ ದೋಷ ಎದುರಾಗಿದೆ"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸುವಾಗ ದೋಷ ಕಂಡುಬಂದಿದೆ"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"ಪೂರ್ಣ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ವೀಕ್ಷಿಸಲಾಗುತ್ತಿದೆ"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ನಿರ್ಗಮಿಸಲು, ಮೇಲಿನಿಂದ ಕೆಳಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"ಅರ್ಥವಾಯಿತು"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ಹಿಂದೆ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ಮುಖಪುಟ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"ಮೆನು"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ಆಡಿಯೋ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ಹೆಡ್‌ಸೆಟ್"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ಇನ್‌ಪುಟ್"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"ಶ್ರವಣ ಸಾಧನಗಳು"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ಶ್ರವಣ ಸಾಧನಗಳು"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ಆನ್ ಮಾಡಲಾಗುತ್ತಿದೆ..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ಸ್ವಯಂ-ತಿರುಗುವಿಕೆ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ಪರದೆಯನ್ನು ಸ್ವಯಂ-ತಿರುಗಿಸಿ"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> ಮಿತಿ"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ನೀವು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡಿಂಗ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಕ್ಯಾಸ್ಟ್ ಮಾಡುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಕಾಣಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಮಾಡುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಆದ್ದರಿಂದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು, ಫೋಟೋಗಳು ಹಾಗೂ ಆಡಿಯೊ ಮತ್ತು ವೀಡಿಯೊದಂತಹ ವಿಷಯಗಳ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡಿಂಗ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಕ್ಯಾಸ್ಟ್ ಮಾಡುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್‌ನಲ್ಲಿ ತೋರಿಸುವ ಅಥವಾ ಪ್ಲೇ ಮಾಡುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಆದ್ದರಿಂದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು, ಫೋಟೋಗಳು ಹಾಗೂ ಆಡಿಯೊ ಮತ್ತು ವೀಡಿಯೊದಂತಹ ವಿಷಯಗಳ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ಪ್ರಾರಂಭಿಸಿ"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"ನೀವು ಆ್ಯಪ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಿದಾಗ ಹಂಚಿಕೊಳ್ಳುವಿಕೆಯನ್ನು ವಿರಾಮಗೊಳಿಸಲಾಗುತ್ತದೆ"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"ಬದಲಿಗೆ ಈ ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಿ"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"ಮರಳಿ ಬದಲಿಸಿ"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"ಆ್ಯಪ್ ಸ್ವಿಚ್"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ನಿಮ್ಮ IT ನಿರ್ವಾಹಕರು ನಿರ್ಬಂಧಿಸಿದ್ದಾರೆ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ಸಾಧನ ನೀತಿಯಿಂದ ಸ್ಕ್ರೀನ್ ಕ್ಯಾಪ್ಚರಿಂಗ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ಪೂರ್ಣ ಸ್ಕ್ರೀನ್‌ ಅನ್ನು ಹಿಗ್ಗಿಸಿ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ಸ್ಕ್ರೀನ್‌ನ ಅರ್ಧಭಾಗವನ್ನು ಝೂಮ್ ಮಾಡಿ"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"ಹಿಗ್ಗಿಸುವಿಕೆ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಿರಿ"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"ಮ್ಯಾಗ್ನಿಫಿಕೇಶನ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಮುಚ್ಚಿರಿ"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"ಎಡಿಟ್ ಮೋಡ್‌ನಿಂದ ನಿರ್ಗಮಿಸಿ"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ಮರುಗಾತ್ರಗೊಳಿಸಲು ಮೂಲೆಯನ್ನು ಡ್ರ್ಯಾಗ್ ಮಾಡಿ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ಡಯಾಗನಲ್ ಸ್ಕ್ರೋಲಿಂಗ್ ಅನ್ನು ಅನುಮತಿಸಿ"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ಮರುಗಾತ್ರಗೊಳಿಸಿ"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"ನಿಮ್ಮ ಬ್ಯಾಟರಿ ಮೀಟರ್ ಓದುವಾಗ ಸಮಸ್ಯೆ ಎದುರಾಗಿದೆ"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ಇನ್ನಷ್ಟು ಮಾಹಿತಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ಅಲಾರಾಂ ಸೆಟ್ ಆಗಿಲ್ಲ"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ಸ್ಕ್ರೀನ್ ಲಾಕ್ ನಮೂದಿಸಿ"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಸೆನ್ಸರ್"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ದೃಢೀಕರಿಸಿ"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ಸಾಧನವನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"ಅಸಿಸ್ಟೆಂಟ್ ಆಲಿಸುತ್ತಿದೆ"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ನೋಟಿಫಿಕೇಶನ್}one{# ನೋಟಿಫಿಕೇಶನ್‌ಗಳು}other{# ನೋಟಿಫಿಕೇಶನ್‌ಗಳು}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"ಟಿಪ್ಪಣಿಗಳನ್ನು ಬರೆದುಕೊಳ್ಳುವುದು"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"ಟಿಪ್ಪಣಿ ಮಾಡಿಕೊಳ್ಳುವಿಕೆ"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ಟಿಪ್ಪಣಿ ಮಾಡಿಕೊಳ್ಳುವಿಕೆ, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ಪ್ರಸಾರ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ನ ಪ್ರಸಾರವನ್ನು ನಿಲ್ಲಿಸಬೇಕೆ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ನೀವು <xliff:g id="SWITCHAPP">%1$s</xliff:g> ಅನ್ನು ಪ್ರಸಾರ ಮಾಡಿದರೆ ಅಥವಾ ಔಟ್‌ಪುಟ್ ಅನ್ನು ಬದಲಾಯಿಸಿದರೆ, ನಿಮ್ಮ ಪ್ರಸ್ತುತ ಪ್ರಸಾರವು ಸ್ಥಗಿತಗೊಳ್ಳುತ್ತದೆ"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ಅನ್ನು ತೆರೆಯಿರಿ"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"ವಾಲೆಟ್ ಆ್ಯಪ್ ಅನ್ನು ಶಾರ್ಟ್‌ಕಟ್ ಆಗಿ ಸೇರಿಸಲು, ಆ್ಯಪ್ ಅನ್ನು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಲಾಗಿದೆಯೇ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"ವಾಲೆಟ್ ಆ್ಯಪ್ ಅನ್ನು ಶಾರ್ಟ್‌ಕಟ್ ಆಗಿ ಸೇರಿಸಲು, ಕನಿಷ್ಠ ಒಂದು ಕಾರ್ಡ್ ಅನ್ನು ಸೇರಿಸಲಾಗಿದೆಯೇ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR ಕೋಡ್ ಸ್ಕ್ಯಾನರ್ ಅನ್ನು ಶಾರ್ಟ್‌ಕಟ್ ಆಗಿ ಸೇರಿಸಲು, ಕ್ಯಾಮರಾ ಆ್ಯಪ್ ಅನ್ನು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಲಾಗಿದೆಯೇ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home ಆ್ಯಪ್ ಅನ್ನು ಶಾರ್ಟ್‌ಕಟ್ ಆಗಿ ಸೇರಿಸಲು, ಆ್ಯಪ್ ಇನ್‌ಸ್ಟಾಲ್ ಆಗಿದೆಯೇ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• ಕನಿಷ್ಠ ಒಂದು ಸಾಧನ ಲಭ್ಯವಿದೆ"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"ನೋಟ್ಸ್ ಮಾಡಿಕೊಳ್ಳುವಿಕೆ ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ಬಳಸಲು ಡೀಫಾಲ್ಟ್ ಟಿಪ್ಪಣಿಗಳ ಆ್ಯಪ್ ಆಯ್ಕೆಮಾಡಿ"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant ನಿಮ್ಮ ಮಾತನ್ನು ಆಲಿಸುತ್ತಿದೆ"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಡೀಫಾಲ್ಟ್ ಟಿಪ್ಪಣಿಗಳ ಆ್ಯಪ್ ಅನ್ನು ಸೆಟ್ ಮಾಡಿ"</string>
     <string name="install_app" msgid="5066668100199613936">"ಆ್ಯಪ್ ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"ಮೈಕ್ರೊಫೋನ್ ಮತ್ತು ಕ್ಯಾಮರಾ"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"ಇತ್ತೀಚಿನ ಆ್ಯಪ್ ಬಳಕೆ"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ಇತ್ತೀಚಿನ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ನೋಡಿ"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"ಮುಗಿದಿದೆ"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"ಆಯ್ಕೆಗಳನ್ನು ವಿಸ್ತೃತಗೊಳಿಸಿ ಮತ್ತು ತೋರಿಸಿ"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"ಕುಗ್ಗಿಸಿ"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"ಈ ಆ್ಯಪ್‌ ಅನ್ನು ಮುಚ್ಚಿ"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಮುಚ್ಚಲಾಗಿದೆ"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"ಸೇವೆಯನ್ನು ನಿರ್ವಹಿಸಿ"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ನಿರ್ವಹಿಸಿ"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"ಫೋನ್ ಕರೆ ಮೂಲಕ ಬಳಕೆಯಲ್ಲಿದೆ"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"ಫೋನ್ ಕರೆಯಲ್ಲಿ ಇತ್ತೀಚಿಗೆ ಬಳಸಲಾಗಿದೆ"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> ನಿಂದ ಬಳಕೆಯಲ್ಲಿದೆ"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"ಇತ್ತೀಚೆಗೆ <xliff:g id="APP_NAME">%1$s</xliff:g> ಇದನ್ನು ಬಳಸಿದೆ"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> ನಿಂದ ಬಳಕೆಯಲ್ಲಿದೆ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"ಇತ್ತೀಚೆಗೆ <xliff:g id="APP_NAME">%1$s</xliff:g> ಇದನ್ನು ಬಳಸಿದೆ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> ನಿಂದ ಬಳಕೆಯಲ್ಲಿದೆ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"ಇತ್ತೀಚೆಗೆ <xliff:g id="APP_NAME">%1$s</xliff:g> ಇದನ್ನು ಬಳಸಿದೆ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index c1aba1c..ea7d5c2 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"탭하여 보기"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"화면 녹화 저장 중에 오류가 발생했습니다."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"화면 녹화 시작 중 오류 발생"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"전체 화면 모드"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"종료하려면 위에서 아래로 스와이프합니다."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"확인"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"뒤로"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"홈"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"메뉴"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"오디오"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"헤드셋"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"입력"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"보청기"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"보청기"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"켜는 중..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"자동 회전"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"화면 자동 회전"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"한도: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"일시중지됨"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"공유, 녹화 또는 전송 중에 Android가 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"앱을 공유, 녹화 또는 전송할 때는 Android가 해당 앱에 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"시작"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"앱을 전환할 때 공유를 일시중지합니다."</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"대신 이 앱 공유"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"다시 전환"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"앱 전환"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 관리자에 의해 차단됨"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"기기 정책에 의해 화면 캡처가 사용 중지되었습니다."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"전체 화면 확대"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"화면 일부 확대"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"확대 설정 열기"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"확대 설정 닫기"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"모서리를 드래그하여 크기 조절"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"대각선 스크롤 허용"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"크기 조절"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"배터리 수준을 읽는 중에 문제가 발생함"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"탭하여 자세한 정보를 확인하세요."</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"설정된 알람 없음"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"화면 잠금 입력"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"지문 센서"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"인증"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"기기 입력"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"어시스턴트가 대기 중입니다."</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{알림 #개}other{알림 #개}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"메모"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"메모"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"메모, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"방송 중"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> 방송을 중지하시겠습니까?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> 앱을 방송하거나 출력을 변경하면 기존 방송이 중단됩니다"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> 열기"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"월렛 앱을 바로가기로 추가하려면 앱이 설치되어 있는지 확인하세요."</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"월렛 앱을 바로가기로 추가하려면 하나 이상의 카드가 추가되어 있는지 확인하세요."</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR 코드 스캐너를 바로가기로 추가하려면 카메라 앱이 설치되어 있는지 확인하세요."</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home 앱을 바로가기로 추가하려면 앱이 설치되어 있는지 확인하세요."</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• 1대 이상의 기기를 사용할 수 있습니다."</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"메모 바로가기를 사용하려면 기본 메모 앱을 선택합니다."</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"어시스턴트가 대기 중임"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"설정에서 기본 메모 앱 설정"</string>
     <string name="install_app" msgid="5066668100199613936">"앱 설치"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"마이크 및 카메라"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"최근 앱 사용"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"최근 액세스 보기"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"완료"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"펼치기 및 옵션 보기"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"접기"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"이 앱 종료"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> 종료됨"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"서비스 관리"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"액세스 권한 관리"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"전화 통화에서 사용 중"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"최근 전화 통화에서 사용됨"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 사용 중"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"최근 <xliff:g id="APP_NAME">%1$s</xliff:g>에서 사용됨"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 사용 중(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"최근 <xliff:g id="APP_NAME">%1$s</xliff:g>에서 사용됨(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 사용 중(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"최근 <xliff:g id="APP_NAME">%1$s</xliff:g>에서 사용됨(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 0d7693c..cb9e67a 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Көрүү үчүн таптаңыз"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Экран тартылган жок"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Экранды жаздырууну баштоодо ката кетти"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Толук экран режимин көрүү"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Чыгуу үчүн экранды ылдый сүрүп коюңуз."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Түшүндүм"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Артка"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Үйгө"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Киргизүү"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Угуу аппараттары"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Угуу аппараттары"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Күйгүзүлүүдө…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Авто буруу"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Экранды авто буруу"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> чектөө"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"Тындырылды"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Бөлүшүп, жаздырып же тышкы экранга чыгарып жатканда Android экраныңыздагы бардык маалыматты же түзмөктө ойнотулуп жаткан нерселерди көрө алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү, сүрөттөрдү, аудио жана видеону көрсөтүп албаңыз."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Колдонмону бөлүшүп, жаздырып же тышкы экранга чыгарганда Android ал колдонмодо көрсөтүлүп жана ойнотулуп жаткан нерселерди көрө алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү, сүрөттөрдү, аудио жана видеону көрсөтүп албаңыз."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Баштоо"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Колдонмо которулганда бөлүшүү тындырылат"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Анын ордуна бул колдонмону бөлүшүү"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Кайра которулуу"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Колдонмону которуу"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT администраторуңуз бөгөттөп койгон"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Түзмөк саясаты экрандагыны тартып алууну өчүрүп койгон"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Баарын тазалап салуу"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Толук экранда ачуу"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Экрандын бир бөлүгүн чоңойтуу"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Чоңойтуу параметрлерин ачуу"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Чоңойтуу параметрлерин жабуу"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Өлчөмүн өзгөртүү үчүн бурчун сүйрөңүз"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Диагональ боюнча сыдырууга уруксат берүү"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Өлчөмүн өзгөртүү"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Батареяңыздын кубаты аныкталбай жатат"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Кеңири маалымат алуу үчүн таптап коюңуз"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ойготкуч коюлган жок"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"экран кулпусун киргизүү"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Манжа изинин сенсору"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"аныктыгын текшерүү"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"түзмөккө кирүү"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Жардамчы угуп жатат"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# билдирме}other{# билдирме}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Эскертме жазуу"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Эскертме жазуу"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Эскертме жазуу (<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>)"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Кеңири таратуу"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда кабарлоо токтотулсунбу?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Эгер <xliff:g id="SWITCHAPP">%1$s</xliff:g> колдонмосунда кабарласаңыз же аудионун чыгуусун өзгөртсөңүз, учурдагы кабарлоо токтотулат"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ачуу"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Капчык колдонмосун ыкчам баскыч катары кошуу үчүн колдонмону орнотуу керек"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Капчык колдонмосун ыкчам баскыч катары кошуу үчүн кеминде бир картаны кошуу керек"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR кодунун сканерин ыкчам баскыч катары кошуу үчүн камера колдонмосун орнотуу керек"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home колдонмосун ыкчам баскыч катары кошуу үчүн колдонмону орнотуу керек"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Кеминде бир түзмөк жеткиликтүү"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Эскертме жазуу ыкчам баскычын колдонуу үчүн демейки эскертме жазуу колдонмосун тандаңыз"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Жардамчы иштетилди"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Параметрлерден демейки кыска жазуулар колдонмосун тууралаңыз"</string>
     <string name="install_app" msgid="5066668100199613936">"Колдонмону орнотуу"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон жана камера"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Жакында колдонмолордо иштетилген"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Акыркы пайдалануусун көрүү"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Бүттү"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Параметрлерди жайып көрсөтүү"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Жыйыштыруу"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Бул колдонмону жабуу"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> жабылды"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Кызматты тескөө"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Кирүү мүмкүнчүлүгүн тескөө"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Телефон чалууда колдонулуп жатат"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Акыркы жолу телефон чалууда колдонулду"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда иштетилип жатат"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Акыркы жолу <xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда иштетилди"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда иштетилип жатат (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Акыркы жолу <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) колдонмосунда иштетилди"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосунда иштетилип жатат (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Акыркы жолу <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) колдонмосунда иштетилди"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index da4547b..0667cd8 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -35,6 +35,9 @@
     <dimen name="volume_tool_tip_top_margin">12dp</dimen>
     <dimen name="volume_row_slider_height">128dp</dimen>
 
+    <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
+    <dimen name="immersive_mode_cling_width">380dp</dimen>
+
     <dimen name="controls_activity_view_top_offset">25dp</dimen>
 
     <dimen name="biometric_dialog_button_negative_max_width">140dp</dimen>
@@ -66,6 +69,9 @@
 
     <dimen name="controls_header_horizontal_padding">12dp</dimen>
     <dimen name="controls_content_margin_horizontal">16dp</dimen>
+    <dimen name="controls_content_padding">16dp</dimen>
+    <dimen name="control_list_vertical_spacing">8dp</dimen>
+    <dimen name="control_list_horizontal_spacing">16dp</dimen>
 
     <!-- Rear Display Education dimens -->
     <dimen name="rear_display_animation_width">246dp</dimen>
diff --git a/packages/SystemUI/res/values-ldrtl/dimens.xml b/packages/SystemUI/res/values-ldrtl/dimens.xml
new file mode 100644
index 0000000..0d99b61
--- /dev/null
+++ b/packages/SystemUI/res/values-ldrtl/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources>
+    <dimen name="media_output_dialog_icon_left_radius">0dp</dimen>
+    <dimen name="media_output_dialog_icon_right_radius">28dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index e72e7b4..3e26137 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"ແຕະເພື່ອເບິ່ງ"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ເກີດຂໍ້ຜິດພາດໃນການບັນທຶກໜ້າຈໍ"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ເກີດຄວາມຜິດພາດໃນການບັນທຶກໜ້າຈໍ"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"ກຳລັງ​ເບິ່ງ​ເຕັມ​​ຈໍ"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ເພື່ອອອກ, ໃຫ້ປັດລົງຈາກເທິງສຸດ."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"ເຂົ້າໃຈແລ້ວ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ກັບຄືນ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ໜ້າທຳອິດ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"ເມນູ"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ສຽງ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ຊຸດຫູຟັງ"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ການປ້ອນຂໍ້ມູນ"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"ເຄື່ອງຊ່ວຍຟັງ"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ເຄື່ອງຊ່ວຍຟັງ"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ກຳລັງເປີດ..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ໝຸນ​ອັດ​ຕະ​ໂນ​ມັດ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ໝຸນໜ້າຈໍອັດຕະໂນມັດ"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"ຈຳ​ກັດ <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"ຢຸດໄວ້ຊົ່ວຄາວ"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ເມື່ອທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານ, Android ຈະມີສິດເຂົ້າເຖິງທຸກສິ່ງທີ່ປາກົດຢູ່ໜ້າຈໍຂອງທ່ານ ຫຼື ຫຼິ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ດັ່ງນັ້ນ, ໃຫ້ລະມັດລະວັງສິ່ງຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ, ຮູບພາບ ພ້ອມທັງສຽງ ແລະ ວິດີໂອ."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ເມື່ອທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານແອັບ, Android ຈະມີສິດເຂົ້າເຖິງທຸກສິ່ງທີ່ສະແດງ ຫຼື ຫຼິ້ນຢູ່ແອັບດັ່ງກ່າວ. ດັ່ງນັ້ນ, ໃຫ້ລະມັດລະວັງສິ່ງຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ, ຮູບພາບ ພ້ອມທັງສຽງ ແລະ ວິດີໂອ."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ເລີ່ມ"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"ການແບ່ງປັນຈະຢຸດຊົ່ວຄາວເມື່ອທ່ານປ່ຽນແອັບ"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"ແບ່ງປັນແອັບນີ້ແທນ"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"ປ່ຽນກັບຄືນ"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"ປ່ຽນແອັບ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ຖືກບລັອກໄວ້ໂດຍຜູ້ເບິ່ງແຍງລະບົບໄອທີຂອງທ່ານ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ການຖ່າຍຮູບໜ້າຈໍຖືກປິດການນຳໃຊ້ໄວ້ໂດຍນະໂຍບາຍອຸປະກອນ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ລຶບລ້າງທັງໝົດ"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ຂະຫຍາຍເຕັມຈໍ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ຂະຫຍາຍບາງສ່ວນຂອງໜ້າຈໍ"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"ເປີດການຕັ້ງຄ່າການຂະຫຍາຍ"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"ປິດການຕັ້ງຄ່າການຂະຫຍາຍ"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ລາກຢູ່ມຸມເພື່ອປັບຂະໜາດ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ອະນຸຍາດໃຫ້ເລື່ອນທາງຂວາງ"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ປ່ຽນຂະໜາດ"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"ເກີດບັນຫາໃນການອ່ານຕົວວັດແທກແບັດເຕີຣີຂອງທ່ານ"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ແຕະເພື່ອເບິ່ງຂໍ້ມູນເພີ່ມເຕີມ"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ບໍ່ໄດ້ຕັ້ງໂມງປຸກ"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ໃສ່ຂໍ້ມູນການລັອກໜ້າຈໍ"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ເຊັນ​ເຊີລາຍນິ້ວ​ມື"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ພິສູດຢືນຢັນ"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ເຂົ້າອຸປະກອນ"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"ຜູ້ຊ່ວຍກຳລັງຟັງ"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ການແຈ້ງເຕືອນ}other{# ການແຈ້ງເຕືອນ}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"ການຈົດບັນທຶກ"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"ການຈົດບັນທຶກ"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ການຈົດບັນທຶກ <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ກຳລັງອອກອາກາດ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"ຢຸດການອອກອາກາດ <xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ຫາກທ່ານອອກອາກາດ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ຫຼື ປ່ຽນເອົ້າພຸດ, ການອອກອາກາດປັດຈຸບັນຂອງທ່ານຈະຢຸດ"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"ເປີດ <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"ເພື່ອເພີ່ມແອັບ Wallet ເປັນທາງລັດ, ກະລຸນາກວດສອບໃຫ້ແນ່ໃຈວ່າໄດ້ຕິດຕັ້ງແອັບແລ້ວ"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"ເພື່ອເພີ່ມແອັ Wallet ເປັນທາງລັດ, ກະລຸນາກວດສອບໃຫ້ແນ່ໃຈວ່າໄດ້ເພີ່ມຢ່າງໜ້ອຍ 1 ບັດແລ້ວ"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"ເພື່ອເພີ່ມຕົວສະແກນລະຫັດ QR ເປັນທາງລັດ, ກະລຸນາກວດສອບໃຫ້ແນ່ໃຈວ່າໄດ້ຕິດຕັ້ງແອັບກ້ອງຖ່າຍຮູບແລ້ວ"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"ເພື່ອເພີ່ມແອັບ Home ເປັນທາງລັດ, ກະລຸນາກວດສອບໃຫ້ແນ່ໃຈວ່າໄດ້ຕິດຕັ້ງແອັບແລ້ວ"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• ມີຢ່າງໜ້ອຍ 1 ອຸປະກອນພ້ອມໃຫ້ນຳໃຊ້"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"ເລືອກແອັບບັນທຶກເລີ່ມຕົ້ນເພື່ອໃຊ້ທາງລັດການຈົດບັນທຶກ"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"ການເອີ້ນໃຊ້ຜູ້ຊ່ວຍເປີດຢູ່"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ຕັ້ງຄ່າແອັບຈົດບັນທຶກເລີ່ມຕົ້ນໃນການຕັ້ງຄ່າ"</string>
     <string name="install_app" msgid="5066668100199613936">"ຕິດຕັ້ງແອັບ"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"ໄມໂຄຣໂຟນ ແລະ ກ້ອງຖ່າຍຮູບ"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"ການໃຊ້ແອັບຫຼ້າສຸດ"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ເບິ່ງສິດເຂົ້າເຖິງຫຼ້າສຸດ"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"ແລ້ວໆ"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"ຂະຫຍາຍ ແລະ ສະແດງຕົວເລືອກ"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"ຫຍໍ້ລົງ"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"ປິດແອັບນີ້"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"ປິດ <xliff:g id="APP_NAME">%1$s</xliff:g> ແລ້ວ"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"ຈັດການບໍລິການ"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"ຈັດການສິດເຂົ້າເຖິງ"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"ໃຊ້ຢູ່ໂດຍການໂທ"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"ໃຊ້ຫຼ້າສຸດໃນການໂທ"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"ໃຊ້ຢູ່ໂດຍ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"ໃຊ້ຫຼ້າສຸດໂດຍ <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"ໃຊ້ຢູ່ໂດຍ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"ໃຊ້ຫຼ້າສຸດໂດຍ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"ໃຊ້ຢູ່ໂດຍ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"ໃຊ້ຫຼ້າສຸດໂດຍ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 9681464..a7d874c 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Palieskite, kad peržiūrėtumėte"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Išsaugant ekrano įrašą įvyko klaida"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Pradedant ekrano vaizdo įrašymą iškilo problema"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Peržiūrima viso ekrano režimu"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Jei norite išeiti, perbraukite žemyn iš viršaus."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Supratau"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Atgal"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Pagrindinis"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Meniu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Garsas"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Virtualiosios realybės įrenginys"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Įvestis"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Klausos aparatai"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Klausos aparatai"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Įjungiama…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatinis pasukimas"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatiškai sukti ekraną"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Limitas: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"<xliff:g id="DATA_LIMIT">%s</xliff:g> įspėjimas"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Darbo programos"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Pristabdyta"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Nakties šviesa"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Per saulėlydį"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Iki saulėtekio"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kai bendrinate, įrašote ar perduodate turinį, „Android“ gali pasiekti viską, kas rodoma ekrane ar leidžiama įrenginyje. Todėl būkite atsargūs naudodami slaptažodžius, išsamią mokėjimo metodo informaciją, pranešimus, nuotraukas ir garso bei vaizdo įrašus."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kai bendrinate, įrašote ar perduodate programą, „Android“ gali pasiekti viską, kas rodoma ar leidžiama programoje. Todėl būkite atsargūs naudodami slaptažodžius, išsamią mokėjimo metodo informaciją, pranešimus, nuotraukas ir garso bei vaizdo įrašus."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pradėti"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Bendrinimas pristabdomas, kai perjungiate programas"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Bendrinti šią programą"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Perjungti atgal"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Programų jungiklis"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Užblokavo jūsų IT administratorius"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekrano fiksavimo funkcija išjungta vadovaujantis įrenginio politika"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Viską išvalyti"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Viso ekrano didinimas"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Didinti ekrano dalį"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Atidaryti didinimo nustatymus"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Uždaryti didinimo nustatymus"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Norėdami keisti dydį, vilkite kampą"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Slinkimo įstrižai leidimas"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Pakeisti dydį"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Nuskaitant akumuliatoriaus skaitiklį iškilo problema"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Palieskite, kad sužinotumėte daugiau informacijos"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nenustatyta signalų"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"įvesti ekrano užraktą"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Kontrolinio kodo jutiklis"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"nustatytumėte tapatybę"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"pasiektumėte įrenginį"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Padėjėjas klausosi"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# pranešimas}one{# pranešimas}few{# pranešimai}many{# pranešimo}other{# pranešimų}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Užrašų kūrimas"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Užrašų kūrimas"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Užrašų kūrimas, „<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>“"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transliavimas"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Sustabdyti „<xliff:g id="APP_NAME">%1$s</xliff:g>“ transliaciją?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jei transliuosite „<xliff:g id="SWITCHAPP">%1$s</xliff:g>“ arba pakeisite išvestį, dabartinė transliacija bus sustabdyta"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Atidaryti „<xliff:g id="APPNAME">%1$s</xliff:g>“"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Jei norite pridėti programą „Wallet“ kaip šaukinį, įsitikinkite, kad programa įdiegta"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Jei norite pridėti programą „Wallet“ kaip šaukinį, įsitikinkite, kad pridėta bent viena kortelė"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Jei norite pridėti QR kodų skaitytuvą kaip šaukinį, įsitikinkite, kad fotoaparato programa įdiegta"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Jei norite pridėti programą „Home“ kaip šaukinį, įsitikinkite, kad programa įdiegta"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Pasiekiamas bent vienas įrenginys"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Pasirinkite numatytąją užrašų programą, kuriai norite naudoti užrašų kūrimo šaukinį"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Padėjėjas klauso"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nustatykite numatytąją užrašų programą Nustatymuose"</string>
     <string name="install_app" msgid="5066668100199613936">"Įdiegti programą"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofonas ir fotoaparatas"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Pastarasis programos naudojimas"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Žr. pastarąją prieigą"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Atlikta"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Išskleisti ir rodyti parinktis"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Sutraukti"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Uždaryti šią programą"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ uždaryta"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Tvarkyti paslaugą"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Tvarkyti prieigą"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Naudotojo telefono skambučio programa"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Neseniai naudojo telefono skambučio programa"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Naudoja <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Neseniai naudojo „<xliff:g id="APP_NAME">%1$s</xliff:g>“"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Naudoja <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Neseniai naudojo „<xliff:g id="APP_NAME">%1$s</xliff:g>“ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Naudoja <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Neseniai naudojo „<xliff:g id="APP_NAME">%1$s</xliff:g>“ (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 1caf935..972380c 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Pieskarieties, lai skatītu"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Saglabājot ekrāna ierakstu, radās kļūda."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Sākot ierakstīt ekrāna saturu, radās kļūda."</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Skatīšanās pilnekrāna režīmā"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Lai izietu, no augšdaļas velciet lejup."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Labi"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Atpakaļ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Sākums"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Izvēlne"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Austiņas"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ievade"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Dzirdes aparāti"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Dzirdes aparāti"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Notiek ieslēgšana…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automātiska pagriešana"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automātiska ekrāna pagriešana"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Ierobežojums: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"<xliff:g id="DATA_LIMIT">%s</xliff:g> brīdinājums"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Darba lietotnes"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Darbība apturēta"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Nakts režīms"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Saulrietā"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Līdz saullēktam"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kopīgošanas, ierakstīšanas vai apraides 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, fotoattēliem un audio un video saturu."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Lietotnes kopīgošanas, ierakstīšanas vai apraides 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, fotoattēliem un audio un video saturu."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Sākt"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Pārslēdzot lietotnes, tiek pārtraukta kopīgošana."</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Tā vietā kopīgot šo lietotni"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Pārslēgties uz iepriekšējo"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Lietotņu pārslēgšana"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloķējis jūsu IT administrators"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ierīces politika ir atspējojusi ekrānuzņēmumu izveidi"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Dzēst visu"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Palielināt visu ekrānu"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Palielināt ekrāna daļu"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Atvērt palielinājuma iestatījumus"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Aizvērt palielinājuma iestatījumus"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Velciet stūri, lai mainītu izmērus"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Atļaut ritināšanu pa diagonāli"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Mainīt lielumu"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Nevar iegūt informāciju par akumulatora uzlādes līmeni."</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Pieskarieties, lai iegūtu plašāku informāciju."</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nav iestatīts signāls"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ievadīt ekrāna bloķēšanas informāciju"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Pirksta nospieduma sensors"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"veiktu autentificēšanu"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"izmantotu ierīci"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistents klausās"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# paziņojums}zero{# paziņojumu}one{# paziņojums}other{# paziņojumi}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Piezīmju pierakstīšana"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Piezīmju pierakstīšana"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Piezīmju pierakstīšana, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Notiek apraidīšana"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vai apturēt lietotnes <xliff:g id="APP_NAME">%1$s</xliff:g> apraidīšanu?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ja sāksiet lietotnes <xliff:g id="SWITCHAPP">%1$s</xliff:g> apraidīšanu vai mainīsiet izvadi, pašreizējā apraide tiks apturēta"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Atvērt lietotni <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Lai varētu pievienot lietotni Maks kā saīsni, lietotnei ir jābūt instalētai."</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Lai varētu pievienot lietotni Maks kā saīsni, ir jābūt pievienotai vismaz vienai kartei."</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Lai varētu pievienot lietotni Kvadrātkoda skeneris kā saīsni, ir jābūt instalētai kameras lietotnei."</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Lai varētu pievienot lietotni Home kā saīsni, lietotnei ir jābūt instalētai."</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Ir pieejama vismaz viena ierīce."</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Atlasiet noklusējuma piezīmju lietotni, lai izmantotu piezīmju pierakstīšanas saīsni."</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Asistents klausās"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Iestatījumos iestatiet noklusējuma piezīmju lietotni."</string>
     <string name="install_app" msgid="5066668100199613936">"Instalēt lietotni"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofons un kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nesen izmantoja lietotnes"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Skatīt neseno piekļuvi"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Gatavs"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Izvērst un rādīt opcijas"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Sakļaut"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Aizvērt šo lietotni"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> ir aizvērta."</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Pārvaldīt pakalpojumu"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Pārvaldīt piekļuvi"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"To izmanto tālruņa zvanā"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Nesen to izmantoja tālruņa zvanā"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"To izmanto lietotne <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Nesen to izmantoja lietotne <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"To izmanto lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nesen to izmantoja lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"To izmanto lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nesen to izmantoja lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index e269f59..4b1e5a8 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Допрете за прегледување"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Грешка при зачувувањето на снимката од екранот"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Грешка при почетокот на снимањето на екранот"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Се прикажува на цел екран"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"За да излезете, повлечете одозгора надолу."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Сфатив"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Почетна страница"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Мени"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалки"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Влез"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Слушни помагала"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слушни помагала"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Се вклучува…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоматско ротирање"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматско ротирање на екранот"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Лимит: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"Паузирано"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Кога споделувате, снимате или емитувате, Android има пристап до сѐ што е видливо на вашиот екран или пуштено на вашиот уред. Затоа, бидете внимателни со работи како лозинки, детали за плаќање, пораки, фотографии и аудио и видео."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Кога споделувате, снимате или емитувате апликација, Android има пристап до сѐ што се прикажува или пушта на таа апликација. Затоа, бидете внимателни со работи како лозинки, детали за плаќање, пораки, фотографии и аудио и видео."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Започни"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Споделувањето се паузира кога се префрлате на друга апликација"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Наместо тоа, споделете ја апликацијава"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Враќање"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Префрлање на друга апликација"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокирано од IT-администраторот"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Снимањето на екранот е оневозможено со правила на уредот"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Избриши сѐ"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Зголемете го целиот екран"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Зголемувајте дел од екранот"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Отвори поставки за зголемување"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Затворете ги поставките за зголемување"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Повлечете на аголот за да ја промените големината"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Дозволете дијагонално лизгање"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Промени големина"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Проблем при читањето на мерачот на батеријата"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Допрете за повеќе информации"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Не е поставен аларм"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"внесете PIN/шема/лозинка"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сензор за отпечатоци"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"автентицирате"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"внесете уред"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"„Помошникот“ слуша"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# известување}one{# известување}other{# известувања}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Фаќање белешки"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Фаќање белешки"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Фаќање белешки, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Емитување"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Да се прекине емитувањето на <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ако емитувате на <xliff:g id="SWITCHAPP">%1$s</xliff:g> или го промените излезот, тековното емитување ќе запре"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Отворете ја <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"За да ја додадете апликацијата Wallet како кратенка, апликацијата мора да е инсталирана"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"За да ја додадете апликацијата Wallet како кратенка, мора да имате додадено најмалку една картичка"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"За да го додадете скенерот на QR-кодови како кратенка, погрижете се дека имате инсталирано апликација за камера"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"За да ја додадете апликацијата Home како кратенка, апликацијата мора да е инсталирана"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• достапен е најмалку еден уред"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Изберете стандардна апликација за белешки за да ја користите кратенката за фаќање белешки"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Вниманието на „Помошникот“ е вклучено"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Поставете стандардна апликација за белешки во „Поставки“"</string>
     <string name="install_app" msgid="5066668100199613936">"Инсталирајте ја апликацијата"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Неодамнешно користење на апликација"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Видете го скорешниот пристап"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Готово"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Проширување и прикажување на опциите"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Собирање"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Затвори ја апликацијава"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Апликацијата <xliff:g id="APP_NAME">%1$s</xliff:g> е затворена"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Управувајте со услугата"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Управувајте со пристапот"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Се користи од телефонски повик"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Неодамна користено во телефонски повик"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Се користи од <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Неодамна користено од <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Се користи од <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Неодамна користено од <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Се користи од <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Неодамна користено од <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index b0d73ac..7202f5e 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"കാണാൻ ടാപ്പ് ചെയ്യുക"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"സ്ക്രീൻ റെക്കോർഡിംഗ് സംരക്ഷിക്കുന്നതിൽ പിശക്"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"സ്ക്രീൻ റെക്കോർഡിംഗ് ആരംഭിക്കുന്നതിൽ പിശക്"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"പൂർണ്ണ സ്‌ക്രീനിൽ കാണുന്നു"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"പുറത്തുകടക്കാൻ, മുകളിൽ നിന്ന് താഴോട്ട് സ്വൈപ്പ് ചെയ്യുക."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"മനസ്സിലായി"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"മടങ്ങുക"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ഹോം"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"മെനു"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ഓഡിയോ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ഹെഡ്‌സെറ്റ്"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ഇൻപുട്ട്"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"ശ്രവണ സഹായികൾ"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ശ്രവണ സഹായികൾ"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ഓണാക്കുന്നു…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"സ്‌ക്രീൻ സ്വയമേവ തിരിയൽ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"സ്‌ക്രീൻ സ്വയമേവ തിരിക്കുക"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> പരിധി"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"തൽക്കാലം നിർത്തി"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, Android-ന് നിങ്ങളുടെ സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് കാര്യത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, ഓഡിയോ, വീഡിയോ എന്നിവ പോലുള്ള കാര്യങ്ങൾ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ഒരു ആപ്പ് പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, Android-ന് ആ ആപ്പിൽ കാണിക്കുന്ന അല്ലെങ്കിൽ പ്ലേ ചെയ്യുന്ന എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, ഓഡിയോ, വീഡിയോ എന്നിവ പോലുള്ള കാര്യങ്ങൾ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ആരംഭിക്കുക"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"നിങ്ങൾ ആപ്പുകൾ മാറുമ്പോൾ പങ്കിടൽ താൽക്കാലികമായി നിർത്തും"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"പകരം ഈ ആപ്പ് പങ്കിടുക"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"മടങ്ങുക"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"ആപ്പ് മാറുക"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"നിങ്ങളുടെ ഐടി അഡ്‌മിൻ ബ്ലോക്ക് ചെയ്‌തു"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ഉപകരണ നയം, സ്ക്രീൻ ക്യാപ്‌ചർ ചെയ്യൽ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്‌ക്കുക"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"സ്ക്രീൻ പൂർണ്ണമായും മാഗ്നിഫൈ ചെയ്യുക"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"സ്‌ക്രീനിന്റെ ഭാഗം മാഗ്നിഫൈ ചെയ്യുക"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"മാഗ്നിഫിക്കേഷൻ ക്രമീകരണം തുറക്കുക"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"മാഗ്നിഫിക്കേഷൻ ക്രമീകരണം അടയ്‌ക്കുക"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"എഡിറ്റ് മോഡിൽ നിന്ന് പുറത്തുകടക്കുക"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"വലുപ്പം മാറ്റാൻ മൂല വലിച്ചിടുക"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ഡയഗണൽ സ്‌ക്രോളിംഗ് അനുവദിക്കുക"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"വലുപ്പം മാറ്റുക"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"നിങ്ങളുടെ ബാറ്ററി മീറ്റർ വായിക്കുന്നതിൽ പ്രശ്‌നമുണ്ട്"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"കൂടുതൽ വിവരങ്ങൾക്ക് ടാപ്പ് ചെയ്യുക"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"അലാറം സജ്ജീകരിച്ചിട്ടില്ല"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"സ്‌ക്രീൻ ലോക്ക് നൽകുക"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ഫിംഗർപ്രിന്റ് സെൻസർ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"പരിശോധിച്ചുറപ്പിക്കുക"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ഉപകരണം നൽകുക"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant കേൾക്കുന്നു"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# അറിയിപ്പ്}other{# അറിയിപ്പുകൾ}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"കുറിപ്പ് രേഖപ്പെടുത്തൽ"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"കുറിപ്പ് രേഖപ്പെടുത്തൽ"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"കുറിപ്പ് രേഖപ്പെടുത്തൽ, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"പ്രക്ഷേപണം ചെയ്യുന്നു"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ബ്രോഡ്‌കാസ്റ്റ് ചെയ്യുന്നത് അവസാനിപ്പിക്കണോ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"നിങ്ങൾ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ബ്രോഡ്‌കാസ്റ്റ് ചെയ്യുകയോ ഔട്ട്പുട്ട് മാറ്റുകയോ ചെയ്താൽ നിങ്ങളുടെ നിലവിലുള്ള ബ്രോഡ്‌കാസ്റ്റ് അവസാനിക്കും"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> തുറക്കുക"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"കുറുക്കുവഴിയായി Wallet ആപ്പ് ചേർക്കാൻ, ആപ്പ് ഇൻസ്‌റ്റാൾ ചെയ്തിട്ടുണ്ടെന്ന് ഉറപ്പാക്കുക"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"കുറുക്കുവഴിയായി Wallet ആപ്പ് ചേർക്കാൻ, ഒരു കാർഡെങ്കിലും ചേർത്തിട്ടുണ്ടെന്ന് ഉറപ്പാക്കുക"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"കുറുക്കുവഴിയായി QR കോഡ് സ്‌കാനർ ചേർക്കാൻ, ക്യാമറാ ആപ്പ് ഇൻസ്‌റ്റാൾ ചെയ്തിട്ടുണ്ടെന്ന് ഉറപ്പാക്കുക"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"കുറുക്കുവഴിയായി Home ആപ്പ് ചേർക്കാൻ, ആപ്പ് ഇൻസ്‌റ്റാൾ ചെയ്തിട്ടുണ്ടെന്ന് ഉറപ്പാക്കുക"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• ഒരു ഉപകരണമെങ്കിലും ലഭ്യമാണ്"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"കുറിപ്പ് രേഖപ്പെടുത്തൽ കുറുക്കുവഴി ഉപയോഗിക്കുന്നതിന് ഒരു ഡിഫോൾട്ട് കുറിപ്പ് ആപ്പ് തിരഞ്ഞെടുക്കുക"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant സജീവമാണ്"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ക്രമീകരണത്തിൽ കുറിപ്പുകൾക്കുള്ള ഡിഫോൾട്ട് ആപ്പ് സജ്ജീകരിക്കുക"</string>
     <string name="install_app" msgid="5066668100199613936">"ആപ്പ് ഇൻസ്റ്റാൾ ചെയ്യൂ"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"മൈക്രോഫോണും ക്യാമറയും"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"അടുത്തിടെയുള്ള ആപ്പ് ഉപയോഗം"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"അടുത്തിടെയുള്ള ആക്‌സസ് കാണുക"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"പൂർത്തിയായി"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"വികസിപ്പിച്ച് ഓപ്ഷനുകൾ കാണിക്കുക"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"ചുരുക്കുക"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"ഈ ആപ്പ് അടയ്ക്കുക"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> അടച്ചു"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"സേവനം മാനേജ് ചെയ്യുക"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"ആക്‌സസ് മാനേജ് ചെയ്യുക"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"ഫോൺ കോളിൽ ഉപയോഗിക്കുന്നു"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"ഫോൺ കോളിൽ അടുത്തിടെ ഉപയോഗിച്ചു"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> ഉപയോഗിക്കുന്നു"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"<xliff:g id="APP_NAME">%1$s</xliff:g> അടുത്തിടെ ഉപയോഗിച്ചു"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ഉപയോഗിക്കുന്നു"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) അടുത്തിടെ ഉപയോഗിച്ചു"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ഉപയോഗിക്കുന്നു"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) അടുത്തിടെ ഉപയോഗിച്ചു"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 24e6001..470e0fd 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Харахын тулд товшино уу"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Дэлгэцийн бичлэгийг хадгалахад алдаа гарлаа"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Дэлгэцийн бичлэгийг эхлүүлэхэд алдаа гарлаа"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Бүтэн дэлгэцээр үзэж байна"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Гарах бол дээрээс доош шударна уу."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Ойлголоо"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Буцах"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Гэрийн"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Цэс"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Чихэвч"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Оролт"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Сонсголын төхөөрөмж"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Сонсголын төхөөрөмж"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Асааж байна…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоматаар эргэх"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Дэлгэцийг автоматаар эргүүлэх"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> хязгаар"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"Түр зогсоосон"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Таныг хуваалцаж, бичлэг хийж эсвэл дамжуулж байх үед Android таны дэлгэцэд харуулсан эсвэл төхөөрөмжид тань тоглуулсан аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж, зураг, аудио болон видео зэрэг зүйлд болгоомжтой хандаарай."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Таныг хуваалцаж, бичлэг хийж эсвэл дамжуулж байх үед Android тухайн аппад харуулсан эсвэл тоглуулсан аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж, зураг, аудио болон видео зэрэг бусад зүйлд болгоомжтой хандаарай."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Эхлүүлэх"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Таныг аппууд сэлгэх үед хуваалцахыг түр зогсооно"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Оронд нь энэ аппыг хуваалцах"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Буцаж сэлгэх"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Апп сэлгэх"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Таны IT админ блоклосон"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Төхөөрөмжийн бодлогоор дэлгэцийн зураг авахыг идэвхгүй болгосон"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Бүгдийг арилгах"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Бүтэн дэлгэцийг томруулах"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Дэлгэцийн нэг хэсгийг томруулах"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Томруулах тохиргоог нээх"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Томруулах тохиргоог хаах"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Хэмжээг өөрчлөхийн тулд булангаас чирнэ үү"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Хөндлөн гүйлгэхийг зөвшөөрөх"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Хэмжээг өөрчлөх"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Таны батарей хэмжигчийг уншихад асуудал гарлаа"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Нэмэлт мэдээлэл авахын тулд товшино уу"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Сэрүүлэг тавиагүй"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"дэлгэцийн түгжээ оруулах"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Хурууны хээ мэдрэгч"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"баталгаажуулах"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"төхөөрөмж оруулах"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Туслах сонсож байна"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# мэдэгдэл}other{# мэдэгдэл}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Тэмдэглэл хөтлөх"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Тэмдэглэл хөтлөх"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Тэмдэглэл хөтлөх, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Нэвтрүүлэлт"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г нэвтрүүлэхээ зогсоох уу?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Хэрэв та <xliff:g id="SWITCHAPP">%1$s</xliff:g>-г нэвтрүүлсэн эсвэл гаралтыг өөрчилсөн бол таны одоогийн нэвтрүүлэлтийг зогсооно"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g>-г нээх"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Wallet аппыг товчлолоор нэмэхийн тулд уг аппыг суулгасан эсэхийг шалгана уу"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Wallet аппыг товчлолоор нэмэхийн тулд дор хаяж нэг карт нэмсэн эсэхийг шалгана уу"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR код сканнерыг товчлолоор нэмэхийн тулд камерын аппыг суулгасан эсэхийг шалгана уу"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home аппыг товчлолоор нэмэхийн тулд уг аппыг суулгасан эсэхийг шалгана уу"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Дор хаяж нэг төхөөрөмж боломжтой"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Тэмдэглэл хөтлөх товчлолыг ашиглахын тулд тэмдэглэлийн өгөгдмөл аппыг сонгоно уу"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Туслах анхаарлаа хандуулж байна"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Тохиргоонд тэмдэглэлийн өгөгдмөл апп тохируулна уу"</string>
     <string name="install_app" msgid="5066668100199613936">"Аппыг суулгах"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон болон камер"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Аппын саяхны ашиглалт"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Саяхны хандалтыг харах"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Болсон"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Сонголтыг дэлгэж, харуулах"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Хураах"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Энэ аппыг хаах"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г хаасан"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Үйлчилгээг удирдах"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Хандалтыг удирдах"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Утасны дуудлага ашиглаж байна"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Саяхан утасны дуудлагад ашигласан"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> ашиглаж байна"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Саяхан <xliff:g id="APP_NAME">%1$s</xliff:g> ашигласан"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ашиглаж байна"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Саяхан <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ашигласан"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ашиглаж байна"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Саяхан <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ашигласан"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index ab11bf3..f5abe78 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"पाहण्यासाठी टॅप करा"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"स्क्रीन रेकॉर्डिंग सेव्ह करताना एरर आली"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रीन रेकॉर्डिंग सुरू करताना एरर आली"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"पूर्ण स्क्रीन पाहत आहात"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"बाहेर पडण्यासाठी, वरून खाली स्वाइप करा."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"समजले"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"मागे"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"होम"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"मेनू"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ऑडिओ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"इनपुट"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"श्रवणयंत्रे"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"श्रवणयंत्रे"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"सुरू करत आहे…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ऑटो-रोटेट"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ऑटो-रोटेट स्क्रीन"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> मर्यादा"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"थांबवले आहे"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"तुम्ही शेअर, रेकॉर्ड किंवा कास्ट करत असताना, Android ला तुमच्या स्क्रीनवर दाखवलेल्या किंवा डिव्हाइसवर प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज, फोटो आणि ऑडिओ व व्हिडिओ यांसारख्या गोष्टींबाबत सावधगिरी बाळगा."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"तुम्ही एखादे अ‍ॅप शेअर, रेकॉर्ड किंवा कास्ट करत असताना, Android ला त्या अ‍ॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अ‍ॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज, फोटो आणि ऑडिओ व व्हिडिओ यांसारख्या गोष्टींबाबत सावधगिरी बाळगा."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"सुरुवात करा"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"तुम्ही ॲप्स स्विच करता, तेव्हा शेअरिंग थांबते"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"त्याऐवजी हे अ‍ॅप शेअर करा"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"परत स्विच करा"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"अ‍ॅप स्विच"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"तुमच्या आयटी ॲडमिनने ब्लॉक केले आहे"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिव्हाइस धोरणाने स्‍क्रीन कॅप्‍चर करणे बंद केले आहे"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"फुल स्क्रीन मॅग्निफाय करा"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"स्क्रीनचा काही भाग मॅग्निफाय करा"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"मॅग्निफिकेशन सेटिंग्ज उघडा"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"मॅग्निफिकेशन सेटिंग्ज बंद करा"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"आकार बदलण्यासाठी कोपरा ड्रॅग करा"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"तिरपे स्क्रोल करण्याची अनुमती द्या"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"आकार बदला"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"तुमचे बॅटरी मीटर वाचताना समस्या आली"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"अधिक माहितीसाठी टॅप करा"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"अलार्म सेट केला नाही"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"स्क्रीन लॉक एंटर करा"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"फिंगरप्रिंट सेन्सर"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ऑथेंटिकेट करा"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"डिव्हाइस एंटर करा"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant ऐकत आहे"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# सूचना}other{# सूचना}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"नोटटेकिंग"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"नोंद घेणे"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"नोंद घेणे, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ब्रॉडकास्ट करत आहे"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> चे प्रसारण थांबवायचे आहे का?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"तुम्ही <xliff:g id="SWITCHAPP">%1$s</xliff:g> चे प्रसारण केल्यास किंवा आउटपुट बदलल्यास, तुमचे सध्याचे प्रसारण बंद होईल"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> उघडा"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Wallet ॲप शॉर्टकट म्हणून जोडण्यासाठी ॲप इंस्टॉल केले असल्याची खात्री करा"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Wallet ॲप शॉर्टकट म्हणून जोडण्यासाठी किमान एक कार्ड जोडले असल्याची खात्री करा"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR कोड स्कॅनर शॉर्टकट म्हणून जोडण्यासाठी कॅमेरा ॲप इंस्टॉल केले असल्याची खात्री करा"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home अ‍ॅप शॉर्टकट म्हणून जोडण्यासाठी ॲप इंस्टॉल केले असल्याची खात्री करा"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• किमान एक डिव्हाइस उपलब्ध करणे"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"टिपा घेण्यासंबंधित शॉर्टकट वापरण्याकरिता टिपांसाठीचे डीफॉल्ट अ‍ॅप निवडा"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant चे लक्ष हे आता अ‍ॅक्टिव्ह आहे"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिंग्ज मध्ये डीफॉल्ट टिपा अ‍ॅप सेट करा"</string>
     <string name="install_app" msgid="5066668100199613936">"अ‍ॅप इंस्टॉल करा"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"मायक्रोफोन आणि कॅमेरा"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"अलीकडील अ‍ॅप वापर"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"अलीकडील अ‍ॅक्सेस पहा"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"पूर्ण झाले"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"विस्तार करून पर्याय दाखवा"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"कोलॅप्स करा"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"हे ॲप बंद करा"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> बंद केले आहे"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"सेवा व्यवस्थापित करा"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"ॲक्सेस व्यवस्थापित करा"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"फोन कॉलवर वापरले जात आहे"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"अलीकडे फोन कॉलमध्ये वापरले गेले"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> द्वारे वापरले जात आहे"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"अलीकडे <xliff:g id="APP_NAME">%1$s</xliff:g> ने वापरले"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) द्वारे वापरले जात आहे"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"अलीकडे <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ने वापरले"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) द्वारे वापरले जात आहे"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"अलीकडे <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ने वापरले"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 9014407..6b74c43 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Ketik untuk lihat"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Ralat semasa menyimpan rakaman skrin"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Ralat semasa memulakan rakaman skrin"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Melihat skrin penuh"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Untuk keluar, leret ke bawah dari bahagian atas."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Kembali"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Rumah"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Set Kepala"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Alat Bantu Dengar"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Alat bantu pendengaran"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Menghidupkan…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autoputar"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Autoputar skrin"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> had"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Amaran <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Apl kerja"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Dijeda"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Cahaya Malam"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Dihidupkan pd senja"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Hingga matahari terbit"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Apabila anda membuat perkongsian, rakaman atau penghantaran, Android boleh mengakses apa-apa sahaja yang boleh dilihat pada skrin anda atau dimainkan pada peranti anda. Oleh hal yang demikian, berhati-hati dengan perkara seperti kata laluan, butiran pembayaran, mesej, foto dan audio serta video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Apabila anda berkongsi, merakam atau menghantar apl, Android boleh mengakses apa-apa sahaja yang ditunjukan atau dimainkan pada apl tersebut. Oleh hal yang demikian, berhati-hati dengan perkara seperti kata laluan, butiran pembayaran, mesej, foto dan audio serta video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Mula"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Perkongsian dijeda apabila anda menukar apl"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Sebaliknya kongsi apl ini"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Tukar kembali"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Tukar apl"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Disekat oleh pentadbir IT anda"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Tangkapan skrin dilumpuhkan oleh dasar peranti"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Kosongkan semua"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Besarkan skrin penuh"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Besarkan sebahagian skrin"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Buka tetapan pembesaran"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Tutup tetapan pembesaran"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"Keluar daripada mod edit"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Seret sudut untuk mengubah saiz"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Benarkan penatalan pepenjuru"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ubah saiz"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Masalah membaca meter bateri anda"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Ketik untuk mendapatkan maklumat lanjut"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Tiada penggera"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"masukkan kunci skrin"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Penderia cap jari"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"sahkan"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"akses peranti"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Pembantu sedang mendengar"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# pemberitahuan}other{# pemberitahuan}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Pengambilan nota"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Pengambilan nota"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pengambilan nota, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Menyiarkan"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Hentikan siaran <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jika anda siarkan <xliff:g id="SWITCHAPP">%1$s</xliff:g> atau tukarkan output, siaran semasa anda akan berhenti"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Buka <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Untuk menambahkan apl Wallet sebagai pintasan, pastikan apl telah dipasang"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Untuk menambahkan apl Wallet sebagai pintasan, pastikan sekurang-kurangnya satu kad telah ditambahkan"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Untuk menambahkan pengimbas kod QR sebagai pintasan, pastikan apl kamera telah dipasang"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Untuk menambahkan apl Home sebagai pintasan, pastikan apl telah dipasang"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Sekurang-kurangnya satu peranti tersedia"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Pilih apl nota lalai untuk menggunakan pintasan pengambilan nota"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Perhatian pembantu dihidupkan"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Tetapkan apl nota lalai dalam Tetapan"</string>
     <string name="install_app" msgid="5066668100199613936">"Pasang apl"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon &amp; Kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Penggunaan apl terbaharu"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Lihat akses terbaharu"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Selesai"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Kembangkan dan tunjukkan pilihan"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Kuncupkan"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Tutup apl ini"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> ditutup"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Urus perkhidmatan"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Urus akses"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Digunakan oleh panggilan telefon"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Digunakan baru-baru ini dalam panggilan telefon"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Digunakan baru-baru ini oleh <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Digunakan baru-baru ini oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Digunakan oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Digunakan baru-baru ini oleh <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index b2c173c..2e2567d 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"ကြည့်ရှုရန် တို့ပါ"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ဖန်သားပြင်ရိုက်ကူးမှုကို သိမ်းရာတွင် အမှားရှိသည်"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ဖန်သားပြင် ရိုက်ကူးမှု စတင်ရာတွင် အမှားအယွင်းရှိနေသည်"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"ဖန်သားပြင်အပြည့် ကြည့်နေသည်"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ထွက်ရန် အပေါ်မှ အောက်သို့ ပွတ်ဆွဲပါ။"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"နားလည်ပြီ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"နောက်သို့"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ပင်မစာမျက်နှာ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"မီနူး"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"အသံ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"မိုက်ခွက်ပါနားကြပ်"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"အဝင်"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"နားကြားကိရိယာ"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"နားကြားကိရိယာ"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ဖွင့်နေသည်…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"အော်တို-လည်"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"မျက်နှာပြင်အား အလိုအလျောက်လှည့်ခြင်း"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> ကန့်သတ်ချက်"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"ခဏရပ်ထားသည်"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"မျှဝေ၊ ရုပ်သံဖမ်း (သို့) ကာစ်လုပ်သည့်အခါ Android သည် သင့်ဖန်သားပြင်တွင် မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့ အရာများကို ဂရုစိုက်ပါ။"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"အက်ပ်တစ်ခုဖြင့် မျှဝေ၊ ရုပ်သံဖမ်း (သို့) ကာစ်လုပ်သည့်အခါ Android သည် ယင်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့အရာများကို ဂရုစိုက်ပါ။"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"စတင်ရန်"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"အက်ပ်ပြောင်းချိန်တွင် မျှဝေခြင်းကို ခဏရပ်သည်"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"၎င်းအစား ဤအက်ပ်ကို မျှဝေရန်"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"ပြန်ပြောင်းရန်"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"အက်ပ်ပြောင်းစနစ်"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"သင်၏ IT စီမံခန့်ခွဲသူက ပိတ်ထားသည်"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ကိရိယာ မူဝါဒက ဖန်သားပြင်ပုံဖမ်းခြင်းကို ပိတ်ထားသည်"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံးရှင်းရန်"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ဖန်သားပြင်အပြည့် ချဲ့သည်"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ဖန်သားပြင် တစ်စိတ်တစ်ပိုင်းကို ချဲ့ပါ"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"ချဲ့ခြင်း ဆက်တင်များ ဖွင့်ရန်"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"ချဲ့ခြင်း ဆက်တင်များ ပိတ်ရန်"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"ပြင်ဆင်မုဒ်မှ ထွက်ရန်"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"အရွယ်အစားပြန်ပြုပြင်ရန် ထောင့်စွန်းကို ဖိဆွဲပါ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ထောင့်ဖြတ် လှိမ့်ခွင့်ပြုရန်"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"အရွယ်အစားပြန်ပြုပြင်ရန်"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"သင်၏ ဘက်ထရီမီတာကို ဖတ်ရာတွင် ပြဿနာရှိနေသည်"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"နောက်ထပ်အချက်အလက်များအတွက် တို့ပါ"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"နှိုးစက်ပေးမထားပါ"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ဖန်သားပြင်လော့ခ် ထည့်ရန်"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"လက်ဗွေ အာရုံခံကိရိယာ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"အထောက်အထားစိစစ်ရန်"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"စက်ပစ္စည်းသို့ ဝင်ရန်"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant နားထောင်နေသည်"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{အကြောင်းကြားချက် # ခု}other{အကြောင်းကြားချက် # ခု}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>၊ <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"မှတ်စုလိုက်ခြင်း"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"မှတ်စုလိုက်ခြင်း"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"မှတ်စုလိုက်ခြင်း၊ <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ထုတ်လွှင့်ခြင်း"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ထုတ်လွှင့်ခြင်းကို ရပ်မလား။"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> ကို ထုတ်လွှင့်သောအခါ (သို့) အထွက်ကို ပြောင်းသောအခါ သင့်လက်ရှိထုတ်လွှင့်ခြင်း ရပ်သွားမည်"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ဖွင့်ရန်"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Wallet အက်ပ်ကို ဖြတ်လမ်းလင့်ခ်အဖြစ်ထည့်ရန် ၎င်းအားထည့်သွင်းထားကြောင်း သေချာပါစေ"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Wallet အက်ပ်ကို ဖြတ်လမ်းလင့်ခ်အဖြစ်ထည့်ရန် အနည်းဆုံး ကတ်တစ်ခုထည့်ထားကြောင်း သေချာပါစေ"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR ကုဒ် စကင်ဖတ်စနစ်ကို ဖြတ်လမ်းလင့်ခ်အဖြစ်ထည့်ရန် ကင်မရာအက်ပ်အားထည့်သွင်းထားကြောင်း သေချာပါစေ"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home အက်ပ်ကို ဖြတ်လမ်းလင့်ခ်အဖြစ်ထည့်ရန် ၎င်းအားထည့်သွင်းထားကြောင်း သေချာပါစေ"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• အနည်းဆုံး စက်တစ်ခုသုံးနိုင်ရမည်"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"မှတ်စုရေးသည့် ဖြတ်လမ်းလင့်ခ်သုံးရန်အတွက် မူရင်းမှတ်စုများအက်ပ် ရွေးရန်"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant နားထောင်နေသည်"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ဆက်တင်များတွင် မူရင်းမှတ်စုများအက်ပ် သတ်မှတ်ပါ"</string>
     <string name="install_app" msgid="5066668100199613936">"အက်ပ် ထည့်သွင်းရန်"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"မိုက်ခရိုဖုန်းနှင့် ကင်မရာ"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"လတ်တလော အက်ပ်အသုံးပြုမှု"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"လတ်တလောအသုံးပြုမှုကို ကြည့်ရန်"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"ပြီးပြီ"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"ရွေးစရာများကို ချဲ့ပြပါ"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"လျှော့ပြပါ"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"ဤအက်ပ်ကို ပိတ်ရန်"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> ကို ပိတ်ထားသည်"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"ဝန်ဆောင်မှုကို စီမံရန်"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"သုံးခွင့် စီမံရန်"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"ဖုန်းခေါ်ဆိုမှုက သုံးနေသည်"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"ဖုန်းခေါ်ဆိုမှုတွင် လတ်တလောသုံးထားသည်"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> က သုံးနေသည်"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"<xliff:g id="APP_NAME">%1$s</xliff:g> က လတ်တလောသုံးထားသည်"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) က သုံးနေသည်"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) က လတ်တလောသုံးထားသည်"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) က သုံးနေသည်"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) က လတ်တလောသုံးထားသည်"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index f6ad52e..8ef7794 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Trykk for å se"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Feil ved lagring av skjermopptaket"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Feil ved start av skjermopptaket"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visning i fullskjerm"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Sveip ned fra toppen for å avslutte."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Greit"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Tilbake"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Startside"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Meny"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Lyd"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Hodetelefoner"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Innenhet"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Høreapparater"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Høreapparater"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Slår på …"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotér automatisk"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotér skjermen automatisk"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Grense på <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Advarsel for <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Jobbapper"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Satt på pause"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Nattlys"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"På ved solnedgang"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Til soloppgang"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Når du deler, tar opp eller caster noe, har Android tilgang til alt som vises på skjermen eller spilles av på enheten. Derfor bør du være forsiktig med for eksempel passord, betalingsopplysninger, meldinger, bilder, lyd og video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Når du deler, tar opp eller caster en app, har Android tilgang til alt som vises eller spilles av i den aktuelle appen. Derfor bør du være forsiktig med for eksempel passord, betalingsopplysninger, meldinger, bilder, lyd og video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Begynn"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Delingen settes på pause når du bytter app"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Del denne appen i stedet"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Bytt tilbake"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Appbytte"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokkert av IT-administratoren"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skjermdumper er deaktivert av enhetsinnstillingene"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Fjern alt"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Forstørr hele skjermen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Forstørr en del av skjermen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Åpne innstillinger for forstørring"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Lukk forstørringsinnstillingene"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Dra hjørnet for å endre størrelse"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Tillat diagonal rulling"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Endre størrelse"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Kunne ikke lese batterimåleren"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Trykk for å få mer informasjon"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ingen alarm angitt"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"legg inn skjermlåsen"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingeravtrykkssensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentiser"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"åpne enheten"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistenten lytter"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# varsel}other{# varsler}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notatskriving"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Notatskriving"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Notatskriving, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Kringkaster"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vil du stoppe kringkastingen av <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Hvis du kringkaster <xliff:g id="SWITCHAPP">%1$s</xliff:g> eller endrer utgangen, stopper den nåværende kringkastingen din"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Åpne <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"For å legge til Wallet-appen som snarvei, sørg for at appen er installert"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"For å legge til Wallet-appen som snarvei, sørg for at minst ett kort er lagt til"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"For å legge til QR-kodeskanneren som snarvei, sørg for at du har en kameraapp installert"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"For å legge til Home-appen som snarvei, sørg for at appen er installert"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• minst én enhet er tilgjengelig"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Velg en standard notatapp du vil bruke med notatsnarveien"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistentoppmerksomhet er på"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Du kan velge en standardapp for notater i Innstillinger"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer appen"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon og kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nylig appbruk"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Se nylig tilgang"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Ferdig"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Vis alternativer"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Skjul"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Lukk denne appen"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> er lukket"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Administrer tjenesten"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Administrer tilgang"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"I bruk av telefonanrop"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Nylig brukt i telefonanrop"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"I bruk av <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Nylig brukt av <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"I bruk av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nylig brukt av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"I bruk av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nylig brukt av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 9c6e57e..c56ebb6 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"हेर्नका लागि ट्याप गर्नुहोस्"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"स्क्रिन रेकर्डिङ सेभ गर्ने क्रममा त्रुटि भयो"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रिन रेकर्ड गर्न थाल्ने क्रममा त्रुटि भयो"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"फुल स्क्रिन हेरिँदै छ"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"बाहिरिन सिरानबाट तलतिर स्वाइप गर्नुहोस्।"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"बुझेँ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"पछाडि"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"गृह"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"मेनु"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"अडियो"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"हेडसेट"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"इनपुट"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"श्रवण यन्त्रहरू"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"श्रवण यन्त्रहरू"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"सक्रिय गर्दै…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"अटो रोटेट"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"स्क्रिन स्वतःघुम्ने"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> सीमा"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"पज गरिएको छ"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Night Light"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा Android ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिने सबै कुरा हेर्न तथा प्रयोग गर्न सक्छ। त्यसैले पासवर्ड, भुक्तानीसम्बन्धी विवरण, म्यासेज, फोटो र अडियो तथा भिडियो जस्ता कुरा हेर्दा वा प्ले गर्दा सावधानी अपनाउनुहोला।"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"तपाईंले कुनै एप सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा Android ले उक्त एपमा देखाइने वा प्ले गरिने सबै कुरा हेर्न तथा प्रयोग गर्न सक्छ। त्यसैले पासवर्ड, भुक्तानीसम्बन्धी विवरण, म्यासेज, फोटो र अडियो तथा भिडियो जस्ता कुरा हेर्दा वा प्ले गर्दा सावधानी अपनाउनुहोला।"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"सुरु गर्नुहोस्"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"तपाईंले एपहरू बदल्दा सेयर गर्ने प्रक्रिया पज हुन्छ"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"यसको साटो यो एप सेयर गर्नुहोस्"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"पछाडि जानुहोस्"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"एप बदल्नुहोस्"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"तपाईंका IT एड्मिनले ब्लक गर्नुभएको छ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिभाइसको नीतिका कारण स्क्रिन क्याप्चर गर्ने सुविधा अफ गरिएको छ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सबै हटाउनुहोस्"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"पूरै स्क्रिन जुम इन गर्नुहोस्"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"स्क्रिनको केही भाग म्याग्निफाइ गर्नुहोस्"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"जुम इनसम्बन्धी सेटिङ खोल्नुहोस्"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"जुम इन गर्ने सुविधाको सेटिङ बन्द गर्नुहोस्"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"आकार बदल्न कुनाबाट ड्र्याग गर्नुहोस्"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"डायगोनल तरिकाले स्क्रोल गर्ने अनुमति दिनुहोस्"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"आकार बदल्नुहोस्"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"डिभाइसको ब्याट्रीको मिटर रिडिङ क्रममा समस्या भयो"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"थप जानकारी प्राप्त गर्न ट्याप गर्नुहोस्"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"अलार्म राखिएको छैन"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"स्क्रिन लक हाल्नुहोस्"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"फिंगरप्रिन्ट सेन्सर"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"प्रमाणित गर्नुहोस्"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"डिभाइस हाल्नुहोस्"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"सहायकले सुनिरहेको छ"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# वटा सूचना}other{# वटा सूचनाहरू}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"टिपोट गर्ने कार्य"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"नोट लेख्ने कार्य"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"नोट लेख्ने कार्य, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"प्रसारण गरिँदै छ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ब्रोडकास्ट गर्न छाड्ने हो?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"तपाईंले <xliff:g id="SWITCHAPP">%1$s</xliff:g> ब्रोडकास्ट गर्नुभयो वा आउटपुट परिवर्तन गर्नुभयो भने तपाईंको हालको ब्रोडकास्ट रोकिने छ"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> खोल्नुहोस्"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"सर्टकटका रूपमा Wallet एप हाल्न उक्त एप इन्स्टल गरिएको छ भन्ने कुरा सुनिश्चित गर्नुहोस्"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"सर्टकटका रूपमा Wallet एप हाल्न कम्तीमा एउटा कार्ड हालिएको छ भन्ने कुरा सुनिश्चित गर्नुहोस्"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"सर्टकटका रूपमा QR कोड स्क्यानर हाल्न क्यामेरा एप इन्स्टल गरिएको छ भन्ने कुरा सुनिश्चित गर्नुहोस्"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home एपलाई सर्टकटका रूपमा हाल्न उक्त एप इन्स्टल गरिएको छ भन्ने कुरा सुनिश्चित गर्नुहोस्"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• कम्तीमा एउटा डिभाइस उपलब्ध छ"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"नोट बनाउनेसम्बन्धी सर्टकट प्रयोग गर्न नोट बनाउने डिफल्ट एप चयन गर्नुहोस्"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"सहायकले सुनिरहेको छ"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिङमा गई नोट बनाउने डिफल्ट एप तोक्नुहोस्"</string>
     <string name="install_app" msgid="5066668100199613936">"एप इन्स्टल गर्नुहोस्"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"माइक्रोफोन तथा क्यामेरा"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"एपको हालसालैको प्रयोग"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"हालसालै एक्सेस गर्ने एप हेर्नुहोस्"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"पूरा भयो"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"एक्स्पान्ड गरियोस् र विकल्पहरू देखाइयोस्"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"कोल्याप्स गर्नुहोस्"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"यो एप बन्द गर्नुहोस्"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> बन्द गरिएको छ"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"सेवा व्यवस्थित गर्नुहोस्"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"अनुमतिहरू व्यवस्थित गर्नुहोस्"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"फोन कलमा प्रयोग भइरहेको छ"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"फोन कलमा हालसालै प्रयोग गरिएको"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> ले प्रयोग गरिरहेको छ"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"<xliff:g id="APP_NAME">%1$s</xliff:g> ले हालसालै प्रयोग गरेको"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ले प्रयोग गरिरहेको छ"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ले हालसालै प्रयोग गरेको"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ले प्रयोग गरिरहेको छ"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ले हालसालै प्रयोग गरेको"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 2f96597..088bcbd 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tik om te bekijken"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Fout bij opslaan van schermopname"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Fout bij starten van schermopname"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Volledig scherm wordt getoond"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Swipe omlaag vanaf de bovenkant van het scherm om af te sluiten."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Terug"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Startscherm"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Invoer"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hoortoestellen"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hoortoestellen"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aanzetten…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatisch draaien"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Scherm automatisch draaien"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Limiet van <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Waarschuwing voor <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Werk-apps"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Onderbroken"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Nachtverlichting"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Aan bij zonsondergang"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Tot zonsopkomst"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Als je deelt, opneemt of cast, heeft Android toegang tot alles dat zichtbaar is op je scherm of wordt afgespeeld op je apparaat. Wees daarom voorzichtig met bijvoorbeeld wachtwoorden, betalingsgegevens, berichten, foto\'s, en audio en video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Als je deelt, opneemt of cast, heeft Android toegang tot alles dat wordt getoond of afgespeeld in die app. Wees daarom voorzichtig met bijvoorbeeld wachtwoorden, betalingsgegevens, berichten, foto\'s, en audio en video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Starten"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Delen wordt onderbroken als je schakelt tussen apps"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"In plaats daarvan deze app delen"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Terugschakelen"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"App schakelen"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Geblokkeerd door je IT-beheerder"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Schermopname staat uit vanwege apparaatbeleid"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Alles wissen"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Volledig scherm vergroten"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Deel van het scherm vergroten"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Instellingen voor vergroting openen"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Instellingen voor vergroting sluiten"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"Bewerkingsmodus sluiten"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Sleep een hoek om het formaat te wijzigen"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Diagonaal scrollen toestaan"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Formaat aanpassen"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Probleem bij het lezen van je batterijmeter"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tik hier voor meer informatie"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Geen wekker gezet"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"schermvergrendeling invoeren"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Vingerafdruksensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"verifiëren"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"apparaat opgeven"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"De Assistent luistert"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# melding}other{# meldingen}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Aantekeningen maken"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Aantekeningen maken"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Aantekeningen maken, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Uitzending"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Uitzending van <xliff:g id="APP_NAME">%1$s</xliff:g> stopzetten?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Als je <xliff:g id="SWITCHAPP">%1$s</xliff:g> uitzendt of de uitvoer wijzigt, wordt je huidige uitzending gestopt"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> openen"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Als je de Wallet-app wilt toevoegen als sneltoets, zorg je dat de app is geïnstalleerd"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Als je de Wallet-app wilt toevoegen als sneltoets, zorg je dat er minstens één kaart is toegevoegd"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Als je de QR-codescanner wilt toevoegen als sneltoets, zorg je dat er een camera-app is geïnstalleerd"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Als je de Home-app wilt toevoegen als sneltoets, zorg je dat de app is geïnstalleerd"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Er is minstens één apparaat beschikbaar"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Selecteer een standaard notitie-app om de sneltoets voor notities maken te gebruiken"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistent-aandacht aan"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standaard notitie-app instellen in Instellingen"</string>
     <string name="install_app" msgid="5066668100199613936">"App installeren"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfoon en camera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Recent app-gebruik"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Recente toegang bekijken"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Klaar"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Uitvouwen en opties tonen"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Samenvouwen"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Deze app sluiten"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> gesloten"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Service beheren"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Toegang beheren"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Gebruikt door telefoongesprek"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Recent gebruikt in telefoongesprek"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Gebruikt door <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Recent gebruikt door <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Gebruikt door <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Recent gebruikt door <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Gebruikt door <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Recent gebruikt door <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 1a3cc1c..64955e4 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"ଦେଖିବାକୁ ଟାପ୍ କରନ୍ତୁ"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ସ୍କ୍ରିନ ରେକର୍ଡିଂ ସେଭ କରିବାରେ ତ୍ରୁଟି"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ସ୍କ୍ରିନ୍ ରେକର୍ଡିଂ ଆରମ୍ଭ କରିବାରେ ତ୍ରୁଟି"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନରେ ଦେଖିବା"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ବାହାରି ଯିବା ପାଇଁ, ଶୀର୍ଷରୁ ତଳକୁ ସ୍ୱାଇପ କରନ୍ତୁ।"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"ବୁଝିଗଲି"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ଫେରନ୍ତୁ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ହୋମ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"ମେନୁ"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ଅଡିଓ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ହେଡସେଟ୍‍"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ଇନପୁଟ୍"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ଶ୍ରବଣ ଯନ୍ତ୍ର"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ଅନ୍ ହେଉଛି…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ଅଟୋ-ରୋଟେଟ୍"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ଅଟୋ-ରୋଟେଟ ସ୍କ୍ରିନ"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> ସୀମା"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"ବିରତ କରାଯାଇଛି"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ, ଫଟୋ ଏବଂ ଅଡିଓ ଓ ଭିଡିଓ ପରି ବିଷୟଗୁଡ଼ିକ ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ଆପଣ ଏକ ଆପ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, ସେହି ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ, ଫଟୋ ଏବଂ ଅଡିଓ ଓ ଭିଡିଓ ପରି ବିଷୟଗୁଡ଼ିକ ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ଆରମ୍ଭ କରନ୍ତୁ"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"ଆପଣ ଆପ୍ସ ସୁଇଚ କଲେ ସେୟାରିଂ ବିରତ ହୁଏ"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"ଏହା ପରିବର୍ତ୍ତେ ଏହି ଆପ ସେୟାର କରନ୍ତୁ"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"ପୁଣି ସୁଇଚ କରନ୍ତୁ"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"ଆପ ସୁଇଚ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ଆପଣଙ୍କ IT ଆଡମିନଙ୍କ ଦ୍ୱାରା ବ୍ଲକ କରାଯାଇଛି"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ଡିଭାଇସ ନୀତି ଦ୍ୱାରା ସ୍କ୍ରିନ କେପଚରିଂକୁ ଅକ୍ଷମ କରାଯାଇଛି"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ସବୁ ଖାଲି କରନ୍ତୁ"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନକୁ ମ୍ୟାଗ୍ନିଫାଏ କରନ୍ତୁ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ସ୍କ୍ରିନର ଅଂଶ ମାଗ୍ନିଫାଏ କରନ୍ତୁ"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"ମାଗ୍ନିଫିକେସନ ସେଟିଂସ ଖୋଲନ୍ତୁ"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"ମେଗ୍ନିଫିକେସନ ସେଟିଂସକୁ ବନ୍ଦ କରନ୍ତୁ"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ରିସାଇଜ କରିବା ପାଇଁ କୋଣକୁ ଡ୍ରାଗ କରନ୍ତୁ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ଡାଏଗୋନାଲ ସ୍କ୍ରୋଲିଂକୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ରିସାଇଜ କରନ୍ତୁ"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"ଆପଣଙ୍କ ବ୍ୟାଟେରୀ ମିଟର୍ ପଢ଼ିବାରେ ସମସ୍ୟା ହେଉଛି"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ଅଧିକ ସୂଚନା ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ଆଲାରାମ ସେଟ ହୋଇନାହିଁ"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ସ୍କ୍ରିନ ଲକ ଲେଖନ୍ତୁ"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ଟିପଚିହ୍ନ ସେନ୍ସର୍"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ପ୍ରମାଣୀକରଣ କରନ୍ତୁ"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ଡିଭାଇସ୍ ବିଷୟରେ ସୂଚନା ଲେଖନ୍ତୁ"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant ଶୁଣୁଛି"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{#ଟି ବିଜ୍ଞପ୍ତି}other{#ଟି ବିଜ୍ଞପ୍ତି}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"ନୋଟଟେକିଂ"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"ନୋଟ-ଟେକିଂ"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ନୋଟ-ଟେକିଂ, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ବ୍ରଡକାଷ୍ଟ କରୁଛି"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବ୍ରଡକାଷ୍ଟ କରିବା ବନ୍ଦ କରିବେ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ଯଦି ଆପଣ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ବ୍ରଡକାଷ୍ଟ କରନ୍ତି କିମ୍ବା ଆଉଟପୁଟ ବଦଳାନ୍ତି, ତେବେ ଆପଣଙ୍କ ବର୍ତ୍ତମାନର ବ୍ରଡକାଷ୍ଟ ବନ୍ଦ ହୋଇଯିବ"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ଖୋଲନ୍ତୁ"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"ଏକ ସର୍ଟକଟ ଭାବେ Wallet ଆପ ଯୋଗ କରିବା ପାଇଁ ଏହି ଆପ ଇନଷ୍ଟଲ କରାଯାଇଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"ଏକ ସର୍ଟକଟ ଭାବେ Wallet ଆପ ଯୋଗ କରିବାକୁ ଅତିକମରେ ଗୋଟିଏ ଆପ ଯୋଗ କରାଯାଇଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"ଏକ ସର୍ଟକଟ ଭାବେ QR କୋଡ ସ୍କାନର ଯୋଗ କରିବାକୁ ଏକ କେମେରା ଆପ ଇନଷ୍ଟଲ କରାଯାଇଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"ଏକ ସର୍ଟକଟ ଭାବେ Home ଆପ ଯୋଗ କରିବା ପାଇଁ ଏହି ଆପ ଇନଷ୍ଟଲ କରାଯାଇଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• ଅତିକମରେ ଗୋଟିଏ ଡିଭାଇସ ଉପଲବ୍ଧ ଅଛି"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"ନୋଟଟେକିଂ ସର୍ଟକଟ ବ୍ୟବହାର କରିବାକୁ ଏକ ଡିଫଲ୍ଟ ନୋଟ୍ସ ଆପ୍ସ ଚୟନ କରନ୍ତୁ"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant ଆଟେନସନ ଚାଲୁ ଅଛି"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ସେଟିଂସରେ ଡିଫଲ୍ଟ ନୋଟ୍ସ ଆପ ସେଟ କରନ୍ତୁ"</string>
     <string name="install_app" msgid="5066668100199613936">"ଆପ ଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"ମାଇକ୍ରୋଫୋନ ଏବଂ କେମେରା"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"ବର୍ତ୍ତମାନର ଆପ ବ୍ୟବହାର"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ବର୍ତ୍ତମାନର ଆକ୍ସେସ ଦେଖନ୍ତୁ"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"ହୋଇଗଲା"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"ବିସ୍ତାର କରି ବିକଳ୍ପଗୁଡ଼ିକ ଦେଖାନ୍ତୁ"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"ଏହି ଆପକୁ ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g>ଟି ବନ୍ଦ ହୋଇଯାଇଛି"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"ସେବା ପରିଚାଳନା କରନ୍ତୁ"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"ଆକ୍ସେସକୁ ପରିଚାଳନା କରନ୍ତୁ"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"ଫୋନ କଲରେ ବ୍ୟବହାର କରାଯାଉଛି"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"ଏବେ ଫୋନ କଲରେ ବ୍ୟବହାର କରାଯାଇଛି"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> ଦ୍ୱାରା ବ୍ୟବହାର କରାଯାଉଛି"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"ଏବେ <xliff:g id="APP_NAME">%1$s</xliff:g> ଦ୍ୱାରା ବ୍ୟବହାର କରାଯାଉଛି"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> ଦ୍ୱାରା ବ୍ୟବହାର କରାଯାଉଛି (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"ଏବେ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ଦ୍ୱାରା ବ୍ୟବହାର କରାଯାଉଛି"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> ଦ୍ୱାରା ବ୍ୟବହାର କରାଯାଉଛି (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"ଏବେ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ଦ୍ୱାରା ବ୍ୟବହାର କରାଯାଉଛି"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index c5d646c..b3d4482 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"ਦੇਖਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਨੂੰ ਰੱਖਿਅਤ ਕਰਨ ਵੇਲੇ ਗੜਬੜ ਹੋ ਗਈ"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਵੇਲੇ ਗੜਬੜ ਹੋਈ"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"ਪੂਰੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦੇਖਿਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ਬਾਹਰ ਜਾਣ ਲਈ, ਉਪਰੋਂ ਹੇਠਾਂ ਵੱਲ ਸਵਾਈਪ ਕਰੋ।"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"ਸਮਝ ਲਿਆ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ਪਿੱਛੇ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ਘਰ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"ਮੀਨੂ"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ਆਡੀਓ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ਹੈੱਡਸੈੱਟ"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ਇਨਪੁੱਟ"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"ਸੁਣਨ ਦੇ ਸਾਧਨ"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ਸੁਣਨ ਦੇ ਸਾਧਨ"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ਚਾਲੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ਸਵੈ-ਘੁਮਾਓ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ਸਕ੍ਰੀਨ ਨੂੰ ਆਪਣੇ ਆਪ ਘੁੰਮਾਓ"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> ਸੀਮਾ"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"ਰੋਕਿਆ ਗਿਆ"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, Android ਕੋਲ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਸਦੀ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਵਾਸਤੇ ਸਾਵਧਾਨ ਰਹੋ।"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, Android ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ਸ਼ੁਰੂ ਕਰੋ"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"ਐਪਾਂ ਸਵਿੱਚ ਕਰਨ \'ਤੇ ਸਾਂਝਾਕਰਨ ਰੁਕ ਜਾਂਦਾ ਹੈ"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"ਇਸਦੀ ਬਜਾਏ ਇਹ ਐਪ ਸਾਂਝੀ ਕਰੋ"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"ਵਾਪਸ ਸਵਿੱਚ ਕਰੋ"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"ਐਪ ਸਵਿੱਚ ਕਰੋ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ਤੁਹਾਡੇ ਆਈ.ਟੀ. ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਬਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ਡੀਵਾਈਸ ਨੀਤੀ ਦੇ ਕਾਰਨ ਸਕ੍ਰੀਨ ਕੈਪਚਰ ਕਰਨਾ ਬੰਦ ਹੈ"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ਪੂਰੀ ਸਕ੍ਰੀਨ ਨੂੰ ਵੱਡਦਰਸ਼ੀ ਕਰੋ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ਸਕ੍ਰੀਨ ਦੇ ਹਿੱਸੇ ਨੂੰ ਵੱਡਾ ਕਰੋ"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"ਵੱਡਦਰਸ਼ੀਕਰਨ ਸੈਟਿੰਗਾਂ ਖੋਲ੍ਹੋ"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"ਵੱਡਦਰਸ਼ੀਕਰਨ ਸੈਟਿੰਗਾਂ ਬੰਦ ਕਰੋ"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ਆਕਾਰ ਬਦਲਣ ਲਈ ਕੋਨਾ ਘਸੀਟੋ"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"ਟੇਡੀ ਦਿਸ਼ਾ ਵਿੱਚ ਸਕ੍ਰੋਲ ਕਰਨ ਦਿਓ"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ਆਕਾਰ ਬਦਲੋ"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"ਤੁਹਾਡੇ ਬੈਟਰੀ ਮੀਟਰ ਨੂੰ ਪੜ੍ਹਨ ਵਿੱਚ ਸਮੱਸਿਆ ਹੋ ਰਹੀ ਹੈ"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ਕੋਈ ਅਲਾਰਮ ਸੈੱਟ ਨਹੀਂ"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ਸਕ੍ਰੀਨ ਲਾਕ ਦਾਖਲ ਕਰੋ"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਸੈਂਸਰ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ਪ੍ਰਮਾਣਿਤ ਕਰੋ"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ਡੀਵਾਈਸ ਵਿੱਚ ਦਾਖਲ ਹੋਵੋ"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant ਸੁਣ ਰਹੀ ਹੈ"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ਸੂਚਨਾ}one{# ਸੂਚਨਾ}other{# ਸੂਚਨਾਵਾਂ}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"ਨੋਟ ਬਣਾਉਣਾ"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"ਨੋਟ ਬਣਾਉਣਾ"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"ਨੋਟ ਬਣਾਉਣਾ, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ਪ੍ਰਸਾਰਨ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਦੇ ਪ੍ਰਸਾਰਨ ਨੂੰ ਰੋਕਣਾ ਹੈ?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ਜੇ ਤੁਸੀਂ <xliff:g id="SWITCHAPP">%1$s</xliff:g> ਦਾ ਪ੍ਰਸਾਰਨ ਕਰਦੇ ਹੋ ਜਾਂ ਆਊਟਪੁੱਟ ਬਦਲਦੇ ਹੋ, ਤਾਂ ਤੁਹਾਡਾ ਮੌਜੂਦਾ ਪ੍ਰਸਾਰਨ ਰੁਕ ਜਾਵੇਗਾ"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ਖੋਲ੍ਹੋ"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Wallet ਐਪ ਨੂੰ ਸ਼ਾਰਟਕੱਟ ਵਜੋਂ ਸ਼ਾਮਲ ਕਰਨ ਲਈ, ਪੱਕਾ ਕਰੋ ਕਿ ਐਪ ਸਥਾਪਤ ਹੈ"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Wallet ਐਪ ਨੂੰ ਸ਼ਾਰਟਕੱਟ ਵਜੋਂ ਸ਼ਾਮਲ ਕਰਨ ਲਈ, ਪੱਕਾ ਕਰੋ ਕਿ ਘੱਟੋ-ਘੱਟ ਇੱਕ ਕਾਰਡ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ ਹੈ"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR ਕੋਡ ਸਕੈਨਰ ਨੂੰ ਸ਼ਾਰਟਕੱਟ ਵਜੋਂ ਸ਼ਾਮਲ ਕਰਨ ਲਈ, ਪੱਕਾ ਕਰੋ ਕਿ ਕੈਮਰਾ ਐਪ ਸਥਾਪਤ ਹੈ"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home ਐਪ ਨੂੰ ਸ਼ਾਰਟਕੱਟ ਵਜੋਂ ਸ਼ਾਮਲ ਕਰਨ ਲਈ, ਪੱਕਾ ਕਰੋ ਕਿ ਐਪ ਸਥਾਪਤ ਹੈ"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• ਘੱਟੋ-ਘੱਟ ਇੱਕ ਡੀਵਾਈਸ ਉਪਲਬਧ ਹੈ"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"ਨੋਟ ਬਣਾਉਣ ਵਾਲੇ ਸ਼ਾਰਟਕੱਟ ਦੀ ਵਰਤੋਂ ਕਰਨ ਲਈ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਨੋਟ ਐਪ ਚੁਣੋ"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant ਧਿਆਨ ਸੁਵਿਧਾ ਨੂੰ ਚਾਲੂ ਹੈ"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਨੋਟ ਐਪ ਨੂੰ ਸੈੱਟ ਕਰੋ"</string>
     <string name="install_app" msgid="5066668100199613936">"ਐਪ ਸਥਾਪਤ ਕਰੋ"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਅਤੇ ਕੈਮਰਾ"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"ਹਾਲ ਹੀ ਵਿੱਚ ਵਰਤੀ ਗਈ ਐਪ"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ਹਾਲੀਆ ਪਹੁੰਚ ਦੇਖੋ"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"ਹੋ ਗਿਆ"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"ਵਿਸਤਾਰ ਕਰੋ ਅਤੇ ਵਿਕਲਪ ਦਿਖਾਓ"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"ਸਮੇਟੋ"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"ਇਸ ਐਪ ਨੂੰ ਬੰਦ ਕਰੋ"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਬੰਦ ਹੋ ਗਈ"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"ਸੇਵਾ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"ਪਹੁੰਚ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"ਫ਼ੋਨ ਕਾਲ ਵੱਲੋਂ ਵਰਤੋਂ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"ਹਾਲ ਹੀ ਵਿੱਚ ਫ਼ੋਨ ਕਾਲ ਵਿੱਚ ਵਰਤਿਆ ਗਿਆ"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਵੱਲੋਂ ਵਰਤੋਂ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"ਹਾਲ ਹੀ ਵਿੱਚ <xliff:g id="APP_NAME">%1$s</xliff:g> ਵੱਲੋਂ ਵਰਤਿਆ ਗਿਆ"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ਵੱਲੋਂ ਵਰਤੋਂ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"ਹਾਲ ਹੀ ਵਿੱਚ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ਵੱਲੋਂ ਵਰਤਿਆ ਗਿਆ"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ਵੱਲੋਂ ਵਰਤੋਂ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"ਹਾਲ ਹੀ ਵਿੱਚ <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ਵੱਲੋਂ ਵਰਤਿਆ ਗਿਆ"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 4fb95cd..e3beef0 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Kliknij, aby wyświetlić"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Podczas zapisywania nagrania ekranu wystąpił błąd"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Błąd podczas rozpoczynania rejestracji zawartości ekranu"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Włączony pełny ekran"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Aby wyjść, przesuń palcem z góry na dół."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Wróć"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Ekran główny"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Dźwięk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Zestaw słuchawkowy"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Wejście"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Aparaty słuchowe"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparaty słuchowe"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Włączam…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Autoobracanie"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Autoobracanie ekranu"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Limit <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Ostrzeżenie: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Aplikacje służbowe"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Wstrzymano"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Podświetlenie nocne"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Włącz o zachodzie"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Do wschodu słońca"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Podczas udostępniania, nagrywania lub przesyłania treści Android ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Podczas udostępniania, nagrywania lub przesyłania treści Android ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Rozpocznij"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Udostępnianie zostanie wstrzymane, gdy przełączysz aplikacje"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Udostępnij tę aplikację"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Wróć"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Przełączanie aplikacji"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Zablokowane przez administratora IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Zrzuty ekranu są wyłączone zgodnie z zasadami dotyczącymi urządzeń"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Usuń wszystkie"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Powiększanie pełnego ekranu"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Powiększ część ekranu"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Otwórz ustawienia powiększenia"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Zamknij ustawienia powiększenia"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Przeciągnij róg, aby zmienić rozmiar"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Zezwalaj na przewijanie poprzeczne"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Zmień rozmiar"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem z odczytaniem pomiaru wykorzystania baterii"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Kliknij, aby uzyskać więcej informacji"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nie ustawiono alarmu"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"Wprowadź blokadę ekranu"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Czytnik linii papilarnych"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"uwierzytelnij"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"otwórz urządzenie"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asystent słucha"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# powiadomienie}few{# powiadomienia}many{# powiadomień}other{# powiadomienia}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notatki"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Notatki"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Notatki, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transmisja"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Zatrzymaj transmisję aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Jeśli transmitujesz aplikację <xliff:g id="SWITCHAPP">%1$s</xliff:g> lub zmieniasz dane wyjściowe, Twoja obecna transmisja zostanie zakończona"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Otwórz: <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Aby dodać aplikację Portfel jako skrót, upewnij się, że jest zainstalowana"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Aby dodać aplikację Portfel jako skrót, upewnij się, że została dodana co najmniej 1 karta"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Aby dodać Skaner kodów QR jako skrót, upewnij się, że jest zainstalowana aplikacja aparatu"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Aby dodać aplikację Home jako skrót, upewnij się, że jest zainstalowana"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Dostępne jest co najmniej 1 urządzenie."</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Wybierz domyślną aplikację do obsługi notatek, której skrótu będziesz używać do funkcji notowania"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Asystent jest aktywny"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ustaw domyślną aplikację do obsługi notatek w Ustawieniach"</string>
     <string name="install_app" msgid="5066668100199613936">"Zainstaluj aplikację"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon i Aparat"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Aplikacje korzystające w ostatnim czasie"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Zobacz ostatni dostęp"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Gotowe"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Rozwiń i pokaż opcje"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Zwiń"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Zamknij tę aplikację"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Zamknięto aplikację <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Zarządzaj usługą"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Zarządzaj dostępem"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Używany podczas rozmowy telefonicznej"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Ostatnio używany podczas rozmowy telefonicznej"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Używany przez aplikację <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Ostatnio używany przez aplikację <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Używany przez aplikację <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Ostatnio używany przez aplikację <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Używany przez aplikację <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Ostatnio używany przez aplikację <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 7fc97b4..8f5d7fa 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Toque para ver"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Erro ao salvar a gravação da tela"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Erro ao iniciar a gravação de tela"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visualização em tela cheia"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Para sair, deslize de cima para baixo."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendi"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Voltar"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Página inicial"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Fone de ouvido"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Aparelhos auditivos"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparelhos auditivos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ativando…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Giro automático da tela"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Limite: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Aviso de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Apps de trabalho"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Pausado"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Modo noturno"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Ativ. ao pôr do sol"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Até o nascer do sol"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando você compartilha, grava ou transmite a tela, o Android tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando você compartilha, grava ou transmite um app, o Android tem acesso a todas as informações visíveis ou reproduzidas nele. Tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Início"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"O compartilhamento é pausado na troca de apps"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Compartilhar este app"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Voltar"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Troca de app"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Ação bloqueada pelo administrador de TI"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de tela foi desativada pela política do dispositivo"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Remover tudo"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar toda a tela"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte da tela"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Abrir as configurações de ampliação"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Fechar configurações de ampliação"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"Sair do modo de edição"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arraste o canto para redimensionar"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir rolagem diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionar"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problema para ler seu medidor de bateria"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toque para mais informações"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nenhum alarme definido"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"inserir bloqueio de tela"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de impressão digital"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"acessar o dispositivo"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"O Google Assistente está ouvindo"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificação}one{# notificação}many{# notificações}other{# notificações}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Anotações"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Anotações"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Anotações, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transmitindo"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Interromper a transmissão do app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se você transmitir o app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou mudar a saída, a transmissão atual será interrompida"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Abrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Para adicionar o app Carteira como um atalho, verifique se ele está instalado"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Para adicionar o app Carteira como um atalho, verifique se pelo menos um cartão foi adicionado"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Para adicionar o leitor de código QR como um atalho, verifique se algum app de câmera está instalado"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Para adicionar o app Home como um atalho, verifique se ele está instalado"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Pelo menos um dispositivo está disponível"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Selecione um app de notas padrão para usar o atalho de anotações"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Atenção do Assistente ativada"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar o app"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfone e câmera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente do app"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Consultar acessos recentes"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Concluído"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Abrir e mostrar opções"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Fechar"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Fechar este app"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"App <xliff:g id="APP_NAME">%1$s</xliff:g> fechado"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Gerenciar serviço"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Gerenciar o acesso"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Em uso pela ligação telefônica"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Usado recentemente em uma ligação telefônica"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Em uso pelo app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Usado recentemente pelo app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Em uso pelo app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Usado recentemente pelo app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Em uso pelo app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Usado recentemente pelo app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 52074d6..a4f17b6 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Toque para ver"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Erro ao guardar a gravação de ecrã"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Ocorreu um erro ao iniciar a gravação do ecrã."</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visualização de ecrã inteiro"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Para sair, deslize rapidamente para baixo a partir da parte superior."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Anterior"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Página inicial"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ausc. c/ mic. integ."</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Aparelhos auditivos"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparelhos auditivos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"A ativar..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotação auto."</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rodar o ecrã automaticamente"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Limite de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Aviso de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Apps trabalho"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Pausado"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Luz noturna"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Ativ. ao pôr-do-sol"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Até ao amanhecer"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando está a partilhar, gravar ou transmitir conteúdo, o Android tem acesso a tudo o que está visível no seu ecrã ou é reproduzido no seu dispositivo. Por isso, tenha cuidado com, por exemplo, palavras-passe, detalhes de pagamento, mensagens, fotos, áudio e vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando está a partilhar, gravar ou transmitir uma app, o Android tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com, por exemplo, palavras-passe, detalhes de pagamento, mensagens, fotos, áudio e vídeo."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Iniciar"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"A partilha é pausada quando muda de app"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Partilhar antes esta app"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Voltar"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Mudança de app"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueado pelo administrador de TI"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de ecrã está desativada pela política do dispositivo"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar o ecrã inteiro"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte do ecrã"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Abrir definições de ampliação"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Fechar definições de ampliação"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"Sair do modo de edição"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arrastar o canto para redimensionar"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir deslocamento da página na diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionar"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Ocorreu um problema ao ler o medidor da bateria"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toque para obter mais informações"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nenhum alarme defin."</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"introduzir bloqueio de ecrã"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de impressões digitais"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"entrar no dispositivo"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"O Assistente está a ouvir"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificação}many{# notificações}other{# notificações}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Tomar notas"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Tomar notas"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Tomar notas, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"A transmitir"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Interromper a transmissão da app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se transmitir a app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou alterar a saída, a sua transmissão atual é interrompida"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Abrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Para adicionar a app Carteira como um atalho, certifique-se de que a app está instalada"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Para adicionar a app Carteira como um atalho, certifique-se de que foi adicionado, pelo menos, um cartão"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Para adicionar o Leitor de códigos QR como um atalho, certifique-se de que está instalada uma app de câmara"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Para adicionar a app Home como um atalho, certifique-se de que a app está instalada"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Está disponível, pelo menos, um dispositivo"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Selecione uma app de notas predefinida para usar o atalho de anotação"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Atenção do Assistente ativada"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Predefina a app de notas nas Definições"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar app"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfone e câmara"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilização recente da app"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ver acesso recente"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Concluir"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Expandir e mostrar opções"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Reduzir"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Fechar esta app"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"App <xliff:g id="APP_NAME">%1$s</xliff:g> fechada"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Gerir serviço"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Gerir acesso"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Em utilização por uma chamada telefónica"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Usado recentemente numa chamada telefónica"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Em utilização pela app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Usado recentemente pela app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Em utilização pela app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Usado recentemente pela app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Em utilização pela app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Usado recentemente pela app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 7fc97b4..8f5d7fa 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Toque para ver"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Erro ao salvar a gravação da tela"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Erro ao iniciar a gravação de tela"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visualização em tela cheia"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Para sair, deslize de cima para baixo."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendi"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Voltar"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Página inicial"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Áudio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Fone de ouvido"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Entrada"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Aparelhos auditivos"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparelhos auditivos"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ativando…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Giro automático"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Giro automático da tela"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Limite: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Aviso de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Apps de trabalho"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Pausado"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Modo noturno"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Ativ. ao pôr do sol"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Até o nascer do sol"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando você compartilha, grava ou transmite a tela, o Android tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando você compartilha, grava ou transmite um app, o Android tem acesso a todas as informações visíveis ou reproduzidas nele. Tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Início"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"O compartilhamento é pausado na troca de apps"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Compartilhar este app"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Voltar"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Troca de app"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Ação bloqueada pelo administrador de TI"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de tela foi desativada pela política do dispositivo"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Remover tudo"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ampliar toda a tela"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte da tela"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Abrir as configurações de ampliação"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Fechar configurações de ampliação"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"Sair do modo de edição"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Arraste o canto para redimensionar"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permitir rolagem diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionar"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problema para ler seu medidor de bateria"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toque para mais informações"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nenhum alarme definido"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"inserir bloqueio de tela"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de impressão digital"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"acessar o dispositivo"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"O Google Assistente está ouvindo"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificação}one{# notificação}many{# notificações}other{# notificações}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Anotações"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Anotações"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Anotações, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Transmitindo"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Interromper a transmissão do app <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Se você transmitir o app <xliff:g id="SWITCHAPP">%1$s</xliff:g> ou mudar a saída, a transmissão atual será interrompida"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Abrir <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Para adicionar o app Carteira como um atalho, verifique se ele está instalado"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Para adicionar o app Carteira como um atalho, verifique se pelo menos um cartão foi adicionado"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Para adicionar o leitor de código QR como um atalho, verifique se algum app de câmera está instalado"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Para adicionar o app Home como um atalho, verifique se ele está instalado"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Pelo menos um dispositivo está disponível"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Selecione um app de notas padrão para usar o atalho de anotações"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Atenção do Assistente ativada"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar o app"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfone e câmera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Uso recente do app"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Consultar acessos recentes"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Concluído"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Abrir e mostrar opções"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Fechar"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Fechar este app"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"App <xliff:g id="APP_NAME">%1$s</xliff:g> fechado"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Gerenciar serviço"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Gerenciar o acesso"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Em uso pela ligação telefônica"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Usado recentemente em uma ligação telefônica"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Em uso pelo app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Usado recentemente pelo app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Em uso pelo app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Usado recentemente pelo app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Em uso pelo app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Usado recentemente pelo app <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index c276f71..749106b 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Atinge pentru a afișa"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Eroare la salvarea înregistrării ecranului"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Eroare la începerea înregistrării ecranului"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Vizualizare pe ecran complet"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Pentru a ieși, glisează de sus în jos."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Înapoi"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Ecranul de pornire"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Meniu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Căști"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Intrare"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Aparate auditive"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparate auditive"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Se activează..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotire automată"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotirea automată a ecranului"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Limită de <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Avertizare: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Aplicații pentru lucru"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Întreruptă"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Lumină de noapte"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Activată la apus"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Până la răsărit"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Când permiți accesul, înregistrezi sau proiectezi, Android are acces la orice este vizibil pe ecran sau se redă pe dispozitiv. Prin urmare, ai grijă cu parolele, detaliile de plată, mesajele, fotografiile și conținutul audio și video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Când permiți accesul, înregistrezi sau proiectezi o aplicație, Android are acces la orice se afișează pe ecran sau se redă în aplicație. Prin urmare, ai grijă cu informații cum ar fi parolele, detaliile de plată, mesajele, fotografiile și conținutul audio și video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Începe"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Trimiterea se oprește când comuți între aplicații"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Trimite această aplicație"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Revino"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Comutator pentru aplicații"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocată de administratorul IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Capturile de ecran sunt dezactivate de politica privind dispozitivele"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Șterge toate notificările"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Mărește tot ecranul"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Mărește o parte a ecranului"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Deschide setările pentru mărire"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Închide setările de mărire"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Trage de colț pentru a redimensiona"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Permite derularea pe diagonală"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Redimensionează"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problemă la citirea măsurării bateriei"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Atinge pentru mai multe informații"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nicio alarmă setată"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"intră în blocarea ecranului"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor de amprentă"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentifică-te"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"Accesează dispozitivul"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistentul ascultă"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificare}few{# notificări}other{# de notificări}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Notetaking"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Luare de notițe"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Luare de notițe, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Se difuzează"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Oprești transmisia <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Dacă transmiți <xliff:g id="SWITCHAPP">%1$s</xliff:g> sau schimbi ieșirea, transmisia actuală se va opri"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Deschide <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Pentru a adăuga aplicația Portofel drept comandă rapidă, asigură-te că aplicația este instalată"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Pentru a adăuga aplicația Portofel drept comandă rapidă, asigură-te că ai adăugat cel puțin un card"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Pentru a adăuga Scanner de coduri QR drept comandă rapidă, asigură-te că este instalată o aplicație pentru camera foto"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Pentru a adăuga aplicația Home drept comandă rapidă, asigură-te că aplicația este instalată"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Este disponibil cel puțin un dispozitiv"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Selectează o aplicație prestabilită pentru note ca să folosești comanda rapidă de luat note"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Asistentul este atent"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setează aplicația prestabilită de note din Setări"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalează aplicația"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Microfon și cameră"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Utilizare recentă în aplicații"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Vezi accesarea recentă"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Gata"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Extinde și afișează opțiunile"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Restrânge"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Închide aplicația"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> a fost închisă"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Gestionează serviciul"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Gestionează accesul"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Folosit de un apel telefonic"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Folosit recent într-un apel telefonic"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Se folosește pentru <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"S-a folosit recent de <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Se folosește pentru <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Folosit recent de <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Se folosește pentru <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Folosit recent de <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 9e63d47..5f7445a 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Нажмите, чтобы посмотреть."</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Не удалось сохранить запись видео с экрана."</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Не удалось начать запись видео с экрана."</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Полноэкранный режим"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Чтобы выйти, проведите по экрану сверху вниз."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"ОК"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Главный экран"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудиоустройство"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнитура"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Устройство ввода"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Слуховые аппараты"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слуховые аппараты"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Включение…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автоповорот"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоповорот экрана"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Ограничение: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"Приостановлен"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Когда вы демонстрируете, транслируете экран или записываете видео с него, система Android получает доступ ко всему, что видно или воспроизводится на устройстве. Поэтому будьте осторожны с паролями, сведениями о способах оплаты, сообщениями, фотографиями, аудио- и видеозаписями."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Когда вы демонстрируете, записываете или транслируете экран приложения, система Android получает доступ ко всему, что видно или воспроизводится в нем. Поэтому будьте осторожны с паролями, сведениями о способах оплаты, сообщениями, фотографиями, аудио- и видеозаписями."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Начать"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Общий доступ прерывается при переключении приложений"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Открыть доступ к этому приложению"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Переключиться обратно"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Переключение приложений"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблокировано вашим администратором"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Запись экрана отключена в соответствии с правилами для устройства."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Увеличение всего экрана"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Увеличить часть экрана"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Открыть настройки увеличения"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Закрыть настройки увеличения"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Потяните за угол, чтобы изменить размер"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Разрешить прокручивать по диагонали"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Изменить размер"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Не удалось узнать уровень заряда батареи"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Нажмите, чтобы узнать больше."</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Нет будильников"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"разблокировать экран"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сканер отпечатков пальцев"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"выполнить аутентификацию"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"указать устройство"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Ассистент вас слушает"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# уведомление}one{# уведомление}few{# уведомления}many{# уведомлений}other{# уведомления}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Создание заметок"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Создание заметок"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>: создание заметок"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Трансляция"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Остановить трансляцию \"<xliff:g id="APP_NAME">%1$s</xliff:g>\"?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Если вы начнете транслировать \"<xliff:g id="SWITCHAPP">%1$s</xliff:g>\" или смените целевое устройство, текущая трансляция прервется."</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Открыть \"<xliff:g id="APPNAME">%1$s</xliff:g>\""</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Вы можете добавить ярлык приложения \"Кошелек\", только если оно установлено."</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Вы можете добавить ярлык приложения \"Кошелек\", только если добавлена хотя бы одна карта."</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Вы можете добавить ярлык сканера QR-кодов, только если установлено приложение камеры."</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Вы можете добавить ярлык приложения Home, только если оно установлено."</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Доступно хотя бы одно устройство."</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Выберите стандартное приложение для заметок, которое будет открываться при нажатии на ярлык."</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Ассистент готов слушать"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайте стандартное приложение для заметок в настройках."</string>
     <string name="install_app" msgid="5066668100199613936">"Установить приложение"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Недавно использовались приложениями"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Посмотреть недавний доступ"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Готово"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Развернуть и показать параметры"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Свернуть"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Закрыть это приложение"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" закрыто."</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Настроить сервис"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Настроить доступ"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Сейчас используется для телефонного звонка"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Недавно использовалось во время телефонного звонка"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Сейчас используется приложением \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Недавно использовалось приложением \"<xliff:g id="APP_NAME">%1$s</xliff:g>\""</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Сейчас используется приложением \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Недавно использовалось приложением \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Сейчас используется приложением \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Недавно использовалось приложением \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 1e31722..5cc1fa9 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"බැලීමට තට්ටු කරන්න"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"තිර පටිගත කිරීම සුරැකීමේ දෝෂයකි"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"තිර පටිගත කිරීම ආරම්භ කිරීමේ දෝෂයකි"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"මුළු තිරය බලමින්"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"ඉවත් වීමට, ඉහළ සිට පහළට ස්වයිප් කරන්න"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"තේරුණා"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"ආපසු"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"මුල් පිටුව"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"මෙනුව"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ශ්‍රව්‍ය"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"හෙඩ්සෙටය"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ආදානය"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"ශ්‍රවණාධාරක"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"ශ්‍රවණාධාරක"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ක්‍රියාත්මක කරමින්…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ස්වයංක්‍රීය කරකැවීම"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"ස්වයංක්‍රීයව-භ්‍රමණය වන තිරය"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> සීමිත"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"විරාමයි"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ඔබ බෙදා ගන්නා විට, පටිගත කරන විට, හෝ විකාශය කරන විට, Android හට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය වන ඕනෑම දෙයකට ප්‍රවේශය ඇත. ඒ නිසා මුරපද, ගෙවීම් විස්තර, පණිවුඩ, ඡායාරූප, සහ ශ්‍රව්‍ය සහ දෘශ්‍ය වැනි දේවල් පිළිබඳ ප්‍රවේශම් වන්න."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ඔබ යෙදුමක් බෙදා ගන්නා විට, පටිගත කරන විට හෝ විකාශය කරන විට, Android හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්‍රවේශය ඇත. ඒ නිසා මුරපද, ගෙවීම් විස්තර, පණිවුඩ, ඡායාරූප, සහ ශ්‍රව්‍ය සහ දෘශ්‍ය වැනි දේවල් පිළිබඳ ප්‍රවේශම් වන්න."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"අරඹන්න"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"ඔබ යෙදුම් මාරු කරන විට බෙදා ගැනීම විරාම වේ"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"ඒ වෙනුවට මෙම යෙදුම බෙදා ගන්න"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"ආපසු මාරු වන්න"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"යෙදුම් මාරුව"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ඔබේ IT පරිපාලක විසින් අවහිර කර ඇත"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"උපාංග ප්‍රතිපත්තිය මගින් තිර ග්‍රහණය කිරීම අබල කර ඇත"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"සියල්ල හිස් කරන්න"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"පූර්ණ තිරය විශාලනය කරන්න"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"තිරයේ කොටසක් විශාලනය කරන්න"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"විශාලන සැකසීම් විවෘත කරන්න"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"විශාලන සැකසීම් වසන්න"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ප්‍රමාණය වෙනස් කිරීමට කොන අදින්න"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"විකර්ණ අනුචලනයට ඉඩ දෙන්න"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ප්‍රතිප්‍රමාණය කරන්න"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"ඔබගේ බැටරි මනුව කියවීමේ දෝෂයකි"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"තවත් තොරතුරු සඳහා තට්ටු කරන්න"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"එලාම සකසා නැත"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"තිර අගුල ඇතුළු කරන්න"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"ඇඟිලි සලකුණු සංවේදකය"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"සත්‍යාපනය කරන්න"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"උපාංගය ඇතුළු කරන්න"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"සහයක සවන් දෙයි"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{දැනුම්දීම් #ක්}one{දැනුම්දීම් #ක්}other{දැනුම්දීම් #ක්}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"සටහන් කර ගැනීම"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"සටහන් කර ගැනීම"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"සටහන් කර ගැනීම, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"විකාශනය කරමින්"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> විකාශනය කිරීම නවත්වන්නද?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"ඔබ <xliff:g id="SWITCHAPP">%1$s</xliff:g> විකාශනය කළහොත් හෝ ප්‍රතිදානය වෙනස් කළහොත්, ඔබගේ වත්මන් විකාශනය නවතිනු ඇත."</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> විවෘත කරන්න"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"කෙටිමඟක් ලෙස Wallet යෙදුම එක් කිරීම සඳහා, යෙදුම ස්ථාපනය කර ඇති බවට වග බලා ගන්න"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"කෙටිමඟක් ලෙස Wallet යෙදුම එක් කිරීම සඳහා, අවම වශයෙන් එක් කාඩ්පතක් එක් කර ඇති බවට වග බලා ගන්න"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"කෙටිමඟක් ලෙස QR කේත ස්කෑනරය එක් කිරීම සඳහා, කැමරා යෙදුමක් ස්ථාපනය කර ඇති බවට වග බලා ගන්න"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home යෙදුම කෙටිමඟක් ලෙස එක් කිරීම සඳහා, යෙදුම ස්ථාපනය කර ඇති බව සහතික කර ගන්න"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• අවම වශයෙන් එක උපාංගයක් ලැබේ"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"සටහන් ගැනීමේ කෙටිමඟ භාවිතා කිරීමට පෙරනිමි සටහන් යෙදුමක් තෝරන්න"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"සහයක අවධානය යොමු කරයි"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"සැකසීම් තුළ පෙරනිමි සටහන් යෙදුම සකසන්න"</string>
     <string name="install_app" msgid="5066668100199613936">"යෙදුම ස්ථාපනය කරන්න"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"මයික්‍රොෆෝනය සහ කැමරාව"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"මෑත යෙදුම් භාවිතය"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"මෑත ප්‍රවේශය බලන්න"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"නිමයි"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"දිග හැර විකල්ප පෙන්වන්න"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"හකුළන්න"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"මෙම යෙදුම වසන්න"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> වසන ලදි"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"සේවය කළමනා කරන්න"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"ප්‍රවේශ කළමනා කරන්න"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"දුරකථන ඇමතුමේ භාවිත වේ"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"දුරකථන ඇමතුමේ මෑතකදී භාවිත කරන ලදි"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> භාවිත කරයි"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"<xliff:g id="APP_NAME">%1$s</xliff:g> විසින් මෑතකදී භාවිත කරන ලදි"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> භාවිත කරයි (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> විසින් මෑතකදී භාවිත කරන ලදි (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> භාවිත කරයි (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> විසින් මෑතකදී භාවිත කරන ලදි (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 7bbff05..1da7aca 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Zobrazte klepnutím"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Pri ukladaní nahrávky obrazovky sa vyskytla chyba"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Pri spustení nahrávania obrazovky sa vyskytla chyba"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Zobrazenie na celú obrazovku"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Ukončíte potiahnutím zhora nadol."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Dobre"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Späť"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Plocha"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvuk"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Náhlavná súprava"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vstup"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Načúvadlá"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Načúvadlá"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Zapína sa…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Automatické otáčanie"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Automatické otáčanie obrazovky"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Limit: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Upozornenie pri <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Pracovné aplikácie"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Pozastavené"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Nočný režim"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Zapne sa pri západe slnka"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Do východu slnka"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Počas zdieľania, nahrávania alebo prenosu bude mať Android prístup k všetkému, čo sa zobrazuje na obrazovke alebo prehráva v zariadení. Preto zvýšte pozornosť v prípade položiek, ako sú heslá, platobné údaje, správy, fotky a zvuk či video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Počas zdieľania, nahrávania alebo prenosu v aplikácii bude mať Android prístup k všetkému zobrazovanému alebo prehrávaného obsahu v danej aplikácii. Preto zvýšte pozornosť v prípade položiek, ako sú heslá, platobné údaje, správy, fotky a zvuk či video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Začať"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Zdieľanie sa pozastaví, keď prepnete aplikácie"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Zdieľať radšej túto aplikáciu"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Prepnúť späť"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Prepnutie aplikácie"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokované vaším správcom IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snímanie obrazovky je zakázané pravidlami pre zariadenie"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Vymazať všetko"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Zväčšenie celej obrazovky"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Zväčšiť časť obrazovky"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Otvoriť nastavenia zväčšenia"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Zavrieť nastavenia zväčšenia"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Veľkosť zmeníte presunutím rohu"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Povoliť diagonálne posúvanie"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Zmeniť veľkosť"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Pri čítaní meradla batérie sa vyskytol problém"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Klepnutím si zobrazíte ďalšie informácie"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Žiadny budík"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"zadať zámku obrazovky"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Senzor odtlačkov prstov"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"overte"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"vstúpte do zariadenia"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistent počúva"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# upozornenie}few{# upozornenia}many{# notifications}other{# upozornení}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Zapisovanie poznámok"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Zapisovanie poznámok"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Zapisovanie poznámok, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Vysiela"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Chcete zastaviť vysielanie aplikácie <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ak vysielate aplikáciu <xliff:g id="SWITCHAPP">%1$s</xliff:g> alebo zmeníte výstup, aktuálne vysielanie bude zastavené"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Otvoriť <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Ak chcete pridať aplikáciu Peňaženka ako odkaz, uistite sa, že je nainštalovaná"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Ak chcete pridať aplikáciu Peňaženka ako odkaz, uistite sa, že bola pridaná aspoň jedna karta"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Ak chcete pridať skener QR kódov ako odkaz, uistite, že je nainštalovaná aplikácia fotoaparátu"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Ak chcete pridať aplikáciu Home ako odkaz, uistite sa, že je nainštalovaná"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• K dispozícii je minimálne jedno zariadenie"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Ak chcete používať odkaz na písanie poznámok, vyberte predvolenú aplikáciu na písanie poznámok"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Pozornosť Asistenta je zapnutá"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavte predvolenú aplikáciu na poznámky v Nastaveniach"</string>
     <string name="install_app" msgid="5066668100199613936">"Inštalovať aplikáciu"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofón a kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedávne využitie aplikácie"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Zobraziť nedávny prístup"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Hotovo"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Rozbaliť a zobraziť možnosti"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Zbaliť"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Zavrieť túto aplikáciu"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> je zatvorená"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Spravovať službu"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Spravovať prístup"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Využíva telefonický hovor"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Nedávno využil telefonický hovor"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Využíva <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Nedávno využila aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Využíva <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nedávno využila aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Využíva <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nedávno využila aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 3d4eaf3..79e5437 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Dotaknite se za ogled."</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Napaka pri shranjevanju posnetka zaslona"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Napaka pri začenjanju snemanja zaslona"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Vklopljen je celozaslonski način."</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Zaprete ga tako, da z vrha s prstom povlečete navzdol."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Razumem"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Nazaj"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Začetni zaslon"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Meni"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Zvok"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Slušalke z mikrofonom"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vhodna naprava"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Slušni pripomočki"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Slušni aparati"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Vklapljanje …"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Samodejno sukanje"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Samodejno sukanje zaslona"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Omejitev: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Opozorilo – <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Delovne aplikacije"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Začasno zaustavljeno"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Nočna svetloba"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Ob sončnem zahodu"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Do sončnega vzhoda"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Pri deljenju, snemanju ali predvajanju ima Android dostop do vsega, kar je prikazano na zaslonu ali se predvaja v napravi. Zato bodite previdni z gesli, podatki za plačilo, sporočili, fotografijami ter z zvokom in videom."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Pri deljenju, snemanju ali predvajanju aplikacije ima Android dostop do vsega, kar je prikazano ali predvajano v tej aplikaciji, zato bodite previdni z gesli, podatki za plačilo, sporočili, fotografijami ter z zvokom in videom."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Začni"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Deljenje se začasno zaustavi ob preklopu aplikacije"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Delite to aplikacijo"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Preklopite nazaj"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Preklop aplikacije"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokiral skrbnik za IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Zajemanje zaslonske slike je onemogočil pravilnik za naprave."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši vse"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Povečanje celotnega zaslona"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Povečava dela zaslona"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Odpri nastavitve povečave"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Zapri nastavitve povečave"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"Zapri način za urejanje"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Povlecite vogal, da spremenite velikost."</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Dovoli diagonalno pomikanje"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Spremeni velikost"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Težava z branjem indikatorja stanja napolnjenosti baterije"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Dotaknite se za več informacij"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Ni nastavljenih alarmov"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"odklenite zaslon"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Tipalo prstnih odtisov"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"preverjanje pristnosti"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"vstop v napravo"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Pomočnik posluša."</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# obvestilo}one{# obvestilo}two{# obvestili}few{# obvestila}other{# obvestil}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Ustvarjanje zapiskov"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Ustvarjanje zapiskov"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Ustvarjanje zapiskov, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Oddajanje"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Želite ustaviti oddajanje aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Če oddajate aplikacijo <xliff:g id="SWITCHAPP">%1$s</xliff:g> ali spremenite izhod, bo trenutno oddajanje ustavljeno."</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Odpri aplikacijo <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Če želite aplikacijo Denarnica dodati kot bližnjico, poskrbite, da je aplikacija nameščena."</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Če želite aplikacijo Denarnica dodati kot bližnjico, poskrbite, da je dodana vsaj ena kartica."</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Če želite optični bralnik kod QR dodati kot bližnjico, poskrbite, da je nameščena fotografska aplikacija."</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Če želite aplikacijo Home dodati kot bližnjico, poskrbite, da je aplikacija nameščena."</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Na voljo mora biti vsaj ena naprava."</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Izberite privzeto aplikacijo za zapiske, ki jo želite povezati z bližnjico do ustvarjanja zapiskov."</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Zaznavanje pomočnika je vklopljeno."</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavite privzeto aplikacijo za zapiske v nastavitvah."</string>
     <string name="install_app" msgid="5066668100199613936">"Namesti aplikacijo"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon in fotoaparat"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Nedavna uporaba v aplikacijah"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Ogled nedavnih dostopov"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Končano"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Razširi in pokaži možnosti"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Strni"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Zapri to aplikacijo"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> je zaprta"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Upravljanje storitve"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Upravljanje dostopa"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Uporablja se v telefonskem klicu"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Nedavno uporabljeno v telefonskem klicu"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Uporablja aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Nedavno uporabljala aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Uporablja aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Nedavno uporabljala aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Uporablja aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Nedavno uporabljala aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 55328a2..5b58fc7 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Trokit për të parë"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Gabim gjatë ruajtjes së regjistrimit të ekranit"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Gabim gjatë nisjes së regjistrimit të ekranit"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Po shikon ekranin e plotë"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Për të dalë, rrëshqit shpejt poshtë."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"E kuptova"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Prapa"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Faqja bazë"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menyja"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Kufje me mikrofon"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Hyrja"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Aparatet e dëgjimit"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Aparatet e dëgjimit"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Po aktivizohet…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rrotullim automatik"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rrotullimi automatik i ekranit"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Kufiri: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Paralajmërim për kufirin prej <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Aplikacionet e punës"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Vendosur në pauzë"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Drita e natës"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Në perëndim të diellit"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Deri në lindje të diellit"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kur ti ndan, regjistron ose transmeton, Android ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj, ki kujdes me gjërat si fjalëkalimet, detajet e pagesave, mesazhet, fotografitë, si dhe audion dhe videon."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kur ti ndan, regjistron ose transmeton një aplikacion, Android ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në atë aplikacion. Prandaj, ki kujdes me gjërat si fjalëkalimet, detajet e pagesave, mesazhet, fotografitë, si dhe audion dhe videon."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Nis"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Ndarja vendoset në pauzë kur ndërron aplikacionet"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Ndaj më mirë këtë aplikacion"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Kthehu përsëri"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Ndërrimi i aplikacionit"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"U bllokua nga administratori yt i teknologjisë së informacionit"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Regjistrimi i ekranit është çaktivizuar nga politika e pajisjes."</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Pastroji të gjitha"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Zmadho ekranin e plotë"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Zmadho një pjesë të ekranit"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Hap cilësimet e zmadhimit"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Mbyll cilësimet e zmadhimit"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Zvarrit këndin për të ndryshuar përmasat"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Lejo lëvizjen diagonale"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ndrysho përmasat"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Problem me leximin e matësit të baterisë"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Trokit për më shumë informacione"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Nuk është caktuar asnjë alarm"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"hyr te kyçja e ekranit"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensori i gjurmës së gishtit"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"për ta vërtetuar"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"për të hyrë në pajisje"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"\"Asistenti\" po dëgjon"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# njoftim}other{# njoftime}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Mbajtja e shënimeve"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Mbajtja e shënimeve"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Mbajtja e shënimeve, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Po transmeton"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Të ndalohet transmetimi i <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Nëse transmeton <xliff:g id="SWITCHAPP">%1$s</xliff:g> ose ndryshon daljen, transmetimi yt aktual do të ndalojë"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Hap \"<xliff:g id="APPNAME">%1$s</xliff:g>\""</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Për të shtuar aplikacionin \"Portofoli\" si një shkurtore, sigurohu që aplikacioni të jetë i instaluar"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Për të shtuar aplikacionin \"Portofoli\" si një shkurtore, sigurohu që të jetë shtuar të paktën një kartë"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Për të shtuar skanerin e kodeve QR si një shkurtore, sigurohu që aplikacioni i kamerës të jetë i instaluar"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Për të shtuar aplikacionin Home si një shkurtore, sigurohu që aplikacioni të jetë i instaluar"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Ofrohet të paktën një pajisje"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Zgjidh një aplikacion të parazgjedhur shënimesh për të përdorur shkurtoren e mbajtjes së shënimeve"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Vëmendja e \"Asistentit\" aktive"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Cakto aplikacionin e parazgjedhur të shënimeve te \"Cilësimet\""</string>
     <string name="install_app" msgid="5066668100199613936">"Instalo aplikacionin"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofoni dhe kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Përdorimi i fundit i aplikacionit"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Shiko qasjen e fundit"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"U krye"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Zgjero dhe shfaq opsionet"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Palos"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Mbylle këtë aplikacion"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> u mbyll"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Menaxho shërbimin"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Menaxho qasjen"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Në përdorim nga telefonata"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Përdorur së fundi në telefonatë"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Në përdorim nga <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Përdorur së fundi nga <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Në përdorim nga <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Përdorur së fundi nga <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Në përdorim nga <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Përdorur së fundi nga <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 61e78bf..eee0fe6 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Додирните да бисте прегледали"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Грешка при чувању снимка екрана"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Грешка при покретању снимања екрана"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Приказује се цео екран"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Да бисте изашли, превуците надоле одозго."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Важи"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Почетна"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Мени"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудио"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Слушалице"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Унос"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Слушни апарати"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слушни апарати"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Укључује се..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Аутоматска ротација"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Аутоматско ротирање екрана"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Ограничење од <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"Паузирано"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Када делите, снимате или пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Када делите, снимате или пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Покрени"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Дељење се зауставља када мењате апликације"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Дели ову апликацију"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Врати"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Промена апликације"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокира ИТ администратор"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Снимање екрана је онемогућено смерницама за уређај"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Обриши све"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Увећајте цео екран"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Увећајте део екрана"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Отвори подешавања увећања"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Затвори подешавања увећања"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"Затвори режим измене"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Превуците угао да бисте променили величину"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Дозволи дијагонално скроловање"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Промени величину"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Проблем са очитавањем мерача батерије"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Додирните за више информација"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Није подешен"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"унесите закључавање екрана"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сензор за отисак прста"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"потврдите идентитет"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"унесите уређај"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Помоћник слуша"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# обавештење}one{# обавештење}few{# обавештења}other{# обавештења}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Прављење бележака"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Прављење бележака"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Прављење бележака, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Емитовање"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Желите да зауставите емитовање апликације <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ако емитујете апликацију <xliff:g id="SWITCHAPP">%1$s</xliff:g> или промените излаз, актуелно емитовање ће се зауставити"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Отворите: <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Да бисте додали апликацију Новчаник као пречицу, уверите се да је апликација инсталирана"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Да бисте додали апликацију Новчаник као пречицу, уверите се да је додата бар једна картица"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Да бисте додали Скенер QR кода као пречицу, уверите се да је апликација за камеру инсталирана"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Да бисте додали апликацију Home као пречицу, уверите се да је апликација инсталирана"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Доступан је бар један уређај"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Изаберите подразумевану апликацију за белешке да бисте користили пречицу за прављење белешки"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Помоћник је у активном стању"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Подесите подразумевану апликацију за белешке у Подешавањима"</string>
     <string name="install_app" msgid="5066668100199613936">"Инсталирај апликацију"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Микрофон и камера"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Недавно користила апликација"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Прикажи недавни приступ"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Готово"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Прошири и прикажи опције"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Скупи"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Затвори ову апликацију"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Затворено: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Управљај услугом"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Управљај приступом"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Користи телефонски позив"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Недавно коришћено у телефонском позиву"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Користи <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Недавно користила апликација <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Користи <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Недавно користила апликација <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Користи <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Недавно користила апликација <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 6b5e0ba..b996649 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Tryck för att visa"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Det gick inte att spara skärminspelningen"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Det gick inte att starta skärminspelningen"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Visar på fullskärm"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Svep nedåt från skärmens överkant för att avsluta."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Tillbaka"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Startsida"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Meny"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ljud"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Ingång"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Hörapparater"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Hörapparater"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Aktiverar …"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Rotera automatiskt"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Rotera skärmen automatiskt"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Gräns: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Varning <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Jobbappar"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Pausad"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Nattljus"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"På från solnedgången"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Till soluppgången"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"När du delar, spelar in eller castar har Android åtkomst till allt som visas på skärmen eller spelas upp på enheten. Var försiktig med sådant som lösenord, betalningsuppgifter, meddelanden, foton och ljud och video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"När du delar, spelar in eller castar en app har Android åtkomst till allt som visas eller spelas upp i appen. Var försiktig med sådant som lösenord, betalningsuppgifter, meddelanden, foton och ljud och video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Börja"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Delningen pausas när du byter appar"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Dela den här appen i stället"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Byt tillbaka"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Appbyte"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blockeras av IT-administratören"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skärminspelning är inaktiverat av enhetspolicyn"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Rensa alla"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Förstora hela skärmen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Förstora en del av skärmen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Öppna inställningarna för förstoring"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Stäng inställningarna för förstoring"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Dra i hörnet för att ändra storlek"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Tillåt diagonal scrollning"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Ändra storlek"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Batteriindikatorn visas inte"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tryck för mer information"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Inget inställt alarm"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ange skärmlåset"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingeravtryckssensor"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentisera"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ange enhet"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistenten lyssnar"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# avisering}other{# aviseringar}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Anteckningar"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Anteckna"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Anteckna med <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Sänder"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Vill du sluta sända från <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Om en utsändning från <xliff:g id="SWITCHAPP">%1$s</xliff:g> pågår eller om du byter ljudutgång avbryts den nuvarande utsändningen"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Öppna <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Se till att Wallet-appen är installerad om du vill lägga till den som genväg"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Se till att minst ett kort har lagts till om du vill lägga till Wallet-appen som genväg"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Se till att en kameraapp är installerad om du vill lägga till QR-skannern som genväg"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Se till att Home-appen är installerad om du vill lägga till den som genväg"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• minst en enhet är tillgänglig"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Välj en standardapp för anteckningar om du vill använda genvägen för anteckningar"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistenten är aktiverad"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ställ in en standardapp för anteckningar i inställningarna"</string>
     <string name="install_app" msgid="5066668100199613936">"Installera appen"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon och kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Senaste appanvändning"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Se senaste åtkomst"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Klar"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Utöka och visa alternativ"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Komprimera"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Stäng den här appen"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> stängdes"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Hantera tjänst"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Hantera åtkomst"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Används av telefonsamtal"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Användes nyligen i telefonsamtal"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Används av <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Användes nyligen av <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Används av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Användes nyligen av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Används av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Användes nyligen av <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 9311a29..b00f9b6 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Gusa ili uangalie"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Hitilafu imetokea wakati wa kuhifadhi rekodi ya skrini"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Hitilafu imetokea wakati wa kuanza kurekodi skrini"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Unatazama kwenye skrini nzima"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Ili uondoke, telezesha kidole kutoka juu hadi chini."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Nimeelewa"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Nyuma"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Nyumbani"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menyu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Sauti"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Vifaa vya sauti"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Vifaa vya kuingiza sauti"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Visaidizi vya Kusikia"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Visaidizi vya kusikia"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Inawasha..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Zungusha kiotomatiki"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Skrini ijizungushe kiotomatiki"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"kikomo <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Onyo <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Programu za kazini"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Umesitishwa"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Mwanga wa Usiku"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Itawashwa machweo"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Hadi macheo"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Unaposhiriki, kurekodi au kutuma, Android inaweza kufikia kitu chochote kitakachoonekana kwenye skrini yako au kuchezwa kwenye kifaa chako. Kwa hivyo kuwa mwangalifu na vitu kama vile manenosiri, maelezo ya malipo, ujumbe, picha na sauti na video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Unaposhiriki, kurekodi au kutuma programu, Android inaweza kufikia kitu chochote kitakachoonekana au kuchezwa kwenye programu hiyo. Kwa hivyo kuwa mwangalifu na vitu kama vile manenosiri, maelezo ya malipo, ujumbe, picha na sauti na video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Anza"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Itasitisha kushiriki unapobadilisha programu"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Shiriki programu hii badala yake"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Rejea"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Kubadilisha programu"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Umezuiwa na msimamizi wako wa TEHAMA"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Mchakato wa kurekodi skrini umezimwa na sera ya kifaa"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Futa zote"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Kuza skrini nzima"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Kuza sehemu ya skrini"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Fungua mipangilio ya ukuzaji"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Funga mipangilio ya ukuzaji"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Buruta kona ili ubadilishe ukubwa"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Ruhusu usogezaji wa kimshazari"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Badilisha ukubwa"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Tatizo la kusoma mita ya betri yako"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Gusa ili upate maelezo zaidi"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Hujaweka kengele"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"weka kifunga skrini"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Kitambua alama ya kidole"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"thibitisha"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"weka kifaa"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Programu ya Mratibu inasikiliza"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{Arifa #}other{Arifa #}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Kuandika vidokezo"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Kuandika madokezo"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Kuandika madokezo, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Inaarifu"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Ungependa kusimamisha utangazaji kwenye <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Ikiwa unatangaza kwenye <xliff:g id="SWITCHAPP">%1$s</xliff:g> au unabadilisha maudhui, tangazo lako la sasa litasimamishwa"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Fungua <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Ili uweke programu ya Pochi kuwa njia ya mkato, hakikisha umesakinisha programu hiyo"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Ili uweke programu ya Pochi kuwa njia ya mkato, hakikisha umeweka angalau kadi moja"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Ili uweke kichanganuzi cha msimbo wa QR kuwa njia ya mkato, hakikisha umesakinisha programu ya kamera"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Ili uweke programu ya Google Home kuwa njia ya mkato, hakikisha umesakinisha programu hiyo"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Angalau kifaa kimoja kinapatikana"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Chagua programu chaguomsingi ya madokezo ili utumie njia ya mkato ya kuandika madokezo"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Programu ya Mratibu imewashwa"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Teua programu chaguomsingi ya madokezo katika Mipangilio"</string>
     <string name="install_app" msgid="5066668100199613936">"Sakinisha programu"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Maikrofoni na Kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Matumizi ya programu hivi majuzi"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Angalia ufikiaji wa majuzi"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Nimemaliza"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Chaguo za kupanua na kuonyesha"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Kunja"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Funga programu hii"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> imefungwa"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Dhibiti huduma"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Dhibiti idhini ya kufikia"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Inatumiwa na simu"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Ilitumiwa hivi majuzi na simu"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Inatumiwa na <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Ilitumiwa hivi majuzi na <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Inatumiwa na <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Ilitumiwa hivi majuzi na <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Inatumiwa na <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Ilitumiwa hivi majuzi na <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 59becc6..915dcdb 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -20,6 +20,15 @@
          On large screens should be the same as the regular status bar. -->
     <dimen name="status_bar_header_height_keyguard">@dimen/status_bar_height</dimen>
 
+    <!-- padding for container with status icons and battery -->
+    <dimen name="status_bar_icons_padding_end">4dp</dimen>
+    <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
+    <dimen name="status_bar_icons_padding_start">3dp</dimen>
+    <dimen name="status_bar_icons_padding_bottom">2dp</dimen>
+    <dimen name="status_bar_icons_padding_top">2dp</dimen>
+
+    <dimen name="status_bar_padding_end">0dp</dimen>
+
     <!-- Size of user icon + frame in the qs user picker (incl. frame) -->
     <dimen name="qs_framed_avatar_size">60dp</dimen>
     <!-- Size of user icon + frame in the keyguard user picker (incl. frame) -->
@@ -46,6 +55,9 @@
 
     <dimen name="navigation_key_padding">25dp</dimen>
 
+    <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
+    <dimen name="immersive_mode_cling_width">380dp</dimen>
+
     <!-- Keyboard shortcuts helper -->
     <dimen name="ksh_layout_width">488dp</dimen>
 
@@ -67,6 +79,11 @@
     <dimen name="large_dialog_width">472dp</dimen>
 
     <dimen name="large_screen_shade_header_height">42dp</dimen>
+    <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
+    <dimen name="shade_header_system_icons_padding_start">3dp</dimen>
+    <dimen name="shade_header_system_icons_padding_end">4dp</dimen>
+    <dimen name="shade_header_system_icons_padding_top">2dp</dimen>
+    <dimen name="shade_header_system_icons_padding_bottom">2dp</dimen>
 
     <!-- Lockscreen shade transition values -->
     <dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
@@ -90,4 +107,7 @@
 
     <dimen name="controls_header_horizontal_padding">12dp</dimen>
     <dimen name="controls_content_margin_horizontal">24dp</dimen>
+    <dimen name="controls_content_padding">24dp</dimen>
+    <dimen name="control_list_vertical_spacing">8dp</dimen>
+    <dimen name="control_list_horizontal_spacing">16dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index 2086459..dc1f0e4 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -17,10 +17,13 @@
 -->
 <resources>
     <!-- gap on either side of status bar notification icons -->
-    <dimen name="status_bar_icon_padding">1dp</dimen>
+    <dimen name="status_bar_icon_horizontal_margin">1sp</dimen>
 
     <dimen name="controls_header_horizontal_padding">28dp</dimen>
     <dimen name="controls_content_margin_horizontal">40dp</dimen>
+    <dimen name="controls_content_padding">32dp</dimen>
+    <dimen name="control_list_vertical_spacing">16dp</dimen>
+    <dimen name="control_list_horizontal_spacing">16dp</dimen>
 
     <dimen name="large_screen_shade_header_height">56dp</dimen>
 
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index d9aaf90..57f537f 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"பார்க்கத் தட்டவும்"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"ஸ்கிரீன் ரெக்கார்டிங்கைச் சேமிப்பதில் பிழை ஏற்பட்டது"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"ஸ்கிரீன் ரெக்கார்டிங்கைத் தொடங்குவதில் பிழை"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"முழுத் திரையில் காட்டுகிறது"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"வெளியேற, மேலிருந்து கீழே ஸ்வைப் செய்யவும்."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"சரி"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"பின்செல்"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"முகப்பு"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"மெனு"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ஆடியோ"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ஹெட்செட்"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"உள்ளீடு"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"செவித்துணை கருவிகள்"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"செவித்துணைக் கருவி"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ஆன் செய்கிறது…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"தானாகச் சுழற்று"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"திரையைத் தானாகச் சுழற்று"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> வரம்பு"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"இடைநிறுத்தப்பட்டது"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ உங்கள் திரையில் காட்டப்படுகின்ற அல்லது சாதனத்தில் பிளே செய்யப்படுகின்ற அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், படங்கள், ஆடியோ, வீடியோ போன்றவை குறித்துக் கவனத்துடன் இருங்கள்."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"நீங்கள் ஓர் ஆப்ஸைப் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ அந்த ஆப்ஸில் காட்டப்படுகின்ற அல்லது பிளே செய்யப்படுகின்ற அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், படங்கள், ஆடியோ, வீடியோ போன்றவை குறித்துக் கவனத்துடன் இருங்கள்."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"தொடங்கு"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"பகிர்தல் ஆப்ஸிற்கு இடையே மாறும்போது இடைநிறுத்தப்படும்"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"அதற்குப் பதிலாக இந்த ஆப்ஸைப் பகிர்"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"முந்தையதற்கு மாறு"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"ஆப்ஸிற்கு இடையே மாறுதல்"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"உங்கள் IT நிர்வாகி தடுத்துள்ளார்"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"\'திரையைப் படமெடுத்தல்\' சாதனக் கொள்கையின்படி முடக்கப்பட்டுள்ளது"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"எல்லாவற்றையும் அழி"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"முழுத்திரையைப் பெரிதாக்கும்"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"திரையின் ஒரு பகுதியைப் பெரிதாக்கும்"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"பெரிதாக்கல் அமைப்புகளைத் திற"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"பெரிதாக்கல் அமைப்புகளை மூடுக"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"அளவை மாற்ற மூலையை இழுக்கவும்"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"குறுக்கே ஸ்க்ரோல் செய்வதை அனுமதி"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"அளவை மாற்று"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"பேட்டரி அளவை அறிவதில் சிக்கல்"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"மேலும் தகவல்களுக்கு தட்டவும்"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"அலாரம் எதுவுமில்லை"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"திரைப் பூட்டை உள்ளிடலாம்"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"கைரேகை சென்சார்"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"அங்கீகரி"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"சாதனத்தைத் திற"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant கேட்டுக்கொண்டிருக்கிறது"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# அறிவிப்பு}other{# அறிவிப்புகள்}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"குறிப்பெடுத்தல்"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"குறிப்பெடுத்தல்"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"குறிப்பெடுத்தல், <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ஒலிபரப்புதல்"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் ஒலிபரப்பப்படுவதை நிறுத்தவா?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"நீங்கள் <xliff:g id="SWITCHAPP">%1$s</xliff:g> ஆப்ஸை ஒலிபரப்பினாலோ அவுட்புட்டை மாற்றினாலோ உங்களின் தற்போதைய ஒலிபரப்பு நிறுத்தப்படும்"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> ஆப்ஸைத் திற"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Wallet ஆப்ஸை ஷார்ட்கட்டாகச் சேர்க்க, அந்த ஆப்ஸ் நிறுவப்பட்டுள்ளதை உறுதிசெய்துகொள்ளவும்"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Wallet ஆப்ஸை ஷார்ட்கட்டாகச் சேர்க்க, அதில் குறைந்தது ஒரு கார்டாவது சேர்க்கப்பட்டுள்ளதை உறுதிசெய்துகொள்ளவும்"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR குறியீடு ஸ்கேனரை ஷார்ட்கட்டாகச் சேர்க்க, கேமரா ஆப்ஸ் நிறுவப்பட்டுள்ளதை உறுதிசெய்துகொள்ளவும்"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home ஆப்ஸை ஷார்ட்கட்டாகச் சேர்க்க, அந்த ஆப்ஸ் நிறுவப்பட்டுள்ளதை உறுதிசெய்துகொள்ளவும்"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• குறைந்தபட்சம் ஒரு சாதனமாவது இருக்கிறது"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"குறிப்பெடுத்தல் ஷார்ட்கட்டைப் பயன்படுத்த, குறிப்பெடுப்பதற்கான இயல்புநிலை ஆப்ஸைத் தேர்ந்தெடுக்கவும்"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"அசிஸ்டண்ட்டின் கவனம் இயக்கத்தில் உள்ளது"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"குறிப்பு எடுப்பதற்கான இயல்புநிலை ஆப்ஸை அமைப்புகளில் அமையுங்கள்"</string>
     <string name="install_app" msgid="5066668100199613936">"ஆப்ஸை நிறுவுங்கள்"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"மைக்ரோஃபோனும் கேமராவும்"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"சமீபத்திய ஆப்ஸ் பயன்பாடு"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"சமீபத்திய அணுகலைக் காட்டு"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"முடிந்தது"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"விருப்பங்களை விரிவாக்கிக் காட்டும்"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"சுருக்கும்"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"இந்த ஆப்ஸை மூடு"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> மூடப்பட்டது"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"சேவையை நிர்வகியுங்கள்"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"அணுகலை நிர்வகியுங்கள்"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"மொபைல் அழைப்பால் பயன்படுத்தப்படுகிறது"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"மொபைல் அழைப்பால் சமீபத்தில் பயன்படுத்தப்பட்டது"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸால் பயன்படுத்தப்படுகிறது"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸால் சமீபத்தில் பயன்படுத்தப்பட்டது"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ஆப்ஸால் பயன்படுத்தப்படுகிறது"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ஆப்ஸால் சமீபத்தில் பயன்படுத்தப்பட்டது"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ஆப்ஸால் பயன்படுத்தப்படுகிறது"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ஆப்ஸால் சமீபத்தில் பயன்படுத்தப்பட்டது"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 4bb695b..124f273 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"చూడటానికి ట్యాప్ చేయండి"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"స్క్రీన్ రికార్డింగ్‌ను సేవ్ చేయడంలో ఎర్రర్ ఏర్పడింది"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"స్క్రీన్ రికార్డింగ్ ప్రారంభించడంలో ఎర్రర్ ఏర్పడింది"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"ఫుల్ స్క్రీన్‌లో చూస్తున్నారు"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"నిష్క్రమించడానికి, పై నుండి కిందికి స్వైప్ చేయండి."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"సరే"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"వెనుకకు"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"హోమ్"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"మెనూ"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"ఆడియో"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"హెడ్‌సెట్"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ఇన్‌పుట్"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"వినికిడి పరికరాలు"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"వినికిడి పరికరాలు"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"ఆన్ చేస్తోంది…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"ఆటో-రొటేట్‌"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"స్క్రీన్ ఆటో-రొటేట్‌"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> పరిమితి"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"పాజ్ చేయబడింది"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"మీరు షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, మీ స్క్రీన్‌పై కనిపించే దేనికైనా లేదా మీ పరికరంలో ప్లే అయిన దేనికైనా Androidకు యాక్సెస్ ఉంటుంది. కాబట్టి పాస్‌వర్డ్‌లు, పేమెంట్ వివరాలు, మెసేజ్‌లు, ఫోటోలు, ఆడియో, ఇంకా వీడియో వంటి విషయాల్లో జాగ్రత్త వహించండి."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"మీరు ఏదైనా యాప్‌ను షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, ఆ యాప్‌లో చూపబడిన దేనికైనా లేదా ప్లే అయిన దేనికైనా Androidకు యాక్సెస్ ఉంటుంది. కాబట్టి పాస్‌వర్డ్‌లు, పేమెంట్ వివరాలు, మెసేజ్‌లు, ఫోటోలు, ఆడియో, ఇంకా వీడియో వంటి విషయాల్లో జాగ్రత్త వహించండి."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ప్రారంభించండి"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"మీరు యాప్‌లను మార్చినప్పుడు షేరింగ్ పాజ్ చేయబడుతుంది"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"బదులుగా ఈ యాప్‌ను షేర్ చేయండి"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"మునుపటి దానికి స్విచ్ అవ్వండి"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"యాప్ స్విచ్"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"మీ IT అడ్మిన్ ద్వారా బ్లాక్ చేయబడింది"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"పరికర పాలసీ ద్వారా స్క్రీన్ క్యాప్చర్ చేయడం డిజేబుల్ చేయబడింది"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"అన్నీ క్లియర్ చేయండి"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ఫుల్ స్క్రీన్‌ను మ్యాగ్నిఫై చేయండి"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"స్క్రీన్‌లో భాగాన్ని మ్యాగ్నిఫై చేయండి"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"మ్యాగ్నిఫికేషన్ సెట్టింగ్‌లను తెరవండి"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"మాగ్నిఫికేషన్ సెట్టింగ్‌లను మూసివేయండి"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"సవరణ మోడ్ నుండి ఎగ్జిట్ అవ్వండి"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"సైజ్ మార్చడానికి మూలను లాగండి"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"డయాగనల్ స్క్రోలింగ్‌ను అనుమతించండి"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"సైజ్ మార్చండి"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"మీ బ్యాటరీ మీటర్‌ను చదవడంలో సమస్య"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"మరింత సమాచారం కోసం ట్యాప్ చేయండి"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"అలారం సెట్ చేయలేదు"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"స్క్రీన్ లాక్‌ను ఎంటర్ చేయండి"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"వేలిముద్ర సెన్సార్"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ప్రామాణీకరించండి"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"పరికరాన్ని ఎంటర్ చేయండి"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant వింటోంది"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# నోటిఫికేషన్}other{# నోటిఫికేషన్‌లు}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"నోట్‌టేకింగ్"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"నోట్-టేకింగ్"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"నోట్-టేకింగ్, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"ప్రసారం చేస్తోంది"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ప్రసారం చేయడాన్ని ఆపివేయాలా?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"మీరు <xliff:g id="SWITCHAPP">%1$s</xliff:g> ప్రసారం చేస్తే లేదా అవుట్‌పుట్‌ను మార్చినట్లయితే, మీ ప్రస్తుత ప్రసారం ఆగిపోతుంది"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g>‌ను తెరవండి"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Wallet యాప్‌ను షార్ట్‌కట్‌గా జోడించడానికి, యాప్ ఇన్‌స్టాల్ చేయబడిందని నిర్ధారించుకోండి"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Wallet యాప్‌ను షార్ట్‌కట్‌గా జోడించడానికి, కనీసం ఒక కార్డ్ జోడించబడిందని నిర్ధారించుకోండి"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR కోడ్ స్కానర్‌ను షార్ట్‌కట్‌గా జోడించడానికి, కెమెరా యాప్ ఇన్‌స్టాల్ చేయబడిందని నిర్ధారించుకోండి"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home యాప్‌ను షార్ట్‌కట్‌గా జోడించడానికి, యాప్ ఇన్‌స్టాల్ చేయబడిందని నిర్ధారించుకోండి"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• కనీసం ఒక పరికరమైనా అందుబాటులో ఉందని"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"నోట్‌టేకింగ్ షార్ట్‌కట్‌ను ఉపయోగించడానికి ఆటోమేటిక్ సెట్టింగ్ నోట్స్ యాప్‌ను ఎంచుకోండి"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistant అటెన్షన్ ఆన్‌లో ఉంది"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"సెట్టింగ్‌లలో ఆటోమేటిక్‌గా ఉండేలా ఒక నోట్స్ యాప్‌ను సెట్ చేసుకోండి"</string>
     <string name="install_app" msgid="5066668100199613936">"యాప్‌ను ఇన్‌స్టాల్ చేయండి"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"మైక్రోఫోన్ &amp; కెమెరా"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"ఇటీవలి యాప్ వినియోగం"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ఇటీవలి యాక్సెస్‌ను చూడండి"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"పూర్తయింది"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"ఆప్షన్‌లను విస్తరించి, చూడండి"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"కుదించండి"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"ఈ యాప్‌ను మూసివేయండి"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> మూసివేయబడింది"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"సర్వీస్‌ను మేనేజ్ చేయండి"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"యాక్సెస్‌ను మేనేజ్ చేయండి"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"ఫోన్ కాల్ ద్వారా వినియోగంలో ఉంది"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"ఇటీవల ఫోన్ కాల్‌లో వినియోగించబడింది"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> ద్వారా వినియోగంలో ఉంది"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"<xliff:g id="APP_NAME">%1$s</xliff:g> ద్వారా ఇటీవల వినియోగించబడింది"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ద్వారా వినియోగంలో ఉంది"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ద్వారా ఇటీవల వినియోగించబడింది"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ద్వారా వినియోగంలో ఉంది"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ద్వారా ఇటీవల ఉపయోగించబడింది"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 0ca154e..2ace86f 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -20,11 +20,6 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds. -->
 <resources>
-    <!-- SystemUIFactory component -->
-    <string name="config_systemUIFactoryComponent" translatable="false">
-        com.android.systemui.tv.TvSystemUIInitializer
-    </string>
-
     <!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
     <integer name="recents_svelte_level">3</integer>
 
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 042e1ac..46fec04 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"แตะเพื่อดู"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"เกิดข้อผิดพลาดในการบันทึกหน้าจอ"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"เกิดข้อผิดพลาดขณะเริ่มบันทึกหน้าจอ"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"กำลังดูแบบเต็มหน้าจอ"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"หากต้องการออก ให้ปัดลงจากด้านบน"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"รับทราบ"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"กลับ"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"หน้าแรก"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"เมนู"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"เสียง"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ชุดหูฟัง"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"อินพุต"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"เครื่องช่วยฟัง"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"เครื่องช่วยฟัง"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"กำลังเปิด..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"หมุนอัตโนมัติ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"หมุนหน้าจออัตโนมัติ"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"ขีดจำกัด <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"หยุดชั่วคราว"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"เมื่อกำลังแชร์ บันทึก หรือแคสต์ Android จะมีสิทธิ์เข้าถึงทุกสิ่งที่ปรากฏบนหน้าจอหรือเล่นอยู่ในอุปกรณ์ ดังนั้นโปรดระวังสิ่งต่างๆ อย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ รูปภาพ รวมถึงเสียงและวิดีโอ"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"เมื่อกำลังแชร์ บันทึก หรือแคสต์แอป Android จะมีสิทธิ์เข้าถึงทุกสิ่งที่แสดงหรือเล่นอยู่ในแอปดังกล่าว ดังนั้นโปรดระวังสิ่งต่างๆ อย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ รูปภาพ รวมถึงเสียงและวิดีโอ"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"เริ่ม"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"การแชร์จะหยุดชั่วคราวเมื่อสลับแอป"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"แชร์แอปนี้แทน"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"เปลี่ยนกลับ"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"เปลี่ยนแอป"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ผู้ดูแลระบบไอทีบล็อกไว้"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"การจับภาพหน้าจอปิดใช้โดยนโยบายด้านอุปกรณ์"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ล้างทั้งหมด"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"ขยายเป็นเต็มหน้าจอ"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ขยายบางส่วนของหน้าจอ"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"เปิดการตั้งค่าการขยาย"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"ปิดการตั้งค่าการขยาย"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"ออกจากโหมดแก้ไข"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"ลากที่มุมเพื่อปรับขนาด"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"อนุญาตการเลื่อนแบบทแยงมุม"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"ปรับขนาด"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"พบปัญหาในการอ่านเครื่องวัดแบตเตอรี่"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"แตะดูข้อมูลเพิ่มเติม"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"ไม่มีการตั้งปลุก"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ป้อนข้อมูลการล็อกหน้าจอ"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"เซ็นเซอร์ลายนิ้วมือ"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"ตรวจสอบสิทธิ์"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"เข้าถึงอุปกรณ์"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistant กำลังฟังอยู่"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{การแจ้งเตือน # รายการ}other{การแจ้งเตือน # รายการ}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"การจดบันทึก"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"การจดบันทึก"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"การจดบันทึก <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"กำลังออกอากาศ"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"หยุดการออกอากาศ <xliff:g id="APP_NAME">%1$s</xliff:g> ไหม"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"หากคุณออกอากาศ <xliff:g id="SWITCHAPP">%1$s</xliff:g> หรือเปลี่ยนแปลงเอาต์พุต การออกอากาศในปัจจุบันจะหยุดลง"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"เปิด <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"หากต้องการเพิ่มแอป Wallet เป็นทางลัด โปรดตรวจสอบว่าได้ติดตั้งแอปแล้ว"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"หากต้องการเพิ่มแอป Wallet เป็นทางลัด โปรดตรวจสอบว่าได้เพิ่มบัตรอย่างน้อย 1 ใบแล้ว"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"หากต้องการเพิ่มเครื่องมือสแกนคิวอาร์โค้ดเป็นทางลัด โปรดตรวจสอบว่าได้ติดตั้งแอปกล้องแล้ว"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"หากต้องการเพิ่มแอป Home เป็นทางลัด โปรดตรวจสอบว่าได้ติดตั้งแอปแล้ว"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• มีอุปกรณ์พร้อมใช้งานอย่างน้อย 1 รายการ"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"เลือกแอปโน้ตเริ่มต้นเพื่อใช้ทางลัดการจดบันทึก"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"การเรียกใช้งาน Assistant เปิดอยู่"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"กำหนดแอปการจดบันทึกเริ่มต้นในการตั้งค่า"</string>
     <string name="install_app" msgid="5066668100199613936">"ติดตั้งแอป"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"ไมโครโฟนและกล้อง"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"การใช้แอปครั้งล่าสุด"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"ดูการเข้าถึงล่าสุด"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"เสร็จสิ้น"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"ขยายและแสดงตัวเลือก"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"ยุบ"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"ปิดแอปนี้"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> ปิดแล้ว"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"จัดการบริการ"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"จัดการการเข้าถึง"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"ใช้อยู่โดยการโทร"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"ใช้ล่าสุดโดยการโทร"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"ใช้อยู่โดย <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"ใช้ล่าสุดโดย <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"ใช้อยู่โดย <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"ใช้ล่าสุดโดย <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"ใช้อยู่โดย <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"ใช้ล่าสุดโดย <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 370db59..079bd9b 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"I-tap para tingnan"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Nagka-error sa pag-save ng recording ng screen"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Nagkaroon ng error sa pagsisimula ng pag-record ng screen"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Nanonood sa full screen"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Para lumabas, mag-swipe mula sa itaas pababa."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Bumalik"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Headset"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Input"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Mga Hearing Aid"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Mga hearing aid"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Ino-on…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"I-auto rotate"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Awtomatikong i-rotate ang screen"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> ang limitasyon"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Babala sa <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Mga app para sa trabaho"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Naka-pause"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Night Light"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Mao-on sa sunset"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Hanggang sunrise"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kapag nagbabahagi, nagre-record, o nagka-cast ka, may access ang Android sa kahit anong nakikita sa iyong screen o pine-play sa device mo. Kaya mag-ingat sa mga bagay-bagay tulad ng mga password, detalye ng pagbabayad, mensahe, larawan, at audio at video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kapag nagbabahagi, nagre-record, o nagka-cast ka ng app, may access ang Android sa kahit anong ipinapakita o pine-play sa app na iyon. Kaya mag-ingat sa mga bagay-bagay tulad ng mga password, detalye ng pagbabayad, mensahe, larawan, at audio at video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Simulan"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Magpo-pause ang pagbabahagi kapag lumipat ka ng app"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Ibahagi na lang ang app na ito"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Bumalik sa dati"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Lumipat ng app"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Na-block ng iyong IT admin"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Naka-disable ang pag-screen capture ayon sa patakaran ng device"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"I-clear lahat"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"I-magnify ang buong screen"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"I-magnify ang isang bahagi ng screen"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Buksan ang mga setting ng pag-magnify"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Isara ang mga setting ng pag-magnify"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"I-drag ang sulok para i-resize"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Payagan ang diagonal na pag-scroll"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"I-resize"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Nagkaproblema sa pagbabasa ng iyong battery meter"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"I-tap para sa higit pang impormasyon"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Walang alarm"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ilagay ang lock ng screen"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor para sa fingerprint"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"i-authenticate"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"ilagay ang device"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Nakikinig ang Assistant"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notification}one{# notification}other{# na notification}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Pagtatala"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Pagtatala"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Pagtatala, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Nagbo-broadcast"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Ihinto ang pag-broadcast ng <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Kung magbo-broadcast ka ng <xliff:g id="SWITCHAPP">%1$s</xliff:g> o babaguhin mo ang output, hihinto ang iyong kasalukuyang broadcast"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Buksan ang <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Para maidagdag ang Wallet app bilang shortcut, siguraduhing naka-install ang app"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Para maidagdag ang Wallet app bilang shortcut, siguraduhing may naidagdag na kahit isang card man lang"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Para maidagdag ang scanner ng QR code bilang shortcut, siguraduhing may naka-install na camera app"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Para maidagdag ang Home app bilang shortcut, siguraduhing naka-install ang app"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• May kahit isang device na available"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Pumili ng default na app ng mga tala para magamit ang shortcut sa paggawa ng tala"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Naka-on ang atensyon ng Assistant"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Magtakda ng default na app sa pagtatala sa Mga Setting"</string>
     <string name="install_app" msgid="5066668100199613936">"I-install ang app"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikropono at Camera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Kamakailang paggamit ng app"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Tingnan ang kamakailang access"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Tapos na"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"I-expand at ipakita ang mga opsyon"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"I-collapse"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Isara ang app na ito"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Sinara ang <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Pamahalaan ang serbisyo"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Pamahalaan ang access"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Ginagamit ng tawag sa telepono"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Kamakailang ginamit sa tawag sa telepono"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Ginagamit ng <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Kamakailang ginamit ng <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Ginagamit ng <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Kamakailang ginamit ng <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Ginagamit ng <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Kamakailang ginamit ng <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 1b30dcf..7ba95ec 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Görüntülemek için dokunun"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Ekran kaydı saklanırken hata oluştu"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Ekran kaydı başlatılırken hata oluştu"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Tam ekran olarak görüntüleme"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Çıkmak için yukarıdan aşağıya doğru hızlıca kaydırın."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Anladım"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Geri"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Ana sayfa"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menü"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Ses"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Mikrofonlu kulaklık"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Giriş"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"İşitme Cihazları"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"İşitme cihazları"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Açılıyor…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Otomatik döndür"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranı otomatik döndür"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Sınır: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"<xliff:g id="DATA_LIMIT">%s</xliff:g> uyarısı"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"İş uygulamaları"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Duraklatıldı"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Gece Işığı"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Gün batımı açılacak"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Sabaha kadar"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Paylaşma, kaydetme veya yayınlama özelliğini kullandığınızda Android, ekranınızda gösterilen veya cihazınızda oynatılan her şeye erişebilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Bir uygulamayı paylaştığınızda, kaydettiğinizde veya yayınladığınızda Android, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Başlat"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Uygulama değiştirdiğinizde paylaşım duraklatılır"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Bunun yerine bu uygulamayı paylaşın"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Geri dön"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Uygulama değiştirme"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"BT yöneticiniz tarafından engellendi"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekran görüntüsü alma, cihaz politikası tarafından devre dışı bırakıldı"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Tümünü temizle"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Tam ekran büyütme"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekranın bir parçasını büyütün"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Büyütme ayarlarını aç"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Büyütme ayarlarını kapat"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Yeniden boyutlandırmak için köşeyi sürükleyin"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Çapraz kaydırmaya izin ver"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Yeniden boyutlandır"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Pil ölçeriniz okunurken sorun oluştu"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Daha fazla bilgi için dokunun"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Alarm yok"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ekran kilidini gir"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Parmak izi sensörü"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"kimlik doğrulaması yapın"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"cihaz girin"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Asistan dinliyor"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# bildirim}other{# bildirim}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Not alma"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Not alma"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Not alma, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Yayınlama"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulamasında anons durdurulsun mu?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> uygulamasında anons yapar veya çıkışı değiştirirseniz mevcut anonsunuz duraklatılır"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> uygulamasını aç"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Cüzdan uygulamasını kısayol olarak eklemek için uygulamanın yüklü olduğundan emin olun"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Cüzdan uygulamasını kısayol olarak eklemek için en az bir kart eklendiğinden emin olun"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR kodu tarayıcıyı kısayol olarak eklemek için bir kamera uygulamasının yüklü olduğundan emin olun"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home uygulamasını kısayol olarak eklemek için uygulamanın yüklü olduğundan emin olun"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• En az bir cihaz mevcut olmalıdır"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Not alma kısayolunu kullanmak için varsayılan bir notlar uygulaması seçin"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Asistan dinliyor"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlar\'ı kullanarak varsayılan notlar ayarlayın"</string>
     <string name="install_app" msgid="5066668100199613936">"Uygulamayı yükle"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon ve Kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Son uygulama kullanımı"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Son erişimi göster"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Bitti"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Seçenekleri genişletip göster"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Daralt"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Bu uygulamayı kapat"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> kapalı"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Hizmeti yönet"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Erişimi yönet"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Telefon aramasında kullanılıyor"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Yakın zamanda telefon aramasında kullanıldı"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> tarafından kullanılıyor"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"En son <xliff:g id="APP_NAME">%1$s</xliff:g> tarafından kullanıldı"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) tarafından kullanılıyor"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"En son <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) tarafından kullanıldı"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) tarafından kullanılıyor"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"En son <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) tarafından kullanıldı"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 9f0359c..3feefef 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Натисніть, щоб переглянути"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Не вдалося зберегти запис відео з екрана"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Не вдалося почати запис екрана"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Перегляд на весь екран"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Щоб вийти, проведіть пальцем униз від верху екрана."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Головна"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Аудіопристрій"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Гарнітура"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Джерело сигналу"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Слухові апарати"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Слухові апарати"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Увімкнення…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Автообертання"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Автоматично обертати екран"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Обмеження: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"Призупинено"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Коли ви показуєте, записуєте або транслюєте екран, ОС Android отримує доступ до всього, що відображається на ньому чи відтворюється на пристрої. Тому будьте уважні з паролями, повідомленнями, фотографіями, аудіо, відео, платіжною інформацією тощо."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Коли ви показуєте, записуєте або транслюєте додаток, ОС Android отримує доступ до всього, що відображається або відтворюється в ньому. Тому будьте уважні з паролями, повідомленнями, фотографіями, аудіо, відео, платіжною інформацією тощо."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Почати"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Спільний доступ до додатка призупиняється, коли ви переходите в інший додаток"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Поділитися цим додатком"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Повернутися"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Перехід між додатками"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблоковано адміністратором"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Запис екрана вимкнено згідно з правилами для пристрою"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистити все"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Збільшення всього екрана"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Збільшити частину екрана"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Відкрити налаштування збільшення"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Закрити налаштування збільшення"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Потягніть кут, щоб змінити розмір"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Дозволити прокручування по діагоналі"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Змінити розмір"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Не вдалось отримати дані про рівень заряду акумулятора"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Натисніть, щоб дізнатися більше"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Немає будильників"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"показати способи розблокування екрана"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Сканер відбитків пальців"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"пройти автентифікацію"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"відкрити пристрій"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Асистент слухає"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# сповіщення}one{# сповіщення}few{# сповіщення}many{# сповіщень}other{# сповіщення}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Створення нотаток"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Створення нотаток"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Створення нотаток, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Трансляція"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Зупинити трансляцію з додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Якщо ви зміните додаток (<xliff:g id="SWITCHAPP">%1$s</xliff:g>) або аудіовихід, поточну трансляцію буде припинено"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Відкрити <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Щоб додати ярлик для запуску додатка Google Гаманець, переконайтеся, що додаток установлено"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Щоб додати ярлик для запуску додатка Google Гаманець, переконайтеся, що він містить дані принаймні однієї картки"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Щоб додати ярлик для запуску сканера QR-коду, переконайтеся, що встановлено додаток для камери"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Щоб додати ярлик для запуску додатка Google Home, переконайтеся, що додаток установлено"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Принаймні один пристрій доступний"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Виберіть стандартний додаток для нотаток, щоб створювати їх за допомогою ярлика"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Асистента активовано"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Призначте стандартний додаток для нотаток у налаштуваннях"</string>
     <string name="install_app" msgid="5066668100199613936">"Установити додаток"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Мікрофон і камера"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Нещодавнє використання додатками"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Переглянути нещодавній доступ"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Готово"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Розгорнути й показати опції"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Згорнути"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Закрити цей додаток"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> закрито"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Керувати сервісом"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Керувати доступом"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Використовується (телефонний дзвінок)"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Нещодавно використано (телефонний дзвінок)"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Використовується (<xliff:g id="APP_NAME">%1$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Нещодавно використано (<xliff:g id="APP_NAME">%1$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Використовується (<xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Нещодавно використано (<xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Використовується (<xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Нещодавно використано (<xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index baede4e..29d3762 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"دیکھنے کے لیے تھپتھپائیں"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"اسکرین ریکارڈنگ محفوظ کرنے میں خرابی"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"اسکرین ریکارڈنگ شروع کرنے میں خرابی"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"فُل اسکرین میں دیکھ رہے ہیں"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"باہر نکلنے کیلئے اوپر سے نیچے کی طرف سوائپ کریں۔"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"سمجھ آ گئی"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"واپس جائیں"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"ہوم"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"مینیو"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"آڈیو"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"ہیڈ سیٹ"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"ان پٹ"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"سماعتی آلات"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"سماعتی آلات"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"آن ہو رہا ہے…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"خود کار طور پر گھمائیں"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"اسکرین کو خود کار طور پر گھمائیں"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> حد"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"موقوف ہے"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"‏جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو Android کو آپ کی اسکرین پر دکھائی دینے والی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ لہذا، پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، تصاویر، ساتھ ہی آڈیو اور ویڈیو جیسی چیزوں کے سلسلے میں محتاط رہیں۔"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"‏جب آپ اشتراک، ریکارڈنگ یا کسی ایپ کو کاسٹ کر رہے ہوتے ہیں تو Android کو اس ایپ پر دکھائی گئی یا چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ لہذا، پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، تصاویر، ساتھ ہی آڈیو اور ویڈیو جیسی چیزوں کے سلسلے میں محتاط رہیں۔"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"شروع کریں"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"آپ کے ایپس سوئچ کرنے پر اشتراک موقوف ہو جاتا ہے"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"اس کے بجائے اس ایپ کا اشتراک کریں"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"واپس سوئچ کریں"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"ایپ سوئچ کرنے کی سہولت"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"‏آپ کے IT منتظم نے مسدود کر دیا"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"اسکرین کو کیپچر کرنا آلہ کی پالیسی کے ذریعے غیر فعال ہے"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"سبھی کو صاف کریں"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"فُل اسکرین کو بڑا کریں"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"اسکرین کا حصہ بڑا کریں"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"میگنیفکیشن کی ترتیبات کھولیں"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"میگنیفکیشن کی ترتیبات بند کریں"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"سائز تبدیل کرنے کے لیے کونے کو گھسیٹیں"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"وتری سکرولنگ کی اجازت دیں"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"سائز تبدیل کریں"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"آپ کے بیٹری میٹر کو پڑھنے میں دشواری"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"مزید معلومات کے لیے تھپتھپائیں"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"کوئی الارم سیٹ نہیں ہے"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"اسکرین لاک درج کریں"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"فنگر پرنٹ سینسر"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"تصدیق کریں"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"آلہ درج کریں"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"اسسٹنٹ سن رہی ہے"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# اطلاع}other{# اطلاعات}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>، <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"نوٹ لینا"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"نوٹ لکھنا"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"نوٹ لکھنا، <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"نشریات"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> براڈکاسٹنگ روکیں؟"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"اگر آپ <xliff:g id="SWITCHAPP">%1$s</xliff:g> براڈکاسٹ کرتے ہیں یا آؤٹ پٹ کو تبدیل کرتے ہیں تو آپ کا موجودہ براڈکاسٹ رک جائے گا"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"<xliff:g id="APPNAME">%1$s</xliff:g> کھولیں"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"والٹ ایپ کو شارٹ کٹ کے طور پر شامل کرنے کے لیے، یقینی بنائیں کہ ایپ انسٹال ہے"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"والٹ ایپ کو شارٹ کٹ کے طور پر شامل کرنے کے لیے، یقینی بنائیں کہ کم از کم ایک کارڈ شامل کیا گیا ہے"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"‏QR کوڈ اسکینر کو شارٹ کٹ کے طور پر شامل کرنے کے لیے، یقینی بنائیں کہ کیمرا ایپ انسٹال ہے"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"ہوم ایپ کو شارٹ کٹ کے طور پر شامل کرنے کے لیے، یقینی بنائیں کہ ایپ انسٹال ہے"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• کم از کم ایک آلہ دستیاب ہے"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"نوٹ لینے والے شارٹ کٹ کا استعمال کرنے کے لیے ڈیفالٹ نوٹس ایپ منتخب کریں"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"اسسٹنٹ کی توجہ آن ہے"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ترتیبات میں ڈیفالٹ نوٹس ایپ سیٹ کریں"</string>
     <string name="install_app" msgid="5066668100199613936">"ایپ انسٹال کریں"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"مائیکروفون اور کیمرا"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"حالیہ ایپ کا استعمال"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"حالیہ رسائی دیکھیں"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"ہو گیا"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"اختیارات کو پھیلا کر دکھائیں"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"سکیڑیں"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"اس ایپ کو بند کریں"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> کو بند کر دیا گیا"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"سروس کا نظم کریں"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"رسائی کا نظم کریں"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"فون کال کے زیر استعمال"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"فون کال میں حال ہی میں استعمال کیا گیا"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"‫<xliff:g id="APP_NAME">%1$s</xliff:g> کے زیر استعمال"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"حال ہی میں <xliff:g id="APP_NAME">%1$s</xliff:g> نے استعمال کیا"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) کے زیر استعمال"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) کے ذریعے حال ہی میں استعمال کیا گیا"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) کے زیر استعمال"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) کے ذریعے حال ہی میں استعمال کیا گیا"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 63d67ad..b7defbb 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Koʻrish uchun bosing"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Ekran yozuvi saqlanmadi"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Ekranni yozib olish boshlanmadi"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Butun ekran rejimi"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Chiqish uchun tepadan pastga torting."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Orqaga"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Uyga"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menyu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Audio"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Garnitura"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Kirish"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Eshitish apparatlari"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Eshitish moslamalari"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Yoqilmoqda…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Avto-burilish"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Ekranning avtomatik burilishi"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Cheklov: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Ogohlantirish: <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Ish ilovalari"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Pauzada"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Tungi rejim"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Kunbotarda yoqish"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Quyosh chiqqunicha"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Ulashish, yozib olish va translatsiya qilish vaqtida Android ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar, suratlar, audio va video chiqmasligi uchun ehtiyot boʻling."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Ilovani ulashish, yozib olish yoki translatsiya qilayotganingizda Android ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar, suratlar, audio va video chiqmasligi uchun ehtiyot boʻling."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Boshlash"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Ilovalarni almashtirsangiz, ulashuv toʻxtatiladi"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Bu ilovani ulashish"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Ortga qaytarish"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Ilovani almashtirish"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"AT administratoringiz tomonidan bloklangan"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekranni tasvirga olish qurilmadan foydalanish tartibi tomonidan faolsizlantirilgan"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hammasini tozalash"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Ekranni toʻliq kattalashtirish"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekran qismini kattalashtirish"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Kattalashtirish sozlamalarini ochish"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Kattalashtirish sozlamalarini yopish"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Oʻlchamini oʻzgartirish uchun burchakni torting"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Diagonal aylantirishga ruxsat berish"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Oʻlchamini oʻzgartirish"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Batareya quvvati aniqlanmadi"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Batafsil axborot olish uchun bosing"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Signal sozlanmagan"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"ekran qulfini kiriting"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Barmoq izi skaneri"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"autentifikatsiya"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"qurilmani ochish"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Assistent tinglamoqda"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# ta bildirishnoma}other{# ta bildirishnoma}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Eslatma yozish"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Qayd olish"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>: qayd olish"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Signal uzatish"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasiga translatsiya toʻxtatilsinmi?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Agar <xliff:g id="SWITCHAPP">%1$s</xliff:g> ilovasiga translatsiya qilsangiz yoki ovoz chiqishini oʻzgartirsangiz, joriy translatsiya toʻxtab qoladi"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Ochish: <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Wallet ilovasini yorliq sifatida qoʻshish uchun ilova oʻrnatilganiga ishonch hosil qiling"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Wallet ilovasini yorliq sifatida qoʻshish uchun kamida bitta karta kiritilganiga ishonch hosil qiling"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"QR kod skanerini yorliq sifatida qoʻshish uchun kamera ilovasi oʻrnatilganiga ishonch hosil qiling"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Home ilovasini yorliq sifatida qoʻshish uchun ilova oʻrnatilganiga ishonch hosil qiling"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Kamida bitta qurilma mavjud"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Qayd yozish yorligʻidan foydalanish uchun birlamchi qayd ilovasini tanlang"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Assistent diqqati yoniq"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standart qaydlar ilovasini Sozlamalar orqali tanlang"</string>
     <string name="install_app" msgid="5066668100199613936">"Ilovani oʻrnatish"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Mikrofon va kamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Ilovadan oxirgi foydalanish"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Oxirgi ruxsatni koʻrish"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Tayyor"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Parametrlarni ochish"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Yopish"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Bu ilovani yopish"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"Yopildi: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Xizmatni boshqarish"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Ruxsatni boshqarish"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Telefon chaqiruvi ishlatgan"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Yaqinda telefon chaqiruvi ishlatgan"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> ishlatmoqda"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Yaqinda <xliff:g id="APP_NAME">%1$s</xliff:g> ishlatgan"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ishlatmoqda"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Yaqinda <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ishlatgan"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ishlatmoqda"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Yaqinda <xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ishlatgan"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 06bb64e..2d49b02 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Nhấn để xem"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Có lỗi xảy ra khi lưu video ghi màn hình"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Lỗi khi bắt đầu ghi màn hình"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Xem toàn màn hình"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Để thoát, hãy vuốt từ trên cùng xuống dưới."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Tôi hiểu"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Quay lại"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Trang chủ"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Âm thanh"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Tai nghe"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Thiết bị đầu vào"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Thiết bị trợ thính"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Thiết bị trợ thính"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Đang bật…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Tự động xoay"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Tự động xoay màn hình"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"Giới hạn <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"Cảnh báo <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Ứng dụng công việc"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Đã tạm dừng"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Ánh sáng đêm"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Bật khi trời tối"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Cho đến khi trời sáng"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Khi bạn chia sẻ, ghi hoặc truyền, Android sẽ có quyền truy cập vào mọi nội dung xuất hiện trên màn hình hoặc phát trên thiết bị của bạn. Vì vậy, hãy thận trọng để không làm lộ thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Khi bạn chia sẻ, ghi hoặc truyền ứng dụng, Android sẽ có quyền truy cập vào mọi nội dung xuất hiện hoặc phát trên ứng dụng đó. Vì vậy, hãy thận trọng để không làm lộ các thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Bắt đầu"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Quá trình chia sẻ sẽ tạm dừng khi bạn chuyển đổi ứng dụng"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Chia sẻ ứng dụng này"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Chuyển trở lại"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Chuyển đổi ứng dụng"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bị quản trị viên CNTT chặn"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Tính năng chụp ảnh màn hình đã bị tắt theo chính sách thiết bị"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Xóa tất cả"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Phóng to toàn màn hình"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Phóng to một phần màn hình"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Mở chế độ cài đặt phóng to"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Đóng bảng cài đặt tính năng phóng to"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Kéo góc để thay đổi kích thước"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Cho phép cuộn chéo"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Đổi kích thước"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Đã xảy ra vấn đề khi đọc dung lượng pin của bạn"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Nhấn để biết thêm thông tin"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Chưa đặt chuông báo"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"nhập phương thức mở khoá màn hình"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Cảm biến vân tay"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"xác thực"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"truy cập thiết bị"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Trợ lý đang nghe bạn nói"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# thông báo}other{# thông báo}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Ghi chú"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Ghi chú"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Ghi chú, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Phát sóng"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Dừng phát <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Nếu bạn phát <xliff:g id="SWITCHAPP">%1$s</xliff:g> hoặc thay đổi đầu ra, phiên truyền phát hiện tại sẽ dừng"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Mở <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Để tạo lối tắt cho ứng dụng Wallet, hãy đảm bảo bạn đã cài đặt ứng dụng đó"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Để tạo lối tắt cho ứng dụng Wallet, hãy đảm bảo bạn đã thêm ít nhất một thẻ"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Để tạo lối tắt cho Trình quét mã QR, hãy đảm bảo rằng bạn đã cài đặt một ứng dụng máy ảnh"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Để tạo lối tắt cho ứng dụng Home, hãy đảm bảo bạn đã cài đặt ứng dụng đó"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Có ít nhất một thiết bị đang hoạt động"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Chọn một ứng dụng ghi chú mặc định để dùng lối tắt ghi chú"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Trợ lý đang bật"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Đặt ứng dụng ghi chú mặc định trong phần Cài đặt"</string>
     <string name="install_app" msgid="5066668100199613936">"Cài đặt ứng dụng"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Micrô và máy ảnh"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Hoạt động sử dụng gần đây của ứng dụng"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Xem hoạt động truy cập gần đây"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Xong"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Mở rộng và hiện các lựa chọn"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Thu gọn"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Đóng ứng dụng này"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> đã đóng"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Quản lý dịch vụ"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Quản lý quyền truy cập"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Đang được dùng trong cuộc gọi điện thoại"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Đã dùng gần đây trong cuộc gọi điện thoại"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang dùng"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"<xliff:g id="APP_NAME">%1$s</xliff:g> đã dùng gần đây"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) đang dùng"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) đã dùng gần đây"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) đang dùng"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) đã dùng gần đây"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index a086553..8d2b89b 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"点按即可查看"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"保存屏幕录制内容时出错"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"启动屏幕录制时出错"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"目前处于全屏模式"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"要退出,请从顶部向下滑动。"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"知道了"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"返回"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"主屏幕"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"菜单"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音频"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳机"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"输入"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"助听器"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"助听器"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"正在开启…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自动屏幕旋转"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"自动旋转屏幕"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"上限为<xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"已暂停"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"在分享内容时,Android 可以访问屏幕上显示或设备中播放的所有内容。因此,请务必小心操作,谨防密码、付款信息、消息、照片、音频和视频等内容遭到泄露。"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"在分享、录制或投放内容时,Android 可以访问通过此应用显示或播放的所有内容。因此,请务必小心操作,谨防密码、付款信息、消息、照片、音频和视频等内容遭到泄露。"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"开始"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"切换应用后,分享会暂停"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"改为分享此应用"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"切换回去"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"应用切换"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"已被 IT 管理员禁止"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"设备政策已停用屏幕截图功能"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"放大整个屏幕"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"放大部分屏幕"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"打开放大功能设置"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"关闭放大设置"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"退出修改模式"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"拖动一角即可调整大小"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"允许沿对角线滚动"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"调整大小"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"读取电池计量器时出现问题"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"点按即可了解详情"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"未设置闹钟"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"输入屏幕解锁信息"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"指纹传感器"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"身份验证"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"进入设备"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Google 助理正在聆听"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# 条通知}other{# 条通知}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>,<xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"记录"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"记事"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"记事,<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"正在广播"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"要停止广播“<xliff:g id="APP_NAME">%1$s</xliff:g>”的内容吗?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"如果广播“<xliff:g id="SWITCHAPP">%1$s</xliff:g>”的内容或更改输出来源,当前的广播就会停止"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"打开<xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"若要将 Google 钱包应用添加为快捷方式,请确保已安装该应用"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"若要将 Google 钱包应用添加为快捷方式,请确保至少已添加一张银行卡"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"若要将二维码扫描器添加为快捷方式,请确保已安装相机应用"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"若要将 Home 应用添加为快捷方式,请确保已安装该应用"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• 至少有一台设备可用"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"选择默认记事应用即可使用记事快捷方式"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"已开启 Google 助理感知功能"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在设置中设置默认记事应用"</string>
     <string name="install_app" msgid="5066668100199613936">"安装应用"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"麦克风和摄像头"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"近期应用对手机传感器的使用情况"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"查看近期使用情况"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"完成"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"展开并显示选项"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"收起"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"关闭此应用"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”已关闭"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"管理服务"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"管理访问权限"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"正用于通话"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"最近在通话中使用过"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”正在使用"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”最近使用过"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”正在使用(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”最近使用过(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”正在使用(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”最近使用过(<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index e8ff970..9e408f2 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"輕按即可查看"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"儲存螢幕錄影時發生錯誤"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"開始錄影畫面時發生錯誤"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"以全螢幕檢視"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"如要退出,請從螢幕頂部向下滑動。"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"知道了"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"返回"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"首頁"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"選單"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"輸入"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"助聽器"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"助聽器"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"正在開啟…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自動旋轉"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"自動旋轉螢幕"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"上限為 <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"已暫停"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"當你分享、錄影或投放時,Android 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此,請謹慎處理密碼、付款資料、訊息、相片、音訊和影片等。"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"當你分享、錄影或投放應用程式時,Android 可存取顯示在該應用程式中顯示或播放的所有內容。因此,請謹慎處理密碼、付款資料、訊息、相片、音訊和影片等。"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"開始"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"切換應用程式時,系統會暫停分享"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"改為分享此應用程式"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"切換回先前的應用程式"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"切換應用程式"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"已被你的 IT 管理員封鎖"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"螢幕截圖功能因裝置政策而停用"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"放大成個畫面"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"放大部分螢幕畫面"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"開啟放大設定"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"關閉放大設定"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"結束編輯模式"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"拖曳角落即可調整大小"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"允許斜角捲動"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"調整大小"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"讀取電池計量器時發生問題"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"輕按即可瞭解詳情"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"未設定鬧鐘"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"輸入螢幕鎖定憑證"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"指紋感應器"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"驗證"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"進入裝置"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"「Google 助理」正在聆聽"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# 則通知}other{# 則通知}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>,<xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"做筆記"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"做筆記"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"做筆記,<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"廣播"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"要停止廣播「<xliff:g id="APP_NAME">%1$s</xliff:g>」的內容嗎?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"如要廣播「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容或變更輸出來源,系統就會停止廣播目前的內容"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"開啟「<xliff:g id="APPNAME">%1$s</xliff:g>」"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"如要將「錢包」應用程式新增為捷徑,請確認已安裝該應用程式"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"如要將「錢包」應用程式新增為捷徑,請確認已新增至少一張付款卡"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"如要將 QR 碼掃瞄器新增為捷徑,請確認已安裝相機應用程式"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"如要將 Home 應用程式新增為捷徑,請確認已安裝該應用程式"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• 至少一部裝置可用"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"選取預設的筆記應用程式,即可使用筆記捷徑"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"「Google 助理」感應功能已開啟"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在「設定」中指定預設筆記應用程式"</string>
     <string name="install_app" msgid="5066668100199613936">"安裝應用程式"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"麥克風和相機"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"近期應用程式使用情況"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"查看近期存取記錄"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"完成"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"展開並顯示選項"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"收合"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"關閉此應用程式"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"<xliff:g id="APP_NAME">%1$s</xliff:g> 已關閉"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"管理服務"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"管理存取權"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"手機通話正在使用"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"手機通話最近使用過此權限"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"<xliff:g id="APP_NAME">%1$s</xliff:g> 正在使用"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」最近使用過此權限"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"<xliff:g id="APP_NAME">%1$s</xliff:g> 正在使用 (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」最近使用過此權限 (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"<xliff:g id="APP_NAME">%1$s</xliff:g> 正在使用 (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」最近使用過此權限 (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 243d3da..5831ac5 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"輕觸即可查看"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"儲存螢幕錄影內容時發生錯誤"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"開始錄製螢幕畫面時發生錯誤"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"以全螢幕檢視"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"如要退出,請從螢幕頂端向下滑動。"</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"我知道了"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"返回"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"主畫面"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"選單"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"音訊"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"耳機"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"輸入"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"助聽器"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"助聽器"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"開啟中…"</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"自動旋轉"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"自動旋轉螢幕"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"上限為 <xliff:g id="DATA_LIMIT">%s</xliff:g>"</string>
     <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_work_mode_paused_state" msgid="6681788236383735976">"已暫停"</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>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"當你分享、錄製或投放內容時,Android 將可存取畫面上顯示的任何資訊或裝置播放的任何內容。因此,請謹慎處理密碼、付款資料、訊息、相片和影音內容等資訊。"</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"當你分享、錄製或投放內容時,Android 可存取應用程式中顯示的任何資訊或播放的任何內容。因此,請謹慎處理密碼、付款資料、訊息、相片和影音內容等資訊。"</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"開始"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"切換應用程式時暫停分享"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"改為分享這個應用程式"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"切回"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"應用程式切換"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 管理員已封鎖這項操作"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"根據裝置政策規定,螢幕畫面擷取功能已停用"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
@@ -856,6 +864,8 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"放大整個螢幕畫面"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"放大局部螢幕畫面"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"開啟放大功能設定"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"關閉放大設定"</string>
+    <string name="magnification_exit_edit_mode_click_label" msgid="1664818325144887117">"結束編輯模式"</string>
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"拖曳角落即可調整大小"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"允許沿對角線捲動"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"調整大小"</string>
@@ -1043,6 +1053,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"讀取電池計量器時發生問題"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"輕觸即可瞭解詳情"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"未設定鬧鐘"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"輸入螢幕鎖定憑證"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"指紋感應器"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"驗證"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"進入裝置"</string>
@@ -1105,7 +1116,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"Google 助理正在聆聽"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# 則通知}other{# 則通知}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>,<xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"做筆記"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"做筆記"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"做筆記,<xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"廣播"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"要停止播送「<xliff:g id="APP_NAME">%1$s</xliff:g>」的內容嗎?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"如果播送「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容或變更輸出來源,系統就會停止播送目前的內容"</string>
@@ -1123,7 +1135,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"開啟「<xliff:g id="APPNAME">%1$s</xliff:g>」"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"如要將錢包應用程式新增為捷徑,請確認已安裝該應用程式"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"如要將錢包應用程式新增為捷徑,請確認已新增至少一張卡片"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"如要將 QR code 掃描器新增為捷徑,請確認已安裝相機應用程式"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"如要將 Google Home 應用程式新增為捷徑,請確認已安裝該應用程式"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• 至少要有一部可用裝置"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"選取預設的記事應用程式,即可使用筆記捷徑"</string>
@@ -1156,4 +1167,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Google 助理感知功能已開啟"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在「設定」中指定預設記事應用程式"</string>
     <string name="install_app" msgid="5066668100199613936">"安裝應用程式"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"麥克風和相機"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"最近曾使用感應器的應用程式"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"查看近期存取記錄"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"完成"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"展開並顯示選項"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"收合"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"關閉這個應用程式"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」已關閉"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"管理服務"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"管理存取權"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"正用於手機通話"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"最近曾用於手機通話"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"正由「<xliff:g id="APP_NAME">%1$s</xliff:g>」使用"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」最近用過這項權限"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"正由「<xliff:g id="APP_NAME">%1$s</xliff:g>」使用 (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」最近用過這項權限 (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"正由「<xliff:g id="APP_NAME">%1$s</xliff:g>」使用 (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」最近用過這項權限 (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 25b162d..5013f8d 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -118,6 +118,9 @@
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Thepha ukuze ubuke"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Iphutha lokulondoloza okokuqopha iskrini"</string>
     <string name="screenrecord_start_error" msgid="2200660692479682368">"Iphutha lokuqala ukurekhoda isikrini"</string>
+    <string name="immersive_cling_title" msgid="8372056499315585941">"Ukubuka isikrini esigcwele"</string>
+    <string name="immersive_cling_description" msgid="6913958856085185775">"Ukuze uphume, swayiphela phansi kusuka phezulu."</string>
+    <string name="immersive_cling_positive" msgid="3076681691468978568">"Ngiyezwa"</string>
     <string name="accessibility_back" msgid="6530104400086152611">"Emuva"</string>
     <string name="accessibility_home" msgid="5430449841237966217">"Ekhaya"</string>
     <string name="accessibility_menu" msgid="2701163794470513040">"Imenyu"</string>
@@ -245,7 +248,7 @@
     <string name="quick_settings_bluetooth_secondary_label_audio" msgid="780333390310051161">"Umsindo"</string>
     <string name="quick_settings_bluetooth_secondary_label_headset" msgid="2332093067553000852">"Ihedisethi"</string>
     <string name="quick_settings_bluetooth_secondary_label_input" msgid="3887552721233148132">"Okokufaka"</string>
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="3003338571871392293">"Izinsiza zokuzwa"</string>
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids" msgid="5553051568867097111">"Imishini yendlebe"</string>
     <string name="quick_settings_bluetooth_secondary_label_transient" msgid="3882884317600669650">"Iyavula..."</string>
     <string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Ukuphenduka okuzenzakalelayo"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Phendula iskrini ngokuzenzakalela"</string>
@@ -292,6 +295,7 @@
     <string name="quick_settings_cellular_detail_data_limit" msgid="1791389609409211628">"<xliff:g id="DATA_LIMIT">%s</xliff:g> umkhawulo"</string>
     <string name="quick_settings_cellular_detail_data_warning" msgid="7957253810481086455">"<xliff:g id="DATA_LIMIT">%s</xliff:g> isexwayiso"</string>
     <string name="quick_settings_work_mode_label" msgid="6440531507319809121">"Ama-app omsebenzi"</string>
+    <string name="quick_settings_work_mode_paused_state" msgid="6681788236383735976">"Kumisiwe"</string>
     <string name="quick_settings_night_display_label" msgid="8180030659141778180">"Ukukhanya kwasebusuku"</string>
     <string name="quick_settings_night_secondary_label_on_at_sunset" msgid="3358706312129866626">"Kuvulwe ekushoneni kwelanga"</string>
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"Kuze kube sekuphumeni kwelanga"</string>
@@ -415,6 +419,10 @@
     <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Uma wabelana, ukurekhoda, noma ukusakaza, i-Android inokufinyelela kunoma yini ebonakala esikrinini sakho noma okudlalwayo kudivayisi yakho. Ngakho-ke qaphela ngezinto ezifana namaphasiwedi, imininingwane yokukhokha, imilayezo, izithombe, nomsindo nevidiyo."</string>
     <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Uma wabelana, ukurekhoda, noma ukusakaza ku-app, i-Android inokufinyelela kunoma yini eboniswayo noma edlalwa kuleyo app. Ngakho-ke qaphela ngezinto ezfana namaphasiwedi, imininingwane yokukhokha, imilayezo, izithombe, nomsindo nevidiyo."</string>
     <string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Qala"</string>
+    <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Ukwabelana kuyaphumula uma ushintsha ama-app"</string>
+    <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Yabelana ngale-app esikhundleni salokho"</string>
+    <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Shintshela emuva"</string>
+    <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Ukushintsha i-app"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Kuvinjelwe ngumlawuli wakho we-IT"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ukuthwebula isikrini kukhutshazwe yinqubomgomo yedivayisi"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Sula konke"</string>
@@ -856,6 +864,9 @@
     <string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Khulisa isikrini esigcwele"</string>
     <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Khulisa ingxenye eyesikrini"</string>
     <string name="magnification_open_settings_click_label" msgid="6151849212725923363">"Vula amasethingi okukhuliswa"</string>
+    <string name="magnification_close_settings_click_label" msgid="4642477260651704517">"Vala amasethingi okukhuliswa"</string>
+    <!-- no translation found for magnification_exit_edit_mode_click_label (1664818325144887117) -->
+    <skip />
     <string name="magnification_drag_corner_to_resize" msgid="1249766311052418130">"Hudula ikhona ukuze usayize kabusha"</string>
     <string name="accessibility_allow_diagonal_scrolling" msgid="3258050349191496398">"Vumela ukuskrola oku-diagonal"</string>
     <string name="accessibility_resize" msgid="5733759136600611551">"Shintsha usayizi"</string>
@@ -1043,6 +1054,7 @@
     <string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Kube khona inkinga ngokufunda imitha yakho yebhethri"</string>
     <string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Thepha ukuze uthole olunye ulwazi"</string>
     <string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Akukho alamu esethiwe"</string>
+    <string name="accessibility_bouncer" msgid="5896923685673320070">"faka ukukhiya isikrini"</string>
     <string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Inzwa yesigxivizo somunwe"</string>
     <string name="accessibility_authenticate_hint" msgid="798914151813205721">"gunyaza"</string>
     <string name="accessibility_enter_hint" msgid="2617864063504824834">"faka idivayisi"</string>
@@ -1105,7 +1117,8 @@
     <string name="dream_overlay_status_bar_assistant_attention_indicator" msgid="4712565923771372690">"I-Assistant ilalele"</string>
     <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{Isaziso esingu-#}one{Izaziso ezingu-#}other{Izaziso ezingu-#}}"</string>
     <string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
-    <string name="note_task_button_label" msgid="8718616095800343136">"Ukuthatha amanothi"</string>
+    <string name="note_task_button_label" msgid="230135078402003532">"Ukuthatha amanothi"</string>
+    <string name="note_task_shortcut_long_label" msgid="7729325091147319409">"Ukuthatha amanothi, <xliff:g id="NOTE_TAKING_APP">%1$s</xliff:g>"</string>
     <string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"Ukusakaza"</string>
     <string name="bt_le_audio_broadcast_dialog_title" msgid="3605428497924077811">"Misa ukusakaza i-<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
     <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="7889684551194225793">"Uma usakaza i-<xliff:g id="SWITCHAPP">%1$s</xliff:g> noma ushintsha okuphumayo, ukusakaza kwakho kwamanje kuzoma"</string>
@@ -1123,7 +1136,6 @@
     <string name="keyguard_affordance_enablement_dialog_action_template" msgid="8164857863036314664">"Vula i-<xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="wallet_quick_affordance_unavailable_install_the_app" msgid="7298552910007208368">"Ukuze ungeze i-app ye-Wallet njengesinqamuleli, qinisekisa ukuthi i-app ifakiwe"</string>
     <string name="wallet_quick_affordance_unavailable_configure_the_app" msgid="4387433357429873258">"Ukuze ungeze i-app ye-Wallet njengesinqamuleli, qinisekisa ukuthi okungenani ikhadi elilodwa lingeziwe"</string>
-    <string name="qr_scanner_quick_affordance_unavailable_explanation" msgid="3049582306241150946">"Ukuze ungeze Iskena sekhodi ye-QR njengesinqamuleli, qinisekisa ukuthi i-app yekhamera ifakiwe"</string>
     <string name="home_quick_affordance_unavailable_install_the_app" msgid="6187820778998446168">"Ukuze ungeze i-App yasekhaya njengesinqamuleli, qinisekisa ukuthi i-app ifakiwe"</string>
     <string name="home_quick_affordance_unavailable_configure_the_app" msgid="7953595390775156839">"• Okungenani idivayisi eyodwa iyatholakala"</string>
     <string name="notes_app_quick_affordance_unavailable_explanation" msgid="4796955161600178530">"Khetha i-app yamanothi azenzakalelayo ukuze usebenzise isinqamuleli sokubhala amanothi"</string>
@@ -1156,4 +1168,22 @@
     <string name="assistant_attention_content_description" msgid="6830215897604642875">"Ukunaka kwe-Assistant kuvuliwe"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setha i-app yamanothi azenzakalelayo Kumsethingi"</string>
     <string name="install_app" msgid="5066668100199613936">"Faka i-app"</string>
+    <string name="privacy_dialog_title" msgid="7839968133469098311">"Imakrofoni Nekhamera"</string>
+    <string name="privacy_dialog_summary" msgid="2458769652125995409">"Ukusetshenziswa kwakamuva kwe-app"</string>
+    <string name="privacy_dialog_more_button" msgid="7610604080293562345">"Bona ukufinyelela kwakamuva"</string>
+    <string name="privacy_dialog_done_button" msgid="4504330708531434263">"Kwenziwe"</string>
+    <string name="privacy_dialog_expand_action" msgid="9129262348628331377">"Nweba futhi ubonise izinketho"</string>
+    <string name="privacy_dialog_collapse_action" msgid="277419962019466347">"Goqa"</string>
+    <string name="privacy_dialog_close_app_button" msgid="8006250171305878606">"Vala le-app"</string>
+    <string name="privacy_dialog_close_app_message" msgid="1316408652526310985">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> ivaliwe"</string>
+    <string name="privacy_dialog_manage_service" msgid="8320590856621823604">"Phatha isevisi"</string>
+    <string name="privacy_dialog_manage_permissions" msgid="2543451567190470413">"Phatha ukufinyelela"</string>
+    <string name="privacy_dialog_active_call_usage" msgid="7858746847946397562">"Isetshenziswa ngekholi yefoni"</string>
+    <string name="privacy_dialog_recent_call_usage" msgid="1214810644978167344">"Kusetshenziswe kamuva kwikholi yefoni"</string>
+    <string name="privacy_dialog_active_app_usage" msgid="631997836335929880">"Isetshenziswa yi-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_recent_app_usage" msgid="4883417856848222450">"Kusetshenziswe kamuva yi-<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+    <string name="privacy_dialog_active_app_usage_1" msgid="9047570143069220911">"Isetshenziswa yi-<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_1" msgid="2551340497722370109">"Kusetshenziswe kamuva yi-<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>)"</string>
+    <string name="privacy_dialog_active_app_usage_2" msgid="2770926061339921767">"Isetshenziswa yi-<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
+    <string name="privacy_dialog_recent_app_usage_2" msgid="2874689735085367167">"Kusetshenziswe kamuva yi-<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
 </resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index bd86e51..d693631 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -118,8 +118,25 @@
     <attr name="wallpaperTextColorSecondary" format="reference|color" />
     <attr name="wallpaperTextColorAccent" format="reference|color" />
     <attr name="backgroundProtectedStyle" format="reference" />
-    <attr name="offStateColor" format="reference|color" />
-    <attr name="underSurfaceColor" format="reference|color" />
+
+    <!-- color attribute tokens for QS -->
+    <attr name="isQsTheme" format="boolean" />
+    <attr name="underSurface" format="reference|color"/>
+    <attr name="shadeActive" format="reference|color" />
+    <attr name="onShadeActive" format="reference|color" />
+    <attr name="onShadeActiveVariant" format="reference|color" />
+    <attr name="shadeInactive" format="reference|color" />
+    <attr name="onShadeInactive" format="reference|color" />
+    <attr name="onShadeInactiveVariant" format="reference|color" />
+    <attr name="shadeDisabled" format="reference|color" />
+    <attr name="surfaceBright" format="reference|color" />
+    <attr name="scHigh" format="reference|color" />
+    <attr name="tertiary" format="reference|color" />
+    <attr name="onSurface" format="reference|color" />
+    <attr name="onSurfaceVariant" format="reference|color" />
+    <attr name="outline" format="reference|color" />
+    <attr name="primary" format="reference|color" />
+
 
     <declare-styleable name="SmartReplyView">
         <attr name="spacing" format="dimension" />
@@ -216,6 +233,8 @@
         <attr name="progress" format="integer" />
         <attr name="iconStartContentDescription" format="reference" />
         <attr name="iconEndContentDescription" format="reference" />
+        <attr name="tickMark" format="reference" />
+        <attr name="seekBarChangeMagnitude" format="integer" />
     </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/bools.xml b/packages/SystemUI/res/values/bools.xml
index 04fc4b8..405a59f 100644
--- a/packages/SystemUI/res/values/bools.xml
+++ b/packages/SystemUI/res/values/bools.xml
@@ -30,4 +30,25 @@
 
     <!-- Whether to enable transparent background for notification scrims -->
     <bool name="notification_scrim_transparent">false</bool>
+
+    <!-- Control whether to force apps to give up control over the display of system bars at all
+     times regardless of System Ui Flags.
+     In the Automotive case, this is helpful if there's a requirement for an UI element to be on
+     screen at all times. Setting this to true also gives System UI the ability to override the
+     visibility controls for the system through the usage of the
+     "SYSTEM_BAR_VISIBILITY_OVERRIDE" setting.
+     Ex: Only setting the config to true will force show system bars for the entire system.
+     Ex: Setting the config to true and the "SYSTEM_BAR_VISIBILITY_OVERRIDE" setting to
+     "immersive.status=apps" will force show navigation bar for all apps and force hide status
+     bar for all apps. -->
+    <bool name="config_remoteInsetsControllerControlsSystemBars">false</bool>
+
+    <!-- Control whether the system bars can be requested when using a remote insets control target.
+         This allows for specifying whether or not system bars can be shown by the user (via swipe
+         or other means) when they are hidden by the logic defined by the remote insets controller.
+         This is useful for cases where the system provides alternative affordances for showing and
+         hiding the bars or for cases in which it's desired the bars not be shown for any reason.
+         This configuration will only apply when config_remoteInsetsControllerControlsSystemBars.
+         is set to true. -->
+    <bool name="config_remoteInsetsControllerSystemBarsCanBeShownByUserAction">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index db7eb7a..e942258 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -16,7 +16,8 @@
  * limitations under the License.
  */
 -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
     <drawable name="notification_number_text_color">#ffffffff</drawable>
     <drawable name="system_bar_background">@color/system_bar_background_opaque</drawable>
     <color name="system_bar_background_opaque">#ff000000</color>
@@ -25,6 +26,8 @@
     <color name="qs_detail_button_white">#B3FFFFFF</color><!-- 70% white -->
     <color name="status_bar_clock_color">#FFFFFFFF</color>
     <color name="qs_tile_disabled_color">#9E9E9E</color> <!-- 38% black -->
+    <color name="status_bar_icons_hover_color_light">#38FFFFFF</color> <!-- 22% white -->
+    <color name="status_bar_icons_hover_color_dark">#38000000</color> <!-- 22% black -->
 
     <!-- The color of the background in the separated list of the Global Actions menu -->
     <color name="global_actions_separated_background">#F5F5F5</color>
@@ -192,7 +195,7 @@
     <color name="control_primary_text">#E6FFFFFF</color>
     <color name="control_secondary_text">#99FFFFFF</color>
     <color name="control_default_foreground">@color/GM2_grey_500</color>
-    <color name="control_default_background">@color/GM2_grey_900</color>
+    <color name="control_default_background">#303134</color>
     <color name="control_spinner_dropdown">@*android:color/foreground_material_dark</color>
     <color name="control_more_vert">@*android:color/foreground_material_dark</color>
     <color name="control_enabled_light_background">#413C2D</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 166bd2a..4aad6e7 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -302,9 +302,6 @@
     <!-- Determines whether the shell features all run on another thread. -->
     <bool name="config_enableShellMainThread">true</bool>
 
-    <!-- SystemUIFactory component -->
-    <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIInitializerImpl</string>
-
     <!-- QS tile shape store width. negative implies fill configuration instead of stroke-->
     <dimen name="config_qsTileStrokeWidthActive">-1dp</dimen>
     <dimen name="config_qsTileStrokeWidthInactive">-1dp</dimen>
@@ -684,7 +681,6 @@
         <item>6</item> <!-- WAKE_REASON_WAKE_KEY -->
         <item>7</item> <!-- WAKE_REASON_WAKE_MOTION -->
         <item>9</item> <!-- WAKE_REASON_LID -->
-        <item>10</item> <!-- WAKE_REASON_DISPLAY_GROUP_ADDED -->
         <item>12</item> <!-- WAKE_REASON_UNFOLD_DEVICE -->
         <item>15</item> <!-- WAKE_REASON_TAP -->
         <item>16</item> <!-- WAKE_REASON_LIFT -->
@@ -701,6 +697,10 @@
     -->
     <integer name="config_face_auth_supported_posture">0</integer>
 
+    <!-- Components to allow running fingerprint listening if their activity is occluding the lock screen. -->
+    <string-array name="config_fingerprint_listen_on_occluding_activity_packages" translatable="false">
+    </string-array>
+
     <!-- Whether the communal service should be enabled -->
     <bool name="config_communalServiceEnabled">false</bool>
 
@@ -774,6 +774,9 @@
     <!-- Flag to enable privacy dot views, it shall be true for normal case -->
     <bool name="config_enablePrivacyDot">true</bool>
 
+    <!-- Flag to enable privacy chip animation, it shall be true for normal case -->
+    <bool name="config_enablePrivacyChipAnimation">true</bool>
+
     <!-- Class for the communal source connector to be used -->
     <string name="config_communalSourceConnector" translatable="false"></string>
 
@@ -914,4 +917,7 @@
     "$packageName" part that will be replaced by the code with the package name of the target app.
     -->
     <string name="config_appStoreAppLinkTemplate" translatable="false"></string>
+
+    <!-- Flag controlling whether visual query attention detection has been enabled. -->
+    <bool name="config_enableVisualQueryAttentionDetection">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 44a5c07..3ada7c2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -121,24 +121,29 @@
     <dimen name="navigation_edge_cancelled_arrow_height">0dp</dimen>
     <dimen name="navigation_edge_cancelled_edge_corners">6dp</dimen>
 
-    <!-- Height of notification icons in the status bar -->
+    <!-- Height of the system icons container view in the status bar -->
+    <dimen name="status_bar_system_icons_height">@dimen/status_bar_icon_size_sp</dimen>
+
+    <!-- New sp height of notification icons in the status bar -->
+    <dimen name="status_bar_icon_size_sp">@*android:dimen/status_bar_icon_size_sp</dimen>
+    <!-- Original dp height of notification icons in the status bar -->
     <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
 
     <!-- Default horizontal drawable padding for status bar icons. -->
-    <dimen name="status_bar_horizontal_padding">2.5dp</dimen>
+    <dimen name="status_bar_horizontal_padding">2.5sp</dimen>
 
     <!-- Height of the battery icon in the status bar. -->
-    <dimen name="status_bar_battery_icon_height">13.0dp</dimen>
+    <dimen name="status_bar_battery_icon_height">13.0sp</dimen>
 
     <!-- Width of the battery icon in the status bar. The battery drawable assumes a 12x20 canvas,
-    so the width of the icon should be 13.0dp * (12.0 / 20.0) -->
-    <dimen name="status_bar_battery_icon_width">7.8dp</dimen>
+    so the width of the icon should be 13.0sp * (12.0 / 20.0) -->
+    <dimen name="status_bar_battery_icon_width">7.8sp</dimen>
 
-    <!-- The battery icon is 13dp tall, but the other system icons are 15dp tall (see
+    <!-- The battery icon is 13sp tall, but the other system icons are 15sp tall (see
          @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in
          the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its
          bottom still aligns with the bottom of all the other system icons. See b/258672854. -->
-    <dimen name="status_bar_battery_extra_vertical_spacing">1dp</dimen>
+    <dimen name="status_bar_battery_extra_vertical_spacing">1sp</dimen>
 
     <!-- The font size for the clock in the status bar. -->
     <dimen name="status_bar_clock_size">14sp</dimen>
@@ -153,19 +158,34 @@
     <dimen name="status_bar_left_clock_starting_padding">0dp</dimen>
 
     <!-- End padding for left-aligned status bar clock -->
-    <dimen name="status_bar_left_clock_end_padding">2dp</dimen>
+    <dimen name="status_bar_left_clock_end_padding">2sp</dimen>
 
     <!-- Spacing after the wifi signals that is present if there are any icons following it. -->
-    <dimen name="status_bar_wifi_signal_spacer_width">2.5dp</dimen>
+    <dimen name="status_bar_wifi_signal_spacer_width">2.5sp</dimen>
 
-    <!-- Size of the view displaying the wifi signal icon in the status bar. -->
-    <dimen name="status_bar_wifi_signal_size">@*android:dimen/status_bar_system_icon_size</dimen>
+    <!-- Size of the view displaying the wifi inout icon in the status bar. -->
+    <dimen name="status_bar_wifi_inout_container_size">17sp</dimen>
+    <!-- Size of the view displaying the wifi signal icon in the status bar. This value should
+        match the core/status_bar_system_icon_size and change to sp unit -->
+    <dimen name="status_bar_wifi_signal_size">15sp</dimen>
+
+    <!-- Size of the view displaying the mobile inout icon in the status bar. -->
+    <dimen name="status_bar_mobile_inout_container_size">17sp</dimen>
+    <!-- Size of the view displaying the mobile signal icon in the status bar. This value should
+        match the core/status_bar_system_icon_size and change to sp unit -->
+    <dimen name="status_bar_mobile_signal_size">15sp</dimen>
+    <!-- Size of the view displaying the mobile signal icon in the status bar. This value should
+        match the viewport height of mobile signal drawables such as ic_lte_mobiledata -->
+    <dimen name="status_bar_mobile_type_size">16sp</dimen>
+    <!-- Size of the view displaying the mobile roam icon in the status bar. This value should
+        match the viewport size of drawable stat_sys_roaming -->
+    <dimen name="status_bar_mobile_roam_size">8sp</dimen>
 
     <!-- Spacing before the airplane mode icon if there are any icons preceding it. -->
-    <dimen name="status_bar_airplane_spacer_width">4dp</dimen>
+    <dimen name="status_bar_airplane_spacer_width">4sp</dimen>
 
     <!-- Spacing between system icons. -->
-    <dimen name="status_bar_system_icon_spacing">0dp</dimen>
+    <dimen name="status_bar_system_icon_spacing">0sp</dimen>
 
     <!-- The amount to scale each of the status bar icons by. A value of 1 means no scaling. -->
     <item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item>
@@ -235,6 +255,9 @@
          and the notification won't use this much, but is measured with wrap_content -->
     <dimen name="notification_messaging_actions_min_height">196dp</dimen>
 
+    <!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
+    <dimen name="immersive_mode_cling_width">-1px</dimen>
+
     <!-- a threshold in dp per second that is considered fast scrolling -->
     <dimen name="scroll_fast_threshold">1500dp</dimen>
 
@@ -275,6 +298,9 @@
     <!-- The padding at start and end of indication text shown on AOD -->
     <dimen name="keyguard_indication_text_padding">16dp</dimen>
 
+    <!-- The min height on the indication text shown on AOD -->
+    <dimen name="keyguard_indication_text_min_height">48dp</dimen>
+
     <!-- Shadows under the clock, date and other keyguard text fields -->
     <dimen name="keyguard_shadow_radius">5</dimen>
 
@@ -323,23 +349,31 @@
     <!-- opacity at which Notification icons will be drawn in the status bar -->
     <item type="dimen" name="status_bar_icon_drawing_alpha">90%</item>
 
+    <!-- paddings for container with status icons and battery -->
+    <!-- padding start is a bit smaller than end to account for status icon margin-->
+    <dimen name="status_bar_icons_padding_start">3dp</dimen>
+
+    <dimen name="status_bar_icons_padding_end">4dp</dimen>
+    <dimen name="status_bar_icons_padding_bottom">0dp</dimen>
+    <dimen name="status_bar_icons_padding_top">0dp</dimen>
+
     <!-- gap on either side of status bar notification icons -->
-    <dimen name="status_bar_icon_padding">0dp</dimen>
+    <dimen name="status_bar_icon_horizontal_margin">0sp</dimen>
 
     <!-- the padding on the start of the statusbar -->
     <dimen name="status_bar_padding_start">8dp</dimen>
 
     <!-- the padding on the end of the statusbar -->
-    <dimen name="status_bar_padding_end">8dp</dimen>
+    <dimen name="status_bar_padding_end">4dp</dimen>
 
     <!-- the padding on the top of the statusbar (usually 0) -->
     <dimen name="status_bar_padding_top">0dp</dimen>
 
     <!-- the radius of the overflow dot in the status bar -->
-    <dimen name="overflow_dot_radius">2dp</dimen>
+    <dimen name="overflow_dot_radius">2sp</dimen>
 
     <!-- the padding between dots in the icon overflow -->
-    <dimen name="overflow_icon_dot_padding">3dp</dimen>
+    <dimen name="overflow_icon_dot_padding">3sp</dimen>
 
     <!-- Dimensions related to screenshots -->
 
@@ -452,6 +486,8 @@
     <!-- Margin start of the system icons super container -->
     <dimen name="system_icons_super_container_margin_start">16dp</dimen>
 
+    <dimen name="status_icons_hover_state_background_radius">16dp</dimen>
+
     <!-- Width for the notification panel and related windows -->
     <dimen name="match_parent">-1px</dimen>
 
@@ -459,6 +495,11 @@
     <dimen name="large_screen_shade_header_height">48dp</dimen>
     <dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen>
     <dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen>
+    <dimen name="shade_header_system_icons_height">@dimen/large_screen_shade_header_min_height</dimen>
+    <dimen name="shade_header_system_icons_padding_start">0dp</dimen>
+    <dimen name="shade_header_system_icons_padding_end">0dp</dimen>
+    <dimen name="shade_header_system_icons_padding_top">0dp</dimen>
+    <dimen name="shade_header_system_icons_padding_bottom">0dp</dimen>
 
     <!-- The top margin of the panel that holds the list of notifications.
          On phones it's always 0dp but it's overridden in Car UI
@@ -636,9 +677,6 @@
 
     <dimen name="restricted_padlock_pading">4dp</dimen>
 
-    <!-- How far the expanded QS panel peeks from the header in collapsed state. -->
-    <dimen name="qs_peek_height">0dp</dimen>
-
     <!-- Padding between subtitles and the following text in the QSFooter dialog -->
     <dimen name="qs_footer_dialog_subtitle_padding">20dp</dimen>
 
@@ -720,8 +758,6 @@
     <dimen name="keyguard_clock_switch_y_shift">14dp</dimen>
     <!-- When large clock is showing, offset the smartspace by this amount -->
     <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
-    <!-- With the large clock, move up slightly from the center -->
-    <dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
 
     <dimen name="notification_scrim_corner_radius">32dp</dimen>
 
@@ -799,6 +835,12 @@
     <dimen name="keyguard_affordance_fixed_radius">24dp</dimen>
     <dimen name="keyguard_affordance_fixed_padding">12dp</dimen>
 
+    <!--  The width/height/padding of the keyguard settings popup menu  -->
+    <dimen name="keyguard_settings_popup_menu_icon_height">24dp</dimen>
+    <dimen name="keyguard_settings_popup_menu_icon_width">24dp</dimen>
+    <dimen name="keyguard_settings_popup_menu_icon_end_margin">8dp</dimen>
+    <dimen name="keyguard_settings_popup_menu_padding">12dp</dimen>
+
     <!-- Amount the button should shake when it's not long-pressed for long enough. -->
     <dimen name="keyguard_affordance_shake_amplitude">8dp</dimen>
 
@@ -827,7 +869,7 @@
 
     <!-- Padding between the mobile signal indicator and the start icon when the roaming icon
          is displayed in the upper left corner. -->
-    <dimen name="roaming_icon_start_padding">2dp</dimen>
+    <dimen name="roaming_icon_start_padding">2sp</dimen>
 
     <!-- Extra padding between the mobile data type icon and the strength indicator when the data
          type icon is wide for the tile in quick settings. -->
@@ -1053,7 +1095,7 @@
     <!-- Margin between icons of Ongoing App Ops chip -->
     <dimen name="ongoing_appops_chip_icon_margin">4dp</dimen>
     <!-- Icon size of Ongoing App Ops chip -->
-    <dimen name="ongoing_appops_chip_icon_size">16dp</dimen>
+    <dimen name="ongoing_appops_chip_icon_size">16sp</dimen>
     <!-- Radius of Ongoing App Ops chip corners -->
     <dimen name="ongoing_appops_chip_bg_corner_radius">28dp</dimen>
     <!--  One or two privacy items  -->
@@ -1075,6 +1117,9 @@
 
     <dimen name="ongoing_appops_dialog_side_padding">16dp</dimen>
 
+    <dimen name="privacy_dialog_background_radius_large">12dp</dimen>
+    <dimen name="privacy_dialog_background_radius_small">4dp</dimen>
+
     <!-- Size of media cards in the QSPanel carousel -->
     <dimen name="qs_media_padding">16dp</dimen>
     <dimen name="qs_media_album_radius">14dp</dimen>
@@ -1205,6 +1250,7 @@
     <dimen name="controls_header_app_icon_size">24dp</dimen>
     <dimen name="controls_top_margin">48dp</dimen>
     <dimen name="controls_content_margin_horizontal">0dp</dimen>
+    <dimen name="controls_content_padding">16dp</dimen>
     <dimen name="control_header_text_size">24sp</dimen>
     <dimen name="control_item_text_size">14sp</dimen>
     <dimen name="control_menu_item_text_size">16sp</dimen>
@@ -1223,6 +1269,8 @@
     <dimen name="control_chevron_icon_size">20dp</dimen>
     <dimen name="control_spacing">8dp</dimen>
     <dimen name="control_list_divider">1dp</dimen>
+    <dimen name="control_list_vertical_spacing">8dp</dimen>
+    <dimen name="control_list_horizontal_spacing">12dp</dimen>
     <dimen name="control_corner_radius">14dp</dimen>
     <dimen name="control_height">104dp</dimen>
     <dimen name="control_padding">12dp</dimen>
@@ -1324,6 +1372,8 @@
     <dimen name="media_output_dialog_default_margin_end">16dp</dimen>
     <dimen name="media_output_dialog_selectable_margin_end">80dp</dimen>
     <dimen name="media_output_dialog_list_padding_top">8dp</dimen>
+    <dimen name="media_output_dialog_icon_left_radius">28dp</dimen>
+    <dimen name="media_output_dialog_icon_right_radius">0dp</dimen>
 
     <!-- Distance that the full shade transition takes in order to complete by tapping on a button
          like "expand". -->
@@ -1557,7 +1607,8 @@
 
     <!-- Status bar user chip -->
     <dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
-    <dimen name="status_bar_user_chip_end_margin">12dp</dimen>
+    <!-- below also works as break between user chip and hover state of status icons -->
+    <dimen name="status_bar_user_chip_end_margin">8dp</dimen>
     <dimen name="status_bar_user_chip_text_size">12sp</dimen>
 
     <!-- System UI Dialog -->
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 261b08d..0d45422 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -39,7 +39,7 @@
     <bool name="flag_battery_shield_icon">false</bool>
 
     <!-- Whether face auth will immediately stop when the display state is OFF -->
-    <bool name="flag_stop_face_auth_on_display_off">false</bool>
+    <bool name="flag_stop_face_auth_on_display_off">true</bool>
 
     <!-- Whether we want to stop pulsing while running the face scanning animation -->
     <bool name="flag_stop_pulsing_face_scanning_animation">true</bool>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index e5c9461..d2cb475 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -206,5 +206,17 @@
 
     <!-- keyboard backlight indicator-->
     <item type="id" name="backlight_icon" />
-</resources>
 
+    <item type="id" name="keyguard_root_view" />
+    <item type="id" name="keyguard_indication_area" />
+    <item type="id" name="keyguard_indication_text" />
+    <item type="id" name="keyguard_indication_text_bottom" />
+    <item type="id" name="nssl_guideline" />
+    <item type="id" name="split_shade_guideline" />
+    <item type="id" name="lock_icon" />
+    <item type="id" name="lock_icon_bg" />
+
+    <!-- Privacy dialog -->
+    <item type="id" name="privacy_dialog_close_app_button" />
+    <item type="id" name="privacy_dialog_manage_app_button" />
+</resources>
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index 675ae32..c925b26 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -21,6 +21,8 @@
 
     <integer name="magnification_default_scale">2</integer>
 
+    <integer name="dock_enter_exit_duration">250</integer>
+
     <!-- The position of the volume dialog on the screen.
          See com.android.systemui.volume.VolumeDialogImpl.
          Value 21 corresponds to RIGHT|CENTER_VERTICAL. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ccc6f82..cddfda2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -300,6 +300,13 @@
     <!-- A toast message shown when the screen recording cannot be started due to a generic error [CHAR LIMIT=NONE] -->
     <string name="screenrecord_start_error">Error starting screen recording</string>
 
+    <!-- Cling help message title when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
+    <string name="immersive_cling_title">Viewing full screen</string>
+    <!-- Cling help message description when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
+    <string name="immersive_cling_description">To exit, swipe down from the top.</string>
+    <!-- Cling help message confirmation button when hiding the navigation bar entering immersive mode [CHAR LIMIT=30] -->
+    <string name="immersive_cling_positive">Got it</string>
+
     <!-- Content description of the back button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_back">Back</string>
     <!-- Content description of the home button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
@@ -617,8 +624,8 @@
     <string name="quick_settings_bluetooth_secondary_label_headset">Headset</string>
     <!-- QuickSettings: Bluetooth secondary label for an input/IO device being connected [CHAR LIMIT=20]-->
     <string name="quick_settings_bluetooth_secondary_label_input">Input</string>
-    <!-- QuickSettings: Bluetooth secondary label for a Hearing Aids device being connected [CHAR LIMIT=20]-->
-    <string name="quick_settings_bluetooth_secondary_label_hearing_aids">Hearing Aids</string>
+    <!-- QuickSettings: Bluetooth secondary label for a Hearing aids device being connected [CHAR LIMIT=20]-->
+    <string name="quick_settings_bluetooth_secondary_label_hearing_aids">Hearing aids</string>
     <!-- QuickSettings: Bluetooth secondary label shown when bluetooth is being enabled [CHAR LIMIT=NONE] -->
     <string name="quick_settings_bluetooth_secondary_label_transient">Turning on&#8230;</string>
     <!-- QuickSettings: Brightness [CHAR LIMIT=NONE] -->
@@ -740,6 +747,9 @@
     off. This means a separate profile on a user's phone that's specifically for their
     work apps and managed by their company. "Work" is used as an adjective. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_work_mode_label">Work apps</string>
+    <!-- QuickSettings: Subtitle for the Work profile Quick Settings tile when it's in the off
+    state. This corresponds to the work profile not being currently accessible. [CHAR LIMIT=20] -->
+    <string name="quick_settings_work_mode_paused_state">Paused</string>
     <!-- QuickSettings: Label for the toggle to activate Night display (renamed "Night Light" with title caps). [CHAR LIMIT=20] -->
     <string name="quick_settings_night_display_label">Night Light</string>
     <!-- QuickSettings: Secondary text for when the Night Light will be enabled at sunset. [CHAR LIMIT=20] -->
@@ -1113,6 +1123,16 @@
     <!-- System sharing media projection permission button to continue. [CHAR LIMIT=60] -->
     <string name="media_projection_entry_generic_permission_dialog_continue">Start</string>
 
+    <!-- Task switcher notification -->
+    <!-- Task switcher notification text. [CHAR LIMIT=100] -->
+    <string name="media_projection_task_switcher_text">Sharing pauses when you switch apps</string>
+    <!-- The action for switching to the foreground task. [CHAR LIMIT=40] -->
+    <string name="media_projection_task_switcher_action_switch">Share this app instead</string>
+    <!-- The action for switching back to the projected task. [CHAR LIMIT=40] -->
+    <string name="media_projection_task_switcher_action_back">Switch back</string>
+    <!-- Task switcher notification channel name. [CHAR LIMIT=40] -->
+    <string name="media_projection_task_switcher_notification_channel">App switch</string>
+
     <!-- Title for the dialog that is shown when screen capturing is disabled by enterprise policy. [CHAR LIMIT=100] -->
     <string name="screen_capturing_disabled_by_policy_dialog_title">Blocked by your IT admin</string>
 
@@ -2384,6 +2404,10 @@
     <string name="magnification_mode_switch_state_window">Magnify part of screen</string>
     <!-- Click action label for magnification settings panel. [CHAR LIMIT=NONE] -->
     <string name="magnification_open_settings_click_label">Open magnification settings</string>
+    <!-- Click action label for magnification settings panel. [CHAR LIMIT=NONE] -->
+    <string name="magnification_close_settings_click_label">Close magnification settings</string>
+    <!-- Click action label for exiting magnifier edit mode. [CHAR LIMIT=NONE] -->
+    <string name="magnification_exit_edit_mode_click_label">Exit edit mode</string>
     <!-- Label of the corner of a rectangle that you can tap and drag to resize the magnification area. [CHAR LIMIT=NONE] -->
     <string name="magnification_drag_corner_to_resize">Drag corner to resize</string>
 
@@ -2812,6 +2836,8 @@
     <!-- Secondary label for alarm tile when there is no next alarm information [CHAR LIMIT=20] -->
     <string name="qs_alarm_tile_no_alarm">No alarm set</string>
 
+    <!-- Accessibility label for a11y action to show the bouncer (pin/pattern/password) screen lock [CHAR LIMIT=NONE] -->
+    <string name="accessibility_bouncer">enter screen lock</string>
     <!-- Accessibility label for fingerprint sensor [CHAR LIMIT=NONE] -->
     <string name="accessibility_fingerprint_label">Fingerprint sensor</string>
     <!-- Accessibility action for tapping on an affordance that will bring up the user's
@@ -2968,9 +2994,11 @@
         <xliff:g id="weather_condition" example="Partly cloudy">%1$s</xliff:g>, <xliff:g id="temperature" example="7°C">%2$s</xliff:g>
     </string>
 
-    <!-- TODO(b/259369672): Replace with final resource. -->
     <!-- [CHAR LIMIT=30] Label used to open Note Task -->
-    <string name="note_task_button_label">Notetaking</string>
+    <string name="note_task_button_label">Note-taking</string>
+
+    <!-- [CHAR LIMIT=25] Long label used by Note Task Shortcut -->
+    <string name="note_task_shortcut_long_label">Note-taking, <xliff:g id="note_taking_app" example="Note-taking App">%1$s</xliff:g></string>
 
     <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is broadcasting -->
     <string name="broadcasting_description_is_broadcasting">Broadcasting</string>
@@ -3030,13 +3058,6 @@
     <string name="wallet_quick_affordance_unavailable_configure_the_app">To add the Wallet app as a shortcut, make sure at least one card has been added</string>
 
     <!--
-    Requirement for the QR code scanner functionality to be available for the user to use. This is
-    shown as part of a bulleted list of requirements. When all requirements are met, the piece of
-    functionality can be accessed through a shortcut button on the lock screen. [CHAR LIMIT=NONE].
-    -->
-    <string name="qr_scanner_quick_affordance_unavailable_explanation">To add the QR code scanner as a shortcut, make sure a camera app is installed</string>
-
-    <!--
     Explains that the lock screen shortcut for the "home" app is not available because the app isn't
     installed. This is shown as part of a dialog that explains to the user why they cannot select
     this shortcut for their lock screen right now. [CHAR LIMIT=NONE].
@@ -3153,10 +3174,43 @@
     <!--- Content of toast triggered when the notes app entry point is triggered without setting a default notes app. [CHAR LIMIT=NONE] -->
     <string name="set_default_notes_app_toast_content">Set default notes app in Settings</string>
 
-    <!--
-    Label for a button that, when clicked, sends the user to the app store to install an app.
-
-    [CHAR LIMIT=64].
-    -->
+    <!-- Label for a button that, when clicked, sends the user to the app store to install an app. [CHAR LIMIT=64]. -->
     <string name="install_app">Install app</string>
+
+    <!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] -->
+    <string name="privacy_dialog_title">Microphone &amp; Camera</string>
+    <!-- Subtitle of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_summary">Recent app use</string>
+    <!-- Label of the secondary button of the privacy dialog, used to check recent app usage of phone sensors [CHAR LIMIT=30] -->
+    <string name="privacy_dialog_more_button">See recent access</string>
+    <!-- Label of the primary button to dismiss the privacy dialog [CHAR LIMIT=20] -->
+    <string name="privacy_dialog_done_button">Done</string>
+    <!-- Description for expanding a collapsible widget in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_expand_action">Expand and show options</string>
+    <!-- Description for collapsing a collapsible widget in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_collapse_action">Collapse</string>
+    <!-- Label of a button of the privacy dialog to close an app actively using a phone sensor [CHAR LIMIT=50] -->
+    <string name="privacy_dialog_close_app_button">Close this app</string>
+    <!-- Message shown in the privacy dialog when an app actively using a phone sensor is closed [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_close_app_message"><xliff:g id="app_name" example="Gmail">%1$s</xliff:g> closed</string>
+    <!-- Label of a button of the privacy dialog to learn more of a service actively or recently using a phone sensor [CHAR LIMIT=50] -->
+    <string name="privacy_dialog_manage_service">Manage service</string>
+    <!-- Label of a button of the privacy dialog to manage permissions of an app actively or recently using a phone sensor [CHAR LIMIT=50] -->
+    <string name="privacy_dialog_manage_permissions">Manage access</string>
+    <!-- Label for active usage of a phone sensor by phone call in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_active_call_usage">In use by phone call</string>
+    <!-- Label for recent usage of a phone sensor by phone call in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_recent_call_usage">Recently used in phone call</string>
+    <!-- Label for active app usage of a phone sensor in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_active_app_usage">In use by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g></string>
+    <!-- Label for recent app usage of a phone sensor in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_recent_app_usage">Recently used by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g></string>
+    <!-- Label for active app usage of a phone sensor with sub-attribution or proxy label in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_active_app_usage_1">In use by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g>)</string>
+    <!-- Label for recent app usage of a phone sensor with sub-attribution or proxy label in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_recent_app_usage_1">Recently used by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g>)</string>
+    <!-- Label for active app usage of a phone sensor with sub-attribution and proxy label in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_active_app_usage_2">In use by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g> \u2022 <xliff:g id="proxy_label" example="Speech services">%3$s</xliff:g>)</string>
+    <!-- Label for recent app usage of a phone sensor with sub-attribution and proxy label in the privacy dialog [CHAR LIMIT=NONE] -->
+    <string name="privacy_dialog_recent_app_usage_2">Recently used by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g> \u2022 <xliff:g id="proxy_label" example="Speech services">%3$s</xliff:g>)</string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index cb5342a..d520670 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -83,7 +83,7 @@
 
     <style name="TextAppearance.QS">
         <item name="android:textStyle">normal</item>
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?attr/onShadeInactive</item>
         <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
     </style>
 
@@ -93,7 +93,7 @@
 
     <style name="TextAppearance.QS.DetailItemSecondary">
         <item name="android:textSize">@dimen/qs_tile_text_size</item>
-        <item name="android:textColor">?android:attr/colorAccent</item>
+        <item name="android:textColor">?attr/shadeActive</item>
     </style>
 
     <style name="TextAppearance.QS.Introduction">
@@ -117,11 +117,11 @@
 
     <style name="TextAppearance.QS.DataUsage.Usage">
         <item name="android:textSize">@dimen/qs_data_usage_usage_text_size</item>
-        <item name="android:textColor">?android:attr/colorAccent</item>
+        <item name="android:textColor">?attr/shadeActive</item>
     </style>
 
     <style name="TextAppearance.QS.DataUsage.Secondary">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?attr/onShadeInactiveVariant</item>
     </style>
 
     <style name="TextAppearance.QS.TileLabel">
@@ -137,31 +137,31 @@
 
     <style name="TextAppearance.QS.UserSwitcher">
         <item name="android:textSize">@dimen/qs_tile_text_size</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
     </style>
 
     <!-- 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_headlineFontFamily</item>
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?attr/onSurface</item>
         <item name="android:textSize">14sp</item>
         <item name="android:letterSpacing">0.01</item>
     </style>
 
     <style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status">
         <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?attr/onSurface</item>
     </style>
 
     <style name="TextAppearance.QS.Status.Carriers" />
 
     <style name="TextAppearance.QS.Status.Carriers.NoCarrierText">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?attr/onSurfaceVariant</item>
     </style>
 
     <style name="TextAppearance.QS.Status.Build">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?attr/onSurfaceVariant</item>
     </style>
 
     <style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"/>
@@ -278,10 +278,10 @@
 
     <style name="DeviceManagementDialogTitle">
         <item name="android:gravity">center</item>
-        <item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item>
+        <item name="android:textAppearance">@style/TextAppearance.Dialog.Title</item>
     </style>
 
-    <style name="TextAppearance.DeviceManagementDialog.Content" parent="@*android:style/TextAppearance.DeviceDefault.Subhead"/>
+    <style name="TextAppearance.DeviceManagementDialog.Content" parent="@style/TextAppearance.Dialog.Body.Message"/>
 
     <style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI">
         <item name="android:layout_width">match_parent</item>
@@ -371,18 +371,35 @@
     </style>
 
     <style name="Theme.SystemUI.QuickSettings" parent="@*android:style/Theme.DeviceDefault">
+        <item name="isQsTheme">true</item>
         <item name="lightIconTheme">@style/QSIconTheme</item>
         <item name="darkIconTheme">@style/QSIconTheme</item>
         <item name="android:colorError">@*android:color/error_color_material_dark</item>
         <item name="android:windowIsFloating">true</item>
         <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item>
-        <item name="offStateColor">@color/material_dynamic_neutral20</item>
-        <item name="underSurfaceColor">@color/material_dynamic_neutral0</item>
-        <item name="android:colorBackground">@color/material_dynamic_neutral10</item>
+
+        <item name="surfaceBright">?androidprv:attr/materialColorSurfaceBright</item>
+        <item name="android:colorBackground">?attr/surfaceBright</item>
+        <item name="scHigh">?androidprv:attr/materialColorSurfaceContainerHigh</item>
+        <item name="primary">?androidprv:attr/materialColorPrimary</item>
+        <item name="tertiary">?androidprv:attr/materialColorTertiary</item>
+        <item name="onSurface">?androidprv:attr/materialColorOnSurface</item>
+        <item name="onSurfaceVariant">?androidprv:attr/materialColorOnSurfaceVariant</item>
+        <item name="outline">?androidprv:attr/materialColorOutline</item>
+
+        <item name="shadeActive">@color/material_dynamic_primary90</item>
+        <item name="onShadeActive">@color/material_dynamic_primary10</item>
+        <item name="onShadeActiveVariant">@color/material_dynamic_primary30</item>
+        <item name="shadeInactive">@color/material_dynamic_neutral20</item>
+        <item name="onShadeInactive">@color/material_dynamic_neutral90</item>
+        <item name="onShadeInactiveVariant">@color/material_dynamic_neutral_variant80</item>
+        <item name="shadeDisabled">@color/shade_disabled</item>
+        <item name="underSurface">@color/material_dynamic_neutral0</item>
         <item name="android:itemTextAppearance">@style/Control.MenuItem</item>
     </style>
 
-    <style name="Theme.SystemUI.QuickSettings.BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
+    <!-- Cannot double inherit. Use Theme.SystemUI.QuickSettings in code to match -->
+    <style name="BrightnessDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
         <item name="android:windowBackground">@android:color/transparent</item>
     </style>
 
@@ -390,8 +407,15 @@
         <item name="android:windowBackground">@android:color/transparent</item>
     </style>
 
-    <style name="Theme.SystemUI.QuickSettings.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog">
+    <style name="Theme.SystemUI.QuickSettings.Dialog" parent="@style/Theme.SystemUI.Dialog.QuickSettings">
+    </style>
+
+    <!-- Parent style overrides style in the dot inheritance -->
+    <style name="Theme.SystemUI.Dialog.QuickSettings" parent="@style/Theme.SystemUI.QuickSettings">
         <item name="android:dialogCornerRadius">@dimen/notification_corner_radius</item>
+        <item name="android:buttonBarPositiveButtonStyle">@style/Widget.Dialog.Button.QuickSettings</item>
+        <item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.QuickSettings</item>
+        <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.QuickSettings</item>
     </style>
 
     <!-- Overridden by values-television/styles.xml with tv-specific settings -->
@@ -406,7 +430,7 @@
         <item name="android:buttonBarPositiveButtonStyle">@style/Widget.Dialog.Button</item>
         <item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
         <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
-        <item name="android:colorBackground">?androidprv:attr/colorSurface</item>
+        <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceBright</item>
         <item name="android:alertDialogStyle">@style/ScrollableAlertDialogStyle</item>
         <item name="android:buttonBarStyle">@style/ButtonBarStyle</item>
         <item name="android:buttonBarButtonStyle">@style/Widget.Dialog.Button.Large</item>
@@ -605,11 +629,11 @@
         <item name="android:letterSpacing">0.01</item>
         <item name="android:lineHeight">20sp</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?attr/onSurfaceVariant</item>
     </style>
 
     <style name="QSCustomizeToolbar" parent="@*android:style/Widget.DeviceDefault.Toolbar">
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?attr/onSurface</item>
         <item name="android:elevation">10dp</item>
     </style>
 
@@ -1055,7 +1079,7 @@
     </style>
 
     <style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.DeviceDefault.Large">
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
         <item name="android:textSize">@dimen/dialog_title_text_size</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:lineHeight">32sp</item>
@@ -1064,7 +1088,7 @@
     </style>
 
     <style name="TextAppearance.Dialog.Body" parent="@android:style/TextAppearance.DeviceDefault.Medium">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
         <item name="android:textSize">14sp</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:lineHeight">20sp</item>
@@ -1092,7 +1116,7 @@
     <style name="Widget.Dialog.Button">
         <item name="android:buttonCornerRadius">28dp</item>
         <item name="android:background">@drawable/qs_dialog_btn_filled</item>
-        <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
+        <item name="android:textColor">@color/qs_dialog_btn_filled_text_color</item>
         <item name="android:textSize">14sp</item>
         <item name="android:lineHeight">20sp</item>
         <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
@@ -1102,12 +1126,18 @@
 
     <style name="Widget.Dialog.Button.BorderButton">
         <item name="android:background">@drawable/qs_dialog_btn_outline</item>
-        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textColor">@color/qs_dialog_btn_outline_text</item>
     </style>
 
     <style name="Widget.Dialog.Button.Large">
         <item name="android:background">@drawable/qs_dialog_btn_filled_large</item>
         <item name="android:minHeight">56dp</item>
+        <item name="android:textColor">@color/qs_dialog_btn_filled_large_text</item>
+    </style>
+
+    <style name="Widget.Dialog.Button.QuickSettings">
+        <item name="android:textColor">?attr/primary</item>
+        <item name="android:background">?android:attr/selectableItemBackground</item>
     </style>
 
     <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault">
@@ -1409,4 +1439,22 @@
         <item name="android:windowEnterAnimation">@anim/long_press_lock_screen_popup_enter</item>
         <item name="android:windowExitAnimation">@anim/long_press_lock_screen_popup_exit</item>
     </style>
+
+    <style name="TextAppearance.PrivacyDialog.Item.Title"
+           parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textSize">14sp</item>
+        <item name="android:lineHeight">20sp</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+    </style>
+
+    <style name="TextAppearance.PrivacyDialog.Item.Summary"
+           parent="@android:style/TextAppearance.DeviceDefault.Small">
+        <item name="android:textSize">14sp</item>
+        <item name="android:lineHeight">20sp</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
+    </style>
+
+    <style name="Theme.PrivacyDialog" parent="@style/Theme.SystemUI.Dialog">
+        <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainer</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index 8512f6f..c167256 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -53,56 +53,28 @@
                 android:alpha="0"
                 />
             <KeyPosition
+                app:motionTarget="@id/shade_header_system_icons"
                 app:keyPositionType="deltaRelative"
                 app:percentX="0"
                 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" />
+                app:curveFit="linear" />
             <KeyPosition
+                app:motionTarget="@id/shade_header_system_icons"
                 app:keyPositionType="deltaRelative"
                 app:percentX="1"
                 app:percentY="0.5"
                 app:framePosition="50"
                 app:sizePercent="1"
-                app:curveFit="linear"
-                app:motionTarget="@id/statusIcons" />
+                app:curveFit="linear" />
             <KeyAttribute
-                app:motionTarget="@id/statusIcons"
+                app:motionTarget="@id/shade_header_system_icons"
                 app:framePosition="@integer/fade_out_complete_frame"
                 android:alpha="0"
                 />
             <KeyAttribute
-                app:motionTarget="@id/statusIcons"
-                app:framePosition="@integer/fade_in_start_frame"
-                android:alpha="0"
-                />
-            <KeyPosition
-                app:keyPositionType="deltaRelative"
-                app:percentX="0"
-                app:percentY="@dimen/percent_displacement_at_fade_out"
-                app:framePosition="@integer/fade_out_complete_frame"
-                app:percentWidth="1"
-                app:percentHeight="1"
-                app:curveFit="linear"
-                app:motionTarget="@id/batteryRemainingIcon" />
-            <KeyPosition
-                app:keyPositionType="deltaRelative"
-                app:percentX="1"
-                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="@integer/fade_out_complete_frame"
-                android:alpha="0"
-                />
-            <KeyAttribute
-                app:motionTarget="@id/batteryRemainingIcon"
+                app:motionTarget="@id/shade_header_system_icons"
                 app:framePosition="@integer/fade_in_start_frame"
                 android:alpha="0"
                 />
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index 3881e8c..fe61c46 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -45,7 +45,7 @@
             android:layout_height="0dp"
             android:layout_gravity="end|center_vertical"
             app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toStartOf="@id/statusIcons"
+            app:layout_constraintEnd_toStartOf="@id/shade_header_system_icons"
             app:layout_constraintStart_toEndOf="@id/date"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintWidth_default="wrap"
@@ -53,25 +53,14 @@
         <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint android:id="@+id/statusIcons">
+    <Constraint android:id="@+id/shade_header_system_icons">
         <Layout
             android:layout_width="wrap_content"
-            android:layout_height="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintStart_toEndOf="@id/carrier_group"/>
-        <PropertySet android:alpha="1" />
-    </Constraint>
-
-    <Constraint android:id="@+id/batteryRemainingIcon">
-        <Layout
-            android:layout_width="wrap_content"
-            android:layout_height="0dp"
-            app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
+            android:layout_height="wrap_content"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toStartOf="@id/privacy_container"
-            app:layout_constraintTop_toTopOf="parent" />
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintStart_toEndOf="@id/carrier_group"/>
         <PropertySet android:alpha="1" />
     </Constraint>
 
diff --git a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
deleted file mode 100644
index b7d4b3a..0000000
--- a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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
-  -->
-<ConstraintSet
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto" >
-
-    <Constraint
-        android:id="@+id/sizing_view"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/qs_media_session_height_collapsed"
-        />
-
-    <!-- Only the constraintBottom and marginBottom are different. The rest of the constraints are
-         the same as the constraints in media_recommendations_expanded.xml. But, due to how
-         ConstraintSets work, all the constraints need to be in the same place. So, the shared
-         constraints can't be put in the shared layout file media_smartspace_recommendations.xml and
-         the constraints are instead duplicated between here and media_recommendations_expanded.xml.
-         Ditto for the other cover containers. -->
-    <Constraint
-        android:id="@+id/media_cover1_container"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toStartOf="@id/media_cover2_container"
-        android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
-        app:layout_constraintHorizontal_chainStyle="packed"
-        app:layout_constraintHorizontal_bias="1.0"
-        app:layout_constraintVertical_bias="0.5"
-        />
-
-    <Constraint
-        android:id="@+id/media_title1"
-        android:visibility="gone"
-        />
-
-    <Constraint
-        android:id="@+id/media_subtitle1"
-        android:visibility="gone"
-        />
-
-    <Constraint
-        android:id="@+id/media_cover2_container"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toEndOf="@id/media_cover1_container"
-        app:layout_constraintEnd_toStartOf="@id/media_cover3_container"
-        android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
-        app:layout_constraintVertical_bias="0.5"
-        />
-
-    <Constraint
-        android:id="@+id/media_title2"
-        android:visibility="gone"
-        />
-
-    <Constraint
-        android:id="@+id/media_subtitle2"
-        android:visibility="gone"
-        />
-
-    <Constraint
-        android:id="@+id/media_cover3_container"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toEndOf="@id/media_cover2_container"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginEnd="@dimen/qs_media_padding"
-        app:layout_constraintVertical_bias="0.5"
-        />
-
-    <Constraint
-        android:id="@+id/media_title3"
-        android:visibility="gone"
-        />
-
-    <Constraint
-        android:id="@+id/media_subtitle3"
-        android:visibility="gone"
-        />
-
-</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendation_expanded.xml b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
deleted file mode 100644
index ce25a7d..0000000
--- a/packages/SystemUI/res/xml/media_recommendation_expanded.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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
-  -->
-<ConstraintSet
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    >
-
-    <Constraint
-        android:id="@+id/sizing_view"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/qs_media_session_height_expanded"
-        />
-
-    <Constraint
-        android:id="@+id/media_cover1_container"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@+id/media_title1"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toStartOf="@id/media_cover2_container"
-        android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
-        app:layout_constraintHorizontal_chainStyle="packed"
-        app:layout_constraintVertical_chainStyle="packed"
-        app:layout_constraintHorizontal_bias="1.0"
-        app:layout_constraintVertical_bias="0.4"
-        />
-
-    <Constraint
-        android:id="@+id/media_title1"
-        style="@style/MediaPlayer.Recommendation.Text.Title"
-        app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_cover1_container"
-        app:layout_constraintBottom_toTopOf="@+id/media_subtitle1"
-        />
-
-    <Constraint
-        android:id="@+id/media_subtitle1"
-        style="@style/MediaPlayer.Recommendation.Text.Subtitle"
-        app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_title1"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        />
-
-    <Constraint
-        android:id="@+id/media_cover2_container"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/media_title2"
-        app:layout_constraintStart_toEndOf="@id/media_cover1_container"
-        app:layout_constraintEnd_toStartOf="@id/media_cover3_container"
-        android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
-        app:layout_constraintVertical_chainStyle="packed"
-        app:layout_constraintVertical_bias="0.4"
-        />
-
-    <Constraint
-        android:id="@+id/media_title2"
-        style="@style/MediaPlayer.Recommendation.Text.Title"
-        app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_cover2_container"
-        app:layout_constraintBottom_toTopOf="@+id/media_subtitle2"
-        />
-
-    <Constraint
-        android:id="@+id/media_subtitle2"
-        style="@style/MediaPlayer.Recommendation.Text.Subtitle"
-        app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_title2"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        />
-
-    <Constraint
-        android:id="@+id/media_cover3_container"
-        style="@style/MediaPlayer.Recommendation.AlbumContainer"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/media_title3"
-        app:layout_constraintStart_toEndOf="@id/media_cover2_container"
-        app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginEnd="@dimen/qs_media_padding"
-        app:layout_constraintVertical_chainStyle="packed"
-        app:layout_constraintVertical_bias="0.4"
-        />
-
-    <Constraint
-        android:id="@+id/media_title3"
-        style="@style/MediaPlayer.Recommendation.Text.Title"
-        app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_cover3_container"
-        app:layout_constraintBottom_toTopOf="@+id/media_subtitle3"
-        />
-
-    <Constraint
-        android:id="@+id/media_subtitle3"
-        style="@style/MediaPlayer.Recommendation.Text.Subtitle"
-        app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
-        app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
-        app:layout_constraintTop_toBottomOf="@+id/media_title3"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="@dimen/qs_media_padding"
-        />
-
-</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml b/packages/SystemUI/res/xml/media_recommendations_collapsed.xml
similarity index 100%
rename from packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml
rename to packages/SystemUI/res/xml/media_recommendations_collapsed.xml
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml b/packages/SystemUI/res/xml/media_recommendations_expanded.xml
similarity index 100%
rename from packages/SystemUI/res/xml/media_recommendations_view_expanded.xml
rename to packages/SystemUI/res/xml/media_recommendations_expanded.xml
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index 1950965..50a388d 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -54,27 +54,12 @@
     </Constraint>
 
     <Constraint
-        android:id="@+id/statusIcons">
+        android:id="@+id/shade_header_system_icons">
         <Layout
             android:layout_width="wrap_content"
             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"
-            app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
-            app:layout_constraintHorizontal_bias="1"
-            app:layout_constraintHorizontal_chainStyle="packed"
-            />
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/batteryRemainingIcon">
-        <Layout
-            android:layout_width="wrap_content"
-            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="@id/end_guide"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="@id/qqs_header_bottom_guideline"
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index 8039c68..427fd87 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -59,7 +59,7 @@
         />
     </Constraint>
 
-    <!-- LargeScreenShadeHeaderController helps with managing clock width to layout this view -->
+    <!-- ShadeHeaderController helps with managing clock width to layout this view -->
     <Constraint
         android:id="@+id/carrier_group">
         <Layout
@@ -78,25 +78,12 @@
     </Constraint>
 
     <Constraint
-        android:id="@+id/statusIcons">
+        android:id="@+id/shade_header_system_icons">
         <Layout
             android:layout_width="0dp"
             android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintWidth_default="wrap"
             app:layout_constraintStart_toEndOf="@id/date"
-            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
-            app:layout_constraintTop_toTopOf="@id/date"
-            app:layout_constraintBottom_toBottomOf="@id/date"
-            />
-    </Constraint>
-
-    <Constraint
-        android:id="@+id/batteryRemainingIcon">
-        <Layout
-            android:layout_width="0dp"
-            android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
-            app:layout_constraintWidth_default="wrap"
-            app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="@id/date"
             app:layout_constraintBottom_toBottomOf="@id/date"
diff --git a/packages/SystemUI/screenshot/Android.bp b/packages/SystemUI/screenshot/Android.bp
deleted file mode 100644
index f449398..0000000
--- a/packages/SystemUI/screenshot/Android.bp
+++ /dev/null
@@ -1,45 +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 {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_library {
-    name: "SystemUIScreenshotLib",
-    manifest: "AndroidManifest.xml",
-
-    srcs: [
-        "src/**/*.kt",
-    ],
-
-    resource_dirs: [
-        "res",
-    ],
-
-    static_libs: [
-        "SystemUI-core",
-        "androidx.test.espresso.core",
-        "androidx.appcompat_appcompat",
-        "platform-screenshot-diff-core",
-        "guava",
-    ],
-
-    kotlincflags: ["-Xjvm-default=all"],
-}
diff --git a/packages/SystemUI/screenshot/AndroidManifest.xml b/packages/SystemUI/screenshot/AndroidManifest.xml
deleted file mode 100644
index ba3dc8c..0000000
--- a/packages/SystemUI/screenshot/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.systemui.testing.screenshot">
-    <application>
-        <activity
-            android:name="com.android.systemui.testing.screenshot.ScreenshotActivity"
-            android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
-            android:exported="true"
-            android:theme="@style/Theme.SystemUI.Screenshot" />
-    </application>
-</manifest>
diff --git a/packages/SystemUI/screenshot/res/values/themes.xml b/packages/SystemUI/screenshot/res/values/themes.xml
deleted file mode 100644
index a7f8a26..0000000
--- a/packages/SystemUI/screenshot/res/values/themes.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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>
-    <style name="Theme.SystemUI.Screenshot" parent="Theme.SystemUI">
-        <item name="android:windowActionBar">false</item>
-        <item name="android:windowNoTitle">true</item>
-
-        <!-- We make the status and navigation bars transparent so that the screenshotted content is
-             not clipped by the status bar height when drawn into the Bitmap (which is what happens
-             given that we draw the view into the Bitmap using hardware acceleration). -->
-        <item name="android:statusBarColor">@android:color/transparent</item>
-        <item name="android:navigationBarColor">@android:color/transparent</item>
-
-        <!-- Make sure that device specific cutouts don't impact the outcome of screenshot tests -->
-        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
-    </style>
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt
deleted file mode 100644
index a4a70a4..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt
+++ /dev/null
@@ -1,66 +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.testing.screenshot
-
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.os.Build
-import android.view.View
-import platform.test.screenshot.matchers.MSSIMMatcher
-import platform.test.screenshot.matchers.PixelPerfectMatcher
-
-/** Draw this [View] into a [Bitmap]. */
-// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their
-// tests.
-fun View.drawIntoBitmap(): Bitmap {
-    val bitmap =
-        Bitmap.createBitmap(
-            measuredWidth,
-            measuredHeight,
-            Bitmap.Config.ARGB_8888,
-        )
-    val canvas = Canvas(bitmap)
-    draw(canvas)
-    return bitmap
-}
-
-/**
- * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
- * screenshot *unit* tests.
- */
-val UnitTestBitmapMatcher =
-    if (Build.CPU_ABI == "x86_64") {
-        // Different CPU architectures can sometimes end up rendering differently, so we can't do
-        // pixel-perfect matching on different architectures using the same golden. Given that our
-        // presubmits are run on cf_x86_64_phone, our goldens should be perfectly matched on the
-        // x86_64 architecture and use the Structural Similarity Index on others.
-        // TODO(b/237511747): Run our screenshot presubmit tests on arm64 instead so that we can
-        // do pixel perfect matching both at presubmit time and at development time with actual
-        // devices.
-        PixelPerfectMatcher()
-    } else {
-        MSSIMMatcher()
-    }
-
-/**
- * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
- * screenshot *unit* tests.
- *
- * We use the Structural Similarity Index for integration tests because they usually contain
- * additional information and noise that shouldn't break the test.
- */
-val IntegrationTestBitmapMatcher = MSSIMMatcher()
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
deleted file mode 100644
index e032bb9..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
+++ /dev/null
@@ -1,107 +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.testing.screenshot
-
-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
-import androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
-import androidx.test.platform.app.InstrumentationRegistry
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import platform.test.screenshot.*
-
-/**
- * A rule that allows to run a screenshot diff test on a view that is hosted in another activity.
- */
-class ExternalViewScreenshotTestRule(
-    emulationSpec: DeviceEmulationSpec,
-    assetPathRelativeToBuildRoot: String
-) : TestRule {
-
-    private val colorsRule = MaterialYouColorsRule()
-    private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
-    private val screenshotRule =
-        ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(
-                getEmulatedDevicePathConfig(emulationSpec),
-                assetPathRelativeToBuildRoot
-            )
-        )
-    private val delegateRule =
-        RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule)
-    private val matcher = UnitTestBitmapMatcher
-
-    override fun apply(base: Statement, description: Description): Statement {
-        return delegateRule.apply(base, description)
-    }
-
-    /**
-     * Compare the content of the [view] with the golden image identified by [goldenIdentifier] in
-     * the context of [emulationSpec]. Window must be specified to capture views that render
-     * hardware buffers.
-     */
-    fun screenshotTest(goldenIdentifier: String, view: View, window: Window? = null) {
-        view.removeElevationRecursively()
-
-        ScreenshotRuleAsserter.Builder(screenshotRule)
-            .setScreenshotProvider { view.toBitmap(window) }
-            .withMatcher(matcher)
-            .build()
-            .assertGoldenImage(goldenIdentifier)
-    }
-
-    /**
-     * Compare the content of the [activity] with the golden image identified by [goldenIdentifier]
-     * in the context of [emulationSpec].
-     */
-    fun activityScreenshotTest(
-        goldenIdentifier: String,
-        activity: Activity,
-    ) {
-        val rootView = activity.window.decorView
-
-        // Hide system bars, remove insets, focus and make sure device-specific cutouts
-        // don't affect screenshots
-        InstrumentationRegistry.getInstrumentation().runOnMainSync {
-            val window = activity.window
-            window.setDecorFitsSystemWindows(false)
-            WindowInsetsControllerCompat(window, rootView).apply {
-                hide(WindowInsetsCompat.Type.systemBars())
-                systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
-            }
-
-            window.statusBarColor = Color.TRANSPARENT
-            window.navigationBarColor = Color.TRANSPARENT
-            window.attributes =
-                window.attributes.apply {
-                    layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
-                }
-
-            rootView.removeInsetsRecursively()
-            activity.currentFocus?.clearFocus()
-        }
-
-        screenshotTest(goldenIdentifier, rootView, activity.window)
-    }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
deleted file mode 100644
index 2a55a80..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
+++ /dev/null
@@ -1,22 +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.testing.screenshot
-
-import androidx.activity.ComponentActivity
-
-/** The Activity that is launched and whose content is set for screenshot tests. */
-class ScreenshotActivity : ComponentActivity()
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
deleted file mode 100644
index 72d8c5a..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
+++ /dev/null
@@ -1,44 +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.testing.screenshot
-
-import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
-import platform.test.screenshot.PathConfig
-
-/** A [GoldenImagePathManager] that should be used for all SystemUI screenshot tests. */
-class SystemUIGoldenImagePathManager(
-    pathConfig: PathConfig,
-    assetsPathRelativeToBuildRoot: String
-) :
-    GoldenImagePathManager(
-        appContext = InstrumentationRegistry.getInstrumentation().context,
-        assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
-        deviceLocalPath =
-            InstrumentationRegistry.getInstrumentation()
-                .targetContext
-                .filesDir
-                .absolutePath
-                .toString() + "/sysui_screenshots",
-        pathConfig = pathConfig,
-    ) {
-    override fun toString(): String {
-        // This string is appended to all actual/expected screenshots on the device, so make sure
-        // it is a static value.
-        return "SystemUIGoldenImagePathManager"
-    }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt
deleted file mode 100644
index 98e9aaf..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/TestAppComponentFactory.kt
+++ /dev/null
@@ -1,60 +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.testing.screenshot
-
-import android.app.Activity
-import android.content.Intent
-import androidx.core.app.AppComponentFactory
-
-class TestAppComponentFactory : AppComponentFactory() {
-
-    init {
-        instance = this
-    }
-
-    private val overrides: MutableMap<String, () -> Activity> = hashMapOf()
-
-    fun clearOverrides() {
-        overrides.clear()
-    }
-
-    fun <T : Activity> registerActivityOverride(activity: Class<T>, provider: () -> T) {
-        overrides[activity.name] = provider
-    }
-
-    override fun instantiateActivityCompat(
-        cl: ClassLoader,
-        className: String,
-        intent: Intent?
-    ): Activity {
-        return overrides
-            .getOrDefault(className) { super.instantiateActivityCompat(cl, className, intent) }
-            .invoke()
-    }
-
-    companion object {
-
-        private var instance: TestAppComponentFactory? = null
-
-        fun getInstance(): TestAppComponentFactory =
-            instance
-                ?: error(
-                    "TestAppComponentFactory is not initialized, " +
-                        "did you specify it in the manifest?"
-                )
-    }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
deleted file mode 100644
index b84d26a..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt
+++ /dev/null
@@ -1,42 +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.testing.screenshot
-
-import android.view.View
-import android.view.ViewGroup
-import com.android.systemui.util.children
-import android.view.WindowInsets
-
-/**
- * Elevation/shadows is not deterministic when doing hardware rendering, this exentsion allows to
- * disable it for any view in the hierarchy.
- */
-fun View.removeElevationRecursively() {
-    this.elevation = 0f
-    (this as? ViewGroup)?.children?.forEach(View::removeElevationRecursively)
-}
-
-/**
- * Different devices could have different insets (e.g. different height of the navigation bar or
- * taskbar). This method dispatches empty insets to the whole view hierarchy and removes
- * the original listener, so the views won't receive real insets.
- */
-fun View.removeInsetsRecursively() {
-    this.dispatchApplyWindowInsets(WindowInsets.CONSUMED)
-    this.setOnApplyWindowInsetsListener(null)
-    (this as? ViewGroup)?.children?.forEach(View::removeInsetsRecursively)
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
deleted file mode 100644
index dedf0a7..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
+++ /dev/null
@@ -1,233 +0,0 @@
-package com.android.systemui.testing.screenshot
-
-import android.annotation.WorkerThread
-import android.app.Activity
-import android.content.Context
-import android.content.ContextWrapper
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.HardwareRenderer
-import android.graphics.Rect
-import android.os.Build
-import android.os.Handler
-import android.os.Looper
-import android.util.Log
-import android.view.PixelCopy
-import android.view.SurfaceView
-import android.view.View
-import android.view.ViewTreeObserver
-import android.view.Window
-import androidx.annotation.RequiresApi
-import androidx.concurrent.futures.ResolvableFuture
-import androidx.test.annotation.ExperimentalTestApi
-import androidx.test.core.internal.os.HandlerExecutor
-import androidx.test.espresso.Espresso
-import androidx.test.platform.graphics.HardwareRendererCompat
-import com.google.common.util.concurrent.FutureCallback
-import com.google.common.util.concurrent.Futures
-import com.google.common.util.concurrent.ListenableFuture
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.runBlocking
-
-/*
- * This file was forked from androidx/test/core/view/ViewCapture.kt to add [Window] parameter to
- * [View.captureToBitmap].
- * TODO(b/195673633): Remove this fork and use the AndroidX version instead.
- */
-
-/**
- * Asynchronously captures an image of the underlying view into a [Bitmap].
- *
- * For devices below [Build.VERSION_CODES#O] (or if the view's window cannot be determined), the
- * image is obtained using [View#draw]. Otherwise, [PixelCopy] is used.
- *
- * This method will also enable [HardwareRendererCompat#setDrawingEnabled(boolean)] if required.
- *
- * This API is primarily intended for use in lower layer libraries or frameworks. For test authors,
- * its recommended to use espresso or compose's captureToImage.
- *
- * This API is currently experimental and subject to change or removal.
- */
-@ExperimentalTestApi
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
-fun View.captureToBitmap(window: Window? = null): ListenableFuture<Bitmap> {
-    val bitmapFuture: ResolvableFuture<Bitmap> = ResolvableFuture.create()
-    val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper()))
-    val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
-
-    // disable drawing again if necessary once work is complete
-    if (!HardwareRendererCompat.isDrawingEnabled()) {
-        HardwareRendererCompat.setDrawingEnabled(true)
-        bitmapFuture.addListener({ HardwareRendererCompat.setDrawingEnabled(false) }, mainExecutor)
-    }
-
-    mainExecutor.execute {
-        if (isRobolectric) {
-            generateBitmap(bitmapFuture)
-        } else {
-            val forceRedrawFuture = forceRedraw()
-            forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor)
-        }
-    }
-
-    return bitmapFuture
-}
-
-/**
- * Synchronously captures an image of the view into a [Bitmap]. Synchronous equivalent of
- * [captureToBitmap].
- */
-@WorkerThread
-@ExperimentalTestApi
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
-fun View.toBitmap(window: Window? = null): Bitmap {
-    if (Looper.getMainLooper() == Looper.myLooper()) {
-        error("toBitmap() can't be called from the main thread")
-    }
-
-    if (!HardwareRenderer.isDrawingEnabled()) {
-        error("Hardware rendering is not enabled")
-    }
-
-    // Make sure we are idle.
-    Espresso.onIdle()
-
-    val mainExecutor = context.mainExecutor
-    return runBlocking {
-        suspendCoroutine { continuation ->
-            Futures.addCallback(
-                captureToBitmap(window),
-                object : FutureCallback<Bitmap> {
-                    override fun onSuccess(result: Bitmap?) {
-                        continuation.resumeWith(Result.success(result!!))
-                    }
-
-                    override fun onFailure(t: Throwable) {
-                        continuation.resumeWith(Result.failure(t))
-                    }
-                },
-                // We know that we are not on the main thread, so we can block the current
-                // thread and wait for the result in the main thread.
-                mainExecutor,
-            )
-        }
-    }
-}
-
-/**
- * Trigger a redraw of the given view.
- *
- * Should only be called on UI thread.
- *
- * @return a [ListenableFuture] that will be complete once ui drawing is complete
- */
-// NoClassDefFoundError occurs on API 15
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
-// @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-@ExperimentalTestApi
-fun View.forceRedraw(): ListenableFuture<Void> {
-    val future: ResolvableFuture<Void> = ResolvableFuture.create()
-
-    if (Build.VERSION.SDK_INT >= 29 && isHardwareAccelerated) {
-        viewTreeObserver.registerFrameCommitCallback() { future.set(null) }
-    } else {
-        viewTreeObserver.addOnDrawListener(
-            object : ViewTreeObserver.OnDrawListener {
-                var handled = false
-                override fun onDraw() {
-                    if (!handled) {
-                        handled = true
-                        future.set(null)
-                        // cannot remove on draw listener inside of onDraw
-                        Handler(Looper.getMainLooper()).post {
-                            viewTreeObserver.removeOnDrawListener(this)
-                        }
-                    }
-                }
-            }
-        )
-    }
-    invalidate()
-    return future
-}
-
-private fun View.generateBitmap(
-    bitmapFuture: ResolvableFuture<Bitmap>,
-    window: Window? = null,
-) {
-    if (bitmapFuture.isCancelled) {
-        return
-    }
-    val destBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
-    when {
-        Build.VERSION.SDK_INT < 26 -> generateBitmapFromDraw(destBitmap, bitmapFuture)
-        this is SurfaceView -> generateBitmapFromSurfaceViewPixelCopy(destBitmap, bitmapFuture)
-        else -> {
-            val window = window ?: getActivity()?.window
-            if (window != null) {
-                generateBitmapFromPixelCopy(window, destBitmap, bitmapFuture)
-            } else {
-                Log.i(
-                    "View.captureToImage",
-                    "Could not find window for view. Falling back to View#draw instead of PixelCopy"
-                )
-                generateBitmapFromDraw(destBitmap, bitmapFuture)
-            }
-        }
-    }
-}
-
-@SuppressWarnings("NewApi")
-private fun SurfaceView.generateBitmapFromSurfaceViewPixelCopy(
-    destBitmap: Bitmap,
-    bitmapFuture: ResolvableFuture<Bitmap>
-) {
-    val onCopyFinished =
-        PixelCopy.OnPixelCopyFinishedListener { result ->
-            if (result == PixelCopy.SUCCESS) {
-                bitmapFuture.set(destBitmap)
-            } else {
-                bitmapFuture.setException(
-                    RuntimeException(String.format("PixelCopy failed: %d", result))
-                )
-            }
-        }
-    PixelCopy.request(this, null, destBitmap, onCopyFinished, handler)
-}
-
-internal fun View.generateBitmapFromDraw(
-    destBitmap: Bitmap,
-    bitmapFuture: ResolvableFuture<Bitmap>
-) {
-    destBitmap.density = resources.displayMetrics.densityDpi
-    computeScroll()
-    val canvas = Canvas(destBitmap)
-    canvas.translate((-scrollX).toFloat(), (-scrollY).toFloat())
-    draw(canvas)
-    bitmapFuture.set(destBitmap)
-}
-
-private fun View.getActivity(): Activity? {
-    fun Context.getActivity(): Activity? {
-        return when (this) {
-            is Activity -> this
-            is ContextWrapper -> this.baseContext.getActivity()
-            else -> null
-        }
-    }
-    return context.getActivity()
-}
-
-private fun View.generateBitmapFromPixelCopy(
-    window: Window,
-    destBitmap: Bitmap,
-    bitmapFuture: ResolvableFuture<Bitmap>
-) {
-    val locationInWindow = intArrayOf(0, 0)
-    getLocationInWindow(locationInWindow)
-    val x = locationInWindow[0]
-    val y = locationInWindow[1]
-    val boundsInWindow = Rect(x, y, x + width, y + height)
-
-    return window.generateBitmapFromPixelCopy(boundsInWindow, destBitmap, bitmapFuture)
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
deleted file mode 100644
index 070a451..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ /dev/null
@@ -1,188 +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.testing.screenshot
-
-import android.app.Activity
-import android.app.Dialog
-import android.graphics.Bitmap
-import android.os.Build
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import androidx.activity.ComponentActivity
-import androidx.test.ext.junit.rules.ActivityScenarioRule
-import java.util.concurrent.TimeUnit
-import org.junit.Assert.assertEquals
-import org.junit.rules.RuleChain
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import platform.test.screenshot.DeviceEmulationRule
-import platform.test.screenshot.DeviceEmulationSpec
-import platform.test.screenshot.MaterialYouColorsRule
-import platform.test.screenshot.ScreenshotTestRule
-import platform.test.screenshot.getEmulatedDevicePathConfig
-import platform.test.screenshot.matchers.BitmapMatcher
-
-/** A rule for View screenshot diff unit tests. */
-open class ViewScreenshotTestRule(
-    emulationSpec: DeviceEmulationSpec,
-    private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
-    assetsPathRelativeToBuildRoot: String
-) : TestRule {
-    private val colorsRule = MaterialYouColorsRule()
-    private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
-    protected val screenshotRule =
-        ScreenshotTestRule(
-            SystemUIGoldenImagePathManager(
-                getEmulatedDevicePathConfig(emulationSpec),
-                assetsPathRelativeToBuildRoot
-            )
-        )
-    private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
-    private val roboRule =
-        RuleChain.outerRule(deviceEmulationRule).around(screenshotRule).around(activityRule)
-    private val delegateRule = RuleChain.outerRule(colorsRule).around(roboRule)
-    private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
-
-    override fun apply(base: Statement, description: Description): Statement {
-        if (isRobolectric) {
-            // In robolectric mode, we enable NATIVE graphics and unpack font and icu files.
-            // We need to use reflection, as this library is only needed and therefore
-            //  only available in deviceless mode.
-            val nativeLoaderClassName = "org.robolectric.nativeruntime.DefaultNativeRuntimeLoader"
-            val defaultNativeRuntimeLoader = Class.forName(nativeLoaderClassName)
-            System.setProperty("robolectric.graphicsMode", "NATIVE")
-            defaultNativeRuntimeLoader.getMethod("injectAndLoad").invoke(null)
-        }
-        val ruleToApply = if (isRobolectric) roboRule else delegateRule
-        return ruleToApply.apply(base, description)
-    }
-
-    protected fun takeScreenshot(
-        mode: Mode = Mode.WrapContent,
-        viewProvider: (ComponentActivity) -> View,
-        beforeScreenshot: (ComponentActivity) -> Unit = {}
-    ): Bitmap {
-        activityRule.scenario.onActivity { activity ->
-            // Make sure that the activity draws full screen and fits the whole display instead of
-            // the system bars.
-            val window = activity.window
-            window.setDecorFitsSystemWindows(false)
-
-            // Set the content.
-            activity.setContentView(viewProvider(activity), mode.layoutParams)
-
-            // Elevation/shadows is not deterministic when doing hardware rendering, so we disable
-            // it for any view in the hierarchy.
-            window.decorView.removeElevationRecursively()
-
-            activity.currentFocus?.clearFocus()
-        }
-
-        // We call onActivity again because it will make sure that our Activity is done measuring,
-        // laying out and drawing its content (that we set in the previous onActivity lambda).
-        var contentView: View? = null
-        activityRule.scenario.onActivity { activity ->
-            // Check that the content is what we expected.
-            val content = activity.requireViewById<ViewGroup>(android.R.id.content)
-            assertEquals(1, content.childCount)
-            contentView = content.getChildAt(0)
-            beforeScreenshot(activity)
-        }
-
-        return if (isRobolectric) {
-            contentView?.captureToBitmap()?.get(10, TimeUnit.SECONDS)
-                ?: error("timeout while trying to capture view to bitmap")
-        } else {
-            contentView?.toBitmap() ?: error("contentView is null")
-        }
-    }
-
-    /**
-     * Compare the content of the view provided by [viewProvider] with the golden image identified
-     * by [goldenIdentifier] in the context of [emulationSpec].
-     */
-    fun screenshotTest(
-        goldenIdentifier: String,
-        mode: Mode = Mode.WrapContent,
-        beforeScreenshot: (ComponentActivity) -> Unit = {},
-        viewProvider: (ComponentActivity) -> View
-    ) {
-        val bitmap = takeScreenshot(mode, viewProvider, beforeScreenshot)
-        screenshotRule.assertBitmapAgainstGolden(
-            bitmap,
-            goldenIdentifier,
-            matcher,
-        )
-    }
-
-    /**
-     * Compare the content of the dialog provided by [dialogProvider] with the golden image
-     * identified by [goldenIdentifier] in the context of [emulationSpec].
-     */
-    fun dialogScreenshotTest(
-        goldenIdentifier: String,
-        dialogProvider: (Activity) -> Dialog,
-    ) {
-        var dialog: Dialog? = null
-        activityRule.scenario.onActivity { activity ->
-            dialog =
-                dialogProvider(activity).apply {
-                    // Make sure that the dialog draws full screen and fits the whole display
-                    // instead of the system bars.
-                    window.setDecorFitsSystemWindows(false)
-
-                    // Disable enter/exit animations.
-                    create()
-                    window.setWindowAnimations(0)
-
-                    // Elevation/shadows is not deterministic when doing hardware rendering, so we
-                    // disable it for any view in the hierarchy.
-                    window.decorView.removeElevationRecursively()
-
-                    // Show the dialog.
-                    show()
-                }
-        }
-
-        try {
-            val bitmap = dialog?.toBitmap() ?: error("dialog is null")
-            screenshotRule.assertBitmapAgainstGolden(
-                bitmap,
-                goldenIdentifier,
-                matcher,
-            )
-        } finally {
-            dialog?.dismiss()
-        }
-    }
-
-    private fun Dialog.toBitmap(): Bitmap {
-        val window = window
-        return window.decorView.toBitmap(window)
-    }
-
-    enum class Mode(val layoutParams: LayoutParams) {
-        WrapContent(LayoutParams(WRAP_CONTENT, WRAP_CONTENT)),
-        MatchSize(LayoutParams(MATCH_PARENT, MATCH_PARENT)),
-        MatchWidth(LayoutParams(MATCH_PARENT, WRAP_CONTENT)),
-        MatchHeight(LayoutParams(WRAP_CONTENT, MATCH_PARENT)),
-    }
-}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt
deleted file mode 100644
index d34f46b..0000000
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.android.systemui.testing.screenshot
-
-import android.graphics.Bitmap
-import android.graphics.Rect
-import android.os.Handler
-import android.os.Looper
-import android.view.PixelCopy
-import android.view.Window
-import androidx.concurrent.futures.ResolvableFuture
-
-/*
- * This file was forked from androidx/test/core/view/WindowCapture.kt.
- * TODO(b/195673633): Remove this fork and use the AndroidX version instead.
- */
-fun Window.generateBitmapFromPixelCopy(
-    boundsInWindow: Rect? = null,
-    destBitmap: Bitmap,
-    bitmapFuture: ResolvableFuture<Bitmap>
-) {
-    val onCopyFinished =
-        PixelCopy.OnPixelCopyFinishedListener { result ->
-            if (result == PixelCopy.SUCCESS) {
-                bitmapFuture.set(destBitmap)
-            } else {
-                bitmapFuture.setException(
-                    RuntimeException(String.format("PixelCopy failed: %d", result))
-                )
-            }
-        }
-    PixelCopy.request(
-        this,
-        boundsInWindow,
-        destBitmap,
-        onCopyFinished,
-        Handler(Looper.getMainLooper())
-    )
-}
diff --git a/packages/SystemUI/shared/res/values-my/bools.xml b/packages/SystemUI/shared/res/values-my/bools.xml
new file mode 100644
index 0000000..27cbe80
--- /dev/null
+++ b/packages/SystemUI/shared/res/values-my/bools.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<!-- Formatting note: terminate all comments with a period, to avoid breaking
+     the documentation output. To suppress comment lines from the documentation
+     output, insert an eat-comment element after the comment lines.
+-->
+
+<resources>
+    <!-- Whether to add padding at the bottom of the complication clock -->
+    <bool name="dream_overlay_complication_clock_bottom_padding">true</bool>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/res/values/bools.xml b/packages/SystemUI/shared/res/values/bools.xml
new file mode 100644
index 0000000..f22dac4
--- /dev/null
+++ b/packages/SystemUI/shared/res/values/bools.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<!-- Formatting note: terminate all comments with a period, to avoid breaking
+     the documentation output. To suppress comment lines from the documentation
+     output, insert an eat-comment element after the comment lines.
+-->
+
+<resources>
+    <!-- Whether to add padding at the bottom of the complication clock -->
+    <bool name="dream_overlay_complication_clock_bottom_padding">false</bool>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index b6aae69..f1a4007 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -24,14 +24,12 @@
 
 /**
  * Base interface for flags that can change value on a running device.
- * @property id unique id to help identify this flag. Must be unique. This will be removed soon.
  * @property teamfood Set to true to include this flag as part of the teamfood flag. This will
  *                    be removed soon.
  * @property name Used for server-side flagging where appropriate. Also used for display. No spaces.
  * @property namespace The server-side namespace that this flag lives under.
  */
 interface Flag<T> {
-    val id: Int
     val teamfood: Boolean
     val name: String
     val namespace: String
@@ -58,7 +56,6 @@
  */
 // Consider using the "parcelize" kotlin library.
 abstract class BooleanFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val default: Boolean = false,
@@ -74,8 +71,17 @@
         }
     }
 
+    private constructor(
+            id: Int,
+            name: String,
+            namespace: String,
+            default: Boolean,
+            teamfood: Boolean,
+            overridden: Boolean,
+            ) : this(name, namespace, default, teamfood, overridden)
+
     private constructor(parcel: Parcel) : this(
-        id = parcel.readInt(),
+        parcel.readInt(),
         name = parcel.readString() ?: "",
         namespace = parcel.readString() ?: "",
         default = parcel.readBoolean(),
@@ -84,7 +90,7 @@
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
-        parcel.writeInt(id)
+        parcel.writeInt(0)
         parcel.writeString(name)
         parcel.writeString(namespace)
         parcel.writeBoolean(default)
@@ -99,12 +105,11 @@
  * It can be changed or overridden in debug builds but not in release builds.
  */
 data class UnreleasedFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val teamfood: Boolean = false,
     override val overridden: Boolean = false
-) : BooleanFlag(id, name, namespace, false, teamfood, overridden)
+) : BooleanFlag(name, namespace, false, teamfood, overridden)
 
 /**
  * A Flag that is true by default.
@@ -112,12 +117,11 @@
  * It can be changed or overridden in any build, meaning it can be turned off if needed.
  */
 data class ReleasedFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val teamfood: Boolean = false,
     override val overridden: Boolean = false
-) : BooleanFlag(id, name, namespace, true, teamfood, overridden)
+) : BooleanFlag(name, namespace, true, teamfood, overridden)
 
 /**
  * A Flag that reads its default values from a resource overlay instead of code.
@@ -125,7 +129,6 @@
  * Prefer [UnreleasedFlag] and [ReleasedFlag].
  */
 data class ResourceBooleanFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     @BoolRes override val resourceId: Int,
@@ -140,7 +143,6 @@
  * Prefer [UnreleasedFlag] and [ReleasedFlag].
  */
 data class SysPropBooleanFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val default: Boolean = false,
@@ -150,7 +152,6 @@
 }
 
 data class StringFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val default: String = "",
@@ -165,15 +166,21 @@
         }
     }
 
+    private constructor(id: Int, name: String, namespace: String, default: String) : this(
+            name,
+            namespace,
+            default
+    )
+
     private constructor(parcel: Parcel) : this(
-        id = parcel.readInt(),
+        parcel.readInt(),
         name = parcel.readString() ?: "",
         namespace = parcel.readString() ?: "",
         default = parcel.readString() ?: ""
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
-        parcel.writeInt(id)
+        parcel.writeInt(0)
         parcel.writeString(name)
         parcel.writeString(namespace)
         parcel.writeString(default)
@@ -181,7 +188,6 @@
 }
 
 data class ResourceStringFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     @StringRes override val resourceId: Int,
@@ -189,7 +195,6 @@
 ) : ResourceFlag<String>
 
 data class IntFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val default: Int = 0,
@@ -205,15 +210,21 @@
         }
     }
 
+    private constructor(id: Int, name: String, namespace: String, default: Int) : this(
+            name,
+            namespace,
+            default
+    )
+
     private constructor(parcel: Parcel) : this(
-        id = parcel.readInt(),
+        parcel.readInt(),
         name = parcel.readString() ?: "",
         namespace = parcel.readString() ?: "",
         default = parcel.readInt()
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
-        parcel.writeInt(id)
+        parcel.writeInt(0)
         parcel.writeString(name)
         parcel.writeString(namespace)
         parcel.writeInt(default)
@@ -221,7 +232,6 @@
 }
 
 data class ResourceIntFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     @IntegerRes override val resourceId: Int,
@@ -229,11 +239,10 @@
 ) : ResourceFlag<Int>
 
 data class LongFlag constructor(
-    override val id: Int,
-    override val default: Long = 0,
-    override val teamfood: Boolean = false,
     override val name: String,
     override val namespace: String,
+    override val default: Long = 0,
+    override val teamfood: Boolean = false,
     override val overridden: Boolean = false
 ) : ParcelableFlag<Long> {
 
@@ -245,15 +254,21 @@
         }
     }
 
+    private constructor(id: Int, name: String, namespace: String, default: Long) : this(
+            name,
+            namespace,
+            default
+    )
+
     private constructor(parcel: Parcel) : this(
-        id = parcel.readInt(),
+        parcel.readInt(),
         name = parcel.readString() ?: "",
         namespace = parcel.readString() ?: "",
         default = parcel.readLong()
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
-        parcel.writeInt(id)
+        parcel.writeInt(0)
         parcel.writeString(name)
         parcel.writeString(namespace)
         parcel.writeLong(default)
@@ -261,7 +276,6 @@
 }
 
 data class FloatFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val default: Float = 0f,
@@ -277,15 +291,21 @@
         }
     }
 
+    private constructor(id: Int, name: String, namespace: String, default: Float) : this(
+            name,
+            namespace,
+            default
+    )
+
     private constructor(parcel: Parcel) : this(
-        id = parcel.readInt(),
+        parcel.readInt(),
         name = parcel.readString() ?: "",
         namespace = parcel.readString() ?: "",
         default = parcel.readFloat()
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
-        parcel.writeInt(id)
+        parcel.writeInt(0)
         parcel.writeString(name)
         parcel.writeString(namespace)
         parcel.writeFloat(default)
@@ -293,7 +313,6 @@
 }
 
 data class ResourceFloatFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val resourceId: Int,
@@ -301,7 +320,6 @@
 ) : ResourceFlag<Int>
 
 data class DoubleFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val default: Double = 0.0,
@@ -317,15 +335,21 @@
         }
     }
 
+    private constructor(id: Int, name: String, namespace: String, default: Double) : this(
+            name,
+            namespace,
+            default
+    )
+
     private constructor(parcel: Parcel) : this(
-        id = parcel.readInt(),
+        parcel.readInt(),
         name = parcel.readString() ?: "",
         namespace = parcel.readString() ?: "",
         default = parcel.readDouble()
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
-        parcel.writeInt(id)
+        parcel.writeInt(0)
         parcel.writeString(name)
         parcel.writeString(namespace)
         parcel.writeDouble(default)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index da1641c..1366226 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -116,13 +116,6 @@
     }
 
     /** Returns the stored value or null if not set.  */
-    // TODO(b/265188950): Remove method this once ids are fully deprecated.
-    fun <T> readFlagValue(id: Int, serializer: FlagSerializer<T>): T? {
-        val data = settings.getStringFromSecure(idToSettingsKey(id))
-        return serializer.fromSettingsData(data)
-    }
-
-    /** Returns the stored value or null if not set.  */
     fun <T> readFlagValue(name: String, serializer: FlagSerializer<T>): T? {
         val data = settings.getString(nameToSettingsKey(name))
         return serializer.fromSettingsData(data)
@@ -158,11 +151,6 @@
         return intent
     }
 
-    // TODO(b/265188950): Remove method this once ids are fully deprecated.
-    fun idToSettingsKey(id: Int): String {
-        return "$SETTINGS_PREFIX/$id"
-    }
-
     fun nameToSettingsKey(name: String): String {
         return "$SETTINGS_PREFIX/$name"
     }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
index 6beb851..e0a7fea 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
@@ -22,7 +22,6 @@
 
 class FlagSettingsHelper(private val contentResolver: ContentResolver) {
 
-    // TODO(b/265188950): Remove method this once ids are fully deprecated.
     fun getStringFromSecure(key: String): String? = Settings.Secure.getString(contentResolver, key)
 
     fun getString(key: String): String? = Settings.Global.getString(contentResolver, key)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
index 1f61c64..97d6099 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
@@ -215,7 +215,7 @@
             mSubscriptions.put(token, state);
 
             // Add and associate conditions.
-            normalizedCondition.getConditions().stream().forEach(condition -> {
+            normalizedCondition.getConditions().forEach(condition -> {
                 if (!mConditions.containsKey(condition)) {
                     mConditions.put(condition, new ArraySet<>());
                     condition.addCallback(mConditionCallback);
@@ -321,7 +321,6 @@
             private final Callback mCallback;
             private final Subscription mNestedSubscription;
             private final ArraySet<Condition> mConditions;
-            private final ArraySet<Condition> mPreconditions;
 
             /**
              * Default constructor specifying the {@link Callback} for the {@link Subscription}.
@@ -337,8 +336,7 @@
             private Builder(Subscription nestedSubscription, Callback callback) {
                 mNestedSubscription = nestedSubscription;
                 mCallback = callback;
-                mConditions = new ArraySet();
-                mPreconditions = new ArraySet();
+                mConditions = new ArraySet<>();
             }
 
             /**
@@ -352,29 +350,6 @@
             }
 
             /**
-             * Adds a set of {@link Condition} to be a precondition for {@link Subscription}.
-             *
-             * @return The updated {@link Builder}.
-             */
-            public Builder addPreconditions(Set<Condition> condition) {
-                if (condition == null) {
-                    return this;
-                }
-                mPreconditions.addAll(condition);
-                return this;
-            }
-
-            /**
-             * Adds a {@link Condition} to be a precondition for {@link Subscription}.
-             *
-             * @return The updated {@link Builder}.
-             */
-            public Builder addPrecondition(Condition condition) {
-                mPreconditions.add(condition);
-                return this;
-            }
-
-            /**
              * Adds a set of {@link Condition} to be associated with the {@link Subscription}.
              *
              * @return The updated {@link Builder}.
@@ -394,11 +369,7 @@
              * @return The resulting {@link Subscription}.
              */
             public Subscription build() {
-                final Subscription subscription =
-                        new Subscription(mConditions, mCallback, mNestedSubscription);
-                return !mPreconditions.isEmpty()
-                        ? new Subscription(mPreconditions, null, subscription)
-                        : subscription;
+                return new Subscription(mConditions, mCallback, mNestedSubscription);
             }
         }
     }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 117cf78a..4b31498 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -48,7 +48,7 @@
      *
      * Normal gesture: DOWN, MOVE/POINTER_DOWN/POINTER_UP)*, UP or CANCLE
      */
-    oneway void onStatusBarMotionEvent(in MotionEvent event) = 9;
+    oneway void onStatusBarTouchEvent(in MotionEvent event) = 9;
 
     /**
      * Proxies the assistant gesture's progress started from navigation bar.
@@ -125,5 +125,15 @@
      */
     oneway void takeScreenshot(in ScreenshotRequest request) = 51;
 
-    // Next id = 52
+    /**
+     * Dispatches trackpad status bar motion event to the notification shade. Currently these events
+     * are from the input monitor in {@link TouchInteractionService}. This is different from
+     * {@link #onStatusBarTouchEvent} above in that, this directly dispatches motion events to the
+     * notification shade, while {@link #onStatusBarTouchEvent} relies on setting the launcher
+     * window slippery to allow the frameworks to route those events after passing the initial
+     * threshold.
+     */
+    oneway void onStatusBarTrackpadEvent(in MotionEvent event) = 52;
+
+    // Next id = 53
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index fac2f91..3605ac2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -336,6 +336,14 @@
 
     @Override
     public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+
+        if (!(o instanceof Task)) {
+            return false;
+        }
+
         // Check that the id matches
         Task t = (Task) o;
         return key.equals(t.key);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
index 9a00447..f3296f0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
@@ -32,7 +32,7 @@
 @JvmOverloads
 constructor(
     val sampledView: View,
-    mainExecutor: Executor?,
+    val mainExecutor: Executor?,
     val bgExecutor: Executor?,
     val regionSamplingEnabled: Boolean,
     val isLockscreen: Boolean = false,
@@ -166,7 +166,7 @@
                         if (isLockscreen) WallpaperManager.FLAG_LOCK
                         else WallpaperManager.FLAG_SYSTEM
                     )
-                onColorsChanged(sampledRegionWithOffset, initialSampling)
+                mainExecutor?.execute { onColorsChanged(sampledRegionWithOffset, initialSampling) }
             }
         )
     }
@@ -202,8 +202,6 @@
 
     fun calculateScreenLocation(sampledView: View): RectF? {
 
-        if (!sampledView.isLaidOut) return null
-
         val screenLocation = tmpScreenLocation
         /**
          * The method getLocationOnScreen is used to obtain the view coordinates relative to its
@@ -219,6 +217,10 @@
         samplingBounds.right = left + sampledView.width
         samplingBounds.bottom = top + sampledView.height
 
+        // ensure never go out of bounds
+        if (samplingBounds.right > displaySize.x) samplingBounds.right = displaySize.x
+        if (samplingBounds.bottom > displaySize.y) samplingBounds.bottom = displaySize.y
+
         return RectF(samplingBounds)
     }
 
@@ -263,6 +265,8 @@
                 (colors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) !=
                     WallpaperColors.HINT_SUPPORTS_DARK_TEXT
             )
+        if (DEBUG)
+            Log.d(TAG, "onColorsChanged() | region darkness = $regionDarkness for region $area")
         updateForegroundColor()
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt
index 5a6f184..4b602cb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt
@@ -16,6 +16,8 @@
 package com.android.systemui.shared.shadow
 
 import android.content.Context
+import android.content.res.Resources
+import android.content.res.TypedArray
 import android.graphics.Canvas
 import android.util.AttributeSet
 import android.widget.TextClock
@@ -31,19 +33,40 @@
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
-    defStyleRes: Int = 0
+    defStyleRes: Int = 0,
 ) : TextClock(context, attrs, defStyleAttr, defStyleRes) {
-    private val mAmbientShadowInfo: ShadowInfo
-    private val mKeyShadowInfo: ShadowInfo
+    private lateinit var mAmbientShadowInfo: ShadowInfo
+    private lateinit var mKeyShadowInfo: ShadowInfo
+    private var attributesInput: TypedArray? = null
+    private var resources: Resources? = null
 
+    constructor(
+        resources: Resources,
+        context: Context,
+        attrs: AttributeSet? = null,
+        defStyleAttr: Int = 0,
+        defStyleRes: Int = 0,
+        attributesInput: TypedArray? = null
+    ) : this(context, attrs, defStyleAttr, defStyleRes) {
+        this.attributesInput = attributesInput
+        this.resources = resources
+        this.initializeAttributes(attrs, defStyleAttr, defStyleRes)
+    }
     init {
-        val attributes =
-            context.obtainStyledAttributes(
-                attrs,
-                R.styleable.DoubleShadowTextClock,
-                defStyleAttr,
-                defStyleRes
-            )
+        initializeAttributes(attrs, defStyleAttr, defStyleRes)
+    }
+
+    private fun initializeAttributes(attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) {
+        var attributes: TypedArray =
+            this.attributesInput
+                ?: context.obtainStyledAttributes(
+                    attrs,
+                    R.styleable.DoubleShadowTextClock,
+                    defStyleAttr,
+                    defStyleRes
+                )
+
+        var resource: Resources = this.resources ?: context.resources
         try {
             val keyShadowBlur =
                 attributes.getDimensionPixelSize(R.styleable.DoubleShadowTextClock_keyShadowBlur, 0)
@@ -98,18 +121,27 @@
                     0
                 )
             if (removeTextDescent) {
-                setPaddingRelative(
-                    0,
-                    0,
-                    0,
-                    textDescentExtraPadding - floor(paint.fontMetrics.descent.toDouble()).toInt()
-                )
+                val addBottomPaddingToClock =
+                    resource.getBoolean(R.bool.dream_overlay_complication_clock_bottom_padding)
+                val metrics = paint.fontMetrics
+                val padding =
+                    if (addBottomPaddingToClock) {
+                        textDescentExtraPadding +
+                            floor(metrics.descent.toDouble()).toInt() / paddingDividedOffset
+                    } else {
+                        textDescentExtraPadding - floor(metrics.descent.toDouble()).toInt()
+                    }
+                setPaddingRelative(0, 0, 0, padding)
             }
         } finally {
             attributes.recycle()
         }
     }
 
+    companion object {
+        private val paddingDividedOffset = 2
+    }
+
     public override fun onDraw(canvas: Canvas) {
         applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) }
     }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
index c142933..5edd283 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
@@ -27,6 +27,6 @@
      */
     fun ActivityManager.isInForeground(packageName: String): Boolean {
         val tasks: List<ActivityManager.RunningTaskInfo> = getRunningTasks(1)
-        return tasks.isNotEmpty() && packageName == tasks[0].topActivity.packageName
+        return tasks.isNotEmpty() && packageName == tasks[0].topActivity?.packageName
     }
 }
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 4269530..77f6d03 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
@@ -28,8 +28,6 @@
 import java.lang.annotation.RetentionPolicy;
 
 public final class InteractionJankMonitorWrapper {
-    private static final String TAG = "JankMonitorWrapper";
-
     // Launcher journeys.
     public static final int CUJ_APP_LAUNCH_FROM_RECENTS =
             InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS;
@@ -37,6 +35,8 @@
             InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON;
     public static final int CUJ_APP_CLOSE_TO_HOME =
             InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME;
+    public static final int CUJ_APP_CLOSE_TO_HOME_FALLBACK =
+            InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
     public static final int CUJ_APP_CLOSE_TO_PIP =
             InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP;
     public static final int CUJ_QUICK_SWITCH =
@@ -61,11 +61,14 @@
             InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
     public static final int CUJ_OPEN_SEARCH_RESULT =
             InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
+    public static final int CUJ_SHADE_EXPAND_FROM_STATUS_BAR =
+            InteractionJankMonitor.CUJ_SHADE_EXPAND_FROM_STATUS_BAR;
 
     @IntDef({
             CUJ_APP_LAUNCH_FROM_RECENTS,
             CUJ_APP_LAUNCH_FROM_ICON,
             CUJ_APP_CLOSE_TO_HOME,
+            CUJ_APP_CLOSE_TO_HOME_FALLBACK,
             CUJ_APP_CLOSE_TO_PIP,
             CUJ_QUICK_SWITCH,
             CUJ_APP_LAUNCH_FROM_WIDGET,
@@ -76,6 +79,7 @@
             CUJ_CLOSE_ALL_APPS_SWIPE,
             CUJ_CLOSE_ALL_APPS_TO_HOME,
             CUJ_OPEN_SEARCH_RESULT,
+            CUJ_SHADE_EXPAND_FROM_STATUS_BAR,
     })
     @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 3c447a8..4b14d3cf 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
@@ -42,8 +42,6 @@
             "com.google.android.apps.nexuslauncher.NexusLauncherActivity";
 
     public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
-    public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius";
-    public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners";
     public static final String KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER = "extra_unfold_animation";
     // See ISysuiUnlockAnimationController.aidl
     public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";
@@ -324,7 +322,7 @@
      * Returns whether the specified sysui state is such that the back gesture should be
      * disabled.
      */
-    public static boolean isBackGestureDisabled(int sysuiStateFlags) {
+    public static boolean isBackGestureDisabled(int sysuiStateFlags, boolean forTrackpad) {
         // Always allow when the bouncer/global actions/voice session is showing (even on top of
         // the keyguard)
         if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
@@ -335,16 +333,23 @@
         if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
             sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN;
         }
+
+        return (sysuiStateFlags & getBackGestureDisabledMask(forTrackpad)) != 0;
+    }
+
+    private static int getBackGestureDisabledMask(boolean forTrackpad) {
         // Disable when in immersive, or the notifications are interactive
-        int disableFlags = SYSUI_STATE_NAV_BAR_HIDDEN | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+        int disableFlags = SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+        if (!forTrackpad) {
+            disableFlags |= SYSUI_STATE_NAV_BAR_HIDDEN;
+        }
 
         // EdgeBackGestureHandler ignores Back gesture when SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED.
         // To allow Shade to respond to Back, we're bypassing this check (behind a flag).
         if (!ALLOW_BACK_GESTURE_IN_SHADE) {
             disableFlags |= SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
         }
-
-        return (sysuiStateFlags & disableFlags) != 0;
+        return disableFlags;
     }
 
     /**
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
deleted file mode 100644
index 74c325d..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ /dev/null
@@ -1,541 +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.shared.system;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
-import static android.view.WindowManager.TRANSIT_SLEEP;
-
-import android.annotation.SuppressLint;
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.IApplicationThread;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.view.IRecentsAnimationController;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.window.IRemoteTransition;
-import android.window.IRemoteTransitionFinishedCallback;
-import android.window.PictureInPictureSurfaceTransaction;
-import android.window.RemoteTransition;
-import android.window.TaskSnapshot;
-import android.window.TransitionInfo;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.wm.shell.util.TransitionUtil;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Helper class to build {@link RemoteTransition} objects
- */
-public class RemoteTransitionCompat {
-    private static final String TAG = "RemoteTransitionCompat";
-
-    /** Constructor specifically for recents animation */
-    public static RemoteTransition newRemoteTransition(RecentsAnimationListener recents,
-            IApplicationThread appThread) {
-        IRemoteTransition remote = new IRemoteTransition.Stub() {
-            final RecentsControllerWrap mRecentsSession = new RecentsControllerWrap();
-            IBinder mToken = null;
-
-            @Override
-            public void startAnimation(IBinder transition, TransitionInfo info,
-                    SurfaceControl.Transaction t,
-                    IRemoteTransitionFinishedCallback finishedCallback) {
-                // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
-                mToken = transition;
-                mRecentsSession.start(recents, mToken, info, t, finishedCallback);
-            }
-
-            @Override
-            public void mergeAnimation(IBinder transition, TransitionInfo info,
-                    SurfaceControl.Transaction t, IBinder mergeTarget,
-                    IRemoteTransitionFinishedCallback finishedCallback) {
-                if (mergeTarget.equals(mToken) && mRecentsSession.merge(info, t)) {
-                    try {
-                        finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error merging transition.", e);
-                    }
-                    // commit taskAppeared after merge transition finished.
-                    mRecentsSession.commitTasksAppearedIfNeeded();
-                } else {
-                    t.close();
-                    info.releaseAllSurfaces();
-                }
-            }
-        };
-        return new RemoteTransition(remote, appThread, "Recents");
-    }
-
-    /**
-     * Wrapper to hook up parts of recents animation to shell transition.
-     * TODO(b/177438007): Remove this once Launcher handles shell transitions directly.
-     */
-    @VisibleForTesting
-    static class RecentsControllerWrap extends IRecentsAnimationController.Default {
-        private RecentsAnimationListener mListener = null;
-        private IRemoteTransitionFinishedCallback mFinishCB = null;
-
-        /**
-         * List of tasks that we are switching away from via this transition. Upon finish, these
-         * pausing tasks will become invisible.
-         * These need to be ordered since the order must be restored if there is no task-switch.
-         */
-        private ArrayList<TaskState> mPausingTasks = null;
-
-        /**
-         * List of tasks that we are switching to. Upon finish, these will remain visible and
-         * on top.
-         */
-        private ArrayList<TaskState> mOpeningTasks = null;
-
-        private WindowContainerToken mPipTask = null;
-        private WindowContainerToken mRecentsTask = null;
-        private int mRecentsTaskId = 0;
-        private TransitionInfo mInfo = null;
-        private boolean mOpeningSeparateHome = false;
-        private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
-        private PictureInPictureSurfaceTransaction mPipTransaction = null;
-        private IBinder mTransition = null;
-        private boolean mKeyguardLocked = false;
-        private RemoteAnimationTarget[] mAppearedTargets;
-        private boolean mWillFinishToHome = false;
-
-        /** The animation is idle, waiting for the user to choose a task to switch to. */
-        private static final int STATE_NORMAL = 0;
-
-        /** The user chose a new task to switch to and the animation is animating to it. */
-        private static final int STATE_NEW_TASK = 1;
-
-        /** The latest state that the recents animation is operating in. */
-        private int mState = STATE_NORMAL;
-
-        void start(RecentsAnimationListener listener,
-                IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
-                IRemoteTransitionFinishedCallback finishedCallback) {
-            if (mInfo != null) {
-                throw new IllegalStateException("Trying to run a new recents animation while"
-                        + " recents is already active.");
-            }
-            mListener = listener;
-            mInfo = info;
-            mFinishCB = finishedCallback;
-            mPausingTasks = new ArrayList<>();
-            mOpeningTasks = new ArrayList<>();
-            mPipTask = null;
-            mRecentsTask = null;
-            mRecentsTaskId = -1;
-            mLeashMap = new ArrayMap<>();
-            mTransition = transition;
-            mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
-            mState = STATE_NORMAL;
-
-            final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
-            final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>();
-            TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter();
-            // About layering: we divide up the "layer space" into 3 regions (each the size of
-            // the change count). This lets us categorize things into above/below/between
-            // while maintaining their relative ordering.
-            for (int i = 0; i < info.getChanges().size(); ++i) {
-                final TransitionInfo.Change change = info.getChanges().get(i);
-                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-                if (TransitionUtil.isWallpaper(change)) {
-                    final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
-                            // wallpapers go into the "below" layer space
-                            info.getChanges().size() - i, info, t, mLeashMap);
-                    wallpapers.add(target);
-                    // Make all the wallpapers opaque since we want them visible from the start
-                    t.setAlpha(target.leash, 1);
-                } else if (leafTaskFilter.test(change)) {
-                    // start by putting everything into the "below" layer space.
-                    final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
-                            info.getChanges().size() - i, info, t, mLeashMap);
-                    apps.add(target);
-                    if (TransitionUtil.isClosingType(change.getMode())) {
-                        // raise closing (pausing) task to "above" layer so it isn't covered
-                        t.setLayer(target.leash, info.getChanges().size() * 3 - i);
-                        mPausingTasks.add(new TaskState(change, target.leash));
-                        if (taskInfo.pictureInPictureParams != null
-                                && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) {
-                            mPipTask = taskInfo.token;
-                        }
-                    } else if (taskInfo != null
-                            && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
-                        // There's a 3p launcher, so make sure recents goes above that.
-                        t.setLayer(target.leash, info.getChanges().size() * 3 - i);
-                        mRecentsTask = taskInfo.token;
-                        mRecentsTaskId = taskInfo.taskId;
-                    } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
-                        mRecentsTask = taskInfo.token;
-                        mRecentsTaskId = taskInfo.taskId;
-                    } else if (TransitionUtil.isOpeningType(change.getMode())) {
-                        mOpeningTasks.add(new TaskState(change, target.leash));
-                    }
-                }
-            }
-            t.apply();
-            mListener.onAnimationStart(new RecentsAnimationControllerCompat(this),
-                    apps.toArray(new RemoteAnimationTarget[apps.size()]),
-                    wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
-                    new Rect(0, 0, 0, 0), new Rect());
-        }
-
-        @SuppressLint("NewApi")
-        boolean merge(TransitionInfo info, SurfaceControl.Transaction t) {
-            if (info.getType() == TRANSIT_SLEEP) {
-                // A sleep event means we need to stop animations immediately, so cancel here.
-                mListener.onAnimationCanceled(new HashMap<>());
-                finish(mWillFinishToHome, false /* userLeaveHint */);
-                return false;
-            }
-            ArrayList<TransitionInfo.Change> openingTasks = null;
-            ArrayList<TransitionInfo.Change> closingTasks = null;
-            mAppearedTargets = null;
-            mOpeningSeparateHome = false;
-            TransitionInfo.Change recentsOpening = null;
-            boolean foundRecentsClosing = false;
-            boolean hasChangingApp = false;
-            final TransitionUtil.LeafTaskFilter leafTaskFilter =
-                    new TransitionUtil.LeafTaskFilter();
-            for (int i = 0; i < info.getChanges().size(); ++i) {
-                final TransitionInfo.Change change = info.getChanges().get(i);
-                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-                final boolean isLeafTask = leafTaskFilter.test(change);
-                if (TransitionUtil.isOpeningType(change.getMode())) {
-                    if (mRecentsTask.equals(change.getContainer())) {
-                        recentsOpening = change;
-                    } else if (isLeafTask) {
-                        if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
-                            // This is usually a 3p launcher
-                            mOpeningSeparateHome = true;
-                        }
-                        if (openingTasks == null) {
-                            openingTasks = new ArrayList<>();
-                        }
-                        openingTasks.add(change);
-                    }
-                } else if (TransitionUtil.isClosingType(change.getMode())) {
-                    if (mRecentsTask.equals(change.getContainer())) {
-                        foundRecentsClosing = true;
-                    } else if (isLeafTask) {
-                        if (closingTasks == null) {
-                            closingTasks = new ArrayList<>();
-                        }
-                        closingTasks.add(change);
-                    }
-                } else if (change.getMode() == TRANSIT_CHANGE) {
-                    // Finish recents animation if the display is changed, so the default
-                    // transition handler can play the animation such as rotation effect.
-                    if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) {
-                        mListener.onSwitchToScreenshot(() -> finish(false /* toHome */,
-                                false /* userLeaveHint */));
-                        return false;
-                    }
-                    hasChangingApp = true;
-                }
-            }
-            if (hasChangingApp && foundRecentsClosing) {
-                // This happens when a visible app is expanding (usually PiP). In this case,
-                // that transition probably has a special-purpose animation, so finish recents
-                // now and let it do its animation (since recents is going to be occluded).
-                if (!mListener.onSwitchToScreenshot(
-                        () -> finish(true /* toHome */, false /* userLeaveHint */))) {
-                    Log.w(TAG, "Recents callback doesn't support support switching to screenshot"
-                            + ", there might be a flicker.");
-                    finish(true /* toHome */, false /* userLeaveHint */);
-                }
-                return false;
-            }
-            if (recentsOpening != null) {
-                // the recents task re-appeared. This happens if the user gestures before the
-                // task-switch (NEW_TASK) animation finishes.
-                if (mState == STATE_NORMAL) {
-                    Log.e(TAG, "Returning to recents while recents is already idle.");
-                }
-                if (closingTasks == null || closingTasks.size() == 0) {
-                    Log.e(TAG, "Returning to recents without closing any opening tasks.");
-                }
-                // Setup may hide it initially since it doesn't know that overview was still active.
-                t.show(recentsOpening.getLeash());
-                t.setAlpha(recentsOpening.getLeash(), 1.f);
-                mState = STATE_NORMAL;
-            }
-            boolean didMergeThings = false;
-            if (closingTasks != null) {
-                // Cancelling a task-switch. Move the tasks back to mPausing from mOpening
-                for (int i = 0; i < closingTasks.size(); ++i) {
-                    final TransitionInfo.Change change = closingTasks.get(i);
-                    int openingIdx = TaskState.indexOf(mOpeningTasks, change);
-                    if (openingIdx < 0) {
-                        Log.e(TAG, "Back to existing recents animation from an unrecognized "
-                                + "task: " + change.getTaskInfo().taskId);
-                        continue;
-                    }
-                    mPausingTasks.add(mOpeningTasks.remove(openingIdx));
-                    didMergeThings = true;
-                }
-            }
-            if (openingTasks != null && openingTasks.size() > 0) {
-                // Switching to some new tasks, add to mOpening and remove from mPausing. Also,
-                // enter NEW_TASK state since this will start the switch-to animation.
-                final int layer = mInfo.getChanges().size() * 3;
-                final RemoteAnimationTarget[] targets =
-                        new RemoteAnimationTarget[openingTasks.size()];
-                for (int i = 0; i < openingTasks.size(); ++i) {
-                    final TransitionInfo.Change change = openingTasks.get(i);
-                    int pausingIdx = TaskState.indexOf(mPausingTasks, change);
-                    if (pausingIdx >= 0) {
-                        // Something is showing/opening a previously-pausing app.
-                        targets[i] = TransitionUtil.newTarget(change, layer,
-                                mPausingTasks.get(pausingIdx).mLeash);
-                        mOpeningTasks.add(mPausingTasks.remove(pausingIdx));
-                        // Setup hides opening tasks initially, so make it visible again (since we
-                        // are already showing it).
-                        t.show(change.getLeash());
-                        t.setAlpha(change.getLeash(), 1.f);
-                    } else {
-                        // We are receiving new opening tasks, so convert to onTasksAppeared.
-                        targets[i] = TransitionUtil.newTarget(change, layer, info, t, mLeashMap);
-                        // reparent into the original `mInfo` since that's where we are animating.
-                        final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
-                        t.reparent(targets[i].leash, mInfo.getRoot(rootIdx).getLeash());
-                        t.setLayer(targets[i].leash, layer);
-                        mOpeningTasks.add(new TaskState(change, targets[i].leash));
-                    }
-                }
-                didMergeThings = true;
-                mState = STATE_NEW_TASK;
-                mAppearedTargets = targets;
-            }
-            if (!didMergeThings) {
-                // Didn't recognize anything in incoming transition so don't merge it.
-                Log.w(TAG, "Don't know how to merge this transition.");
-                return false;
-            }
-            t.apply();
-            // not using the incoming anim-only surfaces
-            info.releaseAnimSurfaces();
-            return true;
-        }
-
-        private void commitTasksAppearedIfNeeded() {
-            if (mAppearedTargets != null) {
-                mListener.onTasksAppeared(mAppearedTargets);
-                mAppearedTargets = null;
-            }
-        }
-
-        @Override public TaskSnapshot screenshotTask(int taskId) {
-            try {
-                return ActivityTaskManager.getService().takeTaskSnapshot(taskId,
-                        true /* updateCache */);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to screenshot task", e);
-            }
-            return null;
-        }
-
-        @Override public void setInputConsumerEnabled(boolean enabled) {
-            if (!enabled) return;
-            // transient launches don't receive focus automatically. Since we are taking over
-            // the gesture now, take focus explicitly.
-            // This also moves recents back to top if the user gestured before a switch
-            // animation finished.
-            try {
-                ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to set focused task", e);
-            }
-        }
-
-        @Override public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) {
-        }
-
-        @Override public void setFinishTaskTransaction(int taskId,
-                PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
-            mPipTransaction = finishTransaction;
-        }
-
-        @Override
-        @SuppressLint("NewApi")
-        public void finish(boolean toHome, boolean sendUserLeaveHint) {
-            if (mFinishCB == null) {
-                Log.e(TAG, "Duplicate call to finish", new RuntimeException());
-                return;
-            }
-            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
-
-            if (mKeyguardLocked && mRecentsTask != null) {
-                if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
-                else wct.restoreTransientOrder(mRecentsTask);
-            }
-            if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
-                // The gesture is returning to the pausing-task(s) rather than continuing with
-                // recents, so end the transition by moving the app back to the top (and also
-                // re-showing it's task).
-                for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
-                    // reverse order so that index 0 ends up on top
-                    wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */);
-                    t.show(mPausingTasks.get(i).mTaskSurface);
-                }
-                if (!mKeyguardLocked && mRecentsTask != null) {
-                    wct.restoreTransientOrder(mRecentsTask);
-                }
-            } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) {
-                // Special situation where 3p launcher was changed during recents (this happens
-                // during tapltests...). Here we get both "return to home" AND "home opening".
-                // This is basically going home, but we have to restore the recents and home order.
-                for (int i = 0; i < mOpeningTasks.size(); ++i) {
-                    final TaskState state = mOpeningTasks.get(i);
-                    if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
-                        // Make sure it is on top.
-                        wct.reorder(state.mToken, true /* onTop */);
-                    }
-                    t.show(state.mTaskSurface);
-                }
-                for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
-                    t.hide(mPausingTasks.get(i).mTaskSurface);
-                }
-                if (!mKeyguardLocked && mRecentsTask != null) {
-                    wct.restoreTransientOrder(mRecentsTask);
-                }
-            } else {
-                // The general case: committing to recents, going home, or switching tasks.
-                for (int i = 0; i < mOpeningTasks.size(); ++i) {
-                    t.show(mOpeningTasks.get(i).mTaskSurface);
-                }
-                for (int i = 0; i < mPausingTasks.size(); ++i) {
-                    if (!sendUserLeaveHint) {
-                        // This means recents is not *actually* finishing, so of course we gotta
-                        // do special stuff in WMCore to accommodate.
-                        wct.setDoNotPip(mPausingTasks.get(i).mToken);
-                    }
-                    // Since we will reparent out of the leashes, pre-emptively hide the child
-                    // surface to match the leash. Otherwise, there will be a flicker before the
-                    // visibility gets committed in Core when using split-screen (in splitscreen,
-                    // the leaf-tasks are not "independent" so aren't hidden by normal setup).
-                    t.hide(mPausingTasks.get(i).mTaskSurface);
-                }
-                if (mPipTask != null && mPipTransaction != null && sendUserLeaveHint) {
-                    t.show(mInfo.getChange(mPipTask).getLeash());
-                    PictureInPictureSurfaceTransaction.apply(mPipTransaction,
-                            mInfo.getChange(mPipTask).getLeash(), t);
-                    mPipTask = null;
-                    mPipTransaction = null;
-                }
-            }
-            try {
-                mFinishCB.onTransitionFinished(wct.isEmpty() ? null : wct, t);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to call animation finish callback", e);
-                t.apply();
-            }
-            // Only release the non-local created surface references. The animator is responsible
-            // for releasing the leashes created by local.
-            mInfo.releaseAllSurfaces();
-            // Reset all members.
-            mListener = null;
-            mFinishCB = null;
-            mPausingTasks = null;
-            mOpeningTasks = null;
-            mAppearedTargets = null;
-            mInfo = null;
-            mOpeningSeparateHome = false;
-            mLeashMap = null;
-            mTransition = null;
-            mState = STATE_NORMAL;
-        }
-
-        @Override public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
-        }
-
-        @Override public void cleanupScreenshot() {
-        }
-
-        @Override public void setWillFinishToHome(boolean willFinishToHome) {
-            mWillFinishToHome = willFinishToHome;
-        }
-
-        /**
-         * @see IRecentsAnimationController#removeTask
-         */
-        @Override public boolean removeTask(int taskId) {
-            return false;
-        }
-
-        /**
-         * @see IRecentsAnimationController#detachNavigationBarFromApp
-         */
-        @Override public void detachNavigationBarFromApp(boolean moveHomeToTop) {
-            try {
-                ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to detach the navigation bar from app", e);
-            }
-        }
-
-        /**
-         * @see IRecentsAnimationController#animateNavigationBarToApp(long)
-         */
-        @Override public void animateNavigationBarToApp(long duration) {
-        }
-    }
-
-    /** Utility class to track the state of a task as-seen by recents. */
-    private static class TaskState {
-        WindowContainerToken mToken;
-        ActivityManager.RunningTaskInfo mTaskInfo;
-
-        /** The surface/leash of the task provided by Core. */
-        SurfaceControl mTaskSurface;
-
-        /** The (local) animation-leash created for this task. */
-        SurfaceControl mLeash;
-
-        TaskState(TransitionInfo.Change change, SurfaceControl leash) {
-            mToken = change.getContainer();
-            mTaskInfo = change.getTaskInfo();
-            mTaskSurface = change.getLeash();
-            mLeash = leash;
-        }
-
-        static int indexOf(ArrayList<TaskState> list, TransitionInfo.Change change) {
-            for (int i = list.size() - 1; i >= 0; --i) {
-                if (list.get(i).mToken.equals(change.getContainer())) {
-                    return i;
-                }
-            }
-            return -1;
-        }
-
-        public String toString() {
-            return "" + mToken + " : " + mLeash;
-        }
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
index d7e61d6..ebc57d2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
@@ -31,15 +31,15 @@
     var visibleOnScreen = false
 
     constructor(parcel: Parcel) : this() {
-        this.boundsOnScreen = parcel.readParcelable(Rect::javaClass.javaClass.classLoader)
+        this.boundsOnScreen = parcel.readParcelable(Rect::javaClass.javaClass.classLoader) ?: Rect()
         this.selectedPage = parcel.readInt()
         this.visibleOnScreen = parcel.readBoolean()
     }
 
-    override fun writeToParcel(dest: Parcel?, flags: Int) {
-        dest?.writeParcelable(boundsOnScreen, 0)
-        dest?.writeInt(selectedPage)
-        dest?.writeBoolean(visibleOnScreen)
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(boundsOnScreen, 0)
+        dest.writeInt(selectedPage)
+        dest.writeBoolean(visibleOnScreen)
     }
 
     override fun describeContents(): Int {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java b/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java
deleted file mode 100644
index 98212e1..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.tracing;
-
-import android.os.Trace;
-import android.util.Log;
-import android.view.Choreographer;
-
-import com.android.internal.util.TraceBuffer;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Queue;
-import java.util.function.Consumer;
-
-/**
- * A proto tracer implementation that can be updated directly (upon state change), or on the next
- * scheduled frame.
- *
- * @param <P> The class type of the proto provider
- * @param <S> The proto class type of the encapsulating proto
- * @param <T> The proto class type of the individual proto entries in the buffer
- * @param <R> The proto class type of the entry root proto in the buffer
- */
-public class FrameProtoTracer<P, S extends P, T extends P, R>
-        implements Choreographer.FrameCallback {
-
-    private static final String TAG = "FrameProtoTracer";
-    private static final int BUFFER_CAPACITY = 1024 * 1024;
-
-    private final Object mLock = new Object();
-    private final TraceBuffer<P, S, T> mBuffer;
-    private final File mTraceFile;
-    private final ProtoTraceParams<P, S, T, R> mParams;
-    private Choreographer mChoreographer;
-    private final Queue<T> mPool = new ArrayDeque<>();
-    private final ArrayList<ProtoTraceable<R>> mTraceables = new ArrayList<>();
-    private final ArrayList<ProtoTraceable<R>> mTmpTraceables = new ArrayList<>();
-
-    private volatile boolean mEnabled;
-    private boolean mFrameScheduled;
-
-    private final TraceBuffer.ProtoProvider<P, S, T> mProvider =
-            new TraceBuffer.ProtoProvider<P, S, T>() {
-        @Override
-        public int getItemSize(P proto) {
-            return mParams.getProtoSize(proto);
-        }
-
-        @Override
-        public byte[] getBytes(P proto) {
-            return mParams.getProtoBytes(proto);
-        }
-
-        @Override
-        public void write(S encapsulatingProto, Queue<T> buffer, OutputStream os)
-                throws IOException {
-            os.write(mParams.serializeEncapsulatingProto(encapsulatingProto, buffer));
-        }
-    };
-
-    public interface ProtoTraceParams<P, S, T, R> {
-        File getTraceFile();
-        S getEncapsulatingTraceProto();
-        T updateBufferProto(T reuseObj, ArrayList<ProtoTraceable<R>> traceables);
-        byte[] serializeEncapsulatingProto(S encapsulatingProto, Queue<T> buffer);
-        byte[] getProtoBytes(P proto);
-        int getProtoSize(P proto);
-    }
-
-    public FrameProtoTracer(ProtoTraceParams<P, S, T, R> params) {
-        mParams = params;
-        mBuffer = new TraceBuffer<>(BUFFER_CAPACITY, mProvider, new Consumer<T>() {
-            @Override
-            public void accept(T t) {
-                onProtoDequeued(t);
-            }
-        });
-        mTraceFile = params.getTraceFile();
-    }
-
-    public void start() {
-        synchronized (mLock) {
-            if (mEnabled) {
-                return;
-            }
-            mBuffer.resetBuffer();
-            mEnabled = true;
-        }
-        logState();
-    }
-
-    public void stop() {
-        synchronized (mLock) {
-            if (!mEnabled) {
-                return;
-            }
-            mEnabled = false;
-        }
-        writeToFile();
-    }
-
-    public boolean isEnabled() {
-        return mEnabled;
-    }
-
-    public void add(ProtoTraceable<R> traceable) {
-        synchronized (mLock) {
-            mTraceables.add(traceable);
-        }
-    }
-
-    public void remove(ProtoTraceable<R> traceable) {
-        synchronized (mLock) {
-            mTraceables.remove(traceable);
-        }
-    }
-
-    public void scheduleFrameUpdate() {
-        if (!mEnabled || mFrameScheduled) {
-            return;
-        }
-
-        // Schedule an update on the next frame
-        if (mChoreographer == null) {
-            mChoreographer = Choreographer.getMainThreadInstance();
-        }
-        mChoreographer.postFrameCallback(this);
-        mFrameScheduled = true;
-    }
-
-    public void update() {
-        if (!mEnabled) {
-            return;
-        }
-
-        logState();
-    }
-
-    public float getBufferUsagePct() {
-        return (float) mBuffer.getBufferSize() / BUFFER_CAPACITY;
-    }
-
-    @Override
-    public void doFrame(long frameTimeNanos) {
-        logState();
-    }
-
-    private void onProtoDequeued(T proto) {
-        mPool.add(proto);
-    }
-
-    private void logState() {
-        synchronized (mLock) {
-            mTmpTraceables.addAll(mTraceables);
-        }
-
-        mBuffer.add(mParams.updateBufferProto(mPool.poll(), mTmpTraceables));
-        mTmpTraceables.clear();
-        mFrameScheduled = false;
-    }
-
-    private void writeToFile() {
-        try {
-            Trace.beginSection("ProtoTracer.writeToFile");
-            mBuffer.writeTraceToFile(mTraceFile, mParams.getEncapsulatingTraceProto());
-        } catch (IOException e) {
-            Log.e(TAG, "Unable to write buffer to file", e);
-        } finally {
-            Trace.endSection();
-        }
-    }
-}
-
-
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/ProtoTraceable.java b/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/ProtoTraceable.java
deleted file mode 100644
index e05b0b0..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/ProtoTraceable.java
+++ /dev/null
@@ -1,26 +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.systemui.shared.tracing;
-
-/**
- * @see FrameProtoTracer
- */
-public interface ProtoTraceable<T> {
-
-    /**
-     * NOTE: Implementations should update all fields in this proto.
-     */
-    void writeToProto(T proto);
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
index aca9907..dac130d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
@@ -39,7 +39,7 @@
 
     fun init() {
         rotationChangeProvider.addCallback(rotationListener)
-        rotationListener.onRotationChanged(context.display.rotation)
+        context.display?.rotation?.let { rotationListener.onRotationChanged(it) }
     }
 
     private val rotationListener = RotationListener { rotation ->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
index 64234c2..96a974d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
@@ -18,22 +18,23 @@
 
 import android.os.Trace
 import android.os.TraceNameSupplier
+import java.util.concurrent.atomic.AtomicInteger
 
 /**
- * Run a block within a [Trace] section.
- * Calls [Trace.beginSection] before and [Trace.endSection] after the passed block.
+ * Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection]
+ * after the passed block.
  */
 inline fun <T> traceSection(tag: String, block: () -> T): T =
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
-            Trace.traceBegin(Trace.TRACE_TAG_APP, tag)
-            try {
-                block()
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_APP)
-            }
-        } else {
+    if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
+        Trace.traceBegin(Trace.TRACE_TAG_APP, tag)
+        try {
             block()
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_APP)
         }
+    } else {
+        block()
+    }
 
 class TraceUtils {
     companion object {
@@ -43,6 +44,7 @@
 
         /**
          * Helper function for creating a Runnable object that implements TraceNameSupplier.
+         *
          * This is useful for posting Runnables to Handlers with meaningful names.
          */
         inline fun namedRunnable(tag: String, crossinline block: () -> Unit): Runnable {
@@ -51,5 +53,37 @@
                 override fun run() = block()
             }
         }
+
+        /**
+         * Cookie used for async traces. Shouldn't be public, but to use it inside inline methods
+         * there is no other way around.
+         */
+        val lastCookie = AtomicInteger(0)
+
+        /**
+         * Creates an async slice in a track called "AsyncTraces".
+         *
+         * This can be used to trace coroutine code. Note that all usages of this method will appear
+         * under a single track.
+         */
+        inline fun <T> traceAsync(method: String, block: () -> T): T =
+            traceAsync("AsyncTraces", method, block)
+
+        /**
+         * Creates an async slice in a track with [trackName] while [block] runs.
+         *
+         * This can be used to trace coroutine code. [method] will be the name of the slice,
+         * [trackName] of the track. The track is one of the rows visible in a perfetto trace inside
+         * SystemUI process.
+         */
+        inline fun <T> traceAsync(trackName: String, method: String, block: () -> T): T {
+            val cookie = lastCookie.incrementAndGet()
+            Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, trackName, method, cookie)
+            try {
+                return block()
+            } finally {
+                Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, cookie)
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index c22d689..3360c96 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -29,35 +29,31 @@
         }
 
     fun unreleasedFlag(
-        id: Int,
         name: String,
         namespace: String = "systemui",
         teamfood: Boolean = false
     ): UnreleasedFlag {
-        val flag = UnreleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
+        val flag = UnreleasedFlag(name = name, namespace = namespace, teamfood = teamfood)
         checkForDupesAndAdd(flag)
         return flag
     }
 
     fun releasedFlag(
-        id: Int,
         name: String,
         namespace: String = "systemui",
     ): ReleasedFlag {
-        val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = false)
+        val flag = ReleasedFlag(name = name, namespace = namespace, teamfood = false)
         checkForDupesAndAdd(flag)
         return flag
     }
 
     fun resourceBooleanFlag(
-        id: Int,
         @BoolRes resourceId: Int,
         name: String,
         namespace: String = "systemui",
     ): ResourceBooleanFlag {
         val flag =
             ResourceBooleanFlag(
-                id = id,
                 name = name,
                 namespace = namespace,
                 resourceId = resourceId,
@@ -68,13 +64,11 @@
     }
 
     fun sysPropBooleanFlag(
-        id: Int,
         name: String,
         namespace: String = "systemui",
         default: Boolean = false
     ): SysPropBooleanFlag {
-        val flag =
-            SysPropBooleanFlag(id = id, name = name, namespace = "systemui", default = default)
+        val flag = SysPropBooleanFlag(name = name, namespace = "systemui", default = default)
         checkForDupesAndAdd(flag)
         return flag
     }
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
index 5502da1..75465c2 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -29,35 +29,31 @@
         }
 
     fun unreleasedFlag(
-        id: Int,
         name: String,
         namespace: String = "systemui",
         teamfood: Boolean = false
     ): UnreleasedFlag {
         // Unreleased flags are always false in this build.
-        val flag = UnreleasedFlag(id = id, name = "", namespace = "", teamfood = false)
+        val flag = UnreleasedFlag(name = name, namespace = namespace, teamfood = false)
         return flag
     }
 
     fun releasedFlag(
-        id: Int,
         name: String,
         namespace: String = "systemui",
     ): ReleasedFlag {
-        val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = false)
+        val flag = ReleasedFlag(name = name, namespace = namespace, teamfood = false)
         flagMap[name] = flag
         return flag
     }
 
     fun resourceBooleanFlag(
-        id: Int,
         @BoolRes resourceId: Int,
         name: String,
         namespace: String = "systemui",
     ): ResourceBooleanFlag {
         val flag =
             ResourceBooleanFlag(
-                id = id,
                 name = name,
                 namespace = namespace,
                 resourceId = resourceId,
@@ -68,13 +64,11 @@
     }
 
     fun sysPropBooleanFlag(
-        id: Int,
         name: String,
         namespace: String = "systemui",
         default: Boolean = false
     ): SysPropBooleanFlag {
-        val flag =
-            SysPropBooleanFlag(id = id, name = name, namespace = namespace, default = default)
+        val flag = SysPropBooleanFlag(name = name, namespace = namespace, default = default)
         flagMap[name] = flag
         return flag
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 0842953..495367b 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -26,7 +26,7 @@
 import android.view.View
 import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.TITLE
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.TITLE
 
 /** Displays security messages for the keyguard bouncer. */
 open class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
@@ -105,7 +105,7 @@
 
         hideAnimator.addListener(
             object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     super@BouncerKeyguardMessageArea.setMessage(msg, animate)
                 }
             }
@@ -118,7 +118,7 @@
 
         showAnimator.addListener(
             object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     textAboutToShow = null
                 }
             }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 8ea4c31a..4f4eec6 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -15,18 +15,19 @@
  */
 package com.android.keyguard
 
-import android.app.WallpaperManager
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
 import android.content.res.Resources
 import android.text.format.DateFormat
-import android.util.TypedValue
 import android.util.Log
+import android.util.TypedValue
 import android.view.View
 import android.view.View.OnAttachStateChangeListener
 import android.view.ViewTreeObserver
+import android.view.ViewTreeObserver.OnGlobalLayoutListener
+import android.widget.FrameLayout
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
@@ -42,7 +43,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.KeyguardLargeClockLog
 import com.android.systemui.log.dagger.KeyguardSmallClockLog
 import com.android.systemui.plugins.ClockController
@@ -88,17 +89,48 @@
 ) {
     var clock: ClockController? = null
         set(value) {
+            smallClockOnAttachStateChangeListener?.let {
+                field?.smallClock?.view?.removeOnAttachStateChangeListener(it)
+                smallClockFrame?.viewTreeObserver
+                        ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
+            }
+            largeClockOnAttachStateChangeListener?.let {
+                field?.largeClock?.view?.removeOnAttachStateChangeListener(it)
+            }
+
             field = value
             if (value != null) {
                 smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
-                value.smallClock.logBuffer = smallLogBuffer
+                value.smallClock.messageBuffer = smallLogBuffer
                 largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
-                value.largeClock.logBuffer = largeLogBuffer
+                value.largeClock.messageBuffer = largeLogBuffer
 
                 value.initialize(resources, dozeAmount, 0f)
 
                 if (!regionSamplingEnabled) {
                     updateColors()
+                } else {
+                    clock?.let {
+                        smallRegionSampler = createRegionSampler(
+                                it.smallClock.view,
+                                mainExecutor,
+                                bgExecutor,
+                                regionSamplingEnabled,
+                                isLockscreen = true,
+                                ::updateColors
+                        )?.apply { startRegionSampler() }
+
+                        largeRegionSampler = createRegionSampler(
+                                it.largeClock.view,
+                                mainExecutor,
+                                bgExecutor,
+                                regionSamplingEnabled,
+                                isLockscreen = true,
+                                ::updateColors
+                        )?.apply { startRegionSampler() }
+
+                        updateColors()
+                    }
                 }
                 updateFontSizes()
                 updateTimeListeners()
@@ -108,27 +140,62 @@
                     }
                     value.events.onWeatherDataChanged(it)
                 }
-                value.smallClock.view.addOnAttachStateChangeListener(
+
+                smallClockOnAttachStateChangeListener =
                     object : OnAttachStateChangeListener {
-                        override fun onViewAttachedToWindow(p0: View?) {
+                        var pastVisibility: Int? = null
+                        override fun onViewAttachedToWindow(view: View) {
                             value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+                            if (view != null) {
+                                smallClockFrame = view.parent as FrameLayout
+                                smallClockFrame?.let {frame ->
+                                    pastVisibility = frame.visibility
+                                    onGlobalLayoutListener = OnGlobalLayoutListener {
+                                        val currentVisibility = frame.visibility
+                                        if (pastVisibility != currentVisibility) {
+                                            pastVisibility = currentVisibility
+                                            // when small clock  visible,
+                                            // recalculate bounds and sample
+                                            if (currentVisibility == View.VISIBLE) {
+                                                smallRegionSampler?.stopRegionSampler()
+                                                smallRegionSampler?.startRegionSampler()
+                                            }
+                                        }
+                                    }
+                                    frame.viewTreeObserver
+                                            .addOnGlobalLayoutListener(onGlobalLayoutListener)
+                                }
+                            }
                         }
 
-                        override fun onViewDetachedFromWindow(p0: View?) {
+                        override fun onViewDetachedFromWindow(p0: View) {
+                            smallClockFrame?.viewTreeObserver
+                                    ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
                         }
-                })
-                value.largeClock.view.addOnAttachStateChangeListener(
+                }
+                value.smallClock.view
+                        .addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener)
+
+                largeClockOnAttachStateChangeListener =
                     object : OnAttachStateChangeListener {
-                        override fun onViewAttachedToWindow(p0: View?) {
+                        override fun onViewAttachedToWindow(p0: View) {
                             value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
                         }
-
-                        override fun onViewDetachedFromWindow(p0: View?) {
+                        override fun onViewDetachedFromWindow(p0: View) {
                         }
-                })
+                }
+                value.largeClock.view
+                        .addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener)
             }
         }
 
+    @VisibleForTesting
+    var smallClockOnAttachStateChangeListener: OnAttachStateChangeListener? = null
+    @VisibleForTesting
+    var largeClockOnAttachStateChangeListener: OnAttachStateChangeListener? = null
+    private var smallClockFrame: FrameLayout? = null
+    private var onGlobalLayoutListener: OnGlobalLayoutListener? = null
+
     private var isDozing = false
         private set
 
@@ -141,21 +208,19 @@
 
 
     private fun updateColors() {
-        val wallpaperManager = WallpaperManager.getInstance(context)
         if (regionSamplingEnabled) {
-            regionSampler?.let { regionSampler ->
-                clock?.let { clock ->
-                    if (regionSampler.sampledView == clock.smallClock.view) {
-                        smallClockIsDark = regionSampler.currentRegionDarkness().isDark
-                        clock.smallClock.events.onRegionDarknessChanged(smallClockIsDark)
-                        return@updateColors
-                    } else if (regionSampler.sampledView == clock.largeClock.view) {
-                        largeClockIsDark = regionSampler.currentRegionDarkness().isDark
-                        clock.largeClock.events.onRegionDarknessChanged(largeClockIsDark)
-                        return@updateColors
-                    }
+            clock?.let { clock ->
+                smallRegionSampler?.let {
+                    smallClockIsDark = it.currentRegionDarkness().isDark
+                    clock.smallClock.events.onRegionDarknessChanged(smallClockIsDark)
+                }
+
+                largeRegionSampler?.let {
+                    largeClockIsDark = it.currentRegionDarkness().isDark
+                    clock.largeClock.events.onRegionDarknessChanged(largeClockIsDark)
                 }
             }
+            return
         }
 
         val isLightTheme = TypedValue()
@@ -168,23 +233,6 @@
             largeClock.events.onRegionDarknessChanged(largeClockIsDark)
         }
     }
-
-    private fun updateRegionSampler(sampledRegion: View) {
-        regionSampler?.stopRegionSampler()
-        regionSampler =
-            createRegionSampler(
-                    sampledRegion,
-                    mainExecutor,
-                    bgExecutor,
-                    regionSamplingEnabled,
-                    isLockscreen = true,
-                    ::updateColors
-                )
-                ?.apply { startRegionSampler() }
-
-        updateColors()
-    }
-
     protected open fun createRegionSampler(
         sampledView: View,
         mainExecutor: Executor?,
@@ -202,7 +250,10 @@
         ) { updateColors() }
     }
 
-    var regionSampler: RegionSampler? = null
+    var smallRegionSampler: RegionSampler? = null
+        private set
+    var largeRegionSampler: RegionSampler? = null
+        private set
     var smallTimeListener: TimeListener? = null
     var largeTimeListener: TimeListener? = null
     val shouldTimeListenerRun: Boolean
@@ -284,7 +335,6 @@
             return
         }
         isRegistered = true
-
         broadcastDispatcher.registerReceiver(
             localeBroadcastReceiver,
             IntentFilter(Intent.ACTION_LOCALE_CHANGED)
@@ -319,9 +369,16 @@
         configurationController.removeCallback(configListener)
         batteryController.removeCallback(batteryCallback)
         keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
-        regionSampler?.stopRegionSampler()
+        smallRegionSampler?.stopRegionSampler()
+        largeRegionSampler?.stopRegionSampler()
         smallTimeListener?.stop()
         largeTimeListener?.stop()
+        clock?.smallClock?.view
+                ?.removeOnAttachStateChangeListener(smallClockOnAttachStateChangeListener)
+        smallClockFrame?.viewTreeObserver
+                ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
+        clock?.largeClock?.view
+                ?.removeOnAttachStateChangeListener(largeClockOnAttachStateChangeListener)
     }
 
     private fun updateTimeListeners() {
diff --git a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
new file mode 100644
index 0000000..899cad89
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.app.Presentation
+import android.content.Context
+import android.graphics.Color
+import android.os.Bundle
+import android.view.Display
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import com.android.keyguard.dagger.KeyguardStatusViewComponent
+import com.android.systemui.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** [Presentation] shown in connected displays while on keyguard. */
+class ConnectedDisplayKeyguardPresentation
+@AssistedInject
+constructor(
+    @Assisted display: Display,
+    context: Context,
+    private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
+) :
+    Presentation(
+        context,
+        display,
+        R.style.Theme_SystemUI_KeyguardPresentation,
+        WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
+    ) {
+
+    private lateinit var keyguardStatusViewController: KeyguardStatusViewController
+    private lateinit var clock: KeyguardStatusView
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        setContentView(
+            LayoutInflater.from(context)
+                .inflate(R.layout.keyguard_clock_presentation, /* root= */ null)
+        )
+        val window = window ?: error("no window available.")
+
+        // Logic to make the lock screen fullscreen
+        window.decorView.systemUiVisibility =
+            (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
+                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
+                View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
+        window.attributes.fitInsetsTypes = 0
+        window.isNavigationBarContrastEnforced = false
+        window.navigationBarColor = Color.TRANSPARENT
+
+        clock = findViewById(R.id.clock)
+        keyguardStatusViewController =
+            keyguardStatusViewComponentFactory.build(clock).keyguardStatusViewController.apply {
+                setDisplayedOnSecondaryDisplay()
+                init()
+            }
+    }
+
+    /** [ConnectedDisplayKeyguardPresentation] factory. */
+    @AssistedFactory
+    interface Factory {
+        /** Creates a new [Presentation] for the given [display]. */
+        fun create(
+            display: Display,
+        ): ConnectedDisplayKeyguardPresentation
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index d7e8616..e3f9de1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -102,6 +102,12 @@
         super.onViewAttached();
         mView.setKeyDownListener(mKeyDownListener);
         mEmergencyButtonController.setEmergencyButtonCallback(mEmergencyButtonCallback);
+        // if the user is currently locked out, enforce it.
+        long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+                KeyguardUpdateMonitor.getCurrentUser());
+        if (shouldLockout(deadline)) {
+            handleAttemptLockout(deadline);
+        }
     }
 
     @Override
@@ -143,6 +149,7 @@
         long elapsedRealtime = SystemClock.elapsedRealtime();
         long secondsInFuture = (long) Math.ceil(
                 (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
+        getKeyguardSecurityCallback().onAttemptLockoutStart(secondsInFuture);
         mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
 
             @Override
@@ -177,6 +184,7 @@
                 getKeyguardSecurityCallback().dismiss(true, userId, getSecurityMode());
             }
         } else {
+            mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */);
             if (isValidPassword) {
                 getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs);
                 if (timeoutMs > 0) {
@@ -185,7 +193,6 @@
                     handleAttemptLockout(deadline);
                 }
             }
-            mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */);
             if (timeoutMs == 0) {
                 mMessageAreaController.setMessage(mView.getWrongPasswordStringId());
             }
@@ -277,12 +284,6 @@
     @Override
     public void onResume(int reason) {
         mResumed = true;
-        // if the user is currently locked out, enforce it.
-        long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
-                KeyguardUpdateMonitor.getCurrentUser());
-        if (shouldLockout(deadline)) {
-            handleAttemptLockout(deadline);
-        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 376e27c..5915b22 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,10 +1,5 @@
 package com.android.keyguard;
 
-import static android.view.View.ALPHA;
-import static android.view.View.SCALE_X;
-import static android.view.View.SCALE_Y;
-import static android.view.View.TRANSLATION_Y;
-
 import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_X_CLOCK_DESIGN;
 import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_DESIGN;
 import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_SIZE;
@@ -29,7 +24,7 @@
 import com.android.keyguard.dagger.KeyguardStatusViewScope;
 import com.android.systemui.R;
 import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.shared.clocks.DefaultClockController;
 
@@ -145,6 +140,13 @@
         updateStatusArea(/* animate= */false);
     }
 
+    /** Sets whether the large clock is being shown on a connected display. */
+    public void setLargeClockOnSecondaryDisplay(boolean onSecondaryDisplay) {
+        if (mClock != null) {
+            mClock.getLargeClock().getEvents().onSecondaryDisplayChanged(onSecondaryDisplay);
+        }
+    }
+
     /**
      * Enable or disable split shade specific positioning
      */
@@ -303,9 +305,10 @@
         if (!animate) {
             out.setAlpha(0f);
             out.setTranslationY(clockOutYTranslation);
+            out.setVisibility(INVISIBLE);
             in.setAlpha(1f);
             in.setTranslationY(clockInYTranslation);
-            in.setVisibility(View.VISIBLE);
+            in.setVisibility(VISIBLE);
             mStatusArea.setScaleX(statusAreaClockScale);
             mStatusArea.setScaleY(statusAreaClockScale);
             mStatusArea.setTranslateXFromClockDesign(statusAreaClockTranslateX);
@@ -323,11 +326,13 @@
                 ObjectAnimator.ofFloat(out, TRANSLATION_Y, clockOutYTranslation));
         mClockOutAnim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animation) {
-                mClockOutAnim = null;
+                if (mClockOutAnim == animation) {
+                    out.setVisibility(INVISIBLE);
+                    mClockOutAnim = null;
+                }
             }
         });
 
-        in.setAlpha(0);
         in.setVisibility(View.VISIBLE);
         mClockInAnim = new AnimatorSet();
         mClockInAnim.setDuration(CLOCK_IN_MILLIS);
@@ -338,7 +343,9 @@
         mClockInAnim.setStartDelay(CLOCK_IN_START_DELAY_MILLIS);
         mClockInAnim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animation) {
-                mClockInAnim = null;
+                if (mClockInAnim == animation) {
+                    mClockInAnim = null;
+                }
             }
         });
 
@@ -359,7 +366,9 @@
                         statusAreaClockTranslateY));
         mStatusAreaAnim.addListener(new AnimatorListenerAdapter() {
             public void onAnimationEnd(Animator animation) {
-                mStatusAreaAnim = null;
+                if (mStatusAreaAnim == animation) {
+                    mStatusAreaAnim = null;
+                }
             }
         });
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index a026130..8108076 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -21,6 +21,8 @@
 
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.annotation.Nullable;
 import android.database.ContentObserver;
@@ -33,14 +35,17 @@
 import android.widget.LinearLayout;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.log.dagger.KeyguardClockLog;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -58,6 +63,7 @@
 
 import java.io.PrintWriter;
 import java.util.Locale;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 
@@ -98,9 +104,22 @@
 
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
 
+    private boolean mShownOnSecondaryDisplay = false;
     private boolean mOnlyClock = false;
+    private boolean mIsActiveDreamLockscreenHosted = false;
+    private FeatureFlags mFeatureFlags;
+    private KeyguardInteractor mKeyguardInteractor;
     private final DelayableExecutor mUiExecutor;
     private boolean mCanShowDoubleLineClock = true;
+    @VisibleForTesting
+    final Consumer<Boolean> mIsActiveDreamLockscreenHostedCallback =
+            (Boolean isLockscreenHosted) -> {
+                if (mIsActiveDreamLockscreenHosted == isLockscreenHosted) {
+                    return;
+                }
+                mIsActiveDreamLockscreenHosted = isLockscreenHosted;
+                updateKeyguardStatusAreaVisibility();
+            };
     private final ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
         @Override
         public void onChange(boolean change) {
@@ -137,7 +156,9 @@
             @Main DelayableExecutor uiExecutor,
             DumpManager dumpManager,
             ClockEventController clockEventController,
-            @KeyguardClockLog LogBuffer logBuffer) {
+            @KeyguardClockLog LogBuffer logBuffer,
+            KeyguardInteractor keyguardInteractor,
+            FeatureFlags featureFlags) {
         super(keyguardClockSwitch);
         mStatusBarStateController = statusBarStateController;
         mClockRegistry = clockRegistry;
@@ -151,6 +172,8 @@
         mClockEventController = clockEventController;
         mLogBuffer = logBuffer;
         mView.setLogBuffer(mLogBuffer);
+        mFeatureFlags = featureFlags;
+        mKeyguardInteractor = keyguardInteractor;
 
         mClockChangedListener = new ClockRegistry.ClockChangeListener() {
             @Override
@@ -163,8 +186,18 @@
     }
 
     /**
-     * Mostly used for alternate displays, limit the information shown
+     * When set, limits the information shown in an external display.
      */
+    public void setShownOnSecondaryDisplay(boolean shownOnSecondaryDisplay) {
+        mShownOnSecondaryDisplay = shownOnSecondaryDisplay;
+    }
+
+    /**
+     * Mostly used for alternate displays, limit the information shown
+     *
+     * @deprecated use {@link KeyguardClockSwitchController#setShownOnSecondaryDisplay}
+     */
+    @Deprecated
     public void setOnlyClock(boolean onlyClock) {
         mOnlyClock = onlyClock;
     }
@@ -191,6 +224,21 @@
 
         mDumpManager.unregisterDumpable(getClass().toString()); // unregister previous clocks
         mDumpManager.registerDumpable(getClass().toString(), this);
+
+        if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+            mStatusArea = mView.findViewById(R.id.keyguard_status_area);
+            collectFlow(mStatusArea, mKeyguardInteractor.isActiveDreamLockscreenHosted(),
+                    mIsActiveDreamLockscreenHostedCallback);
+        }
+    }
+
+    private void hideSliceViewAndNotificationIconContainer() {
+        View ksv = mView.findViewById(R.id.keyguard_slice_view);
+        ksv.setVisibility(View.GONE);
+
+        View nic = mView.findViewById(
+                R.id.left_aligned_notification_icon_container);
+        nic.setVisibility(View.GONE);
     }
 
     @Override
@@ -205,34 +253,20 @@
         mKeyguardDateWeatherViewInvisibility =
                 mView.getResources().getInteger(R.integer.keyguard_date_weather_view_invisibility);
 
-        if (mOnlyClock) {
-            View ksv = mView.findViewById(R.id.keyguard_slice_view);
-            ksv.setVisibility(View.GONE);
+        if (mShownOnSecondaryDisplay) {
+            mView.setLargeClockOnSecondaryDisplay(true);
+            displayClock(LARGE, /* animate= */ false);
+            hideSliceViewAndNotificationIconContainer();
+            return;
+        }
 
-            View nic = mView.findViewById(
-                    R.id.left_aligned_notification_icon_container);
-            nic.setVisibility(View.GONE);
+        if (mOnlyClock) {
+            hideSliceViewAndNotificationIconContainer();
             return;
         }
         updateAodIcons();
-
         mStatusArea = mView.findViewById(R.id.keyguard_status_area);
 
-        if (mSmartspaceController.isEnabled()) {
-            View ksv = mView.findViewById(R.id.keyguard_slice_view);
-            int viewIndex = mStatusArea.indexOfChild(ksv);
-            ksv.setVisibility(View.GONE);
-
-            // TODO(b/261757708): add content observer for the Settings toggle and add/remove
-            //  weather according to the Settings.
-            if (mSmartspaceController.isDateWeatherDecoupled()) {
-                addDateWeatherView(viewIndex);
-                viewIndex += 1;
-            }
-
-            addSmartspaceView(viewIndex);
-        }
-
         mSecureSettings.registerContentObserverForUser(
                 Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
                 false, /* notifyForDescendants */
@@ -246,13 +280,27 @@
                 mShowWeatherObserver,
                 UserHandle.USER_ALL
         );
-
         updateDoubleLineClock();
-        setDateWeatherVisibility();
-        setWeatherVisibility();
 
         mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
                 mKeyguardUnlockAnimationListener);
+
+        if (mSmartspaceController.isEnabled()) {
+            View ksv = mView.findViewById(R.id.keyguard_slice_view);
+            int viewIndex = mStatusArea.indexOfChild(ksv);
+            ksv.setVisibility(View.GONE);
+
+            mSmartspaceController.removeViewsFromParent(mStatusArea);
+            addSmartspaceView();
+            // TODO(b/261757708): add content observer for the Settings toggle and add/remove
+            //  weather according to the Settings.
+            if (mSmartspaceController.isDateWeatherDecoupled()) {
+                addDateWeatherView();
+            }
+        }
+
+        setDateWeatherVisibility();
+        setWeatherVisibility();
     }
 
     int getNotificationIconAreaHeight() {
@@ -266,6 +314,7 @@
         setClock(null);
 
         mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
+        mSecureSettings.unregisterContentObserver(mShowWeatherObserver);
 
         mKeyguardUnlockAnimationController.removeKeyguardUnlockAnimationListener(
                 mKeyguardUnlockAnimationListener);
@@ -273,29 +322,22 @@
 
     void onLocaleListChanged() {
         if (mSmartspaceController.isEnabled()) {
+            mSmartspaceController.removeViewsFromParent(mStatusArea);
+            addSmartspaceView();
             if (mSmartspaceController.isDateWeatherDecoupled()) {
                 mDateWeatherView.removeView(mWeatherView);
-                int index = mStatusArea.indexOfChild(mDateWeatherView);
-                if (index >= 0) {
-                    mStatusArea.removeView(mDateWeatherView);
-                    addDateWeatherView(index);
-                }
+                addDateWeatherView();
                 setDateWeatherVisibility();
                 setWeatherVisibility();
             }
-            int index = mStatusArea.indexOfChild(mSmartspaceView);
-            if (index >= 0) {
-                mStatusArea.removeView(mSmartspaceView);
-                addSmartspaceView(index);
-            }
         }
     }
 
-    private void addDateWeatherView(int index) {
+    private void addDateWeatherView() {
         mDateWeatherView = (ViewGroup) mSmartspaceController.buildAndConnectDateView(mView);
         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                 MATCH_PARENT, WRAP_CONTENT);
-        mStatusArea.addView(mDateWeatherView, index, lp);
+        mStatusArea.addView(mDateWeatherView, 0, lp);
         int startPadding = getContext().getResources().getDimensionPixelSize(
                 R.dimen.below_clock_padding_start);
         int endPadding = getContext().getResources().getDimensionPixelSize(
@@ -315,11 +357,11 @@
         mWeatherView.setPaddingRelative(0, 0, 4, 0);
     }
 
-    private void addSmartspaceView(int index) {
+    private void addSmartspaceView() {
         mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                 MATCH_PARENT, WRAP_CONTENT);
-        mStatusArea.addView(mSmartspaceView, index, lp);
+        mStatusArea.addView(mSmartspaceView, 0, lp);
         int startPadding = getContext().getResources().getDimensionPixelSize(
                 R.dimen.below_clock_padding_start);
         int endPadding = getContext().getResources().getDimensionPixelSize(
@@ -393,6 +435,7 @@
      * Refresh clock. Called in response to TIME_TICK broadcasts.
      */
     void refresh() {
+        mLogBuffer.log(TAG, LogLevel.INFO, "refresh");
         if (mSmartspaceController != null) {
             mSmartspaceController.requestSmartspaceUpdate();
         }
@@ -444,10 +487,6 @@
             int clockHeight = clock.getLargeClock().getView().getHeight();
             return frameHeight / 2 + clockHeight / 2 + mKeyguardLargeClockTopMargin / -2;
         } else {
-            // This is only called if we've never shown the large clock as the frame is inflated
-            // with 'gone', but then the visibility is never set when it is animated away by
-            // KeyguardClockSwitch, instead it is removed from the view hierarchy.
-            // TODO(b/261755021): Cleanup Large Frame Visibility
             int clockHeight = clock.getSmallClock().getView().getHeight();
             return clockHeight + statusBarHeaderHeight + mKeyguardSmallClockTopMargin;
         }
@@ -465,15 +504,11 @@
         if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
             return clock.getLargeClock().getView().getHeight();
         } else {
-            // Is not called except in certain edge cases, see comment in getClockBottom
-            // TODO(b/261755021): Cleanup Large Frame Visibility
             return clock.getSmallClock().getView().getHeight();
         }
     }
 
     boolean isClockTopAligned() {
-        // Returns false except certain edge cases, see comment in getClockBottom
-        // TODO(b/261755021): Cleanup Large Frame Visibility
         return mLargeClockFrame.getVisibility() != View.VISIBLE;
     }
 
@@ -532,6 +567,15 @@
         }
     }
 
+    private void updateKeyguardStatusAreaVisibility() {
+        if (mStatusArea != null) {
+            mUiExecutor.execute(() -> {
+                mStatusArea.setVisibility(
+                        mIsActiveDreamLockscreenHosted ? View.INVISIBLE : View.VISIBLE);
+            });
+        }
+    }
+
     /**
      * Sets the clipChildren property on relevant views, to allow the smartspace to draw out of
      * bounds during the unlock transition.
@@ -552,9 +596,13 @@
         if (clock != null) {
             clock.dump(pw);
         }
-        final RegionSampler regionSampler = mClockEventController.getRegionSampler();
-        if (regionSampler != null) {
-            regionSampler.dump(pw);
+        final RegionSampler smallRegionSampler = mClockEventController.getSmallRegionSampler();
+        if (smallRegionSampler != null) {
+            smallRegionSampler.dump(pw);
+        }
+        final RegionSampler largeRegionSampler = mClockEventController.getLargeRegionSampler();
+        if (largeRegionSampler != null) {
+            largeRegionSampler.dump(pw);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 9f21a31..1c5a575 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -43,16 +43,19 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import dagger.Lazy;
+
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
 
 @SysUISingleton
 public class KeyguardDisplayManager {
@@ -64,6 +67,9 @@
     private final DisplayTracker mDisplayTracker;
     private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
     private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
+    private final ConnectedDisplayKeyguardPresentation.Factory
+            mConnectedDisplayKeyguardPresentationFactory;
+    private final FeatureFlags mFeatureFlags;
     private final Context mContext;
 
     private boolean mShowing;
@@ -105,7 +111,10 @@
             @Main Executor mainExecutor,
             @UiBackground Executor uiBgExecutor,
             DeviceStateHelper deviceStateHelper,
-            KeyguardStateController keyguardStateController) {
+            KeyguardStateController keyguardStateController,
+            ConnectedDisplayKeyguardPresentation.Factory
+                    connectedDisplayKeyguardPresentationFactory,
+            FeatureFlags featureFlags) {
         mContext = context;
         mNavigationBarControllerLazy = navigationBarControllerLazy;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
@@ -115,6 +124,8 @@
         mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
         mDeviceStateHelper = deviceStateHelper;
         mKeyguardStateController = keyguardStateController;
+        mConnectedDisplayKeyguardPresentationFactory = connectedDisplayKeyguardPresentationFactory;
+        mFeatureFlags = featureFlags;
     }
 
     private boolean isKeyguardShowable(Display display) {
@@ -185,8 +196,12 @@
         return false;
     }
 
-    KeyguardPresentation createPresentation(Display display) {
-        return new KeyguardPresentation(mContext, display, mKeyguardStatusViewComponentFactory);
+    Presentation createPresentation(Display display) {
+        if (mFeatureFlags.isEnabled(Flags.ENABLE_CLOCK_KEYGUARD_PRESENTATION)) {
+            return mConnectedDisplayKeyguardPresentationFactory.create(display);
+        } else {
+            return new KeyguardPresentation(mContext, display, mKeyguardStatusViewComponentFactory);
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt
index f2685c5..f23ae67 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt
@@ -27,6 +27,7 @@
     override var userId: Int = 0,
     override var listening: Boolean = false,
     // keepSorted
+    var allowOnCurrentOccludingActivity: Boolean = false,
     var alternateBouncerShowing: Boolean = false,
     var biometricEnabledForUser: Boolean = false,
     var bouncerIsOrWillShow: Boolean = false,
@@ -58,6 +59,7 @@
             userId.toString(),
             listening.toString(),
             // keep sorted
+            allowOnCurrentOccludingActivity.toString(),
             alternateBouncerShowing.toString(),
             biometricEnabledForUser.toString(),
             bouncerIsOrWillShow.toString(),
@@ -98,6 +100,7 @@
                 userId = model.userId
                 listening = model.listening
                 // keep sorted
+                allowOnCurrentOccludingActivity = model.allowOnCurrentOccludingActivity
                 alternateBouncerShowing = model.alternateBouncerShowing
                 biometricEnabledForUser = model.biometricEnabledForUser
                 bouncerIsOrWillShow = model.bouncerIsOrWillShow
@@ -144,6 +147,7 @@
                 "userId",
                 "listening",
                 // keep sorted
+                "allowOnCurrentOccludingActivity",
                 "alternateBouncerShowing",
                 "biometricAllowedForUser",
                 "bouncerIsOrWillShow",
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 3c05299..42dbc48 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -17,6 +17,7 @@
 package com.android.keyguard;
 
 import android.annotation.CallSuper;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -29,10 +30,14 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.R;
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
+import com.android.systemui.bouncer.ui.BouncerMessageView;
+import com.android.systemui.bouncer.ui.binder.BouncerMessageViewBinder;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.log.BouncerLogger;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -90,6 +95,12 @@
     @CallSuper
     protected void onViewAttached() {
         updateMessageAreaVisibility();
+        if (TextUtils.isEmpty(mMessageAreaController.getMessage())
+                && getInitialMessageResId() != 0) {
+            mMessageAreaController.setMessage(
+                    mView.getResources().getString(getInitialMessageResId()),
+                    /* animate= */ false);
+        }
     }
 
     private void updateMessageAreaVisibility() {
@@ -142,12 +153,6 @@
     }
 
     public void startAppearAnimation() {
-        if (TextUtils.isEmpty(mMessageAreaController.getMessage())
-                && getInitialMessageResId() != 0) {
-            mMessageAreaController.setMessage(
-                    mView.getResources().getString(getInitialMessageResId()),
-                    /* animate= */ false);
-        }
         mView.startAppearAnimation();
     }
 
@@ -168,6 +173,24 @@
     /** Determines the message to show in the bouncer when it first appears. */
     protected abstract int getInitialMessageResId();
 
+    /**
+     * Binds the {@link KeyguardInputView#getBouncerMessageView()} view with the provided context.
+     */
+    public void bindMessageView(
+            @NonNull BouncerMessageInteractor bouncerMessageInteractor,
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+            BouncerLogger bouncerLogger,
+            FeatureFlags featureFlags) {
+        BouncerMessageView bouncerMessageView = (BouncerMessageView) mView.getBouncerMessageView();
+        if (bouncerMessageView != null) {
+            BouncerMessageViewBinder.bind(bouncerMessageView,
+                    bouncerMessageInteractor,
+                    messageAreaControllerFactory,
+                    bouncerLogger,
+                    featureFlags);
+        }
+    }
+
     /** Factory for a {@link KeyguardInputViewController}. */
     public static class Factory {
         private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 2377057..d9b7bde 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -69,7 +69,7 @@
                 (long) (125 * KeyguardPatternView.DISAPPEAR_MULTIPLIER_LOCKED),
                 0.6f /* translationScale */,
                 0.45f /* delayScale */, AnimationUtils.loadInterpolator(
-                        mContext, android.R.interpolator.fast_out_linear_in));
+                       mContext, android.R.interpolator.fast_out_linear_in));
         mDisappearYTranslation = getResources().getDimensionPixelSize(
                 R.dimen.disappear_y_translation);
         mYTrans = getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
@@ -82,8 +82,10 @@
     }
 
     void onDevicePostureChanged(@DevicePostureInt int posture) {
-        mLastDevicePosture = posture;
-        updateMargins();
+        if (mLastDevicePosture != posture) {
+            mLastDevicePosture = posture;
+            updateMargins();
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 03d9eb3..59ee0d8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -32,6 +32,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Trace;
@@ -71,6 +72,8 @@
     private Interpolator mLinearOutSlowInInterpolator;
     private Interpolator mFastOutLinearInInterpolator;
     private DisappearAnimationListener mDisappearAnimationListener;
+    private static final int[] DISABLE_STATE_SET = {-android.R.attr.state_enabled};
+    private static final int[] ENABLE_STATE_SET = {android.R.attr.state_enabled};
 
     public KeyguardPasswordView(Context context) {
         this(context, null);
@@ -148,7 +151,10 @@
 
     @Override
     protected void setPasswordEntryEnabled(boolean enabled) {
-        mPasswordEntry.setEnabled(enabled);
+        int color = mPasswordEntry.getTextColors().getColorForState(
+                enabled ? ENABLE_STATE_SET : DISABLE_STATE_SET, 0);
+        mPasswordEntry.setBackgroundTintList(ColorStateList.valueOf(color));
+        mPasswordEntry.setCursorVisible(enabled);
     }
 
     @Override
@@ -189,17 +195,18 @@
                             if (controller.isCancelled()) {
                                 return;
                             }
+                            float value = (float) animation.getAnimatedValue();
+                            float fraction = anim.getAnimatedFraction();
                             Insets shownInsets = controller.getShownStateInsets();
                             int dist = (int) (-shownInsets.bottom / 4
-                                    * anim.getAnimatedFraction());
+                                    * fraction);
                             Insets insets = Insets.add(shownInsets, Insets.of(0, 0, 0, dist));
                             if (mDisappearAnimationListener != null) {
                                 mDisappearAnimationListener.setTranslationY(-dist);
                             }
 
-                            controller.setInsetsAndAlpha(insets,
-                                    (float) animation.getAnimatedValue(),
-                                    anim.getAnimatedFraction());
+                            controller.setInsetsAndAlpha(insets, value, fraction);
+                            setAlpha(value);
                         });
                         anim.addListener(new AnimatorListenerAdapter() {
                             @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 99b5d52..2bdf46e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -16,8 +16,10 @@
 package com.android.keyguard;
 
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.SystemClock;
 import android.text.TextUtils;
@@ -74,6 +76,7 @@
     BouncerKeyguardMessageArea mSecurityMessageDisplay;
     private View mEcaView;
     private ConstraintLayout mContainer;
+    @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
 
     public KeyguardPatternView(Context context) {
         this(context, null);
@@ -95,14 +98,27 @@
                 mContext, android.R.interpolator.fast_out_linear_in));
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        updateMargins();
+    }
+
     void onDevicePostureChanged(@DevicePostureInt int posture) {
+        if (mLastDevicePosture != posture) {
+            mLastDevicePosture = posture;
+            updateMargins();
+        }
+    }
+
+    private void updateMargins() {
         // Update the guideline based on the device posture...
         float halfOpenPercentage =
                 mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
 
         ConstraintSet cs = new ConstraintSet();
         cs.clone(mContainer);
-        cs.setGuidelinePercent(R.id.pattern_top_guideline, posture == DEVICE_POSTURE_HALF_OPENED
+        cs.setGuidelinePercent(R.id.pattern_top_guideline,
+                mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED
                 ? halfOpenPercentage : 0.0f);
         cs.applyTo(mContainer);
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 3d255a5..a30b447 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -236,7 +236,14 @@
                 getKeyguardSecurityCallback().onCancelClicked();
             });
         }
+        mView.onDevicePostureChanged(mPostureController.getDevicePosture());
         mPostureController.addCallback(mPostureCallback);
+        // if the user is currently locked out, enforce it.
+        long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
+                KeyguardUpdateMonitor.getCurrentUser());
+        if (deadline != 0) {
+            handleAttemptLockout(deadline);
+        }
     }
 
     @Override
@@ -267,12 +274,6 @@
     @Override
     public void onResume(int reason) {
         super.onResume(reason);
-        // if the user is currently locked out, enforce it.
-        long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
-                KeyguardUpdateMonitor.getCurrentUser());
-        if (deadline != 0) {
-            handleAttemptLockout(deadline);
-        }
     }
 
     @Override
@@ -368,6 +369,7 @@
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         final long secondsInFuture = (long) Math.ceil(
                 (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
+        getKeyguardSecurityCallback().onAttemptLockoutStart(secondsInFuture);
         mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
 
             @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 3e16d55..574a059 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -77,7 +77,12 @@
             });
         }
         mPasswordEntry.setUserActivityListener(this::onUserInput);
+        mView.onDevicePostureChanged(mPostureController.getDevicePosture());
         mPostureController.addCallback(mPostureCallback);
+        if (mFeatureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)) {
+            mPasswordEntry.setUsePinShapes(true);
+            updateAutoConfirmationState();
+        }
     }
 
     protected void onUserInput() {
@@ -99,10 +104,6 @@
 
     @Override
     public void startAppearAnimation() {
-        if (mFeatureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)) {
-            mPasswordEntry.setUsePinShapes(true);
-            updateAutoConfirmationState();
-        }
         super.startAppearAnimation();
     }
 
@@ -164,15 +165,18 @@
      * Responsible for identifying if PIN hinting is to be enabled or not
      */
     private boolean isPinHinting() {
-        return mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser())
-                == DEFAULT_PIN_LENGTH;
+        return mPinLength == DEFAULT_PIN_LENGTH;
     }
 
     /**
-     * Responsible for identifying if auto confirm is enabled or not in Settings
+     * Responsible for identifying if auto confirm is enabled or not in Settings and
+     * a valid PIN_LENGTH is stored on the device (though the latter check is only to make it more
+     * robust since we only allow enabling PIN confirmation if the user has a valid PIN length
+     * saved on device)
      */
     private boolean isAutoPinConfirmEnabledInSettings() {
         //Checks if user has enabled the auto confirm in Settings
-        return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser());
+        return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser())
+                && mPinLength != LockPatternUtils.PIN_LENGTH_UNAVAILABLE;
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
index bf9c3bb..2878df2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
@@ -87,6 +87,11 @@
     default void onUserInput() {
     }
 
+    /**
+     * Invoked when the auth input is disabled for specified number of seconds.
+     * @param seconds Number of seconds for which the auth input is disabled.
+     */
+    default void onAttemptLockoutStart(long seconds) {}
 
     /**
      * Dismisses keyguard and go to unlocked state.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 9ff338e..42dceb8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -100,6 +100,7 @@
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -281,6 +282,8 @@
 
     public interface SwipeListener {
         void onSwipeUp();
+        /** */
+        void onSwipeDown();
     }
 
     @VisibleForTesting
@@ -543,6 +546,11 @@
                 if (mSwipeListener != null) {
                     mSwipeListener.onSwipeUp();
                 }
+            } else if (getTranslationY() > TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    MIN_DRAG_SIZE, getResources().getDisplayMetrics())) {
+                if (mSwipeListener != null) {
+                    mSwipeListener.onSwipeDown();
+                }
             }
         }
         return true;
@@ -659,6 +667,11 @@
     }
 
     @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+    }
+
+    @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
         if (mViewMediatorCallback != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 78142cea..04692c4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -27,12 +27,12 @@
 import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER;
 import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES;
 
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.Intent;
 import android.content.res.ColorStateList;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.media.AudioManager;
@@ -68,28 +68,37 @@
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor;
 import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
 import com.android.systemui.biometrics.SideFpsController;
 import com.android.systemui.biometrics.SideFpsUiRequestSource;
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.user.domain.interactor.UserInteractor;
 import com.android.systemui.util.ViewController;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
 
 import java.io.File;
 import java.util.Optional;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
+
+import kotlinx.coroutines.Job;
 
 /** Controller for {@link KeyguardSecurityContainer} */
 @KeyguardBouncerScope
@@ -117,7 +126,9 @@
     private final Optional<SideFpsController> mSideFpsController;
     private final FalsingA11yDelegate mFalsingA11yDelegate;
     private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+    private final BouncerMessageInteractor mBouncerMessageInteractor;
     private int mTranslationY;
+    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     // Whether the volume keys should be handled by keyguard. If true, then
     // they will be handled here for specific media types such as music, otherwise
     // the audio service will bring up the volume dialog.
@@ -133,11 +144,22 @@
     private Runnable mCancelAction;
     private boolean mWillRunDismissFromKeyguard;
 
-    private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
+    private int mLastOrientation;
 
     private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
+    private int mCurrentUser = UserHandle.USER_NULL;
     private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
-            () -> showPrimarySecurityScreen(false);
+            new UserSwitcherController.UserSwitchCallback() {
+        @Override
+        public void onUserSwitched() {
+            if (mCurrentUser == KeyguardUpdateMonitor.getCurrentUser()) {
+                return;
+            }
+            mCurrentUser = KeyguardUpdateMonitor.getCurrentUser();
+            showPrimarySecurityScreen(false);
+            reinflateViewFlipper((l) -> {});
+        }
+    };
 
     @VisibleForTesting
     final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() {
@@ -179,6 +201,7 @@
 
         @Override
         public void onUserInput() {
+            mBouncerMessageInteractor.onPrimaryBouncerUserInput();
             mKeyguardFaceAuthInteractor.onPrimaryBouncerUserInput();
             mUpdateMonitor.cancelFaceAuth();
         }
@@ -208,7 +231,15 @@
         }
 
         @Override
+        public void onAttemptLockoutStart(long seconds) {
+            mBouncerMessageInteractor.onPrimaryAuthLockedOut(seconds);
+        }
+
+        @Override
         public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
+            if (timeoutMs == 0 && !success) {
+                mBouncerMessageInteractor.onPrimaryAuthIncorrectAttempt();
+            }
             int bouncerSide = SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__DEFAULT;
             if (mView.isSidedSecurityMode()) {
                 bouncerSide = mView.isSecurityLeftAligned()
@@ -282,6 +313,10 @@
                     mViewMediatorCallback.keyguardDone(fromPrimaryAuth, targetUserId);
                 }
             }
+
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+            }
         }
 
         @Override
@@ -309,13 +344,17 @@
                         "swipeUpOnBouncer");
             }
         }
+
+        @Override
+        public void onSwipeDown() {
+            mViewMediatorCallback.onBouncerSwipeDown();
+        }
     };
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
                 @Override
                 public void onThemeChanged() {
                     reloadColors();
-                    reset();
                 }
 
                 @Override
@@ -325,7 +364,14 @@
 
                 @Override
                 public void onDensityOrFontScaleChanged() {
-                    KeyguardSecurityContainerController.this.onDensityOrFontScaleChanged();
+                    KeyguardSecurityContainerController.this
+                            .onDensityOrFontScaleOrOrientationChanged();
+                }
+
+                @Override
+                public void onOrientationChanged(int orientation) {
+                    KeyguardSecurityContainerController.this
+                            .onDensityOrFontScaleOrOrientationChanged();
                 }
             };
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
@@ -363,6 +409,11 @@
                     showPrimarySecurityScreen(false);
                 }
             };
+    private final UserInteractor mUserInteractor;
+    private final Provider<AuthenticationInteractor> mAuthenticationInteractor;
+    private final Provider<JavaAdapter> mJavaAdapter;
+    private final DeviceProvisionedController mDeviceProvisionedController;
+    @Nullable private Job mSceneTransitionCollectionJob;
 
     @Inject
     public KeyguardSecurityContainerController(KeyguardSecurityContainer view,
@@ -387,7 +438,13 @@
             ViewMediatorCallback viewMediatorCallback,
             AudioManager audioManager,
             KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
-            FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate
+            BouncerMessageInteractor bouncerMessageInteractor,
+            Provider<JavaAdapter> javaAdapter,
+            UserInteractor userInteractor,
+            DeviceProvisionedController deviceProvisionedController,
+            FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
+            KeyguardTransitionInteractor keyguardTransitionInteractor,
+            Provider<AuthenticationInteractor> authenticationInteractor
     ) {
         super(view);
         view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
@@ -414,6 +471,12 @@
         mViewMediatorCallback = viewMediatorCallback;
         mAudioManager = audioManager;
         mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+        mBouncerMessageInteractor = bouncerMessageInteractor;
+        mUserInteractor = userInteractor;
+        mAuthenticationInteractor = authenticationInteractor;
+        mJavaAdapter = javaAdapter;
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mDeviceProvisionedController = deviceProvisionedController;
     }
 
     @Override
@@ -434,7 +497,26 @@
         // Update ViewMediator with the current input method requirements
         mViewMediatorCallback.setNeedsInput(needsInput());
         mView.setOnKeyListener(mOnKeyListener);
+
         showPrimarySecurityScreen(false);
+
+        if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+            // When the scene framework says that the lockscreen has been dismissed, dismiss the
+            // keyguard here, revealing the underlying app or launcher:
+            mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
+                mAuthenticationInteractor.get().isLockscreenDismissed(),
+                isLockscreenDismissed -> {
+                    if (isLockscreenDismissed) {
+                        final int selectedUserId = mUserInteractor.getSelectedUserId();
+                        showNextSecurityScreenOrFinish(
+                            /* authenticated= */ true,
+                            selectedUserId,
+                            /* bypassSecondaryLockScreen= */ true,
+                            mSecurityModel.getSecurityMode(selectedUserId));
+                    }
+                }
+            );
+        }
     }
 
     @Override
@@ -443,6 +525,11 @@
         mConfigurationController.removeCallback(mConfigurationListener);
         mView.removeMotionEventListener(mGlobalTouchListener);
         mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback);
+
+        if (mSceneTransitionCollectionJob != null) {
+            mSceneTransitionCollectionJob.cancel(null);
+            mSceneTransitionCollectionJob = null;
+        }
     }
 
     /** */
@@ -528,7 +615,6 @@
     public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) {
         if (mCancelAction != null) {
             mCancelAction.run();
-            mCancelAction = null;
         }
         mDismissAction = action;
         mCancelAction = cancelAction;
@@ -618,6 +704,14 @@
         mSecurityViewFlipperController.reset();
     }
 
+    /** Prepares views in the bouncer before starting appear animation. */
+    public void prepareToShow() {
+        View bouncerUserSwitcher = mView.findViewById(R.id.keyguard_bouncer_user_switcher);
+        if (bouncerUserSwitcher != null) {
+            bouncerUserSwitcher.setAlpha(0f);
+        }
+    }
+
     @Override
     public void onResume(int reason) {
         if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode()));
@@ -766,8 +860,11 @@
                 case SimPuk:
                     // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
                     SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
-                    if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled(
-                            KeyguardUpdateMonitor.getCurrentUser())) {
+                    boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled(
+                            KeyguardUpdateMonitor.getCurrentUser())
+                            || !mDeviceProvisionedController.isUserSetup(targetUserId);
+
+                    if (securityMode == SecurityMode.None && isLockscreenDisabled) {
                         finish = true;
                         eventSubtype = BOUNCER_DISMISS_SIM;
                         uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
@@ -1022,8 +1119,10 @@
         mLockPatternUtils.reportFailedPasswordAttempt(userId);
         if (timeoutMs > 0) {
             mLockPatternUtils.reportPasswordLockout(timeoutMs, userId);
-            mView.showTimeoutDialog(userId, timeoutMs, mLockPatternUtils,
-                    mSecurityModel.getSecurityMode(userId));
+            if (!mFeatureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) {
+                mView.showTimeoutDialog(userId, timeoutMs, mLockPatternUtils,
+                        mSecurityModel.getSecurityMode(userId));
+            }
         }
     }
 
@@ -1081,11 +1180,11 @@
     }
 
     private void reloadColors() {
-        reinflateViewFlipper(controller -> mView.reloadColors());
+        mView.reloadColors();
     }
 
     /** Handles density or font scale changes. */
-    private void onDensityOrFontScaleChanged() {
+    private void onDensityOrFontScaleOrOrientationChanged() {
         reinflateViewFlipper(controller -> mView.onDensityOrFontScaleChanged());
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index fbacd68..bc5b1ba 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -117,15 +117,19 @@
             KeyguardSecurityCallback keyguardSecurityCallback,
             @Nullable OnViewInflatedCallback onViewInflatedListener) {
         int layoutId = getLayoutIdFor(securityMode);
-        if (layoutId != 0) {
-            if (DEBUG) Log.v(TAG, "inflating on bg thread id = " + layoutId);
+        int viewID = getKeyguardInputViewId(securityMode);
+        if (layoutId != 0 && viewID != 0) {
+            if (DEBUG) {
+                Log.v(TAG, "inflating on bg thread id = "
+                        + layoutId + " . viewID = " + viewID);
+            }
             mAsyncLayoutInflater.inflate(layoutId, mView,
                     (view, resId, parent) -> {
                         mView.addView(view);
                         KeyguardInputViewController<KeyguardInputView> childController =
                                 mKeyguardSecurityViewControllerFactory.create(
-                                        (KeyguardInputView) view, securityMode,
-                                        keyguardSecurityCallback);
+                                        (KeyguardInputView) view.findViewById(viewID),
+                                        securityMode, keyguardSecurityCallback);
                         childController.init();
                         mChildren.add(childController);
                         if (onViewInflatedListener != null) {
@@ -147,6 +151,19 @@
         }
     }
 
+    private int getKeyguardInputViewId(SecurityMode securityMode) {
+        //Keyguard Input View is not the root view of the layout, use these IDs for lookup.
+        switch (securityMode) {
+            case Pattern: return R.id.keyguard_pattern_view;
+            case PIN: return R.id.keyguard_pin_view;
+            case Password: return R.id.keyguard_password_view;
+            case SimPin: return R.id.keyguard_sim_pin_view;
+            case SimPuk: return R.id.keyguard_sim_puk_view;
+            default:
+                return 0;
+        }
+    }
+
     /** Makes the supplied child visible if it is contained win this view, */
     public void show(KeyguardInputViewController<KeyguardInputView> childController) {
         int index = childController.getIndexIn(mView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
index 96ac8ad..e1c060f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
@@ -66,11 +66,11 @@
     }
 
     override fun createAnimator(
-        sceneRoot: ViewGroup?,
+        sceneRoot: ViewGroup,
         startValues: TransitionValues?,
         endValues: TransitionValues?
     ): Animator? {
-        if (sceneRoot == null || startValues == null || endValues == null) {
+        if (startValues == null || endValues == null) {
             return null
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt
index 1461dbe..7c8d91f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt
@@ -22,7 +22,7 @@
 import androidx.core.graphics.drawable.DrawableCompat
 import com.android.settingslib.Utils
 import com.android.systemui.R
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.EMERGENCY_BUTTON
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.EMERGENCY_BUTTON
 
 abstract class KeyguardSimInputView(context: Context, attrs: AttributeSet) :
     KeyguardPinBasedInputView(context, attrs) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index a6252a3..5774e42 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -23,12 +23,14 @@
 import android.os.Build;
 import android.os.Trace;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
 import android.widget.GridLayout;
 
 import com.android.systemui.R;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.statusbar.CrossFadeHelper;
 
 import java.io.PrintWriter;
@@ -110,9 +112,15 @@
         }
     }
 
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+    }
+
     public void dump(PrintWriter pw, String[] args) {
         pw.println("KeyguardStatusView:");
         pw.println("  mDarkAmount: " + mDarkAmount);
+        pw.println("  visibility: " + getVisibility());
         if (mClockView != null) {
             mClockView.dump(pw, args);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 6854c97..c314586 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -20,6 +20,7 @@
 import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.animation.Animator;
 import android.animation.ValueAnimator;
@@ -37,16 +38,24 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.ConstraintSet;
+import androidx.viewpager.widget.ViewPager;
 
 import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
 import com.android.keyguard.logging.KeyguardLogger;
+import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.shared.model.ScreenModel;
+import com.android.systemui.keyguard.shared.model.ScreenState;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -58,14 +67,20 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.ViewController;
 
+import kotlin.coroutines.CoroutineContext;
+import kotlin.coroutines.EmptyCoroutineContext;
+
+import java.io.PrintWriter;
+
 import javax.inject.Inject;
 
 /**
  * Injectable controller for {@link KeyguardStatusView}.
  */
-public class KeyguardStatusViewController extends ViewController<KeyguardStatusView> {
+public class KeyguardStatusViewController extends ViewController<KeyguardStatusView> implements
+        Dumpable {
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
-    private static final String TAG = "KeyguardStatusViewController";
+    @VisibleForTesting static final String TAG = "KeyguardStatusViewController";
 
     /**
      * Duration to use for the animator when the keyguard status view alignment changes, and a
@@ -84,9 +99,12 @@
     private final FeatureFlags mFeatureFlags;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final Rect mClipBounds = new Rect();
+    private final KeyguardInteractor mKeyguardInteractor;
 
     private Boolean mStatusViewCentered = true;
 
+    private DumpManager mDumpManager;
+
     private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
             new TransitionListenerAdapter() {
                 @Override
@@ -112,7 +130,9 @@
             ScreenOffAnimationController screenOffAnimationController,
             KeyguardLogger logger,
             FeatureFlags featureFlags,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            KeyguardInteractor keyguardInteractor,
+            DumpManager dumpManager) {
         super(keyguardStatusView);
         mKeyguardSliceViewController = keyguardSliceViewController;
         mKeyguardClockSwitchController = keyguardClockSwitchController;
@@ -123,11 +143,36 @@
                 logger.getBuffer());
         mInteractionJankMonitor = interactionJankMonitor;
         mFeatureFlags = featureFlags;
+        mDumpManager = dumpManager;
+        mKeyguardInteractor = keyguardInteractor;
     }
 
     @Override
     public void onInit() {
         mKeyguardClockSwitchController.init();
+
+        mDumpManager.registerDumpable(getInstanceName(), this);
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            startCoroutines(EmptyCoroutineContext.INSTANCE);
+        }
+    }
+
+    void startCoroutines(CoroutineContext context) {
+        collectFlow(mView, mKeyguardInteractor.getDozeTimeTick(),
+                (Long millis) -> {
+                        dozeTimeTick();
+                }, context);
+
+        collectFlow(mView, mKeyguardInteractor.getScreenModel(),
+                (ScreenModel model) -> {
+                    if (model.getState() == ScreenState.SCREEN_TURNING_ON) {
+                        dozeTimeTick();
+                    }
+                }, context);
+    }
+
+    public KeyguardStatusView getView() {
+        return mView;
     }
 
     @Override
@@ -142,6 +187,18 @@
         mConfigurationController.removeCallback(mConfigurationListener);
     }
 
+    /** Sets the StatusView as shown on an external display. */
+    public void setDisplayedOnSecondaryDisplay() {
+        mKeyguardClockSwitchController.setShownOnSecondaryDisplay(true);
+    }
+
+    /**
+     * Called in notificationPanelViewController to avoid leak
+     */
+    public void onDestroy() {
+        mDumpManager.unregisterDumpable(getInstanceName());
+    }
+
     /**
      * Updates views on doze time tick.
      */
@@ -289,6 +346,7 @@
     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
         @Override
         public void onTimeChanged() {
+            Slog.v(TAG, "onTimeChanged");
             refreshTime();
         }
 
@@ -333,7 +391,7 @@
      * Updates the alignment of the KeyguardStatusView and animates the transition if requested.
      */
     public void updateAlignment(
-            ConstraintLayout notifContainerParent,
+            ConstraintLayout layout,
             boolean splitShadeEnabled,
             boolean shouldBeCentered,
             boolean animate) {
@@ -343,16 +401,23 @@
         }
 
         mStatusViewCentered = shouldBeCentered;
-        if (notifContainerParent == null) {
+        if (layout == null) {
             return;
         }
 
         ConstraintSet constraintSet = new ConstraintSet();
-        constraintSet.clone(notifContainerParent);
-        int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline;
+        constraintSet.clone(layout);
+        int guideline;
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            guideline = R.id.split_shade_guideline;
+        } else {
+            guideline = R.id.qs_edge_guideline;
+        }
+
+        int statusConstraint = shouldBeCentered ? PARENT_ID : guideline;
         constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END);
         if (!animate) {
-            constraintSet.applyTo(notifContainerParent);
+            constraintSet.applyTo(layout);
             return;
         }
 
@@ -365,6 +430,19 @@
             // Excluding media from the transition on split-shade, as it doesn't transition
             // horizontally properly.
             transition.excludeTarget(R.id.status_view_media_container, true);
+
+            // Exclude smartspace viewpager and its children from the transition.
+            //     - Each step of the transition causes the ViewPager to invoke resize,
+            //     which invokes scrolling to the recalculated position. The scrolling
+            //     actions are congested, resulting in kinky translation, and
+            //     delay in settling to the final position. (http://b/281620564#comment1)
+            //     - Also, the scrolling is unnecessary in the transition. We just want
+            //     the viewpager to stay on the same page.
+            //     - Exclude by Class type instead of resource id, since the resource id
+            //     isn't available for all devices, and probably better to exclude all
+            //     ViewPagers any way.
+            transition.excludeTarget(ViewPager.class, true);
+            transition.excludeChildren(ViewPager.class, true);
         }
 
         transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
@@ -382,7 +460,7 @@
             // old animation rather than setting up the custom animations.
             if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
                 transition.addListener(mKeyguardStatusAlignmentTransitionListener);
-                TransitionManager.beginDelayedTransition(notifContainerParent, transition);
+                TransitionManager.beginDelayedTransition(layout, transition);
             } else {
                 View clockView = clockContainerView.getChildAt(0);
 
@@ -397,15 +475,42 @@
                 adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION);
                 adapter.addTarget(clockView);
                 set.addTransition(adapter);
+
+                if (splitShadeEnabled) {
+                    // Exclude smartspace viewpager and its children from the transition set.
+                    //     - This is necessary in addition to excluding them from the
+                    //     ChangeBounds child transition.
+                    //     - Without this, the viewpager is scrolled to the new position
+                    //     (corresponding to its end size) before the size change is realized.
+                    //     Note that the size change is realized at the end of the ChangeBounds
+                    //     transition. With the "prescrolling", the viewpager ends up in a weird
+                    //     position, then recovers smoothly during the transition, and ends at
+                    //     the position for the current page.
+                    //     - Exclude by Class type instead of resource id, since the resource id
+                    //     isn't available for all devices, and probably better to exclude all
+                    //     ViewPagers any way.
+                    set.excludeTarget(ViewPager.class, true);
+                    set.excludeChildren(ViewPager.class, true);
+                }
+
                 set.addListener(mKeyguardStatusAlignmentTransitionListener);
-                TransitionManager.beginDelayedTransition(notifContainerParent, set);
+                TransitionManager.beginDelayedTransition(layout, set);
             }
         } else {
             transition.addListener(mKeyguardStatusAlignmentTransitionListener);
-            TransitionManager.beginDelayedTransition(notifContainerParent, transition);
+            TransitionManager.beginDelayedTransition(layout, transition);
         }
 
-        constraintSet.applyTo(notifContainerParent);
+        constraintSet.applyTo(layout);
+    }
+
+    @Override
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+        mView.dump(pw, args);
+    }
+
+    String getInstanceName() {
+        return TAG + "#" + hashCode();
     }
 
     @VisibleForTesting
@@ -443,7 +548,8 @@
 
         @Nullable
         @Override
-        public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues,
+        public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+                @Nullable TransitionValues startValues,
                 @Nullable TransitionValues endValues) {
             if (startValues == null || endValues == null) {
                 return null;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bd88b78..5b9b53e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -18,10 +18,14 @@
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.ACTION_USER_STOPPED;
 import static android.content.Intent.ACTION_USER_UNLOCKED;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
@@ -77,9 +81,9 @@
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
 import android.annotation.SuppressLint;
-import android.app.ActivityTaskManager;
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.AlarmManager;
+import android.app.IActivityTaskManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
@@ -162,13 +166,13 @@
 import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent;
-import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.DetectionStatus;
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus;
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus;
 import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.WeatherData;
@@ -252,6 +256,7 @@
     private static final int MSG_REQUIRE_NFC_UNLOCK = 345;
     private static final int MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED = 346;
     private static final int MSG_SERVICE_PROVIDERS_UPDATED = 347;
+    private static final int MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED = 348;
 
     /** Biometric authentication state: Not listening. */
     private static final int BIOMETRIC_STATE_STOPPED = 0;
@@ -313,6 +318,7 @@
     private final AuthController mAuthController;
     private final UiEventLogger mUiEventLogger;
     private final Set<Integer> mFaceAcquiredInfoIgnoreList;
+    private final Set<String> mAllowFingerprintOnOccludingActivitiesFromPackage;
     private final PackageManager mPackageManager;
     private int mStatusBarState;
     private final StatusBarStateController.StateListener mStatusBarStateControllerListener =
@@ -374,6 +380,7 @@
     private boolean mAllowedDisplayStateForFaceAuth = true;
     @VisibleForTesting
     protected boolean mTelephonyCapable;
+    private boolean mAllowFingerprintOnCurrentOccludingActivity;
 
     // Device provisioning state
     private boolean mDeviceProvisioned;
@@ -410,12 +417,15 @@
     private final ActiveUnlockConfig mActiveUnlockConfig;
     private final IDreamManager mDreamManager;
     private final TelephonyManager mTelephonyManager;
+    private final FeatureFlags mFeatureFlags;
     @Nullable
     private final FingerprintManager mFpm;
     @Nullable
     private final FaceManager mFaceManager;
     @Nullable
     private KeyguardFaceAuthInteractor mFaceAuthInteractor;
+    private final TaskStackChangeListeners mTaskStackChangeListeners;
+    private final IActivityTaskManager mActivityTaskManager;
     private final DisplayTracker mDisplayTracker;
     private final LockPatternUtils mLockPatternUtils;
     @VisibleForTesting
@@ -1485,36 +1495,32 @@
     private FaceAuthenticationListener mFaceAuthenticationListener =
             new FaceAuthenticationListener() {
                 @Override
-                public void onAuthenticationStatusChanged(@NonNull AuthenticationStatus status) {
-                    if (status instanceof AcquiredAuthenticationStatus) {
+                public void onAuthenticationStatusChanged(
+                        @NonNull FaceAuthenticationStatus status
+                ) {
+                    if (status instanceof AcquiredFaceAuthenticationStatus) {
                         handleFaceAcquired(
-                                ((AcquiredAuthenticationStatus) status).getAcquiredInfo());
-                    } else if (status instanceof ErrorAuthenticationStatus) {
-                        ErrorAuthenticationStatus error = (ErrorAuthenticationStatus) status;
+                                ((AcquiredFaceAuthenticationStatus) status).getAcquiredInfo());
+                    } else if (status instanceof ErrorFaceAuthenticationStatus) {
+                        ErrorFaceAuthenticationStatus error =
+                                (ErrorFaceAuthenticationStatus) status;
                         handleFaceError(error.getMsgId(), error.getMsg());
-                    } else if (status instanceof FailedAuthenticationStatus) {
-                        if (isFaceLockedOut()) {
-                            // TODO b/270090188: remove this hack when biometrics fixes this issue.
-                            // FailedAuthenticationStatus is emitted after ErrorAuthenticationStatus
-                            // for lockout error is received
-                            mLogger.d("onAuthenticationFailed called after"
-                                    + " face has been locked out");
-                            return;
-                        }
+                    } else if (status instanceof FailedFaceAuthenticationStatus) {
                         handleFaceAuthFailed();
-                    } else if (status instanceof HelpAuthenticationStatus) {
-                        HelpAuthenticationStatus helpMsg = (HelpAuthenticationStatus) status;
+                    } else if (status instanceof HelpFaceAuthenticationStatus) {
+                        HelpFaceAuthenticationStatus helpMsg =
+                                (HelpFaceAuthenticationStatus) status;
                         handleFaceHelp(helpMsg.getMsgId(), helpMsg.getMsg());
-                    } else if (status instanceof SuccessAuthenticationStatus) {
+                    } else if (status instanceof SuccessFaceAuthenticationStatus) {
                         FaceManager.AuthenticationResult result =
-                                ((SuccessAuthenticationStatus) status).getSuccessResult();
+                                ((SuccessFaceAuthenticationStatus) status).getSuccessResult();
                         handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
                     }
                 }
 
                 @Override
-                public void onDetectionStatusChanged(@NonNull DetectionStatus status) {
-                    handleFaceAuthenticated(status.getUserId(), status.isStrongBiometric());
+                public void onDetectionStatusChanged(@NonNull FaceDetectionStatus status) {
+                    handleBiometricDetected(status.getUserId(), FACE, status.isStrongBiometric());
                 }
             };
 
@@ -2011,13 +2017,6 @@
 
                 @Override
                 public void onAuthenticationFailed() {
-                    if (isFaceLockedOut()) {
-                        // TODO b/270090188: remove this hack when biometrics fixes this issue.
-                        // onAuthenticationFailed is called after onAuthenticationError
-                        // for lockout error is received
-                        mLogger.d("onAuthenticationFailed called after face has been locked out");
-                        return;
-                    }
                     handleFaceAuthFailed();
                 }
 
@@ -2367,6 +2366,8 @@
             DevicePostureController devicePostureController,
             Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider,
             FeatureFlags featureFlags,
+            TaskStackChangeListeners taskStackChangeListeners,
+            IActivityTaskManager activityTaskManagerService,
             DisplayTracker displayTracker) {
         mContext = context;
         mSubscriptionManager = subscriptionManager;
@@ -2399,6 +2400,7 @@
         mPackageManager = packageManager;
         mFpm = fingerprintManager;
         mFaceManager = faceManager;
+        mFeatureFlags = featureFlags;
         mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
         mFaceAcquiredInfoIgnoreList = Arrays.stream(
                 mContext.getResources().getIntArray(
@@ -2408,8 +2410,14 @@
         mConfigFaceAuthSupportedPosture = mContext.getResources().getInteger(
                 R.integer.config_face_auth_supported_posture);
         mFaceWakeUpTriggersConfig = faceWakeUpTriggersConfig;
+        mAllowFingerprintOnOccludingActivitiesFromPackage = Arrays.stream(
+                mContext.getResources().getStringArray(
+                        R.array.config_fingerprint_listen_on_occluding_activity_packages))
+                .collect(Collectors.toSet());
+        mTaskStackChangeListeners = taskStackChangeListeners;
+        mActivityTaskManager = activityTaskManagerService;
         mDisplayTracker = displayTracker;
-        if (featureFlags.isEnabled(Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF)) {
+        if (mFeatureFlags.isEnabled(Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF)) {
             mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
         }
 
@@ -2518,6 +2526,9 @@
                     case MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED:
                         handleKeyguardDismissAnimationFinished();
                         break;
+                    case MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED:
+                        notifyAboutEnrollmentChange(msg.arg1);
+                        break;
                     default:
                         super.handleMessage(msg);
                         break;
@@ -2618,6 +2629,8 @@
 
             @Override
             public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) {
+                mHandler.obtainMessage(MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED, modality, 0)
+                        .sendToTarget();
                 mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
                         FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED));
             }
@@ -2627,7 +2640,7 @@
         }
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_ON_KEYGUARD_INIT);
 
-        TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
+        mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
         mIsSystemUser = mUserManager.isSystemUser();
         int user = mUserTracker.getUserId();
         mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
@@ -2713,14 +2726,6 @@
     }
 
     /**
-     * @return true if the FP sensor is non-UDFPS and the device can be unlocked using fingerprint
-     * at this moment.
-     */
-    public boolean isFingerprintAllowedInBouncer() {
-        return !isUdfpsSupported() && isUnlockingWithFingerprintAllowed();
-    }
-
-    /**
      * @return true if there's at least one sfps enrollment for the current user.
      */
     public boolean isSfpsEnrolled() {
@@ -3094,7 +3099,12 @@
                         || shouldListenForFingerprintAssistant
                         || (mKeyguardOccluded && mIsDreaming)
                         || (mKeyguardOccluded && userDoesNotHaveTrust && mKeyguardShowing
-                            && (mOccludingAppRequestingFp || isUdfps || mAlternateBouncerShowing));
+                        && (mOccludingAppRequestingFp
+                        || isUdfps
+                        || mAlternateBouncerShowing
+                        || mAllowFingerprintOnCurrentOccludingActivity
+                )
+            );
 
         // Only listen if this KeyguardUpdateMonitor belongs to the system user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
@@ -3135,6 +3145,7 @@
                     System.currentTimeMillis(),
                     user,
                     shouldListen,
+                    mAllowFingerprintOnCurrentOccludingActivity,
                     mAlternateBouncerShowing,
                     biometricEnabledForUser,
                     mPrimaryBouncerIsOrWillBeShowing,
@@ -3257,6 +3268,13 @@
                 || (posture == mConfigFaceAuthSupportedPosture);
     }
 
+    /**
+     * If the current device posture allows face auth to run.
+     */
+    public boolean doesCurrentPostureAllowFaceAuth() {
+        return doesPostureAllowFaceAuth(mPostureState);
+    }
+
     private void logListenerModelData(@NonNull KeyguardListenModel model) {
         mLogger.logKeyguardListenerModel(model);
         if (model instanceof KeyguardFingerprintListenModel) {
@@ -3460,6 +3478,25 @@
         return mIsFaceEnrolled;
     }
 
+    private void notifyAboutEnrollmentChange(@BiometricAuthenticator.Modality int modality) {
+        BiometricSourceType biometricSourceType;
+        if (modality == TYPE_FINGERPRINT) {
+            biometricSourceType = FINGERPRINT;
+        } else if (modality == TYPE_FACE) {
+            biometricSourceType = FACE;
+        } else {
+            return;
+        }
+        mLogger.notifyAboutEnrollmentsChanged(biometricSourceType);
+        Assert.isMainThread();
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onBiometricEnrollmentStateChanged(biometricSourceType);
+            }
+        }
+    }
+
     private void stopListeningForFingerprint() {
         mLogger.v("stopListeningForFingerprint()");
         if (mFingerprintRunningState == BIOMETRIC_STATE_RUNNING) {
@@ -3550,6 +3587,7 @@
      */
     @VisibleForTesting
     void handleUserSwitching(int userId, CountDownLatch latch) {
+        mLogger.logUserSwitching(userId, "from UserTracker");
         Assert.isMainThread();
         clearBiometricRecognized();
         boolean trustUsuallyManaged = mTrustManager.isTrustUsuallyManaged(userId);
@@ -3570,6 +3608,7 @@
      */
     @VisibleForTesting
     void handleUserSwitchComplete(int userId) {
+        mLogger.logUserSwitchComplete(userId, "from UserTracker");
         Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -4023,6 +4062,11 @@
 
     @AnyThread
     public void setSwitchingUser(boolean switching) {
+        if (switching) {
+            mLogger.logUserSwitching(getCurrentUser(), "from setSwitchingUser");
+        } else {
+            mLogger.logUserSwitchComplete(getCurrentUser(), "from setSwitchingUser");
+        }
         mSwitchingUser = switching;
         // Since this comes in on a binder thread, we need to post it first
         mHandler.post(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
@@ -4161,19 +4205,35 @@
         return mSimDatas.get(subId).slotId;
     }
 
-    private final TaskStackChangeListener
-            mTaskStackListener = new TaskStackChangeListener() {
+    private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
         public void onTaskStackChangedBackground() {
             try {
-                RootTaskInfo info = ActivityTaskManager.getService().getRootTaskInfo(
+                if (mFeatureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
+                    RootTaskInfo standardTask = mActivityTaskManager.getRootTaskInfo(
+                            WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+                    final boolean previousState = mAllowFingerprintOnCurrentOccludingActivity;
+                    mAllowFingerprintOnCurrentOccludingActivity =
+                            standardTask.topActivity != null
+                                    && !TextUtils.isEmpty(standardTask.topActivity.getPackageName())
+                                    && mAllowFingerprintOnOccludingActivitiesFromPackage.contains(
+                                            standardTask.topActivity.getPackageName())
+                                    && standardTask.visible;
+                    if (mAllowFingerprintOnCurrentOccludingActivity != previousState) {
+                        mLogger.allowFingerprintOnCurrentOccludingActivityChanged(
+                                mAllowFingerprintOnCurrentOccludingActivity);
+                        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+                    }
+                }
+
+                RootTaskInfo assistantTask = mActivityTaskManager.getRootTaskInfo(
                         WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
-                if (info == null) {
+                if (assistantTask == null) {
                     return;
                 }
-                mLogger.logTaskStackChangedForAssistant(info.visible);
+                mLogger.logTaskStackChangedForAssistant(assistantTask.visible);
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_ASSISTANT_STACK_CHANGED,
-                        info.visible));
+                        assistantTask.visible));
             } catch (RemoteException e) {
                 mLogger.logException(e, "unable to check task stack ");
             }
@@ -4365,7 +4425,7 @@
 
         mUserTracker.removeCallback(mUserChangedCallback);
 
-        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
+        mTaskStackChangeListeners.unregisterTaskStackListener(mTaskStackListener);
 
         mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
         mBroadcastDispatcher.unregisterReceiver(mBroadcastAllReceiver);
@@ -4425,6 +4485,8 @@
             pw.println("    enabledByUser=" + mBiometricEnabledForUser.get(userId));
             pw.println("    mKeyguardOccluded=" + mKeyguardOccluded);
             pw.println("    mIsDreaming=" + mIsDreaming);
+            pw.println("    mFingerprintListenOnOccludingActivitiesFromPackage="
+                    + mAllowFingerprintOnOccludingActivitiesFromPackage);
             if (isUdfpsSupported()) {
                 pw.println("        udfpsEnrolled=" + isUdfpsEnrolled());
                 pw.println("        shouldListenForUdfps=" + shouldListenForFingerprint(true));
@@ -4521,13 +4583,18 @@
      * Cancels all operations in the scheduler if it is hung for 10 seconds.
      */
     public void startBiometricWatchdog() {
-        if (mFaceManager != null && !isFaceAuthInteractorEnabled()) {
-            mLogger.scheduleWatchdog("face");
-            mFaceManager.scheduleWatchdog();
-        }
-        if (mFpm != null) {
-            mLogger.scheduleWatchdog("fingerprint");
-            mFpm.scheduleWatchdog();
-        }
+        final boolean isFaceAuthInteractorEnabled = isFaceAuthInteractorEnabled();
+        mBackgroundExecutor.execute(() -> {
+            Trace.beginSection("#startBiometricWatchdog");
+            if (mFaceManager != null && !isFaceAuthInteractorEnabled) {
+                mLogger.scheduleWatchdog("face");
+                mFaceManager.scheduleWatchdog();
+            }
+            if (mFpm != null) {
+                mLogger.scheduleWatchdog("fingerprint");
+                mFpm.scheduleWatchdog();
+            }
+            Trace.endSection();
+        });
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 7394005..7b59632 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -327,4 +327,9 @@
      * Called when the enabled trust agents associated with the specified user.
      */
     public void onEnabledTrustAgentsChanged(int userId) { }
+
+    /**
+     * On biometric enrollment state changed
+     */
+    public void onBiometricEnrollmentStateChanged(BiometricSourceType biometricSourceType) { }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 281067d..bc12aee 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -119,6 +119,14 @@
     boolean isUnlockWithWallpaper();
 
     /**
+     * @return Whether the bouncer over dream is showing. Note that the bouncer over dream is
+     * handled independently of the rest of the notification panel. As a result, setting this state
+     * via {@link CentralSurfaces#setBouncerShowing(boolean)} leads to unintended side effects from
+     * states modified behind the dream.
+     */
+    boolean isBouncerShowingOverDream();
+
+    /**
      * @return Whether subtle animation should be used for unlocking the device.
      */
     boolean shouldSubtleWindowAnimationsForUnlock();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 61af722..3990b10 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -23,7 +23,7 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -31,6 +31,7 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.Assert;
 
 import com.google.errorprone.annotations.CompileTimeConstant;
 
@@ -85,6 +86,7 @@
             boolean keyguardFadingAway,
             boolean goingToFullShade,
             int oldStatusBarState) {
+        Assert.isMainThread();
         PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA);
         boolean isOccluded = mKeyguardStateController.isOccluded();
         mKeyguardViewVisibilityAnimating = false;
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index abad0be..8611dbbb 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -16,20 +16,23 @@
 
 package com.android.keyguard;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.graphics.ColorUtils;
 import com.android.settingslib.Utils;
@@ -68,13 +71,9 @@
     public LockIconView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mSensorRect = new RectF();
-    }
 
-    @Override
-    public void onFinishInflate() {
-        super.onFinishInflate();
-        mLockIcon = findViewById(R.id.lock_icon);
-        mBgView = findViewById(R.id.lock_icon_bg);
+        addBgImageView(context, attrs);
+        addLockIconImageView(context, attrs);
     }
 
     void setDozeAmount(float dozeAmount) {
@@ -127,7 +126,6 @@
     /**
      * Set the location of the lock icon.
      */
-    @VisibleForTesting
     public void setCenterLocation(@NonNull Point center, float radius, int drawablePadding) {
         mLockIconCenter = center;
         mRadius = radius;
@@ -157,11 +155,16 @@
     }
 
     float getLocationTop() {
-        return mLockIconCenter.y - mRadius;
+        Rect r = new Rect();
+        mLockIcon.getGlobalVisibleRect(r);
+        return r.top;
     }
 
     float getLocationBottom() {
-        return mLockIconCenter.y + mRadius;
+        Rect r = new Rect();
+        mLockIcon.getGlobalVisibleRect(r);
+        return r.bottom;
+
     }
 
     /**
@@ -184,6 +187,34 @@
         mLockIcon.setImageState(getLockIconState(mIconType, mAod), true);
     }
 
+    public ImageView getLockIcon() {
+        return mLockIcon;
+    }
+
+    private void addLockIconImageView(Context context, AttributeSet attrs) {
+        mLockIcon = new ImageView(context, attrs);
+        mLockIcon.setId(R.id.lock_icon);
+        mLockIcon.setScaleType(ImageView.ScaleType.CENTER_CROP);
+        addView(mLockIcon);
+        LayoutParams lp = (LayoutParams) mLockIcon.getLayoutParams();
+        lp.height = MATCH_PARENT;
+        lp.width = MATCH_PARENT;
+        lp.gravity = Gravity.CENTER;
+        mLockIcon.setLayoutParams(lp);
+    }
+
+    private void addBgImageView(Context context, AttributeSet attrs) {
+        mBgView = new ImageView(context, attrs);
+        mBgView.setId(R.id.lock_icon_bg);
+        mBgView.setImageDrawable(context.getDrawable(R.drawable.fingerprint_bg));
+        mBgView.setVisibility(View.INVISIBLE);
+        addView(mBgView);
+        LayoutParams lp = (LayoutParams) mBgView.getLayoutParams();
+        lp.height = MATCH_PARENT;
+        lp.width = MATCH_PARENT;
+        mBgView.setLayoutParams(lp);
+    }
+
     private static int[] getLockIconState(@IconType int icon, boolean aod) {
         if (icon == ICON_NONE) {
             return new int[0];
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 9003c43..951a6ae 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -24,6 +24,8 @@
 import static com.android.keyguard.LockIconView.ICON_UNLOCK;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.content.res.Configuration;
@@ -38,6 +40,7 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.MathUtils;
+import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -56,13 +59,14 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.AuthRippleController;
 import com.android.systemui.biometrics.UdfpsController;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -123,6 +127,7 @@
     private int mActivePointerId = -1;
 
     private boolean mIsDozing;
+    private boolean mIsActiveDreamLockscreenHosted;
     private boolean mIsBouncerShowing;
     private boolean mRunningFPS;
     private boolean mCanDismissLockScreen;
@@ -164,6 +169,13 @@
         updateVisibility();
     };
 
+    @VisibleForTesting
+    final Consumer<Boolean> mIsActiveDreamLockscreenHostedCallback =
+            (Boolean isLockscreenHosted) -> {
+                mIsActiveDreamLockscreenHosted = isLockscreenHosted;
+                updateVisibility();
+            };
+
     @Inject
     public LockIconViewController(
             @Nullable LockIconView view,
@@ -223,6 +235,11 @@
                     mDozeTransitionCallback);
             collectFlow(mView, mKeyguardInteractor.isDozing(), mIsDozingCallback);
         }
+
+        if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+            collectFlow(mView, mKeyguardInteractor.isActiveDreamLockscreenHosted(),
+                    mIsActiveDreamLockscreenHostedCallback);
+        }
     }
 
     @Override
@@ -288,6 +305,11 @@
             return;
         }
 
+        if (mIsKeyguardShowing && mIsActiveDreamLockscreenHosted) {
+            mView.setVisibility(View.INVISIBLE);
+            return;
+        }
+
         boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon
                 && !mShowAodUnlockedIcon && !mShowAodLockIcon;
         mShowLockIcon = !mCanDismissLockScreen && isLockScreen()
@@ -397,15 +419,20 @@
     private void updateLockIconLocation() {
         final float scaleFactor = mAuthController.getScaleFactor();
         final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor);
-        if (mUdfpsSupported) {
-            mView.setCenterLocation(mAuthController.getUdfpsLocation(),
-                    mAuthController.getUdfpsRadius(), scaledPadding);
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+            mView.getLockIcon().setPadding(scaledPadding, scaledPadding, scaledPadding,
+                    scaledPadding);
         } else {
-            mView.setCenterLocation(
-                    new Point((int) mWidthPixels / 2,
-                            (int) (mHeightPixels
-                                    - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))),
+            if (mUdfpsSupported) {
+                mView.setCenterLocation(mAuthController.getUdfpsLocation(),
+                        mAuthController.getUdfpsRadius(), scaledPadding);
+            } else {
+                mView.setCenterLocation(
+                        new Point((int) mWidthPixels / 2,
+                                (int) (mHeightPixels
+                                        - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))),
                         sLockIconRadiusPx * scaleFactor, scaledPadding);
+            }
         }
     }
 
@@ -433,6 +460,7 @@
         pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
         pw.println(" mSensorTouchLocation: " + mSensorTouchLocation);
         pw.println(" mDefaultPaddingPx: " + mDefaultPaddingPx);
+        pw.println(" mIsActiveDreamLockscreenHosted: " + mIsActiveDreamLockscreenHosted);
 
         if (mView != null) {
             mView.dump(pw, args);
@@ -587,12 +615,7 @@
             case MotionEvent.ACTION_DOWN:
             case MotionEvent.ACTION_HOVER_ENTER:
                 if (!mDownDetected && mAccessibilityManager.isTouchExplorationEnabled()) {
-                    mVibrator.vibrate(
-                            Process.myUid(),
-                            getContext().getOpPackageName(),
-                            UdfpsController.EFFECT_CLICK,
-                            "lock-icon-down",
-                            TOUCH_VIBRATION_ATTRIBUTES);
+                    vibrateOnTouchExploration();
                 }
 
                 // The pointer that causes ACTION_DOWN is always at index 0.
@@ -673,13 +696,8 @@
             mOnGestureDetectedRunnable.run();
         }
 
-        // play device entry haptic (same as biometric success haptic)
-        mVibrator.vibrate(
-                Process.myUid(),
-                getContext().getOpPackageName(),
-                UdfpsController.EFFECT_CLICK,
-                "lock-screen-lock-icon-longpress",
-                TOUCH_VIBRATION_ATTRIBUTES);
+        // play device entry haptic (consistent with UDFPS controller longpress)
+        vibrateOnLongPress();
 
         mKeyguardViewController.showPrimaryBouncer(/* scrim */ true);
     }
@@ -727,6 +745,37 @@
         });
     }
 
+    @VisibleForTesting
+    void vibrateOnTouchExploration() {
+        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            mVibrator.performHapticFeedback(
+                    mView,
+                    HapticFeedbackConstants.CONTEXT_CLICK
+            );
+        } else {
+            mVibrator.vibrate(
+                    Process.myUid(),
+                    getContext().getOpPackageName(),
+                    UdfpsController.EFFECT_CLICK,
+                    "lock-icon-down",
+                    TOUCH_VIBRATION_ATTRIBUTES);
+        }
+    }
+
+    @VisibleForTesting
+    void vibrateOnLongPress() {
+        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            mVibrator.performHapticFeedback(mView, UdfpsController.LONG_PRESS);
+        } else {
+            mVibrator.vibrate(
+                    Process.myUid(),
+                    getContext().getOpPackageName(),
+                    UdfpsController.EFFECT_CLICK,
+                    "lock-screen-lock-icon-longpress",
+                    TOUCH_VIBRATION_ATTRIBUTES);
+        }
+    }
+
     private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
         @Override
         public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index 7d76f12..e773416 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -16,11 +16,11 @@
 package com.android.keyguard;
 
 import static com.android.settingslib.Utils.getColorAttrDefaultColor;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BACKGROUND;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BACKGROUND_PRESSED;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BUTTON;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_PRESSED;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BACKGROUND;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BACKGROUND_PRESSED;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BUTTON;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_PRESSED;
 import static com.android.systemui.util.ColorUtilKt.getPrivateAttrColorIfUnset;
 
 import android.animation.AnimatorSet;
@@ -58,6 +58,7 @@
     private float mStartRadius;
     private float mEndRadius;
     private int mHeight;
+    private int mWidth;
 
     private static final int EXPAND_ANIMATION_MS = 100;
     private static final int EXPAND_COLOR_ANIMATION_MS = 50;
@@ -95,11 +96,17 @@
         mBackground.setCornerRadius(mEndRadius + (mStartRadius - mEndRadius) * progress);
         int height = (int) (mHeight * 0.7f + mHeight * 0.3 * progress);
         int difference = mHeight - height;
-        mBackground.setBounds(0, difference / 2, mHeight, mHeight - difference / 2);
+
+        int left = 0;
+        int top = difference / 2;
+        int right = mWidth;
+        int bottom = mHeight - difference / 2;
+        mBackground.setBounds(left, top, right, bottom);
     }
 
-    void onLayout(int height) {
+    void onLayout(int width, int height) {
         boolean shouldUpdateHeight = height != mHeight;
+        mWidth = width;
         mHeight = height;
         mStartRadius = height / 2f;
         mEndRadius = height / 4f;
@@ -121,7 +128,7 @@
         ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle);
         @SuppressLint("ResourceType") TypedArray a = ctw.obtainStyledAttributes(customAttrs);
         mNormalBackgroundColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0,
-                       NUM_PAD_BACKGROUND);
+                NUM_PAD_BACKGROUND);
         a.recycle();
 
         mPressedBackgroundColor = getColorAttrDefaultColor(context, NUM_PAD_BACKGROUND_PRESSED);
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index ebd234f..5c2f3b3 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -15,8 +15,8 @@
  */
 package com.android.keyguard;
 
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BUTTON;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_BUTTON;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -74,8 +74,9 @@
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
-
-        if (mAnimator != null) mAnimator.onLayout(b - t);
+        int width = r - l;
+        int height = b - t;
+        if (mAnimator != null) mAnimator.onLayout(width, height);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index e22fc30..466d154 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -15,7 +15,7 @@
  */
 package com.android.keyguard;
 
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -211,7 +211,9 @@
         left = centerX - mKlondikeText.getMeasuredWidth() / 2;
         mKlondikeText.layout(left, top, left + mKlondikeText.getMeasuredWidth(), bottom);
 
-        if (mAnimator != null) mAnimator.onLayout(b - t);
+        int width = r - l;
+        int height = b - t;
+        if (mAnimator != null) mAnimator.onLayout(width, height);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java
index 32f06dc..7c129b4 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java
@@ -16,7 +16,7 @@
 
 package com.android.keyguard;
 
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.PIN_SHAPES;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.PIN_SHAPES;
 
 import android.content.Context;
 import android.graphics.drawable.AnimatedVectorDrawable;
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
index 500910c..56c0953 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
@@ -16,7 +16,7 @@
 
 package com.android.keyguard;
 
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.ColorId.PIN_SHAPES;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.PIN_SHAPES;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
index 50f8f7e..14ec27a 100644
--- a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
@@ -104,4 +104,9 @@
      * Call when cancel button is pressed in bouncer.
      */
     void onCancelClicked();
+
+    /**
+     * Determines if bouncer has swiped down.
+     */
+    void onBouncerSwipeDown();
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
deleted file mode 100644
index 7517dee..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import android.app.WallpaperManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
-import android.graphics.Paint.Style;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.TextClock;
-
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.systemui.R;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.plugins.ClockPlugin;
-
-import java.util.TimeZone;
-
-/**
- * Controller for Stretch clock that can appear on lock screen and AOD.
- */
-public class AnalogClockController implements ClockPlugin {
-
-    /**
-     * Resources used to get title and thumbnail.
-     */
-    private final Resources mResources;
-
-    /**
-     * LayoutInflater used to inflate custom clock views.
-     */
-    private final LayoutInflater mLayoutInflater;
-
-    /**
-     * Extracts accent color from wallpaper.
-     */
-    private final SysuiColorExtractor mColorExtractor;
-
-    /**
-     * Computes preferred position of clock.
-     */
-    private final SmallClockPosition mClockPosition;
-
-    /**
-     * Renders preview from clock view.
-     */
-    private final ViewPreviewer mRenderer = new ViewPreviewer();
-
-    /**
-     * Custom clock shown on AOD screen and behind stack scroller on lock.
-     */
-    private ClockLayout mBigClockView;
-    private ImageClock mAnalogClock;
-
-    /**
-     * Small clock shown on lock screen above stack scroller.
-     */
-    private View mView;
-    private TextClock mLockClock;
-
-    /**
-     * Helper to extract colors from wallpaper palette for clock face.
-     */
-    private final ClockPalette mPalette = new ClockPalette();
-
-    /**
-     * Create a BubbleClockController instance.
-     *
-     * @param res Resources contains title and thumbnail.
-     * @param inflater Inflater used to inflate custom clock views.
-     * @param colorExtractor Extracts accent color from wallpaper.
-     */
-    public AnalogClockController(Resources res, LayoutInflater inflater,
-            SysuiColorExtractor colorExtractor) {
-        mResources = res;
-        mLayoutInflater = inflater;
-        mColorExtractor = colorExtractor;
-        mClockPosition = new SmallClockPosition(inflater.getContext());
-    }
-
-    private void createViews() {
-        mBigClockView = (ClockLayout) mLayoutInflater.inflate(R.layout.analog_clock, null);
-        mAnalogClock = mBigClockView.findViewById(R.id.analog_clock);
-
-        mView = mLayoutInflater.inflate(R.layout.digital_clock, null);
-        mLockClock = mView.findViewById(R.id.lock_screen_clock);
-    }
-
-    @Override
-    public void onDestroyView() {
-        mBigClockView = null;
-        mAnalogClock = null;
-        mView = null;
-        mLockClock = null;
-    }
-
-    @Override
-    public String getName() {
-        return "analog";
-    }
-
-    @Override
-    public String getTitle() {
-        return mResources.getString(R.string.clock_title_analog);
-    }
-
-    @Override
-    public Bitmap getThumbnail() {
-        return BitmapFactory.decodeResource(mResources, R.drawable.analog_thumbnail);
-    }
-
-    @Override
-    public Bitmap getPreview(int width, int height) {
-
-        // Use the big clock view for the preview
-        View view = getBigClockView();
-
-        // Initialize state of plugin before generating preview.
-        setDarkAmount(1f);
-        setTextColor(Color.WHITE);
-        ColorExtractor.GradientColors colors = mColorExtractor.getColors(
-                WallpaperManager.FLAG_LOCK);
-        setColorPalette(colors.supportsDarkText(), colors.getColorPalette());
-        onTimeTick();
-
-        return mRenderer.createPreview(view, width, height);
-    }
-
-    @Override
-    public View getView() {
-        if (mView == null) {
-            createViews();
-        }
-        return mView;
-    }
-
-    @Override
-    public View getBigClockView() {
-        if (mBigClockView == null) {
-            createViews();
-        }
-        return mBigClockView;
-    }
-
-    @Override
-    public int getPreferredY(int totalHeight) {
-        return mClockPosition.getPreferredY();
-    }
-
-    @Override
-    public void setStyle(Style style) {}
-
-    @Override
-    public void setTextColor(int color) {
-        updateColor();
-    }
-
-    @Override
-    public void setColorPalette(boolean supportsDarkText, int[] colorPalette) {
-        mPalette.setColorPalette(supportsDarkText, colorPalette);
-        updateColor();
-    }
-
-    private void updateColor() {
-        final int primary = mPalette.getPrimaryColor();
-        final int secondary = mPalette.getSecondaryColor();
-        mLockClock.setTextColor(secondary);
-        mAnalogClock.setClockColors(primary, secondary);
-    }
-
-    @Override
-    public void onTimeTick() {
-        mAnalogClock.onTimeChanged();
-        mBigClockView.onTimeChanged();
-        mLockClock.refreshTime();
-    }
-
-    @Override
-    public void setDarkAmount(float darkAmount) {
-        mPalette.setDarkAmount(darkAmount);
-        mClockPosition.setDarkAmount(darkAmount);
-        mBigClockView.setDarkAmount(darkAmount);
-    }
-
-    @Override
-    public void onTimeZoneChanged(TimeZone timeZone) {
-        mAnalogClock.onTimeZoneChanged(timeZone);
-    }
-
-    @Override
-    public boolean shouldShowStatusArea() {
-        return true;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
deleted file mode 100644
index 1add1a3..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import android.app.WallpaperManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
-import android.graphics.Paint.Style;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.TextClock;
-
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.systemui.R;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.plugins.ClockPlugin;
-
-import java.util.TimeZone;
-
-/**
- * Controller for Bubble clock that can appear on lock screen and AOD.
- */
-public class BubbleClockController implements ClockPlugin {
-
-    /**
-     * Resources used to get title and thumbnail.
-     */
-    private final Resources mResources;
-
-    /**
-     * LayoutInflater used to inflate custom clock views.
-     */
-    private final LayoutInflater mLayoutInflater;
-
-    /**
-     * Extracts accent color from wallpaper.
-     */
-    private final SysuiColorExtractor mColorExtractor;
-
-    /**
-     * Computes preferred position of clock.
-     */
-    private final SmallClockPosition mClockPosition;
-
-    /**
-     * Renders preview from clock view.
-     */
-    private final ViewPreviewer mRenderer = new ViewPreviewer();
-
-    /**
-     * Custom clock shown on AOD screen and behind stack scroller on lock.
-     */
-    private ClockLayout mView;
-    private ImageClock mAnalogClock;
-
-    /**
-     * Small clock shown on lock screen above stack scroller.
-     */
-    private View mLockClockContainer;
-    private TextClock mLockClock;
-
-    /**
-     * Helper to extract colors from wallpaper palette for clock face.
-     */
-    private final ClockPalette mPalette = new ClockPalette();
-
-    /**
-     * Create a BubbleClockController instance.
-     *
-     * @param res Resources contains title and thumbnail.
-     * @param inflater Inflater used to inflate custom clock views.
-     * @param colorExtractor Extracts accent color from wallpaper.
-     */
-    public BubbleClockController(Resources res, LayoutInflater inflater,
-            SysuiColorExtractor colorExtractor) {
-        mResources = res;
-        mLayoutInflater = inflater;
-        mColorExtractor = colorExtractor;
-        mClockPosition = new SmallClockPosition(inflater.getContext());
-    }
-
-    private void createViews() {
-        mView = (ClockLayout) mLayoutInflater.inflate(R.layout.bubble_clock, null);
-        mAnalogClock = (ImageClock) mView.findViewById(R.id.analog_clock);
-
-        mLockClockContainer = mLayoutInflater.inflate(R.layout.digital_clock, null);
-        mLockClock = (TextClock) mLockClockContainer.findViewById(R.id.lock_screen_clock);
-    }
-
-    @Override
-    public void onDestroyView() {
-        mView = null;
-        mAnalogClock = null;
-        mLockClockContainer = null;
-        mLockClock = null;
-    }
-
-    @Override
-    public String getName() {
-        return "bubble";
-    }
-
-    @Override
-    public String getTitle() {
-        return mResources.getString(R.string.clock_title_bubble);
-    }
-
-    @Override
-    public Bitmap getThumbnail() {
-        return BitmapFactory.decodeResource(mResources, R.drawable.bubble_thumbnail);
-    }
-
-    @Override
-    public Bitmap getPreview(int width, int height) {
-
-        // Use the big clock view for the preview
-        View view = getBigClockView();
-
-        // Initialize state of plugin before generating preview.
-        setDarkAmount(1f);
-        setTextColor(Color.WHITE);
-        ColorExtractor.GradientColors colors = mColorExtractor.getColors(
-                WallpaperManager.FLAG_LOCK);
-        setColorPalette(colors.supportsDarkText(), colors.getColorPalette());
-        onTimeTick();
-
-        return mRenderer.createPreview(view, width, height);
-    }
-
-    @Override
-    public View getView() {
-        if (mLockClockContainer == null) {
-            createViews();
-        }
-        return mLockClockContainer;
-    }
-
-    @Override
-    public View getBigClockView() {
-        if (mView == null) {
-            createViews();
-        }
-        return mView;
-    }
-
-    @Override
-    public int getPreferredY(int totalHeight) {
-        return mClockPosition.getPreferredY();
-    }
-
-    @Override
-    public void setStyle(Style style) {}
-
-    @Override
-    public void setTextColor(int color) {
-        updateColor();
-    }
-
-    @Override
-    public void setColorPalette(boolean supportsDarkText, int[] colorPalette) {
-        mPalette.setColorPalette(supportsDarkText, colorPalette);
-        updateColor();
-    }
-
-    private void updateColor() {
-        final int primary = mPalette.getPrimaryColor();
-        final int secondary = mPalette.getSecondaryColor();
-        mLockClock.setTextColor(secondary);
-        mAnalogClock.setClockColors(primary, secondary);
-    }
-
-    @Override
-    public void setDarkAmount(float darkAmount) {
-        mPalette.setDarkAmount(darkAmount);
-        mClockPosition.setDarkAmount(darkAmount);
-        mView.setDarkAmount(darkAmount);
-    }
-
-    @Override
-    public void onTimeTick() {
-        mAnalogClock.onTimeChanged();
-        mView.onTimeChanged();
-        mLockClock.refreshTime();
-    }
-
-    @Override
-    public void onTimeZoneChanged(TimeZone timeZone) {
-        mAnalogClock.onTimeZoneChanged(timeZone);
-    }
-
-    @Override
-    public boolean shouldShowStatusArea() {
-        return true;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockInfo.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfo.java
deleted file mode 100644
index 0210e08..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockInfo.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import android.graphics.Bitmap;
-
-import java.util.function.Supplier;
-
-/**
- * Metadata about an available clock face.
- */
-final class ClockInfo {
-
-    private final String mName;
-    private final Supplier<String> mTitle;
-    private final String mId;
-    private final Supplier<Bitmap> mThumbnail;
-    private final Supplier<Bitmap> mPreview;
-
-    private ClockInfo(String name, Supplier<String> title, String id,
-            Supplier<Bitmap> thumbnail, Supplier<Bitmap> preview) {
-        mName = name;
-        mTitle = title;
-        mId = id;
-        mThumbnail = thumbnail;
-        mPreview = preview;
-    }
-
-    /**
-     * Gets the non-internationalized name for the clock face.
-     */
-    String getName() {
-        return mName;
-    }
-
-    /**
-     * Gets the name (title) of the clock face to be shown in the picker app.
-     */
-    String getTitle() {
-        return mTitle.get();
-    }
-
-    /**
-     * Gets the ID of the clock face, used by the picker to set the current selection.
-     */
-    String getId() {
-        return mId;
-    }
-
-    /**
-     * Gets a thumbnail image of the clock.
-     */
-    Bitmap getThumbnail() {
-        return mThumbnail.get();
-    }
-
-    /**
-     * Gets a potentially realistic preview image of the clock face.
-     */
-    Bitmap getPreview() {
-        return mPreview.get();
-    }
-
-    static Builder builder() {
-        return new Builder();
-    }
-
-    static class Builder {
-        private String mName;
-        private Supplier<String> mTitle;
-        private String mId;
-        private Supplier<Bitmap> mThumbnail;
-        private Supplier<Bitmap> mPreview;
-
-        public ClockInfo build() {
-            return new ClockInfo(mName, mTitle, mId, mThumbnail, mPreview);
-        }
-
-        public Builder setName(String name) {
-            mName = name;
-            return this;
-        }
-
-        public Builder setTitle(Supplier<String> title) {
-            mTitle = title;
-            return this;
-        }
-
-        public Builder setId(String id) {
-            mId = id;
-            return this;
-        }
-
-        public Builder setThumbnail(Supplier<Bitmap> thumbnail) {
-            mThumbnail = thumbnail;
-            return this;
-        }
-
-        public Builder setPreview(Supplier<Bitmap> preview) {
-            mPreview = preview;
-            return this;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java
deleted file mode 100644
index 72a44bd..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockInfoModule.java
+++ /dev/null
@@ -1,38 +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.keyguard.clock;
-
-import java.util.List;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * Dagger Module for clock package.
- *
- * @deprecated Migrate to ClockRegistry
- */
-@Module
-@Deprecated
-public abstract class ClockInfoModule {
-
-    /** */
-    @Provides
-    public static List<ClockInfo> provideClockInfoList(ClockManager clockManager) {
-        return clockManager.getClockInfos();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
deleted file mode 100644
index d44d89e..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.util.AttributeSet;
-import android.util.MathUtils;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.systemui.R;
-
-/**
- * Positions clock faces (analog, digital, typographic) and handles pixel shifting
- * to prevent screen burn-in.
- */
-public class ClockLayout extends FrameLayout {
-
-    private static final int ANALOG_CLOCK_SHIFT_FACTOR = 3;
-    /**
-     * Clock face views.
-     */
-    private View mAnalogClock;
-
-    /**
-     * Pixel shifting amplitudes used to prevent screen burn-in.
-     */
-    private int mBurnInPreventionOffsetX;
-    private int mBurnInPreventionOffsetY;
-
-    private float mDarkAmount;
-
-    public ClockLayout(Context context) {
-        this(context, null);
-    }
-
-    public ClockLayout(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public ClockLayout(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mAnalogClock = findViewById(R.id.analog_clock);
-
-        // Get pixel shifting X, Y amplitudes from resources.
-        Resources resources = getResources();
-        mBurnInPreventionOffsetX = resources.getDimensionPixelSize(
-            R.dimen.burn_in_prevention_offset_x);
-        mBurnInPreventionOffsetY = resources.getDimensionPixelSize(
-            R.dimen.burn_in_prevention_offset_y);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        positionChildren();
-    }
-
-    void onTimeChanged() {
-        positionChildren();
-    }
-
-    /**
-     * See {@link com.android.systemui.plugins.ClockPlugin#setDarkAmount(float)}.
-     */
-    void setDarkAmount(float darkAmount) {
-        mDarkAmount = darkAmount;
-        positionChildren();
-    }
-
-    private void positionChildren() {
-        final float offsetX = MathUtils.lerp(0f,
-                getBurnInOffset(mBurnInPreventionOffsetX * 2, true) - mBurnInPreventionOffsetX,
-                mDarkAmount);
-        final float offsetY = MathUtils.lerp(0f,
-                getBurnInOffset(mBurnInPreventionOffsetY * 2, false)
-                        - 0.5f * mBurnInPreventionOffsetY,
-                mDarkAmount);
-
-        // Put the analog clock in the middle of the screen.
-        if (mAnalogClock != null) {
-            mAnalogClock.setX(Math.max(0f, 0.5f * (getWidth() - mAnalogClock.getWidth()))
-                    + ANALOG_CLOCK_SHIFT_FACTOR * offsetX);
-            mAnalogClock.setY(Math.max(0f, 0.5f * (getHeight() - mAnalogClock.getHeight()))
-                    + ANALOG_CLOCK_SHIFT_FACTOR * offsetY);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
deleted file mode 100644
index 122c521..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import android.annotation.Nullable;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.ArrayMap;
-import android.util.DisplayMetrics;
-import android.view.LayoutInflater;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.dock.DockManager.DockEventListener;
-import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.settings.UserTracker;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.function.Supplier;
-
-import javax.inject.Inject;
-
-/**
- * Manages custom clock faces for AOD and lock screen.
- *
- * @deprecated Migrate to ClockRegistry
- */
-@SysUISingleton
-@Deprecated
-public final class ClockManager {
-
-    private static final String TAG = "ClockOptsProvider";
-
-    private final AvailableClocks mPreviewClocks;
-    private final List<Supplier<ClockPlugin>> mBuiltinClocks = new ArrayList<>();
-
-    private final Context mContext;
-    private final ContentResolver mContentResolver;
-    private final SettingsWrapper mSettingsWrapper;
-    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
-    private final UserTracker mUserTracker;
-    private final Executor mMainExecutor;
-
-    /**
-     * Observe settings changes to know when to switch the clock face.
-     */
-    private final ContentObserver mContentObserver =
-            new ContentObserver(mMainHandler) {
-                @Override
-                public void onChange(boolean selfChange, Collection<Uri> uris,
-                        int flags, int userId) {
-                    if (Objects.equals(userId,
-                            mUserTracker.getUserId())) {
-                        reload();
-                    }
-                }
-            };
-
-    /**
-     * Observe user changes and react by potentially loading the custom clock for the new user.
-     */
-    private final UserTracker.Callback mUserChangedCallback =
-            new UserTracker.Callback() {
-                @Override
-                public void onUserChanged(int newUser, @NonNull Context userContext) {
-                    reload();
-                }
-            };
-
-    private final PluginManager mPluginManager;
-    @Nullable private final DockManager mDockManager;
-
-    /**
-     * Observe changes to dock state to know when to switch the clock face.
-     */
-    private final DockEventListener mDockEventListener =
-            new DockEventListener() {
-                @Override
-                public void onEvent(int event) {
-                    mIsDocked = (event == DockManager.STATE_DOCKED
-                            || event == DockManager.STATE_DOCKED_HIDE);
-                    reload();
-                }
-            };
-
-    /**
-     * When docked, the DOCKED_CLOCK_FACE setting will be checked for the custom clock face
-     * to show.
-     */
-    private boolean mIsDocked;
-
-    /**
-     * Listeners for onClockChanged event.
-     *
-     * Each listener must receive a separate clock plugin instance. Otherwise, there could be
-     * problems like attempting to attach a view that already has a parent. To deal with this issue,
-     * each listener is associated with a collection of available clocks. When onClockChanged is
-     * fired the current clock plugin instance is retrieved from that listeners available clocks.
-     */
-    private final Map<ClockChangedListener, AvailableClocks> mListeners = new ArrayMap<>();
-
-    private final int mWidth;
-    private final int mHeight;
-
-    @Inject
-    public ClockManager(Context context, LayoutInflater layoutInflater,
-            PluginManager pluginManager, SysuiColorExtractor colorExtractor,
-            @Nullable DockManager dockManager, UserTracker userTracker,
-            @Main Executor mainExecutor) {
-        this(context, layoutInflater, pluginManager, colorExtractor,
-                context.getContentResolver(), userTracker, mainExecutor,
-                new SettingsWrapper(context.getContentResolver()), dockManager);
-    }
-
-    @VisibleForTesting
-    ClockManager(Context context, LayoutInflater layoutInflater,
-            PluginManager pluginManager, SysuiColorExtractor colorExtractor,
-            ContentResolver contentResolver, UserTracker userTracker, Executor mainExecutor,
-            SettingsWrapper settingsWrapper, DockManager dockManager) {
-        mContext = context;
-        mPluginManager = pluginManager;
-        mContentResolver = contentResolver;
-        mSettingsWrapper = settingsWrapper;
-        mUserTracker = userTracker;
-        mMainExecutor = mainExecutor;
-        mDockManager = dockManager;
-        mPreviewClocks = new AvailableClocks();
-
-        Resources res = context.getResources();
-
-        addBuiltinClock(() -> new DefaultClockController(res, layoutInflater, colorExtractor));
-
-        // Store the size of the display for generation of clock preview.
-        DisplayMetrics dm = res.getDisplayMetrics();
-        mWidth = dm.widthPixels;
-        mHeight = dm.heightPixels;
-    }
-
-    /**
-     * Add listener to be notified when clock implementation should change.
-     */
-    public void addOnClockChangedListener(ClockChangedListener listener) {
-        if (mListeners.isEmpty()) {
-            register();
-        }
-        AvailableClocks availableClocks = new AvailableClocks();
-        for (int i = 0; i < mBuiltinClocks.size(); i++) {
-            availableClocks.addClockPlugin(mBuiltinClocks.get(i).get());
-        }
-        mListeners.put(listener, availableClocks);
-        mPluginManager.addPluginListener(availableClocks, ClockPlugin.class, true);
-        reload();
-    }
-
-    /**
-     * Remove listener added with {@link addOnClockChangedListener}.
-     */
-    public void removeOnClockChangedListener(ClockChangedListener listener) {
-        AvailableClocks availableClocks = mListeners.remove(listener);
-        mPluginManager.removePluginListener(availableClocks);
-        if (mListeners.isEmpty()) {
-            unregister();
-        }
-    }
-
-    /**
-     * Get information about available clock faces.
-     */
-    List<ClockInfo> getClockInfos() {
-        return mPreviewClocks.getInfo();
-    }
-
-    /**
-     * Get the current clock.
-     * @return current custom clock or null for default.
-     */
-    @Nullable
-    ClockPlugin getCurrentClock() {
-        return mPreviewClocks.getCurrentClock();
-    }
-
-    @VisibleForTesting
-    boolean isDocked() {
-        return mIsDocked;
-    }
-
-    @VisibleForTesting
-    ContentObserver getContentObserver() {
-        return mContentObserver;
-    }
-
-    @VisibleForTesting
-    void addBuiltinClock(Supplier<ClockPlugin> pluginSupplier) {
-        ClockPlugin plugin = pluginSupplier.get();
-        mPreviewClocks.addClockPlugin(plugin);
-        mBuiltinClocks.add(pluginSupplier);
-    }
-
-    private void register() {
-        mPluginManager.addPluginListener(mPreviewClocks, ClockPlugin.class, true);
-        mContentResolver.registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
-                false, mContentObserver, UserHandle.USER_ALL);
-        mContentResolver.registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.DOCKED_CLOCK_FACE),
-                false, mContentObserver, UserHandle.USER_ALL);
-        mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
-        if (mDockManager != null) {
-            mDockManager.addListener(mDockEventListener);
-        }
-    }
-
-    private void unregister() {
-        mPluginManager.removePluginListener(mPreviewClocks);
-        mContentResolver.unregisterContentObserver(mContentObserver);
-        mUserTracker.removeCallback(mUserChangedCallback);
-        if (mDockManager != null) {
-            mDockManager.removeListener(mDockEventListener);
-        }
-    }
-
-    private void reload() {
-        mPreviewClocks.reloadCurrentClock();
-        mListeners.forEach((listener, clocks) -> {
-            clocks.reloadCurrentClock();
-            final ClockPlugin clock = clocks.getCurrentClock();
-            if (Looper.myLooper() == Looper.getMainLooper()) {
-                listener.onClockChanged(clock instanceof DefaultClockController ? null : clock);
-            } else {
-                mMainHandler.post(() -> listener.onClockChanged(
-                        clock instanceof DefaultClockController ? null : clock));
-            }
-        });
-    }
-
-    /**
-     * Listener for events that should cause the custom clock face to change.
-     */
-    public interface ClockChangedListener {
-        /**
-         * Called when custom clock should change.
-         *
-         * @param clock Custom clock face to use. A null value indicates the default clock face.
-         */
-        void onClockChanged(ClockPlugin clock);
-    }
-
-    /**
-     * Collection of available clocks.
-     */
-    private final class AvailableClocks implements PluginListener<ClockPlugin> {
-
-        /**
-         * Map from expected value stored in settings to plugin for custom clock face.
-         */
-        private final Map<String, ClockPlugin> mClocks = new ArrayMap<>();
-
-        /**
-         * Metadata about available clocks, such as name and preview images.
-         */
-        private final List<ClockInfo> mClockInfo = new ArrayList<>();
-
-        /**
-         * Active ClockPlugin.
-         */
-        @Nullable private ClockPlugin mCurrentClock;
-
-        @Override
-        public void onPluginConnected(ClockPlugin plugin, Context pluginContext) {
-            addClockPlugin(plugin);
-            reloadIfNeeded(plugin);
-        }
-
-        @Override
-        public void onPluginDisconnected(ClockPlugin plugin) {
-            removeClockPlugin(plugin);
-            reloadIfNeeded(plugin);
-        }
-
-        /**
-         * Get the current clock.
-         * @return current custom clock or null for default.
-         */
-        @Nullable
-        ClockPlugin getCurrentClock() {
-            return mCurrentClock;
-        }
-
-        /**
-         * Get information about available clock faces.
-         */
-        List<ClockInfo> getInfo() {
-            return mClockInfo;
-        }
-
-        /**
-         * Adds a clock plugin to the collection of available clocks.
-         *
-         * @param plugin The plugin to add.
-         */
-        void addClockPlugin(ClockPlugin plugin) {
-            final String id = plugin.getClass().getName();
-            mClocks.put(plugin.getClass().getName(), plugin);
-            mClockInfo.add(ClockInfo.builder()
-                    .setName(plugin.getName())
-                    .setTitle(plugin::getTitle)
-                    .setId(id)
-                    .setThumbnail(plugin::getThumbnail)
-                    .setPreview(() -> plugin.getPreview(mWidth, mHeight))
-                    .build());
-        }
-
-        private void removeClockPlugin(ClockPlugin plugin) {
-            final String id = plugin.getClass().getName();
-            mClocks.remove(id);
-            for (int i = 0; i < mClockInfo.size(); i++) {
-                if (id.equals(mClockInfo.get(i).getId())) {
-                    mClockInfo.remove(i);
-                    break;
-                }
-            }
-        }
-
-        private void reloadIfNeeded(ClockPlugin plugin) {
-            final boolean wasCurrentClock = plugin == mCurrentClock;
-            reloadCurrentClock();
-            final boolean isCurrentClock = plugin == mCurrentClock;
-            if (wasCurrentClock || isCurrentClock) {
-                ClockManager.this.reload();
-            }
-        }
-
-        /**
-         * Update the current clock.
-         */
-        void reloadCurrentClock() {
-            mCurrentClock = getClockPlugin();
-        }
-
-        private ClockPlugin getClockPlugin() {
-            ClockPlugin plugin = null;
-            if (ClockManager.this.isDocked()) {
-                final String name = mSettingsWrapper.getDockedClockFace(
-                        mUserTracker.getUserId());
-                if (name != null) {
-                    plugin = mClocks.get(name);
-                    if (plugin != null) {
-                        return plugin;
-                    }
-                }
-            }
-            final String name = mSettingsWrapper.getLockScreenCustomClockFace(
-                    mUserTracker.getUserId());
-            if (name != null) {
-                plugin = mClocks.get(name);
-            }
-            return plugin;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
deleted file mode 100644
index b6413cb..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockOptionsProvider.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.FileNotFoundException;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Provider;
-
-/**
- * Exposes custom clock face options and provides realistic preview images.
- *
- * APIs:
- *
- *   /list_options: List the available clock faces, which has the following columns
- *     name: name of the clock face
- *     title: title of the clock face
- *     id: value used to set the clock face
- *     thumbnail: uri of the thumbnail image, should be /thumbnail/{name}
- *     preview: uri of the preview image, should be /preview/{name}
- *
- *   /thumbnail/{id}: Opens a file stream for the thumbnail image for clock face {id}.
- *
- *   /preview/{id}: Opens a file stream for the preview image for clock face {id}.
- */
-public final class ClockOptionsProvider extends ContentProvider {
-
-    private static final String TAG = "ClockOptionsProvider";
-    private static final String KEY_LIST_OPTIONS = "/list_options";
-    private static final String KEY_PREVIEW = "preview";
-    private static final String KEY_THUMBNAIL = "thumbnail";
-    private static final String COLUMN_NAME = "name";
-    private static final String COLUMN_TITLE = "title";
-    private static final String COLUMN_ID = "id";
-    private static final String COLUMN_THUMBNAIL = "thumbnail";
-    private static final String COLUMN_PREVIEW = "preview";
-    private static final String MIME_TYPE_PNG = "image/png";
-    private static final String CONTENT_SCHEME = "content";
-    private static final String AUTHORITY = "com.android.keyguard.clock";
-
-    @Inject
-    public Provider<List<ClockInfo>> mClockInfosProvider;
-
-    @VisibleForTesting
-    ClockOptionsProvider(Provider<List<ClockInfo>> clockInfosProvider) {
-        mClockInfosProvider = clockInfosProvider;
-    }
-
-    @Override
-    public boolean onCreate() {
-        return true;
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        List<String> segments = uri.getPathSegments();
-        if (segments.size() > 0 && (KEY_PREVIEW.equals(segments.get(0))
-                || KEY_THUMBNAIL.equals(segments.get(0)))) {
-            return MIME_TYPE_PNG;
-        }
-        return "vnd.android.cursor.dir/clock_faces";
-    }
-
-    @Override
-    public Cursor query(Uri uri, String[] projection, String selection,
-            String[] selectionArgs, String sortOrder) {
-        if (!KEY_LIST_OPTIONS.equals(uri.getPath())) {
-            return null;
-        }
-        MatrixCursor cursor = new MatrixCursor(new String[] {
-                COLUMN_NAME, COLUMN_TITLE, COLUMN_ID, COLUMN_THUMBNAIL, COLUMN_PREVIEW});
-        List<ClockInfo> clocks = mClockInfosProvider.get();
-        for (int i = 0; i < clocks.size(); i++) {
-            ClockInfo clock = clocks.get(i);
-            cursor.newRow()
-                    .add(COLUMN_NAME, clock.getName())
-                    .add(COLUMN_TITLE, clock.getTitle())
-                    .add(COLUMN_ID, clock.getId())
-                    .add(COLUMN_THUMBNAIL, createThumbnailUri(clock))
-                    .add(COLUMN_PREVIEW, createPreviewUri(clock));
-        }
-        return cursor;
-    }
-
-    @Override
-    public Uri insert(Uri uri, ContentValues initialValues) {
-        return null;
-    }
-
-    @Override
-    public int delete(Uri uri, String selection, String[] selectionArgs) {
-        return 0;
-    }
-
-    @Override
-    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-        return 0;
-    }
-
-    @Override
-    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
-        List<String> segments = uri.getPathSegments();
-        if (segments.size() != 2 || !(KEY_PREVIEW.equals(segments.get(0))
-                || KEY_THUMBNAIL.equals(segments.get(0)))) {
-            throw new FileNotFoundException("Invalid preview url");
-        }
-        String id = segments.get(1);
-        if (TextUtils.isEmpty(id)) {
-            throw new FileNotFoundException("Invalid preview url, missing id");
-        }
-        ClockInfo clock = null;
-        List<ClockInfo> clocks = mClockInfosProvider.get();
-        for (int i = 0; i < clocks.size(); i++) {
-            if (id.equals(clocks.get(i).getId())) {
-                clock = clocks.get(i);
-                break;
-            }
-        }
-        if (clock == null) {
-            throw new FileNotFoundException("Invalid preview url, id not found");
-        }
-        return openPipeHelper(uri, MIME_TYPE_PNG, null, KEY_PREVIEW.equals(segments.get(0))
-                ? clock.getPreview() : clock.getThumbnail(), new MyWriter());
-    }
-
-    private Uri createThumbnailUri(ClockInfo clock) {
-        return new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(KEY_THUMBNAIL)
-                .appendPath(clock.getId())
-                .build();
-    }
-
-    private Uri createPreviewUri(ClockInfo clock) {
-        return new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(KEY_PREVIEW)
-                .appendPath(clock.getId())
-                .build();
-    }
-
-    private static class MyWriter implements ContentProvider.PipeDataWriter<Bitmap> {
-        @Override
-        public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
-                Bundle opts, Bitmap bitmap) {
-            try (AutoCloseOutputStream os = new AutoCloseOutputStream(output)) {
-                bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
-            } catch (Exception e) {
-                Log.w(TAG, "fail to write to pipe", e);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockPalette.kt b/packages/SystemUI/src/com/android/keyguard/clock/ClockPalette.kt
deleted file mode 100644
index 5c5493a..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockPalette.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.keyguard.clock
-
-import android.graphics.Color
-import android.util.MathUtils
-
-private const val PRIMARY_INDEX = 5
-private const val SECONDARY_DARK_INDEX = 8
-private const val SECONDARY_LIGHT_INDEX = 2
-
-/**
- * A helper class to extract colors from a clock face.
- */
-class ClockPalette {
-
-    private var darkAmount: Float = 0f
-    private var accentPrimary: Int = Color.WHITE
-    private var accentSecondaryLight: Int = Color.WHITE
-    private var accentSecondaryDark: Int = Color.BLACK
-    private val lightHSV: FloatArray = FloatArray(3)
-    private val darkHSV: FloatArray = FloatArray(3)
-    private val hsv: FloatArray = FloatArray(3)
-
-    /** Returns a color from the palette as an RGB packed int. */
-    fun getPrimaryColor(): Int {
-        return accentPrimary
-    }
-
-    /** Returns either a light or dark color from the palette as an RGB packed int. */
-    fun getSecondaryColor(): Int {
-        Color.colorToHSV(accentSecondaryLight, lightHSV)
-        Color.colorToHSV(accentSecondaryDark, darkHSV)
-        for (i in 0..2) {
-            hsv[i] = MathUtils.lerp(darkHSV[i], lightHSV[i], darkAmount)
-        }
-        return Color.HSVToColor(hsv)
-    }
-
-    /** See {@link ClockPlugin#setColorPalette}. */
-    fun setColorPalette(supportsDarkText: Boolean, colorPalette: IntArray?) {
-        if (colorPalette == null || colorPalette.isEmpty()) {
-            accentPrimary = Color.WHITE
-            accentSecondaryLight = Color.WHITE
-            accentSecondaryDark = if (supportsDarkText) Color.BLACK else Color.WHITE
-            return
-        }
-        val length = colorPalette.size
-        accentPrimary = colorPalette[Math.max(0, length - PRIMARY_INDEX)]
-        accentSecondaryLight = colorPalette[Math.max(0, length - SECONDARY_LIGHT_INDEX)]
-        accentSecondaryDark = colorPalette[Math.max(0,
-                length - if (supportsDarkText) SECONDARY_DARK_INDEX else SECONDARY_LIGHT_INDEX)]
-    }
-
-    /** See {@link ClockPlugin#setDarkAmount}. */
-    fun setDarkAmount(darkAmount: Float) {
-        this.darkAmount = darkAmount
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java b/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java
deleted file mode 100644
index 3c3f475..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import android.view.View;
-
-/**
- * Controls transition to dark state by cross fading between views.
- */
-final class CrossFadeDarkController {
-
-    private final View mFadeInView;
-    private final View mFadeOutView;
-
-    /**
-     * Creates a new controller that fades between views.
-     *
-     * @param fadeInView View to fade in when transitioning to AOD.
-     * @param fadeOutView View to fade out when transitioning to AOD.
-     */
-    CrossFadeDarkController(View fadeInView, View fadeOutView) {
-        mFadeInView = fadeInView;
-        mFadeOutView = fadeOutView;
-    }
-
-    /**
-     * Sets the amount the system has transitioned to the dark state.
-     *
-     * @param darkAmount Amount of transition to dark state: 1f for AOD and 0f for lock screen.
-     */
-    void setDarkAmount(float darkAmount) {
-        mFadeInView.setAlpha(Math.max(0f, 2f * darkAmount - 1f));
-        if (darkAmount == 0f) {
-            mFadeInView.setVisibility(View.GONE);
-        } else {
-            if (mFadeInView.getVisibility() == View.GONE) {
-                mFadeInView.setVisibility(View.VISIBLE);
-            }
-        }
-        mFadeOutView.setAlpha(Math.max(0f, 1f - 2f * darkAmount));
-        if (darkAmount == 1f) {
-            mFadeOutView.setVisibility(View.GONE);
-        } else {
-            if (mFadeOutView.getVisibility() == View.GONE) {
-                mFadeOutView.setVisibility(View.VISIBLE);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java
deleted file mode 100644
index c81935a..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import android.app.WallpaperManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
-import android.graphics.Paint.Style;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.TextView;
-
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.systemui.R;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.plugins.ClockPlugin;
-
-import java.util.TimeZone;
-
-/**
- * Plugin for the default clock face used only to provide a preview.
- */
-public class DefaultClockController implements ClockPlugin {
-
-    /**
-     * Resources used to get title and thumbnail.
-     */
-    private final Resources mResources;
-
-    /**
-     * LayoutInflater used to inflate custom clock views.
-     */
-    private final LayoutInflater mLayoutInflater;
-
-    /**
-     * Extracts accent color from wallpaper.
-     */
-    private final SysuiColorExtractor mColorExtractor;
-
-    /**
-     * Renders preview from clock view.
-     */
-    private final ViewPreviewer mRenderer = new ViewPreviewer();
-
-    /**
-     * Root view of preview.
-     */
-    private View mView;
-
-    /**
-     * Text clock in preview view hierarchy.
-     */
-    private TextView mTextTime;
-
-    /**
-     * Date showing below time in preview view hierarchy.
-     */
-    private TextView mTextDate;
-
-    /**
-     * Create a DefaultClockController instance.
-     *
-     * @param res Resources contains title and thumbnail.
-     * @param inflater Inflater used to inflate custom clock views.
-     * @param colorExtractor Extracts accent color from wallpaper.
-     */
-    public DefaultClockController(Resources res, LayoutInflater inflater,
-            SysuiColorExtractor colorExtractor) {
-        mResources = res;
-        mLayoutInflater = inflater;
-        mColorExtractor = colorExtractor;
-    }
-
-    private void createViews() {
-        mView = mLayoutInflater.inflate(R.layout.default_clock_preview, null);
-        mTextTime = mView.findViewById(R.id.time);
-        mTextDate = mView.findViewById(R.id.date);
-    }
-
-    @Override
-    public void onDestroyView() {
-        mView = null;
-        mTextTime = null;
-        mTextDate = null;
-    }
-
-    @Override
-    public String getName() {
-        return "default";
-    }
-
-    @Override
-    public String getTitle() {
-        return mResources.getString(R.string.clock_title_default);
-    }
-
-    @Override
-    public Bitmap getThumbnail() {
-        return BitmapFactory.decodeResource(mResources, R.drawable.default_thumbnail);
-    }
-
-    @Override
-    public Bitmap getPreview(int width, int height) {
-
-        // Use the big clock view for the preview
-        View view = getBigClockView();
-
-        // Initialize state of plugin before generating preview.
-        setDarkAmount(1f);
-        setTextColor(Color.WHITE);
-        ColorExtractor.GradientColors colors = mColorExtractor.getColors(
-                WallpaperManager.FLAG_LOCK);
-        setColorPalette(colors.supportsDarkText(), colors.getColorPalette());
-        onTimeTick();
-
-        return mRenderer.createPreview(view, width, height);
-    }
-
-    @Override
-    public View getView() {
-        return null;
-    }
-
-    @Override
-    public View getBigClockView() {
-        if (mView == null) {
-            createViews();
-        }
-        return mView;
-    }
-
-    @Override
-    public int getPreferredY(int totalHeight) {
-        return totalHeight / 2;
-    }
-
-    @Override
-    public void setStyle(Style style) {}
-
-    @Override
-    public void setTextColor(int color) {
-        mTextTime.setTextColor(color);
-        mTextDate.setTextColor(color);
-    }
-
-    @Override
-    public void setColorPalette(boolean supportsDarkText, int[] colorPalette) {}
-
-    @Override
-    public void onTimeTick() {
-    }
-
-    @Override
-    public void setDarkAmount(float darkAmount) {}
-
-    @Override
-    public void onTimeZoneChanged(TimeZone timeZone) {}
-
-    @Override
-    public boolean shouldShowStatusArea() {
-        return true;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java b/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
deleted file mode 100644
index 34c041b..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import android.content.Context;
-import android.text.format.DateFormat;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-import com.android.systemui.R;
-
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.TimeZone;
-
-/**
- * Clock composed of two images that rotate with the time.
- *
- * The images are the clock hands. ImageClock expects two child ImageViews
- * with ids hour_hand and minute_hand.
- */
-public class ImageClock extends FrameLayout {
-
-    private ImageView mHourHand;
-    private ImageView mMinuteHand;
-    private final Calendar mTime = Calendar.getInstance(TimeZone.getDefault());
-    private String mDescFormat;
-    private TimeZone mTimeZone;
-
-    public ImageClock(Context context) {
-        this(context, null);
-    }
-
-    public ImageClock(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public ImageClock(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        mDescFormat = ((SimpleDateFormat) DateFormat.getTimeFormat(context)).toLocalizedPattern();
-    }
-
-    /**
-     * Call when the time changes to update the rotation of the clock hands.
-     */
-    public void onTimeChanged() {
-        mTime.setTimeInMillis(System.currentTimeMillis());
-        final float hourAngle = mTime.get(Calendar.HOUR) * 30f + mTime.get(Calendar.MINUTE) * 0.5f;
-        mHourHand.setRotation(hourAngle);
-        final float minuteAngle = mTime.get(Calendar.MINUTE) * 6f;
-        mMinuteHand.setRotation(minuteAngle);
-        setContentDescription(DateFormat.format(mDescFormat, mTime));
-        invalidate();
-    }
-
-    /**
-     * Call when the time zone has changed to update clock hands.
-     *
-     * @param timeZone The updated time zone that will be used.
-     */
-    public void onTimeZoneChanged(TimeZone timeZone) {
-        mTimeZone = timeZone;
-        mTime.setTimeZone(timeZone);
-    }
-
-    /**
-     * Sets the colors to use on the clock face.
-     * @param dark Darker color obtained from color palette.
-     * @param light Lighter color obtained from color palette.
-     */
-    public void setClockColors(int dark, int light) {
-        mHourHand.setColorFilter(dark);
-        mMinuteHand.setColorFilter(light);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mHourHand = findViewById(R.id.hour_hand);
-        mMinuteHand = findViewById(R.id.minute_hand);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mTime.setTimeZone(mTimeZone != null ? mTimeZone : TimeZone.getDefault());
-        onTimeChanged();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java b/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java
deleted file mode 100644
index 096e943..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import android.annotation.Nullable;
-import android.content.ContentResolver;
-import android.provider.Settings;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Wrapper around Settings used for testing.
- */
-public class SettingsWrapper {
-
-    private static final String TAG = "ClockFaceSettings";
-    private static final String CUSTOM_CLOCK_FACE = Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE;
-    private static final String DOCKED_CLOCK_FACE = Settings.Secure.DOCKED_CLOCK_FACE;
-    private static final String CLOCK_FIELD = "clock";
-
-    private final ContentResolver mContentResolver;
-    private final Migration mMigration;
-
-    SettingsWrapper(ContentResolver contentResolver) {
-        this(contentResolver, new Migrator(contentResolver));
-    }
-
-    @VisibleForTesting
-    SettingsWrapper(ContentResolver contentResolver, Migration migration) {
-        mContentResolver = contentResolver;
-        mMigration = migration;
-    }
-
-    /**
-     * Gets the value stored in settings for the custom clock face.
-     *
-     * @param userId ID of the user.
-     */
-    String getLockScreenCustomClockFace(int userId) {
-        return decode(
-                Settings.Secure.getStringForUser(mContentResolver, CUSTOM_CLOCK_FACE, userId),
-                userId);
-    }
-
-    /**
-     * Gets the value stored in settings for the clock face to use when docked.
-     *
-     * @param userId ID of the user.
-     */
-    String getDockedClockFace(int userId) {
-        return Settings.Secure.getStringForUser(mContentResolver, DOCKED_CLOCK_FACE, userId);
-    }
-
-    /**
-     * Decodes the string stored in settings, which should be formatted as JSON.
-     * @param value String stored in settings. If value is not JSON, then the settings is
-     *              overwritten with JSON containing the prior value.
-     * @return ID of the clock face to show on AOD and lock screen. If value is not JSON, the value
-     *         is returned.
-     */
-    @VisibleForTesting
-    String decode(@Nullable String value, int userId) {
-        if (value == null) {
-            return value;
-        }
-        JSONObject json;
-        try {
-            json = new JSONObject(value);
-        } catch (JSONException ex) {
-            Log.e(TAG, "Settings value is not valid JSON", ex);
-            // The settings value isn't JSON since it didn't parse so migrate the value to JSON.
-            // TODO(b/135674383): Remove this migration path in the following release.
-            mMigration.migrate(value, userId);
-            return value;
-        }
-        try {
-            return json.getString(CLOCK_FIELD);
-        } catch (JSONException ex) {
-            Log.e(TAG, "JSON object does not contain clock field.", ex);
-            return null;
-        }
-    }
-
-    interface Migration {
-        void migrate(String value, int userId);
-    }
-
-    /**
-     * Implementation of {@link Migration} that writes valid JSON back to Settings.
-     */
-    private static final class Migrator implements Migration {
-
-        private final ContentResolver mContentResolver;
-
-        Migrator(ContentResolver contentResolver) {
-            mContentResolver = contentResolver;
-        }
-
-        /**
-         * Migrate settings values that don't parse by converting to JSON format.
-         *
-         * Values in settings must be JSON to be backed up and restored. To help users maintain
-         * their current settings, convert existing values into the JSON format.
-         *
-         * TODO(b/135674383): Remove this migration code in the following release.
-         */
-        @Override
-        public void migrate(String value, int userId) {
-            try {
-                JSONObject json = new JSONObject();
-                json.put(CLOCK_FIELD, value);
-                Settings.Secure.putStringForUser(mContentResolver, CUSTOM_CLOCK_FACE,
-                        json.toString(),
-                        userId);
-            } catch (JSONException ex) {
-                Log.e(TAG, "Failed migrating settings value to JSON format", ex);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java b/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java
deleted file mode 100644
index 4e51b98..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard.clock;
-
-import android.content.Context;
-import android.util.MathUtils;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.SystemBarUtils;
-import com.android.systemui.R;
-
-/**
- * Computes preferred position of clock by considering height of status bar and lock icon.
- */
-class SmallClockPosition {
-
-    /**
-     * Dimensions used to determine preferred clock position.
-     */
-    private final int mStatusBarHeight;
-    private final int mKeyguardLockPadding;
-    private final int mKeyguardLockHeight;
-    private final int mBurnInOffsetY;
-
-    /**
-     * Amount of transition between AOD and lock screen.
-     */
-    private float mDarkAmount;
-
-    SmallClockPosition(Context context) {
-        this(SystemBarUtils.getStatusBarHeight(context),
-                context.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_padding),
-                context.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_height),
-                context.getResources().getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y)
-        );
-    }
-
-    @VisibleForTesting
-    SmallClockPosition(int statusBarHeight, int lockPadding, int lockHeight, int burnInY) {
-        mStatusBarHeight = statusBarHeight;
-        mKeyguardLockPadding = lockPadding;
-        mKeyguardLockHeight = lockHeight;
-        mBurnInOffsetY = burnInY;
-    }
-
-    /**
-     * See {@link ClockPlugin#setDarkAmount}.
-     */
-    void setDarkAmount(float darkAmount) {
-        mDarkAmount = darkAmount;
-    }
-
-    /**
-     * Gets the preferred Y position accounting for status bar and lock icon heights.
-     */
-    int getPreferredY() {
-        // On AOD, clock needs to appear below the status bar with enough room for pixel shifting
-        int aodY = mStatusBarHeight + mKeyguardLockHeight + 2 * mKeyguardLockPadding
-                + mBurnInOffsetY;
-        // On lock screen, clock needs to appear below the lock icon
-        int lockY =  mStatusBarHeight + mKeyguardLockHeight + 2 * mKeyguardLockPadding;
-        return (int) MathUtils.lerp(lockY, aodY, mDarkAmount);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ViewPreviewer.java b/packages/SystemUI/src/com/android/keyguard/clock/ViewPreviewer.java
deleted file mode 100644
index abd0dd2..0000000
--- a/packages/SystemUI/src/com/android/keyguard/clock/ViewPreviewer.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import android.annotation.Nullable;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.concurrent.Callable;
-import java.util.concurrent.FutureTask;
-
-/**
- * Creates a preview image ({@link Bitmap}) of a {@link View} for a custom clock face.
- */
-final class ViewPreviewer {
-
-    private static final String TAG = "ViewPreviewer";
-
-    /**
-     * Handler used to run {@link View#draw(Canvas)} on the main thread.
-     */
-    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
-
-    /**
-     * Generate a realistic preview of a clock face.
-     *
-     * @param view view is used to generate preview image.
-     * @param width width of the preview image, should be the same as device width in pixels.
-     * @param height height of the preview image, should be the same as device height in pixels.
-     * @return bitmap of view.
-     */
-    @Nullable
-    Bitmap createPreview(View view, int width, int height) {
-        if (view == null) {
-            return null;
-        }
-        FutureTask<Bitmap> task = new FutureTask<>(new Callable<Bitmap>() {
-            @Override
-            public Bitmap call() {
-                Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-
-                // Draw clock view hierarchy to canvas.
-                Canvas canvas = new Canvas(bitmap);
-                canvas.drawColor(Color.BLACK);
-                dispatchVisibilityAggregated(view, true);
-                view.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
-                        View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
-                view.layout(0, 0, width, height);
-                view.draw(canvas);
-
-                return bitmap;
-            }
-        });
-
-        if (Looper.myLooper() == Looper.getMainLooper()) {
-            task.run();
-        } else {
-            mMainHandler.post(task);
-        }
-
-        try {
-            return task.get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error completing task", e);
-            return null;
-        }
-    }
-
-    private void dispatchVisibilityAggregated(View view, boolean isVisible) {
-        // Similar to View.dispatchVisibilityAggregated implementation.
-        final boolean thisVisible = view.getVisibility() == View.VISIBLE;
-        if (thisVisible || !isVisible) {
-            view.onVisibilityAggregated(isVisible);
-        }
-
-        if (view instanceof ViewGroup) {
-            isVisible = thisVisible && isVisible;
-            ViewGroup vg = (ViewGroup) view;
-            int count = vg.getChildCount();
-
-            for (int i = 0; i < count; i++) {
-                dispatchVisibilityAggregated(vg.getChildAt(i), isVisible);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index d2b10d6..83fc278 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -71,7 +71,8 @@
                 context.getString(R.string.lockscreen_clock_id_fallback),
                 logBuffer,
                 /* keepAllLoaded = */ false,
-                /* subTag = */ "System");
+                /* subTag = */ "System",
+                /* isTransitClockEnabled = */ featureFlags.isEnabled(Flags.TRANSIT_CLOCK));
         registry.registerListeners();
         return registry;
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java
index 154b0ed..b57c2b5 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java
@@ -20,7 +20,7 @@
 
 import com.android.keyguard.KeyguardSecurityContainerController;
 import com.android.systemui.dagger.qualifiers.RootView;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 
 import dagger.BindsInstance;
 import dagger.Subcomponent;
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
index 38f252a..893239b 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
@@ -28,7 +28,7 @@
 import com.android.systemui.R;
 import com.android.systemui.biometrics.SideFpsController;
 import com.android.systemui.dagger.qualifiers.RootView;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 
 import java.util.Optional;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
index a7d4455..8762769 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
@@ -20,6 +20,7 @@
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
 
 import dagger.Module;
@@ -44,6 +45,13 @@
     /** */
     @Provides
     @KeyguardStatusBarViewScope
+    static StatusBarLocation getStatusBarLocation() {
+        return StatusBarLocation.KEYGUARD;
+    }
+
+    /** */
+    @Provides
+    @KeyguardStatusBarViewScope
     static StatusBarUserSwitcherContainer getUserSwitcherContainer(KeyguardStatusBarView view) {
         return view.findViewById(R.id.user_switcher_container);
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
index e7295ef..54738c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.BiometricLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
index c00b2c6..cfad614 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
@@ -19,9 +19,9 @@
 import android.hardware.biometrics.BiometricSourceType
 import com.android.systemui.dagger.SysUISingleton
 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.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.log.dagger.BiometricLog
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
index 1978715..d02b72f 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
@@ -19,7 +19,7 @@
 import androidx.annotation.IntDef
 import com.android.keyguard.CarrierTextManager.CarrierTextCallbackInfo
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.CarrierTextManagerLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 8b925b1..bddf3b0 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.biometrics.AuthRippleController
 import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.KeyguardLog
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.google.errorprone.annotations.CompileTimeConstant
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index b596331..c26e546 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -18,6 +18,7 @@
 
 import android.content.Intent
 import android.hardware.biometrics.BiometricConstants.LockoutMode
+import android.hardware.biometrics.BiometricSourceType
 import android.os.PowerManager
 import android.os.PowerManager.WakeReason
 import android.telephony.ServiceState
@@ -32,12 +33,12 @@
 import com.android.keyguard.TrustGrantFlags
 import com.android.settingslib.fuelgauge.BatteryStatus
 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.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.WARNING
 import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
@@ -370,16 +371,14 @@
 
     fun logServiceProvidersUpdated(intent: Intent) {
         logBuffer.log(
-                TAG,
-                VERBOSE,
-                {
-                    int1 = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID)
-                    str1 = intent.getStringExtra(TelephonyManager.EXTRA_SPN)
-                    str2 = intent.getStringExtra(TelephonyManager.EXTRA_PLMN)
-                },
-                {
-                    "action SERVICE_PROVIDERS_UPDATED subId=$int1 spn=$str1 plmn=$str2"
-                }
+            TAG,
+            VERBOSE,
+            {
+                int1 = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID)
+                str1 = intent.getStringExtra(TelephonyManager.EXTRA_SPN)
+                str2 = intent.getStringExtra(TelephonyManager.EXTRA_PLMN)
+            },
+            { "action SERVICE_PROVIDERS_UPDATED subId=$int1 spn=$str1 plmn=$str2" }
         )
     }
 
@@ -601,6 +600,15 @@
         )
     }
 
+    fun allowFingerprintOnCurrentOccludingActivityChanged(allow: Boolean) {
+        logBuffer.log(
+            TAG,
+            VERBOSE,
+            { bool1 = allow },
+            { "allowFingerprintOnCurrentOccludingActivityChanged: $bool1" }
+        )
+    }
+
     fun logAssistantVisible(assistantVisible: Boolean) {
         logBuffer.log(
             TAG,
@@ -710,4 +718,37 @@
     fun scheduleWatchdog(@CompileTimeConstant watchdogType: String) {
         logBuffer.log(TAG, DEBUG, "Scheduling biometric watchdog for $watchdogType")
     }
+
+    fun notifyAboutEnrollmentsChanged(biometricSourceType: BiometricSourceType) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { str1 = "$biometricSourceType" },
+            { "notifying about enrollments changed: $str1" }
+        )
+    }
+
+    fun logUserSwitching(userId: Int, context: String) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                int1 = userId
+                str1 = context
+            },
+            { "userCurrentlySwitching: $str1, userId: $int1" }
+        )
+    }
+
+    fun logUserSwitchComplete(userId: Int, context: String) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                int1 = userId
+                str1 = context
+            },
+            { "userSwitchComplete: $str1, userId: $int1" }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
index cb764a8..57b5db1 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
@@ -21,8 +21,8 @@
 import com.android.systemui.keyguard.shared.model.TrustManagedModel
 import com.android.systemui.keyguard.shared.model.TrustModel
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index becf5b3..c07a4d2 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -53,6 +53,11 @@
     default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
     }
 
+    /** Called to determine if the dumpable should be registered as critical or normal priority */
+    default boolean isDumpCritical() {
+        return true;
+    }
+
     /** Called immediately after the system broadcasts
      * {@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED} or during SysUI startup if the
      * property {@code sys.boot_completed} is already set to 1. The latter typically occurs when
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index be5bb07..27b9056 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -33,7 +33,6 @@
 import com.android.internal.util.Preconditions;
 import com.android.keyguard.KeyguardSecurityModel;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.clock.ClockManager;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -124,7 +123,6 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -133,14 +131,14 @@
 import com.android.systemui.util.leak.LeakReporter;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 
-import dagger.Lazy;
-
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
 import javax.inject.Named;
 
+import dagger.Lazy;
+
 /**
  * Class to handle ugly dependencies throughout sysui until we determine the
  * long-term dependency injection solution.
@@ -282,7 +280,6 @@
     @Inject Lazy<AccessibilityManagerWrapper> mAccessibilityManagerWrapper;
     @Inject Lazy<SysuiColorExtractor> mSysuiColorExtractor;
     @Inject Lazy<TunablePaddingService> mTunablePaddingService;
-    @Inject Lazy<ForegroundServiceController> mForegroundServiceController;
     @Inject Lazy<UiOffloadThread> mUiOffloadThread;
     @Inject Lazy<PowerUI.WarningsUI> mWarningsUI;
     @Inject Lazy<LightBarController> mLightBarController;
@@ -323,7 +320,6 @@
     @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
     @Inject @Main Lazy<Executor> mMainExecutor;
     @Inject @Background Lazy<Executor> mBackgroundExecutor;
-    @Inject Lazy<ClockManager> mClockManager;
     @Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
     @Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper;
     @Inject Lazy<PackageManagerWrapper> mPackageManagerWrapper;
@@ -337,7 +333,6 @@
     @Inject Lazy<IWallpaperManager> mWallpaperManager;
     @Inject Lazy<CommandQueue> mCommandQueue;
     @Inject Lazy<RecordingController> mRecordingController;
-    @Inject Lazy<ProtoTracer> mProtoTracer;
     @Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory;
     @Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy;
     @Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager;
@@ -462,8 +457,6 @@
 
         mProviders.put(TunablePaddingService.class, mTunablePaddingService::get);
 
-        mProviders.put(ForegroundServiceController.class, mForegroundServiceController::get);
-
         mProviders.put(UiOffloadThread.class, mUiOffloadThread::get);
 
         mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get);
@@ -517,7 +510,6 @@
         mProviders.put(SmartReplyController.class, mSmartReplyController::get);
         mProviders.put(RemoteInputQuickSettingsDisabler.class,
                 mRemoteInputQuickSettingsDisabler::get);
-        mProviders.put(ClockManager.class, mClockManager::get);
         mProviders.put(PrivacyItemController.class, mPrivacyItemController::get);
         mProviders.put(ActivityManagerWrapper.class, mActivityManagerWrapper::get);
         mProviders.put(DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper::get);
@@ -531,7 +523,6 @@
         mProviders.put(DozeParameters.class, mDozeParameters::get);
         mProviders.put(IWallpaperManager.class, mWallpaperManager::get);
         mProviders.put(CommandQueue.class, mCommandQueue::get);
-        mProviders.put(ProtoTracer.class, mProtoTracer::get);
         mProviders.put(DeviceConfigProxy.class, mDeviceConfigProxy::get);
         mProviders.put(TelephonyListenerManager.class, mTelephonyListenerManager::get);
 
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index f00615b..7c377d2 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -19,9 +19,6 @@
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_EXPAND;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.util.FloatProperty;
 import android.util.Log;
@@ -34,6 +31,11 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 
+import androidx.annotation.NonNull;
+import androidx.core.animation.Animator;
+import androidx.core.animation.AnimatorListenerAdapter;
+import androidx.core.animation.ObjectAnimator;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -616,7 +618,7 @@
                 public boolean mCancelled;
 
                 @Override
-                public void onAnimationEnd(Animator animation) {
+                public void onAnimationEnd(@NonNull Animator animation) {
                     if (!mCancelled) {
                         mCallback.setUserExpandedChild(scaledView, expand);
                         if (!mExpanding) {
@@ -633,7 +635,7 @@
                 }
 
                 @Override
-                public void onAnimationCancel(Animator animation) {
+                public void onAnimationCancel(@NonNull Animator animation) {
                     mCancelled = true;
                 }
             });
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
deleted file mode 100644
index 15e8c4e..0000000
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
+++ /dev/null
@@ -1,183 +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.systemui;
-
-import android.annotation.Nullable;
-import android.app.AppOpsManager;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
-import android.util.SparseArray;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.Assert;
-
-import javax.inject.Inject;
-
-/**
- * Tracks state of foreground services and notifications related to foreground services per user.
- */
-@SysUISingleton
-public class ForegroundServiceController {
-    public static final int[] APP_OPS = new int[] {AppOpsManager.OP_SYSTEM_ALERT_WINDOW};
-
-    private final SparseArray<ForegroundServicesUserState> mUserServices = new SparseArray<>();
-    private final Object mMutex = new Object();
-    private final Handler mMainHandler;
-
-    @Inject
-    public ForegroundServiceController(
-            AppOpsController appOpsController,
-            @Main Handler mainHandler) {
-        mMainHandler = mainHandler;
-        appOpsController.addCallback(APP_OPS, (code, uid, packageName, active) -> {
-            mMainHandler.post(() -> {
-                onAppOpChanged(code, uid, packageName, active);
-            });
-        });
-    }
-
-    /**
-     * @return true if this user has services missing notifications and therefore needs a
-     * disclosure notification for running a foreground service.
-     */
-    public boolean isDisclosureNeededForUser(int userId) {
-        synchronized (mMutex) {
-            final ForegroundServicesUserState services = mUserServices.get(userId);
-            if (services == null) return false;
-            return services.isDisclosureNeeded();
-        }
-    }
-
-    /**
-     * @return true if this user/pkg has a missing or custom layout notification and therefore needs
-     * a disclosure notification showing the user which appsOps the app is using.
-     */
-    public boolean isSystemAlertWarningNeeded(int userId, String pkg) {
-        synchronized (mMutex) {
-            final ForegroundServicesUserState services = mUserServices.get(userId);
-            if (services == null) return false;
-            return services.getStandardLayoutKeys(pkg) == null;
-        }
-    }
-
-    /**
-     * Gets active app ops for this user and package
-     */
-    @Nullable
-    public ArraySet<Integer> getAppOps(int userId, String pkg) {
-        synchronized (mMutex) {
-            final ForegroundServicesUserState services = mUserServices.get(userId);
-            if (services == null) {
-                return null;
-            }
-            return services.getFeatures(pkg);
-        }
-    }
-
-    /**
-     * Records active app ops and updates the app op for the pending or visible notifications
-     * with the given parameters.
-     * App Ops are stored in FSC in addition to NotificationEntry in case they change before we
-     * have a notification to tag.
-     * @param appOpCode code for appOp to add/remove
-     * @param uid of user the notification is sent to
-     * @param packageName package that created the notification
-     * @param active whether the appOpCode is active or not
-     */
-    void onAppOpChanged(int appOpCode, int uid, String packageName, boolean active) {
-        Assert.isMainThread();
-
-        int userId = UserHandle.getUserId(uid);
-        // Record active app ops
-        synchronized (mMutex) {
-            ForegroundServicesUserState userServices = mUserServices.get(userId);
-            if (userServices == null) {
-                userServices = new ForegroundServicesUserState();
-                mUserServices.put(userId, userServices);
-            }
-            if (active) {
-                userServices.addOp(packageName, appOpCode);
-            } else {
-                userServices.removeOp(packageName, appOpCode);
-            }
-        }
-    }
-
-    /**
-     * Looks up the {@link ForegroundServicesUserState} for the given {@code userId}, then performs
-     * the given {@link UserStateUpdateCallback} on it.  If no state exists for the user ID, creates
-     * a new one if {@code createIfNotFound} is true, then performs the update on the new state.
-     * If {@code createIfNotFound} is false, no update is performed.
-     *
-     * @return false if no user state was found and none was created; true otherwise.
-     */
-    boolean updateUserState(int userId,
-            UserStateUpdateCallback updateCallback,
-            boolean createIfNotFound) {
-        synchronized (mMutex) {
-            ForegroundServicesUserState userState = mUserServices.get(userId);
-            if (userState == null) {
-                if (createIfNotFound) {
-                    userState = new ForegroundServicesUserState();
-                    mUserServices.put(userId, userState);
-                } else {
-                    return false;
-                }
-            }
-            return updateCallback.updateUserState(userState);
-        }
-    }
-
-    /**
-     * @return true if {@code sbn} is the system-provided disclosure notification containing the
-     * list of running foreground services.
-     */
-    public boolean isDisclosureNotification(StatusBarNotification sbn) {
-        return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
-                && sbn.getTag() == null
-                && sbn.getPackageName().equals("android");
-    }
-
-    /**
-     * @return true if sbn is one of the window manager "drawing over other apps" notifications
-     */
-    public boolean isSystemAlertNotification(StatusBarNotification sbn) {
-        return sbn.getPackageName().equals("android")
-                && sbn.getTag() != null
-                && sbn.getTag().contains("AlertWindowNotification");
-    }
-
-    /**
-     * Callback provided to {@link #updateUserState(int, UserStateUpdateCallback, boolean)}
-     * to perform the update.
-     */
-    interface UserStateUpdateCallback {
-        /**
-         * Perform update operations on the provided {@code userState}.
-         *
-         * @return true if the update succeeded.
-         */
-        boolean updateUserState(ForegroundServicesUserState userState);
-
-        /** Called if the state was not found and was not created. */
-        default void userStateNotFound(int userId) {
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
deleted file mode 100644
index a1a3b72..0000000
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ /dev/null
@@ -1,150 +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;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.os.Bundle;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-
-import javax.inject.Inject;
-
-/** Updates foreground service notification state in response to notification data events. */
-@SysUISingleton
-public class ForegroundServiceNotificationListener {
-
-    private static final String TAG = "FgServiceController";
-    private static final boolean DBG = false;
-
-    private final Context mContext;
-    private final ForegroundServiceController mForegroundServiceController;
-    private final NotifPipeline mNotifPipeline;
-
-    @Inject
-    public ForegroundServiceNotificationListener(Context context,
-            ForegroundServiceController foregroundServiceController,
-            NotifPipeline notifPipeline) {
-        mContext = context;
-        mForegroundServiceController = foregroundServiceController;
-        mNotifPipeline = notifPipeline;
-    }
-
-    /** Initializes this listener by connecting it to the notification pipeline. */
-    public void init() {
-        mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
-            @Override
-            public void onEntryAdded(NotificationEntry entry) {
-                addNotification(entry, entry.getImportance());
-            }
-
-            @Override
-            public void onEntryUpdated(NotificationEntry entry) {
-                updateNotification(entry, entry.getImportance());
-            }
-
-            @Override
-            public void onEntryRemoved(NotificationEntry entry, int reason) {
-                removeNotification(entry.getSbn());
-            }
-        });
-    }
-
-    /**
-     * @param entry notification that was just posted
-     */
-    private void addNotification(NotificationEntry entry, int importance) {
-        updateNotification(entry, importance);
-    }
-
-    /**
-     * @param sbn notification that was just removed
-     */
-    private void removeNotification(StatusBarNotification sbn) {
-        mForegroundServiceController.updateUserState(
-                sbn.getUserId(),
-                new ForegroundServiceController.UserStateUpdateCallback() {
-                    @Override
-                    public boolean updateUserState(ForegroundServicesUserState userState) {
-                        if (mForegroundServiceController.isDisclosureNotification(sbn)) {
-                            // if you remove the dungeon entirely, we take that to mean there are
-                            // no running services
-                            userState.setRunningServices(null, 0);
-                            return true;
-                        } else {
-                            // this is safe to call on any notification, not just
-                            // FLAG_FOREGROUND_SERVICE
-                            return userState.removeNotification(sbn.getPackageName(), sbn.getKey());
-                        }
-                    }
-
-                    @Override
-                    public void userStateNotFound(int userId) {
-                        if (DBG) {
-                            Log.w(TAG, String.format(
-                                    "user %d with no known notifications got removeNotification "
-                                            + "for %s",
-                                    sbn.getUserId(), sbn));
-                        }
-                    }
-                },
-                false /* don't create */);
-    }
-
-    /**
-     * @param entry notification that was just changed in some way
-     */
-    private void updateNotification(NotificationEntry entry, int newImportance) {
-        final StatusBarNotification sbn = entry.getSbn();
-        mForegroundServiceController.updateUserState(
-                sbn.getUserId(),
-                userState -> {
-                    if (mForegroundServiceController.isDisclosureNotification(sbn)) {
-                        final Bundle extras = sbn.getNotification().extras;
-                        if (extras != null) {
-                            final String[] svcs = extras.getStringArray(
-                                    Notification.EXTRA_FOREGROUND_APPS);
-                            userState.setRunningServices(svcs, sbn.getNotification().when);
-                        }
-                    } else {
-                        userState.removeNotification(sbn.getPackageName(), sbn.getKey());
-                        if (0 != (sbn.getNotification().flags
-                                & Notification.FLAG_FOREGROUND_SERVICE)) {
-                            if (newImportance > NotificationManager.IMPORTANCE_MIN) {
-                                userState.addImportantNotification(sbn.getPackageName(),
-                                        sbn.getKey());
-                            }
-                        }
-                        final Notification.Builder builder =
-                                Notification.Builder.recoverBuilder(
-                                        mContext, sbn.getNotification());
-                        if (builder.usesStandardHeader()) {
-                            userState.addStandardLayoutNotification(
-                                    sbn.getPackageName(), sbn.getKey());
-                        }
-                    }
-                    return true;
-                },
-                true /* create if not found */);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/PhoneSystemUIAppComponentFactory.kt b/packages/SystemUI/src/com/android/systemui/PhoneSystemUIAppComponentFactory.kt
new file mode 100644
index 0000000..f06cb41
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/PhoneSystemUIAppComponentFactory.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.content.Context
+
+class PhoneSystemUIAppComponentFactory : SystemUIAppComponentFactoryBase() {
+    override fun createSystemUIInitializer(context: Context) = SystemUIInitializerImpl(context)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
index 99dd6b6..c465585 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt
@@ -49,13 +49,21 @@
  * When the HWC of the device supports Composition.DISPLAY_DECORATION, we use this layer to draw
  * screen decorations.
  */
-class ScreenDecorHwcLayer(context: Context, displayDecorationSupport: DisplayDecorationSupport) :
-        DisplayCutoutBaseView(context) {
+class ScreenDecorHwcLayer(
+    context: Context,
+    displayDecorationSupport: DisplayDecorationSupport,
+    private val debug: Boolean,
+) : DisplayCutoutBaseView(context) {
     val colorMode: Int
     private val useInvertedAlphaColor: Boolean
-    private val color: Int
+    private var color: Int = Color.BLACK
+        set(value) {
+            field = value
+            paint.color = value
+        }
+
     private val bgColor: Int
-    private val cornerFilter: ColorFilter
+    private var cornerFilter: ColorFilter
     private val cornerBgFilter: ColorFilter
     private val clearPaint: Paint
     @JvmField val transparentRect: Rect = Rect()
@@ -74,7 +82,7 @@
             throw IllegalArgumentException("Attempting to use unsupported mode " +
                     "${PixelFormat.formatToString(displayDecorationSupport.format)}")
         }
-        if (DEBUG_COLOR) {
+        if (debug) {
             color = Color.GREEN
             bgColor = Color.TRANSPARENT
             colorMode = ActivityInfo.COLOR_MODE_DEFAULT
@@ -106,10 +114,16 @@
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
         parent.requestTransparentRegion(this)
-        if (!DEBUG_COLOR) {
+        updateColors()
+    }
+
+    private fun updateColors() {
+        if (!debug) {
             viewRootImpl.setDisplayDecoration(true)
         }
 
+        cornerFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)
+
         if (useInvertedAlphaColor) {
             paint.set(clearPaint)
         } else {
@@ -118,6 +132,21 @@
         }
     }
 
+    fun setDebugColor(color: Int) {
+        if (!debug) {
+            return
+        }
+
+        if (this.color == color) {
+            return
+        }
+
+        this.color = color
+
+        updateColors()
+        invalidate()
+    }
+
     override fun onUpdate() {
         parent.requestTransparentRegion(this)
     }
@@ -143,12 +172,12 @@
     override fun gatherTransparentRegion(region: Region?): Boolean {
         region?.let {
             calculateTransparentRect()
-            if (DEBUG_COLOR) {
+            if (debug) {
                 // Since we're going to draw a rectangle where the layer would
                 // normally be transparent, treat the transparent region as
                 // empty. We still want this method to be called, though, so
                 // that it calculates the transparent rect at the right time
-                // to match !DEBUG_COLOR.
+                // to match ![debug]
                 region.setEmpty()
             } else {
                 region.op(transparentRect, Region.Op.INTERSECT)
@@ -364,7 +393,7 @@
     /**
      * Update the rounded corner drawables.
      */
-    fun updateRoundedCornerDrawable(top: Drawable, bottom: Drawable) {
+    fun updateRoundedCornerDrawable(top: Drawable?, bottom: Drawable?) {
         roundedCornerDrawableTop = top
         roundedCornerDrawableBottom = bottom
         updateRoundedCornerDrawableBounds()
@@ -421,8 +450,4 @@
         ipw.println("roundedCornerBottomSize=$roundedCornerBottomSize")
         ipw.decreaseIndent()
     }
-
-    companion object {
-        private val DEBUG_COLOR = ScreenDecorations.DEBUG_COLOR
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index ea0f343..ff395da 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -36,6 +36,7 @@
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.hardware.graphics.common.AlphaInterpretation;
@@ -69,8 +70,9 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.decor.CutoutDecorProviderFactory;
+import com.android.systemui.decor.DebugRoundedCornerDelegate;
+import com.android.systemui.decor.DebugRoundedCornerModel;
 import com.android.systemui.decor.DecorProvider;
 import com.android.systemui.decor.DecorProviderFactory;
 import com.android.systemui.decor.DecorProviderKt;
@@ -78,14 +80,14 @@
 import com.android.systemui.decor.OverlayWindow;
 import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
 import com.android.systemui.decor.RoundedCornerDecorProviderFactory;
-import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.decor.RoundedCornerResDelegateImpl;
+import com.android.systemui.decor.ScreenDecorCommand;
 import com.android.systemui.log.ScreenDecorationsLogger;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.ThreadFactory;
 import com.android.systemui.util.settings.SecureSettings;
@@ -96,7 +98,6 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -105,19 +106,17 @@
  * for antialiasing and emulation purposes.
  */
 @SysUISingleton
-public class ScreenDecorations implements CoreStartable, Tunable , Dumpable {
-    private static final boolean DEBUG = false;
+public class ScreenDecorations implements CoreStartable, Dumpable {
+    private static final boolean DEBUG_LOGGING = false;
     private static final String TAG = "ScreenDecorations";
 
-    public static final String SIZE = "sysui_rounded_size";
-    public static final String PADDING = "sysui_rounded_content_padding";
     // Provide a way for factory to disable ScreenDecorations to run the Display tests.
     private static final boolean DEBUG_DISABLE_SCREEN_DECORATIONS =
             SystemProperties.getBoolean("debug.disable_screen_decorations", false);
     private static final boolean DEBUG_SCREENSHOT_ROUNDED_CORNERS =
             SystemProperties.getBoolean("debug.screenshot_rounded_corners", false);
-    private static final boolean VERBOSE = false;
-    static final boolean DEBUG_COLOR = DEBUG_SCREENSHOT_ROUNDED_CORNERS;
+    private boolean mDebug = DEBUG_SCREENSHOT_ROUNDED_CORNERS;
+    private int mDebugColor = Color.RED;
 
     private static final int[] DISPLAY_CUTOUT_IDS = {
             R.id.display_cutout,
@@ -133,8 +132,7 @@
     @VisibleForTesting
     protected boolean mIsRegistered;
     private final Context mContext;
-    private final Executor mMainExecutor;
-    private final TunerService mTunerService;
+    private final CommandRegistry mCommandRegistry;
     private final SecureSettings mSecureSettings;
     @VisibleForTesting
     DisplayTracker.Callback mDisplayListener;
@@ -147,9 +145,13 @@
     public final int mFaceScanningViewId;
 
     @VisibleForTesting
-    protected RoundedCornerResDelegate mRoundedCornerResDelegate;
+    protected RoundedCornerResDelegateImpl mRoundedCornerResDelegate;
     @VisibleForTesting
     protected DecorProviderFactory mRoundedCornerFactory;
+    @VisibleForTesting
+    protected DebugRoundedCornerDelegate mDebugRoundedCornerDelegate =
+            new DebugRoundedCornerDelegate();
+    protected DecorProviderFactory mDebugRoundedCornerFactory;
     private CutoutDecorProviderFactory mCutoutFactory;
     private int mProviderRefreshToken = 0;
     @VisibleForTesting
@@ -170,7 +172,7 @@
     private int mTintColor = Color.BLACK;
     @VisibleForTesting
     protected DisplayDecorationSupport mHwcScreenDecorationSupport;
-    private Display.Mode mDisplayMode;
+    private final Point mDisplaySize = new Point();
     @VisibleForTesting
     protected DisplayInfo mDisplayInfo = new DisplayInfo();
     private DisplayCutout mDisplayCutout;
@@ -313,9 +315,8 @@
 
     @Inject
     public ScreenDecorations(Context context,
-            @Main Executor mainExecutor,
             SecureSettings secureSettings,
-            TunerService tunerService,
+            CommandRegistry commandRegistry,
             UserTracker userTracker,
             DisplayTracker displayTracker,
             PrivacyDotViewController dotViewController,
@@ -325,9 +326,8 @@
             ScreenDecorationsLogger logger,
             AuthController authController) {
         mContext = context;
-        mMainExecutor = mainExecutor;
         mSecureSettings = secureSettings;
-        mTunerService = tunerService;
+        mCommandRegistry = commandRegistry;
         mUserTracker = userTracker;
         mDisplayTracker = displayTracker;
         mDotViewController = dotViewController;
@@ -352,6 +352,45 @@
         }
     };
 
+    private final ScreenDecorCommand.Callback mScreenDecorCommandCallback = (cmd, pw) -> {
+        // If we are exiting debug mode, we can set it (false) and bail, otherwise we will
+        // ensure that debug mode is set
+        if (cmd.getDebug() != null && !cmd.getDebug()) {
+            setDebug(false);
+            return;
+        } else {
+            // setDebug is idempotent
+            setDebug(true);
+        }
+
+        if (cmd.getColor() != null) {
+            mDebugColor = cmd.getColor();
+            mExecutor.execute(() -> {
+                if (mScreenDecorHwcLayer != null) {
+                    mScreenDecorHwcLayer.setDebugColor(cmd.getColor());
+                }
+                updateColorInversionDefault();
+            });
+        }
+
+        DebugRoundedCornerModel roundedTop = null;
+        DebugRoundedCornerModel roundedBottom = null;
+        if (cmd.getRoundedTop() != null) {
+            roundedTop = cmd.getRoundedTop().toRoundedCornerDebugModel();
+        }
+        if (cmd.getRoundedBottom() != null) {
+            roundedBottom = cmd.getRoundedBottom().toRoundedCornerDebugModel();
+        }
+        if (roundedTop != null || roundedBottom != null) {
+            mDebugRoundedCornerDelegate.applyNewDebugCorners(roundedTop, roundedBottom);
+            mExecutor.execute(() -> {
+                removeAllOverlays();
+                removeHwcOverlay();
+                setupDecorations();
+            });
+        }
+    };
+
     @Override
     public void start() {
         if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
@@ -363,6 +402,34 @@
         mExecutor.execute(this::startOnScreenDecorationsThread);
         mDotViewController.setUiExecutor(mExecutor);
         mAuthController.addCallback(mAuthControllerCallback);
+        mCommandRegistry.registerCommand(ScreenDecorCommand.SCREEN_DECOR_CMD_NAME,
+                () -> new ScreenDecorCommand(mScreenDecorCommandCallback));
+    }
+
+    /**
+     * Change the value of {@link ScreenDecorations#mDebug}. This operation is heavyweight, since
+     * it requires essentially re-init-ing this screen decorations process with the debug
+     * information taken into account.
+     */
+    @VisibleForTesting
+    protected void setDebug(boolean debug) {
+        if (mDebug == debug) {
+            return;
+        }
+
+        mDebug = debug;
+        if (!mDebug) {
+            mDebugRoundedCornerDelegate.removeDebugState();
+        }
+
+        mExecutor.execute(() -> {
+            // Re-trigger all of the screen decorations setup here so that the debug values
+            // can be picked up
+            removeAllOverlays();
+            removeHwcOverlay();
+            startOnScreenDecorationsThread();
+            updateColorInversionDefault();
+        });
     }
 
     private boolean isPrivacyDotEnabled() {
@@ -370,11 +437,16 @@
     }
 
     @NonNull
-    private List<DecorProvider> getProviders(boolean hasHwLayer) {
+    @VisibleForTesting
+    protected List<DecorProvider> getProviders(boolean hasHwLayer) {
         List<DecorProvider> decorProviders = new ArrayList<>(mDotFactory.getProviders());
         decorProviders.addAll(mFaceScanningFactory.getProviders());
         if (!hasHwLayer) {
-            decorProviders.addAll(mRoundedCornerFactory.getProviders());
+            if (mDebug && mDebugRoundedCornerFactory.getHasProviders()) {
+                decorProviders.addAll(mDebugRoundedCornerFactory.getProviders());
+            } else {
+                decorProviders.addAll(mRoundedCornerFactory.getProviders());
+            }
             decorProviders.addAll(mCutoutFactory.getProviders());
         }
         return decorProviders;
@@ -413,14 +485,17 @@
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mContext.getDisplay().getDisplayInfo(mDisplayInfo);
         mRotation = mDisplayInfo.rotation;
-        mDisplayMode = mDisplayInfo.getMode();
+        mDisplaySize.x = mDisplayInfo.getNaturalWidth();
+        mDisplaySize.y = mDisplayInfo.getNaturalHeight();
         mDisplayUniqueId = mDisplayInfo.uniqueId;
         mDisplayCutout = mDisplayInfo.displayCutout;
-        mRoundedCornerResDelegate = new RoundedCornerResDelegate(mContext.getResources(),
-                mDisplayUniqueId);
+        mRoundedCornerResDelegate =
+                new RoundedCornerResDelegateImpl(mContext.getResources(), mDisplayUniqueId);
         mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
                 getPhysicalPixelDisplaySizeRatio());
         mRoundedCornerFactory = new RoundedCornerDecorProviderFactory(mRoundedCornerResDelegate);
+        mDebugRoundedCornerFactory =
+                new RoundedCornerDecorProviderFactory(mDebugRoundedCornerDelegate);
         mCutoutFactory = getCutoutFactory();
         mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport();
         updateHwLayerRoundedCornerDrawable();
@@ -432,10 +507,12 @@
             public void onDisplayChanged(int displayId) {
                 mContext.getDisplay().getDisplayInfo(mDisplayInfo);
                 final int newRotation = mDisplayInfo.rotation;
-                final Display.Mode newDisplayMode = mDisplayInfo.getMode();
                 if ((mOverlays != null || mScreenDecorHwcWindow != null)
                         && (mRotation != newRotation
-                        || displayModeChanged(mDisplayMode, newDisplayMode))) {
+                        || displaySizeChanged(mDisplaySize, mDisplayInfo))) {
+                    final Point newSize = new Point();
+                    newSize.x = mDisplayInfo.getNaturalWidth();
+                    newSize.y = mDisplayInfo.getNaturalHeight();
                     // We cannot immediately update the orientation. Otherwise
                     // WindowManager is still deferring layout until it has finished dispatching
                     // the config changes, which may cause divergence between what we draw
@@ -444,15 +521,11 @@
                     // - we are trying to redraw. This because WM resized our window and told us to.
                     // - the config change has been dispatched, so WM is no longer deferring layout.
                     mPendingConfigChange = true;
-                    if (DEBUG) {
-                        if (mRotation != newRotation) {
-                            Log.i(TAG, "Rotation changed, deferring " + newRotation
-                                            + ", staying at " + mRotation);
-                        }
-                        if (displayModeChanged(mDisplayMode, newDisplayMode)) {
-                            Log.i(TAG, "Resolution changed, deferring " + newDisplayMode
-                                    + ", staying at " + mDisplayMode);
-                        }
+                    if (mRotation != newRotation) {
+                        mLogger.logRotationChangeDeferred(mRotation, newRotation);
+                    }
+                    if (!mDisplaySize.equals(newSize)) {
+                        mLogger.logDisplaySizeChanged(mDisplaySize, newSize);
                     }
 
                     if (mOverlays != null) {
@@ -461,7 +534,7 @@
                                 final ViewGroup overlayView = mOverlays[i].getRootView();
                                 overlayView.getViewTreeObserver().addOnPreDrawListener(
                                         new RestartingPreDrawListener(
-                                                overlayView, i, newRotation, newDisplayMode));
+                                                overlayView, i, newRotation, newSize));
                             }
                         }
                     }
@@ -471,7 +544,7 @@
                                 new RestartingPreDrawListener(
                                         mScreenDecorHwcWindow,
                                         -1, // Pass -1 for views with no specific position.
-                                        newRotation, newDisplayMode));
+                                        newRotation, newSize));
                     }
                     if (mScreenDecorHwcLayer != null) {
                         mScreenDecorHwcLayer.pendingConfigChange = true;
@@ -608,12 +681,6 @@
                 return;
             }
 
-            mMainExecutor.execute(() -> {
-                Trace.beginSection("ScreenDecorations#addTunable");
-                mTunerService.addTunable(this, SIZE);
-                Trace.endSection();
-            });
-
             // Watch color inversion and invert the overlay as needed.
             if (mColorInversionSetting == null) {
                 mColorInversionSetting = new SettingObserver(mSecureSettings, mHandler,
@@ -632,12 +699,6 @@
             mUserTracker.addCallback(mUserChangedCallback, mExecutor);
             mIsRegistered = true;
         } else {
-            mMainExecutor.execute(() -> {
-                Trace.beginSection("ScreenDecorations#removeTunable");
-                mTunerService.removeTunable(this);
-                Trace.endSection();
-            });
-
             if (mColorInversionSetting != null) {
                 mColorInversionSetting.setListening(false);
             }
@@ -777,7 +838,8 @@
         }
         mScreenDecorHwcWindow = (ViewGroup) LayoutInflater.from(mContext).inflate(
                 R.layout.screen_decor_hwc_layer, null);
-        mScreenDecorHwcLayer = new ScreenDecorHwcLayer(mContext, mHwcScreenDecorationSupport);
+        mScreenDecorHwcLayer =
+                new ScreenDecorHwcLayer(mContext, mHwcScreenDecorationSupport, mDebug);
         mScreenDecorHwcWindow.addView(mScreenDecorHwcLayer, new FrameLayout.LayoutParams(
                 MATCH_PARENT, MATCH_PARENT, Gravity.TOP | Gravity.START));
         mWindowManager.addView(mScreenDecorHwcWindow, getHwcWindowLayoutParams());
@@ -825,7 +887,7 @@
         lp.height = MATCH_PARENT;
         lp.setTitle("ScreenDecorHwcOverlay");
         lp.gravity = Gravity.TOP | Gravity.START;
-        if (!DEBUG_COLOR) {
+        if (!mDebug) {
             lp.setColorMode(ActivityInfo.COLOR_MODE_A8);
         }
         return lp;
@@ -884,15 +946,8 @@
         }
     }
 
-    private static boolean displayModeChanged(Display.Mode oldMode, Display.Mode newMode) {
-        if (oldMode == null) {
-            return true;
-        }
-
-        // We purposely ignore refresh rate and id changes here, because we don't need to
-        // invalidate for those, and they can trigger the refresh rate to increase
-        return oldMode.getPhysicalWidth() != newMode.getPhysicalWidth()
-                || oldMode.getPhysicalHeight() != newMode.getPhysicalHeight();
+    private static boolean displaySizeChanged(Point size, DisplayInfo info) {
+        return size.x != info.getNaturalWidth() || size.y != info.getNaturalHeight();
     }
 
     private int getOverlayWindowGravity(@BoundsPosition int pos) {
@@ -933,19 +988,42 @@
             new UserTracker.Callback() {
                 @Override
                 public void onUserChanged(int newUser, @NonNull Context userContext) {
-                    if (DEBUG) {
-                        Log.d(TAG, "UserSwitched newUserId=" + newUser);
-                    }
+                    mLogger.logUserSwitched(newUser);
                     // update color inversion setting to the new user
                     mColorInversionSetting.setUserId(newUser);
                     updateColorInversion(mColorInversionSetting.getValue());
                 }
             };
 
+    /**
+     * Use the current value of {@link ScreenDecorations#mColorInversionSetting} and passes it
+     * to {@link ScreenDecorations#updateColorInversion}
+     */
+    private void updateColorInversionDefault() {
+        int inversion = 0;
+        if (mColorInversionSetting != null) {
+            inversion = mColorInversionSetting.getValue();
+        }
+
+        updateColorInversion(inversion);
+    }
+
+    /**
+     * Update the tint color of screen decoration assets. Defaults to Color.BLACK. In the case of
+     * a color inversion being set, use Color.WHITE (which inverts to black).
+     *
+     * When {@link ScreenDecorations#mDebug} is {@code true}, this value is updated to use
+     * {@link ScreenDecorations#mDebugColor}, and does not handle inversion.
+     *
+     * @param colorsInvertedValue if non-zero, assume that colors are inverted, and use Color.WHITE
+     *                            for screen decoration tint
+     */
     private void updateColorInversion(int colorsInvertedValue) {
         mTintColor = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK;
-        if (DEBUG_COLOR) {
-            mTintColor = Color.RED;
+        if (mDebug) {
+            mTintColor = mDebugColor;
+            mDebugRoundedCornerDelegate.setColor(mTintColor);
+            //TODO(b/285941724): update the hwc layer color here too (or disable it in debug mode)
         }
 
         updateOverlayProviderViews(new Integer[] {
@@ -986,7 +1064,9 @@
             int oldRotation = mRotation;
             mPendingConfigChange = false;
             updateConfiguration();
-            if (DEBUG) Log.i(TAG, "onConfigChanged from rot " + oldRotation + " to " + mRotation);
+            if (oldRotation != mRotation) {
+                mLogger.logRotationChanged(oldRotation, mRotation);
+            }
             setupDecorations();
             if (mOverlays != null) {
                 // Updating the layout params ensures that ViewRootImpl will call relayoutWindow(),
@@ -1016,6 +1096,7 @@
         if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
             return;
         }
+        ipw.println("mDebug:" + mDebug);
 
         ipw.println("mIsPrivacyDotEnabled:" + isPrivacyDotEnabled());
         ipw.println("shouldOptimizeOverlayVisibility:" + shouldOptimizeVisibility());
@@ -1071,6 +1152,7 @@
             }
         }
         mRoundedCornerResDelegate.dump(pw, args);
+        mDebugRoundedCornerDelegate.dump(pw);
     }
 
     @VisibleForTesting
@@ -1084,17 +1166,18 @@
         if (mRotation != newRotation) {
             mDotViewController.setNewRotation(newRotation);
         }
-        final Display.Mode newMod = mDisplayInfo.getMode();
         final DisplayCutout newCutout = mDisplayInfo.displayCutout;
 
         if (!mPendingConfigChange
-                && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod)
+                && (newRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo)
                 || !Objects.equals(newCutout, mDisplayCutout))) {
             mRotation = newRotation;
-            mDisplayMode = newMod;
+            mDisplaySize.x = mDisplayInfo.getNaturalWidth();
+            mDisplaySize.y = mDisplayInfo.getNaturalHeight();
             mDisplayCutout = newCutout;
-            mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
-                    getPhysicalPixelDisplaySizeRatio());
+            float ratio = getPhysicalPixelDisplaySizeRatio();
+            mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(ratio);
+            mDebugRoundedCornerDelegate.setPhysicalPixelDisplaySizeRatio(ratio);
             if (mScreenDecorHwcLayer != null) {
                 mScreenDecorHwcLayer.pendingConfigChange = false;
                 mScreenDecorHwcLayer.updateConfiguration(mDisplayUniqueId);
@@ -1117,7 +1200,8 @@
     }
 
     private boolean hasRoundedCorners() {
-        return mRoundedCornerFactory.getHasProviders();
+        return mRoundedCornerFactory.getHasProviders()
+                || mDebugRoundedCornerFactory.getHasProviders();
     }
 
     private boolean shouldOptimizeVisibility() {
@@ -1170,43 +1254,19 @@
         Trace.endSection();
     }
 
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
-            Log.i(TAG, "ScreenDecorations is disabled");
-            return;
-        }
-        mExecutor.execute(() -> {
-            if (mOverlays == null || !SIZE.equals(key)) {
-                return;
-            }
-            Trace.beginSection("ScreenDecorations#onTuningChanged");
-            try {
-                final int sizeFactor = Integer.parseInt(newValue);
-                mRoundedCornerResDelegate.setTuningSizeFactor(sizeFactor);
-            } catch (NumberFormatException e) {
-                mRoundedCornerResDelegate.setTuningSizeFactor(null);
-            }
-            updateOverlayProviderViews(new Integer[] {
-                    R.id.rounded_corner_top_left,
-                    R.id.rounded_corner_top_right,
-                    R.id.rounded_corner_bottom_left,
-                    R.id.rounded_corner_bottom_right
-            });
-            updateHwLayerRoundedCornerExistAndSize();
-            Trace.endSection();
-        });
-    }
-
     private void updateHwLayerRoundedCornerDrawable() {
         if (mScreenDecorHwcLayer == null) {
             return;
         }
 
-        final Drawable topDrawable = mRoundedCornerResDelegate.getTopRoundedDrawable();
-        final Drawable bottomDrawable = mRoundedCornerResDelegate.getBottomRoundedDrawable();
+        Drawable topDrawable = mRoundedCornerResDelegate.getTopRoundedDrawable();
+        Drawable bottomDrawable = mRoundedCornerResDelegate.getBottomRoundedDrawable();
+        if (mDebug && (mDebugRoundedCornerFactory.getHasProviders())) {
+            topDrawable = mDebugRoundedCornerDelegate.getTopRoundedDrawable();
+            bottomDrawable = mDebugRoundedCornerDelegate.getBottomRoundedDrawable();
+        }
 
-        if (topDrawable == null || bottomDrawable == null) {
+        if (topDrawable == null && bottomDrawable == null) {
             return;
         }
         mScreenDecorHwcLayer.updateRoundedCornerDrawable(topDrawable, bottomDrawable);
@@ -1216,11 +1276,19 @@
         if (mScreenDecorHwcLayer == null) {
             return;
         }
-        mScreenDecorHwcLayer.updateRoundedCornerExistenceAndSize(
-                mRoundedCornerResDelegate.getHasTop(),
-                mRoundedCornerResDelegate.getHasBottom(),
-                mRoundedCornerResDelegate.getTopRoundedSize().getWidth(),
-                mRoundedCornerResDelegate.getBottomRoundedSize().getWidth());
+        if (mDebug && mDebugRoundedCornerFactory.getHasProviders()) {
+            mScreenDecorHwcLayer.updateRoundedCornerExistenceAndSize(
+                    mDebugRoundedCornerDelegate.getHasTop(),
+                    mDebugRoundedCornerDelegate.getHasBottom(),
+                    mDebugRoundedCornerDelegate.getTopRoundedSize().getWidth(),
+                    mDebugRoundedCornerDelegate.getBottomRoundedSize().getWidth());
+        } else {
+            mScreenDecorHwcLayer.updateRoundedCornerExistenceAndSize(
+                    mRoundedCornerResDelegate.getHasTop(),
+                    mRoundedCornerResDelegate.getHasBottom(),
+                    mRoundedCornerResDelegate.getTopRoundedSize().getWidth(),
+                    mRoundedCornerResDelegate.getBottomRoundedSize().getWidth());
+        }
     }
 
     @VisibleForTesting
@@ -1247,7 +1315,7 @@
 
             paint.setColor(mColor);
             paint.setStyle(Paint.Style.FILL);
-            if (DEBUG) {
+            if (DEBUG_LOGGING) {
                 getViewTreeObserver().addOnDrawListener(() -> Log.i(TAG,
                         getWindowTitleByPos(pos) + " drawn in rot " + mRotation));
             }
@@ -1422,31 +1490,29 @@
 
         private final View mView;
         private final int mTargetRotation;
-        private final Display.Mode mTargetDisplayMode;
+        private final Point mTargetDisplaySize;
         // Pass -1 for ScreenDecorHwcLayer since it's a fullscreen window and has no specific
         // position.
         private final int mPosition;
 
         private RestartingPreDrawListener(View view, @BoundsPosition int position,
-                int targetRotation, Display.Mode targetDisplayMode) {
+                int targetRotation, Point targetDisplaySize) {
             mView = view;
             mTargetRotation = targetRotation;
-            mTargetDisplayMode = targetDisplayMode;
+            mTargetDisplaySize = targetDisplaySize;
             mPosition = position;
         }
 
         @Override
         public boolean onPreDraw() {
             mView.getViewTreeObserver().removeOnPreDrawListener(this);
-            if (mTargetRotation == mRotation
-                    && !displayModeChanged(mDisplayMode, mTargetDisplayMode)) {
-                if (DEBUG) {
+            if (mTargetRotation == mRotation && mDisplaySize.equals(mTargetDisplaySize)) {
+                if (DEBUG_LOGGING) {
                     final String title = mPosition < 0 ? "ScreenDecorHwcLayer"
                             : getWindowTitleByPos(mPosition);
                     Log.i(TAG, title + " already in target rot "
                             + mTargetRotation + " and in target resolution "
-                            + mTargetDisplayMode.getPhysicalWidth() + "x"
-                            + mTargetDisplayMode.getPhysicalHeight()
+                            + mTargetDisplaySize.x + "x" + mTargetDisplaySize.y
                             + ", allow draw without restarting it");
                 }
                 return true;
@@ -1456,13 +1522,12 @@
             // This changes the window attributes - we need to restart the traversal for them to
             // take effect.
             updateConfiguration();
-            if (DEBUG) {
+            if (DEBUG_LOGGING) {
                 final String title = mPosition < 0 ? "ScreenDecorHwcLayer"
                         : getWindowTitleByPos(mPosition);
                 Log.i(TAG, title
                         + " restarting listener fired, restarting draw for rot " + mRotation
-                        + ", resolution " + mDisplayMode.getPhysicalWidth() + "x"
-                        + mDisplayMode.getPhysicalHeight());
+                        + ", resolution " + mDisplaySize.x + "x" + mDisplaySize.y);
             }
             mView.invalidate();
             return false;
@@ -1488,19 +1553,18 @@
         public boolean onPreDraw() {
             mContext.getDisplay().getDisplayInfo(mDisplayInfo);
             final int displayRotation = mDisplayInfo.rotation;
-            final Display.Mode displayMode = mDisplayInfo.getMode();
-            if ((displayRotation != mRotation || displayModeChanged(mDisplayMode, displayMode))
+            if ((displayRotation != mRotation || displaySizeChanged(mDisplaySize, mDisplayInfo))
                     && !mPendingConfigChange) {
-                if (DEBUG) {
+                if (DEBUG_LOGGING) {
                     if (displayRotation != mRotation) {
                         Log.i(TAG, "Drawing rot " + mRotation + ", but display is at rot "
                                 + displayRotation + ". Restarting draw");
                     }
-                    if (displayModeChanged(mDisplayMode, displayMode)) {
-                        Log.i(TAG, "Drawing at " + mDisplayMode.getPhysicalWidth()
-                                + "x" + mDisplayMode.getPhysicalHeight() + ", but display is at "
-                                + displayMode.getPhysicalWidth() + "x"
-                                + displayMode.getPhysicalHeight() + ". Restarting draw");
+                    if (displaySizeChanged(mDisplaySize, mDisplayInfo)) {
+                        Log.i(TAG, "Drawing at " + mDisplaySize.x + "x" + mDisplaySize.y
+                                + ", but display is at "
+                                + mDisplayInfo.getNaturalWidth() + "x"
+                                + mDisplayInfo.getNaturalHeight() + ". Restarting draw");
                     }
                 }
                 mView.invalidate();
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 806d1af..3ca74ac 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -81,9 +81,6 @@
 
     protected final Handler mHandler;
 
-    private float mMinSwipeProgress = 0f;
-    private float mMaxSwipeProgress = 1f;
-
     private final SpringConfig mSnapBackSpringConfig =
             new SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
 
@@ -227,18 +224,11 @@
         return v.getMeasuredWidth();
     }
 
-    public void setMinSwipeProgress(float minSwipeProgress) {
-        mMinSwipeProgress = minSwipeProgress;
-    }
-
-    public void setMaxSwipeProgress(float maxSwipeProgress) {
-        mMaxSwipeProgress = maxSwipeProgress;
-    }
-
     private float getSwipeProgressForOffset(View view, float translation) {
+        if (translation == 0) return 0;
         float viewSize = getSize(view);
         float result = Math.abs(translation / viewSize);
-        return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
+        return Math.min(Math.max(0, result), 1);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
deleted file mode 100644
index 527ce12..0000000
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui;
-
-import android.content.Context;
-
-/**
- * Starts up SystemUI using the AOSP {@link SystemUIInitializerImpl}.
- *
- * This initializer relies on reflection to start everything up and should be considered deprecated.
- * Instead, create your own {@link SystemUIAppComponentFactoryBase}, specify it in your
- * AndroidManifest.xml and construct your own {@link SystemUIInitializer} directly.
- *
- * @deprecated Define your own SystemUIAppComponentFactoryBase implementation and use that. This
- *             implementation may be changed or removed in future releases.
- */
-@Deprecated
-public class SystemUIAppComponentFactory extends SystemUIAppComponentFactoryBase {
-    @Override
-    protected SystemUIInitializer createSystemUIInitializer(Context context) {
-        return SystemUIInitializerFactory.createWithContext(context);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 70c39df..e8458e7 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -54,7 +54,7 @@
  * Application class for SystemUI.
  */
 public class SystemUIApplication extends Application implements
-        SystemUIAppComponentFactory.ContextInitializer {
+        SystemUIAppComponentFactoryBase.ContextInitializer {
 
     public static final String TAG = "SystemUIService";
     private static final boolean DEBUG = false;
@@ -66,7 +66,7 @@
      */
     private CoreStartable[] mServices;
     private boolean mServicesStarted;
-    private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
+    private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback;
     private SysUIComponent mSysUIComponent;
     private SystemUIInitializer mInitializer;
 
@@ -254,11 +254,16 @@
         }
 
         for (i = 0; i < mServices.length; i++) {
+            CoreStartable service = mServices[i];
             if (mBootCompleteCache.isBootComplete()) {
-                notifyBootCompleted(mServices[i]);
+                notifyBootCompleted(service);
             }
 
-            dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
+            if (service.isDumpCritical()) {
+                dumpManager.registerCriticalDumpable(service.getClass().getName(), service);
+            } else {
+                dumpManager.registerNormalDumpable(service.getClass().getName(), service);
+            }
         }
         mSysUIComponent.getInitController().executePostInitTasks();
         log.traceEnd();
@@ -366,7 +371,7 @@
 
     @Override
     public void setContextAvailableCallback(
-            SystemUIAppComponentFactory.ContextAvailableCallback callback) {
+            SystemUIAppComponentFactoryBase.ContextAvailableCallback callback) {
         mContextAvailableCallback = callback;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt
deleted file mode 100644
index b9454e8..0000000
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt
+++ /dev/null
@@ -1,72 +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
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.util.Log
-import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.util.Assert
-
-/**
- * Factory to reflectively lookup a [SystemUIInitializer] to start SystemUI with.
- */
-@Deprecated("Provide your own {@link SystemUIAppComponentFactoryBase} that doesn't need this.")
-object SystemUIInitializerFactory {
-    private const val TAG = "SysUIInitializerFactory"
-    @SuppressLint("StaticFieldLeak")
-    private var initializer: SystemUIInitializer? = null
-
-    /**
-     * Instantiate a [SystemUIInitializer] reflectively.
-     */
-    @JvmStatic
-    fun createWithContext(context: Context): SystemUIInitializer {
-        return createFromConfig(context)
-    }
-
-    /**
-     * Instantiate a [SystemUIInitializer] reflectively.
-     */
-    @JvmStatic
-    private fun createFromConfig(context: Context): SystemUIInitializer {
-        Assert.isMainThread()
-
-        return createFromConfigNoAssert(context)
-    }
-
-    @JvmStatic
-    @VisibleForTesting
-    fun createFromConfigNoAssert(context: Context): SystemUIInitializer {
-
-        return initializer ?: run {
-            val className = context.getString(R.string.config_systemUIFactoryComponent)
-            if (className.isEmpty()) {
-                throw RuntimeException("No SystemUIFactory component configured")
-            }
-            try {
-                val cls = context.classLoader.loadClass(className)
-                val constructor = cls.getConstructor(Context::class.java)
-                (constructor.newInstance(context) as SystemUIInitializer).apply {
-                    initializer = this
-                }
-            } catch (t: Throwable) {
-                Log.w(TAG, "Error creating SystemUIInitializer component: $className", t)
-                throw t
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 50e0399..6cf9eff 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -30,8 +30,10 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpHandler;
+import com.android.systemui.dump.LogBufferEulogizer;
 import com.android.systemui.dump.LogBufferFreezer;
 import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
+import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager;
 import com.android.systemui.statusbar.policy.BatteryStateNotifier;
 
 import java.io.FileDescriptor;
@@ -44,22 +46,29 @@
     private final Handler mMainHandler;
     private final DumpHandler mDumpHandler;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final LogBufferEulogizer mLogBufferEulogizer;
     private final LogBufferFreezer mLogBufferFreezer;
     private final BatteryStateNotifier mBatteryStateNotifier;
 
+    private final UncaughtExceptionPreHandlerManager mUncaughtExceptionPreHandlerManager;
+
     @Inject
     public SystemUIService(
             @Main Handler mainHandler,
             DumpHandler dumpHandler,
             BroadcastDispatcher broadcastDispatcher,
+            LogBufferEulogizer logBufferEulogizer,
             LogBufferFreezer logBufferFreezer,
-            BatteryStateNotifier batteryStateNotifier) {
+            BatteryStateNotifier batteryStateNotifier,
+            UncaughtExceptionPreHandlerManager uncaughtExceptionPreHandlerManager) {
         super();
         mMainHandler = mainHandler;
         mDumpHandler = dumpHandler;
         mBroadcastDispatcher = broadcastDispatcher;
+        mLogBufferEulogizer = logBufferEulogizer;
         mLogBufferFreezer = logBufferFreezer;
         mBatteryStateNotifier = batteryStateNotifier;
+        mUncaughtExceptionPreHandlerManager = uncaughtExceptionPreHandlerManager;
     }
 
     @Override
@@ -71,7 +80,10 @@
 
         // Finish initializing dump logic
         mLogBufferFreezer.attach(mBroadcastDispatcher);
-        mDumpHandler.init();
+
+        // Attempt to dump all LogBuffers for any uncaught exception
+        mUncaughtExceptionPreHandlerManager.registerHandler(
+                (thread, throwable) -> mLogBufferEulogizer.record(throwable));
 
         // If configured, set up a battery notification
         if (getResources().getBoolean(R.bool.config_showNotificationForUnknownBatteryState)) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
index c3bb423..859e183 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
@@ -87,14 +87,15 @@
     }
 
     /**
-     * Shows magnification settings panel {@link WindowMagnificationSettings}.
+     * Toggles the visibility of magnification settings panel {@link WindowMagnificationSettings}.
+     * We show the panel if it is not visible. Otherwise, hide the panel.
      */
-    void showMagnificationSettings() {
+    void toggleSettingsPanelVisibility() {
         if (!mWindowMagnificationSettings.isSettingPanelShowing()) {
             onConfigurationChanged(mContext.getResources().getConfiguration());
             mContext.registerComponentCallbacks(this);
         }
-        mWindowMagnificationSettings.showSettingPanel();
+        mWindowMagnificationSettings.toggleSettingsPanelVisibility();
     }
 
     void closeMagnificationSettings() {
@@ -106,6 +107,10 @@
         return mWindowMagnificationSettings.isSettingPanelShowing();
     }
 
+    void setMagnificationScale(float scale) {
+        mWindowMagnificationSettings.setMagnificationScale(scale);
+    }
+
     @Override
     public void onConfigurationChanged(@NonNull Configuration newConfig) {
         final int configDiff = newConfig.diff(mConfiguration);
@@ -159,8 +164,9 @@
          *
          * @param displayId The logical display id.
          * @param scale Magnification scale value.
+         * @param updatePersistence whether the new scale should be persisted.
          */
-        void onMagnifierScale(int displayId, float scale);
+        void onMagnifierScale(int displayId, float scale, boolean updatePersistence);
 
         /**
          * Called when magnification mode changed.
@@ -210,9 +216,9 @@
         }
 
         @Override
-        public void onMagnifierScale(float scale) {
+        public void onMagnifierScale(float scale, boolean updatePersistence) {
             mSettingsControllerCallback.onMagnifierScale(mDisplayId,
-                    A11Y_ACTION_SCALE_RANGE.clamp(scale));
+                    A11Y_ACTION_SCALE_RANGE.clamp(scale), updatePersistence);
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 4158390..03ad132 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -54,6 +54,7 @@
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -188,6 +189,7 @@
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     private final NotificationShadeWindowController mNotificationShadeController;
     private final ShadeController mShadeController;
+    private final Lazy<ShadeViewController> mShadeViewController;
     private final StatusBarWindowCallback mNotificationShadeCallback;
     private boolean mDismissNotificationShadeActionRegistered;
 
@@ -196,12 +198,14 @@
             UserTracker userTracker,
             NotificationShadeWindowController notificationShadeController,
             ShadeController shadeController,
+            Lazy<ShadeViewController> shadeViewController,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             Optional<Recents> recentsOptional,
             DisplayTracker displayTracker) {
         mContext = context;
         mUserTracker = userTracker;
         mShadeController = shadeController;
+        mShadeViewController = shadeViewController;
         mRecentsOptional = recentsOptional;
         mDisplayTracker = displayTracker;
         mReceiver = new SystemActionsBroadcastReceiver();
@@ -329,7 +333,8 @@
         // binder calls
         final Optional<CentralSurfaces> centralSurfacesOptional =
                 mCentralSurfacesOptionalLazy.get();
-        if (centralSurfacesOptional.map(CentralSurfaces::isPanelExpanded).orElse(false)
+        if (centralSurfacesOptional.isPresent()
+                && mShadeViewController.get().isPanelExpanded()
                 && !centralSurfacesOptional.get().isKeyguardShowing()) {
             if (!mDismissNotificationShadeActionRegistered) {
                 mA11yManager.registerSystemAction(
@@ -485,13 +490,11 @@
     }
 
     private void handleNotifications() {
-        mCentralSurfacesOptionalLazy.get().ifPresent(
-                CentralSurfaces::animateExpandNotificationsPanel);
+        mShadeController.animateExpandShade();
     }
 
     private void handleQuickSettings() {
-        mCentralSurfacesOptionalLazy.get().ifPresent(
-                centralSurfaces -> centralSurfaces.animateExpandSettingsPanel(null));
+        mShadeController.animateExpandQs();
     }
 
     private void handlePowerDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index e2b85fa..baabd95 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -28,6 +28,7 @@
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.SurfaceControl;
 import android.view.WindowManagerGlobal;
@@ -72,6 +73,9 @@
     private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl;
     private SysUiState mSysUiState;
 
+    @VisibleForTesting
+    SparseArray<SparseArray<Float>> mUsersScales = new SparseArray();
+
     private static class ControllerSupplier extends
             DisplayIdIndexSupplier<WindowMagnificationController> {
 
@@ -171,7 +175,7 @@
 
         mModeSwitchesController.setClickListenerDelegate(
                 displayId -> mHandler.post(() -> {
-                    showMagnificationSettingsPanel(displayId);
+                    toggleSettingsPanelVisibility(displayId);
                 }));
     }
 
@@ -254,11 +258,11 @@
     }
 
     @MainThread
-    void showMagnificationSettingsPanel(int displayId) {
+    void toggleSettingsPanelVisibility(int displayId) {
         final MagnificationSettingsController magnificationSettingsController =
                 mMagnificationSettingsSupplier.get(displayId);
         if (magnificationSettingsController != null) {
-            magnificationSettingsController.showMagnificationSettings();
+            magnificationSettingsController.toggleSettingsPanelVisibility();
         }
     }
 
@@ -295,6 +299,23 @@
         mModeSwitchesController.removeButton(displayId);
     }
 
+    @MainThread
+    void setUserMagnificationScale(int userId, int displayId, float scale) {
+        SparseArray<Float> scales = mUsersScales.get(userId);
+        if (scales == null) {
+            scales = new SparseArray<>();
+            mUsersScales.put(userId, scales);
+        }
+        if (scales.contains(displayId) && scales.get(displayId) == scale) {
+            return;
+        }
+        scales.put(displayId, scale);
+
+        final MagnificationSettingsController magnificationSettingsController =
+                mMagnificationSettingsSupplier.get(displayId);
+        magnificationSettingsController.setMagnificationScale(scale);
+    }
+
     @VisibleForTesting
     final WindowMagnifierCallback mWindowMagnifierCallback = new WindowMagnifierCallback() {
         @Override
@@ -312,9 +333,10 @@
         }
 
         @Override
-        public void onPerformScaleAction(int displayId, float scale) {
+        public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
             if (mWindowMagnificationConnectionImpl != null) {
-                mWindowMagnificationConnectionImpl.onPerformScaleAction(displayId, scale);
+                mWindowMagnificationConnectionImpl.onPerformScaleAction(
+                        displayId, scale, updatePersistence);
             }
         }
 
@@ -335,7 +357,7 @@
         @Override
         public void onClickSettingsButton(int displayId) {
             mHandler.post(() -> {
-                showMagnificationSettingsPanel(displayId);
+                toggleSettingsPanelVisibility(displayId);
             });
         }
     };
@@ -363,9 +385,10 @@
         }
 
         @Override
-        public void onMagnifierScale(int displayId, float scale) {
+        public void onMagnifierScale(int displayId, float scale, boolean updatePersistence) {
             if (mWindowMagnificationConnectionImpl != null) {
-                mWindowMagnificationConnectionImpl.onPerformScaleAction(displayId, scale);
+                mWindowMagnificationConnectionImpl.onPerformScaleAction(
+                        displayId, scale, updatePersistence);
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
index c081893..928445b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
@@ -99,6 +99,12 @@
     }
 
     @Override
+    public void onUserMagnificationScaleChanged(int userId, int displayId, float scale) {
+        mHandler.post(() -> mWindowMagnification.setUserMagnificationScale(
+                userId, displayId, scale));
+    }
+
+    @Override
     public void setConnectionCallback(IWindowMagnificationConnectionCallback callback) {
         mConnectionCallback = callback;
     }
@@ -123,10 +129,10 @@
         }
     }
 
-    void onPerformScaleAction(int displayId, float scale) {
+    void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
         if (mConnectionCallback != null) {
             try {
-                mConnectionCallback.onPerformScaleAction(displayId, scale);
+                mConnectionCallback.onPerformScaleAction(displayId, scale, updatePersistence);
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to inform performing scale action", e);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index a67f706..28d59c2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -76,6 +76,7 @@
 
 import androidx.core.math.MathUtils;
 
+import com.android.internal.accessibility.common.MagnificationConstants;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.R;
@@ -101,7 +102,9 @@
     // Delay to avoid updating state description too frequently.
     private static final int UPDATE_STATE_DESCRIPTION_DELAY_MS = 100;
     // It should be consistent with the value defined in WindowMagnificationGestureHandler.
-    private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(1.0f, 8.0f);
+    private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(
+            MagnificationConstants.SCALE_MIN_VALUE,
+            MagnificationConstants.SCALE_MAX_VALUE);
     private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
     private static final float ANIMATION_BOUNCE_EFFECT_SCALE = 1.05f;
     private final SparseArray<Float> mMagnificationSizeScaleOptions = new SparseArray<>();
@@ -221,6 +224,7 @@
 
     private boolean mAllowDiagonalScrolling = false;
     private boolean mEditSizeEnable = false;
+    private boolean mSettingsPanelVisibility = false;
 
     @Nullable
     private final MirrorWindowControl mMirrorWindowControl;
@@ -1399,6 +1403,8 @@
             return;
         }
 
+        mSettingsPanelVisibility = settingsPanelIsShown;
+
         mDragView.setBackground(mContext.getResources().getDrawable(settingsPanelIsShown
                 ? R.drawable.accessibility_window_magnification_drag_handle_background_change
                 : R.drawable.accessibility_window_magnification_drag_handle_background));
@@ -1439,12 +1445,25 @@
 
     private class MirrorWindowA11yDelegate extends View.AccessibilityDelegate {
 
+        private CharSequence getClickAccessibilityActionLabel() {
+            if (mEditSizeEnable) {
+                // Perform click action to exit edit mode
+                return mContext.getResources().getString(
+                        R.string.magnification_exit_edit_mode_click_label);
+            }
+
+            return mSettingsPanelVisibility
+                    ? mContext.getResources().getString(
+                            R.string.magnification_close_settings_click_label)
+                    : mContext.getResources().getString(
+                            R.string.magnification_open_settings_click_label);
+        }
+
         @Override
         public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
             super.onInitializeAccessibilityNodeInfo(host, info);
             final AccessibilityAction clickAction = new AccessibilityAction(
-                    AccessibilityAction.ACTION_CLICK.getId(), mContext.getResources().getString(
-                    R.string.magnification_open_settings_click_label));
+                    AccessibilityAction.ACTION_CLICK.getId(), getClickAccessibilityActionLabel());
             info.addAction(clickAction);
             info.setClickable(true);
             info.addAction(
@@ -1475,16 +1494,18 @@
 
         private boolean performA11yAction(int action) {
             if (action == AccessibilityAction.ACTION_CLICK.getId()) {
-                // Simulate tapping the drag view so it opens the Settings.
-                handleSingleTap(mDragView);
+                if (mEditSizeEnable) {
+                    // When edit mode is enabled, click the magnifier to exit edit mode.
+                    setEditMagnifierSizeMode(false);
+                } else {
+                    // Simulate tapping the drag view so it opens the Settings.
+                    handleSingleTap(mDragView);
+                }
+
             } else if (action == R.id.accessibility_action_zoom_in) {
-                final float scale = mScale + A11Y_CHANGE_SCALE_DIFFERENCE;
-                mWindowMagnifierCallback.onPerformScaleAction(mDisplayId,
-                        A11Y_ACTION_SCALE_RANGE.clamp(scale));
+                performScale(mScale + A11Y_CHANGE_SCALE_DIFFERENCE);
             } else if (action == R.id.accessibility_action_zoom_out) {
-                final float scale = mScale - A11Y_CHANGE_SCALE_DIFFERENCE;
-                mWindowMagnifierCallback.onPerformScaleAction(mDisplayId,
-                        A11Y_ACTION_SCALE_RANGE.clamp(scale));
+                performScale(mScale - A11Y_CHANGE_SCALE_DIFFERENCE);
             } else if (action == R.id.accessibility_action_move_up) {
                 move(0, -mSourceBounds.height());
             } else if (action == R.id.accessibility_action_move_down) {
@@ -1499,5 +1520,11 @@
             mWindowMagnifierCallback.onAccessibilityActionPerformed(mDisplayId);
             return true;
         }
+
+        private void performScale(float scale) {
+            scale = A11Y_ACTION_SCALE_RANGE.clamp(scale);
+            mWindowMagnifierCallback.onPerformScaleAction(
+                    mDisplayId, scale, /* updatePersistence= */ true);
+        }
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 3b1d695..6ec5320 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -18,10 +18,12 @@
 
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
-import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE;
+
 import android.annotation.IntDef;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -55,7 +57,6 @@
 import android.widget.Switch;
 import android.widget.TextView;
 
-import com.android.internal.accessibility.common.MagnificationConstants;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.R;
@@ -79,7 +80,8 @@
     private final Runnable mWindowInsetChangeRunnable;
     private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
 
-    private final LayoutParams mParams;
+    @VisibleForTesting
+    final LayoutParams mParams;
     @VisibleForTesting
     final Rect mDraggableWindowBounds = new Rect();
     private boolean mIsVisible = false;
@@ -92,7 +94,6 @@
     private Switch mAllowDiagonalScrollingSwitch;
     private LinearLayout mPanelView;
     private LinearLayout mSettingView;
-    private LinearLayout mButtonView;
     private ImageButton mSmallButton;
     private ImageButton mMediumButton;
     private ImageButton mLargeButton;
@@ -101,8 +102,13 @@
     private ImageButton mFullScreenButton;
     private int mLastSelectedButtonIndex = MagnificationSize.NONE;
     private boolean mAllowDiagonalScrolling = false;
-    private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
-    private static final float A11Y_SCALE_MIN_VALUE = 1.0f;
+    /**
+     * Amount by which magnification scale changes compared to seekbar in settings.
+     * magnitude = 10 means, for every 1 scale increase, 10 progress increase in seekbar.
+     */
+    private int mSeekBarMagnitude;
+    private float mScale = SCALE_MIN_VALUE;
+
     private WindowMagnificationSettingsCallback mCallback;
 
     private ContentObserver mMagnificationCapabilityObserver;
@@ -157,19 +163,18 @@
         };
     }
 
-    private class ZoomSeekbarChangeListener implements SeekBar.OnSeekBarChangeListener {
+    private class ZoomSeekbarChangeListener implements
+            SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener {
         @Override
         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-            float scale = progress * A11Y_CHANGE_SCALE_DIFFERENCE + A11Y_SCALE_MIN_VALUE;
-            // Update persisted scale only when scale >= PERSISTED_SCALE_MIN_VALUE const.
-            // We assume if the scale is lower than the PERSISTED_SCALE_MIN_VALUE, there will be
-            // no obvious magnification effect.
-            if (scale >= MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
-                Settings.Secure.putFloatForUser(mContext.getContentResolver(),
-                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale,
-                        UserHandle.USER_CURRENT);
+            // Notify the service to update the magnifier scale only when the progress changed is
+            // triggered by user interaction on seekbar
+            if (fromUser) {
+                final float scale = transformProgressToScale(progress);
+                // We don't need to update the persisted scale when the seekbar progress is
+                // changing. The update should be triggered when the changing is ended.
+                mCallback.onMagnifierScale(scale, /* updatePersistence= */ false);
             }
-            mCallback.onMagnifierScale(scale);
         }
 
         @Override
@@ -181,6 +186,18 @@
         public void onStopTrackingTouch(SeekBar seekBar) {
             // Do nothing
         }
+
+        @Override
+        public void onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control) {
+            // Update the Settings persisted scale only when user interaction with seekbar ends
+            final int progress = seekBar.getProgress();
+            final float scale = transformProgressToScale(progress);
+            mCallback.onMagnifierScale(scale, /* updatePersistence= */ true);
+        }
+
+        private float transformProgressToScale(float progress) {
+            return (progress / (float) mSeekBarMagnitude) + SCALE_MIN_VALUE;
+        }
     }
 
     private final AccessibilityDelegate mPanelDelegate = new AccessibilityDelegate() {
@@ -311,6 +328,14 @@
         mCallback.onSettingsPanelVisibilityChanged(/* shown= */ false);
     }
 
+    public void toggleSettingsPanelVisibility() {
+        if (!mIsVisible) {
+            showSettingPanel();
+        } else {
+            hideSettingPanel();
+        }
+    }
+
     public void showSettingPanel() {
         showSettingPanel(true);
     }
@@ -320,7 +345,13 @@
     }
 
     public void setScaleSeekbar(float scale) {
-        setSeekbarProgress(scale);
+        int index = (int) ((scale - SCALE_MIN_VALUE) * mSeekBarMagnitude);
+        if (index < 0) {
+            index = 0;
+        } else if (index > mZoomSeekbar.getMax()) {
+            index = mZoomSeekbar.getMax();
+        }
+        mZoomSeekbar.setProgress(index);
     }
 
     private void transitToMagnificationMode(int mode) {
@@ -337,6 +368,7 @@
     private void showSettingPanel(boolean resetPosition) {
         if (!mIsVisible) {
             updateUIControlsIfNeeded();
+            setScaleSeekbar(mScale);
             if (resetPosition) {
                 mDraggableWindowBounds.set(getDraggableWindowBounds());
                 mParams.x = mDraggableWindowBounds.right;
@@ -368,22 +400,41 @@
     }
 
     private int getMagnificationMode() {
+        // If current capability is window mode, we would like the default value of the mode to
+        // be WINDOW, otherwise, the default value would be FULLSCREEN.
+        int defaultValue =
+                (getMagnificationCapability() == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)
+                        ? ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
+                        : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+
         return mSecureSettings.getIntForUser(
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
-                ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
+                defaultValue,
                 UserHandle.USER_CURRENT);
     }
 
     private int getMagnificationCapability() {
         return mSecureSettings.getIntForUser(
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
-                ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
+                ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
                 UserHandle.USER_CURRENT);
     }
 
+    /**
+     * Only called from outside to notify the controlling magnifier scale changed
+     *
+     * @param scale The new controlling magnifier scale
+     */
+    public void setMagnificationScale(float scale) {
+        mScale = scale;
+
+        if (isSettingPanelShowing()) {
+            setScaleSeekbar(scale);
+        }
+    }
+
     private void updateUIControlsIfNeeded() {
         int capability = getMagnificationCapability();
-
         int selectedButtonIndex = mLastSelectedButtonIndex;
         switch (capability) {
             case ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW:
@@ -444,14 +495,6 @@
         }
     };
 
-    private void setSeekbarProgress(float scale) {
-        int index = (int) ((scale - A11Y_SCALE_MIN_VALUE) / A11Y_CHANGE_SCALE_DIFFERENCE);
-        if (index < 0) {
-            index = 0;
-        }
-        mZoomSeekbar.setProgress(index);
-    }
-
     void inflateView() {
         mSettingView = (LinearLayout) View.inflate(mContext,
                 R.layout.window_magnification_settings_view, null);
@@ -473,11 +516,11 @@
                 mSettingView.findViewById(R.id.magnifier_horizontal_lock_title);
 
         mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
-        float scale = mSecureSettings.getFloatForUser(
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0,
-                UserHandle.USER_CURRENT);
-        setSeekbarProgress(scale);
-        mZoomSeekbar.setOnSeekBarChangeListener(new ZoomSeekbarChangeListener());
+        mZoomSeekbar.setMax((int) (mZoomSeekbar.getChangeMagnitude()
+                * (SCALE_MAX_VALUE - SCALE_MIN_VALUE)));
+        mSeekBarMagnitude = mZoomSeekbar.getChangeMagnitude();
+        setScaleSeekbar(mScale);
+        mZoomSeekbar.setOnSeekBarWithIconButtonsChangeListener(new ZoomSeekbarChangeListener());
 
         mAllowDiagonalScrollingView =
                 (LinearLayout) mSettingView.findViewById(R.id.magnifier_horizontal_lock_view);
@@ -521,7 +564,6 @@
             // CONFIG_FONT_SCALE: font size change
             // CONFIG_LOCALE: language change
             // CONFIG_DENSITY: display size change
-
             mParams.accessibilityTitle = getAccessibilityWindowTitle(mContext);
 
             boolean showSettingPanelAfterConfigChange = mIsVisible;
@@ -533,16 +575,13 @@
             return;
         }
 
-        if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
-            final Rect previousDraggableBounds = new Rect(mDraggableWindowBounds);
+        if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0
+                || (configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) {
             mDraggableWindowBounds.set(getDraggableWindowBounds());
-            // Keep the Y position with the same height ratio before the window bounds and
-            // draggable bounds are changed.
-            final float windowHeightFraction = (float) (mParams.y - previousDraggableBounds.top)
-                    / previousDraggableBounds.height();
-            mParams.y = (int) (windowHeightFraction * mDraggableWindowBounds.height())
-                    + mDraggableWindowBounds.top;
-            return;
+            // reset the panel position to the right-bottom corner
+            mParams.x = mDraggableWindowBounds.right;
+            mParams.y = mDraggableWindowBounds.bottom;
+            updateButtonViewLayoutIfNeeded();
         }
     }
 
@@ -554,7 +593,8 @@
         mDraggableWindowBounds.set(newBounds);
     }
 
-    private void updateButtonViewLayoutIfNeeded() {
+    @VisibleForTesting
+    void updateButtonViewLayoutIfNeeded() {
         if (mIsVisible) {
             mParams.x = MathUtils.constrain(mParams.x, mDraggableWindowBounds.left,
                     mDraggableWindowBounds.right);
@@ -595,7 +635,7 @@
 
     @VisibleForTesting
     void setDiagonalScrolling(boolean enabled) {
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+        mSecureSettings.putIntForUser(
                 Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, enabled ? 1 : 0,
                 UserHandle.USER_CURRENT);
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
index 3dbff5d..2eee7a6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
@@ -52,8 +52,9 @@
      * Called when set magnification scale.
      *
      * @param scale Magnification scale value.
+     * @param updatePersistence whether the scale should be persisted
      */
-    void onMagnifierScale(float scale);
+    void onMagnifierScale(float scale, boolean updatePersistence);
 
     /**
      * Called when magnification mode changed.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
index e18161d..a25e9a2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
@@ -44,8 +44,9 @@
      *
      * @param displayId The logical display id.
      * @param scale the target scale, or {@link Float#NaN} to leave unchanged
+     * @param updatePersistence whether the scale should be persisted
      */
-    void onPerformScaleAction(int displayId, float scale);
+    void onPerformScaleAction(int displayId, float scale, boolean updatePersistence);
 
     /**
      * Called when the accessibility action is performed.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
index 7f4e7844..225a2f7 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
@@ -25,7 +25,7 @@
 import androidx.dynamicanimation.animation.DynamicAnimation;
 
 import com.android.systemui.R;
-import com.android.wm.shell.bubbles.DismissView;
+import com.android.wm.shell.common.bubbles.DismissView;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 3f41a76..6ba40d6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -61,7 +61,8 @@
 import com.android.internal.util.Preconditions;
 import com.android.systemui.R;
 import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissView;
+import com.android.wm.shell.bubbles.DismissViewUtils;
+import com.android.wm.shell.common.bubbles.DismissView;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
 import java.lang.annotation.Retention;
@@ -184,6 +185,7 @@
         mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
         mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
         mDismissView = new DismissView(context);
+        DismissViewUtils.setup(mDismissView);
         mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
         mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
index e79b3f4..0ef256d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
@@ -26,14 +26,16 @@
 import android.view.LayoutInflater
 import android.widget.Button
 import android.widget.SeekBar
-import android.widget.SeekBar.OnSeekBarChangeListener
 import android.widget.TextView
 import androidx.annotation.MainThread
 import androidx.annotation.WorkerThread
 import com.android.systemui.R
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener.ControlUnitType
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.settings.SecureSettings
@@ -48,6 +50,7 @@
     private val systemSettings: SystemSettings,
     private val secureSettings: SecureSettings,
     private val systemClock: SystemClock,
+    private val userTracker: UserTracker,
     @Main mainHandler: Handler,
     @Background private val backgroundDelayableExecutor: DelayableExecutor
 ) : SystemUIDialog(context) {
@@ -98,32 +101,37 @@
 
         seekBarWithIconButtonsView.setMax((strEntryValues).size - 1)
 
-        val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, 1.0f)
+        val currentScale =
+            systemSettings.getFloatForUser(Settings.System.FONT_SCALE, 1.0f, userTracker.userId)
         lastProgress.set(fontSizeValueToIndex(currentScale))
         seekBarWithIconButtonsView.setProgress(lastProgress.get())
 
-        seekBarWithIconButtonsView.setOnSeekBarChangeListener(
-            object : OnSeekBarChangeListener {
-                var isTrackingTouch = false
-
+        seekBarWithIconButtonsView.setOnSeekBarWithIconButtonsChangeListener(
+            object : OnSeekBarWithIconButtonsChangeListener {
                 override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
                     // Always provide preview configuration for text first when there is a change
                     // in the seekbar progress.
                     createTextPreview(progress)
-
-                    if (!isTrackingTouch) {
-                        // The seekbar progress is changed by icon buttons
-                        changeFontSize(progress, CHANGE_BY_BUTTON_DELAY_MS)
-                    }
                 }
 
                 override fun onStartTrackingTouch(seekBar: SeekBar) {
-                    isTrackingTouch = true
+                    // Do nothing
                 }
 
                 override fun onStopTrackingTouch(seekBar: SeekBar) {
-                    isTrackingTouch = false
-                    changeFontSize(seekBar.progress, CHANGE_BY_SEEKBAR_DELAY_MS)
+                    // Do nothing
+                }
+
+                override fun onUserInteractionFinalized(
+                    seekBar: SeekBar,
+                    @ControlUnitType control: Int
+                ) {
+                    if (control == ControlUnitType.BUTTON) {
+                        // The seekbar progress is changed by icon buttons
+                        changeFontSize(seekBar.progress, CHANGE_BY_BUTTON_DELAY_MS)
+                    } else {
+                        changeFontSize(seekBar.progress, CHANGE_BY_SEEKBAR_DELAY_MS)
+                    }
                 }
             }
         )
@@ -137,6 +145,8 @@
      */
     @MainThread
     fun updateFontScaleDelayed(delayMsFromSource: Long) {
+        doneButton.isEnabled = false
+
         var delayMs = delayMsFromSource
         if (systemClock.elapsedRealtime() - lastUpdateTime < MIN_UPDATE_INTERVAL_MS) {
             delayMs += MIN_UPDATE_INTERVAL_MS
@@ -189,24 +199,36 @@
             title.post {
                 title.setTextAppearance(R.style.TextAppearance_Dialog_Title)
                 doneButton.setTextAppearance(R.style.Widget_Dialog_Button)
+                doneButton.isEnabled = true
             }
         }
     }
 
     @WorkerThread
     fun updateFontScale() {
-        systemSettings.putString(Settings.System.FONT_SCALE, strEntryValues[lastProgress.get()])
+        if (
+            !systemSettings.putStringForUser(
+                Settings.System.FONT_SCALE,
+                strEntryValues[lastProgress.get()],
+                userTracker.userId
+            )
+        ) {
+            title.post { doneButton.isEnabled = true }
+        }
     }
 
     @WorkerThread
     fun updateSecureSettingsIfNeeded() {
         if (
-            secureSettings.getString(Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED) !=
-                ON
-        ) {
-            secureSettings.putString(
+            secureSettings.getStringForUser(
                 Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
-                ON
+                userTracker.userId
+            ) != ON
+        ) {
+            secureSettings.putStringForUser(
+                Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
+                ON,
+                userTracker.userId
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 665a398..590056f 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -23,6 +23,8 @@
 import android.util.Log;
 
 import com.android.internal.app.AssistUtils;
+import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.app.IVisualQueryRecognitionStatusListener;
 import com.android.internal.app.IVoiceInteractionSessionListener;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -39,10 +41,13 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.settings.SecureSettings;
 
-import javax.inject.Inject;
-
 import dagger.Lazy;
 
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
 /**
  * Class to manage everything related to assist in SystemUI.
  */
@@ -78,6 +83,18 @@
         void hide();
     }
 
+    /**
+     * An interface for a listener that receives notification that visual query attention has
+     * either been gained or lost.
+     */
+    public interface VisualQueryAttentionListener {
+        /** Called when visual query attention has been gained. */
+        void onAttentionGained();
+
+        /** Called when visual query attention has been lost. */
+        void onAttentionLost();
+    }
+
     private static final String TAG = "AssistManager";
 
     // Note that VERBOSE logging may leak PII (e.g. transcription contents).
@@ -105,6 +122,8 @@
             AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
     public static final int INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS =
             AssistUtils.INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS;
+    public static final int INVOCATION_TYPE_NAV_HANDLE_LONG_PRESS =
+            AssistUtils.INVOCATION_TYPE_NAV_HANDLE_LONG_PRESS;
 
     public static final int DISMISS_REASON_INVOCATION_CANCELLED = 1;
     public static final int DISMISS_REASON_TAP = 2;
@@ -125,6 +144,23 @@
     private final SecureSettings mSecureSettings;
 
     private final DeviceProvisionedController mDeviceProvisionedController;
+
+    private final List<VisualQueryAttentionListener> mVisualQueryAttentionListeners =
+            new ArrayList<>();
+
+    private final IVisualQueryDetectionAttentionListener mVisualQueryDetectionAttentionListener =
+            new IVisualQueryDetectionAttentionListener.Stub() {
+        @Override
+        public void onAttentionGained() {
+            mVisualQueryAttentionListeners.forEach(VisualQueryAttentionListener::onAttentionGained);
+        }
+
+        @Override
+        public void onAttentionLost() {
+            mVisualQueryAttentionListeners.forEach(VisualQueryAttentionListener::onAttentionLost);
+        }
+    };
+
     private final CommandQueue mCommandQueue;
     protected final AssistUtils mAssistUtils;
 
@@ -155,6 +191,7 @@
         mSecureSettings = secureSettings;
 
         registerVoiceInteractionSessionListener();
+        registerVisualQueryRecognitionStatusListener();
 
         mUiController = defaultUiController;
 
@@ -264,6 +301,24 @@
         mAssistUtils.hideCurrentSession();
     }
 
+    /**
+     * Add the given {@link VisualQueryAttentionListener} to the list of listeners awaiting
+     * notification of gaining/losing visual query attention.
+     */
+    public void addVisualQueryAttentionListener(VisualQueryAttentionListener listener) {
+        if (!mVisualQueryAttentionListeners.contains(listener)) {
+            mVisualQueryAttentionListeners.add(listener);
+        }
+    }
+
+    /**
+     * Remove the given {@link VisualQueryAttentionListener} from the list of listeners awaiting
+     * notification of gaining/losing visual query attention.
+     */
+    public void removeVisualQueryAttentionListener(VisualQueryAttentionListener listener) {
+        mVisualQueryAttentionListeners.remove(listener);
+    }
+
     private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
             boolean isService) {
         if (isService) {
@@ -324,6 +379,27 @@
                 null, null);
     }
 
+    private void registerVisualQueryRecognitionStatusListener() {
+        if (!mContext.getResources()
+                .getBoolean(R.bool.config_enableVisualQueryAttentionDetection)) {
+            return;
+        }
+
+        mAssistUtils.subscribeVisualQueryRecognitionStatus(
+                new IVisualQueryRecognitionStatusListener.Stub() {
+                    @Override
+                    public void onStartPerceiving() {
+                        mAssistUtils.enableVisualQueryDetection(
+                                mVisualQueryDetectionAttentionListener);
+                    }
+
+                    @Override
+                    public void onStopPerceiving() {
+                        mAssistUtils.disableVisualQueryDetection();
+                    }
+                });
+    }
+
     public void launchVoiceAssistFromKeyguard() {
         mAssistUtils.launchVoiceAssistFromKeyguard();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt b/packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt
index 7c394a6..6128d91 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/AuthenticationModule.kt
@@ -16,8 +16,11 @@
 
 package com.android.systemui.authentication
 
+import com.android.keyguard.KeyguardSecurityModel
 import com.android.systemui.authentication.data.repository.AuthenticationRepositoryModule
 import dagger.Module
+import dagger.Provides
+import java.util.function.Function
 
 @Module(
     includes =
@@ -25,4 +28,12 @@
             AuthenticationRepositoryModule::class,
         ],
 )
-object AuthenticationModule
+object AuthenticationModule {
+
+    @Provides
+    fun getSecurityMode(
+        model: KeyguardSecurityModel,
+    ): Function<Int, KeyguardSecurityModel.SecurityMode> {
+        return Function { userId -> model.getSecurityMode(userId) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt
new file mode 100644
index 0000000..6d23b11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.authentication.data.model
+
+/** Enumerates all known authentication methods. */
+sealed class AuthenticationMethodModel(
+    /**
+     * Whether the authentication method is considered to be "secure".
+     *
+     * "Secure" authentication methods require authentication to unlock the device. Non-secure auth
+     * methods simply require user dismissal.
+     */
+    open val isSecure: Boolean,
+) {
+    /** There is no authentication method on the device. We shouldn't even show the lock screen. */
+    object None : AuthenticationMethodModel(isSecure = false)
+
+    object Pin : AuthenticationMethodModel(isSecure = true)
+
+    object Password : AuthenticationMethodModel(isSecure = true)
+
+    object Pattern : AuthenticationMethodModel(isSecure = true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index c684dc5..8d1fc5d 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -14,15 +14,48 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.authentication.data.repository
 
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import android.app.admin.DevicePolicyManager
+import android.content.IntentFilter
+import android.os.UserHandle
+import com.android.internal.widget.LockPatternChecker
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockscreenCredential
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationResultModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.time.SystemClock
 import dagger.Binds
 import dagger.Module
+import java.util.function.Function
 import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /** Defines interface for classes that can access authentication-related application state. */
 interface AuthenticationRepository {
@@ -31,77 +64,309 @@
      * Whether the device is unlocked.
      *
      * A device that is not yet unlocked requires unlocking by completing an authentication
-     * challenge according to the current authentication method.
-     *
-     * Note that this state has no real bearing on whether the lockscreen is showing or dismissed.
+     * challenge according to the current authentication method, unless in cases when the current
+     * authentication method is not "secure" (for example, None); in such cases, the value of this
+     * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed
+     * by the user to proceed.
      */
     val isUnlocked: StateFlow<Boolean>
 
     /**
+     * Whether the auto confirm feature is enabled for the currently-selected user.
+     *
+     * Note that the length of the PIN is also important to take into consideration, please see
+     * [hintedPinLength].
+     */
+    val isAutoConfirmEnabled: StateFlow<Boolean>
+
+    /**
+     * The exact length a PIN should be for us to enable PIN length hinting.
+     *
+     * A PIN that's shorter or longer than this is not eligible for the UI to render hints showing
+     * how many digits the current PIN is, even if [isAutoConfirmEnabled] is enabled.
+     *
+     * Note that PIN length hinting is only available if the PIN auto confirmation feature is
+     * available.
+     */
+    val hintedPinLength: Int
+
+    /** Whether the pattern should be visible for the currently-selected user. */
+    val isPatternVisible: StateFlow<Boolean>
+
+    /** The current throttling state, as cached via [setThrottling]. */
+    val throttling: StateFlow<AuthenticationThrottlingModel>
+
+    /**
      * The currently-configured authentication method. This determines how the authentication
-     * challenge is completed in order to unlock an otherwise locked device.
+     * challenge needs to be completed in order to unlock an otherwise locked device.
+     *
+     * Note: there may be other ways to unlock the device that "bypass" the need for this
+     * authentication challenge (notably, biometrics like fingerprint or face unlock).
+     *
+     * Note: by design, this is a [Flow] and not a [StateFlow]; a consumer who wishes to get a
+     * snapshot of the current authentication method without establishing a collector of the flow
+     * can do so by invoking [getAuthenticationMethod].
      */
-    val authenticationMethod: StateFlow<AuthenticationMethodModel>
+    val authenticationMethod: Flow<AuthenticationMethodModel>
 
     /**
-     * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
-     * dismisses once the authentication challenge is completed. For example, completing a biometric
-     * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
-     * lock screen.
+     * Returns the currently-configured authentication method. This determines how the
+     * authentication challenge needs to be completed in order to unlock an otherwise locked device.
+     *
+     * Note: there may be other ways to unlock the device that "bypass" the need for this
+     * authentication challenge (notably, biometrics like fingerprint or face unlock).
+     *
+     * Note: by design, this is offered as a convenience method alongside [authenticationMethod].
+     * The flow should be used for code that wishes to stay up-to-date its logic as the
+     * authentication changes over time and this method should be used for simple code that only
+     * needs to check the current value.
      */
-    val isBypassEnabled: StateFlow<Boolean>
+    suspend fun getAuthenticationMethod(): AuthenticationMethodModel
+
+    /** Returns the length of the PIN or `0` if the current auth method is not PIN. */
+    suspend fun getPinLength(): Int
 
     /**
-     * Number of consecutively failed authentication attempts. This resets to `0` when
-     * authentication succeeds.
+     * Returns whether the lockscreen is enabled.
+     *
+     * When the lockscreen is not enabled, it shouldn't show in cases when the authentication method
+     * is considered not secure (for example, "swipe" is considered to be "none").
      */
-    val failedAuthenticationAttempts: StateFlow<Int>
+    suspend fun isLockscreenEnabled(): Boolean
 
-    /** See [isUnlocked]. */
-    fun setUnlocked(isUnlocked: Boolean)
+    /** Reports an authentication attempt. */
+    suspend fun reportAuthenticationAttempt(isSuccessful: Boolean)
 
-    /** See [authenticationMethod]. */
-    fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel)
+    /** Returns the current number of failed authentication attempts. */
+    suspend fun getFailedAuthenticationAttemptCount(): Int
 
-    /** See [isBypassEnabled]. */
-    fun setBypassEnabled(isBypassEnabled: Boolean)
+    /**
+     * Returns the timestamp for when the current throttling will end, allowing the user to attempt
+     * authentication again.
+     *
+     * Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime].
+     */
+    suspend fun getThrottlingEndTimestamp(): Long
 
-    /** See [failedAuthenticationAttempts]. */
-    fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int)
+    /** Sets the cached throttling state, updating the [throttling] flow. */
+    fun setThrottling(throttlingModel: AuthenticationThrottlingModel)
+
+    /**
+     * Sets the throttling timeout duration (time during which the user should not be allowed to
+     * attempt authentication).
+     */
+    suspend fun setThrottleDuration(durationMs: Int)
+
+    /**
+     * Checks the given [LockscreenCredential] to see if it's correct, returning an
+     * [AuthenticationResultModel] representing what happened.
+     */
+    suspend fun checkCredential(credential: LockscreenCredential): AuthenticationResultModel
 }
 
-class AuthenticationRepositoryImpl @Inject constructor() : AuthenticationRepository {
-    // TODO(b/280883900): get data from real data sources in SysUI.
+class AuthenticationRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val userRepository: UserRepository,
+    keyguardRepository: KeyguardRepository,
+    private val lockPatternUtils: LockPatternUtils,
+    broadcastDispatcher: BroadcastDispatcher,
+) : AuthenticationRepository {
 
-    private val _isUnlocked = MutableStateFlow(false)
-    override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
+    override val isUnlocked = keyguardRepository.isKeyguardUnlocked
 
-    private val _authenticationMethod =
-        MutableStateFlow<AuthenticationMethodModel>(AuthenticationMethodModel.PIN(1234))
-    override val authenticationMethod: StateFlow<AuthenticationMethodModel> =
-        _authenticationMethod.asStateFlow()
-
-    private val _isBypassEnabled = MutableStateFlow(false)
-    override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow()
-
-    private val _failedAuthenticationAttempts = MutableStateFlow(0)
-    override val failedAuthenticationAttempts: StateFlow<Int> =
-        _failedAuthenticationAttempts.asStateFlow()
-
-    override fun setUnlocked(isUnlocked: Boolean) {
-        _isUnlocked.value = isUnlocked
+    override suspend fun isLockscreenEnabled(): Boolean {
+        return withContext(backgroundDispatcher) {
+            val selectedUserId = userRepository.selectedUserId
+            !lockPatternUtils.isLockScreenDisabled(selectedUserId)
+        }
     }
 
-    override fun setBypassEnabled(isBypassEnabled: Boolean) {
-        _isBypassEnabled.value = isBypassEnabled
+    override val isAutoConfirmEnabled: StateFlow<Boolean> =
+        refreshingFlow(
+            initialValue = false,
+            getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled,
+        )
+
+    override val hintedPinLength: Int = 6
+
+    override val isPatternVisible: StateFlow<Boolean> =
+        refreshingFlow(
+            initialValue = true,
+            getFreshValue = lockPatternUtils::isVisiblePatternEnabled,
+        )
+
+    private val _throttling = MutableStateFlow(AuthenticationThrottlingModel())
+    override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow()
+
+    private val UserRepository.selectedUserId: Int
+        get() = getSelectedUserInfo().id
+
+    override val authenticationMethod: Flow<AuthenticationMethodModel> =
+        userRepository.selectedUserInfo
+            .map { it.id }
+            .distinctUntilChanged()
+            .flatMapLatest { selectedUserId ->
+                broadcastDispatcher
+                    .broadcastFlow(
+                        filter =
+                            IntentFilter(
+                                DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
+                            ),
+                        user = UserHandle.of(selectedUserId),
+                    )
+                    .onStart { emit(Unit) }
+                    .map { selectedUserId }
+            }
+            .map { selectedUserId ->
+                withContext(backgroundDispatcher) {
+                    blockingAuthenticationMethodInternal(selectedUserId)
+                }
+            }
+
+    override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
+        return withContext(backgroundDispatcher) {
+            blockingAuthenticationMethodInternal(userRepository.selectedUserId)
+        }
     }
 
-    override fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
-        _authenticationMethod.value = authenticationMethod
+    override suspend fun getPinLength(): Int {
+        return withContext(backgroundDispatcher) {
+            val selectedUserId = userRepository.selectedUserId
+            lockPatternUtils.getPinLength(selectedUserId)
+        }
     }
 
-    override fun setFailedAuthenticationAttempts(failedAuthenticationAttempts: Int) {
-        _failedAuthenticationAttempts.value = failedAuthenticationAttempts
+    override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
+        val selectedUserId = userRepository.selectedUserId
+        withContext(backgroundDispatcher) {
+            if (isSuccessful) {
+                lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
+            } else {
+                lockPatternUtils.reportFailedPasswordAttempt(selectedUserId)
+            }
+        }
+    }
+
+    override suspend fun getFailedAuthenticationAttemptCount(): Int {
+        return withContext(backgroundDispatcher) {
+            val selectedUserId = userRepository.selectedUserId
+            lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
+        }
+    }
+
+    override suspend fun getThrottlingEndTimestamp(): Long {
+        return withContext(backgroundDispatcher) {
+            val selectedUserId = userRepository.selectedUserId
+            lockPatternUtils.getLockoutAttemptDeadline(selectedUserId)
+        }
+    }
+
+    override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) {
+        _throttling.value = throttlingModel
+    }
+
+    override suspend fun setThrottleDuration(durationMs: Int) {
+        withContext(backgroundDispatcher) {
+            lockPatternUtils.setLockoutAttemptDeadline(
+                userRepository.selectedUserId,
+                durationMs,
+            )
+        }
+    }
+
+    override suspend fun checkCredential(
+        credential: LockscreenCredential
+    ): AuthenticationResultModel {
+        return suspendCoroutine { continuation ->
+            LockPatternChecker.checkCredential(
+                lockPatternUtils,
+                credential,
+                userRepository.selectedUserId,
+                object : LockPatternChecker.OnCheckCallback {
+                    override fun onChecked(matched: Boolean, throttleTimeoutMs: Int) {
+                        continuation.resume(
+                            AuthenticationResultModel(
+                                isSuccessful = matched,
+                                throttleDurationMs = throttleTimeoutMs,
+                            )
+                        )
+                    }
+
+                    override fun onCancelled() {
+                        continuation.resume(AuthenticationResultModel(isSuccessful = false))
+                    }
+
+                    override fun onEarlyMatched() = Unit
+                }
+            )
+        }
+    }
+
+    /**
+     * Returns a [StateFlow] that's automatically kept fresh. The passed-in [getFreshValue] is
+     * invoked on a background thread every time the selected user is changed and every time a new
+     * downstream subscriber is added to the flow.
+     *
+     * Initially, the flow will emit [initialValue] while it refreshes itself in the background by
+     * invoking the [getFreshValue] function and emitting the fresh value when that's done.
+     *
+     * Every time the selected user is changed, the flow will re-invoke [getFreshValue] and emit the
+     * new value.
+     *
+     * Every time a new downstream subscriber is added to the flow it first receives the latest
+     * cached value that's either the [initialValue] or the latest previously fetched value. In
+     * addition, adding a new downstream subscriber also triggers another [getFreshValue] call and a
+     * subsequent emission of that newest value.
+     */
+    private fun <T> refreshingFlow(
+        initialValue: T,
+        getFreshValue: suspend (selectedUserId: Int) -> T,
+    ): StateFlow<T> {
+        val flow = MutableStateFlow(initialValue)
+        applicationScope.launch {
+            combine(
+                    // Emits a value initially and every time the selected user is changed.
+                    userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged(),
+                    // Emits a value only when the number of downstream subscribers of this flow
+                    // increases.
+                    flow.subscriptionCount.pairwise(initialValue = 0).filter { (previous, current)
+                        ->
+                        current > previous
+                    },
+                ) { selectedUserId, _ ->
+                    selectedUserId
+                }
+                .collect { selectedUserId ->
+                    flow.value = withContext(backgroundDispatcher) { getFreshValue(selectedUserId) }
+                }
+        }
+
+        return flow.asStateFlow()
+    }
+
+    /**
+     * Returns the authentication method for the given user ID.
+     *
+     * WARNING: this is actually a blocking IPC/"binder" call that's expensive to do on the main
+     * thread. We keep it not marked as `suspend` because we want to be able to run this without a
+     * `runBlocking` which has a ton of performance/blocking problems.
+     */
+    private fun blockingAuthenticationMethodInternal(
+        userId: Int,
+    ): AuthenticationMethodModel {
+        return when (getSecurityMode.apply(userId)) {
+            KeyguardSecurityModel.SecurityMode.PIN,
+            KeyguardSecurityModel.SecurityMode.SimPin,
+            KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin
+            KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password
+            KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
+            KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None
+            KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!")
+        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 3984627..ecd7bae 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -16,200 +16,367 @@
 
 package com.android.systemui.authentication.domain.interactor
 
+import com.android.internal.widget.LockPatternView
+import com.android.internal.widget.LockscreenCredential
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
+import kotlin.math.max
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /** Hosts application business logic related to authentication. */
 @SysUISingleton
 class AuthenticationInteractor
 @Inject
 constructor(
-    @Application applicationScope: CoroutineScope,
+    @Application private val applicationScope: CoroutineScope,
     private val repository: AuthenticationRepository,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val userRepository: UserRepository,
+    private val keyguardRepository: KeyguardRepository,
+    sceneInteractor: SceneInteractor,
+    private val clock: SystemClock,
 ) {
     /**
      * The currently-configured authentication method. This determines how the authentication
-     * challenge is completed in order to unlock an otherwise locked device.
+     * challenge needs to be completed in order to unlock an otherwise locked device.
+     *
+     * Note: there may be other ways to unlock the device that "bypass" the need for this
+     * authentication challenge (notably, biometrics like fingerprint or face unlock).
+     *
+     * Note: by design, this is a [Flow] and not a [StateFlow]; a consumer who wishes to get a
+     * snapshot of the current authentication method without establishing a collector of the flow
+     * can do so by invoking [getAuthenticationMethod].
+     *
+     * Note: this layer adds the synthetic authentication method of "swipe" which is special. When
+     * the current authentication method is "swipe", the user does not need to complete any
+     * authentication challenge to unlock the device; they just need to dismiss the lockscreen to
+     * get past it. This also means that the value of [isUnlocked] remains `false` even when the
+     * lockscreen is showing and still needs to be dismissed by the user to proceed.
      */
-    val authenticationMethod: StateFlow<AuthenticationMethodModel> = repository.authenticationMethod
+    val authenticationMethod: Flow<DomainLayerAuthenticationMethodModel> =
+        repository.authenticationMethod.map { rawModel -> rawModel.toDomainLayer() }
 
     /**
      * Whether the device is unlocked.
      *
      * A device that is not yet unlocked requires unlocking by completing an authentication
-     * challenge according to the current authentication method.
-     *
-     * Note that this state has no real bearing on whether the lock screen is showing or dismissed.
+     * challenge according to the current authentication method, unless in cases when the current
+     * authentication method is not "secure" (for example, None and Swipe); in such cases, the value
+     * of this flow will always be `true`, even if the lockscreen is showing and still needs to be
+     * dismissed by the user to proceed.
      */
     val isUnlocked: StateFlow<Boolean> =
-        combine(authenticationMethod, repository.isUnlocked) { authMethod, isUnlocked ->
-                isUnlockedWithAuthMethod(
-                    isUnlocked = isUnlocked,
-                    authMethod = authMethod,
-                )
+        combine(
+                repository.isUnlocked,
+                authenticationMethod,
+            ) { isUnlocked, authenticationMethod ->
+                !authenticationMethod.isSecure || isUnlocked
             }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue =
-                    isUnlockedWithAuthMethod(
-                        isUnlocked = repository.isUnlocked.value,
-                        authMethod = repository.authenticationMethod.value,
-                    )
+                initialValue = false,
             )
 
     /**
+     * Whether the lockscreen has been dismissed (by any method). This can be false even when the
+     * device is unlocked, e.g. when swipe to unlock is enabled.
+     *
+     * Note:
+     * - `false` doesn't mean the lockscreen is visible (it may be occluded or covered by other UI).
+     * - `true` doesn't mean the lockscreen is invisible (since this state changes before the
+     *   transition occurs).
+     */
+    val isLockscreenDismissed: StateFlow<Boolean> =
+        sceneInteractor.desiredScene
+            .map { it.key }
+            .filter { currentScene ->
+                currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen
+            }
+            .map { it == SceneKey.Gone }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /**
+     * Whether it's currently possible to swipe up to dismiss the lockscreen without requiring
+     * authentication. This returns false whenever the lockscreen has been dismissed.
+     *
+     * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other
+     * UI.
+     */
+    val canSwipeToDismiss =
+        combine(authenticationMethod, isLockscreenDismissed) {
+                authenticationMethod,
+                isLockscreenDismissed ->
+                authenticationMethod is DomainLayerAuthenticationMethodModel.Swipe &&
+                    !isLockscreenDismissed
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
+    val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling
+
+    /**
+     * Whether currently throttled and the user has to wait before being able to try another
+     * authentication attempt.
+     */
+    val isThrottled: StateFlow<Boolean> =
+        throttling
+            .map { it.remainingMs > 0 }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = throttling.value.remainingMs > 0,
+            )
+
+    /** The length of the hinted PIN, or `null` if pin length hint should not be shown. */
+    val hintedPinLength: StateFlow<Int?> =
+        repository.isAutoConfirmEnabled
+            .map { isAutoConfirmEnabled ->
+                repository.getPinLength().takeIf {
+                    isAutoConfirmEnabled && it == repository.hintedPinLength
+                }
+            }
+            .stateIn(
+                scope = applicationScope,
+                // Make sure this is kept as WhileSubscribed or we can run into a bug where the
+                // downstream continues to receive old/stale/cached values.
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = null,
+            )
+
+    /** Whether the auto confirm feature is enabled for the currently-selected user. */
+    val isAutoConfirmEnabled: StateFlow<Boolean> = repository.isAutoConfirmEnabled
+
+    /** Whether the pattern should be visible for the currently-selected user. */
+    val isPatternVisible: StateFlow<Boolean> = repository.isPatternVisible
+
+    private var throttlingCountdownJob: Job? = null
+
+    init {
+        applicationScope.launch {
+            userRepository.selectedUserInfo
+                .map { it.id }
+                .distinctUntilChanged()
+                .collect { onSelectedUserChanged() }
+        }
+    }
+
+    /**
+     * Returns the currently-configured authentication method. This determines how the
+     * authentication challenge needs to be completed in order to unlock an otherwise locked device.
+     *
+     * Note: there may be other ways to unlock the device that "bypass" the need for this
+     * authentication challenge (notably, biometrics like fingerprint or face unlock).
+     *
+     * Note: by design, this is offered as a convenience method alongside [authenticationMethod].
+     * The flow should be used for code that wishes to stay up-to-date its logic as the
+     * authentication changes over time and this method should be used for simple code that only
+     * needs to check the current value.
+     *
+     * Note: this layer adds the synthetic authentication method of "swipe" which is special. When
+     * the current authentication method is "swipe", the user does not need to complete any
+     * authentication challenge to unlock the device; they just need to dismiss the lockscreen to
+     * get past it. This also means that the value of [isUnlocked] remains `false` even when the
+     * lockscreen is showing and still needs to be dismissed by the user to proceed.
+     */
+    suspend fun getAuthenticationMethod(): DomainLayerAuthenticationMethodModel {
+        return repository.getAuthenticationMethod().toDomainLayer()
+    }
+
+    /**
+     * Returns `true` if the device currently requires authentication before content can be viewed;
+     * `false` if content can be displayed without unlocking first.
+     */
+    suspend fun isAuthenticationRequired(): Boolean {
+        return !isUnlocked.value && getAuthenticationMethod().isSecure
+    }
+
+    /**
      * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
      * dismisses once the authentication challenge is completed. For example, completing a biometric
      * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
      * lock screen.
      */
-    val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
-
-    /**
-     * Number of consecutively failed authentication attempts. This resets to `0` when
-     * authentication succeeds.
-     */
-    val failedAuthenticationAttempts: StateFlow<Int> = repository.failedAuthenticationAttempts
-
-    init {
-        // UNLOCKS WHEN AUTH METHOD REMOVED.
-        //
-        // Unlocks the device if the auth method becomes None.
-        applicationScope.launch {
-            repository.authenticationMethod.collect {
-                if (it is AuthenticationMethodModel.None) {
-                    unlockDevice()
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns `true` if the device currently requires authentication before content can be viewed;
-     * `false` if content can be displayed without unlocking first.
-     */
-    fun isAuthenticationRequired(): Boolean {
-        return !isUnlocked.value && authenticationMethod.value.isSecure
-    }
-
-    /**
-     * Unlocks the device, assuming that the authentication challenge has been completed
-     * successfully.
-     */
-    fun unlockDevice() {
-        repository.setUnlocked(true)
-    }
-
-    /**
-     * Locks the device. From now on, the device will remain locked until [authenticate] is called
-     * with the correct input.
-     */
-    fun lockDevice() {
-        repository.setUnlocked(false)
+    fun isBypassEnabled(): Boolean {
+        return keyguardRepository.isBypassEnabled()
     }
 
     /**
      * Attempts to authenticate the user and unlock the device.
      *
+     * If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method
+     * supports auto-confirming, and the input's length is at least the code's length. Otherwise,
+     * `null` is returned.
+     *
      * @param input The input from the user to try to authenticate with. This can be a list of
      *   different things, based on the current authentication method.
-     * @return `true` if the authentication succeeded and the device is now unlocked; `false`
-     *   otherwise.
+     * @param tryAutoConfirm `true` if called while the user inputs the code, without an explicit
+     *   request to validate.
+     * @return `true` if the authentication succeeded and the device is now unlocked; `false` when
+     *   authentication failed, `null` if the check was not performed.
      */
-    fun authenticate(input: List<Any>): Boolean {
-        val isSuccessful =
-            when (val authMethod = this.authenticationMethod.value) {
-                is AuthenticationMethodModel.PIN -> input.asCode() == authMethod.code
-                is AuthenticationMethodModel.Password -> input.asPassword() == authMethod.password
-                is AuthenticationMethodModel.Pattern -> input.asPattern() == authMethod.coordinates
-                else -> true
-            }
+    suspend fun authenticate(input: List<Any>, tryAutoConfirm: Boolean = false): Boolean? {
+        if (input.isEmpty()) {
+            throw IllegalArgumentException("Input was empty!")
+        }
 
-        if (isSuccessful) {
-            repository.setFailedAuthenticationAttempts(0)
-            repository.setUnlocked(true)
-        } else {
-            repository.setFailedAuthenticationAttempts(
-                repository.failedAuthenticationAttempts.value + 1
+        val skipCheck =
+            when {
+                // We're being throttled, the UI layer should not have called this; skip the
+                // attempt.
+                isThrottled.value -> true
+                // Auto-confirm attempt when the feature is not enabled; skip the attempt.
+                tryAutoConfirm && !isAutoConfirmEnabled.value -> true
+                // Auto-confirm should skip the attempt if the pin entered is too short.
+                tryAutoConfirm && input.size < repository.getPinLength() -> true
+                else -> false
+            }
+        if (skipCheck) {
+            return null
+        }
+
+        // Attempt to authenticate:
+        val authMethod = getAuthenticationMethod()
+        val credential = authMethod.createCredential(input) ?: return null
+        val authenticationResult = repository.checkCredential(credential)
+        credential.zeroize()
+
+        if (authenticationResult.isSuccessful || !tryAutoConfirm) {
+            repository.reportAuthenticationAttempt(
+                isSuccessful = authenticationResult.isSuccessful,
             )
         }
 
-        return isSuccessful
-    }
-
-    /** Triggers a biometric-powered unlock of the device. */
-    fun biometricUnlock() {
-        // TODO(b/280883900): only allow this if the biometric is enabled and there's a match.
-        repository.setUnlocked(true)
-    }
-
-    /** See [authenticationMethod]. */
-    fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
-        repository.setAuthenticationMethod(authenticationMethod)
-    }
-
-    /** See [isBypassEnabled]. */
-    fun toggleBypassEnabled() {
-        repository.setBypassEnabled(!repository.isBypassEnabled.value)
-    }
-
-    companion object {
-        private fun isUnlockedWithAuthMethod(
-            isUnlocked: Boolean,
-            authMethod: AuthenticationMethodModel,
-        ): Boolean {
-            return if (authMethod is AuthenticationMethodModel.None) {
-                true
-            } else {
-                isUnlocked
-            }
+        // Check if we need to throttle and, if so, kick off the throttle countdown:
+        if (!authenticationResult.isSuccessful && authenticationResult.throttleDurationMs > 0) {
+            repository.setThrottleDuration(
+                durationMs = authenticationResult.throttleDurationMs,
+            )
+            startThrottlingCountdown()
         }
 
-        /**
-         * Returns a PIN code from the given list. It's assumed the given list elements are all
-         * [Int].
-         */
-        private fun List<Any>.asCode(): Int? {
-            if (isEmpty()) {
-                return null
-            }
-
-            var code = 0
-            map { it as Int }.forEach { integer -> code = code * 10 + integer }
-
-            return code
+        if (authenticationResult.isSuccessful) {
+            // Since authentication succeeded, we should refresh throttling to make sure that our
+            // state is completely reflecting the upstream source of truth.
+            refreshThrottling()
         }
 
-        /**
-         * Returns a password from the given list. It's assumed the given list elements are all
-         * [Char].
-         */
-        private fun List<Any>.asPassword(): String {
-            val anyList = this
-            return buildString { anyList.forEach { append(it as Char) } }
-        }
+        return authenticationResult.isSuccessful
+    }
 
-        /**
-         * Returns a list of [AuthenticationMethodModel.Pattern.PatternCoordinate] from the given
-         * list. It's assumed the given list elements are all
-         * [AuthenticationMethodModel.Pattern.PatternCoordinate].
-         */
-        private fun List<Any>.asPattern():
-            List<AuthenticationMethodModel.Pattern.PatternCoordinate> {
-            val anyList = this
-            return buildList {
-                anyList.forEach { add(it as AuthenticationMethodModel.Pattern.PatternCoordinate) }
+    /** Starts refreshing the throttling state every second. */
+    private suspend fun startThrottlingCountdown() {
+        cancelCountdown()
+        throttlingCountdownJob =
+            applicationScope.launch {
+                while (refreshThrottling() > 0) {
+                    delay(1.seconds.inWholeMilliseconds)
+                }
             }
+    }
+
+    /** Cancels any throttling state countdown started in [startThrottlingCountdown]. */
+    private fun cancelCountdown() {
+        throttlingCountdownJob?.cancel()
+        throttlingCountdownJob = null
+    }
+
+    /** Notifies that the currently-selected user has changed. */
+    private suspend fun onSelectedUserChanged() {
+        cancelCountdown()
+        if (refreshThrottling() > 0) {
+            startThrottlingCountdown()
+        }
+    }
+
+    /**
+     * Refreshes the throttling state, hydrating the repository with the latest state.
+     *
+     * @return The remaining time for the current throttling countdown, in milliseconds or `0` if
+     *   not being throttled.
+     */
+    private suspend fun refreshThrottling(): Long {
+        return withContext(backgroundDispatcher) {
+            val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() }
+            val deadline = async { repository.getThrottlingEndTimestamp() }
+            val remainingMs = max(0, deadline.await() - clock.elapsedRealtime())
+            repository.setThrottling(
+                AuthenticationThrottlingModel(
+                    failedAttemptCount = failedAttemptCount.await(),
+                    remainingMs = remainingMs.toInt(),
+                ),
+            )
+            remainingMs
+        }
+    }
+
+    private fun DomainLayerAuthenticationMethodModel.createCredential(
+        input: List<Any>
+    ): LockscreenCredential? {
+        return when (this) {
+            is DomainLayerAuthenticationMethodModel.Pin ->
+                LockscreenCredential.createPin(input.joinToString(""))
+            is DomainLayerAuthenticationMethodModel.Password ->
+                LockscreenCredential.createPassword(input.joinToString(""))
+            is DomainLayerAuthenticationMethodModel.Pattern ->
+                LockscreenCredential.createPattern(
+                    input
+                        .map { it as AuthenticationPatternCoordinate }
+                        .map { LockPatternView.Cell.of(it.y, it.x) }
+                )
+            else -> null
+        }
+    }
+
+    private suspend fun DataLayerAuthenticationMethodModel.toDomainLayer():
+        DomainLayerAuthenticationMethodModel {
+        return when (this) {
+            is DataLayerAuthenticationMethodModel.None ->
+                if (repository.isLockscreenEnabled()) {
+                    DomainLayerAuthenticationMethodModel.Swipe
+                } else {
+                    DomainLayerAuthenticationMethodModel.None
+                }
+            is DataLayerAuthenticationMethodModel.Pin -> DomainLayerAuthenticationMethodModel.Pin
+            is DataLayerAuthenticationMethodModel.Password ->
+                DomainLayerAuthenticationMethodModel.Password
+            is DataLayerAuthenticationMethodModel.Pattern ->
+                DomainLayerAuthenticationMethodModel.Pattern
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt
new file mode 100644
index 0000000..d7e6099
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.authentication.domain.model
+
+/** Enumerates all known authentication methods. */
+sealed class AuthenticationMethodModel(
+    /**
+     * Whether the authentication method is considered to be "secure".
+     *
+     * "Secure" authentication methods require authentication to unlock the device. Non-secure auth
+     * methods simply require user dismissal.
+     */
+    open val isSecure: Boolean,
+) {
+    /** There is no authentication method on the device. We shouldn't even show the lock screen. */
+    object None : AuthenticationMethodModel(isSecure = false)
+
+    /** The most basic authentication method. The lock screen can be swiped away when displayed. */
+    object Swipe : AuthenticationMethodModel(isSecure = false)
+
+    object Pin : AuthenticationMethodModel(isSecure = true)
+
+    object Password : AuthenticationMethodModel(isSecure = true)
+
+    object Pattern : AuthenticationMethodModel(isSecure = true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
deleted file mode 100644
index 6f008c3..0000000
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.authentication.shared.model
-
-/** Enumerates all known authentication methods. */
-sealed class AuthenticationMethodModel(
-    /**
-     * Whether the authentication method is considered to be "secure".
-     *
-     * "Secure" authentication methods require authentication to unlock the device. Non-secure auth
-     * methods simply require user dismissal.
-     */
-    open val isSecure: Boolean,
-) {
-    /** There is no authentication method on the device. We shouldn't even show the lock screen. */
-    object None : AuthenticationMethodModel(isSecure = false)
-
-    /** The most basic authentication method. The lock screen can be swiped away when displayed. */
-    object Swipe : AuthenticationMethodModel(isSecure = false)
-
-    data class PIN(val code: Int) : AuthenticationMethodModel(isSecure = true)
-
-    data class Password(val password: String) : AuthenticationMethodModel(isSecure = true)
-
-    data class Pattern(
-        val coordinates: List<PatternCoordinate>,
-        val isPatternVisible: Boolean = true,
-    ) : AuthenticationMethodModel(isSecure = true) {
-
-        data class PatternCoordinate(
-            val x: Int,
-            val y: Int,
-        )
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationPatternCoordinate.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationPatternCoordinate.kt
new file mode 100644
index 0000000..8a3f780
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationPatternCoordinate.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.authentication.shared.model
+
+data class AuthenticationPatternCoordinate(
+    val x: Int,
+    val y: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
new file mode 100644
index 0000000..f2a3e74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.authentication.shared.model
+
+/** Models the result of an authentication attempt. */
+data class AuthenticationResultModel(
+    /** Whether authentication was successful. */
+    val isSuccessful: Boolean = false,
+    /** If [isSuccessful] is `false`, how long the user must wait before trying again. */
+    val throttleDurationMs: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
new file mode 100644
index 0000000..d0d398e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.authentication.shared.model
+
+/** Models a state for throttling the next authentication attempt. */
+data class AuthenticationThrottlingModel(
+
+    /** Number of failed authentication attempts so far. If not throttling this will be `0`. */
+    val failedAttemptCount: Int = 0,
+
+    /**
+     * Remaining amount of time, in milliseconds, before another authentication attempt can be done.
+     * If not throttling this will be `0`.
+     *
+     * This number is changed throughout the timeout.
+     */
+    val remainingMs: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
new file mode 100644
index 0000000..3c74bf4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.back.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.QuickSettingsController
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import javax.inject.Inject
+
+/** Handles requests to go back either from a button or gesture. */
+@SysUISingleton
+class BackActionInteractor
+@Inject
+constructor(
+    private val statusBarStateController: StatusBarStateController,
+    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+    private val shadeController: ShadeController
+) {
+    private lateinit var shadeViewController: ShadeViewController
+    private lateinit var qsController: QuickSettingsController
+
+    fun setup(qsController: QuickSettingsController, svController: ShadeViewController) {
+        this.qsController = qsController
+        this.shadeViewController = svController
+    }
+
+    fun shouldBackBeHandled(): Boolean {
+        return statusBarStateController.state != StatusBarState.KEYGUARD &&
+            statusBarStateController.state != StatusBarState.SHADE_LOCKED &&
+            !statusBarKeyguardViewManager.isBouncerShowingOverDream
+    }
+
+    fun onBackRequested(): Boolean {
+        if (statusBarKeyguardViewManager.canHandleBackPressed()) {
+            statusBarKeyguardViewManager.onBackPressed()
+            return true
+        }
+        if (qsController.isCustomizing) {
+            qsController.closeQsCustomizer()
+            return true
+        }
+        if (qsController.expanded) {
+            shadeViewController.animateCollapseQs(false)
+            return true
+        }
+        if (shadeViewController.closeUserSwitcherIfOpen()) {
+            return true
+        }
+        if (shouldBackBeHandled()) {
+            if (shadeViewController.canBeCollapsed()) {
+                // this is the Shade dismiss animation, so make sure QQS closes when it ends.
+                shadeViewController.onBackPressed()
+                shadeController.animateCollapseShade()
+            }
+            return true
+        }
+        return false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
index b52ddc1..b81d7fc 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
@@ -87,6 +87,10 @@
     }
 
     var displayShield: Boolean = false
+        set(value) {
+            field = value
+            postInvalidate()
+        }
 
     private fun updateSizes() {
         val b = bounds
@@ -174,6 +178,11 @@
         mainBatteryDrawable.charging = charging
     }
 
+    /** Returns whether the battery is currently charging. */
+    fun getCharging(): Boolean {
+        return mainBatteryDrawable.charging
+    }
+
     /** Sets the current level (out of 100) of the battery. */
     fun setBatteryLevel(level: Int) {
         mainBatteryDrawable.setBatteryLevel(level)
@@ -204,4 +213,11 @@
         val shieldPathString = context.resources.getString(R.string.config_batterymeterShieldPath)
         shieldPath.set(PathParser.createPathFromPathData(shieldPathString))
     }
+
+    private val invalidateRunnable: () -> Unit = { invalidateSelf() }
+
+    private fun postInvalidate() {
+        unscheduleSelf(invalidateRunnable)
+        scheduleSelf(invalidateRunnable, 0)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 263df33..ca43705 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -77,8 +77,9 @@
     private int mShowPercentMode = MODE_DEFAULT;
     private boolean mShowPercentAvailable;
     private String mEstimateText = null;
-    private boolean mCharging;
+    private boolean mPluggedIn;
     private boolean mIsBatteryDefender;
+    private boolean mIsIncompatibleCharging;
     private boolean mDisplayShieldEnabled;
     // Error state where we know nothing about the current battery state
     private boolean mBatteryStateUnknown;
@@ -202,10 +203,10 @@
      * @param pluggedIn whether the device is plugged in or not
      */
     public void onBatteryLevelChanged(@IntRange(from = 0, to = 100) int level, boolean pluggedIn) {
-        mDrawable.setCharging(pluggedIn);
-        mDrawable.setBatteryLevel(level);
-        mCharging = pluggedIn;
+        mPluggedIn = pluggedIn;
         mLevel = level;
+        mDrawable.setCharging(isCharging());
+        mDrawable.setBatteryLevel(level);
         updatePercentText();
     }
 
@@ -224,6 +225,15 @@
         }
     }
 
+    void onIsIncompatibleChargingChanged(boolean isIncompatibleCharging) {
+        boolean valueChanged = mIsIncompatibleCharging != isIncompatibleCharging;
+        mIsIncompatibleCharging = isIncompatibleCharging;
+        if (valueChanged) {
+            mDrawable.setCharging(isCharging());
+            updateContentDescription();
+        }
+    }
+
     private TextView loadPercentView() {
         return (TextView) LayoutInflater.from(getContext())
                 .inflate(R.layout.battery_percentage_view, null);
@@ -263,7 +273,7 @@
         }
 
         if (mBatteryPercentView != null) {
-            if (mShowPercentMode == MODE_ESTIMATE && !mCharging) {
+            if (mShowPercentMode == MODE_ESTIMATE && !isCharging()) {
                 mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate(
                         (String estimate) -> {
                     if (mBatteryPercentView == null) {
@@ -316,7 +326,7 @@
         } else if (mIsBatteryDefender) {
             contentDescription =
                     context.getString(R.string.accessibility_battery_level_charging_paused, mLevel);
-        } else if (mCharging) {
+        } else if (isCharging()) {
             contentDescription =
                     context.getString(R.string.accessibility_battery_level_charging, mLevel);
         } else {
@@ -348,7 +358,7 @@
                 updatePercentText();
                 addView(mBatteryPercentView, new LayoutParams(
                         LayoutParams.WRAP_CONTENT,
-                        LayoutParams.MATCH_PARENT));
+                        LayoutParams.WRAP_CONTENT));
             }
         } else {
             if (showing) {
@@ -462,14 +472,24 @@
         }
     }
 
+    private boolean isCharging() {
+        return mPluggedIn && !mIsIncompatibleCharging;
+    }
+
     public void dump(PrintWriter pw, String[] args) {
         String powerSave = mDrawable == null ? null : mDrawable.getPowerSaveEnabled() + "";
+        String displayShield = mDrawable == null ? null : mDrawable.getDisplayShield() + "";
+        String charging = mDrawable == null ? null : mDrawable.getCharging() + "";
         CharSequence percent = mBatteryPercentView == null ? null : mBatteryPercentView.getText();
         pw.println("  BatteryMeterView:");
         pw.println("    mDrawable.getPowerSave: " + powerSave);
+        pw.println("    mDrawable.getDisplayShield: " + displayShield);
+        pw.println("    mDrawable.getCharging: " + charging);
         pw.println("    mBatteryPercentView.getText(): " + percent);
         pw.println("    mTextColor: #" + Integer.toHexString(mTextColor));
         pw.println("    mBatteryStateUnknown: " + mBatteryStateUnknown);
+        pw.println("    mIsIncompatibleCharging: " + mIsIncompatibleCharging);
+        pw.println("    mPluggedIn: " + mPluggedIn);
         pw.println("    mLevel: " + mLevel);
         pw.println("    mMode: " + mShowPercentMode);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index f6a10bd..0ca3883 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -30,16 +30,20 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.ViewController;
 
+import java.io.PrintWriter;
+
 import javax.inject.Inject;
 
 /** Controller for {@link BatteryMeterView}. **/
@@ -48,11 +52,13 @@
     private final TunerService mTunerService;
     private final Handler mMainHandler;
     private final ContentResolver mContentResolver;
+    private final FeatureFlags mFeatureFlags;
     private final BatteryController mBatteryController;
 
     private final String mSlotBattery;
     private final SettingObserver mSettingObserver;
     private final UserTracker mUserTracker;
+    private final StatusBarLocation mLocation;
 
     private final ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
@@ -94,6 +100,20 @@
                 public void onIsBatteryDefenderChanged(boolean isBatteryDefender) {
                     mView.onIsBatteryDefenderChanged(isBatteryDefender);
                 }
+
+                @Override
+                public void onIsIncompatibleChargingChanged(boolean isIncompatibleCharging) {
+                    if (mFeatureFlags.isEnabled(Flags.INCOMPATIBLE_CHARGING_BATTERY_ICON)) {
+                        mView.onIsIncompatibleChargingChanged(isIncompatibleCharging);
+                    }
+                }
+
+                @Override
+                public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+                    pw.print(super.toString());
+                    pw.println(" location=" + mLocation);
+                    mView.dump(pw, args);
+                }
             };
 
     private final UserTracker.Callback mUserChangedCallback =
@@ -113,6 +133,7 @@
     @Inject
     public BatteryMeterViewController(
             BatteryMeterView view,
+            StatusBarLocation location,
             UserTracker userTracker,
             ConfigurationController configurationController,
             TunerService tunerService,
@@ -121,15 +142,18 @@
             FeatureFlags featureFlags,
             BatteryController batteryController) {
         super(view);
+        mLocation = location;
         mUserTracker = userTracker;
         mConfigurationController = configurationController;
         mTunerService = tunerService;
         mMainHandler = mainHandler;
         mContentResolver = contentResolver;
+        mFeatureFlags = featureFlags;
         mBatteryController = batteryController;
 
         mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
-        mView.setDisplayShieldEnabled(featureFlags.isEnabled(Flags.BATTERY_SHIELD_ICON));
+        mView.setDisplayShieldEnabled(
+                getContext().getResources().getBoolean(R.bool.flag_battery_shield_icon));
 
         mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
         mSettingObserver = new SettingObserver(mMainHandler);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 7618109..58c8000 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -82,6 +82,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.io.PrintWriter;
@@ -254,7 +255,7 @@
                 .setMessage(messageBody)
                 .setPositiveButton(android.R.string.ok, null)
                 .create();
-        alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+        alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
         alertDialog.show();
     }
 
@@ -267,7 +268,7 @@
                 .setOnDismissListener(
                         dialog -> animateAway(AuthDialogCallback.DISMISSED_ERROR))
                 .create();
-        alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+        alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
         alertDialog.show();
     }
 
@@ -288,12 +289,13 @@
             @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractor,
             @NonNull PromptViewModel promptViewModel,
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
-            @NonNull @Background DelayableExecutor bgExecutor) {
+            @NonNull @Background DelayableExecutor bgExecutor,
+            @NonNull VibratorHelper vibratorHelper) {
         this(config, featureFlags, applicationCoroutineScope, fpProps, faceProps,
                 wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
                 jankMonitor, authBiometricFingerprintViewModelProvider, promptSelectorInteractor,
                 promptCredentialInteractor, promptViewModel, credentialViewModelProvider,
-                new Handler(Looper.getMainLooper()), bgExecutor);
+                new Handler(Looper.getMainLooper()), bgExecutor, vibratorHelper);
     }
 
     @VisibleForTesting
@@ -314,7 +316,8 @@
             @NonNull PromptViewModel promptViewModel,
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
             @NonNull Handler mainHandler,
-            @NonNull @Background DelayableExecutor bgExecutor) {
+            @NonNull @Background DelayableExecutor bgExecutor,
+            @NonNull VibratorHelper vibratorHelper) {
         super(config.mContext);
 
         mConfig = config;
@@ -364,7 +367,8 @@
         if (featureFlags.isEnabled(Flags.BIOMETRIC_BP_STRONG)) {
             showPrompt(config, layoutInflater, promptViewModel,
                     Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
-                    Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
+                    Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds),
+                    vibratorHelper, featureFlags);
         } else {
             showLegacyPrompt(config, layoutInflater, fpProps, faceProps);
         }
@@ -388,7 +392,10 @@
     private void showPrompt(@NonNull Config config, @NonNull LayoutInflater layoutInflater,
             @NonNull PromptViewModel viewModel,
             @Nullable FingerprintSensorPropertiesInternal fpProps,
-            @Nullable FaceSensorPropertiesInternal faceProps) {
+            @Nullable FaceSensorPropertiesInternal faceProps,
+            @NonNull VibratorHelper vibratorHelper,
+            @NonNull FeatureFlags featureFlags
+    ) {
         if (Utils.isBiometricAllowed(config.mPromptInfo)) {
             mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication(
                     config.mPromptInfo,
@@ -401,7 +408,8 @@
             mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
                     // TODO(b/201510778): This uses the wrong timeout in some cases
                     getJankListener(view, TRANSIT, AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
-                    mBackgroundView, mBiometricCallback, mApplicationCoroutineScope);
+                    mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
+                    vibratorHelper, featureFlags);
 
             // TODO(b/251476085): migrate these dependencies
             if (fpProps != null && fpProps.isAnyUdfpsType()) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 2ca4f0c..7b288a8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -85,6 +85,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.data.repository.BiometricType;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
 
@@ -184,12 +185,15 @@
     @NonNull private final UdfpsUtils mUdfpsUtils;
     private final @Background DelayableExecutor mBackgroundExecutor;
     private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
+    @NonNull private final VibratorHelper mVibratorHelper;
 
     @VisibleForTesting
     final TaskStackListener mTaskStackListener = new TaskStackListener() {
         @Override
         public void onTaskStackChanged() {
-            mHandler.post(AuthController.this::cancelIfOwnerIsNotInForeground);
+            if (!isOwnerInForeground()) {
+                mHandler.post(AuthController.this::cancelIfOwnerIsNotInForeground);
+            }
         }
     };
 
@@ -227,33 +231,39 @@
         }
     }
 
+    private boolean isOwnerInForeground() {
+        if (mCurrentDialog != null) {
+            final String clientPackage = mCurrentDialog.getOpPackageName();
+            final List<ActivityManager.RunningTaskInfo> runningTasks =
+                    mActivityTaskManager.getTasks(1);
+            if (!runningTasks.isEmpty()) {
+                final String topPackage = runningTasks.get(0).topActivity.getPackageName();
+                if (!topPackage.contentEquals(clientPackage)
+                        && !Utils.isSystem(mContext, clientPackage)) {
+                    Log.w(TAG, "Evicting client due to: " + topPackage);
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
     private void cancelIfOwnerIsNotInForeground() {
         mExecution.assertIsMainThread();
         if (mCurrentDialog != null) {
             try {
-                final String clientPackage = mCurrentDialog.getOpPackageName();
-                Log.w(TAG, "Task stack changed, current client: " + clientPackage);
-                final List<ActivityManager.RunningTaskInfo> runningTasks =
-                        mActivityTaskManager.getTasks(1);
-                if (!runningTasks.isEmpty()) {
-                    final String topPackage = runningTasks.get(0).topActivity.getPackageName();
-                    if (!topPackage.contentEquals(clientPackage)
-                            && !Utils.isSystem(mContext, clientPackage)) {
-                        Log.e(TAG, "Evicting client due to: " + topPackage);
-                        mCurrentDialog.dismissWithoutCallback(true /* animate */);
-                        mCurrentDialog = null;
+                mCurrentDialog.dismissWithoutCallback(true /* animate */);
+                mCurrentDialog = null;
 
-                        for (Callback cb : mCallbacks) {
-                            cb.onBiometricPromptDismissed();
-                        }
+                for (Callback cb : mCallbacks) {
+                    cb.onBiometricPromptDismissed();
+                }
 
-                        if (mReceiver != null) {
-                            mReceiver.onDialogDismissed(
-                                    BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
-                                    null /* credentialAttestation */);
-                            mReceiver = null;
-                        }
-                    }
+                if (mReceiver != null) {
+                    mReceiver.onDialogDismissed(
+                            BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
+                            null /* credentialAttestation */);
+                    mReceiver = null;
                 }
             } catch (RemoteException e) {
                 Log.e(TAG, "Remote exception", e);
@@ -764,7 +774,8 @@
             @NonNull InteractionJankMonitor jankMonitor,
             @Main Handler handler,
             @Background DelayableExecutor bgExecutor,
-            @NonNull UdfpsUtils udfpsUtils) {
+            @NonNull UdfpsUtils udfpsUtils,
+            @NonNull VibratorHelper vibratorHelper) {
         mContext = context;
         mFeatureFlags = featureFlags;
         mExecution = execution;
@@ -787,6 +798,7 @@
         mFaceEnrolledForUser = new SparseBooleanArray();
         mUdfpsUtils = udfpsUtils;
         mApplicationCoroutineScope = applicationCoroutineScope;
+        mVibratorHelper = vibratorHelper;
 
         mLogContextInteractor = logContextInteractor;
         mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider;
@@ -1251,10 +1263,11 @@
             cb.onBiometricPromptShown();
         }
         mCurrentDialog = newDialog;
-        mCurrentDialog.show(mWindowManager, savedState);
 
-        if (!promptInfo.isAllowBackgroundAuthentication()) {
-            mHandler.post(this::cancelIfOwnerIsNotInForeground);
+        if (!promptInfo.isAllowBackgroundAuthentication() && !isOwnerInForeground()) {
+            cancelIfOwnerIsNotInForeground();
+        } else {
+            mCurrentDialog.show(mWindowManager, savedState);
         }
     }
 
@@ -1333,7 +1346,7 @@
                 wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
                 mInteractionJankMonitor, mAuthBiometricFingerprintViewModelProvider,
                 mPromptCredentialInteractor, mPromptSelectorInteractor, viewModel,
-                mCredentialViewModelProvider, bgExecutor);
+                mCredentialViewModelProvider, bgExecutor, mVibratorHelper);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 6f0f633..ea9fe5f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -37,7 +37,7 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LiftReveal
@@ -230,7 +230,7 @@
                         lightRevealScrim.revealAmount = animator.animatedValue as Float
                     }
                     addListener(object : AnimatorListenerAdapter() {
-                        override fun onAnimationEnd(animation: Animator?) {
+                        override fun onAnimationEnd(animation: Animator) {
                             // Reset light reveal scrim to the default, so the CentralSurfaces
                             // can handle any subsequent light reveal changes
                             // (ie: from dozing changes)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 5ede16d..4c2dc41 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -147,12 +147,12 @@
             retractDwellAnimator = AnimatorSet().apply {
                 playTogether(retractDwellRippleAnimator, retractAlphaAnimator)
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationStart(animation: Animator?) {
+                    override fun onAnimationStart(animation: Animator) {
                         dwellPulseOutAnimator?.cancel()
                         drawDwell = true
                     }
 
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         drawDwell = false
                         resetDwellAlpha()
                     }
@@ -182,13 +182,13 @@
                     invalidate()
                 }
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationStart(animation: Animator?) {
+                    override fun onAnimationStart(animation: Animator) {
                         retractDwellAnimator?.cancel()
                         dwellPulseOutAnimator?.cancel()
                         drawDwell = true
                     }
 
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         drawDwell = false
                         resetDwellAlpha()
                     }
@@ -239,14 +239,14 @@
                     expandDwellRippleAnimator
             )
             addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationStart(animation: Animator?) {
+                override fun onAnimationStart(animation: Animator) {
                     retractDwellAnimator?.cancel()
                     fadeDwellAnimator?.cancel()
                     visibility = VISIBLE
                     drawDwell = true
                 }
 
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     drawDwell = false
                 }
             })
@@ -273,12 +273,12 @@
 
         unlockedRippleAnimator = rippleAnimator.apply {
             addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationStart(animation: Animator?) {
+                override fun onAnimationStart(animation: Animator) {
                     drawRipple = true
                     visibility = VISIBLE
                 }
 
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     onAnimationEnd?.run()
                     drawRipple = false
                     visibility = GONE
@@ -327,7 +327,7 @@
         }
     }
 
-    override fun onDraw(canvas: Canvas?) {
+    override fun onDraw(canvas: Canvas) {
         // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
         // the active effect area. Values here should be kept in sync with the
         // animation implementation in the ripple shader. (Twice bigger)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
index 9d8dcc1..c2b9102 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
@@ -177,6 +177,9 @@
         if (mFaceManager != null) {
             mFaceManager.registerBiometricStateListener(mFaceStateListener);
         }
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.FACE_UNLOCK_RE_ENROLL, REENROLL_NOT_REQUIRED,
+                UserHandle.USER_CURRENT);
     }
 
     private void queueFaceReenrollNotification() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
index b9fa240..a24a47b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
@@ -40,7 +40,7 @@
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val faceAuthInteractor: KeyguardFaceAuthInteractor,
 ) : View.AccessibilityDelegate() {
-    override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo) {
+    override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
         super.onInitializeAccessibilityNodeInfo(host, info)
         if (keyguardUpdateMonitor.shouldListenForFace()) {
             val clickActionToRetryFace =
@@ -52,7 +52,7 @@
         }
     }
 
-    override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+    override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
         return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
             keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.ACCESSIBILITY_ACTION)
             faceAuthInteractor.onAccessibilityAction()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 54aef00..eb6864d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -50,15 +50,17 @@
 import com.airbnb.lottie.LottieAnimationView
 import com.airbnb.lottie.LottieProperty
 import com.airbnb.lottie.model.KeyPath
+import com.android.app.animation.Interpolators
 import com.android.internal.annotations.VisibleForTesting
+import com.android.keyguard.KeyguardPINView
 import com.android.systemui.Dumpable
 import com.android.systemui.R
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.util.boundsOnScreen
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.traceSection
@@ -112,23 +114,33 @@
     private val isReverseDefaultRotation =
         context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
 
-    private var overlayHideAnimator: ViewPropertyAnimator? = null
+    private var overlayShowAnimator: ViewPropertyAnimator? = null
 
     private var overlayView: View? = null
         set(value) {
             field?.let { oldView ->
-                val lottie = oldView.findViewById(R.id.sidefps_animation) as LottieAnimationView
+                val lottie = oldView.requireViewById(R.id.sidefps_animation) as LottieAnimationView
                 lottie.pauseAnimation()
                 windowManager.removeView(oldView)
                 orientationListener.disable()
             }
-            overlayHideAnimator?.cancel()
-            overlayHideAnimator = null
+            overlayShowAnimator?.cancel()
+            overlayShowAnimator = null
 
             field = value
             field?.let { newView ->
+                if (requests.contains(SideFpsUiRequestSource.PRIMARY_BOUNCER)) {
+                    newView.alpha = 0f
+                    overlayShowAnimator =
+                        newView
+                            .animate()
+                            .alpha(1f)
+                            .setDuration(KeyguardPINView.ANIMATION_DURATION)
+                            .setInterpolator(Interpolators.ALPHA_IN)
+                }
                 windowManager.addView(newView, overlayViewParams)
                 orientationListener.enable()
+                overlayShowAnimator?.start()
             }
         }
     @VisibleForTesting var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
@@ -174,7 +186,7 @@
     }
 
     private fun listenForAlternateBouncerVisibility() {
-        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true)
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, "SideFpsController")
         scope.launch {
             alternateBouncerInteractor.isVisible.collect { isVisible: Boolean ->
                 if (isVisible) {
@@ -262,7 +274,7 @@
             }
         overlayOffsets = offsets
 
-        val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
+        val lottie = view.requireViewById(R.id.sidefps_animation) as LottieAnimationView
         view.rotation =
             display.asSideFpsAnimationRotation(
                 offsets.isYAligned(),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index e6aeb43..802eea3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -37,4 +37,8 @@
     dumpManager
 ) {
     override val tag = "UdfpsBpViewController"
+
+    override fun shouldPauseAuth(): Boolean {
+        return false
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 0ccda1f..b23e085 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -26,6 +26,7 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -50,6 +51,7 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.util.Log;
+import android.view.HapticFeedbackConstants;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
@@ -75,6 +77,8 @@
 import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
 import com.android.systemui.biometrics.udfps.TouchProcessor;
 import com.android.systemui.biometrics.udfps.TouchProcessorResult;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeReceiver;
@@ -82,9 +86,9 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter;
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -153,6 +157,7 @@
     @NonNull private final SystemUIDialogManager mDialogManager;
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+    @NonNull private final Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
     @NonNull private final VibratorHelper mVibrator;
     @NonNull private final FeatureFlags mFeatureFlags;
     @NonNull private final FalsingManager mFalsingManager;
@@ -173,6 +178,7 @@
     @NonNull private final SecureSettings mSecureSettings;
     @NonNull private final UdfpsUtils mUdfpsUtils;
     @NonNull private final InputManager mInputManager;
+    @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
     private final boolean mIgnoreRefreshRate;
 
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
@@ -230,6 +236,8 @@
     public static final VibrationEffect EFFECT_CLICK =
             VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
 
+    public static final int LONG_PRESS = HapticFeedbackConstants.LONG_PRESS;
+
     private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurnedOn() {
@@ -259,10 +267,6 @@
         @Override
         public void showUdfpsOverlay(long requestId, int sensorId, int reason,
                 @NonNull IUdfpsOverlayControllerCallback callback) {
-            if (mFeatureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
-                return;
-            }
-
             mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay(
                     new UdfpsControllerOverlay(mContext, mFingerprintManager, mInflater,
                             mWindowManager, mAccessibilityManager, mStatusBarStateController,
@@ -274,15 +278,13 @@
                             mUdfpsDisplayMode, mSecureSettings, requestId, reason, callback,
                             (view, event, fromUdfpsView) -> onTouch(requestId, event,
                                     fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
-                            mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils)));
+                            mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils,
+                            mUdfpsKeyguardAccessibilityDelegate,
+                            mUdfpsKeyguardViewModels)));
         }
 
         @Override
         public void hideUdfpsOverlay(int sensorId) {
-            if (mFeatureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
-                return;
-            }
-
             mFgExecutor.execute(() -> {
                 if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
                     // if we get here, we expect keyguardUpdateMonitor's fingerprintRunningState
@@ -797,7 +799,7 @@
     private boolean shouldTryToDismissKeyguard() {
         return mOverlay != null
                 && mOverlay.getAnimationViewController()
-                instanceof UdfpsKeyguardViewControllerLegacy
+                instanceof UdfpsKeyguardViewControllerAdapter
                 && mKeyguardStateController.canDismissLockScreen()
                 && !mAttemptedToDismissKeyguard;
     }
@@ -841,7 +843,9 @@
             @NonNull SecureSettings secureSettings,
             @NonNull InputManager inputManager,
             @NonNull UdfpsUtils udfpsUtils,
-            @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
+            @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
+            @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
+            @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -887,6 +891,7 @@
         mSecureSettings = secureSettings;
         mUdfpsUtils = udfpsUtils;
         mInputManager = inputManager;
+        mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
 
         mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
                 ? singlePointerTouchProcessor : null;
@@ -906,6 +911,7 @@
                     return Unit.INSTANCE;
                 });
         mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+        mUdfpsKeyguardViewModels = udfpsKeyguardViewModelsProvider;
 
         final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController();
         mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController);
@@ -925,12 +931,24 @@
     @VisibleForTesting
     public void playStartHaptic() {
         if (mAccessibilityManager.isTouchExplorationEnabled()) {
-            mVibrator.vibrate(
-                    Process.myUid(),
-                    mContext.getOpPackageName(),
-                    EFFECT_CLICK,
-                    "udfps-onStart-click",
-                    UDFPS_VIBRATION_ATTRIBUTES);
+            if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+                if (mOverlay != null && mOverlay.getOverlayView() != null) {
+                    mVibrator.performHapticFeedback(
+                            mOverlay.getOverlayView(),
+                            HapticFeedbackConstants.CONTEXT_CLICK
+                    );
+                } else {
+                    Log.e(TAG, "No haptics played. Could not obtain overlay view to perform"
+                            + "vibration. Either the controller overlay is null or has no view");
+                }
+            } else {
+                mVibrator.vibrate(
+                        Process.myUid(),
+                        mContext.getOpPackageName(),
+                        EFFECT_CLICK,
+                        "udfps-onStart-click",
+                        UDFPS_VIBRATION_ATTRIBUTES);
+            }
         }
     }
 
@@ -1023,12 +1041,24 @@
             mKeyguardViewManager.showPrimaryBouncer(true);
 
             // play the same haptic as the LockIconViewController longpress
-            mVibrator.vibrate(
-                    Process.myUid(),
-                    mContext.getOpPackageName(),
-                    UdfpsController.EFFECT_CLICK,
-                    "aod-lock-icon-longpress",
-                    LOCK_ICON_VIBRATION_ATTRIBUTES);
+            if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+                if (mOverlay != null && mOverlay.getOverlayView() != null) {
+                    mVibrator.performHapticFeedback(
+                            mOverlay.getOverlayView(),
+                            UdfpsController.LONG_PRESS
+                    );
+                } else {
+                    Log.e(TAG, "No haptics played. Could not obtain overlay view to perform"
+                            + "vibration. Either the controller overlay is null or has no view");
+                }
+            } else {
+                mVibrator.vibrate(
+                        Process.myUid(),
+                        mContext.getOpPackageName(),
+                        UdfpsController.EFFECT_CLICK,
+                        "aod-lock-icon-longpress",
+                        LOCK_ICON_VIBRATION_ATTRIBUTES);
+            }
             return;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index cabe900..d6ef94d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -46,15 +46,19 @@
 import androidx.annotation.LayoutRes
 import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.settingslib.udfps.UdfpsUtils
 import com.android.settingslib.udfps.UdfpsOverlayParams
+import com.android.settingslib.udfps.UdfpsUtils
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.flags.Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -64,6 +68,8 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.settings.SecureSettings
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import javax.inject.Provider
 
 private const val TAG = "UdfpsControllerOverlay"
 
@@ -75,6 +81,7 @@
  * request. This state can persist across configuration changes via the [show] and [hide]
  * methods.
  */
+@ExperimentalCoroutinesApi
 @UiThread
 class UdfpsControllerOverlay @JvmOverloads constructor(
         private val context: Context,
@@ -103,7 +110,9 @@
         private val primaryBouncerInteractor: PrimaryBouncerInteractor,
         private val alternateBouncerInteractor: AlternateBouncerInteractor,
         private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
-        private val udfpsUtils: UdfpsUtils
+        private val udfpsUtils: UdfpsUtils,
+        private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
+        private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>,
 ) {
     /** The view, when [isShowing], or null. */
     var overlayView: UdfpsView? = null
@@ -242,26 +251,40 @@
                 )
             }
             REASON_AUTH_KEYGUARD -> {
-                UdfpsKeyguardViewControllerLegacy(
-                    view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) {
-                        updateSensorLocation(sensorBounds)
-                    },
-                    statusBarStateController,
-                    shadeExpansionStateManager,
-                    statusBarKeyguardViewManager,
-                    keyguardUpdateMonitor,
-                    dumpManager,
-                    transitionController,
-                    configurationController,
-                    keyguardStateController,
-                    unlockedScreenOffAnimationController,
-                    dialogManager,
-                    controller,
-                    activityLaunchAnimator,
-                    featureFlags,
-                    primaryBouncerInteractor,
-                    alternateBouncerInteractor,
-                )
+                if (featureFlags.isEnabled(REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
+                    udfpsKeyguardViewModels.get().setSensorBounds(sensorBounds)
+                    UdfpsKeyguardViewController(
+                        view.addUdfpsView(R.layout.udfps_keyguard_view),
+                        statusBarStateController,
+                        shadeExpansionStateManager,
+                        dialogManager,
+                        dumpManager,
+                        alternateBouncerInteractor,
+                        udfpsKeyguardViewModels.get(),
+                    )
+                } else {
+                    UdfpsKeyguardViewControllerLegacy(
+                        view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) {
+                            updateSensorLocation(sensorBounds)
+                        },
+                        statusBarStateController,
+                        shadeExpansionStateManager,
+                        statusBarKeyguardViewManager,
+                        keyguardUpdateMonitor,
+                        dumpManager,
+                        transitionController,
+                        configurationController,
+                        keyguardStateController,
+                        unlockedScreenOffAnimationController,
+                        dialogManager,
+                        controller,
+                        activityLaunchAnimator,
+                        featureFlags,
+                        primaryBouncerInteractor,
+                        alternateBouncerInteractor,
+                        udfpsKeyguardAccessibilityDelegate,
+                    )
+                }
             }
             REASON_AUTH_BP -> {
                 // note: empty controller, currently shows no visual affordance
@@ -413,7 +436,7 @@
     }
 
     private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean {
-        if (animation !is UdfpsKeyguardViewControllerLegacy) {
+        if (animation !is UdfpsKeyguardViewControllerAdapter) {
             // always rotate view if we're not on the keyguard
             return true
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
index 8352d0a..5dafa61 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
@@ -38,7 +38,8 @@
     override fun getDrawable(): UdfpsDrawable = fingerprintDrawable
 
     fun updateAccessibilityViewLocation(sensorBounds: Rect) {
-        val fingerprintAccessibilityView: View = findViewById(R.id.udfps_enroll_accessibility_view)
+        val fingerprintAccessibilityView: View =
+            requireViewById(R.id.udfps_enroll_accessibility_view)
         val params: ViewGroup.LayoutParams = fingerprintAccessibilityView.layoutParams
         params.width = sensorBounds.width()
         params.height = sensorBounds.height()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
new file mode 100644
index 0000000..8497879
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.content.res.Resources
+import android.os.Bundle
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import javax.inject.Inject
+
+@SysUISingleton
+class UdfpsKeyguardAccessibilityDelegate
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val keyguardViewManager: StatusBarKeyguardViewManager,
+) : View.AccessibilityDelegate() {
+    override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
+        super.onInitializeAccessibilityNodeInfo(host, info)
+        val clickAction =
+            AccessibilityNodeInfo.AccessibilityAction(
+                AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+                resources.getString(R.string.accessibility_bouncer)
+            )
+        info.addAction(clickAction)
+    }
+
+    override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
+        // when an a11y service is enabled, double tapping on the fingerprint sensor should
+        // show the primary bouncer
+        return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
+            keyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
+            true
+        } else super.performAccessibilityAction(host, action, args)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt
new file mode 100644
index 0000000..8cc15da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.content.Context
+import android.util.AttributeSet
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** View corresponding with udfps_keyguard_view.xml */
+@ExperimentalCoroutinesApi
+class UdfpsKeyguardView(
+    context: Context,
+    attrs: AttributeSet?,
+) :
+    UdfpsAnimationView(
+        context,
+        attrs,
+    ) {
+    private val fingerprintDrawablePlaceHolder = UdfpsFpDrawable(context)
+    private var visible = false
+
+    override fun calculateAlpha(): Int {
+        return if (mPauseAuth) {
+            0
+        } else 255 // ViewModels handle animating alpha values
+    }
+
+    override fun getDrawable(): UdfpsDrawable {
+        return fingerprintDrawablePlaceHolder
+    }
+
+    fun useExpandedOverlay(useExpandedOverlay: Boolean) {
+        mUseExpandedOverlay = useExpandedOverlay
+    }
+
+    fun isVisible(): Boolean {
+        return visible
+    }
+
+    fun setVisible(isVisible: Boolean) {
+        visible = isVisible
+        isPauseAuth = !visible
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 3fc3e82..e3fd3ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -20,6 +20,7 @@
 import android.content.res.Configuration
 import android.util.MathUtils
 import android.view.MotionEvent
+import android.view.View
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
@@ -28,11 +29,12 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionListener
@@ -71,6 +73,7 @@
     featureFlags: FeatureFlags,
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
 ) :
     UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
         view,
@@ -78,7 +81,9 @@
         shadeExpansionStateManager,
         systemUIDialogManager,
         dumpManager,
-    ) {
+    ),
+    UdfpsKeyguardViewControllerAdapter {
+    private val uniqueIdentifier = this.toString()
     private val useExpandedOverlay: Boolean =
         featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
     private var showingUdfpsBouncer = false
@@ -278,7 +283,7 @@
 
     public override fun onViewAttached() {
         super.onViewAttached()
-        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true)
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, uniqueIdentifier)
         val dozeAmount = statusBarStateController.dozeAmount
         lastDozeAmount = dozeAmount
         stateListener.onDozeAmountChanged(dozeAmount, dozeAmount)
@@ -300,12 +305,16 @@
         lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = this
         activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
         view.mUseExpandedOverlay = useExpandedOverlay
-        view.startIconAsyncInflate()
+        view.startIconAsyncInflate {
+            val animationViewInternal: View =
+                view.requireViewById(R.id.udfps_animation_view_internal)
+            animationViewInternal.accessibilityDelegate = udfpsKeyguardAccessibilityDelegate
+        }
     }
 
     override fun onViewDetached() {
         super.onViewDetached()
-        alternateBouncerInteractor.setAlternateBouncerUIAvailable(false)
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(false, uniqueIdentifier)
         faceDetectRunning = false
         keyguardStateController.removeCallback(keyguardStateControllerCallback)
         statusBarStateController.removeCallback(stateListener)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
index 056d692..b916810c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
@@ -79,6 +79,7 @@
     private float mInterpolatedDarkAmount;
     private int mAnimationType = ANIMATION_NONE;
     private boolean mFullyInflated;
+    private Runnable mOnFinishInflateRunnable;
 
     public UdfpsKeyguardViewLegacy(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
@@ -90,7 +91,12 @@
             .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
     }
 
-    public void startIconAsyncInflate() {
+    /**
+     * Inflate internal udfps view on a background thread and call the onFinishRunnable
+     * when inflation is finished.
+     */
+    public void startIconAsyncInflate(Runnable onFinishInflate) {
+        mOnFinishInflateRunnable = onFinishInflate;
         // inflate Lottie views on a background thread in case it takes a while to inflate
         AsyncLayoutInflater inflater = new AsyncLayoutInflater(mContext);
         inflater.inflate(R.layout.udfps_keyguard_view_internal, this,
@@ -330,6 +336,7 @@
                     frameInfo -> new PorterDuffColorFilter(mTextColorPrimary,
                             PorterDuff.Mode.SRC_ATOP)
             );
+            mOnFinishInflateRunnable.run();
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
index 39199d1..2102a1f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.biometrics
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-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.core.LogLevel
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.WARNING
 import com.android.systemui.log.dagger.UdfpsLog
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index a5e846a..53dc0e3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.biometrics.dagger
 
 import com.android.settingslib.udfps.UdfpsUtils
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository
+import com.android.systemui.biometrics.data.repository.FacePropertyRepositoryImpl
 import com.android.systemui.biometrics.data.repository.FaceSettingsRepository
 import com.android.systemui.biometrics.data.repository.FaceSettingsRepositoryImpl
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
@@ -53,6 +55,10 @@
 
     @Binds
     @SysUISingleton
+    fun faceSensors(impl: FacePropertyRepositoryImpl): FacePropertyRepository
+
+    @Binds
+    @SysUISingleton
     fun biometricPromptRepository(impl: PromptRepositoryImpl): PromptRepository
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
new file mode 100644
index 0000000..d2cb849
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import android.hardware.face.FaceManager
+import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.biometrics.shared.model.toSensorStrength
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+
+/** A repository for the global state of Face sensor. */
+interface FacePropertyRepository {
+    /** Face sensor information, null if it is not available. */
+    val sensorInfo: Flow<FaceSensorInfo?>
+}
+
+/** Describes a biometric sensor */
+data class FaceSensorInfo(val id: Int, val strength: SensorStrength)
+
+private const val TAG = "FaceSensorPropertyRepositoryImpl"
+
+@SysUISingleton
+class FacePropertyRepositoryImpl
+@Inject
+constructor(@Application private val applicationScope: CoroutineScope, faceManager: FaceManager?) :
+    FacePropertyRepository {
+
+    private val sensorProps: Flow<List<FaceSensorPropertiesInternal>> =
+        faceManager?.let {
+            ConflatedCallbackFlow.conflatedCallbackFlow {
+                    val callback =
+                        object : IFaceAuthenticatorsRegisteredCallback.Stub() {
+                            override fun onAllAuthenticatorsRegistered(
+                                sensors: List<FaceSensorPropertiesInternal>
+                            ) {
+                                trySendWithFailureLogging(
+                                    sensors,
+                                    TAG,
+                                    "onAllAuthenticatorsRegistered"
+                                )
+                            }
+                        }
+                    it.addAuthenticatorsRegisteredCallback(callback)
+                    awaitClose {}
+                }
+                .shareIn(applicationScope, SharingStarted.Eagerly)
+        }
+            ?: flowOf(emptyList())
+
+    override val sensorInfo: Flow<FaceSensorInfo?> =
+        sensorProps
+            .map { it.firstOrNull() }
+            .map { it?.let { FaceSensorInfo(it.sensorId, it.sensorStrength.toSensorStrength()) } }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index c43722f..daff5fe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -22,6 +22,7 @@
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.biometrics.shared.model.toSensorStrength
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -66,7 +67,7 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    private val fingerprintManager: FingerprintManager
+    private val fingerprintManager: FingerprintManager?,
 ) : FingerprintPropertyRepository {
 
     override val isInitialized: Flow<Boolean> =
@@ -82,7 +83,7 @@
                             }
                         }
                     }
-                fingerprintManager.addAuthenticatorsRegisteredCallback(callback)
+                fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
                 trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
                 awaitClose {}
             }
@@ -106,7 +107,7 @@
 
     private fun setProperties(prop: FingerprintSensorPropertiesInternal) {
         _sensorId.value = prop.sensorId
-        _strength.value = sensorStrengthIntToObject(prop.sensorStrength)
+        _strength.value = prop.sensorStrength.toSensorStrength()
         _sensorType.value = sensorTypeIntToObject(prop.sensorType)
         _sensorLocations.value =
             prop.allLocations.associateBy { sensorLocationInternal ->
@@ -119,15 +120,6 @@
     }
 }
 
-private fun sensorStrengthIntToObject(value: Int): SensorStrength {
-    return when (value) {
-        0 -> SensorStrength.CONVENIENCE
-        1 -> SensorStrength.WEAK
-        2 -> SensorStrength.STRONG
-        else -> throw IllegalArgumentException("Invalid SensorStrength value: $value")
-    }
-}
-
 private fun sensorTypeIntToObject(value: Int): FingerprintSensorType {
     return when (value) {
         0 -> FingerprintSensorType.UNKNOWN
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
index 1f1a1b5..2a02667 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -89,7 +89,7 @@
                 )
             val hat = gkResponse.gatekeeperHAT
             lockPatternUtils.removeGatekeeperPasswordHandle(pwHandle)
-            emit(CredentialStatus.Success.Verified(hat))
+            emit(CredentialStatus.Success.Verified(checkNotNull(hat)))
         } else if (response.timeout > 0) {
             // if requests are being throttled, update the error message every
             // second until the temporary lock has expired
@@ -226,8 +226,7 @@
             is BiometricPromptRequest.Credential.Password ->
                 DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT
         }
-    return devicePolicyManager.resources.getString(id) {
-        // use fallback a string if not found
+    val getFallbackString = {
         val defaultId =
             when (request) {
                 is BiometricPromptRequest.Credential.Pin ->
@@ -239,6 +238,8 @@
             }
         getString(defaultId)
     }
+
+    return devicePolicyManager.resources?.getString(id, getFallbackString) ?: getFallbackString()
 }
 
 private fun Context.getLastAttemptBeforeWipeUserMessage(
@@ -266,8 +267,8 @@
                 DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS
             else -> DevicePolicyResources.UNDEFINED
         }
-    return devicePolicyManager.resources.getString(id) {
-        // use fallback a string if not found
+
+    val getFallbackString = {
         val defaultId =
             when (userType) {
                 UserType.PRIMARY ->
@@ -279,4 +280,6 @@
             }
         getString(defaultId)
     }
+
+    return devicePolicyManager.resources?.getString(id, getFallbackString) ?: getFallbackString()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
index 86940ca..863ba8d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractor.kt
@@ -82,7 +82,9 @@
 ) : LogContextInteractor {
 
     init {
-        foldProvider.start()
+        applicationScope.launch {
+            foldProvider.start()
+        }
     }
 
     override val displayState =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
index d92c217..ac4b717 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.biometrics.data.repository.PromptRepository
 import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
-import com.android.systemui.biometrics.domain.model.BiometricUserInfo
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 import com.android.systemui.biometrics.shared.model.PromptKind
 import com.android.systemui.dagger.qualifiers.Background
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index be99dd9..bb87dca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.biometrics.domain.model.BiometricModalities
 import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
-import com.android.systemui.biometrics.domain.model.BiometricUserInfo
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 import com.android.systemui.biometrics.shared.model.PromptKind
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index 75de47d..caebc30 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -1,6 +1,7 @@
 package com.android.systemui.biometrics.domain.model
 
 import android.hardware.biometrics.PromptInfo
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 
 /**
  * Preferences for BiometricPrompt, such as title & description, that are immutable while the prompt
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt
deleted file mode 100644
index 08da04d..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.android.systemui.biometrics.domain.model
-
-/** Metadata about the current user BiometricPrompt is being shown to. */
-data class BiometricUserInfo(
-    val userId: Int,
-    val deviceCredentialOwnerId: Int = userId,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
new file mode 100644
index 0000000..39689ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
@@ -0,0 +1,12 @@
+package com.android.systemui.biometrics.shared.model
+
+/**
+ * Metadata about the current user BiometricPrompt is being shown to.
+ *
+ * If the user's fallback credential is owned by another profile user the [deviceCredentialOwnerId]
+ * will differ from the user's [userId].
+ */
+data class BiometricUserInfo(
+    val userId: Int,
+    val deviceCredentialOwnerId: Int = userId,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
index 2982d0b..30e865e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
@@ -18,9 +18,18 @@
 
 import android.hardware.biometrics.SensorProperties
 
-/** Fingerprint sensor security strength. Represents [SensorProperties.Strength]. */
+/** Sensor security strength. Represents [SensorProperties.Strength]. */
 enum class SensorStrength {
     CONVENIENCE,
     WEAK,
     STRONG,
 }
+
+/** Convert [this] to corresponding [SensorStrength] */
+fun Int.toSensorStrength(): SensorStrength =
+    when (this) {
+        0 -> SensorStrength.CONVENIENCE
+        1 -> SensorStrength.WEAK
+        2 -> SensorStrength.STRONG
+        else -> throw IllegalArgumentException("Invalid SensorStrength value: $this")
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
index a3f34ce..b940ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
@@ -122,7 +122,7 @@
                 titleView.ellipsize = TextUtils.TruncateAt.MARQUEE
                 titleView.marqueeRepeatLimit = -1
                 // select to enable marquee unless a screen reader is enabled
-                titleView.isSelected = accessibilityManager.shouldMarquee()
+                titleView.isSelected = accessibilityManager?.shouldMarquee() ?: false
             } else {
                 titleView.isSingleLine = false
                 titleView.ellipsize = null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 1bf7ffa..d054751 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -26,6 +26,7 @@
 import android.os.Bundle
 import android.text.method.ScrollingMovementMethod
 import android.util.Log
+import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
 import android.view.View
 import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
@@ -56,9 +57,13 @@
 import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
 import com.android.systemui.biometrics.ui.viewmodel.PromptSize
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.VibratorHelper
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.map
@@ -80,6 +85,8 @@
         backgroundView: View,
         legacyCallback: Callback,
         applicationScope: CoroutineScope,
+        vibratorHelper: VibratorHelper,
+        featureFlags: FeatureFlags,
     ): AuthBiometricViewAdapter {
         val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
 
@@ -88,9 +95,9 @@
         val textColorHint =
             view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
 
-        val titleView = view.findViewById<TextView>(R.id.title)
-        val subtitleView = view.findViewById<TextView>(R.id.subtitle)
-        val descriptionView = view.findViewById<TextView>(R.id.description)
+        val titleView = view.requireViewById<TextView>(R.id.title)
+        val subtitleView = view.requireViewById<TextView>(R.id.subtitle)
+        val descriptionView = view.requireViewById<TextView>(R.id.description)
 
         // set selected to enable marquee unless a screen reader is enabled
         titleView.isSelected =
@@ -99,18 +106,18 @@
             !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
         descriptionView.movementMethod = ScrollingMovementMethod()
 
-        val iconViewOverlay = view.findViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
-        val iconView = view.findViewById<LottieAnimationView>(R.id.biometric_icon)
-        val indicatorMessageView = view.findViewById<TextView>(R.id.indicator)
+        val iconViewOverlay = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
+        val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
+        val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
 
         // Negative-side (left) buttons
-        val negativeButton = view.findViewById<Button>(R.id.button_negative)
-        val cancelButton = view.findViewById<Button>(R.id.button_cancel)
-        val credentialFallbackButton = view.findViewById<Button>(R.id.button_use_credential)
+        val negativeButton = view.requireViewById<Button>(R.id.button_negative)
+        val cancelButton = view.requireViewById<Button>(R.id.button_cancel)
+        val credentialFallbackButton = view.requireViewById<Button>(R.id.button_use_credential)
 
         // Positive-side (right) buttons
-        val confirmationButton = view.findViewById<Button>(R.id.button_confirm)
-        val retryButton = view.findViewById<Button>(R.id.button_try_again)
+        val confirmationButton = view.requireViewById<Button>(R.id.button_confirm)
+        val retryButton = view.requireViewById<Button>(R.id.button_try_again)
 
         // TODO(b/251476085): temporary workaround for the unsafe callbacks & legacy controllers
         val adapter =
@@ -392,6 +399,18 @@
                         }
                     }
                 }
+
+                // Play haptics
+                if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+                    launch {
+                        viewModel.hapticsToPlay.collect { hapticFeedbackConstant ->
+                            if (hapticFeedbackConstant != HapticFeedbackConstants.NO_HAPTICS) {
+                                vibratorHelper.performHapticFeedback(view, hapticFeedbackConstant)
+                                viewModel.clearHaptics()
+                            }
+                        }
+                    }
+                }
             }
         }
 
@@ -501,10 +520,10 @@
                 failureReason,
                 messageAfterError = modalities.asDefaultHelpMessage(applicationContext),
                 authenticateAfterError = modalities.hasFingerprint,
-                suppressIf = { currentMessage ->
+                suppressIf = { currentMessage, history ->
                     modalities.hasFaceAndFingerprint &&
                         failedModality == BiometricModality.Face &&
-                        currentMessage.isError
+                        (currentMessage.isError || history.faceFailed)
                 },
                 failedModality = failedModality,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 1a286cf..370b36b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -72,7 +72,7 @@
             }
         }
 
-        val iconHolderView = view.findViewById<View>(R.id.biometric_icon_frame)
+        val iconHolderView = view.requireViewById<View>(R.id.biometric_icon_frame)
         val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding)
         val fullSizeYOffset =
             view.resources.getDimension(R.dimen.biometric_dialog_medium_to_large_translation_offset)
@@ -205,7 +205,7 @@
 }
 
 private fun View.isLandscape(): Boolean {
-    val r = context.display.rotation
+    val r = context.display?.rotation
     return r == Surface.ROTATION_90 || r == Surface.ROTATION_270
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
index 6fb8e34..c27d7152 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.biometrics.ui.binder
 
+import android.os.UserHandle
 import android.view.KeyEvent
 import android.view.View
 import android.view.inputmethod.EditorInfo
@@ -9,6 +10,7 @@
 import android.window.OnBackInvokedCallback
 import android.window.OnBackInvokedDispatcher
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.R
 import com.android.systemui.biometrics.ui.CredentialPasswordView
@@ -16,6 +18,8 @@
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.firstOrNull
 import kotlinx.coroutines.launch
 
 /** Sub-binder for the [CredentialPasswordView]. */
@@ -35,31 +39,22 @@
         val onBackInvokedCallback = OnBackInvokedCallback { host.onCredentialAborted() }
 
         view.repeatWhenAttached {
+            // the header info never changes - do it early
+            val header = viewModel.header.first()
+            passwordField.setTextOperationUser(UserHandle.of(header.user.deviceCredentialOwnerId))
+            viewModel.inputFlags.firstOrNull()?.let { flags -> passwordField.inputType = flags }
             if (requestFocusForInput) {
                 passwordField.requestFocus()
                 passwordField.scheduleShowSoftInput()
             }
+            passwordField.setOnEditorActionListener(
+                OnImeSubmitListener { text ->
+                    lifecycleScope.launch { viewModel.checkCredential(text, header) }
+                }
+            )
+            passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback))
 
             repeatOnLifecycle(Lifecycle.State.STARTED) {
-                // observe credential validation attempts and submit/cancel buttons
-                launch {
-                    viewModel.header.collect { header ->
-                        passwordField.setTextOperationUser(header.user)
-                        passwordField.setOnEditorActionListener(
-                            OnImeSubmitListener { text ->
-                                launch { viewModel.checkCredential(text, header) }
-                            }
-                        )
-                        passwordField.setOnKeyListener(OnBackButtonListener(onBackInvokedCallback))
-                    }
-                }
-
-                launch {
-                    viewModel.inputFlags.collect { flags ->
-                        flags?.let { passwordField.inputType = it }
-                    }
-                }
-
                 // dismiss on a valid credential check
                 launch {
                     viewModel.validatedAttestation.collect { attestation ->
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
new file mode 100644
index 0000000..c9b1624
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.controller
+
+import com.android.systemui.biometrics.UdfpsAnimationViewController
+import com.android.systemui.biometrics.UdfpsKeyguardView
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Class that coordinates non-HBM animations during keyguard authentication. */
+@ExperimentalCoroutinesApi
+open class UdfpsKeyguardViewController(
+    val view: UdfpsKeyguardView,
+    statusBarStateController: StatusBarStateController,
+    shadeExpansionStateManager: ShadeExpansionStateManager,
+    systemUIDialogManager: SystemUIDialogManager,
+    dumpManager: DumpManager,
+    private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    udfpsKeyguardViewModels: UdfpsKeyguardViewModels,
+) :
+    UdfpsAnimationViewController<UdfpsKeyguardView>(
+        view,
+        statusBarStateController,
+        shadeExpansionStateManager,
+        systemUIDialogManager,
+        dumpManager,
+    ),
+    UdfpsKeyguardViewControllerAdapter {
+    private val uniqueIdentifier = this.toString()
+    override val tag: String
+        get() = TAG
+
+    init {
+        udfpsKeyguardViewModels.bindViews(view)
+    }
+
+    public override fun onViewAttached() {
+        super.onViewAttached()
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, uniqueIdentifier)
+    }
+
+    public override fun onViewDetached() {
+        super.onViewDetached()
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(false, uniqueIdentifier)
+    }
+
+    override fun shouldPauseAuth(): Boolean {
+        return !view.isVisible()
+    }
+
+    override fun listenForTouchesOutsideView(): Boolean {
+        return true
+    }
+
+    companion object {
+        private const val TAG = "UdfpsKeyguardViewController"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt
index a64798c..3257f20 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialHeaderViewModel.kt
@@ -1,11 +1,11 @@
 package com.android.systemui.biometrics.ui.viewmodel
 
 import android.graphics.drawable.Drawable
-import android.os.UserHandle
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 
 /** View model for the top-level header / info area of BiometricPrompt. */
 interface CredentialHeaderViewModel {
-    val user: UserHandle
+    val user: BiometricUserInfo
     val title: String
     val subtitle: String
     val description: String
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
index 9d7b940..a3b23ca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
@@ -2,7 +2,6 @@
 
 import android.content.Context
 import android.graphics.drawable.Drawable
-import android.os.UserHandle
 import android.text.InputType
 import com.android.internal.widget.LockPatternView
 import com.android.systemui.R
@@ -10,6 +9,7 @@
 import com.android.systemui.biometrics.domain.interactor.CredentialStatus
 import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 import com.android.systemui.dagger.qualifiers.Application
 import javax.inject.Inject
 import kotlin.reflect.KClass
@@ -36,7 +36,7 @@
             request ->
             BiometricPromptHeaderViewModelImpl(
                 request,
-                user = UserHandle.of(request.userInfo.userId),
+                user = request.userInfo,
                 title = request.title,
                 subtitle = request.subtitle,
                 description = request.description,
@@ -169,7 +169,7 @@
 
 private class BiometricPromptHeaderViewModelImpl(
     val request: BiometricPromptRequest.Credential,
-    override val user: UserHandle,
+    override val user: BiometricUserInfo,
     override val title: String,
     override val subtitle: String,
     override val description: String,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistory.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistory.kt
new file mode 100644
index 0000000..d002bf0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistory.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.viewmodel
+
+import com.android.systemui.biometrics.shared.model.BiometricModality
+
+/** Contains metadata about key events that have occurred while biometric prompt is showing. */
+interface PromptHistory {
+
+    /** If face authentication has failed at least once. */
+    val faceFailed: Boolean
+
+    /** If fingerprint authentication has failed at least once. */
+    val fingerprintFailed: Boolean
+}
+
+class PromptHistoryImpl : PromptHistory {
+    private var failures = mutableSetOf<BiometricModality>()
+
+    override val faceFailed
+        get() = failures.contains(BiometricModality.Face)
+
+    override val fingerprintFailed
+        get() = failures.contains(BiometricModality.Fingerprint)
+
+    /** Record a failure event. */
+    fun failure(modality: BiometricModality) {
+        if (modality != BiometricModality.None) {
+            failures.add(modality)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index a148d08..89561a5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -17,12 +17,15 @@
 
 import android.hardware.biometrics.BiometricPrompt
 import android.util.Log
+import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
 import com.android.systemui.biometrics.AuthBiometricView
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
 import com.android.systemui.biometrics.domain.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.statusbar.VibratorHelper
 import javax.inject.Inject
 import kotlinx.coroutines.Job
@@ -44,6 +47,7 @@
 constructor(
     private val interactor: PromptSelectorInteractor,
     private val vibrator: VibratorHelper,
+    private val featureFlags: FeatureFlags,
 ) {
     /** The set of modalities available for this prompt */
     val modalities: Flow<BiometricModalities> =
@@ -98,6 +102,11 @@
     private val _forceLargeSize = MutableStateFlow(false)
     private val _forceMediumSize = MutableStateFlow(false)
 
+    private val _hapticsToPlay = MutableStateFlow(HapticFeedbackConstants.NO_HAPTICS)
+
+    /** Event fired to the view indicating a [HapticFeedbackConstants] to be played */
+    val hapticsToPlay = _hapticsToPlay.asStateFlow()
+
     /** The size of the prompt. */
     val size: Flow<PromptSize> =
         combine(
@@ -218,6 +227,7 @@
             }
             .distinctUntilChanged()
 
+    private val history = PromptHistoryImpl()
     private var messageJob: Job? = null
 
     /**
@@ -228,13 +238,13 @@
      * is set (or via [showHelp] when not set) after the error is dismissed.
      *
      * The error is ignored if the user has already authenticated or if [suppressIf] is true given
-     * the currently showing [PromptMessage].
+     * the currently showing [PromptMessage] and [PromptHistory].
      */
     suspend fun showTemporaryError(
         message: String,
         messageAfterError: String,
         authenticateAfterError: Boolean,
-        suppressIf: (PromptMessage) -> Boolean = { false },
+        suppressIf: (PromptMessage, PromptHistory) -> Boolean = { _, _ -> false },
         hapticFeedback: Boolean = true,
         failedModality: BiometricModality = BiometricModality.None,
     ) = coroutineScope {
@@ -244,7 +254,9 @@
 
         _canTryAgainNow.value = supportsRetry(failedModality)
 
-        if (suppressIf(_message.value)) {
+        val suppress = suppressIf(_message.value, history)
+        history.failure(failedModality)
+        if (suppress) {
             return@coroutineScope
         }
 
@@ -463,11 +475,26 @@
         _forceLargeSize.value = true
     }
 
-    private fun VibratorHelper.success(modality: BiometricModality) =
-        vibrateAuthSuccess("$TAG, modality = $modality BP::success")
+    private fun VibratorHelper.success(modality: BiometricModality) {
+        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            _hapticsToPlay.value = HapticFeedbackConstants.CONFIRM
+        } else {
+            vibrateAuthSuccess("$TAG, modality = $modality BP::success")
+        }
+    }
 
-    private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) =
-        vibrateAuthError("$TAG, modality = $modality BP::error")
+    private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) {
+        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            _hapticsToPlay.value = HapticFeedbackConstants.REJECT
+        } else {
+            vibrateAuthError("$TAG, modality = $modality BP::error")
+        }
+    }
+
+    /** Clears the [hapticsToPlay] variable by setting it to the NO_HAPTICS default. */
+    fun clearHaptics() {
+        _hapticsToPlay.value = HapticFeedbackConstants.NO_HAPTICS
+    }
 
     companion object {
         private const val TAG = "PromptViewModel"
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
index 96af42b..2a457eb 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.BluetoothLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactory.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactory.kt
new file mode 100644
index 0000000..64bf688
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactory.kt
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.data.factory
+
+import android.annotation.IntDef
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FACE_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FINGERPRINT_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FACE_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
+import com.android.systemui.R.string.bouncer_face_not_recognized
+import com.android.systemui.R.string.keyguard_enter_password
+import com.android.systemui.R.string.keyguard_enter_pattern
+import com.android.systemui.R.string.keyguard_enter_pin
+import com.android.systemui.R.string.kg_bio_too_many_attempts_password
+import com.android.systemui.R.string.kg_bio_too_many_attempts_pattern
+import com.android.systemui.R.string.kg_bio_too_many_attempts_pin
+import com.android.systemui.R.string.kg_bio_try_again_or_password
+import com.android.systemui.R.string.kg_bio_try_again_or_pattern
+import com.android.systemui.R.string.kg_bio_try_again_or_pin
+import com.android.systemui.R.string.kg_face_locked_out
+import com.android.systemui.R.string.kg_fp_locked_out
+import com.android.systemui.R.string.kg_fp_not_recognized
+import com.android.systemui.R.string.kg_primary_auth_locked_out_password
+import com.android.systemui.R.string.kg_primary_auth_locked_out_pattern
+import com.android.systemui.R.string.kg_primary_auth_locked_out_pin
+import com.android.systemui.R.string.kg_prompt_after_dpm_lock
+import com.android.systemui.R.string.kg_prompt_after_update_password
+import com.android.systemui.R.string.kg_prompt_after_update_pattern
+import com.android.systemui.R.string.kg_prompt_after_update_pin
+import com.android.systemui.R.string.kg_prompt_after_user_lockdown_password
+import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pattern
+import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pin
+import com.android.systemui.R.string.kg_prompt_auth_timeout
+import com.android.systemui.R.string.kg_prompt_password_auth_timeout
+import com.android.systemui.R.string.kg_prompt_pattern_auth_timeout
+import com.android.systemui.R.string.kg_prompt_pin_auth_timeout
+import com.android.systemui.R.string.kg_prompt_reason_restart_password
+import com.android.systemui.R.string.kg_prompt_reason_restart_pattern
+import com.android.systemui.R.string.kg_prompt_reason_restart_pin
+import com.android.systemui.R.string.kg_prompt_unattended_update
+import com.android.systemui.R.string.kg_too_many_failed_attempts_countdown
+import com.android.systemui.R.string.kg_trust_agent_disabled
+import com.android.systemui.R.string.kg_unlock_with_password_or_fp
+import com.android.systemui.R.string.kg_unlock_with_pattern_or_fp
+import com.android.systemui.R.string.kg_unlock_with_pin_or_fp
+import com.android.systemui.R.string.kg_wrong_input_try_fp_suggestion
+import com.android.systemui.R.string.kg_wrong_password_try_again
+import com.android.systemui.R.string.kg_wrong_pattern_try_again
+import com.android.systemui.R.string.kg_wrong_pin_try_again
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.bouncer.shared.model.Message
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import javax.inject.Inject
+
+@SysUISingleton
+class BouncerMessageFactory
+@Inject
+constructor(
+    private val biometricSettingsRepository: BiometricSettingsRepository,
+    private val securityModel: KeyguardSecurityModel,
+) {
+
+    fun createFromPromptReason(
+        @BouncerPromptReason reason: Int,
+        userId: Int,
+        secondaryMsgOverride: String? = null
+    ): BouncerMessageModel? {
+        val pair =
+            getBouncerMessage(
+                reason,
+                securityModel.getSecurityMode(userId),
+                biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value
+            )
+        return pair?.let {
+            BouncerMessageModel(
+                message = Message(messageResId = pair.first, animate = false),
+                secondaryMessage =
+                    secondaryMsgOverride?.let {
+                        Message(message = secondaryMsgOverride, animate = false)
+                    }
+                        ?: Message(messageResId = pair.second, animate = false)
+            )
+        }
+    }
+
+    /**
+     * Helper method that provides the relevant bouncer message that should be shown for different
+     * scenarios indicated by [reason]. [securityMode] & [fpAuthIsAllowed] parameters are used to
+     * provide a more specific message.
+     */
+    private fun getBouncerMessage(
+        @BouncerPromptReason reason: Int,
+        securityMode: SecurityMode,
+        fpAuthIsAllowed: Boolean = false
+    ): Pair<Int, Int>? {
+        return when (reason) {
+            // Primary auth locked out
+            PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT -> primaryAuthLockedOut(securityMode)
+            // Primary auth required reasons
+            PROMPT_REASON_RESTART -> authRequiredAfterReboot(securityMode)
+            PROMPT_REASON_TIMEOUT -> authRequiredAfterPrimaryAuthTimeout(securityMode)
+            PROMPT_REASON_DEVICE_ADMIN -> authRequiredAfterAdminLockdown(securityMode)
+            PROMPT_REASON_USER_REQUEST -> authRequiredAfterUserLockdown(securityMode)
+            PROMPT_REASON_PREPARE_FOR_UPDATE -> authRequiredForUnattendedUpdate(securityMode)
+            PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE -> authRequiredForMainlineUpdate(securityMode)
+            PROMPT_REASON_FINGERPRINT_LOCKED_OUT -> fingerprintUnlockUnavailable(securityMode)
+            PROMPT_REASON_AFTER_LOCKOUT -> biometricLockout(securityMode)
+            // Non strong auth not available reasons
+            PROMPT_REASON_FACE_LOCKED_OUT ->
+                if (fpAuthIsAllowed) faceLockedOutButFingerprintAvailable(securityMode)
+                else faceLockedOut(securityMode)
+            PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT ->
+                if (fpAuthIsAllowed) nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
+                else nonStrongAuthTimeout(securityMode)
+            PROMPT_REASON_TRUSTAGENT_EXPIRED ->
+                if (fpAuthIsAllowed) trustAgentDisabledWithFingerprintAllowed(securityMode)
+                else trustAgentDisabled(securityMode)
+            // Auth incorrect input reasons.
+            PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT ->
+                if (fpAuthIsAllowed) incorrectSecurityInputWithFingerprint(securityMode)
+                else incorrectSecurityInput(securityMode)
+            PROMPT_REASON_INCORRECT_FACE_INPUT ->
+                if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode)
+                else incorrectFaceInput(securityMode)
+            PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT -> incorrectFingerprintInput(securityMode)
+            // Default message
+            PROMPT_REASON_DEFAULT ->
+                if (fpAuthIsAllowed) defaultMessageWithFingerprint(securityMode)
+                else defaultMessage(securityMode)
+            else -> null
+        }
+    }
+
+    fun emptyMessage(): BouncerMessageModel =
+        BouncerMessageModel(Message(message = ""), Message(message = ""))
+}
+
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(
+    PROMPT_REASON_TIMEOUT,
+    PROMPT_REASON_DEVICE_ADMIN,
+    PROMPT_REASON_USER_REQUEST,
+    PROMPT_REASON_AFTER_LOCKOUT,
+    PROMPT_REASON_PREPARE_FOR_UPDATE,
+    PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT,
+    PROMPT_REASON_TRUSTAGENT_EXPIRED,
+    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+    PROMPT_REASON_INCORRECT_FACE_INPUT,
+    PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT,
+    PROMPT_REASON_FACE_LOCKED_OUT,
+    PROMPT_REASON_FINGERPRINT_LOCKED_OUT,
+    PROMPT_REASON_DEFAULT,
+    PROMPT_REASON_NONE,
+    PROMPT_REASON_RESTART,
+    PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT,
+    PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE,
+)
+annotation class BouncerPromptReason
+
+private fun defaultMessage(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
+        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun defaultMessageWithFingerprint(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0)
+        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, 0)
+        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, 0)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun incorrectSecurityInput(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0)
+        SecurityMode.Password -> Pair(kg_wrong_password_try_again, 0)
+        SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, 0)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion)
+        SecurityMode.Password -> Pair(kg_wrong_password_try_again, kg_wrong_input_try_fp_suggestion)
+        SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, kg_wrong_input_try_fp_suggestion)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun incorrectFingerprintInput(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern)
+        SecurityMode.Password -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_password)
+        SecurityMode.PIN -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun incorrectFaceInput(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern)
+        SecurityMode.Password -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_password)
+        SecurityMode.PIN -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun incorrectFaceInputWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized)
+        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, bouncer_face_not_recognized)
+        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, bouncer_face_not_recognized)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun biometricLockout(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun authRequiredAfterReboot(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_dpm_lock)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern)
+        SecurityMode.Password ->
+            Pair(keyguard_enter_password, kg_prompt_after_user_lockdown_password)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_unattended_update)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_unattended_update)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun authRequiredForMainlineUpdate(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_update_pattern)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_update_password)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_update_pin)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_auth_timeout)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun nonStrongAuthTimeout(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_auth_timeout)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_auth_timeout)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout)
+        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_prompt_auth_timeout)
+        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_auth_timeout)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun faceLockedOut(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_face_locked_out)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun faceLockedOutButFingerprintAvailable(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_face_locked_out)
+        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_face_locked_out)
+        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_face_locked_out)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun fingerprintUnlockUnavailable(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_fp_locked_out)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_fp_locked_out)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_fp_locked_out)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun trustAgentDisabled(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled)
+        SecurityMode.Password -> Pair(keyguard_enter_password, kg_trust_agent_disabled)
+        SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_trust_agent_disabled)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun trustAgentDisabledWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled)
+        SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_trust_agent_disabled)
+        SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_trust_agent_disabled)
+        else -> Pair(0, 0)
+    }
+}
+
+private fun primaryAuthLockedOut(securityMode: SecurityMode): Pair<Int, Int> {
+    return when (securityMode) {
+        SecurityMode.Pattern ->
+            Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pattern)
+        SecurityMode.Password ->
+            Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_password)
+        SecurityMode.PIN ->
+            Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pin)
+        else -> Pair(0, 0)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt
deleted file mode 100644
index 49a0a3c..0000000
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repo/BouncerRepository.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.bouncer.data.repo
-
-import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Provides access to bouncer-related application state. */
-@SysUISingleton
-class BouncerRepository @Inject constructor() {
-    private val _message = MutableStateFlow<String?>(null)
-    /** The user-facing message to show in the bouncer. */
-    val message: StateFlow<String?> = _message.asStateFlow()
-
-    private val _throttling = MutableStateFlow<AuthenticationThrottledModel?>(null)
-    /** The current authentication throttling state. If `null`, there's no throttling. */
-    val throttling: StateFlow<AuthenticationThrottledModel?> = _throttling.asStateFlow()
-
-    fun setMessage(message: String?) {
-        _message.value = message
-    }
-
-    fun setThrottling(throttling: AuthenticationThrottledModel?) {
-        _throttling.value = throttling
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
new file mode 100644
index 0000000..97c1bdb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.data.repository
+
+import android.hardware.biometrics.BiometricSourceType
+import android.hardware.biometrics.BiometricSourceType.FACE
+import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FACE_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FINGERPRINT_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FACE_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+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.flags.SystemPropertiesHelper
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Provide different sources of messages that needs to be shown on the bouncer. */
+interface BouncerMessageRepository {
+    /**
+     * Messages that are shown in response to the incorrect security attempts on the bouncer and
+     * primary authentication method being locked out, along with countdown messages before primary
+     * auth is active again.
+     */
+    val primaryAuthMessage: Flow<BouncerMessageModel?>
+
+    /**
+     * Help messages that are shown to the user on how to successfully perform authentication using
+     * face.
+     */
+    val faceAcquisitionMessage: Flow<BouncerMessageModel?>
+
+    /**
+     * Help messages that are shown to the user on how to successfully perform authentication using
+     * fingerprint.
+     */
+    val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?>
+
+    /** Custom message that is displayed when the bouncer is being shown to launch an app. */
+    val customMessage: Flow<BouncerMessageModel?>
+
+    /**
+     * Messages that are shown in response to biometric authentication attempts through face or
+     * fingerprint.
+     */
+    val biometricAuthMessage: Flow<BouncerMessageModel?>
+
+    /** Messages that are shown when certain auth flags are set. */
+    val authFlagsMessage: Flow<BouncerMessageModel?>
+
+    /** Messages that are show after biometrics are locked out temporarily or permanently */
+    val biometricLockedOutMessage: Flow<BouncerMessageModel?>
+
+    /** Set the value for [primaryAuthMessage] */
+    fun setPrimaryAuthMessage(value: BouncerMessageModel?)
+
+    /** Set the value for [faceAcquisitionMessage] */
+    fun setFaceAcquisitionMessage(value: BouncerMessageModel?)
+    /** Set the value for [fingerprintAcquisitionMessage] */
+    fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?)
+
+    /** Set the value for [customMessage] */
+    fun setCustomMessage(value: BouncerMessageModel?)
+
+    /**
+     * Clear any previously set messages for [primaryAuthMessage], [faceAcquisitionMessage],
+     * [fingerprintAcquisitionMessage] & [customMessage]
+     */
+    fun clearMessage()
+}
+
+private const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
+private const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
+
+@SysUISingleton
+class BouncerMessageRepositoryImpl
+@Inject
+constructor(
+    trustRepository: TrustRepository,
+    biometricSettingsRepository: BiometricSettingsRepository,
+    updateMonitor: KeyguardUpdateMonitor,
+    private val bouncerMessageFactory: BouncerMessageFactory,
+    private val userRepository: UserRepository,
+    private val systemPropertiesHelper: SystemPropertiesHelper,
+    fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+) : BouncerMessageRepository {
+
+    private val isAnyBiometricsEnabledAndEnrolled =
+        or(
+            biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
+            biometricSettingsRepository.isFingerprintEnrolledAndEnabled,
+        )
+
+    private val wasRebootedForMainlineUpdate
+        get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
+
+    private val authFlagsBasedPromptReason: Flow<Int> =
+        combine(
+                biometricSettingsRepository.authenticationFlags,
+                trustRepository.isCurrentUserTrustManaged,
+                isAnyBiometricsEnabledAndEnrolled,
+                ::Triple
+            )
+            .map { (flags, isTrustManaged, biometricsEnrolledAndEnabled) ->
+                val trustOrBiometricsAvailable = (isTrustManaged || biometricsEnrolledAndEnabled)
+                return@map if (
+                    trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
+                ) {
+                    if (wasRebootedForMainlineUpdate) PROMPT_REASON_RESTART_FOR_MAINLINE_UPDATE
+                    else PROMPT_REASON_RESTART
+                } else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
+                    PROMPT_REASON_TIMEOUT
+                } else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
+                    PROMPT_REASON_DEVICE_ADMIN
+                } else if (isTrustManaged && flags.someAuthRequiredAfterUserRequest) {
+                    PROMPT_REASON_TRUSTAGENT_EXPIRED
+                } else if (isTrustManaged && flags.someAuthRequiredAfterTrustAgentExpired) {
+                    PROMPT_REASON_TRUSTAGENT_EXPIRED
+                } else if (trustOrBiometricsAvailable && flags.isInUserLockdown) {
+                    PROMPT_REASON_USER_REQUEST
+                } else if (
+                    trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate
+                ) {
+                    PROMPT_REASON_PREPARE_FOR_UPDATE
+                } else if (
+                    trustOrBiometricsAvailable &&
+                        flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
+                ) {
+                    PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
+                } else {
+                    PROMPT_REASON_NONE
+                }
+            }
+
+    private val biometricAuthReason: Flow<Int> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : KeyguardUpdateMonitorCallback() {
+                        override fun onBiometricAuthFailed(
+                            biometricSourceType: BiometricSourceType?
+                        ) {
+                            val promptReason =
+                                if (biometricSourceType == FINGERPRINT)
+                                    PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
+                                else if (
+                                    biometricSourceType == FACE && !updateMonitor.isFaceLockedOut
+                                ) {
+                                    PROMPT_REASON_INCORRECT_FACE_INPUT
+                                } else PROMPT_REASON_NONE
+                            trySendWithFailureLogging(promptReason, TAG, "onBiometricAuthFailed")
+                        }
+
+                        override fun onBiometricsCleared() {
+                            trySendWithFailureLogging(
+                                PROMPT_REASON_NONE,
+                                TAG,
+                                "onBiometricsCleared"
+                            )
+                        }
+
+                        override fun onBiometricAcquired(
+                            biometricSourceType: BiometricSourceType?,
+                            acquireInfo: Int
+                        ) {
+                            trySendWithFailureLogging(
+                                PROMPT_REASON_NONE,
+                                TAG,
+                                "clearBiometricPrompt for new auth session."
+                            )
+                        }
+
+                        override fun onBiometricAuthenticated(
+                            userId: Int,
+                            biometricSourceType: BiometricSourceType?,
+                            isStrongBiometric: Boolean
+                        ) {
+                            trySendWithFailureLogging(
+                                PROMPT_REASON_NONE,
+                                TAG,
+                                "onBiometricAuthenticated"
+                            )
+                        }
+                    }
+                updateMonitor.registerCallback(callback)
+                awaitClose { updateMonitor.removeCallback(callback) }
+            }
+            .distinctUntilChanged()
+
+    private val _primaryAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val primaryAuthMessage: Flow<BouncerMessageModel?> = _primaryAuthMessage
+
+    private val _faceAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val faceAcquisitionMessage: Flow<BouncerMessageModel?> = _faceAcquisitionMessage
+
+    private val _fingerprintAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?> =
+        _fingerprintAcquisitionMessage
+
+    private val _customMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val customMessage: Flow<BouncerMessageModel?> = _customMessage
+
+    override val biometricAuthMessage: Flow<BouncerMessageModel?> =
+        biometricAuthReason
+            .map {
+                if (it == PROMPT_REASON_NONE) null
+                else
+                    bouncerMessageFactory.createFromPromptReason(
+                        it,
+                        userRepository.getSelectedUserInfo().id
+                    )
+            }
+            .onStart { emit(null) }
+            .distinctUntilChanged()
+
+    override val authFlagsMessage: Flow<BouncerMessageModel?> =
+        authFlagsBasedPromptReason
+            .map {
+                if (it == PROMPT_REASON_NONE) null
+                else
+                    bouncerMessageFactory.createFromPromptReason(
+                        it,
+                        userRepository.getSelectedUserInfo().id
+                    )
+            }
+            .onStart { emit(null) }
+            .distinctUntilChanged()
+
+    // TODO (b/262838215): Replace with DeviceEntryFaceAuthRepository when the new face auth system
+    // has been launched.
+    private val faceLockedOut: Flow<Boolean> = conflatedCallbackFlow {
+        val callback =
+            object : KeyguardUpdateMonitorCallback() {
+                override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType?) {
+                    if (biometricSourceType == FACE) {
+                        trySendWithFailureLogging(
+                            updateMonitor.isFaceLockedOut,
+                            TAG,
+                            "face lock out state changed."
+                        )
+                    }
+                }
+            }
+        updateMonitor.registerCallback(callback)
+        trySendWithFailureLogging(updateMonitor.isFaceLockedOut, TAG, "face lockout initial value")
+        awaitClose { updateMonitor.removeCallback(callback) }
+    }
+
+    override val biometricLockedOutMessage: Flow<BouncerMessageModel?> =
+        combine(fingerprintAuthRepository.isLockedOut, faceLockedOut) { fp, face ->
+            return@combine if (fp) {
+                bouncerMessageFactory.createFromPromptReason(
+                    PROMPT_REASON_FINGERPRINT_LOCKED_OUT,
+                    userRepository.getSelectedUserInfo().id
+                )
+            } else if (face) {
+                bouncerMessageFactory.createFromPromptReason(
+                    PROMPT_REASON_FACE_LOCKED_OUT,
+                    userRepository.getSelectedUserInfo().id
+                )
+            } else null
+        }
+
+    override fun setPrimaryAuthMessage(value: BouncerMessageModel?) {
+        _primaryAuthMessage.value = value
+    }
+
+    override fun setFaceAcquisitionMessage(value: BouncerMessageModel?) {
+        _faceAcquisitionMessage.value = value
+    }
+
+    override fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?) {
+        _fingerprintAcquisitionMessage.value = value
+    }
+
+    override fun setCustomMessage(value: BouncerMessageModel?) {
+        _customMessage.value = value
+    }
+
+    override fun clearMessage() {
+        _fingerprintAcquisitionMessage.value = null
+        _faceAcquisitionMessage.value = null
+        _primaryAuthMessage.value = null
+        _customMessage.value = null
+    }
+
+    companion object {
+        const val TAG = "BouncerDetailedMessageRepository"
+    }
+}
+
+private fun or(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
+    flow.combine(anotherFlow) { a, b -> a || b }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
new file mode 100644
index 0000000..943216e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepository.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Provides access to bouncer-related application state. */
+@SysUISingleton
+class BouncerRepository @Inject constructor() {
+    private val _message = MutableStateFlow<String?>(null)
+    /** The user-facing message to show in the bouncer. */
+    val message: StateFlow<String?> = _message.asStateFlow()
+
+    fun setMessage(message: String?) {
+        _message.value = message
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
new file mode 100644
index 0000000..918e168
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
@@ -0,0 +1,273 @@
+/*
+ * 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.bouncer.data.repository
+
+import android.os.Build
+import android.util.Log
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.dagger.BouncerTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Encapsulates app state for the lock screen primary and alternate bouncer.
+ *
+ * Make sure to add newly added flows to the logger.
+ */
+interface KeyguardBouncerRepository {
+    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
+    val primaryBouncerShow: StateFlow<Boolean>
+    val primaryBouncerShowingSoon: StateFlow<Boolean>
+    val primaryBouncerStartingToHide: StateFlow<Boolean>
+    val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
+    /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
+    val primaryBouncerScrimmed: StateFlow<Boolean>
+    /**
+     * Set how much of the notification panel is showing on the screen.
+     *
+     * ```
+     *      0f = panel fully hidden = bouncer fully showing
+     *      1f = panel fully showing = bouncer fully hidden
+     * ```
+     */
+    val panelExpansionAmount: StateFlow<Float>
+    val keyguardPosition: StateFlow<Float>
+    val isBackButtonEnabled: StateFlow<Boolean?>
+    /** Determines if user is already unlocked */
+    val keyguardAuthenticated: StateFlow<Boolean?>
+    val showMessage: StateFlow<BouncerShowMessageModel?>
+    val resourceUpdateRequests: StateFlow<Boolean>
+    val alternateBouncerVisible: StateFlow<Boolean>
+    val alternateBouncerUIAvailable: StateFlow<Boolean>
+    val sideFpsShowing: StateFlow<Boolean>
+
+    var lastAlternateBouncerVisibleTime: Long
+
+    fun setPrimaryScrimmed(isScrimmed: Boolean)
+
+    fun setPrimaryShow(isShowing: Boolean)
+
+    fun setPrimaryShowingSoon(showingSoon: Boolean)
+
+    fun setPrimaryStartingToHide(startingToHide: Boolean)
+
+    fun setPrimaryStartDisappearAnimation(runnable: Runnable?)
+
+    fun setPanelExpansion(panelExpansion: Float)
+
+    fun setKeyguardPosition(keyguardPosition: Float)
+
+    fun setResourceUpdateRequests(willUpdateResources: Boolean)
+
+    fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?)
+
+    fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?)
+
+    fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean)
+
+    fun setAlternateVisible(isVisible: Boolean)
+
+    fun setAlternateBouncerUIAvailable(isAvailable: Boolean)
+
+    fun setSideFpsShowing(isShowing: Boolean)
+}
+
+@SysUISingleton
+class KeyguardBouncerRepositoryImpl
+@Inject
+constructor(
+    private val clock: SystemClock,
+    @Application private val applicationScope: CoroutineScope,
+    @BouncerTableLog private val buffer: TableLogBuffer,
+) : KeyguardBouncerRepository {
+    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
+    private val _primaryBouncerShow = MutableStateFlow(false)
+    override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
+    private val _primaryBouncerShowingSoon = MutableStateFlow(false)
+    override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
+    private val _primaryBouncerStartingToHide = MutableStateFlow(false)
+    override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
+    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
+    override val primaryBouncerStartingDisappearAnimation =
+        _primaryBouncerDisappearAnimation.asStateFlow()
+    /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
+    private val _primaryBouncerScrimmed = MutableStateFlow(false)
+    override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
+    /**
+     * Set how much of the notification panel is showing on the screen.
+     *
+     * ```
+     *      0f = panel fully hidden = bouncer fully showing
+     *      1f = panel fully showing = bouncer fully hidden
+     * ```
+     */
+    private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
+    override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
+    private val _keyguardPosition = MutableStateFlow(0f)
+    override val keyguardPosition = _keyguardPosition.asStateFlow()
+    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
+    override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
+    private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
+    /** Determines if user is already unlocked */
+    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
+    override val showMessage = _showMessage.asStateFlow()
+    private val _resourceUpdateRequests = MutableStateFlow(false)
+    override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+    /** Values associated with the AlternateBouncer */
+    private val _alternateBouncerVisible = MutableStateFlow(false)
+    override val alternateBouncerVisible = _alternateBouncerVisible.asStateFlow()
+    override var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
+    private val _alternateBouncerUIAvailable = MutableStateFlow(false)
+    override val alternateBouncerUIAvailable: StateFlow<Boolean> =
+        _alternateBouncerUIAvailable.asStateFlow()
+    private val _sideFpsShowing = MutableStateFlow(false)
+    override val sideFpsShowing: StateFlow<Boolean> = _sideFpsShowing.asStateFlow()
+
+    init {
+        setUpLogging()
+    }
+
+    override fun setPrimaryScrimmed(isScrimmed: Boolean) {
+        _primaryBouncerScrimmed.value = isScrimmed
+    }
+
+    override fun setAlternateVisible(isVisible: Boolean) {
+        if (isVisible && !_alternateBouncerVisible.value) {
+            lastAlternateBouncerVisibleTime = clock.uptimeMillis()
+        } else if (!isVisible) {
+            lastAlternateBouncerVisibleTime = NOT_VISIBLE
+        }
+        _alternateBouncerVisible.value = isVisible
+    }
+
+    override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+        _alternateBouncerUIAvailable.value = isAvailable
+    }
+
+    override fun setPrimaryShow(isShowing: Boolean) {
+        _primaryBouncerShow.value = isShowing
+    }
+
+    override fun setPrimaryShowingSoon(showingSoon: Boolean) {
+        _primaryBouncerShowingSoon.value = showingSoon
+    }
+
+    override fun setPrimaryStartingToHide(startingToHide: Boolean) {
+        _primaryBouncerStartingToHide.value = startingToHide
+    }
+
+    override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
+        _primaryBouncerDisappearAnimation.value = runnable
+    }
+
+    override fun setPanelExpansion(panelExpansion: Float) {
+        _panelExpansionAmount.value = panelExpansion
+    }
+
+    override fun setKeyguardPosition(keyguardPosition: Float) {
+        _keyguardPosition.value = keyguardPosition
+    }
+
+    override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
+        _resourceUpdateRequests.value = willUpdateResources
+    }
+
+    override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
+        _showMessage.value = bouncerShowMessageModel
+    }
+
+    override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+        _keyguardAuthenticated.value = keyguardAuthenticated
+    }
+
+    override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
+        _isBackButtonEnabled.value = isBackButtonEnabled
+    }
+
+    override fun setSideFpsShowing(isShowing: Boolean) {
+        _sideFpsShowing.value = isShowing
+    }
+
+    /** Sets up logs for state flows. */
+    private fun setUpLogging() {
+        if (!Build.IS_DEBUGGABLE) {
+            return
+        }
+
+        primaryBouncerShow
+            .logDiffsForTable(buffer, "", "PrimaryBouncerShow", false)
+            .onEach { Log.d(TAG, "Keyguard Bouncer is ${if (it) "showing" else "hiding."}") }
+            .launchIn(applicationScope)
+        primaryBouncerShowingSoon
+            .logDiffsForTable(buffer, "", "PrimaryBouncerShowingSoon", false)
+            .launchIn(applicationScope)
+        primaryBouncerStartingToHide
+            .logDiffsForTable(buffer, "", "PrimaryBouncerStartingToHide", false)
+            .launchIn(applicationScope)
+        primaryBouncerStartingDisappearAnimation
+            .map { it != null }
+            .logDiffsForTable(buffer, "", "PrimaryBouncerStartingDisappearAnimation", false)
+            .launchIn(applicationScope)
+        primaryBouncerScrimmed
+            .logDiffsForTable(buffer, "", "PrimaryBouncerScrimmed", false)
+            .launchIn(applicationScope)
+        panelExpansionAmount
+            .map { (it * 1000).toInt() }
+            .logDiffsForTable(buffer, "", "PanelExpansionAmountMillis", -1)
+            .launchIn(applicationScope)
+        keyguardPosition
+            .map { it.toInt() }
+            .logDiffsForTable(buffer, "", "KeyguardPosition", -1)
+            .launchIn(applicationScope)
+        isBackButtonEnabled
+            .filterNotNull()
+            .logDiffsForTable(buffer, "", "IsBackButtonEnabled", false)
+            .launchIn(applicationScope)
+        showMessage
+            .map { it?.message }
+            .logDiffsForTable(buffer, "", "ShowMessage", null)
+            .launchIn(applicationScope)
+        resourceUpdateRequests
+            .logDiffsForTable(buffer, "", "ResourceUpdateRequests", false)
+            .launchIn(applicationScope)
+        alternateBouncerUIAvailable
+            .logDiffsForTable(buffer, "", "IsAlternateBouncerUIAvailable", false)
+            .launchIn(applicationScope)
+        sideFpsShowing
+            .logDiffsForTable(buffer, "", "isSideFpsShowing", false)
+            .launchIn(applicationScope)
+    }
+
+    companion object {
+        private const val NOT_VISIBLE = -1L
+        private const val TAG = "KeyguardBouncerRepositoryImpl"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
new file mode 100644
index 0000000..9a7fec1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.bouncer.domain.interactor
+
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */
+@SysUISingleton
+class AlternateBouncerInteractor
+@Inject
+constructor(
+    private val statusBarStateController: StatusBarStateController,
+    private val keyguardStateController: KeyguardStateController,
+    private val bouncerRepository: KeyguardBouncerRepository,
+    private val biometricSettingsRepository: BiometricSettingsRepository,
+    private val systemClock: SystemClock,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) {
+    var receivedDownTouch = false
+    val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
+    private val alternateBouncerUiAvailableFromSource: HashSet<String> = HashSet()
+
+    /**
+     * Sets the correct bouncer states to show the alternate bouncer if it can show.
+     *
+     * @return whether alternateBouncer is visible
+     */
+    fun show(): Boolean {
+        bouncerRepository.setAlternateVisible(canShowAlternateBouncerForFingerprint())
+        return isVisibleState()
+    }
+
+    /**
+     * Sets the correct bouncer states to hide the bouncer. Should only be called through
+     * StatusBarKeyguardViewManager until ScrimController is refactored to use
+     * alternateBouncerInteractor.
+     *
+     * @return true if the alternate bouncer was newly hidden, else false.
+     */
+    fun hide(): Boolean {
+        receivedDownTouch = false
+        val wasAlternateBouncerVisible = isVisibleState()
+        bouncerRepository.setAlternateVisible(false)
+        return wasAlternateBouncerVisible && !isVisibleState()
+    }
+
+    fun isVisibleState(): Boolean {
+        return bouncerRepository.alternateBouncerVisible.value
+    }
+
+    fun setAlternateBouncerUIAvailable(isAvailable: Boolean, token: String) {
+        if (isAvailable) {
+            alternateBouncerUiAvailableFromSource.add(token)
+        } else {
+            alternateBouncerUiAvailableFromSource.remove(token)
+        }
+        bouncerRepository.setAlternateBouncerUIAvailable(
+            alternateBouncerUiAvailableFromSource.isNotEmpty()
+        )
+    }
+
+    fun canShowAlternateBouncerForFingerprint(): Boolean {
+        return bouncerRepository.alternateBouncerUIAvailable.value &&
+            biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value &&
+            !keyguardUpdateMonitor.isFingerprintLockedOut &&
+            !keyguardStateController.isUnlocked &&
+            !statusBarStateController.isDozing
+    }
+
+    /**
+     * Whether the alt bouncer has shown for a minimum time before allowing touches to dismiss the
+     * alternate bouncer and show the primary bouncer.
+     */
+    fun hasAlternateBouncerShownWithMinTime(): Boolean {
+        return (systemClock.uptimeMillis() - bouncerRepository.lastAlternateBouncerVisibleTime) >
+            MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS
+    }
+    /**
+     * Should only be called through StatusBarKeyguardViewManager which propagates the source of
+     * truth to other concerned controllers. Will hide the alternate bouncer if it's no longer
+     * allowed to show.
+     *
+     * @return true if the alternate bouncer was newly hidden, else false.
+     */
+    fun maybeHide(): Boolean {
+        if (isVisibleState() && !canShowAlternateBouncerForFingerprint()) {
+            return hide()
+        }
+        return false
+    }
+
+    companion object {
+        private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 1d2fce7..1bf3a9e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -17,21 +17,22 @@
 package com.android.systemui.bouncer.domain.interactor
 
 import android.content.Context
-import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.data.repo.BouncerRepository
-import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
+import com.android.systemui.bouncer.data.repository.BouncerRepository
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import com.android.systemui.util.kotlin.pairwise
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -39,24 +40,26 @@
 import kotlinx.coroutines.launch
 
 /** Encapsulates business logic and application state accessing use-cases. */
+@SysUISingleton
 class BouncerInteractor
-@AssistedInject
+@Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
     @Application private val applicationContext: Context,
     private val repository: BouncerRepository,
     private val authenticationInteractor: AuthenticationInteractor,
     private val sceneInteractor: SceneInteractor,
-    @Assisted private val containerName: String,
+    featureFlags: FeatureFlags,
 ) {
 
     /** The user-facing message to show in the bouncer. */
     val message: StateFlow<String?> =
         combine(
                 repository.message,
-                repository.throttling,
-            ) { message, throttling ->
-                messageOrThrottlingMessage(message, throttling)
+                authenticationInteractor.isThrottled,
+                authenticationInteractor.throttling,
+            ) { message, isThrottled, throttling ->
+                messageOrThrottlingMessage(message, isThrottled, throttling)
             }
             .stateIn(
                 scope = applicationScope,
@@ -64,69 +67,65 @@
                 initialValue =
                     messageOrThrottlingMessage(
                         repository.message.value,
-                        repository.throttling.value,
+                        authenticationInteractor.isThrottled.value,
+                        authenticationInteractor.throttling.value,
                     )
             )
 
-    /**
-     * The currently-configured authentication method. This determines how the authentication
-     * challenge is completed in order to unlock an otherwise locked device.
-     */
-    val authenticationMethod: StateFlow<AuthenticationMethodModel> =
-        authenticationInteractor.authenticationMethod
+    /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
+    val throttling: StateFlow<AuthenticationThrottlingModel> = authenticationInteractor.throttling
 
-    /** The current authentication throttling state. If `null`, there's no throttling. */
-    val throttling: StateFlow<AuthenticationThrottledModel?> = repository.throttling
+    /**
+     * Whether currently throttled and the user has to wait before being able to try another
+     * authentication attempt.
+     */
+    val isThrottled: StateFlow<Boolean> = authenticationInteractor.isThrottled
+
+    /** Whether the auto confirm feature is enabled for the currently-selected user. */
+    val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled
+
+    /** The length of the hinted PIN, or `null`, if pin length hint should not be shown. */
+    val hintedPinLength: StateFlow<Int?> = authenticationInteractor.hintedPinLength
+
+    /** Whether the pattern should be visible for the currently-selected user. */
+    val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible
 
     init {
-        applicationScope.launch {
-            combine(
-                    sceneInteractor.currentScene(containerName),
-                    authenticationInteractor.authenticationMethod,
-                    ::Pair,
-                )
-                .collect { (currentScene, authMethod) ->
-                    if (currentScene.key == SceneKey.Bouncer) {
-                        when (authMethod) {
-                            is AuthenticationMethodModel.None ->
-                                sceneInteractor.setCurrentScene(
-                                    containerName,
-                                    SceneModel(SceneKey.Gone),
-                                )
-                            is AuthenticationMethodModel.Swipe ->
-                                sceneInteractor.setCurrentScene(
-                                    containerName,
-                                    SceneModel(SceneKey.Lockscreen),
-                                )
-                            else -> Unit
-                        }
+        if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+            // Clear the message if moved from throttling to no-longer throttling.
+            applicationScope.launch {
+                isThrottled.pairwise().collect { (wasThrottled, currentlyThrottled) ->
+                    if (wasThrottled && !currentlyThrottled) {
+                        clearMessage()
                     }
                 }
+            }
         }
     }
 
     /**
      * Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown.
      *
-     * @param containerName The name of the scene container to show the bouncer in.
      * @param message An optional message to show to the user in the bouncer.
      */
     fun showOrUnlockDevice(
-        containerName: String,
         message: String? = null,
     ) {
-        if (authenticationInteractor.isAuthenticationRequired()) {
-            repository.setMessage(message ?: promptMessage(authenticationMethod.value))
-            sceneInteractor.setCurrentScene(
-                containerName = containerName,
-                scene = SceneModel(SceneKey.Bouncer),
-            )
-        } else {
-            authenticationInteractor.unlockDevice()
-            sceneInteractor.setCurrentScene(
-                containerName = containerName,
-                scene = SceneModel(SceneKey.Gone),
-            )
+        applicationScope.launch {
+            if (authenticationInteractor.isAuthenticationRequired()) {
+                repository.setMessage(
+                    message ?: promptMessage(authenticationInteractor.getAuthenticationMethod())
+                )
+                sceneInteractor.changeScene(
+                    scene = SceneModel(SceneKey.Bouncer),
+                    loggingReason = "request to unlock device while authentication required",
+                )
+            } else {
+                sceneInteractor.changeScene(
+                    scene = SceneModel(SceneKey.Gone),
+                    loggingReason = "request to unlock device while authentication isn't required",
+                )
+            }
         }
     }
 
@@ -135,7 +134,9 @@
      * method.
      */
     fun resetMessage() {
-        repository.setMessage(promptMessage(authenticationMethod.value))
+        applicationScope.launch {
+            repository.setMessage(promptMessage(authenticationInteractor.getAuthenticationMethod()))
+        }
     }
 
     /** Removes the user-facing message. */
@@ -149,47 +150,31 @@
      * If the input is correct, the device will be unlocked and the lock screen and bouncer will be
      * dismissed and hidden.
      *
+     * If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method
+     * supports auto-confirming, and the input's length is at least the code's length. Otherwise,
+     * `null` is returned.
+     *
      * @param input The input from the user to try to authenticate with. This can be a list of
      *   different things, based on the current authentication method.
-     * @return `true` if the authentication succeeded and the device is now unlocked; `false`
-     *   otherwise.
+     * @param tryAutoConfirm `true` if called while the user inputs the code, without an explicit
+     *   request to validate.
+     * @return `true` if the authentication succeeded and the device is now unlocked; `false` when
+     *   authentication failed, `null` if the check was not performed.
      */
-    fun authenticate(
+    suspend fun authenticate(
         input: List<Any>,
-    ): Boolean {
-        if (repository.throttling.value != null) {
-            return false
-        }
+        tryAutoConfirm: Boolean = false,
+    ): Boolean? {
+        val isAuthenticated =
+            authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null
 
-        val isAuthenticated = authenticationInteractor.authenticate(input)
-        val failedAttempts = authenticationInteractor.failedAuthenticationAttempts.value
-        when {
-            isAuthenticated -> {
-                repository.setThrottling(null)
-                sceneInteractor.setCurrentScene(
-                    containerName = containerName,
-                    scene = SceneModel(SceneKey.Gone),
-                )
-            }
-            failedAttempts >= THROTTLE_AGGRESSIVELY_AFTER || failedAttempts % THROTTLE_EVERY == 0 ->
-                applicationScope.launch {
-                    var remainingDurationSec = THROTTLE_DURATION_SEC
-                    while (remainingDurationSec > 0) {
-                        repository.setThrottling(
-                            AuthenticationThrottledModel(
-                                failedAttemptCount = failedAttempts,
-                                totalDurationSec = THROTTLE_DURATION_SEC,
-                                remainingDurationSec = remainingDurationSec,
-                            )
-                        )
-                        remainingDurationSec--
-                        delay(1000)
-                    }
-
-                    repository.setThrottling(null)
-                    clearMessage()
-                }
-            else -> repository.setMessage(errorMessage(authenticationMethod.value))
+        if (isAuthenticated) {
+            sceneInteractor.changeScene(
+                scene = SceneModel(SceneKey.Gone),
+                loggingReason = "successful authentication",
+            )
+        } else {
+            repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod()))
         }
 
         return isAuthenticated
@@ -197,7 +182,7 @@
 
     private fun promptMessage(authMethod: AuthenticationMethodModel): String {
         return when (authMethod) {
-            is AuthenticationMethodModel.PIN ->
+            is AuthenticationMethodModel.Pin ->
                 applicationContext.getString(R.string.keyguard_enter_your_pin)
             is AuthenticationMethodModel.Password ->
                 applicationContext.getString(R.string.keyguard_enter_your_password)
@@ -209,7 +194,7 @@
 
     private fun errorMessage(authMethod: AuthenticationMethodModel): String {
         return when (authMethod) {
-            is AuthenticationMethodModel.PIN -> applicationContext.getString(R.string.kg_wrong_pin)
+            is AuthenticationMethodModel.Pin -> applicationContext.getString(R.string.kg_wrong_pin)
             is AuthenticationMethodModel.Password ->
                 applicationContext.getString(R.string.kg_wrong_password)
             is AuthenticationMethodModel.Pattern ->
@@ -220,29 +205,17 @@
 
     private fun messageOrThrottlingMessage(
         message: String?,
-        throttling: AuthenticationThrottledModel?,
+        isThrottled: Boolean,
+        throttlingModel: AuthenticationThrottlingModel,
     ): String {
         return when {
-            throttling != null ->
+            isThrottled ->
                 applicationContext.getString(
                     com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown,
-                    throttling.remainingDurationSec,
+                    throttlingModel.remainingMs.milliseconds.inWholeSeconds,
                 )
             message != null -> message
             else -> ""
         }
     }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(
-            containerName: String,
-        ): BouncerInteractor
-    }
-
-    companion object {
-        @VisibleForTesting const val THROTTLE_DURATION_SEC = 30
-        @VisibleForTesting const val THROTTLE_AGGRESSIVELY_AFTER = 15
-        @VisibleForTesting const val THROTTLE_EVERY = 5
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageAuditLogger.kt
new file mode 100644
index 0000000..497747f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageAuditLogger.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.os.Build
+import android.util.Log
+import com.android.systemui.CoreStartable
+import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+
+private val TAG = BouncerMessageAuditLogger::class.simpleName!!
+
+/** Logger that echoes bouncer messages state to logcat in debuggable builds. */
+@SysUISingleton
+class BouncerMessageAuditLogger
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val repository: BouncerMessageRepository,
+    private val interactor: BouncerMessageInteractor,
+) : CoreStartable {
+    override fun start() {
+        if (Build.isDebuggable()) {
+            collectAndLog(repository.biometricAuthMessage, "biometricMessage: ")
+            collectAndLog(repository.primaryAuthMessage, "primaryAuthMessage: ")
+            collectAndLog(repository.customMessage, "customMessage: ")
+            collectAndLog(repository.faceAcquisitionMessage, "faceAcquisitionMessage: ")
+            collectAndLog(
+                repository.fingerprintAcquisitionMessage,
+                "fingerprintAcquisitionMessage: "
+            )
+            collectAndLog(repository.authFlagsMessage, "authFlagsMessage: ")
+            collectAndLog(interactor.bouncerMessage, "interactor.bouncerMessage: ")
+        }
+    }
+
+    private fun collectAndLog(flow: Flow<BouncerMessageModel?>, context: String) {
+        scope.launch { flow.collect { Log.d(TAG, context + it) } }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
new file mode 100644
index 0000000..fe01d08
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.os.CountDownTimer
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT
+import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class BouncerMessageInteractor
+@Inject
+constructor(
+    private val repository: BouncerMessageRepository,
+    private val factory: BouncerMessageFactory,
+    private val userRepository: UserRepository,
+    private val countDownTimerUtil: CountDownTimerUtil,
+    private val featureFlags: FeatureFlags,
+) {
+    fun onPrimaryAuthLockedOut(secondsBeforeLockoutReset: Long) {
+        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+        val callback =
+            object : CountDownTimerCallback {
+                override fun onFinish() {
+                    repository.clearMessage()
+                }
+
+                override fun onTick(millisUntilFinished: Long) {
+                    val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt()
+                    val message =
+                        factory.createFromPromptReason(
+                            reason = PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT,
+                            userId = userRepository.getSelectedUserInfo().id
+                        )
+                    message?.message?.animate = false
+                    message?.message?.formatterArgs =
+                        mutableMapOf<String, Any>(Pair("count", secondsRemaining))
+                    repository.setPrimaryAuthMessage(message)
+                }
+            }
+        countDownTimerUtil.startNewTimer(secondsBeforeLockoutReset * 1000, 1000, callback)
+    }
+
+    fun onPrimaryAuthIncorrectAttempt() {
+        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+        repository.setPrimaryAuthMessage(
+            factory.createFromPromptReason(
+                PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+                userRepository.getSelectedUserInfo().id
+            )
+        )
+    }
+
+    fun setFingerprintAcquisitionMessage(value: String?) {
+        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+        repository.setFingerprintAcquisitionMessage(
+            if (value != null) {
+                factory.createFromPromptReason(
+                    PROMPT_REASON_DEFAULT,
+                    userRepository.getSelectedUserInfo().id,
+                    secondaryMsgOverride = value
+                )
+            } else {
+                null
+            }
+        )
+    }
+
+    fun setFaceAcquisitionMessage(value: String?) {
+        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+        repository.setFaceAcquisitionMessage(
+            if (value != null) {
+                factory.createFromPromptReason(
+                    PROMPT_REASON_DEFAULT,
+                    userRepository.getSelectedUserInfo().id,
+                    secondaryMsgOverride = value
+                )
+            } else {
+                null
+            }
+        )
+    }
+
+    fun setCustomMessage(value: String?) {
+        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+        repository.setCustomMessage(
+            if (value != null) {
+                factory.createFromPromptReason(
+                    PROMPT_REASON_DEFAULT,
+                    userRepository.getSelectedUserInfo().id,
+                    secondaryMsgOverride = value
+                )
+            } else {
+                null
+            }
+        )
+    }
+
+    fun onPrimaryBouncerUserInput() {
+        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+        repository.clearMessage()
+    }
+
+    fun onBouncerBeingHidden() {
+        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+        repository.clearMessage()
+    }
+
+    private fun firstNonNullMessage(
+        oneMessageModel: Flow<BouncerMessageModel?>,
+        anotherMessageModel: Flow<BouncerMessageModel?>
+    ): Flow<BouncerMessageModel?> {
+        return oneMessageModel.combine(anotherMessageModel) { a, b -> a ?: b }
+    }
+
+    // Null if feature flag is enabled which gets ignored always or empty bouncer message model that
+    // always maps to an empty string.
+    private fun nullOrEmptyMessage() =
+        flowOf(
+            if (featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) null else factory.emptyMessage()
+        )
+
+    val bouncerMessage =
+        listOf(
+                nullOrEmptyMessage(),
+                repository.primaryAuthMessage,
+                repository.biometricAuthMessage,
+                repository.fingerprintAcquisitionMessage,
+                repository.faceAcquisitionMessage,
+                repository.customMessage,
+                repository.authFlagsMessage,
+                repository.biometricLockedOutMessage,
+                userRepository.selectedUserInfo.map {
+                    factory.createFromPromptReason(PROMPT_REASON_DEFAULT, it.id)
+                },
+            )
+            .reduce(::firstNonNullMessage)
+            .distinctUntilChanged()
+}
+
+interface CountDownTimerCallback {
+    fun onFinish()
+    fun onTick(millisUntilFinished: Long)
+}
+
+@SysUISingleton
+open class CountDownTimerUtil @Inject constructor() {
+
+    /**
+     * Start a new count down timer that runs for [millisInFuture] with a tick every
+     * [millisInterval]
+     */
+    fun startNewTimer(
+        millisInFuture: Long,
+        millisInterval: Long,
+        callback: CountDownTimerCallback,
+    ): CountDownTimer {
+        return object : CountDownTimer(millisInFuture, millisInterval) {
+                override fun onFinish() = callback.onFinish()
+
+                override fun onTick(millisUntilFinished: Long) =
+                    callback.onTick(millisUntilFinished)
+            }
+            .start()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractor.kt
new file mode 100644
index 0000000..5fbe99a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractor.kt
@@ -0,0 +1,140 @@
+/*
+ * 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.bouncer.domain.interactor
+
+import android.view.View
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.ListenerSet
+import javax.inject.Inject
+
+/** Interactor to add and remove callbacks for the bouncer. */
+@SysUISingleton
+class PrimaryBouncerCallbackInteractor @Inject constructor() {
+    private var resetCallbacks = ListenerSet<KeyguardResetCallback>()
+    private var expansionCallbacks = ArrayList<PrimaryBouncerExpansionCallback>()
+
+    /** Add a KeyguardResetCallback. */
+    fun addKeyguardResetCallback(callback: KeyguardResetCallback) {
+        resetCallbacks.addIfAbsent(callback)
+    }
+
+    /** Remove a KeyguardResetCallback. */
+    fun removeKeyguardResetCallback(callback: KeyguardResetCallback) {
+        resetCallbacks.remove(callback)
+    }
+
+    /** Adds a callback to listen to bouncer expansion updates. */
+    fun addBouncerExpansionCallback(callback: PrimaryBouncerExpansionCallback) {
+        if (!expansionCallbacks.contains(callback)) {
+            expansionCallbacks.add(callback)
+        }
+    }
+
+    /**
+     * Removes a previously added callback. If the callback was never added, this method does
+     * nothing.
+     */
+    fun removeBouncerExpansionCallback(callback: PrimaryBouncerExpansionCallback) {
+        expansionCallbacks.remove(callback)
+    }
+
+    /** Propagate fully shown to bouncer expansion callbacks. */
+    fun dispatchFullyShown() {
+        for (callback in expansionCallbacks) {
+            callback.onFullyShown()
+        }
+    }
+
+    /** Propagate starting to hide to bouncer expansion callbacks. */
+    fun dispatchStartingToHide() {
+        for (callback in expansionCallbacks) {
+            callback.onStartingToHide()
+        }
+    }
+
+    /** Propagate starting to show to bouncer expansion callbacks. */
+    fun dispatchStartingToShow() {
+        for (callback in expansionCallbacks) {
+            callback.onStartingToShow()
+        }
+    }
+
+    /** Propagate fully hidden to bouncer expansion callbacks. */
+    fun dispatchFullyHidden() {
+        for (callback in expansionCallbacks) {
+            callback.onFullyHidden()
+        }
+    }
+
+    /** Propagate expansion changes to bouncer expansion callbacks. */
+    fun dispatchExpansionChanged(expansion: Float) {
+        for (callback in expansionCallbacks) {
+            callback.onExpansionChanged(expansion)
+        }
+    }
+    /** Propagate visibility changes to bouncer expansion callbacks. */
+    fun dispatchVisibilityChanged(visibility: Int) {
+        for (callback in expansionCallbacks) {
+            callback.onVisibilityChanged(visibility == View.VISIBLE)
+        }
+    }
+
+    /** Propagate keyguard reset. */
+    fun dispatchReset() {
+        for (callback in resetCallbacks) {
+            callback.onKeyguardReset()
+        }
+    }
+
+    /** Callback updated when the primary bouncer's show and hide states change. */
+    interface PrimaryBouncerExpansionCallback {
+        /**
+         * Invoked when the bouncer expansion reaches [EXPANSION_VISIBLE]. This is NOT called each
+         * time the bouncer is shown, but rather only when the fully shown amount has changed based
+         * on the panel expansion. The bouncer's visibility can still change when the expansion
+         * amount hasn't changed. See [PrimaryBouncerInteractor.isFullyShowing] for the checks for
+         * the bouncer showing state.
+         */
+        fun onFullyShown() {}
+
+        /** Invoked when the bouncer is starting to transition to a hidden state. */
+        fun onStartingToHide() {}
+
+        /** Invoked when the bouncer is starting to transition to a visible state. */
+        fun onStartingToShow() {}
+
+        /** Invoked when the bouncer expansion reaches [EXPANSION_HIDDEN]. */
+        fun onFullyHidden() {}
+
+        /**
+         * From 0f [EXPANSION_VISIBLE] when fully visible to 1f [EXPANSION_HIDDEN] when fully hidden
+         */
+        fun onExpansionChanged(bouncerHideAmount: Float) {}
+
+        /**
+         * Invoked when visibility of KeyguardBouncer has changed. Note the bouncer expansion can be
+         * [EXPANSION_VISIBLE], but the view's visibility can be [View.INVISIBLE].
+         */
+        fun onVisibilityChanged(isVisible: Boolean) {}
+    }
+
+    interface KeyguardResetCallback {
+        fun onKeyguardReset()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
new file mode 100644
index 0000000..c486603
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -0,0 +1,410 @@
+/*
+ * 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.bouncer.domain.interactor
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.hardware.biometrics.BiometricSourceType
+import android.os.Handler
+import android.os.Trace
+import android.util.Log
+import android.view.View
+import com.android.keyguard.KeyguardConstants
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.DejankUtils
+import com.android.systemui.R
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
+import com.android.systemui.bouncer.ui.BouncerView
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Encapsulates business logic for interacting with the lock-screen primary (pin/pattern/password)
+ * bouncer.
+ */
+@SysUISingleton
+class PrimaryBouncerInteractor
+@Inject
+constructor(
+    private val repository: KeyguardBouncerRepository,
+    private val primaryBouncerView: BouncerView,
+    @Main private val mainHandler: Handler,
+    private val keyguardStateController: KeyguardStateController,
+    private val keyguardSecurityModel: KeyguardSecurityModel,
+    private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
+    private val falsingCollector: FalsingCollector,
+    private val dismissCallbackRegistry: DismissCallbackRegistry,
+    private val context: Context,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val trustRepository: TrustRepository,
+    private val featureFlags: FeatureFlags,
+    @Application private val applicationScope: CoroutineScope,
+) {
+    private val passiveAuthBouncerDelay =
+        context.resources.getInteger(R.integer.primary_bouncer_passive_auth_delay).toLong()
+    /** Runnable to show the primary bouncer. */
+    val showRunnable = Runnable {
+        repository.setPrimaryShow(true)
+        repository.setPrimaryShowingSoon(false)
+        primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE)
+    }
+
+    val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
+    val isShowing: StateFlow<Boolean> = repository.primaryBouncerShow
+    val startingToHide: Flow<Unit> = repository.primaryBouncerStartingToHide.filter { it }.map {}
+    val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
+    val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
+    val startingDisappearAnimation: Flow<Runnable> =
+        repository.primaryBouncerStartingDisappearAnimation.filterNotNull()
+    val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
+    val keyguardPosition: Flow<Float> = repository.keyguardPosition
+    val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
+    /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
+    val bouncerExpansion: Flow<Float> =
+        combine(repository.panelExpansionAmount, repository.primaryBouncerShow) {
+            panelExpansion,
+            primaryBouncerIsShowing ->
+            if (primaryBouncerIsShowing) {
+                1f - panelExpansion
+            } else {
+                0f
+            }
+        }
+    /** Allow for interaction when just about fully visible */
+    val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
+    val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
+    private var currentUserActiveUnlockRunning = false
+
+    /** This callback needs to be a class field so it does not get garbage collected. */
+    val keyguardUpdateMonitorCallback =
+        object : KeyguardUpdateMonitorCallback() {
+            override fun onBiometricRunningStateChanged(
+                running: Boolean,
+                biometricSourceType: BiometricSourceType?
+            ) {
+                updateSideFpsVisibility()
+            }
+
+            override fun onStrongAuthStateChanged(userId: Int) {
+                updateSideFpsVisibility()
+            }
+        }
+
+    init {
+        keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+        if (featureFlags.isEnabled(Flags.DELAY_BOUNCER)) {
+            applicationScope.launch {
+                trustRepository.isCurrentUserActiveUnlockRunning.collect {
+                    currentUserActiveUnlockRunning = it
+                }
+            }
+        }
+    }
+
+    // TODO(b/243685699): Move isScrimmed logic to data layer.
+    // TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
+    /** Show the bouncer if necessary and set the relevant states. */
+    @JvmOverloads
+    fun show(isScrimmed: Boolean) {
+        // Reset some states as we show the bouncer.
+        repository.setKeyguardAuthenticated(null)
+        repository.setPrimaryStartingToHide(false)
+
+        val resumeBouncer =
+            (isBouncerShowing() || repository.primaryBouncerShowingSoon.value) &&
+                needsFullscreenBouncer()
+
+        Trace.beginSection("KeyguardBouncer#show")
+        repository.setPrimaryScrimmed(isScrimmed)
+        if (isScrimmed) {
+            setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
+        }
+
+        // In this special case, we want to hide the bouncer and show it again. We want to emit
+        // show(true) again so that we can reinflate the new view.
+        if (resumeBouncer) {
+            repository.setPrimaryShow(false)
+        }
+
+        if (primaryBouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
+            // Keyguard is done.
+            return
+        }
+
+        repository.setPrimaryShowingSoon(true)
+        if (usePrimaryBouncerPassiveAuthDelay()) {
+            Log.d(TAG, "delay bouncer, passive auth may succeed")
+            mainHandler.postDelayed(showRunnable, passiveAuthBouncerDelay)
+        } else {
+            DejankUtils.postAfterTraversal(showRunnable)
+        }
+        keyguardStateController.notifyPrimaryBouncerShowing(true)
+        primaryBouncerCallbackInteractor.dispatchStartingToShow()
+        Trace.endSection()
+    }
+
+    /** Sets the correct bouncer states to hide the bouncer. */
+    fun hide() {
+        Trace.beginSection("KeyguardBouncer#hide")
+        if (isFullyShowing()) {
+            SysUiStatsLog.write(
+                SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
+                SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN
+            )
+            dismissCallbackRegistry.notifyDismissCancelled()
+        }
+
+        repository.setPrimaryStartDisappearAnimation(null)
+        falsingCollector.onBouncerHidden()
+        keyguardStateController.notifyPrimaryBouncerShowing(false /* showing */)
+        cancelShowRunnable()
+        repository.setPrimaryShowingSoon(false)
+        repository.setPrimaryShow(false)
+        repository.setPanelExpansion(EXPANSION_HIDDEN)
+        primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
+        Trace.endSection()
+    }
+
+    /**
+     * Sets the panel expansion which is calculated further upstream. Panel expansion is from 0f
+     * (panel fully hidden) to 1f (panel fully showing). As the panel shows (from 0f => 1f), the
+     * bouncer hides and as the panel becomes hidden (1f => 0f), the bouncer starts to show.
+     * Therefore, a panel expansion of 1f represents the bouncer fully hidden and a panel expansion
+     * of 0f represents the bouncer fully showing.
+     */
+    fun setPanelExpansion(expansion: Float) {
+        val oldExpansion = repository.panelExpansionAmount.value
+        val expansionChanged = oldExpansion != expansion
+        if (repository.primaryBouncerStartingDisappearAnimation.value == null) {
+            repository.setPanelExpansion(expansion)
+        }
+
+        if (
+            expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE &&
+                oldExpansion != KeyguardBouncerConstants.EXPANSION_VISIBLE
+        ) {
+            falsingCollector.onBouncerShown()
+            primaryBouncerCallbackInteractor.dispatchFullyShown()
+        } else if (
+            expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN &&
+                oldExpansion != KeyguardBouncerConstants.EXPANSION_HIDDEN
+        ) {
+            /*
+             * There are cases where #hide() was not invoked, such as when
+             * NotificationPanelViewController controls the hide animation. Make sure the state gets
+             * updated by calling #hide() directly.
+             */
+            hide()
+            DejankUtils.postAfterTraversal { primaryBouncerCallbackInteractor.dispatchReset() }
+            primaryBouncerCallbackInteractor.dispatchFullyHidden()
+        } else if (
+            expansion != KeyguardBouncerConstants.EXPANSION_VISIBLE &&
+                oldExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE
+        ) {
+            primaryBouncerCallbackInteractor.dispatchStartingToHide()
+            repository.setPrimaryStartingToHide(true)
+        }
+        if (expansionChanged) {
+            primaryBouncerCallbackInteractor.dispatchExpansionChanged(expansion)
+        }
+    }
+
+    /** Set the initial keyguard message to show when bouncer is shown. */
+    fun showMessage(message: String?, colorStateList: ColorStateList?) {
+        repository.setShowMessage(BouncerShowMessageModel(message, colorStateList))
+    }
+
+    /**
+     * Sets actions to the bouncer based on how the bouncer is dismissed. If the bouncer is
+     * unlocked, we will run the onDismissAction. If the bouncer is existed before unlocking, we
+     * call cancelAction.
+     */
+    fun setDismissAction(
+        onDismissAction: ActivityStarter.OnDismissAction?,
+        cancelAction: Runnable?
+    ) {
+        primaryBouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
+    }
+
+    /** Update the resources of the views. */
+    fun updateResources() {
+        repository.setResourceUpdateRequests(true)
+    }
+
+    /** Tell the bouncer that keyguard is authenticated. */
+    fun notifyKeyguardAuthenticated(strongAuth: Boolean) {
+        repository.setKeyguardAuthenticated(strongAuth)
+    }
+
+    /** Update the position of the bouncer when showing. */
+    fun setKeyguardPosition(position: Float) {
+        repository.setKeyguardPosition(position)
+    }
+
+    /** Notifies that the state change was handled. */
+    fun notifyKeyguardAuthenticatedHandled() {
+        repository.setKeyguardAuthenticated(null)
+    }
+
+    /** Notifies that the message was shown. */
+    fun onMessageShown() {
+        repository.setShowMessage(null)
+    }
+
+    /** Notify that the resources have been updated */
+    fun notifyUpdatedResources() {
+        repository.setResourceUpdateRequests(false)
+    }
+
+    /** Set whether back button is enabled when on the bouncer screen. */
+    fun setBackButtonEnabled(enabled: Boolean) {
+        repository.setIsBackButtonEnabled(enabled)
+    }
+
+    /** Tell the bouncer to start the pre hide animation. */
+    fun startDisappearAnimation(runnable: Runnable) {
+        if (willRunDismissFromKeyguard()) {
+            runnable.run()
+            return
+        }
+
+        repository.setPrimaryStartDisappearAnimation(runnable)
+    }
+
+    /** Determine whether to show the side fps animation. */
+    fun updateSideFpsVisibility() {
+        val sfpsEnabled: Boolean =
+            context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
+        val fpsDetectionRunning: Boolean = keyguardUpdateMonitor.isFingerprintDetectionRunning
+        val isUnlockingWithFpAllowed: Boolean =
+            keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
+        val toShow =
+            (isBouncerShowing() &&
+                sfpsEnabled &&
+                fpsDetectionRunning &&
+                isUnlockingWithFpAllowed &&
+                !isAnimatingAway())
+
+        if (KeyguardConstants.DEBUG) {
+            Log.d(
+                TAG,
+                ("sideFpsToShow=$toShow\n" +
+                    "isBouncerShowing=${isBouncerShowing()}\n" +
+                    "configEnabled=$sfpsEnabled\n" +
+                    "fpsDetectionRunning=$fpsDetectionRunning\n" +
+                    "isUnlockingWithFpAllowed=$isUnlockingWithFpAllowed\n" +
+                    "isAnimatingAway=${isAnimatingAway()}")
+            )
+        }
+        repository.setSideFpsShowing(toShow)
+    }
+
+    /** Returns whether bouncer is fully showing. */
+    fun isFullyShowing(): Boolean {
+        return (repository.primaryBouncerShowingSoon.value || isBouncerShowing()) &&
+            repository.panelExpansionAmount.value == KeyguardBouncerConstants.EXPANSION_VISIBLE &&
+            repository.primaryBouncerStartingDisappearAnimation.value == null
+    }
+
+    /** Returns whether bouncer is scrimmed. */
+    fun isScrimmed(): Boolean {
+        return repository.primaryBouncerScrimmed.value
+    }
+
+    /** If bouncer expansion is between 0f and 1f non-inclusive. */
+    fun isInTransit(): Boolean {
+        return repository.primaryBouncerShowingSoon.value ||
+            repository.panelExpansionAmount.value != KeyguardBouncerConstants.EXPANSION_HIDDEN &&
+                repository.panelExpansionAmount.value != KeyguardBouncerConstants.EXPANSION_VISIBLE
+    }
+
+    /** Return whether bouncer is animating away. */
+    fun isAnimatingAway(): Boolean {
+        return repository.primaryBouncerStartingDisappearAnimation.value != null
+    }
+
+    /** Return whether bouncer will dismiss with actions */
+    fun willDismissWithAction(): Boolean {
+        return primaryBouncerView.delegate?.willDismissWithActions() == true
+    }
+
+    /** Will the dismissal run from the keyguard layout (instead of from bouncer) */
+    fun willRunDismissFromKeyguard(): Boolean {
+        return primaryBouncerView.delegate?.willRunDismissFromKeyguard() == true
+    }
+
+    /** Returns whether the bouncer should be full screen. */
+    private fun needsFullscreenBouncer(): Boolean {
+        val mode: KeyguardSecurityModel.SecurityMode =
+            keyguardSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser())
+        return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
+            mode == KeyguardSecurityModel.SecurityMode.SimPuk
+    }
+
+    /** Remove the show runnable from the main handler queue to improve performance. */
+    private fun cancelShowRunnable() {
+        DejankUtils.removeCallbacks(showRunnable)
+        mainHandler.removeCallbacks(showRunnable)
+    }
+
+    /** Returns whether the primary bouncer is currently showing. */
+    fun isBouncerShowing(): Boolean {
+        return isShowing.value
+    }
+
+    /** Whether we want to wait to show the bouncer in case passive auth succeeds. */
+    private fun usePrimaryBouncerPassiveAuthDelay(): Boolean {
+        val canRunFaceAuth =
+            keyguardStateController.isFaceAuthEnabled &&
+                keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) &&
+                keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()
+        val canRunActiveUnlock =
+            currentUserActiveUnlockRunning &&
+                keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()
+
+        return featureFlags.isEnabled(Flags.DELAY_BOUNCER) &&
+            !needsFullscreenBouncer() &&
+            (canRunFaceAuth || canRunActiveUnlock)
+    }
+
+    companion object {
+        private const val TAG = "PrimaryBouncerInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/constants/KeyguardBouncerConstants.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/constants/KeyguardBouncerConstants.kt
new file mode 100644
index 0000000..9f17811
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/constants/KeyguardBouncerConstants.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.shared.constants
+
+object KeyguardBouncerConstants {
+    /**
+     * Values for the bouncer expansion represented as the panel expansion. Panel expansion 1f =
+     * panel fully showing = bouncer fully hidden Panel expansion 0f = panel fully hiding = bouncer
+     * fully showing
+     */
+    const val EXPANSION_HIDDEN = 1f
+    const val EXPANSION_VISIBLE = 0f
+    const val ALPHA_EXPANSION_THRESHOLD = 0.95f
+
+    /**
+     * This value is used for denoting the PIN length at which we want to layout the view in which
+     * PIN hinting is enabled
+     */
+    const val DEFAULT_PIN_LENGTH = 6
+
+    object ColorId {
+        const val TITLE = com.android.internal.R.attr.materialColorOnSurface
+        const val PIN_SHAPES = com.android.internal.R.attr.materialColorOnSurfaceVariant
+        const val NUM_PAD_BACKGROUND = com.android.internal.R.attr.materialColorSurfaceContainerHigh
+        const val NUM_PAD_BACKGROUND_PRESSED = com.android.internal.R.attr.materialColorPrimaryFixed
+        const val NUM_PAD_PRESSED = com.android.internal.R.attr.materialColorOnPrimaryFixed
+        const val NUM_PAD_KEY = com.android.internal.R.attr.materialColorOnSurface
+        const val NUM_PAD_BUTTON = com.android.internal.R.attr.materialColorOnSecondaryFixed
+        const val EMERGENCY_BUTTON = com.android.internal.R.attr.materialColorTertiaryFixed
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt
deleted file mode 100644
index cbea635..0000000
--- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/AuthenticationThrottledModel.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.bouncer.shared.model
-
-/**
- * Models application state for when further authentication attempts are being throttled due to too
- * many consecutive failed authentication attempts.
- */
-data class AuthenticationThrottledModel(
-    /** Total number of failed attempts so far. */
-    val failedAttemptCount: Int,
-    /** Total amount of time the user has to wait before attempting again. */
-    val totalDurationSec: Int,
-    /** Remaining amount of time the user has to wait before attempting again. */
-    val remainingDurationSec: Int,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerCallbackActionsModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerCallbackActionsModel.kt
new file mode 100644
index 0000000..afddd39
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerCallbackActionsModel.kt
@@ -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
+ */
+
+package com.android.systemui.bouncer.shared.model
+
+import com.android.systemui.plugins.ActivityStarter
+
+/** Encapsulates callbacks to be invoked by the bouncer logic. */
+// TODO(b/243683121): Move dismiss logic from view controllers
+data class BouncerCallbackActionsModel(
+    val onDismissAction: ActivityStarter.OnDismissAction?,
+    val cancelAction: Runnable?
+)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageModel.kt
new file mode 100644
index 0000000..0e9e962
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageModel.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.shared.model
+
+import android.content.res.ColorStateList
+
+/**
+ * Represents the message displayed on the bouncer. It has two parts, primary and a secondary
+ * message
+ */
+data class BouncerMessageModel(val message: Message? = null, val secondaryMessage: Message? = null)
+
+/**
+ * Representation of a single message on the bouncer. It can be either a string or a string resource
+ * ID
+ */
+data class Message(
+    val message: String? = null,
+    val messageResId: Int? = null,
+    val colorState: ColorStateList? = null,
+    /** Any plural formatter arguments that can used to format the [messageResId] */
+    var formatterArgs: Map<String, Any>? = null,
+    /** Specifies whether this text should be animated when it is shown. */
+    var animate: Boolean = true,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerShowMessageModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerShowMessageModel.kt
new file mode 100644
index 0000000..1878572
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerShowMessageModel.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.bouncer.shared.model
+
+import android.content.res.ColorStateList
+
+/** Show a keyguard message to the bouncer. */
+data class BouncerShowMessageModel(val message: String?, val colorStateList: ColorStateList?)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerMessageView.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerMessageView.kt
new file mode 100644
index 0000000..6486802
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerMessageView.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.LinearLayout
+import com.android.keyguard.BouncerKeyguardMessageArea
+import com.android.keyguard.KeyguardMessageArea
+import com.android.keyguard.KeyguardMessageAreaController
+import com.android.systemui.R
+
+class BouncerMessageView : LinearLayout {
+    constructor(context: Context?) : super(context)
+
+    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+
+    init {
+        inflate(context, R.layout.bouncer_message_view, this)
+    }
+
+    var primaryMessageView: BouncerKeyguardMessageArea? = null
+    var secondaryMessageView: BouncerKeyguardMessageArea? = null
+    var primaryMessage: KeyguardMessageAreaController<KeyguardMessageArea>? = null
+    var secondaryMessage: KeyguardMessageAreaController<KeyguardMessageArea>? = null
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        primaryMessageView = findViewById(R.id.bouncer_primary_message_area)
+        secondaryMessageView = findViewById(R.id.bouncer_secondary_message_area)
+    }
+
+    fun init(factory: KeyguardMessageAreaController.Factory) {
+        primaryMessage = factory.create(primaryMessageView)
+        primaryMessage?.init()
+        secondaryMessage = factory.create(secondaryMessageView)
+        secondaryMessage?.init()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerView.kt
new file mode 100644
index 0000000..dc00321
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerView.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.bouncer.ui
+
+import android.view.KeyEvent
+import android.window.OnBackAnimationCallback
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import java.lang.ref.WeakReference
+import javax.inject.Inject
+
+/** An abstraction to interface with the ui layer, without changing state. */
+interface BouncerView {
+    var delegate: BouncerViewDelegate?
+}
+
+/** A lightweight class to hold reference to the ui delegate. */
+@SysUISingleton
+class BouncerViewImpl @Inject constructor() : BouncerView {
+    private var _delegate: WeakReference<BouncerViewDelegate?> = WeakReference(null)
+    override var delegate: BouncerViewDelegate?
+        get() = _delegate.get()
+        set(value) {
+            _delegate = WeakReference(value)
+        }
+}
+
+/** An abstraction that implements view logic. */
+interface BouncerViewDelegate {
+    fun isFullScreenBouncer(): Boolean
+    fun shouldDismissOnMenuPressed(): Boolean
+    fun interceptMediaKey(event: KeyEvent?): Boolean
+    fun dispatchBackKeyEventPreIme(): Boolean
+    fun showNextSecurityScreenOrFinish(): Boolean
+    fun resume()
+    fun setDismissAction(
+        onDismissAction: ActivityStarter.OnDismissAction?,
+        cancelAction: Runnable?,
+    )
+    fun willDismissWithActions(): Boolean
+    fun willRunDismissFromKeyguard(): Boolean
+    /** @return the {@link OnBackAnimationCallback} to animate Bouncer during a back gesture. */
+    fun getBackCallback(): OnBackAnimationCallback
+    fun showPromptReason(reason: Int)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
new file mode 100644
index 0000000..0cbfb68
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
@@ -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.
+ */
+
+package com.android.systemui.bouncer.ui
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface BouncerViewModule {
+    /** Binds BouncerView to BouncerViewImpl and makes it injectable. */
+    @Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerMessageViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerMessageViewBinder.kt
new file mode 100644
index 0000000..5a59012
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerMessageViewBinder.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui.binder
+
+import android.text.TextUtils
+import android.util.PluralsMessageFormatter
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.BouncerKeyguardMessageArea
+import com.android.keyguard.KeyguardMessageArea
+import com.android.keyguard.KeyguardMessageAreaController
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
+import com.android.systemui.bouncer.shared.model.Message
+import com.android.systemui.bouncer.ui.BouncerMessageView
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.BouncerLogger
+import kotlinx.coroutines.launch
+
+object BouncerMessageViewBinder {
+    @JvmStatic
+    fun bind(
+        view: BouncerMessageView,
+        interactor: BouncerMessageInteractor,
+        factory: KeyguardMessageAreaController.Factory,
+        bouncerLogger: BouncerLogger,
+        featureFlags: FeatureFlags
+    ) {
+        view.repeatWhenAttached {
+            if (!featureFlags.isEnabled(Flags.REVAMPED_BOUNCER_MESSAGES)) {
+                view.primaryMessageView?.disable()
+                view.secondaryMessageView?.disable()
+                return@repeatWhenAttached
+            }
+            view.init(factory)
+            view.primaryMessage?.setIsVisible(true)
+            view.secondaryMessage?.setIsVisible(true)
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                bouncerLogger.startBouncerMessageInteractor()
+                launch {
+                    interactor.bouncerMessage.collect {
+                        bouncerLogger.bouncerMessageUpdated(it)
+                        updateView(
+                            view.primaryMessage,
+                            view.primaryMessageView,
+                            message = it?.message,
+                            allowTruncation = true,
+                        )
+                        updateView(
+                            view.secondaryMessage,
+                            view.secondaryMessageView,
+                            message = it?.secondaryMessage,
+                            allowTruncation = false,
+                        )
+                        view.requestLayout()
+                    }
+                }
+            }
+        }
+    }
+
+    private fun updateView(
+        controller: KeyguardMessageAreaController<KeyguardMessageArea>?,
+        view: BouncerKeyguardMessageArea?,
+        message: Message?,
+        allowTruncation: Boolean = false,
+    ) {
+        if (view == null || controller == null) return
+        if (message?.message != null || message?.messageResId != null) {
+            controller.setIsVisible(true)
+            var newMessage = message.message ?: ""
+            if (message.messageResId != null && message.messageResId != 0) {
+                newMessage = view.resources.getString(message.messageResId)
+                if (message.formatterArgs != null) {
+                    newMessage =
+                        PluralsMessageFormatter.format(
+                            view.resources,
+                            message.formatterArgs,
+                            message.messageResId
+                        )
+                }
+            }
+            controller.setMessage(newMessage, message.animate)
+        } else {
+            controller.setIsVisible(false)
+            controller.setMessage(0)
+        }
+        message?.colorState?.let { controller.setNextMessageColor(it) }
+        view.ellipsize =
+            if (allowTruncation) TextUtils.TruncateAt.END else TextUtils.TruncateAt.MARQUEE
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
new file mode 100644
index 0000000..56f1cf6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -0,0 +1,255 @@
+/*
+ * 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.bouncer.ui.binder
+
+import android.view.KeyEvent
+import android.view.View
+import android.view.ViewGroup
+import android.window.OnBackAnimationCallback
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.KeyguardMessageAreaController
+import com.android.keyguard.KeyguardSecurityContainerController
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardSecurityView
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
+import com.android.systemui.bouncer.ui.BouncerViewDelegate
+import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.BouncerLogger
+import com.android.systemui.plugins.ActivityStarter
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.launch
+
+/** Binds the bouncer container to its view model. */
+object KeyguardBouncerViewBinder {
+    @JvmStatic
+    fun bind(
+        view: ViewGroup,
+        viewModel: KeyguardBouncerViewModel,
+        primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
+        componentFactory: KeyguardBouncerComponent.Factory,
+        messageAreaControllerFactory: KeyguardMessageAreaController.Factory,
+        bouncerMessageInteractor: BouncerMessageInteractor,
+        bouncerLogger: BouncerLogger,
+        featureFlags: FeatureFlags,
+    ) {
+        // Builds the KeyguardSecurityContainerController from bouncer view group.
+        val securityContainerController: KeyguardSecurityContainerController =
+            componentFactory.create(view).securityContainerController
+        securityContainerController.init()
+        val delegate =
+            object : BouncerViewDelegate {
+                override fun isFullScreenBouncer(): Boolean {
+                    val mode = securityContainerController.currentSecurityMode
+                    return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
+                        mode == KeyguardSecurityModel.SecurityMode.SimPuk
+                }
+
+                override fun getBackCallback(): OnBackAnimationCallback {
+                    return securityContainerController.backCallback
+                }
+
+                override fun shouldDismissOnMenuPressed(): Boolean {
+                    return securityContainerController.shouldEnableMenuKey()
+                }
+
+                override fun interceptMediaKey(event: KeyEvent?): Boolean {
+                    return securityContainerController.interceptMediaKey(event)
+                }
+
+                override fun dispatchBackKeyEventPreIme(): Boolean {
+                    return securityContainerController.dispatchBackKeyEventPreIme()
+                }
+
+                override fun showNextSecurityScreenOrFinish(): Boolean {
+                    return securityContainerController.dismiss(
+                        KeyguardUpdateMonitor.getCurrentUser()
+                    )
+                }
+
+                override fun resume() {
+                    securityContainerController.showPrimarySecurityScreen(/* isTurningOff= */ false)
+                    securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
+                }
+
+                override fun setDismissAction(
+                    onDismissAction: ActivityStarter.OnDismissAction?,
+                    cancelAction: Runnable?
+                ) {
+                    securityContainerController.setOnDismissAction(onDismissAction, cancelAction)
+                }
+
+                override fun willDismissWithActions(): Boolean {
+                    return securityContainerController.hasDismissActions()
+                }
+
+                override fun willRunDismissFromKeyguard(): Boolean {
+                    return securityContainerController.willRunDismissFromKeyguard()
+                }
+
+                override fun showPromptReason(reason: Int) {
+                    securityContainerController.showPromptReason(reason)
+                }
+            }
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                try {
+                    viewModel.setBouncerViewDelegate(delegate)
+                    launch {
+                        viewModel.isShowing.collect { isShowing ->
+                            view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE
+                            if (isShowing) {
+                                // Reset security container because these views are not reinflated.
+                                securityContainerController.prepareToShow()
+                                securityContainerController.reinflateViewFlipper {
+                                    // Reset Security Container entirely.
+                                    securityContainerController.onBouncerVisibilityChanged(
+                                        /* isVisible= */ true
+                                    )
+                                    securityContainerController.showPrimarySecurityScreen(
+                                        /* turningOff= */ false
+                                    )
+                                    securityContainerController.setInitialMessage()
+                                    securityContainerController.appear()
+                                    securityContainerController.onResume(
+                                        KeyguardSecurityView.SCREEN_ON
+                                    )
+                                    bouncerLogger.bindingBouncerMessageView()
+                                    it.bindMessageView(
+                                        bouncerMessageInteractor,
+                                        messageAreaControllerFactory,
+                                        bouncerLogger,
+                                        featureFlags
+                                    )
+                                }
+                            } else {
+                                bouncerMessageInteractor.onBouncerBeingHidden()
+                                securityContainerController.onBouncerVisibilityChanged(
+                                    /* isVisible= */ false
+                                )
+                                securityContainerController.cancelDismissAction()
+                                securityContainerController.reset()
+                                securityContainerController.onPause()
+                            }
+                        }
+                    }
+
+                    launch {
+                        viewModel.startingToHide.collect {
+                            securityContainerController.onStartingToHide()
+                        }
+                    }
+
+                    launch {
+                        viewModel.startDisappearAnimation.collect {
+                            securityContainerController.startDisappearAnimation(it)
+                        }
+                    }
+
+                    launch {
+                        viewModel.bouncerExpansionAmount.collect { expansion ->
+                            securityContainerController.setExpansion(expansion)
+                        }
+                    }
+
+                    launch {
+                        primaryBouncerToGoneTransitionViewModel.bouncerAlpha.collect { alpha ->
+                            securityContainerController.setAlpha(alpha)
+                        }
+                    }
+
+                    launch {
+                        viewModel.bouncerExpansionAmount
+                            .filter { it == EXPANSION_VISIBLE }
+                            .collect {
+                                securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
+                                view.announceForAccessibility(securityContainerController.title)
+                            }
+                    }
+
+                    launch {
+                        viewModel.isInteractable.collect { isInteractable ->
+                            securityContainerController.setInteractable(isInteractable)
+                        }
+                    }
+
+                    launch {
+                        viewModel.keyguardPosition.collect { position ->
+                            securityContainerController.updateKeyguardPosition(position)
+                        }
+                    }
+
+                    launch {
+                        viewModel.updateResources.collect {
+                            securityContainerController.updateResources()
+                            viewModel.notifyUpdateResources()
+                        }
+                    }
+
+                    launch {
+                        viewModel.bouncerShowMessage.collect {
+                            securityContainerController.showMessage(
+                                it.message,
+                                it.colorStateList,
+                                /* animated= */ true
+                            )
+                            viewModel.onMessageShown()
+                        }
+                    }
+
+                    launch {
+                        viewModel.keyguardAuthenticated.collect {
+                            securityContainerController.finish(
+                                it,
+                                KeyguardUpdateMonitor.getCurrentUser()
+                            )
+                            viewModel.notifyKeyguardAuthenticated()
+                        }
+                    }
+
+                    launch {
+                        viewModel
+                            .observeOnIsBackButtonEnabled { view.systemUiVisibility }
+                            .collect { view.systemUiVisibility = it }
+                    }
+
+                    launch {
+                        viewModel.shouldUpdateSideFps.collect {
+                            viewModel.updateSideFpsVisibility()
+                        }
+                    }
+
+                    launch {
+                        viewModel.sideFpsShowing.collect {
+                            securityContainerController.updateSideFpsVisibility(it)
+                        }
+                    }
+                    awaitCancellation()
+                } finally {
+                    viewModel.setBouncerViewDelegate(null)
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 984d9ab..5b1998d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -18,13 +18,15 @@
 
 import android.content.Context
 import com.android.systemui.R
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.bouncer.shared.model.AuthenticationThrottledModel
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+import kotlin.math.ceil
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
@@ -37,36 +39,38 @@
 import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input on bouncer UIs. */
+@SysUISingleton
 class BouncerViewModel
-@AssistedInject
+@Inject
 constructor(
     @Application private val applicationContext: Context,
     @Application private val applicationScope: CoroutineScope,
-    interactorFactory: BouncerInteractor.Factory,
-    @Assisted containerName: String,
+    private val bouncerInteractor: BouncerInteractor,
+    private val authenticationInteractor: AuthenticationInteractor,
+    featureFlags: FeatureFlags,
 ) {
-    private val interactor: BouncerInteractor = interactorFactory.create(containerName)
-
     private val isInputEnabled: StateFlow<Boolean> =
-        interactor.throttling
-            .map { it == null }
+        bouncerInteractor.isThrottled
+            .map { !it }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = interactor.throttling.value == null,
+                initialValue = !bouncerInteractor.isThrottled.value,
             )
 
     private val pin: PinBouncerViewModel by lazy {
         PinBouncerViewModel(
+            applicationContext = applicationContext,
             applicationScope = applicationScope,
-            interactor = interactor,
+            interactor = bouncerInteractor,
             isInputEnabled = isInputEnabled,
         )
     }
 
     private val password: PasswordBouncerViewModel by lazy {
         PasswordBouncerViewModel(
-            interactor = interactor,
+            applicationScope = applicationScope,
+            interactor = bouncerInteractor,
             isInputEnabled = isInputEnabled,
         )
     }
@@ -75,36 +79,79 @@
         PatternBouncerViewModel(
             applicationContext = applicationContext,
             applicationScope = applicationScope,
-            interactor = interactor,
+            interactor = bouncerInteractor,
             isInputEnabled = isInputEnabled,
         )
     }
 
     /** View-model for the current UI, based on the current authentication method. */
     val authMethod: StateFlow<AuthMethodBouncerViewModel?> =
-        interactor.authenticationMethod
-            .map { authMethod -> toViewModel(authMethod) }
+        authenticationInteractor.authenticationMethod
+            .map { authenticationMethod ->
+                when (authenticationMethod) {
+                    is AuthenticationMethodModel.Pin -> pin
+                    is AuthenticationMethodModel.Password -> password
+                    is AuthenticationMethodModel.Pattern -> pattern
+                    else -> null
+                }
+            }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = toViewModel(interactor.authenticationMethod.value),
+                initialValue = null,
             )
 
+    init {
+        if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+            applicationScope.launch {
+                bouncerInteractor.isThrottled
+                    .map { isThrottled ->
+                        if (isThrottled) {
+                            when (authenticationInteractor.getAuthenticationMethod()) {
+                                is AuthenticationMethodModel.Pin ->
+                                    R.string.kg_too_many_failed_pin_attempts_dialog_message
+                                is AuthenticationMethodModel.Password ->
+                                    R.string.kg_too_many_failed_password_attempts_dialog_message
+                                is AuthenticationMethodModel.Pattern ->
+                                    R.string.kg_too_many_failed_pattern_attempts_dialog_message
+                                else -> null
+                            }?.let { stringResourceId ->
+                                applicationContext.getString(
+                                    stringResourceId,
+                                    bouncerInteractor.throttling.value.failedAttemptCount,
+                                    ceil(bouncerInteractor.throttling.value.remainingMs / 1000f)
+                                        .toInt(),
+                                )
+                            }
+                        } else {
+                            null
+                        }
+                    }
+                    .distinctUntilChanged()
+                    .collect { dialogMessageOrNull ->
+                        if (dialogMessageOrNull != null) {
+                            _throttlingDialogMessage.value = dialogMessageOrNull
+                        }
+                    }
+            }
+        }
+    }
+
     /** The user-facing message to show in the bouncer. */
     val message: StateFlow<MessageViewModel> =
         combine(
-                interactor.message,
-                interactor.throttling,
-            ) { message, throttling ->
-                toMessageViewModel(message, throttling)
+                bouncerInteractor.message,
+                bouncerInteractor.isThrottled,
+            ) { message, isThrottled ->
+                toMessageViewModel(message, isThrottled)
             }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue =
                     toMessageViewModel(
-                        message = interactor.message.value,
-                        throttling = interactor.throttling.value,
+                        message = bouncerInteractor.message.value,
+                        isThrottled = bouncerInteractor.isThrottled.value,
                     ),
             )
 
@@ -120,37 +167,6 @@
      */
     val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow()
 
-    init {
-        applicationScope.launch {
-            interactor.throttling
-                .map { model ->
-                    model?.let {
-                        when (interactor.authenticationMethod.value) {
-                            is AuthenticationMethodModel.PIN ->
-                                R.string.kg_too_many_failed_pin_attempts_dialog_message
-                            is AuthenticationMethodModel.Password ->
-                                R.string.kg_too_many_failed_password_attempts_dialog_message
-                            is AuthenticationMethodModel.Pattern ->
-                                R.string.kg_too_many_failed_pattern_attempts_dialog_message
-                            else -> null
-                        }?.let { stringResourceId ->
-                            applicationContext.getString(
-                                stringResourceId,
-                                model.failedAttemptCount,
-                                model.totalDurationSec,
-                            )
-                        }
-                    }
-                }
-                .distinctUntilChanged()
-                .collect { dialogMessageOrNull ->
-                    if (dialogMessageOrNull != null) {
-                        _throttlingDialogMessage.value = dialogMessageOrNull
-                    }
-                }
-        }
-    }
-
     /** Notifies that the emergency services button was clicked. */
     fun onEmergencyServicesButtonClicked() {
         // TODO(b/280877228): implement this
@@ -161,24 +177,13 @@
         _throttlingDialogMessage.value = null
     }
 
-    private fun toViewModel(
-        authMethod: AuthenticationMethodModel,
-    ): AuthMethodBouncerViewModel? {
-        return when (authMethod) {
-            is AuthenticationMethodModel.PIN -> pin
-            is AuthenticationMethodModel.Password -> password
-            is AuthenticationMethodModel.Pattern -> pattern
-            else -> null
-        }
-    }
-
     private fun toMessageViewModel(
         message: String?,
-        throttling: AuthenticationThrottledModel?,
+        isThrottled: Boolean,
     ): MessageViewModel {
         return MessageViewModel(
             text = message ?: "",
-            isUpdateAnimated = throttling == null,
+            isUpdateAnimated = !isThrottled,
         )
     }
 
@@ -193,11 +198,4 @@
          */
         val isUpdateAnimated: Boolean,
     )
-
-    @AssistedFactory
-    interface Factory {
-        fun create(
-            containerName: String,
-        ): BouncerViewModel
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
new file mode 100644
index 0000000..6ba8439
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.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.bouncer.ui.viewmodel
+
+import android.view.View
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
+import com.android.systemui.bouncer.ui.BouncerView
+import com.android.systemui.bouncer.ui.BouncerViewDelegate
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** Models UI state for the lock screen bouncer; handles user input. */
+class KeyguardBouncerViewModel
+@Inject
+constructor(
+    private val view: BouncerView,
+    private val interactor: PrimaryBouncerInteractor,
+) {
+    /** Observe on bouncer expansion amount. */
+    val bouncerExpansionAmount: Flow<Float> = interactor.panelExpansionAmount
+
+    /** Can the user interact with the view? */
+    val isInteractable: Flow<Boolean> = interactor.isInteractable
+
+    /** Observe whether bouncer is showing or not. */
+    val isShowing: Flow<Boolean> = interactor.isShowing
+
+    /** Observe whether bouncer is starting to hide. */
+    val startingToHide: Flow<Unit> = interactor.startingToHide
+
+    /** Observe whether we want to start the disappear animation. */
+    val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation
+
+    /** Observe whether we want to update keyguard position. */
+    val keyguardPosition: Flow<Float> = interactor.keyguardPosition
+
+    /** Observe whether we want to update resources. */
+    val updateResources: Flow<Boolean> = interactor.resourceUpdateRequests
+
+    /** Observe whether we want to set a keyguard message when the bouncer shows. */
+    val bouncerShowMessage: Flow<BouncerShowMessageModel> = interactor.showMessage
+
+    /** Observe whether keyguard is authenticated already. */
+    val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticated
+
+    /** Observe whether the side fps is showing. */
+    val sideFpsShowing: Flow<Boolean> = interactor.sideFpsShowing
+
+    /** Observe whether we should update fps is showing. */
+    val shouldUpdateSideFps: Flow<Unit> =
+        merge(
+            interactor.isShowing.map {},
+            interactor.startingToHide,
+            interactor.startingDisappearAnimation.filterNotNull().map {}
+        )
+
+    /** Observe whether we want to update resources. */
+    fun notifyUpdateResources() {
+        interactor.notifyUpdatedResources()
+    }
+
+    /** Notify that keyguard authenticated was handled */
+    fun notifyKeyguardAuthenticated() {
+        interactor.notifyKeyguardAuthenticatedHandled()
+    }
+
+    /** Notifies that the message was shown. */
+    fun onMessageShown() {
+        interactor.onMessageShown()
+    }
+
+    fun updateSideFpsVisibility() {
+        interactor.updateSideFpsVisibility()
+    }
+
+    /** Observe whether back button is enabled. */
+    fun observeOnIsBackButtonEnabled(systemUiVisibility: () -> Int): Flow<Int> {
+        return interactor.isBackButtonEnabled.map { enabled ->
+            var vis: Int = systemUiVisibility()
+            vis =
+                if (enabled) {
+                    vis and View.STATUS_BAR_DISABLE_BACK.inv()
+                } else {
+                    vis or View.STATUS_BAR_DISABLE_BACK
+                }
+            vis
+        }
+    }
+
+    /** Set an abstraction that will hold reference to the ui delegate for the bouncer view. */
+    fun setBouncerViewDelegate(delegate: BouncerViewDelegate?) {
+        view.delegate = delegate
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 55929b5..ca15f4e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -17,12 +17,15 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the password bouncer UI. */
 class PasswordBouncerViewModel(
+    private val applicationScope: CoroutineScope,
     private val interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
 ) :
@@ -50,10 +53,13 @@
 
     /** Notifies that the user has pressed the key for attempting to authenticate the password. */
     fun onAuthenticateKeyPressed() {
-        if (!interactor.authenticate(password.value.toCharArray().toList())) {
-            showFailureAnimation()
-        }
-
+        val password = _password.value.toCharArray().toList()
         _password.value = ""
+
+        applicationScope.launch {
+            if (interactor.authenticate(password) != true) {
+                showFailureAnimation()
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index d9ef75d..4425f9f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -18,7 +18,7 @@
 
 import android.content.Context
 import android.util.TypedValue
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import kotlin.math.max
 import kotlin.math.min
@@ -31,11 +31,12 @@
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the pattern bouncer UI. */
 class PatternBouncerViewModel(
     private val applicationContext: Context,
-    applicationScope: CoroutineScope,
+    private val applicationScope: CoroutineScope,
     private val interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
 ) :
@@ -68,14 +69,7 @@
     val dots: StateFlow<List<PatternDotViewModel>> = _dots.asStateFlow()
 
     /** Whether the pattern itself should be rendered visibly. */
-    val isPatternVisible: StateFlow<Boolean> =
-        interactor.authenticationMethod
-            .map { authMethod -> isPatternVisible(authMethod) }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.Eagerly,
-                initialValue = isPatternVisible(interactor.authenticationMethod.value),
-            )
+    val isPatternVisible: StateFlow<Boolean> = interactor.isPatternVisible
 
     /** Notifies that the UI has been shown to the user. */
     fun onShown() {
@@ -153,19 +147,16 @@
 
     /** Notifies that the user has ended the drag gesture across the dot grid. */
     fun onDragEnd() {
-        val isSuccessfullyAuthenticated =
-            interactor.authenticate(_selectedDots.value.map { it.toCoordinate() })
-        if (!isSuccessfullyAuthenticated) {
-            showFailureAnimation()
-        }
-
+        val pattern = _selectedDots.value.map { it.toCoordinate() }
         _dots.value = defaultDots()
         _currentDot.value = null
         _selectedDots.value = linkedSetOf()
-    }
 
-    private fun isPatternVisible(authMethodModel: AuthenticationMethodModel): Boolean {
-        return (authMethodModel as? AuthenticationMethodModel.Pattern)?.isPatternVisible ?: false
+        applicationScope.launch {
+            if (interactor.authenticate(pattern) != true) {
+                showFailureAnimation()
+            }
+        }
     }
 
     private fun defaultDots(): List<PatternDotViewModel> {
@@ -202,8 +193,8 @@
     val x: Int,
     val y: Int,
 ) {
-    fun toCoordinate(): AuthenticationMethodModel.Pattern.PatternCoordinate {
-        return AuthenticationMethodModel.Pattern.PatternCoordinate(
+    fun toCoordinate(): AuthenticationPatternCoordinate {
+        return AuthenticationPatternCoordinate(
             x = x,
             y = y,
         )
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 5c0fd92..844cf02 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -16,21 +16,21 @@
 
 package com.android.systemui.bouncer.ui.viewmodel
 
-import androidx.annotation.VisibleForTesting
+import android.content.Context
+import com.android.keyguard.PinShapeAdapter
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.util.kotlin.pairwise
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the PIN code bouncer UI. */
 class PinBouncerViewModel(
+    applicationContext: Context,
     private val applicationScope: CoroutineScope,
     private val interactor: BouncerInteractor,
     isInputEnabled: StateFlow<Boolean>,
@@ -39,21 +39,49 @@
         isInputEnabled = isInputEnabled,
     ) {
 
-    private val entered = MutableStateFlow<List<Int>>(emptyList())
-    /**
-     * The length of the PIN digits that were input so far, two values are supplied the previous and
-     * the current.
-     */
-    val pinLengths: StateFlow<Pair<Int, Int>> =
-        entered
-            .pairwise()
-            .map { it.previousValue.size to it.newValue.size }
+    val pinShapes = PinShapeAdapter(applicationContext)
+    private val mutablePinInput = MutableStateFlow(PinInputViewModel.empty())
+
+    /** Currently entered pin keys. */
+    val pinInput: StateFlow<PinInputViewModel> = mutablePinInput
+
+    /** The length of the PIN for which we should show a hint. */
+    val hintedPinLength: StateFlow<Int?> = interactor.hintedPinLength
+
+    /** Appearance of the backspace button. */
+    val backspaceButtonAppearance: StateFlow<ActionButtonAppearance> =
+        combine(
+                mutablePinInput,
+                interactor.isAutoConfirmEnabled,
+            ) { mutablePinEntries, isAutoConfirmEnabled ->
+                computeBackspaceButtonAppearance(
+                    pinInput = mutablePinEntries,
+                    isAutoConfirmEnabled = isAutoConfirmEnabled,
+                )
+            }
             .stateIn(
                 scope = applicationScope,
+                // Make sure this is kept as WhileSubscribed or we can run into a bug where the
+                // downstream continues to receive old/stale/cached values.
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = 0 to 0,
+                initialValue = ActionButtonAppearance.Hidden,
             )
-    private var resetPinJob: Job? = null
+
+    /** Appearance of the confirm button. */
+    val confirmButtonAppearance: StateFlow<ActionButtonAppearance> =
+        interactor.isAutoConfirmEnabled
+            .map {
+                if (it) {
+                    ActionButtonAppearance.Hidden
+                } else {
+                    ActionButtonAppearance.Shown
+                }
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = ActionButtonAppearance.Hidden,
+            )
 
     /** Notifies that the UI has been shown to the user. */
     fun onShown() {
@@ -62,47 +90,66 @@
 
     /** Notifies that the user clicked on a PIN button with the given digit value. */
     fun onPinButtonClicked(input: Int) {
-        resetPinJob?.cancel()
-        resetPinJob = null
-
-        if (entered.value.isEmpty()) {
+        val pinInput = mutablePinInput.value
+        if (pinInput.isEmpty()) {
             interactor.clearMessage()
         }
 
-        entered.value += input
+        mutablePinInput.value = pinInput.append(input)
+        tryAuthenticate(useAutoConfirm = true)
     }
 
     /** Notifies that the user clicked the backspace button. */
     fun onBackspaceButtonClicked() {
-        if (entered.value.isEmpty()) {
-            return
-        }
-
-        entered.value = entered.value.toMutableList().apply { removeLast() }
+        mutablePinInput.value = mutablePinInput.value.deleteLast()
     }
 
     /** Notifies that the user long-pressed the backspace button. */
     fun onBackspaceButtonLongPressed() {
-        resetPinJob?.cancel()
-        resetPinJob =
-            applicationScope.launch {
-                while (entered.value.isNotEmpty()) {
-                    onBackspaceButtonClicked()
-                    delay(BACKSPACE_LONG_PRESS_DELAY_MS)
-                }
-            }
+        mutablePinInput.value = mutablePinInput.value.clearAll()
     }
 
     /** Notifies that the user clicked the "enter" button. */
     fun onAuthenticateButtonClicked() {
-        if (!interactor.authenticate(entered.value)) {
-            showFailureAnimation()
+        tryAuthenticate(useAutoConfirm = false)
+    }
+
+    private fun tryAuthenticate(useAutoConfirm: Boolean) {
+        val pinCode = mutablePinInput.value.getPin()
+
+        applicationScope.launch {
+            val isSuccess = interactor.authenticate(pinCode, useAutoConfirm) ?: return@launch
+
+            if (!isSuccess) {
+                showFailureAnimation()
+            }
+
+            // TODO(b/291528545): this should not be cleared on success (at least until the view
+            // is animated away).
+            mutablePinInput.value = mutablePinInput.value.clearAll()
         }
-
-        entered.value = emptyList()
     }
 
-    companion object {
-        @VisibleForTesting const val BACKSPACE_LONG_PRESS_DELAY_MS = 80L
+    private fun computeBackspaceButtonAppearance(
+        pinInput: PinInputViewModel,
+        isAutoConfirmEnabled: Boolean,
+    ): ActionButtonAppearance {
+        val isEmpty = pinInput.isEmpty()
+
+        return when {
+            isAutoConfirmEnabled && isEmpty -> ActionButtonAppearance.Hidden
+            isAutoConfirmEnabled -> ActionButtonAppearance.Subtle
+            else -> ActionButtonAppearance.Shown
+        }
     }
 }
+
+/** Appearance of pin-pad action buttons. */
+enum class ActionButtonAppearance {
+    /** Button must not be shown. */
+    Hidden,
+    /** Button is shown, but with no background to make it less prominent. */
+    Subtle,
+    /** Button is shown. */
+    Shown,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModel.kt
new file mode 100644
index 0000000..4efc21b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModel.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui.viewmodel
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.bouncer.ui.viewmodel.EntryToken.ClearAll
+import com.android.systemui.bouncer.ui.viewmodel.EntryToken.Digit
+
+/**
+ * Immutable pin input state.
+ *
+ * The input is a hybrid of state ([Digit]) and event ([ClearAll]) tokens. The [ClearAll] token can
+ * be interpreted as a watermark, indicating that the current input up to that point is deleted
+ * (after a auth failure or when long-pressing the delete button). Therefore, [Digit]s following a
+ * [ClearAll] make up the next pin input entry. Up to two complete pin inputs are memoized.
+ *
+ * This is required when auto-confirm rejects the input, and the last digit will be animated-in at
+ * the end of the input, concurrently with the staggered clear-all animation starting to play at the
+ * beginning of the input.
+ *
+ * The input is guaranteed to always contain a initial [ClearAll] token as a sentinel, thus clients
+ * can always assume there is a 'ClearAll' watermark available.
+ */
+data class PinInputViewModel
+internal constructor(
+    @get:VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    internal val input: List<EntryToken>
+) {
+    init {
+        require(input.firstOrNull() is ClearAll) { "input does not begin with a ClearAll token" }
+        require(input.zipWithNext().all { it.first < it.second }) {
+            "EntryTokens are not sorted by their sequenceNumber"
+        }
+    }
+    /**
+     * [PinInputViewModel] with [previousInput] and appended [newToken].
+     *
+     * [previousInput] is trimmed so that the new [PinBouncerViewModel] contains at most two pin
+     * inputs.
+     */
+    private constructor(
+        previousInput: List<EntryToken>,
+        newToken: EntryToken
+    ) : this(
+        buildList {
+            addAll(
+                previousInput.subList(previousInput.indexOfLastClearAllToKeep(), previousInput.size)
+            )
+            add(newToken)
+        }
+    )
+
+    fun append(digit: Int): PinInputViewModel {
+        return PinInputViewModel(input, Digit(digit))
+    }
+
+    /**
+     * Delete last digit.
+     *
+     * This removes the last digit from the input. Returns `this` if the last token is [ClearAll].
+     */
+    fun deleteLast(): PinInputViewModel {
+        if (isEmpty()) return this
+        return PinInputViewModel(input.take(input.size - 1))
+    }
+
+    /**
+     * Appends a [ClearAll] watermark, completing the current pin.
+     *
+     * Returns `this` if the last token is [ClearAll].
+     */
+    fun clearAll(): PinInputViewModel {
+        if (isEmpty()) return this
+        return PinInputViewModel(input, ClearAll())
+    }
+
+    /** Whether the current pin is empty. */
+    fun isEmpty(): Boolean {
+        return input.last() is ClearAll
+    }
+
+    /** The current pin, or an empty list if [isEmpty]. */
+    fun getPin(): List<Int> {
+        return getDigits(mostRecentClearAll()).map { it.input }
+    }
+
+    /**
+     * The digits following the specified [ClearAll] marker, up to the next marker or the end of the
+     * input.
+     *
+     * Returns an empty list if the [ClearAll] is not in the input.
+     */
+    fun getDigits(clearAllMarker: ClearAll): List<Digit> {
+        val startIndex = input.indexOf(clearAllMarker) + 1
+        if (startIndex == 0 || startIndex == input.size) return emptyList()
+
+        return input.subList(startIndex, input.size).takeWhile { it is Digit }.map { it as Digit }
+    }
+
+    /** The most recent [ClearAll] marker. */
+    fun mostRecentClearAll(): ClearAll {
+        return input.last { it is ClearAll } as ClearAll
+    }
+
+    companion object {
+        fun empty() = PinInputViewModel(listOf(ClearAll()))
+    }
+}
+
+/**
+ * Pin bouncer entry token with a [sequenceNumber] to indicate input event ordering.
+ *
+ * Since the model only allows appending/removing [Digit]s from the end, the [sequenceNumber] is
+ * strictly increasing in input order of the pin, but not guaranteed to be monotonic or start at a
+ * specific number.
+ */
+sealed interface EntryToken : Comparable<EntryToken> {
+    val sequenceNumber: Int
+
+    /** The pin bouncer [input] as digits 0-9. */
+    data class Digit
+    internal constructor(val input: Int, override val sequenceNumber: Int = nextSequenceNumber++) :
+        EntryToken {
+        init {
+            check(input in 0..9)
+        }
+    }
+
+    /**
+     * Marker to indicate the input is completely cleared, and subsequent [EntryToken]s mark a new
+     * pin entry.
+     */
+    data class ClearAll
+    internal constructor(override val sequenceNumber: Int = nextSequenceNumber++) : EntryToken
+
+    override fun compareTo(other: EntryToken): Int =
+        compareValuesBy(this, other, EntryToken::sequenceNumber)
+
+    companion object {
+        private var nextSequenceNumber = 1
+    }
+}
+
+/**
+ * Index of the last [ClearAll] token to keep for a new [PinInputViewModel], so that after appending
+ * another [EntryToken], there are at most two pin inputs in the [PinInputViewModel].
+ */
+private fun List<EntryToken>.indexOfLastClearAllToKeep(): Int {
+    require(isNotEmpty() && first() is ClearAll)
+
+    var seenClearAll = 0
+    for (i in size - 1 downTo 0) {
+        if (get(i) is ClearAll) {
+            seenClearAll++
+            if (seenClearAll == 2) {
+                return i
+            }
+        }
+    }
+
+    // The first element is guaranteed to be a ClearAll marker.
+    return 0
+}
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..068f329 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt
@@ -21,10 +21,10 @@
 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.log.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogMessage
 import com.android.systemui.log.dagger.BroadcastDispatcherLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 5ca36ab..ddb09749 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -156,9 +156,9 @@
         }
         windowLayoutParams.packageName = context.opPackageName
         rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
-            override fun onViewDetachedFromWindow(view: View?) {}
+            override fun onViewDetachedFromWindow(view: View) {}
 
-            override fun onViewAttachedToWindow(view: View?) {
+            override fun onViewAttachedToWindow(view: View) {
                 layoutRipple()
                 rippleView.startRipple(Runnable {
                     windowManager.removeView(rippleView)
@@ -176,7 +176,7 @@
         val height = bounds.height()
         val maxDiameter = Integer.max(width, height) * 2f
         rippleView.setMaxSize(maxDiameter, maxDiameter)
-        when (context.display.rotation) {
+        when (context.display?.rotation) {
             Surface.ROTATION_0 -> {
                 rippleView.setCenter(
                         width * normalizedPortPosX, height * normalizedPortPosY)
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index b2bcb05..25ccc16 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -396,6 +396,7 @@
                 || mDataProvider.isDocked()
                 || mAccessibilityManager.isTouchExplorationEnabled()
                 || mDataProvider.isA11yAction()
+                || mDataProvider.isFromTrackpad()
                 || (mFeatureFlags.isEnabled(Flags.FALSING_OFF_FOR_UNFOLDED)
                     && mDataProvider.isUnfolded());
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
index 63d57cc..a9f3b77 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
@@ -29,7 +29,7 @@
  */
 class FalsingA11yDelegate @Inject constructor(private val falsingCollector: FalsingCollector) :
     View.AccessibilityDelegate() {
-    override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+    override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
         if (action == ACTION_CLICK) {
             falsingCollector.onA11yAction()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index d6c85fb..809d5b2 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -261,6 +261,16 @@
         return mLastMotionEvent.getY() < mFirstRecentMotionEvent.getY();
     }
 
+    public boolean isFromTrackpad() {
+        if (mRecentMotionEvents.isEmpty()) {
+            return false;
+        }
+
+        int classification = mRecentMotionEvents.get(0).getClassification();
+        return classification == MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE
+                || classification == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
+    }
+
     private void recalculateData() {
         if (!mDirty) {
             return;
@@ -343,7 +353,9 @@
                     motionEvent.getDeviceId(),
                     motionEvent.getEdgeFlags(),
                     motionEvent.getSource(),
-                    motionEvent.getFlags()
+                    motionEvent.getDisplayId(),
+                    motionEvent.getFlags(),
+                    motionEvent.getClassification()
             ));
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 757ebf4..ffd836b 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -18,6 +18,7 @@
 
 import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
 
+
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
@@ -33,6 +34,7 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
 import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
+import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -47,6 +49,7 @@
 import android.net.Uri;
 import android.os.Looper;
 import android.provider.DeviceConfig;
+import android.util.Log;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.InputMonitor;
@@ -54,6 +57,7 @@
 import android.view.WindowInsets;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
@@ -73,7 +77,8 @@
 /**
  * Controls state and UI for the overlay that appears when something is added to the clipboard
  */
-public class ClipboardOverlayController implements ClipboardListener.ClipboardOverlay {
+public class ClipboardOverlayController implements ClipboardListener.ClipboardOverlay,
+        ClipboardOverlayView.ClipboardOverlayCallbacks {
     private static final String TAG = "ClipboardOverlayCtrlr";
 
     /** Constants for screenshot/copy deconflicting */
@@ -92,6 +97,7 @@
     private final FeatureFlags mFeatureFlags;
     private final Executor mBgExecutor;
     private final ClipboardImageLoader mClipboardImageLoader;
+    private final ClipboardTransitionExecutor mTransitionExecutor;
 
     private final ClipboardOverlayView mView;
 
@@ -179,10 +185,12 @@
             ClipboardOverlayUtils clipboardUtils,
             @Background Executor bgExecutor,
             ClipboardImageLoader clipboardImageLoader,
+            ClipboardTransitionExecutor transitionExecutor,
             UiEventLogger uiEventLogger) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
         mClipboardImageLoader = clipboardImageLoader;
+        mTransitionExecutor = transitionExecutor;
 
         mClipboardLogger = new ClipboardLogger(uiEventLogger);
 
@@ -200,7 +208,11 @@
         mClipboardUtils = clipboardUtils;
         mBgExecutor = bgExecutor;
 
-        mView.setCallbacks(mClipboardCallbacks);
+        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+            mView.setCallbacks(this);
+        } else {
+            mView.setCallbacks(mClipboardCallbacks);
+        }
 
         mWindow.withWindowAttached(() -> {
             mWindow.setContentView(mView);
@@ -209,16 +221,24 @@
         });
 
         mTimeoutHandler.setOnTimeoutRunnable(() -> {
-            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
-            animateOut();
+            if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                finish(CLIPBOARD_OVERLAY_TIMED_OUT);
+            } else {
+                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();
+                    if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                        finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+                    } else {
+                        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+                        animateOut();
+                    }
                 }
             }
         };
@@ -229,8 +249,12 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (SCREENSHOT_ACTION.equals(intent.getAction())) {
-                    mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
-                    animateOut();
+                    if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                        finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+                    } else {
+                        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
+                        animateOut();
+                    }
                 }
             }
         };
@@ -457,8 +481,12 @@
                 remoteAction.ifPresent(action -> {
                     mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
                     mView.post(() -> mView.setActionChip(action, () -> {
-                        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
-                        animateOut();
+                        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                            finish(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+                        } else {
+                            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+                            animateOut();
+                        }
                     }));
                 });
             }
@@ -500,8 +528,12 @@
                     if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
                         if (!mView.isInTouchRegion(
                                 (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
-                            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
-                            animateOut();
+                            if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                                finish(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
+                            } else {
+                                mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
+                                animateOut();
+                            }
                         }
                     }
                 }
@@ -551,12 +583,16 @@
         mEnterAnimator.start();
     }
 
+    private void finish(ClipboardOverlayEvent event) {
+        finish(event, null);
+    }
+
     private void animateOut() {
         if (mExitAnimator != null && mExitAnimator.isRunning()) {
             return;
         }
-        Animator anim = mView.getExitAnimation();
-        anim.addListener(new AnimatorListenerAdapter() {
+        mExitAnimator = mView.getExitAnimation();
+        mExitAnimator.addListener(new AnimatorListenerAdapter() {
             private boolean mCancelled;
 
             @Override
@@ -573,8 +609,47 @@
                 }
             }
         });
-        mExitAnimator = anim;
-        anim.start();
+        mExitAnimator.start();
+    }
+
+    private void finish(ClipboardOverlayEvent event, @Nullable Intent intent) {
+        if (mExitAnimator != null && mExitAnimator.isRunning()) {
+            return;
+        }
+        mExitAnimator = mView.getExitAnimation();
+        mExitAnimator.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) {
+                    mClipboardLogger.logSessionComplete(event);
+                    if (intent != null) {
+                        mContext.startActivity(intent);
+                    }
+                    hideImmediate();
+                }
+            }
+        });
+        mExitAnimator.start();
+    }
+
+    private void finishWithSharedTransition(ClipboardOverlayEvent event, Intent intent) {
+        if (mExitAnimator != null && mExitAnimator.isRunning()) {
+            return;
+        }
+        mClipboardLogger.logSessionComplete(event);
+        mExitAnimator = mView.getFadeOutAnimation();
+        mExitAnimator.start();
+        mTransitionExecutor.startSharedTransition(
+                mWindow, mView.getPreview(), intent, this::hideImmediate);
     }
 
     void hideImmediate() {
@@ -613,6 +688,76 @@
         mClipboardLogger.reset();
     }
 
+    @Override
+    public void onDismissButtonTapped() {
+        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+            finish(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+        }
+    }
+
+    @Override
+    public void onRemoteCopyButtonTapped() {
+        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+            finish(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED,
+                    IntentCreator.getRemoteCopyIntent(mClipboardModel.getClipData(), mContext));
+        }
+    }
+
+    @Override
+    public void onShareButtonTapped() {
+        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+            if (mClipboardModel.getType() != ClipboardModel.Type.OTHER) {
+                finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED,
+                        IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
+            }
+        }
+    }
+
+    @Override
+    public void onPreviewTapped() {
+        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+            switch (mClipboardModel.getType()) {
+                case TEXT:
+                    finish(CLIPBOARD_OVERLAY_EDIT_TAPPED,
+                            IntentCreator.getTextEditorIntent(mContext));
+                    break;
+                case IMAGE:
+                    finishWithSharedTransition(CLIPBOARD_OVERLAY_EDIT_TAPPED,
+                            IntentCreator.getImageEditIntent(mClipboardModel.getUri(), mContext));
+                    break;
+                default:
+                    Log.w(TAG, "Got preview tapped callback for non-editable type "
+                            + mClipboardModel.getType());
+            }
+        }
+    }
+
+    @Override
+    public void onMinimizedViewTapped() {
+        animateFromMinimized();
+    }
+
+    @Override
+    public void onInteraction() {
+        if (!mClipboardModel.isRemote()) {
+            mTimeoutHandler.resetTimeout();
+        }
+    }
+
+    @Override
+    public void onSwipeDismissInitiated(Animator animator) {
+        if (mExitAnimator != null && mExitAnimator.isRunning()) {
+            mExitAnimator.cancel();
+        }
+        mExitAnimator = animator;
+        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+    }
+
+    @Override
+    public void onDismissComplete() {
+        hideImmediate();
+    }
+
     static class ClipboardLogger {
         private final UiEventLogger mUiEventLogger;
         private String mClipSource;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index 28c57d3..a76d2ea 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -254,6 +254,10 @@
                 });
     }
 
+    View getPreview() {
+        return mClipboardPreview;
+    }
+
     void showImagePreview(@Nullable Bitmap thumbnail) {
         if (thumbnail == null) {
             mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
@@ -368,6 +372,19 @@
         return enterAnim;
     }
 
+    Animator getFadeOutAnimation() {
+        ValueAnimator alphaAnim = ValueAnimator.ofFloat(1, 0);
+        alphaAnim.addUpdateListener(animation -> {
+            float alpha = (float) animation.getAnimatedValue();
+            mActionContainer.setAlpha(alpha);
+            mActionContainerBackground.setAlpha(alpha);
+            mPreviewBorder.setAlpha(alpha);
+            mDismissButton.setAlpha(alpha);
+        });
+        alphaAnim.setDuration(300);
+        return alphaAnim;
+    }
+
     Animator getExitAnimation() {
         TimeInterpolator linearInterpolator = new LinearInterpolator();
         TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt
new file mode 100644
index 0000000..1b45ecd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay
+
+import android.app.ActivityOptions
+import android.app.ExitTransitionCoordinator
+import android.content.Context
+import android.content.Intent
+import android.os.RemoteException
+import android.util.Log
+import android.util.Pair
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.IRemoteAnimationRunner
+import android.view.RemoteAnimationAdapter
+import android.view.RemoteAnimationTarget
+import android.view.View
+import android.view.Window
+import android.view.WindowManagerGlobal
+import com.android.internal.app.ChooserActivity
+import com.android.systemui.settings.DisplayTracker
+import javax.inject.Inject
+
+class ClipboardTransitionExecutor
+@Inject
+constructor(val context: Context, val displayTracker: DisplayTracker) {
+    fun startSharedTransition(window: Window, view: View, intent: Intent, onReady: Runnable) {
+        val transition: Pair<ActivityOptions, ExitTransitionCoordinator> =
+            ActivityOptions.startSharedElementAnimation(
+                window,
+                object : ExitTransitionCoordinator.ExitTransitionCallbacks {
+                    override fun isReturnTransitionAllowed(): Boolean {
+                        return false
+                    }
+
+                    override fun hideSharedElements() {
+                        onReady.run()
+                    }
+
+                    override fun onFinish() {}
+                },
+                null,
+                Pair.create(view, ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)
+            )
+        transition.second.startExit()
+        context.startActivity(intent, transition.first.toBundle())
+        val runner = RemoteAnimationAdapter(NULL_ACTIVITY_TRANSITION, 0, 0)
+        try {
+            checkNotNull(WindowManagerGlobal.getWindowManagerService())
+                .overridePendingAppTransitionRemote(runner, displayTracker.defaultDisplayId)
+        } catch (e: Exception) {
+            Log.e(TAG, "Error overriding clipboard app transition", e)
+        }
+    }
+
+    private val TAG: String = "ClipboardTransitionExec"
+
+    /**
+     * 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 NULL_ACTIVITY_TRANSITION: IRemoteAnimationRunner.Stub =
+        object : IRemoteAnimationRunner.Stub() {
+            override fun onAnimationStart(
+                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() {}
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
index e342ac2..d58fab4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/IntentCreator.java
@@ -17,6 +17,7 @@
 package com.android.systemui.clipboardoverlay;
 
 import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -26,7 +27,8 @@
 import com.android.systemui.R;
 
 class IntentCreator {
-    private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard";
+    private static final String EXTRA_EDIT_SOURCE = "edit_source";
+    private static final String EDIT_SOURCE_CLIPBOARD = "clipboard";
     private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
 
     static Intent getTextEditorIntent(Context context) {
@@ -41,10 +43,16 @@
         // From the ACTION_SEND docs:
         //   "If using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it should be the
         //    MIME type of the data in EXTRA_STREAM"
-        if (clipData.getItemAt(0).getUri() != null) {
-            shareIntent.setDataAndType(
-                    clipData.getItemAt(0).getUri(), clipData.getDescription().getMimeType(0));
-            shareIntent.putExtra(Intent.EXTRA_STREAM, clipData.getItemAt(0).getUri());
+        Uri uri = clipData.getItemAt(0).getUri();
+        if (uri != null) {
+            // We don't use setData here because some apps interpret this as "to:".
+            shareIntent.setType(clipData.getDescription().getMimeType(0));
+            // Include URI in ClipData also, so that grantPermission picks it up.
+            shareIntent.setClipData(new ClipData(
+                    new ClipDescription(
+                            "content", new String[]{clipData.getDescription().getMimeType(0)}),
+                    new ClipData.Item(uri)));
+            shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
             shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
         } else {
             shareIntent.putExtra(
@@ -67,7 +75,7 @@
         editIntent.setDataAndType(uri, "image/*");
         editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
         editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        editIntent.putExtra(EXTRA_EDIT_SOURCE_CLIPBOARD, true);
+        editIntent.putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_CLIPBOARD);
         return editIntent;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index 3e6ac86..c0d1951 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -100,7 +100,7 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), getResolutionScale())
 
     override fun getResolutionScale(): Float {
-        context.display.getDisplayInfo(displayInfo.value)
+        context.display?.getDisplayInfo(displayInfo.value)
         val maxDisplayMode =
             displayUtils.getMaximumResolutionDisplayMode(displayInfo.value.supportedModes)
         maxDisplayMode?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
index 2dd98dc..323070a 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
@@ -22,6 +22,7 @@
 import android.util.AttributeSet
 import android.view.MotionEvent
 import android.view.View
+import com.android.systemui.shade.TouchLogger
 import kotlin.math.pow
 import kotlin.math.sqrt
 import kotlinx.coroutines.DisposableHandle
@@ -83,6 +84,10 @@
         interactionHandler.isLongPressHandlingEnabled = isEnabled
     }
 
+    override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+        return TouchLogger.logDispatchTouch("long_press", event, super.dispatchTouchEvent(event))
+    }
+
     @SuppressLint("ClickableViewAccessibility")
     override fun onTouchEvent(event: MotionEvent?): Boolean {
         return interactionHandler.onTouchEvent(event?.toModel())
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
index de3a990..f362831 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.common.ui.view;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -27,8 +28,12 @@
 import android.widget.LinearLayout;
 import android.widget.SeekBar;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * The layout contains a seekbar whose progress could be modified
  * through the icons on two ends of the seekbar.
@@ -37,12 +42,16 @@
 
     private static final int DEFAULT_SEEKBAR_MAX = 6;
     private static final int DEFAULT_SEEKBAR_PROGRESS = 0;
+    private static final int DEFAULT_SEEKBAR_TICK_MARK = 0;
 
     private ViewGroup mIconStartFrame;
     private ViewGroup mIconEndFrame;
     private ImageView mIconStart;
     private ImageView mIconEnd;
     private SeekBar mSeekbar;
+    private int mSeekBarChangeMagnitude = 1;
+
+    private boolean mSetProgressFromButtonFlag = false;
 
     private SeekBarChangeListener mSeekBarListener = new SeekBarChangeListener();
     private String[] mStateLabels = null;
@@ -102,6 +111,15 @@
                         context.getString(iconEndFrameContentDescriptionId);
                 mIconEndFrame.setContentDescription(contentDescription);
             }
+            int tickMarkId = typedArray.getResourceId(
+                    R.styleable.SeekBarWithIconButtonsView_Layout_tickMark,
+                    DEFAULT_SEEKBAR_TICK_MARK);
+            if (tickMarkId != DEFAULT_SEEKBAR_TICK_MARK) {
+                mSeekbar.setTickMark(getResources().getDrawable(tickMarkId));
+            }
+            mSeekBarChangeMagnitude = typedArray.getInt(
+                    R.styleable.SeekBarWithIconButtonsView_Layout_seekBarChangeMagnitude,
+                    /* defValue= */ 1);
         } else {
             mSeekbar.setMax(DEFAULT_SEEKBAR_MAX);
             setProgress(DEFAULT_SEEKBAR_PROGRESS);
@@ -109,21 +127,8 @@
 
         mSeekbar.setOnSeekBarChangeListener(mSeekBarListener);
 
-        mIconStartFrame.setOnClickListener((view) -> {
-            final int progress = mSeekbar.getProgress();
-            if (progress > 0) {
-                mSeekbar.setProgress(progress - 1);
-                setIconViewAndFrameEnabled(mIconStart, mSeekbar.getProgress() > 0);
-            }
-        });
-
-        mIconEndFrame.setOnClickListener((view) -> {
-            final int progress = mSeekbar.getProgress();
-            if (progress < mSeekbar.getMax()) {
-                mSeekbar.setProgress(progress + 1);
-                setIconViewAndFrameEnabled(mIconEnd, mSeekbar.getProgress() < mSeekbar.getMax());
-            }
-        });
+        mIconStartFrame.setOnClickListener((view) -> onIconStartClicked());
+        mIconEndFrame.setOnClickListener((view) -> onIconEndClicked());
     }
 
     private static void setIconViewAndFrameEnabled(View iconView, boolean enabled) {
@@ -160,9 +165,25 @@
      * Sets a onSeekbarChangeListener to the seekbar in the layout.
      * We update the Start Icon and End Icon if needed when the seekbar progress is changed.
      */
-    public void setOnSeekBarChangeListener(
-            @Nullable SeekBar.OnSeekBarChangeListener onSeekBarChangeListener) {
-        mSeekBarListener.setOnSeekBarChangeListener(onSeekBarChangeListener);
+    public void setOnSeekBarWithIconButtonsChangeListener(
+            @Nullable OnSeekBarWithIconButtonsChangeListener onSeekBarChangeListener) {
+        mSeekBarListener.setOnSeekBarWithIconButtonsChangeListener(onSeekBarChangeListener);
+    }
+
+    /**
+     * Only for testing. Get previous set mOnSeekBarChangeListener to the seekbar.
+     */
+    @VisibleForTesting
+    public OnSeekBarWithIconButtonsChangeListener getOnSeekBarWithIconButtonsChangeListener() {
+        return mSeekBarListener.mOnSeekBarChangeListener;
+    }
+
+    /**
+     * Only for testing. Get {@link #mSeekbar} in the layout.
+     */
+    @VisibleForTesting
+    public SeekBar getSeekbar() {
+        return mSeekbar;
     }
 
     /**
@@ -183,6 +204,20 @@
     }
 
     /**
+     * Gets max to the seekbar in the layout.
+     */
+    public int getMax() {
+        return mSeekbar.getMax();
+    }
+
+    /**
+     * @return the magnitude by which seekbar progress changes when start and end icons are clicked.
+     */
+    public int getChangeMagnitude() {
+        return mSeekBarChangeMagnitude;
+    }
+
+    /**
      * Sets progress to the seekbar in the layout.
      * If the progress is smaller than or equals to 0, the IconStart will be disabled. If the
      * progress is larger than or equals to Max, the IconEnd will be disabled. The seekbar progress
@@ -190,11 +225,72 @@
      */
     public void setProgress(int progress) {
         mSeekbar.setProgress(progress);
-        updateIconViewIfNeeded(progress);
+        updateIconViewIfNeeded(mSeekbar.getProgress());
+    }
+
+    private void setProgressFromButton(int progress) {
+        mSetProgressFromButtonFlag = true;
+        mSeekbar.setProgress(progress);
+        updateIconViewIfNeeded(mSeekbar.getProgress());
+    }
+
+    private void onIconStartClicked() {
+        final int progress = mSeekbar.getProgress();
+        if (progress > 0) {
+            setProgressFromButton(progress - mSeekBarChangeMagnitude);
+        }
+    }
+
+    private void onIconEndClicked() {
+        final int progress = mSeekbar.getProgress();
+        if (progress < mSeekbar.getMax()) {
+            setProgressFromButton(progress + mSeekBarChangeMagnitude);
+        }
+    }
+
+    /**
+     * Get current seekbar progress
+     *
+     * @return
+     */
+    @VisibleForTesting
+    public int getProgress() {
+        return mSeekbar.getProgress();
+    }
+
+    /**
+     * Extended from {@link SeekBar.OnSeekBarChangeListener} to add callback to notify the listeners
+     * the user interaction with the SeekBarWithIconButtonsView is finalized.
+     */
+    public interface OnSeekBarWithIconButtonsChangeListener
+            extends SeekBar.OnSeekBarChangeListener {
+
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({
+                ControlUnitType.SLIDER,
+                ControlUnitType.BUTTON
+        })
+        /** Denotes the Last user interacted control unit type. */
+        @interface ControlUnitType {
+            int SLIDER = 0;
+            int BUTTON = 1;
+        }
+
+        /**
+         * Notification that the user interaction with SeekBarWithIconButtonsView is finalized. This
+         * would be triggered after user ends dragging on the slider or clicks icon buttons.
+         *
+         * @param seekBar The SeekBar in which the user ends interaction with
+         * @param control The last user interacted control unit. It would be
+         *                {@link ControlUnitType#SLIDER} if the user was changing the seekbar
+         *                progress through dragging the slider, or {@link ControlUnitType#BUTTON}
+         *                is the user was clicking button to change the progress.
+         */
+        void onUserInteractionFinalized(SeekBar seekBar, @ControlUnitType int control);
     }
 
     private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
-        private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = null;
+        private OnSeekBarWithIconButtonsChangeListener mOnSeekBarChangeListener = null;
 
         @Override
         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
@@ -202,7 +298,17 @@
                 setSeekbarStateDescription();
             }
             if (mOnSeekBarChangeListener != null) {
-                mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
+                if (mSetProgressFromButtonFlag) {
+                    mSetProgressFromButtonFlag = false;
+                    mOnSeekBarChangeListener.onProgressChanged(
+                            seekBar, progress, /* fromUser= */ true);
+                    // Directly trigger onUserInteractionFinalized since the interaction
+                    // (click button) is ended.
+                    mOnSeekBarChangeListener.onUserInteractionFinalized(
+                            seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON);
+                } else {
+                    mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
+                }
             }
             updateIconViewIfNeeded(progress);
         }
@@ -218,10 +324,13 @@
         public void onStopTrackingTouch(SeekBar seekBar) {
             if (mOnSeekBarChangeListener != null) {
                 mOnSeekBarChangeListener.onStopTrackingTouch(seekBar);
+                mOnSeekBarChangeListener.onUserInteractionFinalized(
+                        seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER);
             }
         }
 
-        void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener listener) {
+        void setOnSeekBarWithIconButtonsChangeListener(
+                OnSeekBarWithIconButtonsChangeListener listener) {
             mOnSeekBarChangeListener = listener;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java
index 9c3448b..dc32a59 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java
@@ -16,34 +16,36 @@
 
 package com.android.systemui.complication;
 
-import static com.android.systemui.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW;
+import static com.android.systemui.complication.dagger.DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW;
 import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
 
 import android.view.View;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.CoreStartable;
+import com.android.systemui.complication.dagger.DreamClockTimeComplicationComponent;
 import com.android.systemui.dagger.qualifiers.SystemUser;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.ViewController;
 import com.android.systemui.util.condition.ConditionalCoreStartable;
 
 import javax.inject.Inject;
 import javax.inject.Named;
-import javax.inject.Provider;
 
 /**
  * Clock Time Complication that produce Clock Time view holder.
  */
 public class DreamClockTimeComplication implements Complication {
-    private final Provider<DreamClockTimeViewHolder> mDreamClockTimeViewHolderProvider;
+    private final DreamClockTimeComplicationComponent.Factory mComponentFactory;
 
     /**
      * Default constructor for {@link DreamClockTimeComplication}.
      */
     @Inject
     public DreamClockTimeComplication(
-            Provider<DreamClockTimeViewHolder> dreamClockTimeViewHolderProvider) {
-        mDreamClockTimeViewHolderProvider = dreamClockTimeViewHolderProvider;
+            DreamClockTimeComplicationComponent.Factory componentFactory) {
+        mComponentFactory = componentFactory;
     }
 
     @Override
@@ -56,7 +58,7 @@
      */
     @Override
     public ViewHolder createView(ComplicationViewModel model) {
-        return mDreamClockTimeViewHolderProvider.get();
+        return mComponentFactory.create().getViewHolder();
     }
 
     /**
@@ -94,11 +96,14 @@
         private final ComplicationLayoutParams mLayoutParams;
 
         @Inject
-        DreamClockTimeViewHolder(@Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) View view,
+        DreamClockTimeViewHolder(
+                @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) View view,
                 @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
-                        ComplicationLayoutParams layoutParams) {
+                        ComplicationLayoutParams layoutParams,
+                DreamClockTimeViewController viewController) {
             mView = view;
             mLayoutParams = layoutParams;
+            viewController.init();
         }
 
         @Override
@@ -111,4 +116,29 @@
             return mLayoutParams;
         }
     }
+
+    static class DreamClockTimeViewController extends ViewController<View> {
+        private final UiEventLogger mUiEventLogger;
+
+        @Inject
+        DreamClockTimeViewController(
+                @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) View view,
+                UiEventLogger uiEventLogger) {
+            super(view);
+
+            mUiEventLogger = uiEventLogger;
+        }
+
+        @Override
+        protected void onViewAttached() {
+            mView.setOnClickListener(this::onClick);
+        }
+
+        @Override
+        protected void onViewDetached() {}
+
+        private void onClick(View v) {
+            mUiEventLogger.log(DreamOverlayUiEvent.DREAM_CLOCK_TAPPED);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
index 4d99282..7ac1cc7 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
@@ -29,8 +29,6 @@
 import android.view.View;
 import android.widget.ImageView;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -201,23 +199,6 @@
 
         private final UiEventLogger mUiEventLogger;
 
-        @VisibleForTesting
-        public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum {
-            @UiEvent(doc = "The home controls on the screensaver has been tapped.")
-            DREAM_HOME_CONTROLS_TAPPED(1212);
-
-            private final int mId;
-
-            DreamOverlayEvent(int id) {
-                mId = id;
-            }
-
-            @Override
-            public int getId() {
-                return mId;
-            }
-        }
-
         @Inject
         DreamHomeControlsChipViewController(
                 @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
@@ -246,7 +227,7 @@
         private void onClickHomeControls(View v) {
             if (DEBUG) Log.d(TAG, "home controls complication tapped");
 
-            mUiEventLogger.log(DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
+            mUiEventLogger.log(DreamOverlayUiEvent.DREAM_HOME_CONTROLS_TAPPED);
 
             final Intent intent = new Intent(mContext, ControlsActivity.class)
                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt b/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt
new file mode 100644
index 0000000..17cc829
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamOverlayUiEvent.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.complication
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger.UiEventEnum
+
+/** UI log events for the dream overlay. */
+enum class DreamOverlayUiEvent(private val mId: Int) : UiEventEnum {
+    @UiEvent(doc = "The home controls on the screensaver has been tapped.")
+    DREAM_HOME_CONTROLS_TAPPED(1212),
+    @UiEvent(doc = "The clock on the screensaver has been tapped") DREAM_CLOCK_TAPPED(1440),
+    @UiEvent(doc = "The weather on the screensaver has been tapped") DREAM_WEATHER_TAPPED(1441);
+
+    override fun getId(): Int {
+        return mId
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
new file mode 100644
index 0000000..87c3b2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
@@ -0,0 +1,81 @@
+/*
+ *  Copyright (C) 2023 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.complication.dagger
+
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.TextClock
+import com.android.internal.util.Preconditions
+import com.android.systemui.R
+import com.android.systemui.complication.DreamClockTimeComplication
+import com.android.systemui.complication.DreamClockTimeComplication.DreamClockTimeViewHolder
+import dagger.Module
+import dagger.Provides
+import dagger.Subcomponent
+import javax.inject.Named
+import javax.inject.Scope
+
+/** Responsible for generating dependencies for the [DreamClockTimeComplication]. */
+@Subcomponent(
+    modules = [DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule::class]
+)
+@DreamClockTimeComplicationComponent.DreamClockTimeComplicationScope
+interface DreamClockTimeComplicationComponent {
+    /** Scope of the clock complication. */
+    @MustBeDocumented
+    @Retention(AnnotationRetention.RUNTIME)
+    @Scope
+    annotation class DreamClockTimeComplicationScope
+
+    /** Factory that generates a component for the clock complication. */
+    @Subcomponent.Factory
+    interface Factory {
+        fun create(): DreamClockTimeComplicationComponent
+    }
+
+    /** Creates a view holder for the clock complication. */
+    fun getViewHolder(): DreamClockTimeViewHolder
+
+    /** Module for providing injected values within the clock complication scope. */
+    @Module
+    interface DreamClockTimeComplicationModule {
+        companion object {
+            const val DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view"
+            private const val TAG_WEIGHT = "'wght' "
+            private const val WEIGHT = 400
+
+            /** Provides the complication view. */
+            @Provides
+            @DreamClockTimeComplicationScope
+            @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW)
+            fun provideComplicationView(layoutInflater: LayoutInflater): View {
+                val view =
+                    Preconditions.checkNotNull(
+                        layoutInflater.inflate(
+                            R.layout.dream_overlay_complication_clock_time,
+                            /* root = */ null,
+                            /* attachToRoot = */ false,
+                        ) as TextClock,
+                        "R.layout.dream_overlay_complication_clock_time did not properly inflate"
+                    )
+                view.setFontVariationSettings(TAG_WEIGHT + WEIGHT)
+                return view
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationModule.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationModule.java
deleted file mode 100644
index fd711ee..0000000
--- a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationModule.java
+++ /dev/null
@@ -1,56 +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.complication.dagger;
-
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.TextClock;
-
-import com.android.internal.util.Preconditions;
-import com.android.systemui.R;
-import com.android.systemui.complication.DreamClockTimeComplication;
-
-import dagger.Module;
-import dagger.Provides;
-
-import javax.inject.Named;
-
-/**
- * Module for providing {@link DreamClockTimeComplication}.
- */
-@Module
-public interface DreamClockTimeComplicationModule {
-    String DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view";
-    String TAG_WEIGHT = "'wght' ";
-    int WEIGHT = 400;
-
-    /**
-     * Provides the complication view.
-     */
-    @Provides
-    @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW)
-    static View provideComplicationView(LayoutInflater layoutInflater) {
-        final View view = Preconditions.checkNotNull(
-                        layoutInflater.inflate(R.layout.dream_overlay_complication_clock_time,
-                                null, false),
-                "R.layout.dream_overlay_complication_clock_time did not properly inflated");
-        ((TextClock) view.findViewById(R.id.time_view)).setFontVariationSettings(
-                TAG_WEIGHT + WEIGHT);
-        return view;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java
index 98975fbd..776c972 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java
@@ -35,10 +35,9 @@
  * Module for all components with corresponding dream layer complications registered in
  * {@link SystemUIBinder}.
  */
-@Module(includes = {
-                DreamClockTimeComplicationModule.class,
-        },
+@Module(
         subcomponents = {
+                DreamClockTimeComplicationComponent.class,
                 DreamHomeControlsComplicationComponent.class,
                 DreamMediaEntryComplicationComponent.class
         })
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 4173bdc..85f31e5 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -21,10 +21,11 @@
 import android.view.View
 import androidx.activity.ComponentActivity
 import androidx.lifecycle.LifecycleOwner
-import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.util.time.SystemClock
+import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 
 /**
  * A facade to interact with Compose, when it is available.
@@ -61,9 +62,9 @@
     ): View
 
     /** Create a [View] to represent [viewModel] on screen. */
-    fun createMultiShadeView(
+    fun createSceneContainerView(
         context: Context,
-        viewModel: MultiShadeViewModel,
-        clock: SystemClock,
+        viewModel: SceneContainerViewModel,
+        sceneByKey: Map<SceneKey, Scene>,
     ): View
 }
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
index f17d0f3..0b327a1 100644
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
@@ -66,9 +66,9 @@
 
         contrastButtons =
             mapOf(
-                CONTRAST_LEVEL_STANDARD to findViewById(R.id.contrast_button_standard),
-                CONTRAST_LEVEL_MEDIUM to findViewById(R.id.contrast_button_medium),
-                CONTRAST_LEVEL_HIGH to findViewById(R.id.contrast_button_high)
+                CONTRAST_LEVEL_STANDARD to requireViewById(R.id.contrast_button_standard),
+                CONTRAST_LEVEL_MEDIUM to requireViewById(R.id.contrast_button_medium),
+                CONTRAST_LEVEL_HIGH to requireViewById(R.id.contrast_button_high)
             )
 
         contrastButtons.forEach { (contrastLevel, contrastButton) ->
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
index 18bd467..ccbde01 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -312,6 +312,7 @@
                 Log.d(TAG, "Canceling loadSubscribtion")
                 it.invoke()
             }
+            callback.error("Load cancelled")
         }
 
         override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index e8c97bf..4a534e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -190,7 +190,7 @@
                     PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>())
                 val servicePackageSet = serviceInfoSet.map { it.packageName }
                 prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
-                    completedSeedingPackageSet.intersect(servicePackageSet)).apply()
+                    completedSeedingPackageSet?.intersect(servicePackageSet) ?: emptySet()).apply()
 
                 var changed = false
                 favoriteComponentSet.subtract(serviceInfoSet).forEach {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index 773f423..4c9dbe0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -40,8 +40,6 @@
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.ui.ControlsActivity
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -50,7 +48,6 @@
  * Activity for rearranging and removing controls for a given structure
  */
 open class ControlsEditingActivity @Inject constructor(
-    featureFlags: FeatureFlags,
     @Main private val mainExecutor: Executor,
     private val controller: ControlsControllerImpl,
     private val userTracker: UserTracker,
@@ -76,8 +73,6 @@
 
     private var isFromFavoriting: Boolean = false
 
-    private val isNewFlowEnabled: Boolean =
-        featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
     private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
         private val startingUser = controller.currentUserId
 
@@ -154,7 +149,7 @@
     private fun bindViews() {
         setContentView(R.layout.controls_management)
 
-        getLifecycle().addObserver(
+        lifecycle.addObserver(
             ControlsAnimations.observerForAnimations(
                 requireViewById<ViewGroup>(R.id.controls_management_root),
                 window,
@@ -176,7 +171,7 @@
     private fun bindButtons() {
         addControls = requireViewById<Button>(R.id.addControls).apply {
             isEnabled = true
-            visibility = if (isNewFlowEnabled) View.VISIBLE else View.GONE
+            visibility = View.VISIBLE
             setOnClickListener {
                 if (saveButton.isEnabled) {
                     // The user has made changes
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index d4713d2..8bae667 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -33,7 +33,6 @@
 import android.widget.Button
 import android.widget.FrameLayout
 import android.widget.TextView
-import android.widget.Toast
 import android.window.OnBackInvokedCallback
 import android.window.OnBackInvokedDispatcher
 import androidx.activity.ComponentActivity
@@ -41,24 +40,19 @@
 import androidx.viewpager2.widget.ViewPager2
 import com.android.systemui.Prefs
 import com.android.systemui.R
-import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.TooltipManager
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.ui.ControlsActivity
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
 import java.text.Collator
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
 open class ControlsFavoritingActivity @Inject constructor(
-    featureFlags: FeatureFlags,
     @Main private val executor: Executor,
     private val controller: ControlsControllerImpl,
-    private val listingController: ControlsListingController,
     private val userTracker: UserTracker,
 ) : ComponentActivity() {
 
@@ -92,7 +86,6 @@
     private lateinit var pageIndicator: ManagementPageIndicator
     private var mTooltipManager: TooltipManager? = null
     private lateinit var doneButton: View
-    private lateinit var otherAppsButton: View
     private lateinit var rearrangeButton: Button
     private var listOfStructures = emptyList<StructureContainer>()
 
@@ -104,8 +97,6 @@
         get() = openSource == EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR
     private val fromEditing: Boolean
         get() = openSource == EXTRA_SOURCE_VALUE_FROM_EDITING
-    private val isNewFlowEnabled: Boolean =
-        featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
     private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
         private val startingUser = controller.currentUserId
 
@@ -124,20 +115,6 @@
         onBackPressed()
     }
 
-    private val listingCallback = object : ControlsListingController.ControlsListingCallback {
-
-        override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
-            if (serviceInfos.size > 1) {
-                val newVisibility = if (isNewFlowEnabled) View.GONE else View.VISIBLE
-                if (otherAppsButton.visibility != newVisibility) {
-                    otherAppsButton.post {
-                        otherAppsButton.visibility = newVisibility
-                    }
-                }
-            }
-        }
-    }
-
     override fun onBackPressed() {
         if (fromEditing) {
             animateExitAndFinish()
@@ -216,7 +193,7 @@
 
                         ControlsAnimations.enterAnimation(pageIndicator).apply {
                             addListener(object : AnimatorListenerAdapter() {
-                                override fun onAnimationEnd(animation: Animator?) {
+                                override fun onAnimationEnd(animation: Animator) {
                                     // Position the tooltip if necessary after animations are complete
                                     // so we can get the position on screen. The tooltip is not
                                     // rooted in the layout root.
@@ -268,7 +245,7 @@
     private fun bindViews() {
         setContentView(R.layout.controls_management)
 
-        getLifecycle().addObserver(
+        lifecycle.addObserver(
             ControlsAnimations.observerForAnimations(
                 requireViewById<ViewGroup>(R.id.controls_management_root),
                 window,
@@ -342,7 +319,7 @@
                 getString(R.string.controls_favorite_rearrange_button)
             }
             isEnabled = false
-            visibility = if (isNewFlowEnabled) View.VISIBLE else View.GONE
+            visibility = View.VISIBLE
             setOnClickListener {
                 if (component == null) return@setOnClickListener
                 saveFavorites()
@@ -361,24 +338,6 @@
                 )
             }
         }
-        otherAppsButton = requireViewById<Button>(R.id.other_apps).apply {
-            setOnClickListener {
-                if (doneButton.isEnabled) {
-                    // The user has made changes
-                    Toast.makeText(
-                            applicationContext,
-                            R.string.controls_favorite_toast_no_changes,
-                            Toast.LENGTH_SHORT
-                            ).show()
-                }
-                startActivity(
-                    Intent(context, ControlsProviderSelectorActivity::class.java),
-                    ActivityOptions
-                        .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle()
-                )
-                animateExitAndFinish()
-            }
-        }
 
         doneButton = requireViewById<Button>(R.id.done).apply {
             isEnabled = false
@@ -415,7 +374,6 @@
     override fun onStart() {
         super.onStart()
 
-        listingController.addCallback(listingCallback)
         userTracker.addCallback(userTrackerCallback, executor)
 
         if (DEBUG) {
@@ -440,7 +398,6 @@
     override fun onStop() {
         super.onStop()
 
-        listingController.removeCallback(listingCallback)
         userTracker.removeCallback(userTrackerCallback)
 
         if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 1eba667..83bec66 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -124,8 +124,7 @@
     }
 
     private fun updateServices(newServices: List<ControlsServiceInfo>) {
-        if (featureFlags.isEnabled(Flags.USE_APP_PANELS) &&
-                activityTaskManagerProxy.supportsMultiWindow(context)) {
+        if (activityTaskManagerProxy.supportsMultiWindow(context)) {
             val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED)
             newServices.forEach {
                 it.resolvePanelActivity(allowAllApps) }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index fb19ac9..d5b8693 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -91,7 +91,7 @@
 
         setContentView(R.layout.controls_management)
 
-        getLifecycle().addObserver(
+        lifecycle.addObserver(
             ControlsAnimations.observerForAnimations(
                 requireViewById<ViewGroup>(R.id.controls_management_root),
                 window,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index 5c2402b..4aef209 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -20,8 +20,6 @@
 import android.content.Context
 import android.content.SharedPreferences
 import com.android.systemui.R
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
@@ -33,7 +31,6 @@
     private val context: Context,
     private val userFileManager: UserFileManager,
     private val userTracker: UserTracker,
-    private val featureFlags: FeatureFlags,
 ) : AuthorizedPanelsRepository {
 
     override fun getAuthorizedPanels(): Set<String> {
@@ -74,17 +71,8 @@
                 userTracker.userId,
             )
 
-        // We should add default packages in two cases:
-        // 1) We've never run this
-        // 2) APP_PANELS_REMOVE_APPS_ALLOWED got disabled after user removed all apps
-        val needToSetup =
-            if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
-                sharedPref.getStringSet(KEY, null) == null
-            } else {
-                // There might be an empty set that need to be overridden after the feature has been
-                // turned off after being turned on
-                sharedPref.getStringSet(KEY, null).isNullOrEmpty()
-            }
+        // We should add default packages when we've never run this
+        val needToSetup = sharedPref.getStringSet(KEY, null) == null
         if (needToSetup) {
             sharedPref.edit().putStringSet(KEY, getPreferredPackages()).apply()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
index 0fb5b66..c9edd4a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -21,7 +21,6 @@
 import android.content.SharedPreferences
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
@@ -83,11 +82,7 @@
     }
 
     override fun shouldAddDefaultComponent(): Boolean =
-        if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
-            sharedPreferences.getBoolean(SHOULD_ADD_DEFAULT_PANEL, true)
-        } else {
-            true
-        }
+        sharedPreferences.getBoolean(SHOULD_ADD_DEFAULT_PANEL, true)
 
     override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
         sharedPreferences.edit().putBoolean(SHOULD_ADD_DEFAULT_PANEL, shouldAdd).apply()
diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
index ce0f2e9..501bcf0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
@@ -135,7 +135,7 @@
         val listener = DialogListener(prefs, attempts, onAttemptCompleted)
         val d =
             dialogProvider(activityContext, R.style.Theme_SystemUI_Dialog).apply {
-                setIcon(R.drawable.ic_warning)
+                setIcon(R.drawable.ic_lock_locked)
                 setOnCancelListener(listener)
                 setNeutralButton(R.string.controls_settings_dialog_neutral_button, listener)
                 setPositiveButton(R.string.controls_settings_dialog_positive_button, listener)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
index ff55b76d..a13f717 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
@@ -106,10 +106,8 @@
                 }
             )
 
-            getWindow().apply {
-                setType(WINDOW_TYPE)
-                setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
-            }
+            window?.setType(WINDOW_TYPE)
+            window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
             setOnShowListener(DialogInterface.OnShowListener { _ ->
                 val editText = requireViewById<EditText>(R.id.controls_pin_input)
                 editText.setHint(instructions)
@@ -153,9 +151,7 @@
             )
         }
         return builder.create().apply {
-            getWindow().apply {
-                setType(WINDOW_TYPE)
-            }
+            window?.setType(WINDOW_TYPE)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
index 8029ba8..534832c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
@@ -54,9 +54,10 @@
     /**
      * When a ToggleRange control is interacting with, a drag event is sent.
      *
+     * @param cvh [ControlViewHolder] for the control
      * @param isEdge did the drag event reach a control edge
      */
-    fun drag(isEdge: Boolean)
+    fun drag(cvh: ControlViewHolder, isEdge: Boolean)
 
     /**
      * Send a request to update the value of a device using the [FloatAction].
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 3713811..00d95c0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -34,12 +34,11 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.controls.ControlsMetricsLogger
-import com.android.systemui.controls.settings.ControlsSettingsDialogManager
 import com.android.systemui.controls.settings.ControlsSettingsRepository
 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.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -60,7 +59,6 @@
         private val controlsMetricsLogger: ControlsMetricsLogger,
         private val vibrator: VibratorHelper,
         private val controlsSettingsRepository: ControlsSettingsRepository,
-        private val controlsSettingsDialogManager: ControlsSettingsDialogManager,
         private val featureFlags: FeatureFlags,
 ) : ControlActionCoordinator {
     private var dialog: Dialog? = null
@@ -77,9 +75,6 @@
     }
 
     override fun closeDialogs() {
-        if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
-            controlsSettingsDialogManager.closeDialog()
-        }
         val isActivityFinishing =
             (activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed }
         if (isActivityFinishing == true) {
@@ -127,11 +122,17 @@
         )
     }
 
-    override fun drag(isEdge: Boolean) {
-        if (isEdge) {
-            vibrate(Vibrations.rangeEdgeEffect)
+    override fun drag(cvh: ControlViewHolder, isEdge: Boolean) {
+        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            val constant =
+                if (isEdge)
+                    HapticFeedbackConstants.SEGMENT_TICK
+                else
+                    HapticFeedbackConstants.SEGMENT_FREQUENT_TICK
+            vibrator.performHapticFeedback(cvh.layout, constant)
         } else {
-            vibrate(Vibrations.rangeMiddleEffect)
+            val effect = if (isEdge) Vibrations.rangeEdgeEffect else Vibrations.rangeMiddleEffect
+            vibrate(effect)
         }
     }
 
@@ -169,7 +170,6 @@
     override fun runPendingAction(controlId: String) {
         if (isLocked) return
         if (pendingAction?.controlId == controlId) {
-            showSettingsDialogIfNeeded(pendingAction!!)
             pendingAction?.invoke()
             pendingAction = null
         }
@@ -208,7 +208,6 @@
                 true
             }, { pendingAction = null }, true /* afterKeyguardGone */)
         } else {
-            showSettingsDialogIfNeeded(action)
             action.invoke()
         }
     }
@@ -243,15 +242,6 @@
         }
     }
 
-    private fun showSettingsDialogIfNeeded(action: Action) {
-        if (action.authIsRequired) {
-            return
-        }
-        if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
-            controlsSettingsDialogManager.maybeShowDialog(activityContext) {}
-        }
-    }
-
     @VisibleForTesting
     fun createAction(
         controlId: String,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index c04bc87..abe3423 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -384,7 +384,7 @@
                 )
             }
             addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     stateAnimator = null
                 }
             })
@@ -438,7 +438,7 @@
                 duration = 200L
                 interpolator = Interpolators.LINEAR
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         statusRowUpdater.invoke()
                     }
                 })
@@ -450,7 +450,7 @@
             statusAnimator = AnimatorSet().apply {
                 playSequentially(fadeOut, fadeIn)
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         status.alpha = STATUS_ALPHA_ENABLED
                         statusAnimator = null
                     }
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 4a22e4e..8341964 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -36,7 +36,6 @@
 import com.android.systemui.controls.management.ControlsAnimations
 import com.android.systemui.controls.settings.ControlsSettingsDialogManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
 
@@ -66,18 +65,16 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         lastConfiguration.setTo(resources.configuration)
-        if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
-            window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
-        }
+        window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
 
         setContentView(R.layout.controls_fullscreen)
 
-        getLifecycle().addObserver(
+        lifecycle.addObserver(
             ControlsAnimations.observerForAnimations(
                 requireViewById(R.id.control_detail_root),
                 window,
                 intent,
-                !featureFlags.isEnabled(Flags.USE_APP_PANELS)
+                false
             )
         )
 
@@ -114,7 +111,7 @@
 
         parent = requireViewById(R.id.control_detail_root)
         parent.alpha = 0f
-        if (featureFlags.isEnabled(Flags.USE_APP_PANELS) && !keyguardStateController.isUnlocked) {
+        if (!keyguardStateController.isUnlocked) {
             controlsSettingsDialogManager.maybeShowDialog(this) {
                 uiController.show(parent, { finishOrReturnToDream() }, this)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 631ed3c..b0491ce 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -72,7 +72,6 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -506,32 +505,22 @@
         val isPanel = selectedItem is SelectedItem.PanelItem
         val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
                 ?: EMPTY_STRUCTURE
-        val newFlows = featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
 
         val items = buildList {
             add(OverflowMenuAdapter.MenuItem(
                     context.getText(R.string.controls_open_app),
                     OPEN_APP_ID
             ))
-            if (newFlows || isPanel) {
-                if (extraApps) {
-                    add(OverflowMenuAdapter.MenuItem(
-                            context.getText(R.string.controls_menu_add_another_app),
-                            ADD_APP_ID
-                    ))
-                }
-                if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
-                    add(OverflowMenuAdapter.MenuItem(
-                            context.getText(R.string.controls_menu_remove),
-                            REMOVE_APP_ID,
-                    ))
-                }
-            } else {
+            if (extraApps) {
                 add(OverflowMenuAdapter.MenuItem(
-                        context.getText(R.string.controls_menu_add),
-                        ADD_CONTROLS_ID
+                        context.getText(R.string.controls_menu_add_another_app),
+                        ADD_APP_ID
                 ))
             }
+            add(OverflowMenuAdapter.MenuItem(
+                    context.getText(R.string.controls_menu_remove),
+                    REMOVE_APP_ID,
+            ))
             if (!isPanel) {
                 add(OverflowMenuAdapter.MenuItem(
                         context.getText(R.string.controls_menu_edit),
@@ -665,7 +654,7 @@
 
         val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources)
 
-        val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup
+        val listView = parent.requireViewById(R.id.controls_list) as ViewGroup
         listView.removeAllViews()
         var lastRow: ViewGroup = createRow(inflater, listView)
         selectedStructure.controls.forEach {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index be50a14..98f17f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -132,8 +132,8 @@
 
     init {
         // To pass touches to the task inside TaskView.
-        window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
-        window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+        window?.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
+        window?.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
 
         setContentView(R.layout.controls_detail_dialog)
 
@@ -182,7 +182,7 @@
         }
 
         // consume all insets to achieve slide under effect
-        window.getDecorView().setOnApplyWindowInsetsListener {
+        checkNotNull(window).decorView.setOnApplyWindowInsetsListener {
             v: View, insets: WindowInsets ->
                 val l = v.getPaddingLeft()
                 val r = v.getPaddingRight()
@@ -202,7 +202,7 @@
     }
 
     fun getTaskViewBounds(): Rect {
-        val wm = context.getSystemService(WindowManager::class.java)
+        val wm = checkNotNull(context.getSystemService(WindowManager::class.java))
         val windowMetrics = wm.getCurrentWindowMetrics()
         val rect = windowMetrics.bounds
         val metricInsets = windowMetrics.windowInsets
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
index ad2b785..dbbda9a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
@@ -67,7 +67,8 @@
                     iconMap.put(resourceId, icon)
                 }
             }
-            return RenderInfo(icon!!.constantState.newDrawable(context.resources), fg, bg)
+            return RenderInfo(
+                checkNotNull(icon?.constantState).newDrawable(context.resources), fg, bg)
         }
 
         fun registerComponentIcon(componentName: ComponentName, icon: Drawable) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
index 84cda5a..3c2bfa0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
@@ -94,10 +94,8 @@
             )
         }
         cvh.visibleDialog = builder.create().apply {
-            getWindow().apply {
-                setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
-                show()
-            }
+            window?.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
+            show()
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index 1461135..0d570d2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -235,7 +235,7 @@
         if (isDragging) {
             val isEdge = newLevel == MIN_LEVEL || newLevel == MAX_LEVEL
             if (clipLayer.level != newLevel) {
-                cvh.controlActionCoordinator.drag(isEdge)
+                cvh.controlActionCoordinator.drag(cvh, isEdge)
                 clipLayer.level = newLevel
             }
         } else if (newLevel != clipLayer.level) {
@@ -244,7 +244,7 @@
                     cvh.clipLayer.level = it.animatedValue as Int
                 }
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         rangeAnimator = null
                     }
                 })
@@ -335,7 +335,7 @@
         }
 
         override fun onScroll(
-            e1: MotionEvent,
+            e1: MotionEvent?,
             e2: MotionEvent,
             xDiff: Float,
             yDiff: Float
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java b/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java
index 0992882..585c390 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java
@@ -24,11 +24,11 @@
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.internal.widget.LockPatternUtils;
 
-import javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
+import javax.inject.Singleton;
+
 /**
  * Provides items imported from com.android.internal.
  */
@@ -51,7 +51,7 @@
     /** */
     @Provides
     public NotificationMessagingUtil provideNotificationMessagingUtil(Context context) {
-        return new NotificationMessagingUtil(context);
+        return new NotificationMessagingUtil(context, null);
     }
 
     /** Provides an instance of {@link com.android.internal.logging.UiEventLogger} */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
index a3e26b8..d727a70 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/PluginModule.java
@@ -28,6 +28,7 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.phone.ActivityStarterImpl;
 import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher;
 import com.android.systemui.volume.VolumeDialogControllerImpl;
 
 import dagger.Binds;
@@ -49,6 +50,10 @@
     @Binds
     abstract DarkIconDispatcher provideDarkIconDispatcher(DarkIconDispatcherImpl controllerImpl);
 
+    @Binds
+    abstract SysuiDarkIconDispatcher provideSysuiDarkIconDispatcher(
+            DarkIconDispatcherImpl controllerImpl);
+
     /** */
     @Binds
     abstract FalsingManager provideFalsingManager(FalsingManagerProxy falsingManagerImpl);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index a90980f..046ccf16 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -18,6 +18,7 @@
 
 import com.android.systemui.globalactions.ShutdownUiModule;
 import com.android.systemui.keyguard.CustomizationProvider;
+import com.android.systemui.shade.ShadeModule;
 import com.android.systemui.statusbar.NotificationInsetsModule;
 import com.android.systemui.statusbar.QsFrameTranslateModule;
 
@@ -32,6 +33,7 @@
         DependencyProvider.class,
         NotificationInsetsModule.class,
         QsFrameTranslateModule.class,
+        ShadeModule.class,
         ShutdownUiModule.class,
         SystemUIBinder.class,
         SystemUIModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 2262d8a..3562477 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -40,12 +40,9 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImplementation;
 import com.android.systemui.rotationlock.RotationLockModule;
-import com.android.systemui.scene.SceneContainerFrameworkModule;
 import com.android.systemui.screenshot.ReferenceScreenshotModule;
 import com.android.systemui.settings.dagger.MultiUserUtilsModule;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeControllerImpl;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyboardShortcutsModule;
@@ -72,6 +69,7 @@
 import com.android.systemui.statusbar.policy.SensorPrivacyController;
 import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
 import com.android.systemui.volume.dagger.VolumeModule;
+import com.android.systemui.wallpapers.dagger.WallpaperModule;
 
 import dagger.Binds;
 import dagger.Module;
@@ -104,10 +102,10 @@
         QSModule.class,
         ReferenceScreenshotModule.class,
         RotationLockModule.class,
-        SceneContainerFrameworkModule.class,
         StatusBarEventsModule.class,
         StartCentralSurfacesModule.class,
         VolumeModule.class,
+        WallpaperModule.class,
         KeyboardShortcutsModule.class
 })
 public abstract class ReferenceSystemUIModule {
@@ -150,9 +148,6 @@
     @Binds
     abstract DockManager bindDockManager(DockManagerImpl dockManager);
 
-    @Binds
-    abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
-
     @SysUISingleton
     @Provides
     @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 52355f3..6fdb4ca 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.dagger;
 
-import com.android.keyguard.clock.ClockOptionsProvider;
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.Dependency;
@@ -224,10 +223,10 @@
     Optional<NaturalRotationUnfoldProgressProvider> getNaturalRotationUnfoldProgressProvider();
 
     /** */
-    Optional<MediaMuteAwaitConnectionCli> getMediaMuteAwaitConnectionCli();
+    MediaMuteAwaitConnectionCli getMediaMuteAwaitConnectionCli();
 
     /** */
-    Optional<NearbyMediaDevicesManager> getNearbyMediaDevicesManager();
+    NearbyMediaDevicesManager getNearbyMediaDevicesManager();
 
     /**
      * Returns {@link CoreStartable}s that should be started with the application.
@@ -252,10 +251,5 @@
     /**
      * Member injection into the supplied argument.
      */
-    void inject(ClockOptionsProvider clockOptionsProvider);
-
-    /**
-     * Member injection into the supplied argument.
-     */
     void inject(PeopleProvider peopleProvider);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 25634f0..484be9c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.keyboard.KeyboardUI
 import com.android.systemui.keyboard.PhysicalKeyboardCoreStartable
 import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.KeyguardViewConfigurator
 import com.android.systemui.keyguard.data.quickaffordance.MuteQuickAffordanceCoreStartable
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.media.RingtonePlayer
@@ -41,13 +42,18 @@
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
 import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
+import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
 import com.android.systemui.power.PowerUI
 import com.android.systemui.reardisplay.RearDisplayDialogController
 import com.android.systemui.recents.Recents
 import com.android.systemui.settings.dagger.MultiUserUtilsModule
 import com.android.systemui.shortcut.ShortcutKeyDispatcher
+import com.android.systemui.statusbar.ImmersiveModeConfirmation
 import com.android.systemui.statusbar.notification.InstantAppNotifier
 import com.android.systemui.statusbar.phone.KeyguardLiftController
+import com.android.systemui.statusbar.phone.LockscreenWallpaper
+import com.android.systemui.statusbar.phone.ScrimController
+import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener
 import com.android.systemui.stylus.StylusUsiPowerStartable
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.theme.ThemeOverlayController
@@ -56,6 +62,7 @@
 import com.android.systemui.util.NotificationChannels
 import com.android.systemui.util.StartBinderLoggerModule
 import com.android.systemui.volume.VolumeUI
+import com.android.systemui.wallpapers.dagger.WallpaperModule
 import com.android.systemui.wmshell.WMShell
 import dagger.Binds
 import dagger.Module
@@ -69,6 +76,7 @@
     MultiUserUtilsModule::class,
     StartControlsStartableModule::class,
     StartBinderLoggerModule::class,
+    WallpaperModule::class,
 ])
 abstract class SystemUICoreStartableModule {
     /** Inject into AuthController.  */
@@ -109,6 +117,14 @@
     @ClassKey(KeyboardUI::class)
     abstract fun bindKeyboardUI(sysui: KeyboardUI): CoreStartable
 
+    /** Inject into MediaProjectionTaskSwitcherCoreStartable. */
+    @Binds
+    @IntoMap
+    @ClassKey(MediaProjectionTaskSwitcherCoreStartable::class)
+    abstract fun bindProjectedTaskListener(
+            sysui: MediaProjectionTaskSwitcherCoreStartable
+    ): CoreStartable
+
     /** Inject into KeyguardBiometricLockoutLogger */
     @Binds
     @IntoMap
@@ -148,6 +164,12 @@
     @ClassKey(Recents::class)
     abstract fun bindRecents(sysui: Recents): CoreStartable
 
+    /** Inject into ImmersiveModeConfirmation.  */
+    @Binds
+    @IntoMap
+    @ClassKey(ImmersiveModeConfirmation::class)
+    abstract fun bindImmersiveModeConfirmation(sysui: ImmersiveModeConfirmation): CoreStartable
+
     /** Inject into RingtonePlayer.  */
     @Binds
     @IntoMap
@@ -295,4 +317,26 @@
     @IntoMap
     @ClassKey(AssistantAttentionMonitor::class)
     abstract fun bindAssistantAttentionMonitor(sysui: AssistantAttentionMonitor): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(KeyguardViewConfigurator::class)
+    abstract fun bindKeyguardViewConfigurator(impl: KeyguardViewConfigurator): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(LockscreenWallpaper::class)
+    abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(ScrimController::class)
+    abstract fun bindScrimController(impl: ScrimController): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(StatusBarHeadsUpChangeListener::class)
+    abstract fun bindStatusBarHeadsUpChangeListener(
+        impl: StatusBarHeadsUpChangeListener
+    ): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 5bcf32a..f73a602 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -23,7 +23,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.clock.ClockInfoModule;
 import com.android.keyguard.dagger.ClockRegistryModule;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.BootCompleteCache;
@@ -39,6 +38,7 @@
 import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
 import com.android.systemui.biometrics.dagger.BiometricsModule;
 import com.android.systemui.biometrics.dagger.UdfpsModule;
+import com.android.systemui.bouncer.ui.BouncerViewModule;
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
 import com.android.systemui.common.ui.data.repository.CommonRepositoryModule;
@@ -47,17 +47,19 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.SystemUser;
 import com.android.systemui.demomode.dagger.DemoModeModule;
+import com.android.systemui.display.DisplayModule;
 import com.android.systemui.doze.dagger.DozeComponent;
 import com.android.systemui.dreams.dagger.DreamModule;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.FlagsModule;
 import com.android.systemui.keyboard.KeyboardModule;
-import com.android.systemui.keyguard.data.BouncerViewModule;
+import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule;
 import com.android.systemui.log.dagger.LogModule;
 import com.android.systemui.log.dagger.MonitorLog;
 import com.android.systemui.log.table.TableLogBuffer;
 import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
+import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherModule;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.motiontool.MotionToolModule;
 import com.android.systemui.navigationbar.NavigationBarComponent;
@@ -74,12 +76,12 @@
 import com.android.systemui.qs.footer.dagger.FooterActionsModule;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.retail.dagger.RetailModeModule;
+import com.android.systemui.scene.SceneContainerFrameworkModule;
 import com.android.systemui.screenrecord.ScreenRecordModule;
 import com.android.systemui.screenshot.dagger.ScreenshotModule;
 import com.android.systemui.security.data.repository.SecurityRepositoryModule;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeModule;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolatorImpl;
 import com.android.systemui.shared.condition.Monitor;
@@ -88,6 +90,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.connectivity.ConnectivityModule;
+import com.android.systemui.statusbar.disableflags.dagger.DisableFlagsModule;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -160,14 +163,15 @@
             BiometricsModule.class,
             BouncerViewModule.class,
             ClipboardOverlayModule.class,
-            ClockInfoModule.class,
             ClockRegistryModule.class,
             CommonRepositoryModule.class,
+            DisplayModule.class,
             ConnectivityModule.class,
             CoroutinesModule.class,
             DreamModule.class,
             ControlsModule.class,
             DemoModeModule.class,
+            DisableFlagsModule.class,
             FalsingModule.class,
             FlagsModule.class,
             SystemPropertiesFlagsModule.class,
@@ -175,8 +179,10 @@
             GarbageMonitorModule.class,
             KeyboardModule.class,
             LetterboxModule.class,
+            KeyguardBlueprintModule.class,
             LogModule.class,
             MediaProjectionModule.class,
+            MediaProjectionTaskSwitcherModule.class,
             MotionToolModule.class,
             PeopleHubModule.class,
             PeopleModule.class,
@@ -186,12 +192,12 @@
             QRCodeScannerModule.class,
             QSFragmentStartableModule.class,
             RetailModeModule.class,
+            SceneContainerFrameworkModule.class,
             ScreenshotModule.class,
             SensorModule.class,
             SecurityRepositoryModule.class,
             ScreenRecordModule.class,
             SettingsUtilModule.class,
-            ShadeModule.class,
             SmartRepliesInflationModule.class,
             SmartspaceModule.class,
             StatusBarPipelineModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index b71871e..d70c57f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -16,12 +16,11 @@
 
 package com.android.systemui.dagger;
 
-import android.content.Context;
 import android.os.HandlerThread;
 
 import androidx.annotation.Nullable;
 
-import com.android.systemui.SystemUIInitializerFactory;
+import com.android.systemui.SystemUIInitializer;
 import com.android.systemui.tv.TvWMComponent;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -49,7 +48,7 @@
 /**
  * Dagger Subcomponent for WindowManager.  This class explicitly describes the interfaces exported
  * from the WM component into the SysUI component (in
- * {@link SystemUIInitializerFactory#init(Context, boolean)}), and references the specific dependencies
+ * {@link SystemUIInitializer#init(boolean)}), and references the specific dependencies
  * provided by its particular device/form-factor SystemUI implementation.
  *
  * ie. {@link WMComponent} includes {@link WMShellModule}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt
new file mode 100644
index 0000000..5571687
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.decor
+
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PixelFormat
+import android.graphics.drawable.Drawable
+import android.util.Size
+import java.io.PrintWriter
+
+/**
+ * Rounded corner delegate that handles incoming debug commands and can convert them to path
+ * drawables to be shown instead of the system-defined rounded corners.
+ *
+ * These debug corners are expected to supersede the system-defined corners
+ */
+class DebugRoundedCornerDelegate : RoundedCornerResDelegate {
+    override var hasTop: Boolean = false
+        private set
+    override var topRoundedDrawable: Drawable? = null
+        private set
+    override var topRoundedSize: Size = Size(0, 0)
+        private set
+
+    override var hasBottom: Boolean = false
+        private set
+    override var bottomRoundedDrawable: Drawable? = null
+        private set
+    override var bottomRoundedSize: Size = Size(0, 0)
+        private set
+
+    override var physicalPixelDisplaySizeRatio: Float = 1f
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            reloadMeasures()
+        }
+
+    var color: Int = Color.RED
+        set(value) {
+            if (field == value) {
+                return
+            }
+
+            field = value
+            paint.color = field
+        }
+
+    var paint =
+        Paint().apply {
+            color = Color.RED
+            style = Paint.Style.FILL
+        }
+
+    override fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
+        // nop -- debug corners draw the same on every display
+    }
+
+    fun applyNewDebugCorners(
+        topCorner: DebugRoundedCornerModel?,
+        bottomCorner: DebugRoundedCornerModel?,
+    ) {
+        topCorner?.let {
+            hasTop = true
+            topRoundedDrawable = it.toPathDrawable(paint)
+            topRoundedSize = it.size()
+        }
+            ?: {
+                hasTop = false
+                topRoundedDrawable = null
+                topRoundedSize = Size(0, 0)
+            }
+
+        bottomCorner?.let {
+            hasBottom = true
+            bottomRoundedDrawable = it.toPathDrawable(paint)
+            bottomRoundedSize = it.size()
+        }
+            ?: {
+                hasBottom = false
+                bottomRoundedDrawable = null
+                bottomRoundedSize = Size(0, 0)
+            }
+    }
+
+    /**
+     * Remove accumulated debug state by clearing out the drawables and setting [hasTop] and
+     * [hasBottom] to false.
+     */
+    fun removeDebugState() {
+        hasTop = false
+        topRoundedDrawable = null
+        topRoundedSize = Size(0, 0)
+
+        hasBottom = false
+        bottomRoundedDrawable = null
+        bottomRoundedSize = Size(0, 0)
+    }
+
+    /**
+     * Scaling here happens when the display resolution is changed. This logic is exactly the same
+     * as in [RoundedCornerResDelegateImpl]
+     */
+    private fun reloadMeasures() {
+        topRoundedDrawable?.let { topRoundedSize = Size(it.intrinsicWidth, it.intrinsicHeight) }
+        bottomRoundedDrawable?.let {
+            bottomRoundedSize = Size(it.intrinsicWidth, it.intrinsicHeight)
+        }
+
+        if (physicalPixelDisplaySizeRatio != 1f) {
+            if (topRoundedSize.width != 0) {
+                topRoundedSize =
+                    Size(
+                        (physicalPixelDisplaySizeRatio * topRoundedSize.width + 0.5f).toInt(),
+                        (physicalPixelDisplaySizeRatio * topRoundedSize.height + 0.5f).toInt()
+                    )
+            }
+            if (bottomRoundedSize.width != 0) {
+                bottomRoundedSize =
+                    Size(
+                        (physicalPixelDisplaySizeRatio * bottomRoundedSize.width + 0.5f).toInt(),
+                        (physicalPixelDisplaySizeRatio * bottomRoundedSize.height + 0.5f).toInt()
+                    )
+            }
+        }
+    }
+
+    fun dump(pw: PrintWriter) {
+        pw.println("DebugRoundedCornerDelegate state:")
+        pw.println("  hasTop=$hasTop")
+        pw.println("  hasBottom=$hasBottom")
+        pw.println("  topRoundedSize(w,h)=(${topRoundedSize.width},${topRoundedSize.height})")
+        pw.println(
+            "  bottomRoundedSize(w,h)=(${bottomRoundedSize.width},${bottomRoundedSize.height})"
+        )
+        pw.println("  physicalPixelDisplaySizeRatio=$physicalPixelDisplaySizeRatio")
+    }
+}
+
+/** Encapsulates the data coming in from the command line args and turns into a [PathDrawable] */
+data class DebugRoundedCornerModel(
+    val path: Path,
+    val width: Int,
+    val height: Int,
+    val scaleX: Float,
+    val scaleY: Float,
+) {
+    fun size() = Size(width, height)
+
+    fun toPathDrawable(paint: Paint) =
+        PathDrawable(
+            path,
+            width,
+            height,
+            scaleX,
+            scaleY,
+            paint,
+        )
+}
+
+/**
+ * PathDrawable accepts paths from the command line via [DebugRoundedCornerModel], and renders them
+ * in the canvas provided by the screen decor rounded corner provider
+ */
+class PathDrawable(
+    val path: Path,
+    val width: Int,
+    val height: Int,
+    val scaleX: Float = 1f,
+    val scaleY: Float = 1f,
+    val paint: Paint,
+) : Drawable() {
+    private var cf: ColorFilter? = null
+
+    override fun draw(canvas: Canvas) {
+        if (scaleX != 1f || scaleY != 1f) {
+            canvas.scale(scaleX, scaleY)
+        }
+        canvas.drawPath(path, paint)
+    }
+
+    override fun getIntrinsicHeight(): Int = height
+    override fun getIntrinsicWidth(): Int = width
+
+    override fun getOpacity(): Int = PixelFormat.OPAQUE
+
+    override fun setAlpha(alpha: Int) {}
+
+    override fun setColorFilter(colorFilter: ColorFilter?) {
+        cf = colorFilter
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
index 8b4aeef..c64766a 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
@@ -27,44 +27,50 @@
 import com.android.systemui.R
 import java.io.PrintWriter
 
-class RoundedCornerResDelegate(
+interface RoundedCornerResDelegate {
+    val hasTop: Boolean
+    val topRoundedDrawable: Drawable?
+    val topRoundedSize: Size
+
+    val hasBottom: Boolean
+    val bottomRoundedDrawable: Drawable?
+    val bottomRoundedSize: Size
+
+    var physicalPixelDisplaySizeRatio: Float
+
+    fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?)
+}
+
+/**
+ * Delegate for the device-default rounded corners. These will always be loaded from the config
+ * values `R.array.config_roundedCornerTopDrawableArray` and `R.drawable.rounded_corner_top`
+ */
+class RoundedCornerResDelegateImpl(
     private val res: Resources,
     private var displayUniqueId: String?
-) : Dumpable {
-
-    private val density: Float
-        get() = res.displayMetrics.density
+) : RoundedCornerResDelegate, Dumpable {
 
     private var reloadToken: Int = 0
 
-    var hasTop: Boolean = false
+    override var hasTop: Boolean = false
         private set
 
-    var hasBottom: Boolean = false
+    override var hasBottom: Boolean = false
         private set
 
-    var topRoundedDrawable: Drawable? = null
+    override var topRoundedDrawable: Drawable? = null
         private set
 
-    var bottomRoundedDrawable: Drawable? = null
+    override var bottomRoundedDrawable: Drawable? = null
         private set
 
-    var topRoundedSize = Size(0, 0)
+    override var topRoundedSize = Size(0, 0)
         private set
 
-    var bottomRoundedSize = Size(0, 0)
+    override var bottomRoundedSize = Size(0, 0)
         private set
 
-    var tuningSizeFactor: Int? = null
-        set(value) {
-            if (field == value) {
-                return
-            }
-            field = value
-            reloadMeasures()
-        }
-
-    var physicalPixelDisplaySizeRatio: Float = 1f
+    override var physicalPixelDisplaySizeRatio: Float = 1f
         set(value) {
             if (field == value) {
                 return
@@ -78,7 +84,7 @@
         reloadMeasures()
     }
 
-    fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
+    override fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
         if (displayUniqueId != newDisplayUniqueId) {
             displayUniqueId = newDisplayUniqueId
             newReloadToken ?.let { reloadToken = it }
@@ -122,19 +128,6 @@
             bottomRoundedSize = Size(it.intrinsicWidth, it.intrinsicHeight)
         }
 
-        tuningSizeFactor?.let {
-            if (it <= 0) {
-                return
-            }
-            val length: Int = (it * density).toInt()
-            if (topRoundedSize.width > 0) {
-                topRoundedSize = Size(length, length)
-            }
-            if (bottomRoundedSize.width > 0) {
-                bottomRoundedSize = Size(length, length)
-            }
-        }
-
         if (physicalPixelDisplaySizeRatio != 1f) {
             if (topRoundedSize.width != 0) {
                 topRoundedSize = Size(
diff --git a/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt b/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt
new file mode 100644
index 0000000..fa1d898
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.decor
+
+import android.graphics.Color
+import android.graphics.Path
+import android.util.PathParser
+import com.android.systemui.statusbar.commandline.ParseableCommand
+import com.android.systemui.statusbar.commandline.Type
+import com.android.systemui.statusbar.commandline.map
+import java.io.PrintWriter
+
+/** Debug screen-decor command to be handled by the SystemUI command line interface */
+class ScreenDecorCommand(
+    private val callback: Callback,
+) : ParseableCommand(SCREEN_DECOR_CMD_NAME) {
+    val debug: Boolean? by
+        param(
+            longName = "debug",
+            description =
+                "Enter or exits debug mode. Effectively makes the corners visible and allows " +
+                    "for overriding the path data for the anti-aliasing corner paths and display " +
+                    "cutout.",
+            valueParser = Type.Boolean,
+        )
+
+    val color: Int? by
+        param(
+            longName = "color",
+            shortName = "c",
+            description =
+                "Set a specific color for the debug assets. See Color#parseString() for " +
+                    "accepted inputs.",
+            valueParser = Type.String.map { it.toColorIntOrNull() }
+        )
+
+    val roundedTop: RoundedCornerSubCommand? by subCommand(RoundedCornerSubCommand("rounded-top"))
+
+    val roundedBottom: RoundedCornerSubCommand? by
+        subCommand(RoundedCornerSubCommand("rounded-bottom"))
+
+    override fun execute(pw: PrintWriter) {
+        callback.onExecute(this, pw)
+    }
+
+    override fun toString(): String {
+        return "ScreenDecorCommand(" +
+            "debug=$debug, " +
+            "color=$color, " +
+            "roundedTop=$roundedTop, " +
+            "roundedBottom=$roundedBottom)"
+    }
+
+    /** For use in ScreenDecorations.java, define a Callback */
+    interface Callback {
+        fun onExecute(cmd: ScreenDecorCommand, pw: PrintWriter)
+    }
+
+    companion object {
+        const val SCREEN_DECOR_CMD_NAME = "screen-decor"
+    }
+}
+
+/**
+ * Defines a subcommand suitable for `rounded-top` and `rounded-bottom`. They both have the same
+ * API.
+ */
+class RoundedCornerSubCommand(name: String) : ParseableCommand(name) {
+    val height by
+        param(
+                longName = "height",
+                description = "The height of a corner, in pixels.",
+                valueParser = Type.Int,
+            )
+            .required()
+
+    val width by
+        param(
+                longName = "width",
+                description =
+                    "The width of the corner, in pixels. Likely should be equal to the height.",
+                valueParser = Type.Int,
+            )
+            .required()
+
+    val pathData by
+        param(
+                longName = "path-data",
+                shortName = "d",
+                description =
+                    "PathParser-compatible path string to be rendered as the corner drawable. " +
+                        "This path should be a closed arc oriented as the top-left corner " +
+                        "of the device",
+                valueParser = Type.String.map { it.toPathOrNull() }
+            )
+            .required()
+
+    val viewportHeight: Float? by
+        param(
+            longName = "viewport-height",
+            description =
+                "The height of the viewport for the given path string. " +
+                    "If null, the corner height will be used.",
+            valueParser = Type.Float,
+        )
+
+    val scaleY: Float
+        get() = viewportHeight?.let { height.toFloat() / it } ?: 1.0f
+
+    val viewportWidth: Float? by
+        param(
+            longName = "viewport-width",
+            description =
+                "The width of the viewport for the given path string. " +
+                    "If null, the corner width will be used.",
+            valueParser = Type.Float,
+        )
+
+    val scaleX: Float
+        get() = viewportWidth?.let { width.toFloat() / it } ?: 1.0f
+
+    override fun execute(pw: PrintWriter) {
+        // Not needed for a subcommand
+    }
+
+    override fun toString(): String {
+        return "RoundedCornerSubCommand(" +
+            "height=$height," +
+            " width=$width," +
+            " pathData='$pathData'," +
+            " viewportHeight=$viewportHeight," +
+            " viewportWidth=$viewportWidth)"
+    }
+
+    fun toRoundedCornerDebugModel(): DebugRoundedCornerModel =
+        DebugRoundedCornerModel(
+            path = pathData,
+            width = width,
+            height = height,
+            scaleX = scaleX,
+            scaleY = scaleY,
+        )
+}
+
+fun String.toPathOrNull(): Path? =
+    try {
+        PathParser.createPathFromPathData(this)
+    } catch (e: Exception) {
+        null
+    }
+
+fun String.toColorIntOrNull(): Int? =
+    try {
+        Color.parseColor(this)
+    } catch (e: Exception) {
+        null
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
new file mode 100644
index 0000000..65cd84b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display
+
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayRepositoryImpl
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractorImpl
+import dagger.Binds
+import dagger.Module
+
+/** Module binding display related classes. */
+@Module
+interface DisplayModule {
+    @Binds
+    fun bindConnectedDisplayInteractor(
+        provider: ConnectedDisplayInteractorImpl
+    ): ConnectedDisplayInteractor
+
+    @Binds fun bindsDisplayRepository(displayRepository: DisplayRepositoryImpl): DisplayRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
new file mode 100644
index 0000000..cef45dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import android.content.Context
+import android.content.res.Configuration
+import android.util.DisplayMetrics
+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.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.DisplayMetricsRepoLog
+import com.android.systemui.statusbar.policy.ConfigurationController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository tracking display-related metrics like display height and width. */
+@SysUISingleton
+class DisplayMetricsRepository
+@Inject
+constructor(
+    @Application scope: CoroutineScope,
+    configurationController: ConfigurationController,
+    displayMetricsHolder: DisplayMetrics,
+    context: Context,
+    @DisplayMetricsRepoLog logBuffer: LogBuffer,
+) {
+
+    private val displayMetrics: StateFlow<DisplayMetrics> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : ConfigurationController.ConfigurationListener {
+                        override fun onConfigChanged(newConfig: Configuration?) {
+                            context.display?.getMetrics(displayMetricsHolder)
+                            trySend(displayMetricsHolder)
+                        }
+                    }
+                configurationController.addCallback(callback)
+                awaitClose { configurationController.removeCallback(callback) }
+            }
+            .onEach {
+                logBuffer.log(
+                    "DisplayMetrics",
+                    LogLevel.INFO,
+                    { str1 = it.toString() },
+                    { "New metrics: $str1" },
+                )
+            }
+            .stateIn(scope, SharingStarted.Eagerly, displayMetricsHolder)
+
+    /** Returns the current display height in pixels. */
+    val heightPixels: Int
+        get() = displayMetrics.value.heightPixels
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
new file mode 100644
index 0000000..b18f7bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManager.DisplayListener
+import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_ADDED
+import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
+import android.os.Handler
+import android.view.Display
+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.util.traceSection
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
+
+/** Provides a [Flow] of [Display] as returned by [DisplayManager]. */
+interface DisplayRepository {
+    /** Provides a nullable set of displays. */
+    val displays: Flow<Set<Display>>
+}
+
+@SysUISingleton
+class DisplayRepositoryImpl
+@Inject
+constructor(
+    private val displayManager: DisplayManager,
+    @Background backgroundHandler: Handler,
+    @Application applicationScope: CoroutineScope,
+    @Background backgroundCoroutineDispatcher: CoroutineDispatcher
+) : DisplayRepository {
+
+    override val displays: Flow<Set<Display>> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : DisplayListener {
+                        override fun onDisplayAdded(displayId: Int) {
+                            trySend(getDisplays())
+                        }
+
+                        override fun onDisplayRemoved(displayId: Int) {
+                            trySend(getDisplays())
+                        }
+
+                        override fun onDisplayChanged(displayId: Int) {
+                            trySend(getDisplays())
+                        }
+                    }
+                displayManager.registerDisplayListener(
+                    callback,
+                    backgroundHandler,
+                    EVENT_FLAG_DISPLAY_ADDED or
+                        EVENT_FLAG_DISPLAY_CHANGED or
+                        EVENT_FLAG_DISPLAY_REMOVED,
+                )
+                awaitClose { displayManager.unregisterDisplayListener(callback) }
+            }
+            .flowOn(backgroundCoroutineDispatcher)
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = getDisplays()
+            )
+
+    fun getDisplays(): Set<Display> =
+        traceSection("DisplayRepository#getDisplays()") {
+            displayManager.displays?.toSet() ?: emptySet()
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
new file mode 100644
index 0000000..4b957c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.domain.interactor
+
+import android.view.Display
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** Provides information about an external connected display. */
+interface ConnectedDisplayInteractor {
+    /**
+     * Provides the current external display state.
+     *
+     * The state is:
+     * - [State.CONNECTED] when there is at least one display with [TYPE_EXTERNAL].
+     * - [State.CONNECTED_SECURE] when is at least one display with both [TYPE_EXTERNAL] AND
+     *   [Display.FLAG_SECURE] set
+     */
+    val connectedDisplayState: Flow<State>
+
+    /** Possible connected display state. */
+    enum class State {
+        DISCONNECTED,
+        CONNECTED,
+        CONNECTED_SECURE,
+    }
+}
+
+@SysUISingleton
+class ConnectedDisplayInteractorImpl
+@Inject
+constructor(
+    displayRepository: DisplayRepository,
+) : ConnectedDisplayInteractor {
+
+    override val connectedDisplayState: Flow<State> =
+        displayRepository.displays
+            .map { displays ->
+                val externalDisplays =
+                    displays.filter { display -> display.type == Display.TYPE_EXTERNAL }
+
+                val secureExternalDisplays =
+                    externalDisplays.filter { it.flags and Display.FLAG_SECURE != 0 }
+
+                if (externalDisplays.isEmpty()) {
+                    State.DISCONNECTED
+                } else if (!secureExternalDisplays.isEmpty()) {
+                    State.CONNECTED_SECURE
+                } else {
+                    State.CONNECTED
+                }
+            }
+            .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 5369780..75b8e51 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -20,9 +20,9 @@
 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.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.log.dagger.DozeLog
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.google.errorprone.annotations.CompileTimeConstant
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 7c816ce..34a80e8 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -26,9 +26,9 @@
 import android.text.format.Formatter;
 import android.util.Log;
 
+import com.android.systemui.DejankUtils;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.dagger.DozeScope;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.AlarmTimeout;
 import com.android.systemui.util.wakelock.WakeLock;
@@ -52,15 +52,21 @@
     private final boolean mCanAnimateTransition;
     private final DozeParameters mDozeParameters;
     private final DozeLog mDozeLog;
-    private final StatusBarStateController mStatusBarStateController;
 
     private long mLastTimeTickElapsed = 0;
+    // If time tick is scheduled and there's not a pending runnable to cancel:
+    private boolean mTimeTickScheduled;
+    private final Runnable mCancelTimeTickerRunnable =  new Runnable() {
+        @Override
+        public void run() {
+            mTimeTicker.cancel();
+        }
+    };
 
     @Inject
     public DozeUi(Context context, AlarmManager alarmManager,
             WakeLock wakeLock, DozeHost host, @Main Handler handler,
             DozeParameters params,
-            StatusBarStateController statusBarStateController,
             DozeLog dozeLog) {
         mContext = context;
         mWakeLock = wakeLock;
@@ -70,7 +76,6 @@
         mDozeParameters = params;
         mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler);
         mDozeLog = dozeLog;
-        mStatusBarStateController = statusBarStateController;
     }
 
     @Override
@@ -157,13 +162,15 @@
     }
 
     private void scheduleTimeTick() {
-        if (mTimeTicker.isScheduled()) {
+        if (mTimeTickScheduled) {
             return;
         }
+        mTimeTickScheduled = true;
+        DejankUtils.removeCallbacks(mCancelTimeTickerRunnable);
 
         long time = System.currentTimeMillis();
         long delta = roundToNextMinute(time) - System.currentTimeMillis();
-        boolean scheduled = mTimeTicker.schedule(delta, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+        boolean scheduled = mTimeTicker.schedule(delta, AlarmTimeout.MODE_RESCHEDULE_IF_SCHEDULED);
         if (scheduled) {
             mDozeLog.traceTimeTickScheduled(time, time + delta);
         }
@@ -171,11 +178,11 @@
     }
 
     private void unscheduleTimeTick() {
-        if (!mTimeTicker.isScheduled()) {
+        if (!mTimeTickScheduled) {
             return;
         }
-        verifyLastTimeTick();
-        mTimeTicker.cancel();
+        mTimeTickScheduled = false;
+        DejankUtils.postAfterTraversal(mCancelTimeTickerRunnable);
     }
 
     private void verifyLastTimeTick() {
@@ -205,6 +212,7 @@
         // Keep wakelock until a frame has been pushed.
         mHandler.post(mWakeLock.wrap(() -> {}));
 
+        mTimeTickScheduled = false;
         scheduleTimeTick();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
index fdb7651..f3a07fc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
@@ -16,15 +16,52 @@
 
 package com.android.systemui.dreams
 
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.DreamLog
-import javax.inject.Inject
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
 
 /** Logs dream-related stuff to a {@link LogBuffer}. */
-class DreamLogger @Inject constructor(@DreamLog private val buffer: LogBuffer) {
-    /** Logs a debug message to the buffer. */
-    fun d(tag: String, message: String) {
-        buffer.log(tag, LogLevel.DEBUG, { str1 = message }, { message })
-    }
+class DreamLogger(buffer: MessageBuffer, tag: String) : Logger(buffer, tag) {
+    fun logDreamOverlayEnabled(enabled: Boolean) =
+        d({ "Dream overlay enabled: $bool1" }) { bool1 = enabled }
+
+    fun logIgnoreAddComplication(reason: String, complication: String) =
+        d({ "Ignore adding complication, reason: $str1, complication: $str2" }) {
+            str1 = reason
+            str2 = complication
+        }
+
+    fun logIgnoreRemoveComplication(reason: String, complication: String) =
+        d({ "Ignore removing complication, reason: $str1, complication: $str2" }) {
+            str1 = reason
+            str2 = complication
+        }
+
+    fun logAddComplication(complication: String) =
+        d({ "Add dream complication: $str1" }) { str1 = complication }
+
+    fun logRemoveComplication(complication: String) =
+        d({ "Remove dream complication: $str1" }) { str1 = complication }
+
+    fun logOverlayActive(active: Boolean) = d({ "Dream overlay active: $bool1" }) { bool1 = active }
+
+    fun logLowLightActive(active: Boolean) =
+        d({ "Low light mode active: $bool1" }) { bool1 = active }
+
+    fun logHasAssistantAttention(hasAttention: Boolean) =
+        d({ "Dream overlay has Assistant attention: $bool1" }) { bool1 = hasAttention }
+
+    fun logStatusBarVisible(visible: Boolean) =
+        d({ "Dream overlay status bar visible: $bool1" }) { bool1 = visible }
+
+    fun logAvailableComplicationTypes(types: Int) =
+        d({ "Available complication types: $int1" }) { int1 = types }
+
+    fun logShouldShowComplications(showComplications: Boolean) =
+        d({ "Dream overlay should show complications: $bool1" }) { bool1 = showComplications }
+
+    fun logShowOrHideStatusBarItem(show: Boolean, type: String) =
+        d({ "${if (bool1) "Showing" else "Hiding"} dream status bar item: $int1" }) {
+            bool1 = show
+            str1 = type
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index ee046c2..01fb522 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -26,6 +26,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
+import com.android.dream.lowlight.util.TruncatedInterpolator
 import com.android.systemui.R
 import com.android.systemui.complication.ComplicationHostViewController
 import com.android.systemui.complication.ComplicationLayoutParams
@@ -35,6 +36,9 @@
 import com.android.systemui.dreams.dagger.DreamOverlayModule
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.DreamLog
 import com.android.systemui.statusbar.BlurUtils
 import com.android.systemui.statusbar.CrossFadeHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -64,12 +68,14 @@
     private val mDreamInTranslationYDistance: Int,
     @Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DURATION)
     private val mDreamInTranslationYDurationMs: Long,
-    private val mLogger: DreamLogger,
+    @DreamLog logBuffer: LogBuffer,
 ) {
     companion object {
         private const val TAG = "DreamOverlayAnimationsController"
     }
 
+    private val logger = Logger(logBuffer, TAG)
+
     private var mAnimator: Animator? = null
     private lateinit var view: View
 
@@ -178,11 +184,11 @@
                 doOnEnd {
                     mAnimator = null
                     mOverlayStateController.setEntryAnimationsFinished(true)
-                    mLogger.d(TAG, "Dream overlay entry animations finished.")
+                    logger.d("Dream overlay entry animations finished.")
                 }
-                doOnCancel { mLogger.d(TAG, "Dream overlay entry animations canceled.") }
+                doOnCancel { logger.d("Dream overlay entry animations canceled.") }
                 start()
-                mLogger.d(TAG, "Dream overlay entry animations started.")
+                logger.d("Dream overlay entry animations started.")
             }
     }
 
@@ -204,31 +210,28 @@
                     translationYAnimator(
                         from = 0f,
                         to = -mDreamInTranslationYDistance.toFloat(),
-                        durationMs = mDreamInTranslationYDurationMs,
+                        durationMs = mDreamInComplicationsAnimDurationMs,
                         delayMs = 0,
-                        interpolator = Interpolators.EMPHASIZED
+                        // Truncate the animation from the full duration to match the alpha
+                        // animation so that the whole animation ends at the same time.
+                        interpolator =
+                            TruncatedInterpolator(
+                                Interpolators.EMPHASIZED,
+                                /*originalDuration=*/ mDreamInTranslationYDurationMs.toFloat(),
+                                /*newDuration=*/ mDreamInComplicationsAnimDurationMs.toFloat()
+                            )
                     ),
                     alphaAnimator(
-                            from =
-                                mCurrentAlphaAtPosition.getOrDefault(
-                                    key = POSITION_BOTTOM,
-                                    defaultValue = 1f
-                                ),
-                            to = 0f,
-                            durationMs = mDreamInComplicationsAnimDurationMs,
-                            delayMs = 0,
-                            positions = POSITION_BOTTOM
-                        )
-                        .apply {
-                            doOnEnd {
-                                // The logical end of the animation is once the alpha and blur
-                                // animations finish, end the animation so that any listeners are
-                                // notified. The Y translation animation is much longer than all of
-                                // the other animations due to how the spec is defined, but is not
-                                // expected to run to completion.
-                                mAnimator?.end()
-                            }
-                        },
+                        from =
+                            mCurrentAlphaAtPosition.getOrDefault(
+                                key = POSITION_BOTTOM,
+                                defaultValue = 1f
+                            ),
+                        to = 0f,
+                        durationMs = mDreamInComplicationsAnimDurationMs,
+                        delayMs = 0,
+                        positions = POSITION_BOTTOM
+                    ),
                     alphaAnimator(
                         from =
                             mCurrentAlphaAtPosition.getOrDefault(
@@ -244,11 +247,11 @@
                 doOnEnd {
                     mAnimator = null
                     mOverlayStateController.setExitAnimationsRunning(false)
-                    mLogger.d(TAG, "Dream overlay exit animations finished.")
+                    logger.d("Dream overlay exit animations finished.")
                 }
-                doOnCancel { mLogger.d(TAG, "Dream overlay exit animations canceled.") }
+                doOnCancel { logger.d("Dream overlay exit animations canceled.") }
                 start()
-                mLogger.d(TAG, "Dream overlay exit animations started.")
+                logger.d("Dream overlay exit animations started.")
             }
         mOverlayStateController.setExitAnimationsRunning(true)
         return mAnimator as AnimatorSet
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index ff07bb6..78ac453 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -39,8 +39,8 @@
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayModule;
 import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.util.ViewController;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt
index 8325356..003d2c7 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt
@@ -28,7 +28,8 @@
 class DreamOverlayLifecycleOwner @Inject constructor() : LifecycleOwner {
     val registry: LifecycleRegistry = LifecycleRegistry(this)
 
-    override fun getLifecycle(): Lifecycle {
-        return registry
-    }
+    override val lifecycle: Lifecycle
+        get() {
+            return registry
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index d509015..5577cbc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -17,6 +17,7 @@
 package com.android.systemui.dreams;
 
 import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_WINDOW_TITLE;
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_TOUCH_INSET_MANAGER;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -161,7 +162,7 @@
             DreamOverlayStateController stateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             UiEventLogger uiEventLogger,
-            TouchInsetManager touchInsetManager,
+            @Named(DREAM_TOUCH_INSET_MANAGER) TouchInsetManager touchInsetManager,
             @Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
                     ComponentName lowLightDreamComponent,
             DreamOverlayCallbackController dreamOverlayCallbackController,
@@ -294,6 +295,7 @@
 
         mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
         mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
+        mWindow.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
         mWindow.requestFeature(Window.FEATURE_NO_TITLE);
         // Hide all insets when the dream is showing
         mWindow.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index b141db1..c9748f9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -19,7 +19,6 @@
 import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_ENABLED;
 
 import android.service.dreams.DreamService;
-import android.util.Log;
 
 import androidx.annotation.NonNull;
 
@@ -29,6 +28,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.dagger.DreamLog;
 import com.android.systemui.statusbar.policy.CallbackController;
 
 import java.util.ArrayList;
@@ -52,7 +53,6 @@
 public class DreamOverlayStateController implements
         CallbackController<DreamOverlayStateController.Callback> {
     private static final String TAG = "DreamOverlayStateCtlr";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
     public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
@@ -110,13 +110,17 @@
 
     private final int mSupportedTypes;
 
+    private final DreamLogger mLogger;
+
     @VisibleForTesting
     @Inject
     public DreamOverlayStateController(@Main Executor executor,
             @Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            @DreamLog LogBuffer logBuffer) {
         mExecutor = executor;
         mOverlayEnabled = overlayEnabled;
+        mLogger = new DreamLogger(logBuffer, TAG);
         mFeatureFlags = featureFlags;
         if (mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)) {
             mSupportedTypes = Complication.COMPLICATION_TYPE_NONE
@@ -124,9 +128,7 @@
         } else {
             mSupportedTypes = Complication.COMPLICATION_TYPE_NONE;
         }
-        if (DEBUG) {
-            Log.d(TAG, "Dream overlay enabled:" + mOverlayEnabled);
-        }
+        mLogger.logDreamOverlayEnabled(mOverlayEnabled);
     }
 
     /**
@@ -134,18 +136,13 @@
      */
     public void addComplication(Complication complication) {
         if (!mOverlayEnabled) {
-            if (DEBUG) {
-                Log.d(TAG,
-                        "Ignoring adding complication due to overlay disabled:" + complication);
-            }
+            mLogger.logIgnoreAddComplication("overlay disabled", complication.toString());
             return;
         }
 
         mExecutor.execute(() -> {
             if (mComplications.add(complication)) {
-                if (DEBUG) {
-                    Log.d(TAG, "addComplication: added " + complication);
-                }
+                mLogger.logAddComplication(complication.toString());
                 mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
             }
         });
@@ -156,18 +153,13 @@
      */
     public void removeComplication(Complication complication) {
         if (!mOverlayEnabled) {
-            if (DEBUG) {
-                Log.d(TAG,
-                        "Ignoring removing complication due to overlay disabled:" + complication);
-            }
+            mLogger.logIgnoreRemoveComplication("overlay disabled", complication.toString());
             return;
         }
 
         mExecutor.execute(() -> {
             if (mComplications.remove(complication)) {
-                if (DEBUG) {
-                    Log.d(TAG, "removeComplication: removed " + complication);
-                }
+                mLogger.logRemoveComplication(complication.toString());
                 mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
             }
         });
@@ -313,6 +305,7 @@
      * @param active {@code true} if overlay is active, {@code false} otherwise.
      */
     public void setOverlayActive(boolean active) {
+        mLogger.logOverlayActive(active);
         modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE);
     }
 
@@ -321,6 +314,8 @@
      * @param active {@code true} if low light mode is active, {@code false} otherwise.
      */
     public void setLowLightActive(boolean active) {
+        mLogger.logLowLightActive(active);
+
         if (isLowLightActive() && !active) {
             // Notify that we're exiting low light only on the transition from active to not active.
             mCallbacks.forEach(Callback::onExitLowLight);
@@ -351,6 +346,7 @@
      * @param hasAttention {@code true} if has the user's attention, {@code false} otherwise.
      */
     public void setHasAssistantAttention(boolean hasAttention) {
+        mLogger.logHasAssistantAttention(hasAttention);
         modifyState(hasAttention ? OP_SET_STATE : OP_CLEAR_STATE, STATE_HAS_ASSISTANT_ATTENTION);
     }
 
@@ -359,6 +355,7 @@
      * @param visible {@code true} if the status bar is visible, {@code false} otherwise.
      */
     public void setDreamOverlayStatusBarVisible(boolean visible) {
+        mLogger.logStatusBarVisible(visible);
         modifyState(
                 visible ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE);
     }
@@ -376,6 +373,7 @@
      */
     public void setAvailableComplicationTypes(@Complication.ComplicationType int types) {
         mExecutor.execute(() -> {
+            mLogger.logAvailableComplicationTypes(types);
             mAvailableComplicationTypes = types;
             mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
         });
@@ -393,6 +391,7 @@
      */
     public void setShouldShowComplications(boolean shouldShowComplications) {
         mExecutor.execute(() -> {
+            mLogger.logShouldShowComplications(shouldShowComplications);
             mShouldShowComplications = shouldShowComplications;
             mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
         });
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index 7c1bfed..1865c38 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -141,6 +141,20 @@
         mExtraSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items);
     }
 
+    protected static String getLoggableStatusIconType(@StatusIconType int type) {
+        return switch (type) {
+            case STATUS_ICON_NOTIFICATIONS -> "notifications";
+            case STATUS_ICON_WIFI_UNAVAILABLE -> "wifi_unavailable";
+            case STATUS_ICON_ALARM_SET -> "alarm_set";
+            case STATUS_ICON_CAMERA_DISABLED -> "camera_disabled";
+            case STATUS_ICON_MIC_DISABLED -> "mic_disabled";
+            case STATUS_ICON_MIC_CAMERA_DISABLED -> "mic_camera_disabled";
+            case STATUS_ICON_PRIORITY_MODE_ON -> "priority_mode_on";
+            case STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE -> "assistant_attention_active";
+            default -> type + "(unknown)";
+        };
+    }
+
     void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) {
         View icon = mStatusIcons.get(iconType);
         if (icon == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index c954f98..a6401b6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -36,6 +36,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.dagger.DreamLog;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
@@ -61,6 +63,8 @@
  */
 @DreamOverlayComponent.DreamOverlayScope
 public class DreamOverlayStatusBarViewController extends ViewController<DreamOverlayStatusBarView> {
+    private static final String TAG = "DreamStatusBarCtrl";
+
     private final ConnectivityManager mConnectivityManager;
     private final TouchInsetManager.TouchInsetSession mTouchInsetSession;
     private final NextAlarmController mNextAlarmController;
@@ -78,6 +82,7 @@
     private final Executor mMainExecutor;
     private final List<DreamOverlayStatusBarItemsProvider.StatusBarItem> mExtraStatusBarItems =
             new ArrayList<>();
+    private final DreamLogger mLogger;
 
     private boolean mIsAttached;
 
@@ -157,7 +162,8 @@
             StatusBarWindowStateController statusBarWindowStateController,
             DreamOverlayStatusBarItemsProvider statusBarItemsProvider,
             DreamOverlayStateController dreamOverlayStateController,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            @DreamLog LogBuffer logBuffer) {
         super(view);
         mResources = resources;
         mMainExecutor = mainExecutor;
@@ -173,6 +179,7 @@
         mZenModeController = zenModeController;
         mDreamOverlayStateController = dreamOverlayStateController;
         mUserTracker = userTracker;
+        mLogger = new DreamLogger(logBuffer, TAG);
 
         // Register to receive show/hide updates for the system status bar. Our custom status bar
         // needs to hide when the system status bar is showing to ovoid overlapping status bars.
@@ -341,6 +348,8 @@
             @Nullable String contentDescription) {
         mMainExecutor.execute(() -> {
             if (mIsAttached) {
+                mLogger.logShowOrHideStatusBarItem(
+                        show, DreamOverlayStatusBarView.getLoggableStatusIconType(iconType));
                 mView.showIcon(iconType, show, contentDescription);
             }
         });
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
index c889ac2..4dd97d5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/AssistantAttentionCondition.java
@@ -16,10 +16,9 @@
 
 package com.android.systemui.dreams.conditions;
 
-import com.android.internal.app.AssistUtils;
-import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.assist.AssistManager.VisualQueryAttentionListener;
 import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.shared.condition.Condition;
 
 import javax.inject.Inject;
@@ -30,12 +29,10 @@
  * {@link AssistantAttentionCondition} provides a signal when assistant has the user's attention.
  */
 public class AssistantAttentionCondition extends Condition {
-    private final DreamOverlayStateController mDreamOverlayStateController;
-    private final AssistUtils mAssistUtils;
-    private boolean mEnabled;
+    private final AssistManager mAssistManager;
 
-    private final IVisualQueryDetectionAttentionListener mVisualQueryDetectionAttentionListener =
-            new IVisualQueryDetectionAttentionListener.Stub() {
+    private final VisualQueryAttentionListener mVisualQueryAttentionListener =
+            new VisualQueryAttentionListener() {
         @Override
         public void onAttentionGained() {
             updateCondition(true);
@@ -47,59 +44,26 @@
         }
     };
 
-    private final DreamOverlayStateController.Callback mCallback =
-            new DreamOverlayStateController.Callback() {
-        @Override
-        public void onStateChanged() {
-            if (mDreamOverlayStateController.isDreamOverlayStatusBarVisible()) {
-                enableVisualQueryDetection();
-            } else {
-                disableVisualQueryDetection();
-            }
-        }
-    };
-
     @Inject
     public AssistantAttentionCondition(
             @Application CoroutineScope scope,
-            DreamOverlayStateController dreamOverlayStateController,
-            AssistUtils assistUtils) {
+            AssistManager assistManager) {
         super(scope);
-        mDreamOverlayStateController = dreamOverlayStateController;
-        mAssistUtils = assistUtils;
+        mAssistManager = assistManager;
     }
 
     @Override
     protected void start() {
-        mDreamOverlayStateController.addCallback(mCallback);
+        mAssistManager.addVisualQueryAttentionListener(mVisualQueryAttentionListener);
     }
 
     @Override
     protected void stop() {
-        disableVisualQueryDetection();
-        mDreamOverlayStateController.removeCallback(mCallback);
+        mAssistManager.removeVisualQueryAttentionListener(mVisualQueryAttentionListener);
     }
 
     @Override
     protected int getStartStrategy() {
         return START_EAGERLY;
     }
-
-    private void enableVisualQueryDetection() {
-        if (mEnabled) {
-            return;
-        }
-        mEnabled = true;
-        mAssistUtils.enableVisualQueryDetection(mVisualQueryDetectionAttentionListener);
-    }
-
-    private void disableVisualQueryDetection() {
-        if (!mEnabled) {
-            return;
-        }
-        mEnabled = false;
-        mAssistUtils.disableVisualQueryDetection();
-        // Make sure the condition is set to false as well.
-        updateCondition(false);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index c61b4775..4bafe32 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -31,11 +31,13 @@
 import com.android.systemui.dreams.DreamOverlayService;
 import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
 import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule;
+import com.android.systemui.touch.TouchInsetManager;
 
 import dagger.Module;
 import dagger.Provides;
 
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import javax.inject.Named;
 
@@ -55,7 +57,7 @@
     String DREAM_ONLY_ENABLED_FOR_DOCK_USER = "dream_only_enabled_for_dock_user";
     String DREAM_OVERLAY_SERVICE_COMPONENT = "dream_overlay_service_component";
     String DREAM_OVERLAY_ENABLED = "dream_overlay_enabled";
-
+    String DREAM_TOUCH_INSET_MANAGER = "dream_touch_inset_manager";
     String DREAM_SUPPORTED = "dream_supported";
     String DREAM_OVERLAY_WINDOW_TITLE = "dream_overlay_window_title";
 
@@ -69,6 +71,15 @@
     }
 
     /**
+     * Provides a touch inset manager for dreams.
+     */
+    @Provides
+    @Named(DREAM_TOUCH_INSET_MANAGER)
+    static TouchInsetManager providesTouchInsetManager(@Main Executor executor) {
+        return new TouchInsetManager(executor);
+    }
+
+    /**
      * Provides whether dream overlay is enabled.
      */
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 570132e1..1cd3774 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -35,9 +35,11 @@
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dreams.touch.scrim.ScrimController;
 import com.android.systemui.dreams.touch.scrim.ScrimManager;
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -76,6 +78,8 @@
 
     private static final String TAG = "BouncerSwipeTouchHandler";
     private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final LockPatternUtils mLockPatternUtils;
+    private final UserTracker mUserTracker;
     private final float mBouncerZoneScreenPercentage;
 
     private final ScrimManager mScrimManager;
@@ -151,6 +155,11 @@
                         return true;
                     }
 
+                    // Don't set expansion if the user doesn't have a pin/password set.
+                    if (!mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
+                        return true;
+                    }
+
                     // For consistency, we adopt the expansion definition found in the
                     // PanelViewController. In this case, expansion refers to the view above the
                     // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
@@ -204,6 +213,8 @@
             NotificationShadeWindowController notificationShadeWindowController,
             ValueAnimatorCreator valueAnimatorCreator,
             VelocityTrackerFactory velocityTrackerFactory,
+            LockPatternUtils lockPatternUtils,
+            UserTracker userTracker,
             @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
                     FlingAnimationUtils flingAnimationUtils,
             @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
@@ -213,6 +224,8 @@
         mCentralSurfaces = centralSurfaces;
         mScrimManager = scrimManager;
         mNotificationShadeWindowController = notificationShadeWindowController;
+        mLockPatternUtils = lockPatternUtils;
+        mUserTracker = userTracker;
         mBouncerZoneScreenPercentage = swipeRegionPercentage;
         mFlingAnimationUtils = flingAnimationUtils;
         mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
@@ -347,6 +360,11 @@
             return;
         }
 
+        // Don't set expansion if the user doesn't have a pin/password set.
+        if (!mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
+            return;
+        }
+
         // The animation utils deal in pixel units, rather than expansion height.
         final float viewHeight = mTouchSession.getBounds().height();
         final float currentHeight = viewHeight * mCurrentExpansion;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
index 99451f2..6f05e83 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
@@ -37,12 +37,15 @@
  */
 public class ShadeTouchHandler implements DreamTouchHandler {
     private final Optional<CentralSurfaces> mSurfaces;
+    private final ShadeViewController mShadeViewController;
     private final int mInitiationHeight;
 
     @Inject
     ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces,
+            ShadeViewController shadeViewController,
             @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) {
         mSurfaces = centralSurfaces;
+        mShadeViewController = shadeViewController;
         mInitiationHeight = initiationHeight;
     }
 
@@ -54,12 +57,7 @@
         }
 
         session.registerInputListener(ev -> {
-            final ShadeViewController viewController =
-                    mSurfaces.map(CentralSurfaces::getShadeViewController).orElse(null);
-
-            if (viewController != null) {
-                viewController.handleExternalTouch((MotionEvent) ev);
-            }
+            mShadeViewController.handleExternalTouch((MotionEvent) ev);
 
             if (ev instanceof MotionEvent) {
                 if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index 4b03fd3..7150d69e 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -16,34 +16,37 @@
 
 package com.android.systemui.dump
 
-import android.content.Context
+import android.icu.text.SimpleDateFormat
 import android.os.SystemClock
 import android.os.Trace
-import com.android.systemui.CoreStartable
-import com.android.systemui.R
+import com.android.systemui.ProtoDumpable
 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL
 import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL
+import com.android.systemui.dump.DumpsysEntry.DumpableEntry
+import com.android.systemui.dump.DumpsysEntry.LogBufferEntry
+import com.android.systemui.dump.DumpsysEntry.TableLogBufferEntry
 import com.android.systemui.dump.nano.SystemUIProtoDump
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
+import com.android.systemui.log.table.TableLogBuffer
 import com.google.protobuf.nano.MessageNano
 import java.io.BufferedOutputStream
 import java.io.FileDescriptor
 import java.io.FileOutputStream
 import java.io.PrintWriter
+import java.util.Locale
 import javax.inject.Inject
-import javax.inject.Provider
+import kotlin.system.measureTimeMillis
 
 /**
  * Oversees SystemUI's output during bug reports (and dumpsys in general)
  *
  * Dump output is split into two sections, CRITICAL and NORMAL. In general, the CRITICAL section
  * contains all dumpables that were registered to the [DumpManager], while the NORMAL sections
- * contains all [LogBuffer]s (due to their length).
+ * contains all [LogBuffer]s and [TableLogBuffer]s (due to their length).
  *
- * The CRITICAL and NORMAL sections can be found within a bug report by searching for
- * "SERVICE com.android.systemui/.SystemUIService" and
- * "SERVICE com.android.systemui/.dump.SystemUIAuxiliaryDumpService", respectively.
+ * The CRITICAL and NORMAL sections can be found within a bug report by searching for "SERVICE
+ * com.android.systemui/.SystemUIService" and "SERVICE
+ * com.android.systemui/.dump.SystemUIAuxiliaryDumpService", respectively.
  *
  * Finally, some or all of the dump can be triggered on-demand via adb (see below).
  *
@@ -72,6 +75,8 @@
  * # To dump all dumpables or all buffers:
  * $ <invocation> dumpables
  * $ <invocation> buffers
+ * $ <invocation> tables
+ * $ <invocation> all
  *
  * # Finally, the following will simulate what we dump during the CRITICAL and NORMAL sections of a
  * # bug report:
@@ -83,38 +88,28 @@
  * $ <invocation> --help
  * ```
  */
-class DumpHandler @Inject constructor(
-    private val context: Context,
+class DumpHandler
+@Inject
+constructor(
     private val dumpManager: DumpManager,
     private val logBufferEulogizer: LogBufferEulogizer,
-    private val startables: MutableMap<Class<*>, Provider<CoreStartable>>,
-    private val uncaughtExceptionPreHandlerManager: UncaughtExceptionPreHandlerManager
+    private val config: SystemUIConfigDumpable,
 ) {
-    /**
-     * Registers an uncaught exception handler
-     */
-    fun init() {
-        uncaughtExceptionPreHandlerManager.registerHandler { _, e ->
-            if (e is Exception) {
-                logBufferEulogizer.record(e)
-            }
-        }
-    }
-
-    /**
-     * Dump the diagnostics! Behavior can be controlled via [args].
-     */
+    /** Dump the diagnostics! Behavior can be controlled via [args]. */
     fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
         Trace.beginSection("DumpManager#dump()")
         val start = SystemClock.uptimeMillis()
 
-        val parsedArgs = try {
-            parseArgs(args)
-        } catch (e: ArgParseException) {
-            pw.println(e.message)
-            return
-        }
+        val parsedArgs =
+            try {
+                parseArgs(args)
+            } catch (e: ArgParseException) {
+                pw.println(e.message)
+                return
+            }
 
+        pw.print("Dump starting: ")
+        pw.println(DATE_FORMAT.format(System.currentTimeMillis()))
         when {
             parsedArgs.dumpPriority == PRIORITY_ARG_CRITICAL -> dumpCritical(pw, parsedArgs)
             parsedArgs.dumpPriority == PRIORITY_ARG_NORMAL && !parsedArgs.proto -> {
@@ -134,6 +129,12 @@
             "bugreport-normal" -> dumpNormal(pw, args)
             "dumpables" -> dumpDumpables(pw, args)
             "buffers" -> dumpBuffers(pw, args)
+            "tables" -> dumpTables(pw, args)
+            "all" -> {
+                dumpDumpables(pw, args)
+                dumpBuffers(pw, args)
+                dumpTables(pw, args)
+            }
             "config" -> dumpConfig(pw)
             "help" -> dumpHelp(pw)
             else -> {
@@ -147,44 +148,64 @@
     }
 
     private fun dumpCritical(pw: PrintWriter, args: ParsedArgs) {
-        dumpManager.dumpCritical(pw, args.rawArgs)
-        dumpConfig(pw)
+        val targets = dumpManager.getDumpables()
+        for (target in targets) {
+            if (target.priority == DumpPriority.CRITICAL) {
+                dumpDumpable(target, pw, args.rawArgs)
+            }
+        }
     }
 
     private fun dumpNormal(pw: PrintWriter, args: ParsedArgs) {
-        dumpManager.dumpNormal(pw, args.rawArgs, args.tailLength)
+        val targets = dumpManager.getDumpables()
+        for (target in targets) {
+            if (target.priority == DumpPriority.NORMAL) {
+                dumpDumpable(target, pw, args.rawArgs)
+            }
+        }
+
+        val buffers = dumpManager.getLogBuffers()
+        for (buffer in buffers) {
+            dumpBuffer(buffer, pw, args.tailLength)
+        }
+
+        val tableBuffers = dumpManager.getTableLogBuffers()
+        for (table in tableBuffers) {
+            dumpTableBuffer(table, pw, args.rawArgs)
+        }
+
         logBufferEulogizer.readEulogyIfPresent(pw)
     }
 
-    private fun dumpDumpables(pw: PrintWriter, args: ParsedArgs) {
-        if (args.listOnly) {
-            dumpManager.listDumpables(pw)
-        } else {
-            dumpManager.dumpDumpables(pw, args.rawArgs)
+    private fun dumpDumpables(pw: PrintWriter, args: ParsedArgs) =
+        dumpManager.getDumpables().listOrDumpEntries(pw, args)
+
+    private fun dumpBuffers(pw: PrintWriter, args: ParsedArgs) =
+        dumpManager.getLogBuffers().listOrDumpEntries(pw, args)
+
+    private fun dumpTables(pw: PrintWriter, args: ParsedArgs) =
+        dumpManager.getTableLogBuffers().listOrDumpEntries(pw, args)
+
+    private fun listTargetNames(targets: Collection<DumpsysEntry>, pw: PrintWriter) {
+        for (target in targets) {
+            pw.println(target.name)
         }
     }
 
-    private fun dumpBuffers(pw: PrintWriter, args: ParsedArgs) {
-        if (args.listOnly) {
-            dumpManager.listBuffers(pw)
-        } else {
-            dumpManager.dumpBuffers(pw, args.tailLength)
-        }
-    }
-
-    private fun dumpProtoTargets(
-            targets: List<String>,
-            fd: FileDescriptor,
-            args: ParsedArgs
-    ) {
+    private fun dumpProtoTargets(targets: List<String>, fd: FileDescriptor, args: ParsedArgs) {
         val systemUIProto = SystemUIProtoDump()
+        val dumpables = dumpManager.getDumpables()
         if (targets.isNotEmpty()) {
             for (target in targets) {
-                dumpManager.dumpProtoTarget(target, systemUIProto, args.rawArgs)
+                findBestProtoTargetMatch(dumpables, target)?.dumpProto(systemUIProto, args.rawArgs)
             }
         } else {
-            dumpManager.dumpProtoDumpables(systemUIProto, args.rawArgs)
+            // Dump all protos
+            for (dumpable in dumpables) {
+                (dumpable.dumpable as? ProtoDumpable)?.dumpProto(systemUIProto, args.rawArgs)
+            }
         }
+
         val buffer = BufferedOutputStream(FileOutputStream(fd))
         buffer.use {
             it.write(MessageNano.toByteArray(systemUIProto))
@@ -192,62 +213,51 @@
         }
     }
 
-    private fun dumpTargets(
-        targets: List<String>,
-        pw: PrintWriter,
-        args: ParsedArgs
-    ) {
+    // Attempts to dump the target list to the given PrintWriter. Since the arguments come in as
+    // a list of strings, we use the [findBestTargetMatch] method to determine the most-correct
+    // target with the given search string.
+    private fun dumpTargets(targets: List<String>, pw: PrintWriter, args: ParsedArgs) {
         if (targets.isNotEmpty()) {
-            for (target in targets) {
-                dumpManager.dumpTarget(target, pw, args.rawArgs, args.tailLength)
+            val dumpables = dumpManager.getDumpables()
+            val buffers = dumpManager.getLogBuffers()
+            val tableBuffers = dumpManager.getTableLogBuffers()
+
+            targets.forEach { target ->
+                findTargetInCollection(target, dumpables, buffers, tableBuffers)?.dump(pw, args)
             }
         } else {
             if (args.listOnly) {
+                val dumpables = dumpManager.getDumpables()
+                val buffers = dumpManager.getLogBuffers()
+
                 pw.println("Dumpables:")
-                dumpManager.listDumpables(pw)
+                listTargetNames(dumpables, pw)
                 pw.println()
 
                 pw.println("Buffers:")
-                dumpManager.listBuffers(pw)
+                listTargetNames(buffers, pw)
             } else {
                 pw.println("Nothing to dump :(")
             }
         }
     }
 
+    private fun findTargetInCollection(
+        target: String,
+        dumpables: Collection<DumpableEntry>,
+        logBuffers: Collection<LogBufferEntry>,
+        tableBuffers: Collection<TableLogBufferEntry>,
+    ) =
+        sequence {
+                findBestTargetMatch(dumpables, target)?.let { yield(it) }
+                findBestTargetMatch(logBuffers, target)?.let { yield(it) }
+                findBestTargetMatch(tableBuffers, target)?.let { yield(it) }
+            }
+            .sortedBy { it.name }
+            .minByOrNull { it.name.length }
+
     private fun dumpConfig(pw: PrintWriter) {
-        pw.println("SystemUiServiceComponents configuration:")
-        pw.print("vendor component: ")
-        pw.println(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
-        val services: MutableList<String> = startables.keys
-                .map({ cls: Class<*> -> cls.simpleName })
-                .toMutableList()
-
-        services.add(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
-        dumpServiceList(pw, "global", services.toTypedArray())
-        dumpServiceList(pw, "per-user", R.array.config_systemUIServiceComponentsPerUser)
-    }
-
-    private fun dumpServiceList(pw: PrintWriter, type: String, resId: Int) {
-        val services: Array<String> = context.resources.getStringArray(resId)
-        dumpServiceList(pw, type, services)
-    }
-
-    private fun dumpServiceList(pw: PrintWriter, type: String, services: Array<String>?) {
-        pw.print(type)
-        pw.print(": ")
-        if (services == null) {
-            pw.println("N/A")
-            return
-        }
-        pw.print(services.size)
-        pw.println(" services")
-        for (i in services.indices) {
-            pw.print("  ")
-            pw.print(i)
-            pw.print(": ")
-            pw.println(services[i])
-        }
+        config.dump(pw, arrayOf())
     }
 
     private fun dumpHelp(pw: PrintWriter) {
@@ -265,6 +275,7 @@
         pw.println("Special commands:")
         pw.println("$ <invocation> dumpables")
         pw.println("$ <invocation> buffers")
+        pw.println("$ <invocation> tables")
         pw.println("$ <invocation> bugreport-critical")
         pw.println("$ <invocation> bugreport-normal")
         pw.println("$ <invocation> config")
@@ -274,6 +285,7 @@
         pw.println("$ <invocation> --list")
         pw.println("$ <invocation> dumpables --list")
         pw.println("$ <invocation> buffers --list")
+        pw.println("$ <invocation> tables --list")
         pw.println()
 
         pw.println("Show only the most recent N lines of buffers")
@@ -291,24 +303,26 @@
                 iterator.remove()
                 when (arg) {
                     PRIORITY_ARG -> {
-                        pArgs.dumpPriority = readArgument(iterator, PRIORITY_ARG) {
-                            if (PRIORITY_OPTIONS.contains(it)) {
-                                it
-                            } else {
-                                throw IllegalArgumentException()
+                        pArgs.dumpPriority =
+                            readArgument(iterator, PRIORITY_ARG) {
+                                if (PRIORITY_OPTIONS.contains(it)) {
+                                    it
+                                } else {
+                                    throw IllegalArgumentException()
+                                }
                             }
-                        }
                     }
                     PROTO -> pArgs.proto = true
-                    "-t", "--tail" -> {
-                        pArgs.tailLength = readArgument(iterator, arg) {
-                            it.toInt()
-                        }
+                    "-t",
+                    "--tail" -> {
+                        pArgs.tailLength = readArgument(iterator, arg) { it.toInt() }
                     }
-                    "-l", "--list" -> {
+                    "-l",
+                    "--list" -> {
                         pArgs.listOnly = true
                     }
-                    "-h", "--help" -> {
+                    "-h",
+                    "--help" -> {
                         pArgs.command = "help"
                     }
                     // This flag is passed as part of the proto dump in Bug reports, we can ignore
@@ -345,29 +359,156 @@
         }
     }
 
+    private fun DumpsysEntry.dump(pw: PrintWriter, args: ParsedArgs) =
+        when (this) {
+            is DumpableEntry -> dumpDumpable(this, pw, args.rawArgs)
+            is LogBufferEntry -> dumpBuffer(this, pw, args.tailLength)
+            is TableLogBufferEntry -> dumpTableBuffer(this, pw, args.rawArgs)
+        }
+
+    private fun Collection<DumpsysEntry>.listOrDumpEntries(pw: PrintWriter, args: ParsedArgs) =
+        if (args.listOnly) {
+            listTargetNames(this, pw)
+        } else {
+            forEach { it.dump(pw, args) }
+        }
+
     companion object {
         const val PRIORITY_ARG = "--dump-priority"
         const val PRIORITY_ARG_CRITICAL = "CRITICAL"
         const val PRIORITY_ARG_NORMAL = "NORMAL"
         const val PROTO = "--proto"
+
+        /**
+         * Important: do not change this divider without updating any bug report processing tools
+         * (e.g. ABT), since this divider is used to determine boundaries for bug report views
+         */
+        const val DUMPSYS_DUMPABLE_DIVIDER =
+            "----------------------------------------------------------------------------"
+
+        private fun findBestTargetMatch(c: Collection<DumpsysEntry>, target: String) =
+            c.asSequence().filter { it.name.endsWith(target) }.minByOrNull { it.name.length }
+
+        private fun findBestProtoTargetMatch(
+            c: Collection<DumpableEntry>,
+            target: String
+        ): ProtoDumpable? =
+            c.asSequence()
+                .filter { it.name.endsWith(target) }
+                .filter { it.dumpable is ProtoDumpable }
+                .minByOrNull { it.name.length }
+                ?.dumpable as? ProtoDumpable
+
+        private fun PrintWriter.preamble(entry: DumpsysEntry) =
+            when (entry) {
+                // Historically TableLogBuffer was not separate from dumpables, so they have the
+                // same header
+                is DumpableEntry,
+                is TableLogBufferEntry -> {
+                    println()
+                    println("${entry.name}:")
+                    println(DUMPSYS_DUMPABLE_DIVIDER)
+                }
+                is LogBufferEntry -> {
+                    println()
+                    println()
+                    println("BUFFER ${entry.name}:")
+                    println(DUMPSYS_DUMPABLE_DIVIDER)
+                }
+            }
+
+        private fun PrintWriter.footer(entry: DumpsysEntry, dumpTimeMillis: Long) {
+            if (entry !is DumpableEntry) return
+            println()
+            print(entry.priority)
+            print(" dump took ")
+            print(dumpTimeMillis)
+            print("ms -- ")
+            print(entry.name)
+            if (entry.priority == DumpPriority.CRITICAL && dumpTimeMillis > 25) {
+                print(" -- warning: individual dump time exceeds 5% of total CRITICAL dump time!")
+            }
+            println()
+        }
+
+        private inline fun PrintWriter.wrapSection(entry: DumpsysEntry, block: () -> Unit) {
+            Trace.beginSection(entry.name)
+            preamble(entry)
+            val dumpTime = measureTimeMillis(block)
+            footer(entry, dumpTime)
+            Trace.endSection()
+        }
+
+        /**
+         * Utility to write a [DumpableEntry] to the given [PrintWriter] in a
+         * dumpsys-appropriate format.
+         */
+        private fun dumpDumpable(
+                entry: DumpableEntry,
+                pw: PrintWriter,
+                args: Array<String> = arrayOf(),
+        ) = pw.wrapSection(entry) {
+            entry.dumpable.dump(pw, args)
+        }
+
+        /**
+         * Utility to write a [LogBufferEntry] to the given [PrintWriter] in a
+         * dumpsys-appropriate format.
+         */
+        private fun dumpBuffer(
+                entry: LogBufferEntry,
+                pw: PrintWriter,
+                tailLength: Int = 0,
+        ) = pw.wrapSection(entry) {
+            entry.buffer.dump(pw, tailLength)
+        }
+
+        /**
+         * Utility to write a [TableLogBufferEntry] to the given [PrintWriter] in a
+         * dumpsys-appropriate format.
+         */
+        private fun dumpTableBuffer(
+                entry: TableLogBufferEntry,
+                pw: PrintWriter,
+                args: Array<String> = arrayOf(),
+        ) = pw.wrapSection(entry) {
+            entry.table.dump(pw, args)
+        }
+
+        /**
+         * Zero-arg utility to write a [DumpsysEntry] to the given [PrintWriter] in a
+         * dumpsys-appropriate format.
+         */
+        fun DumpsysEntry.dump(pw: PrintWriter) {
+            when (this) {
+                is DumpableEntry -> dumpDumpable(this, pw)
+                is LogBufferEntry -> dumpBuffer(this, pw)
+                is TableLogBufferEntry -> dumpTableBuffer(this, pw)
+            }
+        }
+
+        /** Format [entries] in a dumpsys-appropriate way, using [pw] */
+        fun dumpEntries(entries: Collection<DumpsysEntry>, pw: PrintWriter) {
+            entries.forEach { it.dump(pw) }
+        }
     }
 }
 
+private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
 private val PRIORITY_OPTIONS = arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_NORMAL)
 
-private val COMMANDS = arrayOf(
+private val COMMANDS =
+    arrayOf(
         "bugreport-critical",
         "bugreport-normal",
         "buffers",
         "dumpables",
+        "tables",
         "config",
         "help"
-)
+    )
 
-private class ParsedArgs(
-    val rawArgs: Array<String>,
-    val nonFlagArgs: List<String>
-) {
+private class ParsedArgs(val rawArgs: Array<String>, val nonFlagArgs: List<String>) {
     var dumpPriority: String? = null
     var tailLength: Int = 0
     var command: String? = null
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index 2d57633..c924df6 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -18,9 +18,11 @@
 
 import com.android.systemui.Dumpable
 import com.android.systemui.ProtoDumpable
-import com.android.systemui.dump.nano.SystemUIProtoDump
+import com.android.systemui.dump.DumpsysEntry.DumpableEntry
+import com.android.systemui.dump.DumpsysEntry.LogBufferEntry
+import com.android.systemui.dump.DumpsysEntry.TableLogBufferEntry
 import com.android.systemui.log.LogBuffer
-import java.io.PrintWriter
+import com.android.systemui.log.table.TableLogBuffer
 import java.util.TreeMap
 import javax.inject.Inject
 import javax.inject.Singleton
@@ -37,8 +39,9 @@
 @Singleton
 open class DumpManager @Inject constructor() {
     // NOTE: Using TreeMap ensures that iteration is in a predictable & alphabetical order.
-    private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = TreeMap()
-    private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = TreeMap()
+    private val dumpables: MutableMap<String, DumpableEntry> = TreeMap()
+    private val buffers: MutableMap<String, LogBufferEntry> = TreeMap()
+    private val tableLogBuffers: MutableMap<String, TableLogBufferEntry> = TreeMap()
 
     /** See [registerCriticalDumpable]. */
     fun registerCriticalDumpable(module: Dumpable) {
@@ -77,14 +80,14 @@
      * Register a dumpable to be called during a bug report.
      *
      * @param name The name to register the dumpable under. This is typically the qualified class
-     * name of the thing being dumped (getClass().getName()), but can be anything as long as it
-     * doesn't clash with an existing registration.
+     *   name of the thing being dumped (getClass().getName()), but can be anything as long as it
+     *   doesn't clash with an existing registration.
      * @param priority the priority level of this dumpable, which affects at what point in the bug
-     * report this gets dump. By default, the dumpable will be called during the CRITICAL section of
-     * the bug report, so don't dump an excessive amount of stuff here.
+     *   report this gets dump. By default, the dumpable will be called during the CRITICAL section
+     *   of the bug report, so don't dump an excessive amount of stuff here.
      *
      * TODO(b/259973758): Replace all calls to this method with calls to [registerCriticalDumpable]
-     * or [registerNormalDumpable] instead.
+     *   or [registerNormalDumpable] instead.
      */
     @Synchronized
     @JvmOverloads
@@ -98,7 +101,7 @@
             throw IllegalArgumentException("'$name' is already registered")
         }
 
-        dumpables[name] = RegisteredDumpable(name, module, priority)
+        dumpables[name] = DumpableEntry(module, name, priority)
     }
 
     /**
@@ -110,217 +113,62 @@
         registerDumpable(module::class.java.simpleName, module)
     }
 
-    /**
-     * Unregisters a previously-registered dumpable.
-     */
+    /** Unregisters a previously-registered dumpable. */
     @Synchronized
     fun unregisterDumpable(name: String) {
         dumpables.remove(name)
     }
 
-    /**
-     * Register a [LogBuffer] to be dumped during a bug report.
-     */
+    /** Register a [LogBuffer] to be dumped during a bug report. */
     @Synchronized
     fun registerBuffer(name: String, buffer: LogBuffer) {
         if (!canAssignToNameLocked(name, buffer)) {
             throw IllegalArgumentException("'$name' is already registered")
         }
 
+        buffers[name] = LogBufferEntry(buffer, name)
+    }
+
+    /** Register a [TableLogBuffer] to be dumped during a bugreport */
+    @Synchronized
+    fun registerTableLogBuffer(name: String, buffer: TableLogBuffer) {
+        if (!canAssignToNameLocked(name, buffer)) {
+            throw IllegalArgumentException("'$name' is already registered")
+        }
+
         // All buffers must be priority NORMAL, not CRITICAL, because they often contain a lot of
         // data.
-        buffers[name] = RegisteredDumpable(name, buffer, DumpPriority.NORMAL)
+        tableLogBuffers[name] = TableLogBufferEntry(buffer, name)
     }
 
-    /**
-     * Dumps the alphabetically first, shortest-named dumpable or buffer whose registered name ends
-     * with [target].
-     */
-    @Synchronized
-    fun dumpTarget(
-        target: String,
-        pw: PrintWriter,
-        args: Array<String>,
-        tailLength: Int,
-    ) {
-        sequence {
-            findBestTargetMatch(dumpables, target)?.let {
-                yield(it.name to { dumpDumpable(it, pw, args) })
-            }
-            findBestTargetMatch(buffers, target)?.let {
-                yield(it.name to { dumpBuffer(it, pw, tailLength) })
-            }
-        }.sortedBy { it.first }.minByOrNull { it.first.length }?.second?.invoke()
-    }
+    @Synchronized fun getDumpables(): Collection<DumpableEntry> = dumpables.values.toList()
+
+    @Synchronized fun getLogBuffers(): Collection<LogBufferEntry> = buffers.values.toList()
 
     @Synchronized
-    fun dumpProtoTarget(
-        target: String,
-        protoDump: SystemUIProtoDump,
-        args: Array<String>
-    ) {
-        findBestProtoTargetMatch(dumpables, target)?.let {
-            dumpProtoDumpable(it, protoDump, args)
-        }
-    }
-
-    @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 with critical priority to [pw]
-     */
-    @Synchronized
-    fun dumpCritical(pw: PrintWriter, args: Array<String>) {
-        for (dumpable in dumpables.values) {
-            if (dumpable.priority == DumpPriority.CRITICAL) {
-                dumpDumpable(dumpable, pw, args)
-            }
-        }
-    }
-
-    /**
-     * To [pw], dumps (1) all registered dumpables with normal priority; and (2) all [LogBuffer]s.
-     */
-    @Synchronized
-    fun dumpNormal(pw: PrintWriter, args: Array<String>, tailLength: Int = 0) {
-        for (dumpable in dumpables.values) {
-            if (dumpable.priority == DumpPriority.NORMAL) {
-                dumpDumpable(dumpable, pw, args)
-            }
-        }
-
-        for (buffer in buffers.values) {
-            dumpBuffer(buffer, pw, tailLength)
-        }
-    }
-
-    /**
-     * Dump all the instances of [Dumpable].
-     */
-    @Synchronized
-    fun dumpDumpables(pw: PrintWriter, args: Array<String>) {
-        for (module in dumpables.values) {
-            dumpDumpable(module, pw, args)
-        }
-    }
-
-    /**
-     * Dumps the names of all registered dumpables (one per line)
-     */
-    @Synchronized
-    fun listDumpables(pw: PrintWriter) {
-        for (module in dumpables.values) {
-            pw.println(module.name)
-        }
-    }
-
-    /**
-     * Dumps all registered [LogBuffer]s to [pw]
-     */
-    @Synchronized
-    fun dumpBuffers(pw: PrintWriter, tailLength: Int) {
-        for (buffer in buffers.values) {
-            dumpBuffer(buffer, pw, tailLength)
-        }
-    }
-
-    /**
-     * Dumps the names of all registered buffers (one per line)
-     */
-    @Synchronized
-    fun listBuffers(pw: PrintWriter) {
-        for (buffer in buffers.values) {
-            pw.println(buffer.name)
-        }
-    }
+    fun getTableLogBuffers(): Collection<TableLogBufferEntry> = tableLogBuffers.values.toList()
 
     @Synchronized
     fun freezeBuffers() {
         for (buffer in buffers.values) {
-            buffer.dumpable.freeze()
+            buffer.buffer.freeze()
         }
     }
 
     @Synchronized
     fun unfreezeBuffers() {
         for (buffer in buffers.values) {
-            buffer.dumpable.unfreeze()
+            buffer.buffer.unfreeze()
         }
     }
 
-    private fun dumpDumpable(
-        dumpable: RegisteredDumpable<Dumpable>,
-        pw: PrintWriter,
-        args: Array<String>
-    ) {
-        pw.println()
-        pw.println("${dumpable.name}:")
-        pw.println("----------------------------------------------------------------------------")
-        dumpable.dumpable.dump(pw, args)
-    }
-
-    private fun dumpBuffer(
-        buffer: RegisteredDumpable<LogBuffer>,
-        pw: PrintWriter,
-        tailLength: Int
-    ) {
-        pw.println()
-        pw.println()
-        pw.println("BUFFER ${buffer.name}:")
-        pw.println("============================================================================")
-        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
+        val existingDumpable =
+            dumpables[name]?.dumpable ?: buffers[name]?.buffer ?: tableLogBuffers[name]?.table
         return existingDumpable == null || newDumpable == existingDumpable
     }
-
-    private fun <V : Any> findBestTargetMatch(map: Map<String, V>, target: String): V? = map
-        .asSequence()
-        .filter { it.key.endsWith(target) }
-        .minByOrNull { it.key.length }
-        ?.value
-
-    private fun findBestProtoTargetMatch(
-        map: Map<String, RegisteredDumpable<Dumpable>>,
-        target: String
-    ): ProtoDumpable? = map
-        .asSequence()
-        .filter { it.key.endsWith(target) }
-        .filter { it.value.dumpable is ProtoDumpable }
-        .minByOrNull { it.key.length }
-        ?.value?.dumpable as? ProtoDumpable
 }
 
-private data class RegisteredDumpable<T>(
-    val name: String,
-    val dumpable: T,
-    val priority: DumpPriority,
-)
-
 /**
  * The priority level for a given dumpable, which affects at what point in the bug report this gets
  * dumped.
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpsysEntry.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpsysEntry.kt
new file mode 100644
index 0000000..cd3e1bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpsysEntry.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dump
+
+import com.android.systemui.Dumpable
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.table.TableLogBuffer
+
+/**
+ * A DumpsysEntry is a named, registered entry tracked by [DumpManager] which can be addressed and
+ * used both in a bugreport / dumpsys invocation or in an individual CLI implementation.
+ *
+ * The idea here is that we define every type that [DumpManager] knows about and defines the minimum
+ * shared interface between each type. So far, just [name] and [priority]. This way, [DumpManager]
+ * can just store them in separate maps and do the minimal amount of work to discriminate between
+ * them.
+ *
+ * Individual consumers can request these participants in a list via the relevant get* methods on
+ * [DumpManager]
+ */
+sealed interface DumpsysEntry {
+    val name: String
+    val priority: DumpPriority
+
+    data class DumpableEntry(
+        val dumpable: Dumpable,
+        override val name: String,
+        override val priority: DumpPriority,
+    ) : DumpsysEntry
+
+    data class LogBufferEntry(
+        val buffer: LogBuffer,
+        override val name: String,
+    ) : DumpsysEntry {
+        // All buffers must be priority NORMAL, not CRITICAL, because they often contain a lot of
+        // data.
+        override val priority: DumpPriority = DumpPriority.NORMAL
+    }
+
+    data class TableLogBufferEntry(
+        val table: TableLogBuffer,
+        override val name: String,
+    ) : DumpsysEntry {
+        // All buffers must be priority NORMAL, not CRITICAL, because they often contain a lot of
+        // data.
+        override val priority: DumpPriority = DumpPriority.NORMAL
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
index f7e6b98..2e9d04b 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dump
 
+import android.os.Trace
 import java.io.PrintWriter
 
 /**
@@ -83,31 +84,33 @@
 ) {
 
     fun printTableData(pw: PrintWriter) {
+        Trace.beginSection("DumpsysTableLogger#printTableData")
         printSectionStart(pw)
         printSchema(pw)
         printData(pw)
         printSectionEnd(pw)
+        Trace.endSection()
     }
 
     private fun printSectionStart(pw: PrintWriter) {
-        pw.println(HEADER_PREFIX + sectionName)
-        pw.println("version $VERSION")
+        pw.append(HEADER_PREFIX).println(sectionName)
+        pw.append("version ").println(VERSION)
     }
 
     private fun printSectionEnd(pw: PrintWriter) {
-        pw.println(FOOTER_PREFIX + sectionName)
+        pw.append(FOOTER_PREFIX).println(sectionName)
     }
 
     private fun printSchema(pw: PrintWriter) {
-        pw.println(columns.joinToString(separator = SEPARATOR))
+        columns.joinTo(pw, separator = SEPARATOR).println()
     }
 
     private fun printData(pw: PrintWriter) {
         val count = columns.size
-        rows
-            .filter { it.size == count }
-            .forEach { dataLine ->
-                pw.println(dataLine.joinToString(separator = SEPARATOR))
+        rows.forEach { dataLine ->
+            if (dataLine.size == count) {
+                dataLine.joinTo(pw, separator = SEPARATOR).println()
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
index 2d5c9ae..bd43302 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
@@ -20,6 +20,7 @@
 import android.icu.text.SimpleDateFormat
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpHandler.Companion.dumpEntries
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.util.io.Files
 import com.android.systemui.util.time.SystemClock
@@ -48,20 +49,21 @@
     private val files: Files,
     private val logPath: Path,
     private val minWriteGap: Long,
-    private val maxLogAgeToDump: Long
+    private val maxLogAgeToDump: Long,
 ) {
-    @Inject constructor(
+    @Inject
+    constructor(
         context: Context,
         dumpManager: DumpManager,
         systemClock: SystemClock,
-        files: Files
+        files: Files,
     ) : this(
         dumpManager,
         systemClock,
         files,
         Paths.get(context.filesDir.toPath().toString(), "log_buffers.txt"),
         MIN_WRITE_GAP,
-        MAX_AGE_TO_DUMP
+        MAX_AGE_TO_DUMP,
     )
 
     /**
@@ -70,7 +72,7 @@
      * The file will be prefaced by the [reason], which will then be returned (presumably so it can
      * be thrown).
      */
-    fun <T : Exception> record(reason: T): T {
+    fun <T : Throwable> record(reason: T): T {
         val start = systemClock.uptimeMillis()
         var duration = 0L
 
@@ -91,7 +93,8 @@
                 pw.println()
                 pw.println("Dump triggered by exception:")
                 reason.printStackTrace(pw)
-                dumpManager.dumpBuffers(pw, 0)
+                val buffers = dumpManager.getLogBuffers()
+                dumpEntries(buffers, pw)
                 duration = systemClock.uptimeMillis() - start
                 pw.println()
                 pw.println("Buffer eulogy took ${duration}ms")
@@ -105,16 +108,17 @@
         return reason
     }
 
-    /**
-     * If a eulogy file is present, writes its contents to [pw].
-     */
+    /** If a eulogy file is present, writes its contents to [pw]. */
     fun readEulogyIfPresent(pw: PrintWriter) {
         try {
             val millisSinceLastWrite = getMillisSinceLastWrite(logPath)
             if (millisSinceLastWrite > maxLogAgeToDump) {
-                Log.i(TAG, "Not eulogizing buffers; they are " +
+                Log.i(
+                    TAG,
+                    "Not eulogizing buffers; they are " +
                         TimeUnit.HOURS.convert(millisSinceLastWrite, TimeUnit.MILLISECONDS) +
-                        " hours old")
+                        " hours old"
+                )
                 return
             }
 
@@ -122,9 +126,7 @@
                 pw.println()
                 pw.println()
                 pw.println("=============== BUFFERS FROM MOST RECENT CRASH ===============")
-                s.forEach { line ->
-                    pw.println(line)
-                }
+                s.forEach { line -> pw.println(line) }
             }
         } catch (e: IOException) {
             // File doesn't exist, okay
@@ -134,12 +136,13 @@
     }
 
     private fun getMillisSinceLastWrite(path: Path): Long {
-        val stats = try {
-            files.readAttributes(path, BasicFileAttributes::class.java)
-        } catch (e: IOException) {
-            // File doesn't exist
-            null
-        }
+        val stats =
+            try {
+                files.readAttributes(path, BasicFileAttributes::class.java)
+            } catch (e: IOException) {
+                // File doesn't exist
+                null
+            }
         return systemClock.currentTimeMillis() - (stats?.lastModifiedTime()?.toMillis() ?: 0)
     }
 }
@@ -147,4 +150,4 @@
 private const val TAG = "BufferEulogizer"
 private val MIN_WRITE_GAP = TimeUnit.MINUTES.toMillis(5)
 private val MAX_AGE_TO_DUMP = TimeUnit.HOURS.toMillis(48)
-private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
\ No newline at end of file
+private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
diff --git a/packages/SystemUI/src/com/android/systemui/dump/SystemUIConfigDumpable.kt b/packages/SystemUI/src/com/android/systemui/dump/SystemUIConfigDumpable.kt
new file mode 100644
index 0000000..b70edcc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dump/SystemUIConfigDumpable.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dump
+
+import android.content.Context
+import com.android.systemui.CoreStartable
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import java.io.PrintWriter
+import javax.inject.Inject
+import javax.inject.Provider
+
+@SysUISingleton
+class SystemUIConfigDumpable
+@Inject
+constructor(
+    dumpManager: DumpManager,
+    private val context: Context,
+    private val startables: MutableMap<Class<*>, Provider<CoreStartable>>,
+) : Dumpable {
+    init {
+        dumpManager.registerCriticalDumpable("SystemUiServiceComponents", this)
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("SystemUiServiceComponents configuration:")
+        pw.print("vendor component: ")
+        pw.println(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
+        val services: MutableList<String> =
+            startables.keys.map { cls: Class<*> -> cls.simpleName }.toMutableList()
+
+        services.add(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
+        dumpServiceList(pw, "global", services.toTypedArray())
+        dumpServiceList(pw, "per-user", R.array.config_systemUIServiceComponentsPerUser)
+    }
+
+    private fun dumpServiceList(pw: PrintWriter, type: String, resId: Int) {
+        val services: Array<String> = context.resources.getStringArray(resId)
+        dumpServiceList(pw, type, services)
+    }
+
+    private fun dumpServiceList(pw: PrintWriter, type: String, services: Array<String>?) {
+        pw.print(type)
+        pw.print(": ")
+        if (services == null) {
+            pw.println("N/A")
+            return
+        }
+        pw.print(services.size)
+        pw.println(" services")
+        for (i in services.indices) {
+            pw.print("  ")
+            pw.print(i)
+            pw.print(": ")
+            pw.println(services[i])
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 2c11d78..4c78e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -32,7 +32,6 @@
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.UserHandle;
-import android.provider.DeviceConfig;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -94,8 +93,7 @@
                             shouldRestart = true;
                         }
                     } else if (mStringFlagCache.containsKey(flag.getName())) {
-                        String newValue = value == null ? "" : value;
-                        if (mStringFlagCache.get(flag.getName()) != value) {
+                        if (!mStringFlagCache.get(flag.getName()).equals(value)) {
                             shouldRestart = true;
                         }
                     } else if (mIntFlagCache.containsKey(flag.getName())) {
@@ -203,8 +201,7 @@
         String name = flag.getName();
         if (!mStringFlagCache.containsKey(name)) {
             mStringFlagCache.put(name,
-                    readFlagValueInternal(
-                            flag.getId(), name, flag.getDefault(), StringFlagSerializer.INSTANCE));
+                    readFlagValueInternal(name, flag.getDefault(), StringFlagSerializer.INSTANCE));
         }
 
         return mStringFlagCache.get(name);
@@ -216,36 +213,30 @@
         String name = flag.getName();
         if (!mStringFlagCache.containsKey(name)) {
             mStringFlagCache.put(name,
-                    readFlagValueInternal(
-                            flag.getId(), name, mResources.getString(flag.getResourceId()),
+                    readFlagValueInternal(name, mResources.getString(flag.getResourceId()),
                             StringFlagSerializer.INSTANCE));
         }
 
         return mStringFlagCache.get(name);
     }
 
-
-    @NonNull
     @Override
     public int getInt(@NonNull IntFlag flag) {
         String name = flag.getName();
         if (!mIntFlagCache.containsKey(name)) {
             mIntFlagCache.put(name,
-                    readFlagValueInternal(
-                            flag.getId(), name, flag.getDefault(), IntFlagSerializer.INSTANCE));
+                    readFlagValueInternal(name, flag.getDefault(), IntFlagSerializer.INSTANCE));
         }
 
         return mIntFlagCache.get(name);
     }
 
-    @NonNull
     @Override
     public int getInt(@NonNull ResourceIntFlag flag) {
         String name = flag.getName();
         if (!mIntFlagCache.containsKey(name)) {
             mIntFlagCache.put(name,
-                    readFlagValueInternal(
-                            flag.getId(), name, mResources.getInteger(flag.getResourceId()),
+                    readFlagValueInternal(name, mResources.getInteger(flag.getResourceId()),
                             IntFlagSerializer.INSTANCE));
         }
 
@@ -255,13 +246,6 @@
     /** Specific override for Boolean flags that checks against the teamfood list.*/
     private boolean readBooleanFlagInternal(Flag<Boolean> flag, boolean defaultValue) {
         Boolean result = readBooleanFlagOverride(flag.getName());
-        if (result == null) {
-            result = readBooleanFlagOverride(flag.getId());
-            if (result != null) {
-                // Move overrides from id to name
-                setFlagValueInternal(flag.getName(), result, BooleanFlagSerializer.INSTANCE);
-            }
-        }
         boolean hasServerOverride = mServerFlagReader.hasOverride(
                 flag.getNamespace(), flag.getName());
 
@@ -279,45 +263,22 @@
                 flag.getNamespace(), flag.getName(), defaultValue) : result;
     }
 
-    private Boolean readBooleanFlagOverride(int id) {
-        return readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE);
-    }
 
     private Boolean readBooleanFlagOverride(String name) {
         return readFlagValueInternal(name, BooleanFlagSerializer.INSTANCE);
     }
 
-    // TODO(b/265188950): Remove id from this method once ids are fully deprecated.
     @NonNull
     private <T> T readFlagValueInternal(
-            int id, String name, @NonNull T defaultValue, FlagSerializer<T> serializer) {
+            String name, @NonNull T defaultValue, FlagSerializer<T> serializer) {
         requireNonNull(defaultValue, "defaultValue");
         T resultForName = readFlagValueInternal(name, serializer);
         if (resultForName == null) {
-            T resultForId = readFlagValueInternal(id, serializer);
-            if (resultForId == null) {
-                return defaultValue;
-            } else {
-                setFlagValue(name, resultForId, serializer);
-                return resultForId;
-            }
+            return defaultValue;
         }
         return resultForName;
     }
 
-
-    /** Returns the stored value or null if not set. */
-    // TODO(b/265188950): Remove method this once ids are fully deprecated.
-    @Nullable
-    private <T> T readFlagValueInternal(int id, FlagSerializer<T> serializer) {
-        try {
-            return mFlagManager.readFlagValue(id, serializer);
-        } catch (Exception e) {
-            eraseInternal(id);
-        }
-        return null;
-    }
-
     /** Returns the stored value or null if not set. */
     @Nullable
     private <T> T readFlagValueInternal(String name, FlagSerializer<T> serializer) {
@@ -385,15 +346,6 @@
     }
 
     /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */
-    // TODO(b/265188950): Remove method this once ids are fully deprecated.
-    private void eraseInternal(int id) {
-        // We can't actually "erase" things from settings, but we can set them to empty!
-        mGlobalSettings.putStringForUser(mFlagManager.idToSettingsKey(id), "",
-                UserHandle.USER_CURRENT);
-        Log.i(TAG, "Erase name " + id);
-    }
-
-    /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */
     private void eraseInternal(String name) {
         // We can't actually "erase" things from settings, but we can set them to empty!
         mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), "",
@@ -434,7 +386,7 @@
             setFlagValue(flag.getName(), value, BooleanFlagSerializer.INSTANCE);
         } else if (flag instanceof SysPropBooleanFlag) {
             // Store SysProp flags in SystemProperties where they can read by outside parties.
-            mSystemProperties.setBoolean(((SysPropBooleanFlag) flag).getName(), value);
+            mSystemProperties.setBoolean(flag.getName(), value);
             dispatchListenersAndMaybeRestart(
                     flag.getName(),
                     suppressRestart -> restartSystemUI(
@@ -527,7 +479,7 @@
                 }
             } catch (IllegalArgumentException e) {
                 Log.w(TAG,
-                        "Unable to set " + flag.getId() + " of type " + flag.getClass()
+                        "Unable to set " + flag.getName() + " of type " + flag.getClass()
                                 + " to value of type " + (value == null ? null : value.getClass()));
             }
         }
@@ -545,21 +497,18 @@
 
             if (f instanceof ReleasedFlag) {
                 enabled = isEnabled((ReleasedFlag) f);
-                overridden = readBooleanFlagOverride(f.getName()) != null
-                            || readBooleanFlagOverride(f.getId()) != null;
+                overridden = readBooleanFlagOverride(f.getName()) != null;
             } else if (f instanceof UnreleasedFlag) {
                 enabled = isEnabled((UnreleasedFlag) f);
-                overridden = readBooleanFlagOverride(f.getName()) != null
-                            || readBooleanFlagOverride(f.getId()) != null;
+                overridden = readBooleanFlagOverride(f.getName()) != null;
             } else if (f instanceof ResourceBooleanFlag) {
                 enabled = isEnabled((ResourceBooleanFlag) f);
-                overridden = readBooleanFlagOverride(f.getName()) != null
-                            || readBooleanFlagOverride(f.getId()) != null;
+                overridden = readBooleanFlagOverride(f.getName()) != null;
             } else if (f instanceof SysPropBooleanFlag) {
                 // TODO(b/223379190): Teamfood not supported for sysprop flags yet.
                 enabled = isEnabled((SysPropBooleanFlag) f);
                 teamfood = false;
-                overridden = !mSystemProperties.get(((SysPropBooleanFlag) f).getName()).isEmpty();
+                overridden = !mSystemProperties.get(f.getName()).isEmpty();
             } else {
                 // TODO: add support for other flag types.
                 Log.w(TAG, "Unsupported Flag Type. Please file a bug.");
@@ -567,11 +516,9 @@
             }
 
             if (enabled) {
-                return new ReleasedFlag(
-                        f.getId(), f.getName(), f.getNamespace(), teamfood, overridden);
+                return new ReleasedFlag(f.getName(), f.getNamespace(), teamfood, overridden);
             } else {
-                return new UnreleasedFlag(
-                        f.getId(), f.getName(), f.getNamespace(), teamfood, overridden);
+                return new UnreleasedFlag(f.getName(), f.getNamespace(), teamfood, overridden);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 9d19a7d..e03b438 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -67,7 +67,7 @@
                         }
                     } else if (mStringCache.containsKey(flag.getName())) {
                         String newValue = value == null ? "" : value;
-                        if (mStringCache.get(flag.getName()) != newValue) {
+                        if (!mStringCache.get(flag.getName()).equals(newValue)) {
                             shouldRestart = true;
                         }
                     } else if (mIntCache.containsKey(flag.getName())) {
@@ -185,14 +185,12 @@
         return mStringCache.get(name);
     }
 
-    @NonNull
     @Override
     public int getInt(@NonNull IntFlag flag) {
         // Fill the cache.
         return getIntInternal(flag.getName(), flag.getDefault());
     }
 
-    @NonNull
     @Override
     public int getInt(@NonNull ResourceIntFlag flag) {
         // Fill the cache.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
index daf9429..bd0ed48 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -219,17 +219,6 @@
         return 0;
     }
 
-    private int flagNameToId(String flagName) {
-        Map<String, Flag<?>> flagFields = FlagsFactory.INSTANCE.getKnownFlags();
-        for (String fieldName : flagFields.keySet()) {
-            if (flagName.equals(fieldName)) {
-                return flagFields.get(fieldName).getId();
-            }
-        }
-
-        return 0;
-    }
-
     private void printKnownFlags(PrintWriter pw) {
         Map<String, Flag<?>> fields = FlagsFactory.INSTANCE.getKnownFlags();
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ee8fae5..93bdac2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -36,140 +36,134 @@
  * See [FeatureFlagsDebug] for instructions on flipping the flags via adb.
  */
 object Flags {
-    @JvmField val TEAMFOOD = unreleasedFlag(1, "teamfood")
+    @JvmField val TEAMFOOD = unreleasedFlag("teamfood")
 
     // 100 - notification
     // TODO(b/254512751): Tracking Bug
     val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
-        unreleasedFlag(103, "notification_pipeline_developer_logging")
+        unreleasedFlag("notification_pipeline_developer_logging")
 
     // TODO(b/254512732): Tracking Bug
-    @JvmField val NSSL_DEBUG_LINES = unreleasedFlag(105, "nssl_debug_lines")
+    @JvmField val NSSL_DEBUG_LINES = unreleasedFlag("nssl_debug_lines")
 
     // TODO(b/254512505): Tracking Bug
-    @JvmField val NSSL_DEBUG_REMOVE_ANIMATION = unreleasedFlag(106, "nssl_debug_remove_animation")
+    @JvmField val NSSL_DEBUG_REMOVE_ANIMATION = unreleasedFlag("nssl_debug_remove_animation")
 
     // TODO(b/254512624): Tracking Bug
     @JvmField
     val NOTIFICATION_DRAG_TO_CONTENTS =
         resourceBooleanFlag(
-            108,
             R.bool.config_notificationToContents,
             "notification_drag_to_contents"
         )
 
     // TODO(b/254512538): Tracking Bug
-    val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply")
-
-    // TODO(b/279735475): Tracking Bug
-    @JvmField
-    val NEW_LIGHT_BAR_LOGIC = releasedFlag(279735475, "new_light_bar_logic")
+    val INSTANT_VOICE_REPLY = unreleasedFlag("instant_voice_reply")
 
     /**
-     * This flag is server-controlled and should stay as [unreleasedFlag] since we never want to
-     * enable it on release builds.
+     * This flag controls whether we register a listener for StatsD notification memory reports.
+     * For statsd to actually call the listener however, a server-side toggle needs to be
+     * enabled as well.
      */
     val NOTIFICATION_MEMORY_LOGGING_ENABLED =
-        unreleasedFlag(119, "notification_memory_logging_enabled")
-
-    @JvmField
-    val SIMPLIFIED_APPEAR_FRACTION =
-        releasedFlag(259395680, "simplified_appear_fraction")
-
-    // TODO(b/257315550): Tracking Bug
-    val NO_HUN_FOR_OLD_WHEN = releasedFlag(118, "no_hun_for_old_when")
+            releasedFlag("notification_memory_logging_enabled")
 
     // TODO(b/260335638): Tracking Bug
     @JvmField
     val NOTIFICATION_INLINE_REPLY_ANIMATION =
-        unreleasedFlag(174148361, "notification_inline_reply_animation")
+        unreleasedFlag("notification_inline_reply_animation")
 
     /** Makes sure notification panel is updated before the user switch is complete. */
     // TODO(b/278873737): Tracking Bug
     @JvmField
     val LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE =
-            releasedFlag(278873737, "load_notifications_before_the_user_switch_is_complete")
+        releasedFlag("load_notifications_before_the_user_switch_is_complete")
 
     // TODO(b/277338665): Tracking Bug
     @JvmField
     val NOTIFICATION_SHELF_REFACTOR =
-        unreleasedFlag(271161129, "notification_shelf_refactor")
+        unreleasedFlag("notification_shelf_refactor", teamfood = true)
+
+    // TODO(b/290787599): Tracking Bug
+    @JvmField
+    val NOTIFICATION_ICON_CONTAINER_REFACTOR =
+        unreleasedFlag("notification_icon_container_refactor")
+
+    // TODO(b/288326013): Tracking Bug
+    @JvmField
+    val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION =
+        unreleasedFlag("notification_async_hybrid_view_inflation", teamfood = false)
 
     @JvmField
     val ANIMATED_NOTIFICATION_SHADE_INSETS =
-        releasedFlag(270682168, "animated_notification_shade_insets")
+        releasedFlag("animated_notification_shade_insets")
 
     // TODO(b/268005230): Tracking Bug
     @JvmField
-    val SENSITIVE_REVEAL_ANIM =
-        unreleasedFlag(268005230, "sensitive_reveal_anim", teamfood = true)
+    val SENSITIVE_REVEAL_ANIM = unreleasedFlag("sensitive_reveal_anim", teamfood = true)
 
     // TODO(b/280783617): Tracking Bug
     @Keep
     @JvmField
     val BUILDER_EXTRAS_OVERRIDE =
-            sysPropBooleanFlag(
-                    128,
-                    "persist.sysui.notification.builder_extras_override",
-                    default = false
-            )
+        sysPropBooleanFlag(
+            "persist.sysui.notification.builder_extras_override",
+            default = true
+        )
+
+    /** Only notify group expansion listeners when a change happens. */
+    // TODO(b/292213543): Tracking Bug
+    @JvmField
+    val NOTIFICATION_GROUP_EXPANSION_CHANGE =
+            unreleasedFlag("notification_group_expansion_change", teamfood = true)
 
     // 200 - keyguard/lockscreen
     // ** Flag retired **
     // public static final BooleanFlag KEYGUARD_LAYOUT =
-    //         new BooleanFlag(200, true);
+    //         new BooleanFlag(true);
 
     // TODO(b/254512750): Tracking Bug
-    val NEW_UNLOCK_SWIPE_ANIMATION = releasedFlag(202, "new_unlock_swipe_animation")
-    val CHARGING_RIPPLE = resourceBooleanFlag(203, R.bool.flag_charging_ripple, "charging_ripple")
+    val NEW_UNLOCK_SWIPE_ANIMATION = releasedFlag("new_unlock_swipe_animation")
+    val CHARGING_RIPPLE = resourceBooleanFlag(R.bool.flag_charging_ripple, "charging_ripple")
 
     // TODO(b/254512281): Tracking Bug
     @JvmField
     val BOUNCER_USER_SWITCHER =
-        resourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher, "bouncer_user_switcher")
+        resourceBooleanFlag(R.bool.config_enableBouncerUserSwitcher, "bouncer_user_switcher")
 
     // TODO(b/254512676): Tracking Bug
     @JvmField
-    val LOCKSCREEN_CUSTOM_CLOCKS = resourceBooleanFlag(
-        207,
-        R.bool.config_enableLockScreenCustomClocks,
-        "lockscreen_custom_clocks"
-    )
+    val LOCKSCREEN_CUSTOM_CLOCKS =
+        resourceBooleanFlag(
+            R.bool.config_enableLockScreenCustomClocks,
+            "lockscreen_custom_clocks"
+        )
 
     // TODO(b/275694445): Tracking Bug
     @JvmField
-    val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING = releasedFlag(208,
-        "lockscreen_without_secure_lock_when_dreaming")
+    val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING =
+        releasedFlag("lockscreen_without_secure_lock_when_dreaming")
+
+    // TODO(b/286092087): Tracking Bug
+    @JvmField
+    val ENABLE_SYSTEM_UI_DREAM_CONTROLLER = unreleasedFlag("enable_system_ui_dream_controller")
+
+    // TODO(b/288287730): Tracking Bug
+    @JvmField
+    val ENABLE_SYSTEM_UI_DREAM_HOSTING = unreleasedFlag("enable_system_ui_dream_hosting")
 
     /**
      * 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 = releasedFlag(212, "step_clock_animation")
+    @JvmField val STEP_CLOCK_ANIMATION = releasedFlag("step_clock_animation")
 
     /**
      * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository
      * will occur in stages. This is one stage of many to come.
      */
     // TODO(b/255607168): Tracking Bug
-    @JvmField val DOZING_MIGRATION_1 = unreleasedFlag(213, "dozing_migration_1")
-
-    // TODO(b/252897742): Tracking Bug
-    @JvmField val NEW_ELLIPSE_DETECTION = unreleasedFlag(214, "new_ellipse_detection")
-
-    // TODO(b/252897742): Tracking Bug
-    @JvmField val NEW_UDFPS_OVERLAY = unreleasedFlag(215, "new_udfps_overlay")
-
-    /**
-     * Whether to enable the code powering customizable lock screen quick affordances.
-     *
-     * This flag enables any new prebuilt quick affordances as well.
-     */
-    // TODO(b/255618149): Tracking Bug
-    @JvmField
-    val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
-        releasedFlag(216, "customizable_lock_screen_quick_affordances")
+    @JvmField val DOZING_MIGRATION_1 = unreleasedFlag("dozing_migration_1")
 
     /**
      * Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the
@@ -177,306 +171,316 @@
      */
     // TODO(b/281655028): Tracking bug
     @JvmField
-    val LIGHT_REVEAL_MIGRATION = unreleasedFlag(218, "light_reveal_migration", teamfood = false)
+    val LIGHT_REVEAL_MIGRATION = unreleasedFlag("light_reveal_migration", teamfood = false)
 
     /** Flag to control the migration of face auth to modern architecture. */
     // TODO(b/262838215): Tracking bug
-    @JvmField val FACE_AUTH_REFACTOR = unreleasedFlag(220, "face_auth_refactor")
+    @JvmField val FACE_AUTH_REFACTOR = unreleasedFlag("face_auth_refactor")
 
     /** Flag to control the revamp of keyguard biometrics progress animation */
     // TODO(b/244313043): Tracking bug
-    @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag(221, "biometrics_animation_revamp")
+    @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag("biometrics_animation_revamp")
 
     // TODO(b/262780002): Tracking Bug
-    @JvmField
-    val REVAMPED_WALLPAPER_UI = releasedFlag(222, "revamped_wallpaper_ui")
+    @JvmField val REVAMPED_WALLPAPER_UI = releasedFlag("revamped_wallpaper_ui")
 
     // flag for controlling auto pin confirmation and material u shapes in bouncer
     @JvmField
-    val AUTO_PIN_CONFIRMATION =
-        releasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation")
+    val AUTO_PIN_CONFIRMATION = releasedFlag("auto_pin_confirmation", "auto_pin_confirmation")
 
     // TODO(b/262859270): Tracking Bug
-    @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded")
+    @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag("falsing_off_for_unfolded")
 
     /** Enables code to show contextual loyalty cards in wallet entrypoints */
-    // TODO(b/247587924): Tracking Bug
+    // TODO(b/294110497): Tracking Bug
     @JvmField
     val ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS =
-        unreleasedFlag(226, "enable_wallet_contextual_loyalty_cards", teamfood = false)
+        releasedFlag("enable_wallet_contextual_loyalty_cards")
 
     // TODO(b/242908637): Tracking Bug
-    @JvmField val WALLPAPER_FULLSCREEN_PREVIEW = releasedFlag(227, "wallpaper_fullscreen_preview")
+    @JvmField val WALLPAPER_FULLSCREEN_PREVIEW = releasedFlag("wallpaper_fullscreen_preview")
 
     /** Whether the long-press gesture to open wallpaper picker is enabled. */
     // TODO(b/266242192): Tracking Bug
     @JvmField
-    val LOCK_SCREEN_LONG_PRESS_ENABLED =
-        releasedFlag(
-            228,
-            "lock_screen_long_press_enabled"
-        )
+    val LOCK_SCREEN_LONG_PRESS_ENABLED = releasedFlag("lock_screen_long_press_enabled")
 
     /** Enables UI updates for AI wallpapers in the wallpaper picker. */
     // TODO(b/267722622): Tracking Bug
-    @JvmField
-    val WALLPAPER_PICKER_UI_FOR_AIWP =
-            releasedFlag(
-                    229,
-                    "wallpaper_picker_ui_for_aiwp"
-            )
+    @JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = releasedFlag("wallpaper_picker_ui_for_aiwp")
 
     /** Whether to use a new data source for intents to run on keyguard dismissal. */
     // TODO(b/275069969): Tracking bug.
     @JvmField
-    val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag(231, "refactor_keyguard_dismiss_intent")
+    val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag("refactor_keyguard_dismiss_intent")
 
     /** Whether to allow long-press on the lock screen to directly open wallpaper picker. */
     // TODO(b/277220285): Tracking bug.
     @JvmField
     val LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP =
-        unreleasedFlag(232, "lock_screen_long_press_directly_opens_wallpaper_picker")
+        unreleasedFlag("lock_screen_long_press_directly_opens_wallpaper_picker")
+
+    /** Whether page transition animations in the wallpaper picker are enabled */
+    // TODO(b/291710220): Tracking bug.
+    @JvmField
+    val WALLPAPER_PICKER_PAGE_TRANSITIONS =
+        unreleasedFlag("wallpaper_picker_page_transitions")
+
+    /** Add "Apply" button to wall paper picker's grid preview page. */
+    // TODO(b/294866904): Tracking bug.
+    @JvmField
+    val WALLPAPER_PICKER_GRID_APPLY_BUTTON =
+            unreleasedFlag("wallpaper_picker_grid_apply_button")
 
     /** Whether to run the new udfps keyguard refactor code. */
     // TODO(b/279440316): Tracking bug.
     @JvmField
-    val REFACTOR_UDFPS_KEYGUARD_VIEWS = unreleasedFlag(233, "refactor_udfps_keyguard_views")
+    val REFACTOR_UDFPS_KEYGUARD_VIEWS = unreleasedFlag("refactor_udfps_keyguard_views")
 
     /** Provide new auth messages on the bouncer. */
     // TODO(b/277961132): Tracking bug.
-    @JvmField
-    val REVAMPED_BOUNCER_MESSAGES =
-        unreleasedFlag(234, "revamped_bouncer_messages")
+    @JvmField val REVAMPED_BOUNCER_MESSAGES = unreleasedFlag("revamped_bouncer_messages")
 
     /** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */
     // TODO(b/279794160): Tracking bug.
-    @JvmField
-    val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer")
+    @JvmField val DELAY_BOUNCER = releasedFlag("delay_bouncer")
 
-    /** Migrate the indication area to the new keyguard root view. */
-    // TODO(b/280067944): Tracking bug.
+    /** Keyguard Migration */
+
+    /**
+     * Migrate the bottom area to the new keyguard root view. Because there is no such thing as a
+     * "bottom area" after this, this also breaks it up into many smaller, modular pieces.
+     */
+    // TODO(b/290652751): Tracking bug.
     @JvmField
-    val MIGRATE_INDICATION_AREA = unreleasedFlag(236, "migrate_indication_area")
+    val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA =
+        unreleasedFlag("migrate_split_keyguard_bottom_area")
 
     /** Whether to listen for fingerprint authentication over keyguard occluding activities. */
     // TODO(b/283260512): Tracking bug.
-    @JvmField
-    val FP_LISTEN_OCCLUDING_APPS = unreleasedFlag(237, "fp_listen_occluding_apps")
+    @JvmField val FP_LISTEN_OCCLUDING_APPS = releasedFlag("fp_listen_occluding_apps")
 
     /** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */
     // TODO(b/286563884): Tracking bug
+    @JvmField val KEYGUARD_TALKBACK_FIX = releasedFlag("keyguard_talkback_fix")
+
+    // TODO(b/287268101): Tracking bug.
+    @JvmField val TRANSIT_CLOCK = releasedFlag("lockscreen_custom_transit_clock")
+
+    /** Migrate the lock icon view to the new keyguard root view. */
+    // TODO(b/286552209): Tracking bug.
+    @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag("migrate_lock_icon")
+
+    // TODO(b/288276738): Tracking bug.
+    @JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag("widget_on_keyguard")
+
+    /** Migrate the NSSL to the a sibling to both the panel and keyguard root view. */
+    // TODO(b/288074305): Tracking bug.
+    @JvmField val MIGRATE_NSSL = unreleasedFlag("migrate_nssl")
+
+    /** Migrate the status view from the notification panel to keyguard root view. */
+    // TODO(b/291767565): Tracking bug.
+    @JvmField val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag("migrate_keyguard_status_view")
+
+    /** Enables preview loading animation in the wallpaper picker. */
+    // TODO(b/274443705): Tracking Bug
     @JvmField
-    val KEYGUARD_TALKBACK_FIX = releasedFlag(238, "keyguard_talkback_fix")
+    val WALLPAPER_PICKER_PREVIEW_ANIMATION =
+            unreleasedFlag(
+                    "wallpaper_picker_preview_animation",
+                teamfood = true
+            )
 
     /** Stop running face auth when the display state changes to OFF. */
     // TODO(b/294221702): Tracking bug.
-    @JvmField val STOP_FACE_AUTH_ON_DISPLAY_OFF = resourceBooleanFlag(245,
+    @JvmField val STOP_FACE_AUTH_ON_DISPLAY_OFF = resourceBooleanFlag(
             R.bool.flag_stop_face_auth_on_display_off, "stop_face_auth_on_display_off")
 
     /** Flag to disable the face scanning animation pulsing. */
     // TODO(b/295245791): Tracking bug.
-    @JvmField val STOP_PULSING_FACE_SCANNING_ANIMATION = resourceBooleanFlag(246,
+    @JvmField val STOP_PULSING_FACE_SCANNING_ANIMATION = resourceBooleanFlag(
             R.bool.flag_stop_pulsing_face_scanning_animation,
             "stop_pulsing_face_scanning_animation")
 
+    /**
+     * TODO(b/278086361): Tracking bug
+     * Complete rewrite of the interactions between System UI and Window Manager involving keyguard
+     * state. When enabled, calls to ActivityTaskManagerService from System UI will exclusively
+     * occur from [WmLockscreenVisibilityManager] rather than the legacy KeyguardViewMediator.
+     *
+     * This flag is under development; some types of unlock may not animate properly if you enable
+     * it.
+     */
+    @JvmField
+    val KEYGUARD_WM_STATE_REFACTOR: UnreleasedFlag =
+            unreleasedFlag("keyguard_wm_state_refactor")
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
-    @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
+    @JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite")
 
     // 400 - smartspace
 
     // TODO(b/254513100): Tracking Bug
     val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
-        releasedFlag(401, "smartspace_shared_element_transition_enabled")
+        releasedFlag("smartspace_shared_element_transition_enabled")
 
     // TODO(b/258517050): Clean up after the feature is launched.
     @JvmField
     val SMARTSPACE_DATE_WEATHER_DECOUPLED =
-        sysPropBooleanFlag(403, "persist.sysui.ss.dw_decoupled", default = true)
+        sysPropBooleanFlag("persist.sysui.ss.dw_decoupled", default = true)
 
     // TODO(b/270223352): Tracking Bug
     @JvmField
-    val HIDE_SMARTSPACE_ON_DREAM_OVERLAY =
-        releasedFlag(404, "hide_smartspace_on_dream_overlay")
+    val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = releasedFlag("hide_smartspace_on_dream_overlay")
 
     // TODO(b/271460958): Tracking Bug
     @JvmField
     val SHOW_WEATHER_COMPLICATION_ON_DREAM_OVERLAY =
-        releasedFlag(405, "show_weather_complication_on_dream_overlay")
+        releasedFlag("show_weather_complication_on_dream_overlay")
 
     // 500 - quick settings
 
-    val PEOPLE_TILE = resourceBooleanFlag(502, R.bool.flag_conversations, "people_tile")
+    val PEOPLE_TILE = resourceBooleanFlag(R.bool.flag_conversations, "people_tile")
 
     @JvmField
     val QS_USER_DETAIL_SHORTCUT =
         resourceBooleanFlag(
-            503,
             R.bool.flag_lockscreen_qs_user_detail_shortcut,
             "qs_user_detail_shortcut"
         )
 
     @JvmField
-    val QS_PIPELINE_NEW_HOST = unreleasedFlag(504, "qs_pipeline_new_host", teamfood = true)
+    val QS_PIPELINE_NEW_HOST = unreleasedFlag("qs_pipeline_new_host", teamfood = true)
 
     // TODO(b/278068252): Tracking Bug
     @JvmField
-    val QS_PIPELINE_AUTO_ADD = unreleasedFlag(505, "qs_pipeline_auto_add", teamfood = false)
+    val QS_PIPELINE_AUTO_ADD = unreleasedFlag("qs_pipeline_auto_add", teamfood = false)
 
     // TODO(b/254512383): Tracking Bug
     @JvmField
     val FULL_SCREEN_USER_SWITCHER =
         resourceBooleanFlag(
-            506,
             R.bool.config_enableFullscreenUserSwitcher,
             "full_screen_user_switcher"
         )
 
     // TODO(b/244064524): Tracking Bug
-    @JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag(508, "qs_secondary_data_sub_info")
+    @JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag("qs_secondary_data_sub_info")
 
     /** Enables Font Scaling Quick Settings tile */
     // TODO(b/269341316): Tracking Bug
-    @JvmField
-    val ENABLE_FONT_SCALING_TILE = releasedFlag(509, "enable_font_scaling_tile")
+    @JvmField val ENABLE_FONT_SCALING_TILE = releasedFlag("enable_font_scaling_tile")
 
     /** Enables new QS Edit Mode visual refresh */
     // TODO(b/269787742): Tracking Bug
     @JvmField
-    val ENABLE_NEW_QS_EDIT_MODE = unreleasedFlag(510, "enable_new_qs_edit_mode", teamfood = false)
+    val ENABLE_NEW_QS_EDIT_MODE = unreleasedFlag("enable_new_qs_edit_mode", teamfood = false)
 
     // 600- status bar
 
-    // TODO(b/256614753): Tracking Bug
-    val NEW_STATUS_BAR_MOBILE_ICONS = releasedFlag(606, "new_status_bar_mobile_icons")
-
-    // TODO(b/256614210): Tracking Bug
-    val NEW_STATUS_BAR_WIFI_ICON = releasedFlag(607, "new_status_bar_wifi_icon")
-
-    // TODO(b/256614751): Tracking Bug
-    val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND =
-        unreleasedFlag(608, "new_status_bar_mobile_icons_backend", teamfood = true)
-
-    // TODO(b/256613548): Tracking Bug
-    val NEW_STATUS_BAR_WIFI_ICON_BACKEND =
-        unreleasedFlag(609, "new_status_bar_wifi_icon_backend", teamfood = true)
-
-    // TODO(b/256623670): Tracking Bug
-    @JvmField
-    val BATTERY_SHIELD_ICON =
-        resourceBooleanFlag(610, R.bool.flag_battery_shield_icon, "battery_shield_icon")
-
-    // TODO(b/260881289): Tracking Bug
-    val NEW_STATUS_BAR_ICONS_DEBUG_COLORING =
-        unreleasedFlag(611, "new_status_bar_icons_debug_coloring")
-
     // TODO(b/265892345): Tracking Bug
-    val PLUG_IN_STATUS_BAR_CHIP = releasedFlag(265892345, "plug_in_status_bar_chip")
+    val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip")
 
     // TODO(b/280426085): Tracking Bug
-    @JvmField
-    val NEW_BLUETOOTH_REPOSITORY = unreleasedFlag(612, "new_bluetooth_repository")
+    @JvmField val NEW_BLUETOOTH_REPOSITORY = releasedFlag("new_bluetooth_repository")
+
+    // TODO(b/292533677): Tracking Bug
+    val WIFI_TRACKER_LIB_FOR_WIFI_ICON = releasedFlag("wifi_tracker_lib_for_wifi_icon")
+
+    // TODO(b/293863612): Tracking Bug
+    @JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
+        releasedFlag("incompatible_charging_battery_icon")
+
+    // TODO(b/293585143): Tracking Bug
+    val INSTANT_TETHER = unreleasedFlag("instant_tether")
+
+    // TODO(b/294588085): Tracking Bug
+    val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks")
 
     // 700 - dialer/calls
     // TODO(b/254512734): Tracking Bug
-    val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")
+    val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag("ongoing_call_status_bar_chip")
 
     // TODO(b/254512681): Tracking Bug
-    val ONGOING_CALL_IN_IMMERSIVE = releasedFlag(701, "ongoing_call_in_immersive")
+    val ONGOING_CALL_IN_IMMERSIVE = releasedFlag("ongoing_call_in_immersive")
 
     // TODO(b/254512753): Tracking Bug
-    val ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = releasedFlag(702, "ongoing_call_in_immersive_chip_tap")
+    val ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = releasedFlag("ongoing_call_in_immersive_chip_tap")
 
     // 800 - general visual/theme
-    @JvmField val MONET = resourceBooleanFlag(800, R.bool.flag_monet, "monet")
+    @JvmField val MONET = resourceBooleanFlag(R.bool.flag_monet, "monet")
 
     // 801 - region sampling
     // TODO(b/254512848): Tracking Bug
-    val REGION_SAMPLING = unreleasedFlag(801, "region_sampling")
+    val REGION_SAMPLING = unreleasedFlag("region_sampling")
 
     // 803 - screen contents translation
     // TODO(b/254513187): Tracking Bug
-    val SCREEN_CONTENTS_TRANSLATION = unreleasedFlag(803, "screen_contents_translation")
+    val SCREEN_CONTENTS_TRANSLATION = unreleasedFlag("screen_contents_translation")
 
     // 804 - monochromatic themes
-    @JvmField val MONOCHROMATIC_THEME = releasedFlag(804, "monochromatic")
+    @JvmField val MONOCHROMATIC_THEME = releasedFlag("monochromatic")
+
+    // TODO(b/293380347): Tracking Bug
+    @JvmField val COLOR_FIDELITY = unreleasedFlag("color_fidelity")
 
     // 900 - media
     // TODO(b/254512697): Tracking Bug
-    val MEDIA_TAP_TO_TRANSFER = releasedFlag(900, "media_tap_to_transfer")
+    val MEDIA_TAP_TO_TRANSFER = releasedFlag("media_tap_to_transfer")
 
     // TODO(b/254512502): Tracking Bug
-    val MEDIA_SESSION_ACTIONS = unreleasedFlag(901, "media_session_actions")
-
-    // TODO(b/254512726): Tracking Bug
-    val MEDIA_NEARBY_DEVICES = releasedFlag(903, "media_nearby_devices")
-
-    // TODO(b/254512695): Tracking Bug
-    val MEDIA_MUTE_AWAIT = releasedFlag(904, "media_mute_await")
+    val MEDIA_SESSION_ACTIONS = unreleasedFlag("media_session_actions")
 
     // TODO(b/254512654): Tracking Bug
-    @JvmField val DREAM_MEDIA_COMPLICATION = unreleasedFlag(905, "dream_media_complication")
+    @JvmField val DREAM_MEDIA_COMPLICATION = unreleasedFlag("dream_media_complication")
 
     // TODO(b/254512673): Tracking Bug
-    @JvmField val DREAM_MEDIA_TAP_TO_OPEN = unreleasedFlag(906, "dream_media_tap_to_open")
+    @JvmField val DREAM_MEDIA_TAP_TO_OPEN = unreleasedFlag("dream_media_tap_to_open")
 
     // TODO(b/254513168): Tracking Bug
-    @JvmField val UMO_SURFACE_RIPPLE = releasedFlag(907, "umo_surface_ripple")
+    @JvmField val UMO_SURFACE_RIPPLE = releasedFlag("umo_surface_ripple")
 
     // TODO(b/261734857): Tracking Bug
-    @JvmField val UMO_TURBULENCE_NOISE = releasedFlag(909, "umo_turbulence_noise")
+    @JvmField val UMO_TURBULENCE_NOISE = releasedFlag("umo_turbulence_noise")
 
     // TODO(b/263272731): Tracking Bug
-    val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag(910, "media_ttt_receiver_success_ripple")
-
-    // TODO(b/263512203): Tracking Bug
-    val MEDIA_EXPLICIT_INDICATOR = releasedFlag(911, "media_explicit_indicator")
-
-    // TODO(b/265813373): Tracking Bug
-    val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE = releasedFlag(912, "media_ttt_dismiss_gesture")
+    val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag("media_ttt_receiver_success_ripple")
 
     // TODO(b/266157412): Tracking Bug
-    val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
-
-    // TODO(b/266739309): Tracking Bug
-    @JvmField
-    val MEDIA_RECOMMENDATION_CARD_UPDATE = releasedFlag(914, "media_recommendation_card_update")
+    val MEDIA_RETAIN_SESSIONS = unreleasedFlag("media_retain_sessions")
 
     // TODO(b/267007629): Tracking Bug
-    val MEDIA_RESUME_PROGRESS = releasedFlag(915, "media_resume_progress")
+    val MEDIA_RESUME_PROGRESS = releasedFlag("media_resume_progress")
 
     // TODO(b/267166152) : Tracking Bug
-    val MEDIA_RETAIN_RECOMMENDATIONS = unreleasedFlag(916, "media_retain_recommendations")
+    val MEDIA_RETAIN_RECOMMENDATIONS = unreleasedFlag("media_retain_recommendations")
 
     // TODO(b/270437894): Tracking Bug
-    val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume")
+    val MEDIA_REMOTE_RESUME = unreleasedFlag("media_remote_resume")
 
     // 1000 - dock
-    val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
+    val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging")
 
     // TODO(b/254512758): Tracking Bug
-    @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
-
-    // TODO(b/265045965): Tracking Bug
-    val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
+    @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag("rounded_box_ripple")
 
     // TODO(b/273509374): Tracking Bug
     @JvmField
-    val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS = releasedFlag(1006,
-        "always_show_home_controls_on_dreams")
+    val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS =
+        releasedFlag("always_show_home_controls_on_dreams")
 
     // 1100 - windowing
     @Keep
     @JvmField
     val WM_ENABLE_SHELL_TRANSITIONS =
-        sysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", default = true)
+        sysPropBooleanFlag("persist.wm.debug.shell_transit", default = true)
 
     // TODO(b/254513207): Tracking Bug
     @Keep
     @JvmField
     val WM_ENABLE_PARTIAL_SCREEN_SHARING =
         unreleasedFlag(
-            1102,
             name = "record_task_content",
             namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
             teamfood = true
@@ -486,54 +490,49 @@
     @Keep
     @JvmField
     val HIDE_NAVBAR_WINDOW =
-        sysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", default = false)
+        sysPropBooleanFlag("persist.wm.debug.hide_navbar_window", default = false)
 
     @Keep
     @JvmField
     val WM_DESKTOP_WINDOWING =
-        sysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", default = false)
+        sysPropBooleanFlag("persist.wm.debug.desktop_mode", default = false)
 
     @Keep
     @JvmField
     val WM_CAPTION_ON_SHELL =
-        sysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", default = false)
+        sysPropBooleanFlag("persist.wm.debug.caption_on_shell", default = true)
 
     @Keep
     @JvmField
     val ENABLE_FLING_TO_DISMISS_BUBBLE =
-        sysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", default = true)
+        sysPropBooleanFlag("persist.wm.debug.fling_to_dismiss_bubble", default = true)
 
     @Keep
     @JvmField
     val ENABLE_FLING_TO_DISMISS_PIP =
-        sysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", default = true)
+        sysPropBooleanFlag("persist.wm.debug.fling_to_dismiss_pip", default = true)
 
     @Keep
     @JvmField
     val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
-        sysPropBooleanFlag(
-            1110,
-            "persist.wm.debug.enable_pip_keep_clear_algorithm",
-            default = true
-        )
+        sysPropBooleanFlag("persist.wm.debug.enable_pip_keep_clear_algorithm", default = true)
 
     // TODO(b/256873975): Tracking Bug
     @JvmField
     @Keep
-    val WM_BUBBLE_BAR = sysPropBooleanFlag(1111, "persist.wm.debug.bubble_bar", default = false)
+    val WM_BUBBLE_BAR = sysPropBooleanFlag("persist.wm.debug.bubble_bar", default = false)
 
     // TODO(b/260271148): Tracking bug
     @Keep
     @JvmField
     val WM_DESKTOP_WINDOWING_2 =
-        sysPropBooleanFlag(1112, "persist.wm.debug.desktop_mode_2", default = false)
+        sysPropBooleanFlag("persist.wm.debug.desktop_mode_2", default = false)
 
     // TODO(b/254513207): Tracking Bug to delete
     @Keep
     @JvmField
     val WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES =
         unreleasedFlag(
-            1113,
             name = "screen_record_enterprise_policies",
             namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
             teamfood = false
@@ -543,237 +542,250 @@
     @Keep
     @JvmField
     val ENABLE_PIP_SIZE_LARGE_SCREEN =
-        sysPropBooleanFlag(1114, "persist.wm.debug.enable_pip_size_large_screen", default = true)
+        sysPropBooleanFlag("persist.wm.debug.enable_pip_size_large_screen", default = true)
 
-    // TODO(b/265998256): Tracking bug
-    @Keep
+
+    // TODO(b/293252410) : Tracking Bug
     @JvmField
-    val ENABLE_PIP_APP_ICON_OVERLAY =
-        sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = true)
+    val LOCKSCREEN_ENABLE_LANDSCAPE =
+            unreleasedFlag("lockscreen.enable_landscape")
 
     // TODO(b/273443374): Tracking Bug
     @Keep
-    @JvmField val LOCKSCREEN_LIVE_WALLPAPER =
-        sysPropBooleanFlag(1117, "persist.wm.debug.lockscreen_live_wallpaper", default = false)
+    @JvmField
+    val LOCKSCREEN_LIVE_WALLPAPER =
+        sysPropBooleanFlag("persist.wm.debug.lockscreen_live_wallpaper", default = true)
 
     // TODO(b/281648899): Tracking bug
     @Keep
     @JvmField
     val WALLPAPER_MULTI_CROP =
-        sysPropBooleanFlag(1118, "persist.wm.debug.wallpaper_multi_crop", default = false)
+        sysPropBooleanFlag("persist.wm.debug.wallpaper_multi_crop", default = false)
 
+    // TODO(b/290220798): Tracking Bug
+    @Keep
+    @JvmField
+    val ENABLE_PIP2_IMPLEMENTATION =
+        sysPropBooleanFlag("persist.wm.debug.enable_pip2_implementation", default = false)
 
     // 1200 - predictive back
     @Keep
     @JvmField
     val WM_ENABLE_PREDICTIVE_BACK =
-        sysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", default = true)
+        sysPropBooleanFlag("persist.wm.debug.predictive_back", default = true)
 
     @Keep
     @JvmField
     val WM_ENABLE_PREDICTIVE_BACK_ANIM =
-        sysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", default = true)
+        sysPropBooleanFlag("persist.wm.debug.predictive_back_anim", default = true)
 
     @Keep
     @JvmField
     val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
-        sysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", default = false)
+        sysPropBooleanFlag("persist.wm.debug.predictive_back_always_enforce", default = false)
 
     // TODO(b/254512728): Tracking Bug
-    @JvmField val NEW_BACK_AFFORDANCE = releasedFlag(1203, "new_back_affordance")
+    @JvmField val NEW_BACK_AFFORDANCE = releasedFlag("new_back_affordance")
 
     // TODO(b/255854141): Tracking Bug
     @JvmField
     val WM_ENABLE_PREDICTIVE_BACK_SYSUI =
-        unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = true)
+        unreleasedFlag("persist.wm.debug.predictive_back_sysui_enable", teamfood = true)
 
     // TODO(b/270987164): Tracking Bug
-    @JvmField
-    val TRACKPAD_GESTURE_FEATURES = unreleasedFlag(1205, "trackpad_gesture_features", teamfood = true)
+    @JvmField val TRACKPAD_GESTURE_FEATURES = releasedFlag("trackpad_gesture_features")
 
     // TODO(b/263826204): Tracking Bug
     @JvmField
     val WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM =
-        unreleasedFlag(1206, "persist.wm.debug.predictive_back_bouncer_anim", teamfood = true)
+        unreleasedFlag("persist.wm.debug.predictive_back_bouncer_anim", teamfood = true)
 
     // TODO(b/238475428): Tracking Bug
     @JvmField
     val WM_SHADE_ALLOW_BACK_GESTURE =
-        sysPropBooleanFlag(1207, "persist.wm.debug.shade_allow_back_gesture", default = false)
+        sysPropBooleanFlag("persist.wm.debug.shade_allow_back_gesture", default = false)
 
     // TODO(b/238475428): Tracking Bug
     @JvmField
     val WM_SHADE_ANIMATE_BACK_GESTURE =
-        unreleasedFlag(1208, "persist.wm.debug.shade_animate_back_gesture", teamfood = false)
+        unreleasedFlag("persist.wm.debug.shade_animate_back_gesture", teamfood = false)
 
     // TODO(b/265639042): Tracking Bug
     @JvmField
     val WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM =
-        unreleasedFlag(1209, "persist.wm.debug.predictive_back_qs_dialog_anim", teamfood = true)
+        unreleasedFlag("persist.wm.debug.predictive_back_qs_dialog_anim", teamfood = true)
 
     // TODO(b/273800936): Tracking Bug
-    @JvmField
-    val TRACKPAD_GESTURE_COMMON = releasedFlag(1210, "trackpad_gesture_common")
+    @JvmField val TRACKPAD_GESTURE_COMMON = releasedFlag("trackpad_gesture_common")
 
     // 1300 - screenshots
     // TODO(b/264916608): Tracking Bug
-    @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata")
+    @JvmField val SCREENSHOT_METADATA = unreleasedFlag("screenshot_metadata")
 
     // TODO(b/266955521): Tracking bug
-    @JvmField val SCREENSHOT_DETECTION = releasedFlag(1303, "screenshot_detection")
+    @JvmField val SCREENSHOT_DETECTION = releasedFlag("screenshot_detection")
 
     // TODO(b/251205791): Tracking Bug
-    @JvmField val SCREENSHOT_APP_CLIPS = releasedFlag(1304, "screenshot_app_clips")
+    @JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips")
 
     // 1400 - columbus
     // TODO(b/254512756): Tracking Bug
-    val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc")
+    val QUICK_TAP_IN_PCC = releasedFlag("quick_tap_in_pcc")
 
     // TODO(b/261979569): Tracking Bug
     val QUICK_TAP_FLOW_FRAMEWORK =
-        unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false)
+        unreleasedFlag("quick_tap_flow_framework", teamfood = false)
 
     // 1500 - chooser aka sharesheet
 
     // 1700 - clipboard
-    @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
+    @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag("clipboard_remote_behavior")
     // TODO(b/278714186) Tracking Bug
-    @JvmField val CLIPBOARD_IMAGE_TIMEOUT =
-            unreleasedFlag(1702, "clipboard_image_timeout", teamfood = true)
+    @JvmField
+    val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag("clipboard_image_timeout", teamfood = true)
     // TODO(b/279405451): Tracking Bug
     @JvmField
-    val CLIPBOARD_SHARED_TRANSITIONS = unreleasedFlag(1703, "clipboard_shared_transitions")
+    val CLIPBOARD_SHARED_TRANSITIONS =
+            unreleasedFlag("clipboard_shared_transitions", teamfood = true)
 
-    // 1800 - shade container
-    // TODO(b/265944639): Tracking Bug
-    @JvmField val DUAL_SHADE = unreleasedFlag(1801, "dual_shade")
+    // TODO(b/283300105): Tracking Bug
+    @JvmField val SCENE_CONTAINER = unreleasedFlag("scene_container")
 
     // 1900
-    @JvmField val NOTE_TASKS = releasedFlag(1900, "keycode_flag")
+    @JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
 
     // 2000 - device controls
-    @Keep @JvmField val USE_APP_PANELS = releasedFlag(2000, "use_app_panels")
-
-    @JvmField
-    val APP_PANELS_ALL_APPS_ALLOWED =
-        releasedFlag(2001, "app_panels_all_apps_allowed")
-
-    @JvmField
-    val CONTROLS_MANAGEMENT_NEW_FLOWS =
-        releasedFlag(2002, "controls_management_new_flows")
-
-    // Enables removing app from Home control panel as a part of a new flow
-    // TODO(b/269132640): Tracking Bug
-    @JvmField
-    val APP_PANELS_REMOVE_APPS_ALLOWED =
-        releasedFlag(2003, "app_panels_remove_apps_allowed")
+    @JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag("app_panels_all_apps_allowed")
 
     // 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.)
     // TODO(b/259264861): Tracking Bug
-    @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection")
-    @JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag(2201, "udfps_ellipse_detection")
+    @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag("udfps_new_touch_detection")
+    @JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag("udfps_ellipse_detection")
     // TODO(b/278622168): Tracking Bug
-    @JvmField val BIOMETRIC_BP_STRONG = releasedFlag(2202, "biometric_bp_strong")
+    @JvmField val BIOMETRIC_BP_STRONG = releasedFlag("biometric_bp_strong")
 
     // 2300 - stylus
-    @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag(2300, "track_stylus_ever_used")
+    @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag("track_stylus_ever_used")
+    @JvmField val ENABLE_STYLUS_CHARGING_UI = releasedFlag("enable_stylus_charging_ui")
     @JvmField
-    val ENABLE_STYLUS_CHARGING_UI = releasedFlag(2301, "enable_stylus_charging_ui")
-    @JvmField
-    val ENABLE_USI_BATTERY_NOTIFICATIONS =
-        releasedFlag(2302, "enable_usi_battery_notifications")
-    @JvmField val ENABLE_STYLUS_EDUCATION =
-        unreleasedFlag(2303, "enable_stylus_education", teamfood = true)
+    val ENABLE_USI_BATTERY_NOTIFICATIONS = releasedFlag("enable_usi_battery_notifications")
+    @JvmField val ENABLE_STYLUS_EDUCATION = releasedFlag("enable_stylus_education")
 
     // 2400 - performance tools and debugging info
     // TODO(b/238923086): Tracking Bug
     @JvmField
     val WARN_ON_BLOCKING_BINDER_TRANSACTIONS =
-        unreleasedFlag(2400, "warn_on_blocking_binder_transactions")
+        unreleasedFlag("warn_on_blocking_binder_transactions")
 
     // TODO(b/283071711): Tracking bug
     @JvmField
     val TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK =
-            unreleasedFlag(2401, "trim_resources_with_background_trim_on_lock")
+        unreleasedFlag("trim_resources_with_background_trim_on_lock")
 
     // TODO:(b/283203305): Tracking bug
-    @JvmField
-    val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag(2402, "trim_font_caches_on_unlock")
+    @JvmField val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag("trim_font_caches_on_unlock")
 
     // 2700 - unfold transitions
     // TODO(b/265764985): Tracking Bug
     @Keep
     @JvmField
     val ENABLE_DARK_VIGNETTE_WHEN_FOLDING =
-        unreleasedFlag(2700, "enable_dark_vignette_when_folding")
+        unreleasedFlag("enable_dark_vignette_when_folding")
 
     // TODO(b/265764985): Tracking Bug
     @Keep
     @JvmField
     val ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS =
-        unreleasedFlag(2701, "enable_unfold_status_bar_animations")
+        unreleasedFlag("enable_unfold_status_bar_animations")
 
     // TODO(b259590361): Tracking bug
-    val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
+    val EXPERIMENTAL_FLAG = unreleasedFlag("exp_flag_release")
 
     // 2600 - keyboard
     // TODO(b/259352579): Tracking Bug
-    @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = releasedFlag(2600, "shortcut_list_search_layout")
+    @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = releasedFlag("shortcut_list_search_layout")
 
     // TODO(b/259428678): Tracking Bug
-    @JvmField
-    val KEYBOARD_BACKLIGHT_INDICATOR =
-            unreleasedFlag(2601, "keyboard_backlight_indicator", teamfood = true)
+    @JvmField val KEYBOARD_BACKLIGHT_INDICATOR = releasedFlag("keyboard_backlight_indicator")
 
     // TODO(b/277192623): Tracking Bug
-    @JvmField
-    val KEYBOARD_EDUCATION =
-        unreleasedFlag(2603, "keyboard_education", teamfood = false)
-
-    // TODO(b/272036292): Tracking Bug
-    @JvmField
-    val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION =
-            releasedFlag(2602, "large_shade_granular_alpha_interpolation")
+    @JvmField val KEYBOARD_EDUCATION = unreleasedFlag("keyboard_education", teamfood = false)
 
     // TODO(b/277201412): Tracking Bug
     @JvmField
-    val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION =
-            unreleasedFlag(2805, "split_shade_subpixel_optimization", teamfood = true)
+    val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION = releasedFlag("split_shade_subpixel_optimization")
+
+    // TODO(b/288868056): Tracking Bug
+    @JvmField
+    val PARTIAL_SCREEN_SHARING_TASK_SWITCHER = unreleasedFlag("pss_task_switcher")
 
     // TODO(b/278761837): Tracking Bug
-    @JvmField
-    val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter")
+    @JvmField val USE_NEW_ACTIVITY_STARTER = releasedFlag(name = "use_new_activity_starter")
 
     // 2900 - Zero Jank fixes. Naming convention is: zj_<bug number>_<cuj name>
 
     // TODO:(b/285623104): Tracking bug
     @JvmField
     val ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD =
-        releasedFlag(2900, "zj_285570694_lockscreen_transition_from_aod")
+        releasedFlag("zj_285570694_lockscreen_transition_from_aod")
 
     // 3000 - dream
     // TODO(b/285059790) : Tracking Bug
     @JvmField
     val LOCKSCREEN_WALLPAPER_DREAM_ENABLED =
-        unreleasedFlag(3000, name = "enable_lockscreen_wallpaper_dream")
+        unreleasedFlag(name = "enable_lockscreen_wallpaper_dream")
 
     // TODO(b/283084712): Tracking Bug
-    @JvmField
-    val IMPROVED_HUN_ANIMATIONS = unreleasedFlag(283084712, "improved_hun_animations")
+    @JvmField val IMPROVED_HUN_ANIMATIONS = unreleasedFlag("improved_hun_animations")
 
     // TODO(b/283447257): Tracking bug
     @JvmField
     val BIGPICTURE_NOTIFICATION_LAZY_LOADING =
-            unreleasedFlag(283447257, "bigpicture_notification_lazy_loading")
+        unreleasedFlag("bigpicture_notification_lazy_loading")
+
+    // TODO(b/292062937): Tracking bug
+    @JvmField
+    val NOTIFICATION_CLEARABLE_REFACTOR =
+            unreleasedFlag("notification_clearable_refactor")
 
     // TODO(b/283740863): Tracking Bug
     @JvmField
     val ENABLE_NEW_PRIVACY_DIALOG =
-            unreleasedFlag(283740863, "enable_new_privacy_dialog", teamfood = false)
+        unreleasedFlag("enable_new_privacy_dialog", teamfood = true)
+
+    // TODO(b/289573946): Tracking Bug
+    @JvmField val PRECOMPUTED_TEXT = unreleasedFlag("precomputed_text")
 
     // 2900 - CentralSurfaces-related flags
 
     // TODO(b/285174336): Tracking Bug
     @JvmField
-    val USE_REPOS_FOR_BOUNCER_SHOWING = unreleasedFlag(2900, "use_repos_for_bouncer_showing")
+    val USE_REPOS_FOR_BOUNCER_SHOWING =
+        unreleasedFlag("use_repos_for_bouncer_showing", teamfood = true)
+
+    // 3100 - Haptic interactions
+
+    // TODO(b/290213663): Tracking Bug
+    @JvmField
+    val ONE_WAY_HAPTICS_API_MIGRATION = unreleasedFlag("oneway_haptics_api_migration")
+
+    /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
+    @JvmField
+    val ENABLE_CLOCK_KEYGUARD_PRESENTATION = unreleasedFlag("enable_clock_keyguard_presentation")
+
+    /** Enable the Compose implementation of the PeopleSpaceActivity. */
+    @JvmField
+    val COMPOSE_PEOPLE_SPACE = unreleasedFlag("compose_people_space")
+
+    /** Enable the Compose implementation of the Quick Settings footer actions. */
+    @JvmField
+    val COMPOSE_QS_FOOTER_ACTIONS = unreleasedFlag("compose_qs_footer_actions")
+
+    /** Enable the share wifi button in Quick Settings internet dialog. */
+    @JvmField
+    val SHARE_WIFI_QS_BUTTON = unreleasedFlag("share_wifi_qs_button")
+
+    /** Enable haptic slider component in the brightness slider */
+    @JvmField
+    val HAPTIC_BRIGHTNESS_SLIDER = unreleasedFlag("haptic_brightness_slider")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt
new file mode 100644
index 0000000..eaecda5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/ViewRefactorFlag.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.util.Log
+import com.android.systemui.Dependency
+
+/**
+ * This class promotes best practices for flag guarding System UI view refactors.
+ * * [isEnabled] allows changing an implementation.
+ * * [assertDisabled] allows authors to flag code as being "dead" when the flag gets enabled and
+ *   ensure that it is not being invoked accidentally in the post-flag refactor.
+ * * [expectEnabled] allows authors to guard new code with a "safe" alternative when invoked on
+ *   flag-disabled builds, but with a check that should crash eng builds or tests when the
+ *   expectation is violated.
+ *
+ * The constructors prefer that you provide a [FeatureFlags] instance, but does not require it,
+ * falling back to [Dependency.get]. This fallback should ONLY be used to flag-guard code changes
+ * inside views where injecting flag values after initialization can be error-prone.
+ */
+class ViewRefactorFlag
+private constructor(
+    private val injectedFlags: FeatureFlags?,
+    private val flag: BooleanFlag,
+    private val readFlagValue: (FeatureFlags) -> Boolean
+) {
+    @JvmOverloads
+    constructor(
+        flags: FeatureFlags? = null,
+        flag: UnreleasedFlag
+    ) : this(flags, flag, { it.isEnabled(flag) })
+
+    @JvmOverloads
+    constructor(
+        flags: FeatureFlags? = null,
+        flag: ReleasedFlag
+    ) : this(flags, flag, { it.isEnabled(flag) })
+
+    /** Whether the flag is enabled. Called to switch between an old behavior and a new behavior. */
+    val isEnabled by lazy {
+        @Suppress("DEPRECATION")
+        val featureFlags = injectedFlags ?: Dependency.get(FeatureFlags::class.java)
+        readFlagValue(featureFlags)
+    }
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     *
+     * Example usage:
+     * ```
+     * public void setController(NotificationShelfController notificationShelfController) {
+     *     mShelfRefactor.assertDisabled();
+     *     mController = notificationShelfController;
+     * }
+     * ````
+     */
+    fun assertDisabled() = check(!isEnabled) { "Code path not supported when $flag is enabled." }
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     *
+     * Example usage:
+     * ```
+     * public void setShelfIcons(NotificationIconContainer icons) {
+     *     if (mShelfRefactor.expectEnabled()) {
+     *         mShelfIcons = icons;
+     *     }
+     * }
+     * ```
+     */
+    fun expectEnabled(): Boolean {
+        if (!isEnabled) {
+            val message = "Code path not supported when $flag is disabled."
+            Log.wtf(TAG, message, Exception(message))
+        }
+        return isEnabled
+    }
+
+    private companion object {
+        private const val TAG = "ViewRefactorFlag"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 76322ad..040ee79 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -125,6 +125,7 @@
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.scrim.ScrimDrawable;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -251,6 +252,7 @@
     private int mSmallestScreenWidthDp;
     private int mOrientation;
     private final Optional<CentralSurfaces> mCentralSurfacesOptional;
+    private final ShadeController mShadeController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
 
@@ -361,6 +363,7 @@
             @Main Handler handler,
             PackageManager packageManager,
             Optional<CentralSurfaces> centralSurfacesOptional,
+            ShadeController shadeController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             DialogLaunchAnimator dialogLaunchAnimator) {
         mContext = context;
@@ -394,6 +397,7 @@
         mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp;
         mOrientation = resources.getConfiguration().orientation;
         mCentralSurfacesOptional = centralSurfacesOptional;
+        mShadeController = shadeController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mDialogLaunchAnimator = dialogLaunchAnimator;
 
@@ -702,7 +706,9 @@
                 mAdapter, mOverflowAdapter, mSysuiColorExtractor, mStatusBarService,
                 mLightBarController,
                 mNotificationShadeWindowController, this::onRefresh, mKeyguardShowing,
-                mPowerAdapter, mUiEventLogger, mCentralSurfacesOptional, mKeyguardUpdateMonitor,
+                mPowerAdapter, mUiEventLogger, mCentralSurfacesOptional,
+                mShadeController,
+                mKeyguardUpdateMonitor,
                 mLockPatternUtils);
 
         dialog.setOnDismissListener(this);
@@ -912,7 +918,7 @@
             mUiEventLogger.log(GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS);
             if (mTelecomManager != null) {
                 // Close shade so user sees the activity
-                mCentralSurfacesOptional.ifPresent(CentralSurfaces::collapseShade);
+                mShadeController.cancelExpansionAndCollapseShade();
                 Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent(
                         null /* number */);
                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
@@ -2209,6 +2215,7 @@
         private UiEventLogger mUiEventLogger;
         private GestureDetector mGestureDetector;
         private Optional<CentralSurfaces> mCentralSurfacesOptional;
+        private final ShadeController mShadeController;
         private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
         private LockPatternUtils mLockPatternUtils;
         private float mWindowDimAmount;
@@ -2282,6 +2289,7 @@
                 Runnable onRefreshCallback, boolean keyguardShowing,
                 MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
                 Optional<CentralSurfaces> centralSurfacesOptional,
+                ShadeController shadeController,
                 KeyguardUpdateMonitor keyguardUpdateMonitor,
                 LockPatternUtils lockPatternUtils) {
             // We set dismissOnDeviceLock to false because we have a custom broadcast receiver to
@@ -2299,6 +2307,7 @@
             mKeyguardShowing = keyguardShowing;
             mUiEventLogger = uiEventLogger;
             mCentralSurfacesOptional = centralSurfacesOptional;
+            mShadeController = shadeController;
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mGestureDetector = new GestureDetector(mContext, mGestureListener);
@@ -2348,12 +2357,10 @@
             mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
             if (mCentralSurfacesOptional.map(CentralSurfaces::isKeyguardShowing).orElse(false)) {
                 // match existing lockscreen behavior to open QS when swiping from status bar
-                mCentralSurfacesOptional.ifPresent(
-                        centralSurfaces -> centralSurfaces.animateExpandSettingsPanel(null));
+                mShadeController.animateExpandQs();
             } else {
                 // otherwise, swiping down should expand notification shade
-                mCentralSurfacesOptional.ifPresent(
-                        centralSurfaces -> centralSurfaces.animateExpandNotificationsPanel());
+                mShadeController.animateExpandShade();
             }
             dismiss();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
index de67ba8..6d083f6 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
@@ -87,7 +87,7 @@
         if (mIsDropDownMode) {
             // use a divider
             listView.setDividerHeight(res.getDimensionPixelSize(R.dimen.control_list_divider));
-            listView.setDivider(res.getDrawable(R.drawable.controls_list_divider_inset));
+            listView.setDivider(res.getDrawable(R.drawable.global_actions_list_divider_inset));
         } else {
             if (mAdapter == null) return;
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index 7078341..b5b56b2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -161,7 +161,7 @@
     }
 
     private fun updateIconTile() {
-        val iconTile = rootView.findViewById(BACKLIGHT_ICON_ID) as ImageView
+        val iconTile = rootView.requireViewById(BACKLIGHT_ICON_ID) as ImageView
         val backgroundDrawable = iconTile.background as ShapeDrawable
         if (currentLevel == 0) {
             iconTile.setColorFilter(dimmedIconColor)
diff --git a/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
new file mode 100644
index 0000000..3f2f67d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractor.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyevent.domain.interactor
+
+import android.view.KeyEvent
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
+import javax.inject.Inject
+
+/**
+ * Sends key events to the appropriate interactors and then acts upon key events that haven't
+ * already been handled but should be handled by SystemUI.
+ */
+@SysUISingleton
+class KeyEventInteractor
+@Inject
+constructor(
+    private val backActionInteractor: BackActionInteractor,
+    private val keyguardKeyEventInteractor: KeyguardKeyEventInteractor,
+) {
+    fun dispatchKeyEvent(event: KeyEvent): Boolean {
+        if (keyguardKeyEventInteractor.dispatchKeyEvent(event)) {
+            return true
+        }
+
+        when (event.keyCode) {
+            KeyEvent.KEYCODE_BACK -> {
+                if (event.handleAction()) {
+                    backActionInteractor.onBackRequested()
+                }
+                return true
+            }
+        }
+        return false
+    }
+
+    fun interceptMediaKey(event: KeyEvent): Boolean {
+        return keyguardKeyEventInteractor.interceptMediaKey(event)
+    }
+
+    fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+        return keyguardKeyEventInteractor.dispatchKeyEventPreIme(event)
+    }
+
+    companion object {
+        // Most actions shouldn't be handled on the down event and instead handled on subsequent
+        // key events like ACTION_UP.
+        fun KeyEvent.handleAction(): Boolean {
+            return action != KeyEvent.ACTION_DOWN
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
index 757afb6..67c85bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
@@ -16,11 +16,19 @@
 
 package com.android.systemui.keyguard;
 
+import android.annotation.IntDef;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
-import android.os.Trace;
+import android.os.TraceNameSupplier;
+
+import androidx.annotation.NonNull;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 import javax.inject.Inject;
 
@@ -29,7 +37,6 @@
  */
 @SysUISingleton
 public class KeyguardLifecyclesDispatcher {
-
     static final int SCREEN_TURNING_ON = 0;
     static final int SCREEN_TURNED_ON = 1;
     static final int SCREEN_TURNING_OFF = 2;
@@ -39,19 +46,46 @@
     static final int FINISHED_WAKING_UP = 5;
     static final int STARTED_GOING_TO_SLEEP = 6;
     static final int FINISHED_GOING_TO_SLEEP = 7;
-    private static final String TAG = "KeyguardLifecyclesDispatcher";
 
-    private final ScreenLifecycle mScreenLifecycle;
-    private final WakefulnessLifecycle mWakefulnessLifecycle;
-
-    @Inject
-    public KeyguardLifecyclesDispatcher(ScreenLifecycle screenLifecycle,
-            WakefulnessLifecycle wakefulnessLifecycle) {
-        mScreenLifecycle = screenLifecycle;
-        mWakefulnessLifecycle = wakefulnessLifecycle;
+    @IntDef({
+            SCREEN_TURNING_ON,
+            SCREEN_TURNED_ON,
+            SCREEN_TURNING_OFF,
+            SCREEN_TURNED_OFF,
+            STARTED_WAKING_UP,
+            FINISHED_WAKING_UP,
+            STARTED_GOING_TO_SLEEP,
+            FINISHED_GOING_TO_SLEEP,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface KeyguardLifecycleMessageType {
     }
 
-    void dispatch(int what) {
+    private static String getNameOfMessage(@KeyguardLifecycleMessageType int what) {
+        return switch (what) {
+            case SCREEN_TURNING_ON -> "SCREEN_TURNING_ON";
+            case SCREEN_TURNED_ON -> "SCREEN_TURNED_ON";
+            case SCREEN_TURNING_OFF -> "SCREEN_TURNING_OFF";
+            case SCREEN_TURNED_OFF -> "SCREEN_TURNED_OFF";
+            case STARTED_WAKING_UP -> "STARTED_WAKING_UP";
+            case FINISHED_WAKING_UP -> "FINISHED_WAKING_UP";
+            case STARTED_GOING_TO_SLEEP -> "STARTED_GOING_TO_SLEEP";
+            case FINISHED_GOING_TO_SLEEP -> "FINISHED_GOING_TO_SLEEP";
+            default -> "UNKNOWN";
+        };
+    }
+
+    private final Handler mHandler;
+
+    @Inject
+    public KeyguardLifecyclesDispatcher(
+            @Main Looper mainLooper,
+            ScreenLifecycle screenLifecycle,
+            WakefulnessLifecycle wakefulnessLifecycle) {
+        mHandler = new KeyguardLifecycleHandler(mainLooper, screenLifecycle, wakefulnessLifecycle);
+    }
+
+    void dispatch(@KeyguardLifecycleMessageType int what) {
         mHandler.obtainMessage(what).sendToTarget();
     }
 
@@ -60,7 +94,7 @@
      * @param pmReason Reason this message was triggered - this should be a value from either
      * {@link PowerManager.WakeReason} or {@link PowerManager.GoToSleepReason}.
      */
-    void dispatch(int what, int pmReason) {
+    void dispatch(@KeyguardLifecycleMessageType int what, int pmReason) {
         final Message message = mHandler.obtainMessage(what);
         message.arg1 = pmReason;
         message.sendToTarget();
@@ -70,44 +104,48 @@
      * @param what Message to send.
      * @param object Object to send with the message
      */
-    void dispatch(int what, Object object) {
+    void dispatch(@KeyguardLifecycleMessageType int what, Object object) {
         mHandler.obtainMessage(what, object).sendToTarget();
     }
 
-    private Handler mHandler = new Handler() {
+    private static class KeyguardLifecycleHandler extends Handler {
+        private static final String TAG = "KeyguardLifecycleHandler";
+        private final ScreenLifecycle mScreenLifecycle;
+        private final WakefulnessLifecycle mWakefulnessLifecycle;
+
+        public KeyguardLifecycleHandler(Looper looper,
+                                         ScreenLifecycle screenLifecycle,
+                                         WakefulnessLifecycle wakefulnessLifecycle) {
+            super(looper);
+            mScreenLifecycle = screenLifecycle;
+            mWakefulnessLifecycle = wakefulnessLifecycle;
+        }
+
+        @NonNull
         @Override
-        public void handleMessage(Message msg) {
+        public String getTraceName(@NonNull Message msg) {
+            if (msg.getCallback() instanceof TraceNameSupplier || msg.getCallback() != null) {
+                return super.getTraceName(msg);
+            }
+            return TAG + "#" + getNameOfMessage(msg.what);
+        }
+
+        @Override
+        public void handleMessage(@NonNull Message msg) {
             switch (msg.what) {
-                case SCREEN_TURNING_ON:
-                    Trace.beginSection("KeyguardLifecyclesDispatcher#SCREEN_TURNING_ON");
-                    mScreenLifecycle.dispatchScreenTurningOn();
-                    Trace.endSection();
-                    break;
-                case SCREEN_TURNED_ON:
-                    mScreenLifecycle.dispatchScreenTurnedOn();
-                    break;
-                case SCREEN_TURNING_OFF:
-                    mScreenLifecycle.dispatchScreenTurningOff();
-                    break;
-                case SCREEN_TURNED_OFF:
-                    mScreenLifecycle.dispatchScreenTurnedOff();
-                    break;
-                case STARTED_WAKING_UP:
-                    mWakefulnessLifecycle.dispatchStartedWakingUp(msg.arg1 /* pmReason */);
-                    break;
-                case FINISHED_WAKING_UP:
-                    mWakefulnessLifecycle.dispatchFinishedWakingUp();
-                    break;
-                case STARTED_GOING_TO_SLEEP:
-                    mWakefulnessLifecycle.dispatchStartedGoingToSleep(msg.arg1 /* pmReason */);
-                    break;
-                case FINISHED_GOING_TO_SLEEP:
-                    mWakefulnessLifecycle.dispatchFinishedGoingToSleep();
-                    break;
-                default:
-                    throw new IllegalArgumentException("Unknown message: " + msg);
+                case SCREEN_TURNING_ON -> mScreenLifecycle.dispatchScreenTurningOn();
+                case SCREEN_TURNED_ON -> mScreenLifecycle.dispatchScreenTurnedOn();
+                case SCREEN_TURNING_OFF -> mScreenLifecycle.dispatchScreenTurningOff();
+                case SCREEN_TURNED_OFF -> mScreenLifecycle.dispatchScreenTurnedOff();
+                case STARTED_WAKING_UP ->
+                        mWakefulnessLifecycle.dispatchStartedWakingUp(msg.arg1 /* pmReason */);
+                case FINISHED_WAKING_UP -> mWakefulnessLifecycle.dispatchFinishedWakingUp();
+                case STARTED_GOING_TO_SLEEP ->
+                        mWakefulnessLifecycle.dispatchStartedGoingToSleep(msg.arg1 /* pmReason */);
+                case FINISHED_GOING_TO_SLEEP ->
+                        mWakefulnessLifecycle.dispatchFinishedGoingToSleep();
+                default -> throw new IllegalArgumentException("Unknown message: " + msg);
             }
         }
-    };
-
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index e6053fb..9d2771e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -73,6 +73,14 @@
 import com.android.internal.policy.IKeyguardStateCallback;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.SystemUIApplication;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
+import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.wm.shell.transition.ShellTransitions;
 import com.android.wm.shell.transition.Transitions;
@@ -85,10 +93,13 @@
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.CoroutineScope;
+
 public class KeyguardService extends Service {
     static final String TAG = "KeyguardService";
     static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD;
 
+    private final FeatureFlags mFlags;
     private final KeyguardViewMediator mKeyguardViewMediator;
     private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
     private final ScreenOnCoordinator mScreenOnCoordinator;
@@ -291,13 +302,33 @@
                            KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher,
                            ScreenOnCoordinator screenOnCoordinator,
                            ShellTransitions shellTransitions,
-                           DisplayTracker displayTracker) {
+                           DisplayTracker displayTracker,
+                           WindowManagerLockscreenVisibilityViewModel
+                                   wmLockscreenVisibilityViewModel,
+                           WindowManagerLockscreenVisibilityManager wmLockscreenVisibilityManager,
+                           KeyguardSurfaceBehindViewModel keyguardSurfaceBehindViewModel,
+                           KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator,
+                           @Application CoroutineScope scope,
+                           FeatureFlags featureFlags) {
         super();
         mKeyguardViewMediator = keyguardViewMediator;
         mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
         mScreenOnCoordinator = screenOnCoordinator;
         mShellTransitions = shellTransitions;
         mDisplayTracker = displayTracker;
+        mFlags = featureFlags;
+
+        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            WindowManagerLockscreenVisibilityViewBinder.bind(
+                    wmLockscreenVisibilityViewModel,
+                    wmLockscreenVisibilityManager,
+                    scope);
+
+            KeyguardSurfaceBehindViewBinder.bind(
+                    keyguardSurfaceBehindViewModel,
+                    keyguardSurfaceBehindAnimator,
+                    scope);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 573de97..732102e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -49,7 +49,8 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.R;
-import com.android.systemui.SystemUIAppComponentFactory;
+import com.android.systemui.SystemUIAppComponentFactoryBase;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -72,13 +73,13 @@
 /**
  * Simple Slice provider that shows the current date.
  *
- * Injection is handled by {@link SystemUIAppComponentFactory} +
+ * Injection is handled by {@link SystemUIAppComponentFactoryBase} +
  * {@link com.android.systemui.dagger.GlobalRootComponent#inject(KeyguardSliceProvider)}.
  */
 public class KeyguardSliceProvider extends SliceProvider implements
         NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback,
         NotificationMediaManager.MediaListener, StatusBarStateController.StateListener,
-        SystemUIAppComponentFactory.ContextInitializer {
+        SystemUIAppComponentFactoryBase.ContextInitializer {
 
     private static final String TAG = "KgdSliceProvider";
 
@@ -148,9 +149,12 @@
     protected boolean mDozing;
     private int mStatusBarState;
     private boolean mMediaIsVisible;
-    private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
+    private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback;
     @Inject
     WakeLockLogger mWakeLockLogger;
+    @Inject
+    @Background
+    Handler mBgHandler;
 
     /**
      * Receiver responsible for time ticking and updating the date format.
@@ -502,7 +506,7 @@
     }
 
     protected void notifyChange() {
-        mContentResolver.notifyChange(mSliceUri, null /* observer */);
+        mBgHandler.post(() -> mContentResolver.notifyChange(mSliceUri, null /* observer */));
     }
 
     @Override
@@ -533,7 +537,7 @@
 
     @Override
     public void setContextAvailableCallback(
-            SystemUIAppComponentFactory.ContextAvailableCallback callback) {
+            SystemUIAppComponentFactoryBase.ContextAvailableCallback callback) {
         mContextAvailableCallback = callback;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 4418740..32a9559 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -405,7 +405,9 @@
      * the device.
      */
     fun canPerformInWindowLauncherAnimations(): Boolean {
-        return isNexusLauncherUnderneath() &&
+        // TODO(b/278086361): Refactor in-window animations.
+        return !featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR) &&
+                isNexusLauncherUnderneath() &&
                 // If the launcher is underneath, but we're about to launch an activity, don't do
                 // the animations since they won't be visible.
                 !notificationShadeWindowController.isLaunchingActivity &&
@@ -849,54 +851,57 @@
         }
 
         surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
-            val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
+            if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                val surfaceHeight: Int =
+                        surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
 
-            var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
-                    (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
-                    MathUtils.clamp(amount, 0f, 1f))
+                var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
+                        (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
+                        MathUtils.clamp(amount, 0f, 1f))
 
-            // If we're dismissing via swipe to the Launcher, we'll play in-window scale animations,
-            // so don't also scale the window.
-            if (keyguardStateController.isDismissingFromSwipe &&
-                    willUnlockWithInWindowLauncherAnimations) {
-                scaleFactor = 1f
-            }
-
-            // Translate up from the bottom.
-            surfaceBehindMatrix.setTranslate(
-                    surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
-                    surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
-                            surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
-            )
-
-            // Scale up from a point at the center-bottom of the surface.
-            surfaceBehindMatrix.postScale(
-                    scaleFactor,
-                    scaleFactor,
-                    keyguardViewController.viewRootImpl.width / 2f,
-                    surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
-            )
-
-            // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
-            // unable to draw
-            val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
-            if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
-                    sc?.isValid == true) {
-                with(SurfaceControl.Transaction()) {
-                    setMatrix(sc, surfaceBehindMatrix, tmpFloat)
-                    setCornerRadius(sc, roundedCornerRadius)
-                    setAlpha(sc, animationAlpha)
-                    apply()
+                // If we're dismissing via swipe to the Launcher, we'll play in-window scale
+                // animations, so don't also scale the window.
+                if (keyguardStateController.isDismissingFromSwipe &&
+                        willUnlockWithInWindowLauncherAnimations) {
+                    scaleFactor = 1f
                 }
-            } else {
-                applyParamsToSurface(
-                        SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
-                            surfaceBehindRemoteAnimationTarget.leash)
-                            .withMatrix(surfaceBehindMatrix)
-                            .withCornerRadius(roundedCornerRadius)
-                            .withAlpha(animationAlpha)
-                            .build()
+
+                // Translate up from the bottom.
+                surfaceBehindMatrix.setTranslate(
+                        surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
+                        surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
+                                surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
                 )
+
+                // Scale up from a point at the center-bottom of the surface.
+                surfaceBehindMatrix.postScale(
+                        scaleFactor,
+                        scaleFactor,
+                        keyguardViewController.viewRootImpl.width / 2f,
+                        surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
+                )
+
+                // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
+                // unable to draw
+                val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
+                if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+                        sc?.isValid == true) {
+                    with(SurfaceControl.Transaction()) {
+                        setMatrix(sc, surfaceBehindMatrix, tmpFloat)
+                        setCornerRadius(sc, roundedCornerRadius)
+                        setAlpha(sc, animationAlpha)
+                        apply()
+                    }
+                } else {
+                    applyParamsToSurface(
+                            SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+                                    surfaceBehindRemoteAnimationTarget.leash)
+                                    .withMatrix(surfaceBehindMatrix)
+                                    .withCornerRadius(roundedCornerRadius)
+                                    .withAlpha(animationAlpha)
+                                    .build()
+                    )
+                }
             }
         }
 
@@ -906,10 +911,7 @@
     }
 
     fun setWallpaperAppearAmount(amount: Float) {
-        val animationAlpha = when {
-            !powerManager.isInteractive -> 0f
-            else -> amount
-        }
+        val animationAlpha = amount
 
         wallpaperTargets?.forEach { wallpaper ->
             // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
@@ -988,10 +990,12 @@
         if (keyguardStateController.isShowing) {
             // Hide the keyguard, with no fade out since we animated it away during the unlock.
 
-            keyguardViewController.hide(
-                surfaceBehindRemoteAnimationStartTime,
-                0 /* fadeOutDuration */
-            )
+            if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                keyguardViewController.hide(
+                        surfaceBehindRemoteAnimationStartTime,
+                        0 /* fadeOutDuration */
+                )
+            }
         } else {
             Log.i(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
                     "showing. Ignoring...")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
new file mode 100644
index 0000000..8e323d8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard
+
+import android.content.res.Configuration
+import android.view.View
+import android.view.ViewGroup
+import com.android.keyguard.KeyguardStatusViewController
+import com.android.keyguard.dagger.KeyguardStatusViewComponent
+import com.android.systemui.CoreStartable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.binder.KeyguardAmbientIndicationAreaViewBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.shade.NotificationShadeWindowView
+import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Binds keyguard views on startup, and also exposes methods to allow rebinding if views change */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class KeyguardViewConfigurator
+@Inject
+constructor(
+    private val keyguardRootView: KeyguardRootView,
+    private val sharedNotificationContainer: SharedNotificationContainer,
+    private val keyguardRootViewModel: KeyguardRootViewModel,
+    private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
+    private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+    private val keyguardAmbientIndicationViewModel: KeyguardAmbientIndicationViewModel,
+    private val notificationShadeWindowView: NotificationShadeWindowView,
+    private val featureFlags: FeatureFlags,
+    private val indicationController: KeyguardIndicationController,
+    private val keyguardQuickAffordancesCombinedViewModel:
+        KeyguardQuickAffordancesCombinedViewModel,
+    private val falsingManager: FalsingManager,
+    private val vibratorHelper: VibratorHelper,
+    private val keyguardStateController: KeyguardStateController,
+    private val keyguardSettingsMenuViewModel: KeyguardSettingsMenuViewModel,
+    private val activityStarter: ActivityStarter,
+    private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
+    private val chipbarCoordinator: ChipbarCoordinator,
+    private val keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener,
+    private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
+    private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
+) : CoreStartable {
+
+    private var rootViewHandle: DisposableHandle? = null
+    private var indicationAreaHandle: DisposableHandle? = null
+    private var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
+    private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
+    private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null
+    private var settingsPopupMenuHandle: DisposableHandle? = null
+    private var keyguardStatusViewController: KeyguardStatusViewController? = null
+
+    override fun start() {
+        bindKeyguardRootView()
+        val notificationPanel =
+            notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
+        unbindKeyguardBottomArea(notificationPanel)
+        bindIndicationArea()
+        bindLockIconView(notificationPanel)
+        bindKeyguardStatusView(notificationPanel)
+        setupNotificationStackScrollLayout(notificationPanel)
+        bindLeftShortcut()
+        bindRightShortcut()
+        bindAmbientIndicationArea()
+        bindSettingsPopupMenu()
+
+        KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel)
+        keyguardBlueprintCommandListener.start()
+    }
+
+    fun setupNotificationStackScrollLayout(legacyParent: ViewGroup) {
+        if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+            // This moves the existing NSSL view to a different parent, as the controller is a
+            // singleton and recreating it has other bad side effects
+            val nssl =
+                legacyParent.requireViewById<View>(R.id.notification_stack_scroller).also {
+                    (it.getParent() as ViewGroup).removeView(it)
+                }
+            sharedNotificationContainer.addNotificationStackScrollLayout(nssl)
+            SharedNotificationContainerBinder.bind(
+                sharedNotificationContainer,
+                sharedNotificationContainerViewModel,
+            )
+        }
+    }
+
+    override fun onConfigurationChanged(newConfig: Configuration?) {
+        super.onConfigurationChanged(newConfig)
+        leftShortcutHandle?.onConfigurationChanged()
+        rightShortcutHandle?.onConfigurationChanged()
+        ambientIndicationAreaHandle?.onConfigurationChanged()
+    }
+
+    fun bindIndicationArea() {
+        indicationAreaHandle?.dispose()
+
+        if (!featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let {
+                keyguardRootView.removeView(it)
+            }
+        }
+
+        indicationAreaHandle =
+            KeyguardIndicationAreaBinder.bind(
+                notificationShadeWindowView,
+                keyguardIndicationAreaViewModel,
+                keyguardRootViewModel,
+                indicationController,
+                featureFlags,
+            )
+    }
+
+    private fun bindKeyguardRootView() {
+        rootViewHandle?.dispose()
+        rootViewHandle =
+            KeyguardRootViewBinder.bind(
+                keyguardRootView,
+                keyguardRootViewModel,
+                featureFlags,
+                occludingAppDeviceEntryMessageViewModel,
+                chipbarCoordinator,
+                keyguardStateController,
+            )
+    }
+
+    private fun bindLockIconView(legacyParent: ViewGroup) {
+        if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+            legacyParent.requireViewById<View>(R.id.lock_icon_view).let {
+                legacyParent.removeView(it)
+            }
+        } else {
+            keyguardRootView.findViewById<View?>(R.id.lock_icon_view)?.let {
+                keyguardRootView.removeView(it)
+            }
+        }
+    }
+
+    private fun bindAmbientIndicationArea() {
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            ambientIndicationAreaHandle?.destroy()
+            ambientIndicationAreaHandle =
+                KeyguardAmbientIndicationAreaViewBinder.bind(
+                    notificationShadeWindowView,
+                    keyguardAmbientIndicationViewModel,
+                    keyguardRootViewModel,
+                )
+        } else {
+            keyguardRootView.findViewById<View?>(R.id.ambient_indication_container)?.let {
+                keyguardRootView.removeView(it)
+            }
+        }
+    }
+
+    private fun bindSettingsPopupMenu() {
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            settingsPopupMenuHandle?.dispose()
+            settingsPopupMenuHandle =
+                KeyguardSettingsViewBinder.bind(
+                    keyguardRootView,
+                    keyguardSettingsMenuViewModel,
+                    vibratorHelper,
+                    activityStarter,
+                )
+        } else {
+            keyguardRootView.findViewById<View?>(R.id.keyguard_settings_button)?.let {
+                keyguardRootView.removeView(it)
+            }
+        }
+    }
+
+    private fun unbindKeyguardBottomArea(legacyParent: ViewGroup) {
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            legacyParent.requireViewById<View>(R.id.keyguard_bottom_area).let {
+                legacyParent.removeView(it)
+            }
+        }
+    }
+
+    private fun bindLeftShortcut() {
+        leftShortcutHandle?.destroy()
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            leftShortcutHandle =
+                KeyguardQuickAffordanceViewBinder.bind(
+                    keyguardRootView.requireViewById(R.id.start_button),
+                    keyguardQuickAffordancesCombinedViewModel.startButton,
+                    keyguardRootViewModel.alpha,
+                    falsingManager,
+                    vibratorHelper,
+                ) {
+                    indicationController.showTransientIndication(it)
+                }
+        } else {
+            keyguardRootView.findViewById<View?>(R.id.start_button)?.let {
+                keyguardRootView.removeView(it)
+            }
+        }
+    }
+
+    private fun bindRightShortcut() {
+        rightShortcutHandle?.destroy()
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            rightShortcutHandle =
+                KeyguardQuickAffordanceViewBinder.bind(
+                    keyguardRootView.requireViewById(R.id.end_button),
+                    keyguardQuickAffordancesCombinedViewModel.endButton,
+                    keyguardRootViewModel.alpha,
+                    falsingManager,
+                    vibratorHelper,
+                ) {
+                    indicationController.showTransientIndication(it)
+                }
+        } else {
+            keyguardRootView.findViewById<View?>(R.id.end_button)?.let {
+                keyguardRootView.removeView(it)
+            }
+        }
+    }
+
+    fun bindKeyguardStatusView(legacyParent: ViewGroup) {
+        // At startup, 2 views with the ID `R.id.keyguard_status_view` will be available.
+        // Disable one of them
+        if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            legacyParent.findViewById<View>(R.id.keyguard_status_view)?.let {
+                legacyParent.removeView(it)
+            }
+
+            val keyguardStatusView = keyguardRootView.addStatusView()
+            val statusViewComponent = keyguardStatusViewComponentFactory.build(keyguardStatusView)
+            val controller = statusViewComponent.getKeyguardStatusViewController()
+            controller.init()
+            keyguardStatusViewController = controller
+        } else {
+            keyguardRootView.findViewById<View?>(R.id.keyguard_status_view)?.let {
+                keyguardRootView.removeView(it)
+            }
+        }
+    }
+
+    /**
+     * Temporary, to allow NotificationPanelViewController to use the same instance while code is
+     * migrated: b/288242803
+     */
+    fun getKeyguardStatusViewController() = keyguardStatusViewController
+    fun getKeyguardRootView() = keyguardRootView
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 49ae876..a55cfa7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -17,6 +17,8 @@
 package com.android.systemui.keyguard;
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
+import static android.provider.Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT;
+import static android.provider.Settings.System.LOCKSCREEN_SOUNDS_ENABLED;
 import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
@@ -73,7 +75,6 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -163,10 +164,13 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.settings.SystemSettings;
+import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -176,6 +180,7 @@
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
+import dagger.Lazy;
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -229,7 +234,7 @@
 
     private final static String TAG = "KeyguardViewMediator";
 
-    private static final String DELAYED_KEYGUARD_ACTION =
+    public static final String DELAYED_KEYGUARD_ACTION =
         "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD";
     private static final String DELAYED_LOCK_PROFILE_ACTION =
             "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_LOCK";
@@ -280,7 +285,7 @@
      * turning on the keyguard (i.e, the user has this much time to turn
      * the screen back on without having to face the keyguard).
      */
-    private static final int KEYGUARD_LOCK_AFTER_DELAY_DEFAULT = 5000;
+    public static final int KEYGUARD_LOCK_AFTER_DELAY_DEFAULT = 5000;
 
     /**
      * How long we'll wait for the {@link ViewMediatorCallback#keyguardDoneDrawing()}
@@ -305,6 +310,8 @@
     public static final String SYS_BOOT_REASON_PROP = "sys.boot.reason.last";
     public static final String REBOOT_MAINLINE_UPDATE = "reboot,mainline_update";
     private final DreamOverlayStateController mDreamOverlayStateController;
+    private final JavaAdapter mJavaAdapter;
+    private final WallpaperRepository mWallpaperRepository;
 
     /** The stream type that the lock sounds are tied to. */
     private int mUiSoundsStreamType;
@@ -339,6 +346,9 @@
 
     /** UserSwitcherController for creating guest user on boot complete */
     private final UserSwitcherController mUserSwitcherController;
+    private final SecureSettings mSecureSettings;
+    private final SystemSettings mSystemSettings;
+    private final SystemClock mSystemClock;
     private SystemPropertiesHelper mSystemPropertiesHelper;
 
     /**
@@ -598,17 +608,9 @@
         @Override
         public void onUserSwitching(int userId) {
             if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
-            // Note that the mLockPatternUtils user has already been updated from setCurrentUser.
-            // We need to force a reset of the views, since lockNow (called by
-            // ActivityManagerService) will not reconstruct the keyguard if it is already showing.
             synchronized (KeyguardViewMediator.this) {
                 resetKeyguardDonePendingLocked();
-                if (mLockPatternUtils.isLockScreenDisabled(userId)) {
-                    // If we are switching to a user that has keyguard disabled, dismiss keyguard.
-                    dismiss(null /* callback */, null /* message */);
-                } else {
-                    resetStateLocked();
-                }
+                dismiss(null /* callback */, null /* message */);
                 adjustStatusBarLocked();
             }
         }
@@ -616,16 +618,9 @@
         @Override
         public void onUserSwitchComplete(int userId) {
             if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
-            if (userId != UserHandle.USER_SYSTEM) {
-                UserInfo info = UserManager.get(mContext).getUserInfo(userId);
-                // Don't try to dismiss if the user has Pin/Pattern/Password set
-                if (info == null || mLockPatternUtils.isSecure(userId)) {
-                    return;
-                } else if (info.isGuest() || info.isDemo()) {
-                    // If we just switched to a guest, try to dismiss keyguard.
-                    dismiss(null /* callback */, null /* message */);
-                }
-            }
+            // We are calling dismiss again and with a delay as there are race conditions
+            // in some scenarios caused by async layout listeners
+            mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500);
         }
 
         @Override
@@ -839,7 +834,7 @@
                             .notifyKeyguardAuthenticated(!mWakeAndUnlocking);
                 } else {
                     Log.d(TAG, "keyguard gone, waking up from dream");
-                    mPM.wakeUp(SystemClock.uptimeMillis(),
+                    mPM.wakeUp(mSystemClock.uptimeMillis(),
                             mWakeAndUnlocking ? PowerManager.WAKE_REASON_BIOMETRIC
                             : PowerManager.WAKE_REASON_GESTURE,
                             "com.android.systemui:UNLOCK_DREAMING");
@@ -869,6 +864,11 @@
         }
 
         @Override
+        public void onBouncerSwipeDown() {
+            mKeyguardViewControllerLazy.get().reset(/* hideBouncerWhenShowing= */ true);
+        }
+
+        @Override
         public void playTrustedSound() {
             KeyguardViewMediator.this.playTrustedSound();
         }
@@ -1034,12 +1034,19 @@
                 IRemoteAnimationFinishedCallback finishedCallback) {
             Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
             startKeyguardExitAnimation(transit, apps, wallpapers, nonApps, finishedCallback);
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationStart(
+                        transit, apps, wallpapers, nonApps, finishedCallback);
+            }
             Trace.endSection();
         }
 
         @Override // Binder interface
         public void onAnimationCancelled() {
             cancelKeyguardExitAnimation();
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationCancelled();
+            }
         }
     };
 
@@ -1105,7 +1112,7 @@
 
                         mOccludeByDreamAnimator = ValueAnimator.ofFloat(0f, 1f);
                         mOccludeByDreamAnimator.setDuration(mDreamOpenAnimationDuration);
-                        mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
+                        //mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
                         mOccludeByDreamAnimator.addUpdateListener(
                                 animation -> {
                                     SyncRtSurfaceTransactionApplier.SurfaceParams.Builder
@@ -1334,6 +1341,8 @@
             mDreamingToLockscreenTransitionViewModel;
     private RemoteAnimationTarget mRemoteAnimationTarget;
 
+    private Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+
     /**
      * Injected constructor. See {@link KeyguardModule}.
      */
@@ -1364,14 +1373,20 @@
             KeyguardTransitions keyguardTransitions,
             InteractionJankMonitor interactionJankMonitor,
             DreamOverlayStateController dreamOverlayStateController,
+            JavaAdapter javaAdapter,
+            WallpaperRepository wallpaperRepository,
             Lazy<ShadeController> shadeControllerLazy,
             Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
             Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
             Lazy<ScrimController> scrimControllerLazy,
             FeatureFlags featureFlags,
+            SecureSettings secureSettings,
+            SystemSettings systemSettings,
+            SystemClock systemClock,
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
-            SystemPropertiesHelper systemPropertiesHelper) {
+            SystemPropertiesHelper systemPropertiesHelper,
+            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
         mContext = context;
         mUserTracker = userTracker;
         mFalsingCollector = falsingCollector;
@@ -1385,6 +1400,9 @@
         mPM = powerManager;
         mTrustManager = trustManager;
         mUserSwitcherController = userSwitcherController;
+        mSecureSettings = secureSettings;
+        mSystemSettings = systemSettings;
+        mSystemClock = systemClock;
         mSystemPropertiesHelper = systemPropertiesHelper;
         mStatusBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -1418,6 +1436,8 @@
         mScreenOffAnimationController = screenOffAnimationController;
         mInteractionJankMonitor = interactionJankMonitor;
         mDreamOverlayStateController = dreamOverlayStateController;
+        mJavaAdapter = javaAdapter;
+        mWallpaperRepository = wallpaperRepository;
 
         mActivityLaunchAnimator = activityLaunchAnimator;
         mScrimControllerLazy = scrimControllerLazy;
@@ -1432,12 +1452,13 @@
         mUiEventLogger = uiEventLogger;
         mSessionTracker = sessionTracker;
 
-        mMainDispatcher = mainDispatcher;
         mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
+        mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager;
+        mMainDispatcher = mainDispatcher;
     }
 
     public void userActivity() {
-        mPM.userActivity(SystemClock.uptimeMillis(), false);
+        mPM.userActivity(mSystemClock.uptimeMillis(), false);
     }
 
     private void setupLocked() {
@@ -1520,6 +1541,10 @@
                 com.android.internal.R.anim.lock_screen_behind_enter);
 
         mWorkLockController = new WorkLockActivityController(mContext, mUserTracker);
+
+        mJavaAdapter.alwaysCollectFlow(
+                mWallpaperRepository.getWallpaperSupportsAmbientMode(),
+                this::setWallpaperSupportsAmbientMode);
     }
 
     // TODO(b/273443374) remove, temporary util to get a feature flag
@@ -1642,7 +1667,7 @@
 
             if (cameraGestureTriggered) {
                 // Just to make sure, make sure the device is awake.
-                mContext.getSystemService(PowerManager.class).wakeUp(SystemClock.uptimeMillis(),
+                mContext.getSystemService(PowerManager.class).wakeUp(mSystemClock.uptimeMillis(),
                         PowerManager.WAKE_REASON_CAMERA_LAUNCH,
                         "com.android.systemui:CAMERA_GESTURE_PREVENT_LOCK");
                 setPendingLock(false);
@@ -1739,12 +1764,11 @@
         // to enable it a bit later (i.e, give the user a chance
         // to turn the screen back on within a certain window without
         // having to unlock the screen)
-        final ContentResolver cr = mContext.getContentResolver();
 
         // From SecuritySettings
-        final long lockAfterTimeout = Settings.Secure.getIntForUser(cr,
-                Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
-                KEYGUARD_LOCK_AFTER_DELAY_DEFAULT, userId);
+        final long lockAfterTimeout = mSecureSettings.getIntForUser(LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+                KEYGUARD_LOCK_AFTER_DELAY_DEFAULT,
+                userId);
 
         // From DevicePolicyAdmin
         final long policyTimeout = mLockPatternUtils.getDevicePolicyManager()
@@ -1756,8 +1780,9 @@
             timeout = lockAfterTimeout;
         } else {
             // From DisplaySettings
-            long displayTimeout = Settings.System.getIntForUser(cr, SCREEN_OFF_TIMEOUT,
-                    KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT, userId);
+            long displayTimeout = mSystemSettings.getIntForUser(SCREEN_OFF_TIMEOUT,
+                    KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT,
+                    userId);
 
             // policy in effect. Make sure we don't go beyond policy limit.
             displayTimeout = Math.max(displayTimeout, 0); // ignore negative values
@@ -1778,7 +1803,7 @@
 
     private void doKeyguardLaterLocked(long timeout) {
         // Lock in the future
-        long when = SystemClock.elapsedRealtime() + timeout;
+        long when = mSystemClock.elapsedRealtime() + timeout;
         Intent intent = new Intent(DELAYED_KEYGUARD_ACTION);
         intent.setPackage(mContext.getPackageName());
         intent.putExtra("seq", mDelayedShowingSequence);
@@ -1800,7 +1825,7 @@
                 if (userTimeout == 0) {
                     doKeyguardForChildProfilesLocked();
                 } else {
-                    long userWhen = SystemClock.elapsedRealtime() + userTimeout;
+                    long userWhen = mSystemClock.elapsedRealtime() + userTimeout;
                     Intent lockIntent = new Intent(DELAYED_LOCK_PROFILE_ACTION);
                     lockIntent.setPackage(mContext.getPackageName());
                     lockIntent.putExtra("seq", mDelayedProfileShowingSequence);
@@ -2423,58 +2448,72 @@
     private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
         @Override
         public void handleMessage(Message msg) {
+            String message = "";
             switch (msg.what) {
                 case SHOW:
+                    message = "SHOW";
                     handleShow((Bundle) msg.obj);
                     break;
                 case HIDE:
+                    message = "HIDE";
                     handleHide();
                     break;
                 case RESET:
+                    message = "RESET";
                     handleReset(msg.arg1 != 0);
                     break;
                 case VERIFY_UNLOCK:
+                    message = "VERIFY_UNLOCK";
                     Trace.beginSection("KeyguardViewMediator#handleMessage VERIFY_UNLOCK");
                     handleVerifyUnlock();
                     Trace.endSection();
                     break;
                 case NOTIFY_STARTED_GOING_TO_SLEEP:
+                    message = "NOTIFY_STARTED_GOING_TO_SLEEP";
                     handleNotifyStartedGoingToSleep();
                     break;
                 case NOTIFY_FINISHED_GOING_TO_SLEEP:
+                    message = "NOTIFY_FINISHED_GOING_TO_SLEEP";
                     handleNotifyFinishedGoingToSleep();
                     break;
                 case NOTIFY_STARTED_WAKING_UP:
+                    message = "NOTIFY_STARTED_WAKING_UP";
                     Trace.beginSection(
                             "KeyguardViewMediator#handleMessage NOTIFY_STARTED_WAKING_UP");
                     handleNotifyStartedWakingUp();
                     Trace.endSection();
                     break;
                 case KEYGUARD_DONE:
+                    message = "KEYGUARD_DONE";
                     Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE");
                     handleKeyguardDone();
                     Trace.endSection();
                     break;
                 case KEYGUARD_DONE_DRAWING:
+                    message = "KEYGUARD_DONE_DRAWING";
                     Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE_DRAWING");
                     handleKeyguardDoneDrawing();
                     Trace.endSection();
                     break;
                 case SET_OCCLUDED:
+                    message = "SET_OCCLUDED";
                     Trace.beginSection("KeyguardViewMediator#handleMessage SET_OCCLUDED");
                     handleSetOccluded(msg.arg1 != 0, msg.arg2 != 0);
                     Trace.endSection();
                     break;
                 case KEYGUARD_TIMEOUT:
+                    message = "KEYGUARD_TIMEOUT";
                     synchronized (KeyguardViewMediator.this) {
                         doKeyguardLocked((Bundle) msg.obj);
                     }
                     break;
                 case DISMISS:
-                    final DismissMessage message = (DismissMessage) msg.obj;
-                    handleDismiss(message.getCallback(), message.getMessage());
+                    message = "DISMISS";
+                    final DismissMessage dismissMsg = (DismissMessage) msg.obj;
+                    handleDismiss(dismissMsg.getCallback(), dismissMsg.getMessage());
                     break;
                 case START_KEYGUARD_EXIT_ANIM:
+                    message = "START_KEYGUARD_EXIT_ANIM";
                     Trace.beginSection(
                             "KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
                     synchronized (KeyguardViewMediator.this) {
@@ -2492,21 +2531,25 @@
                     Trace.endSection();
                     break;
                 case CANCEL_KEYGUARD_EXIT_ANIM:
+                    message = "CANCEL_KEYGUARD_EXIT_ANIM";
                     Trace.beginSection(
                             "KeyguardViewMediator#handleMessage CANCEL_KEYGUARD_EXIT_ANIM");
                     handleCancelKeyguardExitAnimation();
                     Trace.endSection();
                     break;
                 case KEYGUARD_DONE_PENDING_TIMEOUT:
+                    message = "KEYGUARD_DONE_PENDING_TIMEOUT";
                     Trace.beginSection("KeyguardViewMediator#handleMessage"
                             + " KEYGUARD_DONE_PENDING_TIMEOUT");
                     Log.w(TAG, "Timeout while waiting for activity drawn!");
                     Trace.endSection();
                     break;
                 case SYSTEM_READY:
+                    message = "SYSTEM_READY";
                     handleSystemReady();
                     break;
             }
+            Log.d(TAG, "KeyguardViewMediator queue processing message: " + message);
         }
     };
 
@@ -2609,9 +2652,7 @@
 
     private void playSound(int soundId) {
         if (soundId == 0) return;
-        final ContentResolver cr = mContext.getContentResolver();
-        int lockscreenSoundsEnabled = Settings.System.getIntForUser(cr,
-                Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1,
+        int lockscreenSoundsEnabled = mSystemSettings.getIntForUser(LOCKSCREEN_SOUNDS_ENABLED, 1,
                 KeyguardUpdateMonitor.getCurrentUser());
         if (lockscreenSoundsEnabled == 1) {
 
@@ -2646,6 +2687,12 @@
             if (DEBUG) {
                 Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
             }
+
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                // Handled in WmLockscreenVisibilityManager if flag is enabled.
+                return;
+            }
+
             try {
                 ActivityTaskManager.getService().setLockScreenShown(showing, aodShowing);
             } catch (RemoteException e) {
@@ -2685,7 +2732,11 @@
             }
             mHiding = false;
 
-            mKeyguardViewControllerLazy.get().show(options);
+            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                // Handled directly in StatusBarKeyguardViewManager if enabled.
+                mKeyguardViewControllerLazy.get().show(options);
+            }
+
             resetKeyguardDonePendingLocked();
             mHideAnimationRun = false;
             adjustStatusBarLocked();
@@ -2756,19 +2807,22 @@
             mUpdateMonitor.setKeyguardGoingAway(true);
             mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(true);
 
-            // Don't actually hide the Keyguard at the moment, wait for window
-            // manager until it tells us it's safe to do so with
-            // startKeyguardExitAnimation.
-            // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager will be in
-            // order.
-            final int keyguardFlag = flags;
-            mUiBgExecutor.execute(() -> {
-                try {
-                    ActivityTaskManager.getService().keyguardGoingAway(keyguardFlag);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error while calling WindowManager", e);
-                }
-            });
+            // Handled in WmLockscreenVisibilityManager if flag is enabled.
+            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                // Don't actually hide the Keyguard at the moment, wait for window manager until it
+                // tells us it's safe to do so with startKeyguardExitAnimation.
+                // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager will be
+                // in order.
+                final int keyguardFlag = flags;
+                mUiBgExecutor.execute(() -> {
+                    try {
+                        ActivityTaskManager.getService().keyguardGoingAway(keyguardFlag);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error while calling WindowManager", e);
+                    }
+                });
+            }
+
             Trace.endSection();
         }
     };
@@ -2834,7 +2888,7 @@
         // It's possible that the device was unlocked (via BOUNCER) while dozing. It's time to
         // wake up.
         if (mAodShowing) {
-            mPM.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+            mPM.wakeUp(mSystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                     "com.android.systemui:BOUNCER_DOZING");
         }
 
@@ -2858,7 +2912,7 @@
                 // TODO(bc-unlock): Fill parameters
                 mNotificationShadeWindowControllerLazy.get().batchApplyWindowLayoutParams(() -> {
                     handleStartKeyguardExitAnimation(
-                            SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
+                            mSystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
                             mHideAnimation.getDuration(), null /* apps */, null /* wallpapers */,
                             null /* nonApps */, null /* finishedCallback */);
                 });
@@ -2882,7 +2936,10 @@
             if (!mHiding
                     && !mSurfaceBehindRemoteAnimationRequested
                     && !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) {
-                if (finishedCallback != null) {
+                // If the flag is enabled, remote animation state is handled in
+                // WmLockscreenVisibilityManager.
+                if (finishedCallback != null
+                        && !mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
                     // There will not execute animation, send a finish callback to ensure the remote
                     // animation won't hang there.
                     try {
@@ -2908,10 +2965,12 @@
                         new IRemoteAnimationFinishedCallback() {
                             @Override
                             public void onAnimationFinished() throws RemoteException {
-                                try {
-                                    finishedCallback.onAnimationFinished();
-                                } catch (RemoteException e) {
-                                    Slog.w(TAG, "Failed to call onAnimationFinished", e);
+                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                    try {
+                                        finishedCallback.onAnimationFinished();
+                                    } catch (RemoteException e) {
+                                        Slog.w(TAG, "Failed to call onAnimationFinished", e);
+                                    }
                                 }
                                 onKeyguardExitFinished();
                                 mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
@@ -2938,7 +2997,11 @@
             // it will dismiss the panel in that case.
             } else if (!mStatusBarStateController.leaveOpenOnKeyguardHide()
                     && apps != null && apps.length > 0) {
-                mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
+                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    // Handled in WmLockscreenVisibilityManager. Other logic in this class will
+                    // short circuit when this is null.
+                    mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
+                }
                 mSurfaceBehindRemoteAnimationRunning = true;
 
                 mInteractionJankMonitor.begin(
@@ -2958,7 +3021,10 @@
                         createInteractionJankMonitorConf(
                                 CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled"));
 
-                mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
+                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    // Handled directly in StatusBarKeyguardViewManager if enabled.
+                    mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
+                }
 
                 // TODO(bc-animation): When remote animation is enabled for keyguard exit animation,
                 // apps, wallpapers and finishedCallback are set to non-null. nonApps is not yet
@@ -2972,13 +3038,17 @@
                     }
                     if (apps == null || apps.length == 0) {
                         Slog.e(TAG, "Keyguard exit without a corresponding app to show.");
+
                         try {
-                            finishedCallback.onAnimationFinished();
+                            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                finishedCallback.onAnimationFinished();
+                            }
                         } catch (RemoteException e) {
                             Slog.e(TAG, "RemoteException");
                         } finally {
                             mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
                         }
+
                         return;
                     }
 
@@ -3002,7 +3072,9 @@
                         @Override
                         public void onAnimationEnd(Animator animation) {
                             try {
-                                finishedCallback.onAnimationFinished();
+                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                    finishedCallback.onAnimationFinished();
+                                }
                             } catch (RemoteException e) {
                                 Slog.e(TAG, "RemoteException");
                             } finally {
@@ -3013,7 +3085,9 @@
                         @Override
                         public void onAnimationCancel(Animator animation) {
                             try {
-                                finishedCallback.onAnimationFinished();
+                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                    finishedCallback.onAnimationFinished();
+                                }
                             } catch (RemoteException e) {
                                 Slog.e(TAG, "RemoteException");
                             } finally {
@@ -3162,7 +3236,10 @@
                 flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
             }
 
-            ActivityTaskManager.getService().keyguardGoingAway(flags);
+            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                // Handled in WmLockscreenVisibilityManager.
+                ActivityTaskManager.getService().keyguardGoingAway(flags);
+            }
             mKeyguardStateController.notifyKeyguardGoingAway(true);
         } catch (RemoteException e) {
             mSurfaceBehindRemoteAnimationRequested = false;
@@ -3256,7 +3333,8 @@
                 flags |= StatusBarManager.DISABLE_RECENT;
             }
 
-            if (mPowerGestureIntercepted && mOccluded && isSecure()) {
+            if (mPowerGestureIntercepted && mOccluded && isSecure()
+                    && mUpdateMonitor.isFaceEnrolled()) {
                 flags |= StatusBarManager.DISABLE_RECENT;
             }
 
@@ -3536,7 +3614,7 @@
      * In case it does support it, we have to fade in the incoming app, otherwise we'll reveal it
      * with the light reveal scrim.
      */
-    public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
+    private void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
         mWallpaperSupportsAmbientMode = supportsAmbientMode;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
index 1978b3d..039460d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -61,28 +61,23 @@
 
     override fun start() {
         Log.d(LOG_TAG, "Resource trimmer registered.")
-        if (
-            !(featureFlags.isEnabled(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) ||
-                featureFlags.isEnabled(Flags.TRIM_FONT_CACHES_AT_UNLOCK))
-        ) {
-            return
-        }
-
-        applicationScope.launch(bgDispatcher) {
-            // We need to wait for the AoD transition (and animation) to complete.
-            // This means we're waiting for isDreaming (== implies isDoze) and dozeAmount == 1f
-            // signal. This is to make sure we don't clear font caches during animation which
-            // would jank and leave stale data in memory.
-            val isDozingFully =
-                keyguardInteractor.dozeAmount.map { it == 1f }.distinctUntilChanged()
-            combine(
-                    keyguardInteractor.wakefulnessModel.map { it.state },
-                    keyguardInteractor.isDreaming,
-                    isDozingFully,
-                    ::Triple
-                )
-                .distinctUntilChanged()
-                .collect { onWakefulnessUpdated(it.first, it.second, it.third) }
+        if (featureFlags.isEnabled(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)) {
+            applicationScope.launch(bgDispatcher) {
+                // We need to wait for the AoD transition (and animation) to complete.
+                // This means we're waiting for isDreaming (== implies isDoze) and dozeAmount == 1f
+                // signal. This is to make sure we don't clear font caches during animation which
+                // would jank and leave stale data in memory.
+                val isDozingFully =
+                    keyguardInteractor.dozeAmount.map { it == 1f }.distinctUntilChanged()
+                combine(
+                        keyguardInteractor.wakefulnessModel.map { it.state },
+                        keyguardInteractor.isDreaming,
+                        isDozingFully,
+                        ::Triple
+                    )
+                    .distinctUntilChanged()
+                    .collect { onWakefulnessUpdated(it.first, it.second, it.third) }
+            }
         }
 
         applicationScope.launch(bgDispatcher) {
@@ -97,17 +92,16 @@
 
     @WorkerThread
     private fun onKeyguardGone() {
-        if (!featureFlags.isEnabled(Flags.TRIM_FONT_CACHES_AT_UNLOCK)) {
-            return
-        }
-
-        if (DEBUG) {
-            Log.d(LOG_TAG, "Trimming font caches since keyguard went away.")
-        }
         // We want to clear temporary caches we've created while rendering and animating
         // lockscreen elements, especially clocks.
+        Log.d(LOG_TAG, "Sending TRIM_MEMORY_UI_HIDDEN.")
         globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
-        globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
+        if (featureFlags.isEnabled(Flags.TRIM_FONT_CACHES_AT_UNLOCK)) {
+            if (DEBUG) {
+                Log.d(LOG_TAG, "Trimming font caches since keyguard went away.")
+            }
+            globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
+        }
     }
 
     @WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
new file mode 100644
index 0000000..75677f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import android.app.IActivityTaskManager
+import android.util.Log
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.RemoteAnimationTarget
+import android.view.WindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Manages lockscreen and AOD visibility state via the [IActivityTaskManager], and keeps track of
+ * remote animations related to changes in lockscreen visibility.
+ */
+@SysUISingleton
+class WindowManagerLockscreenVisibilityManager
+@Inject
+constructor(
+    @Main private val executor: Executor,
+    private val activityTaskManagerService: IActivityTaskManager,
+    private val keyguardStateController: KeyguardStateController,
+    private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
+) {
+
+    /**
+     * Whether the lockscreen is showing, which we pass to [IActivityTaskManager.setLockScreenShown]
+     * in order to show the lockscreen and hide the surface behind the keyguard (or the inverse).
+     */
+    private var isLockscreenShowing = true
+
+    /**
+     * Whether AOD is showing, which we pass to [IActivityTaskManager.setLockScreenShown] in order
+     * to show AOD when the lockscreen is visible.
+     */
+    private var isAodVisible = false
+
+    /**
+     * Whether the keyguard is currently "going away", which we triggered via a call to
+     * [IActivityTaskManager.keyguardGoingAway]. When we tell WM that the keyguard is going away,
+     * the app/launcher surface behind the keyguard is made visible, and WM calls
+     * [onKeyguardGoingAwayRemoteAnimationStart] with a RemoteAnimationTarget so that we can animate
+     * it.
+     *
+     * Going away does not inherently result in [isLockscreenShowing] being set to false; we need to
+     * do that ourselves once we are done animating the surface.
+     *
+     * THIS IS THE ONLY PLACE 'GOING AWAY' TERMINOLOGY SHOULD BE USED. 'Going away' is a WM concept
+     * and we have gotten into trouble using it to mean various different things in the past. Unlock
+     * animations may still be visible when the keyguard is NOT 'going away', for example, when we
+     * play in-window animations, we set the surface to alpha=1f and end the animation immediately.
+     * The remainder of the animation occurs in-window, so while you might expect that the keyguard
+     * is still 'going away' because unlock animations are playing, it's actually not.
+     *
+     * If you want to know if the keyguard is 'going away', you probably want to check if we have
+     * STARTED but not FINISHED a transition to GONE.
+     *
+     * The going away animation will run until:
+     * - We manually call [endKeyguardGoingAwayAnimation] after we're done animating.
+     * - We call [setLockscreenShown] = true, which cancels the going away animation.
+     * - WM calls [onKeyguardGoingAwayRemoteAnimationCancelled] for another reason (such as the 10
+     *   second timeout).
+     */
+    private var isKeyguardGoingAway = false
+        private set(value) {
+            // TODO(b/278086361): Extricate the keyguard state controller.
+            keyguardStateController.notifyKeyguardGoingAway(value)
+            field = value
+        }
+
+    /** Callback provided by WM to call once we're done with the going away animation. */
+    private var goingAwayRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
+
+    /**
+     * Set the visibility of the surface behind the keyguard, making the appropriate calls to Window
+     * Manager to effect the change.
+     */
+    fun setSurfaceBehindVisibility(visible: Boolean) {
+        if (isKeyguardGoingAway == visible) {
+            Log.d(TAG, "WmLockscreenVisibilityManager#setVisibility -> already visible=$visible")
+            return
+        }
+
+        // The surface behind is always visible if the lockscreen is not showing, so we're already
+        // visible.
+        if (visible && !isLockscreenShowing) {
+            Log.d(TAG, "#setVisibility -> already visible since the lockscreen isn't showing")
+            return
+        }
+
+        if (visible) {
+            // Make the surface visible behind the keyguard by calling keyguardGoingAway. The
+            // lockscreen is still showing as well, allowing us to animate unlocked.
+            Log.d(TAG, "ActivityTaskManagerService#keyguardGoingAway()")
+            activityTaskManagerService.keyguardGoingAway(0)
+            isKeyguardGoingAway = true
+        } else {
+            // Hide the surface by setting the lockscreen showing.
+            setLockscreenShown(true)
+        }
+    }
+
+    fun setAodVisible(aodVisible: Boolean) {
+        setWmLockscreenState(aodVisible = aodVisible)
+    }
+
+    /** Sets the visibility of the lockscreen. */
+    fun setLockscreenShown(lockscreenShown: Boolean) {
+        setWmLockscreenState(lockscreenShowing = lockscreenShown)
+    }
+
+    fun onKeyguardGoingAwayRemoteAnimationStart(
+        @WindowManager.TransitionOldType transit: Int,
+        apps: Array<RemoteAnimationTarget>,
+        wallpapers: Array<RemoteAnimationTarget>,
+        nonApps: Array<RemoteAnimationTarget>,
+        finishedCallback: IRemoteAnimationFinishedCallback
+    ) {
+        goingAwayRemoteAnimationFinishedCallback = finishedCallback
+        keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
+    }
+
+    fun onKeyguardGoingAwayRemoteAnimationCancelled() {
+        // If WM cancelled the animation, we need to end immediately even if we're still using the
+        // animation.
+        endKeyguardGoingAwayAnimation()
+    }
+
+    /**
+     * Whether the going away remote animation target is in-use, which means we're animating it or
+     * intend to animate it.
+     *
+     * Some unlock animations (such as the translation spring animation) are non-deterministic and
+     * might end after the transition to GONE ends. In that case, we want to keep the remote
+     * animation running until the spring ends.
+     */
+    fun setUsingGoingAwayRemoteAnimation(usingTarget: Boolean) {
+        if (!usingTarget) {
+            endKeyguardGoingAwayAnimation()
+        }
+    }
+
+    private fun setWmLockscreenState(
+        lockscreenShowing: Boolean = this.isLockscreenShowing,
+        aodVisible: Boolean = this.isAodVisible
+    ) {
+        Log.d(
+            TAG,
+            "#setWmLockscreenState(" +
+                "isLockscreenShowing=$lockscreenShowing, " +
+                "aodVisible=$aodVisible)."
+        )
+
+        if (this.isLockscreenShowing == lockscreenShowing && this.isAodVisible == aodVisible) {
+            return
+        }
+
+        activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible)
+        this.isLockscreenShowing = lockscreenShowing
+        this.isAodVisible = aodVisible
+    }
+
+    private fun endKeyguardGoingAwayAnimation() {
+        if (!isKeyguardGoingAway) {
+            Log.d(
+                TAG,
+                "#endKeyguardGoingAwayAnimation() called when isKeyguardGoingAway=false. " +
+                    "Short-circuiting."
+            )
+            return
+        }
+
+        executor.execute {
+            Log.d(TAG, "Finishing remote animation.")
+            goingAwayRemoteAnimationFinishedCallback?.onAnimationFinished()
+            goingAwayRemoteAnimationFinishedCallback = null
+
+            isKeyguardGoingAway = false
+
+            keyguardSurfaceBehindAnimator.notifySurfaceReleased()
+        }
+    }
+
+    companion object {
+        private val TAG = this::class.java.simpleName
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactory.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactory.kt
deleted file mode 100644
index 11bc88a..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactory.kt
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.bouncer.data.factory
-
-import android.annotation.IntDef
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FACE_LOCKED_OUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FINGERPRINT_LOCKED_OUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FACE_INPUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.R.string.keyguard_enter_password
-import com.android.systemui.R.string.keyguard_enter_pattern
-import com.android.systemui.R.string.keyguard_enter_pin
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import com.android.systemui.keyguard.bouncer.shared.model.Message
-import javax.inject.Inject
-
-@SysUISingleton
-class BouncerMessageFactory
-@Inject
-constructor(
-    private val updateMonitor: KeyguardUpdateMonitor,
-    private val securityModel: KeyguardSecurityModel,
-) {
-
-    fun createFromPromptReason(
-        @BouncerPromptReason reason: Int,
-        userId: Int,
-    ): BouncerMessageModel? {
-        val pair =
-            getBouncerMessage(
-                reason,
-                securityModel.getSecurityMode(userId),
-                updateMonitor.isFingerprintAllowedInBouncer
-            )
-        return pair?.let {
-            BouncerMessageModel(
-                message = Message(messageResId = pair.first),
-                secondaryMessage = Message(messageResId = pair.second)
-            )
-        }
-    }
-
-    fun createFromString(
-        primaryMsg: String? = null,
-        secondaryMsg: String? = null
-    ): BouncerMessageModel =
-        BouncerMessageModel(
-            message = primaryMsg?.let { Message(message = it) },
-            secondaryMessage = secondaryMsg?.let { Message(message = it) },
-        )
-
-    /**
-     * Helper method that provides the relevant bouncer message that should be shown for different
-     * scenarios indicated by [reason]. [securityMode] & [fpAllowedInBouncer] parameters are used to
-     * provide a more specific message.
-     */
-    private fun getBouncerMessage(
-        @BouncerPromptReason reason: Int,
-        securityMode: SecurityMode,
-        fpAllowedInBouncer: Boolean = false
-    ): Pair<Int, Int>? {
-        return when (reason) {
-            PROMPT_REASON_RESTART -> authRequiredAfterReboot(securityMode)
-            PROMPT_REASON_TIMEOUT -> authRequiredAfterPrimaryAuthTimeout(securityMode)
-            PROMPT_REASON_DEVICE_ADMIN -> authRequiredAfterAdminLockdown(securityMode)
-            PROMPT_REASON_USER_REQUEST -> authRequiredAfterUserLockdown(securityMode)
-            PROMPT_REASON_AFTER_LOCKOUT -> biometricLockout(securityMode)
-            PROMPT_REASON_PREPARE_FOR_UPDATE -> authRequiredForUnattendedUpdate(securityMode)
-            PROMPT_REASON_FINGERPRINT_LOCKED_OUT -> fingerprintUnlockUnavailable(securityMode)
-            PROMPT_REASON_FACE_LOCKED_OUT -> faceUnlockUnavailable(securityMode)
-            PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT ->
-                if (fpAllowedInBouncer) incorrectSecurityInputWithFingerprint(securityMode)
-                else incorrectSecurityInput(securityMode)
-            PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT ->
-                if (fpAllowedInBouncer) nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
-                else nonStrongAuthTimeout(securityMode)
-            PROMPT_REASON_TRUSTAGENT_EXPIRED ->
-                if (fpAllowedInBouncer) trustAgentDisabledWithFingerprintAllowed(securityMode)
-                else trustAgentDisabled(securityMode)
-            PROMPT_REASON_INCORRECT_FACE_INPUT ->
-                if (fpAllowedInBouncer) incorrectFaceInputWithFingerprintAllowed(securityMode)
-                else incorrectFaceInput(securityMode)
-            PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT -> incorrectFingerprintInput(securityMode)
-            PROMPT_REASON_DEFAULT ->
-                if (fpAllowedInBouncer) defaultMessageWithFingerprint(securityMode)
-                else defaultMessage(securityMode)
-            PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT -> primaryAuthLockedOut(securityMode)
-            else -> null
-        }
-    }
-}
-
-@Retention(AnnotationRetention.SOURCE)
-@IntDef(
-    PROMPT_REASON_TIMEOUT,
-    PROMPT_REASON_DEVICE_ADMIN,
-    PROMPT_REASON_USER_REQUEST,
-    PROMPT_REASON_AFTER_LOCKOUT,
-    PROMPT_REASON_PREPARE_FOR_UPDATE,
-    PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT,
-    PROMPT_REASON_TRUSTAGENT_EXPIRED,
-    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-    PROMPT_REASON_INCORRECT_FACE_INPUT,
-    PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT,
-    PROMPT_REASON_FACE_LOCKED_OUT,
-    PROMPT_REASON_FINGERPRINT_LOCKED_OUT,
-    PROMPT_REASON_DEFAULT,
-    PROMPT_REASON_NONE,
-    PROMPT_REASON_RESTART,
-    PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT,
-)
-annotation class BouncerPromptReason
-
-private fun defaultMessage(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun defaultMessageWithFingerprint(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun incorrectSecurityInput(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun incorrectFingerprintInput(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun incorrectFaceInput(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun incorrectFaceInputWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun biometricLockout(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun authRequiredAfterReboot(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun nonStrongAuthTimeout(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun faceUnlockUnavailable(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun fingerprintUnlockUnavailable(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun trustAgentDisabled(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun trustAgentDisabledWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
-
-private fun primaryAuthLockedOut(securityMode: SecurityMode): Pair<Int, Int> {
-    return when (securityMode) {
-        SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
-        SecurityMode.Password -> Pair(keyguard_enter_password, 0)
-        SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
-        else -> Pair(0, 0)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepository.kt
deleted file mode 100644
index c4400bc..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepository.kt
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.bouncer.data.repository
-
-import android.hardware.biometrics.BiometricSourceType
-import android.hardware.biometrics.BiometricSourceType.FACE
-import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FACE_LOCKED_OUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FINGERPRINT_LOCKED_OUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FACE_INPUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitorCallback
-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.keyguard.bouncer.data.factory.BouncerMessageFactory
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.user.data.repository.UserRepository
-import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-
-/** Provide different sources of messages that needs to be shown on the bouncer. */
-interface BouncerMessageRepository {
-    /**
-     * Messages that are shown in response to the incorrect security attempts on the bouncer and
-     * primary authentication method being locked out, along with countdown messages before primary
-     * auth is active again.
-     */
-    val primaryAuthMessage: Flow<BouncerMessageModel?>
-
-    /**
-     * Help messages that are shown to the user on how to successfully perform authentication using
-     * face.
-     */
-    val faceAcquisitionMessage: Flow<BouncerMessageModel?>
-
-    /**
-     * Help messages that are shown to the user on how to successfully perform authentication using
-     * fingerprint.
-     */
-    val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?>
-
-    /** Custom message that is displayed when the bouncer is being shown to launch an app. */
-    val customMessage: Flow<BouncerMessageModel?>
-
-    /**
-     * Messages that are shown in response to biometric authentication attempts through face or
-     * fingerprint.
-     */
-    val biometricAuthMessage: Flow<BouncerMessageModel?>
-
-    /** Messages that are shown when certain auth flags are set. */
-    val authFlagsMessage: Flow<BouncerMessageModel?>
-
-    /** Messages that are show after biometrics are locked out temporarily or permanently */
-    val biometricLockedOutMessage: Flow<BouncerMessageModel?>
-
-    /** Set the value for [primaryAuthMessage] */
-    fun setPrimaryAuthMessage(value: BouncerMessageModel?)
-
-    /** Set the value for [faceAcquisitionMessage] */
-    fun setFaceAcquisitionMessage(value: BouncerMessageModel?)
-    /** Set the value for [fingerprintAcquisitionMessage] */
-    fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?)
-
-    /** Set the value for [customMessage] */
-    fun setCustomMessage(value: BouncerMessageModel?)
-
-    /**
-     * Clear any previously set messages for [primaryAuthMessage], [faceAcquisitionMessage],
-     * [fingerprintAcquisitionMessage] & [customMessage]
-     */
-    fun clearMessage()
-}
-
-@SysUISingleton
-class BouncerMessageRepositoryImpl
-@Inject
-constructor(
-    trustRepository: TrustRepository,
-    biometricSettingsRepository: BiometricSettingsRepository,
-    updateMonitor: KeyguardUpdateMonitor,
-    private val bouncerMessageFactory: BouncerMessageFactory,
-    private val userRepository: UserRepository,
-    fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
-) : BouncerMessageRepository {
-
-    private val isFaceEnrolledAndEnabled =
-        and(
-            biometricSettingsRepository.isFaceAuthenticationEnabled,
-            biometricSettingsRepository.isFaceEnrolled
-        )
-
-    private val isFingerprintEnrolledAndEnabled =
-        and(
-            biometricSettingsRepository.isFingerprintEnabledByDevicePolicy,
-            biometricSettingsRepository.isFingerprintEnrolled
-        )
-
-    private val isAnyBiometricsEnabledAndEnrolled =
-        or(isFaceEnrolledAndEnabled, isFingerprintEnrolledAndEnabled)
-
-    private val authFlagsBasedPromptReason: Flow<Int> =
-        combine(
-                biometricSettingsRepository.authenticationFlags,
-                trustRepository.isCurrentUserTrustManaged,
-                isAnyBiometricsEnabledAndEnrolled,
-                ::Triple
-            )
-            .map { (flags, isTrustManaged, biometricsEnrolledAndEnabled) ->
-                val trustOrBiometricsAvailable = (isTrustManaged || biometricsEnrolledAndEnabled)
-                return@map if (
-                    trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
-                ) {
-                    PROMPT_REASON_RESTART
-                } else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
-                    PROMPT_REASON_TIMEOUT
-                } else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
-                    PROMPT_REASON_DEVICE_ADMIN
-                } else if (isTrustManaged && flags.someAuthRequiredAfterUserRequest) {
-                    PROMPT_REASON_TRUSTAGENT_EXPIRED
-                } else if (isTrustManaged && flags.someAuthRequiredAfterTrustAgentExpired) {
-                    PROMPT_REASON_TRUSTAGENT_EXPIRED
-                } else if (trustOrBiometricsAvailable && flags.isInUserLockdown) {
-                    PROMPT_REASON_USER_REQUEST
-                } else if (
-                    trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate
-                ) {
-                    PROMPT_REASON_PREPARE_FOR_UPDATE
-                } else if (
-                    trustOrBiometricsAvailable &&
-                        flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
-                ) {
-                    PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
-                } else {
-                    PROMPT_REASON_NONE
-                }
-            }
-
-    private val biometricAuthReason: Flow<Int> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : KeyguardUpdateMonitorCallback() {
-                        override fun onBiometricAuthFailed(
-                            biometricSourceType: BiometricSourceType?
-                        ) {
-                            val promptReason =
-                                if (biometricSourceType == FINGERPRINT)
-                                    PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
-                                else if (
-                                    biometricSourceType == FACE && !updateMonitor.isFaceLockedOut
-                                ) {
-                                    PROMPT_REASON_INCORRECT_FACE_INPUT
-                                } else PROMPT_REASON_NONE
-                            trySendWithFailureLogging(promptReason, TAG, "onBiometricAuthFailed")
-                        }
-
-                        override fun onBiometricsCleared() {
-                            trySendWithFailureLogging(
-                                PROMPT_REASON_NONE,
-                                TAG,
-                                "onBiometricsCleared"
-                            )
-                        }
-
-                        override fun onBiometricAcquired(
-                            biometricSourceType: BiometricSourceType?,
-                            acquireInfo: Int
-                        ) {
-                            trySendWithFailureLogging(
-                                PROMPT_REASON_NONE,
-                                TAG,
-                                "clearBiometricPrompt for new auth session."
-                            )
-                        }
-
-                        override fun onBiometricAuthenticated(
-                            userId: Int,
-                            biometricSourceType: BiometricSourceType?,
-                            isStrongBiometric: Boolean
-                        ) {
-                            trySendWithFailureLogging(
-                                PROMPT_REASON_NONE,
-                                TAG,
-                                "onBiometricAuthenticated"
-                            )
-                        }
-                    }
-                updateMonitor.registerCallback(callback)
-                awaitClose { updateMonitor.removeCallback(callback) }
-            }
-            .distinctUntilChanged()
-
-    private val _primaryAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val primaryAuthMessage: Flow<BouncerMessageModel?> = _primaryAuthMessage
-
-    private val _faceAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val faceAcquisitionMessage: Flow<BouncerMessageModel?> = _faceAcquisitionMessage
-
-    private val _fingerprintAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?> =
-        _fingerprintAcquisitionMessage
-
-    private val _customMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val customMessage: Flow<BouncerMessageModel?> = _customMessage
-
-    override val biometricAuthMessage: Flow<BouncerMessageModel?> =
-        biometricAuthReason
-            .map {
-                if (it == PROMPT_REASON_NONE) null
-                else
-                    bouncerMessageFactory.createFromPromptReason(
-                        it,
-                        userRepository.getSelectedUserInfo().id
-                    )
-            }
-            .onStart { emit(null) }
-            .distinctUntilChanged()
-
-    override val authFlagsMessage: Flow<BouncerMessageModel?> =
-        authFlagsBasedPromptReason
-            .map {
-                if (it == PROMPT_REASON_NONE) null
-                else
-                    bouncerMessageFactory.createFromPromptReason(
-                        it,
-                        userRepository.getSelectedUserInfo().id
-                    )
-            }
-            .onStart { emit(null) }
-            .distinctUntilChanged()
-
-    // TODO (b/262838215): Replace with DeviceEntryFaceAuthRepository when the new face auth system
-    // has been launched.
-    private val faceLockedOut: Flow<Boolean> = conflatedCallbackFlow {
-        val callback =
-            object : KeyguardUpdateMonitorCallback() {
-                override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType?) {
-                    if (biometricSourceType == FACE) {
-                        trySendWithFailureLogging(
-                            updateMonitor.isFaceLockedOut,
-                            TAG,
-                            "face lock out state changed."
-                        )
-                    }
-                }
-            }
-        updateMonitor.registerCallback(callback)
-        trySendWithFailureLogging(updateMonitor.isFaceLockedOut, TAG, "face lockout initial value")
-        awaitClose { updateMonitor.removeCallback(callback) }
-    }
-
-    override val biometricLockedOutMessage: Flow<BouncerMessageModel?> =
-        combine(fingerprintAuthRepository.isLockedOut, faceLockedOut) { fp, face ->
-            return@combine if (fp) {
-                bouncerMessageFactory.createFromPromptReason(
-                    PROMPT_REASON_FINGERPRINT_LOCKED_OUT,
-                    userRepository.getSelectedUserInfo().id
-                )
-            } else if (face) {
-                bouncerMessageFactory.createFromPromptReason(
-                    PROMPT_REASON_FACE_LOCKED_OUT,
-                    userRepository.getSelectedUserInfo().id
-                )
-            } else null
-        }
-
-    override fun setPrimaryAuthMessage(value: BouncerMessageModel?) {
-        _primaryAuthMessage.value = value
-    }
-
-    override fun setFaceAcquisitionMessage(value: BouncerMessageModel?) {
-        _faceAcquisitionMessage.value = value
-    }
-
-    override fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?) {
-        _fingerprintAcquisitionMessage.value = value
-    }
-
-    override fun setCustomMessage(value: BouncerMessageModel?) {
-        _customMessage.value = value
-    }
-
-    override fun clearMessage() {
-        _fingerprintAcquisitionMessage.value = null
-        _faceAcquisitionMessage.value = null
-        _primaryAuthMessage.value = null
-        _customMessage.value = null
-    }
-
-    companion object {
-        const val TAG = "BouncerDetailedMessageRepository"
-    }
-}
-
-private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
-    flow.combine(anotherFlow) { a, b -> a && b }
-
-private fun or(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
-    flow.combine(anotherFlow) { a, b -> a || b }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageAuditLogger.kt
deleted file mode 100644
index 56f81fc..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageAuditLogger.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.bouncer.domain.interactor
-
-import android.os.Build
-import android.util.Log
-import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.launch
-
-private val TAG = BouncerMessageAuditLogger::class.simpleName!!
-
-/** Logger that echoes bouncer messages state to logcat in debuggable builds. */
-@SysUISingleton
-class BouncerMessageAuditLogger
-@Inject
-constructor(
-    @Application private val scope: CoroutineScope,
-    private val repository: BouncerMessageRepository,
-    private val interactor: BouncerMessageInteractor,
-) : CoreStartable {
-    override fun start() {
-        if (Build.isDebuggable()) {
-            collectAndLog(repository.biometricAuthMessage, "biometricMessage: ")
-            collectAndLog(repository.primaryAuthMessage, "primaryAuthMessage: ")
-            collectAndLog(repository.customMessage, "customMessage: ")
-            collectAndLog(repository.faceAcquisitionMessage, "faceAcquisitionMessage: ")
-            collectAndLog(
-                repository.fingerprintAcquisitionMessage,
-                "fingerprintAcquisitionMessage: "
-            )
-            collectAndLog(repository.authFlagsMessage, "authFlagsMessage: ")
-            collectAndLog(interactor.bouncerMessage, "interactor.bouncerMessage: ")
-        }
-    }
-
-    private fun collectAndLog(flow: Flow<BouncerMessageModel?>, context: String) {
-        scope.launch { flow.collect { Log.d(TAG, context + it) } }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractor.kt
deleted file mode 100644
index 1754d93..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.bouncer.domain.interactor
-
-import android.os.CountDownTimer
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
-import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
-import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import com.android.systemui.user.data.repository.UserRepository
-import javax.inject.Inject
-import kotlin.math.roundToInt
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-
-@SysUISingleton
-class BouncerMessageInteractor
-@Inject
-constructor(
-    private val repository: BouncerMessageRepository,
-    private val factory: BouncerMessageFactory,
-    private val userRepository: UserRepository,
-    private val countDownTimerUtil: CountDownTimerUtil,
-    private val featureFlags: FeatureFlags,
-) {
-    fun onPrimaryAuthLockedOut(secondsBeforeLockoutReset: Long) {
-        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
-
-        val callback =
-            object : CountDownTimerCallback {
-                override fun onFinish() {
-                    repository.clearMessage()
-                }
-
-                override fun onTick(millisUntilFinished: Long) {
-                    val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt()
-                    val message =
-                        factory.createFromPromptReason(
-                            reason = PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT,
-                            userId = userRepository.getSelectedUserInfo().id
-                        )
-                    message?.message?.animate = false
-                    message?.message?.formatterArgs =
-                        mutableMapOf<String, Any>(Pair("count", secondsRemaining))
-                    repository.setPrimaryAuthMessage(message)
-                }
-            }
-        countDownTimerUtil.startNewTimer(secondsBeforeLockoutReset * 1000, 1000, callback)
-    }
-
-    fun onPrimaryAuthIncorrectAttempt() {
-        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
-
-        repository.setPrimaryAuthMessage(
-            factory.createFromPromptReason(
-                PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-                userRepository.getSelectedUserInfo().id
-            )
-        )
-    }
-
-    fun setFingerprintAcquisitionMessage(value: String?) {
-        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
-
-        repository.setFingerprintAcquisitionMessage(
-            if (value != null) {
-                factory.createFromString(secondaryMsg = value)
-            } else {
-                null
-            }
-        )
-    }
-
-    fun setFaceAcquisitionMessage(value: String?) {
-        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
-
-        repository.setFaceAcquisitionMessage(
-            if (value != null) {
-                factory.createFromString(secondaryMsg = value)
-            } else {
-                null
-            }
-        )
-    }
-
-    fun setCustomMessage(value: String?) {
-        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
-
-        repository.setCustomMessage(
-            if (value != null) {
-                factory.createFromString(secondaryMsg = value)
-            } else {
-                null
-            }
-        )
-    }
-
-    fun onPrimaryBouncerUserInput() {
-        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
-
-        repository.clearMessage()
-    }
-
-    fun onBouncerBeingHidden() {
-        if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
-
-        repository.clearMessage()
-    }
-
-    private fun firstNonNullMessage(
-        oneMessageModel: Flow<BouncerMessageModel?>,
-        anotherMessageModel: Flow<BouncerMessageModel?>
-    ): Flow<BouncerMessageModel?> {
-        return oneMessageModel.combine(anotherMessageModel) { a, b -> a ?: b }
-    }
-
-    // Null if feature flag is enabled which gets ignored always or empty bouncer message model that
-    // always maps to an empty string.
-    private fun nullOrEmptyMessage() =
-        flowOf(
-            if (featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) null
-            else factory.createFromString("", "")
-        )
-
-    val bouncerMessage =
-        listOf(
-                nullOrEmptyMessage(),
-                repository.primaryAuthMessage,
-                repository.biometricAuthMessage,
-                repository.fingerprintAcquisitionMessage,
-                repository.faceAcquisitionMessage,
-                repository.customMessage,
-                repository.authFlagsMessage,
-                repository.biometricLockedOutMessage,
-                userRepository.selectedUserInfo.map {
-                    factory.createFromPromptReason(PROMPT_REASON_DEFAULT, it.id)
-                },
-            )
-            .reduce(::firstNonNullMessage)
-            .distinctUntilChanged()
-}
-
-interface CountDownTimerCallback {
-    fun onFinish()
-    fun onTick(millisUntilFinished: Long)
-}
-
-@SysUISingleton
-open class CountDownTimerUtil @Inject constructor() {
-
-    /**
-     * Start a new count down timer that runs for [millisInFuture] with a tick every
-     * [millisInterval]
-     */
-    fun startNewTimer(
-        millisInFuture: Long,
-        millisInterval: Long,
-        callback: CountDownTimerCallback,
-    ): CountDownTimer {
-        return object : CountDownTimer(millisInFuture, millisInterval) {
-                override fun onFinish() = callback.onFinish()
-
-                override fun onTick(millisUntilFinished: Long) =
-                    callback.onTick(millisUntilFinished)
-            }
-            .start()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/shared/model/BouncerMessageModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/shared/model/BouncerMessageModel.kt
deleted file mode 100644
index 46e8873..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/shared/model/BouncerMessageModel.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.bouncer.shared.model
-
-import android.content.res.ColorStateList
-
-/**
- * Represents the message displayed on the bouncer. It has two parts, primary and a secondary
- * message
- */
-data class BouncerMessageModel(val message: Message? = null, val secondaryMessage: Message? = null)
-
-/**
- * Representation of a single message on the bouncer. It can be either a string or a string resource
- * ID
- */
-data class Message(
-    val message: String? = null,
-    val messageResId: Int? = null,
-    val colorState: ColorStateList? = null,
-    /** Any plural formatter arguments that can used to format the [messageResId] */
-    var formatterArgs: Map<String, Any>? = null,
-    /** Specifies whether this text should be animated when it is shown. */
-    var animate: Boolean = true,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt
deleted file mode 100644
index 4dc52ff..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.bouncer.ui
-
-import android.content.Context
-import android.util.AttributeSet
-import android.widget.LinearLayout
-import com.android.keyguard.BouncerKeyguardMessageArea
-import com.android.keyguard.KeyguardMessageArea
-import com.android.keyguard.KeyguardMessageAreaController
-import com.android.systemui.R
-
-class BouncerMessageView : LinearLayout {
-    constructor(context: Context?) : super(context)
-
-    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
-
-    init {
-        inflate(context, R.layout.bouncer_message_view, this)
-    }
-
-    var primaryMessageView: BouncerKeyguardMessageArea? = null
-    var secondaryMessageView: BouncerKeyguardMessageArea? = null
-    var primaryMessage: KeyguardMessageAreaController<KeyguardMessageArea>? = null
-    var secondaryMessage: KeyguardMessageAreaController<KeyguardMessageArea>? = null
-    override fun onFinishInflate() {
-        super.onFinishInflate()
-        primaryMessageView = findViewById(R.id.bouncer_primary_message_area)
-        secondaryMessageView = findViewById(R.id.bouncer_secondary_message_area)
-        primaryMessageView?.disable()
-        secondaryMessageView?.disable()
-    }
-
-    fun init(factory: KeyguardMessageAreaController.Factory) {
-        primaryMessage = factory.create(primaryMessageView)
-        primaryMessage?.init()
-        secondaryMessage = factory.create(secondaryMessageView)
-        secondaryMessage?.init()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
index a44df7e..d8eb81c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.keyguard.dagger
 
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.NoopDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.keyguard.domain.interactor.NoopKeyguardFaceAuthInteractor
 import dagger.Binds
@@ -32,4 +34,9 @@
 interface KeyguardFaceAuthNotSupportedModule {
     @Binds
     fun keyguardFaceAuthInteractor(impl: NoopKeyguardFaceAuthInteractor): KeyguardFaceAuthInteractor
+
+    @Binds
+    fun deviceEntryFaceAuthRepository(
+        impl: NoopDeviceEntryFaceAuthRepository
+    ): DeviceEntryFaceAuthRepository
 }
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 1f121e9..dc19ea2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -46,11 +46,11 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager;
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
 import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule;
 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.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
@@ -67,14 +67,18 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.settings.SystemSettings;
+import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
+import java.util.concurrent.Executor;
+
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
-
-import java.util.concurrent.Executor;
-
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -88,7 +92,6 @@
         includes = {
             FalsingModule.class,
             KeyguardDataQuickAffordanceModule.class,
-            KeyguardQuickAffordanceModule.class,
             KeyguardRepositoryModule.class,
             KeyguardFaceAuthModule.class,
             StartKeyguardTransitionModule.class,
@@ -129,14 +132,20 @@
             KeyguardTransitions keyguardTransitions,
             InteractionJankMonitor interactionJankMonitor,
             DreamOverlayStateController dreamOverlayStateController,
+            JavaAdapter javaAdapter,
+            WallpaperRepository wallpaperRepository,
             Lazy<ShadeController> shadeController,
             Lazy<NotificationShadeWindowController> notificationShadeWindowController,
             Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
             Lazy<ScrimController> scrimControllerLazy,
             FeatureFlags featureFlags,
+            SecureSettings secureSettings,
+            SystemSettings systemSettings,
+            SystemClock systemClock,
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
-            SystemPropertiesHelper systemPropertiesHelper) {
+            SystemPropertiesHelper systemPropertiesHelper,
+            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
         return new KeyguardViewMediator(
                 context,
                 uiEventLogger,
@@ -166,14 +175,20 @@
                 keyguardTransitions,
                 interactionJankMonitor,
                 dreamOverlayStateController,
+                javaAdapter,
+                wallpaperRepository,
                 shadeController,
                 notificationShadeWindowController,
                 activityLaunchAnimator,
                 scrimControllerLazy,
                 featureFlags,
+                secureSettings,
+                systemSettings,
+                systemClock,
                 mainDispatcher,
                 dreamingToLockscreenTransitionViewModel,
-                systemPropertiesHelper);
+                systemPropertiesHelper,
+                wmLockscreenVisibilityManager);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
deleted file mode 100644
index 871a3ff..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ /dev/null
@@ -1,59 +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.keyguard.data
-
-import android.view.KeyEvent
-import android.window.OnBackAnimationCallback
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.ActivityStarter
-import java.lang.ref.WeakReference
-import javax.inject.Inject
-
-/** An abstraction to interface with the ui layer, without changing state. */
-interface BouncerView {
-    var delegate: BouncerViewDelegate?
-}
-
-/** A lightweight class to hold reference to the ui delegate. */
-@SysUISingleton
-class BouncerViewImpl @Inject constructor() : BouncerView {
-    private var _delegate: WeakReference<BouncerViewDelegate?> = WeakReference(null)
-    override var delegate: BouncerViewDelegate?
-        get() = _delegate.get()
-        set(value) {
-            _delegate = WeakReference(value)
-        }
-}
-
-/** An abstraction that implements view logic. */
-interface BouncerViewDelegate {
-    fun isFullScreenBouncer(): Boolean
-    fun shouldDismissOnMenuPressed(): Boolean
-    fun interceptMediaKey(event: KeyEvent?): Boolean
-    fun dispatchBackKeyEventPreIme(): Boolean
-    fun showNextSecurityScreenOrFinish(): Boolean
-    fun resume()
-    fun setDismissAction(
-        onDismissAction: ActivityStarter.OnDismissAction?,
-        cancelAction: Runnable?,
-    )
-    fun willDismissWithActions(): Boolean
-    fun willRunDismissFromKeyguard(): Boolean
-    /** @return the {@link OnBackAnimationCallback} to animate Bouncer during a back gesture. */
-    fun getBackCallback(): OnBackAnimationCallback
-    fun showPromptReason(reason: Int)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt
deleted file mode 100644
index 390c54e..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerViewModule.kt
+++ /dev/null
@@ -1,26 +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.keyguard.data
-
-import dagger.Binds
-import dagger.Module
-
-@Module
-interface BouncerViewModule {
-    /** Binds BouncerView to BouncerViewImpl and makes it injectable. */
-    @Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
index cd0805e..7dbe945 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
@@ -24,8 +24,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
@@ -42,7 +40,6 @@
  */
 @SysUISingleton
 class MuteQuickAffordanceCoreStartable @Inject constructor(
-    private val featureFlags: FeatureFlags,
     private val userTracker: UserTracker,
     private val ringerModeTracker: RingerModeTracker,
     private val userFileManager: UserFileManager,
@@ -54,8 +51,6 @@
     private val observer = Observer(this::updateLastNonSilentRingerMode)
 
     override fun start() {
-        if (!featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)) return
-
         // only listen to ringerModeInternal changes when Mute is one of the selected affordances
         keyguardQuickAffordanceRepository
             .selections
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index 20ed549..45277b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -78,16 +78,8 @@
 
     override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
         return when {
-            !controller.isAvailableOnDevice ->
+            !isEnabledForPickerStateOption() ->
                 KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
-            !controller.isAbleToOpenCameraApp -> {
-                KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
-                    explanation =
-                        context.getString(
-                            R.string.qr_scanner_quick_affordance_unavailable_explanation
-                        ),
-                )
-            }
             else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default()
         }
     }
@@ -118,6 +110,11 @@
         }
     }
 
+    /** Returns whether QR scanner be shown as one of available lockscreen shortcut option. */
+    private fun isEnabledForPickerStateOption(): Boolean {
+        return controller.isAbleToLaunchScannerActivity && controller.isAllowedOnLockScreen
+    }
+
     companion object {
         private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index c019d21..5d7a3d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -59,7 +59,7 @@
         conflatedCallbackFlow {
             val callback =
                 object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
-                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
                         val hasCards = response?.walletCards?.isNotEmpty() == true
                         trySendWithFailureLogging(
                             state(
@@ -71,7 +71,7 @@
                         )
                     }
 
-                    override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+                    override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
                         Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
                         trySendWithFailureLogging(
                             KeyguardQuickAffordanceConfig.LockScreenState.Hidden,
@@ -133,13 +133,13 @@
         return suspendCancellableCoroutine { continuation ->
             val callback =
                 object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
-                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
                         continuation.resumeWith(
                             Result.success(response?.walletCards ?: emptyList())
                         )
                     }
 
-                    override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+                    override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
                         continuation.resumeWith(Result.success(emptyList()))
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index 0b6c7c4..682e841 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -28,6 +28,9 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.R
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -68,34 +71,32 @@
  * upstream changes.
  */
 interface BiometricSettingsRepository {
-    /** Whether any fingerprints are enrolled for the current user. */
-    val isFingerprintEnrolled: StateFlow<Boolean>
-
-    /** Whether face authentication is enrolled for the current user. */
-    val isFaceEnrolled: Flow<Boolean>
+    /**
+     * If the current user can enter the device using fingerprint. This is true if user has enrolled
+     * fingerprints and fingerprint auth is not disabled through settings/device policy
+     */
+    val isFingerprintEnrolledAndEnabled: StateFlow<Boolean>
 
     /**
-     * Whether face authentication is enabled/disabled based on system settings like device policy,
-     * biometrics setting.
+     * If the current user can enter the device using fingerprint, right now.
+     *
+     * This returns true if there are no strong auth flags that restrict the user from using
+     * fingerprint and [isFingerprintEnrolledAndEnabled] is true
      */
-    val isFaceAuthenticationEnabled: Flow<Boolean>
+    val isFingerprintAuthCurrentlyAllowed: StateFlow<Boolean>
 
     /**
-     * Whether the current user is allowed to use a strong biometric for device entry based on
-     * Android Security policies. If false, the user may be able to use primary authentication for
-     * device entry.
+     * If the current user can use face auth to enter the device. This is true when the user has
+     * face auth enrolled, and is enabled in settings/device policy.
      */
-    val isStrongBiometricAllowed: StateFlow<Boolean>
+    val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
 
     /**
-     * Whether the current user is allowed to use a convenience biometric for device entry based on
-     * Android Security policies. If false, the user may be able to use strong biometric or primary
-     * authentication for device entry.
+     * If the current user can use face auth to enter the device right now. This is true when
+     * [isFaceAuthEnrolledAndEnabled] is true and strong auth settings allow face auth to run and
+     * face auth is supported by the current device posture.
      */
-    val isNonStrongBiometricAllowed: StateFlow<Boolean>
-
-    /** Whether fingerprint feature is enabled for the current user by the DevicePolicy */
-    val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean>
+    val isFaceAuthCurrentlyAllowed: Flow<Boolean>
 
     /**
      * Whether face authentication is supported for the current device posture. Face auth can be
@@ -130,6 +131,8 @@
     @Background backgroundDispatcher: CoroutineDispatcher,
     biometricManager: BiometricManager?,
     devicePostureRepository: DevicePostureRepository,
+    facePropertyRepository: FacePropertyRepository,
+    fingerprintPropertyRepository: FingerprintPropertyRepository,
     dumpManager: DumpManager,
 ) : BiometricSettingsRepository, Dumpable {
 
@@ -165,7 +168,9 @@
     }
 
     override fun dump(pw: PrintWriter, args: Array<String?>) {
-        pw.println("isFingerprintEnrolled=${isFingerprintEnrolled.value}")
+        pw.println("isFingerprintEnrolledAndEnabled=${isFingerprintEnrolledAndEnabled.value}")
+        pw.println("isFingerprintAuthCurrentlyAllowed=${isFingerprintAuthCurrentlyAllowed.value}")
+        pw.println("isNonStrongBiometricAllowed=${isNonStrongBiometricAllowed.value}")
         pw.println("isStrongBiometricAllowed=${isStrongBiometricAllowed.value}")
         pw.println("isFingerprintEnabledByDevicePolicy=${isFingerprintEnabledByDevicePolicy.value}")
     }
@@ -180,7 +185,7 @@
             user = UserHandle.ALL
         )
 
-    override val isFingerprintEnrolled: StateFlow<Boolean> =
+    private val isFingerprintEnrolled: Flow<Boolean> =
         selectedUserId
             .flatMapLatest { currentUserId ->
                 conflatedCallbackFlow {
@@ -211,7 +216,7 @@
                     authController.isFingerprintEnrolled(userRepository.getSelectedUserInfo().id)
             )
 
-    override val isFaceEnrolled: Flow<Boolean> =
+    private val isFaceEnrolled: Flow<Boolean> =
         selectedUserId.flatMapLatest { selectedUserId: Int ->
             conflatedCallbackFlow {
                 val callback =
@@ -245,14 +250,6 @@
             isFaceEnabledByBiometricsManager.map { biometricsEnabledForUser[userInfo.id] ?: false }
         }
 
-    override val isFaceAuthenticationEnabled: Flow<Boolean>
-        get() =
-            combine(isFaceEnabledByBiometricsManagerForCurrentUser, isFaceEnabledByDevicePolicy) {
-                biometricsManagerSetting,
-                devicePolicySetting ->
-                biometricsManagerSetting && devicePolicySetting
-            }
-
     private val isFaceEnabledByDevicePolicy: Flow<Boolean> =
         combine(selectedUserId, devicePolicyChangedForAllUsers) { userId, _ ->
                 devicePolicyManager.isFaceDisabled(userId)
@@ -263,6 +260,13 @@
             .flowOn(backgroundDispatcher)
             .distinctUntilChanged()
 
+    private val isFaceAuthenticationEnabled: Flow<Boolean> =
+        combine(isFaceEnabledByBiometricsManagerForCurrentUser, isFaceEnabledByDevicePolicy) {
+            biometricsManagerSetting,
+            devicePolicySetting ->
+            biometricsManagerSetting && devicePolicySetting
+        }
+
     private val isFaceEnabledByBiometricsManager: Flow<Pair<Int, Boolean>> =
         conflatedCallbackFlow {
                 val callback =
@@ -283,7 +287,7 @@
             // being registered.
             .stateIn(scope, SharingStarted.Eagerly, Pair(0, false))
 
-    override val isStrongBiometricAllowed: StateFlow<Boolean> =
+    private val isStrongBiometricAllowed: StateFlow<Boolean> =
         strongAuthTracker.isStrongBiometricAllowed.stateIn(
             scope,
             SharingStarted.Eagerly,
@@ -293,7 +297,7 @@
             )
         )
 
-    override val isNonStrongBiometricAllowed: StateFlow<Boolean> =
+    private val isNonStrongBiometricAllowed: StateFlow<Boolean> =
         strongAuthTracker.isNonStrongBiometricAllowed.stateIn(
             scope,
             SharingStarted.Eagerly,
@@ -303,7 +307,19 @@
             )
         )
 
-    override val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
+    private val isFingerprintBiometricAllowed: Flow<Boolean> =
+        fingerprintPropertyRepository.strength.flatMapLatest {
+            if (it == SensorStrength.STRONG) isStrongBiometricAllowed
+            else isNonStrongBiometricAllowed
+        }
+
+    private val isFaceBiometricsAllowed: Flow<Boolean> =
+        facePropertyRepository.sensorInfo.flatMapLatest {
+            if (it?.strength == SensorStrength.STRONG) isStrongBiometricAllowed
+            else isNonStrongBiometricAllowed
+        }
+
+    private val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
         selectedUserId
             .flatMapLatest { userId ->
                 devicePolicyChangedForAllUsers
@@ -319,12 +335,34 @@
                         userRepository.getSelectedUserInfo().id
                     )
             )
+
+    override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> =
+        isFingerprintEnrolled
+            .and(isFingerprintEnabledByDevicePolicy)
+            .stateIn(scope, SharingStarted.Eagerly, false)
+
+    override val isFingerprintAuthCurrentlyAllowed: StateFlow<Boolean> =
+        isFingerprintEnrolledAndEnabled
+            .and(isFingerprintBiometricAllowed)
+            .stateIn(scope, SharingStarted.Eagerly, false)
+
+    override val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
+        get() = isFaceAuthenticationEnabled.and(isFaceEnrolled)
+
+    override val isFaceAuthCurrentlyAllowed: Flow<Boolean>
+        get() =
+            isFaceAuthEnrolledAndEnabled
+                .and(isFaceBiometricsAllowed)
+                .and(isFaceAuthSupportedInCurrentPosture)
 }
 
 @OptIn(ExperimentalCoroutinesApi::class)
 private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) :
     LockPatternUtils.StrongAuthTracker(context) {
 
+    private val selectedUserId =
+        userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged()
+
     // Backing field for onStrongAuthRequiredChanged
     private val _authFlags =
         MutableStateFlow(AuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId)))
@@ -336,15 +374,12 @@
         )
 
     val currentUserAuthFlags: Flow<AuthenticationFlags> =
-        userRepository.selectedUserInfo
-            .map { it.id }
-            .distinctUntilChanged()
-            .flatMapLatest { userId ->
-                _authFlags
-                    .map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) }
-                    .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
-                    .onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) }
-            }
+        selectedUserId.flatMapLatest { userId ->
+            _authFlags
+                .map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) }
+                .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
+                .onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) }
+        }
 
     /** isStrongBiometricAllowed for the current user. */
     val isStrongBiometricAllowed: Flow<Boolean> =
@@ -352,16 +387,17 @@
 
     /** isNonStrongBiometricAllowed for the current user. */
     val isNonStrongBiometricAllowed: Flow<Boolean> =
-        userRepository.selectedUserInfo
-            .map { it.id }
-            .distinctUntilChanged()
+        selectedUserId
             .flatMapLatest { userId ->
                 _nonStrongBiometricAllowed
                     .filter { it.first == userId }
                     .map { it.second }
-                    .onEach { Log.d(TAG, "isNonStrongBiometricAllowed changed for current user") }
+                    .onEach {
+                        Log.d(TAG, "isNonStrongBiometricAllowed changed for current user: $it")
+                    }
                     .onStart { emit(isNonStrongBiometricAllowedAfterIdleTimeout(userId)) }
             }
+            .and(isStrongBiometricAllowed)
 
     private val currentUserId
         get() = userRepository.getSelectedUserInfo().id
@@ -387,3 +423,6 @@
 
 private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean =
     (getKeyguardDisabledFeatures(null, userId) and policy) == 0
+
+private fun Flow<Boolean>.and(anotherFlow: Flow<Boolean>): Flow<Boolean> =
+    this.combine(anotherFlow) { a, b -> a && b }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 9621f03..93eb103 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -26,24 +26,26 @@
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.systemui.Dumpable
 import com.android.systemui.R
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 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.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.log.SessionTracker
@@ -57,6 +59,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.delay
@@ -87,10 +90,10 @@
     val canRunFaceAuth: StateFlow<Boolean>
 
     /** Provide the current status of face authentication. */
-    val authenticationStatus: Flow<AuthenticationStatus>
+    val authenticationStatus: Flow<FaceAuthenticationStatus>
 
     /** Provide the current status of face detection. */
-    val detectionStatus: Flow<DetectionStatus>
+    val detectionStatus: Flow<FaceDetectionStatus>
 
     /** Current state of whether face authentication is locked out or not. */
     val isLockedOut: StateFlow<Boolean>
@@ -101,6 +104,21 @@
     /** Whether bypass is currently enabled */
     val isBypassEnabled: Flow<Boolean>
 
+    /** Set whether face authentication should be locked out or not */
+    fun lockoutFaceAuth()
+
+    /**
+     * Cancel current face authentication and prevent it from running until [resumeFaceAuth] is
+     * invoked.
+     */
+    fun pauseFaceAuth()
+
+    /**
+     * Allow face auth paused using [pauseFaceAuth] to run again. The next invocation to
+     * [authenticate] will run as long as other gating conditions don't stop it from running.
+     */
+    fun resumeFaceAuth()
+
     /**
      * Trigger face authentication.
      *
@@ -116,6 +134,7 @@
     fun cancel()
 }
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class DeviceEntryFaceAuthRepositoryImpl
 @Inject
@@ -126,6 +145,7 @@
     private val keyguardBypassController: KeyguardBypassController? = null,
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val sessionTracker: SessionTracker,
     private val uiEventsLogger: UiEventLogger,
     private val faceAuthLogger: FaceAuthenticationLogger,
@@ -149,13 +169,13 @@
     private var cancelNotReceivedHandlerJob: Job? = null
     private var halErrorRetryJob: Job? = null
 
-    private val _authenticationStatus: MutableStateFlow<AuthenticationStatus?> =
+    private val _authenticationStatus: MutableStateFlow<FaceAuthenticationStatus?> =
         MutableStateFlow(null)
-    override val authenticationStatus: Flow<AuthenticationStatus>
+    override val authenticationStatus: Flow<FaceAuthenticationStatus>
         get() = _authenticationStatus.filterNotNull()
 
-    private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
-    override val detectionStatus: Flow<DetectionStatus>
+    private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null)
+    override val detectionStatus: Flow<FaceDetectionStatus>
         get() = _detectionStatus.filterNotNull()
 
     private val _isLockedOut = MutableStateFlow(false)
@@ -168,6 +188,15 @@
     override val isAuthRunning: StateFlow<Boolean>
         get() = _isAuthRunning
 
+    private val faceAuthPaused = MutableStateFlow(false)
+    override fun pauseFaceAuth() {
+        faceAuthPaused.value = true
+    }
+
+    override fun resumeFaceAuth() {
+        faceAuthPaused.value = false
+    }
+
     private val keyguardSessionId: InstanceId?
         get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
 
@@ -197,6 +226,10 @@
         }
             ?: flowOf(false)
 
+    override fun lockoutFaceAuth() {
+        _isLockedOut.value = true
+    }
+
     private val faceLockoutResetCallback =
         object : FaceManager.LockoutResetCallback() {
             override fun onLockoutReset(sensorId: Int) {
@@ -228,8 +261,12 @@
         keyguardTransitionInteractor.anyStateToGoneTransition
             .filter { it.transitionState == TransitionState.FINISHED }
             .onEach {
-                faceAuthLogger.watchdogScheduled()
-                faceManager?.scheduleWatchdog()
+                // We deliberately want to run this in background because scheduleWatchdog does
+                // a Binder IPC.
+                withContext(backgroundDispatcher) {
+                    faceAuthLogger.watchdogScheduled()
+                    faceManager?.scheduleWatchdog()
+                }
             }
             .launchIn(applicationScope)
     }
@@ -239,8 +276,12 @@
         // starts going to sleep.
         merge(
                 keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() },
-                keyguardRepository.isKeyguardGoingAway,
-                userRepository.userSwitchingInProgress
+                if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
+                } else {
+                    keyguardRepository.isKeyguardGoingAway
+                },
+                userRepository.userSwitchingInProgress,
             )
             .onEach { anyOfThemIsTrue ->
                 if (anyOfThemIsTrue) {
@@ -254,13 +295,17 @@
 
     private fun observeFaceDetectGatingChecks() {
         // Face detection can run only when lockscreen bypass is enabled
-        // & detection is supported & biometric unlock is not allowed.
+        // & detection is supported
+        //   & biometric unlock is not allowed
+        //     or user is trusted by trust manager & we want to run face detect to dismiss keyguard
         listOf(
                 canFaceAuthOrDetectRun(faceDetectLog),
                 logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
                 logAndObserve(
-                    biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
-                    "nonStrongBiometricIsNotAllowed",
+                    biometricSettingsRepository.isFaceAuthCurrentlyAllowed
+                        .isFalse()
+                        .or(trustRepository.isCurrentUserTrusted),
+                    "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted",
                     faceDetectLog
                 ),
                 // We don't want to run face detect if fingerprint can be used to unlock the device
@@ -292,38 +337,30 @@
     private fun canFaceAuthOrDetectRun(tableLogBuffer: TableLogBuffer): Flow<Boolean> {
         return listOf(
                 logAndObserve(
-                    biometricSettingsRepository.isFaceEnrolled,
-                    "isFaceEnrolled",
+                    biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
+                    "isFaceAuthEnrolledAndEnabled",
                     tableLogBuffer
                 ),
-                logAndObserve(
-                    biometricSettingsRepository.isFaceAuthenticationEnabled,
-                    "isFaceAuthenticationEnabled",
-                    tableLogBuffer
-                ),
-                logAndObserve(
-                    userRepository.userSwitchingInProgress.isFalse(),
-                    "userSwitchingNotInProgress",
-                    tableLogBuffer
-                ),
+                logAndObserve(faceAuthPaused.isFalse(), "faceAuthIsNotPaused", tableLogBuffer),
                 logAndObserve(
                     keyguardRepository.isKeyguardGoingAway.isFalse(),
                     "keyguardNotGoingAway",
                     tableLogBuffer
                 ),
                 logAndObserve(
-                    keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() }.isFalse(),
-                    "deviceNotSleepingOrNotStartingToSleep",
+                    keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(),
+                    "deviceNotStartingToSleep",
                     tableLogBuffer
                 ),
                 logAndObserve(
-                    combine(
-                        keyguardInteractor.isSecureCameraActive,
-                        alternateBouncerInteractor.isVisible
-                    ) { a, b ->
-                        !a || b
-                    },
-                    "secureCameraNotActiveOrAltBouncerIsShowing",
+                    keyguardInteractor.isSecureCameraActive
+                        .isFalse()
+                        .or(
+                            alternateBouncerInteractor.isVisible.or(
+                                keyguardInteractor.primaryBouncerShowing
+                            )
+                        ),
+                    "secureCameraNotActiveOrAnyBouncerIsShowing",
                     tableLogBuffer
                 ),
                 logAndObserve(
@@ -335,6 +372,11 @@
                     biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
                     "userHasNotLockedDownDevice",
                     tableLogBuffer
+                ),
+                logAndObserve(
+                    keyguardRepository.isKeyguardShowing,
+                    "isKeyguardShowing",
+                    tableLogBuffer
                 )
             )
             .reduce(::and)
@@ -346,20 +388,16 @@
                 canFaceAuthOrDetectRun(faceAuthLog),
                 logAndObserve(isLockedOut.isFalse(), "isNotInLockOutState", faceAuthLog),
                 logAndObserve(
-                    deviceEntryFingerprintAuthRepository.isLockedOut.isFalse(),
-                    "fpIsNotLockedOut",
-                    faceAuthLog
-                ),
-                logAndObserve(
                     trustRepository.isCurrentUserTrusted.isFalse(),
                     "currentUserIsNotTrusted",
                     faceAuthLog
                 ),
                 logAndObserve(
-                    biometricSettingsRepository.isNonStrongBiometricAllowed,
-                    "nonStrongBiometricIsAllowed",
+                    biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
+                    "isFaceAuthCurrentlyAllowed",
                     faceAuthLog
                 ),
+                logAndObserve(isAuthenticated.isFalse(), "faceNotAuthenticated", faceAuthLog),
             )
             .reduce(::and)
             .distinctUntilChanged()
@@ -379,18 +417,18 @@
     private val faceAuthCallback =
         object : FaceManager.AuthenticationCallback() {
             override fun onAuthenticationFailed() {
-                _authenticationStatus.value = FailedAuthenticationStatus
+                _authenticationStatus.value = FailedFaceAuthenticationStatus()
                 _isAuthenticated.value = false
                 faceAuthLogger.authenticationFailed()
                 onFaceAuthRequestCompleted()
             }
 
             override fun onAuthenticationAcquired(acquireInfo: Int) {
-                _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo)
+                _authenticationStatus.value = AcquiredFaceAuthenticationStatus(acquireInfo)
             }
 
             override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
-                val errorStatus = ErrorAuthenticationStatus(errorCode, errString.toString())
+                val errorStatus = ErrorFaceAuthenticationStatus(errorCode, errString.toString())
                 if (errorStatus.isLockoutError()) {
                     _isLockedOut.value = true
                 }
@@ -416,11 +454,11 @@
                 if (faceAcquiredInfoIgnoreList.contains(code)) {
                     return
                 }
-                _authenticationStatus.value = HelpAuthenticationStatus(code, helpStr.toString())
+                _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr.toString())
             }
 
             override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
-                _authenticationStatus.value = SuccessAuthenticationStatus(result)
+                _authenticationStatus.value = SuccessFaceAuthenticationStatus(result)
                 _isAuthenticated.value = true
                 faceAuthLogger.faceAuthSuccess(result)
                 onFaceAuthRequestCompleted()
@@ -428,7 +466,6 @@
         }
 
     private fun handleFaceCancellationError() {
-        cancelNotReceivedHandlerJob?.cancel()
         applicationScope.launch {
             faceAuthRequestedWhileCancellation?.let {
                 faceAuthLogger.launchingQueuedFaceAuthRequest(it)
@@ -457,6 +494,7 @@
     }
 
     private fun onFaceAuthRequestCompleted() {
+        cancelNotReceivedHandlerJob?.cancel()
         cancellationInProgress = false
         _isAuthRunning.value = false
         authCancellationSignal = null
@@ -465,7 +503,7 @@
     private val detectionCallback =
         FaceManager.FaceDetectionCallback { sensorId, userId, isStrong ->
             faceAuthLogger.faceDetected()
-            _detectionStatus.value = DetectionStatus(sensorId, userId, isStrong)
+            _detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong)
         }
 
     private var cancellationInProgress = false
@@ -528,17 +566,17 @@
             faceAuthLogger.detectionNotSupported(faceManager, faceManager?.sensorPropertiesInternal)
             return
         }
-        if (_isAuthRunning.value || detectCancellationSignal != null) {
+        if (_isAuthRunning.value) {
             faceAuthLogger.skippingDetection(_isAuthRunning.value, detectCancellationSignal != null)
             return
         }
-
+        detectCancellationSignal?.cancel()
         detectCancellationSignal = CancellationSignal()
         withContext(mainDispatcher) {
             // We always want to invoke face detect in the main thread.
             faceAuthLogger.faceDetectionStarted()
             faceManager?.detectFace(
-                detectCancellationSignal,
+                checkNotNull(detectCancellationSignal),
                 detectionCallback,
                 FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
             )
@@ -557,6 +595,7 @@
         if (authCancellationSignal == null) return
 
         authCancellationSignal?.cancel()
+        cancelNotReceivedHandlerJob?.cancel()
         cancelNotReceivedHandlerJob =
             applicationScope.launch {
                 delay(DEFAULT_CANCEL_SIGNAL_TIMEOUT)
@@ -566,6 +605,7 @@
                     cancellationInProgress,
                     faceAuthRequestedWhileCancellation
                 )
+                _authenticationStatus.value = ErrorFaceAuthenticationStatus.cancelNotReceivedError()
                 onFaceAuthRequestCompleted()
             }
         cancellationInProgress = true
@@ -598,7 +638,7 @@
         const val DEFAULT_CANCEL_SIGNAL_TIMEOUT = 3000L
 
         /** Number of allowed retries whenever there is a face hardware error */
-        const val HAL_ERROR_RETRY_MAX = 20
+        const val HAL_ERROR_RETRY_MAX = 5
 
         /** Timeout before retries whenever there is a HAL error. */
         const val HAL_ERROR_RETRY_TIMEOUT = 500L // ms
@@ -634,6 +674,10 @@
 private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
     flow.combine(anotherFlow) { a, b -> a && b }
 
+/** Combine two boolean flows by or-ing both of them */
+private fun Flow<Boolean>.or(anotherFlow: Flow<Boolean>) =
+    this.combine(anotherFlow) { a, b -> a || b }
+
 /** "Not" the given flow. The return [Flow] will be true when [this] flow is false. */
 private fun Flow<Boolean>.isFalse(): Flow<Boolean> {
     return this.map { !it }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 52234b3..9bec300 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -21,27 +21,29 @@
 import android.hardware.biometrics.BiometricSourceType
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.Dumpable
 import com.android.systemui.biometrics.AuthController
 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.dagger.qualifiers.Application
-import com.android.systemui.dump.DumpManager
-import java.io.PrintWriter
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.stateIn
 
 /** Encapsulates state about device entry fingerprint auth mechanism. */
 interface DeviceEntryFingerprintAuthRepository {
     /** Whether the device entry fingerprint auth is locked out. */
-    val isLockedOut: StateFlow<Boolean>
+    val isLockedOut: Flow<Boolean>
 
     /**
      * Whether the fingerprint sensor is currently listening, this doesn't mean that the user is
@@ -53,6 +55,9 @@
      * Fingerprint sensor type present on the device, null if fingerprint sensor is not available.
      */
     val availableFpSensorType: Flow<BiometricType?>
+
+    /** Provide the current status of fingerprint authentication. */
+    val authenticationStatus: Flow<FingerprintAuthenticationStatus>
 }
 
 /**
@@ -69,16 +74,7 @@
     val authController: AuthController,
     val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     @Application scope: CoroutineScope,
-    dumpManager: DumpManager,
-) : DeviceEntryFingerprintAuthRepository, Dumpable {
-
-    init {
-        dumpManager.registerDumpable(this)
-    }
-
-    override fun dump(pw: PrintWriter, args: Array<String?>) {
-        pw.println("isLockedOut=${isLockedOut.value}")
-    }
+) : DeviceEntryFingerprintAuthRepository {
 
     override val availableFpSensorType: Flow<BiometricType?>
         get() {
@@ -114,7 +110,7 @@
         else if (authController.isRearFpsSupported) BiometricType.REAR_FINGERPRINT else null
     }
 
-    override val isLockedOut: StateFlow<Boolean> =
+    override val isLockedOut: Flow<Boolean> =
         conflatedCallbackFlow {
                 val sendLockoutUpdate =
                     fun() {
@@ -138,7 +134,7 @@
                 sendLockoutUpdate()
                 awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
             }
-            .stateIn(scope, started = SharingStarted.Eagerly, initialValue = false)
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
 
     override val isRunning: Flow<Boolean>
         get() = conflatedCallbackFlow {
@@ -166,6 +162,93 @@
             awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
         }
 
+    override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
+        get() = conflatedCallbackFlow {
+            val callback =
+                object : KeyguardUpdateMonitorCallback() {
+                    override fun onBiometricAuthenticated(
+                        userId: Int,
+                        biometricSourceType: BiometricSourceType,
+                        isStrongBiometric: Boolean,
+                    ) {
+
+                        sendUpdateIfFingerprint(
+                            biometricSourceType,
+                            SuccessFingerprintAuthenticationStatus(
+                                userId,
+                                isStrongBiometric,
+                            ),
+                        )
+                    }
+
+                    override fun onBiometricError(
+                        msgId: Int,
+                        errString: String?,
+                        biometricSourceType: BiometricSourceType,
+                    ) {
+                        sendUpdateIfFingerprint(
+                            biometricSourceType,
+                            ErrorFingerprintAuthenticationStatus(
+                                msgId,
+                                errString,
+                            ),
+                        )
+                    }
+
+                    override fun onBiometricHelp(
+                        msgId: Int,
+                        helpString: String?,
+                        biometricSourceType: BiometricSourceType,
+                    ) {
+                        sendUpdateIfFingerprint(
+                            biometricSourceType,
+                            HelpFingerprintAuthenticationStatus(
+                                msgId,
+                                helpString,
+                            ),
+                        )
+                    }
+
+                    override fun onBiometricAuthFailed(
+                        biometricSourceType: BiometricSourceType,
+                    ) {
+                        sendUpdateIfFingerprint(
+                            biometricSourceType,
+                            FailFingerprintAuthenticationStatus,
+                        )
+                    }
+
+                    override fun onBiometricAcquired(
+                        biometricSourceType: BiometricSourceType,
+                        acquireInfo: Int,
+                    ) {
+                        sendUpdateIfFingerprint(
+                            biometricSourceType,
+                            AcquiredFingerprintAuthenticationStatus(
+                                acquireInfo,
+                            ),
+                        )
+                    }
+
+                    private fun sendUpdateIfFingerprint(
+                        biometricSourceType: BiometricSourceType,
+                        authenticationStatus: FingerprintAuthenticationStatus
+                    ) {
+                        if (biometricSourceType != BiometricSourceType.FINGERPRINT) {
+                            return
+                        }
+
+                        trySendWithFailureLogging(
+                            authenticationStatus,
+                            TAG,
+                            "new fingerprint authentication status"
+                        )
+                    }
+                }
+            keyguardUpdateMonitor.registerCallback(callback)
+            awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+        }
+
     companion object {
         const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
new file mode 100644
index 0000000..059f72b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
+import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.launch
+
+/**
+ * Manages blueprint changes for the lockscreen.
+ *
+ * To add a blueprint, create a class that implements LockscreenBlueprint and bind it to the map in
+ * the dagger module:
+ *
+ * A Blueprint determines how the layout should be constrained on a high level.
+ *
+ * A Section is a modular piece of code that implements the constraints. The blueprint uses the
+ * sections to define the constraints.
+ *
+ * @see KeyguardBlueprintModule
+ */
+@SysUISingleton
+class KeyguardBlueprintRepository
+@Inject
+constructor(
+    configurationRepository: ConfigurationRepository,
+    blueprints: Set<@JvmSuppressWildcards KeyguardBlueprint>,
+    @Application private val applicationScope: CoroutineScope,
+) {
+    private val blueprintIdMap: Map<String, KeyguardBlueprint> = blueprints.associateBy { it.id }
+    private val _blueprint: MutableSharedFlow<KeyguardBlueprint> = MutableSharedFlow(replay = 1)
+    val blueprint: Flow<KeyguardBlueprint> = _blueprint.asSharedFlow()
+
+    init {
+        applyBlueprint(blueprintIdMap[DEFAULT]!!)
+        applicationScope.launch {
+            configurationRepository.onAnyConfigurationChange.collect { refreshBlueprint() }
+        }
+    }
+
+    /**
+     * Emits the blueprint value to the collectors.
+     *
+     * @param blueprintId
+     * @return whether the transition has succeeded.
+     */
+    fun applyBlueprint(blueprintId: String?): Boolean {
+        val blueprint = blueprintIdMap[blueprintId] ?: return false
+        applyBlueprint(blueprint)
+        return true
+    }
+
+    /** Emits the blueprint value to the collectors. */
+    fun applyBlueprint(blueprint: KeyguardBlueprint?) {
+        blueprint?.let { _blueprint.tryEmit(it) }
+    }
+
+    /** Re-emits the last emitted blueprint value if possible. */
+    fun refreshBlueprint() {
+        if (_blueprint.replayCache.isNotEmpty()) {
+            _blueprint.tryEmit(_blueprint.replayCache.last())
+        }
+    }
+
+    /** Prints all available blueprints to the PrintWriter. */
+    fun printBlueprints(pw: PrintWriter) {
+        blueprintIdMap.forEach { entry -> pw.println("${entry.key}") }
+    }
+}
+
+/** Determines the constraints for the ConstraintSet in the lockscreen root view. */
+interface KeyguardBlueprint {
+    val id: String
+
+    fun apply(constraintSet: ConstraintSet)
+}
+
+/**
+ * Lower level modules that determine constraints for a particular section in the lockscreen root
+ * view.
+ */
+interface KeyguardSection {
+    fun apply(constraintSet: ConstraintSet)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
deleted file mode 100644
index 5d15e69..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ /dev/null
@@ -1,273 +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.keyguard.data.repository
-
-import android.os.Build
-import android.util.Log
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
-import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.log.dagger.BouncerTableLog
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.util.time.SystemClock
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-
-/**
- * Encapsulates app state for the lock screen primary and alternate bouncer.
- *
- * Make sure to add newly added flows to the logger.
- */
-interface KeyguardBouncerRepository {
-    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
-    val primaryBouncerShow: StateFlow<Boolean>
-    val primaryBouncerShowingSoon: StateFlow<Boolean>
-    val primaryBouncerStartingToHide: StateFlow<Boolean>
-    val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
-    /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
-    val primaryBouncerScrimmed: StateFlow<Boolean>
-    /**
-     * Set how much of the notification panel is showing on the screen.
-     *
-     * ```
-     *      0f = panel fully hidden = bouncer fully showing
-     *      1f = panel fully showing = bouncer fully hidden
-     * ```
-     */
-    val panelExpansionAmount: StateFlow<Float>
-    val keyguardPosition: StateFlow<Float>
-    val isBackButtonEnabled: StateFlow<Boolean?>
-    /** Determines if user is already unlocked */
-    val keyguardAuthenticated: StateFlow<Boolean?>
-    val showMessage: StateFlow<BouncerShowMessageModel?>
-    val resourceUpdateRequests: StateFlow<Boolean>
-    val alternateBouncerVisible: StateFlow<Boolean>
-    val alternateBouncerUIAvailable: StateFlow<Boolean>
-    val sideFpsShowing: StateFlow<Boolean>
-
-    var lastAlternateBouncerVisibleTime: Long
-
-    fun setPrimaryScrimmed(isScrimmed: Boolean)
-
-    fun setPrimaryShow(isShowing: Boolean)
-
-    fun setPrimaryShowingSoon(showingSoon: Boolean)
-
-    fun setPrimaryStartingToHide(startingToHide: Boolean)
-
-    fun setPrimaryStartDisappearAnimation(runnable: Runnable?)
-
-    fun setPanelExpansion(panelExpansion: Float)
-
-    fun setKeyguardPosition(keyguardPosition: Float)
-
-    fun setResourceUpdateRequests(willUpdateResources: Boolean)
-
-    fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?)
-
-    fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?)
-
-    fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean)
-
-    fun setAlternateVisible(isVisible: Boolean)
-
-    fun setAlternateBouncerUIAvailable(isAvailable: Boolean)
-
-    fun setSideFpsShowing(isShowing: Boolean)
-}
-
-@SysUISingleton
-class KeyguardBouncerRepositoryImpl
-@Inject
-constructor(
-    private val clock: SystemClock,
-    @Application private val applicationScope: CoroutineScope,
-    @BouncerTableLog private val buffer: TableLogBuffer,
-) : KeyguardBouncerRepository {
-    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
-    private val _primaryBouncerShow = MutableStateFlow(false)
-    override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
-    private val _primaryBouncerShowingSoon = MutableStateFlow(false)
-    override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
-    private val _primaryBouncerStartingToHide = MutableStateFlow(false)
-    override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
-    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
-    override val primaryBouncerStartingDisappearAnimation =
-        _primaryBouncerDisappearAnimation.asStateFlow()
-    /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
-    private val _primaryBouncerScrimmed = MutableStateFlow(false)
-    override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
-    /**
-     * Set how much of the notification panel is showing on the screen.
-     *
-     * ```
-     *      0f = panel fully hidden = bouncer fully showing
-     *      1f = panel fully showing = bouncer fully hidden
-     * ```
-     */
-    private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
-    override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
-    private val _keyguardPosition = MutableStateFlow(0f)
-    override val keyguardPosition = _keyguardPosition.asStateFlow()
-    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
-    override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
-    private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
-    /** Determines if user is already unlocked */
-    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
-    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
-    override val showMessage = _showMessage.asStateFlow()
-    private val _resourceUpdateRequests = MutableStateFlow(false)
-    override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
-    /** Values associated with the AlternateBouncer */
-    private val _alternateBouncerVisible = MutableStateFlow(false)
-    override val alternateBouncerVisible = _alternateBouncerVisible.asStateFlow()
-    override var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
-    private val _alternateBouncerUIAvailable = MutableStateFlow(false)
-    override val alternateBouncerUIAvailable: StateFlow<Boolean> =
-        _alternateBouncerUIAvailable.asStateFlow()
-    private val _sideFpsShowing = MutableStateFlow(false)
-    override val sideFpsShowing: StateFlow<Boolean> = _sideFpsShowing.asStateFlow()
-
-    init {
-        setUpLogging()
-    }
-
-    override fun setPrimaryScrimmed(isScrimmed: Boolean) {
-        _primaryBouncerScrimmed.value = isScrimmed
-    }
-
-    override fun setAlternateVisible(isVisible: Boolean) {
-        if (isVisible && !_alternateBouncerVisible.value) {
-            lastAlternateBouncerVisibleTime = clock.uptimeMillis()
-        } else if (!isVisible) {
-            lastAlternateBouncerVisibleTime = NOT_VISIBLE
-        }
-        _alternateBouncerVisible.value = isVisible
-    }
-
-    override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
-        _alternateBouncerUIAvailable.value = isAvailable
-    }
-
-    override fun setPrimaryShow(isShowing: Boolean) {
-        _primaryBouncerShow.value = isShowing
-    }
-
-    override fun setPrimaryShowingSoon(showingSoon: Boolean) {
-        _primaryBouncerShowingSoon.value = showingSoon
-    }
-
-    override fun setPrimaryStartingToHide(startingToHide: Boolean) {
-        _primaryBouncerStartingToHide.value = startingToHide
-    }
-
-    override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
-        _primaryBouncerDisappearAnimation.value = runnable
-    }
-
-    override fun setPanelExpansion(panelExpansion: Float) {
-        _panelExpansionAmount.value = panelExpansion
-    }
-
-    override fun setKeyguardPosition(keyguardPosition: Float) {
-        _keyguardPosition.value = keyguardPosition
-    }
-
-    override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
-        _resourceUpdateRequests.value = willUpdateResources
-    }
-
-    override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
-        _showMessage.value = bouncerShowMessageModel
-    }
-
-    override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
-        _keyguardAuthenticated.value = keyguardAuthenticated
-    }
-
-    override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
-        _isBackButtonEnabled.value = isBackButtonEnabled
-    }
-
-    override fun setSideFpsShowing(isShowing: Boolean) {
-        _sideFpsShowing.value = isShowing
-    }
-
-    /** Sets up logs for state flows. */
-    private fun setUpLogging() {
-        if (!Build.IS_DEBUGGABLE) {
-            return
-        }
-
-        primaryBouncerShow
-            .logDiffsForTable(buffer, "", "PrimaryBouncerShow", false)
-            .onEach { Log.d(TAG, "Keyguard Bouncer is ${if (it) "showing" else "hiding."}") }
-            .launchIn(applicationScope)
-        primaryBouncerShowingSoon
-            .logDiffsForTable(buffer, "", "PrimaryBouncerShowingSoon", false)
-            .launchIn(applicationScope)
-        primaryBouncerStartingToHide
-            .logDiffsForTable(buffer, "", "PrimaryBouncerStartingToHide", false)
-            .launchIn(applicationScope)
-        primaryBouncerStartingDisappearAnimation
-            .map { it != null }
-            .logDiffsForTable(buffer, "", "PrimaryBouncerStartingDisappearAnimation", false)
-            .launchIn(applicationScope)
-        primaryBouncerScrimmed
-            .logDiffsForTable(buffer, "", "PrimaryBouncerScrimmed", false)
-            .launchIn(applicationScope)
-        panelExpansionAmount
-            .map { (it * 1000).toInt() }
-            .logDiffsForTable(buffer, "", "PanelExpansionAmountMillis", -1)
-            .launchIn(applicationScope)
-        keyguardPosition
-            .map { it.toInt() }
-            .logDiffsForTable(buffer, "", "KeyguardPosition", -1)
-            .launchIn(applicationScope)
-        isBackButtonEnabled
-            .filterNotNull()
-            .logDiffsForTable(buffer, "", "IsBackButtonEnabled", false)
-            .launchIn(applicationScope)
-        showMessage
-            .map { it?.message }
-            .logDiffsForTable(buffer, "", "ShowMessage", null)
-            .launchIn(applicationScope)
-        resourceUpdateRequests
-            .logDiffsForTable(buffer, "", "ResourceUpdateRequests", false)
-            .launchIn(applicationScope)
-        alternateBouncerUIAvailable
-            .logDiffsForTable(buffer, "", "IsAlternateBouncerUIAvailable", false)
-            .launchIn(applicationScope)
-        sideFpsShowing
-            .logDiffsForTable(buffer, "", "isSideFpsShowing", false)
-            .launchIn(applicationScope)
-    }
-
-    companion object {
-        private const val NOT_VISIBLE = -1L
-        private const val TAG = "KeyguardBouncerRepositoryImpl"
-    }
-}
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 742e535..42cd3a5 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
@@ -25,32 +25,41 @@
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.doze.DozeMachine
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
 import com.android.systemui.dreams.DreamOverlayCallbackController
+import com.android.systemui.keyguard.ScreenLifecycle
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.keyguard.shared.model.ScreenModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
 import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
 
 /** Defines interface for classes that encapsulate application state for the keyguard. */
 interface KeyguardRepository {
@@ -67,6 +76,8 @@
      */
     val bottomAreaAlpha: StateFlow<Float>
 
+    val keyguardAlpha: StateFlow<Float>
+
     /**
      * Observable of the relative offset of the lock-screen clock from its natural position on the
      * screen.
@@ -83,12 +94,21 @@
     val isKeyguardShowing: Flow<Boolean>
 
     /** Is the keyguard in a unlocked state? */
-    val isKeyguardUnlocked: Flow<Boolean>
+    val isKeyguardUnlocked: StateFlow<Boolean>
 
     /** Is an activity showing over the keyguard? */
     val isKeyguardOccluded: Flow<Boolean>
 
-    /** Observable for the signal that keyguard is about to go away. */
+    /**
+     * Observable for the signal that keyguard is about to go away.
+     *
+     * TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed.
+     */
+    @Deprecated(
+        "Use KeyguardTransitionInteractor flows instead. The closest match for 'going " +
+            "away' is isInTransitionToState(GONE), but consider using more specific flows " +
+            "whenever possible."
+    )
     val isKeyguardGoingAway: Flow<Boolean>
 
     /** Is the always-on display available to be used? */
@@ -117,6 +137,9 @@
     /** Observable for whether the device is dreaming with an overlay, see [DreamOverlayService] */
     val isDreamingWithOverlay: Flow<Boolean>
 
+    /** Observable for device dreaming state and the active dream is hosted in lockscreen */
+    val isActiveDreamLockscreenHosted: StateFlow<Boolean>
+
     /**
      * Observable for the amount of doze we are currently in.
      *
@@ -138,7 +161,10 @@
     val statusBarState: Flow<StatusBarState>
 
     /** Observable for device wake/sleep state */
-    val wakefulness: Flow<WakefulnessModel>
+    val wakefulness: StateFlow<WakefulnessModel>
+
+    /** Observable for device screen state */
+    val screenModel: StateFlow<ScreenModel>
 
     /** Observable for biometric unlock modes */
     val biometricUnlockState: Flow<BiometricUnlockModel>
@@ -155,6 +181,12 @@
     /** Whether quick settings or quick-quick settings is visible. */
     val isQuickSettingsVisible: Flow<Boolean>
 
+    /** Represents the current state of the KeyguardRootView visibility */
+    val keyguardRootViewVisibility: Flow<KeyguardRootViewVisibilityState>
+
+    /** Receive an event for doze time tick */
+    val dozeTimeTick: Flow<Long>
+
     /**
      * Returns `true` if the keyguard is showing; `false` otherwise.
      *
@@ -164,11 +196,28 @@
      */
     fun isKeyguardShowing(): Boolean
 
+    /**
+     * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
+     * dismissed once the authentication challenge is completed. For example, completing a biometric
+     * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
+     * lock screen.
+     */
+    fun isBypassEnabled(): Boolean
+
     /** Sets whether the bottom area UI should animate the transition out of doze state. */
     fun setAnimateDozingTransitions(animate: Boolean)
 
     /** Sets the current amount of alpha that should be used for rendering the bottom area. */
-    fun setBottomAreaAlpha(alpha: Float)
+    @Deprecated("Deprecated as part of b/278057014") fun setBottomAreaAlpha(alpha: Float)
+
+    /** Sets the current amount of alpha that should be used for rendering the keyguard. */
+    fun setKeyguardAlpha(alpha: Float)
+
+    fun setKeyguardVisibility(
+        statusBarState: Int,
+        goingToFullShade: Boolean,
+        occlusionTransitionRunning: Boolean
+    )
 
     /**
      * Sets the relative offset of the lock-screen clock from its natural position on the screen.
@@ -186,6 +235,10 @@
     fun setLastDozeTapToWakePosition(position: Point)
 
     fun setIsDozing(isDozing: Boolean)
+
+    fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean)
+
+    fun dozeTimeTick()
 }
 
 /** Encapsulates application state for the keyguard. */
@@ -195,14 +248,18 @@
 constructor(
     statusBarStateController: StatusBarStateController,
     wakefulnessLifecycle: WakefulnessLifecycle,
+    screenLifecycle: ScreenLifecycle,
     biometricUnlockController: BiometricUnlockController,
     private val keyguardStateController: KeyguardStateController,
+    private val keyguardBypassController: KeyguardBypassController,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val dozeTransitionListener: DozeTransitionListener,
     private val dozeParameters: DozeParameters,
     private val authController: AuthController,
     private val dreamOverlayCallbackController: DreamOverlayCallbackController,
-    @Main private val mainDispatcher: CoroutineDispatcher
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    @Application private val scope: CoroutineScope,
+    private val systemClock: SystemClock,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
@@ -211,6 +268,9 @@
     private val _bottomAreaAlpha = MutableStateFlow(1f)
     override val bottomAreaAlpha = _bottomAreaAlpha.asStateFlow()
 
+    private val _keyguardAlpha = MutableStateFlow(1f)
+    override val keyguardAlpha = _keyguardAlpha.asStateFlow()
+
     private val _clockPosition = MutableStateFlow(Position(0, 0))
     override val clockPosition = _clockPosition.asStateFlow()
 
@@ -242,23 +302,17 @@
     override val isAodAvailable: Flow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
-                    object : DozeParameters.Callback {
-                        override fun onAlwaysOnChange() {
-                            trySendWithFailureLogging(
-                                dozeParameters.getAlwaysOn(),
-                                TAG,
-                                "updated isAodAvailable"
-                            )
-                        }
+                    DozeParameters.Callback {
+                        trySendWithFailureLogging(
+                            dozeParameters.alwaysOn,
+                            TAG,
+                            "updated isAodAvailable"
+                        )
                     }
 
                 dozeParameters.addCallback(callback)
                 // Adding the callback does not send an initial update.
-                trySendWithFailureLogging(
-                    dozeParameters.getAlwaysOn(),
-                    TAG,
-                    "initial isAodAvailable"
-                )
+                trySendWithFailureLogging(dozeParameters.alwaysOn, TAG, "initial isAodAvailable")
 
                 awaitClose { dozeParameters.removeCallback(callback) }
             }
@@ -289,7 +343,7 @@
             }
             .distinctUntilChanged()
 
-    override val isKeyguardUnlocked: Flow<Boolean> =
+    override val isKeyguardUnlocked: StateFlow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
                     object : KeyguardStateController.Callback {
@@ -297,7 +351,15 @@
                             trySendWithFailureLogging(
                                 keyguardStateController.isUnlocked,
                                 TAG,
-                                "updated isKeyguardUnlocked"
+                                "updated isKeyguardUnlocked due to onUnlockedChanged"
+                            )
+                        }
+
+                        override fun onKeyguardShowingChanged() {
+                            trySendWithFailureLogging(
+                                keyguardStateController.isUnlocked,
+                                TAG,
+                                "updated isKeyguardUnlocked due to onKeyguardShowingChanged"
                             )
                         }
                     }
@@ -313,6 +375,11 @@
                 awaitClose { keyguardStateController.removeCallback(callback) }
             }
             .distinctUntilChanged()
+            .stateIn(
+                scope,
+                SharingStarted.Eagerly,
+                initialValue = false,
+            )
 
     override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
         val callback =
@@ -344,6 +411,13 @@
         _isDozing.value = isDozing
     }
 
+    private val _dozeTimeTick = MutableStateFlow<Long>(0)
+    override val dozeTimeTick = _dozeTimeTick.asStateFlow()
+
+    override fun dozeTimeTick() {
+        _dozeTimeTick.value = systemClock.uptimeMillis()
+    }
+
     private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
     override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
 
@@ -442,6 +516,10 @@
         return keyguardStateController.isShowing
     }
 
+    override fun isBypassEnabled(): Boolean {
+        return keyguardBypassController.bypassEnabled
+    }
+
     override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow {
         val callback =
             object : StatusBarStateController.StateListener {
@@ -486,47 +564,84 @@
         awaitClose { biometricUnlockController.removeListener(callback) }
     }
 
-    override val wakefulness: Flow<WakefulnessModel> = conflatedCallbackFlow {
-        val observer =
-            object : WakefulnessLifecycle.Observer {
-                override fun onStartedWakingUp() {
-                    dispatchNewState()
-                }
+    override val wakefulness: StateFlow<WakefulnessModel> =
+        conflatedCallbackFlow {
+                val observer =
+                    object : WakefulnessLifecycle.Observer {
+                        override fun onStartedWakingUp() {
+                            dispatchNewState()
+                        }
 
-                override fun onFinishedWakingUp() {
-                    dispatchNewState()
-                }
+                        override fun onFinishedWakingUp() {
+                            dispatchNewState()
+                        }
 
-                override fun onPostFinishedWakingUp() {
-                    dispatchNewState()
-                }
+                        override fun onPostFinishedWakingUp() {
+                            dispatchNewState()
+                        }
 
-                override fun onStartedGoingToSleep() {
-                    dispatchNewState()
-                }
+                        override fun onStartedGoingToSleep() {
+                            dispatchNewState()
+                        }
 
-                override fun onFinishedGoingToSleep() {
-                    dispatchNewState()
-                }
+                        override fun onFinishedGoingToSleep() {
+                            dispatchNewState()
+                        }
 
-                private fun dispatchNewState() {
-                    trySendWithFailureLogging(
-                        WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
-                        TAG,
-                        "updated wakefulness state"
-                    )
-                }
+                        private fun dispatchNewState() {
+                            trySendWithFailureLogging(
+                                WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
+                                TAG,
+                                "updated wakefulness state",
+                            )
+                        }
+                    }
+
+                wakefulnessLifecycle.addObserver(observer)
+                awaitClose { wakefulnessLifecycle.removeObserver(observer) }
             }
+            .stateIn(
+                scope,
+                // Use Eagerly so that we're always listening and never miss an event.
+                SharingStarted.Eagerly,
+                initialValue = WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
+            )
 
-        wakefulnessLifecycle.addObserver(observer)
-        trySendWithFailureLogging(
-            WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
-            TAG,
-            "initial wakefulness state"
-        )
+    override val screenModel: StateFlow<ScreenModel> =
+        conflatedCallbackFlow {
+                val observer =
+                    object : ScreenLifecycle.Observer {
+                        override fun onScreenTurningOn() {
+                            dispatchNewState()
+                        }
+                        override fun onScreenTurnedOn() {
+                            dispatchNewState()
+                        }
+                        override fun onScreenTurningOff() {
+                            dispatchNewState()
+                        }
+                        override fun onScreenTurnedOff() {
+                            dispatchNewState()
+                        }
 
-        awaitClose { wakefulnessLifecycle.removeObserver(observer) }
-    }
+                        private fun dispatchNewState() {
+                            trySendWithFailureLogging(
+                                ScreenModel.fromScreenLifecycle(screenLifecycle),
+                                TAG,
+                                "updated screen state",
+                            )
+                        }
+                    }
+
+                screenLifecycle.addObserver(observer)
+                awaitClose { screenLifecycle.removeObserver(observer) }
+            }
+            .stateIn(
+                scope,
+                // Use Eagerly so that we're always listening and never miss an event.
+                SharingStarted.Eagerly,
+                initialValue = ScreenModel.fromScreenLifecycle(screenLifecycle),
+            )
 
     override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow {
         fun sendFpLocation() {
@@ -596,6 +711,20 @@
     private val _isQuickSettingsVisible = MutableStateFlow(false)
     override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow()
 
+    private val _isActiveDreamLockscreenHosted = MutableStateFlow(false)
+    override val isActiveDreamLockscreenHosted = _isActiveDreamLockscreenHosted.asStateFlow()
+
+    private val _keyguardRootViewVisibility =
+        MutableStateFlow(
+            KeyguardRootViewVisibilityState(
+                com.android.systemui.statusbar.StatusBarState.SHADE,
+                goingToFullShade = false,
+                occlusionTransitionRunning = false,
+            )
+        )
+    override val keyguardRootViewVisibility: Flow<KeyguardRootViewVisibilityState> =
+        _keyguardRootViewVisibility.asStateFlow()
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.value = animate
     }
@@ -604,6 +733,23 @@
         _bottomAreaAlpha.value = alpha
     }
 
+    override fun setKeyguardAlpha(alpha: Float) {
+        _keyguardAlpha.value = alpha
+    }
+
+    override fun setKeyguardVisibility(
+        statusBarState: Int,
+        goingToFullShade: Boolean,
+        occlusionTransitionRunning: Boolean
+    ) {
+        _keyguardRootViewVisibility.value =
+            KeyguardRootViewVisibilityState(
+                statusBarState,
+                goingToFullShade,
+                occlusionTransitionRunning
+            )
+    }
+
     override fun setClockPosition(x: Int, y: Int) {
         _clockPosition.value = Position(x, y)
     }
@@ -614,6 +760,10 @@
         _isQuickSettingsVisible.value = isVisible
     }
 
+    override fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean) {
+        _isActiveDreamLockscreenHosted.value = isLockscreenHosted
+    }
+
     private fun statusBarStateIntToObject(value: Int): StatusBarState {
         return when (value) {
             0 -> StatusBarState.SHADE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 4055fd0..2f80106 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -17,9 +17,11 @@
 package com.android.systemui.keyguard.data.repository
 
 import com.android.systemui.CoreStartable
-import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
-import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepositoryImpl
-import com.android.systemui.keyguard.bouncer.domain.interactor.BouncerMessageAuditLogger
+import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
+import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageAuditLogger
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -30,6 +32,11 @@
     @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
 
     @Binds
+    fun keyguardSurfaceBehindRepository(
+        impl: KeyguardSurfaceBehindRepositoryImpl
+    ): KeyguardSurfaceBehindRepository
+
+    @Binds
     fun keyguardTransitionRepository(
         impl: KeyguardTransitionRepositoryImpl
     ): KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt
new file mode 100644
index 0000000..014b7fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * State related to SysUI's handling of the surface behind the keyguard (typically an app or the
+ * launcher). We manipulate this surface during unlock animations.
+ */
+interface KeyguardSurfaceBehindRepository {
+
+    /** Whether we're running animations on the surface. */
+    val isAnimatingSurface: Flow<Boolean>
+
+    /** Set whether we're running animations on the surface. */
+    fun setAnimatingSurface(animating: Boolean)
+}
+
+@SysUISingleton
+class KeyguardSurfaceBehindRepositoryImpl @Inject constructor() : KeyguardSurfaceBehindRepository {
+    private val _isAnimatingSurface = MutableStateFlow(false)
+    override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
+
+    override fun setAnimatingSurface(animating: Boolean) {
+        _isAnimatingSurface.value = animating
+    }
+}
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
index 358ab01..84cd3ef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -47,6 +47,11 @@
  * To create or modify logic that controls when and how transitions get created, look at
  * [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on
  * this repository.
+ *
+ * To print all transitions to logcat to help with debugging, run this command:
+ * adb shell settings put global systemui/buffer/KeyguardLog VERBOSE
+ *
+ * This will print all keyguard transitions to logcat with the KeyguardTransitionAuditLogger tag.
  */
 interface KeyguardTransitionRepository {
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
index 482e9a3d..1c0b73f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -20,10 +20,13 @@
 
 import android.content.Context
 import android.graphics.Point
+import androidx.core.animation.Animator
+import androidx.core.animation.ValueAnimator
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.WakeSleepReason.TAP
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LiftReveal
 import com.android.systemui.statusbar.LightRevealEffect
@@ -31,9 +34,12 @@
 import javax.inject.Inject
 import kotlin.math.max
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
@@ -52,6 +58,10 @@
      * at the current screen position of the appropriate sensor.
      */
     val revealEffect: Flow<LightRevealEffect>
+
+    val revealAmount: Flow<Float>
+
+    fun startRevealAmountAnimator(reveal: Boolean)
 }
 
 @SysUISingleton
@@ -108,13 +118,30 @@
 
     /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */
     private val nonBiometricRevealEffect: Flow<LightRevealEffect?> =
-        keyguardRepository.wakefulness.flatMapLatest { wakefulnessModel ->
-            when {
-                wakefulnessModel.isTransitioningFromPowerButton() -> powerButtonRevealEffect
-                wakefulnessModel.isAwakeFromTap() -> tapRevealEffect
-                else -> flowOf(LiftReveal)
+        keyguardRepository.wakefulness
+            .filter { it.isStartingToWake() || it.isStartingToSleep() }
+            .flatMapLatest { wakefulnessModel ->
+                when {
+                    wakefulnessModel.isTransitioningFromPowerButton() -> powerButtonRevealEffect
+                    wakefulnessModel.isWakingFrom(TAP) -> tapRevealEffect
+                    else -> flowOf(LiftReveal)
+                }
             }
-        }
+
+    private val revealAmountAnimator = ValueAnimator.ofFloat(0f, 1f).apply { duration = 500 }
+
+    override val revealAmount: Flow<Float> = callbackFlow {
+        val updateListener =
+            Animator.AnimatorUpdateListener {
+                trySend((it as ValueAnimator).animatedValue as Float)
+            }
+        revealAmountAnimator.addUpdateListener(updateListener)
+        awaitClose { revealAmountAnimator.removeUpdateListener(updateListener) }
+    }
+
+    override fun startRevealAmountAnimator(reveal: Boolean) {
+        if (reveal) revealAmountAnimator.start() else revealAmountAnimator.reverse()
+    }
 
     override val revealEffect =
         combine(
@@ -136,12 +163,13 @@
 
     private fun constructCircleRevealFromPoint(point: Point): LightRevealEffect {
         return with(point) {
+            val display = checkNotNull(context.display)
             CircleReveal(
                 x,
                 y,
                 startRadius = 0,
                 endRadius =
-                    max(max(x, context.display.width - x), max(y, context.display.height - y)),
+                    max(max(x, display.width - x), max(y, display.height - y)),
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
new file mode 100644
index 0000000..e4e6a6d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
@@ -0,0 +1,76 @@
+/*
+ *   Copyright (C) 2023 The Android Open Source Project
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * Implementation of the repository that noops all face auth operations.
+ *
+ * This is required for SystemUI variants that do not support face authentication but still inject
+ * other SysUI components that depend on [DeviceEntryFaceAuthRepository].
+ */
+@SysUISingleton
+class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceAuthRepository {
+    override val isAuthenticated: Flow<Boolean>
+        get() = emptyFlow()
+
+    private val _canRunFaceAuth = MutableStateFlow(false)
+    override val canRunFaceAuth: StateFlow<Boolean> = _canRunFaceAuth
+
+    override val authenticationStatus: Flow<FaceAuthenticationStatus>
+        get() = emptyFlow()
+
+    override val detectionStatus: Flow<FaceDetectionStatus>
+        get() = emptyFlow()
+
+    private val _isLockedOut = MutableStateFlow(false)
+    override val isLockedOut: StateFlow<Boolean> = _isLockedOut
+
+    private val _isAuthRunning = MutableStateFlow(false)
+    override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning
+
+    override val isBypassEnabled: Flow<Boolean>
+        get() = emptyFlow()
+
+    override fun lockoutFaceAuth() = Unit
+    override fun pauseFaceAuth() = Unit
+
+    override fun resumeFaceAuth() = Unit
+
+    /**
+     * Trigger face authentication.
+     *
+     * [uiEvent] provided should be logged whenever face authentication runs. Invocation should be
+     * ignored if face authentication is already running. Results should be propagated through
+     * [authenticationStatus]
+     *
+     * Run only face detection when [fallbackToDetection] is true and [canRunFaceAuth] is false.
+     */
+    override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {}
+
+    /** Stop currently running face authentication or detection. */
+    override fun cancel() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
deleted file mode 100644
index 148d425..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
+++ /dev/null
@@ -1,111 +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.keyguard.domain.interactor
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.time.SystemClock
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */
-@SysUISingleton
-class AlternateBouncerInteractor
-@Inject
-constructor(
-    private val statusBarStateController: StatusBarStateController,
-    private val keyguardStateController: KeyguardStateController,
-    private val bouncerRepository: KeyguardBouncerRepository,
-    private val biometricSettingsRepository: BiometricSettingsRepository,
-    private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
-    private val systemClock: SystemClock,
-) {
-    var receivedDownTouch = false
-    val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
-
-    /**
-     * Sets the correct bouncer states to show the alternate bouncer if it can show.
-     *
-     * @return whether alternateBouncer is visible
-     */
-    fun show(): Boolean {
-        bouncerRepository.setAlternateVisible(canShowAlternateBouncerForFingerprint())
-        return isVisibleState()
-    }
-
-    /**
-     * Sets the correct bouncer states to hide the bouncer. Should only be called through
-     * StatusBarKeyguardViewManager until ScrimController is refactored to use
-     * alternateBouncerInteractor.
-     *
-     * @return true if the alternate bouncer was newly hidden, else false.
-     */
-    fun hide(): Boolean {
-        receivedDownTouch = false
-        val wasAlternateBouncerVisible = isVisibleState()
-        bouncerRepository.setAlternateVisible(false)
-        return wasAlternateBouncerVisible && !isVisibleState()
-    }
-
-    fun isVisibleState(): Boolean {
-        return bouncerRepository.alternateBouncerVisible.value
-    }
-
-    fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
-        bouncerRepository.setAlternateBouncerUIAvailable(isAvailable)
-    }
-
-    fun canShowAlternateBouncerForFingerprint(): Boolean {
-        return bouncerRepository.alternateBouncerUIAvailable.value &&
-            biometricSettingsRepository.isFingerprintEnrolled.value &&
-            biometricSettingsRepository.isStrongBiometricAllowed.value &&
-            biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
-            !deviceEntryFingerprintAuthRepository.isLockedOut.value &&
-            !keyguardStateController.isUnlocked &&
-            !statusBarStateController.isDozing
-    }
-
-    /**
-     * Whether the alt bouncer has shown for a minimum time before allowing touches to dismiss the
-     * alternate bouncer and show the primary bouncer.
-     */
-    fun hasAlternateBouncerShownWithMinTime(): Boolean {
-        return (systemClock.uptimeMillis() - bouncerRepository.lastAlternateBouncerVisibleTime) >
-            MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS
-    }
-    /**
-     * Should only be called through StatusBarKeyguardViewManager which propagates the source of
-     * truth to other concerned controllers. Will hide the alternate bouncer if it's no longer
-     * allowed to show.
-     *
-     * @return true if the alternate bouncer was newly hidden, else false.
-     */
-    fun maybeHide(): Boolean {
-        if (isVisibleState() && !canShowAlternateBouncerForFingerprint()) {
-            return hide()
-        }
-        return false
-    }
-
-    companion object {
-        private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt
new file mode 100644
index 0000000..508f71a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt
@@ -0,0 +1,146 @@
+/*
+ *  Copyright (C) 2023 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.res.Resources
+import android.hardware.biometrics.BiometricSourceType
+import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
+import android.hardware.fingerprint.FingerprintManager
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.util.IndicationHelper
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+
+/**
+ * BiometricMessage business logic. Filters biometric error/acquired/fail/success events for
+ * authentication events that should never surface a message to the user at the current device
+ * state.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class BiometricMessageInteractor
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+    private val fingerprintPropertyRepository: FingerprintPropertyRepository,
+    private val indicationHelper: IndicationHelper,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) {
+    val fingerprintErrorMessage: Flow<BiometricMessage> =
+        fingerprintAuthRepository.authenticationStatus
+            .filter {
+                it is ErrorFingerprintAuthenticationStatus &&
+                    !indicationHelper.shouldSuppressErrorMsg(FINGERPRINT, it.msgId)
+            }
+            .map {
+                val errorStatus = it as ErrorFingerprintAuthenticationStatus
+                BiometricMessage(
+                    FINGERPRINT,
+                    BiometricMessageType.ERROR,
+                    errorStatus.msgId,
+                    errorStatus.msg,
+                )
+            }
+
+    val fingerprintHelpMessage: Flow<BiometricMessage> =
+        fingerprintAuthRepository.authenticationStatus
+            .filter { it is HelpFingerprintAuthenticationStatus }
+            .filterNot { isPrimaryAuthRequired() }
+            .map {
+                val helpStatus = it as HelpFingerprintAuthenticationStatus
+                BiometricMessage(
+                    FINGERPRINT,
+                    BiometricMessageType.HELP,
+                    helpStatus.msgId,
+                    helpStatus.msg,
+                )
+            }
+
+    val fingerprintFailMessage: Flow<BiometricMessage> =
+        isUdfps().flatMapLatest { isUdfps ->
+            fingerprintAuthRepository.authenticationStatus
+                .filter { it is FailFingerprintAuthenticationStatus }
+                .filterNot { isPrimaryAuthRequired() }
+                .map {
+                    BiometricMessage(
+                        FINGERPRINT,
+                        BiometricMessageType.FAIL,
+                        BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+                        if (isUdfps) {
+                            resources.getString(
+                                com.android.internal.R.string.fingerprint_udfps_error_not_match
+                            )
+                        } else {
+                            resources.getString(
+                                com.android.internal.R.string.fingerprint_error_not_match
+                            )
+                        },
+                    )
+                }
+        }
+
+    private fun isUdfps() =
+        fingerprintPropertyRepository.sensorType.map {
+            it == FingerprintSensorType.UDFPS_OPTICAL ||
+                it == FingerprintSensorType.UDFPS_ULTRASONIC
+        }
+
+    private fun isPrimaryAuthRequired(): Boolean {
+        // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+        // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to
+        // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+        // check of whether non-strong biometric is allowed since strong biometrics can still be
+        // used.
+        return !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
+    }
+}
+
+data class BiometricMessage(
+    val source: BiometricSourceType,
+    val type: BiometricMessageType,
+    val id: Int,
+    val message: String?,
+) {
+    fun isFingerprintLockoutMessage(): Boolean {
+        return source == FINGERPRINT &&
+            type == BiometricMessageType.ERROR &&
+            (id == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT ||
+                id == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
+    }
+}
+
+enum class BiometricMessageType {
+    HELP,
+    ERROR,
+    FAIL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
index 252982f..ac936b1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
@@ -24,14 +24,11 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.stateIn
@@ -46,24 +43,17 @@
     private val burnInHelperWrapper: BurnInHelperWrapper,
     @Application private val scope: CoroutineScope,
     private val configurationRepository: ConfigurationRepository,
-    private val systemClock: SystemClock,
+    private val keyguardInteractor: KeyguardInteractor,
 ) {
-    private val _dozeTimeTick = MutableStateFlow<Long>(0)
-    val dozeTimeTick: StateFlow<Long> = _dozeTimeTick.asStateFlow()
-
     val udfpsBurnInXOffset: StateFlow<Int> =
         burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true)
     val udfpsBurnInYOffset: StateFlow<Int> =
         burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false)
     val udfpsBurnInProgress: StateFlow<Float> =
-        dozeTimeTick
+        keyguardInteractor.dozeTimeTick
             .mapLatest { burnInHelperWrapper.burnInProgressOffset() }
             .stateIn(scope, SharingStarted.Lazily, burnInHelperWrapper.burnInProgressOffset())
 
-    fun dozeTimeTick() {
-        _dozeTimeTick.value = systemClock.uptimeMillis()
-    }
-
     /**
      * Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the
      * max burn-in offset on any configuration changes. If the max burn-in offset is specified in
@@ -77,7 +67,9 @@
             .flatMapLatest {
                 val maxBurnInOffsetPixels =
                     context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
-                dozeTimeTick.mapLatest { calculateOffset(maxBurnInOffsetPixels, isXAxis) }
+                keyguardInteractor.dozeTimeTick.mapLatest {
+                    calculateOffset(maxBurnInOffsetPixels, isXAxis)
+                }
             }
             .stateIn(
                 scope,
@@ -102,7 +94,9 @@
             .flatMapLatest { scale ->
                 val maxBurnInOffsetPixels =
                     context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
-                dozeTimeTick.mapLatest { calculateOffset(maxBurnInOffsetPixels, isXAxis, scale) }
+                keyguardInteractor.dozeTimeTick.mapLatest {
+                    calculateOffset(maxBurnInOffsetPixels, isXAxis, scale)
+                }
             }
             .stateIn(
                 scope,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
index 2efcd0c..0c898be 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
@@ -35,4 +35,8 @@
     fun setLastTapToWakePosition(position: Point) {
         keyguardRepository.setLastDozeTapToWakePosition(position)
     }
+
+    fun dozeTimeTick() {
+        keyguardRepository.dozeTimeTick()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 38eacce..271bc38 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -17,16 +17,15 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 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 com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.Utils.Companion.toQuint
 import com.android.systemui.util.kotlin.sample
+import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
@@ -38,11 +37,14 @@
 class FromAlternateBouncerTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromAlternateBouncerTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.ALTERNATE_BOUNCER,
+    ) {
 
     override fun start() {
         listenForAlternateBouncerToGone()
@@ -60,7 +62,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.primaryBouncerShowing,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.wakefulnessModel,
                         keyguardInteractor.isAodAvailable,
                         ::toQuad
@@ -92,14 +94,7 @@
                             } else {
                                 KeyguardState.LOCKSCREEN
                             }
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.ALTERNATE_BOUNCER,
-                                to = to,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(to)
                     }
                 }
         }
@@ -108,17 +103,10 @@
     private fun listenForAlternateBouncerToGone() {
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
+                .sample(transitionInteractor.finishedKeyguardState, ::Pair)
                 .collect { (isKeyguardGoingAway, keyguardState) ->
                     if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.ALTERNATE_BOUNCER,
-                                to = KeyguardState.GONE,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.GONE)
                     }
                 }
         }
@@ -127,26 +115,19 @@
     private fun listenForAlternateBouncerToPrimaryBouncer() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (isPrimaryBouncerShowing, startedKeyguardState) ->
                     if (
                         isPrimaryBouncerShowing &&
                             startedKeyguardState.to == KeyguardState.ALTERNATE_BOUNCER
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.ALTERNATE_BOUNCER,
-                                to = KeyguardState.PRIMARY_BOUNCER,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
                     }
                 }
         }
     }
 
-    private fun getAnimator(): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
             interpolator = Interpolators.LINEAR
             duration = TRANSITION_DURATION_MS
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 7e9cbc1..888f746 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -24,42 +24,47 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 @SysUISingleton
 class FromAodTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromAodTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.AOD,
+    ) {
 
     override fun start() {
-        listenForAodToLockscreen()
+        listenForAodToLockscreenOrOccluded()
         listenForAodToGone()
     }
 
-    private fun listenForAodToLockscreen() {
+    private fun listenForAodToLockscreenOrOccluded() {
         scope.launch {
             keyguardInteractor
                 .dozeTransitionTo(DozeStateModel.FINISH)
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
-                .collect { pair ->
-                    val (dozeToAod, lastStartedStep) = pair
+                .sample(
+                    combine(
+                        transitionInteractor.startedKeyguardTransitionStep,
+                        keyguardInteractor.isKeyguardOccluded,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect { (_, lastStartedStep, occluded) ->
                     if (lastStartedStep.to == KeyguardState.AOD) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.AOD,
-                                KeyguardState.LOCKSCREEN,
-                                getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
                         )
                     }
                 }
@@ -69,29 +74,22 @@
     private fun listenForAodToGone() {
         scope.launch {
             keyguardInteractor.biometricUnlockState
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
+                .sample(transitionInteractor.finishedKeyguardState, ::Pair)
                 .collect { pair ->
                     val (biometricUnlockState, keyguardState) = pair
                     if (
                         keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.AOD,
-                                KeyguardState.GONE,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.GONE)
                     }
                 }
         }
     }
 
-    private fun getAnimator(): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(TRANSITION_DURATION_MS)
+            interpolator = Interpolators.LINEAR
+            duration = TRANSITION_DURATION_MS
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index ee2c2df..76d9893 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -23,45 +23,50 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 @SysUISingleton
 class FromDozingTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromDozingTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.DOZING,
+    ) {
 
     override fun start() {
-        listenForDozingToLockscreen()
+        listenForDozingToLockscreenOrOccluded()
         listenForDozingToGone()
     }
 
-    private fun listenForDozingToLockscreen() {
+    private fun listenForDozingToLockscreenOrOccluded() {
         scope.launch {
             keyguardInteractor.wakefulnessModel
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
-                .collect { (wakefulnessModel, lastStartedTransition) ->
+                .sample(
+                    combine(
+                        transitionInteractor.startedKeyguardTransitionStep,
+                        keyguardInteractor.isKeyguardOccluded,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect { (wakefulnessModel, lastStartedTransition, occluded) ->
                     if (
                         wakefulnessModel.isStartingToWakeOrAwake() &&
                             lastStartedTransition.to == KeyguardState.DOZING
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.DOZING,
-                                KeyguardState.LOCKSCREEN,
-                                getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
                         )
                     }
                 }
@@ -71,29 +76,22 @@
     private fun listenForDozingToGone() {
         scope.launch {
             keyguardInteractor.biometricUnlockState
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (biometricUnlockState, lastStartedTransition) ->
                     if (
                         lastStartedTransition.to == KeyguardState.DOZING &&
                             isWakeAndUnlock(biometricUnlockState)
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.DOZING,
-                                KeyguardState.GONE,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.GONE)
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration = DEFAULT_DURATION.inWholeMilliseconds
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
new file mode 100644
index 0000000..dac6ef5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.app.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class FromDreamingLockscreenHostedTransitionInteractor
+@Inject
+constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
+    @Application private val scope: CoroutineScope,
+    private val keyguardInteractor: KeyguardInteractor,
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+    ) {
+
+    override fun start() {
+        listenForDreamingLockscreenHostedToLockscreen()
+        listenForDreamingLockscreenHostedToGone()
+        listenForDreamingLockscreenHostedToDozing()
+        listenForDreamingLockscreenHostedToOccluded()
+        listenForDreamingLockscreenHostedToPrimaryBouncer()
+    }
+
+    private fun listenForDreamingLockscreenHostedToLockscreen() {
+        scope.launch {
+            keyguardInteractor.isActiveDreamLockscreenHosted
+                // Add a slight delay to prevent transitioning to lockscreen from happening too soon
+                // as dozing can arrive in a slight gap after the lockscreen hosted dream stops.
+                .onEach { delay(50) }
+                .sample(
+                    combine(
+                        keyguardInteractor.dozeTransitionModel,
+                        transitionInteractor.startedKeyguardTransitionStep,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect {
+                    (isActiveDreamLockscreenHosted, dozeTransitionModel, lastStartedTransition) ->
+                    if (
+                        !isActiveDreamLockscreenHosted &&
+                            DozeStateModel.isDozeOff(dozeTransitionModel.to) &&
+                            lastStartedTransition.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+                    ) {
+                        startTransitionTo(KeyguardState.LOCKSCREEN)
+                    }
+                }
+        }
+    }
+
+    private fun listenForDreamingLockscreenHostedToOccluded() {
+        scope.launch {
+            keyguardInteractor.isActiveDreamLockscreenHosted
+                .sample(
+                    combine(
+                        keyguardInteractor.isKeyguardOccluded,
+                        transitionInteractor.startedKeyguardTransitionStep,
+                        ::Pair,
+                    ),
+                    ::toTriple
+                )
+                .collect { (isActiveDreamLockscreenHosted, isOccluded, lastStartedTransition) ->
+                    if (
+                        isOccluded &&
+                            !isActiveDreamLockscreenHosted &&
+                            lastStartedTransition.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+                    ) {
+                        startTransitionTo(KeyguardState.OCCLUDED)
+                    }
+                }
+        }
+    }
+
+    private fun listenForDreamingLockscreenHostedToPrimaryBouncer() {
+        scope.launch {
+            keyguardInteractor.primaryBouncerShowing
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { (isBouncerShowing, lastStartedTransitionStep) ->
+                    if (
+                        isBouncerShowing &&
+                            lastStartedTransitionStep.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+                    ) {
+                        startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+                    }
+                }
+        }
+    }
+
+    private fun listenForDreamingLockscreenHostedToGone() {
+        scope.launch {
+            keyguardInteractor.biometricUnlockState
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { (biometricUnlockState, lastStartedTransitionStep) ->
+                    if (
+                        lastStartedTransitionStep.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED &&
+                            biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+                    ) {
+                        startTransitionTo(KeyguardState.GONE)
+                    }
+                }
+        }
+    }
+
+    private fun listenForDreamingLockscreenHostedToDozing() {
+        scope.launch {
+            combine(
+                    keyguardInteractor.dozeTransitionModel,
+                    transitionInteractor.startedKeyguardTransitionStep,
+                    ::Pair
+                )
+                .collect { (dozeTransitionModel, lastStartedTransitionStep) ->
+                    if (
+                        dozeTransitionModel.to == DozeStateModel.DOZE &&
+                            lastStartedTransitionStep.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+                    ) {
+                        startTransitionTo(KeyguardState.DOZING)
+                    }
+                }
+        }
+    }
+
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
+        return ValueAnimator().apply {
+            interpolator = Interpolators.LINEAR
+            duration =
+                if (toState == KeyguardState.LOCKSCREEN) TO_LOCKSCREEN_DURATION.inWholeMilliseconds
+                else DEFAULT_DURATION.inWholeMilliseconds
+        }
+    }
+
+    companion object {
+        private val DEFAULT_DURATION = 500.milliseconds
+        val TO_LOCKSCREEN_DURATION = 1167.milliseconds
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index ccf4bc1..954ff6f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -24,11 +24,9 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
@@ -40,11 +38,14 @@
 class FromDreamingTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromDreamingTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.DREAMING,
+    ) {
 
     override fun start() {
         listenForDreamingToOccluded()
@@ -54,15 +55,8 @@
 
     fun startToLockscreenTransition() {
         scope.launch {
-            if (keyguardTransitionInteractor.startedKeyguardState.value == KeyguardState.DREAMING) {
-                keyguardTransitionRepository.startTransition(
-                    TransitionInfo(
-                        name,
-                        KeyguardState.DREAMING,
-                        KeyguardState.LOCKSCREEN,
-                        getAnimator(TO_LOCKSCREEN_DURATION),
-                    )
-                )
+            if (transitionInteractor.startedKeyguardState.value == KeyguardState.DREAMING) {
+                startTransitionTo(KeyguardState.LOCKSCREEN)
             }
         }
     }
@@ -76,7 +70,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardOccluded,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         ::Pair,
                     ),
                     ::toTriple
@@ -92,14 +86,7 @@
                         // action. There's no great signal to determine when the dream is ending
                         // and a transition to OCCLUDED is beginning directly. For now, the solution
                         // is DREAMING->LOCKSCREEN->OCCLUDED
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                lastStartedTransition.to,
-                                KeyguardState.OCCLUDED,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.OCCLUDED)
                     }
                 }
         }
@@ -107,18 +94,16 @@
 
     private fun listenForDreamingToGone() {
         scope.launch {
-            keyguardInteractor.biometricUnlockState.collect { biometricUnlockState ->
-                if (biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM) {
-                    keyguardTransitionRepository.startTransition(
-                        TransitionInfo(
-                            name,
-                            KeyguardState.DREAMING,
-                            KeyguardState.GONE,
-                            getAnimator(),
-                        )
-                    )
+            keyguardInteractor.biometricUnlockState
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { (biometricUnlockState, lastStartedTransitionStep) ->
+                    if (
+                        lastStartedTransitionStep.to == KeyguardState.DREAMING &&
+                            biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+                    ) {
+                        startTransitionTo(KeyguardState.GONE)
+                    }
                 }
-            }
         }
     }
 
@@ -126,7 +111,7 @@
         scope.launch {
             combine(
                     keyguardInteractor.dozeTransitionModel,
-                    keyguardTransitionInteractor.finishedKeyguardState,
+                    transitionInteractor.finishedKeyguardState,
                     ::Pair
                 )
                 .collect { (dozeTransitionModel, keyguardState) ->
@@ -134,23 +119,18 @@
                         dozeTransitionModel.to == DozeStateModel.DOZE &&
                             keyguardState == KeyguardState.DREAMING
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.DREAMING,
-                                KeyguardState.DOZING,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.DOZING)
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration =
+                if (toState == KeyguardState.LOCKSCREEN) TO_LOCKSCREEN_DURATION.inWholeMilliseconds
+                else DEFAULT_DURATION.inWholeMilliseconds
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index cfcb654..2b08b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -22,12 +22,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 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 com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
@@ -37,33 +35,42 @@
 class FromGoneTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromGoneTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.GONE,
+    ) {
 
     override fun start() {
         listenForGoneToAodOrDozing()
         listenForGoneToDreaming()
         listenForGoneToLockscreen()
+        listenForGoneToDreamingLockscreenHosted()
     }
 
     // Primarily for when the user chooses to lock down the device
     private fun listenForGoneToLockscreen() {
         scope.launch {
             keyguardInteractor.isKeyguardShowing
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (isKeyguardShowing, lastStartedStep) ->
                     if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.GONE,
-                                KeyguardState.LOCKSCREEN,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.LOCKSCREEN)
+                    }
+                }
+        }
+    }
+
+    private fun listenForGoneToDreamingLockscreenHosted() {
+        scope.launch {
+            keyguardInteractor.isActiveDreamLockscreenHosted
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { (isActiveDreamLockscreenHosted, lastStartedStep) ->
+                    if (isActiveDreamLockscreenHosted && lastStartedStep.to == KeyguardState.GONE) {
+                        startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
                     }
                 }
         }
@@ -72,17 +79,21 @@
     private fun listenForGoneToDreaming() {
         scope.launch {
             keyguardInteractor.isAbleToDream
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
-                .collect { (isAbleToDream, lastStartedStep) ->
-                    if (isAbleToDream && lastStartedStep.to == KeyguardState.GONE) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.GONE,
-                                KeyguardState.DREAMING,
-                                getAnimator(TO_DREAMING_DURATION),
-                            )
-                        )
+                .sample(
+                    combine(
+                        transitionInteractor.startedKeyguardTransitionStep,
+                        keyguardInteractor.isActiveDreamLockscreenHosted,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect { (isAbleToDream, lastStartedStep, isActiveDreamLockscreenHosted) ->
+                    if (
+                        isAbleToDream &&
+                            lastStartedStep.to == KeyguardState.GONE &&
+                            !isActiveDreamLockscreenHosted
+                    ) {
+                        startTransitionTo(KeyguardState.DREAMING)
                     }
                 }
         }
@@ -93,7 +104,7 @@
             keyguardInteractor.wakefulnessModel
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
@@ -104,30 +115,24 @@
                         lastStartedStep.to == KeyguardState.GONE &&
                             wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.GONE,
-                                if (isAodAvailable) {
-                                    KeyguardState.AOD
-                                } else {
-                                    KeyguardState.DOZING
-                                },
-                                getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
                         )
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration =
+                when (toState) {
+                    KeyguardState.DREAMING -> TO_DREAMING_DURATION
+                    else -> DEFAULT_DURATION
+                }.inWholeMilliseconds
         }
     }
-
     companion object {
         private val DEFAULT_DURATION = 500.milliseconds
         val TO_DREAMING_DURATION = 933.milliseconds
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index b5e289f..aa771fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,8 +20,11 @@
 import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -32,25 +35,33 @@
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
 @SysUISingleton
 class FromLockscreenTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
+    private val flags: FeatureFlags,
     private val shadeRepository: ShadeRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-) : TransitionInteractor(FromLockscreenTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.LOCKSCREEN,
+    ) {
 
     override fun start() {
         listenForLockscreenToGone()
+        listenForLockscreenToGoneDragging()
         listenForLockscreenToOccluded()
         listenForLockscreenToCamera()
         listenForLockscreenToAodOrDozing()
@@ -60,32 +71,92 @@
         listenForLockscreenToAlternateBouncer()
     }
 
+    /**
+     * Whether we want the surface behind the keyguard visible for the transition from LOCKSCREEN,
+     * or null if we don't care and should just use a reasonable default.
+     *
+     * [KeyguardSurfaceBehindInteractor] will switch to this flow whenever a transition from
+     * LOCKSCREEN is running.
+     */
+    val surfaceBehindVisibility: Flow<Boolean?> =
+        transitionInteractor.startedKeyguardTransitionStep
+            .map { startedStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    // LOCKSCREEN to anything but GONE does not require any special surface
+                    // visibility handling.
+                    return@map null
+                }
+
+                true // TODO(b/278086361): Implement continuous swipe to unlock.
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
+    /**
+     * The surface behind view params to use for the transition from LOCKSCREEN, or null if we don't
+     * care and should use a reasonable default.
+     */
+    val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.transitionStepsFromState(KeyguardState.LOCKSCREEN)
+            ) { startedStep, fromLockscreenStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    // Only LOCKSCREEN -> GONE has specific surface params (for the unlock
+                    // animation).
+                    return@combine null
+                } else if (fromLockscreenStep.value > 0.5f) {
+                    // Start the animation once we're 50% transitioned to GONE.
+                    KeyguardSurfaceBehindModel(
+                        animateFromAlpha = 0f,
+                        alpha = 1f,
+                        animateFromTranslationY = 500f,
+                        translationY = 0f
+                    )
+                } else {
+                    KeyguardSurfaceBehindModel(
+                        alpha = 0f,
+                    )
+                }
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
     private fun listenForLockscreenToDreaming() {
         val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
         scope.launch {
             keyguardInteractor.isAbleToDream
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
-                        keyguardTransitionInteractor.finishedKeyguardState,
-                        ::Pair
+                        transitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.finishedKeyguardState,
+                        keyguardInteractor.isActiveDreamLockscreenHosted,
+                        ::Triple
                     ),
-                    ::toTriple
+                    ::toQuad
                 )
-                .collect { (isAbleToDream, lastStartedTransition, finishedKeyguardState) ->
+                .collect {
+                    (
+                        isAbleToDream,
+                        lastStartedTransition,
+                        finishedKeyguardState,
+                        isActiveDreamLockscreenHosted) ->
                     val isOnLockscreen = finishedKeyguardState == KeyguardState.LOCKSCREEN
                     val isTransitionInterruptible =
                         lastStartedTransition.to == KeyguardState.LOCKSCREEN &&
                             !invalidFromStates.contains(lastStartedTransition.from)
                     if (isAbleToDream && (isOnLockscreen || isTransitionInterruptible)) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.LOCKSCREEN,
-                                KeyguardState.DREAMING,
-                                getAnimator(TO_DREAMING_DURATION),
-                            )
-                        )
+                        if (isActiveDreamLockscreenHosted) {
+                            startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                        } else {
+                            startTransitionTo(KeyguardState.DREAMING)
+                        }
                     }
                 }
         }
@@ -94,20 +165,13 @@
     private fun listenForLockscreenToPrimaryBouncer() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isBouncerShowing, lastStartedTransitionStep) = pair
                     if (
                         isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.LOCKSCREEN,
-                                to = KeyguardState.PRIMARY_BOUNCER,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
                     }
                 }
         }
@@ -116,21 +180,14 @@
     private fun listenForLockscreenToAlternateBouncer() {
         scope.launch {
             keyguardInteractor.alternateBouncerShowing
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isAlternateBouncerShowing, lastStartedTransitionStep) = pair
                     if (
                         isAlternateBouncerShowing &&
                             lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.LOCKSCREEN,
-                                to = KeyguardState.ALTERNATE_BOUNCER,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.ALTERNATE_BOUNCER)
                     }
                 }
         }
@@ -143,7 +200,7 @@
             shadeRepository.shadeModel
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.statusBarState,
                         keyguardInteractor.isKeyguardUnlocked,
                         ::Triple
@@ -164,7 +221,7 @@
                                 } else {
                                     TransitionState.RUNNING
                                 }
-                            keyguardTransitionRepository.updateTransition(
+                            transitionRepository.updateTransition(
                                 id,
                                 1f - shadeModel.expansionAmount,
                                 nextState,
@@ -178,13 +235,19 @@
                             }
 
                             // If canceled, just put the state back
+                            // TODO(b/278086361): This logic should happen in
+                            //  FromPrimaryBouncerInteractor.
                             if (nextState == TransitionState.CANCELED) {
-                                keyguardTransitionRepository.startTransition(
+                                transitionRepository.startTransition(
                                     TransitionInfo(
                                         ownerName = name,
                                         from = KeyguardState.PRIMARY_BOUNCER,
                                         to = KeyguardState.LOCKSCREEN,
-                                        animator = getAnimator(0.milliseconds)
+                                        animator =
+                                            getDefaultAnimatorForTransitionsToState(
+                                                    KeyguardState.LOCKSCREEN
+                                                )
+                                                .apply { duration = 0 }
                                     )
                                 )
                             }
@@ -198,36 +261,46 @@
                                 !isKeyguardUnlocked &&
                                 statusBarState == KEYGUARD
                         ) {
-                            transitionId =
-                                keyguardTransitionRepository.startTransition(
-                                    TransitionInfo(
-                                        ownerName = name,
-                                        from = KeyguardState.LOCKSCREEN,
-                                        to = KeyguardState.PRIMARY_BOUNCER,
-                                        animator = null,
-                                    )
-                                )
+                            transitionId = startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
                         }
                     }
                 }
         }
     }
 
+    fun dismissKeyguard() {
+        startTransitionTo(KeyguardState.GONE)
+    }
+
     private fun listenForLockscreenToGone() {
+        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            return
+        }
+
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (isKeyguardGoingAway, lastStartedStep) = pair
                     if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.LOCKSCREEN,
-                                KeyguardState.GONE,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.GONE)
+                    }
+                }
+        }
+    }
+
+    private fun listenForLockscreenToGoneDragging() {
+        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            return
+        }
+
+        scope.launch {
+            keyguardInteractor.isKeyguardGoingAway
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (isKeyguardGoingAway, lastStartedStep) = pair
+                    if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                        startTransitionTo(KeyguardState.GONE)
                     }
                 }
         }
@@ -238,7 +311,7 @@
             keyguardInteractor.isKeyguardOccluded
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.finishedKeyguardState,
+                        transitionInteractor.finishedKeyguardState,
                         keyguardInteractor.isDreaming,
                         ::Pair
                     ),
@@ -246,14 +319,7 @@
                 )
                 .collect { (isOccluded, keyguardState, isDreaming) ->
                     if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                keyguardState,
-                                KeyguardState.OCCLUDED,
-                                getAnimator(TO_OCCLUDED_DURATION),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.OCCLUDED)
                     }
                 }
         }
@@ -263,7 +329,7 @@
     private fun listenForLockscreenToCamera() {
         scope.launch {
             keyguardInteractor.onCameraLaunchDetected
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (_, lastStartedStep) ->
                     // DREAMING/AOD/OFF may trigger on the first power button push, so include this
                     // state in order to cancel and correct the transition
@@ -274,14 +340,7 @@
                             lastStartedStep.to == KeyguardState.AOD ||
                             lastStartedStep.to == KeyguardState.OFF
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.LOCKSCREEN,
-                                KeyguardState.OCCLUDED,
-                                getAnimator(TO_OCCLUDED_DURATION),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.OCCLUDED)
                     }
                 }
         }
@@ -292,7 +351,7 @@
             keyguardInteractor.wakefulnessModel
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
@@ -303,32 +362,28 @@
                         lastStartedStep.to == KeyguardState.LOCKSCREEN &&
                             wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.LOCKSCREEN,
-                                if (isAodAvailable) {
-                                    KeyguardState.AOD
-                                } else {
-                                    KeyguardState.DOZING
-                                },
-                                getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
                         )
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration =
+                when (toState) {
+                    KeyguardState.DREAMING -> TO_DREAMING_DURATION
+                    KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
+                    else -> DEFAULT_DURATION
+                }.inWholeMilliseconds
         }
     }
 
     companion object {
-        private val DEFAULT_DURATION = 500.milliseconds
+        private val DEFAULT_DURATION = 400.milliseconds
         val TO_DREAMING_DURATION = 933.milliseconds
         val TO_OCCLUDED_DURATION = 450.milliseconds
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index b0dbc59..ff0db34 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -22,12 +22,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 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 com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.combine
@@ -37,11 +35,14 @@
 class FromOccludedTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(FromOccludedTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.OCCLUDED,
+    ) {
 
     override fun start() {
         listenForOccludedToLockscreen()
@@ -54,18 +55,11 @@
     private fun listenForOccludedToDreaming() {
         scope.launch {
             keyguardInteractor.isAbleToDream
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
+                .sample(transitionInteractor.finishedKeyguardState, ::Pair)
                 .collect { pair ->
                     val (isAbleToDream, keyguardState) = pair
                     if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.OCCLUDED,
-                                KeyguardState.DREAMING,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.DREAMING)
                     }
                 }
         }
@@ -77,7 +71,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardShowing,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         ::Pair
                     ),
                     ::toTriple
@@ -90,14 +84,7 @@
                             isShowing &&
                             lastStartedKeyguardState.to == KeyguardState.OCCLUDED
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.OCCLUDED,
-                                KeyguardState.LOCKSCREEN,
-                                getAnimator(TO_LOCKSCREEN_DURATION),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.LOCKSCREEN)
                     }
                 }
         }
@@ -109,7 +96,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardShowing,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         ::Pair
                     ),
                     ::toTriple
@@ -122,14 +109,7 @@
                             !isShowing &&
                             lastStartedKeyguardState.to == KeyguardState.OCCLUDED
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.OCCLUDED,
-                                KeyguardState.GONE,
-                                getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.GONE)
                     }
                 }
         }
@@ -140,7 +120,7 @@
             keyguardInteractor.wakefulnessModel
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
@@ -151,17 +131,8 @@
                         lastStartedStep.to == KeyguardState.OCCLUDED &&
                             wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.OCCLUDED,
-                                if (isAodAvailable) {
-                                    KeyguardState.AOD
-                                } else {
-                                    KeyguardState.DOZING
-                                },
-                                getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
                         )
                     }
                 }
@@ -171,29 +142,31 @@
     private fun listenForOccludedToAlternateBouncer() {
         scope.launch {
             keyguardInteractor.alternateBouncerShowing
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (isAlternateBouncerShowing, lastStartedTransitionStep) ->
                     if (
                         isAlternateBouncerShowing &&
                             lastStartedTransitionStep.to == KeyguardState.OCCLUDED
                     ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.OCCLUDED,
-                                to = KeyguardState.ALTERNATE_BOUNCER,
-                                animator = getAnimator(),
-                            )
-                        )
+                        startTransitionTo(KeyguardState.ALTERNATE_BOUNCER)
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator =
+                when (toState) {
+                    KeyguardState.ALTERNATE_BOUNCER -> Interpolators.FAST_OUT_SLOW_IN
+                    else -> Interpolators.LINEAR
+                }
+
+            duration =
+                when (toState) {
+                    KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+                    else -> DEFAULT_DURATION
+                }.inWholeMilliseconds
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index da09e1f..c9f32da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -17,40 +17,101 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 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 com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
+import com.android.systemui.util.kotlin.Utils.Companion.toQuint
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
+import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
 @SysUISingleton
 class FromPrimaryBouncerTransitionInteractor
 @Inject
 constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val flags: FeatureFlags,
     private val keyguardSecurityModel: KeyguardSecurityModel,
-) : TransitionInteractor(FromPrimaryBouncerTransitionInteractor::class.simpleName!!) {
+) :
+    TransitionInteractor(
+        fromState = KeyguardState.PRIMARY_BOUNCER,
+    ) {
 
     override fun start() {
         listenForPrimaryBouncerToGone()
         listenForPrimaryBouncerToAodOrDozing()
         listenForPrimaryBouncerToLockscreenOrOccluded()
+        listenForPrimaryBouncerToDreamingLockscreenHosted()
+    }
+
+    val surfaceBehindVisibility: Flow<Boolean?> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
+            ) { startedStep, fromBouncerStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    return@combine null
+                }
+
+                fromBouncerStep.value > 0.5f
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
+    val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
+            ) { startedStep, fromBouncerStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    // BOUNCER to anything but GONE does not require any special surface
+                    // visibility handling.
+                    return@combine null
+                }
+
+                if (fromBouncerStep.value > 0.5f) {
+                    KeyguardSurfaceBehindModel(
+                        animateFromAlpha = 0f,
+                        alpha = 1f,
+                        animateFromTranslationY = 500f,
+                        translationY = 0f,
+                    )
+                } else {
+                    KeyguardSurfaceBehindModel(
+                        alpha = 0f,
+                    )
+                }
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
+    fun dismissPrimaryBouncer() {
+        startTransitionTo(KeyguardState.GONE)
     }
 
     private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
@@ -59,34 +120,29 @@
                 .sample(
                     combine(
                         keyguardInteractor.wakefulnessModel,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isKeyguardOccluded,
-                        ::Triple
+                        keyguardInteractor.isActiveDreamLockscreenHosted,
+                        ::toQuad
                     ),
-                    ::toQuad
+                    ::toQuint
                 )
-                .collect { (isBouncerShowing, wakefulnessState, lastStartedTransitionStep, occluded)
-                    ->
+                .collect {
+                    (
+                        isBouncerShowing,
+                        wakefulnessState,
+                        lastStartedTransitionStep,
+                        occluded,
+                        isActiveDreamLockscreenHosted) ->
                     if (
                         !isBouncerShowing &&
                             lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
                             (wakefulnessState.state == WakefulnessState.AWAKE ||
-                                wakefulnessState.state == WakefulnessState.STARTING_TO_WAKE)
+                                wakefulnessState.state == WakefulnessState.STARTING_TO_WAKE) &&
+                            !isActiveDreamLockscreenHosted
                     ) {
-                        val to =
-                            if (occluded) {
-                                KeyguardState.OCCLUDED
-                            } else {
-                                KeyguardState.LOCKSCREEN
-                            }
-
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.PRIMARY_BOUNCER,
-                                to = to,
-                                animator = getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
                         )
                     }
                 }
@@ -99,7 +155,7 @@
                 .sample(
                     combine(
                         keyguardInteractor.wakefulnessModel,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        transitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Triple
                     ),
@@ -114,30 +170,48 @@
                             (wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
                                 wakefulnessState.state == WakefulnessState.ASLEEP)
                     ) {
-                        val to =
-                            if (isAodAvailable) {
-                                KeyguardState.AOD
-                            } else {
-                                KeyguardState.DOZING
-                            }
-
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.PRIMARY_BOUNCER,
-                                to = to,
-                                animator = getAnimator(),
-                            )
+                        startTransitionTo(
+                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
                         )
                     }
                 }
         }
     }
 
+    private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
+        scope.launch {
+            keyguardInteractor.primaryBouncerShowing
+                    .sample(
+                            combine(
+                                    keyguardInteractor.isActiveDreamLockscreenHosted,
+                                    transitionInteractor.startedKeyguardTransitionStep,
+                                    ::Pair
+                            ),
+                            ::toTriple
+                    )
+                    .collect { (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
+                        if (
+                                !isBouncerShowing &&
+                                isActiveDreamLockscreenHosted &&
+                                lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
+                        ) {
+                            startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                        }
+                    }
+        }
+    }
+
     private fun listenForPrimaryBouncerToGone() {
+        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            // This is handled in KeyguardSecurityContainerController and
+            // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
+            // transition vs. listening to legacy state flags.
+            return
+        }
+
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (isKeyguardGoingAway, lastStartedTransitionStep) ->
                     if (
                         isKeyguardGoingAway &&
@@ -149,35 +223,35 @@
                             )
                         // IME for password requires a slightly faster animation
                         val duration =
-                            if (securityMode == Password) {
+                            if (securityMode == KeyguardSecurityModel.SecurityMode.Password) {
                                 TO_GONE_SHORT_DURATION
                             } else {
                                 TO_GONE_DURATION
                             }
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.PRIMARY_BOUNCER,
-                                to = KeyguardState.GONE,
-                                animator = getAnimator(duration),
-                            ),
-                            resetIfCanceled = true,
+
+                        startTransitionTo(
+                            toState = KeyguardState.GONE,
+                            animator =
+                                getDefaultAnimatorForTransitionsToState(KeyguardState.GONE).apply {
+                                    this.duration = duration.inWholeMilliseconds
+                                },
+                            resetIfCancelled = true
                         )
                     }
                 }
         }
     }
 
-    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(duration.inWholeMilliseconds)
+            interpolator = Interpolators.LINEAR
+            duration = DEFAULT_DURATION.inWholeMilliseconds
         }
     }
 
     companion object {
         private val DEFAULT_DURATION = 300.milliseconds
-        val TO_GONE_DURATION = 250.milliseconds
+        val TO_GONE_DURATION = 500.milliseconds
         val TO_GONE_SHORT_DURATION = 200.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
new file mode 100644
index 0000000..390ad7e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardBlueprintInteractor
+@Inject
+constructor(private val keyguardBlueprintRepository: KeyguardBlueprintRepository) {
+    val blueprint = keyguardBlueprintRepository.blueprint
+
+    /**
+     * Transitions to a blueprint.
+     *
+     * @param blueprintId
+     * @return whether the transition has succeeded.
+     */
+    fun transitionToBlueprint(blueprintId: String): Boolean {
+        return keyguardBlueprintRepository.applyBlueprint(blueprintId)
+    }
+
+    /** Re-emits the blueprint value to the collectors. */
+    fun refreshBlueprint() {
+        keyguardBlueprintRepository.refreshBlueprint()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
index fe778a6..e57c919 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
 import kotlinx.coroutines.flow.Flow
 
 /**
@@ -27,10 +27,10 @@
 interface KeyguardFaceAuthInteractor {
 
     /** Current authentication status */
-    val authenticationStatus: Flow<AuthenticationStatus>
+    val authenticationStatus: Flow<FaceAuthenticationStatus>
 
     /** Current detection status */
-    val detectionStatus: Flow<DetectionStatus>
+    val detectionStatus: Flow<FaceDetectionStatus>
 
     /** Can face auth be run right now */
     fun canFaceAuthRun(): Boolean
@@ -73,8 +73,8 @@
  */
 interface FaceAuthenticationListener {
     /** Receive face authentication status updates */
-    fun onAuthenticationStatusChanged(status: AuthenticationStatus)
+    fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus)
 
     /** Receive status updates whenever face detection runs */
-    fun onDetectionStatusChanged(status: DetectionStatus)
+    fun onDetectionStatusChanged(status: FaceDetectionStatus)
 }
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 c44435e..562c4db 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
@@ -19,26 +19,32 @@
 
 import android.app.StatusBarManager
 import android.graphics.Point
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.Position
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.ScreenModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.kotlin.sample
-import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
@@ -47,6 +53,7 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
+import javax.inject.Inject
 
 /**
  * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -59,7 +66,20 @@
     private val commandQueue: CommandQueue,
     featureFlags: FeatureFlags,
     bouncerRepository: KeyguardBouncerRepository,
+    configurationRepository: ConfigurationRepository,
 ) {
+
+    data class PreviewMode(
+        val isInPreviewMode: Boolean = false,
+        val shouldHighlightSelectedAffordance: Boolean = false,
+    )
+
+    /**
+     * Whether this view-model instance is powering the preview experience that renders exclusively
+     * in the wallpaper picker application. This should _always_ be `false` for the real lock screen
+     * experience.
+     */
+    val previewMode = MutableStateFlow(PreviewMode())
     /**
      * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
      * all.
@@ -67,6 +87,8 @@
     val dozeAmount: Flow<Float> = repository.linearDozeAmount
     /** Whether the system is in doze mode. */
     val isDozing: Flow<Boolean> = repository.isDozing
+    /** Receive an event for doze time tick */
+    val dozeTimeTick: Flow<Long> = repository.dozeTimeTick
     /** Whether Always-on Display mode is available. */
     val isAodAvailable: Flow<Boolean> = repository.isAodAvailable
     /** Doze transition information. */
@@ -78,6 +100,8 @@
     val isDreaming: Flow<Boolean> = repository.isDreaming
     /** Whether the system is dreaming with an overlay active */
     val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
+    /** Whether the system is dreaming and the active dream is hosted in lockscreen */
+    val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted
     /** Event for when the camera gesture is detected */
     val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> = conflatedCallbackFlow {
         val callback =
@@ -97,7 +121,10 @@
     }
 
     /** The device wake/sleep state */
-    val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness
+    val wakefulnessModel: StateFlow<WakefulnessModel> = repository.wakefulness
+
+    /** The device screen state */
+    val screenModel: StateFlow<ScreenModel> = repository.screenModel
 
     /**
      * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
@@ -135,8 +162,6 @@
     val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
     /** Observable for the [StatusBarState] */
     val statusBarState: Flow<StatusBarState> = repository.statusBarState
-    /** Whether or not quick settings or quick quick settings are showing. */
-    val isQuickSettingsVisible: Flow<Boolean> = repository.isQuickSettingsVisible
     /**
      * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
      * side, under display) is used to unlock the device.
@@ -172,6 +197,21 @@
     /** The approximate location on the screen of the face unlock sensor, if one is available. */
     val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
 
+    /** Notifies when a new configuration is set */
+    val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
+
+    /** Represents the current state of the KeyguardRootView visibility */
+    val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
+        repository.keyguardRootViewVisibility
+
+    /** The position of the keyguard clock. */
+    val clockPosition: Flow<Position> = repository.clockPosition
+
+    val keyguardAlpha: Flow<Float> = repository.keyguardAlpha
+
+    /** Whether to animate the next doze mode transition. */
+    val animateDozingTransitions: Flow<Boolean> = repository.animateBottomAreaDozingTransitions
+
     fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
         return dozeTransitionModel.filter { states.contains(it.to) }
     }
@@ -192,12 +232,59 @@
         }
     }
 
+    fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean) {
+        repository.setIsActiveDreamLockscreenHosted(isLockscreenHosted)
+    }
+
     /** Sets whether quick settings or quick-quick settings is visible. */
     fun setQuickSettingsVisible(isVisible: Boolean) {
         repository.setQuickSettingsVisible(isVisible)
     }
 
+    fun setKeyguardRootVisibility(
+        statusBarState: Int,
+        goingToFullShade: Boolean,
+        isOcclusionTransitionRunning: Boolean
+    ) {
+        repository.setKeyguardVisibility(
+            statusBarState,
+            goingToFullShade,
+            isOcclusionTransitionRunning
+        )
+    }
+
+    fun setClockPosition(x: Int, y: Int) {
+        repository.setClockPosition(x, y)
+    }
+
+    fun setAlpha(alpha: Float) {
+        repository.setKeyguardAlpha(alpha)
+    }
+
+    fun setAnimateDozingTransitions(animate: Boolean) {
+        repository.setAnimateDozingTransitions(animate)
+    }
+
+    fun isKeyguardDismissable(): Boolean {
+        return repository.isKeyguardUnlocked.value
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
+
+        fun isKeyguardVisibleInState(state: KeyguardState): Boolean {
+            return when (state) {
+                KeyguardState.OFF -> true
+                KeyguardState.DOZING -> true
+                KeyguardState.DREAMING -> true
+                KeyguardState.AOD -> true
+                KeyguardState.ALTERNATE_BOUNCER -> true
+                KeyguardState.PRIMARY_BOUNCER -> true
+                KeyguardState.LOCKSCREEN -> true
+                KeyguardState.GONE -> false
+                KeyguardState.OCCLUDED -> true
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> false
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
new file mode 100644
index 0000000..635961b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.Context
+import android.media.AudioManager
+import android.view.KeyEvent
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor.Companion.handleAction
+import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import javax.inject.Inject
+
+/** Handles key events arriving when the keyguard is showing or device is dozing. */
+@SysUISingleton
+class KeyguardKeyEventInteractor
+@Inject
+constructor(
+    private val context: Context,
+    private val statusBarStateController: StatusBarStateController,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+    private val shadeController: ShadeController,
+    private val mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper,
+    private val backActionInteractor: BackActionInteractor,
+) {
+
+    fun dispatchKeyEvent(event: KeyEvent): Boolean {
+        if (statusBarStateController.isDozing) {
+            when (event.keyCode) {
+                KeyEvent.KEYCODE_VOLUME_DOWN,
+                KeyEvent.KEYCODE_VOLUME_UP -> return dispatchVolumeKeyEvent(event)
+            }
+        }
+
+        if (event.handleAction()) {
+            when (event.keyCode) {
+                KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent()
+                KeyEvent.KEYCODE_SPACE -> return dispatchSpaceEvent()
+            }
+        }
+        return false
+    }
+
+    /**
+     * While IME is active and a BACK event is detected, check with {@link
+     * StatusBarKeyguardViewManager#dispatchBackKeyEventPreIme()} to see if the event should be
+     * handled before routing to IME, in order to prevent the user from having to hit back twice to
+     * exit bouncer.
+     */
+    fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
+        when (event.keyCode) {
+            KeyEvent.KEYCODE_BACK ->
+                if (
+                    statusBarStateController.state == StatusBarState.KEYGUARD &&
+                        statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()
+                ) {
+                    return backActionInteractor.onBackRequested()
+                }
+        }
+        return false
+    }
+
+    fun interceptMediaKey(event: KeyEvent): Boolean {
+        return statusBarStateController.state == StatusBarState.KEYGUARD &&
+            statusBarKeyguardViewManager.interceptMediaKey(event)
+    }
+
+    private fun dispatchMenuKeyEvent(): Boolean {
+        val shouldUnlockOnMenuPressed =
+            isDeviceInteractive() &&
+                (statusBarStateController.state != StatusBarState.SHADE) &&
+                statusBarKeyguardViewManager.shouldDismissOnMenuPressed()
+        if (shouldUnlockOnMenuPressed) {
+            shadeController.animateCollapseShadeForced()
+            return true
+        }
+        return false
+    }
+
+    private fun dispatchSpaceEvent(): Boolean {
+        if (isDeviceInteractive() && statusBarStateController.state != StatusBarState.SHADE) {
+            shadeController.animateCollapseShadeForced()
+            return true
+        }
+        return false
+    }
+
+    private fun dispatchVolumeKeyEvent(event: KeyEvent): Boolean {
+        mediaSessionLegacyHelperWrapper
+            .getHelper(context)
+            .sendVolumeKeyEvent(event, AudioManager.USE_DEFAULT_STREAM_TYPE, true)
+        return true
+    }
+
+    private fun isDeviceInteractive(): Boolean {
+        return keyguardInteractor.wakefulnessModel.value.isDeviceInteractive()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index ea9c2b2..9c796f8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -38,7 +38,6 @@
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
 import com.android.systemui.keyguard.shared.model.KeyguardPickerFlag
 import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
 import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
@@ -49,6 +48,7 @@
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.TraceUtils.Companion.traceAsync
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -67,7 +67,6 @@
 @Inject
 constructor(
     private val keyguardInteractor: KeyguardInteractor,
-    private val registry: KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>,
     private val lockPatternUtils: LockPatternUtils,
     private val keyguardStateController: KeyguardStateController,
     private val userTracker: UserTracker,
@@ -82,20 +81,13 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     @Application private val appContext: Context,
 ) {
-    private val isUsingRepository: Boolean
-        get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
 
     /**
      * Whether the UI should use the long press gesture to activate quick affordances.
      *
      * If `false`, the UI goes back to using single taps.
      */
-    fun useLongPress(): Flow<Boolean> =
-        if (featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)) {
-            dockManager.retrieveIsDocked().map { !it }
-        } else {
-            flowOf(false)
-        }
+    fun useLongPress(): Flow<Boolean> = dockManager.retrieveIsDocked().map { !it }
 
     /** Returns an observable for the quick affordance at the given position. */
     suspend fun quickAffordance(
@@ -109,10 +101,9 @@
             quickAffordanceAlwaysVisible(position),
             keyguardInteractor.isDozing,
             keyguardInteractor.isKeyguardShowing,
-            keyguardInteractor.isQuickSettingsVisible,
             biometricSettingsRepository.isCurrentUserInLockdown,
-        ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
-            if (!isDozing && isKeyguardShowing && !isQuickSettingsVisible && !isUserInLockdown) {
+        ) { affordance, isDozing, isKeyguardShowing, isUserInLockdown ->
+            if (!isDozing && isKeyguardShowing && !isUserInLockdown) {
                 affordance
             } else {
                 KeyguardQuickAffordanceModel.Hidden
@@ -146,14 +137,9 @@
         expandable: Expandable?,
         slotId: String,
     ) {
-        @Suppress("UNCHECKED_CAST")
+        val (decodedSlotId, decodedConfigKey) = configKey.decode()
         val config =
-            if (isUsingRepository) {
-                val (slotId, decodedConfigKey) = configKey.decode()
-                repository.get().selections.value[slotId]?.find { it.key == decodedConfigKey }
-            } else {
-                registry.get(configKey)
-            }
+            repository.get().selections.value[decodedSlotId]?.find { it.key == decodedConfigKey }
         if (config == null) {
             Log.e(TAG, "Affordance config with key of \"$configKey\" not found!")
             return
@@ -182,7 +168,6 @@
      * @return `true` if the affordance was selected successfully; `false` otherwise.
      */
     suspend fun select(slotId: String, affordanceId: String): Boolean {
-        check(isUsingRepository)
         if (isFeatureDisabledByDevicePolicy()) {
             return false
         }
@@ -225,7 +210,6 @@
      *   the affordance was not on the slot to begin with).
      */
     suspend fun unselect(slotId: String, affordanceId: String?): Boolean {
-        check(isUsingRepository)
         if (isFeatureDisabledByDevicePolicy()) {
             return false
         }
@@ -285,17 +269,12 @@
 
     private fun quickAffordanceInternal(
         position: KeyguardQuickAffordancePosition
-    ): Flow<KeyguardQuickAffordanceModel> {
-        return if (isUsingRepository) {
-            repository
-                .get()
-                .selections
-                .map { it[position.toSlotId()] ?: emptyList() }
-                .flatMapLatest { configs -> combinedConfigs(position, configs) }
-        } else {
-            combinedConfigs(position, registry.getAll(position))
-        }
-    }
+    ): Flow<KeyguardQuickAffordanceModel> =
+        repository
+            .get()
+            .selections
+            .map { it[position.toSlotId()] ?: emptyList() }
+            .flatMapLatest { configs -> combinedConfigs(position, configs) }
 
     private fun combinedConfigs(
         position: KeyguardQuickAffordancePosition,
@@ -325,12 +304,7 @@
                     states[index] as KeyguardQuickAffordanceConfig.LockScreenState.Visible
                 val configKey = configs[index].key
                 KeyguardQuickAffordanceModel.Visible(
-                    configKey =
-                        if (isUsingRepository) {
-                            configKey.encode(position.toSlotId())
-                        } else {
-                            configKey
-                        },
+                    configKey = configKey.encode(position.toSlotId()),
                     icon = visibleState.icon,
                     activationState = visibleState.activationState,
                 )
@@ -396,8 +370,6 @@
     }
 
     suspend fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
-        check(isUsingRepository)
-
         if (isFeatureDisabledByDevicePolicy()) {
             return emptyList()
         }
@@ -415,7 +387,6 @@
                 name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED,
                 value =
                     !isFeatureDisabledByDevicePolicy() &&
-                        featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES) &&
                         appContext.resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled),
             ),
             KeyguardPickerFlag(
@@ -433,13 +404,27 @@
             KeyguardPickerFlag(
                 name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_PICKER_UI_FOR_AIWP,
                 value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_UI_FOR_AIWP)
-            )
+            ),
+            KeyguardPickerFlag(
+                name = Contract.FlagsTable.FLAG_NAME_TRANSIT_CLOCK,
+                value = featureFlags.isEnabled(Flags.TRANSIT_CLOCK)
+            ),
+            KeyguardPickerFlag(
+                name = Contract.FlagsTable.FLAG_NAME_PAGE_TRANSITIONS,
+                value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PAGE_TRANSITIONS)
+            ),
+            KeyguardPickerFlag(
+                name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_PICKER_PREVIEW_ANIMATION,
+                value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PREVIEW_ANIMATION)
+            ),
         )
     }
 
     private suspend fun isFeatureDisabledByDevicePolicy(): Boolean =
-        withContext(backgroundDispatcher) {
-            devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
+        traceAsync(TAG, "isFeatureDisabledByDevicePolicy") {
+            withContext(backgroundDispatcher) {
+                devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
+            }
         }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
new file mode 100644
index 0000000..bf04f8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardSurfaceBehindInteractor
+@Inject
+constructor(
+    private val repository: KeyguardSurfaceBehindRepository,
+    private val fromLockscreenInteractor: FromLockscreenTransitionInteractor,
+    private val fromPrimaryBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+    transitionInteractor: KeyguardTransitionInteractor,
+) {
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val viewParams: Flow<KeyguardSurfaceBehindModel> =
+        transitionInteractor.isInTransitionToAnyState
+            .flatMapLatest { isInTransition ->
+                if (!isInTransition) {
+                    defaultParams
+                } else {
+                    combine(
+                        transitionSpecificViewParams,
+                        defaultParams,
+                    ) { transitionParams, defaultParams ->
+                        transitionParams ?: defaultParams
+                    }
+                }
+            }
+
+    val isAnimatingSurface = repository.isAnimatingSurface
+
+    private val defaultParams =
+        transitionInteractor.finishedKeyguardState.map { state ->
+            KeyguardSurfaceBehindModel(
+                alpha =
+                    if (WindowManagerLockscreenVisibilityInteractor.isSurfaceVisible(state)) 1f
+                    else 0f
+            )
+        }
+
+    /**
+     * View params provided by the transition interactor for the most recently STARTED transition.
+     * This is used to run transition-specific animations on the surface.
+     *
+     * If null, there are no transition-specific view params needed for this transition and we will
+     * use a reasonable default.
+     */
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private val transitionSpecificViewParams: Flow<KeyguardSurfaceBehindModel?> =
+        transitionInteractor.startedKeyguardTransitionStep.flatMapLatest { startedStep ->
+            when (startedStep.from) {
+                KeyguardState.LOCKSCREEN -> fromLockscreenInteractor.surfaceBehindModel
+                KeyguardState.PRIMARY_BOUNCER -> fromPrimaryBouncerInteractor.surfaceBehindModel
+                // Return null for other states, where no transition specific params are needed.
+                else -> flowOf(null)
+            }
+        }
+
+    fun setAnimatingSurface(animating: Boolean) {
+        repository.setAnimatingSurface(animating)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 7c5641f..4f7abd4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -19,7 +19,7 @@
 import com.android.keyguard.logging.KeyguardLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.VERBOSE
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
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
index efc1bd0..ba7b987 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -45,6 +45,7 @@
                     is FromOccludedTransitionInteractor -> Log.d(TAG, "Started $it")
                     is FromDozingTransitionInteractor -> Log.d(TAG, "Started $it")
                     is FromAlternateBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromDreamingLockscreenHostedTransitionInteractor -> Log.d(TAG, "Started $it")
                 }
             it.start()
         }
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
index 41a81a8..9382618 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -17,15 +17,20 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -34,6 +39,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
@@ -44,9 +50,15 @@
 class KeyguardTransitionInteractor
 @Inject
 constructor(
-    private val repository: KeyguardTransitionRepository,
     @Application val scope: CoroutineScope,
+    private val repository: KeyguardTransitionRepository,
+    private val keyguardInteractor: dagger.Lazy<KeyguardInteractor>,
+    private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
+    private val fromPrimaryBouncerTransitionInteractor:
+        dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
 ) {
+    private val TAG = this::class.simpleName
+
     /** (any)->GONE transition information */
     val anyStateToGoneTransition: Flow<TransitionStep> =
         repository.transitions.filter { step -> step.to == GONE }
@@ -59,6 +71,26 @@
     val fromDreamingTransition: Flow<TransitionStep> =
         repository.transitions.filter { step -> step.from == DREAMING }
 
+    /** (any)->Lockscreen transition information */
+    val anyStateToLockscreenTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == LOCKSCREEN }
+
+    /** (any)->Occluded transition information */
+    val anyStateToOccludedTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == OCCLUDED }
+
+    /** (any)->PrimaryBouncer transition information */
+    val anyStateToPrimaryBouncerTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == PRIMARY_BOUNCER }
+
+    /** (any)->Dreaming transition information */
+    val anyStateToDreamingTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == DREAMING }
+
+    /** (any)->AlternateBouncer transition information */
+    val anyStateToAlternateBouncerTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == ALTERNATE_BOUNCER }
+
     /** AOD->LOCKSCREEN transition information. */
     val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
 
@@ -66,9 +98,20 @@
     val dreamingToLockscreenTransition: Flow<TransitionStep> =
         repository.transition(DREAMING, LOCKSCREEN)
 
+    /** DREAMING_LOCKSCREEN_HOSTED->LOCKSCREEN transition information. */
+    val dreamingLockscreenHostedToLockscreenTransition: Flow<TransitionStep> =
+        repository.transition(DREAMING_LOCKSCREEN_HOSTED, LOCKSCREEN)
+
+    /** GONE->AOD transition information. */
+    val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD)
+
     /** GONE->DREAMING transition information. */
     val goneToDreamingTransition: Flow<TransitionStep> = repository.transition(GONE, DREAMING)
 
+    /** GONE->DREAMING_LOCKSCREEN_HOSTED transition information. */
+    val goneToDreamingLockscreenHostedTransition: Flow<TransitionStep> =
+        repository.transition(GONE, DREAMING_LOCKSCREEN_HOSTED)
+
     /** LOCKSCREEN->AOD transition information. */
     val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
 
@@ -76,6 +119,10 @@
     val lockscreenToDreamingTransition: Flow<TransitionStep> =
         repository.transition(LOCKSCREEN, DREAMING)
 
+    /** LOCKSCREEN->DREAMING_LOCKSCREEN_HOSTED transition information. */
+    val lockscreenToDreamingLockscreenHostedTransition: Flow<TransitionStep> =
+        repository.transition(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED)
+
     /** LOCKSCREEN->OCCLUDED transition information. */
     val lockscreenToOccludedTransition: Flow<TransitionStep> =
         repository.transition(LOCKSCREEN, OCCLUDED)
@@ -89,12 +136,11 @@
         repository.transition(PRIMARY_BOUNCER, GONE)
 
     /** OFF->LOCKSCREEN transition information. */
-    val offToLockscreenTransition: Flow<TransitionStep> =
-        repository.transition(KeyguardState.OFF, LOCKSCREEN)
+    val offToLockscreenTransition: Flow<TransitionStep> = repository.transition(OFF, LOCKSCREEN)
 
     /** DOZING->LOCKSCREEN transition information. */
     val dozingToLockscreenTransition: Flow<TransitionStep> =
-        repository.transition(KeyguardState.DOZING, LOCKSCREEN)
+        repository.transition(DOZING, LOCKSCREEN)
 
     /**
      * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
@@ -118,17 +164,30 @@
     val finishedKeyguardTransitionStep: Flow<TransitionStep> =
         repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
 
-    /** The destination state of the last started transition */
+    /** The destination state of the last started transition. */
     val startedKeyguardState: StateFlow<KeyguardState> =
         startedKeyguardTransitionStep
             .map { step -> step.to }
-            .stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF)
+            .stateIn(scope, SharingStarted.Eagerly, OFF)
 
     /** The last completed [KeyguardState] transition */
     val finishedKeyguardState: StateFlow<KeyguardState> =
         finishedKeyguardTransitionStep
             .map { step -> step.to }
             .stateIn(scope, SharingStarted.Eagerly, LOCKSCREEN)
+
+    /**
+     * Whether we're currently in a transition to a new [KeyguardState] and haven't yet completed
+     * it.
+     */
+    val isInTransitionToAnyState =
+            combine(
+                    startedKeyguardTransitionStep,
+                    finishedKeyguardState,
+            ) { startedStep, finishedState ->
+                startedStep.to != finishedState
+            }
+
     /**
      * The amount of transition into or out of the given [KeyguardState].
      *
@@ -148,4 +207,41 @@
                 }
             }
     }
+
+    fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> {
+        return repository.transitions.filter { step -> step.from == fromState }
+    }
+
+    fun transitionStepsToState(toState: KeyguardState): Flow<TransitionStep> {
+        return repository.transitions.filter { step -> step.to == toState }
+    }
+
+    /**
+     * Called to start a transition that will ultimately dismiss the keyguard from the current
+     * state.
+     */
+    fun startDismissKeyguardTransition() {
+        when (startedKeyguardState.value) {
+            LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
+            PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+            else ->
+                Log.e(
+                    "KeyguardTransitionInteractor",
+                    "We don't know how to dismiss keyguard from state " +
+                        "${startedKeyguardState.value}"
+                )
+        }
+    }
+
+    /** Whether we're in a transition to the given [KeyguardState], but haven't yet completed it. */
+    fun isInTransitionToState(
+            state: KeyguardState,
+    ): Flow<Boolean> {
+        return combine(
+                startedKeyguardTransitionStep,
+                finishedKeyguardState,
+        ) { startedStep, finishedState ->
+            startedStep.to == state && finishedState != state
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 833eda7..6115d90 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -17,28 +17,44 @@
 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.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.statusbar.LightRevealEffect
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
 
 @ExperimentalCoroutinesApi
 @SysUISingleton
 class LightRevealScrimInteractor
 @Inject
 constructor(
-    transitionRepository: KeyguardTransitionRepository,
-    transitionInteractor: KeyguardTransitionInteractor,
-    lightRevealScrimRepository: LightRevealScrimRepository,
+    private val transitionInteractor: KeyguardTransitionInteractor,
+    private val lightRevealScrimRepository: LightRevealScrimRepository,
+    @Application private val scope: CoroutineScope,
 ) {
 
+    init {
+        listenForStartedKeyguardTransitionStep()
+    }
+
+    private fun listenForStartedKeyguardTransitionStep() {
+        scope.launch {
+            transitionInteractor.startedKeyguardTransitionStep.collect {
+                if (willTransitionChangeEndState(it)) {
+                    lightRevealScrimRepository.startRevealAmountAnimator(
+                        willBeRevealedInState(it.to)
+                    )
+                }
+            }
+        }
+    }
+
     /**
      * Whenever a keyguard transition starts, sample the latest reveal effect from the repository
      * and use that for the starting transition.
@@ -54,17 +70,7 @@
             lightRevealScrimRepository.revealEffect
         )
 
-    /**
-     * The reveal amount to use for the light reveal scrim, which is derived from the keyguard
-     * transition steps.
-     */
-    val revealAmount: Flow<Float> =
-        transitionRepository.transitions
-            // Only listen to transitions that change the reveal amount.
-            .filter { willTransitionAffectRevealAmount(it) }
-            // Use the transition amount as the reveal amount, inverting it if we're transitioning
-            // to a non-revealed (hidden) state.
-            .map { step -> if (willBeRevealedInState(step.to)) step.value else 1f - step.value }
+    val revealAmount = lightRevealScrimRepository.revealAmount
 
     companion object {
 
@@ -72,7 +78,7 @@
          * Whether the transition requires a change in the reveal amount of the light reveal scrim.
          * If not, we don't care about the transition and don't need to listen to it.
          */
-        fun willTransitionAffectRevealAmount(transition: TransitionStep): Boolean {
+        fun willTransitionChangeEndState(transition: TransitionStep): Boolean {
             return willBeRevealedInState(transition.from) != willBeRevealedInState(transition.to)
         }
 
@@ -86,6 +92,7 @@
                 KeyguardState.DOZING -> false
                 KeyguardState.AOD -> false
                 KeyguardState.DREAMING -> true
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
                 KeyguardState.ALTERNATE_BOUNCER -> true
                 KeyguardState.PRIMARY_BOUNCER -> true
                 KeyguardState.LOCKSCREEN -> true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
deleted file mode 100644
index d0bc25f..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.interactor
-
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.util.kotlin.pairwise
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-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.map
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-
-/** Hosts business and application state accessing logic for the lockscreen scene. */
-class LockscreenSceneInteractor
-@AssistedInject
-constructor(
-    @Application applicationScope: CoroutineScope,
-    private val authenticationInteractor: AuthenticationInteractor,
-    bouncerInteractorFactory: BouncerInteractor.Factory,
-    private val sceneInteractor: SceneInteractor,
-    @Assisted private val containerName: String,
-) {
-    private val bouncerInteractor: BouncerInteractor =
-        bouncerInteractorFactory.create(containerName)
-
-    /** Whether the device is currently locked. */
-    val isDeviceLocked: StateFlow<Boolean> =
-        authenticationInteractor.isUnlocked
-            .map { !it }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = !authenticationInteractor.isUnlocked.value,
-            )
-
-    /** Whether it's currently possible to swipe up to dismiss the lockscreen. */
-    val isSwipeToDismissEnabled: StateFlow<Boolean> =
-        combine(
-                authenticationInteractor.isUnlocked,
-                authenticationInteractor.authenticationMethod,
-            ) { isUnlocked, authMethod ->
-                isSwipeToUnlockEnabled(
-                    isUnlocked = isUnlocked,
-                    authMethod = authMethod,
-                )
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue =
-                    isSwipeToUnlockEnabled(
-                        isUnlocked = authenticationInteractor.isUnlocked.value,
-                        authMethod = authenticationInteractor.authenticationMethod.value,
-                    ),
-            )
-
-    init {
-        // LOCKING SHOWS Lockscreen.
-        //
-        // Move to the lockscreen scene if the device becomes locked while in any scene.
-        applicationScope.launch {
-            authenticationInteractor.isUnlocked
-                .map { !it }
-                .distinctUntilChanged()
-                .collect { isLocked ->
-                    if (isLocked) {
-                        sceneInteractor.setCurrentScene(
-                            containerName = containerName,
-                            scene = SceneModel(SceneKey.Lockscreen),
-                        )
-                    }
-                }
-        }
-
-        // BYPASS UNLOCK.
-        //
-        // Moves to the gone scene if bypass is enabled and the device becomes unlocked while in the
-        // lockscreen scene.
-        applicationScope.launch {
-            combine(
-                    authenticationInteractor.isBypassEnabled,
-                    authenticationInteractor.isUnlocked,
-                    sceneInteractor.currentScene(containerName),
-                    ::Triple,
-                )
-                .collect { (isBypassEnabled, isUnlocked, currentScene) ->
-                    if (isBypassEnabled && isUnlocked && currentScene.key == SceneKey.Lockscreen) {
-                        sceneInteractor.setCurrentScene(
-                            containerName = containerName,
-                            scene = SceneModel(SceneKey.Gone),
-                        )
-                    }
-                }
-        }
-
-        // SWIPE TO DISMISS Lockscreen.
-        //
-        // If switched from the lockscreen to the gone scene and the auth method was a swipe,
-        // unlocks the device.
-        applicationScope.launch {
-            combine(
-                    authenticationInteractor.authenticationMethod,
-                    sceneInteractor.currentScene(containerName).pairwise(),
-                    ::Pair,
-                )
-                .collect { (authMethod, scenes) ->
-                    val (previousScene, currentScene) = scenes
-                    if (
-                        authMethod is AuthenticationMethodModel.Swipe &&
-                            previousScene.key == SceneKey.Lockscreen &&
-                            currentScene.key == SceneKey.Gone
-                    ) {
-                        authenticationInteractor.unlockDevice()
-                    }
-                }
-        }
-
-        // DISMISS Lockscreen IF AUTH METHOD IS REMOVED.
-        //
-        // If the auth method becomes None while on the lockscreen scene, dismisses the lock
-        // screen.
-        applicationScope.launch {
-            combine(
-                    authenticationInteractor.authenticationMethod,
-                    sceneInteractor.currentScene(containerName),
-                    ::Pair,
-                )
-                .collect { (authMethod, scene) ->
-                    if (
-                        scene.key == SceneKey.Lockscreen &&
-                            authMethod == AuthenticationMethodModel.None
-                    ) {
-                        sceneInteractor.setCurrentScene(
-                            containerName = containerName,
-                            scene = SceneModel(SceneKey.Gone),
-                        )
-                    }
-                }
-        }
-    }
-
-    /** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */
-    fun dismissLockscreen() {
-        bouncerInteractor.showOrUnlockDevice(containerName = containerName)
-    }
-
-    private fun isSwipeToUnlockEnabled(
-        isUnlocked: Boolean,
-        authMethod: AuthenticationMethodModel,
-    ): Boolean {
-        return !isUnlocked && authMethod is AuthenticationMethodModel.Swipe
-    }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(
-            containerName: String,
-        ): LockscreenSceneInteractor
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
index 69474e7..596a1c0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.emptyFlow
@@ -31,9 +31,9 @@
  */
 @SysUISingleton
 class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInteractor {
-    override val authenticationStatus: Flow<AuthenticationStatus>
+    override val authenticationStatus: Flow<FaceAuthenticationStatus>
         get() = emptyFlow()
-    override val detectionStatus: Flow<DetectionStatus>
+    override val detectionStatus: Flow<FaceDetectionStatus>
         get() = emptyFlow()
 
     override fun canFaceAuthRun(): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
new file mode 100644
index 0000000..3ec660a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
+
+/** Business logic for handling authentication events when an app is occluding the lockscreen. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class OccludingAppDeviceEntryInteractor
+@Inject
+constructor(
+    biometricMessageInteractor: BiometricMessageInteractor,
+    fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+    keyguardInteractor: KeyguardInteractor,
+    primaryBouncerInteractor: PrimaryBouncerInteractor,
+    alternateBouncerInteractor: AlternateBouncerInteractor,
+    @Application scope: CoroutineScope,
+    private val context: Context,
+    activityStarter: ActivityStarter,
+    powerInteractor: PowerInteractor,
+    featureFlags: FeatureFlags,
+) {
+    private val keyguardOccludedByApp: Flow<Boolean> =
+        combine(
+                keyguardInteractor.isKeyguardOccluded,
+                keyguardInteractor.isKeyguardShowing,
+                primaryBouncerInteractor.isShowing,
+                alternateBouncerInteractor.isVisible,
+            ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible ->
+                occluded && showing && !primaryBouncerShowing && !alternateBouncerVisible
+            }
+            .distinctUntilChanged()
+    private val fingerprintUnlockSuccessEvents: Flow<Unit> =
+        fingerprintAuthRepository.authenticationStatus
+            .ifKeyguardOccludedByApp()
+            .filter { it is SuccessFingerprintAuthenticationStatus }
+            .map {} // maps FingerprintAuthenticationStatus => Unit
+    private val fingerprintLockoutEvents: Flow<Unit> =
+        fingerprintAuthRepository.authenticationStatus
+            .ifKeyguardOccludedByApp()
+            .filter { it is ErrorFingerprintAuthenticationStatus && it.isLockoutMessage() }
+            .map {} // maps FingerprintAuthenticationStatus => Unit
+    val message: Flow<BiometricMessage?> =
+        merge(
+                biometricMessageInteractor.fingerprintErrorMessage.filterNot {
+                    it.isFingerprintLockoutMessage()
+                },
+                biometricMessageInteractor.fingerprintFailMessage,
+                biometricMessageInteractor.fingerprintHelpMessage,
+            )
+            .ifKeyguardOccludedByApp(/* elseFlow */ flowOf(null))
+
+    init {
+        if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
+            scope.launch {
+                // On fingerprint success when the screen is on, go to the home screen
+                fingerprintUnlockSuccessEvents.sample(powerInteractor.isInteractive).collect {
+                    if (it) {
+                        goToHomeScreen()
+                    }
+                    // don't go to the home screen if the authentication is from AOD/dozing/off
+                }
+            }
+
+            scope.launch {
+                // On device fingerprint lockout, request the bouncer with a runnable to
+                // go to the home screen. Without this, the bouncer won't proceed to the home
+                // screen.
+                fingerprintLockoutEvents.collect {
+                    activityStarter.dismissKeyguardThenExecute(
+                        object : ActivityStarter.OnDismissAction {
+                            override fun onDismiss(): Boolean {
+                                goToHomeScreen()
+                                return false
+                            }
+
+                            override fun willRunAnimationOnKeyguard(): Boolean {
+                                return false
+                            }
+                        },
+                        /* cancel= */ null,
+                        /* afterKeyguardGone */ false
+                    )
+                }
+            }
+        }
+    }
+
+    /** Launches an Activity which forces the current app to background by going home. */
+    private fun goToHomeScreen() {
+        context.startActivity(
+            Intent(Intent.ACTION_MAIN).apply {
+                addCategory(Intent.CATEGORY_HOME)
+                flags = Intent.FLAG_ACTIVITY_NEW_TASK
+            }
+        )
+    }
+
+    private fun <T> Flow<T>.ifKeyguardOccludedByApp(elseFlow: Flow<T> = emptyFlow()): Flow<T> {
+        return keyguardOccludedByApp.flatMapLatest { keyguardOccludedByApp ->
+            if (keyguardOccludedByApp) {
+                this
+            } else {
+                elseFlow
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt
deleted file mode 100644
index 3099a49..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt
+++ /dev/null
@@ -1,140 +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.keyguard.domain.interactor
-
-import android.view.View
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
-import com.android.systemui.util.ListenerSet
-import javax.inject.Inject
-
-/** Interactor to add and remove callbacks for the bouncer. */
-@SysUISingleton
-class PrimaryBouncerCallbackInteractor @Inject constructor() {
-    private var resetCallbacks = ListenerSet<KeyguardResetCallback>()
-    private var expansionCallbacks = ArrayList<PrimaryBouncerExpansionCallback>()
-
-    /** Add a KeyguardResetCallback. */
-    fun addKeyguardResetCallback(callback: KeyguardResetCallback) {
-        resetCallbacks.addIfAbsent(callback)
-    }
-
-    /** Remove a KeyguardResetCallback. */
-    fun removeKeyguardResetCallback(callback: KeyguardResetCallback) {
-        resetCallbacks.remove(callback)
-    }
-
-    /** Adds a callback to listen to bouncer expansion updates. */
-    fun addBouncerExpansionCallback(callback: PrimaryBouncerExpansionCallback) {
-        if (!expansionCallbacks.contains(callback)) {
-            expansionCallbacks.add(callback)
-        }
-    }
-
-    /**
-     * Removes a previously added callback. If the callback was never added, this method does
-     * nothing.
-     */
-    fun removeBouncerExpansionCallback(callback: PrimaryBouncerExpansionCallback) {
-        expansionCallbacks.remove(callback)
-    }
-
-    /** Propagate fully shown to bouncer expansion callbacks. */
-    fun dispatchFullyShown() {
-        for (callback in expansionCallbacks) {
-            callback.onFullyShown()
-        }
-    }
-
-    /** Propagate starting to hide to bouncer expansion callbacks. */
-    fun dispatchStartingToHide() {
-        for (callback in expansionCallbacks) {
-            callback.onStartingToHide()
-        }
-    }
-
-    /** Propagate starting to show to bouncer expansion callbacks. */
-    fun dispatchStartingToShow() {
-        for (callback in expansionCallbacks) {
-            callback.onStartingToShow()
-        }
-    }
-
-    /** Propagate fully hidden to bouncer expansion callbacks. */
-    fun dispatchFullyHidden() {
-        for (callback in expansionCallbacks) {
-            callback.onFullyHidden()
-        }
-    }
-
-    /** Propagate expansion changes to bouncer expansion callbacks. */
-    fun dispatchExpansionChanged(expansion: Float) {
-        for (callback in expansionCallbacks) {
-            callback.onExpansionChanged(expansion)
-        }
-    }
-    /** Propagate visibility changes to bouncer expansion callbacks. */
-    fun dispatchVisibilityChanged(visibility: Int) {
-        for (callback in expansionCallbacks) {
-            callback.onVisibilityChanged(visibility == View.VISIBLE)
-        }
-    }
-
-    /** Propagate keyguard reset. */
-    fun dispatchReset() {
-        for (callback in resetCallbacks) {
-            callback.onKeyguardReset()
-        }
-    }
-
-    /** Callback updated when the primary bouncer's show and hide states change. */
-    interface PrimaryBouncerExpansionCallback {
-        /**
-         * Invoked when the bouncer expansion reaches [EXPANSION_VISIBLE]. This is NOT called each
-         * time the bouncer is shown, but rather only when the fully shown amount has changed based
-         * on the panel expansion. The bouncer's visibility can still change when the expansion
-         * amount hasn't changed. See [PrimaryBouncerInteractor.isFullyShowing] for the checks for
-         * the bouncer showing state.
-         */
-        fun onFullyShown() {}
-
-        /** Invoked when the bouncer is starting to transition to a hidden state. */
-        fun onStartingToHide() {}
-
-        /** Invoked when the bouncer is starting to transition to a visible state. */
-        fun onStartingToShow() {}
-
-        /** Invoked when the bouncer expansion reaches [EXPANSION_HIDDEN]. */
-        fun onFullyHidden() {}
-
-        /**
-         * From 0f [EXPANSION_VISIBLE] when fully visible to 1f [EXPANSION_HIDDEN] when fully hidden
-         */
-        fun onExpansionChanged(bouncerHideAmount: Float) {}
-
-        /**
-         * Invoked when visibility of KeyguardBouncer has changed. Note the bouncer expansion can be
-         * [EXPANSION_VISIBLE], but the view's visibility can be [View.INVISIBLE].
-         */
-        fun onVisibilityChanged(isVisible: Boolean) {}
-    }
-
-    interface KeyguardResetCallback {
-        fun onKeyguardReset()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
deleted file mode 100644
index 54bc349..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ /dev/null
@@ -1,406 +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.keyguard.domain.interactor
-
-import android.content.Context
-import android.content.res.ColorStateList
-import android.hardware.biometrics.BiometricSourceType
-import android.os.Handler
-import android.os.Trace
-import android.util.Log
-import android.view.View
-import com.android.keyguard.KeyguardConstants
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.DejankUtils
-import com.android.systemui.R
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
-import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.shared.system.SysUiStatsLog
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-/**
- * Encapsulates business logic for interacting with the lock-screen primary (pin/pattern/password)
- * bouncer.
- */
-@SysUISingleton
-class PrimaryBouncerInteractor
-@Inject
-constructor(
-    private val repository: KeyguardBouncerRepository,
-    private val primaryBouncerView: BouncerView,
-    @Main private val mainHandler: Handler,
-    private val keyguardStateController: KeyguardStateController,
-    private val keyguardSecurityModel: KeyguardSecurityModel,
-    private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
-    private val falsingCollector: FalsingCollector,
-    private val dismissCallbackRegistry: DismissCallbackRegistry,
-    private val context: Context,
-    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val trustRepository: TrustRepository,
-    private val featureFlags: FeatureFlags,
-    @Application private val applicationScope: CoroutineScope,
-) {
-    private val passiveAuthBouncerDelay = context.resources.getInteger(
-            R.integer.primary_bouncer_passive_auth_delay).toLong()
-    /** Runnable to show the primary bouncer. */
-    val showRunnable = Runnable {
-        repository.setPrimaryShow(true)
-        repository.setPrimaryShowingSoon(false)
-        primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE)
-    }
-
-    val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
-    val isShowing: Flow<Boolean> = repository.primaryBouncerShow
-    val startingToHide: Flow<Unit> = repository.primaryBouncerStartingToHide.filter { it }.map {}
-    val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
-    val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
-    val startingDisappearAnimation: Flow<Runnable> =
-        repository.primaryBouncerStartingDisappearAnimation.filterNotNull()
-    val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
-    val keyguardPosition: Flow<Float> = repository.keyguardPosition
-    val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
-    /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
-    val bouncerExpansion: Flow<Float> =
-        combine(repository.panelExpansionAmount, repository.primaryBouncerShow) {
-            panelExpansion,
-            primaryBouncerIsShowing ->
-            if (primaryBouncerIsShowing) {
-                1f - panelExpansion
-            } else {
-                0f
-            }
-        }
-    /** Allow for interaction when just about fully visible */
-    val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
-    val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
-    private var currentUserActiveUnlockRunning = false
-
-    /** This callback needs to be a class field so it does not get garbage collected. */
-    val keyguardUpdateMonitorCallback =
-        object : KeyguardUpdateMonitorCallback() {
-            override fun onBiometricRunningStateChanged(
-                running: Boolean,
-                biometricSourceType: BiometricSourceType?
-            ) {
-                updateSideFpsVisibility()
-            }
-
-            override fun onStrongAuthStateChanged(userId: Int) {
-                updateSideFpsVisibility()
-            }
-        }
-
-    init {
-        keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
-        if (featureFlags.isEnabled(Flags.DELAY_BOUNCER)) {
-            applicationScope.launch {
-                trustRepository.isCurrentUserActiveUnlockRunning.collect {
-                    currentUserActiveUnlockRunning = it
-                }
-            }
-        }
-    }
-
-    // TODO(b/243685699): Move isScrimmed logic to data layer.
-    // TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
-    /** Show the bouncer if necessary and set the relevant states. */
-    @JvmOverloads
-    fun show(isScrimmed: Boolean) {
-        // Reset some states as we show the bouncer.
-        repository.setKeyguardAuthenticated(null)
-        repository.setPrimaryStartingToHide(false)
-
-        val resumeBouncer =
-            (isBouncerShowing() || repository.primaryBouncerShowingSoon.value) &&
-                needsFullscreenBouncer()
-
-        Trace.beginSection("KeyguardBouncer#show")
-        repository.setPrimaryScrimmed(isScrimmed)
-        if (isScrimmed) {
-            setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
-        }
-
-        // In this special case, we want to hide the bouncer and show it again. We want to emit
-        // show(true) again so that we can reinflate the new view.
-        if (resumeBouncer) {
-            repository.setPrimaryShow(false)
-        }
-
-        if (primaryBouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
-            // Keyguard is done.
-            return
-        }
-
-        repository.setPrimaryShowingSoon(true)
-        if (usePrimaryBouncerPassiveAuthDelay()) {
-            Log.d(TAG, "delay bouncer, passive auth may succeed")
-            mainHandler.postDelayed(showRunnable, passiveAuthBouncerDelay)
-        } else {
-            DejankUtils.postAfterTraversal(showRunnable)
-        }
-        keyguardStateController.notifyPrimaryBouncerShowing(true)
-        primaryBouncerCallbackInteractor.dispatchStartingToShow()
-        Trace.endSection()
-    }
-
-    /** Sets the correct bouncer states to hide the bouncer. */
-    fun hide() {
-        Trace.beginSection("KeyguardBouncer#hide")
-        if (isFullyShowing()) {
-            SysUiStatsLog.write(
-                SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
-                SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN
-            )
-            dismissCallbackRegistry.notifyDismissCancelled()
-        }
-
-        repository.setPrimaryStartDisappearAnimation(null)
-        falsingCollector.onBouncerHidden()
-        keyguardStateController.notifyPrimaryBouncerShowing(false /* showing */)
-        cancelShowRunnable()
-        repository.setPrimaryShowingSoon(false)
-        repository.setPrimaryShow(false)
-        repository.setPanelExpansion(EXPANSION_HIDDEN)
-        primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
-        Trace.endSection()
-    }
-
-    /**
-     * Sets the panel expansion which is calculated further upstream. Panel expansion is from 0f
-     * (panel fully hidden) to 1f (panel fully showing). As the panel shows (from 0f => 1f), the
-     * bouncer hides and as the panel becomes hidden (1f => 0f), the bouncer starts to show.
-     * Therefore, a panel expansion of 1f represents the bouncer fully hidden and a panel expansion
-     * of 0f represents the bouncer fully showing.
-     */
-    fun setPanelExpansion(expansion: Float) {
-        val oldExpansion = repository.panelExpansionAmount.value
-        val expansionChanged = oldExpansion != expansion
-        if (repository.primaryBouncerStartingDisappearAnimation.value == null) {
-            repository.setPanelExpansion(expansion)
-        }
-
-        if (
-            expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE &&
-                oldExpansion != KeyguardBouncerConstants.EXPANSION_VISIBLE
-        ) {
-            falsingCollector.onBouncerShown()
-            primaryBouncerCallbackInteractor.dispatchFullyShown()
-        } else if (
-            expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN &&
-                oldExpansion != KeyguardBouncerConstants.EXPANSION_HIDDEN
-        ) {
-            /*
-             * There are cases where #hide() was not invoked, such as when
-             * NotificationPanelViewController controls the hide animation. Make sure the state gets
-             * updated by calling #hide() directly.
-             */
-            hide()
-            DejankUtils.postAfterTraversal { primaryBouncerCallbackInteractor.dispatchReset() }
-            primaryBouncerCallbackInteractor.dispatchFullyHidden()
-        } else if (
-            expansion != KeyguardBouncerConstants.EXPANSION_VISIBLE &&
-                oldExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE
-        ) {
-            primaryBouncerCallbackInteractor.dispatchStartingToHide()
-            repository.setPrimaryStartingToHide(true)
-        }
-        if (expansionChanged) {
-            primaryBouncerCallbackInteractor.dispatchExpansionChanged(expansion)
-        }
-    }
-
-    /** Set the initial keyguard message to show when bouncer is shown. */
-    fun showMessage(message: String?, colorStateList: ColorStateList?) {
-        repository.setShowMessage(BouncerShowMessageModel(message, colorStateList))
-    }
-
-    /**
-     * Sets actions to the bouncer based on how the bouncer is dismissed. If the bouncer is
-     * unlocked, we will run the onDismissAction. If the bouncer is existed before unlocking, we
-     * call cancelAction.
-     */
-    fun setDismissAction(
-        onDismissAction: ActivityStarter.OnDismissAction?,
-        cancelAction: Runnable?
-    ) {
-        primaryBouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
-    }
-
-    /** Update the resources of the views. */
-    fun updateResources() {
-        repository.setResourceUpdateRequests(true)
-    }
-
-    /** Tell the bouncer that keyguard is authenticated. */
-    fun notifyKeyguardAuthenticated(strongAuth: Boolean) {
-        repository.setKeyguardAuthenticated(strongAuth)
-    }
-
-    /** Update the position of the bouncer when showing. */
-    fun setKeyguardPosition(position: Float) {
-        repository.setKeyguardPosition(position)
-    }
-
-    /** Notifies that the state change was handled. */
-    fun notifyKeyguardAuthenticatedHandled() {
-        repository.setKeyguardAuthenticated(null)
-    }
-
-    /** Notifies that the message was shown. */
-    fun onMessageShown() {
-        repository.setShowMessage(null)
-    }
-
-    /** Notify that the resources have been updated */
-    fun notifyUpdatedResources() {
-        repository.setResourceUpdateRequests(false)
-    }
-
-    /** Set whether back button is enabled when on the bouncer screen. */
-    fun setBackButtonEnabled(enabled: Boolean) {
-        repository.setIsBackButtonEnabled(enabled)
-    }
-
-    /** Tell the bouncer to start the pre hide animation. */
-    fun startDisappearAnimation(runnable: Runnable) {
-        if (willRunDismissFromKeyguard()) {
-            runnable.run()
-            return
-        }
-
-        repository.setPrimaryStartDisappearAnimation(runnable)
-    }
-
-    /** Determine whether to show the side fps animation. */
-    fun updateSideFpsVisibility() {
-        val sfpsEnabled: Boolean =
-            context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
-        val fpsDetectionRunning: Boolean = keyguardUpdateMonitor.isFingerprintDetectionRunning
-        val isUnlockingWithFpAllowed: Boolean =
-            keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
-        val toShow =
-            (isBouncerShowing() &&
-                sfpsEnabled &&
-                fpsDetectionRunning &&
-                isUnlockingWithFpAllowed &&
-                !isAnimatingAway())
-
-        if (KeyguardConstants.DEBUG) {
-            Log.d(
-                TAG,
-                ("sideFpsToShow=$toShow\n" +
-                    "isBouncerShowing=${isBouncerShowing()}\n" +
-                    "configEnabled=$sfpsEnabled\n" +
-                    "fpsDetectionRunning=$fpsDetectionRunning\n" +
-                    "isUnlockingWithFpAllowed=$isUnlockingWithFpAllowed\n" +
-                    "isAnimatingAway=${isAnimatingAway()}")
-            )
-        }
-        repository.setSideFpsShowing(toShow)
-    }
-
-    /** Returns whether bouncer is fully showing. */
-    fun isFullyShowing(): Boolean {
-        return (repository.primaryBouncerShowingSoon.value || isBouncerShowing()) &&
-            repository.panelExpansionAmount.value == KeyguardBouncerConstants.EXPANSION_VISIBLE &&
-            repository.primaryBouncerStartingDisappearAnimation.value == null
-    }
-
-    /** Returns whether bouncer is scrimmed. */
-    fun isScrimmed(): Boolean {
-        return repository.primaryBouncerScrimmed.value
-    }
-
-    /** If bouncer expansion is between 0f and 1f non-inclusive. */
-    fun isInTransit(): Boolean {
-        return repository.primaryBouncerShowingSoon.value ||
-            repository.panelExpansionAmount.value != KeyguardBouncerConstants.EXPANSION_HIDDEN &&
-                repository.panelExpansionAmount.value != KeyguardBouncerConstants.EXPANSION_VISIBLE
-    }
-
-    /** Return whether bouncer is animating away. */
-    fun isAnimatingAway(): Boolean {
-        return repository.primaryBouncerStartingDisappearAnimation.value != null
-    }
-
-    /** Return whether bouncer will dismiss with actions */
-    fun willDismissWithAction(): Boolean {
-        return primaryBouncerView.delegate?.willDismissWithActions() == true
-    }
-
-    /** Will the dismissal run from the keyguard layout (instead of from bouncer) */
-    fun willRunDismissFromKeyguard(): Boolean {
-        return primaryBouncerView.delegate?.willRunDismissFromKeyguard() == true
-    }
-
-    /** Returns whether the bouncer should be full screen. */
-    private fun needsFullscreenBouncer(): Boolean {
-        val mode: KeyguardSecurityModel.SecurityMode =
-            keyguardSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser())
-        return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
-            mode == KeyguardSecurityModel.SecurityMode.SimPuk
-    }
-
-    /** Remove the show runnable from the main handler queue to improve performance. */
-    private fun cancelShowRunnable() {
-        DejankUtils.removeCallbacks(showRunnable)
-        mainHandler.removeCallbacks(showRunnable)
-    }
-
-    private fun isBouncerShowing(): Boolean {
-        return repository.primaryBouncerShow.value
-    }
-
-    /** Whether we want to wait to show the bouncer in case passive auth succeeds. */
-    private fun usePrimaryBouncerPassiveAuthDelay(): Boolean {
-        val canRunFaceAuth = keyguardStateController.isFaceAuthEnabled &&
-                keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)
-        val canRunActiveUnlock = currentUserActiveUnlockRunning &&
-                keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()
-
-        return featureFlags.isEnabled(Flags.DELAY_BOUNCER) &&
-                !needsFullscreenBouncer() &&
-                (canRunFaceAuth || canRunActiveUnlock)
-    }
-
-    companion object {
-        private const val TAG = "PrimaryBouncerInteractor"
-    }
-}
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
index d9690b7..56f5529 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -50,6 +50,12 @@
 
     @Binds
     @IntoSet
+    abstract fun fromDreamingLockscreenHosted(
+        impl: FromDreamingLockscreenHostedTransitionInteractor
+    ): TransitionInteractor
+
+    @Binds
+    @IntoSet
     abstract fun fromOccluded(impl: FromOccludedTransitionInteractor): TransitionInteractor
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index bcd11a7..2a3f852 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -16,29 +16,41 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.content.Context
+import android.hardware.biometrics.BiometricFaceConstants
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.CoreStartable
+import com.android.systemui.R
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /**
  * Encapsulates business logic related face authentication being triggered for device entry from
@@ -48,6 +60,7 @@
 class SystemUIKeyguardFaceAuthInteractor
 @Inject
 constructor(
+    private val context: Context,
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     private val repository: DeviceEntryFaceAuthRepository,
@@ -57,6 +70,8 @@
     private val featureFlags: FeatureFlags,
     private val faceAuthenticationLogger: FaceAuthenticationLogger,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+    private val userRepository: UserRepository,
 ) : CoreStartable, KeyguardFaceAuthInteractor {
 
     private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -107,6 +122,32 @@
                 )
             }
             .launchIn(applicationScope)
+
+        deviceEntryFingerprintAuthRepository.isLockedOut
+            .onEach {
+                if (it) {
+                    faceAuthenticationLogger.faceLockedOut("Fingerprint locked out")
+                    repository.lockoutFaceAuth()
+                }
+            }
+            .launchIn(applicationScope)
+
+        // User switching should stop face auth and then when it is complete we should trigger face
+        // auth so that the switched user can unlock the device with face auth.
+        userRepository.userSwitchingInProgress
+            .pairwise(false)
+            .onEach { (wasSwitching, isSwitching) ->
+                if (!wasSwitching && isSwitching) {
+                    repository.pauseFaceAuth()
+                } else if (wasSwitching && !isSwitching) {
+                    repository.resumeFaceAuth()
+                    runFaceAuth(
+                        FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
+                        fallbackToDetect = true
+                    )
+                }
+            }
+            .launchIn(applicationScope)
     }
 
     override fun onSwipeUpOnBouncer() {
@@ -159,17 +200,30 @@
         repository.cancel()
     }
 
+    private val faceAuthenticationStatusOverride = MutableStateFlow<FaceAuthenticationStatus?>(null)
     /** Provide the status of face authentication */
-    override val authenticationStatus = repository.authenticationStatus
+    override val authenticationStatus =
+        merge(faceAuthenticationStatusOverride.filterNotNull(), repository.authenticationStatus)
 
     /** Provide the status of face detection */
     override val detectionStatus = repository.detectionStatus
 
     private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
         if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
-            applicationScope.launch {
-                faceAuthenticationLogger.authRequested(uiEvent)
-                repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
+            if (repository.isLockedOut.value) {
+                faceAuthenticationStatusOverride.value =
+                    ErrorFaceAuthenticationStatus(
+                        BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT,
+                        context.resources.getString(R.string.keyguard_face_unlock_unavailable)
+                    )
+            } else {
+                faceAuthenticationStatusOverride.value = null
+                applicationScope.launch {
+                    withContext(mainDispatcher) {
+                        faceAuthenticationLogger.authRequested(uiEvent)
+                        repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
+                    }
+                }
             }
         } else {
             faceAuthenticationLogger.ignoredFaceAuthTrigger(
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
index b7dd1a5..0dda625 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -15,6 +15,14 @@
  */
 
 package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import android.util.Log
+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 java.util.UUID
+
 /**
  * Each TransitionInteractor is responsible for determining under which conditions to notify
  * [KeyguardTransitionRepository] to signal a transition. When (and if) the transition occurs is
@@ -26,6 +34,50 @@
  * MUST list implementing classes in dagger module [StartKeyguardTransitionModule] and also in the
  * 'when' clause of [KeyguardTransitionCoreStartable]
  */
-sealed class TransitionInteractor(val name: String) {
+sealed class TransitionInteractor(
+    val fromState: KeyguardState,
+) {
+    val name = this::class.simpleName ?: "UnknownTransitionInteractor"
+
+    abstract val transitionRepository: KeyguardTransitionRepository
+    abstract val transitionInteractor: KeyguardTransitionInteractor
     abstract fun start()
+
+    fun startTransitionTo(
+        toState: KeyguardState,
+        animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
+        resetIfCancelled: Boolean = false
+    ): UUID? {
+        if (
+            fromState != transitionInteractor.startedKeyguardState.value &&
+                fromState != transitionInteractor.finishedKeyguardState.value
+        ) {
+            Log.e(
+                name,
+                "startTransition: We were asked to transition from " +
+                    "$fromState to $toState, however we last finished a transition to " +
+                    "${transitionInteractor.finishedKeyguardState.value}, " +
+                    "and last started a transition to " +
+                    "${transitionInteractor.startedKeyguardState.value}. " +
+                    "Ignoring startTransition, but this should never happen."
+            )
+            return null
+        }
+
+        return transitionRepository.startTransition(
+            TransitionInfo(
+                name,
+                fromState,
+                toState,
+                animator,
+            ),
+            resetIfCancelled
+        )
+    }
+
+    /**
+     * Returns a ValueAnimator to be used for transitions to [toState], if one is not explicitly
+     * passed to [startTransitionTo].
+     */
+    abstract fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator?
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
new file mode 100644
index 0000000..c0308e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt
@@ -0,0 +1,93 @@
+/*
+ *  Copyright (C) 2023 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.FloatEvaluator
+import android.animation.IntEvaluator
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.statusbar.phone.hideAffordancesRequest
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Encapsulates business logic for transitions between UDFPS states on the keyguard. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class UdfpsKeyguardInteractor
+@Inject
+constructor(
+    configRepo: ConfigurationRepository,
+    burnInInteractor: BurnInInteractor,
+    keyguardInteractor: KeyguardInteractor,
+    shadeRepository: ShadeRepository,
+    dialogManager: SystemUIDialogManager,
+) {
+    private val intEvaluator = IntEvaluator()
+    private val floatEvaluator = FloatEvaluator()
+
+    val dozeAmount = keyguardInteractor.dozeAmount
+    val scaleForResolution = configRepo.scaleForResolution
+
+    /** Burn-in offsets for the UDFPS view to mitigate burn-in on AOD. */
+    val burnInOffsets: Flow<BurnInOffsets> =
+        combine(
+            keyguardInteractor.dozeAmount,
+            burnInInteractor.udfpsBurnInXOffset,
+            burnInInteractor.udfpsBurnInYOffset,
+            burnInInteractor.udfpsBurnInProgress
+        ) { dozeAmount, fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress ->
+            BurnInOffsets(
+                intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInX),
+                intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInY),
+                floatEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInProgress),
+            )
+        }
+
+    val dialogHideAffordancesRequest: Flow<Boolean> = dialogManager.hideAffordancesRequest
+
+    val qsProgress: Flow<Float> =
+        shadeRepository.qsExpansion // swipe from top of LS
+            .map { (it * 2).coerceIn(0f, 1f) }
+            .onStart { emit(0f) }
+
+    val shadeExpansion: Flow<Float> =
+        combine(
+                shadeRepository.udfpsTransitionToFullShadeProgress, // swipe from middle of LS
+                keyguardInteractor.statusBarState, // quick swipe from middle of LS
+            ) { shadeProgress, statusBarState ->
+                if (statusBarState == StatusBarState.SHADE_LOCKED) {
+                    1f
+                } else {
+                    shadeProgress
+                }
+            }
+            .onStart { emit(0f) }
+}
+
+data class BurnInOffsets(
+    val burnInXOffset: Int, // current x burn in offset based on the aodTransitionAmount
+    val burnInYOffset: Int, // current y burn in offset based on the aodTransitionAmount
+    val burnInProgress: Float, // current progress based on the aodTransitionAmount
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
new file mode 100644
index 0000000..96bfdc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowManagerLockscreenVisibilityInteractor
+@Inject
+constructor(
+    keyguardInteractor: KeyguardInteractor,
+    transitionInteractor: KeyguardTransitionInteractor,
+    surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
+    fromLockscreenInteractor: FromLockscreenTransitionInteractor,
+    fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+) {
+    private val defaultSurfaceBehindVisibility =
+        transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
+
+    /**
+     * Surface visibility provided by the From*TransitionInteractor responsible for the currently
+     * RUNNING transition, or null if the current transition does not require special surface
+     * visibility handling.
+     *
+     * An example of transition-specific visibility is swipe to unlock, where the surface should
+     * only be visible after swiping 20% of the way up the screen, and should become invisible again
+     * if the user swipes back down.
+     */
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private val transitionSpecificSurfaceBehindVisibility: Flow<Boolean?> =
+        transitionInteractor.startedKeyguardTransitionStep
+            .flatMapLatest { startedStep ->
+                when (startedStep.from) {
+                    KeyguardState.LOCKSCREEN -> {
+                        fromLockscreenInteractor.surfaceBehindVisibility
+                    }
+                    KeyguardState.PRIMARY_BOUNCER -> {
+                        fromBouncerInteractor.surfaceBehindVisibility
+                    }
+                    else -> flowOf(null)
+                }
+            }
+            .distinctUntilChanged()
+
+    /**
+     * Surface visibility, which is either determined by the default visibility in the FINISHED
+     * KeyguardState, or the transition-specific visibility used during certain RUNNING transitions.
+     */
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val surfaceBehindVisibility: Flow<Boolean> =
+        transitionInteractor
+                .isInTransitionToAnyState
+            .flatMapLatest { isInTransition ->
+                if (!isInTransition) {
+                    defaultSurfaceBehindVisibility
+                } else {
+                    combine(
+                        transitionSpecificSurfaceBehindVisibility,
+                        defaultSurfaceBehindVisibility,
+                    ) { transitionVisibility, defaultVisibility ->
+                        // Defer to the transition-specific visibility since we're RUNNING a
+                        // transition, but fall back to the default visibility if the current
+                        // transition's interactor did not specify a visibility.
+                        transitionVisibility ?: defaultVisibility
+                    }
+                }
+            }
+            .distinctUntilChanged()
+
+    /**
+     * Whether we're animating, or intend to animate, the surface behind the keyguard via remote
+     * animation. This is used to keep the RemoteAnimationTarget alive until we're done using it.
+     */
+    val usingKeyguardGoingAwayAnimation: Flow<Boolean> =
+        combine(
+                transitionInteractor.isInTransitionToState(KeyguardState.GONE),
+                transitionInteractor.finishedKeyguardState,
+                surfaceBehindInteractor.isAnimatingSurface
+            ) { isInTransitionToGone, finishedState, isAnimatingSurface ->
+                // We may still be animating the surface after the keyguard is fully GONE, since
+                // some animations (like the translation spring) are not tied directly to the
+                // transition step amount.
+                isInTransitionToGone || (finishedState == KeyguardState.GONE && isAnimatingSurface)
+            }
+            .distinctUntilChanged()
+
+    /**
+     * Whether the lockscreen is visible, from the Window Manager (WM) perspective.
+     *
+     * Note: This may briefly be true even if the lockscreen UI has animated out (alpha = 0f), as we
+     * only inform WM once we're done with the keyguard and we're fully GONE. Don't use this if you
+     * want to know if the AOD/clock/notifs/etc. are visible.
+     */
+    val lockscreenVisibility: Flow<Boolean> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.finishedKeyguardState,
+            ) { startedStep, finishedState ->
+                // If we finished the transition, use the finished state. If we're running a
+                // transition, use the state we're transitioning FROM. This can be different from
+                // the last finished state if a transition is interrupted. For example, if we were
+                // transitioning from GONE to AOD and then started AOD -> LOCKSCREEN mid-transition,
+                // we want to immediately use the visibility for AOD (lockscreenVisibility=true)
+                // even though the lastFinishedState is still GONE (lockscreenVisibility=false).
+                if (finishedState == startedStep.to) finishedState else startedStep.from
+            }
+            .map(::isLockscreenVisible)
+            .distinctUntilChanged()
+
+    /**
+     * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
+     * manager's perspective.
+     *
+     * Note: This may be true even if AOD is not user-visible, such as when the light sensor
+     * indicates the device is in the user's pocket. Don't use this if you want to know if the AOD
+     * clock/smartspace/notif icons are visible.
+     */
+    val aodVisibility: Flow<Boolean> =
+        combine(
+                keyguardInteractor.isDozing,
+                keyguardInteractor.biometricUnlockState,
+            ) { isDozing, biometricUnlockState ->
+                // AOD is visible if we're dozing, unless we are wake and unlocking (where we go
+                // directly from AOD to unlocked while dozing).
+                isDozing && !BiometricUnlockModel.isWakeAndUnlock(biometricUnlockState)
+            }
+            .distinctUntilChanged()
+
+    companion object {
+        fun isSurfaceVisible(state: KeyguardState): Boolean {
+            return !isLockscreenVisible(state)
+        }
+
+        fun isLockscreenVisible(state: KeyguardState): Boolean {
+            return state != KeyguardState.GONE
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
deleted file mode 100644
index b48acb6..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
+++ /dev/null
@@ -1,30 +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.keyguard.domain.quickaffordance
-
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import dagger.Binds
-import dagger.Module
-
-@Module
-interface KeyguardQuickAffordanceModule {
-    @Binds
-    fun keyguardQuickAffordanceRegistry(
-        impl: KeyguardQuickAffordanceRegistryImpl
-    ): KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
deleted file mode 100644
index 8526ada..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
+++ /dev/null
@@ -1,66 +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.keyguard.domain.quickaffordance
-
-import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-import javax.inject.Inject
-
-/** Central registry of all known quick affordance configs. */
-interface KeyguardQuickAffordanceRegistry<T : KeyguardQuickAffordanceConfig> {
-    fun getAll(position: KeyguardQuickAffordancePosition): List<T>
-    fun get(key: String): T
-}
-
-class KeyguardQuickAffordanceRegistryImpl
-@Inject
-constructor(
-    homeControls: HomeControlsKeyguardQuickAffordanceConfig,
-    quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
-    qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
-) : KeyguardQuickAffordanceRegistry<KeyguardQuickAffordanceConfig> {
-    private val configsByPosition =
-        mapOf(
-            KeyguardQuickAffordancePosition.BOTTOM_START to
-                listOf(
-                    homeControls,
-                ),
-            KeyguardQuickAffordancePosition.BOTTOM_END to
-                listOf(
-                    quickAccessWallet,
-                    qrCodeScanner,
-                ),
-        )
-    private val configByKey =
-        configsByPosition.values.flatten().associateBy { config -> config.key }
-
-    override fun getAll(
-        position: KeyguardQuickAffordancePosition,
-    ): List<KeyguardQuickAffordanceConfig> {
-        return configsByPosition.getValue(position)
-    }
-
-    override fun get(
-        key: String,
-    ): KeyguardQuickAffordanceConfig {
-        return configByKey.getValue(key)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt
deleted file mode 100644
index c45faf0..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.shared.constants
-
-object KeyguardBouncerConstants {
-    /**
-     * Values for the bouncer expansion represented as the panel expansion. Panel expansion 1f =
-     * panel fully showing = bouncer fully hidden Panel expansion 0f = panel fully hiding = bouncer
-     * fully showing
-     */
-    const val EXPANSION_HIDDEN = 1f
-    const val EXPANSION_VISIBLE = 0f
-    const val ALPHA_EXPANSION_THRESHOLD = 0.95f
-
-    /**
-     * This value is used for denoting the PIN length at which we want to layout the view in which
-     * PIN hinting is enabled
-     */
-    const val DEFAULT_PIN_LENGTH = 6
-
-    object ColorId {
-        const val TITLE = com.android.internal.R.attr.materialColorOnSurface
-        const val PIN_SHAPES = com.android.internal.R.attr.materialColorOnSurfaceVariant
-        const val NUM_PAD_BACKGROUND = com.android.internal.R.attr.materialColorSurfaceContainerHigh
-        const val NUM_PAD_BACKGROUND_PRESSED = com.android.internal.R.attr.materialColorPrimaryFixed
-        const val NUM_PAD_PRESSED = com.android.internal.R.attr.materialColorOnPrimaryFixed
-        const val NUM_PAD_KEY = com.android.internal.R.attr.materialColorOnSurface
-        const val NUM_PAD_BUTTON = com.android.internal.R.attr.materialColorOnSecondaryFixed
-        const val EMERGENCY_BUTTON = com.android.internal.R.attr.materialColorTertiaryFixed
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerCallbackActionsModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerCallbackActionsModel.kt
deleted file mode 100644
index 81cf5b4..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerCallbackActionsModel.kt
+++ /dev/null
@@ -1,26 +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.keyguard.shared.model
-
-import com.android.systemui.plugins.ActivityStarter
-
-/** Encapsulates callbacks to be invoked by the bouncer logic. */
-// TODO(b/243683121): Move dismiss logic from view controllers
-data class BouncerCallbackActionsModel(
-    val onDismissAction: ActivityStarter.OnDismissAction?,
-    val cancelAction: Runnable?
-)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt
deleted file mode 100644
index 05cdeaa..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BouncerShowMessageModel.kt
+++ /dev/null
@@ -1,22 +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.keyguard.shared.model
-
-import android.content.res.ColorStateList
-
-/** Show a keyguard message to the bouncer. */
-data class BouncerShowMessageModel(val message: String?, val colorStateList: ColorStateList?)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
index 9e7dec4..3de3666 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
@@ -17,34 +17,53 @@
 package com.android.systemui.keyguard.shared.model
 
 import android.hardware.face.FaceManager
+import android.os.SystemClock.elapsedRealtime
 
 /**
  * Authentication status provided by
  * [com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository]
  */
-sealed class AuthenticationStatus
+sealed class FaceAuthenticationStatus
 
 /** Success authentication status. */
-data class SuccessAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) :
-    AuthenticationStatus()
+data class SuccessFaceAuthenticationStatus(
+    val successResult: FaceManager.AuthenticationResult,
+    // present to break equality check if the same error occurs repeatedly.
+    @JvmField val createdAt: Long = elapsedRealtime()
+) : FaceAuthenticationStatus()
 
 /** Face authentication help message. */
-data class HelpAuthenticationStatus(val msgId: Int, val msg: String?) : AuthenticationStatus()
+data class HelpFaceAuthenticationStatus(
+    val msgId: Int,
+    val msg: String?, // present to break equality check if the same error occurs repeatedly.
+    @JvmField val createdAt: Long = elapsedRealtime()
+) : FaceAuthenticationStatus()
 
 /** Face acquired message. */
-data class AcquiredAuthenticationStatus(val acquiredInfo: Int) : AuthenticationStatus()
+data class AcquiredFaceAuthenticationStatus(
+    val acquiredInfo: Int, // present to break equality check if the same error occurs repeatedly.
+    @JvmField val createdAt: Long = elapsedRealtime()
+) : FaceAuthenticationStatus()
 
 /** Face authentication failed message. */
-object FailedAuthenticationStatus : AuthenticationStatus()
+data class FailedFaceAuthenticationStatus(
+    // present to break equality check if the same error occurs repeatedly.
+    @JvmField val createdAt: Long = elapsedRealtime()
+) : FaceAuthenticationStatus()
 
 /** Face authentication error message */
-data class ErrorAuthenticationStatus(val msgId: Int, val msg: String? = null) :
-    AuthenticationStatus() {
+data class ErrorFaceAuthenticationStatus(
+    val msgId: Int,
+    val msg: String? = null,
+    // present to break equality check if the same error occurs repeatedly.
+    @JvmField val createdAt: Long = elapsedRealtime()
+) : FaceAuthenticationStatus() {
     /**
      * Method that checks if [msgId] is a lockout error. A lockout error means that face
      * authentication is locked out.
      */
-    fun isLockoutError() = msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
+    fun isLockoutError() =
+        msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT || msgId == FaceManager.FACE_ERROR_LOCKOUT
 
     /**
      * Method that checks if [msgId] is a cancellation error. This means that face authentication
@@ -56,7 +75,21 @@
     fun isHardwareError() =
         msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE ||
             msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS
+
+    companion object {
+        /**
+         * Error message that is created when cancel confirmation is not received from FaceManager
+         * after we request for a cancellation of face auth.
+         */
+        fun cancelNotReceivedError() = ErrorFaceAuthenticationStatus(-1, "")
+    }
 }
 
 /** Face detection success message. */
-data class DetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean)
+data class FaceDetectionStatus(
+    val sensorId: Int,
+    val userId: Int,
+    val isStrongBiometric: Boolean,
+    // present to break equality check if the same error occurs repeatedly.
+    @JvmField val createdAt: Long = elapsedRealtime()
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
new file mode 100644
index 0000000..ae18681
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import android.hardware.fingerprint.FingerprintManager
+import android.os.SystemClock.elapsedRealtime
+
+/**
+ * Fingerprint authentication status provided by
+ * [com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository]
+ */
+sealed class FingerprintAuthenticationStatus
+
+/** Fingerprint authentication success status. */
+data class SuccessFingerprintAuthenticationStatus(
+    val userId: Int,
+    val isStrongBiometric: Boolean,
+) : FingerprintAuthenticationStatus()
+
+/** Fingerprint authentication help message. */
+data class HelpFingerprintAuthenticationStatus(
+    val msgId: Int,
+    val msg: String?,
+) : FingerprintAuthenticationStatus()
+
+/** Fingerprint acquired message. */
+data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) :
+    FingerprintAuthenticationStatus()
+
+/** Fingerprint authentication failed message. */
+object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
+
+/** Fingerprint authentication error message */
+data class ErrorFingerprintAuthenticationStatus(
+    val msgId: Int,
+    val msg: String? = null,
+    // present to break equality check if the same error occurs repeatedly.
+    val createdAt: Long = elapsedRealtime(),
+) : FingerprintAuthenticationStatus() {
+    fun isLockoutMessage(): Boolean {
+        return msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT ||
+            msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardRootViewVisibilityState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardRootViewVisibilityState.kt
new file mode 100644
index 0000000..9a57aef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardRootViewVisibilityState.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+/**
+ * Provides a stateful representation of the visibility of the KeyguardRootView
+ *
+ * @param statusBarState State of the status bar represented by [StatusBarState]
+ * @param goingToFullShade Whether status bar is going to full shade
+ * @param occlusionTransitionRunning Whether the occlusion transition is running in this instant
+ */
+data class KeyguardRootViewVisibilityState(
+    val statusBarState: Int,
+    val goingToFullShade: Boolean,
+    val occlusionTransitionRunning: Boolean,
+)
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
index 87b4321..1e20cdb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -35,6 +35,12 @@
      * parties to present their own UI over keyguard, like a screensaver.
      */
     DREAMING,
+    /*
+     * A device state after the device times out, which can be from both LOCKSCREEN or GONE states.
+     * It is a special version of DREAMING state but not DOZING. The active dream will be windowless
+     * and hosted in the lockscreen.
+     */
+    DREAMING_LOCKSCREEN_HOSTED,
     /**
      * The device has entered a special low-power mode within SystemUI, also called the Always-on
      * Display (AOD). A minimal UI is presented to show critical information. If the device is in
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
new file mode 100644
index 0000000..7fb5cfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+/**
+ * Models the appearance of the surface behind the keyguard, and (optionally) how it should be
+ * animating.
+ *
+ * This is intended to be an atomic, high-level description of the surface's appearance and related
+ * animations, which we can derive from the STARTED/FINISHED transition states rather than the
+ * individual TransitionSteps.
+ *
+ * For example, if we're transitioning from LOCKSCREEN to GONE, that means we should be
+ * animatingFromAlpha 0f -> 1f and animatingFromTranslationY 500f -> 0f.
+ * KeyguardSurfaceBehindAnimator can decide how best to implement this, depending on previously
+ * running animations, spring momentum, and other state.
+ */
+data class KeyguardSurfaceBehindModel(
+    val alpha: Float = 1f,
+
+    /**
+     * If provided, animate from this value to [alpha] unless an animation is already running, in
+     * which case we'll animate from the current value to [alpha].
+     */
+    val animateFromAlpha: Float = alpha,
+    val translationY: Float = 0f,
+
+    /**
+     * If provided, animate from this value to [translationY] unless an animation is already
+     * running, in which case we'll animate from the current value to [translationY].
+     */
+    val animateFromTranslationY: Float = translationY,
+) {
+    fun willAnimateAlpha(): Boolean {
+        return animateFromAlpha != alpha
+    }
+
+    fun willAnimateTranslationY(): Boolean {
+        return animateFromTranslationY != translationY
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt
new file mode 100644
index 0000000..80a1b75
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+import com.android.systemui.keyguard.ScreenLifecycle
+
+/** Model device screen lifecycle states. */
+data class ScreenModel(
+    val state: ScreenState,
+) {
+    companion object {
+        fun fromScreenLifecycle(screenLifecycle: ScreenLifecycle): ScreenModel {
+            return ScreenModel(ScreenState.fromScreenLifecycleInt(screenLifecycle.getScreenState()))
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt
new file mode 100644
index 0000000..fe5d935
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.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.shared.model
+
+import com.android.systemui.keyguard.ScreenLifecycle
+
+enum class ScreenState {
+    /** Screen is fully off. */
+    SCREEN_OFF,
+    /** Signal that the screen is turning on. */
+    SCREEN_TURNING_ON,
+    /** Screen is fully on. */
+    SCREEN_ON,
+    /** Signal that the screen is turning off. */
+    SCREEN_TURNING_OFF;
+
+    companion object {
+        fun fromScreenLifecycleInt(value: Int): ScreenState {
+            return when (value) {
+                ScreenLifecycle.SCREEN_OFF -> SCREEN_OFF
+                ScreenLifecycle.SCREEN_TURNING_ON -> SCREEN_TURNING_ON
+                ScreenLifecycle.SCREEN_ON -> SCREEN_ON
+                ScreenLifecycle.SCREEN_TURNING_OFF -> SCREEN_TURNING_OFF
+                else -> throw IllegalArgumentException("Invalid screen value: $value")
+            }
+        }
+    }
+}
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
index 767fd58..0fa6f4f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -16,7 +16,9 @@
 package com.android.systemui.keyguard.shared.model
 
 /** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */
-data class TransitionStep(
+data class TransitionStep
+@JvmOverloads
+constructor(
     val from: KeyguardState = KeyguardState.OFF,
     val to: KeyguardState = KeyguardState.OFF,
     val value: Float = 0f, // constrained [0.0, 1.0]
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
index 51ce7ff..fb685da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
@@ -23,9 +23,12 @@
     /** The physical power button was pressed to wake up or sleep the device. */
     POWER_BUTTON,
 
-    /** The user has taped or double tapped to wake the screen */
+    /** The user has tapped or double tapped to wake the screen. */
     TAP,
 
+    /** The user performed some sort of gesture to wake the screen. */
+    GESTURE,
+
     /** Something else happened to wake up or sleep the device. */
     OTHER;
 
@@ -34,6 +37,7 @@
             return when (reason) {
                 PowerManager.WAKE_REASON_POWER_BUTTON -> POWER_BUTTON
                 PowerManager.WAKE_REASON_TAP -> TAP
+                PowerManager.WAKE_REASON_GESTURE -> GESTURE
                 else -> OTHER
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
index 52e15be..62f43ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
@@ -16,6 +16,13 @@
 package com.android.systemui.keyguard.shared.model
 
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.model.WakeSleepReason.GESTURE
+import com.android.systemui.keyguard.shared.model.WakeSleepReason.POWER_BUTTON
+import com.android.systemui.keyguard.shared.model.WakeSleepReason.TAP
+import com.android.systemui.keyguard.shared.model.WakefulnessState.ASLEEP
+import com.android.systemui.keyguard.shared.model.WakefulnessState.AWAKE
+import com.android.systemui.keyguard.shared.model.WakefulnessState.STARTING_TO_SLEEP
+import com.android.systemui.keyguard.shared.model.WakefulnessState.STARTING_TO_WAKE
 
 /** Model device wakefulness states. */
 data class WakefulnessModel(
@@ -23,25 +30,32 @@
     val lastWakeReason: WakeSleepReason,
     val lastSleepReason: WakeSleepReason,
 ) {
-    fun isStartingToWake() = state == WakefulnessState.STARTING_TO_WAKE
+    fun isStartingToWake() = state == STARTING_TO_WAKE
 
-    fun isStartingToSleep() = state == WakefulnessState.STARTING_TO_SLEEP
+    fun isStartingToSleep() = state == STARTING_TO_SLEEP
 
-    fun isStartingToSleepOrAsleep() = isStartingToSleep() || state == WakefulnessState.ASLEEP
+    private fun isAsleep() = state == ASLEEP
 
-    fun isStartingToWakeOrAwake() = isStartingToWake() || state == WakefulnessState.AWAKE
+    private fun isAwake() = state == AWAKE
 
-    fun isStartingToSleepFromPowerButton() =
-        isStartingToSleep() && lastWakeReason == WakeSleepReason.POWER_BUTTON
+    fun isStartingToWakeOrAwake() = isStartingToWake() || isAwake()
 
-    fun isWakingFromPowerButton() =
-        isStartingToWake() && lastWakeReason == WakeSleepReason.POWER_BUTTON
+    fun isStartingToSleepOrAsleep() = isStartingToSleep() || isAsleep()
+
+    fun isDeviceInteractive() = !isAsleep()
+
+    fun isWakingFrom(wakeSleepReason: WakeSleepReason) =
+        isStartingToWake() && lastWakeReason == wakeSleepReason
+
+    fun isStartingToSleepFrom(wakeSleepReason: WakeSleepReason) =
+        isStartingToSleep() && lastSleepReason == wakeSleepReason
 
     fun isTransitioningFromPowerButton() =
-        isStartingToSleepFromPowerButton() || isWakingFromPowerButton()
+        isStartingToSleepFrom(POWER_BUTTON) || isWakingFrom(POWER_BUTTON)
 
-    fun isAwakeFromTap() =
-        state == WakefulnessState.STARTING_TO_WAKE && lastWakeReason == WakeSleepReason.TAP
+    fun isDeviceInteractiveFromTapOrGesture(): Boolean {
+        return isDeviceInteractive() && (lastWakeReason == TAP || lastWakeReason == GESTURE)
+    }
 
     companion object {
         fun fromWakefulnessLifecycle(wakefulnessLifecycle: WakefulnessLifecycle): WakefulnessModel {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt
new file mode 100644
index 0000000..ebf1beb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.keyguard.ui.adapter
+
+/**
+ * Temporary adapter class while
+ * [com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController] is being refactored
+ * before [com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy] is removed.
+ *
+ * TODO (b/278719514): Delete once udfps keyguard view is fully refactored.
+ */
+interface UdfpsKeyguardViewControllerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
new file mode 100644
index 0000000..5c072fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewPropertyAnimator
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+object KeyguardAmbientIndicationAreaViewBinder {
+    /**
+     * Defines interface for an object that acts as the binding between the view and its view-model.
+     *
+     * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
+     * it is bound.
+     */
+    interface Binding {
+        /**
+         * Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the
+         * indication areas.
+         */
+        fun getIndicationAreaAnimators(): List<ViewPropertyAnimator>
+
+        /** Notifies that device configuration has changed. */
+        fun onConfigurationChanged()
+
+        /** Destroys this binding, releases resources, and cancels any coroutines. */
+        fun destroy()
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    fun bind(
+        view: ViewGroup,
+        viewModel: KeyguardAmbientIndicationViewModel,
+        keyguardRootViewModel: KeyguardRootViewModel,
+    ): Binding {
+        val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
+        val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
+
+        val disposableHandle =
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch {
+                        keyguardRootViewModel.alpha.collect { alpha ->
+                            ambientIndicationArea?.apply {
+                                this.importantForAccessibility =
+                                    if (alpha == 0f) {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                    } else {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                    }
+                                this.alpha = alpha
+                            }
+                        }
+                    }
+
+                    launch {
+                        viewModel.indicationAreaTranslationX.collect { translationX ->
+                            ambientIndicationArea?.translationX = translationX
+                        }
+                    }
+
+                    launch {
+                        configurationBasedDimensions
+                            .map { it.defaultBurnInPreventionYOffsetPx }
+                            .flatMapLatest { defaultBurnInOffsetY ->
+                                viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
+                            }
+                            .collect { translationY ->
+                                ambientIndicationArea?.translationY = translationY
+                            }
+                    }
+
+                }
+            }
+
+
+        return object : Binding {
+            override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> {
+                return listOf(ambientIndicationArea).mapNotNull { it?.animate() }
+            }
+
+            override fun onConfigurationChanged() {
+                configurationBasedDimensions.value = loadFromResources(view)
+            }
+
+            override fun destroy() {
+                disposableHandle.dispose()
+            }
+        }
+    }
+
+    private fun loadFromResources(view: View): ConfigurationBasedDimensions {
+        return ConfigurationBasedDimensions(
+            defaultBurnInPreventionYOffsetPx =
+                view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
+        )
+    }
+
+    private data class ConfigurationBasedDimensions(
+        val defaultBurnInPreventionYOffsetPx: Int,
+    )
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
new file mode 100644
index 0000000..ed4dd6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.os.Trace
+import android.util.Log
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.launch
+
+class KeyguardBlueprintViewBinder {
+    companion object {
+        private const val TAG = "KeyguardBlueprintViewBinder"
+
+        fun bind(constraintLayout: ConstraintLayout, viewModel: KeyguardBlueprintViewModel) {
+            constraintLayout.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    launch {
+                        viewModel.blueprint.collect { blueprint ->
+                            Trace.beginSection("KeyguardBlueprintController#applyBlueprint")
+                            Log.d(TAG, "applying blueprint: $blueprint")
+                            ConstraintSet().apply {
+                                clone(constraintLayout)
+                                val emptyLayout = ConstraintSet.Layout()
+                                knownIds.forEach { getConstraint(it).layout.copyFrom(emptyLayout) }
+                                blueprint?.apply(this)
+                                applyTo(constraintLayout)
+                            }
+                            Trace.endSection()
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 5ad6e51..44acf4f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -21,12 +21,10 @@
 import android.graphics.Rect
 import android.graphics.drawable.Animatable2
 import android.util.Size
-import android.util.TypedValue
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewPropertyAnimator
 import android.widget.ImageView
-import android.widget.TextView
 import androidx.core.view.isInvisible
 import androidx.core.view.isVisible
 import androidx.core.view.updateLayoutParams
@@ -65,6 +63,7 @@
  * view-model to be reused for multiple view/view-binder bindings.
  */
 @OptIn(ExperimentalCoroutinesApi::class)
+@Deprecated("Deprecated as part of b/278057014")
 object KeyguardBottomAreaViewBinder {
 
     private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
@@ -77,6 +76,8 @@
      * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
      * it is bound.
      */
+    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
+    @Deprecated("Deprecated as part of b/278057014")
     interface Binding {
         /**
          * Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the
@@ -98,6 +99,7 @@
     }
 
     /** Binds the view to the view-model, continuing to update the former based on the latter. */
+    @Deprecated("Deprecated as part of b/278057014")
     @SuppressLint("ClickableViewAccessibility")
     @JvmStatic
     fun bind(
@@ -108,14 +110,10 @@
         activityStarter: ActivityStarter?,
         messageDisplayer: (Int) -> Unit,
     ): Binding {
-        val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
         val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
         val startButton: ImageView = view.requireViewById(R.id.start_button)
         val endButton: ImageView = view.requireViewById(R.id.end_button)
         val overlayContainer: View = view.requireViewById(R.id.overlay_container)
-        val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text)
-        val indicationTextBottom: TextView =
-            view.requireViewById(R.id.keyguard_indication_text_bottom)
         val settingsMenu: LaunchableLinearLayout =
             view.requireViewById(R.id.keyguard_settings_button)
 
@@ -138,6 +136,8 @@
         val disposableHandle =
             view.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
+
+                    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
                     launch {
                         viewModel.startButton.collect { buttonModel ->
                             updateButton(
@@ -150,6 +150,7 @@
                         }
                     }
 
+                    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
                     launch {
                         viewModel.endButton.collect { buttonModel ->
                             updateButton(
@@ -175,18 +176,19 @@
 
                     launch {
                         viewModel.alpha.collect { alpha ->
-                            view.importantForAccessibility =
-                                if (alpha == 0f) {
-                                    View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                                } else {
-                                    View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                                }
-
-                            ambientIndicationArea?.alpha = alpha
-                            indicationArea.alpha = alpha
+                            ambientIndicationArea?.apply {
+                                this.importantForAccessibility =
+                                    if (alpha == 0f) {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                    } else {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                    }
+                                this.alpha = alpha
+                            }
                         }
                     }
 
+                    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
                     launch {
                         updateButtonAlpha(
                             view = startButton,
@@ -195,6 +197,7 @@
                         )
                     }
 
+                    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
                     launch {
                         updateButtonAlpha(
                             view = endButton,
@@ -205,50 +208,24 @@
 
                     launch {
                         viewModel.indicationAreaTranslationX.collect { translationX ->
-                            indicationArea.translationX = translationX
                             ambientIndicationArea?.translationX = translationX
                         }
                     }
 
                     launch {
-                        combine(
-                                viewModel.isIndicationAreaPadded,
-                                configurationBasedDimensions.map { it.indicationAreaPaddingPx },
-                            ) { isPadded, paddingIfPaddedPx ->
-                                if (isPadded) {
-                                    paddingIfPaddedPx
-                                } else {
-                                    0
-                                }
-                            }
-                            .collect { paddingPx ->
-                                indicationArea.setPadding(paddingPx, 0, paddingPx, 0)
-                            }
-                    }
-
-                    launch {
                         configurationBasedDimensions
                             .map { it.defaultBurnInPreventionYOffsetPx }
                             .flatMapLatest { defaultBurnInOffsetY ->
                                 viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
                             }
                             .collect { translationY ->
-                                indicationArea.translationY = translationY
                                 ambientIndicationArea?.translationY = translationY
                             }
                     }
 
+                    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
                     launch {
                         configurationBasedDimensions.collect { dimensions ->
-                            indicationText.setTextSize(
-                                TypedValue.COMPLEX_UNIT_PX,
-                                dimensions.indicationTextSizePx.toFloat(),
-                            )
-                            indicationTextBottom.setTextSize(
-                                TypedValue.COMPLEX_UNIT_PX,
-                                dimensions.indicationTextSizePx.toFloat(),
-                            )
-
                             startButton.updateLayoutParams<ViewGroup.LayoutParams> {
                                 width = dimensions.buttonSizePx.width
                                 height = dimensions.buttonSizePx.height
@@ -305,7 +282,7 @@
 
         return object : Binding {
             override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> {
-                return listOf(indicationArea, ambientIndicationArea).mapNotNull { it?.animate() }
+                return listOf(ambientIndicationArea).mapNotNull { it?.animate() }
             }
 
             override fun onConfigurationChanged() {
@@ -321,6 +298,8 @@
         }
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
+    // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
     @SuppressLint("ClickableViewAccessibility")
     private fun updateButton(
         view: ImageView,
@@ -428,6 +407,8 @@
         view.isSelected = viewModel.isSelected
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
+    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
     private suspend fun updateButtonAlpha(
         view: View,
         viewModel: Flow<KeyguardQuickAffordanceViewModel>,
@@ -439,6 +420,7 @@
             .collect { view.alpha = it }
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
     private fun View.animateVisibility(visible: Boolean) {
         animate()
             .withStartAction {
@@ -456,6 +438,8 @@
             .start()
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
+    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
     private class OnLongClickListener(
         private val falsingManager: FalsingManager?,
         private val viewModel: KeyguardQuickAffordanceViewModel,
@@ -488,10 +472,11 @@
             return true
         }
 
-        override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
-
+        override fun onLongClickUseDefaultHapticFeedback(view: View) = false
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
+    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
     private class OnClickListener(
         private val viewModel: KeyguardQuickAffordanceViewModel,
         private val falsingManager: FalsingManager,
@@ -513,16 +498,11 @@
         }
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
     private fun loadFromResources(view: View): ConfigurationBasedDimensions {
         return ConfigurationBasedDimensions(
             defaultBurnInPreventionYOffsetPx =
                 view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
-            indicationAreaPaddingPx =
-                view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding),
-            indicationTextSizePx =
-                view.resources.getDimensionPixelSize(
-                    com.android.internal.R.dimen.text_size_small_material,
-                ),
             buttonSizePx =
                 Size(
                     view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
@@ -531,6 +511,7 @@
         )
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
     /** Opens the wallpaper picker screen after the device is unlocked by the user. */
     private fun navigateToLockScreenSettings(
         activityStarter: ActivityStarter,
@@ -550,10 +531,10 @@
         )
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
+    //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
     private data class ConfigurationBasedDimensions(
         val defaultBurnInPreventionYOffsetPx: Int,
-        val indicationAreaPaddingPx: Int,
-        val indicationTextSizePx: Int,
         val buttonSizePx: Size,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
deleted file mode 100644
index c1aefc7..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ /dev/null
@@ -1,237 +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.keyguard.ui.binder
-
-import android.view.KeyEvent
-import android.view.View
-import android.view.ViewGroup
-import android.window.OnBackAnimationCallback
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.keyguard.KeyguardSecurityContainerController
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityView
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.dagger.KeyguardBouncerComponent
-import com.android.systemui.keyguard.data.BouncerViewDelegate
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
-import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.plugins.ActivityStarter
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.launch
-
-/** Binds the bouncer container to its view model. */
-object KeyguardBouncerViewBinder {
-    @JvmStatic
-    fun bind(
-        view: ViewGroup,
-        viewModel: KeyguardBouncerViewModel,
-        primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
-        componentFactory: KeyguardBouncerComponent.Factory
-    ) {
-        // Builds the KeyguardSecurityContainerController from bouncer view group.
-        val securityContainerController: KeyguardSecurityContainerController =
-            componentFactory.create(view).securityContainerController
-        securityContainerController.init()
-        val delegate =
-            object : BouncerViewDelegate {
-                override fun isFullScreenBouncer(): Boolean {
-                    val mode = securityContainerController.currentSecurityMode
-                    return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
-                        mode == KeyguardSecurityModel.SecurityMode.SimPuk
-                }
-
-                override fun getBackCallback(): OnBackAnimationCallback {
-                    return securityContainerController.backCallback
-                }
-
-                override fun shouldDismissOnMenuPressed(): Boolean {
-                    return securityContainerController.shouldEnableMenuKey()
-                }
-
-                override fun interceptMediaKey(event: KeyEvent?): Boolean {
-                    return securityContainerController.interceptMediaKey(event)
-                }
-
-                override fun dispatchBackKeyEventPreIme(): Boolean {
-                    return securityContainerController.dispatchBackKeyEventPreIme()
-                }
-
-                override fun showNextSecurityScreenOrFinish(): Boolean {
-                    return securityContainerController.dismiss(
-                        KeyguardUpdateMonitor.getCurrentUser()
-                    )
-                }
-
-                override fun resume() {
-                    securityContainerController.showPrimarySecurityScreen(/* isTurningOff= */ false)
-                    securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
-                }
-
-                override fun setDismissAction(
-                    onDismissAction: ActivityStarter.OnDismissAction?,
-                    cancelAction: Runnable?
-                ) {
-                    securityContainerController.setOnDismissAction(onDismissAction, cancelAction)
-                }
-
-                override fun willDismissWithActions(): Boolean {
-                    return securityContainerController.hasDismissActions()
-                }
-
-                override fun willRunDismissFromKeyguard(): Boolean {
-                    return securityContainerController.willRunDismissFromKeyguard()
-                }
-
-                override fun showPromptReason(reason: Int) {
-                    securityContainerController.showPromptReason(reason)
-                }
-            }
-        view.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.CREATED) {
-                try {
-                    viewModel.setBouncerViewDelegate(delegate)
-                    launch {
-                        viewModel.isShowing.collect { isShowing ->
-                            view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE
-                            if (isShowing) {
-                                securityContainerController.reinflateViewFlipper {
-                                    // Reset Security Container entirely.
-                                    securityContainerController.onBouncerVisibilityChanged(
-                                        /* isVisible= */ true
-                                    )
-                                    securityContainerController.showPrimarySecurityScreen(
-                                        /* turningOff= */ false
-                                    )
-                                    securityContainerController.setInitialMessage()
-                                    securityContainerController.appear()
-                                    securityContainerController.onResume(
-                                        KeyguardSecurityView.SCREEN_ON
-                                    )
-                                }
-                            } else {
-                                securityContainerController.onBouncerVisibilityChanged(
-                                    /* isVisible= */ false
-                                )
-                                securityContainerController.cancelDismissAction()
-                                securityContainerController.reset()
-                                securityContainerController.onPause()
-                            }
-                        }
-                    }
-
-                    launch {
-                        viewModel.startingToHide.collect {
-                            securityContainerController.onStartingToHide()
-                        }
-                    }
-
-                    launch {
-                        viewModel.startDisappearAnimation.collect {
-                            securityContainerController.startDisappearAnimation(it)
-                        }
-                    }
-
-                    launch {
-                        viewModel.bouncerExpansionAmount.collect { expansion ->
-                            securityContainerController.setExpansion(expansion)
-                        }
-                    }
-
-                    launch {
-                        primaryBouncerToGoneTransitionViewModel.bouncerAlpha.collect { alpha ->
-                            securityContainerController.setAlpha(alpha)
-                        }
-                    }
-
-                    launch {
-                        viewModel.bouncerExpansionAmount
-                            .filter { it == EXPANSION_VISIBLE }
-                            .collect {
-                                securityContainerController.onResume(KeyguardSecurityView.SCREEN_ON)
-                                view.announceForAccessibility(securityContainerController.title)
-                            }
-                    }
-
-                    launch {
-                        viewModel.isInteractable.collect { isInteractable ->
-                            securityContainerController.setInteractable(isInteractable)
-                        }
-                    }
-
-                    launch {
-                        viewModel.keyguardPosition.collect { position ->
-                            securityContainerController.updateKeyguardPosition(position)
-                        }
-                    }
-
-                    launch {
-                        viewModel.updateResources.collect {
-                            securityContainerController.updateResources()
-                            viewModel.notifyUpdateResources()
-                        }
-                    }
-
-                    launch {
-                        viewModel.bouncerShowMessage.collect {
-                            securityContainerController.showMessage(
-                                it.message,
-                                it.colorStateList,
-                                /* animated= */ true
-                            )
-                            viewModel.onMessageShown()
-                        }
-                    }
-
-                    launch {
-                        viewModel.keyguardAuthenticated.collect {
-                            securityContainerController.finish(
-                                it,
-                                KeyguardUpdateMonitor.getCurrentUser()
-                            )
-                            viewModel.notifyKeyguardAuthenticated()
-                        }
-                    }
-
-                    launch {
-                        viewModel
-                            .observeOnIsBackButtonEnabled { view.systemUiVisibility }
-                            .collect { view.systemUiVisibility = it }
-                    }
-
-                    launch {
-                        viewModel.shouldUpdateSideFps.collect {
-                            viewModel.updateSideFpsVisibility()
-                        }
-                    }
-
-                    launch {
-                        viewModel.sideFpsShowing.collect {
-                            securityContainerController.updateSideFpsVisibility(it)
-                        }
-                    }
-                    awaitCancellation()
-                } finally {
-                    viewModel.setBouncerViewDelegate(null)
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
new file mode 100644
index 0000000..dc51944
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.util.TypedValue
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.KeyguardIndicationController
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Binds a keyguard indication area view to its view-model.
+ *
+ * To use this properly, users should maintain a one-to-one relationship between the [View] and the
+ * view-binding, binding each view only once. It is okay and expected for the same instance of the
+ * view-model to be reused for multiple view/view-binder bindings.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+object KeyguardIndicationAreaBinder {
+
+    /** Binds the view to the view-model, continuing to update the former based on the latter. */
+    @JvmStatic
+    fun bind(
+        view: ViewGroup,
+        viewModel: KeyguardIndicationAreaViewModel,
+        keyguardRootViewModel: KeyguardRootViewModel,
+        indicationController: KeyguardIndicationController,
+        featureFlags: FeatureFlags,
+    ): DisposableHandle {
+        val indicationArea: ViewGroup = view.requireViewById(R.id.keyguard_indication_area)
+        indicationController.setIndicationArea(indicationArea)
+
+        val indicationText: TextView = indicationArea.requireViewById(R.id.keyguard_indication_text)
+        val indicationTextBottom: TextView =
+            indicationArea.requireViewById(R.id.keyguard_indication_text_bottom)
+
+        view.clipChildren = false
+        view.clipToPadding = false
+
+        val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
+        val disposableHandle =
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch {
+                        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+                            keyguardRootViewModel.alpha.collect { alpha ->
+                                indicationArea.apply {
+                                    this.importantForAccessibility =
+                                        if (alpha == 0f) {
+                                            View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                        } else {
+                                            View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                        }
+                                    this.alpha = alpha
+                                }
+                            }
+                        } else {
+                            viewModel.alpha.collect { alpha ->
+                                indicationArea.apply {
+                                    this.importantForAccessibility =
+                                        if (alpha == 0f) {
+                                            View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                        } else {
+                                            View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                        }
+                                    this.alpha = alpha
+                                }
+                            }
+                        }
+                    }
+
+                    launch {
+                        viewModel.indicationAreaTranslationX.collect { translationX ->
+                            indicationArea.translationX = translationX
+                        }
+                    }
+
+                    launch {
+                        combine(
+                                viewModel.isIndicationAreaPadded,
+                                configurationBasedDimensions.map { it.indicationAreaPaddingPx },
+                            ) { isPadded, paddingIfPaddedPx ->
+                                if (isPadded) {
+                                    paddingIfPaddedPx
+                                } else {
+                                    0
+                                }
+                            }
+                            .collect { paddingPx ->
+                                indicationArea.setPadding(paddingPx, 0, paddingPx, 0)
+                            }
+                    }
+
+                    launch {
+                        configurationBasedDimensions
+                            .map { it.defaultBurnInPreventionYOffsetPx }
+                            .flatMapLatest { defaultBurnInOffsetY ->
+                                viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
+                            }
+                            .collect { translationY -> indicationArea.translationY = translationY }
+                    }
+
+                    launch {
+                        configurationBasedDimensions.collect { dimensions ->
+                            indicationText.setTextSize(
+                                TypedValue.COMPLEX_UNIT_PX,
+                                dimensions.indicationTextSizePx.toFloat(),
+                            )
+                            indicationTextBottom.setTextSize(
+                                TypedValue.COMPLEX_UNIT_PX,
+                                dimensions.indicationTextSizePx.toFloat(),
+                            )
+                        }
+                    }
+
+                    launch {
+                        viewModel.configurationChange.collect {
+                            configurationBasedDimensions.value = loadFromResources(view)
+                        }
+                    }
+                }
+            }
+        return disposableHandle
+    }
+
+    private fun loadFromResources(view: View): ConfigurationBasedDimensions {
+        return ConfigurationBasedDimensions(
+            defaultBurnInPreventionYOffsetPx =
+                view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
+            indicationAreaPaddingPx =
+                view.resources.getDimensionPixelOffset(R.dimen.keyguard_indication_area_padding),
+            indicationTextSizePx =
+                view.resources.getDimensionPixelSize(
+                    com.android.internal.R.dimen.text_size_small_material,
+                ),
+        )
+    }
+
+    private data class ConfigurationBasedDimensions(
+        val defaultBurnInPreventionYOffsetPx: Int,
+        val indicationAreaPaddingPx: Int,
+        val indicationTextSizePx: Int,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
new file mode 100644
index 0000000..83b5463
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.annotation.SuppressLint
+import android.graphics.drawable.Animatable2
+import android.util.Size
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.core.view.isInvisible
+import androidx.core.view.isVisible
+import androidx.core.view.updateLayoutParams
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.animation.view.LaunchableImageView
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * This is only for a SINGLE Quick affordance
+ */
+object KeyguardQuickAffordanceViewBinder {
+
+    private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
+    private const val SCALE_SELECTED_BUTTON = 1.23f
+    private const val DIM_ALPHA = 0.3f
+
+    /**
+     * Defines interface for an object that acts as the binding between the view and its view-model.
+     *
+     * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
+     * it is bound.
+     */
+    interface Binding {
+        /** Notifies that device configuration has changed. */
+        fun onConfigurationChanged()
+
+        /** Destroys this binding, releases resources, and cancels any coroutines. */
+        fun destroy()
+    }
+
+    fun bind(
+        view: LaunchableImageView,
+        viewModel: Flow<KeyguardQuickAffordanceViewModel>,
+        alpha: Flow<Float>,
+        falsingManager: FalsingManager?,
+        vibratorHelper: VibratorHelper?,
+        messageDisplayer: (Int) -> Unit,
+    ): Binding {
+        val button = view as ImageView
+        val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
+        val disposableHandle =
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch {
+                        viewModel.collect { buttonModel ->
+                            updateButton(
+                                view = button,
+                                viewModel = buttonModel,
+                                falsingManager = falsingManager,
+                                messageDisplayer = messageDisplayer,
+                                vibratorHelper = vibratorHelper,
+                            )
+                        }
+                    }
+
+                    launch {
+                        updateButtonAlpha(
+                            view = button,
+                            viewModel = viewModel,
+                            alphaFlow = alpha,
+                        )
+                    }
+
+                    launch {
+                        configurationBasedDimensions.collect { dimensions ->
+                            button.updateLayoutParams<ViewGroup.LayoutParams> {
+                                width = dimensions.buttonSizePx.width
+                                height = dimensions.buttonSizePx.height
+                            }
+                        }
+                    }
+                }
+            }
+
+        return object : Binding {
+            override fun onConfigurationChanged() {
+                configurationBasedDimensions.value = loadFromResources(view)
+            }
+
+            override fun destroy() {
+                disposableHandle.dispose()
+            }
+        }
+    }
+
+    @SuppressLint("ClickableViewAccessibility")
+    private fun updateButton(
+        view: ImageView,
+        viewModel: KeyguardQuickAffordanceViewModel,
+        falsingManager: FalsingManager?,
+        messageDisplayer: (Int) -> Unit,
+        vibratorHelper: VibratorHelper?,
+    ) {
+        if (!viewModel.isVisible) {
+            view.isInvisible = true
+            return
+        }
+
+        if (!view.isVisible) {
+            view.isVisible = true
+            if (viewModel.animateReveal) {
+                view.alpha = 0f
+                view.translationY = view.height / 2f
+                view
+                    .animate()
+                    .alpha(1f)
+                    .translationY(0f)
+                    .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+                    .setDuration(EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS)
+                    .start()
+            }
+        }
+
+        IconViewBinder.bind(viewModel.icon, view)
+
+        (view.drawable as? Animatable2)?.let { animatable ->
+            (viewModel.icon as? Icon.Resource)?.res?.let { iconResourceId ->
+                // Always start the animation (we do call stop() below, if we need to skip it).
+                animatable.start()
+
+                if (view.tag != iconResourceId) {
+                    // Here when we haven't run the animation on a previous update.
+                    //
+                    // Save the resource ID for next time, so we know not to re-animate the same
+                    // animation again.
+                    view.tag = iconResourceId
+                } else {
+                    // Here when we've already done this animation on a previous update and want to
+                    // skip directly to the final frame of the animation to avoid running it.
+                    //
+                    // By calling stop after start, we go to the final frame of the animation.
+                    animatable.stop()
+                }
+            }
+        }
+
+        view.isActivated = viewModel.isActivated
+        view.drawable.setTint(
+            Utils.getColorAttrDefaultColor(
+                view.context,
+                if (viewModel.isActivated) {
+                    com.android.internal.R.attr.materialColorOnPrimaryFixed
+                } else {
+                    com.android.internal.R.attr.materialColorOnSurface
+                },
+            )
+        )
+
+        view.backgroundTintList =
+            if (!viewModel.isSelected) {
+                Utils.getColorAttr(
+                    view.context,
+                    if (viewModel.isActivated) {
+                        com.android.internal.R.attr.materialColorPrimaryFixed
+                    } else {
+                        com.android.internal.R.attr.materialColorSurfaceContainerHigh
+                    }
+                )
+            } else {
+                null
+            }
+        view
+            .animate()
+            .scaleX(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
+            .scaleY(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
+            .start()
+
+        view.isClickable = viewModel.isClickable
+        if (viewModel.isClickable) {
+            if (viewModel.useLongPress) {
+                val onTouchListener = KeyguardQuickAffordanceOnTouchListener(
+                    view,
+                    viewModel,
+                    messageDisplayer,
+                    vibratorHelper,
+                    falsingManager,
+                )
+                view.setOnTouchListener(onTouchListener)
+                view.onLongClickListener =
+                    OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener)
+            } else {
+                view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
+            }
+        } else {
+            view.onLongClickListener = null
+            view.setOnClickListener(null)
+            view.setOnTouchListener(null)
+        }
+
+        view.isSelected = viewModel.isSelected
+    }
+
+    private suspend fun updateButtonAlpha(
+        view: View,
+        viewModel: Flow<KeyguardQuickAffordanceViewModel>,
+        alphaFlow: Flow<Float>,
+    ) {
+        combine(viewModel.map { it.isDimmed }, alphaFlow) { isDimmed, alpha ->
+            if (isDimmed) DIM_ALPHA else alpha
+        }
+            .collect { view.alpha = it }
+    }
+
+    private fun loadFromResources(view: View): ConfigurationBasedDimensions {
+        return ConfigurationBasedDimensions(
+            buttonSizePx =
+            Size(
+                view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
+                view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+            ),
+        )
+    }
+
+    private class OnClickListener(
+        private val viewModel: KeyguardQuickAffordanceViewModel,
+        private val falsingManager: FalsingManager,
+    ) : View.OnClickListener {
+        override fun onClick(view: View) {
+            if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                return
+            }
+
+            if (viewModel.configKey != null) {
+                viewModel.onClicked(
+                    KeyguardQuickAffordanceViewModel.OnClickedParameters(
+                        configKey = viewModel.configKey,
+                        expandable = Expandable.fromView(view),
+                        slotId = viewModel.slotId,
+                    )
+                )
+            }
+        }
+    }
+
+    private class OnLongClickListener(
+        private val falsingManager: FalsingManager?,
+        private val viewModel: KeyguardQuickAffordanceViewModel,
+        private val vibratorHelper: VibratorHelper?,
+        private val onTouchListener: KeyguardQuickAffordanceOnTouchListener
+    ) : View.OnLongClickListener {
+        override fun onLongClick(view: View): Boolean {
+            if (falsingManager?.isFalseLongTap(FalsingManager.MODERATE_PENALTY) == true) {
+                return true
+            }
+
+            if (viewModel.configKey != null) {
+                viewModel.onClicked(
+                    KeyguardQuickAffordanceViewModel.OnClickedParameters(
+                        configKey = viewModel.configKey,
+                        expandable = Expandable.fromView(view),
+                        slotId = viewModel.slotId,
+                    )
+                )
+                vibratorHelper?.vibrate(
+                    if (viewModel.isActivated) {
+                        KeyguardBottomAreaVibrations.Activated
+                    } else {
+                        KeyguardBottomAreaVibrations.Deactivated
+                    }
+                )
+            }
+
+            onTouchListener.cancel()
+            return true
+        }
+
+        override fun onLongClickUseDefaultHapticFeedback(view: View) = false
+
+    }
+
+    private data class ConfigurationBasedDimensions(
+        val buttonSizePx: Size,
+    )
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
new file mode 100644
index 0000000..19f622b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.annotation.DrawableRes
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.animation.Interpolators
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.temporarydisplay.ViewPriority
+import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+/** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
+@ExperimentalCoroutinesApi
+object KeyguardRootViewBinder {
+    @JvmStatic
+    fun bind(
+        view: ViewGroup,
+        viewModel: KeyguardRootViewModel,
+        featureFlags: FeatureFlags,
+        occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
+        chipbarCoordinator: ChipbarCoordinator,
+        keyguardStateController: KeyguardStateController,
+    ): DisposableHandle {
+        val disposableHandle =
+            view.repeatWhenAttached {
+                if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
+                    repeatOnLifecycle(Lifecycle.State.CREATED) {
+                        launch {
+                            occludingAppDeviceEntryMessageViewModel.message.collect {
+                                biometricMessage ->
+                                if (biometricMessage?.message != null) {
+                                    chipbarCoordinator.displayView(
+                                        createChipbarInfo(
+                                            biometricMessage.message,
+                                            R.drawable.ic_lock,
+                                        )
+                                    )
+                                } else {
+                                    chipbarCoordinator.removeView(ID, "occludingAppMsgNull")
+                                }
+                            }
+                        }
+                    }
+                }
+
+                if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+                    repeatOnLifecycle(Lifecycle.State.STARTED) {
+                        launch {
+                            viewModel.keyguardRootViewVisibilityState.collect { visibilityState ->
+                                view.animate().cancel()
+                                val goingToFullShade = visibilityState.goingToFullShade
+                                val statusBarState = visibilityState.statusBarState
+                                val isOcclusionTransitionRunning =
+                                    visibilityState.occlusionTransitionRunning
+                                if (goingToFullShade) {
+                                    view.animate().alpha(0f).setStartDelay(
+                                        keyguardStateController.keyguardFadingAwayDelay
+                                    ).setDuration(
+                                        keyguardStateController.shortenedFadingAwayDuration
+                                    ).setInterpolator(
+                                        Interpolators.ALPHA_OUT
+                                    ).withEndAction { view.visibility = View.GONE }.start()
+                                } else if (
+                                    statusBarState == StatusBarState.KEYGUARD ||
+                                    statusBarState == StatusBarState.SHADE_LOCKED
+                                ) {
+                                    view.visibility = View.VISIBLE
+                                    if (!isOcclusionTransitionRunning) {
+                                        view.alpha = 1f
+                                    }
+                                } else {
+                                    view.visibility = View.GONE
+                                }
+                            }
+                        }
+
+                        launch {
+                            viewModel.alpha.collect { alpha ->
+                                view.alpha = alpha
+                            }
+                        }
+                    }
+                }
+            }
+        return disposableHandle
+    }
+
+    /**
+     * Creates an instance of [ChipbarInfo] that can be sent to [ChipbarCoordinator] for display.
+     */
+    private fun createChipbarInfo(message: String, @DrawableRes icon: Int): ChipbarInfo {
+        return ChipbarInfo(
+            startIcon =
+            TintedIcon(
+                Icon.Resource(icon, null),
+                ChipbarInfo.DEFAULT_ICON_TINT,
+            ),
+            text = Text.Loaded(message),
+            endItem = null,
+            vibrationEffect = null,
+            windowTitle = "OccludingAppUnlockMsgChip",
+            wakeReason = "OCCLUDING_APP_UNLOCK_MSG_CHIP",
+            timeoutMs = 3500,
+            id = ID,
+            priority = ViewPriority.CRITICAL,
+            instanceId = null,
+        )
+    }
+
+    private const val ID = "occluding_app_device_entry_unlock_msg"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
new file mode 100644
index 0000000..82610e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.content.Intent
+import android.view.View
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.view.LaunchableLinearLayout
+import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.common.ui.binder.TextViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.VibratorHelper
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.launch
+
+object KeyguardSettingsViewBinder {
+    fun bind(
+        parentView: View,
+        viewModel: KeyguardSettingsMenuViewModel,
+        vibratorHelper: VibratorHelper,
+        activityStarter: ActivityStarter
+    ): DisposableHandle {
+        val view = parentView.requireViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
+
+        val disposableHandle =
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch {
+                        viewModel.isVisible.distinctUntilChanged().collect { isVisible ->
+                            view.animateVisibility(visible = isVisible)
+                            if (isVisible) {
+                                vibratorHelper.vibrate(KeyguardBottomAreaVibrations.Activated)
+                                view.setOnTouchListener(
+                                    KeyguardSettingsButtonOnTouchListener(
+                                        view = view,
+                                        viewModel = viewModel,
+                                    )
+                                )
+                                IconViewBinder.bind(
+                                    icon = viewModel.icon,
+                                    view = view.requireViewById(R.id.icon),
+                                )
+                                TextViewBinder.bind(
+                                    view = view.requireViewById(R.id.text),
+                                    viewModel = viewModel.text,
+                                )
+                            }
+                        }
+                    }
+
+                    // activityStarter will only be null when rendering the preview that
+                    // shows up in the Wallpaper Picker app. If we do that, then the
+                    // settings menu should never be visible.
+                    if (activityStarter != null) {
+                        launch {
+                            viewModel.shouldOpenSettings
+                                .filter { it }
+                                .collect {
+                                    navigateToLockScreenSettings(
+                                        activityStarter = activityStarter,
+                                        view = view,
+                                    )
+                                    viewModel.onSettingsShown()
+                                }
+                        }
+                    }
+                }
+            }
+        return disposableHandle
+    }
+
+    /** Opens the wallpaper picker screen after the device is unlocked by the user. */
+    private fun navigateToLockScreenSettings(
+        activityStarter: ActivityStarter,
+        view: View,
+    ) {
+        activityStarter.postStartActivityDismissingKeyguard(
+            Intent(Intent.ACTION_SET_WALLPAPER).apply {
+                flags = Intent.FLAG_ACTIVITY_NEW_TASK
+                view.context
+                    .getString(R.string.config_wallpaperPickerPackage)
+                    .takeIf { it.isNotEmpty() }
+                    ?.let { packageName -> setPackage(packageName) }
+            },
+            /* delay= */ 0,
+            /* animationController= */ ActivityLaunchAnimator.Controller.fromView(view),
+            /* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls)
+        )
+    }
+
+    private fun View.animateVisibility(visible: Boolean) {
+        animate()
+            .withStartAction {
+                if (visible) {
+                    alpha = 0f
+                    isVisible = true
+                }
+            }
+            .alpha(if (visible) 1f else 0f)
+            .withEndAction {
+                if (!visible) {
+                    isVisible = false
+                }
+            }
+            .start()
+    }
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
new file mode 100644
index 0000000..fe2df21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Matrix
+import android.util.Log
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.SyncRtSurfaceTransactionApplier
+import android.view.View
+import androidx.dynamicanimation.animation.FloatValueHolder
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.TAG
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.wm.shell.animation.Interpolators
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Applies [KeyguardSurfaceBehindViewParams] to a RemoteAnimationTarget, starting and managing
+ * animations as needed.
+ */
+@SysUISingleton
+class KeyguardSurfaceBehindParamsApplier
+@Inject
+constructor(
+    @Main private val executor: Executor,
+    private val keyguardViewController: KeyguardViewController,
+    private val interactor: KeyguardSurfaceBehindInteractor,
+) {
+    private var surfaceBehind: RemoteAnimationTarget? = null
+    private val surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+        get() = SyncRtSurfaceTransactionApplier(keyguardViewController.viewRootImpl.view)
+
+    private val matrix = Matrix()
+    private val tmpFloat = FloatArray(9)
+
+    private var animatedTranslationY = FloatValueHolder()
+    private val translateYSpring =
+        SpringAnimation(animatedTranslationY).apply {
+            spring =
+                SpringForce().apply {
+                    stiffness = 200f
+                    dampingRatio = 1f
+                }
+            addUpdateListener { _, _, _ -> applyToSurfaceBehind() }
+            addEndListener { _, _, _, _ ->
+                try {
+                    updateIsAnimatingSurface()
+                } catch (e: NullPointerException) {
+                    // TODO(b/291645410): Remove when we can isolate DynamicAnimations.
+                    e.printStackTrace()
+                }
+            }
+        }
+
+    private var animatedAlpha = 0f
+    private var alphaAnimator =
+        ValueAnimator.ofFloat(0f, 1f).apply {
+            duration = 500
+            interpolator = Interpolators.ALPHA_IN
+            addUpdateListener {
+                animatedAlpha = it.animatedValue as Float
+                applyToSurfaceBehind()
+            }
+            addListener(
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator?) {
+                        updateIsAnimatingSurface()
+                    }
+                }
+            )
+        }
+
+    /**
+     * ViewParams to apply to the surface provided to [applyParamsToSurface]. If the surface is null
+     * these will be applied once someone gives us a surface via [applyParamsToSurface].
+     */
+    var viewParams: KeyguardSurfaceBehindModel = KeyguardSurfaceBehindModel()
+        set(newParams) {
+            field = newParams
+            startOrUpdateAnimators()
+            applyToSurfaceBehind()
+        }
+
+    /**
+     * Provides us with a surface to animate. We'll apply the [viewParams] to this surface and start
+     * any necessary animations.
+     */
+    fun applyParamsToSurface(surface: RemoteAnimationTarget) {
+        this.surfaceBehind = surface
+        startOrUpdateAnimators()
+    }
+
+    /**
+     * Notifies us that the [RemoteAnimationTarget] has been released, one way or another.
+     * Attempting to animate a released target will cause a crash.
+     *
+     * This can be called either because we finished animating the surface naturally, or by WM
+     * because external factors cancelled the remote animation (timeout, re-lock, etc). If it's the
+     * latter, cancel any outstanding animations we have.
+     */
+    fun notifySurfaceReleased() {
+        surfaceBehind = null
+
+        if (alphaAnimator.isRunning) {
+            alphaAnimator.cancel()
+        }
+
+        if (translateYSpring.isRunning) {
+            translateYSpring.cancel()
+        }
+    }
+
+    private fun startOrUpdateAnimators() {
+        if (surfaceBehind == null) {
+            return
+        }
+
+        if (viewParams.willAnimateAlpha()) {
+            var fromAlpha = viewParams.animateFromAlpha
+
+            if (alphaAnimator.isRunning) {
+                alphaAnimator.cancel()
+                fromAlpha = animatedAlpha
+            }
+
+            alphaAnimator.setFloatValues(fromAlpha, viewParams.alpha)
+            alphaAnimator.start()
+        }
+
+        if (viewParams.willAnimateTranslationY()) {
+            if (!translateYSpring.isRunning) {
+                // If the spring isn't running yet, set the start value. Otherwise, respect the
+                // current position.
+                animatedTranslationY.value = viewParams.animateFromTranslationY
+            }
+
+            translateYSpring.animateToFinalPosition(viewParams.translationY)
+        }
+
+        updateIsAnimatingSurface()
+    }
+
+    private fun updateIsAnimatingSurface() {
+        interactor.setAnimatingSurface(translateYSpring.isRunning || alphaAnimator.isRunning)
+    }
+
+    private fun applyToSurfaceBehind() {
+        surfaceBehind?.leash?.let { sc ->
+            executor.execute {
+                if (surfaceBehind == null) {
+                    Log.d(
+                        TAG,
+                        "Attempting to modify params of surface that isn't " +
+                            "animating. Ignoring."
+                    )
+                    matrix.set(Matrix.IDENTITY_MATRIX)
+                    return@execute
+                }
+
+                val translationY =
+                    if (translateYSpring.isRunning) animatedTranslationY.value
+                    else viewParams.translationY
+
+                val alpha =
+                    if (alphaAnimator.isRunning) {
+                        animatedAlpha
+                    } else {
+                        viewParams.alpha
+                    }
+
+                if (
+                    keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+                        sc.isValid
+                ) {
+                    with(SurfaceControl.Transaction()) {
+                        setMatrix(
+                            sc,
+                            matrix.apply { setTranslate(/* dx= */ 0f, translationY) },
+                            tmpFloat
+                        )
+                        setAlpha(sc, alpha)
+                        apply()
+                    }
+                } else {
+                    surfaceTransactionApplier.scheduleApply(
+                        SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(sc)
+                            .withMatrix(matrix.apply { setTranslate(/* dx= */ 0f, translationY) })
+                            .withAlpha(alpha)
+                            .build()
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
new file mode 100644
index 0000000..599f69f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
+ * surface behind the keyguard.
+ */
+object KeyguardSurfaceBehindViewBinder {
+    @JvmStatic
+    fun bind(
+        viewModel: KeyguardSurfaceBehindViewModel,
+        applier: KeyguardSurfaceBehindParamsApplier,
+        scope: CoroutineScope
+    ) {
+        scope.launch { viewModel.surfaceBehindViewParams.collect { applier.viewParams = it } }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
new file mode 100644
index 0000000..9872d97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsAodFingerprintViewBinder {
+
+    /**
+     * Drives UI for the UDFPS aod fingerprint view. See [UdfpsFingerprintViewBinder] and
+     * [UdfpsBackgroundViewBinder].
+     */
+    @JvmStatic
+    fun bind(
+        view: LottieAnimationView,
+        viewModel: UdfpsAodViewModel,
+    ) {
+        view.alpha = 0f
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.burnInOffsets.collect { burnInOffsets ->
+                        view.progress = burnInOffsets.burnInProgress
+                        view.translationX = burnInOffsets.burnInXOffset.toFloat()
+                        view.translationY = burnInOffsets.burnInYOffset.toFloat()
+                    }
+                }
+
+                launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } }
+
+                launch {
+                    viewModel.padding.collect { padding ->
+                        view.setPadding(padding, padding, padding, padding)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
new file mode 100644
index 0000000..0113628
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.content.res.ColorStateList
+import android.widget.ImageView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsBackgroundViewBinder {
+
+    /**
+     * Drives UI for the udfps background view. See [UdfpsAodFingerprintViewBinder] and
+     * [UdfpsFingerprintViewBinder].
+     */
+    @JvmStatic
+    fun bind(
+        view: ImageView,
+        viewModel: BackgroundViewModel,
+    ) {
+        view.alpha = 0f
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.transition.collect {
+                        view.alpha = it.alpha
+                        view.scaleX = it.scale
+                        view.scaleY = it.scale
+                        view.imageTintList = ColorStateList.valueOf(it.color)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
new file mode 100644
index 0000000..bab04f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.model.KeyPath
+import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsFingerprintViewBinder {
+    private var udfpsIconColor = 0
+
+    /**
+     * Drives UI for the UDFPS fingerprint view when it's NOT on aod. See
+     * [UdfpsAodFingerprintViewBinder] and [UdfpsBackgroundViewBinder].
+     */
+    @JvmStatic
+    fun bind(
+        view: LottieAnimationView,
+        viewModel: FingerprintViewModel,
+    ) {
+        view.alpha = 0f
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.transition.collect {
+                        view.alpha = it.alpha
+                        view.scaleX = it.scale
+                        view.scaleY = it.scale
+                        if (udfpsIconColor != (it.color)) {
+                            udfpsIconColor = it.color
+                            view.invalidate()
+                        }
+                    }
+                }
+
+                launch {
+                    viewModel.burnInOffsets.collect { burnInOffsets ->
+                        view.translationX = burnInOffsets.burnInXOffset.toFloat()
+                        view.translationY = burnInOffsets.burnInYOffset.toFloat()
+                    }
+                }
+
+                launch {
+                    viewModel.dozeAmount.collect { dozeAmount ->
+                        // Lottie progress represents: aod=0 to lockscreen=1
+                        view.progress = 1f - dozeAmount
+                    }
+                }
+
+                launch {
+                    viewModel.padding.collect { padding ->
+                        view.setPadding(padding, padding, padding, padding)
+                    }
+                }
+            }
+        }
+
+        // Add a callback that updates the color to `udfpsIconColor` whenever invalidate is called
+        view.addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER) {
+            PorterDuffColorFilter(udfpsIconColor, PorterDuff.Mode.SRC_ATOP)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
new file mode 100644
index 0000000..3bb01f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.ui.binder
+
+import android.view.View
+import com.android.systemui.R
+import com.android.systemui.keyguard.ui.binder.UdfpsAodFingerprintViewBinder
+import com.android.systemui.keyguard.ui.binder.UdfpsBackgroundViewBinder
+import com.android.systemui.keyguard.ui.binder.UdfpsFingerprintViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+object UdfpsKeyguardInternalViewBinder {
+
+    @JvmStatic
+    fun bind(
+        view: View,
+        viewModel: UdfpsKeyguardInternalViewModel,
+        aodViewModel: UdfpsAodViewModel,
+        fingerprintViewModel: FingerprintViewModel,
+        backgroundViewModel: BackgroundViewModel,
+    ) {
+        view.accessibilityDelegate = viewModel.accessibilityDelegate
+
+        // bind child views
+        UdfpsAodFingerprintViewBinder.bind(view.requireViewById(R.id.udfps_aod_fp), aodViewModel)
+        UdfpsFingerprintViewBinder.bind(
+            view.requireViewById(R.id.udfps_lockscreen_fp),
+            fingerprintViewModel
+        )
+        UdfpsBackgroundViewBinder.bind(
+            view.requireViewById(R.id.udfps_keyguard_fp_bg),
+            backgroundViewModel
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
new file mode 100644
index 0000000..667abae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.graphics.RectF
+import android.view.View
+import android.widget.FrameLayout
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.biometrics.UdfpsKeyguardView
+import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@ExperimentalCoroutinesApi
+object UdfpsKeyguardViewBinder {
+    /**
+     * Drives UI for the keyguard UDFPS view. Inflates child views on a background thread. For view
+     * binders for its child views, see [UdfpsFingerprintViewBinder], [UdfpsBackgroundViewBinder] &
+     * [UdfpsAodFingerprintViewBinder].
+     */
+    @JvmStatic
+    fun bind(
+        view: UdfpsKeyguardView,
+        viewModel: UdfpsKeyguardViewModel,
+        udfpsKeyguardInternalViewModel: UdfpsKeyguardInternalViewModel,
+        aodViewModel: UdfpsAodViewModel,
+        fingerprintViewModel: FingerprintViewModel,
+        backgroundViewModel: BackgroundViewModel,
+    ) {
+        view.useExpandedOverlay(viewModel.useExpandedOverlay())
+
+        val layoutInflaterFinishListener =
+            AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent ->
+                UdfpsKeyguardInternalViewBinder.bind(
+                    inflatedInternalView,
+                    udfpsKeyguardInternalViewModel,
+                    aodViewModel,
+                    fingerprintViewModel,
+                    backgroundViewModel,
+                )
+                if (viewModel.useExpandedOverlay()) {
+                    val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams
+                    lp.width = viewModel.sensorBounds.width()
+                    lp.height = viewModel.sensorBounds.height()
+                    val relativeToView =
+                        getBoundsRelativeToView(
+                            inflatedInternalView,
+                            RectF(viewModel.sensorBounds),
+                        )
+                    lp.setMarginsRelative(
+                        relativeToView.left.toInt(),
+                        relativeToView.top.toInt(),
+                        relativeToView.right.toInt(),
+                        relativeToView.bottom.toInt(),
+                    )
+                    parent!!.addView(inflatedInternalView, lp)
+                } else {
+                    parent!!.addView(inflatedInternalView)
+                }
+            }
+        val inflater = AsyncLayoutInflater(view.context)
+        inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener)
+
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                launch {
+                    combine(aodViewModel.isVisible, fingerprintViewModel.visible) {
+                            isAodVisible,
+                            isFingerprintVisible ->
+                            isAodVisible || isFingerprintVisible
+                        }
+                        .collect { view.setVisible(it) }
+                }
+            }
+        }
+    }
+
+    /**
+     * Converts coordinates of RectF relative to the screen to coordinates relative to this view.
+     *
+     * @param bounds RectF based off screen coordinates in current orientation
+     */
+    private fun getBoundsRelativeToView(view: View, bounds: RectF): RectF {
+        val pos: IntArray = view.locationOnScreen
+        return RectF(
+            bounds.left - pos[0],
+            bounds.top - pos[1],
+            bounds.right - pos[0],
+            bounds.bottom - pos[1]
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
new file mode 100644
index 0000000..fc0c78a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
+ * surface behind the keyguard.
+ */
+object WindowManagerLockscreenVisibilityViewBinder {
+    @JvmStatic
+    fun bind(
+        viewModel: WindowManagerLockscreenVisibilityViewModel,
+        lockscreenVisibilityManager: WindowManagerLockscreenVisibilityManager,
+        scope: CoroutineScope
+    ) {
+        scope.launch {
+            viewModel.surfaceBehindVisibility.collect {
+                lockscreenVisibilityManager.setSurfaceBehindVisibility(it)
+            }
+        }
+
+        scope.launch {
+            viewModel.lockscreenVisibility.collect {
+                lockscreenVisibilityManager.setLockscreenShown(it)
+            }
+        }
+
+        scope.launch {
+            viewModel.aodVisibility.collect { lockscreenVisibilityManager.setAodVisible(it) }
+        }
+
+        scope.launch {
+            viewModel.surfaceBehindAnimating.collect {
+                lockscreenVisibilityManager.setUsingGoingAwayRemoteAnimation(it)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index b92d104..580db35 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -42,17 +42,29 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.shared.clocks.DefaultClockController
 import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
+import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
 import dagger.assisted.Assisted
@@ -71,6 +83,7 @@
     private val clockViewModel: KeyguardPreviewClockViewModel,
     private val smartspaceViewModel: KeyguardPreviewSmartspaceViewModel,
     private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
+    private val quickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
     displayManager: DisplayManager,
     private val windowManager: WindowManager,
     private val clockController: ClockEventController,
@@ -78,7 +91,14 @@
     private val broadcastDispatcher: BroadcastDispatcher,
     private val lockscreenSmartspaceController: LockscreenSmartspaceController,
     private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
+    private val featureFlags: FeatureFlags,
+    private val falsingManager: FalsingManager,
+    private val vibratorHelper: VibratorHelper,
+    private val indicationController: KeyguardIndicationController,
+    private val keyguardRootViewModel: KeyguardRootViewModel,
     @Assisted bundle: Bundle,
+    private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
+    private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
 ) {
 
     val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
@@ -97,7 +117,7 @@
     private var host: SurfaceControlViewHost
 
     val surfacePackage: SurfaceControlViewHost.SurfacePackage
-        get() = host.surfacePackage
+        get() = checkNotNull(host.surfacePackage)
 
     private lateinit var largeClockHostView: FrameLayout
     private lateinit var smallClockHostView: FrameLayout
@@ -106,14 +126,26 @@
     private val disposables = mutableSetOf<DisposableHandle>()
     private var isDestroyed = false
 
+    private val shortcutsBindings = mutableSetOf<KeyguardQuickAffordanceViewBinder.Binding>()
+
     init {
-        bottomAreaViewModel.enablePreviewMode(
-            initiallySelectedSlotId =
-                bundle.getString(
-                    KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
-                ),
-            shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
-        )
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            keyguardRootViewModel.enablePreviewMode(
+                initiallySelectedSlotId =
+                    bundle.getString(
+                        KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
+                    ),
+                shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
+            )
+        } else {
+            bottomAreaViewModel.enablePreviewMode(
+                initiallySelectedSlotId =
+                    bundle.getString(
+                        KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
+                    ),
+                shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
+            )
+        }
         runBlocking(mainDispatcher) {
             host =
                 SurfaceControlViewHost(
@@ -130,7 +162,21 @@
         mainHandler.post {
             val rootView = FrameLayout(context)
 
-            setUpBottomArea(rootView)
+            if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+                val keyguardRootView = KeyguardRootView(context, null)
+                rootView.addView(
+                    keyguardRootView,
+                    FrameLayout.LayoutParams(
+                        FrameLayout.LayoutParams.MATCH_PARENT,
+                        FrameLayout.LayoutParams.MATCH_PARENT,
+                    ),
+                )
+                setupShortcuts(keyguardRootView)
+                KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel)
+                keyguardBlueprintInteractor.refreshBlueprint()
+            } else {
+                setUpBottomArea(rootView)
+            }
 
             setUpSmartspace(rootView)
             smartSpaceView?.let {
@@ -182,13 +228,20 @@
     }
 
     fun onSlotSelected(slotId: String) {
-        bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            quickAffordancesCombinedViewModel.onPreviewSlotSelected(slotId = slotId)
+        } else {
+            bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
+        }
     }
 
     fun destroy() {
         isDestroyed = true
         lockscreenSmartspaceController.disconnect()
         disposables.forEach { it.dispose() }
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            shortcutsBindings.forEach { it.destroy() }
+        }
     }
 
     /**
@@ -250,6 +303,7 @@
         smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
     private fun setUpBottomArea(parentView: ViewGroup) {
         val bottomAreaView =
             LayoutInflater.from(context)
@@ -270,6 +324,32 @@
         )
     }
 
+    private fun setupShortcuts(keyguardRootView: KeyguardRootView) {
+        shortcutsBindings.add(
+            KeyguardQuickAffordanceViewBinder.bind(
+                keyguardRootView.requireViewById(R.id.start_button),
+                quickAffordancesCombinedViewModel.startButton,
+                keyguardRootViewModel.alpha,
+                falsingManager,
+                vibratorHelper,
+            ) {
+                indicationController.showTransientIndication(it)
+            }
+        )
+
+        shortcutsBindings.add(
+            KeyguardQuickAffordanceViewBinder.bind(
+                keyguardRootView.requireViewById(R.id.end_button),
+                quickAffordancesCombinedViewModel.endButton,
+                keyguardRootViewModel.alpha,
+                falsingManager,
+                vibratorHelper,
+            ) {
+                indicationController.showTransientIndication(it)
+            }
+        )
+    }
+
     private fun setUpUdfps(parentView: ViewGroup) {
         val sensorBounds = udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt
new file mode 100644
index 0000000..890d565
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardIndicationArea.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view
+
+import android.content.Context
+import android.text.TextUtils
+import android.util.AttributeSet
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.LinearLayout
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.KeyguardIndicationTextView
+
+class KeyguardIndicationArea(
+    context: Context,
+    private val attrs: AttributeSet?,
+) :
+    LinearLayout(
+        context,
+        attrs,
+    ) {
+
+    init {
+        setId(R.id.keyguard_indication_area)
+        orientation = LinearLayout.VERTICAL
+
+        addView(indicationTopRow(), LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT))
+        addView(
+            indicationBottomRow(),
+            LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
+                gravity = Gravity.CENTER_HORIZONTAL
+            }
+        )
+    }
+
+    private fun indicationTopRow(): KeyguardIndicationTextView {
+        return KeyguardIndicationTextView(context, attrs).apply {
+            id = R.id.keyguard_indication_text
+            gravity = Gravity.CENTER
+            accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_POLITE
+            setTextAppearance(R.style.TextAppearance_Keyguard_BottomArea)
+
+            val padding = R.dimen.keyguard_indication_text_padding.dp()
+            setPaddingRelative(padding, 0, padding, 0)
+        }
+    }
+
+    private fun indicationBottomRow(): KeyguardIndicationTextView {
+        return KeyguardIndicationTextView(context, attrs).apply {
+            id = R.id.keyguard_indication_text_bottom
+            gravity = Gravity.CENTER
+            accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_POLITE
+
+            setTextAppearance(R.style.TextAppearance_Keyguard_BottomArea)
+            setEllipsize(TextUtils.TruncateAt.END)
+            setAlpha(0.8f)
+            setMinHeight(R.dimen.keyguard_indication_text_min_height.dp())
+            setMaxLines(2)
+            setVisibility(View.GONE)
+
+            val padding = R.dimen.keyguard_indication_text_padding.dp()
+            setPaddingRelative(padding, 0, padding, 0)
+        }
+    }
+
+    private fun Int.dp(): Int {
+        return context.resources.getDimensionPixelSize(this)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
new file mode 100644
index 0000000..a948741
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.ImageView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.res.ResourcesCompat
+import com.android.keyguard.KeyguardStatusView
+import com.android.keyguard.LockIconView
+import com.android.systemui.R
+import com.android.systemui.animation.view.LaunchableImageView
+
+/** Provides a container for all keyguard ui content. */
+class KeyguardRootView(
+    context: Context,
+    private val attrs: AttributeSet?,
+) :
+    ConstraintLayout(
+        context,
+        attrs,
+    ) {
+
+    private var statusView: KeyguardStatusView? = null
+
+    init {
+        addIndicationTextArea()
+        addLockIconView()
+        addAmbientIndicationArea()
+        addLeftShortcut()
+        addRightShortcut()
+        addSettingsPopupMenu()
+        addStatusView()
+    }
+
+    private fun addIndicationTextArea() {
+        val view = KeyguardIndicationArea(context, attrs)
+        addView(view)
+    }
+
+    private fun addLockIconView() {
+        val view = LockIconView(context, attrs).apply { id = R.id.lock_icon_view }
+        addView(view)
+    }
+
+    private fun addAmbientIndicationArea() {
+        LayoutInflater.from(context).inflate(R.layout.ambient_indication, this)
+    }
+
+    private fun addLeftShortcut() {
+        val padding = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_padding)
+        val view =
+            LaunchableImageView(context, attrs).apply {
+                id = R.id.start_button
+                scaleType = ImageView.ScaleType.FIT_CENTER
+                background =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_bg,
+                        context.theme
+                    )
+                foreground =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_selected_border,
+                        context.theme
+                    )
+                visibility = View.INVISIBLE
+                setPadding(padding, padding, padding, padding)
+            }
+        addView(view)
+    }
+
+    private fun addRightShortcut() {
+        val padding = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_padding)
+        val view =
+            LaunchableImageView(context, attrs).apply {
+                id = R.id.end_button
+                scaleType = ImageView.ScaleType.FIT_CENTER
+                background =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_bg,
+                        context.theme
+                    )
+                foreground =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_selected_border,
+                        context.theme
+                    )
+                visibility = View.INVISIBLE
+                setPadding(padding, padding, padding, padding)
+            }
+        addView(view)
+    }
+
+    private fun addSettingsPopupMenu() {
+        val view =
+            LayoutInflater.from(context)
+                .inflate(R.layout.keyguard_settings_popup_menu, this, false)
+                .apply {
+                    id = R.id.keyguard_settings_button
+                    visibility = GONE
+                }
+        addView(view)
+    }
+
+    fun addStatusView(): KeyguardStatusView {
+        // StatusView may need to be rebuilt on config changes. Remove and reinflate
+        statusView?.let { removeView(it) }
+        val view =
+            (LayoutInflater.from(context).inflate(R.layout.keyguard_status_view, this, false)
+                    as KeyguardStatusView)
+                .apply {
+                    setClipChildren(false)
+                    statusView = this
+                }
+
+        addView(view)
+        return view
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
new file mode 100644
index 0000000..36d21f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout
+
+import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** Uses $ adb shell cmd statusbar blueprint <BlueprintId> */
+class KeyguardBlueprintCommandListener
+@Inject
+constructor(
+    private val commandRegistry: CommandRegistry,
+    private val keyguardBlueprintRepository: KeyguardBlueprintRepository,
+    private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
+) {
+    private val layoutCommand = KeyguardLayoutManagerCommand()
+
+    fun start() {
+        commandRegistry.registerCommand(COMMAND) { layoutCommand }
+    }
+
+    internal inner class KeyguardLayoutManagerCommand : Command {
+        override fun execute(pw: PrintWriter, args: List<String>) {
+            val arg = args.getOrNull(0)
+            if (arg == null || arg.lowercase() == "help") {
+                help(pw)
+                return
+            }
+
+            if (keyguardBlueprintInteractor.transitionToBlueprint(arg)) {
+                pw.println("Transition succeeded!")
+            } else {
+                pw.println("Invalid argument! To see available blueprint ids, run:")
+                pw.println("$ adb shell cmd statusbar blueprint help")
+            }
+        }
+
+        override fun help(pw: PrintWriter) {
+            pw.println("Usage: $ adb shell cmd statusbar blueprint <blueprintId>")
+            pw.println("Existing Blueprint Ids: ")
+            keyguardBlueprintRepository.printBlueprints(pw)
+        }
+    }
+
+    companion object {
+        internal const val COMMAND = "blueprint"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
new file mode 100644
index 0000000..518df07
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.blueprints
+
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardBlueprint
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
+import javax.inject.Inject
+
+/**
+ * Positions elements of the lockscreen to the default position.
+ *
+ * This will be the most common use case for phones in portrait mode.
+ */
+@SysUISingleton
+@JvmSuppressWildcards
+class DefaultKeyguardBlueprint
+@Inject
+constructor(
+    private val defaultIndicationAreaSection: DefaultIndicationAreaSection,
+    private val defaultLockIconSection: DefaultLockIconSection,
+    private val defaultShortcutsSection: DefaultShortcutsSection,
+    private val defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
+    private val defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
+    private val defaultStatusViewSection: DefaultStatusViewSection,
+    private val splitShadeGuidelines: SplitShadeGuidelines,
+) : KeyguardBlueprint {
+    override val id: String = DEFAULT
+
+    override fun apply(constraintSet: ConstraintSet) {
+        defaultIndicationAreaSection.apply(constraintSet)
+        defaultLockIconSection.apply(constraintSet)
+        defaultShortcutsSection.apply(constraintSet)
+        defaultAmbientIndicationAreaSection.apply(constraintSet)
+        defaultSettingsPopupMenuSection.apply(constraintSet)
+        defaultStatusViewSection.apply(constraintSet)
+        splitShadeGuidelines.apply(constraintSet)
+    }
+
+    companion object {
+        const val DEFAULT = "default"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
new file mode 100644
index 0000000..fefe679
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.blueprints
+
+import com.android.systemui.keyguard.data.repository.KeyguardBlueprint
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+abstract class KeyguardBlueprintModule {
+    @Binds
+    @IntoSet
+    abstract fun bindDefaultBlueprint(
+        defaultLockscreenBlueprint: DefaultKeyguardBlueprint
+    ): KeyguardBlueprint
+
+    @Binds
+    @IntoSet
+    abstract fun bindShortcutsBesideUdfpsLockscreenBlueprint(
+        shortcutsBesideUdfpsLockscreenBlueprint: ShortcutsBesideUdfpsKeyguardBlueprint
+    ): KeyguardBlueprint
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
new file mode 100644
index 0000000..54c2796
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.blueprints
+
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardBlueprint
+import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
+import javax.inject.Inject
+
+/** Vertically aligns the shortcuts with the udfps. */
+@SysUISingleton
+class ShortcutsBesideUdfpsKeyguardBlueprint
+@Inject
+constructor(
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val defaultIndicationAreaSection: DefaultIndicationAreaSection,
+    private val defaultLockIconSection: DefaultLockIconSection,
+    private val defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
+    private val defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
+    private val alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
+    private val defaultShortcutsSection: DefaultShortcutsSection,
+    private val defaultStatusViewSection: DefaultStatusViewSection,
+    private val splitShadeGuidelines: SplitShadeGuidelines,
+) : KeyguardBlueprint {
+    override val id: String = SHORTCUTS_BESIDE_UDFPS
+
+    override fun apply(constraintSet: ConstraintSet) {
+        defaultIndicationAreaSection.apply(constraintSet)
+        defaultLockIconSection.apply(constraintSet)
+        defaultAmbientIndicationAreaSection.apply(constraintSet)
+        defaultSettingsPopupMenuSection.apply(constraintSet)
+        if (keyguardUpdateMonitor.isUdfpsSupported) {
+            alignShortcutsToUdfpsSection.apply(constraintSet)
+        } else {
+            defaultShortcutsSection.apply(constraintSet)
+        }
+        defaultStatusViewSection.apply(constraintSet)
+        splitShadeGuidelines.apply(constraintSet)
+    }
+
+    companion object {
+        const val SHORTCUTS_BESIDE_UDFPS = "shortcutsBesideUdfps"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
new file mode 100644
index 0000000..156b9f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.res.Resources
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.LEFT
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.RIGHT
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.data.repository.KeyguardSection
+import javax.inject.Inject
+
+class AlignShortcutsToUdfpsSection @Inject constructor(@Main private val resources: Resources) :
+    KeyguardSection {
+    override fun apply(constraintSet: ConstraintSet) {
+        val width = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
+        val height = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
+
+        constraintSet.apply {
+            constrainWidth(R.id.start_button, width)
+            constrainHeight(R.id.start_button, height)
+            connect(R.id.start_button, LEFT, PARENT_ID, LEFT)
+            connect(R.id.start_button, RIGHT, R.id.lock_icon_view, LEFT)
+            connect(R.id.start_button, TOP, R.id.lock_icon_view, TOP)
+            connect(R.id.start_button, BOTTOM, R.id.lock_icon_view, BOTTOM)
+
+            constrainWidth(R.id.end_button, width)
+            constrainHeight(R.id.end_button, height)
+            connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT)
+            connect(R.id.end_button, LEFT, R.id.lock_icon_view, RIGHT)
+            connect(R.id.end_button, TOP, R.id.lock_icon_view, TOP)
+            connect(R.id.end_button, BOTTOM, R.id.lock_icon_view, BOTTOM)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
new file mode 100644
index 0000000..abf25a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.keyguard.data.repository.KeyguardSection
+import javax.inject.Inject
+
+class DefaultAmbientIndicationAreaSection
+@Inject
+constructor(private val keyguardUpdateMonitor: KeyguardUpdateMonitor) : KeyguardSection {
+    override fun apply(constraintSet: ConstraintSet) {
+        constraintSet.apply {
+            constrainWidth(R.id.ambient_indication_container, MATCH_PARENT)
+
+            if (keyguardUpdateMonitor.isUdfpsSupported) {
+                // constrain below udfps and above indication area
+                constrainHeight(R.id.ambient_indication_container, MATCH_CONSTRAINT)
+                connect(R.id.ambient_indication_container, TOP, R.id.lock_icon_view, BOTTOM)
+                connect(
+                    R.id.ambient_indication_container,
+                    BOTTOM,
+                    R.id.keyguard_indication_area,
+                    TOP
+                )
+                connect(R.id.ambient_indication_container, START, PARENT_ID, START)
+                connect(R.id.ambient_indication_container, END, PARENT_ID, END)
+            } else {
+                // constrain above lock icon
+                constrainHeight(R.id.ambient_indication_container, WRAP_CONTENT)
+                connect(R.id.ambient_indication_container, BOTTOM, R.id.lock_icon_view, TOP)
+                connect(R.id.ambient_indication_container, START, PARENT_ID, START)
+                connect(R.id.ambient_indication_container, END, PARENT_ID, END)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
new file mode 100644
index 0000000..dee7ed5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.R
+import com.android.systemui.keyguard.data.repository.KeyguardSection
+import javax.inject.Inject
+
+class DefaultIndicationAreaSection @Inject constructor(private val context: Context) :
+    KeyguardSection {
+    private val indicationAreaViewId = R.id.keyguard_indication_area
+
+    override fun apply(constraintSet: ConstraintSet) {
+        constraintSet.apply {
+            constrainWidth(indicationAreaViewId, ViewGroup.LayoutParams.MATCH_PARENT)
+            constrainHeight(indicationAreaViewId, ViewGroup.LayoutParams.WRAP_CONTENT)
+            connect(
+                indicationAreaViewId,
+                ConstraintSet.BOTTOM,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.BOTTOM,
+                context.resources.getDimensionPixelSize(R.dimen.keyguard_indication_margin_bottom)
+            )
+            connect(
+                indicationAreaViewId,
+                ConstraintSet.START,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.START
+            )
+            connect(
+                indicationAreaViewId,
+                ConstraintSet.END,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.END
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
new file mode 100644
index 0000000..461faec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
+import android.util.DisplayMetrics
+import android.view.WindowManager
+import androidx.annotation.VisibleForTesting
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.keyguard.data.repository.KeyguardSection
+import javax.inject.Inject
+
+class DefaultLockIconSection
+@Inject
+constructor(
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val authController: AuthController,
+    private val windowManager: WindowManager,
+    private val context: Context,
+) : KeyguardSection {
+    private val lockIconViewId = R.id.lock_icon_view
+
+    override fun apply(constraintSet: ConstraintSet) {
+        val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
+        val scaleFactor: Float = authController.scaleFactor
+        val mBottomPaddingPx =
+            context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
+        val mDefaultPaddingPx = context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding)
+        val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt()
+        val bounds = windowManager.currentWindowMetrics.bounds
+        val widthPixels = bounds.right.toFloat()
+        val heightPixels = bounds.bottom.toFloat()
+        val defaultDensity =
+            DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+                DisplayMetrics.DENSITY_DEFAULT.toFloat()
+        val lockIconRadiusPx = (defaultDensity * 36).toInt()
+
+        if (isUdfpsSupported) {
+            authController.udfpsLocation?.let { udfpsLocation ->
+                centerLockIcon(
+                    udfpsLocation,
+                    authController.udfpsRadius,
+                    scaledPadding,
+                    constraintSet
+                )
+            }
+        } else {
+            centerLockIcon(
+                Point(
+                    (widthPixels / 2).toInt(),
+                    (heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
+                ),
+                lockIconRadiusPx * scaleFactor,
+                scaledPadding,
+                constraintSet,
+            )
+        }
+    }
+
+    @VisibleForTesting
+    internal fun centerLockIcon(
+        center: Point,
+        radius: Float,
+        drawablePadding: Int,
+        constraintSet: ConstraintSet
+    ) {
+        val sensorRect =
+            Rect().apply {
+                set(
+                    center.x - radius.toInt(),
+                    center.y - radius.toInt(),
+                    center.x + radius.toInt(),
+                    center.y + radius.toInt(),
+                )
+            }
+
+        constraintSet.apply {
+            constrainWidth(lockIconViewId, sensorRect.right - sensorRect.left)
+            constrainHeight(lockIconViewId, sensorRect.bottom - sensorRect.top)
+            connect(
+                lockIconViewId,
+                ConstraintSet.TOP,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.TOP,
+                sensorRect.top
+            )
+            connect(
+                lockIconViewId,
+                ConstraintSet.START,
+                ConstraintSet.PARENT_ID,
+                ConstraintSet.START,
+                sensorRect.left
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
new file mode 100644
index 0000000..ad1e4f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.res.Resources
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.data.repository.KeyguardSection
+import javax.inject.Inject
+
+class DefaultSettingsPopupMenuSection @Inject constructor(@Main private val resources: Resources) :
+    KeyguardSection {
+    override fun apply(constraintSet: ConstraintSet) {
+        val horizontalOffsetMargin =
+            resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
+
+        constraintSet.apply {
+            constrainWidth(R.id.keyguard_settings_button, WRAP_CONTENT)
+            constrainHeight(R.id.keyguard_settings_button, WRAP_CONTENT)
+            constrainMinHeight(
+                R.id.keyguard_settings_button,
+                resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
+            )
+            connect(R.id.keyguard_settings_button, START, PARENT_ID, START, horizontalOffsetMargin)
+            connect(R.id.keyguard_settings_button, END, PARENT_ID, END, horizontalOffsetMargin)
+            connect(
+                R.id.keyguard_settings_button,
+                BOTTOM,
+                PARENT_ID,
+                BOTTOM,
+                resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
new file mode 100644
index 0000000..db4653d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.res.Resources
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.LEFT
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.RIGHT
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.data.repository.KeyguardSection
+import javax.inject.Inject
+
+class DefaultShortcutsSection @Inject constructor(@Main private val resources: Resources) :
+    KeyguardSection {
+    override fun apply(constraintSet: ConstraintSet) {
+        val width = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
+        val height = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
+        val horizontalOffsetMargin =
+            resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
+        val verticalOffsetMargin =
+            resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
+
+        constraintSet.apply {
+            constrainWidth(R.id.start_button, width)
+            constrainHeight(R.id.start_button, height)
+            connect(R.id.start_button, LEFT, PARENT_ID, LEFT, horizontalOffsetMargin)
+            connect(R.id.start_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin)
+
+            constrainWidth(R.id.end_button, width)
+            constrainHeight(R.id.end_button, height)
+            connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT, horizontalOffsetMargin)
+            connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
new file mode 100644
index 0000000..3f319ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.R
+import com.android.systemui.keyguard.data.repository.KeyguardSection
+import javax.inject.Inject
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.END
+
+class DefaultStatusViewSection @Inject constructor(private val context: Context) :
+    KeyguardSection {
+    private val statusViewId = R.id.keyguard_status_view
+
+    override fun apply(constraintSet: ConstraintSet) {
+        constraintSet.apply {
+            constrainWidth(statusViewId, MATCH_CONSTRAINT)
+            constrainHeight(statusViewId, WRAP_CONTENT)
+            connect(statusViewId, TOP, PARENT_ID, TOP)
+            connect(statusViewId, START, PARENT_ID, START)
+            connect(statusViewId, END, PARENT_ID, END)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt
new file mode 100644
index 0000000..668b17f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.R
+import com.android.systemui.keyguard.data.repository.KeyguardSection
+import javax.inject.Inject
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.VERTICAL
+
+class SplitShadeGuidelines @Inject constructor(private val context: Context) :
+    KeyguardSection {
+
+    override fun apply(constraintSet: ConstraintSet) {
+        constraintSet.apply {
+            // For use on large screens, it will provide a guideline vertically in the center to
+            // enable items to be aligned on the left or right sides
+            create(R.id.split_shade_guideline, VERTICAL)
+            setGuidelinePercent(R.id.split_shade_guideline, 0.5f)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
new file mode 100644
index 0000000..113f01c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down GONE->DREAMING_LOCKSCREEN_HOSTED transition into discrete steps for corresponding
+ * views to consume.
+ */
+@SysUISingleton
+class GoneToDreamingLockscreenHostedTransitionViewModel
+@Inject
+constructor(
+    interactor: KeyguardTransitionInteractor,
+) {
+
+    private val transitionAnimation =
+        KeyguardTransitionAnimationFlow(
+            transitionDuration = TO_DREAMING_DURATION,
+            transitionFlow = interactor.goneToDreamingLockscreenHostedTransition,
+        )
+
+    /** Lockscreen views alpha - hide immediately */
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            duration = 1.milliseconds,
+            onStep = { 0f },
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt
new file mode 100644
index 0000000..dd3967a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+class KeyguardAmbientIndicationViewModel
+@Inject
+constructor(
+    private val keyguardInteractor: KeyguardInteractor,
+    private val burnInHelperWrapper: BurnInHelperWrapper,
+) {
+
+    /** An observable for the x-offset by which the indication area should be translated. */
+    val indicationAreaTranslationX: Flow<Float> =
+        keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
+
+    /** Returns an observable for the y-offset by which the indication area should be translated. */
+    fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
+        return keyguardInteractor.dozeAmount
+            .map { dozeAmount ->
+                dozeAmount *
+                    (burnInHelperWrapper.burnInOffset(
+                        /* amplitude = */ defaultBurnInOffset * 2,
+                        /* xAxis= */ false,
+                    ) - defaultBurnInOffset)
+            }
+            .distinctUntilChanged()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
new file mode 100644
index 0000000..5e9e553
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardBlueprintViewModel
+@Inject
+constructor(keyguardBlueprintInteractor: KeyguardBlueprintInteractor) {
+    val blueprint = keyguardBlueprintInteractor.blueprint
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 62a1a9e..3e6f8e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -57,7 +57,7 @@
      * in the wallpaper picker application. This should _always_ be `false` for the real lock screen
      * experience.
      */
-    private val previewMode = MutableStateFlow(PreviewMode())
+    val previewMode = MutableStateFlow(PreviewMode())
 
     /**
      * ID of the slot that's currently selected in the preview that renders exclusively in the
@@ -101,12 +101,6 @@
                 bottomAreaInteractor.alpha.distinctUntilChanged()
             }
         }
-    /** An observable for whether the indication area should be padded. */
-    val isIndicationAreaPadded: Flow<Boolean> =
-        combine(startButton, endButton) { startButtonModel, endButtonModel ->
-                startButtonModel.isVisible || endButtonModel.isVisible
-            }
-            .distinctUntilChanged()
     /** An observable for the x-offset by which the indication area should be translated. */
     val indicationAreaTranslationX: Flow<Float> =
         bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
deleted file mode 100644
index 9602888..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ /dev/null
@@ -1,112 +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.keyguard.ui.viewmodel
-
-import android.view.View
-import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.BouncerViewDelegate
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-
-/** Models UI state for the lock screen bouncer; handles user input. */
-class KeyguardBouncerViewModel
-@Inject
-constructor(
-    private val view: BouncerView,
-    private val interactor: PrimaryBouncerInteractor,
-) {
-    /** Observe on bouncer expansion amount. */
-    val bouncerExpansionAmount: Flow<Float> = interactor.panelExpansionAmount
-
-    /** Can the user interact with the view? */
-    val isInteractable: Flow<Boolean> = interactor.isInteractable
-
-    /** Observe whether bouncer is showing or not. */
-    val isShowing: Flow<Boolean> = interactor.isShowing
-
-    /** Observe whether bouncer is starting to hide. */
-    val startingToHide: Flow<Unit> = interactor.startingToHide
-
-    /** Observe whether we want to start the disappear animation. */
-    val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation
-
-    /** Observe whether we want to update keyguard position. */
-    val keyguardPosition: Flow<Float> = interactor.keyguardPosition
-
-    /** Observe whether we want to update resources. */
-    val updateResources: Flow<Boolean> = interactor.resourceUpdateRequests
-
-    /** Observe whether we want to set a keyguard message when the bouncer shows. */
-    val bouncerShowMessage: Flow<BouncerShowMessageModel> = interactor.showMessage
-
-    /** Observe whether keyguard is authenticated already. */
-    val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticated
-
-    /** Observe whether the side fps is showing. */
-    val sideFpsShowing: Flow<Boolean> = interactor.sideFpsShowing
-
-    /** Observe whether we should update fps is showing. */
-    val shouldUpdateSideFps: Flow<Unit> =
-        merge(
-            interactor.isShowing.map {},
-            interactor.startingToHide,
-            interactor.startingDisappearAnimation.filterNotNull().map {}
-        )
-
-    /** Observe whether we want to update resources. */
-    fun notifyUpdateResources() {
-        interactor.notifyUpdatedResources()
-    }
-
-    /** Notify that keyguard authenticated was handled */
-    fun notifyKeyguardAuthenticated() {
-        interactor.notifyKeyguardAuthenticatedHandled()
-    }
-
-    /** Notifies that the message was shown. */
-    fun onMessageShown() {
-        interactor.onMessageShown()
-    }
-
-    fun updateSideFpsVisibility() {
-        interactor.updateSideFpsVisibility()
-    }
-
-    /** Observe whether back button is enabled. */
-    fun observeOnIsBackButtonEnabled(systemUiVisibility: () -> Int): Flow<Int> {
-        return interactor.isBackButtonEnabled.map { enabled ->
-            var vis: Int = systemUiVisibility()
-            vis =
-                if (enabled) {
-                    vis and View.STATUS_BAR_DISABLE_BACK.inv()
-                } else {
-                    vis or View.STATUS_BAR_DISABLE_BACK
-                }
-            vis
-        }
-    }
-
-    /** Set an abstraction that will hold reference to the ui delegate for the bouncer view. */
-    fun setBouncerViewDelegate(delegate: BouncerViewDelegate?) {
-        view.delegate = delegate
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
new file mode 100644
index 0000000..980cc1b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** View-model for the keyguard indication area view */
+class KeyguardIndicationAreaViewModel
+@Inject
+constructor(
+    private val keyguardInteractor: KeyguardInteractor,
+    bottomAreaInteractor: KeyguardBottomAreaInteractor,
+    keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
+    private val burnInHelperWrapper: BurnInHelperWrapper,
+    private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
+    private val featureFlags: FeatureFlags,
+) {
+
+    /** Notifies when a new configuration is set */
+    val configurationChange: Flow<Unit> = keyguardInteractor.configurationChange
+
+    /** An observable for the alpha level for the entire bottom area. */
+    val alpha: Flow<Float> = keyguardBottomAreaViewModel.alpha
+
+    /** An observable for whether the indication area should be padded. */
+    val isIndicationAreaPadded: Flow<Boolean> =
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            combine(shortcutsCombinedViewModel.startButton, shortcutsCombinedViewModel.endButton) {
+                startButtonModel,
+                endButtonModel ->
+                startButtonModel.isVisible || endButtonModel.isVisible
+            }
+                .distinctUntilChanged()
+        } else {
+            combine(keyguardBottomAreaViewModel.startButton, keyguardBottomAreaViewModel.endButton) {
+                startButtonModel,
+                endButtonModel ->
+                startButtonModel.isVisible || endButtonModel.isVisible
+            }
+                .distinctUntilChanged()
+        }
+    /** An observable for the x-offset by which the indication area should be translated. */
+    val indicationAreaTranslationX: Flow<Float> =
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
+        } else {
+            bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
+        }
+
+    /** Returns an observable for the y-offset by which the indication area should be translated. */
+    fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
+        return keyguardInteractor.dozeAmount
+            .map { dozeAmount ->
+                dozeAmount *
+                    (burnInHelperWrapper.burnInOffset(
+                        /* amplitude = */ defaultBurnInOffset * 2,
+                        /* xAxis= */ false,
+                    ) - defaultBurnInOffset)
+            }
+            .distinctUntilChanged()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
new file mode 100644
index 0000000..56a98455
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class KeyguardQuickAffordancesCombinedViewModel
+@Inject
+constructor(
+    private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
+    private val keyguardInteractor: KeyguardInteractor,
+) {
+
+    /**
+     * ID of the slot that's currently selected in the preview that renders exclusively in the
+     * wallpaper picker application. This is ignored for the actual, real lock screen experience.
+     */
+    private val selectedPreviewSlotId =
+        MutableStateFlow(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
+
+    /**
+     * Whether quick affordances are "opaque enough" to be considered visible to and interactive by
+     * the user. If they are not interactive, user input should not be allowed on them.
+     *
+     * Note that there is a margin of error, where we allow very, very slightly transparent views to
+     * be considered "fully opaque" for the purpose of being interactive. This is to accommodate the
+     * error margin of floating point arithmetic.
+     *
+     * A view that is visible but with an alpha of less than our threshold either means it's not
+     * fully done fading in or is fading/faded out. Either way, it should not be
+     * interactive/clickable unless "fully opaque" to avoid issues like in b/241830987.
+     */
+    private val areQuickAffordancesFullyOpaque: Flow<Boolean> =
+        keyguardInteractor.keyguardAlpha
+            .map { alpha -> alpha >= AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD }
+            .distinctUntilChanged()
+
+    /** An observable for the view-model of the "start button" quick affordance. */
+    val startButton: Flow<KeyguardQuickAffordanceViewModel> =
+        button(KeyguardQuickAffordancePosition.BOTTOM_START)
+
+    /** An observable for the view-model of the "end button" quick affordance. */
+    val endButton: Flow<KeyguardQuickAffordanceViewModel> =
+        button(KeyguardQuickAffordancePosition.BOTTOM_END)
+
+    /**
+     * Notifies that a slot with the given ID has been selected in the preview experience that is
+     * rendering in the wallpaper picker. This is ignored for the real lock screen experience.
+     *
+     * @see [KeyguardRootViewModel.enablePreviewMode]
+     */
+    fun onPreviewSlotSelected(slotId: String) {
+        selectedPreviewSlotId.value = slotId
+    }
+
+    private fun button(
+        position: KeyguardQuickAffordancePosition
+    ): Flow<KeyguardQuickAffordanceViewModel> {
+        return keyguardInteractor.previewMode.flatMapLatest { previewMode ->
+            combine(
+                if (previewMode.isInPreviewMode) {
+                    quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position)
+                } else {
+                    quickAffordanceInteractor.quickAffordance(position = position)
+                },
+                keyguardInteractor.animateDozingTransitions.distinctUntilChanged(),
+                areQuickAffordancesFullyOpaque,
+                selectedPreviewSlotId,
+                quickAffordanceInteractor.useLongPress(),
+            ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId, useLongPress ->
+                val slotId = position.toSlotId()
+                val isSelected = selectedPreviewSlotId == slotId
+                model.toViewModel(
+                    animateReveal = !previewMode.isInPreviewMode && animateReveal,
+                    isClickable = isFullyOpaque && !previewMode.isInPreviewMode,
+                    isSelected =
+                    previewMode.isInPreviewMode &&
+                        previewMode.shouldHighlightSelectedAffordance &&
+                        isSelected,
+                    isDimmed =
+                    previewMode.isInPreviewMode &&
+                        previewMode.shouldHighlightSelectedAffordance &&
+                        !isSelected,
+                    forceInactive = previewMode.isInPreviewMode,
+                    slotId = slotId,
+                    useLongPress = useLongPress,
+                )
+            }
+                .distinctUntilChanged()
+        }
+    }
+
+    private fun KeyguardQuickAffordanceModel.toViewModel(
+        animateReveal: Boolean,
+        isClickable: Boolean,
+        isSelected: Boolean,
+        isDimmed: Boolean,
+        forceInactive: Boolean,
+        slotId: String,
+        useLongPress: Boolean,
+    ): KeyguardQuickAffordanceViewModel {
+        return when (this) {
+            is KeyguardQuickAffordanceModel.Visible ->
+                KeyguardQuickAffordanceViewModel(
+                    configKey = configKey,
+                    isVisible = true,
+                    animateReveal = animateReveal,
+                    icon = icon,
+                    onClicked = { parameters ->
+                        quickAffordanceInteractor.onQuickAffordanceTriggered(
+                            configKey = parameters.configKey,
+                            expandable = parameters.expandable,
+                            slotId = parameters.slotId,
+                        )
+                    },
+                    isClickable = isClickable,
+                    isActivated = !forceInactive && activationState is ActivationState.Active,
+                    isSelected = isSelected,
+                    useLongPress = useLongPress,
+                    isDimmed = isDimmed,
+                    slotId = slotId,
+                )
+            is KeyguardQuickAffordanceModel.Hidden ->
+                KeyguardQuickAffordanceViewModel(
+                    slotId = slotId,
+                )
+        }
+    }
+
+    companion object {
+        // We select a value that's less than 1.0 because we want floating point math precision to
+        // not be a factor in determining whether the affordance UI is fully opaque. The number we
+        // choose needs to be close enough 1.0 such that the user can't easily tell the difference
+        // between the UI with an alpha at the threshold and when the alpha is 1.0. At the same
+        // time, we don't want the number to be too close to 1.0 such that there is a chance that we
+        // never treat the affordance UI as "fully opaque" as that would risk making it forever not
+        // clickable.
+        @VisibleForTesting
+        const val AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD = 0.95f
+    }
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
new file mode 100644
index 0000000..316ca77
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import javax.inject.Inject
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class KeyguardRootViewModel
+@Inject
+constructor(
+    private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardQuickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
+)
+{
+    /** Represents the current state of the KeyguardRootView visibility */
+    val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
+        keyguardInteractor.keyguardRootViewVisibilityState
+
+    /** An observable for the alpha level for the entire keyguard root view. */
+    val alpha: Flow<Float> =
+        keyguardInteractor.previewMode.flatMapLatest {
+            if (it.isInPreviewMode) {
+                flowOf(1f)
+            } else {
+                keyguardInteractor.keyguardAlpha.distinctUntilChanged()
+            }
+        }
+
+    /**
+     * Puts this view-model in "preview mode", which means it's being used for UI that is rendering
+     * the lock screen preview in wallpaper picker / settings and not the real experience on the
+     * lock screen.
+     *
+     * @param initiallySelectedSlotId The ID of the initial slot to render as the selected one.
+     * @param shouldHighlightSelectedAffordance Whether the selected quick affordance should be
+     *   highlighted (while all others are dimmed to make the selected one stand out).
+     */
+    fun enablePreviewMode(
+        initiallySelectedSlotId: String?,
+        shouldHighlightSelectedAffordance: Boolean,
+    ) {
+        keyguardInteractor.previewMode.value =
+            KeyguardInteractor.PreviewMode(
+                isInPreviewMode = true,
+                shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
+            )
+        keyguardQuickAffordancesCombinedViewModel.onPreviewSlotSelected(
+            initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+        )
+    }
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt
new file mode 100644
index 0000000..4f52962
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardSurfaceBehindViewModel
+@Inject
+constructor(interactor: KeyguardSurfaceBehindInteractor) {
+    val surfaceBehindViewParams = interactor.viewParams
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
index a46d441..82f40bf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModel.kt
@@ -19,12 +19,14 @@
 import com.android.systemui.keyguard.domain.interactor.LightRevealScrimInteractor
 import com.android.systemui.statusbar.LightRevealEffect
 import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 
 /**
  * Models UI state for the light reveal scrim, which is used during screen on and off animations to
  * draw a gradient that reveals/hides the contents of the screen.
  */
+@OptIn(ExperimentalCoroutinesApi::class)
 class LightRevealScrimViewModel @Inject constructor(interactor: LightRevealScrimInteractor) {
     val lightRevealEffect: Flow<LightRevealEffect> = interactor.lightRevealEffect
     val revealAmount: Flow<Float> = interactor.revealAmount
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index f212a55..6d3b7f1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -17,92 +17,79 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.R
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 /** Models UI state and handles user input for the lockscreen scene. */
+@SysUISingleton
 class LockscreenSceneViewModel
-@AssistedInject
+@Inject
 constructor(
     @Application applicationScope: CoroutineScope,
-    interactorFactory: LockscreenSceneInteractor.Factory,
-    @Assisted containerName: String,
+    authenticationInteractor: AuthenticationInteractor,
+    private val bouncerInteractor: BouncerInteractor,
 ) {
-    private val interactor: LockscreenSceneInteractor = interactorFactory.create(containerName)
-
     /** The icon for the "lock" button on the lockscreen. */
     val lockButtonIcon: StateFlow<Icon> =
-        interactor.isDeviceLocked
-            .map { isLocked -> lockIcon(isLocked = isLocked) }
+        authenticationInteractor.isUnlocked
+            .map { isUnlocked -> lockIcon(isUnlocked = isUnlocked) }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = lockIcon(isLocked = interactor.isDeviceLocked.value),
+                initialValue = lockIcon(isUnlocked = authenticationInteractor.isUnlocked.value),
             )
 
     /** The key of the scene we should switch to when swiping up. */
-    val upDestinationSceneKey: StateFlow<SceneKey> =
-        interactor.isSwipeToDismissEnabled
-            .map { isSwipeToUnlockEnabled -> upDestinationSceneKey(isSwipeToUnlockEnabled) }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = upDestinationSceneKey(interactor.isSwipeToDismissEnabled.value),
-            )
+    val upDestinationSceneKey: Flow<SceneKey> =
+        authenticationInteractor.isUnlocked.map { isUnlocked ->
+            if (isUnlocked) {
+                SceneKey.Gone
+            } else {
+                SceneKey.Bouncer
+            }
+        }
 
     /** Notifies that the lock button on the lock screen was clicked. */
     fun onLockButtonClicked() {
-        interactor.dismissLockscreen()
+        bouncerInteractor.showOrUnlockDevice()
     }
 
     /** Notifies that some content on the lock screen was clicked. */
     fun onContentClicked() {
-        interactor.dismissLockscreen()
+        bouncerInteractor.showOrUnlockDevice()
     }
 
     private fun upDestinationSceneKey(
-        isSwipeToUnlockEnabled: Boolean,
+        canSwipeToDismiss: Boolean,
     ): SceneKey {
-        return if (isSwipeToUnlockEnabled) SceneKey.Gone else SceneKey.Bouncer
+        return if (canSwipeToDismiss) SceneKey.Gone else SceneKey.Bouncer
     }
 
     private fun lockIcon(
-        isLocked: Boolean,
+        isUnlocked: Boolean,
     ): Icon {
-        return Icon.Resource(
-            res =
-                if (isLocked) {
-                    R.drawable.ic_device_lock_on
-                } else {
-                    R.drawable.ic_device_lock_off
-                },
-            contentDescription =
-                ContentDescription.Resource(
-                    res =
-                        if (isLocked) {
-                            R.string.accessibility_lock_icon
-                        } else {
-                            R.string.accessibility_unlock_button
-                        }
-                )
-        )
-    }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(
-            containerName: String,
-        ): LockscreenSceneViewModel
+        return if (isUnlocked) {
+            Icon.Resource(
+                R.drawable.ic_device_lock_off,
+                ContentDescription.Resource(R.string.accessibility_unlock_button)
+            )
+        } else {
+            Icon.Resource(
+                R.drawable.ic_device_lock_on,
+                ContentDescription.Resource(R.string.accessibility_lock_icon)
+            )
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludingAppDeviceEntryMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludingAppDeviceEntryMessageViewModel.kt
new file mode 100644
index 0000000..3a162d7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludingAppDeviceEntryMessageViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.BiometricMessage
+import com.android.systemui.keyguard.domain.interactor.OccludingAppDeviceEntryInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/** Shows authentication messages over occcluding apps over the lockscreen. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class OccludingAppDeviceEntryMessageViewModel
+@Inject
+constructor(
+    interactor: OccludingAppDeviceEntryInteractor,
+) {
+    val message: Flow<BiometricMessage?> = interactor.message
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 68810f9..cca96b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -66,6 +66,23 @@
             },
         )
 
+    /** Lockscreen alpha */
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            duration = 50.milliseconds,
+            onStart = {
+                leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+                willRunDismissFromKeyguard = primaryBouncerInteractor.willRunDismissFromKeyguard()
+            },
+            onStep = {
+                if (willRunDismissFromKeyguard || leaveShadeOpen) {
+                    1f
+                } else {
+                    0f
+                }
+            },
+        )
+
     /** Scrim alpha values */
     val scrimAlpha: Flow<ScrimAlpha> =
         transitionAnimation
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
new file mode 100644
index 0000000..667c2f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.keyguard.domain.interactor.BurnInOffsets
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** View-model for UDFPS AOD view. */
+@ExperimentalCoroutinesApi
+class UdfpsAodViewModel
+@Inject
+constructor(
+    val interactor: UdfpsKeyguardInteractor,
+    val context: Context,
+) {
+    val alpha: Flow<Float> = interactor.dozeAmount
+    val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets
+    val isVisible: Flow<Boolean> = alpha.map { it != 0f }
+
+    // Padding between the fingerprint icon and its bounding box in pixels.
+    val padding: Flow<Int> =
+        interactor.scaleForResolution.map { scale ->
+            (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+                .roundToInt()
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt
new file mode 100644
index 0000000..d894a11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.biometrics.UdfpsKeyguardAccessibilityDelegate
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+class UdfpsKeyguardInternalViewModel
+@Inject
+constructor(val accessibilityDelegate: UdfpsKeyguardAccessibilityDelegate)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
new file mode 100644
index 0000000..929f27f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.graphics.Rect
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+class UdfpsKeyguardViewModel
+@Inject
+constructor(
+    private val featureFlags: FeatureFlags,
+) {
+    var sensorBounds: Rect = Rect()
+
+    fun useExpandedOverlay(): Boolean {
+        return featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
new file mode 100644
index 0000000..098b481
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt
@@ -0,0 +1,36 @@
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.graphics.Rect
+import com.android.systemui.biometrics.UdfpsKeyguardView
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.binder.UdfpsKeyguardViewBinder
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class UdfpsKeyguardViewModels
+@Inject
+constructor(
+    private val viewModel: UdfpsKeyguardViewModel,
+    private val internalViewModel: UdfpsKeyguardInternalViewModel,
+    private val aodViewModel: UdfpsAodViewModel,
+    private val lockscreenFingerprintViewModel: FingerprintViewModel,
+    private val lockscreenBackgroundViewModel: BackgroundViewModel,
+) {
+
+    fun setSensorBounds(sensorBounds: Rect) {
+        viewModel.sensorBounds = sensorBounds
+    }
+
+    fun bindViews(view: UdfpsKeyguardView) {
+        UdfpsKeyguardViewBinder.bind(
+            view,
+            viewModel,
+            internalViewModel,
+            aodViewModel,
+            lockscreenFingerprintViewModel,
+            lockscreenBackgroundViewModel
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
new file mode 100644
index 0000000..dd58607
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.Context
+import androidx.annotation.ColorInt
+import com.android.settingslib.Utils.getColorAttrDefaultColor
+import com.android.systemui.R
+import com.android.systemui.keyguard.domain.interactor.BurnInOffsets
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.wm.shell.animation.Interpolators
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** View-model for UDFPS lockscreen views. */
+@ExperimentalCoroutinesApi
+open class UdfpsLockscreenViewModel(
+    context: Context,
+    lockscreenColorResId: Int,
+    alternateBouncerColorResId: Int,
+    transitionInteractor: KeyguardTransitionInteractor,
+    udfpsKeyguardInteractor: UdfpsKeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
+) {
+    private val toLockscreen: Flow<TransitionViewModel> =
+        transitionInteractor.anyStateToLockscreenTransition.map {
+            TransitionViewModel(
+                alpha =
+                    if (it.from == KeyguardState.AOD) {
+                        it.value // animate
+                    } else {
+                        1f
+                    },
+                scale = 1f,
+                color = getColorAttrDefaultColor(context, lockscreenColorResId),
+            )
+        }
+
+    private val toAlternateBouncer: Flow<TransitionViewModel> =
+        keyguardInteractor.statusBarState.flatMapLatest { statusBarState ->
+            transitionInteractor.anyStateToAlternateBouncerTransition.map {
+                TransitionViewModel(
+                    alpha = 1f,
+                    scale =
+                        if (visibleInKeyguardState(it.from, statusBarState)) {
+                            1f
+                        } else {
+                            Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value)
+                        },
+                    color = getColorAttrDefaultColor(context, alternateBouncerColorResId),
+                )
+            }
+        }
+
+    private val fadeOut: Flow<TransitionViewModel> =
+        keyguardInteractor.statusBarState.flatMapLatest { statusBarState ->
+            merge(
+                    transitionInteractor.anyStateToGoneTransition,
+                    transitionInteractor.anyStateToAodTransition,
+                    transitionInteractor.anyStateToOccludedTransition,
+                    transitionInteractor.anyStateToPrimaryBouncerTransition,
+                    transitionInteractor.anyStateToDreamingTransition,
+                )
+                .map {
+                    TransitionViewModel(
+                        alpha =
+                            if (visibleInKeyguardState(it.from, statusBarState)) {
+                                1f - it.value
+                            } else {
+                                0f
+                            },
+                        scale = 1f,
+                        color =
+                            if (it.from == KeyguardState.ALTERNATE_BOUNCER) {
+                                getColorAttrDefaultColor(context, alternateBouncerColorResId)
+                            } else {
+                                getColorAttrDefaultColor(context, lockscreenColorResId)
+                            },
+                    )
+                }
+        }
+
+    private fun visibleInKeyguardState(
+        state: KeyguardState,
+        statusBarState: StatusBarState
+    ): Boolean {
+        return when (state) {
+            KeyguardState.OFF,
+            KeyguardState.DOZING,
+            KeyguardState.DREAMING,
+            KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+            KeyguardState.AOD,
+            KeyguardState.PRIMARY_BOUNCER,
+            KeyguardState.GONE,
+            KeyguardState.OCCLUDED -> false
+            KeyguardState.LOCKSCREEN -> statusBarState == StatusBarState.KEYGUARD
+            KeyguardState.ALTERNATE_BOUNCER -> true
+        }
+    }
+
+    private val keyguardStateTransition =
+        merge(
+            toAlternateBouncer,
+            toLockscreen,
+            fadeOut,
+        )
+
+    private val dialogHideAffordancesAlphaMultiplier: Flow<Float> =
+        udfpsKeyguardInteractor.dialogHideAffordancesRequest.map { hideAffordances ->
+            if (hideAffordances) {
+                0f
+            } else {
+                1f
+            }
+        }
+
+    private val alphaMultiplier: Flow<Float> =
+        combine(
+            transitionInteractor.startedKeyguardState,
+            dialogHideAffordancesAlphaMultiplier,
+            udfpsKeyguardInteractor.shadeExpansion,
+            udfpsKeyguardInteractor.qsProgress,
+        ) { startedKeyguardState, dialogHideAffordancesAlphaMultiplier, shadeExpansion, qsProgress
+            ->
+            if (startedKeyguardState == KeyguardState.ALTERNATE_BOUNCER) {
+                1f
+            } else {
+                dialogHideAffordancesAlphaMultiplier * (1f - shadeExpansion) * (1f - qsProgress)
+            }
+        }
+
+    val transition: Flow<TransitionViewModel> =
+        combine(
+            alphaMultiplier,
+            keyguardStateTransition,
+        ) { alphaMultiplier, keyguardStateTransition ->
+            TransitionViewModel(
+                alpha = keyguardStateTransition.alpha * alphaMultiplier,
+                scale = keyguardStateTransition.scale,
+                color = keyguardStateTransition.color,
+            )
+        }
+    val visible: Flow<Boolean> = transition.map { it.alpha != 0f }
+}
+
+@ExperimentalCoroutinesApi
+class FingerprintViewModel
+@Inject
+constructor(
+    val context: Context,
+    transitionInteractor: KeyguardTransitionInteractor,
+    interactor: UdfpsKeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
+) :
+    UdfpsLockscreenViewModel(
+        context,
+        android.R.attr.textColorPrimary,
+        com.android.internal.R.attr.materialColorOnPrimaryFixed,
+        transitionInteractor,
+        interactor,
+        keyguardInteractor,
+    ) {
+    val dozeAmount: Flow<Float> = interactor.dozeAmount
+    val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets
+
+    // Padding between the fingerprint icon and its bounding box in pixels.
+    val padding: Flow<Int> =
+        interactor.scaleForResolution.map { scale ->
+            (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+                .roundToInt()
+        }
+}
+
+@ExperimentalCoroutinesApi
+class BackgroundViewModel
+@Inject
+constructor(
+    val context: Context,
+    transitionInteractor: KeyguardTransitionInteractor,
+    interactor: UdfpsKeyguardInteractor,
+    keyguardInteractor: KeyguardInteractor,
+) :
+    UdfpsLockscreenViewModel(
+        context,
+        com.android.internal.R.attr.colorSurface,
+        com.android.internal.R.attr.materialColorPrimaryFixed,
+        transitionInteractor,
+        interactor,
+        keyguardInteractor,
+    )
+
+data class TransitionViewModel(
+    val alpha: Float,
+    val scale: Float,
+    @ColorInt val color: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt
new file mode 100644
index 0000000..f797640
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowManagerLockscreenVisibilityViewModel
+@Inject
+constructor(interactor: WindowManagerLockscreenVisibilityInteractor) {
+    val surfaceBehindVisibility = interactor.surfaceBehindVisibility
+    val surfaceBehindAnimating = interactor.usingKeyguardGoingAwayAnimation
+    val lockscreenVisibility = interactor.lockscreenVisibility
+    val aodVisibility = interactor.aodVisibility
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/util/IndicationHelper.kt b/packages/SystemUI/src/com/android/systemui/keyguard/util/IndicationHelper.kt
new file mode 100644
index 0000000..7129001
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/util/IndicationHelper.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.util
+
+import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.biometrics.BiometricFingerprintConstants
+import android.hardware.biometrics.BiometricSourceType
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class IndicationHelper
+@Inject
+constructor(
+    val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) {
+    fun shouldSuppressErrorMsg(biometricSource: BiometricSourceType, msgId: Int): Boolean {
+        return when (biometricSource) {
+            BiometricSourceType.FINGERPRINT ->
+                (isPrimaryAuthRequired() && !isFingerprintLockoutErrorMsg(msgId)) ||
+                    msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED ||
+                    msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED ||
+                    msgId == BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED
+            BiometricSourceType.FACE ->
+                (isPrimaryAuthRequired() && !isFaceLockoutErrorMsg(msgId)) ||
+                    msgId == BiometricFaceConstants.FACE_ERROR_CANCELED ||
+                    msgId == BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS
+            else -> false
+        }
+    }
+
+    private fun isFingerprintLockoutErrorMsg(msgId: Int): Boolean {
+        return msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT ||
+            msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT
+    }
+
+    fun isFaceLockoutErrorMsg(msgId: Int): Boolean {
+        return msgId == BiometricFaceConstants.FACE_ERROR_LOCKOUT ||
+            msgId == BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT
+    }
+
+    private fun isPrimaryAuthRequired(): Boolean {
+        // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+        // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to
+        // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+        // check of whether non-strong biometric is allowed since strong biometrics can still be
+        // used.
+        return !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index 34a6740..5f7991e 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -70,7 +70,7 @@
     var lifecycleOwner: ViewLifecycleOwner? = null
     val onAttachListener =
         object : View.OnAttachStateChangeListener {
-            override fun onViewAttachedToWindow(v: View?) {
+            override fun onViewAttachedToWindow(v: View) {
                 Assert.isMainThread()
                 lifecycleOwner?.onDestroy()
                 lifecycleOwner =
@@ -81,7 +81,7 @@
                     )
             }
 
-            override fun onViewDetachedFromWindow(v: View?) {
+            override fun onViewDetachedFromWindow(v: View) {
                 lifecycleOwner?.onDestroy()
                 lifecycleOwner = null
             }
@@ -167,9 +167,10 @@
         registry.currentState = Lifecycle.State.DESTROYED
     }
 
-    override fun getLifecycle(): Lifecycle {
-        return registry
-    }
+    override val lifecycle: Lifecycle
+        get() {
+            return registry
+        }
 
     private fun updateState() {
         registry.currentState =
diff --git a/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt b/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt
index 3be4499..d4b799f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.log
 
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.BouncerLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index fefa1b2..66067b1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -4,10 +4,11 @@
 import android.hardware.face.FaceSensorPropertiesInternal
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.FaceAuthLog
+import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
 private const val TAG = "DeviceEntryFaceAuthRepositoryLog"
@@ -240,7 +241,7 @@
         )
     }
 
-    fun hardwareError(errorStatus: ErrorAuthenticationStatus) {
+    fun hardwareError(errorStatus: ErrorFaceAuthenticationStatus) {
         logBuffer.log(
             TAG,
             DEBUG,
@@ -264,4 +265,8 @@
     fun watchdogScheduled() {
         logBuffer.log(TAG, DEBUG, "FaceManager Biometric watchdog scheduled.")
     }
+
+    fun faceLockedOut(@CompileTimeConstant reason: String) {
+        logBuffer.log(TAG, DEBUG, "Face auth has been locked out: $reason")
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
index f727784..702a23e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
@@ -21,9 +21,9 @@
 import android.graphics.RectF
 import androidx.core.graphics.toRectF
 import com.android.systemui.dagger.SysUISingleton
-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.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.log.dagger.ScreenDecorationsLog
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
@@ -164,4 +164,49 @@
     fun cameraProtectionEvent(@CompileTimeConstant cameraProtectionEvent: String) {
         logBuffer.log(TAG, DEBUG, cameraProtectionEvent)
     }
+
+    fun logRotationChangeDeferred(currentRot: Int, newRot: Int) {
+        logBuffer.log(
+            TAG,
+            INFO,
+            {
+                int1 = currentRot
+                int2 = newRot
+            },
+            { "Rotation changed, deferring $int2, staying at $int2" },
+        )
+    }
+
+    fun logRotationChanged(oldRot: Int, newRot: Int) {
+        logBuffer.log(
+            TAG,
+            INFO,
+            {
+                int1 = oldRot
+                int2 = newRot
+            },
+            { "Rotation changed from $int1 to $int2" }
+        )
+    }
+
+    fun logDisplaySizeChanged(currentSize: Point, newSize: Point) {
+        logBuffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = currentSize.flattenToString()
+                str2 = newSize.flattenToString()
+            },
+            { "Resolution changed, deferring size change to $str2, staying at $str1" },
+        )
+    }
+
+    fun logUserSwitched(newUser: Int) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { int1 = newUser },
+            { "UserSwitched newUserId=$int1. Updating color inversion setting" },
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DisableFlagsRepositoryLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/DisableFlagsRepositoryLog.kt
new file mode 100644
index 0000000..e141291
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DisableFlagsRepositoryLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.log.dagger
+
+import com.android.systemui.log.LogBuffer
+import javax.inject.Qualifier
+
+/** A [LogBuffer] for changes to [DisableFlagsRepository]. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class DisableFlagsRepositoryLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DisplayMetricsRepoLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/DisplayMetricsRepoLog.kt
new file mode 100644
index 0000000..fa9ec88
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DisplayMetricsRepoLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for display metrics related logging. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class DisplayMetricsRepoLog
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 0261ee5..b6577f7 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -120,6 +120,14 @@
         return factory.create("ShadeLog", 500, false);
     }
 
+    /** Provides a logging buffer for Shade messages. */
+    @Provides
+    @SysUISingleton
+    @ShadeTouchLog
+    public static LogBuffer provideShadeTouchLogBuffer(LogBufferFactory factory) {
+        return factory.create("ShadeTouchLog", 500, false);
+    }
+
     /** Provides a logging buffer for all logs related to managing notification sections. */
     @Provides
     @SysUISingleton
@@ -224,6 +232,15 @@
                 false /* systrace */);
     }
 
+    /** Provides a logging buffer for the disable flags repository. */
+    @Provides
+    @SysUISingleton
+    @DisableFlagsRepositoryLog
+    public static LogBuffer provideDisableFlagsRepositoryLogBuffer(LogBufferFactory factory) {
+        return factory.create("DisableFlagsRepository", 40 /* maxSize */,
+                false /* systrace */);
+    }
+
     /** Provides a logging buffer for logs related to swipe up gestures. */
     @Provides
     @SysUISingleton
@@ -479,4 +496,20 @@
     public static LogBuffer provideDreamLogBuffer(LogBufferFactory factory) {
         return factory.create("DreamLog", 250);
     }
+
+    /** Provides a {@link LogBuffer} for display metrics related logs. */
+    @Provides
+    @SysUISingleton
+    @DisplayMetricsRepoLog
+    public static LogBuffer provideDisplayMetricsRepoLogBuffer(LogBufferFactory factory) {
+        return factory.create("DisplayMetricsRepo", 50);
+    }
+
+    /** Provides a {@link LogBuffer} for the scene framework. */
+    @Provides
+    @SysUISingleton
+    @SceneFrameworkLog
+    public static LogBuffer provideSceneFrameworkLogBuffer(LogBufferFactory factory) {
+        return factory.create("SceneFramework", 50);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/SceneFrameworkLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/SceneFrameworkLog.kt
new file mode 100644
index 0000000..ef5f4e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SceneFrameworkLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for the Scene Framework. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class SceneFrameworkLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java
new file mode 100644
index 0000000..b13667e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java
@@ -0,0 +1,33 @@
+/*
+ * 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 static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for tracking touches in various shade child views. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface ShadeTouchLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 8d622ae..a7ffc5f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -21,8 +21,8 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.common.buffer.RingBuffer
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.log.TableLogBufferBase
 import com.android.systemui.util.time.SystemClock
 import java.io.PrintWriter
@@ -302,14 +302,14 @@
 
     @Synchronized
     override fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.println(HEADER_PREFIX + name)
-        pw.println("version $VERSION")
+        pw.append(HEADER_PREFIX).println(name)
+        pw.append("version ").println(VERSION)
 
         lastEvictedValues.values.sortedBy { it.timestamp }.forEach { it.dump(pw) }
         for (i in 0 until buffer.size) {
             buffer[i].dump(pw)
         }
-        pw.println(FOOTER_PREFIX + name)
+        pw.append(FOOTER_PREFIX).println(name)
     }
 
     /** Dumps an individual [TableChange]. */
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
index 19e1124..1e2f71f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
@@ -61,7 +61,7 @@
                 bgDispatcher,
                 coroutineScope,
             )
-        dumpManager.registerNormalDumpable(name, tableBuffer)
+        dumpManager.registerTableLogBuffer(name, tableBuffer)
         tableBuffer.init()
         return tableBuffer
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 8d3c6d5..8f884d24 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -30,6 +30,9 @@
 import android.os.ResultReceiver
 import android.os.UserHandle
 import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider
@@ -54,7 +57,11 @@
     /** This is used to override the dependency in a screenshot test */
     @VisibleForTesting
     private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)?
-) : ChooserActivity(), MediaProjectionAppSelectorView, MediaProjectionAppSelectorResultHandler {
+) :
+    ChooserActivity(),
+    MediaProjectionAppSelectorView,
+    MediaProjectionAppSelectorResultHandler,
+    LifecycleOwner {
 
     @Inject
     constructor(
@@ -62,6 +69,8 @@
         activityLauncher: AsyncActivityLauncher
     ) : this(componentFactory, activityLauncher, listControllerFactory = null)
 
+    private val lifecycleRegistry = LifecycleRegistry(this)
+    override val lifecycle = lifecycleRegistry
     private lateinit var configurationController: ConfigurationController
     private lateinit var controller: MediaProjectionAppSelectorController
     private lateinit var recentsViewController: MediaProjectionRecentsViewController
@@ -75,7 +84,9 @@
     override fun getLayoutResource() = R.layout.media_projection_app_selector
 
     public override fun onCreate(bundle: Bundle?) {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
         component = componentFactory.create(activity = this, view = this, resultHandler = this)
+        component.lifecycleObservers.forEach { lifecycle.addObserver(it) }
 
         // Create a separate configuration controller for this activity as the configuration
         // might be different from the global one
@@ -96,6 +107,26 @@
         controller.init()
     }
 
+    override fun onStart() {
+        super.onStart()
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
+    }
+
+    override fun onResume() {
+        super.onResume()
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
+    }
+
+    override fun onPause() {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+        super.onPause()
+    }
+
+    override fun onStop() {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
+        super.onStop()
+    }
+
     override fun onConfigurationChanged(newConfig: Configuration) {
         super.onConfigurationChanged(newConfig)
         configurationController.onConfigurationChanged(newConfig)
@@ -152,6 +183,8 @@
     }
 
     override fun onDestroy() {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+        component.lifecycleObservers.forEach { lifecycle.removeObserver(it) }
         // onDestroy is also called when an app is selected, in that case we only want to send
         // RECORD_CONTENT_TASK but not RECORD_CANCEL
         if (!taskSelected) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index f6a2f37..72352e3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -330,6 +330,8 @@
         // Don't send cancel if the user has moved on to the next activity.
         if (!mUserSelectingTask) {
             finish(RECORD_CANCEL, /* projection= */ null);
+        } else {
+            super.finish();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index 35f5a8c..a91917a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -514,7 +514,7 @@
          * Returns true when the down event of the scroll hits within the target box of the thumb.
          */
         override fun onScroll(
-            eventStart: MotionEvent,
+            eventStart: MotionEvent?,
             event: MotionEvent,
             distanceX: Float,
             distanceY: Float
@@ -528,7 +528,7 @@
          * Gestures that include a fling are considered a false gesture on the seek bar.
          */
         override fun onFling(
-            eventStart: MotionEvent,
+            eventStart: MotionEvent?,
             event: MotionEvent,
             velocityX: Float,
             velocityY: Float
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
index 0b33904..258284e 100644
--- 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
@@ -31,15 +31,12 @@
 private const val TAG = "RecommendationViewHolder"
 
 /** ViewHolder for a Smartspace media recommendation. */
-class RecommendationViewHolder private constructor(itemView: View, updatedView: Boolean) {
+class RecommendationViewHolder private constructor(itemView: View) {
 
     val recommendations = itemView as TransitionLayout
 
     // Recommendation screen
-    lateinit var cardIcon: ImageView
-    lateinit var mediaAppIcons: List<CachingIconView>
-    lateinit var mediaProgressBars: List<SeekBar>
-    lateinit var cardTitle: TextView
+    val cardTitle: TextView = itemView.requireViewById(R.id.media_rec_title)
 
     val mediaCoverContainers =
         listOf<ViewGroup>(
@@ -47,53 +44,25 @@
             itemView.requireViewById(R.id.media_cover2_container),
             itemView.requireViewById(R.id.media_cover3_container)
         )
+    val mediaAppIcons: List<CachingIconView> =
+        mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
     val mediaTitles: List<TextView> =
-        if (updatedView) {
-            mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
-        } else {
-            listOf(
-                itemView.requireViewById(R.id.media_title1),
-                itemView.requireViewById(R.id.media_title2),
-                itemView.requireViewById(R.id.media_title3)
-            )
-        }
+        mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
     val mediaSubtitles: List<TextView> =
-        if (updatedView) {
-            mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
-        } else {
-            listOf(
-                itemView.requireViewById(R.id.media_subtitle1),
-                itemView.requireViewById(R.id.media_subtitle2),
-                itemView.requireViewById(R.id.media_subtitle3)
-            )
+        mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
+    val mediaProgressBars: List<SeekBar> =
+        mediaCoverContainers.map {
+            it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply {
+                // Media playback is in the direction of tape, not time, so it stays LTR
+                layoutDirection = View.LAYOUT_DIRECTION_LTR
+            }
         }
 
     val mediaCoverItems: List<ImageView> =
-        if (updatedView) {
-            mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
-        } else {
-            listOf(
-                itemView.requireViewById(R.id.media_cover1),
-                itemView.requireViewById(R.id.media_cover2),
-                itemView.requireViewById(R.id.media_cover3)
-            )
-        }
+        mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
     val gutsViewHolder = GutsViewHolder(itemView)
 
     init {
-        if (updatedView) {
-            mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
-            cardTitle = itemView.requireViewById(R.id.media_rec_title)
-            mediaProgressBars =
-                mediaCoverContainers.map {
-                    it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply {
-                        // Media playback is in the direction of tape, not time, so it stays LTR
-                        layoutDirection = View.LAYOUT_DIRECTION_LTR
-                    }
-                }
-        } else {
-            cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
-        }
         (recommendations.background as IlluminationDrawable).let { background ->
             mediaCoverContainers.forEach { background.registerLightSource(it) }
             background.registerLightSource(gutsViewHolder.cancel)
@@ -114,63 +83,31 @@
          * @param parent Parent of inflated view.
          */
         @JvmStatic
-        fun create(
-            inflater: LayoutInflater,
-            parent: ViewGroup,
-            updatedView: Boolean,
-        ): RecommendationViewHolder {
+        fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder {
             val itemView =
-                if (updatedView) {
-                    inflater.inflate(
-                        R.layout.media_recommendations,
-                        parent,
-                        false /* attachToRoot */
-                    )
-                } else {
-                    inflater.inflate(
-                        R.layout.media_smartspace_recommendations,
-                        parent,
-                        false /* attachToRoot */
-                    )
-                }
+                inflater.inflate(R.layout.media_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, updatedView)
+            return RecommendationViewHolder(itemView)
         }
 
         // Res Ids for the control components on the recommendation view.
         val controlsIds =
             setOf(
-                R.id.recommendation_card_icon,
                 R.id.media_rec_title,
-                R.id.media_cover1,
-                R.id.media_cover2,
-                R.id.media_cover3,
                 R.id.media_cover,
                 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_title,
-                R.id.media_subtitle1,
-                R.id.media_subtitle2,
-                R.id.media_subtitle3,
                 R.id.media_subtitle,
             )
 
         val mediaTitlesAndSubtitlesIds =
             setOf(
-                R.id.media_title1,
-                R.id.media_title2,
-                R.id.media_title3,
                 R.id.media_title,
-                R.id.media_subtitle1,
-                R.id.media_subtitle2,
-                R.id.media_subtitle3,
                 R.id.media_subtitle,
             )
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index 207df6b..a1291a4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -149,11 +149,7 @@
         // Check if smartspace has explicitly specified whether to re-activate resumable media.
         // The default behavior is to trigger if the smartspace data is active.
         val shouldTriggerResume =
-            if (data.cardAction?.extras?.containsKey(EXTRA_KEY_TRIGGER_RESUME) == true) {
-                data.cardAction.extras.getBoolean(EXTRA_KEY_TRIGGER_RESUME, true)
-            } else {
-                true
-            }
+            data.cardAction?.extras?.getBoolean(EXTRA_KEY_TRIGGER_RESUME, true) ?: true
         val shouldReactivate =
             shouldTriggerResume && !hasActiveMedia() && hasAnyMedia() && data.isActive
 
@@ -269,9 +265,7 @@
                     "Cannot create dismiss action click action: extras missing dismiss_intent."
                 )
             } else if (
-                dismissIntent.getComponent() != null &&
-                    dismissIntent.getComponent().getClassName() ==
-                        EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
+                dismissIntent.component?.className == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
             ) {
                 // Dismiss the card Smartspace data through Smartspace trampoline activity.
                 context.startActivity(dismissIntent)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 6b993ce..282a65a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -22,6 +22,7 @@
 import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
 import android.app.PendingIntent
 import android.app.StatusBarManager
+import android.app.smartspace.SmartspaceAction
 import android.app.smartspace.SmartspaceConfig
 import android.app.smartspace.SmartspaceManager
 import android.app.smartspace.SmartspaceSession
@@ -716,8 +717,7 @@
         val appUid = currentEntry?.appUid ?: Process.INVALID_UID
         val isExplicit =
             desc.extras?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
-                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT &&
-                mediaFlags.isExplicitIndicatorEnabled()
+                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
 
         val progress =
             if (mediaFlags.isResumeProgressEnabled()) {
@@ -826,12 +826,10 @@
 
         // Explicit Indicator
         var isExplicit = false
-        if (mediaFlags.isExplicitIndicatorEnabled()) {
-            val mediaMetadataCompat = MediaMetadataCompat.fromMediaMetadata(metadata)
-            isExplicit =
-                mediaMetadataCompat?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
-                    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
-        }
+        val mediaMetadataCompat = MediaMetadataCompat.fromMediaMetadata(metadata)
+        isExplicit =
+            mediaMetadataCompat?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
+                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
 
         // Artist name
         var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST)
@@ -1253,6 +1251,9 @@
         return try {
             val options = BroadcastOptions.makeBasic()
             options.setInteractive(true)
+            options.setPendingIntentBackgroundActivityStartMode(
+                BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+            )
             intent.send(options.toBundle())
             true
         } catch (e: PendingIntent.CanceledException) {
@@ -1623,20 +1624,18 @@
      *   SmartspaceTarget's data is invalid.
      */
     private fun toSmartspaceMediaData(target: SmartspaceTarget): 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?
-        }
+        val baseAction: SmartspaceAction? = target.baseAction
+        val dismissIntent =
+            baseAction?.extras?.getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) as Intent?
 
         val isActive =
             when {
                 !mediaFlags.isPersistentSsCardEnabled() -> true
-                target.baseAction == null -> true
-                else ->
-                    target.baseAction.extras.getString(EXTRA_KEY_TRIGGER_SOURCE) !=
-                        EXTRA_VALUE_TRIGGER_PERIODIC
+                baseAction == null -> true
+                else -> {
+                    val triggerSource = baseAction.extras?.getString(EXTRA_KEY_TRIGGER_SOURCE)
+                    triggerSource != EXTRA_VALUE_TRIGGER_PERIODIC
+                }
             }
 
         packageName(target)?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
index 3fc3ad6..0a5f857 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
@@ -144,7 +144,7 @@
         val oldKey: String?,
         val controller: MediaController?,
         val localMediaManager: LocalMediaManager,
-        val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager?
+        val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager,
     ) :
         LocalMediaManager.DeviceCallback,
         MediaController.Callback(),
@@ -180,7 +180,7 @@
                 if (!started) {
                     localMediaManager.registerCallback(this)
                     localMediaManager.startScan()
-                    muteAwaitConnectionManager?.startListening()
+                    muteAwaitConnectionManager.startListening()
                     playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
                     playbackVolumeControlId = controller?.playbackInfo?.volumeControlId
                     controller?.registerCallback(this)
@@ -198,7 +198,7 @@
                     controller?.unregisterCallback(this)
                     localMediaManager.stopScan()
                     localMediaManager.unregisterCallback(this)
-                    muteAwaitConnectionManager?.stopListening()
+                    muteAwaitConnectionManager.stopListening()
                     configurationController.removeCallback(configListener)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
index d6f941d..6a8ffb7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
@@ -65,7 +65,7 @@
 
     private val sessionListener =
         object : MediaSessionManager.OnActiveSessionsChangedListener {
-            override fun onActiveSessionsChanged(controllers: List<MediaController>) {
+            override fun onActiveSessionsChanged(controllers: List<MediaController>?) {
                 handleControllersChanged(controllers)
             }
         }
@@ -190,16 +190,18 @@
         }
     }
 
-    private fun handleControllersChanged(controllers: List<MediaController>) {
+    private fun handleControllersChanged(controllers: List<MediaController>?) {
         packageControllers.clear()
-        controllers.forEach { controller ->
+        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 { TokenId(it.sessionToken) })
+        controllers?.map { TokenId(it.sessionToken) }?.let {
+            tokensWithNotifications.retainAll(it)
+        }
     }
 
     /**
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
index e2e269d..534241e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
@@ -19,7 +19,7 @@
 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.core.LogLevel
 import com.android.systemui.log.dagger.MediaTimeoutListenerLog
 import javax.inject.Inject
 
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
index 9e53d77..888b9c7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
@@ -19,7 +19,7 @@
 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.core.LogLevel
 import com.android.systemui.log.dagger.MediaBrowserLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
index b46ebb2..b9cc772 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
@@ -195,7 +195,7 @@
                 }
                 addListener(
                     object : AnimatorListenerAdapter() {
-                        override fun onAnimationEnd(animation: Animator?) {
+                        override fun onAnimationEnd(animation: Animator) {
                             backgroundAnimation = null
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
index 937a618..646d1d0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
@@ -98,11 +98,11 @@
                         addListener(
                             object : AnimatorListenerAdapter() {
                                 var cancelled = false
-                                override fun onAnimationCancel(animation: Animator?) {
+                                override fun onAnimationCancel(animation: Animator) {
                                     cancelled = true
                                 }
 
-                                override fun onAnimationEnd(animation: Animator?) {
+                                override fun onAnimationEnd(animation: Animator) {
                                     if (cancelled) {
                                         return
                                     }
@@ -226,7 +226,7 @@
                 )
                 addListener(
                     object : AnimatorListenerAdapter() {
-                        override fun onAnimationEnd(animation: Animator?) {
+                        override fun onAnimationEnd(animation: Animator) {
                             rippleData.progress = 0f
                             rippleAnimation = null
                             invalidateSelf()
@@ -270,11 +270,8 @@
         return bounds
     }
 
-    override fun onStateChange(stateSet: IntArray?): Boolean {
+    override fun onStateChange(stateSet: IntArray): Boolean {
         val changed = super.onStateChange(stateSet)
-        if (stateSet == null) {
-            return changed
-        }
 
         val wasPressed = pressed
         var enabled = false
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
index 70b5e75..398dcf2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -744,11 +744,7 @@
 
             val newRecs = mediaControlPanelFactory.get()
             newRecs.attachRecommendation(
-                RecommendationViewHolder.create(
-                    LayoutInflater.from(context),
-                    mediaContent,
-                    mediaFlags.isRecommendationCardUpdateEnabled()
-                )
+                RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
             )
             newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
             val lp =
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
index 0ed2434..3dc0000 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.MediaCarouselControllerLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index 1ace316..ce50a11 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -127,19 +127,19 @@
         object : GestureDetector.SimpleOnGestureListener() {
             override fun onFling(
                 eStart: MotionEvent?,
-                eCurrent: MotionEvent?,
+                eCurrent: MotionEvent,
                 vX: Float,
                 vY: Float
             ) = onFling(vX, vY)
 
             override fun onScroll(
                 down: MotionEvent?,
-                lastMotion: MotionEvent?,
+                lastMotion: MotionEvent,
                 distanceX: Float,
                 distanceY: Float
-            ) = onScroll(down!!, lastMotion!!, distanceX)
+            ) = onScroll(down!!, lastMotion, distanceX)
 
-            override fun onDown(e: MotionEvent?): Boolean {
+            override fun onDown(e: MotionEvent): Boolean {
                 if (falsingProtectionNeeded) {
                     falsingCollector.onNotificationStartDismissing()
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 35082fd..a12bc2c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -23,6 +23,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
 import android.animation.AnimatorSet;
+import android.app.ActivityOptions;
 import android.app.BroadcastOptions;
 import android.app.PendingIntent;
 import android.app.WallpaperColors;
@@ -535,7 +536,10 @@
                         mLockscreenUserManager.getCurrentUserId());
                 if (showOverLockscreen) {
                     try {
-                        clickIntent.send();
+                        ActivityOptions opts = ActivityOptions.makeBasic();
+                        opts.setPendingIntentBackgroundActivityStartMode(
+                                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                        clickIntent.send(opts.toBundle());
                     } catch (PendingIntent.CanceledException e) {
                         Log.e(TAG, "Pending intent for " + key + " was cancelled");
                     }
@@ -684,6 +688,8 @@
                                 try {
                                     BroadcastOptions options = BroadcastOptions.makeBasic();
                                     options.setInteractive(true);
+                                    options.setPendingIntentBackgroundActivityStartMode(
+                                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
                                     deviceIntent.send(options.toBundle());
                                 } catch (PendingIntent.CanceledException e) {
                                     Log.e(TAG, "Device pending intent was canceled");
@@ -778,14 +784,7 @@
             contentDescription =
                     mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText();
         } else if (data != null) {
-            if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
-                contentDescription = mContext.getString(
-                        R.string.controls_media_smartspace_rec_header);
-            } else {
-                contentDescription = mContext.getString(
-                        R.string.controls_media_smartspace_rec_description,
-                        data.getAppName(mContext));
-            }
+            contentDescription = mContext.getString(R.string.controls_media_smartspace_rec_header);
         } else {
             contentDescription = null;
         }
@@ -1371,10 +1370,6 @@
         PackageManager packageManager = mContext.getPackageManager();
         // Set up media source app's logo.
         Drawable icon = packageManager.getApplicationIcon(applicationInfo);
-        if (!mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
-            ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
-            headerLogoImageView.setImageDrawable(icon);
-        }
         fetchAndUpdateRecommendationColors(icon);
 
         // Set up media rec card's tap action if applicable.
@@ -1395,16 +1390,7 @@
 
             // Set up media item cover.
             ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex);
-            if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
-                bindRecommendationArtwork(
-                        recommendation,
-                        data.getPackageName(),
-                        itemIndex
-                );
-            } else {
-                mediaCoverImageView.post(
-                        () -> mediaCoverImageView.setImageIcon(recommendation.getIcon()));
-            }
+            bindRecommendationArtwork(recommendation, data.getPackageName(), itemIndex);
 
             // Set up the media item's click listener if applicable.
             ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex);
@@ -1449,21 +1435,18 @@
             subtitleView.setText(subtitle);
 
             // Set up progress bar
-            if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
-                SeekBar mediaProgressBar =
-                        mRecommendationViewHolder.getMediaProgressBars().get(itemIndex);
-                TextView mediaSubtitle =
-                        mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
-                // show progress bar if the recommended album is played.
-                Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras());
-                if (progress == null || progress <= 0.0) {
-                    mediaProgressBar.setVisibility(View.GONE);
-                    mediaSubtitle.setVisibility(View.VISIBLE);
-                } else {
-                    mediaProgressBar.setProgress((int) (progress * 100));
-                    mediaProgressBar.setVisibility(View.VISIBLE);
-                    mediaSubtitle.setVisibility(View.GONE);
-                }
+            SeekBar mediaProgressBar =
+                    mRecommendationViewHolder.getMediaProgressBars().get(itemIndex);
+            TextView mediaSubtitle = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
+            // show progress bar if the recommended album is played.
+            Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras());
+            if (progress == null || progress <= 0.0) {
+                mediaProgressBar.setVisibility(View.GONE);
+                mediaSubtitle.setVisibility(View.VISIBLE);
+            } else {
+                mediaProgressBar.setProgress((int) (progress * 100));
+                mediaProgressBar.setVisibility(View.VISIBLE);
+                mediaSubtitle.setVisibility(View.GONE);
             }
         }
         mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS;
@@ -1582,9 +1565,7 @@
         int textPrimaryColor = MediaColorSchemesKt.textPrimaryFromScheme(colorScheme);
         int textSecondaryColor = MediaColorSchemesKt.textSecondaryFromScheme(colorScheme);
 
-        if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
-            mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
-        }
+        mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
 
         mRecommendationViewHolder.getRecommendations()
                 .setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
@@ -1592,12 +1573,9 @@
                 (title) -> title.setTextColor(textPrimaryColor));
         mRecommendationViewHolder.getMediaSubtitles().forEach(
                 (subtitle) -> subtitle.setTextColor(textSecondaryColor));
-        if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
-            mRecommendationViewHolder.getMediaProgressBars().forEach(
-                    (progressBar) -> progressBar.setProgressTintList(
-                            ColorStateList.valueOf(textPrimaryColor))
-            );
-        }
+        mRecommendationViewHolder.getMediaProgressBars().forEach(
+                (progressBar) -> progressBar.setProgressTintList(
+                        ColorStateList.valueOf(textPrimaryColor)));
 
         mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme);
     }
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
index fe8ebaf..c1c757e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -180,20 +180,20 @@
                 object : AnimatorListenerAdapter() {
                     private var cancelled: Boolean = false
 
-                    override fun onAnimationCancel(animation: Animator?) {
+                    override fun onAnimationCancel(animation: Animator) {
                         cancelled = true
                         animationPending = false
                         rootView?.removeCallbacks(startAnimation)
                     }
 
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         isCrossFadeAnimatorRunning = false
                         if (!cancelled) {
                             applyTargetStateIfNotAnimating()
                         }
                     }
 
-                    override fun onAnimationStart(animation: Animator?) {
+                    override fun onAnimationStart(animation: Animator) {
                         cancelled = false
                         animationPending = false
                     }
@@ -606,7 +606,7 @@
         val viewHost = UniqueObjectHostView(context)
         viewHost.addOnAttachStateChangeListener(
             object : View.OnAttachStateChangeListener {
-                override fun onViewAttachedToWindow(p0: View?) {
+                override fun onViewAttachedToWindow(p0: View) {
                     if (rootOverlay == null) {
                         rootView = viewHost.viewRootImpl.view
                         rootOverlay = (rootView!!.overlay as ViewGroupOverlay)
@@ -614,7 +614,7 @@
                     viewHost.removeOnAttachStateChangeListener(this)
                 }
 
-                override fun onViewDetachedFromWindow(p0: View?) {}
+                override fun onViewDetachedFromWindow(p0: View) {}
             }
         )
         return viewHost
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
index be570b4..631a0b8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
@@ -144,12 +144,12 @@
         setListeningToMediaData(true)
         hostView.addOnAttachStateChangeListener(
             object : OnAttachStateChangeListener {
-                override fun onViewAttachedToWindow(v: View?) {
+                override fun onViewAttachedToWindow(v: View) {
                     setListeningToMediaData(true)
                     updateViewVisibility()
                 }
 
-                override fun onViewDetachedFromWindow(v: View?) {
+                override fun onViewDetachedFromWindow(v: View) {
                     setListeningToMediaData(false)
                 }
             }
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
index 4bca778..1dd969f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -655,13 +655,8 @@
                 expandedLayout.load(context, R.xml.media_session_expanded)
             }
             TYPE.RECOMMENDATION -> {
-                if (mediaFlags.isRecommendationCardUpdateEnabled()) {
-                    collapsedLayout.load(context, R.xml.media_recommendations_view_collapsed)
-                    expandedLayout.load(context, R.xml.media_recommendations_view_expanded)
-                } else {
-                    collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
-                    expandedLayout.load(context, R.xml.media_recommendation_expanded)
-                }
+                collapsedLayout.load(context, R.xml.media_recommendations_collapsed)
+                expandedLayout.load(context, R.xml.media_recommendations_expanded)
             }
         }
         refreshState()
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
index c781b76..8f1595d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.MediaViewLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
index 583c626..16dfc21 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
@@ -117,7 +117,7 @@
                     }
                     addListener(
                         object : AnimatorListenerAdapter() {
-                            override fun onAnimationEnd(animation: Animator?) {
+                            override fun onAnimationEnd(animation: Animator) {
                                 heightAnimator = null
                             }
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 9bc66f6..f2db088 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -34,28 +34,12 @@
         return enabled || featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS)
     }
 
-    /** Check whether we support displaying information about mute await connections. */
-    fun areMuteAwaitConnectionsEnabled() = featureFlags.isEnabled(Flags.MEDIA_MUTE_AWAIT)
-
-    /**
-     * Check whether we enable support for nearby media devices. See
-     * [android.app.StatusBarManager.registerNearbyMediaDevicesProvider] for more information.
-     */
-    fun areNearbyMediaDevicesEnabled() = featureFlags.isEnabled(Flags.MEDIA_NEARBY_DEVICES)
-
-    /** Check whether we show explicit indicator on UMO */
-    fun isExplicitIndicatorEnabled() = featureFlags.isEnabled(Flags.MEDIA_EXPLICIT_INDICATOR)
-
     /**
      * If true, keep active media controls for the lifetime of the MediaSession, regardless of
      * whether the underlying notification was dismissed
      */
     fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
 
-    /** Check whether we show the updated recommendation card. */
-    fun isRecommendationCardUpdateEnabled() =
-        featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
-
     /** Check whether to get progress information for resume players */
     fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.kt
new file mode 100644
index 0000000..9924369
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSessionLegacyHelperWrapper.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaSessionLegacyHelper
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Injectable wrapper around `MediaSessionLegacyHelper` functions */
+@SysUISingleton
+class MediaSessionLegacyHelperWrapper @Inject constructor() {
+    fun getHelper(context: Context): MediaSessionLegacyHelper {
+        return MediaSessionLegacyHelper.getHelper(context)
+    }
+}
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 46efac5..888cd0b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -23,10 +23,7 @@
 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.receiver.MediaTttReceiverLogBuffer;
@@ -119,29 +116,4 @@
         }
         return Optional.of(helperLazy.get());
     }
-
-    /** */
-    @Provides
-    @SysUISingleton
-    static Optional<MediaMuteAwaitConnectionCli> providesMediaMuteAwaitConnectionCli(
-            MediaFlags mediaFlags,
-            Lazy<MediaMuteAwaitConnectionCli> muteAwaitConnectionCliLazy
-    ) {
-        if (!mediaFlags.areMuteAwaitConnectionsEnabled()) {
-            return Optional.empty();
-        }
-        return Optional.of(muteAwaitConnectionCliLazy.get());
-    }
-
-    /** */
-    @Provides
-    @SysUISingleton
-    static Optional<NearbyMediaDevicesManager> providesNearbyMediaDevicesManager(
-            MediaFlags mediaFlags,
-            Lazy<NearbyMediaDevicesManager> nearbyMediaDevicesManagerLazy) {
-        if (!mediaFlags.areNearbyMediaDevicesEnabled()) {
-            return Optional.empty();
-        }
-        return Optional.of(nearbyMediaDevicesManagerLazy.get());
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index b88eba9..a3d1d8c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -157,6 +157,7 @@
         private String mDeviceId;
         private ValueAnimator mCornerAnimator;
         private ValueAnimator mVolumeAnimator;
+        private int mLatestUpdateVolume = -1;
 
         MediaDeviceBaseViewHolder(View view) {
             super(view);
@@ -314,7 +315,11 @@
             mSeekBar.setMaxVolume(device.getMaxVolume());
             final int currentVolume = device.getCurrentVolume();
             if (!mIsDragging) {
-                if (mSeekBar.getVolume() != currentVolume) {
+                if (mSeekBar.getVolume() != currentVolume && (mLatestUpdateVolume == -1
+                        || currentVolume == mLatestUpdateVolume)) {
+                    // Update only if volume of device and value of volume bar doesn't match.
+                    // Check if response volume match with the latest request, to ignore obsolete
+                    // response
                     if (isCurrentSeekbarInvisible && !mIsInitVolumeFirstTime) {
                         updateTitleIcon(currentVolume == 0 ? R.drawable.media_output_icon_volume_off
                                         : R.drawable.media_output_icon_volume,
@@ -330,12 +335,16 @@
                                 updateUnmutedVolumeIcon();
                             }
                             mSeekBar.setVolume(currentVolume);
+                            mLatestUpdateVolume = -1;
                         }
                     }
                 } else if (currentVolume == 0) {
                     mSeekBar.resetVolume();
                     updateMutedVolumeIcon();
                 }
+                if (currentVolume == mLatestUpdateVolume) {
+                    mLatestUpdateVolume = -1;
+                }
             }
             if (mIsInitVolumeFirstTime) {
                 mIsInitVolumeFirstTime = false;
@@ -360,6 +369,7 @@
                         mStartFromMute = false;
                     }
                     if (progressToVolume != deviceVolume) {
+                        mLatestUpdateVolume = progressToVolume;
                         mController.adjustVolume(device, progressToVolume);
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 7712690..3a1d8b0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -296,7 +296,10 @@
 
     @Override
     public void stop() {
-        if (isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) {
+        // unregister broadcast callback should only depend on profile and registered flag
+        // rather than remote device or broadcast state
+        // otherwise it might have risks of leaking registered callback handle
+        if (mMediaOutputController.isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) {
             mMediaOutputController.unregisterLeBroadcastServiceCallback(mBroadcastCallback);
             mIsLeBroadcastCallbackRegistered = false;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index b4578e9..11a16a4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -261,7 +261,10 @@
             mMediaOutputController.registerLeBroadcastAssistantServiceCallback(mExecutor,
                     mBroadcastAssistantCallback);
         }
-        connectBroadcastWithActiveDevice();
+        /* Add local source broadcast to connected capable devices that may be possible receivers
+         * of stream.
+         */
+        startBroadcastWithConnectedDevices();
     }
 
     @Override
@@ -394,30 +397,26 @@
         }
     }
 
-    void connectBroadcastWithActiveDevice() {
+    void startBroadcastWithConnectedDevices() {
         //get the Metadata, and convert to BT QR code format.
         BluetoothLeBroadcastMetadata broadcastMetadata = getBroadcastMetadata();
         if (broadcastMetadata == null) {
             Log.e(TAG, "Error: There is no broadcastMetadata.");
             return;
         }
-        MediaDevice mediaDevice = mMediaOutputController.getCurrentConnectedMediaDevice();
-        if (mediaDevice == null || !(mediaDevice instanceof BluetoothMediaDevice)
-                || !mediaDevice.isBLEDevice()) {
-            Log.e(TAG, "Error: There is no active BT LE device.");
-            return;
-        }
-        BluetoothDevice sink = ((BluetoothMediaDevice) mediaDevice).getCachedDevice().getDevice();
-        Log.d(TAG, "The broadcastMetadata broadcastId: " + broadcastMetadata.getBroadcastId()
-                + ", the device: " + sink.getAnonymizedAddress());
 
-        if (mMediaOutputController.isThereAnyBroadcastSourceIntoSinkDevice(sink)) {
-            Log.d(TAG, "The sink device has the broadcast source now.");
-            return;
-        }
-        if (!mMediaOutputController.addSourceIntoSinkDeviceWithBluetoothLeAssistant(sink,
-                broadcastMetadata, /*isGroupOp=*/ true)) {
-            Log.e(TAG, "Error: Source add failed");
+        for (BluetoothDevice sink : mMediaOutputController.getConnectedBroadcastSinkDevices()) {
+            Log.d(TAG, "The broadcastMetadata broadcastId: " + broadcastMetadata.getBroadcastId()
+                    + ", the device: " + sink.getAnonymizedAddress());
+
+            if (mMediaOutputController.isThereAnyBroadcastSourceIntoSinkDevice(sink)) {
+                Log.d(TAG, "The sink device has the broadcast source now.");
+                return;
+            }
+            if (!mMediaOutputController.addSourceIntoSinkDeviceWithBluetoothLeAssistant(sink,
+                    broadcastMetadata, /*isGroupOp=*/ false)) {
+                Log.e(TAG, "Error: Source add failed");
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
index a1e9995..18d5103 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import java.util.Optional
 import javax.inject.Inject
 
 /**
@@ -46,7 +45,7 @@
     private val notifCollection: CommonNotifCollection,
     private val uiEventLogger: UiEventLogger,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
-    private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
+    private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
     private val audioManager: AudioManager,
     private val powerExemptionManager: PowerExemptionManager,
     private val keyGuardManager: KeyguardManager,
@@ -62,7 +61,7 @@
 
         val controller = MediaOutputController(context, packageName,
                 mediaSessionManager, lbm, starter, notifCollection,
-                dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
+                dialogLaunchAnimator, nearbyMediaDevicesManager, audioManager,
                 powerExemptionManager, keyGuardManager, featureFlags, userTracker)
         val dialog =
                 MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index cc75478..b431bab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -99,7 +99,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -177,7 +176,7 @@
             lbm, ActivityStarter starter,
             CommonNotifCollection notifCollection,
             DialogLaunchAnimator dialogLaunchAnimator,
-            Optional<NearbyMediaDevicesManager> nearbyMediaDevicesManagerOptional,
+            NearbyMediaDevicesManager nearbyMediaDevicesManager,
             AudioManager audioManager,
             PowerExemptionManager powerExemptionManager,
             KeyguardManager keyGuardManager,
@@ -198,7 +197,7 @@
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
         mDialogLaunchAnimator = dialogLaunchAnimator;
-        mNearbyMediaDevicesManager = nearbyMediaDevicesManagerOptional.orElse(null);
+        mNearbyMediaDevicesManager = nearbyMediaDevicesManager;
         mColorItemContent = Utils.getColorStateListDefaultColor(mContext,
                 R.color.media_dialog_item_main_content);
         mColorSeekbarProgress = Utils.getColorStateListDefaultColor(mContext,
@@ -927,7 +926,7 @@
     void launchMediaOutputBroadcastDialog(View mediaOutputDialog, BroadcastSender broadcastSender) {
         MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
                 mMediaSessionManager, mLocalBluetoothManager, mActivityStarter,
-                mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager),
+                mNotifCollection, mDialogLaunchAnimator, mNearbyMediaDevicesManager,
                 mAudioManager, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags,
                 mUserTracker);
         MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
@@ -990,7 +989,7 @@
         LocalBluetoothLeBroadcast broadcast =
                 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
         if (broadcast == null) {
-            Log.d(TAG, "getBroadcastMetadata: LE Audio Broadcast is null");
+            Log.d(TAG, "getLocalBroadcastMetadataQrCodeString: LE Audio Broadcast is null");
             return "";
         }
         final LocalBluetoothLeBroadcastMetadata metadata =
@@ -1094,11 +1093,23 @@
         broadcast.unregisterServiceCallBack(callback);
     }
 
+    List<BluetoothDevice> getConnectedBroadcastSinkDevices() {
+        LocalBluetoothLeBroadcastAssistant assistant =
+                mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+        if (assistant == null) {
+            Log.d(TAG, "getConnectedBroadcastSinkDevices: The broadcast assistant profile is null");
+            return null;
+        }
+
+        return assistant.getConnectedDevices();
+    }
+
     boolean isThereAnyBroadcastSourceIntoSinkDevice(BluetoothDevice sink) {
         LocalBluetoothLeBroadcastAssistant assistant =
                 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
         if (assistant == null) {
-            Log.d(TAG, "The broadcast assistant profile is null");
+            Log.d(TAG, "isThereAnyBroadcastSourceIntoSinkDevice: The broadcast assistant profile "
+                    + "is null");
             return false;
         }
         List<BluetoothLeBroadcastReceiveState> sourceList = assistant.getAllSources(sink);
@@ -1111,7 +1122,8 @@
         LocalBluetoothLeBroadcastAssistant assistant =
                 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
         if (assistant == null) {
-            Log.d(TAG, "The broadcast assistant profile is null");
+            Log.d(TAG, "addSourceIntoSinkDeviceWithBluetoothLeAssistant: The broadcast assistant "
+                    + "profile is null");
             return false;
         }
         assistant.addSource(sink, metadata, isGroupOp);
@@ -1124,7 +1136,8 @@
         LocalBluetoothLeBroadcastAssistant assistant =
                 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
         if (assistant == null) {
-            Log.d(TAG, "The broadcast assistant profile is null");
+            Log.d(TAG, "registerLeBroadcastAssistantServiceCallback: The broadcast assistant "
+                    + "profile is null");
             return;
         }
         Log.d(TAG, "Register LE broadcast assistant callback");
@@ -1136,7 +1149,8 @@
         LocalBluetoothLeBroadcastAssistant assistant =
                 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
         if (assistant == null) {
-            Log.d(TAG, "The broadcast assistant profile is null");
+            Log.d(TAG, "unregisterLeBroadcastAssistantServiceCallback: The broadcast assistant "
+                    + "profile is null");
             return;
         }
         Log.d(TAG, "Unregister LE broadcast assistant callback");
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index f3865f5..e5a6bb5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -104,11 +104,16 @@
     @Override
     public boolean isBroadcastSupported() {
         boolean isBluetoothLeDevice = false;
+        boolean isBroadcastEnabled = false;
         if (FeatureFlagUtils.isEnabled(mContext,
                 FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST)) {
             if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
                 isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice(
                     mMediaOutputController.getCurrentConnectedMediaDevice());
+                // if broadcast is active, broadcast should be considered as supported
+                // there could be a valid case that broadcast is ongoing
+                // without active LEA device connected
+                isBroadcastEnabled = mMediaOutputController.isBluetoothLeBroadcastEnabled();
             }
         } else {
             // To decouple LE Audio Broadcast and Unicast, it always displays the button when there
@@ -116,7 +121,8 @@
             isBluetoothLeDevice = true;
         }
 
-        return mMediaOutputController.isBroadcastSupported() && isBluetoothLeDevice;
+        return mMediaOutputController.isBroadcastSupported()
+                && (isBluetoothLeDevice || isBroadcastEnabled);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 4c168ec..af65937 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import java.util.Optional
 import javax.inject.Inject
 
 /**
@@ -48,7 +47,7 @@
     private val notifCollection: CommonNotifCollection,
     private val uiEventLogger: UiEventLogger,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
-    private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
+    private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
     private val audioManager: AudioManager,
     private val powerExemptionManager: PowerExemptionManager,
     private val keyGuardManager: KeyguardManager,
@@ -68,7 +67,7 @@
         val controller = MediaOutputController(
             context, packageName,
             mediaSessionManager, lbm, starter, notifCollection,
-            dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
+            dialogLaunchAnimator, nearbyMediaDevicesManager, audioManager,
             powerExemptionManager, keyGuardManager, featureFlags, userTracker)
         val dialog =
             MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller,
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
index 39d4e6e..412d1a3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java
@@ -56,6 +56,7 @@
      * Update the endpoints of a content switching operation.
      * This method should be called before a switching operation, so the metric logger can track
      * source and target devices.
+     *
      * @param source the current connected media device
      * @param target the target media device for content switching to
      */
@@ -72,37 +73,9 @@
 
     /**
      * Do the metric logging of content switching success.
+     *
      * @param selectedDeviceType string representation of the target media device
-     * @param deviceList media device list for device count updating
-     */
-    public void logOutputSuccess(String selectedDeviceType, List<MediaDevice> deviceList) {
-        if (DEBUG) {
-            Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType);
-        }
-
-        if (mSourceDevice == null && mTargetDevice == null) {
-            return;
-        }
-
-        updateLoggingDeviceCount(deviceList);
-
-        SysUiStatsLog.write(
-                SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
-                getLoggingDeviceType(mSourceDevice, true),
-                getLoggingDeviceType(mTargetDevice, false),
-                SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__OK,
-                SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NO_ERROR,
-                getLoggingPackageName(),
-                mWiredDeviceCount,
-                mConnectedBluetoothDeviceCount,
-                mRemoteDeviceCount,
-                mAppliedDeviceCountWithinRemoteGroup);
-    }
-
-    /**
-     * Do the metric logging of content switching success.
-     * @param selectedDeviceType string representation of the target media device
-     * @param deviceItemList media item list for device count updating
+     * @param deviceItemList     media item list for device count updating
      */
     public void logOutputItemSuccess(String selectedDeviceType, List<MediaItem> deviceItemList) {
         if (DEBUG) {
@@ -125,11 +98,14 @@
                 mWiredDeviceCount,
                 mConnectedBluetoothDeviceCount,
                 mRemoteDeviceCount,
-                mAppliedDeviceCountWithinRemoteGroup);
+                mAppliedDeviceCountWithinRemoteGroup,
+                mTargetDevice.isSuggestedDevice(),
+                mTargetDevice.hasOngoingSession());
     }
 
     /**
      * Do the metric logging of volume adjustment.
+     *
      * @param source the device been adjusted
      */
     public void logInteractionAdjustVolume(MediaDevice source) {
@@ -141,7 +117,8 @@
                 SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
                 SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__ADJUST_VOLUME,
                 getInteractionDeviceType(source),
-                getLoggingPackageName());
+                getLoggingPackageName(),
+                source.isSuggestedDevice());
     }
 
     /**
@@ -156,7 +133,8 @@
                 SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
                 SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__STOP_CASTING,
                 SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE,
-                getLoggingPackageName());
+                getLoggingPackageName(),
+                /*isSuggestedDevice = */false);
     }
 
     /**
@@ -171,42 +149,15 @@
                 SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
                 SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__EXPANSION,
                 getInteractionDeviceType(source),
-                getLoggingPackageName());
-    }
-
-    /**
-     * Do the metric logging of content switching failure.
-     * @param deviceList media device list for device count updating
-     * @param reason the reason of content switching failure
-     */
-    public void logOutputFailure(List<MediaDevice> deviceList, int reason) {
-        if (DEBUG) {
-            Log.e(TAG, "logRequestFailed - " + reason);
-        }
-
-        if (mSourceDevice == null && mTargetDevice == null) {
-            return;
-        }
-
-        updateLoggingDeviceCount(deviceList);
-
-        SysUiStatsLog.write(
-                SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
-                getLoggingDeviceType(mSourceDevice, true),
-                getLoggingDeviceType(mTargetDevice, false),
-                SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__ERROR,
-                getLoggingSwitchOpSubResult(reason),
                 getLoggingPackageName(),
-                mWiredDeviceCount,
-                mConnectedBluetoothDeviceCount,
-                mRemoteDeviceCount,
-                mAppliedDeviceCountWithinRemoteGroup);
+                source.isSuggestedDevice());
     }
 
     /**
      * Do the metric logging of content switching failure.
+     *
      * @param deviceItemList media item list for device count updating
-     * @param reason the reason of content switching failure
+     * @param reason         the reason of content switching failure
      */
     public void logOutputItemFailure(List<MediaItem> deviceItemList, int reason) {
         if (DEBUG) {
@@ -229,7 +180,9 @@
                 mWiredDeviceCount,
                 mConnectedBluetoothDeviceCount,
                 mRemoteDeviceCount,
-                mAppliedDeviceCountWithinRemoteGroup);
+                mAppliedDeviceCountWithinRemoteGroup,
+                mTargetDevice.isSuggestedDevice(),
+                mTargetDevice.hasOngoingSession());
     }
 
     private void updateLoggingDeviceCount(List<MediaDevice> deviceList) {
@@ -266,7 +219,7 @@
         mWiredDeviceCount = mConnectedBluetoothDeviceCount = mRemoteDeviceCount = 0;
         mAppliedDeviceCountWithinRemoteGroup = 0;
 
-        for (MediaItem mediaItem: deviceItemList) {
+        for (MediaItem mediaItem : deviceItemList) {
             if (mediaItem.getMediaDevice().isPresent()
                     && mediaItem.getMediaDevice().get().isConnected()) {
                 switch (mediaItem.getMediaDevice().get().getDeviceType()) {
@@ -326,6 +279,10 @@
                 return isSourceDevice
                         ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__REMOTE_GROUP
                         : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__REMOTE_GROUP;
+            case MediaDevice.MediaDeviceType.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER:
+                return isSourceDevice
+                        ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__AVR
+                        : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__AVR;
             default:
                 return isSourceDevice
                         ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__UNKNOWN_TYPE
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 e260894..97ec654 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt
@@ -21,14 +21,12 @@
 import com.android.settingslib.media.LocalMediaManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.controls.util.MediaFlags
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
 /** Factory class to create [MediaMuteAwaitConnectionManager] instances. */
 @SysUISingleton
 class MediaMuteAwaitConnectionManagerFactory @Inject constructor(
-    private val mediaFlags: MediaFlags,
     private val context: Context,
     private val logger: MediaMuteAwaitLogger,
     @Main private val mainExecutor: Executor
@@ -36,10 +34,7 @@
     private val deviceIconUtil = DeviceIconUtil()
 
     /** Creates a [MediaMuteAwaitConnectionManager]. */
-    fun create(localMediaManager: LocalMediaManager): MediaMuteAwaitConnectionManager? {
-        if (!mediaFlags.areMuteAwaitConnectionsEnabled()) {
-            return null
-        }
+    fun create(localMediaManager: LocalMediaManager): MediaMuteAwaitConnectionManager {
         return MediaMuteAwaitConnectionManager(
                 mainExecutor, localMediaManager, context, deviceIconUtil, logger
         )
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 bbcf259..4171682 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt
@@ -3,7 +3,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.MediaMuteAwaitLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.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 66399d5..46c0132 100644
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt
@@ -3,7 +3,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.NearbyMediaDevicesLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 /** Log messages for [NearbyMediaDevicesManager]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
index 60504e4..8a565fa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
@@ -30,8 +30,4 @@
     /** Check whether the flag for the receiver success state is enabled. */
     fun isMediaTttReceiverSuccessRippleEnabled(): Boolean =
         featureFlags.isEnabled(Flags.MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE)
-
-    /** True if the media transfer chip can be dismissed via a gesture. */
-    fun isMediaTttDismissGestureEnabled(): Boolean =
-        featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
index eeda1027..3c2226f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.media.taptotransfer.common
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 
 /** A helper for logging media tap-to-transfer events. */
 object MediaTttLoggerUtils {
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 bbd3d33..da8e106 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
@@ -201,13 +201,13 @@
     }
 
     override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
-        val packageName = newInfo.routeInfo.clientPackageName
+        val packageName: String? = newInfo.routeInfo.clientPackageName
         var iconInfo = MediaTttUtils.getIconInfoFromPackageName(
             context,
             packageName,
             isReceiver = true,
         ) {
-            logger.logPackageNotFound(packageName)
+            packageName?.let { logger.logPackageNotFound(it) }
         }
 
         if (newInfo.appNameOverride != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
index 5013802..fbf7e25 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
@@ -68,9 +68,9 @@
         )
         rippleView.addOnAttachStateChangeListener(
             object : View.OnAttachStateChangeListener {
-                override fun onViewDetachedFromWindow(view: View?) {}
+                override fun onViewDetachedFromWindow(view: View) {}
 
-                override fun onViewAttachedToWindow(view: View?) {
+                override fun onViewAttachedToWindow(view: View) {
                     if (view == null) {
                         return
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 0b0535d..35018f1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -54,7 +54,7 @@
         // Reset all listeners to animator.
         animator.removeAllListeners()
         animator.addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationEnd(animation: Animator?) {
+            override fun onAnimationEnd(animation: Animator) {
                 onAnimationEnd?.run()
                 isStarted = false
             }
@@ -86,7 +86,7 @@
             invalidate()
         }
         animator.addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationEnd(animation: Animator?) {
+            override fun onAnimationEnd(animation: Animator) {
                 animation?.let { visibility = GONE }
                 onAnimationEnd?.run()
                 isStarted = false
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
index f75f8b9..87d0098 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -162,7 +162,7 @@
         logger: MediaTttSenderLogger,
         instanceId: InstanceId,
     ): ChipbarInfo {
-        val packageName = routeInfo.clientPackageName
+        val packageName = checkNotNull(routeInfo.clientPackageName)
         val otherDeviceName =
             if (routeInfo.name.isBlank()) {
                 context.getString(R.string.media_ttt_default_device_type)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
index 206e5e3..503afd3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
@@ -20,7 +20,7 @@
 import com.android.internal.logging.InstanceId
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.media.taptotransfer.common.MediaTttLoggerUtils
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 3088d8b..11538fa 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -20,6 +20,7 @@
 import android.content.ComponentName
 import android.content.Context
 import android.os.UserHandle
+import androidx.lifecycle.DefaultLifecycleObserver
 import com.android.launcher3.icons.IconFactory
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.MediaProjectionAppSelectorActivity
@@ -46,6 +47,7 @@
 import dagger.Subcomponent
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
 import javax.inject.Qualifier
 import javax.inject.Scope
 import kotlinx.coroutines.CoroutineScope
@@ -100,6 +102,12 @@
     @MediaProjectionAppSelectorScope
     fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
 
+    @Binds
+    @IntoSet
+    fun taskPreviewSizeProviderAsLifecycleObserver(
+        impl: TaskPreviewSizeProvider
+    ): DefaultLifecycleObserver
+
     companion object {
         @Provides
         @MediaProjectionAppSelector
@@ -166,4 +174,5 @@
     @get:PersonalProfile val personalProfileUserHandle: UserHandle
 
     @MediaProjectionAppSelector val configurationController: ConfigurationController
+    val lifecycleObservers: Set<DefaultLifecycleObserver>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index c816446..64de9bd 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -88,7 +88,7 @@
                 .inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false)
                 as ViewGroup
 
-        val container = recentsRoot.findViewById<View>(R.id.media_projection_recent_tasks_container)
+        val container = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
         container.setTaskHeightSize()
 
         val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
index 89f66b7..864d35a 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
@@ -21,6 +21,8 @@
 import android.graphics.Rect
 import android.view.WindowInsets.Type
 import android.view.WindowManager
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
 import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen
@@ -35,18 +37,22 @@
 constructor(
     private val context: Context,
     private val windowManager: WindowManager,
-    configurationController: ConfigurationController
-) : CallbackController<TaskPreviewSizeListener>, ConfigurationListener {
+    private val configurationController: ConfigurationController,
+) : CallbackController<TaskPreviewSizeListener>, ConfigurationListener, DefaultLifecycleObserver {
 
     /** Returns the size of the task preview on the screen in pixels */
     val size: Rect = calculateSize()
 
     private val listeners = arrayListOf<TaskPreviewSizeListener>()
 
-    init {
+    override fun onCreate(owner: LifecycleOwner) {
         configurationController.addCallback(this)
     }
 
+    override fun onDestroy(owner: LifecycleOwner) {
+        configurationController.removeCallback(this)
+    }
+
     override fun onConfigChanged(newConfig: Configuration) {
         val newSize = calculateSize()
         if (newSize != size) {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
new file mode 100644
index 0000000..3c50127
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher
+
+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.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
+import javax.inject.Inject
+
+@SysUISingleton
+class MediaProjectionTaskSwitcherCoreStartable
+@Inject
+constructor(
+    private val notificationCoordinator: TaskSwitcherNotificationCoordinator,
+    private val featureFlags: FeatureFlags,
+) : CoreStartable {
+
+    override fun start() {
+        if (featureFlags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)) {
+            notificationCoordinator.start()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt
new file mode 100644
index 0000000..22ad07e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher
+
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface MediaProjectionTaskSwitcherModule {
+
+    @Binds fun mediaRepository(impl: MediaProjectionManagerRepository): MediaProjectionRepository
+
+    @Binds fun tasksRepository(impl: ActivityTaskManagerTasksRepository): TasksRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
new file mode 100644
index 0000000..9938f11
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/model/MediaProjectionState.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.data.model
+
+import android.app.TaskInfo
+
+/** Represents the state of media projection. */
+sealed interface MediaProjectionState {
+    object NotProjecting : MediaProjectionState
+    object EntireScreen : MediaProjectionState
+    data class SingleTask(val task: TaskInfo) : MediaProjectionState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
new file mode 100644
index 0000000..492d482
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepository.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager
+import android.app.TaskStackListener
+import android.os.IBinder
+import android.util.Log
+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.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.withContext
+
+/** Implementation of [TasksRepository] that uses [ActivityTaskManager] as the data source. */
+@SysUISingleton
+class ActivityTaskManagerTasksRepository
+@Inject
+constructor(
+    private val activityTaskManager: ActivityTaskManager,
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : TasksRepository {
+
+    override suspend fun findRunningTaskFromWindowContainerToken(
+        windowContainerToken: IBinder
+    ): RunningTaskInfo? =
+        getRunningTasks().firstOrNull { taskInfo ->
+            taskInfo.token.asBinder() == windowContainerToken
+        }
+
+    private suspend fun getRunningTasks(): List<RunningTaskInfo> =
+        withContext(backgroundDispatcher) { activityTaskManager.getTasks(Integer.MAX_VALUE) }
+
+    override val foregroundTask: Flow<RunningTaskInfo> =
+        conflatedCallbackFlow {
+                val listener =
+                    object : TaskStackListener() {
+                        override fun onTaskMovedToFront(taskInfo: RunningTaskInfo) {
+                            Log.d(TAG, "onTaskMovedToFront: $taskInfo")
+                            trySendWithFailureLogging(taskInfo, TAG)
+                        }
+                    }
+                activityTaskManager.registerTaskStackListener(listener)
+                awaitClose { activityTaskManager.unregisterTaskStackListener(listener) }
+            }
+            .shareIn(applicationScope, SharingStarted.Lazily, replay = 1)
+
+    companion object {
+        private const val TAG = "TasksRepository"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
new file mode 100644
index 0000000..6480a47
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Handler
+import android.util.Log
+import android.view.ContentRecordingSession
+import android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY
+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.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class MediaProjectionManagerRepository
+@Inject
+constructor(
+    private val mediaProjectionManager: MediaProjectionManager,
+    @Main private val handler: Handler,
+    @Application private val applicationScope: CoroutineScope,
+    private val tasksRepository: TasksRepository,
+) : MediaProjectionRepository {
+
+    override val mediaProjectionState: Flow<MediaProjectionState> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : MediaProjectionManager.Callback() {
+                        override fun onStart(info: MediaProjectionInfo?) {
+                            Log.d(TAG, "MediaProjectionManager.Callback#onStart")
+                            trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+                        }
+
+                        override fun onStop(info: MediaProjectionInfo?) {
+                            Log.d(TAG, "MediaProjectionManager.Callback#onStop")
+                            trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+                        }
+
+                        override fun onRecordingSessionSet(
+                            info: MediaProjectionInfo,
+                            session: ContentRecordingSession?
+                        ) {
+                            Log.d(TAG, "MediaProjectionManager.Callback#onSessionStarted: $session")
+                            launch { trySendWithFailureLogging(stateForSession(session), TAG) }
+                        }
+                    }
+                mediaProjectionManager.addCallback(callback, handler)
+                awaitClose { mediaProjectionManager.removeCallback(callback) }
+            }
+            .shareIn(scope = applicationScope, started = SharingStarted.Lazily, replay = 1)
+
+    private suspend fun stateForSession(session: ContentRecordingSession?): MediaProjectionState {
+        if (session == null) {
+            return MediaProjectionState.NotProjecting
+        }
+        if (session.contentToRecord == RECORD_CONTENT_DISPLAY || session.tokenToRecord == null) {
+            return MediaProjectionState.EntireScreen
+        }
+        val matchingTask =
+            tasksRepository.findRunningTaskFromWindowContainerToken(
+                checkNotNull(session.tokenToRecord)) ?: return MediaProjectionState.EntireScreen
+        return MediaProjectionState.SingleTask(matchingTask)
+    }
+
+    companion object {
+        private const val TAG = "MediaProjectionMngrRepo"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
new file mode 100644
index 0000000..5bec692
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionRepository.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.data.repository
+
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import kotlinx.coroutines.flow.Flow
+
+/** Represents a repository to retrieve and change data related to media projection. */
+interface MediaProjectionRepository {
+
+    /** Represents the current [MediaProjectionState]. */
+    val mediaProjectionState: Flow<MediaProjectionState>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt
new file mode 100644
index 0000000..544eb6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/NoOpMediaProjectionRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * No-op implementation of [MediaProjectionRepository] that does nothing. Currently used as a
+ * placeholder, while the real implementation is not completed.
+ */
+@SysUISingleton
+class NoOpMediaProjectionRepository @Inject constructor() : MediaProjectionRepository {
+
+    override val mediaProjectionState: Flow<MediaProjectionState> = emptyFlow()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt
new file mode 100644
index 0000000..6a535e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/TasksRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.os.IBinder
+import kotlinx.coroutines.flow.Flow
+
+/** Repository responsible for retrieving data related to running tasks. */
+interface TasksRepository {
+
+    /**
+     * Tries to find a [RunningTaskInfo] with a matching window container token. Returns `null` when
+     * no matching task was found.
+     */
+    suspend fun findRunningTaskFromWindowContainerToken(
+        windowContainerToken: IBinder
+    ): RunningTaskInfo?
+
+    /**
+     * Emits a stream of [RunningTaskInfo] that have been moved to the foreground.
+     *
+     * Note: when subscribing for the first time, it will not immediately emit the current
+     * foreground task. Only after a change in foreground task has occurred.
+     */
+    val foregroundTask: Flow<RunningTaskInfo>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
new file mode 100644
index 0000000..fc5cf7d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractor.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.domain.interactor
+
+import android.app.TaskInfo
+import android.content.Intent
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.TasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Interactor with logic related to task switching in the context of media projection. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class TaskSwitchInteractor
+@Inject
+constructor(
+    mediaProjectionRepository: MediaProjectionRepository,
+    private val tasksRepository: TasksRepository,
+) {
+
+    /**
+     * Emits a stream of changes to the state of task switching, in the context of media projection.
+     */
+    val taskSwitchChanges: Flow<TaskSwitchState> =
+        mediaProjectionRepository.mediaProjectionState.flatMapLatest { projectionState ->
+            Log.d(TAG, "MediaProjectionState -> $projectionState")
+            when (projectionState) {
+                is MediaProjectionState.SingleTask -> {
+                    val projectedTask = projectionState.task
+                    tasksRepository.foregroundTask.map { foregroundTask ->
+                        if (hasForegroundTaskSwitched(projectedTask, foregroundTask)) {
+                            TaskSwitchState.TaskSwitched(projectedTask, foregroundTask)
+                        } else {
+                            TaskSwitchState.TaskUnchanged
+                        }
+                    }
+                }
+                is MediaProjectionState.EntireScreen,
+                is MediaProjectionState.NotProjecting -> {
+                    flowOf(TaskSwitchState.NotProjectingTask)
+                }
+            }
+        }
+
+    /**
+     * Returns whether tasks have been switched.
+     *
+     * Always returns `false` when launcher is in the foreground. The reason is that when going to
+     * recents to switch apps, launcher becomes the new foreground task, and we don't want to show
+     * the notification then.
+     */
+    private fun hasForegroundTaskSwitched(projectedTask: TaskInfo, foregroundTask: TaskInfo) =
+        projectedTask.taskId != foregroundTask.taskId && !foregroundTask.isLauncher
+
+    private val TaskInfo.isLauncher
+        get() =
+            baseIntent.hasCategory(Intent.CATEGORY_HOME) && baseIntent.action == Intent.ACTION_MAIN
+
+    companion object {
+        private const val TAG = "TaskSwitchInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt
new file mode 100644
index 0000000..cd1258e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/domain/model/TaskSwitchState.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.domain.model
+
+import android.app.TaskInfo
+
+/** Represents tha state of task switching in the context of single task media projection. */
+sealed interface TaskSwitchState {
+    /** Currently no task is being projected. */
+    object NotProjectingTask : TaskSwitchState
+    /** The foreground task is the same as the task that is currently being projected. */
+    object TaskUnchanged : TaskSwitchState
+    /** The foreground task is a different one to the task it currently being projected. */
+    data class TaskSwitched(val projectedTask: TaskInfo, val foregroundTask: TaskInfo) :
+        TaskSwitchState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
new file mode 100644
index 0000000..a437139
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinator.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.ui
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Context
+import android.util.Log
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.NotShowing
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState.Showing
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import com.android.systemui.util.NotificationChannels
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.launch
+
+/** Coordinator responsible for showing/hiding the task switcher notification. */
+@SysUISingleton
+class TaskSwitcherNotificationCoordinator
+@Inject
+constructor(
+    private val context: Context,
+    private val notificationManager: NotificationManager,
+    @Application private val applicationScope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    private val viewModel: TaskSwitcherNotificationViewModel,
+) {
+    fun start() {
+        applicationScope.launch {
+            viewModel.uiState.flowOn(mainDispatcher).collect { uiState ->
+                Log.d(TAG, "uiState -> $uiState")
+                when (uiState) {
+                    is Showing -> showNotification()
+                    is NotShowing -> hideNotification()
+                }
+            }
+        }
+    }
+
+    private fun showNotification() {
+        notificationManager.notify(TAG, NOTIFICATION_ID, createNotification())
+    }
+
+    private fun createNotification(): Notification {
+        // TODO(b/286201261): implement actions
+        val actionSwitch =
+            Notification.Action.Builder(
+                    /* icon = */ null,
+                    context.getString(R.string.media_projection_task_switcher_action_switch),
+                    /* intent = */ null
+                )
+                .build()
+
+        val actionBack =
+            Notification.Action.Builder(
+                    /* icon = */ null,
+                    context.getString(R.string.media_projection_task_switcher_action_back),
+                    /* intent = */ null
+                )
+                .build()
+
+        val channel =
+            NotificationChannel(
+                NotificationChannels.HINTS,
+                context.getString(R.string.media_projection_task_switcher_notification_channel),
+                NotificationManager.IMPORTANCE_HIGH
+            )
+        notificationManager.createNotificationChannel(channel)
+        return Notification.Builder(context, channel.id)
+            .setSmallIcon(R.drawable.qs_screen_record_icon_on)
+            .setAutoCancel(true)
+            .setContentText(context.getString(R.string.media_projection_task_switcher_text))
+            .addAction(actionSwitch)
+            .addAction(actionBack)
+            .setPriority(Notification.PRIORITY_HIGH)
+            .setDefaults(Notification.DEFAULT_VIBRATE)
+            .build()
+    }
+
+    private fun hideNotification() {
+        notificationManager.cancel(NOTIFICATION_ID)
+    }
+
+    companion object {
+        private const val TAG = "TaskSwitchNotifCoord"
+        private const val NOTIFICATION_ID = 5566
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt
new file mode 100644
index 0000000..21aee72
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/model/TaskSwitcherNotificationUiState.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.ui.model
+
+import android.app.TaskInfo
+
+/** Represents the UI state for the task switcher notification. */
+sealed interface TaskSwitcherNotificationUiState {
+    /** The notification should not be shown. */
+    object NotShowing : TaskSwitcherNotificationUiState
+    /** The notification should be shown. */
+    data class Showing(
+        val projectedTask: TaskInfo,
+        val foregroundTask: TaskInfo,
+    ) : TaskSwitcherNotificationUiState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
new file mode 100644
index 0000000..d9754d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel
+
+import android.util.Log
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class TaskSwitcherNotificationViewModel @Inject constructor(interactor: TaskSwitchInteractor) {
+
+    val uiState: Flow<TaskSwitcherNotificationUiState> =
+        interactor.taskSwitchChanges.map { taskSwitchChange ->
+            Log.d(TAG, "taskSwitchChange: $taskSwitchChange")
+            when (taskSwitchChange) {
+                is TaskSwitchState.TaskSwitched -> {
+                    TaskSwitcherNotificationUiState.Showing(
+                        projectedTask = taskSwitchChange.projectedTask,
+                        foregroundTask = taskSwitchChange.foregroundTask,
+                    )
+                }
+                is TaskSwitchState.NotProjectingTask,
+                is TaskSwitchState.TaskUnchanged -> {
+                    TaskSwitcherNotificationUiState.NotShowing
+                }
+            }
+        }
+
+    companion object {
+        private const val TAG = "TaskSwitchNotifVM"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index 8d80990..07846b5 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -114,7 +114,7 @@
         pw.print("  mSysUiStateFlags="); pw.println(mFlags);
         pw.println("    " + QuickStepContract.getSystemUiStateString(mFlags));
         pw.print("    backGestureDisabled=");
-        pw.println(QuickStepContract.isBackGestureDisabled(mFlags));
+        pw.println(QuickStepContract.isBackGestureDisabled(mFlags, false /* forTrackpad */));
         pw.print("    assistantGestureDisabled=");
         pw.println(QuickStepContract.isAssistantGestureDisabled(mFlags));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
new file mode 100644
index 0000000..c74a71c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.model
+
+import com.android.systemui.dagger.qualifiers.DisplayId
+
+/**
+ * In-bulk updates multiple flag values and commits the update.
+ *
+ * Example:
+ * ```
+ * sysuiState.updateFlags(
+ *     displayId,
+ *     SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to (sceneKey != SceneKey.Gone),
+ *     SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to (sceneKey == SceneKey.Shade),
+ *     SYSUI_STATE_QUICK_SETTINGS_EXPANDED to (sceneKey == SceneKey.QuickSettings),
+ *     SYSUI_STATE_BOUNCER_SHOWING to (sceneKey == SceneKey.Bouncer),
+ *     SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to (sceneKey == SceneKey.Lockscreen),
+ * )
+ * ```
+ *
+ * You can inject [displayId] by injecting it using:
+ * ```
+ *     @DisplayId private val displayId: Int`,
+ * ```
+ */
+fun SysUiState.updateFlags(
+    @DisplayId displayId: Int,
+    vararg flagValuePairs: Pair<Int, Boolean>,
+) {
+    flagValuePairs.forEach { (flag, enabled) -> setFlag(flag, enabled) }
+    commitUpdate(displayId)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt
deleted file mode 100644
index c48028c..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/data/model/MultiShadeInteractionModel.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.data.model
-
-import com.android.systemui.multishade.shared.model.ShadeId
-
-/** Models the current interaction with one of the shades. */
-data class MultiShadeInteractionModel(
-    /** The ID of the shade that the user is currently interacting with. */
-    val shadeId: ShadeId,
-    /** Whether the interaction is proxied (as in: coming from an external app or different UI). */
-    val isProxied: Boolean,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt
deleted file mode 100644
index 86f0c0d..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/data/remoteproxy/MultiShadeInputProxy.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.data.remoteproxy
-
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import javax.inject.Inject
-import javax.inject.Singleton
-import kotlinx.coroutines.channels.BufferOverflow
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.asSharedFlow
-
-/**
- * Acts as a hub for routing proxied user input into the multi shade system.
- *
- * "Proxied" user input is coming through a proxy; typically from an external app or different UI.
- * In other words: it's not user input that's occurring directly on the shade UI itself. This class
- * is that proxy.
- */
-@Singleton
-class MultiShadeInputProxy @Inject constructor() {
-    private val _proxiedTouch =
-        MutableSharedFlow<ProxiedInputModel>(
-            replay = 1,
-            onBufferOverflow = BufferOverflow.DROP_OLDEST,
-        )
-    val proxiedInput: Flow<ProxiedInputModel> = _proxiedTouch.asSharedFlow()
-
-    fun onProxiedInput(proxiedInput: ProxiedInputModel) {
-        _proxiedTouch.tryEmit(proxiedInput)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt
deleted file mode 100644
index 1172030..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/data/repository/MultiShadeRepository.kt
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.data.repository
-
-import android.content.Context
-import androidx.annotation.FloatRange
-import com.android.systemui.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeConfig
-import com.android.systemui.multishade.shared.model.ShadeId
-import com.android.systemui.multishade.shared.model.ShadeModel
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Encapsulates application state for all shades. */
-@SysUISingleton
-class MultiShadeRepository
-@Inject
-constructor(
-    @Application private val applicationContext: Context,
-    inputProxy: MultiShadeInputProxy,
-) {
-    /**
-     * Remote input coming from sources outside of system UI (for example, swiping down on the
-     * Launcher or from the status bar).
-     */
-    val proxiedInput: Flow<ProxiedInputModel> = inputProxy.proxiedInput
-
-    /** Width of the left-hand side shade, in pixels. */
-    private val leftShadeWidthPx =
-        applicationContext.resources.getDimensionPixelSize(R.dimen.left_shade_width)
-
-    /** Width of the right-hand side shade, in pixels. */
-    private val rightShadeWidthPx =
-        applicationContext.resources.getDimensionPixelSize(R.dimen.right_shade_width)
-
-    /**
-     * The amount that the user must swipe up when the shade is fully expanded to automatically
-     * collapse once the user lets go of the shade. If the user swipes less than this amount, the
-     * shade will automatically revert back to fully expanded once the user stops swiping.
-     *
-     * This is a fraction between `0` and `1`.
-     */
-    private val swipeCollapseThreshold =
-        checkInBounds(applicationContext.resources.getFloat(R.dimen.shade_swipe_collapse_threshold))
-
-    /**
-     * The amount that the user must swipe down when the shade is fully collapsed to automatically
-     * expand once the user lets go of the shade. If the user swipes less than this amount, the
-     * shade will automatically revert back to fully collapsed once the user stops swiping.
-     *
-     * This is a fraction between `0` and `1`.
-     */
-    private val swipeExpandThreshold =
-        checkInBounds(applicationContext.resources.getFloat(R.dimen.shade_swipe_expand_threshold))
-
-    /**
-     * Maximum opacity when the scrim that shows up behind the dual shades is fully visible.
-     *
-     * This is a fraction between `0` and `1`.
-     */
-    private val dualShadeScrimAlpha =
-        checkInBounds(applicationContext.resources.getFloat(R.dimen.dual_shade_scrim_alpha))
-
-    /** The current configuration of the shade system. */
-    val shadeConfig: StateFlow<ShadeConfig> =
-        MutableStateFlow(
-                if (applicationContext.resources.getBoolean(R.bool.dual_shade_enabled)) {
-                    ShadeConfig.DualShadeConfig(
-                        leftShadeWidthPx = leftShadeWidthPx,
-                        rightShadeWidthPx = rightShadeWidthPx,
-                        swipeCollapseThreshold = swipeCollapseThreshold,
-                        swipeExpandThreshold = swipeExpandThreshold,
-                        splitFraction =
-                            applicationContext.resources.getFloat(
-                                R.dimen.dual_shade_split_fraction
-                            ),
-                        scrimAlpha = dualShadeScrimAlpha,
-                    )
-                } else {
-                    ShadeConfig.SingleShadeConfig(
-                        swipeCollapseThreshold = swipeCollapseThreshold,
-                        swipeExpandThreshold = swipeExpandThreshold,
-                    )
-                }
-            )
-            .asStateFlow()
-
-    private val _forceCollapseAll = MutableStateFlow(false)
-    /** Whether all shades should be collapsed. */
-    val forceCollapseAll: StateFlow<Boolean> = _forceCollapseAll.asStateFlow()
-
-    private val _shadeInteraction = MutableStateFlow<MultiShadeInteractionModel?>(null)
-    /** The current shade interaction or `null` if no shade is interacted with currently. */
-    val shadeInteraction: StateFlow<MultiShadeInteractionModel?> = _shadeInteraction.asStateFlow()
-
-    private val stateByShade = mutableMapOf<ShadeId, MutableStateFlow<ShadeModel>>()
-
-    /** The model for the shade with the given ID. */
-    fun getShade(
-        shadeId: ShadeId,
-    ): StateFlow<ShadeModel> {
-        return getMutableShade(shadeId).asStateFlow()
-    }
-
-    /** Sets the expansion amount for the shade with the given ID. */
-    fun setExpansion(
-        shadeId: ShadeId,
-        @FloatRange(from = 0.0, to = 1.0) expansion: Float,
-    ) {
-        getMutableShade(shadeId).let { mutableState ->
-            mutableState.value = mutableState.value.copy(expansion = expansion)
-        }
-    }
-
-    /** Sets whether all shades should be immediately forced to collapse. */
-    fun setForceCollapseAll(isForced: Boolean) {
-        _forceCollapseAll.value = isForced
-    }
-
-    /** Sets the current shade interaction; use `null` if no shade is interacted with currently. */
-    fun setShadeInteraction(shadeInteraction: MultiShadeInteractionModel?) {
-        _shadeInteraction.value = shadeInteraction
-    }
-
-    private fun getMutableShade(id: ShadeId): MutableStateFlow<ShadeModel> {
-        return stateByShade.getOrPut(id) { MutableStateFlow(ShadeModel(id)) }
-    }
-
-    /** Asserts that the given [Float] is in the range of `0` and `1`, inclusive. */
-    private fun checkInBounds(float: Float): Float {
-        check(float in 0f..1f) { "$float isn't between 0 and 1." }
-        return float
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt
deleted file mode 100644
index ebb8639..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.domain.interactor
-
-import androidx.annotation.FloatRange
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.data.repository.MultiShadeRepository
-import com.android.systemui.multishade.shared.math.isZero
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeConfig
-import com.android.systemui.multishade.shared.model.ShadeId
-import com.android.systemui.multishade.shared.model.ShadeModel
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.shareIn
-import kotlinx.coroutines.yield
-
-/** Encapsulates business logic related to interactions with the multi-shade system. */
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class MultiShadeInteractor
-@Inject
-constructor(
-    @Application private val applicationScope: CoroutineScope,
-    private val repository: MultiShadeRepository,
-    private val inputProxy: MultiShadeInputProxy,
-) {
-    /** The current configuration of the shade system. */
-    val shadeConfig: StateFlow<ShadeConfig> = repository.shadeConfig
-
-    /** The expansion of the shade that's most expanded. */
-    val maxShadeExpansion: Flow<Float> =
-        repository.shadeConfig.flatMapLatest { shadeConfig ->
-            combine(allShades(shadeConfig)) { shadeModels ->
-                shadeModels.maxOfOrNull { it.expansion } ?: 0f
-            }
-        }
-
-    /** Whether any shade is expanded, even a little bit. */
-    val isAnyShadeExpanded: Flow<Boolean> =
-        maxShadeExpansion.map { maxExpansion -> !maxExpansion.isZero() }.distinctUntilChanged()
-
-    /**
-     * A _processed_ version of the proxied input flow.
-     *
-     * All internal dependencies on the proxied input flow *must* use this one for two reasons:
-     * 1. It's a [SharedFlow] so we only do the upstream work once, no matter how many usages we
-     *    actually have.
-     * 2. It actually does some preprocessing as the proxied input events stream through, handling
-     *    common things like recording the current state of the system based on incoming input
-     *    events.
-     */
-    private val processedProxiedInput: SharedFlow<ProxiedInputModel> =
-        combine(
-                repository.shadeConfig,
-                repository.proxiedInput.distinctUntilChanged(),
-                ::Pair,
-            )
-            .map { (shadeConfig, proxiedInput) ->
-                if (proxiedInput !is ProxiedInputModel.OnTap) {
-                    // If the user is interacting with any other gesture type (for instance,
-                    // dragging),
-                    // we no longer want to force collapse all shades.
-                    repository.setForceCollapseAll(false)
-                }
-
-                when (proxiedInput) {
-                    is ProxiedInputModel.OnDrag -> {
-                        val affectedShadeId = affectedShadeId(shadeConfig, proxiedInput.xFraction)
-                        // This might be the start of a new drag gesture, let's update our
-                        // application
-                        // state to record that fact.
-                        onUserInteractionStarted(
-                            shadeId = affectedShadeId,
-                            isProxied = true,
-                        )
-                    }
-                    is ProxiedInputModel.OnTap -> {
-                        // Tapping outside any shade collapses all shades. This code path is not hit
-                        // for
-                        // taps that happen _inside_ a shade as that input event is directly applied
-                        // through the UI and is, hence, not a proxied input.
-                        collapseAll()
-                    }
-                    else -> Unit
-                }
-
-                proxiedInput
-            }
-            .shareIn(
-                scope = applicationScope,
-                started = SharingStarted.Eagerly,
-                replay = 1,
-            )
-
-    /** Whether the shade with the given ID should be visible. */
-    fun isVisible(shadeId: ShadeId): Flow<Boolean> {
-        return repository.shadeConfig.map { shadeConfig -> shadeConfig.shadeIds.contains(shadeId) }
-    }
-
-    /** Whether direct user input is allowed on the shade with the given ID. */
-    fun isNonProxiedInputAllowed(shadeId: ShadeId): Flow<Boolean> {
-        return combine(
-                isForceCollapsed(shadeId),
-                repository.shadeInteraction,
-                ::Pair,
-            )
-            .map { (isForceCollapsed, shadeInteraction) ->
-                !isForceCollapsed && shadeInteraction?.isProxied != true
-            }
-    }
-
-    /** Whether the shade with the given ID is forced to collapse. */
-    fun isForceCollapsed(shadeId: ShadeId): Flow<Boolean> {
-        return combine(
-                repository.forceCollapseAll,
-                repository.shadeInteraction.map { it?.shadeId },
-                ::Pair,
-            )
-            .map { (collapseAll, userInteractedShadeIdOrNull) ->
-                val counterpartShadeIdOrNull =
-                    when (shadeId) {
-                        ShadeId.SINGLE -> null
-                        ShadeId.LEFT -> ShadeId.RIGHT
-                        ShadeId.RIGHT -> ShadeId.LEFT
-                    }
-
-                when {
-                    // If all shades have been told to collapse (by a tap outside, for example),
-                    // then this shade is collapsed.
-                    collapseAll -> true
-                    // A shade that doesn't have a counterpart shade cannot be force-collapsed by
-                    // interactions on the counterpart shade.
-                    counterpartShadeIdOrNull == null -> false
-                    // If the current user interaction is on the counterpart shade, then this shade
-                    // should be force-collapsed.
-                    else -> userInteractedShadeIdOrNull == counterpartShadeIdOrNull
-                }
-            }
-    }
-
-    /**
-     * Proxied input affecting the shade with the given ID. This is input coming from sources
-     * outside of system UI (for example, swiping down on the Launcher or from the status bar) or
-     * outside the UI of any shade (for example, the scrim that's shown behind the shades).
-     */
-    fun proxiedInput(shadeId: ShadeId): Flow<ProxiedInputModel?> {
-        return combine(
-                processedProxiedInput,
-                isForceCollapsed(shadeId).distinctUntilChanged(),
-                repository.shadeInteraction,
-                ::Triple,
-            )
-            .map { (proxiedInput, isForceCollapsed, shadeInteraction) ->
-                when {
-                    // If the shade is force-collapsed, we ignored proxied input on it.
-                    isForceCollapsed -> null
-                    // If the proxied input does not belong to this shade, ignore it.
-                    shadeInteraction?.shadeId != shadeId -> null
-                    // If there is ongoing non-proxied user input on any shade, ignore the
-                    // proxied input.
-                    !shadeInteraction.isProxied -> null
-                    // Otherwise, send the proxied input downstream.
-                    else -> proxiedInput
-                }
-            }
-            .onEach { proxiedInput ->
-                // We use yield() to make sure that the following block of code happens _after_
-                // downstream collectors had a chance to process the proxied input. Otherwise, we
-                // might change our state to clear the current UserInteraction _before_ those
-                // downstream collectors get a chance to process the proxied input, which will make
-                // them ignore it (since they ignore proxied input when the current user interaction
-                // doesn't match their shade).
-                yield()
-
-                if (
-                    proxiedInput is ProxiedInputModel.OnDragEnd ||
-                        proxiedInput is ProxiedInputModel.OnDragCancel
-                ) {
-                    onUserInteractionEnded(shadeId = shadeId, isProxied = true)
-                }
-            }
-    }
-
-    /** Sets the expansion amount for the shade with the given ID. */
-    fun setExpansion(
-        shadeId: ShadeId,
-        @FloatRange(from = 0.0, to = 1.0) expansion: Float,
-    ) {
-        repository.setExpansion(shadeId, expansion)
-    }
-
-    /** Collapses all shades. */
-    fun collapseAll() {
-        repository.setForceCollapseAll(true)
-    }
-
-    /**
-     * Notifies that a new non-proxied interaction may have started. Safe to call multiple times for
-     * the same interaction as it won't overwrite an existing interaction.
-     *
-     * Existing interactions can be cleared by calling [onUserInteractionEnded].
-     */
-    fun onUserInteractionStarted(shadeId: ShadeId) {
-        onUserInteractionStarted(
-            shadeId = shadeId,
-            isProxied = false,
-        )
-    }
-
-    /**
-     * Notifies that the current non-proxied interaction has ended.
-     *
-     * Safe to call multiple times, even if there's no current interaction or even if the current
-     * interaction doesn't belong to the given shade or is proxied as the code is a no-op unless
-     * there's a match between the parameters and the current interaction.
-     */
-    fun onUserInteractionEnded(
-        shadeId: ShadeId,
-    ) {
-        onUserInteractionEnded(
-            shadeId = shadeId,
-            isProxied = false,
-        )
-    }
-
-    fun sendProxiedInput(proxiedInput: ProxiedInputModel) {
-        inputProxy.onProxiedInput(proxiedInput)
-    }
-
-    /**
-     * Notifies that a new interaction may have started. Safe to call multiple times for the same
-     * interaction as it won't overwrite an existing interaction.
-     *
-     * Existing interactions can be cleared by calling [onUserInteractionEnded].
-     */
-    private fun onUserInteractionStarted(
-        shadeId: ShadeId,
-        isProxied: Boolean,
-    ) {
-        if (repository.shadeInteraction.value != null) {
-            return
-        }
-
-        repository.setShadeInteraction(
-            MultiShadeInteractionModel(
-                shadeId = shadeId,
-                isProxied = isProxied,
-            )
-        )
-    }
-
-    /**
-     * Notifies that the current interaction has ended.
-     *
-     * Safe to call multiple times, even if there's no current interaction or even if the current
-     * interaction doesn't belong to the given shade or [isProxied] value as the code is a no-op
-     * unless there's a match between the parameters and the current interaction.
-     */
-    private fun onUserInteractionEnded(
-        shadeId: ShadeId,
-        isProxied: Boolean,
-    ) {
-        repository.shadeInteraction.value?.let { (interactionShadeId, isInteractionProxied) ->
-            if (shadeId == interactionShadeId && isProxied == isInteractionProxied) {
-                repository.setShadeInteraction(null)
-            }
-        }
-    }
-
-    /**
-     * Returns the ID of the shade that's affected by user input at a given coordinate.
-     *
-     * @param config The shade configuration being used.
-     * @param xFraction The horizontal position of the user input as a fraction along the width of
-     *   its container where `0` is all the way to the left and `1` is all the way to the right.
-     */
-    private fun affectedShadeId(
-        config: ShadeConfig,
-        @FloatRange(from = 0.0, to = 1.0) xFraction: Float,
-    ): ShadeId {
-        return if (config is ShadeConfig.DualShadeConfig) {
-            if (xFraction <= config.splitFraction) {
-                ShadeId.LEFT
-            } else {
-                ShadeId.RIGHT
-            }
-        } else {
-            ShadeId.SINGLE
-        }
-    }
-
-    /** Returns the list of flows of all the shades in the given configuration. */
-    private fun allShades(
-        config: ShadeConfig,
-    ): List<Flow<ShadeModel>> {
-        return config.shadeIds.map { shadeId -> repository.getShade(shadeId) }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
deleted file mode 100644
index 1894bc4..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.multishade.domain.interactor
-
-import android.content.Context
-import android.view.MotionEvent
-import android.view.ViewConfiguration
-import com.android.systemui.classifier.Classifier
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.multishade.shared.math.isZero
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.shade.ShadeController
-import javax.inject.Inject
-import kotlin.math.abs
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-
-/**
- * Encapsulates business logic to handle [MotionEvent]-based user input.
- *
- * This class is meant purely for the legacy `View`-based system to be able to pass `MotionEvent`s
- * into the newer multi-shade framework for processing.
- */
-@SysUISingleton
-class MultiShadeMotionEventInteractor
-@Inject
-constructor(
-    @Application private val applicationContext: Context,
-    @Application private val applicationScope: CoroutineScope,
-    private val multiShadeInteractor: MultiShadeInteractor,
-    featureFlags: FeatureFlags,
-    keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    private val falsingManager: FalsingManager,
-    private val shadeController: ShadeController,
-) {
-    init {
-        if (featureFlags.isEnabled(Flags.DUAL_SHADE)) {
-            applicationScope.launch {
-                multiShadeInteractor.isAnyShadeExpanded.collect {
-                    if (!it && !shadeController.isKeyguard) {
-                        shadeController.makeExpandedInvisible()
-                    } else {
-                        shadeController.makeExpandedVisible(false)
-                    }
-                }
-            }
-        }
-    }
-
-    private val isAnyShadeExpanded: StateFlow<Boolean> =
-        multiShadeInteractor.isAnyShadeExpanded.stateIn(
-            scope = applicationScope,
-            started = SharingStarted.Eagerly,
-            initialValue = false,
-        )
-
-    private val isBouncerShowing: StateFlow<Boolean> =
-        keyguardTransitionInteractor
-            .transitionValue(state = KeyguardState.PRIMARY_BOUNCER)
-            .map { !it.isZero() }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.Eagerly,
-                initialValue = false,
-            )
-
-    private var interactionState: InteractionState? = null
-
-    /**
-     * Returns `true` if the given [MotionEvent] and the rest of events in this gesture should be
-     * passed to this interactor's [onTouchEvent] method.
-     *
-     * Note: the caller should continue to pass [MotionEvent] instances into this method, even if it
-     * returns `false` as the gesture may be intercepted mid-stream.
-     */
-    fun shouldIntercept(event: MotionEvent): Boolean {
-        if (isAnyShadeExpanded.value) {
-            // If any shade is expanded, we assume that touch handling outside the shades is handled
-            // by the scrim that appears behind the shades. No need to intercept anything here.
-            return false
-        }
-
-        if (isBouncerShowing.value) {
-            return false
-        }
-
-        return when (event.actionMasked) {
-            MotionEvent.ACTION_DOWN -> {
-                // Record where the pointer was placed and which pointer it was.
-                interactionState =
-                    InteractionState(
-                        initialX = event.x,
-                        initialY = event.y,
-                        currentY = event.y,
-                        pointerId = event.getPointerId(0),
-                        isDraggingHorizontally = false,
-                        isDraggingShade = false,
-                    )
-
-                false
-            }
-            MotionEvent.ACTION_MOVE -> {
-                onMove(event)
-
-                // We want to intercept the rest of the gesture if we're dragging the shade.
-                isDraggingShade()
-            }
-            MotionEvent.ACTION_UP,
-            MotionEvent.ACTION_CANCEL ->
-                // Make sure that we intercept the up or cancel if we're dragging the shade, to
-                // handle drag end or cancel.
-                isDraggingShade()
-            else -> false
-        }
-    }
-
-    /**
-     * Notifies that a [MotionEvent] in a series of events of a gesture that was intercepted due to
-     * the result of [shouldIntercept] has been received.
-     *
-     * @param event The [MotionEvent] to handle.
-     * @param viewWidthPx The width of the view, in pixels.
-     * @return `true` if the event was consumed, `false` otherwise.
-     */
-    fun onTouchEvent(event: MotionEvent, viewWidthPx: Int): Boolean {
-        return when (event.actionMasked) {
-            MotionEvent.ACTION_MOVE -> {
-                interactionState?.let {
-                    if (it.isDraggingShade) {
-                        val pointerIndex = event.findPointerIndex(it.pointerId)
-                        val previousY = it.currentY
-                        val currentY = event.getY(pointerIndex)
-                        interactionState = it.copy(currentY = currentY)
-
-                        val yDragAmountPx = currentY - previousY
-
-                        if (yDragAmountPx != 0f) {
-                            multiShadeInteractor.sendProxiedInput(
-                                ProxiedInputModel.OnDrag(
-                                    xFraction = event.x / viewWidthPx,
-                                    yDragAmountPx = yDragAmountPx,
-                                )
-                            )
-                        }
-                        true
-                    } else {
-                        onMove(event)
-                        isDraggingShade()
-                    }
-                }
-                    ?: false
-            }
-            MotionEvent.ACTION_UP -> {
-                if (isDraggingShade()) {
-                    // We finished dragging the shade. Record that so the multi-shade framework can
-                    // issue a fling, if the velocity reached in the drag was high enough, for
-                    // example.
-                    multiShadeInteractor.sendProxiedInput(ProxiedInputModel.OnDragEnd)
-
-                    if (falsingManager.isFalseTouch(Classifier.SHADE_DRAG)) {
-                        multiShadeInteractor.collapseAll()
-                    }
-                }
-
-                interactionState = null
-                true
-            }
-            MotionEvent.ACTION_POINTER_UP -> {
-                val removedPointerId = event.getPointerId(event.actionIndex)
-                if (removedPointerId == interactionState?.pointerId && event.pointerCount > 1) {
-                    // We removed the original pointer but there must be another pointer because the
-                    // gesture is still ongoing. Let's switch to that pointer.
-                    interactionState =
-                        event.firstUnremovedPointerId(removedPointerId)?.let { replacementPointerId
-                            ->
-                            interactionState?.copy(
-                                pointerId = replacementPointerId,
-                                // We want to update the currentY of our state so that the
-                                // transition to the next pointer doesn't report a big jump between
-                                // the Y coordinate of the removed pointer and the Y coordinate of
-                                // the replacement pointer.
-                                currentY = event.getY(replacementPointerId),
-                            )
-                        }
-                }
-                true
-            }
-            MotionEvent.ACTION_CANCEL -> {
-                if (isDraggingShade()) {
-                    // Our drag gesture was canceled by the system. This happens primarily in one of
-                    // two occasions: (a) the parent view has decided to intercept the gesture
-                    // itself and/or route it to a different child view or (b) the pointer has
-                    // traveled beyond the bounds of our view and/or the touch display. Either way,
-                    // we pass the cancellation event to the multi-shade framework to record it.
-                    // Doing that allows the multi-shade framework to know that the gesture ended to
-                    // allow new gestures to be accepted.
-                    multiShadeInteractor.sendProxiedInput(ProxiedInputModel.OnDragCancel)
-
-                    if (falsingManager.isFalseTouch(Classifier.SHADE_DRAG)) {
-                        multiShadeInteractor.collapseAll()
-                    }
-                }
-
-                interactionState = null
-                true
-            }
-            else -> false
-        }
-    }
-
-    /**
-     * Handles [MotionEvent.ACTION_MOVE] and sets whether or not we are dragging shade in our
-     * current interaction
-     *
-     * @param event The [MotionEvent] to handle.
-     */
-    private fun onMove(event: MotionEvent) {
-        interactionState?.let {
-            val pointerIndex = event.findPointerIndex(it.pointerId)
-            val currentX = event.getX(pointerIndex)
-            val currentY = event.getY(pointerIndex)
-            if (!it.isDraggingHorizontally && !it.isDraggingShade) {
-                val xDistanceTravelled = currentX - it.initialX
-                val yDistanceTravelled = currentY - it.initialY
-                val touchSlop = ViewConfiguration.get(applicationContext).scaledTouchSlop
-                interactionState =
-                    when {
-                        yDistanceTravelled > touchSlop -> it.copy(isDraggingShade = true)
-                        abs(xDistanceTravelled) > touchSlop ->
-                            it.copy(isDraggingHorizontally = true)
-                        else -> interactionState
-                    }
-            }
-        }
-    }
-
-    private data class InteractionState(
-        val initialX: Float,
-        val initialY: Float,
-        val currentY: Float,
-        val pointerId: Int,
-        /** Whether the current gesture is dragging horizontally. */
-        val isDraggingHorizontally: Boolean,
-        /** Whether the current gesture is dragging the shade vertically. */
-        val isDraggingShade: Boolean,
-    )
-
-    private fun isDraggingShade(): Boolean {
-        return interactionState?.isDraggingShade ?: false
-    }
-
-    /**
-     * Returns the index of the first pointer that is not [removedPointerId] or `null`, if there is
-     * no other pointer.
-     */
-    private fun MotionEvent.firstUnremovedPointerId(removedPointerId: Int): Int? {
-        return (0 until pointerCount)
-            .firstOrNull { pointerIndex ->
-                val pointerId = getPointerId(pointerIndex)
-                pointerId != removedPointerId
-            }
-            ?.let { pointerIndex -> getPointerId(pointerIndex) }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/math/Math.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/math/Math.kt
deleted file mode 100644
index c2eaf72..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/shared/math/Math.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.multishade.shared.math
-
-import androidx.annotation.VisibleForTesting
-import kotlin.math.abs
-
-/** Returns `true` if this [Float] is within [epsilon] of `0`. */
-fun Float.isZero(epsilon: Float = EPSILON): Boolean {
-    return abs(this) < epsilon
-}
-
-@VisibleForTesting private const val EPSILON = 0.0001f
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt
deleted file mode 100644
index ee1dd65..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ProxiedInputModel.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.shared.model
-
-import androidx.annotation.FloatRange
-
-/**
- * Models a part of an ongoing proxied user input gesture.
- *
- * "Proxied" user input is coming through a proxy; typically from an external app or different UI.
- * In other words: it's not user input that's occurring directly on the shade UI itself.
- */
-sealed class ProxiedInputModel {
-    /** The user is dragging their pointer. */
-    data class OnDrag(
-        /**
-         * The relative position of the pointer as a fraction of its container width where `0` is
-         * all the way to the left and `1` is all the way to the right.
-         */
-        @FloatRange(from = 0.0, to = 1.0) val xFraction: Float,
-        /** The amount that the pointer was dragged, in pixels. */
-        val yDragAmountPx: Float,
-    ) : ProxiedInputModel()
-
-    /** The user finished dragging by lifting up their pointer. */
-    object OnDragEnd : ProxiedInputModel()
-
-    /**
-     * The drag gesture has been canceled. Usually because the pointer exited the draggable area.
-     */
-    object OnDragCancel : ProxiedInputModel()
-
-    /** The user has tapped (clicked). */
-    object OnTap : ProxiedInputModel()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt
deleted file mode 100644
index a4cd35c..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeConfig.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.shared.model
-
-import androidx.annotation.FloatRange
-
-/** Enumerates the various possible configurations of the shade system. */
-sealed class ShadeConfig(
-
-    /** IDs of the shade(s) in this configuration. */
-    open val shadeIds: List<ShadeId>,
-
-    /**
-     * The amount that the user must swipe up when the shade is fully expanded to automatically
-     * collapse once the user lets go of the shade. If the user swipes less than this amount, the
-     * shade will automatically revert back to fully expanded once the user stops swiping.
-     */
-    @FloatRange(from = 0.0, to = 1.0) open val swipeCollapseThreshold: Float,
-
-    /**
-     * The amount that the user must swipe down when the shade is fully collapsed to automatically
-     * expand once the user lets go of the shade. If the user swipes less than this amount, the
-     * shade will automatically revert back to fully collapsed once the user stops swiping.
-     */
-    @FloatRange(from = 0.0, to = 1.0) open val swipeExpandThreshold: Float,
-) {
-
-    /** There is a single shade. */
-    data class SingleShadeConfig(
-        @FloatRange(from = 0.0, to = 1.0) override val swipeCollapseThreshold: Float,
-        @FloatRange(from = 0.0, to = 1.0) override val swipeExpandThreshold: Float,
-    ) :
-        ShadeConfig(
-            shadeIds = listOf(ShadeId.SINGLE),
-            swipeCollapseThreshold = swipeCollapseThreshold,
-            swipeExpandThreshold = swipeExpandThreshold,
-        )
-
-    /** There are two shades arranged side-by-side. */
-    data class DualShadeConfig(
-        /** Width of the left-hand side shade. */
-        val leftShadeWidthPx: Int,
-        /** Width of the right-hand side shade. */
-        val rightShadeWidthPx: Int,
-        @FloatRange(from = 0.0, to = 1.0) override val swipeCollapseThreshold: Float,
-        @FloatRange(from = 0.0, to = 1.0) override val swipeExpandThreshold: Float,
-        /**
-         * The position of the "split" between interaction areas for each of the shades, as a
-         * fraction of the width of the container.
-         *
-         * Interactions that occur on the start-side (left-hand side in left-to-right languages like
-         * English) affect the start-side shade. Interactions that occur on the end-side (right-hand
-         * side in left-to-right languages like English) affect the end-side shade.
-         */
-        @FloatRange(from = 0.0, to = 1.0) val splitFraction: Float,
-        /** Maximum opacity when the scrim that shows up behind the dual shades is fully visible. */
-        @FloatRange(from = 0.0, to = 1.0) val scrimAlpha: Float,
-    ) :
-        ShadeConfig(
-            shadeIds = listOf(ShadeId.LEFT, ShadeId.RIGHT),
-            swipeCollapseThreshold = swipeCollapseThreshold,
-            swipeExpandThreshold = swipeExpandThreshold,
-        )
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt
deleted file mode 100644
index 9e02657..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeId.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.shared.model
-
-/** Enumerates all known shade IDs. */
-enum class ShadeId {
-    /** ID of the shade on the left in dual shade configurations. */
-    LEFT,
-    /** ID of the shade on the right in dual shade configurations. */
-    RIGHT,
-    /** ID of the single shade in single shade configurations. */
-    SINGLE,
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
deleted file mode 100644
index 49ac64c..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/shared/model/ShadeModel.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.shared.model
-
-import androidx.annotation.FloatRange
-
-/** Models the current state of a shade. */
-data class ShadeModel(
-    val id: ShadeId,
-    @FloatRange(from = 0.0, to = 1.0) val expansion: Float = 0f,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt
deleted file mode 100644
index aecec39..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.ui.view
-
-import android.content.Context
-import android.util.AttributeSet
-import android.widget.FrameLayout
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.compose.ComposeFacade
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
-import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
-import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.launch
-
-/**
- * View that hosts the multi-shade system and acts as glue between legacy code and the
- * implementation.
- */
-class MultiShadeView(
-    context: Context,
-    attrs: AttributeSet?,
-) :
-    FrameLayout(
-        context,
-        attrs,
-    ) {
-
-    fun init(
-        interactor: MultiShadeInteractor,
-        clock: SystemClock,
-    ) {
-        repeatWhenAttached {
-            lifecycleScope.launch {
-                repeatOnLifecycle(Lifecycle.State.CREATED) {
-                    addView(
-                        ComposeFacade.createMultiShadeView(
-                            context = context,
-                            viewModel =
-                                MultiShadeViewModel(
-                                    viewModelScope = this,
-                                    interactor = interactor,
-                                ),
-                            clock = clock,
-                        )
-                    )
-                }
-
-                // Here when destroyed.
-                removeAllViews()
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt
deleted file mode 100644
index ed92c54..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.ui.viewmodel
-
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeConfig
-import com.android.systemui.multishade.shared.model.ShadeId
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-/** Models UI state for UI that supports multi (or single) shade. */
-@OptIn(ExperimentalCoroutinesApi::class)
-class MultiShadeViewModel(
-    viewModelScope: CoroutineScope,
-    private val interactor: MultiShadeInteractor,
-) {
-    /** Models UI state for the single shade. */
-    val singleShade =
-        ShadeViewModel(
-            viewModelScope,
-            ShadeId.SINGLE,
-            interactor,
-        )
-
-    /** Models UI state for the shade on the left-hand side. */
-    val leftShade =
-        ShadeViewModel(
-            viewModelScope,
-            ShadeId.LEFT,
-            interactor,
-        )
-
-    /** Models UI state for the shade on the right-hand side. */
-    val rightShade =
-        ShadeViewModel(
-            viewModelScope,
-            ShadeId.RIGHT,
-            interactor,
-        )
-
-    /** The amount of alpha that the scrim should have. This is a value between `0` and `1`. */
-    val scrimAlpha: StateFlow<Float> =
-        combine(
-                interactor.maxShadeExpansion,
-                interactor.shadeConfig
-                    .map { it as? ShadeConfig.DualShadeConfig }
-                    .map { dualShadeConfigOrNull -> dualShadeConfigOrNull?.scrimAlpha ?: 0f },
-                ::Pair,
-            )
-            .map { (anyShadeExpansion, scrimAlpha) ->
-                (anyShadeExpansion * scrimAlpha).coerceIn(0f, 1f)
-            }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = 0f,
-            )
-
-    /** Whether the scrim should accept touch events. */
-    val isScrimEnabled: StateFlow<Boolean> =
-        interactor.shadeConfig
-            .flatMapLatest { shadeConfig ->
-                when (shadeConfig) {
-                    // In the dual shade configuration, the scrim is enabled when the expansion is
-                    // greater than zero on any one of the shades.
-                    is ShadeConfig.DualShadeConfig -> interactor.isAnyShadeExpanded
-                    // No scrim in the single shade configuration.
-                    is ShadeConfig.SingleShadeConfig -> flowOf(false)
-                }
-            }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = false,
-            )
-
-    /** Notifies that the scrim has been touched. */
-    fun onScrimTouched(proxiedInput: ProxiedInputModel) {
-        if (!isScrimEnabled.value) {
-            return
-        }
-
-        interactor.sendProxiedInput(proxiedInput)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt
deleted file mode 100644
index e828dbd..0000000
--- a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModel.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.ui.viewmodel
-
-import androidx.annotation.FloatRange
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeConfig
-import com.android.systemui.multishade.shared.model.ShadeId
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-/** Models UI state for a single shade. */
-class ShadeViewModel(
-    viewModelScope: CoroutineScope,
-    private val shadeId: ShadeId,
-    private val interactor: MultiShadeInteractor,
-) {
-    /** Whether the shade is visible. */
-    val isVisible: StateFlow<Boolean> =
-        interactor
-            .isVisible(shadeId)
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = false,
-            )
-
-    /** Whether swiping on the shade UI is currently enabled. */
-    val isSwipingEnabled: StateFlow<Boolean> =
-        interactor
-            .isNonProxiedInputAllowed(shadeId)
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = false,
-            )
-
-    /** Whether the shade must be collapsed immediately. */
-    val isForceCollapsed: Flow<Boolean> =
-        interactor.isForceCollapsed(shadeId).distinctUntilChanged()
-
-    /** The width of the shade. */
-    val width: StateFlow<Size> =
-        interactor.shadeConfig
-            .map { shadeWidth(it) }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = shadeWidth(interactor.shadeConfig.value),
-            )
-
-    /**
-     * The amount that the user must swipe up when the shade is fully expanded to automatically
-     * collapse once the user lets go of the shade. If the user swipes less than this amount, the
-     * shade will automatically revert back to fully expanded once the user stops swiping.
-     */
-    val swipeCollapseThreshold: StateFlow<Float> =
-        interactor.shadeConfig
-            .map { it.swipeCollapseThreshold }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = interactor.shadeConfig.value.swipeCollapseThreshold,
-            )
-
-    /**
-     * The amount that the user must swipe down when the shade is fully collapsed to automatically
-     * expand once the user lets go of the shade. If the user swipes less than this amount, the
-     * shade will automatically revert back to fully collapsed once the user stops swiping.
-     */
-    val swipeExpandThreshold: StateFlow<Float> =
-        interactor.shadeConfig
-            .map { it.swipeExpandThreshold }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = interactor.shadeConfig.value.swipeExpandThreshold,
-            )
-
-    /**
-     * Proxied input affecting the shade. This is input coming from sources outside of system UI
-     * (for example, swiping down on the Launcher or from the status bar) or outside the UI of any
-     * shade (for example, the scrim that's shown behind the shades).
-     */
-    val proxiedInput: Flow<ProxiedInputModel?> =
-        interactor
-            .proxiedInput(shadeId)
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = null,
-            )
-
-    /** Notifies that the expansion amount for the shade has changed. */
-    fun onExpansionChanged(
-        expansion: Float,
-    ) {
-        interactor.setExpansion(shadeId, expansion.coerceIn(0f, 1f))
-    }
-
-    /** Notifies that a drag gesture has started. */
-    fun onDragStarted() {
-        interactor.onUserInteractionStarted(shadeId)
-    }
-
-    /** Notifies that a drag gesture has ended. */
-    fun onDragEnded() {
-        interactor.onUserInteractionEnded(shadeId = shadeId)
-    }
-
-    private fun shadeWidth(shadeConfig: ShadeConfig): Size {
-        return when (shadeId) {
-            ShadeId.LEFT ->
-                Size.Pixels((shadeConfig as? ShadeConfig.DualShadeConfig)?.leftShadeWidthPx ?: 0)
-            ShadeId.RIGHT ->
-                Size.Pixels((shadeConfig as? ShadeConfig.DualShadeConfig)?.rightShadeWidthPx ?: 0)
-            ShadeId.SINGLE -> Size.Fraction(1f)
-        }
-    }
-
-    sealed class Size {
-        data class Fraction(
-            @FloatRange(from = 0.0, to = 1.0) val fraction: Float,
-        ) : Size()
-        data class Pixels(
-            val pixels: Int,
-        ) : Size()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 1da8718..8225c47 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -51,7 +51,6 @@
 import android.view.IWindowManager;
 import android.view.View;
 import android.view.WindowInsets;
-import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.NonNull;
@@ -69,10 +68,13 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -80,8 +82,6 @@
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /**
  * Extracts shared elements between navbar and taskbar delegate to de-dupe logic and help them
  * experience the joys of friendship.
@@ -111,6 +111,7 @@
     private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
     private final List<NavbarTaskbarStateUpdater> mStateListeners = new ArrayList<>();
     private final Context mContext;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final CommandQueue mCommandQueue;
     private final ContentResolver mContentResolver;
     private final EdgeBackGestureHandler mEdgeBackGestureHandler;
@@ -182,9 +183,11 @@
             IWindowManager wm,
             UserTracker userTracker,
             DisplayTracker displayTracker,
+            NotificationShadeWindowController notificationShadeWindowController,
             DumpManager dumpManager,
             CommandQueue commandQueue) {
         mContext = context;
+        mNotificationShadeWindowController = notificationShadeWindowController;
         mCommandQueue = commandQueue;
         mContentResolver = mContext.getContentResolver();
         mAccessibilityManager = accessibilityManager;
@@ -460,11 +463,7 @@
      * {@link InputMethodService} and the keyguard states.
      */
     public boolean isImeShown(int vis) {
-        View shadeWindowView = null;
-        if (mCentralSurfacesOptionalLazy.get().isPresent()) {
-            shadeWindowView =
-                    mCentralSurfacesOptionalLazy.get().get().getNotificationShadeWindowView();
-        }
+        View shadeWindowView =  mNotificationShadeWindowController.getWindowRootView();
         boolean isKeyguardShowing = mKeyguardStateController.isShowing();
         boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow()
                 && shadeWindowView.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 682335e..e134f7c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -133,6 +133,7 @@
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.rotation.RotationButton;
@@ -199,6 +200,7 @@
     private final SysUiState mSysUiFlagsContainer;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     private final ShadeController mShadeController;
+    private final ShadeViewController mShadeViewController;
     private final NotificationRemoteInputManager mNotificationRemoteInputManager;
     private final OverviewProxyService mOverviewProxyService;
     private final NavigationModeController mNavigationModeController;
@@ -523,6 +525,7 @@
     @Inject
     NavigationBar(
             NavigationBarView navigationBarView,
+            ShadeController shadeController,
             NavigationBarFrame navigationBarFrame,
             @Nullable Bundle savedState,
             @DisplayId Context context,
@@ -541,7 +544,7 @@
             Optional<Pip> pipOptional,
             Optional<Recents> recentsOptional,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
-            ShadeController shadeController,
+            ShadeViewController shadeViewController,
             NotificationRemoteInputManager notificationRemoteInputManager,
             NotificationShadeDepthController notificationShadeDepthController,
             @Main Handler mainHandler,
@@ -577,6 +580,7 @@
         mSysUiFlagsContainer = sysUiFlagsContainer;
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
         mShadeController = shadeController;
+        mShadeViewController = shadeViewController;
         mNotificationRemoteInputManager = notificationRemoteInputManager;
         mOverviewProxyService = overviewProxyService;
         mNavigationModeController = navigationModeController;
@@ -739,8 +743,7 @@
         final Display display = mView.getDisplay();
         mView.setComponents(mRecentsOptional);
         if (mCentralSurfacesOptionalLazy.get().isPresent()) {
-            mView.setComponents(
-                    mCentralSurfacesOptionalLazy.get().get().getShadeViewController());
+            mView.setComponents(mShadeViewController);
         }
         mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
         mView.setOnVerticalChangedListener(this::onVerticalChanged);
@@ -1341,9 +1344,10 @@
     }
 
     private void onVerticalChanged(boolean isVertical) {
-        Optional<CentralSurfaces> cs = mCentralSurfacesOptionalLazy.get();
-        if (cs.isPresent() && cs.get().getShadeViewController() != null) {
-            cs.get().getShadeViewController().setQsScrimEnabled(!isVertical);
+        // This check can probably be safely removed. It only remained to reduce regression
+        // risk for a broad change that removed the CentralSurfaces reference in the if block
+        if (mCentralSurfacesOptionalLazy.get().isPresent()) {
+            mShadeViewController.setQsScrimEnabled(!isVertical);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
index 63276fe..99daf36 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
@@ -28,18 +28,21 @@
 import android.os.PatternMatcher;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -64,22 +67,21 @@
     private Context mCurrentUserContext;
     private final IOverlayManager mOverlayManager;
     private final Executor mUiBgExecutor;
+    private final UserTracker mUserTracker;
 
     private ArrayList<ModeChangedListener> mListeners = new ArrayList<>();
 
-    private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback =
-            new DeviceProvisionedController.DeviceProvisionedListener() {
-                @Override
-                public void onUserSwitched() {
-                    if (DEBUG) {
-                        Log.d(TAG, "onUserSwitched: "
-                                + ActivityManagerWrapper.getInstance().getCurrentUserId());
-                    }
+    private final UserTracker.Callback mUserTrackerCallback = new UserTracker.Callback() {
+        @Override
+        public void onUserChanged(int newUser, @NonNull Context userContext) {
+            if (DEBUG) {
+                Log.d(TAG, "onUserChanged: "
+                        + newUser);
+            }
 
-                    // Update the nav mode for the current user
-                    updateCurrentInteractionMode(true /* notify */);
-                }
-            };
+            updateCurrentInteractionMode(true /* notify */);
+        }
+    };
 
     // The primary user SysUI process doesn't get AppInfo changes from overlay package changes for
     // the secondary user (b/158613864), so we need to update the interaction mode here as well
@@ -97,19 +99,20 @@
 
     @Inject
     public NavigationModeController(Context context,
-            DeviceProvisionedController deviceProvisionedController,
             ConfigurationController configurationController,
+            UserTracker userTracker,
+            @Main Executor mainExecutor,
             @UiBackground Executor uiBgExecutor,
             DumpManager dumpManager) {
         mContext = context;
         mCurrentUserContext = context;
+        mUserTracker = userTracker;
+        mUserTracker.addCallback(mUserTrackerCallback, mainExecutor);
         mOverlayManager = IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
         mUiBgExecutor = uiBgExecutor;
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
 
-        deviceProvisionedController.addCallback(mDeviceProvisionedCallback);
-
         IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
         overlayFilter.addDataScheme("package");
         overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
@@ -129,6 +132,7 @@
     }
 
     public void updateCurrentInteractionMode(boolean notify) {
+        Trace.beginSection("NMC#updateCurrentInteractionMode");
         mCurrentUserContext = getCurrentUserContext();
         int mode = getCurrentInteractionMode(mCurrentUserContext);
         mUiBgExecutor.execute(() ->
@@ -144,6 +148,7 @@
                 mListeners.get(i).onNavigationModeChanged(mode);
             }
         }
+        Trace.endSection();
     }
 
     public int addListener(ModeChangedListener listener) {
@@ -171,7 +176,7 @@
     }
 
     public Context getCurrentUserContext() {
-        int userId = ActivityManagerWrapper.getInstance().getCurrentUserId();
+        int userId = mUserTracker.getUserId();
         if (DEBUG) {
             Log.d(TAG, "getCurrentUserContext: contextUser=" + mContext.getUserId()
                     + " currentUser=" + userId);
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 77e2847..c4749e0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -26,6 +26,7 @@
 import android.util.Log
 import android.util.MathUtils
 import android.view.Gravity
+import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
 import android.view.VelocityTracker
 import android.view.ViewConfiguration
@@ -36,6 +37,8 @@
 import androidx.dynamicanimation.animation.DynamicAnimation
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.plugins.NavigationEdgeBackPlugin
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -76,27 +79,24 @@
 private const val POP_ON_INACTIVE_VELOCITY = -1.5f
 
 internal val VIBRATE_ACTIVATED_EFFECT =
-        VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
+    VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
 
 internal val VIBRATE_DEACTIVATED_EFFECT =
-        VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)
+    VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)
 
 private const val DEBUG = false
 
-class BackPanelController internal constructor(
-        context: Context,
-        private val windowManager: WindowManager,
-        private val viewConfiguration: ViewConfiguration,
-        @Main private val mainHandler: Handler,
-        private val vibratorHelper: VibratorHelper,
-        private val configurationController: ConfigurationController,
-        private val latencyTracker: LatencyTracker
-) : ViewController<BackPanel>(
-        BackPanel(
-                context,
-                latencyTracker
-        )
-), NavigationEdgeBackPlugin {
+class BackPanelController
+internal constructor(
+    context: Context,
+    private val windowManager: WindowManager,
+    private val viewConfiguration: ViewConfiguration,
+    @Main private val mainHandler: Handler,
+    private val vibratorHelper: VibratorHelper,
+    private val configurationController: ConfigurationController,
+    private val latencyTracker: LatencyTracker,
+    private val featureFlags: FeatureFlags
+) : ViewController<BackPanel>(BackPanel(context, latencyTracker)), NavigationEdgeBackPlugin {
 
     /**
      * Injectable instance to create a new BackPanelController.
@@ -104,34 +104,37 @@
      * Necessary because EdgeBackGestureHandler sometimes needs to create new instances of
      * BackPanelController, and we need to match EdgeBackGestureHandler's context.
      */
-    class Factory @Inject constructor(
-            private val windowManager: WindowManager,
-            private val viewConfiguration: ViewConfiguration,
-            @Main private val mainHandler: Handler,
-            private val vibratorHelper: VibratorHelper,
-            private val configurationController: ConfigurationController,
-            private val latencyTracker: LatencyTracker
+    class Factory
+    @Inject
+    constructor(
+        private val windowManager: WindowManager,
+        private val viewConfiguration: ViewConfiguration,
+        @Main private val mainHandler: Handler,
+        private val vibratorHelper: VibratorHelper,
+        private val configurationController: ConfigurationController,
+        private val latencyTracker: LatencyTracker,
+        private val featureFlags: FeatureFlags
     ) {
-        /** Construct a [BackPanelController].  */
+        /** Construct a [BackPanelController]. */
         fun create(context: Context): BackPanelController {
-            val backPanelController = BackPanelController(
+            val backPanelController =
+                BackPanelController(
                     context,
                     windowManager,
                     viewConfiguration,
                     mainHandler,
                     vibratorHelper,
                     configurationController,
-                    latencyTracker
-            )
+                    latencyTracker,
+                    featureFlags
+                )
             backPanelController.init()
             return backPanelController
         }
     }
 
-    @VisibleForTesting
-    internal var params: EdgePanelParams = EdgePanelParams(resources)
-    @VisibleForTesting
-    internal var currentState: GestureState = GestureState.GONE
+    @VisibleForTesting internal var params: EdgePanelParams = EdgePanelParams(resources)
+    @VisibleForTesting internal var currentState: GestureState = GestureState.GONE
     private var previousState: GestureState = GestureState.GONE
 
     // Screen attributes
@@ -167,7 +170,6 @@
     private val elapsedTimeSinceEntry
         get() = SystemClock.uptimeMillis() - gestureEntryTime
 
-
     private var pastThresholdWhileEntryOrInactiveTime = 0L
     private var entryToActiveDelay = 0F
     private val entryToActiveDelayCalculation = {
@@ -206,24 +208,25 @@
         COMMITTED,
 
         /* back action currently cancelling, arrow soon to be GONE */
-        CANCELLED;
+        CANCELLED
     }
 
     /**
      * Wrapper around OnAnimationEndListener which runs the given runnable after a delay. The
      * runnable is not called if the animation is cancelled
      */
-    inner class DelayedOnAnimationEndListener internal constructor(
-            private val handler: Handler,
-            private val runnableDelay: Long,
-            val runnable: Runnable,
+    inner class DelayedOnAnimationEndListener
+    internal constructor(
+        private val handler: Handler,
+        private val runnableDelay: Long,
+        val runnable: Runnable,
     ) : DynamicAnimation.OnAnimationEndListener {
 
         override fun onAnimationEnd(
-                animation: DynamicAnimation<*>,
-                canceled: Boolean,
-                value: Float,
-                velocity: Float
+            animation: DynamicAnimation<*>,
+            canceled: Boolean,
+            value: Float,
+            velocity: Float
         ) {
             animation.removeEndListener(this)
 
@@ -239,45 +242,43 @@
         internal fun run() = runnable.run()
     }
 
-    private val onEndSetCommittedStateListener = DelayedOnAnimationEndListener(mainHandler, 0L) {
-        updateArrowState(GestureState.COMMITTED)
-    }
-
+    private val onEndSetCommittedStateListener =
+        DelayedOnAnimationEndListener(mainHandler, 0L) { updateArrowState(GestureState.COMMITTED) }
 
     private val onEndSetGoneStateListener =
-            DelayedOnAnimationEndListener(mainHandler, runnableDelay = 0L) {
-                cancelFailsafe()
-                updateArrowState(GestureState.GONE)
-            }
-
-    private val onAlphaEndSetGoneStateListener = DelayedOnAnimationEndListener(mainHandler, 0L) {
-        updateRestingArrowDimens()
-        if (!mView.addAnimationEndListener(mView.backgroundAlpha, onEndSetGoneStateListener)) {
-            scheduleFailsafe()
+        DelayedOnAnimationEndListener(mainHandler, runnableDelay = 0L) {
+            cancelFailsafe()
+            updateArrowState(GestureState.GONE)
         }
-    }
+
+    private val onAlphaEndSetGoneStateListener =
+        DelayedOnAnimationEndListener(mainHandler, 0L) {
+            updateRestingArrowDimens()
+            if (!mView.addAnimationEndListener(mView.backgroundAlpha, onEndSetGoneStateListener)) {
+                scheduleFailsafe()
+            }
+        }
 
     // Minimum of the screen's width or the predefined threshold
     private var fullyStretchedThreshold = 0f
 
-    /**
-     * Used for initialization and configuration changes
-     */
+    /** Used for initialization and configuration changes */
     private fun updateConfiguration() {
         params.update(resources)
         mView.updateArrowPaint(params.arrowThickness)
         minFlingDistance = viewConfiguration.scaledTouchSlop * 3
     }
 
-    private val configurationListener = object : ConfigurationController.ConfigurationListener {
-        override fun onConfigChanged(newConfig: Configuration?) {
-            updateConfiguration()
-        }
+    private val configurationListener =
+        object : ConfigurationController.ConfigurationListener {
+            override fun onConfigChanged(newConfig: Configuration?) {
+                updateConfiguration()
+            }
 
-        override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
-            updateArrowDirection(isLayoutRtl)
+            override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
+                updateArrowDirection(isLayoutRtl)
+            }
         }
-    }
 
     override fun onViewAttached() {
         updateConfiguration()
@@ -320,8 +321,9 @@
             MotionEvent.ACTION_UP -> {
                 when (currentState) {
                     GestureState.ENTRY -> {
-                        if (isFlungAwayFromEdge(endX = event.x) ||
-                            previousXTranslation > params.staticTriggerThreshold
+                        if (
+                            isFlungAwayFromEdge(endX = event.x) ||
+                                previousXTranslation > params.staticTriggerThreshold
                         ) {
                             updateArrowState(GestureState.FLUNG)
                         } else {
@@ -342,14 +344,16 @@
                         }
                     }
                     GestureState.ACTIVE -> {
-                        if (previousState == GestureState.ENTRY &&
-                                elapsedTimeSinceEntry
-                                    < MIN_DURATION_ENTRY_TO_ACTIVE_CONSIDERED_AS_FLING
+                        if (
+                            previousState == GestureState.ENTRY &&
+                                elapsedTimeSinceEntry <
+                                    MIN_DURATION_ENTRY_TO_ACTIVE_CONSIDERED_AS_FLING
                         ) {
                             updateArrowState(GestureState.FLUNG)
-                        } else if (previousState == GestureState.INACTIVE &&
-                                elapsedTimeSinceInactive
-                                    < MIN_DURATION_INACTIVE_TO_ACTIVE_CONSIDERED_AS_FLING
+                        } else if (
+                            previousState == GestureState.INACTIVE &&
+                                elapsedTimeSinceInactive <
+                                    MIN_DURATION_INACTIVE_TO_ACTIVE_CONSIDERED_AS_FLING
                         ) {
                             // A delay is added to allow the background to transition back to ACTIVE
                             // since it was briefly in INACTIVE. Without this delay, setting it
@@ -390,10 +394,10 @@
     }
 
     /**
-     * Returns false until the current gesture exceeds the touch slop threshold,
-     * and returns true thereafter (we reset on the subsequent back gesture).
-     * The moment it switches from false -> true is important,
-     * because that's when we switch state, from GONE -> ENTRY.
+     * Returns false until the current gesture exceeds the touch slop threshold, and returns true
+     * thereafter (we reset on the subsequent back gesture). The moment it switches from false ->
+     * true is important, because that's when we switch state, from GONE -> ENTRY.
+     *
      * @return whether the current gesture has moved past a minimum threshold.
      */
     private fun dragSlopExceeded(curX: Float, startX: Float): Boolean {
@@ -416,7 +420,8 @@
         val isPastStaticThreshold = xTranslation > params.staticTriggerThreshold
         when (currentState) {
             GestureState.ENTRY -> {
-                if (isPastThresholdToActive(
+                if (
+                    isPastThresholdToActive(
                         isPastThreshold = isPastStaticThreshold,
                         dynamicDelay = entryToActiveDelayCalculation
                     )
@@ -428,8 +433,10 @@
                 val isPastDynamicReactivationThreshold =
                     totalTouchDeltaInactive >= params.reactivationTriggerThreshold
 
-                if (isPastThresholdToActive(
-                        isPastThreshold = isPastStaticThreshold &&
+                if (
+                    isPastThresholdToActive(
+                        isPastThreshold =
+                            isPastStaticThreshold &&
                                 isPastDynamicReactivationThreshold &&
                                 isWithinYActivationThreshold,
                         delay = MIN_DURATION_INACTIVE_BEFORE_ACTIVE_ANIMATION
@@ -489,19 +496,19 @@
             // Add a slop to to prevent small jitters when arrow is at edge in
             // emitting small values that cause the arrow to poke out slightly
             val minimumDelta = -viewConfiguration.scaledTouchSlop.toFloat()
-            totalTouchDeltaInactive = totalTouchDeltaInactive
-                .plus(xDelta)
-                .coerceAtLeast(minimumDelta)
+            totalTouchDeltaInactive =
+                totalTouchDeltaInactive.plus(xDelta).coerceAtLeast(minimumDelta)
         }
 
         updateArrowStateOnMove(yTranslation, xTranslation)
 
-        val gestureProgress = when (currentState) {
-            GestureState.ACTIVE -> fullScreenProgress(xTranslation)
-            GestureState.ENTRY -> staticThresholdProgress(xTranslation)
-            GestureState.INACTIVE -> reactivationThresholdProgress(totalTouchDeltaInactive)
-            else -> null
-        }
+        val gestureProgress =
+            when (currentState) {
+                GestureState.ACTIVE -> fullScreenProgress(xTranslation)
+                GestureState.ENTRY -> staticThresholdProgress(xTranslation)
+                GestureState.INACTIVE -> reactivationThresholdProgress(totalTouchDeltaInactive)
+                else -> null
+            }
 
         gestureProgress?.let {
             when (currentState) {
@@ -517,27 +524,30 @@
     }
 
     private fun setArrowStrokeAlpha(gestureProgress: Float?) {
-        val strokeAlphaProgress = when (currentState) {
-            GestureState.ENTRY -> gestureProgress
-            GestureState.INACTIVE -> gestureProgress
-            GestureState.ACTIVE,
-            GestureState.FLUNG,
-            GestureState.COMMITTED -> 1f
-            GestureState.CANCELLED,
-            GestureState.GONE -> 0f
-        }
+        val strokeAlphaProgress =
+            when (currentState) {
+                GestureState.ENTRY -> gestureProgress
+                GestureState.INACTIVE -> gestureProgress
+                GestureState.ACTIVE,
+                GestureState.FLUNG,
+                GestureState.COMMITTED -> 1f
+                GestureState.CANCELLED,
+                GestureState.GONE -> 0f
+            }
 
-        val indicator = when (currentState) {
-            GestureState.ENTRY -> params.entryIndicator
-            GestureState.INACTIVE -> params.preThresholdIndicator
-            GestureState.ACTIVE -> params.activeIndicator
-            else -> params.preThresholdIndicator
-        }
+        val indicator =
+            when (currentState) {
+                GestureState.ENTRY -> params.entryIndicator
+                GestureState.INACTIVE -> params.preThresholdIndicator
+                GestureState.ACTIVE -> params.activeIndicator
+                else -> params.preThresholdIndicator
+            }
 
         strokeAlphaProgress?.let { progress ->
-            indicator.arrowDimens.alphaSpring?.get(progress)?.takeIf { it.isNewState }?.let {
-                mView.popArrowAlpha(0f, it.value)
-            }
+            indicator.arrowDimens.alphaSpring
+                ?.get(progress)
+                ?.takeIf { it.isNewState }
+                ?.let { mView.popArrowAlpha(0f, it.value) }
         }
     }
 
@@ -546,15 +556,16 @@
         val maxYOffset = (mView.height - params.entryIndicator.backgroundDimens.height) / 2f
         val rubberbandAmount = 15f
         val yProgress = MathUtils.saturate(yTranslation / (maxYOffset * rubberbandAmount))
-        val yPosition = params.verticalTranslationInterpolator.getInterpolation(yProgress) *
+        val yPosition =
+            params.verticalTranslationInterpolator.getInterpolation(yProgress) *
                 maxYOffset *
                 sign(yOffset)
         mView.animateVertically(yPosition)
     }
 
     /**
-     * Tracks the relative position of the drag from the time after the arrow is activated until
-     * the arrow is fully stretched (between 0.0 - 1.0f)
+     * Tracks the relative position of the drag from the time after the arrow is activated until the
+     * arrow is fully stretched (between 0.0 - 1.0f)
      */
     private fun fullScreenProgress(xTranslation: Float): Float {
         val progress = (xTranslation - previousXTranslationOnActiveOffset) / fullyStretchedThreshold
@@ -575,35 +586,32 @@
 
     private fun stretchActiveBackIndicator(progress: Float) {
         mView.setStretch(
-                horizontalTranslationStretchAmount = params.horizontalTranslationInterpolator
-                        .getInterpolation(progress),
-                arrowStretchAmount = params.arrowAngleInterpolator.getInterpolation(progress),
-                backgroundWidthStretchAmount = params.activeWidthInterpolator
-                        .getInterpolation(progress),
-                backgroundAlphaStretchAmount = 1f,
-                backgroundHeightStretchAmount = 1f,
-                arrowAlphaStretchAmount = 1f,
-                edgeCornerStretchAmount = 1f,
-                farCornerStretchAmount = 1f,
-                fullyStretchedDimens = params.fullyStretchedIndicator
+            horizontalTranslationStretchAmount =
+                params.horizontalTranslationInterpolator.getInterpolation(progress),
+            arrowStretchAmount = params.arrowAngleInterpolator.getInterpolation(progress),
+            backgroundWidthStretchAmount =
+                params.activeWidthInterpolator.getInterpolation(progress),
+            backgroundAlphaStretchAmount = 1f,
+            backgroundHeightStretchAmount = 1f,
+            arrowAlphaStretchAmount = 1f,
+            edgeCornerStretchAmount = 1f,
+            farCornerStretchAmount = 1f,
+            fullyStretchedDimens = params.fullyStretchedIndicator
         )
     }
 
     private fun stretchEntryBackIndicator(progress: Float) {
         mView.setStretch(
-                horizontalTranslationStretchAmount = 0f,
-                arrowStretchAmount = params.arrowAngleInterpolator
-                        .getInterpolation(progress),
-                backgroundWidthStretchAmount = params.entryWidthInterpolator
-                        .getInterpolation(progress),
-                backgroundHeightStretchAmount = params.heightInterpolator
-                        .getInterpolation(progress),
-                backgroundAlphaStretchAmount = 1f,
-                arrowAlphaStretchAmount = params.entryIndicator.arrowDimens
-                        .alphaInterpolator?.get(progress)?.value ?: 0f,
-                edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress),
-                farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress),
-                fullyStretchedDimens = params.preThresholdIndicator
+            horizontalTranslationStretchAmount = 0f,
+            arrowStretchAmount = params.arrowAngleInterpolator.getInterpolation(progress),
+            backgroundWidthStretchAmount = params.entryWidthInterpolator.getInterpolation(progress),
+            backgroundHeightStretchAmount = params.heightInterpolator.getInterpolation(progress),
+            backgroundAlphaStretchAmount = 1f,
+            arrowAlphaStretchAmount =
+                params.entryIndicator.arrowDimens.alphaInterpolator?.get(progress)?.value ?: 0f,
+            edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress),
+            farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress),
+            fullyStretchedDimens = params.preThresholdIndicator
         )
     }
 
@@ -612,31 +620,32 @@
         val interpolator = run {
             val isPastSlop = totalTouchDeltaInactive > viewConfiguration.scaledTouchSlop
             if (isPastSlop) {
-                if (totalTouchDeltaInactive > 0) {
-                    params.entryWidthInterpolator
+                    if (totalTouchDeltaInactive > 0) {
+                        params.entryWidthInterpolator
+                    } else {
+                        params.entryWidthTowardsEdgeInterpolator
+                    }
                 } else {
-                    params.entryWidthTowardsEdgeInterpolator
+                    previousPreThresholdWidthInterpolator
                 }
-            } else {
-                previousPreThresholdWidthInterpolator
-            }.also { previousPreThresholdWidthInterpolator = it }
+                .also { previousPreThresholdWidthInterpolator = it }
         }
         return interpolator.getInterpolation(progress).coerceAtLeast(0f)
     }
 
     private fun stretchInactiveBackIndicator(progress: Float) {
         mView.setStretch(
-                horizontalTranslationStretchAmount = 0f,
-                arrowStretchAmount = params.arrowAngleInterpolator.getInterpolation(progress),
-                backgroundWidthStretchAmount = preThresholdWidthStretchAmount(progress),
-                backgroundHeightStretchAmount = params.heightInterpolator
-                        .getInterpolation(progress),
-                backgroundAlphaStretchAmount = 1f,
-                arrowAlphaStretchAmount = params.preThresholdIndicator.arrowDimens
-                        .alphaInterpolator?.get(progress)?.value ?: 0f,
-                edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress),
-                farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress),
-                fullyStretchedDimens = params.preThresholdIndicator
+            horizontalTranslationStretchAmount = 0f,
+            arrowStretchAmount = params.arrowAngleInterpolator.getInterpolation(progress),
+            backgroundWidthStretchAmount = preThresholdWidthStretchAmount(progress),
+            backgroundHeightStretchAmount = params.heightInterpolator.getInterpolation(progress),
+            backgroundAlphaStretchAmount = 1f,
+            arrowAlphaStretchAmount =
+                params.preThresholdIndicator.arrowDimens.alphaInterpolator?.get(progress)?.value
+                    ?: 0f,
+            edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress),
+            farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress),
+            fullyStretchedDimens = params.preThresholdIndicator
         )
     }
 
@@ -647,11 +656,12 @@
 
     override fun setIsLeftPanel(isLeftPanel: Boolean) {
         mView.isLeftPanel = isLeftPanel
-        layoutParams.gravity = if (isLeftPanel) {
-            Gravity.LEFT or Gravity.TOP
-        } else {
-            Gravity.RIGHT or Gravity.TOP
-        }
+        layoutParams.gravity =
+            if (isLeftPanel) {
+                Gravity.LEFT or Gravity.TOP
+            } else {
+                Gravity.RIGHT or Gravity.TOP
+            }
     }
 
     override fun setInsets(insetLeft: Int, insetRight: Int) = Unit
@@ -667,12 +677,14 @@
 
     private fun isFlungAwayFromEdge(endX: Float, startX: Float = touchDeltaStartX): Boolean {
         val flingDistance = if (mView.isLeftPanel) endX - startX else startX - endX
-        val flingVelocity = velocityTracker?.run {
-            computeCurrentVelocity(PX_PER_SEC)
-            xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1)
-        } ?: 0f
+        val flingVelocity =
+            velocityTracker?.run {
+                computeCurrentVelocity(PX_PER_SEC)
+                xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1)
+            }
+                ?: 0f
         val isPastFlingVelocityThreshold =
-                flingVelocity > viewConfiguration.scaledMinimumFlingVelocity
+            flingVelocity > viewConfiguration.scaledMinimumFlingVelocity
         return flingDistance > minFlingDistance && isPastFlingVelocityThreshold
     }
 
@@ -699,8 +711,8 @@
     }
 
     private fun playWithBackgroundWidthAnimation(
-            onEnd: DelayedOnAnimationEndListener,
-            delay: Long = 0L
+        onEnd: DelayedOnAnimationEndListener,
+        delay: Long = 0L
     ) {
         if (delay == 0L) {
             updateRestingArrowDimens()
@@ -724,104 +736,103 @@
         fullyStretchedThreshold = min(displaySize.x.toFloat(), params.swipeProgressThreshold)
     }
 
-    /**
-     * Updates resting arrow and background size not accounting for stretch
-     */
+    /** Updates resting arrow and background size not accounting for stretch */
     private fun updateRestingArrowDimens() {
         when (currentState) {
             GestureState.GONE,
             GestureState.ENTRY -> {
                 mView.setSpring(
-                        arrowLength = params.entryIndicator.arrowDimens.lengthSpring,
-                        arrowHeight = params.entryIndicator.arrowDimens.heightSpring,
-                        scale = params.entryIndicator.scaleSpring,
-                        verticalTranslation = params.entryIndicator.verticalTranslationSpring,
-                        horizontalTranslation = params.entryIndicator.horizontalTranslationSpring,
-                        backgroundAlpha = params.entryIndicator.backgroundDimens.alphaSpring,
-                        backgroundWidth = params.entryIndicator.backgroundDimens.widthSpring,
-                        backgroundHeight = params.entryIndicator.backgroundDimens.heightSpring,
-                        backgroundEdgeCornerRadius = params.entryIndicator.backgroundDimens
-                                .edgeCornerRadiusSpring,
-                        backgroundFarCornerRadius = params.entryIndicator.backgroundDimens
-                                .farCornerRadiusSpring,
+                    arrowLength = params.entryIndicator.arrowDimens.lengthSpring,
+                    arrowHeight = params.entryIndicator.arrowDimens.heightSpring,
+                    scale = params.entryIndicator.scaleSpring,
+                    verticalTranslation = params.entryIndicator.verticalTranslationSpring,
+                    horizontalTranslation = params.entryIndicator.horizontalTranslationSpring,
+                    backgroundAlpha = params.entryIndicator.backgroundDimens.alphaSpring,
+                    backgroundWidth = params.entryIndicator.backgroundDimens.widthSpring,
+                    backgroundHeight = params.entryIndicator.backgroundDimens.heightSpring,
+                    backgroundEdgeCornerRadius =
+                        params.entryIndicator.backgroundDimens.edgeCornerRadiusSpring,
+                    backgroundFarCornerRadius =
+                        params.entryIndicator.backgroundDimens.farCornerRadiusSpring,
                 )
             }
             GestureState.INACTIVE -> {
                 mView.setSpring(
-                        arrowLength = params.preThresholdIndicator.arrowDimens.lengthSpring,
-                        arrowHeight = params.preThresholdIndicator.arrowDimens.heightSpring,
-                        horizontalTranslation = params.preThresholdIndicator
-                                .horizontalTranslationSpring,
-                        scale = params.preThresholdIndicator.scaleSpring,
-                        backgroundWidth = params.preThresholdIndicator.backgroundDimens
-                                .widthSpring,
-                        backgroundHeight = params.preThresholdIndicator.backgroundDimens
-                                .heightSpring,
-                        backgroundEdgeCornerRadius = params.preThresholdIndicator.backgroundDimens
-                                .edgeCornerRadiusSpring,
-                        backgroundFarCornerRadius = params.preThresholdIndicator.backgroundDimens
-                                .farCornerRadiusSpring,
+                    arrowLength = params.preThresholdIndicator.arrowDimens.lengthSpring,
+                    arrowHeight = params.preThresholdIndicator.arrowDimens.heightSpring,
+                    horizontalTranslation =
+                        params.preThresholdIndicator.horizontalTranslationSpring,
+                    scale = params.preThresholdIndicator.scaleSpring,
+                    backgroundWidth = params.preThresholdIndicator.backgroundDimens.widthSpring,
+                    backgroundHeight = params.preThresholdIndicator.backgroundDimens.heightSpring,
+                    backgroundEdgeCornerRadius =
+                        params.preThresholdIndicator.backgroundDimens.edgeCornerRadiusSpring,
+                    backgroundFarCornerRadius =
+                        params.preThresholdIndicator.backgroundDimens.farCornerRadiusSpring,
                 )
             }
             GestureState.ACTIVE -> {
                 mView.setSpring(
-                        arrowLength = params.activeIndicator.arrowDimens.lengthSpring,
-                        arrowHeight = params.activeIndicator.arrowDimens.heightSpring,
-                        scale = params.activeIndicator.scaleSpring,
-                        horizontalTranslation = params.activeIndicator.horizontalTranslationSpring,
-                        backgroundWidth = params.activeIndicator.backgroundDimens.widthSpring,
-                        backgroundHeight = params.activeIndicator.backgroundDimens.heightSpring,
-                        backgroundEdgeCornerRadius = params.activeIndicator.backgroundDimens
-                                .edgeCornerRadiusSpring,
-                        backgroundFarCornerRadius = params.activeIndicator.backgroundDimens
-                                .farCornerRadiusSpring,
+                    arrowLength = params.activeIndicator.arrowDimens.lengthSpring,
+                    arrowHeight = params.activeIndicator.arrowDimens.heightSpring,
+                    scale = params.activeIndicator.scaleSpring,
+                    horizontalTranslation = params.activeIndicator.horizontalTranslationSpring,
+                    backgroundWidth = params.activeIndicator.backgroundDimens.widthSpring,
+                    backgroundHeight = params.activeIndicator.backgroundDimens.heightSpring,
+                    backgroundEdgeCornerRadius =
+                        params.activeIndicator.backgroundDimens.edgeCornerRadiusSpring,
+                    backgroundFarCornerRadius =
+                        params.activeIndicator.backgroundDimens.farCornerRadiusSpring,
                 )
             }
             GestureState.FLUNG -> {
                 mView.setSpring(
-                        arrowLength = params.flungIndicator.arrowDimens.lengthSpring,
-                        arrowHeight = params.flungIndicator.arrowDimens.heightSpring,
-                        backgroundWidth = params.flungIndicator.backgroundDimens.widthSpring,
-                        backgroundHeight = params.flungIndicator.backgroundDimens.heightSpring,
-                        backgroundEdgeCornerRadius = params.flungIndicator.backgroundDimens
-                                .edgeCornerRadiusSpring,
-                        backgroundFarCornerRadius = params.flungIndicator.backgroundDimens
-                                .farCornerRadiusSpring,
+                    arrowLength = params.flungIndicator.arrowDimens.lengthSpring,
+                    arrowHeight = params.flungIndicator.arrowDimens.heightSpring,
+                    backgroundWidth = params.flungIndicator.backgroundDimens.widthSpring,
+                    backgroundHeight = params.flungIndicator.backgroundDimens.heightSpring,
+                    backgroundEdgeCornerRadius =
+                        params.flungIndicator.backgroundDimens.edgeCornerRadiusSpring,
+                    backgroundFarCornerRadius =
+                        params.flungIndicator.backgroundDimens.farCornerRadiusSpring,
                 )
             }
             GestureState.COMMITTED -> {
                 mView.setSpring(
-                        arrowLength = params.committedIndicator.arrowDimens.lengthSpring,
-                        arrowHeight = params.committedIndicator.arrowDimens.heightSpring,
-                        scale = params.committedIndicator.scaleSpring,
-                        backgroundAlpha = params.committedIndicator.backgroundDimens.alphaSpring,
-                        backgroundWidth = params.committedIndicator.backgroundDimens.widthSpring,
-                        backgroundHeight = params.committedIndicator.backgroundDimens.heightSpring,
-                        backgroundEdgeCornerRadius = params.committedIndicator.backgroundDimens
-                                .edgeCornerRadiusSpring,
-                        backgroundFarCornerRadius = params.committedIndicator.backgroundDimens
-                                .farCornerRadiusSpring,
+                    arrowLength = params.committedIndicator.arrowDimens.lengthSpring,
+                    arrowHeight = params.committedIndicator.arrowDimens.heightSpring,
+                    scale = params.committedIndicator.scaleSpring,
+                    backgroundAlpha = params.committedIndicator.backgroundDimens.alphaSpring,
+                    backgroundWidth = params.committedIndicator.backgroundDimens.widthSpring,
+                    backgroundHeight = params.committedIndicator.backgroundDimens.heightSpring,
+                    backgroundEdgeCornerRadius =
+                        params.committedIndicator.backgroundDimens.edgeCornerRadiusSpring,
+                    backgroundFarCornerRadius =
+                        params.committedIndicator.backgroundDimens.farCornerRadiusSpring,
                 )
             }
             GestureState.CANCELLED -> {
                 mView.setSpring(
-                        backgroundAlpha = params.cancelledIndicator.backgroundDimens.alphaSpring)
+                    backgroundAlpha = params.cancelledIndicator.backgroundDimens.alphaSpring
+                )
             }
             else -> {}
         }
 
         mView.setRestingDimens(
-                animate = !(currentState == GestureState.FLUNG ||
-                        currentState == GestureState.COMMITTED),
-                restingParams = EdgePanelParams.BackIndicatorDimens(
-                        scale = when (currentState) {
+            animate =
+                !(currentState == GestureState.FLUNG || currentState == GestureState.COMMITTED),
+            restingParams =
+                EdgePanelParams.BackIndicatorDimens(
+                    scale =
+                        when (currentState) {
                             GestureState.ACTIVE,
-                            GestureState.FLUNG,
-                            -> params.activeIndicator.scale
+                            GestureState.FLUNG, -> params.activeIndicator.scale
                             GestureState.COMMITTED -> params.committedIndicator.scale
                             else -> params.preThresholdIndicator.scale
                         },
-                        scalePivotX = when (currentState) {
+                    scalePivotX =
+                        when (currentState) {
                             GestureState.GONE,
                             GestureState.ENTRY,
                             GestureState.INACTIVE,
@@ -830,7 +841,8 @@
                             GestureState.FLUNG,
                             GestureState.COMMITTED -> params.committedIndicator.scalePivotX
                         },
-                        horizontalTranslation = when (currentState) {
+                    horizontalTranslation =
+                        when (currentState) {
                             GestureState.GONE -> {
                                 params.activeIndicator.backgroundDimens.width?.times(-1)
                             }
@@ -843,7 +855,8 @@
                             }
                             else -> null
                         },
-                        arrowDimens = when (currentState) {
+                    arrowDimens =
+                        when (currentState) {
                             GestureState.GONE,
                             GestureState.ENTRY,
                             GestureState.INACTIVE -> params.entryIndicator.arrowDimens
@@ -852,7 +865,8 @@
                             GestureState.COMMITTED -> params.committedIndicator.arrowDimens
                             GestureState.CANCELLED -> params.cancelledIndicator.arrowDimens
                         },
-                        backgroundDimens = when (currentState) {
+                    backgroundDimens =
+                        when (currentState) {
                             GestureState.GONE,
                             GestureState.ENTRY,
                             GestureState.INACTIVE -> params.entryIndicator.backgroundDimens
@@ -894,7 +908,7 @@
             GestureState.ACTIVE -> {
                 backCallback.setTriggerBack(true)
             }
-            GestureState.GONE -> { }
+            GestureState.GONE -> {}
         }
 
         when (currentState) {
@@ -913,18 +927,25 @@
             GestureState.ACTIVE -> {
                 previousXTranslationOnActiveOffset = previousXTranslation
                 updateRestingArrowDimens()
-                vibratorHelper.cancel()
-                mainHandler.postDelayed(10L) {
-                    vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
-                }
-                val popVelocity = if (previousState == GestureState.INACTIVE) {
-                    POP_ON_INACTIVE_TO_ACTIVE_VELOCITY
+                if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+                    vibratorHelper.performHapticFeedback(
+                        mView,
+                        HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
+                    )
                 } else {
-                    POP_ON_ENTRY_TO_ACTIVE_VELOCITY
+                    vibratorHelper.cancel()
+                    mainHandler.postDelayed(10L) {
+                        vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
+                    }
                 }
+                val popVelocity =
+                    if (previousState == GestureState.INACTIVE) {
+                        POP_ON_INACTIVE_TO_ACTIVE_VELOCITY
+                    } else {
+                        POP_ON_ENTRY_TO_ACTIVE_VELOCITY
+                    }
                 mView.popOffEdge(popVelocity)
             }
-
             GestureState.INACTIVE -> {
                 gestureInactiveTime = SystemClock.uptimeMillis()
 
@@ -937,7 +958,14 @@
 
                 mView.popOffEdge(POP_ON_INACTIVE_VELOCITY)
 
-                vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
+                if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+                    vibratorHelper.performHapticFeedback(
+                        mView,
+                        HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
+                    )
+                } else {
+                    vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
+                }
                 updateRestingArrowDimens()
             }
             GestureState.FLUNG -> {
@@ -945,8 +973,10 @@
                     mView.popScale(POP_ON_FLING_VELOCITY)
                 }
                 updateRestingArrowDimens()
-                mainHandler.postDelayed(onEndSetCommittedStateListener.runnable,
-                        MIN_DURATION_FLING_ANIMATION)
+                mainHandler.postDelayed(
+                    onEndSetCommittedStateListener.runnable,
+                    MIN_DURATION_FLING_ANIMATION
+                )
             }
             GestureState.COMMITTED -> {
                 // In most cases, animating between states is handled via `updateRestingArrowDimens`
@@ -956,36 +986,43 @@
                 // manually play these kinds of animations in parallel.
                 if (previousState == GestureState.FLUNG) {
                     updateRestingArrowDimens()
-                    mainHandler.postDelayed(onEndSetGoneStateListener.runnable,
-                            MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION)
+                    mainHandler.postDelayed(
+                        onEndSetGoneStateListener.runnable,
+                        MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION
+                    )
                 } else {
                     mView.popScale(POP_ON_COMMITTED_VELOCITY)
-                    mainHandler.postDelayed(onAlphaEndSetGoneStateListener.runnable,
-                            MIN_DURATION_COMMITTED_ANIMATION)
+                    mainHandler.postDelayed(
+                        onAlphaEndSetGoneStateListener.runnable,
+                        MIN_DURATION_COMMITTED_ANIMATION
+                    )
                 }
             }
             GestureState.CANCELLED -> {
                 val delay = max(0, MIN_DURATION_CANCELLED_ANIMATION - elapsedTimeSinceEntry)
                 playWithBackgroundWidthAnimation(onEndSetGoneStateListener, delay)
 
-                val springForceOnCancelled = params.cancelledIndicator
-                        .arrowDimens.alphaSpring?.get(0f)?.value
+                val springForceOnCancelled =
+                    params.cancelledIndicator.arrowDimens.alphaSpring?.get(0f)?.value
                 mView.popArrowAlpha(0f, springForceOnCancelled)
-                mainHandler.postDelayed(10L) { vibratorHelper.cancel() }
+                if (!featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION))
+                    mainHandler.postDelayed(10L) { vibratorHelper.cancel() }
             }
         }
     }
 
     private fun convertVelocityToAnimationFactor(
-            valueOnFastVelocity: Float,
-            valueOnSlowVelocity: Float,
-            fastVelocityBound: Float = 1f,
-            slowVelocityBound: Float = 0.5f,
+        valueOnFastVelocity: Float,
+        valueOnSlowVelocity: Float,
+        fastVelocityBound: Float = 1f,
+        slowVelocityBound: Float = 0.5f,
     ): Float {
-        val factor = velocityTracker?.run {
-            computeCurrentVelocity(PX_PER_MS)
-            MathUtils.smoothStep(slowVelocityBound, fastVelocityBound, abs(xVelocity))
-        } ?: valueOnFastVelocity
+        val factor =
+            velocityTracker?.run {
+                computeCurrentVelocity(PX_PER_MS)
+                MathUtils.smoothStep(slowVelocityBound, fastVelocityBound, abs(xVelocity))
+            }
+                ?: valueOnFastVelocity
 
         return MathUtils.lerp(valueOnFastVelocity, valueOnSlowVelocity, 1 - factor)
     }
@@ -1014,77 +1051,76 @@
     }
 
     init {
-        if (DEBUG) mView.drawDebugInfo = { canvas ->
-            val debugStrings = listOf(
-                    "$currentState",
-                    "startX=$startX",
-                    "startY=$startY",
-                    "xDelta=${"%.1f".format(totalTouchDeltaActive)}",
-                    "xTranslation=${"%.1f".format(previousXTranslation)}",
-                    "pre=${"%.0f".format(staticThresholdProgress(previousXTranslation) * 100)}%",
-                    "post=${"%.0f".format(fullScreenProgress(previousXTranslation) * 100)}%"
-            )
-            val debugPaint = Paint().apply {
-                color = Color.WHITE
-            }
-            val debugInfoBottom = debugStrings.size * 32f + 4f
-            canvas.drawRect(
+        if (DEBUG)
+            mView.drawDebugInfo = { canvas ->
+                val preProgress = staticThresholdProgress(previousXTranslation) * 100
+                val postProgress = fullScreenProgress(previousXTranslation) * 100
+                val debugStrings =
+                    listOf(
+                        "$currentState",
+                        "startX=$startX",
+                        "startY=$startY",
+                        "xDelta=${"%.1f".format(totalTouchDeltaActive)}",
+                        "xTranslation=${"%.1f".format(previousXTranslation)}",
+                        "pre=${"%.0f".format(preProgress)}%",
+                        "post=${"%.0f".format(postProgress)}%"
+                    )
+                val debugPaint = Paint().apply { color = Color.WHITE }
+                val debugInfoBottom = debugStrings.size * 32f + 4f
+                canvas.drawRect(
                     4f,
                     4f,
                     canvas.width.toFloat(),
                     debugStrings.size * 32f + 4f,
                     debugPaint
-            )
-            debugPaint.apply {
-                color = Color.BLACK
-                textSize = 32f
-            }
-            var offset = 32f
-            for (debugText in debugStrings) {
-                canvas.drawText(debugText, 10f, offset, debugPaint)
-                offset += 32f
-            }
-            debugPaint.apply {
-                color = Color.RED
-                style = Paint.Style.STROKE
-                strokeWidth = 4f
-            }
-            val canvasWidth = canvas.width.toFloat()
-            val canvasHeight = canvas.height.toFloat()
-            canvas.drawRect(0f, 0f, canvasWidth, canvasHeight, debugPaint)
+                )
+                debugPaint.apply {
+                    color = Color.BLACK
+                    textSize = 32f
+                }
+                var offset = 32f
+                for (debugText in debugStrings) {
+                    canvas.drawText(debugText, 10f, offset, debugPaint)
+                    offset += 32f
+                }
+                debugPaint.apply {
+                    color = Color.RED
+                    style = Paint.Style.STROKE
+                    strokeWidth = 4f
+                }
+                val canvasWidth = canvas.width.toFloat()
+                val canvasHeight = canvas.height.toFloat()
+                canvas.drawRect(0f, 0f, canvasWidth, canvasHeight, debugPaint)
 
-            fun drawVerticalLine(x: Float, color: Int) {
-                debugPaint.color = color
-                val x = if (mView.isLeftPanel) x else canvasWidth - x
-                canvas.drawLine(x, debugInfoBottom, x, canvas.height.toFloat(), debugPaint)
-            }
+                fun drawVerticalLine(x: Float, color: Int) {
+                    debugPaint.color = color
+                    val x = if (mView.isLeftPanel) x else canvasWidth - x
+                    canvas.drawLine(x, debugInfoBottom, x, canvas.height.toFloat(), debugPaint)
+                }
 
-            drawVerticalLine(x = params.staticTriggerThreshold, color = Color.BLUE)
-            drawVerticalLine(x = params.deactivationTriggerThreshold, color = Color.BLUE)
-            drawVerticalLine(x = startX, color = Color.GREEN)
-            drawVerticalLine(x = previousXTranslation, color = Color.DKGRAY)
-        }
+                drawVerticalLine(x = params.staticTriggerThreshold, color = Color.BLUE)
+                drawVerticalLine(x = params.deactivationTriggerThreshold, color = Color.BLUE)
+                drawVerticalLine(x = startX, color = Color.GREEN)
+                drawVerticalLine(x = previousXTranslation, color = Color.DKGRAY)
+            }
     }
 }
 
 /**
- * In addition to a typical step function which returns one or two
- * values based on a threshold, `Step` also gracefully handles quick
- * changes in input near the threshold value that would typically
- * result in the output rapidly changing.
+ * In addition to a typical step function which returns one or two values based on a threshold,
+ * `Step` also gracefully handles quick changes in input near the threshold value that would
+ * typically result in the output rapidly changing.
  *
- * In the context of Back arrow, the arrow's stroke opacity should
- * always appear transparent or opaque. Using a typical Step function,
- * this would resulting in a flickering appearance as the output would
- * change rapidly. `Step` addresses this by moving the threshold after
- * it is crossed so it cannot be easily crossed again with small changes
- * in touch events.
+ * In the context of Back arrow, the arrow's stroke opacity should always appear transparent or
+ * opaque. Using a typical Step function, this would resulting in a flickering appearance as the
+ * output would change rapidly. `Step` addresses this by moving the threshold after it is crossed so
+ * it cannot be easily crossed again with small changes in touch events.
  */
 class Step<T>(
-        private val threshold: Float,
-        private val factor: Float = 1.1f,
-        private val postThreshold: T,
-        private val preThreshold: T
+    private val threshold: Float,
+    private val factor: Float = 1.1f,
+    private val postThreshold: T,
+    private val preThreshold: T
 ) {
 
     data class Value<T>(val value: T, val isNewState: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 8b3d7a6..b21b001 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -15,11 +15,13 @@
  */
 package com.android.systemui.navigationbar.gestural;
 
+import static android.view.InputDevice.SOURCE_MOUSE;
+import static android.view.InputDevice.SOURCE_TOUCHPAD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
 
 import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
-import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadFourFingerSwipe;
-import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadMultiFingerSwipe;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -37,6 +39,7 @@
 import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.icu.text.SimpleDateFormat;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -85,11 +88,7 @@
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
-import com.android.systemui.shared.tracing.ProtoTraceable;
 import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.tracing.ProtoTracer;
-import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
-import com.android.systemui.tracing.nano.SystemUiTraceProto;
 import com.android.systemui.util.Assert;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.desktopmode.DesktopMode;
@@ -112,8 +111,7 @@
 /**
  * Utility class to handle edge swipes for back gesture
  */
-public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBackPlugin>,
-        ProtoTraceable<SystemUiTraceProto> {
+public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBackPlugin> {
 
     private static final String TAG = "EdgeBackGestureHandler";
     private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
@@ -189,12 +187,12 @@
     private Consumer<Boolean> mButtonForcedVisibleCallback;
 
     private final PluginManager mPluginManager;
-    private final ProtoTracer mProtoTracer;
     private final NavigationModeController mNavigationModeController;
     private final BackPanelController.Factory mBackPanelControllerFactory;
     private final ViewConfiguration mViewConfiguration;
     private final WindowManager mWindowManager;
     private final IWindowManager mWindowManagerService;
+    private final InputManager mInputManager;
     private final Optional<Pip> mPipOptional;
     private final Optional<DesktopMode> mDesktopModeOptional;
     private final FalsingManager mFalsingManager;
@@ -206,6 +204,7 @@
     private final int mDisplayId;
 
     private final Executor mMainExecutor;
+    private final Handler mMainHandler;
     private final Executor mBackgroundExecutor;
 
     private final Rect mPipExcludedBounds = new Rect();
@@ -249,13 +248,17 @@
     private boolean mDeferSetIsOnLeftEdge;
 
     private boolean mIsAttached;
-    private boolean mIsGesturalModeEnabled;
+    private boolean mIsGestureHandlingEnabled;
+    private boolean mIsTrackpadConnected;
+    private boolean mInGestureNavMode;
+    private boolean mUsingThreeButtonNav;
     private boolean mIsEnabled;
     private boolean mIsNavBarShownTransiently;
     private boolean mIsBackGestureAllowed;
     private boolean mGestureBlockingActivityRunning;
     private boolean mIsNewBackAffordanceEnabled;
     private boolean mIsTrackpadGestureFeaturesEnabled;
+    private boolean mIsTrackpadThreeFingerSwipe;
     private boolean mIsButtonForcedVisible;
 
     private InputMonitor mInputMonitor;
@@ -349,20 +352,56 @@
                 }
             };
 
+    private final InputManager.InputDeviceListener mInputDeviceListener =
+            new InputManager.InputDeviceListener() {
+
+        // Only one trackpad can be connected to a device at a time, since it takes over the
+        // only USB port.
+        private int mTrackpadDeviceId;
+
+        @Override
+        public void onInputDeviceAdded(int deviceId) {
+            if (isTrackpadDevice(deviceId)) {
+                mTrackpadDeviceId = deviceId;
+                update(true /* isTrackpadConnected */);
+            }
+        }
+
+        @Override
+        public void onInputDeviceChanged(int deviceId) { }
+
+        @Override
+        public void onInputDeviceRemoved(int deviceId) {
+            if (mTrackpadDeviceId == deviceId) {
+                update(false /* isTrackpadConnected */);
+            }
+        }
+
+        private void update(boolean isTrackpadConnected) {
+            boolean isPreviouslyTrackpadConnected = mIsTrackpadConnected;
+            mIsTrackpadConnected = isTrackpadConnected;
+            if (isPreviouslyTrackpadConnected != mIsTrackpadConnected) {
+                updateIsEnabled();
+                updateCurrentUserResources();
+            }
+        }
+    };
+
     EdgeBackGestureHandler(
             Context context,
             OverviewProxyService overviewProxyService,
             SysUiState sysUiState,
             PluginManager pluginManager,
             @Main Executor executor,
+            @Main Handler handler,
             @Background Executor backgroundExecutor,
             UserTracker userTracker,
-            ProtoTracer protoTracer,
             NavigationModeController navigationModeController,
             BackPanelController.Factory backPanelControllerFactory,
             ViewConfiguration viewConfiguration,
             WindowManager windowManager,
             IWindowManager windowManagerService,
+            InputManager inputManager,
             Optional<Pip> pipOptional,
             Optional<DesktopMode> desktopModeOptional,
             FalsingManager falsingManager,
@@ -373,17 +412,18 @@
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = executor;
+        mMainHandler = handler;
         mBackgroundExecutor = backgroundExecutor;
         mUserTracker = userTracker;
         mOverviewProxyService = overviewProxyService;
         mSysUiState = sysUiState;
         mPluginManager = pluginManager;
-        mProtoTracer = protoTracer;
         mNavigationModeController = navigationModeController;
         mBackPanelControllerFactory = backPanelControllerFactory;
         mViewConfiguration = viewConfiguration;
         mWindowManager = windowManager;
         mWindowManagerService = windowManagerService;
+        mInputManager = inputManager;
         mPipOptional = pipOptional;
         mDesktopModeOptional = desktopModeOptional;
         mFalsingManager = falsingManager;
@@ -392,6 +432,8 @@
         mFeatureFlags = featureFlags;
         mLightBarControllerProvider = lightBarControllerProvider;
         mLastReportedConfig.setTo(mContext.getResources().getConfiguration());
+        mIsTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(
+                Flags.TRACKPAD_GESTURE_FEATURES);
         ComponentName recentsComponentName = ComponentName.unflattenFromString(
                 context.getString(com.android.internal.R.string.config_recentsComponentName));
         if (recentsComponentName != null) {
@@ -423,7 +465,7 @@
                 ViewConfiguration.getLongPressTimeout());
 
         mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
-                mContext.getMainThreadHandler(), mContext, this::onNavigationSettingsChanged);
+                mMainHandler, mContext, this::onNavigationSettingsChanged);
 
         updateCurrentUserResources();
     }
@@ -507,9 +549,15 @@
      */
     public void onNavBarAttached() {
         mIsAttached = true;
-        mProtoTracer.add(this);
         mOverviewProxyService.addCallback(mQuickSwitchListener);
         mSysUiState.addCallback(mSysUiStateCallback);
+        if (mIsTrackpadGestureFeaturesEnabled) {
+            mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler);
+            int [] inputDevices = mInputManager.getInputDeviceIds();
+            for (int inputDeviceId : inputDevices) {
+                mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
+            }
+        }
         updateIsEnabled();
         mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
     }
@@ -519,9 +567,9 @@
      */
     public void onNavBarDetached() {
         mIsAttached = false;
-        mProtoTracer.remove(this);
         mOverviewProxyService.removeCallback(mQuickSwitchListener);
         mSysUiState.removeCallback(mSysUiStateCallback);
+        mInputManager.unregisterInputDeviceListener(mInputDeviceListener);
         updateIsEnabled();
         mUserTracker.removeCallback(mUserChangedCallback);
     }
@@ -530,7 +578,8 @@
      * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged
      */
     public void onNavigationModeChanged(int mode) {
-        mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode);
+        mUsingThreeButtonNav = QuickStepContract.isLegacyMode(mode);
+        mInGestureNavMode = QuickStepContract.isGesturalMode(mode);
         updateIsEnabled();
         updateCurrentUserResources();
     }
@@ -553,83 +602,83 @@
     private void updateIsEnabled() {
         try {
             Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled");
-            updateIsEnabledTraced();
+
+            mIsGestureHandlingEnabled =
+                    mInGestureNavMode || (mIsTrackpadGestureFeaturesEnabled && mUsingThreeButtonNav
+                            && mIsTrackpadConnected);
+            boolean isEnabled = mIsAttached && mIsGestureHandlingEnabled;
+            if (isEnabled == mIsEnabled) {
+                return;
+            }
+            mIsEnabled = isEnabled;
+            disposeInputChannel();
+
+            if (mEdgeBackPlugin != null) {
+                mEdgeBackPlugin.onDestroy();
+                mEdgeBackPlugin = null;
+            }
+
+            if (!mIsEnabled) {
+                mGestureNavigationSettingsObserver.unregister();
+                if (DEBUG_MISSING_GESTURE) {
+                    Log.d(DEBUG_MISSING_GESTURE_TAG, "Unregister display listener");
+                }
+                mPluginManager.removePluginListener(this);
+                TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
+                        mTaskStackListener);
+                DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
+                mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(null));
+
+                try {
+                    mWindowManagerService.unregisterSystemGestureExclusionListener(
+                            mGestureExclusionListener, mDisplayId);
+                } catch (RemoteException | IllegalArgumentException e) {
+                    Log.e(TAG, "Failed to unregister window manager callbacks", e);
+                }
+
+            } else {
+                mGestureNavigationSettingsObserver.register();
+                updateDisplaySize();
+                if (DEBUG_MISSING_GESTURE) {
+                    Log.d(DEBUG_MISSING_GESTURE_TAG, "Register display listener");
+                }
+                TaskStackChangeListeners.getInstance().registerTaskStackListener(
+                        mTaskStackListener);
+                DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+                        mMainExecutor::execute, mOnPropertiesChangedListener);
+                mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(
+                        mOnIsInPipStateChangedListener));
+                mDesktopModeOptional.ifPresent(
+                        dm -> dm.addDesktopGestureExclusionRegionListener(
+                                mDesktopCornersChangedListener, mMainExecutor));
+
+                try {
+                    mWindowManagerService.registerSystemGestureExclusionListener(
+                            mGestureExclusionListener, mDisplayId);
+                } catch (RemoteException | IllegalArgumentException e) {
+                    Log.e(TAG, "Failed to register window manager callbacks", e);
+                }
+
+                // Register input event receiver
+                mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
+                        "edge-swipe", mDisplayId);
+                mInputEventReceiver = new InputChannelCompat.InputEventReceiver(
+                        mInputMonitor.getInputChannel(), Looper.getMainLooper(),
+                        Choreographer.getInstance(), this::onInputEvent);
+
+                // Add a nav bar panel window
+                mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE);
+                resetEdgeBackPlugin();
+                mPluginManager.addPluginListener(
+                        this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
+            }
+            // Update the ML model resources.
+            updateMLModelState();
         } finally {
             Trace.endSection();
         }
     }
 
-    private void updateIsEnabledTraced() {
-        boolean isEnabled = mIsAttached && mIsGesturalModeEnabled;
-        if (isEnabled == mIsEnabled) {
-            return;
-        }
-        mIsEnabled = isEnabled;
-        disposeInputChannel();
-
-        if (mEdgeBackPlugin != null) {
-            mEdgeBackPlugin.onDestroy();
-            mEdgeBackPlugin = null;
-        }
-
-        if (!mIsEnabled) {
-            mGestureNavigationSettingsObserver.unregister();
-            if (DEBUG_MISSING_GESTURE) {
-                Log.d(DEBUG_MISSING_GESTURE_TAG, "Unregister display listener");
-            }
-            mPluginManager.removePluginListener(this);
-            TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
-            DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
-            mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(null));
-
-            try {
-                mWindowManagerService.unregisterSystemGestureExclusionListener(
-                        mGestureExclusionListener, mDisplayId);
-            } catch (RemoteException | IllegalArgumentException e) {
-                Log.e(TAG, "Failed to unregister window manager callbacks", e);
-            }
-
-        } else {
-            mGestureNavigationSettingsObserver.register();
-            updateDisplaySize();
-            if (DEBUG_MISSING_GESTURE) {
-                Log.d(DEBUG_MISSING_GESTURE_TAG, "Register display listener");
-            }
-            TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
-            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
-                    mMainExecutor::execute, mOnPropertiesChangedListener);
-            mPipOptional.ifPresent(
-                    pip -> pip.setOnIsInPipStateChangedListener(mOnIsInPipStateChangedListener));
-            mDesktopModeOptional.ifPresent(
-                    dm -> dm.addDesktopGestureExclusionRegionListener(
-                            mDesktopCornersChangedListener, mMainExecutor));
-
-            try {
-                mWindowManagerService.registerSystemGestureExclusionListener(
-                        mGestureExclusionListener, mDisplayId);
-            } catch (RemoteException | IllegalArgumentException e) {
-                Log.e(TAG, "Failed to register window manager callbacks", e);
-            }
-
-            // Register input event receiver
-            mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
-                    "edge-swipe", mDisplayId);
-            mInputEventReceiver = new InputChannelCompat.InputEventReceiver(
-                    mInputMonitor.getInputChannel(), Looper.getMainLooper(),
-                    Choreographer.getInstance(), this::onInputEvent);
-
-            // Add a nav bar panel window
-            mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE);
-            mIsTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(
-                    Flags.TRACKPAD_GESTURE_FEATURES);
-            resetEdgeBackPlugin();
-            mPluginManager.addPluginListener(
-                    this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
-        }
-        // Update the ML model resources.
-        updateMLModelState();
-    }
-
     @Override
     public void onPluginConnected(NavigationEdgeBackPlugin plugin, Context context) {
         setEdgeBackPlugin(plugin);
@@ -704,9 +753,9 @@
     }
 
     private void updateMLModelState() {
-        boolean newState =
-                mIsGesturalModeEnabled && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                        SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false);
+        boolean newState = mIsGestureHandlingEnabled && DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false);
 
         if (newState == mUseMLModel) {
             return;
@@ -825,6 +874,15 @@
                 mDisplaySize.y - insets.bottom);
     }
 
+    private boolean isTrackpadDevice(int deviceId) {
+        InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
+        if (inputDevice == null) {
+            return false;
+        }
+        return inputDevice.getSources() == (InputDevice.SOURCE_MOUSE
+                | InputDevice.SOURCE_TOUCHPAD);
+    }
+
     private boolean desktopExcludeRegionContains(int x, int y) {
         return mDesktopModeExcludeRegion.contains(x, y);
     }
@@ -919,18 +977,21 @@
                 (int) mEndPoint.x, (int) mEndPoint.y,
                 mEdgeWidthLeft + mLeftInset,
                 mDisplaySize.x - (mEdgeWidthRight + mRightInset),
-                mUseMLModel ? mMLResults : -2, logPackageName);
+                mUseMLModel ? mMLResults : -2, logPackageName,
+                mIsTrackpadThreeFingerSwipe ? SysUiStatsLog.BACK_GESTURE__INPUT_TYPE__TRACKPAD
+                        : SysUiStatsLog.BACK_GESTURE__INPUT_TYPE__TOUCH);
     }
 
     private void onMotionEvent(MotionEvent ev) {
         int action = ev.getActionMasked();
-        boolean isTrackpadMultiFingerSwipe = isTrackpadMultiFingerSwipe(
-                mIsTrackpadGestureFeaturesEnabled, ev);
         if (action == MotionEvent.ACTION_DOWN) {
             if (DEBUG_MISSING_GESTURE) {
                 Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev);
             }
 
+            mIsTrackpadThreeFingerSwipe = isTrackpadThreeFingerSwipe(
+                    mIsTrackpadGestureFeaturesEnabled, ev);
+
             // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
             // ACTION_DOWN, in that case we should just reuse the old instance.
             mVelocityTracker.clear();
@@ -938,7 +999,7 @@
             // Verify if this is in within the touch region and we aren't in immersive mode, and
             // either the bouncer is showing or the notification panel is hidden
             mInputEventReceiver.setBatchingEnabled(false);
-            if (isTrackpadMultiFingerSwipe) {
+            if (mIsTrackpadThreeFingerSwipe) {
                 // Since trackpad gestures don't have zones, this will be determined later by the
                 // direction of the gesture. {@code mIsOnLeftEdge} is set to false to begin with.
                 mDeferSetIsOnLeftEdge = true;
@@ -950,20 +1011,27 @@
             mLogGesture = false;
             mInRejectedExclusion = false;
             boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY());
-            // Trackpad back gestures don't have zones, so we don't need to check if the down event
-            // is within insets.
-            mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed
-                    && (isTrackpadMultiFingerSwipe || isWithinInsets)
+            boolean isBackAllowedCommon = !mDisabledForQuickstep && mIsBackGestureAllowed
                     && !mGestureBlockingActivityRunning
-                    && !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
-                    && (isValidTrackpadBackGesture(isTrackpadMultiFingerSwipe)
-                        || isWithinTouchRegion((int) ev.getX(), (int) ev.getY()));
+                    && !QuickStepContract.isBackGestureDisabled(mSysUiFlags,
+                            mIsTrackpadThreeFingerSwipe)
+                    && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev);
+            if (mIsTrackpadThreeFingerSwipe) {
+                // Trackpad back gestures don't have zones, so we don't need to check if the down
+                // event is within insets.
+                mAllowGesture = isBackAllowedCommon && isValidTrackpadBackGesture(
+                        true /* isTrackpadEvent */);
+            } else {
+                mAllowGesture = isBackAllowedCommon && !mUsingThreeButtonNav && isWithinInsets
+                    && isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
+                    && !isButtonPressFromTrackpad(ev);
+            }
             if (mAllowGesture) {
                 mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
                 mEdgeBackPlugin.onMotionEvent(ev);
                 dispatchToBackAnimation(ev);
             }
-            if (mLogGesture || isTrackpadMultiFingerSwipe) {
+            if (mLogGesture || mIsTrackpadThreeFingerSwipe) {
                 mDownPoint.set(ev.getX(), ev.getY());
                 mEndPoint.set(-1, -1);
                 mThresholdCrossed = false;
@@ -974,20 +1042,20 @@
             mTmpLogDate.setTime(curTime);
             String curTimeStr = mLogDateFormat.format(mTmpLogDate);
             (isWithinInsets ? mGestureLogInsideInsets : mGestureLogOutsideInsets).log(String.format(
-                    "Gesture [%d [%s],alw=%B, mltf=%B, left=%B, defLeft=%B, backAlw=%B, disbld=%B,"
+                    "Gesture [%d [%s],alw=%B, t3fs=%B, left=%B, defLeft=%B, backAlw=%B, disbld=%B,"
                             + " qsDisbld=%b, blkdAct=%B, pip=%B,"
                             + " disp=%s, wl=%d, il=%d, wr=%d, ir=%d, excl=%s]",
-                    curTime, curTimeStr, mAllowGesture, isTrackpadMultiFingerSwipe,
+                    curTime, curTimeStr, mAllowGesture, mIsTrackpadThreeFingerSwipe,
                     mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed,
-                    QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisabledForQuickstep,
-                    mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
+                    QuickStepContract.isBackGestureDisabled(mSysUiFlags,
+                            mIsTrackpadThreeFingerSwipe),
+                    mDisabledForQuickstep, mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
                     mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion));
         } else if (mAllowGesture || mLogGesture) {
             if (!mThresholdCrossed) {
                 mEndPoint.x = (int) ev.getX();
                 mEndPoint.y = (int) ev.getY();
-                if (action == MotionEvent.ACTION_POINTER_DOWN && (!isTrackpadMultiFingerSwipe
-                        || isTrackpadFourFingerSwipe(mIsTrackpadGestureFeaturesEnabled, ev))) {
+                if (action == MotionEvent.ACTION_POINTER_DOWN && !mIsTrackpadThreeFingerSwipe) {
                     if (mAllowGesture) {
                         logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_MULTI_TOUCH);
                         if (DEBUG_MISSING_GESTURE) {
@@ -999,11 +1067,7 @@
                     mLogGesture = false;
                     return;
                 } else if (action == MotionEvent.ACTION_MOVE) {
-                    if (isTrackpadFourFingerSwipe(isTrackpadMultiFingerSwipe, ev)) {
-                        cancelGesture(ev);
-                        return;
-                    }
-                    if (isTrackpadMultiFingerSwipe && mDeferSetIsOnLeftEdge) {
+                    if (mIsTrackpadThreeFingerSwipe && mDeferSetIsOnLeftEdge) {
                         // mIsOnLeftEdge is determined by the relative position between the down
                         // and the current motion event for trackpad gestures instead of zoning.
                         mIsOnLeftEdge = mEndPoint.x > mDownPoint.x;
@@ -1061,8 +1125,13 @@
                 dispatchToBackAnimation(ev);
             }
         }
+    }
 
-        mProtoTracer.scheduleFrameUpdate();
+    private boolean isButtonPressFromTrackpad(MotionEvent ev) {
+        // We don't allow back for button press from the trackpad, and yet we do with a mouse.
+        int sources = InputManager.getInstance().getInputDevice(ev.getDeviceId()).getSources();
+        int sourceTrackpad = (SOURCE_MOUSE | SOURCE_TOUCHPAD);
+        return (sources & sourceTrackpad) == sourceTrackpad && ev.getButtonState() != 0;
     }
 
     private void dispatchToBackAnimation(MotionEvent event) {
@@ -1159,7 +1228,7 @@
         pw.println("  mIsEnabled=" + mIsEnabled);
         pw.println("  mIsAttached=" + mIsAttached);
         pw.println("  mIsBackGestureAllowed=" + mIsBackGestureAllowed);
-        pw.println("  mIsGesturalModeEnabled=" + mIsGesturalModeEnabled);
+        pw.println("  mIsGestureHandlingEnabled=" + mIsGestureHandlingEnabled);
         pw.println("  mIsNavBarShownTransiently=" + mIsNavBarShownTransiently);
         pw.println("  mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning);
         pw.println("  mAllowGesture=" + mAllowGesture);
@@ -1184,6 +1253,8 @@
         pw.println("  mPredictionLog=" + String.join("\n", mPredictionLog));
         pw.println("  mGestureLogInsideInsets=" + String.join("\n", mGestureLogInsideInsets));
         pw.println("  mGestureLogOutsideInsets=" + String.join("\n", mGestureLogOutsideInsets));
+        pw.println("  mIsTrackpadConnected=" + mIsTrackpadConnected);
+        pw.println("  mUsingThreeButtonNav=" + mUsingThreeButtonNav);
         pw.println("  mEdgeBackPlugin=" + mEdgeBackPlugin);
         if (mEdgeBackPlugin != null) {
             mEdgeBackPlugin.dump(pw);
@@ -1202,14 +1273,6 @@
         return topActivity != null && mGestureBlockingActivities.contains(topActivity);
     }
 
-    @Override
-    public void writeToProto(SystemUiTraceProto proto) {
-        if (proto.edgeBackGestureHandler == null) {
-            proto.edgeBackGestureHandler = new EdgeBackGestureHandlerProto();
-        }
-        proto.edgeBackGestureHandler.allowGesture = mAllowGesture;
-    }
-
     public void setBackAnimation(BackAnimation backAnimation) {
         mBackAnimation = backAnimation;
         updateBackAnimationThresholds();
@@ -1233,14 +1296,15 @@
         private final SysUiState mSysUiState;
         private final PluginManager mPluginManager;
         private final Executor mExecutor;
+        private final Handler mHandler;
         private final Executor mBackgroundExecutor;
         private final UserTracker mUserTracker;
-        private final ProtoTracer mProtoTracer;
         private final NavigationModeController mNavigationModeController;
         private final BackPanelController.Factory mBackPanelControllerFactory;
         private final ViewConfiguration mViewConfiguration;
         private final WindowManager mWindowManager;
         private final IWindowManager mWindowManagerService;
+        private final InputManager mInputManager;
         private final Optional<Pip> mPipOptional;
         private final Optional<DesktopMode> mDesktopModeOptional;
         private final FalsingManager mFalsingManager;
@@ -1255,14 +1319,15 @@
                        SysUiState sysUiState,
                        PluginManager pluginManager,
                        @Main Executor executor,
+                       @Main Handler handler,
                        @Background Executor backgroundExecutor,
                        UserTracker userTracker,
-                       ProtoTracer protoTracer,
                        NavigationModeController navigationModeController,
                        BackPanelController.Factory backPanelControllerFactory,
                        ViewConfiguration viewConfiguration,
                        WindowManager windowManager,
                        IWindowManager windowManagerService,
+                       InputManager inputManager,
                        Optional<Pip> pipOptional,
                        Optional<DesktopMode> desktopModeOptional,
                        FalsingManager falsingManager,
@@ -1275,14 +1340,15 @@
             mSysUiState = sysUiState;
             mPluginManager = pluginManager;
             mExecutor = executor;
+            mHandler = handler;
             mBackgroundExecutor = backgroundExecutor;
             mUserTracker = userTracker;
-            mProtoTracer = protoTracer;
             mNavigationModeController = navigationModeController;
             mBackPanelControllerFactory = backPanelControllerFactory;
             mViewConfiguration = viewConfiguration;
             mWindowManager = windowManager;
             mWindowManagerService = windowManagerService;
+            mInputManager = inputManager;
             mPipOptional = pipOptional;
             mDesktopModeOptional = desktopModeOptional;
             mFalsingManager = falsingManager;
@@ -1300,14 +1366,15 @@
                     mSysUiState,
                     mPluginManager,
                     mExecutor,
+                    mHandler,
                     mBackgroundExecutor,
                     mUserTracker,
-                    mProtoTracer,
                     mNavigationModeController,
                     mBackPanelControllerFactory,
                     mViewConfiguration,
                     mWindowManager,
                     mWindowManagerService,
+                    mInputManager,
                     mPipOptional,
                     mDesktopModeOptional,
                     mFalsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
index 50e8aa7..10a88c8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
@@ -16,27 +16,24 @@
 
 package com.android.systemui.navigationbar.gestural;
 
+import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT;
 import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
+import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
 
 import android.view.MotionEvent;
 
 public final class Utilities {
 
-    public static boolean isTrackpadMultiFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
+    public static boolean isTrackpadScroll(boolean isTrackpadGestureFeaturesEnabled,
             MotionEvent event) {
         return isTrackpadGestureFeaturesEnabled
-                && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE;
+                && event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE;
     }
 
     public static boolean isTrackpadThreeFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
             MotionEvent event) {
-        return isTrackpadMultiFingerSwipe(isTrackpadGestureFeaturesEnabled, event)
-                && event.getPointerCount() == 3;
-    }
-
-    public static boolean isTrackpadFourFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
-            MotionEvent event) {
-        return isTrackpadMultiFingerSwipe(isTrackpadGestureFeaturesEnabled, event)
-                && event.getPointerCount() == 4;
+        return isTrackpadGestureFeaturesEnabled
+                && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+                && event.getAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl b/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl
new file mode 100644
index 0000000..3e947d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask;
+
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.UserHandle;
+
+/** A service to help with controlling the state of notes app bubble through the system user. */
+interface INoteTaskBubblesService {
+
+    boolean areBubblesAvailable();
+
+    void showOrHideAppBubble(in Intent intent, in UserHandle userHandle, in Icon icon);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt
new file mode 100644
index 0000000..ec205f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.graphics.drawable.Icon
+import android.os.IBinder
+import android.os.UserHandle
+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 com.android.systemui.log.DebugLogger.debugLog
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/**
+ * A utility class to help interact with [Bubbles] as system user. The SysUI instance running as
+ * system user is the only instance that has the instance of [Bubbles] that manages the notes app
+ * bubble for all users.
+ *
+ * <p>Note: This class is made overridable so that a fake can be created for as mocking suspending
+ * functions is not supported by the Android tree's version of mockito.
+ */
+@SysUISingleton
+open class NoteTaskBubblesController
+@Inject
+constructor(
+    @Application private val context: Context,
+    @Background private val bgDispatcher: CoroutineDispatcher
+) {
+
+    private val serviceConnector: ServiceConnector<INoteTaskBubblesService> =
+        ServiceConnector.Impl(
+            context,
+            Intent(context, NoteTaskBubblesService::class.java),
+            Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+            UserHandle.USER_SYSTEM,
+            INoteTaskBubblesService.Stub::asInterface
+        )
+
+    /** Returns whether notes app bubble is supported. */
+    open suspend fun areBubblesAvailable(): Boolean =
+        withContext(bgDispatcher) {
+            suspendCoroutine { continuation ->
+                serviceConnector
+                    .postForResult { it.areBubblesAvailable() }
+                    .whenComplete { available, error ->
+                        if (error != null) {
+                            debugLog(error = error) { "Failed to query Bubbles as system user." }
+                        }
+                        continuation.resume(available ?: false)
+                    }
+            }
+        }
+
+    /** Calls the [Bubbles.showOrHideAppBubble] API as [UserHandle.USER_SYSTEM]. */
+    open suspend fun showOrHideAppBubble(
+        intent: Intent,
+        userHandle: UserHandle,
+        icon: Icon
+    ) {
+        withContext(bgDispatcher) {
+            serviceConnector
+                .post { it.showOrHideAppBubble(intent, userHandle, icon) }
+                .whenComplete { _, error ->
+                    if (error != null) {
+                        debugLog(error = error) {
+                            "Failed to show notes app bubble for intent $intent, " +
+                                "user $userHandle, and icon $icon."
+                        }
+                    } else {
+                        debugLog {
+                            "Call to show notes app bubble for intent $intent, " +
+                                "user $userHandle, and icon $icon successful."
+                        }
+                    }
+                }
+        }
+    }
+
+    /**
+     * A helper service to call [Bubbles] APIs that should always be called from the system user
+     * instance of SysUI.
+     *
+     * <p>Note: This service always runs in the SysUI process running on the system user
+     * irrespective of which user started the service. This is required so that the correct instance
+     * of {@link Bubbles} is injected. This is set via attribute {@code android:singleUser=”true”}
+     * in AndroidManifest.
+     */
+    class NoteTaskBubblesService
+    @Inject
+    constructor(private val mOptionalBubbles: Optional<Bubbles>) : Service() {
+
+        override fun onBind(intent: Intent): IBinder {
+            return object : INoteTaskBubblesService.Stub() {
+                override fun areBubblesAvailable() = mOptionalBubbles.isPresent
+
+                override fun showOrHideAppBubble(
+                    intent: Intent,
+                    userHandle: UserHandle,
+                    icon: Icon
+                ) {
+                    mOptionalBubbles.ifPresentOrElse(
+                        { bubbles -> bubbles.showOrHideAppBubble(intent, userHandle, icon) },
+                        {
+                            debugLog {
+                                "Failed to show or hide bubble for intent $intent," +
+                                    "user $user, and icon $icon as bubble is empty."
+                            }
+                        }
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index ccfbaf1..0842fe0 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -31,27 +31,31 @@
 import android.content.pm.PackageManager
 import android.content.pm.ShortcutManager
 import android.graphics.drawable.Icon
+import android.os.Process
 import android.os.UserHandle
 import android.os.UserManager
+import android.provider.Settings
 import android.widget.Toast
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
 import com.android.systemui.log.DebugLogger.debugLog
+import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
+import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
 import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser
 import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
-import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
-import com.android.systemui.util.kotlin.getOrNull
+import com.android.systemui.util.settings.SecureSettings
 import com.android.wm.shell.bubbles.Bubble
-import com.android.wm.shell.bubbles.Bubbles
 import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener
-import java.util.Optional
 import java.util.concurrent.atomic.AtomicReference
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
 /**
  * Entry point for creating and managing note.
@@ -69,13 +73,15 @@
     private val shortcutManager: ShortcutManager,
     private val resolver: NoteTaskInfoResolver,
     private val eventLogger: NoteTaskEventLogger,
-    private val optionalBubbles: Optional<Bubbles>,
+    private val noteTaskBubblesController: NoteTaskBubblesController,
     private val userManager: UserManager,
     private val keyguardManager: KeyguardManager,
     private val activityManager: ActivityManager,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
     private val devicePolicyManager: DevicePolicyManager,
     private val userTracker: UserTracker,
+    private val secureSettings: SecureSettings,
+    @Application private val applicationScope: CoroutineScope
 ) {
 
     @VisibleForTesting val infoReference = AtomicReference<NoteTaskInfo?>()
@@ -100,18 +106,6 @@
         }
     }
 
-    /** Starts [LaunchNoteTaskProxyActivity] on the given [user]. */
-    fun startNoteTaskProxyActivityForUser(user: UserHandle) {
-        context.startActivityAsUser(
-            Intent().apply {
-                component =
-                    ComponentName(context, LaunchNoteTaskManagedProfileProxyActivity::class.java)
-                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-            },
-            user
-        )
-    }
-
     /** Starts the notes role setting. */
     fun startNotesRoleSetting(activityContext: Context, entryPoint: NoteTaskEntryPoint?) {
         val user =
@@ -130,23 +124,26 @@
 
     /**
      * Returns the [UserHandle] of an android user that should handle the notes taking [entryPoint].
-     *
-     * On company owned personally enabled (COPE) devices, if the given [entryPoint] is in the
-     * [FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES] list, the default notes app in the work
-     * profile user will always be launched.
-     *
-     * On non managed devices or devices with other management modes, the current [UserHandle] is
-     * returned.
+     * 1. tail button entry point: In COPE or work profile devices, the user can select whether the
+     *    work or main profile notes app should be launched in the Settings app. In non-management
+     *    or device owner devices, the user can only select main profile notes app.
+     * 2. lock screen quick affordance: since there is no user setting, the main profile notes app
+     *    is used as default for work profile devices while the work profile notes app is used for
+     *    COPE devices.
+     * 3. Other entry point: the current user from [UserTracker.userHandle].
      */
     fun getUserForHandlingNotesTaking(entryPoint: NoteTaskEntryPoint): UserHandle =
-        if (
-            entryPoint in FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES &&
-                devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile
-        ) {
-            userTracker.userProfiles.firstOrNull { userManager.isManagedProfile(it.id) }?.userHandle
-                ?: userTracker.userHandle
-        } else {
-            userTracker.userHandle
+        when {
+            entryPoint == TAIL_BUTTON -> secureSettings.preferredUser
+            devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile &&
+                entryPoint == QUICK_AFFORDANCE -> {
+                userTracker.userProfiles
+                    .firstOrNull { userManager.isManagedProfile(it.id) }
+                    ?.userHandle
+                    ?: userTracker.userHandle
+            }
+            // On work profile devices, SysUI always run in the main user.
+            else -> userTracker.userHandle
         }
 
     /**
@@ -175,7 +172,19 @@
     ) {
         if (!isEnabled) return
 
-        val bubbles = optionalBubbles.getOrNull() ?: return
+        applicationScope.launch { awaitShowNoteTaskAsUser(entryPoint, user) }
+    }
+
+    private suspend fun awaitShowNoteTaskAsUser(
+        entryPoint: NoteTaskEntryPoint,
+        user: UserHandle,
+    ) {
+        if (!isEnabled) return
+
+        if (!noteTaskBubblesController.areBubblesAvailable()) {
+            debugLog { "Bubbles not available in the system user SysUI instance" }
+            return
+        }
 
         // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
         if (!userManager.isUserUnlocked) return
@@ -210,7 +219,7 @@
                     val intent = createNoteTaskIntent(info)
                     val icon =
                         Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
-                    bubbles.showOrHideAppBubble(intent, user, icon)
+                    noteTaskBubblesController.showOrHideAppBubble(intent, user, icon)
                     // App bubble logging happens on `onBubbleExpandChanged`.
                     debugLog { "onShowNoteTask - opened as app bubble: $info" }
                 }
@@ -264,15 +273,7 @@
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED
             }
 
-        // If the required user matches the tracking user, the injected context is already a context
-        // of the required user. Avoid calling #createContextAsUser because creating a context for
-        // a user takes time.
-        val userContext =
-            if (user == userTracker.userHandle) {
-                context
-            } else {
-                context.createContextAsUser(user, /* flags= */ 0)
-            }
+        val userContext = context.createContextAsUser(user, /* flags= */ 0)
 
         userContext.packageManager.setComponentEnabledSetting(
             componentName,
@@ -280,19 +281,61 @@
             PackageManager.DONT_KILL_APP,
         )
 
-        debugLog { "setNoteTaskShortcutEnabled - completed: $isEnabled" }
+        debugLog { "setNoteTaskShortcutEnabled for user $user- completed: $enabledState" }
+    }
+
+    /**
+     * Like [updateNoteTaskAsUser] but automatically apply to the current user and all its work
+     * profiles.
+     *
+     * @see updateNoteTaskAsUser
+     * @see UserTracker.userHandle
+     * @see UserTracker.userProfiles
+     */
+    fun updateNoteTaskForCurrentUserAndManagedProfiles() {
+        updateNoteTaskAsUser(userTracker.userHandle)
+        for (profile in userTracker.userProfiles) {
+            if (userManager.isManagedProfile(profile.id)) {
+                updateNoteTaskAsUser(profile.userHandle)
+            }
+        }
     }
 
     /**
      * Updates all [NoteTaskController] related information, including but not exclusively the
      * widget shortcut created by the [user] - by default it will use the current user.
      *
+     * If the user is not current user, the update will be dispatched to run in that user's process.
+     *
      * Keep in mind the shortcut API has a
      * [rate limiting](https://developer.android.com/develop/ui/views/launch/shortcuts/managing-shortcuts#rate-limiting)
      * and may not be updated in real-time. To reduce the chance of stale shortcuts, we run the
      * function during System UI initialization.
      */
     fun updateNoteTaskAsUser(user: UserHandle) {
+        if (!userManager.isUserUnlocked(user)) {
+            debugLog { "updateNoteTaskAsUser call but user locked: user=$user" }
+            return
+        }
+
+        // When switched to a secondary user, the sysUI is still running in the main user, we will
+        // need to update the shortcut in the secondary user.
+        if (user == getCurrentRunningUser()) {
+            updateNoteTaskAsUserInternal(user)
+        } else {
+            // TODO(b/278729185): Replace fire and forget service with a bounded service.
+            val intent = NoteTaskControllerUpdateService.createIntent(context)
+            context.startServiceAsUser(intent, user)
+        }
+    }
+
+    @InternalNoteTaskApi
+    fun updateNoteTaskAsUserInternal(user: UserHandle) {
+        if (!userManager.isUserUnlocked(user)) {
+            debugLog { "updateNoteTaskAsUserInternal call but user locked: user=$user" }
+            return
+        }
+
         val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user)
         val hasNotesRoleHolder = isEnabled && !packageName.isNullOrEmpty()
 
@@ -310,20 +353,25 @@
     /** @see OnRoleHoldersChangedListener */
     fun onRoleHoldersChanged(roleName: String, user: UserHandle) {
         if (roleName != ROLE_NOTES) return
-        if (!userManager.isUserUnlocked(user)) {
-            debugLog { "onRoleHoldersChanged call but user locked: role=$roleName, user=$user" }
-            return
-        }
 
-        if (user == userTracker.userHandle) {
-            updateNoteTaskAsUser(user)
-        } else {
-            // TODO(b/278729185): Replace fire and forget service with a bounded service.
-            val intent = NoteTaskControllerUpdateService.createIntent(context)
-            context.startServiceAsUser(intent, user)
-        }
+        updateNoteTaskAsUser(user)
     }
 
+    // Returns the [UserHandle] that this class is running on.
+    @VisibleForTesting internal fun getCurrentRunningUser(): UserHandle = Process.myUserHandle()
+
+    private val SecureSettings.preferredUser: UserHandle
+        get() {
+            val trackingUserId = userTracker.userHandle.identifier
+            val userId =
+                secureSettings.getIntForUser(
+                    /* name= */ Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
+                    /* def= */ trackingUserId,
+                    /* userHandle= */ trackingUserId,
+                )
+            return UserHandle.of(userId)
+        }
+
     companion object {
         val TAG = NoteTaskController::class.simpleName.orEmpty()
 
@@ -338,16 +386,6 @@
          * @see com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE
          */
         const val EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE = "extra_shortcut_badge_override_package"
-
-        /**
-         * A list of entry points which should be redirected to the work profile default notes app
-         * on company owned personally enabled (COPE) devices.
-         *
-         * Entry points in this list don't let users / admin to select the work or personal default
-         * notes app to be launched.
-         */
-        val FORCE_WORK_NOTE_APPS_ENTRY_POINTS_ON_COPE_DEVICES =
-            listOf(NoteTaskEntryPoint.TAIL_BUTTON, NoteTaskEntryPoint.QUICK_AFFORDANCE)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt
index 26b35cc..3e352af 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt
@@ -44,7 +44,7 @@
     override fun onCreate() {
         super.onCreate()
         // TODO(b/278729185): Replace fire and forget service with a bounded service.
-        controller.updateNoteTaskAsUser(user)
+        controller.updateNoteTaskAsUserInternal(user)
         stopSelf()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 221ff65..fe1034a 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -15,7 +15,10 @@
  */
 package com.android.systemui.notetask
 
+import android.app.role.OnRoleHoldersChangedListener
 import android.app.role.RoleManager
+import android.content.Context
+import android.content.pm.UserInfo
 import android.os.UserHandle
 import android.view.KeyEvent
 import android.view.KeyEvent.KEYCODE_N
@@ -54,6 +57,7 @@
         initializeHandleSystemKey()
         initializeOnRoleHoldersChanged()
         initializeOnUserUnlocked()
+        initializeUserTracker()
     }
 
     /**
@@ -79,7 +83,7 @@
     private fun initializeOnRoleHoldersChanged() {
         roleManager.addOnRoleHoldersChangedListenerAsUser(
             backgroundExecutor,
-            controller::onRoleHoldersChanged,
+            callbacks,
             UserHandle.ALL,
         )
     }
@@ -93,18 +97,41 @@
      */
     private fun initializeOnUserUnlocked() {
         if (keyguardUpdateMonitor.isUserUnlocked(userTracker.userId)) {
-            controller.setNoteTaskShortcutEnabled(true, userTracker.userHandle)
-        } else {
-            keyguardUpdateMonitor.registerCallback(onUserUnlockedCallback)
+            controller.updateNoteTaskForCurrentUserAndManagedProfiles()
         }
+        keyguardUpdateMonitor.registerCallback(callbacks)
     }
 
-    // KeyguardUpdateMonitor.registerCallback uses a weak reference, so we need a hard reference.
-    private val onUserUnlockedCallback =
-        object : KeyguardUpdateMonitorCallback() {
+    private fun initializeUserTracker() {
+        userTracker.addCallback(callbacks, backgroundExecutor)
+    }
+
+    // Some callbacks use a weak reference, so we play safe and keep a hard reference to them all.
+    private val callbacks =
+        object :
+            KeyguardUpdateMonitorCallback(),
+            CommandQueue.Callbacks,
+            UserTracker.Callback,
+            OnRoleHoldersChangedListener {
+
+            override fun handleSystemKey(key: KeyEvent) {
+                key.toNoteTaskEntryPointOrNull()?.let(controller::showNoteTask)
+            }
+
+            override fun onRoleHoldersChanged(roleName: String, user: UserHandle) {
+                controller.onRoleHoldersChanged(roleName, user)
+            }
+
             override fun onUserUnlocked() {
-                controller.setNoteTaskShortcutEnabled(true, userTracker.userHandle)
-                keyguardUpdateMonitor.removeCallback(this)
+                controller.updateNoteTaskForCurrentUserAndManagedProfiles()
+            }
+
+            override fun onUserChanged(newUser: Int, userContext: Context) {
+                controller.updateNoteTaskForCurrentUserAndManagedProfiles()
+            }
+
+            override fun onProfilesChanged(profiles: List<UserInfo>) {
+                controller.updateNoteTaskForCurrentUserAndManagedProfiles()
             }
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index 109cfee..c0e688f 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
-import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -40,12 +39,12 @@
     @[Binds IntoMap ClassKey(NoteTaskControllerUpdateService::class)]
     fun NoteTaskControllerUpdateService.bindNoteTaskControllerUpdateService(): Service
 
+    @[Binds IntoMap ClassKey(NoteTaskBubblesController.NoteTaskBubblesService::class)]
+    fun NoteTaskBubblesController.NoteTaskBubblesService.bindNoteTaskBubblesService(): Service
+
     @[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
     fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity
 
-    @[Binds IntoMap ClassKey(LaunchNoteTaskManagedProfileProxyActivity::class)]
-    fun LaunchNoteTaskManagedProfileProxyActivity.bindNoteTaskLauncherProxyActivity(): Activity
-
     @[Binds IntoMap ClassKey(LaunchNotesRoleSettingsTrampolineActivity::class)]
     fun LaunchNotesRoleSettingsTrampolineActivity.bindLaunchNotesRoleSettingsTrampolineActivity():
         Activity
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
index 754c365..63d4634 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
@@ -19,6 +19,7 @@
 import android.app.role.RoleManager
 import android.app.role.RoleManager.ROLE_NOTES
 import android.content.Context
+import android.content.pm.PackageManager
 import android.content.pm.ShortcutInfo
 import android.graphics.drawable.Icon
 import android.os.PersistableBundle
@@ -42,21 +43,47 @@
         context: Context,
         user: UserHandle,
     ): ShortcutInfo {
+        val packageName = getDefaultRoleHolderAsUser(ROLE_NOTES, user)
+
         val extras = PersistableBundle()
-        getDefaultRoleHolderAsUser(ROLE_NOTES, user)?.let { packageName ->
+        if (packageName != null) {
             // Set custom app badge using the icon from ROLES_NOTES default app.
             extras.putString(NoteTaskController.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE, packageName)
         }
 
+        val shortLabel = context.getString(R.string.note_task_button_label)
+
+        val applicationLabel = context.packageManager.getApplicationLabel(packageName)
+        val longLabel =
+            if (applicationLabel == null) {
+                shortLabel
+            } else {
+                context.getString(
+                    R.string.note_task_shortcut_long_label,
+                    applicationLabel,
+                )
+            }
+
         val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
 
         return ShortcutInfo.Builder(context, NoteTaskController.SHORTCUT_ID)
             .setIntent(LaunchNoteTaskActivity.createIntent(context))
             .setActivity(LaunchNoteTaskActivity.createComponent(context))
-            .setShortLabel(context.getString(R.string.note_task_button_label))
+            .setShortLabel(shortLabel)
+            .setLongLabel(longLabel)
             .setLongLived(true)
             .setIcon(icon)
             .setExtras(extras)
             .build()
     }
+
+    private fun PackageManager.getApplicationLabel(packageName: String?): String? {
+        if (packageName == null) {
+            return null
+        }
+
+        return runCatching { getApplicationInfo(packageName, /* flags= */ 0)!! }
+            .getOrNull()
+            ?.let { info -> getApplicationLabel(info).toString() }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
index 7ef149d..d00a79e 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -20,67 +20,24 @@
 import android.content.Context
 import android.content.Intent
 import android.os.Bundle
-import android.os.UserHandle
-import android.os.UserManager
 import androidx.activity.ComponentActivity
-import com.android.systemui.log.DebugLogger.debugLog
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEntryPoint
-import com.android.systemui.settings.UserTracker
 import javax.inject.Inject
 
 /** Activity responsible for launching the note experience, and finish. */
-class LaunchNoteTaskActivity
-@Inject
-constructor(
-    private val controller: NoteTaskController,
-    private val userManager: UserManager,
-    private val userTracker: UserTracker,
-) : ComponentActivity() {
+class LaunchNoteTaskActivity @Inject constructor(private val controller: NoteTaskController) :
+    ComponentActivity() {
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-
-        // Under the hood, notes app shortcuts are shown in a floating window, called Bubble.
-        // Bubble API is only available in the main user but not work profile.
-        //
-        // On devices with work profile (WP), SystemUI provides both personal notes app shortcuts &
-        // work profile notes app shortcuts. In order to make work profile notes app shortcuts to
-        // show in Bubble, a few redirections across users are required:
-        // 1. When `LaunchNoteTaskActivity` is started in the work profile user, we launch
-        //    `LaunchNoteTaskManagedProfileProxyActivity` on the main user, which has access to the
-        //    Bubble API.
-        // 2. `LaunchNoteTaskManagedProfileProxyActivity` calls `Bubble#showOrHideAppBubble` with
-        //     the work profile user ID.
-        // 3. Bubble renders the work profile notes app activity in a floating window, which is
-        //    hosted in the main user.
-        //
-        //            WP                                main user
-        //  ------------------------          -------------------------------------------
-        // | LaunchNoteTaskActivity |   ->   | LaunchNoteTaskManagedProfileProxyActivity |
-        //  ------------------------          -------------------------------------------
-        //                                                        |
-        //                 main user                              |
-        //         ----------------------------                   |
-        //        | Bubble#showOrHideAppBubble |   <--------------
-        //        |      (with WP user ID)     |
-        //         ----------------------------
-        val mainUser: UserHandle? = userManager.mainUser
-        if (userManager.isManagedProfile) {
-            if (mainUser == null) {
-                debugLog { "Can't find the main user. Skipping the notes app launch." }
+        val entryPoint =
+            if (isInMultiWindowMode) {
+                NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
             } else {
-                controller.startNoteTaskProxyActivityForUser(mainUser)
+                NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
             }
-        } else {
-            val entryPoint =
-                if (isInMultiWindowMode) {
-                    NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
-                } else {
-                    NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
-                }
-            controller.showNoteTask(entryPoint)
-        }
+        controller.showNoteTaskAsUser(entryPoint, user)
         finish()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivity.kt
deleted file mode 100644
index 3259b0d..0000000
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivity.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.notetask.shortcut
-
-import android.os.Build
-import android.os.Bundle
-import android.os.UserManager
-import android.util.Log
-import androidx.activity.ComponentActivity
-import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.notetask.NoteTaskEntryPoint
-import com.android.systemui.settings.UserTracker
-import javax.inject.Inject
-
-/**
- * An internal proxy activity that starts notes app in the work profile.
- *
- * If there is no work profile, this activity finishes gracefully.
- *
- * This activity MUST NOT be exported because that would expose the INTERACT_ACROSS_USER privilege
- * to any apps.
- */
-class LaunchNoteTaskManagedProfileProxyActivity
-@Inject
-constructor(
-    private val controller: NoteTaskController,
-    private val userTracker: UserTracker,
-    private val userManager: UserManager,
-) : ComponentActivity() {
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-
-        val managedProfileUser =
-            userTracker.userProfiles.firstOrNull { userManager.isManagedProfile(it.id) }
-
-        if (managedProfileUser == null) {
-            logDebug { "Fail to find the work profile user." }
-        } else {
-            controller.showNoteTaskAsUser(
-                entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT,
-                user = managedProfileUser.userHandle
-            )
-        }
-        finish()
-    }
-}
-
-private inline fun logDebug(message: () -> String) {
-    if (Build.IS_DEBUGGABLE) {
-        Log.d(NoteTaskController.TAG, message())
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
deleted file mode 100644
index 7f0f894..0000000
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ /dev/null
@@ -1,92 +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.people;
-
-import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
-import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.ViewGroup;
-
-import androidx.activity.ComponentActivity;
-import androidx.lifecycle.ViewModelProvider;
-
-import com.android.systemui.compose.ComposeFacade;
-import com.android.systemui.people.ui.view.PeopleViewBinder;
-import com.android.systemui.people.ui.viewmodel.PeopleViewModel;
-
-import javax.inject.Inject;
-
-import kotlin.Unit;
-import kotlin.jvm.functions.Function1;
-
-/** People Tile Widget configuration activity that shows the user their conversation tiles. */
-public class PeopleSpaceActivity extends ComponentActivity {
-
-    private static final String TAG = "PeopleSpaceActivity";
-    private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
-
-    private final PeopleViewModel.Factory mViewModelFactory;
-
-    @Inject
-    public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory) {
-        super();
-        mViewModelFactory = viewModelFactory;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setResult(RESULT_CANCELED);
-
-        PeopleViewModel viewModel = new ViewModelProvider(this, mViewModelFactory).get(
-                PeopleViewModel.class);
-
-        // Update the widget ID coming from the intent.
-        int widgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID);
-        viewModel.onWidgetIdChanged(widgetId);
-
-        Function1<PeopleViewModel.Result, Unit> onResult = (result) -> {
-            finishActivity(result);
-            return null;
-        };
-
-        if (ComposeFacade.INSTANCE.isComposeAvailable()) {
-            Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity");
-            ComposeFacade.INSTANCE.setPeopleSpaceActivityContent(this, viewModel, onResult);
-        } else {
-            Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity");
-            ViewGroup view = PeopleViewBinder.create(this);
-            PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this, onResult);
-            setContentView(view);
-        }
-    }
-
-    private void finishActivity(PeopleViewModel.Result result) {
-        if (result instanceof PeopleViewModel.Result.Success) {
-            if (DEBUG) Log.d(TAG, "Widget added!");
-            Intent data = ((PeopleViewModel.Result.Success) result).getData();
-            setResult(RESULT_OK, data);
-        } else {
-            if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!");
-            setResult(RESULT_CANCELED);
-        }
-        finish();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt
new file mode 100644
index 0000000..5b7eb45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people
+
+import android.appwidget.AppWidgetManager
+import android.os.Bundle
+import android.util.Log
+import androidx.activity.ComponentActivity
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.compose.ComposeFacade.isComposeAvailable
+import com.android.systemui.compose.ComposeFacade.setPeopleSpaceActivityContent
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.people.ui.view.PeopleViewBinder
+import com.android.systemui.people.ui.view.PeopleViewBinder.bind
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.launch
+
+/** People Tile Widget configuration activity that shows the user their conversation tiles. */
+class PeopleSpaceActivity
+@Inject
+constructor(
+    private val viewModelFactory: PeopleViewModel.Factory,
+    private val featureFlags: FeatureFlags,
+) : ComponentActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setResult(RESULT_CANCELED)
+
+        // Update the widget ID coming from the intent.
+        val viewModel = ViewModelProvider(this, viewModelFactory)[PeopleViewModel::class.java]
+        val widgetId =
+            intent.getIntExtra(
+                AppWidgetManager.EXTRA_APPWIDGET_ID,
+                AppWidgetManager.INVALID_APPWIDGET_ID,
+            )
+        viewModel.onWidgetIdChanged(widgetId)
+
+        // Make sure to refresh the tiles/conversations when the lifecycle is resumed, so that it
+        // updates them when going back to the Activity after leaving it.
+        // Note that we do this here instead of inside an effect in the PeopleScreen() composable
+        // because otherwise onTileRefreshRequested() will be called after the first composition,
+        // which will trigger a new recomposition and redraw, affecting the GPU memory (see
+        // b/276871425).
+        lifecycleScope.launch {
+            repeatOnLifecycle(Lifecycle.State.RESUMED) { viewModel.onTileRefreshRequested() }
+        }
+
+        // Set the content of the activity, using either the View or Compose implementation.
+        if (featureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE) && isComposeAvailable()) {
+            Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity")
+            setPeopleSpaceActivityContent(
+                activity = this,
+                viewModel,
+                onResult = { finishActivity(it) },
+            )
+        } else {
+            Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity")
+            val view = PeopleViewBinder.create(this)
+            bind(view, viewModel, lifecycleOwner = this, onResult = { finishActivity(it) })
+            setContentView(view)
+        }
+    }
+
+    private fun finishActivity(result: PeopleViewModel.Result) {
+        if (result is PeopleViewModel.Result.Success) {
+            if (DEBUG) Log.d(TAG, "Widget added!")
+            setResult(RESULT_OK, result.data)
+        } else {
+            if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!")
+            setResult(RESULT_CANCELED)
+        }
+
+        finish()
+    }
+
+    companion object {
+        private const val TAG = "PeopleSpaceActivity"
+        private const val DEBUG = PeopleSpaceUtils.DEBUG
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
index d8a429e..46c8d35 100644
--- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
@@ -109,14 +109,6 @@
                     }
             }
         }
-
-        // Make sure to refresh the tiles/conversations when the Activity is resumed, so that it
-        // updates them when going back to the Activity after leaving it.
-        lifecycleOwner.lifecycleScope.launch {
-            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
-                viewModel.onTileRefreshRequested()
-            }
-        }
     }
 
     private fun setNoConversationsContent(view: ViewGroup, onGotItClicked: () -> Unit) {
@@ -140,13 +132,13 @@
             LayoutInflater.from(context)
                 .inflate(R.layout.people_space_activity_no_conversations, /* root= */ view)
 
-        noConversationsView.findViewById<View>(R.id.got_it_button).setOnClickListener {
+        noConversationsView.requireViewById<View>(R.id.got_it_button).setOnClickListener {
             onGotItClicked()
         }
 
         // The Tile preview has colorBackground as its background. Change it so it's different than
         // the activity's background.
-        val item = noConversationsView.findViewById<LinearLayout>(android.R.id.background)
+        val item = noConversationsView.requireViewById<LinearLayout>(android.R.id.background)
         val shape = item.background as GradientDrawable
         val ta =
             context.theme.obtainStyledAttributes(
diff --git a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
index b2e04bb..69cb611 100644
--- a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
@@ -26,6 +26,8 @@
 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.dagger.qualifiers.Application
+import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
@@ -34,13 +36,18 @@
 interface PowerRepository {
     /** Whether the device is interactive. Starts with the current state. */
     val isInteractive: Flow<Boolean>
+
+    /** Wakes up the device. */
+    fun wakeUp(why: String, @PowerManager.WakeReason wakeReason: Int)
 }
 
 @SysUISingleton
 class PowerRepositoryImpl
 @Inject
 constructor(
-    manager: PowerManager,
+    private val manager: PowerManager,
+    @Application private val applicationContext: Context,
+    private val systemClock: SystemClock,
     dispatcher: BroadcastDispatcher,
 ) : PowerRepository {
 
@@ -68,6 +75,14 @@
         awaitClose { dispatcher.unregisterReceiver(receiver) }
     }
 
+    override fun wakeUp(why: String, wakeReason: Int) {
+        manager.wakeUp(
+            systemClock.uptimeMillis(),
+            wakeReason,
+            "${applicationContext.packageName}:$why",
+        )
+    }
+
     companion object {
         private const val TAG = "PowerRepository"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index 3f799f7..809edc0 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -17,8 +17,13 @@
 
 package com.android.systemui.power.domain.interactor
 
+import android.os.PowerManager
+import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.data.repository.PowerRepository
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 
@@ -27,8 +32,58 @@
 class PowerInteractor
 @Inject
 constructor(
-    repository: PowerRepository,
+    private val repository: PowerRepository,
+    private val keyguardRepository: KeyguardRepository,
+    private val falsingCollector: FalsingCollector,
+    private val screenOffAnimationController: ScreenOffAnimationController,
+    private val statusBarStateController: StatusBarStateController,
 ) {
     /** Whether the screen is on or off. */
     val isInteractive: Flow<Boolean> = repository.isInteractive
+
+    /**
+     * Wakes up the device if the device was dozing.
+     *
+     * @param why a string explaining why we're waking the device for debugging purposes. Should be
+     *   in SCREAMING_SNAKE_CASE.
+     * @param wakeReason the PowerManager-based reason why we're waking the device.
+     */
+    fun wakeUpIfDozing(why: String, @PowerManager.WakeReason wakeReason: Int) {
+        if (
+            statusBarStateController.isDozing && screenOffAnimationController.allowWakeUpIfDozing()
+        ) {
+            repository.wakeUp(why, wakeReason)
+            falsingCollector.onScreenOnFromTouch()
+        }
+    }
+
+    /**
+     * Wakes up the device if the device was dozing or going to sleep in order to display a
+     * full-screen intent.
+     */
+    fun wakeUpForFullScreenIntent() {
+        if (
+            keyguardRepository.wakefulness.value.isStartingToSleep() ||
+                statusBarStateController.isDozing
+        ) {
+            repository.wakeUp(why = FSI_WAKE_WHY, wakeReason = PowerManager.WAKE_REASON_APPLICATION)
+        }
+    }
+
+    /**
+     * Wakes up the device if dreaming with a screensaver.
+     *
+     * @param why a string explaining why we're waking the device for debugging purposes. Should be
+     *   in SCREAMING_SNAKE_CASE.
+     * @param wakeReason the PowerManager-based reason why we're waking the device.
+     */
+    fun wakeUpIfDreaming(why: String, @PowerManager.WakeReason wakeReason: Int) {
+        if (statusBarStateController.isDreaming) {
+            repository.wakeUp(why, wakeReason)
+        }
+    }
+
+    companion object {
+        private const val FSI_WAKE_WHY = "full_screen_intent"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 79167f2..310d234 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -15,6 +15,8 @@
 package com.android.systemui.privacy
 
 import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.res.Configuration
 import android.util.AttributeSet
 import android.view.Gravity.CENTER_VERTICAL
 import android.view.Gravity.END
@@ -25,7 +27,7 @@
 import android.widget.LinearLayout
 import com.android.settingslib.Utils
 import com.android.systemui.R
-import com.android.systemui.animation.LaunchableFrameLayout
+import com.android.systemui.animation.view.LaunchableFrameLayout
 import com.android.systemui.statusbar.events.BackgroundAnimatableView
 
 class OngoingPrivacyChip @JvmOverloads constructor(
@@ -35,6 +37,7 @@
     defStyleRes: Int = 0
 ) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
 
+    private var configuration: Configuration
     private var iconMargin = 0
     private var iconSize = 0
     private var iconColor = 0
@@ -54,6 +57,7 @@
         clipChildren = true
         clipToPadding = true
         iconsContainer = requireViewById(R.id.icons_container)
+        configuration = Configuration(context.resources.configuration)
         updateResources()
     }
 
@@ -102,6 +106,17 @@
                 R.string.ongoing_privacy_chip_content_multiple_apps, typesText)
     }
 
+    override fun onConfigurationChanged(newConfig: Configuration?) {
+        super.onConfigurationChanged(newConfig)
+        if (newConfig != null) {
+            val diff = newConfig.diff(configuration)
+            configuration.setTo(newConfig)
+            if (diff.and(ActivityInfo.CONFIG_DENSITY.or(ActivityInfo.CONFIG_FONT_SCALE)) != 0) {
+                updateResources()
+            }
+        }
+    }
+
     private fun updateResources() {
         iconMargin = context.resources
                 .getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_margin)
@@ -110,8 +125,11 @@
         iconColor =
                 Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
 
+        val height = context.resources
+                .getDimensionPixelSize(R.dimen.ongoing_appops_chip_height)
         val padding = context.resources
                 .getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
+        iconsContainer.layoutParams.height = height
         iconsContainer.setPaddingRelative(padding, 0, padding, 0)
         iconsContainer.background = context.getDrawable(R.drawable.statusbar_privacy_chip_bg)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
new file mode 100644
index 0000000..fdc70a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.privacy
+
+import android.Manifest
+import android.app.ActivityManager
+import android.app.Dialog
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import android.permission.PermissionGroupUsage
+import android.permission.PermissionManager
+import android.view.View
+import androidx.annotation.MainThread
+import androidx.annotation.WorkerThread
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.appops.AppOpsController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.privacy.logging.PrivacyLogger
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+private val defaultDialogProvider =
+    object : PrivacyDialogControllerV2.DialogProvider {
+        override fun makeDialog(
+            context: Context,
+            list: List<PrivacyDialogV2.PrivacyElement>,
+            manageApp: (String, Int, Intent) -> Unit,
+            closeApp: (String, Int) -> Unit,
+            openPrivacyDashboard: () -> Unit
+        ): PrivacyDialogV2 {
+            return PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+        }
+    }
+
+/**
+ * Controller for [PrivacyDialogV2].
+ *
+ * This controller shows and dismissed the dialog, as well as determining the information to show in
+ * it.
+ */
+@SysUISingleton
+class PrivacyDialogControllerV2(
+    private val permissionManager: PermissionManager,
+    private val packageManager: PackageManager,
+    private val privacyItemController: PrivacyItemController,
+    private val userTracker: UserTracker,
+    private val activityStarter: ActivityStarter,
+    private val backgroundExecutor: Executor,
+    private val uiExecutor: Executor,
+    private val privacyLogger: PrivacyLogger,
+    private val keyguardStateController: KeyguardStateController,
+    private val appOpsController: AppOpsController,
+    private val uiEventLogger: UiEventLogger,
+    private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val dialogProvider: DialogProvider
+) {
+
+    @Inject
+    constructor(
+        permissionManager: PermissionManager,
+        packageManager: PackageManager,
+        privacyItemController: PrivacyItemController,
+        userTracker: UserTracker,
+        activityStarter: ActivityStarter,
+        @Background backgroundExecutor: Executor,
+        @Main uiExecutor: Executor,
+        privacyLogger: PrivacyLogger,
+        keyguardStateController: KeyguardStateController,
+        appOpsController: AppOpsController,
+        uiEventLogger: UiEventLogger,
+        dialogLaunchAnimator: DialogLaunchAnimator
+    ) : this(
+        permissionManager,
+        packageManager,
+        privacyItemController,
+        userTracker,
+        activityStarter,
+        backgroundExecutor,
+        uiExecutor,
+        privacyLogger,
+        keyguardStateController,
+        appOpsController,
+        uiEventLogger,
+        dialogLaunchAnimator,
+        defaultDialogProvider
+    )
+
+    private var dialog: Dialog? = null
+
+    private val onDialogDismissed =
+        object : PrivacyDialogV2.OnDialogDismissed {
+            override fun onDialogDismissed() {
+                privacyLogger.logPrivacyDialogDismissed()
+                uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
+                dialog = null
+            }
+        }
+
+    @WorkerThread
+    private fun closeApp(packageName: String, userId: Int) {
+        uiEventLogger.log(
+            PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP,
+            userId,
+            packageName
+        )
+        privacyLogger.logCloseAppFromDialog(packageName, userId)
+        ActivityManager.getService().stopAppForUser(packageName, userId)
+    }
+
+    @MainThread
+    private fun manageApp(packageName: String, userId: Int, navigationIntent: Intent) {
+        uiEventLogger.log(
+            PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
+            userId,
+            packageName
+        )
+        privacyLogger.logStartSettingsActivityFromDialog(packageName, userId)
+        startActivity(navigationIntent)
+    }
+
+    @MainThread
+    private fun openPrivacyDashboard() {
+        uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_CLICK_TO_PRIVACY_DASHBOARD)
+        privacyLogger.logStartPrivacyDashboardFromDialog()
+        startActivity(Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE))
+    }
+
+    @MainThread
+    private fun startActivity(navigationIntent: Intent) {
+        if (!keyguardStateController.isUnlocked) {
+            // If we are locked, hide the dialog so the user can unlock
+            dialog?.hide()
+        }
+        // startActivity calls internally startActivityDismissingKeyguard
+        activityStarter.startActivity(navigationIntent, true) {
+            if (ActivityManager.isStartResultSuccessful(it)) {
+                dismissDialog()
+            } else {
+                dialog?.show()
+            }
+        }
+    }
+
+    @WorkerThread
+    private fun getStartViewPermissionUsageIntent(
+        packageName: String,
+        permGroupName: String,
+        attributionTag: CharSequence?,
+        isAttributionSupported: Boolean
+    ): Intent? {
+        if (attributionTag != null && isAttributionSupported) {
+            val intent = Intent(Intent.ACTION_MANAGE_PERMISSION_USAGE)
+            intent.setPackage(packageName)
+            intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, permGroupName)
+            intent.putExtra(Intent.EXTRA_ATTRIBUTION_TAGS, arrayOf(attributionTag.toString()))
+            intent.putExtra(Intent.EXTRA_SHOWING_ATTRIBUTION, true)
+            val resolveInfo =
+                packageManager.resolveActivity(intent, PackageManager.ResolveInfoFlags.of(0))
+            if (
+                resolveInfo?.activityInfo?.permission ==
+                    Manifest.permission.START_VIEW_PERMISSION_USAGE
+            ) {
+                intent.component = ComponentName(packageName, resolveInfo.activityInfo.name)
+                return intent
+            }
+        }
+        return null
+    }
+
+    fun getDefaultManageAppPermissionsIntent(packageName: String, userId: Int): Intent {
+        val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+        intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
+        return intent
+    }
+
+    @WorkerThread
+    private fun permGroupUsage(): List<PermissionGroupUsage> {
+        return permissionManager.getIndicatorAppOpUsageData(appOpsController.isMicMuted)
+    }
+
+    /**
+     * Show the [PrivacyDialogV2]
+     *
+     * This retrieves the permission usage from [PermissionManager] and creates a new
+     * [PrivacyDialogV2] with a list of [PrivacyDialogV2.PrivacyElement] to show.
+     *
+     * This list will be filtered by [filterAndSelect]. Only types available by
+     * [PrivacyItemController] will be shown.
+     *
+     * @param context A context to use to create the dialog.
+     * @see filterAndSelect
+     */
+    fun showDialog(context: Context, view: View? = null) {
+        dismissDialog()
+        backgroundExecutor.execute {
+            val usage = permGroupUsage()
+            val userInfos = userTracker.userProfiles
+            privacyLogger.logUnfilteredPermGroupUsage(usage)
+            val items =
+                usage.mapNotNull {
+                    val userInfo =
+                        userInfos.firstOrNull { ui -> ui.id == UserHandle.getUserId(it.uid) }
+                    if (
+                        isAvailable(it.permissionGroupName) && (userInfo != null || it.isPhoneCall)
+                    ) {
+                        // Only try to get the app name if we actually need it
+                        val appName =
+                            if (it.isPhoneCall) {
+                                ""
+                            } else {
+                                getLabelForPackage(it.packageName, it.uid)
+                            }
+                        val userId = UserHandle.getUserId(it.uid)
+                        val viewUsageIntent =
+                            getStartViewPermissionUsageIntent(
+                                it.packageName,
+                                it.permissionGroupName,
+                                it.attributionTag,
+                                // attributionLabel is set only when subattribution policies
+                                // are supported and satisfied
+                                it.attributionLabel != null
+                            )
+                        PrivacyDialogV2.PrivacyElement(
+                            permGroupToPrivacyType(it.permissionGroupName)!!,
+                            it.packageName,
+                            userId,
+                            appName,
+                            it.attributionTag,
+                            it.attributionLabel,
+                            it.proxyLabel,
+                            it.lastAccessTimeMillis,
+                            it.isActive,
+                            it.isPhoneCall,
+                            viewUsageIntent != null,
+                            it.permissionGroupName,
+                            viewUsageIntent
+                                ?: getDefaultManageAppPermissionsIntent(it.packageName, userId)
+                        )
+                    } else {
+                        null
+                    }
+                }
+            uiExecutor.execute {
+                val elements = filterAndSelect(items)
+                if (elements.isNotEmpty()) {
+                    val d =
+                        dialogProvider.makeDialog(
+                            context,
+                            elements,
+                            this::manageApp,
+                            this::closeApp,
+                            this::openPrivacyDashboard
+                        )
+                    d.setShowForAllUsers(true)
+                    d.addOnDismissListener(onDialogDismissed)
+                    if (view != null) {
+                        dialogLaunchAnimator.showFromView(d, view)
+                    } else {
+                        d.show()
+                    }
+                    privacyLogger.logShowDialogV2Contents(elements)
+                    dialog = d
+                } else {
+                    privacyLogger.logEmptyDialog()
+                }
+            }
+        }
+    }
+
+    /** Dismisses the dialog */
+    fun dismissDialog() {
+        dialog?.dismiss()
+    }
+
+    @WorkerThread
+    private fun getLabelForPackage(packageName: String, uid: Int): CharSequence {
+        return try {
+            packageManager
+                .getApplicationInfoAsUser(packageName, 0, UserHandle.getUserId(uid))
+                .loadLabel(packageManager)
+        } catch (_: PackageManager.NameNotFoundException) {
+            privacyLogger.logLabelNotFound(packageName)
+            packageName
+        }
+    }
+
+    private fun permGroupToPrivacyType(group: String): PrivacyType? {
+        return when (group) {
+            Manifest.permission_group.CAMERA -> PrivacyType.TYPE_CAMERA
+            Manifest.permission_group.MICROPHONE -> PrivacyType.TYPE_MICROPHONE
+            Manifest.permission_group.LOCATION -> PrivacyType.TYPE_LOCATION
+            else -> null
+        }
+    }
+
+    private fun isAvailable(group: String): Boolean {
+        return when (group) {
+            Manifest.permission_group.CAMERA -> privacyItemController.micCameraAvailable
+            Manifest.permission_group.MICROPHONE -> privacyItemController.micCameraAvailable
+            Manifest.permission_group.LOCATION -> privacyItemController.locationAvailable
+            else -> false
+        }
+    }
+
+    /**
+     * Filters the list of elements to show.
+     *
+     * For each privacy type, it'll return all active elements. If there are no active elements,
+     * it'll return the most recent access
+     */
+    private fun filterAndSelect(
+        list: List<PrivacyDialogV2.PrivacyElement>
+    ): List<PrivacyDialogV2.PrivacyElement> {
+        return list
+            .groupBy { it.type }
+            .toSortedMap()
+            .flatMap { (_, elements) ->
+                val actives = elements.filter { it.isActive }
+                if (actives.isNotEmpty()) {
+                    actives.sortedByDescending { it.lastActiveTimestamp }
+                } else {
+                    elements.maxByOrNull { it.lastActiveTimestamp }?.let { listOf(it) }
+                        ?: emptyList()
+                }
+            }
+    }
+
+    /**
+     * Interface to create a [PrivacyDialogV2].
+     *
+     * Can be used to inject a mock creator.
+     */
+    interface DialogProvider {
+        /** Create a [PrivacyDialogV2]. */
+        fun makeDialog(
+            context: Context,
+            list: List<PrivacyDialogV2.PrivacyElement>,
+            manageApp: (String, Int, Intent) -> Unit,
+            closeApp: (String, Int) -> Unit,
+            openPrivacyDashboard: () -> Unit
+        ): PrivacyDialogV2
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
index 3ecc5a5..250976c 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
@@ -18,13 +18,20 @@
 
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.UiEventLogger.UiEventEnum.RESERVE_NEW_UI_EVENT_ID
 
 enum class PrivacyDialogEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
     @UiEvent(doc = "Privacy dialog is clicked by user to go to the app settings page.")
     PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS(904),
 
     @UiEvent(doc = "Privacy dialog is dismissed by user.")
-    PRIVACY_DIALOG_DISMISSED(905);
+    PRIVACY_DIALOG_DISMISSED(905),
+
+    @UiEvent(doc = "Privacy dialog item is clicked by user to close the app using a sensor.")
+    PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP(1396),
+
+    @UiEvent(doc = "Privacy dialog is clicked by user to see the privacy dashboard.")
+    PRIVACY_DIALOG_CLICK_TO_PRIVACY_DASHBOARD(1397);
 
     override fun getId() = _id
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
new file mode 100644
index 0000000..c202f14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.privacy
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageItemInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.content.res.Resources.NotFoundException
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.os.Bundle
+import android.text.TextUtils
+import android.util.Log
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.annotation.ColorInt
+import androidx.annotation.DrawableRes
+import androidx.annotation.WorkerThread
+import androidx.core.view.ViewCompat
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.animation.ViewHierarchyAnimator
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.maybeForceFullscreen
+import java.lang.ref.WeakReference
+import java.util.concurrent.atomic.AtomicBoolean
+
+/**
+ * Dialog to show ongoing and recent app ops element.
+ *
+ * @param context A context to create the dialog
+ * @param list list of elements to show in the dialog. The elements will show in the same order they
+ *   appear in the list
+ * @param manageApp a callback to start an activity for a given package name, user id, and intent
+ * @param closeApp a callback to close an app for a given package name, user id
+ * @param openPrivacyDashboard a callback to open the privacy dashboard
+ * @see PrivacyDialogControllerV2
+ */
+class PrivacyDialogV2(
+    context: Context,
+    private val list: List<PrivacyElement>,
+    private val manageApp: (String, Int, Intent) -> Unit,
+    private val closeApp: (String, Int) -> Unit,
+    private val openPrivacyDashboard: () -> Unit
+) : SystemUIDialog(context, R.style.Theme_PrivacyDialog) {
+
+    private val dismissListeners = mutableListOf<WeakReference<OnDialogDismissed>>()
+    private val dismissed = AtomicBoolean(false)
+    // Note: this will call the dialog create method during init
+    private val decorViewLayoutListener = maybeForceFullscreen()?.component2()
+
+    /**
+     * Add a listener that will be called when the dialog is dismissed.
+     *
+     * If the dialog has already been dismissed, the listener will be called immediately, in the
+     * same thread.
+     */
+    fun addOnDismissListener(listener: OnDialogDismissed) {
+        if (dismissed.get()) {
+            listener.onDialogDismissed()
+        } else {
+            dismissListeners.add(WeakReference(listener))
+        }
+    }
+
+    override fun stop() {
+        dismissed.set(true)
+        val iterator = dismissListeners.iterator()
+        while (iterator.hasNext()) {
+            val el = iterator.next()
+            iterator.remove()
+            el.get()?.onDialogDismissed()
+        }
+        // Remove the layout change listener we may have added to the DecorView.
+        if (decorViewLayoutListener != null) {
+            window!!.decorView.removeOnLayoutChangeListener(decorViewLayoutListener)
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        window!!.setGravity(Gravity.CENTER)
+        setTitle(R.string.privacy_dialog_title)
+        setContentView(R.layout.privacy_dialog_v2)
+
+        val closeButton = requireViewById<Button>(R.id.privacy_dialog_close_button)
+        closeButton.setOnClickListener { dismiss() }
+
+        val moreButton = requireViewById<Button>(R.id.privacy_dialog_more_button)
+        moreButton.setOnClickListener { openPrivacyDashboard() }
+
+        val itemsContainer = requireViewById<ViewGroup>(R.id.privacy_dialog_items_container)
+        list.forEach { itemsContainer.addView(createView(it, itemsContainer)) }
+    }
+
+    private fun createView(element: PrivacyElement, itemsContainer: ViewGroup): View {
+        val itemCard =
+            LayoutInflater.from(context)
+                .inflate(R.layout.privacy_dialog_item_v2, itemsContainer, false) as ViewGroup
+
+        updateItemHeader(element, itemCard)
+
+        if (element.isPhoneCall) {
+            return itemCard
+        }
+
+        setItemExpansionBehavior(itemCard)
+
+        configureIndicatorActionButtons(element, itemCard)
+
+        return itemCard
+    }
+
+    private fun updateItemHeader(element: PrivacyElement, itemCard: View) {
+        val itemHeader = itemCard.findViewById<ViewGroup>(R.id.privacy_dialog_item_header)!!
+        val permGroupLabel = context.packageManager.getDefaultPermGroupLabel(element.permGroupName)
+
+        val iconView = itemHeader.findViewById<ImageView>(R.id.privacy_dialog_item_header_icon)!!
+        val indicatorIcon = context.getPermGroupIcon(element.permGroupName)
+        updateIconView(iconView, indicatorIcon, element.isActive)
+        iconView.contentDescription = permGroupLabel
+
+        val titleView = itemHeader.findViewById<TextView>(R.id.privacy_dialog_item_header_title)!!
+        titleView.text = permGroupLabel
+        titleView.contentDescription = permGroupLabel
+
+        val usageText = getUsageText(element)
+        val summaryView =
+            itemHeader.findViewById<TextView>(R.id.privacy_dialog_item_header_summary)!!
+        summaryView.text = usageText
+        summaryView.contentDescription = usageText
+    }
+
+    private fun configureIndicatorActionButtons(element: PrivacyElement, itemCard: View) {
+        val expandedLayout =
+            itemCard.findViewById<ViewGroup>(R.id.privacy_dialog_item_header_expanded_layout)!!
+
+        val buttons: MutableList<View> = mutableListOf()
+        configureCloseAppButton(element, expandedLayout)?.also { buttons.add(it) }
+        buttons.add(configureManageButton(element, expandedLayout))
+
+        val backgroundColor = getBackgroundColor(element.isActive)
+        when (buttons.size) {
+            0 -> return
+            1 -> {
+                val background =
+                    getMutableDrawable(R.drawable.privacy_dialog_background_large_top_large_bottom)
+                background.setTint(backgroundColor)
+                buttons[0].background = background
+            }
+            else -> {
+                val firstBackground =
+                    getMutableDrawable(R.drawable.privacy_dialog_background_large_top_small_bottom)
+                val middleBackground =
+                    getMutableDrawable(R.drawable.privacy_dialog_background_small_top_small_bottom)
+                val lastBackground =
+                    getMutableDrawable(R.drawable.privacy_dialog_background_small_top_large_bottom)
+                firstBackground.setTint(backgroundColor)
+                middleBackground.setTint(backgroundColor)
+                lastBackground.setTint(backgroundColor)
+                buttons.forEach { it.background = middleBackground }
+                buttons.first().background = firstBackground
+                buttons.last().background = lastBackground
+            }
+        }
+    }
+
+    private fun configureCloseAppButton(element: PrivacyElement, expandedLayout: ViewGroup): View? {
+        if (element.isService || !element.isActive) {
+            return null
+        }
+        val closeAppButton =
+            checkNotNull(window).layoutInflater.inflate(
+                R.layout.privacy_dialog_card_button,
+                expandedLayout,
+                false
+            ) as Button
+        expandedLayout.addView(closeAppButton)
+        closeAppButton.id = R.id.privacy_dialog_close_app_button
+        closeAppButton.setText(R.string.privacy_dialog_close_app_button)
+        closeAppButton.setTextColor(getForegroundColor(true))
+        closeAppButton.tag = element
+        closeAppButton.setOnClickListener { v ->
+            v.tag?.let {
+                val element = it as PrivacyElement
+                closeApp(element.packageName, element.userId)
+                closeAppTransition(element.packageName, element.userId)
+            }
+        }
+        return closeAppButton
+    }
+
+    private fun closeAppTransition(packageName: String, userId: Int) {
+        val itemsContainer = requireViewById<ViewGroup>(R.id.privacy_dialog_items_container)
+        var shouldTransition = false
+        for (i in 0 until itemsContainer.getChildCount()) {
+            val itemCard = itemsContainer.getChildAt(i)
+            val button = itemCard.findViewById<Button>(R.id.privacy_dialog_close_app_button)
+            if (button == null || button.tag == null) {
+                continue
+            }
+            val element = button.tag as PrivacyElement
+            if (element.packageName != packageName || element.userId != userId) {
+                continue
+            }
+
+            itemCard.setEnabled(false)
+
+            val expandToggle =
+                itemCard.findViewById<ImageView>(R.id.privacy_dialog_item_header_expand_toggle)!!
+            expandToggle.visibility = View.GONE
+
+            disableIndicatorCardUi(itemCard, element.applicationName)
+
+            val expandedLayout =
+                itemCard.findViewById<View>(R.id.privacy_dialog_item_header_expanded_layout)!!
+            if (expandedLayout.visibility == View.VISIBLE) {
+                expandedLayout.visibility = View.GONE
+                shouldTransition = true
+            }
+        }
+        if (shouldTransition) {
+            ViewHierarchyAnimator.animateNextUpdate(window!!.decorView)
+        }
+    }
+
+    private fun configureManageButton(element: PrivacyElement, expandedLayout: ViewGroup): View {
+        val manageButton =
+            checkNotNull(window).layoutInflater.inflate(
+                R.layout.privacy_dialog_card_button,
+                expandedLayout,
+                false
+            ) as Button
+        expandedLayout.addView(manageButton)
+        manageButton.id = R.id.privacy_dialog_manage_app_button
+        manageButton.setText(
+            if (element.isService) R.string.privacy_dialog_manage_service
+            else R.string.privacy_dialog_manage_permissions
+        )
+        manageButton.setTextColor(getForegroundColor(element.isActive))
+        manageButton.tag = element
+        manageButton.setOnClickListener { v ->
+            v.tag?.let {
+                val element = it as PrivacyElement
+                manageApp(element.packageName, element.userId, element.navigationIntent)
+            }
+        }
+        return manageButton
+    }
+
+    private fun disableIndicatorCardUi(itemCard: View, applicationName: CharSequence) {
+        val iconView = itemCard.findViewById<ImageView>(R.id.privacy_dialog_item_header_icon)!!
+        val indicatorIcon = getMutableDrawable(R.drawable.privacy_dialog_check_icon)
+        updateIconView(iconView, indicatorIcon, false)
+
+        val closedAppText =
+            context.getString(R.string.privacy_dialog_close_app_message, applicationName)
+        val summaryView = itemCard.findViewById<TextView>(R.id.privacy_dialog_item_header_summary)!!
+        summaryView.text = closedAppText
+        summaryView.contentDescription = closedAppText
+    }
+
+    private fun setItemExpansionBehavior(itemCard: ViewGroup) {
+        val itemHeader = itemCard.findViewById<ViewGroup>(R.id.privacy_dialog_item_header)!!
+
+        val expandToggle =
+            itemHeader.findViewById<ImageView>(R.id.privacy_dialog_item_header_expand_toggle)!!
+        expandToggle.setImageResource(R.drawable.privacy_dialog_expand_toggle_down)
+        expandToggle.visibility = View.VISIBLE
+
+        ViewCompat.replaceAccessibilityAction(
+            itemCard,
+            AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+            context.getString(R.string.privacy_dialog_expand_action),
+            null
+        )
+
+        val expandedLayout =
+            itemCard.findViewById<View>(R.id.privacy_dialog_item_header_expanded_layout)!!
+        expandedLayout.setOnClickListener {
+            // Stop clicks from propagating
+        }
+
+        itemCard.setOnClickListener {
+            if (expandedLayout.visibility == View.VISIBLE) {
+                expandedLayout.visibility = View.GONE
+                expandToggle.setImageResource(R.drawable.privacy_dialog_expand_toggle_down)
+                ViewCompat.replaceAccessibilityAction(
+                    it!!,
+                    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+                    context.getString(R.string.privacy_dialog_expand_action),
+                    null
+                )
+            } else {
+                expandedLayout.visibility = View.VISIBLE
+                expandToggle.setImageResource(R.drawable.privacy_dialog_expand_toggle_up)
+                ViewCompat.replaceAccessibilityAction(
+                    it!!,
+                    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
+                    context.getString(R.string.privacy_dialog_collapse_action),
+                    null
+                )
+            }
+            ViewHierarchyAnimator.animateNextUpdate(
+                rootView = window!!.decorView,
+                excludedViews = setOf(expandedLayout)
+            )
+        }
+    }
+
+    private fun updateIconView(iconView: ImageView, indicatorIcon: Drawable, active: Boolean) {
+        indicatorIcon.setTint(getForegroundColor(active))
+        val backgroundIcon = getMutableDrawable(R.drawable.privacy_dialog_background_circle)
+        backgroundIcon.setTint(getBackgroundColor(active))
+        val backgroundSize =
+            context.resources.getDimension(R.dimen.ongoing_appops_dialog_circle_size).toInt()
+        val indicatorSize =
+            context.resources.getDimension(R.dimen.ongoing_appops_dialog_icon_size).toInt()
+        iconView.setImageDrawable(
+            constructLayeredIcon(indicatorIcon, indicatorSize, backgroundIcon, backgroundSize)
+        )
+    }
+
+    @ColorInt
+    private fun getForegroundColor(active: Boolean) =
+        Utils.getColorAttrDefaultColor(
+            context,
+            if (active) com.android.internal.R.attr.materialColorOnPrimaryFixed
+            else com.android.internal.R.attr.materialColorOnSurface
+        )
+
+    @ColorInt
+    private fun getBackgroundColor(active: Boolean) =
+        Utils.getColorAttrDefaultColor(
+            context,
+            if (active) com.android.internal.R.attr.materialColorPrimaryFixed
+            else com.android.internal.R.attr.materialColorSurfaceContainerHigh
+        )
+
+    private fun getMutableDrawable(@DrawableRes resId: Int) = context.getDrawable(resId)!!.mutate()
+
+    private fun getUsageText(element: PrivacyElement) =
+        if (element.isPhoneCall) {
+            val phoneCallResId =
+                if (element.isActive) R.string.privacy_dialog_active_call_usage
+                else R.string.privacy_dialog_recent_call_usage
+            context.getString(phoneCallResId)
+        } else if (element.attributionLabel == null && element.proxyLabel == null) {
+            val usageResId: Int =
+                if (element.isActive) R.string.privacy_dialog_active_app_usage
+                else R.string.privacy_dialog_recent_app_usage
+            context.getString(usageResId, element.applicationName)
+        } else if (element.attributionLabel == null || element.proxyLabel == null) {
+            val singleUsageResId: Int =
+                if (element.isActive) R.string.privacy_dialog_active_app_usage_1
+                else R.string.privacy_dialog_recent_app_usage_1
+            context.getString(
+                singleUsageResId,
+                element.applicationName,
+                element.attributionLabel ?: element.proxyLabel
+            )
+        } else {
+            val doubleUsageResId: Int =
+                if (element.isActive) R.string.privacy_dialog_active_app_usage_2
+                else R.string.privacy_dialog_recent_app_usage_2
+            context.getString(
+                doubleUsageResId,
+                element.applicationName,
+                element.attributionLabel,
+                element.proxyLabel
+            )
+        }
+
+    companion object {
+        private const val LOG_TAG = "PrivacyDialogV2"
+        private const val REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE"
+
+        /**
+         * Gets a permission group's icon from the system.
+         *
+         * @param groupName The name of the permission group whose icon we want
+         * @return The permission group's icon, the privacy_dialog_default_permission_icon icon if
+         *   the group has no icon, or the group does not exist
+         */
+        @WorkerThread
+        private fun Context.getPermGroupIcon(groupName: String): Drawable {
+            val groupInfo = packageManager.getGroupInfo(groupName)
+            if (groupInfo != null && groupInfo.icon != 0) {
+                val icon = packageManager.loadDrawable(groupInfo.packageName, groupInfo.icon)
+                if (icon != null) {
+                    return icon
+                }
+            }
+
+            return getDrawable(R.drawable.privacy_dialog_default_permission_icon)!!.mutate()
+        }
+
+        /**
+         * Gets a permission group's label from the system.
+         *
+         * @param groupName The name of the permission group whose label we want
+         * @return The permission group's label, or the group name, if the group is invalid
+         */
+        @WorkerThread
+        private fun PackageManager.getDefaultPermGroupLabel(groupName: String): CharSequence {
+            val groupInfo = getGroupInfo(groupName) ?: return groupName
+            return groupInfo.loadSafeLabel(
+                this,
+                0f,
+                TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM
+            )
+        }
+
+        /**
+         * Get the [infos][PackageItemInfo] for the given permission group.
+         *
+         * @param groupName the group
+         * @return The info of permission group or null if the group does not have runtime
+         *   permissions.
+         */
+        @WorkerThread
+        private fun PackageManager.getGroupInfo(groupName: String): PackageItemInfo? {
+            try {
+                return getPermissionGroupInfo(groupName, 0)
+            } catch (e: NameNotFoundException) {
+                /* ignore */
+            }
+            try {
+                return getPermissionInfo(groupName, 0)
+            } catch (e: NameNotFoundException) {
+                /* ignore */
+            }
+            return null
+        }
+
+        @WorkerThread
+        private fun PackageManager.loadDrawable(pkg: String, @DrawableRes resId: Int): Drawable? {
+            return try {
+                getResourcesForApplication(pkg).getDrawable(resId, null)?.mutate()
+            } catch (e: NotFoundException) {
+                Log.w(LOG_TAG, "Couldn't get resource", e)
+                null
+            } catch (e: NameNotFoundException) {
+                Log.w(LOG_TAG, "Couldn't get resource", e)
+                null
+            }
+        }
+
+        private fun constructLayeredIcon(
+            icon: Drawable,
+            iconSize: Int,
+            background: Drawable,
+            backgroundSize: Int
+        ): Drawable {
+            val layered = LayerDrawable(arrayOf(background, icon))
+            layered.setLayerSize(0, backgroundSize, backgroundSize)
+            layered.setLayerGravity(0, Gravity.CENTER)
+            layered.setLayerSize(1, iconSize, iconSize)
+            layered.setLayerGravity(1, Gravity.CENTER)
+            return layered
+        }
+    }
+
+    /**  */
+    data class PrivacyElement(
+        val type: PrivacyType,
+        val packageName: String,
+        val userId: Int,
+        val applicationName: CharSequence,
+        val attributionTag: CharSequence?,
+        val attributionLabel: CharSequence?,
+        val proxyLabel: CharSequence?,
+        val lastActiveTimestamp: Long,
+        val isActive: Boolean,
+        val isPhoneCall: Boolean,
+        val isService: Boolean,
+        val permGroupName: String,
+        val navigationIntent: Intent
+    ) {
+        private val builder = StringBuilder("PrivacyElement(")
+
+        init {
+            builder.append("type=${type.logName}")
+            builder.append(", packageName=$packageName")
+            builder.append(", userId=$userId")
+            builder.append(", appName=$applicationName")
+            if (attributionTag != null) {
+                builder.append(", attributionTag=$attributionTag")
+            }
+            if (attributionLabel != null) {
+                builder.append(", attributionLabel=$attributionLabel")
+            }
+            if (proxyLabel != null) {
+                builder.append(", proxyLabel=$proxyLabel")
+            }
+            builder.append(", lastActive=$lastActiveTimestamp")
+            if (isActive) {
+                builder.append(", active")
+            }
+            if (isPhoneCall) {
+                builder.append(", phoneCall")
+            }
+            if (isService) {
+                builder.append(", service")
+            }
+            builder.append(", permGroupName=$permGroupName")
+            builder.append(", navigationIntent=$navigationIntent)")
+        }
+
+        override fun toString(): String = builder.toString()
+    }
+
+    /**  */
+    interface OnDialogDismissed {
+        fun onDialogDismissed()
+    }
+}
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 e56106d..1a4642f 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
@@ -18,11 +18,12 @@
 
 import android.icu.text.SimpleDateFormat
 import android.permission.PermissionGroupUsage
-import com.android.systemui.log.dagger.PrivacyLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.LogMessage
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.dagger.PrivacyLog
 import com.android.systemui.privacy.PrivacyDialog
+import com.android.systemui.privacy.PrivacyDialogV2
 import com.android.systemui.privacy.PrivacyItem
 import java.util.Locale
 import javax.inject.Inject
@@ -126,6 +127,14 @@
         })
     }
 
+    fun logShowDialogV2Contents(contents: List<PrivacyDialogV2.PrivacyElement>) {
+        log(LogLevel.INFO, {
+            str1 = contents.toString()
+        }, {
+            "Privacy dialog shown. Contents: $str1"
+        })
+    }
+
     fun logEmptyDialog() {
         log(LogLevel.WARNING, {}, {
             "Trying to show an empty dialog"
@@ -147,6 +156,23 @@
         })
     }
 
+    fun logCloseAppFromDialog(packageName: String, userId: Int) {
+        log(LogLevel.INFO, {
+            str1 = packageName
+            int1 = userId
+        }, {
+            "Close app from dialog for packageName=$str1, userId=$int1"
+        })
+    }
+
+    fun logStartPrivacyDashboardFromDialog() {
+        log(LogLevel.INFO, {}, { "Start privacy dashboard from dialog" })
+    }
+
+    fun logLabelNotFound(packageName: String) {
+        log(LogLevel.WARNING, { str1 = packageName }, { "Label not found for: $str1" })
+    }
+
     private fun listToString(list: List<PrivacyItem>): String {
         return list.joinToString(separator = ", ", transform = PrivacyItem::log)
     }
@@ -158,4 +184,4 @@
     ) {
         buffer.log(TAG, logLevel, initializer, printer)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
index fa3f878f..2d460a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
@@ -120,6 +120,7 @@
         mUserTracker = userTracker;
         mConfigEnableLockScreenButton = mContext.getResources().getBoolean(
             android.R.bool.config_enableQrCodeScannerOnLockScreen);
+        mExecutor.execute(this::updateQRCodeScannerActivityDetails);
     }
 
     /**
@@ -158,18 +159,18 @@
      * Returns true if lock screen entry point for QR Code Scanner is to be enabled.
      */
     public boolean isEnabledForLockScreenButton() {
-        return mQRCodeScannerEnabled && isAbleToOpenCameraApp() && isAvailableOnDevice();
+        return mQRCodeScannerEnabled && isAbleToLaunchScannerActivity() && isAllowedOnLockScreen();
     }
 
-    /** Returns whether the feature is available on the device. */
-    public boolean isAvailableOnDevice() {
+    /** Returns whether the QR scanner button is allowed on lockscreen. */
+    public boolean isAllowedOnLockScreen() {
         return mConfigEnableLockScreenButton;
     }
 
     /**
-     * Returns true if the feature can open a camera app on the device.
+     * Returns true if the feature can open the configured QR scanner activity.
      */
-    public boolean isAbleToOpenCameraApp() {
+    public boolean isAbleToLaunchScannerActivity() {
         return mIntent != null && isActivityCallable(mIntent);
     }
 
@@ -355,9 +356,6 @@
 
         // Reset cached values to default as we are no longer listening
         mOnDefaultQRCodeScannerChangedListener = null;
-        mQRCodeScannerActivity = null;
-        mIntent = null;
-        mComponentName = null;
     }
 
     private void notifyQRCodeScannerActivityChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
index 995c6a4..0941a20 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
@@ -14,10 +14,13 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.appops.AppOpsController
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.privacy.PrivacyChipEvent
 import com.android.systemui.privacy.PrivacyDialogController
+import com.android.systemui.privacy.PrivacyDialogControllerV2
 import com.android.systemui.privacy.PrivacyItem
 import com.android.systemui.privacy.PrivacyItemController
 import com.android.systemui.privacy.logging.PrivacyLogger
@@ -26,7 +29,7 @@
 import javax.inject.Inject
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.shade.ShadeModule.Companion.SHADE_HEADER
+import com.android.systemui.shade.ShadeViewProviderModule.Companion.SHADE_HEADER
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import javax.inject.Named
 
@@ -49,6 +52,7 @@
     private val uiEventLogger: UiEventLogger,
     @Named(SHADE_HEADER) private val privacyChip: OngoingPrivacyChip,
     private val privacyDialogController: PrivacyDialogController,
+    private val privacyDialogControllerV2: PrivacyDialogControllerV2,
     private val privacyLogger: PrivacyLogger,
     @Named(SHADE_HEADER) private val iconContainer: StatusIconContainer,
     private val permissionManager: PermissionManager,
@@ -58,7 +62,8 @@
     private val appOpsController: AppOpsController,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val safetyCenterManager: SafetyCenterManager,
-    private val deviceProvisionedController: DeviceProvisionedController
+    private val deviceProvisionedController: DeviceProvisionedController,
+    private val featureFlags: FeatureFlags
 ) {
 
     var chipVisibilityListener: ChipVisibilityListener? = null
@@ -143,7 +148,11 @@
             // If the privacy chip is visible, it means there were some indicators
             uiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK)
             if (safetyCenterEnabled) {
-                showSafetyCenter()
+                if (featureFlags.isEnabled(Flags.ENABLE_NEW_PRIVACY_DIALOG)) {
+                    privacyDialogControllerV2.showDialog(privacyChip.context, privacyChip)
+                } else {
+                    showSafetyCenter()
+                }
             } else {
                 privacyDialogController.showDialog(privacyChip.context)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 7523d6e..ddd9463 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -7,6 +7,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Bundle;
@@ -549,10 +550,8 @@
         return mPages.get(0).mRecords.size();
     }
 
-    public void startTileReveal(Set<String> tileSpecs, final Runnable postAnimation) {
-        if (tileSpecs.isEmpty() || mPages.size() < 2 || getScrollX() != 0 || !beginFakeDrag()) {
-            // Do not start the reveal animation unless there are tiles to animate, multiple
-            // TileLayouts available and the user has not already started dragging.
+    public void startTileReveal(Set<String> tilesToReveal, final Runnable postAnimation) {
+        if (shouldNotRunAnimation(tilesToReveal)) {
             return;
         }
 
@@ -560,13 +559,13 @@
         final TileLayout lastPage = mPages.get(lastPageNumber);
         final ArrayList<Animator> bounceAnims = new ArrayList<>();
         for (TileRecord tr : lastPage.mRecords) {
-            if (tileSpecs.contains(tr.tile.getTileSpec())) {
+            if (tilesToReveal.contains(tr.tile.getTileSpec())) {
                 bounceAnims.add(setupBounceAnimator(tr.tileView, bounceAnims.size()));
             }
         }
 
         if (bounceAnims.isEmpty()) {
-            // All tileSpecs are on the first page. Nothing to do.
+            // All tilesToReveal are on the first page. Nothing to do.
             // TODO: potentially show a bounce animation for first page QS tiles
             endFakeDrag();
             return;
@@ -588,6 +587,16 @@
         postInvalidateOnAnimation();
     }
 
+    private boolean shouldNotRunAnimation(Set<String> tilesToReveal) {
+        boolean noAnimationNeeded = tilesToReveal.isEmpty() || mPages.size() < 2;
+        boolean scrollingInProgress = getScrollX() != 0 || !beginFakeDrag();
+        // isRunningInTestHarness() to disable animation in functional testing as it caused
+        // flakiness and is not needed there. Alternative solutions were more complex and would
+        // still be either potentially flaky or modify internal data.
+        // For more info see b/253493927 and b/293234595
+        return noAnimationNeeded || scrollingInProgress || ActivityManager.isRunningInTestHarness();
+    }
+
     private int sanitizePageAction(int action) {
         int pageLeftId = AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT.getId();
         int pageRightId = AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT.getId();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 1afc885..d2eac45 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -23,12 +23,14 @@
 import android.graphics.Path;
 import android.graphics.PointF;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.widget.FrameLayout;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.util.LargeScreenUtils;
 
 import java.io.PrintWriter;
@@ -129,6 +131,11 @@
     }
 
     @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch("QS", ev, super.dispatchTouchEvent(ev));
+    }
+
+    @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         updateExpansion();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index b8c2fad..ddb395b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -286,7 +286,8 @@
     private void bindFooterActionsView(View root) {
         LinearLayout footerActionsView = root.findViewById(R.id.qs_footer_actions);
 
-        if (!ComposeFacade.INSTANCE.isComposeAvailable()) {
+        if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS)
+                || !ComposeFacade.INSTANCE.isComposeAvailable()) {
             Log.d(TAG, "Binding the View implementation of the QS footer actions");
             mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
                     mListeningAndVisibilityLifecycleOwner);
@@ -755,8 +756,7 @@
             // Alpha progress should be linear on lockscreen shade expansion.
             return progress;
         }
-        if (mIsSmallScreen || !mFeatureFlags.isEnabled(
-                Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+        if (mIsSmallScreen) {
             return ShadeInterpolation.getContentAlpha(progress);
         } else {
             return mLargeScreenShadeInterpolator.getQsAlpha(progress);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
index cd52ec2..6563e42 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt
@@ -2,7 +2,7 @@
 
 import com.android.systemui.log.dagger.QSFragmentDisableLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
 import javax.inject.Inject
 
@@ -34,7 +34,6 @@
             },
             {
                 disableFlagsLogger.getDisableFlagsString(
-                    old = null,
                     new = DisableFlagsLogger.DisableState(int1, int2),
                     newAfterLocalModification =
                         DisableFlagsLogger.DisableState(long1.toInt(), long2.toInt())
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 83b373d..856a92e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -58,6 +58,7 @@
     private final BrightnessSliderController mBrightnessSliderController;
     private final BrightnessMirrorHandler mBrightnessMirrorHandler;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private boolean mListening;
 
     private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() {
         @Override
@@ -159,12 +160,15 @@
     public void setListening(boolean listening, boolean expanded) {
         setListening(listening && expanded);
 
-        // Set the listening as soon as the QS fragment starts listening regardless of the
-        //expansion, so it will update the current brightness before the slider is visible.
-        if (listening) {
-            mBrightnessController.registerCallbacks();
-        } else {
-            mBrightnessController.unregisterCallbacks();
+        if (listening != mListening) {
+            mListening = listening;
+            // Set the listening as soon as the QS fragment starts listening regardless of the
+            //expansion, so it will update the current brightness before the slider is visible.
+            if (listening) {
+                mBrightnessController.registerCallbacks();
+            } else {
+                mBrightnessController.unregisterCallbacks();
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 868dbfa..432147f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -52,8 +52,8 @@
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 import com.android.systemui.util.settings.SecureSettings;
@@ -66,7 +66,6 @@
 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;
@@ -108,7 +107,7 @@
     private AutoTileManager mAutoTiles;
     private final ArrayList<QSFactory> mQsFactories = new ArrayList<>();
     private int mCurrentUser;
-    private final Optional<CentralSurfaces> mCentralSurfacesOptional;
+    private final ShadeController mShadeController;
     private Context mUserContext;
     private UserTracker mUserTracker;
     private SecureSettings mSecureSettings;
@@ -129,7 +128,7 @@
             PluginManager pluginManager,
             TunerService tunerService,
             Provider<AutoTileManager> autoTiles,
-            Optional<CentralSurfaces> centralSurfacesOptional,
+            ShadeController shadeController,
             QSLogger qsLogger,
             UserTracker userTracker,
             SecureSettings secureSettings,
@@ -148,11 +147,12 @@
         mUserFileManager = userFileManager;
         mFeatureFlags = featureFlags;
 
-        mCentralSurfacesOptional = centralSurfacesOptional;
+        mShadeController = shadeController;
 
         mQsFactories.add(defaultFactory);
         pluginManager.addPluginListener(this, QSFactory.class, true);
         mUserTracker = userTracker;
+        mCurrentUser = userTracker.getUserId();
         mSecureSettings = secureSettings;
         mCustomTileStatePersister = customTileStatePersister;
 
@@ -162,7 +162,9 @@
             // finishes before creating any tiles.
             tunerService.addTunable(this, TILES_SETTING);
             // AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
-            mAutoTiles = autoTiles.get();
+            if (!mFeatureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)) {
+                mAutoTiles = autoTiles.get();
+            }
         });
     }
 
@@ -209,17 +211,17 @@
 
     @Override
     public void collapsePanels() {
-        mCentralSurfacesOptional.ifPresent(CentralSurfaces::postAnimateCollapsePanels);
+        mShadeController.postAnimateCollapseShade();
     }
 
     @Override
     public void forceCollapsePanels() {
-        mCentralSurfacesOptional.ifPresent(CentralSurfaces::postAnimateForceCollapsePanels);
+        mShadeController.postAnimateForceCollapseShade();
     }
 
     @Override
     public void openPanels() {
-        mCentralSurfacesOptional.ifPresent(CentralSurfaces::postAnimateOpenPanels);
+        mShadeController.postAnimateExpandQs();
     }
 
     @Override
@@ -273,6 +275,13 @@
         if (!TILES_SETTING.equals(key)) {
             return;
         }
+        int currentUser = mUserTracker.getUserId();
+        if (currentUser != mCurrentUser) {
+            mUserContext = mUserTracker.getUserContext();
+            if (mAutoTiles != null) {
+                mAutoTiles.changeUser(UserHandle.of(currentUser));
+            }
+        }
         // Do not process tiles if the flag is enabled.
         if (mFeatureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
             return;
@@ -281,13 +290,6 @@
             newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
         }
         final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
-        int currentUser = mUserTracker.getUserId();
-        if (currentUser != mCurrentUser) {
-            mUserContext = mUserTracker.getUserContext();
-            if (mAutoTiles != null) {
-                mAutoTiles.changeUser(UserHandle.of(currentUser));
-            }
-        }
         if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
         Log.d(TAG, "Recreating tiles: " + tileSpecs);
         mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index dffe7fd..03de3a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -19,9 +19,9 @@
 import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
 
 import android.content.Context;
-import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
 
+import com.android.systemui.dagger.NightDisplayListenerModule;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.media.dagger.MediaModule;
@@ -41,14 +41,14 @@
 import com.android.systemui.statusbar.policy.WalletController;
 import com.android.systemui.util.settings.SecureSettings;
 
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.Multibinds;
-
 import java.util.Map;
 
 import javax.inject.Named;
 
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.Multibinds;
+
 /**
  * Module for QS dependencies
  */
@@ -79,7 +79,7 @@
             HotspotController hotspotController,
             DataSaverController dataSaverController,
             ManagedProfileController managedProfileController,
-            NightDisplayListener nightDisplayListener,
+            NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
             CastController castController,
             ReduceBrightColorsController reduceBrightColorsController,
             DeviceControlsController deviceControlsController,
@@ -95,7 +95,7 @@
                 hotspotController,
                 dataSaverController,
                 managedProfileController,
-                nightDisplayListener,
+                nightDisplayListenerBuilder,
                 castController,
                 reduceBrightColorsController,
                 deviceControlsController,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
index 6265b3c..3432628 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.graphics.drawable.Icon
+import android.view.ContextThemeWrapper
 import android.view.LayoutInflater
 import android.view.ViewGroup
 import android.widget.TextView
@@ -66,7 +67,8 @@
     }
 
     private fun createTileView(tileData: TileData): QSTileView {
-        val tile = QSTileViewImpl(context, QSIconViewImpl(context), true)
+        val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+        val tile = QSTileViewImpl(themedContext, QSIconViewImpl(themedContext), true)
         val state = QSTile.BooleanState().apply {
             label = tileData.label
             handlesLongClick = false
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 9c9ad33..3c53d77 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
@@ -244,8 +244,8 @@
 
         val backgroundResource =
             when (model.backgroundColor) {
-                R.attr.offStateColor -> R.drawable.qs_footer_action_circle
-                com.android.internal.R.attr.colorAccent -> R.drawable.qs_footer_action_circle_color
+                R.attr.shadeInactive -> R.drawable.qs_footer_action_circle
+                R.attr.shadeActive -> R.drawable.qs_footer_action_circle_color
                 else -> error("Unsupported icon background resource ${model.backgroundColor}")
             }
         buttonView.setBackgroundResource(backgroundResource)
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 b3596a2..32146b5 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
@@ -145,8 +145,12 @@
                 R.drawable.ic_settings,
                 ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
             ),
-            iconTint = null,
-            backgroundColor = R.attr.offStateColor,
+            iconTint =
+                Utils.getColorAttrDefaultColor(
+                    context,
+                    R.attr.onShadeInactiveVariant,
+                ),
+            backgroundColor = R.attr.shadeInactive,
             this::onSettingsButtonClicked,
         )
 
@@ -162,9 +166,9 @@
                 iconTint =
                     Utils.getColorAttrDefaultColor(
                         context,
-                        com.android.internal.R.attr.textColorOnAccent,
+                        R.attr.onShadeActive,
                     ),
-                backgroundColor = com.android.internal.R.attr.colorAccent,
+                backgroundColor = R.attr.shadeActive,
                 this::onPowerButtonClicked,
             )
         } else {
@@ -264,7 +268,7 @@
                     ),
                 ),
             iconTint = null,
-            backgroundColor = R.attr.offStateColor,
+            backgroundColor = R.attr.shadeInactive,
             onClick = this::onUserSwitcherClicked,
         )
     }
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 c00a81c..39745c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -24,9 +24,9 @@
 import com.android.systemui.log.ConstantStringsLogger
 import com.android.systemui.log.ConstantStringsLoggerImpl
 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.VERBOSE
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.VERBOSE
 import com.android.systemui.log.dagger.QSConfigLog
 import com.android.systemui.log.dagger.QSLog
 import com.android.systemui.plugins.qs.QSTile
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
new file mode 100644
index 0000000..adea26e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.dagger
+
+import android.content.res.Resources
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSetting
+import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSettingList
+import com.android.systemui.qs.pipeline.domain.autoaddable.CastAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.DataSaverAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.DeviceControlsAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.HotspotAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.NightDisplayAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.ReduceBrightColorsAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.WalletAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.WorkTileAutoAddable
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import dagger.multibindings.IntoSet
+
+@Module
+interface BaseAutoAddableModule {
+
+    companion object {
+        @Provides
+        @ElementsIntoSet
+        fun providesAutoAddableSetting(
+            @Main resources: Resources,
+            autoAddableSettingFactory: AutoAddableSetting.Factory
+        ): Set<AutoAddable> {
+            return AutoAddableSettingList.parseSettingsResource(
+                    resources,
+                    autoAddableSettingFactory
+                )
+                .toSet()
+        }
+    }
+
+    @Binds @IntoSet fun bindCastAutoAddable(impl: CastAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindDataSaverAutoAddable(impl: DataSaverAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindDeviceControlsAutoAddable(impl: DeviceControlsAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindHotspotAutoAddable(impl: HotspotAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindNightDisplayAutoAddable(impl: NightDisplayAutoAddable): AutoAddable
+
+    @Binds
+    @IntoSet
+    fun bindReduceBrightColorsAutoAddable(impl: ReduceBrightColorsAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindWalletAutoAddable(impl: WalletAutoAddable): AutoAddable
+
+    @Binds @IntoSet fun bindWorkModeAutoAddable(impl: WorkTileAutoAddable): AutoAddable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddLog.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddLog.kt
new file mode 100644
index 0000000..91cb5bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddLog.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** A [LogBuffer] for the QS pipeline to track auto-added tiles */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class QSAutoAddLog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt
index 9979228..a010ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt
@@ -16,13 +16,40 @@
 
 package com.android.systemui.qs.pipeline.dagger
 
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
 import com.android.systemui.qs.pipeline.data.repository.AutoAddSettingRepository
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
+import dagger.multibindings.Multibinds
 
-@Module
+@Module(
+    includes =
+        [
+            BaseAutoAddableModule::class,
+        ]
+)
 abstract class QSAutoAddModule {
 
     @Binds abstract fun bindAutoAddRepository(impl: AutoAddSettingRepository): AutoAddRepository
+
+    @Multibinds abstract fun providesAutoAddableSet(): Set<AutoAddable>
+
+    companion object {
+        /**
+         * Provides a logging buffer for all logs related to the new Quick Settings pipeline to log
+         * auto added tiles.
+         */
+        @Provides
+        @SysUISingleton
+        @QSAutoAddLog
+        fun provideQSAutoAddLogBuffer(factory: LogBufferFactory): LogBuffer {
+            return factory.create(QSPipelineLogger.AUTO_ADD_TAG, maxSize = 100, systrace = false)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index d7ae575..a4600fb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl
-import com.android.systemui.qs.pipeline.prototyping.PrototypeCoreStartable
+import com.android.systemui.qs.pipeline.domain.startable.QSPipelineCoreStartable
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import dagger.Binds
 import dagger.Module
@@ -53,8 +53,8 @@
 
     @Binds
     @IntoMap
-    @ClassKey(PrototypeCoreStartable::class)
-    abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable
+    @ClassKey(QSPipelineCoreStartable::class)
+    abstract fun provideCoreStartable(startable: QSPipelineCoreStartable): CoreStartable
 
     companion object {
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
index ad8bfea..c56ca8c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSTileListLog.kt
@@ -19,5 +19,5 @@
 import java.lang.annotation.RetentionPolicy
 import javax.inject.Qualifier
 
-/** A {@link LogBuffer} for the new QS Pipeline for logging changes to the set of current tiles. */
+/** A [LogBuffer] for the new QS Pipeline for logging changes to the set of current tiles. */
 @Qualifier @MustBeDocumented @Retention(RetentionPolicy.RUNTIME) annotation class QSTileListLog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
index 498f403..6e7e099 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -51,12 +51,26 @@
 @Inject
 constructor(
     @Application private val applicationContext: Context,
-    private val packageManager: PackageManager,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) : InstalledTilesComponentRepository {
 
-    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> =
-        conflatedCallbackFlow {
+    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
+        /*
+         * In order to query [PackageManager] for different users, this implementation will call
+         * [Context.createContextAsUser] and retrieve the [PackageManager] from that context.
+         */
+        val packageManager =
+            if (applicationContext.userId == userId) {
+                applicationContext.packageManager
+            } else {
+                applicationContext
+                    .createContextAsUser(
+                        UserHandle.of(userId),
+                        /* flags */ 0,
+                    )
+                    .packageManager
+            }
+        return conflatedCallbackFlow {
                 val receiver =
                     object : BroadcastReceiver() {
                         override fun onReceive(context: Context?, intent: Intent?) {
@@ -74,12 +88,13 @@
                 awaitClose { applicationContext.unregisterReceiver(receiver) }
             }
             .onStart { emit(Unit) }
-            .map { reloadComponents(userId) }
+            .map { reloadComponents(userId, packageManager) }
             .distinctUntilChanged()
             .flowOn(backgroundDispatcher)
+    }
 
     @WorkerThread
-    private fun reloadComponents(userId: Int): Set<ComponentName> {
+    private fun reloadComponents(userId: Int, packageManager: PackageManager): Set<ComponentName> {
         return packageManager
             .queryIntentServicesAsUser(INTENT, FLAGS, userId)
             .mapNotNull { it.serviceInfo }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSetting.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSetting.kt
new file mode 100644
index 0000000..45129b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSetting.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.Objects
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * It tracks a specific `Secure` int [setting] and when its value changes to non-zero, it will emit
+ * a [AutoAddSignal.Add] for [spec].
+ */
+class AutoAddableSetting
+@AssistedInject
+constructor(
+    private val secureSettings: SecureSettings,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Assisted private val setting: String,
+    @Assisted private val spec: TileSpec,
+) : AutoAddable {
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return secureSettings
+            .observerFlow(userId, setting)
+            .onStart { emit(Unit) }
+            .map { secureSettings.getIntForUser(setting, 0, userId) != 0 }
+            .distinctUntilChanged()
+            .filter { it }
+            .map { AutoAddSignal.Add(spec) }
+            .flowOn(bgDispatcher)
+    }
+
+    override val autoAddTracking = AutoAddTracking.IfNotAdded(spec)
+
+    override val description = "AutoAddableSetting: $setting:$spec ($autoAddTracking)"
+
+    override fun equals(other: Any?): Boolean {
+        return other is AutoAddableSetting && spec == other.spec && setting == other.setting
+    }
+
+    override fun hashCode(): Int {
+        return Objects.hash(spec, setting)
+    }
+
+    override fun toString(): String {
+        return description
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(setting: String, spec: TileSpec): AutoAddableSetting
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingList.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingList.kt
new file mode 100644
index 0000000..b1c7433
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingList.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.res.Resources
+import android.util.Log
+import com.android.systemui.R
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+object AutoAddableSettingList {
+
+    /** Parses [R.array.config_quickSettingsAutoAdd] into a collection of [AutoAddableSetting]. */
+    fun parseSettingsResource(
+        resources: Resources,
+        autoAddableSettingFactory: AutoAddableSetting.Factory,
+    ): Iterable<AutoAddable> {
+        val autoAddList = resources.getStringArray(R.array.config_quickSettingsAutoAdd)
+        return autoAddList.mapNotNull {
+            val elements = it.split(SETTING_SEPARATOR, limit = 2)
+            if (elements.size == 2) {
+                val setting = elements[0]
+                val spec = elements[1]
+                val tileSpec = TileSpec.create(spec)
+                if (tileSpec == TileSpec.Invalid) {
+                    Log.w(TAG, "Malformed item in array: $it")
+                    null
+                } else {
+                    autoAddableSettingFactory.create(setting, TileSpec.create(spec))
+                }
+            } else {
+                Log.w(TAG, "Malformed item in array: $it")
+                null
+            }
+        }
+    }
+
+    private const val SETTING_SEPARATOR = ":"
+    private const val TAG = "AutoAddableSettingList"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt
new file mode 100644
index 0000000..88a49ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddable.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.CallbackController
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Generic [AutoAddable] for tiles that are added based on a signal from a [CallbackController]. */
+abstract class CallbackControllerAutoAddable<
+    Callback : Any, Controller : CallbackController<Callback>>(
+    private val controller: Controller,
+) : AutoAddable {
+
+    /** [TileSpec] for the tile to add. */
+    protected abstract val spec: TileSpec
+
+    /**
+     * Callback to be used to determine when to add the tile. When the callback determines that the
+     * feature has been enabled, it should call [sendAdd].
+     */
+    protected abstract fun ProducerScope<AutoAddSignal>.getCallback(): Callback
+
+    /** Sends an [AutoAddSignal.Add] for [spec]. */
+    protected fun ProducerScope<AutoAddSignal>.sendAdd() {
+        trySend(AutoAddSignal.Add(spec))
+    }
+
+    final override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return conflatedCallbackFlow {
+            val callback = getCallback()
+            controller.addCallback(callback)
+
+            awaitClose { controller.removeCallback(callback) }
+        }
+    }
+
+    override val autoAddTracking: AutoAddTracking
+        get() = AutoAddTracking.IfNotAdded(spec)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.kt
new file mode 100644
index 0000000..b5bef9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddable.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.CastTile
+import com.android.systemui.statusbar.policy.CastController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [CastTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when there's a casting device connected or connecting.
+ */
+@SysUISingleton
+class CastAutoAddable
+@Inject
+constructor(
+    private val controller: CastController,
+) : CallbackControllerAutoAddable<CastController.Callback, CastController>(controller) {
+
+    override val spec: TileSpec
+        get() = TileSpec.create(CastTile.TILE_SPEC)
+
+    override fun ProducerScope<AutoAddSignal>.getCallback(): CastController.Callback {
+        return CastController.Callback {
+            val isCasting =
+                controller.castDevices.any {
+                    it.state == CastController.CastDevice.STATE_CONNECTED ||
+                        it.state == CastController.CastDevice.STATE_CONNECTING
+                }
+            if (isCasting) {
+                sendAdd()
+            }
+        }
+    }
+
+    override val description = "CastAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddable.kt
new file mode 100644
index 0000000..a877aee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddable.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DataSaverTile
+import com.android.systemui.statusbar.policy.DataSaverController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [DataSaverTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when data saver is enabled.
+ */
+@SysUISingleton
+class DataSaverAutoAddable
+@Inject
+constructor(
+    dataSaverController: DataSaverController,
+) :
+    CallbackControllerAutoAddable<DataSaverController.Listener, DataSaverController>(
+        dataSaverController
+    ) {
+
+    override val spec
+        get() = TileSpec.create(DataSaverTile.TILE_SPEC)
+
+    override fun ProducerScope<AutoAddSignal>.getCallback(): DataSaverController.Listener {
+        return DataSaverController.Listener { enabled ->
+            if (enabled) {
+                sendAdd()
+            }
+        }
+    }
+
+    override val description = "DataSaverAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt
new file mode 100644
index 0000000..76bfad9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddable.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DeviceControlsTile
+import com.android.systemui.statusbar.policy.DeviceControlsController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * [AutoAddable] for [DeviceControlsTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when updating to a device that supports device controls. It
+ * will send a signal to remove the tile when the device does not support controls.
+ */
+@SysUISingleton
+class DeviceControlsAutoAddable
+@Inject
+constructor(
+    private val deviceControlsController: DeviceControlsController,
+) : AutoAddable {
+
+    private val spec = TileSpec.create(DeviceControlsTile.TILE_SPEC)
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return conflatedCallbackFlow {
+            val callback =
+                object : DeviceControlsController.Callback {
+                    override fun onControlsUpdate(position: Int?) {
+                        position?.let { trySend(AutoAddSignal.Add(spec, position)) }
+                        deviceControlsController.removeCallback()
+                    }
+
+                    override fun removeControlsAutoTracker() {
+                        trySend(AutoAddSignal.Remove(spec))
+                    }
+                }
+
+            deviceControlsController.setCallback(callback)
+
+            awaitClose { deviceControlsController.removeCallback() }
+        }
+    }
+
+    override val autoAddTracking: AutoAddTracking
+        get() = AutoAddTracking.Always
+
+    override val description = "DeviceControlsAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddable.kt
new file mode 100644
index 0000000..9c59e12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddable.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.HotspotTile
+import com.android.systemui.statusbar.policy.HotspotController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [HotspotTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when hotspot is enabled.
+ */
+@SysUISingleton
+class HotspotAutoAddable
+@Inject
+constructor(
+    hotspotController: HotspotController,
+) :
+    CallbackControllerAutoAddable<HotspotController.Callback, HotspotController>(
+        hotspotController
+    ) {
+
+    override val spec
+        get() = TileSpec.create(HotspotTile.TILE_SPEC)
+
+    override fun ProducerScope<AutoAddSignal>.getCallback(): HotspotController.Callback {
+        return HotspotController.Callback { enabled, _ ->
+            if (enabled) {
+                sendAdd()
+            }
+        }
+    }
+
+    override val description = "HotspotAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt
new file mode 100644
index 0000000..31ea734
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.Context
+import android.hardware.display.ColorDisplayManager
+import android.hardware.display.NightDisplayListener
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.NightDisplayTile
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * [AutoAddable] for [NightDisplayTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when night display is enabled or when the auto mode changes
+ * to one that supports night display.
+ */
+@SysUISingleton
+class NightDisplayAutoAddable
+@Inject
+constructor(
+    private val nightDisplayListenerBuilder: NightDisplayListenerModule.Builder,
+    context: Context,
+) : AutoAddable {
+
+    private val enabled = ColorDisplayManager.isNightDisplayAvailable(context)
+    private val spec = TileSpec.create(NightDisplayTile.TILE_SPEC)
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return conflatedCallbackFlow {
+            val nightDisplayListener = nightDisplayListenerBuilder.setUser(userId).build()
+
+            val callback =
+                object : NightDisplayListener.Callback {
+                    override fun onActivated(activated: Boolean) {
+                        if (activated) {
+                            sendAdd()
+                        }
+                    }
+
+                    override fun onAutoModeChanged(autoMode: Int) {
+                        if (
+                            autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME ||
+                                autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT
+                        ) {
+                            sendAdd()
+                        }
+                    }
+
+                    private fun sendAdd() {
+                        trySend(AutoAddSignal.Add(spec))
+                    }
+                }
+
+            nightDisplayListener.setCallback(callback)
+
+            awaitClose { nightDisplayListener.setCallback(null) }
+        }
+    }
+
+    override val autoAddTracking =
+        if (enabled) {
+            AutoAddTracking.IfNotAdded(spec)
+        } else {
+            AutoAddTracking.Disabled
+        }
+
+    override val description = "NightDisplayAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
new file mode 100644
index 0000000..267e2b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.ReduceBrightColorsController
+import com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import javax.inject.Inject
+import javax.inject.Named
+import kotlinx.coroutines.channels.ProducerScope
+
+/**
+ * [AutoAddable] for [ReduceBrightColorsTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when reduce bright colors is enabled.
+ */
+@SysUISingleton
+class ReduceBrightColorsAutoAddable
+@Inject
+constructor(
+    controller: ReduceBrightColorsController,
+    @Named(RBC_AVAILABLE) private val available: Boolean,
+) :
+    CallbackControllerAutoAddable<
+        ReduceBrightColorsController.Listener, ReduceBrightColorsController
+    >(controller) {
+
+    override val spec: TileSpec
+        get() = TileSpec.create(ReduceBrightColorsTile.TILE_SPEC)
+
+    override fun ProducerScope<AutoAddSignal>.getCallback(): ReduceBrightColorsController.Listener {
+        return object : ReduceBrightColorsController.Listener {
+            override fun onActivated(activated: Boolean) {
+                if (activated) {
+                    sendAdd()
+                }
+            }
+        }
+    }
+
+    override val autoAddTracking
+        get() =
+            if (available) {
+                super.autoAddTracking
+            } else {
+                AutoAddTracking.Disabled
+            }
+
+    override val description = "ReduceBrightColorsAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt
new file mode 100644
index 0000000..58a31bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.text.TextUtils
+import com.android.systemui.R
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.SafetyController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.withContext
+
+/**
+ * [AutoAddable] for the safety tile.
+ *
+ * It will send a signal to add the tile when the feature is enabled, indicating the component
+ * corresponding to the tile. If the feature is disabled, it will send a signal to remove the tile.
+ */
+@SysUISingleton
+class SafetyCenterAutoAddable
+@Inject
+constructor(
+    private val safetyController: SafetyController,
+    private val packageManager: PackageManager,
+    @Main private val resources: Resources,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+) : AutoAddable {
+
+    private suspend fun getSpec(): TileSpec? {
+        val specClass = resources.getString(R.string.safety_quick_settings_tile_class)
+        return if (TextUtils.isEmpty(specClass)) {
+            null
+        } else {
+            val packageName =
+                withContext(bgDispatcher) { packageManager.permissionControllerPackageName }
+            TileSpec.create(ComponentName(packageName, specClass))
+        }
+    }
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return conflatedCallbackFlow {
+            val spec = getSpec()
+            if (spec != null) {
+                // If not added, we always try to add it
+                trySend(AutoAddSignal.Add(spec))
+                val listener =
+                    SafetyController.Listener { isSafetyCenterEnabled ->
+                        if (isSafetyCenterEnabled) {
+                            trySend(AutoAddSignal.Add(spec))
+                        } else {
+                            trySend(AutoAddSignal.Remove(spec))
+                        }
+                    }
+
+                safetyController.addCallback(listener)
+
+                awaitClose { safetyController.removeCallback(listener) }
+            } else {
+                awaitClose {}
+            }
+        }
+    }
+
+    override val autoAddTracking: AutoAddTracking
+        get() = AutoAddTracking.Always
+
+    override val description = "SafetyCenterAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddable.kt
new file mode 100644
index 0000000..b3bc25f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddable.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.QuickAccessWalletTile
+import com.android.systemui.statusbar.policy.WalletController
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+/**
+ * [AutoAddable] for [QuickAccessWalletTile.TILE_SPEC].
+ *
+ * It will always try to add the tile if [WalletController.getWalletPosition] is non-null.
+ */
+@SysUISingleton
+class WalletAutoAddable
+@Inject
+constructor(
+    private val walletController: WalletController,
+) : AutoAddable {
+
+    private val spec = TileSpec.create(QuickAccessWalletTile.TILE_SPEC)
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return flow {
+            val position = walletController.getWalletPosition()
+            if (position != null) {
+                emit(AutoAddSignal.Add(spec, position))
+            }
+        }
+    }
+
+    override val autoAddTracking: AutoAddTracking
+        get() = AutoAddTracking.IfNotAdded(spec)
+
+    override val description = "WalletAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
new file mode 100644
index 0000000..5e3c348
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.pm.UserInfo
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.WorkModeTile
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * [AutoAddable] for [WorkModeTile.TILE_SPEC].
+ *
+ * It will send a signal to add the tile when there is a managed profile for the current user, and a
+ * signal to remove it if there is not.
+ */
+@SysUISingleton
+class WorkTileAutoAddable @Inject constructor(private val userTracker: UserTracker) : AutoAddable {
+
+    private val spec = TileSpec.create(WorkModeTile.TILE_SPEC)
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return conflatedCallbackFlow {
+            fun maybeSend(profiles: List<UserInfo>) {
+                if (profiles.any { it.id == userId }) {
+                    // We are looking at the profiles of the correct user.
+                    if (profiles.any { it.isManagedProfile }) {
+                        trySend(AutoAddSignal.Add(spec))
+                    } else {
+                        trySend(AutoAddSignal.Remove(spec))
+                    }
+                }
+            }
+
+            val callback =
+                object : UserTracker.Callback {
+                    override fun onProfilesChanged(profiles: List<UserInfo>) {
+                        maybeSend(profiles)
+                    }
+                }
+
+            userTracker.addCallback(callback) { it.run() }
+            maybeSend(userTracker.userProfiles)
+
+            awaitClose { userTracker.removeCallback(callback) }
+        }
+    }
+
+    override val autoAddTracking = AutoAddTracking.Always
+
+    override val description = "WorkTileAutoAddable ($autoAddTracking)"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
new file mode 100644
index 0000000..b747393
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.indentIfPossible
+import java.io.PrintWriter
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.launch
+
+/**
+ * Collects the signals coming from all registered [AutoAddable] and adds/removes tiles accordingly.
+ */
+@SysUISingleton
+class AutoAddInteractor
+@Inject
+constructor(
+    private val autoAddables: Set<@JvmSuppressWildcards AutoAddable>,
+    private val repository: AutoAddRepository,
+    private val dumpManager: DumpManager,
+    private val qsPipelineLogger: QSPipelineLogger,
+    @Application private val scope: CoroutineScope,
+) : Dumpable {
+
+    private val initialized = AtomicBoolean(false)
+
+    /** Start collection of signals following the user from [currentTilesInteractor]. */
+    fun init(currentTilesInteractor: CurrentTilesInteractor) {
+        if (!initialized.compareAndSet(false, true)) {
+            return
+        }
+
+        dumpManager.registerNormalDumpable(TAG, this)
+
+        scope.launch {
+            currentTilesInteractor.userId.collectLatest { userId ->
+                coroutineScope {
+                    val previouslyAdded = repository.autoAddedTiles(userId).stateIn(this)
+
+                    autoAddables
+                        .map { addable ->
+                            val autoAddSignal = addable.autoAddSignal(userId)
+                            when (val lifecycle = addable.autoAddTracking) {
+                                is AutoAddTracking.Always -> autoAddSignal
+                                is AutoAddTracking.Disabled -> emptyFlow()
+                                is AutoAddTracking.IfNotAdded -> {
+                                    if (lifecycle.spec !in previouslyAdded.value) {
+                                        autoAddSignal.filterIsInstance<AutoAddSignal.Add>().take(1)
+                                    } else {
+                                        emptyFlow()
+                                    }
+                                }
+                            }
+                        }
+                        .merge()
+                        .collect { signal ->
+                            when (signal) {
+                                is AutoAddSignal.Add -> {
+                                    if (signal.spec !in previouslyAdded.value) {
+                                        currentTilesInteractor.addTile(signal.spec, signal.position)
+                                        qsPipelineLogger.logTileAutoAdded(
+                                            userId,
+                                            signal.spec,
+                                            signal.position
+                                        )
+                                        repository.markTileAdded(userId, signal.spec)
+                                    }
+                                }
+                                is AutoAddSignal.Remove -> {
+                                    currentTilesInteractor.removeTiles(setOf(signal.spec))
+                                    qsPipelineLogger.logTileAutoRemoved(userId, signal.spec)
+                                    repository.unmarkTileAdded(userId, signal.spec)
+                                }
+                            }
+                        }
+                }
+            }
+        }
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        with(pw.asIndenting()) {
+            println("AutoAddables:")
+            indentIfPossible { autoAddables.forEach { println(it.description) } }
+        }
+    }
+
+    companion object {
+        private const val TAG = "AutoAddInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt
index 260caa7..fa6de8d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt
@@ -16,8 +16,7 @@
 package com.android.systemui.qs.pipeline.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import java.util.Optional
+import com.android.systemui.shade.ShadeController
 import javax.inject.Inject
 
 /** Encapsulates business logic for interacting with the QS panel. */
@@ -37,17 +36,17 @@
 class PanelInteractorImpl
 @Inject
 constructor(
-    private val centralSurfaces: Optional<CentralSurfaces>,
+    private val shadeController: ShadeController,
 ) : PanelInteractor {
     override fun collapsePanels() {
-        centralSurfaces.ifPresent { it.postAnimateCollapsePanels() }
+        shadeController.postAnimateCollapseShade()
     }
 
     override fun forceCollapsePanels() {
-        centralSurfaces.ifPresent { it.postAnimateForceCollapsePanels() }
+        shadeController.postAnimateForceCollapseShade()
     }
 
     override fun openPanels() {
-        centralSurfaces.ifPresent { it.postAnimateOpenPanels() }
+        shadeController.postAnimateExpandQs()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt
new file mode 100644
index 0000000..ed7b8bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.model
+
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+/** Signal indicating when a tile needs to be auto-added or removed */
+sealed interface AutoAddSignal {
+    /** Tile for this object */
+    val spec: TileSpec
+
+    /** Signal for auto-adding a tile at [position]. */
+    data class Add(
+        override val spec: TileSpec,
+        val position: Int = POSITION_AT_END,
+    ) : AutoAddSignal
+
+    /** Signal for removing a tile. */
+    data class Remove(
+        override val spec: TileSpec,
+    ) : AutoAddSignal
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddTracking.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddTracking.kt
new file mode 100644
index 0000000..154d045
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddTracking.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.model
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+/** Strategy for when to track a particular [AutoAddable]. */
+sealed interface AutoAddTracking {
+
+    /**
+     * Indicates that the signals from the associated [AutoAddable] should all be collected and
+     * reacted accordingly. It may have [AutoAddSignal.Add] and [AutoAddSignal.Remove].
+     */
+    object Always : AutoAddTracking {
+        override fun toString(): String {
+            return "Always"
+        }
+    }
+
+    /**
+     * Indicates that the associated [AutoAddable] is [Disabled] and doesn't need to be collected.
+     */
+    object Disabled : AutoAddTracking {
+        override fun toString(): String {
+            return "Disabled"
+        }
+    }
+
+    /**
+     * Only the first [AutoAddSignal.Add] for each flow of signals needs to be collected, and only
+     * if the tile hasn't been auto-added yet. The associated [AutoAddable] will only emit
+     * [AutoAddSignal.Add].
+     */
+    data class IfNotAdded(val spec: TileSpec) : AutoAddTracking
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddable.kt
new file mode 100644
index 0000000..61fe5b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddable.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.model
+
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Tracks conditions for auto-adding or removing specific tiles.
+ *
+ * When creating a new [AutoAddable], it needs to be registered in a [Module] like
+ * [BaseAutoAddableModule], for example:
+ * ```
+ * @Binds
+ * @IntoSet
+ * fun providesMyAutoAddable(autoAddable: MyAutoAddable): AutoAddable
+ * ```
+ */
+interface AutoAddable {
+
+    /**
+     * Signals associated with a particular user indicating whether a particular tile needs to be
+     * auto-added or auto-removed.
+     */
+    fun autoAddSignal(userId: Int): Flow<AutoAddSignal>
+
+    /**
+     * Lifecycle for this object. It indicates in which cases [autoAddSignal] should be collected
+     */
+    val autoAddTracking: AutoAddTracking
+
+    /** Human readable description */
+    val description: String
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
new file mode 100644
index 0000000..224fc1a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.startable
+
+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.qs.pipeline.domain.interactor.AutoAddInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class QSPipelineCoreStartable
+@Inject
+constructor(
+    private val currentTilesInteractor: CurrentTilesInteractor,
+    private val autoAddInteractor: AutoAddInteractor,
+    private val featureFlags: FeatureFlags,
+) : CoreStartable {
+
+    override fun start() {
+        if (
+            featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST) &&
+                featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)
+        ) {
+            autoAddInteractor.init(currentTilesInteractor)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
deleted file mode 100644
index bbd7234..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.pipeline.prototyping
-
-import android.util.Log
-import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
-import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.statusbar.commandline.Command
-import com.android.systemui.statusbar.commandline.CommandRegistry
-import com.android.systemui.user.data.repository.UserRepository
-import java.io.PrintWriter
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.launch
-
-/**
- * Class for observing results while prototyping.
- *
- * The flows do their own logging, so we just need to make sure that they collect.
- *
- * This will be torn down together with the last of the new pipeline flags remaining here.
- */
-// TODO(b/270385608)
-@SysUISingleton
-class PrototypeCoreStartable
-@Inject
-constructor(
-    private val tileSpecRepository: TileSpecRepository,
-    private val autoAddRepository: AutoAddRepository,
-    private val userRepository: UserRepository,
-    private val featureFlags: FeatureFlags,
-    @Application private val scope: CoroutineScope,
-    private val commandRegistry: CommandRegistry,
-) : CoreStartable {
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    override fun start() {
-        if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
-            scope.launch {
-                userRepository.selectedUserInfo
-                    .flatMapLatest { user -> tileSpecRepository.tilesSpecs(user.id) }
-                    .collect {}
-            }
-            if (featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)) {
-                scope.launch {
-                    userRepository.selectedUserInfo
-                        .flatMapLatest { user -> autoAddRepository.autoAddedTiles(user.id) }
-                        .collect { tiles -> Log.d(TAG, "Auto-added tiles: $tiles") }
-                }
-            }
-            commandRegistry.registerCommand(COMMAND, ::CommandExecutor)
-        }
-    }
-
-    private inner class CommandExecutor : Command {
-        override fun execute(pw: PrintWriter, args: List<String>) {
-            if (args.size < 2) {
-                pw.println("Error: needs at least two arguments")
-                return
-            }
-            val spec = TileSpec.create(args[1])
-            if (spec == TileSpec.Invalid) {
-                pw.println("Error: Invalid tile spec ${args[1]}")
-            }
-            if (args[0] == "add") {
-                performAdd(args, spec)
-                pw.println("Requested tile added")
-            } else if (args[0] == "remove") {
-                performRemove(args, spec)
-                pw.println("Requested tile removed")
-            } else {
-                pw.println("Error: unknown command")
-            }
-        }
-
-        private fun performAdd(args: List<String>, spec: TileSpec) {
-            val position = args.getOrNull(2)?.toInt() ?: TileSpecRepository.POSITION_AT_END
-            val user = args.getOrNull(3)?.toInt() ?: userRepository.getSelectedUserInfo().id
-            scope.launch { tileSpecRepository.addTile(user, spec, position) }
-        }
-
-        private fun performRemove(args: List<String>, spec: TileSpec) {
-            val user = args.getOrNull(2)?.toInt() ?: userRepository.getSelectedUserInfo().id
-            scope.launch { tileSpecRepository.removeTiles(user, listOf(spec)) }
-        }
-
-        override fun help(pw: PrintWriter) {
-            pw.println("Usage: adb shell cmd statusbar $COMMAND:")
-            pw.println("  add <spec> [position] [user]")
-            pw.println("  remove <spec> [user]")
-        }
-    }
-
-    companion object {
-        private const val COMMAND = "qs-pipeline"
-        private const val TAG = "PrototypeCoreStartable"
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
index af1cd09..11b5dd7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
@@ -52,7 +52,11 @@
     internal constructor(
         override val spec: String,
         val componentName: ComponentName,
-    ) : TileSpec(spec)
+    ) : TileSpec(spec) {
+        override fun toString(): String {
+            return "CustomTileSpec(${componentName.toShortString()})"
+        }
+    }
 
     companion object {
         /** Create a [TileSpec] from the string [spec]. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index 8318ec9..573cb715 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -18,7 +18,8 @@
 
 import android.annotation.UserIdInt
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.qs.pipeline.dagger.QSAutoAddLog
 import com.android.systemui.qs.pipeline.dagger.QSTileListLog
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
@@ -32,10 +33,12 @@
 @Inject
 constructor(
     @QSTileListLog private val tileListLogBuffer: LogBuffer,
+    @QSAutoAddLog private val tileAutoAddLogBuffer: LogBuffer,
 ) {
 
     companion object {
         const val TILE_LIST_TAG = "QSTileListLog"
+        const val AUTO_ADD_TAG = "QSAutoAddableLog"
     }
 
     /**
@@ -136,6 +139,31 @@
         )
     }
 
+    fun logTileAutoAdded(userId: Int, spec: TileSpec, position: Int) {
+        tileAutoAddLogBuffer.log(
+            AUTO_ADD_TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = userId
+                int2 = position
+                str1 = spec.toString()
+            },
+            { "Tile $str1 auto added for user $int1 at position $int2" }
+        )
+    }
+
+    fun logTileAutoRemoved(userId: Int, spec: TileSpec) {
+        tileAutoAddLogBuffer.log(
+            AUTO_ADD_TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = userId
+                str1 = spec.toString()
+            },
+            { "Tile $str1 auto removed for user $int1" }
+        )
+    }
+
     /** Reasons for destroying an existing tile. */
     enum class TileDestroyedReason(val readable: String) {
         TILE_REMOVED("Tile removed from  current set"),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ChevronImageView.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ChevronImageView.kt
new file mode 100644
index 0000000..8fd1d6f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/ChevronImageView.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.ImageView
+
+class ChevronImageView(context: Context, attrs: AttributeSet?) : ImageView(context, attrs) {
+
+    override fun resolveLayoutDirection(): Boolean {
+        val previousLayoutDirection = layoutDirection
+        return super.resolveLayoutDirection().also { resolved ->
+            if (resolved && layoutDirection != previousLayoutDirection) {
+                onRtlPropertiesChanged(layoutDirection)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index cd69f4e..7e45491 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -130,6 +130,11 @@
                 d.setLayoutDirection(getLayoutDirection());
             }
 
+            final Drawable lastDrawable = iv.getDrawable();
+            if (lastDrawable instanceof Animatable2) {
+                ((Animatable2) lastDrawable).clearAnimationCallbacks();
+            }
+
             if (iv instanceof SlashImageView) {
                 ((SlashImageView) iv).setAnimationEnabled(shouldAnimate);
                 ((SlashImageView) iv).setState(null, d);
@@ -243,13 +248,11 @@
      */
     private static int getIconColorForState(Context context, QSTile.State state) {
         if (state.disabledByPolicy || state.state == Tile.STATE_UNAVAILABLE) {
-            return Utils.getColorAttrDefaultColor(
-                    context, com.android.internal.R.attr.textColorTertiary);
+            return Utils.getColorAttrDefaultColor(context, R.attr.outline);
         } else if (state.state == Tile.STATE_INACTIVE) {
-            return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary);
+            return Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactiveVariant);
         } else if (state.state == Tile.STATE_ACTIVE) {
-            return Utils.getColorAttrDefaultColor(context,
-                    com.android.internal.R.attr.textColorOnAccent);
+            return Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive);
         } else {
             Log.e("QSIconView", "Invalid state " + state);
             return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 2a9e7d0..1ca2a96 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -330,7 +330,9 @@
         final int eventId = mClickEventId++;
         mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state,
                 eventId);
-        mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget();
+        if (!mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+            mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget();
+        }
     }
 
     public LogMaker populate(LogMaker logMaker) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index b806683..764ef68 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -29,6 +29,7 @@
 import android.service.quicksettings.Tile
 import android.text.TextUtils
 import android.util.Log
+import android.util.TypedValue
 import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.View
@@ -92,24 +93,21 @@
             updateHeight()
         }
 
-    private val colorActive = Utils.getColorAttrDefaultColor(context,
-            com.android.internal.R.attr.colorAccentPrimary)
-    private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.offStateColor)
-    private val colorUnavailable = Utils.applyAlpha(UNAVAILABLE_ALPHA, colorInactive)
+    private val colorActive = Utils.getColorAttrDefaultColor(context, R.attr.shadeActive)
+    private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.shadeInactive)
+    private val colorUnavailable = Utils.getColorAttrDefaultColor(context, R.attr.shadeDisabled)
 
-    private val colorLabelActive =
-            Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorOnAccent)
-    private val colorLabelInactive =
-            Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
+    private val colorLabelActive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive)
+    private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive)
     private val colorLabelUnavailable =
-        Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary)
+        Utils.getColorAttrDefaultColor(context, R.attr.outline)
 
     private val colorSecondaryLabelActive =
-            Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondaryInverse)
+        Utils.getColorAttrDefaultColor(context, R.attr.onShadeActiveVariant)
     private val colorSecondaryLabelInactive =
-            Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary)
+            Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactiveVariant)
     private val colorSecondaryLabelUnavailable =
-        Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary)
+        Utils.getColorAttrDefaultColor(context, R.attr.outline)
 
     private lateinit var label: TextView
     protected lateinit var secondaryLabel: TextView
@@ -118,6 +116,10 @@
     private lateinit var customDrawableView: ImageView
     private lateinit var chevronView: ImageView
     private var mQsLogger: QSLogger? = null
+
+    /**
+     * Controls if tile background is set to a [RippleDrawable] see [setClickable]
+     */
     protected var showRippleEffect = true
 
     private lateinit var ripple: RippleDrawable
@@ -151,6 +153,11 @@
     private val locInScreen = IntArray(2)
 
     init {
+        val typedValue = TypedValue()
+        if (!getContext().theme.resolveAttribute(R.attr.isQsTheme, typedValue, true)) {
+            throw IllegalStateException("QSViewImpl must be inflated with a theme that contains " +
+                    "Theme.SystemUI.QuickSettings")
+        }
         setId(generateViewId())
         orientation = LinearLayout.HORIZONTAL
         gravity = Gravity.CENTER_VERTICAL or Gravity.START
@@ -437,7 +444,6 @@
 
     protected open fun handleStateChanged(state: QSTile.State) {
         val allowAnimations = animationsEnabled()
-        showRippleEffect = state.showRippleEffect
         isClickable = state.state != Tile.STATE_UNAVAILABLE
         isLongClickable = state.handlesLongClick
         icon.setIcon(state, allowAnimations)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index a444e76..9f10e6f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -155,7 +155,6 @@
         state.contentDescription = state.label;
         state.value = mPowerSave;
         state.expandedAccessibilityClassName = Switch.class.getName();
-        state.showRippleEffect = mSetting.getValue() == 0;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 3e7bdd1..9bb192b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -34,6 +34,7 @@
 import android.service.notification.ZenModeConfig;
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.View;
 import android.widget.Switch;
 
@@ -315,6 +316,7 @@
 
     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
         public void onZenChanged(int zen) {
+            Log.d(TAG, "Zen changed to " + zen + ". Requesting refresh of tile.");
             refreshState(zen);
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index eef4c1d..b5e6a0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -166,17 +166,6 @@
     }
 
     @Override
-    protected void handleLongClick(@Nullable View view) {
-        try {
-            // Need to wake on long click so bouncer->settings works.
-            mDreamManager.awaken();
-        } catch (RemoteException e) {
-            Log.e(LOG_TAG, "Can't awaken", e);
-        }
-        super.handleLongClick(view);
-    }
-
-    @Override
     protected void handleUpdateState(BooleanState state, Object arg) {
         state.label = getTileLabel();
         state.secondaryLabel = getActiveDreamName();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index c013486..423fa80 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -38,7 +38,9 @@
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SystemSettings
@@ -57,11 +59,13 @@
     statusBarStateController: StatusBarStateController,
     activityStarter: ActivityStarter,
     qsLogger: QSLogger,
+    private val keyguardStateController: KeyguardStateController,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
     private val systemSettings: SystemSettings,
     private val secureSettings: SecureSettings,
     private val systemClock: SystemClock,
     private val featureFlags: FeatureFlags,
+    private val userTracker: UserTracker,
     @Background private val backgroundDelayableExecutor: DelayableExecutor
 ) :
     QSTileImpl<QSTile.State?>(
@@ -86,26 +90,40 @@
     }
 
     override fun handleClick(view: View?) {
-        mUiHandler.post {
+        // We animate from the touched view only if we are not on the keyguard
+        val animateFromView: Boolean = view != null && !keyguardStateController.isShowing
+
+        val runnable = Runnable {
             val dialog: SystemUIDialog =
                 FontScalingDialog(
                     mContext,
                     systemSettings,
                     secureSettings,
                     systemClock,
+                    userTracker,
                     mainHandler,
                     backgroundDelayableExecutor
                 )
-            if (view != null) {
+            if (animateFromView) {
                 dialogLaunchAnimator.showFromView(
                     dialog,
-                    view,
+                    view!!,
                     DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
                 )
             } else {
                 dialog.show()
             }
         }
+
+        mainHandler.post {
+            mActivityStarter.executeRunnableDismissingKeyguard(
+                runnable,
+                /* cancelAction= */ null,
+                /* dismissShade= */ true,
+                /* afterKeyguardGone= */ true,
+                /* deferred= */ false
+            )
+        }
     }
 
     override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index 9e365d3..1ba377b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -120,7 +120,7 @@
         state.label = mContext.getString(R.string.qr_code_scanner_title);
         state.contentDescription = state.label;
         state.icon = ResourceIcon.get(R.drawable.ic_qr_code_scanner);
-        state.state = mQRCodeScannerController.isAbleToOpenCameraApp() ? Tile.STATE_INACTIVE
+        state.state = mQRCodeScannerController.isAbleToLaunchScannerActivity() ? Tile.STATE_INACTIVE
                 : Tile.STATE_UNAVAILABLE;
         // The assumption is that if the OEM has the QR code scanner module enabled then the scanner
         // would go to "Unavailable" state only when GMS core is updating.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index a60d1ad..9ffcba6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -169,7 +169,6 @@
         state.icon = ResourceIcon.get(state.state == Tile.STATE_ACTIVE
                 ? R.drawable.qs_light_dark_theme_icon_on
                 : R.drawable.qs_light_dark_theme_icon_off);
-        state.showRippleEffect = false;
         state.expandedAccessibilityClassName = Switch.class.getName();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 17e72e5..469ddd8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -141,6 +141,9 @@
         state.contentDescription = state.label;
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+        state.secondaryLabel = state.value
+                ? ""
+                : mContext.getString(R.string.quick_settings_work_mode_paused_state);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index ad65845..6e1ef91 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -284,7 +284,6 @@
         mHandler.removeCallbacks(mHideProgressBarRunnable);
         mHandler.removeCallbacks(mHideSearchingRunnable);
         mMobileNetworkLayout.setOnClickListener(null);
-        mMobileDataToggle.setOnCheckedChangeListener(null);
         mConnectedWifListLayout.setOnClickListener(null);
         if (mSecondaryMobileNetworkLayout != null) {
             mSecondaryMobileNetworkLayout.setOnClickListener(null);
@@ -349,18 +348,16 @@
             }
             mInternetDialogController.connectCarrierNetwork();
         });
-        mMobileDataToggle.setOnCheckedChangeListener(
-                (buttonView, isChecked) -> {
-                    if (!isChecked && shouldShowMobileDialog()) {
-                        showTurnOffMobileDialog();
-                    } else if (!shouldShowMobileDialog()) {
-                        if (mInternetDialogController.isMobileDataEnabled() == isChecked) {
-                            return;
-                        }
-                        mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
-                                isChecked, false);
-                    }
-                });
+        mMobileDataToggle.setOnClickListener(v -> {
+            boolean isChecked = mMobileDataToggle.isChecked();
+            if (!isChecked && shouldShowMobileDialog()) {
+                mMobileDataToggle.setChecked(true);
+                showTurnOffMobileDialog();
+            } else if (mInternetDialogController.isMobileDataEnabled() != isChecked) {
+                mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
+                        isChecked, false);
+            }
+        });
         mConnectedWifListLayout.setOnClickListener(this::onClickConnectedWifi);
         mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton);
         mWiFiToggle.setOnCheckedChangeListener(
@@ -686,9 +683,7 @@
         mAlertDialog = new Builder(mContext)
                 .setTitle(R.string.mobile_data_disable_title)
                 .setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName))
-                .setNegativeButton(android.R.string.cancel, (d, w) -> {
-                    mMobileDataToggle.setChecked(true);
-                })
+                .setNegativeButton(android.R.string.cancel, (d, w) -> {})
                 .setPositiveButton(
                         com.android.internal.R.string.alert_windows_notification_turn_off_action,
                         (d, w) -> {
@@ -698,7 +693,6 @@
                             Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true);
                         })
                 .create();
-        mAlertDialog.setOnCancelListener(dialog -> mMobileDataToggle.setChecked(true));
         mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
         SystemUIDialog.setShowForAllUsers(mAlertDialog, true);
         SystemUIDialog.registerDismissListener(mAlertDialog);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index abeb5af..ec12580 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -172,7 +172,9 @@
     private Executor mExecutor;
     private AccessPointController mAccessPointController;
     private IntentFilter mConnectionStateFilter;
-    private InternetDialogCallback mCallback;
+    @VisibleForTesting
+    @Nullable
+    InternetDialogCallback mCallback;
     private UiEventLogger mUiEventLogger;
     private BroadcastDispatcher mBroadcastDispatcher;
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -215,12 +217,16 @@
             new KeyguardUpdateMonitorCallback() {
                 @Override
                 public void onRefreshCarrierInfo() {
-                    mCallback.onRefreshCarrierInfo();
+                    if (mCallback != null) {
+                        mCallback.onRefreshCarrierInfo();
+                    }
                 }
 
                 @Override
                 public void onSimStateChanged(int subId, int slotId, int simState) {
-                    mCallback.onSimStateChanged();
+                    if (mCallback != null) {
+                        mCallback.onSimStateChanged();
+                    }
                 }
             };
 
@@ -331,6 +337,7 @@
         mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
         mConnectivityManager.unregisterNetworkCallback(mConnectivityManagerNetworkCallback);
         mConnectedWifiInternetMonitor.unregisterCallback();
+        mCallback = null;
     }
 
     @VisibleForTesting
@@ -724,7 +731,7 @@
         ActivityLaunchAnimator.Controller controller =
                 mDialogLaunchAnimator.createActivityLaunchController(view);
 
-        if (controller == null) {
+        if (controller == null && mCallback != null) {
             mCallback.dismissDialog();
         }
 
@@ -1095,7 +1102,9 @@
             mHasWifiEntries = false;
         }
 
-        mCallback.onAccessPointsChanged(wifiEntries, connectedEntry, hasMoreWifiEntries);
+        if (mCallback != null) {
+            mCallback.onAccessPointsChanged(wifiEntries, connectedEntry, hasMoreWifiEntries);
+        }
     }
 
     @Override
@@ -1117,34 +1126,46 @@
 
         @Override
         public void onServiceStateChanged(@NonNull ServiceState serviceState) {
-            mCallback.onServiceStateChanged(serviceState);
+            if (mCallback != null) {
+                mCallback.onServiceStateChanged(serviceState);
+            }
         }
 
         @Override
         public void onDataConnectionStateChanged(int state, int networkType) {
-            mCallback.onDataConnectionStateChanged(state, networkType);
+            if (mCallback != null) {
+                mCallback.onDataConnectionStateChanged(state, networkType);
+            }
         }
 
         @Override
         public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength) {
-            mCallback.onSignalStrengthsChanged(signalStrength);
+            if (mCallback != null) {
+                mCallback.onSignalStrengthsChanged(signalStrength);
+            }
         }
 
         @Override
         public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
             mSubIdTelephonyDisplayInfoMap.put(mSubId, telephonyDisplayInfo);
-            mCallback.onDisplayInfoChanged(telephonyDisplayInfo);
+            if (mCallback != null) {
+                mCallback.onDisplayInfoChanged(telephonyDisplayInfo);
+            }
         }
 
         @Override
         public void onUserMobileDataStateChanged(boolean enabled) {
-            mCallback.onUserMobileDataStateChanged(enabled);
+            if (mCallback != null) {
+                mCallback.onUserMobileDataStateChanged(enabled);
+            }
         }
 
         @Override
         public void onCarrierNetworkChange(boolean active) {
             mCarrierNetworkChangeMode = active;
-            mCallback.onCarrierNetworkChange(active);
+            if (mCallback != null) {
+                mCallback.onCarrierNetworkChange(active);
+            }
         }
     }
 
@@ -1171,14 +1192,18 @@
                 scanWifiAccessPoints();
             }
             // update UI
-            mCallback.onCapabilitiesChanged(network, capabilities);
+            if (mCallback != null) {
+                mCallback.onCapabilitiesChanged(network, capabilities);
+            }
         }
 
         @Override
         @WorkerThread
         public void onLost(@NonNull Network network) {
             mHasEthernet = false;
-            mCallback.onLost(network);
+            if (mCallback != null) {
+                mCallback.onLost(network);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 36dec1d..4c6281e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -16,30 +16,17 @@
 
 package com.android.systemui.qs.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
 
 /** Models UI state and handles user input for the quick settings scene. */
+@SysUISingleton
 class QuickSettingsSceneViewModel
-@AssistedInject
-constructor(
-    lockscreenSceneInteractorFactory: LockscreenSceneInteractor.Factory,
-    @Assisted containerName: String,
-) {
-    private val lockscreenSceneInteractor: LockscreenSceneInteractor =
-        lockscreenSceneInteractorFactory.create(containerName)
-
+@Inject
+constructor(private val bouncerInteractor: BouncerInteractor) {
     /** Notifies that some content in quick settings was clicked. */
     fun onContentClicked() {
-        lockscreenSceneInteractor.dismissLockscreen()
-    }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(
-            containerName: String,
-        ): QuickSettingsSceneViewModel
+        bouncerInteractor.showOrUnlockDevice()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 07259c2..9f45f66 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -25,11 +25,9 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
@@ -80,13 +78,14 @@
 import com.android.internal.app.AssistUtils;
 import com.android.internal.app.IVoiceInteractionSessionListener;
 import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.util.ScreenshotHelper;
 import com.android.internal.util.ScreenshotRequest;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -97,6 +96,7 @@
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.buttons.KeyButtonView;
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeViewController;
@@ -123,6 +123,7 @@
 import java.util.function.Supplier;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * Class to send information from overview to launcher with a binder.
@@ -142,13 +143,17 @@
     private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
 
     private final Context mContext;
+    private final FeatureFlags mFeatureFlags;
     private final Executor mMainExecutor;
     private final ShellInterface mShellInterface;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+    private final Lazy<ShadeViewController> mShadeViewControllerLazy;
     private SysUiState mSysUiState;
     private final Handler mHandler;
     private final Lazy<NavigationBarController> mNavBarControllerLazy;
     private final NotificationShadeWindowController mStatusBarWinController;
+    private final Provider<SceneInteractor> mSceneInteractor;
+
     private final Runnable mConnectionRunnable = () ->
             internalConnectToCurrentUser("runnable: startConnectionToCurrentUser");
     private final ComponentName mRecentsComponentName;
@@ -173,8 +178,6 @@
     private boolean mInputFocusTransferStarted;
     private float mInputFocusTransferStartY;
     private long mInputFocusTransferStartMillis;
-    private float mWindowCornerRadius;
-    private boolean mSupportsRoundedCornersOnWindows;
     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
 
     @VisibleForTesting
@@ -200,16 +203,12 @@
 
         // TODO: change the method signature to use (boolean inputFocusTransferStarted)
         @Override
-        public void onStatusBarMotionEvent(MotionEvent event) {
-            verifyCallerAndClearCallingIdentity("onStatusBarMotionEvent", () -> {
+        public void onStatusBarTouchEvent(MotionEvent event) {
+            verifyCallerAndClearCallingIdentity("onStatusBarTouchEvent", () -> {
                 // TODO move this logic to message queue
                 mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> {
                     if (event.getActionMasked() == ACTION_DOWN) {
-                        ShadeViewController shadeViewController =
-                                centralSurfaces.getShadeViewController();
-                        if (shadeViewController != null) {
-                            shadeViewController.startExpandLatencyTracking();
-                        }
+                        mShadeViewControllerLazy.get().startExpandLatencyTracking();
                     }
                     mHandler.post(() -> {
                         int action = event.getActionMasked();
@@ -217,17 +216,27 @@
                             mInputFocusTransferStarted = true;
                             mInputFocusTransferStartY = event.getY();
                             mInputFocusTransferStartMillis = event.getEventTime();
-                            centralSurfaces.onInputFocusTransfer(
-                                    mInputFocusTransferStarted, false /* cancel */,
-                                    0 /* velocity */);
+
+                            // If scene framework is enabled, set the scene container window to
+                            // visible and let the touch "slip" into that window.
+                            if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+                                mSceneInteractor.get().setVisible(true, "swipe down on launcher");
+                            } else {
+                                centralSurfaces.onInputFocusTransfer(
+                                        mInputFocusTransferStarted, false /* cancel */,
+                                        0 /* velocity */);
+                            }
                         }
                         if (action == ACTION_UP || action == ACTION_CANCEL) {
                             mInputFocusTransferStarted = false;
-                            float velocity = (event.getY() - mInputFocusTransferStartY)
-                                    / (event.getEventTime() - mInputFocusTransferStartMillis);
-                            centralSurfaces.onInputFocusTransfer(mInputFocusTransferStarted,
-                                    action == ACTION_CANCEL,
-                                    velocity);
+
+                            if (!mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+                                float velocity = (event.getY() - mInputFocusTransferStartY)
+                                        / (event.getEventTime() - mInputFocusTransferStartMillis);
+                                centralSurfaces.onInputFocusTransfer(mInputFocusTransferStarted,
+                                        action == ACTION_CANCEL,
+                                        velocity);
+                            }
                         }
                         event.recycle();
                     });
@@ -236,6 +245,13 @@
         }
 
         @Override
+        public void onStatusBarTrackpadEvent(MotionEvent event) {
+            verifyCallerAndClearCallingIdentityPostMain("onStatusBarTrackpadEvent", () ->
+                    mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces ->
+                            centralSurfaces.onStatusBarTrackpadEvent(event)));
+        }
+
+        @Override
         public void onBackPressed() {
             verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
                 sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
@@ -345,7 +361,7 @@
 
         @Override
         public void expandNotificationPanel() {
-            verifyCallerAndClearCallingIdentity("expandNotificationPanel",
+            verifyCallerAndClearCallingIdentityPostMain("expandNotificationPanel",
                     () -> mCommandQueue.handleSystemKey(new KeyEvent(KeyEvent.ACTION_DOWN,
                             KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN)));
         }
@@ -353,7 +369,7 @@
         @Override
         public void toggleNotificationPanel() {
             verifyCallerAndClearCallingIdentityPostMain("toggleNotificationPanel", () ->
-                    mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::togglePanel));
+                    mCommandQueue.togglePanel());
         }
 
         private boolean verifyCaller(String reason) {
@@ -447,8 +463,6 @@
 
             Bundle params = new Bundle();
             params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
-            params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
-            params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
             params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
                     mSysuiUnlockAnimationController.asBinder());
             mUnfoldTransitionProgressForwarder.ifPresent(
@@ -551,8 +565,11 @@
             ShellInterface shellInterface,
             Lazy<NavigationBarController> navBarControllerLazy,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
+            Lazy<ShadeViewController> shadeViewControllerLazy,
             NavigationModeController navModeController,
-            NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
+            NotificationShadeWindowController statusBarWinController,
+            SysUiState sysUiState,
+            Provider<SceneInteractor> sceneInteractor,
             UserTracker userTracker,
             ScreenLifecycle screenLifecycle,
             WakefulnessLifecycle wakefulnessLifecycle,
@@ -560,6 +577,7 @@
             DisplayTracker displayTracker,
             KeyguardUnlockAnimationController sysuiUnlockAnimationController,
             AssistUtils assistUtils,
+            FeatureFlags featureFlags,
             DumpManager dumpManager,
             Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder
     ) {
@@ -569,21 +587,21 @@
         }
 
         mContext = context;
+        mFeatureFlags = featureFlags;
         mMainExecutor = mainExecutor;
         mShellInterface = shellInterface;
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+        mShadeViewControllerLazy = shadeViewControllerLazy;
         mHandler = new Handler();
         mNavBarControllerLazy = navBarControllerLazy;
         mStatusBarWinController = statusBarWinController;
+        mSceneInteractor = sceneInteractor;
         mUserTracker = userTracker;
         mConnectionBackoffAttempts = 0;
         mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
                 com.android.internal.R.string.config_recentsComponentName));
         mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
                 .setPackage(mRecentsComponentName.getPackageName());
-        mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
-        mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils
-                .supportsRoundedCornersOnWindows(mContext.getResources());
         mSysUiState = sysUiState;
         mSysUiState.addCallback(this::notifySystemUiStateFlags);
         mUiEventLogger = uiEventLogger;
@@ -679,13 +697,10 @@
                 mNavBarControllerLazy.get().getDefaultNavigationBar();
         final NavigationBarView navBarView =
                 mNavBarControllerLazy.get().getNavigationBarView(mContext.getDisplayId());
-        final ShadeViewController panelController =
-                mCentralSurfacesOptionalLazy.get()
-                        .map(CentralSurfaces::getShadeViewController)
-                        .orElse(null);
         if (SysUiState.DEBUG) {
             Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment
-                    + " navBarView=" + navBarView + " panelController=" + panelController);
+                    + " navBarView=" + navBarView
+                    + " shadeViewController=" + mShadeViewControllerLazy.get());
         }
 
         if (navBarFragment != null) {
@@ -694,9 +709,7 @@
         if (navBarView != null) {
             navBarView.updateDisabledSystemUiStateFlags(mSysUiState);
         }
-        if (panelController != null) {
-            panelController.updateSystemUiStateFlags();
-        }
+        mShadeViewControllerLazy.get().updateSystemUiStateFlags();
         if (mStatusBarWinController != null) {
             mStatusBarWinController.notifyStateChangedCallbacks();
         }
@@ -1077,8 +1090,6 @@
         pw.print("  mInputFocusTransferStarted="); pw.println(mInputFocusTransferStarted);
         pw.print("  mInputFocusTransferStartY="); pw.println(mInputFocusTransferStartY);
         pw.print("  mInputFocusTransferStartMillis="); pw.println(mInputFocusTransferStartMillis);
-        pw.print("  mWindowCornerRadius="); pw.println(mWindowCornerRadius);
-        pw.print("  mSupportsRoundedCornersOnWindows="); pw.println(mSupportsRoundedCornersOnWindows);
         pw.print("  mActiveNavBarRegion="); pw.println(mActiveNavBarRegion);
         pw.print("  mNavigationBarSurface="); pw.println(mNavigationBarSurface);
         pw.print("  mNavBarMode="); pw.println(mNavBarMode);
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 752471d..398e64b 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -16,16 +16,16 @@
 
 package com.android.systemui.scene
 
-import com.android.systemui.scene.data.model.SceneContainerConfigModule
+import com.android.systemui.scene.domain.startable.SceneContainerStartableModule
+import com.android.systemui.scene.shared.model.SceneContainerConfigModule
 import com.android.systemui.scene.ui.composable.SceneModule
-import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModelModule
 import dagger.Module
 
 @Module(
     includes =
         [
             SceneContainerConfigModule::class,
-            SceneContainerViewModelModule::class,
+            SceneContainerStartableModule::class,
             SceneModule::class,
         ],
 )
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfig.kt
deleted file mode 100644
index d0769eb..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfig.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.data.model
-
-import com.android.systemui.scene.shared.model.SceneKey
-
-/** Models the configuration of a single scene container. */
-data class SceneContainerConfig(
-    /** Container name. Must be unique across all containers in System UI. */
-    val name: String,
-
-    /**
-     * The keys to all scenes in the container, sorted by z-order such that the last one renders on
-     * top of all previous ones. Scene keys within the same container must not repeat but it's okay
-     * to have the same scene keys in different containers.
-     */
-    val sceneKeys: List<SceneKey>,
-
-    /**
-     * The key of the scene that is the initial current scene when the container is first set up,
-     * before taking any application state in to account.
-     */
-    val initialSceneKey: SceneKey,
-) {
-    init {
-        check(sceneKeys.isNotEmpty()) { "A container must have at least one scene key." }
-
-        check(sceneKeys.contains(initialSceneKey)) {
-            "The initial key \"$initialSceneKey\" is not present in this container."
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfigModule.kt b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfigModule.kt
deleted file mode 100644
index 0af8094..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfigModule.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.data.model
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.scene.shared.model.SceneContainerNames
-import com.android.systemui.scene.shared.model.SceneKey
-import dagger.Module
-import dagger.Provides
-import javax.inject.Named
-
-@Module
-object SceneContainerConfigModule {
-
-    @Provides
-    fun containerConfigs(): Map<String, SceneContainerConfig> {
-        return mapOf(
-            SceneContainerNames.SYSTEM_UI_DEFAULT to
-                SceneContainerConfig(
-                    name = SceneContainerNames.SYSTEM_UI_DEFAULT,
-                    // Note that this list is in z-order. The first one is the bottom-most and the
-                    // last
-                    // one is top-most.
-                    sceneKeys =
-                        listOf(
-                            SceneKey.Gone,
-                            SceneKey.Lockscreen,
-                            SceneKey.Bouncer,
-                            SceneKey.Shade,
-                            SceneKey.QuickSettings,
-                        ),
-                    initialSceneKey = SceneKey.Lockscreen,
-                ),
-        )
-    }
-
-    @Provides
-    @SysUISingleton
-    @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
-    fun provideDefaultSceneContainerConfig(
-        configs: Map<String, SceneContainerConfig>,
-    ): SceneContainerConfig {
-        return checkNotNull(configs[SceneContainerNames.SYSTEM_UI_DEFAULT]) {
-            "No SceneContainerConfig named \"${SceneContainerNames.SYSTEM_UI_DEFAULT}\"."
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 61b162b..350fa38 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -14,123 +14,84 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.scene.data.repository
 
-import com.android.systemui.scene.data.model.SceneContainerConfig
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
 
 /** Source of truth for scene framework application state. */
 class SceneContainerRepository
 @Inject
 constructor(
-    containerConfigurations: Set<SceneContainerConfig>,
+    @Application applicationScope: CoroutineScope,
+    private val config: SceneContainerConfig,
 ) {
+    private val _desiredScene = MutableStateFlow(SceneModel(config.initialSceneKey))
+    val desiredScene: StateFlow<SceneModel> = _desiredScene.asStateFlow()
 
-    private val containerConfigByName: Map<String, SceneContainerConfig> =
-        containerConfigurations.associateBy { config -> config.name }
-    private val containerVisibilityByName: Map<String, MutableStateFlow<Boolean>> =
-        containerConfigByName
-            .map { (containerName, _) -> containerName to MutableStateFlow(true) }
-            .toMap()
-    private val currentSceneByContainerName: Map<String, MutableStateFlow<SceneModel>> =
-        containerConfigByName
-            .map { (containerName, config) ->
-                containerName to MutableStateFlow(SceneModel(config.initialSceneKey))
-            }
-            .toMap()
-    private val sceneTransitionProgressByContainerName: Map<String, MutableStateFlow<Float>> =
-        containerConfigByName
-            .map { (containerName, _) -> containerName to MutableStateFlow(1f) }
-            .toMap()
+    private val _isVisible = MutableStateFlow(true)
+    val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
 
-    init {
-        val repeatedContainerNames =
-            containerConfigurations
-                .groupingBy { config -> config.name }
-                .eachCount()
-                .filter { (_, count) -> count > 1 }
-        check(repeatedContainerNames.isEmpty()) {
-            "Container names must be unique. The following container names appear more than once: ${
-                repeatedContainerNames
-                        .map { (name, count) -> "\"$name\" appears $count times" }
-                        .joinToString(", ")
-            }"
-        }
-    }
+    private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey)
+    private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
+    val transitionState: StateFlow<ObservableTransitionState> =
+        _transitionState
+            .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = defaultTransitionState,
+            )
 
     /**
-     * Returns the keys to all scenes in the container with the given name.
+     * Returns the keys to all scenes in the container.
      *
      * The scenes will be sorted in z-order such that the last one is the one that should be
      * rendered on top of all previous ones.
      */
-    fun allSceneKeys(containerName: String): List<SceneKey> {
-        return containerConfigByName[containerName]?.sceneKeys
-            ?: error(noSuchContainerErrorMessage(containerName))
+    fun allSceneKeys(): List<SceneKey> {
+        return config.sceneKeys
     }
 
-    /** Sets the current scene in the container with the given name. */
-    fun setCurrentScene(containerName: String, scene: SceneModel) {
-        check(allSceneKeys(containerName).contains(scene.key)) {
+    fun setDesiredScene(scene: SceneModel) {
+        check(allSceneKeys().contains(scene.key)) {
             """
-                Cannot set current scene key to "${scene.key}". The container "$containerName" does
-                not contain a scene with that key.
+                Cannot set the desired scene key to "${scene.key}". The configuration does not
+                contain a scene with that key.
             """
                 .trimIndent()
         }
 
-        currentSceneByContainerName.setValue(containerName, scene)
+        _desiredScene.value = scene
     }
 
-    /** The current scene in the container with the given name. */
-    fun currentScene(containerName: String): StateFlow<SceneModel> {
-        return currentSceneByContainerName.mutableOrError(containerName).asStateFlow()
+    /** Sets whether the container is visible. */
+    fun setVisible(isVisible: Boolean) {
+        _isVisible.value = isVisible
     }
 
-    /** Sets whether the container with the given name is visible. */
-    fun setVisible(containerName: String, isVisible: Boolean) {
-        containerVisibilityByName.setValue(containerName, isVisible)
-    }
-
-    /** Whether the container with the given name should be visible. */
-    fun isVisible(containerName: String): StateFlow<Boolean> {
-        return containerVisibilityByName.mutableOrError(containerName).asStateFlow()
-    }
-
-    /** Sets scene transition progress to the current scene in the container with the given name. */
-    fun setSceneTransitionProgress(containerName: String, progress: Float) {
-        sceneTransitionProgressByContainerName.setValue(containerName, progress)
-    }
-
-    /** Progress of the transition into the current scene in the container with the given name. */
-    fun sceneTransitionProgress(containerName: String): StateFlow<Float> {
-        return sceneTransitionProgressByContainerName.mutableOrError(containerName).asStateFlow()
-    }
-
-    private fun <T> Map<String, MutableStateFlow<T>>.mutableOrError(
-        containerName: String,
-    ): MutableStateFlow<T> {
-        return this[containerName] ?: error(noSuchContainerErrorMessage(containerName))
-    }
-
-    private fun <T> Map<String, MutableStateFlow<T>>.setValue(
-        containerName: String,
-        value: T,
-    ) {
-        val mutable = mutableOrError(containerName)
-        mutable.value = value
-    }
-
-    private fun noSuchContainerErrorMessage(containerName: String): String {
-        return """
-            No container named "$containerName". Existing containers:
-            ${containerConfigByName.values.joinToString(", ") { it.name }}
-        """
-            .trimIndent()
+    /**
+     * Binds the given flow so the system remembers it.
+     *
+     * Note that you must call is with `null` when the UI is done or risk a memory leak.
+     */
+    fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
+        _transitionState.value = transitionState
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 1e55975..a7434c6 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -17,57 +17,169 @@
 package com.android.systemui.scene.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.data.repository.SceneContainerRepository
+import com.android.systemui.scene.shared.logger.SceneLogger
+import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 
-/** Business logic and app state accessors for the scene framework. */
+/**
+ * Generic business logic and app state accessors for the scene framework.
+ *
+ * Note that this class should not depend on state or logic of other modules or features. Instead,
+ * other feature modules should depend on and call into this class when their parts of the
+ * application state change.
+ */
 @SysUISingleton
 class SceneInteractor
 @Inject
 constructor(
+    @Application applicationScope: CoroutineScope,
     private val repository: SceneContainerRepository,
+    private val logger: SceneLogger,
 ) {
 
     /**
-     * Returns the keys of all scenes in the container with the given name.
+     * The currently *desired* scene.
+     *
+     * **Important:** this value will _commonly be different_ from what is being rendered in the UI,
+     * by design.
+     *
+     * There are two intended sources for this value:
+     * 1. Programmatic requests to transition to another scene (calls to [changeScene]).
+     * 2. Reports from the UI about completing a transition to another scene (calls to
+     *    [onSceneChanged]).
+     *
+     * Both the sources above cause the value of this flow to change; however, they cause mismatches
+     * in different ways.
+     *
+     * **Updates from programmatic transitions**
+     *
+     * When an external bit of code asks the framework to switch to another scene, the value here
+     * will update immediately. Downstream, the UI will detect this change and initiate the
+     * transition animation. As the transition animation progresses, a threshold will be reached, at
+     * which point the UI and the state here will match each other.
+     *
+     * **Updates from the UI**
+     *
+     * When the user interacts with the UI, the UI runs a transition animation that tracks the user
+     * pointer (for example, the user's finger). During this time, the state value here and what the
+     * UI shows will likely not match. Once/if a threshold is met, the UI reports it and commits the
+     * change, making the value here match the UI again.
+     */
+    val desiredScene: StateFlow<SceneModel> = repository.desiredScene
+
+    /**
+     * The current state of the transition.
+     *
+     * Consumers should use this state to know:
+     * 1. Whether there is an ongoing transition or if the system is at rest.
+     * 2. When transitioning, which scenes are being transitioned between.
+     * 3. When transitioning, what the progress of the transition is.
+     */
+    val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState
+
+    /**
+     * The key of the scene that the UI is currently transitioning to or `null` if there is no
+     * active transition at the moment.
+     *
+     * This is a convenience wrapper around [transitionState], meant for flow-challenged consumers
+     * like Java code.
+     */
+    val transitioningTo: StateFlow<SceneKey?> =
+        transitionState
+            .map { state -> (state as? ObservableTransitionState.Transition)?.toScene }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = null,
+            )
+
+    /** Whether the scene container is visible. */
+    val isVisible: StateFlow<Boolean> = repository.isVisible
+
+    /**
+     * Returns the keys of all scenes in the container.
      *
      * The scenes will be sorted in z-order such that the last one is the one that should be
      * rendered on top of all previous ones.
      */
-    fun allSceneKeys(containerName: String): List<SceneKey> {
-        return repository.allSceneKeys(containerName)
+    fun allSceneKeys(): List<SceneKey> {
+        return repository.allSceneKeys()
     }
 
-    /** Sets the scene in the container with the given name. */
-    fun setCurrentScene(containerName: String, scene: SceneModel) {
-        repository.setCurrentScene(containerName, scene)
+    /**
+     * Requests a scene change to the given scene.
+     *
+     * The change is animated. Therefore, while the value in [desiredScene] will update immediately,
+     * it will be some time before the UI will switch to the desired scene. The scene change
+     * requested is remembered here but served by the UI layer, which will start a transition
+     * animation. Once enough of the transition has occurred, the system will come into agreement
+     * between the [desiredScene] and the UI.
+     */
+    fun changeScene(scene: SceneModel, loggingReason: String) {
+        updateDesiredScene(scene, loggingReason, logger::logSceneChangeRequested)
     }
 
-    /** The current scene in the container with the given name. */
-    fun currentScene(containerName: String): StateFlow<SceneModel> {
-        return repository.currentScene(containerName)
+    /** Sets the visibility of the container. */
+    fun setVisible(isVisible: Boolean, loggingReason: String) {
+        val wasVisible = repository.isVisible.value
+        if (wasVisible == isVisible) {
+            return
+        }
+
+        logger.logVisibilityChange(
+            from = wasVisible,
+            to = isVisible,
+            reason = loggingReason,
+        )
+        return repository.setVisible(isVisible)
     }
 
-    /** Sets the visibility of the container with the given name. */
-    fun setVisible(containerName: String, isVisible: Boolean) {
-        return repository.setVisible(containerName, isVisible)
+    /**
+     * Binds the given flow so the system remembers it.
+     *
+     * Note that you must call is with `null` when the UI is done or risk a memory leak.
+     */
+    fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
+        repository.setTransitionState(transitionState)
     }
 
-    /** Whether the container with the given name is visible. */
-    fun isVisible(containerName: String): StateFlow<Boolean> {
-        return repository.isVisible(containerName)
+    /**
+     * Notifies that the UI has transitioned sufficiently to the given scene.
+     *
+     * *Not intended for external use!*
+     *
+     * Once a transition between one scene and another passes a threshold, the UI invokes this
+     * method to report it, updating the value in [desiredScene] to match what the UI shows.
+     */
+    internal fun onSceneChanged(scene: SceneModel, loggingReason: String) {
+        updateDesiredScene(scene, loggingReason, logger::logSceneChangeCommitted)
     }
 
-    /** Sets scene transition progress to the current scene in the container with the given name. */
-    fun setSceneTransitionProgress(containerName: String, progress: Float) {
-        repository.setSceneTransitionProgress(containerName, progress)
-    }
+    private fun updateDesiredScene(
+        scene: SceneModel,
+        loggingReason: String,
+        log: (from: SceneKey, to: SceneKey, loggingReason: String) -> Unit,
+    ) {
+        val currentSceneKey = desiredScene.value.key
+        if (currentSceneKey == scene.key) {
+            return
+        }
 
-    /** Progress of the transition into the current scene in the container with the given name. */
-    fun sceneTransitionProgress(containerName: String): StateFlow<Float> {
-        return repository.sceneTransitionProgress(containerName)
+        log(
+            /* from= */ currentSceneKey,
+            /* to= */ scene.key,
+            /* loggingReason= */ loggingReason,
+        )
+        repository.setDesiredScene(scene)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
new file mode 100644
index 0000000..1747099
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.domain.startable
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.model.SysUiState
+import com.android.systemui.model.updateFlags
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.logger.SceneLogger
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.launch
+
+/**
+ * Hooks up business logic that manipulates the state of the [SceneInteractor] for the system UI
+ * scene container based on state from other systems.
+ */
+@SysUISingleton
+class SceneContainerStartable
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val sceneInteractor: SceneInteractor,
+    private val authenticationInteractor: AuthenticationInteractor,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val featureFlags: FeatureFlags,
+    private val sysUiState: SysUiState,
+    @DisplayId private val displayId: Int,
+    private val sceneLogger: SceneLogger,
+) : CoreStartable {
+
+    override fun start() {
+        if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+            sceneLogger.logFrameworkEnabled(isEnabled = true)
+            hydrateVisibility()
+            automaticallySwitchScenes()
+            hydrateSystemUiState()
+        } else {
+            sceneLogger.logFrameworkEnabled(isEnabled = false)
+        }
+    }
+
+    /** Updates the visibility of the scene container. */
+    private fun hydrateVisibility() {
+        applicationScope.launch {
+            sceneInteractor.transitionState
+                .mapNotNull { state ->
+                    when (state) {
+                        is ObservableTransitionState.Idle -> {
+                            if (state.scene != SceneKey.Gone) {
+                                true to "scene is not Gone"
+                            } else {
+                                false to "scene is Gone"
+                            }
+                        }
+                        is ObservableTransitionState.Transition -> {
+                            if (state.fromScene == SceneKey.Gone) {
+                                true to "scene transitioning away from Gone"
+                            } else {
+                                null
+                            }
+                        }
+                    }
+                }
+                .distinctUntilChanged()
+                .collect { (isVisible, loggingReason) ->
+                    sceneInteractor.setVisible(isVisible, loggingReason)
+                }
+        }
+    }
+
+    /** Switches between scenes based on ever-changing application state. */
+    private fun automaticallySwitchScenes() {
+        applicationScope.launch {
+            authenticationInteractor.isUnlocked
+                .mapNotNull { isUnlocked ->
+                    val renderedScenes =
+                        when (val transitionState = sceneInteractor.transitionState.value) {
+                            is ObservableTransitionState.Idle -> setOf(transitionState.scene)
+                            is ObservableTransitionState.Transition ->
+                                setOf(
+                                    transitionState.progress,
+                                    transitionState.toScene,
+                                )
+                        }
+                    val isBypassEnabled = authenticationInteractor.isBypassEnabled()
+                    when {
+                        isUnlocked ->
+                            when {
+                                // When the device becomes unlocked in Bouncer, go to Gone.
+                                renderedScenes.contains(SceneKey.Bouncer) ->
+                                    SceneKey.Gone to "device unlocked in Bouncer scene"
+
+                                // When the device becomes unlocked in Lockscreen, go to Gone if
+                                // bypass is enabled.
+                                renderedScenes.contains(SceneKey.Lockscreen) ->
+                                    if (isBypassEnabled) {
+                                        SceneKey.Gone to
+                                            "device unlocked in Lockscreen scene with bypass"
+                                    } else {
+                                        null
+                                    }
+
+                                // We got unlocked while on a scene that's not Lockscreen or
+                                // Bouncer, no need to change scenes.
+                                else -> null
+                            }
+
+                        // When the device becomes locked, to Lockscreen.
+                        !isUnlocked ->
+                            when {
+                                // Already on lockscreen or bouncer, no need to change scenes.
+                                renderedScenes.contains(SceneKey.Lockscreen) ||
+                                    renderedScenes.contains(SceneKey.Bouncer) -> null
+
+                                // We got locked while on a scene that's not Lockscreen or Bouncer,
+                                // go to Lockscreen.
+                                else ->
+                                    SceneKey.Lockscreen to
+                                        "device locked in non-Lockscreen and non-Bouncer scene"
+                            }
+                        else -> null
+                    }
+                }
+                .collect { (targetSceneKey, loggingReason) ->
+                    switchToScene(
+                        targetSceneKey = targetSceneKey,
+                        loggingReason = loggingReason,
+                    )
+                }
+        }
+
+        applicationScope.launch {
+            keyguardInteractor.wakefulnessModel
+                .map { wakefulnessModel -> wakefulnessModel.state }
+                .distinctUntilChanged()
+                .collect { wakefulnessState ->
+                    when (wakefulnessState) {
+                        WakefulnessState.STARTING_TO_SLEEP -> {
+                            switchToScene(
+                                targetSceneKey = SceneKey.Lockscreen,
+                                loggingReason = "device is starting to sleep",
+                            )
+                        }
+                        WakefulnessState.STARTING_TO_WAKE -> {
+                            val authMethod = authenticationInteractor.getAuthenticationMethod()
+                            val isUnlocked = authenticationInteractor.isUnlocked.value
+                            when {
+                                authMethod == AuthenticationMethodModel.None -> {
+                                    switchToScene(
+                                        targetSceneKey = SceneKey.Gone,
+                                        loggingReason =
+                                            "device is starting to wake up while auth method is" +
+                                                " none",
+                                    )
+                                }
+                                authMethod.isSecure && isUnlocked -> {
+                                    switchToScene(
+                                        targetSceneKey = SceneKey.Gone,
+                                        loggingReason =
+                                            "device is starting to wake up while unlocked with a" +
+                                                " secure auth method",
+                                    )
+                                }
+                            }
+                        }
+                        else -> Unit
+                    }
+                }
+        }
+    }
+
+    /** Keeps [SysUiState] up-to-date */
+    private fun hydrateSystemUiState() {
+        applicationScope.launch {
+            sceneInteractor.transitionState
+                .mapNotNull { it as? ObservableTransitionState.Idle }
+                .map { it.scene }
+                .distinctUntilChanged()
+                .collect { sceneKey ->
+                    sysUiState.updateFlags(
+                        displayId,
+                        SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to (sceneKey != SceneKey.Gone),
+                        SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to (sceneKey == SceneKey.Shade),
+                        SYSUI_STATE_QUICK_SETTINGS_EXPANDED to (sceneKey == SceneKey.QuickSettings),
+                        SYSUI_STATE_BOUNCER_SHOWING to (sceneKey == SceneKey.Bouncer),
+                        SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to
+                            (sceneKey == SceneKey.Lockscreen),
+                    )
+                }
+        }
+    }
+
+    private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
+        sceneInteractor.changeScene(
+            scene = SceneModel(targetSceneKey),
+            loggingReason = loggingReason,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
new file mode 100644
index 0000000..8da1803
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.domain.startable
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface SceneContainerStartableModule {
+
+    @Binds
+    @IntoMap
+    @ClassKey(SceneContainerStartable::class)
+    fun bind(impl: SceneContainerStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
new file mode 100644
index 0000000..62136dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.shared.logger
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.SceneFrameworkLog
+import com.android.systemui.scene.shared.model.SceneKey
+import javax.inject.Inject
+
+class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: LogBuffer) {
+
+    fun logFrameworkEnabled(isEnabled: Boolean) {
+        fun asWord(isEnabled: Boolean): String {
+            return if (isEnabled) "enabled" else "disabled"
+        }
+
+        logBuffer.log(
+            tag = TAG,
+            level = LogLevel.INFO,
+            messageInitializer = { bool1 = isEnabled },
+            messagePrinter = { "Scene framework is ${asWord(bool1)}" }
+        )
+    }
+
+    fun logSceneChangeRequested(
+        from: SceneKey,
+        to: SceneKey,
+        reason: String,
+    ) {
+        logBuffer.log(
+            tag = TAG,
+            level = LogLevel.INFO,
+            messageInitializer = {
+                str1 = from.toString()
+                str2 = to.toString()
+                str3 = reason
+            },
+            messagePrinter = { "Scene change requested: $str1 → $str2, reason: $str3" },
+        )
+    }
+
+    fun logSceneChangeCommitted(
+        from: SceneKey,
+        to: SceneKey,
+        reason: String,
+    ) {
+        logBuffer.log(
+            tag = TAG,
+            level = LogLevel.INFO,
+            messageInitializer = {
+                str1 = from.toString()
+                str2 = to.toString()
+                str3 = reason
+            },
+            messagePrinter = { "Scene change committed: $str1 → $str2, reason: $str3" },
+        )
+    }
+
+    fun logVisibilityChange(
+        from: Boolean,
+        to: Boolean,
+        reason: String,
+    ) {
+        fun asWord(isVisible: Boolean): String {
+            return if (isVisible) "visible" else "invisible"
+        }
+
+        logBuffer.log(
+            tag = TAG,
+            level = LogLevel.INFO,
+            messageInitializer = {
+                str1 = asWord(from)
+                str2 = asWord(to)
+                str3 = reason
+            },
+            messagePrinter = { "$str1 → $str2, reason: $str3" },
+        )
+    }
+
+    companion object {
+        private const val TAG = "SceneFramework"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt
new file mode 100644
index 0000000..9a30aa6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.shared.model
+
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * This is a fork of a class by the same name in the `com.android.compose.animation.scene` package.
+ *
+ * TODO(b/293899074): remove this fork, once we can compile Compose into System UI.
+ */
+sealed class ObservableTransitionState {
+    /** No transition/animation is currently running. */
+    data class Idle(val scene: SceneKey) : ObservableTransitionState()
+
+    /** There is a transition animating between two scenes. */
+    data class Transition(
+        val fromScene: SceneKey,
+        val toScene: SceneKey,
+        val progress: Flow<Float>,
+    ) : ObservableTransitionState()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
index 354de8a..31597c1 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -27,8 +27,6 @@
  * takes care of rendering the current scene and allowing scenes to be switched from one to another
  * based on either user action (for example, swiping down while on the lock screen scene may switch
  * to the shade scene).
- *
- * The framework also supports multiple containers, each one with its own configuration.
  */
 interface Scene {
 
@@ -59,7 +57,7 @@
      * The API is designed such that it's possible to emit ever-changing values for each
      * [UserAction] to enable, disable, or change the destination scene of a given user action.
      */
-    fun destinationScenes(containerName: String): StateFlow<Map<UserAction, SceneModel>> =
+    fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
         MutableStateFlow(emptyMap<UserAction, SceneModel>()).asStateFlow()
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
new file mode 100644
index 0000000..8204edc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.shared.model
+
+/** Models the configuration of the scene container. */
+data class SceneContainerConfig(
+
+    /**
+     * The keys to all scenes in the container, sorted by z-order such that the last one renders on
+     * top of all previous ones. Scene keys within the same container must not repeat but it's okay
+     * to have the same scene keys in different containers.
+     */
+    val sceneKeys: List<SceneKey>,
+
+    /**
+     * The key of the scene that is the initial current scene when the container is first set up,
+     * before taking any application state in to account.
+     */
+    val initialSceneKey: SceneKey,
+) {
+    init {
+        check(sceneKeys.isNotEmpty()) { "A container must have at least one scene key." }
+
+        check(sceneKeys.contains(initialSceneKey)) {
+            "The initial key \"$initialSceneKey\" is not present in this container."
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt
new file mode 100644
index 0000000..f74005b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.shared.model
+
+import dagger.Module
+import dagger.Provides
+
+@Module
+object SceneContainerConfigModule {
+
+    @Provides
+    fun containerConfig(): SceneContainerConfig {
+        return SceneContainerConfig(
+            // Note that this list is in z-order. The first one is the bottom-most and the
+            // last
+            // one is top-most.
+            sceneKeys =
+                listOf(
+                    SceneKey.Gone,
+                    SceneKey.Lockscreen,
+                    SceneKey.Bouncer,
+                    SceneKey.Shade,
+                    SceneKey.QuickSettings,
+                ),
+            initialSceneKey = SceneKey.Lockscreen,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt
deleted file mode 100644
index 64f5087..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.shared.model
-
-object SceneContainerNames {
-    const val SYSTEM_UI_DEFAULT = "system_ui"
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
new file mode 100644
index 0000000..cdf50ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -0,0 +1,45 @@
+package com.android.systemui.scene.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.shared.model.SceneContainerConfig
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+
+/** A root view of the main SysUI window that supports scenes. */
+class SceneWindowRootView(
+    context: Context,
+    attrs: AttributeSet?,
+) :
+    WindowRootView(
+        context,
+        attrs,
+    ) {
+
+    private lateinit var viewModel: SceneContainerViewModel
+
+    fun init(
+        viewModel: SceneContainerViewModel,
+        containerConfig: SceneContainerConfig,
+        scenes: Set<Scene>,
+        layoutInsetController: LayoutInsetsController,
+    ) {
+        this.viewModel = viewModel
+        setLayoutInsetsController(layoutInsetController)
+        SceneWindowRootViewBinder.bind(
+            view = this@SceneWindowRootView,
+            viewModel = viewModel,
+            containerConfig = containerConfig,
+            scenes = scenes,
+            onVisibilityChangedInternal = { isVisible ->
+                super.setVisibility(if (isVisible) View.VISIBLE else View.INVISIBLE)
+            }
+        )
+    }
+
+    override fun setVisibility(visibility: Int) {
+        // Do nothing. We don't want external callers to invoke this. Instead, we drive our own
+        // visibility from our view-binder.
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
new file mode 100644
index 0000000..f9324a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.view
+
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.shared.model.SceneContainerConfig
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import java.time.Instant
+import kotlinx.coroutines.launch
+
+object SceneWindowRootViewBinder {
+
+    /** Binds between the view and view-model pertaining to a specific scene container. */
+    fun bind(
+        view: ViewGroup,
+        viewModel: SceneContainerViewModel,
+        containerConfig: SceneContainerConfig,
+        scenes: Set<Scene>,
+        onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
+    ) {
+        val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key }
+        val sortedSceneByKey: Map<SceneKey, Scene> = buildMap {
+            containerConfig.sceneKeys.forEach { sceneKey ->
+                val scene =
+                    checkNotNull(unsortedSceneByKey[sceneKey]) {
+                        "Scene not found for key \"$sceneKey\"!"
+                    }
+
+                put(sceneKey, scene)
+            }
+        }
+
+        view.repeatWhenAttached {
+            lifecycleScope.launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    view.setViewTreeOnBackPressedDispatcherOwner(
+                        object : OnBackPressedDispatcherOwner {
+                            override val onBackPressedDispatcher =
+                                OnBackPressedDispatcher().apply {
+                                    setOnBackInvokedDispatcher(
+                                        view.viewRootImpl.onBackInvokedDispatcher
+                                    )
+                                }
+
+                            override val lifecycle: Lifecycle = this@repeatWhenAttached.lifecycle
+                        }
+                    )
+
+                    view.addView(
+                        ComposeFacade.createSceneContainerView(
+                            context = view.context,
+                            viewModel = viewModel,
+                            sceneByKey = sortedSceneByKey,
+                        )
+                    )
+
+                    val legacyView = view.requireViewById<View>(R.id.legacy_window_root)
+                    view.addView(createVisibilityToggleView(legacyView))
+
+                    launch {
+                        viewModel.isVisible.collect { isVisible ->
+                            onVisibilityChangedInternal(isVisible)
+                        }
+                    }
+                }
+
+                // Here when destroyed.
+                view.removeAllViews()
+            }
+        }
+    }
+
+    private var clickCount = 0
+    private var lastClick = Instant.now()
+
+    /**
+     * A temporary UI to toggle on/off the visibility of the given [otherView]. It is toggled by
+     * tapping 5 times in quick succession on the device camera (top center).
+     */
+    // TODO(b/291321285): Remove this when the Flexiglass UI is mature enough to turn off legacy
+    //  SysUI altogether.
+    private fun createVisibilityToggleView(otherView: View): View {
+        val toggleView = View(otherView.context)
+        toggleView.layoutParams = FrameLayout.LayoutParams(200, 200, Gravity.CENTER_HORIZONTAL)
+        toggleView.setOnClickListener {
+            val now = Instant.now()
+            clickCount = if (now.minusSeconds(2) > lastClick) 1 else clickCount + 1
+            if (clickCount == 5) {
+                otherView.isVisible = !otherView.isVisible
+                clickCount = 0
+            }
+            lastClick = now
+        }
+        return toggleView
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
new file mode 100644
index 0000000..4afe679
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -0,0 +1,164 @@
+package com.android.systemui.scene.ui.view
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.util.Pair
+import android.view.DisplayCutout
+import android.view.View
+import android.view.WindowInsets
+import android.widget.FrameLayout
+import androidx.core.view.updateMargins
+import com.android.systemui.R
+import com.android.systemui.compose.ComposeFacade
+
+/**
+ * A view that can serve as the root of the main SysUI window (but might not, see below for more
+ * information regarding this confusing comment).
+ *
+ * Naturally, only one view may ever be at the root of a view hierarchy tree. Under certain
+ * conditions, the view hierarchy tree in the scene-containing window view may actually have one
+ * [WindowRootView] acting as the true root view and another [WindowRootView] which doesn't and is,
+ * instead, a child of the true root view. To discern which one is which, please use the [isRoot]
+ * method.
+ */
+open class WindowRootView(
+    context: Context,
+    attrs: AttributeSet?,
+) :
+    FrameLayout(
+        context,
+        attrs,
+    ) {
+
+    private lateinit var layoutInsetsController: LayoutInsetsController
+    private var leftInset = 0
+    private var rightInset = 0
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+
+        if (ComposeFacade.isComposeAvailable() && isRoot()) {
+            ComposeFacade.composeInitializer().onAttachedToWindow(this)
+        }
+    }
+
+    override fun onDetachedFromWindow() {
+        super.onDetachedFromWindow()
+
+        if (ComposeFacade.isComposeAvailable() && isRoot()) {
+            ComposeFacade.composeInitializer().onDetachedFromWindow(this)
+        }
+    }
+
+    override fun generateLayoutParams(attrs: AttributeSet?): FrameLayout.LayoutParams? {
+        return LayoutParams(context, attrs)
+    }
+
+    override fun generateDefaultLayoutParams(): FrameLayout.LayoutParams? {
+        return LayoutParams(
+            FrameLayout.LayoutParams.MATCH_PARENT,
+            FrameLayout.LayoutParams.MATCH_PARENT
+        )
+    }
+
+    override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
+        val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
+        if (fitsSystemWindows) {
+            val paddingChanged = insets.top != paddingTop || insets.bottom != paddingBottom
+
+            // Drop top inset, and pass through bottom inset.
+            if (paddingChanged) {
+                setPadding(0, 0, 0, 0)
+            }
+        } else {
+            val changed =
+                paddingLeft != 0 || paddingRight != 0 || paddingTop != 0 || paddingBottom != 0
+            if (changed) {
+                setPadding(0, 0, 0, 0)
+            }
+        }
+        leftInset = 0
+        rightInset = 0
+
+        val displayCutout = rootWindowInsets.displayCutout
+        val pairInsets: Pair<Int, Int> =
+            layoutInsetsController.getinsets(windowInsets, displayCutout)
+        leftInset = pairInsets.first
+        rightInset = pairInsets.second
+        applyMargins()
+        return windowInsets
+    }
+
+    fun setLayoutInsetsController(layoutInsetsController: LayoutInsetsController) {
+        this.layoutInsetsController = layoutInsetsController
+    }
+
+    private fun applyMargins() {
+        val count = childCount
+        for (i in 0 until count) {
+            val child = getChildAt(i)
+            if (child.layoutParams is LayoutParams) {
+                val layoutParams = child.layoutParams as LayoutParams
+                if (
+                    !layoutParams.ignoreRightInset &&
+                        (layoutParams.rightMargin != rightInset ||
+                            layoutParams.leftMargin != leftInset)
+                ) {
+                    layoutParams.updateMargins(left = leftInset, right = rightInset)
+                    child.requestLayout()
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns `true` if this view is the true root of the view-hierarchy; `false` otherwise.
+     *
+     * Please see the class-level documentation to understand why this is possible.
+     */
+    private fun isRoot(): Boolean {
+        // TODO(b/283300105): remove this check once there's only one subclass of WindowRootView.
+        return parent.let { it !is View || it.id == android.R.id.content }
+    }
+
+    /** Controller responsible for calculating insets for the shade window. */
+    interface LayoutInsetsController {
+
+        /** Update the insets and calculate them accordingly. */
+        fun getinsets(
+            windowInsets: WindowInsets?,
+            displayCutout: DisplayCutout?,
+        ): Pair<Int, Int>
+    }
+
+    private class LayoutParams : FrameLayout.LayoutParams {
+        var ignoreRightInset = false
+
+        constructor(
+            width: Int,
+            height: Int,
+        ) : super(
+            width,
+            height,
+        )
+
+        @SuppressLint("CustomViewStyleable")
+        constructor(
+            context: Context,
+            attrs: AttributeSet?,
+        ) : super(
+            context,
+            attrs,
+        ) {
+            val obtainedAttributes =
+                context.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout)
+            ignoreRightInset =
+                obtainedAttributes.getBoolean(
+                    R.styleable.StatusBarWindowView_Layout_ignoreRightInset,
+                    false
+                )
+            obtainedAttributes.recycle()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 8c1ad9b..5c16fb5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -16,15 +16,21 @@
 
 package com.android.systemui.scene.ui.viewmodel
 
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
-/** Models UI state for a single scene container. */
-class SceneContainerViewModel(
+/** Models UI state for the scene container. */
+@SysUISingleton
+class SceneContainerViewModel
+@Inject
+constructor(
     private val interactor: SceneInteractor,
-    val containerName: String,
 ) {
     /**
      * Keys of all scenes in the container.
@@ -32,21 +38,32 @@
      * The scenes will be sorted in z-order such that the last one is the one that should be
      * rendered on top of all previous ones.
      */
-    val allSceneKeys: List<SceneKey> = interactor.allSceneKeys(containerName)
+    val allSceneKeys: List<SceneKey> = interactor.allSceneKeys()
 
-    /** The current scene. */
-    val currentScene: StateFlow<SceneModel> = interactor.currentScene(containerName)
+    /** The scene that should be rendered. */
+    val currentScene: StateFlow<SceneModel> = interactor.desiredScene
 
     /** Whether the container is visible. */
-    val isVisible: StateFlow<Boolean> = interactor.isVisible(containerName)
+    val isVisible: StateFlow<Boolean> = interactor.isVisible
 
-    /** Requests a transition to the scene with the given key. */
-    fun setCurrentScene(scene: SceneModel) {
-        interactor.setCurrentScene(containerName, scene)
+    /** Notifies that the UI has transitioned sufficiently to the given scene. */
+    fun onSceneChanged(scene: SceneModel) {
+        interactor.onSceneChanged(
+            scene = scene,
+            loggingReason = SCENE_TRANSITION_LOGGING_REASON,
+        )
     }
 
-    /** Notifies of the progress of a scene transition. */
-    fun setSceneTransitionProgress(progress: Float) {
-        interactor.setSceneTransitionProgress(containerName, progress)
+    /**
+     * Binds the given flow so the system remembers it.
+     *
+     * Note that you must call is with `null` when the UI is done or risk a memory leak.
+     */
+    fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
+        interactor.setTransitionState(transitionState)
+    }
+
+    companion object {
+        private const val SCENE_TRANSITION_LOGGING_REASON = "user input"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt
deleted file mode 100644
index 100f427..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.ui.viewmodel
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneContainerNames
-import dagger.Module
-import dagger.Provides
-import javax.inject.Named
-
-@Module
-object SceneContainerViewModelModule {
-
-    @Provides
-    @SysUISingleton
-    @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
-    fun defaultSceneContainerViewModel(
-        interactor: SceneInteractor,
-    ): SceneContainerViewModel {
-        return SceneContainerViewModel(
-            interactor = interactor,
-            containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
-        )
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
index b340043..23894a3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
@@ -50,22 +50,20 @@
 
     public override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        window.apply {
-            addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
-            setGravity(Gravity.CENTER)
-        }
+        window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+        window?.setGravity(Gravity.CENTER)
         setContentView(R.layout.screen_share_dialog)
-        dialogTitle = findViewById(R.id.screen_share_dialog_title)
-        warning = findViewById(R.id.text_warning)
-        startButton = findViewById(android.R.id.button1)
-        cancelButton = findViewById(android.R.id.button2)
+        dialogTitle = requireViewById(R.id.screen_share_dialog_title)
+        warning = requireViewById(R.id.text_warning)
+        startButton = requireViewById(android.R.id.button1)
+        cancelButton = requireViewById(android.R.id.button2)
         updateIcon()
         initScreenShareOptions()
         createOptionsView(getOptionsViewLayoutId())
     }
 
     private fun updateIcon() {
-        val icon = findViewById<ImageView>(R.id.screen_share_dialog_icon)
+        val icon = requireViewById<ImageView>(R.id.screen_share_dialog_icon)
         if (dialogIconTint != null) {
             icon.setColorFilter(context.getColor(dialogIconTint))
         }
@@ -92,7 +90,7 @@
                 options
             )
         adapter.setDropDownViewResource(R.layout.screen_share_dialog_spinner_item_text)
-        screenShareModeSpinner = findViewById(R.id.screen_share_mode_spinner)
+        screenShareModeSpinner = requireViewById(R.id.screen_share_mode_spinner)
         screenShareModeSpinner.adapter = adapter
         screenShareModeSpinner.onItemSelectedListener = this
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index 604d449..e8683fb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -100,11 +100,11 @@
     @LayoutRes override fun getOptionsViewLayoutId(): Int = R.layout.screen_record_options
 
     private fun initRecordOptionsView() {
-        audioSwitch = findViewById(R.id.screenrecord_audio_switch)
-        tapsSwitch = findViewById(R.id.screenrecord_taps_switch)
-        tapsView = findViewById(R.id.show_taps)
+        audioSwitch = requireViewById(R.id.screenrecord_audio_switch)
+        tapsSwitch = requireViewById(R.id.screenrecord_taps_switch)
+        tapsView = requireViewById(R.id.show_taps)
         updateTapsViewVisibility()
-        options = findViewById(R.id.screen_recording_options)
+        options = requireViewById(R.id.screen_recording_options)
         val a: ArrayAdapter<*> =
             ScreenRecordingAdapter(context, android.R.layout.simple_spinner_dropdown_item, MODES)
         a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index a8f99be..ab2a8d9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -19,6 +19,7 @@
 import android.content.ClipData
 import android.content.ClipDescription
 import android.content.ComponentName
+import android.content.ContentProvider
 import android.content.Context
 import android.content.Intent
 import android.net.Uri
@@ -26,21 +27,19 @@
 
 object ActionIntentCreator {
     /** @return a chooser intent to share the given URI. */
-    fun createShareIntent(uri: Uri) = createShareIntent(uri, null, null)
+    fun createShare(uri: Uri): Intent = createShare(uri, subject = null, text = null)
 
     /** @return a chooser intent to share the given URI with the optional provided subject. */
-    fun createShareIntentWithSubject(uri: Uri, subject: String?) =
-        createShareIntent(uri, subject = subject)
+    fun createShareWithSubject(uri: Uri, subject: String): Intent =
+        createShare(uri, subject = subject)
 
     /** @return a chooser intent to share the given URI with the optional provided extra text. */
-    fun createShareIntentWithExtraText(uri: Uri, extraText: String?) =
-        createShareIntent(uri, extraText = extraText)
+    fun createShareWithText(uri: Uri, extraText: String): Intent =
+        createShare(uri, text = extraText)
 
-    private fun createShareIntent(
-        uri: Uri,
-        subject: String? = null,
-        extraText: String? = null
-    ): Intent {
+    private fun createShare(rawUri: Uri, subject: String? = null, text: String? = null): Intent {
+        val uri = uriWithoutUserId(rawUri)
+
         // Create a share intent, this will always go through the chooser activity first
         // which should not trigger auto-enter PiP
         val sharingIntent =
@@ -56,8 +55,8 @@
                         ClipData.Item(uri)
                     )
 
-                putExtra(Intent.EXTRA_SUBJECT, subject)
-                putExtra(Intent.EXTRA_TEXT, extraText)
+                subject?.let { putExtra(Intent.EXTRA_SUBJECT, subject) }
+                text?.let { putExtra(Intent.EXTRA_TEXT, text) }
                 addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                 addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
             }
@@ -72,20 +71,33 @@
      * @return an ACTION_EDIT intent for the given URI, directed to config_screenshotEditor if
      *   available.
      */
-    fun createEditIntent(uri: Uri, context: Context): Intent {
+    fun createEdit(rawUri: Uri, context: Context): Intent {
+        val uri = uriWithoutUserId(rawUri)
         val editIntent = Intent(Intent.ACTION_EDIT)
 
-        context.getString(R.string.config_screenshotEditor)?.let {
-            if (it.isNotEmpty()) {
-                editIntent.component = ComponentName.unflattenFromString(it)
-            }
+        val editor = context.getString(R.string.config_screenshotEditor)
+        if (editor.isNotEmpty()) {
+            editIntent.component = ComponentName.unflattenFromString(editor)
         }
 
         return editIntent
             .setDataAndType(uri, "image/png")
+            .putExtra(EXTRA_EDIT_SOURCE, EDIT_SOURCE_SCREENSHOT)
             .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)
     }
+
+    private const val EXTRA_EDIT_SOURCE = "edit_source"
+    private const val EDIT_SOURCE_SCREENSHOT = "screenshot"
+}
+
+/**
+ * URIs here are passed only via Intent which are sent to the target user via Intent. Because of
+ * this, the userId component can be removed to prevent compatibility issues when an app attempts
+ * valid a URI containing a userId within the authority.
+ */
+private fun uriWithoutUserId(uri: Uri): Uri {
+    return ContentProvider.getUriWithoutUserId(uri)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 187019a..aa6bfc3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.content.Intent
 import android.os.Bundle
+import android.os.Process.myUserHandle
 import android.os.RemoteException
 import android.os.UserHandle
 import android.util.Log
@@ -44,10 +45,10 @@
 class ActionIntentExecutor
 @Inject
 constructor(
+    private val context: Context,
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
-    private val context: Context,
-    private val displayTracker: DisplayTracker
+    private val displayTracker: DisplayTracker,
 ) {
     /**
      * Execute the given intent with startActivity while performing operations for screenshot action
@@ -58,31 +59,31 @@
      */
     fun launchIntentAsync(
         intent: Intent,
-        bundle: Bundle,
-        userId: Int,
+        options: Bundle?,
+        user: UserHandle,
         overrideTransition: Boolean,
     ) {
-        applicationScope.launch { launchIntent(intent, bundle, userId, overrideTransition) }
+        applicationScope.launch { launchIntent(intent, options, user, overrideTransition) }
     }
 
     suspend fun launchIntent(
         intent: Intent,
-        bundle: Bundle,
-        userId: Int,
+        options: Bundle?,
+        user: UserHandle,
         overrideTransition: Boolean,
     ) {
         dismissKeyguard()
 
-        if (userId == UserHandle.myUserId()) {
-            withContext(mainDispatcher) { context.startActivity(intent, bundle) }
+        if (user == myUserHandle()) {
+            withContext(mainDispatcher) { context.startActivity(intent, options) }
         } else {
-            launchCrossProfileIntent(userId, intent, bundle)
+            launchCrossProfileIntent(user, intent, options)
         }
 
         if (overrideTransition) {
             val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
             try {
-                WindowManagerGlobal.getWindowManagerService()
+                checkNotNull(WindowManagerGlobal.getWindowManagerService())
                     .overridePendingAppTransitionRemote(runner, displayTracker.defaultDisplayId)
             } catch (e: Exception) {
                 Log.e(TAG, "Error overriding screenshot app transition", e)
@@ -111,17 +112,21 @@
         completion.await()
     }
 
-    private fun getCrossProfileConnector(userId: Int): ServiceConnector<ICrossProfileService> =
+    private fun getCrossProfileConnector(user: UserHandle): 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,
+            user.identifier,
             ICrossProfileService.Stub::asInterface,
         )
 
-    private suspend fun launchCrossProfileIntent(userId: Int, intent: Intent, bundle: Bundle) {
-        val connector = getCrossProfileConnector(userId)
+    private suspend fun launchCrossProfileIntent(
+        user: UserHandle,
+        intent: Intent,
+        bundle: Bundle?
+    ) {
+        val connector = getCrossProfileConnector(user)
         val completion = CompletableDeferred<Unit>()
         connector.post {
             it.launchIntent(intent, bundle)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
index 3aefcb3..7e234ae 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
@@ -71,6 +71,8 @@
             ActivityOptions opts = ActivityOptions.makeBasic();
             opts.setDisallowEnterPictureInPictureWhileLaunching(
                     intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
+            opts.setPendingIntentBackgroundActivityStartMode(
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
             try {
                 actionIntent.send(context, 0, null, null, null, null, opts.toBundle());
                 if (intent.getBooleanExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, false)) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index a9cecaa..6f2256e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -117,18 +117,22 @@
 
     @Override
     protected Parcelable onSaveInstanceState() {
+        Log.d(TAG, "onSaveInstanceState");
         Parcelable superState = super.onSaveInstanceState();
 
         SavedState ss = new SavedState(superState);
         ss.mCrop = mCrop;
+        Log.d(TAG, "saving mCrop=" + mCrop);
+
         return ss;
     }
 
     @Override
     protected void onRestoreInstanceState(Parcelable state) {
+        Log.d(TAG, "onRestoreInstanceState");
         SavedState ss = (SavedState) state;
         super.onRestoreInstanceState(ss.getSuperState());
-
+        Log.d(TAG, "restoring mCrop=" + ss.mCrop + " (was " + mCrop + ")");
         mCrop = ss.mCrop;
     }
 
@@ -242,6 +246,7 @@
      * Set the given boundary to the given value without animation.
      */
     public void setBoundaryPosition(CropBoundary boundary, float position) {
+        Log.i(TAG, "setBoundaryPosition: " + boundary + ", position=" + position);
         position = (float) getAllowedValues(boundary).clamp(position);
         switch (boundary) {
             case TOP:
@@ -260,6 +265,7 @@
                 Log.w(TAG, "No boundary selected");
                 break;
         }
+        Log.i(TAG,  "Updated mCrop: " + mCrop);
 
         invalidate();
     }
@@ -350,26 +356,31 @@
         mCropInteractionListener = listener;
     }
 
-    private Range getAllowedValues(CropBoundary boundary) {
+    private Range<Float> getAllowedValues(CropBoundary boundary) {
+        float upper = 0f;
+        float lower = 1f;
         switch (boundary) {
             case TOP:
-                return new Range<>(0f,
-                        mCrop.bottom - pixelDistanceToFraction(mCropTouchMargin,
-                                CropBoundary.BOTTOM));
+                lower = 0f;
+                upper = mCrop.bottom - pixelDistanceToFraction(mCropTouchMargin,
+                        CropBoundary.BOTTOM);
+                break;
             case BOTTOM:
-                return new Range<>(
-                        mCrop.top + pixelDistanceToFraction(mCropTouchMargin,
-                                CropBoundary.TOP), 1f);
+                lower = mCrop.top + pixelDistanceToFraction(mCropTouchMargin, CropBoundary.TOP);
+                upper = 1;
+                break;
             case LEFT:
-                return new Range<>(0f,
-                        mCrop.right - pixelDistanceToFraction(mCropTouchMargin,
-                                CropBoundary.RIGHT));
+                lower = 0f;
+                upper = mCrop.right - pixelDistanceToFraction(mCropTouchMargin, CropBoundary.RIGHT);
+                break;
             case RIGHT:
-                return new Range<>(
-                        mCrop.left + pixelDistanceToFraction(mCropTouchMargin,
-                                CropBoundary.LEFT), 1f);
+                lower = mCrop.left + pixelDistanceToFraction(mCropTouchMargin, CropBoundary.LEFT);
+                upper = 1;
+                break;
         }
-        return null;
+        Log.i(TAG, "getAllowedValues: " + boundary + ", "
+                + "result=[lower=" + lower + ", upper=" + upper + "]");
+        return new Range<>(lower, upper);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
index 0b4b7c6..bdbc470 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
@@ -43,6 +43,7 @@
  */
 public class DraggableConstraintLayout extends ConstraintLayout
         implements ViewTreeObserver.OnComputeInternalInsetsListener {
+    public static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
 
     private static final float VELOCITY_DP_PER_MS = 1;
     private static final int MAXIMUM_DISMISS_DISTANCE_DP = 400;
@@ -179,8 +180,13 @@
         Region r = new Region();
         Rect rect = new Rect();
         for (int i = 0; i < getChildCount(); i++) {
-            getChildAt(i).getGlobalVisibleRect(rect);
-            r.op(rect, Region.Op.UNION);
+            View child = getChildAt(i);
+            if (child.getVisibility() == View.VISIBLE) {
+                child.getGlobalVisibleRect(rect);
+                rect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+                        (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+                r.op(rect, Region.Op.UNION);
+            }
         }
         inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
         inoutInfo.touchableRegion.set(r);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 4bc7ec8..53dbe76 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -36,17 +36,18 @@
 import android.util.Log;
 import android.view.ScrollCaptureResponse;
 import android.view.View;
-import android.view.ViewTreeObserver;
 import android.widget.ImageView;
 
 import androidx.constraintlayout.widget.ConstraintLayout;
 
 import com.android.internal.app.ChooserActivity;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.view.OneShotPreDrawListener;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.screenshot.CropView.CropBoundary;
 import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot;
 import com.android.systemui.settings.UserTracker;
 
@@ -215,6 +216,7 @@
         mPreview.setImageDrawable(drawable);
         mMagnifierView.setDrawable(mLongScreenshot.getDrawable(),
                 mLongScreenshot.getWidth(), mLongScreenshot.getHeight());
+        Log.i(TAG, "Completed: " + longScreenshot);
         // Original boundaries go from the image tile set's y=0 to y=pageSize, so
         // we animate to that as a starting crop position.
         float topFraction = Math.max(0,
@@ -223,31 +225,26 @@
                 1 - (mLongScreenshot.getBottom() - mLongScreenshot.getPageHeight())
                         / (float) mLongScreenshot.getHeight());
 
+        Log.i(TAG, "topFraction: " + topFraction);
+        Log.i(TAG, "bottomFraction: " + bottomFraction);
+
         mEnterTransitionView.setImageDrawable(drawable);
-        mEnterTransitionView.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        mEnterTransitionView.getViewTreeObserver().removeOnPreDrawListener(this);
-                        updateImageDimensions();
-                        mEnterTransitionView.post(() -> {
-                            Rect dest = new Rect();
-                            mEnterTransitionView.getBoundsOnScreen(dest);
-                            mLongScreenshotHolder.takeTransitionDestinationCallback()
-                                    .setTransitionDestination(dest, () -> {
-                                        mPreview.animate().alpha(1f);
-                                        mCropView.setBoundaryPosition(
-                                                CropView.CropBoundary.TOP, topFraction);
-                                        mCropView.setBoundaryPosition(
-                                                CropView.CropBoundary.BOTTOM, bottomFraction);
-                                        mCropView.animateEntrance();
-                                        mCropView.setVisibility(View.VISIBLE);
-                                        setButtonsEnabled(true);
-                                    });
+        OneShotPreDrawListener.add(mEnterTransitionView, () -> {
+            updateImageDimensions();
+            mEnterTransitionView.post(() -> {
+                Rect dest = new Rect();
+                mEnterTransitionView.getBoundsOnScreen(dest);
+                mLongScreenshotHolder.takeTransitionDestinationCallback()
+                        .setTransitionDestination(dest, () -> {
+                            mPreview.animate().alpha(1f);
+                            mCropView.setBoundaryPosition(CropBoundary.TOP, topFraction);
+                            mCropView.setBoundaryPosition(CropBoundary.BOTTOM, bottomFraction);
+                            mCropView.animateEntrance();
+                            mCropView.setVisibility(View.VISIBLE);
+                            setButtonsEnabled(true);
                         });
-                        return true;
-                    }
-                });
+            });
+        });
 
         // Immediately export to temp image file for saved state
         mCacheSaveFuture = mImageExporter.exportToRawFile(mBackgroundExecutor,
@@ -337,9 +334,9 @@
         if (mScreenshotUserHandle != Process.myUserHandle()) {
             // TODO: Fix transition for work profile. Omitting it in the meantime.
             mActionExecutor.launchIntentAsync(
-                    ActionIntentCreator.INSTANCE.createEditIntent(uri, this),
+                    ActionIntentCreator.INSTANCE.createEdit(uri, this),
                     null,
-                    mScreenshotUserHandle.getIdentifier(), false);
+                    mScreenshotUserHandle, false);
         } else {
             String editorPackage = getString(R.string.config_screenshotEditor);
             Intent intent = new Intent(Intent.ACTION_EDIT);
@@ -365,9 +362,8 @@
     }
 
     private void doShare(Uri uri) {
-        Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri);
-        mActionExecutor.launchIntentAsync(shareIntent, null,
-                mScreenshotUserHandle.getIdentifier(), false);
+        Intent shareIntent = ActionIntentCreator.INSTANCE.createShare(uri);
+        mActionExecutor.launchIntentAsync(shareIntent, null, mScreenshotUserHandle, false);
     }
 
     private void onClicked(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
index 13678b0..9e8ea3a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
@@ -99,6 +99,8 @@
             try {
                 BroadcastOptions options = BroadcastOptions.makeBasic();
                 options.setInteractive(true);
+                options.setPendingIntentBackgroundActivityStartMode(
+                        BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
                 intent.send(options.toBundle());
                 finisher.run();
             } catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 093c09f..03e1e15 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -68,7 +68,6 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.ScrollCaptureResponse;
-import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
@@ -123,7 +122,6 @@
     public static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
     private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
     private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
-    private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
 
     private final Resources mResources;
     private final Interpolator mFastOutSlowIn;
@@ -284,17 +282,22 @@
         Region swipeRegion = new Region();
 
         final Rect tmpRect = new Rect();
+        int swipePadding = (int) FloatingWindowUtil.dpToPx(
+                mDisplayMetrics, DraggableConstraintLayout.SWIPE_PADDING_DP * -1);
         mScreenshotPreview.getBoundsOnScreen(tmpRect);
-        tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
-                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+        tmpRect.inset(swipePadding, swipePadding);
         swipeRegion.op(tmpRect, Region.Op.UNION);
         mActionsContainerBackground.getBoundsOnScreen(tmpRect);
-        tmpRect.inset((int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
-                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+        tmpRect.inset(swipePadding, swipePadding);
         swipeRegion.op(tmpRect, Region.Op.UNION);
         mDismissButton.getBoundsOnScreen(tmpRect);
         swipeRegion.op(tmpRect, Region.Op.UNION);
 
+        View messageContainer = findViewById(R.id.screenshot_message_container);
+        if (messageContainer != null) {
+            messageContainer.getBoundsOnScreen(tmpRect);
+            swipeRegion.op(tmpRect, Region.Op.UNION);
+        }
         View messageDismiss = findViewById(R.id.message_dismiss_button);
         if (messageDismiss != null) {
             messageDismiss.getBoundsOnScreen(tmpRect);
@@ -378,16 +381,6 @@
         mEditChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_edit_chip));
         mScrollChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_scroll_chip));
 
-        int swipePaddingPx = (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, SWIPE_PADDING_DP);
-        TouchDelegate previewDelegate = new TouchDelegate(
-                new Rect(swipePaddingPx, swipePaddingPx, swipePaddingPx, swipePaddingPx),
-                mScreenshotPreview);
-        mScreenshotPreview.setTouchDelegate(previewDelegate);
-        TouchDelegate actionsDelegate = new TouchDelegate(
-                new Rect(swipePaddingPx, swipePaddingPx, swipePaddingPx, swipePaddingPx),
-                mActionsContainerBackground);
-        mActionsContainerBackground.setTouchDelegate(actionsDelegate);
-
         setFocusable(true);
         mActionsContainer.setScrollX(0);
 
@@ -801,31 +794,31 @@
             Intent shareIntent;
             if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && mScreenshotData != null
                     && mScreenshotData.getContextUrl() != null) {
-                shareIntent = ActionIntentCreator.INSTANCE.createShareIntentWithExtraText(
+                shareIntent = ActionIntentCreator.INSTANCE.createShareWithText(
                         imageData.uri, mScreenshotData.getContextUrl().toString());
             } else {
-                shareIntent = ActionIntentCreator.INSTANCE.createShareIntentWithSubject(
+                shareIntent = ActionIntentCreator.INSTANCE.createShareWithSubject(
                         imageData.uri, imageData.subject);
             }
             mActionExecutor.launchIntentAsync(shareIntent,
                     imageData.shareTransition.get().bundle,
-                    imageData.owner.getIdentifier(), false);
+                    imageData.owner, false);
         });
         mEditChip.setOnClickListener(v -> {
             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName);
             prepareSharedTransition();
             mActionExecutor.launchIntentAsync(
-                    ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext),
+                    ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
                     imageData.editTransition.get().bundle,
-                    imageData.owner.getIdentifier(), true);
+                    imageData.owner, true);
         });
         mScreenshotPreview.setOnClickListener(v -> {
             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName);
             prepareSharedTransition();
             mActionExecutor.launchIntentAsync(
-                    ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext),
+                    ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
                     imageData.editTransition.get().bundle,
-                    imageData.owner.getIdentifier(), true);
+                    imageData.owner, true);
         });
         if (mQuickShareChip != null) {
             if (imageData.quickShareAction != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 30a0b8f..bb34ede 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -130,8 +130,14 @@
 
         @Override
         public String toString() {
-            return "LongScreenshot{w=" + mImageTileSet.getWidth()
-                    + ", h=" + mImageTileSet.getHeight() + "}";
+            return "LongScreenshot{"
+                    + "l=" + mImageTileSet.getLeft() + ", "
+                    + "t=" + mImageTileSet.getTop() + ", "
+                    + "r=" + mImageTileSet.getRight() + ", "
+                    + "b=" + mImageTileSet.getBottom() + ", "
+                    + "w=" + mImageTileSet.getWidth() + ", "
+                    + "h=" + mImageTileSet.getHeight()
+                    + "}";
         }
 
         public Drawable getDrawable() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
index 9761f59..ef58b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
@@ -55,7 +55,8 @@
             Log.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent());
         }
         ActivityOptions opts = ActivityOptions.makeBasic();
-
+        opts.setPendingIntentBackgroundActivityStartMode(
+                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
         try {
             pendingIntent.send(context, 0, fillIn, null, null, null, opts.toBundle());
         } catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
index afc8bff..7de22b1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
@@ -19,7 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
-import android.os.UserManager;
+import android.os.UserHandle;
 
 import androidx.annotation.Nullable;
 
@@ -39,15 +39,14 @@
     private final DisplayTracker mDisplayTracker;
 
     @Inject
-    AppClipsCrossProcessHelper(@Application Context context, UserManager userManager,
-            DisplayTracker displayTracker) {
+    AppClipsCrossProcessHelper(@Application Context context, DisplayTracker displayTracker) {
         // Start a service as main user so that even if the app clips activity is running as work
         // profile user the service is able to use correct instance of Bubbles to grab a screenshot
         // excluding the bubble layer.
         mProxyConnector = new ServiceConnector.Impl<>(context,
                 new Intent(context, AppClipsScreenshotHelperService.class),
                 Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
-                        | Context.BIND_NOT_VISIBLE, userManager.getMainUser().getIdentifier(),
+                        | Context.BIND_NOT_VISIBLE, UserHandle.USER_SYSTEM,
                 IAppClipsScreenshotHelperService.Stub::asInterface);
         mDisplayTracker = displayTracker;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
index 4c01315..4cc2f62 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
@@ -33,6 +33,11 @@
 /**
  * A helper service that runs in SysUI process and helps {@link AppClipsActivity} which runs in its
  * own separate process take a screenshot.
+ *
+ * <p>Note: This service always runs in the SysUI process running on the system user irrespective of
+ * which user started the service. This is required so that the correct instance of {@link Bubbles}
+ * instance is injected. This is set via attribute {@code android:singleUser=”true”} in
+ * AndroidManifest.
  */
 public class AppClipsScreenshotHelperService extends Service {
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
index 3949492..dce8c81 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
@@ -16,6 +16,11 @@
 
 package com.android.systemui.screenshot.appclips;
 
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
+
 import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
 
 import android.app.Activity;
@@ -25,17 +30,12 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.Intent.CaptureContentForNoteStatusCodes;
 import android.content.res.Resources;
 import android.os.IBinder;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
 
 import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
 
-import com.android.internal.infra.AndroidFuture;
-import com.android.internal.infra.ServiceConnector;
 import com.android.internal.statusbar.IAppClipsService;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Application;
@@ -43,73 +43,36 @@
 import com.android.wm.shell.bubbles.Bubbles;
 
 import java.util.Optional;
-import java.util.concurrent.ExecutionException;
 
 import javax.inject.Inject;
 
 /**
  * A service that communicates with {@link StatusBarManager} to support the
- * {@link StatusBarManager#canLaunchCaptureContentActivityForNote(Activity)} API.
+ * {@link StatusBarManager#canLaunchCaptureContentActivityForNote(Activity)} API. Also used by
+ * {@link AppClipsTrampolineActivity} to query if an app should be allowed to user App Clips.
+ *
+ * <p>Note: This service always runs in the SysUI process running on the system user irrespective of
+ * which user started the service. This is required so that the correct instance of {@link Bubbles}
+ * instance is injected. This is set via attribute {@code android:singleUser=”true”} in
+ * AndroidManifest.
  */
 public class AppClipsService extends Service {
 
-    private static final String TAG = AppClipsService.class.getSimpleName();
-
     @Application private final Context mContext;
     private final FeatureFlags mFeatureFlags;
     private final Optional<Bubbles> mOptionalBubbles;
     private final DevicePolicyManager mDevicePolicyManager;
-    private final UserManager mUserManager;
-
     private final boolean mAreTaskAndTimeIndependentPrerequisitesMet;
 
-    @VisibleForTesting()
-    @Nullable ServiceConnector<IAppClipsService> mProxyConnectorToMainProfile;
-
     @Inject
     public AppClipsService(@Application Context context, FeatureFlags featureFlags,
-            Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager,
-            UserManager userManager) {
+            Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager) {
         mContext = context;
         mFeatureFlags = featureFlags;
         mOptionalBubbles = optionalBubbles;
         mDevicePolicyManager = devicePolicyManager;
-        mUserManager = userManager;
-
-        // The consumer of this service are apps that call through StatusBarManager API to query if
-        // it can use app clips API. Since these apps can be launched as work profile users, this
-        // service will start as work profile user. SysUI doesn't share injected instances for
-        // different users. This is why the bubbles instance injected will be incorrect. As the apps
-        // don't generally have permission to connect to a service running as different user, we
-        // start a proxy connection to communicate with the main user's version of this service.
-        if (mUserManager.isManagedProfile()) {
-            // No need to check for prerequisites in this case as those are incorrect for work
-            // profile user instance of the service and the main user version of the service will
-            // take care of this check.
-            mAreTaskAndTimeIndependentPrerequisitesMet = false;
-
-            // Get the main user so that we can connect to the main user's version of the service.
-            UserHandle mainUser = mUserManager.getMainUser();
-            if (mainUser == null) {
-                // If main user is not available there isn't much we can do, no apps can use app
-                // clips.
-                return;
-            }
-
-            // Set up the connection to be used later during onBind callback.
-            mProxyConnectorToMainProfile =
-                    new ServiceConnector.Impl<>(
-                            context,
-                            new Intent(context, AppClipsService.class),
-                            Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
-                                    | Context.BIND_NOT_VISIBLE,
-                            mainUser.getIdentifier(),
-                            IAppClipsService.Stub::asInterface);
-            return;
-        }
 
         mAreTaskAndTimeIndependentPrerequisitesMet = checkIndependentVariables();
-        mProxyConnectorToMainProfile = null;
     }
 
     private boolean checkIndependentVariables() {
@@ -144,40 +107,25 @@
         return new IAppClipsService.Stub() {
             @Override
             public boolean canLaunchCaptureContentActivityForNote(int taskId) {
-                // In case of managed profile, use the main user's instance of the service. Callers
-                // cannot directly connect to the main user's instance as they may not have the
-                // permission to interact across users.
-                if (mUserManager.isManagedProfile()) {
-                    return canLaunchCaptureContentActivityForNoteFromMainUser(taskId);
-                }
+                return canLaunchCaptureContentActivityForNoteInternal(taskId)
+                        == CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
+            }
 
+            @Override
+            @CaptureContentForNoteStatusCodes
+            public int canLaunchCaptureContentActivityForNoteInternal(int taskId) {
                 if (!mAreTaskAndTimeIndependentPrerequisitesMet) {
-                    return false;
+                    return CAPTURE_CONTENT_FOR_NOTE_FAILED;
                 }
 
                 if (!mOptionalBubbles.get().isAppBubbleTaskId(taskId)) {
-                    return false;
+                    return CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
                 }
 
-                return !mDevicePolicyManager.getScreenCaptureDisabled(null);
+                return mDevicePolicyManager.getScreenCaptureDisabled(null)
+                        ? CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN
+                        : CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
             }
         };
     }
-
-    /** Returns whether the app clips API can be used by querying the service as the main user. */
-    private boolean canLaunchCaptureContentActivityForNoteFromMainUser(int taskId) {
-        if (mProxyConnectorToMainProfile == null) {
-            return false;
-        }
-
-        try {
-            AndroidFuture<Boolean> future = mProxyConnectorToMainProfile.postForResult(
-                    service -> service.canLaunchCaptureContentActivityForNote(taskId));
-            return future.get();
-        } catch (ExecutionException | InterruptedException e) {
-            Log.d(TAG, "Exception from service\n" + e);
-        }
-
-        return false;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
index f00803c..6e5cef4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -22,41 +22,41 @@
 import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
 import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE;
 
-import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
 import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;
 
 import android.app.Activity;
-import android.app.admin.DevicePolicyManager;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.content.Intent.CaptureContentForNoteStatusCodes;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.ApplicationInfoFlags;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.util.Log;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IAppClipsService;
 import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.notetask.NoteTaskController;
 import com.android.systemui.notetask.NoteTaskEntryPoint;
-import com.android.systemui.settings.UserTracker;
-import com.android.wm.shell.bubbles.Bubbles;
 
-import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -82,39 +82,57 @@
     private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName();
     static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
     static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
-    @VisibleForTesting
-    static final String EXTRA_USE_WP_USER = TAG + "USE_WP_USER";
     static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
     static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
     static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
     private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0);
 
-    private final DevicePolicyManager mDevicePolicyManager;
-    private final FeatureFlags mFeatureFlags;
-    private final Optional<Bubbles> mOptionalBubbles;
     private final NoteTaskController mNoteTaskController;
     private final PackageManager mPackageManager;
-    private final UserTracker mUserTracker;
     private final UiEventLogger mUiEventLogger;
-    private final UserManager mUserManager;
+    private final BroadcastSender mBroadcastSender;
+    @Background
+    private final Executor mBgExecutor;
+    @Main
+    private final Executor mMainExecutor;
     private final ResultReceiver mResultReceiver;
 
+    private final ServiceConnector<IAppClipsService> mAppClipsServiceConnector;
+
+    private UserHandle mUserHandle;
     private Intent mKillAppClipsBroadcastIntent;
-    private UserHandle mNotesAppUser;
 
     @Inject
-    public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags,
-            Optional<Bubbles> optionalBubbles, NoteTaskController noteTaskController,
-            PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger,
-            UserManager userManager, @Main Handler mainHandler) {
-        mDevicePolicyManager = devicePolicyManager;
-        mFeatureFlags = flags;
-        mOptionalBubbles = optionalBubbles;
+    public AppClipsTrampolineActivity(@Application Context context,
+            NoteTaskController noteTaskController, PackageManager packageManager,
+            UiEventLogger uiEventLogger, BroadcastSender broadcastSender,
+            @Background Executor bgExecutor, @Main Executor mainExecutor,
+            @Main Handler mainHandler) {
         mNoteTaskController = noteTaskController;
         mPackageManager = packageManager;
-        mUserTracker = userTracker;
         mUiEventLogger = uiEventLogger;
-        mUserManager = userManager;
+        mBroadcastSender = broadcastSender;
+        mBgExecutor = bgExecutor;
+        mMainExecutor = mainExecutor;
+
+        mResultReceiver = createResultReceiver(mainHandler);
+        mAppClipsServiceConnector = createServiceConnector(context);
+    }
+
+    /** A constructor used only for testing to verify interactions with {@link ServiceConnector}. */
+    @VisibleForTesting
+    AppClipsTrampolineActivity(ServiceConnector<IAppClipsService> appClipsServiceConnector,
+            NoteTaskController noteTaskController, PackageManager packageManager,
+            UiEventLogger uiEventLogger, BroadcastSender broadcastSender,
+            @Background Executor bgExecutor, @Main Executor mainExecutor,
+            @Main Handler mainHandler) {
+        mAppClipsServiceConnector = appClipsServiceConnector;
+        mNoteTaskController = noteTaskController;
+        mPackageManager = packageManager;
+        mUiEventLogger = uiEventLogger;
+        mBroadcastSender = broadcastSender;
+        mBgExecutor = bgExecutor;
+        mMainExecutor = mainExecutor;
 
         mResultReceiver = createResultReceiver(mainHandler);
     }
@@ -127,62 +145,62 @@
             return;
         }
 
-        if (mUserManager.isManagedProfile()) {
-            maybeStartActivityForWPUser();
-            finish();
+        mUserHandle = getUser();
+
+        mBgExecutor.execute(() -> {
+            AndroidFuture<Integer> statusCodeFuture = mAppClipsServiceConnector.postForResult(
+                    service -> service.canLaunchCaptureContentActivityForNoteInternal(getTaskId()));
+            statusCodeFuture.whenCompleteAsync(this::handleAppClipsStatusCode, mMainExecutor);
+        });
+    }
+
+    @Override
+    protected void onDestroy() {
+        if (isFinishing() && mKillAppClipsBroadcastIntent != null) {
+            mBroadcastSender.sendBroadcast(mKillAppClipsBroadcastIntent, PERMISSION_SELF);
+        }
+
+        super.onDestroy();
+    }
+
+    private void handleAppClipsStatusCode(@CaptureContentForNoteStatusCodes int statusCode,
+            Throwable error) {
+        if (isFinishing()) {
+            // It's too late, trampoline activity is finishing or already finished. Return early.
             return;
         }
 
-        if (!mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)) {
-            finish();
+        if (error != null) {
+            Log.d(TAG, "Error querying app clips service", error);
+            setErrorResultAndFinish(statusCode);
             return;
         }
 
-        if (mOptionalBubbles.isEmpty()) {
-            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
-            return;
-        }
+        switch (statusCode) {
+            case CAPTURE_CONTENT_FOR_NOTE_SUCCESS:
+                launchAppClipsActivity();
+                break;
 
-        if (!mOptionalBubbles.get().isAppBubbleTaskId(getTaskId())) {
-            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED);
-            return;
+            case CAPTURE_CONTENT_FOR_NOTE_FAILED:
+            case CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED:
+            case CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN:
+            default:
+                setErrorResultAndFinish(statusCode);
         }
+    }
 
-        if (mDevicePolicyManager.getScreenCaptureDisabled(null)) {
-            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN);
-            return;
-        }
-
-        ComponentName componentName;
-        try {
-            componentName = ComponentName.unflattenFromString(
+    private void launchAppClipsActivity() {
+        ComponentName componentName = ComponentName.unflattenFromString(
                     getString(R.string.config_screenshotAppClipsActivityComponent));
-        } catch (Resources.NotFoundException e) {
-            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
-            return;
-        }
-
-        if (componentName == null || componentName.getPackageName().isEmpty()
-                || componentName.getClassName().isEmpty()) {
-            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
-            return;
-        }
-
-        mNotesAppUser = getUser();
-        if (getIntent().getBooleanExtra(EXTRA_USE_WP_USER, /* defaultValue= */ false)) {
-            // Get the work profile user internally instead of passing around via intent extras as
-            // this activity is exported apps could potentially mess around with intent extras.
-            mNotesAppUser = getWorkProfileUser().orElse(mNotesAppUser);
-        }
-
         String callingPackageName = getCallingPackage();
-        Intent intent = new Intent().setComponent(componentName)
+
+        Intent intent = new Intent()
+                .setComponent(componentName)
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                 .putExtra(EXTRA_RESULT_RECEIVER, mResultReceiver)
                 .putExtra(EXTRA_CALLING_PACKAGE_NAME, callingPackageName);
         try {
-            // Start the App Clips activity for the user corresponding to the notes app user.
-            startActivityAsUser(intent, mNotesAppUser);
+            startActivity(intent);
 
             // Set up the broadcast intent that will inform the above App Clips activity to finish
             // when this trampoline activity is finished.
@@ -198,39 +216,6 @@
         }
     }
 
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-
-        if (isFinishing() && mKillAppClipsBroadcastIntent != null) {
-            sendBroadcast(mKillAppClipsBroadcastIntent, PERMISSION_SELF);
-        }
-    }
-
-    private Optional<UserHandle> getWorkProfileUser() {
-        return mUserTracker.getUserProfiles().stream()
-                .filter(profile -> mUserManager.isManagedProfile(profile.id))
-                .findFirst()
-                .map(UserInfo::getUserHandle);
-    }
-
-    private void maybeStartActivityForWPUser() {
-        UserHandle mainUser = mUserManager.getMainUser();
-        if (mainUser == null) {
-            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
-            return;
-        }
-
-        // Start the activity as the main user with activity result forwarding. Set the intent extra
-        // so that the newly started trampoline activity starts the actual app clips activity as the
-        // work profile user. Starting the app clips activity as the work profile user is required
-        // to save the screenshot in work profile user storage and grant read permission to the URI.
-        startActivityAsUser(
-                new Intent(this, AppClipsTrampolineActivity.class)
-                        .putExtra(EXTRA_USE_WP_USER, /* value= */ true)
-                        .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), mainUser);
-    }
-
     private void setErrorResultAndFinish(int errorCode) {
         setResult(RESULT_OK,
                 new Intent().putExtra(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, errorCode));
@@ -241,7 +226,7 @@
         int callingPackageUid = 0;
         try {
             callingPackageUid = mPackageManager.getApplicationInfoAsUser(callingPackageName,
-                    APPLICATION_INFO_FLAGS, mNotesAppUser.getIdentifier()).uid;
+                    APPLICATION_INFO_FLAGS, mUserHandle.getIdentifier()).uid;
         } catch (NameNotFoundException e) {
             Log.d(TAG, "Couldn't find notes app UID " + e);
         }
@@ -281,7 +266,7 @@
             mKillAppClipsBroadcastIntent = null;
 
             // Expand the note bubble before returning the result.
-            mNoteTaskController.showNoteTaskAsUser(NoteTaskEntryPoint.APP_CLIPS, mNotesAppUser);
+            mNoteTaskController.showNoteTaskAsUser(NoteTaskEntryPoint.APP_CLIPS, mUserHandle);
             setResult(RESULT_OK, convertedData);
             finish();
         }
@@ -298,11 +283,18 @@
         appClipsResultReceiver.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
 
-        ResultReceiver resultReceiver  = ResultReceiver.CREATOR.createFromParcel(parcel);
+        ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(parcel);
         parcel.recycle();
         return resultReceiver;
     }
 
+    private ServiceConnector<IAppClipsService> createServiceConnector(
+            @Application Context context) {
+        return new ServiceConnector.Impl<>(context, new Intent(context, AppClipsService.class),
+                Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY | Context.BIND_NOT_VISIBLE,
+                UserHandle.USER_SYSTEM, IAppClipsService.Stub::asInterface);
+    }
+
     /** This is a test only API for mocking response from {@link AppClipsActivity}. */
     @VisibleForTesting
     public ResultReceiver getResultReceiverForTest() {
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index fc89a9e..f4d19dc 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -30,6 +30,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Looper;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 
 import androidx.annotation.Nullable;
@@ -38,6 +39,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.util.LargeScreenUtils;
 
 import java.util.concurrent.Executor;
@@ -59,6 +61,7 @@
     private float mViewAlpha = 1.0f;
     private Drawable mDrawable;
     private PorterDuffColorFilter mColorFilter;
+    private String mScrimName;
     private int mTintColor;
     private boolean mBlendWithMainColor = true;
     private Runnable mChangeRunnable;
@@ -336,6 +339,15 @@
         }
     }
 
+    public void setScrimName(String scrimName) {
+        mScrimName = scrimName;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch(mScrimName, ev, super.dispatchTouchEvent(ev));
+    }
+
     /**
      * The position of the bottom of the scrim, used for clipping.
      * @see #enableBottomEdgeConcave(boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 9594ba3..6af9b73 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -22,7 +22,6 @@
 
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.hardware.display.BrightnessInfo;
@@ -31,10 +30,10 @@
 import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -43,6 +42,8 @@
 import android.util.Log;
 import android.util.MathUtils;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -52,10 +53,13 @@
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.concurrent.Executor;
 
-import javax.inject.Inject;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
 
 public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
     private static final String TAG = "CentralSurfaces.BrightnessController";
@@ -75,8 +79,11 @@
     private final DisplayManager mDisplayManager;
     private final UserTracker mUserTracker;
     private final DisplayTracker mDisplayTracker;
+    @Nullable
     private final IVrManager mVrManager;
 
+    private final SecureSettings mSecureSettings;
+
     private final Executor mMainExecutor;
     private final Handler mBackgroundHandler;
     private final BrightnessObserver mBrightnessObserver;
@@ -106,6 +113,8 @@
     /** ContentObserver to watch brightness */
     private class BrightnessObserver extends ContentObserver {
 
+        private boolean mObserving = false;
+
         BrightnessObserver(Handler handler) {
             super(handler);
         }
@@ -124,19 +133,17 @@
         }
 
         public void startObserving() {
-            final ContentResolver cr = mContext.getContentResolver();
-            cr.unregisterContentObserver(this);
-            cr.registerContentObserver(
-                    BRIGHTNESS_MODE_URI,
-                    false, this, UserHandle.USER_ALL);
-            mDisplayTracker.addBrightnessChangeCallback(mBrightnessListener,
-                    new HandlerExecutor(mHandler));
+            if (!mObserving) {
+                mObserving = true;
+                mSecureSettings.registerContentObserverForUser(
+                        BRIGHTNESS_MODE_URI,
+                        false, this, UserHandle.USER_ALL);
+            }
         }
 
         public void stopObserving() {
-            final ContentResolver cr = mContext.getContentResolver();
-            cr.unregisterContentObserver(this);
-            mDisplayTracker.removeCallback(mBrightnessListener);
+            mSecureSettings.unregisterContentObserver(this);
+            mObserving = false;
         }
 
     }
@@ -159,6 +166,8 @@
             }
 
             mBrightnessObserver.startObserving();
+            mDisplayTracker.addBrightnessChangeCallback(mBrightnessListener,
+                    new HandlerExecutor(mMainHandler));
             mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
 
             // Update the slider and mode before attaching the listener so we don't
@@ -166,7 +175,7 @@
             mUpdateModeRunnable.run();
             mUpdateSliderRunnable.run();
 
-            mHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);
+            mMainHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);
         }
     };
 
@@ -187,9 +196,10 @@
             }
 
             mBrightnessObserver.stopObserving();
+            mDisplayTracker.removeCallback(mBrightnessListener);
             mUserTracker.removeCallback(mUserChangedCallback);
 
-            mHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
+            mMainHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
         }
     };
 
@@ -225,7 +235,7 @@
             mBrightnessMin = info.brightnessMinimum;
             // Value is passed as intbits, since this is what the message takes.
             final int valueAsIntBits = Float.floatToIntBits(info.brightness);
-            mHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
+            mMainHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
                     inVrMode ? 1 : 0).sendToTarget();
         }
     };
@@ -233,14 +243,14 @@
     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
         @Override
         public void onVrStateChanged(boolean enabled) {
-            mHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0)
+            mMainHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0)
                     .sendToTarget();
         }
     };
 
-    private final Handler mHandler = new Handler() {
+    private final Handler.Callback mHandlerCallback = new Handler.Callback() {
         @Override
-        public void handleMessage(Message msg) {
+        public boolean handleMessage(Message msg) {
             mExternalChange = true;
             try {
                 switch (msg.what) {
@@ -257,14 +267,18 @@
                         updateVrMode(msg.arg1 != 0);
                         break;
                     default:
-                        super.handleMessage(msg);
+                        return false;
+
                 }
             } finally {
                 mExternalChange = false;
             }
+            return true;
         }
     };
 
+    private final Handler mMainHandler;
+
     private final UserTracker.Callback mUserChangedCallback =
             new UserTracker.Callback() {
                 @Override
@@ -274,12 +288,17 @@
                 }
             };
 
+    @AssistedInject
     public BrightnessController(
             Context context,
-            ToggleSlider control,
+            @Assisted ToggleSlider control,
             UserTracker userTracker,
             DisplayTracker displayTracker,
+            DisplayManager displayManager,
+            SecureSettings secureSettings,
+            @Nullable IVrManager iVrManager,
             @Main Executor mainExecutor,
+            @Main Looper mainLooper,
             @Background Handler bgHandler) {
         mContext = context;
         mControl = control;
@@ -288,22 +307,23 @@
         mBackgroundHandler = bgHandler;
         mUserTracker = userTracker;
         mDisplayTracker = displayTracker;
-        mBrightnessObserver = new BrightnessObserver(mHandler);
-
+        mSecureSettings = secureSettings;
         mDisplayId = mContext.getDisplayId();
-        PowerManager pm = context.getSystemService(PowerManager.class);
+        mDisplayManager = displayManager;
+        mVrManager = iVrManager;
 
-        mDisplayManager = context.getSystemService(DisplayManager.class);
-        mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
-                Context.VR_SERVICE));
+        mMainHandler = new Handler(mainLooper, mHandlerCallback);
+        mBrightnessObserver = new BrightnessObserver(mMainHandler);
     }
 
     public void registerCallbacks() {
+        mBackgroundHandler.removeCallbacks(mStartListeningRunnable);
         mBackgroundHandler.post(mStartListeningRunnable);
     }
 
     /** Unregister all call backs, both to and from the controller */
     public void unregisterCallbacks() {
+        mBackgroundHandler.removeCallbacks(mStopListeningRunnable);
         mBackgroundHandler.post(mStopListeningRunnable);
         mControlValueInitialized = false;
     }
@@ -418,38 +438,12 @@
         mSliderAnimator.start();
     }
 
+
+
     /** Factory for creating a {@link BrightnessController}. */
-    public static class Factory {
-        private final Context mContext;
-        private final UserTracker mUserTracker;
-        private final DisplayTracker mDisplayTracker;
-        private final Executor mMainExecutor;
-        private final Handler mBackgroundHandler;
-
-        @Inject
-        public Factory(
-                Context context,
-                UserTracker userTracker,
-                DisplayTracker displayTracker,
-                @Main Executor mainExecutor,
-                @Background Handler bgHandler) {
-            mContext = context;
-            mUserTracker = userTracker;
-            mDisplayTracker = displayTracker;
-            mMainExecutor = mainExecutor;
-            mBackgroundHandler = bgHandler;
-        }
-
+    @AssistedFactory
+    public interface Factory {
         /** Create a {@link BrightnessController} */
-        public BrightnessController create(ToggleSlider toggleSlider) {
-            return new BrightnessController(
-                    mContext,
-                    toggleSlider,
-                    mUserTracker,
-                    mDisplayTracker,
-                    mMainExecutor,
-                    mBackgroundHandler);
-        }
+        BrightnessController create(ToggleSlider toggleSlider);
     }
-
 }
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 8879501..38b1f14 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -18,54 +18,56 @@
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
 
 import android.app.Activity;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.os.Handler;
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.List;
-import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
 /** A dialog that provides controls for adjusting the screen brightness. */
 public class BrightnessDialog extends Activity {
 
+    @VisibleForTesting
+    static final int DIALOG_TIMEOUT_MILLIS = 3000;
+
     private BrightnessController mBrightnessController;
     private final BrightnessSliderController.Factory mToggleSliderFactory;
-    private final UserTracker mUserTracker;
-    private final DisplayTracker mDisplayTracker;
-    private final Executor mMainExecutor;
-    private final Handler mBackgroundHandler;
+    private final BrightnessController.Factory mBrightnessControllerFactory;
+    private final DelayableExecutor mMainExecutor;
+    private final AccessibilityManagerWrapper mAccessibilityMgr;
+    private Runnable mCancelTimeoutRunnable;
 
     @Inject
     public BrightnessDialog(
-            UserTracker userTracker,
-            DisplayTracker displayTracker,
-            BrightnessSliderController.Factory factory,
-            @Main Executor mainExecutor,
-            @Background Handler bgHandler) {
-        mUserTracker = userTracker;
-        mDisplayTracker = displayTracker;
-        mToggleSliderFactory = factory;
+            BrightnessSliderController.Factory brightnessSliderfactory,
+            BrightnessController.Factory brightnessControllerFactory,
+            @Main DelayableExecutor mainExecutor,
+            AccessibilityManagerWrapper accessibilityMgr
+    ) {
+        mToggleSliderFactory = brightnessSliderfactory;
+        mBrightnessControllerFactory = brightnessControllerFactory;
         mMainExecutor = mainExecutor;
-        mBackgroundHandler = bgHandler;
+        mAccessibilityMgr = accessibilityMgr;
     }
 
 
@@ -84,6 +86,7 @@
         window.getDecorView();
         window.setLayout(
                 WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
+        getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false);
 
         setContentView(R.layout.brightness_mirror_container);
         FrameLayout frame = findViewById(R.id.brightness_mirror_container);
@@ -109,8 +112,7 @@
         controller.init();
         frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
 
-        mBrightnessController = new BrightnessController(
-                this, controller, mUserTracker, mDisplayTracker, mMainExecutor, mBackgroundHandler);
+        mBrightnessController = mBrightnessControllerFactory.create(controller);
     }
 
     @Override
@@ -121,6 +123,14 @@
     }
 
     @Override
+    protected void onResume() {
+        super.onResume();
+        if (triggeredByBrightnessKey()) {
+            scheduleTimeout();
+        }
+    }
+
+    @Override
     protected void onPause() {
         super.onPause();
         overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
@@ -138,9 +148,25 @@
         if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
                 || keyCode == KeyEvent.KEYCODE_VOLUME_UP
                 || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
+            if (mCancelTimeoutRunnable != null) {
+                mCancelTimeoutRunnable.run();
+            }
             finish();
         }
 
         return super.onKeyDown(keyCode, event);
     }
+
+    private boolean triggeredByBrightnessKey() {
+        return getIntent().getBooleanExtra(EXTRA_FROM_BRIGHTNESS_KEY, false);
+    }
+
+    private void scheduleTimeout() {
+        if (mCancelTimeoutRunnable != null) {
+            mCancelTimeoutRunnable.run();
+        }
+        final int timeout = mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS,
+                AccessibilityManager.FLAG_CONTENT_CONTROLS);
+        mCancelTimeoutRunnable = mMainExecutor.executeDelayed(this::finish, timeout);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
index 7e0f504..a9b3d0a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -31,8 +31,7 @@
         val constraintAlpha = if (visible) 0f else 1f
         return ConstraintsChanges(
             qqsConstraintsChanges = {
-                setAlpha(R.id.statusIcons, constraintAlpha)
-                setAlpha(R.id.batteryRemainingIcon, constraintAlpha)
+                setAlpha(R.id.shade_header_system_icons, constraintAlpha)
             }
         )
     }
@@ -45,14 +44,15 @@
                     R.id.barrier,
                     ConstraintSet.START,
                     0,
-                    R.id.statusIcons,
+                    R.id.shade_header_system_icons,
                     R.id.privacy_container
                 )
-                connect(R.id.statusIcons, ConstraintSet.START, R.id.date, ConstraintSet.END)
+                connect(R.id.shade_header_system_icons, ConstraintSet.START, R.id.date,
+                    ConstraintSet.END)
                 connect(R.id.privacy_container, ConstraintSet.START, R.id.date, ConstraintSet.END)
-                constrainWidth(R.id.statusIcons, ViewGroup.LayoutParams.WRAP_CONTENT)
+                constrainWidth(R.id.shade_header_system_icons, ViewGroup.LayoutParams.WRAP_CONTENT)
                 constrainedWidth(R.id.date, true)
-                constrainedWidth(R.id.statusIcons, true)
+                constrainedWidth(R.id.shade_header_system_icons, true)
             }
         )
     }
@@ -84,7 +84,7 @@
                 setGuidelineEnd(centerEnd, offsetFromEdge)
                 connect(R.id.date, ConstraintSet.END, centerStart, ConstraintSet.START)
                 connect(
-                    R.id.statusIcons,
+                    R.id.shade_header_system_icons,
                     ConstraintSet.START,
                     centerEnd,
                     ConstraintSet.END
@@ -96,7 +96,7 @@
                     ConstraintSet.END
                 )
                 constrainedWidth(R.id.date, true)
-                constrainedWidth(R.id.statusIcons, true)
+                constrainedWidth(R.id.shade_header_system_icons, true)
             },
             qsConstraintsChanges = {
                 setGuidelineBegin(centerStart, offsetFromEdge)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt
new file mode 100644
index 0000000..45fc68a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.os.PowerManager
+import android.view.GestureDetector
+import android.view.MotionEvent
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import javax.inject.Inject
+
+/**
+ * This gestureListener will wake up by tap when the device is dreaming but not dozing, and the
+ * selected screensaver is hosted in lockscreen. Tap is gated by the falsing manager.
+ *
+ * Touches go through the [NotificationShadeWindowViewController].
+ */
+@SysUISingleton
+class LockscreenHostedDreamGestureListener
+@Inject
+constructor(
+    private val falsingManager: FalsingManager,
+    private val powerInteractor: PowerInteractor,
+    private val statusBarStateController: StatusBarStateController,
+    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    private val keyguardRepository: KeyguardRepository,
+    private val shadeLogger: ShadeLogger,
+) : GestureDetector.SimpleOnGestureListener() {
+    private val TAG = this::class.simpleName
+
+    override fun onSingleTapUp(e: MotionEvent): Boolean {
+        if (shouldHandleMotionEvent()) {
+            if (!falsingManager.isFalseTap(LOW_PENALTY)) {
+                shadeLogger.d("$TAG#onSingleTapUp tap handled, requesting wakeUpIfDreaming")
+                powerInteractor.wakeUpIfDreaming(
+                    "DREAMING_SINGLE_TAP",
+                    PowerManager.WAKE_REASON_TAP
+                )
+            } else {
+                shadeLogger.d("$TAG#onSingleTapUp false tap ignored")
+            }
+            return true
+        }
+        return false
+    }
+
+    private fun shouldHandleMotionEvent(): Boolean {
+        return keyguardRepository.isActiveDreamLockscreenHosted.value &&
+            statusBarStateController.state == StatusBarState.KEYGUARD &&
+            !primaryBouncerInteractor.isBouncerShowing()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
index 6143308..4644d41 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
@@ -108,7 +108,7 @@
          * @see NPVCDownEventState.asStringList
          */
         fun toList(): List<Row> {
-            return buffer.asSequence().map { it.asStringList }.toList()
+            return buffer.map { it.asStringList }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
index af3cc86..c501d88 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
@@ -106,6 +106,11 @@
     }
 
     @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch("NPV", ev, super.dispatchTouchEvent(ev));
+    }
+
+    @Override
     public void dispatchConfigurationChanged(Configuration newConfig) {
         super.dispatchConfigurationChanged(newConfig);
         mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 267b147..132cd61 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -17,8 +17,6 @@
 package com.android.systemui.shade;
 
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
-import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
@@ -30,6 +28,9 @@
 import static com.android.systemui.classifier.Classifier.GENERIC;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 import static com.android.systemui.classifier.Classifier.UNLOCK;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING;
@@ -70,6 +71,7 @@
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.MathUtils;
+import android.view.HapticFeedbackConstants;
 import android.view.InputDevice;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -88,9 +90,10 @@
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 
+import androidx.constraintlayout.widget.ConstraintLayout;
+
 import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.policy.SystemBarUtils;
@@ -114,8 +117,11 @@
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.LaunchAnimator;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeLog;
@@ -125,27 +131,29 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.keyguard.KeyguardViewConfigurator;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.shared.model.WakefulnessModel;
 import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
+import com.android.systemui.keyguard.ui.view.KeyguardRootView;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -156,6 +164,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.shade.transition.ShadeTransitionController;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.GestureRecorder;
@@ -165,7 +174,6 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -206,7 +214,6 @@
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.TapAgainViewController;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
@@ -236,7 +243,7 @@
 
 import kotlinx.coroutines.CoroutineDispatcher;
 
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
 public final class NotificationPanelViewController implements ShadeSurface, Dumpable {
 
     public static final String TAG = NotificationPanelView.class.getSimpleName();
@@ -346,8 +353,8 @@
     private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
     private final NotificationGutsManager mGutsManager;
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
+    private final KeyguardRootView mKeyguardRootView;
     private final QuickSettingsController mQsController;
-    private final InteractionJankMonitor mInteractionJankMonitor;
     private final TouchHandler mTouchHandler = new TouchHandler();
 
     private long mDownTime;
@@ -360,11 +367,12 @@
     /** The current squish amount for the predictive back animation */
     private float mCurrentBackProgress = 0.0f;
     private boolean mTracking;
+    private boolean mIsTrackingExpansionFromStatusBar;
     private boolean mHintAnimationRunning;
+    @Deprecated
     private KeyguardBottomAreaView mKeyguardBottomArea;
     private boolean mExpanding;
     private boolean mSplitShadeEnabled;
-    private boolean mDualShadeEnabled;
     /** The bottom padding reserved for elements of the keyguard measuring notifications. */
     private float mKeyguardNotificationBottomPadding;
     /**
@@ -438,8 +446,6 @@
     private final FalsingCollector mFalsingCollector;
     private final ShadeHeadsUpTrackerImpl mShadeHeadsUpTracker = new ShadeHeadsUpTrackerImpl();
     private final ShadeFoldAnimator mShadeFoldAnimator = new ShadeFoldAnimatorImpl();
-    private final ShadeNotificationPresenterImpl mShadeNotificationPresenter =
-            new ShadeNotificationPresenterImpl();
 
     private boolean mShowIconsWhenExpanded;
     private int mIndicationBottomPadding;
@@ -595,14 +601,19 @@
     private final OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel;
     private final LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel;
     private final GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
+    private final GoneToDreamingLockscreenHostedTransitionViewModel
+            mGoneToDreamingLockscreenHostedTransitionViewModel;
+
     private final LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel;
+    private final PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
 
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final KeyguardInteractor mKeyguardInteractor;
-    private final @Nullable MultiShadeInteractor mMultiShadeInteractor;
+    private final KeyguardViewConfigurator mKeyguardViewConfigurator;
     private final CoroutineDispatcher mMainDispatcher;
     private boolean mIsAnyMultiShadeExpanded;
     private boolean mIsOcclusionTransitionRunning = false;
+    private boolean mIsGoneToDreamingLockscreenHostedTransitionRunning;
     private int mDreamingToLockscreenTransitionTranslationY;
     private int mOccludedToLockscreenTransitionTranslationY;
     private int mLockscreenToDreamingTransitionTranslationY;
@@ -650,6 +661,25 @@
                     step.getTransitionState() == TransitionState.RUNNING;
             };
 
+    private final Consumer<TransitionStep> mGoneToDreamingLockscreenHostedTransition =
+            (TransitionStep step) -> {
+                mIsOcclusionTransitionRunning =
+                        step.getTransitionState() == TransitionState.RUNNING;
+                mIsGoneToDreamingLockscreenHostedTransitionRunning = mIsOcclusionTransitionRunning;
+            };
+
+    private final Consumer<TransitionStep> mLockscreenToDreamingLockscreenHostedTransition =
+            (TransitionStep step) -> {
+                mIsOcclusionTransitionRunning =
+                        step.getTransitionState() == TransitionState.RUNNING;
+            };
+
+    private final Consumer<TransitionStep> mDreamingLockscreenHostedToLockscreenTransition =
+            (TransitionStep step) -> {
+                mIsOcclusionTransitionRunning =
+                        step.getTransitionState() == TransitionState.RUNNING;
+            };
+
     private final Consumer<TransitionStep> mLockscreenToOccludedTransition =
             (TransitionStep step) -> {
                 mIsOcclusionTransitionRunning =
@@ -724,7 +754,6 @@
             NotificationStackSizeCalculator notificationStackSizeCalculator,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             ShadeTransitionController shadeTransitionController,
-            InteractionJankMonitor interactionJankMonitor,
             SystemClock systemClock,
             KeyguardBottomAreaViewModel keyguardBottomAreaViewModel,
             KeyguardBottomAreaInteractor keyguardBottomAreaInteractor,
@@ -733,16 +762,19 @@
             OccludedToLockscreenTransitionViewModel occludedToLockscreenTransitionViewModel,
             LockscreenToDreamingTransitionViewModel lockscreenToDreamingTransitionViewModel,
             GoneToDreamingTransitionViewModel goneToDreamingTransitionViewModel,
+            GoneToDreamingLockscreenHostedTransitionViewModel
+                    goneToDreamingLockscreenHostedTransitionViewModel,
             LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel,
+            PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
             @Main CoroutineDispatcher mainDispatcher,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
-            Provider<MultiShadeInteractor> multiShadeInteractorProvider,
             DumpManager dumpManager,
             KeyguardLongPressViewModel keyguardLongPressViewModel,
             KeyguardInteractor keyguardInteractor,
             ActivityStarter activityStarter,
-            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
-        mInteractionJankMonitor = interactionJankMonitor;
+            KeyguardViewConfigurator keyguardViewConfigurator,
+            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
+            KeyguardRootView keyguardRootView) {
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onKeyguardFadingAwayChanged() {
@@ -760,9 +792,13 @@
         mOccludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel;
         mLockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel;
         mGoneToDreamingTransitionViewModel = goneToDreamingTransitionViewModel;
+        mGoneToDreamingLockscreenHostedTransitionViewModel =
+                goneToDreamingLockscreenHostedTransitionViewModel;
         mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel;
+        mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mKeyguardInteractor = keyguardInteractor;
+        mKeyguardViewConfigurator = keyguardViewConfigurator;
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
@@ -839,8 +875,6 @@
         mFeatureFlags = featureFlags;
         mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
         mTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
-        mDualShadeEnabled = mFeatureFlags.isEnabled(Flags.DUAL_SHADE);
-        mMultiShadeInteractor = mDualShadeEnabled ? multiShadeInteractorProvider.get() : null;
         mFalsingCollector = falsingCollector;
         mPowerManager = powerManager;
         mWakeUpCoordinator = coordinator;
@@ -943,6 +977,7 @@
                     }
                 });
         mAlternateBouncerInteractor = alternateBouncerInteractor;
+        mKeyguardRootView = keyguardRootView;
         dumpManager.registerDumpable(this);
     }
 
@@ -1020,20 +1055,19 @@
         mKeyguardStatusBarViewController.init();
 
         mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
-        updateViewControllers(
-                mView.findViewById(R.id.keyguard_status_view),
-                userAvatarContainer,
-                keyguardUserSwitcherView);
+        updateViewControllers(userAvatarContainer, keyguardUserSwitcherView);
 
         mNotificationStackScrollLayoutController.setOnHeightChangedListener(
                 new NsslHeightChangedListener());
         mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
                 mOnEmptySpaceClickListener);
-        mQsController.initNotificationStackScrollLayoutController();
+        mQsController.init();
         mShadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged);
         mShadeHeadsUpTracker.addTrackingHeadsUpListener(
                 mNotificationStackScrollLayoutController::setTrackingHeadsUp);
-        setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
+        }
 
         initBottomArea();
 
@@ -1073,24 +1107,40 @@
 
         mTapAgainViewController.init();
         mShadeHeaderController.init();
+        mShadeHeaderController.setShadeCollapseAction(
+                () -> collapse(/* delayed= */ false , /* speedUpFactor= */ 1.0f));
         mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
         mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
                 controller.setup(mNotificationContainerParent));
 
-        if (mDualShadeEnabled) {
-            collectFlow(mView, mMultiShadeInteractor.isAnyShadeExpanded(),
-                    mMultiShadeExpansionConsumer, mMainDispatcher);
-        }
-
         // Dreaming->Lockscreen
         collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
                 mDreamingToLockscreenTransition, mMainDispatcher);
         collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(),
-                setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+                setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
+                mMainDispatcher);
         collectFlow(mView, mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY(
                 mDreamingToLockscreenTransitionTranslationY),
                 setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
 
+        // Gone -> Dreaming hosted in lockscreen
+        collectFlow(mView, mKeyguardTransitionInteractor
+                        .getGoneToDreamingLockscreenHostedTransition(),
+                mGoneToDreamingLockscreenHostedTransition, mMainDispatcher);
+        collectFlow(mView, mGoneToDreamingLockscreenHostedTransitionViewModel.getLockscreenAlpha(),
+                setTransitionAlpha(mNotificationStackScrollLayoutController),
+                mMainDispatcher);
+
+        // Lockscreen -> Dreaming hosted in lockscreen
+        collectFlow(mView, mKeyguardTransitionInteractor
+                        .getLockscreenToDreamingLockscreenHostedTransition(),
+                mLockscreenToDreamingLockscreenHostedTransition, mMainDispatcher);
+
+        // Dreaming hosted in lockscreen -> Lockscreen
+        collectFlow(mView, mKeyguardTransitionInteractor
+                        .getDreamingLockscreenHostedToLockscreenTransition(),
+                mDreamingLockscreenHostedToLockscreenTransition, mMainDispatcher);
+
         // Occluded->Lockscreen
         collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(),
                 mOccludedToLockscreenTransition, mMainDispatcher);
@@ -1104,7 +1154,8 @@
         collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(),
                 mLockscreenToDreamingTransition, mMainDispatcher);
         collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(),
-                setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+                setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
+                mMainDispatcher);
         collectFlow(mView, mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY(
                 mLockscreenToDreamingTransitionTranslationY),
                 setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
@@ -1126,6 +1177,10 @@
         collectFlow(mView, mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY(
                 mLockscreenToOccludedTransitionTranslationY),
                 setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
+
+        // Primary bouncer->Gone (ensures lockscreen content is not visible on successful auth)
+        collectFlow(mView, mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha(),
+                setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
     }
 
     @VisibleForTesting
@@ -1164,15 +1219,31 @@
         mQsController.loadDimens();
     }
 
-    private void updateViewControllers(KeyguardStatusView keyguardStatusView,
+    private void updateViewControllers(
             FrameLayout userAvatarView,
             KeyguardUserSwitcherView keyguardUserSwitcherView) {
         // Re-associate the KeyguardStatusViewController
-        KeyguardStatusViewComponent statusViewComponent =
+        if (mKeyguardStatusViewController != null) {
+            mKeyguardStatusViewController.onDestroy();
+        }
+
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            // Need a shared controller until mKeyguardStatusViewController can be removed from
+            // here, due to important state being set in that controller. Rebind in order to pick
+            // up config changes
+            mKeyguardViewConfigurator.bindKeyguardStatusView(mView);
+            mKeyguardStatusViewController =
+                    mKeyguardViewConfigurator.getKeyguardStatusViewController();
+        } else {
+            KeyguardStatusView keyguardStatusView = mView.getRootView().findViewById(
+                    R.id.keyguard_status_view);
+            KeyguardStatusViewComponent statusViewComponent =
                 mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
-        mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
-        mKeyguardStatusViewController.init();
+            mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
+            mKeyguardStatusViewController.init();
+        }
         mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+
         updateClockAppearance();
 
         if (mKeyguardUserSwitcherController != null) {
@@ -1278,15 +1349,22 @@
     void reInflateViews() {
         debugLog("reInflateViews");
         // Re-inflate the status view group.
-        KeyguardStatusView keyguardStatusView =
-                mNotificationContainerParent.findViewById(R.id.keyguard_status_view);
-        int statusIndex = mNotificationContainerParent.indexOfChild(keyguardStatusView);
-        mNotificationContainerParent.removeView(keyguardStatusView);
-        keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate(
-                R.layout.keyguard_status_view, mNotificationContainerParent, false);
-        mNotificationContainerParent.addView(keyguardStatusView, statusIndex);
-        attachSplitShadeMediaPlayerContainer(
-                keyguardStatusView.findViewById(R.id.status_view_media_container));
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            KeyguardStatusView keyguardStatusView =
+                    mNotificationContainerParent.findViewById(R.id.keyguard_status_view);
+            int statusIndex = mNotificationContainerParent.indexOfChild(keyguardStatusView);
+            mNotificationContainerParent.removeView(keyguardStatusView);
+            keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate(
+                    R.layout.keyguard_status_view, mNotificationContainerParent, false);
+            mNotificationContainerParent.addView(keyguardStatusView, statusIndex);
+
+            attachSplitShadeMediaPlayerContainer(
+                    keyguardStatusView.findViewById(R.id.status_view_media_container));
+        } else {
+            attachSplitShadeMediaPlayerContainer(
+                    mKeyguardViewConfigurator.getKeyguardRootView()
+                        .findViewById(R.id.status_view_media_container));
+        }
 
         // we need to update KeyguardStatusView constraints after reinflating it
         updateResources();
@@ -1312,18 +1390,19 @@
                         R.layout.keyguard_user_switcher /* layoutId */,
                         showKeyguardUserSwitcher /* enabled */);
 
-        updateViewControllers(mView.findViewById(R.id.keyguard_status_view), userAvatarView,
-                keyguardUserSwitcherView);
+        updateViewControllers(userAvatarView, keyguardUserSwitcherView);
 
-        // Update keyguard bottom area
-        int index = mView.indexOfChild(mKeyguardBottomArea);
-        mView.removeView(mKeyguardBottomArea);
-        KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
-        setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView());
-        mKeyguardBottomArea.initFrom(oldBottomArea);
-        mView.addView(mKeyguardBottomArea, index);
-        initBottomArea();
-        mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            // Update keyguard bottom area
+            int index = mView.indexOfChild(mKeyguardBottomArea);
+            mView.removeView(mKeyguardBottomArea);
+            KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
+            setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView());
+            mKeyguardBottomArea.initFrom(oldBottomArea);
+            mView.addView(mKeyguardBottomArea, index);
+
+            initBottomArea();
+        }
         mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
                 mStatusBarStateController.getInterpolatedDozeAmount());
 
@@ -1346,7 +1425,12 @@
                     false,
                     mBarState);
         }
-        setKeyguardBottomAreaVisibility(mBarState, false);
+
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            setKeyguardVisibility(mBarState, false);
+        } else {
+            setKeyguardBottomAreaVisibility(mBarState, false);
+        }
 
         mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
         mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView));
@@ -1357,7 +1441,8 @@
     }
 
     private void initBottomArea() {
-        mKeyguardBottomArea.init(
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            mKeyguardBottomArea.init(
                 mKeyguardBottomAreaViewModel,
                 mFalsingManager,
                 mLockIconViewController,
@@ -1365,6 +1450,10 @@
                         mKeyguardIndicationController.showTransientIndication(stringResourceId),
                 mVibratorHelper,
                 mActivityStarter);
+
+            // Rebind (for now), as a new bottom area and indication area may have been created
+            mKeyguardViewConfigurator.bindIndicationArea();
+        }
     }
 
     @VisibleForTesting
@@ -1400,16 +1489,18 @@
         return mHintAnimationRunning || mUnlockedScreenOffAnimationController.isAnimationPlaying();
     }
 
+    @Deprecated
     private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) {
         mKeyguardBottomArea = keyguardBottomArea;
-        mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
     }
 
-    void setOpenCloseListener(OpenCloseListener openCloseListener) {
+    @Override
+    public void setOpenCloseListener(OpenCloseListener openCloseListener) {
         mOpenCloseListener = openCloseListener;
     }
 
-    void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) {
+    @Override
+    public void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) {
         mTrackingStartedListener = trackingStartedListener;
     }
 
@@ -1465,7 +1556,7 @@
                 // so we should not add a padding for them
                 stackScrollerPadding = 0;
             } else {
-                stackScrollerPadding = mQsController.getUnlockedStackScrollerPadding();
+                stackScrollerPadding = mQsController.getHeaderHeight();
             }
         } else {
             stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
@@ -1479,13 +1570,18 @@
         mAnimateNextPositionUpdate = false;
     }
 
+    private boolean shouldAnimateKeyguardStatusViewAlignment() {
+        // Do not animate when transitioning from Gone->DreamingLockscreenHosted
+        return !mIsGoneToDreamingLockscreenHostedTransitionRunning;
+    }
+
     private void updateClockAppearance() {
         int userSwitcherPreferredY = mStatusBarHeaderHeightKeyguard;
         boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
         boolean shouldAnimateClockChange = mScreenOffAnimationController.shouldAnimateClockChange();
         mKeyguardStatusViewController.displayClock(computeDesiredClockSize(),
                 shouldAnimateClockChange);
-        updateKeyguardStatusViewAlignment(/* animate= */true);
+        updateKeyguardStatusViewAlignment(/* animate= */shouldAnimateKeyguardStatusViewAlignment());
         int userSwitcherHeight = mKeyguardQsUserSwitchController != null
                 ? mKeyguardQsUserSwitchController.getUserIconHeight() : 0;
         if (mKeyguardUserSwitcherController != null) {
@@ -1512,7 +1608,7 @@
                 userSwitcherPreferredY,
                 darkAmount, mOverStretchAmount,
                 bypassEnabled,
-                mQsController.getUnlockedStackScrollerPadding(),
+                mQsController.getHeaderHeight(),
                 mQsController.computeExpansionFraction(),
                 mDisplayTopInset,
                 mSplitShadeEnabled,
@@ -1522,8 +1618,13 @@
         mClockPositionAlgorithm.run(mClockPositionResult);
         mKeyguardStatusViewController.setLockscreenClockY(
                 mClockPositionAlgorithm.getExpandedPreferredClockY());
-        mKeyguardBottomAreaInteractor.setClockPosition(
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            mKeyguardInteractor.setClockPosition(
                 mClockPositionResult.clockX, mClockPositionResult.clockY);
+        } else {
+            mKeyguardBottomAreaInteractor.setClockPosition(
+                mClockPositionResult.clockX, mClockPositionResult.clockY);
+        }
         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
         boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
         mKeyguardStatusViewController.updatePosition(
@@ -1585,8 +1686,14 @@
 
     private void updateKeyguardStatusViewAlignment(boolean animate) {
         boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
+        ConstraintLayout layout;
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            layout = mKeyguardViewConfigurator.getKeyguardRootView();
+        } else {
+            layout = mNotificationContainerParent;
+        }
         mKeyguardStatusViewController.updateAlignment(
-                mNotificationContainerParent, mSplitShadeEnabled, shouldBeCentered, animate);
+                layout, mSplitShadeEnabled, shouldBeCentered, animate);
         mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
     }
 
@@ -1603,6 +1710,10 @@
             // overlap.
             return true;
         }
+        if (isActiveDreamLockscreenHosted()) {
+            // Dreaming hosted in lockscreen, no "visible" notifications. Safe to center the clock.
+            return true;
+        }
         if (mNotificationListContainer.hasPulsingNotifications()) {
             // Pulsing notification appears on the right. Move clock left to avoid overlap.
             return false;
@@ -1631,6 +1742,11 @@
         return mDozing && mDozeParameters.getAlwaysOn();
     }
 
+
+    private boolean isActiveDreamLockscreenHosted() {
+        return mKeyguardInteractor.isActiveDreamLockscreenHosted().getValue();
+    }
+
     private boolean hasVisibleNotifications() {
         return mNotificationStackScrollLayoutController
                 .getVisibleNotificationCount() != 0
@@ -1819,6 +1935,7 @@
 
             // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
             setClosing(true);
+            mUpdateFlingOnLayout = false;
             if (delayed) {
                 mNextCollapseSpeedUpFactor = speedUpFactor;
                 this.mView.postDelayed(mFlingCollapseRunnable, 120);
@@ -2014,6 +2131,7 @@
         }
         updateExpansionAndVisibility();
         mNotificationStackScrollLayoutController.setPanelFlinging(false);
+        mShadeLog.d("onFlingEnd called"); // TODO(b/277909752): remove log when bug is fixed
         // expandImmediate should be always reset at the end of animation
         mQsController.setExpandImmediate(false);
     }
@@ -2151,10 +2269,14 @@
     }
 
     int getFalsingThreshold() {
-        float factor = mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+        float factor = ShadeViewController.getFalsingThresholdFactor(getWakefulness());
         return (int) (mQsController.getFalsingThreshold() * factor);
     }
 
+    private WakefulnessModel getWakefulness() {
+        return mKeyguardInteractor.getWakefulnessModel().getValue();
+    }
+
     private void maybeAnimateBottomAreaAlpha() {
         mBottomAreaShadeAlphaAnimator.cancel();
         if (mBarState == StatusBarState.SHADE_LOCKED) {
@@ -2165,6 +2287,15 @@
         }
     }
 
+    private void setKeyguardVisibility(int statusBarState, boolean goingToFullShade) {
+        mKeyguardInteractor.setKeyguardRootVisibility(
+            statusBarState,
+            goingToFullShade,
+            mIsOcclusionTransitionRunning
+        );
+    }
+
+    @Deprecated
     private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
         mKeyguardBottomArea.animate().cancel();
         if (goingToFullShade) {
@@ -2173,8 +2304,7 @@
                     mKeyguardStateController.getShortenedFadingAwayDuration()).setInterpolator(
                     Interpolators.ALPHA_OUT).withEndAction(
                     mAnimateKeyguardBottomAreaInvisibleEndRunnable).start();
-        } else if (statusBarState == KEYGUARD
-                || statusBarState == StatusBarState.SHADE_LOCKED) {
+        } else if (statusBarState == KEYGUARD || statusBarState == StatusBarState.SHADE_LOCKED) {
             mKeyguardBottomArea.setVisibility(View.VISIBLE);
             if (!mIsOcclusionTransitionRunning) {
                 mKeyguardBottomArea.setAlpha(1f);
@@ -2432,6 +2562,7 @@
         boolean isExpanded = !isFullyCollapsed() || mExpectingSynthesizedDown;
         if (mPanelExpanded != isExpanded) {
             mPanelExpanded = isExpanded;
+            updateSystemUiStateFlags();
             mShadeExpansionStateManager.onShadeExpansionFullyChanged(isExpanded);
             if (!isExpanded) {
                 mQsController.closeQsCustomizer();
@@ -2439,6 +2570,7 @@
         }
     }
 
+    @Override
     public boolean isPanelExpanded() {
         return mPanelExpanded;
     }
@@ -2466,7 +2598,8 @@
             alpha = getFadeoutAlpha();
         }
         if (mBarState == KEYGUARD && !mHintAnimationRunning
-                && !mKeyguardBypassController.getBypassEnabled()) {
+                && !mKeyguardBypassController.getBypassEnabled()
+                && !mQsController.getFullyExpanded()) {
             alpha *= mClockPositionResult.clockAlpha;
         }
         mNotificationStackScrollLayoutController.setAlpha(alpha);
@@ -2507,7 +2640,11 @@
                 getExpandedFraction());
         float alpha = Math.min(expansionAlpha, 1 - mQsController.computeExpansionFraction());
         alpha *= mBottomAreaShadeAlpha;
-        mKeyguardBottomAreaInteractor.setAlpha(alpha);
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            mKeyguardInteractor.setAlpha(alpha);
+        } else {
+            mKeyguardBottomAreaInteractor.setAlpha(alpha);
+        }
         mLockIconViewController.setAlpha(alpha);
     }
 
@@ -2530,6 +2667,11 @@
             setListening(true);
         }
         if (mBarState != SHADE) {
+            // TODO(b/277909752): remove below logs when bug is fixed
+            mShadeLog.d("onExpandingFinished called");
+            if (mSplitShadeEnabled && !mQsController.getExpanded()) {
+                mShadeLog.d("onExpandingFinished called before QS got expanded");
+            }
             // updating qsExpandImmediate is done in onPanelStateChanged for unlocked shade but
             // on keyguard panel state is always OPEN so we need to have that extra update
             mQsController.setExpandImmediate(false);
@@ -2574,7 +2716,7 @@
                                         this);
                                 return;
                             }
-                            if (mCentralSurfaces.getNotificationShadeWindowView()
+                            if (mNotificationShadeWindowController.getWindowRootView()
                                     .isVisibleToUser()) {
                                 mView.getViewTreeObserver().removeOnGlobalLayoutListener(
                                         this);
@@ -2627,12 +2769,16 @@
         }
 
         if (!mStatusBarStateController.isDozing()) {
-            mVibratorHelper.vibrate(
-                    Process.myUid(),
-                    mView.getContext().getPackageName(),
-                    ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT,
-                    "falsing-additional-tap-required",
-                    TOUCH_VIBRATION_ATTRIBUTES);
+            if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+                mVibratorHelper.performHapticFeedback(mView, HapticFeedbackConstants.REJECT);
+            } else {
+                mVibratorHelper.vibrate(
+                        Process.myUid(),
+                        mView.getContext().getPackageName(),
+                        ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT,
+                        "falsing-additional-tap-required",
+                        TOUCH_VIBRATION_ATTRIBUTES);
+            }
         }
     }
 
@@ -2655,6 +2801,8 @@
     private void onTrackingStopped(boolean expand) {
         mFalsingCollector.onTrackingStopped();
         mTracking = false;
+        maybeStopTrackingExpansionFromStatusBar(expand);
+
         updateExpansionAndVisibility();
         if (expand) {
             mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */,
@@ -2760,7 +2908,11 @@
     }
 
     private void updateDozingVisibilities(boolean animate) {
-        mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            mKeyguardInteractor.setAnimateDozingTransitions(animate);
+        } else {
+            mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
+        }
         if (!mDozing && animate) {
             mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
         }
@@ -2768,7 +2920,9 @@
 
     @Override
     public void onScreenTurningOn() {
-        mKeyguardStatusViewController.dozeTimeTick();
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            mKeyguardStatusViewController.dozeTimeTick();
+        }
     }
 
     private void onMiddleClicked() {
@@ -2896,9 +3050,7 @@
         float scrimMinFraction;
         if (mSplitShadeEnabled) {
             boolean highHun = mHeadsUpStartHeight * 2.5
-                    >
-                    (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
-                    ? mSplitShadeFullTransitionDistance : mSplitShadeScrimTransitionDistance);
+                    > mSplitShadeFullTransitionDistance;
             // if HUN height is higher than 40% of predefined transition distance, it means HUN
             // is too high for regular transition. In that case we need to calculate transition
             // distance - here we take scrim transition distance as equal to shade transition
@@ -2966,7 +3118,11 @@
         mDozing = dozing;
         // TODO (b/) make listeners for this
         mNotificationStackScrollLayoutController.setDozing(mDozing, animate);
-        mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            mKeyguardInteractor.setAnimateDozingTransitions(animate);
+        } else {
+            mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
+        }
         mKeyguardStatusBarViewController.setDozing(mDozing);
         mQsController.setDozing(mDozing);
 
@@ -3016,10 +3172,11 @@
         }
     }
 
-    @Override
     public void dozeTimeTick() {
         mLockIconViewController.dozeTimeTick();
-        mKeyguardStatusViewController.dozeTimeTick();
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            mKeyguardStatusViewController.dozeTimeTick();
+        }
         if (mInterpolatedDarkAmount > 0) {
             positionClockAndNotifications();
         }
@@ -3029,12 +3186,6 @@
         mKeyguardStatusViewController.setStatusAccessibilityImportance(mode);
     }
 
-    //TODO(b/254875405): this should be removed.
-    @Override
-    public KeyguardBottomAreaView getKeyguardBottomAreaView() {
-        return mKeyguardBottomArea;
-    }
-
     @Override
     public void applyLaunchAnimationProgress(float linearProgress) {
         boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
@@ -3271,7 +3422,6 @@
         ipw.print("mPanelFlingOvershootAmount="); ipw.println(mPanelFlingOvershootAmount);
         ipw.print("mLastGesturedOverExpansion="); ipw.println(mLastGesturedOverExpansion);
         ipw.print("mIsSpringBackAnimation="); ipw.println(mIsSpringBackAnimation);
-        ipw.print("mSplitShadeEnabled="); ipw.println(mSplitShadeEnabled);
         ipw.print("mHintDistance="); ipw.println(mHintDistance);
         ipw.print("mInitialOffsetOnTouch="); ipw.println(mInitialOffsetOnTouch);
         ipw.print("mCollapsedAndHeadsUpOnDown="); ipw.println(mCollapsedAndHeadsUpOnDown);
@@ -3304,30 +3454,14 @@
         ipw.print("mGestureWaitForTouchSlop="); ipw.println(mGestureWaitForTouchSlop);
         ipw.print("mIgnoreXTouchSlop="); ipw.println(mIgnoreXTouchSlop);
         ipw.print("mExpandLatencyTracking="); ipw.println(mExpandLatencyTracking);
-        ipw.print("mExpandLatencyTracking="); ipw.println(mExpandLatencyTracking);
         ipw.println("gestureExclusionRect:" + calculateGestureExclusionRect());
+        Trace.beginSection("Table<DownEvents>");
         new DumpsysTableLogger(
                 TAG,
                 NPVCDownEventState.TABLE_HEADERS,
                 mLastDownEvents.toList()
         ).printTableData(ipw);
-    }
-
-    private final class ShadeNotificationPresenterImpl implements ShadeNotificationPresenter{
-        @Override
-        public RemoteInputController.Delegate createRemoteInputDelegate() {
-            return mNotificationStackScrollLayoutController.createDelegate();
-        }
-
-        @Override
-        public boolean hasPulsingNotifications() {
-            return mNotificationListContainer.hasPulsingNotifications();
-        }
-    }
-
-    @Override
-    public ShadeNotificationPresenter getShadeNotificationPresenter() {
-        return mShadeNotificationPresenter;
+        Trace.endSection();
     }
 
     @Override
@@ -3375,11 +3509,13 @@
         ViewGroupFadeHelper.reset(mView);
     }
 
-    void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+    @Override
+    public void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
         mView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
     }
 
-    void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+    @Override
+    public void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
         mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
     }
 
@@ -3438,9 +3574,8 @@
             Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
                     + isFullyExpanded() + " inQs=" + mQsController.getExpanded());
         }
-        boolean isPanelVisible = mCentralSurfaces != null && mCentralSurfaces.isPanelExpanded();
         mSysUiState
-                .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE, isPanelVisible)
+                .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE, mPanelExpanded)
                 .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
                         isFullyExpanded() && !mQsController.getExpanded())
                 .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
@@ -3521,7 +3656,14 @@
     private void maybeVibrateOnOpening(boolean openingWithTouch) {
         if (mVibrateOnOpening && mBarState != KEYGUARD && mBarState != SHADE_LOCKED) {
             if (!openingWithTouch || !mHasVibratedOnOpen) {
-                mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+                if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+                    mVibratorHelper.performHapticFeedback(
+                            mView,
+                            HapticFeedbackConstants.GESTURE_START
+                    );
+                } else {
+                    mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+                }
                 mHasVibratedOnOpen = true;
                 mShadeLog.v("Vibrating on opening, mHasVibratedOnOpen=true");
             }
@@ -3600,8 +3742,10 @@
                 mShadeLog.logEndMotionEvent("endMotionEvent: flingExpands", forceCancel, expand);
             }
 
-            mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
-                    mCentralSurfaces.isWakeUpComingFromTouch());
+            mDozeLog.traceFling(
+                    expand,
+                    mTouchAboveFalsingThreshold,
+                    /* screenOnFromTouch=*/ getWakefulness().isDeviceInteractiveFromTapOrGesture());
             // Log collapse gesture if on lock screen.
             if (!expand && onKeyguard) {
                 float displayDensity = mCentralSurfaces.getDisplayDensity();
@@ -3848,8 +3992,8 @@
         return !isFullyCollapsed() && !mTracking && !mClosing;
     }
 
-    /** Collapses the shade instantly without animation. */
-    void instantCollapse() {
+    @Override
+    public void instantCollapse() {
         abortAnimations();
         setExpandedFraction(0f);
         if (mExpanding) {
@@ -3900,20 +4044,37 @@
         animator.start();
         setAnimator(animator);
 
-        final List<ViewPropertyAnimator> indicationAnimators =
-                mKeyguardBottomArea.getIndicationAreaAnimators();
-        for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) {
-            indicationAreaAnimator
+
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            final ViewPropertyAnimator mKeyguardRootViewAnimator = mKeyguardRootView.animate();
+            mKeyguardRootViewAnimator
                     .translationY(-mHintDistance)
                     .setDuration(250)
                     .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                    .withEndAction(() -> indicationAreaAnimator
+                    .withEndAction(() -> mKeyguardRootViewAnimator
                             .translationY(0)
                             .setDuration(450)
                             .setInterpolator(mBounceInterpolator)
                             .start())
                     .start();
+        } else {
+            final List<ViewPropertyAnimator> indicationAnimators =
+                    mKeyguardBottomArea.getIndicationAreaAnimators();
+
+            for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) {
+                indicationAreaAnimator
+                    .translationY(-mHintDistance)
+                    .setDuration(250)
+                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                    .withEndAction(() -> indicationAreaAnimator
+                        .translationY(0)
+                        .setDuration(450)
+                        .setInterpolator(mBounceInterpolator)
+                        .start())
+                    .start();
+            }
         }
+
     }
 
     private void setAnimator(ValueAnimator animator) {
@@ -4022,8 +4183,8 @@
         mFixedDuration = NO_FIXED_DURATION;
     }
 
-    /** */
-    boolean postToView(Runnable action) {
+    @Override
+    public boolean postToView(Runnable action) {
         return mView.post(action);
     }
 
@@ -4038,6 +4199,42 @@
     }
 
     @Override
+    public void startTrackingExpansionFromStatusBar() {
+        mIsTrackingExpansionFromStatusBar = true;
+        InteractionJankMonitorWrapper.begin(
+                mView, InteractionJankMonitorWrapper.CUJ_SHADE_EXPAND_FROM_STATUS_BAR);
+    }
+
+    /**
+     * Stops tracking an expansion that originated from the status bar (if we had started tracking
+     * it).
+     *
+     * @param expand the expand boolean passed to {@link #onTrackingStopped(boolean)}.
+     */
+    private void maybeStopTrackingExpansionFromStatusBar(boolean expand) {
+        if (!mIsTrackingExpansionFromStatusBar) {
+            return;
+        }
+        mIsTrackingExpansionFromStatusBar = false;
+
+        // Determine whether the shade actually expanded due to the status bar touch:
+        // - If the user just taps on the status bar, then #isExpanded is false but
+        // #onTrackingStopped is called with `true`.
+        // - If the user drags down on the status bar but doesn't drag down far enough, then
+        // #onTrackingStopped is called with `false` but #isExpanded is true.
+        // So, we need *both* #onTrackingStopped called with `true` *and* #isExpanded to be true in
+        // order to confirm that the shade successfully opened.
+        boolean shadeExpansionFromStatusBarSucceeded = expand && isExpanded();
+        if (shadeExpansionFromStatusBarSucceeded) {
+            InteractionJankMonitorWrapper.end(
+                    InteractionJankMonitorWrapper.CUJ_SHADE_EXPAND_FROM_STATUS_BAR);
+        } else {
+            InteractionJankMonitorWrapper.cancel(
+                    InteractionJankMonitorWrapper.CUJ_SHADE_EXPAND_FROM_STATUS_BAR);
+        }
+    }
+
+    @Override
     public void updateTouchableRegion() {
         //A layout will ensure that onComputeInternalInsets will be called and after that we can
         // resize the layout. Make sure that the window stays small for one frame until the
@@ -4318,7 +4515,11 @@
                     goingToFullShade,
                     mBarState);
 
-            setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
+            if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+                setKeyguardVisibility(statusBarState, goingToFullShade);
+            } else {
+                setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
+            }
 
             // TODO: maybe add a listener for barstate
             mBarState = statusBarState;
@@ -4523,6 +4724,7 @@
     }
 
     private void onPanelStateChanged(@PanelState int state) {
+        mShadeLog.logPanelStateChanged(state);
         mQsController.updateExpansionEnabledAmbient();
 
         if (state == STATE_OPEN && mCurrentPanelState != state) {
@@ -4549,13 +4751,27 @@
         mCurrentPanelState = state;
     }
 
+    private Consumer<Float> setDreamLockscreenTransitionAlpha(
+            NotificationStackScrollLayoutController stackScroller) {
+        return (Float alpha) -> {
+            // Also animate the status bar's alpha during transitions between the lockscreen and
+            // dreams.
+            mKeyguardStatusBarViewController.setAlpha(alpha);
+            setTransitionAlpha(stackScroller).accept(alpha);
+        };
+    }
+
     private Consumer<Float> setTransitionAlpha(
             NotificationStackScrollLayoutController stackScroller) {
         return (Float alpha) -> {
             mKeyguardStatusViewController.setAlpha(alpha);
             stackScroller.setAlpha(alpha);
 
-            mKeyguardBottomAreaInteractor.setAlpha(alpha);
+            if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+                mKeyguardInteractor.setAlpha(alpha);
+            } else {
+                mKeyguardBottomAreaInteractor.setAlpha(alpha);
+            }
             mLockIconViewController.setAlpha(alpha);
 
             if (mKeyguardQsUserSwitchController != null) {
@@ -4668,6 +4884,9 @@
             final float x = event.getX(pointerIndex);
             final float y = event.getY(pointerIndex);
             boolean canCollapsePanel = canCollapsePanelOnTouch();
+            final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
+                    mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
+                    mTrackpadGestureFeaturesEnabled, event);
 
             switch (event.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN:
@@ -4702,7 +4921,7 @@
                     addMovement(event);
                     break;
                 case MotionEvent.ACTION_POINTER_UP:
-                    if (isTrackpadMotionEvent(event)) {
+                    if (isTrackpadTwoOrThreeFingerSwipe) {
                         break;
                     }
                     final int upPointer = event.getPointerId(event.getActionIndex());
@@ -4718,7 +4937,7 @@
                     mShadeLog.logMotionEventStatusBarState(event,
                             mStatusBarStateController.getState(),
                             "onInterceptTouchEvent: pointer down action");
-                    if (!isTrackpadMotionEvent(event)
+                    if (!isTrackpadTwoOrThreeFingerSwipe
                             && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                         mMotionAborted = true;
                         mVelocityTracker.clear();
@@ -4868,9 +5087,13 @@
                 return false;
             }
 
+            final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
+                    mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
+                    mTrackpadGestureFeaturesEnabled, event);
+
             // On expanding, single mouse click expands the panel instead of dragging.
             if (isFullyCollapsed() && (event.isFromSource(InputDevice.SOURCE_MOUSE)
-                    && !isTrackpadMotionEvent(event))) {
+                    && !isTrackpadTwoOrThreeFingerSwipe)) {
                 if (event.getAction() == MotionEvent.ACTION_UP) {
                     expand(true /* animate */);
                 }
@@ -4932,7 +5155,7 @@
                     break;
 
                 case MotionEvent.ACTION_POINTER_UP:
-                    if (isTrackpadMotionEvent(event)) {
+                    if (isTrackpadTwoOrThreeFingerSwipe) {
                         break;
                     }
                     final int upPointer = event.getPointerId(event.getActionIndex());
@@ -4951,7 +5174,7 @@
                     mShadeLog.logMotionEventStatusBarState(event,
                             mStatusBarStateController.getState(),
                             "handleTouch: pointer down action");
-                    if (!isTrackpadMotionEvent(event)
+                    if (!isTrackpadTwoOrThreeFingerSwipe
                             && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                         mMotionAborted = true;
                         endMotionEvent(event, x, y, true /* forceCancel */);
@@ -5025,12 +5248,6 @@
             }
             return !mGestureWaitForTouchSlop || mTracking;
         }
-
-        private boolean isTrackpadMotionEvent(MotionEvent ev) {
-            return mTrackpadGestureFeaturesEnabled && (
-                    ev.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
-                            || ev.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE);
-        }
     }
 
     private final class HeadsUpNotificationViewControllerImpl implements
@@ -5081,18 +5298,5 @@
             return super.performAccessibilityAction(host, action, args);
         }
     }
-
-    /** Listens for when touch tracking begins. */
-    interface TrackingStartedListener {
-        void onTrackingStarted();
-    }
-
-    /** Listens for when shade begins opening of finishes closing. */
-    interface OpenCloseListener {
-        /** Called when the shade finishes closing. */
-        void onClosingFinished();
-        /** Called when the shade starts opening. */
-        void onOpenStarted();
-    }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 0c800d4..d2b62eb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -103,7 +103,7 @@
     private final KeyguardViewMediator mKeyguardViewMediator;
     private final KeyguardBypassController mKeyguardBypassController;
     private final AuthController mAuthController;
-    private ViewGroup mNotificationShadeView;
+    private ViewGroup mWindowRootView;
     private LayoutParams mLp;
     private boolean mHasTopUi;
     private boolean mHasTopUiChanged;
@@ -262,7 +262,7 @@
         mLp.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
         mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 
-        mWindowManager.addView(mNotificationShadeView, mLp);
+        mWindowManager.addView(mWindowRootView, mLp);
 
         mLpChanged.copyFrom(mLp);
         onThemeChanged();
@@ -274,13 +274,13 @@
     }
 
     @Override
-    public void setNotificationShadeView(ViewGroup view) {
-        mNotificationShadeView = view;
+    public void setWindowRootView(ViewGroup view) {
+        mWindowRootView = view;
     }
 
     @Override
-    public ViewGroup getNotificationShadeView() {
-        return mNotificationShadeView;
+    public ViewGroup getWindowRootView() {
+        return mWindowRootView;
     }
 
     @Override
@@ -289,7 +289,7 @@
     }
 
     private void setKeyguardDark(boolean dark) {
-        int vis = mNotificationShadeView.getSystemUiVisibility();
+        int vis = mWindowRootView.getSystemUiVisibility();
         if (dark) {
             vis = vis | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
             vis = vis | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
@@ -297,7 +297,7 @@
             vis = vis & ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
             vis = vis & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
         }
-        mNotificationShadeView.setSystemUiVisibility(vis);
+        mWindowRootView.setSystemUiVisibility(vis);
     }
 
     private void applyKeyguardFlags(NotificationShadeWindowState state) {
@@ -413,11 +413,11 @@
             visible = true;
             mLogger.d("Visibility forced to be true");
         }
-        if (mNotificationShadeView != null) {
+        if (mWindowRootView != null) {
             if (visible) {
-                mNotificationShadeView.setVisibility(View.VISIBLE);
+                mWindowRootView.setVisibility(View.VISIBLE);
             } else {
-                mNotificationShadeView.setVisibility(View.INVISIBLE);
+                mWindowRootView.setVisibility(View.INVISIBLE);
             }
         }
     }
@@ -439,10 +439,10 @@
 
     private void applyFitsSystemWindows(NotificationShadeWindowState state) {
         boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
-        if (mNotificationShadeView != null
-                && mNotificationShadeView.getFitsSystemWindows() != fitsSystemWindows) {
-            mNotificationShadeView.setFitsSystemWindows(fitsSystemWindows);
-            mNotificationShadeView.requestApplyInsets();
+        if (mWindowRootView != null
+                && mWindowRootView.getFitsSystemWindows() != fitsSystemWindows) {
+            mWindowRootView.setFitsSystemWindows(fitsSystemWindows);
+            mWindowRootView.requestApplyInsets();
         }
     }
 
@@ -480,9 +480,8 @@
 
     private void applyWindowLayoutParams() {
         if (mDeferWindowLayoutParams == 0 && mLp != null && mLp.copyFrom(mLpChanged) != 0) {
-            mLogger.logApplyingWindowLayoutParams(mLp);
             Trace.beginSection("updateViewLayout");
-            mWindowManager.updateViewLayout(mNotificationShadeView, mLp);
+            mWindowManager.updateViewLayout(mWindowRootView, mLp);
             Trace.endSection();
         }
     }
@@ -544,7 +543,6 @@
                 state.forceUserActivity,
                 state.launchingActivityFromNotification,
                 state.mediaBackdropShowing,
-                state.wallpaperSupportsAmbientMode,
                 state.windowNotTouchable,
                 state.componentsForcingTopUi,
                 state.forceOpenTokens,
@@ -608,7 +606,7 @@
         try {
             final IWindowSession session = WindowManagerGlobal.getWindowSession();
             session.updateTapExcludeRegion(
-                    IWindow.Stub.asInterface(getNotificationShadeView().getWindowToken()),
+                    IWindow.Stub.asInterface(getWindowRootView().getWindowToken()),
                     region);
         } catch (RemoteException e) {
             Log.e(TAG, "could not update the tap exclusion region:" + e);
@@ -735,12 +733,6 @@
         apply(mCurrentState);
     }
 
-    @Override
-    public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
-        mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode;
-        apply(mCurrentState);
-    }
-
     /**
      * @param state The {@link StatusBarStateController} of the status bar.
      */
@@ -847,14 +839,18 @@
         pw.println("  mKeyguardPreferredRefreshRate=" + mKeyguardPreferredRefreshRate);
         pw.println("  mDeferWindowLayoutParams=" + mDeferWindowLayoutParams);
         pw.println(mCurrentState);
-        if (mNotificationShadeView != null && mNotificationShadeView.getViewRootImpl() != null) {
-            mNotificationShadeView.getViewRootImpl().dump("  ", pw);
+        if (mWindowRootView != null && mWindowRootView.getViewRootImpl() != null) {
+            Trace.beginSection("mWindowRootView.dump()");
+            mWindowRootView.getViewRootImpl().dump("  ", pw);
+            Trace.endSection();
         }
+        Trace.beginSection("Table<State>");
         new DumpsysTableLogger(
                 TAG,
                 NotificationShadeWindowState.TABLE_HEADERS,
                 mStateBuffer.toList()
         ).printTableData(pw);
+        Trace.endSection();
     }
 
     @Override
@@ -864,7 +860,7 @@
 
     @Override
     public void onThemeChanged() {
-        if (mNotificationShadeView == null) {
+        if (mWindowRootView == null) {
             return;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index 7812f07..e3010ca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -46,7 +46,6 @@
     @JvmField var forceUserActivity: Boolean = false,
     @JvmField var launchingActivityFromNotification: Boolean = false,
     @JvmField var mediaBackdropShowing: Boolean = false,
-    @JvmField var wallpaperSupportsAmbientMode: Boolean = false,
     @JvmField var windowNotTouchable: Boolean = false,
     @JvmField var componentsForcingTopUi: MutableSet<String> = mutableSetOf(),
     @JvmField var forceOpenTokens: MutableSet<Any> = mutableSetOf(),
@@ -84,7 +83,6 @@
             forceUserActivity.toString(),
             launchingActivityFromNotification.toString(),
             mediaBackdropShowing.toString(),
-            wallpaperSupportsAmbientMode.toString(),
             windowNotTouchable.toString(),
             componentsForcingTopUi.toString(),
             forceOpenTokens.toString(),
@@ -124,7 +122,6 @@
             forceUserActivity: Boolean,
             launchingActivity: Boolean,
             backdropShowing: Boolean,
-            wallpaperSupportsAmbientMode: Boolean,
             notTouchable: Boolean,
             componentsForcingTopUi: MutableSet<String>,
             forceOpenTokens: MutableSet<Any>,
@@ -153,7 +150,6 @@
                 this.forceUserActivity = forceUserActivity
                 this.launchingActivityFromNotification = launchingActivity
                 this.mediaBackdropShowing = backdropShowing
-                this.wallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode
                 this.windowNotTouchable = notTouchable
                 this.componentsForcingTopUi.clear()
                 this.componentsForcingTopUi.addAll(componentsForcingTopUi)
@@ -174,7 +170,7 @@
          * @see [NotificationShadeWindowState.asStringList]
          */
         fun toList(): List<Row> {
-            return buffer.asSequence().map { it.asStringList }.toList()
+            return buffer.map { it.asStringList }
         }
     }
 
@@ -200,7 +196,6 @@
                 "forceUserActivity",
                 "launchingActivity",
                 "backdropShowing",
-                "wallpaperSupportsAmbientMode",
                 "notTouchable",
                 "componentsForcingTopUi",
                 "forceOpenTokens",
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index d75190e..f9b4e67 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -17,19 +17,15 @@
 package com.android.systemui.shade;
 
 import static android.os.Trace.TRACE_TAG_APP;
-import static android.view.WindowInsets.Type.systemBars;
 
 import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
 
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
 import android.annotation.LayoutRes;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.content.res.TypedArray;
 import android.graphics.Canvas;
-import android.graphics.Insets;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -37,9 +33,7 @@
 import android.os.Bundle;
 import android.os.Trace;
 import android.util.AttributeSet;
-import android.util.Pair;
 import android.view.ActionMode;
-import android.view.DisplayCutout;
 import android.view.InputQueue;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -51,24 +45,20 @@
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.Window;
-import android.view.WindowInsets;
 import android.view.WindowInsetsController;
-import android.widget.FrameLayout;
 
 import com.android.internal.view.FloatingActionMode;
 import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
-import com.android.systemui.R;
-import com.android.systemui.compose.ComposeFacade;
+import com.android.systemui.scene.ui.view.WindowRootView;
 
 /**
- * Combined keyguard and notification panel view. Also holding backdrop and scrims.
+ * Combined keyguard and notification panel view. Also holding backdrop and scrims. This view can
+ * serve as the root view of the main SysUI window, but because other views can also serve that
+ * purpose, users of this class cannot assume it is the root.
  */
-public class NotificationShadeWindowView extends FrameLayout {
+public class NotificationShadeWindowView extends WindowRootView {
     public static final String TAG = "NotificationShadeWindowView";
 
-    private int mRightInset = 0;
-    private int mLeftInset = 0;
-
     // Implements the floating action mode for TextView's Cut/Copy/Past menu. Normally provided by
     // DecorView, but since this is a special window we have to roll our own.
     private View mFloatingActionModeOriginatingView;
@@ -77,7 +67,6 @@
     private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
 
     private InteractionEventHandler mInteractionEventHandler;
-    private LayoutInsetsController mLayoutInsetProvider;
 
     public NotificationShadeWindowView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -85,79 +74,9 @@
     }
 
     @Override
-    public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) {
-        final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars());
-        if (getFitsSystemWindows()) {
-            boolean paddingChanged = insets.top != getPaddingTop()
-                    || insets.bottom != getPaddingBottom();
-
-            // Drop top inset, and pass through bottom inset.
-            if (paddingChanged) {
-                setPadding(0, 0, 0, 0);
-            }
-        } else {
-            boolean changed = getPaddingLeft() != 0
-                    || getPaddingRight() != 0
-                    || getPaddingTop() != 0
-                    || getPaddingBottom() != 0;
-            if (changed) {
-                setPadding(0, 0, 0, 0);
-            }
-        }
-
-        mLeftInset = 0;
-        mRightInset = 0;
-        DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
-        Pair<Integer, Integer> pairInsets = mLayoutInsetProvider
-                .getinsets(windowInsets, displayCutout);
-        mLeftInset = pairInsets.first;
-        mRightInset = pairInsets.second;
-        applyMargins();
-        return windowInsets;
-    }
-
-    private void applyMargins() {
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            View child = getChildAt(i);
-            if (child.getLayoutParams() instanceof LayoutParams) {
-                LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                if (!lp.ignoreRightInset
-                        && (lp.rightMargin != mRightInset || lp.leftMargin != mLeftInset)) {
-                    lp.rightMargin = mRightInset;
-                    lp.leftMargin = mLeftInset;
-                    child.requestLayout();
-                }
-            }
-        }
-    }
-
-    @Override
-    public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
-        return new LayoutParams(getContext(), attrs);
-    }
-
-    @Override
-    protected FrameLayout.LayoutParams generateDefaultLayoutParams() {
-        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
-    }
-
-    @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         setWillNotDraw(!DEBUG);
-
-        if (ComposeFacade.INSTANCE.isComposeAvailable()) {
-            ComposeFacade.INSTANCE.composeInitializer().onAttachedToWindow(this);
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        if (ComposeFacade.INSTANCE.isComposeAvailable()) {
-            ComposeFacade.INSTANCE.composeInitializer().onDetachedFromWindow(this);
-        }
     }
 
     @Override
@@ -182,16 +101,14 @@
         mInteractionEventHandler = listener;
     }
 
-    protected void setLayoutInsetsController(LayoutInsetsController provider) {
-        mLayoutInsetProvider = provider;
-    }
-
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev);
 
         result = result != null ? result : super.dispatchTouchEvent(ev);
 
+        TouchLogger.logDispatchTouch(TAG, ev, result);
+
         mInteractionEventHandler.dispatchTouchEventComplete();
 
         return result;
@@ -237,24 +154,6 @@
         }
     }
 
-    private static class LayoutParams extends FrameLayout.LayoutParams {
-
-        public boolean ignoreRightInset;
-
-        LayoutParams(int width, int height) {
-            super(width, height);
-        }
-
-        LayoutParams(Context c, AttributeSet attrs) {
-            super(c, attrs);
-
-            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout);
-            ignoreRightInset = a.getBoolean(
-                    R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false);
-            a.recycle();
-        }
-    }
-
     @Override
     public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback,
             int type) {
@@ -367,18 +266,6 @@
         }
     }
 
-    /**
-     * Controller responsible for calculating insets for the shade window.
-     */
-    public interface LayoutInsetsController {
-
-        /**
-         * Update the insets and calculate them accordingly.
-         */
-        Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets,
-                @Nullable DisplayCutout displayCutout);
-    }
-
     interface InteractionEventHandler {
         /**
          * Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index c7a7d97..832a25b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade;
 
+import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
 import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
@@ -30,17 +31,19 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewStub;
-
-import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.AuthKeyguardMessageArea;
+import com.android.keyguard.KeyguardMessageAreaController;
 import com.android.keyguard.LockIconViewController;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.R;
+import com.android.systemui.back.domain.interactor.BackActionInteractor;
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
+import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder;
+import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.compose.ComposeFacade;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -48,12 +51,9 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
-import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
-import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor;
-import com.android.systemui.multishade.ui.view.MultiShadeView;
+import com.android.systemui.log.BouncerLogger;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -61,13 +61,13 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.util.time.SystemClock;
@@ -77,12 +77,11 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
-import javax.inject.Provider;
 
 /**
  * Controller for {@link NotificationShadeWindowView}.
  */
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
 public class NotificationShadeWindowViewController {
     private static final String TAG = "NotifShadeWindowVC";
     private final FalsingCollector mFalsingCollector;
@@ -92,14 +91,18 @@
     private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     private final LockIconViewController mLockIconViewController;
+    private final ShadeLogger mShadeLogger;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final StatusBarWindowStateController mStatusBarWindowStateController;
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final AmbientState mAmbientState;
     private final PulsingGestureListener mPulsingGestureListener;
+    private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
     private final NotificationInsetsController mNotificationInsetsController;
     private final boolean mIsTrackpadCommonEnabled;
+    private final FeatureFlags mFeatureFlags;
     private GestureDetector mPulsingWakeupGestureHandler;
+    private GestureDetector mDreamingWakeupGestureHandler;
     private View mBrightnessMirror;
     private boolean mTouchActive;
     private boolean mTouchCancelled;
@@ -108,6 +111,8 @@
     private NotificationStackScrollLayout mStackScrollLayout;
     private PhoneStatusBarViewController mStatusBarViewController;
     private final CentralSurfaces mService;
+    private final BackActionInteractor mBackActionInteractor;
+    private final PowerInteractor mPowerInteractor;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private DragDownHelper mDragDownHelper;
     private boolean mExpandingBelowNotch;
@@ -124,7 +129,6 @@
                     step.getTransitionState() == TransitionState.RUNNING;
             };
     private final SystemClock mClock;
-    private final @Nullable MultiShadeMotionEventInteractor mMultiShadeMotionEventInteractor;
 
     @Inject
     public NotificationShadeWindowViewController(
@@ -141,20 +145,26 @@
             StatusBarWindowStateController statusBarWindowStateController,
             LockIconViewController lockIconViewController,
             CentralSurfaces centralSurfaces,
+            BackActionInteractor backActionInteractor,
+            PowerInteractor powerInteractor,
             NotificationShadeWindowController controller,
             Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             NotificationInsetsController notificationInsetsController,
             AmbientState ambientState,
+            ShadeLogger shadeLogger,
             PulsingGestureListener pulsingGestureListener,
+            LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener,
             KeyguardBouncerViewModel keyguardBouncerViewModel,
             KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
+            KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
+            NotificationExpansionRepository notificationExpansionRepository,
             FeatureFlags featureFlags,
-            Provider<MultiShadeInteractor> multiShadeInteractorProvider,
             SystemClock clock,
-            Provider<MultiShadeMotionEventInteractor> multiShadeMotionEventInteractorProvider) {
+            BouncerMessageInteractor bouncerMessageInteractor,
+            BouncerLogger bouncerLogger) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
         mStatusBarStateController = statusBarStateController;
@@ -167,14 +177,19 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mStatusBarWindowStateController = statusBarWindowStateController;
         mLockIconViewController = lockIconViewController;
+        mBackActionInteractor = backActionInteractor;
+        mShadeLogger = shadeLogger;
         mLockIconViewController.init();
         mService = centralSurfaces;
+        mPowerInteractor = powerInteractor;
         mNotificationShadeWindowController = controller;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mAmbientState = ambientState;
         mPulsingGestureListener = pulsingGestureListener;
+        mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
         mNotificationInsetsController = notificationInsetsController;
         mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
+        mFeatureFlags = featureFlags;
 
         // This view is not part of the newly inflated expanded status bar.
         mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -183,10 +198,18 @@
                 mView.findViewById(R.id.keyguard_bouncer_container),
                 keyguardBouncerViewModel,
                 primaryBouncerToGoneTransitionViewModel,
-                keyguardBouncerComponentFactory);
+                keyguardBouncerComponentFactory,
+                messageAreaControllerFactory,
+                bouncerMessageInteractor,
+                bouncerLogger,
+                featureFlags);
 
         collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
                 mLockscreenToDreamingTransition);
+        collectFlow(
+                mView,
+                notificationExpansionRepository.isExpandAnimationRunning(),
+                this::setExpandAnimationRunning);
 
         mClock = clock;
         if (featureFlags.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) {
@@ -194,17 +217,6 @@
                     progressProvider -> progressProvider.addCallback(
                             mDisableSubpixelTextTransitionListener));
         }
-        if (ComposeFacade.INSTANCE.isComposeAvailable()
-                && featureFlags.isEnabled(Flags.DUAL_SHADE)) {
-            mMultiShadeMotionEventInteractor = multiShadeMotionEventInteractorProvider.get();
-            final ViewStub multiShadeViewStub = mView.findViewById(R.id.multi_shade_stub);
-            if (multiShadeViewStub != null) {
-                final MultiShadeView multiShadeView = (MultiShadeView) multiShadeViewStub.inflate();
-                multiShadeView.init(multiShadeInteractorProvider.get(), clock);
-            }
-        } else {
-            mMultiShadeMotionEventInteractor = null;
-        }
     }
 
     /**
@@ -214,19 +226,29 @@
         return mView.findViewById(R.id.keyguard_message_area);
     }
 
+    private Boolean logDownDispatch(MotionEvent ev, String msg, Boolean result) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mShadeLogger.logShadeWindowDispatch(ev, msg, result);
+        }
+        return result;
+    }
+
     /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
     public void setupExpandedStatusBar() {
         mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
         mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
                 mPulsingGestureListener);
-
+        if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+            mDreamingWakeupGestureHandler = new GestureDetector(mView.getContext(),
+                    mLockscreenHostedDreamGestureListener);
+        }
         mView.setLayoutInsetsController(mNotificationInsetsController);
         mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
             @Override
             public Boolean handleDispatchTouchEvent(MotionEvent ev) {
                 if (mStatusBarViewController == null) { // Fix for b/192490822
-                    Log.w(TAG, "Ignoring touch while statusBarView not yet set.");
-                    return false;
+                    return logDownDispatch(ev,
+                            "Ignoring touch while statusBarView not yet set", false);
                 }
                 boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN;
                 boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
@@ -238,10 +260,9 @@
                 }
 
                 // Reset manual touch dispatch state here but make sure the UP/CANCEL event still
-                // gets
-                // delivered.
+                // gets delivered.
                 if (!isCancel && mService.shouldIgnoreTouch()) {
-                    return false;
+                    return logDownDispatch(ev, "touch ignored by CS", false);
                 }
 
                 if (isDown) {
@@ -253,8 +274,11 @@
                     mTouchActive = false;
                     mDownEvent = null;
                 }
-                if (mTouchCancelled || mExpandAnimationRunning) {
-                    return false;
+                if (mTouchCancelled) {
+                    return logDownDispatch(ev, "touch cancelled", false);
+                }
+                if (mExpandAnimationRunning) {
+                    return logDownDispatch(ev, "expand animation running", false);
                 }
 
                 if (mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
@@ -268,13 +292,17 @@
                 }
 
                 if (mIsOcclusionTransitionRunning) {
-                    return false;
+                    return logDownDispatch(ev, "occlusion transition running", false);
                 }
 
                 mFalsingCollector.onTouchEvent(ev);
                 mPulsingWakeupGestureHandler.onTouchEvent(ev);
+                if (mDreamingWakeupGestureHandler != null
+                        && mDreamingWakeupGestureHandler.onTouchEvent(ev)) {
+                    return logDownDispatch(ev, "dream wakeup gesture handled", true);
+                }
                 if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) {
-                    return true;
+                    return logDownDispatch(ev, "dispatched to Keyguard", true);
                 }
                 if (mBrightnessMirror != null
                         && mBrightnessMirror.getVisibility() == View.VISIBLE) {
@@ -282,7 +310,7 @@
                     // you can't touch anything other than the brightness slider while the mirror is
                     // showing and the rest of the panel is transparent.
                     if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
-                        return false;
+                        return logDownDispatch(ev, "disallowed new pointer", false);
                     }
                 }
                 if (isDown) {
@@ -297,8 +325,7 @@
                         /* onGestureDetectedRunnable */
                         () -> {
                             mService.userActivity();
-                            mService.wakeUpIfDozing(
-                                    mClock.uptimeMillis(),
+                            mPowerInteractor.wakeUpIfDozing(
                                     "LOCK_ICON_TOUCH",
                                     PowerManager.WAKE_REASON_GESTURE);
                         }
@@ -314,7 +341,9 @@
                     expandingBelowNotch = true;
                 }
                 if (expandingBelowNotch) {
-                    return mStatusBarViewController.sendTouchToView(ev);
+                    return logDownDispatch(ev,
+                            "expand below notch. sending touch to status bar",
+                            mStatusBarViewController.sendTouchToView(ev));
                 }
 
                 if (!mIsTrackingBarGesture && isDown
@@ -324,9 +353,10 @@
                     if (mStatusBarViewController.touchIsWithinView(x, y)) {
                         if (mStatusBarWindowStateController.windowIsShowing()) {
                             mIsTrackingBarGesture = true;
-                            return mStatusBarViewController.sendTouchToView(ev);
-                        } else { // it's hidden or hiding, don't send to notification shade.
-                            return true;
+                            return logDownDispatch(ev, "sending touch to status bar",
+                                    mStatusBarViewController.sendTouchToView(ev));
+                        } else {
+                            return logDownDispatch(ev, "hidden or hiding", true);
                         }
                     }
                 } else if (mIsTrackingBarGesture) {
@@ -334,10 +364,10 @@
                     if (isUp || isCancel) {
                         mIsTrackingBarGesture = false;
                     }
-                    return sendToStatusBar;
+                    return logDownDispatch(ev, "sending bar gesture to status bar",
+                            sendToStatusBar);
                 }
-
-                return null;
+                return logDownDispatch(ev, "no custom touch dispatch of down event", null);
             }
 
             @Override
@@ -349,29 +379,40 @@
             public boolean shouldInterceptTouchEvent(MotionEvent ev) {
                 if (mStatusBarStateController.isDozing() && !mService.isPulsing()
                         && !mDockManager.isDocked()) {
-                    // Capture all touch events in always-on.
+                    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                        mShadeLogger.d("NSWVC: capture all touch events in always-on");
+                    }
                     return true;
                 }
 
                 if (mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(ev)) {
                     // Don't allow touches to proceed to underlying views if alternate
                     // bouncer is showing
+                    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                        mShadeLogger.d("NSWVC: alt bouncer showing");
+                    }
                     return true;
                 }
 
                 if (mLockIconViewController.onInterceptTouchEvent(ev)) {
                     // immediately return true; don't send the touch to the drag down helper
+                    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                        mShadeLogger.d("NSWVC: don't send touch to drag down helper");
+                    }
                     return true;
                 }
 
-                if (mMultiShadeMotionEventInteractor != null) {
-                    // This interactor is not null only if the dual shade feature is enabled.
-                    return mMultiShadeMotionEventInteractor.shouldIntercept(ev);
-                } else if (mNotificationPanelViewController.isFullyExpanded()
+                if (mNotificationPanelViewController.isFullyExpanded()
                         && mDragDownHelper.isDragDownEnabled()
                         && !mService.isBouncerShowing()
                         && !mStatusBarStateController.isDozing()) {
-                    return mDragDownHelper.onInterceptTouchEvent(ev);
+                    boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
+                    if (result) {
+                        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                            mShadeLogger.d("NSWVC: drag down helper intercepted");
+                        }
+                    }
+                    return result;
                 } else {
                     return false;
                 }
@@ -397,10 +438,7 @@
                     return true;
                 }
 
-                if (mMultiShadeMotionEventInteractor != null) {
-                    // This interactor is not null only if the dual shade feature is enabled.
-                    return mMultiShadeMotionEventInteractor.onTouchEvent(ev, mView.getWidth());
-                } else if (mDragDownHelper.isDragDownEnabled()
+                if (mDragDownHelper.isDragDownEnabled()
                         || mDragDownHelper.isDraggingDown()) {
                     // we still want to finish our drag down gesture when locking the screen
                     return mDragDownHelper.onTouchEvent(ev) || handled;
@@ -433,7 +471,7 @@
                 switch (event.getKeyCode()) {
                     case KeyEvent.KEYCODE_BACK:
                         if (!down) {
-                            mService.onBackPressed();
+                            mBackActionInteractor.onBackRequested();
                         }
                         return true;
                     case KeyEvent.KEYCODE_MENU:
@@ -486,6 +524,7 @@
     }
 
     public void cancelCurrentTouch() {
+        mShadeLogger.d("NSWVC: cancelling current touch");
         if (mTouchActive) {
             final long now = mClock.uptimeMillis();
             final MotionEvent event;
@@ -516,7 +555,7 @@
         pw.println(mTouchActive);
     }
 
-    public void setExpandAnimationRunning(boolean running) {
+    private void setExpandAnimationRunning(boolean running) {
         if (mExpandAnimationRunning != running) {
             mExpandAnimationRunning = running;
             mNotificationShadeWindowController.setLaunchingActivity(mExpandAnimationRunning);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 81fe3ca..9412542 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -27,7 +27,10 @@
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.R
+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.fragments.FragmentService
 import com.android.systemui.navigationbar.NavigationModeController
 import com.android.systemui.plugins.qs.QS
@@ -35,6 +38,7 @@
 import com.android.systemui.recents.OverviewProxyService
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
 import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.util.LargeScreenUtils
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -45,6 +49,7 @@
 @VisibleForTesting
 internal const val INSET_DEBOUNCE_MILLIS = 500L
 
+@SysUISingleton
 class NotificationsQSContainerController @Inject constructor(
         view: NotificationsQuickSettingsContainer,
         private val navigationModeController: NavigationModeController,
@@ -52,7 +57,10 @@
         private val shadeHeaderController: ShadeHeaderController,
         private val shadeExpansionStateManager: ShadeExpansionStateManager,
         private val fragmentService: FragmentService,
-        @Main private val delayableExecutor: DelayableExecutor
+        @Main private val delayableExecutor: DelayableExecutor,
+        private val featureFlags: FeatureFlags,
+        private val
+            notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
 ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
 
     private var qsExpanded = false
@@ -116,6 +124,9 @@
             isGestureNavigation = QuickStepContract.isGesturalMode(mode)
         }
         isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
+
+        mView.setStackScroller(notificationStackScrollLayoutController.getView())
+        mView.setMigratingNSSL(featureFlags.isEnabled(Flags.MIGRATE_NSSL))
     }
 
     public override fun onViewAttached() {
@@ -144,10 +155,8 @@
         largeScreenShadeHeaderActive = LargeScreenUtils.shouldUseLargeScreenShadeHeader(resources)
         notificationsBottomMargin = resources.getDimensionPixelSize(
                 R.dimen.notification_panel_margin_bottom)
-        largeScreenShadeHeaderHeight =
-                resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
-        shadeHeaderHeight =
-                resources.getDimensionPixelSize(R.dimen.qs_header_height)
+        largeScreenShadeHeaderHeight = calculateLargeShadeHeaderHeight()
+        shadeHeaderHeight = calculateShadeHeaderHeight()
         panelMarginHorizontal = resources.getDimensionPixelSize(
                 R.dimen.notification_panel_margin_horizontal)
         topMargin = if (largeScreenShadeHeaderActive) {
@@ -171,6 +180,23 @@
         }
     }
 
+    private fun calculateLargeShadeHeaderHeight(): Int {
+        return resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
+    }
+
+    private fun calculateShadeHeaderHeight(): Int {
+        val minHeight = resources.getDimensionPixelSize(R.dimen.qs_header_height)
+
+        // Following the constraints in xml/qs_header, the total needed height would be the sum of
+        // 1. privacy_container height (R.dimen.large_screen_shade_header_min_height)
+        // 2. carrier_group height (R.dimen.large_screen_shade_header_min_height)
+        // 3. date height (R.dimen.new_qs_header_non_clickable_element_height)
+        val estimatedHeight =
+                2 * resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height) +
+                resources.getDimensionPixelSize(R.dimen.new_qs_header_non_clickable_element_height)
+        return estimatedHeight.coerceAtLeast(minHeight)
+    }
+
     override fun setCustomizerAnimating(animating: Boolean) {
         if (isQSCustomizerAnimating != animating) {
             isQSCustomizerAnimating = animating
@@ -252,14 +278,17 @@
     }
 
     private fun setNotificationsConstraints(constraintSet: ConstraintSet) {
+        if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+            return
+        }
         val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID
+        val nsslId = R.id.notification_stack_scroller
         constraintSet.apply {
-            connect(R.id.notification_stack_scroller, START, startConstraintId, START)
-            setMargin(R.id.notification_stack_scroller, START,
-                    if (splitShadeEnabled) 0 else panelMarginHorizontal)
-            setMargin(R.id.notification_stack_scroller, END, panelMarginHorizontal)
-            setMargin(R.id.notification_stack_scroller, TOP, topMargin)
-            setMargin(R.id.notification_stack_scroller, BOTTOM, notificationsBottomMargin)
+            connect(nsslId, START, startConstraintId, START)
+            setMargin(nsslId, START, if (splitShadeEnabled) 0 else panelMarginHorizontal)
+            setMargin(nsslId, END, panelMarginHorizontal)
+            setMargin(nsslId, TOP, topMargin)
+            setMargin(nsslId, BOTTOM, notificationsBottomMargin)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index e5b84bd..a4e439b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -22,7 +22,9 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
 import android.view.WindowInsets;
 
 import androidx.annotation.Nullable;
@@ -56,6 +58,7 @@
     private QS mQs;
     private View mQSContainer;
     private int mLastQSPaddingBottom;
+    private boolean mIsMigratingNSSL;
 
     /**
      *  These are used to compute the bounding box containing the shade and the notification scrim,
@@ -75,10 +78,13 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mQsFrame = findViewById(R.id.qs_frame);
-        mStackScroller = findViewById(R.id.notification_stack_scroller);
         mKeyguardStatusBar = findViewById(R.id.keyguard_header);
     }
 
+    void setStackScroller(View stackScroller) {
+        mStackScroller = stackScroller;
+    }
+
     @Override
     public void onFragmentViewCreated(String tag, Fragment fragment) {
         mQs = (QS) fragment;
@@ -108,7 +114,7 @@
     }
 
     public void setNotificationsMarginBottom(int margin) {
-        LayoutParams params = (LayoutParams) mStackScroller.getLayoutParams();
+        MarginLayoutParams params = (MarginLayoutParams) mStackScroller.getLayoutParams();
         params.bottomMargin = margin;
         mStackScroller.setLayoutParams(params);
     }
@@ -173,8 +179,21 @@
         super.dispatchDraw(canvas);
     }
 
+    void setMigratingNSSL(boolean isMigrating) {
+        mIsMigratingNSSL = isMigrating;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev,
+                super.dispatchTouchEvent(ev));
+    }
+
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        if (mIsMigratingNSSL) {
+            return super.drawChild(canvas, child, drawingTime);
+        }
         int layoutIndex = mLayoutDrawingOrder.indexOf(child);
         if (layoutIndex >= 0) {
             return super.drawChild(canvas, mDrawingOrderedChildren.get(layoutIndex), drawingTime);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index fd82e2f..fe4832f0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -16,21 +16,22 @@
 
 package com.android.systemui.shade
 
+import android.graphics.Point
 import android.hardware.display.AmbientDisplayConfiguration
 import android.os.PowerManager
-import android.os.SystemClock
 import android.provider.Settings
 import android.view.GestureDetector
 import android.view.MotionEvent
 import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.DozeInteractor
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.tuner.TunerService.Tunable
 import java.io.PrintWriter
@@ -45,15 +46,15 @@
  * screen is still ON and not in the true AoD display state. When the device is in the true AoD
  * display state, wake-ups are handled by [com.android.systemui.doze.DozeSensors].
  */
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
 class PulsingGestureListener @Inject constructor(
-        private val notificationShadeWindowView: NotificationShadeWindowView,
         private val falsingManager: FalsingManager,
         private val dockManager: DockManager,
-        private val centralSurfaces: CentralSurfaces,
+        private val powerInteractor: PowerInteractor,
         private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
         private val statusBarStateController: StatusBarStateController,
         private val shadeLogger: ShadeLogger,
+        private val dozeInteractor: DozeInteractor,
         userTracker: UserTracker,
         tunerService: TunerService,
         dumpManager: DumpManager
@@ -88,11 +89,8 @@
             shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap)
             if (proximityIsNotNear && isNotAFalseTap) {
                 shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing")
-                centralSurfaces.wakeUpIfDozing(
-                    SystemClock.uptimeMillis(),
-                    "PULSING_SINGLE_TAP",
-                    PowerManager.WAKE_REASON_TAP
-                )
+                dozeInteractor.setLastTapToWakePosition(Point(e.x.toInt(), e.y.toInt()))
+                powerInteractor.wakeUpIfDozing("PULSING_SINGLE_TAP", PowerManager.WAKE_REASON_TAP)
             }
             return true
         }
@@ -113,11 +111,7 @@
                 !falsingManager.isProximityNear &&
                 !falsingManager.isFalseDoubleTap
         ) {
-            centralSurfaces.wakeUpIfDozing(
-                    SystemClock.uptimeMillis(),
-                    "PULSING_DOUBLE_TAP",
-                    PowerManager.WAKE_REASON_TAP
-            )
+            powerInteractor.wakeUpIfDozing("PULSING_DOUBLE_TAP", PowerManager.WAKE_REASON_TAP)
             return true
         }
         return false
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 7d66a0b..ac7aeae 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -68,6 +68,7 @@
 import com.android.systemui.R;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentHostManager;
@@ -78,6 +79,7 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.data.repository.ShadeRepository;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.shade.transition.ShadeTransitionController;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -97,10 +99,10 @@
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.LargeScreenUtils;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import dagger.Lazy;
 
@@ -111,7 +113,7 @@
 /** Handles QuickSettings touch handling, expansion and animation state
  * TODO (b/264460656) make this dumpable
  */
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
 public class QuickSettingsController implements Dumpable {
     public static final String TAG = "QuickSettingsController";
 
@@ -153,6 +155,8 @@
     private final FeatureFlags mFeatureFlags;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final ShadeRepository mShadeRepository;
+    private final ShadeInteractor mShadeInteractor;
+    private final JavaAdapter mJavaAdapter;
     private final FalsingManager mFalsingManager;
     private final AccessibilityManager mAccessibilityManager;
     private final MetricsLogger mMetricsLogger;
@@ -229,7 +233,6 @@
     private int mMaxExpansionHeight;
     /** Expansion fraction of the notification shade */
     private float mShadeExpandedFraction;
-    private int mPeekHeight;
     private float mLastOverscroll;
     private boolean mExpansionFromOverscroll;
     private boolean mExpansionEnabledPolicy = true;
@@ -342,6 +345,8 @@
             DumpManager dumpManager,
             KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
             ShadeRepository shadeRepository,
+            ShadeInteractor shadeInteractor,
+            JavaAdapter javaAdapter,
             CastController castController
     ) {
         mPanelViewControllerLazy = panelViewControllerLazy;
@@ -387,6 +392,8 @@
         mFeatureFlags = featureFlags;
         mInteractionJankMonitor = interactionJankMonitor;
         mShadeRepository = shadeRepository;
+        mShadeInteractor = shadeInteractor;
+        mJavaAdapter = javaAdapter;
 
         mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback());
         dumpManager.registerDumpable(this);
@@ -421,7 +428,6 @@
         final ViewConfiguration configuration = ViewConfiguration.get(this.mPanelView.getContext());
         mTouchSlop = configuration.getScaledTouchSlop();
         mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
-        mPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height);
         mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mPanelView.getContext());
         mScrimCornerRadius = mResources.getDimensionPixelSize(
                 R.dimen.notification_scrim_corner_radius);
@@ -459,7 +465,13 @@
     }
 
     // TODO (b/265054088): move this and others to a CoreStartable
-    void initNotificationStackScrollLayoutController() {
+    void init() {
+        initNotificationStackScrollLayoutController();
+        mJavaAdapter.alwaysCollectFlow(
+                mShadeInteractor.isExpandToQsEnabled(), this::setExpansionEnabledPolicy);
+    }
+
+    private void initNotificationStackScrollLayoutController() {
         mNotificationStackScrollLayoutController.setOverscrollTopChangedListener(
                 new NsslOverscrollTopChangedListener());
         mNotificationStackScrollLayoutController.setOnStackYChanged(this::onStackYChanged);
@@ -486,12 +498,7 @@
     }
 
     int getHeaderHeight() {
-        return mQs.getHeader().getHeight();
-    }
-
-    /** Returns the padding of the stackscroller when unlocked */
-    int getUnlockedStackScrollerPadding() {
-        return (mQs != null ? mQs.getHeader().getHeight() : 0) + mPeekHeight;
+        return isQsFragmentCreated() ? mQs.getHeader().getHeight() : 0;
     }
 
     private boolean isRemoteInputActiveWithKeyboardUp() {
@@ -780,7 +787,12 @@
 
     /** update Qs height state */
     public void setExpansionHeight(float height) {
-        checkCorrectSplitShadeState(height);
+        // TODO(b/277909752): remove below log when bug is fixed
+        if (mSplitShadeEnabled && mShadeExpandedFraction == 1.0f && height == 0) {
+            Log.wtf(TAG,
+                    "setting QS height to 0 in split shade while shade is open(ing). "
+                            + "Value of mExpandImmediate = " + mExpandImmediate);
+        }
         int maxHeight = getMaxExpansionHeight();
         height = Math.min(Math.max(
                 height, getMinExpansionHeight()), maxHeight);
@@ -802,14 +814,6 @@
         }
     }
 
-    /** TODO(b/269742565) Remove this logging */
-    private void checkCorrectSplitShadeState(float height) {
-        if (mSplitShadeEnabled && height == 0
-                && mPanelViewControllerLazy.get().isShadeFullyExpanded()) {
-            Log.wtfStack(TAG, "qsExpansion set to 0 while split shade is expanding or open");
-        }
-    }
-
     /** */
     public void setHeightOverrideToDesiredHeight() {
         if (isSizeChangeAnimationRunning() && isQsFragmentCreated()) {
@@ -892,7 +896,7 @@
     }
 
     /** */
-    public void setExpansionEnabledPolicy(boolean expansionEnabledPolicy) {
+    private void setExpansionEnabledPolicy(boolean expansionEnabledPolicy) {
         mExpansionEnabledPolicy = expansionEnabledPolicy;
         if (mQs != null) {
             mQs.setHeaderClickable(isExpansionEnabled());
@@ -935,7 +939,6 @@
         return mShadeExpandedHeight;
     }
 
-    @VisibleForTesting
     void setExpandImmediate(boolean expandImmediate) {
         if (expandImmediate != mExpandImmediate) {
             mShadeLog.logQsExpandImmediateChanged(expandImmediate);
@@ -1215,14 +1218,15 @@
         if (mIsFullWidth) {
             clipStatusView = qsVisible;
             float screenCornerRadius =
-                    !mSplitShadeEnabled || mRecordingController.isRecording()
-                            || mCastController.hasConnectedCastDevice()
+                    mRecordingController.isRecording() || mCastController.hasConnectedCastDevice()
                             ? 0 : mScreenCornerRadius;
             radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
                     Math.min(top / (float) mScrimCornerRadius, 1f));
 
-            float bottomRadius = mExpanded ? screenCornerRadius :
-                    calculateBottomCornerRadius(screenCornerRadius);
+            float bottomRadius = mSplitShadeEnabled ? screenCornerRadius : 0;
+            if (!mExpanded) {
+                bottomRadius = calculateBottomCornerRadius(bottomRadius);
+            }
             mScrimController.setNotificationBottomRadius(bottomRadius);
         }
         if (isQsFragmentCreated()) {
@@ -1888,7 +1892,7 @@
                 target = getMaxExpansionHeight();
                 break;
             case FLING_COLLAPSE:
-                if (mSplitShadeEnabled) { // TODO:(b/269742565) remove below log
+                if (mSplitShadeEnabled) {
                     Log.wtfStack(TAG, "FLING_COLLAPSE called in split shade");
                 }
                 setExpandImmediate(false);
@@ -2084,8 +2088,6 @@
         ipw.println(mMaxExpansionHeight);
         ipw.print("mShadeExpandedFraction=");
         ipw.println(mShadeExpandedFraction);
-        ipw.print("mPeekHeight=");
-        ipw.println(mPeekHeight);
         ipw.print("mLastOverscroll=");
         ipw.println(mLastOverscroll);
         ipw.print("mExpansionFromOverscroll=");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index d0a3cbb..02f337a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -18,6 +18,8 @@
 
 import android.view.MotionEvent;
 
+import com.android.systemui.CoreStartable;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -30,7 +32,7 @@
  * these are coordinated with {@link StatusBarKeyguardViewManager} via
  * {@link com.android.systemui.keyguard.KeyguardViewMediator} and others.
  */
-public interface ShadeController {
+public interface ShadeController extends CoreStartable {
 
     /** Make our window larger and the shade expanded */
     void instantExpandShade();
@@ -38,23 +40,49 @@
     /** Collapse the shade instantly with no animation. */
     void instantCollapseShade();
 
-    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
-    void animateCollapseShade();
+    /** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
+    default void animateCollapseShade() {
+        animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
+    }
 
-    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
-    void animateCollapseShade(int flags);
+    /** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
+    default void animateCollapseShade(int flags) {
+        animateCollapseShade(flags, false, false, 1.0f);
+    }
 
-    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
-    void animateCollapseShadeForced();
+    /** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
+    default void animateCollapseShadeForced() {
+        animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f);
+    }
 
-    /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
-    void animateCollapseShadeDelayed();
+    /** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
+    default void animateCollapseShadeForcedDelayed() {
+        animateCollapseShade(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f);
+    }
 
     /**
      * Collapse the shade animated, showing the bouncer when on {@link StatusBarState#KEYGUARD} or
      * dismissing status bar when on {@link StatusBarState#SHADE}.
      */
-    void animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor);
+    void animateCollapseShade(int flags, boolean force, boolean delayed, float speedUpFactor);
+
+    /** Expand the shade with an animation. */
+    void animateExpandShade();
+
+    /** Expand the shade with quick settings expanded with an animation. */
+    void animateExpandQs();
+
+    /** Posts a request to collapse the shade. */
+    void postAnimateCollapseShade();
+
+    /** Posts a request to force collapse the shade. */
+    void postAnimateForceCollapseShade();
+
+    /** Posts a request to expand the shade to quick settings. */
+    void postAnimateExpandQs();
+
+    /** Cancels any ongoing expansion touch handling and collapses the shade. */
+    void cancelExpansionAndCollapseShade();
 
     /**
      * If the shade is not fully expanded, collapse it animated.
@@ -115,6 +143,9 @@
      */
     void collapseShade(boolean animate);
 
+    /** Calls #collapseShade if already on the main thread. If not, posts a call to it. */
+    void collapseOnMainThread();
+
     /** Makes shade expanded but not visible. */
     void makeExpandedInvisible();
 
@@ -127,22 +158,21 @@
     /** Handle status bar touch event. */
     void onStatusBarTouch(MotionEvent event);
 
-    /** Called when the shade finishes collapsing. */
-    void onClosingFinished();
+    /** Called when a launch animation was cancelled. */
+    void onLaunchAnimationCancelled(boolean isLaunchForActivity);
+
+    /** Called when a launch animation ends. */
+    void onLaunchAnimationEnd(boolean launchIsFullScreen);
 
     /** Sets the listener for when the visibility of the shade changes. */
-    void setVisibilityListener(ShadeVisibilityListener listener);
+    default void setVisibilityListener(ShadeVisibilityListener listener) {}
 
     /** */
-    void setNotificationPresenter(NotificationPresenter presenter);
+    default void setNotificationPresenter(NotificationPresenter presenter) {}
 
     /** */
-    void setNotificationShadeWindowViewController(
-            NotificationShadeWindowViewController notificationShadeWindowViewController);
-
-    /** */
-    void setNotificationPanelViewController(
-            NotificationPanelViewController notificationPanelViewController);
+    default void setNotificationShadeWindowViewController(
+            NotificationShadeWindowViewController notificationShadeWindowViewController) {}
 
     /** Listens for shade visibility changes. */
     interface ShadeVisibilityListener {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
new file mode 100644
index 0000000..5f95bca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Empty implementation of ShadeController for variants of Android without shades. */
+@SysUISingleton
+open class ShadeControllerEmptyImpl @Inject constructor() : ShadeController {
+    override fun start() {}
+    override fun instantExpandShade() {}
+    override fun instantCollapseShade() {}
+    override fun animateCollapseShade(
+        flags: Int,
+        force: Boolean,
+        delayed: Boolean,
+        speedUpFactor: Float
+    ) {}
+    override fun animateExpandShade() {}
+    override fun animateExpandQs() {}
+    override fun postAnimateCollapseShade() {}
+    override fun postAnimateForceCollapseShade() {}
+    override fun postAnimateExpandQs() {}
+    override fun cancelExpansionAndCollapseShade() {}
+    override fun closeShadeIfOpen(): Boolean {
+        return false
+    }
+    override fun isKeyguard(): Boolean {
+        return false
+    }
+    override fun isShadeFullyOpen(): Boolean {
+        return false
+    }
+    override fun isExpandingOrCollapsing(): Boolean {
+        return false
+    }
+    override fun postOnShadeExpanded(action: Runnable?) {}
+    override fun addPostCollapseAction(action: Runnable?) {}
+    override fun runPostCollapseRunnables() {}
+    override fun collapseShade(): Boolean {
+        return false
+    }
+    override fun collapseShade(animate: Boolean) {}
+    override fun collapseOnMainThread() {}
+    override fun makeExpandedInvisible() {}
+    override fun makeExpandedVisible(force: Boolean) {}
+    override fun isExpandedVisible(): Boolean {
+        return false
+    }
+    override fun onStatusBarTouch(event: MotionEvent?) {}
+    override fun onLaunchAnimationCancelled(isLaunchForActivity: Boolean) {}
+    override fun onLaunchAnimationEnd(launchIsFullScreen: Boolean) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index d00dab6..d7a3392 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -17,6 +17,7 @@
 package com.android.systemui.shade;
 
 import android.content.ComponentCallbacks2;
+import android.os.Looper;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.ViewTreeObserver;
@@ -25,6 +26,9 @@
 
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.dagger.ShadeTouchLog;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -32,12 +36,14 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 
 import dagger.Lazy;
 
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -51,12 +57,16 @@
     private final int mDisplayId;
 
     private final CommandQueue mCommandQueue;
+    private final Executor mMainExecutor;
+    private final LogBuffer mTouchLog;
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarStateController mStatusBarStateController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final StatusBarWindowController mStatusBarWindowController;
+    private final DeviceProvisionedController mDeviceProvisionedController;
 
+    private final Lazy<ShadeViewController> mShadeViewControllerLazy;
     private final Lazy<AssistManager> mAssistManagerLazy;
     private final Lazy<NotificationGutsManager> mGutsManager;
 
@@ -64,7 +74,6 @@
 
     private boolean mExpandedVisible;
 
-    private NotificationPanelViewController mNotificationPanelViewController;
     private NotificationPresenter mPresenter;
     private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     private ShadeVisibilityListener mShadeVisibilityListener;
@@ -72,18 +81,26 @@
     @Inject
     public ShadeControllerImpl(
             CommandQueue commandQueue,
+            @Main Executor mainExecutor,
+            @ShadeTouchLog LogBuffer touchLog,
             KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             StatusBarWindowController statusBarWindowController,
+            DeviceProvisionedController deviceProvisionedController,
             NotificationShadeWindowController notificationShadeWindowController,
             WindowManager windowManager,
+            Lazy<ShadeViewController> shadeViewControllerLazy,
             Lazy<AssistManager> assistManagerLazy,
             Lazy<NotificationGutsManager> gutsManager
     ) {
         mCommandQueue = commandQueue;
+        mMainExecutor = mainExecutor;
+        mTouchLog = touchLog;
+        mShadeViewControllerLazy = shadeViewControllerLazy;
         mStatusBarStateController = statusBarStateController;
         mStatusBarWindowController = statusBarWindowController;
+        mDeviceProvisionedController = deviceProvisionedController;
         mGutsManager = gutsManager;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
@@ -96,32 +113,12 @@
     public void instantExpandShade() {
         // Make our window larger and the panel expanded.
         makeExpandedVisible(true /* force */);
-        mNotificationPanelViewController.expand(false /* animate */);
+        getShadeViewController().expand(false /* animate */);
         mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
     }
 
     @Override
-    public void animateCollapseShade() {
-        animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
-    }
-
-    @Override
-    public void animateCollapseShade(int flags) {
-        animateCollapsePanels(flags, false, false, 1.0f);
-    }
-
-    @Override
-    public void animateCollapseShadeForced() {
-        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f);
-    }
-
-    @Override
-    public void animateCollapseShadeDelayed() {
-        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f);
-    }
-
-    @Override
-    public void animateCollapsePanels(int flags, boolean force, boolean delayed,
+    public void animateCollapseShade(int flags, boolean force, boolean delayed,
             float speedUpFactor) {
         if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) {
             runPostCollapseRunnables();
@@ -132,19 +129,38 @@
                     "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
         }
         if (getNotificationShadeWindowView() != null
-                && mNotificationPanelViewController.canBeCollapsed()
+                && getShadeViewController().canBeCollapsed()
                 && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
             // release focus immediately to kick off focus change transition
             mNotificationShadeWindowController.setNotificationShadeFocusable(false);
 
             mNotificationShadeWindowViewController.cancelExpandHelper();
-            mNotificationPanelViewController.collapse(true, delayed, speedUpFactor);
+            getShadeViewController().collapse(true, delayed, speedUpFactor);
         }
     }
 
     @Override
+    public void animateExpandShade() {
+        if (!mCommandQueue.panelsEnabled()) {
+            return;
+        }
+        getShadeViewController().expandToNotifications();
+    }
+
+    @Override
+    public void animateExpandQs() {
+        if (!mCommandQueue.panelsEnabled()) {
+            return;
+        }
+        // Settings are not available in setup
+        if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
+
+        getShadeViewController().expandToQs();
+    }
+
+    @Override
     public boolean closeShadeIfOpen() {
-        if (!mNotificationPanelViewController.isFullyCollapsed()) {
+        if (!getShadeViewController().isFullyCollapsed()) {
             mCommandQueue.animateCollapsePanels(
                     CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
             notifyVisibilityChanged(false);
@@ -160,23 +176,37 @@
 
     @Override
     public boolean isShadeFullyOpen() {
-        return mNotificationPanelViewController.isShadeFullyExpanded();
+        return getShadeViewController().isShadeFullyExpanded();
     }
 
     @Override
     public boolean isExpandingOrCollapsing() {
-        return mNotificationPanelViewController.isExpandingOrCollapsing();
+        return getShadeViewController().isExpandingOrCollapsing();
+    }
+    @Override
+    public void postAnimateCollapseShade() {
+        mMainExecutor.execute(this::animateCollapseShade);
+    }
+
+    @Override
+    public void postAnimateForceCollapseShade() {
+        mMainExecutor.execute(this::animateCollapseShadeForced);
+    }
+
+    @Override
+    public void postAnimateExpandQs() {
+        mMainExecutor.execute(this::animateExpandQs);
     }
 
     @Override
     public void postOnShadeExpanded(Runnable executable) {
-        mNotificationPanelViewController.addOnGlobalLayoutListener(
+        getShadeViewController().addOnGlobalLayoutListener(
                 new ViewTreeObserver.OnGlobalLayoutListener() {
                     @Override
                     public void onGlobalLayout() {
                         if (getNotificationShadeWindowView().isVisibleToUser()) {
-                            mNotificationPanelViewController.removeOnGlobalLayoutListener(this);
-                            mNotificationPanelViewController.postToView(executable);
+                            getShadeViewController().removeOnGlobalLayoutListener(this);
+                            getShadeViewController().postToView(executable);
                         }
                     }
                 });
@@ -200,9 +230,9 @@
 
     @Override
     public boolean collapseShade() {
-        if (!mNotificationPanelViewController.isFullyCollapsed()) {
+        if (!getShadeViewController().isFullyCollapsed()) {
             // close the shade if it was open
-            animateCollapseShadeDelayed();
+            animateCollapseShadeForcedDelayed();
             notifyVisibilityChanged(false);
 
             return true;
@@ -227,6 +257,26 @@
     }
 
     @Override
+    public void cancelExpansionAndCollapseShade() {
+        if (getShadeViewController().isTracking()) {
+            mNotificationShadeWindowViewController.cancelCurrentTouch();
+        }
+        if (getShadeViewController().isPanelExpanded()
+                && mStatusBarStateController.getState() == StatusBarState.SHADE) {
+            animateCollapseShade();
+        }
+    }
+
+    @Override
+    public void collapseOnMainThread() {
+        if (Looper.getMainLooper().isCurrentThread()) {
+            collapseShade();
+        } else {
+            mMainExecutor.execute(this::collapseShade);
+        }
+    }
+
+    @Override
     public void onStatusBarTouch(MotionEvent event) {
         if (event.getAction() == MotionEvent.ACTION_UP) {
             if (mExpandedVisible) {
@@ -235,8 +285,7 @@
         }
     }
 
-    @Override
-    public void onClosingFinished() {
+    private void onClosingFinished() {
         runPostCollapseRunnables();
         if (!mPresenter.isPresenterFullyCollapsed()) {
             // if we set it not to be focusable when collapsing, we have to undo it when we aborted
@@ -246,8 +295,29 @@
     }
 
     @Override
+    public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
+        if (mPresenter.isPresenterFullyCollapsed()
+                && !mPresenter.isCollapsing()
+                && isLaunchForActivity) {
+            onClosingFinished();
+        } else {
+            collapseShade(true /* animate */);
+        }
+    }
+
+    @Override
+    public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
+        if (!mPresenter.isCollapsing()) {
+            onClosingFinished();
+        }
+        if (launchIsFullScreen) {
+            instantCollapseShade();
+        }
+    }
+
+    @Override
     public void instantCollapseShade() {
-        mNotificationPanelViewController.instantCollapse();
+        getShadeViewController().instantCollapse();
         runPostCollapseRunnables();
     }
 
@@ -278,7 +348,7 @@
         }
 
         // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
-        mNotificationPanelViewController.collapse(false, false, 1.0f);
+        getShadeViewController().collapse(false, false, 1.0f);
 
         mExpandedVisible = false;
         notifyVisibilityChanged(false);
@@ -300,7 +370,7 @@
         notifyExpandedVisibleChanged(false);
         mCommandQueue.recomputeDisableFlags(
                 mDisplayId,
-                mNotificationPanelViewController.shouldHideStatusBarIconsWhenExpanded());
+                getShadeViewController().shouldHideStatusBarIconsWhenExpanded());
 
         // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
         // the bouncer appear animation.
@@ -342,13 +412,16 @@
         return mNotificationShadeWindowViewController.getView();
     }
 
+    private ShadeViewController getShadeViewController() {
+        return mShadeViewControllerLazy.get();
+    }
+
     @Override
-    public void setNotificationPanelViewController(
-            NotificationPanelViewController notificationPanelViewController) {
-        mNotificationPanelViewController = notificationPanelViewController;
-        mNotificationPanelViewController.setTrackingStartedListener(this::runPostCollapseRunnables);
-        mNotificationPanelViewController.setOpenCloseListener(
-                new NotificationPanelViewController.OpenCloseListener() {
+    public void start() {
+        TouchLogger.logTouchesTo(mTouchLog);
+        getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables);
+        getShadeViewController().setOpenCloseListener(
+                new OpenCloseListener() {
                     @Override
                     public void onClosingFinished() {
                         ShadeControllerImpl.this.onClosingFinished();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
new file mode 100644
index 0000000..7a803867
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+
+/** Fulfills dependencies on the shade with empty implementations for variants with no shade. */
+@Module
+abstract class ShadeEmptyImplModule {
+    @Binds
+    @SysUISingleton
+    abstract fun bindsShadeViewController(svc: ShadeViewControllerEmptyImpl): ShadeViewController
+
+    @Binds
+    @SysUISingleton
+    abstract fun bindsShadeController(sc: ShadeControllerEmptyImpl): ShadeController
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 3af75ce..6564118 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -54,13 +54,14 @@
 import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
 import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
 import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
-import com.android.systemui.shade.ShadeModule.Companion.SHADE_HEADER
+import com.android.systemui.shade.ShadeViewProviderModule.Companion.SHADE_HEADER
 import com.android.systemui.shade.carrier.ShadeCarrierGroup
 import com.android.systemui.shade.carrier.ShadeCarrierGroupController
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.phone.StatusOverlayHoverListenerFactory
 import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.NextAlarmController
@@ -99,6 +100,7 @@
     private val qsBatteryModeController: QsBatteryModeController,
     private val nextAlarmController: NextAlarmController,
     private val activityStarter: ActivityStarter,
+    private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
 ) : ViewController<View>(header), Dumpable {
 
     companion object {
@@ -122,15 +124,18 @@
             }
     }
 
+    var shadeCollapseAction: Runnable? = null
+
     private lateinit var iconManager: StatusBarIconController.TintedIconManager
     private lateinit var carrierIconSlots: List<String>
     private lateinit var mShadeCarrierGroupController: ShadeCarrierGroupController
 
-    private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
-    private val clock: Clock = header.findViewById(R.id.clock)
-    private val date: TextView = header.findViewById(R.id.date)
-    private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
-    private val mShadeCarrierGroup: ShadeCarrierGroup = header.findViewById(R.id.carrier_group)
+    private val batteryIcon: BatteryMeterView = header.requireViewById(R.id.batteryRemainingIcon)
+    private val clock: Clock = header.requireViewById(R.id.clock)
+    private val date: TextView = header.requireViewById(R.id.date)
+    private val iconContainer: StatusIconContainer = header.requireViewById(R.id.statusIcons)
+    private val mShadeCarrierGroup: ShadeCarrierGroup = header.requireViewById(R.id.carrier_group)
+    private val systemIcons: View = header.requireViewById(R.id.shade_header_system_icons)
 
     private var roundedCorners = 0
     private var cutout: DisplayCutout? = null
@@ -195,7 +200,9 @@
         set(value) {
             if (visible && field != value) {
                 field = value
+                iconContainer.setQsExpansionTransitioning(value > 0f && value < 1.0f)
                 updatePosition()
+                updateIgnoredSlots()
             }
         }
 
@@ -216,6 +223,8 @@
             view.onApplyWindowInsets(insets)
         }
 
+    private var singleCarrier = false
+
     private val demoModeReceiver =
         object : DemoMode {
             override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
@@ -250,6 +259,16 @@
                     header.paddingRight,
                     header.paddingBottom
                 )
+                systemIcons.setPaddingRelative(
+                    resources.getDimensionPixelSize(
+                        R.dimen.shade_header_system_icons_padding_start
+                    ),
+                    resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_top),
+                    resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_end),
+                    resources.getDimensionPixelSize(
+                        R.dimen.shade_header_system_icons_padding_bottom
+                    )
+                )
             }
 
             override fun onDensityOrFontScaleChanged() {
@@ -262,6 +281,7 @@
                 lastInsets?.let { updateConstraintsForInsets(header, it) }
                 updateResources()
                 updateCarrierGroupPadding()
+                clock.onDensityOrFontScaleChanged()
             }
         }
 
@@ -310,6 +330,9 @@
         demoModeController.addCallback(demoModeReceiver)
         statusBarIconController.addIconGroup(iconManager)
         nextAlarmController.addCallback(nextAlarmCallback)
+        systemIcons.setOnHoverListener(
+            statusOverlayHoverListenerFactory.createListener(systemIcons)
+        )
     }
 
     override fun onViewDetached() {
@@ -320,6 +343,7 @@
         demoModeController.removeCallback(demoModeReceiver)
         statusBarIconController.removeIconGroup(iconManager)
         nextAlarmController.removeCallback(nextAlarmCallback)
+        systemIcons.setOnHoverListener(null)
     }
 
     fun disable(state1: Int, state2: Int, animate: Boolean) {
@@ -455,9 +479,11 @@
         if (largeScreenActive) {
             logInstantEvent("Large screen constraints set")
             header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
+            systemIcons.setOnClickListener { shadeCollapseAction?.run() }
         } else {
             logInstantEvent("Small screen constraints set")
             header.setTransition(HEADER_TRANSITION_ID)
+            systemIcons.setOnClickListener(null)
         }
         header.jumpToState(header.startState)
         updatePosition()
@@ -479,17 +505,20 @@
     private fun updateListeners() {
         mShadeCarrierGroupController.setListening(visible)
         if (visible) {
-            updateSingleCarrier(mShadeCarrierGroupController.isSingleCarrier)
+            singleCarrier = mShadeCarrierGroupController.isSingleCarrier
+            updateIgnoredSlots()
             mShadeCarrierGroupController.setOnSingleCarrierChangedListener {
-                updateSingleCarrier(it)
+                singleCarrier = it
+                updateIgnoredSlots()
             }
         } else {
             mShadeCarrierGroupController.setOnSingleCarrierChangedListener(null)
         }
     }
 
-    private fun updateSingleCarrier(singleCarrier: Boolean) {
-        if (singleCarrier) {
+    private fun updateIgnoredSlots() {
+        // switching from QQS to QS state halfway through the transition
+        if (singleCarrier || qsExpandedFraction < 0.5) {
             iconContainer.removeIgnoredSlots(carrierIconSlots)
         } else {
             iconContainer.addIgnoredSlots(carrierIconSlots)
@@ -555,7 +584,7 @@
     inner class CustomizerAnimationListener(
         private val enteringCustomizing: Boolean,
     ) : AnimatorListenerAdapter() {
-        override fun onAnimationEnd(animation: Animator?) {
+        override fun onAnimationEnd(animation: Animator) {
             super.onAnimationEnd(animation)
             header.animate().setListener(null)
             if (enteringCustomizing) {
@@ -563,7 +592,7 @@
             }
         }
 
-        override fun onAnimationStart(animation: Animator?) {
+        override fun onAnimationStart(animation: Animator) {
             super.onAnimationStart(animation)
             if (!enteringCustomizing) {
                 customizing = false
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 4fdd6e1..8d23f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -19,7 +19,7 @@
 import android.view.MotionEvent
 import com.android.systemui.log.dagger.ShadeLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.shade.ShadeViewController.Companion.FLING_COLLAPSE
 import com.android.systemui.shade.ShadeViewController.Companion.FLING_EXPAND
 import com.android.systemui.shade.ShadeViewController.Companion.FLING_HIDE
@@ -79,19 +79,39 @@
 
     fun logMotionEvent(event: MotionEvent, message: String) {
         buffer.log(
-            TAG,
-            LogLevel.VERBOSE,
-            {
-                str1 = message
-                long1 = event.eventTime
-                long2 = event.downTime
-                int1 = event.action
-                int2 = event.classification
-                double1 = event.y.toDouble()
-            },
-            {
-                "$str1: eventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2"
-            }
+                TAG,
+                LogLevel.VERBOSE,
+                {
+                    str1 = message
+                    long1 = event.eventTime
+                    long2 = event.downTime
+                    int1 = event.action
+                    int2 = event.classification
+                },
+                {
+                    "$str1: eventTime=$long1,downTime=$long2,action=$int1,class=$int2"
+                }
+        )
+    }
+
+    /** Logs motion event dispatch results from NotificationShadeWindowViewController. */
+    fun logShadeWindowDispatch(event: MotionEvent, message: String, result: Boolean?) {
+        buffer.log(
+                TAG,
+                LogLevel.VERBOSE,
+                {
+                    str1 = message
+                    long1 = event.eventTime
+                    long2 = event.downTime
+                },
+                {
+                    val prefix = when (result) {
+                        true -> "SHADE TOUCH REROUTED"
+                        false -> "SHADE TOUCH BLOCKED"
+                        null -> "SHADE TOUCH DISPATCHED"
+                    }
+                    "$prefix: eventTime=$long1,downTime=$long2, reason=$str1"
+                }
         )
     }
 
@@ -316,6 +336,17 @@
         )
     }
 
+    fun logPanelStateChanged(@PanelState panelState: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                str1 = panelState.panelStateToString()
+            },
+            { "New panel State: $str1" }
+        )
+    }
+
     fun flingQs(flingType: Int, isClick: Boolean) {
         buffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index b7551cf..6a332dd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -16,185 +16,20 @@
 
 package com.android.systemui.shade
 
-import android.content.ContentResolver
-import android.os.Handler
-import android.view.LayoutInflater
-import android.view.ViewStub
-import androidx.constraintlayout.motion.widget.MotionLayout
-import com.android.keyguard.LockIconView
-import com.android.systemui.CoreStartable
-import com.android.systemui.R
-import com.android.systemui.battery.BatteryMeterView
-import com.android.systemui.battery.BatteryMeterViewController
-import com.android.systemui.biometrics.AuthRippleController
-import com.android.systemui.biometrics.AuthRippleView
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.privacy.OngoingPrivacyChip
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.LightRevealScrim
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
-import com.android.systemui.statusbar.phone.StatusIconContainer
-import com.android.systemui.statusbar.phone.TapAgainView
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.tuner.TunerService
 import dagger.Binds
 import dagger.Module
-import dagger.Provides
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
-import javax.inject.Named
 
 /** Module for classes related to the notification shade. */
-@Module
+@Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class])
 abstract class ShadeModule {
+    @Binds
+    @SysUISingleton
+    abstract fun bindsShadeViewController(
+        notificationPanelViewController: NotificationPanelViewController
+    ): ShadeViewController
 
     @Binds
-    @IntoMap
-    @ClassKey(AuthRippleController::class)
-    abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable
-
-    companion object {
-        const val SHADE_HEADER = "large_screen_shade_header"
-
-        @Provides
-        @SysUISingleton
-        // TODO(b/277762009): Do something similar to
-        //  {@link StatusBarWindowModule.InternalWindowView} so that only
-        //  {@link NotificationShadeWindowViewController} can inject this view.
-        fun providesNotificationShadeWindowView(
-            layoutInflater: LayoutInflater,
-        ): NotificationShadeWindowView {
-            return layoutInflater.inflate(R.layout.super_notification_shade, /* root= */ null)
-                as NotificationShadeWindowView?
-                ?: throw IllegalStateException(
-                    "R.layout.super_notification_shade could not be properly inflated"
-                )
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesNotificationStackScrollLayout(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): NotificationStackScrollLayout {
-            return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller)
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesNotificationPanelView(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): NotificationPanelView {
-            return notificationShadeWindowView.findViewById(R.id.notification_panel)
-        }
-
-        @Provides
-        @SysUISingleton
-        fun providesLightRevealScrim(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): LightRevealScrim {
-            return notificationShadeWindowView.findViewById(R.id.light_reveal_scrim)
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesAuthRippleView(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): AuthRippleView? {
-            return notificationShadeWindowView.findViewById(R.id.auth_ripple)
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesLockIconView(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): LockIconView {
-            return notificationShadeWindowView.findViewById(R.id.lock_icon_view)
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesTapAgainView(
-            notificationPanelView: NotificationPanelView,
-        ): TapAgainView {
-            return notificationPanelView.findViewById(R.id.shade_falsing_tap_again)
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        @Named(SHADE_HEADER)
-        fun providesShadeHeaderView(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): MotionLayout {
-            val stub = notificationShadeWindowView.findViewById<ViewStub>(R.id.qs_header_stub)
-            val layoutId = R.layout.combined_qs_header
-            stub.layoutResource = layoutId
-            return stub.inflate() as MotionLayout
-        }
-
-        @Provides
-        @SysUISingleton
-        fun providesCombinedShadeHeadersConstraintManager(): CombinedShadeHeadersConstraintManager {
-            return CombinedShadeHeadersConstraintManagerImpl
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        @Named(SHADE_HEADER)
-        fun providesBatteryMeterView(@Named(SHADE_HEADER) view: MotionLayout): BatteryMeterView {
-            return view.findViewById(R.id.batteryRemainingIcon)
-        }
-
-        @Provides
-        @SysUISingleton
-        @Named(SHADE_HEADER)
-        fun providesBatteryMeterViewController(
-            @Named(SHADE_HEADER) batteryMeterView: BatteryMeterView,
-            userTracker: UserTracker,
-            configurationController: ConfigurationController,
-            tunerService: TunerService,
-            @Main mainHandler: Handler,
-            contentResolver: ContentResolver,
-            featureFlags: FeatureFlags,
-            batteryController: BatteryController,
-        ): BatteryMeterViewController {
-            return BatteryMeterViewController(
-                batteryMeterView,
-                userTracker,
-                configurationController,
-                tunerService,
-                mainHandler,
-                contentResolver,
-                featureFlags,
-                batteryController,
-            )
-        }
-
-        @Provides
-        @SysUISingleton
-        @Named(SHADE_HEADER)
-        fun providesOngoingPrivacyChip(
-            @Named(SHADE_HEADER) header: MotionLayout,
-        ): OngoingPrivacyChip {
-            return header.findViewById(R.id.privacy_chip)
-        }
-
-        @Provides
-        @SysUISingleton
-        @Named(SHADE_HEADER)
-        fun providesStatusIconContainer(
-            @Named(SHADE_HEADER) header: MotionLayout,
-        ): StatusIconContainer {
-            return header.findViewById(R.id.statusIcons)
-        }
-    }
+    @SysUISingleton
+    abstract fun bindsShadeController(shadeControllerImpl: ShadeControllerImpl): ShadeController
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 5ac36bf..2532bad 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -18,6 +18,7 @@
 import android.view.ViewPropertyAnimator
 import com.android.systemui.statusbar.GestureRecorder
 import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
 
@@ -36,33 +37,12 @@
         headsUpManager: HeadsUpManagerPhone
     )
 
-    /**
-     * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
-     * in split shade, it will collapse the whole shade.
-     *
-     * @param fullyCollapse Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
-     */
-    fun animateCollapseQs(fullyCollapse: Boolean)
-
-    /** Returns whether the shade can be collapsed. */
-    fun canBeCollapsed(): Boolean
-
     /** Cancels any pending collapses. */
     fun cancelPendingCollapse()
 
     /** Cancels the views current animation. */
     fun cancelAnimation()
 
-    /**
-     * Close the keyguard user switcher if it is open and capable of closing.
-     *
-     * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
-     * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
-     *
-     * @return true if the keyguard user switcher was open, and is now closed
-     */
-    fun closeUserSwitcherIfOpen(): Boolean
-
     /** Input focus transfer is about to happen. */
     fun startWaitingForExpandGesture()
 
@@ -84,6 +64,9 @@
     /** Animates the view from its current alpha to zero then runs the runnable. */
     fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable): ViewPropertyAnimator
 
+    /** Returns the NSSL controller. */
+    val notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+
     /** Set whether the bouncer is showing. */
     fun setBouncerShowing(bouncerShowing: Boolean)
 
@@ -116,9 +99,6 @@
     /** Sets the view's alpha to max. */
     fun resetAlpha()
 
-    /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
-    fun onBackPressed()
-
     /** Sets progress of the predictive back animation. */
     fun onBackProgressed(progressFraction: Float)
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index f75047c..d5b5c87 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -17,11 +17,10 @@
 
 import android.view.MotionEvent
 import android.view.ViewGroup
-import com.android.systemui.statusbar.RemoteInputController
+import android.view.ViewTreeObserver
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView
 import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController
 import java.util.function.Consumer
@@ -60,7 +59,7 @@
      * Returns whether the shade height is greater than zero or the shade is expecting a synthesized
      * down event.
      */
-    @get:Deprecated("use {@link #isExpanded()} instead") val isPanelExpanded: Boolean
+    val isPanelExpanded: Boolean
 
     /** Returns whether the shade is fully expanded in either QS or QQS. */
     val isShadeFullyExpanded: Boolean
@@ -77,6 +76,20 @@
     /** Collapses the shade with an animation duration in milliseconds. */
     fun collapseWithDuration(animationDuration: Int)
 
+    /** Collapses the shade instantly without animation. */
+    fun instantCollapse()
+
+    /**
+     * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
+     * in split shade, it will collapse the whole shade.
+     *
+     * @param fullyCollapse Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
+     */
+    fun animateCollapseQs(fullyCollapse: Boolean)
+
+    /** Returns whether the shade can be collapsed. */
+    fun canBeCollapsed(): Boolean
+
     /** Returns whether the shade is in the process of collapsing. */
     val isCollapsing: Boolean
 
@@ -89,6 +102,9 @@
     /** Returns whether the shade's top level view is enabled. */
     val isViewEnabled: Boolean
 
+    /** Sets a listener to be notified when the shade starts opening or finishes closing. */
+    fun setOpenCloseListener(openCloseListener: OpenCloseListener)
+
     /** Returns whether status bar icons should be hidden when the shade is expanded. */
     fun shouldHideStatusBarIconsWhenExpanded(): Boolean
 
@@ -98,6 +114,9 @@
      */
     fun blockExpansionForCurrentTouch()
 
+    /** Sets a listener to be notified when touch tracking begins. */
+    fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener)
+
     /**
      * Disables the shade header.
      *
@@ -120,19 +139,22 @@
     /** Returns the StatusBarState. */
     val barState: Int
 
-    /**
-     * Returns the bottom part of the keyguard, which contains quick affordances.
-     *
-     * TODO(b/275550429): this should be removed.
-     */
-    val keyguardBottomAreaView: KeyguardBottomAreaView?
-
-    /** Returns the NSSL controller. */
-    val notificationStackScrollLayoutController: NotificationStackScrollLayoutController
-
     /** Sets the amount of progress in the status bar launch animation. */
     fun applyLaunchAnimationProgress(linearProgress: Float)
 
+    /**
+     * Close the keyguard user switcher if it is open and capable of closing.
+     *
+     * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
+     * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
+     *
+     * @return true if the keyguard user switcher was open, and is now closed
+     */
+    fun closeUserSwitcherIfOpen(): Boolean
+
+    /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
+    fun onBackPressed()
+
     /** Sets whether the status bar launch animation is currently running. */
     fun setIsLaunchAnimationRunning(running: Boolean)
 
@@ -161,6 +183,15 @@
     /** Ensures that the touchable region is updated. */
     fun updateTouchableRegion()
 
+    /** Adds a global layout listener. */
+    fun addOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener)
+
+    /** Removes a global layout listener. */
+    fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener)
+
+    /** Posts the given runnable to the view. */
+    fun postToView(action: Runnable): Boolean
+
     // ******* Begin Keyguard Section *********
     /** Animate to expanded shade after a delay in ms. Used for lockscreen to shade transition. */
     fun transitionToExpandedShade(delay: Long)
@@ -214,6 +245,9 @@
     /** Sends an external (e.g. Status Bar) touch event to the Shade touch handler. */
     fun handleExternalTouch(event: MotionEvent): Boolean
 
+    /** Starts tracking a shade expansion gesture that originated from the status bar. */
+    fun startTrackingExpansionFromStatusBar()
+
     // ******* End Keyguard Section *********
 
     /** Returns the ShadeHeadsUpTracker. */
@@ -222,10 +256,17 @@
     /** Returns the ShadeFoldAnimator. */
     val shadeFoldAnimator: ShadeFoldAnimator
 
-    /** Returns the ShadeNotificationPresenter. */
-    val shadeNotificationPresenter: ShadeNotificationPresenter
-
     companion object {
+        /**
+         * Returns a multiplicative factor to use when determining the falsing threshold for touches
+         * on the shade. The factor will be larger when the device is waking up due to a touch or
+         * gesture.
+         */
+        @JvmStatic
+        fun getFalsingThresholdFactor(wakefulness: WakefulnessModel): Float {
+            return if (wakefulness.isDeviceInteractiveFromTapOrGesture()) 1.5f else 1.0f
+        }
+
         const val WAKEUP_ANIMATION_DELAY_MS = 250
         const val FLING_MAX_LENGTH_SECONDS = 0.6f
         const val FLING_SPEED_UP_FACTOR = 0.6f
@@ -276,16 +317,7 @@
     fun cancelFoldToAodAnimation()
 
     /** Returns the main view of the shade. */
-    val view: ViewGroup
-}
-
-/** Handles the shade's interactions with StatusBarNotificationPresenter. */
-interface ShadeNotificationPresenter {
-    /** Returns a new delegate for some view controller pieces of the remote input process. */
-    fun createRemoteInputDelegate(): RemoteInputController.Delegate
-
-    /** Returns whether the screen has temporarily woken up to display notifications. */
-    fun hasPulsingNotifications(): Boolean
+    val view: ViewGroup?
 }
 
 /**
@@ -307,3 +339,17 @@
     /** Return the fraction of the shade that's expanded, when in lockscreen. */
     val lockscreenShadeDragProgress: Float
 }
+
+/** Listens for when touch tracking begins. */
+interface TrackingStartedListener {
+    fun onTrackingStarted()
+}
+
+/** Listens for when shade begins opening or finishes closing. */
+interface OpenCloseListener {
+    /** Called when the shade finishes closing. */
+    fun onClosingFinished()
+
+    /** Called when the shade starts opening. */
+    fun onOpenStarted()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
new file mode 100644
index 0000000..287ac52
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
+import java.util.function.Consumer
+import javax.inject.Inject
+
+/** Empty implementation of ShadeViewController for variants with no shade. */
+class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeViewController {
+    override fun expand(animate: Boolean) {}
+    override fun expandToQs() {}
+    override fun expandToNotifications() {}
+    override val isExpandingOrCollapsing: Boolean = false
+    override val isExpanded: Boolean = false
+    override val isPanelExpanded: Boolean = false
+    override val isShadeFullyExpanded: Boolean = false
+    override fun collapse(delayed: Boolean, speedUpFactor: Float) {}
+    override fun collapse(animate: Boolean, delayed: Boolean, speedUpFactor: Float) {}
+    override fun collapseWithDuration(animationDuration: Int) {}
+    override fun instantCollapse() {}
+    override fun animateCollapseQs(fullyCollapse: Boolean) {}
+    override fun canBeCollapsed(): Boolean = false
+    override val isCollapsing: Boolean = false
+    override val isFullyCollapsed: Boolean = false
+    override val isTracking: Boolean = false
+    override val isViewEnabled: Boolean = false
+    override fun setOpenCloseListener(openCloseListener: OpenCloseListener) {}
+    override fun shouldHideStatusBarIconsWhenExpanded() = false
+    override fun blockExpansionForCurrentTouch() {}
+    override fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener) {}
+    override fun disableHeader(state1: Int, state2: Int, animated: Boolean) {}
+    override fun startExpandLatencyTracking() {}
+    override fun startBouncerPreHideAnimation() {}
+    override fun dozeTimeTick() {}
+    override fun resetViews(animate: Boolean) {}
+    override val barState: Int = 0
+    override fun applyLaunchAnimationProgress(linearProgress: Float) {}
+    override fun closeUserSwitcherIfOpen(): Boolean {
+        return false
+    }
+    override fun onBackPressed() {}
+    override fun setIsLaunchAnimationRunning(running: Boolean) {}
+    override fun setAlpha(alpha: Int, animate: Boolean) {}
+    override fun setAlphaChangeAnimationEndAction(r: Runnable) {}
+    override fun setPulsing(pulsing: Boolean) {}
+    override fun setQsScrimEnabled(qsScrimEnabled: Boolean) {}
+    override fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean) {}
+    override fun updateSystemUiStateFlags() {}
+    override fun updateTouchableRegion() {}
+    override fun addOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {}
+    override fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {}
+    override fun postToView(action: Runnable): Boolean {
+        return false
+    }
+    override fun transitionToExpandedShade(delay: Long) {}
+    override val isUnlockHintRunning: Boolean = false
+
+    override fun resetViewGroupFade() {}
+    override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {}
+    override fun setOverStretchAmount(amount: Float) {}
+    override fun setKeyguardStatusBarAlpha(alpha: Float) {}
+    override fun showAodUi() {}
+    override fun isFullyExpanded(): Boolean {
+        return false
+    }
+    override fun handleExternalTouch(event: MotionEvent): Boolean {
+        return false
+    }
+    override fun startTrackingExpansionFromStatusBar() {}
+    override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
+    override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
+}
+
+class ShadeHeadsUpTrackerEmptyImpl : ShadeHeadsUpTracker {
+    override fun addTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {}
+    override fun removeTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {}
+    override fun setHeadsUpAppearanceController(
+        headsUpAppearanceController: HeadsUpAppearanceController?
+    ) {}
+    override val trackedHeadsUpNotification: ExpandableNotificationRow? = null
+}
+
+class ShadeFoldAnimatorEmptyImpl : ShadeFoldAnimator {
+    override fun prepareFoldToAodAnimation() {}
+    override fun startFoldToAodAnimation(
+        startAction: Runnable,
+        endAction: Runnable,
+        cancelAction: Runnable,
+    ) {}
+    override fun cancelFoldToAodAnimation() {}
+    override val view: ViewGroup? = null
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
new file mode 100644
index 0000000..05b1ac6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.annotation.SuppressLint
+import android.content.ContentResolver
+import android.os.Handler
+import android.view.LayoutInflater
+import android.view.ViewStub
+import androidx.constraintlayout.motion.widget.MotionLayout
+import com.android.keyguard.LockIconView
+import com.android.systemui.R
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.biometrics.AuthRippleView
+import com.android.systemui.compose.ComposeFacade
+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.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.privacy.OngoingPrivacyChip
+import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.shared.model.SceneContainerConfig
+import com.android.systemui.scene.ui.view.SceneWindowRootView
+import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.NotificationInsetsController
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent
+import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.phone.TapAgainView
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.tuner.TunerService
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+import javax.inject.Provider
+
+/** Module for providing views related to the shade. */
+@Module
+abstract class ShadeViewProviderModule {
+    companion object {
+        const val SHADE_HEADER = "large_screen_shade_header"
+
+        @SuppressLint("InflateParams") // Root views don't have parents.
+        @Provides
+        @SysUISingleton
+        fun providesWindowRootView(
+            layoutInflater: LayoutInflater,
+            featureFlags: FeatureFlags,
+            viewModelProvider: Provider<SceneContainerViewModel>,
+            containerConfigProvider: Provider<SceneContainerConfig>,
+            scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
+            layoutInsetController: NotificationInsetsController,
+        ): WindowRootView {
+            return if (
+                featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable()
+            ) {
+                val sceneWindowRootView =
+                    layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
+                sceneWindowRootView.init(
+                    viewModel = viewModelProvider.get(),
+                    containerConfig = containerConfigProvider.get(),
+                    scenes = scenesProvider.get(),
+                    layoutInsetController = layoutInsetController,
+                )
+                sceneWindowRootView
+            } else {
+                layoutInflater.inflate(R.layout.super_notification_shade, null)
+            }
+                as WindowRootView?
+                ?: throw IllegalStateException("Window root view could not be properly inflated")
+        }
+
+        @Provides
+        @SysUISingleton
+        // TODO(b/277762009): Do something similar to
+        //  {@link StatusBarWindowModule.InternalWindowView} so that only
+        //  {@link NotificationShadeWindowViewController} can inject this view.
+        fun providesNotificationShadeWindowView(
+            root: WindowRootView,
+            featureFlags: FeatureFlags,
+        ): NotificationShadeWindowView {
+            if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+                return root.requireViewById(R.id.legacy_window_root)
+            }
+            return root as NotificationShadeWindowView?
+                ?: throw IllegalStateException("root view not a NotificationShadeWindowView")
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesNotificationStackScrollLayout(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): NotificationStackScrollLayout {
+            return notificationShadeWindowView.requireViewById(R.id.notification_stack_scroller)
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesNotificationShelfController(
+            featureFlags: FeatureFlags,
+            newImpl: Provider<NotificationShelfViewBinderWrapperControllerImpl>,
+            notificationShelfComponentBuilder: NotificationShelfComponent.Builder,
+            layoutInflater: LayoutInflater,
+            notificationStackScrollLayout: NotificationStackScrollLayout,
+        ): NotificationShelfController {
+            return if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+                newImpl.get()
+            } else {
+                val shelfView =
+                    layoutInflater.inflate(
+                        R.layout.status_bar_notification_shelf,
+                        notificationStackScrollLayout,
+                        false
+                    ) as NotificationShelf
+                val component =
+                    notificationShelfComponentBuilder.notificationShelf(shelfView).build()
+                val notificationShelfController = component.notificationShelfController
+                notificationShelfController.init()
+                notificationShelfController
+            }
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesNotificationPanelView(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): NotificationPanelView {
+            return notificationShadeWindowView.requireViewById(R.id.notification_panel)
+        }
+
+        /**
+         * Constructs a new, unattached [KeyguardBottomAreaView].
+         *
+         * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it
+         */
+        @Provides
+        fun providesKeyguardBottomAreaView(
+            npv: NotificationPanelView,
+            layoutInflater: LayoutInflater,
+        ): KeyguardBottomAreaView {
+            return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false)
+                as KeyguardBottomAreaView
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesLightRevealScrim(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): LightRevealScrim {
+            return notificationShadeWindowView.requireViewById(R.id.light_reveal_scrim)
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesKeyguardRootView(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): KeyguardRootView {
+            return notificationShadeWindowView.requireViewById(R.id.keyguard_root_view)
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesSharedNotificationContainer(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): SharedNotificationContainer {
+            return notificationShadeWindowView.requireViewById(R.id.shared_notification_container)
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesAuthRippleView(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): AuthRippleView? {
+            return notificationShadeWindowView.requireViewById(R.id.auth_ripple)
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesLockIconView(
+            keyguardRootView: KeyguardRootView,
+            notificationPanelView: NotificationPanelView,
+            featureFlags: FeatureFlags
+        ): LockIconView {
+            if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+                return keyguardRootView.requireViewById(R.id.lock_icon_view)
+            } else {
+                return notificationPanelView.requireViewById(R.id.lock_icon_view)
+            }
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesTapAgainView(
+            notificationPanelView: NotificationPanelView,
+        ): TapAgainView {
+            return notificationPanelView.requireViewById(R.id.shade_falsing_tap_again)
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesNotificationsQuickSettingsContainer(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): NotificationsQuickSettingsContainer {
+            return notificationShadeWindowView.requireViewById(R.id.notification_container_parent)
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        @Named(SHADE_HEADER)
+        fun providesShadeHeaderView(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): MotionLayout {
+            val stub = notificationShadeWindowView.requireViewById<ViewStub>(R.id.qs_header_stub)
+            val layoutId = R.layout.combined_qs_header
+            stub.layoutResource = layoutId
+            return stub.inflate() as MotionLayout
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesCombinedShadeHeadersConstraintManager(): CombinedShadeHeadersConstraintManager {
+            return CombinedShadeHeadersConstraintManagerImpl
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        @Named(SHADE_HEADER)
+        fun providesBatteryMeterView(@Named(SHADE_HEADER) view: MotionLayout): BatteryMeterView {
+            return view.requireViewById(R.id.batteryRemainingIcon)
+        }
+
+        @Provides
+        @SysUISingleton
+        @Named(SHADE_HEADER)
+        fun providesBatteryMeterViewController(
+            @Named(SHADE_HEADER) batteryMeterView: BatteryMeterView,
+            userTracker: UserTracker,
+            configurationController: ConfigurationController,
+            tunerService: TunerService,
+            @Main mainHandler: Handler,
+            contentResolver: ContentResolver,
+            featureFlags: FeatureFlags,
+            batteryController: BatteryController,
+        ): BatteryMeterViewController {
+            return BatteryMeterViewController(
+                batteryMeterView,
+                StatusBarLocation.QS,
+                userTracker,
+                configurationController,
+                tunerService,
+                mainHandler,
+                contentResolver,
+                featureFlags,
+                batteryController,
+            )
+        }
+
+        @Provides
+        @SysUISingleton
+        @Named(SHADE_HEADER)
+        fun providesOngoingPrivacyChip(
+            @Named(SHADE_HEADER) header: MotionLayout,
+        ): OngoingPrivacyChip {
+            return header.requireViewById(R.id.privacy_chip)
+        }
+
+        @Provides
+        @SysUISingleton
+        @Named(SHADE_HEADER)
+        fun providesStatusIconContainer(
+            @Named(SHADE_HEADER) header: MotionLayout,
+        ): StatusIconContainer {
+            return header.requireViewById(R.id.statusIcons)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
index d06634b..e7a397b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt
@@ -16,14 +16,13 @@
 
 package com.android.systemui.shade
 
-import android.view.WindowManager
-import com.android.systemui.log.dagger.ShadeWindowLog
 import com.android.systemui.log.ConstantStringsLogger
 import com.android.systemui.log.ConstantStringsLoggerImpl
 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.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.dagger.ShadeWindowLog
 import javax.inject.Inject
 
 private const val TAG = "systemui.shadewindow"
@@ -31,15 +30,6 @@
 class ShadeWindowLogger @Inject constructor(@ShadeWindowLog private val buffer: LogBuffer) :
     ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
 
-    fun logApplyingWindowLayoutParams(lp: WindowManager.LayoutParams) {
-        buffer.log(
-            TAG,
-            DEBUG,
-            { str1 = lp.toString() },
-            { "Applying new window layout params: $str1" }
-        )
-    }
-
     fun logNewState(state: Any) {
         buffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
new file mode 100644
index 0000000..15ec18c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.biometrics.AuthRippleController
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+internal abstract class StartShadeModule {
+    @Binds
+    @IntoMap
+    @ClassKey(ShadeController::class)
+    abstract fun bind(shadeController: ShadeController): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(AuthRippleController::class)
+    abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt
new file mode 100644
index 0000000..58704bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+
+private const val TAG = "systemui.shade.touch"
+
+/**
+ * A logger for tracking touch dispatching in the shade view hierarchy. The purpose of this logger
+ * is to passively observe dispatchTouchEvent calls in order to see which subtrees of the shade are
+ * handling touches. Additionally, some touches may be passively observed for views near the top of
+ * the shade hierarchy that cannot intercept touches, i.e. scrims. The usage of static methods for
+ * logging is sub-optimal in many ways, but it was selected in this case to make usage of this
+ * non-function diagnostic code as low friction as possible.
+ */
+class TouchLogger {
+    companion object {
+        private var touchLogger: DispatchTouchLogger? = null
+
+        @JvmStatic
+        fun logTouchesTo(buffer: LogBuffer) {
+            touchLogger = DispatchTouchLogger(buffer)
+        }
+
+        @JvmStatic
+        fun logDispatchTouch(viewTag: String, ev: MotionEvent, result: Boolean): Boolean {
+            touchLogger?.logDispatchTouch(viewTag, ev, result)
+            return result
+        }
+    }
+}
+
+/** Logs touches. */
+private class DispatchTouchLogger(private val buffer: LogBuffer) {
+    fun logDispatchTouch(viewTag: String, ev: MotionEvent, result: Boolean) {
+        // NOTE: never log position of touches for security purposes
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = viewTag
+                int1 = ev.action
+                long1 = ev.downTime
+                bool1 = result
+            },
+            { "Touch: view=$str1, type=${typeToString(int1)}, downtime=$long1, result=$bool1" }
+        )
+    }
+
+    private fun typeToString(type: Int): String {
+        return when (type) {
+            MotionEvent.ACTION_DOWN -> "DOWN"
+            MotionEvent.ACTION_UP -> "UP"
+            MotionEvent.ACTION_MOVE -> "MOVE"
+            MotionEvent.ACTION_CANCEL -> "CANCEL"
+            MotionEvent.ACTION_POINTER_DOWN -> "POINTER_DOWN"
+            MotionEvent.ACTION_POINTER_UP -> "POINTER_UP"
+            else -> "OTHER"
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
new file mode 100644
index 0000000..6fde84a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.user.domain.interactor.UserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Business logic for shade interactions. */
+@SysUISingleton
+class ShadeInteractor
+@Inject
+constructor(
+    @Application scope: CoroutineScope,
+    disableFlagsRepository: DisableFlagsRepository,
+    keyguardRepository: KeyguardRepository,
+    userSetupRepository: UserSetupRepository,
+    deviceProvisionedController: DeviceProvisionedController,
+    userInteractor: UserInteractor,
+) {
+    /** Emits true if the shade is currently allowed and false otherwise. */
+    val isShadeEnabled: StateFlow<Boolean> =
+        disableFlagsRepository.disableFlags
+            .map { it.isShadeEnabled() }
+            .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
+
+    /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */
+    val isExpandToQsEnabled: Flow<Boolean> =
+        combine(
+            disableFlagsRepository.disableFlags,
+            isShadeEnabled,
+            keyguardRepository.isDozing,
+            userSetupRepository.isUserSetupFlow,
+        ) { disableFlags, isShadeEnabled, isDozing, isUserSetup ->
+            deviceProvisionedController.isDeviceProvisioned &&
+                // Disallow QS during setup if it's a simple user switcher. (The user intends to
+                // use the lock screen user switcher, QS is not needed.)
+                (isUserSetup || !userInteractor.isSimpleUserSwitcher) &&
+                isShadeEnabled &&
+                disableFlags.isQuickSettingsEnabled() &&
+                !isDozing
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index 4e1c272..abb69f6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -16,24 +16,11 @@
 
 package com.android.systemui.shade.transition
 
-import android.content.res.Configuration
-import android.content.res.Resources
-import android.util.MathUtils.constrain
-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.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.shade.PanelState
-import com.android.systemui.shade.STATE_OPENING
 import com.android.systemui.shade.ShadeExpansionChangeEvent
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.util.LargeScreenUtils
 import java.io.PrintWriter
 import javax.inject.Inject
 
@@ -42,37 +29,19 @@
 class ScrimShadeTransitionController
 @Inject
 constructor(
-    configurationController: ConfigurationController,
     dumpManager: DumpManager,
     private val scrimController: ScrimController,
-    @Main private val resources: Resources,
-    private val statusBarStateController: SysuiStatusBarStateController,
-    private val headsUpManager: HeadsUpManager,
-    private val featureFlags: FeatureFlags,
 ) {
 
-    private var inSplitShade = false
-    private var splitShadeScrimTransitionDistance = 0
     private var lastExpansionFraction: Float? = null
     private var lastExpansionEvent: ShadeExpansionChangeEvent? = null
     private var currentPanelState: Int? = null
 
     init {
-        updateResources()
-        configurationController.addCallback(
-            object : ConfigurationController.ConfigurationListener {
-                override fun onConfigChanged(newConfig: Configuration?) {
-                    updateResources()
-                }
-            })
         dumpManager.registerDumpable(
-            ScrimShadeTransitionController::class.java.simpleName, this::dump)
-    }
-
-    private fun updateResources() {
-        inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(resources)
-        splitShadeScrimTransitionDistance =
-            resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
+            ScrimShadeTransitionController::class.java.simpleName,
+            this::dump
+        )
     }
 
     fun onPanelStateChanged(@PanelState state: Int) {
@@ -87,46 +56,25 @@
 
     private fun onStateChanged() {
         val expansionEvent = lastExpansionEvent ?: return
-        val panelState = currentPanelState
-        val expansionFraction = calculateScrimExpansionFraction(expansionEvent, panelState)
+        val expansionFraction = calculateScrimExpansionFraction(expansionEvent)
         scrimController.setRawPanelExpansionFraction(expansionFraction)
         lastExpansionFraction = expansionFraction
     }
 
-    private fun calculateScrimExpansionFraction(
-        expansionEvent: ShadeExpansionChangeEvent,
-        @PanelState panelState: Int?
-    ): Float {
-        return if (canUseCustomFraction(panelState)) {
-            constrain(expansionEvent.dragDownPxAmount / splitShadeScrimTransitionDistance, 0f, 1f)
-        } else {
-            expansionEvent.fraction
-        }
+    private fun calculateScrimExpansionFraction(expansionEvent: ShadeExpansionChangeEvent): Float {
+        return expansionEvent.fraction
     }
 
-    private fun canUseCustomFraction(panelState: Int?) =
-        inSplitShade && isScreenUnlocked() && panelState == STATE_OPENING &&
-                // in case of HUN we can't always use predefined distances to manage scrim
-                // transition because dragDownPxAmount can start from value bigger than
-                // splitShadeScrimTransitionDistance
-                !headsUpManager.isTrackingHeadsUp &&
-                !featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
-
-    private fun isScreenUnlocked() =
-        statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
-
     private fun dump(printWriter: PrintWriter, args: Array<String>) {
         printWriter.println(
             """
                 ScrimShadeTransitionController:
-                  Resources:
-                    inSplitShade: $inSplitShade
-                    isScreenUnlocked: ${isScreenUnlocked()}
-                    splitShadeScrimTransitionDistance: $splitShadeScrimTransitionDistance
                   State:
                     currentPanelState: $currentPanelState
                     lastExpansionFraction: $lastExpansionFraction
                     lastExpansionEvent: $lastExpansionEvent
-            """.trimIndent())
+            """
+                .trimIndent()
+        )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 8a96a47..8edc26d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -16,57 +16,61 @@
 
 package com.android.systemui.shade.ui.viewmodel
 
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
 
 /** Models UI state and handles user input for the shade scene. */
+@SysUISingleton
 class ShadeSceneViewModel
-@AssistedInject
+@Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    lockscreenSceneInteractorFactory: LockscreenSceneInteractor.Factory,
-    @Assisted private val containerName: String,
+    authenticationInteractor: AuthenticationInteractor,
+    private val bouncerInteractor: BouncerInteractor,
 ) {
-    private val lockScreenInteractor: LockscreenSceneInteractor =
-        lockscreenSceneInteractorFactory.create(containerName)
-
     /** The key of the scene we should switch to when swiping up. */
     val upDestinationSceneKey: StateFlow<SceneKey> =
-        lockScreenInteractor.isDeviceLocked
-            .map { isLocked -> upDestinationSceneKey(isLocked = isLocked) }
+        combine(
+                authenticationInteractor.isUnlocked,
+                authenticationInteractor.canSwipeToDismiss,
+            ) { isUnlocked, canSwipeToDismiss ->
+                upDestinationSceneKey(
+                    isUnlocked = isUnlocked,
+                    canSwipeToDismiss = canSwipeToDismiss,
+                )
+            }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue =
                     upDestinationSceneKey(
-                        isLocked = lockScreenInteractor.isDeviceLocked.value,
+                        isUnlocked = authenticationInteractor.isUnlocked.value,
+                        canSwipeToDismiss = authenticationInteractor.canSwipeToDismiss.value,
                     ),
             )
 
     /** Notifies that some content in the shade was clicked. */
     fun onContentClicked() {
-        lockScreenInteractor.dismissLockscreen()
+        bouncerInteractor.showOrUnlockDevice()
     }
 
     private fun upDestinationSceneKey(
-        isLocked: Boolean,
+        isUnlocked: Boolean,
+        canSwipeToDismiss: Boolean,
     ): SceneKey {
-        return if (isLocked) SceneKey.Lockscreen else SceneKey.Gone
-    }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(
-            containerName: String,
-        ): ShadeSceneViewModel
+        return when {
+            canSwipeToDismiss -> SceneKey.Lockscreen
+            isUnlocked -> SceneKey.Gone
+            else -> SceneKey.Lockscreen
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
index e008ec0..d3c19b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt
@@ -19,7 +19,7 @@
 import android.app.PendingIntent
 import com.android.systemui.log.dagger.NotifInteractionLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
index ce730ba..15ff31f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBarFrameLayout.kt
@@ -21,11 +21,12 @@
 
 /**
  * A temporary base class that's shared between our old status bar connectivity view implementations
- * ([StatusBarWifiView], [StatusBarMobileView]) and our new status bar implementations (
- * [ModernStatusBarWifiView], [ModernStatusBarMobileView]).
+ * and our new status bar implementations ([ModernStatusBarWifiView], [ModernStatusBarMobileView]).
  *
  * Once our refactor is over, we should be able to delete this go-between class and the old view
  * class.
+ *
+ * NOTE: the old classes are now deleted, and this class can be removed.
  */
 abstract class BaseStatusBarFrameLayout
 @JvmOverloads
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
index 37140ec..5209767 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
@@ -37,8 +37,8 @@
 
     init {
         inflate(context, R.layout.battery_status_chip, this)
-        roundedContainer = findViewById(R.id.rounded_container)
-        batteryMeterView = findViewById(R.id.battery_meter_view)
+        roundedContainer = requireViewById(R.id.rounded_container)
+        batteryMeterView = requireViewById(R.id.battery_meter_view)
         updateResources()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index a532195..6304c1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -73,7 +73,6 @@
 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;
@@ -169,7 +168,10 @@
     private static final int MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP = 71 << MSG_SHIFT;
     private static final int MSG_SHOW_MEDIA_OUTPUT_SWITCHER = 72 << MSG_SHIFT;
     private static final int MSG_TOGGLE_TASKBAR = 73 << MSG_SHIFT;
-
+    private static final int MSG_SETTING_CHANGED = 74 << MSG_SHIFT;
+    private static final int MSG_LOCK_TASK_MODE_CHANGED = 75 << MSG_SHIFT;
+    private static final int MSG_CONFIRM_IMMERSIVE_PROMPT = 77 << MSG_SHIFT;
+    private static final int MSG_IMMERSIVE_CHANGED = 78 << MSG_SHIFT;
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
     public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1;
@@ -190,7 +192,6 @@
      */
     private int mLastUpdatedImeDisplayId = INVALID_DISPLAY;
     private final DisplayTracker mDisplayTracker;
-    private ProtoTracer mProtoTracer;
     private final @Nullable CommandRegistry mRegistry;
     private final @Nullable DumpHandler mDumpHandler;
 
@@ -500,22 +501,30 @@
          * @see IStatusBar#showMediaOutputSwitcher
          */
         default void showMediaOutputSwitcher(String packageName) {}
+
+        /**
+         * @see IStatusBar#confirmImmersivePrompt
+         */
+        default void confirmImmersivePrompt() {}
+
+        /**
+         * @see IStatusBar#immersiveModeChanged
+         */
+        default void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) {}
     }
 
     @VisibleForTesting
     public CommandQueue(Context context, DisplayTracker displayTracker) {
-        this(context, displayTracker, null, null, null);
+        this(context, displayTracker, null, null);
     }
 
     public CommandQueue(
             Context context,
             DisplayTracker displayTracker,
-            ProtoTracer protoTracer,
             CommandRegistry registry,
             DumpHandler dumpHandler
     ) {
         mDisplayTracker = displayTracker;
-        mProtoTracer = protoTracer;
         mRegistry = registry;
         mDumpHandler = dumpHandler;
         mDisplayTracker.addDisplayChangeCallback(new DisplayTracker.Callback() {
@@ -787,6 +796,23 @@
     }
 
     @Override
+    public void confirmImmersivePrompt() {
+        synchronized (mLock) {
+            mHandler.obtainMessage(MSG_CONFIRM_IMMERSIVE_PROMPT).sendToTarget();
+        }
+    }
+
+    @Override
+    public void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) {
+        synchronized (mLock) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.argi1 = rootDisplayAreaId;
+            args.argi2 = isImmersiveMode ? 1 : 0;
+            mHandler.obtainMessage(MSG_IMMERSIVE_CHANGED, args).sendToTarget();
+        }
+    }
+
+    @Override
     public void appTransitionPending(int displayId) {
         appTransitionPending(displayId, false /* forced */);
     }
@@ -1160,9 +1186,6 @@
     @Override
     public void startTracing() {
         synchronized (mLock) {
-            if (mProtoTracer != null) {
-                mProtoTracer.start();
-            }
             mHandler.obtainMessage(MSG_TRACING_STATE_CHANGED, true).sendToTarget();
         }
     }
@@ -1170,9 +1193,6 @@
     @Override
     public void stopTracing() {
         synchronized (mLock) {
-            if (mProtoTracer != null) {
-                mProtoTracer.stop();
-            }
             mHandler.obtainMessage(MSG_TRACING_STATE_CHANGED, false).sendToTarget();
         }
     }
@@ -1820,6 +1840,19 @@
                         mCallbacks.get(i).showMediaOutputSwitcher(clientPackageName);
                     }
                     break;
+                case MSG_CONFIRM_IMMERSIVE_PROMPT:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).confirmImmersivePrompt();
+                    }
+                    break;
+                case MSG_IMMERSIVE_CHANGED:
+                    args = (SomeArgs) msg.obj;
+                    int rootDisplayAreaId = args.argi1;
+                    boolean isImmersiveMode = args.argi2 != 0;
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).immersiveModeChanged(rootDisplayAreaId, isImmersiveMode);
+                    }
+                    break;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ConnectedDisplayChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ConnectedDisplayChip.kt
new file mode 100644
index 0000000..76636ab8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ConnectedDisplayChip.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.res.Configuration
+import android.util.AttributeSet
+import android.widget.FrameLayout
+import com.android.systemui.R
+import com.android.systemui.statusbar.events.BackgroundAnimatableView
+
+/** Chip that appears in the status bar when an external display is connected. */
+class ConnectedDisplayChip
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null) :
+    FrameLayout(context, attrs), BackgroundAnimatableView {
+
+    private val iconContainer: FrameLayout
+    init {
+        inflate(context, R.layout.connected_display_chip, this)
+        iconContainer = requireViewById(R.id.icons_rounded_container)
+    }
+
+    /**
+     * When animating as a chip in the status bar, we want to animate the width for the rounded
+     * container. We have to subtract our own top and left offset because the bounds come to us as
+     * absolute on-screen bounds.
+     */
+    override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) {
+        iconContainer.setLeftTopRightBottom(l - left, t - top, r - left, b - top)
+    }
+
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        super.onConfigurationChanged(newConfig)
+        updateResources()
+    }
+
+    @SuppressLint("UseCompatLoadingForDrawables")
+    private fun updateResources() {
+        iconContainer.background = context.getDrawable(R.drawable.statusbar_chip_bg)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
new file mode 100644
index 0000000..a7ec02f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
@@ -0,0 +1,590 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
+import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
+import static android.app.StatusBarManager.DISABLE_BACK;
+import static android.app.StatusBarManager.DISABLE_HOME;
+import static android.app.StatusBarManager.DISABLE_RECENT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.ViewRootImpl.CLIENT_IMMERSIVE_CONFIRMATION;
+import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
+import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.database.ContentObserver;
+import android.graphics.Insets;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type;
+import android.view.WindowManager;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.R;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.systemui.util.settings.SecureSettings;
+
+import javax.inject.Inject;
+
+/**
+ *  Helper to manage showing/hiding a confirmation prompt when the navigation bar is hidden
+ *  entering immersive mode.
+ */
+public class ImmersiveModeConfirmation implements CoreStartable, CommandQueue.Callbacks,
+        TaskStackChangeListener {
+    private static final String TAG = "ImmersiveModeConfirm";
+    private static final boolean DEBUG = false;
+    private static final boolean DEBUG_SHOW_EVERY_TIME = false; // super annoying, use with caution
+    private static final String CONFIRMED = "confirmed";
+    private static final int IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE =
+            WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
+
+    private static boolean sConfirmed;
+    private final SecureSettings mSecureSettings;
+
+    private Context mDisplayContext;
+    private final Context mSysUiContext;
+    private final Handler mHandler = new H(Looper.getMainLooper());
+    private long mShowDelayMs = 0L;
+    private final IBinder mWindowToken = new Binder();
+    private final CommandQueue mCommandQueue;
+
+    private ClingWindowView mClingWindow;
+    /** The last {@link WindowManager} that is used to add the confirmation window. */
+    @Nullable
+    private WindowManager mWindowManager;
+    /**
+     * The WindowContext that is registered with {@link #mWindowManager} with options to specify the
+     * {@link RootDisplayArea} to attach the confirmation window.
+     */
+    @Nullable
+    private Context mWindowContext;
+    /**
+     * The root display area feature id that the {@link #mWindowContext} is attaching to.
+     */
+    private int mWindowContextRootDisplayAreaId = FEATURE_UNDEFINED;
+    // Local copy of vr mode enabled state, to avoid calling into VrManager with
+    // the lock held.
+    private boolean mVrModeEnabled = false;
+    private boolean mCanSystemBarsBeShownByUser = true;
+    private int mLockTaskState = LOCK_TASK_MODE_NONE;
+    private boolean mNavBarEmpty;
+
+    private ContentObserver mContentObserver;
+
+    @Inject
+    public ImmersiveModeConfirmation(Context context, CommandQueue commandQueue,
+                                     SecureSettings secureSettings) {
+        mSysUiContext = context;
+        final Display display = mSysUiContext.getDisplay();
+        mDisplayContext = display.getDisplayId() == DEFAULT_DISPLAY
+                ? mSysUiContext : mSysUiContext.createDisplayContext(display);
+        mCommandQueue = commandQueue;
+        mSecureSettings = secureSettings;
+    }
+
+    boolean loadSetting(int currentUserId) {
+        final boolean wasConfirmed = sConfirmed;
+        sConfirmed = false;
+        if (DEBUG) Log.d(TAG, String.format("loadSetting() currentUserId=%d", currentUserId));
+        String value = null;
+        try {
+            value = mSecureSettings.getStringForUser(Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+                    UserHandle.USER_CURRENT);
+            sConfirmed = CONFIRMED.equals(value);
+            if (DEBUG) Log.d(TAG, "Loaded sConfirmed=" + sConfirmed);
+        } catch (Throwable t) {
+            Log.w(TAG, "Error loading confirmations, value=" + value, t);
+        }
+        return sConfirmed != wasConfirmed;
+    }
+
+    private static void saveSetting(Context context) {
+        if (DEBUG) Log.d(TAG, "saveSetting()");
+        try {
+            final String value = sConfirmed ? CONFIRMED : null;
+            Settings.Secure.putStringForUser(context.getContentResolver(),
+                    Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+                    value,
+                    UserHandle.USER_CURRENT);
+            if (DEBUG) Log.d(TAG, "Saved value=" + value);
+        } catch (Throwable t) {
+            Log.w(TAG, "Error saving confirmations, sConfirmed=" + sConfirmed, t);
+        }
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        if (displayId != mSysUiContext.getDisplayId()) {
+            return;
+        }
+        mHandler.removeMessages(H.SHOW);
+        mHandler.removeMessages(H.HIDE);
+        IVrManager vrManager = IVrManager.Stub.asInterface(
+                ServiceManager.getService(Context.VR_SERVICE));
+        if (vrManager != null) {
+            try {
+                vrManager.unregisterListener(mVrStateCallbacks);
+            } catch (RemoteException ex) {
+            }
+        }
+        mCommandQueue.removeCallback(this);
+    }
+
+    private void onSettingChanged(int currentUserId) {
+        final boolean changed = loadSetting(currentUserId);
+        // Remove the window if the setting changes to be confirmed.
+        if (changed && sConfirmed) {
+            mHandler.sendEmptyMessage(H.HIDE);
+        }
+    }
+
+    @Override
+    public void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) {
+        mHandler.removeMessages(H.SHOW);
+        if (isImmersiveMode) {
+            if (DEBUG) Log.d(TAG, "immersiveModeChanged() sConfirmed=" +  sConfirmed);
+            boolean userSetupComplete = (mSecureSettings.getIntForUser(
+                    Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0);
+
+            if ((DEBUG_SHOW_EVERY_TIME || !sConfirmed)
+                    && userSetupComplete
+                    && !mVrModeEnabled
+                    && mCanSystemBarsBeShownByUser
+                    && !mNavBarEmpty
+                    && !UserManager.isDeviceInDemoMode(mDisplayContext)
+                    && (mLockTaskState != LOCK_TASK_MODE_LOCKED)) {
+                final Message msg = mHandler.obtainMessage(
+                        H.SHOW);
+                msg.arg1 = rootDisplayAreaId;
+                mHandler.sendMessageDelayed(msg, mShowDelayMs);
+            }
+        } else {
+            mHandler.sendEmptyMessage(H.HIDE);
+        }
+    }
+
+    @Override
+    public void disable(int displayId, int disableFlag, int disableFlag2, boolean animate) {
+        if (mSysUiContext.getDisplayId() != displayId) {
+            return;
+        }
+        final int disableNavigationBar = (DISABLE_HOME | DISABLE_BACK | DISABLE_RECENT);
+        mNavBarEmpty = (disableFlag & disableNavigationBar) == disableNavigationBar;
+    }
+
+    @Override
+    public void confirmImmersivePrompt() {
+        if (mClingWindow != null) {
+            if (DEBUG) Log.d(TAG, "confirmImmersivePrompt()");
+            mHandler.post(mConfirm);
+        }
+    }
+
+    private void handleHide() {
+        if (mClingWindow != null) {
+            if (DEBUG) Log.d(TAG, "Hiding immersive mode confirmation");
+            if (mWindowManager != null) {
+                try {
+                    mWindowManager.removeView(mClingWindow);
+                } catch (WindowManager.InvalidDisplayException e) {
+                    Log.w(TAG, "Fail to hide the immersive confirmation window because of "
+                            + e);
+                }
+                mWindowManager = null;
+                mWindowContext = null;
+            }
+            mClingWindow = null;
+        }
+    }
+
+    private WindowManager.LayoutParams getClingWindowLayoutParams() {
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
+                PixelFormat.TRANSLUCENT);
+        lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~Type.statusBars());
+        // Trusted overlay so touches outside the touchable area are allowed to pass through
+        lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
+                | WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+                | WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
+        lp.setTitle("ImmersiveModeConfirmation");
+        lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation;
+        lp.token = getWindowToken();
+        return lp;
+    }
+
+    private FrameLayout.LayoutParams getBubbleLayoutParams() {
+        return new FrameLayout.LayoutParams(
+                mSysUiContext.getResources().getDimensionPixelSize(
+                        R.dimen.immersive_mode_cling_width),
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                Gravity.CENTER_HORIZONTAL | Gravity.TOP);
+    }
+
+    /**
+     * @return the window token that's used by all ImmersiveModeConfirmation windows.
+     */
+    IBinder getWindowToken() {
+        return mWindowToken;
+    }
+
+    @Override
+    public void start() {
+        if (CLIENT_TRANSIENT || CLIENT_IMMERSIVE_CONFIRMATION) {
+            mCommandQueue.addCallback(this);
+
+            final Resources r = mSysUiContext.getResources();
+            mShowDelayMs = r.getInteger(R.integer.dock_enter_exit_duration) * 3L;
+            mCanSystemBarsBeShownByUser = !r.getBoolean(
+                    R.bool.config_remoteInsetsControllerControlsSystemBars) || r.getBoolean(
+                    R.bool.config_remoteInsetsControllerSystemBarsCanBeShownByUserAction);
+            IVrManager vrManager = IVrManager.Stub.asInterface(
+                    ServiceManager.getService(Context.VR_SERVICE));
+            if (vrManager != null) {
+                try {
+                    mVrModeEnabled = vrManager.getVrModeState();
+                    vrManager.registerListener(mVrStateCallbacks);
+                    mVrStateCallbacks.onVrStateChanged(mVrModeEnabled);
+                } catch (RemoteException e) {
+                    // Ignore, we cannot do anything if we failed to access vr manager.
+                }
+            }
+            TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
+            mContentObserver = new ContentObserver(mHandler) {
+                @Override
+                public void onChange(boolean selfChange) {
+                    onSettingChanged(mSysUiContext.getUserId());
+                }
+            };
+
+            // Register to listen for changes in Settings.Secure settings.
+            mSecureSettings.registerContentObserverForUser(
+                    Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, mContentObserver,
+                    UserHandle.USER_CURRENT);
+            mSecureSettings.registerContentObserverForUser(
+                    Settings.Secure.USER_SETUP_COMPLETE, mContentObserver,
+                    UserHandle.USER_CURRENT);
+        }
+    }
+
+    private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
+        @Override
+        public void onVrStateChanged(boolean enabled) {
+            mVrModeEnabled = enabled;
+            if (mVrModeEnabled) {
+                mHandler.removeMessages(H.SHOW);
+                mHandler.sendEmptyMessage(H.HIDE);
+            }
+        }
+    };
+
+    private class ClingWindowView extends FrameLayout {
+        private static final int BGCOLOR = 0x80000000;
+        private static final int OFFSET_DP = 96;
+        private static final int ANIMATION_DURATION = 250;
+
+        private final Runnable mConfirm;
+        private final ColorDrawable mColor = new ColorDrawable(0);
+        private final Interpolator mInterpolator;
+        private ValueAnimator mColorAnim;
+        private ViewGroup mClingLayout;
+
+        private Runnable mUpdateLayoutRunnable = new Runnable() {
+            @Override
+            public void run() {
+                if (mClingLayout != null && mClingLayout.getParent() != null) {
+                    mClingLayout.setLayoutParams(getBubbleLayoutParams());
+                }
+            }
+        };
+
+        private ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener =
+                new ViewTreeObserver.OnComputeInternalInsetsListener() {
+                    private final int[] mTmpInt2 = new int[2];
+
+                    @Override
+                    public void onComputeInternalInsets(
+                            ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+                        // Set touchable region to cover the cling layout.
+                        mClingLayout.getLocationInWindow(mTmpInt2);
+                        inoutInfo.setTouchableInsets(
+                                ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+                        inoutInfo.touchableRegion.set(
+                                mTmpInt2[0],
+                                mTmpInt2[1],
+                                mTmpInt2[0] + mClingLayout.getWidth(),
+                                mTmpInt2[1] + mClingLayout.getHeight());
+                    }
+                };
+
+        private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+                    post(mUpdateLayoutRunnable);
+                }
+            }
+        };
+
+        ClingWindowView(Context context, Runnable confirm) {
+            super(context);
+            mConfirm = confirm;
+            setBackground(mColor);
+            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+            mInterpolator = AnimationUtils
+                    .loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in);
+        }
+
+        @Override
+        public void onAttachedToWindow() {
+            super.onAttachedToWindow();
+
+            DisplayMetrics metrics = new DisplayMetrics();
+            mContext.getDisplay().getMetrics(metrics);
+            float density = metrics.density;
+
+            getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener);
+
+            // create the confirmation cling
+            mClingLayout = (ViewGroup)
+                    View.inflate(mSysUiContext, R.layout.immersive_mode_cling, null);
+
+            TypedArray ta = mDisplayContext.obtainStyledAttributes(
+                    new int[]{android.R.attr.colorAccent});
+            int colorAccent = ta.getColor(0, 0);
+            ta.recycle();
+            mClingLayout.setBackgroundColor(colorAccent);
+            ImageView expandMore = mClingLayout.findViewById(R.id.immersive_cling_ic_expand_more);
+            if (expandMore != null) {
+                expandMore.setImageTintList(ColorStateList.valueOf(colorAccent));
+            }
+            ImageView lightBgCirc = mClingLayout.findViewById(R.id.immersive_cling_back_bg_light);
+            if (lightBgCirc != null) {
+                // Set transparency to 50%
+                lightBgCirc.setImageAlpha(128);
+            }
+
+            final Button ok = mClingLayout.findViewById(R.id.ok);
+            ok.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mConfirm.run();
+                }
+            });
+            addView(mClingLayout, getBubbleLayoutParams());
+
+            if (ActivityManager.isHighEndGfx()) {
+                final View cling = mClingLayout;
+                cling.setAlpha(0f);
+                cling.setTranslationY(-OFFSET_DP * density);
+
+                postOnAnimation(new Runnable() {
+                    @Override
+                    public void run() {
+                        cling.animate()
+                                .alpha(1f)
+                                .translationY(0)
+                                .setDuration(ANIMATION_DURATION)
+                                .setInterpolator(mInterpolator)
+                                .withLayer()
+                                .start();
+
+                        mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, BGCOLOR);
+                        mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                            @Override
+                            public void onAnimationUpdate(ValueAnimator animation) {
+                                final int c = (Integer) animation.getAnimatedValue();
+                                mColor.setColor(c);
+                            }
+                        });
+                        mColorAnim.setDuration(ANIMATION_DURATION);
+                        mColorAnim.setInterpolator(mInterpolator);
+                        mColorAnim.start();
+                    }
+                });
+            } else {
+                mColor.setColor(BGCOLOR);
+            }
+
+            mContext.registerReceiver(mReceiver,
+                    new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
+        }
+
+        @Override
+        public void onDetachedFromWindow() {
+            mContext.unregisterReceiver(mReceiver);
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent motion) {
+            return true;
+        }
+
+        @Override
+        public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+            // we will be hiding the nav bar, so layout as if it's already hidden
+            return new WindowInsets.Builder(insets).setInsets(
+                    Type.systemBars(), Insets.NONE).build();
+        }
+    }
+
+    /**
+     * To get window manager for the display.
+     *
+     * @return the WindowManager specifying with the {@code rootDisplayAreaId} to attach the
+     *         confirmation window.
+     */
+    @NonNull
+    private WindowManager createWindowManager(int rootDisplayAreaId) {
+        if (mWindowManager != null) {
+            throw new IllegalStateException(
+                    "Must not create a new WindowManager while there is an existing one");
+        }
+        // Create window context to specify the RootDisplayArea
+        final Bundle options = getOptionsForWindowContext(rootDisplayAreaId);
+        mWindowContextRootDisplayAreaId = rootDisplayAreaId;
+        mWindowContext = mDisplayContext.createWindowContext(
+                IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, options);
+        mWindowManager = mWindowContext.getSystemService(WindowManager.class);
+        return mWindowManager;
+    }
+
+    /**
+     * Returns options that specify the {@link RootDisplayArea} to attach the confirmation window.
+     *         {@code null} if the {@code rootDisplayAreaId} is {@link FEATURE_UNDEFINED}.
+     */
+    @Nullable
+    private Bundle getOptionsForWindowContext(int rootDisplayAreaId) {
+        // In case we don't care which root display area the window manager is specifying.
+        if (rootDisplayAreaId == FEATURE_UNDEFINED) {
+            return null;
+        }
+
+        final Bundle options = new Bundle();
+        options.putInt(KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId);
+        return options;
+    }
+
+    private void handleShow(int rootDisplayAreaId) {
+        if (mClingWindow != null) {
+            if (rootDisplayAreaId == mWindowContextRootDisplayAreaId) {
+                if (DEBUG) Log.d(TAG, "Immersive mode confirmation has already been shown");
+                return;
+            } else {
+                // Hide the existing confirmation before show a new one in the new root.
+                if (DEBUG) Log.d(TAG, "Immersive mode confirmation was shown in a different root");
+                handleHide();
+            }
+        }
+        if (DEBUG) Log.d(TAG, "Showing immersive mode confirmation");
+        mClingWindow = new ClingWindowView(mDisplayContext, mConfirm);
+        // show the confirmation
+        final WindowManager.LayoutParams lp = getClingWindowLayoutParams();
+        try {
+            createWindowManager(rootDisplayAreaId).addView(mClingWindow, lp);
+        } catch (WindowManager.InvalidDisplayException e) {
+            Log.w(TAG, "Fail to show the immersive confirmation window because of " + e);
+        }
+    }
+
+    private final Runnable mConfirm = new Runnable() {
+        @Override
+        public void run() {
+            if (DEBUG) Log.d(TAG, "mConfirm.run()");
+            if (!sConfirmed) {
+                sConfirmed = true;
+                saveSetting(mDisplayContext);
+            }
+            handleHide();
+        }
+    };
+
+    private final class H extends Handler {
+        private static final int SHOW = 1;
+        private static final int HIDE = 2;
+
+        H(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) {
+                return;
+            }
+            switch(msg.what) {
+                case SHOW:
+                    handleShow(msg.arg1);
+                    break;
+                case HIDE:
+                    handleHide();
+                    break;
+            }
+        }
+    }
+
+    @Override
+    public void onLockTaskModeChanged(int lockTaskState) {
+        mLockTaskState = lockTaskState;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 906c5ed..ec66e99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -56,7 +56,6 @@
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
-import android.view.WindowManager.KeyboardShortcutsReceiver;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Button;
 import android.widget.EditText;
@@ -337,6 +336,12 @@
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_HENKAN, "変換");
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_LEFT, "Alt");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_RIGHT, "Alt");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_LEFT, "Ctrl");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_RIGHT, "Ctrl");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_LEFT, "Shift");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_RIGHT, "Shift");
 
         mModifierNames.put(KeyEvent.META_META_ON, "Meta");
         mModifierNames.put(KeyEvent.META_CTRL_ON, "Ctrl");
@@ -411,27 +416,45 @@
         mKeyCharacterMap = mBackupKeyCharacterMap;
     }
 
+    private boolean mAppShortcutsReceived;
+    private boolean mImeShortcutsReceived;
+
     @VisibleForTesting
     void showKeyboardShortcuts(int deviceId) {
         retrieveKeyCharacterMap(deviceId);
-        mWindowManager.requestAppKeyboardShortcuts(new KeyboardShortcutsReceiver() {
-            @Override
-            public void onKeyboardShortcutsReceived(
-                    final List<KeyboardShortcutGroup> result) {
-                // Add specific app shortcuts
-                if (result.isEmpty()) {
-                    mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, false);
-                } else {
-                    mSpecificAppGroup = reMapToKeyboardShortcutMultiMappingGroup(result);
-                    mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, true);
-                }
-                mFullShortsGroup.add(SHORTCUT_SYSTEM_INDEX, mSystemGroup);
-                mFullShortsGroup.add(SHORTCUT_INPUT_INDEX, mInputGroup);
-                mFullShortsGroup.add(SHORTCUT_OPENAPPS_INDEX, mOpenAppsGroup);
-                mFullShortsGroup.add(SHORTCUT_SPECIFICAPP_INDEX, mSpecificAppGroup);
-                showKeyboardShortcutSearchList(mFullShortsGroup);
+        mAppShortcutsReceived = false;
+        mImeShortcutsReceived = false;
+        mWindowManager.requestAppKeyboardShortcuts(result -> {
+            // Add specific app shortcuts
+            if (result.isEmpty()) {
+                mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, false);
+            } else {
+                mSpecificAppGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result));
+                mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, true);
+            }
+            mAppShortcutsReceived = true;
+            if (mImeShortcutsReceived) {
+                mergeAndShowKeyboardShortcutsGroups();
             }
         }, deviceId);
+        mWindowManager.requestImeKeyboardShortcuts(result -> {
+            // Add specific Ime shortcuts
+            if (!result.isEmpty()) {
+                mInputGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result));
+            }
+            mImeShortcutsReceived = true;
+            if (mAppShortcutsReceived) {
+                mergeAndShowKeyboardShortcutsGroups();
+            }
+        }, deviceId);
+    }
+
+    private void mergeAndShowKeyboardShortcutsGroups() {
+        mFullShortsGroup.add(SHORTCUT_SYSTEM_INDEX, mSystemGroup);
+        mFullShortsGroup.add(SHORTCUT_INPUT_INDEX, mInputGroup);
+        mFullShortsGroup.add(SHORTCUT_OPENAPPS_INDEX, mOpenAppsGroup);
+        mFullShortsGroup.add(SHORTCUT_SPECIFICAPP_INDEX, mSpecificAppGroup);
+        showKeyboardShortcutSearchList(mFullShortsGroup);
     }
 
     // The original data structure is only for 1-to-1 shortcut mapping, so remap the old
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 43fbc7c..a3fd82e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -56,7 +56,6 @@
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
-import android.view.WindowManager.KeyboardShortcutsReceiver;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -129,6 +128,9 @@
     private KeyCharacterMap mKeyCharacterMap;
     private KeyCharacterMap mBackupKeyCharacterMap;
 
+    @Nullable private List<KeyboardShortcutGroup> mReceivedAppShortcutGroups = null;
+    @Nullable private List<KeyboardShortcutGroup> mReceivedImeShortcutGroups = null;
+
     @VisibleForTesting
     KeyboardShortcuts(Context context, WindowManager windowManager) {
         this.mContext = new ContextThemeWrapper(
@@ -324,6 +326,12 @@
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_HENKAN, "変換");
         mSpecialCharacterNames.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_LEFT, "Alt");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_RIGHT, "Alt");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_LEFT, "Ctrl");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_RIGHT, "Ctrl");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_LEFT, "Shift");
+        mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_RIGHT, "Shift");
 
         mModifierNames.put(KeyEvent.META_META_ON, "Meta");
         mModifierNames.put(KeyEvent.META_CTRL_ON, "Ctrl");
@@ -382,18 +390,36 @@
     @VisibleForTesting
     void showKeyboardShortcuts(int deviceId) {
         retrieveKeyCharacterMap(deviceId);
-        mWindowManager.requestAppKeyboardShortcuts(new KeyboardShortcutsReceiver() {
-            @Override
-            public void onKeyboardShortcutsReceived(
-                    final List<KeyboardShortcutGroup> result) {
-                result.add(getSystemShortcuts());
-                final KeyboardShortcutGroup appShortcuts = getDefaultApplicationShortcuts();
-                if (appShortcuts != null) {
-                    result.add(appShortcuts);
-                }
-                showKeyboardShortcutsDialog(result);
-            }
-        }, deviceId);
+        mReceivedAppShortcutGroups = null;
+        mReceivedImeShortcutGroups = null;
+        mWindowManager.requestAppKeyboardShortcuts(
+                result -> {
+                    mReceivedAppShortcutGroups = result;
+                    maybeMergeAndShowKeyboardShortcuts();
+                }, deviceId);
+        mWindowManager.requestImeKeyboardShortcuts(
+                result -> {
+                    mReceivedImeShortcutGroups = result;
+                    maybeMergeAndShowKeyboardShortcuts();
+                }, deviceId);
+    }
+
+    private void maybeMergeAndShowKeyboardShortcuts() {
+        if (mReceivedAppShortcutGroups == null || mReceivedImeShortcutGroups == null) {
+            return;
+        }
+        List<KeyboardShortcutGroup> shortcutGroups = mReceivedAppShortcutGroups;
+        shortcutGroups.addAll(mReceivedImeShortcutGroups);
+        mReceivedAppShortcutGroups = null;
+        mReceivedImeShortcutGroups = null;
+
+        final KeyboardShortcutGroup defaultAppShortcuts =
+                getDefaultApplicationShortcuts();
+        if (defaultAppShortcuts != null) {
+            shortcutGroups.add(defaultAppShortcuts);
+        }
+        shortcutGroups.add(getSystemShortcuts());
+        showKeyboardShortcutsDialog(shortcutGroups);
     }
 
     private void dismissKeyboardShortcuts() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 03c4589..feb0258 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -29,6 +29,7 @@
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.IMPORTANT_MSG_MIN_DURATION;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
@@ -41,8 +42,9 @@
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
-import static com.android.systemui.log.LogLevel.ERROR;
+import static com.android.systemui.log.core.LogLevel.ERROR;
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.app.AlarmManager;
 import android.app.admin.DevicePolicyManager;
@@ -56,7 +58,6 @@
 import android.graphics.Color;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.face.FaceManager;
-import android.hardware.fingerprint.FingerprintManager;
 import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -86,6 +87,8 @@
 import com.android.systemui.R;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FaceHelpMessageDeferral;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -95,8 +98,9 @@
 import com.android.systemui.keyguard.KeyguardIndication;
 import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
 import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.util.IndicationHelper;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
@@ -113,6 +117,7 @@
 import java.text.NumberFormat;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 
@@ -148,6 +153,7 @@
     private final AuthController mAuthController;
     private final KeyguardLogger mKeyguardLogger;
     private final UserTracker mUserTracker;
+    private final BouncerMessageInteractor mBouncerMessageInteractor;
     private ViewGroup mIndicationArea;
     private KeyguardIndicationTextView mTopIndicationView;
     private KeyguardIndicationTextView mLockScreenIndicationView;
@@ -169,7 +175,7 @@
     public KeyguardIndicationRotateTextViewController mRotateTextViewController;
     private BroadcastReceiver mBroadcastReceiver;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-
+    private KeyguardInteractor mKeyguardInteractor;
     private String mPersistentUnlockMessage;
     private String mAlignmentIndication;
     private CharSequence mTrustGrantedIndication;
@@ -203,7 +209,17 @@
     private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
 
     private boolean mDozing;
+    private boolean mIsActiveDreamLockscreenHosted;
     private final ScreenLifecycle mScreenLifecycle;
+    @VisibleForTesting
+    final Consumer<Boolean> mIsActiveDreamLockscreenHostedCallback =
+            (Boolean isLockscreenHosted) -> {
+                if (mIsActiveDreamLockscreenHosted == isLockscreenHosted) {
+                    return;
+                }
+                mIsActiveDreamLockscreenHosted = isLockscreenHosted;
+                updateDeviceEntryIndication(false);
+            };
     private final ScreenLifecycle.Observer mScreenObserver =
             new ScreenLifecycle.Observer() {
         @Override
@@ -225,7 +241,8 @@
     // triggered while the device is asleep
     private final AlarmTimeout mHideTransientMessageHandler;
     private final AlarmTimeout mHideBiometricMessageHandler;
-    private FeatureFlags mFeatureFlags;
+    private final FeatureFlags mFeatureFlags;
+    private final IndicationHelper mIndicationHelper;
 
     /**
      * Creates a new KeyguardIndicationController and registers callbacks.
@@ -256,7 +273,10 @@
             AlternateBouncerInteractor alternateBouncerInteractor,
             AlarmManager alarmManager,
             UserTracker userTracker,
-            FeatureFlags flags
+            BouncerMessageInteractor bouncerMessageInteractor,
+            FeatureFlags flags,
+            IndicationHelper indicationHelper,
+            KeyguardInteractor keyguardInteractor
     ) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -281,7 +301,10 @@
         mScreenLifecycle.addObserver(mScreenObserver);
         mAlternateBouncerInteractor = alternateBouncerInteractor;
         mUserTracker = userTracker;
+        mBouncerMessageInteractor = bouncerMessageInteractor;
         mFeatureFlags = flags;
+        mIndicationHelper = indicationHelper;
+        mKeyguardInteractor = keyguardInteractor;
 
         mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
         mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>();
@@ -364,6 +387,10 @@
             intentFilter.addAction(Intent.ACTION_USER_REMOVED);
             mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, intentFilter);
         }
+        if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+            collectFlow(mIndicationArea, mKeyguardInteractor.isActiveDreamLockscreenHosted(),
+                    mIsActiveDreamLockscreenHostedCallback);
+        }
     }
 
     /**
@@ -871,6 +898,12 @@
             return;
         }
 
+        // Device is dreaming and the dream is hosted in lockscreen
+        if (mIsActiveDreamLockscreenHosted) {
+            mIndicationArea.setVisibility(GONE);
+            return;
+        }
+
         // A few places might need to hide the indication, so always start by making it visible
         mIndicationArea.setVisibility(VISIBLE);
 
@@ -1068,6 +1101,7 @@
         pw.println("  mBiometricMessageFollowUp: " + mBiometricMessageFollowUp);
         pw.println("  mBatteryLevel: " + mBatteryLevel);
         pw.println("  mBatteryPresent: " + mBatteryPresent);
+        pw.println("  mIsActiveDreamLockscreenHosted: " + mIsActiveDreamLockscreenHosted);
         pw.println("  AOD text: " + (
                 mTopIndicationView == null ? null : mTopIndicationView.getText()));
         pw.println("  computePowerIndication(): " + computePowerIndication());
@@ -1162,6 +1196,11 @@
                         msgId,
                         helpString);
             } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
+                if (biometricSourceType == FINGERPRINT && !fpAuthFailed) {
+                    mBouncerMessageInteractor.setFingerprintAcquisitionMessage(helpString);
+                } else if (faceAuthSoftError) {
+                    mBouncerMessageInteractor.setFaceAcquisitionMessage(helpString);
+                }
                 mStatusBarKeyguardViewManager.setKeyguardMessage(helpString,
                         mInitialTextColorState);
             } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
@@ -1217,6 +1256,8 @@
             if (biometricSourceType == FACE) {
                 mFaceAcquiredMessageDeferral.reset();
             }
+            mBouncerMessageInteractor.setFaceAcquisitionMessage(null);
+            mBouncerMessageInteractor.setFingerprintAcquisitionMessage(null);
         }
 
         @Override
@@ -1237,18 +1278,20 @@
             } else if (biometricSourceType == FINGERPRINT) {
                 onFingerprintAuthError(msgId, errString);
             }
+            mBouncerMessageInteractor.setFaceAcquisitionMessage(null);
+            mBouncerMessageInteractor.setFingerprintAcquisitionMessage(null);
         }
 
         private void onFaceAuthError(int msgId, String errString) {
             CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
             mFaceAcquiredMessageDeferral.reset();
-            if (shouldSuppressFaceError(msgId)) {
-                mKeyguardLogger.logBiometricMessage("suppressingFaceError", msgId, errString);
+            if (mIndicationHelper.shouldSuppressErrorMsg(FACE, msgId)) {
+                mKeyguardLogger.logBiometricMessage("KIC suppressingFaceError", msgId, errString);
                 return;
             }
             if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
                 handleFaceAuthTimeoutError(deferredFaceMessage);
-            } else if (isLockoutError(msgId)) {
+            } else if (mIndicationHelper.isFaceLockoutErrorMsg(msgId)) {
                 handleFaceLockoutError(errString);
             } else {
                 showErrorMessageNowOrLater(errString, null);
@@ -1256,8 +1299,8 @@
         }
 
         private void onFingerprintAuthError(int msgId, String errString) {
-            if (shouldSuppressFingerprintError(msgId)) {
-                mKeyguardLogger.logBiometricMessage("suppressingFingerprintError",
+            if (mIndicationHelper.shouldSuppressErrorMsg(FINGERPRINT, msgId)) {
+                mKeyguardLogger.logBiometricMessage("KIC suppressingFingerprintError",
                         msgId,
                         errString);
             } else {
@@ -1265,19 +1308,6 @@
             }
         }
 
-        private boolean shouldSuppressFingerprintError(int msgId) {
-            return ((isPrimaryAuthRequired() && !isLockoutError(msgId))
-                    || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
-                    || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
-                    || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED);
-        }
-
-        private boolean shouldSuppressFaceError(int msgId) {
-            return ((isPrimaryAuthRequired() && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
-                    || msgId == FaceManager.FACE_ERROR_CANCELED
-                    || msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS);
-        }
-
         @Override
         public void onTrustChanged(int userId) {
             if (!isCurrentUser(userId)) return;
@@ -1321,6 +1351,8 @@
                     showActionToUnlock();
                 }
             }
+            mBouncerMessageInteractor.setFaceAcquisitionMessage(null);
+            mBouncerMessageInteractor.setFingerprintAcquisitionMessage(null);
         }
 
         @Override
@@ -1399,11 +1431,6 @@
         return mContext.getString(followupMsgId);
     }
 
-    private static boolean isLockoutError(int msgId) {
-        return msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
-                || msgId == FaceManager.FACE_ERROR_LOCKOUT;
-    }
-
     private void handleFaceAuthTimeoutError(@Nullable CharSequence deferredFaceMessage) {
         mKeyguardLogger.logBiometricMessage("deferred message after face auth timeout",
                 null, String.valueOf(deferredFaceMessage));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
index dcd9dc3..7a989cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
@@ -19,7 +19,6 @@
 import android.view.View;
 
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -52,7 +51,6 @@
         mActivatableNotificationViewController = activatableNotificationViewController;
         mKeyguardBypassController = keyguardBypassController;
         mStatusBarStateController = statusBarStateController;
-        mView.setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
         mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 823bb35..3120128 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -14,9 +14,11 @@
 import android.os.Trace
 import android.util.AttributeSet
 import android.util.MathUtils.lerp
+import android.view.MotionEvent
 import android.view.View
 import android.view.animation.PathInterpolator
 import com.android.app.animation.Interpolators
+import com.android.systemui.shade.TouchLogger
 import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
 import com.android.systemui.util.getColorWithAlpha
 import com.android.systemui.util.leak.RotationUtils
@@ -234,6 +236,8 @@
     }
 }
 
+private const val TAG = "LightRevealScrim"
+
 /**
  * Scrim view that partially reveals the content underneath it using a [RadialGradient] with a
  * transparent center. The center position, size, and stops of the gradient can be manipulated to
@@ -419,15 +423,14 @@
         revealGradientCenter.y = top + (revealGradientHeight / 2f)
     }
 
-    override fun onDraw(canvas: Canvas?) {
+    override fun onDraw(canvas: Canvas) {
         if (
-            canvas == null ||
-                revealGradientWidth <= 0 ||
-                revealGradientHeight <= 0 ||
-                revealAmount == 0f
+            revealGradientWidth <= 0 ||
+            revealGradientHeight <= 0 ||
+            revealAmount == 0f
         ) {
             if (revealAmount < 1f) {
-                canvas?.drawColor(revealGradientEndColor)
+                canvas.drawColor(revealGradientEndColor)
             }
             return
         }
@@ -447,6 +450,10 @@
         canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), gradientPaint)
     }
 
+    override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+        return TouchLogger.logDispatchTouch(TAG, event, super.dispatchTouchEvent(event))
+    }
+
     private fun setPaintColorFilter() {
         gradientPaint.colorFilter =
             PorterDuffColorFilter(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index c098f45..672796a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -6,7 +6,6 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.os.PowerManager
-import android.os.SystemClock
 import android.util.IndentingPrintWriter
 import android.util.MathUtils
 import android.view.MotionEvent
@@ -30,8 +29,10 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -76,6 +77,8 @@
     dumpManager: DumpManager,
     qsTransitionControllerFactory: LockscreenShadeQsTransitionController.Factory,
     private val shadeRepository: ShadeRepository,
+    private val shadeInteractor: ShadeInteractor,
+    private val powerInteractor: PowerInteractor,
 ) : Dumpable {
     private var pulseHeight: Float = 0f
     @get:VisibleForTesting
@@ -278,11 +281,7 @@
         // Bind the click listener of the shelf to go to the full shade
         notificationShelfController.setOnClickListener {
             if (statusBarStateController.state == StatusBarState.KEYGUARD) {
-                centralSurfaces.wakeUpIfDozing(
-                        SystemClock.uptimeMillis(),
-                        "SHADE_CLICK",
-                        PowerManager.WAKE_REASON_GESTURE,
-                )
+                powerInteractor.wakeUpIfDozing("SHADE_CLICK", PowerManager.WAKE_REASON_GESTURE)
                 goToLockedShade(it)
             }
         }
@@ -475,7 +474,7 @@
         }
         if (endlistener != null) {
             dragDownAnimator.addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     endlistener.invoke()
                 }
             })
@@ -561,7 +560,7 @@
         animationHandler: ((Long) -> Unit)? = null,
         cancelAction: Runnable? = null
     ) {
-        if (centralSurfaces.isShadeDisabled) {
+        if (!shadeInteractor.isShadeEnabled.value) {
             cancelAction?.run()
             logger.logShadeDisabledOnGoToLockedShade()
             return
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
index 750272d..17b4e3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
@@ -66,7 +66,7 @@
                 inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */)
                 oldIn.recycle()
             }
-            val outBitmap = Bitmap.createBitmap(inBitmap.width, inBitmap.height,
+            val outBitmap = Bitmap.createBitmap(inBitmap?.width ?: 0, inBitmap?.height ?: 0,
                     Bitmap.Config.ARGB_8888)
 
             input = Allocation.createFromBitmap(renderScript, inBitmap,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
index 39d7d66..27f42c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
@@ -16,11 +16,11 @@
 
 package com.android.systemui.statusbar;
 
-import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.scene.ui.view.WindowRootView;
 
 /**
  * Calculates insets for the notification shade window view.
  */
 public abstract class NotificationInsetsController
-        implements NotificationShadeWindowView.LayoutInsetsController {
+        implements WindowRootView.LayoutInsetsController {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index e6e3e7e..ea5ca27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,6 +19,7 @@
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
+import android.app.ActivityOptions;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.admin.DevicePolicyManager;
@@ -158,7 +159,11 @@
                     final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
                     if (intentSender != null) {
                         try {
-                            mContext.startIntentSender(intentSender, null, 0, 0, 0);
+                            ActivityOptions options = ActivityOptions.makeBasic();
+                            options.setPendingIntentBackgroundActivityStartMode(
+                                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                            mContext.startIntentSender(intentSender, null, 0, 0, 0,
+                                    options.toBundle());
                         } catch (IntentSender.SendIntentException e) {
                             /* ignore */
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 1c9bc60..5dcf6d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -43,6 +43,11 @@
     void onUserSwitched(int newUserId);
 
     /**
+     * Called when a new row is created and bound to a notification.
+     */
+    void onBindRow(ExpandableNotificationRow row);
+
+    /**
      * @return true iff the device is in vr mode
      */
     boolean isDeviceInVrMode();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index c519115..da84afe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -27,7 +27,6 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserManager;
 import android.service.notification.StatusBarNotification;
@@ -52,6 +51,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
@@ -60,19 +60,15 @@
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.policy.RemoteInputView;
 import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.ListenerSet;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.function.Consumer;
 
 /**
@@ -95,10 +91,8 @@
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final SmartReplyController mSmartReplyController;
     private final NotificationVisibilityProvider mVisibilityProvider;
+    private final PowerInteractor mPowerInteractor;
     private final ActionClickLogger mLogger;
-
-    private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
-
     protected final Context mContext;
     protected final NotifPipelineFlags mNotifPipelineFlags;
     private final UserManager mUserManager;
@@ -122,10 +116,8 @@
         @Override
         public boolean onInteraction(
                 View view, PendingIntent pendingIntent, RemoteViews.RemoteResponse response) {
-            mCentralSurfacesOptionalLazy.get().ifPresent(
-                    centralSurfaces -> centralSurfaces.wakeUpIfDozing(
-                            SystemClock.uptimeMillis(), "NOTIFICATION_CLICK",
-                            PowerManager.WAKE_REASON_GESTURE));
+            mPowerInteractor.wakeUpIfDozing(
+                    "NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE);
 
             final NotificationEntry entry = getNotificationForParent(view.getParent());
             mLogger.logInitialClick(entry, pendingIntent);
@@ -259,7 +251,7 @@
             NotificationLockscreenUserManager lockscreenUserManager,
             SmartReplyController smartReplyController,
             NotificationVisibilityProvider visibilityProvider,
-            Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
+            PowerInteractor powerInteractor,
             StatusBarStateController statusBarStateController,
             RemoteInputUriController remoteInputUriController,
             RemoteInputControllerLogger remoteInputControllerLogger,
@@ -271,7 +263,7 @@
         mLockscreenUserManager = lockscreenUserManager;
         mSmartReplyController = smartReplyController;
         mVisibilityProvider = visibilityProvider;
-        mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+        mPowerInteractor = powerInteractor;
         mLogger = logger;
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -402,7 +394,7 @@
         while (p != null) {
             if (p instanceof View) {
                 View pv = (View) p;
-                if (pv.isRootNamespace()) {
+                if (pv.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
                     riv = findRemoteInputView(pv);
                     row = (ExpandableNotificationRow) pv.getTag(R.id.row_tag_for_content_view);
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 0e20df6..6ad1395 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -272,7 +272,7 @@
                             blurUtils.blurRadiusOfRatio(animation.animatedValue as Float)
                 }
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         keyguardAnimator = null
                         wakeAndUnlockBlurRadius = 0f
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 2ca0b00..5ac542b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -60,11 +60,11 @@
     default void attach() {}
 
     /** Sets the notification shade view. */
-    default void setNotificationShadeView(ViewGroup view) {}
+    default void setWindowRootView(ViewGroup view) {}
 
     /** Gets the notification shade view. */
     @Nullable
-    default ViewGroup getNotificationShadeView() {
+    default ViewGroup getWindowRootView() {
         return null;
     }
 
@@ -112,9 +112,6 @@
     /** Sets the state of whether heads up is showing or not. */
     default void setHeadsUpShowing(boolean showing) {}
 
-    /** Sets whether the wallpaper supports ambient mode or not. */
-    default void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {}
-
     /** Gets whether the wallpaper is showing or not. */
     default boolean isShowingWallpaper() {
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 9d7f3be..3f37c60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -24,7 +24,6 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.IndentingPrintWriter;
-import android.util.Log;
 import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewGroup;
@@ -40,8 +39,8 @@
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.R;
 import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.ViewRefactorFlag;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -97,8 +96,10 @@
     private float mCornerAnimationDistance;
     private NotificationShelfController mController;
     private float mActualWidth = -1;
-    private boolean mSensitiveRevealAnimEndabled;
-    private boolean mShelfRefactorFlagEnabled;
+    private final ViewRefactorFlag mSensitiveRevealAnim =
+            new ViewRefactorFlag(Flags.SENSITIVE_REVEAL_ANIM);
+    private final ViewRefactorFlag mShelfRefactor =
+            new ViewRefactorFlag(Flags.NOTIFICATION_SHELF_REFACTOR);
     private boolean mCanModifyColorOfNotifications;
     private boolean mCanInteract;
     private NotificationStackScrollLayout mHostLayout;
@@ -132,7 +133,7 @@
 
     public void bind(AmbientState ambientState,
                      NotificationStackScrollLayoutController hostLayoutController) {
-        assertRefactorFlagDisabled();
+        mShelfRefactor.assertDisabled();
         mAmbientState = ambientState;
         mHostLayoutController = hostLayoutController;
         hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
@@ -142,7 +143,7 @@
 
     public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout,
             NotificationRoundnessManager roundnessManager) {
-        if (!checkRefactorFlagEnabled()) return;
+        if (!mShelfRefactor.expectEnabled()) return;
         mAmbientState = ambientState;
         mHostLayout = hostLayout;
         mRoundnessManager = roundnessManager;
@@ -226,9 +227,7 @@
                 if (ambientState.isBouncerInTransit()) {
                     viewState.setAlpha(aboutToShowBouncerProgress(expansion));
                 } else {
-                    FeatureFlags flags = ambientState.getFeatureFlags();
-                    if (ambientState.isSmallScreen() || !flags.isEnabled(
-                            Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+                    if (ambientState.isSmallScreen()) {
                         viewState.setAlpha(ShadeInterpolation.getContentAlpha(expansion));
                     } else {
                         LargeScreenShadeInterpolator interpolator =
@@ -265,25 +264,25 @@
                     viewState.hidden = true;
                 }
             }
-
-            final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
-            if (mSensitiveRevealAnimEndabled && viewState.hidden) {
-                // if the shelf is hidden, position it at the end of the stack (plus the clip
-                // padding), such that when it appears animated, it will smoothly move in from the
-                // bottom, without jump cutting any notifications
-                viewState.setYTranslation(stackEnd + mPaddingBetweenElements);
-            } else {
-                viewState.setYTranslation(stackEnd - viewState.height);
-            }
         } else {
             viewState.hidden = true;
             viewState.location = ExpandableViewState.LOCATION_GONE;
             viewState.hasItemsInStableShelf = false;
         }
+
+        final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
+        if (mSensitiveRevealAnim.isEnabled() && viewState.hidden) {
+            // if the shelf is hidden, position it at the end of the stack (plus the clip
+            // padding), such that when it appears animated, it will smoothly move in from the
+            // bottom, without jump cutting any notifications
+            viewState.setYTranslation(stackEnd + mPaddingBetweenElements);
+        } else {
+            viewState.setYTranslation(stackEnd - viewState.height);
+        }
     }
 
     private int getSpeedBumpIndex() {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return mHostLayout.getSpeedBumpIndex();
         } else {
             return mHostLayoutController.getSpeedBumpIndex();
@@ -417,7 +416,7 @@
                     expandingAnimated, isLastChild, shelfClipStart);
 
             // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
-            if ((!mSensitiveRevealAnimEndabled && ((isLastChild && !child.isInShelf())
+            if ((!mSensitiveRevealAnim.isEnabled() && ((isLastChild && !child.isInShelf())
                     || backgroundForceHidden)) || aboveShelf) {
                 notificationClipEnd = shelfStart + getIntrinsicHeight();
             } else {
@@ -466,7 +465,7 @@
                 // if the shelf is visible, but if the shelf is hidden, it causes incorrect curling.
                 // notificationClipEnd handles the discrepancy between a visible and hidden shelf,
                 // so we use that when on the keyguard (and while animating away) to reduce curling.
-                final float keyguardSafeShelfStart = !mSensitiveRevealAnimEndabled
+                final float keyguardSafeShelfStart = !mSensitiveRevealAnim.isEnabled()
                         && mAmbientState.isOnKeyguard() ? notificationClipEnd : shelfStart;
                 updateCornerRoundnessOnScroll(anv, viewStart, keyguardSafeShelfStart);
             }
@@ -508,7 +507,7 @@
     }
 
     private ExpandableView getHostLayoutChildAt(int index) {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return (ExpandableView) mHostLayout.getChildAt(index);
         } else {
             return mHostLayoutController.getChildAt(index);
@@ -516,7 +515,7 @@
     }
 
     private int getHostLayoutChildCount() {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return mHostLayout.getChildCount();
         } else {
             return mHostLayoutController.getChildCount();
@@ -524,7 +523,7 @@
     }
 
     private boolean canModifyColorOfNotifications() {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded();
         } else {
             return mController.canModifyColorOfNotifications();
@@ -587,7 +586,7 @@
     }
 
     private boolean isViewAffectedBySwipe(ExpandableView expandableView) {
-        if (!mShelfRefactorFlagEnabled) {
+        if (!mShelfRefactor.isEnabled()) {
             return mHostLayoutController.isViewAffectedBySwipe(expandableView);
         } else {
             return mRoundnessManager.isViewAffectedBySwipe(expandableView);
@@ -611,7 +610,7 @@
     }
 
     private View getHostLayoutTransientView(int index) {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return mHostLayout.getTransientView(index);
         } else {
             return mHostLayoutController.getTransientView(index);
@@ -619,7 +618,7 @@
     }
 
     private int getHostLayoutTransientViewCount() {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return mHostLayout.getTransientViewCount();
         } else {
             return mHostLayoutController.getTransientViewCount();
@@ -965,7 +964,7 @@
 
     @Override
     public void onStateChanged(int newState) {
-        assertRefactorFlagDisabled();
+        mShelfRefactor.assertDisabled();
         mStatusBarState = newState;
         updateInteractiveness();
     }
@@ -979,7 +978,7 @@
     }
 
     private boolean canInteract() {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return mCanInteract;
         } else {
             return mStatusBarState == StatusBarState.KEYGUARD;
@@ -1022,32 +1021,18 @@
         return false;
     }
 
-    private void assertRefactorFlagDisabled() {
-        if (mShelfRefactorFlagEnabled) {
-            NotificationShelfController.throwIllegalFlagStateError(false);
-        }
-    }
-
-    private boolean checkRefactorFlagEnabled() {
-        if (!mShelfRefactorFlagEnabled) {
-            Log.wtf(TAG,
-                    "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is disabled.");
-        }
-        return mShelfRefactorFlagEnabled;
-    }
-
     public void setController(NotificationShelfController notificationShelfController) {
-        assertRefactorFlagDisabled();
+        mShelfRefactor.assertDisabled();
         mController = notificationShelfController;
     }
 
     public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
-        if (!checkRefactorFlagEnabled()) return;
+        if (!mShelfRefactor.expectEnabled()) return;
         mCanModifyColorOfNotifications = canModifyColorOfNotifications;
     }
 
     public void setCanInteract(boolean canInteract) {
-        if (!checkRefactorFlagEnabled()) return;
+        if (!mShelfRefactor.expectEnabled()) return;
         mCanInteract = canInteract;
         updateInteractiveness();
     }
@@ -1057,27 +1042,15 @@
     }
 
     private int getIndexOfViewInHostLayout(ExpandableView child) {
-        if (mShelfRefactorFlagEnabled) {
+        if (mShelfRefactor.isEnabled()) {
             return mHostLayout.indexOfChild(child);
         } else {
             return mHostLayoutController.indexOfChild(child);
         }
     }
 
-    /**
-     * Set whether the sensitive reveal animation feature flag is enabled
-     * @param enabled true if enabled
-     */
-    public void setSensitiveRevealAnimEndabled(boolean enabled) {
-        mSensitiveRevealAnimEndabled = enabled;
-    }
-
-    public void setRefactorFlagEnabled(boolean enabled) {
-        mShelfRefactorFlagEnabled = enabled;
-    }
-
     public void requestRoundnessResetFor(ExpandableView child) {
-        if (!checkRefactorFlagEnabled()) return;
+        if (!mShelfRefactor.expectEnabled()) return;
         child.requestRoundnessReset(SHELF_SCROLL);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
index 1619dda..8a3e217 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
@@ -16,11 +16,8 @@
 
 package com.android.systemui.statusbar
 
-import android.util.Log
 import android.view.View
 import android.view.View.OnClickListener
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
@@ -49,29 +46,4 @@
 
     /** @see View.setOnClickListener */
     fun setOnClickListener(listener: OnClickListener)
-
-    companion object {
-        @JvmStatic
-        fun assertRefactorFlagDisabled(featureFlags: FeatureFlags) {
-            if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
-                throwIllegalFlagStateError(expected = false)
-            }
-        }
-
-        @JvmStatic
-        fun checkRefactorFlagEnabled(featureFlags: FeatureFlags): Boolean =
-            featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR).also { enabled ->
-                if (!enabled) {
-                    Log.wtf("NotifShelf", getErrorMessage(expected = true))
-                }
-            }
-
-        @JvmStatic
-        fun throwIllegalFlagStateError(expected: Boolean): Nothing =
-            error(getErrorMessage(expected))
-
-        private fun getErrorMessage(expected: Boolean): String =
-            "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is " +
-                if (expected) "disabled" else "enabled"
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
index 59afb18..9702bfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateController.java
@@ -18,19 +18,19 @@
 
 import android.view.View;
 
+import com.android.systemui.display.data.repository.DisplayMetricsRepository;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 /**
  * Calculates and moves the QS frame vertically.
  */
 public abstract class QsFrameTranslateController {
 
-    protected CentralSurfaces mCentralSurfaces;
+    protected DisplayMetricsRepository mDisplayMetricsRepository;
 
-    public QsFrameTranslateController(CentralSurfaces centralSurfaces) {
-        mCentralSurfaces = centralSurfaces;
+    public QsFrameTranslateController(DisplayMetricsRepository displayMetricsRepository) {
+        mDisplayMetricsRepository = displayMetricsRepository;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
index 85b522c..e429b8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QsFrameTranslateImpl.java
@@ -19,9 +19,9 @@
 import android.view.View;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.display.data.repository.DisplayMetricsRepository;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 import javax.inject.Inject;
 
@@ -34,8 +34,8 @@
 public class QsFrameTranslateImpl extends QsFrameTranslateController {
 
     @Inject
-    public QsFrameTranslateImpl(CentralSurfaces centralSurfaces) {
-        super(centralSurfaces);
+    public QsFrameTranslateImpl(DisplayMetricsRepository displayMetricsRepository) {
+        super(displayMetricsRepository);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 91c08a0..fbbee53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -26,6 +26,7 @@
 import android.app.ActivityManager;
 import android.app.Notification;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
@@ -41,18 +42,19 @@
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
-import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Log;
 import android.util.Property;
 import android.util.TypedValue;
 import android.view.ViewDebug;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.Interpolator;
 
 import androidx.core.graphics.ColorUtils;
 
 import com.android.app.animation.Interpolators;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.R;
@@ -131,10 +133,12 @@
         }
     };
 
-    private boolean mAlwaysScaleIcon;
     private int mStatusBarIconDrawingSizeIncreased = 1;
-    private int mStatusBarIconDrawingSize = 1;
-    private int mStatusBarIconSize = 1;
+    @VisibleForTesting int mStatusBarIconDrawingSize = 1;
+
+    @VisibleForTesting int mOriginalStatusBarIconSize = 1;
+    @VisibleForTesting int mNewStatusBarIconSize = 1;
+    @VisibleForTesting float mScaleToFitNewIconSize = 1;
     private StatusBarIcon mIcon;
     @ViewDebug.ExportedProperty private String mSlot;
     private Drawable mNumberBackground;
@@ -144,7 +148,7 @@
     private String mNumberText;
     private StatusBarNotification mNotification;
     private final boolean mBlocked;
-    private int mDensity;
+    private Configuration mConfiguration;
     private boolean mNightMode;
     private float mIconScale = 1.0f;
     private final Paint mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -156,7 +160,6 @@
     private ObjectAnimator mIconAppearAnimator;
     private ObjectAnimator mDotAnimator;
     private float mDotAppearAmount;
-    private OnVisibilityChangedListener mOnVisibilityChangedListener;
     private int mDrawableColor;
     private int mIconColor;
     private int mDecorColor;
@@ -175,7 +178,6 @@
     private int mCachedContrastBackgroundColor = NO_COLOR;
     private float[] mMatrix;
     private ColorMatrixColorFilter mMatrixColorFilter;
-    private boolean mIsInShelf;
     private Runnable mLayoutRunnable;
     private boolean mDismissed;
     private Runnable mOnDismissListener;
@@ -198,30 +200,20 @@
         mNumberPain.setAntiAlias(true);
         setNotification(sbn);
         setScaleType(ScaleType.CENTER);
-        mDensity = context.getResources().getDisplayMetrics().densityDpi;
-        Configuration configuration = context.getResources().getConfiguration();
-        mNightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+        mConfiguration = new Configuration(context.getResources().getConfiguration());
+        mNightMode = (mConfiguration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
                 == Configuration.UI_MODE_NIGHT_YES;
         initializeDecorColor();
         reloadDimens();
         maybeUpdateIconScaleDimens();
     }
 
-    public StatusBarIconView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mDozer = new NotificationIconDozeHelper(context);
-        mBlocked = false;
-        mAlwaysScaleIcon = true;
-        reloadDimens();
-        maybeUpdateIconScaleDimens();
-        mDensity = context.getResources().getDisplayMetrics().densityDpi;
-    }
-
     /** Should always be preceded by {@link #reloadDimens()} */
-    private void maybeUpdateIconScaleDimens() {
+    @VisibleForTesting
+    public void maybeUpdateIconScaleDimens() {
         // We do not resize and scale system icons (on the right), only notification icons (on the
         // left).
-        if (mNotification != null || mAlwaysScaleIcon) {
+        if (isNotification()) {
             updateIconScaleForNotifications();
         } else {
             updateIconScaleForSystemIcons();
@@ -229,22 +221,63 @@
     }
 
     private void updateIconScaleForNotifications() {
+        float iconScale;
+        // we need to scale the image size to be same as the original size
+        // (fit mOriginalStatusBarIconSize), then we can scale it with mScaleToFitNewIconSize
+        // to fit mNewStatusBarIconSize
+        float scaleToOriginalDrawingSize = 1.0f;
+        ViewGroup.LayoutParams lp = getLayoutParams();
+        if (getDrawable() != null && (lp != null && lp.width > 0 && lp.height > 0)) {
+            final int iconViewWidth = lp.width;
+            final int iconViewHeight = lp.height;
+            // first we estimate the image exact size when put the drawable in scaled iconView size,
+            // then we can compute the scaleToOriginalDrawingSize to make the image size fit in
+            // mOriginalStatusBarIconSize
+            final int drawableWidth = getDrawable().getIntrinsicWidth();
+            final int drawableHeight = getDrawable().getIntrinsicHeight();
+            float scaleToFitIconView = Math.min(
+                    (float) iconViewWidth / drawableWidth,
+                    (float) iconViewHeight / drawableHeight);
+            // if the drawable size <= the icon view size, the drawable won't be scaled
+            if (scaleToFitIconView > 1.0f) {
+                scaleToFitIconView = 1.0f;
+            }
+            final float scaledImageWidth = drawableWidth * scaleToFitIconView;
+            final float scaledImageHeight = drawableHeight * scaleToFitIconView;
+            // if the scaled image size <= mOriginalStatusBarIconSize, we don't need to enlarge it
+            scaleToOriginalDrawingSize = Math.min(
+                    (float) mOriginalStatusBarIconSize / scaledImageWidth,
+                    (float) mOriginalStatusBarIconSize / scaledImageHeight);
+            if (scaleToOriginalDrawingSize > 1.0f) {
+                scaleToOriginalDrawingSize = 1.0f;
+            }
+        }
+        iconScale = scaleToOriginalDrawingSize;
+
         final float imageBounds = mIncreasedSize ?
                 mStatusBarIconDrawingSizeIncreased : mStatusBarIconDrawingSize;
-        final int outerBounds = mStatusBarIconSize;
-        mIconScale = imageBounds / (float)outerBounds;
+        final int originalOuterBounds = mOriginalStatusBarIconSize;
+        iconScale = iconScale * (imageBounds / (float) originalOuterBounds);
+
+        // scale image to fit new icon size
+        mIconScale = iconScale * mScaleToFitNewIconSize;
+
         updatePivot();
     }
 
     // Makes sure that all icons are scaled to the same height (15dp). If we cannot get a height
     // for the icon, it uses the default SCALE (15f / 17f) which is the old behavior
     private void updateIconScaleForSystemIcons() {
+        float iconScale;
         float iconHeight = getIconHeight();
         if (iconHeight != 0) {
-            mIconScale = mSystemIconDesiredHeight / iconHeight;
+            iconScale = mSystemIconDesiredHeight / iconHeight;
         } else {
-            mIconScale = mSystemIconDefaultScale;
+            iconScale = mSystemIconDefaultScale;
         }
+
+        // scale image to fit new icon size
+        mIconScale = iconScale * mScaleToFitNewIconSize;
     }
 
     private float getIconHeight() {
@@ -267,12 +300,10 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        int density = newConfig.densityDpi;
-        if (density != mDensity) {
-            mDensity = density;
-            reloadDimens();
-            updateDrawable();
-            maybeUpdateIconScaleDimens();
+        final int configDiff = newConfig.diff(mConfiguration);
+        mConfiguration.setTo(newConfig);
+        if ((configDiff & (ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_FONT_SCALE)) != 0) {
+            updateIconDimens();
         }
         boolean nightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
                 == Configuration.UI_MODE_NIGHT_YES;
@@ -282,11 +313,22 @@
         }
     }
 
+    /**
+     * Update the icon dimens and drawable with current resources
+     */
+    public void updateIconDimens() {
+        reloadDimens();
+        updateDrawable();
+        maybeUpdateIconScaleDimens();
+    }
+
     private void reloadDimens() {
         boolean applyRadius = mDotRadius == mStaticDotRadius;
         Resources res = getResources();
         mStaticDotRadius = res.getDimensionPixelSize(R.dimen.overflow_dot_radius);
-        mStatusBarIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
+        mOriginalStatusBarIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
+        mNewStatusBarIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size_sp);
+        mScaleToFitNewIconSize = (float) mNewStatusBarIconSize / mOriginalStatusBarIconSize;
         mStatusBarIconDrawingSizeIncreased =
                 res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size_dark);
         mStatusBarIconDrawingSize =
@@ -309,17 +351,8 @@
         maybeUpdateIconScaleDimens();
     }
 
-    private static boolean streq(String a, String b) {
-        if (a == b) {
-            return true;
-        }
-        if (a == null && b != null) {
-            return false;
-        }
-        if (a != null && b == null) {
-            return false;
-        }
-        return a.equals(b);
+    private boolean isNotification() {
+        return mNotification != null;
     }
 
     public boolean equalIcons(Icon a, Icon b) {
@@ -416,7 +449,7 @@
 
     Drawable getIcon(StatusBarIcon icon) {
         Context notifContext = getContext();
-        if (mNotification != null) {
+        if (isNotification()) {
             notifContext = mNotification.getPackageContext(getContext());
         }
         return getIcon(getContext(), notifContext != null ? notifContext : getContext(), icon);
@@ -471,7 +504,7 @@
     @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
-        if (mNotification != null) {
+        if (isNotification()) {
             event.setParcelableData(mNotification.getNotification());
         }
     }
@@ -491,11 +524,29 @@
     }
 
     @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        if (!isNotification()) {
+            // for system icons, calculated measured width from super is for image drawable real
+            // width (17dp). We may scale the image with font scale, so we also need to scale the
+            // measured width so that scaled measured width and image width would be fit.
+            int measuredWidth = getMeasuredWidth();
+            int measuredHeight = getMeasuredHeight();
+            setMeasuredDimension((int) (measuredWidth * mScaleToFitNewIconSize), measuredHeight);
+        }
+    }
+
+    @Override
     protected void onDraw(Canvas canvas) {
+        // In this method, for width/height division computation we intend to discard the
+        // fractional part as the original behavior.
         if (mIconAppearAmount > 0.0f) {
             canvas.save();
+            int px = getWidth() / 2;
+            int py = getHeight() / 2;
             canvas.scale(mIconScale * mIconAppearAmount, mIconScale * mIconAppearAmount,
-                    getWidth() / 2, getHeight() / 2);
+                    (float) px, (float) py);
             super.onDraw(canvas);
             canvas.restore();
         }
@@ -512,10 +563,15 @@
             } else {
                 float fadeOutAmount = mDotAppearAmount - 1.0f;
                 alpha = alpha * (1.0f - fadeOutAmount);
-                radius = NotificationUtils.interpolate(mDotRadius, getWidth() / 4, fadeOutAmount);
+                int end = getWidth() / 4;
+                radius = NotificationUtils.interpolate(mDotRadius, (float) end, fadeOutAmount);
             }
             mDotPaint.setAlpha((int) (alpha * 255));
-            canvas.drawCircle(mStatusBarIconSize / 2, getHeight() / 2, radius, mDotPaint);
+            int cx = mNewStatusBarIconSize / 2;
+            int cy = getHeight() / 2;
+            canvas.drawCircle(
+                    (float) cx, (float) cy,
+                    radius, mDotPaint);
         }
     }
 
@@ -624,7 +680,7 @@
     }
 
     private void initializeDecorColor() {
-        if (mNotification != null) {
+        if (isNotification()) {
             setDecorColor(getContext().getColor(mNightMode
                     ? com.android.internal.R.color.notification_default_color_dark
                     : com.android.internal.R.color.notification_default_color_light));
@@ -837,7 +893,7 @@
                 if (targetAmount != currentAmount) {
                     mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
                             currentAmount, targetAmount);
-                    mDotAnimator.setInterpolator(interpolator);;
+                    mDotAnimator.setInterpolator(interpolator);
                     mDotAnimator.setDuration(duration == 0 ? ANIMATION_DURATION_FAST
                             : duration);
                     final boolean runRunnable = !runnableAdded;
@@ -894,22 +950,10 @@
         }
     }
 
-    @Override
-    public void setVisibility(int visibility) {
-        super.setVisibility(visibility);
-        if (mOnVisibilityChangedListener != null) {
-            mOnVisibilityChangedListener.onVisibilityChanged(visibility);
-        }
-    }
-
     public float getDotAppearAmount() {
         return mDotAppearAmount;
     }
 
-    public void setOnVisibilityChangedListener(OnVisibilityChangedListener listener) {
-        mOnVisibilityChangedListener = listener;
-    }
-
     public void setDozing(boolean dozing, boolean fade, long delay) {
         mDozer.setDozing(f -> {
             mDozeAmount = f;
@@ -943,14 +987,6 @@
         outRect.bottom += translationY;
     }
 
-    public void setIsInShelf(boolean isInShelf) {
-        mIsInShelf = isInShelf;
-    }
-
-    public boolean isInShelf() {
-        return mIsInShelf;
-    }
-
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
@@ -1032,8 +1068,4 @@
     public boolean showsConversation() {
         return mShowsConversation;
     }
-
-    public interface OnVisibilityChangedListener {
-        void onVisibilityChanged(int newVisibility);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
deleted file mode 100644
index fdad101..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ /dev/null
@@ -1,327 +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.statusbar;
-
-import static com.android.systemui.plugins.DarkIconDispatcher.getTint;
-import static com.android.systemui.plugins.DarkIconDispatcher.isInAreas;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.settingslib.graph.SignalDrawable;
-import com.android.systemui.DualToneHandler;
-import com.android.systemui.R;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-
-import java.util.ArrayList;
-
-/**
- * View group for the mobile icon in the status bar
- */
-public class StatusBarMobileView extends BaseStatusBarFrameLayout implements DarkReceiver,
-        StatusIconDisplayable {
-    private static final String TAG = "StatusBarMobileView";
-
-    /// Used to show etc dots
-    private StatusBarIconView mDotView;
-    /// The main icon view
-    private LinearLayout mMobileGroup;
-    private String mSlot;
-    private MobileIconState mState;
-    private SignalDrawable mMobileDrawable;
-    private View mInoutContainer;
-    private ImageView mIn;
-    private ImageView mOut;
-    private ImageView mMobile, mMobileType, mMobileRoaming;
-    private View mMobileRoamingSpace;
-    @StatusBarIconView.VisibleState
-    private int mVisibleState = STATE_HIDDEN;
-    private DualToneHandler mDualToneHandler;
-    private boolean mForceHidden;
-
-    /**
-     * Designated constructor
-     *
-     * This view is special, in that it is the only view in SystemUI that allows for a configuration
-     * override on a MCC/MNC-basis. This means that for every mobile view inflated, we have to
-     * construct a context with that override, since the resource system doesn't have a way to
-     * handle this for us.
-     *
-     * @param context A context with resources configured by MCC/MNC
-     * @param slot The string key defining which slot this icon refers to. Always "mobile" for the
-     *             mobile icon
-     */
-    public static StatusBarMobileView fromContext(
-            Context context,
-            String slot
-    ) {
-        LayoutInflater inflater = LayoutInflater.from(context);
-        StatusBarMobileView v = (StatusBarMobileView)
-                inflater.inflate(R.layout.status_bar_mobile_signal_group, null);
-        v.setSlot(slot);
-        v.init();
-        v.setVisibleState(STATE_ICON);
-        return v;
-    }
-
-    public StatusBarMobileView(Context context) {
-        super(context);
-    }
-
-    public StatusBarMobileView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public StatusBarMobileView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    public void getDrawingRect(Rect outRect) {
-        super.getDrawingRect(outRect);
-        float translationX = getTranslationX();
-        float translationY = getTranslationY();
-        outRect.left += translationX;
-        outRect.right += translationX;
-        outRect.top += translationY;
-        outRect.bottom += translationY;
-    }
-
-    private void init() {
-        mDualToneHandler = new DualToneHandler(getContext());
-        mMobileGroup = findViewById(R.id.mobile_group);
-        mMobile = findViewById(R.id.mobile_signal);
-        mMobileType = findViewById(R.id.mobile_type);
-        mMobileRoaming = findViewById(R.id.mobile_roaming);
-        mMobileRoamingSpace = findViewById(R.id.mobile_roaming_space);
-        mIn = findViewById(R.id.mobile_in);
-        mOut = findViewById(R.id.mobile_out);
-        mInoutContainer = findViewById(R.id.inout_container);
-
-        mMobileDrawable = new SignalDrawable(getContext());
-        mMobile.setImageDrawable(mMobileDrawable);
-
-        initDotView();
-    }
-
-    private void initDotView() {
-        mDotView = new StatusBarIconView(mContext, mSlot, null);
-        mDotView.setVisibleState(STATE_DOT);
-
-        int width = mContext.getResources().getDimensionPixelSize(R.dimen.status_bar_icon_size);
-        LayoutParams lp = new LayoutParams(width, width);
-        lp.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
-        addView(mDotView, lp);
-    }
-
-    public void applyMobileState(MobileIconState state) {
-        boolean requestLayout = false;
-        if (state == null) {
-            requestLayout = getVisibility() != View.GONE;
-            setVisibility(View.GONE);
-            mState = null;
-        } else if (mState == null) {
-            requestLayout = true;
-            mState = state.copy();
-            initViewState();
-        } else if (!mState.equals(state)) {
-            requestLayout = updateState(state.copy());
-        }
-
-        if (requestLayout) {
-            requestLayout();
-        }
-    }
-
-    private void initViewState() {
-        setContentDescription(mState.contentDescription);
-        if (!mState.visible || mForceHidden) {
-            mMobileGroup.setVisibility(View.GONE);
-        } else {
-            mMobileGroup.setVisibility(View.VISIBLE);
-        }
-        mMobileDrawable.setLevel(mState.strengthId);
-        if (mState.typeId > 0) {
-            mMobileType.setContentDescription(mState.typeContentDescription);
-            mMobileType.setImageResource(mState.typeId);
-            mMobileType.setVisibility(View.VISIBLE);
-        } else {
-            mMobileType.setVisibility(View.GONE);
-        }
-        mMobile.setVisibility(mState.showTriangle ? View.VISIBLE : View.GONE);
-        mMobileRoaming.setVisibility(mState.roaming ? View.VISIBLE : View.GONE);
-        mMobileRoamingSpace.setVisibility(mState.roaming ? View.VISIBLE : View.GONE);
-        mIn.setVisibility(mState.activityIn ? View.VISIBLE : View.GONE);
-        mOut.setVisibility(mState.activityOut ? View.VISIBLE : View.GONE);
-        mInoutContainer.setVisibility((mState.activityIn || mState.activityOut)
-                ? View.VISIBLE : View.GONE);
-    }
-
-    private boolean updateState(MobileIconState state) {
-        boolean needsLayout = false;
-
-        setContentDescription(state.contentDescription);
-        int newVisibility = state.visible && !mForceHidden ? View.VISIBLE : View.GONE;
-        if (newVisibility != mMobileGroup.getVisibility() && STATE_ICON == mVisibleState) {
-            mMobileGroup.setVisibility(newVisibility);
-            needsLayout = true;
-        }
-        if (mState.strengthId != state.strengthId) {
-            mMobileDrawable.setLevel(state.strengthId);
-        }
-        if (mState.typeId != state.typeId) {
-            needsLayout |= state.typeId == 0 || mState.typeId == 0;
-            if (state.typeId != 0) {
-                mMobileType.setContentDescription(state.typeContentDescription);
-                mMobileType.setImageResource(state.typeId);
-                mMobileType.setVisibility(View.VISIBLE);
-            } else {
-                mMobileType.setVisibility(View.GONE);
-            }
-        }
-
-        mMobile.setVisibility(state.showTriangle ? View.VISIBLE : View.GONE);
-        mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
-        mMobileRoamingSpace.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
-        mIn.setVisibility(state.activityIn ? View.VISIBLE : View.GONE);
-        mOut.setVisibility(state.activityOut ? View.VISIBLE : View.GONE);
-        mInoutContainer.setVisibility((state.activityIn || state.activityOut)
-                ? View.VISIBLE : View.GONE);
-
-        needsLayout |= state.roaming != mState.roaming
-                || state.activityIn != mState.activityIn
-                || state.activityOut != mState.activityOut
-                || state.showTriangle != mState.showTriangle;
-
-        mState = state;
-        return needsLayout;
-    }
-
-    @Override
-    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
-        float intensity = isInAreas(areas, this) ? darkIntensity : 0;
-        mMobileDrawable.setTintList(
-                ColorStateList.valueOf(mDualToneHandler.getSingleColor(intensity)));
-        ColorStateList color = ColorStateList.valueOf(getTint(areas, this, tint));
-        mIn.setImageTintList(color);
-        mOut.setImageTintList(color);
-        mMobileType.setImageTintList(color);
-        mMobileRoaming.setImageTintList(color);
-        mDotView.setDecorColor(tint);
-        mDotView.setIconColor(tint, false);
-    }
-
-    @Override
-    public String getSlot() {
-        return mSlot;
-    }
-
-    public void setSlot(String slot) {
-        mSlot = slot;
-    }
-
-    @Override
-    public void setStaticDrawableColor(int color) {
-        ColorStateList list = ColorStateList.valueOf(color);
-        mMobileDrawable.setTintList(list);
-        mIn.setImageTintList(list);
-        mOut.setImageTintList(list);
-        mMobileType.setImageTintList(list);
-        mMobileRoaming.setImageTintList(list);
-        mDotView.setDecorColor(color);
-    }
-
-    @Override
-    public void setDecorColor(int color) {
-        mDotView.setDecorColor(color);
-    }
-
-    @Override
-    public boolean isIconVisible() {
-        return mState.visible && !mForceHidden;
-    }
-
-    @Override
-    public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
-        if (state == mVisibleState) {
-            return;
-        }
-
-        mVisibleState = state;
-        switch (state) {
-            case STATE_ICON:
-                mMobileGroup.setVisibility(View.VISIBLE);
-                mDotView.setVisibility(View.GONE);
-                break;
-            case STATE_DOT:
-                mMobileGroup.setVisibility(View.INVISIBLE);
-                mDotView.setVisibility(View.VISIBLE);
-                break;
-            case STATE_HIDDEN:
-            default:
-                mMobileGroup.setVisibility(View.INVISIBLE);
-                mDotView.setVisibility(View.INVISIBLE);
-                break;
-        }
-    }
-
-    /**
-     * Forces the state to be hidden (views will be GONE) and if necessary updates the layout.
-     *
-     * Makes sure that the {@link StatusBarIconController} cannot make it visible while this flag
-     * is enabled.
-     * @param forceHidden {@code true} if the icon should be GONE in its view regardless of its
-     *                                state.
-     *               {@code false} if the icon should show as determined by its controller.
-     */
-    public void forceHidden(boolean forceHidden) {
-        if (mForceHidden != forceHidden) {
-            mForceHidden = forceHidden;
-            updateState(mState);
-            requestLayout();
-        }
-    }
-
-    @Override
-    @StatusBarIconView.VisibleState
-    public int getVisibleState() {
-        return mVisibleState;
-    }
-
-    @VisibleForTesting
-    public MobileIconState getState() {
-        return mState;
-    }
-
-    @Override
-    public String toString() {
-        return "StatusBarMobileView(slot=" + mSlot + " state=" + mState + ")";
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
deleted file mode 100644
index decc70d..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ /dev/null
@@ -1,249 +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.statusbar;
-
-import static com.android.systemui.plugins.DarkIconDispatcher.getTint;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
-
-import java.util.ArrayList;
-
-/**
- * Start small: StatusBarWifiView will be able to layout from a WifiIconState
- */
-public class StatusBarWifiView extends BaseStatusBarFrameLayout implements DarkReceiver {
-    private static final String TAG = "StatusBarWifiView";
-
-    /// Used to show etc dots
-    private StatusBarIconView mDotView;
-    /// Contains the main icon layout
-    private LinearLayout mWifiGroup;
-    private ImageView mWifiIcon;
-    private ImageView mIn;
-    private ImageView mOut;
-    private View mInoutContainer;
-    private View mSignalSpacer;
-    private View mAirplaneSpacer;
-    private WifiIconState mState;
-    private String mSlot;
-    @StatusBarIconView.VisibleState
-    private int mVisibleState = STATE_HIDDEN;
-
-    public static StatusBarWifiView fromContext(Context context, String slot) {
-        LayoutInflater inflater = LayoutInflater.from(context);
-        StatusBarWifiView v = (StatusBarWifiView) inflater.inflate(R.layout.status_bar_wifi_group, null);
-        v.setSlot(slot);
-        v.init();
-        v.setVisibleState(STATE_ICON);
-        return v;
-    }
-
-    public StatusBarWifiView(Context context) {
-        super(context);
-    }
-
-    public StatusBarWifiView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public StatusBarWifiView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public void setSlot(String slot) {
-        mSlot = slot;
-    }
-
-    @Override
-    public void setStaticDrawableColor(int color) {
-        ColorStateList list = ColorStateList.valueOf(color);
-        mWifiIcon.setImageTintList(list);
-        mIn.setImageTintList(list);
-        mOut.setImageTintList(list);
-        mDotView.setDecorColor(color);
-    }
-
-    @Override
-    public void setDecorColor(int color) {
-        mDotView.setDecorColor(color);
-    }
-
-    @Override
-    public String getSlot() {
-        return mSlot;
-    }
-
-    @Override
-    public boolean isIconVisible() {
-        return mState != null && mState.visible;
-    }
-
-    @Override
-    public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
-        if (state == mVisibleState) {
-            return;
-        }
-        mVisibleState = state;
-
-        switch (state) {
-            case STATE_ICON:
-                mWifiGroup.setVisibility(View.VISIBLE);
-                mDotView.setVisibility(View.GONE);
-                break;
-            case STATE_DOT:
-                mWifiGroup.setVisibility(View.GONE);
-                mDotView.setVisibility(View.VISIBLE);
-                break;
-            case STATE_HIDDEN:
-            default:
-                mWifiGroup.setVisibility(View.GONE);
-                mDotView.setVisibility(View.GONE);
-                break;
-        }
-    }
-
-    @Override
-    @StatusBarIconView.VisibleState
-    public int getVisibleState() {
-        return mVisibleState;
-    }
-
-    @Override
-    public void getDrawingRect(Rect outRect) {
-        super.getDrawingRect(outRect);
-        float translationX = getTranslationX();
-        float translationY = getTranslationY();
-        outRect.left += translationX;
-        outRect.right += translationX;
-        outRect.top += translationY;
-        outRect.bottom += translationY;
-    }
-
-    private void init() {
-        mWifiGroup = findViewById(R.id.wifi_group);
-        mWifiIcon = findViewById(R.id.wifi_signal);
-        mIn = findViewById(R.id.wifi_in);
-        mOut = findViewById(R.id.wifi_out);
-        mSignalSpacer = findViewById(R.id.wifi_signal_spacer);
-        mAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer);
-        mInoutContainer = findViewById(R.id.inout_container);
-
-        initDotView();
-    }
-
-    private void initDotView() {
-        mDotView = new StatusBarIconView(mContext, mSlot, null);
-        mDotView.setVisibleState(STATE_DOT);
-
-        int width = mContext.getResources().getDimensionPixelSize(R.dimen.status_bar_icon_size);
-        LayoutParams lp = new LayoutParams(width, width);
-        lp.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
-        addView(mDotView, lp);
-    }
-
-    public void applyWifiState(WifiIconState state) {
-        boolean requestLayout = false;
-
-        if (state == null) {
-            requestLayout = getVisibility() != View.GONE;
-            setVisibility(View.GONE);
-            mState = null;
-        } else if (mState == null) {
-            requestLayout = true;
-            mState = state.copy();
-            initViewState();
-        } else if (!mState.equals(state)) {
-            requestLayout = updateState(state.copy());
-        }
-
-        if (requestLayout) {
-            requestLayout();
-        }
-    }
-
-    private boolean updateState(WifiIconState state) {
-        setContentDescription(state.contentDescription);
-        if (mState.resId != state.resId && state.resId >= 0) {
-            mWifiIcon.setImageDrawable(mContext.getDrawable(state.resId));
-        }
-
-        mIn.setVisibility(state.activityIn ? View.VISIBLE : View.GONE);
-        mOut.setVisibility(state.activityOut ? View.VISIBLE : View.GONE);
-        mInoutContainer.setVisibility(
-                (state.activityIn || state.activityOut) ? View.VISIBLE : View.GONE);
-        mAirplaneSpacer.setVisibility(state.airplaneSpacerVisible ? View.VISIBLE : View.GONE);
-        mSignalSpacer.setVisibility(state.signalSpacerVisible ? View.VISIBLE : View.GONE);
-
-        boolean needsLayout = state.activityIn != mState.activityIn
-                ||state.activityOut != mState.activityOut;
-
-        if (mState.visible != state.visible) {
-            needsLayout |= true;
-            setVisibility(state.visible ? View.VISIBLE : View.GONE);
-        }
-
-        mState = state;
-        return needsLayout;
-    }
-
-    private void initViewState() {
-        setContentDescription(mState.contentDescription);
-        if (mState.resId >= 0) {
-            mWifiIcon.setImageDrawable(mContext.getDrawable(mState.resId));
-        }
-
-        mIn.setVisibility(mState.activityIn ? View.VISIBLE : View.GONE);
-        mOut.setVisibility(mState.activityOut ? View.VISIBLE : View.GONE);
-        mInoutContainer.setVisibility(
-                (mState.activityIn || mState.activityOut) ? View.VISIBLE : View.GONE);
-        mAirplaneSpacer.setVisibility(mState.airplaneSpacerVisible ? View.VISIBLE : View.GONE);
-        mSignalSpacer.setVisibility(mState.signalSpacerVisible ? View.VISIBLE : View.GONE);
-        setVisibility(mState.visible ? View.VISIBLE : View.GONE);
-    }
-
-    @Override
-    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
-        int areaTint = getTint(areas, this, tint);
-        ColorStateList color = ColorStateList.valueOf(areaTint);
-        mWifiIcon.setImageTintList(color);
-        mIn.setImageTintList(color);
-        mOut.setImageTintList(color);
-        mDotView.setDecorColor(areaTint);
-        mDotView.setIconColor(areaTint, false);
-    }
-
-
-    @Override
-    public String toString() {
-        return "StatusBarWifiView(slot=" + mSlot + " state=" + mState + ")";
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING
new file mode 100644
index 0000000..8849d6e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING
@@ -0,0 +1,29 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsNotificationTestCases",
+      "options": [
+        {
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "android.platform.test.annotations.LargeTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsNotificationTestCases"
+    }
+  ]
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index 324e972..645595c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -23,6 +23,7 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.view.View;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -151,4 +152,20 @@
                 BIOMETRIC_ERROR_VIBRATION_EFFECT, reason,
                 HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
     }
+
+    /**
+     * Perform a vibration using a view and the one-way API with flags
+     * @see View#performHapticFeedback(int feedbackConstant, int flags)
+     */
+    public void performHapticFeedback(@NonNull View view, int feedbackConstant, int flags) {
+        view.performHapticFeedback(feedbackConstant, flags);
+    }
+
+    /**
+     * Perform a vibration using a view and the one-way API
+     * @see View#performHapticFeedback(int feedbackConstant)
+     */
+    public void performHapticFeedback(@NonNull View view, int feedbackConstant) {
+        view.performHapticFeedback(feedbackConstant);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt
new file mode 100644
index 0000000..de369c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.commandline
+
+/**
+ * [CommandParser] defines the collection of tokens which can be parsed from an incoming command
+ * list, and parses them into their respective containers. Supported tokens are of the following
+ * forms:
+ * ```
+ * Flag: boolean value, false by default. always optional.
+ * Param: named parameter, taking N args all of a given type. Currently only single arg parameters
+ *        are supported.
+ * SubCommand: named command created by adding a command to a parent. Supports all fields above, but
+ *             not other subcommands.
+ * ```
+ *
+ * Tokens are added via the factory methods for each token type. They can be made `required` by
+ * calling the [require] method for the appropriate type, as follows:
+ * ```
+ * val requiredParam = parser.require(parser.param(...))
+ * ```
+ *
+ * The reason for having an explicit require is so that generic type arguments can be handled
+ * properly. See [SingleArgParam] and [SingleArgParamOptional] for the difference between an
+ * optional parameter and a required one.
+ *
+ * Typical usage of a required parameter, however, will occur within the context of a
+ * [ParseableCommand], which defines a convenience `require()` method:
+ * ```
+ * class MyCommand : ParseableCommand {
+ *   val requiredParam = param(...).require()
+ * }
+ * ```
+ *
+ * This parser defines two modes of parsing, both of which validate for required parameters.
+ * 1. [parse] is a top-level parsing method. This parser will walk the given arg list and populate
+ *    all of the delegate classes based on their type. It will handle SubCommands, and after parsing
+ *    will check for any required-but-missing SubCommands or Params.
+ *
+ *    **This method requires that every received token is represented in its grammar.**
+ * 2. [parseAsSubCommand] is a second-level parsing method suitable for any [SubCommand]. This
+ *    method will handle _only_ flags and params. It will return parsing control to its parent
+ *    parser on the first unknown token rather than throwing.
+ */
+class CommandParser {
+    private val _flags = mutableListOf<Flag>()
+    val flags: List<Flag> = _flags
+    private val _params = mutableListOf<Param>()
+    val params: List<Param> = _params
+    private val _subCommands = mutableListOf<SubCommand>()
+    val subCommands: List<SubCommand> = _subCommands
+
+    private val tokenSet = mutableSetOf<String>()
+
+    /**
+     * Parse the arg list into the fields defined in the containing class.
+     *
+     * @return true if all required fields are present after parsing
+     * @throws ArgParseError on any failure to process args
+     */
+    fun parse(args: List<String>): Boolean {
+        if (args.isEmpty()) {
+            return false
+        }
+
+        val iterator = args.listIterator()
+        var tokenHandled: Boolean
+        while (iterator.hasNext()) {
+            val token = iterator.next()
+            tokenHandled = false
+
+            flags
+                .find { it.matches(token) }
+                ?.let {
+                    it.inner = true
+                    tokenHandled = true
+                }
+
+            if (tokenHandled) continue
+
+            params
+                .find { it.matches(token) }
+                ?.let {
+                    it.parseArgsFromIter(iterator)
+                    tokenHandled = true
+                }
+
+            if (tokenHandled) continue
+
+            subCommands
+                .find { it.matches(token) }
+                ?.let {
+                    it.parseSubCommandArgs(iterator)
+                    tokenHandled = true
+                }
+
+            if (!tokenHandled) {
+                throw ArgParseError("Unknown token: $token")
+            }
+        }
+
+        return validateRequiredParams()
+    }
+
+    /**
+     * Parse a subset of the commands that came in from the top-level [parse] method, for the
+     * subcommand that this parser represents. Note that subcommands may not contain other
+     * subcommands. But they may contain flags and params.
+     *
+     * @return true if all required fields are present after parsing
+     * @throws ArgParseError on any failure to process args
+     */
+    fun parseAsSubCommand(iter: ListIterator<String>): Boolean {
+        // arg[-1] is our subcommand name, so the rest of the args are either for this
+        // subcommand, OR for the top-level command to handle. Therefore, we bail on the first
+        // failure, but still check our own required params
+
+        // The mere presence of a subcommand (similar to a flag) is a valid subcommand
+        if (flags.isEmpty() && params.isEmpty()) {
+            return validateRequiredParams()
+        }
+
+        var tokenHandled: Boolean
+        while (iter.hasNext()) {
+            val token = iter.next()
+            tokenHandled = false
+
+            flags
+                .find { it.matches(token) }
+                ?.let {
+                    it.inner = true
+                    tokenHandled = true
+                }
+
+            if (tokenHandled) continue
+
+            params
+                .find { it.matches(token) }
+                ?.let {
+                    it.parseArgsFromIter(iter)
+                    tokenHandled = true
+                }
+
+            if (!tokenHandled) {
+                // Move the cursor position backwards since we've arrived at a token
+                // that we don't own
+                iter.previous()
+                break
+            }
+        }
+
+        return validateRequiredParams()
+    }
+
+    /**
+     * If [parse] or [parseAsSubCommand] does not produce a valid result, generate a list of errors
+     * based on missing elements
+     */
+    fun generateValidationErrorMessages(): List<String> {
+        val missingElements = mutableListOf<String>()
+
+        if (unhandledParams.isNotEmpty()) {
+            val names = unhandledParams.map { it.longName }
+            missingElements.add("No values passed for required params: $names")
+        }
+
+        if (unhandledSubCmds.isNotEmpty()) {
+            missingElements.addAll(unhandledSubCmds.map { it.longName })
+            val names = unhandledSubCmds.map { it.shortName }
+            missingElements.add("No values passed for required sub-commands: $names")
+        }
+
+        return missingElements
+    }
+
+    /** Check for any missing, required params, or any invalid subcommands */
+    private fun validateRequiredParams(): Boolean =
+        unhandledParams.isEmpty() && unhandledSubCmds.isEmpty() && unvalidatedSubCmds.isEmpty()
+
+    // If any required param (aka non-optional) hasn't handled a field, then return false
+    private val unhandledParams: List<Param>
+        get() = params.filter { (it is SingleArgParam<*>) && !it.handled }
+
+    private val unhandledSubCmds: List<SubCommand>
+        get() = subCommands.filter { (it is RequiredSubCommand<*> && !it.handled) }
+
+    private val unvalidatedSubCmds: List<SubCommand>
+        get() = subCommands.filter { !it.validationStatus }
+
+    private fun checkCliNames(short: String?, long: String): String? {
+        if (short != null && tokenSet.contains(short)) {
+            return short
+        }
+
+        if (tokenSet.contains(long)) {
+            return long
+        }
+
+        return null
+    }
+
+    private fun subCommandContainsSubCommands(cmd: ParseableCommand): Boolean =
+        cmd.parser.subCommands.isNotEmpty()
+
+    private fun registerNames(short: String?, long: String) {
+        if (short != null) {
+            tokenSet.add(short)
+        }
+        tokenSet.add(long)
+    }
+
+    /**
+     * Turns a [SingleArgParamOptional]<T> into a [SingleArgParam] by converting the [T?] into [T]
+     *
+     * @return a [SingleArgParam] property delegate
+     */
+    fun <T : Any> require(old: SingleArgParamOptional<T>): SingleArgParam<T> {
+        val newParam =
+            SingleArgParam(
+                longName = old.longName,
+                shortName = old.shortName,
+                description = old.description,
+                valueParser = old.valueParser,
+            )
+
+        replaceWithRequired(old, newParam)
+        return newParam
+    }
+
+    private fun <T : Any> replaceWithRequired(
+        old: SingleArgParamOptional<T>,
+        new: SingleArgParam<T>,
+    ) {
+        _params.remove(old)
+        _params.add(new)
+    }
+
+    /**
+     * Turns an [OptionalSubCommand] into a [RequiredSubCommand] by converting the [T?] in to [T]
+     *
+     * @return a [RequiredSubCommand] property delegate
+     */
+    fun <T : ParseableCommand> require(optional: OptionalSubCommand<T>): RequiredSubCommand<T> {
+        val newCmd = RequiredSubCommand(optional.cmd)
+        replaceWithRequired(optional, newCmd)
+        return newCmd
+    }
+
+    private fun <T : ParseableCommand> replaceWithRequired(
+        old: OptionalSubCommand<T>,
+        new: RequiredSubCommand<T>,
+    ) {
+        _subCommands.remove(old)
+        _subCommands.add(new)
+    }
+
+    internal fun flag(
+        longName: String,
+        shortName: String? = null,
+        description: String = "",
+    ): Flag {
+        checkCliNames(shortName, longName)?.let {
+            throw IllegalArgumentException("Detected reused flag name ($it)")
+        }
+        registerNames(shortName, longName)
+
+        val flag = Flag(shortName, longName, description)
+        _flags.add(flag)
+        return flag
+    }
+
+    internal fun <T : Any> param(
+        longName: String,
+        shortName: String? = null,
+        description: String = "",
+        valueParser: ValueParser<T>,
+    ): SingleArgParamOptional<T> {
+        checkCliNames(shortName, longName)?.let {
+            throw IllegalArgumentException("Detected reused param name ($it)")
+        }
+        registerNames(shortName, longName)
+
+        val param =
+            SingleArgParamOptional(
+                shortName = shortName,
+                longName = longName,
+                description = description,
+                valueParser = valueParser,
+            )
+        _params.add(param)
+        return param
+    }
+
+    internal fun <T : ParseableCommand> subCommand(
+        command: T,
+    ): OptionalSubCommand<T> {
+        checkCliNames(null, command.name)?.let {
+            throw IllegalArgumentException("Cannot re-use name for subcommand ($it)")
+        }
+
+        if (subCommandContainsSubCommands(command)) {
+            throw IllegalArgumentException(
+                "SubCommands may not contain other SubCommands. $command"
+            )
+        }
+
+        registerNames(null, command.name)
+
+        val subCmd = OptionalSubCommand(command)
+        _subCommands.add(subCmd)
+        return subCmd
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt
new file mode 100644
index 0000000..6ed5eed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.commandline
+
+import android.util.IndentingPrintWriter
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+/**
+ * Definitions for all parameter types usable by [ParseableCommand]. Parameters are command line
+ * tokens that accept a fixed number of arguments and convert them to a parsed type.
+ *
+ * Example:
+ * ```
+ * my_command --single-arg-param arg
+ * ```
+ *
+ * In the example, `my_command` is the name of the command, `--single-arg-param` is the parameter,
+ * and `arg` is the value parsed by that parameter into its eventual type.
+ *
+ * Note on generics: The intended usage for parameters is to be able to return the parsed type from
+ * the given command as a `val` via property delegation. For example, let's say we have a command
+ * that has one optional and one required parameter:
+ * ```
+ * class MyCommand : ParseableCommand {
+ *   val requiredParam: Int by parser.param(...).required()
+ *   val optionalParam: Int? by parser.param(...)
+ * }
+ * ```
+ *
+ * In order to make the simple `param` method return the correct type, we need to do two things:
+ * 1. Break out the generic type into 2 pieces (TParsed and T)
+ * 2. Create two different underlying Parameter subclasses to handle the property delegation. One
+ *    handles `T?` and the other handles `T`. Note that in both cases, `TParsed` is always non-null
+ *    since the value parsed from the argument will throw an exception if missing or if it cannot be
+ *    parsed.
+ */
+
+/** A param type knows the number of arguments it expects */
+sealed interface Param : Describable {
+    val numArgs: Int
+
+    /**
+     * Consume [numArgs] items from the iterator and relay the result into its corresponding
+     * delegated type.
+     */
+    fun parseArgsFromIter(iterator: Iterator<String>)
+}
+
+/**
+ * Base class for required and optional SingleArgParam classes. For convenience, UnaryParam is
+ * defined as a [MultipleArgParam] where numArgs = 1. The benefit is that we can define the parsing
+ * in a single place, and yet on the client side we can unwrap the underlying list of params
+ * automatically.
+ */
+abstract class UnaryParamBase<out T, out TParsed : T>(val wrapped: MultipleArgParam<T, TParsed>) :
+    Param, ReadOnlyProperty<Any?, T> {
+    var handled = false
+
+    override fun describe(pw: IndentingPrintWriter) {
+        if (shortName != null) {
+            pw.print("$shortName, ")
+        }
+        pw.print(longName)
+        pw.println(" ${typeDescription()}")
+        if (description != null) {
+            pw.indented { pw.println(description) }
+        }
+    }
+
+    /**
+     * Try to describe the arg type. We can know if it's one of the base types what kind of input it
+     * takes. Otherwise just print "<arg>" and let the clients describe in the help text
+     */
+    private fun typeDescription() =
+        when (wrapped.valueParser) {
+            Type.Int -> "<int>"
+            Type.Float -> "<float>"
+            Type.String -> "<string>"
+            Type.Boolean -> "<boolean>"
+            else -> "<arg>"
+        }
+}
+
+/** Required single-arg parameter, delegating a non-null type to the client. */
+class SingleArgParam<out T : Any>(
+    override val longName: String,
+    override val shortName: String? = null,
+    override val description: String? = null,
+    val valueParser: ValueParser<T>,
+) :
+    UnaryParamBase<T, T>(
+        MultipleArgParam(
+            longName,
+            shortName,
+            1,
+            description,
+            valueParser,
+        )
+    ) {
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): T =
+        if (handled) {
+            wrapped.getValue(thisRef, property)[0]
+        } else {
+            throw IllegalStateException("Attempt to read property before parse() has executed")
+        }
+
+    override val numArgs: Int = 1
+
+    override fun parseArgsFromIter(iterator: Iterator<String>) {
+        wrapped.parseArgsFromIter(iterator)
+        handled = true
+    }
+}
+
+/** Optional single-argument parameter, delegating a nullable type to the client. */
+class SingleArgParamOptional<out T : Any>(
+    override val longName: String,
+    override val shortName: String? = null,
+    override val description: String? = null,
+    val valueParser: ValueParser<T>,
+) :
+    UnaryParamBase<T?, T>(
+        MultipleArgParam(
+            longName,
+            shortName,
+            1,
+            description,
+            valueParser,
+        )
+    ) {
+    override fun getValue(thisRef: Any?, property: KProperty<*>): T? =
+        wrapped.getValue(thisRef, property).getOrNull(0)
+
+    override val numArgs: Int = 1
+
+    override fun parseArgsFromIter(iterator: Iterator<String>) {
+        wrapped.parseArgsFromIter(iterator)
+        handled = true
+    }
+}
+
+/**
+ * Parses a list of args into the underlying [T] data type. The resultant value is an ordered list
+ * of type [TParsed].
+ *
+ * [T] and [TParsed] are split out here in the case where the entire param is optional. I.e., a
+ * MultipleArgParam<T?, T> indicates a command line argument that can be omitted. In that case, the
+ * inner list is List<T>?, NOT List<T?>. If the argument is provided, then the type is always going
+ * to be parsed into T rather than T?.
+ */
+class MultipleArgParam<out T, out TParsed : T>(
+    override val longName: String,
+    override val shortName: String? = null,
+    override val numArgs: Int = 1,
+    override val description: String? = null,
+    val valueParser: ValueParser<TParsed>,
+) : ReadOnlyProperty<Any?, List<TParsed>>, Param {
+    private val inner: MutableList<TParsed> = mutableListOf()
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): List<TParsed> = inner
+
+    /**
+     * Consumes [numArgs] values of the iterator and parses them into [TParsed].
+     *
+     * @throws ArgParseError on the first failure
+     */
+    override fun parseArgsFromIter(iterator: Iterator<String>) {
+        if (!iterator.hasNext()) {
+            throw ArgParseError("no argument provided for $shortName")
+        }
+        for (i in 0 until numArgs) {
+            valueParser
+                .parseValue(iterator.next())
+                .fold(onSuccess = { inner.add(it) }, onFailure = { throw it })
+        }
+    }
+}
+
+data class ArgParseError(override val message: String) : Exception(message)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt
new file mode 100644
index 0000000..ecd3fa6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.commandline
+
+import android.util.IndentingPrintWriter
+import java.io.PrintWriter
+import java.lang.IllegalArgumentException
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+/**
+ * An implementation of [Command] that includes a [CommandParser] which can set all delegated
+ * properties.
+ *
+ * As the number of registrants to [CommandRegistry] grows, we should have a default mechanism for
+ * parsing common command line arguments. We are not expecting to build an arbitrarily-functional
+ * CLI, nor a GNU arg parse compliant interface here, we simply want to be able to empower clients
+ * to create simple CLI grammars such as:
+ * ```
+ * $ my_command [-f|--flag]
+ * $ my_command [-a|--arg] <params...>
+ * $ my_command [subcommand1] [subcommand2]
+ * $ my_command <positional_arg ...> # not-yet implemented
+ * ```
+ *
+ * Note that the flags `-h` and `--help` are reserved for the base class. It seems prudent to just
+ * avoid them in your implementation.
+ *
+ * Usage:
+ *
+ * The intended usage tries to be clever enough to enable good ergonomics, while not too clever as
+ * to be unmaintainable. Using the default parser is done using property delegates, and looks like:
+ * ```
+ * class MyCommand(
+ *     onExecute: (cmd: MyCommand, pw: PrintWriter) -> ()
+ * ) : ParseableCommand(name) {
+ *     val flag1 by flag(
+ *         shortName = "-f",
+ *         longName = "--flag",
+ *         required = false,
+ *     )
+ *     val param1: String by param(
+ *         shortName = "-a",
+ *         longName = "--args",
+ *         valueParser = Type.String
+ *     ).required()
+ *     val param2: Int by param(..., valueParser = Type.Int)
+ *     val subCommand by subCommand(...)
+ *
+ *     override fun execute(pw: PrintWriter) {
+ *         onExecute(this, pw)
+ *     }
+ *
+ *     companion object {
+ *        const val name = "my_command"
+ *     }
+ * }
+ *
+ * fun main() {
+ *     fun printArgs(cmd: MyCommand, pw: PrintWriter) {
+ *         pw.println("${cmd.flag1}")
+ *         pw.println("${cmd.param1}")
+ *         pw.println("${cmd.param2}")
+ *         pw.println("${cmd.subCommand}")
+ *     }
+ *
+ *     commandRegistry.registerCommand(MyCommand.companion.name) {
+ *         MyCommand() { (cmd, pw) ->
+ *             printArgs(cmd, pw)
+ *         }
+ *     }
+ * }
+ *
+ * ```
+ */
+abstract class ParseableCommand(val name: String, val description: String? = null) : Command {
+    val parser: CommandParser = CommandParser()
+
+    val help by flag(longName = "help", shortName = "h", description = "Print help and return")
+
+    /**
+     * After [execute(pw, args)] is called, this class goes through a parsing stage and sets all
+     * delegated properties. It is safe to read any delegated properties here.
+     *
+     * This method is never called for [SubCommand]s, since they are associated with a top-level
+     * command that handles [execute]
+     */
+    abstract fun execute(pw: PrintWriter)
+
+    /**
+     * Given a command string list, [execute] parses the incoming command and validates the input.
+     * If this command or any of its subcommands is passed `-h` or `--help`, then execute will only
+     * print the relevant help message and exit.
+     *
+     * If any error is thrown during parsing, we will catch and log the error. This process should
+     * _never_ take down its process. Override [onParseFailed] to handle an [ArgParseError].
+     *
+     * Important: none of the delegated fields can be read before this stage.
+     */
+    override fun execute(pw: PrintWriter, args: List<String>) {
+        val success: Boolean
+        try {
+            success = parser.parse(args)
+        } catch (e: ArgParseError) {
+            pw.println(e.message)
+            onParseFailed(e)
+            return
+        } catch (e: Exception) {
+            pw.println("Unknown exception encountered during parse")
+            pw.println(e)
+            return
+        }
+
+        // Now we've parsed the incoming command without error. There are two things to check:
+        // 1. If any help is requested, print the help message and return
+        // 2. Otherwise, make sure required params have been passed in, and execute
+
+        val helpSubCmds = subCmdsRequestingHelp()
+
+        // Top-level help encapsulates subcommands. Otherwise, if _any_ subcommand requests
+        // help then defer to them. Else, just execute
+        if (help) {
+            help(pw)
+        } else if (helpSubCmds.isNotEmpty()) {
+            helpSubCmds.forEach { it.help(pw) }
+        } else {
+            if (!success) {
+                parser.generateValidationErrorMessages().forEach { pw.println(it) }
+            } else {
+                execute(pw)
+            }
+        }
+    }
+
+    /**
+     * Returns a list of all commands that asked for help. If non-empty, parsing will stop to print
+     * help. It is not guaranteed that delegates are fulfilled if help is requested
+     */
+    private fun subCmdsRequestingHelp(): List<ParseableCommand> =
+        parser.subCommands.filter { it.cmd.help }.map { it.cmd }
+
+    /** Override to do something when parsing fails */
+    open fun onParseFailed(error: ArgParseError) {}
+
+    /** Override to print a usage clause. E.g. `usage: my-cmd <arg1> <arg2>` */
+    open fun usage(pw: IndentingPrintWriter) {}
+
+    /**
+     * Print out the list of tokens, their received types if any, and their description in a
+     * formatted string.
+     *
+     * Example:
+     * ```
+     * my-command:
+     *   MyCmd.description
+     *
+     * [optional] usage block
+     *
+     * Flags:
+     *   -f
+     *     description
+     *   --flag2
+     *     description
+     *
+     * Parameters:
+     *   Required:
+     *     -p1 [Param.Type]
+     *       description
+     *     --param2 [Param.Type]
+     *       description
+     *   Optional:
+     *     same as above
+     *
+     * SubCommands:
+     *   Required:
+     *     ...
+     *   Optional:
+     *     ...
+     * ```
+     */
+    override fun help(pw: PrintWriter) {
+        val ipw = IndentingPrintWriter(pw)
+        ipw.printBoxed(name)
+        ipw.println()
+
+        // Allow for a simple `usage` block for clients
+        ipw.indented { usage(ipw) }
+
+        if (description != null) {
+            ipw.indented { ipw.println(description) }
+            ipw.println()
+        }
+
+        val flags = parser.flags
+        if (flags.isNotEmpty()) {
+            ipw.println("FLAGS:")
+            ipw.indented {
+                flags.forEach {
+                    it.describe(ipw)
+                    ipw.println()
+                }
+            }
+        }
+
+        val (required, optional) = parser.params.partition { it is SingleArgParam<*> }
+        if (required.isNotEmpty()) {
+            ipw.println("REQUIRED PARAMS:")
+            required.describe(ipw)
+        }
+        if (optional.isNotEmpty()) {
+            ipw.println("OPTIONAL PARAMS:")
+            optional.describe(ipw)
+        }
+
+        val (reqSub, optSub) = parser.subCommands.partition { it is RequiredSubCommand<*> }
+        if (reqSub.isNotEmpty()) {
+            ipw.println("REQUIRED SUBCOMMANDS:")
+            reqSub.describe(ipw)
+        }
+        if (optSub.isNotEmpty()) {
+            ipw.println("OPTIONAL SUBCOMMANDS:")
+            optSub.describe(ipw)
+        }
+    }
+
+    fun flag(
+        longName: String,
+        shortName: String? = null,
+        description: String = "",
+    ): Flag {
+        if (!checkShortName(shortName)) {
+            throw IllegalArgumentException(
+                "Flag short name must be one character long, or null. Got ($shortName)"
+            )
+        }
+
+        if (!checkLongName(longName)) {
+            throw IllegalArgumentException("Flags must not start with '-'. Got $($longName)")
+        }
+
+        val short = shortName?.let { "-$shortName" }
+        val long = "--$longName"
+
+        return parser.flag(long, short, description)
+    }
+
+    fun <T : Any> param(
+        longName: String,
+        shortName: String? = null,
+        description: String = "",
+        valueParser: ValueParser<T>,
+    ): SingleArgParamOptional<T> {
+        if (!checkShortName(shortName)) {
+            throw IllegalArgumentException(
+                "Parameter short name must be one character long, or null. Got ($shortName)"
+            )
+        }
+
+        if (!checkLongName(longName)) {
+            throw IllegalArgumentException("Parameters must not start with '-'. Got $($longName)")
+        }
+
+        val short = shortName?.let { "-$shortName" }
+        val long = "--$longName"
+
+        return parser.param(long, short, description, valueParser)
+    }
+
+    fun <T : ParseableCommand> subCommand(
+        command: T,
+    ) = parser.subCommand(command)
+
+    /** For use in conjunction with [param], makes the parameter required */
+    fun <T : Any> SingleArgParamOptional<T>.required(): SingleArgParam<T> = parser.require(this)
+
+    /** For use in conjunction with [subCommand], makes the given [SubCommand] required */
+    fun <T : ParseableCommand> OptionalSubCommand<T>.required(): RequiredSubCommand<T> =
+        parser.require(this)
+
+    private fun checkShortName(short: String?): Boolean {
+        return short == null || short.length == 1
+    }
+
+    private fun checkLongName(long: String): Boolean {
+        return !long.startsWith("-")
+    }
+
+    companion object {
+        fun Iterable<Describable>.describe(pw: IndentingPrintWriter) {
+            pw.indented {
+                forEach {
+                    it.describe(pw)
+                    pw.println()
+                }
+            }
+        }
+    }
+}
+
+/**
+ * A flag is a boolean value passed over the command line. It can have a short form or long form.
+ * The value is [Boolean.true] if the flag is found, else false
+ */
+data class Flag(
+    override val shortName: String? = null,
+    override val longName: String,
+    override val description: String? = null,
+) : ReadOnlyProperty<Any?, Boolean>, Describable {
+    var inner: Boolean = false
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>) = inner
+}
+
+/**
+ * Named CLI token. Can have a short or long name. Note: consider renaming to "primary" and
+ * "secondary" names since we don't actually care what the strings are
+ *
+ * Flags and params will have [shortName]s that are always prefixed with a single dash, while
+ * [longName]s are prefixed by a double dash. E.g., `my_command -f --flag`.
+ *
+ * Subcommands do not do any prefixing, and register their name as the [longName]
+ *
+ * Can be matched against an incoming token
+ */
+interface CliNamed {
+    val shortName: String?
+    val longName: String
+
+    fun matches(token: String) = shortName == token || longName == token
+}
+
+interface Describable : CliNamed {
+    val description: String?
+
+    fun describe(pw: IndentingPrintWriter) {
+        if (shortName != null) {
+            pw.print("$shortName, ")
+        }
+        pw.print(longName)
+        pw.println()
+        if (description != null) {
+            pw.indented { pw.println(description) }
+        }
+    }
+}
+
+/**
+ * Print [s] inside of a unicode character box, like so:
+ * ```
+ *  ╔═══════════╗
+ *  ║ my-string ║
+ *  ╚═══════════╝
+ * ```
+ */
+fun PrintWriter.printDoubleBoxed(s: String) {
+    val length = s.length
+    println("╔${"═".repeat(length + 2)}╗")
+    println("║ $s ║")
+    println("╚${"═".repeat(length + 2)}╝")
+}
+
+/**
+ * Print [s] inside of a unicode character box, like so:
+ * ```
+ *  ┌───────────┐
+ *  │ my-string │
+ *  └───────────┘
+ * ```
+ */
+fun PrintWriter.printBoxed(s: String) {
+    val length = s.length
+    println("┌${"─".repeat(length + 2)}┐")
+    println("│ $s │")
+    println("└${"─".repeat(length + 2)}┘")
+}
+
+fun IndentingPrintWriter.indented(block: () -> Unit) {
+    increaseIndent()
+    block()
+    decreaseIndent()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt
new file mode 100644
index 0000000..41bac86
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.commandline
+
+import android.util.IndentingPrintWriter
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+/**
+ * Sub commands wrap [ParseableCommand]s and are attached to a parent [ParseableCommand]. As such
+ * they have their own parser which will parse the args as a subcommand. I.e., the subcommand's
+ * parser will consume the iterator created by the parent, reversing the index when it reaches an
+ * unknown token.
+ *
+ * In order to keep subcommands relatively simple and not have to do complicated validation, sub
+ * commands will return control to the parent parser as soon as they discover a token that they do
+ * not own. They will throw an [ArgParseError] if parsing fails or if they don't receive arguments
+ * for a required parameter.
+ */
+sealed interface SubCommand : Describable {
+    val cmd: ParseableCommand
+
+    /** Checks if all of the required elements were passed in to [parseSubCommandArgs] */
+    var validationStatus: Boolean
+
+    /**
+     * To keep parsing simple, [parseSubCommandArgs] requires a [ListIterator] so that it can rewind
+     * the iterator when it yields control upwards
+     */
+    fun parseSubCommandArgs(iterator: ListIterator<String>)
+}
+
+/**
+ * Note that the delegated type from the subcommand is `T: ParseableCommand?`. SubCommands are
+ * created via adding a fully-formed [ParseableCommand] to parent command.
+ *
+ * At this point in time, I don't recommend nesting subcommands.
+ */
+class OptionalSubCommand<T : ParseableCommand>(
+    override val cmd: T,
+) : SubCommand, ReadOnlyProperty<Any?, ParseableCommand?> {
+    override val shortName: String? = null
+    override val longName: String = cmd.name
+    override val description: String? = cmd.description
+    override var validationStatus = true
+
+    private var isPresent = false
+
+    /** Consume tokens from the iterator and pass them to the wrapped command */
+    override fun parseSubCommandArgs(iterator: ListIterator<String>) {
+        validationStatus = cmd.parser.parseAsSubCommand(iterator)
+        isPresent = true
+    }
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): T? =
+        if (isPresent) {
+            cmd
+        } else {
+            null
+        }
+
+    override fun describe(pw: IndentingPrintWriter) {
+        cmd.help(pw)
+    }
+}
+
+/**
+ * Non-optional subcommand impl. Top-level parser is expected to throw [ArgParseError] if this token
+ * is not present in the incoming command
+ */
+class RequiredSubCommand<T : ParseableCommand>(
+    override val cmd: T,
+) : SubCommand, ReadOnlyProperty<Any?, ParseableCommand> {
+    override val shortName: String? = null
+    override val longName: String = cmd.name
+    override val description: String? = cmd.description
+    override var validationStatus = true
+
+    /** Unhandled, required subcommands are an error */
+    var handled = false
+
+    override fun parseSubCommandArgs(iterator: ListIterator<String>) {
+        validationStatus = cmd.parser.parseAsSubCommand(iterator)
+        handled = true
+    }
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): ParseableCommand = cmd
+
+    override fun describe(pw: IndentingPrintWriter) {
+        cmd.help(pw)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt
new file mode 100644
index 0000000..01083d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.commandline
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
+
+/**
+ * Utilities for parsing the [String] command line arguments. Arguments are related to the
+ * [Parameter] type, which declares the number of, and resulting type of, the arguments that it
+ * takes when parsing. For Example:
+ * ```
+ * my-command --param <str> --param2 <int>
+ * ```
+ *
+ * Defines 2 parameters, the first of which takes a string, and the second requires an int. Because
+ * fundamentally _everything_ is a string, we have to define a convenient way to get from the
+ * incoming `StringArg` to the resulting `T`-arg, where `T` is the type required by the client.
+ *
+ * Parsing is therefore a relatively straightforward operation: (String) -> T. However, since
+ * parsing can always fail, the type is actually (String) -> Result<T>. We will always want to fail
+ * on the first error and propagate it to the caller (typically this results in printing the `help`
+ * message of the command`).
+ *
+ * The identity parsing is trivial:
+ * ```
+ * (s: String) -> String = { s -> s }
+ * ```
+ *
+ * Basic mappings are actually even provided by Kotlin's stdlib:
+ * ```
+ * (s: String) -> Boolean = { s -> s.toBooleanOrNull() }
+ * (s: String) -> Int = { s -> s.toIntOrNull() }
+ * ...
+ * ```
+ *
+ * In order to properly encode errors, we will ascribe an error type to any `null` values, such that
+ * parsing looks like this:
+ * ```
+ * val mapping: (String) -> T? = {...} // for some T
+ * val parser: (String) -> Result<T> = { s ->
+ *   mapping(s)?.let {
+ *     Result.success(it)
+ *   } ?: Result.failure(/* some failure type */)
+ * }
+ * ```
+ *
+ * Composition
+ *
+ * The ability to compose value parsing enables us to provide a couple of reasonable default parsers
+ * and allow clients to seamlessly build upon that using map functions. Consider the case where we
+ * want to validate that a value is an [Int] between 0 and 100. We start with the generic [Int]
+ * parser, and a validator, of the type (Int) -> Result<Int>:
+ * ```
+ * val intParser = { s ->
+ *   s.toStringOrNull().?let {...} ?: ...
+ * }
+ *
+ * val validator = { i ->
+ *   if (i > 100 || i < 0) {
+ *     Result.failure(...)
+ *   } else {
+ *     Result.success(i)
+ *   }
+ * ```
+ *
+ * In order to combine these functions, we need to define a new [flatMap] function that can get us
+ * from a `Result<T>` to a `Result<R>`, and short-circuit on any error. We want to see this:
+ * ```
+ * val validatingParser = { s ->
+ *   intParser.invoke(s).flatMap { i ->
+ *     validator(i)
+ *   }
+ * }
+ * ```
+ *
+ * The flatMap is relatively simply defined, we can mimic the existing definition for [Result.map],
+ * though the implementation is uglier because of the `internal` definition for `value`
+ *
+ * ```
+ * inline fun <R, T> Result<T>.flatMap(transform: (value: T) -> Result<R>): Result<R> {
+ *   return when {
+ *     isSuccess -> transform(getOrThrow())
+ *     else -> Result.failure(exceptionOrNull()!!)
+ *   }
+ * }
+ * ```
+ */
+
+/**
+ * Given a [transform] that returns a [Result], apply the transform to this result, unwrapping the
+ * return value so that
+ *
+ * These [contract] and [callsInPlace] methods are copied from the [Result.map] definition
+ */
+@OptIn(ExperimentalContracts::class)
+inline fun <R, T> Result<T>.flatMap(transform: (value: T) -> Result<R>): Result<R> {
+    contract { callsInPlace(transform, InvocationKind.AT_MOST_ONCE) }
+
+    return when {
+        // Should never throw, we just don't have access to [this.value]
+        isSuccess -> transform(getOrThrow())
+        // Exception should never be null here
+        else -> Result.failure(exceptionOrNull()!!)
+    }
+}
+
+/**
+ * ValueParser turns a [String] into a Result<A> by applying a transform. See the default
+ * implementations below for starting points. The intention here is to provide the base mappings and
+ * allow clients to attach their own transforms. They are expected to succeed or return null on
+ * failure. The failure is propagated to the command parser as a Result and will fail on any
+ * [Result.failure]
+ */
+fun interface ValueParser<out A> {
+    fun parseValue(value: String): Result<A>
+}
+
+/** Map a [ValueParser] of type A to one of type B, by applying the given [transform] */
+inline fun <A, B> ValueParser<A>.map(crossinline transform: (A) -> B?): ValueParser<B> {
+    return ValueParser<B> { value ->
+        this.parseValue(value).flatMap { a ->
+            transform(a)?.let { b -> Result.success(b) }
+                ?: Result.failure(ArgParseError("Failed to transform value $value"))
+        }
+    }
+}
+
+/**
+ * Base type parsers are provided by the lib, and can be simply composed upon by [ValueParser.map]
+ * functions on the parser
+ */
+
+/** String parsing always succeeds if the value exists */
+private val parseString: ValueParser<String> = ValueParser { value -> Result.success(value) }
+
+private val parseBoolean: ValueParser<Boolean> = ValueParser { value ->
+    value.toBooleanStrictOrNull()?.let { Result.success(it) }
+        ?: Result.failure(ArgParseError("Failed to parse $value as a boolean"))
+}
+
+private val parseInt: ValueParser<Int> = ValueParser { value ->
+    value.toIntOrNull()?.let { Result.success(it) }
+        ?: Result.failure(ArgParseError("Failed to parse $value as an int"))
+}
+
+private val parseFloat: ValueParser<Float> = ValueParser { value ->
+    value.toFloatOrNull()?.let { Result.success(it) }
+        ?: Result.failure(ArgParseError("Failed to parse $value as a float"))
+}
+
+/** Default parsers that can be use as-is, or [map]ped to another type */
+object Type {
+    val Boolean = parseBoolean
+    val Int = parseInt
+    val Float = parseFloat
+    val String = parseString
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
index c9ce12a..ff40f70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
@@ -16,13 +16,7 @@
 
 package com.android.systemui.statusbar.connectivity;
 
-import android.content.Context;
 import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.os.SimpleClock;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -35,25 +29,18 @@
 import androidx.lifecycle.LifecycleOwner;
 import androidx.lifecycle.LifecycleRegistry;
 
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.settings.UserTracker;
 import com.android.wifitrackerlib.MergedCarrierEntry;
 import com.android.wifitrackerlib.WifiEntry;
 import com.android.wifitrackerlib.WifiPickerTracker;
 
 import java.io.PrintWriter;
-import java.time.Clock;
-import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executor;
 
-import javax.inject.Inject;
-
 /** */
 public class AccessPointControllerImpl implements AccessPointController,
         WifiPickerTracker.WifiPickerTrackerCallback,
@@ -272,77 +259,4 @@
             }
         }
     };
-
-    /**
-     * Factory for creating {@link WifiPickerTracker}.
-     *
-     * Uses the same time intervals as the Settings page for Wifi.
-     */
-    @SysUISingleton
-    public static class WifiPickerTrackerFactory {
-
-        // Max age of tracked WifiEntries
-        private static final long MAX_SCAN_AGE_MILLIS = 15_000;
-        // Interval between initiating WifiPickerTracker scans
-        private static final long SCAN_INTERVAL_MILLIS = 10_000;
-
-        private final Context mContext;
-        private final @Nullable WifiManager mWifiManager;
-        private final ConnectivityManager mConnectivityManager;
-        private final Handler mMainHandler;
-        private final Handler mWorkerHandler;
-        private final Clock mClock = new SimpleClock(ZoneOffset.UTC) {
-            @Override
-            public long millis() {
-                return SystemClock.elapsedRealtime();
-            }
-        };
-
-        @Inject
-        public WifiPickerTrackerFactory(
-                Context context,
-                @Nullable WifiManager wifiManager,
-                ConnectivityManager connectivityManager,
-                @Main Handler mainHandler,
-                @Background Handler workerHandler
-        ) {
-            mContext = context;
-            mWifiManager = wifiManager;
-            mConnectivityManager = connectivityManager;
-            mMainHandler = mainHandler;
-            mWorkerHandler = workerHandler;
-        }
-
-        private boolean isSupported() {
-            return mWifiManager != null;
-        }
-
-        /**
-         * Create a {@link WifiPickerTracker}
-         *
-         * @param lifecycle
-         * @param listener
-         * @return a new {@link WifiPickerTracker} or {@code null} if {@link WifiManager} is null.
-         */
-        public @Nullable WifiPickerTracker create(
-                Lifecycle lifecycle,
-                WifiPickerTracker.WifiPickerTrackerCallback listener
-        ) {
-            if (mWifiManager == null) {
-                return null;
-            }
-            return new WifiPickerTracker(
-                    lifecycle,
-                    mContext,
-                    mWifiManager,
-                    mConnectivityManager,
-                    mMainHandler,
-                    mWorkerHandler,
-                    mClock,
-                    MAX_SCAN_AGE_MILLIS,
-                    SCAN_INTERVAL_MILLIS,
-                    listener
-            );
-        }
-    }
 }
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 2465c21..f381b37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -18,10 +18,6 @@
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN;
-import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
-import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE;
-import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import android.annotation.Nullable;
@@ -74,7 +70,7 @@
 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.core.LogLevel;
 import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.settings.UserTracker;
@@ -87,8 +83,6 @@
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.CarrierConfigTracker;
 
-import kotlin.Unit;
-
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -103,6 +97,8 @@
 
 import javax.inject.Inject;
 
+import kotlin.Unit;
+
 /** Platform implementation of the network controller. **/
 @SysUISingleton
 public class NetworkControllerImpl extends BroadcastReceiver
@@ -469,7 +465,7 @@
 
         mDemoModeController.addCallback(this);
 
-        mDumpManager.registerDumpable(TAG, this);
+        mDumpManager.registerNormalDumpable(TAG, this);
     }
 
     private final Runnable mClearForceValidated = () -> {
@@ -557,10 +553,6 @@
         mBroadcastDispatcher.unregisterReceiver(this);
     }
 
-    public int getConnectedWifiLevel() {
-        return mWifiSignalController.getState().level;
-    }
-
     @Override
     public AccessPointController getAccessPointController() {
         return mAccessPoints;
@@ -654,14 +646,6 @@
         return mWifiSignalController.isCarrierMergedWifi(subId);
     }
 
-    boolean hasDefaultNetwork() {
-        return !mNoDefaultNetwork;
-    }
-
-    boolean isNonCarrierWifiNetworkAvailable() {
-        return !mNoNetworksAvailable;
-    }
-
     boolean isEthernetDefault() {
         return mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
     }
@@ -1242,15 +1226,12 @@
     }
 
     private boolean mDemoInetCondition;
-    private WifiState mDemoWifiState;
 
     @Override
     public void onDemoModeStarted() {
         if (DEBUG) Log.d(TAG, "Entering demo mode");
         unregisterListeners();
         mDemoInetCondition = mInetCondition;
-        mDemoWifiState = mWifiSignalController.getState();
-        mDemoWifiState.ssid = "DemoMode";
     }
 
     @Override
@@ -1300,43 +1281,8 @@
                 controller.updateConnectivity(connected, connected);
             }
         }
-        String wifi = args.getString("wifi");
-        if (wifi != null && !mStatusBarPipelineFlags.runNewWifiIconBackend()) {
-            boolean show = wifi.equals("show");
-            String level = args.getString("level");
-            if (level != null) {
-                mDemoWifiState.level = level.equals("null") ? -1
-                        : Math.min(Integer.parseInt(level), WifiIcons.WIFI_LEVEL_COUNT - 1);
-                mDemoWifiState.connected = mDemoWifiState.level >= 0;
-            }
-            String activity = args.getString("activity");
-            if (activity != null) {
-                switch (activity) {
-                    case "inout":
-                        mWifiSignalController.setActivity(DATA_ACTIVITY_INOUT);
-                        break;
-                    case "in":
-                        mWifiSignalController.setActivity(DATA_ACTIVITY_IN);
-                        break;
-                    case "out":
-                        mWifiSignalController.setActivity(DATA_ACTIVITY_OUT);
-                        break;
-                    default:
-                        mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
-                        break;
-                }
-            } else {
-                mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
-            }
-            String ssid = args.getString("ssid");
-            if (ssid != null) {
-                mDemoWifiState.ssid = ssid;
-            }
-            mDemoWifiState.enabled = show;
-            mWifiSignalController.notifyListeners();
-        }
         String sims = args.getString("sims");
-        if (sims != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
+        if (sims != null) {
             int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
             List<SubscriptionInfo> subs = new ArrayList<>();
             if (num != mMobileSignalControllers.size()) {
@@ -1359,7 +1305,7 @@
             mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
         }
         String mobile = args.getString("mobile");
-        if (mobile != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
+        if (mobile != null) {
             boolean show = mobile.equals("show");
             String datatype = args.getString("datatype");
             String slotString = args.getString("slot");
@@ -1444,7 +1390,7 @@
             controller.notifyListeners();
         }
         String carrierNetworkChange = args.getString("carriernetworkchange");
-        if (carrierNetworkChange != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
+        if (carrierNetworkChange != null) {
             boolean show = carrierNetworkChange.equals("show");
             for (int i = 0; i < mMobileSignalControllers.size(); i++) {
                 MobileSignalController controller = mMobileSignalControllers.valueAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt
new file mode 100644
index 0000000..ddbfcef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.connectivity
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.wifi.WifiManager
+import android.os.Handler
+import android.os.SimpleClock
+import androidx.lifecycle.Lifecycle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.time.SystemClock
+import com.android.wifitrackerlib.WifiPickerTracker
+import com.android.wifitrackerlib.WifiPickerTracker.WifiPickerTrackerCallback
+import java.time.Clock
+import java.time.ZoneOffset
+import javax.inject.Inject
+
+/**
+ * Factory for creating [WifiPickerTracker] for SysUI.
+ *
+ * Uses the same time intervals as the Settings page for Wifi.
+ */
+@SysUISingleton
+class WifiPickerTrackerFactory
+@Inject
+constructor(
+    private val context: Context,
+    private val wifiManager: WifiManager?,
+    private val connectivityManager: ConnectivityManager,
+    private val systemClock: SystemClock,
+    @Main private val mainHandler: Handler,
+    @Background private val workerHandler: Handler,
+) {
+    private val clock: Clock =
+        object : SimpleClock(ZoneOffset.UTC) {
+            override fun millis(): Long {
+                return systemClock.elapsedRealtime()
+            }
+        }
+    val isSupported: Boolean
+        get() = wifiManager != null
+
+    /**
+     * Creates a [WifiPickerTracker] instance.
+     *
+     * @return a new [WifiPickerTracker] or null if [WifiManager] is null.
+     */
+    fun create(
+        lifecycle: Lifecycle,
+        listener: WifiPickerTrackerCallback,
+    ): WifiPickerTracker? {
+        return if (wifiManager == null) {
+            null
+        } else
+            WifiPickerTracker(
+                lifecycle,
+                context,
+                wifiManager,
+                connectivityManager,
+                mainHandler,
+                workerHandler,
+                clock,
+                MAX_SCAN_AGE_MILLIS,
+                SCAN_INTERVAL_MILLIS,
+                listener,
+            )
+    }
+
+    companion object {
+        /** Max age of tracked WifiEntries. */
+        private const val MAX_SCAN_AGE_MILLIS: Long = 15000
+        /** Interval between initiating WifiPickerTracker scans. */
+        private const val SCAN_INTERVAL_MILLIS: Long = 10000
+    }
+}
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 c67e81f1..f726c4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -29,6 +29,7 @@
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.AnimationFeatureFlags;
 import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -36,11 +37,13 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 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.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeSurface;
 import com.android.systemui.shade.carrier.ShadeCarrierGroupController;
 import com.android.systemui.statusbar.ActionClickLogger;
 import com.android.systemui.statusbar.CommandQueue;
@@ -69,6 +72,7 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBarIconList;
+import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
 import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags;
@@ -76,25 +80,24 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
-import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.time.SystemClock;
 
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
 import dagger.Binds;
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
 /**
  * This module provides instances needed to construct {@link CentralSurfacesImpl}. These are moved to
  * this separate from {@link CentralSurfacesModule} module so that components that wish to build
  * their own version of CentralSurfaces can include just dependencies, without injecting
  * CentralSurfaces itself.
  */
-@Module
+@Module(includes = {StatusBarNotificationPresenterModule.class})
 public interface CentralSurfacesDependenciesModule {
     /** */
     @SysUISingleton
@@ -105,7 +108,7 @@
             NotificationLockscreenUserManager lockscreenUserManager,
             SmartReplyController smartReplyController,
             NotificationVisibilityProvider visibilityProvider,
-            Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
+            PowerInteractor powerInteractor,
             StatusBarStateController statusBarStateController,
             RemoteInputUriController remoteInputUriController,
             RemoteInputControllerLogger remoteInputControllerLogger,
@@ -118,7 +121,7 @@
                 lockscreenUserManager,
                 smartReplyController,
                 visibilityProvider,
-                centralSurfacesOptionalLazy,
+                powerInteractor,
                 statusBarStateController,
                 remoteInputUriController,
                 remoteInputControllerLogger,
@@ -195,11 +198,10 @@
     static CommandQueue provideCommandQueue(
             Context context,
             DisplayTracker displayTracker,
-            ProtoTracer protoTracer,
             CommandRegistry registry,
             DumpHandler dumpHandler
     ) {
-        return new CommandQueue(context, displayTracker, protoTracer, registry, dumpHandler);
+        return new CommandQueue(context, displayTracker, registry, dumpHandler);
     }
 
     /**
@@ -275,6 +277,21 @@
         return ongoingCallController;
     }
 
+    /**
+     * {@link NotificationPanelViewController} implements two interfaces:
+     *  - {@link com.android.systemui.shade.ShadeViewController}, which can be used by any class
+     *    needing access to the shade.
+     *  - {@link ShadeSurface}, which should *only* be used by {@link CentralSurfacesImpl}.
+     *
+     * Since {@link ShadeSurface} should only be accessible by {@link CentralSurfacesImpl}, it's
+     * *only* bound in this CentralSurfaces dependencies module.
+     * The {@link com.android.systemui.shade.ShadeViewController} interface is bound in
+     * {@link com.android.systemui.shade.ShadeModule} so others can access it.
+     */
+    @Binds
+    @SysUISingleton
+    ShadeSurface provideShadeSurface(NotificationPanelViewController impl);
+
     /** */
     @Binds
     ShadeCarrierGroupController.SlotIndexResolver provideSlotIndexResolver(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt
index f04159c..dc86869 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt
@@ -71,15 +71,14 @@
     }
 
     /**
-     * Returns a string representing the, old, new, and new-after-modification disable flag states,
-     * as well as the differences between each of the states.
+     * Returns a string representing the new and new-after-modification disable flag states,
+     * as well as the differences between them (if there are any).
      *
-     * Example if [old], [new], and [newAfterLocalModification] are all different:
-     *   Old: EnaiHbcRso.qINgr | New: EnaihBcRso.qiNGR (changed: hB.iGR) | New after local
-     *   modification: EnaihBcRso.qInGR (changed: .n)
+     * Example if [new] and [newAfterLocalModification] are different:
+     *   New: EnaihBcRso.qiNGR | New after local modification: EnaihBCRso.qInGR (changed: C.In)
      *
-     * Example if [old] and [new] are the same:
-     *   EnaihBcRso.qiNGR (unchanged)
+     * Example if [new] and [newAfterLocalModification] are the same:
+     *   New: EnaihBcRso.qiNGR
      *
      * A capital character signifies the flag is set and a lowercase character signifies that the
      * flag isn't set. The flag states will be logged in the same order as the passed-in lists.
@@ -88,37 +87,17 @@
      * is no difference. the new-after-modification state also won't be included if there's no
      * difference from the new state.
      *
-     * @param old the disable state that had been previously sent. Null if we don't need to log the
-     *   previously sent state.
      * @param new the new disable state that has just been sent.
      * @param newAfterLocalModification the new disable states after a class has locally modified
      *   them. Null if the class does not locally modify.
      */
     fun getDisableFlagsString(
-        old: DisableState? = null,
         new: DisableState,
         newAfterLocalModification: DisableState? = null
     ): String {
         val builder = StringBuilder("Received new disable state: ")
 
-        // This if/else has slightly repetitive code but is easier to read.
-        if (old != null && old != new) {
-            builder.append("Old: ")
-            builder.append(getFlagsString(old))
-            builder.append(" | ")
-            builder.append("New: ")
-            builder.append(getFlagsString(new))
-            builder.append(" ")
-            builder.append(getDiffString(old, new))
-        } else if (old != null && old == new) {
-            // If old and new are the same, we only need to print one of them.
-            builder.append(getFlagsString(new))
-            builder.append(" ")
-            builder.append(getDiffString(old, new))
-        } else { // old == null
-            builder.append(getFlagsString(new))
-            // Don't get a diff string because we have no [old] to compare with.
-        }
+        builder.append(getFlagsString(new))
 
         if (newAfterLocalModification != null && new != newAfterLocalModification) {
             builder.append(" | New after local modification: ")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/dagger/DisableFlagsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/dagger/DisableFlagsModule.kt
new file mode 100644
index 0000000..3d5f8df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/dagger/DisableFlagsModule.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.disableflags.dagger
+
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+/** Provides information related to disable flags. */
+@Module
+abstract class DisableFlagsModule {
+    @Binds
+    abstract fun bindDisableFlagsRepo(impl: DisableFlagsRepositoryImpl): DisableFlagsRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt
new file mode 100644
index 0000000..2bb4765
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.disableflags.data.model
+
+import android.app.StatusBarManager.DISABLE2_NONE
+import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
+import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
+import android.app.StatusBarManager.DISABLE_NONE
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
+
+/**
+ * Model for the disable flags that come from [IStatusBar].
+ *
+ * For clients of the disable flags: do *not* refer to the disable integers directly. Instead,
+ * re-use or define a helper method that internally processes the flags. (We want to hide the
+ * bitwise logic here so no one else has to worry about it.)
+ */
+data class DisableFlagsModel(
+    private val disable1: Int = DISABLE_NONE,
+    private val disable2: Int = DISABLE2_NONE,
+) {
+    /** Returns true if notification alerts are allowed based on the flags. */
+    fun areNotificationAlertsEnabled(): Boolean {
+        return (disable1 and DISABLE_NOTIFICATION_ALERTS) == 0
+    }
+
+    /** Returns true if the shade is allowed based on the flags. */
+    fun isShadeEnabled(): Boolean {
+        return (disable2 and DISABLE2_NOTIFICATION_SHADE) == 0
+    }
+
+    /** Returns true if full quick settings are allowed based on the flags. */
+    fun isQuickSettingsEnabled(): Boolean {
+        return (disable2 and DISABLE2_QUICK_SETTINGS) == 0
+    }
+
+    /** Logs the change to the provided buffer. */
+    fun logChange(buffer: LogBuffer, disableFlagsLogger: DisableFlagsLogger) {
+        buffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                int1 = disable1
+                int2 = disable2
+            },
+            {
+                disableFlagsLogger.getDisableFlagsString(
+                    new = DisableFlagsLogger.DisableState(int1, int2),
+                )
+            }
+        )
+    }
+
+    private companion object {
+        const val TAG = "DisableFlagsModel"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt
new file mode 100644
index 0000000..13b74b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.disableflags.data.repository
+
+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.DisplayId
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.dagger.DisableFlagsRepositoryLog
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository for the disable flags received from external systems. See [IStatusBar.disable]. */
+interface DisableFlagsRepository {
+    /** A model of the disable flags last received from [IStatusBar]. */
+    val disableFlags: StateFlow<DisableFlagsModel>
+}
+
+@SysUISingleton
+class DisableFlagsRepositoryImpl
+@Inject
+constructor(
+    commandQueue: CommandQueue,
+    @DisplayId private val thisDisplayId: Int,
+    @Application scope: CoroutineScope,
+    remoteInputQuickSettingsDisabler: RemoteInputQuickSettingsDisabler,
+    @DisableFlagsRepositoryLog private val logBuffer: LogBuffer,
+    private val disableFlagsLogger: DisableFlagsLogger,
+) : DisableFlagsRepository {
+    override val disableFlags: StateFlow<DisableFlagsModel> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : CommandQueue.Callbacks {
+                        override fun disable(
+                            displayId: Int,
+                            state1: Int,
+                            state2: Int,
+                            animate: Boolean,
+                        ) {
+                            if (displayId != thisDisplayId) {
+                                return
+                            }
+                            trySend(
+                                DisableFlagsModel(
+                                    state1,
+                                    // Ideally, [RemoteInputQuickSettingsDisabler] should instead
+                                    // expose a flow that gets `combine`d with this [disableFlags]
+                                    // flow in a [DisableFlagsInteractor] or
+                                    // [QuickSettingsInteractor]-type class. However, that's out of
+                                    // scope for the CentralSurfaces removal project.
+                                    remoteInputQuickSettingsDisabler.adjustDisableFlags(state2),
+                                )
+                            )
+                        }
+                    }
+                commandQueue.addCallback(callback)
+                awaitClose { commandQueue.removeCallback(callback) }
+            }
+            .distinctUntilChanged()
+            .onEach { it.logChange(logBuffer, disableFlagsLogger) }
+            // Use Eagerly because we always need to know about disable flags
+            .stateIn(scope, SharingStarted.Eagerly, DisableFlagsModel())
+}
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 eddb683..d1e0a71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -234,7 +234,7 @@
             }
 
             // Set the dot's view gravity to hug the status bar
-            (corner.findViewById<View>(R.id.privacy_dot)
+            (corner.requireViewById<View>(R.id.privacy_dot)
                     .layoutParams as FrameLayout.LayoutParams)
                         .gravity = rotatedCorner.innerGravity()
         }
@@ -255,7 +255,7 @@
         // in every rotation. The only thing we need to check is rtl
         val rtl = state.layoutRtl
         val size = Point()
-        tl.context.display.getRealSize(size)
+        tl.context.display?.getRealSize(size)
         val currentRotation = RotationUtils.getExactRotation(tl.context)
 
         val displayWidth: Int
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt
index 3d6d489..84796f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.time.SystemClock
@@ -36,6 +38,13 @@
 
         @Provides
         @SysUISingleton
+        @SystemStatusAnimationSchedulerLog
+        fun provideSystemStatusAnimationSchedulerLogBuffer(factory: LogBufferFactory): LogBuffer {
+            return factory.create("SystemStatusAnimationSchedulerLog", 60)
+        }
+
+        @Provides
+        @SysUISingleton
         fun provideSystemStatusAnimationScheduler(
                 featureFlags: FeatureFlags,
                 coordinator: SystemEventCoordinator,
@@ -44,7 +53,8 @@
                 dumpManager: DumpManager,
                 systemClock: SystemClock,
                 @Application coroutineScope: CoroutineScope,
-                @Main executor: DelayableExecutor
+                @Main executor: DelayableExecutor,
+                logger: SystemStatusAnimationSchedulerLogger
         ): SystemStatusAnimationScheduler {
             return if (featureFlags.isEnabled(Flags.PLUG_IN_STATUS_BAR_CHIP)) {
                 SystemStatusAnimationSchedulerImpl(
@@ -53,7 +63,8 @@
                         statusBarWindowController,
                         dumpManager,
                         systemClock,
-                        coroutineScope
+                        coroutineScope,
+                        logger
                 )
             } else {
                 SystemStatusAnimationSchedulerLegacyImpl(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
index e5849c0..bde298d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.privacy.PrivacyItem
 import com.android.systemui.statusbar.BatteryStatusChip
+import com.android.systemui.statusbar.ConnectedDisplayChip
 
 typealias ViewCreator = (context: Context) -> BackgroundAnimatableView
 
@@ -87,6 +88,23 @@
     }
 }
 
+/** Event that triggers a connected display chip in the status bar. */
+class ConnectedDisplayEvent : StatusEvent {
+    /** Priority is set higher than [BatteryEvent]. */
+    override val priority = 60
+    override var forceVisible = false
+    override val showAnimation = true
+    override var contentDescription: String? = ""
+
+    override val viewCreator: ViewCreator = { context ->
+        ConnectedDisplayChip(context)
+    }
+
+    override fun toString(): String {
+        return javaClass.simpleName
+    }
+}
+
 /** open only for testing purposes. (See [FakeStatusEvent.kt]) */
 open class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent {
     override var contentDescription: String? = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index 776956a..1ad4620 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -30,9 +30,11 @@
 import androidx.core.animation.AnimatorListenerAdapter
 import androidx.core.animation.AnimatorSet
 import androidx.core.animation.ValueAnimator
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.R
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.animation.AnimationUtil.Companion.frames
@@ -46,7 +48,7 @@
     private val context: Context,
     private val statusBarWindowController: StatusBarWindowController,
     private val contentInsetsProvider: StatusBarContentInsetsProvider,
-    private val featureFlags: FeatureFlags
+    private val featureFlags: FeatureFlags,
 ) : SystemStatusAnimationCallback {
 
     private lateinit var animationWindowView: FrameLayout
@@ -56,7 +58,8 @@
 
     // Left for LTR, Right for RTL
     private var animationDirection = LEFT
-    private var chipBounds = Rect()
+
+    @VisibleForTesting var chipBounds = Rect()
     private val chipWidth get() = chipBounds.width()
     private val chipRight get() = chipBounds.right
     private val chipLeft get() = chipBounds.left
@@ -69,7 +72,7 @@
     private var animRect = Rect()
 
     // TODO: move to dagger
-    private var initialized = false
+    @VisibleForTesting var initialized = false
 
     /**
      * Give the chip controller a chance to inflate and configure the chip view before we start
@@ -98,23 +101,7 @@
                     View.MeasureSpec.makeMeasureSpec(
                             (animationWindowView.parent as View).height, AT_MOST))
 
-            // decide which direction we're animating from, and then set some screen coordinates
-            val contentRect = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
-            val chipTop = ((animationWindowView.parent as View).height - it.view.measuredHeight) / 2
-            val chipBottom = chipTop + it.view.measuredHeight
-            val chipRight: Int
-            val chipLeft: Int
-            when (animationDirection) {
-                LEFT -> {
-                    chipRight = contentRect.right
-                    chipLeft = contentRect.right - it.chipWidth
-                }
-                else /* RIGHT */ -> {
-                    chipLeft = contentRect.left
-                    chipRight = contentRect.left + it.chipWidth
-                }
-            }
-            chipBounds = Rect(chipLeft, chipTop, chipRight, chipBottom)
+            updateChipBounds(it, contentInsetsProvider.getStatusBarContentAreaForCurrentRotation())
         }
     }
 
@@ -181,10 +168,8 @@
         }
 
         val keyFrame1Height = dotSize * 2
-        val v = currentAnimatedView!!.view
-        val chipVerticalCenter = v.top + v.measuredHeight / 2
-        val height1 = ValueAnimator.ofInt(
-                currentAnimatedView!!.view.measuredHeight, keyFrame1Height).apply {
+        val chipVerticalCenter = chipBounds.top + chipBounds.height() / 2
+        val height1 = ValueAnimator.ofInt(chipBounds.height(), keyFrame1Height).apply {
             startDelay = 8.frames
             duration = 6.frames
             interpolator = STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_1
@@ -253,16 +238,72 @@
         return animSet
     }
 
-    private fun init() {
+    fun init() {
         initialized = true
         themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
         animationWindowView = LayoutInflater.from(themedContext)
                 .inflate(R.layout.system_event_animation_window, null) as FrameLayout
-        val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
-        lp.gravity = Gravity.END or Gravity.CENTER_VERTICAL
+        // Matches status_bar.xml
+        val height = themedContext.resources.getDimensionPixelSize(R.dimen.status_bar_height)
+        val lp = FrameLayout.LayoutParams(MATCH_PARENT, height)
+        lp.gravity = Gravity.END or Gravity.TOP
         statusBarWindowController.addViewToWindow(animationWindowView, lp)
         animationWindowView.clipToPadding = false
         animationWindowView.clipChildren = false
+
+        // Use contentInsetsProvider rather than configuration controller, since we only care
+        // about status bar dimens
+        contentInsetsProvider.addCallback(object : StatusBarContentInsetsChangedListener {
+            override fun onStatusBarContentInsetsChanged() {
+                val newContentArea = contentInsetsProvider
+                    .getStatusBarContentAreaForCurrentRotation()
+                updateDimens(newContentArea)
+
+                // If we are currently animating, we have to re-solve for the chip bounds. If we're
+                // not animating then [prepareChipAnimation] will take care of it for us
+                currentAnimatedView?.let {
+                    updateChipBounds(it, newContentArea)
+                    // Since updateCurrentAnimatedView can only be called during an animation, we
+                    // have to create a dummy animator here to apply the new chip bounds
+                    val animator = ValueAnimator.ofInt(0, 1).setDuration(0)
+                    animator.addUpdateListener { updateCurrentAnimatedView() }
+                    animator.start()
+                }
+            }
+        })
+    }
+
+    private fun updateDimens(contentArea: Rect) {
+        val lp = animationWindowView.layoutParams as FrameLayout.LayoutParams
+        lp.height = contentArea.height()
+
+        animationWindowView.layoutParams = lp
+    }
+
+    /**
+     * Use the current status bar content area and the current chip's measured size to update
+     * the animation rect and chipBounds. This method can be called at any time and will update
+     * the current animation values properly during e.g. a rotation.
+     */
+    private fun updateChipBounds(chip: BackgroundAnimatableView, contentArea: Rect) {
+        // decide which direction we're animating from, and then set some screen coordinates
+        val chipTop = (contentArea.bottom - chip.view.measuredHeight) / 2
+        val chipBottom = chipTop + chip.view.measuredHeight
+        val chipRight: Int
+        val chipLeft: Int
+
+        when (animationDirection) {
+            LEFT -> {
+                chipRight = contentArea.right
+                chipLeft = contentArea.right - chip.chipWidth
+            }
+            else /* RIGHT */ -> {
+                chipLeft = contentArea.left
+                chipRight = contentArea.left + chip.chipWidth
+            }
+        }
+        chipBounds = Rect(chipLeft, chipTop, chipRight, chipBottom)
+        animRect.set(chipBounds)
     }
 
     private fun layoutParamsDefault(marginEnd: Int): FrameLayout.LayoutParams =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
index 26fd230..2403920 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
@@ -22,6 +22,9 @@
 import android.provider.DeviceConfig.NAMESPACE_PRIVACY
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.privacy.PrivacyChipBuilder
@@ -30,29 +33,45 @@
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 
 /**
- * Listens for system events (battery, privacy, connectivity) and allows listeners
- * to show status bar animations when they happen
+ * Listens for system events (battery, privacy, connectivity) and allows listeners to show status
+ * bar animations when they happen
  */
 @SysUISingleton
-class SystemEventCoordinator @Inject constructor(
+class SystemEventCoordinator
+@Inject
+constructor(
     private val systemClock: SystemClock,
     private val batteryController: BatteryController,
     private val privacyController: PrivacyItemController,
     private val context: Context,
-    private val featureFlags: FeatureFlags
+    private val featureFlags: FeatureFlags,
+    @Application private val appScope: CoroutineScope,
+    connectedDisplayInteractor: ConnectedDisplayInteractor
 ) {
+    private val onDisplayConnectedFlow =
+        connectedDisplayInteractor.connectedDisplayState
+                .filter { it != State.DISCONNECTED }
+
+    private var connectedDisplayCollectionJob: Job? = null
     private lateinit var scheduler: SystemStatusAnimationScheduler
 
     fun startObserving() {
         batteryController.addCallback(batteryStateListener)
         privacyController.addCallback(privacyStateListener)
+        startConnectedDisplayCollection()
     }
 
     fun stopObserving() {
         batteryController.removeCallback(batteryStateListener)
         privacyController.removeCallback(privacyStateListener)
+        connectedDisplayCollectionJob?.cancel()
     }
 
     fun attachScheduler(s: SystemStatusAnimationScheduler) {
@@ -80,6 +99,13 @@
         scheduler.onStatusEvent(event)
     }
 
+    private fun startConnectedDisplayCollection() {
+        connectedDisplayCollectionJob =
+                onDisplayConnectedFlow
+                        .onEach { scheduler.onStatusEvent(ConnectedDisplayEvent()) }
+                        .launchIn(appScope)
+    }
+
     private val batteryStateListener = object : BatteryController.BatteryStateChangeCallback {
         private var plugged = false
         private var stateKnown = false
@@ -138,7 +164,9 @@
         }
 
         private fun isChipAnimationEnabled(): Boolean {
-            return DeviceConfig.getBoolean(NAMESPACE_PRIVACY, CHIP_ANIMATION_ENABLED, true)
+            val defaultValue =
+                context.resources.getBoolean(R.bool.config_enablePrivacyChipAnimation)
+            return DeviceConfig.getBoolean(NAMESPACE_PRIVACY, CHIP_ANIMATION_ENABLED, defaultValue)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index 56ea703..f40f570 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -18,7 +18,6 @@
 
 import android.os.Process
 import android.provider.DeviceConfig
-import android.util.Log
 import androidx.core.animation.Animator
 import androidx.core.animation.AnimatorListenerAdapter
 import androidx.core.animation.AnimatorSet
@@ -69,7 +68,8 @@
     private val statusBarWindowController: StatusBarWindowController,
     dumpManager: DumpManager,
     private val systemClock: SystemClock,
-    @Application private val coroutineScope: CoroutineScope
+    @Application private val coroutineScope: CoroutineScope,
+    private val logger: SystemStatusAnimationSchedulerLogger?
 ) : SystemStatusAnimationScheduler {
 
     companion object {
@@ -121,6 +121,10 @@
                     }
                 }
         }
+
+        coroutineScope.launch {
+            animationState.collect { logger?.logAnimationStateUpdate(it) }
+        }
     }
 
     @SystemAnimationState override fun getAnimationState(): Int = animationState.value
@@ -140,32 +144,17 @@
         ) {
             // a event can only be scheduled if no other event is in progress or it has a higher
             // priority. If a persistent dot is currently displayed, don't schedule the event.
-            if (DEBUG) {
-                Log.d(TAG, "scheduling event $event")
-            }
-
+            logger?.logScheduleEvent(event)
             scheduleEvent(event)
         } else if (currentlyDisplayedEvent?.shouldUpdateFromEvent(event) == true) {
-            if (DEBUG) {
-                Log.d(
-                    TAG,
-                    "updating current event from: $event. animationState=${animationState.value}"
-                )
-            }
+            logger?.logUpdateEvent(event, animationState.value)
             currentlyDisplayedEvent?.updateFromEvent(event)
             if (event.forceVisible) hasPersistentDot = true
         } else if (scheduledEvent.value?.shouldUpdateFromEvent(event) == true) {
-            if (DEBUG) {
-                Log.d(
-                    TAG,
-                    "updating scheduled event from: $event. animationState=${animationState.value}"
-                )
-            }
+            logger?.logUpdateEvent(event, animationState.value)
             scheduledEvent.value?.updateFromEvent(event)
         } else {
-            if (DEBUG) {
-                Log.d(TAG, "ignoring event $event")
-            }
+            logger?.logIgnoreEvent(event)
         }
     }
 
@@ -181,19 +170,19 @@
         // Set hasPersistentDot to false. If the animationState is anything before ANIMATING_OUT,
         // the disappear animation will not animate into a dot but remove the chip entirely
         hasPersistentDot = false
-        // if we are currently showing a persistent dot, hide it
-        if (animationState.value == SHOWING_PERSISTENT_DOT) notifyHidePersistentDot()
-        // if we are currently animating into a dot, wait for the animation to finish and then hide
-        // the dot
-        if (animationState.value == ANIMATING_OUT) {
-            coroutineScope.launch {
-                withTimeout(DISAPPEAR_ANIMATION_DURATION) {
-                    animationState.first {
-                        it == SHOWING_PERSISTENT_DOT || it == IDLE || it == ANIMATION_QUEUED
-                    }
-                    notifyHidePersistentDot()
-                }
+
+        if (animationState.value == SHOWING_PERSISTENT_DOT) {
+            // if we are currently showing a persistent dot, hide it and update the animationState
+            notifyHidePersistentDot()
+            if (scheduledEvent.value != null) {
+                animationState.value = ANIMATION_QUEUED
+            } else {
+                animationState.value = IDLE
             }
+        } else if (animationState.value == ANIMATING_OUT) {
+            // if we are currently animating out, hide the dot. The animationState will be updated
+            // once the animation has ended in the onAnimationEnd callback
+            notifyHidePersistentDot()
         }
     }
 
@@ -254,7 +243,7 @@
         if (!event.showAnimation && event.forceVisible) {
             // If animations are turned off, we'll transition directly to the dot
             animationState.value = SHOWING_PERSISTENT_DOT
-            notifyTransitionToPersistentDot()
+            notifyTransitionToPersistentDot(event)
             return
         }
 
@@ -346,7 +335,7 @@
         }
         animators.add(chipAnimationController.onSystemEventAnimationFinish(hasPersistentDot))
         if (hasPersistentDot) {
-            val dotAnim = notifyTransitionToPersistentDot()
+            val dotAnim = notifyTransitionToPersistentDot(currentlyDisplayedEvent)
             if (dotAnim != null) {
                 animators.add(dotAnim)
             }
@@ -355,11 +344,12 @@
         return AnimatorSet().also { it.playTogether(animators) }
     }
 
-    private fun notifyTransitionToPersistentDot(): Animator? {
+    private fun notifyTransitionToPersistentDot(event: StatusEvent?): Animator? {
+        logger?.logTransitionToPersistentDotCallbackInvoked()
         val anims: List<Animator> =
             listeners.mapNotNull {
                 it.onSystemStatusAnimationTransitionToPersistentDot(
-                    currentlyDisplayedEvent?.contentDescription
+                    event?.contentDescription
                 )
             }
         if (anims.isNotEmpty()) {
@@ -373,16 +363,9 @@
 
     private fun notifyHidePersistentDot(): Animator? {
         Assert.isMainThread()
+        logger?.logHidePersistentDotCallbackInvoked()
         val anims: List<Animator> = listeners.mapNotNull { it.onHidePersistentDot() }
 
-        if (animationState.value == SHOWING_PERSISTENT_DOT) {
-            if (scheduledEvent.value != null) {
-                animationState.value = ANIMATION_QUEUED
-            } else {
-                animationState.value = IDLE
-            }
-        }
-
         if (anims.isNotEmpty()) {
             val aSet = AnimatorSet()
             aSet.playTogether(anims)
@@ -424,5 +407,4 @@
     }
 }
 
-private const val DEBUG = false
 private const val TAG = "SystemStatusAnimationSchedulerImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLog.kt
new file mode 100644
index 0000000..4ac94a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+import javax.inject.Qualifier
+
+/** Logs for the SystemStatusAnimationScheduler. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class SystemStatusAnimationSchedulerLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLogger.kt
new file mode 100644
index 0000000..22b0b69
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLogger.kt
@@ -0,0 +1,92 @@
+package com.android.systemui.statusbar.events
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import javax.inject.Inject
+
+/** Logs for the SystemStatusAnimationScheduler. */
+@SysUISingleton
+class SystemStatusAnimationSchedulerLogger
+@Inject
+constructor(
+    @SystemStatusAnimationSchedulerLog private val logBuffer: LogBuffer,
+) {
+
+    fun logScheduleEvent(event: StatusEvent) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = event.javaClass.simpleName
+                int1 = event.priority
+                bool1 = event.forceVisible
+                bool2 = event.showAnimation
+            },
+            { "Scheduling event: $str1(forceVisible=$bool1, priority=$int1, showAnimation=$bool2)" }
+        )
+    }
+
+    fun logUpdateEvent(event: StatusEvent, @SystemAnimationState animationState: Int) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = event.javaClass.simpleName
+                int1 = event.priority
+                bool1 = event.forceVisible
+                bool2 = event.showAnimation
+                int2 = animationState
+            },
+            {
+                "Updating current event from: $str1(forceVisible=$bool1, priority=$int1, " +
+                    "showAnimation=$bool2), animationState=${animationState.name()}"
+            }
+        )
+    }
+
+    fun logIgnoreEvent(event: StatusEvent) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = event.javaClass.simpleName
+                int1 = event.priority
+                bool1 = event.forceVisible
+                bool2 = event.showAnimation
+            },
+            { "Ignore event: $str1(forceVisible=$bool1, priority=$int1, showAnimation=$bool2)" }
+        )
+    }
+
+    fun logHidePersistentDotCallbackInvoked() {
+        logBuffer.log(TAG, LogLevel.DEBUG, "Hide persistent dot callback invoked")
+    }
+
+    fun logTransitionToPersistentDotCallbackInvoked() {
+        logBuffer.log(TAG, LogLevel.DEBUG, "Transition to persistent dot callback invoked")
+    }
+
+    fun logAnimationStateUpdate(@SystemAnimationState animationState: Int) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { int1 = animationState },
+            { "AnimationState update: ${int1.name()}" }
+        )
+        animationState.name()
+    }
+
+    private fun @receiver:SystemAnimationState Int.name() =
+        when (this) {
+            IDLE -> "IDLE"
+            ANIMATION_QUEUED -> "ANIMATION_QUEUED"
+            ANIMATING_IN -> "ANIMATING_IN"
+            RUNNING_CHIP_ANIM -> "RUNNING_CHIP_ANIM"
+            ANIMATING_OUT -> "ANIMATING_OUT"
+            SHOWING_PERSISTENT_DOT -> "SHOWING_PERSISTENT_DOT"
+            else -> "UNKNOWN_ANIMATION_STATE"
+        }
+}
+
+private const val TAG = "SystemStatusAnimationSchedulerLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
index a67c26c..96725fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.SwipeUpLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 /** Log messages for [SwipeUpGestureHandler]. */
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 6c1dc8c..c5de165 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -139,6 +139,19 @@
 
             updateTextColorFromWallpaper()
             statusBarStateListener.onDozeAmountChanged(0f, statusBarStateController.dozeAmount)
+
+            if (regionSamplingEnabled && (!regionSamplers.containsKey(v))) {
+                var regionSampler = RegionSampler(
+                        v as View,
+                        uiExecutor,
+                        bgExecutor,
+                        regionSamplingEnabled,
+                        isLockscreen = true,
+                ) { updateTextColorFromRegionSampler() }
+                initializeTextColors(regionSampler)
+                regionSamplers[v] = regionSampler
+                regionSampler.startRegionSampler()
+            }
         }
 
         override fun onViewDetachedFromWindow(v: View) {
@@ -165,7 +178,21 @@
                     now.isBefore(Instant.ofEpochMilli(t.expiryTimeMillis))
         }
         if (weatherTarget != null) {
-            val weatherData = WeatherData.fromBundle(weatherTarget.baseAction.extras)
+            val clickIntent = weatherTarget.headerAction?.intent
+            val weatherData = weatherTarget.baseAction?.extras?.let { extras ->
+                WeatherData.fromBundle(
+                    extras,
+                ) { _ ->
+                    if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                        activityStarter.startActivity(
+                            clickIntent,
+                            true, /* dismissShade */
+                            null,
+                            false)
+                    }
+                }
+            }
+
             if (weatherData != null) {
                 keyguardUpdateMonitor.sendWeatherData(weatherData)
             }
@@ -173,23 +200,6 @@
 
         val filteredTargets = targets.filter(::filterSmartspaceTarget)
         plugin?.onTargetsAvailable(filteredTargets)
-        if (!isRegionSamplersCreated) {
-            for (v in smartspaceViews) {
-                if (regionSamplingEnabled) {
-                    var regionSampler = RegionSampler(
-                        v as View,
-                        uiExecutor,
-                        bgExecutor,
-                        regionSamplingEnabled,
-                        isLockscreen = true,
-                    ) { updateTextColorFromRegionSampler() }
-                    initializeTextColors(regionSampler)
-                    regionSamplers[v] = regionSampler
-                    regionSampler.startRegionSampler()
-                }
-            }
-            isRegionSamplersCreated = true
-        }
     }
 
     private val userTrackerCallback = object : UserTracker.Callback {
@@ -440,6 +450,12 @@
         session?.requestSmartspaceUpdate()
     }
 
+    fun removeViewsFromParent(viewGroup: ViewGroup) {
+        smartspaceViews.toList().forEach {
+            viewGroup.removeView(it as View)
+        }
+    }
+
     /**
      * Disconnects the smartspace view from the smartspace service and cleans up any resources.
      */
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 5f28ecb..bac8982 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -16,27 +16,15 @@
 
 package com.android.systemui.statusbar.notification
 
-import android.content.Context
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import javax.inject.Inject
 
-class NotifPipelineFlags @Inject constructor(
-    val context: Context,
-    val featureFlags: FeatureFlags,
-    val sysPropFlags: FlagResolver,
+class NotifPipelineFlags
+@Inject
+constructor(
+    private val featureFlags: FeatureFlags
 ) {
     fun isDevLoggingEnabled(): Boolean =
         featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
-
-    fun allowDismissOngoing(): Boolean =
-            sysPropFlags.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)
-
-    fun isOtpRedactionEnabled(): Boolean =
-            sysPropFlags.isEnabled(NotificationFlags.OTP_REDACTION)
-
-    val isNoHunForOldWhenEnabled: Boolean
-        get() = featureFlags.isEnabled(Flags.NO_HUN_FOR_OLD_WHEN)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index bab553e..d10fac6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -17,15 +17,14 @@
 
 import android.app.Notification;
 import android.os.PowerManager;
-import android.os.SystemClock;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 import android.view.View;
 
 import com.android.systemui.DejankUtils;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.wm.shell.bubbles.Bubbles;
 
 import java.util.Optional;
@@ -40,7 +39,7 @@
     private static final String TAG = "NotificationClicker";
 
     private final NotificationClickerLogger mLogger;
-    private final Optional<CentralSurfaces> mCentralSurfacesOptional;
+    private final PowerInteractor mPowerInteractor;
     private final Optional<Bubbles> mBubblesOptional;
     private final NotificationActivityStarter mNotificationActivityStarter;
 
@@ -54,11 +53,11 @@
 
     private NotificationClicker(
             NotificationClickerLogger logger,
-            Optional<CentralSurfaces> centralSurfacesOptional,
+            PowerInteractor powerInteractor,
             Optional<Bubbles> bubblesOptional,
             NotificationActivityStarter notificationActivityStarter) {
         mLogger = logger;
-        mCentralSurfacesOptional = centralSurfacesOptional;
+        mPowerInteractor = powerInteractor;
         mBubblesOptional = bubblesOptional;
         mNotificationActivityStarter = notificationActivityStarter;
     }
@@ -70,9 +69,7 @@
             return;
         }
 
-        mCentralSurfacesOptional.ifPresent(centralSurfaces -> centralSurfaces.wakeUpIfDozing(
-                SystemClock.uptimeMillis(), "NOTIFICATION_CLICK",
-                PowerManager.WAKE_REASON_GESTURE));
+        mPowerInteractor.wakeUpIfDozing("NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE);
 
         final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
         final NotificationEntry entry = row.getEntry();
@@ -131,21 +128,22 @@
     /** Daggerized builder for NotificationClicker. */
     public static class Builder {
         private final NotificationClickerLogger mLogger;
+        private final PowerInteractor mPowerInteractor;
 
         @Inject
-        public Builder(NotificationClickerLogger logger) {
+        public Builder(NotificationClickerLogger logger, PowerInteractor powerInteractor) {
             mLogger = logger;
+            mPowerInteractor = powerInteractor;
         }
 
         /** Builds an instance. */
         public NotificationClicker build(
-                Optional<CentralSurfaces> centralSurfacesOptional,
                 Optional<Bubbles> bubblesOptional,
                 NotificationActivityStarter notificationActivityStarter
         ) {
             return new NotificationClicker(
                     mLogger,
-                    centralSurfacesOptional,
+                    mPowerInteractor,
                     bubblesOptional,
                     notificationActivityStarter);
         }
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 a3a72d9..cea2b59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotifInteractionLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.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 798bbe8..c1b905a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -1,10 +1,26 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.statusbar.notification
 
 import android.view.ViewGroup
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.LaunchAnimator
-import com.android.systemui.shade.NotificationShadeWindowViewController
+import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
@@ -17,7 +33,7 @@
 /** A provider of [NotificationLaunchAnimatorController]. */
 @CentralSurfacesComponent.CentralSurfacesScope
 class NotificationLaunchAnimatorControllerProvider @Inject constructor(
-    private val notificationShadeWindowViewController: NotificationShadeWindowViewController,
+    private val notificationExpansionRepository: NotificationExpansionRepository,
     private val notificationListContainer: NotificationListContainer,
     private val headsUpManager: HeadsUpManagerPhone,
     private val jankMonitor: InteractionJankMonitor
@@ -28,7 +44,7 @@
         onFinishAnimationCallback: Runnable? = null
     ): NotificationLaunchAnimatorController {
         return NotificationLaunchAnimatorController(
-            notificationShadeWindowViewController,
+            notificationExpansionRepository,
             notificationListContainer,
             headsUpManager,
             notification,
@@ -44,7 +60,7 @@
  * notification expanding into an opening window.
  */
 class NotificationLaunchAnimatorController(
-    private val notificationShadeWindowViewController: NotificationShadeWindowViewController,
+    private val notificationExpansionRepository: NotificationExpansionRepository,
     private val notificationListContainer: NotificationListContainer,
     private val headsUpManager: HeadsUpManagerPhone,
     private val notification: ExpandableNotificationRow,
@@ -119,7 +135,7 @@
     }
 
     override fun onIntentStarted(willAnimate: Boolean) {
-        notificationShadeWindowViewController.setExpandAnimationRunning(willAnimate)
+        notificationExpansionRepository.setIsExpandAnimationRunning(willAnimate)
         notificationEntry.isExpandAnimationRunning = willAnimate
 
         if (!willAnimate) {
@@ -140,7 +156,7 @@
     override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
         // TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started
         // here?
-        notificationShadeWindowViewController.setExpandAnimationRunning(false)
+        notificationExpansionRepository.setIsExpandAnimationRunning(false)
         notificationEntry.isExpandAnimationRunning = false
         removeHun(animate = true)
         onFinishAnimationCallback?.run()
@@ -158,7 +174,7 @@
         jankMonitor.end(InteractionJankMonitor.CUJ_NOTIFICATION_APP_START)
 
         notification.isExpandAnimationRunning = false
-        notificationShadeWindowViewController.setExpandAnimationRunning(false)
+        notificationExpansionRepository.setIsExpandAnimationRunning(false)
         notificationEntry.isExpandAnimationRunning = false
         notificationListContainer.setExpandingNotification(null)
         applyParams(null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
index f7679ed..502e1d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -14,7 +14,7 @@
 package com.android.systemui.statusbar.notification
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.NotificationLockscreenLog
 import com.android.systemui.statusbar.StatusBarState
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
index 487a5f8..7809eaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.NotificationRemoteInputLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index 212f2c215..1c5aa3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -4,6 +4,9 @@
 import android.view.View
 import androidx.annotation.FloatRange
 import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.ViewRefactorFlag
 import com.android.systemui.statusbar.notification.stack.AnimationProperties
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import kotlin.math.abs
@@ -20,6 +23,8 @@
     /** Properties required for a Roundable */
     val roundableState: RoundableState
 
+    val clipHeight: Int
+
     /** Current top roundness */
     @get:FloatRange(from = 0.0, to = 1.0)
     @JvmDefault
@@ -40,12 +45,16 @@
     /** Current top corner in pixel, based on [topRoundness] and [maxRadius] */
     @JvmDefault
     val topCornerRadius: Float
-        get() = topRoundness * maxRadius
+        get() =
+            if (roundableState.newHeadsUpAnim.isEnabled) roundableState.topCornerRadius
+            else topRoundness * maxRadius
 
     /** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */
     @JvmDefault
     val bottomCornerRadius: Float
-        get() = bottomRoundness * maxRadius
+        get() =
+            if (roundableState.newHeadsUpAnim.isEnabled) roundableState.bottomCornerRadius
+            else bottomRoundness * maxRadius
 
     /** Get and update the current radii */
     @JvmDefault
@@ -320,14 +329,19 @@
  * @param roundable Target of the radius animation
  * @param maxRadius Max corner radius in pixels
  */
-class RoundableState(
+class RoundableState
+@JvmOverloads
+constructor(
     internal val targetView: View,
     private val roundable: Roundable,
     maxRadius: Float,
+    featureFlags: FeatureFlags? = null
 ) {
     internal var maxRadius = maxRadius
         private set
 
+    internal val newHeadsUpAnim = ViewRefactorFlag(featureFlags, Flags.IMPROVED_HUN_ANIMATIONS)
+
     /** Animatable for top roundness */
     private val topAnimatable = topAnimatable(roundable)
 
@@ -344,6 +358,41 @@
     internal var bottomRoundness = 0f
         private set
 
+    internal val topCornerRadius: Float
+        get() {
+            val height = roundable.clipHeight
+            val topRadius = topRoundness * maxRadius
+            val bottomRadius = bottomRoundness * maxRadius
+
+            if (height == 0) {
+                return 0f
+            } else if (topRadius + bottomRadius > height) {
+                // The sum of top and bottom corner radii should be at max the clipped height
+                val overShoot = topRadius + bottomRadius - height
+                return topRadius - (overShoot * topRoundness / (topRoundness + bottomRoundness))
+            }
+
+            return topRadius
+        }
+
+    internal val bottomCornerRadius: Float
+        get() {
+            val height = roundable.clipHeight
+            val topRadius = topRoundness * maxRadius
+            val bottomRadius = bottomRoundness * maxRadius
+
+            if (height == 0) {
+                return 0f
+            } else if (topRadius + bottomRadius > height) {
+                // The sum of top and bottom corner radii should be at max the clipped height
+                val overShoot = topRadius + bottomRadius - height
+                return bottomRadius -
+                    (overShoot * bottomRoundness / (topRoundness + bottomRoundness))
+            }
+
+            return bottomRadius
+        }
+
     /** Last requested top roundness associated by [SourceType] */
     internal val topRoundnessMap = mutableMapOf<SourceType, Float>()
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
index 16f1a45..1b43922 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
@@ -74,7 +74,7 @@
                     root.setTag(R.id.view_group_fade_helper_previous_value_tag, newAlpha)
                 }
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         endRunnable?.run()
                     }
                 })
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 d2c6d4b..e763797 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
@@ -61,6 +61,7 @@
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.util.Pair;
 
 import androidx.annotation.NonNull;
@@ -98,6 +99,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.RankingUpdatedEvent;
 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.NamedListenerSet;
 import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
@@ -160,7 +162,8 @@
     private final HashMap<String, FutureDismissal> mFutureDismissals = new HashMap<>();
 
     @Nullable private CollectionReadyForBuildListener mBuildListener;
-    private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
+    private final NamedListenerSet<NotifCollectionListener>
+            mNotifCollectionListeners = new NamedListenerSet<>();
     private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
     private final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>();
 
@@ -235,7 +238,7 @@
     /** @see NotifPipeline#addCollectionListener(NotifCollectionListener) */
     void addCollectionListener(NotifCollectionListener listener) {
         Assert.isMainThread();
-        mNotifCollectionListeners.add(listener);
+        mNotifCollectionListeners.addIfAbsent(listener);
     }
 
     /** @see NotifPipeline#removeCollectionListener(NotifCollectionListener) */
@@ -274,10 +277,7 @@
         Assert.isMainThread();
         checkForReentrantCall();
 
-        // TODO (b/206842750): This method is called from (silent) clear all and non-clear all
-        // contexts and should be checking the NO_CLEAR flag, rather than depending on NSSL
-        // to pass in a properly filtered list of notifications
-
+        final int entryCount = entriesToDismiss.size();
         final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>();
         for (int i = 0; i < entriesToDismiss.size(); i++) {
             NotificationEntry entry = entriesToDismiss.get(i).first;
@@ -286,28 +286,34 @@
             requireNonNull(stats);
             NotificationEntry storedEntry = mNotificationSet.get(entry.getKey());
             if (storedEntry == null) {
-                mLogger.logNonExistentNotifDismissed(entry);
+                mLogger.logDismissNonExistentNotif(entry, i, entryCount);
                 continue;
             }
             if (entry != storedEntry) {
                 throw mEulogizer.record(
                         new IllegalStateException("Invalid entry: "
                                 + "different stored and dismissed entries for " + logKey(entry)
+                                + " (" + i + "/" + entryCount + ")"
+                                + " dismissed=@" + Integer.toHexString(entry.hashCode())
                                 + " stored=@" + Integer.toHexString(storedEntry.hashCode())));
             }
 
             if (entry.getDismissState() == DISMISSED) {
+                mLogger.logDismissAlreadyDismissedNotif(entry, i, entryCount);
                 continue;
+            } else if (entry.getDismissState() == PARENT_DISMISSED) {
+                mLogger.logDismissAlreadyParentDismissedNotif(entry, i, entryCount);
             }
 
             updateDismissInterceptors(entry);
             if (isDismissIntercepted(entry)) {
-                mLogger.logNotifDismissedIntercepted(entry);
+                mLogger.logNotifDismissedIntercepted(entry, i, entryCount);
                 continue;
             }
 
             entriesToLocallyDismiss.add(entry);
             if (!entry.isCanceled()) {
+                int finalI = i;
                 // send message to system server if this notification hasn't already been cancelled
                 mBgExecutor.execute(() -> {
                     try {
@@ -320,7 +326,7 @@
                                 stats.notificationVisibility);
                     } catch (RemoteException e) {
                         // system process is dead if we're here.
-                        mLogger.logRemoteExceptionOnNotificationClear(entry, e);
+                        mLogger.logRemoteExceptionOnNotificationClear(entry, finalI, entryCount, e);
                     }
                 });
             }
@@ -357,14 +363,16 @@
         }
 
         final List<NotificationEntry> entries = new ArrayList<>(getAllNotifs());
+        final int initialEntryCount = entries.size();
         for (int i = entries.size() - 1; i >= 0; i--) {
             NotificationEntry entry = entries.get(i);
+
             if (!shouldDismissOnClearAll(entry, userId)) {
                 // system server won't be removing these notifications, but we still give dismiss
                 // interceptors the chance to filter the notification
                 updateDismissInterceptors(entry);
                 if (isDismissIntercepted(entry)) {
-                    mLogger.logNotifClearAllDismissalIntercepted(entry);
+                    mLogger.logNotifClearAllDismissalIntercepted(entry, i, initialEntryCount);
                 }
                 entries.remove(i);
             }
@@ -380,25 +388,46 @@
      */
     private void locallyDismissNotifications(List<NotificationEntry> entries) {
         final List<NotificationEntry> canceledEntries = new ArrayList<>();
-
+        final int entryCount = entries.size();
         for (int i = 0; i < entries.size(); i++) {
             NotificationEntry entry = entries.get(i);
 
+            final NotificationEntry storedEntry = mNotificationSet.get(entry.getKey());
+            if (storedEntry == null) {
+                mLogger.logLocallyDismissNonExistentNotif(entry, i, entryCount);
+            } else if (storedEntry != entry) {
+                mLogger.logLocallyDismissMismatchedEntry(entry, i, entryCount, storedEntry);
+            }
+
+            if (entry.getDismissState() == DISMISSED) {
+                mLogger.logLocallyDismissAlreadyDismissedNotif(entry, i, entryCount);
+            } else if (entry.getDismissState() == PARENT_DISMISSED) {
+                mLogger.logLocallyDismissAlreadyParentDismissedNotif(entry, i, entryCount);
+            }
+
             entry.setDismissState(DISMISSED);
-            mLogger.logNotifDismissed(entry);
+            mLogger.logLocallyDismissed(entry, i, entryCount);
 
             if (entry.isCanceled()) {
                 canceledEntries.add(entry);
-            } else {
-                // Mark any children as dismissed as system server will auto-dismiss them as well
-                if (entry.getSbn().getNotification().isGroupSummary()) {
-                    for (NotificationEntry otherEntry : mNotificationSet.values()) {
-                        if (shouldAutoDismissChildren(otherEntry, entry.getSbn().getGroupKey())) {
-                            otherEntry.setDismissState(PARENT_DISMISSED);
-                            mLogger.logChildDismissed(otherEntry);
-                            if (otherEntry.isCanceled()) {
-                                canceledEntries.add(otherEntry);
-                            }
+                continue;
+            }
+
+            // Mark any children as dismissed as system server will auto-dismiss them as well
+            if (entry.getSbn().getNotification().isGroupSummary()) {
+                for (NotificationEntry otherEntry : mNotificationSet.values()) {
+                    if (shouldAutoDismissChildren(otherEntry, entry.getSbn().getGroupKey())) {
+                        if (otherEntry.getDismissState() == DISMISSED) {
+                            mLogger.logLocallyDismissAlreadyDismissedChild(
+                                    otherEntry, entry, i, entryCount);
+                        } else if (otherEntry.getDismissState() == PARENT_DISMISSED) {
+                            mLogger.logLocallyDismissAlreadyParentDismissedChild(
+                                    otherEntry, entry, i, entryCount);
+                        }
+                        otherEntry.setDismissState(PARENT_DISMISSED);
+                        mLogger.logLocallyDismissedChild(otherEntry, entry, i, entryCount);
+                        if (otherEntry.isCanceled()) {
+                            canceledEntries.add(otherEntry);
                         }
                     }
                 }
@@ -408,7 +437,7 @@
         // Immediately remove any dismissed notifs that have already been canceled by system server
         // (probably due to being lifetime-extended up until this point).
         for (NotificationEntry canceledEntry : canceledEntries) {
-            mLogger.logDismissOnAlreadyCanceledEntry(canceledEntry);
+            mLogger.logLocallyDismissedAlreadyCanceledEntry(canceledEntry);
             tryRemoveNotification(canceledEntry);
         }
     }
@@ -517,10 +546,16 @@
      * @return True if the notification was removed, false otherwise.
      */
     private boolean tryRemoveNotification(NotificationEntry entry) {
-        if (mNotificationSet.get(entry.getKey()) != entry) {
+        final NotificationEntry storedEntry = mNotificationSet.get(entry.getKey());
+        if (storedEntry == null) {
+            Log.wtf(TAG, "TRY REMOVE non-existent notification " + logKey(entry));
+            return false;
+        } else if (storedEntry != entry) {
             throw mEulogizer.record(
-                    new IllegalStateException("No notification to remove with key "
-                            + logKey(entry)));
+                    new IllegalStateException("Mismatched stored and tryRemoved entries"
+                            + " for key " + logKey(entry) + ":"
+                            + " stored=@" + Integer.toHexString(storedEntry.hashCode())
+                            + " tryRemoved=@" + Integer.toHexString(entry.hashCode())));
         }
 
         if (!entry.isCanceled()) {
@@ -734,14 +769,16 @@
     }
 
     private void cancelLocalDismissal(NotificationEntry entry) {
-        if (entry.getDismissState() != NOT_DISMISSED) {
-            entry.setDismissState(NOT_DISMISSED);
-            if (entry.getSbn().getNotification().isGroupSummary()) {
-                for (NotificationEntry otherEntry : mNotificationSet.values()) {
-                    if (otherEntry.getSbn().getGroupKey().equals(entry.getSbn().getGroupKey())
-                            && otherEntry.getDismissState() == PARENT_DISMISSED) {
-                        otherEntry.setDismissState(NOT_DISMISSED);
-                    }
+        if (entry.getDismissState() == NOT_DISMISSED) {
+            mLogger.logCancelLocalDismissalNotDismissedNotif(entry);
+            return;
+        }
+        entry.setDismissState(NOT_DISMISSED);
+        if (entry.getSbn().getNotification().isGroupSummary()) {
+            for (NotificationEntry otherEntry : mNotificationSet.values()) {
+                if (otherEntry.getSbn().getGroupKey().equals(entry.getSbn().getGroupKey())
+                        && otherEntry.getDismissState() == PARENT_DISMISSED) {
+                    otherEntry.setDismissState(NOT_DISMISSED);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
index d95d593..5acc50a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.util.Assert
 import com.android.systemui.util.ListenerSet
-import com.android.systemui.util.isNotEmpty
 import com.android.systemui.util.traceSection
 import java.util.Collections.unmodifiableList
 import java.util.concurrent.Executor
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 7898736..affd2d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -791,28 +791,6 @@
         return !mSbn.isOngoing() || !isLocked;
     }
 
-    /**
-     * @return Can the underlying notification be individually dismissed?
-     * @see #canViewBeDismissed()
-     */
-    // TODO: This logic doesn't belong on NotificationEntry. It should be moved to a controller
-    // that can be added as a dependency to any class that needs to answer this question.
-    public boolean legacyIsDismissableRecursive() {
-        if  (mSbn.isOngoing()) {
-            return false;
-        }
-        List<NotificationEntry> children = getAttachedNotifChildren();
-        if (children != null && children.size() > 0) {
-            for (int i = 0; i < children.size(); i++) {
-                NotificationEntry child =  children.get(i);
-                if (child.getSbn().isOngoing()) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
     public boolean canViewBeDismissed() {
         if (row == null) return true;
         return row.canViewBeDismissed();
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 0205523..240ae0c 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
@@ -69,6 +69,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
 import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.NamedListenerSet;
 import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
@@ -121,14 +122,14 @@
     private final List<NotifSection> mNotifSections = new ArrayList<>();
     private NotifStabilityManager mNotifStabilityManager;
 
-    private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners =
-            new ArrayList<>();
-    private final List<OnBeforeSortListener> mOnBeforeSortListeners =
-            new ArrayList<>();
-    private final List<OnBeforeFinalizeFilterListener> mOnBeforeFinalizeFilterListeners =
-            new ArrayList<>();
-    private final List<OnBeforeRenderListListener> mOnBeforeRenderListListeners =
-            new ArrayList<>();
+    private final NamedListenerSet<OnBeforeTransformGroupsListener>
+            mOnBeforeTransformGroupsListeners = new NamedListenerSet<>();
+    private final NamedListenerSet<OnBeforeSortListener>
+            mOnBeforeSortListeners = new NamedListenerSet<>();
+    private final NamedListenerSet<OnBeforeFinalizeFilterListener>
+            mOnBeforeFinalizeFilterListeners = new NamedListenerSet<>();
+    private final NamedListenerSet<OnBeforeRenderListListener>
+            mOnBeforeRenderListListeners = new NamedListenerSet<>();
     @Nullable private OnRenderListListener mOnRenderListListener;
 
     private List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList);
@@ -184,28 +185,28 @@
         Assert.isMainThread();
 
         mPipelineState.requireState(STATE_IDLE);
-        mOnBeforeTransformGroupsListeners.add(listener);
+        mOnBeforeTransformGroupsListeners.addIfAbsent(listener);
     }
 
     void addOnBeforeSortListener(OnBeforeSortListener listener) {
         Assert.isMainThread();
 
         mPipelineState.requireState(STATE_IDLE);
-        mOnBeforeSortListeners.add(listener);
+        mOnBeforeSortListeners.addIfAbsent(listener);
     }
 
     void addOnBeforeFinalizeFilterListener(OnBeforeFinalizeFilterListener listener) {
         Assert.isMainThread();
 
         mPipelineState.requireState(STATE_IDLE);
-        mOnBeforeFinalizeFilterListeners.add(listener);
+        mOnBeforeFinalizeFilterListeners.addIfAbsent(listener);
     }
 
     void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) {
         Assert.isMainThread();
 
         mPipelineState.requireState(STATE_IDLE);
-        mOnBeforeRenderListListeners.add(listener);
+        mOnBeforeRenderListListeners.addIfAbsent(listener);
     }
 
     void addPreRenderInvalidator(Invalidator invalidator) {
@@ -496,7 +497,9 @@
                     mTempSectionMembers.add(entry);
                 }
             }
+            Trace.beginSection(section.getLabel());
             section.getSectioner().onEntriesUpdated(mTempSectionMembers);
+            Trace.endSection();
             mTempSectionMembers.clear();
         }
         Trace.endSection();
@@ -1430,33 +1433,33 @@
 
     private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) {
         Trace.beginSection("ShadeListBuilder.dispatchOnBeforeTransformGroups");
-        for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) {
-            mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries);
-        }
+        mOnBeforeTransformGroupsListeners.forEachTraced(listener -> {
+            listener.onBeforeTransformGroups(entries);
+        });
         Trace.endSection();
     }
 
     private void dispatchOnBeforeSort(List<ListEntry> entries) {
         Trace.beginSection("ShadeListBuilder.dispatchOnBeforeSort");
-        for (int i = 0; i < mOnBeforeSortListeners.size(); i++) {
-            mOnBeforeSortListeners.get(i).onBeforeSort(entries);
-        }
+        mOnBeforeSortListeners.forEachTraced(listener -> {
+            listener.onBeforeSort(entries);
+        });
         Trace.endSection();
     }
 
     private void dispatchOnBeforeFinalizeFilter(List<ListEntry> entries) {
         Trace.beginSection("ShadeListBuilder.dispatchOnBeforeFinalizeFilter");
-        for (int i = 0; i < mOnBeforeFinalizeFilterListeners.size(); i++) {
-            mOnBeforeFinalizeFilterListeners.get(i).onBeforeFinalizeFilter(entries);
-        }
+        mOnBeforeFinalizeFilterListeners.forEachTraced(listener -> {
+            listener.onBeforeFinalizeFilter(entries);
+        });
         Trace.endSection();
     }
 
     private void dispatchOnBeforeRenderList(List<ListEntry> entries) {
         Trace.beginSection("ShadeListBuilder.dispatchOnBeforeRenderList");
-        for (int i = 0; i < mOnBeforeRenderListListeners.size(); i++) {
-            mOnBeforeRenderListListeners.get(i).onBeforeRenderList(entries);
-        }
+        mOnBeforeRenderListListeners.forEachTraced(listener -> {
+            listener.onBeforeRenderList(entries);
+        });
         Trace.endSection();
     }
 
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 39d0833..0ab348d 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
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 class GroupCoalescerLogger @Inject constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
deleted file mode 100644
index f04b24e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.coordinator;
-
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import android.app.Notification;
-import android.service.notification.StatusBarNotification;
-
-import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
-import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import javax.inject.Inject;
-
-/**
- * Handles ForegroundService and AppOp interactions with notifications.
- *  Tags notifications with appOps
- *  Lifetime extends notifications associated with an ongoing ForegroundService.
- *  Filters out notifications that represent foreground services that are no longer running
- *  Puts foreground service notifications into the FGS section. See {@link NotifCoordinators} for
- *      section ordering priority.
- *
- * Previously this logic lived in
- *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceController
- *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener
- *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender
- */
-@CoordinatorScope
-public class AppOpsCoordinator implements Coordinator {
-    private static final String TAG = "AppOpsCoordinator";
-
-    private final ForegroundServiceController mForegroundServiceController;
-    private final AppOpsController mAppOpsController;
-    private final DelayableExecutor mMainExecutor;
-
-    private NotifPipeline mNotifPipeline;
-
-    @Inject
-    public AppOpsCoordinator(
-            ForegroundServiceController foregroundServiceController,
-            AppOpsController appOpsController,
-            @Main DelayableExecutor mainExecutor) {
-        mForegroundServiceController = foregroundServiceController;
-        mAppOpsController = appOpsController;
-        mMainExecutor = mainExecutor;
-    }
-
-    @Override
-    public void attach(NotifPipeline pipeline) {
-        mNotifPipeline = pipeline;
-
-        // filter out foreground service notifications that aren't necessary anymore
-        mNotifPipeline.addPreGroupFilter(mNotifFilter);
-
-    }
-
-    public NotifSectioner getSectioner() {
-        return mNotifSectioner;
-    }
-
-    /**
-     * Filters out notifications that represent foreground services that are no longer running or
-     * that already have an app notification with the appOps tagged to
-     */
-    private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
-        @Override
-        public boolean shouldFilterOut(NotificationEntry entry, long now) {
-            StatusBarNotification sbn = entry.getSbn();
-
-            // Filters out system-posted disclosure notifications when unneeded
-            if (mForegroundServiceController.isDisclosureNotification(sbn)
-                    && !mForegroundServiceController.isDisclosureNeededForUser(
-                            sbn.getUser().getIdentifier())) {
-                return true;
-            }
-            return false;
-        }
-    };
-
-    /**
-     * Puts colorized foreground service and call notifications into its own section.
-     */
-    private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService",
-            NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
-        @Override
-        public boolean isInSection(ListEntry entry) {
-            NotificationEntry notificationEntry = entry.getRepresentativeEntry();
-            if (notificationEntry != null) {
-                return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry);
-            }
-            return false;
-        }
-
-        private boolean isColorizedForegroundService(NotificationEntry entry) {
-            Notification notification = entry.getSbn().getNotification();
-            return notification.isForegroundService()
-                    && notification.isColorized()
-                    && entry.getImportance() > IMPORTANCE_MIN;
-        }
-
-        private boolean isCall(NotificationEntry entry) {
-            Notification notification = entry.getSbn().getNotification();
-            return entry.getImportance() > IMPORTANCE_MIN
-                    && notification.isStyle(Notification.CallStyle.class);
-        }
-    };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
new file mode 100644
index 0000000..63997f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import android.app.Notification;
+
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
+import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
+
+import javax.inject.Inject;
+
+/**
+ * Handles sectioning for foreground service notifications.
+ *  Puts non-min colorized foreground service notifications into the FGS section. See
+ *  {@link NotifCoordinators} for section ordering priority.
+ */
+@CoordinatorScope
+public class ColorizedFgsCoordinator implements Coordinator {
+    private static final String TAG = "ColorizedCoordinator";
+
+    @Inject
+    public ColorizedFgsCoordinator() {
+    }
+
+    @Override
+    public void attach(NotifPipeline pipeline) {
+    }
+
+    public NotifSectioner getSectioner() {
+        return mNotifSectioner;
+    }
+
+
+    /**
+     * Puts colorized foreground service and call notifications into its own section.
+     */
+    private final NotifSectioner mNotifSectioner = new NotifSectioner("ColorizedSectioner",
+            NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
+        @Override
+        public boolean isInSection(ListEntry entry) {
+            NotificationEntry notificationEntry = entry.getRepresentativeEntry();
+            if (notificationEntry != null) {
+                return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry);
+            }
+            return false;
+        }
+
+        private boolean isColorizedForegroundService(NotificationEntry entry) {
+            Notification notification = entry.getSbn().getNotification();
+            return notification.isForegroundService()
+                    && notification.isColorized()
+                    && entry.getImportance() > IMPORTANCE_MIN;
+        }
+
+        private boolean isCall(NotificationEntry entry) {
+            Notification notification = entry.getSbn().getNotification();
+            return entry.getImportance() > IMPORTANCE_MIN
+                    && notification.isStyle(Notification.CallStyle.class);
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt
new file mode 100644
index 0000000..d268e35
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Filter out notifications on the lockscreen if the lockscreen hosted dream is active. If the user
+ * stops dreaming, pulls the shade down or unlocks the device, then the notifications are unhidden.
+ */
+@CoordinatorScope
+class DreamCoordinator
+@Inject
+constructor(
+    private val statusBarStateController: SysuiStatusBarStateController,
+    @Application private val scope: CoroutineScope,
+    private val keyguardRepository: KeyguardRepository,
+) : Coordinator {
+    private var isOnKeyguard = false
+    private var isLockscreenHostedDream = false
+
+    override fun attach(pipeline: NotifPipeline) {
+        pipeline.addPreGroupFilter(filter)
+        statusBarStateController.addCallback(statusBarStateListener)
+        scope.launch { attachFilterOnDreamingStateChange() }
+        recordStatusBarState(statusBarStateController.state)
+    }
+
+    private val filter =
+        object : NotifFilter("LockscreenHostedDreamFilter") {
+            var isFiltering = false
+            override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean {
+                return isFiltering
+            }
+            inline fun update(msg: () -> String) {
+                val wasFiltering = isFiltering
+                isFiltering = isLockscreenHostedDream && isOnKeyguard
+                if (wasFiltering != isFiltering) {
+                    invalidateList(msg())
+                }
+            }
+        }
+
+    private val statusBarStateListener =
+        object : StatusBarStateController.StateListener {
+            override fun onStateChanged(newState: Int) {
+                recordStatusBarState(newState)
+            }
+        }
+
+    private suspend fun attachFilterOnDreamingStateChange() {
+        keyguardRepository.isActiveDreamLockscreenHosted.collect { isDreaming ->
+            recordDreamingState(isDreaming)
+        }
+    }
+
+    private fun recordStatusBarState(newState: Int) {
+        isOnKeyguard = newState == StatusBarState.KEYGUARD
+        filter.update { "recordStatusBarState: " + StatusBarState.toString(newState) }
+    }
+
+    private fun recordDreamingState(isDreaming: Boolean) {
+        isLockscreenHostedDream = isDreaming
+        filter.update { "recordLockscreenHostedDreamState: $isDreaming" }
+    }
+}
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 79c63e6..bd1141e 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
@@ -2,7 +2,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.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/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index e17ce5c..496fb83 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
@@ -4,7 +4,7 @@
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "HeadsUpCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 2fa070c..07eb8a00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -28,12 +28,9 @@
 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.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.expansionChanges
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
@@ -50,30 +47,29 @@
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import java.io.PrintWriter
 import javax.inject.Inject
-import kotlin.time.Duration
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.emitAll
-import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.transformLatest
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.yield
 
 /**
  * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
- * headers on the lockscreen.
+ * headers on the lockscreen. If enabled, it will also track and hide seen notifications on the
+ * lockscreen.
  */
 @CoordinatorScope
 class KeyguardCoordinator
@@ -86,7 +82,6 @@
     private val keyguardRepository: KeyguardRepository,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val logger: KeyguardCoordinatorLogger,
-    private val notifPipelineFlags: NotifPipelineFlags,
     @Application private val scope: CoroutineScope,
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
     private val secureSettings: SecureSettings,
@@ -95,6 +90,8 @@
 ) : Coordinator, Dumpable {
 
     private val unseenNotifications = mutableSetOf<NotificationEntry>()
+    private val unseenEntryAdded = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
+    private val unseenEntryRemoved = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
     private var unseenFilterEnabled = false
 
     override fun attach(pipeline: NotifPipeline) {
@@ -109,79 +106,130 @@
     private fun attachUnseenFilter(pipeline: NotifPipeline) {
         pipeline.addFinalizeFilter(unseenNotifFilter)
         pipeline.addCollectionListener(collectionListener)
-        scope.launch { trackUnseenNotificationsWhileUnlocked() }
-        scope.launch { invalidateWhenUnseenSettingChanges() }
+        scope.launch { trackUnseenFilterSettingChanges() }
         dumpManager.registerDumpable(this)
     }
 
-    private suspend fun trackUnseenNotificationsWhileUnlocked() {
-        // Whether or not we're actively tracking unseen notifications to mark them as seen when
-        // appropriate.
-        val isTrackingUnseen: Flow<Boolean> =
-            keyguardRepository.isKeyguardShowing
-                // transformLatest so that we can cancel listening to keyguard transitions once
-                // isKeyguardShowing changes (after a successful transition to the keyguard).
-                .transformLatest { isShowing ->
-                    if (isShowing) {
-                        // If the keyguard is showing, we're not tracking unseen.
-                        emit(false)
-                    } else {
-                        // If the keyguard stops showing, then start tracking unseen notifications.
-                        emit(true)
-                        // If the screen is turning off, stop tracking, but if that transition is
-                        // cancelled, then start again.
-                        emitAll(
-                            keyguardTransitionRepository.transitions.map { step ->
-                                !step.isScreenTurningOff
-                            }
-                        )
-                    }
-                }
-                // Prevent double emit of `false` caused by transition to AOD, followed by keyguard
-                // showing
+    private suspend fun trackSeenNotifications() {
+        // Whether or not keyguard is visible (or occluded).
+        val isKeyguardPresent: Flow<Boolean> =
+            keyguardTransitionRepository.transitions
+                .map { step -> step.to != KeyguardState.GONE }
                 .distinctUntilChanged()
                 .onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }
 
-        // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
-        // showing again
-        var clearUnseenOnBeginTracking = false
-        isTrackingUnseen.collectLatest { trackingUnseen ->
-            if (!trackingUnseen) {
-                // Wait for the user to spend enough time on the lock screen before clearing unseen
-                // set when unlocked
-                awaitTimeSpentNotDozing(SEEN_TIMEOUT)
-                clearUnseenOnBeginTracking = true
-                logger.logSeenOnLockscreen()
+        // Separately track seen notifications while the device is locked, applying once the device
+        // is unlocked.
+        val notificationsSeenWhileLocked = mutableSetOf<NotificationEntry>()
+
+        // Use [collectLatest] to cancel any running jobs when [trackingUnseen] changes.
+        isKeyguardPresent.collectLatest { isKeyguardPresent: Boolean ->
+            if (isKeyguardPresent) {
+                // Keyguard is not gone, notifications need to be visible for a certain threshold
+                // before being marked as seen
+                trackSeenNotificationsWhileLocked(notificationsSeenWhileLocked)
             } else {
-                if (clearUnseenOnBeginTracking) {
-                    clearUnseenOnBeginTracking = false
-                    logger.logAllMarkedSeenOnUnlock()
-                    unseenNotifications.clear()
+                // Mark all seen-while-locked notifications as seen for real.
+                if (notificationsSeenWhileLocked.isNotEmpty()) {
+                    unseenNotifications.removeAll(notificationsSeenWhileLocked)
+                    logger.logAllMarkedSeenOnUnlock(
+                        seenCount = notificationsSeenWhileLocked.size,
+                        remainingUnseenCount = unseenNotifications.size
+                    )
+                    notificationsSeenWhileLocked.clear()
                 }
                 unseenNotifFilter.invalidateList("keyguard no longer showing")
-                trackUnseenNotifications()
+                // Keyguard is gone, notifications can be immediately marked as seen when they
+                // become visible.
+                trackSeenNotificationsWhileUnlocked()
             }
         }
     }
 
-    private suspend fun awaitTimeSpentNotDozing(duration: Duration) {
-        keyguardRepository.isDozing
-            // Use transformLatest so that the timeout delay is cancelled if the device enters doze,
-            // and is restarted when doze ends.
-            .transformLatest { isDozing ->
-                if (!isDozing) {
-                    delay(duration)
-                    // Signal timeout has completed
-                    emit(Unit)
+    /**
+     * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
+     * been "seen" while the device is on the keyguard.
+     */
+    private suspend fun trackSeenNotificationsWhileLocked(
+        notificationsSeenWhileLocked: MutableSet<NotificationEntry>,
+    ) = coroutineScope {
+        // Remove removed notifications from the set
+        launch {
+            unseenEntryRemoved.collect { entry ->
+                if (notificationsSeenWhileLocked.remove(entry)) {
+                    logger.logRemoveSeenOnLockscreen(entry)
                 }
             }
-            // Suspend until the first emission
-            .first()
+        }
+        // Use collectLatest so that the timeout delay is cancelled if the device enters doze, and
+        // is restarted when doze ends.
+        keyguardRepository.isDozing.collectLatest { isDozing ->
+            if (!isDozing) {
+                trackSeenNotificationsWhileLockedAndNotDozing(notificationsSeenWhileLocked)
+            }
+        }
     }
 
-    // Track "unseen" notifications, marking them as seen when either shade is expanded or the
+    /**
+     * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
+     * been "seen" while the device is on the keyguard and not dozing. Any new and existing unseen
+     * notifications are not marked as seen until they are visible for the [SEEN_TIMEOUT] duration.
+     */
+    private suspend fun trackSeenNotificationsWhileLockedAndNotDozing(
+        notificationsSeenWhileLocked: MutableSet<NotificationEntry>
+    ) = coroutineScope {
+        // All child tracking jobs will be cancelled automatically when this is cancelled.
+        val trackingJobsByEntry = mutableMapOf<NotificationEntry, Job>()
+
+        /**
+         * Wait for the user to spend enough time on the lock screen before removing notification
+         * from unseen set upon unlock.
+         */
+        suspend fun trackSeenDurationThreshold(entry: NotificationEntry) {
+            if (notificationsSeenWhileLocked.remove(entry)) {
+                logger.logResetSeenOnLockscreen(entry)
+            }
+            delay(SEEN_TIMEOUT)
+            notificationsSeenWhileLocked.add(entry)
+            trackingJobsByEntry.remove(entry)
+            logger.logSeenOnLockscreen(entry)
+        }
+
+        /** Stop any unseen tracking when a notification is removed. */
+        suspend fun stopTrackingRemovedNotifs(): Nothing =
+            unseenEntryRemoved.collect { entry ->
+                trackingJobsByEntry.remove(entry)?.let {
+                    it.cancel()
+                    logger.logStopTrackingLockscreenSeenDuration(entry)
+                }
+            }
+
+        /** Start tracking new notifications when they are posted. */
+        suspend fun trackNewUnseenNotifs(): Nothing = coroutineScope {
+            unseenEntryAdded.collect { entry ->
+                logger.logTrackingLockscreenSeenDuration(entry)
+                // If this is an update, reset the tracking.
+                trackingJobsByEntry[entry]?.let {
+                    it.cancel()
+                    logger.logResetSeenOnLockscreen(entry)
+                }
+                trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
+            }
+        }
+
+        // Start tracking for all notifications that are currently unseen.
+        logger.logTrackingLockscreenSeenDuration(unseenNotifications)
+        unseenNotifications.forEach { entry ->
+            trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
+        }
+
+        launch { trackNewUnseenNotifs() }
+        launch { stopTrackingRemovedNotifs() }
+    }
+
+    // Track "seen" notifications, marking them as such when either shade is expanded or the
     // notification becomes heads up.
-    private suspend fun trackUnseenNotifications() {
+    private suspend fun trackSeenNotificationsWhileUnlocked() {
         coroutineScope {
             launch { clearUnseenNotificationsWhenShadeIsExpanded() }
             launch { markHeadsUpNotificationsAsSeen() }
@@ -212,7 +260,7 @@
         }
     }
 
-    private suspend fun invalidateWhenUnseenSettingChanges() {
+    private suspend fun trackUnseenFilterSettingChanges() {
         secureSettings
             // emit whenever the setting has changed
             .observerFlow(
@@ -228,17 +276,23 @@
                     UserHandle.USER_CURRENT,
                 ) == 1
             }
+            // don't emit anything if nothing has changed
+            .distinctUntilChanged()
             // perform lookups on the bg thread pool
             .flowOn(bgDispatcher)
             // only track the most recent emission, if events are happening faster than they can be
             // consumed
             .conflate()
-            // update local field and invalidate if necessary
-            .collect { setting ->
+            .collectLatest { setting ->
+                // update local field and invalidate if necessary
                 if (setting != unseenFilterEnabled) {
                     unseenFilterEnabled = setting
                     unseenNotifFilter.invalidateList("unseen setting changed")
                 }
+                // if the setting is enabled, then start tracking and filtering unseen notifications
+                if (setting) {
+                    trackSeenNotifications()
+                }
             }
     }
 
@@ -250,6 +304,7 @@
                 ) {
                     logger.logUnseenAdded(entry.key)
                     unseenNotifications.add(entry)
+                    unseenEntryAdded.tryEmit(entry)
                 }
             }
 
@@ -259,12 +314,14 @@
                 ) {
                     logger.logUnseenUpdated(entry.key)
                     unseenNotifications.add(entry)
+                    unseenEntryAdded.tryEmit(entry)
                 }
             }
 
             override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
                 if (unseenNotifications.remove(entry)) {
                     logger.logUnseenRemoved(entry.key)
+                    unseenEntryRemoved.tryEmit(entry)
                 }
             }
         }
@@ -347,6 +404,3 @@
         private val SEEN_TIMEOUT = 5.seconds
     }
 }
-
-private val TransitionStep.isScreenTurningOff: Boolean
-    get() = transitionState == TransitionState.STARTED && to != KeyguardState.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
index 1f8ec34..788659e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
@@ -17,8 +17,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.core.LogLevel
 import com.android.systemui.log.dagger.UnseenNotificationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import javax.inject.Inject
 
 private const val TAG = "KeyguardCoordinator"
@@ -28,11 +29,14 @@
 constructor(
     @UnseenNotificationLog private val buffer: LogBuffer,
 ) {
-    fun logSeenOnLockscreen() =
+    fun logSeenOnLockscreen(entry: NotificationEntry) =
         buffer.log(
             TAG,
             LogLevel.DEBUG,
-            "Notifications on lockscreen will be marked as seen when unlocked."
+            messageInitializer = { str1 = entry.key },
+            messagePrinter = {
+                "Notification [$str1] on lockscreen will be marked as seen when unlocked."
+            },
         )
 
     fun logTrackingUnseen(trackingUnseen: Boolean) =
@@ -43,11 +47,21 @@
             messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." },
         )
 
-    fun logAllMarkedSeenOnUnlock() =
+    fun logAllMarkedSeenOnUnlock(
+        seenCount: Int,
+        remainingUnseenCount: Int,
+    ) =
         buffer.log(
             TAG,
             LogLevel.DEBUG,
-            "Notifications have been marked as seen now that device is unlocked."
+            messageInitializer = {
+                int1 = seenCount
+                int2 = remainingUnseenCount
+            },
+            messagePrinter = {
+                "$int1 Notifications have been marked as seen now that device is unlocked. " +
+                    "$int2 notifications remain unseen."
+            },
         )
 
     fun logShadeExpanded() =
@@ -96,4 +110,60 @@
             messageInitializer = { str1 = key },
             messagePrinter = { "Unseen notif has become heads up: $str1" },
         )
+
+    fun logTrackingLockscreenSeenDuration(unseenNotifications: Set<NotificationEntry>) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = {
+                str1 = unseenNotifications.joinToString { it.key }
+                int1 = unseenNotifications.size
+            },
+            messagePrinter = {
+                "Tracking $int1 unseen notifications for lockscreen seen duration threshold: $str1"
+            },
+        )
+    }
+
+    fun logTrackingLockscreenSeenDuration(entry: NotificationEntry) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = entry.key },
+            messagePrinter = {
+                "Tracking new notification for lockscreen seen duration threshold: $str1"
+            },
+        )
+    }
+
+    fun logStopTrackingLockscreenSeenDuration(entry: NotificationEntry) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = entry.key },
+            messagePrinter = {
+                "Stop tracking removed notification for lockscreen seen duration threshold: $str1"
+            },
+        )
+    }
+
+    fun logResetSeenOnLockscreen(entry: NotificationEntry) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = entry.key },
+            messagePrinter = {
+                "Reset tracking updated notification for lockscreen seen duration threshold: $str1"
+            },
+        )
+    }
+
+    fun logRemoveSeenOnLockscreen(entry: NotificationEntry) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            messageInitializer = { str1 = entry.key },
+            messagePrinter = { "Notification marked as seen on lockscreen removed: $str1" },
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index e5953cf..226a957 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.statusbar.notification.collection.coordinator
 
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.PipelineDumpable
 import com.android.systemui.statusbar.notification.collection.PipelineDumper
@@ -32,12 +34,13 @@
 @CoordinatorScope
 class NotifCoordinatorsImpl @Inject constructor(
         sectionStyleProvider: SectionStyleProvider,
+        featureFlags: FeatureFlags,
         dataStoreCoordinator: DataStoreCoordinator,
         hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
         hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
         keyguardCoordinator: KeyguardCoordinator,
         rankingCoordinator: RankingCoordinator,
-        appOpsCoordinator: AppOpsCoordinator,
+        colorizedFgsCoordinator: ColorizedFgsCoordinator,
         deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
         bubbleCoordinator: BubbleCoordinator,
         headsUpCoordinator: HeadsUpCoordinator,
@@ -57,6 +60,7 @@
         visualStabilityCoordinator: VisualStabilityCoordinator,
         sensitiveContentCoordinator: SensitiveContentCoordinator,
         dismissibilityCoordinator: DismissibilityCoordinator,
+        dreamCoordinator: DreamCoordinator,
 ) : NotifCoordinators {
 
     private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
@@ -75,7 +79,7 @@
         mCoordinators.add(hideNotifsForOtherUsersCoordinator)
         mCoordinators.add(keyguardCoordinator)
         mCoordinators.add(rankingCoordinator)
-        mCoordinators.add(appOpsCoordinator)
+        mCoordinators.add(colorizedFgsCoordinator)
         mCoordinators.add(deviceProvisionedCoordinator)
         mCoordinators.add(bubbleCoordinator)
         mCoordinators.add(debugModeCoordinator)
@@ -96,9 +100,13 @@
         mCoordinators.add(remoteInputCoordinator)
         mCoordinators.add(dismissibilityCoordinator)
 
+        if (featureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+            mCoordinators.add(dreamCoordinator)
+        }
+
         // Manually add Ordered Sections
         mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
-        mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
+        mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
         mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting
         mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
         mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
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 6500ff7..73decfc 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
@@ -23,6 +23,7 @@
 
 import android.annotation.IntDef;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -342,11 +343,13 @@
     private void inflateEntry(NotificationEntry entry,
             NotifUiAdjustment newAdjustment,
             String reason) {
+        Trace.beginSection("PrepCoord.inflateEntry");
         abortInflation(entry, reason);
         mInflationAdjustments.put(entry, newAdjustment);
         mInflatingNotifs.add(entry);
         NotifInflater.Params params = getInflaterParams(newAdjustment, reason);
         mNotifInflater.inflateViews(entry, params, this::onInflationFinished);
+        Trace.endSection();
     }
 
     private void rebind(NotificationEntry entry,
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 6271d38..bf65043 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
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.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 1f4861a..0d9681f 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
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "ShadeEventCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 5c2f9a8..62a0d13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -39,10 +39,7 @@
 
     override fun attach(pipeline: NotifPipeline) {
         pipeline.addOnAfterRenderListListener(::onAfterRenderList)
-        // TODO(b/282865576): This has an issue where it makes changes to some groups without
-        // notifying listeners. To be fixed in QPR, but for now let's comment it out to avoid the
-        // group expansion bug.
-        // groupExpansionManagerImpl.attach(pipeline)
+        groupExpansionManagerImpl.attach(pipeline)
     }
 
     fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index bdb206b..9ecf50e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.notification.collection.inflation;
 
-import static com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
@@ -71,7 +70,6 @@
 
     private NotificationPresenter mPresenter;
     private NotificationListContainer mListContainer;
-    private BindRowCallback mBindRowCallback;
     private NotificationClicker mNotificationClicker;
     private FeatureFlags mFeatureFlags;
 
@@ -103,11 +101,9 @@
      * Sets up late-bound dependencies for this component.
      */
     public void setUpWithPresenter(NotificationPresenter presenter,
-            NotificationListContainer listContainer,
-            BindRowCallback bindRowCallback) {
+            NotificationListContainer listContainer) {
         mPresenter = presenter;
         mListContainer = listContainer;
-        mBindRowCallback = bindRowCallback;
 
         mIconManager.attach();
     }
@@ -143,7 +139,6 @@
                                         .expandableNotificationRow(row)
                                         .notificationEntry(entry)
                                         .onExpandClickListener(mPresenter)
-                                        .listContainer(mListContainer)
                                         .build();
                         ExpandableNotificationRowController rowController =
                                 component.getExpandableNotificationRowController();
@@ -179,9 +174,7 @@
         mNotificationRemoteInputManager.bindRow(row);
         entry.setRow(row);
         mNotifBindPipeline.manageRow(entry, row);
-        mBindRowCallback.onBindRow(row);
-        row.setInlineReplyAnimationFlagEnabled(
-                mFeatureFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION));
+        mPresenter.onBindRow(row);
     }
 
     /**
@@ -235,12 +228,4 @@
             }
         });
     }
-
-    /** Callback for when a row is bound to an entry. */
-    public interface BindRowCallback {
-        /**
-         * Called when a new row is created and bound to a notification.
-         */
-        void onBindRow(ExpandableNotificationRow row);
-    }
 }
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 f13ff68..a8409d0c 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
@@ -18,9 +18,9 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 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.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.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 20de785..014ac54 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
@@ -22,11 +22,11 @@
 import android.service.notification.StatusBarNotification
 import com.android.systemui.log.dagger.NotificationLog
 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.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.WARNING
+import com.android.systemui.log.core.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
@@ -108,27 +108,39 @@
         })
     }
 
-    fun logNotifDismissed(entry: NotificationEntry) {
+    fun logLocallyDismissed(entry: NotificationEntry, index: Int, count: Int) {
         buffer.log(TAG, INFO, {
             str1 = entry.logKey
+            int1 = index
+            int2 = count
         }, {
-            "DISMISSED $str1"
+            "LOCALLY DISMISSED $str1 ($int1/$int2)"
         })
     }
 
-    fun logNonExistentNotifDismissed(entry: NotificationEntry) {
+    fun logDismissNonExistentNotif(entry: NotificationEntry, index: Int, count: Int) {
         buffer.log(TAG, INFO, {
             str1 = entry.logKey
+            int1 = index
+            int2 = count
         }, {
-            "DISMISSED Non Existent $str1"
+            "DISMISS Non Existent $str1 ($int1/$int2)"
         })
     }
 
-    fun logChildDismissed(entry: NotificationEntry) {
+    fun logLocallyDismissedChild(
+            child: NotificationEntry,
+            parent: NotificationEntry,
+            parentIndex: Int,
+            parentCount: Int
+    ) {
         buffer.log(TAG, DEBUG, {
-            str1 = entry.logKey
+            str1 = child.logKey
+            str2 = parent.logKey
+            int1 = parentIndex
+            int2 = parentCount
         }, {
-            "CHILD DISMISSED (inferred): $str1"
+            "LOCALLY DISMISSED CHILD (inferred): $str1 of parent $str2 ($int1/$int2)"
         })
     }
 
@@ -140,27 +152,31 @@
         })
     }
 
-    fun logDismissOnAlreadyCanceledEntry(entry: NotificationEntry) {
+    fun logLocallyDismissedAlreadyCanceledEntry(entry: NotificationEntry) {
         buffer.log(TAG, DEBUG, {
             str1 = entry.logKey
         }, {
-            "Dismiss on $str1, which was already canceled. Trying to remove..."
+            "LOCALLY DISMISSED Already Canceled $str1. Trying to remove."
         })
     }
 
-    fun logNotifDismissedIntercepted(entry: NotificationEntry) {
+    fun logNotifDismissedIntercepted(entry: NotificationEntry, index: Int, count: Int) {
         buffer.log(TAG, INFO, {
             str1 = entry.logKey
+            int1 = index
+            int2 = count
         }, {
-            "DISMISS INTERCEPTED $str1"
+            "DISMISS INTERCEPTED $str1 ($int1/$int2)"
         })
     }
 
-    fun logNotifClearAllDismissalIntercepted(entry: NotificationEntry) {
+    fun logNotifClearAllDismissalIntercepted(entry: NotificationEntry, index: Int, count: Int) {
         buffer.log(TAG, INFO, {
             str1 = entry.logKey
+            int1 = index
+            int2 = count
         }, {
-            "CLEAR ALL DISMISSAL INTERCEPTED $str1"
+            "CLEAR ALL DISMISSAL INTERCEPTED $str1 ($int1/$int2)"
         })
     }
 
@@ -251,12 +267,19 @@
         })
     }
 
-    fun logRemoteExceptionOnNotificationClear(entry: NotificationEntry, e: RemoteException) {
+    fun logRemoteExceptionOnNotificationClear(
+            entry: NotificationEntry,
+            index: Int,
+            count: Int,
+            e: RemoteException
+    ) {
         buffer.log(TAG, WTF, {
             str1 = entry.logKey
+            int1 = index
+            int2 = count
             str2 = e.toString()
         }, {
-            "RemoteException while attempting to clear $str1:\n$str2"
+            "RemoteException while attempting to clear $str1 ($int1/$int2):\n$str2"
         })
     }
 
@@ -387,6 +410,126 @@
             "Mismatch: current $str2 is $str3 for: $str1"
         })
     }
+
+    fun logDismissAlreadyDismissedNotif(entry: NotificationEntry, index: Int, count: Int) {
+        buffer.log(TAG, DEBUG, {
+            str1 = entry.logKey
+            int1 = index
+            int2 = count
+        }, {
+            "DISMISS Already Dismissed $str1 ($int1/$int2)"
+        })
+    }
+
+    fun logDismissAlreadyParentDismissedNotif(
+            childEntry: NotificationEntry,
+            childIndex: Int,
+            childCount: Int
+    ) {
+        buffer.log(TAG, DEBUG, {
+            str1 = childEntry.logKey
+            int1 = childIndex
+            int2 = childCount
+            str2 = childEntry.parent?.summary?.logKey ?: "(null)"
+        }, {
+            "DISMISS Already Parent-Dismissed $str1 ($int1/$int2) with summary $str2"
+        })
+    }
+
+    fun logLocallyDismissNonExistentNotif(entry: NotificationEntry, index: Int, count: Int) {
+        buffer.log(TAG, INFO, {
+            str1 = entry.logKey
+            int1 = index
+            int2 = count
+        }, {
+            "LOCALLY DISMISS Non Existent $str1 ($int1/$int2)"
+        })
+    }
+
+    fun logLocallyDismissMismatchedEntry(
+            entry: NotificationEntry,
+            index: Int,
+            count: Int,
+            storedEntry: NotificationEntry
+    ) {
+        buffer.log(TAG, INFO, {
+            str1 = entry.logKey
+            int1 = index
+            int2 = count
+            str2 = Integer.toHexString(entry.hashCode())
+            str3 = Integer.toHexString(storedEntry.hashCode())
+        }, {
+            "LOCALLY DISMISS Mismatch $str1 ($int1/$int2): dismissing @$str2 but stored @$str3"
+        })
+    }
+
+    fun logLocallyDismissAlreadyDismissedNotif(
+            entry: NotificationEntry,
+            index: Int,
+            count: Int
+    ) {
+        buffer.log(TAG, INFO, {
+            str1 = entry.logKey
+            int1 = index
+            int2 = count
+        }, {
+            "LOCALLY DISMISS Already Dismissed $str1 ($int1/$int2)"
+        })
+    }
+
+    fun logLocallyDismissAlreadyParentDismissedNotif(
+            entry: NotificationEntry,
+            index: Int,
+            count: Int
+    ) {
+        buffer.log(TAG, INFO, {
+            str1 = entry.logKey
+            int1 = index
+            int2 = count
+        }, {
+            "LOCALLY DISMISS Already Dismissed $str1 ($int1/$int2)"
+        })
+    }
+
+    fun logLocallyDismissAlreadyDismissedChild(
+            childEntry: NotificationEntry,
+            parentEntry: NotificationEntry,
+            parentIndex: Int,
+            parentCount: Int
+    ) {
+        buffer.log(TAG, INFO, {
+            str1 = childEntry.logKey
+            str2 = parentEntry.logKey
+            int1 = parentIndex
+            int2 = parentCount
+        }, {
+            "LOCALLY DISMISS Already Dismissed Child $str1 of parent $str2 ($int1/$int2)"
+        })
+    }
+
+    fun logLocallyDismissAlreadyParentDismissedChild(
+            childEntry: NotificationEntry,
+            parentEntry: NotificationEntry,
+            parentIndex: Int,
+            parentCount: Int
+    ) {
+        buffer.log(TAG, INFO, {
+            str1 = childEntry.logKey
+            str2 = parentEntry.logKey
+            int1 = parentIndex
+            int2 = parentCount
+        }, {
+            "LOCALLY DISMISS Already Parent-Dismissed Child $str1 of parent $str2 ($int1/$int2)"
+        })
+    }
+
+    fun logCancelLocalDismissalNotDismissedNotif(entry: NotificationEntry) {
+        buffer.log(TAG, INFO, {
+            str1 = entry.logKey
+        }, {
+            "CANCEL LOCAL DISMISS Not Dismissed $str1"
+        })
+    }
 }
 
 private const val TAG = "NotifCollection"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
index e20f0e5..e06e2d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
@@ -22,6 +22,8 @@
 import android.service.notification.StatusBarNotification
 import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.util.NamedListenerSet
+import com.android.systemui.util.traceSection
 
 /**
  * Set of classes that represent the various events that [NotifCollection] can dispatch to
@@ -30,10 +32,10 @@
  * These events build up in a queue and are periodically emitted in chunks by the collection.
  */
 
-sealed class NotifEvent {
-    fun dispatchTo(listeners: List<NotifCollectionListener>) {
-        for (i in listeners.indices) {
-            dispatchToListener(listeners[i])
+sealed class NotifEvent(private val traceName: String) {
+    fun dispatchTo(listeners: NamedListenerSet<NotifCollectionListener>) {
+        traceSection(traceName) {
+            listeners.forEachTraced(::dispatchToListener)
         }
     }
 
@@ -43,7 +45,7 @@
 data class BindEntryEvent(
     val entry: NotificationEntry,
     val sbn: StatusBarNotification
-) : NotifEvent() {
+) : NotifEvent("onEntryBind") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onEntryBind(entry, sbn)
     }
@@ -51,7 +53,7 @@
 
 data class InitEntryEvent(
     val entry: NotificationEntry
-) : NotifEvent() {
+) : NotifEvent("onEntryInit") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onEntryInit(entry)
     }
@@ -59,7 +61,7 @@
 
 data class EntryAddedEvent(
     val entry: NotificationEntry
-) : NotifEvent() {
+) : NotifEvent("onEntryAdded") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onEntryAdded(entry)
     }
@@ -68,7 +70,7 @@
 data class EntryUpdatedEvent(
     val entry: NotificationEntry,
     val fromSystem: Boolean
-) : NotifEvent() {
+) : NotifEvent(if (fromSystem) "onEntryUpdated" else "onEntryUpdated fromSystem=true") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onEntryUpdated(entry, fromSystem)
     }
@@ -77,7 +79,7 @@
 data class EntryRemovedEvent(
     val entry: NotificationEntry,
     val reason: Int
-) : NotifEvent() {
+) : NotifEvent("onEntryRemoved ${cancellationReasonDebugString(reason)}") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onEntryRemoved(entry, reason)
     }
@@ -85,7 +87,7 @@
 
 data class CleanUpEntryEvent(
     val entry: NotificationEntry
-) : NotifEvent() {
+) : NotifEvent("onEntryCleanUp") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onEntryCleanUp(entry)
     }
@@ -93,13 +95,13 @@
 
 data class RankingUpdatedEvent(
     val rankingMap: RankingMap
-) : NotifEvent() {
+) : NotifEvent("onRankingUpdate") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onRankingUpdate(rankingMap)
     }
 }
 
-class RankingAppliedEvent() : NotifEvent() {
+class RankingAppliedEvent : NotifEvent("onRankingApplied") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onRankingApplied()
     }
@@ -110,7 +112,7 @@
     val user: UserHandle,
     val channel: NotificationChannel,
     val modificationType: Int
-) : NotifEvent() {
+) : NotifEvent("onNotificationChannelModified") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onNotificationChannelModified(pkgName, user, channel, modificationType)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
index fd5bae1..c873e6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.util.Assert
 import com.android.systemui.util.ListenerSet
-import com.android.systemui.util.isNotEmpty
 import java.io.PrintWriter
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt
index b318252..78e9a74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationDismissibilityProviderImpl.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.withIncreasedIndent
@@ -28,9 +27,7 @@
 import javax.inject.Inject
 
 @SysUISingleton
-class NotificationDismissibilityProviderImpl
-@Inject
-constructor(private val notifPipelineFlags: NotifPipelineFlags, dumpManager: DumpManager) :
+class NotificationDismissibilityProviderImpl @Inject constructor(dumpManager: DumpManager) :
     NotificationDismissibilityProvider, Dumpable {
 
     init {
@@ -43,11 +40,7 @@
         private set
 
     override fun isDismissable(entry: NotificationEntry): Boolean {
-        return if (notifPipelineFlags.allowDismissOngoing()) {
-            entry.key !in nonDismissableEntryKeys
-        } else {
-            entry.legacyIsDismissableRecursive()
-        }
+        return entry.key !in nonDismissableEntryKeys
     }
 
     @Synchronized
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index 4568c0c..5d33804 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -21,6 +21,8 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -44,32 +46,50 @@
     private final GroupMembershipManager mGroupMembershipManager;
     private final Set<OnGroupExpansionChangeListener> mOnGroupChangeListeners = new HashSet<>();
 
-    // Set of summary keys whose groups are expanded
+    /**
+     * Set of summary keys whose groups are expanded.
+     * NOTE: This should not be modified without notifying listeners, so prefer using
+     * {@code setGroupExpanded} when making changes.
+      */
     private final Set<NotificationEntry> mExpandedGroups = new HashSet<>();
 
+    private final FeatureFlags mFeatureFlags;
+
     @Inject
     public GroupExpansionManagerImpl(DumpManager dumpManager,
-            GroupMembershipManager groupMembershipManager) {
+            GroupMembershipManager groupMembershipManager, FeatureFlags featureFlags) {
         mDumpManager = dumpManager;
         mGroupMembershipManager = groupMembershipManager;
+        mFeatureFlags = featureFlags;
     }
 
     /**
      * Cleanup entries from mExpandedGroups that no longer exist in the pipeline.
      */
     private final OnBeforeRenderListListener mNotifTracker = (entries) -> {
+        if (mExpandedGroups.isEmpty()) {
+            return; // nothing to do
+        }
+
         final Set<NotificationEntry> renderingSummaries = new HashSet<>();
         for (ListEntry entry : entries) {
             if (entry instanceof GroupEntry) {
                 renderingSummaries.add(entry.getRepresentativeEntry());
             }
         }
-        mExpandedGroups.removeIf(expandedGroup -> !renderingSummaries.contains(expandedGroup));
+
+        // Create a copy of mExpandedGroups so we can modify it in a thread-safe way.
+        final var currentExpandedGroups = new HashSet<>(mExpandedGroups);
+        for (NotificationEntry entry : currentExpandedGroups) {
+            setExpanded(entry, renderingSummaries.contains(entry));
+        }
     };
 
     public void attach(NotifPipeline pipeline) {
-        mDumpManager.registerDumpable(this);
-        pipeline.addOnBeforeRenderListListener(mNotifTracker);
+        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
+            mDumpManager.registerDumpable(this);
+            pipeline.addOnBeforeRenderListListener(mNotifTracker);
+        }
     }
 
     @Override
@@ -85,13 +105,30 @@
     @Override
     public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
         final NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry);
+        setExpanded(groupSummary, expanded);
+    }
+
+    /**
+     * Add or remove {@code entry} to/from {@code mExpandedGroups} and notify listeners if
+     * something changed. This assumes that {@code entry} is a group summary.
+     * <p>
+     * TODO(b/293434635): Currently, in spite of its docs,
+     * {@code mGroupMembershipManager.getGroupSummary(entry)} returns null if {@code entry} is
+     * already a summary. Instead of needing this helper method to bypass that, we probably want to
+     * move this code back to {@code setGroupExpanded} and use that everywhere.
+     */
+    private void setExpanded(NotificationEntry entry, boolean expanded) {
+        boolean changed;
         if (expanded) {
-            mExpandedGroups.add(groupSummary);
+            changed = mExpandedGroups.add(entry);
         } else {
-            mExpandedGroups.remove(groupSummary);
+            changed = mExpandedGroups.remove(entry);
         }
 
-        sendOnGroupExpandedChange(entry, expanded);
+        // Only notify listeners if something changed.
+        if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE) || changed) {
+            sendOnGroupExpandedChange(entry, expanded);
+        }
     }
 
     @Override
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 07fd349..e61f9bd 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
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.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/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index a880b71..082f308 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
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import java.lang.RuntimeException
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationMemoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationMemoryModule.kt
new file mode 100644
index 0000000..92e9765
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationMemoryModule.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.dagger
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.notification.logging.NotificationMemoryMonitor
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface NotificationMemoryModule {
+
+    /** Binds memory monitor into startable map. */
+    @Binds
+    @IntoMap
+    @ClassKey(NotificationMemoryMonitor::class)
+    fun bindsNotificationMemoryMonitorStartable(monitor: NotificationMemoryMonitor): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index b100d44..ea3a8f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -63,8 +63,12 @@
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModelModule;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 
 import dagger.Binds;
@@ -85,6 +89,9 @@
         ShadeEventsModule.class,
         NotifPipelineChoreographerModule.class,
         NotificationSectionHeadersModule.class,
+        NotificationListViewModelModule.class,
+        ActivatableNotificationViewModelModule.class,
+        NotificationMemoryModule.class,
 })
 public interface NotificationsModule {
     @Binds
@@ -159,6 +166,14 @@
         }
     }
 
+    /** Provides the container for the notification list. */
+    @Provides
+    @SysUISingleton
+    static NotificationListContainer provideListContainer(
+            NotificationStackScrollLayoutController nsslController) {
+        return nsslController.getNotificationListContainer();
+    }
+
     /**
      * Provide the active notification collection managing the notifications to render.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt
new file mode 100644
index 0000000..f605bdf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** A repository tracking the status of notification expansion animations. */
+@SysUISingleton
+class NotificationExpansionRepository @Inject constructor() {
+    private val _isExpandAnimationRunning = MutableStateFlow(false)
+
+    /**
+     * Emits true if an animation that expands a notification object into an opening window is
+     * running and false otherwise.
+     *
+     * See [com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController].
+     */
+    val isExpandAnimationRunning: Flow<Boolean> = _isExpandAnimationRunning.asStateFlow()
+
+    /** Sets whether the notification expansion animation is currently running. */
+    fun setIsExpandAnimationRunning(running: Boolean) {
+        _isExpandAnimationRunning.value = running
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt
new file mode 100644
index 0000000..8f7e269
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import javax.inject.Inject
+
+/** Interactor for notifications in general. */
+@SysUISingleton
+class NotificationsInteractor
+@Inject
+constructor(
+    private val disableFlagsRepository: DisableFlagsRepository,
+) {
+    /** Returns true if notification alerts are allowed. */
+    fun areNotificationAlertsEnabled(): Boolean {
+        return disableFlagsRepository.disableFlags.value.areNotificationAlertsEnabled()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index d896541..9d95342 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.util.traceSection
 import javax.inject.Inject
 
 /**
@@ -95,7 +96,7 @@
      * @throws InflationException Exception if required icons are not valid or specified
      */
     @Throws(InflationException::class)
-    fun createIcons(entry: NotificationEntry) {
+    fun createIcons(entry: NotificationEntry) = traceSection("IconManager.createIcons") {
         // Construct the status bar icon view.
         val sbIcon = iconBuilder.createIconView(entry)
         sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
@@ -143,9 +144,9 @@
      * @throws InflationException Exception if required icons are not valid or specified
      */
     @Throws(InflationException::class)
-    fun updateIcons(entry: NotificationEntry) {
+    fun updateIcons(entry: NotificationEntry) = traceSection("IconManager.updateIcons") {
         if (!entry.icons.areIconsAvailable) {
-            return
+            return@traceSection
         }
         entry.icons.smallIconDescriptor = null
         entry.icons.peopleAvatarDescriptor = null
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 a5278c3d..76e228b 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
@@ -20,10 +20,8 @@
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
-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
@@ -33,12 +31,10 @@
  */
 interface NotificationsController {
     fun initialize(
-        centralSurfaces: CentralSurfaces,
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
         stackController: NotifStackController,
         notificationActivityStarter: NotificationActivityStarter,
-        bindRowCallback: NotificationRowBinderImpl.BindRowCallback
     )
 
     fun resetUserExpandedStates()
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 0ed4175..7d1cca8 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
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.init
 
 import android.service.notification.StatusBarNotification
-import com.android.systemui.ForegroundServiceNotificationListener
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager
@@ -39,10 +38,8 @@
 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
-import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.wm.shell.bubbles.Bubbles
 import dagger.Lazy
 import java.util.Optional
@@ -72,18 +69,14 @@
     private val animatedImageNotificationManager: AnimatedImageNotificationManager,
     private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
     private val bubblesOptional: Optional<Bubbles>,
-    private val fgsNotifListener: ForegroundServiceNotificationListener,
-    private val memoryMonitor: Lazy<NotificationMemoryMonitor>,
     private val featureFlags: FeatureFlags
 ) : NotificationsController {
 
     override fun initialize(
-        centralSurfaces: CentralSurfaces,
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
         stackController: NotifStackController,
         notificationActivityStarter: NotificationActivityStarter,
-        bindRowCallback: NotificationRowBinderImpl.BindRowCallback
     ) {
         notificationListener.registerAsSystemService()
 
@@ -94,13 +87,8 @@
         })
 
         notificationRowBinder.setNotificationClicker(
-                clickerBuilder.build(
-                    Optional.ofNullable(centralSurfaces), bubblesOptional,
-                        notificationActivityStarter))
-        notificationRowBinder.setUpWithPresenter(
-                presenter,
-                listContainer,
-                bindRowCallback)
+            clickerBuilder.build(bubblesOptional, notificationActivityStarter))
+        notificationRowBinder.setUpWithPresenter(presenter, listContainer)
         headsUpViewBinder.setPresenter(presenter)
         notifBindPipelineInitializer.initialize()
         animatedImageNotificationManager.bind()
@@ -115,8 +103,6 @@
         notificationsMediaManager.setUpWithPresenter(presenter)
         notificationLogger.setUpWithContainer(listContainer)
         peopleSpaceWidgetManager.attach(notificationListener)
-        fgsNotifListener.init()
-        memoryMonitor.get().init()
     }
 
     // TODO: Convert all functions below this line into listeners instead of public methods
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 14856da..65ba6de 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
@@ -21,10 +21,8 @@
 import com.android.systemui.statusbar.NotificationListener
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
-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
 
 /**
@@ -35,12 +33,10 @@
 ) : NotificationsController {
 
     override fun initialize(
-        centralSurfaces: CentralSurfaces,
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
         stackController: NotifStackController,
         notificationActivityStarter: NotificationActivityStarter,
-        bindRowCallback: NotificationRowBinderImpl.BindRowCallback
     ) {
         // Always connect the listener even if notification-handling is disabled. Being a listener
         // grants special permissions and it's not clear if other things will break if we lose those
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 0b31265..c6d2861 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
@@ -2,7 +2,7 @@
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.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/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 5bac2a9..4a823a4 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
@@ -20,9 +20,9 @@
 
 import com.android.systemui.log.dagger.NotificationInterruptLog
 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.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.WARNING
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import com.android.systemui.util.Compile
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 609f9d4..0c43da0 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
@@ -582,10 +582,6 @@
     }
 
     private boolean shouldSuppressHeadsUpWhenAwakeForOldWhen(NotificationEntry entry, boolean log) {
-        if (!mFlags.isNoHunForOldWhenEnabled()) {
-            return false;
-        }
-
         final Notification notification = entry.getSbn().getNotification();
         if (notification == null) {
             return false;
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 f38c1e5..0fdf681 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
@@ -18,6 +18,7 @@
 package com.android.systemui.statusbar.notification.logging
 
 import android.util.Log
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -32,13 +33,13 @@
     private val featureFlags: FeatureFlags,
     private val notificationMemoryDumper: NotificationMemoryDumper,
     private val notificationMemoryLogger: Lazy<NotificationMemoryLogger>,
-) {
+) : CoreStartable {
 
     companion object {
         private const val TAG = "NotificationMemory"
     }
 
-    fun init() {
+    override fun start() {
         Log.d(TAG, "NotificationMemoryMonitor initialized.")
         notificationMemoryDumper.init()
         if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_LOGGING_ENABLED)) {
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..0e1f66f 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
@@ -17,7 +17,7 @@
 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.core.LogLevel.INFO
 import com.android.systemui.log.dagger.NotificationRenderLog
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
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 417d4ac..1b790fd 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
@@ -360,9 +360,9 @@
     }
 
     @Override
-    public long performRemoveAnimation(long duration, long delay,
-            float translationDirection, boolean isHeadsUpAnimation, float endLocation,
-            Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) {
+    public long performRemoveAnimation(long duration, long delay, float translationDirection,
+            boolean isHeadsUpAnimation, Runnable onFinishedRunnable,
+            AnimatorListenerAdapter animationListener) {
         enableAppearDrawing(true);
         mIsHeadsUpAnimation = isHeadsUpAnimation;
         if (mDrawingAppearAnimation) {
@@ -427,16 +427,21 @@
         updateAppearAnimationAlpha();
         updateAppearRect();
         mAppearAnimator.addListener(new AnimatorListenerAdapter() {
-            private boolean mWasCancelled;
+            private boolean mRunWithoutInterruptions;
 
             @Override
             public void onAnimationEnd(Animator animation) {
                 if (onFinishedRunnable != null) {
                     onFinishedRunnable.run();
                 }
-                if (!mWasCancelled) {
+                if (mRunWithoutInterruptions) {
                     enableAppearDrawing(false);
-                    onAppearAnimationFinished(isAppearing);
+                }
+
+                // We need to reset the View state, even if the animation was cancelled
+                onAppearAnimationFinished(isAppearing);
+
+                if (mRunWithoutInterruptions) {
                     InteractionJankMonitor.getInstance().end(getCujType(isAppearing));
                 } else {
                     InteractionJankMonitor.getInstance().cancel(getCujType(isAppearing));
@@ -445,7 +450,7 @@
 
             @Override
             public void onAnimationStart(Animator animation) {
-                mWasCancelled = false;
+                mRunWithoutInterruptions = true;
                 Configuration.Builder builder = Configuration.Builder
                         .withView(getCujType(isAppearing), ActivatableNotificationView.this);
                 InteractionJankMonitor.getInstance().begin(builder);
@@ -453,7 +458,7 @@
 
             @Override
             public void onAnimationCancel(Animator animation) {
-                mWasCancelled = true;
+                mRunWithoutInterruptions = false;
             }
         });
 
@@ -567,12 +572,20 @@
 
     @Override
     public float getTopCornerRadius() {
+        if (mImprovedHunAnimation.isEnabled()) {
+            return super.getTopCornerRadius();
+        }
+
         float fraction = getInterpolatedAppearAnimationFraction();
         return MathUtils.lerp(0, super.getTopCornerRadius(), fraction);
     }
 
     @Override
     public float getBottomCornerRadius() {
+        if (mImprovedHunAnimation.isEnabled()) {
+            return super.getBottomCornerRadius();
+        }
+
         float fraction = getInterpolatedAppearAnimationFraction();
         return MathUtils.lerp(0, super.getBottomCornerRadius(), fraction);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
index 38a1579..9c4aa07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
@@ -60,7 +60,7 @@
     override fun onFinishInflate() {
         super.onFinishInflate()
 
-        appControlRow = findViewById(R.id.app_control)
+        appControlRow = requireViewById(R.id.app_control)
     }
 
     /**
@@ -143,9 +143,9 @@
     lateinit var switch: Switch
 
     override fun onFinishInflate() {
-        iconView = findViewById(R.id.icon)
-        channelName = findViewById(R.id.app_name)
-        switch = findViewById(R.id.toggle)
+        iconView = requireViewById(R.id.icon)
+        channelName = requireViewById(R.id.app_name)
+        switch = requireViewById(R.id.toggle)
 
         setOnClickListener { switch.toggle() }
     }
@@ -174,9 +174,9 @@
 
     override fun onFinishInflate() {
         super.onFinishInflate()
-        channelName = findViewById(R.id.channel_name)
-        channelDescription = findViewById(R.id.channel_description)
-        switch = findViewById(R.id.toggle)
+        channelName = requireViewById(R.id.channel_name)
+        channelDescription = requireViewById(R.id.channel_description)
+        switch = requireViewById(R.id.toggle)
         switch.setOnCheckedChangeListener { _, b ->
             channel?.let {
                 controller.proposeEditForChannel(it, if (b) it.importance else IMPORTANCE_NONE)
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 9f397fe..d92d11b 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
@@ -53,6 +53,7 @@
 import android.view.NotificationHeaderView;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.view.ViewStub;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -76,6 +77,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.ViewRefactorFlag;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -103,6 +105,8 @@
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
+import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.SwipeableView;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -170,6 +174,7 @@
     private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
     private Optional<BubblesManager> mBubblesManagerOptional;
     private MetricsLogger mMetricsLogger;
+    private NotificationChildrenContainerLogger mChildrenContainerLogger;
     private NotificationDismissibilityProvider mDismissibilityProvider;
     private FeatureFlags mFeatureFlags;
     private int mIconTransformContentShift;
@@ -271,7 +276,8 @@
     private OnExpandClickListener mOnExpandClickListener;
     private View.OnClickListener mOnFeedbackClickListener;
     private Path mExpandingClipPath;
-    private boolean mIsInlineReplyAnimationFlagEnabled = false;
+    private final ViewRefactorFlag mInlineReplyAnimation =
+            new ViewRefactorFlag(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
 
     // Listener will be called when receiving a long click event.
     // Use #setLongPressPosition to optionally assign positional data with the long press.
@@ -1627,6 +1633,51 @@
                 NotificationEntry child,
                 NotificationEntry newParent
         );
+
+        /**
+         * Called when an ExpandableNotificationRow transient view is removed from the
+         * NotificationChildrenContainer
+         */
+        void logRemoveTransientFromContainer(
+                NotificationEntry childEntry,
+                NotificationEntry containerEntry
+        );
+
+        /**
+         * Called when an ExpandableNotificationRow transient view is removed from the
+         * NotificationStackScrollLayout
+         */
+        void logRemoveTransientFromNssl(
+                NotificationEntry childEntry
+        );
+
+        /**
+         * Called when an ExpandableNotificationRow transient view is removed from a ViewGroup that
+         * is not NotificationChildrenContainer or NotificationStackScrollLayout
+         */
+        void logRemoveTransientFromViewGroup(
+                NotificationEntry childEntry,
+                ViewGroup containerView
+        );
+
+        /**
+         * Called when an ExpandableNotificationRow transient view is added to this
+         * ExpandableNotificationRow
+         */
+        void logAddTransientRow(
+                NotificationEntry childEntry,
+                NotificationEntry containerEntry,
+                int index
+        );
+
+        /**
+         * Called when an ExpandableNotificationRow transient view is removed from this
+         * ExpandableNotificationRow
+         */
+        void logRemoveTransientRow(
+                NotificationEntry childEntry,
+                NotificationEntry containerEntry
+        );
     }
 
     /**
@@ -1668,6 +1719,7 @@
             NotificationGutsManager gutsManager,
             NotificationDismissibilityProvider dismissibilityProvider,
             MetricsLogger metricsLogger,
+            NotificationChildrenContainerLogger childrenContainerLogger,
             SmartReplyConstants smartReplyConstants,
             SmartReplyController smartReplyController,
             FeatureFlags featureFlags,
@@ -1706,6 +1758,7 @@
         mBubblesManagerOptional = bubblesManagerOptional;
         mNotificationGutsManager = gutsManager;
         mMetricsLogger = metricsLogger;
+        mChildrenContainerLogger = childrenContainerLogger;
         mDismissibilityProvider = dismissibilityProvider;
         mFeatureFlags = featureFlags;
     }
@@ -1841,6 +1894,9 @@
             return traceTag;
         }
 
+        if (isSummaryWithChildren()) {
+            return traceTag + "(summary)";
+        }
         Class<? extends Notification.Style> style =
                 getEntry().getSbn().getNotification().getNotificationStyle();
         if (style == null) {
@@ -1874,6 +1930,7 @@
             mChildrenContainer.setIsLowPriority(mIsLowPriority);
             mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
             mChildrenContainer.onNotificationUpdated();
+            mChildrenContainer.setLogger(mChildrenContainerLogger);
 
             mTranslateableViews.add(mChildrenContainer);
         });
@@ -2673,7 +2730,12 @@
     }
 
     public boolean isExpanded(boolean allowOnKeyguard) {
-        return (!mOnKeyguard || allowOnKeyguard)
+        if (DEBUG) {
+            if (!mShowingPublicInitialized && !allowOnKeyguard) {
+                Log.d(TAG, "mShowingPublic is not initialized.");
+            }
+        }
+        return !mShowingPublic && (!mOnKeyguard || allowOnKeyguard)
                 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
                 || isUserExpanded());
     }
@@ -2918,7 +2980,6 @@
             long delay,
             float translationDirection,
             boolean isHeadsUpAnimation,
-            float endLocation,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         if (mMenuRow != null && mMenuRow.isMenuVisible()) {
@@ -2929,7 +2990,7 @@
                     public void onAnimationEnd(Animator animation) {
                         ExpandableNotificationRow.super.performRemoveAnimation(
                                 duration, delay, translationDirection, isHeadsUpAnimation,
-                                endLocation, onFinishedRunnable, animationListener);
+                                onFinishedRunnable, animationListener);
                     }
                 });
                 anim.start();
@@ -2937,7 +2998,7 @@
             }
         }
         return super.performRemoveAnimation(duration, delay, translationDirection,
-                isHeadsUpAnimation, endLocation, onFinishedRunnable, animationListener);
+                isHeadsUpAnimation, onFinishedRunnable, animationListener);
     }
 
     @Override
@@ -3065,10 +3126,6 @@
         return showingLayout != null && showingLayout.requireRowToHaveOverlappingRendering();
     }
 
-    public void setInlineReplyAnimationFlagEnabled(boolean isEnabled) {
-        mIsInlineReplyAnimationFlagEnabled = isEnabled;
-    }
-
     @Override
     public void setActualHeight(int height, boolean notifyListeners) {
         boolean changed = height != getActualHeight();
@@ -3088,7 +3145,7 @@
         }
         int contentHeight = Math.max(getMinHeight(), height);
         for (NotificationContentView l : mLayouts) {
-            if (mIsInlineReplyAnimationFlagEnabled) {
+            if (mInlineReplyAnimation.isEnabled()) {
                 l.setContentHeight(height);
             } else {
                 l.setContentHeight(contentHeight);
@@ -3620,8 +3677,11 @@
             pw.print(", mOnUserInteractionCallback null: " + (mOnUserInteractionCallback == null));
             pw.print(", removed: " + isRemoved());
             pw.print(", expandAnimationRunning: " + mExpandAnimationRunning);
+            pw.print(", mShowingPublic: " + mShowingPublic);
+            pw.print(", mShowingPublicInitialized: " + mShowingPublicInitialized);
             NotificationContentView showingLayout = getShowingLayout();
             pw.print(", privateShowing: " + (showingLayout == mPrivateLayout));
+            pw.print(", mShowNoBackground: " + mShowNoBackground);
             pw.println();
             showingLayout.dump(pw, args);
 
@@ -3707,4 +3767,73 @@
     public boolean getShowSnooze() {
         return mShowSnooze;
     }
+
+    @Override
+    public void removeFromTransientContainer() {
+        final ViewGroup transientContainer = getTransientContainer();
+        final ViewParent parent = getParent();
+        // Only log when there is real removal of transient views
+        if (transientContainer == null || transientContainer != parent) {
+            super.removeFromTransientContainer();
+            return;
+        }
+        logRemoveFromTransientContainer(transientContainer);
+        super.removeFromTransientContainer();
+    }
+
+    /**
+     * Log calls to removeFromTransientContainer when the container is NotificationChildrenContainer
+     * or NotificationStackScrollLayout.
+     */
+    public void logRemoveFromTransientContainer(ViewGroup transientContainer) {
+        if (mLogger == null) {
+            return;
+        }
+        if (transientContainer instanceof NotificationChildrenContainer) {
+            mLogger.logRemoveTransientFromContainer(
+                    /* childEntry = */ getEntry(),
+                    /* containerEntry = */ ((NotificationChildrenContainer) transientContainer)
+                            .getContainingNotification().getEntry()
+            );
+        } else if (transientContainer instanceof NotificationStackScrollLayout) {
+            mLogger.logRemoveTransientFromNssl(
+                    /* childEntry = */ getEntry()
+            );
+        } else {
+            mLogger.logRemoveTransientFromViewGroup(
+                    /* childEntry = */ getEntry(),
+                    /* containerView = */ transientContainer
+            );
+        }
+    }
+
+    @Override
+    public void addTransientView(View view, int index) {
+        if (view instanceof ExpandableNotificationRow) {
+            logAddTransientRow((ExpandableNotificationRow) view, index);
+        }
+        super.addTransientView(view, index);
+    }
+
+    private void logAddTransientRow(ExpandableNotificationRow row, int index) {
+        if (mLogger == null) {
+            return;
+        }
+        mLogger.logAddTransientRow(row.getEntry(), getEntry(), index);
+    }
+
+    @Override
+    public void removeTransientView(View view) {
+        if (view instanceof ExpandableNotificationRow) {
+            logRemoveTransientRow((ExpandableNotificationRow) view);
+        }
+        super.removeTransientView(view);
+    }
+
+    private void logRemoveTransientRow(ExpandableNotificationRow row) {
+        if (mLogger == null) {
+            return;
+        }
+        mLogger.logRemoveTransientRow(row.getEntry(), getEntry());
+    }
 }
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 1acc9f9..80f5d19 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
@@ -21,12 +21,16 @@
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
 
+import android.net.Uri;
+import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
@@ -50,6 +54,7 @@
 import com.android.systemui.statusbar.notification.row.dagger.AppName;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
+import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -70,6 +75,10 @@
 @NotificationRowScope
 public class ExpandableNotificationRowController implements NotifViewController {
     private static final String TAG = "NotifRowController";
+
+    static final Uri BUBBLES_SETTING_URI =
+            Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES);
+    private static final String BUBBLES_SETTING_ENABLED_VALUE = "1";
     private final ExpandableNotificationRow mView;
     private final NotificationListContainer mListContainer;
     private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory;
@@ -88,6 +97,7 @@
     private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener;
     private final StatusBarStateController mStatusBarStateController;
     private final MetricsLogger mMetricsLogger;
+    private final NotificationChildrenContainerLogger mChildrenContainerLogger;
     private final ExpandableNotificationRow.CoordinateOnClickListener mOnFeedbackClickListener;
     private final NotificationGutsManager mNotificationGutsManager;
     private final OnUserInteractionCallback mOnUserInteractionCallback;
@@ -102,6 +112,23 @@
     private final ExpandableNotificationRowDragController mDragController;
     private final NotificationDismissibilityProvider mDismissibilityProvider;
     private final IStatusBarService mStatusBarService;
+
+    private final NotificationSettingsController mSettingsController;
+
+    @VisibleForTesting
+    final NotificationSettingsController.Listener mSettingsListener =
+            new NotificationSettingsController.Listener() {
+                @Override
+                public void onSettingChanged(Uri setting, int userId, String value) {
+                    if (BUBBLES_SETTING_URI.equals(setting)) {
+                        final int viewUserId = mView.getEntry().getSbn().getUserId();
+                        if (viewUserId == UserHandle.USER_ALL || viewUserId == userId) {
+                            mView.getPrivateLayout().setBubblesEnabledForUser(
+                                    BUBBLES_SETTING_ENABLED_VALUE.equals(value));
+                        }
+                    }
+                }
+            };
     private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback =
             new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
                 @Override
@@ -125,6 +152,46 @@
                 ) {
                     mLogBufferLogger.logSkipAttachingKeepInParentChild(child, newParent);
                 }
+
+                @Override
+                public void logRemoveTransientFromContainer(
+                        NotificationEntry childEntry,
+                        NotificationEntry containerEntry
+                ) {
+                    mLogBufferLogger.logRemoveTransientFromContainer(childEntry, containerEntry);
+                }
+
+                @Override
+                public void logRemoveTransientFromNssl(
+                        NotificationEntry childEntry
+                ) {
+                    mLogBufferLogger.logRemoveTransientFromNssl(childEntry);
+                }
+
+                @Override
+                public void logRemoveTransientFromViewGroup(
+                        NotificationEntry childEntry,
+                        ViewGroup containerView
+                ) {
+                    mLogBufferLogger.logRemoveTransientFromViewGroup(childEntry, containerView);
+                }
+
+                @Override
+                public void logAddTransientRow(
+                        NotificationEntry childEntry,
+                        NotificationEntry containerEntry,
+                        int index
+                ) {
+                    mLogBufferLogger.logAddTransientRow(childEntry, containerEntry, index);
+                }
+
+                @Override
+                public void logRemoveTransientRow(
+                        NotificationEntry childEntry,
+                        NotificationEntry containerEntry
+                ) {
+                    mLogBufferLogger.logRemoveTransientRow(childEntry, containerEntry);
+                }
             };
 
 
@@ -135,6 +202,7 @@
             RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
             MetricsLogger metricsLogger,
             NotificationRowLogger logBufferLogger,
+            NotificationChildrenContainerLogger childrenContainerLogger,
             NotificationListContainer listContainer,
             SmartReplyConstants smartReplyConstants,
             SmartReplyController smartReplyController,
@@ -158,6 +226,7 @@
             FeatureFlags featureFlags,
             PeopleNotificationIdentifier peopleNotificationIdentifier,
             Optional<BubblesManager> bubblesManagerOptional,
+            NotificationSettingsController settingsController,
             ExpandableNotificationRowDragController dragController,
             NotificationDismissibilityProvider dismissibilityProvider,
             IStatusBarService statusBarService) {
@@ -186,8 +255,10 @@
         mFeatureFlags = featureFlags;
         mPeopleNotificationIdentifier = peopleNotificationIdentifier;
         mBubblesManagerOptional = bubblesManagerOptional;
+        mSettingsController = settingsController;
         mDragController = dragController;
         mMetricsLogger = metricsLogger;
+        mChildrenContainerLogger = childrenContainerLogger;
         mLogBufferLogger = logBufferLogger;
         mSmartReplyConstants = smartReplyConstants;
         mSmartReplyController = smartReplyController;
@@ -222,6 +293,7 @@
                 mNotificationGutsManager,
                 mDismissibilityProvider,
                 mMetricsLogger,
+                mChildrenContainerLogger,
                 mSmartReplyConstants,
                 mSmartReplyController,
                 mFeatureFlags,
@@ -253,12 +325,14 @@
                         NotificationMenuRowPlugin.class, false /* Allow multiple */);
                 mView.setOnKeyguard(mStatusBarStateController.getState() == KEYGUARD);
                 mStatusBarStateController.addCallback(mStatusBarStateListener);
+                mSettingsController.addCallback(BUBBLES_SETTING_URI, mSettingsListener);
             }
 
             @Override
             public void onViewDetachedFromWindow(View v) {
                 mPluginManager.removePluginListener(mView);
                 mStatusBarStateController.removeCallback(mStatusBarStateListener);
+                mSettingsController.removeCallback(BUBBLES_SETTING_URI, mSettingsListener);
             }
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 7a2bee9..b950187 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -148,7 +148,7 @@
     private void dismissShade() {
         // Speed up dismissing the shade since the drag needs to be handled by
         // the shell layer underneath
-        mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
+        mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
                 false /* delayed */, 1.1f /* speedUpFactor */);
     }
 
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 9aa50e9..c8f13a6 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
@@ -29,6 +29,8 @@
 import android.view.ViewOutlineProvider;
 
 import com.android.systemui.R;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.ViewRefactorFlag;
 import com.android.systemui.statusbar.notification.RoundableState;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 import com.android.systemui.util.DumpUtilsKt;
@@ -47,12 +49,15 @@
     private float mOutlineAlpha = -1f;
     private boolean mAlwaysRoundBothCorners;
     private Path mTmpPath = new Path();
+    protected final ViewRefactorFlag mImprovedHunAnimation =
+            new ViewRefactorFlag(Flags.IMPROVED_HUN_ANIMATIONS);
 
     /**
      * {@code false} if the children views of the {@link ExpandableOutlineView} are translated when
      * it is moved. Otherwise, the translation is set on the {@code ExpandableOutlineView} itself.
      */
     protected boolean mDismissUsingRowTranslationX = true;
+
     private float[] mTmpCornerRadii = new float[8];
 
     private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@@ -81,6 +86,15 @@
         return mRoundableState;
     }
 
+    @Override
+    public int getClipHeight() {
+        if (mCustomOutline) {
+            return mOutlineRect.height();
+        }
+
+        return super.getClipHeight();
+    }
+
     protected Path getClipPath(boolean ignoreTranslation) {
         int left;
         int top;
@@ -112,7 +126,7 @@
             return EMPTY_PATH;
         }
         float bottomRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
-        if (topRadius + bottomRadius > height) {
+        if (!mImprovedHunAnimation.isEnabled() && (topRadius + bottomRadius > height)) {
             float overShoot = topRadius + bottomRadius - height;
             float currentTopRoundness = getTopRoundness();
             float currentBottomRoundness = getBottomRoundness();
@@ -360,4 +374,5 @@
             }
         });
     }
+
 }
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 f0e15c2..c4c116b 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
@@ -93,6 +93,12 @@
         return mRoundableState;
     }
 
+    @Override
+    public int getClipHeight() {
+        int clipHeight = Math.max(mActualHeight - mClipTopAmount - mClipBottomAmount, 0);
+        return Math.max(clipHeight, mMinimumHeightForClipping);
+    }
+
     private void initDimens() {
         mContentShift = getResources().getDimensionPixelSize(
                 R.dimen.shelf_transform_content_shift);
@@ -367,7 +373,6 @@
      *                             such that the  child appears to be going away to the top. 1
      *                             Should mean the opposite.
      * @param isHeadsUpAnimation Is this a headsUp animation.
-     * @param endLocation The location where the horizonal heads up disappear animation should end.
      * @param onFinishedRunnable A runnable which should be run when the animation is finished.
      * @param animationListener An animation listener to add to the animation.
      *
@@ -375,7 +380,7 @@
      * animation starts.
      */
     public abstract long performRemoveAnimation(long duration,
-            long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation,
+            long delay, float translationDirection, boolean isHeadsUpAnimation,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener);
 
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 45be0b1..3856700 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
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.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/NotifLayoutInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
new file mode 100644
index 0000000..4429939
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import com.android.systemui.Dumpable
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.row.NotificationRowModule.NOTIF_REMOTEVIEWS_FACTORIES
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.withIncreasedIndent
+import java.io.PrintWriter
+import javax.inject.Inject
+import javax.inject.Named
+
+/**
+ * Implementation of [NotifLayoutInflaterFactory]. This class uses a set of
+ * [NotifRemoteViewsFactory] objects to create replacement views for Notification RemoteViews.
+ */
+open class NotifLayoutInflaterFactory
+@Inject
+constructor(
+    dumpManager: DumpManager,
+    @Named(NOTIF_REMOTEVIEWS_FACTORIES)
+    private val remoteViewsFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory>
+) : LayoutInflater.Factory2, Dumpable {
+    init {
+        dumpManager.registerNormalDumpable(TAG, this)
+    }
+
+    override fun onCreateView(
+        parent: View?,
+        name: String,
+        context: Context,
+        attrs: AttributeSet
+    ): View? {
+        var view: View? = null
+        var handledFactory: NotifRemoteViewsFactory? = null
+        for (layoutFactory in remoteViewsFactories) {
+            view = layoutFactory.instantiate(parent, name, context, attrs)
+            if (view != null) {
+                check(handledFactory == null) {
+                    "${layoutFactory.javaClass.name} tries to produce view. However, " +
+                        "${handledFactory?.javaClass?.name} produced view for $name before."
+                }
+                handledFactory = layoutFactory
+            }
+        }
+        logOnCreateView(name, view, handledFactory)
+        return view
+    }
+
+    override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? =
+        onCreateView(null, name, context, attrs)
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        val indentingPW = pw.asIndenting()
+
+        indentingPW.appendLine("$TAG ReplacementFactories:")
+        indentingPW.withIncreasedIndent {
+            remoteViewsFactories.forEach { indentingPW.appendLine(it.javaClass.simpleName) }
+        }
+    }
+
+    private fun logOnCreateView(
+        name: String,
+        replacedView: View?,
+        factory: NotifRemoteViewsFactory?
+    ) {
+        if (SPEW && replacedView != null && factory != null) {
+            Log.d(TAG, "$factory produced view for $name: $replacedView")
+        }
+    }
+
+    private companion object {
+        private const val TAG = "NotifLayoutInflaterFac"
+        private val SPEW = Log.isLoggable(TAG, Log.VERBOSE)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt
new file mode 100644
index 0000000..eebd4d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactory.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+
+/** Interface used to create replacement view instances in Notification RemoteViews. */
+interface NotifRemoteViewsFactory {
+
+    /** return the replacement view instance for the given view name */
+    fun instantiate(parent: View?, name: String, context: Context, attrs: AttributeSet): View?
+}
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 b4bfded..0ad77bd 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
@@ -79,6 +79,7 @@
     private final ConversationNotificationProcessor mConversationProcessor;
     private final Executor mBgExecutor;
     private final SmartReplyStateInflater mSmartReplyStateInflater;
+    private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
 
     @Inject
     NotificationContentInflater(
@@ -87,13 +88,15 @@
             ConversationNotificationProcessor conversationProcessor,
             MediaFeatureFlag mediaFeatureFlag,
             @Background Executor bgExecutor,
-            SmartReplyStateInflater smartRepliesInflater) {
+            SmartReplyStateInflater smartRepliesInflater,
+            NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
         mRemoteViewCache = remoteViewCache;
         mRemoteInputManager = remoteInputManager;
         mConversationProcessor = conversationProcessor;
         mIsMediaInQS = mediaFeatureFlag.getEnabled();
         mBgExecutor = bgExecutor;
         mSmartReplyStateInflater = smartRepliesInflater;
+        mNotifLayoutInflaterFactory = notifLayoutInflaterFactory;
     }
 
     @Override
@@ -137,7 +140,8 @@
                 callback,
                 mRemoteInputManager.getRemoteViewsOnClickHandler(),
                 mIsMediaInQS,
-                mSmartReplyStateInflater);
+                mSmartReplyStateInflater,
+                mNotifLayoutInflaterFactory);
         if (mInflateSynchronously) {
             task.onPostExecute(task.doInBackground());
         } else {
@@ -160,7 +164,8 @@
                 bindParams.isLowPriority,
                 bindParams.usesIncreasedHeight,
                 bindParams.usesIncreasedHeadsUpHeight,
-                packageContext);
+                packageContext,
+                mNotifLayoutInflaterFactory);
 
         result = inflateSmartReplyViews(result, reInflateFlags, entry,
                 row.getContext(), packageContext,
@@ -298,7 +303,8 @@
 
     private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags,
             Notification.Builder builder, boolean isLowPriority, boolean usesIncreasedHeight,
-            boolean usesIncreasedHeadsUpHeight, Context packageContext) {
+            boolean usesIncreasedHeadsUpHeight, Context packageContext,
+            NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
         InflationProgress result = new InflationProgress();
 
         if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
@@ -316,7 +322,7 @@
         if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
             result.newPublicView = builder.makePublicContentView(isLowPriority);
         }
-
+        setNotifsViewsInflaterFactory(result, notifLayoutInflaterFactory);
         result.packageContext = packageContext;
         result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */);
         result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
@@ -324,6 +330,22 @@
         return result;
     }
 
+    private static void setNotifsViewsInflaterFactory(InflationProgress result,
+            NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
+        setRemoteViewsInflaterFactory(result.newContentView, notifLayoutInflaterFactory);
+        setRemoteViewsInflaterFactory(result.newExpandedView,
+                notifLayoutInflaterFactory);
+        setRemoteViewsInflaterFactory(result.newHeadsUpView, notifLayoutInflaterFactory);
+        setRemoteViewsInflaterFactory(result.newPublicView, notifLayoutInflaterFactory);
+    }
+
+    private static void setRemoteViewsInflaterFactory(RemoteViews remoteViews,
+            NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
+        if (remoteViews != null) {
+            remoteViews.setLayoutInflaterFactory(notifLayoutInflaterFactory);
+        }
+    }
+
     private static CancellationSignal apply(
             Executor bgExecutor,
             boolean inflateSynchronously,
@@ -348,7 +370,6 @@
                 public void setResultView(View v) {
                     result.inflatedContentView = v;
                 }
-
                 @Override
                 public RemoteViews getRemoteView() {
                     return result.newContentView;
@@ -356,7 +377,7 @@
             };
             applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,
                     remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,
-                    privateLayout,  privateLayout.getContractedChild(),
+                    privateLayout, privateLayout.getContractedChild(),
                     privateLayout.getVisibleWrapper(
                             NotificationContentView.VISIBLE_TYPE_CONTRACTED),
                     runningInflations, applyCallback);
@@ -474,7 +495,6 @@
                             parentLayout,
                             remoteViewClickHandler);
                     validateView(v, entry, row.getResources());
-                    v.setIsRootNamespace(true);
                     applyCallback.setResultView(v);
                 } else {
                     newContentView.reapply(
@@ -511,7 +531,6 @@
                     return;
                 }
                 if (isNewView) {
-                    v.setIsRootNamespace(true);
                     applyCallback.setResultView(v);
                 } else if (existingWrapper != null) {
                     existingWrapper.onReinflated();
@@ -760,8 +779,8 @@
      * @param oldView The old view that was applied to the existing view before
      * @return {@code true} if the RemoteViews are the same and the view can be reused to reapply.
      */
-     @VisibleForTesting
-     static boolean canReapplyRemoteView(final RemoteViews newView,
+    @VisibleForTesting
+    static boolean canReapplyRemoteView(final RemoteViews newView,
             final RemoteViews oldView) {
         return (newView == null && oldView == null) ||
                 (newView != null && oldView != null
@@ -802,6 +821,7 @@
         private final ConversationNotificationProcessor mConversationProcessor;
         private final boolean mIsMediaInQS;
         private final SmartReplyStateInflater mSmartRepliesInflater;
+        private final NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
 
         private AsyncInflationTask(
                 Executor bgExecutor,
@@ -817,7 +837,8 @@
                 InflationCallback callback,
                 RemoteViews.InteractionHandler remoteViewClickHandler,
                 boolean isMediaFlagEnabled,
-                SmartReplyStateInflater smartRepliesInflater) {
+                SmartReplyStateInflater smartRepliesInflater,
+                NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
             mEntry = entry;
             mRow = row;
             mBgExecutor = bgExecutor;
@@ -833,6 +854,7 @@
             mCallback = callback;
             mConversationProcessor = conversationProcessor;
             mIsMediaInQS = isMediaFlagEnabled;
+            mNotifLayoutInflaterFactory = notifLayoutInflaterFactory;
             entry.setInflationTask(this);
         }
 
@@ -876,7 +898,8 @@
                 }
                 InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
                         recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
-                        mUsesIncreasedHeadsUpHeight, packageContext);
+                        mUsesIncreasedHeadsUpHeight, packageContext,
+                        mNotifLayoutInflaterFactory);
                 InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
                 InflationProgress result = inflateSmartReplyViews(
                         inflationProgress,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 124df8c..f4f78d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -41,6 +41,8 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 
+import androidx.annotation.MainThread;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.R;
@@ -65,7 +67,6 @@
 import com.android.systemui.statusbar.policy.SmartReplyView;
 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
 import com.android.systemui.util.Compile;
-import com.android.systemui.wmshell.BubblesManager;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -134,6 +135,7 @@
     private PeopleNotificationIdentifier mPeopleIdentifier;
     private RemoteInputViewSubcomponent.Factory mRemoteInputSubcomponentFactory;
     private IStatusBarService mStatusBarService;
+    private boolean mBubblesEnabledForUser;
 
     /**
      * List of listeners for when content views become inactive (i.e. not the showing view).
@@ -208,6 +210,23 @@
         mSmartReplyConstants = smartReplyConstants;
         mSmartReplyController = smartReplyController;
         mStatusBarService = statusBarService;
+        // We set root namespace so that we avoid searching children for id. Notification  might
+        // contain custom view and their ids may clash with ids already existing in shade or
+        // notification panel
+        setIsRootNamespace(true);
+    }
+
+    @Override
+    public View focusSearch(View focused, int direction) {
+        // This implementation is copied from ViewGroup but with removed special handling of
+        // setIsRootNamespace. This view is set as tree root using setIsRootNamespace and it
+        // causes focus to be stuck inside of it. We need to be root to avoid id conflicts
+        // but we don't want to behave like root when it comes to focusing.
+        if (mParent != null) {
+            return mParent.focusSearch(focused, direction);
+        }
+        Log.wtf(TAG, "NotificationContentView doesn't have parent");
+        return null;
     }
 
     public void reinflate() {
@@ -1423,12 +1442,20 @@
         }
     }
 
+    @MainThread
+    public void setBubblesEnabledForUser(boolean enabled) {
+        mBubblesEnabledForUser = enabled;
+
+        applyBubbleAction(mExpandedChild, mNotificationEntry);
+        applyBubbleAction(mHeadsUpChild, mNotificationEntry);
+    }
+
     @VisibleForTesting
     boolean shouldShowBubbleButton(NotificationEntry entry) {
         boolean isPersonWithShortcut =
                 mPeopleIdentifier.getPeopleNotificationType(entry)
                         >= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
-        return BubblesManager.areBubblesEnabled(mContext, entry.getSbn().getUser())
+        return mBubblesEnabledForUser
                 && isPersonWithShortcut
                 && entry.getBubbleMetadata() != null;
     }
@@ -2062,6 +2089,7 @@
             pw.print("null");
         }
         pw.println();
+        pw.println("mBubblesEnabledForUser: " + mBubblesEnabledForUser);
 
         pw.print("RemoteInputViews { ");
         pw.print(" visibleType: " + mVisibleType);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 9bc0333..7134f15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -48,6 +48,7 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.transition.ChangeBounds;
@@ -118,6 +119,8 @@
     private NotificationGuts mGutsContainer;
     private OnConversationSettingsClickListener mOnConversationSettingsClickListener;
 
+    private UserManager mUm;
+
     @VisibleForTesting
     boolean mSkipPost = false;
     private int mActualHeight;
@@ -155,7 +158,9 @@
         // People Tile add request.
         if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
             mShadeController.animateCollapseShade();
-            mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
+            if (mUm.isSameProfileGroup(UserHandle.USER_SYSTEM, mSbn.getNormalizedUserId())) {
+                mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
+            }
         }
         mGutsContainer.closeControls(v, /* save= */ true);
     };
@@ -188,6 +193,7 @@
     public void bindNotification(
             ShortcutManager shortcutManager,
             PackageManager pm,
+            UserManager um,
             PeopleSpaceWidgetManager peopleSpaceWidgetManager,
             INotificationManager iNotificationManager,
             OnUserInteractionCallback onUserInteractionCallback,
@@ -211,6 +217,7 @@
         mEntry = entry;
         mSbn = entry.getSbn();
         mPm = pm;
+        mUm = um;
         mAppName = mPackageName;
         mOnSettingsClickListener = onSettingsClick;
         mNotificationChannel = notificationChannel;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 7dbca42..6f79ea8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -30,6 +30,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
@@ -112,6 +113,9 @@
     private Runnable mOpenRunnable;
     private final INotificationManager mNotificationManager;
     private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
+
+    private final UserManager mUserManager;
+
     private final LauncherApps mLauncherApps;
     private final ShortcutManager mShortcutManager;
     private final UserContextProvider mContextTracker;
@@ -128,6 +132,7 @@
             AccessibilityManager accessibilityManager,
             HighPriorityProvider highPriorityProvider,
             INotificationManager notificationManager,
+            UserManager userManager,
             PeopleSpaceWidgetManager peopleSpaceWidgetManager,
             LauncherApps launcherApps,
             ShortcutManager shortcutManager,
@@ -150,6 +155,7 @@
         mAccessibilityManager = accessibilityManager;
         mHighPriorityProvider = highPriorityProvider;
         mNotificationManager = notificationManager;
+        mUserManager = userManager;
         mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
         mLauncherApps = launcherApps;
         mShortcutManager = shortcutManager;
@@ -471,6 +477,7 @@
         notificationInfoView.bindNotification(
                 mShortcutManager,
                 pmUser,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mNotificationManager,
                 mOnUserInteractionCallback,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
index c3dd92a..4f5a04f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
@@ -17,14 +17,21 @@
 
 package com.android.systemui.statusbar.notification.row
 
+import android.view.ViewGroup
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.log.dagger.NotificationRenderLog
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
 
-class NotificationRowLogger @Inject constructor(@NotificationLog private val buffer: LogBuffer) {
+class NotificationRowLogger
+@Inject
+constructor(
+    @NotificationLog private val buffer: LogBuffer,
+    @NotificationRenderLog private val notificationRenderBuffer: LogBuffer
+) {
     fun logKeepInParentChildDetached(child: NotificationEntry, oldParent: NotificationEntry?) {
         buffer.log(
             TAG,
@@ -48,6 +55,79 @@
             { "Skipping to attach $str1 to $str2, because it still flagged to keep in parent" }
         )
     }
+
+    fun logRemoveTransientFromContainer(
+        childEntry: NotificationEntry,
+        containerEntry: NotificationEntry
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                str1 = childEntry.logKey
+                str2 = containerEntry.logKey
+            },
+            { "RemoveTransientRow from ChildrenContainer: childKey: $str1 -- containerKey: $str2" }
+        )
+    }
+
+    fun logRemoveTransientFromNssl(
+        childEntry: NotificationEntry,
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            { str1 = childEntry.logKey },
+            { "RemoveTransientRow from Nssl: childKey: $str1" }
+        )
+    }
+
+    fun logRemoveTransientFromViewGroup(
+        childEntry: NotificationEntry,
+        containerView: ViewGroup,
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.WARNING,
+            {
+                str1 = childEntry.logKey
+                str2 = containerView.toString()
+            },
+            { "RemoveTransientRow from other ViewGroup: childKey: $str1 -- ViewGroup: $str2" }
+        )
+    }
+
+    fun logAddTransientRow(
+        childEntry: NotificationEntry,
+        containerEntry: NotificationEntry,
+        index: Int
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.ERROR,
+            {
+                str1 = childEntry.logKey
+                str2 = containerEntry.logKey
+                int1 = index
+            },
+            { "addTransientRow to row: childKey: $str1 -- containerKey: $str2 -- index: $int1" }
+        )
+    }
+
+    fun logRemoveTransientRow(
+        childEntry: NotificationEntry,
+        containerEntry: NotificationEntry,
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.ERROR,
+            {
+                str1 = childEntry.logKey
+                str2 = containerEntry.logKey
+            },
+            { "removeTransientRow from row: childKey: $str1 -- containerKey: $str2" }
+        )
+    }
 }
 
 private const val TAG = "NotifRow"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index 111b575..867e08b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -17,15 +17,27 @@
 package com.android.systemui.statusbar.notification.row;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 
 import dagger.Binds;
 import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.ElementsIntoSet;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.inject.Named;
 
 /**
  * Dagger Module containing notification row and view inflation implementations.
  */
 @Module
 public abstract class NotificationRowModule {
+    public static final String NOTIF_REMOTEVIEWS_FACTORIES =
+            "notif_remoteviews_factories";
+
     /**
      * Provides notification row content binder instance.
      */
@@ -41,4 +53,19 @@
     @SysUISingleton
     public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
             NotifRemoteViewCacheImpl cacheImpl);
+
+    /** Provides view factories to be inflated in notification content. */
+    @Provides
+    @ElementsIntoSet
+    @Named(NOTIF_REMOTEVIEWS_FACTORIES)
+    static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
+            FeatureFlags featureFlags,
+            PrecomputedTextViewFactory precomputedTextViewFactory
+    ) {
+        final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
+        if (featureFlags.isEnabled(Flags.PRECOMPUTED_TEXT)) {
+            replacementFactories.add(precomputedTextViewFactory);
+        }
+        return replacementFactories;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java
new file mode 100644
index 0000000..51e4537
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import javax.inject.Inject;
+
+/**
+ * Centralized controller for listening to Secure Settings changes and informing in-process
+ * listeners, on a background thread.
+ */
+@SysUISingleton
+public class NotificationSettingsController implements Dumpable {
+
+    private final static String TAG = "NotificationSettingsController";
+    private final UserTracker mUserTracker;
+    private final UserTracker.Callback mCurrentUserTrackerCallback;
+    private final Handler mMainHandler;
+    private final Handler mBackgroundHandler;
+    private final ContentObserver mContentObserver;
+    private final SecureSettings mSecureSettings;
+    private final HashMap<Uri, ArrayList<Listener>> mListeners = new HashMap<>();
+
+    @Inject
+    public NotificationSettingsController(UserTracker userTracker,
+            @Main Handler mainHandler,
+            @Background Handler backgroundHandler,
+            SecureSettings secureSettings,
+            DumpManager dumpManager) {
+        mUserTracker = userTracker;
+        mMainHandler = mainHandler;
+        mBackgroundHandler = backgroundHandler;
+        mSecureSettings = secureSettings;
+        mContentObserver = new ContentObserver(mBackgroundHandler) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                super.onChange(selfChange, uri);
+                synchronized (mListeners) {
+                    if (mListeners.containsKey(uri)) {
+                        int userId = mUserTracker.getUserId();
+                        String value = getCurrentSettingValue(uri, userId);
+                        for (Listener listener : mListeners.get(uri)) {
+                            mMainHandler.post(() -> listener.onSettingChanged(uri, userId, value));
+                        }
+                    }
+                }
+            }
+        };
+
+        mCurrentUserTrackerCallback = new UserTracker.Callback() {
+            @Override
+            public void onUserChanged(int newUser, Context userContext) {
+                synchronized (mListeners) {
+                    if (mListeners.size() > 0) {
+                        mSecureSettings.unregisterContentObserver(mContentObserver);
+                        for (Uri uri : mListeners.keySet()) {
+                            mSecureSettings.registerContentObserverForUser(
+                                    uri, false, mContentObserver, newUser);
+                        }
+                    }
+                }
+            }
+        };
+        mUserTracker.addCallback(
+                mCurrentUserTrackerCallback, new HandlerExecutor(mBackgroundHandler));
+
+        dumpManager.registerNormalDumpable(TAG, this);
+    }
+
+    /**
+     * Register a callback whenever the given secure settings changes.
+     *
+     * On registration, will trigger the listener on the main thread with the current value of
+     * the setting.
+     */
+    @Main
+    public void addCallback(@NonNull Uri uri, @NonNull Listener listener) {
+        if (uri == null || listener == null) {
+            return;
+        }
+        synchronized (mListeners) {
+            ArrayList<Listener> currentListeners = mListeners.get(uri);
+            if (currentListeners == null) {
+                currentListeners = new ArrayList<>();
+            }
+            if (!currentListeners.contains(listener)) {
+                currentListeners.add(listener);
+            }
+            mListeners.put(uri, currentListeners);
+            if (currentListeners.size() == 1) {
+                mSecureSettings.registerContentObserverForUser(
+                        uri, false, mContentObserver, mUserTracker.getUserId());
+            }
+        }
+        mBackgroundHandler.post(() -> {
+            int userId = mUserTracker.getUserId();
+            String value = getCurrentSettingValue(uri, userId);
+            mMainHandler.post(() -> listener.onSettingChanged(uri, userId, value));
+        });
+
+    }
+
+    public void removeCallback(Uri uri, Listener listener) {
+        synchronized (mListeners) {
+            ArrayList<Listener> currentListeners = mListeners.get(uri);
+
+            if (currentListeners != null) {
+                currentListeners.remove(listener);
+            }
+            if (currentListeners == null || currentListeners.size() == 0) {
+                mListeners.remove(uri);
+            }
+
+            if (mListeners.size() == 0) {
+                mSecureSettings.unregisterContentObserver(mContentObserver);
+            }
+        }
+    }
+
+    @Override
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+        synchronized (mListeners) {
+            pw.println("Settings Uri Listener List:");
+            for (Uri uri : mListeners.keySet()) {
+                pw.println("   Uri=" + uri);
+                for (Listener listener : mListeners.get(uri)) {
+                    pw.println("      Listener=" + listener.getClass().getName());
+                }
+            }
+        }
+    }
+
+    private String getCurrentSettingValue(Uri uri, int userId) {
+        final String setting = uri == null ? null : uri.getLastPathSegment();
+        return mSecureSettings.getStringForUser(setting, userId);
+    }
+
+    /**
+     * Listener invoked whenever settings are changed.
+     */
+    public interface Listener {
+        @MainThread
+        void onSettingChanged(@NonNull Uri setting, int userId, @Nullable String value);
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt
new file mode 100644
index 0000000..57c82dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.RemoteViews
+import com.android.internal.widget.ImageFloatingTextView
+
+/** Precomputed version of [ImageFloatingTextView] */
+@RemoteViews.RemoteView
+class PrecomputedImageFloatingTextView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+    ImageFloatingTextView(context, attrs, defStyleAttr), TextPrecomputer {
+
+    override fun setTextAsync(text: CharSequence?): Runnable = precompute(this, text)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.kt
new file mode 100644
index 0000000..8508b1f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.RemoteViews
+import android.widget.TextView
+
+/**
+ * A user interface element that uses the PrecomputedText API to display text in a notification,
+ * with the help of RemoteViews.
+ */
+@RemoteViews.RemoteView
+class PrecomputedTextView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+    TextView(context, attrs, defStyleAttr), TextPrecomputer {
+
+    override fun setTextAsync(text: CharSequence?): Runnable = precompute(this, text)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt
new file mode 100644
index 0000000..b002330
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.TextView
+import com.android.internal.widget.ImageFloatingTextView
+import javax.inject.Inject
+
+class PrecomputedTextViewFactory @Inject constructor() : NotifRemoteViewsFactory {
+    override fun instantiate(
+        parent: View?,
+        name: String,
+        context: Context,
+        attrs: AttributeSet
+    ): View? {
+        return when (name) {
+            TextView::class.java.name,
+            TextView::class.java.simpleName -> PrecomputedTextView(context, attrs)
+            ImageFloatingTextView::class.java.name ->
+                PrecomputedImageFloatingTextView(context, attrs)
+            else -> null
+        }
+    }
+}
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 684a276..02627fd 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
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.NotificationLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.core.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/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index b24cec1..0c686be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -235,7 +235,7 @@
 
     @Override
     public long performRemoveAnimation(long duration, long delay,
-            float translationDirection, boolean isHeadsUpAnimation, float endLocation,
+            float translationDirection, boolean isHeadsUpAnimation,
             Runnable onFinishedRunnable,
             AnimatorListenerAdapter animationListener) {
         // TODO: Use duration
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.kt
new file mode 100644
index 0000000..49f4a33
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.text.PrecomputedText
+import android.text.Spannable
+import android.util.Log
+import android.widget.TextView
+
+internal interface TextPrecomputer {
+    /**
+     * Creates PrecomputedText from given text and returns a runnable which sets precomputed text to
+     * the textview on main thread.
+     *
+     * @param text text to be converted to PrecomputedText
+     * @return Runnable that sets precomputed text on the main thread
+     */
+    fun precompute(
+        textView: TextView,
+        text: CharSequence?,
+        logException: Boolean = true
+    ): Runnable {
+        val precomputedText: Spannable? =
+            text?.let { PrecomputedText.create(it, textView.textMetricsParams) }
+
+        return Runnable {
+            try {
+                textView.text = precomputedText
+            } catch (exception: IllegalArgumentException) {
+                if (logException) {
+                    Log.wtf(
+                        /* tag = */ TAG,
+                        /* msg = */ "PrecomputedText setText failed for TextView:$textView",
+                        /* tr = */ exception
+                    )
+                }
+                textView.text = text
+            }
+        }
+    }
+
+    private companion object {
+        private const val TAG = "TextPrecomputer"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
index 1a7417a..3588621 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
@@ -25,7 +25,6 @@
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 import dagger.Binds;
@@ -59,8 +58,6 @@
         Builder notificationEntry(NotificationEntry entry);
         @BindsInstance
         Builder onExpandClickListener(ExpandableNotificationRow.OnExpandClickListener presenter);
-        @BindsInstance
-        Builder listContainer(NotificationListContainer listContainer);
         ExpandableNotificationRowComponent build();
     }
 
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 ef5e86f06..87205e2 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
@@ -120,6 +120,11 @@
     }
 
     @Override
+    public int getClipHeight() {
+        return mView.getHeight();
+    }
+
+    @Override
     public void applyRoundnessAndInvalidate() {
         if (mRoundnessChangedListener != null) {
             // We cannot apply the rounded corner to this View, so our parents (in drawChild()) will
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
index 014406f..4b89615 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
@@ -17,26 +17,24 @@
 package com.android.systemui.statusbar.notification.shelf.domain.interactor
 
 import android.os.PowerManager
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.NotificationShelf
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
-import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 
 /** Interactor for the [NotificationShelf] */
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
 class NotificationShelfInteractor
 @Inject
 constructor(
     private val keyguardRepository: KeyguardRepository,
     private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
-    private val centralSurfaces: CentralSurfaces,
-    private val systemClock: SystemClock,
+    private val powerInteractor: PowerInteractor,
     private val keyguardTransitionController: LockscreenShadeTransitionController,
 ) {
     /** Is the shelf showing on the keyguard? */
@@ -55,11 +53,7 @@
 
     /** Transition keyguard to the locked shade, triggered by the shelf. */
     fun goToLockedShadeFromShelf() {
-        centralSurfaces.wakeUpIfDozing(
-            systemClock.uptimeMillis(),
-            "SHADE_CLICK",
-            PowerManager.WAKE_REASON_GESTURE,
-        )
+        powerInteractor.wakeUpIfDozing("SHADE_CLICK", PowerManager.WAKE_REASON_GESTURE)
         keyguardTransitionController.goToLockedShade(null)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index 12956ab..22a87a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -19,8 +19,8 @@
 import android.view.View
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
@@ -32,7 +32,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.phone.NotificationIconContainer
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
 import javax.inject.Inject
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.launch
@@ -43,7 +42,7 @@
  * [NotificationShelfController] interface. Once the [LegacyNotificationShelfControllerImpl] is
  * removed, this class can go away and the ViewBinder can be used directly.
  */
-@CentralSurfacesScope
+@SysUISingleton
 class NotificationShelfViewBinderWrapperControllerImpl @Inject constructor() :
     NotificationShelfController {
 
@@ -66,7 +65,7 @@
     override fun setOnClickListener(listener: View.OnClickListener) = unsupported
 
     private val unsupported: Nothing
-        get() = NotificationShelfController.throwIllegalFlagStateError(expected = true)
+        get() = error("Code path not supported when NOTIFICATION_SHELF_REFACTOR is disabled")
 }
 
 /** Binds a [NotificationShelf] to its [view model][NotificationShelfViewModel]. */
@@ -80,8 +79,6 @@
     ) {
         ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager)
         shelf.apply {
-            setRefactorFlagEnabled(true)
-            setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
             // TODO(278765923): Replace with eventual NotificationIconContainerViewBinder#bind()
             notificationIconAreaController.setShelfIcons(shelfIcons)
             repeatWhenAttached {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
index fb19443..5ca8b53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -16,16 +16,16 @@
 
 package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
 
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
 import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
 
 /** ViewModel for [NotificationShelf]. */
-@CentralSurfacesScope
+@SysUISingleton
 class NotificationShelfViewModel
 @Inject
 constructor(
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 b0f3f59..95e74f2 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
@@ -29,7 +29,6 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarState;
@@ -57,7 +56,6 @@
     private final SectionProvider mSectionProvider;
     private final BypassController mBypassController;
     private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
-    private final FeatureFlags mFeatureFlags;
     /**
      *  Used to read bouncer states.
      */
@@ -261,13 +259,12 @@
             @NonNull SectionProvider sectionProvider,
             @NonNull BypassController bypassController,
             @Nullable StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            @NonNull LargeScreenShadeInterpolator largeScreenShadeInterpolator,
-            @NonNull FeatureFlags featureFlags) {
+            @NonNull LargeScreenShadeInterpolator largeScreenShadeInterpolator
+    ) {
         mSectionProvider = sectionProvider;
         mBypassController = bypassController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
-        mFeatureFlags = featureFlags;
         reload(context);
         dumpManager.registerDumpable(this);
     }
@@ -753,10 +750,6 @@
         return mLargeScreenShadeInterpolator;
     }
 
-    public FeatureFlags getFeatureFlags() {
-        return mFeatureFlags;
-    }
-
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("mTopPadding=" + mTopPadding);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
index b8f28b5..a8d8a8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt
@@ -30,8 +30,8 @@
  */
 class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableView(context, attrs) {
 
+    override var clipHeight = 0
     var cornerRadius = 0f
-    var clipHeight = 0
     var clipRect = RectF()
     var clipPath = Path()
 
@@ -69,11 +69,14 @@
         canvas.clipPath(clipPath)
     }
 
-
-    override fun performRemoveAnimation(duration: Long, delay: Long, translationDirection: Float,
-                                        isHeadsUpAnimation: Boolean, endLocation: Float,
-                                        onFinishedRunnable: Runnable?,
-                                        animationListener: AnimatorListenerAdapter?): Long {
+    override fun performRemoveAnimation(
+            duration: Long,
+            delay: Long,
+            translationDirection: Float,
+            isHeadsUpAnimation: Boolean,
+            onFinishedRunnable: Runnable?,
+            animationListener: AnimatorListenerAdapter?
+    ): Long {
         return 0
     }
 
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 f8e374d..626f851 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
@@ -132,6 +132,8 @@
     private boolean mContainingNotificationIsFaded = false;
     private RoundableState mRoundableState;
 
+    private NotificationChildrenContainerLogger mLogger;
+
     public NotificationChildrenContainer(Context context) {
         this(context, null);
     }
@@ -190,6 +192,11 @@
     }
 
     @Override
+    public int getClipHeight() {
+        return Math.max(mActualHeight - mClipBottomAmount, 0);
+    }
+
+    @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         int childCount =
                 Math.min(mAttachedChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
@@ -1507,6 +1514,33 @@
         return mNotificationHeaderWrapper;
     }
 
+    public void setLogger(NotificationChildrenContainerLogger logger) {
+        mLogger = logger;
+    }
+
+    @Override
+    public void addTransientView(View view, int index) {
+        if (mLogger != null && view instanceof ExpandableNotificationRow) {
+            mLogger.addTransientRow(
+                    ((ExpandableNotificationRow) view).getEntry(),
+                    getContainingNotification().getEntry(),
+                    index
+            );
+        }
+        super.addTransientView(view, index);
+    }
+
+    @Override
+    public void removeTransientView(View view) {
+        if (mLogger != null && view instanceof ExpandableNotificationRow) {
+            mLogger.removeTransientRow(
+                    ((ExpandableNotificationRow) view).getEntry(),
+                    getContainingNotification().getEntry()
+            );
+        }
+        super.removeTransientView(view);
+    }
+
     public String debugString() {
         return TAG + " { "
                 + "visibility: " + getVisibility()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt
new file mode 100644
index 0000000..4986b63
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NotificationRenderLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import javax.inject.Inject
+
+class NotificationChildrenContainerLogger
+@Inject
+constructor(@NotificationRenderLog private val notificationRenderBuffer: LogBuffer) {
+    fun addTransientRow(
+        childEntry: NotificationEntry,
+        containerEntry: NotificationEntry,
+        index: Int
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                str1 = childEntry.logKey
+                str2 = containerEntry.logKey
+                int1 = index
+            },
+            { "addTransientRow: childKey: $str1 -- containerKey: $str2 -- index: $int1" }
+        )
+    }
+
+    fun removeTransientRow(
+        childEntry: NotificationEntry,
+        containerEntry: NotificationEntry,
+    ) {
+        notificationRenderBuffer.log(
+            TAG,
+            LogLevel.INFO,
+            {
+                str1 = childEntry.logKey
+                str2 = containerEntry.logKey
+            },
+            { "removeTransientRow: childKey: $str1 -- containerKey: $str2" }
+        )
+    }
+
+    companion object {
+        private const val TAG = "NotifChildrenContainer"
+    }
+}
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 f953187..2da5582 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
@@ -19,7 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.dagger.NotificationSectionLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 private const val TAG = "NotifSections"
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 df6fe3d..5e3a67e 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
@@ -89,9 +89,11 @@
 import com.android.systemui.R;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.ViewRefactorFlag;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
@@ -104,13 +106,13 @@
 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;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.FooterView;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -197,9 +199,9 @@
      */
     private Set<Integer> mDebugTextUsedYPositions;
     private final boolean mDebugRemoveAnimation;
-    private final boolean mSimplifiedAppearFraction;
     private final boolean mSensitiveRevealAnimEndabled;
-    private boolean mAnimatedInsets;
+    private final ViewRefactorFlag mAnimatedInsets;
+    private final ViewRefactorFlag mShelfRefactor;
 
     private int mContentHeight;
     private float mIntrinsicContentHeight;
@@ -316,7 +318,7 @@
         }
     };
     private NotificationStackScrollLogger mLogger;
-    private CentralSurfaces mCentralSurfaces;
+    private NotificationsController mNotificationsController;
     private ActivityStarter mActivityStarter;
     private final int[] mTempInt2 = new int[2];
     private boolean mGenerateChildOrderChangedEvent;
@@ -621,9 +623,10 @@
         FeatureFlags featureFlags = Dependency.get(FeatureFlags.class);
         mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
         mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
-        mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION);
         mSensitiveRevealAnimEndabled = featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
-        setAnimatedInsetsEnabled(featureFlags.isEnabled(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS));
+        mAnimatedInsets =
+                new ViewRefactorFlag(featureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
+        mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
         mSectionsManager = Dependency.get(NotificationSectionsManager.class);
         mScreenOffAnimationController =
                 Dependency.get(ScreenOffAnimationController.class);
@@ -662,7 +665,7 @@
         mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
         mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-        if (mAnimatedInsets) {
+        if (mAnimatedInsets.isEnabled()) {
             setWindowInsetsAnimationCallback(mInsetsCallback);
         }
     }
@@ -732,11 +735,6 @@
     }
 
     @VisibleForTesting
-    void setAnimatedInsetsEnabled(boolean enabled) {
-        mAnimatedInsets = enabled;
-    }
-
-    @VisibleForTesting
     public void updateFooter() {
         if (mFooterView == null) {
             return;
@@ -1638,14 +1636,6 @@
         return mAmbientState.getTrackedHeadsUpRow() != null;
     }
 
-    // TODO(b/246353296): remove it when Flags.SIMPLIFIED_APPEAR_FRACTION is removed
-    public float calculateAppearFractionOld(float height) {
-        float appearEndPosition = getAppearEndPosition();
-        float appearStartPosition = getAppearStartPosition();
-        return (height - appearStartPosition)
-                / (appearEndPosition - appearStartPosition);
-    }
-
     /**
      * @param height the height of the panel
      * @return Fraction of the appear animation that has been performed. Normally follows expansion
@@ -1653,33 +1643,24 @@
      * when HUN is swiped up.
      */
     @FloatRange(from = -1.0, to = 1.0)
-    public float simplifiedAppearFraction(float height) {
+    public float calculateAppearFraction(float height) {
         if (isHeadsUpTransition()) {
             // HUN is a special case because fraction can go negative if swiping up. And for now
             // it must go negative as other pieces responsible for proper translation up assume
             // negative value for HUN going up.
             // This can't use expansion fraction as that goes only from 0 to 1. Also when
             // appear fraction for HUN is 0, expansion fraction will be already around 0.2-0.3
-            // and that makes translation jump immediately. Let's use old implementation for now and
-            // see if we can figure out something better
-            return MathUtils.constrain(calculateAppearFractionOld(height), -1, 1);
+            // and that makes translation jump immediately.
+            float appearEndPosition = getAppearEndPosition();
+            float appearStartPosition = getAppearStartPosition();
+            float hunAppearFraction = (height - appearStartPosition)
+                    / (appearEndPosition - appearStartPosition);
+            return MathUtils.constrain(hunAppearFraction, -1, 1);
         } else {
             return mAmbientState.getExpansionFraction();
         }
     }
 
-    public float calculateAppearFraction(float height) {
-        if (mSimplifiedAppearFraction) {
-            return simplifiedAppearFraction(height);
-        } else if (mShouldUseSplitNotificationShade) {
-            // for split shade we want to always use the new way of calculating appear fraction
-            // because without it heads-up experience is very broken and it's less risky change
-            return simplifiedAppearFraction(height);
-        } else {
-            return calculateAppearFractionOld(height);
-        }
-    }
-
     public float getStackTranslation() {
         return mStackTranslation;
     }
@@ -1792,7 +1773,7 @@
             return;
         }
         mForcedScroll = v;
-        if (mAnimatedInsets) {
+        if (mAnimatedInsets.isEnabled()) {
             updateForcedScroll();
         } else {
             scrollTo(v);
@@ -1841,7 +1822,7 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        if (!mAnimatedInsets) {
+        if (!mAnimatedInsets.isEnabled()) {
             mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
         }
         mWaterfallTopInset = 0;
@@ -1849,11 +1830,11 @@
         if (cutout != null) {
             mWaterfallTopInset = cutout.getWaterfallInsets().top;
         }
-        if (mAnimatedInsets && !mIsInsetAnimationRunning) {
+        if (mAnimatedInsets.isEnabled() && !mIsInsetAnimationRunning) {
             // update bottom inset e.g. after rotation
             updateBottomInset(insets);
         }
-        if (!mAnimatedInsets) {
+        if (!mAnimatedInsets.isEnabled()) {
             int range = getScrollRange();
             if (mOwnScrollY > range) {
                 // HACK: We're repeatedly getting staggered insets here while the IME is
@@ -2733,7 +2714,7 @@
      * @param listener callback for notification removed
      */
     public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) {
-        NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags());
+        mShelfRefactor.assertDisabled();
         mOnNotificationRemovedListener = listener;
     }
 
@@ -2746,7 +2727,7 @@
         if (!mChildTransferInProgress) {
             onViewRemovedInternal(expandableView, this);
         }
-        if (mAmbientState.getFeatureFlags().isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+        if (mShelfRefactor.isEnabled()) {
             mShelf.requestRoundnessResetFor(expandableView);
         } else {
             if (mOnNotificationRemovedListener != null) {
@@ -2778,6 +2759,7 @@
         boolean animationGenerated = container != null && generateRemoveAnimation(child);
         if (animationGenerated) {
             if (!mSwipedOutViews.contains(child) || !isFullySwipedOut(child)) {
+                logAddTransientChild(child, container);
                 container.addTransientView(child, 0);
                 child.setTransientContainer(container);
             }
@@ -2793,6 +2775,46 @@
         focusNextViewIfFocused(child);
     }
 
+    private void logAddTransientChild(ExpandableView child, ViewGroup container) {
+        if (mLogger == null) {
+            return;
+        }
+        if (child instanceof ExpandableNotificationRow) {
+            if (container instanceof NotificationChildrenContainer) {
+                mLogger.addTransientChildNotificationToChildContainer(
+                        ((ExpandableNotificationRow) child).getEntry(),
+                        ((NotificationChildrenContainer) container)
+                                .getContainingNotification().getEntry()
+                );
+            } else if (container instanceof NotificationStackScrollLayout) {
+                mLogger.addTransientChildNotificationToNssl(
+                        ((ExpandableNotificationRow) child).getEntry()
+                );
+            } else {
+                mLogger.addTransientChildNotificationToViewGroup(
+                        ((ExpandableNotificationRow) child).getEntry(),
+                        container
+                );
+            }
+        }
+    }
+
+    @Override
+    public void addTransientView(View view, int index) {
+        if (mLogger != null && view instanceof ExpandableNotificationRow) {
+            mLogger.addTransientRow(((ExpandableNotificationRow) view).getEntry(), index);
+        }
+        super.addTransientView(view, index);
+    }
+
+    @Override
+    public void removeTransientView(View view) {
+        if (mLogger != null && view instanceof ExpandableNotificationRow) {
+            mLogger.removeTransientRow(((ExpandableNotificationRow) view).getEntry());
+        }
+        super.removeTransientView(view);
+    }
+
     /**
      * Has this view been fully swiped out such that it's not visible anymore.
      */
@@ -3052,7 +3074,9 @@
         if (!animationsEnabled) {
             mSwipedOutViews.clear();
             mChildrenToRemoveAnimated.clear();
-            clearTemporaryViewsInGroup(this);
+            clearTemporaryViewsInGroup(
+                    /* viewGroup = */ this,
+                    /* reason = */ "setAnimationsEnabled");
         }
     }
 
@@ -3457,6 +3481,11 @@
         return super.onTouchEvent(ev);
     }
 
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+    }
+
     void dispatchDownEventToScroller(MotionEvent ev) {
         MotionEvent downEvent = MotionEvent.obtain(ev);
         downEvent.setAction(MotionEvent.ACTION_DOWN);
@@ -3734,20 +3763,20 @@
             case MotionEvent.ACTION_UP:
                 if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
                         isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
-                    debugLog("handleEmptySpaceClick: touch event propagated further");
+                    debugShadeLog("handleEmptySpaceClick: touch event propagated further");
                     mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
                 }
                 break;
             default:
-                debugLog("handleEmptySpaceClick: MotionEvent ignored");
+                debugShadeLog("handleEmptySpaceClick: MotionEvent ignored");
         }
     }
 
-    private void debugLog(@CompileTimeConstant final String s) {
+    private void debugShadeLog(@CompileTimeConstant final String s) {
         if (mLogger == null) {
             return;
         }
-        mLogger.d(s);
+        mLogger.logShadeDebugEvent(s);
     }
 
     private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification,
@@ -4008,7 +4037,7 @@
         mAmbientState.setExpansionChanging(false);
         if (!mIsExpanded) {
             resetScrollPosition();
-            mCentralSurfaces.resetUserExpandedStates();
+            mNotificationsController.resetUserExpandedStates();
             clearTemporaryViews();
             clearUserLockedViews();
             resetAllSwipeState();
@@ -4027,26 +4056,48 @@
 
     private void clearTemporaryViews() {
         // lets make sure nothing is transient anymore
-        clearTemporaryViewsInGroup(this);
+        clearTemporaryViewsInGroup(
+                /* viewGroup = */ this,
+                /* reason = */ "clearTemporaryViews"
+        );
         for (int i = 0; i < getChildCount(); i++) {
             ExpandableView child = getChildAtIndex(i);
             if (child instanceof ExpandableNotificationRow) {
                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-                clearTemporaryViewsInGroup(row.getChildrenContainer());
+                clearTemporaryViewsInGroup(
+                        /* viewGroup = */ row.getChildrenContainer(),
+                        /* reason = */ "clearTemporaryViewsInGroup(row.getChildrenContainer())"
+                );
             }
         }
     }
 
-    private void clearTemporaryViewsInGroup(ViewGroup viewGroup) {
+    private void clearTemporaryViewsInGroup(ViewGroup viewGroup, String reason) {
         while (viewGroup != null && viewGroup.getTransientViewCount() != 0) {
             final View transientView = viewGroup.getTransientView(0);
             viewGroup.removeTransientView(transientView);
             if (transientView instanceof ExpandableView) {
                 ((ExpandableView) transientView).setTransientContainer(null);
+                if (transientView instanceof ExpandableNotificationRow) {
+                    logTransientNotificationRowTraversalCleaned(
+                            (ExpandableNotificationRow) transientView,
+                            reason
+                    );
+                }
             }
         }
     }
 
+    private void logTransientNotificationRowTraversalCleaned(
+            ExpandableNotificationRow transientView,
+            String reason
+    ) {
+        if (mLogger == null) {
+            return;
+        }
+        mLogger.transientNotificationRowTraversalCleaned(transientView.getEntry(), reason);
+    }
+
     void onPanelTrackingStarted() {
         mPanelTracking = true;
         mAmbientState.setPanelTracking(true);
@@ -4602,8 +4653,8 @@
         return max + getStackTranslation();
     }
 
-    public void setCentralSurfaces(CentralSurfaces centralSurfaces) {
-        this.mCentralSurfaces = centralSurfaces;
+    public void setNotificationsController(NotificationsController notificationsController) {
+        this.mNotificationsController = notificationsController;
     }
 
     public void setActivityStarter(ActivityStarter activityStarter) {
@@ -4897,18 +4948,12 @@
 
     @Nullable
     public ExpandableView getShelf() {
-        if (NotificationShelfController.checkRefactorFlagEnabled(mAmbientState.getFeatureFlags())) {
-            return mShelf;
-        } else {
-            return null;
-        }
+        if (!mShelfRefactor.expectEnabled()) return null;
+        return mShelf;
     }
 
     public void setShelf(NotificationShelf shelf) {
-        if (!NotificationShelfController.checkRefactorFlagEnabled(
-                mAmbientState.getFeatureFlags())) {
-            return;
-        }
+        if (!mShelfRefactor.expectEnabled()) return;
         int index = -1;
         if (mShelf != null) {
             index = indexOfChild(mShelf);
@@ -4922,7 +4967,7 @@
     }
 
     public void setShelfController(NotificationShelfController notificationShelfController) {
-        NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags());
+        mShelfRefactor.assertDisabled();
         int index = -1;
         if (mShelf != null) {
             index = indexOfChild(mShelf);
@@ -5070,6 +5115,9 @@
             println(pw, "intrinsicPadding", mIntrinsicPadding);
             println(pw, "topPadding", mTopPadding);
             println(pw, "bottomPadding", mBottomPadding);
+            println(pw, "translationX", getTranslationX());
+            println(pw, "translationY", getTranslationY());
+            println(pw, "translationZ", getTranslationZ());
             mNotificationStackSizeCalculator.dump(pw, args);
         });
         pw.println();
@@ -5870,7 +5918,17 @@
         Trace.beginSection("NSSL.resetAllSwipeState()");
         mSwipeHelper.resetTouchState();
         for (int i = 0; i < getChildCount(); i++) {
-            mSwipeHelper.forceResetSwipeState(getChildAt(i));
+            View child = getChildAt(i);
+            mSwipeHelper.forceResetSwipeState(child);
+            if (child instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow childRow = (ExpandableNotificationRow) child;
+                List<ExpandableNotificationRow> grandchildren = childRow.getAttachedChildren();
+                if (grandchildren != null) {
+                    for (ExpandableNotificationRow grandchild : grandchildren) {
+                        mSwipeHelper.forceResetSwipeState(grandchild);
+                    }
+                }
+            }
         }
         updateContinuousShadowDrawing();
         Trace.endSection();
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 11ba753..4668aa4 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
@@ -29,6 +29,7 @@
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.SelectedRows;
 import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -56,11 +57,19 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.Gefingerpoken;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.classifier.Classifier;
 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.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.ViewRefactorFlag;
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
@@ -69,6 +78,7 @@
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -98,6 +108,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 import com.android.systemui.statusbar.notification.dagger.SilentHeader;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -107,14 +118,12 @@
 import com.android.systemui.statusbar.notification.row.NotificationSnooze;
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -137,13 +146,14 @@
 /**
  * Controller for {@link NotificationStackScrollLayout}.
  */
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
 public class NotificationStackScrollLayoutController {
     private static final String TAG = "StackScrollerController";
     private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
 
     private final boolean mAllowLongPress;
     private final NotificationGutsManager mNotificationGutsManager;
+    private final NotificationsController mNotificationsController;
     private final NotificationVisibilityProvider mVisibilityProvider;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final NotificationRoundnessManager mNotificationRoundnessManager;
@@ -170,9 +180,9 @@
     private final KeyguardMediaController mKeyguardMediaController;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final KeyguardBypassController mKeyguardBypassController;
+    private final KeyguardInteractor mKeyguardInteractor;
+    private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
-    // TODO: CentralSurfaces should be encapsulated behind a Controller
-    private final CentralSurfaces mCentralSurfaces;
     private final SectionHeaderController mSilentHeaderController;
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     private final InteractionJankMonitor mJankMonitor;
@@ -184,14 +194,19 @@
     private final GroupExpansionManager mGroupExpansionManager;
     private final NotifPipelineFlags mNotifPipelineFlags;
     private final SeenNotificationsProvider mSeenNotificationsProvider;
+    private final KeyguardTransitionRepository mKeyguardTransitionRepo;
 
     private NotificationStackScrollLayout mView;
     private NotificationSwipeHelper mSwipeHelper;
     @Nullable
     private Boolean mHistoryEnabled;
     private int mBarState;
+    private boolean mIsBouncerShowingFromCentralSurfaces;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
+    private boolean mIsInTransitionToAod = false;
+
     private final FeatureFlags mFeatureFlags;
+    private final ViewRefactorFlag mShelfRefactor;
     private final NotificationTargetsHelper mNotificationTargetsHelper;
     private final SecureSettings mSecureSettings;
     private final NotificationDismissibilityProvider mDismissibilityProvider;
@@ -430,7 +445,7 @@
                 @Override
                 public void onSnooze(StatusBarNotification sbn,
                                      NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
-                    mCentralSurfaces.setNotificationSnoozed(sbn, snoozeOption);
+                    mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
                 }
 
                 @Override
@@ -561,7 +576,8 @@
 
                 @Override
                 public float getFalsingThresholdFactor() {
-                    return mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+                    return ShadeViewController.getFalsingThresholdFactor(
+                            mKeyguardInteractor.getWakefulnessModel().getValue());
                 }
 
                 @Override
@@ -615,6 +631,7 @@
             NotificationStackScrollLayout view,
             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
             NotificationGutsManager notificationGutsManager,
+            NotificationsController notificationsController,
             NotificationVisibilityProvider visibilityProvider,
             HeadsUpManagerPhone headsUpManager,
             NotificationRoundnessManager notificationRoundnessManager,
@@ -625,6 +642,9 @@
             SysuiStatusBarStateController statusBarStateController,
             KeyguardMediaController keyguardMediaController,
             KeyguardBypassController keyguardBypassController,
+            KeyguardInteractor keyguardInteractor,
+            PrimaryBouncerInteractor primaryBouncerInteractor,
+            KeyguardTransitionRepository keyguardTransitionRepo,
             ZenModeController zenModeController,
             NotificationLockscreenUserManager lockscreenUserManager,
             Optional<NotificationListViewModel> nsslViewModel,
@@ -634,7 +654,6 @@
             FalsingManager falsingManager,
             @Main Resources resources,
             NotificationSwipeHelper.Builder notificationSwipeHelperBuilder,
-            CentralSurfaces centralSurfaces,
             ScrimController scrimController,
             GroupExpansionManager groupManager,
             @SilentHeader SectionHeaderController silentHeaderController,
@@ -658,10 +677,12 @@
             NotificationDismissibilityProvider dismissibilityProvider,
             ActivityStarter activityStarter) {
         mView = view;
+        mKeyguardTransitionRepo = keyguardTransitionRepo;
         mStackStateLogger = stackLogger;
         mLogger = logger;
         mAllowLongPress = allowLongPress;
         mNotificationGutsManager = notificationGutsManager;
+        mNotificationsController = notificationsController;
         mVisibilityProvider = visibilityProvider;
         mHeadsUpManager = headsUpManager;
         mNotificationRoundnessManager = notificationRoundnessManager;
@@ -672,6 +693,8 @@
         mStatusBarStateController = statusBarStateController;
         mKeyguardMediaController = keyguardMediaController;
         mKeyguardBypassController = keyguardBypassController;
+        mKeyguardInteractor = keyguardInteractor;
+        mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mZenModeController = zenModeController;
         mLockscreenUserManager = lockscreenUserManager;
         mViewModel = nsslViewModel;
@@ -682,7 +705,6 @@
         mFalsingManager = falsingManager;
         mResources = resources;
         mNotificationSwipeHelperBuilder = notificationSwipeHelperBuilder;
-        mCentralSurfaces = centralSurfaces;
         mScrimController = scrimController;
         mJankMonitor = jankMonitor;
         mNotificationStackSizeCalculator = notificationStackSizeCalculator;
@@ -698,6 +720,7 @@
         mShadeController = shadeController;
         mNotifIconAreaController = notifIconAreaController;
         mFeatureFlags = featureFlags;
+        mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
         mNotificationTargetsHelper = notificationTargetsHelper;
         mSecureSettings = secureSettings;
         mDismissibilityProvider = dismissibilityProvider;
@@ -711,7 +734,7 @@
         mView.setController(this);
         mView.setLogger(mLogger);
         mView.setTouchHandler(new TouchHandler());
-        mView.setCentralSurfaces(mCentralSurfaces);
+        mView.setNotificationsController(mNotificationsController);
         mView.setActivityStarter(mActivityStarter);
         mView.setClearAllAnimationListener(this::onAnimationEnd);
         mView.setClearAllListener((selection) -> mUiEventLogger.log(
@@ -810,6 +833,9 @@
         mViewModel.ifPresent(
                 vm -> NotificationListViewBinder
                         .bind(mView, vm, mFalsingManager, mFeatureFlags, mNotifIconAreaController));
+
+        collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
+                this::onKeyguardTransitionChanged);
     }
 
     private boolean isInVisibleLocation(NotificationEntry entry) {
@@ -1198,6 +1224,14 @@
     }
 
     /**
+     * Sets whether the bouncer is currently showing. Should only be called from
+     * {@link CentralSurfaces}.
+     */
+    public void setBouncerShowingFromCentralSurfaces(boolean bouncerShowing) {
+        mIsBouncerShowingFromCentralSurfaces = bouncerShowing;
+    }
+
+    /**
      * Set the visibility of the view, and propagate it to specific children.
      *
      * @param visible either the view is visible or not.
@@ -1224,11 +1258,12 @@
 
         final boolean shouldShow = getVisibleNotificationCount() == 0
                 && !mView.isQsFullScreen()
-                // Hide empty shade view when in transition to Keyguard.
+                // Hide empty shade view when in transition to AOD.
                 // That avoids "No Notifications" to blink when transitioning to AOD.
                 // For more details, see: b/228790482
-                && !isInTransitionToKeyguard()
-                && !mCentralSurfaces.isBouncerShowing();
+                && !mIsInTransitionToAod
+                // Don't show any notification content if the bouncer is showing. See b/267060171.
+                && !isBouncerShowing();
 
         mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
 
@@ -1236,6 +1271,24 @@
     }
 
     /**
+     * Returns whether the bouncer is currently showing.
+     *
+     * There's a possible timing difference between when CentralSurfaces marks the bouncer as not
+     * showing and when PrimaryBouncerInteractor marks the bouncer as not showing. (CentralSurfaces
+     * appears to mark the bouncer as showing for 10-200ms longer than PrimaryBouncerInteractor.)
+     *
+     * This timing difference could be load bearing, which is why we have a feature flag protecting
+     * where we fetch the value from. This flag is intended to be short-lived.
+     */
+    private boolean isBouncerShowing() {
+        if (mFeatureFlags.isEnabled(Flags.USE_REPOS_FOR_BOUNCER_SHOWING)) {
+            return mPrimaryBouncerInteractor.isBouncerShowing();
+        } else {
+            return mIsBouncerShowingFromCentralSurfaces;
+        }
+    }
+
+    /**
      * Update the importantForAccessibility of NotificationStackScrollLayout.
      * <p>
      * We want the NSSL to be unimportant for accessibility when there's no
@@ -1382,7 +1435,7 @@
     }
 
     public void setShelfController(NotificationShelfController notificationShelfController) {
-        NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags);
+        mShelfRefactor.assertDisabled();
         mView.setShelfController(notificationShelfController);
     }
 
@@ -1426,7 +1479,7 @@
         return mNotificationRoundnessManager;
     }
 
-    NotificationListContainer getNotificationListContainer() {
+    public NotificationListContainer getNotificationListContainer() {
         return mNotificationListContainer;
     }
 
@@ -1595,18 +1648,28 @@
     }
 
     public void setShelf(NotificationShelf shelf) {
-        if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) return;
+        if (!mShelfRefactor.expectEnabled()) return;
         mView.setShelf(shelf);
     }
 
     public int getShelfHeight() {
-        if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) {
+        if (!mShelfRefactor.expectEnabled()) {
             return 0;
         }
         ExpandableView shelf = mView.getShelf();
         return shelf == null ? 0 : shelf.getIntrinsicHeight();
     }
 
+    @VisibleForTesting
+    void onKeyguardTransitionChanged(TransitionStep transitionStep) {
+        boolean isTransitionToAod = transitionStep.getFrom().equals(KeyguardState.GONE)
+                && transitionStep.getTo().equals(KeyguardState.AOD);
+        if (mIsInTransitionToAod != isTransitionToAod) {
+            mIsInTransitionToAod = isTransitionToAod;
+            updateShowEmptyShadeView();
+        }
+    }
+
     /**
      * Enum for UiEvent logged from this class
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutListContainerModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutListContainerModule.java
deleted file mode 100644
index 3dcaae2..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutListContainerModule.java
+++ /dev/null
@@ -1,32 +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.statusbar.notification.stack;
-
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
-
-import dagger.Module;
-import dagger.Provides;
-
-@Module
-public abstract class NotificationStackScrollLayoutListContainerModule {
-    @Provides
-    @CentralSurfacesComponent.CentralSurfacesScope
-    static NotificationListContainer provideListContainer(
-            NotificationStackScrollLayoutController nsslController) {
-        return nsslController.getNotificationListContainer();
-    }
-}
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 5b0ec1d..3396306 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,9 +1,13 @@
 package com.android.systemui.statusbar.notification.stack
 
-import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import android.view.ViewGroup
 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.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.log.dagger.NotificationRenderLog
+import com.android.systemui.log.dagger.ShadeLog
 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
@@ -15,7 +19,9 @@
 import javax.inject.Inject
 
 class NotificationStackScrollLogger @Inject constructor(
-    @NotificationHeadsUpLog private val buffer: LogBuffer
+    @NotificationHeadsUpLog private val buffer: LogBuffer,
+    @NotificationRenderLog private val notificationRenderBuffer: LogBuffer,
+    @ShadeLog private val shadeLogBuffer: LogBuffer,
 ) {
     fun hunAnimationSkipped(entry: NotificationEntry, reason: String) {
         buffer.log(TAG, INFO, {
@@ -59,7 +65,7 @@
         })
     }
 
-    fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
+    fun logShadeDebugEvent(@CompileTimeConstant msg: String) = shadeLogBuffer.log(TAG, DEBUG, msg)
 
     fun logEmptySpaceClick(
         isBelowLastNotification: Boolean,
@@ -67,7 +73,7 @@
         touchIsClick: Boolean,
         motionEventDesc: String
     ) {
-        buffer.log(TAG, DEBUG, {
+        shadeLogBuffer.log(TAG, DEBUG, {
             int1 = statusBarState
             bool1 = touchIsClick
             bool2 = isBelowLastNotification
@@ -77,6 +83,79 @@
                     "isTouchBelowNotification: $bool2 motionEvent: $str1"
         })
     }
+
+    fun transientNotificationRowTraversalCleaned(entry: NotificationEntry, reason: String) {
+        notificationRenderBuffer.log(TAG, INFO, {
+            str1 = entry.logKey
+            str2 = reason
+        }, {
+            "transientNotificationRowTraversalCleaned: key: $str1 reason: $str2"
+        })
+    }
+
+    fun addTransientChildNotificationToChildContainer(
+            childEntry: NotificationEntry,
+            containerEntry: NotificationEntry,
+    ) {
+        notificationRenderBuffer.log(TAG, INFO, {
+            str1 = childEntry.logKey
+            str2 = containerEntry.logKey
+        }, {
+            "addTransientChildToContainer from onViewRemovedInternal: childKey: $str1 " +
+                    "-- containerKey: $str2"
+        })
+    }
+
+    fun addTransientChildNotificationToNssl(
+            childEntry: NotificationEntry,
+    ) {
+        notificationRenderBuffer.log(TAG, INFO, {
+            str1 = childEntry.logKey
+        }, {
+            "addTransientRowToNssl from onViewRemovedInternal: childKey: $str1"
+        })
+    }
+
+    fun addTransientChildNotificationToViewGroup(
+            childEntry: NotificationEntry,
+            container: ViewGroup
+    ) {
+        notificationRenderBuffer.log(TAG, ERROR, {
+            str1 = childEntry.logKey
+            str2 = container.toString()
+        }, {
+            "addTransientRowTo unhandled ViewGroup from onViewRemovedInternal: childKey: $str1 " +
+                    "-- ViewGroup: $str2"
+        })
+    }
+
+    fun addTransientRow(
+            childEntry: NotificationEntry,
+            index: Int
+    ) {
+        notificationRenderBuffer.log(
+                TAG,
+                INFO,
+                {
+                    str1 = childEntry.logKey
+                    int1 = index
+                },
+                { "addTransientRow to NSSL: childKey: $str1 -- index: $int1" }
+        )
+    }
+
+    fun removeTransientRow(
+            childEntry: NotificationEntry,
+    ) {
+        notificationRenderBuffer.log(
+                TAG,
+                INFO,
+                {
+                    str1 = childEntry.logKey
+                },
+                { "removeTransientRow from NSSL: childKey: $str1" }
+        )
+    }
 }
 
 private const val TAG = "NotificationStackScroll"
\ No newline at end of file
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 efb7926..b2d26d9 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
@@ -29,8 +29,6 @@
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.R;
 import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
@@ -189,9 +187,7 @@
 
     private float interpolateFooterAlpha(AmbientState ambientState) {
         float expansion = ambientState.getExpansionFraction();
-        FeatureFlags flags = ambientState.getFeatureFlags();
-        if (ambientState.isSmallScreen()
-                || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+        if (ambientState.isSmallScreen()) {
             return ShadeInterpolation.getContentAlpha(expansion);
         }
         LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
@@ -200,9 +196,7 @@
 
     private float interpolateNotificationContentAlpha(AmbientState ambientState) {
         float expansion = ambientState.getExpansionFraction();
-        FeatureFlags flags = ambientState.getFeatureFlags();
-        if (ambientState.isSmallScreen()
-                || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+        if (ambientState.isSmallScreen()) {
             return ShadeInterpolation.getContentAlpha(expansion);
         }
         LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
@@ -772,8 +766,9 @@
             boolean isTopEntry = topHeadsUpEntry == row;
             float unmodifiedEndLocation = childState.getYTranslation() + childState.height;
             if (mIsExpanded) {
-                if (row.mustStayOnScreen() && !childState.headsUpIsVisible
-                        && !row.showingPulsing() && !ambientState.isOnKeyguard()) {
+                if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(),
+                        childState.headsUpIsVisible, row.showingPulsing(),
+                        ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) {
                     // Ensure that the heads up is always visible even when scrolled off
                     clampHunToTop(mQuickQsOffsetHeight, ambientState.getStackTranslation(),
                             row.getCollapsedHeight(), childState);
@@ -821,7 +816,15 @@
         }
     }
 
-    /**
+    @VisibleForTesting
+    boolean shouldHunBeVisibleWhenScrolled(boolean mustStayOnScreen, boolean headsUpIsVisible,
+            boolean showingPulsing, boolean isOnKeyguard, boolean headsUpOnKeyguard) {
+        return mustStayOnScreen && !headsUpIsVisible
+                        && !showingPulsing
+                        && (!isOnKeyguard || headsUpOnKeyguard);
+    }
+
+     /**
      * When shade is open and we are scrolled to the bottom of notifications,
      * clamp incoming HUN in its collapsed form, right below qs offset.
      * Transition pinned collapsed HUN to full height when scrolling back up.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index f07dd00..2742a23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -27,8 +27,6 @@
 import com.android.systemui.R;
 import com.android.systemui.shared.clocks.AnimatableClockView;
 import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
@@ -347,10 +345,12 @@
             final ExpandableView changingView = (ExpandableView) event.mChangingView;
             boolean loggable = false;
             boolean isHeadsUp = false;
+            boolean isGroupChild = false;
             String key = null;
             if (changingView instanceof ExpandableNotificationRow && mLogger != null) {
                 loggable = true;
                 isHeadsUp = ((ExpandableNotificationRow) changingView).isHeadsUp();
+                isGroupChild = changingView.isChildInGroup();
                 key = ((ExpandableNotificationRow) changingView).getEntry().getKey();
             }
             if (event.animationType ==
@@ -407,17 +407,25 @@
 
                 }
                 Runnable postAnimation = changingView::removeFromTransientContainer;
-                if (loggable && isHeadsUp) {
-                    mLogger.logHUNViewDisappearingWithRemoveEvent(key);
+                if (loggable) {
                     String finalKey = key;
-                    postAnimation = () -> {
-                        mLogger.disappearAnimationEnded(finalKey);
-                        changingView.removeFromTransientContainer();
-                    };
+                    if (isHeadsUp) {
+                        mLogger.logHUNViewDisappearingWithRemoveEvent(key);
+                        postAnimation = () -> {
+                            mLogger.disappearAnimationEnded(finalKey);
+                            changingView.removeFromTransientContainer();
+                        };
+                    } else if (isGroupChild) {
+                        mLogger.groupChildRemovalEventProcessed(key);
+                        postAnimation = () -> {
+                            mLogger.groupChildRemovalAnimationEnded(finalKey);
+                            changingView.removeFromTransientContainer();
+                        };
+                    }
                 }
                 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
                         0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
-                        0, postAnimation, null);
+                        postAnimation, null);
             } else if (event.animationType ==
                 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
                 if (mHostLayout.isFullySwipedOut(changingView)) {
@@ -464,28 +472,12 @@
                     mTmpState.initFrom(changingView);
                     endRunnable = changingView::removeFromTransientContainer;
                 }
-                float targetLocation = 0;
                 boolean needsAnimation = true;
                 if (changingView instanceof ExpandableNotificationRow) {
                     ExpandableNotificationRow row = (ExpandableNotificationRow) changingView;
                     if (row.isDismissed()) {
                         needsAnimation = false;
                     }
-
-                    NotificationEntry entry = row.getEntry();
-                    StatusBarIconView icon = entry.getIcons().getStatusBarIcon();
-                    final StatusBarIconView centeredIcon = entry.getIcons().getCenteredIcon();
-                    if (centeredIcon != null && centeredIcon.getParent() != null) {
-                        icon = centeredIcon;
-                    }
-                    if (icon.getParent() != null) {
-                        icon.getLocationOnScreen(mTmpLocation);
-                        float iconPosition = mTmpLocation[0] - icon.getTranslationX()
-                                + ViewState.getFinalTranslationX(icon)
-                                + icon.getWidth() * 0.25f;
-                        mHostLayout.getLocationOnScreen(mTmpLocation);
-                        targetLocation = iconPosition - mTmpLocation[0];
-                    }
                 }
 
                 if (needsAnimation) {
@@ -505,7 +497,7 @@
                     }
                     long removeAnimationDelay = changingView.performRemoveAnimation(
                             ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
-                            0, 0.0f, true /* isHeadsUpAppear */, targetLocation,
+                            0, 0.0f, true /* isHeadsUpAppear */,
                             postAnimation, getGlobalAnimationFinishedListener());
                     mAnimationProperties.delay += removeAnimationDelay;
                 } else if (endRunnable != null) {
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 cca84b3..0b2c486 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,13 +1,15 @@
 package com.android.systemui.statusbar.notification.stack
 
-import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.log.dagger.NotificationRenderLog
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
 
 class StackStateLogger @Inject constructor(
-    @NotificationHeadsUpLog private val buffer: LogBuffer
+    @NotificationHeadsUpLog private val buffer: LogBuffer,
+    @NotificationRenderLog private val notificationRenderBuffer: LogBuffer
 ) {
     fun logHUNViewDisappearing(key: String) {
         buffer.log(TAG, LogLevel.INFO, {
@@ -56,6 +58,21 @@
             "Heads up notification appear animation ended $str1 "
         })
     }
+
+    fun groupChildRemovalEventProcessed(key: String) {
+        notificationRenderBuffer.log(TAG, LogLevel.DEBUG, {
+            str1 = logKey(key)
+        }, {
+            "Group Child Notification removal event processed $str1 for ANIMATION_TYPE_REMOVE"
+        })
+    }
+    fun groupChildRemovalAnimationEnded(key: String) {
+        notificationRenderBuffer.log(TAG, LogLevel.INFO, {
+            str1 = logKey(key)
+        }, {
+            "Group child notification removal animation ended $str1 "
+        })
+    }
 }
 
 private const val TAG = "StackScroll"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
new file mode 100644
index 0000000..874450b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.stack.domain.interactor
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Encapsulates business-logic specifically related to the shared notification stack container. */
+class SharedNotificationContainerInteractor
+@Inject
+constructor(
+    configurationRepository: ConfigurationRepository,
+    private val context: Context,
+) {
+    val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
+        configurationRepository.onAnyConfigurationChange
+            .onStart { emit(Unit) }
+            .map { _ ->
+                with(context.resources) {
+                    ConfigurationBasedDimensions(
+                        useSplitShade = getBoolean(R.bool.config_use_split_notification_shade),
+                        useLargeScreenHeader =
+                            getBoolean(R.bool.config_use_large_screen_shade_header),
+                        marginHorizontal =
+                            getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal),
+                        marginBottom =
+                            getDimensionPixelSize(R.dimen.notification_panel_margin_bottom),
+                        marginTop = getDimensionPixelSize(R.dimen.notification_panel_margin_top),
+                        marginTopLargeScreen =
+                            getDimensionPixelSize(R.dimen.large_screen_shade_header_height),
+                    )
+                }
+            }
+            .distinctUntilChanged()
+
+    data class ConfigurationBasedDimensions(
+        val useSplitShade: Boolean,
+        val useLargeScreenHeader: Boolean,
+        val marginHorizontal: Int,
+        val marginBottom: Int,
+        val marginTop: Int,
+        val marginTopLargeScreen: Int,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
new file mode 100644
index 0000000..688843d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.VERTICAL
+import com.android.systemui.R
+
+/**
+ * Container for the stack scroller, so that the bounds can be externally specified, such as from
+ * the keyguard or shade scenes.
+ */
+class SharedNotificationContainer(
+    context: Context,
+    private val attrs: AttributeSet?,
+) :
+    ConstraintLayout(
+        context,
+        attrs,
+    ) {
+
+    private val baseConstraintSet = ConstraintSet()
+
+    init {
+        baseConstraintSet.apply {
+            create(R.id.nssl_guideline, VERTICAL)
+            setGuidelinePercent(R.id.nssl_guideline, 0.5f)
+        }
+        baseConstraintSet.applyTo(this)
+    }
+
+    fun addNotificationStackScrollLayout(nssl: View) {
+        addView(nssl)
+    }
+
+    fun updateConstraints(
+        useSplitShade: Boolean,
+        marginStart: Int,
+        marginTop: Int,
+        marginEnd: Int,
+        marginBottom: Int
+    ) {
+        val constraintSet = ConstraintSet()
+        constraintSet.clone(baseConstraintSet)
+
+        val startConstraintId =
+            if (useSplitShade) {
+                R.id.nssl_guideline
+            } else {
+                PARENT_ID
+            }
+        val nsslId = R.id.notification_stack_scroller
+        constraintSet.apply {
+            connect(nsslId, START, startConstraintId, START)
+            connect(nsslId, END, PARENT_ID, END)
+            connect(nsslId, BOTTOM, PARENT_ID, BOTTOM)
+            connect(nsslId, TOP, PARENT_ID, TOP)
+            setMargin(nsslId, START, marginStart)
+            setMargin(nsslId, END, marginEnd)
+            setMargin(nsslId, TOP, marginTop)
+            setMargin(nsslId, BOTTOM, marginBottom)
+        }
+        constraintSet.applyTo(this)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
new file mode 100644
index 0000000..fb1d55d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import kotlinx.coroutines.launch
+
+/** Binds the shared notification container to its view-model. */
+object SharedNotificationContainerBinder {
+
+    @JvmStatic
+    fun bind(
+        view: SharedNotificationContainer,
+        viewModel: SharedNotificationContainerViewModel,
+    ) {
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.configurationBasedDimensions.collect {
+                        view.updateConstraints(
+                            useSplitShade = it.useSplitShade,
+                            marginStart = it.marginStart,
+                            marginTop = it.marginTop,
+                            marginEnd = it.marginEnd,
+                            marginBottom = it.marginBottom,
+                        )
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index aab1c2b..11f68e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
@@ -33,6 +34,7 @@
 object NotificationListViewModelModule {
     @JvmStatic
     @Provides
+    @SysUISingleton
     fun maybeProvideViewModel(
         featureFlags: FeatureFlags,
         shelfViewModel: Provider<NotificationShelfViewModel>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
new file mode 100644
index 0000000..b2e5ac1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** View-model for the shared notification container */
+class SharedNotificationContainerViewModel
+@Inject
+constructor(
+    interactor: SharedNotificationContainerInteractor,
+) {
+    val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
+        interactor.configurationBasedDimensions
+            .map {
+                ConfigurationBasedDimensions(
+                    marginStart = if (it.useSplitShade) 0 else it.marginHorizontal,
+                    marginEnd = it.marginHorizontal,
+                    marginBottom = it.marginBottom,
+                    marginTop =
+                        if (it.useLargeScreenHeader) it.marginTopLargeScreen else it.marginTop,
+                    useSplitShade = it.useSplitShade,
+                )
+            }
+            .distinctUntilChanged()
+
+    data class ConfigurationBasedDimensions(
+        val marginStart: Int,
+        val marginTop: Int,
+        val marginEnd: Int,
+        val marginBottom: Int,
+        val useSplitShade: Boolean,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 1827a46..baeae79 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.assist.AssistManager
 import com.android.systemui.camera.CameraIntents.Companion.isInsecureCameraIntent
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.DisplayId
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -44,7 +45,9 @@
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -67,9 +70,12 @@
     private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
     private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
     private val shadeControllerLazy: Lazy<ShadeController>,
+    private val shadeViewControllerLazy: Lazy<ShadeViewController>,
     private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>,
+    private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
     private val activityLaunchAnimator: ActivityLaunchAnimator,
     private val context: Context,
+    @DisplayId private val displayId: Int,
     private val lockScreenUserManager: NotificationLockscreenUserManager,
     private val statusBarWindowController: StatusBarWindowController,
     private val wakefulnessLifecycle: WakefulnessLifecycle,
@@ -387,6 +393,35 @@
     }
 
     /**
+     * Whether we should animate an activity launch.
+     *
+     * Note: This method must be called *before* dismissing the keyguard.
+     */
+    private fun shouldAnimateLaunch(
+        isActivityIntent: Boolean,
+        showOverLockscreen: Boolean,
+    ): Boolean {
+        // TODO(b/294418322): Support launch animations when occluded.
+        if (keyguardStateController.isOccluded) {
+            return false
+        }
+
+        // Always animate if we are not showing the keyguard or if we animate over the lockscreen
+        // (without unlocking it).
+        if (showOverLockscreen || !keyguardStateController.isShowing) {
+            return true
+        }
+
+        // We don't animate non-activity launches as they can break the animation.
+        // TODO(b/184121838): Support non activity launches on the lockscreen.
+        return isActivityIntent
+    }
+
+    override fun shouldAnimateLaunch(isActivityIntent: Boolean): Boolean {
+        return shouldAnimateLaunch(isActivityIntent, false)
+    }
+
+    /**
      * Encapsulates the activity logic for activity starter.
      *
      * Logic is duplicated in {@link CentralSurfacesImpl}
@@ -417,7 +452,7 @@
             val animate =
                 animationController != null &&
                     !willLaunchResolverActivity &&
-                    centralSurfaces?.shouldAnimateLaunch(true /* isActivityIntent */) == true
+                    shouldAnimateLaunch(isActivityIntent = true)
             val animController =
                 wrapAnimationController(
                     animationController = animationController,
@@ -440,9 +475,7 @@
                     intent.getPackage()
                 ) { adapter: RemoteAnimationAdapter? ->
                     val options =
-                        ActivityOptions(
-                            CentralSurfaces.getActivityOptions(centralSurfaces!!.displayId, adapter)
-                        )
+                        ActivityOptions(CentralSurfaces.getActivityOptions(displayId, adapter))
 
                     // We know that the intent of the caller is to dismiss the keyguard and
                     // this runnable is called right after the keyguard is solved, so we tell
@@ -536,7 +569,7 @@
             val animate =
                 !willLaunchResolverActivity &&
                     animationController != null &&
-                    centralSurfaces?.shouldAnimateLaunch(intent.isActivity) == true
+                    shouldAnimateLaunch(intent.isActivity)
 
             // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
             // run the animation on the keyguard). The animation will take care of (instantly)
@@ -565,7 +598,7 @@
                                     val options =
                                         ActivityOptions(
                                             CentralSurfaces.getActivityOptions(
-                                                centralSurfaces!!.displayId,
+                                                displayId,
                                                 animationAdapter
                                             )
                                         )
@@ -593,7 +626,7 @@
                         Log.w(TAG, "Sending intent failed: $e")
                         if (!collapse) {
                             // executeRunnableDismissingKeyguard did not collapse for us already.
-                            centralSurfaces?.collapsePanelOnMainThread()
+                            shadeControllerLazy.get().collapseOnMainThread()
                         }
                         // TODO: Dismiss Keyguard.
                     }
@@ -635,7 +668,7 @@
 
             val animate =
                 animationController != null &&
-                    centralSurfaces?.shouldAnimateLaunch(
+                    shouldAnimateLaunch(
                         /* isActivityIntent= */ true,
                         showOverLockscreenWhenLocked
                     ) == true
@@ -713,7 +746,7 @@
             } else if (dismissShade) {
                 // The animation will take care of dismissing the shade at the end of the animation.
                 // If we don't animate, collapse it directly.
-                centralSurfaces?.collapseShade()
+                shadeControllerLazy.get().cancelExpansionAndCollapseShade()
             }
 
             // We should exit the dream to prevent the activity from starting below the
@@ -731,7 +764,7 @@
                 TaskStackBuilder.create(context)
                     .addNextIntent(intent)
                     .startActivities(
-                        CentralSurfaces.getActivityOptions(centralSurfaces!!.displayId, adapter),
+                        CentralSurfaces.getActivityOptions(displayId, adapter),
                         userHandle
                     )
             }
@@ -802,7 +835,7 @@
                                 shadeControllerLazy.get().isExpandedVisible &&
                                     !statusBarKeyguardViewManagerLazy.get().isBouncerShowing
                             ) {
-                                shadeControllerLazy.get().animateCollapseShadeDelayed()
+                                shadeControllerLazy.get().animateCollapseShadeForcedDelayed()
                             } else {
                                 // Do it after DismissAction has been processed to conserve the
                                 // needed ordering.
@@ -865,7 +898,9 @@
                 if (dismissShade) {
                     return StatusBarLaunchAnimatorController(
                         animationController,
-                        it,
+                        shadeViewControllerLazy.get(),
+                        shadeControllerLazy.get(),
+                        notifShadeWindowControllerLazy.get(),
                         isLaunchForActivity
                     )
                 }
@@ -879,8 +914,8 @@
             val packages: Array<String> =
                 context.resources.getStringArray(R.array.system_ui_packages)
             for (pkg in packages) {
-                if (intent.component == null) break
-                if (pkg == intent.component.packageName) {
+                val componentName = intent.component ?: break
+                if (pkg == componentName.packageName) {
                     return UserHandle(UserHandle.myUserId())
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index f6d53b3..5eafa9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -20,6 +20,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.hardware.display.ColorDisplayManager;
 import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
@@ -28,6 +29,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
+import com.android.systemui.dagger.NightDisplayListenerModule;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.AutoAddTracker;
@@ -82,7 +84,8 @@
     private final HotspotController mHotspotController;
     private final DataSaverController mDataSaverController;
     private final ManagedProfileController mManagedProfileController;
-    private final NightDisplayListener mNightDisplayListener;
+    private final NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
+    private NightDisplayListener mNightDisplayListener;
     private final CastController mCastController;
     private final DeviceControlsController mDeviceControlsController;
     private final WalletController mWalletController;
@@ -98,7 +101,7 @@
             HotspotController hotspotController,
             DataSaverController dataSaverController,
             ManagedProfileController managedProfileController,
-            NightDisplayListener nightDisplayListener,
+            NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
             CastController castController,
             ReduceBrightColorsController reduceBrightColorsController,
             DeviceControlsController deviceControlsController,
@@ -114,7 +117,7 @@
         mHotspotController = hotspotController;
         mDataSaverController = dataSaverController;
         mManagedProfileController = managedProfileController;
-        mNightDisplayListener = nightDisplayListener;
+        mNightDisplayListenerBuilder = nightDisplayListenerBuilder;
         mCastController = castController;
         mReduceBrightColorsController = reduceBrightColorsController;
         mIsReduceBrightColorsAvailable = isReduceBrightColorsAvailable;
@@ -157,6 +160,10 @@
             mDataSaverController.addCallback(mDataSaverListener);
         }
         mManagedProfileController.addCallback(mProfileCallback);
+
+        mNightDisplayListener = mNightDisplayListenerBuilder
+                .setUser(mCurrentUser.getIdentifier())
+                .build();
         if (!mAutoTracker.isAdded(NIGHT)
                 && ColorDisplayManager.isNightDisplayAvailable(mContext)) {
             mNightDisplayListener.setCallback(mNightDisplayCallback);
@@ -193,7 +200,8 @@
         mHotspotController.removeCallback(mHotspotCallback);
         mDataSaverController.removeCallback(mDataSaverListener);
         mManagedProfileController.removeCallback(mProfileCallback);
-        if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
+        if (ColorDisplayManager.isNightDisplayAvailable(mContext)
+                && mNightDisplayListener != null) {
             mNightDisplayListener.setCallback(null);
         }
         if (mIsReduceBrightColorsAvailable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index ed11711..adff681 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -18,6 +18,8 @@
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
 
+import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.UNKNOWN_LAST_WAKE_TIME;
 
 import android.annotation.IntDef;
@@ -30,6 +32,7 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.Trace;
+import android.view.HapticFeedbackConstants;
 
 import androidx.annotation.Nullable;
 
@@ -51,6 +54,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -177,6 +181,8 @@
     private long mLastFpFailureUptimeMillis;
     private int mNumConsecutiveFpFailures;
 
+    private final FeatureFlags mFeatureFlags;
+
     private static final class PendingAuthenticated {
         public final int userId;
         public final BiometricSourceType biometricSourceType;
@@ -280,7 +286,8 @@
             LatencyTracker latencyTracker,
             ScreenOffAnimationController screenOffAnimationController,
             VibratorHelper vibrator,
-            SystemClock systemClock
+            SystemClock systemClock,
+            FeatureFlags featureFlags
     ) {
         mPowerManager = powerManager;
         mUpdateMonitor = keyguardUpdateMonitor;
@@ -308,6 +315,7 @@
         mVibratorHelper = vibrator;
         mLogger = biometricUnlockLogger;
         mSystemClock = systemClock;
+        mFeatureFlags = featureFlags;
 
         dumpManager.registerDumpable(getClass().getName(), this);
     }
@@ -689,8 +697,9 @@
             mLatencyTracker.onActionCancel(action);
         }
 
-        if (!mVibratorHelper.hasVibrator()
-                && (!mUpdateMonitor.isDeviceInteractive() || mUpdateMonitor.isDreaming())) {
+        final boolean screenOff = !mUpdateMonitor.isDeviceInteractive();
+        if (!mVibratorHelper.hasVibrator() && (screenOff || (mUpdateMonitor.isDreaming()
+                && !mFeatureFlags.isEnabled(FP_LISTEN_OCCLUDING_APPS)))) {
             mLogger.d("wakeup device on authentication failure (device doesn't have a vibrator)");
             startWakeAndUnlock(MODE_ONLY_WAKE);
         } else if (biometricSourceType == BiometricSourceType.FINGERPRINT
@@ -750,8 +759,15 @@
             mLogger.d("Skip auth success haptic. Power button was recently pressed.");
             return;
         }
-        mVibratorHelper.vibrateAuthSuccess(
-                getClass().getSimpleName() + ", type =" + type + "device-entry::success");
+        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            mVibratorHelper.performHapticFeedback(
+                    mKeyguardViewController.getViewRootImpl().getView(),
+                    HapticFeedbackConstants.CONFIRM
+            );
+        } else {
+            mVibratorHelper.vibrateAuthSuccess(
+                    getClass().getSimpleName() + ", type =" + type + "device-entry::success");
+        }
     }
 
     private boolean lastWakeupFromPowerButtonWithinHapticThreshold() {
@@ -764,8 +780,15 @@
     }
 
     private void vibrateError(BiometricSourceType type) {
-        mVibratorHelper.vibrateAuthError(
-                getClass().getSimpleName() + ", type =" + type + "device-entry::error");
+        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            mVibratorHelper.performHapticFeedback(
+                    mKeyguardViewController.getViewRootImpl().getView(),
+                    HapticFeedbackConstants.REJECT
+            );
+        } else {
+            mVibratorHelper.vibrateAuthError(
+                    getClass().getSimpleName() + ", type =" + type + "device-entry::error");
+        }
     }
 
     private void cleanup() {
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 86bf7cb..5c28be3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -24,13 +24,11 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
-import android.os.PowerManager;
 import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.RemoteAnimationAdapter;
 import android.view.View;
-import android.view.ViewGroup;
 import android.window.RemoteTransition;
 import android.window.SplashScreen;
 
@@ -39,19 +37,14 @@
 import androidx.lifecycle.LifecycleOwner;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.RegisterStatusBarResult;
 import com.android.keyguard.AuthKeyguardMessageArea;
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.display.data.repository.DisplayMetricsRepository;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.qs.QSPanelController;
-import com.android.systemui.shade.NotificationShadeWindowView;
-import com.android.systemui.shade.NotificationShadeWindowViewController;
-import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
-import com.android.systemui.statusbar.LightRevealScrim;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.util.Compile;
@@ -69,14 +62,11 @@
     String TAG = "CentralSurfaces";
     boolean DEBUG = false;
     boolean SPEW = false;
-    boolean DUMPTRUCK = true; // extra dumpsys info
     boolean DEBUG_GESTURES = false;
     boolean DEBUG_MEDIA_FAKE_ARTWORK = false;
     boolean DEBUG_CAMERA_LIFT = false;
     boolean DEBUG_WINDOW_STATE = false;
     boolean DEBUG_WAKEUP_DELAY = Compile.IS_DEBUG;
-    // additional instrumentation for testing purposes; intended to be left on during development
-    boolean CHATTY = DEBUG;
     boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = true;
     String ACTION_FAKE_ARTWORK = "fake_artwork";
     int FADE_KEYGUARD_START_DELAY = 100;
@@ -194,14 +184,6 @@
         return contextForUser.getPackageManager();
     }
 
-    void animateExpandNotificationsPanel();
-
-    void animateExpandSettingsPanel(@Nullable String subpanel);
-
-    void collapsePanelOnMainThread();
-
-    void togglePanel();
-
     void start();
 
     boolean updateIsKeyguard();
@@ -212,63 +194,37 @@
     @Override
     Lifecycle getLifecycle();
 
-    /**
-     * Wakes up the device if the device was dozing.
-     */
-    void wakeUpIfDozing(long time, String why, @PowerManager.WakeReason int wakeReason);
-
-    NotificationShadeWindowView getNotificationShadeWindowView();
-
-    NotificationShadeWindowViewController getNotificationShadeWindowViewController();
-
-    /** */
-    ShadeViewController getShadeViewController();
-
     /** Get the Keyguard Message Area that displays auth messages. */
     AuthKeyguardMessageArea getKeyguardMessageArea();
 
     int getStatusBarHeight();
 
-    void updateQsExpansionEnabled();
-
-    boolean isShadeDisabled();
-
     boolean isLaunchingActivityOverLockscreen();
 
-    boolean isWakeUpComingFromTouch();
-
     void onKeyguardViewManagerStatesUpdated();
 
-    ViewGroup getNotificationScrollLayout();
-
     boolean isPulsing();
 
     boolean isOccluded();
 
-    //TODO: These can / should probably be moved to NotificationPresenter or ShadeController
-    void onLaunchAnimationCancelled(boolean isLaunchForActivity);
-
-    void onLaunchAnimationEnd(boolean launchIsFullScreen);
-
-    boolean shouldAnimateLaunch(boolean isActivityIntent, boolean showOverLockscreen);
-
-    boolean shouldAnimateLaunch(boolean isActivityIntent);
-
     boolean isDeviceInVrMode();
 
     NotificationPresenter getPresenter();
 
-    void postAnimateCollapsePanels();
-
-    void postAnimateForceCollapsePanels();
-
-    void postAnimateOpenPanels();
-
-    boolean isPanelExpanded();
-
+    /**
+     * Used to dispatch initial touch events before crossing the threshold to pull down the
+     * notification shade. After that, since the launcher window is set to slippery, input
+     * frameworks take care of routing the events to the notification shade.
+     */
     void onInputFocusTransfer(boolean start, boolean cancel, float velocity);
 
-    void animateCollapseQuickSettings();
+    /**
+     * Dispatches status bar motion event to the notification shade. This is different from
+     * {@link #onInputFocusTransfer(boolean, boolean, float)} as it doesn't rely on setting the
+     * launcher window slippery to allow the frameworks to route those events after passing the
+     * initial threshold.
+     */
+    default void onStatusBarTrackpadEvent(MotionEvent event) {}
 
     /** */
     boolean getCommandQueuePanelsEnabled();
@@ -289,18 +245,16 @@
     @Override
     void dump(PrintWriter pwOriginal, String[] args);
 
-    void createAndAddWindows(@Nullable RegisterStatusBarResult result);
-
+    /** @deprecated Use {@link DisplayMetricsRepository} instead. */
+    @Deprecated
     float getDisplayWidth();
 
+    /** @deprecated Use {@link DisplayMetricsRepository} instead. */
+    @Deprecated
     float getDisplayHeight();
 
     void readyForKeyguardDone();
 
-    void resetUserExpandedStates();
-
-    void setLockscreenUser(int newUserId);
-
     void showKeyguard();
 
     boolean hideKeyguard();
@@ -333,15 +287,11 @@
     /** Should the keyguard be hidden immediately in response to a back press/gesture. */
     boolean shouldKeyguardHideImmediately();
 
-    boolean onBackPressed();
-
     boolean onSpacePressed();
 
     void showBouncerWithDimissAndCancelIfKeyguard(OnDismissAction performAction,
             Runnable cancelAction);
 
-    LightRevealScrim getLightRevealScrim();
-
     // TODO: Figure out way to remove these.
     NavigationBarView getNavigationBarView();
 
@@ -353,10 +303,6 @@
 
     void setBouncerShowing(boolean bouncerShowing);
 
-    void setBouncerShowingOverDream(boolean bouncerShowingOverDream);
-
-    void collapseShade();
-
     int getWakefulnessState();
 
     boolean isScreenFullyOff();
@@ -389,9 +335,6 @@
 
     boolean isDeviceInteractive();
 
-    void setNotificationSnoozed(StatusBarNotification sbn,
-            NotificationSwipeActionHelper.SnoozeOption snoozeOption);
-
     void awakenDreams();
 
     void clearNotificationEffects();
@@ -406,15 +349,11 @@
 
     void updateNotificationPanelTouchState();
 
-    int getDisplayId();
-
     int getRotation();
 
     @VisibleForTesting
     void setBarStateForTest(int state);
 
-    void wakeUpForFullScreenIntent();
-
     void showTransientUnchecked();
 
     void clearTransient();
@@ -429,14 +368,6 @@
 
     void resendMessage(Object msg);
 
-    int getDisabled1();
-
-    void setDisabled1(int disabled);
-
-    int getDisabled2();
-
-    void setDisabled2(int disabled);
-
     void setLastCameraLaunchSource(int source);
 
     void setLaunchCameraOnFinishedGoingToSleep(boolean launch);
@@ -449,14 +380,12 @@
 
     QSPanelController getQSPanelController();
 
-    boolean areNotificationAlertsDisabled();
-
+    /** @deprecated Use {@link DisplayMetricsRepository} instead. */
+    @Deprecated
     float getDisplayDensity();
 
     void extendDozePulse();
 
-    boolean shouldDelayWakeUpAnimation();
-
     public static class KeyboardShortcutsMessage {
         final int mDeviceId;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 0ccc819..6431ef9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -111,6 +111,9 @@
     private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
 
+    private int mDisabled1;
+    private int mDisabled2;
+
     @Inject
     CentralSurfacesCommandQueueCallbacks(
             CentralSurfaces centralSurfaces,
@@ -208,7 +211,7 @@
 
     @Override
     public void animateCollapsePanels(int flags, boolean force) {
-        mShadeController.animateCollapsePanels(flags, force, false /* delayed */,
+        mShadeController.animateCollapseShade(flags, force, false /* delayed */,
                 1.0f /* speedUpFactor */);
     }
 
@@ -218,11 +221,7 @@
             Log.d(CentralSurfaces.TAG,
                     "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
         }
-        if (!mCommandQueue.panelsEnabled()) {
-            return;
-        }
-
-        mShadeViewController.expandToNotifications();
+        mShadeController.animateExpandShade();
     }
 
     @Override
@@ -231,14 +230,7 @@
             Log.d(CentralSurfaces.TAG,
                     "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
         }
-        if (!mCommandQueue.panelsEnabled()) {
-            return;
-        }
-
-        // Settings are not available in setup
-        if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
-
-        mShadeViewController.expandToQs();
+        mShadeController.animateExpandQs();
     }
 
     @Override
@@ -255,31 +247,26 @@
     }
     /**
      * State is one or more of the DISABLE constants from StatusBarManager.
+     *
+     * @deprecated If you need to react to changes in disable flags, listen to
+     * {@link com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository}
+     * instead.
      */
     @Override
+    @Deprecated
     public void disable(int displayId, int state1, int state2, boolean animate) {
         if (displayId != mDisplayId) {
             return;
         }
 
-        int state2BeforeAdjustment = state2;
-        state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
-        Log.d(CentralSurfaces.TAG,
-                mDisableFlagsLogger.getDisableFlagsString(
-                        /* old= */ new DisableFlagsLogger.DisableState(
-                                mCentralSurfaces.getDisabled1(), mCentralSurfaces.getDisabled2()),
-                        /* new= */ new DisableFlagsLogger.DisableState(
-                                state1, state2BeforeAdjustment),
-                        /* newStateAfterLocalModification= */ new DisableFlagsLogger.DisableState(
-                                state1, state2)));
-
-        final int old1 = mCentralSurfaces.getDisabled1();
+        final int old1 = mDisabled1;
         final int diff1 = state1 ^ old1;
-        mCentralSurfaces.setDisabled1(state1);
+        mDisabled1 = state1;
 
-        final int old2 = mCentralSurfaces.getDisabled2();
+        state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2);
+        final int old2 = mDisabled2;
         final int diff2 = state2 ^ old2;
-        mCentralSurfaces.setDisabled2(state2);
+        mDisabled2 = state2;
 
         if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
             if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
@@ -288,17 +275,12 @@
         }
 
         if ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
-            if (mCentralSurfaces.areNotificationAlertsDisabled()) {
+            if ((state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
                 mHeadsUpManager.releaseAllImmediately();
             }
         }
 
-        if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
-            mCentralSurfaces.updateQsExpansionEnabled();
-        }
-
         if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
-            mCentralSurfaces.updateQsExpansionEnabled();
             if ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
                 mShadeController.animateCollapseShade();
             }
@@ -556,10 +538,10 @@
 
     @Override
     public void togglePanel() {
-        if (mCentralSurfaces.isPanelExpanded()) {
+        if (mShadeViewController.isPanelExpanded()) {
             mShadeController.animateCollapseShade();
         } else {
-            animateExpandNotificationsPanel();
+            mShadeController.animateExpandShade();
         }
     }
 
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 ae38105..8ffd43a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -42,6 +42,7 @@
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.app.IWallpaperManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
@@ -50,7 +51,6 @@
 import android.app.StatusBarManager;
 import android.app.TaskInfo;
 import android.app.UiModeManager;
-import android.app.WallpaperInfo;
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
@@ -69,7 +69,6 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -91,6 +90,7 @@
 import android.view.IRemoteAnimationRunner;
 import android.view.IWindowManager;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.ThreadedRenderer;
 import android.view.View;
 import android.view.ViewGroup;
@@ -135,7 +135,9 @@
 import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.back.domain.interactor.BackActionInteractor;
 import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.camera.CameraIntents;
 import com.android.systemui.charging.WiredChargingRippleController;
@@ -157,7 +159,6 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder;
 import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
 import com.android.systemui.navigationbar.NavigationBarController;
@@ -172,8 +173,8 @@
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.recents.ScreenPinningRequest;
@@ -181,7 +182,6 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.shade.CameraLauncher;
-import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.QuickSettingsController;
@@ -220,6 +220,7 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -311,7 +312,8 @@
     private final DeviceStateManager mDeviceStateManager;
     private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks;
     private float mTransitionToFullShadeProgress = 0f;
-    private NotificationListContainer mNotifListContainer;
+    private final NotificationListContainer mNotifListContainer;
+    private final NotificationExpansionRepository mNotificationExpansionRepository;
     private boolean mIsShortcutListSearchEnabled;
 
     private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
@@ -362,26 +364,6 @@
     }
 
     @Override
-    public int getDisabled1() {
-        return mDisabled1;
-    }
-
-    @Override
-    public void setDisabled1(int disabled) {
-        mDisabled1 = disabled;
-    }
-
-    @Override
-    public int getDisabled2() {
-        return mDisabled2;
-    }
-
-    @Override
-    public void setDisabled2(int disabled) {
-        mDisabled2 = disabled;
-    }
-
-    @Override
     public void setLastCameraLaunchSource(int source) {
         mLastCameraLaunchSource = source;
     }
@@ -411,23 +393,6 @@
         return mQSPanelController;
     }
 
-    /** */
-    @Override
-    public void animateExpandNotificationsPanel() {
-        mCommandQueueCallbacks.animateExpandNotificationsPanel();
-    }
-
-    /** */
-    @Override
-    public void animateExpandSettingsPanel(@Nullable String subpanel) {
-        mCommandQueueCallbacks.animateExpandSettingsPanel(subpanel);
-    }
-
-    /** */
-    @Override
-    public void togglePanel() {
-        mCommandQueueCallbacks.togglePanel();
-    }
     /**
      * The {@link StatusBarState} of the status bar.
      */
@@ -449,13 +414,12 @@
 
     private final Point mCurrentDisplaySize = new Point();
 
-    protected NotificationShadeWindowView mNotificationShadeWindowView;
     protected PhoneStatusBarView mStatusBarView;
     private PhoneStatusBarViewController mPhoneStatusBarViewController;
     private PhoneStatusBarTransitions mStatusBarTransitions;
     private final AuthRippleController mAuthRippleController;
     @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
-    protected final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarInitializer mStatusBarInitializer;
     private final StatusBarWindowController mStatusBarWindowController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -464,8 +428,6 @@
     private final LightRevealScrim mLightRevealScrim;
     private PowerButtonReveal mPowerButtonReveal;
 
-    private boolean mWakeUpComingFromTouch;
-
     /**
      * Whether we should delay the wakeup animation (which shows the notifications and moves the
      * clock view). This is typically done when waking up from a 'press to unlock' gesture on a
@@ -494,7 +456,8 @@
     private final FalsingManager mFalsingManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final ConfigurationController mConfigurationController;
-    protected NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+    private final Lazy<NotificationShadeWindowViewController>
+            mNotificationShadeWindowViewControllerLazy;
     private final DozeParameters mDozeParameters;
     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
@@ -505,7 +468,6 @@
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
 
     private final PluginDependencyProvider mPluginDependencyProvider;
-    private final KeyguardDismissUtil mKeyguardDismissUtil;
     private final ExtensionController mExtensionController;
     private final UserInfoControllerImpl mUserInfoControllerImpl;
     private final DemoModeController mDemoModeController;
@@ -516,14 +478,12 @@
     private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy;
 
     /** Controller for the Shade. */
-    @VisibleForTesting
-    ShadeSurface mShadeSurface;
+    private final ShadeSurface mShadeSurface;
     private final ShadeLogger mShadeLogger;
 
     // settings
     private QSPanelController mQSPanelController;
-    @VisibleForTesting
-    QuickSettingsController mQsController;
+    private final QuickSettingsController mQsController;
 
     KeyguardIndicationController mKeyguardIndicationController;
 
@@ -549,11 +509,6 @@
 
     private CentralSurfacesComponent mCentralSurfacesComponent;
 
-    // Flags for disabling the status bar
-    // Two variables because the first one evidently ran out of room for new flags.
-    private int mDisabled1 = 0;
-    private int mDisabled2 = 0;
-
     /**
      * This keeps track of whether we have (or haven't) registered the predictive back callback.
      * Since we can have visible -> visible transitions, we need to avoid
@@ -627,6 +582,7 @@
     private final ViewMediatorCallback mKeyguardViewMediatorCallback;
     private final ScrimController mScrimController;
     protected DozeScrimController mDozeScrimController;
+    private final BackActionInteractor mBackActionInteractor;
     private final Executor mUiBgExecutor;
 
     protected boolean mDozing;
@@ -655,7 +611,6 @@
     private final UserSwitcherController mUserSwitcherController;
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
     protected final BatteryController mBatteryController;
-    protected boolean mPanelExpanded;
     private UiModeManager mUiModeManager;
     private LogMaker mStatusBarStateLog;
     protected final NotificationIconAreaController mNotificationIconAreaController;
@@ -663,13 +618,14 @@
     private final SysuiColorExtractor mColorExtractor;
     private final ScreenLifecycle mScreenLifecycle;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
+    protected final PowerInteractor mPowerInteractor;
 
     private boolean mNoAnimationOnNextBarModeChange;
     private final SysuiStatusBarStateController mStatusBarStateController;
 
     private final ActivityLaunchAnimator mActivityLaunchAnimator;
     private NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
-    protected NotificationPresenter mPresenter;
+    private final NotificationPresenter mPresenter;
     private NotificationActivityStarter mNotificationActivityStarter;
     private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
     private final Optional<Bubbles> mBubblesOptional;
@@ -677,7 +633,8 @@
     private final Optional<StartingSurface> mStartingSurfaceOptional;
 
     private final ActivityIntentHelper mActivityIntentHelper;
-    private NotificationStackScrollLayoutController mStackScrollerController;
+
+    public final NotificationStackScrollLayoutController mStackScrollerController;
 
     private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
             (extractor, which) -> updateTheme();
@@ -685,17 +642,7 @@
     private final InteractionJankMonitor mJankMonitor;
 
     /** Existing callback that handles back gesture invoked for the Shade. */
-    private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
-        if (DEBUG) {
-            Log.d(TAG, "mOnBackInvokedCallback() called");
-        }
-        onBackPressed();
-    };
-
-    private boolean shouldBackBeHandled() {
-        return (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
-                && !isBouncerShowingOverDream());
-    }
+    private final OnBackInvokedCallback mOnBackInvokedCallback;
 
     /**
      *  New callback that handles back gesture invoked, cancel, progress
@@ -705,12 +652,12 @@
     private final OnBackAnimationCallback mOnBackAnimationCallback = new OnBackAnimationCallback() {
         @Override
         public void onBackInvoked() {
-            onBackPressed();
+            mBackActionInteractor.onBackRequested();
         }
 
         @Override
         public void onBackProgressed(BackEvent event) {
-            if (shouldBackBeHandled()) {
+            if (mBackActionInteractor.shouldBackBeHandled()) {
                 if (mShadeSurface.canBeCollapsed()) {
                     float fraction = event.getProgress();
                     mShadeSurface.onBackProgressed(fraction);
@@ -756,14 +703,17 @@
             MetricsLogger metricsLogger,
             ShadeLogger shadeLogger,
             @UiBackground Executor uiBgExecutor,
+            ShadeSurface shadeSurface,
             NotificationMediaManager notificationMediaManager,
             NotificationLockscreenUserManager lockScreenUserManager,
             NotificationRemoteInputManager remoteInputManager,
+            QuickSettingsController quickSettingsController,
             UserSwitcherController userSwitcherController,
             BatteryController batteryController,
             SysuiColorExtractor colorExtractor,
             ScreenLifecycle screenLifecycle,
             WakefulnessLifecycle wakefulnessLifecycle,
+            PowerInteractor powerInteractor,
             SysuiStatusBarStateController statusBarStateController,
             Optional<Bubbles> bubblesOptional,
             Lazy<NoteTaskController> noteTaskControllerLazy,
@@ -773,12 +723,18 @@
             Lazy<AssistManager> assistManagerLazy,
             ConfigurationController configurationController,
             NotificationShadeWindowController notificationShadeWindowController,
+            Lazy<NotificationShadeWindowViewController> notificationShadeWindowViewControllerLazy,
+            NotificationShelfController notificationShelfController,
+            NotificationStackScrollLayoutController notificationStackScrollLayoutController,
+            NotificationPresenter notificationPresenter,
+            NotificationExpansionRepository notificationExpansionRepository,
             DozeParameters dozeParameters,
             ScrimController scrimController,
             Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
             AuthRippleController authRippleController,
             DozeServiceHost dozeServiceHost,
+            BackActionInteractor backActionInteractor,
             PowerManager powerManager,
             ScreenPinningRequest screenPinningRequest,
             DozeScrimController dozeScrimController,
@@ -792,7 +748,6 @@
             InitController initController,
             @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
             PluginDependencyProvider pluginDependencyProvider,
-            KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
             PhoneStatusBarPolicy phoneStatusBarPolicy,
@@ -839,6 +794,7 @@
         mKeyguardBypassController = keyguardBypassController;
         mKeyguardStateController = keyguardStateController;
         mHeadsUpManager = headsUpManagerPhone;
+        mBackActionInteractor = backActionInteractor;
         mKeyguardIndicationController = keyguardIndicationController;
         mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
         mFalsingCollector = falsingCollector;
@@ -853,14 +809,17 @@
         mMetricsLogger = metricsLogger;
         mShadeLogger = shadeLogger;
         mUiBgExecutor = uiBgExecutor;
+        mShadeSurface = shadeSurface;
         mMediaManager = notificationMediaManager;
         mLockscreenUserManager = lockScreenUserManager;
         mRemoteInputManager = remoteInputManager;
+        mQsController = quickSettingsController;
         mUserSwitcherController = userSwitcherController;
         mBatteryController = batteryController;
         mColorExtractor = colorExtractor;
         mScreenLifecycle = screenLifecycle;
         mWakefulnessLifecycle = wakefulnessLifecycle;
+        mPowerInteractor = powerInteractor;
         mStatusBarStateController = statusBarStateController;
         mBubblesOptional = bubblesOptional;
         mNoteTaskControllerLazy = noteTaskControllerLazy;
@@ -870,6 +829,13 @@
         mAssistManagerLazy = assistManagerLazy;
         mConfigurationController = configurationController;
         mNotificationShadeWindowController = notificationShadeWindowController;
+        mNotificationShadeWindowViewControllerLazy = notificationShadeWindowViewControllerLazy;
+        mNotificationShelfController = notificationShelfController;
+        mStackScrollerController = notificationStackScrollLayoutController;
+        mStackScroller = mStackScrollerController.getView();
+        mNotifListContainer = mStackScrollerController.getNotificationListContainer();
+        mPresenter = notificationPresenter;
+        mNotificationExpansionRepository = notificationExpansionRepository;
         mDozeServiceHost = dozeServiceHost;
         mPowerManager = powerManager;
         mDozeParameters = dozeParameters;
@@ -889,7 +855,6 @@
         mKeyguardViewMediatorCallback = viewMediatorCallback;
         mInitController = initController;
         mPluginDependencyProvider = pluginDependencyProvider;
-        mKeyguardDismissUtil = keyguardDismissUtil;
         mExtensionController = extensionController;
         mUserInfoControllerImpl = userInfoControllerImpl;
         mIconPolicy = phoneStatusBarPolicy;
@@ -958,6 +923,12 @@
         }
         // Based on teamfood flag, enable predictive back animation for the Shade.
         mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
+        mOnBackInvokedCallback = () -> {
+            if (DEBUG) {
+                Log.d(TAG, "mOnBackInvokedCallback() called");
+            }
+            mBackActionInteractor.onBackRequested();
+        };
     }
 
     private void initBubbles(Bubbles bubbles) {
@@ -1014,16 +985,6 @@
 
         createAndAddWindows(result);
 
-        if (mWallpaperSupported) {
-            // Make sure we always have the most current wallpaper info.
-            IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
-            mBroadcastDispatcher.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter,
-                    null /* handler */, UserHandle.ALL);
-            mWallpaperChangedReceiver.onReceive(mContext, null);
-        } else if (DEBUG) {
-            Log.v(TAG, "start(): no wallpaper service ");
-        }
-
         // Set up the initial notification state. This needs to happen before CommandQueue.disable()
         setUpPresenter();
 
@@ -1109,7 +1070,7 @@
         mDozeServiceHost.initialize(
                 this,
                 mStatusBarKeyguardViewManager,
-                mNotificationShadeWindowViewController,
+                getNotificationShadeWindowViewController(),
                 mShadeSurface,
                 mAmbientIndicationContainer);
         updateLightRevealScrimVisibility();
@@ -1151,7 +1112,8 @@
                     @Override
                     public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) {
                         mMainExecutor.execute(
-                                () -> plugin.setup(getNotificationShadeWindowView(),
+                                () -> plugin.setup(
+                                        mNotificationShadeWindowController.getWindowRootView(),
                                         getNavigationBarView(),
                                         new Callback(plugin), mDozeParameters));
                     }
@@ -1270,8 +1232,8 @@
         updateTheme();
 
         inflateStatusBarWindow();
-        mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
-        mWallpaperController.setRootView(mNotificationShadeWindowView);
+        getNotificationShadeWindowView().setOnTouchListener(getStatusBarWindowTouchListener());
+        mWallpaperController.setRootView(getNotificationShadeWindowView());
 
         // 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.
@@ -1292,7 +1254,7 @@
                     mStatusBarView = statusBarView;
                     mPhoneStatusBarViewController = statusBarViewController;
                     mStatusBarTransitions = statusBarTransitions;
-                    mNotificationShadeWindowViewController
+                    getNotificationShadeWindowViewController()
                             .setStatusBarViewController(mPhoneStatusBarViewController);
                     // Ensure we re-propagate panel expansion values to the panel controller and
                     // any listeners it may have, such as PanelBar. This will also ensure we
@@ -1306,7 +1268,7 @@
         mStatusBarInitializer.initializeStatusBar(
                 mCentralSurfacesComponent::createCollapsedStatusBarFragment);
 
-        mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
+        mStatusBarTouchableRegionManager.setup(this, getNotificationShadeWindowView());
 
         createNavigationBar(result);
 
@@ -1314,7 +1276,7 @@
             mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
         }
 
-        mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById(
+        mAmbientIndicationContainer = getNotificationShadeWindowView().findViewById(
                 R.id.ambient_indication_container);
 
         mAutoHideController.setStatusBar(new AutoHideUiElement() {
@@ -1339,10 +1301,10 @@
             }
         });
 
-        ScrimView scrimBehind = mNotificationShadeWindowView.findViewById(R.id.scrim_behind);
-        ScrimView notificationsScrim = mNotificationShadeWindowView
+        ScrimView scrimBehind = getNotificationShadeWindowView().findViewById(R.id.scrim_behind);
+        ScrimView notificationsScrim = getNotificationShadeWindowView()
                 .findViewById(R.id.scrim_notifications);
-        ScrimView scrimInFront = mNotificationShadeWindowView.findViewById(R.id.scrim_in_front);
+        ScrimView scrimInFront = getNotificationShadeWindowView().findViewById(R.id.scrim_in_front);
 
         mScrimController.setScrimVisibleListener(scrimsVisible -> {
             mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible);
@@ -1370,7 +1332,7 @@
             }
         });
 
-        mScreenOffAnimationController.initialize(this, mLightRevealScrim);
+        mScreenOffAnimationController.initialize(this, mShadeSurface, mLightRevealScrim);
         updateLightRevealScrimVisibility();
 
         mShadeSurface.initDependencies(
@@ -1380,7 +1342,7 @@
                 mNotificationShelfController,
                 mHeadsUpManager);
 
-        BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
+        BackDropView backdrop = getNotificationShadeWindowView().findViewById(R.id.backdrop);
         if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
             mMediaManager.setup(null, null, null, mScrimController, null);
         } else {
@@ -1399,7 +1361,7 @@
         });
 
         // Set up the quick settings tile panel
-        final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
+        final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame);
         if (container != null) {
             FragmentHostManager fragmentHostManager =
                     mFragmentService.getFragmentHostManager(container);
@@ -1414,7 +1376,7 @@
                             .withDefault(this::createDefaultQSFragment)
                             .build());
             mBrightnessMirrorController = new BrightnessMirrorController(
-                    mNotificationShadeWindowView,
+                    getNotificationShadeWindowView(),
                     mShadeSurface,
                     mNotificationShadeDepthControllerLazy.get(),
                     mBrightnessSliderFactory,
@@ -1431,7 +1393,7 @@
             });
         }
 
-        mReportRejectedTouch = mNotificationShadeWindowView
+        mReportRejectedTouch = getNotificationShadeWindowView()
                 .findViewById(R.id.report_rejected_touch);
         if (mReportRejectedTouch != null) {
             updateReportRejectedTouchVisibility();
@@ -1551,22 +1513,15 @@
 
     @VisibleForTesting
     void onShadeExpansionFullyChanged(Boolean isExpanded) {
-        if (mPanelExpanded != isExpanded) {
-            mPanelExpanded = isExpanded;
-            if (getShadeViewController() != null) {
-                // Needed to update SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
-                getShadeViewController().updateSystemUiStateFlags();
+        if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
+            if (DEBUG) {
+                Log.v(TAG, "clearing notification effects from Height");
             }
-            if (isExpanded && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
-                if (DEBUG) {
-                    Log.v(TAG, "clearing notification effects from Height");
-                }
-                clearNotificationEffects();
-            }
+            clearNotificationEffects();
+        }
 
-            if (!isExpanded) {
-                mRemoteInputManager.onPanelCollapsed();
-            }
+        if (!isExpanded) {
+            mRemoteInputManager.onPanelCollapsed();
         }
     }
 
@@ -1586,7 +1541,7 @@
 
     protected QS createDefaultQSFragment() {
         return mFragmentService
-                .getFragmentHostManager(mNotificationShadeWindowView)
+                .getFragmentHostManager(getNotificationShadeWindowView())
                 .create(QSFragment.class);
     }
 
@@ -1595,7 +1550,7 @@
         mActivityLaunchAnimator.setCallback(mActivityLaunchAnimatorCallback);
         mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener);
         mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
-                mNotificationShadeWindowViewController,
+                mNotificationExpansionRepository,
                 mNotifListContainer,
                 mHeadsUpManager,
                 mJankMonitor);
@@ -1604,12 +1559,10 @@
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
         mShadeController.setNotificationPresenter(mPresenter);
         mNotificationsController.initialize(
-                this,
                 mPresenter,
                 mNotifListContainer,
                 mStackScrollerController.getNotifStackController(),
-                mNotificationActivityStarter,
-                mCentralSurfacesComponent.getBindRowCallback());
+                mNotificationActivityStarter);
     }
 
     /**
@@ -1621,22 +1574,6 @@
         mCommandQueue.disable(mDisplayId, state1, state2, false /* animate */);
     }
 
-    /**
-     * Ask the display to wake up if currently dozing, else do nothing
-     *
-     * @param time when to wake up
-     * @param why the reason for the wake up
-     */
-    @Override
-    public void wakeUpIfDozing(long time, String why, @PowerManager.WakeReason int wakeReason) {
-        if (mDozing && mScreenOffAnimationController.allowWakeUpIfDozing()) {
-            mPowerManager.wakeUp(
-                    time, wakeReason, "com.android.systemui:" + why);
-            mWakeUpComingFromTouch = true;
-            mFalsingCollector.onScreenOnFromTouch();
-        }
-    }
-
     // TODO(b/117478341): This was left such that CarStatusBar can override this method.
     // Try to remove this.
     protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
@@ -1652,7 +1589,7 @@
             mAutoHideController.checkUserAutoHide(event);
             mRemoteInputManager.checkRemoteInputOutside(event);
             mShadeController.onStatusBarTouch(event);
-            return mNotificationShadeWindowView.onTouchEvent(event);
+            return getNotificationShadeWindowView().onTouchEvent(event);
         };
     }
 
@@ -1665,29 +1602,15 @@
                 CollapsedStatusBarFragment.class,
                 mCentralSurfacesComponent::createCollapsedStatusBarFragment);
 
-        mNotificationShadeWindowView = mCentralSurfacesComponent.getNotificationShadeWindowView();
-        mNotificationShadeWindowViewController = mCentralSurfacesComponent
-                .getNotificationShadeWindowViewController();
+        ViewGroup windowRootView = mCentralSurfacesComponent.getWindowRootView();
         // TODO(b/277762009): Inject [NotificationShadeWindowView] directly into the controller.
         //  (Right now, there's a circular dependency.)
-        mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
-        mNotificationShadeWindowViewController.setupExpandedStatusBar();
-        NotificationPanelViewController npvc =
-                mCentralSurfacesComponent.getNotificationPanelViewController();
-        mShadeSurface = npvc;
-        mShadeController.setNotificationPanelViewController(npvc);
+        mNotificationShadeWindowController.setWindowRootView(windowRootView);
+        getNotificationShadeWindowViewController().setupExpandedStatusBar();
         mShadeController.setNotificationShadeWindowViewController(
-                mNotificationShadeWindowViewController);
-        mStackScrollerController =
-                mCentralSurfacesComponent.getNotificationStackScrollLayoutController();
-        mQsController = mCentralSurfacesComponent.getQuickSettingsController();
-        mStackScroller = mStackScrollerController.getView();
-        mNotifListContainer = mCentralSurfacesComponent.getNotificationListContainer();
-        mPresenter = mCentralSurfacesComponent.getNotificationPresenter();
+                getNotificationShadeWindowViewController());
+        mBackActionInteractor.setup(mQsController, mShadeSurface);
         mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter();
-        mNotificationShelfController = mCentralSurfacesComponent.getNotificationShelfController();
-
-        mHeadsUpManager.addListener(mCentralSurfacesComponent.getStatusBarHeadsUpChangeListener());
 
         // Listen for demo mode changes
         mDemoModeController.addCallback(mDemoModeCallback);
@@ -1701,6 +1624,14 @@
         mCommandQueue.addCallback(mCommandQueueCallbacks);
     }
 
+    protected NotificationShadeWindowViewController getNotificationShadeWindowViewController() {
+        return mNotificationShadeWindowViewControllerLazy.get();
+    }
+
+    protected NotificationShadeWindowView getNotificationShadeWindowView() {
+        return getNotificationShadeWindowViewController().getView();
+    }
+
     protected void startKeyguard() {
         Trace.beginSection("CentralSurfaces#startKeyguard");
         mStatusBarStateController.addCallback(mStateListener,
@@ -1746,28 +1677,16 @@
 
         mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
         mMediaManager.setBiometricUnlockController(mBiometricUnlockController);
-        mKeyguardDismissUtil.setDismissHandler(this::executeWhenUnlocked);
         Trace.endSection();
     }
 
-    @Override
-    public NotificationShadeWindowView getNotificationShadeWindowView() {
-        return mNotificationShadeWindowView;
-    }
-
-    @Override
-    public NotificationShadeWindowViewController getNotificationShadeWindowViewController() {
-        return mNotificationShadeWindowViewController;
-    }
-
-    @Override
-    public ShadeViewController getShadeViewController() {
+    protected ShadeViewController getShadeViewController() {
         return mShadeSurface;
     }
 
     @Override
     public AuthKeyguardMessageArea getKeyguardMessageArea() {
-        return mNotificationShadeWindowViewController.getKeyguardMessageArea();
+        return getNotificationShadeWindowViewController().getKeyguardMessageArea();
     }
 
     @Override
@@ -1775,28 +1694,6 @@
         return mStatusBarWindowController.getStatusBarHeight();
     }
 
-    /**
-     * Disable QS if device not provisioned.
-     * If the user switcher is simple then disable QS during setup because
-     * the user intends to use the lock screen user switcher, QS in not needed.
-     */
-    @Override
-    public void updateQsExpansionEnabled() {
-        final boolean expandEnabled = mDeviceProvisionedController.isDeviceProvisioned()
-                && (mUserSetup || mUserSwitcherController == null
-                        || !mUserSwitcherController.isSimpleUserSwitcher())
-                && !isShadeDisabled()
-                && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0)
-                && !mDozing;
-        mQsController.setExpansionEnabledPolicy(expandEnabled);
-        Log.d(TAG, "updateQsExpansionEnabled - QS Expand enabled: " + expandEnabled);
-    }
-
-    @Override
-    public boolean isShadeDisabled() {
-        return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0;
-    }
-
     private void updateReportRejectedTouchVisibility() {
         if (mReportRejectedTouch == null) {
             return;
@@ -1805,11 +1702,6 @@
                 && mFalsingCollector.isReportingEnabled() ? View.VISIBLE : View.INVISIBLE);
     }
 
-    @Override
-    public boolean areNotificationAlertsDisabled() {
-        return (mDisabled1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
-    }
-
     /**
      * Whether we are currently animating an activity launch above the lockscreen (occluding
      * activity).
@@ -1819,11 +1711,6 @@
         return mIsLaunchingActivityOverLockscreen;
     }
 
-    @Override
-    public boolean isWakeUpComingFromTouch() {
-        return mWakeUpComingFromTouch;
-    }
-
     /**
      * To be called when there's a state change in StatusBarKeyguardViewManager.
      */
@@ -1833,11 +1720,6 @@
     }
 
     @Override
-    public ViewGroup getNotificationScrollLayout() {
-        return mStackScroller;
-    }
-
-    @Override
     public boolean isPulsing() {
         return mDozeServiceHost.isPulsing();
     }
@@ -1853,58 +1735,6 @@
         return mKeyguardStateController.isOccluded();
     }
 
-    /** A launch animation was cancelled. */
-    //TODO: These can / should probably be moved to NotificationPresenter or ShadeController
-    @Override
-    public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
-        if (mPresenter.isPresenterFullyCollapsed() && !mPresenter.isCollapsing()
-                && isLaunchForActivity) {
-            mShadeController.onClosingFinished();
-        } else {
-            mShadeController.collapseShade(true /* animate */);
-        }
-    }
-
-    /** A launch animation ended. */
-    @Override
-    public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
-        if (!mPresenter.isCollapsing()) {
-            mShadeController.onClosingFinished();
-        }
-        if (launchIsFullScreen) {
-            mShadeController.instantCollapseShade();
-        }
-    }
-
-    /**
-     * Whether we should animate an activity launch.
-     *
-     * Note: This method must be called *before* dismissing the keyguard.
-     */
-    @Override
-    public boolean shouldAnimateLaunch(boolean isActivityIntent, boolean showOverLockscreen) {
-        // TODO(b/184121838): Support launch animations when occluded.
-        if (isOccluded()) {
-            return false;
-        }
-
-        // Always animate if we are not showing the keyguard or if we animate over the lockscreen
-        // (without unlocking it).
-        if (showOverLockscreen || !mKeyguardStateController.isShowing()) {
-            return true;
-        }
-
-        // We don't animate non-activity launches as they can break the animation.
-        // TODO(b/184121838): Support non activity launches on the lockscreen.
-        return isActivityIntent;
-    }
-
-    /** Whether we should animate an activity launch. */
-    @Override
-    public boolean shouldAnimateLaunch(boolean isActivityIntent) {
-        return shouldAnimateLaunch(isActivityIntent, false /* showOverLockscreen */);
-    }
-
     @Override
     public boolean isDeviceInVrMode() {
         return mPresenter.isDeviceInVrMode();
@@ -1940,8 +1770,11 @@
                 try {
                     EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
                             sbn.getKey());
-                    wakeUpForFullScreenIntent();
-                    notification.fullScreenIntent.send();
+                    mPowerInteractor.wakeUpForFullScreenIntent();
+                    ActivityOptions opts = ActivityOptions.makeBasic();
+                    opts.setPendingIntentBackgroundActivityStartMode(
+                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                    notification.fullScreenIntent.send(opts.toBundle());
                     entry.notifyFullScreenIntentLaunched();
                 } catch (PendingIntent.CanceledException e) {
                 }
@@ -1950,37 +1783,6 @@
         mHeadsUpManager.releaseAllImmediately();
     }
 
-    @Override
-    public void wakeUpForFullScreenIntent() {
-        if (isGoingToSleep() || mDozing) {
-            mPowerManager.wakeUp(
-                    SystemClock.uptimeMillis(),
-                    PowerManager.WAKE_REASON_APPLICATION,
-                    "com.android.systemui:full_screen_intent");
-            mWakeUpComingFromTouch = false;
-        }
-    }
-
-    @Override
-    public void postAnimateCollapsePanels() {
-        mMainExecutor.execute(mShadeController::animateCollapseShade);
-    }
-
-    @Override
-    public void postAnimateForceCollapsePanels() {
-        mMainExecutor.execute(mShadeController::animateCollapseShadeForced);
-    }
-
-    @Override
-    public void postAnimateOpenPanels() {
-        mMessageRouter.sendMessage(MSG_OPEN_SETTINGS_PANEL);
-    }
-
-    @Override
-    public boolean isPanelExpanded() {
-        return mPanelExpanded;
-    }
-
     /**
      * Called when another window is about to transfer it's input focus.
      */
@@ -1998,11 +1800,8 @@
     }
 
     @Override
-    public void animateCollapseQuickSettings() {
-        if (mState == StatusBarState.SHADE) {
-            mShadeSurface.collapse(
-                    true, false /* delayed */, 1.0f /* speedUpFactor */);
-        }
+    public void onStatusBarTrackpadEvent(MotionEvent event) {
+        mShadeSurface.handleExternalTouch(event);
     }
 
     private void onExpandedInvisible() {
@@ -2181,11 +1980,9 @@
         pw.print("  mWallpaperSupported= "); pw.println(mWallpaperSupported);
 
         pw.println("  ShadeWindowView: ");
-        if (mNotificationShadeWindowViewController != null) {
-            mNotificationShadeWindowViewController.dump(pw, args);
-            CentralSurfaces.dumpBarTransitions(
-                    pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
-        }
+        getNotificationShadeWindowViewController().dump(pw, args);
+        CentralSurfaces.dumpBarTransitions(
+                pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
 
         pw.println("  mMediaManager: ");
         if (mMediaManager != null) {
@@ -2256,8 +2053,7 @@
                 + CameraIntents.getOverrideCameraPackage(mContext));
     }
 
-    @Override
-    public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
+    private void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
         makeStatusBarView(result);
         mNotificationShadeWindowController.attach();
         mStatusBarWindowController.attach();
@@ -2275,16 +2071,19 @@
     }
 
     @Override
+    @Deprecated
     public float getDisplayDensity() {
         return mDisplayMetrics.density;
     }
 
     @Override
+    @Deprecated
     public float getDisplayWidth() {
         return mDisplayMetrics.widthPixels;
     }
 
     @Override
+    @Deprecated
     public float getDisplayHeight() {
         return mDisplayMetrics.heightPixels;
     }
@@ -2295,11 +2094,6 @@
     }
 
     @Override
-    public int getDisplayId() {
-        return mDisplayId;
-    }
-
-    @Override
     public void readyForKeyguardDone() {
         mStatusBarKeyguardViewManager.readyForKeyguardDone();
     }
@@ -2342,7 +2136,7 @@
                     mNotificationShadeWindowController.setNotTouchable(false);
                 }
                 finishBarAnimations();
-                resetUserExpandedStates();
+                mNotificationsController.resetUserExpandedStates();
             }
             Trace.endSection();
         }
@@ -2361,36 +2155,6 @@
         }
     };
 
-    @Override
-    public void resetUserExpandedStates() {
-        mNotificationsController.resetUserExpandedStates();
-    }
-
-    private void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen,
-            boolean afterKeyguardGone) {
-        if (mKeyguardStateController.isShowing() && requiresShadeOpen) {
-            mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
-        }
-        mActivityStarter.dismissKeyguardThenExecute(action, null /* cancelAction */,
-                afterKeyguardGone /* afterKeyguardGone */);
-    }
-
-    /**
-     * Notify the shade controller that the current user changed
-     *
-     * @param newUserId userId of the new user
-     */
-    @Override
-    public void setLockscreenUser(int newUserId) {
-        if (mLockscreenWallpaper != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
-            mLockscreenWallpaper.setCurrentUser(newUserId);
-        }
-        mScrimController.setCurrentUser(newUserId);
-        if (mWallpaperSupported) {
-            mWallpaperChangedReceiver.onReceive(mContext, null);
-        }
-    }
-
     /**
      * Reload some of our resources when the configuration changes.
      *
@@ -2861,7 +2625,6 @@
                 || (mDozing && mDozeParameters.shouldControlScreenOff() && keyguardVisibleOrWillBe);
 
         mShadeSurface.setDozing(mDozing, animate);
-        updateQsExpansionEnabled();
         Trace.endSection();
     }
 
@@ -2890,7 +2653,7 @@
             case KeyEvent.KEYCODE_BACK:
                 if (mState == StatusBarState.KEYGUARD
                         && mStatusBarKeyguardViewManager.dispatchBackKeyEventPreIme()) {
-                    return onBackPressed();
+                    return mBackActionInteractor.onBackRequested();
                 }
         }
         return false;
@@ -2930,34 +2693,6 @@
     }
 
     @Override
-    public boolean onBackPressed() {
-        if (mStatusBarKeyguardViewManager.canHandleBackPressed()) {
-            mStatusBarKeyguardViewManager.onBackPressed();
-            return true;
-        }
-        if (mQsController.isCustomizing()) {
-            mQsController.closeQsCustomizer();
-            return true;
-        }
-        if (mQsController.getExpanded()) {
-            mShadeSurface.animateCollapseQs(false);
-            return true;
-        }
-        if (mShadeSurface.closeUserSwitcherIfOpen()) {
-            return true;
-        }
-        if (shouldBackBeHandled()) {
-            if (mShadeSurface.canBeCollapsed()) {
-                // this is the Shade dismiss animation, so make sure QQS closes when it ends.
-                mShadeSurface.onBackPressed();
-                mShadeController.animateCollapseShade();
-            }
-            return true;
-        }
-        return false;
-    }
-
-    @Override
     public boolean onSpacePressed() {
         if (mDeviceInteractive && mState != StatusBarState.SHADE) {
             mShadeController.animateCollapseShadeForced();
@@ -3000,19 +2735,6 @@
     }
 
     /**
-     * Collapse the panel directly if we are on the main thread, post the collapsing on the main
-     * thread if we are not.
-     */
-    @Override
-    public void collapsePanelOnMainThread() {
-        if (Looper.getMainLooper().isCurrentThread()) {
-            mShadeController.collapseShade();
-        } else {
-            mContext.getMainExecutor().execute(mShadeController::collapseShade);
-        }
-    }
-
-    /**
      * Updates the light reveal effect to reflect the reason we're waking or sleeping (for example,
      * from the power button).
      * @param wakingUp Whether we're updating because we're waking up (true) or going to sleep
@@ -3048,11 +2770,6 @@
         }
     }
 
-    @Override
-    public LightRevealScrim getLightRevealScrim() {
-        return mLightRevealScrim;
-    }
-
     // TODO: Figure out way to remove these.
     @Override
     public NavigationBarView getNavigationBarView() {
@@ -3075,9 +2792,8 @@
     }
 
     protected ViewRootImpl getViewRootImpl()  {
-        NotificationShadeWindowView nswv = getNotificationShadeWindowView();
-        if (nswv != null) return nswv.getViewRootImpl();
-
+        View root = mNotificationShadeWindowController.getWindowRootView();
+        if (root != null) return root.getViewRootImpl();
         return null;
     }
     /**
@@ -3088,12 +2804,12 @@
         mBouncerShowing = bouncerShowing;
         mKeyguardBypassController.setBouncerShowing(bouncerShowing);
         mPulseExpansionHandler.setBouncerShowing(bouncerShowing);
+        mStackScrollerController.setBouncerShowingFromCentralSurfaces(bouncerShowing);
         setBouncerShowingForStatusBarComponents(bouncerShowing);
         mStatusBarHideIconsForBouncerManager.setBouncerShowingAndTriggerUpdate(bouncerShowing);
         mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
         if (mBouncerShowing) {
-            wakeUpIfDozing(SystemClock.uptimeMillis(), "BOUNCER_VISIBLE",
-                    PowerManager.WAKE_REASON_GESTURE);
+            mPowerInteractor.wakeUpIfDozing("BOUNCER_VISIBLE", PowerManager.WAKE_REASON_GESTURE);
         }
         updateScrimController();
         if (!mBouncerShowing) {
@@ -3102,17 +2818,6 @@
     }
 
     /**
-     * Sets whether the bouncer over dream is showing. Note that the bouncer over dream is handled
-     * independently of the rest of the notification panel. As a result, setting this state via
-     * {@link #setBouncerShowing(boolean)} leads to unintended side effects from states modified
-     * behind the dream.
-     */
-    @Override
-    public void setBouncerShowingOverDream(boolean bouncerShowingOverDream) {
-        mBouncerShowingOverDream = bouncerShowingOverDream;
-    }
-
-    /**
      * Propagate the bouncer state to status bar components.
      *
      * Separate from {@link #setBouncerShowing} because we sometimes re-create the status bar and
@@ -3129,19 +2834,6 @@
         mShadeSurface.setBouncerShowing(bouncerShowing);
     }
 
-    /**
-     * Collapses the notification shade if it is tracking or expanded.
-     */
-    @Override
-    public void collapseShade() {
-        if (mShadeSurface.isTracking()) {
-            mNotificationShadeWindowViewController.cancelCurrentTouch();
-        }
-        if (mPanelExpanded && mState == StatusBarState.SHADE) {
-            mShadeController.animateCollapseShade();
-        }
-    }
-
     @VisibleForTesting
     final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
         @Override
@@ -3150,11 +2842,10 @@
             releaseGestureWakeLock();
             mLaunchCameraWhenFinishedWaking = false;
             mDeviceInteractive = false;
-            mWakeUpComingFromTouch = false;
             updateVisibleToUser();
 
             updateNotificationPanelTouchState();
-            mNotificationShadeWindowViewController.cancelCurrentTouch();
+            getNotificationShadeWindowViewController().cancelCurrentTouch();
             if (mLaunchCameraOnFinishedGoingToSleep) {
                 mLaunchCameraOnFinishedGoingToSleep = false;
 
@@ -3466,12 +3157,6 @@
         updateScrimController();
     }
 
-    @VisibleForTesting
-    public void setNotificationShadeWindowViewController(
-            NotificationShadeWindowViewController nswvc) {
-        mNotificationShadeWindowViewController = nswvc;
-    }
-
     /**
      * Set 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
@@ -3506,7 +3191,7 @@
         mScrimController.setExpansionAffectsAlpha(!unlocking);
 
         if (mAlternateBouncerInteractor.isVisibleState()) {
-            if ((!isOccluded() || isPanelExpanded())
+            if ((!isOccluded() || mShadeSurface.isPanelExpanded())
                     && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
                     || mTransitionToFullShadeProgress > 0f)) {
                 mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
@@ -3573,7 +3258,7 @@
     protected IStatusBarService mBarService;
 
     // all notifications
-    protected NotificationStackScrollLayout mStackScroller;
+    private final NotificationStackScrollLayout mStackScroller;
 
     protected AccessibilityManager mAccessibilityManager;
 
@@ -3603,7 +3288,7 @@
     protected Display mDisplay;
     private int mDisplayId;
 
-    protected NotificationShelfController mNotificationShelfController;
+    private final NotificationShelfController mNotificationShelfController;
 
     private final Lazy<AssistManager> mAssistManagerLazy;
 
@@ -3636,12 +3321,6 @@
     };
 
     @Override
-    public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
-        mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
-    }
-
-
-    @Override
     public void awakenDreams() {
         mUiBgExecutor.execute(() -> {
             try {
@@ -3712,7 +3391,7 @@
             mVisible = visible;
             if (visible) {
                 DejankUtils.notifyRendererOfExpensiveFrame(
-                        mNotificationShadeWindowView, "onShadeVisibilityChanged");
+                        getNotificationShadeWindowView(), "onShadeVisibilityChanged");
             } else {
                 mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
                         true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
@@ -3842,38 +3521,14 @@
 
             if (userSetup != mUserSetup) {
                 mUserSetup = userSetup;
-                if (!mUserSetup) {
-                    animateCollapseQuickSettings();
+                if (!mUserSetup && mState == StatusBarState.SHADE) {
+                    mShadeSurface.collapse(true /* animate */, false  /* delayed */,
+                            1.0f  /* speedUpFactor */);
                 }
-                updateQsExpansionEnabled();
             }
         }
     };
 
-    private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (!mWallpaperSupported) {
-                // Receiver should not have been registered at all...
-                Log.wtf(TAG, "WallpaperManager not supported");
-                return;
-            }
-            WallpaperInfo info = mWallpaperManager.getWallpaperInfoForUser(
-                    mUserTracker.getUserId());
-            mWallpaperController.onWallpaperInfoUpdated(info);
-
-            final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
-                    com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
-            // If WallpaperInfo is null, it must be ImageWallpaper.
-            final boolean supportsAmbientMode = deviceSupportsAodWallpaper
-                    && (info != null && info.supportsAmbientMode());
-
-            mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
-            mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
-            mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode);
-        }
-    };
-
     private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
         @Override
         public void onConfigChanged(Configuration newConfig) {
@@ -3980,7 +3635,6 @@
                     // reset them again when we're waking up
                     mShadeSurface.resetViews(dozingAnimated && isDozing);
 
-                    updateQsExpansionEnabled();
                     mKeyguardViewMediator.setDozing(mDozing);
 
                     updateDozingState();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 7dcdc0b..97cb45a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -22,8 +22,6 @@
 import android.view.View.LAYOUT_DIRECTION_RTL
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.policy.ConfigurationController
-
-import java.util.ArrayList
 import javax.inject.Inject
 
 @SysUISingleton
@@ -40,6 +38,7 @@
     private var localeList: LocaleList? = null
     private val context: Context
     private var layoutDirection: Int
+    private var orientation = Configuration.ORIENTATION_UNDEFINED
 
     init {
         val currentConfig = context.resources.configuration
@@ -134,8 +133,18 @@
                 it.onThemeChanged()
             }
         }
+
+        val newOrientation = newConfig.orientation
+        if (orientation != newOrientation) {
+            orientation = newOrientation
+            listeners.filterForEach({ this.listeners.contains(it) }) {
+                it.onOrientationChanged(orientation)
+            }
+        }
     }
 
+
+
     override fun addCallback(listener: ConfigurationController.ConfigurationListener) {
         listeners.add(listener)
         listener.onDensityOrFontScaleChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index 2677c3f2..c4495ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -32,6 +32,11 @@
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.flow.FlowKt;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.flow.StateFlow;
+import kotlinx.coroutines.flow.StateFlowKt;
+
 /**
  */
 @SysUISingleton
@@ -47,6 +52,9 @@
     private int mDarkModeIconColorSingleTone;
     private int mLightModeIconColorSingleTone;
 
+    private final MutableStateFlow<DarkChange> mDarkChangeFlow = StateFlowKt.MutableStateFlow(
+            DarkChange.EMPTY);
+
     /**
      */
     @Inject
@@ -66,6 +74,11 @@
         return mTransitionsController;
     }
 
+    @Override
+    public StateFlow<DarkChange> darkChangeFlow() {
+        return FlowKt.asStateFlow(mDarkChangeFlow);
+    }
+
     public void addDarkReceiver(DarkReceiver receiver) {
         mReceivers.put(receiver, receiver);
         receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
@@ -122,6 +135,7 @@
     }
 
     private void applyIconTint() {
+        mDarkChangeFlow.setValue(new DarkChange(mTintAreas, mDarkIntensity, mIconTint));
         for (int i = 0; i < mReceivers.size(); i++) {
             mReceivers.valueAt(i).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 39b5b5a..374543d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
@@ -34,12 +33,7 @@
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.StatusBarMobileView;
-import com.android.systemui.statusbar.StatusBarWifiView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
-import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger;
 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
@@ -54,11 +48,9 @@
     private static final String TAG = "DemoStatusIcons";
 
     private final LinearLayout mStatusIcons;
-    private final ArrayList<StatusBarMobileView> mMobileViews = new ArrayList<>();
     private final ArrayList<ModernStatusBarMobileView> mModernMobileViews = new ArrayList<>();
     private final int mIconSize;
 
-    private StatusBarWifiView mWifiView;
     private ModernStatusBarWifiView mModernWifiView;
     private boolean mDemoMode;
     private int mColor;
@@ -94,7 +86,6 @@
     }
 
     public void remove() {
-        mMobileViews.clear();
         ((ViewGroup) getParent()).removeView(this);
     }
 
@@ -130,7 +121,6 @@
         mDemoMode = false;
         mStatusIcons.setVisibility(View.VISIBLE);
         mModernMobileViews.clear();
-        mMobileViews.clear();
         setVisibility(View.GONE);
     }
 
@@ -238,52 +228,6 @@
         addView(v, 0, createLayoutParams());
     }
 
-    public void addDemoWifiView(WifiIconState state) {
-        Log.d(TAG, "addDemoWifiView: ");
-        StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, state.slot);
-
-        int viewIndex = getChildCount();
-        // If we have mobile views, put wifi before them
-        for (int i = 0; i < getChildCount(); i++) {
-            View child = getChildAt(i);
-            if (child instanceof StatusBarMobileView
-                    || child instanceof ModernStatusBarMobileView) {
-                viewIndex = i;
-                break;
-            }
-        }
-
-        mWifiView = view;
-        mWifiView.applyWifiState(state);
-        mWifiView.setStaticDrawableColor(mColor);
-        addView(view, viewIndex, createLayoutParams());
-    }
-
-    public void updateWifiState(WifiIconState state) {
-        Log.d(TAG, "updateWifiState: ");
-        if (mWifiView == null) {
-            addDemoWifiView(state);
-        } else {
-            mWifiView.applyWifiState(state);
-        }
-    }
-
-    /**
-     * Add a new mobile icon view
-     */
-    public void addMobileView(MobileIconState state, Context mobileContext) {
-        Log.d(TAG, "addMobileView: ");
-        StatusBarMobileView view = StatusBarMobileView
-                .fromContext(mobileContext, state.slot);
-
-        view.applyMobileState(state);
-        view.setStaticDrawableColor(mColor);
-
-        // mobile always goes at the end
-        mMobileViews.add(view);
-        addView(view, getChildCount(), createLayoutParams());
-    }
-
     /**
      * Add a {@link ModernStatusBarMobileView}
      * @param mobileContext possibly mcc/mnc overridden mobile context
@@ -318,8 +262,7 @@
         // If we have mobile views, put wifi before them
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
-            if (child instanceof StatusBarMobileView
-                    || child instanceof ModernStatusBarMobileView) {
+            if (child instanceof ModernStatusBarMobileView) {
                 viewIndex = i;
                 break;
             }
@@ -330,42 +273,13 @@
         addView(view, viewIndex, createLayoutParams());
     }
 
-    /**
-     * Apply an update to a mobile icon view for the given {@link MobileIconState}. For
-     * compatibility with {@link MobileContextProvider}, we have to recreate the view every time we
-     * update it, since the context (and thus the {@link Configuration}) may have changed
-     */
-    public void updateMobileState(MobileIconState state, Context mobileContext) {
-        Log.d(TAG, "updateMobileState: " + state);
-
-        // The mobile config provided by MobileContextProvider could have changed; always recreate
-        for (int i = 0; i < mMobileViews.size(); i++) {
-            StatusBarMobileView view = mMobileViews.get(i);
-            if (view.getState().subId == state.subId) {
-                removeView(view);
-            }
-        }
-
-        // Add the replacement or new icon
-        addMobileView(state, mobileContext);
-    }
-
     public void onRemoveIcon(StatusIconDisplayable view) {
         if (view.getSlot().equals("wifi")) {
-            if (view instanceof StatusBarWifiView) {
-                removeView(mWifiView);
-                mWifiView = null;
-            } else if (view instanceof ModernStatusBarWifiView) {
+            if (view instanceof ModernStatusBarWifiView) {
                 Log.d(TAG, "onRemoveIcon: removing modern wifi view");
                 removeView(mModernWifiView);
                 mModernWifiView = null;
             }
-        } else if (view instanceof StatusBarMobileView) {
-            StatusBarMobileView mobileView = matchingMobileView(view);
-            if (mobileView != null) {
-                removeView(mobileView);
-                mMobileViews.remove(mobileView);
-            }
         } else if (view instanceof ModernStatusBarMobileView) {
             ModernStatusBarMobileView mobileView = matchingModernMobileView(
                     (ModernStatusBarMobileView) view);
@@ -376,21 +290,6 @@
         }
     }
 
-    private StatusBarMobileView matchingMobileView(StatusIconDisplayable otherView) {
-        if (!(otherView instanceof StatusBarMobileView)) {
-            return null;
-        }
-
-        StatusBarMobileView v = (StatusBarMobileView) otherView;
-        for (StatusBarMobileView view : mMobileViews) {
-            if (view.getState().subId == v.getState().subId) {
-                return view;
-            }
-        }
-
-        return null;
-    }
-
     private ModernStatusBarMobileView matchingModernMobileView(ModernStatusBarMobileView other) {
         for (ModernStatusBarMobileView v : mModernMobileViews) {
             if (v.getSubId() == other.getSubId()) {
@@ -409,15 +308,9 @@
     public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
         setColor(DarkIconDispatcher.getTint(areas, mStatusIcons, tint));
 
-        if (mWifiView != null) {
-            mWifiView.onDarkChanged(areas, darkIntensity, tint);
-        }
         if (mModernWifiView != null) {
             mModernWifiView.onDarkChanged(areas, darkIntensity, tint);
         }
-        for (StatusBarMobileView view : mMobileViews) {
-            view.onDarkChanged(areas, darkIntensity, tint);
-        }
         for (ModernStatusBarMobileView view : mModernMobileViews) {
             view.onDarkChanged(areas, darkIntensity, tint);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 7312db6..801cdbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -38,7 +38,6 @@
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor;
 import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.ShadeViewController;
@@ -94,7 +93,6 @@
     private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
     private final AuthController mAuthController;
     private final NotificationIconAreaController mNotificationIconAreaController;
-    private final BurnInInteractor mBurnInInteractor;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private ShadeViewController mNotificationPanel;
     private View mAmbientIndicationContainer;
@@ -118,8 +116,7 @@
             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
             AuthController authController,
             NotificationIconAreaController notificationIconAreaController,
-            DozeInteractor dozeInteractor,
-            BurnInInteractor burnInInteractor) {
+            DozeInteractor dozeInteractor) {
         super();
         mDozeLog = dozeLog;
         mPowerManager = powerManager;
@@ -138,7 +135,6 @@
         mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
         mAuthController = authController;
         mNotificationIconAreaController = notificationIconAreaController;
-        mBurnInInteractor = burnInInteractor;
         mHeadsUpManagerPhone.addListener(mOnHeadsUpChangedListener);
         mDozeInteractor = dozeInteractor;
     }
@@ -311,12 +307,12 @@
 
     @Override
     public void dozeTimeTick() {
+        mDozeInteractor.dozeTimeTick();
         mNotificationPanel.dozeTimeTick();
         mAuthController.dozeTimeTick();
         if (mAmbientIndicationContainer instanceof DozeReceiver) {
             ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
         }
-        mBurnInInteractor.dozeTimeTick();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index f15dcc3..a1f12b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -81,6 +81,7 @@
     private final BiConsumer<Float, Float> mSetExpandedHeight = this::setAppearFraction;
     private final KeyguardBypassController mBypassController;
     private final StatusBarStateController mStatusBarStateController;
+    private final PhoneStatusBarTransitions mPhoneStatusBarTransitions;
     private final CommandQueue mCommandQueue;
     private final NotificationWakeUpCoordinator mWakeUpCoordinator;
 
@@ -109,6 +110,7 @@
             NotificationIconAreaController notificationIconAreaController,
             HeadsUpManagerPhone headsUpManager,
             StatusBarStateController stateController,
+            PhoneStatusBarTransitions phoneStatusBarTransitions,
             KeyguardBypassController bypassController,
             NotificationWakeUpCoordinator wakeUpCoordinator,
             DarkIconDispatcher darkIconDispatcher,
@@ -156,6 +158,7 @@
         });
         mBypassController = bypassController;
         mStatusBarStateController = stateController;
+        mPhoneStatusBarTransitions = phoneStatusBarTransitions;
         mWakeUpCoordinator = wakeUpCoordinator;
         mCommandQueue = commandQueue;
         mKeyguardStateController = keyguardStateController;
@@ -203,6 +206,7 @@
     @Override
     public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {
         updateHeadsUpAndPulsingRoundness(entry);
+        mPhoneStatusBarTransitions.onHeadsUpStateChanged(isHeadsUp);
     }
 
     private void updateTopEntry() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index d433814..cdd410e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -32,12 +32,14 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.animation.requiresRemeasuring
 
 /**
  * Renders the bottom area of the lock-screen. Concerned primarily with the quick affordance UI
  * elements. A secondary concern is the interaction of the quick affordance elements with the
  * indication area between them, though the indication area is primarily controlled elsewhere.
  */
+@Deprecated("Deprecated as part of b/278057014")
 class KeyguardBottomAreaView
 @JvmOverloads
 constructor(
@@ -53,6 +55,7 @@
         defStyleRes,
     ) {
 
+    @Deprecated("Deprecated as part of b/278057014")
     interface MessageDisplayer {
         fun display(@StringRes stringResourceId: Int)
     }
@@ -62,6 +65,7 @@
     private var lockIconViewController: LockIconViewController? = null
 
     /** Initializes the view. */
+    @Deprecated("Deprecated as part of b/278057014")
     fun init(
         viewModel: KeyguardBottomAreaViewModel,
         falsingManager: FalsingManager? = null,
@@ -88,13 +92,14 @@
      * Initializes this instance of [KeyguardBottomAreaView] based on the given instance of another
      * [KeyguardBottomAreaView]
      */
+    @Deprecated("Deprecated as part of b/278057014")
     fun initFrom(oldBottomArea: KeyguardBottomAreaView) {
         // if it exists, continue to use the original ambient indication container
         // instead of the newly inflated one
         ambientIndicationArea?.let { nonNullAmbientIndicationArea ->
             // remove old ambient indication from its parent
             val originalAmbientIndicationView =
-                oldBottomArea.findViewById<View>(R.id.ambient_indication_container)
+                oldBottomArea.requireViewById<View>(R.id.ambient_indication_container)
             (originalAmbientIndicationView.parent as ViewGroup).removeView(
                 originalAmbientIndicationView
             )
@@ -122,9 +127,11 @@
     }
 
     /** Returns a list of animators to use to animate the indication areas. */
+    @Deprecated("Deprecated as part of b/278057014")
     val indicationAreaAnimators: List<ViewPropertyAnimator>
         get() = checkNotNull(binding).getIndicationAreaAnimators()
 
+
     override fun hasOverlappingRendering(): Boolean {
         return false
     }
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 ff1b31d..924aac4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -50,12 +50,6 @@
     @DevicePostureInt private var postureState: Int = DEVICE_POSTURE_UNKNOWN
     private var pendingUnlock: PendingUnlock? = null
     private val listeners = mutableListOf<OnBypassStateChangedListener>()
-    private val postureCallback = DevicePostureController.Callback { posture ->
-        if (postureState != posture) {
-            postureState = posture
-            notifyListeners()
-        }
-    }
     private val faceAuthEnabledChangedCallback = object : KeyguardStateController.Callback {
         override fun onFaceAuthEnabledChanged() = notifyListeners()
     }
@@ -162,10 +156,8 @@
 
         val dismissByDefault = if (context.resources.getBoolean(
                         com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
-        tunerService.addTunable(object : TunerService.Tunable {
-            override fun onTuningChanged(key: String?, newValue: String?) {
-                bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
-            }
+        tunerService.addTunable({ key, _ ->
+            bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
         }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
         lockscreenUserManager.addUserChangedListener(
                 object : NotificationLockscreenUserManager.UserChangedListener {
@@ -281,8 +273,6 @@
     }
 
     companion object {
-        const val BYPASS_FADE_DURATION = 67
-
         private const val FACE_UNLOCK_BYPASS_NO_OVERRIDE = 0
         private const val FACE_UNLOCK_BYPASS_ALWAYS = 1
         private const val FACE_UNLOCK_BYPASS_NEVER = 2
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
index 27b68f2..1c90c0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardDismissUtil.java
@@ -16,48 +16,41 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.util.Log;
-
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
 
 /**
- * Executes actions that require the screen to be unlocked. Delegates the actual handling to an
- * implementation passed via {@link #setDismissHandler}.
+ * Executes actions that require the screen to be unlocked.
  */
 @SysUISingleton
 public class KeyguardDismissUtil implements KeyguardDismissHandler {
-    private static final String TAG = "KeyguardDismissUtil";
+    private final KeyguardStateController mKeyguardStateController;
 
-    private volatile KeyguardDismissHandler mDismissHandler;
+    private final SysuiStatusBarStateController mStatusBarStateController;
+
+    private final ActivityStarter mActivityStarter;
 
     @Inject
-    public KeyguardDismissUtil() {
+    public KeyguardDismissUtil(KeyguardStateController keyguardStateController,
+            SysuiStatusBarStateController statusBarStateController,
+            ActivityStarter activityStarter) {
+        mKeyguardStateController = keyguardStateController;
+        mStatusBarStateController = statusBarStateController;
+        mActivityStarter = activityStarter;
     }
 
-    /** Sets the actual {@link KeyguardDismissHandler} implementation. */
-    public void setDismissHandler(KeyguardDismissHandler dismissHandler) {
-        mDismissHandler = dismissHandler;
-    }
-
-    /**
-     * Executes an action that requires the screen to be unlocked.
-     *
-     * <p>Must be called after {@link #setDismissHandler}.
-     *
-     * @param requiresShadeOpen does the shade need to be forced open when hiding the keyguard?
-     */
     @Override
     public void executeWhenUnlocked(OnDismissAction action, boolean requiresShadeOpen,
             boolean afterKeyguardGone) {
-        KeyguardDismissHandler dismissHandler = mDismissHandler;
-        if (dismissHandler == null) {
-            Log.wtf(TAG, "KeyguardDismissHandler not set.");
-            action.onDismiss();
-            return;
+        if (mKeyguardStateController.isShowing() && requiresShadeOpen) {
+            mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
         }
-        dismissHandler.executeWhenUnlocked(action, requiresShadeOpen, afterKeyguardGone);
+        mActivityStarter.dismissKeyguardThenExecute(action, null /* cancelAction */,
+                afterKeyguardGone /* afterKeyguardGone */);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 720eeba..38c3815 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -34,7 +34,6 @@
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -43,11 +42,11 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.app.animation.Interpolators;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
 import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
 import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
@@ -55,6 +54,11 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
+import kotlinx.coroutines.flow.FlowKt;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.flow.StateFlow;
+import kotlinx.coroutines.flow.StateFlowKt;
+
 /**
  * The header group on Keyguard.
  */
@@ -83,6 +87,9 @@
     private int mStatusBarPaddingEnd;
     private int mMinDotWidth;
     private View mSystemIconsContainer;
+    private View mSystemIcons;
+    private final MutableStateFlow<DarkChange> mDarkChange = StateFlowKt.MutableStateFlow(
+            DarkChange.EMPTY);
 
     private View mCutoutSpace;
     private ViewGroup mStatusIconArea;
@@ -113,6 +120,7 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mSystemIconsContainer = findViewById(R.id.system_icons_container);
+        mSystemIcons = findViewById(R.id.system_icons);
         mMultiUserAvatar = findViewById(R.id.multi_user_avatar);
         mCarrierLabel = findViewById(R.id.keyguard_carrier_text);
         mBatteryView = mSystemIconsContainer.findViewById(R.id.battery);
@@ -161,6 +169,13 @@
                 mStatusIconContainer.getPaddingBottom()
         );
 
+        mSystemIcons.setPaddingRelative(
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_start),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_top),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_end),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_bottom)
+        );
+
         // Respect font size setting.
         mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
                 getResources().getDimensionPixelSize(
@@ -374,49 +389,6 @@
         return mKeyguardUserAvatarEnabled;
     }
 
-    private void animateNextLayoutChange() {
-        final int systemIconsCurrentX = mSystemIconsContainer.getLeft();
-        final boolean userAvatarVisible = mMultiUserAvatar.getParent() == mStatusIconArea;
-        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
-            @Override
-            public boolean onPreDraw() {
-                getViewTreeObserver().removeOnPreDrawListener(this);
-                boolean userAvatarHiding = userAvatarVisible
-                        && mMultiUserAvatar.getParent() != mStatusIconArea;
-                mSystemIconsContainer.setX(systemIconsCurrentX);
-                mSystemIconsContainer.animate()
-                        .translationX(0)
-                        .setDuration(400)
-                        .setStartDelay(userAvatarHiding ? 300 : 0)
-                        .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                        .start();
-                if (userAvatarHiding) {
-                    getOverlay().add(mMultiUserAvatar);
-                    mMultiUserAvatar.animate()
-                            .alpha(0f)
-                            .setDuration(300)
-                            .setStartDelay(0)
-                            .setInterpolator(Interpolators.ALPHA_OUT)
-                            .withEndAction(() -> {
-                                mMultiUserAvatar.setAlpha(1f);
-                                getOverlay().remove(mMultiUserAvatar);
-                            })
-                            .start();
-
-                } else {
-                    mMultiUserAvatar.setAlpha(0f);
-                    mMultiUserAvatar.animate()
-                            .alpha(1f)
-                            .setDuration(300)
-                            .setStartDelay(200)
-                            .setInterpolator(Interpolators.ALPHA_IN);
-                }
-                return true;
-            }
-        });
-
-    }
-
     @Override
     public void setVisibility(int visibility) {
         super.setVisibility(visibility);
@@ -474,6 +446,7 @@
             iconManager.setTint(iconColor);
         }
 
+        mDarkChange.setValue(new DarkChange(mEmptyTintRect, intensity, iconColor));
         applyDarkness(R.id.battery, mEmptyTintRect, intensity, iconColor);
         applyDarkness(R.id.clock, mEmptyTintRect, intensity, iconColor);
     }
@@ -536,4 +509,8 @@
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         Trace.endSection();
     }
+
+    public StateFlow<DarkChange> darkChangeFlow() {
+        return FlowKt.asStateFlow(mDarkChange);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 4d716c2..be336e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -45,7 +45,7 @@
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeViewStateProvider;
 import com.android.systemui.statusbar.CommandQueue;
@@ -69,8 +69,6 @@
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.settings.SecureSettings;
 
-import kotlin.Unit;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -78,6 +76,8 @@
 
 import javax.inject.Inject;
 
+import kotlin.Unit;
+
 /** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */
 public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> {
     private static final String TAG = "KeyguardStatusBarViewController";
@@ -119,6 +119,9 @@
     private final Object mLock = new Object();
     private final KeyguardLogger mLogger;
 
+    private View mSystemIconsContainer;
+    private final StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
+
     // TODO(b/273443374): remove
     private NotificationMediaManager mNotificationMediaManager;
 
@@ -286,7 +289,8 @@
             CommandQueue commandQueue,
             @Main Executor mainExecutor,
             KeyguardLogger logger,
-            NotificationMediaManager notificationMediaManager
+            NotificationMediaManager notificationMediaManager,
+            StatusOverlayHoverListenerFactory statusOverlayHoverListenerFactory
     ) {
         super(view);
         mCarrierTextController = carrierTextController;
@@ -339,6 +343,7 @@
                 this::updateViewState
         );
         mNotificationMediaManager = notificationMediaManager;
+        mStatusOverlayHoverListenerFactory = statusOverlayHoverListenerFactory;
     }
 
     @Override
@@ -363,6 +368,10 @@
             mTintedIconManager.setBlockList(getBlockedIcons());
             mStatusBarIconController.addIconGroup(mTintedIconManager);
         }
+        mSystemIconsContainer = mView.findViewById(R.id.system_icons);
+        StatusOverlayHoverListener hoverListener = mStatusOverlayHoverListenerFactory
+                .createDarkAwareListener(mSystemIconsContainer, mView.darkChangeFlow());
+        mSystemIconsContainer.setOnHoverListener(hoverListener);
         mView.setOnApplyWindowInsetsListener(
                 (view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider));
         mSecureSettings.registerContentObserverForUser(
@@ -376,6 +385,7 @@
 
     @Override
     protected void onViewDetached() {
+        mSystemIconsContainer.setOnHoverListener(null);
         mConfigurationController.removeCallback(mConfigurationListener);
         mAnimationScheduler.removeCallback(mAnimationCallback);
         mUserInfoController.removeCallback(mOnUserInfoChangedListener);
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 5c357d7..686efb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt
@@ -21,7 +21,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent
 import com.android.systemui.log.dagger.LSShadeTransitionLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.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/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index f26a84b..dc5eb0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -38,8 +38,6 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.settings.DisplayTracker;
@@ -65,7 +63,6 @@
 
     private final SysuiDarkIconDispatcher mStatusBarIconController;
     private final BatteryController mBatteryController;
-    private final boolean mUseNewLightBarLogic;
     private BiometricUnlockController mBiometricUnlockController;
 
     private LightBarTransitionsController mNavigationBarController;
@@ -123,10 +120,8 @@
             DarkIconDispatcher darkIconDispatcher,
             BatteryController batteryController,
             NavigationModeController navModeController,
-            FeatureFlags featureFlags,
             DumpManager dumpManager,
             DisplayTracker displayTracker) {
-        mUseNewLightBarLogic = featureFlags.isEnabled(Flags.NEW_LIGHT_BAR_LOGIC);
         mDarkIconColor = ctx.getColor(R.color.dark_mode_icon_color_single_tone);
         mLightIconColor = ctx.getColor(R.color.light_mode_icon_color_single_tone);
         mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
@@ -188,51 +183,31 @@
             final boolean last = mNavigationLight;
             mHasLightNavigationBar = isLight(appearance, navigationBarMode,
                     APPEARANCE_LIGHT_NAVIGATION_BARS);
-            if (mUseNewLightBarLogic) {
-                final boolean ignoreScrimForce = mDirectReplying && mNavbarColorManagedByIme;
-                final boolean darkForScrim = mForceDarkForScrim && !ignoreScrimForce;
-                final boolean lightForScrim = mForceLightForScrim && !ignoreScrimForce;
-                final boolean darkForQs = (mQsCustomizing || mQsExpanded) && !mBouncerVisible;
-                final boolean darkForTop = darkForQs || mGlobalActionsVisible;
-                mNavigationLight =
-                        ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop;
-                if (DEBUG_NAVBAR) {
-                    mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
-                            .append("onNavigationBarAppearanceChanged()")
-                            .append(" appearance=").append(appearance)
-                            .append(" nbModeChanged=").append(nbModeChanged)
-                            .append(" navigationBarMode=").append(navigationBarMode)
-                            .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
-                            .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
-                            .append(" ignoreScrimForce=").append(ignoreScrimForce)
-                            .append(" darkForScrim=").append(darkForScrim)
-                            .append(" lightForScrim=").append(lightForScrim)
-                            .append(" darkForQs=").append(darkForQs)
-                            .append(" darkForTop=").append(darkForTop)
-                            .append(" mNavigationLight=").append(mNavigationLight)
-                            .append(" last=").append(last)
-                            .append(" timestamp=").append(System.currentTimeMillis())
-                            .toString();
-                    if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
-                }
-            } else {
-                mNavigationLight = mHasLightNavigationBar
-                        && (mDirectReplying && mNavbarColorManagedByIme || !mForceDarkForScrim)
-                        && !mQsCustomizing;
-                if (DEBUG_NAVBAR) {
-                    mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
-                            .append("onNavigationBarAppearanceChanged()")
-                            .append(" appearance=").append(appearance)
-                            .append(" nbModeChanged=").append(nbModeChanged)
-                            .append(" navigationBarMode=").append(navigationBarMode)
-                            .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
-                            .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
-                            .append(" mNavigationLight=").append(mNavigationLight)
-                            .append(" last=").append(last)
-                            .append(" timestamp=").append(System.currentTimeMillis())
-                            .toString();
-                    if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
-                }
+            final boolean ignoreScrimForce = mDirectReplying && mNavbarColorManagedByIme;
+            final boolean darkForScrim = mForceDarkForScrim && !ignoreScrimForce;
+            final boolean lightForScrim = mForceLightForScrim && !ignoreScrimForce;
+            final boolean darkForQs = (mQsCustomizing || mQsExpanded) && !mBouncerVisible;
+            final boolean darkForTop = darkForQs || mGlobalActionsVisible;
+            mNavigationLight =
+                    ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop;
+            if (DEBUG_NAVBAR) {
+                mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
+                        .append("onNavigationBarAppearanceChanged()")
+                        .append(" appearance=").append(appearance)
+                        .append(" nbModeChanged=").append(nbModeChanged)
+                        .append(" navigationBarMode=").append(navigationBarMode)
+                        .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
+                        .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+                        .append(" ignoreScrimForce=").append(ignoreScrimForce)
+                        .append(" darkForScrim=").append(darkForScrim)
+                        .append(" lightForScrim=").append(lightForScrim)
+                        .append(" darkForQs=").append(darkForQs)
+                        .append(" darkForTop=").append(darkForTop)
+                        .append(" mNavigationLight=").append(mNavigationLight)
+                        .append(" last=").append(last)
+                        .append(" timestamp=").append(System.currentTimeMillis())
+                        .toString();
+                if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
             }
             if (mNavigationLight != last) {
                 updateNavigation();
@@ -311,66 +286,39 @@
 
     public void setScrimState(ScrimState scrimState, float scrimBehindAlpha,
             GradientColors scrimInFrontColor) {
-        if (mUseNewLightBarLogic) {
-            boolean bouncerVisibleLast = mBouncerVisible;
-            boolean forceDarkForScrimLast = mForceDarkForScrim;
-            boolean forceLightForScrimLast = mForceLightForScrim;
-            mBouncerVisible =
-                    scrimState == ScrimState.BOUNCER || scrimState == ScrimState.BOUNCER_SCRIMMED;
-            final boolean forceForScrim = mBouncerVisible
-                    || scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD;
-            final boolean scrimColorIsLight = scrimInFrontColor.supportsDarkText();
+        boolean bouncerVisibleLast = mBouncerVisible;
+        boolean forceDarkForScrimLast = mForceDarkForScrim;
+        boolean forceLightForScrimLast = mForceLightForScrim;
+        mBouncerVisible =
+                scrimState == ScrimState.BOUNCER || scrimState == ScrimState.BOUNCER_SCRIMMED;
+        final boolean forceForScrim = mBouncerVisible
+                || scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD;
+        final boolean scrimColorIsLight = scrimInFrontColor.supportsDarkText();
 
-            mForceDarkForScrim = forceForScrim && !scrimColorIsLight;
-            mForceLightForScrim = forceForScrim && scrimColorIsLight;
-            if (mBouncerVisible != bouncerVisibleLast) {
-                reevaluate();
-            } else if (mHasLightNavigationBar) {
-                if (mForceDarkForScrim != forceDarkForScrimLast) reevaluate();
-            } else {
-                if (mForceLightForScrim != forceLightForScrimLast) reevaluate();
-            }
-            if (DEBUG_NAVBAR) {
-                mLastSetScrimStateLog = getLogStringBuilder()
-                        .append("setScrimState()")
-                        .append(" scrimState=").append(scrimState)
-                        .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
-                        .append(" scrimInFrontColor=").append(scrimInFrontColor)
-                        .append(" forceForScrim=").append(forceForScrim)
-                        .append(" scrimColorIsLight=").append(scrimColorIsLight)
-                        .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
-                        .append(" mBouncerVisible=").append(mBouncerVisible)
-                        .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
-                        .append(" mForceLightForScrim=").append(mForceLightForScrim)
-                        .append(" timestamp=").append(System.currentTimeMillis())
-                        .toString();
-                if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
-            }
+        mForceDarkForScrim = forceForScrim && !scrimColorIsLight;
+        mForceLightForScrim = forceForScrim && scrimColorIsLight;
+        if (mBouncerVisible != bouncerVisibleLast) {
+            reevaluate();
+        } else if (mHasLightNavigationBar) {
+            if (mForceDarkForScrim != forceDarkForScrimLast) reevaluate();
         } else {
-            boolean forceDarkForScrimLast = mForceDarkForScrim;
-            // For BOUNCER/BOUNCER_SCRIMMED cases, we assume that alpha is always below threshold.
-            // This enables IMEs to control the navigation bar color.
-            // For other cases, scrim should be able to veto the light navigation bar.
-            // NOTE: this was also wrong for S and has been removed in the new logic.
-            mForceDarkForScrim = scrimState != ScrimState.BOUNCER
-                    && scrimState != ScrimState.BOUNCER_SCRIMMED
-                    && scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD
-                    && !scrimInFrontColor.supportsDarkText();
-            if (mHasLightNavigationBar && (mForceDarkForScrim != forceDarkForScrimLast)) {
-                reevaluate();
-            }
-            if (DEBUG_NAVBAR) {
-                mLastSetScrimStateLog = getLogStringBuilder()
-                        .append("setScrimState()")
-                        .append(" scrimState=").append(scrimState)
-                        .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
-                        .append(" scrimInFrontColor=").append(scrimInFrontColor)
-                        .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
-                        .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
-                        .append(" timestamp=").append(System.currentTimeMillis())
-                        .toString();
-                if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
-            }
+            if (mForceLightForScrim != forceLightForScrimLast) reevaluate();
+        }
+        if (DEBUG_NAVBAR) {
+            mLastSetScrimStateLog = getLogStringBuilder()
+                    .append("setScrimState()")
+                    .append(" scrimState=").append(scrimState)
+                    .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
+                    .append(" scrimInFrontColor=").append(scrimInFrontColor)
+                    .append(" forceForScrim=").append(forceForScrim)
+                    .append(" scrimColorIsLight=").append(scrimColorIsLight)
+                    .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+                    .append(" mBouncerVisible=").append(mBouncerVisible)
+                    .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
+                    .append(" mForceLightForScrim=").append(mForceLightForScrim)
+                    .append(" timestamp=").append(System.currentTimeMillis())
+                    .toString();
+            if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
         }
     }
 
@@ -498,7 +446,6 @@
         private final DarkIconDispatcher mDarkIconDispatcher;
         private final BatteryController mBatteryController;
         private final NavigationModeController mNavModeController;
-        private final FeatureFlags mFeatureFlags;
         private final DumpManager mDumpManager;
         private final DisplayTracker mDisplayTracker;
 
@@ -507,14 +454,12 @@
                 DarkIconDispatcher darkIconDispatcher,
                 BatteryController batteryController,
                 NavigationModeController navModeController,
-                FeatureFlags featureFlags,
                 DumpManager dumpManager,
                 DisplayTracker displayTracker) {
 
             mDarkIconDispatcher = darkIconDispatcher;
             mBatteryController = batteryController;
             mNavModeController = navModeController;
-            mFeatureFlags = featureFlags;
             mDumpManager = dumpManager;
             mDisplayTracker = displayTracker;
         }
@@ -522,7 +467,7 @@
         /** Create an {@link LightBarController} */
         public LightBarController create(Context context) {
             return new LightBarController(context, mDarkIconDispatcher, mBatteryController,
-                    mNavModeController, mFeatureFlags, mDumpManager, mDisplayTracker);
+                    mNavModeController, mDumpManager, mDisplayTracker);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 4569099..92c786f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -40,12 +40,17 @@
 
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.user.data.model.SelectedUserModel;
+import com.android.systemui.user.data.model.SelectionStatus;
+import com.android.systemui.user.data.repository.UserRepository;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import libcore.io.IoUtils;
 
@@ -59,7 +64,7 @@
  */
 @SysUISingleton
 public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable,
-        Dumpable {
+        Dumpable, CoreStartable {
 
     private static final String TAG = "LockscreenWallpaper";
 
@@ -72,6 +77,8 @@
     private final WallpaperManager mWallpaperManager;
     private final KeyguardUpdateMonitor mUpdateMonitor;
     private final Handler mH;
+    private final JavaAdapter mJavaAdapter;
+    private final UserRepository mUserRepository;
 
     private boolean mCached;
     private Bitmap mCache;
@@ -88,6 +95,8 @@
             DumpManager dumpManager,
             NotificationMediaManager mediaManager,
             @Main Handler mainHandler,
+            JavaAdapter javaAdapter,
+            UserRepository userRepository,
             UserTracker userTracker) {
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
         mWallpaperManager = wallpaperManager;
@@ -95,6 +104,8 @@
         mUpdateMonitor = keyguardUpdateMonitor;
         mMediaManager = mediaManager;
         mH = mainHandler;
+        mJavaAdapter = javaAdapter;
+        mUserRepository = userRepository;
 
         if (iWallpaperManager != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
             // Service is disabled on some devices like Automotive
@@ -106,6 +117,14 @@
         }
     }
 
+    @Override
+    public void start() {
+        if (!isLockscreenLiveWallpaperEnabled()) {
+            mJavaAdapter.alwaysCollectFlow(
+                    mUserRepository.getSelectedUser(), this::setSelectedUser);
+        }
+    }
+
     public Bitmap getBitmap() {
         assertLockscreenLiveWallpaperNotEnabled();
 
@@ -169,9 +188,15 @@
         }
     }
 
-    public void setCurrentUser(int user) {
+    private void setSelectedUser(SelectedUserModel selectedUserModel) {
         assertLockscreenLiveWallpaperNotEnabled();
 
+        if (selectedUserModel.getSelectionStatus().equals(SelectionStatus.SELECTION_IN_PROGRESS)) {
+            // Wait until the selection has finished before updating.
+            return;
+        }
+
+        int user = selectedUserModel.getUserInfo().id;
         if (user != mCurrentUserId) {
             if (mSelectedUser == null || user != mSelectedUser.getIdentifier()) {
                 mCached = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 313410a..0bf0f4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -24,6 +24,8 @@
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.ViewRefactorFlag;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -33,14 +35,11 @@
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
-import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.wm.shell.bubbles.Bubbles;
 
@@ -91,9 +90,7 @@
     private final ArrayList<Rect> mTintAreas = new ArrayList<>();
     private final Context mContext;
 
-    private final DemoModeController mDemoModeController;
-
-    private final FeatureFlags mFeatureFlags;
+    private final ViewRefactorFlag mShelfRefactor;
 
     private int mAodIconAppearTranslation;
 
@@ -125,12 +122,13 @@
             Optional<Bubbles> bubblesOptional,
             DemoModeController demoModeController,
             DarkIconDispatcher darkIconDispatcher,
-            FeatureFlags featureFlags, StatusBarWindowController statusBarWindowController,
+            FeatureFlags featureFlags,
+            StatusBarWindowController statusBarWindowController,
             ScreenOffAnimationController screenOffAnimationController) {
         mContrastColorUtil = ContrastColorUtil.getInstance(context);
         mContext = context;
         mStatusBarStateController = statusBarStateController;
-        mFeatureFlags = featureFlags;
+        mShelfRefactor = new ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR);
         mStatusBarStateController.addCallback(this);
         mMediaManager = notificationMediaManager;
         mDozeParameters = dozeParameters;
@@ -139,8 +137,7 @@
         wakeUpCoordinator.addListener(this);
         mBypassController = keyguardBypassController;
         mBubblesOptional = bubblesOptional;
-        mDemoModeController = demoModeController;
-        mDemoModeController.addCallback(this);
+        demoModeController.addCallback(this);
         mStatusBarWindowController = statusBarWindowController;
         mScreenOffAnimationController = screenOffAnimationController;
         notificationListener.addNotificationSettingsListener(mSettingsListener);
@@ -163,7 +160,6 @@
         LayoutInflater layoutInflater = LayoutInflater.from(context);
         mNotificationIconArea = inflateIconArea(layoutInflater);
         mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);
-
     }
 
     /**
@@ -185,23 +181,13 @@
         updateIconLayoutParams(mContext);
     }
 
-    /**
-     * Update position of the view, with optional animation
-     */
-    public void updatePosition(int x, AnimationProperties props, boolean animate) {
-        if (mAodIcons != null) {
-            PropertyAnimator.setProperty(mAodIcons, AnimatableProperty.TRANSLATION_X, x, props,
-                    animate);
-        }
-    }
-
     public void setupShelf(NotificationShelfController notificationShelfController) {
-        NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags);
+        mShelfRefactor.assertDisabled();
         mShelfIcons = notificationShelfController.getShelfIcons();
     }
 
     public void setShelfIcons(NotificationIconContainer icons) {
-        if (NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) {
+        if (mShelfRefactor.expectEnabled()) {
             mShelfIcons = icons;
         }
     }
@@ -239,8 +225,8 @@
 
     private void reloadDimens(Context context) {
         Resources res = context.getResources();
-        mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
-        mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding);
+        mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size_sp);
+        mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin);
         mAodIconAppearTranslation = res.getDimensionPixelSize(
                 R.dimen.shelf_appear_translation);
     }
@@ -303,6 +289,7 @@
         }
         return true;
     }
+
     /**
      * Updates the notifications with the given list of notifications to display.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index bef422c..3770c1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -54,19 +54,13 @@
  * correctly on the screen.
  */
 public class NotificationIconContainer extends ViewGroup {
-    /**
-     * A float value indicating how much before the overflow start the icons should transform into
-     * a dot. A value of 0 means that they are exactly at the end and a value of 1 means it starts
-     * 1 icon width early.
-     */
-    public static final float OVERFLOW_EARLY_AMOUNT = 0.2f;
     private static final int NO_VALUE = Integer.MIN_VALUE;
     private static final String TAG = "NotificationIconContainer";
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_OVERFLOW = false;
     private static final int CANNED_ANIMATION_DURATION = 100;
     private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() {
-        private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
+        private final AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
 
         @Override
         public AnimationFilter getAnimationFilter() {
@@ -75,7 +69,7 @@
     }.setDuration(200);
 
     private static final AnimationProperties ICON_ANIMATION_PROPERTIES = new AnimationProperties() {
-        private AnimationFilter mAnimationFilter = new AnimationFilter()
+        private final AnimationFilter mAnimationFilter = new AnimationFilter()
                 .animateX()
                 .animateY()
                 .animateAlpha()
@@ -92,7 +86,7 @@
      * Temporary AnimationProperties to avoid unnecessary allocations.
      */
     private static final AnimationProperties sTempProperties = new AnimationProperties() {
-        private AnimationFilter mAnimationFilter = new AnimationFilter();
+        private final AnimationFilter mAnimationFilter = new AnimationFilter();
 
         @Override
         public AnimationFilter getAnimationFilter() {
@@ -101,7 +95,7 @@
     };
 
     private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() {
-        private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
+        private final AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
 
         @Override
         public AnimationFilter getAnimationFilter() {
@@ -115,7 +109,7 @@
      */
     private static final AnimationProperties UNISOLATION_PROPERTY_OTHERS
             = new AnimationProperties() {
-        private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
+        private final AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
 
         @Override
         public AnimationFilter getAnimationFilter() {
@@ -128,7 +122,7 @@
      * This animates the translation back to the right position.
      */
     private static final AnimationProperties UNISOLATION_PROPERTY = new AnimationProperties() {
-        private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
+        private final AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
 
         @Override
         public AnimationFilter getAnimationFilter() {
@@ -147,7 +141,6 @@
     private boolean mIsStaticLayout = true;
     private final HashMap<View, IconState> mIconStates = new HashMap<>();
     private int mDotPadding;
-    private int mStaticDotRadius;
     private int mStaticDotDiameter;
     private int mActualLayoutWidth = NO_VALUE;
     private float mActualPaddingEnd = NO_VALUE;
@@ -170,7 +163,7 @@
     private boolean mIsShowingOverflowDot;
     private StatusBarIconView mIsolatedIcon;
     private Rect mIsolatedIconLocation;
-    private int[] mAbsolutePosition = new int[2];
+    private final int[] mAbsolutePosition = new int[2];
     private View mIsolatedIconForAnimation;
     private int mThemedTextColorPrimary;
 
@@ -186,8 +179,8 @@
         mMaxStaticIcons = getResources().getInteger(R.integer.max_notif_static_icons);
 
         mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
-        mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
-        mStaticDotDiameter = 2 * mStaticDotRadius;
+        int staticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
+        mStaticDotDiameter = 2 * staticDotRadius;
 
         final Context themedContext = new ContextThemeWrapper(getContext(),
                 com.android.internal.R.style.Theme_DeviceDefault_DayNight);
@@ -339,6 +332,7 @@
             }
         }
         if (child instanceof StatusBarIconView) {
+            ((StatusBarIconView) child).updateIconDimens();
             ((StatusBarIconView) child).setDozing(mDozing, false, 0);
         }
     }
@@ -447,9 +441,14 @@
     @VisibleForTesting
     boolean isOverflowing(boolean isLastChild, float translationX, float layoutEnd,
             float iconSize) {
-        // Layout end, as used here, does not include padding end.
-        final float overflowX = isLastChild ? layoutEnd : layoutEnd - iconSize;
-        return translationX >= overflowX;
+        if (isLastChild) {
+            return translationX + iconSize > layoutEnd;
+        } else {
+            // If the child is not the last child, we need to ensure that we have room for the next
+            // icon and the dot. The dot could be as large as an icon, so verify that we have room
+            // for 2 icons.
+            return translationX + iconSize * 2f > layoutEnd;
+        }
     }
 
     /**
@@ -489,10 +488,7 @@
             // First icon to overflow.
             if (firstOverflowIndex == -1 && isOverflowing) {
                 firstOverflowIndex = i;
-                mVisualOverflowStart = layoutEnd - mIconSize;
-                if (forceOverflow || mIsStaticLayout) {
-                    mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
-                }
+                mVisualOverflowStart = translationX;
             }
             final float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
                     ? ((StatusBarIconView) view).getIconScaleIncreased()
@@ -537,9 +533,10 @@
             IconState iconState = mIconStates.get(mIsolatedIcon);
             if (iconState != null) {
                 // Most of the time the icon isn't yet added when this is called but only happening
-                // later
-                iconState.setXTranslation(mIsolatedIconLocation.left - mAbsolutePosition[0]
-                        - (1 - mIsolatedIcon.getIconScale()) * mIsolatedIcon.getWidth() / 2.0f);
+                // later. The isolated icon position left should equal to the mIsolatedIconLocation
+                // to ensure the icon be put at the center of the HUN icon placeholder,
+                // {@See HeadsUpAppearanceController#updateIsolatedIconLocation}.
+                iconState.setXTranslation(mIsolatedIconLocation.left - mAbsolutePosition[0]);
                 iconState.visibleState = StatusBarIconView.STATE_ICON;
             }
         }
@@ -695,7 +692,6 @@
     }
 
     public class IconState extends ViewState {
-        public static final int NO_VALUE = NotificationIconContainer.NO_VALUE;
         public float iconAppearAmount = 1.0f;
         public float clampedAppearAmount = 1.0f;
         public int visibleState;
@@ -813,8 +809,6 @@
                     super.applyToView(view);
                 }
                 sTempProperties.setAnimationEndAction(null);
-                boolean inShelf = iconAppearAmount == 1.0f;
-                icon.setIsInShelf(inShelf);
             }
             justAdded = false;
             justReplaced = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index e6b76ad..6dbf707 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -48,6 +48,7 @@
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor;
 import com.android.systemui.privacy.PrivacyItem;
 import com.android.systemui.privacy.PrivacyItemController;
 import com.android.systemui.privacy.PrivacyType;
@@ -74,6 +75,7 @@
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.RingerModeTracker;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.DateFormatUtil;
 
 import java.io.PrintWriter;
@@ -121,9 +123,12 @@
     private final String mSlotCamera;
     private final String mSlotSensorsOff;
     private final String mSlotScreenRecord;
+    private final String mSlotConnectedDisplay;
     private final int mDisplayId;
     private final SharedPreferences mSharedPreferences;
     private final DateFormatUtil mDateFormatUtil;
+    private final JavaAdapter mJavaAdapter;
+    private final ConnectedDisplayInteractor mConnectedDisplayInteractor;
     private final TelecomManager mTelecomManager;
 
     private final Handler mHandler;
@@ -182,9 +187,13 @@
             @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
             RingerModeTracker ringerModeTracker,
             PrivacyItemController privacyItemController,
-            PrivacyLogger privacyLogger) {
+            PrivacyLogger privacyLogger,
+            ConnectedDisplayInteractor connectedDisplayInteractor,
+            JavaAdapter javaAdapter
+    ) {
         mIconController = iconController;
         mCommandQueue = commandQueue;
+        mConnectedDisplayInteractor = connectedDisplayInteractor;
         mBroadcastDispatcher = broadcastDispatcher;
         mHandler = new Handler(looper);
         mResources = resources;
@@ -211,8 +220,11 @@
         mTelecomManager = telecomManager;
         mRingerModeTracker = ringerModeTracker;
         mPrivacyLogger = privacyLogger;
+        mJavaAdapter = javaAdapter;
 
         mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
+        mSlotConnectedDisplay = resources.getString(
+                com.android.internal.R.string.status_bar_connected_display);
         mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot);
         mSlotBluetooth = resources.getString(com.android.internal.R.string.status_bar_bluetooth);
         mSlotTty = resources.getString(com.android.internal.R.string.status_bar_tty);
@@ -285,6 +297,10 @@
         mIconController.setIcon(mSlotCast, R.drawable.stat_sys_cast, null);
         mIconController.setIconVisibility(mSlotCast, false);
 
+        // connected display
+        mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display, null);
+        mIconController.setIconVisibility(mSlotConnectedDisplay, false);
+
         // hotspot
         mIconController.setIcon(mSlotHotspot, R.drawable.stat_sys_hotspot,
                 mResources.getString(R.string.accessibility_status_bar_hotspot));
@@ -342,6 +358,8 @@
         mSensorPrivacyController.addCallback(mSensorPrivacyListener);
         mLocationController.addCallback(this);
         mRecordingController.addCallback(this);
+        mJavaAdapter.alwaysCollectFlow(mConnectedDisplayInteractor.getConnectedDisplayState(),
+                this::onConnectedDisplayAvailabilityChanged);
 
         mCommandQueue.addCallback(this);
     }
@@ -528,7 +546,7 @@
                 userId = ActivityTaskManager.getService().getLastResumedActivityUserId();
                 boolean isManagedProfile = mUserManager.isManagedProfile(userId);
                 String accessibilityString = getManagedProfileAccessibilityString();
-                mHandler.post(() -> {
+                mMainExecutor.execute(() -> {
                     final boolean showIcon;
                     if (isManagedProfile && (!mKeyguardStateController.isShowing()
                             || mKeyguardStateController.isOccluded())) {
@@ -609,6 +627,13 @@
     }
 
     @Override
+    public void appTransitionFinished(int displayId) {
+        if (mDisplayId == displayId) {
+            updateManagedProfile();
+        }
+    }
+
+    @Override
     public void onKeyguardShowingChanged() {
         updateManagedProfile();
     }
@@ -800,4 +825,14 @@
         if (DEBUG) Log.d(TAG, "screenrecord: hiding icon");
         mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
     }
+
+    private void onConnectedDisplayAvailabilityChanged(ConnectedDisplayInteractor.State state) {
+        boolean visible = state != ConnectedDisplayInteractor.State.DISCONNECTED;
+
+        if (DEBUG) {
+            Log.d(TAG, "connected_display: " + (visible ? "showing" : "hiding") + " icon");
+        }
+
+        mIconController.setIconVisibility(mSlotConnectedDisplay, visible);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
index 15c6dcf..cc38405 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
@@ -31,6 +31,8 @@
 
     private final float mIconAlphaWhenOpaque;
 
+    private boolean mIsHeadsUp;
+
     private View mStartSide, mStatusIcons, mBattery;
     private Animator mCurrentAnimation;
 
@@ -52,15 +54,32 @@
         return ObjectAnimator.ofFloat(v, "alpha", v.getAlpha(), toAlpha);
     }
 
-    private float getNonBatteryClockAlphaFor(int mode) {
-        return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_NON_BATTERY_CLOCK
-                : !isOpaque(mode) ? ICON_ALPHA_WHEN_NOT_OPAQUE
-                : mIconAlphaWhenOpaque;
+    private float getStatusIconsAlphaFor(int mode) {
+        return getDefaultAlphaFor(mode);
+    }
+
+    private float getStartSideAlphaFor(int mode) {
+        // When there's a heads up notification, we need the start side icons to show regardless of
+        // lights out mode.
+        if (mIsHeadsUp) {
+            return getIconAlphaBasedOnOpacity(mode);
+        }
+        return getDefaultAlphaFor(mode);
     }
 
     private float getBatteryClockAlpha(int mode) {
         return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_BATTERY_CLOCK
-                : getNonBatteryClockAlphaFor(mode);
+                : getIconAlphaBasedOnOpacity(mode);
+    }
+
+    private float getDefaultAlphaFor(int mode) {
+        return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_NON_BATTERY_CLOCK
+                : getIconAlphaBasedOnOpacity(mode);
+    }
+
+    private float getIconAlphaBasedOnOpacity(int mode) {
+        return !isOpaque(mode) ? ICON_ALPHA_WHEN_NOT_OPAQUE
+                : mIconAlphaWhenOpaque;
     }
 
     private boolean isOpaque(int mode) {
@@ -74,19 +93,28 @@
         applyMode(newMode, animate);
     }
 
+    /** Informs this controller that the heads up notification state has changed. */
+    public void onHeadsUpStateChanged(boolean isHeadsUp) {
+        mIsHeadsUp = isHeadsUp;
+        // We want the icon to be fully visible when the HUN appears, so just immediately change the
+        // icon visibility and don't animate.
+        applyMode(getMode(), /* animate= */ false);
+    }
+
     private void applyMode(int mode, boolean animate) {
         if (mStartSide == null) return; // pre-init
-        float newAlpha = getNonBatteryClockAlphaFor(mode);
-        float newAlphaBC = getBatteryClockAlpha(mode);
+        float newStartSideAlpha = getStartSideAlphaFor(mode);
+        float newStatusIconsAlpha = getStatusIconsAlphaFor(mode);
+        float newBatteryAlpha = getBatteryClockAlpha(mode);
         if (mCurrentAnimation != null) {
             mCurrentAnimation.cancel();
         }
         if (animate) {
             AnimatorSet anims = new AnimatorSet();
             anims.playTogether(
-                    animateTransitionTo(mStartSide, newAlpha),
-                    animateTransitionTo(mStatusIcons, newAlpha),
-                    animateTransitionTo(mBattery, newAlphaBC)
+                    animateTransitionTo(mStartSide, newStartSideAlpha),
+                    animateTransitionTo(mStatusIcons, newStatusIconsAlpha),
+                    animateTransitionTo(mBattery, newBatteryAlpha)
                     );
             if (isLightsOut(mode)) {
                 anims.setDuration(LIGHTS_OUT_DURATION);
@@ -94,9 +122,9 @@
             anims.start();
             mCurrentAnimation = anims;
         } else {
-            mStartSide.setAlpha(newAlpha);
-            mStatusIcons.setAlpha(newAlpha);
-            mBattery.setAlpha(newAlphaBC);
+            mStartSide.setAlpha(newStartSideAlpha);
+            mStatusIcons.setAlpha(newStatusIconsAlpha);
+            mBattery.setAlpha(newBatteryAlpha);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index d546a84..83a040c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -208,25 +208,29 @@
         ViewGroup.LayoutParams layoutParams = getLayoutParams();
         mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
         layoutParams.height = mStatusBarHeight - waterfallTopInset;
+        updatePaddings();
+        setLayoutParams(layoutParams);
+    }
 
-        int statusBarPaddingTop = getResources().getDimensionPixelSize(
-                R.dimen.status_bar_padding_top);
+    private void updatePaddings() {
         int statusBarPaddingStart = getResources().getDimensionPixelSize(
                 R.dimen.status_bar_padding_start);
-        int statusBarPaddingEnd = getResources().getDimensionPixelSize(
-                R.dimen.status_bar_padding_end);
 
-        View sbContents = findViewById(R.id.status_bar_contents);
-        sbContents.setPaddingRelative(
+        findViewById(R.id.status_bar_contents).setPaddingRelative(
                 statusBarPaddingStart,
-                statusBarPaddingTop,
-                statusBarPaddingEnd,
+                getResources().getDimensionPixelSize(R.dimen.status_bar_padding_top),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end),
                 0);
 
         findViewById(R.id.notification_lights_out)
                 .setPaddingRelative(0, statusBarPaddingStart, 0, 0);
 
-        setLayoutParams(layoutParams);
+        findViewById(R.id.system_icons).setPaddingRelative(
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_start),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_top),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_end),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_bottom)
+        );
     }
 
     private void updateLayoutForCutout() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index e6cb68f..4de669c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -28,8 +28,10 @@
 import com.android.systemui.R
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeLogger
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.unfold.SysUIUnfoldComponent
@@ -42,6 +44,7 @@
 import java.util.Optional
 import javax.inject.Inject
 import javax.inject.Named
+import javax.inject.Provider
 
 private const val TAG = "PhoneStatusBarViewController"
 
@@ -51,13 +54,19 @@
     @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
     private val centralSurfaces: CentralSurfaces,
     private val shadeController: ShadeController,
+    private val shadeViewController: ShadeViewController,
+    private val windowRootView: Provider<WindowRootView>,
     private val shadeLogger: ShadeLogger,
     private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
     private val userChipViewModel: StatusBarUserChipViewModel,
     private val viewUtil: ViewUtil,
-    private val configurationController: ConfigurationController
+    private val featureFlags: FeatureFlags,
+    private val configurationController: ConfigurationController,
+    private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
 ) : ViewController<PhoneStatusBarView>(view) {
 
+    private lateinit var statusContainer: View
+
     private val configurationListener = object : ConfigurationController.ConfigurationListener {
         override fun onConfigChanged(newConfig: Configuration?) {
             mView.updateResources()
@@ -65,10 +74,14 @@
     }
 
     override fun onViewAttached() {
+        statusContainer = mView.requireViewById(R.id.system_icons)
+        statusContainer.setOnHoverListener(
+            statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer))
         if (moveFromCenterAnimationController == null) return
 
-        val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_start_side_except_heads_up)
-        val systemIconArea: ViewGroup = mView.findViewById(R.id.status_bar_end_side_content)
+        val statusBarLeftSide: View =
+                mView.requireViewById(R.id.status_bar_start_side_except_heads_up)
+        val systemIconArea: ViewGroup = mView.requireViewById(R.id.status_bar_end_side_content)
 
         val viewsToAnimate = arrayOf(
             statusBarLeftSide,
@@ -96,6 +109,7 @@
     }
 
     override fun onViewDetached() {
+        statusContainer.setOnHoverListener(null)
         progressProvider?.setReadyToHandleTransition(false)
         moveFromCenterAnimationController?.onViewDetached()
         configurationController.removeCallback(configurationListener)
@@ -136,10 +150,10 @@
     fun onTouch(event: MotionEvent) {
         if (centralSurfaces.statusBarWindowState == WINDOW_STATE_SHOWING) {
             val upOrCancel =
-                    event.action == MotionEvent.ACTION_UP ||
+                event.action == MotionEvent.ACTION_UP ||
                     event.action == MotionEvent.ACTION_CANCEL
             centralSurfaces.setInteracting(WINDOW_STATUS_BAR,
-                    !upOrCancel || shadeController.isExpandedVisible)
+                !upOrCancel || shadeController.isExpandedVisible)
         }
     }
 
@@ -157,27 +171,35 @@
             if (!centralSurfaces.commandQueuePanelsEnabled) {
                 if (event.action == MotionEvent.ACTION_DOWN) {
                     Log.v(TAG, String.format("onTouchForwardedFromStatusBar: panel disabled, " +
-                            "ignoring touch at (${event.x.toInt()},${event.y.toInt()})"))
+                        "ignoring touch at (${event.x.toInt()},${event.y.toInt()})"))
                 }
                 return false
             }
 
+            // If scene framework is enabled, route the touch to it and
+            // ignore the rest of the gesture.
+            if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+                windowRootView.get().dispatchTouchEvent(event)
+                return true
+            }
+
             if (event.action == MotionEvent.ACTION_DOWN) {
                 // If the view that would receive the touch is disabled, just have status
                 // bar eat the gesture.
-                if (!centralSurfaces.shadeViewController.isViewEnabled) {
+                if (!shadeViewController.isViewEnabled) {
                     shadeLogger.logMotionEvent(event,
-                            "onTouchForwardedFromStatusBar: panel view disabled")
+                        "onTouchForwardedFromStatusBar: panel view disabled")
                     return true
                 }
-                if (centralSurfaces.shadeViewController.isFullyCollapsed &&
-                        event.y < 1f) {
+                if (shadeViewController.isFullyCollapsed &&
+                    event.y < 1f) {
                     // b/235889526 Eat events on the top edge of the phone when collapsed
                     shadeLogger.logMotionEvent(event, "top edge touch ignored")
                     return true
                 }
+                shadeViewController.startTrackingExpansionFromStatusBar()
             }
-            return centralSurfaces.shadeViewController.handleExternalTouch(event)
+            return shadeViewController.handleExternalTouch(event)
         }
     }
 
@@ -221,31 +243,38 @@
         private val userChipViewModel: StatusBarUserChipViewModel,
         private val centralSurfaces: CentralSurfaces,
         private val shadeController: ShadeController,
+        private val shadeViewController: ShadeViewController,
+        private val windowRootView: Provider<WindowRootView>,
         private val shadeLogger: ShadeLogger,
         private val viewUtil: ViewUtil,
         private val configurationController: ConfigurationController,
+        private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
     ) {
         fun create(
             view: PhoneStatusBarView
         ): PhoneStatusBarViewController {
             val statusBarMoveFromCenterAnimationController =
-                    if (featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) {
-                        unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController()
-                    } else {
-                        null
-                    }
+                if (featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) {
+                    unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController()
+                } else {
+                    null
+                }
 
             return PhoneStatusBarViewController(
-                    view,
-                    progressProvider.getOrNull(),
-                    centralSurfaces,
-                    shadeController,
-                    shadeLogger,
-                    statusBarMoveFromCenterAnimationController,
-                    userChipViewModel,
-                    viewUtil,
-                    configurationController
+                view,
+                progressProvider.getOrNull(),
+                centralSurfaces,
+                shadeController,
+                shadeViewController,
+                windowRootView,
+                shadeLogger,
+                statusBarMoveFromCenterAnimationController,
+                userChipViewModel,
+                viewUtil,
+                featureFlags,
+                configurationController,
+                statusOverlayHoverListenerFactory,
             )
         }
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
index c817466..89c3a02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
@@ -18,6 +18,7 @@
 import android.view.View
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.unfold.FoldAodAnimationController
 import com.android.systemui.unfold.SysUIUnfoldComponent
@@ -37,8 +38,12 @@
     private val animations: List<ScreenOffAnimation> =
         listOfNotNull(foldToAodAnimation, unlockedScreenOffAnimation)
 
-    fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {
-        animations.forEach { it.initialize(centralSurfaces, lightRevealScrim) }
+    fun initialize(
+            centralSurfaces: CentralSurfaces,
+            shadeViewController: ShadeViewController,
+            lightRevealScrim: LightRevealScrim,
+    ) {
+        animations.forEach { it.initialize(centralSurfaces, shadeViewController, lightRevealScrim) }
         wakefulnessLifecycle.addObserver(this)
     }
 
@@ -197,7 +202,11 @@
 }
 
 interface ScreenOffAnimation {
-    fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {}
+    fun initialize(
+            centralSurfaces: CentralSurfaces,
+            shadeViewController: ShadeViewController,
+            lightRevealScrim: LightRevealScrim,
+    ) {}
 
     /**
      * Called when started going to sleep, should return true if the animation will be played
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 c42a6dc..fc66138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -48,18 +48,17 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.settingslib.Utils;
+import com.android.systemui.CoreStartable;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.keyguard.shared.model.ScrimAlpha;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -71,8 +70,10 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.AlarmTimeout;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -89,7 +90,8 @@
  * security method gets shown).
  */
 @SysUISingleton
-public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dumpable {
+public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dumpable,
+        CoreStartable {
 
     static final String TAG = "ScrimController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -207,6 +209,7 @@
     private final KeyguardVisibilityCallback mKeyguardVisibilityCallback;
     private final Handler mHandler;
     private final Executor mMainExecutor;
+    private final JavaAdapter mJavaAdapter;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -249,8 +252,6 @@
     private int mScrimsVisibility;
     private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
     private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
-    private final FeatureFlags mFeatureFlags;
-    private final boolean mUseNewLightBarLogic;
     private Consumer<Integer> mScrimVisibleListener;
     private boolean mBlankScreen;
     private boolean mScreenBlankingCallbackCalled;
@@ -268,6 +269,7 @@
     private boolean mKeyguardOccluded;
 
     private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final WallpaperRepository mWallpaperRepository;
     private CoroutineDispatcher mMainDispatcher;
     private boolean mIsBouncerToGoneTransitionRunning = false;
     private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
@@ -297,18 +299,17 @@
             DockManager dockManager,
             ConfigurationController configurationController,
             @Main Executor mainExecutor,
+            JavaAdapter javaAdapter,
             ScreenOffAnimationController screenOffAnimationController,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
+            WallpaperRepository wallpaperRepository,
             @Main CoroutineDispatcher mainDispatcher,
-            LargeScreenShadeInterpolator largeScreenShadeInterpolator,
-            FeatureFlags featureFlags) {
+            LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
         mScrimStateListener = lightBarController::setScrimState;
         mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
-        mFeatureFlags = featureFlags;
-        mUseNewLightBarLogic = featureFlags.isEnabled(Flags.NEW_LIGHT_BAR_LOGIC);
         mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
 
         mKeyguardStateController = keyguardStateController;
@@ -317,6 +318,7 @@
         mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
         mHandler = handler;
         mMainExecutor = mainExecutor;
+        mJavaAdapter = javaAdapter;
         mScreenOffAnimationController = screenOffAnimationController;
         mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
                 "hide_aod_wallpaper", mHandler);
@@ -348,9 +350,17 @@
         mColors = new GradientColors();
         mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mWallpaperRepository = wallpaperRepository;
         mMainDispatcher = mainDispatcher;
     }
 
+    @Override
+    public void start() {
+        mJavaAdapter.alwaysCollectFlow(
+                mWallpaperRepository.getWallpaperSupportsAmbientMode(),
+                this::setWallpaperSupportsAmbientMode);
+    }
+
     /**
      * Attach the controller to the supplied views.
      */
@@ -360,6 +370,9 @@
         mScrimBehind = behindScrim;
         mScrimInFront = scrimInFront;
         updateThemeColors();
+        mNotificationsScrim.setScrimName(getScrimName(mNotificationsScrim));
+        mScrimBehind.setScrimName(getScrimName(mScrimBehind));
+        mScrimInFront.setScrimName(getScrimName(mScrimInFront));
 
         behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
         mNotificationsScrim.enableRoundedCorners(true);
@@ -881,24 +894,11 @@
                     mBehindAlpha = 1;
                     mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
                 } else {
-                    if (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
-                        mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha(
-                                mPanelExpansionFraction * mDefaultScrimAlpha);
-                        mNotificationsAlpha =
-                                mLargeScreenShadeInterpolator.getNotificationScrimAlpha(
-                                        mPanelExpansionFraction);
-                    } else {
-                        // Behind scrim will finish fading in at 30% expansion.
-                        float behindFraction = MathUtils
-                                .constrainedMap(0f, 1f, 0f, 0.3f, mPanelExpansionFraction);
-                        mBehindAlpha = behindFraction * mDefaultScrimAlpha;
-                        // Delay fade-in of notification scrim a bit further, to coincide with the
-                        // behind scrim finishing fading in.
-                        // Also to coincide with the view starting to fade in, otherwise the empty
-                        // panel can be quite jarring.
-                        mNotificationsAlpha = MathUtils
-                                .constrainedMap(0f, 1f, 0.3f, 0.75f, mPanelExpansionFraction);
-                    }
+                    mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha(
+                            mPanelExpansionFraction * mDefaultScrimAlpha);
+                    mNotificationsAlpha =
+                            mLargeScreenShadeInterpolator.getNotificationScrimAlpha(
+                                    mPanelExpansionFraction);
                 }
                 mBehindTint = mState.getBehindTint();
                 mInFrontAlpha = 0;
@@ -1166,13 +1166,7 @@
         if (mClipsQsScrim && mQsBottomVisible) {
             alpha = mNotificationsAlpha;
         }
-        if (mUseNewLightBarLogic) {
-            mScrimStateListener.accept(mState, alpha, mColors);
-        } else {
-            // NOTE: This wasn't wrong, but it implied that each scrim might have different colors,
-            //  when in fact they all share the same GradientColors instance, which we own.
-            mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors());
-        }
+        mScrimStateListener.accept(mState, alpha, mColors);
     }
 
     private void dispatchScrimsVisible() {
@@ -1489,10 +1483,6 @@
         }
     }
 
-    public void setCurrentUser(int currentUser) {
-        // Don't care in the base class.
-    }
-
     private void updateThemeColors() {
         if (mScrimBehind == null) return;
         int background = Utils.getColorAttr(mScrimBehind.getContext(),
@@ -1500,15 +1490,8 @@
         int accent = Utils.getColorAccent(mScrimBehind.getContext()).getDefaultColor();
         mColors.setMainColor(background);
         mColors.setSecondaryColor(accent);
-        if (mUseNewLightBarLogic) {
-            final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background);
-            mColors.setSupportsDarkText(isBackgroundLight);
-        } else {
-            // NOTE: This was totally backward, but LightBarController was flipping it back.
-            // There may be other consumers of this which would struggle though
-            mColors.setSupportsDarkText(
-                    ColorUtils.calculateContrast(mColors.getMainColor(), Color.WHITE) > 4.5);
-        }
+        final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background);
+        mColors.setSupportsDarkText(isBackgroundLight);
 
         int surface = Utils.getColorAttr(mScrimBehind.getContext(),
                 com.android.internal.R.attr.materialColorSurface).getDefaultColor();
@@ -1568,7 +1551,7 @@
         pw.println(mState.getMaxLightRevealScrimAlpha());
     }
 
-    public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
+    private void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
         mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode;
         ScrimState[] states = ScrimState.values();
         for (int i = 0; i < states.length; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index c850d4f..ad18170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -117,11 +117,11 @@
      * status bar area is contiguous.
      */
     fun currentRotationHasCornerCutout(): Boolean {
-        val cutout = context.display.cutout ?: return false
+        val cutout = checkNotNull(context.display).cutout ?: return false
         val topBounds = cutout.boundingRectTop
 
         val point = Point()
-        context.display.getRealSize(point)
+        checkNotNull(context.display).getRealSize(point)
 
         return topBounds.left <= 0 || topBounds.right >= point.x
     }
@@ -161,7 +161,7 @@
      */
     fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Pair<Int, Int> =
         traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
-            val displayCutout = context.display.cutout
+            val displayCutout = checkNotNull(context.display).cutout
             val key = getCacheKey(rotation, displayCutout)
 
             val screenBounds = context.resources.configuration.windowConfiguration.maxBounds
@@ -198,7 +198,7 @@
     fun getStatusBarContentAreaForRotation(
         @Rotation rotation: Int
     ): Rect {
-        val displayCutout = context.display.cutout
+        val displayCutout = checkNotNull(context.display).cutout
         val key = getCacheKey(rotation, displayCutout)
         return insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
                 rotation, displayCutout, getResourcesForRotation(rotation, context), key)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 481cf3c..4b39854 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -16,25 +16,28 @@
 
 package com.android.systemui.statusbar.phone;
 
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 
 import javax.inject.Inject;
 
 /**
- * Ties the {@link CentralSurfaces} to {@link com.android.systemui.statusbar.policy.HeadsUpManager}.
+ * Ties the status bar to {@link com.android.systemui.statusbar.policy.HeadsUpManager}.
  */
-@CentralSurfacesComponent.CentralSurfacesScope
-public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener {
+@SysUISingleton
+public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener, CoreStartable {
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarWindowController mStatusBarWindowController;
     private final ShadeViewController mShadeViewController;
+    private final NotificationStackScrollLayoutController mNsslController;
     private final KeyguardBypassController mKeyguardBypassController;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final StatusBarStateController mStatusBarStateController;
@@ -45,14 +48,15 @@
             NotificationShadeWindowController notificationShadeWindowController,
             StatusBarWindowController statusBarWindowController,
             ShadeViewController shadeViewController,
+            NotificationStackScrollLayoutController nsslController,
             KeyguardBypassController keyguardBypassController,
             HeadsUpManagerPhone headsUpManager,
             StatusBarStateController statusBarStateController,
             NotificationRemoteInputManager notificationRemoteInputManager) {
-
         mNotificationShadeWindowController = notificationShadeWindowController;
         mStatusBarWindowController = statusBarWindowController;
         mShadeViewController = shadeViewController;
+        mNsslController = nsslController;
         mKeyguardBypassController = keyguardBypassController;
         mHeadsUpManager = headsUpManager;
         mStatusBarStateController = statusBarStateController;
@@ -60,6 +64,11 @@
     }
 
     @Override
+    public void start() {
+        mHeadsUpManager.addListener(this);
+    }
+
+    @Override
     public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
         if (inPinnedMode) {
             mNotificationShadeWindowController.setHeadsUpShowing(true);
@@ -85,8 +94,7 @@
                 //animation
                 // is finished.
                 mHeadsUpManager.setHeadsUpGoingAway(true);
-                mShadeViewController.getNotificationStackScrollLayoutController()
-                        .runAfterAnimationFinished(() -> {
+                mNsslController.runAfterAnimationFinished(() -> {
                     if (!mHeadsUpManager.hasPinnedHeadsUp()) {
                         mNotificationShadeWindowController.setHeadsUpShowing(false);
                         mHeadsUpManager.setHeadsUpGoingAway(false);
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 a8a834f..d5cb6b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -15,9 +15,7 @@
 package com.android.systemui.statusbar.phone;
 
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
-import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
-import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
 
 import android.annotation.Nullable;
@@ -25,10 +23,8 @@
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.ArraySet;
-import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.LinearLayout.LayoutParams;
 
@@ -42,14 +38,9 @@
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
 import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.StatusBarMobileView;
-import com.android.systemui.statusbar.StatusBarWifiView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
 import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
@@ -97,26 +88,16 @@
      */
     void setIcon(String slot, int resourceId, CharSequence contentDescription);
 
-    /** */
-    void setWifiIcon(String slot, WifiIconState state);
-
     /**
      * Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been
      * set up (inflated and added to the view hierarchy).
-     *
-     * This method completely replaces {@link #setWifiIcon} with the information from the new wifi
-     * data pipeline. Icons will automatically keep their state up to date, so we don't have to
-     * worry about funneling state objects through anymore.
      */
     void setNewWifiIcon();
 
-    /** */
-    void setMobileIcons(String slot, List<MobileIconState> states);
-
     /**
-     * This method completely replaces {@link #setMobileIcons} with the information from the new
-     * mobile data pipeline. Icons will automatically keep their state up to date, so we don't have
-     * to worry about funneling MobileIconState objects through anymore.
+     * Notify this class that there is a new set of mobile icons to display, keyed off of this list
+     * of subIds. The icons will be added and bound to the mobile data pipeline via
+     * {@link com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder}.
      */
     void setNewMobileIconSubIds(List<Integer> subIds);
     /**
@@ -173,24 +154,22 @@
      */
     class DarkIconManager extends IconManager {
         private final DarkIconDispatcher mDarkIconDispatcher;
-        private int mIconHPadding;
+        private final int mIconHorizontalMargin;
 
         public DarkIconManager(
                 LinearLayout linearLayout,
                 StatusBarLocation location,
-                StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(linearLayout,
                     location,
-                    statusBarPipelineFlags,
                     wifiUiAdapter,
                     mobileUiAdapter,
                     mobileContextProvider);
-            mIconHPadding = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.status_bar_icon_padding);
+            mIconHorizontalMargin = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.status_bar_icon_horizontal_margin);
             mDarkIconDispatcher = darkIconDispatcher;
         }
 
@@ -205,7 +184,7 @@
         protected LayoutParams onCreateLayoutParams() {
             LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                     ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
-            lp.setMargins(mIconHPadding, 0, mIconHPadding, 0);
+            lp.setMargins(mIconHorizontalMargin, 0, mIconHorizontalMargin, 0);
             return lp;
         }
 
@@ -245,7 +224,6 @@
 
         @SysUISingleton
         public static class Factory {
-            private final StatusBarPipelineFlags mStatusBarPipelineFlags;
             private final WifiUiAdapter mWifiUiAdapter;
             private final MobileContextProvider mMobileContextProvider;
             private final MobileUiAdapter mMobileUiAdapter;
@@ -253,12 +231,10 @@
 
             @Inject
             public Factory(
-                    StatusBarPipelineFlags statusBarPipelineFlags,
                     WifiUiAdapter wifiUiAdapter,
                     MobileContextProvider mobileContextProvider,
                     MobileUiAdapter mobileUiAdapter,
                     DarkIconDispatcher darkIconDispatcher) {
-                mStatusBarPipelineFlags = statusBarPipelineFlags;
                 mWifiUiAdapter = wifiUiAdapter;
                 mMobileContextProvider = mobileContextProvider;
                 mMobileUiAdapter = mobileUiAdapter;
@@ -269,7 +245,6 @@
                 return new DarkIconManager(
                         group,
                         location,
-                        mStatusBarPipelineFlags,
                         mWifiUiAdapter,
                         mMobileUiAdapter,
                         mMobileContextProvider,
@@ -287,14 +262,12 @@
         public TintedIconManager(
                 ViewGroup group,
                 StatusBarLocation location,
-                StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider
         ) {
             super(group,
                     location,
-                    statusBarPipelineFlags,
                     wifiUiAdapter,
                     mobileUiAdapter,
                     mobileContextProvider);
@@ -329,19 +302,16 @@
 
         @SysUISingleton
         public static class Factory {
-            private final StatusBarPipelineFlags mStatusBarPipelineFlags;
             private final WifiUiAdapter mWifiUiAdapter;
             private final MobileContextProvider mMobileContextProvider;
             private final MobileUiAdapter mMobileUiAdapter;
 
             @Inject
             public Factory(
-                    StatusBarPipelineFlags statusBarPipelineFlags,
                     WifiUiAdapter wifiUiAdapter,
                     MobileUiAdapter mobileUiAdapter,
                     MobileContextProvider mobileContextProvider
             ) {
-                mStatusBarPipelineFlags = statusBarPipelineFlags;
                 mWifiUiAdapter = wifiUiAdapter;
                 mMobileUiAdapter = mobileUiAdapter;
                 mMobileContextProvider = mobileContextProvider;
@@ -351,7 +321,6 @@
                 return new TintedIconManager(
                         group,
                         location,
-                        mStatusBarPipelineFlags,
                         mWifiUiAdapter,
                         mMobileUiAdapter,
                         mMobileContextProvider);
@@ -364,13 +333,12 @@
      */
     class IconManager implements DemoModeCommandReceiver {
         protected final ViewGroup mGroup;
-        private final StatusBarPipelineFlags mStatusBarPipelineFlags;
         private final MobileContextProvider mMobileContextProvider;
         private final LocationBasedWifiViewModel mWifiViewModel;
         private final MobileIconsViewModel mMobileIconsViewModel;
 
         protected final Context mContext;
-        protected final int mIconSize;
+        protected int mIconSize;
         // Whether or not these icons show up in dumpsys
         protected boolean mShouldLog = false;
         private StatusBarIconController mController;
@@ -386,35 +354,23 @@
         public IconManager(
                 ViewGroup group,
                 StatusBarLocation location,
-                StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider
         ) {
             mGroup = group;
-            mStatusBarPipelineFlags = statusBarPipelineFlags;
             mMobileContextProvider = mobileContextProvider;
             mContext = group.getContext();
-            mIconSize = mContext.getResources().getDimensionPixelSize(
-                    com.android.internal.R.dimen.status_bar_icon_size);
             mLocation = location;
 
-            if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
-                // This starts the flow for the new pipeline, and will notify us of changes if
-                // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
-                mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
-                MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
-            } else {
-                mMobileIconsViewModel = null;
-            }
+            reloadDimens();
 
-            if (statusBarPipelineFlags.runNewWifiIconBackend()) {
-                // This starts the flow for the new pipeline, and will notify us of changes if
-                // {@link StatusBarPipelineFlags#useNewWifiIcon} is also true.
-                mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
-            } else {
-                mWifiViewModel = null;
-            }
+            // This starts the flow for the new pipeline, and will notify us of changes via
+            // {@link #setNewMobileIconIds}
+            mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
+            MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
+
+            mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
         }
 
         public boolean isDemoable() {
@@ -462,15 +418,9 @@
                 case TYPE_ICON:
                     return addIcon(index, slot, blocked, holder.getIcon());
 
-                case TYPE_WIFI:
-                    return addWifiIcon(index, slot, holder.getWifiState());
-
                 case TYPE_WIFI_NEW:
                     return addNewWifiIcon(index, slot);
 
-                case TYPE_MOBILE:
-                    return addMobileIcon(index, slot, holder.getMobileState());
-
                 case TYPE_MOBILE_NEW:
                     return addNewMobileIcon(index, slot, holder.getTag());
             }
@@ -487,29 +437,7 @@
             return view;
         }
 
-        @VisibleForTesting
-        protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
-            if (mStatusBarPipelineFlags.useNewWifiIcon()) {
-                throw new IllegalStateException("Attempting to add a wifi icon while the new "
-                        + "icons are enabled is not supported");
-            }
-
-            final StatusBarWifiView view = onCreateStatusBarWifiView(slot);
-            view.applyWifiState(state);
-            mGroup.addView(view, index, onCreateLayoutParams());
-
-            if (mIsInDemoMode) {
-                mDemoStatusIcons.addDemoWifiView(state);
-            }
-            return view;
-        }
-
         protected StatusIconDisplayable addNewWifiIcon(int index, String slot) {
-            if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
-                throw new IllegalStateException("Attempting to add a wifi icon using the new"
-                        + "pipeline, but the enabled flag is false.");
-            }
-
             ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
             mGroup.addView(view, index, onCreateLayoutParams());
 
@@ -520,40 +448,12 @@
             return view;
         }
 
-        @VisibleForTesting
-        protected StatusIconDisplayable addMobileIcon(
-                int index,
-                String slot,
-                MobileIconState state
-        ) {
-            if (mStatusBarPipelineFlags.useNewMobileIcons()) {
-                throw new IllegalStateException("Attempting to add a mobile icon while the new "
-                        + "icons are enabled is not supported");
-            }
-
-            // Use the `subId` field as a key to query for the correct context
-            StatusBarMobileView mobileView = onCreateStatusBarMobileView(state.subId, slot);
-            mobileView.applyMobileState(state);
-            mGroup.addView(mobileView, index, onCreateLayoutParams());
-
-            if (mIsInDemoMode) {
-                Context mobileContext = mMobileContextProvider
-                        .getMobileContextForSub(state.subId, mContext);
-                mDemoStatusIcons.addMobileView(state, mobileContext);
-            }
-            return mobileView;
-        }
 
         protected StatusIconDisplayable addNewMobileIcon(
                 int index,
                 String slot,
                 int subId
         ) {
-            if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
-                throw new IllegalStateException("Attempting to add a mobile icon using the new"
-                        + "pipeline, but the enabled flag is false.");
-            }
-
             BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
             mGroup.addView(view, index, onCreateLayoutParams());
 
@@ -573,22 +473,10 @@
             return new StatusBarIconView(mContext, slot, null, blocked);
         }
 
-        private StatusBarWifiView onCreateStatusBarWifiView(String slot) {
-            StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, slot);
-            return view;
-        }
-
         private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
             return ModernStatusBarWifiView.constructAndBind(mContext, slot, mWifiViewModel);
         }
 
-        private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
-            Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
-            StatusBarMobileView view = StatusBarMobileView
-                    .fromContext(mobileContext, slot);
-            return view;
-        }
-
         private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
                 String slot, int subId) {
             Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
@@ -609,22 +497,9 @@
             mGroup.removeAllViews();
         }
 
-        protected void onDensityOrFontScaleChanged() {
-            for (int i = 0; i < mGroup.getChildCount(); i++) {
-                View child = mGroup.getChildAt(i);
-                LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
-                        ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
-                child.setLayoutParams(lp);
-            }
-        }
-
-        private void setHeightAndCenter(ImageView imageView, int height) {
-            ViewGroup.LayoutParams params = imageView.getLayoutParams();
-            params.height = height;
-            if (params instanceof LinearLayout.LayoutParams) {
-                ((LinearLayout.LayoutParams) params).gravity = Gravity.CENTER_VERTICAL;
-            }
-            imageView.setLayoutParams(params);
+        protected void reloadDimens() {
+            mIconSize = mContext.getResources().getDimensionPixelSize(
+                    com.android.internal.R.dimen.status_bar_icon_size_sp);
         }
 
         protected void onRemoveIcon(int viewIndex) {
@@ -644,12 +519,6 @@
                 case TYPE_ICON:
                     onSetIcon(viewIndex, holder.getIcon());
                     return;
-                case TYPE_WIFI:
-                    onSetWifiIcon(viewIndex, holder.getWifiState());
-                    return;
-                case TYPE_MOBILE:
-                    onSetMobileIcon(viewIndex, holder.getMobileState());
-                    return;
                 case TYPE_MOBILE_NEW:
                 case TYPE_WIFI_NEW:
                     // Nothing, the new icons update themselves
@@ -659,40 +528,6 @@
             }
         }
 
-        public void onSetWifiIcon(int viewIndex, WifiIconState state) {
-            View view = mGroup.getChildAt(viewIndex);
-            if (view instanceof StatusBarWifiView) {
-                ((StatusBarWifiView) view).applyWifiState(state);
-            } else if (view instanceof ModernStatusBarWifiView) {
-                // ModernStatusBarWifiView will automatically apply state based on its callbacks, so
-                // we don't need to call applyWifiState.
-            } else {
-                throw new IllegalStateException("View at " + viewIndex + " must be of type "
-                        + "StatusBarWifiView or ModernStatusBarWifiView");
-            }
-
-            if (mIsInDemoMode) {
-                mDemoStatusIcons.updateWifiState(state);
-            }
-        }
-
-        public void onSetMobileIcon(int viewIndex, MobileIconState state) {
-            View view = mGroup.getChildAt(viewIndex);
-            if (view instanceof StatusBarMobileView) {
-                ((StatusBarMobileView) view).applyMobileState(state);
-            } else {
-                // ModernStatusBarMobileView automatically updates via the ViewModel
-                throw new IllegalStateException("Cannot update ModernStatusBarMobileView outside of"
-                        + "the new pipeline");
-            }
-
-            if (mIsInDemoMode) {
-                Context mobileContext = mMobileContextProvider
-                        .getMobileContextForSub(state.subId, mContext);
-                mDemoStatusIcons.updateMobileState(state, mobileContext);
-            }
-        }
-
         @Override
         public void dispatchDemoCommand(String command, Bundle args) {
             if (!mDemoable) {
@@ -707,9 +542,7 @@
             mIsInDemoMode = true;
             if (mDemoStatusIcons == null) {
                 mDemoStatusIcons = createDemoStatusIcons();
-                if (mStatusBarPipelineFlags.useNewWifiIcon()) {
-                    mDemoStatusIcons.addModernWifiView(mWifiViewModel);
-                }
+                mDemoStatusIcons.addModernWifiView(mWifiViewModel);
             }
             mDemoStatusIcons.onDemoModeStarted();
         }
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 3a18423..553cbc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -39,8 +39,6 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -109,6 +107,7 @@
         }
 
         group.setController(this);
+        group.reloadDimens();
         mIconGroups.add(group);
         List<Slot> allSlots = mStatusBarIconList.getSlots();
         for (int i = 0; i < allSlots.size(); i++) {
@@ -201,35 +200,7 @@
     }
 
     @Override
-    public void setWifiIcon(String slot, WifiIconState state) {
-        if (mStatusBarPipelineFlags.useNewWifiIcon()) {
-            Log.d(TAG, "ignoring old pipeline callback because the new wifi icon is enabled");
-            return;
-        }
-
-        if (state == null) {
-            removeIcon(slot, 0);
-            return;
-        }
-
-        StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, 0);
-        if (holder == null) {
-            holder = StatusBarIconHolder.fromWifiIconState(state);
-            setIcon(slot, holder);
-        } else {
-            holder.setWifiState(state);
-            handleSet(slot, holder);
-        }
-    }
-
-
-    @Override
     public void setNewWifiIcon() {
-        if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
-            Log.d(TAG, "ignoring new pipeline callback because the new wifi icon is disabled");
-            return;
-        }
-
         String slot = mContext.getString(com.android.internal.R.string.status_bar_wifi);
         StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, /* tag= */ 0);
         if (holder == null) {
@@ -243,41 +214,10 @@
     /**
      * Accept a list of MobileIconStates, which all live in the same slot(?!), and then are sorted
      * by subId. Don't worry this definitely makes sense and works.
-     * @param slot da slot
-     * @param iconStates All of the mobile icon states
+     * @param subIds list of subscription ID integers that provide the key to the icon to display.
      */
     @Override
-    public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
-        if (mStatusBarPipelineFlags.useNewMobileIcons()) {
-            Log.d(TAG, "ignoring old pipeline callbacks, because the new mobile "
-                    + "icons are enabled");
-            return;
-        }
-        Slot mobileSlot = mStatusBarIconList.getSlot(slot);
-
-        // Reverse the sort order to show icons with left to right([Slot1][Slot2]..).
-        // StatusBarIconList has UI design that first items go to the right of second items.
-        Collections.reverse(iconStates);
-
-        for (MobileIconState state : iconStates) {
-            StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
-            if (holder == null) {
-                holder = StatusBarIconHolder.fromMobileIconState(state);
-                setIcon(slot, holder);
-            } else {
-                holder.setMobileState(state);
-                handleSet(slot, holder);
-            }
-        }
-    }
-
-    @Override
     public void setNewMobileIconSubIds(List<Integer> subIds) {
-        if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
-            Log.d(TAG, "ignoring new pipeline callback, "
-                    + "since the new mobile icons are disabled");
-            return;
-        }
         String slotName = mContext.getString(com.android.internal.R.string.status_bar_mobile);
         Slot mobileSlot = mStatusBarIconList.getSlot(slotName);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 833cb93..7048a78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -24,8 +24,6 @@
 
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
 
 import java.lang.annotation.Retention;
@@ -36,8 +34,6 @@
  */
 public class StatusBarIconHolder {
     public static final int TYPE_ICON = 0;
-    public static final int TYPE_WIFI = 1;
-    public static final int TYPE_MOBILE = 2;
     /**
      * TODO (b/249790733): address this once the new pipeline is in place
      * This type exists so that the new pipeline (see {@link MobileIconViewModel}) can be used
@@ -65,8 +61,6 @@
 
     @IntDef({
             TYPE_ICON,
-            TYPE_WIFI,
-            TYPE_MOBILE,
             TYPE_MOBILE_NEW,
             TYPE_WIFI_NEW
     })
@@ -74,8 +68,6 @@
     @interface IconType {}
 
     private StatusBarIcon mIcon;
-    private WifiIconState mWifiState;
-    private MobileIconState mMobileState;
     private @IconType int mType = TYPE_ICON;
     private int mTag = 0;
 
@@ -83,8 +75,6 @@
     public static String getTypeString(@IconType int type) {
         switch(type) {
             case TYPE_ICON: return "ICON";
-            case TYPE_WIFI: return "WIFI_OLD";
-            case TYPE_MOBILE: return "MOBILE_OLD";
             case TYPE_MOBILE_NEW: return "MOBILE_NEW";
             case TYPE_WIFI_NEW: return "WIFI_NEW";
             default: return "UNKNOWN";
@@ -101,25 +91,6 @@
         return wrapper;
     }
 
-    /** */
-    public static StatusBarIconHolder fromResId(
-            Context context,
-            int resId,
-            CharSequence contentDescription) {
-        StatusBarIconHolder holder = new StatusBarIconHolder();
-        holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
-                Icon.createWithResource( context, resId), 0, 0, contentDescription);
-        return holder;
-    }
-
-    /** */
-    public static StatusBarIconHolder fromWifiIconState(WifiIconState state) {
-        StatusBarIconHolder holder = new StatusBarIconHolder();
-        holder.mWifiState = state;
-        holder.mType = TYPE_WIFI;
-        return holder;
-    }
-
     /** Creates a new holder with for the new wifi icon. */
     public static StatusBarIconHolder forNewWifiIcon() {
         StatusBarIconHolder holder = new StatusBarIconHolder();
@@ -127,15 +98,6 @@
         return holder;
     }
 
-    /** */
-    public static StatusBarIconHolder fromMobileIconState(MobileIconState state) {
-        StatusBarIconHolder holder = new StatusBarIconHolder();
-        holder.mMobileState = state;
-        holder.mType = TYPE_MOBILE;
-        holder.mTag = state.subId;
-        return holder;
-    }
-
     /**
      * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
      * determine icon ordering and building the correct view model
@@ -177,32 +139,10 @@
         mIcon = icon;
     }
 
-    @Nullable
-    public WifiIconState getWifiState() {
-        return mWifiState;
-    }
-
-    public void setWifiState(WifiIconState state) {
-        mWifiState = state;
-    }
-
-    @Nullable
-    public MobileIconState getMobileState() {
-        return mMobileState;
-    }
-
-    public void setMobileState(MobileIconState state) {
-        mMobileState = state;
-    }
-
     public boolean isVisible() {
         switch (mType) {
             case TYPE_ICON:
                 return mIcon.visible;
-            case TYPE_WIFI:
-                return mWifiState.visible;
-            case TYPE_MOBILE:
-                return mMobileState.visible;
             case TYPE_MOBILE_NEW:
             case TYPE_WIFI_NEW:
                 // The new pipeline controls visibilities via the view model and view binder, so
@@ -223,14 +163,6 @@
                 mIcon.visible = visible;
                 break;
 
-            case TYPE_WIFI:
-                mWifiState.visible = visible;
-                break;
-
-            case TYPE_MOBILE:
-                mMobileState.visible = visible;
-                break;
-
             case TYPE_MOBILE_NEW:
             case TYPE_WIFI_NEW:
                 // The new pipeline controls visibilities via the view model and view binder, so
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 d97e64e..ea57eb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -18,10 +18,12 @@
 
 import static android.view.WindowInsets.Type.navigationBars;
 
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
 import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -54,16 +56,20 @@
 import com.android.keyguard.TrustGrantFlags;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.bouncer.ui.BouncerView;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.keyguard.data.BouncerView;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
@@ -86,8 +92,6 @@
 import com.android.systemui.unfold.FoldAodAnimationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -97,6 +101,9 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+import kotlinx.coroutines.CoroutineDispatcher;
+
 /**
  * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
  * via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -145,6 +152,7 @@
     // Local cache of expansion events, to avoid duplicates
     private float mFraction = -1f;
     private boolean mTracking = false;
+    private boolean mBouncerShowingOverDream;
 
     private final PrimaryBouncerExpansionCallback mExpansionCallback =
             new PrimaryBouncerExpansionCallback() {
@@ -183,8 +191,8 @@
 
             @Override
             public void onVisibilityChanged(boolean isVisible) {
-                mCentralSurfaces.setBouncerShowingOverDream(
-                        isVisible && mDreamOverlayStateController.isOverlayActive());
+                mBouncerShowingOverDream =
+                        isVisible && mDreamOverlayStateController.isOverlayActive();
 
                 if (!isVisible) {
                     mCentralSurfaces.setPrimaryBouncerHiddenFraction(EXPANSION_HIDDEN);
@@ -280,6 +288,9 @@
     private int mLastBiometricMode;
     private boolean mLastScreenOffAnimationPlaying;
     private float mQsExpansion;
+
+    private FeatureFlags mFlags;
+
     final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
     private boolean mIsBackAnimationEnabled;
     private final boolean mUdfpsNewTouchDetectionEnabled;
@@ -325,6 +336,7 @@
             }
         }
     };
+    private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
 
     @Inject
     public StatusBarKeyguardViewManager(
@@ -351,7 +363,10 @@
             BouncerView primaryBouncerView,
             AlternateBouncerInteractor alternateBouncerInteractor,
             UdfpsOverlayInteractor udfpsOverlayInteractor,
-            ActivityStarter activityStarter
+            ActivityStarter activityStarter,
+            KeyguardTransitionInteractor keyguardTransitionInteractor,
+            @Main CoroutineDispatcher mainDispatcher,
+            Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor
     ) {
         mContext = context;
         mViewMediatorCallback = callback;
@@ -369,6 +384,7 @@
         mShadeController = shadeController;
         mLatencyTracker = latencyTracker;
         mKeyguardSecurityModel = keyguardSecurityModel;
+        mFlags = featureFlags;
         mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mPrimaryBouncerView = primaryBouncerView;
@@ -380,8 +396,14 @@
         mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION);
         mUdfpsOverlayInteractor = udfpsOverlayInteractor;
         mActivityStarter = activityStarter;
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mMainDispatcher = mainDispatcher;
+        mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
     }
 
+    KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    CoroutineDispatcher mMainDispatcher;
+
     @Override
     public void registerCentralSurfaces(CentralSurfaces centralSurfaces,
             ShadeViewController shadeViewController,
@@ -428,6 +450,14 @@
         }
     }
 
+    private KeyguardStateController.Callback mKeyguardStateControllerCallback =
+            new KeyguardStateController.Callback() {
+                @Override
+                public void onUnlockedChanged() {
+                    updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide());
+                }
+            };
+
     private void registerListeners() {
         mKeyguardUpdateManager.registerCallback(mUpdateMonitorCallback);
         mStatusBarStateController.addCallback(this);
@@ -441,6 +471,32 @@
             mDockManager.addListener(mDockEventListener);
             mIsDocked = mDockManager.isDocked();
         }
+        mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+
+        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            mShadeViewController.postToView(() ->
+                    collectFlow(
+                        getViewRootImpl().getView(),
+                        combineFlows(
+                                mKeyguardTransitionInteractor.getFinishedKeyguardState(),
+                                mWmLockscreenVisibilityInteractor.get()
+                                        .getUsingKeyguardGoingAwayAnimation(),
+                                (finishedState, animating) ->
+                                        KeyguardInteractor.Companion.isKeyguardVisibleInState(
+                                                finishedState)
+                                                || animating),
+                        this::consumeShowStatusBarKeyguardView));
+        }
+    }
+
+    private void consumeShowStatusBarKeyguardView(boolean show) {
+        if (show != mLastShowing) {
+            if (show) {
+                show(null);
+            } else {
+                hide(0, 0);
+            }
+        }
     }
 
     /** Register a callback, to be invoked by the Predictive Back system. */
@@ -712,7 +768,7 @@
 
     @Override
     public void reset(boolean hideBouncerWhenShowing) {
-        if (mKeyguardStateController.isShowing()) {
+        if (mKeyguardStateController.isShowing() && !bouncerIsAnimatingAway()) {
             final boolean isOccluded = mKeyguardStateController.isOccluded();
             // Hide quick settings.
             mShadeViewController.resetViews(/* animate= */ !isOccluded);
@@ -760,7 +816,7 @@
 
     @Override
     public void onStartedWakingUp() {
-        mCentralSurfaces.getNotificationShadeWindowView().getWindowInsetsController()
+        mNotificationShadeWindowController.getWindowRootView().getWindowInsetsController()
                 .setAnimationsDisabled(false);
         NavigationBarView navBarView = mCentralSurfaces.getNavigationBarView();
         if (navBarView != null) {
@@ -774,7 +830,7 @@
 
     @Override
     public void onStartedGoingToSleep() {
-        mCentralSurfaces.getNotificationShadeWindowView().getWindowInsetsController()
+        mNotificationShadeWindowController.getWindowRootView().getWindowInsetsController()
                 .setAnimationsDisabled(true);
         NavigationBarView navBarView = mCentralSurfaces.getNavigationBarView();
         if (navBarView != null) {
@@ -833,6 +889,11 @@
     }
 
     @Override
+    public boolean isBouncerShowingOverDream() {
+        return mBouncerShowingOverDream;
+    }
+
+    @Override
     public void setOccluded(boolean occluded, boolean animate) {
         final boolean wasOccluded = mKeyguardStateController.isOccluded();
         final boolean isOccluding = !wasOccluded && occluded;
@@ -1124,7 +1185,7 @@
             if (view != null) {
                 view.setVisibility(View.VISIBLE);
             }
-            mCentralSurfaces.getNotificationShadeWindowView().getWindowInsetsController()
+            mNotificationShadeWindowController.getWindowRootView().getWindowInsetsController()
                     .show(navigationBars());
         }
     };
@@ -1202,7 +1263,7 @@
                 }
             } else {
                 mNotificationContainer.removeCallbacks(mMakeNavigationBarVisibleRunnable);
-                mCentralSurfaces.getNotificationShadeWindowView().getWindowInsetsController()
+                mNotificationShadeWindowController.getWindowRootView().getWindowInsetsController()
                         .hide(navigationBars());
             }
         }
@@ -1307,6 +1368,10 @@
             hideAlternateBouncer(false);
             executeAfterKeyguardGoneAction();
         }
+
+        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+        }
     }
 
     /** Display security message to relevant KeyguardMessageArea. */
@@ -1322,7 +1387,7 @@
 
     @Override
     public ViewRootImpl getViewRootImpl() {
-        ViewGroup viewGroup = mNotificationShadeWindowController.getNotificationShadeView();
+        ViewGroup viewGroup = mNotificationShadeWindowController.getWindowRootView();
         if (viewGroup != null) {
             return viewGroup.getViewRootImpl();
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index 9f69db9..b67ec58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -3,6 +3,9 @@
 import android.view.View
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.NotificationShadeWindowController
 
 /**
  * A [ActivityLaunchAnimator.Controller] that takes care of collapsing the status bar at the right
@@ -10,36 +13,38 @@
  */
 class StatusBarLaunchAnimatorController(
     private val delegate: ActivityLaunchAnimator.Controller,
-    private val centralSurfaces: CentralSurfaces,
+    private val shadeViewController: ShadeViewController,
+    private val shadeController: ShadeController,
+    private val notificationShadeWindowController: NotificationShadeWindowController,
     private val isLaunchForActivity: Boolean = true
 ) : ActivityLaunchAnimator.Controller by delegate {
     // Always sync the opening window with the shade, given that we draw a hole punch in the shade
     // of the same size and position as the opening app to make it visible.
     override val openingWindowSyncView: View?
-        get() = centralSurfaces.notificationShadeWindowView
+        get() = notificationShadeWindowController.windowRootView
 
     override fun onIntentStarted(willAnimate: Boolean) {
         delegate.onIntentStarted(willAnimate)
         if (willAnimate) {
-            centralSurfaces.shadeViewController.setIsLaunchAnimationRunning(true)
+            shadeViewController.setIsLaunchAnimationRunning(true)
         } else {
-            centralSurfaces.collapsePanelOnMainThread()
+            shadeController.collapseOnMainThread()
         }
     }
 
     override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
         delegate.onLaunchAnimationStart(isExpandingFullyAbove)
-        centralSurfaces.shadeViewController.setIsLaunchAnimationRunning(true)
+        shadeViewController.setIsLaunchAnimationRunning(true)
         if (!isExpandingFullyAbove) {
-            centralSurfaces.shadeViewController.collapseWithDuration(
+            shadeViewController.collapseWithDuration(
                 ActivityLaunchAnimator.TIMINGS.totalDuration.toInt())
         }
     }
 
     override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
         delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
-        centralSurfaces.shadeViewController.setIsLaunchAnimationRunning(false)
-        centralSurfaces.onLaunchAnimationEnd(isExpandingFullyAbove)
+        shadeViewController.setIsLaunchAnimationRunning(false)
+        shadeController.onLaunchAnimationEnd(isExpandingFullyAbove)
     }
 
     override fun onLaunchAnimationProgress(
@@ -48,12 +53,12 @@
         linearProgress: Float
     ) {
         delegate.onLaunchAnimationProgress(state, progress, linearProgress)
-        centralSurfaces.shadeViewController.applyLaunchAnimationProgress(linearProgress)
+        shadeViewController.applyLaunchAnimationProgress(linearProgress)
     }
 
     override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
         delegate.onLaunchAnimationCancelled()
-        centralSurfaces.shadeViewController.setIsLaunchAnimationRunning(false)
-        centralSurfaces.onLaunchAnimationCancelled(isLaunchForActivity)
+        shadeViewController.setIsLaunchAnimationRunning(false)
+        shadeController.onLaunchAnimationCancelled(isLaunchForActivity)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 7bbb03b..8de213f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -54,8 +54,10 @@
 import com.android.systemui.EventLogTags;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeViewController;
@@ -63,6 +65,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -93,6 +96,7 @@
 class StatusBarNotificationActivityStarter implements NotificationActivityStarter {
 
     private final Context mContext;
+    private final int mDisplayId;
 
     private final Handler mMainThreadHandler;
     private final Executor mUiBgExecutor;
@@ -119,11 +123,12 @@
     private final MetricsLogger mMetricsLogger;
     private final StatusBarNotificationActivityStarterLogger mLogger;
 
-    private final CentralSurfaces mCentralSurfaces;
     private final NotificationPresenter mPresenter;
-    private final ShadeViewController mNotificationPanel;
+    private final ShadeViewController mShadeViewController;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final ActivityLaunchAnimator mActivityLaunchAnimator;
     private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
+    private final PowerInteractor mPowerInteractor;
     private final UserTracker mUserTracker;
     private final OnUserInteractionCallback mOnUserInteractionCallback;
 
@@ -132,6 +137,7 @@
     @Inject
     StatusBarNotificationActivityStarter(
             Context context,
+            @DisplayId int displayId,
             Handler mainThreadHandler,
             Executor uiBgExecutor,
             NotificationVisibilityProvider visibilityProvider,
@@ -154,15 +160,17 @@
             MetricsLogger metricsLogger,
             StatusBarNotificationActivityStarterLogger logger,
             OnUserInteractionCallback onUserInteractionCallback,
-            CentralSurfaces centralSurfaces,
             NotificationPresenter presenter,
-            ShadeViewController panel,
+            ShadeViewController shadeViewController,
+            NotificationShadeWindowController notificationShadeWindowController,
             ActivityLaunchAnimator activityLaunchAnimator,
             NotificationLaunchAnimatorControllerProvider notificationAnimationProvider,
             LaunchFullScreenIntentProvider launchFullScreenIntentProvider,
+            PowerInteractor powerInteractor,
             FeatureFlags featureFlags,
             UserTracker userTracker) {
         mContext = context;
+        mDisplayId = displayId;
         mMainThreadHandler = mainThreadHandler;
         mUiBgExecutor = uiBgExecutor;
         mVisibilityProvider = visibilityProvider;
@@ -182,16 +190,16 @@
         mLockPatternUtils = lockPatternUtils;
         mStatusBarRemoteInputCallback = remoteInputCallback;
         mActivityIntentHelper = activityIntentHelper;
+        mNotificationShadeWindowController = notificationShadeWindowController;
         mFeatureFlags = featureFlags;
         mMetricsLogger = metricsLogger;
         mLogger = logger;
         mOnUserInteractionCallback = onUserInteractionCallback;
-        // TODO: use KeyguardStateController#isOccluded to remove this dependency
-        mCentralSurfaces = centralSurfaces;
         mPresenter = presenter;
-        mNotificationPanel = panel;
+        mShadeViewController = shadeViewController;
         mActivityLaunchAnimator = activityLaunchAnimator;
         mNotificationAnimationProvider = notificationAnimationProvider;
+        mPowerInteractor = powerInteractor;
         mUserTracker = userTracker;
 
         launchFullScreenIntentProvider.registerListener(entry -> launchFullScreenIntent(entry));
@@ -207,8 +215,7 @@
     public void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row) {
         mLogger.logStartingActivityFromClick(entry);
 
-        if (mRemoteInputManager.isRemoteInputActive(entry)
-                && !TextUtils.isEmpty(row.getActiveRemoteInputText())) {
+        if (mRemoteInputManager.isRemoteInputActive(entry)) {
             // We have an active remote input typed and the user clicked on the notification.
             // this was probably unintentional, so we're closing the edit text instead.
             mRemoteInputManager.closeRemoteInputs();
@@ -233,7 +240,7 @@
                 && mActivityIntentHelper.wouldPendingLaunchResolverActivity(intent,
                 mLockscreenUserManager.getCurrentUserId());
         final boolean animate = !willLaunchResolverActivity
-                && mCentralSurfaces.shouldAnimateLaunch(isActivityIntent);
+                && mActivityStarter.shouldAnimateLaunch(isActivityIntent);
         boolean showOverLockscreen = mKeyguardStateController.isShowing() && intent != null
                 && mActivityIntentHelper.wouldPendingShowOverLockscreen(intent,
                 mLockscreenUserManager.getCurrentUserId());
@@ -276,7 +283,7 @@
             mShadeController.addPostCollapseAction(runnable);
             mShadeController.collapseShade(true /* animate */);
         } else if (mKeyguardStateController.isShowing()
-                && mCentralSurfaces.isOccluded()) {
+                && mKeyguardStateController.isOccluded()) {
             mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
             mShadeController.collapseShade();
         } else {
@@ -284,7 +291,7 @@
         }
 
         // Always defer the keyguard dismiss when animating.
-        return animate || !mNotificationPanel.isFullyCollapsed();
+        return animate || !mShadeViewController.isFullyCollapsed();
     }
 
     private void handleNotificationClickAfterPanelCollapsed(
@@ -319,7 +326,7 @@
                     removeHunAfterClick(row);
                     // Show work challenge, do not run PendingIntent and
                     // remove notification
-                    collapseOnMainThread();
+                    mShadeController.collapseOnMainThread();
                     return;
                 }
             }
@@ -436,7 +443,9 @@
             ActivityLaunchAnimator.Controller animationController =
                     new StatusBarLaunchAnimatorController(
                             mNotificationAnimationProvider.getAnimatorController(row, null),
-                            mCentralSurfaces,
+                            mShadeViewController,
+                            mShadeController,
+                            mNotificationShadeWindowController,
                             isActivityIntent);
             mActivityLaunchAnimator.startPendingIntentWithAnimation(
                     animationController,
@@ -446,11 +455,11 @@
                         long eventTime = row.getAndResetLastActionUpTime();
                         Bundle options = eventTime > 0
                                 ? getActivityOptions(
-                                mCentralSurfaces.getDisplayId(),
+                                mDisplayId,
                                 adapter,
                                 mKeyguardStateController.isShowing(),
                                 eventTime)
-                                : getActivityOptions(mCentralSurfaces.getDisplayId(), adapter);
+                                : getActivityOptions(mDisplayId, adapter);
                         int result = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
                                 null, null, options);
                         mLogger.logSendPendingIntent(entry, intent, result);
@@ -467,7 +476,7 @@
     @Override
     public void startNotificationGutsIntent(final Intent intent, final int appUid,
             ExpandableNotificationRow row) {
-        boolean animate = mCentralSurfaces.shouldAnimateLaunch(true /* isActivityIntent */);
+        boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
         ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
             @Override
             public boolean onDismiss() {
@@ -475,14 +484,17 @@
                     ActivityLaunchAnimator.Controller animationController =
                             new StatusBarLaunchAnimatorController(
                                     mNotificationAnimationProvider.getAnimatorController(row),
-                                    mCentralSurfaces, true /* isActivityIntent */);
+                                    mShadeViewController,
+                                    mShadeController,
+                                    mNotificationShadeWindowController,
+                                    true /* isActivityIntent */);
 
                     mActivityLaunchAnimator.startIntentWithAnimation(
                             animationController, animate, intent.getPackage(),
                             (adapter) -> TaskStackBuilder.create(mContext)
                                     .addNextIntentWithParentStack(intent)
                                     .startActivities(getActivityOptions(
-                                            mCentralSurfaces.getDisplayId(),
+                                            mDisplayId,
                                             adapter),
                                             new UserHandle(UserHandle.getUserId(appUid))));
                 });
@@ -500,7 +512,7 @@
 
     @Override
     public void startHistoryIntent(View view, boolean showHistory) {
-        boolean animate = mCentralSurfaces.shouldAnimateLaunch(true /* isActivityIntent */);
+        boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
         ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
             @Override
             public boolean onDismiss() {
@@ -520,14 +532,17 @@
                             );
                     ActivityLaunchAnimator.Controller animationController =
                             viewController == null ? null
-                                : new StatusBarLaunchAnimatorController(viewController,
-                                        mCentralSurfaces,
-                                    true /* isActivityIntent */);
+                                : new StatusBarLaunchAnimatorController(
+                                        viewController,
+                                        mShadeViewController,
+                                        mShadeController,
+                                        mNotificationShadeWindowController,
+                                        true /* isActivityIntent */);
 
                     mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate,
                             intent.getPackage(),
                             (adapter) -> tsb.startActivities(
-                                    getActivityOptions(mCentralSurfaces.getDisplayId(), adapter),
+                                    getActivityOptions(mDisplayId, adapter),
                                     mUserTracker.getUserHandle()));
                 });
                 return true;
@@ -580,7 +595,7 @@
         try {
             EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
                     entry.getKey());
-            mCentralSurfaces.wakeUpForFullScreenIntent();
+            mPowerInteractor.wakeUpForFullScreenIntent();
 
             ActivityOptions options = ActivityOptions.makeBasic();
             options.setPendingIntentBackgroundActivityStartMode(
@@ -621,11 +636,4 @@
         return true;
     }
 
-    private void collapseOnMainThread() {
-        if (Looper.getMainLooper().isCurrentThread()) {
-            mShadeController.collapseShade();
-        } else {
-            mMainThreadHandler.post(mShadeController::collapseShade);
-        }
-    }
 }
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 12f023b..d07378e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -19,10 +19,10 @@
 import android.app.PendingIntent
 import com.android.systemui.log.dagger.NotifInteractionLog
 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.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.core.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 dfaee4c..ecc996c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -22,25 +22,24 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemClock;
 import android.service.notification.StatusBarNotification;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
 import android.util.Log;
 import android.util.Slog;
 import android.view.View;
-import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.InitController;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -51,27 +50,22 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
-import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
 
-@CentralSurfacesComponent.CentralSurfacesScope
-class StatusBarNotificationPresenter implements NotificationPresenter,
-        NotificationRowBinderImpl.BindRowCallback,
-        CommandQueue.Callbacks {
+@SysUISingleton
+class StatusBarNotificationPresenter implements NotificationPresenter, CommandQueue.Callbacks {
     private static final String TAG = "StatusBarNotificationPresenter";
 
     private final ActivityStarter mActivityStarter;
@@ -81,20 +75,17 @@
     private final NotifShadeEventSource mNotifShadeEventSource;
     private final NotificationMediaManager mMediaManager;
     private final NotificationGutsManager mGutsManager;
-
     private final ShadeViewController mNotificationPanel;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final AboveShelfObserver mAboveShelfObserver;
     private final DozeScrimController mDozeScrimController;
-    private final KeyguardIndicationController mKeyguardIndicationController;
-    private final CentralSurfaces mCentralSurfaces;
+    private final NotificationsInteractor mNotificationsInteractor;
+    private final NotificationStackScrollLayoutController mNsslController;
     private final LockscreenShadeTransitionController mShadeTransitionController;
+    private final PowerInteractor mPowerInteractor;
     private final CommandQueue mCommandQueue;
-
-    private final AccessibilityManager mAccessibilityManager;
     private final KeyguardManager mKeyguardManager;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
-    private final NotifPipelineFlags mNotifPipelineFlags;
     private final IStatusBarService mBarService;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final NotificationListContainer mNotifListContainer;
@@ -115,20 +106,18 @@
             NotificationShadeWindowController notificationShadeWindowController,
             DynamicPrivacyController dynamicPrivacyController,
             KeyguardStateController keyguardStateController,
-            KeyguardIndicationController keyguardIndicationController,
-            CentralSurfaces centralSurfaces,
+            NotificationsInteractor notificationsInteractor,
             LockscreenShadeTransitionController shadeTransitionController,
+            PowerInteractor powerInteractor,
             CommandQueue commandQueue,
             NotificationLockscreenUserManager lockscreenUserManager,
             SysuiStatusBarStateController sysuiStatusBarStateController,
             NotifShadeEventSource notifShadeEventSource,
             NotificationMediaManager notificationMediaManager,
             NotificationGutsManager notificationGutsManager,
-            LockscreenGestureLogger lockscreenGestureLogger,
             InitController initController,
             NotificationInterruptStateProvider notificationInterruptStateProvider,
             NotificationRemoteInputManager remoteInputManager,
-            NotifPipelineFlags notifPipelineFlags,
             NotificationRemoteInputManager.Callback remoteInputManagerCallback,
             NotificationListContainer notificationListContainer) {
         mActivityStarter = activityStarter;
@@ -137,10 +126,10 @@
         mQsController = quickSettingsController;
         mHeadsUpManager = headsUp;
         mDynamicPrivacyController = dynamicPrivacyController;
-        mKeyguardIndicationController = keyguardIndicationController;
-        // TODO: use KeyguardStateController#isOccluded to remove this dependency
-        mCentralSurfaces = centralSurfaces;
+        mNotificationsInteractor = notificationsInteractor;
+        mNsslController = stackScrollerController;
         mShadeTransitionController = shadeTransitionController;
+        mPowerInteractor = powerInteractor;
         mCommandQueue = commandQueue;
         mLockscreenUserManager = lockscreenUserManager;
         mStatusBarStateController = sysuiStatusBarStateController;
@@ -149,10 +138,8 @@
         mGutsManager = notificationGutsManager;
         mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView());
         mNotificationShadeWindowController = notificationShadeWindowController;
-        mNotifPipelineFlags = notifPipelineFlags;
         mAboveShelfObserver.setListener(statusBarWindow.findViewById(
                 R.id.notification_container_parent));
-        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mDozeScrimController = dozeScrimController;
         mKeyguardManager = context.getSystemService(KeyguardManager.class);
         mBarService = IStatusBarService.Stub.asInterface(
@@ -170,10 +157,9 @@
         }
         remoteInputManager.setUpWithCallback(
                 remoteInputManagerCallback,
-                mNotificationPanel.getShadeNotificationPresenter().createRemoteInputDelegate());
+                mNsslController.createDelegate());
 
         initController.addPostInitTask(() -> {
-            mKeyguardIndicationController.init();
             mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
             mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
             notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
@@ -203,7 +189,7 @@
     }
 
     private void maybeEndAmbientPulse() {
-        if (mNotificationPanel.getShadeNotificationPresenter().hasPulsingNotifications()
+        if (mNsslController.getNotificationListContainer().hasPulsingNotifications()
                 && !mHeadsUpManager.hasNotifications()) {
             // We were showing a pulse for a notification, but no notifications are pulsing anymore.
             // Finish the pulse.
@@ -218,7 +204,6 @@
         // End old BaseStatusBar.userSwitched
         mCommandQueue.animateCollapsePanels();
         mMediaManager.clearCurrentMediaNotification();
-        mCentralSurfaces.setLockscreenUser(newUserId);
         updateMediaMetaData(true, false);
     }
 
@@ -242,9 +227,7 @@
     public void onExpandClicked(NotificationEntry clickedEntry, View clickedView,
             boolean nowExpanded) {
         mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
-        mCentralSurfaces.wakeUpIfDozing(
-                SystemClock.uptimeMillis(), "NOTIFICATION_CLICK",
-                PowerManager.WAKE_REASON_GESTURE);
+        mPowerInteractor.wakeUpIfDozing("NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE);
         if (nowExpanded) {
             if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                 mShadeTransitionController.goToLockedShade(clickedEntry.getRow());
@@ -275,22 +258,6 @@
         }
     };
 
-    private final CheckSaveListener mCheckSaveListener = new CheckSaveListener() {
-        @Override
-        public void checkSave(Runnable saveImportance, StatusBarNotification sbn) {
-            // If the user has security enabled, show challenge if the setting is changed.
-            if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier())
-                    && mKeyguardManager.isKeyguardLocked()) {
-                onLockedNotificationImportanceChange(() -> {
-                    saveImportance.run();
-                    return true;
-                });
-            } else {
-                saveImportance.run();
-            }
-        }
-    };
-
     private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() {
         @Override
         public void onSettingsClick(String key) {
@@ -312,7 +279,7 @@
         @Override
         public boolean suppressAwakeHeadsUp(NotificationEntry entry) {
             final StatusBarNotification sbn = entry.getSbn();
-            if (mCentralSurfaces.isOccluded()) {
+            if (mKeyguardStateController.isOccluded()) {
                 boolean devicePublic = mLockscreenUserManager
                         .isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId());
                 boolean userPublic = devicePublic
@@ -341,7 +308,7 @@
 
         @Override
         public boolean suppressInterruptions(NotificationEntry entry) {
-            return mCentralSurfaces.areNotificationAlertsDisabled();
+            return !mNotificationsInteractor.areNotificationAlertsEnabled();
         }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterModule.java
index 26c483c..d94e434 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterModule.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
 
 import dagger.Binds;
 import dagger.Module;
@@ -26,8 +25,4 @@
 public abstract class StatusBarNotificationPresenterModule {
     @Binds
     abstract NotificationPresenter bindPresenter(StatusBarNotificationPresenter impl);
-
-    @Binds
-    abstract NotificationRowBinderImpl.BindRowCallback bindBindRowCallback(
-            StatusBarNotificationPresenter impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index d731f88..344e56c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.os.Handler;
-import android.telephony.SubscriptionInfo;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -27,10 +26,8 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.connectivity.IconState;
-import com.android.systemui.statusbar.connectivity.MobileDataIndicators;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.SignalCallback;
-import com.android.systemui.statusbar.connectivity.WifiIndicators;
 import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
@@ -51,7 +48,6 @@
 
     private final String mSlotAirplane;
     private final String mSlotMobile;
-    private final String mSlotWifi;
     private final String mSlotEthernet;
     private final String mSlotVpn;
     private final String mSlotNoCalling;
@@ -67,17 +63,13 @@
 
     private boolean mHideAirplane;
     private boolean mHideMobile;
-    private boolean mHideWifi;
     private boolean mHideEthernet;
     private boolean mActivityEnabled;
 
     // Track as little state as possible, and only for padding purposes
     private boolean mIsAirplaneMode = false;
-    private boolean mIsWifiEnabled = false;
 
-    private ArrayList<MobileIconState> mMobileStates = new ArrayList<>();
     private ArrayList<CallIndicatorIconState> mCallIndicatorStates = new ArrayList<>();
-    private WifiIconState mWifiIconState = new WifiIconState();
     private boolean mInitialized;
 
     @Inject
@@ -99,7 +91,6 @@
 
         mSlotAirplane = mContext.getString(com.android.internal.R.string.status_bar_airplane);
         mSlotMobile   = mContext.getString(com.android.internal.R.string.status_bar_mobile);
-        mSlotWifi     = mContext.getString(com.android.internal.R.string.status_bar_wifi);
         mSlotEthernet = mContext.getString(com.android.internal.R.string.status_bar_ethernet);
         mSlotVpn      = mContext.getString(com.android.internal.R.string.status_bar_vpn);
         mSlotNoCalling = mContext.getString(com.android.internal.R.string.status_bar_no_calling);
@@ -108,7 +99,7 @@
         mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);
     }
 
-    /** Call to initilaize and register this classw with the system. */
+    /** Call to initialize and register this class with the system. */
     public void init() {
         if (mInitialized) {
             return;
@@ -154,15 +145,13 @@
         ArraySet<String> hideList = StatusBarIconController.getIconHideList(mContext, newValue);
         boolean hideAirplane = hideList.contains(mSlotAirplane);
         boolean hideMobile = hideList.contains(mSlotMobile);
-        boolean hideWifi = hideList.contains(mSlotWifi);
         boolean hideEthernet = hideList.contains(mSlotEthernet);
 
         if (hideAirplane != mHideAirplane || hideMobile != mHideMobile
-                || hideEthernet != mHideEthernet || hideWifi != mHideWifi) {
+                || hideEthernet != mHideEthernet) {
             mHideAirplane = hideAirplane;
             mHideMobile = hideMobile;
             mHideEthernet = hideEthernet;
-            mHideWifi = hideWifi;
             // Re-register to get new callbacks.
             mNetworkController.removeCallback(this);
             mNetworkController.addCallback(this);
@@ -170,56 +159,6 @@
     }
 
     @Override
-    public void setWifiIndicators(@NonNull WifiIndicators indicators) {
-        if (DEBUG) {
-            Log.d(TAG, "setWifiIndicators: " + indicators);
-        }
-        boolean visible = indicators.statusIcon.visible && !mHideWifi;
-        boolean in = indicators.activityIn && mActivityEnabled && visible;
-        boolean out = indicators.activityOut && mActivityEnabled && visible;
-        mIsWifiEnabled = indicators.enabled;
-
-        WifiIconState newState = mWifiIconState.copy();
-
-        if (mWifiIconState.noDefaultNetwork && mWifiIconState.noNetworksAvailable
-                && !mIsAirplaneMode) {
-            newState.visible = true;
-            newState.resId = R.drawable.ic_qs_no_internet_unavailable;
-        } else if (mWifiIconState.noDefaultNetwork && !mWifiIconState.noNetworksAvailable
-                && (!mIsAirplaneMode || (mIsAirplaneMode && mIsWifiEnabled))) {
-            newState.visible = true;
-            newState.resId = R.drawable.ic_qs_no_internet_available;
-        } else {
-            newState.visible = visible;
-            newState.resId = indicators.statusIcon.icon;
-            newState.activityIn = in;
-            newState.activityOut = out;
-            newState.contentDescription = indicators.statusIcon.contentDescription;
-            MobileIconState first = getFirstMobileState();
-            newState.signalSpacerVisible = first != null && first.typeId != 0;
-        }
-        newState.slot = mSlotWifi;
-        newState.airplaneSpacerVisible = mIsAirplaneMode;
-        updateWifiIconWithState(newState);
-        mWifiIconState = newState;
-    }
-
-    private void updateShowWifiSignalSpacer(WifiIconState state) {
-        MobileIconState first = getFirstMobileState();
-        state.signalSpacerVisible = first != null && first.typeId != 0;
-    }
-
-    private void updateWifiIconWithState(WifiIconState state) {
-        if (DEBUG) Log.d(TAG, "WifiIconState: " + state == null ? "" : state.toString());
-        if (state.visible && state.resId > 0) {
-            mIconController.setWifiIcon(mSlotWifi, state);
-            mIconController.setIconVisibility(mSlotWifi, true);
-        } else {
-            mIconController.setIconVisibility(mSlotWifi, false);
-        }
-    }
-
-    @Override
     public void setCallIndicator(@NonNull IconState statusIcon, int subId) {
         if (DEBUG) {
             Log.d(TAG, "setCallIndicator: "
@@ -247,47 +186,6 @@
                 CallIndicatorIconState.copyStates(mCallIndicatorStates));
     }
 
-    @Override
-    public void setMobileDataIndicators(@NonNull MobileDataIndicators indicators) {
-        if (DEBUG) {
-            Log.d(TAG, "setMobileDataIndicators: " + indicators);
-        }
-        MobileIconState state = getState(indicators.subId);
-        if (state == null) {
-            return;
-        }
-
-        // Visibility of the data type indicator changed
-        boolean typeChanged = indicators.statusType != state.typeId
-                && (indicators.statusType == 0 || state.typeId == 0);
-
-        state.visible = indicators.statusIcon.visible && !mHideMobile;
-        state.strengthId = indicators.statusIcon.icon;
-        state.typeId = indicators.statusType;
-        state.contentDescription = indicators.statusIcon.contentDescription;
-        state.typeContentDescription = indicators.typeContentDescription;
-        state.showTriangle = indicators.showTriangle;
-        state.roaming = indicators.roaming;
-        state.activityIn = indicators.activityIn && mActivityEnabled;
-        state.activityOut = indicators.activityOut && mActivityEnabled;
-
-        if (DEBUG) {
-            Log.d(TAG, "MobileIconStates: "
-                    + (mMobileStates == null ? "" : mMobileStates.toString()));
-        }
-        // Always send a copy to maintain value type semantics
-        mIconController.setMobileIcons(mSlotMobile, MobileIconState.copyStates(mMobileStates));
-
-        if (typeChanged) {
-            WifiIconState wifiCopy = mWifiIconState.copy();
-            updateShowWifiSignalSpacer(wifiCopy);
-            if (!Objects.equals(wifiCopy, mWifiIconState)) {
-                updateWifiIconWithState(wifiCopy);
-                mWifiIconState = wifiCopy;
-            }
-        }
-    }
-
     private CallIndicatorIconState getNoCallingState(int subId) {
         for (CallIndicatorIconState state : mCallIndicatorStates) {
             if (state.subId == subId) {
@@ -298,83 +196,8 @@
         return null;
     }
 
-    private MobileIconState getState(int subId) {
-        for (MobileIconState state : mMobileStates) {
-            if (state.subId == subId) {
-                return state;
-            }
-        }
-        Log.e(TAG, "Unexpected subscription " + subId);
-        return null;
-    }
-
-    private MobileIconState getFirstMobileState() {
-        if (mMobileStates.size() > 0) {
-            return mMobileStates.get(0);
-        }
-
-        return null;
-    }
-
-
-    /**
-     * It is expected that a call to setSubs will be immediately followed by setMobileDataIndicators
-     * so we don't have to update the icon manager at this point, just remove the old ones
-     * @param subs list of mobile subscriptions, displayed as mobile data indicators (max 8)
-     */
-    @Override
-    public void setSubs(List<SubscriptionInfo> subs) {
-        if (DEBUG) Log.d(TAG, "setSubs: " + (subs == null ? "" : subs.toString()));
-        if (hasCorrectSubs(subs)) {
-            return;
-        }
-
-        mIconController.removeAllIconsForSlot(mSlotMobile);
-        mIconController.removeAllIconsForSlot(mSlotNoCalling);
-        mIconController.removeAllIconsForSlot(mSlotCallStrength);
-        mMobileStates.clear();
-        List<CallIndicatorIconState> noCallingStates = new ArrayList<CallIndicatorIconState>();
-        noCallingStates.addAll(mCallIndicatorStates);
-        mCallIndicatorStates.clear();
-        final int n = subs.size();
-        for (int i = 0; i < n; i++) {
-            mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId()));
-            boolean isNewSub = true;
-            for (CallIndicatorIconState state : noCallingStates) {
-                if (state.subId == subs.get(i).getSubscriptionId()) {
-                    mCallIndicatorStates.add(state);
-                    isNewSub = false;
-                    break;
-                }
-            }
-            if (isNewSub) {
-                mCallIndicatorStates.add(
-                        new CallIndicatorIconState(subs.get(i).getSubscriptionId()));
-            }
-        }
-    }
-
-    private boolean hasCorrectSubs(List<SubscriptionInfo> subs) {
-        final int N = subs.size();
-        if (N != mMobileStates.size()) {
-            return false;
-        }
-        for (int i = 0; i < N; i++) {
-            if (mMobileStates.get(i).subId != subs.get(i).getSubscriptionId()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public void setNoSims(boolean show, boolean simDetected) {
-        // Noop yay!
-    }
-
     @Override
     public void setEthernetIndicators(IconState state) {
-        boolean visible = state.visible && !mHideEthernet;
         int resId = state.icon;
         String description = state.contentDescription;
 
@@ -404,11 +227,6 @@
         }
     }
 
-    @Override
-    public void setMobileDataEnabled(boolean enabled) {
-        // Don't care.
-    }
-
     /**
      * Stores the statusbar state for no Calling & SMS.
      */
@@ -468,171 +286,4 @@
             return outStates;
         }
     }
-
-    private static abstract class SignalIconState {
-        public boolean visible;
-        public boolean activityOut;
-        public boolean activityIn;
-        public String slot;
-        public String contentDescription;
-
-        @Override
-        public boolean equals(Object o) {
-            // Skipping reference equality bc this should be more of a value type
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            SignalIconState that = (SignalIconState) o;
-            return visible == that.visible &&
-                    activityOut == that.activityOut &&
-                    activityIn == that.activityIn &&
-                    Objects.equals(contentDescription, that.contentDescription) &&
-                    Objects.equals(slot, that.slot);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(visible, activityOut, slot);
-        }
-
-        protected void copyTo(SignalIconState other) {
-            other.visible = visible;
-            other.activityIn = activityIn;
-            other.activityOut = activityOut;
-            other.slot = slot;
-            other.contentDescription = contentDescription;
-        }
-    }
-
-    public static class WifiIconState extends SignalIconState{
-        public int resId;
-        public boolean airplaneSpacerVisible;
-        public boolean signalSpacerVisible;
-        public boolean noDefaultNetwork;
-        public boolean noValidatedNetwork;
-        public boolean noNetworksAvailable;
-
-        @Override
-        public boolean equals(Object o) {
-            // Skipping reference equality bc this should be more of a value type
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            if (!super.equals(o)) {
-                return false;
-            }
-            WifiIconState that = (WifiIconState) o;
-            return resId == that.resId
-                    && airplaneSpacerVisible == that.airplaneSpacerVisible
-                    && signalSpacerVisible == that.signalSpacerVisible
-                    && noDefaultNetwork == that.noDefaultNetwork
-                    && noValidatedNetwork == that.noValidatedNetwork
-                    && noNetworksAvailable == that.noNetworksAvailable;
-        }
-
-        public void copyTo(WifiIconState other) {
-            super.copyTo(other);
-            other.resId = resId;
-            other.airplaneSpacerVisible = airplaneSpacerVisible;
-            other.signalSpacerVisible = signalSpacerVisible;
-            other.noDefaultNetwork = noDefaultNetwork;
-            other.noValidatedNetwork = noValidatedNetwork;
-            other.noNetworksAvailable = noNetworksAvailable;
-        }
-
-        public WifiIconState copy() {
-            WifiIconState newState = new WifiIconState();
-            copyTo(newState);
-            return newState;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(super.hashCode(),
-                    resId, airplaneSpacerVisible, signalSpacerVisible, noDefaultNetwork,
-                    noValidatedNetwork, noNetworksAvailable);
-        }
-
-        @Override public String toString() {
-            return "WifiIconState(resId=" + resId + ", visible=" + visible + ")";
-        }
-    }
-
-    /**
-     * A little different. This one delegates to SignalDrawable instead of a specific resId
-     */
-    public static class MobileIconState extends SignalIconState {
-        public int subId;
-        public int strengthId;
-        public int typeId;
-        public boolean showTriangle;
-        public boolean roaming;
-        public boolean needsLeadingPadding;
-        public CharSequence typeContentDescription;
-
-        private MobileIconState(int subId) {
-            super();
-            this.subId = subId;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            if (!super.equals(o)) {
-                return false;
-            }
-            MobileIconState that = (MobileIconState) o;
-            return subId == that.subId
-                    && strengthId == that.strengthId
-                    && typeId == that.typeId
-                    && showTriangle == that.showTriangle
-                    && roaming == that.roaming
-                    && needsLeadingPadding == that.needsLeadingPadding
-                    && Objects.equals(typeContentDescription, that.typeContentDescription);
-        }
-
-        @Override
-        public int hashCode() {
-
-            return Objects
-                    .hash(super.hashCode(), subId, strengthId, typeId, showTriangle, roaming,
-                            needsLeadingPadding, typeContentDescription);
-        }
-
-        public MobileIconState copy() {
-            MobileIconState copy = new MobileIconState(this.subId);
-            copyTo(copy);
-            return copy;
-        }
-
-        public void copyTo(MobileIconState other) {
-            super.copyTo(other);
-            other.subId = subId;
-            other.strengthId = strengthId;
-            other.typeId = typeId;
-            other.showTriangle = showTriangle;
-            other.roaming = roaming;
-            other.needsLeadingPadding = needsLeadingPadding;
-            other.typeContentDescription = typeContentDescription;
-        }
-
-        private static List<MobileIconState> copyStates(List<MobileIconState> inStates) {
-            ArrayList<MobileIconState> outStates = new ArrayList<>();
-            for (MobileIconState state : inStates) {
-                MobileIconState copy = new MobileIconState(state.subId);
-                state.copyTo(copy);
-                outStates.add(copy);
-            }
-
-            return outStates;
-        }
-
-        @Override public String toString() {
-            return "MobileIconState(subId=" + subId + ", strengthId=" + strengthId
-                    + ", showTriangle=" + showTriangle + ", roaming=" + roaming
-                    + ", typeId=" + typeId + ", visible=" + visible + ")";
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index 2a039da..fa9b9d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -34,15 +34,20 @@
 import com.android.systemui.R;
 import com.android.systemui.ScreenDecorations;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * Manages what parts of the status bar are touchable. Clients are primarily UI that display in the
@@ -78,6 +83,9 @@
             ConfigurationController configurationController,
             HeadsUpManagerPhone headsUpManager,
             ShadeExpansionStateManager shadeExpansionStateManager,
+            Provider<SceneInteractor> sceneInteractor,
+            Provider<JavaAdapter> javaAdapter,
+            FeatureFlags featureFlags,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController
     ) {
         mContext = context;
@@ -115,6 +123,12 @@
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
 
+        if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+            javaAdapter.get().alwaysCollectFlow(
+                    sceneInteractor.get().isVisible(),
+                    this::onShadeExpansionFullyChanged);
+        }
+
         mOnComputeInternalInsetsListener = this::onComputeInternalInsets;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index 26c1767..d83664f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -22,6 +22,8 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -64,6 +66,7 @@
     private boolean mNeedsUnderflow;
     // Individual StatusBarIconViews draw their etc dots centered in this width
     private int mIconDotFrameWidth;
+    private boolean mQsExpansionTransitioning;
     private boolean mShouldRestrictIcons = true;
     // Used to count which states want to be visible during layout
     private ArrayList<StatusIconState> mLayoutStates = new ArrayList<>();
@@ -72,13 +75,16 @@
     // Any ignored icon will never be added as a child
     private ArrayList<String> mIgnoredSlots = new ArrayList<>();
 
+    private Configuration mConfiguration;
+
     public StatusIconContainer(Context context) {
         this(context, null);
     }
 
     public StatusIconContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
-        initDimens();
+        mConfiguration = new Configuration(context.getResources().getConfiguration());
+        reloadDimens();
         setWillNotDraw(!DEBUG_OVERFLOW);
     }
 
@@ -87,6 +93,10 @@
         super.onFinishInflate();
     }
 
+    public void setQsExpansionTransitioning(boolean expansionTransitioning) {
+        mQsExpansionTransitioning = expansionTransitioning;
+    }
+
     public void setShouldRestrictIcons(boolean should) {
         mShouldRestrictIcons = should;
     }
@@ -95,10 +105,10 @@
         return mShouldRestrictIcons;
     }
 
-    private void initDimens() {
+    private void reloadDimens() {
         // This is the same value that StatusBarIconView uses
         mIconDotFrameWidth = getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.status_bar_icon_size);
+                com.android.internal.R.dimen.status_bar_icon_size_sp);
         mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
         mIconSpacing = getResources().getDimensionPixelSize(R.dimen.status_bar_system_icon_spacing);
         int radius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
@@ -145,8 +155,8 @@
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         mMeasureViews.clear();
-        int mode = MeasureSpec.getMode(widthMeasureSpec);
-        final int width = MeasureSpec.getSize(widthMeasureSpec);
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
         final int count = getChildCount();
         // Collect all of the views which want to be laid out
         for (int i = 0; i < count; i++) {
@@ -163,7 +173,7 @@
         boolean trackWidth = true;
 
         // Measure all children so that they report the correct width
-        int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
+        int childWidthSpec = MeasureSpec.makeMeasureSpec(specWidth, MeasureSpec.UNSPECIFIED);
         mNeedsUnderflow = mShouldRestrictIcons && visibleCount > MAX_ICONS;
         for (int i = 0; i < visibleCount; i++) {
             // Walking backwards
@@ -182,18 +192,35 @@
                 totalWidth += getViewTotalMeasuredWidth(child) + spacing;
             }
         }
+        setMeasuredDimension(
+                getMeasuredWidth(widthMode, specWidth, totalWidth),
+                getMeasuredHeight(heightMeasureSpec, mMeasureViews));
+    }
 
-        if (mode == MeasureSpec.EXACTLY) {
-            if (!mNeedsUnderflow && totalWidth > width) {
-                mNeedsUnderflow = true;
-            }
-            setMeasuredDimension(width, MeasureSpec.getSize(heightMeasureSpec));
+    private int getMeasuredHeight(int heightMeasureSpec, List<View> measuredChildren) {
+        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
+            return MeasureSpec.getSize(heightMeasureSpec);
         } else {
-            if (mode == MeasureSpec.AT_MOST && totalWidth > width) {
-                mNeedsUnderflow = true;
-                totalWidth = width;
+            int highest = 0;
+            for (View child : measuredChildren) {
+                highest = Math.max(child.getMeasuredHeight(), highest);
             }
-            setMeasuredDimension(totalWidth, MeasureSpec.getSize(heightMeasureSpec));
+            return highest + getPaddingTop() + getPaddingBottom();
+        }
+    }
+
+    private int getMeasuredWidth(int widthMode, int specWidth, int totalWidth) {
+        if (widthMode == MeasureSpec.EXACTLY) {
+            if (!mNeedsUnderflow && totalWidth > specWidth) {
+                mNeedsUnderflow = true;
+            }
+            return specWidth;
+        } else {
+            if (widthMode == MeasureSpec.AT_MOST && totalWidth > specWidth) {
+                mNeedsUnderflow = true;
+                totalWidth = specWidth;
+            }
+            return totalWidth;
         }
     }
 
@@ -211,6 +238,16 @@
         child.setTag(R.id.status_bar_view_state_tag, null);
     }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        final int configDiff = newConfig.diff(mConfiguration);
+        mConfiguration.setTo(newConfig);
+        if ((configDiff & (ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_FONT_SCALE)) != 0) {
+            reloadDimens();
+        }
+    }
+
     /**
      * Add a name of an icon slot to be ignored. It will not show up nor be measured
      * @param slotName name of the icon as it exists in
@@ -280,34 +317,6 @@
     }
 
     /**
-     * Sets the list of ignored icon slots clearing the current list.
-     * @param slots names of the icons to ignore
-     */
-    public void setIgnoredSlots(List<String> slots) {
-        mIgnoredSlots.clear();
-        addIgnoredSlots(slots);
-    }
-
-    /**
-     * Returns the view corresponding to a particular slot.
-     *
-     * Use it solely to manipulate how it is presented.
-     * @param slot name of the slot to find. Names are defined in
-     *            {@link com.android.internal.R.config_statusBarIcons}
-     * @return a view for the slot if this container has it, else {@code null}
-     */
-    public View getViewForSlot(String slot) {
-        for (int i = 0; i < getChildCount(); i++) {
-            View child = getChildAt(i);
-            if (child instanceof StatusIconDisplayable
-                    && ((StatusIconDisplayable) child).getSlot().equals(slot)) {
-                return child;
-            }
-        }
-        return null;
-    }
-
-    /**
      * Layout is happening from end -> start
      */
     private void calculateIconTranslations() {
@@ -348,13 +357,17 @@
         int totalVisible = mLayoutStates.size();
         int maxVisible = totalVisible <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
 
-        mUnderflowStart = 0;
+        // Init mUnderflowStart value with the offset to let the dot be placed next to battery icon.
+        // This is to prevent if the underflow happens at rightest(totalVisible - 1) child then
+        // break the for loop with mUnderflowStart staying 0(initial value), causing the dot be
+        // placed at the leftest side.
+        mUnderflowStart = (int) Math.max(contentStart, width - getPaddingEnd() - mUnderflowWidth);
         int visible = 0;
         int firstUnderflowIndex = -1;
         for (int i = totalVisible - 1; i >= 0; i--) {
             StatusIconState state = mLayoutStates.get(i);
             // Allow room for underflow if we found we need it in onMeasure
-            if (mNeedsUnderflow && (state.getXTranslation() < (contentStart + mUnderflowWidth))
+            if ((mNeedsUnderflow && (state.getXTranslation() < (contentStart + mUnderflowWidth)))
                     || (mShouldRestrictIcons && (visible >= maxVisible))) {
                 firstUnderflowIndex = i;
                 break;
@@ -397,6 +410,7 @@
             StatusIconState vs = getViewStateFromChild(child);
             if (vs != null) {
                 vs.applyToView(child);
+                vs.qsExpansionTransitioning = mQsExpansionTransitioning;
             }
         }
     }
@@ -431,6 +445,7 @@
         /// StatusBarIconView.STATE_*
         public int visibleState = STATE_ICON;
         public boolean justAdded = true;
+        public boolean qsExpansionTransitioning = false;
 
         // How far we are from the end of the view actually is the most relevant for animation
         float distanceToViewEnd = -1;
@@ -473,12 +488,13 @@
             }
 
             icon.setVisibleState(visibleState, animateVisibility);
-            if (animationProperties != null) {
+            if (animationProperties != null && !qsExpansionTransitioning) {
                 animateTo(view, animationProperties);
             } else {
                 super.applyToView(view);
             }
 
+            qsExpansionTransitioning = false;
             justAdded = false;
             distanceToViewEnd = currentDistanceToEnd;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt
new file mode 100644
index 0000000..881741a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListener.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Color
+import android.graphics.drawable.PaintDrawable
+import android.view.MotionEvent
+import android.view.View
+import android.view.View.OnHoverListener
+import androidx.annotation.ColorInt
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+class StatusOverlayHoverListenerFactory
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val configurationController: ConfigurationController,
+    private val darkIconDispatcher: SysuiDarkIconDispatcher,
+) {
+
+    /** Creates listener always using the same light color for overlay */
+    fun createListener(view: View) =
+        StatusOverlayHoverListener(
+            view,
+            configurationController,
+            resources,
+            flowOf(HoverTheme.LIGHT),
+        )
+
+    /**
+     * Creates listener using [DarkIconDispatcher] to determine light or dark color of the overlay
+     */
+    fun createDarkAwareListener(view: View) =
+        createDarkAwareListener(view, darkIconDispatcher.darkChangeFlow())
+
+    /**
+     * Creates listener using provided [DarkChange] producer to determine light or dark color of the
+     * overlay
+     */
+    fun createDarkAwareListener(view: View, darkFlow: StateFlow<DarkChange>) =
+        StatusOverlayHoverListener(
+            view,
+            configurationController,
+            resources,
+            darkFlow.map { toHoverTheme(view, it) },
+        )
+
+    private fun toHoverTheme(view: View, darkChange: DarkChange): HoverTheme {
+        val calculatedTint = DarkIconDispatcher.getTint(darkChange.areas, view, darkChange.tint)
+        // currently calculated tint is either white or some shade of black.
+        // So checking for Color.WHITE is deterministic compared to checking for Color.BLACK.
+        // In the future checking Color.luminance() might be more appropriate.
+        return if (calculatedTint == Color.WHITE) HoverTheme.LIGHT else HoverTheme.DARK
+    }
+}
+
+/**
+ * theme of hover drawable - it's different from device theme. This theme depends on view's
+ * background and/or dark value returned from [DarkIconDispatcher]
+ */
+enum class HoverTheme {
+    LIGHT,
+    DARK
+}
+
+/**
+ * [OnHoverListener] that adds [Drawable] overlay on top of the status icons when cursor/stylus
+ * starts hovering over them and removes overlay when status icons are no longer hovered
+ */
+class StatusOverlayHoverListener(
+    view: View,
+    configurationController: ConfigurationController,
+    private val resources: Resources,
+    private val themeFlow: Flow<HoverTheme>,
+) : OnHoverListener {
+
+    @ColorInt private var darkColor: Int = 0
+    @ColorInt private var lightColor: Int = 0
+    private var cornerRadius = 0f
+
+    private var lastTheme = HoverTheme.LIGHT
+
+    val backgroundColor
+        get() = if (lastTheme == HoverTheme.LIGHT) lightColor else darkColor
+
+    init {
+        view.repeatWhenAttached {
+            lifecycleScope.launch {
+                val configurationListener =
+                    object : ConfigurationListener {
+                        override fun onConfigChanged(newConfig: Configuration?) {
+                            updateResources()
+                        }
+                    }
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    configurationController.addCallback(configurationListener)
+                }
+                configurationController.removeCallback(configurationListener)
+            }
+            lifecycleScope.launch { themeFlow.collect { lastTheme = it } }
+        }
+        updateResources()
+    }
+
+    override fun onHover(v: View, event: MotionEvent): Boolean {
+        if (event.action == MotionEvent.ACTION_HOVER_ENTER) {
+            val drawable =
+                PaintDrawable(backgroundColor).apply {
+                    setCornerRadius(cornerRadius)
+                    setBounds(0, 0, v.width, v.height)
+                }
+            v.overlay.add(drawable)
+        } else if (event.action == MotionEvent.ACTION_HOVER_EXIT) {
+            v.overlay.clear()
+        }
+        return true
+    }
+
+    private fun updateResources() {
+        lightColor = resources.getColor(R.color.status_bar_icons_hover_color_light)
+        darkColor = resources.getColor(R.color.status_bar_icons_hover_color_dark)
+        cornerRadius = resources.getDimension(R.dimen.status_icons_hover_state_background_radius)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt
new file mode 100644
index 0000000..fbc6b95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Whether dialogs are requesting for affordances to be hidden or not. */
+val SystemUIDialogManager.hideAffordancesRequest: Flow<Boolean>
+    get() = conflatedCallbackFlow {
+        val callback =
+            SystemUIDialogManager.Listener { hideAffordance ->
+                trySendWithFailureLogging(hideAffordance, "dialogHideAffordancesRequest")
+            }
+        registerListener(callback)
+        trySendWithFailureLogging(shouldHideAffordance(), "dialogHideAffordancesRequestInitial")
+        awaitClose { unregisterListener(callback) }
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java
index d537721..f5e9034 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java
@@ -16,9 +16,16 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.graphics.Rect;
+
 import com.android.systemui.Dumpable;
 import com.android.systemui.plugins.DarkIconDispatcher;
 
+import java.util.ArrayList;
+import java.util.Collection;
+
+import kotlinx.coroutines.flow.StateFlow;
+
 /**
  * Dispatches events to {@link DarkReceiver}s about changes in darkness, tint area
  * and dark intensity.
@@ -29,4 +36,26 @@
      * @return LightBarTransitionsController
      */
     LightBarTransitionsController getTransitionsController();
+
+    /**
+     * Flow equivalent of registering {@link DarkReceiver} using
+     * {@link DarkIconDispatcher#addDarkReceiver(DarkReceiver)}
+     */
+    StateFlow<DarkChange> darkChangeFlow();
+
+    /** Model for {@link #darkChangeFlow()} */
+    class DarkChange {
+
+        public static final DarkChange EMPTY = new DarkChange(new ArrayList<>(), 0, 0);
+
+        public DarkChange(Collection<Rect> areas, float darkIntensity, int tint) {
+            this.areas = areas;
+            this.darkIntensity = darkIntensity;
+            this.tint = tint;
+        }
+
+        public final Collection<Rect> areas;
+        public final float darkIntensity;
+        public final int tint;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 135307a..1bceb29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -20,8 +20,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.StatusBarStateControllerImpl
 import com.android.systemui.statusbar.notification.AnimatableProperty
@@ -60,11 +62,13 @@
     private val keyguardStateController: KeyguardStateController,
     private val dozeParameters: dagger.Lazy<DozeParameters>,
     private val globalSettings: GlobalSettings,
+    private val notifShadeWindowControllerLazy: dagger.Lazy<NotificationShadeWindowController>,
     private val interactionJankMonitor: InteractionJankMonitor,
     private val powerManager: PowerManager,
     private val handler: Handler = Handler()
 ) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
-    private lateinit var mCentralSurfaces: CentralSurfaces
+    private lateinit var centralSurfaces: CentralSurfaces
+    private lateinit var shadeViewController: ShadeViewController
     /**
      * Whether or not [initialize] has been called to provide us with the StatusBar,
      * NotificationPanelViewController, and LightRevealSrim so that we can run the unlocked screen
@@ -101,20 +105,20 @@
             }
         }
         addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationCancel(animation: Animator?) {
+            override fun onAnimationCancel(animation: Animator) {
                 if (lightRevealScrim.revealEffect !is CircleReveal) {
                     lightRevealScrim.revealAmount = 1f
                 }
             }
 
-            override fun onAnimationEnd(animation: Animator?) {
+            override fun onAnimationEnd(animation: Animator) {
                 lightRevealAnimationPlaying = false
                 interactionJankMonitor.end(CUJ_SCREEN_OFF)
             }
 
-            override fun onAnimationStart(animation: Animator?) {
+            override fun onAnimationStart(animation: Animator) {
                 interactionJankMonitor.begin(
-                    mCentralSurfaces.notificationShadeWindowView, CUJ_SCREEN_OFF)
+                        notifShadeWindowControllerLazy.get().windowRootView, CUJ_SCREEN_OFF)
             }
         })
     }
@@ -124,7 +128,7 @@
         lightRevealAnimator.start()
     }
 
-    val animatorDurationScaleObserver = object : ContentObserver(null) {
+    private val animatorDurationScaleObserver = object : ContentObserver(null) {
         override fun onChange(selfChange: Boolean) {
             updateAnimatorDurationScale()
         }
@@ -132,11 +136,13 @@
 
     override fun initialize(
         centralSurfaces: CentralSurfaces,
+        shadeViewController: ShadeViewController,
         lightRevealScrim: LightRevealScrim
     ) {
         this.initialized = true
         this.lightRevealScrim = lightRevealScrim
-        this.mCentralSurfaces = centralSurfaces
+        this.centralSurfaces = centralSurfaces
+        this.shadeViewController = shadeViewController
 
         updateAnimatorDurationScale()
         globalSettings.registerContentObserver(
@@ -196,7 +202,7 @@
 
                     // Tell the CentralSurfaces to become keyguard for real - we waited on that
                     // since it is slow and would have caused the animation to jank.
-                    mCentralSurfaces.updateIsKeyguard()
+                    centralSurfaces.updateIsKeyguard()
 
                     // Run the callback given to us by the KeyguardVisibilityHelper.
                     after.run()
@@ -218,7 +224,7 @@
                 .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_SLOW_IN),
             true /* animate */)
         interactionJankMonitor.begin(
-            mCentralSurfaces.notificationShadeWindowView,
+                notifShadeWindowControllerLazy.get().windowRootView,
             CUJ_SCREEN_OFF_SHOW_AOD
         )
     }
@@ -249,7 +255,7 @@
             // even if we're going from SHADE to SHADE or KEYGUARD to KEYGUARD, since we might have
             // changed parts of the UI (such as showing AOD in the shade) without actually changing
             // the StatusBarState. This ensures that the UI definitely reflects the desired state.
-            mCentralSurfaces.updateIsKeyguard(true /* forceStateChange */)
+            centralSurfaces.updateIsKeyguard(true /* forceStateChange */)
         }
     }
 
@@ -278,7 +284,7 @@
 
                     // Show AOD. That'll cause the KeyguardVisibilityHelper to call
                     // #animateInKeyguard.
-                    mCentralSurfaces.shadeViewController.showAodUi()
+                    shadeViewController.showAodUi()
                 }
             }, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong())
 
@@ -326,8 +332,8 @@
         // We currently draw both the light reveal scrim, and the AOD UI, in the shade. If it's
         // already expanded and showing notifications/QS, the animation looks really messy. For now,
         // disable it if the notification panel is expanded.
-        if ((!this::mCentralSurfaces.isInitialized ||
-                mCentralSurfaces.shadeViewController.isPanelExpanded) &&
+        if ((!this::centralSurfaces.isInitialized ||
+                shadeViewController.isPanelExpanded) &&
                 // Status bar might be expanded because we have started
                 // playing the animation already
                 !isAnimationPlaying()
@@ -339,7 +345,7 @@
         // portrait. If we're in another orientation, disable the screen off animation so we don't
         // animate in the keyguard AOD UI sideways or upside down.
         if (!keyguardStateController.isKeyguardScreenRotationAllowed &&
-            context.display.rotation != Surface.ROTATION_0) {
+            context.display?.rotation != Surface.ROTATION_0) {
             return false
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
index 273e783..bbbe16f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
@@ -20,23 +20,12 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.shade.NotificationShadeWindowView;
-import com.android.systemui.shade.NotificationShadeWindowViewController;
-import com.android.systemui.shade.QuickSettingsController;
+import com.android.systemui.scene.ui.view.WindowRootView;
 import com.android.systemui.shade.ShadeHeaderController;
-import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutListContainerModule;
 import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks;
 import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
-import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarterModule;
-import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 
 import dagger.Subcomponent;
@@ -57,10 +46,8 @@
  * outside the component. Should more items be moved *into* this component to avoid so many getters?
  */
 @Subcomponent(modules = {
-        NotificationStackScrollLayoutListContainerModule.class,
         StatusBarViewModule.class,
         StatusBarNotificationActivityStarterModule.class,
-        StatusBarNotificationPresenterModule.class,
 })
 @CentralSurfacesComponent.CentralSurfacesScope
 public interface CentralSurfacesComponent {
@@ -80,34 +67,8 @@
     @Scope
     @interface CentralSurfacesScope {}
 
-    /**
-     * Creates a {@link NotificationShadeWindowView}.
-     */
-    NotificationShadeWindowView getNotificationShadeWindowView();
-
-    /** */
-    NotificationShelfController getNotificationShelfController();
-
-    /** */
-    NotificationStackScrollLayoutController getNotificationStackScrollLayoutController();
-
-    /**
-     * Creates a NotificationShadeWindowViewController.
-     */
-    NotificationShadeWindowViewController getNotificationShadeWindowViewController();
-
-    /**
-     * Creates a NotificationPanelViewController.
-     */
-    NotificationPanelViewController getNotificationPanelViewController();
-
-    /** Creates a QuickSettingsController. */
-    QuickSettingsController getQuickSettingsController();
-
-    /**
-     * Creates a StatusBarHeadsUpChangeListener.
-     */
-    StatusBarHeadsUpChangeListener getStatusBarHeadsUpChangeListener();
+    /** Creates the root view of the main SysUI window}. */
+    WindowRootView getWindowRootView();
 
     /**
      * Creates a CentralSurfacesCommandQueueCallbacks.
@@ -127,10 +88,4 @@
     CollapsedStatusBarFragment createCollapsedStatusBarFragment();
 
     NotificationActivityStarter getNotificationActivityStarter();
-
-    NotificationPresenter getNotificationPresenter();
-
-    NotificationRowBinderImpl.BindRowCallback getBindRowCallback();
-
-    NotificationListContainer getNotificationListContainer();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 4ccbc5a..ebdde78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -16,39 +16,20 @@
 
 package com.android.systemui.statusbar.phone.dagger;
 
-import android.view.LayoutInflater;
-
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.NotificationPanelView;
-import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.shade.NotificationShadeWindowView;
-import com.android.systemui.shade.NotificationsQuickSettingsContainer;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl;
-import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
-import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule;
-import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModelModule;
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
 import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
-import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
@@ -60,81 +41,25 @@
 import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.settings.SecureSettings;
 
-import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
-import dagger.multibindings.IntoSet;
 
 import java.util.concurrent.Executor;
 
 import javax.inject.Named;
-import javax.inject.Provider;
 
-@Module(subcomponents = StatusBarFragmentComponent.class,
-        includes = {
-                ActivatableNotificationViewModelModule.class,
-                NotificationListViewModelModule.class,
-        })
+/**
+ * A module for {@link CentralSurfacesComponent.CentralSurfacesScope} components.
+ *
+ * @deprecated CentralSurfacesScope will be removed shortly (b/277762009). Classes should be
+ *   annotated with @SysUISingleton instead.
+ */
+@Module(subcomponents = StatusBarFragmentComponent.class)
+@Deprecated
 public abstract class StatusBarViewModule {
 
     public static final String STATUS_BAR_FRAGMENT = "status_bar_fragment";
 
-    /** */
-    @Provides
-    @CentralSurfacesComponent.CentralSurfacesScope
-    public static NotificationShelf providesNotificationShelf(LayoutInflater layoutInflater,
-            NotificationStackScrollLayout notificationStackScrollLayout) {
-        NotificationShelf view = (NotificationShelf) layoutInflater.inflate(
-                R.layout.status_bar_notification_shelf, notificationStackScrollLayout, false);
-
-        if (view == null) {
-            throw new IllegalStateException(
-                    "R.layout.status_bar_notification_shelf could not be properly inflated");
-        }
-        return view;
-    }
-
-    /** */
-    @Provides
-    @CentralSurfacesComponent.CentralSurfacesScope
-    public static NotificationShelfController providesStatusBarWindowView(
-            FeatureFlags featureFlags,
-            Provider<NotificationShelfViewBinderWrapperControllerImpl> newImpl,
-            NotificationShelfComponent.Builder notificationShelfComponentBuilder,
-            NotificationShelf notificationShelf) {
-        if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
-            return newImpl.get();
-        } else {
-            NotificationShelfComponent component = notificationShelfComponentBuilder
-                    .notificationShelf(notificationShelf)
-                    .build();
-            LegacyNotificationShelfControllerImpl notificationShelfController =
-                    component.getNotificationShelfController();
-            notificationShelfController.init();
-
-            return notificationShelfController;
-        }
-    }
-
-    /** */
-    @Binds
-    @CentralSurfacesComponent.CentralSurfacesScope
-    abstract ShadeViewController bindsShadeViewController(
-            NotificationPanelViewController notificationPanelViewController);
-
-    /** */
-    @Provides
-    @CentralSurfacesComponent.CentralSurfacesScope
-    public static NotificationsQuickSettingsContainer getNotificationsQuickSettingsContainer(
-            NotificationShadeWindowView notificationShadeWindowView) {
-        return notificationShadeWindowView.findViewById(R.id.notification_container_parent);
-    }
-
-    @Binds
-    @IntoSet
-    abstract StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener(
-            SystemBarAttributesListener systemBarAttributesListener);
-
     /**
      * Creates a new {@link CollapsedStatusBarFragment}.
      *
@@ -199,17 +124,4 @@
                 statusBarWindowStateController,
                 keyguardUpdateMonitor);
     }
-
-    /**
-     * Constructs a new, unattached {@link KeyguardBottomAreaView}.
-     *
-     * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it
-     */
-    @Provides
-    public static KeyguardBottomAreaView providesKeyguardBottomAreaView(
-            NotificationPanelView npv, LayoutInflater layoutInflater) {
-        return (KeyguardBottomAreaView) layoutInflater.inflate(R
-                .layout.keyguard_bottom_area, npv, false);
-    }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index fcae23b..0d58079 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -14,9 +14,6 @@
 
 package com.android.systemui.statusbar.phone.fragment;
 
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT;
-
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.Fragment;
@@ -39,6 +36,7 @@
 import androidx.core.animation.Animator;
 
 import com.android.app.animation.Interpolators;
+import com.android.app.animation.InterpolatorsAndroidX;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
@@ -77,6 +75,8 @@
 import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
 import com.android.systemui.util.settings.SecureSettings;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -99,12 +99,16 @@
     private static final String EXTRA_PANEL_STATE = "panel_state";
     public static final String STATUS_BAR_ICON_MANAGER_TAG = "status_bar_icon_manager";
     public static final int FADE_IN_DURATION = 320;
+    public static final int FADE_OUT_DURATION = 160;
     public static final int FADE_IN_DELAY = 50;
+    private static final int SOURCE_SYSTEM_EVENT_ANIMATOR = 1;
+    private static final int SOURCE_OTHER = 2;
     private StatusBarFragmentComponent mStatusBarFragmentComponent;
     private PhoneStatusBarView mStatusBar;
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardStateController mKeyguardStateController;
     private final ShadeViewController mShadeViewController;
+    private MultiSourceMinAlphaController mEndSideAlphaController;
     private LinearLayout mEndSideContent;
     private View mClockView;
     private View mOngoingCallChip;
@@ -149,7 +153,7 @@
         }
     };
     private OperatorNameViewController mOperatorNameViewController;
-    private StatusBarSystemEventAnimator mSystemEventAnimator;
+    private StatusBarSystemEventDefaultAnimator mSystemEventAnimator;
 
     private final CarrierConfigChangedListener mCarrierConfigCallback =
             new CarrierConfigChangedListener() {
@@ -297,14 +301,14 @@
         updateBlockedIcons();
         mStatusBarIconController.addIconGroup(mDarkIconManager);
         mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content);
+        mEndSideAlphaController = new MultiSourceMinAlphaController(mEndSideContent);
         mClockView = mStatusBar.findViewById(R.id.clock);
         mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
         showEndSideContent(false);
         showClock(false);
         initOperatorName();
         initNotificationIconArea();
-        mSystemEventAnimator =
-                new StatusBarSystemEventAnimator(mEndSideContent, getResources());
+        mSystemEventAnimator = getSystemEventAnimator();
         mCarrierConfigTracker.addCallback(mCarrierConfigCallback);
         mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
 
@@ -593,18 +597,27 @@
     }
 
     private void hideEndSideContent(boolean animate) {
-        animateHide(mEndSideContent, animate);
+        if (!animate) {
+            mEndSideAlphaController.setAlpha(/*alpha*/ 0f, SOURCE_OTHER);
+        } else {
+            mEndSideAlphaController.animateToAlpha(/*alpha*/ 0f, SOURCE_OTHER, FADE_OUT_DURATION,
+                    InterpolatorsAndroidX.ALPHA_OUT, /*startDelay*/ 0);
+        }
     }
 
     private void showEndSideContent(boolean animate) {
-        // Only show the system icon area if we are not currently animating
-        int state = mAnimationScheduler.getAnimationState();
-        if (state == IDLE || state == SHOWING_PERSISTENT_DOT) {
-            animateShow(mEndSideContent, animate);
+        if (!animate) {
+            mEndSideAlphaController.setAlpha(1f, SOURCE_OTHER);
+            return;
+        }
+        if (mKeyguardStateController.isKeyguardFadingAway()) {
+            mEndSideAlphaController.animateToAlpha(/*alpha*/ 1f, SOURCE_OTHER,
+                    mKeyguardStateController.getKeyguardFadingAwayDuration(),
+                    InterpolatorsAndroidX.LINEAR_OUT_SLOW_IN,
+                    mKeyguardStateController.getKeyguardFadingAwayDelay());
         } else {
-            // We are in the middle of a system status event animation, which will animate the
-            // alpha (but not the visibility). Allow the view to become visible again
-            mEndSideContent.setVisibility(View.VISIBLE);
+            mEndSideAlphaController.animateToAlpha(/*alpha*/ 1f, SOURCE_OTHER, FADE_IN_DURATION,
+                    InterpolatorsAndroidX.ALPHA_IN, FADE_IN_DELAY);
         }
     }
 
@@ -671,7 +684,7 @@
 
         v.animate()
                 .alpha(0f)
-                .setDuration(160)
+                .setDuration(FADE_OUT_DURATION)
                 .setStartDelay(0)
                 .setInterpolator(Interpolators.ALPHA_OUT)
                 .withEndAction(() -> v.setVisibility(state));
@@ -754,6 +767,16 @@
         return mSystemEventAnimator.onSystemEventAnimationFinish(hasPersistentDot);
     }
 
+    private StatusBarSystemEventDefaultAnimator getSystemEventAnimator() {
+        return new StatusBarSystemEventDefaultAnimator(getResources(), (alpha) -> {
+            mEndSideAlphaController.setAlpha(alpha, SOURCE_SYSTEM_EVENT_ANIMATOR);
+            return Unit.INSTANCE;
+        }, (translationX) -> {
+            mEndSideContent.setTranslationX(translationX);
+            return Unit.INSTANCE;
+        }, /*isAnimationRunning*/ false);
+    }
+
     private void updateStatusBarLocation(int left, int right) {
         int leftMargin = left - mStatusBar.getLeft();
         int rightMargin = mStatusBar.getRight() - right;
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 8c19fb4..7cdb9c0 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
@@ -18,7 +18,7 @@
 
 import com.android.systemui.log.dagger.CollapsedSbFragmentLog
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
 import javax.inject.Inject
 
@@ -33,7 +33,6 @@
      * modifications that were made to the flags locally.
      *
      * @param new see [DisableFlagsLogger.getDisableFlagsString]
-     * @param newAfterLocalModification see [DisableFlagsLogger.getDisableFlagsString]
      */
     fun logDisableFlagChange(
         new: DisableFlagsLogger.DisableState,
@@ -47,8 +46,7 @@
                 },
                 {
                     disableFlagsLogger.getDisableFlagsString(
-                        old = null,
-                        new = DisableFlagsLogger.DisableState(int1, int2),
+                        DisableFlagsLogger.DisableState(int1, int2),
                     )
                 }
         )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
new file mode 100644
index 0000000..c8836e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.fragment
+
+import android.view.View
+import androidx.core.animation.Interpolator
+import androidx.core.animation.ValueAnimator
+import com.android.app.animation.InterpolatorsAndroidX
+
+/**
+ * A controller that keeps track of multiple sources applying alpha value changes to a view. It will
+ * always apply the minimum alpha value of all sources.
+ */
+internal class MultiSourceMinAlphaController
+@JvmOverloads
+constructor(private val view: View, private val initialAlpha: Float = 1f) {
+
+    private val alphas = mutableMapOf<Int, Float>()
+    private val animators = mutableMapOf<Int, ValueAnimator>()
+
+    /**
+     * Sets the alpha of the provided source and applies it to the view (if no other source has set
+     * a lower alpha currently). If an animator of the same source is still running (i.e.
+     * [animateToAlpha] was called before), that animator is cancelled.
+     */
+    fun setAlpha(alpha: Float, sourceId: Int) {
+        animators[sourceId]?.cancel()
+        updateAlpha(alpha, sourceId)
+    }
+
+    /** Animates to the alpha of the provided source. */
+    fun animateToAlpha(
+        alpha: Float,
+        sourceId: Int,
+        duration: Long,
+        interpolator: Interpolator = InterpolatorsAndroidX.ALPHA_IN,
+        startDelay: Long = 0
+    ) {
+        animators[sourceId]?.cancel()
+        val animator = ValueAnimator.ofFloat(getMinAlpha(), alpha)
+        animator.duration = duration
+        animator.startDelay = startDelay
+        animator.interpolator = interpolator
+        animator.addUpdateListener { updateAlpha(animator.animatedValue as Float, sourceId) }
+        animator.start()
+        animators[sourceId] = animator
+    }
+
+    fun reset() {
+        alphas.clear()
+        animators.forEach { it.value.cancel() }
+        animators.clear()
+        applyAlphaToView()
+    }
+
+    private fun updateAlpha(alpha: Float, sourceId: Int) {
+        alphas[sourceId] = alpha
+        applyAlphaToView()
+    }
+
+    private fun applyAlphaToView() {
+        val minAlpha = getMinAlpha()
+        view.visibility = if (minAlpha != 0f) View.VISIBLE else View.INVISIBLE
+        view.alpha = minAlpha
+    }
+
+    private fun getMinAlpha() = alphas.minOfOrNull { it.value } ?: initialAlpha
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 730ecde..c8e73d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -26,20 +26,24 @@
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
 import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
+import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
 import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.IntoSet;
+import dagger.multibindings.Multibinds;
+
 import java.util.Optional;
 import java.util.Set;
 
 import javax.inject.Named;
 
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.Multibinds;
-
 /** Dagger module for {@link StatusBarFragmentComponent}. */
 @Module
 public interface StatusBarFragmentModule {
@@ -69,6 +73,13 @@
     /** */
     @Provides
     @StatusBarFragmentScope
+    static StatusBarLocation getStatusBarLocation() {
+        return StatusBarLocation.HOME;
+    }
+
+    /** */
+    @Provides
+    @StatusBarFragmentScope
     @Named(START_SIDE_CONTENT)
     static View startSideContent(@RootView PhoneStatusBarView view) {
         return view.findViewById(R.id.status_bar_start_side_content);
@@ -151,4 +162,10 @@
     /** */
     @Multibinds
     Set<StatusBarBoundsProvider.BoundsChangeListener> boundsChangeListeners();
+
+    /** */
+    @Binds
+    @IntoSet
+    StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener(
+            SystemBarAttributesListener systemBarAttributesListener);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
index 270c592..1259477 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
@@ -34,7 +34,7 @@
 
     override fun onFinishInflate() {
         super.onFinishInflate()
-        text = findViewById(R.id.current_user_name)
-        avatar = findViewById(R.id.current_user_avatar)
+        text = requireViewById(R.id.current_user_name)
+        avatar = requireViewById(R.id.current_user_avatar)
     }
 }
\ No newline at end of file
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 4a684d9..6e51ed0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -18,8 +18,6 @@
 
 import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import javax.inject.Inject
 
 /** All flagging methods related to the new status bar pipeline (see b/238425913). */
@@ -28,42 +26,10 @@
 @Inject
 constructor(
     context: Context,
-    private val featureFlags: FeatureFlags,
 ) {
     private val mobileSlot = context.getString(com.android.internal.R.string.status_bar_mobile)
     private val wifiSlot = context.getString(com.android.internal.R.string.status_bar_wifi)
 
-    /** 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 run the new mobile icons backend to get the logging.
-     *
-     * Does *not* affect whether we render the mobile icons using the new backend data. See
-     * [useNewMobileIcons] for that.
-     */
-    fun runNewMobileIconsBackend(): Boolean =
-        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS_BACKEND) || useNewMobileIcons()
-
-    /** 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)
-
-    /**
-     * True if we should run the new wifi icon backend to get the logging.
-     *
-     * Does *not* affect whether we render the wifi icon using the new backend data. See
-     * [useNewWifiIcon] for that.
-     */
-    fun runNewWifiIconBackend(): Boolean =
-        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON_BACKEND) || useNewWifiIcon()
-
-    /**
-     * Returns true if we should apply some coloring to the icons that were rendered with the new
-     * pipeline to help with debugging.
-     */
-    fun useDebugColoring(): Boolean =
-        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_ICONS_DEBUG_COLORING)
-
     /**
      * For convenience in the StatusBarIconController, we want to gate some actions based on slot
      * name and the flag together.
@@ -71,5 +37,5 @@
      * @return true if this icon is controlled by any of the status bar pipeline flags
      */
     fun isIconControlledByFlags(slotName: String): Boolean =
-        slotName == wifiSlot && useNewWifiIcon() || slotName == mobileSlot && useNewMobileIcons()
+        slotName == wifiSlot || slotName == mobileSlot
 }
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 27cc64f..e1fd37f 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
@@ -19,12 +19,12 @@
 import android.net.wifi.WifiManager
 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.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl
 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.airplane.ui.viewmodel.AirplaneModeViewModel
@@ -46,11 +46,16 @@
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinderImpl
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.DisabledWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryViaTrackerLib
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
 import dagger.Binds
@@ -58,9 +63,9 @@
 import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
-import kotlinx.coroutines.flow.Flow
 import java.util.function.Supplier
 import javax.inject.Named
+import kotlinx.coroutines.flow.Flow
 
 @Module
 abstract class StatusBarPipelineModule {
@@ -114,14 +119,19 @@
         impl: CollapsedStatusBarViewBinderImpl
     ): CollapsedStatusBarViewBinder
 
+    @Binds
+    @IntoMap
+    @ClassKey(WifiRepositoryDagger::class)
+    abstract fun bindWifiRepositoryDagger(impl: WifiRepositoryDagger): CoreStartable
+
     companion object {
         @Provides
         @SysUISingleton
-        fun provideRealWifiRepository(
+        fun provideWifiRepositoryDagger(
             wifiManager: WifiManager?,
             disabledWifiRepository: DisabledWifiRepository,
             wifiRepositoryImplFactory: WifiRepositoryImpl.Factory,
-        ): RealWifiRepository {
+        ): WifiRepositoryDagger {
             // If we have a null [WifiManager], then the wifi repository should be permanently
             // disabled.
             return if (wifiManager == null) {
@@ -133,6 +143,36 @@
 
         @Provides
         @SysUISingleton
+        fun provideWifiRepositoryViaTrackerLibDagger(
+            wifiManager: WifiManager?,
+            disabledWifiRepository: DisabledWifiRepository,
+            wifiRepositoryFromTrackerLibFactory: WifiRepositoryViaTrackerLib.Factory,
+        ): WifiRepositoryViaTrackerLibDagger {
+            // If we have a null [WifiManager], then the wifi repository should be permanently
+            // disabled.
+            return if (wifiManager == null) {
+                disabledWifiRepository
+            } else {
+                wifiRepositoryFromTrackerLibFactory.create(wifiManager)
+            }
+        }
+
+        @Provides
+        @SysUISingleton
+        fun provideRealWifiRepository(
+            wifiRepository: WifiRepositoryDagger,
+            wifiRepositoryFromTrackerLib: WifiRepositoryViaTrackerLibDagger,
+            flags: FeatureFlags,
+        ): RealWifiRepository {
+            return if (flags.isEnabled(Flags.WIFI_TRACKER_LIB_FOR_WIFI_ICON)) {
+                wifiRepositoryFromTrackerLib
+            } else {
+                wifiRepository
+            }
+        }
+
+        @Provides
+        @SysUISingleton
         @Named(FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON)
         fun provideFirstMobileSubShowingNetworkTypeIconProvider(
             mobileIconsViewModel: MobileIconsViewModel,
@@ -151,6 +191,14 @@
 
         @Provides
         @SysUISingleton
+        @WifiTrackerLibInputLog
+        fun provideWifiTrackerLibInputLogBuffer(factory: LogBufferFactory): LogBuffer {
+            // WifiTrackerLib is pretty noisy, so give it more room than WifiInputLog.
+            return factory.create("WifiTrackerLibInputLog", 200)
+        }
+
+        @Provides
+        @SysUISingleton
         @WifiTableLog
         fun provideWifiTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
             return factory.create("WifiTableLog", 100)
@@ -158,6 +206,13 @@
 
         @Provides
         @SysUISingleton
+        @WifiTrackerLibTableLog
+        fun provideWifiTrackerLibTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
+            return factory.create("WifiTrackerLibTableLog", 100)
+        }
+
+        @Provides
+        @SysUISingleton
         @AirplaneTableLog
         fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
             return factory.create("AirplaneTableLog", 30)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
new file mode 100644
index 0000000..b84b01e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** Wifi logs for inputs into [WifiRepositoryViaTrackerLib]. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class WifiTrackerLibInputLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt
new file mode 100644
index 0000000..7ca7030
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** Wifi logs from [WifiRepositoryViaTrackerLib] in table format. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class WifiTrackerLibTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index b3a1c40..051e88f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -23,7 +23,7 @@
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.MobileInputLog
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 3a11635..c1af6df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -118,6 +118,11 @@
     /** The service provider name for this network connection, or the default name */
     val networkName: StateFlow<NetworkNameModel>
 
+    /**
+     * True if this type of connection is allowed while airplane mode is on, and false otherwise.
+     */
+    val isAllowedDuringAirplaneMode: StateFlow<Boolean>
+
     companion object {
         /** The default number of levels to use for [numberOfLevels]. */
         const val DEFAULT_NUM_LEVELS = 4
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index 6b86432..17d20c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -186,6 +186,8 @@
 
     override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo network"))
 
+    override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
+
     /**
      * Process a new demo mobile event. Note that [resolvedNetworkType] must be passed in separately
      * from the event, due to the requirement to reverse the mobile mappings lookup in the top-level
@@ -217,6 +219,8 @@
             (event.activity ?: TelephonyManager.DATA_ACTIVITY_NONE).toMobileDataActivityModel()
         _carrierNetworkChangeActive.value = event.carrierNetworkChange
         _resolvedNetworkType.value = resolvedNetworkType
+
+        isAllowedDuringAirplaneMode.value = false
     }
 
     fun processCarrierMergedEvent(event: FakeWifiEventModel.CarrierMerged) {
@@ -240,6 +244,7 @@
         _isInService.value = true
         _isGsm.value = false
         _carrierNetworkChangeActive.value = false
+        isAllowedDuringAirplaneMode.value = true
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index a609917..65f4866 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -165,6 +165,13 @@
     override val isGsm = MutableStateFlow(false).asStateFlow()
     override val carrierNetworkChangeActive = MutableStateFlow(false).asStateFlow()
 
+    /**
+     * Carrier merged connections happen over wifi but are displayed as a mobile triangle. Because
+     * they occur over wifi, it's possible to have a valid carrier merged connection even during
+     * airplane mode. See b/291993542.
+     */
+    override val isAllowedDuringAirplaneMode = MutableStateFlow(true).asStateFlow()
+
     override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index 8869dfe..8ba7d21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -287,6 +287,15 @@
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value)
 
+    override val isAllowedDuringAirplaneMode =
+        activeRepo
+            .flatMapLatest { it.isAllowedDuringAirplaneMode }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                activeRepo.value.isAllowedDuringAirplaneMode.value,
+            )
+
     class Factory
     @Inject
     constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index b475183..aadc975 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -59,8 +59,10 @@
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
@@ -331,6 +333,9 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
     }
 
+    /** Typical mobile connections aren't available during airplane mode. */
+    override val isAllowedDuringAirplaneMode = MutableStateFlow(false).asStateFlow()
+
     class Factory
     @Inject
     constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 74352d29..54948a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -250,15 +250,8 @@
             .distinctUntilChanged()
             .onEach { logger.logDefaultMobileIconGroup(it) }
 
-    override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository {
-        if (!isValidSubId(subId)) {
-            throw IllegalArgumentException(
-                "subscriptionId $subId is not in the list of valid subscriptions"
-            )
-        }
-
-        return getOrCreateRepoForSubId(subId)
-    }
+    override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository =
+        getOrCreateRepoForSubId(subId)
 
     private fun getOrCreateRepoForSubId(subId: Int) =
         subIdRepositoryCache[subId]
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index d42e30c..1a13827 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -111,6 +111,9 @@
     /** See [MobileIconsInteractor.isForceHidden]. */
     val isForceHidden: Flow<Boolean>
 
+    /** See [MobileConnectionRepository.isAllowedDuringAirplaneMode]. */
+    val isAllowedDuringAirplaneMode: StateFlow<Boolean>
+
     /** True when in carrier network change mode */
     val carrierNetworkChangeActive: StateFlow<Boolean>
 }
@@ -267,4 +270,6 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val isInService = connectionRepository.isInService
+
+    override val isAllowedDuringAirplaneMode = connectionRepository.isAllowedDuringAirplaneMode
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index a05ab84..d7fcf48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.phone.StatusBarIconController
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import java.io.PrintWriter
@@ -46,24 +45,18 @@
     val mobileIconsViewModel: MobileIconsViewModel,
     private val logger: MobileViewLogger,
     @Application private val scope: CoroutineScope,
-    private val statusBarPipelineFlags: StatusBarPipelineFlags,
 ) : CoreStartable {
     private var isCollecting: Boolean = false
     private var lastValue: List<Int>? = null
 
     override fun start() {
-        // Only notify the icon controller if we want to *render* the new icons.
-        // Note that this flow may still run if
-        // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
-        // get the logging data without rendering.
-        if (statusBarPipelineFlags.useNewMobileIcons()) {
-            scope.launch {
-                isCollecting = true
-                mobileIconsViewModel.subscriptionIdsFlow.collectLatest {
-                    logger.logUiAdapterSubIdsSentToIconController(it)
-                    lastValue = it
-                    iconController.setNewMobileIconSubIds(it)
-                }
+        // Start notifying the icon controller of subscriptions
+        scope.launch {
+            isCollecting = true
+            mobileIconsViewModel.subscriptionIdsFlow.collectLatest {
+                logger.logUiAdapterSubIdsSentToIconController(it)
+                lastValue = it
+                iconController.setNewMobileIconSubIds(it)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
index 7e0c145..cea6654 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.MobileViewLog
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
 import java.io.PrintWriter
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
index 507549b..cffc833 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.VerboseMobileViewLog
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging
 import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
@@ -38,6 +38,19 @@
 constructor(
     @VerboseMobileViewLog private val buffer: LogBuffer,
 ) {
+    fun logBinderReceivedVisibility(parentView: View, subId: Int, visibility: Boolean) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                str1 = parentView.getIdForLogging()
+                int1 = subId
+                bool1 = visibility
+            },
+            { "Binder[subId=$int1, viewId=$str1] received visibility: $bool1" },
+        )
+    }
+
     fun logBinderReceivedSignalIcon(parentView: View, subId: Int, icon: SignalIconModel) {
         buffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index a2a247a..55bc8d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -99,7 +99,16 @@
                     }
                 }
 
-                launch { viewModel.isVisible.collect { isVisible -> view.isVisible = isVisible } }
+                launch {
+                    viewModel.isVisible.collect { isVisible ->
+                        viewModel.verboseLogger?.logBinderReceivedVisibility(
+                            view,
+                            viewModel.subscriptionId,
+                            isVisible
+                        )
+                        view.isVisible = isVisible
+                    }
+                }
 
                 // Set the icon for the triangle
                 launch {
@@ -183,16 +192,10 @@
             }
 
             override fun onIconTintChanged(newTint: Int) {
-                if (viewModel.useDebugColoring) {
-                    return
-                }
                 iconTint.value = newTint
             }
 
             override fun onDecorTintChanged(newTint: Int) {
-                if (viewModel.useDebugColoring) {
-                    return
-                }
                 decorTint.value = newTint
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
index f775940..a51982c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
@@ -18,7 +18,6 @@
 
 import android.graphics.Color
 import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
 
 /**
@@ -32,24 +31,14 @@
  */
 abstract class LocationBasedMobileViewModel(
     val commonImpl: MobileIconViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-    debugTint: Int,
     val locationName: String,
     val verboseLogger: VerboseMobileViewLogger?,
 ) : MobileIconViewModelCommon by commonImpl {
-    val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring()
-
-    val defaultColor: Int =
-        if (useDebugColoring) {
-            debugTint
-        } else {
-            Color.WHITE
-        }
+    val defaultColor: Int = Color.WHITE
 
     companion object {
         fun viewModelForLocation(
             commonImpl: MobileIconViewModelCommon,
-            statusBarPipelineFlags: StatusBarPipelineFlags,
             verboseMobileViewLogger: VerboseMobileViewLogger,
             loc: StatusBarLocation,
         ): LocationBasedMobileViewModel =
@@ -57,39 +46,31 @@
                 StatusBarLocation.HOME ->
                     HomeMobileIconViewModel(
                         commonImpl,
-                        statusBarPipelineFlags,
                         verboseMobileViewLogger,
                     )
-                StatusBarLocation.KEYGUARD ->
-                    KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
-                StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+                StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl)
+                StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl)
             }
     }
 }
 
 class HomeMobileIconViewModel(
     commonImpl: MobileIconViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
     verboseMobileViewLogger: VerboseMobileViewLogger,
 ) :
     MobileIconViewModelCommon,
     LocationBasedMobileViewModel(
         commonImpl,
-        statusBarPipelineFlags,
-        debugTint = Color.CYAN,
         locationName = "Home",
         verboseMobileViewLogger,
     )
 
 class QsMobileIconViewModel(
     commonImpl: MobileIconViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
 ) :
     MobileIconViewModelCommon,
     LocationBasedMobileViewModel(
         commonImpl,
-        statusBarPipelineFlags,
-        debugTint = Color.GREEN,
         locationName = "QS",
         // Only do verbose logging for the Home location.
         verboseLogger = null,
@@ -97,13 +78,10 @@
 
 class KeyguardMobileIconViewModel(
     commonImpl: MobileIconViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
 ) :
     MobileIconViewModelCommon,
     LocationBasedMobileViewModel(
         commonImpl,
-        statusBarPipelineFlags,
-        debugTint = Color.MAGENTA,
         locationName = "Keyguard",
         // Only do verbose logging for the Home location.
         verboseLogger = null,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 35f4f9a..275cfc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -102,9 +102,16 @@
             } else {
                 combine(
                     airplaneModeInteractor.isAirplaneMode,
+                    iconInteractor.isAllowedDuringAirplaneMode,
                     iconInteractor.isForceHidden,
-                ) { isAirplaneMode, isForceHidden ->
-                    !isAirplaneMode && !isForceHidden
+                ) { isAirplaneMode, isAllowedDuringAirplaneMode, isForceHidden ->
+                    if (isForceHidden) {
+                        false
+                    } else if (isAirplaneMode) {
+                        isAllowedDuringAirplaneMode
+                    } else {
+                        true
+                    }
                 }
             }
             .distinctUntilChanged()
@@ -214,36 +221,15 @@
     override val activityInVisible: Flow<Boolean> =
         activity
             .map { it?.hasActivityIn ?: false }
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                iconInteractor.tableLogBuffer,
-                columnPrefix = "",
-                columnName = "activityInVisible",
-                initialValue = false,
-            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val activityOutVisible: Flow<Boolean> =
         activity
             .map { it?.hasActivityOut ?: false }
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                iconInteractor.tableLogBuffer,
-                columnPrefix = "",
-                columnName = "activityOutVisible",
-                initialValue = false,
-            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val activityContainerVisible: Flow<Boolean> =
         activity
             .map { it != null && (it.hasActivityIn || it.hasActivityOut) }
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                iconInteractor.tableLogBuffer,
-                columnPrefix = "",
-                columnName = "activityContainerVisible",
-                initialValue = false,
-            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 40b8c90..5cf887e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -98,7 +98,6 @@
         val common = commonViewModelForSub(subId)
         return LocationBasedMobileViewModel.viewModelForLocation(
             common,
-            statusBarPipelineFlags,
             verboseLogger,
             location,
         )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
index cac0ae3..8a4d14e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
@@ -20,7 +20,7 @@
 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.core.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.SharedConnectivityInputLog
 import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
index 328d901..4b9de85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
@@ -19,7 +19,7 @@
 import android.net.Network
 import android.net.NetworkCapabilities
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 
 /** Helper object for logs that are shared between wifi and mobile. */
 object LoggerHelper {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt
index 058eda4..9a504c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.pipeline.shared.data.model
 
 import android.net.NetworkCapabilities
-import com.android.systemui.log.LogMessage
+import com.android.systemui.log.core.LogMessage
 
 /**
  * A model for all of the current default connections(s).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index 1a13404..a1b96dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -101,12 +101,7 @@
         this.binding = bindingCreator.invoke()
     }
 
-    /**
-     * Creates a [StatusBarIconView] that is always in DOT mode and adds it to this view.
-     *
-     * Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView] and
-     * [com.android.systemui.statusbar.StatusBarMobileView].
-     */
+    /** Creates a [StatusBarIconView] that is always in DOT mode and adds it to this view. */
     private fun initDotView() {
         // TODO(b/238425913): Could we just have this dot view be part of the layout with a dot
         //  drawable so we don't need to inflate it manually? Would that not work with animations?
@@ -118,7 +113,7 @@
                 it.visibleState = STATE_DOT
             }
 
-        val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size)
+        val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size_sp)
         val lp = LayoutParams(width, width)
         lp.gravity = Gravity.CENTER_VERTICAL or Gravity.START
         addView(dotView, lp)
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 f800cf4..b5b99a7 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.repository
 
+import com.android.systemui.CoreStartable
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import kotlinx.coroutines.flow.StateFlow
@@ -28,9 +29,18 @@
     /** Observable for the current wifi default status. */
     val isWifiDefault: StateFlow<Boolean>
 
-    /** Observable for the current wifi network. */
+    /** Observable for the current primary wifi network. */
     val wifiNetwork: StateFlow<WifiNetworkModel>
 
+    /**
+     * Observable for secondary wifi networks (if any). Should specifically exclude the primary
+     * network emitted by [wifiNetwork].
+     *
+     * This isn't used by phones/tablets, which only display the primary network, but may be used by
+     * other variants like Car.
+     */
+    val secondaryNetworks: StateFlow<List<WifiNetworkModel>>
+
     /** Observable for the current wifi network activity. */
     val wifiActivity: StateFlow<DataActivityModel>
 
@@ -42,6 +52,16 @@
         val currentNetwork = wifiNetwork.value
         return currentNetwork is WifiNetworkModel.Active && currentNetwork.hasValidSsid()
     }
+
+    companion object {
+        /** Column name to use for [isWifiEnabled] for table logging. */
+        const val COL_NAME_IS_ENABLED = "isEnabled"
+        /** Column name to use for [isWifiDefault] for table logging. */
+        const val COL_NAME_IS_DEFAULT = "isDefault"
+
+        const val CARRIER_MERGED_INVALID_SUB_ID_REASON =
+            "Wifi network was carrier merged but had invalid sub ID"
+    }
 }
 
 /**
@@ -53,3 +73,8 @@
  * repository.
  */
 interface RealWifiRepository : WifiRepository
+
+/** Used only by Dagger to bind [WifiRepositoryImpl]. */
+interface WifiRepositoryDagger : RealWifiRepository, CoreStartable
+/** Used only by Dagger to bind [WifiRepositoryViaTrackerLib]. */
+interface WifiRepositoryViaTrackerLibDagger : RealWifiRepository
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index e96288a..80091ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -115,6 +115,11 @@
             .flatMapLatest { it.wifiNetwork }
             .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiNetwork.value)
 
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+        activeRepo
+            .flatMapLatest { it.secondaryNetworks }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.secondaryNetworks.value)
+
     override val wifiActivity: StateFlow<DataActivityModel> =
         activeRepo
             .flatMapLatest { it.wifiActivity }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
index 7d2501ca..ab9b516 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
@@ -56,12 +57,14 @@
         val activity = getString("activity").toActivity()
         val ssid = getString("ssid")
         val validated = getString("fully").toBoolean()
+        val hotspotDeviceType = getString("hotspot").toHotspotDeviceType()
 
         return FakeWifiEventModel.Wifi(
             level = level,
             activity = activity,
             ssid = ssid,
             validated = validated,
+            hotspotDeviceType,
         )
     }
 
@@ -82,6 +85,20 @@
             else -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE
         }
 
+    private fun String?.toHotspotDeviceType(): WifiNetworkModel.HotspotDeviceType {
+        return when (this) {
+            null,
+            "none" -> WifiNetworkModel.HotspotDeviceType.NONE
+            "unknown" -> WifiNetworkModel.HotspotDeviceType.UNKNOWN
+            "phone" -> WifiNetworkModel.HotspotDeviceType.PHONE
+            "tablet" -> WifiNetworkModel.HotspotDeviceType.TABLET
+            "laptop" -> WifiNetworkModel.HotspotDeviceType.LAPTOP
+            "watch" -> WifiNetworkModel.HotspotDeviceType.WATCH
+            "auto" -> WifiNetworkModel.HotspotDeviceType.AUTO
+            else -> WifiNetworkModel.HotspotDeviceType.INVALID
+        }
+    }
+
     companion object {
         const val DEFAULT_CARRIER_MERGED_SUB_ID = 10
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
index a4fbc2c..4b19c3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -48,6 +48,9 @@
     private val _wifiNetwork = MutableStateFlow<WifiNetworkModel>(WifiNetworkModel.Inactive)
     override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
 
+    private val _secondaryNetworks = MutableStateFlow<List<WifiNetworkModel>>(emptyList())
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> = _secondaryNetworks
+
     private val _wifiActivity =
         MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
     override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
@@ -96,7 +99,8 @@
             networkId = DEMO_NET_ID,
             isValidated = validated ?: true,
             level = level ?: 0,
-            ssid = ssid,
+            ssid = ssid ?: DEMO_NET_SSID,
+            hotspotDeviceType = hotspotDeviceType,
 
             // These fields below aren't supported in demo mode, since they aren't needed to satisfy
             // the interface.
@@ -115,5 +119,6 @@
 
     companion object {
         private const val DEMO_NET_ID = 1234
+        private const val DEMO_NET_SSID = "Demo SSID"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
index f5035cbc..b2e843e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model
 
 import android.telephony.Annotation
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 
 /**
  * Model for demo wifi commands, ported from [NetworkControllerImpl]
@@ -29,6 +30,8 @@
         @Annotation.DataActivityType val activity: Int,
         val ssid: String?,
         val validated: Boolean?,
+        val hotspotDeviceType: WifiNetworkModel.HotspotDeviceType =
+            WifiNetworkModel.HotspotDeviceType.NONE,
     ) : FakeWifiEventModel
 
     data class CarrierMerged(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
index 86a668a..36c46a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
@@ -18,7 +18,8 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -32,13 +33,19 @@
  * wifi information.
  */
 @SysUISingleton
-class DisabledWifiRepository @Inject constructor() : RealWifiRepository {
+class DisabledWifiRepository @Inject constructor() :
+    WifiRepositoryDagger, WifiRepositoryViaTrackerLibDagger {
+    override fun start() {}
+
     override val isWifiEnabled: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
 
     override val isWifiDefault: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
 
     override val wifiNetwork: StateFlow<WifiNetworkModel> = MutableStateFlow(NETWORK).asStateFlow()
 
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+        MutableStateFlow(emptyList<WifiNetworkModel>()).asStateFlow()
+
     override val wifiActivity: StateFlow<DataActivityModel> =
         MutableStateFlow(ACTIVITY).asStateFlow()
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
new file mode 100644
index 0000000..f1b98b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
+
+import android.net.wifi.WifiManager
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
+import java.util.concurrent.Executor
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Object to provide shared helper functions between [WifiRepositoryImpl] and
+ * [WifiRepositoryViaTrackerLib].
+ */
+object WifiRepositoryHelper {
+    /** Creates a flow that fetches the [DataActivityModel] from [WifiManager]. */
+    fun createActivityFlow(
+        wifiManager: WifiManager,
+        @Main mainExecutor: Executor,
+        scope: CoroutineScope,
+        tableLogBuffer: TableLogBuffer,
+        inputLogger: (String) -> Unit,
+    ): StateFlow<DataActivityModel> {
+        return conflatedCallbackFlow {
+                val callback =
+                    WifiManager.TrafficStateCallback { state ->
+                        inputLogger.invoke(prettyPrintActivity(state))
+                        trySend(state.toWifiDataActivityModel())
+                    }
+                wifiManager.registerTrafficStateCallback(mainExecutor, callback)
+                awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
+            }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = ACTIVITY_PREFIX,
+                initialValue = ACTIVITY_DEFAULT,
+            )
+            .stateIn(
+                scope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = ACTIVITY_DEFAULT,
+            )
+    }
+
+    // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer.
+    private fun prettyPrintActivity(activity: Int): String {
+        return when (activity) {
+            WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
+            WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
+            WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
+            WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
+            else -> "INVALID"
+        }
+    }
+
+    private const val ACTIVITY_PREFIX = "wifiActivity"
+    val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 4e52be9..7c7b58d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -28,39 +28,47 @@
 import android.net.NetworkRequest
 import android.net.wifi.WifiInfo
 import android.net.wifi.WifiManager
-import android.net.wifi.WifiManager.TrafficStateCallback
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import java.util.concurrent.Executor
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /** Real implementation of [WifiRepository]. */
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -76,9 +84,22 @@
     logger: WifiInputLogger,
     @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
     @Main mainExecutor: Executor,
-    @Application scope: CoroutineScope,
-    wifiManager: WifiManager,
-) : RealWifiRepository {
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Application private val scope: CoroutineScope,
+    private val wifiManager: WifiManager,
+) : WifiRepositoryDagger {
+
+    override fun start() {
+        // There are two possible [WifiRepository] implementations: This class (old) and
+        // [WifiRepositoryFromTrackerLib] (new). While we migrate to the new class, we want this old
+        // class to still be running in the background so that we can collect logs and compare
+        // discrepancies. This #start method collects on the flows to ensure that the logs are
+        // collected.
+        scope.launch { isWifiEnabled.collect {} }
+        scope.launch { isWifiDefault.collect {} }
+        scope.launch { wifiNetwork.collect {} }
+        scope.launch { wifiActivity.collect {} }
+    }
 
     private val wifiStateChangeEvents: Flow<Unit> =
         broadcastDispatcher
@@ -93,20 +114,25 @@
     // have changed.
     override val isWifiEnabled: StateFlow<Boolean> =
         merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
-            .mapLatest { wifiManager.isWifiEnabled }
+            .onStart { emit(Unit) }
+            .mapLatest { isWifiEnabled() }
             .distinctUntilChanged()
             .logDiffsForTable(
                 wifiTableLogBuffer,
                 columnPrefix = "",
-                columnName = "isEnabled",
-                initialValue = wifiManager.isWifiEnabled,
+                columnName = COL_NAME_IS_ENABLED,
+                initialValue = false,
             )
             .stateIn(
                 scope = scope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = wifiManager.isWifiEnabled,
+                started = SharingStarted.Eagerly,
+                initialValue = false,
             )
 
+    // [WifiManager.isWifiEnabled] is a blocking IPC call, so fetch it in the background.
+    private suspend fun isWifiEnabled(): Boolean =
+        withContext(bgDispatcher) { wifiManager.isWifiEnabled }
+
     override val isWifiDefault: StateFlow<Boolean> =
         connectivityRepository.defaultConnections
             // TODO(b/274493701): Should wifi be considered default if it's carrier merged?
@@ -115,7 +141,7 @@
             .logDiffsForTable(
                 wifiTableLogBuffer,
                 columnPrefix = "",
-                columnName = "isDefault",
+                columnName = COL_NAME_IS_DEFAULT,
                 initialValue = false,
             )
             .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
@@ -192,30 +218,20 @@
                 initialValue = WIFI_NETWORK_DEFAULT,
             )
 
+    // Secondary networks can only be supported by [WifiRepositoryViaTrackerLib].
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+        MutableStateFlow(emptyList<WifiNetworkModel>()).asStateFlow()
+
     override val wifiActivity: StateFlow<DataActivityModel> =
-        conflatedCallbackFlow {
-                val callback = TrafficStateCallback { state ->
-                    logger.logActivity(prettyPrintActivity(state))
-                    trySend(state.toWifiDataActivityModel())
-                }
-                wifiManager.registerTrafficStateCallback(mainExecutor, callback)
-                awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
-            }
-            .logDiffsForTable(
-                wifiTableLogBuffer,
-                columnPrefix = ACTIVITY_PREFIX,
-                initialValue = ACTIVITY_DEFAULT,
-            )
-            .stateIn(
-                scope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = ACTIVITY_DEFAULT,
-            )
+        WifiRepositoryHelper.createActivityFlow(
+            wifiManager,
+            mainExecutor,
+            scope,
+            wifiTableLogBuffer,
+            logger::logActivity,
+        )
 
     companion object {
-        private const val ACTIVITY_PREFIX = "wifiActivity"
-
-        val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
         // Start out with no known wifi network.
         // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an
         // initial fetch to get a starting wifi network. But, it uses a deprecated API
@@ -224,6 +240,8 @@
         // NetworkCallback inside [wifiNetwork] for our wifi network information.
         val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
 
+        const val WIFI_STATE_DEFAULT = WifiManager.WIFI_STATE_DISABLED
+
         private fun createWifiNetworkModel(
             wifiInfo: WifiInfo,
             network: Network,
@@ -250,6 +268,8 @@
                     isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED),
                     level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
                     wifiInfo.ssid,
+                    // This repository doesn't support any hotspot information.
+                    WifiNetworkModel.HotspotDeviceType.NONE,
                     wifiInfo.isPasspointAp,
                     wifiInfo.isOsuAp,
                     wifiInfo.passpointProviderFriendlyName
@@ -257,16 +277,6 @@
             }
         }
 
-        private fun prettyPrintActivity(activity: Int): String {
-            return when (activity) {
-                TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
-                TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
-                TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
-                TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
-                else -> "INVALID"
-            }
-        }
-
         private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest =
             NetworkRequest.Builder()
                 .clearCapabilities()
@@ -274,9 +284,6 @@
                 .addTransportType(TRANSPORT_WIFI)
                 .addTransportType(TRANSPORT_CELLULAR)
                 .build()
-
-        private const val CARRIER_MERGED_INVALID_SUB_ID_REASON =
-            "Wifi network was carrier merged but had invalid sub ID"
     }
 
     @SysUISingleton
@@ -289,6 +296,7 @@
         private val logger: WifiInputLogger,
         @WifiTableLog private val wifiTableLogBuffer: TableLogBuffer,
         @Main private val mainExecutor: Executor,
+        @Background private val bgDispatcher: CoroutineDispatcher,
         @Application private val scope: CoroutineScope,
     ) {
         fun create(wifiManager: WifiManager): WifiRepositoryImpl {
@@ -299,6 +307,7 @@
                 logger,
                 wifiTableLogBuffer,
                 mainExecutor,
+                bgDispatcher,
                 scope,
                 wifiManager,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
new file mode 100644
index 0000000..d4f40dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
+
+import android.net.wifi.WifiManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
+import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibInputLog
+import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibTableLog
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_STATE_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType
+import com.android.wifitrackerlib.HotspotNetworkEntry
+import com.android.wifitrackerlib.MergedCarrierEntry
+import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
+import com.android.wifitrackerlib.WifiPickerTracker
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * An implementation of [WifiRepository] that uses [com.android.wifitrackerlib] as the source of
+ * truth for wifi information.
+ *
+ * Serves as a possible replacement for [WifiRepositoryImpl]. See b/292534484.
+ */
+@SysUISingleton
+class WifiRepositoryViaTrackerLib
+@Inject
+constructor(
+    featureFlags: FeatureFlags,
+    @Application private val scope: CoroutineScope,
+    @Main private val mainExecutor: Executor,
+    private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
+    private val wifiManager: WifiManager,
+    @WifiTrackerLibInputLog private val inputLogger: LogBuffer,
+    @WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer,
+) : WifiRepositoryViaTrackerLibDagger, LifecycleOwner {
+
+    override val lifecycle =
+        LifecycleRegistry(this).also {
+            mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
+        }
+
+    private val isInstantTetherEnabled = featureFlags.isEnabled(Flags.INSTANT_TETHER)
+
+    private var wifiPickerTracker: WifiPickerTracker? = null
+
+    private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
+        var current =
+            WifiPickerTrackerInfo(
+                state = WIFI_STATE_DEFAULT,
+                isDefault = false,
+                primaryNetwork = WIFI_NETWORK_DEFAULT,
+                secondaryNetworks = emptyList(),
+            )
+        callbackFlow {
+                val callback =
+                    object : WifiPickerTracker.WifiPickerTrackerCallback {
+                        override fun onWifiEntriesChanged() {
+                            val connectedEntry = wifiPickerTracker?.connectedWifiEntry
+                            logOnWifiEntriesChanged(connectedEntry)
+
+                            val secondaryNetworks =
+                                if (featureFlags.isEnabled(Flags.WIFI_SECONDARY_NETWORKS)) {
+                                    val activeNetworks =
+                                        wifiPickerTracker?.activeWifiEntries ?: emptyList()
+                                    activeNetworks
+                                        .filter { it != connectedEntry && !it.isPrimaryNetwork }
+                                        .map { it.toWifiNetworkModel() }
+                                } else {
+                                    emptyList()
+                                }
+
+                            // [WifiPickerTracker.connectedWifiEntry] will return the same instance
+                            // but with updated internals. For example, when its validation status
+                            // changes from false to true, the same instance is re-used but with the
+                            // validated field updated.
+                            //
+                            // Because it's the same instance, the flow won't re-emit the value
+                            // (even though the internals have changed). So, we need to transform it
+                            // into our internal model immediately. [toWifiNetworkModel] always
+                            // returns a new instance, so the flow is guaranteed to emit.
+                            send(
+                                newPrimaryNetwork = connectedEntry?.toPrimaryWifiNetworkModel()
+                                        ?: WIFI_NETWORK_DEFAULT,
+                                newSecondaryNetworks = secondaryNetworks,
+                                newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
+                            )
+                        }
+
+                        override fun onWifiStateChanged() {
+                            val state = wifiPickerTracker?.wifiState
+                            logOnWifiStateChanged(state)
+                            send(newState = state ?: WIFI_STATE_DEFAULT)
+                        }
+
+                        override fun onNumSavedNetworksChanged() {}
+
+                        override fun onNumSavedSubscriptionsChanged() {}
+
+                        private fun send(
+                            newState: Int = current.state,
+                            newIsDefault: Boolean = current.isDefault,
+                            newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
+                            newSecondaryNetworks: List<WifiNetworkModel> =
+                                current.secondaryNetworks,
+                        ) {
+                            val new =
+                                WifiPickerTrackerInfo(
+                                    newState,
+                                    newIsDefault,
+                                    newPrimaryNetwork,
+                                    newSecondaryNetworks,
+                                )
+                            current = new
+                            trySend(new)
+                        }
+                    }
+
+                wifiPickerTracker =
+                    wifiPickerTrackerFactory.create(lifecycle, callback).apply {
+                        // By default, [WifiPickerTracker] will scan to see all available wifi
+                        // networks in the area. Because SysUI only needs to display the
+                        // **connected** network, we don't need scans to be running (and in fact,
+                        // running scans is costly and should be avoided whenever possible).
+                        this?.disableScanning()
+                    }
+                // The lifecycle must be STARTED in order for the callback to receive events.
+                mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
+                awaitClose {
+                    mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED }
+                }
+            }
+            .stateIn(scope, SharingStarted.Eagerly, current)
+    }
+
+    override val isWifiEnabled: StateFlow<Boolean> =
+        wifiPickerTrackerInfo
+            .map { it.state == WifiManager.WIFI_STATE_ENABLED }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                wifiTrackerLibTableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_NAME_IS_ENABLED,
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.Eagerly, false)
+
+    override val wifiNetwork: StateFlow<WifiNetworkModel> =
+        wifiPickerTrackerInfo
+            .map { it.primaryNetwork }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                wifiTrackerLibTableLogBuffer,
+                columnPrefix = "",
+                initialValue = WIFI_NETWORK_DEFAULT,
+            )
+            .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
+
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+        wifiPickerTrackerInfo
+            .map { it.secondaryNetworks }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                wifiTrackerLibTableLogBuffer,
+                columnPrefix = "",
+                columnName = "secondaryNetworks",
+                initialValue = emptyList(),
+            )
+            .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+    /**
+     * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the
+     * primary network. Returns an inactive network if it's not primary.
+     */
+    private fun WifiEntry.toPrimaryWifiNetworkModel(): WifiNetworkModel {
+        return if (!this.isPrimaryNetwork) {
+            WIFI_NETWORK_DEFAULT
+        } else {
+            this.toWifiNetworkModel()
+        }
+    }
+
+    /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */
+    private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel {
+        return if (this is MergedCarrierEntry) {
+            this.convertCarrierMergedToModel()
+        } else {
+            this.convertNormalToModel()
+        }
+    }
+
+    private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel {
+        return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) {
+            WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
+        } else {
+            WifiNetworkModel.CarrierMerged(
+                networkId = NETWORK_ID,
+                subscriptionId = this.subscriptionId,
+                level = this.level,
+                // WifiManager APIs to calculate the signal level start from 0, so
+                // maxSignalLevel + 1 represents the total level buckets count.
+                numberOfLevels = wifiManager.maxSignalLevel + 1,
+            )
+        }
+    }
+
+    private fun WifiEntry.convertNormalToModel(): WifiNetworkModel {
+        if (this.level == WIFI_LEVEL_UNREACHABLE || this.level !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX) {
+            // If our level means the network is unreachable or the level is otherwise invalid, we
+            // don't have an active network.
+            return WifiNetworkModel.Inactive
+        }
+
+        val hotspotDeviceType =
+            if (isInstantTetherEnabled && this is HotspotNetworkEntry) {
+                this.deviceType.toHotspotDeviceType()
+            } else {
+                WifiNetworkModel.HotspotDeviceType.NONE
+            }
+
+        return WifiNetworkModel.Active(
+            networkId = NETWORK_ID,
+            isValidated = this.hasInternetAccess(),
+            level = this.level,
+            ssid = this.title,
+            hotspotDeviceType = hotspotDeviceType,
+            // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the  SSID for
+            // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can
+            // always be false/null in this repository.
+            // TODO(b/292534484): Remove these fields from the wifi network model once this
+            //  repository is fully enabled.
+            isPasspointAccessPoint = false,
+            isOnlineSignUpForPasspointAccessPoint = false,
+            passpointProviderFriendlyName = null,
+        )
+    }
+
+    override val isWifiDefault: StateFlow<Boolean> =
+        wifiPickerTrackerInfo
+            .map { it.isDefault }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                wifiTrackerLibTableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_NAME_IS_DEFAULT,
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.Eagerly, false)
+
+    override val wifiActivity: StateFlow<DataActivityModel> =
+        WifiRepositoryHelper.createActivityFlow(
+            wifiManager,
+            mainExecutor,
+            scope,
+            wifiTrackerLibTableLogBuffer,
+            this::logActivity,
+        )
+
+    private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) {
+        inputLogger.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = connectedEntry.toString() },
+            { "onWifiEntriesChanged. ConnectedEntry=$str1" },
+        )
+    }
+
+    private fun logOnWifiStateChanged(state: Int?) {
+        inputLogger.log(
+            TAG,
+            LogLevel.DEBUG,
+            { int1 = state ?: -1 },
+            { "onWifiStateChanged. State=${if (int1 == -1) null else int1}" },
+        )
+    }
+
+    private fun logActivity(activity: String) {
+        inputLogger.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "onActivityChanged: $str1" })
+    }
+
+    /**
+     * Data class storing all the information fetched from [WifiPickerTracker].
+     *
+     * Used so that we only register a single callback on [WifiPickerTracker].
+     */
+    data class WifiPickerTrackerInfo(
+        /** The current wifi state. See [WifiManager.getWifiState]. */
+        val state: Int,
+        /** True if wifi is currently the default connection and false otherwise. */
+        val isDefault: Boolean,
+        /** The currently primary wifi network. */
+        val primaryNetwork: WifiNetworkModel,
+        /** The current secondary network(s), if any. Specifically excludes the primary network. */
+        val secondaryNetworks: List<WifiNetworkModel>
+    )
+
+    @SysUISingleton
+    class Factory
+    @Inject
+    constructor(
+        private val featureFlags: FeatureFlags,
+        @Application private val scope: CoroutineScope,
+        @Main private val mainExecutor: Executor,
+        private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
+        @WifiTrackerLibInputLog private val inputLogger: LogBuffer,
+        @WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer,
+    ) {
+        fun create(wifiManager: WifiManager): WifiRepositoryViaTrackerLib {
+            return WifiRepositoryViaTrackerLib(
+                featureFlags,
+                scope,
+                mainExecutor,
+                wifiPickerTrackerFactory,
+                wifiManager,
+                inputLogger,
+                wifiTrackerLibTableLogBuffer,
+            )
+        }
+    }
+
+    companion object {
+        private const val TAG = "WifiTrackerLibInputLog"
+
+        /**
+         * [WifiNetworkModel.Active.networkId] is only used at the repository layer. It's used by
+         * [WifiRepositoryImpl], which tracks the ID in order to correctly apply the framework
+         * callbacks within the repository.
+         *
+         * Since this class does not need to manually apply framework callbacks and since the
+         * network ID is not used beyond the repository, it's safe to use an invalid ID in this
+         * repository.
+         *
+         * The [WifiNetworkModel.Active.networkId] field should be deleted once we've fully migrated
+         * to [WifiRepositoryViaTrackerLib].
+         */
+        private const val NETWORK_ID = -1
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
index 4a9ceac..f244376 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
@@ -20,7 +20,7 @@
 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.core.LogLevel
 import com.android.systemui.statusbar.pipeline.dagger.WifiInputLog
 import com.android.systemui.statusbar.pipeline.shared.LoggerHelper
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
index 4b33c88..7078a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
@@ -17,11 +17,13 @@
 package com.android.systemui.statusbar.pipeline.wifi.shared.model
 
 import android.net.wifi.WifiManager.UNKNOWN_SSID
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo
 import android.telephony.SubscriptionManager
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.log.table.Diffable
 import com.android.systemui.log.table.TableRowLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType
 
 /** Provides information about the current wifi network. */
 sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
@@ -52,6 +54,7 @@
             row.logChange(COL_LEVEL, LEVEL_DEFAULT)
             row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
             row.logChange(COL_SSID, null)
+            row.logChange(COL_HOTSPOT, null)
             row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
             row.logChange(COL_ONLINE_SIGN_UP, false)
             row.logChange(COL_PASSPOINT_NAME, null)
@@ -83,6 +86,7 @@
             row.logChange(COL_LEVEL, LEVEL_DEFAULT)
             row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
             row.logChange(COL_SSID, null)
+            row.logChange(COL_HOTSPOT, null)
             row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
             row.logChange(COL_ONLINE_SIGN_UP, false)
             row.logChange(COL_PASSPOINT_NAME, null)
@@ -110,6 +114,7 @@
             row.logChange(COL_LEVEL, LEVEL_DEFAULT)
             row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
             row.logChange(COL_SSID, null)
+            row.logChange(COL_HOTSPOT, null)
             row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
             row.logChange(COL_ONLINE_SIGN_UP, false)
             row.logChange(COL_PASSPOINT_NAME, null)
@@ -184,6 +189,7 @@
             row.logChange(COL_LEVEL, level)
             row.logChange(COL_NUM_LEVELS, numberOfLevels)
             row.logChange(COL_SSID, null)
+            row.logChange(COL_HOTSPOT, null)
             row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
             row.logChange(COL_ONLINE_SIGN_UP, false)
             row.logChange(COL_PASSPOINT_NAME, null)
@@ -209,6 +215,12 @@
         /** See [android.net.wifi.WifiInfo.ssid]. */
         val ssid: String? = null,
 
+        /**
+         * The type of device providing a hotspot connection, or [HotspotDeviceType.NONE] if this
+         * isn't a hotspot connection.
+         */
+        val hotspotDeviceType: HotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE,
+
         /** See [android.net.wifi.WifiInfo.isPasspointAp]. */
         val isPasspointAccessPoint: Boolean = false,
 
@@ -247,6 +259,9 @@
             if (prevVal.ssid != ssid) {
                 row.logChange(COL_SSID, ssid)
             }
+            if (prevVal.hotspotDeviceType != hotspotDeviceType) {
+                row.logChange(COL_HOTSPOT, hotspotDeviceType.name)
+            }
 
             // TODO(b/238425913): The passpoint-related values are frequently never used, so it
             //   would be great to not log them when they're not used.
@@ -272,6 +287,7 @@
             row.logChange(COL_LEVEL, level)
             row.logChange(COL_NUM_LEVELS, null)
             row.logChange(COL_SSID, ssid)
+            row.logChange(COL_HOTSPOT, hotspotDeviceType.name)
             row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint)
             row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint)
             row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName)
@@ -298,13 +314,51 @@
         }
 
         companion object {
+            // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX] instead
+            // once the migration to WifiTrackerLib is complete.
             @VisibleForTesting internal const val MAX_VALID_LEVEL = 4
         }
     }
 
     companion object {
+        // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN] instead
+        // once the migration to WifiTrackerLib is complete.
         @VisibleForTesting internal const val MIN_VALID_LEVEL = 0
     }
+
+    /**
+     * Enum for the type of device providing the hotspot connection, or [NONE] if this connection
+     * isn't a hotspot.
+     */
+    enum class HotspotDeviceType {
+        /* This wifi connection isn't a hotspot. */
+        NONE,
+        /** The device type for this hotspot is unknown. */
+        UNKNOWN,
+        PHONE,
+        TABLET,
+        LAPTOP,
+        WATCH,
+        AUTO,
+        /** The device type sent for this hotspot is invalid to SysUI. */
+        INVALID,
+    }
+
+    /**
+     * Converts a device type from [com.android.wifitrackerlib.HotspotNetworkEntry.deviceType] to
+     * our internal representation.
+     */
+    fun @receiver:DeviceType Int.toHotspotDeviceType(): HotspotDeviceType {
+        return when (this) {
+            NetworkProviderInfo.DEVICE_TYPE_UNKNOWN -> HotspotDeviceType.UNKNOWN
+            NetworkProviderInfo.DEVICE_TYPE_PHONE -> HotspotDeviceType.PHONE
+            NetworkProviderInfo.DEVICE_TYPE_TABLET -> HotspotDeviceType.TABLET
+            NetworkProviderInfo.DEVICE_TYPE_LAPTOP -> HotspotDeviceType.LAPTOP
+            NetworkProviderInfo.DEVICE_TYPE_WATCH -> HotspotDeviceType.WATCH
+            NetworkProviderInfo.DEVICE_TYPE_AUTO -> HotspotDeviceType.AUTO
+            else -> HotspotDeviceType.INVALID
+        }
+    }
 }
 
 const val TYPE_CARRIER_MERGED = "CarrierMerged"
@@ -319,6 +373,7 @@
 const val COL_LEVEL = "level"
 const val COL_NUM_LEVELS = "maxLevel"
 const val COL_SSID = "ssid"
+const val COL_HOTSPOT = "hotspot"
 const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint"
 const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint"
 const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
index 174298a..7a60d96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel.Companion.viewModelForLocation
@@ -46,7 +45,6 @@
 constructor(
     private val iconController: StatusBarIconController,
     private val wifiViewModel: WifiViewModel,
-    private val statusBarPipelineFlags: StatusBarPipelineFlags,
 ) {
     /**
      * Binds the container for all the status bar icons to a view model, so that we inflate the wifi
@@ -60,20 +58,14 @@
         statusBarIconGroup: ViewGroup,
         location: StatusBarLocation,
     ): LocationBasedWifiViewModel {
-        val locationViewModel =
-            viewModelForLocation(wifiViewModel, statusBarPipelineFlags, location)
+        val locationViewModel = viewModelForLocation(wifiViewModel, location)
 
         statusBarIconGroup.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 launch {
                     locationViewModel.wifiIcon.collect { wifiIcon ->
                         // Only notify the icon controller if we want to *render* the new icon.
-                        // Note that this flow may still run if
-                        // [statusBarPipelineFlags.runNewWifiIconBackend] is true because we may
-                        // want to get the logging data without rendering.
-                        if (
-                            wifiIcon is WifiIcon.Visible && statusBarPipelineFlags.useNewWifiIcon()
-                        ) {
+                        if (wifiIcon is WifiIcon.Visible) {
                             iconController.setNewWifiIcon()
                         }
                     }
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 e819c4f..3082a66 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
@@ -157,16 +157,10 @@
             }
 
             override fun onIconTintChanged(newTint: Int) {
-                if (viewModel.useDebugColoring) {
-                    return
-                }
                 iconTint.value = newTint
             }
 
             override fun onDecorTintChanged(newTint: Int) {
-                if (viewModel.useDebugColoring) {
-                    return
-                }
                 decorTint.value = newTint
             }
 
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 b731a41..cd5b92c 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
@@ -18,7 +18,6 @@
 
 import android.graphics.Color
 import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 
 /**
  * A view model for a wifi icon in a specific location. This allows us to control parameters that
@@ -27,18 +26,9 @@
  * Must be subclassed for each distinct location.
  */
 abstract class LocationBasedWifiViewModel(
-    val commonImpl: WifiViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-    debugTint: Int,
+    private val commonImpl: WifiViewModelCommon,
 ) : WifiViewModelCommon by commonImpl {
-    val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring()
-
-    val defaultColor: Int =
-        if (useDebugColoring) {
-            debugTint
-        } else {
-            Color.WHITE
-        }
+    val defaultColor: Int = Color.WHITE
 
     companion object {
         /**
@@ -47,13 +37,12 @@
          */
         fun viewModelForLocation(
             commonImpl: WifiViewModelCommon,
-            flags: StatusBarPipelineFlags,
             location: StatusBarLocation,
         ): LocationBasedWifiViewModel =
             when (location) {
-                StatusBarLocation.HOME -> HomeWifiViewModel(commonImpl, flags)
-                StatusBarLocation.KEYGUARD -> KeyguardWifiViewModel(commonImpl, flags)
-                StatusBarLocation.QS -> QsWifiViewModel(commonImpl, flags)
+                StatusBarLocation.HOME -> HomeWifiViewModel(commonImpl)
+                StatusBarLocation.KEYGUARD -> KeyguardWifiViewModel(commonImpl)
+                StatusBarLocation.QS -> QsWifiViewModel(commonImpl)
             }
     }
 }
@@ -64,23 +53,14 @@
  */
 class HomeWifiViewModel(
     commonImpl: WifiViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-) :
-    WifiViewModelCommon,
-    LocationBasedWifiViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.CYAN)
+) : WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)
 
 /** A view model for the wifi icon shown on keyguard (lockscreen). */
 class KeyguardWifiViewModel(
     commonImpl: WifiViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-) :
-    WifiViewModelCommon,
-    LocationBasedWifiViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.MAGENTA)
+) : WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)
 
 /** A view model for the wifi icon shown in quick settings (when the shade is pulled down). */
 class QsWifiViewModel(
     commonImpl: WifiViewModelCommon,
-    statusBarPipelineFlags: StatusBarPipelineFlags,
-) :
-    WifiViewModelCommon,
-    LocationBasedWifiViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.GREEN)
+) : WifiViewModelCommon, LocationBasedWifiViewModel(commonImpl)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 3d16591..37eda64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -19,6 +19,9 @@
 import android.annotation.Nullable;
 import android.view.View;
 
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 
@@ -136,7 +139,7 @@
      * A listener that will be notified whenever a change in battery level or power save mode has
      * occurred.
      */
-    interface BatteryStateChangeCallback {
+    interface BatteryStateChangeCallback extends Dumpable {
 
         default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
         }
@@ -158,6 +161,14 @@
 
         default void onIsBatteryDefenderChanged(boolean isBatteryDefender) {
         }
+
+        default void onIsIncompatibleChargingChanged(boolean isIncompatibleCharging) {
+        }
+
+        @Override
+        default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+            pw.println(this);
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index e69d86c..4b51511 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -22,17 +22,20 @@
 import static android.os.BatteryManager.EXTRA_PRESENT;
 
 import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+import static com.android.systemui.util.DumpUtilsKt.asIndenting;
 
 import android.annotation.WorkerThread;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
 import android.os.BatteryManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.PowerSaveState;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.view.View;
 
@@ -40,6 +43,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.Utils;
 import com.android.settingslib.fuelgauge.BatterySaverUtils;
 import com.android.settingslib.fuelgauge.Estimate;
 import com.android.settingslib.utils.PowerUtil;
@@ -95,6 +99,7 @@
     private boolean mAodPowerSave;
     private boolean mWirelessCharging;
     private boolean mIsBatteryDefender = false;
+    private boolean mIsIncompatibleCharging = false;
     private boolean mTestMode = false;
     @VisibleForTesting
     boolean mHasReceivedBattery = false;
@@ -134,6 +139,7 @@
         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
         filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
         filter.addAction(ACTION_LEVEL_TEST);
+        filter.addAction(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
         mBroadcastDispatcher.registerReceiver(this, filter);
     }
 
@@ -157,15 +163,30 @@
     }
 
     @Override
-    public void dump(PrintWriter pw, String[] args) {
-        pw.println("BatteryController state:");
-        pw.print("  mLevel="); pw.println(mLevel);
-        pw.print("  mPluggedIn="); pw.println(mPluggedIn);
-        pw.print("  mCharging="); pw.println(mCharging);
-        pw.print("  mCharged="); pw.println(mCharged);
-        pw.print("  mIsBatteryDefender="); pw.println(mIsBatteryDefender);
-        pw.print("  mPowerSave="); pw.println(mPowerSave);
-        pw.print("  mStateUnknown="); pw.println(mStateUnknown);
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+        IndentingPrintWriter ipw = asIndenting(pw);
+        ipw.println("BatteryController state:");
+        ipw.increaseIndent();
+        ipw.print("mHasReceivedBattery="); ipw.println(mHasReceivedBattery);
+        ipw.print("mLevel="); ipw.println(mLevel);
+        ipw.print("mPluggedIn="); ipw.println(mPluggedIn);
+        ipw.print("mCharging="); ipw.println(mCharging);
+        ipw.print("mCharged="); ipw.println(mCharged);
+        ipw.print("mIsBatteryDefender="); ipw.println(mIsBatteryDefender);
+        ipw.print("mIsIncompatibleCharging="); ipw.println(mIsIncompatibleCharging);
+        ipw.print("mPowerSave="); ipw.println(mPowerSave);
+        ipw.print("mStateUnknown="); ipw.println(mStateUnknown);
+        ipw.println("Callbacks:------------------");
+        // Since the above lines are already indented, we need to indent twice for the callbacks.
+        ipw.increaseIndent();
+        synchronized (mChangeCallbacks) {
+            final int n = mChangeCallbacks.size();
+            for (int i = 0; i < n; i++) {
+                mChangeCallbacks.get(i).dump(ipw, args);
+            }
+        }
+        ipw.decreaseIndent();
+        ipw.println("------------------");
     }
 
     @Override
@@ -198,6 +219,7 @@
         cb.onBatteryUnknownStateChanged(mStateUnknown);
         cb.onWirelessChargingChanged(mWirelessCharging);
         cb.onIsBatteryDefenderChanged(mIsBatteryDefender);
+        cb.onIsIncompatibleChargingChanged(mIsIncompatibleCharging);
     }
 
     @Override
@@ -213,7 +235,7 @@
         if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
             if (mTestMode && !intent.getBooleanExtra("testmode", false)) return;
             mHasReceivedBattery = true;
-            mLevel = (int)(100f
+            mLevel = (int) (100f
                     * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
                     / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
             mPluggedChargingSource = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
@@ -246,6 +268,12 @@
             fireBatteryLevelChanged();
         } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
             updatePowerSave();
+        } else if (action.equals(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED)) {
+            boolean isIncompatibleCharging = Utils.containsIncompatibleChargers(mContext, TAG);
+            if (isIncompatibleCharging != mIsIncompatibleCharging) {
+                mIsIncompatibleCharging = isIncompatibleCharging;
+                fireIsIncompatibleChargingChanged();
+            }
         } else if (action.equals(ACTION_LEVEL_TEST)) {
             mTestMode = true;
             mMainHandler.post(new Runnable() {
@@ -254,6 +282,7 @@
                 int mSavedLevel = mLevel;
                 boolean mSavedPluggedIn = mPluggedIn;
                 Intent mTestIntent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+
                 @Override
                 public void run() {
                     if (mCurrentLevel < 0) {
@@ -317,6 +346,13 @@
         return mIsBatteryDefender;
     }
 
+    /**
+     * Returns whether the charging adapter is incompatible.
+     */
+    public boolean isIncompatibleCharging() {
+        return mIsIncompatibleCharging;
+    }
+
     @Override
     public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
         // Need to fetch or refresh the estimate, but it may involve binder calls so offload the
@@ -437,6 +473,15 @@
         }
     }
 
+    private void fireIsIncompatibleChargingChanged() {
+        synchronized (mChangeCallbacks) {
+            final int n = mChangeCallbacks.size();
+            for (int i = 0; i < n; i++) {
+                mChangeCallbacks.get(i).onIsIncompatibleChargingChanged(mIsIncompatibleCharging);
+            }
+        }
+    }
+
     @Override
     public void dispatchDemoCommand(String command, Bundle args) {
         if (!mDemoModeController.isInDemoMode()) {
@@ -448,6 +493,7 @@
         String powerSave = args.getString("powersave");
         String present = args.getString("present");
         String defender = args.getString("defender");
+        String incompatible = args.getString("incompatible");
         if (level != null) {
             mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
         }
@@ -466,6 +512,10 @@
             mIsBatteryDefender = defender.equals("true");
             fireIsBatteryDefenderChanged();
         }
+        if (incompatible != null) {
+            mIsIncompatibleCharging = incompatible.equals("true");
+            fireIsIncompatibleChargingChanged();
+        }
         fireBatteryLevelChanged();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 6875b52..f994372 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -374,6 +374,13 @@
 
     @Override
     public void onDensityOrFontScaleChanged() {
+        reloadDimens();
+    }
+
+    private void reloadDimens() {
+        // reset mCachedWidth so the new width would be updated properly when next onMeasure
+        mCachedWidth = -1;
+
         FontSizeUtils.updateFontSize(this, R.dimen.status_bar_clock_size);
         setPaddingRelative(
                 mContext.getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index 6b80a9d..b2ef818 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -42,5 +42,6 @@
         default void onThemeChanged() {}
         default void onLocaleListChanged() {}
         default void onLayoutDirectionChanged(boolean isLayoutRtl) {}
+        default void onOrientationChanged(int orientation) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index 4950482..ffb743f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -128,7 +128,8 @@
 
         val prefs = userContextProvider.userContext.getSharedPreferences(
             PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
-        val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
+        val seededPackages =
+            prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet()
 
         val controlsController = controlsComponent.getControlsController().get()
         val componentsToSeed = mutableListOf<ComponentName>()
@@ -174,7 +175,8 @@
     }
 
     private fun addPackageToSeededSet(prefs: SharedPreferences, pkg: String) {
-        val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
+        val seededPackages =
+            prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet()
         val updatedPkgs = seededPackages.toMutableSet()
         updatedPkgs.add(pkg)
         prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
index 6ba2a81..096ad1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
@@ -22,7 +22,7 @@
 import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED
 import com.android.internal.R
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel.VERBOSE
+import com.android.systemui.log.core.LogLevel.VERBOSE
 import com.android.systemui.log.dagger.DeviceStateAutoRotationLog
 import javax.inject.Inject
 
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 06ed1fd..175473f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -18,8 +18,8 @@
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
 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.core.LogLevel.INFO
+import com.android.systemui.log.core.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/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index a82646a..63dcad9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.hardware.biometrics.BiometricSourceType.FACE;
+
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -36,6 +40,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 
 import dagger.Lazy;
@@ -47,6 +52,7 @@
 import javax.inject.Inject;
 
 /**
+ *
  */
 @SysUISingleton
 public class KeyguardStateControllerImpl implements KeyguardStateController, Dumpable {
@@ -101,7 +107,10 @@
      */
     private boolean mSnappingKeyguardBackAfterSwipe = false;
 
+    private FeatureFlags mFeatureFlags;
+
     /**
+     *
      */
     @Inject
     public KeyguardStateControllerImpl(
@@ -110,13 +119,15 @@
             LockPatternUtils lockPatternUtils,
             Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
             KeyguardUpdateMonitorLogger logger,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            FeatureFlags featureFlags) {
         mContext = context;
         mLogger = logger;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
         mUnlockAnimationControllerLazy = keyguardUnlockAnimationController;
+        mFeatureFlags = featureFlags;
 
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
 
@@ -199,6 +210,11 @@
         Trace.endSection();
     }
 
+    private void notifyKeyguardFaceAuthEnabledChanged() {
+        // Copy the list to allow removal during callback.
+        new ArrayList<>(mCallbacks).forEach(Callback::onFaceAuthEnabledChanged);
+    }
+
     private void notifyUnlockedChanged() {
         Trace.beginSection("KeyguardStateController#notifyUnlockedChanged");
         // Copy the list to allow removal during callback.
@@ -265,7 +281,8 @@
     @Override
     public boolean isKeyguardScreenRotationAllowed() {
         return SystemProperties.getBoolean("lockscreen.rot_override", false)
-                || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation);
+                || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation)
+                || mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE);
     }
 
     @Override
@@ -419,6 +436,16 @@
         }
 
         @Override
+        public void onBiometricEnrollmentStateChanged(BiometricSourceType biometricSourceType) {
+            if (biometricSourceType == FACE) {
+                // We only care about enrollment state here. Keyguard face auth enabled is just
+                // same as face auth enrolled
+                update(false);
+                notifyKeyguardFaceAuthEnabledChanged();
+            }
+        }
+
+        @Override
         public void onStartedWakingUp() {
             update(false /* updateAlways */);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index f8c36dc..518a9b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -55,7 +55,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.Utils;
 import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.ArrayList;
@@ -362,7 +361,8 @@
         private static final int MSG_ADD_CALLBACK = 3;
         private static final int MSG_REMOVE_CALLBACK = 4;
 
-        private ArrayList<LocationChangeCallback> mSettingsChangeCallbacks = new ArrayList<>();
+        private final ArrayList<LocationChangeCallback> mSettingsChangeCallbacks =
+                new ArrayList<>();
 
         H(Looper looper) {
             super(looper);
@@ -388,14 +388,23 @@
         }
 
         private void locationActiveChanged() {
-            Utils.safeForeach(mSettingsChangeCallbacks,
-                    cb -> cb.onLocationActiveChanged(mAreActiveLocationRequests));
+            synchronized (mSettingsChangeCallbacks) {
+                final int n = mSettingsChangeCallbacks.size();
+                for (int i = 0; i < n; i++) {
+                    mSettingsChangeCallbacks.get(i)
+                            .onLocationActiveChanged(mAreActiveLocationRequests);
+                }
+            }
         }
 
         private void locationSettingsChanged() {
             boolean isEnabled = isLocationEnabled();
-            Utils.safeForeach(mSettingsChangeCallbacks,
-                    cb -> cb.onLocationSettingsChanged(isEnabled));
+            synchronized (mSettingsChangeCallbacks) {
+                final int n = mSettingsChangeCallbacks.size();
+                for (int i = 0; i < n; i++) {
+                    mSettingsChangeCallbacks.get(i).onLocationSettingsChanged(isEnabled);
+                }
+            }
         }
     }
 
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 02b7e91..e00365d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -30,6 +30,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.text.Editable;
 import android.text.SpannedString;
@@ -1032,10 +1033,12 @@
         }
 
         private void hideIme() {
+            Trace.beginSection("RemoteEditText#hideIme");
             final WindowInsetsController insetsController = getWindowInsetsController();
             if (insetsController != null) {
                 insetsController.hide(WindowInsets.Type.ime());
             }
+            Trace.endSection();
         }
 
         private void defocusIfNeeded(boolean animate) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index cac5e32..1776e5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy
 
+import android.app.ActivityOptions
 import android.app.Notification
 import android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY
 import android.app.PendingIntent
@@ -491,7 +492,11 @@
             entry.setHasSentReply()
             try {
                 val intent = createRemoteInputIntent(smartReplies, choice)
-                smartReplies.pendingIntent.send(context, 0, intent)
+                val opts = ActivityOptions.makeBasic()
+                opts.setPendingIntentBackgroundActivityStartMode(
+                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+                smartReplies.pendingIntent.send(context, 0, intent, /* onFinished */null,
+                        /* handler */ null, /* requiredPermission */ null, opts.toBundle())
             } catch (e: PendingIntent.CanceledException) {
                 Log.w(TAG, "Unable to send smart reply", e)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 1c3a8850..dabdcc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -46,7 +46,6 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.Utils;
 import com.android.systemui.util.settings.GlobalSettings;
@@ -68,17 +67,17 @@
     private final Context mContext;
     private final UserTracker mUserTracker;
     private final BroadcastDispatcher mBroadcastDispatcher;
-    private final SettingObserver mModeSetting;
-    private final SettingObserver mConfigSetting;
     private final NotificationManager mNoMan;
     private final AlarmManager mAlarmManager;
     private final SetupObserver mSetupObserver;
     private final UserManager mUserManager;
+    private final GlobalSettings mGlobalSettings;
 
     private int mUserId;
     private boolean mRegistered;
     private ZenModeConfig mConfig;
-    private int mZenMode;
+    // This value is changed in the main thread, but may be read in a background thread.
+    private volatile int mZenMode;
     private long mZenUpdateTime;
     private NotificationManager.Policy mConsolidatedNotificationPolicy;
 
@@ -111,18 +110,20 @@
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
         mUserTracker = userTracker;
-        mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE,
-                userTracker.getUserId()) {
+        mGlobalSettings = globalSettings;
+
+        ContentObserver modeContentObserver = new ContentObserver(handler) {
             @Override
-            protected void handleValueChanged(int value, boolean observedChange) {
+            public void onChange(boolean selfChange) {
+                int value = getModeSettingValueFromProvider();
+                Log.d(TAG, "Zen mode setting changed to " + value);
                 updateZenMode(value);
                 fireZenChanged(value);
             }
         };
-        mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG,
-                userTracker.getUserId()) {
+        ContentObserver configContentObserver = new ContentObserver(handler) {
             @Override
-            protected void handleValueChanged(int value, boolean observedChange) {
+            public void onChange(boolean selfChange) {
                 try {
                     Trace.beginSection("updateZenModeConfig");
                     updateZenModeConfig();
@@ -132,9 +133,9 @@
             }
         };
         mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
-        mModeSetting.setListening(true);
-        updateZenMode(mModeSetting.getValue());
-        mConfigSetting.setListening(true);
+        globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
+        updateZenMode(getModeSettingValueFromProvider());
+        globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG, configContentObserver);
         updateZenModeConfig();
         updateConsolidatedNotificationPolicy();
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
@@ -146,6 +147,10 @@
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
     }
 
+    private int getModeSettingValueFromProvider() {
+        return mGlobalSettings.getInt(Global.ZEN_MODE, /* default */ Global.ZEN_MODE_OFF);
+    }
+
     @Override
     public boolean isVolumeRestricted() {
         return mUserManager.hasUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index e1a7b6d..c2a8e70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -29,6 +29,7 @@
 import com.android.systemui.statusbar.connectivity.AccessPointControllerImpl;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.connectivity.NetworkControllerImpl;
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
@@ -165,7 +166,7 @@
             UserManager userManager,
             UserTracker userTracker,
             @Main Executor mainExecutor,
-            AccessPointControllerImpl.WifiPickerTrackerFactory wifiPickerTrackerFactory
+            WifiPickerTrackerFactory wifiPickerTrackerFactory
     ) {
         AccessPointControllerImpl controller = new AccessPointControllerImpl(
                 userManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
index 3362097..fd7c30f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationAdapter.java
@@ -103,6 +103,8 @@
                 if (mPendingIntent != null) {
                     BroadcastOptions options = BroadcastOptions.makeBasic();
                     options.setInteractive(true);
+                    options.setPendingIntentBackgroundActivityStartMode(
+                            BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
                     mPendingIntent.send(options.toBundle());
                 }
             } catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
index 06cc96e..d696979 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
@@ -46,6 +46,7 @@
 
     public StatusBarWindowView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        setClipChildren(false);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 27aaa68..eb7d339 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -353,8 +353,8 @@
                     // before CoreStartables run, and will not be removed.
                     // In many cases, it reports the battery level of the stylus.
                     registerBatteryListener(deviceId)
-                } else if (device.bluetoothAddress != null) {
-                    onStylusBluetoothConnected(deviceId, device.bluetoothAddress)
+                } else {
+                    device.bluetoothAddress?.let { onStylusBluetoothConnected(deviceId, it) }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
index 3e1c13c..c1ac800 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
@@ -64,9 +64,9 @@
     // These values must only be accessed on the handler.
     private var batteryCapacity = 1.0f
     private var suppressed = false
-    private var inputDeviceId: Int? = null
     private var instanceId: InstanceId? = null
-
+    @VisibleForTesting var inputDeviceId: Int? = null
+      private set
     @VisibleForTesting var instanceIdSequence = InstanceIdSequence(1 shl 13)
 
     fun init() {
@@ -110,10 +110,10 @@
 
     fun updateBatteryState(deviceId: Int, batteryState: BatteryState) {
         handler.post updateBattery@{
+            inputDeviceId = deviceId
             if (batteryState.capacity == batteryCapacity || batteryState.capacity <= 0f)
                 return@updateBattery
 
-            inputDeviceId = deviceId
             batteryCapacity = batteryState.capacity
             debugLog {
                 "Updating notification battery state to $batteryCapacity " +
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index 066ac04..a9d2029 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -18,7 +18,7 @@
 
 import android.view.View
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 
 /** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */
 open class TemporaryViewLogger<T : TemporaryViewInfo>(
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 7ed56e7..f0aae0f 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -48,6 +48,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -88,12 +90,13 @@
     private val chipbarAnimator: ChipbarAnimator,
     private val falsingManager: FalsingManager,
     private val falsingCollector: FalsingCollector,
-    private val swipeChipbarAwayGestureHandler: SwipeChipbarAwayGestureHandler?,
+    private val swipeChipbarAwayGestureHandler: SwipeChipbarAwayGestureHandler,
     private val viewUtil: ViewUtil,
     private val vibratorHelper: VibratorHelper,
     wakeLockBuilder: WakeLock.Builder,
     systemClock: SystemClock,
     tempViewUiEventLogger: TemporaryViewUiEventLogger,
+    private val featureFlags: FeatureFlags,
 ) :
     TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
         context,
@@ -231,14 +234,18 @@
         maybeGetAccessibilityFocus(newInfo, currentView)
 
         // ---- Haptics ----
-        newInfo.vibrationEffect?.let {
-            vibratorHelper.vibrate(
-                Process.myUid(),
-                context.getApplicationContext().getPackageName(),
-                it,
-                newInfo.windowTitle,
-                VIBRATION_ATTRIBUTES,
-            )
+        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            vibratorHelper.performHapticFeedback(parent, newInfo.vibrationConstant)
+        } else {
+            newInfo.vibrationEffect?.let {
+                vibratorHelper.vibrate(
+                    Process.myUid(),
+                    context.getApplicationContext().getPackageName(),
+                    it,
+                    newInfo.windowTitle,
+                    VIBRATION_ATTRIBUTES,
+                )
+            }
         }
     }
 
@@ -289,10 +296,6 @@
     }
 
     private fun updateGestureListening() {
-        if (swipeChipbarAwayGestureHandler == null) {
-            return
-        }
-
         val currentDisplayInfo = getCurrentDisplayInfo()
         if (currentDisplayInfo != null && currentDisplayInfo.info.allowSwipeToDismiss) {
             swipeChipbarAwayGestureHandler.setViewFetcher { currentDisplayInfo.view }
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index 1d50241..f24d526 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.temporarydisplay.chipbar
 
 import android.os.VibrationEffect
+import android.view.HapticFeedbackConstants
 import android.view.View
 import androidx.annotation.AttrRes
 import com.android.internal.logging.InstanceId
@@ -42,6 +43,7 @@
     val text: Text,
     val endItem: ChipbarEndItem?,
     val vibrationEffect: VibrationEffect? = null,
+    val vibrationConstant: Int = HapticFeedbackConstants.NO_HAPTICS,
     val allowSwipeToDismiss: Boolean = false,
     override val windowTitle: String,
     override val wakeReason: String,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
index d55751b..6706873 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.temporarydisplay.TemporaryViewLogger
 import com.android.systemui.temporarydisplay.dagger.ChipbarLog
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt
index 9dbc4b3..80de523 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt
@@ -19,10 +19,12 @@
 import android.content.Context
 import android.view.MotionEvent
 import android.view.View
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.settings.DisplayTracker
 import com.android.systemui.statusbar.gesture.SwipeUpGestureHandler
 import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
 import com.android.systemui.util.boundsOnScreen
+import javax.inject.Inject
 
 /**
  * A class to detect when a user has swiped the chipbar away.
@@ -30,7 +32,10 @@
  * Effectively [SysUISingleton]. But, this shouldn't be created if the gesture isn't enabled. See
  * [TemporaryDisplayModule.provideSwipeChipbarAwayGestureHandler].
  */
-class SwipeChipbarAwayGestureHandler(
+@SysUISingleton
+class SwipeChipbarAwayGestureHandler
+@Inject
+constructor(
     context: Context,
     displayTracker: DisplayTracker,
     logger: SwipeUpGestureLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
index cae1308..2d05573 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
@@ -16,14 +16,9 @@
 
 package com.android.systemui.temporarydisplay.dagger
 
-import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.media.taptotransfer.MediaTttFlags
-import com.android.systemui.settings.DisplayTracker
-import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
-import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
 import dagger.Module
 import dagger.Provides
 
@@ -36,20 +31,5 @@
         fun provideChipbarLogBuffer(factory: LogBufferFactory): LogBuffer {
             return factory.create("ChipbarLog", 40)
         }
-
-        @Provides
-        @SysUISingleton
-        fun provideSwipeChipbarAwayGestureHandler(
-            mediaTttFlags: MediaTttFlags,
-            context: Context,
-            displayTracker: DisplayTracker,
-            logger: SwipeUpGestureLogger,
-        ): SwipeChipbarAwayGestureHandler? {
-            return if (mediaTttFlags.isMediaTttDismissGestureEnabled()) {
-                SwipeChipbarAwayGestureHandler(context, displayTracker, logger)
-            } else {
-                null
-            }
-        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
index ae48208..a983d2f 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
+++ b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
@@ -17,88 +17,91 @@
 package com.android.systemui.theme
 
 import android.util.Pair
-import com.android.systemui.monet.dynamiccolor.DynamicColor
-import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors
+import com.google.ux.material.libmonet.dynamiccolor.DynamicColor
+import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors
 
 class DynamicColors {
     companion object {
-        private val MDC = MaterialDynamicColors()
-        @JvmField
-        val ALL_DYNAMIC_COLORS_MAPPED: List<Pair<String, DynamicColor>> =
-            arrayListOf(
-                Pair.create("primary_container", MDC.primaryContainer()),
-                Pair.create("on_primary_container", MDC.onPrimaryContainer()),
-                Pair.create("primary", MDC.primary()),
-                Pair.create("on_primary", MDC.onPrimary()),
-                Pair.create("secondary_container", MDC.secondaryContainer()),
-                Pair.create("on_secondary_container", MDC.onSecondaryContainer()),
-                Pair.create("secondary", MDC.secondary()),
-                Pair.create("on_secondary", MDC.onSecondary()),
-                Pair.create("tertiary_container", MDC.tertiaryContainer()),
-                Pair.create("on_tertiary_container", MDC.onTertiaryContainer()),
-                Pair.create("tertiary", MDC.tertiary()),
-                Pair.create("on_tertiary", MDC.onTertiary()),
-                Pair.create("background", MDC.background()),
-                Pair.create("on_background", MDC.onBackground()),
-                Pair.create("surface", MDC.surface()),
-                Pair.create("on_surface", MDC.onSurface()),
-                Pair.create("surface_container_low", MDC.surfaceContainerLow()),
-                Pair.create("surface_container_lowest", MDC.surfaceContainerLowest()),
-                Pair.create("surface_container", MDC.surfaceContainer()),
-                Pair.create("surface_container_high", MDC.surfaceContainerHigh()),
-                Pair.create("surface_container_highest", MDC.surfaceContainerHighest()),
-                Pair.create("surface_bright", MDC.surfaceBright()),
-                Pair.create("surface_dim", MDC.surfaceDim()),
-                Pair.create("surface_variant", MDC.surfaceVariant()),
-                Pair.create("on_surface_variant", MDC.onSurfaceVariant()),
-                Pair.create("outline", MDC.outline()),
-                Pair.create("outline_variant", MDC.outlineVariant()),
-                Pair.create("error", MDC.error()),
-                Pair.create("on_error", MDC.onError()),
-                Pair.create("error_container", MDC.errorContainer()),
-                Pair.create("on_error_container", MDC.onErrorContainer()),
-                Pair.create("control_activated", MDC.controlActivated()),
-                Pair.create("control_normal", MDC.controlNormal()),
-                Pair.create("control_highlight", MDC.controlHighlight()),
-                Pair.create("text_primary_inverse", MDC.textPrimaryInverse()),
+        @JvmStatic
+        fun allDynamicColorsMapped(isExtendedFidelity: Boolean): List<Pair<String, DynamicColor>> {
+            val mdc = MaterialDynamicColors(isExtendedFidelity)
+            return arrayListOf(
+                Pair.create("primary_container", mdc.primaryContainer()),
+                Pair.create("on_primary_container", mdc.onPrimaryContainer()),
+                Pair.create("primary", mdc.primary()),
+                Pair.create("on_primary", mdc.onPrimary()),
+                Pair.create("secondary_container", mdc.secondaryContainer()),
+                Pair.create("on_secondary_container", mdc.onSecondaryContainer()),
+                Pair.create("secondary", mdc.secondary()),
+                Pair.create("on_secondary", mdc.onSecondary()),
+                Pair.create("tertiary_container", mdc.tertiaryContainer()),
+                Pair.create("on_tertiary_container", mdc.onTertiaryContainer()),
+                Pair.create("tertiary", mdc.tertiary()),
+                Pair.create("on_tertiary", mdc.onTertiary()),
+                Pair.create("background", mdc.background()),
+                Pair.create("on_background", mdc.onBackground()),
+                Pair.create("surface", mdc.surface()),
+                Pair.create("on_surface", mdc.onSurface()),
+                Pair.create("surface_container_low", mdc.surfaceContainerLow()),
+                Pair.create("surface_container_lowest", mdc.surfaceContainerLowest()),
+                Pair.create("surface_container", mdc.surfaceContainer()),
+                Pair.create("surface_container_high", mdc.surfaceContainerHigh()),
+                Pair.create("surface_container_highest", mdc.surfaceContainerHighest()),
+                Pair.create("surface_bright", mdc.surfaceBright()),
+                Pair.create("surface_dim", mdc.surfaceDim()),
+                Pair.create("surface_variant", mdc.surfaceVariant()),
+                Pair.create("on_surface_variant", mdc.onSurfaceVariant()),
+                Pair.create("outline", mdc.outline()),
+                Pair.create("outline_variant", mdc.outlineVariant()),
+                Pair.create("error", mdc.error()),
+                Pair.create("on_error", mdc.onError()),
+                Pair.create("error_container", mdc.errorContainer()),
+                Pair.create("on_error_container", mdc.onErrorContainer()),
+                Pair.create("control_activated", mdc.controlActivated()),
+                Pair.create("control_normal", mdc.controlNormal()),
+                Pair.create("control_highlight", mdc.controlHighlight()),
+                Pair.create("text_primary_inverse", mdc.textPrimaryInverse()),
                 Pair.create(
                     "text_secondary_and_tertiary_inverse",
-                    MDC.textSecondaryAndTertiaryInverse()
+                    mdc.textSecondaryAndTertiaryInverse()
                 ),
                 Pair.create(
                     "text_primary_inverse_disable_only",
-                    MDC.textPrimaryInverseDisableOnly()
+                    mdc.textPrimaryInverseDisableOnly()
                 ),
                 Pair.create(
                     "text_secondary_and_tertiary_inverse_disabled",
-                    MDC.textSecondaryAndTertiaryInverseDisabled()
+                    mdc.textSecondaryAndTertiaryInverseDisabled()
                 ),
-                Pair.create("text_hint_inverse", MDC.textHintInverse()),
-                Pair.create("palette_key_color_primary", MDC.primaryPaletteKeyColor()),
-                Pair.create("palette_key_color_secondary", MDC.secondaryPaletteKeyColor()),
-                Pair.create("palette_key_color_tertiary", MDC.tertiaryPaletteKeyColor()),
-                Pair.create("palette_key_color_neutral", MDC.neutralPaletteKeyColor()),
+                Pair.create("text_hint_inverse", mdc.textHintInverse()),
+                Pair.create("palette_key_color_primary", mdc.primaryPaletteKeyColor()),
+                Pair.create("palette_key_color_secondary", mdc.secondaryPaletteKeyColor()),
+                Pair.create("palette_key_color_tertiary", mdc.tertiaryPaletteKeyColor()),
+                Pair.create("palette_key_color_neutral", mdc.neutralPaletteKeyColor()),
                 Pair.create(
                     "palette_key_color_neutral_variant",
-                    MDC.neutralVariantPaletteKeyColor()
+                    mdc.neutralVariantPaletteKeyColor()
                 ),
             )
+        }
 
-        @JvmField
-        val FIXED_COLORS_MAPPED: List<Pair<String, DynamicColor>> =
-            arrayListOf(
-                Pair.create("primary_fixed", MDC.primaryFixed()),
-                Pair.create("primary_fixed_dim", MDC.primaryFixedDim()),
-                Pair.create("on_primary_fixed", MDC.onPrimaryFixed()),
-                Pair.create("on_primary_fixed_variant", MDC.onPrimaryFixedVariant()),
-                Pair.create("secondary_fixed", MDC.secondaryFixed()),
-                Pair.create("secondary_fixed_dim", MDC.secondaryFixedDim()),
-                Pair.create("on_secondary_fixed", MDC.onSecondaryFixed()),
-                Pair.create("on_secondary_fixed_variant", MDC.onSecondaryFixedVariant()),
-                Pair.create("tertiary_fixed", MDC.tertiaryFixed()),
-                Pair.create("tertiary_fixed_dim", MDC.tertiaryFixedDim()),
-                Pair.create("on_tertiary_fixed", MDC.onTertiaryFixed()),
-                Pair.create("on_tertiary_fixed_variant", MDC.onTertiaryFixedVariant()),
+        @JvmStatic
+        fun getFixedColorsMapped(isExtendedFidelity: Boolean): List<Pair<String, DynamicColor>> {
+            val mdc = MaterialDynamicColors(isExtendedFidelity)
+            return arrayListOf(
+                Pair.create("primary_fixed", mdc.primaryFixed()),
+                Pair.create("primary_fixed_dim", mdc.primaryFixedDim()),
+                Pair.create("on_primary_fixed", mdc.onPrimaryFixed()),
+                Pair.create("on_primary_fixed_variant", mdc.onPrimaryFixedVariant()),
+                Pair.create("secondary_fixed", mdc.secondaryFixed()),
+                Pair.create("secondary_fixed_dim", mdc.secondaryFixedDim()),
+                Pair.create("on_secondary_fixed", mdc.onSecondaryFixed()),
+                Pair.create("on_secondary_fixed_variant", mdc.onSecondaryFixedVariant()),
+                Pair.create("tertiary_fixed", mdc.tertiaryFixed()),
+                Pair.create("tertiary_fixed_dim", mdc.tertiaryFixedDim()),
+                Pair.create("on_tertiary_fixed", mdc.onTertiaryFixed()),
+                Pair.create("on_tertiary_fixed_variant", mdc.onTertiaryFixedVariant()),
             )
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index b78329c..5a9f5d5 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -74,21 +74,22 @@
 import com.android.systemui.monet.ColorScheme;
 import com.android.systemui.monet.Style;
 import com.android.systemui.monet.TonalPalette;
-import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.scheme.DynamicScheme;
-import com.android.systemui.monet.scheme.SchemeExpressive;
-import com.android.systemui.monet.scheme.SchemeFruitSalad;
-import com.android.systemui.monet.scheme.SchemeMonochrome;
-import com.android.systemui.monet.scheme.SchemeNeutral;
-import com.android.systemui.monet.scheme.SchemeRainbow;
-import com.android.systemui.monet.scheme.SchemeTonalSpot;
-import com.android.systemui.monet.scheme.SchemeVibrant;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.util.settings.SecureSettings;
 
+import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors;
+import com.google.ux.material.libmonet.hct.Hct;
+import com.google.ux.material.libmonet.scheme.DynamicScheme;
+import com.google.ux.material.libmonet.scheme.SchemeExpressive;
+import com.google.ux.material.libmonet.scheme.SchemeFruitSalad;
+import com.google.ux.material.libmonet.scheme.SchemeMonochrome;
+import com.google.ux.material.libmonet.scheme.SchemeNeutral;
+import com.google.ux.material.libmonet.scheme.SchemeRainbow;
+import com.google.ux.material.libmonet.scheme.SchemeTonalSpot;
+import com.google.ux.material.libmonet.scheme.SchemeVibrant;
+
 import org.json.JSONException;
 import org.json.JSONObject;
 
@@ -129,6 +130,7 @@
     private final boolean mIsMonochromaticEnabled;
     private final Context mContext;
     private final boolean mIsMonetEnabled;
+    private final boolean mIsFidelityEnabled;
     private final UserTracker mUserTracker;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final Resources mResources;
@@ -397,6 +399,7 @@
         mContext = context;
         mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME);
         mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
+        mIsFidelityEnabled = featureFlags.isEnabled(Flags.COLOR_FIDELITY);
         mDeviceProvisionedController = deviceProvisionedController;
         mBroadcastDispatcher = broadcastDispatcher;
         mUserManager = userManager;
@@ -631,7 +634,7 @@
     private void assignDynamicPaletteToOverlay(FabricatedOverlay overlay, boolean isDark) {
         String suffix = isDark ? "dark" : "light";
         DynamicScheme scheme = isDark ? mDynamicSchemeDark : mDynamicSchemeLight;
-        DynamicColors.ALL_DYNAMIC_COLORS_MAPPED.forEach(p -> {
+        DynamicColors.allDynamicColorsMapped(mIsFidelityEnabled).forEach(p -> {
             String resourceName = "android:color/system_" + p.first + "_" + suffix;
             int colorValue = p.second.getArgb(scheme);
             overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
@@ -640,7 +643,7 @@
     }
 
     private void assignFixedColorsToOverlay(FabricatedOverlay overlay) {
-        DynamicColors.FIXED_COLORS_MAPPED.forEach(p -> {
+        DynamicColors.getFixedColorsMapped(mIsFidelityEnabled).forEach(p -> {
             String resourceName = "android:color/system_" + p.first;
             int colorValue = p.second.getArgb(mDynamicSchemeLight);
             overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
@@ -659,7 +662,7 @@
             Resources res = userHandle.isSystem()
                     ? mResources : mContext.createContextAsUser(userHandle, 0).getResources();
             Resources.Theme theme = mContext.getTheme();
-            MaterialDynamicColors dynamicColors = new MaterialDynamicColors();
+            MaterialDynamicColors dynamicColors = new MaterialDynamicColors(mIsFidelityEnabled);
             if (!(res.getColor(android.R.color.system_accent1_500, theme)
                     == mColorScheme.getAccent1().getS500()
                     && res.getColor(android.R.color.system_accent2_500, theme)
@@ -818,6 +821,7 @@
         pw.println("mNeutralOverlay=" + mNeutralOverlay);
         pw.println("mDynamicOverlay=" + mDynamicOverlay);
         pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
+        pw.println("mIsFidelityEnabled=" + mIsFidelityEnabled);
         pw.println("mColorScheme=" + mColorScheme);
         pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation);
         pw.println("mAcceptColorEvents=" + mAcceptColorEvents);
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
index dfe748a..324ef4b 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -18,9 +18,9 @@
 
 import com.android.systemui.log.dagger.ToastLog
 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.core.LogLevel
+import com.android.systemui.log.core.LogLevel.DEBUG
+import com.android.systemui.log.core.LogMessage
 import javax.inject.Inject
 
 private const val TAG = "ToastLog"
@@ -58,6 +58,16 @@
         })
     }
 
+    fun logOnSkipToastForInvalidDisplay(packageName: String, token: String, displayId: Int) {
+        log(DEBUG, {
+            str1 = packageName
+            str2 = token
+            int1 = displayId
+        }, {
+            "[$str2] Skip toast for [$str1] scheduled on unavailable display #$int1"
+        })
+    }
+
     private inline fun log(
         logLevel: LogLevel,
         initializer: LogMessage.() -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index ed14c8a..ae8128d 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -32,6 +32,7 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.Log;
+import android.view.Display;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
 import android.widget.ToastPresenter;
@@ -115,8 +116,14 @@
             Context context = mContext.createContextAsUser(userHandle, 0);
 
             DisplayManager mDisplayManager = mContext.getSystemService(DisplayManager.class);
-            Context displayContext = context.createDisplayContext(
-                    mDisplayManager.getDisplay(displayId));
+            Display display = mDisplayManager.getDisplay(displayId);
+            if (display == null) {
+                // Display for which this toast was scheduled for is no longer available.
+                mToastLogger.logOnSkipToastForInvalidDisplay(packageName, token.toString(),
+                        displayId);
+                return;
+            }
+            Context displayContext = context.createDisplayContext(display);
             mToast = mToastFactory.createToast(mContext /* sysuiContext */, text, packageName,
                     userHandle.getIdentifier(), mOrientation);
 
diff --git a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
index 757b4e5..13c1019 100644
--- a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
@@ -25,16 +25,12 @@
 
 import androidx.concurrent.futures.CallbackToFutureAdapter;
 
-import com.android.systemui.dagger.qualifiers.Main;
-
 import com.google.common.util.concurrent.ListenableFuture;
 
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.concurrent.Executor;
 
-import javax.inject.Inject;
-
 /**
  * {@link TouchInsetManager} handles setting the touchable inset regions for a given View. This
  * is useful for passing through touch events for all but select areas.
@@ -153,8 +149,7 @@
      * Default constructor.
      * @param executor An {@link Executor} to marshal all operations on.
      */
-    @Inject
-    public TouchInsetManager(@Main Executor executor) {
+    public TouchInsetManager(Executor executor) {
         mExecutor = executor;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java b/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java
deleted file mode 100644
index b54d156..0000000
--- a/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.tracing;
-
-import static com.android.systemui.tracing.nano.SystemUiTraceFileProto.MAGIC_NUMBER_H;
-import static com.android.systemui.tracing.nano.SystemUiTraceFileProto.MAGIC_NUMBER_L;
-
-import android.content.Context;
-import android.os.SystemClock;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.shared.tracing.FrameProtoTracer;
-import com.android.systemui.shared.tracing.FrameProtoTracer.ProtoTraceParams;
-import com.android.systemui.shared.tracing.ProtoTraceable;
-import com.android.systemui.tracing.nano.SystemUiTraceEntryProto;
-import com.android.systemui.tracing.nano.SystemUiTraceFileProto;
-import com.android.systemui.tracing.nano.SystemUiTraceProto;
-
-import com.google.protobuf.nano.MessageNano;
-
-import java.io.File;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Queue;
-
-import javax.inject.Inject;
-
-/**
- * Controller for coordinating winscope proto tracing.
- */
-@SysUISingleton
-public class ProtoTracer implements
-        Dumpable,
-        ProtoTraceParams<
-                MessageNano,
-                SystemUiTraceFileProto,
-                SystemUiTraceEntryProto,
-                SystemUiTraceProto> {
-
-    private static final String TAG = "ProtoTracer";
-    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
-
-    private final Context mContext;
-    private final FrameProtoTracer<MessageNano, SystemUiTraceFileProto, SystemUiTraceEntryProto,
-            SystemUiTraceProto> mProtoTracer;
-
-    @Inject
-    public ProtoTracer(Context context, DumpManager dumpManager) {
-        mContext = context;
-        mProtoTracer = new FrameProtoTracer<>(this);
-        dumpManager.registerDumpable(this);
-    }
-
-    @Override
-    public File getTraceFile() {
-        return new File(mContext.getFilesDir(), "sysui_trace.pb");
-    }
-
-    @Override
-    public SystemUiTraceFileProto getEncapsulatingTraceProto() {
-        return new SystemUiTraceFileProto();
-    }
-
-    @Override
-    public SystemUiTraceEntryProto updateBufferProto(SystemUiTraceEntryProto reuseObj,
-            ArrayList<ProtoTraceable<SystemUiTraceProto>> traceables) {
-        SystemUiTraceEntryProto proto = reuseObj != null
-                ? reuseObj
-                : new SystemUiTraceEntryProto();
-        proto.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
-        proto.systemUi = proto.systemUi != null ? proto.systemUi : new SystemUiTraceProto();
-        for (ProtoTraceable t : traceables) {
-            t.writeToProto(proto.systemUi);
-        }
-        return proto;
-    }
-
-    @Override
-    public byte[] serializeEncapsulatingProto(SystemUiTraceFileProto encapsulatingProto,
-            Queue<SystemUiTraceEntryProto> buffer) {
-        encapsulatingProto.magicNumber = MAGIC_NUMBER_VALUE;
-        encapsulatingProto.entry = buffer.toArray(new SystemUiTraceEntryProto[0]);
-        return MessageNano.toByteArray(encapsulatingProto);
-    }
-
-    @Override
-    public byte[] getProtoBytes(MessageNano proto) {
-        return MessageNano.toByteArray(proto);
-    }
-
-    @Override
-    public int getProtoSize(MessageNano proto) {
-        return proto.getCachedSize();
-    }
-
-    public void start() {
-        mProtoTracer.start();
-    }
-
-    public void stop() {
-        mProtoTracer.stop();
-    }
-
-    public boolean isEnabled() {
-        return mProtoTracer.isEnabled();
-    }
-
-    public void add(ProtoTraceable<SystemUiTraceProto> traceable) {
-        mProtoTracer.add(traceable);
-    }
-
-    public void remove(ProtoTraceable<SystemUiTraceProto> traceable) {
-        mProtoTracer.remove(traceable);
-    }
-
-    public void scheduleFrameUpdate() {
-        mProtoTracer.scheduleFrameUpdate();
-    }
-
-    public void update() {
-        mProtoTracer.update();
-    }
-
-    @Override
-    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println("ProtoTracer:");
-        pw.print("    "); pw.println("enabled: " + mProtoTracer.isEnabled());
-        pw.print("    "); pw.println("usagePct: " + mProtoTracer.getBufferUsagePct());
-        pw.print("    "); pw.println("file: " + getTraceFile());
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto
deleted file mode 100644
index d940a6b..0000000
--- a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-syntax = "proto2";
-
-package com.android.systemui.tracing;
-
-option java_multiple_files = true;
-
-message SystemUiTraceProto {
-
-    optional EdgeBackGestureHandlerProto edge_back_gesture_handler = 1;
-}
-
-message EdgeBackGestureHandlerProto {
-
-    optional bool allow_gesture = 1;
-}
-
-/* represents a file full of system ui trace entries.
-   Encoded, it should start with 0x9 0x53 0x59 0x53 0x55 0x49 0x54 0x52 0x43 (.SYSUITRC), such
-   that they can be easily identified. */
-message SystemUiTraceFileProto {
-
-    /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
-       (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
-        constants into .proto files. */
-    enum MagicNumber {
-        INVALID = 0;
-        MAGIC_NUMBER_L = 0x55535953;  /* SYSU (little-endian ASCII) */
-        MAGIC_NUMBER_H = 0x43525449;  /* ITRC (little-endian ASCII) */
-    }
-
-    optional fixed64 magic_number = 1;  /* Must be the first field, set to value in MagicNumber */
-    repeated SystemUiTraceEntryProto entry = 2;
-}
-
-/* one system ui trace entry. */
-message SystemUiTraceEntryProto {
-    /* required: elapsed realtime in nanos since boot of when this entry was logged */
-    optional fixed64 elapsed_realtime_nanos = 1;
-
-    optional SystemUiTraceProto system_ui = 3;
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 5d09e06..a501e87 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -20,6 +20,11 @@
 
 import com.android.systemui.Dependency;
 
+/**
+ * @deprecated Don't use this class to listen to Secure Settings. Use {@code SecureSettings} instead
+ * or {@code SettingsObserver} to be able to specify the handler.
+ */
+@Deprecated
 public abstract class TunerService {
 
     public static final String ACTION_CLEAR = "com.android.systemui.action.CLEAR_TUNER";
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 8cfe2ea..ccc0a79 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -56,7 +56,11 @@
 
 
 /**
+ * @deprecated Don't use this class to listen to Secure Settings. Use {@code SecureSettings} instead
+ * or {@code SettingsObserver} to be able to specify the handler.
+ * This class will interact with SecureSettings using the main looper.
  */
+@Deprecated
 @SysUISingleton
 public class TunerServiceImpl extends TunerService {
 
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
index 5e489b0..82589d3 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponent.java
@@ -27,6 +27,7 @@
 import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+import com.android.systemui.wallpapers.dagger.NoopWallpaperModule;
 
 import dagger.Subcomponent;
 
@@ -39,6 +40,7 @@
         DefaultComponentBinder.class,
         DependencyProvider.class,
         KeyguardModule.class,
+        NoopWallpaperModule.class,
         NotificationRowModule.class,
         NotificationsModule.class,
         RecentsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index d9a8e0c..95e1e43 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -44,8 +44,7 @@
 import com.android.systemui.screenshot.ReferenceScreenshotModule;
 import com.android.systemui.settings.dagger.MultiUserUtilsModule;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeControllerImpl;
+import com.android.systemui.shade.ShadeEmptyImplModule;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationListener;
@@ -94,6 +93,7 @@
                 PowerModule.class,
                 QSModule.class,
                 ReferenceScreenshotModule.class,
+                ShadeEmptyImplModule.class,
                 StatusBarEventsModule.class,
                 VolumeModule.class,
         }
@@ -137,9 +137,6 @@
     @Binds
     abstract DockManager bindDockManager(DockManagerImpl dockManager);
 
-    @Binds
-    abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
-
     @SysUISingleton
     @Provides
     @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index eed7950..098d51e 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.shade.ShadeFoldAnimator
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.ScreenOffAnimation
@@ -62,7 +63,7 @@
     private val keyguardInteractor: Lazy<KeyguardInteractor>,
 ) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer {
 
-    private lateinit var centralSurfaces: CentralSurfaces
+    private lateinit var shadeViewController: ShadeViewController
 
     private var isFolded = false
     private var isFoldHandled = true
@@ -87,14 +88,18 @@
         )
     }
 
-    override fun initialize(centralSurfaces: CentralSurfaces, lightRevealScrim: LightRevealScrim) {
-        this.centralSurfaces = centralSurfaces
+    override fun initialize(
+            centralSurfaces: CentralSurfaces,
+            shadeViewController: ShadeViewController,
+            lightRevealScrim: LightRevealScrim,
+    ) {
+        this.shadeViewController = shadeViewController
 
         deviceStateManager.registerCallback(mainExecutor, FoldListener())
         wakefulnessLifecycle.addObserver(this)
 
         // TODO(b/254878364): remove this call to NPVC.getView()
-        getShadeFoldAnimator().view.repeatWhenAttached {
+        getShadeFoldAnimator().view?.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) { listenForDozing(this) }
         }
     }
@@ -128,7 +133,7 @@
     }
 
     private fun getShadeFoldAnimator(): ShadeFoldAnimator =
-        centralSurfaces.shadeViewController.shadeFoldAnimator
+        shadeViewController.shadeFoldAnimator
 
     private fun setAnimationState(playing: Boolean) {
         shouldPlayAnimation = playing
@@ -161,10 +166,9 @@
             // but we should wait for the initial animation preparations to be drawn
             // (setting initial alpha/translation)
             // TODO(b/254878364): remove this call to NPVC.getView()
-            OneShotPreDrawListener.add(
-                getShadeFoldAnimator().view,
-                onReady
-            )
+            getShadeFoldAnimator().view?.let {
+                OneShotPreDrawListener.add(it, onReady)
+            }
         } else {
             // No animation, call ready callback immediately
             onReady.run()
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
index 1e73cb3..1e65566 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
@@ -79,15 +79,15 @@
 
     private val hapticsScale: Float
         get() {
-            val intensityString = SystemProperties.get("persist.unfold.haptics_scale", "0.1")
-            return intensityString.toFloatOrNull() ?: 0.1f
+            val intensityString = SystemProperties.get("persist.unfold.haptics_scale", "0.5")
+            return intensityString.toFloatOrNull() ?: 0.5f
         }
 
     private val hapticsScaleTick: Float
         get() {
             val intensityString =
-                SystemProperties.get("persist.unfold.haptics_scale_end_tick", "0.6")
-            return intensityString.toFloatOrNull() ?: 0.6f
+                SystemProperties.get("persist.unfold.haptics_scale_end_tick", "1.0")
+            return intensityString.toFloatOrNull() ?: 1.0f
         }
 
     private val primitivesCount: Int
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
index 72786ef..5ad9630 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
@@ -60,7 +60,7 @@
 
     override fun getWidth(): Int {
         val displayMetrics = context.resources.displayMetrics.apply {
-            context.display.getRealMetrics(this)
+            checkNotNull(context.display).getRealMetrics(this)
         }
         return displayMetrics.widthPixels
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
index 088cd93..ee84580 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
@@ -52,22 +52,22 @@
     override fun show() {
         // need to call show() first in order to construct the listView
         super.show()
-        val listView = getListView()
+        listView?.apply {
+            isVerticalScrollBarEnabled = false
+            isHorizontalScrollBarEnabled = false
 
-        listView.setVerticalScrollBarEnabled(false)
-        listView.setHorizontalScrollBarEnabled(false)
+            // Creates a transparent spacer between items
+            val shape = ShapeDrawable()
+            shape.alpha = 0
+            divider = shape
+            dividerHeight = res.getDimensionPixelSize(
+                R.dimen.bouncer_user_switcher_popup_divider_height)
 
-        // Creates a transparent spacer between items
-        val shape = ShapeDrawable()
-        shape.setAlpha(0)
-        listView.setDivider(shape)
-        listView.setDividerHeight(res.getDimensionPixelSize(
-            R.dimen.bouncer_user_switcher_popup_divider_height))
-
-        val height = res.getDimensionPixelSize(R.dimen.bouncer_user_switcher_popup_header_height)
-        listView.addHeaderView(createSpacer(height), null, false)
-        listView.addFooterView(createSpacer(height), null, false)
-        setWidth(findMaxWidth(listView))
+            val height = res.getDimensionPixelSize(R.dimen.bouncer_user_switcher_popup_header_height)
+            addHeaderView(createSpacer(height), null, false)
+            addFooterView(createSpacer(height), null, false)
+            setWidth(findMaxWidth(this))
+        }
 
         super.show()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt
new file mode 100644
index 0000000..cefd466
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.user.data.model
+
+import android.content.pm.UserInfo
+
+/** A model for the currently selected user. */
+data class SelectedUserModel(
+    /** Information about the user. */
+    val userInfo: UserInfo,
+    /** The current status of the selection. */
+    val selectionStatus: SelectionStatus,
+)
+
+/** The current status of the selection. */
+enum class SelectionStatus {
+    /** This user has started being selected but the selection hasn't completed. */
+    SELECTION_IN_PROGRESS,
+    /** The selection of this user has completed. */
+    SELECTION_COMPLETE,
+}
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 3de75ca..954765c 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
@@ -33,6 +33,8 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -69,6 +71,9 @@
     /** List of all users on the device. */
     val userInfos: Flow<List<UserInfo>>
 
+    /** Information about the currently-selected user, including [UserInfo] and other details. */
+    val selectedUser: StateFlow<SelectedUserModel>
+
     /** [UserInfo] of the currently-selected user. */
     val selectedUserInfo: Flow<UserInfo>
 
@@ -146,9 +151,6 @@
     private val _userInfos = MutableStateFlow<List<UserInfo>?>(null)
     override val userInfos: Flow<List<UserInfo>> = _userInfos.filterNotNull()
 
-    private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
-    override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
-
     override var mainUserId: Int = UserHandle.USER_NULL
         private set
     override var lastSelectedNonGuestUserId: Int = UserHandle.USER_NULL
@@ -174,12 +176,57 @@
     override var isRefreshUsersPaused: Boolean = false
 
     init {
-        observeSelectedUser()
         if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) {
             observeUserSwitching()
         }
     }
 
+    override val selectedUser: StateFlow<SelectedUserModel> = run {
+        // Some callbacks don't modify the selection status, so maintain the current value.
+        var currentSelectionStatus = SelectionStatus.SELECTION_COMPLETE
+        conflatedCallbackFlow {
+                fun send(selectionStatus: SelectionStatus) {
+                    currentSelectionStatus = selectionStatus
+                    trySendWithFailureLogging(
+                        SelectedUserModel(tracker.userInfo, selectionStatus),
+                        TAG,
+                    )
+                }
+
+                val callback =
+                    object : UserTracker.Callback {
+                        override fun onUserChanging(newUser: Int, userContext: Context) {
+                            send(SelectionStatus.SELECTION_IN_PROGRESS)
+                        }
+
+                        override fun onUserChanged(newUser: Int, userContext: Context) {
+                            send(SelectionStatus.SELECTION_COMPLETE)
+                        }
+
+                        override fun onProfilesChanged(profiles: List<UserInfo>) {
+                            send(currentSelectionStatus)
+                        }
+                    }
+
+                tracker.addCallback(callback, mainDispatcher.asExecutor())
+                send(currentSelectionStatus)
+
+                awaitClose { tracker.removeCallback(callback) }
+            }
+            .onEach {
+                if (!it.userInfo.isGuest) {
+                    lastSelectedNonGuestUserId = it.userInfo.id
+                }
+            }
+            .stateIn(
+                applicationScope,
+                SharingStarted.Eagerly,
+                initialValue = SelectedUserModel(tracker.userInfo, currentSelectionStatus)
+            )
+    }
+
+    override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
+
     override fun refreshUsers() {
         applicationScope.launch {
             val result = withContext(backgroundDispatcher) { manager.aliveUsers }
@@ -201,7 +248,7 @@
     }
 
     override fun getSelectedUserInfo(): UserInfo {
-        return checkNotNull(_selectedUserInfo.value)
+        return selectedUser.value.userInfo
     }
 
     override fun isSimpleUserSwitcher(): Boolean {
@@ -234,38 +281,6 @@
             .launchIn(applicationScope)
     }
 
-    private fun observeSelectedUser() {
-        conflatedCallbackFlow {
-                fun send() {
-                    trySendWithFailureLogging(tracker.userInfo, TAG)
-                }
-
-                val callback =
-                    object : UserTracker.Callback {
-                        override fun onUserChanging(newUser: Int, userContext: Context) {
-                            send()
-                        }
-
-                        override fun onProfilesChanged(profiles: List<UserInfo>) {
-                            send()
-                        }
-                    }
-
-                tracker.addCallback(callback, mainDispatcher.asExecutor())
-                send()
-
-                awaitClose { tracker.removeCallback(callback) }
-            }
-            .onEach {
-                if (!it.isGuest) {
-                    lastSelectedNonGuestUserId = it.id
-                }
-
-                _selectedUserInfo.value = it
-            }
-            .launchIn(applicationScope)
-    }
-
     private suspend fun getSettings(): UserSwitcherSettingsModel {
         return withContext(backgroundDispatcher) {
             val isSimpleUserSwitcher =
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
index f026f0f..bfed0c4 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -227,7 +227,8 @@
                     )
                 }
                 try {
-                    WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null)
+                    checkNotNull(WindowManagerGlobal.getWindowManagerService())
+                        .lockNow(/* options= */ null)
                 } catch (e: RemoteException) {
                     Log.e(
                         TAG,
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 a487f53..4d506f0 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
@@ -27,6 +27,7 @@
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
+import android.os.Process
 import android.os.RemoteException
 import android.os.UserHandle
 import android.os.UserManager
@@ -334,6 +335,7 @@
                 onBroadcastReceived(intent, previousSelectedUser)
             }
             .launchIn(applicationScope)
+        restartSecondaryService(repository.getSelectedUserInfo().id)
         keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
     }
 
@@ -530,6 +532,12 @@
         }
     }
 
+    /** Returns the ID of the currently-selected user. */
+    @UserIdInt
+    fun getSelectedUserId(): Int {
+        return repository.getSelectedUserInfo().id
+    }
+
     private fun showDialog(request: ShowDialogRequestModel) {
         _dialogShowRequests.value = request
     }
@@ -646,7 +654,7 @@
 
         // Connect to the new secondary user's service (purely to ensure that a persistent
         // SystemUI application is created for that user)
-        if (userId != UserHandle.USER_SYSTEM) {
+        if (userId != Process.myUserHandle().identifier) {
             applicationContext.startServiceAsUser(
                 intent,
                 UserHandle.of(userId),
@@ -772,17 +780,16 @@
         }
 
         // TODO(b/246631653): cache the bitmaps to avoid the background work to fetch them.
-        val userIcon = withContext(backgroundDispatcher) {
-            manager.getUserIcon(userId)
-                ?.let { bitmap ->
+        val userIcon =
+            withContext(backgroundDispatcher) {
+                manager.getUserIcon(userId)?.let { bitmap ->
                     val iconSize =
-                        applicationContext
-                            .resources
-                            .getDimensionPixelSize(R.dimen.bouncer_user_switcher_icon_size)
+                        applicationContext.resources.getDimensionPixelSize(
+                            R.dimen.bouncer_user_switcher_icon_size
+                        )
                     Icon.scaleDownIfNecessary(bitmap, iconSize, iconSize)
                 }
-        }
-
+            }
 
         if (userIcon != null) {
             return BitmapDrawable(userIcon)
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
index e74232d..7f16e47 100644
--- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
@@ -67,7 +67,7 @@
         val resourceId: Int? = getGuestUserRecordNameResourceId(record)
         return when {
             resourceId != null -> context.getString(resourceId)
-            record.info != null -> record.info.name
+            record.info != null -> checkNotNull(record.info.name)
             else ->
                 context.getString(
                     getUserSwitcherActionTextResourceId(
diff --git a/packages/SystemUI/src/com/android/systemui/util/IListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/IListenerSet.kt
new file mode 100644
index 0000000..b0230b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/IListenerSet.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+/**
+ * A collection of listeners, observers, callbacks, etc.
+ *
+ * This container is optimized for infrequent mutation and frequent iteration, with thread safety
+ * and reentrant-safety guarantees as well. Specifically, to ensure that
+ * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes made to
+ * the set after the iterator is constructed.
+ */
+interface IListenerSet<E : Any> : Set<E> {
+    /**
+     * A thread-safe, reentrant-safe method to add a listener. Does nothing if the listener is
+     * already in the set.
+     */
+    fun addIfAbsent(element: E): Boolean
+
+    /** A thread-safe, reentrant-safe method to remove a listener. */
+    fun remove(element: E): Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
index a47e614..f8e0b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
@@ -29,20 +29,12 @@
 class ListenerSet<E : Any>
 /** Private constructor takes the internal list so that we can use auto-delegation */
 private constructor(private val listeners: CopyOnWriteArrayList<E>) :
-    Collection<E> by listeners, Set<E> {
+    Collection<E> by listeners, IListenerSet<E> {
 
     /** Create a new instance */
     constructor() : this(CopyOnWriteArrayList())
 
-    /**
-     * A thread-safe, reentrant-safe method to add a listener. Does nothing if the listener is
-     * already in the set.
-     */
-    fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(element)
+    override fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(element)
 
-    /** A thread-safe, reentrant-safe method to remove a listener. */
-    fun remove(element: E): Boolean = listeners.remove(element)
+    override fun remove(element: E): Boolean = listeners.remove(element)
 }
-
-/** Extension to match Collection which is implemented to only be (easily) accessible in kotlin */
-fun <T : Any> ListenerSet<T>.isNotEmpty(): Boolean = !isEmpty()
diff --git a/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt
new file mode 100644
index 0000000..c90b57e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import java.util.concurrent.CopyOnWriteArrayList
+import java.util.function.Consumer
+
+/**
+ * A collection of listeners, observers, callbacks, etc.
+ *
+ * This container is optimized for infrequent mutation and frequent iteration, with thread safety
+ * and reentrant-safety guarantees as well. Specifically, to ensure that
+ * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes made to
+ * the set after the iterator is constructed.
+ *
+ * This class provides all the abilities of [ListenerSet], except that each listener has a name
+ * calculated at runtime which can be used for time-efficient tracing of listener invocations.
+ */
+class NamedListenerSet<E : Any>(
+    private val getName: (E) -> String = { it.javaClass.name },
+) : IListenerSet<E> {
+    private val listeners = CopyOnWriteArrayList<NamedListener>()
+
+    override val size: Int
+        get() = listeners.size
+
+    override fun isEmpty() = listeners.isEmpty()
+
+    override fun iterator(): Iterator<E> = iterator {
+        listeners.iterator().forEach { yield(it.listener) }
+    }
+
+    override fun containsAll(elements: Collection<E>) =
+        listeners.count { it.listener in elements } == elements.size
+
+    override fun contains(element: E) = listeners.firstOrNull { it.listener == element } != null
+
+    override fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(NamedListener(element))
+
+    override fun remove(element: E): Boolean = listeners.removeIf { it.listener == element }
+
+    /** A wrapper for the listener with an associated name. */
+    inner class NamedListener(val listener: E) {
+        val name: String = getName(listener)
+
+        override fun hashCode(): Int {
+            return listener.hashCode()
+        }
+
+        override fun equals(other: Any?): Boolean =
+            when {
+                other === null -> false
+                other === this -> true
+                other !is NamedListenerSet<*>.NamedListener -> false
+                listener == other.listener -> true
+                else -> false
+            }
+    }
+
+    /** Iterate the listeners in the set, providing the name for each one as well. */
+    inline fun forEachNamed(block: (String, E) -> Unit) =
+        namedIterator().forEach { element -> block(element.name, element.listener) }
+
+    /**
+     * Iterate the listeners in the set, wrapping each call to the block with [traceSection] using
+     * the listener name.
+     */
+    inline fun forEachTraced(block: (E) -> Unit) = forEachNamed { name, listener ->
+        traceSection(name) { block(listener) }
+    }
+
+    /**
+     * Iterate the listeners in the set, wrapping each call to the block with [traceSection] using
+     * the listener name.
+     */
+    fun forEachTraced(consumer: Consumer<E>) = forEachNamed { name, listener ->
+        traceSection(name) { consumer.accept(listener) }
+    }
+
+    /** Iterate over the [NamedListener]s currently in the set. */
+    fun namedIterator(): Iterator<NamedListener> = listeners.iterator()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index c2727fc..e0daa070 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -37,6 +37,10 @@
     /**
      * Allows lambda iteration over a list. It is done in reverse order so it is safe
      * to add or remove items during the iteration.  Skips over null items.
+     *
+     * @deprecated According to b/286841705, this is *not* safe: If an item is removed from the
+     *   list, then list.get(i) could throw an IndexOutOfBoundsException. This method should not be
+     *   used; try using `synchronized` or making a copy of the list instead.
      */
     public static <T> void safeForeach(List<T> list, Consumer<T> c) {
         for (int i = list.size() - 1; i >= 0; i--) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
index db2aca8..65a0218 100644
--- a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
@@ -16,32 +16,34 @@
 
 package com.android.systemui.util
 
-import android.app.WallpaperInfo
 import android.app.WallpaperManager
 import android.util.Log
 import android.view.View
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
 import javax.inject.Inject
 import kotlin.math.max
 
 private const val TAG = "WallpaperController"
 
+/**
+ * Controller for wallpaper-related logic.
+ *
+ * Note: New logic should be added to [WallpaperRepository], not this class.
+ */
 @SysUISingleton
-class WallpaperController @Inject constructor(private val wallpaperManager: WallpaperManager) {
+class WallpaperController @Inject constructor(
+    private val wallpaperManager: WallpaperManager,
+    private val wallpaperRepository: WallpaperRepository,
+) {
 
     var rootView: View? = null
 
     private var notificationShadeZoomOut: Float = 0f
     private var unfoldTransitionZoomOut: Float = 0f
 
-    private var wallpaperInfo: WallpaperInfo? = null
-
-    fun onWallpaperInfoUpdated(wallpaperInfo: WallpaperInfo?) {
-        this.wallpaperInfo = wallpaperInfo
-    }
-
     private val shouldUseDefaultUnfoldTransition: Boolean
-        get() = wallpaperInfo?.shouldUseDefaultUnfoldTransition()
+        get() = wallpaperRepository.wallpaperInfo.value?.shouldUseDefaultUnfoldTransition()
             ?: true
 
     fun setNotificationShadeZoom(zoomOut: Float) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
index 56c5d3b..7866d76 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
@@ -223,11 +223,11 @@
         }
     }
 
-    override fun dispatchDraw(canvas: Canvas?) {
-        canvas?.save()
-        canvas?.clipRect(boundsRect)
+    override fun dispatchDraw(canvas: Canvas) {
+        canvas.save()
+        canvas.clipRect(boundsRect)
         super.dispatchDraw(canvas)
-        canvas?.restore()
+        canvas.restore()
     }
 
     private fun updateBounds() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index d6b3b22..0d0a646 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -19,12 +19,44 @@
 import android.view.View
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.lifecycle.repeatWhenAttached
 import java.util.function.Consumer
+import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+/** A class allowing Java classes to collect on Kotlin flows. */
+@SysUISingleton
+class JavaAdapter
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+) {
+    /**
+     * Collect information for the given [flow], calling [consumer] for each emitted event.
+     *
+     * Important: This will immediately start collection and *never* stop it. This should only be
+     * used by classes that *need* to always be collecting a value and processing it. Whenever
+     * possible, please use [collectFlow] instead; that method will stop the collection when a view
+     * has disappeared, which will ensure that we don't perform unnecessary work.
+     *
+     * Do *not* call this method in a class's constructor. Instead, call it in
+     * [com.android.systemui.CoreStartable.start] or similar method.
+     */
+    fun <T> alwaysCollectFlow(
+        flow: Flow<T>,
+        consumer: Consumer<T>,
+    ): Job {
+        return scope.launch { flow.collect { consumer.accept(it) } }
+    }
+}
 
 /**
  * Collect information for the given [flow], calling [consumer] for each emitted event. Defaults to
@@ -43,3 +75,7 @@
         repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } }
     }
 }
+
+fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
+    return combine(flow1, flow2, bifunction)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
index 891ee0c..e32ed5f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt
@@ -16,12 +16,19 @@
 
 package com.android.systemui.util.kotlin
 
+import android.annotation.UserHandleAware
 import android.annotation.WorkerThread
 import android.content.pm.ComponentInfo
 import android.content.pm.PackageManager
 import com.android.systemui.util.Assert
 
+/**
+ * Determines whether a component is actually enabled (not just its default value).
+ *
+ * @throws IllegalArgumentException if the component is not found
+ */
 @WorkerThread
+@UserHandleAware
 fun PackageManager.isComponentActuallyEnabled(componentInfo: ComponentInfo): Boolean {
     Assert.isNotMainThread()
     return when (getComponentEnabledSetting(componentInfo.componentName)) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt
index 0926800..d69b10f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt
@@ -18,7 +18,7 @@
 
 import android.os.PowerManager
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import javax.inject.Inject
 
 class WakeLockLogger @Inject constructor(@WakeLockLog private val buffer: LogBuffer) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index d39a53d..9cc3cdb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -44,6 +44,7 @@
 import android.media.session.MediaSession.Token;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
@@ -57,6 +58,7 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.CaptioningManager;
 
+import androidx.annotation.NonNull;
 import androidx.lifecycle.Observer;
 
 import com.android.internal.annotations.GuardedBy;
@@ -81,6 +83,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
 
 import javax.inject.Inject;
 
@@ -131,7 +134,7 @@
     private final Receiver mReceiver = new Receiver();
     private final RingerModeObservers mRingerModeObservers;
     private final MediaSessions mMediaSessions;
-    private final CaptioningManager mCaptioningManager;
+    private final AtomicReference<CaptioningManager> mCaptioningManager = new AtomicReference<>();
     private final KeyguardManager mKeyguardManager;
     private final ActivityManager mActivityManager;
     private final UserTracker mUserTracker;
@@ -155,16 +158,16 @@
 
     private final WakefulnessLifecycle.Observer mWakefullnessLifecycleObserver =
             new WakefulnessLifecycle.Observer() {
-        @Override
-        public void onStartedWakingUp() {
-            mDeviceInteractive = true;
-        }
+                @Override
+                public void onStartedWakingUp() {
+                    mDeviceInteractive = true;
+                }
 
-        @Override
-        public void onFinishedGoingToSleep() {
-            mDeviceInteractive = false;
-        }
-    };
+                @Override
+                public void onFinishedGoingToSleep() {
+                    mDeviceInteractive = false;
+                }
+            };
 
     @Inject
     public VolumeDialogControllerImpl(
@@ -179,7 +182,6 @@
             AccessibilityManager accessibilityManager,
             PackageManager packageManager,
             WakefulnessLifecycle wakefulnessLifecycle,
-            CaptioningManager captioningManager,
             KeyguardManager keyguardManager,
             ActivityManager activityManager,
             UserTracker userTracker,
@@ -209,17 +211,19 @@
         mVibrator = vibrator;
         mHasVibrator = mVibrator.hasVibrator();
         mAudioService = iAudioService;
-        mCaptioningManager = captioningManager;
         mKeyguardManager = keyguardManager;
         mActivityManager = activityManager;
         mUserTracker = userTracker;
+        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mWorker));
+        createCaptioningManagerServiceByUserContext(mUserTracker.getUserContext());
+
         dumpManager.registerDumpable("VolumeDialogControllerImpl", this);
 
         boolean accessibilityVolumeStreamActive = accessibilityManager
                 .isAccessibilityVolumeStreamActive();
         mVolumeController.setA11yMode(accessibilityVolumeStreamActive ?
-                    VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
-                        VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
+                VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
+                VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
 
         mWakefulnessLifecycle.addObserver(mWakefullnessLifecycleObserver);
     }
@@ -316,12 +320,31 @@
         mWorker.sendEmptyMessage(W.GET_STATE);
     }
 
-    public boolean areCaptionsEnabled() {
-        return mCaptioningManager.isSystemAudioCaptioningEnabled();
+    /**
+     * We met issues about the wrong state of System Caption in multi-user mode.
+     * It happened in the usage of CaptioningManager Service from SysUI process
+     * that is a global system process of User 0.
+     * Therefore, we have to add callback on UserTracker that allows us to get the Context of
+     * active User and then get the corresponding CaptioningManager Service for further usages.
+     */
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    createCaptioningManagerServiceByUserContext(userContext);
+                }
+            };
+
+    private void createCaptioningManagerServiceByUserContext(@NonNull Context userContext) {
+        mCaptioningManager.set(userContext.getSystemService(CaptioningManager.class));
     }
 
-    public void setCaptionsEnabled(boolean isEnabled) {
-        mCaptioningManager.setSystemAudioCaptioningEnabled(isEnabled);
+    public void getCaptionsEnabledState(boolean checkForSwitchState) {
+        mWorker.obtainMessage(W.GET_CAPTIONS_ENABLED_STATE, checkForSwitchState).sendToTarget();
+    }
+
+    public void setCaptionsEnabledState(boolean enabled) {
+        mWorker.obtainMessage(W.SET_CAPTIONS_ENABLED_STATE, enabled).sendToTarget();
     }
 
     public void getCaptionsComponentState(boolean fromTooltip) {
@@ -362,8 +385,8 @@
     }
 
     public void setEnableDialogs(boolean volumeUi, boolean safetyWarning) {
-      mShowVolumeDialog = volumeUi;
-      mShowSafetyWarning = safetyWarning;
+        mShowVolumeDialog = volumeUi;
+        mShowSafetyWarning = safetyWarning;
     }
 
     @Override
@@ -414,12 +437,38 @@
     }
 
     private void onShowCsdWarningW(@AudioManager.CsdWarning int csdWarning, int durationMs) {
-            mCallbacks.onShowCsdWarning(csdWarning, durationMs);
+        mCallbacks.onShowCsdWarning(csdWarning, durationMs);
     }
 
     private void onGetCaptionsComponentStateW(boolean fromTooltip) {
-        mCallbacks.onCaptionComponentStateChanged(
-                mCaptioningManager.isSystemAudioCaptioningUiEnabled(), fromTooltip);
+        CaptioningManager captioningManager = mCaptioningManager.get();
+        if (null != captioningManager) {
+            mCallbacks.onCaptionComponentStateChanged(
+                    captioningManager.isSystemAudioCaptioningUiEnabled(), fromTooltip);
+        } else {
+            Log.e(TAG, "onGetCaptionsComponentStateW(), null captioningManager");
+        }
+    }
+
+    private void onGetCaptionsEnabledStateW(boolean checkForSwitchState) {
+        CaptioningManager captioningManager = mCaptioningManager.get();
+        if (null != captioningManager) {
+            mCallbacks.onCaptionEnabledStateChanged(
+                    captioningManager.isSystemAudioCaptioningEnabled(), checkForSwitchState);
+        } else {
+            Log.e(TAG, "onGetCaptionsEnabledStateW(), null captioningManager");
+        }
+    }
+
+    private void onSetCaptionsEnabledStateW(boolean enabled) {
+        CaptioningManager captioningManager = mCaptioningManager.get();
+        if (null != captioningManager) {
+            captioningManager.setSystemAudioCaptioningEnabled(enabled);
+            mCallbacks.onCaptionEnabledStateChanged(
+                    captioningManager.isSystemAudioCaptioningEnabled(), false);
+        } else {
+            Log.e(TAG, "onGetCaptionsEnabledStateW(), null captioningManager");
+        }
     }
 
     private void onAccessibilityModeChanged(Boolean showA11yStream) {
@@ -719,7 +768,7 @@
          * This method will never be called if the CSD (Computed Sound Dose) feature is
          * not enabled. See com.android.android.server.audio.SoundDoseHelper for the state of
          * the feature.
-         * @param warning the type of warning to display, values are one of
+         * @param csdWarning the type of warning to display, values are one of
          *        {@link android.media.AudioManager#CSD_WARNING_DOSE_REACHED_1X},
          *        {@link android.media.AudioManager#CSD_WARNING_DOSE_REPEATED_5X},
          *        {@link android.media.AudioManager#CSD_WARNING_MOMENTARY_EXPOSURE},
@@ -798,6 +847,8 @@
         private static final int ACCESSIBILITY_MODE_CHANGED = 15;
         private static final int GET_CAPTIONS_COMPONENT_STATE = 16;
         private static final int SHOW_CSD_WARNING = 17;
+        private static final int GET_CAPTIONS_ENABLED_STATE = 18;
+        private static final int SET_CAPTIONS_ENABLED_STATE = 19;
 
         W(Looper looper) {
             super(looper);
@@ -825,6 +876,10 @@
                 case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj);
                     break;
                 case SHOW_CSD_WARNING: onShowCsdWarningW(msg.arg1, msg.arg2); break;
+                case GET_CAPTIONS_ENABLED_STATE:
+                    onGetCaptionsEnabledStateW((Boolean) msg.obj); break;
+                case SET_CAPTIONS_ENABLED_STATE:
+                    onSetCaptionsEnabledStateW((Boolean) msg.obj); break;
             }
         }
     }
@@ -993,6 +1048,17 @@
                                 componentEnabled, fromTooltip));
             }
         }
+
+        @Override
+        public void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkBeforeSwitch) {
+            boolean captionsEnabled = isEnabled != null && isEnabled;
+            for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+                entry.getValue().post(
+                        () -> entry.getKey().onCaptionEnabledStateChanged(
+                                captionsEnabled, checkBeforeSwitch));
+            }
+        }
+
     }
 
     private final class RingerModeObservers {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 9362220..aafa16f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -34,6 +34,7 @@
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
 import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
 import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
 
@@ -82,6 +83,7 @@
 import android.util.SparseBooleanArray;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
@@ -120,6 +122,7 @@
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialog;
@@ -168,6 +171,13 @@
     /** Volume dialog slider animation. */
     private static final String TYPE_UPDATE = "update";
 
+    /**
+     *  TODO(b/290612381): remove lingering animations or tolerate them
+     *  When false, this will cause this class to not listen to animator events and not record jank
+     *  events. This should never be false in production code, and only is false for unit tests for
+     *  this class. This flag should be true in Scenario/Integration tests.
+     */
+    private final boolean mShouldListenForJank;
     private final int mDialogShowAnimationDurationMs;
     private final int mDialogHideAnimationDurationMs;
     private int mDialogWidth;
@@ -293,6 +303,7 @@
     private final DevicePostureController mDevicePostureController;
     private @DevicePostureController.DevicePostureInt int mDevicePosture;
     private int mOrientation;
+    private final FeatureFlags mFeatureFlags;
 
     public VolumeDialogImpl(
             Context context,
@@ -304,13 +315,18 @@
             VolumePanelFactory volumePanelFactory,
             ActivityStarter activityStarter,
             InteractionJankMonitor interactionJankMonitor,
+            boolean shouldListenForJank,
             CsdWarningDialog.Factory csdWarningDialogFactory,
             DevicePostureController devicePostureController,
             Looper looper,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            FeatureFlags featureFlags) {
+        mFeatureFlags = featureFlags;
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
         mHandler = new H(looper);
+
+        mShouldListenForJank = shouldListenForJank;
         mController = volumeDialogController;
         mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -929,6 +945,7 @@
                 showRingerDrawer();
             }
         });
+        updateSelectedRingerContainerDescription(mIsRingerDrawerOpen);
 
         mRingerDrawerVibrate.setOnClickListener(
                 new RingerDrawerItemClickListener(RINGER_MODE_VIBRATE));
@@ -991,6 +1008,19 @@
                         : 0;
     }
 
+    @VisibleForTesting String getSelectedRingerContainerDescription() {
+        return mSelectedRingerContainer == null ? null :
+                mSelectedRingerContainer.getContentDescription().toString();
+    }
+
+    @VisibleForTesting void toggleRingerDrawer(boolean show) {
+        if (show) {
+            showRingerDrawer();
+        } else {
+            hideRingerDrawer();
+        }
+    }
+
     /** Animates in the ringer drawer. */
     private void showRingerDrawer() {
         if (mIsRingerDrawerOpen) {
@@ -1068,12 +1098,7 @@
                     .start();
         }
 
-        // When the ringer drawer is open, tapping the currently selected ringer will set the ringer
-        // to the current ringer mode. Change the content description to that, instead of the 'tap
-        // to change ringer mode' default.
-        mSelectedRingerContainer.setContentDescription(
-                mContext.getString(getStringDescriptionResourceForRingerMode(
-                        mState.ringerModeInternal)));
+        updateSelectedRingerContainerDescription(true);
 
         mIsRingerDrawerOpen = true;
     }
@@ -1119,14 +1144,38 @@
                 .translationY(0f)
                 .start();
 
-        // When the drawer is closed, tapping the selected ringer drawer will open it, allowing the
-        // user to change the ringer.
-        mSelectedRingerContainer.setContentDescription(
-                mContext.getString(R.string.volume_ringer_change));
+        updateSelectedRingerContainerDescription(false);
 
         mIsRingerDrawerOpen = false;
     }
 
+
+    /**
+     * @param open false to set the description when drawer is closed
+     */
+    private void updateSelectedRingerContainerDescription(boolean open) {
+        if (mState == null || mSelectedRingerContainer == null) return;
+
+        String currentMode = mContext.getString(getStringDescriptionResourceForRingerMode(
+                mState.ringerModeInternal));
+        String tapToSelect;
+
+        if (open) {
+            // When the ringer drawer is open, tapping the currently selected ringer will set the
+            // ringer to the current ringer mode. Change the content description to that, instead of
+            // the 'tap to change ringer mode' default.
+            tapToSelect = "";
+
+        } else {
+            // When the drawer is closed, tapping the selected ringer drawer will open it, allowing
+            // the user to change the ringer. The user needs to know that, and also the current mode
+            currentMode += ", ";
+            tapToSelect = mContext.getString(R.string.volume_ringer_change);
+        }
+
+        mSelectedRingerContainer.setContentDescription(currentMode + tapToSelect);
+    }
+
     private void initSettingsH(int lockTaskModeState) {
         if (mSettingsView != null) {
             mSettingsView.setVisibility(
@@ -1284,21 +1333,30 @@
 
         if (!isServiceComponentEnabled) return;
 
-        updateCaptionsIcon();
+        checkEnabledStateForCaptionsIconUpdate();
         if (fromTooltip) showCaptionsTooltip();
     }
 
-    private void updateCaptionsIcon() {
-        boolean captionsEnabled = mController.areCaptionsEnabled();
-        if (mODICaptionsIcon.getCaptionsEnabled() != captionsEnabled) {
-            mHandler.post(mODICaptionsIcon.setCaptionsEnabled(captionsEnabled));
+    private void updateCaptionsEnabledH(boolean isCaptionsEnabled, boolean checkForSwitchState) {
+        if (checkForSwitchState) {
+            mController.setCaptionsEnabledState(!isCaptionsEnabled);
+        } else {
+            updateCaptionsIcon(isCaptionsEnabled);
+        }
+    }
+
+    private void checkEnabledStateForCaptionsIconUpdate() {
+        mController.getCaptionsEnabledState(false);
+    }
+
+    private void updateCaptionsIcon(boolean isCaptionsEnabled) {
+        if (mODICaptionsIcon.getCaptionsEnabled() != isCaptionsEnabled) {
+            mHandler.post(mODICaptionsIcon.setCaptionsEnabled(isCaptionsEnabled));
         }
     }
 
     private void onCaptionIconClicked() {
-        boolean isEnabled = mController.areCaptionsEnabled();
-        mController.setCaptionsEnabled(!isEnabled);
-        updateCaptionsIcon();
+        mController.getCaptionsEnabledState(true);
     }
 
     private void incrementManualToggleCount() {
@@ -1309,12 +1367,14 @@
 
     private void provideTouchFeedbackH(int newRingerMode) {
         VibrationEffect effect = null;
+        int hapticConstant = HapticFeedbackConstants.NO_HAPTICS;
         switch (newRingerMode) {
             case RINGER_MODE_NORMAL:
                 mController.scheduleTouchFeedback();
                 break;
             case RINGER_MODE_SILENT:
                 effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+                hapticConstant = HapticFeedbackConstants.TOGGLE_OFF;
                 break;
             case RINGER_MODE_VIBRATE:
                 // Feedback handled by onStateChange, for feedback both when user toggles
@@ -1322,8 +1382,11 @@
                 break;
             default:
                 effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+                hapticConstant = HapticFeedbackConstants.TOGGLE_ON;
         }
-        if (effect != null) {
+        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            mDialogView.performHapticFeedback(hapticConstant);
+        } else if (effect != null) {
             mController.vibrate(effect);
         }
     }
@@ -1368,7 +1431,10 @@
     }
 
     private Animator.AnimatorListener getJankListener(View v, String type, long timeout) {
-        return new Animator.AnimatorListener() {
+        if (!mShouldListenForJank) {
+            // TODO(b/290612381): temporary fix to prevent null pointers on leftover JankMonitors
+            return null;
+        } else return new Animator.AnimatorListener() {
             @Override
             public void onAnimationStart(@NonNull Animator animation) {
                 if (!v.isAttachedToWindow()) {
@@ -1702,7 +1768,7 @@
         });
     }
 
-    private int getStringDescriptionResourceForRingerMode(int mode) {
+    @VisibleForTesting int getStringDescriptionResourceForRingerMode(int mode) {
         switch (mode) {
             case RINGER_MODE_SILENT:
                 return R.string.volume_ringer_status_silent;
@@ -1757,7 +1823,22 @@
                 && mState.ringerModeInternal != -1
                 && mState.ringerModeInternal != state.ringerModeInternal
                 && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
-            mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
+
+            if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+                if (mShowing) {
+                    // The dialog view is responsible for triggering haptics in the oneway API
+                    mDialogView.performHapticFeedback(HapticFeedbackConstants.TOGGLE_ON);
+                }
+                /*
+                TODO(b/290642122): If the dialog is not showing, we have the case where haptics is
+                enabled by dragging the volume slider of Settings to a value of 0. This must be
+                handled by view Slices in Settings whilst using the performHapticFeedback API.
+                 */
+
+            } else {
+                // Old behavior only active if the oneway API is not used.
+                mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
+            }
         }
         mState = state;
         mDynamic.clear();
@@ -1784,6 +1865,7 @@
             updateVolumeRowH(row);
         }
         updateRingerH();
+        updateSelectedRingerContainerDescription(mIsRingerDrawerOpen);
         mWindow.setTitle(composeWindowTitle());
     }
 
@@ -2290,7 +2372,6 @@
             } else {
                 updateRowsH(activeRow);
             }
-
         }
 
         @Override
@@ -2298,6 +2379,11 @@
                 Boolean isComponentEnabled, Boolean fromTooltip) {
             updateODICaptionsH(isComponentEnabled, fromTooltip);
         }
+
+        @Override
+        public void onCaptionEnabledStateChanged(Boolean isEnabled, Boolean checkForSwitchState) {
+            updateCaptionsEnabledH(isEnabled, checkForSwitchState);
+        }
     };
 
     @VisibleForTesting void onPostureChanged(int posture) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index aa4ee54..cc9f3e1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -22,6 +22,7 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialog;
@@ -61,7 +62,8 @@
             InteractionJankMonitor interactionJankMonitor,
             CsdWarningDialog.Factory csdFactory,
             DevicePostureController devicePostureController,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            FeatureFlags featureFlags) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
                 volumeDialogController,
@@ -72,10 +74,12 @@
                 volumePanelFactory,
                 activityStarter,
                 interactionJankMonitor,
+                true, /* should listen for jank */
                 csdFactory,
                 devicePostureController,
                 Looper.getMainLooper(),
-                dumpManager);
+                dumpManager,
+                featureFlags);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
         impl.setSilentMode(false);
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index 4da5d49..de9b5ee 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -157,9 +157,10 @@
      * Query the wallet cards from {@link QuickAccessWalletClient}.
      *
      * @param cardsRetriever a callback to retrieve wallet cards.
+     * @param maxCards the maximum number of cards requested from the QuickAccessWallet
      */
     public void queryWalletCards(
-            QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) {
+            QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever, int maxCards) {
         if (mClock.elapsedRealtime() - mQawClientCreatedTimeMillis
                 > RECREATION_TIME_WINDOW) {
             Log.i(TAG, "Re-creating the QAW client to avoid stale.");
@@ -175,11 +176,22 @@
                 mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_height);
         int iconSizePx = mContext.getResources().getDimensionPixelSize(R.dimen.wallet_icon_size);
         GetWalletCardsRequest request =
-                new GetWalletCardsRequest(cardWidth, cardHeight, iconSizePx, /* maxCards= */ 1);
+                new GetWalletCardsRequest(cardWidth, cardHeight, iconSizePx, maxCards);
         mQuickAccessWalletClient.getWalletCards(mBgExecutor, request, cardsRetriever);
     }
 
     /**
+     * Query the wallet cards from {@link QuickAccessWalletClient}.
+     *
+     * @param cardsRetriever a callback to retrieve wallet cards.
+     */
+    public void queryWalletCards(
+            QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) {
+        queryWalletCards(cardsRetriever, /* maxCards= */ 1);
+    }
+
+
+    /**
      * Re-create the {@link QuickAccessWalletClient} of the controller.
      */
     public void reCreateWalletClient() {
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
index b3ad9b0..75df1bd 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
@@ -39,7 +39,6 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
@@ -88,7 +87,7 @@
                             QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
                         )
                         walletController.updateWalletPreference()
-                        walletController.queryWalletCards(callback)
+                        walletController.queryWalletCards(callback, MAX_CARDS)
 
                         awaitClose {
                             walletController.unregisterWalletChangeObservers(
@@ -152,5 +151,6 @@
 
     companion object {
         private const val TAG = "WalletSuggestions"
+        private const val MAX_CARDS = 50
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 316b54e..091a54f 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -109,6 +109,7 @@
         private WallpaperManager mWallpaperManager;
         private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor;
         private SurfaceHolder mSurfaceHolder;
+        private boolean mDrawn = false;
         @VisibleForTesting
         static final int MIN_SURFACE_WIDTH = 128;
         @VisibleForTesting
@@ -238,6 +239,7 @@
 
         private void drawFrameSynchronized() {
             synchronized (mLock) {
+                if (mDrawn) return;
                 drawFrameInternal();
             }
         }
@@ -275,6 +277,7 @@
                 Rect dest = mSurfaceHolder.getSurfaceFrame();
                 try {
                     canvas.drawBitmap(bitmap, null, dest, null);
+                    mDrawn = true;
                 } finally {
                     surface.unlockCanvasAndPost(canvas);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.kt
new file mode 100644
index 0000000..baf88b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/NoopWallpaperModule.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.wallpapers.data.repository.NoopWallpaperRepository
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface NoopWallpaperModule {
+    @Binds
+    @SysUISingleton
+    fun bindWallpaperRepository(impl: NoopWallpaperRepository): WallpaperRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.kt
new file mode 100644
index 0000000..1b89978
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/dagger/WallpaperModule.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
+import com.android.systemui.wallpapers.data.repository.WallpaperRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface WallpaperModule {
+    @Binds
+    @SysUISingleton
+    fun bindWallpaperRepository(impl: WallpaperRepositoryImpl): WallpaperRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
new file mode 100644
index 0000000..b45b8cd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * A no-op implementation of [WallpaperRepository].
+ *
+ * Used for variants of SysUI that do not support wallpaper but require other SysUI classes that
+ * have a wallpaper dependency.
+ */
+@SysUISingleton
+class NoopWallpaperRepository @Inject constructor() : WallpaperRepository {
+    override val wallpaperInfo: StateFlow<WallpaperInfo?> = MutableStateFlow(null).asStateFlow()
+    override val wallpaperSupportsAmbientMode = MutableStateFlow(false).asStateFlow()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
new file mode 100644
index 0000000..b8f9583
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import android.app.WallpaperManager
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/** A repository storing information about the current wallpaper. */
+interface WallpaperRepository {
+    /** Emits the current user's current wallpaper. */
+    val wallpaperInfo: StateFlow<WallpaperInfo?>
+
+    /** Emits true if the current user's current wallpaper supports ambient mode. */
+    val wallpaperSupportsAmbientMode: StateFlow<Boolean>
+}
+
+@SysUISingleton
+class WallpaperRepositoryImpl
+@Inject
+constructor(
+    @Application scope: CoroutineScope,
+    broadcastDispatcher: BroadcastDispatcher,
+    userRepository: UserRepository,
+    private val wallpaperManager: WallpaperManager,
+    context: Context,
+) : WallpaperRepository {
+    private val deviceSupportsAodWallpaper =
+        context.resources.getBoolean(com.android.internal.R.bool.config_dozeSupportsAodWallpaper)
+
+    private val wallpaperChanged: Flow<Unit> =
+        broadcastDispatcher
+            .broadcastFlow(
+                IntentFilter(Intent.ACTION_WALLPAPER_CHANGED),
+                user = UserHandle.ALL,
+            )
+            // The `combine` defining `wallpaperSupportsAmbientMode` will not run until both of the
+            // input flows emit at least once. Since this flow is an input flow, it needs to emit
+            // when it starts up to ensure that the `combine` will run if the user changes before we
+            // receive a ACTION_WALLPAPER_CHANGED intent.
+            // Note that the `selectedUser` flow does *not* need to emit on start because
+            // [UserRepository.selectedUser] is a state flow which will automatically emit a value
+            // on start.
+            .onStart { emit(Unit) }
+
+    private val selectedUser: Flow<SelectedUserModel> =
+        userRepository.selectedUser
+            // Only update the wallpaper status once the user selection has finished.
+            .filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE }
+
+    override val wallpaperInfo: StateFlow<WallpaperInfo?> =
+        if (!wallpaperManager.isWallpaperSupported || !deviceSupportsAodWallpaper) {
+            MutableStateFlow(null).asStateFlow()
+        } else {
+            combine(wallpaperChanged, selectedUser) { _, selectedUser ->
+                    getWallpaper(selectedUser)
+                }
+                .stateIn(
+                    scope,
+                    // Always be listening for wallpaper changes.
+                    SharingStarted.Eagerly,
+                    initialValue = getWallpaper(userRepository.selectedUser.value),
+                )
+        }
+
+    override val wallpaperSupportsAmbientMode: StateFlow<Boolean> =
+        wallpaperInfo
+            .map {
+                // If WallpaperInfo is null, it's ImageWallpaper which never supports ambient mode.
+                it?.supportsAmbientMode() == true
+            }
+            .stateIn(
+                scope,
+                // Always be listening for wallpaper changes.
+                SharingStarted.Eagerly,
+                initialValue = wallpaperInfo.value?.supportsAmbientMode() == true,
+            )
+
+    private fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? {
+        return wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index a5365fb..2acd4b9 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -621,13 +621,9 @@
     }
 
     private boolean isDismissableFromBubbles(NotificationEntry e) {
-        if (mNotifPipelineFlags.allowDismissOngoing()) {
-            // Bubbles are only accessible from the unlocked state,
-            // so we can calculate this from the Notification flags only.
-            return e.isDismissableForState(/*isLocked=*/ false);
-        } else {
-            return e.legacyIsDismissableRecursive();
-        }
+        // Bubbles are only accessible from the unlocked state,
+        // so we can calculate this from the Notification flags only.
+        return e.isDismissableForState(/*isLocked=*/ false);
     }
 
     private boolean shouldBubbleUp(NotificationEntry e) {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 5144d19..6fafcd5 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -51,15 +51,11 @@
 import com.android.systemui.notetask.NoteTaskInitializer;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.tracing.ProtoTraceable;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 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.nano.WmShellTraceProto;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedEventCallback;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -94,8 +90,7 @@
 @SysUISingleton
 public final class WMShell implements
         CoreStartable,
-        CommandQueue.Callbacks,
-        ProtoTraceable<SystemUiTraceProto> {
+        CommandQueue.Callbacks {
     private static final String TAG = WMShell.class.getName();
     private static final int INVALID_SYSUI_STATE_MASK =
             SYSUI_STATE_DIALOG_SHOWING
@@ -122,7 +117,6 @@
     private final ScreenLifecycle mScreenLifecycle;
     private final SysUiState mSysUiState;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
-    private final ProtoTracer mProtoTracer;
     private final UserTracker mUserTracker;
     private final DisplayTracker mDisplayTracker;
     private final NoteTaskInitializer mNoteTaskInitializer;
@@ -184,7 +178,6 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             ScreenLifecycle screenLifecycle,
             SysUiState sysUiState,
-            ProtoTracer protoTracer,
             WakefulnessLifecycle wakefulnessLifecycle,
             UserTracker userTracker,
             DisplayTracker displayTracker,
@@ -203,7 +196,6 @@
         mOneHandedOptional = oneHandedOptional;
         mDesktopModeOptional = desktopMode;
         mWakefulnessLifecycle = wakefulnessLifecycle;
-        mProtoTracer = protoTracer;
         mUserTracker = userTracker;
         mDisplayTracker = displayTracker;
         mNoteTaskInitializer = noteTaskInitializer;
@@ -223,7 +215,6 @@
         // Subscribe to user changes
         mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
 
-        mProtoTracer.add(this);
         mCommandQueue.addCallback(this);
         mPipOptional.ifPresent(this::initPip);
         mSplitScreenOptional.ifPresent(this::initSplitScreen);
@@ -361,9 +352,10 @@
     }
 
     @Override
-    public void writeToProto(SystemUiTraceProto proto) {
-        // Dump to WMShell proto here
-        // TODO: Figure out how we want to synchronize while dumping to proto
+    public boolean isDumpCritical() {
+        // Dump can't be critical because the shell has to dump on the main thread for
+        // synchronization reasons, which isn't reliably fast.
+        return false;
     }
 
     @Override
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 080be6d..12da17f 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -192,12 +192,6 @@
             android:excludeFromRecents="true" />
 
         <activity
-            android:name="com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity"
-            android:exported="false"
-            android:permission="com.android.systemui.permission.SELF"
-            android:excludeFromRecents="true" />
-
-        <activity
             android:name="com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity"
             android:exported="false"
             android:permission="com.android.systemui.permission.SELF"
diff --git a/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt b/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt
new file mode 100644
index 0000000..0fe2283
--- /dev/null
+++ b/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.animation
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.core.animation.doOnEnd
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This test class validates that two tests' animators are isolated from each other when using the
+ * same animator test rule. This is a test to prevent future instances of b/275602127.
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class AnimatorTestRuleIsolationTest : SysuiTestCase() {
+
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
+    @Test
+    fun testA() {
+        // GIVEN global state is reset at the start of the test
+        didTouchA = false
+        didTouchB = false
+        // WHEN starting 2 animations of different durations, and setting didTouchA at the end
+        ObjectAnimator.ofFloat(0f, 1f).apply {
+            duration = 100
+            doOnEnd { didTouchA = true }
+            start()
+        }
+        ObjectAnimator.ofFloat(0f, 1f).apply {
+            duration = 150
+            doOnEnd { didTouchA = true }
+            start()
+        }
+        // WHEN when you advance time so that only one of the animations has ended
+        animatorTestRule.advanceTimeBy(100)
+        // VERIFY we did indeed end the current animation
+        assertThat(didTouchA).isTrue()
+        // VERIFY advancing the animator did NOT cause testB's animator to end
+        assertThat(didTouchB).isFalse()
+    }
+
+    @Test
+    fun testB() {
+        // GIVEN global state is reset at the start of the test
+        didTouchA = false
+        didTouchB = false
+        // WHEN starting 2 animations of different durations, and setting didTouchB at the end
+        ObjectAnimator.ofFloat(0f, 1f).apply {
+            duration = 100
+            doOnEnd { didTouchB = true }
+            start()
+        }
+        ObjectAnimator.ofFloat(0f, 1f).apply {
+            duration = 150
+            doOnEnd { didTouchB = true }
+            start()
+        }
+        animatorTestRule.advanceTimeBy(100)
+        // VERIFY advancing the animator did NOT cause testA's animator to end
+        assertThat(didTouchA).isFalse()
+        // VERIFY we did indeed end the current animation
+        assertThat(didTouchB).isTrue()
+    }
+
+    companion object {
+        var didTouchA = false
+        var didTouchB = false
+    }
+}
diff --git a/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt b/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt
new file mode 100644
index 0000000..cc7f7e4
--- /dev/null
+++ b/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.animation
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.core.animation.doOnEnd
+import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class AnimatorTestRulePrecisionTest : SysuiTestCase() {
+
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
+    var value1: Float = -1f
+    var value2: Float = -1f
+
+    private inline fun animateThis(
+        propertyName: String,
+        duration: Long,
+        startDelay: Long = 0,
+        crossinline onEndAction: (animator: Animator) -> Unit,
+    ) {
+        ObjectAnimator.ofFloat(this, propertyName, 0f, 1f).also {
+            it.interpolator = Interpolators.LINEAR
+            it.duration = duration
+            it.startDelay = startDelay
+            it.doOnEnd(onEndAction)
+            it.start()
+        }
+    }
+
+    @Test
+    fun testSingleAnimator() {
+        var ended = false
+        animateThis("value1", duration = 100) { ended = true }
+
+        assertThat(value1).isEqualTo(0f)
+        assertThat(ended).isFalse()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+        animatorTestRule.advanceTimeBy(50)
+        assertThat(value1).isEqualTo(0.5f)
+        assertThat(ended).isFalse()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+        animatorTestRule.advanceTimeBy(49)
+        assertThat(value1).isEqualTo(0.99f)
+        assertThat(ended).isFalse()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+        animatorTestRule.advanceTimeBy(1)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(ended).isTrue()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(0)
+    }
+
+    @Test
+    fun testDelayedAnimator() {
+        var ended = false
+        animateThis("value1", duration = 100, startDelay = 50) { ended = true }
+
+        assertThat(value1).isEqualTo(-1f)
+        assertThat(ended).isFalse()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+        animatorTestRule.advanceTimeBy(49)
+        assertThat(value1).isEqualTo(-1f)
+        assertThat(ended).isFalse()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+        animatorTestRule.advanceTimeBy(1)
+        assertThat(value1).isEqualTo(0f)
+        assertThat(ended).isFalse()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+        animatorTestRule.advanceTimeBy(99)
+        assertThat(value1).isEqualTo(0.99f)
+        assertThat(ended).isFalse()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+        animatorTestRule.advanceTimeBy(1)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(ended).isTrue()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(0)
+    }
+
+    @Test
+    fun testTwoAnimators() {
+        var ended1 = false
+        var ended2 = false
+        animateThis("value1", duration = 100) { ended1 = true }
+        animateThis("value2", duration = 200) { ended2 = true }
+        assertThat(value1).isEqualTo(0f)
+        assertThat(value2).isEqualTo(0f)
+        assertThat(ended1).isFalse()
+        assertThat(ended2).isFalse()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(2)
+
+        animatorTestRule.advanceTimeBy(99)
+        assertThat(value1).isEqualTo(0.99f)
+        assertThat(value2).isEqualTo(0.495f)
+        assertThat(ended1).isFalse()
+        assertThat(ended2).isFalse()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(2)
+
+        animatorTestRule.advanceTimeBy(1)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(value2).isEqualTo(0.5f)
+        assertThat(ended1).isTrue()
+        assertThat(ended2).isFalse()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+        animatorTestRule.advanceTimeBy(99)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(value2).isEqualTo(0.995f)
+        assertThat(ended1).isTrue()
+        assertThat(ended2).isFalse()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+        animatorTestRule.advanceTimeBy(1)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(value2).isEqualTo(1f)
+        assertThat(ended1).isTrue()
+        assertThat(ended2).isTrue()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(0)
+    }
+
+    @Test
+    fun testChainedAnimators() {
+        var ended1 = false
+        var ended2 = false
+        animateThis("value1", duration = 100) {
+            ended1 = true
+            animateThis("value2", duration = 100) { ended2 = true }
+        }
+
+        assertThat(value1).isEqualTo(0f)
+        assertThat(value2).isEqualTo(-1f)
+        assertThat(ended1).isFalse()
+        assertThat(ended2).isFalse()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+        animatorTestRule.advanceTimeBy(99)
+        assertThat(value1).isEqualTo(0.99f)
+        assertThat(value2).isEqualTo(-1f)
+        assertThat(ended1).isFalse()
+        assertThat(ended2).isFalse()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+        animatorTestRule.advanceTimeBy(1)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(value2).isEqualTo(0f)
+        assertThat(ended1).isTrue()
+        assertThat(ended2).isFalse()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+        animatorTestRule.advanceTimeBy(99)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(value2).isEqualTo(0.99f)
+        assertThat(ended1).isTrue()
+        assertThat(ended2).isFalse()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(1)
+
+        animatorTestRule.advanceTimeBy(1)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(value2).isEqualTo(1f)
+        assertThat(ended1).isTrue()
+        assertThat(ended2).isTrue()
+        assertThat(AnimationHandler.getAnimationCount()).isEqualTo(0)
+    }
+}
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleIsolationTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleIsolationTest.kt
new file mode 100644
index 0000000..2d84fba
--- /dev/null
+++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleIsolationTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.core.animation
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.doOnEnd
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This test class validates that two tests' animators are isolated from each other when using the
+ * same animator test rule. This is a test to prevent future instances of b/275602127.
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class AnimatorTestRuleIsolationTest : SysuiTestCase() {
+
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
+    @Test
+    fun testA() {
+        // GIVEN global state is reset at the start of the test
+        didTouchA = false
+        didTouchB = false
+        // WHEN starting 2 animations of different durations, and setting didTouchA at the end
+        ObjectAnimator.ofFloat(0f, 1f).apply {
+            duration = 100
+            doOnEnd { didTouchA = true }
+            start()
+        }
+        ObjectAnimator.ofFloat(0f, 1f).apply {
+            duration = 150
+            doOnEnd { didTouchA = true }
+            start()
+        }
+        // WHEN when you advance time so that only one of the animations has ended
+        animatorTestRule.advanceTimeBy(100)
+        // VERIFY we did indeed end the current animation
+        assertThat(didTouchA).isTrue()
+        // VERIFY advancing the animator did NOT cause testB's animator to end
+        assertThat(didTouchB).isFalse()
+    }
+
+    @Test
+    fun testB() {
+        // GIVEN global state is reset at the start of the test
+        didTouchA = false
+        didTouchB = false
+        // WHEN starting 2 animations of different durations, and setting didTouchB at the end
+        ObjectAnimator.ofFloat(0f, 1f).apply {
+            duration = 100
+            doOnEnd { didTouchB = true }
+            start()
+        }
+        ObjectAnimator.ofFloat(0f, 1f).apply {
+            duration = 150
+            doOnEnd { didTouchB = true }
+            start()
+        }
+        animatorTestRule.advanceTimeBy(100)
+        // VERIFY advancing the animator did NOT cause testA's animator to end
+        assertThat(didTouchA).isFalse()
+        // VERIFY we did indeed end the current animation
+        assertThat(didTouchB).isTrue()
+    }
+
+    companion object {
+        var didTouchA = false
+        var didTouchB = false
+    }
+}
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
deleted file mode 100644
index e7738af..0000000
--- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.core.animation
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.doOnEnd
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-@RunWithLooper(setAsMainLooper = true)
-class AnimatorTestRuleTest : SysuiTestCase() {
-
-    @get:Rule val animatorTestRule = AnimatorTestRule()
-
-    @Test
-    fun testA() {
-        didTouchA = false
-        didTouchB = false
-        ObjectAnimator.ofFloat(0f, 1f).apply {
-            duration = 100
-            doOnEnd { didTouchA = true }
-            start()
-        }
-        ObjectAnimator.ofFloat(0f, 1f).apply {
-            duration = 150
-            doOnEnd { didTouchA = true }
-            start()
-        }
-        animatorTestRule.advanceTimeBy(100)
-        assertThat(didTouchA).isTrue()
-        assertThat(didTouchB).isFalse()
-    }
-
-    @Test
-    fun testB() {
-        didTouchA = false
-        didTouchB = false
-        ObjectAnimator.ofFloat(0f, 1f).apply {
-            duration = 100
-            doOnEnd { didTouchB = true }
-            start()
-        }
-        ObjectAnimator.ofFloat(0f, 1f).apply {
-            duration = 150
-            doOnEnd { didTouchB = true }
-            start()
-        }
-        animatorTestRule.advanceTimeBy(100)
-        assertThat(didTouchA).isFalse()
-        assertThat(didTouchB).isTrue()
-    }
-
-    companion object {
-        var didTouchA = false
-        var didTouchB = false
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index f5cd0ca..d506584 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -33,12 +33,15 @@
 import android.app.admin.IKeyguardClient;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.testing.ViewUtils;
+import android.view.Display;
 import android.view.SurfaceControlViewHost;
 import android.view.SurfaceView;
 import android.view.View;
@@ -77,7 +80,7 @@
     private KeyguardSecurityCallback mKeyguardCallback;
     @Mock
     private KeyguardUpdateMonitor mUpdateMonitor;
-    @Mock
+
     private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
 
     @Before
@@ -97,6 +100,11 @@
         when(mKeyguardClient.queryLocalInterface(anyString())).thenReturn(mKeyguardClient);
         when(mKeyguardClient.asBinder()).thenReturn(mKeyguardClient);
 
+        Display display = mContext.getSystemService(DisplayManager.class).getDisplay(
+                Display.DEFAULT_DISPLAY);
+        mSurfacePackage = (new SurfaceControlViewHost(mContext, display,
+                new Binder())).getSurfacePackage();
+
         mTestController = new AdminSecondaryLockScreenController.Factory(
                 mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler)
                 .create(mKeyguardCallback);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index e7d420b..9016220 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -419,7 +419,15 @@
 
         assertFalse(mWifiRepository.isWifiConnectedWithValidSsid());
         mWifiRepository.setWifiNetwork(
-                new WifiNetworkModel.Active(0, false, 0, "", false, false, null));
+                new WifiNetworkModel.Active(
+                        /* networkId= */ 0,
+                        /* isValidated= */ false,
+                        /* level= */ 0,
+                        /* ssid= */ "",
+                        /* hotspotDeviceType= */ WifiNetworkModel.HotspotDeviceType.NONE,
+                        /* isPasspointAccessPoint= */ false,
+                        /* isOnlineSignUpForPasspointAccessPoint= */ false,
+                        /* passpointProviderFriendlyName= */ null));
         assertTrue(mWifiRepository.isWifiConnectedWithValidSsid());
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index e9a9f3a..b67f280 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -18,24 +18,26 @@
 import android.content.BroadcastReceiver
 import android.testing.AndroidTestingRunner
 import android.view.View
-import android.widget.TextView
+import android.view.ViewTreeObserver
+import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 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.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.log.LogBuffer
 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.ClockFaceConfig
+import com.android.systemui.plugins.ClockFaceController
 import com.android.systemui.plugins.ClockFaceEvents
 import com.android.systemui.plugins.ClockTickRate
-import com.android.systemui.log.LogBuffer
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -54,6 +56,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyFloat
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
@@ -81,7 +84,13 @@
     @Mock private lateinit var bgExecutor: Executor
     @Mock private lateinit var featureFlags: FeatureFlags
     @Mock private lateinit var smallClockController: ClockFaceController
+    @Mock private lateinit var smallClockView: View
+    @Mock private lateinit var smallClockViewTreeObserver: ViewTreeObserver
+    @Mock private lateinit var smallClockFrame: FrameLayout
+    @Mock private lateinit var smallClockFrameViewTreeObserver: ViewTreeObserver
     @Mock private lateinit var largeClockController: ClockFaceController
+    @Mock private lateinit var largeClockView: View
+    @Mock private lateinit var largeClockViewTreeObserver: ViewTreeObserver
     @Mock private lateinit var smallClockEvents: ClockFaceEvents
     @Mock private lateinit var largeClockEvents: ClockFaceEvents
     @Mock private lateinit var parentView: View
@@ -97,8 +106,12 @@
     fun setUp() {
         whenever(clock.smallClock).thenReturn(smallClockController)
         whenever(clock.largeClock).thenReturn(largeClockController)
-        whenever(smallClockController.view).thenReturn(TextView(context))
-        whenever(largeClockController.view).thenReturn(TextView(context))
+        whenever(smallClockController.view).thenReturn(smallClockView)
+        whenever(smallClockView.parent).thenReturn(smallClockFrame)
+        whenever(smallClockView.viewTreeObserver).thenReturn(smallClockViewTreeObserver)
+        whenever(smallClockFrame.viewTreeObserver).thenReturn(smallClockFrameViewTreeObserver)
+        whenever(largeClockController.view).thenReturn(largeClockView)
+        whenever(largeClockView.viewTreeObserver).thenReturn(largeClockViewTreeObserver)
         whenever(smallClockController.events).thenReturn(smallClockEvents)
         whenever(largeClockController.events).thenReturn(largeClockEvents)
         whenever(clock.events).thenReturn(events)
@@ -118,8 +131,11 @@
                 commandQueue = commandQueue,
                 featureFlags = featureFlags,
                 bouncerRepository = bouncerRepository,
+                configurationRepository = FakeConfigurationRepository(),
             ),
-            KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+            ).keyguardTransitionInteractor,
             broadcastDispatcher,
             batteryController,
             keyguardUpdateMonitor,
@@ -156,9 +172,8 @@
 
     @Test
     fun themeChanged_verifyClockPaletteUpdated() = runBlocking(IMMEDIATE) {
-        // TODO(b/266103601): delete this test and add more coverage for updateColors()
-        // verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
-        // verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
+         verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
+         verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
 
         val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
         verify(configurationController).addCallback(capture(captor))
@@ -298,8 +313,38 @@
         verify(configurationController).removeCallback(any())
         verify(batteryController).removeCallback(any())
         verify(keyguardUpdateMonitor).removeCallback(any())
+        verify(smallClockController.view)
+                .removeOnAttachStateChangeListener(underTest.smallClockOnAttachStateChangeListener)
+        verify(largeClockController.view)
+                .removeOnAttachStateChangeListener(underTest.largeClockOnAttachStateChangeListener)
     }
 
+    @Test
+    fun registerOnAttachStateChangeListener_validate() = runBlocking(IMMEDIATE) {
+        verify(smallClockController.view)
+            .addOnAttachStateChangeListener(underTest.smallClockOnAttachStateChangeListener)
+        verify(largeClockController.view)
+            .addOnAttachStateChangeListener(underTest.largeClockOnAttachStateChangeListener)
+    }
+
+    @Test
+    fun registerAndRemoveOnGlobalLayoutListener_correctly() = runBlocking(IMMEDIATE) {
+        underTest.smallClockOnAttachStateChangeListener!!.onViewAttachedToWindow(smallClockView)
+        verify(smallClockFrame.viewTreeObserver).addOnGlobalLayoutListener(any())
+        underTest.smallClockOnAttachStateChangeListener!!.onViewDetachedFromWindow(smallClockView)
+        verify(smallClockFrame.viewTreeObserver).removeOnGlobalLayoutListener(any())
+    }
+
+    @Test
+    fun registerOnGlobalLayoutListener_RemoveOnAttachStateChangeListener_correctly() =
+        runBlocking(IMMEDIATE) {
+            underTest.smallClockOnAttachStateChangeListener!!
+                .onViewAttachedToWindow(smallClockView)
+            verify(smallClockFrame.viewTreeObserver).addOnGlobalLayoutListener(any())
+            underTest.unregisterListeners()
+            verify(smallClockFrame.viewTreeObserver).removeOnGlobalLayoutListener(any())
+        }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 677d3ff..c894d91 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -180,8 +180,9 @@
     }
 
     @Test
-    public void testResume() {
-        mKeyguardAbsKeyInputViewController.onResume(KeyguardSecurityView.VIEW_REVEALED);
+    public void testOnViewAttached() {
+        reset(mLockPatternUtils);
+        mKeyguardAbsKeyInputViewController.onViewAttached();
         verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
new file mode 100644
index 0000000..98d4d22
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import static android.view.View.INVISIBLE;
+
+import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.plugins.ClockAnimations;
+import com.android.systemui.plugins.ClockController;
+import com.android.systemui.plugins.ClockEvents;
+import com.android.systemui.plugins.ClockFaceConfig;
+import com.android.systemui.plugins.ClockFaceController;
+import com.android.systemui.plugins.ClockFaceEvents;
+import com.android.systemui.plugins.ClockTickRate;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.shared.clocks.ClockRegistry;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
+import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase {
+
+    @Mock
+    protected KeyguardClockSwitch mView;
+    @Mock
+    protected StatusBarStateController mStatusBarStateController;
+    @Mock
+    protected ClockRegistry mClockRegistry;
+    @Mock
+    KeyguardSliceViewController mKeyguardSliceViewController;
+    @Mock
+    NotificationIconAreaController mNotificationIconAreaController;
+    @Mock
+    LockscreenSmartspaceController mSmartspaceController;
+
+    @Mock
+    Resources mResources;
+    @Mock
+    KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+    @Mock
+    protected ClockController mClockController;
+    @Mock
+    protected ClockFaceController mLargeClockController;
+    @Mock
+    protected ClockFaceController mSmallClockController;
+    @Mock
+    protected ClockAnimations mClockAnimations;
+    @Mock
+    protected ClockEvents mClockEvents;
+    @Mock
+    protected ClockFaceEvents mClockFaceEvents;
+    @Mock
+    DumpManager mDumpManager;
+    @Mock
+    ClockEventController mClockEventController;
+
+    @Mock
+    protected NotificationIconContainer mNotificationIcons;
+    @Mock
+    protected AnimatableClockView mSmallClockView;
+    @Mock
+    protected AnimatableClockView mLargeClockView;
+    @Mock
+    protected FrameLayout mSmallClockFrame;
+    @Mock
+    protected FrameLayout mLargeClockFrame;
+    @Mock
+    protected SecureSettings mSecureSettings;
+    @Mock
+    protected LogBuffer mLogBuffer;
+
+    protected final View mFakeDateView = (View) (new ViewGroup(mContext) {
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {}
+    });
+    protected final View mFakeWeatherView = new View(mContext);
+    protected final View mFakeSmartspaceView = new View(mContext);
+
+    protected KeyguardClockSwitchController mController;
+    protected View mSliceView;
+    protected LinearLayout mStatusArea;
+    protected FakeExecutor mExecutor;
+    protected FakeFeatureFlags mFakeFeatureFlags;
+    @Captor protected ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
+            ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mView.findViewById(R.id.left_aligned_notification_icon_container))
+                .thenReturn(mNotificationIcons);
+        when(mNotificationIcons.getLayoutParams()).thenReturn(
+                mock(RelativeLayout.LayoutParams.class));
+        when(mView.getContext()).thenReturn(getContext());
+        when(mView.getResources()).thenReturn(mResources);
+        when(mResources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin))
+                .thenReturn(100);
+        when(mResources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin))
+                .thenReturn(-200);
+        when(mResources.getInteger(R.integer.keyguard_date_weather_view_invisibility))
+                .thenReturn(INVISIBLE);
+
+        when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame);
+        when(mView.findViewById(R.id.lockscreen_clock_view)).thenReturn(mSmallClockFrame);
+        when(mSmallClockView.getContext()).thenReturn(getContext());
+        when(mLargeClockView.getContext()).thenReturn(getContext());
+
+        when(mView.isAttachedToWindow()).thenReturn(true);
+        when(mSmartspaceController.buildAndConnectDateView(any())).thenReturn(mFakeDateView);
+        when(mSmartspaceController.buildAndConnectWeatherView(any())).thenReturn(mFakeWeatherView);
+        when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
+        doAnswer(invocation -> {
+            removeView(mFakeDateView);
+            removeView(mFakeWeatherView);
+            removeView(mFakeSmartspaceView);
+            return null;
+        }).when(mSmartspaceController).removeViewsFromParent(any());
+        mExecutor = new FakeExecutor(new FakeSystemClock());
+        mFakeFeatureFlags = new FakeFeatureFlags();
+        mFakeFeatureFlags.set(FACE_AUTH_REFACTOR, false);
+        mFakeFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
+        mController = new KeyguardClockSwitchController(
+                mView,
+                mStatusBarStateController,
+                mClockRegistry,
+                mKeyguardSliceViewController,
+                mNotificationIconAreaController,
+                mSmartspaceController,
+                mKeyguardUnlockAnimationController,
+                mSecureSettings,
+                mExecutor,
+                mDumpManager,
+                mClockEventController,
+                mLogBuffer,
+                KeyguardInteractorFactory.create(mFakeFeatureFlags).getKeyguardInteractor(),
+                mFakeFeatureFlags
+        );
+
+        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
+        when(mLargeClockController.getView()).thenReturn(mLargeClockView);
+        when(mSmallClockController.getView()).thenReturn(mSmallClockView);
+        when(mClockController.getLargeClock()).thenReturn(mLargeClockController);
+        when(mClockController.getSmallClock()).thenReturn(mSmallClockController);
+        when(mClockController.getEvents()).thenReturn(mClockEvents);
+        when(mSmallClockController.getEvents()).thenReturn(mClockFaceEvents);
+        when(mLargeClockController.getEvents()).thenReturn(mClockFaceEvents);
+        when(mLargeClockController.getAnimations()).thenReturn(mClockAnimations);
+        when(mSmallClockController.getAnimations()).thenReturn(mClockAnimations);
+        when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
+        when(mClockEventController.getClock()).thenReturn(mClockController);
+        when(mSmallClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false));
+        when(mLargeClockController.getConfig())
+                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false));
+
+        mSliceView = new View(getContext());
+        when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
+        mStatusArea = new LinearLayout(getContext());
+        when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea);
+    }
+
+    private void removeView(View v) {
+        ViewGroup group = ((ViewGroup) v.getParent());
+        if (group != null) {
+            group.removeView(v);
+        }
+    }
+
+    protected void init() {
+        mController.init();
+
+        verify(mView, atLeast(1)).addOnAttachStateChangeListener(mAttachCaptor.capture());
+        mAttachCaptor.getValue().onViewAttachedToWindow(mView);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 9e561ed..e64ef04 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -21,186 +21,33 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
-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.content.res.Resources;
 import android.database.ContentObserver;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.log.LogBuffer;
-import com.android.systemui.plugins.ClockAnimations;
-import com.android.systemui.plugins.ClockController;
-import com.android.systemui.plugins.ClockEvents;
 import com.android.systemui.plugins.ClockFaceConfig;
-import com.android.systemui.plugins.ClockFaceController;
-import com.android.systemui.plugins.ClockFaceEvents;
 import com.android.systemui.plugins.ClockTickRate;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.clocks.AnimatableClockView;
 import com.android.systemui.shared.clocks.ClockRegistry;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.SecureSettings;
-import com.android.systemui.util.time.FakeSystemClock;
 
-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;
 import org.mockito.verification.VerificationMode;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
-
-    @Mock
-    private KeyguardClockSwitch mView;
-    @Mock
-    private StatusBarStateController mStatusBarStateController;
-    @Mock
-    private ClockRegistry mClockRegistry;
-    @Mock
-    KeyguardSliceViewController mKeyguardSliceViewController;
-    @Mock
-    NotificationIconAreaController mNotificationIconAreaController;
-    @Mock
-    LockscreenSmartspaceController mSmartspaceController;
-
-    @Mock
-    Resources mResources;
-    @Mock
-    KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
-    @Mock
-    private ClockController mClockController;
-    @Mock
-    private ClockFaceController mLargeClockController;
-    @Mock
-    private ClockFaceController mSmallClockController;
-    @Mock
-    private ClockAnimations mClockAnimations;
-    @Mock
-    private ClockEvents mClockEvents;
-    @Mock
-    private ClockFaceEvents mClockFaceEvents;
-    @Mock
-    DumpManager mDumpManager;
-    @Mock
-    ClockEventController mClockEventController;
-
-    @Mock
-    private NotificationIconContainer mNotificationIcons;
-    @Mock
-    private AnimatableClockView mSmallClockView;
-    @Mock
-    private AnimatableClockView mLargeClockView;
-    @Mock
-    private FrameLayout mSmallClockFrame;
-    @Mock
-    private FrameLayout mLargeClockFrame;
-    @Mock
-    private SecureSettings mSecureSettings;
-    @Mock
-    private LogBuffer mLogBuffer;
-
-    private final View mFakeDateView = (View) (new ViewGroup(mContext) {
-        @Override
-        protected void onLayout(boolean changed, int l, int t, int r, int b) {}
-    });
-    private final View mFakeWeatherView = new View(mContext);
-    private final View mFakeSmartspaceView = new View(mContext);
-
-    private KeyguardClockSwitchController mController;
-    private View mSliceView;
-    private FakeExecutor mExecutor;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        when(mView.findViewById(R.id.left_aligned_notification_icon_container))
-                .thenReturn(mNotificationIcons);
-        when(mNotificationIcons.getLayoutParams()).thenReturn(
-                mock(RelativeLayout.LayoutParams.class));
-        when(mView.getContext()).thenReturn(getContext());
-        when(mView.getResources()).thenReturn(mResources);
-        when(mResources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin))
-                .thenReturn(100);
-        when(mResources.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin))
-                .thenReturn(-200);
-        when(mResources.getInteger(R.integer.keyguard_date_weather_view_invisibility))
-                .thenReturn(View.INVISIBLE);
-
-        when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame);
-        when(mView.findViewById(R.id.lockscreen_clock_view)).thenReturn(mSmallClockFrame);
-        when(mSmallClockView.getContext()).thenReturn(getContext());
-        when(mLargeClockView.getContext()).thenReturn(getContext());
-
-        when(mView.isAttachedToWindow()).thenReturn(true);
-        when(mSmartspaceController.buildAndConnectDateView(any())).thenReturn(mFakeDateView);
-        when(mSmartspaceController.buildAndConnectWeatherView(any())).thenReturn(mFakeWeatherView);
-        when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
-        mExecutor = new FakeExecutor(new FakeSystemClock());
-        mController = new KeyguardClockSwitchController(
-                mView,
-                mStatusBarStateController,
-                mClockRegistry,
-                mKeyguardSliceViewController,
-                mNotificationIconAreaController,
-                mSmartspaceController,
-                mKeyguardUnlockAnimationController,
-                mSecureSettings,
-                mExecutor,
-                mDumpManager,
-                mClockEventController,
-                mLogBuffer
-        );
-
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
-        when(mLargeClockController.getView()).thenReturn(mLargeClockView);
-        when(mSmallClockController.getView()).thenReturn(mSmallClockView);
-        when(mClockController.getLargeClock()).thenReturn(mLargeClockController);
-        when(mClockController.getSmallClock()).thenReturn(mSmallClockController);
-        when(mClockController.getEvents()).thenReturn(mClockEvents);
-        when(mSmallClockController.getEvents()).thenReturn(mClockFaceEvents);
-        when(mLargeClockController.getEvents()).thenReturn(mClockFaceEvents);
-        when(mLargeClockController.getAnimations()).thenReturn(mClockAnimations);
-        when(mSmallClockController.getAnimations()).thenReturn(mClockAnimations);
-        when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
-        when(mClockEventController.getClock()).thenReturn(mClockController);
-        when(mSmallClockController.getConfig())
-                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false));
-        when(mLargeClockController.getConfig())
-                .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false));
-
-        mSliceView = new View(getContext());
-        when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
-        when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(
-                new LinearLayout(getContext()));
-    }
-
+public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchControllerBaseTest {
     @Test
     public void testInit_viewAlreadyAttached() {
         mController.init();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..9a1a4e2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class KeyguardClockSwitchControllerWithCoroutinesTest : KeyguardClockSwitchControllerBaseTest() {
+
+    @Test
+    fun testStatusAreaVisibility_onLockscreenHostedDreamStateChanged() =
+        runBlocking(IMMEDIATE) {
+            // GIVEN starting state for the keyguard clock and wallpaper dream enabled
+            mFakeFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, true)
+            init()
+
+            // WHEN dreaming starts
+            mController.mIsActiveDreamLockscreenHostedCallback.accept(
+                true /* isActiveDreamLockscreenHosted */
+            )
+
+            // THEN the status area is hidden
+            mExecutor.runAllReady()
+            assertEquals(View.INVISIBLE, mStatusArea.visibility)
+
+            // WHEN dreaming stops
+            mController.mIsActiveDreamLockscreenHostedCallback.accept(
+                false /* isActiveDreamLockscreenHosted */
+            )
+            mExecutor.runAllReady()
+
+            // THEN status area view is visible
+            assertEquals(View.VISIBLE, mStatusArea.visibility)
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 061340e..65c677e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
@@ -192,6 +193,7 @@
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
         assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
         assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
+        assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE);
     }
 
     @Test
@@ -201,6 +203,7 @@
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
         assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
         assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
+        assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE);
     }
 
     @Test
@@ -216,6 +219,7 @@
         // only big clock is removed at switch
         assertThat(mLargeClockFrame.getParent()).isNull();
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
+        assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE);
     }
 
     @Test
@@ -227,6 +231,7 @@
         // only big clock is removed at switch
         assertThat(mLargeClockFrame.getParent()).isNull();
         assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
+        assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
index b349696..438f0f4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
@@ -18,6 +18,8 @@
 
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 
+import static com.android.systemui.flags.Flags.ENABLE_CLOCK_KEYGUARD_PRESENTATION;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
@@ -37,6 +39,7 @@
 
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -59,8 +62,13 @@
     @Mock
     private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     @Mock
+    private ConnectedDisplayKeyguardPresentation.Factory
+            mConnectedDisplayKeyguardPresentationFactory;
+    @Mock
     private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation;
     @Mock
+    private ConnectedDisplayKeyguardPresentation mConnectedDisplayKeyguardPresentation;
+    @Mock
     private KeyguardDisplayManager.DeviceStateHelper mDeviceStateHelper;
     @Mock
     private KeyguardStateController mKeyguardStateController;
@@ -69,7 +77,7 @@
     private Executor mBackgroundExecutor = Runnable::run;
     private KeyguardDisplayManager mManager;
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
-
+    private FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
     // The default and secondary displays are both in the default group
     private Display mDefaultDisplay;
     private Display mSecondaryDisplay;
@@ -80,10 +88,14 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mFakeFeatureFlags.set(ENABLE_CLOCK_KEYGUARD_PRESENTATION, false);
         mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
                 mKeyguardStatusViewComponentFactory, mDisplayTracker, mMainExecutor,
-                mBackgroundExecutor, mDeviceStateHelper, mKeyguardStateController));
+                mBackgroundExecutor, mDeviceStateHelper, mKeyguardStateController,
+                mConnectedDisplayKeyguardPresentationFactory, mFakeFeatureFlags));
         doReturn(mKeyguardPresentation).when(mManager).createPresentation(any());
+        doReturn(mConnectedDisplayKeyguardPresentation).when(
+                mConnectedDisplayKeyguardPresentationFactory).create(any());
 
         mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
                 new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
@@ -138,4 +150,15 @@
         when(mKeyguardStateController.isOccluded()).thenReturn(true);
         verify(mManager, never()).createPresentation(eq(mSecondaryDisplay));
     }
+
+    @Test
+    public void testShow_withClockPresentationFlagEnabled_presentationCreated() {
+        when(mManager.createPresentation(any())).thenCallRealMethod();
+        mFakeFeatureFlags.set(ENABLE_CLOCK_KEYGUARD_PRESENTATION, true);
+        mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
+
+        mManager.show();
+
+        verify(mConnectedDisplayKeyguardPresentationFactory).create(eq(mSecondaryDisplay));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 1a9260c..3a94730 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -20,6 +20,7 @@
 import android.testing.TestableLooper
 import android.view.inputmethod.InputMethodManager
 import android.widget.EditText
+import android.widget.ImageView
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
@@ -29,6 +30,7 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.mockito.whenever
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -36,6 +38,7 @@
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
@@ -45,104 +48,109 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class KeyguardPasswordViewControllerTest : SysuiTestCase() {
-  @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView
-  @Mock private lateinit var passwordEntry: EditText
-  @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-  @Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode
-  @Mock lateinit var lockPatternUtils: LockPatternUtils
-  @Mock lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
-  @Mock lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
-  @Mock lateinit var latencyTracker: LatencyTracker
-  @Mock lateinit var inputMethodManager: InputMethodManager
-  @Mock lateinit var emergencyButtonController: EmergencyButtonController
-  @Mock lateinit var mainExecutor: DelayableExecutor
-  @Mock lateinit var falsingCollector: FalsingCollector
-  @Mock lateinit var keyguardViewController: KeyguardViewController
-  @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
-  @Mock
-  private lateinit var mKeyguardMessageAreaController:
-      KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+    @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView
+    @Mock private lateinit var passwordEntry: EditText
+    @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode
+    @Mock lateinit var lockPatternUtils: LockPatternUtils
+    @Mock lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
+    @Mock lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+    @Mock lateinit var latencyTracker: LatencyTracker
+    @Mock lateinit var inputMethodManager: InputMethodManager
+    @Mock lateinit var emergencyButtonController: EmergencyButtonController
+    @Mock lateinit var mainExecutor: DelayableExecutor
+    @Mock lateinit var falsingCollector: FalsingCollector
+    @Mock lateinit var keyguardViewController: KeyguardViewController
+    @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
+    @Mock
+    private lateinit var mKeyguardMessageAreaController:
+        KeyguardMessageAreaController<BouncerKeyguardMessageArea>
 
-  private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
+    private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
 
-  @Before
-  fun setup() {
-    MockitoAnnotations.initMocks(this)
-    Mockito.`when`(
-            keyguardPasswordView.requireViewById<BouncerKeyguardMessageArea>(
-                R.id.bouncer_message_area))
-        .thenReturn(mKeyguardMessageArea)
-    Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
-        .thenReturn(mKeyguardMessageAreaController)
-    Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
-    Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
-        .thenReturn(passwordEntry)
-    `when`(keyguardPasswordView.resources).thenReturn(context.resources)
-    val fakeFeatureFlags = FakeFeatureFlags()
-    fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
-    keyguardPasswordViewController =
-        KeyguardPasswordViewController(
-            keyguardPasswordView,
-            keyguardUpdateMonitor,
-            securityMode,
-            lockPatternUtils,
-            keyguardSecurityCallback,
-            messageAreaControllerFactory,
-            latencyTracker,
-            inputMethodManager,
-            emergencyButtonController,
-            mainExecutor,
-            mContext.resources,
-            falsingCollector,
-            keyguardViewController,
-            fakeFeatureFlags)
-  }
-
-  @Test
-  fun testFocusWhenBouncerIsShown() {
-    Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
-    Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
-    keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
-    keyguardPasswordView.post {
-      verify(keyguardPasswordView).requestFocus()
-      verify(keyguardPasswordView).showKeyboard()
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        Mockito.`when`(
+                keyguardPasswordView.requireViewById<BouncerKeyguardMessageArea>(
+                    R.id.bouncer_message_area
+                )
+            )
+            .thenReturn(mKeyguardMessageArea)
+        Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
+            .thenReturn(mKeyguardMessageAreaController)
+        Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
+        Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
+            .thenReturn(passwordEntry)
+        whenever(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button))
+            .thenReturn(mock(ImageView::class.java))
+        `when`(keyguardPasswordView.resources).thenReturn(context.resources)
+        val fakeFeatureFlags = FakeFeatureFlags()
+        fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+        keyguardPasswordViewController =
+            KeyguardPasswordViewController(
+                keyguardPasswordView,
+                keyguardUpdateMonitor,
+                securityMode,
+                lockPatternUtils,
+                keyguardSecurityCallback,
+                messageAreaControllerFactory,
+                latencyTracker,
+                inputMethodManager,
+                emergencyButtonController,
+                mainExecutor,
+                mContext.resources,
+                falsingCollector,
+                keyguardViewController,
+                fakeFeatureFlags
+            )
     }
-  }
 
-  @Test
-  fun testDoNotFocusWhenBouncerIsHidden() {
-    Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
-    Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
-    keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
-    verify(keyguardPasswordView, never()).requestFocus()
-  }
-
-  @Test
-  fun testHideKeyboardWhenOnPause() {
-    keyguardPasswordViewController.onPause()
-    keyguardPasswordView.post {
-      verify(keyguardPasswordView).clearFocus()
-      verify(keyguardPasswordView).hideKeyboard()
+    @Test
+    fun testFocusWhenBouncerIsShown() {
+        Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
+        Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+        keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+        keyguardPasswordView.post {
+            verify(keyguardPasswordView).requestFocus()
+            verify(keyguardPasswordView).showKeyboard()
+        }
     }
-  }
 
-  @Test
-  fun startAppearAnimation() {
-    keyguardPasswordViewController.startAppearAnimation()
-    verify(mKeyguardMessageAreaController)
-        .setMessage(context.resources.getString(R.string.keyguard_enter_your_password), false)
-  }
+    @Test
+    fun testDoNotFocusWhenBouncerIsHidden() {
+        Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
+        Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+        keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+        verify(keyguardPasswordView, never()).requestFocus()
+    }
 
-  @Test
-  fun startAppearAnimation_withExistingMessage() {
-    `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
-    keyguardPasswordViewController.startAppearAnimation()
-    verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
-  }
+    @Test
+    fun testHideKeyboardWhenOnPause() {
+        keyguardPasswordViewController.onPause()
+        keyguardPasswordView.post {
+            verify(keyguardPasswordView).clearFocus()
+            verify(keyguardPasswordView).hideKeyboard()
+        }
+    }
 
-  @Test
-  fun testMessageIsSetWhenReset() {
-    keyguardPasswordViewController.resetState()
-    verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
-  }
+    @Test
+    fun testOnViewAttached() {
+        keyguardPasswordViewController.onViewAttached()
+        verify(mKeyguardMessageAreaController)
+            .setMessage(context.resources.getString(R.string.keyguard_enter_your_password), false)
+    }
+
+    @Test
+    fun testOnViewAttached_withExistingMessage() {
+        `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+        keyguardPasswordViewController.onViewAttached()
+        verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+    }
+
+    @Test
+    fun testMessageIsSetWhenReset() {
+        keyguardPasswordViewController.resetState()
+        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 71a57c7..1acd676 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -18,10 +18,12 @@
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
-import com.android.internal.widget.LockPatternView
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
@@ -29,14 +31,22 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.util.mockito.any
+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.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
@@ -45,100 +55,140 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class KeyguardPatternViewControllerTest : SysuiTestCase() {
-  @Mock private lateinit var mKeyguardPatternView: KeyguardPatternView
+    private lateinit var mKeyguardPatternView: KeyguardPatternView
 
-  @Mock private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
 
-  @Mock private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
+    @Mock private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
 
-  @Mock private lateinit var mLockPatternUtils: LockPatternUtils
+    @Mock private lateinit var mLockPatternUtils: LockPatternUtils
 
-  @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
+    @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
 
-  @Mock private lateinit var mLatencyTracker: LatencyTracker
-  private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
+    @Mock private lateinit var mLatencyTracker: LatencyTracker
+    private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
 
-  @Mock private lateinit var mEmergencyButtonController: EmergencyButtonController
+    @Mock private lateinit var mEmergencyButtonController: EmergencyButtonController
 
-  @Mock
-  private lateinit var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
+    @Mock
+    private lateinit var mKeyguardMessageAreaControllerFactory:
+        KeyguardMessageAreaController.Factory
 
-  @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
+    @Mock
+    private lateinit var mKeyguardMessageAreaController:
+        KeyguardMessageAreaController<BouncerKeyguardMessageArea>
 
-  @Mock
-  private lateinit var mKeyguardMessageAreaController:
-      KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+    @Mock private lateinit var mPostureController: DevicePostureController
 
-  @Mock private lateinit var mLockPatternView: LockPatternView
+    private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
+    private lateinit var fakeFeatureFlags: FakeFeatureFlags
 
-  @Mock private lateinit var mPostureController: DevicePostureController
+    @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
 
-  private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
-  private lateinit var fakeFeatureFlags: FakeFeatureFlags
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        whenever(mKeyguardMessageAreaControllerFactory.create(any()))
+            .thenReturn(mKeyguardMessageAreaController)
+        fakeFeatureFlags = FakeFeatureFlags()
+        fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false)
+        mKeyguardPatternView =
+            View.inflate(mContext, R.layout.keyguard_pattern_view, null) as KeyguardPatternView
 
-  @Before
-  fun setup() {
-    MockitoAnnotations.initMocks(this)
-    `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
-    `when`(
-            mKeyguardPatternView.requireViewById<BouncerKeyguardMessageArea>(
-                R.id.bouncer_message_area))
-        .thenReturn(mKeyguardMessageArea)
-    `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
-        .thenReturn(mLockPatternView)
-    `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
-        .thenReturn(mKeyguardMessageAreaController)
-    `when`(mKeyguardPatternView.resources).thenReturn(context.resources)
-    fakeFeatureFlags = FakeFeatureFlags()
-    fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false)
-    mKeyguardPatternViewController =
-        KeyguardPatternViewController(
-            mKeyguardPatternView,
-            mKeyguardUpdateMonitor,
-            mSecurityMode,
-            mLockPatternUtils,
-            mKeyguardSecurityCallback,
-            mLatencyTracker,
-            mFalsingCollector,
-            mEmergencyButtonController,
-            mKeyguardMessageAreaControllerFactory,
-            mPostureController,
-            fakeFeatureFlags)
-  }
+        mKeyguardPatternViewController =
+            KeyguardPatternViewController(
+                mKeyguardPatternView,
+                mKeyguardUpdateMonitor,
+                mSecurityMode,
+                mLockPatternUtils,
+                mKeyguardSecurityCallback,
+                mLatencyTracker,
+                mFalsingCollector,
+                mEmergencyButtonController,
+                mKeyguardMessageAreaControllerFactory,
+                mPostureController,
+                fakeFeatureFlags
+            )
+        mKeyguardPatternView.onAttachedToWindow()
+    }
 
-  @Test
-  fun withFeatureFlagOn_oldMessage_isHidden() {
-    fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+    @Test
+    fun onViewAttached_deviceHalfFolded_propagatedToPatternView() {
+        overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+        whenever(mPostureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
 
-    mKeyguardPatternViewController.init()
+        mKeyguardPatternViewController.onViewAttached()
 
-    verify<KeyguardMessageAreaController<*>>(mKeyguardMessageAreaController).disable()
-  }
+        assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
+    }
 
-  @Test
-  fun onPause_resetsText() {
-    mKeyguardPatternViewController.init()
-    mKeyguardPatternViewController.onPause()
-    verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
-  }
+    @Test
+    fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() {
+        overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+        whenever(mPostureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
 
-  @Test
-  fun startAppearAnimation() {
-    mKeyguardPatternViewController.startAppearAnimation()
-    verify(mKeyguardMessageAreaController)
-        .setMessage(context.resources.getString(R.string.keyguard_enter_your_pattern), false)
-  }
+        mKeyguardPatternViewController.onViewAttached()
 
-  @Test
-  fun startAppearAnimation_withExistingMessage() {
-    `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
-    mKeyguardPatternViewController.startAppearAnimation()
-    verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
-  }
+        // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
+        assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
 
-  @Test
-  fun resume() {
-    mKeyguardPatternViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
-    verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
-  }
+        // Simulate posture change to state DEVICE_POSTURE_OPENED with callback
+        verify(mPostureController).addCallback(postureCallbackCaptor.capture())
+        val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value
+        postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        // Simulate posture change to same state with callback
+        assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+
+        postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        // Verify view is still in posture state DEVICE_POSTURE_OPENED
+        assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+    }
+
+    private fun getPatternTopGuideline(): Float {
+        val cs = ConstraintSet()
+        val container =
+            mKeyguardPatternView.requireViewById(R.id.pattern_container) as ConstraintLayout
+        cs.clone(container)
+        return cs.getConstraint(R.id.pattern_top_guideline).layout.guidePercent
+    }
+
+    private fun getHalfOpenedBouncerHeightRatio(): Float {
+        return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
+    }
+
+    @Test
+    fun withFeatureFlagOn_oldMessage_isHidden() {
+        fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+
+        mKeyguardPatternViewController.onViewAttached()
+
+        verify<KeyguardMessageAreaController<*>>(mKeyguardMessageAreaController).disable()
+    }
+
+    @Test
+    fun onPause_resetsText() {
+        mKeyguardPatternViewController.init()
+        mKeyguardPatternViewController.onPause()
+        verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
+    }
+
+    @Test
+    fun testOnViewAttached() {
+        reset(mKeyguardMessageAreaController)
+        reset(mLockPatternUtils)
+        mKeyguardPatternViewController.onViewAttached()
+        verify(mKeyguardMessageAreaController)
+            .setMessage(context.resources.getString(R.string.keyguard_enter_your_pattern), false)
+        verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
+    }
+
+    @Test
+    fun testOnViewAttached_withExistingMessage() {
+        reset(mKeyguardMessageAreaController)
+        `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+        mKeyguardPatternViewController.onViewAttached()
+        verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index cf86c21..efe1955 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -100,6 +100,8 @@
                 .thenReturn(mDeleteButton);
         when(mPinBasedInputView.findViewById(R.id.key_enter))
                 .thenReturn(mOkButton);
+
+        when(mPinBasedInputView.getResources()).thenReturn(getContext().getResources());
         FakeFeatureFlags featureFlags = new FakeFeatureFlags();
         featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index d3b4190..80fd721 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -19,6 +19,8 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
@@ -30,12 +32,18 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
+import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+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.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.any
@@ -47,7 +55,10 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class KeyguardPinViewControllerTest : SysuiTestCase() {
-    @Mock private lateinit var keyguardPinView: KeyguardPINView
+
+    private lateinit var objectKeyguardPINView: KeyguardPINView
+
+    @Mock private lateinit var mockKeyguardPinView: KeyguardPINView
 
     @Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea
 
@@ -79,64 +90,131 @@
     @Mock lateinit var deleteButton: NumPadButton
     @Mock lateinit var enterButton: View
 
-    lateinit var pinViewController: KeyguardPinViewController
+    @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        Mockito.`when`(keyguardPinView.requireViewById<View>(R.id.bouncer_message_area))
+        Mockito.`when`(mockKeyguardPinView.requireViewById<View>(R.id.bouncer_message_area))
             .thenReturn(keyguardMessageArea)
         Mockito.`when`(
                 keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java))
             )
             .thenReturn(keyguardMessageAreaController)
-        `when`(keyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry)
-        `when`(keyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry))
+        `when`(mockKeyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry)
+        `when`(mockKeyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry))
             .thenReturn(passwordTextView)
-        `when`(keyguardPinView.resources).thenReturn(context.resources)
-        `when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
+        `when`(mockKeyguardPinView.resources).thenReturn(context.resources)
+        `when`(mockKeyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
             .thenReturn(deleteButton)
-        `when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
-        pinViewController =
-            KeyguardPinViewController(
-                keyguardPinView,
-                keyguardUpdateMonitor,
-                securityMode,
-                lockPatternUtils,
-                mKeyguardSecurityCallback,
-                keyguardMessageAreaControllerFactory,
-                mLatencyTracker,
-                liftToActivateListener,
-                mEmergencyButtonController,
-                falsingCollector,
-                postureController,
-                featureFlags
-            )
+        `when`(mockKeyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
+        // For posture tests:
+        `when`(mockKeyguardPinView.buttons).thenReturn(arrayOf())
+        `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
+
+        objectKeyguardPINView =
+            View.inflate(mContext, R.layout.keyguard_pin_view, null)
+                .requireViewById(R.id.keyguard_pin_view) as KeyguardPINView
+    }
+
+    private fun constructPinViewController(
+        mKeyguardPinView: KeyguardPINView
+    ): KeyguardPinViewController {
+        return KeyguardPinViewController(
+            mKeyguardPinView,
+            keyguardUpdateMonitor,
+            securityMode,
+            lockPatternUtils,
+            mKeyguardSecurityCallback,
+            keyguardMessageAreaControllerFactory,
+            mLatencyTracker,
+            liftToActivateListener,
+            mEmergencyButtonController,
+            falsingCollector,
+            postureController,
+            featureFlags
+        )
     }
 
     @Test
-    fun startAppearAnimation() {
-        pinViewController.startAppearAnimation()
+    fun onViewAttached_deviceHalfFolded_propagatedToPatternView() {
+        val pinViewController = constructPinViewController(objectKeyguardPINView)
+        overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+        whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+
+        pinViewController.onViewAttached()
+
+        assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
+    }
+
+    @Test
+    fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() {
+        val pinViewController = constructPinViewController(objectKeyguardPINView)
+        overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+
+        whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+        pinViewController.onViewAttached()
+
+        // Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
+        assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
+
+        // Simulate posture change to state DEVICE_POSTURE_OPENED with callback
+        verify(postureController).addCallback(postureCallbackCaptor.capture())
+        val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value
+        postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        // Verify view is now in posture state DEVICE_POSTURE_OPENED
+        assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+
+        // Simulate posture change to same state with callback
+        postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+        // Verify view is still in posture state DEVICE_POSTURE_OPENED
+        assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+    }
+
+    private fun getPinTopGuideline(): Float {
+        val cs = ConstraintSet()
+        val container = objectKeyguardPINView.requireViewById(R.id.pin_container) as ConstraintLayout
+        cs.clone(container)
+        return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent
+    }
+
+    private fun getHalfOpenedBouncerHeightRatio(): Float {
+        return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
+    }
+
+    @Test
+    fun testOnViewAttached() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
+
+        pinViewController.onViewAttached()
+
         verify(keyguardMessageAreaController)
             .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
     }
 
     @Test
-    fun startAppearAnimation_withExistingMessage() {
+    fun testOnViewAttached_withExistingMessage() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
         Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
-        pinViewController.startAppearAnimation()
+
+        pinViewController.onViewAttached()
+
         verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean())
     }
 
     @Test
-    fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
+    fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
         `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
         `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
         `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
         `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3)
         `when`(passwordTextView.text).thenReturn("")
 
-        pinViewController.startAppearAnimation()
+        pinViewController.onViewAttached()
+
         verify(deleteButton).visibility = View.INVISIBLE
         verify(enterButton).visibility = View.INVISIBLE
         verify(passwordTextView).setUsePinShapes(true)
@@ -144,14 +222,16 @@
     }
 
     @Test
-    fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
+    fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
         `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
         `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
         `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
         `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6)
         `when`(passwordTextView.text).thenReturn("")
 
-        pinViewController.startAppearAnimation()
+        pinViewController.onViewAttached()
+
         verify(deleteButton).visibility = View.VISIBLE
         verify(enterButton).visibility = View.VISIBLE
         verify(passwordTextView).setUsePinShapes(true)
@@ -160,7 +240,10 @@
 
     @Test
     fun handleLockout_readsNumberOfErrorAttempts() {
+        val pinViewController = constructPinViewController(mockKeyguardPinView)
+
         pinViewController.handleAttemptLockout(0)
+
         verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
deleted file mode 100644
index 95daf19..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ /dev/null
@@ -1,716 +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 static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
-import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.hardware.biometrics.BiometricOverlayConstants;
-import android.media.AudioManager;
-import android.telephony.TelephonyManager;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableResources;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.WindowInsetsController;
-import android.widget.FrameLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
-import com.android.systemui.biometrics.SideFpsController;
-import com.android.systemui.biometrics.SideFpsUiRequestSource;
-import com.android.systemui.classifier.FalsingA11yDelegate;
-import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.log.SessionTracker;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.util.settings.GlobalSettings;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper()
-public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
-    private static final int TARGET_USER_ID = 100;
-    @Rule
-    public MockitoRule mRule = MockitoJUnit.rule();
-    @Mock
-    private KeyguardSecurityContainer mView;
-    @Mock
-    private AdminSecondaryLockScreenController.Factory mAdminSecondaryLockScreenControllerFactory;
-    @Mock
-    private AdminSecondaryLockScreenController mAdminSecondaryLockScreenController;
-    @Mock
-    private LockPatternUtils mLockPatternUtils;
-    @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
-    private KeyguardSecurityModel mKeyguardSecurityModel;
-    @Mock
-    private MetricsLogger mMetricsLogger;
-    @Mock
-    private UiEventLogger mUiEventLogger;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-    @Mock
-    private KeyguardInputViewController mInputViewController;
-    @Mock
-    private WindowInsetsController mWindowInsetsController;
-    @Mock
-    private KeyguardSecurityViewFlipper mSecurityViewFlipper;
-    @Mock
-    private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
-    @Mock
-    private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory;
-    @Mock
-    private KeyguardMessageAreaController mKeyguardMessageAreaController;
-    @Mock
-    private BouncerKeyguardMessageArea mKeyguardMessageArea;
-    @Mock
-    private ConfigurationController mConfigurationController;
-    @Mock
-    private EmergencyButtonController mEmergencyButtonController;
-    @Mock
-    private FalsingCollector mFalsingCollector;
-    @Mock
-    private FalsingManager mFalsingManager;
-    @Mock
-    private GlobalSettings mGlobalSettings;
-    @Mock
-    private FeatureFlags mFeatureFlags;
-    @Mock
-    private UserSwitcherController mUserSwitcherController;
-    @Mock
-    private SessionTracker mSessionTracker;
-    @Mock
-    private KeyguardViewController mKeyguardViewController;
-    @Mock
-    private SideFpsController mSideFpsController;
-    @Mock
-    private KeyguardPasswordViewController mKeyguardPasswordViewControllerMock;
-    @Mock
-    private FalsingA11yDelegate mFalsingA11yDelegate;
-    @Mock
-    private TelephonyManager mTelephonyManager;
-    @Mock
-    private ViewMediatorCallback mViewMediatorCallback;
-    @Mock
-    private AudioManager mAudioManager;
-    @Mock
-    private FaceAuthAccessibilityDelegate mFaceAuthAccessibilityDelegate;
-
-    @Captor
-    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
-    @Captor
-    private ArgumentCaptor<KeyguardSecurityContainer.SwipeListener> mSwipeListenerArgumentCaptor;
-
-    @Captor
-    private ArgumentCaptor<KeyguardSecurityViewFlipperController.OnViewInflatedCallback>
-            mOnViewInflatedCallbackArgumentCaptor;
-
-    private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
-    private KeyguardPasswordViewController mKeyguardPasswordViewController;
-    private KeyguardPasswordView mKeyguardPasswordView;
-    private TestableResources mTestableResources;
-
-    @Before
-    public void setup() {
-        mTestableResources = mContext.getOrCreateTestableResources();
-        mTestableResources.getResources().getConfiguration().orientation =
-                Configuration.ORIENTATION_UNDEFINED;
-
-        when(mView.getContext()).thenReturn(mContext);
-        when(mView.getResources()).thenReturn(mTestableResources.getResources());
-        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(/* width=  */ 0, /* height= */
-                0);
-        lp.gravity = 0;
-        when(mView.getLayoutParams()).thenReturn(lp);
-        when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
-                .thenReturn(mAdminSecondaryLockScreenController);
-        when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
-        mKeyguardPasswordView = spy((KeyguardPasswordView) LayoutInflater.from(mContext).inflate(
-                R.layout.keyguard_password_view, null));
-        when(mKeyguardPasswordView.getRootView()).thenReturn(mSecurityViewFlipper);
-        when(mKeyguardPasswordView.requireViewById(R.id.bouncer_message_area))
-                .thenReturn(mKeyguardMessageArea);
-        when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class)))
-                .thenReturn(mKeyguardMessageAreaController);
-        when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
-        when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN);
-        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
-        FakeFeatureFlags featureFlags = new FakeFeatureFlags();
-        featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
-
-        mKeyguardPasswordViewController = new KeyguardPasswordViewController(
-                (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
-                SecurityMode.Password, mLockPatternUtils, null,
-                mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController,
-                null, mock(Resources.class), null, mKeyguardViewController,
-                featureFlags);
-
-        mKeyguardSecurityContainerController = new KeyguardSecurityContainerController(
-                mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
-                mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
-                mKeyguardStateController, mKeyguardSecurityViewFlipperController,
-                mConfigurationController, mFalsingCollector, mFalsingManager,
-                mUserSwitcherController, mFeatureFlags, mGlobalSettings,
-                mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate,
-                mTelephonyManager, mViewMediatorCallback, mAudioManager,
-                mock(KeyguardFaceAuthInteractor.class),
-                mFaceAuthAccessibilityDelegate);
-    }
-
-    @Test
-    public void onInitConfiguresViewMode() {
-        mKeyguardSecurityContainerController.onInit();
-        verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
-                eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
-                eq(mFalsingA11yDelegate));
-    }
-
-    @Test
-    public void showSecurityScreen_canInflateAllModes() {
-        SecurityMode[] modes = SecurityMode.values();
-        for (SecurityMode mode : modes) {
-            when(mInputViewController.getSecurityMode()).thenReturn(mode);
-            mKeyguardSecurityContainerController.showSecurityScreen(mode);
-            if (mode == SecurityMode.Invalid) {
-                verify(mKeyguardSecurityViewFlipperController, never()).getSecurityView(
-                        any(SecurityMode.class), any(KeyguardSecurityCallback.class), any(
-                                KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class)
-                );
-            } else {
-                verify(mKeyguardSecurityViewFlipperController).getSecurityView(
-                        eq(mode), any(KeyguardSecurityCallback.class), any(
-                                KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class)
-                );
-            }
-        }
-    }
-
-    @Test
-    public void onResourcesUpdate_callsThroughOnRotationChange() {
-        clearInvocations(mView);
-
-        // Rotation is the same, shouldn't cause an update
-        mKeyguardSecurityContainerController.updateResources();
-        verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
-                eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
-                eq(mFalsingA11yDelegate));
-
-        // Update rotation. Should trigger update
-        mTestableResources.getResources().getConfiguration().orientation =
-                Configuration.ORIENTATION_LANDSCAPE;
-
-        mKeyguardSecurityContainerController.updateResources();
-        verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
-                eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
-                eq(mFalsingA11yDelegate));
-    }
-
-    private void touchDown() {
-        mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent(
-                MotionEvent.obtain(
-                        /* downTime= */0,
-                        /* eventTime= */0,
-                        MotionEvent.ACTION_DOWN,
-                        /* x= */0,
-                        /* y= */0,
-                        /* metaState= */0));
-    }
-
-    @Test
-    public void onInterceptTap_inhibitsFalsingInSidedSecurityMode() {
-
-        when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false);
-        touchDown();
-        verify(mFalsingCollector, never()).avoidGesture();
-
-        when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true);
-        touchDown();
-        verify(mFalsingCollector).avoidGesture();
-    }
-
-    @Test
-    public void showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() {
-        mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, false);
-        setupGetSecurityView(SecurityMode.Pattern);
-
-        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
-        verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
-                eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
-                eq(mFalsingA11yDelegate));
-    }
-
-    @Test
-    public void showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() {
-        mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, true);
-        setupGetSecurityView(SecurityMode.Pattern);
-        verify(mView).initMode(eq(MODE_ONE_HANDED), eq(mGlobalSettings), eq(mFalsingManager),
-                eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
-                eq(mFalsingA11yDelegate));
-    }
-
-    @Test
-    public void showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() {
-        mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, true);
-        setupGetSecurityView(SecurityMode.Password);
-
-        verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
-                eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
-                eq(mFalsingA11yDelegate));
-    }
-
-    @Test
-    public void addUserSwitcherCallback() {
-        ArgumentCaptor<KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback>
-                captor = ArgumentCaptor.forClass(
-                KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class);
-        setupGetSecurityView(SecurityMode.Password);
-
-        verify(mView).initMode(anyInt(), any(GlobalSettings.class), any(FalsingManager.class),
-                any(UserSwitcherController.class),
-                captor.capture(),
-                eq(mFalsingA11yDelegate));
-        captor.getValue().showUnlockToContinueMessage();
-        getViewControllerImmediately();
-        verify(mKeyguardPasswordViewControllerMock).showMessage(
-                /* message= */ getContext().getString(R.string.keyguard_unlock_to_continue),
-                /* colorState= */ null,
-                /* animated= */ true);
-    }
-
-    @Test
-    public void addUserSwitchCallback() {
-        mKeyguardSecurityContainerController.onViewAttached();
-        verify(mUserSwitcherController)
-                .addUserSwitchCallback(any(UserSwitcherController.UserSwitchCallback.class));
-        mKeyguardSecurityContainerController.onViewDetached();
-        verify(mUserSwitcherController)
-                .removeUserSwitchCallback(any(UserSwitcherController.UserSwitchCallback.class));
-    }
-
-    @Test
-    public void onBouncerVisibilityChanged_resetsScale() {
-        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(false);
-        verify(mView).resetScale();
-    }
-
-    @Test
-    public void showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
-        // GIVEN the current security method is SimPin
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
-        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
-
-        // WHEN a request is made from the SimPin screens to show the next security method
-        when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN);
-        mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
-                /* authenticated= */true,
-                TARGET_USER_ID,
-                /* bypassSecondaryLockScreen= */true,
-                SecurityMode.SimPin);
-
-        // THEN the next security method of PIN is set, and the keyguard is not marked as done
-
-        verify(mViewMediatorCallback, never()).keyguardDonePending(anyBoolean(), anyInt());
-        verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt());
-        assertThat(mKeyguardSecurityContainerController.getCurrentSecurityMode())
-                .isEqualTo(SecurityMode.PIN);
-    }
-
-    @Test
-    public void showNextSecurityScreenOrFinish_DeviceNotSecure() {
-        // GIVEN the current security method is SimPin
-        when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
-        when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false);
-        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin);
-
-        // WHEN a request is made from the SimPin screens to show the next security method
-        when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None);
-        mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
-                /* authenticated= */true,
-                TARGET_USER_ID,
-                /* bypassSecondaryLockScreen= */true,
-                SecurityMode.SimPin);
-
-        // THEN the next security method of None will dismiss keyguard.
-        verify(mViewMediatorCallback).keyguardDone(anyBoolean(), anyInt());
-    }
-
-    @Test
-    public void showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() {
-        //GIVEN current security mode has been set to PIN
-        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.PIN);
-
-        //WHEN a request comes from SimPin to dismiss the security screens
-        boolean keyguardDone = mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
-                /* authenticated= */true,
-                TARGET_USER_ID,
-                /* bypassSecondaryLockScreen= */true,
-                SecurityMode.SimPin);
-
-        //THEN no action has happened, which will not dismiss the security screens
-        assertThat(keyguardDone).isEqualTo(false);
-        verify(mKeyguardUpdateMonitor, never()).getUserHasTrust(anyInt());
-    }
-
-    @Test
-    public void onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
-        KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
-                getRegisteredSwipeListener();
-        when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(false);
-        setupGetSecurityView(SecurityMode.Password);
-
-        registeredSwipeListener.onSwipeUp();
-
-        verify(mKeyguardUpdateMonitor).requestFaceAuth(
-                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(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
-    }
-
-    @Test
-    public void onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
-        KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
-                getRegisteredSwipeListener();
-        when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
-                .thenReturn(true);
-        setupGetSecurityView(SecurityMode.Password);
-
-        clearInvocations(mKeyguardSecurityViewFlipperController);
-        registeredSwipeListener.onSwipeUp();
-        getViewControllerImmediately();
-
-        verify(mKeyguardPasswordViewControllerMock).showMessage(/* message= */
-                null, /* colorState= */ null, /* animated= */ true);
-    }
-
-    @Test
-    public void onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
-        KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
-                getRegisteredSwipeListener();
-        when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
-                .thenReturn(false);
-        setupGetSecurityView(SecurityMode.Password);
-
-        registeredSwipeListener.onSwipeUp();
-
-        verify(mKeyguardPasswordViewControllerMock, never()).showMessage(/* message= */
-                null, /* colorState= */ null, /* animated= */ true);
-    }
-
-    @Test
-    public void onDensityOrFontScaleChanged() {
-        ArgumentCaptor<ConfigurationController.ConfigurationListener>
-                configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
-                ConfigurationController.ConfigurationListener.class);
-        mKeyguardSecurityContainerController.onViewAttached();
-        verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
-        clearInvocations(mKeyguardSecurityViewFlipperController);
-
-        configurationListenerArgumentCaptor.getValue().onDensityOrFontScaleChanged();
-
-        verify(mKeyguardSecurityViewFlipperController).clearViews();
-        verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
-                eq(SecurityMode.PIN),
-                any(KeyguardSecurityCallback.class),
-                mOnViewInflatedCallbackArgumentCaptor.capture());
-
-        mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
-
-        verify(mView).onDensityOrFontScaleChanged();
-    }
-
-    @Test
-    public void onThemeChanged() {
-        ArgumentCaptor<ConfigurationController.ConfigurationListener>
-                configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
-                ConfigurationController.ConfigurationListener.class);
-        mKeyguardSecurityContainerController.onViewAttached();
-        verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
-        clearInvocations(mKeyguardSecurityViewFlipperController);
-
-        configurationListenerArgumentCaptor.getValue().onThemeChanged();
-
-        verify(mKeyguardSecurityViewFlipperController).clearViews();
-        verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
-                eq(SecurityMode.PIN),
-                any(KeyguardSecurityCallback.class),
-                mOnViewInflatedCallbackArgumentCaptor.capture());
-
-        mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
-
-        verify(mView).reset();
-        verify(mKeyguardSecurityViewFlipperController).reset();
-        verify(mView).reloadColors();
-    }
-
-    @Test
-    public void onUiModeChanged() {
-        ArgumentCaptor<ConfigurationController.ConfigurationListener>
-                configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
-                ConfigurationController.ConfigurationListener.class);
-        mKeyguardSecurityContainerController.onViewAttached();
-        verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
-        clearInvocations(mKeyguardSecurityViewFlipperController);
-
-        configurationListenerArgumentCaptor.getValue().onUiModeChanged();
-
-        verify(mKeyguardSecurityViewFlipperController).clearViews();
-        verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
-                eq(SecurityMode.PIN),
-                any(KeyguardSecurityCallback.class),
-                mOnViewInflatedCallbackArgumentCaptor.capture());
-
-        mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
-
-        verify(mView).reloadColors();
-    }
-
-    @Test
-    public void testHasDismissActions() {
-        assertFalse("Action not set yet", mKeyguardSecurityContainerController.hasDismissActions());
-        mKeyguardSecurityContainerController.setOnDismissAction(mock(
-                        ActivityStarter.OnDismissAction.class),
-                null /* cancelAction */);
-        assertTrue("Action should exist", mKeyguardSecurityContainerController.hasDismissActions());
-    }
-
-    @Test
-    public void testWillRunDismissFromKeyguardIsTrue() {
-        ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
-        when(action.willRunAnimationOnKeyguard()).thenReturn(true);
-        mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
-
-        mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
-
-        assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isTrue();
-    }
-
-    @Test
-    public void testWillRunDismissFromKeyguardIsFalse() {
-        ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
-        when(action.willRunAnimationOnKeyguard()).thenReturn(false);
-        mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
-
-        mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
-
-        assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
-    }
-
-    @Test
-    public void testWillRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() {
-        mKeyguardSecurityContainerController.setOnDismissAction(null /* action */,
-                null /* cancelAction */);
-
-        mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
-
-        assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
-    }
-
-    @Test
-    public void testOnStartingToHide() {
-        mKeyguardSecurityContainerController.onStartingToHide();
-        verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
-                any(KeyguardSecurityCallback.class),
-                mOnViewInflatedCallbackArgumentCaptor.capture());
-
-        mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController);
-        verify(mInputViewController).onStartingToHide();
-    }
-
-    @Test
-    public void testGravityReappliedOnConfigurationChange() {
-        // Set initial gravity
-        mTestableResources.addOverride(R.integer.keyguard_host_view_gravity,
-                Gravity.CENTER);
-        mTestableResources.addOverride(
-                R.bool.can_use_one_handed_bouncer, false);
-
-        // Kick off the initial pass...
-        mKeyguardSecurityContainerController.onInit();
-        verify(mView).setLayoutParams(any());
-        clearInvocations(mView);
-
-        // Now simulate a config change
-        mTestableResources.addOverride(R.integer.keyguard_host_view_gravity,
-                Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
-
-        mKeyguardSecurityContainerController.updateResources();
-        verify(mView).setLayoutParams(any());
-    }
-
-    @Test
-    public void testGravityUsesOneHandGravityWhenApplicable() {
-        mTestableResources.addOverride(
-                R.integer.keyguard_host_view_gravity,
-                Gravity.CENTER);
-        mTestableResources.addOverride(
-                R.integer.keyguard_host_view_one_handed_gravity,
-                Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
-
-        // Start disabled.
-        mTestableResources.addOverride(
-                R.bool.can_use_one_handed_bouncer, false);
-
-        mKeyguardSecurityContainerController.onInit();
-        verify(mView).setLayoutParams(argThat(
-                (ArgumentMatcher<FrameLayout.LayoutParams>) argument ->
-                        argument.gravity == Gravity.CENTER));
-        clearInvocations(mView);
-
-        // And enable
-        mTestableResources.addOverride(
-                R.bool.can_use_one_handed_bouncer, true);
-
-        mKeyguardSecurityContainerController.updateResources();
-        verify(mView).setLayoutParams(argThat(
-                (ArgumentMatcher<FrameLayout.LayoutParams>) argument ->
-                        argument.gravity == (Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM)));
-    }
-
-    @Test
-    public void testUpdateKeyguardPositionDelegatesToSecurityContainer() {
-        mKeyguardSecurityContainerController.updateKeyguardPosition(1.0f);
-        verify(mView).updatePositionByTouchX(1.0f);
-    }
-
-    @Test
-    public void testReinflateViewFlipper() {
-        KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback =
-                controller -> {
-                };
-        mKeyguardSecurityContainerController.reinflateViewFlipper(onViewInflatedCallback);
-        verify(mKeyguardSecurityViewFlipperController).clearViews();
-        verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView(
-                any(SecurityMode.class),
-                any(KeyguardSecurityCallback.class), eq(onViewInflatedCallback));
-    }
-
-    @Test
-    public void testSideFpsControllerShow() {
-        mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ true);
-        verify(mSideFpsController).show(
-                SideFpsUiRequestSource.PRIMARY_BOUNCER,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
-    }
-
-    @Test
-    public void testSideFpsControllerHide() {
-        mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ false);
-        verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
-    }
-
-    @Test
-    public void setExpansion_setsAlpha() {
-        mKeyguardSecurityContainerController.setExpansion(EXPANSION_VISIBLE);
-
-        verify(mView).setAlpha(1f);
-        verify(mView).setTranslationY(0f);
-    }
-
-    @Test
-    public void setAccessibilityDelegate() {
-        verify(mView).setAccessibilityDelegate(eq(mFaceAuthAccessibilityDelegate));
-    }
-
-    private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
-        mKeyguardSecurityContainerController.onViewAttached();
-        verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
-        return mSwipeListenerArgumentCaptor.getValue();
-    }
-
-    private void setupGetSecurityView(SecurityMode securityMode) {
-        mKeyguardSecurityContainerController.showSecurityScreen(securityMode);
-        getViewControllerImmediately();
-    }
-
-    private void getViewControllerImmediately() {
-        verify(mKeyguardSecurityViewFlipperController, atLeastOnce()).getSecurityView(
-                any(SecurityMode.class), any(),
-                mOnViewInflatedCallbackArgumentCaptor.capture());
-        mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(
-                (KeyguardInputViewController) mKeyguardPasswordViewControllerMock);
-
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
new file mode 100644
index 0000000..a9aed2f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -0,0 +1,925 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.keyguard
+
+import android.content.res.Configuration
+import android.hardware.biometrics.BiometricOverlayConstants
+import android.media.AudioManager
+import android.telephony.TelephonyManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.TestableResources
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.WindowInsetsController
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
+import com.android.systemui.biometrics.SideFpsController
+import com.android.systemui.biometrics.SideFpsUiRequestSource
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.classifier.FalsingA11yDelegate
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.log.SessionTracker
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argThat
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.GlobalSettings
+import com.google.common.truth.Truth
+import java.util.Optional
+import junit.framework.Assert
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatcher
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
+
+    @Mock private lateinit var view: KeyguardSecurityContainer
+    @Mock
+    private lateinit var adminSecondaryLockScreenControllerFactory:
+        AdminSecondaryLockScreenController.Factory
+    @Mock
+    private lateinit var adminSecondaryLockScreenController: AdminSecondaryLockScreenController
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+    @Mock private lateinit var metricsLogger: MetricsLogger
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var inputViewController: KeyguardInputViewController<KeyguardInputView>
+    @Mock private lateinit var windowInsetsController: WindowInsetsController
+    @Mock private lateinit var securityViewFlipper: KeyguardSecurityViewFlipper
+    @Mock private lateinit var viewFlipperController: KeyguardSecurityViewFlipperController
+    @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+    @Mock private lateinit var keyguardMessageAreaController: KeyguardMessageAreaController<*>
+    @Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea
+    @Mock private lateinit var configurationController: ConfigurationController
+    @Mock private lateinit var emergencyButtonController: EmergencyButtonController
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var falsingManager: FalsingManager
+    @Mock private lateinit var globalSettings: GlobalSettings
+    @Mock private lateinit var userSwitcherController: UserSwitcherController
+    @Mock private lateinit var sessionTracker: SessionTracker
+    @Mock private lateinit var keyguardViewController: KeyguardViewController
+    @Mock private lateinit var sideFpsController: SideFpsController
+    @Mock private lateinit var keyguardPasswordViewControllerMock: KeyguardPasswordViewController
+    @Mock private lateinit var falsingA11yDelegate: FalsingA11yDelegate
+    @Mock private lateinit var telephonyManager: TelephonyManager
+    @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
+    @Mock private lateinit var audioManager: AudioManager
+    @Mock private lateinit var userInteractor: UserInteractor
+    @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+
+    @Captor
+    private lateinit var swipeListenerArgumentCaptor:
+        ArgumentCaptor<KeyguardSecurityContainer.SwipeListener>
+    @Captor
+    private lateinit var onViewInflatedCallbackArgumentCaptor:
+        ArgumentCaptor<KeyguardSecurityViewFlipperController.OnViewInflatedCallback>
+
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
+    private lateinit var keyguardPasswordView: KeyguardPasswordView
+    private lateinit var testableResources: TestableResources
+    private lateinit var sceneTestUtils: SceneTestUtils
+    private lateinit var sceneInteractor: SceneInteractor
+    private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    private lateinit var authenticationInteractor: AuthenticationInteractor
+    private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
+
+    private lateinit var underTest: KeyguardSecurityContainerController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        testableResources = mContext.getOrCreateTestableResources()
+        testableResources.resources.configuration.orientation = Configuration.ORIENTATION_UNDEFINED
+        whenever(view.context).thenReturn(mContext)
+        whenever(view.resources).thenReturn(testableResources.resources)
+
+        val lp = FrameLayout.LayoutParams(/* width=  */ 0, /* height= */ 0)
+        lp.gravity = 0
+        whenever(view.layoutParams).thenReturn(lp)
+
+        whenever(adminSecondaryLockScreenControllerFactory.create(any()))
+            .thenReturn(adminSecondaryLockScreenController)
+        whenever(securityViewFlipper.windowInsetsController).thenReturn(windowInsetsController)
+        keyguardPasswordView =
+            spy(
+                LayoutInflater.from(mContext).inflate(R.layout.keyguard_password_view, null)
+                    as KeyguardPasswordView
+            )
+        whenever(keyguardPasswordView.rootView).thenReturn(securityViewFlipper)
+        whenever<Any?>(keyguardPasswordView.requireViewById(R.id.bouncer_message_area))
+            .thenReturn(keyguardMessageArea)
+        whenever(messageAreaControllerFactory.create(any()))
+            .thenReturn(keyguardMessageAreaController)
+        whenever(keyguardPasswordView.windowInsetsController).thenReturn(windowInsetsController)
+        whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN)
+        whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
+        whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true)
+
+        featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+        featureFlags.set(Flags.SCENE_CONTAINER, false)
+        featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
+        featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
+
+        keyguardPasswordViewController =
+            KeyguardPasswordViewController(
+                keyguardPasswordView,
+                keyguardUpdateMonitor,
+                SecurityMode.Password,
+                lockPatternUtils,
+                null,
+                messageAreaControllerFactory,
+                null,
+                null,
+                emergencyButtonController,
+                null,
+                mock(),
+                null,
+                keyguardViewController,
+                featureFlags
+            )
+
+        whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
+        sceneTestUtils = SceneTestUtils(this)
+        sceneInteractor = sceneTestUtils.sceneInteractor()
+        keyguardTransitionInteractor =
+            KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope)
+                .keyguardTransitionInteractor
+        sceneTransitionStateFlow =
+            MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
+        sceneInteractor.setTransitionState(sceneTransitionStateFlow)
+        authenticationInteractor =
+            sceneTestUtils.authenticationInteractor(
+                repository = sceneTestUtils.authenticationRepository(),
+                sceneInteractor = sceneInteractor
+            )
+
+        underTest =
+            KeyguardSecurityContainerController(
+                view,
+                adminSecondaryLockScreenControllerFactory,
+                lockPatternUtils,
+                keyguardUpdateMonitor,
+                keyguardSecurityModel,
+                metricsLogger,
+                uiEventLogger,
+                keyguardStateController,
+                viewFlipperController,
+                configurationController,
+                falsingCollector,
+                falsingManager,
+                userSwitcherController,
+                featureFlags,
+                globalSettings,
+                sessionTracker,
+                Optional.of(sideFpsController),
+                falsingA11yDelegate,
+                telephonyManager,
+                viewMediatorCallback,
+                audioManager,
+                mock(),
+                mock(),
+                { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
+                userInteractor,
+                deviceProvisionedController,
+                faceAuthAccessibilityDelegate,
+                keyguardTransitionInteractor,
+                { authenticationInteractor },
+            )
+    }
+
+    @Test
+    fun onInitConfiguresViewMode() {
+        underTest.onInit()
+        verify(view)
+            .initMode(
+                eq(KeyguardSecurityContainer.MODE_DEFAULT),
+                eq(globalSettings),
+                eq(falsingManager),
+                eq(userSwitcherController),
+                any(),
+                eq(falsingA11yDelegate)
+            )
+    }
+
+    @Test
+    fun setAccessibilityDelegate() {
+        verify(view).accessibilityDelegate = eq(faceAuthAccessibilityDelegate)
+    }
+
+    @Test
+    fun showSecurityScreen_canInflateAllModes() {
+        val modes = SecurityMode.values()
+        for (mode in modes) {
+            whenever(inputViewController.securityMode).thenReturn(mode)
+            underTest.showSecurityScreen(mode)
+            if (mode == SecurityMode.Invalid) {
+                verify(viewFlipperController, never()).getSecurityView(any(), any(), any())
+            } else {
+                verify(viewFlipperController).getSecurityView(eq(mode), any(), any())
+            }
+        }
+    }
+
+    @Test
+    fun onResourcesUpdate_callsThroughOnRotationChange() {
+        clearInvocations(view)
+
+        // Rotation is the same, shouldn't cause an update
+        underTest.updateResources()
+        verify(view, never())
+            .initMode(
+                eq(KeyguardSecurityContainer.MODE_DEFAULT),
+                eq(globalSettings),
+                eq(falsingManager),
+                eq(userSwitcherController),
+                any(),
+                eq(falsingA11yDelegate)
+            )
+
+        // Update rotation. Should trigger update
+        testableResources.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+        underTest.updateResources()
+        verify(view)
+            .initMode(
+                eq(KeyguardSecurityContainer.MODE_DEFAULT),
+                eq(globalSettings),
+                eq(falsingManager),
+                eq(userSwitcherController),
+                any(),
+                eq(falsingA11yDelegate)
+            )
+    }
+
+    private fun touchDown() {
+        underTest.mGlobalTouchListener.onTouchEvent(
+            MotionEvent.obtain(
+                /* downTime= */ 0,
+                /* eventTime= */ 0,
+                MotionEvent.ACTION_DOWN,
+                /* x= */ 0f,
+                /* y= */ 0f,
+                /* metaState= */ 0
+            )
+        )
+    }
+
+    @Test
+    fun onInterceptTap_inhibitsFalsingInSidedSecurityMode() {
+        whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false)
+        touchDown()
+        verify(falsingCollector, never()).avoidGesture()
+        whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true)
+        touchDown()
+        verify(falsingCollector).avoidGesture()
+    }
+
+    @Test
+    fun showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() {
+        testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
+        setupGetSecurityView(SecurityMode.Pattern)
+        underTest.showSecurityScreen(SecurityMode.Pattern)
+        verify(view)
+            .initMode(
+                eq(KeyguardSecurityContainer.MODE_DEFAULT),
+                eq(globalSettings),
+                eq(falsingManager),
+                eq(userSwitcherController),
+                any(),
+                eq(falsingA11yDelegate)
+            )
+    }
+
+    @Test
+    fun showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() {
+        testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+        setupGetSecurityView(SecurityMode.Pattern)
+        verify(view)
+            .initMode(
+                eq(KeyguardSecurityContainer.MODE_ONE_HANDED),
+                eq(globalSettings),
+                eq(falsingManager),
+                eq(userSwitcherController),
+                any(),
+                eq(falsingA11yDelegate)
+            )
+    }
+
+    @Test
+    fun showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() {
+        testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+        setupGetSecurityView(SecurityMode.Password)
+        verify(view)
+            .initMode(
+                eq(KeyguardSecurityContainer.MODE_DEFAULT),
+                eq(globalSettings),
+                eq(falsingManager),
+                eq(userSwitcherController),
+                any(),
+                eq(falsingA11yDelegate)
+            )
+    }
+
+    @Test
+    fun addUserSwitcherCallback() {
+        val captor = ArgumentCaptor.forClass(UserSwitcherCallback::class.java)
+        setupGetSecurityView(SecurityMode.Password)
+        verify(view)
+            .initMode(anyInt(), any(), any(), any(), captor.capture(), eq(falsingA11yDelegate))
+        captor.value.showUnlockToContinueMessage()
+        viewControllerImmediately
+        verify(keyguardPasswordViewControllerMock)
+            .showMessage(
+                /* message= */ context.getString(R.string.keyguard_unlock_to_continue),
+                /* colorState= */ null,
+                /* animated= */ true
+            )
+    }
+
+    @Test
+    fun addUserSwitchCallback() {
+        underTest.onViewAttached()
+        verify(userSwitcherController).addUserSwitchCallback(any())
+        underTest.onViewDetached()
+        verify(userSwitcherController).removeUserSwitchCallback(any())
+    }
+
+    @Test
+    fun onBouncerVisibilityChanged_resetsScale() {
+        underTest.onBouncerVisibilityChanged(false)
+        verify(view).resetScale()
+    }
+
+    @Test
+    fun showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
+        // GIVEN the current security method is SimPin
+        whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+            .thenReturn(false)
+        underTest.showSecurityScreen(SecurityMode.SimPin)
+
+        // WHEN a request is made from the SimPin screens to show the next security method
+        whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN)
+        underTest.showNextSecurityScreenOrFinish(
+            /* authenticated= */ true,
+            TARGET_USER_ID,
+            /* bypassSecondaryLockScreen= */ true,
+            SecurityMode.SimPin
+        )
+
+        // THEN the next security method of PIN is set, and the keyguard is not marked as done
+        verify(viewMediatorCallback, never()).keyguardDonePending(anyBoolean(), anyInt())
+        verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+        Truth.assertThat(underTest.currentSecurityMode).isEqualTo(SecurityMode.PIN)
+    }
+
+    @Test
+    fun showNextSecurityScreenOrFinish_DeviceNotSecure() {
+        // GIVEN the current security method is SimPin
+        whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+            .thenReturn(false)
+        underTest.showSecurityScreen(SecurityMode.SimPin)
+
+        // WHEN a request is made from the SimPin screens to show the next security method
+        whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+            .thenReturn(SecurityMode.None)
+        whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+        underTest.showNextSecurityScreenOrFinish(
+            /* authenticated= */ true,
+            TARGET_USER_ID,
+            /* bypassSecondaryLockScreen= */ true,
+            SecurityMode.SimPin
+        )
+
+        // THEN the next security method of None will dismiss keyguard.
+        verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+    }
+
+    @Test
+    fun showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() {
+        // GIVEN current security mode has been set to PIN
+        underTest.showSecurityScreen(SecurityMode.PIN)
+
+        // WHEN a request comes from SimPin to dismiss the security screens
+        val keyguardDone =
+            underTest.showNextSecurityScreenOrFinish(
+                /* authenticated= */ true,
+                TARGET_USER_ID,
+                /* bypassSecondaryLockScreen= */ true,
+                SecurityMode.SimPin
+            )
+
+        // THEN no action has happened, which will not dismiss the security screens
+        Truth.assertThat(keyguardDone).isEqualTo(false)
+        verify(keyguardUpdateMonitor, never()).getUserHasTrust(anyInt())
+    }
+
+    @Test
+    fun showNextSecurityScreenOrFinish_SimPin_Swipe() {
+        // GIVEN the current security method is SimPin
+        whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+            .thenReturn(false)
+        underTest.showSecurityScreen(SecurityMode.SimPin)
+
+        // WHEN a request is made from the SimPin screens to show the next security method
+        whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+            .thenReturn(SecurityMode.None)
+        // WHEN security method is SWIPE
+        whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+        underTest.showNextSecurityScreenOrFinish(
+            /* authenticated= */ true,
+            TARGET_USER_ID,
+            /* bypassSecondaryLockScreen= */ true,
+            SecurityMode.SimPin
+        )
+
+        // THEN the next security method of None will dismiss keyguard.
+        verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+    }
+    @Test
+    fun showNextSecurityScreenOrFinish_SimPin_Swipe_userNotSetup() {
+        // GIVEN the current security method is SimPin
+        whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
+        whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
+            .thenReturn(false)
+        underTest.showSecurityScreen(SecurityMode.SimPin)
+
+        // WHEN a request is made from the SimPin screens to show the next security method
+        whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
+            .thenReturn(SecurityMode.None)
+        // WHEN security method is SWIPE
+        whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+        whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(false)
+        underTest.showNextSecurityScreenOrFinish(
+            /* authenticated= */ true,
+            TARGET_USER_ID,
+            /* bypassSecondaryLockScreen= */ true,
+            SecurityMode.SimPin
+        )
+
+        // THEN the next security method of None will dismiss keyguard.
+        verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+    }
+
+    @Test
+    fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
+        val registeredSwipeListener = registeredSwipeListener
+        whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(false)
+        setupGetSecurityView(SecurityMode.Password)
+        registeredSwipeListener.onSwipeUp()
+        verify(keyguardUpdateMonitor).requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
+    }
+
+    @Test
+    fun onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() {
+        val registeredSwipeListener = registeredSwipeListener
+        whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true)
+        registeredSwipeListener.onSwipeUp()
+        verify(keyguardUpdateMonitor, never())
+            .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
+    }
+
+    @Test
+    fun onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
+        val registeredSwipeListener = registeredSwipeListener
+        whenever(
+                keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
+            )
+            .thenReturn(true)
+        setupGetSecurityView(SecurityMode.Password)
+        clearInvocations(viewFlipperController)
+        registeredSwipeListener.onSwipeUp()
+        viewControllerImmediately
+        verify(keyguardPasswordViewControllerMock)
+            .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true)
+    }
+
+    @Test
+    fun onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
+        val registeredSwipeListener = registeredSwipeListener
+        whenever(
+                keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
+            )
+            .thenReturn(false)
+        setupGetSecurityView(SecurityMode.Password)
+        registeredSwipeListener.onSwipeUp()
+        verify(keyguardPasswordViewControllerMock, never())
+            .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true)
+    }
+
+    @Test
+    fun onDensityOrFontScaleChanged() {
+        val configurationListenerArgumentCaptor =
+            ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+        underTest.onViewAttached()
+        verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+        clearInvocations(viewFlipperController)
+        configurationListenerArgumentCaptor.value.onDensityOrFontScaleChanged()
+        verify(viewFlipperController).clearViews()
+        verify(viewFlipperController)
+            .asynchronouslyInflateView(
+                eq(SecurityMode.PIN),
+                any(),
+                onViewInflatedCallbackArgumentCaptor.capture()
+            )
+        onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
+        verify(view).onDensityOrFontScaleChanged()
+    }
+
+    @Test
+    fun onThemeChanged() {
+        val configurationListenerArgumentCaptor =
+            ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+        underTest.onViewAttached()
+        verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+        configurationListenerArgumentCaptor.value.onThemeChanged()
+        verify(view).reloadColors()
+    }
+
+    @Test
+    fun onUiModeChanged() {
+        val configurationListenerArgumentCaptor =
+            ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+        underTest.onViewAttached()
+        verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
+        configurationListenerArgumentCaptor.value.onUiModeChanged()
+        verify(view).reloadColors()
+    }
+
+    @Test
+    fun hasDismissActions() {
+        Assert.assertFalse("Action not set yet", underTest.hasDismissActions())
+        underTest.setOnDismissAction(mock(), null /* cancelAction */)
+        Assert.assertTrue("Action should exist", underTest.hasDismissActions())
+    }
+
+    @Test
+    fun willRunDismissFromKeyguardIsTrue() {
+        val action: OnDismissAction = mock()
+        whenever(action.willRunAnimationOnKeyguard()).thenReturn(true)
+        underTest.setOnDismissAction(action, null /* cancelAction */)
+        underTest.finish(false /* strongAuth */, 0 /* currentUser */)
+        Truth.assertThat(underTest.willRunDismissFromKeyguard()).isTrue()
+    }
+
+    @Test
+    fun willRunDismissFromKeyguardIsFalse() {
+        val action: OnDismissAction = mock()
+        whenever(action.willRunAnimationOnKeyguard()).thenReturn(false)
+        underTest.setOnDismissAction(action, null /* cancelAction */)
+        underTest.finish(false /* strongAuth */, 0 /* currentUser */)
+        Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse()
+    }
+
+    @Test
+    fun willRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() {
+        underTest.setOnDismissAction(null /* action */, null /* cancelAction */)
+        underTest.finish(false /* strongAuth */, 0 /* currentUser */)
+        Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse()
+    }
+
+    @Test
+    fun onStartingToHide() {
+        underTest.onStartingToHide()
+        verify(viewFlipperController)
+            .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture())
+        onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
+        verify(inputViewController).onStartingToHide()
+    }
+
+    @Test
+    fun gravityReappliedOnConfigurationChange() {
+        // Set initial gravity
+        testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
+        testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
+
+        // Kick off the initial pass...
+        underTest.onInit()
+        verify(view).layoutParams = any()
+        clearInvocations(view)
+
+        // Now simulate a config change
+        testableResources.addOverride(
+            R.integer.keyguard_host_view_gravity,
+            Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+        )
+        underTest.updateResources()
+        verify(view).layoutParams = any()
+    }
+
+    @Test
+    fun gravityUsesOneHandGravityWhenApplicable() {
+        testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
+        testableResources.addOverride(
+            R.integer.keyguard_host_view_one_handed_gravity,
+            Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+        )
+
+        // Start disabled.
+        testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
+        underTest.onInit()
+        verify(view).layoutParams =
+            argThat(
+                ArgumentMatcher { argument: FrameLayout.LayoutParams ->
+                    argument.gravity == Gravity.CENTER
+                }
+                    as ArgumentMatcher<FrameLayout.LayoutParams>
+            )
+        clearInvocations(view)
+
+        // And enable
+        testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+        underTest.updateResources()
+        verify(view).layoutParams =
+            argThat(
+                ArgumentMatcher { argument: FrameLayout.LayoutParams ->
+                    argument.gravity == Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+                }
+                    as ArgumentMatcher<FrameLayout.LayoutParams>
+            )
+    }
+
+    @Test
+    fun updateKeyguardPositionDelegatesToSecurityContainer() {
+        underTest.updateKeyguardPosition(1.0f)
+        verify(view).updatePositionByTouchX(1.0f)
+    }
+
+    @Test
+    fun reinflateViewFlipper() {
+        val onViewInflatedCallback = KeyguardSecurityViewFlipperController.OnViewInflatedCallback {}
+        underTest.reinflateViewFlipper(onViewInflatedCallback)
+        verify(viewFlipperController).clearViews()
+        verify(viewFlipperController)
+            .asynchronouslyInflateView(any(), any(), eq(onViewInflatedCallback))
+    }
+
+    @Test
+    fun sideFpsControllerShow() {
+        underTest.updateSideFpsVisibility(/* isVisible= */ true)
+        verify(sideFpsController)
+            .show(
+                SideFpsUiRequestSource.PRIMARY_BOUNCER,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+            )
+    }
+
+    @Test
+    fun sideFpsControllerHide() {
+        underTest.updateSideFpsVisibility(/* isVisible= */ false)
+        verify(sideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER)
+    }
+
+    @Test
+    fun setExpansion_setsAlpha() {
+        underTest.setExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
+        verify(view).alpha = 1f
+        verify(view).translationY = 0f
+    }
+
+    @Test
+    fun dismissesKeyguard_whenSceneChangesToGone() =
+        sceneTestUtils.testScope.runTest {
+            featureFlags.set(Flags.SCENE_CONTAINER, true)
+
+            // Upon init, we have never dismisses the keyguard.
+            underTest.onInit()
+            runCurrent()
+            verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+            // Once the view is attached, we start listening but simply going to the bouncer scene
+            // is
+            // not enough to trigger a dismissal of the keyguard.
+            underTest.onViewAttached()
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
+            sceneTransitionStateFlow.value =
+                ObservableTransitionState.Transition(
+                    SceneKey.Lockscreen,
+                    SceneKey.Bouncer,
+                    flowOf(.5f)
+                )
+            runCurrent()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
+            runCurrent()
+            verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+            // While listening, going from the bouncer scene to the gone scene, does dismiss the
+            // keyguard.
+            sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
+            sceneTransitionStateFlow.value =
+                ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f))
+            runCurrent()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
+            runCurrent()
+            verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+
+            // While listening, moving back to the lockscreen scene does not dismiss the keyguard
+            // again.
+            clearInvocations(viewMediatorCallback)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen, null), "reason")
+            sceneTransitionStateFlow.value =
+                ObservableTransitionState.Transition(
+                    SceneKey.Gone,
+                    SceneKey.Lockscreen,
+                    flowOf(.5f)
+                )
+            runCurrent()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason")
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen)
+            runCurrent()
+            verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+            // While listening, moving back to the bouncer scene does not dismiss the keyguard
+            // again.
+            clearInvocations(viewMediatorCallback)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
+            sceneTransitionStateFlow.value =
+                ObservableTransitionState.Transition(
+                    SceneKey.Lockscreen,
+                    SceneKey.Bouncer,
+                    flowOf(.5f)
+                )
+            runCurrent()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
+            runCurrent()
+            verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+            // Detaching the view stops listening, so moving from the bouncer scene to the gone
+            // scene
+            // does not dismiss the keyguard while we're not listening.
+            underTest.onViewDetached()
+            sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
+            sceneTransitionStateFlow.value =
+                ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f))
+            runCurrent()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
+            runCurrent()
+            verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+            // While not listening, moving to the lockscreen does not dismiss the keyguard.
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen, null), "reason")
+            sceneTransitionStateFlow.value =
+                ObservableTransitionState.Transition(
+                    SceneKey.Gone,
+                    SceneKey.Lockscreen,
+                    flowOf(.5f)
+                )
+            runCurrent()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason")
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen)
+            runCurrent()
+            verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+            // While not listening, moving to the bouncer does not dismiss the keyguard.
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
+            sceneTransitionStateFlow.value =
+                ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f))
+            runCurrent()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
+            runCurrent()
+            verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
+
+            // Reattaching the view starts listening again so moving from the bouncer scene to the
+            // gone scene now does dismiss the keyguard again, this time from lockscreen.
+            underTest.onViewAttached()
+            sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
+            sceneTransitionStateFlow.value =
+                ObservableTransitionState.Transition(
+                    SceneKey.Lockscreen,
+                    SceneKey.Gone,
+                    flowOf(.5f)
+                )
+            runCurrent()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
+            runCurrent()
+            verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
+        }
+
+    @Test
+    fun testResetUserSwitcher() {
+        val userSwitcher = mock(View::class.java)
+        whenever(view.findViewById<View>(R.id.keyguard_bouncer_user_switcher))
+            .thenReturn(userSwitcher)
+
+        underTest.prepareToShow()
+        verify(userSwitcher).setAlpha(0f)
+    }
+
+    @Test
+    fun testOnUserSwitched() {
+        val userSwitchCallbackArgumentCaptor =
+            argumentCaptor<UserSwitcherController.UserSwitchCallback>()
+        underTest.onViewAttached()
+        verify(userSwitcherController)
+            .addUserSwitchCallback(capture(userSwitchCallbackArgumentCaptor))
+        userSwitchCallbackArgumentCaptor.value.onUserSwitched()
+        verify(viewFlipperController).asynchronouslyInflateView(any(), any(), any())
+    }
+
+    private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener
+        get() {
+            underTest.onViewAttached()
+            verify(view).setSwipeListener(swipeListenerArgumentCaptor.capture())
+            return swipeListenerArgumentCaptor.value
+        }
+
+    private fun setupGetSecurityView(securityMode: SecurityMode) {
+        underTest.showSecurityScreen(securityMode)
+        viewControllerImmediately
+    }
+
+    private val viewControllerImmediately: Unit
+        get() {
+            verify(viewFlipperController, atLeastOnce())
+                .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture())
+            @Suppress("UNCHECKED_CAST")
+            onViewInflatedCallbackArgumentCaptor.value.onViewInflated(
+                keyguardPasswordViewControllerMock as KeyguardInputViewController<KeyguardInputView>
+            )
+        }
+
+    companion object {
+        private const val TARGET_USER_ID = 100
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index cd18754..64e1458 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -138,6 +138,11 @@
 
     @Test
     public void asynchronouslyInflateView_setNeedsInput() {
+        when(mKeyguardSecurityViewControllerFactory.create(
+               any(), any(SecurityMode.class),
+                any(KeyguardSecurityCallback.class)))
+                .thenReturn(mKeyguardInputViewController);
+
         ArgumentCaptor<AsyncLayoutInflater.OnInflateFinishedListener> argumentCaptor =
                 ArgumentCaptor.forClass(AsyncLayoutInflater.OnInflateFinishedListener.class);
         mKeyguardSecurityViewFlipperController.asynchronouslyInflateView(SecurityMode.PIN,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index a3acc78..291dda25 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -97,6 +97,8 @@
     @Test
     fun onViewAttached() {
         underTest.onViewAttached()
+        verify(keyguardMessageAreaController)
+            .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
     }
 
     @Test
@@ -120,8 +122,6 @@
     @Test
     fun startAppearAnimation() {
         underTest.startAppearAnimation()
-        verify(keyguardMessageAreaController)
-            .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index efcf4dd..626faa6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -98,6 +98,8 @@
         underTest.onViewAttached()
         Mockito.verify(keyguardUpdateMonitor)
             .registerCallback(any(KeyguardUpdateMonitorCallback::class.java))
+        Mockito.verify(keyguardMessageAreaController)
+            .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
     }
 
     @Test
@@ -120,8 +122,6 @@
     @Test
     fun startAppearAnimation() {
         underTest.startAppearAnimation()
-        Mockito.verify(keyguardMessageAreaController)
-            .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
new file mode 100644
index 0000000..ba3dbf0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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 static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.keyguard.logging.KeyguardLogger;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import org.junit.Before;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase {
+
+    @Mock protected KeyguardStatusView mKeyguardStatusView;
+    @Mock protected KeyguardSliceViewController mKeyguardSliceViewController;
+    @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController;
+    @Mock protected KeyguardStateController mKeyguardStateController;
+    @Mock protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock protected ConfigurationController mConfigurationController;
+    @Mock protected DozeParameters mDozeParameters;
+    @Mock protected ScreenOffAnimationController mScreenOffAnimationController;
+    @Mock protected KeyguardLogger mKeyguardLogger;
+    @Mock protected KeyguardStatusViewController mControllerMock;
+    @Mock protected FeatureFlags mFeatureFlags;
+    @Mock protected InteractionJankMonitor mInteractionJankMonitor;
+    @Mock protected ViewTreeObserver mViewTreeObserver;
+    @Mock protected DumpManager mDumpManager;
+    protected FakeKeyguardRepository mFakeKeyguardRepository;
+
+    protected KeyguardStatusViewController mController;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        KeyguardInteractorFactory.WithDependencies deps = KeyguardInteractorFactory.create();
+        mFakeKeyguardRepository = deps.getRepository();
+
+        mController = new KeyguardStatusViewController(
+                mKeyguardStatusView,
+                mKeyguardSliceViewController,
+                mKeyguardClockSwitchController,
+                mKeyguardStateController,
+                mKeyguardUpdateMonitor,
+                mConfigurationController,
+                mDozeParameters,
+                mScreenOffAnimationController,
+                mKeyguardLogger,
+                mFeatureFlags,
+                mInteractionJankMonitor,
+                deps.getKeyguardInteractor(),
+                mDumpManager) {
+                    @Override
+                    void setProperty(
+                            AnimatableProperty property,
+                            float value,
+                            boolean animate) {
+                        // Route into the mock version for verification
+                        mControllerMock.setProperty(property, value, animate);
+                    }
+                };
+
+        when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
+    }
+
+    protected void givenViewAttached() {
+        ArgumentCaptor<View.OnAttachStateChangeListener> captor =
+                ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+        verify(mKeyguardStatusView, atLeast(1)).addOnAttachStateChangeListener(captor.capture());
+
+        for (View.OnAttachStateChangeListener listener : captor.getAllValues()) {
+            listener.onViewAttachedToWindow(mKeyguardStatusView);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 512e5dc..7d23c80 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -23,76 +24,21 @@
 
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.keyguard.logging.KeyguardLogger;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ClockConfig;
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
-import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 
-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
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner.class)
-public class KeyguardStatusViewControllerTest extends SysuiTestCase {
-
-    @Mock private KeyguardStatusView mKeyguardStatusView;
-    @Mock private KeyguardSliceViewController mKeyguardSliceViewController;
-    @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController;
-    @Mock private KeyguardStateController mKeyguardStateController;
-    @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock private ConfigurationController mConfigurationController;
-    @Mock private DozeParameters mDozeParameters;
-    @Mock private ScreenOffAnimationController mScreenOffAnimationController;
-    @Mock private KeyguardLogger mKeyguardLogger;
-    @Mock private KeyguardStatusViewController mControllerMock;
-    @Mock private FeatureFlags mFeatureFlags;
-    @Mock private InteractionJankMonitor mInteractionJankMonitor;
-
-    @Captor
-    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
-
-    private KeyguardStatusViewController mController;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        mController = new KeyguardStatusViewController(
-                mKeyguardStatusView,
-                mKeyguardSliceViewController,
-                mKeyguardClockSwitchController,
-                mKeyguardStateController,
-                mKeyguardUpdateMonitor,
-                mConfigurationController,
-                mDozeParameters,
-                mScreenOffAnimationController,
-                mKeyguardLogger,
-                mFeatureFlags,
-                mInteractionJankMonitor) {
-                    @Override
-                    void setProperty(
-                            AnimatableProperty property,
-                            float value,
-                            boolean animate) {
-                        // Route into the mock version for verification
-                        mControllerMock.setProperty(property, value, animate);
-                    }
-                };
-    }
+public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControllerBaseTest {
 
     @Test
     public void dozeTimeTick_updatesSlice() {
@@ -170,4 +116,12 @@
         verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(false);
         verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(true);
     }
+
+    @Test
+    public void correctlyDump() {
+        mController.onInit();
+        verify(mDumpManager).registerDumpable(eq(mController.getInstanceName()), eq(mController));
+        mController.onDestroy();
+        verify(mDumpManager, times(1)).unregisterDumpable(eq(mController.getInstanceName()));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..2b9797e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import com.android.systemui.keyguard.shared.model.ScreenModel
+import com.android.systemui.keyguard.shared.model.ScreenState
+import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class KeyguardStatusViewControllerWithCoroutinesTest : KeyguardStatusViewControllerBaseTest() {
+
+    @Test
+    fun dozeTimeTickUpdatesSlices() = runTest {
+        mController.startCoroutines(coroutineContext)
+        givenViewAttached()
+        runCurrent()
+        clearInvocations(mKeyguardSliceViewController)
+
+        mFakeKeyguardRepository.dozeTimeTick()
+        runCurrent()
+        verify(mKeyguardSliceViewController).refresh()
+
+        coroutineContext.cancelChildren()
+    }
+
+    @Test
+    fun onScreenTurningOnUpdatesSlices() = runTest {
+        mController.startCoroutines(coroutineContext)
+        givenViewAttached()
+        runCurrent()
+        clearInvocations(mKeyguardSliceViewController)
+
+        mFakeKeyguardRepository.setScreenModel(ScreenModel(ScreenState.SCREEN_ON))
+        runCurrent()
+        verify(mKeyguardSliceViewController, never()).refresh()
+
+        // Should only be called during a 'turning on' event
+        mFakeKeyguardRepository.setScreenModel(ScreenModel(ScreenState.SCREEN_TURNING_ON))
+        runCurrent()
+        verify(mKeyguardSliceViewController).refresh()
+
+        coroutineContext.cancelChildren()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index f8262d4..210f3cb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -21,9 +21,9 @@
 
     private lateinit var keyguardStatusView: KeyguardStatusView
     private val mediaView: View
-        get() = keyguardStatusView.findViewById(R.id.status_view_media_container)
+        get() = keyguardStatusView.requireViewById(R.id.status_view_media_container)
     private val statusViewContainer: ViewGroup
-        get() = keyguardStatusView.findViewById(R.id.status_view_container)
+        get() = keyguardStatusView.requireViewById(R.id.status_view_container)
     private val childrenExcludingMedia
         get() = statusViewContainer.children.filter { it != mediaView }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 1a9ac0b..6f3322a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -17,6 +17,7 @@
 package com.android.keyguard;
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
@@ -38,6 +39,7 @@
 import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
 import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
 import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
+import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS;
 import static com.android.systemui.flags.Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
@@ -65,6 +67,8 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Activity;
+import android.app.ActivityTaskManager;
+import android.app.IActivityTaskManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.IStrongAuthTracker;
 import android.app.trust.TrustManager;
@@ -80,6 +84,7 @@
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
+import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
@@ -117,6 +122,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.text.TextUtils;
 import android.view.Display;
 import android.view.DisplayAdjustments;
 import android.view.DisplayInfo;
@@ -144,6 +150,8 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -180,6 +188,8 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class KeyguardUpdateMonitorTest extends SysuiTestCase {
+    private static final String PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY =
+            "test_app_fp_listen_on_occluding_activity";
     private static final String TEST_CARRIER = "TEST_CARRIER";
     private static final String TEST_CARRIER_2 = "TEST_CARRIER_2";
     private static final int TEST_CARRIER_ID = 1;
@@ -269,6 +279,10 @@
     private UsbPortStatus mUsbPortStatus;
     @Mock
     private Uri mURI;
+    @Mock
+    private TaskStackChangeListeners mTaskStackChangeListeners;
+    @Mock
+    private IActivityTaskManager mActivityTaskManager;
 
     private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
     private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
@@ -285,6 +299,7 @@
     private final Executor mBackgroundExecutor = Runnable::run;
     private final Executor mMainExecutor = Runnable::run;
     private TestableLooper mTestableLooper;
+    private FakeFeatureFlags mFeatureFlags;
     private Handler mHandler;
     private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private MockitoSession mMockitoSession;
@@ -295,7 +310,6 @@
             mFingerprintAuthenticatorsRegisteredCallback;
     private IFaceAuthenticatorsRegisteredCallback mFaceAuthenticatorsRegisteredCallback;
     private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999);
-    private FakeFeatureFlags mFeatureFlags;
     private FakeDisplayTracker mDisplayTracker;
 
     @Before
@@ -334,9 +348,14 @@
                 mDumpManager
         );
 
+        mContext.getOrCreateTestableResources().addOverride(com.android.systemui
+                        .R.array.config_fingerprint_listen_on_occluding_activity_packages,
+                new String[]{ PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY });
+
         mTestableLooper = TestableLooper.get(this);
         allowTestableLooperAsMainThread();
         mFeatureFlags = new FakeFeatureFlags();
+        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
         mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, false);
 
         when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
@@ -1572,6 +1591,50 @@
     }
 
     @Test
+    public void listenForFingerprint_whenOccludingAppPkgOnAllowlist()
+            throws RemoteException {
+        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, true);
+
+        // GIVEN keyguard isn't visible (app occluding)
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
+        when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
+
+        // GIVEN the top activity is from a package that allows fingerprint listening over its
+        // occluding activities
+        setTopStandardActivity(PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY);
+        onTaskStackChanged();
+
+        // THEN we SHOULD listen for non-UDFPS fingerprint
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isEqualTo(true);
+
+        // THEN we should listen for udfps (hiding mechanism to actually auth is
+        // controlled by UdfpsKeyguardViewController)
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(true);
+    }
+
+    @Test
+    public void doNotListenForFingerprint_whenOccludingAppPkgNotOnAllowlist()
+            throws RemoteException {
+        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, true);
+
+        // GIVEN keyguard isn't visible (app occluding)
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
+        when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
+
+        // GIVEN top activity is not in the allowlist for listening to fp over occluding activities
+        setTopStandardActivity("notInAllowList");
+
+        // THEN we should not listen for non-UDFPS fingerprint
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isEqualTo(false);
+
+        // THEN we should listen for udfps (hiding mechanism to actually auth is
+        // controlled by UdfpsKeyguardViewController)
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(true);
+    }
+
+    @Test
     public void testOccludingAppFingerprintListeningState() {
         // GIVEN keyguard isn't visible (app occluding)
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -2963,6 +3026,24 @@
     }
 
     @Test
+    public void onAuthEnrollmentChangesCallbacksAreNotified() {
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        ArgumentCaptor<AuthController.Callback> authCallback = ArgumentCaptor.forClass(
+                AuthController.Callback.class);
+        verify(mAuthController).addCallback(authCallback.capture());
+
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        authCallback.getValue().onEnrollmentsChanged(TYPE_FINGERPRINT);
+        mTestableLooper.processAllMessages();
+        verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FINGERPRINT);
+
+        authCallback.getValue().onEnrollmentsChanged(BiometricAuthenticator.TYPE_FACE);
+        mTestableLooper.processAllMessages();
+        verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FACE);
+    }
+
+    @Test
     public void stopFaceAuthOnDisplayOffFlagNotEnabled_doNotRegisterForDisplayCallback() {
         assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(0);
     }
@@ -3357,6 +3438,23 @@
                 BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE);
     }
 
+    private void setTopStandardActivity(String pkgName) throws RemoteException {
+        final ActivityTaskManager.RootTaskInfo taskInfo = new ActivityTaskManager.RootTaskInfo();
+        taskInfo.visible = true;
+        taskInfo.topActivity = TextUtils.isEmpty(pkgName)
+                ? null : new ComponentName(pkgName, "testClass");
+        when(mActivityTaskManager.getRootTaskInfo(anyInt(), eq(ACTIVITY_TYPE_STANDARD)))
+                .thenReturn(taskInfo);
+    }
+
+    private void onTaskStackChanged() {
+        ArgumentCaptor<TaskStackChangeListener> taskStackChangeListenerCaptor =
+                ArgumentCaptor.forClass(TaskStackChangeListener.class);
+        verify(mTaskStackChangeListeners).registerTaskStackListener(
+                taskStackChangeListenerCaptor.capture());
+        taskStackChangeListenerCaptor.getValue().onTaskStackChangedBackground();
+    }
+
     private class TestableKeyguardUpdateMonitor extends KeyguardUpdateMonitor {
         AtomicBoolean mSimStateChanged = new AtomicBoolean(false);
         AtomicInteger mCachedSimState = new AtomicInteger(-1);
@@ -3375,7 +3473,7 @@
                     mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
                     mFaceWakeUpTriggersConfig, mDevicePostureController,
                     Optional.of(mInteractiveToAuthProvider), mFeatureFlags,
-                    mDisplayTracker);
+                    mTaskStackChangeListeners, mActivityTaskManager, mDisplayTracker);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index 403bd8c..b18137c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -18,6 +18,9 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
+import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
+import static com.android.systemui.flags.Flags.MIGRATE_LOCK_ICON;
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
@@ -40,18 +43,15 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.doze.util.BurnInHelperKt;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.flags.FakeFeatureFlags;
 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.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -92,13 +92,11 @@
     protected @Mock ConfigurationController mConfigurationController;
     protected @Mock VibratorHelper mVibrator;
     protected @Mock AuthRippleController mAuthRippleController;
-    protected @Mock FeatureFlags mFeatureFlags;
     protected @Mock KeyguardTransitionRepository mTransitionRepository;
-    protected @Mock CommandQueue mCommandQueue;
     protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
+    protected FakeFeatureFlags mFeatureFlags;
     protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
 
-
     protected LockIconViewController mUnderTest;
 
     // Capture listeners so that they can be used to send events
@@ -147,6 +145,10 @@
         when(mStatusBarStateController.isDozing()).thenReturn(false);
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
 
+        mFeatureFlags = new FakeFeatureFlags();
+        mFeatureFlags.set(FACE_AUTH_REFACTOR, false);
+        mFeatureFlags.set(MIGRATE_LOCK_ICON, false);
+        mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
         mUnderTest = new LockIconViewController(
                 mLockIconView,
                 mStatusBarStateController,
@@ -162,14 +164,10 @@
                 mVibrator,
                 mAuthRippleController,
                 mResources,
-                new KeyguardTransitionInteractor(mTransitionRepository,
-                        TestScopeProvider.getTestScope().getBackgroundScope()),
-                new KeyguardInteractor(
-                        new FakeKeyguardRepository(),
-                        mCommandQueue,
-                        mFeatureFlags,
-                        new FakeKeyguardBouncerRepository()
-                ),
+                KeyguardTransitionInteractorFactory.create(
+                        TestScopeProvider.getTestScope().getBackgroundScope(),
+                                mTransitionRepository).getKeyguardTransitionInteractor(),
+                KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
                 mFeatureFlags,
                 mPrimaryBouncerInteractor
         );
@@ -231,7 +229,7 @@
     }
 
     protected void init(boolean useMigrationFlag) {
-        when(mFeatureFlags.isEnabled(DOZING_MIGRATION_1)).thenReturn(useMigrationFlag);
+        mFeatureFlags.set(DOZING_MIGRATION_1, useMigrationFlag);
         mUnderTest.init();
 
         verify(mLockIconView, atLeast(1)).addOnAttachStateChangeListener(mAttachCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index ed6a891..45021ba 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -20,7 +20,9 @@
 
 import static com.android.keyguard.LockIconView.ICON_LOCK;
 import static com.android.keyguard.LockIconView.ICON_UNLOCK;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
@@ -33,11 +35,13 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Pair;
+import android.view.HapticFeedbackConstants;
 import android.view.View;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.settingslib.udfps.UdfpsOverlayParams;
+import com.android.systemui.biometrics.UdfpsController;
 import com.android.systemui.doze.util.BurnInHelperKt;
 
 import org.junit.Test;
@@ -339,4 +343,59 @@
         // THEN the lock icon is shown
         verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
     }
+
+    @Test
+    public void playHaptic_onTouchExploration_NoOneWayHaptics_usesVibrate() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+
+        // WHEN request to vibrate on touch exploration
+        mUnderTest.vibrateOnTouchExploration();
+
+        // THEN vibrates
+        verify(mVibrator).vibrate(
+                anyInt(),
+                any(),
+                eq(UdfpsController.EFFECT_CLICK),
+                eq("lock-icon-down"),
+                any());
+    }
+
+    @Test
+    public void playHaptic_onTouchExploration_withOneWayHaptics_performHapticFeedback() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+
+        // WHEN request to vibrate on touch exploration
+        mUnderTest.vibrateOnTouchExploration();
+
+        // THEN performHapticFeedback is used
+        verify(mVibrator).performHapticFeedback(any(), eq(HapticFeedbackConstants.CONTEXT_CLICK));
+    }
+
+    @Test
+    public void playHaptic_onLongPress_NoOneWayHaptics_usesVibrate() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+
+        // WHEN request to vibrate on long press
+        mUnderTest.vibrateOnLongPress();
+
+        // THEN uses vibrate
+        verify(mVibrator).vibrate(
+                anyInt(),
+                any(),
+                eq(UdfpsController.EFFECT_CLICK),
+                eq("lock-screen-lock-icon-longpress"),
+                any());
+    }
+
+    @Test
+    public void playHaptic_onLongPress_withOneWayHaptics_performHapticFeedback() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+
+        // WHEN request to vibrate on long press
+        mUnderTest.vibrateOnLongPress();
+
+        // THEN uses perform haptic feedback
+        verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS));
+
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt
index d2c54b4..c372f45 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt
@@ -17,9 +17,11 @@
 package com.android.keyguard
 
 import android.testing.AndroidTestingRunner
+import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.keyguard.LockIconView.ICON_LOCK
 import com.android.systemui.doze.util.getBurnInOffset
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
 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.TransitionState.FINISHED
@@ -117,6 +119,33 @@
             verify(mLockIconView).setTranslationX(0f)
         }
 
+    @Test
+    fun testHideLockIconView_onLockscreenHostedDreamStateChanged() =
+        runBlocking(IMMEDIATE) {
+            // GIVEN starting state for the lock icon (keyguard) and wallpaper dream enabled
+            mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, true)
+            setupShowLockIcon()
+            init(/* useMigrationFlag= */ true)
+            reset(mLockIconView)
+
+            // WHEN dream starts
+            mUnderTest.mIsActiveDreamLockscreenHostedCallback.accept(
+                true /* isActiveDreamLockscreenHosted */
+            )
+
+            // THEN the lock icon is hidden
+            verify(mLockIconView).visibility = View.INVISIBLE
+            reset(mLockIconView)
+
+            // WHEN the device is no longer dreaming
+            mUnderTest.mIsActiveDreamLockscreenHostedCallback.accept(
+                false /* isActiveDreamLockscreenHosted */
+            )
+
+            // THEN lock icon is visible
+            verify(mLockIconView).visibility = View.VISIBLE
+        }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
index 9fcb9c8..7c2550f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
@@ -47,10 +47,10 @@
 
     @Test
     fun testOnLayout() {
-        underTest.onLayout(100)
+        underTest.onLayout(100, 100)
         verify(background).cornerRadius = 50f
         reset(background)
-        underTest.onLayout(100)
+        underTest.onLayout(100, 100)
         verify(background, never()).cornerRadius = anyFloat()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
index 6fe8892..9f9b9a4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
@@ -19,9 +19,11 @@
 import android.testing.AndroidTestingRunner
 import android.transition.TransitionValues
 import android.view.View
+import android.view.ViewGroup
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardStatusViewController.SplitShadeTransitionAdapter
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -84,5 +86,5 @@
     startValues: TransitionValues?,
     endValues: TransitionValues?
 ): Animator? {
-    return createAnimator(/* sceneRoot= */ null, startValues, endValues)
+    return createAnimator(/* sceneRoot= */ mock(), startValues, endValues)
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/AnalogClockControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/AnalogClockControllerTest.java
deleted file mode 100644
index ef3af8a..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/AnalogClockControllerTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-
-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
-public final class AnalogClockControllerTest extends SysuiTestCase {
-
-    private AnalogClockController mClockController;
-    @Mock SysuiColorExtractor mMockColorExtractor;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        Resources res = getContext().getResources();
-        LayoutInflater layoutInflater = LayoutInflater.from(getContext());
-        mClockController = new AnalogClockController(res, layoutInflater,
-                mMockColorExtractor);
-    }
-
-    @Test
-    public void setDarkAmount_AOD() {
-        ViewGroup smallClockFrame = (ViewGroup) mClockController.getView();
-        View smallClock = smallClockFrame.getChildAt(0);
-        // WHEN dark amount is set to AOD
-        mClockController.setDarkAmount(1f);
-        // THEN small clock should be shown.
-        assertThat(smallClock.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    public void setColorPalette_setDigitalClock() {
-        ViewGroup smallClock = (ViewGroup) mClockController.getView();
-        // WHEN color palette is set
-        mClockController.setColorPalette(true, new int[]{Color.RED});
-        // THEN child of small clock should have text color set.
-        TextView digitalClock = (TextView) smallClock.getChildAt(0);
-        assertThat(digitalClock.getCurrentTextColor()).isEqualTo(Color.RED);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java
deleted file mode 100644
index b56986e..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-
-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
-public final class BubbleClockControllerTest extends SysuiTestCase {
-
-    private BubbleClockController mClockController;
-    @Mock SysuiColorExtractor mMockColorExtractor;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        Resources res = getContext().getResources();
-        LayoutInflater layoutInflater = LayoutInflater.from(getContext());
-        mClockController = new BubbleClockController(res, layoutInflater, mMockColorExtractor);
-    }
-
-    @Test
-    public void setDarkAmount_AOD() {
-        ViewGroup smallClockFrame = (ViewGroup) mClockController.getView();
-        View smallClock = smallClockFrame.getChildAt(0);
-        // WHEN dark amount is set to AOD
-        mClockController.setDarkAmount(1f);
-        // THEN small clock should not be shown.
-        assertThat(smallClock.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    public void setColorPalette_setDigitalClock() {
-        ViewGroup smallClock = (ViewGroup) mClockController.getView();
-        // WHEN text color is set
-        mClockController.setColorPalette(true, new int[]{Color.RED});
-        // THEN child of small clock should have text color set.
-        TextView digitalClock = (TextView) smallClock.getChildAt(0);
-        assertThat(digitalClock.getCurrentTextColor()).isEqualTo(Color.RED);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockInfoTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockInfoTest.java
deleted file mode 100644
index 4c0890a..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockInfoTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.verify;
-
-import android.graphics.Bitmap;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.function.Supplier;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public final class ClockInfoTest extends SysuiTestCase {
-
-    @Mock
-    private Supplier<Bitmap> mMockSupplier;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void testGetName() {
-        final String name = "name";
-        ClockInfo info = ClockInfo.builder().setName(name).build();
-        assertThat(info.getName()).isEqualTo(name);
-    }
-
-    @Test
-    public void testGetTitle() {
-        final String title = "title";
-        ClockInfo info = ClockInfo.builder().setTitle(() -> title).build();
-        assertThat(info.getTitle()).isEqualTo(title);
-    }
-
-    @Test
-    public void testGetId() {
-        final String id = "id";
-        ClockInfo info = ClockInfo.builder().setId(id).build();
-        assertThat(info.getId()).isEqualTo(id);
-    }
-
-    @Test
-    public void testGetThumbnail() {
-        ClockInfo info = ClockInfo.builder().setThumbnail(mMockSupplier).build();
-        info.getThumbnail();
-        verify(mMockSupplier).get();
-    }
-
-    @Test
-    public void testGetPreview() {
-        ClockInfo info = ClockInfo.builder().setPreview(mMockSupplier).build();
-        info.getPreview();
-        verify(mMockSupplier).get();
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
deleted file mode 100644
index 7a5b772..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.LayoutInflater;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.dock.DockManagerFake;
-import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.After;
-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;
-
-import java.util.Arrays;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-// Need to run tests on main looper to allow for onClockChanged operation to happen synchronously.
-@RunWithLooper(setAsMainLooper = true)
-public final class ClockManagerTest extends SysuiTestCase {
-
-    private static final String BUBBLE_CLOCK = BubbleClockController.class.getName();
-    private static final Class<?> BUBBLE_CLOCK_CLASS = BubbleClockController.class;
-    private static final int MAIN_USER_ID = 0;
-    private static final int SECONDARY_USER_ID = 11;
-    private static final Uri SETTINGS_URI = null;
-
-    private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
-    private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
-    private ClockManager mClockManager;
-    private ContentObserver mContentObserver;
-    private DockManagerFake mFakeDockManager;
-    private ArgumentCaptor<UserTracker.Callback> mUserTrackerCallbackCaptor;
-    @Mock PluginManager mMockPluginManager;
-    @Mock SysuiColorExtractor mMockColorExtractor;
-    @Mock ContentResolver mMockContentResolver;
-    @Mock UserTracker mUserTracker;
-    @Mock SettingsWrapper mMockSettingsWrapper;
-    @Mock ClockManager.ClockChangedListener mMockListener1;
-    @Mock ClockManager.ClockChangedListener mMockListener2;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        LayoutInflater inflater = LayoutInflater.from(getContext());
-
-        mFakeDockManager = new DockManagerFake();
-
-        when(mUserTracker.getUserId()).thenReturn(MAIN_USER_ID);
-        mUserTrackerCallbackCaptor = ArgumentCaptor.forClass(UserTracker.Callback.class);
-
-        mClockManager = new ClockManager(getContext(), inflater,
-                mMockPluginManager, mMockColorExtractor, mMockContentResolver,
-                mUserTracker, mMainExecutor, mMockSettingsWrapper, mFakeDockManager);
-
-        mClockManager.addBuiltinClock(() -> new BubbleClockController(
-                getContext().getResources(), inflater, mMockColorExtractor));
-        mClockManager.addOnClockChangedListener(mMockListener1);
-        mClockManager.addOnClockChangedListener(mMockListener2);
-        verify(mUserTracker).addCallback(mUserTrackerCallbackCaptor.capture(), any());
-        reset(mMockListener1, mMockListener2);
-
-        mContentObserver = mClockManager.getContentObserver();
-    }
-
-    @After
-    public void tearDown() {
-        mClockManager.removeOnClockChangedListener(mMockListener1);
-        mClockManager.removeOnClockChangedListener(mMockListener2);
-    }
-
-    @Test
-    public void dockEvent() {
-        mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
-        assertThat(mClockManager.isDocked()).isTrue();
-    }
-
-    @Test
-    public void undockEvent() {
-        mFakeDockManager.setDockEvent(DockManager.STATE_NONE);
-        assertThat(mClockManager.isDocked()).isFalse();
-    }
-
-    @Test
-    public void getCurrentClock_default() {
-        // GIVEN that settings doesn't contain any values
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(null);
-        when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn(null);
-        // WHEN settings change event is fired
-        mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID);
-        // THEN the result is null, indicated the default clock face should be used.
-        assertThat(mClockManager.getCurrentClock()).isNull();
-    }
-
-    @Test
-    public void getCurrentClock_customClock() {
-        // GIVEN that settings is set to the bubble clock face
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
-        // WHEN settings change event is fired
-        mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID);
-        // THEN the plugin is the bubble clock face.
-        assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-    }
-
-    @Test
-    public void onClockChanged_customClock() {
-        // GIVEN that settings is set to the bubble clock face
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
-        // WHEN settings change event is fired
-        mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID);
-        // THEN the plugin is the bubble clock face.
-        ArgumentCaptor<ClockPlugin> captor = ArgumentCaptor.forClass(ClockPlugin.class);
-        verify(mMockListener1).onClockChanged(captor.capture());
-        assertThat(captor.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-    }
-
-    @Test
-    public void onClockChanged_uniqueInstances() {
-        // GIVEN that settings is set to the bubble clock face
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
-        // WHEN settings change event is fired
-        mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID);
-        // THEN the listeners receive separate instances of the Bubble clock plugin.
-        ArgumentCaptor<ClockPlugin> captor1 = ArgumentCaptor.forClass(ClockPlugin.class);
-        ArgumentCaptor<ClockPlugin> captor2 = ArgumentCaptor.forClass(ClockPlugin.class);
-        verify(mMockListener1).onClockChanged(captor1.capture());
-        verify(mMockListener2).onClockChanged(captor2.capture());
-        assertThat(captor1.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-        assertThat(captor2.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-        assertThat(captor1.getValue()).isNotSameInstanceAs(captor2.getValue());
-    }
-
-    @Test
-    public void getCurrentClock_badSettingsValue() {
-        // GIVEN that settings contains a value that doesn't correspond to a
-        // custom clock face.
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn("bad value");
-        // WHEN settings change event is fired
-        mContentObserver.onChange(false, Arrays.asList(SETTINGS_URI), 0, MAIN_USER_ID);
-        // THEN the result is null.
-        assertThat(mClockManager.getCurrentClock()).isNull();
-    }
-
-    @Test
-    public void getCurrentClock_dockedDefault() {
-        // WHEN dock event is fired
-        mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
-        // THEN the result is null, indicating the default clock face.
-        assertThat(mClockManager.getCurrentClock()).isNull();
-    }
-
-    @Test
-    public void getCurrentClock_dockedCustomClock() {
-        // GIVEN settings is set to the bubble clock face
-        when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
-        // WHEN dock event fires
-        mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
-        // THEN the plugin is the bubble clock face.
-        assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-    }
-
-    @Test
-    public void getCurrentClock_badDockedSettingsValue() {
-        // GIVEN settings contains a value that doesn't correspond to an available clock face.
-        when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn("bad value");
-        // WHEN dock event fires
-        mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
-        // THEN the result is null.
-        assertThat(mClockManager.getCurrentClock()).isNull();
-    }
-
-    @Test
-    public void getCurrentClock_badDockedSettingsFallback() {
-        // GIVEN settings contains a value that doesn't correspond to an available clock face, but
-        // locked screen settings is set to bubble clock.
-        when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn("bad value");
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
-        // WHEN dock event is fired
-        mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
-        // THEN the plugin is the bubble clock face.
-        assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-    }
-
-    @Test
-    public void onUserChanged_defaultClock() {
-        // WHEN the user changes
-        switchUser(SECONDARY_USER_ID);
-        // THEN the plugin is null for the default clock face
-        assertThat(mClockManager.getCurrentClock()).isNull();
-    }
-
-    @Test
-    public void onUserChanged_customClock() {
-        // GIVEN that a second user has selected the bubble clock face
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace(SECONDARY_USER_ID)).thenReturn(
-                BUBBLE_CLOCK);
-        // WHEN the user changes
-        switchUser(SECONDARY_USER_ID);
-        // THEN the plugin is the bubble clock face.
-        assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-    }
-
-    @Test
-    public void onUserChanged_docked() {
-        // GIVEN device is docked
-        mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
-        // AND the second user as selected the bubble clock for the dock
-        when(mMockSettingsWrapper.getDockedClockFace(SECONDARY_USER_ID)).thenReturn(BUBBLE_CLOCK);
-        // WHEN the user changes
-        switchUser(SECONDARY_USER_ID);
-        // THEN the plugin is the bubble clock face.
-        assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
-    }
-
-    private void switchUser(int newUser) {
-        when(mUserTracker.getUserId()).thenReturn(newUser);
-        mUserTrackerCallbackCaptor.getValue().onUserChanged(newUser, mContext);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockOptionsProviderTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockOptionsProviderTest.java
deleted file mode 100644
index d2832fb9..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockOptionsProviderTest.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.verify;
-
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Supplier;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public final class ClockOptionsProviderTest extends SysuiTestCase {
-
-    private static final String CONTENT_SCHEME = "content";
-    private static final String AUTHORITY = "com.android.keyguard.clock";
-    private static final String LIST_OPTIONS = "list_options";
-    private static final String PREVIEW = "preview";
-    private static final String THUMBNAIL = "thumbnail";
-    private static final String MIME_TYPE_LIST_OPTIONS = "vnd.android.cursor.dir/clock_faces";
-    private static final String MIME_TYPE_PNG = "image/png";
-    private static final String NAME_COLUMN = "name";
-    private static final String TITLE_COLUMN = "title";
-    private static final String ID_COLUMN = "id";
-    private static final String PREVIEW_COLUMN = "preview";
-    private static final String THUMBNAIL_COLUMN = "thumbnail";
-
-    private ClockOptionsProvider mProvider;
-    private Supplier<List<ClockInfo>> mMockSupplier;
-    private List<ClockInfo> mClocks;
-    private Uri mListOptionsUri;
-    @Mock
-    private Supplier<Bitmap> mMockBitmapSupplier;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mClocks = new ArrayList<>();
-        mProvider = new ClockOptionsProvider(() -> mClocks);
-        mListOptionsUri = new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(LIST_OPTIONS)
-                .build();
-    }
-
-    @Test
-    public void testGetType_listOptions() {
-        Uri uri = new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(LIST_OPTIONS)
-                .build();
-        assertThat(mProvider.getType(uri)).isEqualTo(MIME_TYPE_LIST_OPTIONS);
-    }
-
-    @Test
-    public void testGetType_preview() {
-        Uri uri = new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(PREVIEW)
-                .appendPath("id")
-                .build();
-        assertThat(mProvider.getType(uri)).isEqualTo(MIME_TYPE_PNG);
-    }
-
-    @Test
-    public void testGetType_thumbnail() {
-        Uri uri = new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(THUMBNAIL)
-                .appendPath("id")
-                .build();
-        assertThat(mProvider.getType(uri)).isEqualTo(MIME_TYPE_PNG);
-    }
-
-    @Test
-    public void testQuery_noClocks() {
-        Cursor cursor = mProvider.query(mListOptionsUri, null, null, null);
-        assertThat(cursor.getCount()).isEqualTo(0);
-    }
-
-    @Test
-    public void testQuery_listOptions() {
-        mClocks.add(ClockInfo.builder()
-                .setName("name_a")
-                .setTitle(() -> "title_a")
-                .setId("id_a")
-                .build());
-        mClocks.add(ClockInfo.builder()
-                .setName("name_b")
-                .setTitle(() -> "title_b")
-                .setId("id_b")
-                .build());
-        Cursor cursor = mProvider.query(mListOptionsUri, null, null, null);
-        assertThat(cursor.getCount()).isEqualTo(2);
-        cursor.moveToFirst();
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(NAME_COLUMN))).isEqualTo("name_a");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(TITLE_COLUMN))).isEqualTo("title_a");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(ID_COLUMN))).isEqualTo("id_a");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(PREVIEW_COLUMN)))
-                .isEqualTo("content://com.android.keyguard.clock/preview/id_a");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(THUMBNAIL_COLUMN)))
-                .isEqualTo("content://com.android.keyguard.clock/thumbnail/id_a");
-        cursor.moveToNext();
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(NAME_COLUMN))).isEqualTo("name_b");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(TITLE_COLUMN))).isEqualTo("title_b");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(ID_COLUMN))).isEqualTo("id_b");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(PREVIEW_COLUMN)))
-                .isEqualTo("content://com.android.keyguard.clock/preview/id_b");
-        assertThat(cursor.getString(
-                cursor.getColumnIndex(THUMBNAIL_COLUMN)))
-                .isEqualTo("content://com.android.keyguard.clock/thumbnail/id_b");
-    }
-
-    @Test
-    public void testOpenFile_preview() throws Exception {
-        mClocks.add(ClockInfo.builder()
-                .setId("id")
-                .setPreview(mMockBitmapSupplier)
-                .build());
-        Uri uri = new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(PREVIEW)
-                .appendPath("id")
-                .build();
-        mProvider.openFile(uri, "r").close();
-        verify(mMockBitmapSupplier).get();
-    }
-
-    @Test
-    public void testOpenFile_thumbnail() throws Exception {
-        mClocks.add(ClockInfo.builder()
-                .setId("id")
-                .setThumbnail(mMockBitmapSupplier)
-                .build());
-        Uri uri = new Uri.Builder()
-                .scheme(CONTENT_SCHEME)
-                .authority(AUTHORITY)
-                .appendPath(THUMBNAIL)
-                .appendPath("id")
-                .build();
-        mProvider.openFile(uri, "r").close();
-        verify(mMockBitmapSupplier).get();
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockPaletteTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockPaletteTest.kt
deleted file mode 100644
index 347b26d..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockPaletteTest.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.keyguard.clock
-
-import android.graphics.Color
-import android.testing.AndroidTestingRunner
-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
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class ClockPaletteTest : SysuiTestCase() {
-
-    private lateinit var clockPalette: ClockPalette
-    private lateinit var colors: IntArray
-
-    @Before
-    fun setUp() {
-        clockPalette = ClockPalette()
-        // colors used are reds from light to dark.
-        val hsv: FloatArray = FloatArray(3)
-        Color.colorToHSV(Color.RED, hsv)
-        colors = IntArray(10)
-        val step: Float = (0f - hsv[2]) / colors.size
-        for (i in 0 until colors.size) {
-            hsv[2] += step
-            colors[i] = Color.HSVToColor(hsv)
-        }
-    }
-
-    @Test
-    fun testDark() {
-        // GIVEN on AOD
-        clockPalette.setDarkAmount(1f)
-        // AND GIVEN that wallpaper doesn't support dark text
-        clockPalette.setColorPalette(false, colors)
-        // THEN the secondary color should be lighter than the primary color
-        assertThat(value(clockPalette.getPrimaryColor()))
-                .isGreaterThan(value(clockPalette.getSecondaryColor()))
-    }
-
-    @Test
-    fun testDarkText() {
-        // GIVEN on lock screen
-        clockPalette.setDarkAmount(0f)
-        // AND GIVEN that wallpaper supports dark text
-        clockPalette.setColorPalette(true, colors)
-        // THEN the secondary color should be darker the primary color
-        assertThat(value(clockPalette.getPrimaryColor()))
-                .isLessThan(value(clockPalette.getSecondaryColor()))
-    }
-
-    @Test
-    fun testLightText() {
-        // GIVEN on lock screen
-        clockPalette.setDarkAmount(0f)
-        // AND GIVEN that wallpaper doesn't support dark text
-        clockPalette.setColorPalette(false, colors)
-        // THEN the secondary color should be darker than the primary color
-        assertThat(value(clockPalette.getPrimaryColor()))
-                .isGreaterThan(value(clockPalette.getSecondaryColor()))
-    }
-
-    @Test
-    fun testNullColors() {
-        // GIVEN on AOD
-        clockPalette.setDarkAmount(1f)
-        // AND GIVEN that wallpaper colors are null
-        clockPalette.setColorPalette(false, null)
-        // THEN the primary color should be whilte
-        assertThat(clockPalette.getPrimaryColor()).isEqualTo(Color.WHITE)
-    }
-
-    private fun value(color: Int): Float {
-        val hsv: FloatArray = FloatArray(3)
-        Color.colorToHSV(color, hsv)
-        return hsv[2]
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/CrossFadeDarkControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/CrossFadeDarkControllerTest.java
deleted file mode 100644
index fd7657f..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/CrossFadeDarkControllerTest.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.keyguard.clock;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.View;
-import android.widget.TextView;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-public final class CrossFadeDarkControllerTest extends SysuiTestCase {
-
-    private View mViewFadeIn;
-    private View mViewFadeOut;
-    private CrossFadeDarkController mDarkController;
-
-    @Before
-    public void setUp() {
-        mViewFadeIn = new TextView(getContext());
-        mViewFadeIn.setVisibility(View.VISIBLE);
-        mViewFadeIn.setAlpha(1f);
-        mViewFadeOut = new TextView(getContext());
-        mViewFadeOut.setVisibility(View.VISIBLE);
-        mViewFadeOut.setAlpha(1f);
-
-        mDarkController = new CrossFadeDarkController(mViewFadeIn, mViewFadeOut);
-    }
-
-    @Test
-    public void setDarkAmount_fadeIn() {
-        // WHEN dark amount corresponds to AOD
-        mDarkController.setDarkAmount(1f);
-        // THEN fade in view should be faded in and fade out view faded out.
-        assertThat(mViewFadeIn.getAlpha()).isEqualTo(1f);
-        assertThat(mViewFadeIn.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(mViewFadeOut.getAlpha()).isEqualTo(0f);
-        assertThat(mViewFadeOut.getVisibility()).isEqualTo(View.GONE);
-    }
-
-    @Test
-    public void setDarkAmount_fadeOut() {
-        // WHEN dark amount corresponds to lock screen
-        mDarkController.setDarkAmount(0f);
-        // THEN fade out view should bed faded out and fade in view faded in.
-        assertThat(mViewFadeIn.getAlpha()).isEqualTo(0f);
-        assertThat(mViewFadeIn.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mViewFadeOut.getAlpha()).isEqualTo(1f);
-        assertThat(mViewFadeOut.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    public void setDarkAmount_partialFadeIn() {
-        // WHEN dark amount corresponds to a partial transition
-        mDarkController.setDarkAmount(0.9f);
-        // THEN views should have intermediate alpha value.
-        assertThat(mViewFadeIn.getAlpha()).isGreaterThan(0f);
-        assertThat(mViewFadeIn.getAlpha()).isLessThan(1f);
-        assertThat(mViewFadeIn.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    public void setDarkAmount_partialFadeOut() {
-        // WHEN dark amount corresponds to a partial transition
-        mDarkController.setDarkAmount(0.1f);
-        // THEN views should have intermediate alpha value.
-        assertThat(mViewFadeOut.getAlpha()).isGreaterThan(0f);
-        assertThat(mViewFadeOut.getAlpha()).isLessThan(1f);
-        assertThat(mViewFadeOut.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/SettingsWrapperTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/clock/SettingsWrapperTest.kt
deleted file mode 100644
index 573581d..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/SettingsWrapperTest.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.keyguard.clock
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.google.common.truth.Truth.assertThat
-import org.json.JSONObject
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-
-private const val PACKAGE = "com.android.keyguard.clock.Clock"
-private const val CLOCK_FIELD = "clock"
-private const val TIMESTAMP_FIELD = "_applied_timestamp"
-private const val USER_ID = 0
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class SettingsWrapperTest : SysuiTestCase() {
-
-    private lateinit var wrapper: SettingsWrapper
-    private lateinit var migration: SettingsWrapper.Migration
-
-    @Before
-    fun setUp() {
-        migration = mock(SettingsWrapper.Migration::class.java)
-        wrapper = SettingsWrapper(getContext().contentResolver, migration)
-    }
-
-    @Test
-    fun testDecodeUnnecessary() {
-        // GIVEN a settings value that doesn't need to be decoded
-        val value = PACKAGE
-        // WHEN the value is decoded
-        val decoded = wrapper.decode(value, USER_ID)
-        // THEN the same value is returned, because decoding isn't necessary.
-        // TODO(b/135674383): Null should be returned when the migration code in removed.
-        assertThat(decoded).isEqualTo(value)
-        // AND the value is migrated to JSON format
-        verify(migration).migrate(value, USER_ID)
-    }
-
-    @Test
-    fun testDecodeJSON() {
-        // GIVEN a settings value that is encoded in JSON
-        val json: JSONObject = JSONObject()
-        json.put(CLOCK_FIELD, PACKAGE)
-        json.put(TIMESTAMP_FIELD, System.currentTimeMillis())
-        val value = json.toString()
-        // WHEN the value is decoded
-        val decoded = wrapper.decode(value, USER_ID)
-        // THEN the clock field should have been extracted
-        assertThat(decoded).isEqualTo(PACKAGE)
-    }
-
-    @Test
-    fun testDecodeJSONWithoutClockField() {
-        // GIVEN a settings value that doesn't contain the CLOCK_FIELD
-        val json: JSONObject = JSONObject()
-        json.put(TIMESTAMP_FIELD, System.currentTimeMillis())
-        val value = json.toString()
-        // WHEN the value is decoded
-        val decoded = wrapper.decode(value, USER_ID)
-        // THEN null is returned
-        assertThat(decoded).isNull()
-        // AND the value is not migrated to JSON format
-        verify(migration, never()).migrate(value, USER_ID)
-    }
-
-    @Test
-    fun testDecodeNullJSON() {
-        assertThat(wrapper.decode(null, USER_ID)).isNull()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/SmallClockPositionTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/clock/SmallClockPositionTest.kt
deleted file mode 100644
index 3a27e35..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/SmallClockPositionTest.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.keyguard.clock
-
-import android.testing.AndroidTestingRunner
-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
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class SmallClockPositionTest : SysuiTestCase() {
-
-    private val statusBarHeight = 100
-    private val lockPadding = 15
-    private val lockHeight = 35
-    private val burnInY = 20
-
-    private lateinit var position: SmallClockPosition
-
-    @Before
-    fun setUp() {
-        position = SmallClockPosition(statusBarHeight, lockPadding, lockHeight, burnInY)
-    }
-
-    @Test
-    fun loadResources() {
-        // Cover constructor taking Resources object.
-        position = SmallClockPosition(context)
-        position.setDarkAmount(1f)
-        assertThat(position.preferredY).isGreaterThan(0)
-    }
-
-    @Test
-    fun darkPosition() {
-        // GIVEN on AOD
-        position.setDarkAmount(1f)
-        // THEN Y is sum of statusBarHeight, lockPadding, lockHeight, lockPadding, burnInY
-        assertThat(position.preferredY).isEqualTo(185)
-    }
-
-    @Test
-    fun lockPosition() {
-        // GIVEN on lock screen
-        position.setDarkAmount(0f)
-        // THEN Y position is statusBarHeight + lockPadding + lockHeight + lockPadding
-        // (100 + 15 + 35 + 15 = 165)
-        assertThat(position.preferredY).isEqualTo(165)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ViewPreviewerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/clock/ViewPreviewerTest.kt
deleted file mode 100644
index 5ece6ef..0000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ViewPreviewerTest.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard.clock
-
-import android.content.Context
-import com.google.common.truth.Truth.assertThat
-
-import android.graphics.Canvas
-import android.graphics.Color
-import android.testing.AndroidTestingRunner
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class ViewPreviewerTest : SysuiTestCase() {
-
-    private lateinit var previewer: ViewPreviewer
-    private lateinit var view: View
-
-    @Before
-    fun setUp() {
-        previewer = ViewPreviewer()
-        view = TestView(context)
-    }
-
-    @Test
-    fun testCreatePreview() {
-        val width = 100
-        val height = 100
-        // WHEN a preview image is created
-        val bitmap = previewer.createPreview(view, width, height)!!
-        // THEN the bitmap has the expected width and height
-        assertThat(bitmap.height).isEqualTo(height)
-        assertThat(bitmap.width).isEqualTo(width)
-        assertThat(bitmap.getPixel(0, 0)).isEqualTo(Color.RED)
-    }
-
-    class TestView(context: Context) : View(context) {
-        override fun onDraw(canvas: Canvas?) {
-            super.onDraw(canvas)
-            canvas?.drawColor(Color.RED)
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index a0fdc8f..24a5e80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -48,8 +48,7 @@
     @Test
     public void testInitDependency() throws ExecutionException, InterruptedException {
         Dependency.clearDependencies();
-        SystemUIInitializer initializer =
-                SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
+        SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext);
         initializer.init(true);
         Dependency dependency = initializer.getSysUIComponent().createDependency();
         dependency.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index 6decb88..ba27fcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -20,21 +20,25 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
+import androidx.core.animation.ObjectAnimator;
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.animation.AnimatorTestRule;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -43,12 +47,17 @@
 @RunWithLooper
 public class ExpandHelperTest extends SysuiTestCase {
 
+    @Rule
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private ExpandableNotificationRow mRow;
     private ExpandHelper mExpandHelper;
     private ExpandHelper.Callback mCallback;
 
     @Before
     public void setUp() throws Exception {
+        mFeatureFlags.setDefault(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectMockDependency(NotificationMediaManager.class);
         allowTestableLooperAsMainThread();
@@ -56,7 +65,8 @@
         NotificationTestHelper helper = new NotificationTestHelper(
                 mContext,
                 mDependency,
-                TestableLooper.get(this));
+                TestableLooper.get(this),
+                mFeatureFlags);
         mRow = helper.createRow();
         mCallback = mock(ExpandHelper.Callback.class);
         mExpandHelper = new ExpandHelper(context, mCallback, 10, 100);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
deleted file mode 100644
index b47b08c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ /dev/null
@@ -1,487 +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.systemui;
-
-import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.fail;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.UserIdInt;
-import android.app.AppOpsManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.widget.RemoteViews;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.appops.AppOpsController;
-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.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-
-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;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class ForegroundServiceControllerTest extends SysuiTestCase {
-    private ForegroundServiceController mFsc;
-    private ForegroundServiceNotificationListener mListener;
-    private NotifCollectionListener mCollectionListener;
-    @Mock private AppOpsController mAppOpsController;
-    @Mock private Handler mMainHandler;
-    @Mock private NotifPipeline mNotifPipeline;
-
-    @Before
-    public void setUp() throws Exception {
-        // allow the TestLooper to be asserted as the main thread these tests
-        allowTestableLooperAsMainThread();
-
-        MockitoAnnotations.initMocks(this);
-        mFsc = new ForegroundServiceController(mAppOpsController, mMainHandler);
-        mListener = new ForegroundServiceNotificationListener(
-                mContext, mFsc, mNotifPipeline);
-        mListener.init();
-        ArgumentCaptor<NotifCollectionListener> entryListenerCaptor =
-                ArgumentCaptor.forClass(NotifCollectionListener.class);
-        verify(mNotifPipeline).addCollectionListener(
-                entryListenerCaptor.capture());
-        mCollectionListener = entryListenerCaptor.getValue();
-    }
-
-    @Test
-    public void testAppOpsChangedCalledFromBgThread() {
-        try {
-            // WHEN onAppOpChanged is called from a different thread than the MainLooper
-            disallowTestableLooperAsMainThread();
-            NotificationEntry entry = createFgEntry();
-            mFsc.onAppOpChanged(
-                    AppOpsManager.OP_CAMERA,
-                    entry.getSbn().getUid(),
-                    entry.getSbn().getPackageName(),
-                    true);
-
-            // This test is run on the TestableLooper, which is not the MainLooper, so
-            // we expect an exception to be thrown
-            fail("onAppOpChanged shouldn't be allowed to be called from a bg thread.");
-        } catch (IllegalStateException e) {
-            // THEN expect an exception
-        }
-    }
-
-    @Test
-    public void testAppOpsCRUD() {
-        // no crash on remove that doesn't exist
-        mFsc.onAppOpChanged(9, 1000, "pkg1", false);
-        assertNull(mFsc.getAppOps(0, "pkg1"));
-
-        // multiuser & multipackage
-        mFsc.onAppOpChanged(8, 50, "pkg1", true);
-        mFsc.onAppOpChanged(1, 60, "pkg3", true);
-        mFsc.onAppOpChanged(7, 500000, "pkg2", true);
-
-        assertEquals(1, mFsc.getAppOps(0, "pkg1").size());
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
-
-        assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
-        assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
-
-        assertEquals(1, mFsc.getAppOps(0, "pkg3").size());
-        assertTrue(mFsc.getAppOps(0, "pkg3").contains(1));
-
-        // multiple ops for the same package
-        mFsc.onAppOpChanged(9, 50, "pkg1", true);
-        mFsc.onAppOpChanged(5, 50, "pkg1", true);
-
-        assertEquals(3, mFsc.getAppOps(0, "pkg1").size());
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(9));
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(5));
-
-        assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
-        assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
-
-        // remove one of the multiples
-        mFsc.onAppOpChanged(9, 50, "pkg1", false);
-        assertEquals(2, mFsc.getAppOps(0, "pkg1").size());
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(5));
-
-        // remove last op
-        mFsc.onAppOpChanged(1, 60, "pkg3", false);
-        assertNull(mFsc.getAppOps(0, "pkg3"));
-    }
-
-    @Test
-    public void testDisclosurePredicate() {
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        StatusBarNotification sbn_user1_disclosure = makeMockSBN(USERID_ONE, "android",
-                SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
-                null, Notification.FLAG_NO_CLEAR);
-
-        assertTrue(mFsc.isDisclosureNotification(sbn_user1_disclosure));
-        assertFalse(mFsc.isDisclosureNotification(sbn_user1_app1));
-    }
-
-    @Test
-    public void testNeedsDisclosureAfterRemovingUnrelatedNotification() {
-        final String PKG1 = "com.example.app100";
-
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1);
-
-        // first add a normal notification
-        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
-        // nothing required yet
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        // now the app starts a fg service
-        entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-        // add the fg notification
-        entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered
-        // remove the boring notification
-        entryRemoved(sbn_user1_app1);
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has STILL got it covered
-        entryRemoved(sbn_user1_app1_fg);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-    }
-
-    @Test
-    public void testSimpleAddRemove() {
-        final String PKG1 = "com.example.app1";
-        final String PKG2 = "com.example.app2";
-
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
-
-        // no services are "running"
-        entryAdded(makeMockDisclosure(USERID_ONE, null),
-                NotificationManager.IMPORTANCE_DEFAULT);
-
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        // switch to different package
-        entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        entryUpdated(makeMockDisclosure(USERID_TWO, new String[]{PKG1}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO)); // finally user2 needs one too
-
-        entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2, PKG1}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        entryRemoved(makeMockDisclosure(USERID_ONE, null /*unused*/));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        entryRemoved(makeMockDisclosure(USERID_TWO, null /*unused*/));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-    }
-
-    @Test
-    public void testDisclosureBasic() {
-        final String PKG1 = "com.example.app0";
-
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1);
-
-        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); // not fg
-        entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-        entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        // let's take out the other notification and see what happens.
-
-        entryRemoved(sbn_user1_app1);
-        assertFalse(
-                mFsc.isDisclosureNeededForUser(USERID_ONE)); // still covered by sbn_user1_app1_fg
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        // let's attempt to downgrade the notification from FLAG_FOREGROUND and see what we get
-        StatusBarNotification sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1);
-        sbn_user1_app1_fg_sneaky.getNotification().flags = 0;
-        entryUpdated(sbn_user1_app1_fg_sneaky,
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        // ok, ok, we'll put it back
-        sbn_user1_app1_fg_sneaky.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
-        entryUpdated(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        entryRemoved(sbn_user1_app1_fg_sneaky);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        // now let's test an upgrade
-        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-        sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
-        entryUpdated(sbn_user1_app1,
-                NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
-
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-
-        // remove it, make sure we're out of compliance again
-        entryRemoved(sbn_user1_app1); // was fg, should return true
-        entryRemoved(sbn_user1_app1);
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-
-        // importance upgrade
-        entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-        sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
-        entryUpdated(sbn_user1_app1_fg,
-                NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
-
-        // finally, let's turn off the service
-        entryAdded(makeMockDisclosure(USERID_ONE, null),
-                NotificationManager.IMPORTANCE_DEFAULT);
-
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-    }
-
-    @Test
-    public void testNoNotifsNorAppOps_noSystemAlertWarningRequired() {
-        // no notifications nor app op signals that this package/userId requires system alert
-        // warning
-        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, "any"));
-    }
-
-    @Test
-    public void testCustomLayouts_systemAlertWarningRequired() {
-        // GIVEN a notification with a custom layout
-        final String pkg = "com.example.app0";
-        StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
-                false);
-
-        // WHEN the custom layout entry is added
-        entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-
-        // THEN a system alert warning is required since there aren't any notifications that can
-        // display the app ops
-        assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
-    }
-
-    @Test
-    public void testStandardLayoutExists_noSystemAlertWarningRequired() {
-        // GIVEN two notifications (one with a custom layout, the other with a standard layout)
-        final String pkg = "com.example.app0";
-        StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
-                false);
-        StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
-
-        // WHEN the entries are added
-        entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-        entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-
-        // THEN no system alert warning is required, since there is at least one notification
-        // with a standard layout that can display the app ops on the notification
-        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
-    }
-
-    @Test
-    public void testStandardLayoutRemoved_systemAlertWarningRequired() {
-        // GIVEN two notifications (one with a custom layout, the other with a standard layout)
-        final String pkg = "com.example.app0";
-        StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
-                false);
-        StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
-
-        // WHEN the entries are added and then the standard layout notification is removed
-        entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-        entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-        entryRemoved(standardLayoutNotif);
-
-        // THEN a system alert warning is required since there aren't any notifications that can
-        // display the app ops
-        assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
-    }
-
-    @Test
-    public void testStandardLayoutUpdatedToCustomLayout_systemAlertWarningRequired() {
-        // GIVEN a standard layout notification and then an updated version with a customLayout
-        final String pkg = "com.example.app0";
-        StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
-        StatusBarNotification updatedToCustomLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, false);
-
-        // WHEN the entries is added and then updated to a custom layout
-        entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-        entryUpdated(updatedToCustomLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-
-        // THEN a system alert warning is required since there aren't any notifications that can
-        // display the app ops
-        assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
-    }
-
-    private StatusBarNotification makeMockSBN(int userId, String pkg, int id, String tag,
-            int flags) {
-        final Notification n = mock(Notification.class);
-        n.extras = new Bundle();
-        n.flags = flags;
-        return makeMockSBN(userId, pkg, id, tag, n);
-    }
-
-    private StatusBarNotification makeMockSBN(int userid, String pkg, int id, String tag,
-            Notification n) {
-        final StatusBarNotification sbn = mock(StatusBarNotification.class);
-        when(sbn.getNotification()).thenReturn(n);
-        when(sbn.getId()).thenReturn(id);
-        when(sbn.getPackageName()).thenReturn(pkg);
-        when(sbn.getTag()).thenReturn(tag);
-        when(sbn.getUserId()).thenReturn(userid);
-        when(sbn.getUser()).thenReturn(new UserHandle(userid));
-        when(sbn.getKey()).thenReturn("MOCK:"+userid+"|"+pkg+"|"+id+"|"+tag);
-        return sbn;
-    }
-
-    private StatusBarNotification makeMockSBN(int uid, String pkg, int id,
-            boolean usesStdLayout) {
-        StatusBarNotification sbn = makeMockSBN(uid, pkg, id, "foo", 0);
-        if (usesStdLayout) {
-            sbn.getNotification().contentView = null;
-            sbn.getNotification().headsUpContentView = null;
-            sbn.getNotification().bigContentView = null;
-        } else {
-            sbn.getNotification().contentView = mock(RemoteViews.class);
-        }
-        return sbn;
-    }
-
-    private StatusBarNotification makeMockFgSBN(int uid, String pkg, int id,
-            boolean usesStdLayout) {
-        StatusBarNotification sbn =
-                makeMockSBN(uid, pkg, id, "foo", Notification.FLAG_FOREGROUND_SERVICE);
-        if (usesStdLayout) {
-            sbn.getNotification().contentView = null;
-            sbn.getNotification().headsUpContentView = null;
-            sbn.getNotification().bigContentView = null;
-        } else {
-            sbn.getNotification().contentView = mock(RemoteViews.class);
-        }
-        return sbn;
-    }
-
-    private StatusBarNotification makeMockFgSBN(int uid, String pkg) {
-        return makeMockSBN(uid, pkg, 1000, "foo", Notification.FLAG_FOREGROUND_SERVICE);
-    }
-
-    private StatusBarNotification makeMockDisclosure(int userid, String[] pkgs) {
-        final Notification n = mock(Notification.class);
-        n.flags = Notification.FLAG_ONGOING_EVENT;
-        final Bundle extras = new Bundle();
-        if (pkgs != null) extras.putStringArray(Notification.EXTRA_FOREGROUND_APPS, pkgs);
-        n.extras = extras;
-        n.when = System.currentTimeMillis() - 10000; // ten seconds ago
-        final StatusBarNotification sbn = makeMockSBN(userid, "android",
-                SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
-                null, n);
-        sbn.getNotification().extras = extras;
-        return sbn;
-    }
-
-    private NotificationEntry addFgEntry() {
-        NotificationEntry entry = createFgEntry();
-        mCollectionListener.onEntryAdded(entry);
-        return entry;
-    }
-
-    private NotificationEntry createFgEntry() {
-        return new NotificationEntryBuilder()
-                .setSbn(makeMockFgSBN(0, TEST_PACKAGE_NAME, 1000, true))
-                .setImportance(NotificationManager.IMPORTANCE_DEFAULT)
-                .build();
-    }
-
-    private void entryRemoved(StatusBarNotification notification) {
-        mCollectionListener.onEntryRemoved(
-                new NotificationEntryBuilder()
-                        .setSbn(notification)
-                        .build(),
-                REASON_APP_CANCEL);
-    }
-
-    private void entryAdded(StatusBarNotification notification, int importance) {
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setSbn(notification)
-                .setImportance(importance)
-                .build();
-        mCollectionListener.onEntryAdded(entry);
-    }
-
-    private void entryUpdated(StatusBarNotification notification, int importance) {
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setSbn(notification)
-                .setImportance(importance)
-                .build();
-        mCollectionListener.onEntryUpdated(entry);
-    }
-
-    @UserIdInt private static final int USERID_ONE = 10; // UserManagerService.MIN_USER_ID;
-    @UserIdInt private static final int USERID_TWO = USERID_ONE + 1;
-    private static final String TEST_PACKAGE_NAME = "test";
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
index 8207fa6..d500b5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
@@ -74,7 +74,8 @@
 
         val decorationSupport = DisplayDecorationSupport()
         decorationSupport.format = PixelFormat.R_8
-        decorHwcLayer = Mockito.spy(ScreenDecorHwcLayer(mContext, decorationSupport))
+        decorHwcLayer =
+            Mockito.spy(ScreenDecorHwcLayer(mContext, decorationSupport, /* debug */ false))
         whenever(decorHwcLayer.width).thenReturn(displayWidth)
         whenever(decorHwcLayer.height).thenReturn(displayHeight)
         whenever(decorHwcLayer.context).thenReturn(mockContext)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 13a1fe9..f81ef10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -43,7 +43,6 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -63,6 +62,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
+import android.util.PathParser;
 import android.util.Size;
 import android.view.Display;
 import android.view.DisplayCutout;
@@ -83,6 +83,7 @@
 import com.android.systemui.decor.CornerDecorProvider;
 import com.android.systemui.decor.CutoutDecorProviderFactory;
 import com.android.systemui.decor.CutoutDecorProviderImpl;
+import com.android.systemui.decor.DebugRoundedCornerModel;
 import com.android.systemui.decor.DecorProvider;
 import com.android.systemui.decor.DecorProviderFactory;
 import com.android.systemui.decor.FaceScanningOverlayProviderImpl;
@@ -97,8 +98,8 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
-import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.FakeThreadFactory;
 import com.android.systemui.util.settings.FakeSettings;
@@ -141,7 +142,7 @@
     @Mock
     private Display mDisplay;
     @Mock
-    private TunerService mTunerService;
+    private CommandRegistry mCommandRegistry;
     @Mock
     private UserTracker mUserTracker;
     @Mock
@@ -238,8 +239,9 @@
                 new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
                 featureFlags));
 
-        mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
-                mTunerService, mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory,
+        mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings,
+                mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController,
+                mThreadFactory,
                 mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
                 new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
                 mAuthController) {
@@ -256,12 +258,6 @@
             }
 
             @Override
-            public void onTuningChanged(String key, String newValue) {
-                super.onTuningChanged(key, newValue);
-                mExecutor.runAllReady();
-            }
-
-            @Override
             protected void updateOverlayWindowVisibilityIfViewExists(@Nullable View view) {
                 super.updateOverlayWindowVisibilityIfViewExists(view);
                 mExecutor.runAllReady();
@@ -273,9 +269,10 @@
             }
         });
         mScreenDecorations.mDisplayInfo = mDisplayInfo;
+        // Make sure tests are never run starting in debug mode
+        mScreenDecorations.setDebug(false);
         doReturn(1f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
         doNothing().when(mScreenDecorations).updateOverlayProviderViews(any());
-        reset(mTunerService);
 
         try {
             mPrivacyDotShowingListener = mScreenDecorations.mPrivacyDotShowingListener.getClass()
@@ -469,8 +466,6 @@
         mScreenDecorations.start();
         // No views added.
         verifyOverlaysExistAndAdded(false, false, false, false, null);
-        // No Tuners tuned.
-        verify(mTunerService, never()).addTunable(any(), any());
         // No dot controller init
         verify(mDotViewController, never()).initialize(any(), any(), any(), any());
     }
@@ -502,8 +497,6 @@
         // Face scanning doesn't exist
         verifyFaceScanningViewExists(false);
 
-        // One tunable.
-        verify(mTunerService, times(1)).addTunable(any(), any());
         // Dot controller init
         verify(mDotViewController, times(1)).initialize(
                 isA(View.class), isA(View.class), isA(View.class), isA(View.class));
@@ -533,8 +526,6 @@
         // Face scanning doesn't exist
         verifyFaceScanningViewExists(false);
 
-        // One tunable.
-        verify(mTunerService, times(1)).addTunable(any(), any());
         // No dot controller init
         verify(mDotViewController, never()).initialize(any(), any(), any(), any());
     }
@@ -565,8 +556,6 @@
         // Face scanning doesn't exist
         verifyFaceScanningViewExists(false);
 
-        // One tunable.
-        verify(mTunerService, times(1)).addTunable(any(), any());
         // Dot controller init
         verify(mDotViewController, times(1)).initialize(
                 isA(View.class), isA(View.class), isA(View.class), isA(View.class));
@@ -1078,18 +1067,89 @@
     }
 
     @Test
+    public void testDebugRoundedCorners_noDeviceCornersSet() {
+        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
+                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
+
+        mScreenDecorations.start();
+        // No rounded corners exist at this point
+        verifyOverlaysExistAndAdded(false, false, false, false, View.VISIBLE);
+
+        // Path from rounded.xml, scaled by 10x to produce 80x80 corners
+        Path debugPath = PathParser.createPathFromPathData("M8,0H0v8C0,3.6,3.6,0,8,0z");
+        // WHEN debug corners are added to the delegate
+        DebugRoundedCornerModel debugCorner = new DebugRoundedCornerModel(
+                debugPath,
+                80,
+                80,
+                10f,
+                10f
+        );
+        mScreenDecorations.mDebugRoundedCornerDelegate
+                .applyNewDebugCorners(debugCorner, debugCorner);
+
+        // AND debug mode is entered
+        mScreenDecorations.setDebug(true);
+        mExecutor.runAllReady();
+
+        // THEN the debug corners provide decor
+        List<DecorProvider> providers = mScreenDecorations.getProviders(false);
+        assertEquals(4, providers.size());
+
+        // Top and bottom overlays contain the debug rounded corners
+        verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
+    }
+
+    @Test
+    public void testDebugRoundedCornersRemoved_noDeviceCornersSet() {
+        // GIVEN a device with no rounded corners defined
+        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
+                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
+
+        mScreenDecorations.start();
+        // No rounded corners exist at this point
+        verifyOverlaysExistAndAdded(false, false, false, false, View.VISIBLE);
+
+        // Path from rounded.xml, scaled by 10x to produce 80x80 corners
+        Path debugPath = PathParser.createPathFromPathData("M8,0H0v8C0,3.6,3.6,0,8,0z");
+        // WHEN debug corners are added to the delegate
+        DebugRoundedCornerModel debugCorner = new DebugRoundedCornerModel(
+                debugPath,
+                80,
+                80,
+                10f,
+                10f
+        );
+        mScreenDecorations.mDebugRoundedCornerDelegate
+                .applyNewDebugCorners(debugCorner, debugCorner);
+
+        // AND debug mode is entered
+        mScreenDecorations.setDebug(true);
+        mExecutor.runAllReady();
+
+        // Top and bottom overlays contain the debug rounded corners
+        verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
+
+        // WHEN debug is exited
+        mScreenDecorations.setDebug(false);
+        mExecutor.runAllReady();
+
+        // THEN the decor is removed
+        verifyOverlaysExistAndAdded(false, false, false, false, View.VISIBLE);
+        assertThat(mScreenDecorations.mDebugRoundedCornerDelegate.getHasBottom()).isFalse();
+        assertThat(mScreenDecorations.mDebugRoundedCornerDelegate.getHasTop()).isFalse();
+    }
+
+    @Test
     public void testRegistration_From_NoOverlay_To_HasOverlays() {
         doReturn(false).when(mScreenDecorations).hasOverlays();
         mScreenDecorations.start();
-        verify(mTunerService, times(0)).addTunable(any(), any());
-        verify(mTunerService, times(1)).removeTunable(any());
         assertThat(mScreenDecorations.mIsRegistered, is(false));
-        reset(mTunerService);
 
         doReturn(true).when(mScreenDecorations).hasOverlays();
         mScreenDecorations.onConfigurationChanged(new Configuration());
-        verify(mTunerService, times(1)).addTunable(any(), any());
-        verify(mTunerService, times(0)).removeTunable(any());
         assertThat(mScreenDecorations.mIsRegistered, is(true));
     }
 
@@ -1098,14 +1158,9 @@
         doReturn(true).when(mScreenDecorations).hasOverlays();
 
         mScreenDecorations.start();
-        verify(mTunerService, times(1)).addTunable(any(), any());
-        verify(mTunerService, times(0)).removeTunable(any());
         assertThat(mScreenDecorations.mIsRegistered, is(true));
-        reset(mTunerService);
 
         mScreenDecorations.onConfigurationChanged(new Configuration());
-        verify(mTunerService, times(0)).addTunable(any(), any());
-        verify(mTunerService, times(0)).removeTunable(any());
         assertThat(mScreenDecorations.mIsRegistered, is(true));
     }
 
@@ -1114,15 +1169,10 @@
         doReturn(true).when(mScreenDecorations).hasOverlays();
 
         mScreenDecorations.start();
-        verify(mTunerService, times(1)).addTunable(any(), any());
-        verify(mTunerService, times(0)).removeTunable(any());
         assertThat(mScreenDecorations.mIsRegistered, is(true));
-        reset(mTunerService);
 
         doReturn(false).when(mScreenDecorations).hasOverlays();
         mScreenDecorations.onConfigurationChanged(new Configuration());
-        verify(mTunerService, times(0)).addTunable(any(), any());
-        verify(mTunerService, times(1)).removeTunable(any());
         assertThat(mScreenDecorations.mIsRegistered, is(false));
     }
 
@@ -1185,8 +1235,9 @@
         mFaceScanningProviders.add(mFaceScanningDecorProvider);
         when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders);
         when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(true);
-        ScreenDecorations screenDecorations = new ScreenDecorations(mContext, mExecutor,
-                mSecureSettings, mTunerService, mUserTracker, mDisplayTracker, mDotViewController,
+        ScreenDecorations screenDecorations = new ScreenDecorations(mContext,
+                mSecureSettings, mCommandRegistry, mUserTracker, mDisplayTracker,
+                mDotViewController,
                 mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
                 new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController);
         screenDecorations.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index f64db78f..67d6aa8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -16,8 +16,10 @@
 
 package com.android.systemui.accessibility;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
@@ -185,6 +187,20 @@
         verify(mMagnificationSettingsController).closeMagnificationSettings();
     }
 
+    @Test
+    public void onUserMagnificationScaleChanged() throws RemoteException {
+        final int testUserId = 1;
+        final float testScale = 3.0f;
+        mIWindowMagnificationConnection.onUserMagnificationScaleChanged(
+                testUserId, TEST_DISPLAY, testScale);
+        waitForIdleSync();
+
+        assertTrue(mWindowMagnification.mUsersScales.contains(testUserId));
+        assertEquals(mWindowMagnification.mUsersScales.get(testUserId).get(TEST_DISPLAY),
+                (Float) testScale);
+        verify(mMagnificationSettingsController).setMagnificationScale(eq(testScale));
+    }
+
     private class FakeControllerSupplier extends
             DisplayIdIndexSupplier<WindowMagnificationController> {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
index 665246b..9eead6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
@@ -73,9 +73,9 @@
 
     @Test
     public void testShowSettingsPanel() {
-        mMagnificationSettingsController.showMagnificationSettings();
+        mMagnificationSettingsController.toggleSettingsPanelVisibility();
 
-        verify(mWindowMagnificationSettings).showSettingPanel();
+        verify(mWindowMagnificationSettings).toggleSettingsPanelVisibility();
     }
 
     @Test
@@ -86,6 +86,14 @@
     }
 
     @Test
+    public void testSetMagnificationScale() {
+        final float scale = 3.0f;
+        mMagnificationSettingsController.setMagnificationScale(scale);
+
+        verify(mWindowMagnificationSettings).setMagnificationScale(eq(scale));
+    }
+
+    @Test
     public void testOnConfigurationChanged_notifySettingsPanel() {
         mMagnificationSettingsController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
 
@@ -145,10 +153,11 @@
     @Test
     public void testPanelOnMagnifierScale_delegateToCallback() {
         final float scale = 3.0f;
+        final boolean updatePersistence = true;
         mMagnificationSettingsController.mWindowMagnificationSettingsCallback
-                .onMagnifierScale(scale);
+                .onMagnifierScale(scale, updatePersistence);
 
         verify(mMagnificationSettingControllerCallback).onMagnifierScale(
-                eq(mContext.getDisplayId()), eq(scale));
+                eq(mContext.getDisplayId()), eq(scale), eq(updatePersistence));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
index 025c88c..576f689 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
@@ -39,6 +39,7 @@
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
@@ -65,6 +66,8 @@
     @Mock
     private ShadeController mShadeController;
     @Mock
+    private ShadeViewController mShadeViewController;
+    @Mock
     private Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     @Mock
     private Optional<Recents> mRecentsOptional;
@@ -82,7 +85,8 @@
         mContext.addMockSystemService(TelecomManager.class, mTelecomManager);
         mContext.addMockSystemService(InputManager.class, mInputManager);
         mSystemActions = new SystemActions(mContext, mUserTracker, mNotificationShadeController,
-                mShadeController, mCentralSurfacesOptionalLazy, mRecentsOptional, mDisplayTracker);
+                mShadeController, () -> mShadeViewController, mCentralSurfacesOptionalLazy,
+                mRecentsOptional, mDisplayTracker);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 9c36af3..e96ad87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.accessibility;
 
+import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
@@ -96,6 +97,7 @@
 import com.google.common.util.concurrent.AtomicDouble;
 
 import org.junit.After;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -147,8 +149,15 @@
     private View.OnTouchListener mTouchListener;
     private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
 
+    /**
+     *  return whether window magnification is supported for current test context.
+     */
+    private boolean isWindowModeSupported() {
+        return getContext().getPackageManager().hasSystemFeature(FEATURE_WINDOW_MAGNIFICATION);
+    }
+
     @Before
-    public void setUp() throws RemoteException {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mContext = Mockito.spy(getContext());
         mHandler = new FakeHandler(TestableLooper.get(this).getLooper());
@@ -202,6 +211,9 @@
             return null;
         }).when(mSpyView).setOnTouchListener(
                 any(View.OnTouchListener.class));
+
+        // skip test if window magnification is not supported to prevent fail results. (b/279820875)
+        Assume.assumeTrue(isWindowModeSupported());
     }
 
     @After
@@ -633,10 +645,12 @@
         assertTrue(
                 mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null));
         // Minimum scale is 1.0.
-        verify(mWindowMagnifierCallback).onPerformScaleAction(eq(displayId), eq(1.0f));
+        verify(mWindowMagnifierCallback).onPerformScaleAction(
+                eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true));
 
         assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null));
-        verify(mWindowMagnifierCallback).onPerformScaleAction(eq(displayId), eq(2.5f));
+        verify(mWindowMagnifierCallback).onPerformScaleAction(
+                eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true));
 
         // TODO: Verify the final state when the mirror surface is visible.
         assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null));
@@ -668,6 +682,36 @@
     }
 
     @Test
+    public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        View closeButton = getInternalView(R.id.close_button);
+        View bottomRightCorner = getInternalView(R.id.bottom_right_corner);
+        View bottomLeftCorner = getInternalView(R.id.bottom_left_corner);
+        View topRightCorner = getInternalView(R.id.top_right_corner);
+        View topLeftCorner = getInternalView(R.id.top_left_corner);
+
+        assertEquals(View.VISIBLE, closeButton.getVisibility());
+        assertEquals(View.VISIBLE, bottomRightCorner.getVisibility());
+        assertEquals(View.VISIBLE, bottomLeftCorner.getVisibility());
+        assertEquals(View.VISIBLE, topRightCorner.getVisibility());
+        assertEquals(View.VISIBLE, topLeftCorner.getVisibility());
+
+        final View mirrorView = mWindowManager.getAttachedView();
+        mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), null);
+
+        assertEquals(View.GONE, closeButton.getVisibility());
+        assertEquals(View.GONE, bottomRightCorner.getVisibility());
+        assertEquals(View.GONE, bottomLeftCorner.getVisibility());
+        assertEquals(View.GONE, topRightCorner.getVisibility());
+        assertEquals(View.GONE, topLeftCorner.getVisibility());
+    }
+
+    @Test
     public void enableWindowMagnification_hasA11yWindowTitle() {
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index ce96708..eddb8d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -19,7 +19,6 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
-import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -28,7 +27,11 @@
 import static junit.framework.Assert.assertNotNull;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -37,6 +40,7 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.database.ContentObserver;
+import android.graphics.Rect;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
@@ -49,23 +53,27 @@
 import android.widget.CompoundButton;
 import android.widget.LinearLayout;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView;
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.After;
 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;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@TestableLooper.RunWithLooper
 public class WindowMagnificationSettingsTest extends SysuiTestCase {
 
     private static final int MAGNIFICATION_SIZE_SMALL = 1;
@@ -73,6 +81,7 @@
     private static final int MAGNIFICATION_SIZE_LARGE = 3;
 
     private ViewGroup mSettingView;
+    private SeekBarWithIconButtonsView mZoomSeekbar;
     @Mock
     private AccessibilityManager mAccessibilityManager;
     @Mock
@@ -85,6 +94,11 @@
     private WindowMagnificationSettings mWindowMagnificationSettings;
     private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
 
+    private ArgumentCaptor<Float> mSecureSettingsScaleCaptor;
+    private ArgumentCaptor<String> mSecureSettingsNameCaptor;
+    private ArgumentCaptor<Integer> mSecureSettingsUserHandleCaptor;
+    private ArgumentCaptor<Float> mCallbackMagnifierScaleCaptor;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -100,6 +114,11 @@
                 mSecureSettings);
 
         mSettingView = mWindowMagnificationSettings.getSettingView();
+        mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
+        mSecureSettingsScaleCaptor = ArgumentCaptor.forClass(Float.class);
+        mSecureSettingsNameCaptor = ArgumentCaptor.forClass(String.class);
+        mSecureSettingsUserHandleCaptor = ArgumentCaptor.forClass(Integer.class);
+        mCallbackMagnifierScaleCaptor = ArgumentCaptor.forClass(Float.class);
     }
 
     @After
@@ -275,6 +294,39 @@
     }
 
     @Test
+    public void onScreenSizeChanged_resetPositionToRightBottomCorner() {
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // move the panel to the center of draggable window bounds
+        mWindowMagnificationSettings.mParams.x =
+                mWindowMagnificationSettings.mDraggableWindowBounds.centerX();
+        mWindowMagnificationSettings.mParams.y =
+                mWindowMagnificationSettings.mDraggableWindowBounds.centerY();
+        mWindowMagnificationSettings.updateButtonViewLayoutIfNeeded();
+
+        final Rect testWindowBounds = new Rect(
+                mWindowManager.getCurrentWindowMetrics().getBounds());
+        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+                testWindowBounds.right + 200, testWindowBounds.bottom + 50);
+        mWindowManager.setWindowBounds(testWindowBounds);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mWindowMagnificationSettings.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+        });
+
+        // the panel position should be reset to the bottom-right corner
+        assertEquals(
+                mWindowMagnificationSettings.mParams.x,
+                mWindowMagnificationSettings.mDraggableWindowBounds.right);
+        assertEquals(
+                mWindowMagnificationSettings.mParams.y,
+                mWindowMagnificationSettings.mDraggableWindowBounds.bottom);
+    }
+
+    @Test
     public void showSettingsPanel_observerRegistered() {
         setupMagnificationCapabilityAndMode(
                 /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
@@ -300,6 +352,160 @@
         verify(mSecureSettings).unregisterContentObserver(any(ContentObserver.class));
     }
 
+    @Test
+    public void seekbarProgress_justInflated_maxValueAndProgressSetCorrectly() {
+        mWindowMagnificationSettings.setMagnificationScale(2f);
+        mWindowMagnificationSettings.inflateView();
+
+        // inflateView() would create new settingsView in WindowMagnificationSettings so we
+        // need to retrieve the new mZoomSeekbar
+        mSettingView = mWindowMagnificationSettings.getSettingView();
+        mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(10);
+        assertThat(mZoomSeekbar.getMax()).isEqualTo(70);
+    }
+
+    @Test
+    public void seekbarProgress_minMagnification_seekbarProgressIsCorrect() {
+        mWindowMagnificationSettings.setMagnificationScale(1f);
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // Seekbar index from 0 to 70. 1.0f scale (A11Y_SCALE_MIN_VALUE) would correspond to 0.
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(0);
+    }
+
+    @Test
+    public void seekbarProgress_belowMinMagnification_seekbarProgressIsZero() {
+        mWindowMagnificationSettings.setMagnificationScale(0f);
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(0);
+    }
+
+    @Test
+    public void seekbarProgress_magnificationBefore_seekbarProgressIsHalf() {
+        mWindowMagnificationSettings.setMagnificationScale(4f);
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // float scale : from 1.0f to 8.0f, seekbar index from 0 to 70.
+        // 4.0f would correspond to 30.
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(30);
+    }
+
+    @Test
+    public void seekbarProgress_maxMagnificationBefore_seekbarProgressIsMax() {
+        mWindowMagnificationSettings.setMagnificationScale(8f);
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // 8.0f is max magnification {@link MagnificationScaleProvider#MAX_SCALE}.
+        // Max zoom seek bar is 70.
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(70);
+    }
+
+    @Test
+    public void seekbarProgress_aboveMaxMagnificationBefore_seekbarProgressIsMax() {
+        mWindowMagnificationSettings.setMagnificationScale(9f);
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // Max zoom seek bar is 70.
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(70);
+    }
+
+    @Test
+    public void onSeekBarProgressChanged_fromUserFalse_callbackNotTriggered() {
+        OnSeekBarWithIconButtonsChangeListener onChangeListener =
+                mZoomSeekbar.getOnSeekBarWithIconButtonsChangeListener();
+        onChangeListener.onProgressChanged(
+                mZoomSeekbar.getSeekbar(), /* progress= */ 30, /* fromUser= */ false);
+
+        verify(mWindowMagnificationSettingsCallback, never())
+                .onMagnifierScale(/* scale= */ anyFloat(), /* updatePersistence= */ eq(false));
+    }
+
+    @Test
+    public void onSeekBarProgressChangedToRoughlyHalf_fromUserTrue_callbackUpdated() {
+        OnSeekBarWithIconButtonsChangeListener onChangeListener =
+                mZoomSeekbar.getOnSeekBarWithIconButtonsChangeListener();
+        onChangeListener.onProgressChanged(
+                mZoomSeekbar.getSeekbar(), /* progress= */ 30, /* fromUser= */ true);
+
+        verifyCallbackOnMagnifierScale(4f);
+    }
+
+    @Test
+    public void onSeekBarProgressChangedToMin_fromUserTrue_callbackUpdated() {
+        OnSeekBarWithIconButtonsChangeListener onChangeListener =
+                mZoomSeekbar.getOnSeekBarWithIconButtonsChangeListener();
+        onChangeListener.onProgressChanged(
+                mZoomSeekbar.getSeekbar(), /* progress= */ 0, /* fromUser= */ true);
+
+        verifyCallbackOnMagnifierScale(1f);
+    }
+
+    @Test
+    public void onSeekBarProgressChangedToMax_fromUserTrue_callbackUpdated() {
+        OnSeekBarWithIconButtonsChangeListener onChangeListener =
+                mZoomSeekbar.getOnSeekBarWithIconButtonsChangeListener();
+        onChangeListener.onProgressChanged(
+                mZoomSeekbar.getSeekbar(), /* progress= */ 70, /* fromUser= */ true);
+
+        verifyCallbackOnMagnifierScale(8f);
+    }
+
+    @Test
+    public void onSeekbarUserInteractionFinalized_persistedScaleUpdated() {
+        OnSeekBarWithIconButtonsChangeListener onChangeListener =
+                mZoomSeekbar.getOnSeekBarWithIconButtonsChangeListener();
+
+        mZoomSeekbar.setProgress(30);
+        onChangeListener.onUserInteractionFinalized(
+                mZoomSeekbar.getSeekbar(),
+                OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER);
+
+        // should trigger callback to update magnifier scale and persist the scale
+        verify(mWindowMagnificationSettingsCallback)
+                .onMagnifierScale(/* scale= */ eq(4f), /* updatePersistence= */ eq(true));
+    }
+
+    @Test
+    public void seekbarProgress_scaleUpdatedAfterSettingPanelOpened_progressAlsoUpdated() {
+        setupMagnificationCapabilityAndMode(
+                /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
+                /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+        mWindowMagnificationSettings.showSettingPanel();
+
+        // Simulate outside changes.
+        mWindowMagnificationSettings.setMagnificationScale(4f);
+
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(30);
+    }
+
+    private void verifyCallbackOnMagnifierScale(float scale) {
+        verify(mWindowMagnificationSettingsCallback)
+                .onMagnifierScale(mCallbackMagnifierScaleCaptor.capture(), anyBoolean());
+        assertThat(mCallbackMagnifierScaleCaptor.getValue()).isWithin(0.01f).of(scale);
+    }
+
     private <T extends View> T getInternalView(@IdRes int idRes) {
         T view = mSettingView.findViewById(idRes);
         assertNotNull(view);
@@ -308,12 +514,12 @@
 
     private void setupMagnificationCapabilityAndMode(int capability, int mode) {
         when(mSecureSettings.getIntForUser(
-                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
-                ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
-                UserHandle.USER_CURRENT)).thenReturn(capability);
+                eq(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY),
+                anyInt(),
+                eq(UserHandle.USER_CURRENT))).thenReturn(capability);
         when(mSecureSettings.getIntForUser(
-                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
-                ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
-                UserHandle.USER_CURRENT)).thenReturn(mode);
+                eq(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE),
+                anyInt(),
+                eq(UserHandle.USER_CURRENT))).thenReturn(mode);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index 38ecec0..d75781a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -110,7 +110,7 @@
             mWindowMagnification.mMagnificationSettingsControllerCallback
                     .onSettingsPanelVisibilityChanged(TEST_DISPLAY, /* shown= */ true);
             return null;
-        }).when(mMagnificationSettingsController).showMagnificationSettings();
+        }).when(mMagnificationSettingsController).toggleSettingsPanelVisibility();
         doAnswer(invocation -> {
             mWindowMagnification.mMagnificationSettingsControllerCallback
                     .onSettingsPanelVisibilityChanged(TEST_DISPLAY, /* shown= */ false);
@@ -163,13 +163,15 @@
     @Test
     public void onPerformScaleAction_enabled_notifyCallback() throws RemoteException {
         final float newScale = 4.0f;
+        final boolean updatePersistence = true;
         mCommandQueue.requestWindowMagnificationConnection(true);
         waitForIdleSync();
 
         mWindowMagnification.mWindowMagnifierCallback
-                .onPerformScaleAction(TEST_DISPLAY, newScale);
+                .onPerformScaleAction(TEST_DISPLAY, newScale, updatePersistence);
 
-        verify(mConnectionCallback).onPerformScaleAction(TEST_DISPLAY, newScale);
+        verify(mConnectionCallback).onPerformScaleAction(
+                eq(TEST_DISPLAY), eq(newScale), eq(updatePersistence));
     }
 
     @Test
@@ -198,7 +200,7 @@
         mWindowMagnification.mWindowMagnifierCallback.onClickSettingsButton(TEST_DISPLAY);
         waitForIdleSync();
 
-        verify(mMagnificationSettingsController).showMagnificationSettings();
+        verify(mMagnificationSettingsController).toggleSettingsPanelVisibility();
         verify(mA11yLogger).log(
                 eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED));
     }
@@ -249,10 +251,12 @@
         mCommandQueue.requestWindowMagnificationConnection(true);
         waitForIdleSync();
         final float scale = 3.0f;
+        final boolean updatePersistence = false;
         mWindowMagnification.mMagnificationSettingsControllerCallback.onMagnifierScale(
-                TEST_DISPLAY, scale);
+                TEST_DISPLAY, scale, updatePersistence);
 
-        verify(mConnectionCallback).onPerformScaleAction(eq(TEST_DISPLAY), eq(scale));
+        verify(mConnectionCallback).onPerformScaleAction(
+                eq(TEST_DISPLAY), eq(scale), eq(updatePersistence));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
index f6ca938..fd258e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
@@ -29,7 +29,8 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissView;
+import com.android.wm.shell.bubbles.DismissViewUtils;
+import com.android.wm.shell.common.bubbles.DismissView;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -63,6 +64,7 @@
         final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
                 stubMenuViewAppearance);
         mDismissView = spy(new DismissView(mContext));
+        DismissViewUtils.setup(mDismissView);
         mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index d4efbe4..98be49f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -40,7 +40,8 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.MotionEventHelper;
 import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissView;
+import com.android.wm.shell.bubbles.DismissViewUtils;
+import com.android.wm.shell.common.bubbles.DismissView;
 
 import org.junit.After;
 import org.junit.Before;
@@ -88,6 +89,7 @@
         mStubMenuView.setTranslationY(0);
         mMenuAnimationController = spy(new MenuAnimationController(mStubMenuView));
         mDismissView = spy(new DismissView(mContext));
+        DismissViewUtils.setup(mDismissView);
         mDismissAnimationController =
                 spy(new DismissAnimationController(mDismissView, mStubMenuView));
         mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
index f10c21b..7b99314 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
@@ -15,16 +15,20 @@
  */
 package com.android.systemui.accessibility.fontscaling
 
+import android.content.res.Configuration
 import android.os.Handler
 import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.ViewGroup
+import android.widget.Button
 import android.widget.SeekBar
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.whenever
@@ -38,6 +42,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
+import org.mockito.Mock
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -58,21 +63,24 @@
     private lateinit var secureSettings: SecureSettings
     private lateinit var systemClock: FakeSystemClock
     private lateinit var backgroundDelayableExecutor: FakeExecutor
+    private lateinit var testableLooper: TestableLooper
     private val fontSizeValueArray: Array<String> =
         mContext
             .getResources()
             .getStringArray(com.android.settingslib.R.array.entryvalues_font_size)
 
+    @Mock private lateinit var userTracker: UserTracker
     @Captor
-    private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
+    private lateinit var seekBarChangeCaptor: ArgumentCaptor<OnSeekBarWithIconButtonsChangeListener>
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        val mainHandler = Handler(TestableLooper.get(this).getLooper())
+        testableLooper = TestableLooper.get(this)
+        val mainHandler = Handler(testableLooper.looper)
         systemSettings = FakeSettings()
         // Guarantee that the systemSettings always starts with the default font scale.
-        systemSettings.putFloat(Settings.System.FONT_SCALE, 1.0f)
+        systemSettings.putFloatForUser(Settings.System.FONT_SCALE, 1.0f, userTracker.userId)
         secureSettings = FakeSettings()
         systemClock = FakeSystemClock()
         backgroundDelayableExecutor = FakeExecutor(systemClock)
@@ -82,6 +90,7 @@
                 systemSettings,
                 secureSettings,
                 systemClock,
+                userTracker,
                 mainHandler,
                 backgroundDelayableExecutor
             )
@@ -93,7 +102,12 @@
 
         val seekBar: SeekBar = fontScalingDialog.findViewById<SeekBar>(R.id.seekbar)!!
         val progress: Int = seekBar.getProgress()
-        val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
+        val currentScale =
+            systemSettings.getFloatForUser(
+                Settings.System.FONT_SCALE,
+                /* def= */ 1.0f,
+                userTracker.userId
+            )
 
         assertThat(currentScale).isEqualTo(fontSizeValueArray[progress].toFloat())
 
@@ -119,7 +133,12 @@
         backgroundDelayableExecutor.advanceClockToNext()
         backgroundDelayableExecutor.runAllReady()
 
-        val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
+        val currentScale =
+            systemSettings.getFloatForUser(
+                Settings.System.FONT_SCALE,
+                /* def= */ 1.0f,
+                userTracker.userId
+            )
         assertThat(seekBar.getProgress()).isEqualTo(1)
         assertThat(currentScale).isEqualTo(fontSizeValueArray[1].toFloat())
 
@@ -145,7 +164,12 @@
         backgroundDelayableExecutor.advanceClockToNext()
         backgroundDelayableExecutor.runAllReady()
 
-        val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
+        val currentScale =
+            systemSettings.getFloatForUser(
+                Settings.System.FONT_SCALE,
+                /* def= */ 1.0f,
+                userTracker.userId
+            )
         assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2)
         assertThat(currentScale)
             .isEqualTo(fontSizeValueArray[fontSizeValueArray.size - 2].toFloat())
@@ -157,18 +181,24 @@
     fun progressChanged_keyWasNotSetBefore_fontScalingHasBeenChangedIsOn() {
         fontScalingDialog.show()
 
-        val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
-            fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
-        secureSettings.putInt(Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, OFF)
+        val iconStartFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_start_frame)!!
+        secureSettings.putIntForUser(
+            Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
+            OFF,
+            userTracker.userId
+        )
 
-        // Default seekbar progress for font size is 1, set it to another progress 0
-        seekBarWithIconButtonsView.setProgress(0)
+        // Default seekbar progress for font size is 1, click start icon to decrease the progress
+        iconStartFrame.performClick()
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
         backgroundDelayableExecutor.runAllReady()
 
         val currentSettings =
-            secureSettings.getInt(
+            secureSettings.getIntForUser(
                 Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
-                /* def = */ OFF
+                /* def = */ OFF,
+                userTracker.userId
             )
         assertThat(currentSettings).isEqualTo(ON)
 
@@ -184,7 +214,7 @@
             )
             .thenReturn(slider)
         fontScalingDialog.show()
-        verify(slider).setOnSeekBarChangeListener(capture(seekBarChangeCaptor))
+        verify(slider).setOnSeekBarWithIconButtonsChangeListener(capture(seekBarChangeCaptor))
         val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
 
         // Default seekbar progress for font size is 1, simulate dragging to 0 without
@@ -199,7 +229,12 @@
         backgroundDelayableExecutor.runAllReady()
 
         // Verify that the scale of font size remains the default value 1.0f.
-        var systemScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
+        var systemScale =
+            systemSettings.getFloatForUser(
+                Settings.System.FONT_SCALE,
+                /* def= */ 1.0f,
+                userTracker.userId
+            )
         assertThat(systemScale).isEqualTo(1.0f)
 
         // Simulate releasing the finger from the seekbar.
@@ -208,8 +243,22 @@
         backgroundDelayableExecutor.advanceClockToNext()
         backgroundDelayableExecutor.runAllReady()
 
+        // SeekBar interaction is finalized.
+        seekBarChangeCaptor.value.onUserInteractionFinalized(
+            seekBar,
+            OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER
+        )
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
+        backgroundDelayableExecutor.runAllReady()
+
         // Verify that the scale of font size has been updated.
-        systemScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def= */ 1.0f)
+        systemScale =
+            systemSettings.getFloatForUser(
+                Settings.System.FONT_SCALE,
+                /* def= */ 1.0f,
+                userTracker.userId
+            )
         assertThat(systemScale).isEqualTo(fontSizeValueArray[0].toFloat())
 
         fontScalingDialog.dismiss()
@@ -224,7 +273,7 @@
             )
             .thenReturn(slider)
         fontScalingDialog.show()
-        verify(slider).setOnSeekBarChangeListener(capture(seekBarChangeCaptor))
+        verify(slider).setOnSeekBarWithIconButtonsChangeListener(capture(seekBarChangeCaptor))
         val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
 
         // Default seekbar progress for font size is 1, simulate dragging to 0 without
@@ -241,4 +290,26 @@
         verify(fontScalingDialog).createTextPreview(/* index= */ 0)
         fontScalingDialog.dismiss()
     }
+
+    @Test
+    fun changeFontSize_buttonIsDisabledBeforeFontSizeChangeFinishes() {
+        fontScalingDialog.show()
+
+        val iconEndFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_end_frame)!!
+        val doneButton: Button = fontScalingDialog.findViewById(com.android.internal.R.id.button1)!!
+
+        iconEndFrame.performClick()
+        backgroundDelayableExecutor.runAllReady()
+        backgroundDelayableExecutor.advanceClockToNext()
+        backgroundDelayableExecutor.runAllReady()
+
+        // Verify that the button is disabled before receiving onConfigurationChanged
+        assertThat(doneButton.isEnabled).isFalse()
+
+        val config = Configuration()
+        config.fontScale = 1.15f
+        fontScalingDialog.onConfigurationChanged(config)
+        testableLooper.processAllMessages()
+        assertThat(doneButton.isEnabled).isTrue()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
new file mode 100644
index 0000000..a7e7dd0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.animation
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.core.animation.doOnEnd
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.doOnEnd
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class AnimatorTestRuleOrderTest : SysuiTestCase() {
+
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
+    var value1: Float = -1f
+    var value2: Float = -1f
+
+    private inline fun animateThisX(
+        propertyName: String,
+        duration: Long,
+        startDelay: Long = 0,
+        crossinline onEndAction: () -> Unit,
+    ) {
+        androidx.core.animation.ObjectAnimator.ofFloat(this, propertyName, 0f, 1f).also {
+            it.interpolator = null
+            it.duration = duration
+            it.startDelay = startDelay
+            it.doOnEnd { onEndAction() }
+            it.start()
+        }
+    }
+
+    private inline fun animateThisP(
+        propertyName: String,
+        duration: Long,
+        startDelay: Long = 0,
+        crossinline onEndAction: () -> Unit,
+    ) {
+        android.animation.ObjectAnimator.ofFloat(this, propertyName, 0f, 1f).also {
+            it.interpolator = null
+            it.duration = duration
+            it.startDelay = startDelay
+            it.doOnEnd { onEndAction() }
+            it.start()
+        }
+    }
+
+    @Test
+    fun testTwoAnimators() {
+        var ended1 = false
+        var ended2 = false
+        animateThisP("value1", duration = 100) { ended1 = true }
+        animateThisX("value2", duration = 200) { ended2 = true }
+        assertThat(value1).isEqualTo(0f)
+        assertThat(value2).isEqualTo(0f)
+        assertThat(ended1).isFalse()
+        assertThat(ended2).isFalse()
+
+        animatorTestRule.advanceTimeBy(99)
+        assertThat(value1).isEqualTo(0.99f)
+        assertThat(value2).isEqualTo(0.495f)
+        assertThat(ended1).isFalse()
+        assertThat(ended2).isFalse()
+
+        animatorTestRule.advanceTimeBy(1)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(value2).isEqualTo(0.5f)
+        assertThat(ended1).isTrue()
+        assertThat(ended2).isFalse()
+
+        animatorTestRule.advanceTimeBy(99)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(value2).isEqualTo(0.995f)
+        assertThat(ended1).isTrue()
+        assertThat(ended2).isFalse()
+
+        animatorTestRule.advanceTimeBy(1)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(value2).isEqualTo(1f)
+        assertThat(ended1).isTrue()
+        assertThat(ended2).isTrue()
+    }
+
+    @Test
+    fun testChainedAnimatorsPlatformThenX() {
+        var ended1 = false
+        var ended2 = false
+        animateThisP("value1", duration = 100) {
+            ended1 = true
+            animateThisX("value2", duration = 100) { ended2 = true }
+        }
+
+        assertThat(value1).isEqualTo(0f)
+        assertThat(value2).isEqualTo(-1f)
+        assertThat(ended1).isFalse()
+        assertThat(ended2).isFalse()
+
+        animatorTestRule.advanceTimeBy(50)
+        assertThat(value1).isEqualTo(0.5f)
+        assertThat(value2).isEqualTo(-1f)
+        assertThat(ended1).isFalse()
+        assertThat(ended2).isFalse()
+
+        animatorTestRule.advanceTimeBy(50)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(value2).isEqualTo(0f)
+        assertThat(ended1).isTrue()
+        assertThat(ended2).isFalse()
+
+        animatorTestRule.advanceTimeBy(50)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(value2).isEqualTo(0.5f)
+        assertThat(ended1).isTrue()
+        assertThat(ended2).isFalse()
+
+        animatorTestRule.advanceTimeBy(50)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(value2).isEqualTo(1f)
+        assertThat(ended1).isTrue()
+        assertThat(ended2).isTrue()
+    }
+
+    @Test
+    fun testChainedAnimatorsXThenPlatform() {
+        var ended1 = false
+        var ended2 = false
+        animateThisX("value1", duration = 100) {
+            ended1 = true
+            animateThisP("value2", duration = 100) { ended2 = true }
+        }
+
+        assertThat(value1).isEqualTo(0f)
+        assertThat(value2).isEqualTo(-1f)
+        assertThat(ended1).isFalse()
+        assertThat(ended2).isFalse()
+
+        animatorTestRule.advanceTimeBy(50)
+        assertThat(value1).isEqualTo(0.5f)
+        assertThat(value2).isEqualTo(-1f)
+        assertThat(ended1).isFalse()
+        assertThat(ended2).isFalse()
+
+        animatorTestRule.advanceTimeBy(50)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(value2).isEqualTo(0f)
+        assertThat(ended1).isTrue()
+        assertThat(ended2).isFalse()
+
+        animatorTestRule.advanceTimeBy(50)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(value2).isEqualTo(0.5f)
+        assertThat(ended1).isTrue()
+        assertThat(ended2).isFalse()
+
+        animatorTestRule.advanceTimeBy(50)
+        assertThat(value1).isEqualTo(1f)
+        assertThat(value2).isEqualTo(1f)
+        assertThat(ended1).isTrue()
+        assertThat(ended2).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index 316de59..2233e322 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -70,7 +70,7 @@
         assertTrue(dialog.isShowing)
 
         // The dialog is now fullscreen.
-        val window = dialog.window
+        val window = checkNotNull(dialog.window)
         val decorView = window.decorView as DecorView
         assertEquals(MATCH_PARENT, window.attributes.width)
         assertEquals(MATCH_PARENT, window.attributes.height)
@@ -172,14 +172,15 @@
         // Important: the power menu animation relies on this behavior to know when to animate (see
         // http://ag/16774605).
         val dialog = runOnMainThreadAndWaitForIdleSync { TestDialog(context) }
-        dialog.window.setWindowAnimations(0)
-        assertEquals(0, dialog.window.attributes.windowAnimations)
+        val window = checkNotNull(dialog.window)
+        window.setWindowAnimations(0)
+        assertEquals(0, window.attributes.windowAnimations)
 
         val touchSurface = createTouchSurface()
         runOnMainThreadAndWaitForIdleSync {
             dialogLaunchAnimator.showFromView(dialog, touchSurface)
         }
-        assertNotEquals(0, dialog.window.attributes.windowAnimations)
+        assertNotEquals(0, window.attributes.windowAnimations)
     }
 
     @Test
@@ -351,13 +352,14 @@
 
         init {
             // We need to set the window type for dialogs shown by SysUI, otherwise WM will throw.
-            window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+            checkNotNull(window).setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
         }
 
         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
             setContentView(contentView)
 
+            val window = checkNotNull(window)
             window.setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
             window.setBackgroundDrawable(windowBackground)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
index 0798d73..d1ac0e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
@@ -21,6 +21,7 @@
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.view.LaunchableFrameLayout
 import org.junit.Assert.assertThrows
 import org.junit.Test
 import org.junit.runner.RunWith
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 da9ceb4..212dad7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -8,6 +8,7 @@
 import android.widget.LinearLayout
 import android.widget.RelativeLayout
 import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.children
 import junit.framework.Assert.assertEquals
@@ -19,7 +20,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import com.android.app.animation.Interpolators
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -178,7 +178,7 @@
     }
 
     @Test
-    fun animatesRootAndChildren() {
+    fun animatesRootAndChildren_withoutExcludedViews() {
         setUpRootWithChildren()
 
         val success = ViewHierarchyAnimator.animate(rootView)
@@ -208,6 +208,40 @@
     }
 
     @Test
+    fun animatesRootAndChildren_withExcludedViews() {
+        setUpRootWithChildren()
+
+        val success = ViewHierarchyAnimator.animate(
+            rootView,
+            excludedViews = setOf(rootView.getChildAt(0))
+        )
+        // Change all bounds.
+        rootView.measure(
+                View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+        )
+        rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
+
+        assertTrue(success)
+        assertNotNull(rootView.getTag(R.id.tag_animator))
+        assertNull(rootView.getChildAt(0).getTag(R.id.tag_animator))
+        assertNotNull(rootView.getChildAt(1).getTag(R.id.tag_animator))
+        // The initial values for the affected views should be those of the previous layout, while
+        // the excluded view should be at the final values from the beginning.
+        checkBounds(rootView, l = 0, t = 0, r = 200, b = 100)
+        checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 90, b = 100)
+        checkBounds(rootView.getChildAt(1), l = 100, t = 0, r = 200, b = 100)
+        endAnimation(rootView)
+        assertNull(rootView.getTag(R.id.tag_animator))
+        assertNull(rootView.getChildAt(0).getTag(R.id.tag_animator))
+        assertNull(rootView.getChildAt(1).getTag(R.id.tag_animator))
+        // The end values should be those of the latest layout.
+        checkBounds(rootView, l = 10, t = 20, r = 200, b = 120)
+        checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 90, b = 100)
+        checkBounds(rootView.getChildAt(1), l = 90, t = 0, r = 180, b = 100)
+    }
+
+    @Test
     fun animatesInvisibleViews() {
         rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
         rootView.visibility = View.INVISIBLE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
new file mode 100644
index 0000000..d3a2a73
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.authentication.data.repository
+
+import android.app.admin.DevicePolicyManager
+import android.content.Intent
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Function
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class AuthenticationRepositoryTest : SysuiTestCase() {
+
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>
+
+    private val testUtils = SceneTestUtils(this)
+    private val testScope = testUtils.testScope
+    private val userRepository = FakeUserRepository()
+
+    private lateinit var underTest: AuthenticationRepository
+
+    private var currentSecurityMode: KeyguardSecurityModel.SecurityMode =
+        KeyguardSecurityModel.SecurityMode.PIN
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        userRepository.setUserInfos(USER_INFOS)
+        runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }
+        whenever(getSecurityMode.apply(anyInt())).thenAnswer { currentSecurityMode }
+
+        underTest =
+            AuthenticationRepositoryImpl(
+                applicationScope = testScope.backgroundScope,
+                getSecurityMode = getSecurityMode,
+                backgroundDispatcher = testUtils.testDispatcher,
+                userRepository = userRepository,
+                keyguardRepository = testUtils.keyguardRepository,
+                lockPatternUtils = lockPatternUtils,
+                broadcastDispatcher = fakeBroadcastDispatcher,
+            )
+    }
+
+    @Test
+    fun authenticationMethod() =
+        testScope.runTest {
+            val authMethod by collectLastValue(underTest.authenticationMethod)
+            runCurrent()
+            dispatchBroadcast()
+            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin)
+            assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin)
+
+            setSecurityModeAndDispatchBroadcast(KeyguardSecurityModel.SecurityMode.Pattern)
+            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pattern)
+            assertThat(underTest.getAuthenticationMethod())
+                .isEqualTo(AuthenticationMethodModel.Pattern)
+
+            setSecurityModeAndDispatchBroadcast(KeyguardSecurityModel.SecurityMode.None)
+            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None)
+            assertThat(underTest.getAuthenticationMethod())
+                .isEqualTo(AuthenticationMethodModel.None)
+        }
+
+    @Test
+    fun isAutoConfirmEnabled() =
+        testScope.runTest {
+            whenever(lockPatternUtils.isAutoPinConfirmEnabled(USER_INFOS[0].id)).thenReturn(true)
+            whenever(lockPatternUtils.isAutoPinConfirmEnabled(USER_INFOS[1].id)).thenReturn(false)
+
+            val values by collectValues(underTest.isAutoConfirmEnabled)
+            assertThat(values.first()).isFalse()
+            assertThat(values.last()).isTrue()
+
+            userRepository.setSelectedUserInfo(USER_INFOS[1])
+            assertThat(values.last()).isFalse()
+        }
+
+    @Test
+    fun isPatternVisible() =
+        testScope.runTest {
+            whenever(lockPatternUtils.isVisiblePatternEnabled(USER_INFOS[0].id)).thenReturn(false)
+            whenever(lockPatternUtils.isVisiblePatternEnabled(USER_INFOS[1].id)).thenReturn(true)
+
+            val values by collectValues(underTest.isPatternVisible)
+            assertThat(values.first()).isTrue()
+            assertThat(values.last()).isFalse()
+
+            userRepository.setSelectedUserInfo(USER_INFOS[1])
+            assertThat(values.last()).isTrue()
+        }
+
+    private fun setSecurityModeAndDispatchBroadcast(
+        securityMode: KeyguardSecurityModel.SecurityMode,
+    ) {
+        currentSecurityMode = securityMode
+        dispatchBroadcast()
+    }
+
+    private fun dispatchBroadcast() {
+        fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+            context,
+            Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)
+        )
+    }
+
+    companion object {
+        private val USER_INFOS =
+            listOf(
+                UserInfo(
+                    /* id= */ 100,
+                    /* name= */ "First user",
+                    /* flags= */ 0,
+                ),
+                UserInfo(
+                    /* id= */ 101,
+                    /* name= */ "Second user",
+                    /* flags= */ 0,
+                ),
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 1990c8f..707b1b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -16,15 +16,25 @@
 
 package com.android.systemui.authentication.domain.interactor
 
+import android.app.admin.DevicePolicyManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -36,78 +46,150 @@
 @RunWith(JUnit4::class)
 class AuthenticationInteractorTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val repository: AuthenticationRepository = utils.authenticationRepository()
+    private val sceneInteractor = utils.sceneInteractor()
     private val underTest =
         utils.authenticationInteractor(
             repository = repository,
+            sceneInteractor = sceneInteractor,
         )
 
     @Test
-    fun authMethod() =
+    fun authenticationMethod() =
         testScope.runTest {
             val authMethod by collectLastValue(underTest.authenticationMethod)
-            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.PIN(1234))
+            runCurrent()
+            assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Pin)
+            assertThat(underTest.getAuthenticationMethod())
+                .isEqualTo(DomainLayerAuthenticationMethodModel.Pin)
 
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
-            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Password("password"))
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Password
+            )
+
+            assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Password)
+            assertThat(underTest.getAuthenticationMethod())
+                .isEqualTo(DomainLayerAuthenticationMethodModel.Password)
         }
 
     @Test
-    fun isUnlocked_whenAuthMethodIsNone_isTrue() =
+    fun authenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            assertThat(isUnlocked).isFalse()
-
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.None)
-
-            assertThat(isUnlocked).isTrue()
-        }
-
-    @Test
-    fun unlockDevice() =
-        testScope.runTest {
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            assertThat(isUnlocked).isFalse()
-
-            underTest.unlockDevice()
+            val authMethod by collectLastValue(underTest.authenticationMethod)
             runCurrent()
 
-            assertThat(isUnlocked).isTrue()
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(true)
+            }
+
+            assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Swipe)
+            assertThat(underTest.getAuthenticationMethod())
+                .isEqualTo(DomainLayerAuthenticationMethodModel.Swipe)
         }
 
     @Test
-    fun biometricUnlock() =
+    fun authenticationMethod_none_whenLockscreenDisabled() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            assertThat(isUnlocked).isFalse()
-
-            underTest.biometricUnlock()
+            val authMethod by collectLastValue(underTest.authenticationMethod)
             runCurrent()
 
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(false)
+            }
+
+            assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.None)
+            assertThat(underTest.getAuthenticationMethod())
+                .isEqualTo(DomainLayerAuthenticationMethodModel.None)
+        }
+
+    @Test
+    fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(false)
+                // Toggle isUnlocked, twice.
+                //
+                // This is done because the underTest.isUnlocked flow doesn't receive values from
+                // just changing the state above; the actual isUnlocked state needs to change to
+                // cause the logic under test to "pick up" the current state again.
+                //
+                // It is done twice to make sure that we don't actually change the isUnlocked state
+                // from what it originally was.
+                setUnlocked(!utils.authenticationRepository.isUnlocked.value)
+                runCurrent()
+                setUnlocked(!utils.authenticationRepository.isUnlocked.value)
+                runCurrent()
+            }
+
             assertThat(isUnlocked).isTrue()
         }
 
     @Test
-    fun toggleBypassEnabled() =
+    fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isTrue() =
         testScope.runTest {
-            val isBypassEnabled by collectLastValue(underTest.isBypassEnabled)
-            assertThat(isBypassEnabled).isFalse()
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(true)
+            }
 
-            underTest.toggleBypassEnabled()
-            assertThat(isBypassEnabled).isTrue()
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            assertThat(isUnlocked).isTrue()
+        }
 
-            underTest.toggleBypassEnabled()
-            assertThat(isBypassEnabled).isFalse()
+    @Test
+    fun canSwipeToDismiss_onLockscreenWithSwipe_isTrue() =
+        testScope.runTest {
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(true)
+            }
+            switchToScene(SceneKey.Lockscreen)
+
+            val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
+            assertThat(canSwipeToDismiss).isTrue()
+        }
+
+    @Test
+    fun canSwipeToDismiss_onLockscreenWithPin_isFalse() =
+        testScope.runTest {
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setLockscreenEnabled(true)
+            }
+            switchToScene(SceneKey.Lockscreen)
+
+            val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
+            assertThat(canSwipeToDismiss).isFalse()
+        }
+
+    @Test
+    fun canSwipeToDismiss_afterLockscreenDismissedInSwipeMode_isFalse() =
+        testScope.runTest {
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(true)
+            }
+            switchToScene(SceneKey.Lockscreen)
+            switchToScene(SceneKey.Gone)
+
+            val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
+            assertThat(canSwipeToDismiss).isFalse()
         }
 
     @Test
     fun isAuthenticationRequired_lockedAndSecured_true() =
         testScope.runTest {
-            underTest.lockDevice()
-            runCurrent()
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
+            utils.authenticationRepository.apply {
+                setUnlocked(false)
+                runCurrent()
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
+            }
 
             assertThat(underTest.isAuthenticationRequired()).isTrue()
         }
@@ -115,9 +197,11 @@
     @Test
     fun isAuthenticationRequired_lockedAndNotSecured_false() =
         testScope.runTest {
-            underTest.lockDevice()
-            runCurrent()
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.apply {
+                setUnlocked(false)
+                runCurrent()
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+            }
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -125,9 +209,11 @@
     @Test
     fun isAuthenticationRequired_unlockedAndSecured_false() =
         testScope.runTest {
-            underTest.unlockDevice()
-            runCurrent()
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
+            utils.authenticationRepository.apply {
+                setUnlocked(true)
+                runCurrent()
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
+            }
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -135,150 +221,122 @@
     @Test
     fun isAuthenticationRequired_unlockedAndNotSecured_false() =
         testScope.runTest {
-            underTest.unlockDevice()
-            runCurrent()
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.apply {
+                setUnlocked(true)
+                runCurrent()
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+            }
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
 
     @Test
-    fun authenticate_withCorrectPin_returnsTrueAndUnlocksDevice() =
+    fun authenticate_withCorrectPin_returnsTrue() =
         testScope.runTest {
-            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            assertThat(isUnlocked).isFalse()
-
-            assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
-            assertThat(isUnlocked).isTrue()
-            assertThat(failedAttemptCount).isEqualTo(0)
+            val isThrottled by collectLastValue(underTest.isThrottled)
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+            assertThat(isThrottled).isFalse()
         }
 
     @Test
-    fun authenticate_withIncorrectPin_returnsFalseAndDoesNotUnlockDevice() =
+    fun authenticate_withIncorrectPin_returnsFalse() =
         testScope.runTest {
-            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
+            assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))).isFalse()
+        }
 
-            assertThat(underTest.authenticate(listOf(9, 8, 7))).isFalse()
-            assertThat(isUnlocked).isFalse()
-            assertThat(failedAttemptCount).isEqualTo(1)
+    @Test(expected = IllegalArgumentException::class)
+    fun authenticate_withEmptyPin_throwsException() =
+        testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
+            underTest.authenticate(listOf())
         }
 
     @Test
-    fun authenticate_withCorrectPassword_returnsTrueAndUnlocksDevice() =
+    fun authenticate_withCorrectMaxLengthPin_returnsTrue() =
         testScope.runTest {
-            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
-            assertThat(isUnlocked).isFalse()
+            val pin = List(16) { 9 }
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                overrideCredential(pin)
+            }
+
+            assertThat(underTest.authenticate(pin)).isTrue()
+        }
+
+    @Test
+    fun authenticate_withCorrectTooLongPin_returnsFalse() =
+        testScope.runTest {
+            // Max pin length is 16 digits. To avoid issues with overflows, this test ensures
+            // that all pins > 16 decimal digits are rejected.
+
+            // If the policy changes, there is work to do in SysUI.
+            assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
+
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
+            assertThat(underTest.authenticate(List(17) { 9 })).isFalse()
+        }
+
+    @Test
+    fun authenticate_withCorrectPassword_returnsTrue() =
+        testScope.runTest {
+            val isThrottled by collectLastValue(underTest.isThrottled)
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Password
+            )
 
             assertThat(underTest.authenticate("password".toList())).isTrue()
-            assertThat(isUnlocked).isTrue()
-            assertThat(failedAttemptCount).isEqualTo(0)
+            assertThat(isThrottled).isFalse()
         }
 
     @Test
-    fun authenticate_withIncorrectPassword_returnsFalseAndDoesNotUnlockDevice() =
+    fun authenticate_withIncorrectPassword_returnsFalse() =
         testScope.runTest {
-            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(AuthenticationMethodModel.Password("password"))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Password
+            )
 
             assertThat(underTest.authenticate("alohomora".toList())).isFalse()
-            assertThat(isUnlocked).isFalse()
-            assertThat(failedAttemptCount).isEqualTo(1)
         }
 
     @Test
-    fun authenticate_withCorrectPattern_returnsTrueAndUnlocksDevice() =
+    fun authenticate_withCorrectPattern_returnsTrue() =
         testScope.runTest {
-            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(
-                    listOf(
-                        AuthenticationMethodModel.Pattern.PatternCoordinate(
-                            x = 0,
-                            y = 0,
-                        ),
-                        AuthenticationMethodModel.Pattern.PatternCoordinate(
-                            x = 0,
-                            y = 1,
-                        ),
-                        AuthenticationMethodModel.Pattern.PatternCoordinate(
-                            x = 0,
-                            y = 2,
-                        ),
-                    )
-                )
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pattern
             )
-            assertThat(isUnlocked).isFalse()
+
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
+        }
+
+    @Test
+    fun authenticate_withIncorrectPattern_returnsFalse() =
+        testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pattern
+            )
 
             assertThat(
                     underTest.authenticate(
                         listOf(
-                            AuthenticationMethodModel.Pattern.PatternCoordinate(
-                                x = 0,
-                                y = 0,
-                            ),
-                            AuthenticationMethodModel.Pattern.PatternCoordinate(
-                                x = 0,
-                                y = 1,
-                            ),
-                            AuthenticationMethodModel.Pattern.PatternCoordinate(
-                                x = 0,
-                                y = 2,
-                            ),
-                        )
-                    )
-                )
-                .isTrue()
-            assertThat(isUnlocked).isTrue()
-            assertThat(failedAttemptCount).isEqualTo(0)
-        }
-
-    @Test
-    fun authenticate_withIncorrectPattern_returnsFalseAndDoesNotUnlockDevice() =
-        testScope.runTest {
-            val failedAttemptCount by collectLastValue(underTest.failedAuthenticationAttempts)
-            val isUnlocked by collectLastValue(underTest.isUnlocked)
-            underTest.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(
-                    listOf(
-                        AuthenticationMethodModel.Pattern.PatternCoordinate(
-                            x = 0,
-                            y = 0,
-                        ),
-                        AuthenticationMethodModel.Pattern.PatternCoordinate(
-                            x = 0,
-                            y = 1,
-                        ),
-                        AuthenticationMethodModel.Pattern.PatternCoordinate(
-                            x = 0,
-                            y = 2,
-                        ),
-                    )
-                )
-            )
-            assertThat(isUnlocked).isFalse()
-
-            assertThat(
-                    underTest.authenticate(
-                        listOf(
-                            AuthenticationMethodModel.Pattern.PatternCoordinate(
+                            AuthenticationPatternCoordinate(
                                 x = 2,
                                 y = 0,
                             ),
-                            AuthenticationMethodModel.Pattern.PatternCoordinate(
+                            AuthenticationPatternCoordinate(
                                 x = 2,
                                 y = 1,
                             ),
-                            AuthenticationMethodModel.Pattern.PatternCoordinate(
+                            AuthenticationPatternCoordinate(
                                 x = 2,
                                 y = 2,
                             ),
@@ -286,18 +344,330 @@
                     )
                 )
                 .isFalse()
-            assertThat(isUnlocked).isFalse()
-            assertThat(failedAttemptCount).isEqualTo(1)
         }
 
     @Test
-    fun unlocksDevice_whenAuthMethodBecomesNone() =
+    fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() =
+        testScope.runTest {
+            val isThrottled by collectLastValue(underTest.isThrottled)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(true)
+            }
+            assertThat(
+                    underTest.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply {
+                            removeLast()
+                        },
+                        tryAutoConfirm = true
+                    )
+                )
+                .isNull()
+            assertThat(isThrottled).isFalse()
+        }
+
+    @Test
+    fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
         testScope.runTest {
             val isUnlocked by collectLastValue(underTest.isUnlocked)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(true)
+            }
+            assertThat(
+                    underTest.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 },
+                        tryAutoConfirm = true
+                    )
+                )
+                .isFalse()
             assertThat(isUnlocked).isFalse()
+        }
 
-            repository.setAuthenticationMethod(AuthenticationMethodModel.None)
+    @Test
+    fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(true)
+            }
+            assertThat(
+                    underTest.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN + listOf(7),
+                        tryAutoConfirm = true
+                    )
+                )
+                .isFalse()
+            assertThat(isUnlocked).isFalse()
+        }
 
+    @Test
+    fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(true)
+            }
+            assertThat(
+                    underTest.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN,
+                        tryAutoConfirm = true
+                    )
+                )
+                .isTrue()
             assertThat(isUnlocked).isTrue()
         }
+
+    @Test
+    fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(false)
+            }
+            assertThat(
+                    underTest.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN,
+                        tryAutoConfirm = true
+                    )
+                )
+                .isNull()
+            assertThat(isUnlocked).isFalse()
+        }
+
+    @Test
+    fun tryAutoConfirm_withoutCorrectPassword_returnsNullAndHasNoEffects() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Password
+            )
+
+            assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)).isNull()
+            assertThat(isUnlocked).isFalse()
+        }
+
+    @Test
+    fun throttling() =
+        testScope.runTest {
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            val throttling by collectLastValue(underTest.throttling)
+            val isThrottled by collectLastValue(underTest.isThrottled)
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
+            underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
+            assertThat(isUnlocked).isTrue()
+            assertThat(isThrottled).isFalse()
+            assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
+
+            utils.authenticationRepository.setUnlocked(false)
+            assertThat(isUnlocked).isFalse()
+            assertThat(isThrottled).isFalse()
+            assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
+
+            // Make many wrong attempts, but just shy of what's needed to get throttled:
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1) {
+                underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
+                assertThat(isUnlocked).isFalse()
+                assertThat(isThrottled).isFalse()
+                assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
+            }
+
+            // Make one more wrong attempt, leading to throttling:
+            underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
+            assertThat(isUnlocked).isFalse()
+            assertThat(isThrottled).isTrue()
+            assertThat(throttling)
+                .isEqualTo(
+                    AuthenticationThrottlingModel(
+                        failedAttemptCount =
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+                        remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+                    )
+                )
+
+            // Correct PIN, but throttled, so doesn't attempt it:
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isNull()
+            assertThat(isUnlocked).isFalse()
+            assertThat(isThrottled).isTrue()
+            assertThat(throttling)
+                .isEqualTo(
+                    AuthenticationThrottlingModel(
+                        failedAttemptCount =
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+                        remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+                    )
+                )
+
+            // Move the clock forward to ALMOST skip the throttling, leaving one second to go:
+            val throttleTimeoutSec =
+                FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
+                    .toInt()
+            repeat(throttleTimeoutSec - 1) { time ->
+                advanceTimeBy(1000)
+                assertThat(isThrottled).isTrue()
+                assertThat(throttling)
+                    .isEqualTo(
+                        AuthenticationThrottlingModel(
+                            failedAttemptCount =
+                                FakeAuthenticationRepository
+                                    .MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+                            remainingMs =
+                                ((throttleTimeoutSec - (time + 1)).seconds.inWholeMilliseconds)
+                                    .toInt(),
+                        )
+                    )
+            }
+
+            // Move the clock forward one more second, to completely finish the throttling period:
+            advanceTimeBy(1000)
+            assertThat(isUnlocked).isFalse()
+            assertThat(isThrottled).isFalse()
+            assertThat(throttling)
+                .isEqualTo(
+                    AuthenticationThrottlingModel(
+                        failedAttemptCount =
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+                        remainingMs = 0,
+                    )
+                )
+
+            // Correct PIN and no longer throttled so unlocks successfully:
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+            assertThat(isUnlocked).isTrue()
+            assertThat(isThrottled).isFalse()
+            assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
+        }
+
+    @Test
+    fun hintedPinLength_withoutAutoConfirm_isNull() =
+        testScope.runTest {
+            val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(false)
+            }
+
+            assertThat(hintedPinLength).isNull()
+        }
+
+    @Test
+    fun hintedPinLength_withAutoConfirmPinTooShort_isNull() =
+        testScope.runTest {
+            val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                overrideCredential(
+                    buildList {
+                        repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
+                    }
+                )
+                setAutoConfirmEnabled(true)
+            }
+
+            assertThat(hintedPinLength).isNull()
+        }
+
+    @Test
+    fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() =
+        testScope.runTest {
+            val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(true)
+                overrideCredential(
+                    buildList {
+                        repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) }
+                    }
+                )
+            }
+
+            assertThat(hintedPinLength).isEqualTo(utils.authenticationRepository.hintedPinLength)
+        }
+
+    @Test
+    fun hintedPinLength_withAutoConfirmPinTooLong_isNull() =
+        testScope.runTest {
+            val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                overrideCredential(
+                    buildList {
+                        repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
+                    }
+                )
+                setAutoConfirmEnabled(true)
+            }
+
+            assertThat(hintedPinLength).isNull()
+        }
+
+    @Test
+    fun isLockscreenDismissed() =
+        testScope.runTest {
+            val isLockscreenDismissed by collectLastValue(underTest.isLockscreenDismissed)
+            // Start on lockscreen.
+            switchToScene(SceneKey.Lockscreen)
+            assertThat(isLockscreenDismissed).isFalse()
+
+            // The user swipes down to reveal shade.
+            switchToScene(SceneKey.Shade)
+            assertThat(isLockscreenDismissed).isFalse()
+
+            // The user swipes down to reveal quick settings.
+            switchToScene(SceneKey.QuickSettings)
+            assertThat(isLockscreenDismissed).isFalse()
+
+            // The user swipes up to go back to shade.
+            switchToScene(SceneKey.Shade)
+            assertThat(isLockscreenDismissed).isFalse()
+
+            // The user swipes up to reveal bouncer.
+            switchToScene(SceneKey.Bouncer)
+            assertThat(isLockscreenDismissed).isFalse()
+
+            // The user hits back to return to lockscreen.
+            switchToScene(SceneKey.Lockscreen)
+            assertThat(isLockscreenDismissed).isFalse()
+
+            // The user swipes up to reveal bouncer.
+            switchToScene(SceneKey.Bouncer)
+            assertThat(isLockscreenDismissed).isFalse()
+
+            // The user enters correct credentials and goes to gone.
+            switchToScene(SceneKey.Gone)
+            assertThat(isLockscreenDismissed).isTrue()
+
+            // The user swipes down to reveal shade.
+            switchToScene(SceneKey.Shade)
+            assertThat(isLockscreenDismissed).isTrue()
+
+            // The user swipes down to reveal quick settings.
+            switchToScene(SceneKey.QuickSettings)
+            assertThat(isLockscreenDismissed).isTrue()
+
+            // The user swipes up to go back to shade.
+            switchToScene(SceneKey.Shade)
+            assertThat(isLockscreenDismissed).isTrue()
+
+            // The user swipes up to go back to gone.
+            switchToScene(SceneKey.Gone)
+            assertThat(isLockscreenDismissed).isTrue()
+
+            // The device goes to sleep, returning to the lockscreen.
+            switchToScene(SceneKey.Lockscreen)
+            assertThat(isLockscreenDismissed).isFalse()
+        }
+
+    private fun TestScope.switchToScene(sceneKey: SceneKey) {
+        val model = SceneModel(sceneKey)
+        val loggingReason = "reason"
+        sceneInteractor.changeScene(model, loggingReason)
+        sceneInteractor.onSceneChanged(model, loggingReason)
+        runCurrent()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
new file mode 100644
index 0000000..88c710a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.back.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.QuickSettingsController
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+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.anyBoolean
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BackActionInteractorTest : SysuiTestCase() {
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    @Mock private lateinit var shadeController: ShadeController
+    @Mock private lateinit var qsController: QuickSettingsController
+    @Mock private lateinit var shadeViewController: ShadeViewController
+
+    private lateinit var backActionInteractor: BackActionInteractor
+
+    @Before
+    fun setup() {
+        backActionInteractor =
+            BackActionInteractor(
+                statusBarStateController,
+                statusBarKeyguardViewManager,
+                shadeController,
+            )
+        backActionInteractor.setup(qsController, shadeViewController)
+    }
+
+    @Test
+    fun testOnBackRequested_keyguardCanHandleBackPressed() {
+        whenever(statusBarKeyguardViewManager.canHandleBackPressed()).thenReturn(true)
+
+        val result = backActionInteractor.onBackRequested()
+
+        assertTrue(result)
+        verify(statusBarKeyguardViewManager, atLeastOnce()).onBackPressed()
+    }
+
+    @Test
+    fun testOnBackRequested_quickSettingsIsCustomizing() {
+        whenever(qsController.isCustomizing).thenReturn(true)
+
+        val result = backActionInteractor.onBackRequested()
+
+        assertTrue(result)
+        verify(qsController, atLeastOnce()).closeQsCustomizer()
+        verify(statusBarKeyguardViewManager, never()).onBackPressed()
+    }
+
+    @Test
+    fun testOnBackRequested_quickSettingsExpanded() {
+        whenever(qsController.expanded).thenReturn(true)
+
+        val result = backActionInteractor.onBackRequested()
+
+        assertTrue(result)
+        verify(shadeViewController, atLeastOnce()).animateCollapseQs(anyBoolean())
+        verify(statusBarKeyguardViewManager, never()).onBackPressed()
+    }
+
+    @Test
+    fun testOnBackRequested_closeUserSwitcherIfOpen() {
+        whenever(shadeViewController.closeUserSwitcherIfOpen()).thenReturn(true)
+
+        val result = backActionInteractor.onBackRequested()
+
+        assertTrue(result)
+        verify(statusBarKeyguardViewManager, never()).onBackPressed()
+        verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
+    }
+
+    @Test
+    fun testOnBackRequested_returnsFalse() {
+        // make shouldBackBeHandled return false
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+        val result = backActionInteractor.onBackRequested()
+
+        assertFalse(result)
+        verify(statusBarKeyguardViewManager, never()).onBackPressed()
+        verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
index 1482f29..ec8be8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
@@ -33,10 +33,11 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
@@ -61,9 +62,9 @@
     private Handler mHandler;
     @Mock
     private ContentResolver mContentResolver;
-    private FakeFeatureFlags mFeatureFlags;
     @Mock
     private BatteryController mBatteryController;
+    private FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
 
     private BatteryMeterViewController mController;
 
@@ -74,8 +75,8 @@
         when(mBatteryMeterView.getContext()).thenReturn(mContext);
         when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources());
 
-        mFeatureFlags = new FakeFeatureFlags();
-        mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
+        mContext.getOrCreateTestableResources().addOverride(
+                R.bool.flag_battery_shield_icon, false);
     }
 
     @Test
@@ -134,7 +135,8 @@
 
     @Test
     public void shieldFlagDisabled_viewNotified() {
-        mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
+        mContext.getOrCreateTestableResources().addOverride(
+                R.bool.flag_battery_shield_icon, false);
 
         initController();
 
@@ -143,7 +145,8 @@
 
     @Test
     public void shieldFlagEnabled_viewNotified() {
-        mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, true);
+        mContext.getOrCreateTestableResources().addOverride(
+                R.bool.flag_battery_shield_icon, true);
 
         initController();
 
@@ -153,12 +156,13 @@
     private void initController() {
         mController = new BatteryMeterViewController(
                 mBatteryMeterView,
+                StatusBarLocation.HOME,
                 mUserTracker,
                 mConfigurationController,
                 mTunerService,
                 mHandler,
                 mContentResolver,
-                mFeatureFlags,
+                mFakeFeatureFlags,
                 mBatteryController
         );
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index c84efac..f0f4ca7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -131,6 +131,16 @@
     }
 
     @Test
+    fun contentDescription_isIncompatibleCharging_notCharging() {
+        mBatteryMeterView.onBatteryLevelChanged(45, true)
+        mBatteryMeterView.onIsIncompatibleChargingChanged(true)
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level, 45)
+        )
+    }
+
+    @Test
     fun changesFromEstimateToPercent_textAndContentDescriptionChanges() {
         mBatteryMeterView.onBatteryLevelChanged(15, false)
         mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
@@ -231,14 +241,33 @@
         assertThat(drawable.displayShield).isFalse()
     }
 
+    @Test
+    fun isIncompatibleChargingChanged_true_drawableGetsChargingFalse() {
+        mBatteryMeterView.onBatteryLevelChanged(45, true)
+        val drawable = getBatteryDrawable()
+
+        mBatteryMeterView.onIsIncompatibleChargingChanged(true)
+
+        assertThat(drawable.getCharging()).isFalse()
+    }
+
+    @Test
+    fun isIncompatibleChargingChanged_false_drawableGetsChargingTrue() {
+        mBatteryMeterView.onBatteryLevelChanged(45, true)
+        val drawable = getBatteryDrawable()
+
+        mBatteryMeterView.onIsIncompatibleChargingChanged(false)
+
+        assertThat(drawable.getCharging()).isTrue()
+    }
+
     private fun getBatteryDrawable(): AccessorizedBatteryDrawable {
         return (mBatteryMeterView.getChildAt(0) as ImageView)
                 .drawable as AccessorizedBatteryDrawable
     }
 
     private class Fetcher : BatteryEstimateFetcher {
-        override fun fetchBatteryTimeRemainingEstimate(
-                completion: EstimateFetchCompletion) {
+        override fun fetchBatteryTimeRemainingEstimate(completion: EstimateFetchCompletion) {
             completion.onBatteryRemainingEstimateRetrieved(ESTIMATE)
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index e3e6130..4e52e64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -139,6 +139,7 @@
     @Before
     fun setup() {
         featureFlags.set(Flags.BIOMETRIC_BP_STRONG, useNewBiometricPrompt)
+        featureFlags.set(Flags.ONE_WAY_HAPTICS_API_MIGRATION, false)
     }
 
     @After
@@ -151,7 +152,10 @@
     @Test
     fun testNotifiesAnimatedIn() {
         initializeFingerprintContainer()
-        verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+        verify(callback).onDialogAnimatedIn(
+            authContainer?.requestId ?: 0L,
+            true /* startFingerprintNow */
+        )
     }
 
     @Test
@@ -196,7 +200,10 @@
         waitForIdleSync()
 
         // attaching the view resets the state and allows this to happen again
-        verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+        verify(callback).onDialogAnimatedIn(
+            authContainer?.requestId ?: 0L,
+            true /* startFingerprintNow */
+        )
     }
 
     @Test
@@ -211,7 +218,10 @@
 
         // the first time is triggered by initializeFingerprintContainer()
         // the second time was triggered by dismissWithoutCallback()
-        verify(callback, times(2)).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+        verify(callback, times(2)).onDialogAnimatedIn(
+            authContainer?.requestId ?: 0L,
+            true /* startFingerprintNow */
+        )
     }
 
     @Test
@@ -517,10 +527,11 @@
         { authBiometricFingerprintViewModel },
         { promptSelectorInteractor },
         { bpCredentialInteractor },
-        PromptViewModel(promptSelectorInteractor, vibrator),
+        PromptViewModel(promptSelectorInteractor, vibrator, featureFlags),
         { credentialViewModel },
         Handler(TestableLooper.get(this).looper),
-        fakeExecutor
+        fakeExecutor,
+        vibrator
     ) {
         override fun postOnAnimation(runnable: Runnable) {
             runnable.run()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 8278135..48e5131 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -99,6 +99,7 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
 import com.android.systemui.util.concurrency.FakeExecution;
@@ -196,6 +197,8 @@
     private ArgumentCaptor<String> mMessageCaptor;
     @Mock
     private Resources mResources;
+    @Mock
+    private VibratorHelper mVibratorHelper;
 
     private TestableContext mContextSpy;
     private Execution mExecution;
@@ -994,6 +997,25 @@
                 eq(null) /* credentialAttestation */);
     }
 
+    @Test
+    public void testShowDialog_whenOwnerNotInForeground() {
+        PromptInfo promptInfo = createTestPromptInfo();
+        promptInfo.setAllowBackgroundAuthentication(false);
+        switchTask("other_package");
+        mAuthController.showAuthenticationDialog(promptInfo,
+                mReceiver /* receiver */,
+                new int[]{1} /* sensorIds */,
+                false /* credentialAllowed */,
+                true /* requireConfirmation */,
+                0 /* userId */,
+                0 /* operationId */,
+                "testPackage",
+                REQUEST_ID);
+
+        assertNull(mAuthController.mCurrentDialog);
+        verify(mDialog1, never()).show(any(), any());
+    }
+
     private void showDialog(int[] sensorIds, boolean credentialAllowed) {
         mAuthController.showAuthenticationDialog(createTestPromptInfo(),
                 mReceiver /* receiver */,
@@ -1077,7 +1099,7 @@
                     () -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor,
                     () -> mCredentialViewModel, () -> mPromptViewModel,
                     mInteractionJankMonitor, mHandler,
-                    mBackgroundExecutor, mUdfpsUtils);
+                    mBackgroundExecutor, mUdfpsUtils, mVibratorHelper);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index 9cabd35..5766f1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -18,6 +18,7 @@
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
+import androidx.test.filters.RequiresDevice
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.shade.ShadeExpansionStateManager
 import org.junit.Assert
@@ -30,6 +31,7 @@
 import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.junit.MockitoJUnit
 
+@RequiresDevice
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
index 9cb3b1a..60c6e1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
@@ -23,6 +23,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -86,6 +87,7 @@
 
     private final ArgumentCaptor<Notification> mNotificationArgumentCaptor =
             ArgumentCaptor.forClass(Notification.class);
+    private BiometricNotificationService mBiometricNotificationService;
     private TestableLooper mLooper;
     private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
     private KeyguardStateController.Callback mKeyguardStateControllerCallback;
@@ -104,7 +106,7 @@
         BiometricNotificationDialogFactory dialogFactory = new BiometricNotificationDialogFactory();
         BiometricNotificationBroadcastReceiver broadcastReceiver =
                 new BiometricNotificationBroadcastReceiver(mContext, dialogFactory);
-        BiometricNotificationService biometricNotificationService =
+        mBiometricNotificationService =
                 new BiometricNotificationService(mContext,
                         mKeyguardUpdateMonitor, mKeyguardStateController, handler,
                         mNotificationManager,
@@ -112,7 +114,7 @@
                         mFingerprintReEnrollNotificationOptional,
                         mFingerprintManager,
                         mFaceManager);
-        biometricNotificationService.start();
+        mBiometricNotificationService.start();
 
         ArgumentCaptor<KeyguardUpdateMonitorCallback> updateMonitorCallbackArgumentCaptor =
                 ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
@@ -231,4 +233,24 @@
                 eq(UserHandle.CURRENT));
     }
 
+    @Test
+    public void testResetFaceUnlockReEnroll_onStart() {
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+        mKeyguardUpdateMonitorCallback.onBiometricError(
+                BiometricFaceConstants.BIOMETRIC_ERROR_RE_ENROLL,
+                "Testing Face Re-enrollment" /* errString */,
+                BiometricSourceType.FACE
+        );
+
+        mBiometricNotificationService.start();
+        mKeyguardStateControllerCallback.onKeyguardShowingChanged();
+
+        mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS);
+        mLooper.processAllMessages();
+
+        verify(mNotificationManager, never()).notifyAsUser(eq(TAG), eq(FACE_NOTIFICATION_ID),
+                mNotificationArgumentCaptor.capture(), any());
+    }
+
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS
index adb10f0..5420c37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS
@@ -1,4 +1,5 @@
 set noparent
 
+# Bug component: 879035
 include /services/core/java/com/android/server/biometrics/OWNERS
 beverlyt@google.com
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 2908e75..994db46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -51,6 +51,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.airbnb.lottie.LottieAnimationView
+import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
@@ -58,11 +59,10 @@
 import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -149,8 +149,8 @@
                 mock(KeyguardStateController::class.java),
                 keyguardBouncerRepository,
                 FakeBiometricSettingsRepository(),
-                FakeDeviceEntryFingerprintAuthRepository(),
                 FakeSystemClock(),
+                mock(KeyguardUpdateMonitor::class.java),
             )
         displayStateInteractor =
             DisplayStateInteractorImpl(
@@ -164,7 +164,7 @@
         context.addMockSystemService(WindowManager::class.java, windowManager)
 
         whenEver(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView)
-        whenEver(sideFpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
+        whenEver(sideFpsView.requireViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
             .thenReturn(mock(LottieAnimationView::class.java))
         with(mock(ViewPropertyAnimator::class.java)) {
             whenEver(sideFpsView.animate()).thenReturn(this)
@@ -840,6 +840,27 @@
 
             assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
         }
+
+    @Test
+    fun primaryBouncerRequestAnimatesAlphaIn() = testWithDisplay {
+        sideFpsController.show(SideFpsUiRequestSource.PRIMARY_BOUNCER, REASON_AUTH_KEYGUARD)
+        executor.runAllReady()
+        verify(sideFpsView).animate()
+    }
+
+    @Test
+    fun alternateBouncerRequestsDoesNotAnimateAlphaIn() = testWithDisplay {
+        sideFpsController.show(SideFpsUiRequestSource.ALTERNATE_BOUNCER, REASON_AUTH_KEYGUARD)
+        executor.runAllReady()
+        verify(sideFpsView, never()).animate()
+    }
+
+    @Test
+    fun autoShowRequestsDoesNotAnimateAlphaIn() = testWithDisplay {
+        sideFpsController.show(SideFpsUiRequestSource.AUTO_SHOW, REASON_AUTH_KEYGUARD)
+        executor.runAllReady()
+        verify(sideFpsView, never()).animate()
+    }
 }
 
 private fun insetsForSmallNavbar() = insetsWithBottom(60)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
new file mode 100644
index 0000000..7de78a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import org.junit.Assert.assertFalse
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class UdfpsBpViewControllerTest : SysuiTestCase() {
+
+    @JvmField @Rule var rule = MockitoJUnit.rule()
+
+    @Mock lateinit var udfpsBpView: UdfpsBpView
+    @Mock lateinit var statusBarStateController: StatusBarStateController
+    @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+    @Mock lateinit var systemUIDialogManager: SystemUIDialogManager
+    @Mock lateinit var dumpManager: DumpManager
+
+    private lateinit var udfpsBpViewController: UdfpsBpViewController
+
+    @Before
+    fun setup() {
+        udfpsBpViewController =
+            UdfpsBpViewController(
+                udfpsBpView,
+                statusBarStateController,
+                shadeExpansionStateManager,
+                systemUIDialogManager,
+                dumpManager
+            )
+    }
+
+    @Test
+    fun testShouldNeverPauseAuth() {
+        assertFalse(udfpsBpViewController.shouldPauseAuth())
+    }
+}
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 9dcdc46..0e0d0e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -43,11 +43,12 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -58,6 +59,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -70,6 +72,7 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
+import javax.inject.Provider
 import org.mockito.Mockito.`when` as whenever
 
 private const val REQUEST_ID = 2L
@@ -80,6 +83,7 @@
 private const val SENSOR_WIDTH = 30
 private const val SENSOR_HEIGHT = 60
 
+@ExperimentalCoroutinesApi
 @SmallTest
 @RoboPilotTest
 @RunWith(AndroidJUnit4::class)
@@ -114,6 +118,9 @@
     @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
     @Mock private lateinit var udfpsUtils: UdfpsUtils
+    @Mock private lateinit var udfpsKeyguardAccessibilityDelegate:
+            UdfpsKeyguardAccessibilityDelegate
+    @Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>
     @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
 
     private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -144,7 +151,9 @@
             configurationController, keyguardStateController, unlockedScreenOffAnimationController,
             udfpsDisplayMode, secureSettings, REQUEST_ID, reason,
             controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
-            primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils
+            primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils,
+            udfpsKeyguardAccessibilityDelegate,
+            udfpsKeyguardViewModels,
         )
         block()
     }
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 7578cc7..7ab8e8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -23,6 +23,7 @@
 
 import static com.android.internal.util.FunctionalUtils.ThrowingConsumer;
 import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -61,6 +62,7 @@
 import android.os.VibrationAttributes;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.Pair;
+import android.view.HapticFeedbackConstants;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -85,13 +87,14 @@
 import com.android.systemui.biometrics.udfps.NormalizedTouchData;
 import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
 import com.android.systemui.biometrics.udfps.TouchProcessorResult;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -219,6 +222,10 @@
     private AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock
     private SecureSettings mSecureSettings;
+    @Mock
+    private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
+    @Mock
+    private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels;
 
     // Capture listeners so that they can be used to send events
     @Captor
@@ -317,7 +324,8 @@
                 mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
                 mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
                 mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils,
-                mock(KeyguardFaceAuthInteractor.class));
+                mock(KeyguardFaceAuthInteractor.class),
+                mUdfpsKeyguardAccessibilityDelegate, mUdfpsKeyguardViewModels);
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -1122,6 +1130,36 @@
     }
 
     @Test
+    public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled_oneWayHapticsEnabled()
+            throws RemoteException {
+        when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true);
+        // Configure UdfpsView to accept the ACTION_DOWN event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+        // GIVEN that the overlay is showing and a11y touch exploration enabled
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        // WHEN ACTION_HOVER is received
+        verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture());
+        MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0);
+        mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent);
+        enterEvent.recycle();
+        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0);
+        mHoverListenerCaptor.getValue().onHover(mUdfpsView, moveEvent);
+        moveEvent.recycle();
+
+        // THEN context click haptic is played
+        verify(mVibrator).performHapticFeedback(
+                any(),
+                eq(HapticFeedbackConstants.CONTEXT_CLICK)
+        );
+    }
+
+    @Test
     public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException {
         // Configure UdfpsView to accept the ACTION_DOWN event
         when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
@@ -1154,6 +1192,35 @@
     }
 
     @Test
+    public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled__oneWayHapticsEnabled()
+            throws RemoteException {
+        when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true);
+        // Configure UdfpsView to accept the ACTION_DOWN event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        // WHEN ACTION_DOWN is received
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+        mBiometricExecutor.runAllReady();
+        downEvent.recycle();
+        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+        mBiometricExecutor.runAllReady();
+        moveEvent.recycle();
+
+        // THEN NO haptic played
+        verify(mVibrator, never()).performHapticFeedback(any(), anyInt());
+    }
+
+    @Test
     public void onTouch_withoutNewTouchDetection_shouldCallOldFingerprintManagerPath()
             throws RemoteException {
         // Disable new touch detection.
@@ -1508,4 +1575,45 @@
         // THEN is fingerDown should be FALSE
         assertFalse(mUdfpsController.isFingerDown());
     }
+
+    @Test
+    public void playHaptic_onAodInterrupt_oneWayHapticsDisabled_onAcquiredBad_usesVibrate()
+            throws RemoteException {
+        // GIVEN UDFPS overlay is showing
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        // GIVEN there's been an AoD interrupt
+        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false);
+        mScreenObserver.onScreenTurnedOn();
+        mUdfpsController.onAodInterrupt(0, 0, 0, 0);
+
+        // THEN vibrate is used
+        verify(mVibrator).vibrate(
+                anyInt(),
+                anyString(),
+                eq(UdfpsController.EFFECT_CLICK),
+                eq("aod-lock-icon-longpress"),
+                eq(UdfpsController.LOCK_ICON_VIBRATION_ATTRIBUTES)
+        );
+    }
+
+    @Test
+    public void playHaptic_onAodInterrupt_oneWayHapticsEnabled_onAcquiredBad_performHapticFeedback()
+            throws RemoteException {
+        when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true);
+        // GIVEN UDFPS overlay is showing
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        // GIVEN there's been an AoD interrupt
+        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false);
+        mScreenObserver.onScreenTurnedOn();
+        mUdfpsController.onAodInterrupt(0, 0, 0, 0);
+
+        // THEN vibrate is used
+        verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
new file mode 100644
index 0000000..921ff09
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegateTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.testing.TestableLooper
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.util.mockito.argumentCaptor
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class UdfpsKeyguardAccessibilityDelegateTest : SysuiTestCase() {
+
+    @Mock private lateinit var keyguardViewManager: StatusBarKeyguardViewManager
+    @Mock private lateinit var hostView: View
+    private lateinit var underTest: UdfpsKeyguardAccessibilityDelegate
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            UdfpsKeyguardAccessibilityDelegate(
+                context.resources,
+                keyguardViewManager,
+            )
+    }
+
+    @Test
+    fun onInitializeAccessibilityNodeInfo_clickActionAdded() {
+        // WHEN node is initialized
+        val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
+        underTest.onInitializeAccessibilityNodeInfo(hostView, mockedNodeInfo)
+
+        // THEN a11y action is added
+        val argumentCaptor = argumentCaptor<AccessibilityNodeInfo.AccessibilityAction>()
+        verify(mockedNodeInfo).addAction(argumentCaptor.capture())
+
+        // AND the a11y action is a click action
+        assertEquals(
+            AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+            argumentCaptor.value.id
+        )
+    }
+
+    @Test
+    fun performAccessibilityAction_actionClick_showsPrimaryBouncer() {
+        // WHEN click action is performed
+        val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
+        underTest.performAccessibilityAction(
+            hostView,
+            AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+            null
+        )
+
+        // THEN primary bouncer shows
+        verify(keyguardViewManager).showPrimaryBouncer(anyBoolean())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 0d3b394..032753a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -31,8 +31,8 @@
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionListener;
@@ -73,6 +73,7 @@
     protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
     protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
+    protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
 
     protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
@@ -167,7 +168,8 @@
                 mActivityLaunchAnimator,
                 mFeatureFlags,
                 mPrimaryBouncerInteractor,
-                mAlternateBouncerInteractor);
+                mAlternateBouncerInteractor,
+                mUdfpsKeyguardAccessibilityDelegate);
         return controller;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index ffad326..8dfeb3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -22,20 +22,19 @@
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.systemui.RoboPilotTest
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
@@ -106,8 +105,8 @@
                 mock(KeyguardStateController::class.java),
                 keyguardBouncerRepository,
                 mock(BiometricSettingsRepository::class.java),
-                mock(DeviceEntryFingerprintAuthRepository::class.java),
                 mock(SystemClock::class.java),
+                mKeyguardUpdateMonitor,
             )
         return createUdfpsKeyguardViewController(
             /* useModernBouncer */ true, /* useExpandedOverlay */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
new file mode 100644
index 0000000..fcc4040
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import android.hardware.biometrics.SensorProperties
+import android.hardware.face.FaceManager
+import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class FacePropertyRepositoryImplTest : SysuiTestCase() {
+    @JvmField @Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    private lateinit var underTest: FacePropertyRepository
+    private lateinit var testScope: TestScope
+
+    @Captor private lateinit var callback: ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback>
+    @Mock private lateinit var faceManager: FaceManager
+    @Before
+    fun setup() {
+        testScope = TestScope()
+        underTest = createRepository(faceManager)
+    }
+
+    private fun createRepository(manager: FaceManager? = faceManager) =
+        FacePropertyRepositoryImpl(testScope.backgroundScope, manager)
+
+    @Test
+    fun whenFaceManagerIsNotPresentIsNull() =
+        testScope.runTest {
+            underTest = createRepository(null)
+            val sensor = collectLastValue(underTest.sensorInfo)
+
+            assertThat(sensor()).isNull()
+        }
+
+    @Test
+    fun providesTheValuePassedToTheAuthenticatorsRegisteredCallback() {
+        testScope.runTest {
+            val sensor by collectLastValue(underTest.sensorInfo)
+            runCurrent()
+            verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture())
+
+            callback.value.onAllAuthenticatorsRegistered(
+                listOf(createSensorProperties(1, SensorProperties.STRENGTH_STRONG))
+            )
+
+            assertThat(sensor).isEqualTo(FaceSensorInfo(1, SensorStrength.STRONG))
+        }
+    }
+
+    private fun createSensorProperties(id: Int, strength: Int) =
+        FaceSensorPropertiesInternal(id, strength, 0, emptyList(), 1, false, false, false)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
index 97d3e68..5eda2b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
@@ -11,8 +11,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
-import com.android.systemui.biometrics.domain.model.BiometricUserInfo
 import com.android.systemui.biometrics.promptInfo
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
index 263ce1a..8f8004f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt
@@ -7,7 +7,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -54,10 +54,11 @@
             LogContextInteractorImpl(
                 testScope.backgroundScope,
                 foldProvider,
-                KeyguardTransitionInteractor(
-                    keyguardTransitionRepository,
-                    testScope.backgroundScope
-                ),
+                KeyguardTransitionInteractorFactory.create(
+                        repository = keyguardTransitionRepository,
+                        scope = testScope.backgroundScope,
+                    )
+                    .keyguardTransitionInteractor,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
index 720a35c9..dcefea2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
@@ -7,8 +7,8 @@
 import com.android.systemui.biometrics.data.repository.FakePromptRepository
 import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
-import com.android.systemui.biometrics.domain.model.BiometricUserInfo
 import com.android.systemui.biometrics.promptInfo
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 import com.android.systemui.coroutines.collectLastValue
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index e352905..be0276a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -4,6 +4,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
 import com.android.systemui.biometrics.promptInfo
+import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt
new file mode 100644
index 0000000..f9b590f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptHistoryImplTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.shared.model.BiometricModality
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class PromptHistoryImplTest : SysuiTestCase() {
+
+    private lateinit var history: PromptHistoryImpl
+
+    @Before
+    fun setup() {
+        history = PromptHistoryImpl()
+    }
+
+    @Test
+    fun empty() {
+        assertThat(history.faceFailed).isFalse()
+        assertThat(history.fingerprintFailed).isFalse()
+    }
+
+    @Test
+    fun faceFailed() =
+        repeat(2) {
+            history.failure(BiometricModality.None)
+            history.failure(BiometricModality.Face)
+
+            assertThat(history.faceFailed).isTrue()
+            assertThat(history.fingerprintFailed).isFalse()
+        }
+
+    @Test
+    fun fingerprintFailed() =
+        repeat(2) {
+            history.failure(BiometricModality.None)
+            history.failure(BiometricModality.Fingerprint)
+
+            assertThat(history.faceFailed).isFalse()
+            assertThat(history.fingerprintFailed).isTrue()
+        }
+
+    @Test
+    fun coexFailed() =
+        repeat(2) {
+            history.failure(BiometricModality.Face)
+            history.failure(BiometricModality.Fingerprint)
+
+            assertThat(history.faceFailed).isTrue()
+            assertThat(history.fingerprintFailed).isTrue()
+
+            history.failure(BiometricModality.None)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index eed7b66..4d19543 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -19,6 +19,7 @@
 import android.hardware.biometrics.PromptInfo
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.view.HapticFeedbackConstants
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
@@ -33,6 +34,8 @@
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
@@ -71,13 +74,15 @@
 
     private lateinit var selector: PromptSelectorInteractor
     private lateinit var viewModel: PromptViewModel
+    private val featureFlags = FakeFeatureFlags()
 
     @Before
     fun setup() {
         selector = PromptSelectorInteractorImpl(promptRepository, lockPatternUtils)
         selector.resetPrompt()
 
-        viewModel = PromptViewModel(selector, vibrator)
+        viewModel = PromptViewModel(selector, vibrator, featureFlags)
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
     }
 
     @Test
@@ -149,6 +154,29 @@
             verify(vibrator, never()).vibrateAuthError(any())
         }
 
+    @Test
+    fun playSuccessHaptic_onwayHapticsEnabled_SetsConfirmConstant() = runGenericTest {
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+        val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+        viewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
+
+        if (expectConfirmation) {
+            viewModel.confirmAuthenticated()
+        }
+
+        val currentConstant by collectLastValue(viewModel.hapticsToPlay)
+        assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.CONFIRM)
+    }
+
+    @Test
+    fun playErrorHaptic_onwayHapticsEnabled_SetsRejectConstant() = runGenericTest {
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+        viewModel.showTemporaryError("test", "messageAfterError", false)
+
+        val currentConstant by collectLastValue(viewModel.hapticsToPlay)
+        assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.REJECT)
+    }
+
     private suspend fun TestScope.showAuthenticated(
         authenticatedModality: BiometricModality,
         expectConfirmation: Boolean,
@@ -335,7 +363,7 @@
                     error,
                     messageAfterError = "or me",
                     authenticateAfterError = false,
-                    suppressIf = { _ -> true },
+                    suppressIf = { _, _ -> true },
                 )
             }
         }
@@ -364,7 +392,7 @@
                     error,
                     messageAfterError = "$error $afterSuffix",
                     authenticateAfterError = false,
-                    suppressIf = { currentMessage -> suppress && currentMessage.isError },
+                    suppressIf = { currentMessage, _ -> suppress && currentMessage.isError },
                 )
             }
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactoryTest.kt
new file mode 100644
index 0000000..8eb274a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/factory/BouncerMessageFactoryTest.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.data.factory
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Pattern
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.StringSubject
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BouncerMessageFactoryTest : SysuiTestCase() {
+    private lateinit var underTest: BouncerMessageFactory
+
+    @Mock private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+
+    @Mock private lateinit var securityModel: KeyguardSecurityModel
+
+    private lateinit var testScope: TestScope
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+        biometricSettingsRepository = FakeBiometricSettingsRepository()
+        underTest = BouncerMessageFactory(biometricSettingsRepository, securityModel)
+    }
+
+    @Test
+    fun bouncerMessages_choosesTheRightMessage_basedOnSecurityModeAndFpAuthIsAllowed() =
+        testScope.runTest {
+            primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAuthAllowed = false)
+                .isEqualTo("Enter PIN")
+            primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAuthAllowed = true)
+                .isEqualTo("Unlock with PIN or fingerprint")
+
+            primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAuthAllowed = false)
+                .isEqualTo("Enter password")
+            primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAuthAllowed = true)
+                .isEqualTo("Unlock with password or fingerprint")
+
+            primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAuthAllowed = false)
+                .isEqualTo("Draw pattern")
+            primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAuthAllowed = true)
+                .isEqualTo("Unlock with pattern or fingerprint")
+        }
+
+    @Test
+    fun bouncerMessages_overridesSecondaryMessageValue() =
+        testScope.runTest {
+            val bouncerMessageModel =
+                bouncerMessageModel(
+                    PIN,
+                    true,
+                    PROMPT_REASON_DEFAULT,
+                    secondaryMessageOverride = "face acquisition message"
+                )!!
+            assertThat(context.resources.getString(bouncerMessageModel.message!!.messageResId!!))
+                .isEqualTo("Unlock with PIN or fingerprint")
+            assertThat(bouncerMessageModel.secondaryMessage!!.message!!)
+                .isEqualTo("face acquisition message")
+        }
+
+    @Test
+    fun bouncerMessages_setsPrimaryAndSecondaryMessage_basedOnSecurityModeAndFpAuthIsAllowed() =
+        testScope.runTest {
+            primaryMessage(
+                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+                    mode = PIN,
+                    fpAuthAllowed = true
+                )
+                .isEqualTo("Wrong PIN. Try again.")
+            secondaryMessage(
+                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+                    mode = PIN,
+                    fpAuthAllowed = true
+                )
+                .isEqualTo("Or unlock with fingerprint")
+
+            primaryMessage(
+                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+                    mode = Password,
+                    fpAuthAllowed = true
+                )
+                .isEqualTo("Wrong password. Try again.")
+            secondaryMessage(
+                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+                    mode = Password,
+                    fpAuthAllowed = true
+                )
+                .isEqualTo("Or unlock with fingerprint")
+
+            primaryMessage(
+                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+                    mode = Pattern,
+                    fpAuthAllowed = true
+                )
+                .isEqualTo("Wrong pattern. Try again.")
+            secondaryMessage(
+                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+                    mode = Pattern,
+                    fpAuthAllowed = true
+                )
+                .isEqualTo("Or unlock with fingerprint")
+        }
+
+    private fun primaryMessage(
+        reason: Int,
+        mode: KeyguardSecurityModel.SecurityMode,
+        fpAuthAllowed: Boolean
+    ): StringSubject {
+        return assertThat(
+            context.resources.getString(
+                bouncerMessageModel(mode, fpAuthAllowed, reason)!!.message!!.messageResId!!
+            )
+        )!!
+    }
+
+    private fun secondaryMessage(
+        reason: Int,
+        mode: KeyguardSecurityModel.SecurityMode,
+        fpAuthAllowed: Boolean
+    ): StringSubject {
+        return assertThat(
+            context.resources.getString(
+                bouncerMessageModel(mode, fpAuthAllowed, reason)!!.secondaryMessage!!.messageResId!!
+            )
+        )!!
+    }
+
+    private fun bouncerMessageModel(
+        mode: KeyguardSecurityModel.SecurityMode,
+        fpAuthAllowed: Boolean,
+        reason: Int,
+        secondaryMessageOverride: String? = null,
+    ): BouncerMessageModel? {
+        whenever(securityModel.getSecurityMode(0)).thenReturn(mode)
+        biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(fpAuthAllowed)
+
+        return underTest.createFromPromptReason(
+            reason,
+            0,
+            secondaryMsgOverride = secondaryMessageOverride
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repo/BouncerMessageRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repo/BouncerMessageRepositoryTest.kt
new file mode 100644
index 0000000..562a8ef
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repo/BouncerMessageRepositoryTest.kt
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.data.repo
+
+import android.content.pm.UserInfo
+import android.hardware.biometrics.BiometricSourceType
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.R
+import com.android.systemui.R.string.keyguard_enter_pin
+import com.android.systemui.R.string.kg_prompt_after_dpm_lock
+import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pin
+import com.android.systemui.R.string.kg_prompt_auth_timeout
+import com.android.systemui.R.string.kg_prompt_pin_auth_timeout
+import com.android.systemui.R.string.kg_prompt_reason_restart_pin
+import com.android.systemui.R.string.kg_prompt_unattended_update
+import com.android.systemui.R.string.kg_trust_agent_disabled
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
+import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.bouncer.shared.model.Message
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.SystemPropertiesHelper
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4::class)
+class BouncerMessageRepositoryTest : SysuiTestCase() {
+
+    @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var securityModel: KeyguardSecurityModel
+    @Mock private lateinit var systemPropertiesHelper: SystemPropertiesHelper
+    @Captor
+    private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
+
+    private lateinit var underTest: BouncerMessageRepository
+    private lateinit var trustRepository: FakeTrustRepository
+    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+    private lateinit var userRepository: FakeUserRepository
+    private lateinit var fingerprintRepository: FakeDeviceEntryFingerprintAuthRepository
+    private lateinit var testScope: TestScope
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        trustRepository = FakeTrustRepository()
+        biometricSettingsRepository = FakeBiometricSettingsRepository()
+        userRepository = FakeUserRepository()
+        userRepository.setUserInfos(listOf(PRIMARY_USER))
+        fingerprintRepository = FakeDeviceEntryFingerprintAuthRepository()
+        testScope = TestScope()
+
+        biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
+        whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
+        underTest =
+            BouncerMessageRepositoryImpl(
+                trustRepository = trustRepository,
+                biometricSettingsRepository = biometricSettingsRepository,
+                updateMonitor = updateMonitor,
+                bouncerMessageFactory =
+                    BouncerMessageFactory(biometricSettingsRepository, securityModel),
+                userRepository = userRepository,
+                fingerprintAuthRepository = fingerprintRepository,
+                systemPropertiesHelper = systemPropertiesHelper
+            )
+    }
+
+    @Test
+    fun setCustomMessage_propagatesState() =
+        testScope.runTest {
+            underTest.setCustomMessage(message("not empty"))
+
+            val customMessage = collectLastValue(underTest.customMessage)
+
+            assertThat(customMessage()).isEqualTo(message("not empty"))
+        }
+
+    @Test
+    fun setFaceMessage_propagatesState() =
+        testScope.runTest {
+            underTest.setFaceAcquisitionMessage(message("not empty"))
+
+            val faceAcquisitionMessage = collectLastValue(underTest.faceAcquisitionMessage)
+
+            assertThat(faceAcquisitionMessage()).isEqualTo(message("not empty"))
+        }
+
+    @Test
+    fun setFpMessage_propagatesState() =
+        testScope.runTest {
+            underTest.setFingerprintAcquisitionMessage(message("not empty"))
+
+            val fpAcquisitionMsg = collectLastValue(underTest.fingerprintAcquisitionMessage)
+
+            assertThat(fpAcquisitionMsg()).isEqualTo(message("not empty"))
+        }
+
+    @Test
+    fun setPrimaryAuthMessage_propagatesState() =
+        testScope.runTest {
+            underTest.setPrimaryAuthMessage(message("not empty"))
+
+            val primaryAuthMessage = collectLastValue(underTest.primaryAuthMessage)
+
+            assertThat(primaryAuthMessage()).isEqualTo(message("not empty"))
+        }
+
+    @Test
+    fun biometricAuthMessage_propagatesBiometricAuthMessages() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            val biometricAuthMessage = collectLastValue(underTest.biometricAuthMessage)
+            runCurrent()
+
+            verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
+
+            updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT)
+
+            assertThat(biometricAuthMessage())
+                .isEqualTo(message(R.string.kg_fp_not_recognized, R.string.kg_bio_try_again_or_pin))
+
+            updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FACE)
+
+            assertThat(biometricAuthMessage())
+                .isEqualTo(
+                    message(R.string.bouncer_face_not_recognized, R.string.kg_bio_try_again_or_pin)
+                )
+
+            updateMonitorCallback.value.onBiometricAcquired(BiometricSourceType.FACE, 0)
+
+            assertThat(biometricAuthMessage()).isNull()
+        }
+
+    @Test
+    fun onFaceLockout_propagatesState() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            val lockoutMessage = collectLastValue(underTest.biometricLockedOutMessage)
+            runCurrent()
+            verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
+
+            whenever(updateMonitor.isFaceLockedOut).thenReturn(true)
+            updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
+
+            assertThat(lockoutMessage())
+                .isEqualTo(message(keyguard_enter_pin, R.string.kg_face_locked_out))
+
+            whenever(updateMonitor.isFaceLockedOut).thenReturn(false)
+            updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
+            assertThat(lockoutMessage()).isNull()
+        }
+
+    @Test
+    fun onFingerprintLockout_propagatesState() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            val lockedOutMessage = collectLastValue(underTest.biometricLockedOutMessage)
+            runCurrent()
+
+            fingerprintRepository.setLockedOut(true)
+
+            assertThat(lockedOutMessage())
+                .isEqualTo(message(keyguard_enter_pin, R.string.kg_fp_locked_out))
+
+            fingerprintRepository.setLockedOut(false)
+            assertThat(lockedOutMessage()).isNull()
+        }
+
+    @Test
+    fun onRestartForMainlineUpdate_shouldProvideRelevantMessage() =
+        testScope.runTest {
+            whenever(systemPropertiesHelper.get("sys.boot.reason.last"))
+                .thenReturn("reboot,mainline_update")
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+
+            verifyMessagesForAuthFlag(
+                STRONG_AUTH_REQUIRED_AFTER_BOOT to
+                    Pair(keyguard_enter_pin, R.string.kg_prompt_after_update_pin),
+            )
+        }
+
+    @Test
+    fun onAuthFlagsChanged_withTrustNotManagedAndNoBiometrics_isANoop() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            trustRepository.setCurrentUserTrustManaged(false)
+            biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+            biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+
+            verifyMessagesForAuthFlag(
+                STRONG_AUTH_NOT_REQUIRED to null,
+                STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
+                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
+                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
+                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
+                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to null,
+                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
+            )
+        }
+
+    @Test
+    fun authFlagsChanges_withTrustManaged_providesDifferentMessages() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+            biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+
+            trustRepository.setCurrentUserTrustManaged(true)
+
+            verifyMessagesForAuthFlag(
+                STRONG_AUTH_NOT_REQUIRED to null,
+                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+                STRONG_AUTH_REQUIRED_AFTER_BOOT to
+                    Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
+                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+                    Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
+                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
+                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
+                    Pair(keyguard_enter_pin, kg_trust_agent_disabled),
+                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+                    Pair(keyguard_enter_pin, kg_trust_agent_disabled),
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+                    Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
+                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+                    Pair(keyguard_enter_pin, kg_prompt_unattended_update),
+                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+                    Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
+            )
+        }
+
+    @Test
+    fun authFlagsChanges_withFaceEnrolled_providesDifferentMessages() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            trustRepository.setCurrentUserTrustManaged(false)
+            biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+
+            biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+
+            verifyMessagesForAuthFlag(
+                STRONG_AUTH_NOT_REQUIRED to null,
+                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
+                STRONG_AUTH_REQUIRED_AFTER_BOOT to
+                    Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
+                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+                    Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
+                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+                    Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
+                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+                    Pair(keyguard_enter_pin, kg_prompt_unattended_update),
+                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+                    Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
+            )
+        }
+
+    @Test
+    fun authFlagsChanges_withFingerprintEnrolled_providesDifferentMessages() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            trustRepository.setCurrentUserTrustManaged(false)
+            biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+
+            biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+
+            verifyMessagesForAuthFlag(
+                STRONG_AUTH_NOT_REQUIRED to null,
+                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
+                STRONG_AUTH_REQUIRED_AFTER_BOOT to
+                    Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
+                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+                    Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
+                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
+                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+                    Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
+                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+                    Pair(keyguard_enter_pin, kg_prompt_unattended_update),
+                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+                    Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
+            )
+        }
+
+    private fun TestScope.verifyMessagesForAuthFlag(
+        vararg authFlagToExpectedMessages: Pair<Int, Pair<Int, Int>?>
+    ) {
+        val authFlagsMessage = collectLastValue(underTest.authFlagsMessage)
+
+        authFlagToExpectedMessages.forEach { (flag, messagePair) ->
+            biometricSettingsRepository.setAuthenticationFlags(
+                AuthenticationFlags(PRIMARY_USER_ID, flag)
+            )
+
+            assertThat(authFlagsMessage())
+                .isEqualTo(messagePair?.let { message(it.first, it.second) })
+        }
+    }
+
+    private fun message(primaryResId: Int, secondaryResId: Int): BouncerMessageModel {
+        return BouncerMessageModel(
+            message = Message(messageResId = primaryResId, animate = false),
+            secondaryMessage = Message(messageResId = secondaryResId, animate = false)
+        )
+    }
+    private fun message(value: String): BouncerMessageModel {
+        return BouncerMessageModel(message = Message(message = value))
+    }
+
+    companion object {
+        private const val PRIMARY_USER_ID = 0
+        private val PRIMARY_USER =
+            UserInfo(
+                /* id= */ PRIMARY_USER_ID,
+                /* name= */ "primary user",
+                /* flags= */ UserInfo.FLAG_PRIMARY
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
new file mode 100644
index 0000000..a049191
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -0,0 +1,49 @@
+package com.android.systemui.bouncer.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.ViewMediatorCallback
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestCoroutineScope
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardBouncerRepositoryTest : SysuiTestCase() {
+
+    @Mock private lateinit var systemClock: SystemClock
+    @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
+    @Mock private lateinit var bouncerLogger: TableLogBuffer
+    lateinit var underTest: KeyguardBouncerRepository
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        val testCoroutineScope = TestCoroutineScope()
+        underTest =
+            KeyguardBouncerRepositoryImpl(
+                systemClock,
+                testCoroutineScope,
+                bouncerLogger,
+            )
+    }
+
+    @Test
+    fun changingFlowValueTriggersLogging() = runBlocking {
+        underTest.setPrimaryShow(true)
+        Mockito.verify(bouncerLogger)
+            .logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
new file mode 100644
index 0000000..0d17270
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -0,0 +1,193 @@
+/*
+ * 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.bouncer.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScope
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class AlternateBouncerInteractorTest : SysuiTestCase() {
+    private lateinit var underTest: AlternateBouncerInteractor
+    private lateinit var bouncerRepository: KeyguardBouncerRepository
+    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+    private lateinit var deviceEntryFingerprintAuthRepository:
+        FakeDeviceEntryFingerprintAuthRepository
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var systemClock: SystemClock
+    @Mock private lateinit var bouncerLogger: TableLogBuffer
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        bouncerRepository =
+            KeyguardBouncerRepositoryImpl(
+                FakeSystemClock(),
+                TestCoroutineScope(),
+                bouncerLogger,
+            )
+        biometricSettingsRepository = FakeBiometricSettingsRepository()
+        deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+        underTest =
+            AlternateBouncerInteractor(
+                statusBarStateController,
+                keyguardStateController,
+                bouncerRepository,
+                biometricSettingsRepository,
+                systemClock,
+                keyguardUpdateMonitor,
+            )
+    }
+
+    @Test
+    fun canShowAlternateBouncerForFingerprint_givenCanShow() {
+        givenCanShowAlternateBouncer()
+        assertTrue(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
+    fun canShowAlternateBouncerForFingerprint_alternateBouncerUIUnavailable() {
+        givenCanShowAlternateBouncer()
+        bouncerRepository.setAlternateBouncerUIAvailable(false)
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
+    fun canShowAlternateBouncerForFingerprint_ifFingerprintIsNotUsuallyAllowed() {
+        givenCanShowAlternateBouncer()
+        biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
+    fun canShowAlternateBouncerForFingerprint_strongBiometricNotAllowed() {
+        givenCanShowAlternateBouncer()
+        biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
+    fun canShowAlternateBouncerForFingerprint_fingerprintLockedOut() {
+        givenCanShowAlternateBouncer()
+        whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
+    fun show_whenCanShow() {
+        givenCanShowAlternateBouncer()
+
+        assertTrue(underTest.show())
+        assertTrue(bouncerRepository.alternateBouncerVisible.value)
+    }
+
+    @Test
+    fun canShowAlternateBouncerForFingerprint_butCanDismissLockScreen() {
+        givenCanShowAlternateBouncer()
+        whenever(keyguardStateController.isUnlocked).thenReturn(true)
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
+    fun show_whenCannotShow() {
+        givenCannotShowAlternateBouncer()
+
+        assertFalse(underTest.show())
+        assertFalse(bouncerRepository.alternateBouncerVisible.value)
+    }
+
+    @Test
+    fun hide_wasPreviouslyShowing() {
+        bouncerRepository.setAlternateVisible(true)
+
+        assertTrue(underTest.hide())
+        assertFalse(bouncerRepository.alternateBouncerVisible.value)
+    }
+
+    @Test
+    fun hide_wasNotPreviouslyShowing() {
+        bouncerRepository.setAlternateVisible(false)
+
+        assertFalse(underTest.hide())
+        assertFalse(bouncerRepository.alternateBouncerVisible.value)
+    }
+
+    @Test
+    fun alternateBouncerUiAvailable_fromMultipleSources() {
+        assertFalse(bouncerRepository.alternateBouncerUIAvailable.value)
+
+        // GIVEN there are two different sources indicating the alternate bouncer is available
+        underTest.setAlternateBouncerUIAvailable(true, "source1")
+        underTest.setAlternateBouncerUIAvailable(true, "source2")
+        assertTrue(bouncerRepository.alternateBouncerUIAvailable.value)
+
+        // WHEN one of the sources no longer says the UI is available
+        underTest.setAlternateBouncerUIAvailable(false, "source1")
+
+        // THEN alternate bouncer UI is still available (from the other source)
+        assertTrue(bouncerRepository.alternateBouncerUIAvailable.value)
+
+        // WHEN all sources say the UI is not available
+        underTest.setAlternateBouncerUIAvailable(false, "source2")
+
+        // THEN alternate boucer UI is not available
+        assertFalse(bouncerRepository.alternateBouncerUIAvailable.value)
+    }
+
+    private fun givenCanShowAlternateBouncer() {
+        bouncerRepository.setAlternateBouncerUIAvailable(true)
+        biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+        biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+        whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
+        whenever(keyguardStateController.isUnlocked).thenReturn(false)
+    }
+
+    private fun givenCannotShowAlternateBouncer() {
+        biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 374c28d6..86e0c75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -19,14 +19,18 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
+import kotlin.math.ceil
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -40,8 +44,8 @@
 @RunWith(JUnit4::class)
 class BouncerInteractorTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
@@ -66,12 +70,13 @@
     @Test
     fun pinAuthMethod() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(underTest.message)
 
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            underTest.showOrUnlockDevice("container1")
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            runCurrent()
+            utils.authenticationRepository.setUnlocked(false)
+            underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
 
@@ -90,20 +95,88 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
 
             // Correct input.
-            assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     @Test
+    fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            val message by collectLastValue(underTest.message)
+
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            runCurrent()
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            utils.authenticationRepository.setUnlocked(false)
+            underTest.showOrUnlockDevice()
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
+            underTest.clearMessage()
+
+            // Incomplete input.
+            assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true)).isNull()
+            assertThat(message).isEmpty()
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+            // Wrong 6-digit pin
+            assertThat(underTest.authenticate(listOf(1, 2, 3, 5, 5, 6), tryAutoConfirm = true))
+                .isFalse()
+            assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+            // Correct input.
+            assertThat(
+                    underTest.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN,
+                        tryAutoConfirm = true
+                    )
+                )
+                .isTrue()
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+        }
+
+    @Test
+    fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            val message by collectLastValue(underTest.message)
+
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            runCurrent()
+            utils.authenticationRepository.setUnlocked(false)
+            underTest.showOrUnlockDevice()
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            underTest.clearMessage()
+
+            // Incomplete input.
+            assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true)).isNull()
+            assertThat(message).isEmpty()
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+            // Correct input.
+            assertThat(
+                    underTest.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN,
+                        tryAutoConfirm = true
+                    )
+                )
+                .isNull()
+            assertThat(message).isEmpty()
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+        }
+
+    @Test
     fun passwordAuthMethod() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(underTest.message)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
-            underTest.showOrUnlockDevice("container1")
+            runCurrent()
+            utils.authenticationRepository.setUnlocked(false)
+            underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
 
@@ -129,13 +202,14 @@
     @Test
     fun patternAuthMethod() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(underTest.message)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(emptyList())
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
             )
-            authenticationInteractor.lockDevice()
-            underTest.showOrUnlockDevice("container1")
+            runCurrent()
+            utils.authenticationRepository.setUnlocked(false)
+            underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
 
@@ -146,11 +220,7 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
 
             // Wrong input.
-            assertThat(
-                    underTest.authenticate(
-                        listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(3, 4))
-                    )
-                )
+            assertThat(underTest.authenticate(listOf(AuthenticationPatternCoordinate(1, 2))))
                 .isFalse()
             assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -159,19 +229,19 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
 
             // Correct input.
-            assertThat(underTest.authenticate(emptyList())).isTrue()
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     @Test
     fun showOrUnlockDevice_notLocked_switchesToGoneScene() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.unlockDevice()
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
-            underTest.showOrUnlockDevice("container1")
+            underTest.showOrUnlockDevice()
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
@@ -179,11 +249,12 @@
     @Test
     fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            authenticationInteractor.lockDevice()
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.authenticationRepository.setLockscreenEnabled(true)
+            utils.authenticationRepository.setUnlocked(false)
 
-            underTest.showOrUnlockDevice("container1")
+            underTest.showOrUnlockDevice()
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
@@ -191,15 +262,16 @@
     @Test
     fun showOrUnlockDevice_customMessageShown() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene("container1"))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(underTest.message)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
+            runCurrent()
+            utils.authenticationRepository.setUnlocked(false)
 
             val customMessage = "Hello there!"
-            underTest.showOrUnlockDevice("container1", customMessage)
+            underTest.showOrUnlockDevice(customMessage)
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(customMessage)
@@ -208,44 +280,77 @@
     @Test
     fun throttling() =
         testScope.runTest {
+            val isThrottled by collectLastValue(underTest.isThrottled)
             val throttling by collectLastValue(underTest.throttling)
             val message by collectLastValue(underTest.message)
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            assertThat(throttling).isNull()
-            assertThat(message).isEqualTo("")
-            assertThat(isUnlocked).isFalse()
-            repeat(BouncerInteractor.THROTTLE_EVERY) { times ->
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            runCurrent()
+            underTest.showOrUnlockDevice()
+            runCurrent()
+            assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
+            assertThat(isThrottled).isFalse()
+            assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
+            assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times ->
                 // Wrong PIN.
                 assertThat(underTest.authenticate(listOf(6, 7, 8, 9))).isFalse()
-                if (times < BouncerInteractor.THROTTLE_EVERY - 1) {
+                if (
+                    times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
+                ) {
                     assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
                 }
             }
-            assertThat(throttling).isNotNull()
-            assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
+            assertThat(isThrottled).isTrue()
+            assertThat(throttling)
+                .isEqualTo(
+                    AuthenticationThrottlingModel(
+                        failedAttemptCount =
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+                        remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
+                    )
+                )
+            assertTryAgainMessage(
+                message,
+                FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
+                    .toInt()
+            )
 
-            // Correct PIN, but throttled, so doesn't unlock:
-            assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isFalse()
-            assertThat(isUnlocked).isFalse()
-            assertTryAgainMessage(message, BouncerInteractor.THROTTLE_DURATION_SEC)
+            // Correct PIN, but throttled, so doesn't change away from the bouncer scene:
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isNull()
+            assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
+            assertTryAgainMessage(
+                message,
+                FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
+                    .toInt()
+            )
 
-            throttling?.totalDurationSec?.let { seconds ->
+            throttling?.remainingMs?.let { remainingMs ->
+                val seconds = ceil(remainingMs / 1000f).toInt()
                 repeat(seconds) { time ->
                     advanceTimeBy(1000)
-                    val remainingTime = seconds - time - 1
-                    if (remainingTime > 0) {
-                        assertTryAgainMessage(message, remainingTime)
+                    val remainingTimeSec = seconds - time - 1
+                    if (remainingTimeSec > 0) {
+                        assertTryAgainMessage(message, remainingTimeSec)
                     }
                 }
             }
             assertThat(message).isEqualTo("")
-            assertThat(throttling).isNull()
-            assertThat(isUnlocked).isFalse()
+            assertThat(isThrottled).isFalse()
+            assertThat(throttling)
+                .isEqualTo(
+                    AuthenticationThrottlingModel(
+                        failedAttemptCount =
+                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
+                    )
+                )
+            assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
 
-            // Correct PIN and no longer throttled so unlocks:
-            assertThat(underTest.authenticate(listOf(1, 2, 3, 4))).isTrue()
-            assertThat(isUnlocked).isTrue()
+            // Correct PIN and no longer throttled so changes to the Gone scene:
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
+            assertThat(currentScene?.key).isEqualTo(SceneKey.Gone)
+            assertThat(isThrottled).isFalse()
+            assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
         }
 
     private fun assertTryAgainMessage(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
new file mode 100644
index 0000000..4089abe
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import android.content.pm.UserInfo
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.systemui.R.string.kg_too_many_failed_attempts_countdown
+import com.android.systemui.R.string.kg_unlock_with_pin_or_fp
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.bouncer.shared.model.Message
+import com.android.systemui.coroutines.FlowValue
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4::class)
+class BouncerMessageInteractorTest : SysuiTestCase() {
+
+    @Mock private lateinit var securityModel: KeyguardSecurityModel
+    @Mock private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+    @Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
+    private lateinit var countDownTimerCallback: KotlinArgumentCaptor<CountDownTimerCallback>
+    private lateinit var underTest: BouncerMessageInteractor
+    private lateinit var repository: FakeBouncerMessageRepository
+    private lateinit var userRepository: FakeUserRepository
+    private lateinit var testScope: TestScope
+    private lateinit var bouncerMessage: FlowValue<BouncerMessageModel?>
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        repository = FakeBouncerMessageRepository()
+        userRepository = FakeUserRepository()
+        userRepository.setUserInfos(listOf(PRIMARY_USER))
+        testScope = TestScope()
+        countDownTimerCallback = KotlinArgumentCaptor(CountDownTimerCallback::class.java)
+        biometricSettingsRepository = FakeBiometricSettingsRepository()
+
+        allowTestableLooperAsMainThread()
+        whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
+        biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+    }
+
+    suspend fun TestScope.init() {
+        userRepository.setSelectedUserInfo(PRIMARY_USER)
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+        underTest =
+            BouncerMessageInteractor(
+                repository = repository,
+                factory = BouncerMessageFactory(biometricSettingsRepository, securityModel),
+                userRepository = userRepository,
+                countDownTimerUtil = countDownTimerUtil,
+                featureFlags = featureFlags
+            )
+        bouncerMessage = collectLastValue(underTest.bouncerMessage)
+    }
+
+    @Test
+    fun onIncorrectSecurityInput_setsTheBouncerModelInTheRepository() =
+        testScope.runTest {
+            init()
+            underTest.onPrimaryAuthIncorrectAttempt()
+
+            assertThat(repository.primaryAuthMessage).isNotNull()
+            assertThat(
+                    context.resources.getString(
+                        repository.primaryAuthMessage.value!!.message!!.messageResId!!
+                    )
+                )
+                .isEqualTo("Wrong PIN. Try again.")
+        }
+
+    @Test
+    fun onUserStartsPrimaryAuthInput_clearsAllSetBouncerMessages() =
+        testScope.runTest {
+            init()
+            repository.setCustomMessage(message("not empty"))
+            repository.setFaceAcquisitionMessage(message("not empty"))
+            repository.setFingerprintAcquisitionMessage(message("not empty"))
+            repository.setPrimaryAuthMessage(message("not empty"))
+
+            underTest.onPrimaryBouncerUserInput()
+
+            assertThat(repository.customMessage.value).isNull()
+            assertThat(repository.faceAcquisitionMessage.value).isNull()
+            assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
+            assertThat(repository.primaryAuthMessage.value).isNull()
+        }
+
+    @Test
+    fun onBouncerBeingHidden_clearsAllSetBouncerMessages() =
+        testScope.runTest {
+            init()
+            repository.setCustomMessage(message("not empty"))
+            repository.setFaceAcquisitionMessage(message("not empty"))
+            repository.setFingerprintAcquisitionMessage(message("not empty"))
+            repository.setPrimaryAuthMessage(message("not empty"))
+
+            underTest.onBouncerBeingHidden()
+
+            assertThat(repository.customMessage.value).isNull()
+            assertThat(repository.faceAcquisitionMessage.value).isNull()
+            assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
+            assertThat(repository.primaryAuthMessage.value).isNull()
+        }
+
+    @Test
+    fun setCustomMessage_setsRepositoryValue() =
+        testScope.runTest {
+            init()
+
+            underTest.setCustomMessage("not empty")
+
+            val customMessage = repository.customMessage
+            assertThat(customMessage.value!!.message!!.messageResId)
+                .isEqualTo(kg_unlock_with_pin_or_fp)
+            assertThat(customMessage.value!!.secondaryMessage!!.message).isEqualTo("not empty")
+
+            underTest.setCustomMessage(null)
+            assertThat(customMessage.value).isNull()
+        }
+
+    @Test
+    fun setFaceMessage_setsRepositoryValue() =
+        testScope.runTest {
+            init()
+
+            underTest.setFaceAcquisitionMessage("not empty")
+
+            val faceAcquisitionMessage = repository.faceAcquisitionMessage
+
+            assertThat(faceAcquisitionMessage.value!!.message!!.messageResId)
+                .isEqualTo(kg_unlock_with_pin_or_fp)
+            assertThat(faceAcquisitionMessage.value!!.secondaryMessage!!.message)
+                .isEqualTo("not empty")
+
+            underTest.setFaceAcquisitionMessage(null)
+            assertThat(faceAcquisitionMessage.value).isNull()
+        }
+
+    @Test
+    fun setFingerprintMessage_setsRepositoryValue() =
+        testScope.runTest {
+            init()
+
+            underTest.setFingerprintAcquisitionMessage("not empty")
+
+            val fingerprintAcquisitionMessage = repository.fingerprintAcquisitionMessage
+
+            assertThat(fingerprintAcquisitionMessage.value!!.message!!.messageResId)
+                .isEqualTo(kg_unlock_with_pin_or_fp)
+            assertThat(fingerprintAcquisitionMessage.value!!.secondaryMessage!!.message)
+                .isEqualTo("not empty")
+
+            underTest.setFingerprintAcquisitionMessage(null)
+            assertThat(fingerprintAcquisitionMessage.value).isNull()
+        }
+
+    @Test
+    fun onPrimaryAuthLockout_startsTimerForSpecifiedNumberOfSeconds() =
+        testScope.runTest {
+            init()
+
+            underTest.onPrimaryAuthLockedOut(3)
+
+            verify(countDownTimerUtil)
+                .startNewTimer(eq(3000L), eq(1000L), countDownTimerCallback.capture())
+
+            countDownTimerCallback.value.onTick(2000L)
+
+            val primaryMessage = repository.primaryAuthMessage.value!!.message!!
+            assertThat(primaryMessage.messageResId!!)
+                .isEqualTo(kg_too_many_failed_attempts_countdown)
+            assertThat(primaryMessage.formatterArgs).isEqualTo(mapOf(Pair("count", 2)))
+        }
+
+    @Test
+    fun onPrimaryAuthLockout_timerComplete_resetsRepositoryMessages() =
+        testScope.runTest {
+            init()
+            repository.setCustomMessage(message("not empty"))
+            repository.setFaceAcquisitionMessage(message("not empty"))
+            repository.setFingerprintAcquisitionMessage(message("not empty"))
+            repository.setPrimaryAuthMessage(message("not empty"))
+
+            underTest.onPrimaryAuthLockedOut(3)
+
+            verify(countDownTimerUtil)
+                .startNewTimer(eq(3000L), eq(1000L), countDownTimerCallback.capture())
+
+            countDownTimerCallback.value.onFinish()
+
+            assertThat(repository.customMessage.value).isNull()
+            assertThat(repository.faceAcquisitionMessage.value).isNull()
+            assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
+            assertThat(repository.primaryAuthMessage.value).isNull()
+        }
+
+    @Test
+    fun bouncerMessage_hasPriorityOrderOfMessages() =
+        testScope.runTest {
+            init()
+            repository.setBiometricAuthMessage(message("biometric message"))
+            repository.setFaceAcquisitionMessage(message("face acquisition message"))
+            repository.setFingerprintAcquisitionMessage(message("fingerprint acquisition message"))
+            repository.setPrimaryAuthMessage(message("primary auth message"))
+            repository.setAuthFlagsMessage(message("auth flags message"))
+            repository.setBiometricLockedOutMessage(message("biometrics locked out"))
+            repository.setCustomMessage(message("custom message"))
+
+            assertThat(bouncerMessage()).isEqualTo(message("primary auth message"))
+
+            repository.setPrimaryAuthMessage(null)
+
+            assertThat(bouncerMessage()).isEqualTo(message("biometric message"))
+
+            repository.setBiometricAuthMessage(null)
+
+            assertThat(bouncerMessage()).isEqualTo(message("fingerprint acquisition message"))
+
+            repository.setFingerprintAcquisitionMessage(null)
+
+            assertThat(bouncerMessage()).isEqualTo(message("face acquisition message"))
+
+            repository.setFaceAcquisitionMessage(null)
+
+            assertThat(bouncerMessage()).isEqualTo(message("custom message"))
+
+            repository.setCustomMessage(null)
+
+            assertThat(bouncerMessage()).isEqualTo(message("auth flags message"))
+
+            repository.setAuthFlagsMessage(null)
+
+            assertThat(bouncerMessage()).isEqualTo(message("biometrics locked out"))
+
+            repository.setBiometricLockedOutMessage(null)
+
+            // sets the default message if everything else is null
+            assertThat(bouncerMessage()!!.message!!.messageResId)
+                .isEqualTo(kg_unlock_with_pin_or_fp)
+        }
+
+    private fun message(value: String): BouncerMessageModel {
+        return BouncerMessageModel(message = Message(message = value))
+    }
+
+    companion object {
+        private const val PRIMARY_USER_ID = 0
+        private val PRIMARY_USER =
+            UserInfo(
+                /* id= */ PRIMARY_USER_ID,
+                /* name= */ "primary user",
+                /* flags= */ UserInfo.FLAG_PRIMARY
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
new file mode 100644
index 0000000..a81ca86
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.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.bouncer.domain.interactor
+
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class PrimaryBouncerCallbackInteractorTest : SysuiTestCase() {
+    private val mPrimaryBouncerCallbackInteractor = PrimaryBouncerCallbackInteractor()
+    @Mock
+    private lateinit var mPrimaryBouncerExpansionCallback:
+        PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
+    @Mock
+    private lateinit var keyguardResetCallback:
+        PrimaryBouncerCallbackInteractor.KeyguardResetCallback
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(
+            mPrimaryBouncerExpansionCallback
+        )
+        mPrimaryBouncerCallbackInteractor.addKeyguardResetCallback(keyguardResetCallback)
+    }
+
+    @Test
+    fun testOnFullyShown() {
+        mPrimaryBouncerCallbackInteractor.dispatchFullyShown()
+        verify(mPrimaryBouncerExpansionCallback).onFullyShown()
+    }
+
+    @Test
+    fun testOnFullyHidden() {
+        mPrimaryBouncerCallbackInteractor.dispatchFullyHidden()
+        verify(mPrimaryBouncerExpansionCallback).onFullyHidden()
+    }
+
+    @Test
+    fun testOnExpansionChanged() {
+        mPrimaryBouncerCallbackInteractor.dispatchExpansionChanged(5f)
+        verify(mPrimaryBouncerExpansionCallback).onExpansionChanged(5f)
+    }
+
+    @Test
+    fun testOnVisibilityChanged() {
+        mPrimaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
+        verify(mPrimaryBouncerExpansionCallback).onVisibilityChanged(false)
+    }
+
+    @Test
+    fun testOnStartingToHide() {
+        mPrimaryBouncerCallbackInteractor.dispatchStartingToHide()
+        verify(mPrimaryBouncerExpansionCallback).onStartingToHide()
+    }
+
+    @Test
+    fun testOnStartingToShow() {
+        mPrimaryBouncerCallbackInteractor.dispatchStartingToShow()
+        verify(mPrimaryBouncerExpansionCallback).onStartingToShow()
+    }
+
+    @Test
+    fun testOnKeyguardReset() {
+        mPrimaryBouncerCallbackInteractor.dispatchReset()
+        verify(keyguardResetCallback).onKeyguardReset()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
new file mode 100644
index 0000000..f892453
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -0,0 +1,489 @@
+/*
+ * 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.bouncer.domain.interactor
+
+import android.hardware.biometrics.BiometricSourceType
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.TestableResources
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.DejankUtils
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
+import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
+import com.android.systemui.bouncer.ui.BouncerView
+import com.android.systemui.bouncer.ui.BouncerViewDelegate
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+class PrimaryBouncerInteractorTest : SysuiTestCase() {
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private lateinit var repository: KeyguardBouncerRepository
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
+    @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+    @Mock private lateinit var mPrimaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    private lateinit var mainHandler: FakeHandler
+    private lateinit var underTest: PrimaryBouncerInteractor
+    private lateinit var resources: TestableResources
+    private lateinit var trustRepository: FakeTrustRepository
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var testScope: TestScope
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
+            .thenReturn(KeyguardSecurityModel.SecurityMode.PIN)
+
+        DejankUtils.setImmediate(true)
+        testScope = TestScope()
+        mainHandler = FakeHandler(android.os.Looper.getMainLooper())
+        trustRepository = FakeTrustRepository()
+        featureFlags = FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) }
+        underTest =
+            PrimaryBouncerInteractor(
+                repository,
+                bouncerView,
+                mainHandler,
+                keyguardStateController,
+                keyguardSecurityModel,
+                mPrimaryBouncerCallbackInteractor,
+                falsingCollector,
+                dismissCallbackRegistry,
+                context,
+                keyguardUpdateMonitor,
+                trustRepository,
+                featureFlags,
+                testScope.backgroundScope,
+            )
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        whenever(repository.primaryBouncerShow.value).thenReturn(false)
+        whenever(bouncerView.delegate).thenReturn(bouncerViewDelegate)
+        resources = context.orCreateTestableResources
+    }
+
+    @Test
+    fun testShow_isScrimmed() {
+        underTest.show(true)
+        verify(repository).setKeyguardAuthenticated(null)
+        verify(repository).setPrimaryStartingToHide(false)
+        verify(repository).setPrimaryScrimmed(true)
+        verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
+        verify(repository).setPrimaryShowingSoon(true)
+        verify(keyguardStateController).notifyPrimaryBouncerShowing(true)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToShow()
+        verify(repository).setPrimaryShow(true)
+        verify(repository).setPrimaryShowingSoon(false)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.VISIBLE)
+    }
+
+    @Test
+    fun testShow_isNotScrimmed() {
+        verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE)
+    }
+
+    @Test
+    fun testShow_keyguardIsDone() {
+        whenever(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
+        verify(keyguardStateController, never()).notifyPrimaryBouncerShowing(true)
+        verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
+    }
+
+    @Test
+    fun testShow_isResumed() {
+        whenever(repository.primaryBouncerShow.value).thenReturn(true)
+        whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
+            .thenReturn(KeyguardSecurityModel.SecurityMode.SimPuk)
+
+        underTest.show(true)
+        verify(repository).setPrimaryShow(false)
+        verify(repository).setPrimaryShow(true)
+    }
+
+    @Test
+    fun testHide() {
+        underTest.hide()
+        verify(falsingCollector).onBouncerHidden()
+        verify(keyguardStateController).notifyPrimaryBouncerShowing(false)
+        verify(repository).setPrimaryShowingSoon(false)
+        verify(repository).setPrimaryShow(false)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.INVISIBLE)
+        verify(repository).setPrimaryStartDisappearAnimation(null)
+        verify(repository).setPanelExpansion(EXPANSION_HIDDEN)
+    }
+
+    @Test
+    fun testExpansion() {
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        underTest.setPanelExpansion(0.6f)
+        verify(repository).setPanelExpansion(0.6f)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
+    }
+
+    @Test
+    fun testExpansion_fullyShown() {
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        underTest.setPanelExpansion(EXPANSION_VISIBLE)
+        verify(falsingCollector).onBouncerShown()
+        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
+    }
+
+    @Test
+    fun testExpansion_fullyHidden() {
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        underTest.setPanelExpansion(EXPANSION_HIDDEN)
+        verify(repository).setPrimaryShow(false)
+        verify(falsingCollector).onBouncerHidden()
+        verify(mPrimaryBouncerCallbackInteractor).dispatchReset()
+        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden()
+    }
+
+    @Test
+    fun testExpansion_startingToHide() {
+        whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        underTest.setPanelExpansion(0.1f)
+        verify(repository).setPrimaryStartingToHide(true)
+        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
+    }
+
+    @Test
+    fun testShowMessage() {
+        val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
+        underTest.showMessage("abc", null)
+        verify(repository).setShowMessage(argCaptor.capture())
+        assertThat(argCaptor.value.message).isEqualTo("abc")
+    }
+
+    @Test
+    fun testDismissAction() {
+        val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
+        val cancelAction = mock(Runnable::class.java)
+        underTest.setDismissAction(onDismissAction, cancelAction)
+        verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
+    }
+
+    @Test
+    fun testUpdateResources() {
+        underTest.updateResources()
+        verify(repository).setResourceUpdateRequests(true)
+    }
+
+    @Test
+    fun testNotifyKeyguardAuthenticated() {
+        underTest.notifyKeyguardAuthenticated(true)
+        verify(repository).setKeyguardAuthenticated(true)
+    }
+
+    @Test
+    fun testNotifyShowedMessage() {
+        underTest.onMessageShown()
+        verify(repository).setShowMessage(null)
+    }
+
+    @Test
+    fun testSetKeyguardPosition() {
+        underTest.setKeyguardPosition(0f)
+        verify(repository).setKeyguardPosition(0f)
+    }
+
+    @Test
+    fun testNotifyKeyguardAuthenticatedHandled() {
+        underTest.notifyKeyguardAuthenticatedHandled()
+        verify(repository).setKeyguardAuthenticated(null)
+    }
+
+    @Test
+    fun testNotifyUpdatedResources() {
+        underTest.notifyUpdatedResources()
+        verify(repository).setResourceUpdateRequests(false)
+    }
+
+    @Test
+    fun testSetBackButtonEnabled() {
+        underTest.setBackButtonEnabled(true)
+        verify(repository).setIsBackButtonEnabled(true)
+    }
+
+    @Test
+    fun testStartDisappearAnimation_willRunDismissFromKeyguard() {
+        whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(true)
+
+        val runnable = mock(Runnable::class.java)
+        underTest.startDisappearAnimation(runnable)
+        // End runnable should run immediately
+        verify(runnable).run()
+        // ... while the disappear animation should never be run
+        verify(repository, never()).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
+    }
+
+    @Test
+    fun testStartDisappearAnimation_willNotRunDismissFromKeyguard_() {
+        whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(false)
+
+        val runnable = mock(Runnable::class.java)
+        underTest.startDisappearAnimation(runnable)
+        verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
+    }
+
+    @Test
+    fun testIsFullShowing() {
+        whenever(repository.primaryBouncerShow.value).thenReturn(true)
+        whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        assertThat(underTest.isFullyShowing()).isTrue()
+        whenever(repository.primaryBouncerShow.value).thenReturn(false)
+        assertThat(underTest.isFullyShowing()).isFalse()
+    }
+
+    @Test
+    fun testIsScrimmed() {
+        whenever(repository.primaryBouncerScrimmed.value).thenReturn(true)
+        assertThat(underTest.isScrimmed()).isTrue()
+        whenever(repository.primaryBouncerScrimmed.value).thenReturn(false)
+        assertThat(underTest.isScrimmed()).isFalse()
+    }
+
+    @Test
+    fun testIsInTransit() {
+        whenever(repository.primaryBouncerShowingSoon.value).thenReturn(true)
+        assertThat(underTest.isInTransit()).isTrue()
+        whenever(repository.primaryBouncerShowingSoon.value).thenReturn(false)
+        assertThat(underTest.isInTransit()).isFalse()
+        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        assertThat(underTest.isInTransit()).isTrue()
+    }
+
+    @Test
+    fun testIsAnimatingAway() {
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
+        assertThat(underTest.isAnimatingAway()).isTrue()
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        assertThat(underTest.isAnimatingAway()).isFalse()
+    }
+
+    @Test
+    fun testWillDismissWithAction() {
+        whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
+        assertThat(underTest.willDismissWithAction()).isTrue()
+        whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
+        assertThat(underTest.willDismissWithAction()).isFalse()
+    }
+
+    @Test
+    fun testSideFpsVisibility() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(true)
+    }
+
+    @Test
+    fun testSideFpsVisibility_notVisible() {
+        updateSideFpsVisibilityParameters(
+            isVisible = false,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    @Test
+    fun testSideFpsVisibility_sfpsNotEnabled() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = false,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    @Test
+    fun testSideFpsVisibility_fpsDetectionNotRunning() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = false,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    @Test
+    fun testSideFpsVisibility_UnlockingWithFpNotAllowed() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = false,
+            isAnimatingAway = false
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    @Test
+    fun testSideFpsVisibility_AnimatingAway() {
+        updateSideFpsVisibilityParameters(
+            isVisible = true,
+            sfpsEnabled = true,
+            fpsDetectionRunning = true,
+            isUnlockingWithFpAllowed = true,
+            isAnimatingAway = true
+        )
+        underTest.updateSideFpsVisibility()
+        verify(repository).setSideFpsShowing(false)
+    }
+
+    @Test
+    fun delayBouncerWhenFaceAuthPossible() {
+        mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+        // GIVEN bouncer should be delayed due to face auth
+        whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
+        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+            .thenReturn(true)
+        whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(true)
+
+        // WHEN bouncer show is requested
+        underTest.show(true)
+
+        // THEN primary show & primary showing soon aren't updated immediately
+        verify(repository, never()).setPrimaryShow(true)
+        verify(repository, never()).setPrimaryShowingSoon(false)
+
+        // WHEN all queued messages are dispatched
+        mainHandler.dispatchQueuedMessages()
+
+        // THEN primary show & primary showing soon are updated
+        verify(repository).setPrimaryShow(true)
+        verify(repository).setPrimaryShowingSoon(false)
+    }
+
+    @Test
+    fun noDelayBouncer_biometricsAllowed_postureDoesNotAllowFaceAuth() {
+        mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+        // GIVEN bouncer should not be delayed because device isn't in the right posture for
+        // face auth
+        whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
+        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+            .thenReturn(true)
+        whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(false)
+
+        // WHEN bouncer show is requested
+        underTest.show(true)
+
+        // THEN primary show & primary showing soon are updated immediately
+        verify(repository).setPrimaryShow(true)
+        verify(repository).setPrimaryShowingSoon(false)
+    }
+
+    @Test
+    fun delayBouncerWhenActiveUnlockPossible() {
+        testScope.run {
+            mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+            // GIVEN bouncer should be delayed due to active unlock
+            trustRepository.setCurrentUserActiveUnlockAvailable(true)
+            whenever(keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState())
+                .thenReturn(true)
+            runCurrent()
+
+            // WHEN bouncer show is requested
+            underTest.show(true)
+
+            // THEN primary show & primary showing soon were scheduled to update
+            verify(repository, never()).setPrimaryShow(true)
+            verify(repository, never()).setPrimaryShowingSoon(false)
+
+            // WHEN all queued messages are dispatched
+            mainHandler.dispatchQueuedMessages()
+
+            // THEN primary show & primary showing soon are updated
+            verify(repository).setPrimaryShow(true)
+            verify(repository).setPrimaryShowingSoon(false)
+        }
+    }
+
+    private fun updateSideFpsVisibilityParameters(
+        isVisible: Boolean,
+        sfpsEnabled: Boolean,
+        fpsDetectionRunning: Boolean,
+        isUnlockingWithFpAllowed: Boolean,
+        isAnimatingAway: Boolean
+    ) {
+        whenever(repository.primaryBouncerShow.value).thenReturn(isVisible)
+        resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled)
+        whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning)
+            .thenReturn(fpsDetectionRunning)
+        whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
+            .thenReturn(isUnlockingWithFpAllowed)
+        whenever(repository.primaryBouncerStartingDisappearAnimation.value)
+            .thenReturn(if (isAnimatingAway) Runnable {} else null)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
new file mode 100644
index 0000000..665456d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.bouncer.domain.interactor
+
+import android.os.Looper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.ui.BouncerView
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
+    private lateinit var repository: FakeKeyguardBouncerRepository
+    @Mock private lateinit var bouncerView: BouncerView
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+    @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
+    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    private val mainHandler = FakeHandler(Looper.getMainLooper())
+    private lateinit var underTest: PrimaryBouncerInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        repository = FakeKeyguardBouncerRepository()
+        underTest =
+            PrimaryBouncerInteractor(
+                repository,
+                bouncerView,
+                mainHandler,
+                keyguardStateController,
+                keyguardSecurityModel,
+                primaryBouncerCallbackInteractor,
+                falsingCollector,
+                dismissCallbackRegistry,
+                context,
+                keyguardUpdateMonitor,
+                Mockito.mock(TrustRepository::class.java),
+                FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
+                TestScope().backgroundScope,
+            )
+    }
+
+    @Test
+    fun notInteractableWhenExpansionIsBelow90Percent() = runTest {
+        val isInteractable = collectLastValue(underTest.isInteractable)
+
+        repository.setPrimaryShow(true)
+        repository.setPanelExpansion(0.15f)
+
+        assertThat(isInteractable()).isFalse()
+    }
+
+    @Test
+    fun notInteractableWhenExpansionAbove90PercentButNotVisible() = runTest {
+        val isInteractable = collectLastValue(underTest.isInteractable)
+
+        repository.setPrimaryShow(false)
+        repository.setPanelExpansion(0.05f)
+
+        assertThat(isInteractable()).isFalse()
+    }
+
+    @Test
+    fun isInteractableWhenExpansionAbove90PercentAndVisible() = runTest {
+        var isInteractable = collectLastValue(underTest.isInteractable)
+
+        repository.setPrimaryShow(true)
+        repository.setPanelExpansion(0.09f)
+
+        assertThat(isInteractable()).isTrue()
+
+        repository.setPanelExpansion(0.12f)
+        assertThat(isInteractable()).isFalse()
+
+        repository.setPanelExpansion(0f)
+        assertThat(isInteractable()).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 1642410..7af8a04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -18,31 +18,30 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class AuthMethodBouncerViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
             utils.authenticationRepository(),
         )
     private val underTest =
         PinBouncerViewModel(
+            applicationContext = context,
             applicationScope = testScope.backgroundScope,
             interactor =
                 utils.bouncerInteractor(
@@ -55,15 +54,14 @@
     @Test
     fun animateFailure() =
         testScope.runTest {
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
             val animateFailure by collectLastValue(underTest.animateFailure)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(animateFailure).isFalse()
 
             // Wrong PIN:
-            underTest.onPinButtonClicked(3)
-            underTest.onPinButtonClicked(4)
-            underTest.onPinButtonClicked(5)
-            underTest.onPinButtonClicked(6)
+            FakeAuthenticationRepository.DEFAULT_PIN.drop(2).forEach { digit ->
+                underTest.onPinButtonClicked(digit)
+            }
             underTest.onAuthenticateButtonClicked()
             assertThat(animateFailure).isTrue()
 
@@ -71,10 +69,9 @@
             assertThat(animateFailure).isFalse()
 
             // Correct PIN:
-            underTest.onPinButtonClicked(1)
-            underTest.onPinButtonClicked(2)
-            underTest.onPinButtonClicked(3)
-            underTest.onPinButtonClicked(4)
+            FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+                underTest.onPinButtonClicked(digit)
+            }
             underTest.onAuthenticateButtonClicked()
             assertThat(animateFailure).isFalse()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index e8c946c..2c96bcc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -18,16 +18,20 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,8 +42,8 @@
 @RunWith(JUnit4::class)
 class BouncerViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
@@ -49,39 +53,54 @@
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = utils.sceneInteractor(),
         )
-    private val underTest = utils.bouncerViewModel(bouncerInteractor)
+    private val underTest =
+        utils.bouncerViewModel(
+            bouncerInteractor = bouncerInteractor,
+            authenticationInteractor = authenticationInteractor,
+        )
 
     @Test
     fun authMethod_nonNullForSecureMethods_nullForNotSecureMethods() =
         testScope.runTest {
-            val authMethodViewModel: AuthMethodBouncerViewModel? by
-                collectLastValue(underTest.authMethod)
+            var authMethodViewModel: AuthMethodBouncerViewModel? = null
+
             authMethodsToTest().forEach { authMethod ->
-                authenticationInteractor.setAuthenticationMethod(authMethod)
+                utils.authenticationRepository.setAuthenticationMethod(authMethod)
+                val job = underTest.authMethod.onEach { authMethodViewModel = it }.launchIn(this)
+                runCurrent()
 
                 if (authMethod.isSecure) {
-                    assertThat(authMethodViewModel).isNotNull()
+                    assertWithMessage("View-model unexpectedly null for auth method $authMethod")
+                        .that(authMethodViewModel)
+                        .isNotNull()
                 } else {
-                    assertThat(authMethodViewModel).isNull()
+                    assertWithMessage(
+                            "View-model unexpectedly non-null for auth method $authMethod"
+                        )
+                        .that(authMethodViewModel)
+                        .isNull()
                 }
+
+                job.cancel()
             }
         }
 
     @Test
     fun authMethod_reusesInstances() =
         testScope.runTest {
-            val seen = mutableMapOf<AuthenticationMethodModel, AuthMethodBouncerViewModel>()
+            val seen =
+                mutableMapOf<DomainLayerAuthenticationMethodModel, AuthMethodBouncerViewModel>()
             val authMethodViewModel: AuthMethodBouncerViewModel? by
                 collectLastValue(underTest.authMethod)
             // First pass, populate our "seen" map:
             authMethodsToTest().forEach { authMethod ->
-                authenticationInteractor.setAuthenticationMethod(authMethod)
+                utils.authenticationRepository.setAuthenticationMethod(authMethod)
                 authMethodViewModel?.let { seen[authMethod] = it }
             }
 
             // Second pass, assert same instances are reused:
             authMethodsToTest().forEach { authMethod ->
-                authenticationInteractor.setAuthenticationMethod(authMethod)
+                utils.authenticationRepository.setAuthenticationMethod(authMethod)
                 authMethodViewModel?.let { assertThat(it).isSameInstanceAs(seen[authMethod]) }
             }
         }
@@ -89,7 +108,7 @@
     @Test
     fun authMethodsToTest_returnsCompleteSampleOfAllAuthMethodTypes() {
         assertThat(authMethodsToTest().map { it::class }.toSet())
-            .isEqualTo(AuthenticationMethodModel::class.sealedSubclasses.toSet())
+            .isEqualTo(DomainLayerAuthenticationMethodModel::class.sealedSubclasses.toSet())
     }
 
     @Test
@@ -97,16 +116,18 @@
         testScope.runTest {
             val message by collectLastValue(underTest.message)
             val throttling by collectLastValue(bouncerInteractor.throttling)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
             assertThat(message?.isUpdateAnimated).isTrue()
 
-            repeat(BouncerInteractor.THROTTLE_EVERY) {
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
                 // Wrong PIN.
                 bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
             }
             assertThat(message?.isUpdateAnimated).isFalse()
 
-            throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
+            throttling?.remainingMs?.let { remainingMs -> advanceTimeBy(remainingMs.toLong()) }
             assertThat(message?.isUpdateAnimated).isTrue()
         }
 
@@ -120,16 +141,18 @@
                     }
                 )
             val throttling by collectLastValue(bouncerInteractor.throttling)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
             assertThat(isInputEnabled).isTrue()
 
-            repeat(BouncerInteractor.THROTTLE_EVERY) {
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
                 // Wrong PIN.
                 bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
             }
             assertThat(isInputEnabled).isFalse()
 
-            throttling?.totalDurationSec?.let { seconds -> advanceTimeBy(seconds * 1000L) }
+            throttling?.remainingMs?.let { milliseconds -> advanceTimeBy(milliseconds.toLong()) }
             assertThat(isInputEnabled).isTrue()
         }
 
@@ -137,9 +160,11 @@
     fun throttlingDialogMessage() =
         testScope.runTest {
             val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
 
-            repeat(BouncerInteractor.THROTTLE_EVERY) {
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
                 // Wrong PIN.
                 assertThat(throttlingDialogMessage).isNull()
                 bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
@@ -150,15 +175,32 @@
             assertThat(throttlingDialogMessage).isNull()
         }
 
-    private fun authMethodsToTest(): List<AuthenticationMethodModel> {
+    private fun authMethodsToTest(): List<DomainLayerAuthenticationMethodModel> {
         return listOf(
-            AuthenticationMethodModel.None,
-            AuthenticationMethodModel.Swipe,
-            AuthenticationMethodModel.PIN(1234),
-            AuthenticationMethodModel.Password("password"),
-            AuthenticationMethodModel.Pattern(
-                listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1))
-            ),
+            DomainLayerAuthenticationMethodModel.None,
+            DomainLayerAuthenticationMethodModel.Swipe,
+            DomainLayerAuthenticationMethodModel.Pin,
+            DomainLayerAuthenticationMethodModel.Password,
+            DomainLayerAuthenticationMethodModel.Pattern,
         )
     }
+
+    private fun FakeAuthenticationRepository.setAuthenticationMethod(
+        model: DomainLayerAuthenticationMethodModel,
+    ) {
+        setAuthenticationMethod(
+            when (model) {
+                is DomainLayerAuthenticationMethodModel.None,
+                is DomainLayerAuthenticationMethodModel.Swipe ->
+                    DataLayerAuthenticationMethodModel.None
+                is DomainLayerAuthenticationMethodModel.Pin ->
+                    DataLayerAuthenticationMethodModel.Pin
+                is DomainLayerAuthenticationMethodModel.Password ->
+                    DataLayerAuthenticationMethodModel.Password
+                is DomainLayerAuthenticationMethodModel.Pattern ->
+                    DataLayerAuthenticationMethodModel.Pattern
+            }
+        )
+        setLockscreenEnabled(model !is DomainLayerAuthenticationMethodModel.None)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
new file mode 100644
index 0000000..8236165
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.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.bouncer.ui.viewmodel
+
+import android.os.Looper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
+import com.android.systemui.bouncer.ui.BouncerView
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardBouncerViewModelTest : SysuiTestCase() {
+    lateinit var underTest: KeyguardBouncerViewModel
+    lateinit var bouncerInteractor: PrimaryBouncerInteractor
+    @Mock lateinit var bouncerView: BouncerView
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+    @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
+    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    private val mainHandler = FakeHandler(Looper.getMainLooper())
+    val repository = FakeKeyguardBouncerRepository()
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        bouncerInteractor =
+            PrimaryBouncerInteractor(
+                repository,
+                bouncerView,
+                mainHandler,
+                keyguardStateController,
+                keyguardSecurityModel,
+                primaryBouncerCallbackInteractor,
+                falsingCollector,
+                dismissCallbackRegistry,
+                context,
+                keyguardUpdateMonitor,
+                Mockito.mock(TrustRepository::class.java),
+                FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
+                TestScope().backgroundScope,
+            )
+        underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
+    }
+
+    @Test
+    fun setMessage() = runTest {
+        var message: BouncerShowMessageModel? = null
+        val job = underTest.bouncerShowMessage.onEach { message = it }.launchIn(this)
+
+        repository.setShowMessage(BouncerShowMessageModel("abc", null))
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(message?.message).isEqualTo("abc")
+        job.cancel()
+    }
+
+    @Test
+    fun shouldUpdateSideFps_show() = runTest {
+        var count = 0
+        val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
+        repository.setPrimaryShow(true)
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(count).isEqualTo(1)
+        job.cancel()
+    }
+
+    @Test
+    fun shouldUpdateSideFps_hide() = runTest {
+        repository.setPrimaryShow(true)
+        var count = 0
+        val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
+        repository.setPrimaryShow(false)
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(count).isEqualTo(1)
+        job.cancel()
+    }
+
+    @Test
+    fun sideFpsShowing() = runTest {
+        var sideFpsIsShowing = false
+        val job = underTest.sideFpsShowing.onEach { sideFpsIsShowing = it }.launchIn(this)
+        repository.setSideFpsShowing(true)
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(sideFpsIsShowing).isEqualTo(true)
+        job.cancel()
+    }
+
+    @Test
+    fun isShowing() = runTest {
+        var isShowing: Boolean? = null
+        val job = underTest.isShowing.onEach { isShowing = it }.launchIn(this)
+        repository.setPrimaryShow(true)
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(isShowing).isEqualTo(true)
+        job.cancel()
+    }
+
+    @Test
+    fun isNotShowing() = runTest {
+        var isShowing: Boolean? = null
+        val job = underTest.isShowing.onEach { isShowing = it }.launchIn(this)
+        repository.setPrimaryShow(false)
+        // Run the tasks that are pending at this point of virtual time.
+        runCurrent()
+        assertThat(isShowing).isEqualTo(false)
+        job.cancel()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index f436aa3..4380af8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -19,7 +19,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
@@ -28,7 +28,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -40,8 +40,8 @@
 @RunWith(JUnit4::class)
 class PasswordBouncerViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
@@ -55,9 +55,11 @@
     private val bouncerViewModel =
         utils.bouncerViewModel(
             bouncerInteractor = bouncerInteractor,
+            authenticationInteractor = authenticationInteractor,
         )
     private val underTest =
         PasswordBouncerViewModel(
+            applicationScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
         )
@@ -71,84 +73,78 @@
     @Test
     fun onShown() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             underTest.onShown()
 
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD)
             assertThat(password).isEqualTo("")
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onPasswordInputChanged() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
+            runCurrent()
 
             underTest.onPasswordInputChanged("password")
 
             assertThat(message?.text).isEmpty()
             assertThat(password).isEqualTo("password")
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onAuthenticateKeyPressed_whenCorrect() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPasswordInputChanged("password")
 
             underTest.onAuthenticateKeyPressed()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     @Test
     fun onAuthenticateKeyPressed_whenWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPasswordInputChanged("wrong")
@@ -157,30 +153,27 @@
 
             assertThat(password).isEqualTo("")
             assertThat(message?.text).isEqualTo(WRONG_PASSWORD)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onAuthenticateKeyPressed_correctAfterWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPasswordInputChanged("wrong")
             underTest.onAuthenticateKeyPressed()
             assertThat(password).isEqualTo("")
             assertThat(message?.text).isEqualTo(WRONG_PASSWORD)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             // Enter the correct password:
@@ -189,12 +182,10 @@
 
             underTest.onAuthenticateKeyPressed()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     companion object {
-        private const val CONTAINER_NAME = "container1"
         private const val ENTER_YOUR_PASSWORD = "Enter your password"
         private const val WRONG_PASSWORD = "Wrong password"
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index d7d7154..ea2cad2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -19,7 +19,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
@@ -29,7 +30,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -41,8 +42,8 @@
 @RunWith(JUnit4::class)
 class PatternBouncerViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
@@ -56,6 +57,7 @@
     private val bouncerViewModel =
         utils.bouncerViewModel(
             bouncerInteractor = bouncerInteractor,
+            authenticationInteractor = authenticationInteractor,
         )
     private val underTest =
         PatternBouncerViewModel(
@@ -74,17 +76,16 @@
     @Test
     fun onShown() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             underTest.onShown()
@@ -92,49 +93,46 @@
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PATTERN)
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onDragStart() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
+            runCurrent()
 
             underTest.onDragStart()
 
             assertThat(message?.text).isEmpty()
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onDragEnd_whenCorrect() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onDragStart()
@@ -168,24 +166,22 @@
 
             underTest.onDragEnd()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     @Test
     fun onDragEnd_whenWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onDragStart()
@@ -203,24 +199,22 @@
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
             assertThat(message?.text).isEqualTo(WRONG_PATTERN)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onDragEnd_correctAfterWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
             )
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onDragStart()
@@ -236,7 +230,6 @@
             assertThat(selectedDots).isEmpty()
             assertThat(currentDot).isNull()
             assertThat(message?.text).isEqualTo(WRONG_PATTERN)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             // Enter the correct pattern:
@@ -251,25 +244,12 @@
 
             underTest.onDragEnd()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     companion object {
-        private const val CONTAINER_NAME = "container1"
         private const val ENTER_YOUR_PATTERN = "Enter your pattern"
         private const val WRONG_PATTERN = "Wrong pattern"
-        private val CORRECT_PATTERN =
-            listOf(
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 1),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 1),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 0),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 0),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 0),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 1),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 2),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 2),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 2),
-            )
+        private val CORRECT_PATTERN = FakeAuthenticationRepository.PATTERN
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 3bdaf05..531f86a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -19,8 +19,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
@@ -29,8 +29,8 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -42,8 +42,8 @@
 @RunWith(JUnit4::class)
 class PinBouncerViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor =
         utils.authenticationInteractor(
@@ -55,19 +55,13 @@
             sceneInteractor = sceneInteractor,
         )
     private val bouncerViewModel =
-        BouncerViewModel(
-            applicationContext = context,
-            applicationScope = testScope.backgroundScope,
-            interactorFactory =
-                object : BouncerInteractor.Factory {
-                    override fun create(containerName: String): BouncerInteractor {
-                        return bouncerInteractor
-                    }
-                },
-            containerName = CONTAINER_NAME,
+        utils.bouncerViewModel(
+            bouncerInteractor = bouncerInteractor,
+            authenticationInteractor = authenticationInteractor,
         )
     private val underTest =
         PinBouncerViewModel(
+            applicationContext = context,
             applicationScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
@@ -82,133 +76,149 @@
     @Test
     fun onShown() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
-            val pinLengths by collectLastValue(underTest.pinLengths)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             underTest.onShown()
 
             assertThat(message?.text).isEqualTo(ENTER_YOUR_PIN)
-            assertThat(pinLengths).isEqualTo(0 to 0)
-            assertThat(isUnlocked).isFalse()
+            assertThat(pin).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onPinButtonClicked() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
-            val pinLengths by collectLastValue(underTest.pinLengths)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
+            runCurrent()
 
             underTest.onPinButtonClicked(1)
 
             assertThat(message?.text).isEmpty()
-            assertThat(pinLengths).isEqualTo(0 to 1)
-            assertThat(isUnlocked).isFalse()
+            assertThat(pin).containsExactly(1)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onBackspaceButtonClicked() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
-            val pinLengths by collectLastValue(underTest.pinLengths)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
+            runCurrent()
             underTest.onPinButtonClicked(1)
-            assertThat(pinLengths).isEqualTo(0 to 1)
+            assertThat(pin).hasSize(1)
 
             underTest.onBackspaceButtonClicked()
 
             assertThat(message?.text).isEmpty()
-            assertThat(pinLengths).isEqualTo(1 to 0)
-            assertThat(isUnlocked).isFalse()
+            assertThat(pin).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
+    fun onPinEdit() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            underTest.onShown()
+
+            underTest.onPinButtonClicked(1)
+            underTest.onPinButtonClicked(2)
+            underTest.onPinButtonClicked(3)
+            underTest.onBackspaceButtonClicked()
+            underTest.onBackspaceButtonClicked()
+            underTest.onPinButtonClicked(4)
+            underTest.onPinButtonClicked(5)
+
+            assertThat(pin).containsExactly(1, 4, 5).inOrder()
+        }
+
+    @Test
     fun onBackspaceButtonLongPressed() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
-            val pinLengths by collectLastValue(underTest.pinLengths)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
+            runCurrent()
             underTest.onPinButtonClicked(1)
             underTest.onPinButtonClicked(2)
             underTest.onPinButtonClicked(3)
             underTest.onPinButtonClicked(4)
 
             underTest.onBackspaceButtonLongPressed()
-            repeat(4) { index ->
-                assertThat(pinLengths).isEqualTo(4 - index to 3 - index)
-                advanceTimeBy(PinBouncerViewModel.BACKSPACE_LONG_PRESS_DELAY_MS)
-            }
 
             assertThat(message?.text).isEmpty()
-            assertThat(pinLengths).isEqualTo(1 to 0)
-            assertThat(isUnlocked).isFalse()
+            assertThat(pin).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onAuthenticateButtonClicked_whenCorrect() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
-            underTest.onPinButtonClicked(1)
-            underTest.onPinButtonClicked(2)
-            underTest.onPinButtonClicked(3)
-            underTest.onPinButtonClicked(4)
+            FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+                underTest.onPinButtonClicked(digit)
+            }
 
             underTest.onAuthenticateButtonClicked()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
     @Test
     fun onAuthenticateButtonClicked_whenWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
-            val pinLengths by collectLastValue(underTest.pinLengths)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPinButtonClicked(1)
@@ -219,23 +229,22 @@
 
             underTest.onAuthenticateButtonClicked()
 
-            assertThat(pinLengths).isEqualTo(0 to 0)
+            assertThat(pin).isEmpty()
             assertThat(message?.text).isEqualTo(WRONG_PIN)
-            assertThat(isUnlocked).isFalse()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
         }
 
     @Test
     fun onAuthenticateButtonClicked_correctAfterWrong() =
         testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
-            val pinLengths by collectLastValue(underTest.pinLengths)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
-            assertThat(isUnlocked).isFalse()
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPinButtonClicked(1)
@@ -245,25 +254,118 @@
             underTest.onPinButtonClicked(5) // PIN is now wrong!
             underTest.onAuthenticateButtonClicked()
             assertThat(message?.text).isEqualTo(WRONG_PIN)
-            assertThat(pinLengths).isEqualTo(0 to 0)
-            assertThat(isUnlocked).isFalse()
+            assertThat(pin).isEmpty()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             // Enter the correct PIN:
-            underTest.onPinButtonClicked(1)
-            underTest.onPinButtonClicked(2)
-            underTest.onPinButtonClicked(3)
-            underTest.onPinButtonClicked(4)
+            FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+                underTest.onPinButtonClicked(digit)
+            }
             assertThat(message?.text).isEmpty()
 
             underTest.onAuthenticateButtonClicked()
 
-            assertThat(isUnlocked).isTrue()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
+    @Test
+    fun onAutoConfirm_whenCorrect() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            underTest.onShown()
+            FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+                underTest.onPinButtonClicked(digit)
+            }
+
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+        }
+
+    @Test
+    fun onAutoConfirm_whenWrong() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            val message by collectLastValue(bouncerViewModel.message)
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            underTest.onShown()
+            FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
+                underTest.onPinButtonClicked(digit)
+            }
+            underTest.onPinButtonClicked(
+                FakeAuthenticationRepository.DEFAULT_PIN.last() + 1
+            ) // PIN is now wrong!
+
+            assertThat(pin).isEmpty()
+            assertThat(message?.text).isEqualTo(WRONG_PIN)
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+        }
+
+    @Test
+    fun backspaceButtonAppearance_withoutAutoConfirm_alwaysShown() =
+        testScope.runTest {
+            val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
+
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+
+            assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
+        }
+
+    @Test
+    fun backspaceButtonAppearance_withAutoConfirmButNoInput_isHidden() =
+        testScope.runTest {
+            val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+
+            assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
+        }
+
+    @Test
+    fun backspaceButtonAppearance_withAutoConfirmAndInput_isShownQuiet() =
+        testScope.runTest {
+            val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+
+            underTest.onPinButtonClicked(1)
+
+            assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Subtle)
+        }
+
+    @Test
+    fun confirmButtonAppearance_withoutAutoConfirm_alwaysShown() =
+        testScope.runTest {
+            val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
+
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+
+            assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
+        }
+
+    @Test
+    fun confirmButtonAppearance_withAutoConfirm_isHidden() =
+        testScope.runTest {
+            val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAutoConfirmEnabled(true)
+
+            assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
+        }
+
     companion object {
-        private const val CONTAINER_NAME = "container1"
         private const val ENTER_YOUR_PIN = "Enter your pin"
         private const val WRONG_PIN = "Wrong pin"
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
new file mode 100644
index 0000000..4c279ea
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinInputViewModelTest.kt
@@ -0,0 +1,277 @@
+package com.android.systemui.bouncer.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.ui.viewmodel.EntryToken.ClearAll
+import com.android.systemui.bouncer.ui.viewmodel.EntryToken.Digit
+import com.android.systemui.bouncer.ui.viewmodel.PinInputSubject.Companion.assertThat
+import com.android.systemui.bouncer.ui.viewmodel.PinInputViewModel.Companion.empty
+import com.google.common.truth.Fact
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth.assertAbout
+import com.google.common.truth.Truth.assertThat
+import java.lang.Character.isDigit
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/**
+ * This test uses a mnemonic code to create and verify PinInput instances: strings of digits [0-9]
+ * for [Digit] tokens, as well as a `C` for the [ClearAll] token.
+ */
+@SmallTest
+@RunWith(JUnit4::class)
+class PinInputViewModelTest : SysuiTestCase() {
+
+    @Test
+    fun create_emptyList_throws() {
+        assertThrows(IllegalArgumentException::class.java) { PinInputViewModel(emptyList()) }
+    }
+
+    @Test
+    fun create_inputWithoutLeadingClearAll_throws() {
+        val exception =
+            assertThrows(IllegalArgumentException::class.java) {
+                PinInputViewModel(listOf(Digit(0)))
+            }
+        assertThat(exception).hasMessageThat().contains("does not begin with a ClearAll token")
+    }
+
+    @Test
+    fun create_inputNotInAscendingOrder_throws() {
+        val sentinel = ClearAll()
+        val first = Digit(0)
+        val second = Digit(1)
+        // [first] is created before [second] is created, thus their sequence numbers are ordered.
+        check(first.sequenceNumber < second.sequenceNumber)
+
+        val exception =
+            assertThrows(IllegalArgumentException::class.java) {
+                // Passing the [Digit] tokens in reverse order throws.
+                PinInputViewModel(listOf(sentinel, second, first))
+            }
+        assertThat(exception).hasMessageThat().contains("EntryTokens are not sorted")
+    }
+
+    @Test
+    fun append_digitToEmptyInput() {
+        val result = empty().append(0)
+        assertThat(result).matches("C0")
+    }
+
+    @Test
+    fun append_digitToExistingPin() {
+        val subject = pinInput("C1")
+        assertThat(subject.append(2)).matches("C12")
+    }
+
+    @Test
+    fun append_withTwoCompletePinsEntered_dropsFirst() {
+        val subject = pinInput("C12C34C")
+        assertThat(subject.append(5)).matches("C34C5")
+    }
+
+    @Test
+    fun deleteLast_removesLastDigit() {
+        val subject = pinInput("C12")
+        assertThat(subject.deleteLast()).matches("C1")
+    }
+
+    @Test
+    fun deleteLast_onEmptyInput_returnsSameInstance() {
+        val subject = empty()
+        assertThat(subject.deleteLast()).isSameInstanceAs(subject)
+    }
+
+    @Test
+    fun deleteLast_onInputEndingInClearAll_returnsSameInstance() {
+        val subject = pinInput("C12C")
+        assertThat(subject.deleteLast()).isSameInstanceAs(subject)
+    }
+
+    @Test
+    fun clearAll_appendsClearAllEntryToExistingInput() {
+        val subject = pinInput("C12")
+        assertThat(subject.clearAll()).matches("C12C")
+    }
+
+    @Test
+    fun clearAll_onInputEndingInClearAll_returnsSameInstance() {
+        val subject = pinInput("C12C")
+        assertThat(subject.clearAll()).isSameInstanceAs(subject)
+    }
+
+    @Test
+    fun clearAll_retainsUpToTwoPinEntries() {
+        val subject = pinInput("C12C34")
+        assertThat(subject.clearAll()).matches("C12C34C")
+    }
+
+    @Test
+    fun isEmpty_onEmptyInput_returnsTrue() {
+        val subject = empty()
+        assertThat(subject.isEmpty()).isTrue()
+    }
+
+    @Test
+    fun isEmpty_whenLastEntryIsDigit_returnsFalse() {
+        val subject = pinInput("C1234")
+        assertThat(subject.isEmpty()).isFalse()
+    }
+
+    @Test
+    fun isEmpty_whenLastEntryIsClearAll_returnsTrue() {
+        val subject = pinInput("C1234C")
+        assertThat(subject.isEmpty()).isTrue()
+    }
+
+    @Test
+    fun getPin_onEmptyInput_returnsEmptyList() {
+        val subject = empty()
+        assertThat(subject.getPin()).isEmpty()
+    }
+
+    @Test
+    fun getPin_whenLastEntryIsDigit_returnsPin() {
+        val subject = pinInput("C1234")
+        assertThat(subject.getPin()).containsExactly(1, 2, 3, 4)
+    }
+
+    @Test
+    fun getPin_withMultiplePins_returnsLastPin() {
+        val subject = pinInput("C1234C5678")
+        assertThat(subject.getPin()).containsExactly(5, 6, 7, 8)
+    }
+
+    @Test
+    fun getPin_whenLastEntryIsClearAll_returnsEmptyList() {
+        val subject = pinInput("C1234C")
+        assertThat(subject.getPin()).isEmpty()
+    }
+
+    @Test
+    fun mostRecentClearAllMarker_onEmptyInput_returnsSentinel() {
+        val subject = empty()
+        val sentinel = subject.input[0] as ClearAll
+
+        assertThat(subject.mostRecentClearAll()).isSameInstanceAs(sentinel)
+    }
+
+    @Test
+    fun mostRecentClearAllMarker_whenLastEntryIsDigit_returnsSentinel() {
+        val subject = pinInput("C1234")
+        val sentinel = subject.input[0] as ClearAll
+
+        assertThat(subject.mostRecentClearAll()).isSameInstanceAs(sentinel)
+    }
+
+    @Test
+    fun mostRecentClearAllMarker_withMultiplePins_returnsLastMarker() {
+        val subject = pinInput("C1234C5678")
+        val lastMarker = subject.input[5] as ClearAll
+
+        assertThat(subject.mostRecentClearAll()).isSameInstanceAs(lastMarker)
+    }
+
+    @Test
+    fun mostRecentClearAllMarker_whenLastEntryIsClearAll_returnsLastEntry() {
+        val subject = pinInput("C1234C")
+        val lastEntry = subject.input[5] as ClearAll
+
+        assertThat(subject.mostRecentClearAll()).isSameInstanceAs(lastEntry)
+    }
+
+    @Test
+    fun getDigits_invalidClearAllMarker_onEmptyInput_returnsEmptyList() {
+        val subject = empty()
+        assertThat(subject.getDigits(ClearAll())).isEmpty()
+    }
+
+    @Test
+    fun getDigits_invalidClearAllMarker_whenLastEntryIsDigit_returnsEmptyList() {
+        val subject = pinInput("C1234")
+        assertThat(subject.getDigits(ClearAll())).isEmpty()
+    }
+
+    @Test
+    fun getDigits_clearAllMarkerPointsToFirstPin_returnsFirstPinDigits() {
+        val subject = pinInput("C1234C5678")
+        val marker = subject.input[0] as ClearAll
+
+        assertThat(subject.getDigits(marker).map { it.input }).containsExactly(1, 2, 3, 4)
+    }
+
+    @Test
+    fun getDigits_clearAllMarkerPointsToLastPin_returnsLastPinDigits() {
+        val subject = pinInput("C1234C5678")
+        val marker = subject.input[5] as ClearAll
+
+        assertThat(subject.getDigits(marker).map { it.input }).containsExactly(5, 6, 7, 8)
+    }
+
+    @Test
+    fun entryToken_equality() {
+        val clearAll = ClearAll()
+        val zero = Digit(0)
+        val one = Digit(1)
+
+        // Guava's EqualsTester is not available in this codebase.
+        assertThat(zero.equals(zero.copy())).isTrue()
+
+        assertThat(zero.equals(one)).isFalse()
+        assertThat(zero.equals(clearAll)).isFalse()
+
+        assertThat(clearAll.equals(clearAll.copy())).isTrue()
+        assertThat(clearAll.equals(zero)).isFalse()
+
+        // Not equal when the sequence number does not match
+        assertThat(zero.equals(Digit(0))).isFalse()
+        assertThat(clearAll.equals(ClearAll())).isFalse()
+    }
+
+    private fun pinInput(mnemonics: String): PinInputViewModel {
+        return PinInputViewModel(
+            mnemonics.map {
+                when {
+                    it == 'C' -> ClearAll()
+                    isDigit(it) -> Digit(it.digitToInt())
+                    else -> throw AssertionError()
+                }
+            }
+        )
+    }
+}
+
+private class PinInputSubject
+private constructor(metadata: FailureMetadata, private val actual: PinInputViewModel) :
+    Subject(metadata, actual) {
+
+    fun matches(mnemonics: String) {
+        val actualMnemonics =
+            actual.input
+                .map { entry ->
+                    when (entry) {
+                        is Digit -> entry.input.digitToChar()
+                        is ClearAll -> 'C'
+                        else -> throw IllegalArgumentException()
+                    }
+                }
+                .joinToString(separator = "")
+
+        if (mnemonics != actualMnemonics) {
+            failWithActual(
+                Fact.simpleFact(
+                    "expected pin input to be '$mnemonics' but is '$actualMnemonics' instead"
+                )
+            )
+        }
+    }
+
+    companion object {
+        fun assertThat(input: PinInputViewModel): PinInputSubject =
+            assertAbout(Factory(::PinInputSubject)).that(input)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index d6cafcb..5a5c058 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -211,7 +211,7 @@
                 context.resources.getFloat(R.dimen.physical_charger_port_location_normalized_y)
         val expectedCenterX: Float
         val expectedCenterY: Float
-        when (context.display.rotation) {
+        when (checkNotNull(context.display).rotation) {
             Surface.ROTATION_90 -> {
                 expectedCenterX = width * normalizedPortPosY
                 expectedCenterY = height * (1 - normalizedPortPosX)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index 292fdff..f770a38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -187,4 +187,11 @@
         when(mFalsingDataProvider.isUnfolded()).thenReturn(true);
         assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
     }
+
+    @Test
+    public void testTrackpadGesture() {
+        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
+        when(mFalsingDataProvider.isFromTrackpad()).thenReturn(true);
+        assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
index 9254617..4da151c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/ClassifierTest.java
@@ -18,8 +18,11 @@
 
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
 import android.util.DisplayMetrics;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 
+import androidx.test.uiautomator.Configurator;
+
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dock.DockManagerFake;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -80,6 +83,10 @@
         mDataProvider.onSessionEnd();
     }
 
+    protected static int getPointerAction(int actionType, int index) {
+        return actionType + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
+    }
+
     protected MotionEvent appendDownEvent(float x, float y) {
         return appendMotionEvent(MotionEvent.ACTION_DOWN, x, y);
     }
@@ -124,4 +131,55 @@
 
         return motionEvent;
     }
+
+    protected MotionEvent appendTrackpadDownEvent(float x, float y) {
+        return appendTrackpadMotionEvent(MotionEvent.ACTION_DOWN, x, y, 1);
+    }
+
+    protected MotionEvent appendTrackpadMoveEvent(float x, float y, int pointerCount) {
+        return appendTrackpadMotionEvent(MotionEvent.ACTION_MOVE, x, y, pointerCount);
+    }
+
+    protected MotionEvent appendTrackpadPointerDownEvent(int actionType, float x, float y,
+            int pointerCount) {
+        return appendTrackpadMotionEvent(actionType, x, y, pointerCount);
+    }
+
+    private MotionEvent appendTrackpadMotionEvent(int actionType, float x, float y,
+            int pointerCount) {
+        long eventTime = mMotionEvents.isEmpty() ? 1 : mMotionEvents.get(
+                mMotionEvents.size() - 1).getEventTime() + 1;
+        return appendTrackpadMotionEvent(actionType, x, y, pointerCount, eventTime);
+    }
+
+    private MotionEvent appendTrackpadMotionEvent(int actionType, float x, float y,
+            int pointerCount, long eventTime) {
+        MotionEvent.PointerProperties[] pointerProperties =
+                new MotionEvent.PointerProperties[pointerCount];
+        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount];
+        for (int i = 0; i < pointerCount; i++) {
+            pointerProperties[i] = getPointerProperties(i);
+            pointerCoords[i] = getPointerCoords(x, y);
+        }
+        return MotionEvent.obtain(1, eventTime, actionType, pointerCount, pointerProperties,
+                pointerCoords, 0, 0, 1.0f, 1.0f, 0, 0,
+                InputDevice.SOURCE_TOUCHPAD | InputDevice.SOURCE_MOUSE, 0, 0,
+                MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE);
+    }
+
+    private static MotionEvent.PointerProperties getPointerProperties(int pointerId) {
+        MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
+        properties.id = pointerId;
+        properties.toolType = Configurator.getInstance().getToolType();
+        return properties;
+    }
+
+    private static MotionEvent.PointerCoords getPointerCoords(float x, float y) {
+        MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+        coords.pressure = 1;
+        coords.size = 1;
+        coords.x = x;
+        coords.y = y;
+        return coords;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index 7e06680..0353f87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -282,6 +282,22 @@
     }
 
     @Test
+    public void test_IsFromTrackpad() {
+        MotionEvent motionEventOrigin = appendTrackpadDownEvent(0, 0);
+
+        mDataProvider.onMotionEvent(motionEventOrigin);
+        mDataProvider.onMotionEvent(
+                appendTrackpadPointerDownEvent(getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 1),
+                        0, 0, 2));
+        mDataProvider.onMotionEvent(
+                appendTrackpadPointerDownEvent(getPointerAction(MotionEvent.ACTION_POINTER_DOWN, 2),
+                        0, 0, 3));
+        mDataProvider.onMotionEvent(appendTrackpadMoveEvent(1, -1, 3));
+        assertThat(mDataProvider.isFromTrackpad()).isTrue();
+        mDataProvider.onSessionEnd();
+    }
+
+    @Test
     public void test_isWirelessCharging() {
         assertThat(mDataProvider.isDocked()).isFalse();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 39fb7b4..9671966 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -26,6 +26,7 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
 import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
+import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -93,12 +94,16 @@
     @Mock
     private ClipboardImageLoader mClipboardImageLoader;
     @Mock
+    private ClipboardTransitionExecutor mClipboardTransitionExecutor;
+    @Mock
     private UiEventLogger mUiEventLogger;
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
     private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
     @Mock
     private Animator mAnimator;
+    private ArgumentCaptor<Animator.AnimatorListener> mAnimatorListenerCaptor =
+            ArgumentCaptor.forClass(Animator.AnimatorListener.class);
 
     private ClipData mSampleClipData;
 
@@ -117,6 +122,7 @@
 
         when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator);
         when(mClipboardOverlayView.getExitAnimation()).thenReturn(mAnimator);
+        when(mClipboardOverlayView.getFadeOutAnimation()).thenReturn(mAnimator);
         when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
                 getImeInsets(new Rect(0, 0, 0, 0)));
 
@@ -124,7 +130,16 @@
                 new ClipData.Item("Test Item"));
 
         mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, true); // turned off for legacy tests
+        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, true); // turned off for old tests
+    }
 
+    /**
+     * Needs to be done after setting flags for legacy tests, since the value of
+     * CLIPBOARD_SHARED_TRANSITIONS is checked during construction. This can be moved back into
+     * the setup method once CLIPBOARD_SHARED_TRANSITIONS is fully released and the tests where it
+     * is false are removed.[
+     */
+    private void initController() {
         mOverlayController = new ClipboardOverlayController(
                 mContext,
                 mClipboardOverlayView,
@@ -136,6 +151,7 @@
                 mClipboardUtils,
                 mExecutor,
                 mClipboardImageLoader,
+                mClipboardTransitionExecutor,
                 mUiEventLogger);
         verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
         mCallbacks = mOverlayCallbacksCaptor.getValue();
@@ -148,6 +164,8 @@
 
     @Test
     public void test_setClipData_invalidImageData_legacy() {
+        initController();
+
         ClipData clipData = new ClipData("", new String[]{"image/png"},
                 new ClipData.Item(Uri.parse("")));
         mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
@@ -161,6 +179,8 @@
 
     @Test
     public void test_setClipData_nonImageUri_legacy() {
+        initController();
+
         ClipData clipData = new ClipData("", new String[]{"resource/png"},
                 new ClipData.Item(Uri.parse("")));
         mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
@@ -175,6 +195,8 @@
     @Test
     public void test_setClipData_textData_legacy() {
         mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+        initController();
+
         mOverlayController.setClipData(mSampleClipData, "abc");
 
         verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
@@ -186,6 +208,8 @@
     @Test
     public void test_setClipData_sensitiveTextData_legacy() {
         mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+        initController();
+
         ClipDescription description = mSampleClipData.getDescription();
         PersistableBundle b = new PersistableBundle();
         b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
@@ -202,6 +226,7 @@
     public void test_setClipData_repeatedCalls_legacy() {
         when(mAnimator.isRunning()).thenReturn(true);
         mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, false);
+        initController();
 
         mOverlayController.setClipData(mSampleClipData, "");
         mOverlayController.setClipData(mSampleClipData, "");
@@ -211,6 +236,8 @@
 
     @Test
     public void test_setClipData_invalidImageData() {
+        initController();
+
         ClipData clipData = new ClipData("", new String[]{"image/png"},
                 new ClipData.Item(Uri.parse("")));
 
@@ -223,6 +250,7 @@
 
     @Test
     public void test_setClipData_nonImageUri() {
+        initController();
         ClipData clipData = new ClipData("", new String[]{"resource/png"},
                 new ClipData.Item(Uri.parse("")));
 
@@ -235,6 +263,7 @@
 
     @Test
     public void test_setClipData_textData() {
+        initController();
         mOverlayController.setClipData(mSampleClipData, "abc");
 
         verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
@@ -245,6 +274,7 @@
 
     @Test
     public void test_setClipData_sensitiveTextData() {
+        initController();
         ClipDescription description = mSampleClipData.getDescription();
         PersistableBundle b = new PersistableBundle();
         b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
@@ -259,6 +289,7 @@
 
     @Test
     public void test_setClipData_repeatedCalls() {
+        initController();
         when(mAnimator.isRunning()).thenReturn(true);
 
         mOverlayController.setClipData(mSampleClipData, "");
@@ -268,7 +299,9 @@
     }
 
     @Test
-    public void test_viewCallbacks_onShareTapped() {
+    public void test_viewCallbacks_onShareTapped_sharedTransitionsOff() {
+        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
+        initController();
         mOverlayController.setClipData(mSampleClipData, "");
 
         mCallbacks.onShareButtonTapped();
@@ -278,7 +311,22 @@
     }
 
     @Test
-    public void test_viewCallbacks_onDismissTapped() {
+    public void test_viewCallbacks_onShareTapped() {
+        initController();
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        mCallbacks.onShareButtonTapped();
+        verify(mAnimator).addListener(mAnimatorListenerCaptor.capture());
+        mAnimatorListenerCaptor.getValue().onAnimationEnd(mAnimator);
+
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "");
+        verify(mClipboardOverlayView, times(1)).getFadeOutAnimation();
+    }
+
+    @Test
+    public void test_viewCallbacks_onDismissTapped_sharedTransitionsOff() {
+        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
+        initController();
         mOverlayController.setClipData(mSampleClipData, "");
 
         mCallbacks.onDismissButtonTapped();
@@ -288,7 +336,35 @@
     }
 
     @Test
+    public void test_viewCallbacks_onDismissTapped() {
+        initController();
+
+        mCallbacks.onDismissButtonTapped();
+        verify(mAnimator).addListener(mAnimatorListenerCaptor.capture());
+        mAnimatorListenerCaptor.getValue().onAnimationEnd(mAnimator);
+
+        // package name is null since we haven't actually set a source for this test
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, null);
+        verify(mClipboardOverlayView, times(1)).getExitAnimation();
+    }
+
+    @Test
+    public void test_multipleDismissals_dismissesOnce_sharedTransitionsOff() {
+        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
+        initController();
+        mCallbacks.onSwipeDismissInitiated(mAnimator);
+        mCallbacks.onDismissButtonTapped();
+        mCallbacks.onSwipeDismissInitiated(mAnimator);
+        mCallbacks.onDismissButtonTapped();
+
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED, 0, null);
+        verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+    }
+
+    @Test
     public void test_multipleDismissals_dismissesOnce() {
+        initController();
+
         mCallbacks.onSwipeDismissInitiated(mAnimator);
         mCallbacks.onDismissButtonTapped();
         mCallbacks.onSwipeDismissInitiated(mAnimator);
@@ -300,6 +376,7 @@
 
     @Test
     public void test_remoteCopy_withFlagOn() {
+        initController();
         when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
 
         mOverlayController.setClipData(mSampleClipData, "");
@@ -309,6 +386,7 @@
 
     @Test
     public void test_nonRemoteCopy() {
+        initController();
         when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false);
 
         mOverlayController.setClipData(mSampleClipData, "");
@@ -318,13 +396,16 @@
 
     @Test
     public void test_logsUseLastClipSource() {
-        mOverlayController.setClipData(mSampleClipData, "first.package");
-        mCallbacks.onDismissButtonTapped();
-        mOverlayController.setClipData(mSampleClipData, "second.package");
-        mCallbacks.onDismissButtonTapped();
+        initController();
 
-        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "first.package");
-        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "second.package");
+        mOverlayController.setClipData(mSampleClipData, "first.package");
+        mCallbacks.onShareButtonTapped();
+
+        mOverlayController.setClipData(mSampleClipData, "second.package");
+        mCallbacks.onShareButtonTapped();
+
+        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "first.package");
+        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "second.package");
         verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "first.package");
         verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_SHOWN_EXPANDED, 0, "second.package");
         verifyNoMoreInteractions(mUiEventLogger);
@@ -332,6 +413,7 @@
 
     @Test
     public void test_logOnClipboardActionsShown() {
+        initController();
         ClipData.Item item = mSampleClipData.getItemAt(0);
         item.setTextLinks(Mockito.mock(TextLinks.class));
         when(mClipboardUtils.isRemoteCopy(any(Context.class), any(ClipData.class), anyString()))
@@ -357,6 +439,7 @@
 
     @Test
     public void test_noInsets_showsExpanded() {
+        initController();
         mOverlayController.setClipData(mSampleClipData, "");
 
         verify(mClipboardOverlayView, never()).setMinimized(true);
@@ -366,6 +449,7 @@
 
     @Test
     public void test_insets_showsMinimized() {
+        initController();
         when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
                 getImeInsets(new Rect(0, 0, 0, 1)));
         mOverlayController.setClipData(mSampleClipData, "abc");
@@ -389,6 +473,7 @@
 
     @Test
     public void test_insetsChanged_minimizes() {
+        initController();
         mOverlayController.setClipData(mSampleClipData, "");
         verify(mClipboardOverlayView, never()).setMinimized(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
index 2a4c0eb..662c89c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/IntentCreatorTest.java
@@ -80,6 +80,7 @@
         assertEquals(Intent.ACTION_EDIT, intent.getAction());
         assertEquals("image/*", intent.getType());
         assertEquals(null, intent.getComponent());
+        assertEquals("clipboard", intent.getStringExtra("edit_source"));
         assertFlags(intent, EXTERNAL_INTENT_FLAGS);
 
         // try again with an editor component
@@ -126,7 +127,8 @@
         assertEquals(Intent.ACTION_CHOOSER, intent.getAction());
         assertFlags(intent, EXTERNAL_INTENT_FLAGS);
         Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class);
-        assertEquals(uri, target.getData());
+        assertEquals(uri, target.getParcelableExtra(Intent.EXTRA_STREAM, Uri.class));
+        assertEquals(uri, target.getClipData().getItemAt(0).getUri());
         assertEquals("image/png", target.getType());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
index afd9be5..f0006e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
@@ -18,6 +18,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.ViewGroup;
@@ -28,10 +35,15 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
 
 /**
  * Tests for {@link SeekBarWithIconButtonsView}
@@ -48,14 +60,22 @@
     private SeekBar mSeekbar;
     private SeekBarWithIconButtonsView mIconDiscreteSliderLinearLayout;
 
+    @Mock
+    private OnSeekBarWithIconButtonsChangeListener mOnSeekBarChangeListener;
+
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
         mIconDiscreteSliderLinearLayout = new SeekBarWithIconButtonsView(mContext);
         mIconStart = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start);
         mIconEnd = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end);
         mIconStartFrame = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start_frame);
         mIconEndFrame = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end_frame);
         mSeekbar = mIconDiscreteSliderLinearLayout.findViewById(R.id.seekbar);
+
+        mIconDiscreteSliderLinearLayout.setOnSeekBarWithIconButtonsChangeListener(
+                mOnSeekBarChangeListener);
     }
 
     @Test
@@ -109,6 +129,49 @@
     }
 
     @Test
+    public void setProgress_onlyOnProgressChangedTriggeredWithFromUserFalse() {
+        reset(mOnSeekBarChangeListener);
+        mIconDiscreteSliderLinearLayout.setProgress(1);
+
+        verify(mOnSeekBarChangeListener).onProgressChanged(
+                eq(mSeekbar), /* progress= */ eq(1), /* fromUser= */ eq(false));
+        verify(mOnSeekBarChangeListener, never()).onStartTrackingTouch(/* seekBar= */ any());
+        verify(mOnSeekBarChangeListener, never()).onStopTrackingTouch(/* seekBar= */ any());
+        verify(mOnSeekBarChangeListener, never()).onUserInteractionFinalized(
+                /* seekBar= */any(), /* control= */ anyInt());
+    }
+
+    @Test
+    public void clickIconEnd_triggerCallbacksInSequence() {
+        final int magnitude = mIconDiscreteSliderLinearLayout.getChangeMagnitude();
+        mIconDiscreteSliderLinearLayout.setProgress(0);
+        reset(mOnSeekBarChangeListener);
+
+        mIconEndFrame.performClick();
+
+        InOrder inOrder = Mockito.inOrder(mOnSeekBarChangeListener);
+        inOrder.verify(mOnSeekBarChangeListener).onProgressChanged(
+                eq(mSeekbar), /* progress= */ eq(magnitude), /* fromUser= */ eq(true));
+        inOrder.verify(mOnSeekBarChangeListener).onUserInteractionFinalized(
+                eq(mSeekbar), eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON));
+    }
+
+    @Test
+    public void clickIconStart_triggerCallbacksInSequence() {
+        final int magnitude = mIconDiscreteSliderLinearLayout.getChangeMagnitude();
+        mIconDiscreteSliderLinearLayout.setProgress(magnitude);
+        reset(mOnSeekBarChangeListener);
+
+        mIconStartFrame.performClick();
+
+        InOrder inOrder = Mockito.inOrder(mOnSeekBarChangeListener);
+        inOrder.verify(mOnSeekBarChangeListener).onProgressChanged(
+                eq(mSeekbar), /* progress= */ eq(0), /* fromUser= */ eq(true));
+        inOrder.verify(mOnSeekBarChangeListener).onUserInteractionFinalized(
+                eq(mSeekbar), eq(OnSeekBarWithIconButtonsChangeListener.ControlUnitType.BUTTON));
+    }
+
+    @Test
     public void setProgressStateLabels_getExpectedStateDescriptionOnInitialization() {
         String[] stateLabels = new String[]{"1", "2", "3", "4", "5"};
         mIconDiscreteSliderLinearLayout.setMax(stateLabels.length);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
index ca6282c..40f0ed3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
@@ -31,6 +31,7 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.log.core.FakeLogBuffer;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -66,7 +67,8 @@
         mStateController = new DreamOverlayStateController(
                 mExecutor,
                 /* overlayEnabled= */ true,
-                mFeatureFlags);
+                mFeatureFlags,
+                FakeLogBuffer.Factory.Companion.create());
         mLiveData = new ComplicationCollectionLiveData(mStateController);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
index 57d3a01..cbbbe52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
@@ -28,7 +28,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.complication.dagger.DreamClockTimeComplicationComponent;
 import com.android.systemui.condition.SelfExecutingMonitor;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.shared.condition.Monitor;
@@ -36,11 +38,10 @@
 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;
 
-import javax.inject.Provider;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class DreamClockTimeComplicationTest extends SysuiTestCase {
@@ -55,8 +56,10 @@
     private DreamClockTimeComplication mComplication;
 
     @Mock
-    private Provider<DreamClockTimeComplication.DreamClockTimeViewHolder>
-            mDreamClockTimeViewHolderProvider;
+    private DreamClockTimeComplicationComponent.Factory mComponentFactory;
+
+    @Mock
+    private DreamClockTimeComplicationComponent mComponent;
 
     @Mock
     private DreamClockTimeComplication.DreamClockTimeViewHolder
@@ -71,12 +74,19 @@
     @Mock
     private ComplicationLayoutParams mLayoutParams;
 
+    @Mock
+    private DreamClockTimeComplication.DreamClockTimeViewController mViewController;
+
+    @Mock
+    private UiEventLogger mUiEventLogger;
+
     private Monitor mMonitor;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        when(mDreamClockTimeViewHolderProvider.get()).thenReturn(mDreamClockTimeViewHolder);
+        when(mComponentFactory.create()).thenReturn(mComponent);
+        when(mComponent.getViewHolder()).thenReturn(mDreamClockTimeViewHolder);
         mMonitor = SelfExecutingMonitor.createInstance();
     }
 
@@ -100,21 +110,21 @@
     @Test
     public void testComplicationRequiredTypeAvailability() {
         final DreamClockTimeComplication complication =
-                new DreamClockTimeComplication(mDreamClockTimeViewHolderProvider);
+                new DreamClockTimeComplication(mComponentFactory);
         assertEquals(Complication.COMPLICATION_TYPE_TIME,
                 complication.getRequiredTypeAvailability());
     }
 
     /**
      * Verifies {@link DreamClockTimeComplication.DreamClockTimeViewHolder} is obtainable from its
-     * provider when the complication creates view.
+     * component when the complication creates view.
      */
     @Test
-    public void testComplicationViewHolderProviderOnCreateView() {
+    public void testComplicationViewHolderComponentOnCreateView() {
         final DreamClockTimeComplication complication =
-                new DreamClockTimeComplication(mDreamClockTimeViewHolderProvider);
+                new DreamClockTimeComplication(mComponentFactory);
         final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel);
-        verify(mDreamClockTimeViewHolderProvider).get();
+        verify(mComponent).getViewHolder();
         assertThat(viewHolder).isEqualTo(mDreamClockTimeViewHolder);
     }
 
@@ -125,8 +135,23 @@
     @Test
     public void testComplicationViewHolderContentAccessors() {
         final DreamClockTimeComplication.DreamClockTimeViewHolder viewHolder =
-                new DreamClockTimeComplication.DreamClockTimeViewHolder(mView, mLayoutParams);
+                new DreamClockTimeComplication.DreamClockTimeViewHolder(mView, mLayoutParams,
+                        mViewController);
         assertThat(viewHolder.getView()).isEqualTo(mView);
         assertThat(viewHolder.getLayoutParams()).isEqualTo(mLayoutParams);
     }
+
+    @Test
+    public void testClick_logUiEvent() {
+        final DreamClockTimeComplication.DreamClockTimeViewController controller =
+                new DreamClockTimeComplication.DreamClockTimeViewController(mView, mUiEventLogger);
+        controller.onViewAttached();
+
+        final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
+                ArgumentCaptor.forClass(View.OnClickListener.class);
+        verify(mView).setOnClickListener(clickListenerCaptor.capture());
+
+        clickListenerCaptor.getValue().onClick(mView);
+        verify(mUiEventLogger).log(DreamOverlayUiEvent.DREAM_CLOCK_TAPPED);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
index 63b0b25..2207180 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
@@ -234,9 +234,7 @@
         verify(mHomeControlsView).setOnClickListener(clickListenerCaptor.capture());
 
         clickListenerCaptor.getValue().onClick(mHomeControlsView);
-        verify(mUiEventLogger).log(
-                DreamHomeControlsComplication.DreamHomeControlsChipViewController
-                        .DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
+        verify(mUiEventLogger).log(DreamOverlayUiEvent.DREAM_HOME_CONTROLS_TAPPED);
     }
 
     private void setHaveFavorites(boolean value) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 38372a3..8416c46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -18,13 +18,14 @@
 
 import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
+import android.view.HapticFeedbackConstants
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.controls.settings.ControlsSettingsDialogManager
 import com.android.systemui.controls.settings.FakeControlsSettingsRepository
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -34,9 +35,9 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Answers
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.doNothing
 import org.mockito.Mockito.doReturn
@@ -44,6 +45,7 @@
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 import java.util.Optional
 
@@ -69,8 +71,6 @@
     @Mock
     private lateinit var metricsLogger: ControlsMetricsLogger
     @Mock
-    private lateinit var featureFlags: FeatureFlags
-    @Mock
     private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager
 
     companion object {
@@ -83,6 +83,8 @@
     private lateinit var action: ControlActionCoordinatorImpl.Action
     private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
 
+    private val featureFlags = FakeFeatureFlags()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -102,14 +104,12 @@
                 metricsLogger,
                 vibratorHelper,
                 controlsSettingsRepository,
-                controlsSettingsDialogManager,
                 featureFlags
         ))
         coordinator.activityContext = mContext
 
         `when`(cvh.cws.ci.controlId).thenReturn(ID)
         `when`(cvh.cws.control?.isAuthRequired()).thenReturn(true)
-        `when`(featureFlags.isEnabled(Flags.USE_APP_PANELS)).thenReturn(false)
 
         action = spy(coordinator.Action(ID, {}, false, true))
         doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
@@ -155,13 +155,11 @@
         coordinator.toggle(cvh, "", true)
 
         verify(coordinator).bouncerOrRun(action)
-        verify(controlsSettingsDialogManager).maybeShowDialog(any(), any())
         verify(action).invoke()
     }
 
     @Test
     fun testToggleWhenLockedDoesNotTriggerDialog_featureFlagEnabled() {
-        `when`(featureFlags.isEnabled(Flags.USE_APP_PANELS)).thenReturn(true)
         action = spy(coordinator.Action(ID, {}, false, false))
         doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
 
@@ -200,4 +198,50 @@
         verify(coordinator).bouncerOrRun(action)
         verify(action, never()).invoke()
     }
+
+    @Test
+    fun drag_isEdge_oneWayHapticsDisabled_usesVibrate() {
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
+
+        coordinator.drag(cvh, true)
+
+        verify(vibratorHelper).vibrate(
+            Vibrations.rangeEdgeEffect
+        )
+    }
+
+    @Test
+    fun drag_isNotEdge_oneWayHapticsDisabled_usesVibrate() {
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
+
+        coordinator.drag(cvh, false)
+
+        verify(vibratorHelper).vibrate(
+            Vibrations.rangeMiddleEffect
+        )
+    }
+
+    @Test
+    fun drag_isEdge_oneWayHapticsEnabled_usesPerformHapticFeedback() {
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+
+        coordinator.drag(cvh, true)
+
+        verify(vibratorHelper).performHapticFeedback(
+            any(),
+            eq(HapticFeedbackConstants.SEGMENT_TICK)
+        )
+    }
+
+    @Test
+    fun drag_isNotEdge_oneWayHapticsEnabled_usesPerformHapticFeedback() {
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+
+        coordinator.drag(cvh, false)
+
+        verify(vibratorHelper).performHapticFeedback(
+            any(),
+            eq(HapticFeedbackConstants.SEGMENT_FREQUENT_TICK)
+        )
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
index 26cbd77..724c9d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -116,11 +116,7 @@
 
     @Test
     fun testBindAndLoad_cancel() {
-        val callback = object : ControlsBindingController.LoadCallback {
-            override fun error(message: String) {}
-
-            override fun accept(t: List<Control>) {}
-        }
+        val callback = mock(ControlsBindingController.LoadCallback::class.java)
         val subscription = mock(IControlsSubscription::class.java)
 
         val canceller = controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
@@ -130,6 +126,7 @@
 
         canceller.run()
         verify(providers[0]).cancelSubscription(subscription)
+        verify(callback).error(any())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
index 4210675..bd3d09d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
@@ -11,13 +11,11 @@
 import android.window.OnBackInvokedDispatcher
 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.activity.SingleActivityFactory
 import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlsControllerImpl
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
@@ -46,7 +44,6 @@
     }
 
     private val uiExecutor = FakeExecutor(FakeSystemClock())
-    private val featureFlags = FakeFeatureFlags()
 
     @Mock lateinit var controller: ControlsControllerImpl
 
@@ -63,31 +60,23 @@
     @JvmField
     var activityRule =
         ActivityTestRule(
-            object :
-                SingleActivityFactory<TestableControlsEditingActivity>(
-                    TestableControlsEditingActivity::class.java
-                ) {
-                override fun create(intent: Intent?): TestableControlsEditingActivity {
-                    return TestableControlsEditingActivity(
-                        featureFlags,
-                        uiExecutor,
-                        controller,
-                        userTracker,
-                        customIconCache,
-                        mockDispatcher,
-                        latch
-                    )
-                }
+            /* activityFactory= */ SingleActivityFactory {
+                TestableControlsEditingActivity(
+                    uiExecutor,
+                    controller,
+                    userTracker,
+                    customIconCache,
+                    mockDispatcher,
+                    latch
+                )
             },
-            false,
-            false
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
         )
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false)
     }
 
     @Test
@@ -106,16 +95,7 @@
     }
 
     @Test
-    fun testNewFlowDisabled_addControlsButton_gone() {
-        with(launchActivity()) {
-            val addControlsButton = requireViewById<Button>(R.id.addControls)
-            assertThat(addControlsButton.visibility).isEqualTo(View.GONE)
-        }
-    }
-
-    @Test
-    fun testNewFlowEnabled_addControlsButton_visible() {
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+    fun testAddControlsButton_visible() {
         with(launchActivity()) {
             val addControlsButton = requireViewById<Button>(R.id.addControls)
             assertThat(addControlsButton.visibility).isEqualTo(View.VISIBLE)
@@ -125,7 +105,6 @@
 
     @Test
     fun testNotLaunchFromFavoriting_saveButton_disabled() {
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
         with(launchActivity(isFromFavoriting = false)) {
             val saveButton = requireViewById<Button>(R.id.done)
             assertThat(saveButton.isEnabled).isFalse()
@@ -134,7 +113,6 @@
 
     @Test
     fun testLaunchFromFavoriting_saveButton_enabled() {
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
         with(launchActivity(isFromFavoriting = true)) {
             val saveButton = requireViewById<Button>(R.id.done)
             assertThat(saveButton.isEnabled).isTrue()
@@ -143,7 +121,6 @@
 
     @Test
     fun testNotFromFavoriting_addControlsPressed_launchesFavouriting() {
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
         with(launchActivity(isFromFavoriting = false)) {
             val addControls = requireViewById<Button>(R.id.addControls)
 
@@ -182,7 +159,6 @@
         )
 
     class TestableControlsEditingActivity(
-        featureFlags: FakeFeatureFlags,
         executor: FakeExecutor,
         controller: ControlsControllerImpl,
         userTracker: UserTracker,
@@ -191,7 +167,6 @@
         private val latch: CountDownLatch
     ) :
         ControlsEditingActivity(
-            featureFlags,
             executor,
             controller,
             userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
index f4cc8bc..70d93a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -13,18 +13,15 @@
 import androidx.test.filters.FlakyTest
 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.activity.SingleActivityFactory
 import com.android.systemui.controls.ControlStatus
-import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.createLoadDataObject
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
@@ -73,8 +70,6 @@
 
     @Mock lateinit var controller: ControlsControllerImpl
 
-    @Mock lateinit var listingController: ControlsListingController
-
     @Mock lateinit var userTracker: UserTracker
 
     private var latch: CountDownLatch = CountDownLatch(1)
@@ -82,39 +77,28 @@
     @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
     @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback>
     @Captor
-    private lateinit var listingCallback:
-        ArgumentCaptor<ControlsListingController.ControlsListingCallback>
-    @Captor
     private lateinit var controlsCallback: ArgumentCaptor<Consumer<ControlsController.LoadData>>
 
     @Rule
     @JvmField
     var activityRule =
         ActivityTestRule(
-            object :
-                SingleActivityFactory<TestableControlsFavoritingActivity>(
-                    TestableControlsFavoritingActivity::class.java
-                ) {
-                override fun create(intent: Intent?): TestableControlsFavoritingActivity {
-                    return TestableControlsFavoritingActivity(
-                        featureFlags,
-                        executor,
-                        controller,
-                        listingController,
-                        userTracker,
-                        mockDispatcher,
-                        latch
-                    )
-                }
+            /* activityFactory= */ SingleActivityFactory {
+                TestableControlsFavoritingActivity(
+                    executor,
+                    controller,
+                    userTracker,
+                    mockDispatcher,
+                    latch
+                )
             },
-            false,
-            false
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
         )
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false)
     }
 
     // b/259549854 to root-cause and fix
@@ -135,14 +119,8 @@
     }
 
     @Test
-    fun testNewFlowEnabled_buttons() {
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+    fun testButtons() {
         with(launchActivity()) {
-            verify(listingController).addCallback(listingCallback.capture())
-            listingCallback.value.onServicesUpdated(
-                listOf(mock(ControlsServiceInfo::class.java), mock(ControlsServiceInfo::class.java))
-            )
-
             val rearrangeButton = requireViewById<Button>(R.id.rearrange)
             assertThat(rearrangeButton.visibility).isEqualTo(View.VISIBLE)
             assertThat(rearrangeButton.isEnabled).isFalse()
@@ -154,36 +132,8 @@
     }
 
     @Test
-    fun testNewFlowDisabled_buttons() {
+    fun testRearrangePressed_savesAndlaunchesActivity() {
         with(launchActivity()) {
-            verify(listingController).addCallback(listingCallback.capture())
-            activityRule.runOnUiThread {
-                listingCallback.value.onServicesUpdated(
-                    listOf(
-                        mock(ControlsServiceInfo::class.java),
-                        mock(ControlsServiceInfo::class.java)
-                    )
-                )
-            }
-
-            val rearrangeButton = requireViewById<Button>(R.id.rearrange)
-            assertThat(rearrangeButton.visibility).isEqualTo(View.GONE)
-            assertThat(rearrangeButton.isEnabled).isFalse()
-
-            val otherAppsButton = requireViewById<Button>(R.id.other_apps)
-            otherAppsButton.waitForPost()
-            assertThat(otherAppsButton.visibility).isEqualTo(View.VISIBLE)
-        }
-    }
-
-    @Test
-    fun testNewFlowEnabled_rearrangePressed_savesAndlaunchesActivity() {
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
-        with(launchActivity()) {
-            verify(listingController).addCallback(capture(listingCallback))
-            listingCallback.value.onServicesUpdated(
-                listOf(mock(ControlsServiceInfo::class.java), mock(ControlsServiceInfo::class.java))
-            )
             verify(controller).loadForComponent(any(), capture(controlsCallback), any())
             activityRule.runOnUiThread {
                 controlsCallback.value.accept(
@@ -229,19 +179,15 @@
         )
 
     class TestableControlsFavoritingActivity(
-        featureFlags: FeatureFlags,
         executor: Executor,
         controller: ControlsControllerImpl,
-        listingController: ControlsListingController,
         userTracker: UserTracker,
         private val mockDispatcher: OnBackInvokedDispatcher,
         private val latch: CountDownLatch
     ) :
         ControlsFavoritingActivity(
-            featureFlags,
             executor,
             controller,
-            listingController,
             userTracker,
         ) {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index ee213f7..b1061ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -37,7 +37,6 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.APP_PANELS_ALL_APPS_ALLOWED
-import com.android.systemui.flags.Flags.USE_APP_PANELS
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.ActivityTaskManagerProxy
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -124,8 +123,6 @@
                         arrayOf(componentName.packageName)
                 )
 
-        // Return true by default, we'll test the false path
-        `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(true)
         // Return false by default, we'll test the true path
         `when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(false)
 
@@ -445,34 +442,6 @@
     }
 
     @Test
-    fun testActivityDefaultEnabled_flagDisabled_nullPanel() {
-        `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(false)
-        val serviceInfo = ServiceInfo(
-                componentName,
-                activityName,
-        )
-
-        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
-                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
-
-        setUpQueryResult(listOf(
-                ActivityInfo(
-                        activityName,
-                        enabled = true,
-                        exported = true,
-                        permission = Manifest.permission.BIND_CONTROLS
-                )
-        ))
-
-        val list = listOf(serviceInfo)
-        serviceListingCallbackCaptor.value.onServicesReloaded(list)
-
-        executor.runAllReady()
-
-        assertNull(controller.getCurrentServices()[0].panelActivity)
-    }
-
-    @Test
     fun testActivityDifferentPackage_nullPanel() {
         val serviceInfo = ServiceInfo(
                 componentName,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
index 4ba6718..d17495f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
@@ -29,8 +29,8 @@
 import android.window.OnBackInvokedDispatcher
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
@@ -91,26 +91,21 @@
     @JvmField
     var activityRule =
         ActivityTestRule(
-            object :
-                SingleActivityFactory<TestableControlsProviderSelectorActivity>(
-                    TestableControlsProviderSelectorActivity::class.java
-                ) {
-                override fun create(intent: Intent?): TestableControlsProviderSelectorActivity {
-                    return TestableControlsProviderSelectorActivity(
-                        executor,
-                        backExecutor,
-                        listingController,
-                        controlsController,
-                        userTracker,
-                        authorizedPanelsRepository,
-                        dialogFactory,
-                        mockDispatcher,
-                        latch
-                    )
-                }
+            /* activityFactory= */ SingleActivityFactory {
+                TestableControlsProviderSelectorActivity(
+                    executor,
+                    backExecutor,
+                    listingController,
+                    controlsController,
+                    userTracker,
+                    authorizedPanelsRepository,
+                    dialogFactory,
+                    mockDispatcher,
+                    latch
+                )
             },
-            false,
-            false
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
         )
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
index 314b176..ca970bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
@@ -30,8 +30,8 @@
 import androidx.lifecycle.Lifecycle
 import androidx.test.filters.MediumTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.controls.controller.ControlInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.settings.UserTracker
@@ -81,19 +81,18 @@
 
     @Rule
     @JvmField
-    var activityRule = ActivityTestRule<TestControlsRequestDialog>(
-            object : SingleActivityFactory<TestControlsRequestDialog>(
-                    TestControlsRequestDialog::class.java
-            ) {
-                    override fun create(intent: Intent?): TestControlsRequestDialog {
-                        return TestControlsRequestDialog(
-                                mainExecutor,
-                                controller,
-                                userTracker,
-                                listingController
-                        )
-                    }
-            }, false, false)
+    var activityRule = ActivityTestRule(
+        /* activityFactory= */ SingleActivityFactory {
+            TestControlsRequestDialog(
+                    mainExecutor,
+                    controller,
+                    userTracker,
+                    listingController
+            )
+        },
+        /* initialTouchMode= */ false,
+        /* launchActivity= */ false,
+    )
 
     private lateinit var control: Control
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
index 272f589..7ac1953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
@@ -22,8 +22,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.FakeSharedPreferences
@@ -42,8 +40,6 @@
 
     @Mock private lateinit var userTracker: UserTracker
 
-    private val featureFlags = FakeFeatureFlags()
-
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -52,7 +48,6 @@
             arrayOf<String>()
         )
         whenever(userTracker.userId).thenReturn(0)
-        featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
     }
 
     @Test
@@ -132,25 +127,8 @@
         assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
     }
 
-    @Test
-    fun testSetAuthorizedPackageAfterFeatureDisabled() {
-        mContext.orCreateTestableResources.addOverride(
-            R.array.config_controlsPreferredPackages,
-            arrayOf(TEST_PACKAGE)
-        )
-        val sharedPrefs = FakeSharedPreferences()
-        val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
-        val repository = createRepository(fileManager)
-
-        repository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
-
-        featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
-
-        assertThat(repository.getAuthorizedPanels()).isEqualTo(setOf(TEST_PACKAGE))
-    }
-
     private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
-        return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker, featureFlags)
+        return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
     }
 
     private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
index 0c7b9cb..6230ea7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
@@ -22,7 +22,6 @@
 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.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
@@ -103,36 +102,18 @@
     }
 
     @Test
-    fun testFeatureEnabled_shouldAddDefaultPanelDefaultsToTrue() {
-        featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
-
+    fun testShouldAddDefaultPanelDefaultsToTrue() {
         assertThat(repository.shouldAddDefaultComponent()).isTrue()
     }
 
     @Test
-    fun testFeatureDisabled_shouldAddDefaultPanelDefaultsToTrue() {
-        featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
-
-        assertThat(repository.shouldAddDefaultComponent()).isTrue()
-    }
-
-    @Test
-    fun testFeatureEnabled_shouldAddDefaultPanelChecked() {
-        featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
+    fun testShouldAddDefaultPanelChecked() {
         repository.setShouldAddDefaultComponent(false)
 
         assertThat(repository.shouldAddDefaultComponent()).isFalse()
     }
 
     @Test
-    fun testFeatureDisabled_shouldAlwaysAddDefaultPanelAlwaysTrue() {
-        featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
-        repository.setShouldAddDefaultComponent(false)
-
-        assertThat(repository.shouldAddDefaultComponent()).isTrue()
-    }
-
-    @Test
     fun testGetPreferredStructure_differentUserId() {
         sharedPreferences.savePanel(COMPONENT_A)
         whenever(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
index 42f28c8..2ae342a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
@@ -125,7 +125,7 @@
             control
         )
         cvh.bindData(cws, false)
-        val chevronIcon = baseLayout.findViewById<View>(R.id.chevron_icon)
+        val chevronIcon = baseLayout.requireViewById<View>(R.id.chevron_icon)
 
         assertThat(chevronIcon.visibility).isEqualTo(View.VISIBLE)
     }
@@ -138,4 +138,4 @@
 private val DRAWABLE = GradientDrawable()
 private val COLOR = ColorStateList.valueOf(0xffff00)
 private val DEFAULT_CONTROL = Control.StatelessBuilder(
-        CONTROL_ID, mock(PendingIntent::class.java)).build()
\ No newline at end of file
+        CONTROL_ID, mock(PendingIntent::class.java)).build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt
index 2d3e10e..e279d28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsActivityTest.kt
@@ -23,8 +23,8 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.settings.ControlsSettingsDialogManager
 import com.android.systemui.flags.FeatureFlags
@@ -53,23 +53,18 @@
     @JvmField
     var activityRule =
         ActivityTestRule(
-            object :
-                SingleActivityFactory<TestableControlsActivity>(
-                    TestableControlsActivity::class.java
-                ) {
-                override fun create(intent: Intent?): TestableControlsActivity {
-                    return TestableControlsActivity(
-                        uiController,
-                        broadcastDispatcher,
-                        dreamManager,
-                        featureFlags,
-                        controlsSettingsDialogManager,
-                        keyguardStateController,
-                    )
-                }
+            /* activityFactory= */ SingleActivityFactory {
+                TestableControlsActivity(
+                    uiController,
+                    broadcastDispatcher,
+                    dreamManager,
+                    featureFlags,
+                    controlsSettingsDialogManager,
+                    keyguardStateController,
+                )
             },
-            false,
-            false
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
         )
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index fcd6568..a400ff9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -365,7 +365,8 @@
         val selectedItems =
             listOf(
                 SelectedItem.StructureItem(
-                    StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
+                    StructureInfo(checkNotNull(ComponentName.unflattenFromString("pkg/.cls1")),
+                        "a", ArrayList())
                 ),
             )
         preferredPanelRepository.setSelectedComponent(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
index fcc3589..8f0b193 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
@@ -39,7 +39,7 @@
 
     @Before
     fun setUp() {
-        roundedCornerResDelegate = spy(RoundedCornerResDelegate(mContext.resources, null))
+        roundedCornerResDelegate = spy(RoundedCornerResDelegateImpl(mContext.resources, null))
     }
 
     @Test
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 93a1868..4feba7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
@@ -48,7 +48,7 @@
     @Test
     fun testTopAndBottomRoundedCornerExist() {
         setupResources(radius = 5)
-        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
+        roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null)
         assertEquals(true, roundedCornerResDelegate.hasTop)
         assertEquals(true, roundedCornerResDelegate.hasBottom)
     }
@@ -56,7 +56,7 @@
     @Test
     fun testTopRoundedCornerExist() {
         setupResources(radiusTop = 10)
-        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
+        roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null)
         assertEquals(true, roundedCornerResDelegate.hasTop)
         assertEquals(false, roundedCornerResDelegate.hasBottom)
     }
@@ -64,7 +64,7 @@
     @Test
     fun testBottomRoundedCornerExist() {
         setupResources(radiusBottom = 15)
-        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
+        roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null)
         assertEquals(false, roundedCornerResDelegate.hasTop)
         assertEquals(true, roundedCornerResDelegate.hasBottom)
     }
@@ -75,7 +75,7 @@
                 roundedTopDrawable = getTestsDrawable(R.drawable.rounded3px),
                 roundedBottomDrawable = getTestsDrawable(R.drawable.rounded4px))
 
-        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
+        roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null)
 
         assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)
         assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
@@ -96,7 +96,7 @@
                 roundedTopDrawable = getTestsDrawable(R.drawable.rounded3px),
                 roundedBottomDrawable = getTestsDrawable(R.drawable.rounded4px))
 
-        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
+        roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null)
 
         assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)
         assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
@@ -109,33 +109,12 @@
     }
 
     @Test
-    fun testUpdateTuningSizeFactor() {
-        setupResources(radius = 100,
-                roundedTopDrawable = getTestsDrawable(R.drawable.rounded3px),
-                roundedBottomDrawable = getTestsDrawable(R.drawable.rounded4px))
-
-        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
-
-        val factor = 5
-        roundedCornerResDelegate.tuningSizeFactor = factor
-        val length = (factor * mContext.resources.displayMetrics.density).toInt()
-
-        assertEquals(Size(length, length), roundedCornerResDelegate.topRoundedSize)
-        assertEquals(Size(length, length), roundedCornerResDelegate.bottomRoundedSize)
-
-        roundedCornerResDelegate.tuningSizeFactor = null
-
-        assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)
-        assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
-    }
-
-    @Test
     fun testPhysicalPixelDisplaySizeChanged() {
         setupResources(
                 roundedTopDrawable = getTestsDrawable(R.drawable.rounded4px),
                 roundedBottomDrawable = getTestsDrawable(R.drawable.rounded4px))
 
-        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
+        roundedCornerResDelegate = RoundedCornerResDelegateImpl(mContext.resources, null)
         assertEquals(Size(4, 4), roundedCornerResDelegate.topRoundedSize)
         assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
index 75eec72..68ea7eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
@@ -94,7 +94,7 @@
         intent.putExtra("command", command)
         args.forEach { arg -> intent.putExtra(arg.key, arg.value) }
 
-        fakeBroadcastDispatcher.registeredReceivers.forEach { it.onReceive(context, intent) }
+        fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt
new file mode 100644
index 0000000..dd741b4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayMetricsRepositoryTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import android.content.Context
+import android.util.DisplayMetrics
+import android.view.Display
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class DisplayMetricsRepositoryTest : SysuiTestCase() {
+    private lateinit var underTest: DisplayMetricsRepository
+
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val configurationController = FakeConfigurationController()
+
+    private val displayMetrics =
+        DisplayMetrics().apply { this.heightPixels = INITIAL_HEIGHT_PIXELS }
+    private val mockContext: Context = mock()
+    private val mockDisplay: Display = mock()
+
+    @Before
+    fun setUp() {
+        underTest =
+            DisplayMetricsRepository(
+                testScope.backgroundScope,
+                configurationController,
+                displayMetrics,
+                mockContext,
+                LogBuffer("TestBuffer", maxSize = 10, logcatEchoTracker = mock())
+            )
+        whenever(mockContext.display).thenReturn(mockDisplay)
+    }
+
+    @Test
+    fun heightPixels_getsInitialValue() {
+        assertThat(underTest.heightPixels).isEqualTo(INITIAL_HEIGHT_PIXELS)
+    }
+
+    @Test
+    fun heightPixels_configChanged_heightUpdated() =
+        testScope.runTest {
+            runCurrent()
+
+            updateDisplayMetrics(456)
+            configurationController.notifyConfigurationChanged()
+            runCurrent()
+
+            assertThat(underTest.heightPixels).isEqualTo(456)
+
+            updateDisplayMetrics(23)
+            configurationController.notifyConfigurationChanged()
+            runCurrent()
+
+            assertThat(underTest.heightPixels).isEqualTo(23)
+        }
+
+    private fun updateDisplayMetrics(newHeight: Int) {
+        whenever(mockDisplay.getMetrics(displayMetrics)).thenAnswer {
+            it.getArgument<DisplayMetrics>(0).heightPixels = newHeight
+            Unit
+        }
+    }
+
+    private companion object {
+        const val INITIAL_HEIGHT_PIXELS = 345
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
new file mode 100644
index 0000000..9be54fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.data.repository
+
+import android.hardware.display.DisplayManager
+import android.os.Looper
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Display
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.FlowValue
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class DisplayRepositoryTest : SysuiTestCase() {
+
+    private val displayManager = mock<DisplayManager>()
+    private val displayListener = kotlinArgumentCaptor<DisplayManager.DisplayListener>()
+
+    private val testHandler = FakeHandler(Looper.getMainLooper())
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+
+    private lateinit var displayRepository: DisplayRepositoryImpl
+
+    @Before
+    fun setup() {
+        setDisplays(emptyList())
+        displayRepository =
+            DisplayRepositoryImpl(
+                displayManager,
+                testHandler,
+                TestScope(UnconfinedTestDispatcher()),
+                UnconfinedTestDispatcher()
+            )
+        verify(displayManager, never()).registerDisplayListener(any(), any())
+    }
+
+    @Test
+    fun onFlowCollection_displayListenerRegistered() =
+        testScope.runTest {
+            val value by latestDisplayFlowValue()
+
+            assertThat(value).isEmpty()
+
+            verify(displayManager).registerDisplayListener(any(), eq(testHandler), anyLong())
+        }
+
+    @Test
+    fun afterFlowCollection_displayListenerUnregistered() {
+        testScope.runTest {
+            val value by latestDisplayFlowValue()
+
+            assertThat(value).isEmpty()
+
+            verify(displayManager).registerDisplayListener(any(), eq(testHandler), anyLong())
+        }
+        verify(displayManager).unregisterDisplayListener(any())
+    }
+
+    @Test
+    fun afterFlowCollection_multipleSusbcriptions_oneRemoved_displayListenerNotUnregistered() {
+        testScope.runTest {
+            val firstSubscriber by latestDisplayFlowValue()
+
+            assertThat(firstSubscriber).isEmpty()
+            verify(displayManager, times(1))
+                .registerDisplayListener(displayListener.capture(), eq(testHandler), anyLong())
+
+            val innerScope = TestScope()
+            innerScope.runTest {
+                val secondSubscriber by latestDisplayFlowValue()
+                assertThat(secondSubscriber).isEmpty()
+
+                // No new registration, just the precedent one.
+                verify(displayManager, times(1))
+                    .registerDisplayListener(any(), eq(testHandler), anyLong())
+            }
+
+            // Let's make sure it has *NOT* been unregistered, as there is still a subscriber.
+            setDisplays(1)
+            displayListener.value.onDisplayAdded(1)
+            assertThat(firstSubscriber?.ids()).containsExactly(1)
+        }
+
+        // All subscribers are done, unregister should have been called.
+        verify(displayManager).unregisterDisplayListener(any())
+    }
+    @Test
+    fun onDisplayAdded_propagated() =
+        testScope.runTest {
+            val value by latestDisplayFlowValue()
+
+            setDisplays(1)
+            displayListener.value.onDisplayAdded(1)
+
+            assertThat(value?.ids()).containsExactly(1)
+        }
+
+    @Test
+    fun onDisplayRemoved_propagated() =
+        testScope.runTest {
+            val value by latestDisplayFlowValue()
+
+            setDisplays(1, 2, 3, 4)
+            displayListener.value.onDisplayAdded(1)
+            displayListener.value.onDisplayAdded(2)
+            displayListener.value.onDisplayAdded(3)
+            displayListener.value.onDisplayAdded(4)
+
+            setDisplays(1, 2, 3)
+            displayListener.value.onDisplayRemoved(4)
+
+            assertThat(value?.ids()).containsExactly(1, 2, 3)
+        }
+
+    @Test
+    fun onDisplayChanged_propagated() =
+        testScope.runTest {
+            val value by latestDisplayFlowValue()
+
+            setDisplays(1, 2, 3, 4)
+            displayListener.value.onDisplayAdded(1)
+            displayListener.value.onDisplayAdded(2)
+            displayListener.value.onDisplayAdded(3)
+            displayListener.value.onDisplayAdded(4)
+
+            displayListener.value.onDisplayChanged(4)
+
+            assertThat(value?.ids()).containsExactly(1, 2, 3, 4)
+        }
+
+    private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
+
+    // Wrapper to capture the displayListener.
+    private fun TestScope.latestDisplayFlowValue(): FlowValue<Set<Display>?> {
+        val flowValue = collectLastValue(displayRepository.displays)
+        verify(displayManager)
+            .registerDisplayListener(displayListener.capture(), eq(testHandler), anyLong())
+        return flowValue
+    }
+
+    private fun setDisplays(displays: List<Display>) {
+        whenever(displayManager.displays).thenReturn(displays.toTypedArray())
+    }
+
+    private fun setDisplays(vararg ids: Int) {
+        setDisplays(ids.map { display(it) })
+    }
+
+    private fun display(id: Int): Display {
+        return mock<Display>().also { mockDisplay ->
+            whenever(mockDisplay.displayId).thenReturn(id)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
new file mode 100644
index 0000000..1b597f4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Display
+import android.view.Display.TYPE_EXTERNAL
+import android.view.Display.TYPE_INTERNAL
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.FlowValue
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class ConnectedDisplayInteractorTest : SysuiTestCase() {
+
+    private val fakeDisplayRepository = FakeDisplayRepository()
+    private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
+        ConnectedDisplayInteractorImpl(fakeDisplayRepository)
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+
+    @Test
+    fun displayState_nullDisplays_disconnected() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(emptySet())
+
+            assertThat(value).isEqualTo(State.DISCONNECTED)
+        }
+
+    @Test
+    fun displayState_emptyDisplays_disconnected() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(emptySet())
+
+            assertThat(value).isEqualTo(State.DISCONNECTED)
+        }
+
+    @Test
+    fun displayState_internalDisplay_disconnected() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(setOf(display(type = TYPE_INTERNAL)))
+
+            assertThat(value).isEqualTo(State.DISCONNECTED)
+        }
+
+    @Test
+    fun displayState_externalDisplay_connected() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(setOf(display(type = TYPE_EXTERNAL)))
+
+            assertThat(value).isEqualTo(State.CONNECTED)
+        }
+
+    @Test
+    fun displayState_multipleExternalDisplays_connected() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(
+                setOf(display(type = TYPE_EXTERNAL), display(type = TYPE_EXTERNAL))
+            )
+
+            assertThat(value).isEqualTo(State.CONNECTED)
+        }
+
+    @Test
+    fun displayState_externalSecure_connectedSecure() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(
+                setOf(display(type = TYPE_EXTERNAL, flags = Display.FLAG_SECURE))
+            )
+
+            assertThat(value).isEqualTo(State.CONNECTED_SECURE)
+        }
+
+    @Test
+    fun displayState_multipleExternal_onlyOneSecure_connectedSecure() =
+        testScope.runTest {
+            val value by lastValue()
+
+            fakeDisplayRepository.emit(
+                setOf(
+                    display(type = TYPE_EXTERNAL, flags = Display.FLAG_SECURE),
+                    display(type = TYPE_EXTERNAL, flags = 0)
+                )
+            )
+
+            assertThat(value).isEqualTo(State.CONNECTED_SECURE)
+        }
+
+    private fun TestScope.lastValue(): FlowValue<State?> =
+        collectLastValue(connectedDisplayStateProvider.connectedDisplayState)
+
+    private fun display(type: Int, flags: Int = 0): Display {
+        return mock<Display>().also { mockDisplay ->
+            whenever(mockDisplay.type).thenReturn(type)
+            whenever(mockDisplay.flags).thenReturn(flags)
+        }
+    }
+
+    private class FakeDisplayRepository : DisplayRepository {
+        private val flow = MutableSharedFlow<Set<Display>>()
+        suspend fun emit(value: Set<Display>) = flow.emit(value)
+        override val displays: Flow<Set<Display>>
+            get() = flow
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
index 49cdfa7..7311f4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -33,15 +33,14 @@
 import android.app.AlarmManager;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.DejankUtils;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.wakelock.WakeLockFake;
 
 import org.junit.After;
@@ -53,6 +52,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@TestableLooper.RunWithLooper
 public class DozeUiTest extends SysuiTestCase {
 
     @Mock
@@ -62,23 +62,19 @@
     @Mock
     private DozeParameters mDozeParameters;
     @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
     private DozeHost mHost;
     @Mock
     private DozeLog mDozeLog;
-    @Mock
-    private TunerService mTunerService;
     private WakeLockFake mWakeLock;
     private Handler mHandler;
     private HandlerThread mHandlerThread;
     private DozeUi mDozeUi;
-    @Mock
-    private StatusBarStateController mStatusBarStateController;
 
     @Before
     public void setUp() throws Exception {
+        allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
+        DejankUtils.setImmediate(true);
 
         mHandlerThread = new HandlerThread("DozeUiTest");
         mHandlerThread.start();
@@ -86,12 +82,13 @@
         mHandler = mHandlerThread.getThreadHandler();
 
         mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
-                mDozeParameters, mStatusBarStateController, mDozeLog);
+                mDozeParameters, mDozeLog);
         mDozeUi.setDozeMachine(mMachine);
     }
 
     @After
     public void tearDown() throws Exception {
+        DejankUtils.setImmediate(false);
         mHandlerThread.quit();
         mHandler = null;
         mHandlerThread = null;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index a00e545..57307fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -9,6 +9,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.complication.ComplicationHostViewController
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.statusbar.BlurUtils
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.mockito.argumentCaptor
@@ -47,7 +48,7 @@
     @Mock private lateinit var stateController: DreamOverlayStateController
     @Mock private lateinit var configController: ConfigurationController
     @Mock private lateinit var transitionViewModel: DreamingToLockscreenTransitionViewModel
-    @Mock private lateinit var logger: DreamLogger
+    private val logBuffer = FakeLogBuffer.Factory.create()
     private lateinit var controller: DreamOverlayAnimationsController
 
     @Before
@@ -66,7 +67,7 @@
                 DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
                 DREAM_IN_TRANSLATION_Y_DISTANCE,
                 DREAM_IN_TRANSLATION_Y_DURATION,
-                logger
+                logBuffer
             )
 
         val mockView: View = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 47b7d49..8786520 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -41,8 +41,8 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.complication.ComplicationHostViewController;
 import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
 import com.android.systemui.statusbar.BlurUtils;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index d0d348d..d99f0da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.dreams;
 
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -507,4 +509,23 @@
         mService.onWakeUp();
         verify(mDreamOverlayContainerViewController, never()).wakeUp();
     }
+
+    @Test
+    public void testSystemFlagShowForAllUsersSetOnWindow() throws RemoteException {
+        final IDreamOverlayClient client = getClient();
+
+        // Inform the overlay service of dream starting. Do not show dream complications.
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+                false /*shouldShowComplication*/);
+        mMainExecutor.runAllReady();
+
+        final ArgumentCaptor<WindowManager.LayoutParams> paramsCaptor =
+                ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
+
+        // Verify that a new window is added.
+        verify(mWindowManager).addView(any(), paramsCaptor.capture());
+
+        assertThat((paramsCaptor.getValue().privateFlags & SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+                == SYSTEM_FLAG_SHOW_FOR_ALL_USERS).isTrue();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 7b41605..44a78ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -34,6 +34,8 @@
 import com.android.systemui.complication.Complication;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.FakeLogBuffer;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -58,6 +60,8 @@
     @Mock
     private FeatureFlags mFeatureFlags;
 
+    private final LogBuffer mLogBuffer = FakeLogBuffer.Factory.Companion.create();
+
     final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
     @Before
@@ -405,6 +409,11 @@
     }
 
     private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) {
-        return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags);
+        return new DreamOverlayStateController(
+                mExecutor,
+                overlayEnabled,
+                mFeatureFlags,
+                mLogBuffer
+        );
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index d16b757..4e74f451 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -48,6 +48,8 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.FakeLogBuffer;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.NextAlarmController;
@@ -114,6 +116,8 @@
     @Mock
     UserTracker mUserTracker;
 
+    LogBuffer mLogBuffer = FakeLogBuffer.Factory.Companion.create();
+
     @Captor
     private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
 
@@ -146,7 +150,8 @@
                 mStatusBarWindowStateController,
                 mDreamOverlayStatusBarItemsProvider,
                 mDreamOverlayStateController,
-                mUserTracker);
+                mUserTracker,
+                mLogBuffer);
     }
 
     @Test
@@ -289,7 +294,8 @@
                 mStatusBarWindowStateController,
                 mDreamOverlayStatusBarItemsProvider,
                 mDreamOverlayStateController,
-                mUserTracker);
+                mUserTracker,
+                mLogBuffer);
         controller.onViewAttached();
         verify(mView, never()).showIcon(
                 eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
index 07cb5d8..6a17889 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/AssistantAttentionConditionTest.java
@@ -22,17 +22,14 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
-import android.os.RemoteException;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.app.AssistUtils;
-import com.android.internal.app.IVisualQueryDetectionAttentionListener;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.assist.AssistManager.VisualQueryAttentionListener;
 import com.android.systemui.shared.condition.Condition;
 
 import org.junit.Before;
@@ -50,9 +47,7 @@
     @Mock
     Condition.Callback mCallback;
     @Mock
-    AssistUtils mAssistUtils;
-    @Mock
-    DreamOverlayStateController mDreamOverlayStateController;
+    AssistManager mAssistManager;
     @Mock
     CoroutineScope mScope;
 
@@ -62,55 +57,34 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        mAssistantAttentionCondition =
-                new AssistantAttentionCondition(mScope, mDreamOverlayStateController, mAssistUtils);
+        mAssistantAttentionCondition = new AssistantAttentionCondition(mScope, mAssistManager);
         // Adding a callback also starts the condition.
         mAssistantAttentionCondition.addCallback(mCallback);
     }
 
     @Test
     public void testEnableVisualQueryDetection() {
-        final ArgumentCaptor<DreamOverlayStateController.Callback> argumentCaptor =
-                ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
-        verify(mDreamOverlayStateController).addCallback(argumentCaptor.capture());
-
-        when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
-        argumentCaptor.getValue().onStateChanged();
-
-        verify(mAssistUtils).enableVisualQueryDetection(any());
+        verify(mAssistManager).addVisualQueryAttentionListener(
+                any(VisualQueryAttentionListener.class));
     }
 
     @Test
     public void testDisableVisualQueryDetection() {
-        final ArgumentCaptor<DreamOverlayStateController.Callback> argumentCaptor =
-                ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
-        verify(mDreamOverlayStateController).addCallback(argumentCaptor.capture());
-
-        when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
-        argumentCaptor.getValue().onStateChanged();
-        when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(false);
-        argumentCaptor.getValue().onStateChanged();
-
-        verify(mAssistUtils).disableVisualQueryDetection();
+        mAssistantAttentionCondition.stop();
+        verify(mAssistManager).removeVisualQueryAttentionListener(
+                any(VisualQueryAttentionListener.class));
     }
 
     @Test
-    public void testAttentionChangedTriggersCondition() throws RemoteException {
-        final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
-                ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
-        verify(mDreamOverlayStateController).addCallback(callbackCaptor.capture());
+    public void testAttentionChangedTriggersCondition() {
+        final ArgumentCaptor<VisualQueryAttentionListener> argumentCaptor =
+                ArgumentCaptor.forClass(VisualQueryAttentionListener.class);
+        verify(mAssistManager).addVisualQueryAttentionListener(argumentCaptor.capture());
 
-        when(mDreamOverlayStateController.isDreamOverlayStatusBarVisible()).thenReturn(true);
-        callbackCaptor.getValue().onStateChanged();
-
-        final ArgumentCaptor<IVisualQueryDetectionAttentionListener> listenerCaptor =
-                ArgumentCaptor.forClass(IVisualQueryDetectionAttentionListener.class);
-        verify(mAssistUtils).enableVisualQueryDetection(listenerCaptor.capture());
-
-        listenerCaptor.getValue().onAttentionGained();
+        argumentCaptor.getValue().onAttentionGained();
         assertThat(mAssistantAttentionCondition.isConditionMet()).isTrue();
 
-        listenerCaptor.getValue().onAttentionLost();
+        argumentCaptor.getValue().onAttentionLost();
         assertThat(mAssistantAttentionCondition.isConditionMet()).isFalse();
 
         verify(mCallback, times(2)).onConditionChanged(eq(mAssistantAttentionCondition));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 1a89076..3f9b198 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -28,6 +28,7 @@
 
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.content.pm.UserInfo;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.testing.AndroidTestingRunner;
@@ -39,10 +40,12 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dreams.touch.scrim.ScrimController;
 import com.android.systemui.dreams.touch.scrim.ScrimManager;
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.settings.FakeUserTracker;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -57,6 +60,7 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Collections;
 import java.util.Optional;
 
 @SmallTest
@@ -100,21 +104,34 @@
     @Mock
     UiEventLogger mUiEventLogger;
 
+    @Mock
+    LockPatternUtils mLockPatternUtils;
+
+    FakeUserTracker mUserTracker;
+
     private static final float TOUCH_REGION = .3f;
     private static final int SCREEN_WIDTH_PX = 1024;
     private static final int SCREEN_HEIGHT_PX = 100;
 
     private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
+    private static final UserInfo CURRENT_USER_INFO = new UserInfo(
+            10,
+            /* name= */ "user10",
+            /* flags= */ 0
+    );
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mUserTracker = new FakeUserTracker();
         mTouchHandler = new BouncerSwipeTouchHandler(
                 mScrimManager,
                 Optional.of(mCentralSurfaces),
                 mNotificationShadeWindowController,
                 mValueAnimatorCreator,
                 mVelocityTrackerFactory,
+                mLockPatternUtils,
+                mUserTracker,
                 mFlingAnimationUtils,
                 mFlingAnimationUtilsClosing,
                 TOUCH_REGION,
@@ -126,6 +143,9 @@
         when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
         when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
         when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
+        when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(true);
+
+        mUserTracker.set(Collections.singletonList(CURRENT_USER_INFO), 0);
     }
 
     /**
@@ -265,6 +285,32 @@
         verifyScroll(.7f, Direction.DOWN, true, gestureListener);
     }
 
+    /**
+     * Makes sure the expansion amount is proportional to (1 - scroll).
+     */
+    @Test
+    public void testSwipeUp_keyguardNotSecure_doesNotExpand() {
+        when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(false);
+        mTouchHandler.onSessionStart(mTouchSession);
+        ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+        verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+        final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
+
+        final float distanceY = SCREEN_HEIGHT_PX * 0.3f;
+        final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+                0, SCREEN_HEIGHT_PX, 0);
+        final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
+                0, SCREEN_HEIGHT_PX - distanceY, 0);
+
+        reset(mScrimController);
+        assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
+                .isTrue();
+        // We should not expand since the keyguard is not secure
+        verify(mScrimController, never()).expand(any());
+    }
+
     private void verifyScroll(float percent, Direction direction,
             boolean isBouncerInitiallyShowing, GestureDetector.OnGestureListener gestureListener) {
         final float distanceY = SCREEN_HEIGHT_PX * percent;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
index 872c079..2b98214 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
@@ -61,10 +61,8 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces),
+        mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces), mShadeViewController,
                 TOUCH_HEIGHT);
-        when(mCentralSurfaces.getShadeViewController())
-                .thenReturn(mShadeViewController);
     }
 
     /**
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 e8cbdf3..840eb46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -22,14 +22,10 @@
 import com.android.systemui.ProtoDumpable
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
+import com.android.systemui.log.table.TableLogBuffer
 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
@@ -37,6 +33,10 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.io.StringWriter
+import javax.inject.Provider
 
 @SmallTest
 class DumpHandlerTest : SysuiTestCase() {
@@ -45,8 +45,6 @@
 
     @Mock
     private lateinit var logBufferEulogizer: LogBufferEulogizer
-    @Mock
-    private lateinit var exceptionHandlerManager: UncaughtExceptionPreHandlerManager
 
     @Mock
     private lateinit var pw: PrintWriter
@@ -70,34 +68,38 @@
     @Mock
     private lateinit var buffer2: LogBuffer
 
+    @Mock
+    private lateinit var table1: TableLogBuffer
+    @Mock
+    private lateinit var table2: TableLogBuffer
+
     private val dumpManager = DumpManager()
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        dumpHandler = DumpHandler(
-            mContext,
-            dumpManager,
-            logBufferEulogizer,
-            mutableMapOf(
-                EmptyCoreStartable::class.java to Provider { EmptyCoreStartable() }
-            ),
-            exceptionHandlerManager
+        val config = SystemUIConfigDumpable(
+                dumpManager,
+                mContext,
+                mutableMapOf(EmptyCoreStartable::class.java to Provider { EmptyCoreStartable() }),
         )
+        dumpHandler = DumpHandler(dumpManager, logBufferEulogizer, config)
     }
 
     @Test
     fun testDumpablesCanBeDumpedSelectively() {
         // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerDumpable("dumpable1", dumpable1)
-        dumpManager.registerDumpable("dumpable2", dumpable2)
-        dumpManager.registerDumpable("dumpable3", dumpable3)
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
         dumpManager.registerBuffer("buffer1", buffer1)
         dumpManager.registerBuffer("buffer2", buffer2)
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
 
         // WHEN some of them are dumped explicitly
-        val args = arrayOf("dumpable1", "dumpable3", "buffer2")
+        val args = arrayOf("dumpable1", "dumpable3", "buffer2", "table2")
         dumpHandler.dump(fd, pw, args)
 
         // THEN only the requested ones have their dump() method called
@@ -108,12 +110,14 @@
         verify(dumpable3).dump(pw, args)
         verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
         verify(buffer2).dump(pw, 0)
+        verify(table1, never()).dump(any(), any())
+        verify(table2).dump(pw, args)
     }
 
     @Test
     fun testDumpableMatchingIsBasedOnEndOfTag() {
         // GIVEN a dumpable registered to the manager
-        dumpManager.registerDumpable("com.android.foo.bar.dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("com.android.foo.bar.dumpable1", dumpable1)
 
         // WHEN that module is dumped
         val args = arrayOf("dumpable1")
@@ -131,6 +135,8 @@
         dumpManager.registerNormalDumpable("dumpable3", dumpable3)
         dumpManager.registerBuffer("buffer1", buffer1)
         dumpManager.registerBuffer("buffer2", buffer2)
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
 
         // WHEN a critical dump is requested
         val args = arrayOf("--dump-priority", "CRITICAL")
@@ -144,6 +150,8 @@
             any(Array<String>::class.java))
         verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
         verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+        verify(table1, never()).dump(any(), any())
+        verify(table2, never()).dump(any(), any())
     }
 
     @Test
@@ -154,6 +162,8 @@
         dumpManager.registerNormalDumpable("dumpable3", dumpable3)
         dumpManager.registerBuffer("buffer1", buffer1)
         dumpManager.registerBuffer("buffer2", buffer2)
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
 
         // WHEN a normal dump is requested
         val args = arrayOf("--dump-priority", "NORMAL")
@@ -169,6 +179,8 @@
         verify(dumpable3).dump(pw, args)
         verify(buffer1).dump(pw, 0)
         verify(buffer2).dump(pw, 0)
+        verify(table1).dump(pw, args)
+        verify(table2).dump(pw, args)
     }
 
     @Test
@@ -184,6 +196,81 @@
     }
 
     @Test
+    fun testDumpBuffers() {
+        // GIVEN a variety of registered dumpables and buffers and tables
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
+
+        // WHEN a buffer dump is requested
+        val args = arrayOf("buffers", "--tail", "1")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN all buffers are dumped (and no dumpables or tables)
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3, never()).dump(any(), any())
+        verify(buffer1).dump(pw, tailLength = 1)
+        verify(buffer2).dump(pw, tailLength = 1)
+        verify(table1, never()).dump(any(), any())
+        verify(table2, never()).dump(any(), any())
+    }
+
+    @Test
+    fun testDumpDumpables() {
+        // GIVEN a variety of registered dumpables and buffers and tables
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
+
+        // WHEN a dumpable dump is requested
+        val args = arrayOf("dumpables")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN all dumpables are dumped (both critical and normal) (and no dumpables)
+        verify(dumpable1).dump(pw, args)
+        verify(dumpable2).dump(pw, args)
+        verify(dumpable3).dump(pw, args)
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2, never()).dump(any(), anyInt())
+        verify(table1, never()).dump(any(), any())
+        verify(table2, never()).dump(any(), any())
+    }
+
+    @Test
+    fun testDumpTables() {
+        // GIVEN a variety of registered dumpables and buffers and tables
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
+
+        // WHEN a dumpable dump is requested
+        val args = arrayOf("tables")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN all dumpables are dumped (both critical and normal) (and no dumpables)
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3, never()).dump(any(), any())
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2, never()).dump(any(), anyInt())
+        verify(table1).dump(pw, args)
+        verify(table2).dump(pw, args)
+    }
+
+    @Test
     fun testDumpAllProtoDumpables() {
         dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
         dumpManager.registerDumpable("protoDumpable2", protoDumpable2)
@@ -207,6 +294,123 @@
         verify(protoDumpable2, never()).dumpProto(any(), any())
     }
 
+    @Test
+    fun testDumpTarget_selectsShortestNamedDumpable() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("first-dumpable", dumpable1)
+        dumpManager.registerCriticalDumpable("scnd-dumpable", dumpable2)
+        dumpManager.registerCriticalDumpable("third-dumpable", dumpable3)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf("dumpable")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN the matching dumpable with the shorter name is dumped
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2).dump(pw, args)
+        verify(dumpable3, never()).dump(any(), any())
+    }
+
+    @Test
+    fun testDumpTarget_selectsShortestNamedBuffer() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerBuffer("first-buffer", buffer1)
+        dumpManager.registerBuffer("scnd-buffer", buffer2)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf("buffer", "--tail", "14")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN the matching buffer with the shorter name is dumped
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2).dump(pw, tailLength = 14)
+    }
+
+    @Test
+    fun testDumpTarget_selectsShortestNamedMatch_dumpable() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("big-buffer1", buffer1)
+        dumpManager.registerBuffer("big-buffer2", buffer2)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf("2")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN the matching dumpable with the shorter name is dumped
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2).dump(pw, args)
+        verify(dumpable3, never()).dump(any(), any())
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2, never()).dump(any(), anyInt())
+    }
+
+    @Test
+    fun testDumpTarget_selectsShortestNamedMatch_buffer() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf("2", "--tail", "14")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN the matching buffer with the shorter name is dumped
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3, never()).dump(any(), any())
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2).dump(pw, tailLength = 14)
+    }
+
+    @Test
+    fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_dumpable() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("d1x", dumpable1)
+        dumpManager.registerCriticalDumpable("d2x", dumpable2)
+        dumpManager.registerCriticalDumpable("a3x", dumpable3)
+        dumpManager.registerBuffer("ab1x", buffer1)
+        dumpManager.registerBuffer("b2x", buffer2)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf("x")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3).dump(pw, args)
+        verify(buffer1, never()).dump(any(), anyInt())
+        verify(buffer2, never()).dump(any(), anyInt())
+    }
+
+    @Test
+    fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_buffer() {
+        // GIVEN a variety of registered dumpables and buffers
+        dumpManager.registerCriticalDumpable("d1x", dumpable1)
+        dumpManager.registerCriticalDumpable("d2x", dumpable2)
+        dumpManager.registerCriticalDumpable("az1x", dumpable3)
+        dumpManager.registerBuffer("b1x", buffer1)
+        dumpManager.registerBuffer("b2x", buffer2)
+
+        // WHEN a dumpable is dumped by a suffix that matches multiple options
+        val args = arrayOf("x", "--tail", "14")
+        dumpHandler.dump(fd, pw, args)
+
+        // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
+        verify(dumpable1, never()).dump(any(), any())
+        verify(dumpable2, never()).dump(any(), any())
+        verify(dumpable3, never()).dump(any(), any())
+        verify(buffer1).dump(pw, tailLength = 14)
+        verify(buffer2, never()).dump(any(), anyInt())
+    }
+
+
     private class EmptyCoreStartable : CoreStartable {
         override fun start() {}
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
index 02555cf..6d5226f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
@@ -20,21 +20,17 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.util.mockito.any
-import java.io.PrintWriter
+import com.android.systemui.log.table.TableLogBuffer
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 @SmallTest
 class DumpManagerTest : SysuiTestCase() {
 
-    @Mock private lateinit var pw: PrintWriter
-
     @Mock private lateinit var dumpable1: Dumpable
     @Mock private lateinit var dumpable2: Dumpable
     @Mock private lateinit var dumpable3: Dumpable
@@ -42,6 +38,9 @@
     @Mock private lateinit var buffer1: LogBuffer
     @Mock private lateinit var buffer2: LogBuffer
 
+    @Mock private lateinit var table1: TableLogBuffer
+    @Mock private lateinit var table2: TableLogBuffer
+
     private val dumpManager = DumpManager()
 
     @Before
@@ -50,276 +49,144 @@
     }
 
     @Test
-    fun testDumpTarget_dumpable() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("buffer1", buffer1)
-        dumpManager.registerBuffer("buffer2", buffer2)
-
-        // WHEN a dumpable is dumped explicitly
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("dumpable2", pw, args, tailLength = 0)
-
-        // THEN only the requested one has their dump() method called
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2).dump(pw, args)
-        verify(dumpable3, never()).dump(any(), any())
-        verify(buffer1, never()).dump(any(), anyInt())
-        verify(buffer2, never()).dump(any(), anyInt())
-    }
-
-    @Test
-    fun testDumpTarget_buffer() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("buffer1", buffer1)
-        dumpManager.registerBuffer("buffer2", buffer2)
-
-        // WHEN a buffer is dumped explicitly
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("buffer1", pw, args, tailLength = 14)
-
-        // THEN only the requested one has their dump() method called
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2, never()).dump(any(), any())
-        verify(dumpable3, never()).dump(any(), any())
-        verify(buffer1).dump(pw, tailLength = 14)
-        verify(buffer2, never()).dump(any(), anyInt())
-    }
-
-    @Test
-    fun testDumpableMatchingIsBasedOnEndOfTag() {
-        // GIVEN a dumpable registered to the manager
-        dumpManager.registerCriticalDumpable("com.android.foo.bar.dumpable1", dumpable1)
-
-        // WHEN that module is dumped
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("dumpable1", pw, arrayOf(), tailLength = 14)
-
-        // THEN its dump() method is called
-        verify(dumpable1).dump(pw, args)
-    }
-
-    @Test
-    fun testDumpTarget_selectsShortestNamedDumpable() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("first-dumpable", dumpable1)
-        dumpManager.registerCriticalDumpable("scnd-dumpable", dumpable2)
-        dumpManager.registerCriticalDumpable("third-dumpable", dumpable3)
-
-        // WHEN a dumpable is dumped by a suffix that matches multiple options
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("dumpable", pw, args, tailLength = 0)
-
-        // THEN the matching dumpable with the shorter name is dumped
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2).dump(pw, args)
-        verify(dumpable3, never()).dump(any(), any())
-    }
-
-    @Test
-    fun testDumpTarget_selectsShortestNamedBuffer() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerBuffer("first-buffer", buffer1)
-        dumpManager.registerBuffer("scnd-buffer", buffer2)
-
-        // WHEN a dumpable is dumped by a suffix that matches multiple options
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("buffer", pw, args, tailLength = 14)
-
-        // THEN the matching buffer with the shorter name is dumped
-        verify(buffer1, never()).dump(any(), anyInt())
-        verify(buffer2).dump(pw, tailLength = 14)
-    }
-
-    @Test
-    fun testDumpTarget_selectsShortestNamedMatch_dumpable() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("big-buffer1", buffer1)
-        dumpManager.registerBuffer("big-buffer2", buffer2)
-
-        // WHEN a dumpable is dumped by a suffix that matches multiple options
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("2", pw, args, tailLength = 14)
-
-        // THEN the matching dumpable with the shorter name is dumped
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2).dump(pw, args)
-        verify(dumpable3, never()).dump(any(), any())
-        verify(buffer1, never()).dump(any(), anyInt())
-        verify(buffer2, never()).dump(any(), anyInt())
-    }
-
-    @Test
-    fun testDumpTarget_selectsShortestNamedMatch_buffer() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("buffer1", buffer1)
-        dumpManager.registerBuffer("buffer2", buffer2)
-
-        // WHEN a dumpable is dumped by a suffix that matches multiple options
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("2", pw, args, tailLength = 14)
-
-        // THEN the matching buffer with the shorter name is dumped
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2, never()).dump(any(), any())
-        verify(dumpable3, never()).dump(any(), any())
-        verify(buffer1, never()).dump(any(), anyInt())
-        verify(buffer2).dump(pw, tailLength = 14)
-    }
-
-    @Test
-    fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_dumpable() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("d1x", dumpable1)
-        dumpManager.registerCriticalDumpable("d2x", dumpable2)
-        dumpManager.registerCriticalDumpable("a3x", dumpable3)
-        dumpManager.registerBuffer("ab1x", buffer1)
-        dumpManager.registerBuffer("b2x", buffer2)
-
-        // WHEN a dumpable is dumped by a suffix that matches multiple options
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("x", pw, args, tailLength = 14)
-
-        // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2, never()).dump(any(), any())
-        verify(dumpable3).dump(pw, args)
-        verify(buffer1, never()).dump(any(), anyInt())
-        verify(buffer2, never()).dump(any(), anyInt())
-    }
-
-    @Test
-    fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_buffer() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("d1x", dumpable1)
-        dumpManager.registerCriticalDumpable("d2x", dumpable2)
-        dumpManager.registerCriticalDumpable("az1x", dumpable3)
-        dumpManager.registerBuffer("b1x", buffer1)
-        dumpManager.registerBuffer("b2x", buffer2)
-
-        // WHEN a dumpable is dumped by a suffix that matches multiple options
-        val args = arrayOf<String>()
-        dumpManager.dumpTarget("x", pw, args, tailLength = 14)
-
-        // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2, never()).dump(any(), any())
-        verify(dumpable3, never()).dump(any(), any())
-        verify(buffer1).dump(pw, tailLength = 14)
-        verify(buffer2, never()).dump(any(), anyInt())
-    }
-
-    @Test
-    fun testDumpDumpables() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("buffer1", buffer1)
-        dumpManager.registerBuffer("buffer2", buffer2)
-
-        // WHEN a dumpable dump is requested
-        val args = arrayOf<String>()
-        dumpManager.dumpDumpables(pw, args)
-
-        // THEN all dumpables are dumped (both critical and normal) (and no dumpables)
-        verify(dumpable1).dump(pw, args)
-        verify(dumpable2).dump(pw, args)
-        verify(dumpable3).dump(pw, args)
-        verify(buffer1, never()).dump(any(), anyInt())
-        verify(buffer2, never()).dump(any(), anyInt())
-    }
-
-    @Test
-    fun testDumpBuffers() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("buffer1", buffer1)
-        dumpManager.registerBuffer("buffer2", buffer2)
-
-        // WHEN a buffer dump is requested
-        dumpManager.dumpBuffers(pw, tailLength = 1)
-
-        // THEN all buffers are dumped (and no dumpables)
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2, never()).dump(any(), any())
-        verify(dumpable3, never()).dump(any(), any())
-        verify(buffer1).dump(pw, tailLength = 1)
-        verify(buffer2).dump(pw, tailLength = 1)
-    }
-
-    @Test
-    fun testCriticalDump() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("buffer1", buffer1)
-        dumpManager.registerBuffer("buffer2", buffer2)
-
-        // WHEN a critical dump is requested
-        val args = arrayOf<String>()
-        dumpManager.dumpCritical(pw, args)
-
-        // THEN only critical modules are dumped (and no buffers)
-        verify(dumpable1).dump(pw, args)
-        verify(dumpable2).dump(pw, args)
-        verify(dumpable3, never()).dump(any(), any())
-        verify(buffer1, never()).dump(any(), anyInt())
-        verify(buffer2, never()).dump(any(), anyInt())
-    }
-
-    @Test
-    fun testNormalDump() {
-        // GIVEN a variety of registered dumpables and buffers
-        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
-        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
-        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
-        dumpManager.registerBuffer("buffer1", buffer1)
-        dumpManager.registerBuffer("buffer2", buffer2)
-
-        // WHEN a normal dump is requested
-        val args = arrayOf<String>()
-        dumpManager.dumpNormal(pw, args, tailLength = 2)
-
-        // THEN the normal module and all buffers are dumped
-        verify(dumpable1, never()).dump(any(), any())
-        verify(dumpable2, never()).dump(any(), any())
-        verify(dumpable3).dump(pw, args)
-        verify(buffer1).dump(pw, tailLength = 2)
-        verify(buffer2).dump(pw, tailLength = 2)
-    }
-
-    @Test
-    fun testUnregister() {
-        // GIVEN a variety of registered dumpables and buffers
+    fun testRegisterUnregister_dumpables() {
+        // GIVEN a variety of registered dumpables
         dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
         dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
         dumpManager.registerNormalDumpable("dumpable3", dumpable3)
 
+        // WHEN the collection is requested
+        var dumpables = dumpManager.getDumpables().map { it.dumpable }
+
+        // THEN it contains the registered entries
+        assertThat(dumpables).containsExactly(dumpable1, dumpable2, dumpable3)
+
+        // WHEN the dumpables are unregistered
         dumpManager.unregisterDumpable("dumpable2")
         dumpManager.unregisterDumpable("dumpable3")
 
-        // WHEN a dumpables dump is requested
-        val args = arrayOf<String>()
-        dumpManager.dumpDumpables(pw, args)
+        // WHEN the dumpable collection is requests
+        dumpables = dumpManager.getDumpables().map { it.dumpable }
 
-        // THEN the unregistered dumpables (both normal and critical) are not dumped
-        verify(dumpable1).dump(pw, args)
-        verify(dumpable2, never()).dump(any(), any())
-        verify(dumpable3, never()).dump(any(), any())
+        // THEN it contains only the currently-registered entry
+        assertThat(dumpables).containsExactly(dumpable1)
+    }
+
+    @Test
+    fun testRegister_buffers() {
+        // GIVEN a set of registered buffers
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+
+        // WHEN the collection is requested
+        val dumpables = dumpManager.getLogBuffers().map { it.buffer }
+
+        // THEN it contains the registered entries
+        assertThat(dumpables).containsExactly(buffer1, buffer2)
+    }
+
+    @Test
+    fun testRegister_tableLogBuffers() {
+        // GIVEN a set of registered buffers
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
+
+        // WHEN the collection is requested
+        val tables = dumpManager.getTableLogBuffers().map { it.table }
+
+        // THEN it contains the registered entries
+        assertThat(tables).containsExactly(table1, table2)
+    }
+
+    @Test
+    fun registerDumpable_throwsWhenNameCannotBeAssigned() {
+        // GIVEN dumpable1 and buffer1 and table1 are registered
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerTableLogBuffer("table1", table1)
+
+        // THEN an exception is thrown when trying to re-register a new dumpable under the same key
+        assertThrows(IllegalArgumentException::class.java) {
+            dumpManager.registerCriticalDumpable("dumpable1", dumpable2)
+        }
+        assertThrows(IllegalArgumentException::class.java) {
+            dumpManager.registerBuffer("buffer1", buffer2)
+        }
+        assertThrows(IllegalArgumentException::class.java) {
+            dumpManager.registerTableLogBuffer("table1", table2)
+        }
+    }
+
+    @Test
+    fun registerDumpable_doesNotThrowWhenReRegistering() {
+        // GIVEN dumpable1 and buffer1 are registered
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerBuffer("buffer1", buffer1)
+
+        // THEN no exception is thrown when trying to re-register a new dumpable under the same key
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerBuffer("buffer1", buffer1)
+
+        // No exception thrown
+    }
+
+    @Test
+    fun getDumpables_returnsSafeCollection() {
+        // GIVEN a variety of registered dumpables
+        dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+        dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+        dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+
+        // WHEN the collection is retrieved
+        val dumpables = dumpManager.getDumpables()
+
+        // WHEN the collection changes from underneath
+        dumpManager.unregisterDumpable("dumpable1")
+        dumpManager.unregisterDumpable("dumpable2")
+        dumpManager.unregisterDumpable("dumpable3")
+
+        // THEN new collections are empty
+        assertThat(dumpManager.getDumpables()).isEmpty()
+
+        // AND the collection is still safe to use
+        assertThat(dumpables).hasSize(3)
+    }
+
+    @Test
+    fun getBuffers_returnsSafeCollection() {
+        // GIVEN a set of registered buffers
+        dumpManager.registerBuffer("buffer1", buffer1)
+        dumpManager.registerBuffer("buffer2", buffer2)
+
+        // WHEN the collection is requested
+        val buffers = dumpManager.getLogBuffers()
+
+        // WHEN the collection changes
+        dumpManager.registerBuffer("buffer3", buffer1)
+
+        // THEN the new entry is represented
+        assertThat(dumpManager.getLogBuffers()).hasSize(3)
+
+        // AND the previous collection is unchanged
+        assertThat(buffers).hasSize(2)
+    }
+
+    @Test
+    fun getTableBuffers_returnsSafeCollection() {
+        // GIVEN a set of registered buffers
+        dumpManager.registerTableLogBuffer("table1", table1)
+        dumpManager.registerTableLogBuffer("table2", table2)
+
+        // WHEN the collection is requested
+        val tables = dumpManager.getTableLogBuffers()
+
+        // WHEN the collection changes
+        dumpManager.registerTableLogBuffer("table3", table1)
+
+        // THEN the new entry is represented
+        assertThat(dumpManager.getTableLogBuffers()).hasSize(3)
+
+        // AND the previous collection is unchanged
+        assertThat(tables).hasSize(2)
     }
 }
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..8a1b094 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt
@@ -17,12 +17,13 @@
 package com.android.systemui.dump
 
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.LogcatEchoTracker
 
 /**
  * Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests.
  */
+@JvmOverloads
 fun logcatLogBuffer(name: String = "EchoToLogcatLogBuffer") =
     LogBuffer(name, 50, LogcatEchoTrackerAlways())
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
index cb38846..3ff7202 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
@@ -18,20 +18,14 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpHandler.Companion.dump
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.util.io.FakeBasicFileAttributes
 import com.android.systemui.util.io.Files
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 import java.io.BufferedWriter
 import java.io.ByteArrayOutputStream
 import java.io.IOException
@@ -42,17 +36,29 @@
 import java.nio.file.Paths
 import java.nio.file.attribute.BasicFileAttributes
 import java.util.Arrays
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 class LogEulogizerTest : SysuiTestCase() {
 
     lateinit var eulogizer: LogBufferEulogizer
 
-    @Mock
-    lateinit var dumpManager: DumpManager
+    @Mock lateinit var dumpManager: DumpManager
+    @Mock lateinit var logBuffer1: LogBuffer
+    lateinit var logBufferEntry1: DumpsysEntry.LogBufferEntry
+    @Mock lateinit var logBuffer2: LogBuffer
+    lateinit var logBufferEntry2: DumpsysEntry.LogBufferEntry
 
-    @Mock
-    lateinit var files: Files
+    @Mock lateinit var files: Files
 
     private val clock = FakeSystemClock()
 
@@ -67,37 +73,47 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        logBufferEntry1 = DumpsysEntry.LogBufferEntry(logBuffer1, "logbuffer1")
+        logBufferEntry2 = DumpsysEntry.LogBufferEntry(logBuffer2, "logbuffer2")
 
-        eulogizer =
-                LogBufferEulogizer(dumpManager, clock, files, path, MIN_WRITE_GAP, MAX_READ_AGE)
+        eulogizer = LogBufferEulogizer(dumpManager, clock, files, path, MIN_WRITE_GAP, MAX_READ_AGE)
 
         Mockito.`when`(files.newBufferedWriter(eq(path), any(OpenOption::class.java)))
-                .thenReturn(fileWriter)
+            .thenReturn(fileWriter)
 
         Mockito.`when`(
-                files.readAttributes(eq(path),
-                eq(BasicFileAttributes::class.java),
-                any(LinkOption::class.java))
-        ).thenReturn(fileAttrs)
+                files.readAttributes(
+                    eq(path),
+                    eq(BasicFileAttributes::class.java),
+                    any(LinkOption::class.java)
+                )
+            )
+            .thenReturn(fileAttrs)
 
         Mockito.`when`(files.lines(eq(path))).thenReturn(Arrays.stream(FAKE_LINES))
+
+        whenever(dumpManager.getLogBuffers()).thenReturn(listOf(logBufferEntry1, logBufferEntry2))
     }
 
     @Test
     fun testFileIsCreated() {
         // GIVEN that the log file doesn't already exist
         Mockito.`when`(
-                files.readAttributes(eq(path),
-                        eq(BasicFileAttributes::class.java),
-                        any(LinkOption::class.java))
-        ).thenThrow(IOException("File not found"))
+                files.readAttributes(
+                    eq(path),
+                    eq(BasicFileAttributes::class.java),
+                    any(LinkOption::class.java)
+                )
+            )
+            .thenThrow(IOException("File not found"))
 
         // WHEN .record() is called
         val exception = RuntimeException("Something bad happened")
         assertEquals(exception, eulogizer.record(exception))
 
         // THEN the buffers are dumped to the file
-        verify(dumpManager).dumpBuffers(any(PrintWriter::class.java), Mockito.anyInt())
+        verify(logBuffer1).dump(any(PrintWriter::class.java), anyInt())
+        verify(logBuffer2).dump(any(PrintWriter::class.java), anyInt())
         assertTrue(fileStream.toString().isNotEmpty())
     }
 
@@ -111,7 +127,8 @@
         assertEquals(exception, eulogizer.record(exception))
 
         // THEN the buffers are dumped to the file
-        verify(dumpManager).dumpBuffers(any(PrintWriter::class.java), Mockito.anyInt())
+        verify(logBuffer1).dump(any(PrintWriter::class.java), anyInt())
+        verify(logBuffer2).dump(any(PrintWriter::class.java), anyInt())
         assertTrue(fileStream.toString().isNotEmpty())
     }
 
@@ -125,7 +142,8 @@
         assertEquals(exception, eulogizer.record(exception))
 
         // THEN the file isn't written to
-        verify(dumpManager, never()).dumpBuffers(any(PrintWriter::class.java), Mockito.anyInt())
+        verify(logBuffer1, never()).dump(any(PrintWriter::class.java), anyInt())
+        verify(logBuffer2, never()).dump(any(PrintWriter::class.java), anyInt())
         assertTrue(fileStream.toString().isEmpty())
     }
 
@@ -161,9 +179,4 @@
 private const val MIN_WRITE_GAP = 10L
 private const val MAX_READ_AGE = 100L
 
-private val FAKE_LINES =
-        arrayOf(
-                "First line",
-                "Second line",
-                "Third line"
-        )
+private val FAKE_LINES = arrayOf("First line", "Second line", "Third line")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
index 35f0f6c..37c70d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -28,12 +28,12 @@
 @RunWith(AndroidTestingRunner::class)
 class FakeFeatureFlagsTest : SysuiTestCase() {
 
-    private val unreleasedFlag = UnreleasedFlag(-1000, "-1000", "test")
-    private val releasedFlag = ReleasedFlag(-1001, "-1001", "test")
-    private val stringFlag = StringFlag(-1002, "-1002", "test")
-    private val resourceBooleanFlag = ResourceBooleanFlag(-1003, "-1003", "test", resourceId = -1)
-    private val resourceStringFlag = ResourceStringFlag(-1004, "-1004", "test", resourceId = -1)
-    private val sysPropBooleanFlag = SysPropBooleanFlag(-1005, "test", "test")
+    private val unreleasedFlag = UnreleasedFlag("-1000", "test")
+    private val releasedFlag = ReleasedFlag("-1001", "test")
+    private val stringFlag = StringFlag("-1002", "test")
+    private val resourceBooleanFlag = ResourceBooleanFlag("-1003", "test", resourceId = -1)
+    private val resourceStringFlag = ResourceStringFlag("-1004", "test", resourceId = -1)
+    private val sysPropBooleanFlag = SysPropBooleanFlag("test", "test")
 
     /**
      * FakeFeatureFlags does not honor any default values. All flags which are accessed must be
@@ -46,43 +46,43 @@
             assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("id=1")
+            assertThat(ex.message).contains("UNKNOWN(teamfood)")
         }
         try {
             assertThat(flags.isEnabled(unreleasedFlag)).isFalse()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("UNKNOWN(id=-1000)")
+            assertThat(ex.message).contains("UNKNOWN(-1000)")
         }
         try {
             assertThat(flags.isEnabled(releasedFlag)).isFalse()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("UNKNOWN(id=-1001)")
+            assertThat(ex.message).contains("UNKNOWN(-1001)")
         }
         try {
             assertThat(flags.isEnabled(resourceBooleanFlag)).isFalse()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("UNKNOWN(id=-1003)")
+            assertThat(ex.message).contains("UNKNOWN(-1003)")
         }
         try {
             assertThat(flags.isEnabled(sysPropBooleanFlag)).isFalse()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("UNKNOWN(id=-1005)")
+            assertThat(ex.message).contains("UNKNOWN(test)")
         }
         try {
             assertThat(flags.getString(stringFlag)).isEmpty()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("UNKNOWN(id=-1002)")
+            assertThat(ex.message).contains("UNKNOWN(-1002)")
         }
         try {
             assertThat(flags.getString(resourceStringFlag)).isEmpty()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("UNKNOWN(id=-1004)")
+            assertThat(ex.message).contains("UNKNOWN(-1004)")
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 18f7db1..ff15cb3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -74,10 +74,10 @@
     private val serverFlagReader = ServerFlagReaderFake()
 
     private val teamfoodableFlagA = UnreleasedFlag(
-        500, name = "a", namespace = "test", teamfood = true
+        name = "a", namespace = "test", teamfood = true
     )
     private val teamfoodableFlagB = ReleasedFlag(
-        501, name = "b", namespace = "test", teamfood = true
+        name = "b", namespace = "test", teamfood = true
     )
 
     @Before
@@ -119,7 +119,6 @@
         assertThat(
             featureFlagsDebug.isEnabled(
                 ReleasedFlag(
-                    2,
                     name = "2",
                     namespace = "test"
                 )
@@ -128,7 +127,6 @@
         assertThat(
             featureFlagsDebug.isEnabled(
                 UnreleasedFlag(
-                    3,
                     name = "3",
                     namespace = "test"
                 )
@@ -137,7 +135,6 @@
         assertThat(
             featureFlagsDebug.isEnabled(
                 ReleasedFlag(
-                    4,
                     name = "4",
                     namespace = "test"
                 )
@@ -146,7 +143,6 @@
         assertThat(
             featureFlagsDebug.isEnabled(
                 UnreleasedFlag(
-                    5,
                     name = "5",
                     namespace = "test"
                 )
@@ -208,23 +204,22 @@
         assertThat(
             featureFlagsDebug.isEnabled(
                 ResourceBooleanFlag(
-                    1,
                     "1",
                     "test",
                     1001
                 )
             )
         ).isFalse()
-        assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag(2, "2", "test", 1002))).isTrue()
-        assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag(3, "3", "test", 1003))).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag("2", "test", 1002))).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag("3", "test", 1003))).isTrue()
 
         Assert.assertThrows(NameNotFoundException::class.java) {
-            featureFlagsDebug.isEnabled(ResourceBooleanFlag(4, "4", "test", 1004))
+            featureFlagsDebug.isEnabled(ResourceBooleanFlag("4", "test", 1004))
         }
         // Test that resource is loaded (and validated) even when the setting is set.
         //  This prevents developers from not noticing when they reference an invalid resource.
         Assert.assertThrows(NameNotFoundException::class.java) {
-            featureFlagsDebug.isEnabled(ResourceBooleanFlag(5, "5", "test", 1005))
+            featureFlagsDebug.isEnabled(ResourceBooleanFlag("5", "test", 1005))
         }
     }
 
@@ -237,30 +232,29 @@
             return@thenAnswer it.getArgument(1)
         }
 
-        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(1, "a", "test"))).isFalse()
-        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(2, "b", "test"))).isTrue()
-        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(3, "c", "test", true))).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("a", "test"))).isFalse()
+        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("b", "test"))).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("c", "test", true))).isTrue()
         assertThat(
             featureFlagsDebug.isEnabled(
                 SysPropBooleanFlag(
-                    4,
                     "d",
                     "test",
                     false
                 )
             )
         ).isFalse()
-        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(5, "e", "test"))).isFalse()
+        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("e", "test"))).isFalse()
     }
 
     @Test
     fun readStringFlag() {
         whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("foo")
         whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("bar")
-        assertThat(featureFlagsDebug.getString(StringFlag(1, "1", "test", "biz"))).isEqualTo("biz")
-        assertThat(featureFlagsDebug.getString(StringFlag(2, "2", "test", "baz"))).isEqualTo("baz")
-        assertThat(featureFlagsDebug.getString(StringFlag(3, "3", "test", "buz"))).isEqualTo("foo")
-        assertThat(featureFlagsDebug.getString(StringFlag(4, "4", "test", "buz"))).isEqualTo("bar")
+        assertThat(featureFlagsDebug.getString(StringFlag("1", "test", "biz"))).isEqualTo("biz")
+        assertThat(featureFlagsDebug.getString(StringFlag("2", "test", "baz"))).isEqualTo("baz")
+        assertThat(featureFlagsDebug.getString(StringFlag("3", "test", "buz"))).isEqualTo("foo")
+        assertThat(featureFlagsDebug.getString(StringFlag("4", "test", "buz"))).isEqualTo("bar")
     }
 
     @Test
@@ -279,7 +273,6 @@
         assertThat(
             featureFlagsDebug.getString(
                 ResourceStringFlag(
-                    1,
                     "1",
                     "test",
                     1001
@@ -289,7 +282,6 @@
         assertThat(
             featureFlagsDebug.getString(
                 ResourceStringFlag(
-                    2,
                     "2",
                     "test",
                     1002
@@ -299,7 +291,6 @@
         assertThat(
             featureFlagsDebug.getString(
                 ResourceStringFlag(
-                    3,
                     "3",
                     "test",
                     1003
@@ -308,15 +299,15 @@
         ).isEqualTo("override3")
 
         Assert.assertThrows(NullPointerException::class.java) {
-            featureFlagsDebug.getString(ResourceStringFlag(4, "4", "test", 1004))
+            featureFlagsDebug.getString(ResourceStringFlag("4", "test", 1004))
         }
         Assert.assertThrows(NameNotFoundException::class.java) {
-            featureFlagsDebug.getString(ResourceStringFlag(5, "5", "test", 1005))
+            featureFlagsDebug.getString(ResourceStringFlag("5", "test", 1005))
         }
         // Test that resource is loaded (and validated) even when the setting is set.
         //  This prevents developers from not noticing when they reference an invalid resource.
         Assert.assertThrows(NameNotFoundException::class.java) {
-            featureFlagsDebug.getString(ResourceStringFlag(6, "6", "test", 1005))
+            featureFlagsDebug.getString(ResourceStringFlag("6", "test", 1005))
         }
     }
 
@@ -324,10 +315,10 @@
     fun readIntFlag() {
         whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(22)
         whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(48)
-        assertThat(featureFlagsDebug.getInt(IntFlag(1, "1", "test", 12))).isEqualTo(12)
-        assertThat(featureFlagsDebug.getInt(IntFlag(2, "2", "test", 93))).isEqualTo(93)
-        assertThat(featureFlagsDebug.getInt(IntFlag(3, "3", "test", 8))).isEqualTo(22)
-        assertThat(featureFlagsDebug.getInt(IntFlag(4, "4", "test", 234))).isEqualTo(48)
+        assertThat(featureFlagsDebug.getInt(IntFlag("1", "test", 12))).isEqualTo(12)
+        assertThat(featureFlagsDebug.getInt(IntFlag("2", "test", 93))).isEqualTo(93)
+        assertThat(featureFlagsDebug.getInt(IntFlag("3", "test", 8))).isEqualTo(22)
+        assertThat(featureFlagsDebug.getInt(IntFlag("4", "test", 234))).isEqualTo(48)
     }
 
     @Test
@@ -339,30 +330,30 @@
         whenever(resources.getInteger(1005)).thenThrow(NotFoundException("unknown resource"))
         whenever(resources.getInteger(1006)).thenThrow(NotFoundException("unknown resource"))
 
-        whenever(flagManager.readFlagValue<Int>(eq(3), any())).thenReturn(20)
-        whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(500)
-        whenever(flagManager.readFlagValue<Int>(eq(5), any())).thenReturn(9519)
+        whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(20)
+        whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(500)
+        whenever(flagManager.readFlagValue<Int>(eq("5"), any())).thenReturn(9519)
 
-        assertThat(featureFlagsDebug.getInt(ResourceIntFlag(1, "1", "test", 1001))).isEqualTo(88)
-        assertThat(featureFlagsDebug.getInt(ResourceIntFlag(2, "2", "test", 1002))).isEqualTo(61)
-        assertThat(featureFlagsDebug.getInt(ResourceIntFlag(3, "3", "test", 1003))).isEqualTo(20)
+        assertThat(featureFlagsDebug.getInt(ResourceIntFlag("1", "test", 1001))).isEqualTo(88)
+        assertThat(featureFlagsDebug.getInt(ResourceIntFlag("2", "test", 1002))).isEqualTo(61)
+        assertThat(featureFlagsDebug.getInt(ResourceIntFlag("3", "test", 1003))).isEqualTo(20)
 
         Assert.assertThrows(NotFoundException::class.java) {
-            featureFlagsDebug.getInt(ResourceIntFlag(4, "4", "test", 1004))
+            featureFlagsDebug.getInt(ResourceIntFlag("4", "test", 1004))
         }
         // Test that resource is loaded (and validated) even when the setting is set.
         //  This prevents developers from not noticing when they reference an invalid resource.
         Assert.assertThrows(NotFoundException::class.java) {
-            featureFlagsDebug.getInt(ResourceIntFlag(5, "5", "test", 1005))
+            featureFlagsDebug.getInt(ResourceIntFlag("5", "test", 1005))
         }
     }
 
     @Test
     fun broadcastReceiver_IgnoresInvalidData() {
-        addFlag(UnreleasedFlag(1, "1", "test"))
-        addFlag(ResourceBooleanFlag(2, "2", "test", 1002))
-        addFlag(StringFlag(3, "3", "test", "flag3"))
-        addFlag(ResourceStringFlag(4, "4", "test", 1004))
+        addFlag(UnreleasedFlag("1", "test"))
+        addFlag(ResourceBooleanFlag("2", "test", 1002))
+        addFlag(StringFlag("3", "test", "flag3"))
+        addFlag(ResourceStringFlag("4", "test", 1004))
 
         broadcastReceiver.onReceive(mockContext, null)
         broadcastReceiver.onReceive(mockContext, Intent())
@@ -378,7 +369,7 @@
 
     @Test
     fun intentWithId_NoValueKeyClears() {
-        addFlag(UnreleasedFlag(1, name = "1", namespace = "test"))
+        addFlag(UnreleasedFlag(name = "1", namespace = "test"))
 
         // trying to erase an id not in the map does nothing
         broadcastReceiver.onReceive(
@@ -397,10 +388,10 @@
 
     @Test
     fun setBooleanFlag() {
-        addFlag(UnreleasedFlag(1, "1", "test"))
-        addFlag(UnreleasedFlag(2, "2", "test"))
-        addFlag(ResourceBooleanFlag(3, "3", "test", 1003))
-        addFlag(ResourceBooleanFlag(4, "4", "test", 1004))
+        addFlag(UnreleasedFlag("1", "test"))
+        addFlag(UnreleasedFlag("2", "test"))
+        addFlag(ResourceBooleanFlag("3", "test", 1003))
+        addFlag(ResourceBooleanFlag("4", "test", 1004))
 
         setByBroadcast("1", false)
         verifyPutData("1", "{\"type\":\"boolean\",\"value\":false}")
@@ -417,8 +408,8 @@
 
     @Test
     fun setStringFlag() {
-        addFlag(StringFlag(1, "1", "1", "test"))
-        addFlag(ResourceStringFlag(2, "2", "test", 1002))
+        addFlag(StringFlag("1", "1", "test"))
+        addFlag(ResourceStringFlag("2", "test", 1002))
 
         setByBroadcast("1", "override1")
         verifyPutData("1", "{\"type\":\"string\",\"value\":\"override1\"}")
@@ -429,7 +420,7 @@
 
     @Test
     fun setFlag_ClearsCache() {
-        val flag1 = addFlag(StringFlag(1, "1", "test", "flag1"))
+        val flag1 = addFlag(StringFlag("1", "test", "flag1"))
         whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("original")
 
         // gets the flag & cache it
@@ -451,7 +442,7 @@
 
     @Test
     fun serverSide_Overrides_MakesFalse() {
-        val flag = ReleasedFlag(100, "100", "test")
+        val flag = ReleasedFlag("100", "test")
 
         serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
 
@@ -460,7 +451,7 @@
 
     @Test
     fun serverSide_Overrides_MakesTrue() {
-        val flag = UnreleasedFlag(100, name = "100", namespace = "test")
+        val flag = UnreleasedFlag(name = "100", namespace = "test")
 
         serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
         assertThat(featureFlagsDebug.isEnabled(flag)).isTrue()
@@ -494,18 +485,18 @@
 
     @Test
     fun dumpFormat() {
-        val flag1 = ReleasedFlag(1, "1", "test")
-        val flag2 = ResourceBooleanFlag(2, "2", "test", 1002)
-        val flag3 = UnreleasedFlag(3, "3", "test")
-        val flag4 = StringFlag(4, "4", "test", "")
-        val flag5 = StringFlag(5, "5", "test", "flag5default")
-        val flag6 = ResourceStringFlag(6, "6", "test", 1006)
-        val flag7 = ResourceStringFlag(7, "7", "test", 1007)
+        val flag1 = ReleasedFlag("1", "test")
+        val flag2 = ResourceBooleanFlag("2", "test", 1002)
+        val flag3 = UnreleasedFlag("3", "test")
+        val flag4 = StringFlag("4", "test", "")
+        val flag5 = StringFlag("5", "test", "flag5default")
+        val flag6 = ResourceStringFlag("6", "test", 1006)
+        val flag7 = ResourceStringFlag("7", "test", 1007)
 
         whenever(resources.getBoolean(1002)).thenReturn(true)
         whenever(resources.getString(1006)).thenReturn("resource1006")
         whenever(resources.getString(1007)).thenReturn("resource1007")
-        whenever(flagManager.readFlagValue(eq(7), eq(StringFlagSerializer)))
+        whenever(flagManager.readFlagValue(eq("7"), eq(StringFlagSerializer)))
             .thenReturn("override7")
 
         // WHEN the flags have been accessed
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index 917147b..16b4595 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -44,7 +44,7 @@
     private val serverFlagReader = ServerFlagReaderFake()
 
 
-    private val flagA = ReleasedFlag(501, name = "a", namespace = "test")
+    private val flagA = ReleasedFlag(name = "a", namespace = "test")
 
     @Before
     fun setup() {
@@ -62,11 +62,10 @@
 
     @Test
     fun testBooleanResourceFlag() {
-        val flagId = 213
         val flagResourceId = 3
         val flagName = "213"
         val flagNamespace = "test"
-        val flag = ResourceBooleanFlag(flagId, flagName, flagNamespace, flagResourceId)
+        val flag = ResourceBooleanFlag(flagName, flagNamespace, flagResourceId)
         whenever(mResources.getBoolean(flagResourceId)).thenReturn(true)
         assertThat(featureFlagsRelease.isEnabled(flag)).isTrue()
     }
@@ -79,33 +78,32 @@
         whenever(mResources.getString(1004)).thenAnswer { throw NameNotFoundException() }
 
         assertThat(featureFlagsRelease.getString(
-            ResourceStringFlag(1, "1", "test", 1001))).isEqualTo("")
+            ResourceStringFlag("1", "test", 1001))).isEqualTo("")
         assertThat(featureFlagsRelease.getString(
-            ResourceStringFlag(2, "2", "test", 1002))).isEqualTo("res2")
+            ResourceStringFlag("2", "test", 1002))).isEqualTo("res2")
 
         assertThrows(NullPointerException::class.java) {
-            featureFlagsRelease.getString(ResourceStringFlag(3, "3", "test", 1003))
+            featureFlagsRelease.getString(ResourceStringFlag("3", "test", 1003))
         }
         assertThrows(NameNotFoundException::class.java) {
-            featureFlagsRelease.getString(ResourceStringFlag(4, "4", "test", 1004))
+            featureFlagsRelease.getString(ResourceStringFlag("4", "test", 1004))
         }
     }
 
     @Test
     fun testSysPropBooleanFlag() {
-        val flagId = 213
         val flagName = "sys_prop_flag"
         val flagNamespace = "test"
         val flagDefault = true
 
-        val flag = SysPropBooleanFlag(flagId, flagName, flagNamespace, flagDefault)
+        val flag = SysPropBooleanFlag(flagName, flagNamespace, flagDefault)
         whenever(mSystemProperties.getBoolean(flagName, flagDefault)).thenReturn(flagDefault)
         assertThat(featureFlagsRelease.isEnabled(flag)).isEqualTo(flagDefault)
     }
 
     @Test
     fun serverSide_OverridesReleased_MakesFalse() {
-        val flag = ReleasedFlag(100, "100", "test")
+        val flag = ReleasedFlag("100", "test")
 
         serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
 
@@ -114,7 +112,7 @@
 
     @Test
     fun serverSide_OverridesUnreleased_Ignored() {
-        val flag = UnreleasedFlag(100, "100", "test")
+        val flag = UnreleasedFlag("100", "test")
 
         serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
 
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 28131b5..b02baa7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -33,10 +33,10 @@
     @Mock private lateinit var featureFlags: FeatureFlagsDebug
     @Mock private lateinit var pw: PrintWriter
     private val flagMap = mutableMapOf<String, Flag<*>>()
-    private val flagA = UnreleasedFlag(500, "500", "test")
-    private val flagB = ReleasedFlag(501, "501", "test")
-    private val stringFlag = StringFlag(502, "502", "test", "abracadabra")
-    private val intFlag = IntFlag(503, "503", "test", 12)
+    private val flagA = UnreleasedFlag("500", "test")
+    private val flagB = ReleasedFlag("501", "test")
+    private val stringFlag = StringFlag("502", "test", "abracadabra")
+    private val intFlag = IntFlag("503", "test", 12)
 
     private lateinit var cmd: FlagCommand
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
index e679d47..303aaa1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
@@ -64,14 +64,14 @@
         verifyNoMoreInteractions(mFlagSettingsHelper)
 
         // adding the first listener registers the observer
-        mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1)
+        mFlagManager.addListener(ReleasedFlag("1", "test"), listener1)
         val observer = withArgCaptor<ContentObserver> {
             verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
         }
         verifyNoMoreInteractions(mFlagSettingsHelper)
 
         // adding another listener does nothing
-        mFlagManager.addListener(ReleasedFlag(2, "2", "test"), listener2)
+        mFlagManager.addListener(ReleasedFlag("2", "test"), listener2)
         verifyNoMoreInteractions(mFlagSettingsHelper)
 
         // removing the original listener does nothing with second one still present
@@ -89,7 +89,7 @@
         val listener = mock<FlagListenable.Listener>()
         val clearCacheAction = mock<Consumer<String>>()
         mFlagManager.clearCacheAction = clearCacheAction
-        mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener)
+        mFlagManager.addListener(ReleasedFlag("1", "test"), listener)
         val observer = withArgCaptor<ContentObserver> {
             verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
         }
@@ -101,8 +101,8 @@
     fun testObserverInvokesListeners() {
         val listener1 = mock<FlagListenable.Listener>()
         val listener10 = mock<FlagListenable.Listener>()
-        mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1)
-        mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener10)
+        mFlagManager.addListener(ReleasedFlag("1", "test"), listener1)
+        mFlagManager.addListener(ReleasedFlag("10", "test"), listener10)
         val observer = withArgCaptor<ContentObserver> {
             verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
         }
@@ -127,8 +127,8 @@
     fun testOnlySpecificFlagListenerIsInvoked() {
         val listener1 = mock<FlagListenable.Listener>()
         val listener10 = mock<FlagListenable.Listener>()
-        mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1)
-        mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener10)
+        mFlagManager.addListener(ReleasedFlag("1", "test"), listener1)
+        mFlagManager.addListener(ReleasedFlag("10", "test"), listener10)
 
         mFlagManager.dispatchListenersAndMaybeRestart("1", null)
         val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
@@ -148,8 +148,8 @@
     @Test
     fun testSameListenerCanBeUsedForMultipleFlags() {
         val listener = mock<FlagListenable.Listener>()
-        mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener)
-        mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener)
+        mFlagManager.addListener(ReleasedFlag("1", "test"), listener)
+        mFlagManager.addListener(ReleasedFlag("10", "test"), listener)
 
         mFlagManager.dispatchListenersAndMaybeRestart("1", null)
         val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
@@ -177,7 +177,7 @@
     @Test
     fun testListenerCanSuppressRestart() {
         val restartAction = mock<Consumer<Boolean>>()
-        mFlagManager.addListener(ReleasedFlag(1, "1", "test")) { event ->
+        mFlagManager.addListener(ReleasedFlag("1", "test")) { event ->
             event.requestNoRestart()
         }
         mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
@@ -188,7 +188,7 @@
     @Test
     fun testListenerOnlySuppressesRestartForOwnFlag() {
         val restartAction = mock<Consumer<Boolean>>()
-        mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { event ->
+        mFlagManager.addListener(ReleasedFlag("10", "test")) { event ->
             event.requestNoRestart()
         }
         mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
@@ -199,10 +199,10 @@
     @Test
     fun testRestartWhenNotAllListenersRequestSuppress() {
         val restartAction = mock<Consumer<Boolean>>()
-        mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { event ->
+        mFlagManager.addListener(ReleasedFlag("10", "test")) { event ->
             event.requestNoRestart()
         }
-        mFlagManager.addListener(ReleasedFlag(10, "10", "test")) {
+        mFlagManager.addListener(ReleasedFlag("10", "test")) {
             // do not request
         }
         mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
index 953b7fb..1d1949d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -53,7 +53,7 @@
 
     @Test
     fun testChange_alertsListener() {
-        val flag = ReleasedFlag(1, "flag_1", "test")
+        val flag = ReleasedFlag("flag_1", "test")
         serverFlagReader.listenForChanges(listOf(flag), changeListener)
 
         deviceConfig.setProperty(NAMESPACE, "flag_1", "1", false)
@@ -65,7 +65,7 @@
     @Test
     fun testChange_ignoresListenersDuringTest() {
         val serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor, true)
-        val flag = ReleasedFlag(1, "1", "   test")
+        val flag = ReleasedFlag("1", "   test")
         serverFlagReader.listenForChanges(listOf(flag), changeListener)
 
         deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index c9ee1e8..6aa5a00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -67,6 +67,7 @@
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -128,6 +129,7 @@
     @Mock private UserContextProvider mUserContextProvider;
     @Mock private VibratorHelper mVibratorHelper;
     @Mock private CentralSurfaces mCentralSurfaces;
+    @Mock private ShadeController mShadeController;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private DialogLaunchAnimator mDialogLaunchAnimator;
     @Mock private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
@@ -177,6 +179,7 @@
                 mHandler,
                 mPackageManager,
                 Optional.of(mCentralSurfaces),
+                mShadeController,
                 mKeyguardUpdateMonitor,
                 mDialogLaunchAnimator);
         mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
@@ -317,7 +320,7 @@
         MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0);
         gestureListener.onFling(start, end, 0, 1000);
         verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
-        verify(mCentralSurfaces).animateExpandSettingsPanel(null);
+        verify(mShadeController).animateExpandQs();
     }
 
     @Test
@@ -341,7 +344,7 @@
         MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0);
         gestureListener.onFling(start, end, 0, 1000);
         verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
-        verify(mCentralSurfaces).animateExpandNotificationsPanel();
+        verify(mShadeController).animateExpandShade();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
new file mode 100644
index 0000000..632d149
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyevent/domain/interactor/KeyEventInteractorTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyevent.domain.interactor
+
+import android.view.KeyEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardKeyEventInteractor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+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.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyEventInteractorTest : SysuiTestCase() {
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    private lateinit var keyguardInteractorWithDependencies:
+        KeyguardInteractorFactory.WithDependencies
+    @Mock private lateinit var keyguardKeyEventInteractor: KeyguardKeyEventInteractor
+    @Mock private lateinit var backActionInteractor: BackActionInteractor
+
+    private lateinit var underTest: KeyEventInteractor
+
+    @Before
+    fun setup() {
+        keyguardInteractorWithDependencies = KeyguardInteractorFactory.create()
+        underTest =
+            KeyEventInteractor(
+                backActionInteractor,
+                keyguardKeyEventInteractor,
+            )
+    }
+
+    @Test
+    fun dispatchBackKey_notHandledByKeyguardKeyEventInteractor_handledByBackActionInteractor() {
+        val backKeyEventActionDown = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK)
+        val backKeyEventActionUp = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)
+
+        // GIVEN back key ACTION_DOWN and ACTION_UP aren't handled by the keyguardKeyEventInteractor
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionDown))
+            .thenReturn(false)
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(backKeyEventActionUp))
+            .thenReturn(false)
+
+        // WHEN back key event ACTION_DOWN, the event is handled even though back isn't requested
+        assertThat(underTest.dispatchKeyEvent(backKeyEventActionDown)).isTrue()
+        // THEN back event isn't handled on ACTION_DOWN
+        verify(backActionInteractor, never()).onBackRequested()
+
+        // WHEN back key event ACTION_UP
+        assertThat(underTest.dispatchKeyEvent(backKeyEventActionUp)).isTrue()
+        // THEN back event is handled on ACTION_UP
+        verify(backActionInteractor).onBackRequested()
+    }
+
+    @Test
+    fun dispatchKeyEvent_isNotHandledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(false)
+        assertThat(underTest.dispatchKeyEvent(keyEvent)).isFalse()
+    }
+
+    @Test
+    fun dispatchKeyEvent_handledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEvent(eq(keyEvent))).thenReturn(true)
+        assertThat(underTest.dispatchKeyEvent(keyEvent)).isTrue()
+    }
+
+    @Test
+    fun interceptMediaKey_isNotHandledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(false)
+        assertThat(underTest.interceptMediaKey(keyEvent)).isFalse()
+    }
+
+    @Test
+    fun interceptMediaKey_handledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.interceptMediaKey(eq(keyEvent))).thenReturn(true)
+        assertThat(underTest.interceptMediaKey(keyEvent)).isTrue()
+    }
+
+    @Test
+    fun dispatchKeyEventPreIme_isNotHandledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(false)
+        assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isFalse()
+    }
+
+    @Test
+    fun dispatchKeyEventPreIme_handledByKeyguardKeyEventInteractor() {
+        val keyEvent =
+            KeyEvent(
+                KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_SPACE,
+            )
+        whenever(keyguardKeyEventInteractor.dispatchKeyEventPreIme(eq(keyEvent))).thenReturn(true)
+        assertThat(underTest.dispatchKeyEventPreIme(keyEvent)).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index cdc99af3..e73d580 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -34,6 +34,8 @@
 import com.android.systemui.SystemUIAppComponentFactoryBase
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.dock.DockManagerFake
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -43,7 +45,6 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -174,7 +175,6 @@
             )
         val featureFlags =
             FakeFeatureFlags().apply {
-                set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
                 set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true)
                 set(Flags.REVAMPED_WALLPAPER_UI, true)
                 set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true)
@@ -188,8 +188,8 @@
                         commandQueue = commandQueue,
                         featureFlags = featureFlags,
                         bouncerRepository = FakeKeyguardBouncerRepository(),
+                        configurationRepository = FakeConfigurationRepository(),
                     ),
-                registry = mock(),
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 729a1cc..ce8028c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -19,6 +19,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -31,6 +32,7 @@
 import android.media.MediaMetadata;
 import android.media.session.PlaybackState;
 import android.net.Uri;
+import android.os.Handler;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -166,6 +168,7 @@
 
     @Test
     public void updatesClock() {
+        clearInvocations(mContentResolver);
         mProvider.mKeyguardUpdateMonitorCallback.onTimeChanged();
         TestableLooper.get(this).processAllMessages();
         verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
@@ -217,11 +220,13 @@
         reset(mContentResolver);
         mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
                 PlaybackState.STATE_PLAYING);
+        TestableLooper.get(this).processAllMessages();
         verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
 
         // Hides after waking up
         reset(mContentResolver);
         mProvider.onDozingChanged(false);
+        TestableLooper.get(this).processAllMessages();
         verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
     }
 
@@ -231,6 +236,7 @@
         mProvider.onPrimaryMetadataOrStateChanged(mock(MediaMetadata.class),
                 PlaybackState.STATE_PLAYING);
         reset(mContentResolver);
+        TestableLooper.get(this).processAllMessages();
         // Show media when dozing
         mProvider.onDozingChanged(true);
         verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null));
@@ -272,6 +278,8 @@
             mMediaManager = KeyguardSliceProviderTest.this.mNotificationMediaManager;
             mKeyguardUpdateMonitor = KeyguardSliceProviderTest.this.mKeyguardUpdateMonitor;
             mUserTracker = KeyguardSliceProviderTest.this.mUserTracker;
+            mBgHandler =
+                    new Handler(TestableLooper.get(KeyguardSliceProviderTest.this).getLooper());
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 2230841..9a2936e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -385,8 +385,6 @@
         // We expect that we've set the surface behind to alpha = 0f since we're not interactive.
         assertEquals(0f, params.alpha)
         assertTrue(params.matrix.isIdentity)
-        assertEquals("Wallpaper surface was expected to have opacity 0",
-                0f, captorWp.getLastValue().alpha)
 
         verifyNoMoreInteractions(surfaceTransactionApplier)
     }
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 cb2daca..b05de48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -16,12 +16,17 @@
 
 package com.android.systemui.keyguard;
 
+import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
+import static android.provider.Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_TIMEOUT;
 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 com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION;
+import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT;
 import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE;
 import static com.android.systemui.keyguard.KeyguardViewMediator.SYS_BOOT_REASON_PROP;
 
@@ -41,9 +46,12 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.AlarmManager;
 import android.app.IActivityManager;
+import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
+import android.content.Context;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.telephony.TelephonyManager;
@@ -65,6 +73,7 @@
 import com.android.keyguard.KeyguardSecurityView;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TestScopeProvider;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.SysuiTestCase;
@@ -102,9 +111,14 @@
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.DeviceConfigProxyFake;
 import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.settings.SystemSettings;
 import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -115,6 +129,7 @@
 
 import kotlinx.coroutines.CoroutineDispatcher;
 import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.test.TestScope;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -122,6 +137,9 @@
 public class KeyguardViewMediatorTest extends SysuiTestCase {
     private KeyguardViewMediator mViewMediator;
 
+    private final TestScope mTestScope = TestScopeProvider.getTestScope();
+    private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
+
     private @Mock UserTracker mUserTracker;
     private @Mock DevicePolicyManager mDevicePolicyManager;
     private @Mock LockPatternUtils mLockPatternUtils;
@@ -169,21 +187,29 @@
     private @Mock CentralSurfaces mCentralSurfaces;
     private @Mock UiEventLogger mUiEventLogger;
     private @Mock SessionTracker mSessionTracker;
-    private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
+    private @Mock SystemSettings mSystemSettings;
+    private @Mock SecureSettings mSecureSettings;
+    private @Mock AlarmManager mAlarmManager;
+    private FakeSystemClock mSystemClock;
+    private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
+
     private @Mock CoroutineDispatcher mDispatcher;
     private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
+    private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
 
     private FakeFeatureFlags mFeatureFlags;
+    private int mInitialUserId;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mFalsingCollector = new FalsingCollectorFake();
-
+        mSystemClock = new FakeSystemClock();
         when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
         when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
         when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
         when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
+        mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
         final ViewRootImpl testViewRoot = mock(ViewRootImpl.class);
         when(testViewRoot.getView()).thenReturn(mock(View.class));
         when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot);
@@ -198,11 +224,17 @@
                 mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
                 mShadeWindowLogger);
         mFeatureFlags = new FakeFeatureFlags();
-
+        mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
 
         DejankUtils.setImmediate(true);
 
         createAndStartViewMediator();
+        mInitialUserId = KeyguardUpdateMonitor.getCurrentUser();
+    }
+
+    @After
+    public void teardown() {
+        KeyguardUpdateMonitor.setCurrentUser(mInitialUserId);
     }
 
     @Test
@@ -301,6 +333,44 @@
 
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void wakeupFromDreamingWhenKeyguardHides() {
+        mViewMediator.onSystemReady();
+        TestableLooper.get(this).processAllMessages();
+
+        when(mPowerManager.isInteractive()).thenReturn(true);
+
+        // Given device is dreaming
+        when(mUpdateMonitor.isDreaming()).thenReturn(true);
+
+        // When keyguard is going away
+        mKeyguardStateController.notifyKeyguardGoingAway(true);
+
+        // And keyguard is disabled which will call #handleHide
+        mViewMediator.setKeyguardEnabled(false);
+        TestableLooper.get(this).processAllMessages();
+
+        mViewMediator.mViewMediatorCallback.keyguardDonePending(true,
+                mUpdateMonitor.getCurrentUser());
+        mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
+        final ArgumentCaptor<Runnable> animationRunnableCaptor =
+                ArgumentCaptor.forClass(Runnable.class);
+        verify(mStatusBarKeyguardViewManager).startPreHideAnimation(
+                animationRunnableCaptor.capture());
+
+        when(mStatusBarStateController.isDreaming()).thenReturn(true);
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+        animationRunnableCaptor.getValue().run();
+
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+        mViewMediator.mViewMediatorCallback.keyguardGone();
+
+        // Then dream should wake up
+        verify(mPowerManager).wakeUp(anyLong(), anyInt(),
+                eq("com.android.systemui:UNLOCK_DREAMING"));
+    }
+
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void restoreBouncerWhenSimLockedAndKeyguardIsGoingAway() {
         // When showing and provisioned
         mViewMediator.onSystemReady();
@@ -443,6 +513,49 @@
     }
 
     @Test
+    public void lockAfterScreenTimeoutUsesValueFromSettings() {
+        int currentUserId = 99;
+        int userSpecificTimeout = 5999;
+        KeyguardUpdateMonitor.setCurrentUser(currentUserId);
+
+        when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
+        when(mDevicePolicyManager.getMaximumTimeToLock(null, currentUserId)).thenReturn(0L);
+        when(mSecureSettings.getIntForUser(LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+                KEYGUARD_LOCK_AFTER_DELAY_DEFAULT, currentUserId)).thenReturn(userSpecificTimeout);
+        mSystemClock.setElapsedRealtime(0L);
+        ArgumentCaptor<PendingIntent> pendingIntent = ArgumentCaptor.forClass(PendingIntent.class);
+
+        mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_TIMEOUT);
+
+        verify(mAlarmManager).setExactAndAllowWhileIdle(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+                eq(Long.valueOf(userSpecificTimeout)), pendingIntent.capture());
+        assertEquals(DELAYED_KEYGUARD_ACTION, pendingIntent.getValue().getIntent().getAction());
+    }
+
+    @Test
+    public void lockAfterSpecifiedAfterDreamStarted() {
+        int currentUserId = 99;
+        int userSpecificTimeout = 5999;
+        KeyguardUpdateMonitor.setCurrentUser(currentUserId);
+
+        // set mDeviceInteractive to true
+        mViewMediator.onStartedWakingUp(WAKE_REASON_WAKE_MOTION, false);
+        mFeatureFlags.set(Flags.LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING, false);
+        when(mLockPatternUtils.isSecure(currentUserId)).thenReturn(true);
+        when(mDevicePolicyManager.getMaximumTimeToLock(null, currentUserId)).thenReturn(0L);
+        when(mSecureSettings.getIntForUser(LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+                KEYGUARD_LOCK_AFTER_DELAY_DEFAULT, currentUserId)).thenReturn(userSpecificTimeout);
+        mSystemClock.setElapsedRealtime(0L);
+        ArgumentCaptor<PendingIntent> pendingIntent = ArgumentCaptor.forClass(PendingIntent.class);
+
+        mViewMediator.onDreamingStarted();
+
+        verify(mAlarmManager).setExactAndAllowWhileIdle(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+                eq(Long.valueOf(userSpecificTimeout)), pendingIntent.capture());
+        assertEquals(DELAYED_KEYGUARD_ACTION, pendingIntent.getValue().getIntent().getAction());
+    }
+
+    @Test
     public void testHideSurfaceBehindKeyguardMarksKeyguardNotGoingAway() {
         mViewMediator.hideSurfaceBehindKeyguard();
 
@@ -797,6 +910,11 @@
         assertTrue(mViewMediator.isShowingAndNotOccluded());
     }
 
+    @Test
+    public void testBouncerSwipeDown() {
+        mViewMediator.getViewMediatorCallback().onBouncerSwipeDown();
+        verify(mStatusBarKeyguardViewManager).reset(true);
+    }
     private void createAndStartViewMediator() {
         mViewMediator = new KeyguardViewMediator(
                 mContext,
@@ -827,14 +945,20 @@
                 mKeyguardTransitions,
                 mInteractionJankMonitor,
                 mDreamOverlayStateController,
+                mJavaAdapter,
+                mWallpaperRepository,
                 () -> mShadeController,
                 () -> mNotificationShadeWindowController,
                 () -> mActivityLaunchAnimator,
                 () -> mScrimController,
                 mFeatureFlags,
+                mSecureSettings,
+                mSystemSettings,
+                mSystemClock,
                 mDispatcher,
                 () -> mDreamingToLockscreenTransitionViewModel,
-                mSystemPropertiesHelper);
+                mSystemPropertiesHelper,
+                () -> mock(WindowManagerLockscreenVisibilityManager.class));
         mViewMediator.start();
 
         mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index 6447368..05d6b99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -7,17 +7,16 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.util.mockito.any
 import com.android.systemui.utils.GlobalWindowManager
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -60,20 +59,20 @@
         keyguardRepository.setDozeAmount(0f)
         keyguardRepository.setKeyguardGoingAway(false)
 
-        val keyguardInteractor =
-            KeyguardInteractor(
-                keyguardRepository,
-                FakeCommandQueue(),
-                featureFlags,
-                FakeKeyguardBouncerRepository()
+        val withDeps =
+            KeyguardInteractorFactory.create(
+                repository = keyguardRepository,
+                featureFlags = featureFlags,
             )
+        val keyguardInteractor = withDeps.keyguardInteractor
         resourceTrimmer =
             ResourceTrimmer(
                 keyguardInteractor,
-                KeyguardTransitionInteractor(
-                    keyguardTransitionRepository,
-                    testScope.backgroundScope
-                ),
+                KeyguardTransitionInteractorFactory.create(
+                        scope = TestScope().backgroundScope,
+                        repository = keyguardTransitionRepository,
+                    )
+                    .keyguardTransitionInteractor,
                 globalWindowManager,
                 testScope.backgroundScope,
                 testDispatcher,
@@ -227,6 +226,9 @@
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
             )
-            verifyNoMoreInteractions(globalWindowManager)
+            // Memory hidden should still be called.
+            verify(globalWindowManager, times(1))
+                .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+            verify(globalWindowManager, times(0)).trimCaches(any())
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactoryTest.kt
deleted file mode 100644
index 0ef77e8..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactoryTest.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.bouncer.data.factory
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Pattern
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
-import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.StringSubject
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@Ignore("b/236891644")
-class BouncerMessageFactoryTest : SysuiTestCase() {
-    private lateinit var underTest: BouncerMessageFactory
-
-    @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
-
-    @Mock private lateinit var securityModel: KeyguardSecurityModel
-
-    private lateinit var testScope: TestScope
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        testScope = TestScope()
-        underTest = BouncerMessageFactory(updateMonitor, securityModel)
-    }
-
-    @Test
-    fun bouncerMessages_choosesTheRightMessage_basedOnSecurityModeAndFpAllowedInBouncer() =
-        testScope.runTest {
-            primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAllowedInBouncer = false)
-                .isEqualTo("Enter PIN")
-            primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAllowedInBouncer = true)
-                .isEqualTo("Unlock with PIN or fingerprint")
-
-            primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAllowedInBouncer = false)
-                .isEqualTo("Enter password")
-            primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAllowedInBouncer = true)
-                .isEqualTo("Unlock with password or fingerprint")
-
-            primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAllowedInBouncer = false)
-                .isEqualTo("Draw pattern")
-            primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAllowedInBouncer = true)
-                .isEqualTo("Unlock with pattern or fingerprint")
-        }
-
-    @Test
-    fun bouncerMessages_setsPrimaryAndSecondaryMessage_basedOnSecurityModeAndFpAllowedInBouncer() =
-        testScope.runTest {
-            primaryMessage(
-                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-                    mode = PIN,
-                    fpAllowedInBouncer = true
-                )
-                .isEqualTo("Wrong PIN. Try again.")
-            secondaryMessage(
-                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-                    mode = PIN,
-                    fpAllowedInBouncer = true
-                )
-                .isEqualTo("Or unlock with fingerprint")
-
-            primaryMessage(
-                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-                    mode = Password,
-                    fpAllowedInBouncer = true
-                )
-                .isEqualTo("Wrong password. Try again.")
-            secondaryMessage(
-                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-                    mode = Password,
-                    fpAllowedInBouncer = true
-                )
-                .isEqualTo("Or unlock with fingerprint")
-
-            primaryMessage(
-                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-                    mode = Pattern,
-                    fpAllowedInBouncer = true
-                )
-                .isEqualTo("Wrong pattern. Try again.")
-            secondaryMessage(
-                    PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
-                    mode = Pattern,
-                    fpAllowedInBouncer = true
-                )
-                .isEqualTo("Or unlock with fingerprint")
-        }
-
-    private fun primaryMessage(
-        reason: Int,
-        mode: KeyguardSecurityModel.SecurityMode,
-        fpAllowedInBouncer: Boolean
-    ): StringSubject {
-        return assertThat(
-            context.resources.getString(
-                bouncerMessageModel(mode, fpAllowedInBouncer, reason)!!.message!!.messageResId!!
-            )
-        )!!
-    }
-
-    private fun secondaryMessage(
-        reason: Int,
-        mode: KeyguardSecurityModel.SecurityMode,
-        fpAllowedInBouncer: Boolean
-    ): StringSubject {
-        return assertThat(
-            context.resources.getString(
-                bouncerMessageModel(mode, fpAllowedInBouncer, reason)!!
-                    .secondaryMessage!!
-                    .messageResId!!
-            )
-        )!!
-    }
-
-    private fun bouncerMessageModel(
-        mode: KeyguardSecurityModel.SecurityMode,
-        fpAllowedInBouncer: Boolean,
-        reason: Int
-    ): BouncerMessageModel? {
-        whenever(securityModel.getSecurityMode(0)).thenReturn(mode)
-        whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(fpAllowedInBouncer)
-
-        return underTest.createFromPromptReason(reason, 0)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepositoryTest.kt
deleted file mode 100644
index a729de5..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepositoryTest.kt
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.bouncer.data.repository
-
-import android.content.pm.UserInfo
-import android.hardware.biometrics.BiometricSourceType
-import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.R
-import com.android.systemui.R.string.keyguard_enter_pin
-import com.android.systemui.R.string.kg_prompt_after_dpm_lock
-import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pin
-import com.android.systemui.R.string.kg_prompt_auth_timeout
-import com.android.systemui.R.string.kg_prompt_pin_auth_timeout
-import com.android.systemui.R.string.kg_prompt_reason_restart_pin
-import com.android.systemui.R.string.kg_prompt_unattended_update
-import com.android.systemui.R.string.kg_trust_agent_disabled
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import com.android.systemui.keyguard.bouncer.shared.model.Message
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.shared.model.AuthenticationFlags
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Ignore
-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.verify
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidJUnit4::class)
-@Ignore("b/236891644")
-class BouncerMessageRepositoryTest : SysuiTestCase() {
-
-    @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
-    @Mock private lateinit var securityModel: KeyguardSecurityModel
-    @Captor
-    private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
-
-    private lateinit var underTest: BouncerMessageRepository
-    private lateinit var trustRepository: FakeTrustRepository
-    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
-    private lateinit var userRepository: FakeUserRepository
-    private lateinit var fingerprintRepository: FakeDeviceEntryFingerprintAuthRepository
-    private lateinit var testScope: TestScope
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        trustRepository = FakeTrustRepository()
-        biometricSettingsRepository = FakeBiometricSettingsRepository()
-        userRepository = FakeUserRepository()
-        userRepository.setUserInfos(listOf(PRIMARY_USER))
-        fingerprintRepository = FakeDeviceEntryFingerprintAuthRepository()
-        testScope = TestScope()
-
-        whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(false)
-        whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
-        underTest =
-            BouncerMessageRepositoryImpl(
-                trustRepository = trustRepository,
-                biometricSettingsRepository = biometricSettingsRepository,
-                updateMonitor = updateMonitor,
-                bouncerMessageFactory = BouncerMessageFactory(updateMonitor, securityModel),
-                userRepository = userRepository,
-                fingerprintAuthRepository = fingerprintRepository
-            )
-    }
-
-    @Test
-    fun setCustomMessage_propagatesState() =
-        testScope.runTest {
-            underTest.setCustomMessage(message("not empty"))
-
-            val customMessage = collectLastValue(underTest.customMessage)
-
-            assertThat(customMessage()).isEqualTo(message("not empty"))
-        }
-
-    @Test
-    fun setFaceMessage_propagatesState() =
-        testScope.runTest {
-            underTest.setFaceAcquisitionMessage(message("not empty"))
-
-            val faceAcquisitionMessage = collectLastValue(underTest.faceAcquisitionMessage)
-
-            assertThat(faceAcquisitionMessage()).isEqualTo(message("not empty"))
-        }
-
-    @Test
-    fun setFpMessage_propagatesState() =
-        testScope.runTest {
-            underTest.setFingerprintAcquisitionMessage(message("not empty"))
-
-            val fpAcquisitionMsg = collectLastValue(underTest.fingerprintAcquisitionMessage)
-
-            assertThat(fpAcquisitionMsg()).isEqualTo(message("not empty"))
-        }
-
-    @Test
-    fun setPrimaryAuthMessage_propagatesState() =
-        testScope.runTest {
-            underTest.setPrimaryAuthMessage(message("not empty"))
-
-            val primaryAuthMessage = collectLastValue(underTest.primaryAuthMessage)
-
-            assertThat(primaryAuthMessage()).isEqualTo(message("not empty"))
-        }
-
-    @Test
-    fun biometricAuthMessage_propagatesBiometricAuthMessages() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(PRIMARY_USER)
-            val biometricAuthMessage = collectLastValue(underTest.biometricAuthMessage)
-            runCurrent()
-
-            verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
-
-            updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT)
-
-            assertThat(biometricAuthMessage())
-                .isEqualTo(message(R.string.kg_fp_not_recognized, R.string.kg_bio_try_again_or_pin))
-
-            updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FACE)
-
-            assertThat(biometricAuthMessage())
-                .isEqualTo(
-                    message(R.string.bouncer_face_not_recognized, R.string.kg_bio_try_again_or_pin)
-                )
-
-            updateMonitorCallback.value.onBiometricAcquired(BiometricSourceType.FACE, 0)
-
-            assertThat(biometricAuthMessage()).isNull()
-        }
-
-    @Test
-    fun onFaceLockout_propagatesState() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(PRIMARY_USER)
-            val lockoutMessage = collectLastValue(underTest.biometricLockedOutMessage)
-            runCurrent()
-            verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
-
-            whenever(updateMonitor.isFaceLockedOut).thenReturn(true)
-            updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
-
-            assertThat(lockoutMessage())
-                .isEqualTo(message(keyguard_enter_pin, R.string.kg_face_locked_out))
-
-            whenever(updateMonitor.isFaceLockedOut).thenReturn(false)
-            updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
-            assertThat(lockoutMessage()).isNull()
-        }
-
-    @Test
-    fun onFingerprintLockout_propagatesState() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(PRIMARY_USER)
-            val lockedOutMessage = collectLastValue(underTest.biometricLockedOutMessage)
-            runCurrent()
-
-            fingerprintRepository.setLockedOut(true)
-
-            assertThat(lockedOutMessage())
-                .isEqualTo(message(keyguard_enter_pin, R.string.kg_fp_locked_out))
-
-            fingerprintRepository.setLockedOut(false)
-            assertThat(lockedOutMessage()).isNull()
-        }
-
-    @Test
-    fun onAuthFlagsChanged_withTrustNotManagedAndNoBiometrics_isANoop() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(PRIMARY_USER)
-            trustRepository.setCurrentUserTrustManaged(false)
-            biometricSettingsRepository.setFaceEnrolled(false)
-            biometricSettingsRepository.setFingerprintEnrolled(false)
-
-            verifyMessagesForAuthFlag(
-                STRONG_AUTH_NOT_REQUIRED to null,
-                STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
-                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
-                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
-                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
-                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
-                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
-                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
-                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to null,
-                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
-                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
-            )
-        }
-
-    @Test
-    fun authFlagsChanges_withTrustManaged_providesDifferentMessages() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(PRIMARY_USER)
-            biometricSettingsRepository.setFaceEnrolled(false)
-            biometricSettingsRepository.setFingerprintEnrolled(false)
-
-            trustRepository.setCurrentUserTrustManaged(true)
-
-            verifyMessagesForAuthFlag(
-                STRONG_AUTH_NOT_REQUIRED to null,
-                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
-                STRONG_AUTH_REQUIRED_AFTER_BOOT to
-                    Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
-                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
-                    Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
-                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
-                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
-                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
-                    Pair(keyguard_enter_pin, kg_trust_agent_disabled),
-                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
-                    Pair(keyguard_enter_pin, kg_trust_agent_disabled),
-                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
-                    Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
-                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    Pair(keyguard_enter_pin, kg_prompt_unattended_update),
-                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
-                    Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
-            )
-        }
-
-    @Test
-    fun authFlagsChanges_withFaceEnrolled_providesDifferentMessages() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(PRIMARY_USER)
-            trustRepository.setCurrentUserTrustManaged(false)
-            biometricSettingsRepository.setFingerprintEnrolled(false)
-
-            biometricSettingsRepository.setIsFaceAuthEnabled(true)
-            biometricSettingsRepository.setFaceEnrolled(true)
-
-            verifyMessagesForAuthFlag(
-                STRONG_AUTH_NOT_REQUIRED to null,
-                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
-                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
-                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
-                STRONG_AUTH_REQUIRED_AFTER_BOOT to
-                    Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
-                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
-                    Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
-                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
-                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
-                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
-                    Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
-                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    Pair(keyguard_enter_pin, kg_prompt_unattended_update),
-                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
-                    Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
-            )
-        }
-
-    @Test
-    fun authFlagsChanges_withFingerprintEnrolled_providesDifferentMessages() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(PRIMARY_USER)
-            trustRepository.setCurrentUserTrustManaged(false)
-            biometricSettingsRepository.setIsFaceAuthEnabled(false)
-            biometricSettingsRepository.setFaceEnrolled(false)
-
-            biometricSettingsRepository.setFingerprintEnrolled(true)
-            biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
-
-            verifyMessagesForAuthFlag(
-                STRONG_AUTH_NOT_REQUIRED to null,
-                STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
-                SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
-                SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
-                STRONG_AUTH_REQUIRED_AFTER_BOOT to
-                    Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
-                STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
-                    Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
-                STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
-                    Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
-                STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
-                    Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
-                STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
-                    Pair(keyguard_enter_pin, kg_prompt_unattended_update),
-                STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
-                    Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
-            )
-        }
-
-    private fun TestScope.verifyMessagesForAuthFlag(
-        vararg authFlagToExpectedMessages: Pair<Int, Pair<Int, Int>?>
-    ) {
-        val authFlagsMessage = collectLastValue(underTest.authFlagsMessage)
-
-        authFlagToExpectedMessages.forEach { (flag, messagePair) ->
-            biometricSettingsRepository.setAuthenticationFlags(
-                AuthenticationFlags(PRIMARY_USER_ID, flag)
-            )
-
-            assertThat(authFlagsMessage())
-                .isEqualTo(messagePair?.let { message(it.first, it.second) })
-        }
-    }
-
-    private fun message(primaryResId: Int, secondaryResId: Int): BouncerMessageModel {
-        return BouncerMessageModel(
-            message = Message(messageResId = primaryResId),
-            secondaryMessage = Message(messageResId = secondaryResId)
-        )
-    }
-    private fun message(value: String): BouncerMessageModel {
-        return BouncerMessageModel(message = Message(message = value))
-    }
-
-    companion object {
-        private const val PRIMARY_USER_ID = 0
-        private val PRIMARY_USER =
-            UserInfo(
-                /* id= */ PRIMARY_USER_ID,
-                /* name= */ "primary user",
-                /* flags= */ UserInfo.FLAG_PRIMARY
-            )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
deleted file mode 100644
index 3a87680..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.bouncer.domain.interactor
-
-import android.content.pm.UserInfo
-import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.R.string.keyguard_enter_pin
-import com.android.systemui.R.string.kg_too_many_failed_attempts_countdown
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.FlowValue
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import com.android.systemui.keyguard.bouncer.shared.model.Message
-import com.android.systemui.keyguard.data.repository.FakeBouncerMessageRepository
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.mockito.KotlinArgumentCaptor
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidJUnit4::class)
-@Ignore("b/236891644")
-class BouncerMessageInteractorTest : SysuiTestCase() {
-
-    @Mock private lateinit var securityModel: KeyguardSecurityModel
-    @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
-    @Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
-    private lateinit var countDownTimerCallback: KotlinArgumentCaptor<CountDownTimerCallback>
-    private lateinit var underTest: BouncerMessageInteractor
-    private lateinit var repository: FakeBouncerMessageRepository
-    private lateinit var userRepository: FakeUserRepository
-    private lateinit var testScope: TestScope
-    private lateinit var bouncerMessage: FlowValue<BouncerMessageModel?>
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        repository = FakeBouncerMessageRepository()
-        userRepository = FakeUserRepository()
-        userRepository.setUserInfos(listOf(PRIMARY_USER))
-        testScope = TestScope()
-        countDownTimerCallback = KotlinArgumentCaptor(CountDownTimerCallback::class.java)
-
-        allowTestableLooperAsMainThread()
-        whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
-        whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(false)
-    }
-
-    suspend fun TestScope.init() {
-        userRepository.setSelectedUserInfo(PRIMARY_USER)
-        val featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
-        underTest =
-            BouncerMessageInteractor(
-                repository = repository,
-                factory = BouncerMessageFactory(updateMonitor, securityModel),
-                userRepository = userRepository,
-                countDownTimerUtil = countDownTimerUtil,
-                featureFlags = featureFlags
-            )
-        bouncerMessage = collectLastValue(underTest.bouncerMessage)
-    }
-
-    @Test
-    fun onIncorrectSecurityInput_setsTheBouncerModelInTheRepository() =
-        testScope.runTest {
-            init()
-            underTest.onPrimaryAuthIncorrectAttempt()
-
-            assertThat(repository.primaryAuthMessage).isNotNull()
-            assertThat(
-                    context.resources.getString(
-                        repository.primaryAuthMessage.value!!.message!!.messageResId!!
-                    )
-                )
-                .isEqualTo("Wrong PIN. Try again.")
-        }
-
-    @Test
-    fun onUserStartsPrimaryAuthInput_clearsAllSetBouncerMessages() =
-        testScope.runTest {
-            init()
-            repository.setCustomMessage(message("not empty"))
-            repository.setFaceAcquisitionMessage(message("not empty"))
-            repository.setFingerprintAcquisitionMessage(message("not empty"))
-            repository.setPrimaryAuthMessage(message("not empty"))
-
-            underTest.onPrimaryBouncerUserInput()
-
-            assertThat(repository.customMessage.value).isNull()
-            assertThat(repository.faceAcquisitionMessage.value).isNull()
-            assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
-            assertThat(repository.primaryAuthMessage.value).isNull()
-        }
-
-    @Test
-    fun onBouncerBeingHidden_clearsAllSetBouncerMessages() =
-        testScope.runTest {
-            init()
-            repository.setCustomMessage(message("not empty"))
-            repository.setFaceAcquisitionMessage(message("not empty"))
-            repository.setFingerprintAcquisitionMessage(message("not empty"))
-            repository.setPrimaryAuthMessage(message("not empty"))
-
-            underTest.onBouncerBeingHidden()
-
-            assertThat(repository.customMessage.value).isNull()
-            assertThat(repository.faceAcquisitionMessage.value).isNull()
-            assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
-            assertThat(repository.primaryAuthMessage.value).isNull()
-        }
-
-    @Test
-    fun setCustomMessage_setsRepositoryValue() =
-        testScope.runTest {
-            init()
-
-            underTest.setCustomMessage("not empty")
-
-            assertThat(repository.customMessage.value)
-                .isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
-
-            underTest.setCustomMessage(null)
-            assertThat(repository.customMessage.value).isNull()
-        }
-
-    @Test
-    fun setFaceMessage_setsRepositoryValue() =
-        testScope.runTest {
-            init()
-
-            underTest.setFaceAcquisitionMessage("not empty")
-
-            assertThat(repository.faceAcquisitionMessage.value)
-                .isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
-
-            underTest.setFaceAcquisitionMessage(null)
-            assertThat(repository.faceAcquisitionMessage.value).isNull()
-        }
-
-    @Test
-    fun setFingerprintMessage_setsRepositoryValue() =
-        testScope.runTest {
-            init()
-
-            underTest.setFingerprintAcquisitionMessage("not empty")
-
-            assertThat(repository.fingerprintAcquisitionMessage.value)
-                .isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
-
-            underTest.setFingerprintAcquisitionMessage(null)
-            assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
-        }
-
-    @Test
-    fun onPrimaryAuthLockout_startsTimerForSpecifiedNumberOfSeconds() =
-        testScope.runTest {
-            init()
-
-            underTest.onPrimaryAuthLockedOut(3)
-
-            verify(countDownTimerUtil)
-                .startNewTimer(eq(3000L), eq(1000L), countDownTimerCallback.capture())
-
-            countDownTimerCallback.value.onTick(2000L)
-
-            val primaryMessage = repository.primaryAuthMessage.value!!.message!!
-            assertThat(primaryMessage.messageResId!!)
-                .isEqualTo(kg_too_many_failed_attempts_countdown)
-            assertThat(primaryMessage.formatterArgs).isEqualTo(mapOf(Pair("count", 2)))
-        }
-
-    @Test
-    fun onPrimaryAuthLockout_timerComplete_resetsRepositoryMessages() =
-        testScope.runTest {
-            init()
-            repository.setCustomMessage(message("not empty"))
-            repository.setFaceAcquisitionMessage(message("not empty"))
-            repository.setFingerprintAcquisitionMessage(message("not empty"))
-            repository.setPrimaryAuthMessage(message("not empty"))
-
-            underTest.onPrimaryAuthLockedOut(3)
-
-            verify(countDownTimerUtil)
-                .startNewTimer(eq(3000L), eq(1000L), countDownTimerCallback.capture())
-
-            countDownTimerCallback.value.onFinish()
-
-            assertThat(repository.customMessage.value).isNull()
-            assertThat(repository.faceAcquisitionMessage.value).isNull()
-            assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
-            assertThat(repository.primaryAuthMessage.value).isNull()
-        }
-
-    @Test
-    fun bouncerMessage_hasPriorityOrderOfMessages() =
-        testScope.runTest {
-            init()
-            repository.setBiometricAuthMessage(message("biometric message"))
-            repository.setFaceAcquisitionMessage(message("face acquisition message"))
-            repository.setFingerprintAcquisitionMessage(message("fingerprint acquisition message"))
-            repository.setPrimaryAuthMessage(message("primary auth message"))
-            repository.setAuthFlagsMessage(message("auth flags message"))
-            repository.setBiometricLockedOutMessage(message("biometrics locked out"))
-            repository.setCustomMessage(message("custom message"))
-
-            assertThat(bouncerMessage()).isEqualTo(message("primary auth message"))
-
-            repository.setPrimaryAuthMessage(null)
-
-            assertThat(bouncerMessage()).isEqualTo(message("biometric message"))
-
-            repository.setBiometricAuthMessage(null)
-
-            assertThat(bouncerMessage()).isEqualTo(message("fingerprint acquisition message"))
-
-            repository.setFingerprintAcquisitionMessage(null)
-
-            assertThat(bouncerMessage()).isEqualTo(message("face acquisition message"))
-
-            repository.setFaceAcquisitionMessage(null)
-
-            assertThat(bouncerMessage()).isEqualTo(message("custom message"))
-
-            repository.setCustomMessage(null)
-
-            assertThat(bouncerMessage()).isEqualTo(message("auth flags message"))
-
-            repository.setAuthFlagsMessage(null)
-
-            assertThat(bouncerMessage()).isEqualTo(message("biometrics locked out"))
-
-            repository.setBiometricLockedOutMessage(null)
-
-            // sets the default message if everything else is null
-            assertThat(bouncerMessage()!!.message!!.messageResId).isEqualTo(keyguard_enter_pin)
-        }
-
-    private fun message(value: String): BouncerMessageModel {
-        return BouncerMessageModel(message = Message(message = value))
-    }
-
-    companion object {
-        private const val PRIMARY_USER_ID = 0
-        private val PRIMARY_USER =
-            UserInfo(
-                /* id= */ PRIMARY_USER_ID,
-                /* name= */ "primary user",
-                /* flags= */ UserInfo.FLAG_PRIMARY
-            )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
index b2528c5..edaff44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.R
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.backup.BackupHelper
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.util.FakeSharedPreferences
@@ -360,7 +361,10 @@
             }
         clearInvocations(userFileManager)
 
-        fakeBroadcastDispatcher.registeredReceivers.firstOrNull()?.onReceive(context, Intent())
+        fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+            context,
+            Intent(BackupHelper.ACTION_RESTORE_FINISHED),
+        )
 
         verify(userFileManager, atLeastOnce()).getSharedPreferences(anyString(), anyInt(), anyInt())
         job.cancel()
@@ -374,12 +378,13 @@
             arrayOf("leftTest:testShortcut1", "rightTest:testShortcut2")
         )
 
-        assertThat(underTest.getSelections()).isEqualTo(
-            mapOf(
-                "leftTest" to listOf("testShortcut1"),
-                "rightTest" to listOf("testShortcut2"),
+        assertThat(underTest.getSelections())
+            .isEqualTo(
+                mapOf(
+                    "leftTest" to listOf("testShortcut1"),
+                    "rightTest" to listOf("testShortcut2"),
+                )
             )
-        )
     }
 
     private fun assertSelections(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
index f243d7b..df1833e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
@@ -24,8 +24,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
@@ -59,8 +57,6 @@
 class MuteQuickAffordanceCoreStartableTest : SysuiTestCase() {
 
     @Mock
-    private lateinit var featureFlags: FeatureFlags
-    @Mock
     private lateinit var userTracker: UserTracker
     @Mock
     private lateinit var ringerModeTracker: RingerModeTracker
@@ -78,8 +74,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        whenever(featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)).thenReturn(true)
-
         val config: KeyguardQuickAffordanceConfig = mock()
         whenever(config.key).thenReturn(BuiltInKeyguardQuickAffordanceKeys.MUTE)
 
@@ -90,7 +84,6 @@
         testScope = TestScope(testDispatcher)
 
         underTest = MuteQuickAffordanceCoreStartable(
-            featureFlags,
             userTracker,
             ringerModeTracker,
             userFileManager,
@@ -101,20 +94,7 @@
     }
 
     @Test
-    fun featureFlagIsOFF_doNothingWithKeyguardQuickAffordanceRepository() = testScope.runTest {
-        //given
-        whenever(featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)).thenReturn(false)
-
-        //when
-        underTest.start()
-
-        //then
-        verifyZeroInteractions(keyguardQuickAffordanceRepository)
-        coroutineContext.cancelChildren()
-    }
-
-    @Test
-    fun featureFlagIsON_callToKeyguardQuickAffordanceRepository() = testScope.runTest {
+    fun callToKeyguardQuickAffordanceRepository() = testScope.runTest {
         //given
         val ringerModeInternal = mock<MutableLiveData<Int>>()
         whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 7510373..9d983b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -137,27 +137,18 @@
     }
 
     @Test
-    fun getPickerScreenState_enabledIfConfiguredOnDevice_canOpenCamera() = runTest {
-        whenever(controller.isAvailableOnDevice).thenReturn(true)
-        whenever(controller.isAbleToOpenCameraApp).thenReturn(true)
+    fun getPickerScreenState_enabledIfConfiguredOnDevice_isEnabledForPickerState() = runTest {
+        whenever(controller.isAllowedOnLockScreen).thenReturn(true)
+        whenever(controller.isAbleToLaunchScannerActivity).thenReturn(true)
 
         assertThat(underTest.getPickerScreenState())
             .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default())
     }
 
     @Test
-    fun getPickerScreenState_disabledIfConfiguredOnDevice_cannotOpenCamera() = runTest {
-        whenever(controller.isAvailableOnDevice).thenReturn(true)
-        whenever(controller.isAbleToOpenCameraApp).thenReturn(false)
-
-        assertThat(underTest.getPickerScreenState())
-            .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
-    }
-
-    @Test
-    fun getPickerScreenState_unavailableIfNotConfiguredOnDevice() = runTest {
-        whenever(controller.isAvailableOnDevice).thenReturn(false)
-        whenever(controller.isAbleToOpenCameraApp).thenReturn(true)
+    fun getPickerScreenState_disabledIfConfiguredOnDevice_isDisabledForPickerState() = runTest {
+        whenever(controller.isAllowedOnLockScreen).thenReturn(true)
+        whenever(controller.isAbleToLaunchScannerActivity).thenReturn(false)
 
         assertThat(underTest.getPickerScreenState())
             .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index b925aeb..a6930d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -36,6 +36,11 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FaceSensorInfo
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.data.repository.BiometricType.FACE
@@ -63,6 +68,7 @@
 import org.mockito.ArgumentMatchers.isNull
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
@@ -89,6 +95,8 @@
         ArgumentCaptor<IBiometricEnabledOnKeyguardCallback.Stub>
     private lateinit var userRepository: FakeUserRepository
     private lateinit var devicePostureRepository: FakeDevicePostureRepository
+    private lateinit var facePropertyRepository: FakeFacePropertyRepository
+    private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
 
     private lateinit var testDispatcher: TestDispatcher
     private lateinit var testScope: TestScope
@@ -102,6 +110,8 @@
         testScope = TestScope(testDispatcher)
         userRepository = FakeUserRepository()
         devicePostureRepository = FakeDevicePostureRepository()
+        facePropertyRepository = FakeFacePropertyRepository()
+        fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
     }
 
     private suspend fun createBiometricSettingsRepository() {
@@ -120,61 +130,144 @@
                 biometricManager = biometricManager,
                 devicePostureRepository = devicePostureRepository,
                 dumpManager = dumpManager,
+                facePropertyRepository = facePropertyRepository,
+                fingerprintPropertyRepository = fingerprintPropertyRepository,
             )
         testScope.runCurrent()
+        fingerprintPropertyRepository.setProperties(
+            1,
+            SensorStrength.STRONG,
+            FingerprintSensorType.UDFPS_OPTICAL,
+            emptyMap()
+        )
         verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture())
+        verify(authController, atLeastOnce()).addCallback(authControllerCallback.capture())
     }
 
     @Test
     fun fingerprintEnrollmentChange() =
         testScope.runTest {
             createBiometricSettingsRepository()
-            val fingerprintEnrolled = collectLastValue(underTest.isFingerprintEnrolled)
+            val fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
             runCurrent()
 
-            verify(authController).addCallback(authControllerCallback.capture())
             whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(true)
             enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
-            assertThat(fingerprintEnrolled()).isTrue()
+            assertThat(fingerprintAllowed()).isTrue()
 
             whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(false)
             enrollmentChange(UNDER_DISPLAY_FINGERPRINT, ANOTHER_USER_ID, false)
-            assertThat(fingerprintEnrolled()).isTrue()
+            assertThat(fingerprintAllowed()).isTrue()
 
             enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, false)
-            assertThat(fingerprintEnrolled()).isFalse()
+            assertThat(fingerprintAllowed()).isFalse()
         }
 
     @Test
     fun strongBiometricAllowedChange() =
         testScope.runTest {
+            fingerprintIsEnrolled()
+            doNotDisableKeyguardAuthFeatures()
             createBiometricSettingsRepository()
-            val strongBiometricAllowed = collectLastValue(underTest.isStrongBiometricAllowed)
+
+            val strongBiometricAllowed by
+                collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
             runCurrent()
 
             onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
-            assertThat(strongBiometricAllowed()).isTrue()
+            assertThat(strongBiometricAllowed).isTrue()
 
             onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_BOOT, PRIMARY_USER_ID)
-            assertThat(strongBiometricAllowed()).isFalse()
+            assertThat(strongBiometricAllowed).isFalse()
         }
 
     @Test
     fun convenienceBiometricAllowedChange() =
         testScope.runTest {
+            overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, false)
+            deviceIsInPostureThatSupportsFaceAuth()
+            faceAuthIsEnrolled()
+            faceAuthIsNonStrongBiometric()
             createBiometricSettingsRepository()
-            val convenienceBiometricAllowed =
-                collectLastValue(underTest.isNonStrongBiometricAllowed)
-            runCurrent()
+            val convenienceFaceAuthAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
+            doNotDisableKeyguardAuthFeatures()
+            faceAuthIsEnabledByBiometricManager()
 
+            onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
             onNonStrongAuthChanged(true, PRIMARY_USER_ID)
-            assertThat(convenienceBiometricAllowed()).isTrue()
+            runCurrent()
+            assertThat(convenienceFaceAuthAllowed).isTrue()
 
             onNonStrongAuthChanged(false, ANOTHER_USER_ID)
-            assertThat(convenienceBiometricAllowed()).isTrue()
+            assertThat(convenienceFaceAuthAllowed).isTrue()
 
             onNonStrongAuthChanged(false, PRIMARY_USER_ID)
+            assertThat(convenienceFaceAuthAllowed).isFalse()
+            mContext.orCreateTestableResources.removeOverride(
+                com.android.internal.R.bool.config_strongAuthRequiredOnBoot
+            )
+        }
+
+    private fun faceAuthIsNonStrongBiometric() {
+        facePropertyRepository.setSensorInfo(FaceSensorInfo(1, SensorStrength.CONVENIENCE))
+    }
+
+    private fun faceAuthIsStrongBiometric() {
+        facePropertyRepository.setSensorInfo(FaceSensorInfo(1, SensorStrength.STRONG))
+    }
+
+    private fun deviceIsInPostureThatSupportsFaceAuth() {
+        overrideResource(
+            R.integer.config_face_auth_supported_posture,
+            DevicePostureController.DEVICE_POSTURE_FLIPPED
+        )
+        devicePostureRepository.setCurrentPosture(DevicePosture.FLIPPED)
+    }
+
+    @Test
+    fun whenStrongAuthRequiredAfterBoot_nonStrongBiometricNotAllowed() =
+        testScope.runTest {
+            overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, true)
+            createBiometricSettingsRepository()
+            faceAuthIsNonStrongBiometric()
+            faceAuthIsEnrolled()
+            doNotDisableKeyguardAuthFeatures()
+
+            val convenienceBiometricAllowed = collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
+            runCurrent()
+            onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+
             assertThat(convenienceBiometricAllowed()).isFalse()
+            mContext.orCreateTestableResources.removeOverride(
+                com.android.internal.R.bool.config_strongAuthRequiredOnBoot
+            )
+        }
+
+    @Test
+    fun whenStrongBiometricAuthIsNotAllowed_nonStrongBiometrics_alsoNotAllowed() =
+        testScope.runTest {
+            overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, false)
+            faceAuthIsNonStrongBiometric()
+            deviceIsInPostureThatSupportsFaceAuth()
+            faceAuthIsEnrolled()
+            createBiometricSettingsRepository()
+            doNotDisableKeyguardAuthFeatures()
+            faceAuthIsEnabledByBiometricManager()
+            runCurrent()
+
+            val convenienceBiometricAllowed by
+                collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
+
+            onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
+            onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+            runCurrent()
+            assertThat(convenienceBiometricAllowed).isTrue()
+
+            onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, PRIMARY_USER_ID)
+            assertThat(convenienceBiometricAllowed).isFalse()
+            mContext.orCreateTestableResources.removeOverride(
+                com.android.internal.R.bool.config_strongAuthRequiredOnBoot
+            )
         }
 
     private fun onStrongAuthChanged(flags: Int, userId: Int) {
@@ -190,9 +283,11 @@
     @Test
     fun fingerprintDisabledByDpmChange() =
         testScope.runTest {
+            fingerprintIsEnrolled(PRIMARY_USER_ID)
             createBiometricSettingsRepository()
+
             val fingerprintEnabledByDevicePolicy =
-                collectLastValue(underTest.isFingerprintEnabledByDevicePolicy)
+                collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
             runCurrent()
 
             whenever(devicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
@@ -205,43 +300,57 @@
             assertThat(fingerprintEnabledByDevicePolicy()).isTrue()
         }
 
+    private fun fingerprintIsEnrolled(userId: Int = PRIMARY_USER_ID) {
+        whenever(authController.isFingerprintEnrolled(userId)).thenReturn(true)
+    }
+
     @Test
     fun faceEnrollmentChangeIsPropagatedForTheCurrentUser() =
         testScope.runTest {
             createBiometricSettingsRepository()
+            faceAuthIsEnabledByBiometricManager()
+
+            doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
+
             runCurrent()
             clearInvocations(authController)
 
             whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false)
-            val faceEnrolled = collectLastValue(underTest.isFaceEnrolled)
+            val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
 
-            assertThat(faceEnrolled()).isFalse()
+            assertThat(faceAuthAllowed()).isFalse()
             verify(authController).addCallback(authControllerCallback.capture())
             enrollmentChange(REAR_FINGERPRINT, PRIMARY_USER_ID, true)
 
-            assertThat(faceEnrolled()).isFalse()
+            assertThat(faceAuthAllowed()).isFalse()
 
             enrollmentChange(SIDE_FINGERPRINT, PRIMARY_USER_ID, true)
 
-            assertThat(faceEnrolled()).isFalse()
+            assertThat(faceAuthAllowed()).isFalse()
 
             enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
 
-            assertThat(faceEnrolled()).isFalse()
+            assertThat(faceAuthAllowed()).isFalse()
 
             whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
 
             enrollmentChange(FACE, ANOTHER_USER_ID, true)
 
-            assertThat(faceEnrolled()).isFalse()
+            assertThat(faceAuthAllowed()).isFalse()
 
-            whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(true)
+            faceAuthIsEnrolled()
 
             enrollmentChange(FACE, PRIMARY_USER_ID, true)
 
-            assertThat(faceEnrolled()).isTrue()
+            assertThat(faceAuthAllowed()).isTrue()
         }
 
+    private fun faceAuthIsEnabledByBiometricManager(userId: Int = PRIMARY_USER_ID) {
+        verify(biometricManager, atLeastOnce())
+            .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
+        biometricManagerCallback.value.onChanged(true, userId)
+    }
+
     @Test
     fun faceEnrollmentStatusOfNewUserUponUserSwitch() =
         testScope.runTest {
@@ -251,21 +360,26 @@
 
             whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false)
             whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
-            val faceEnrolled = collectLastValue(underTest.isFaceEnrolled)
+            val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
 
-            assertThat(faceEnrolled()).isFalse()
+            assertThat(faceAuthAllowed()).isFalse()
         }
 
     @Test
     fun faceEnrollmentChangesArePropagatedAfterUserSwitch() =
         testScope.runTest {
             createBiometricSettingsRepository()
+            verify(biometricManager)
+                .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
 
             userRepository.setSelectedUserInfo(ANOTHER_USER)
+            doNotDisableKeyguardAuthFeatures(ANOTHER_USER_ID)
+            biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
+
             runCurrent()
             clearInvocations(authController)
 
-            val faceEnrolled = collectLastValue(underTest.isFaceEnrolled)
+            val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
             runCurrent()
 
             verify(authController).addCallback(authControllerCallback.capture())
@@ -273,12 +387,14 @@
             whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
             enrollmentChange(FACE, ANOTHER_USER_ID, true)
 
-            assertThat(faceEnrolled()).isTrue()
+            assertThat(faceAuthAllowed()).isTrue()
         }
 
     @Test
     fun devicePolicyControlsFaceAuthenticationEnabledState() =
         testScope.runTest {
+            faceAuthIsEnrolled()
+
             createBiometricSettingsRepository()
             verify(biometricManager)
                 .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
@@ -286,62 +402,70 @@
             whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
                 .thenReturn(KEYGUARD_DISABLE_FINGERPRINT or KEYGUARD_DISABLE_FACE)
 
-            val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
+            val isFaceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
             runCurrent()
 
             broadcastDPMStateChange()
 
-            assertThat(isFaceAuthEnabled()).isFalse()
+            assertThat(isFaceAuthAllowed()).isFalse()
 
             biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
             runCurrent()
-            assertThat(isFaceAuthEnabled()).isFalse()
+            assertThat(isFaceAuthAllowed()).isFalse()
 
             whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
                 .thenReturn(KEYGUARD_DISABLE_FINGERPRINT)
             broadcastDPMStateChange()
 
-            assertThat(isFaceAuthEnabled()).isTrue()
+            assertThat(isFaceAuthAllowed()).isTrue()
         }
 
     @Test
     fun biometricManagerControlsFaceAuthenticationEnabledStatus() =
         testScope.runTest {
+            faceAuthIsEnrolled()
+
             createBiometricSettingsRepository()
             verify(biometricManager)
                 .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
             whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
                 .thenReturn(0)
             broadcastDPMStateChange()
-            val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
+            val isFaceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
 
-            assertThat(isFaceAuthEnabled()).isFalse()
+            assertThat(isFaceAuthAllowed()).isFalse()
 
             // Value changes for another user
             biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
 
-            assertThat(isFaceAuthEnabled()).isFalse()
+            assertThat(isFaceAuthAllowed()).isFalse()
 
             // Value changes for current user.
             biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
 
-            assertThat(isFaceAuthEnabled()).isTrue()
+            assertThat(isFaceAuthAllowed()).isTrue()
         }
 
+    private fun faceAuthIsEnrolled(userId: Int = PRIMARY_USER_ID) {
+        whenever(authController.isFaceAuthEnrolled(userId)).thenReturn(true)
+    }
+
     @Test
     fun userChange_biometricEnabledChange_handlesRaceCondition() =
         testScope.runTest {
             createBiometricSettingsRepository()
+            whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
+
             verify(biometricManager)
                 .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
-            val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
+            val isFaceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
             biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
             runCurrent()
 
             userRepository.setSelectedUserInfo(ANOTHER_USER)
             runCurrent()
 
-            assertThat(isFaceAuthEnabled()).isTrue()
+            assertThat(isFaceAuthAllowed()).isTrue()
         }
 
     @Test
@@ -349,9 +473,9 @@
         testScope.runTest {
             createBiometricSettingsRepository()
 
-            collectLastValue(underTest.isFaceAuthenticationEnabled)()
-            collectLastValue(underTest.isFaceAuthenticationEnabled)()
-            collectLastValue(underTest.isFaceAuthenticationEnabled)()
+            collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)()
+            collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)()
+            collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)()
 
             verify(biometricManager, times(1)).registerEnabledOnKeyguardCallback(any())
         }
@@ -456,17 +580,143 @@
             assertThat(authFlags()!!.flag).isEqualTo(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
         }
 
+    @Test
+    fun faceAuthCurrentlyAllowed_dependsOnStrongAuthBiometricSetting_ifFaceIsClass3() =
+        testScope.runTest {
+            createBiometricSettingsRepository()
+            val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
+
+            faceAuthIsEnrolled()
+            deviceIsInPostureThatSupportsFaceAuth()
+            doNotDisableKeyguardAuthFeatures()
+            faceAuthIsStrongBiometric()
+            faceAuthIsEnabledByBiometricManager()
+
+            onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
+            onNonStrongAuthChanged(false, PRIMARY_USER_ID)
+
+            assertThat(isFaceAuthCurrentlyAllowed).isTrue()
+
+            onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, PRIMARY_USER_ID)
+            onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+
+            assertThat(isFaceAuthCurrentlyAllowed).isFalse()
+        }
+
+    @Test
+    fun faceAuthCurrentlyAllowed_dependsOnNonStrongAuthBiometricSetting_ifFaceIsNotStrong() =
+        testScope.runTest {
+            createBiometricSettingsRepository()
+            val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
+
+            faceAuthIsEnrolled()
+            deviceIsInPostureThatSupportsFaceAuth()
+            doNotDisableKeyguardAuthFeatures()
+            faceAuthIsNonStrongBiometric()
+            faceAuthIsEnabledByBiometricManager()
+
+            onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
+            onNonStrongAuthChanged(false, PRIMARY_USER_ID)
+
+            assertThat(isFaceAuthCurrentlyAllowed).isFalse()
+
+            onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, PRIMARY_USER_ID)
+            onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+
+            assertThat(isFaceAuthCurrentlyAllowed).isFalse()
+
+            onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
+            onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+
+            assertThat(isFaceAuthCurrentlyAllowed).isTrue()
+        }
+
+    @Test
+    fun fpAuthCurrentlyAllowed_dependsOnNonStrongAuthBiometricSetting_ifFpIsNotStrong() =
+        testScope.runTest {
+            createBiometricSettingsRepository()
+            val isFingerprintCurrentlyAllowed by
+                collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
+
+            fingerprintIsEnrolled(PRIMARY_USER_ID)
+            enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
+            doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
+            runCurrent()
+
+            fingerprintPropertyRepository.setProperties(
+                1,
+                SensorStrength.STRONG,
+                FingerprintSensorType.UDFPS_OPTICAL,
+                emptyMap()
+            )
+            // Non strong auth is not allowed now, FP is marked strong
+            onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
+            onNonStrongAuthChanged(false, PRIMARY_USER_ID)
+
+            assertThat(isFingerprintCurrentlyAllowed).isTrue()
+
+            fingerprintPropertyRepository.setProperties(
+                1,
+                SensorStrength.CONVENIENCE,
+                FingerprintSensorType.UDFPS_OPTICAL,
+                emptyMap()
+            )
+            assertThat(isFingerprintCurrentlyAllowed).isFalse()
+
+            fingerprintPropertyRepository.setProperties(
+                1,
+                SensorStrength.WEAK,
+                FingerprintSensorType.UDFPS_OPTICAL,
+                emptyMap()
+            )
+            assertThat(isFingerprintCurrentlyAllowed).isFalse()
+        }
+
+    @Test
+    fun fpAuthCurrentlyAllowed_dependsOnStrongAuthBiometricSetting_ifFpIsStrong() =
+        testScope.runTest {
+            createBiometricSettingsRepository()
+            val isFingerprintCurrentlyAllowed by
+                collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
+
+            fingerprintIsEnrolled(PRIMARY_USER_ID)
+            enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
+            doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
+            runCurrent()
+
+            fingerprintPropertyRepository.setProperties(
+                1,
+                SensorStrength.STRONG,
+                FingerprintSensorType.UDFPS_OPTICAL,
+                emptyMap()
+            )
+            // Non strong auth is not allowed now, FP is marked strong
+            onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, PRIMARY_USER_ID)
+            onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+
+            assertThat(isFingerprintCurrentlyAllowed).isFalse()
+
+            onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
+            onNonStrongAuthChanged(false, PRIMARY_USER_ID)
+
+            assertThat(isFingerprintCurrentlyAllowed).isTrue()
+        }
+
     private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
         authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
     }
 
+    private fun doNotDisableKeyguardAuthFeatures(userId: Int = PRIMARY_USER_ID) {
+        whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(userId)))
+            .thenReturn(0)
+        broadcastDPMStateChange()
+    }
+
     private fun broadcastDPMStateChange() {
-        fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-            receiver.onReceive(
-                context,
-                Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)
-            )
-        }
+        fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+            context,
+            Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+        )
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 1090c15..64b9470 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -36,24 +36,30 @@
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
 import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
+import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.coroutines.FlowValue
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -111,6 +117,7 @@
     @Mock private lateinit var sessionTracker: SessionTracker
     @Mock private lateinit var uiEventLogger: UiEventLogger
     @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
     @Captor
     private lateinit var authenticationCallback: ArgumentCaptor<FaceManager.AuthenticationCallback>
@@ -129,8 +136,8 @@
     private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
     private lateinit var testScope: TestScope
     private lateinit var fakeUserRepository: FakeUserRepository
-    private lateinit var authStatus: FlowValue<AuthenticationStatus?>
-    private lateinit var detectStatus: FlowValue<DetectionStatus?>
+    private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?>
+    private lateinit var detectStatus: FlowValue<FaceDetectionStatus?>
     private lateinit var authRunning: FlowValue<Boolean?>
     private lateinit var bypassEnabled: FlowValue<Boolean?>
     private lateinit var lockedOut: FlowValue<Boolean?>
@@ -146,6 +153,7 @@
     private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var fakeCommandQueue: FakeCommandQueue
     private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var fakeFacePropertyRepository: FakeFacePropertyRepository
 
     private var wasAuthCancelled = false
     private var wasDetectCancelled = false
@@ -159,25 +167,28 @@
         biometricSettingsRepository = FakeBiometricSettingsRepository()
         deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
         trustRepository = FakeTrustRepository()
-        keyguardRepository = FakeKeyguardRepository()
-        bouncerRepository = FakeKeyguardBouncerRepository()
-        featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
-        fakeCommandQueue = FakeCommandQueue()
-        keyguardInteractor =
-            KeyguardInteractor(
-                keyguardRepository,
-                fakeCommandQueue,
-                featureFlags,
-                bouncerRepository
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(FACE_AUTH_REFACTOR, true)
+                set(KEYGUARD_WM_STATE_REFACTOR, false)
+            }
+        val withDeps =
+            KeyguardInteractorFactory.create(
+                featureFlags = featureFlags,
             )
+        keyguardInteractor = withDeps.keyguardInteractor
+        keyguardRepository = withDeps.repository
+        bouncerRepository = withDeps.bouncerRepository
+        fakeCommandQueue = withDeps.commandQueue
+
         alternateBouncerInteractor =
             AlternateBouncerInteractor(
                 bouncerRepository = bouncerRepository,
                 biometricSettingsRepository = biometricSettingsRepository,
-                deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
                 systemClock = mock(SystemClock::class.java),
                 keyguardStateController = FakeKeyguardStateController(),
                 statusBarStateController = mock(StatusBarStateController::class.java),
+                keyguardUpdateMonitor = keyguardUpdateMonitor,
             )
 
         bypassStateChangedListener =
@@ -215,7 +226,12 @@
             )
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
         val keyguardTransitionInteractor =
-            KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope)
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = keyguardTransitionRepository,
+                )
+                .keyguardTransitionInteractor
+        fakeFacePropertyRepository = FakeFacePropertyRepository()
         return DeviceEntryFaceAuthRepositoryImpl(
             mContext,
             fmOverride,
@@ -223,6 +239,7 @@
             bypassControllerOverride,
             testScope.backgroundScope,
             testDispatcher,
+            testDispatcher,
             sessionTracker,
             uiEventLogger,
             FaceAuthenticationLogger(logcatLogBuffer("DeviceEntryFaceAuthRepositoryLog")),
@@ -256,9 +273,11 @@
             val successResult = successResult()
             authenticationCallback.value.onAuthenticationSucceeded(successResult)
 
-            assertThat(authStatus()).isEqualTo(SuccessAuthenticationStatus(successResult))
+            val response = authStatus() as SuccessFaceAuthenticationStatus
+            assertThat(response.successResult).isEqualTo(successResult)
             assertThat(authenticated()).isTrue()
             assertThat(authRunning()).isFalse()
+            assertThat(canFaceAuthRun()).isFalse()
         }
 
     private fun uiEventIsLogged(faceAuthUiEvent: FaceAuthUiEvent) {
@@ -315,9 +334,6 @@
                 )
                 .isFalse()
 
-            whenever(faceManager.sensorPropertiesInternal).thenReturn(null)
-            assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
-
             whenever(faceManager.sensorPropertiesInternal).thenReturn(listOf())
             assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
 
@@ -376,7 +392,10 @@
 
             detectionCallback.value.onFaceDetected(1, 1, true)
 
-            assertThat(detectStatus()).isEqualTo(DetectionStatus(1, 1, true))
+            val status = detectStatus()!!
+            assertThat(status.sensorId).isEqualTo(1)
+            assertThat(status.userId).isEqualTo(1)
+            assertThat(status.isStrongBiometric).isEqualTo(true)
         }
 
     @Test
@@ -416,13 +435,9 @@
                 FACE_ERROR_CANCELED,
                 "First auth attempt cancellation completed"
             )
-            assertThat(authStatus())
-                .isEqualTo(
-                    ErrorAuthenticationStatus(
-                        FACE_ERROR_CANCELED,
-                        "First auth attempt cancellation completed"
-                    )
-                )
+            val value = authStatus() as ErrorFaceAuthenticationStatus
+            assertThat(value.msgId).isEqualTo(FACE_ERROR_CANCELED)
+            assertThat(value.msg).isEqualTo("First auth attempt cancellation completed")
 
             faceAuthenticateIsCalled()
             uiEventIsLogged(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
@@ -446,6 +461,29 @@
         }
 
     @Test
+    fun multipleCancelCallsShouldNotCauseMultipleCancellationStatusBeingEmitted() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+            val emittedValues by collectValues(underTest.authenticationStatus)
+
+            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            underTest.cancel()
+            advanceTimeBy(100)
+            underTest.cancel()
+
+            advanceTimeBy(DeviceEntryFaceAuthRepositoryImpl.DEFAULT_CANCEL_SIGNAL_TIMEOUT)
+            runCurrent()
+            advanceTimeBy(DeviceEntryFaceAuthRepositoryImpl.DEFAULT_CANCEL_SIGNAL_TIMEOUT)
+            runCurrent()
+
+            assertThat(emittedValues.size).isEqualTo(1)
+            assertThat(emittedValues.first())
+                .isInstanceOf(ErrorFaceAuthenticationStatus::class.java)
+            assertThat((emittedValues.first() as ErrorFaceAuthenticationStatus).msgId).isEqualTo(-1)
+        }
+
+    @Test
     fun faceHelpMessagesAreIgnoredBasedOnConfig() =
         testScope.runTest {
             overrideResource(
@@ -462,7 +500,9 @@
             authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg")
             authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg")
 
-            assertThat(authStatus()).isEqualTo(HelpAuthenticationStatus(9, "help msg"))
+            val response = authStatus() as HelpFaceAuthenticationStatus
+            assertThat(response.msg).isEqualTo("help msg")
+            assertThat(response.msgId).isEqualTo(response.msgId)
         }
 
     @Test
@@ -482,15 +522,11 @@
         }
 
     @Test
-    fun authenticateDoesNotRunIfFaceIsNotEnrolled() =
+    fun authenticateDoesNotRunIfFaceIsNotUsuallyAllowed() =
         testScope.runTest {
-            testGatingCheckForFaceAuth { biometricSettingsRepository.setFaceEnrolled(false) }
-        }
-
-    @Test
-    fun authenticateDoesNotRunIfFaceIsNotEnabled() =
-        testScope.runTest {
-            testGatingCheckForFaceAuth { biometricSettingsRepository.setIsFaceAuthEnabled(false) }
+            testGatingCheckForFaceAuth {
+                biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+            }
         }
 
     @Test
@@ -500,18 +536,26 @@
         }
 
     @Test
-    fun authenticateDoesNotRunIfUserIsCurrentlySwitching() =
+    fun authenticateDoesNotRunIfFaceAuthIsCurrentlyPaused() =
+        testScope.runTest { testGatingCheckForFaceAuth { underTest.pauseFaceAuth() } }
+
+    @Test
+    fun authenticateDoesNotRunIfKeyguardIsNotShowing() =
         testScope.runTest {
-            testGatingCheckForFaceAuth { fakeUserRepository.setUserSwitching(true) }
+            testGatingCheckForFaceAuth { keyguardRepository.setKeyguardShowing(false) }
         }
 
     @Test
-    fun authenticateDoesNotRunWhenFpIsLockedOut() =
+    fun detectDoesNotRunIfKeyguardIsNotShowing() =
         testScope.runTest {
-            testGatingCheckForFaceAuth { deviceEntryFingerprintAuthRepository.setLockedOut(true) }
+            testGatingCheckForDetect { keyguardRepository.setKeyguardShowing(false) }
         }
 
     @Test
+    fun authenticateDoesNotRunWhenFaceIsDisabled() =
+        testScope.runTest { testGatingCheckForFaceAuth { underTest.lockoutFaceAuth() } }
+
+    @Test
     fun authenticateDoesNotRunWhenUserIsCurrentlyTrusted() =
         testScope.runTest {
             testGatingCheckForFaceAuth { trustRepository.setCurrentUserTrusted(true) }
@@ -538,24 +582,10 @@
         }
 
     @Test
-    fun authenticateDoesNotRunWhenDeviceIsSleeping() =
+    fun authenticateDoesNotRunWhenFaceAuthIsNotCurrentlyAllowedToRun() =
         testScope.runTest {
             testGatingCheckForFaceAuth {
-                keyguardRepository.setWakefulnessModel(
-                    WakefulnessModel(
-                        state = WakefulnessState.ASLEEP,
-                        lastWakeReason = WakeSleepReason.OTHER,
-                        lastSleepReason = WakeSleepReason.OTHER,
-                    )
-                )
-            }
-        }
-
-    @Test
-    fun authenticateDoesNotRunWhenNonStrongBiometricIsNotAllowed() =
-        testScope.runTest {
-            testGatingCheckForFaceAuth {
-                biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
+                biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
             }
         }
 
@@ -564,6 +594,8 @@
         testScope.runTest {
             testGatingCheckForFaceAuth {
                 bouncerRepository.setAlternateVisible(false)
+                // Keyguard is occluded when secure camera is active.
+                keyguardRepository.setKeyguardOccluded(true)
                 fakeCommandQueue.doForEachCallback {
                     it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
                 }
@@ -571,6 +603,29 @@
         }
 
     @Test
+    fun authenticateRunsWhenSecureCameraIsActiveIfBouncerIsShowing() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+            bouncerRepository.setAlternateVisible(false)
+            bouncerRepository.setPrimaryShow(false)
+
+            assertThat(canFaceAuthRun()).isTrue()
+
+            // launch secure camera
+            fakeCommandQueue.doForEachCallback {
+                it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+            }
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+            assertThat(canFaceAuthRun()).isFalse()
+
+            // but bouncer is shown after that.
+            bouncerRepository.setPrimaryShow(true)
+            assertThat(canFaceAuthRun()).isTrue()
+        }
+
+    @Test
     fun authenticateDoesNotRunOnUnsupportedPosture() =
         testScope.runTest {
             testGatingCheckForFaceAuth {
@@ -589,7 +644,28 @@
             allPreconditionsToRunFaceAuthAreTrue()
 
             // Flip one precondition to false.
-            biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
+            biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
+            assertThat(canFaceAuthRun()).isFalse()
+            underTest.authenticate(
+                FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
+                fallbackToDetection = true
+            )
+            faceAuthenticateIsNotCalled()
+
+            faceDetectIsCalled()
+        }
+
+    @Test
+    fun authenticateFallbacksToDetectionWhenUserIsAlreadyTrustedByTrustManager() =
+        testScope.runTest {
+            whenever(faceManager.sensorPropertiesInternal)
+                .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
+            whenever(bypassController.bypassEnabled).thenReturn(true)
+            underTest = createDeviceEntryFaceAuthRepositoryImpl()
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+
+            trustRepository.setCurrentUserTrusted(true)
             assertThat(canFaceAuthRun()).isFalse()
             underTest.authenticate(
                 FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
@@ -733,20 +809,16 @@
         }
 
     @Test
-    fun detectDoesNotRunWhenFaceIsNotEnrolled() =
+    fun detectDoesNotRunWhenFaceIsNotUsuallyAllowed() =
         testScope.runTest {
-            testGatingCheckForDetect { biometricSettingsRepository.setFaceEnrolled(false) }
-        }
-
-    @Test
-    fun detectDoesNotRunWhenFaceIsNotEnabled() =
-        testScope.runTest {
-            testGatingCheckForDetect { biometricSettingsRepository.setIsFaceAuthEnabled(false) }
+            testGatingCheckForDetect {
+                biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+            }
         }
 
     @Test
     fun detectDoesNotRunWhenUserSwitchingInProgress() =
-        testScope.runTest { testGatingCheckForDetect { fakeUserRepository.setUserSwitching(true) } }
+        testScope.runTest { testGatingCheckForDetect { underTest.pauseFaceAuth() } }
 
     @Test
     fun detectDoesNotRunWhenKeyguardGoingAway() =
@@ -773,6 +845,8 @@
         testScope.runTest {
             testGatingCheckForDetect {
                 bouncerRepository.setAlternateVisible(false)
+                // Keyguard is occluded when secure camera is active.
+                keyguardRepository.setKeyguardOccluded(true)
                 fakeCommandQueue.doForEachCallback {
                     it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
                 }
@@ -780,6 +854,19 @@
         }
 
     @Test
+    fun disableFaceUnlockLocksOutFaceUnlock() =
+        testScope.runTest {
+            runCurrent()
+            initCollectors()
+            assertThat(underTest.isLockedOut.value).isFalse()
+
+            underTest.lockoutFaceAuth()
+            runCurrent()
+
+            assertThat(underTest.isLockedOut.value).isTrue()
+        }
+
+    @Test
     fun detectDoesNotRunWhenFaceAuthNotSupportedInCurrentPosture() =
         testScope.runTest {
             testGatingCheckForDetect {
@@ -823,10 +910,10 @@
         }
 
     @Test
-    fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() =
+    fun detectDoesNotRunWhenFaceAuthIsCurrentlyAllowedToRun() =
         testScope.runTest {
             testGatingCheckForDetect {
-                biometricSettingsRepository.setIsNonStrongBiometricAllowed(true)
+                biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
             }
         }
 
@@ -956,7 +1043,7 @@
         allPreconditionsToRunFaceAuthAreTrue()
 
         // This will stop face auth from running but is required to be false for detect.
-        biometricSettingsRepository.setIsNonStrongBiometricAllowed(false)
+        biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
         runCurrent()
 
         assertThat(canFaceAuthRun()).isFalse()
@@ -992,10 +1079,9 @@
     }
 
     private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
-        biometricSettingsRepository.setFaceEnrolled(true)
-        biometricSettingsRepository.setIsFaceAuthEnabled(true)
-        fakeUserRepository.setUserSwitching(false)
-        deviceEntryFingerprintAuthRepository.setLockedOut(false)
+        verify(faceManager, atLeastOnce())
+            .addLockoutResetCallback(faceLockoutResetCallback.capture())
+        underTest.resumeFaceAuth()
         trustRepository.setCurrentUserTrusted(false)
         keyguardRepository.setKeyguardGoingAway(false)
         keyguardRepository.setWakefulnessModel(
@@ -1005,11 +1091,14 @@
                 WakeSleepReason.OTHER
             )
         )
-        biometricSettingsRepository.setIsNonStrongBiometricAllowed(true)
+        biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+        biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
+        biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
         biometricSettingsRepository.setIsUserInLockdown(false)
         fakeUserRepository.setSelectedUserInfo(primaryUser)
-        biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
+        faceLockoutResetCallback.value.onLockoutReset(0)
         bouncerRepository.setAlternateVisible(true)
+        keyguardRepository.setKeyguardShowing(true)
         runCurrent()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 264328b..def016a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -26,7 +26,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,7 +53,6 @@
 @RunWith(AndroidJUnit4::class)
 class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var authController: AuthController
     @Captor
     private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -68,7 +71,6 @@
                 authController,
                 keyguardUpdateMonitor,
                 testScope.backgroundScope,
-                dumpManager,
             )
     }
 
@@ -177,4 +179,129 @@
             callback.value.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT)
             assertThat(availableFpSensorType()).isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT)
         }
+
+    @Test
+    fun onFingerprintSuccess_successAuthenticationStatus() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            runCurrent()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+            updateMonitorCallback.value.onBiometricAuthenticated(
+                0,
+                BiometricSourceType.FINGERPRINT,
+                true,
+            )
+
+            val status = authenticationStatus as SuccessFingerprintAuthenticationStatus
+            assertThat(status.userId).isEqualTo(0)
+            assertThat(status.isStrongBiometric).isEqualTo(true)
+        }
+
+    @Test
+    fun onFingerprintFailed_failedAuthenticationStatus() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            runCurrent()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+            updateMonitorCallback.value.onBiometricAuthFailed(
+                BiometricSourceType.FINGERPRINT,
+            )
+
+            assertThat(authenticationStatus)
+                .isInstanceOf(FailFingerprintAuthenticationStatus::class.java)
+        }
+
+    @Test
+    fun onFingerprintError_errorAuthenticationStatus() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            runCurrent()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+            updateMonitorCallback.value.onBiometricError(
+                1,
+                "test_string",
+                BiometricSourceType.FINGERPRINT,
+            )
+
+            val status = authenticationStatus as ErrorFingerprintAuthenticationStatus
+            assertThat(status.msgId).isEqualTo(1)
+            assertThat(status.msg).isEqualTo("test_string")
+        }
+
+    @Test
+    fun onFingerprintHelp_helpAuthenticationStatus() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            runCurrent()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+            updateMonitorCallback.value.onBiometricHelp(
+                1,
+                "test_string",
+                BiometricSourceType.FINGERPRINT,
+            )
+
+            val status = authenticationStatus as HelpFingerprintAuthenticationStatus
+            assertThat(status.msgId).isEqualTo(1)
+            assertThat(status.msg).isEqualTo("test_string")
+        }
+
+    @Test
+    fun onFingerprintAcquired_acquiredAuthenticationStatus() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            runCurrent()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+            updateMonitorCallback.value.onBiometricAcquired(
+                BiometricSourceType.FINGERPRINT,
+                5,
+            )
+
+            val status = authenticationStatus as AcquiredFingerprintAuthenticationStatus
+            assertThat(status.acquiredInfo).isEqualTo(5)
+        }
+
+    @Test
+    fun onFaceCallbacks_fingerprintAuthenticationStatusIsUnchanged() =
+        testScope.runTest {
+            val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+            runCurrent()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+            updateMonitorCallback.value.onBiometricAuthenticated(
+                0,
+                BiometricSourceType.FACE,
+                true,
+            )
+            assertThat(authenticationStatus).isNull()
+
+            updateMonitorCallback.value.onBiometricAuthFailed(
+                BiometricSourceType.FACE,
+            )
+            assertThat(authenticationStatus).isNull()
+
+            updateMonitorCallback.value.onBiometricHelp(
+                1,
+                "test_string",
+                BiometricSourceType.FACE,
+            )
+            assertThat(authenticationStatus).isNull()
+
+            updateMonitorCallback.value.onBiometricAcquired(
+                BiometricSourceType.FACE,
+                5,
+            )
+            assertThat(authenticationStatus).isNull()
+
+            updateMonitorCallback.value.onBiometricError(
+                1,
+                "test_string",
+                BiometricSourceType.FACE,
+            )
+            assertThat(authenticationStatus).isNull()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
new file mode 100644
index 0000000..66ead14
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
+import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class KeyguardBlueprintRepositoryTest : SysuiTestCase() {
+    private lateinit var underTest: KeyguardBlueprintRepository
+    @Mock lateinit var configurationRepository: ConfigurationRepository
+    @Mock lateinit var defaultLockscreenBlueprint: DefaultKeyguardBlueprint
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+    private val configurationFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        whenever(defaultLockscreenBlueprint.id).thenReturn(DEFAULT)
+        whenever(configurationRepository.onAnyConfigurationChange).thenReturn(configurationFlow)
+        underTest =
+            KeyguardBlueprintRepository(
+                configurationRepository,
+                setOf(defaultLockscreenBlueprint),
+                testScope.backgroundScope,
+            )
+    }
+
+    @Test
+    fun testApplyBlueprint_DefaultLayout() {
+        testScope.runTest {
+            val blueprint by collectLastValue(underTest.blueprint)
+            runCurrent()
+            underTest.applyBlueprint(defaultLockscreenBlueprint)
+            runCurrent()
+            assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint)
+        }
+    }
+
+    @Test
+    fun testConfigurationChange() {
+        testScope.runTest {
+            val blueprint by collectLastValue(underTest.blueprint)
+            runCurrent()
+            configurationFlow.tryEmit(Unit)
+            runCurrent()
+            assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint)
+        }
+    }
+
+    @Test
+    fun testRefreshBlueprint() {
+        testScope.runTest {
+            val blueprint by collectLastValue(underTest.blueprint)
+            runCurrent()
+            underTest.refreshBlueprint()
+            runCurrent()
+            assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint)
+        }
+    }
+
+    @Test
+    fun testTransitionToLayout_validId() {
+        assertThat(underTest.applyBlueprint(DEFAULT)).isTrue()
+    }
+    @Test
+    fun testTransitionToLayout_invalidId() {
+        assertThat(underTest.applyBlueprint("abc")).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
deleted file mode 100644
index b3104b7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
+++ /dev/null
@@ -1,64 +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.keyguard.data.repository
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.keyguard.ViewMediatorCallback
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestCoroutineScope
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class KeyguardBouncerRepositoryTest : SysuiTestCase() {
-
-    @Mock private lateinit var systemClock: SystemClock
-    @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
-    @Mock private lateinit var bouncerLogger: TableLogBuffer
-    lateinit var underTest: KeyguardBouncerRepository
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        val testCoroutineScope = TestCoroutineScope()
-        underTest =
-            KeyguardBouncerRepositoryImpl(
-                systemClock,
-                testCoroutineScope,
-                bouncerLogger,
-            )
-    }
-
-    @Test
-    fun changingFlowValueTriggersLogging() = runBlocking {
-        underTest.setPrimaryShow(true)
-        verify(bouncerLogger).logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any())
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 4b797cb..5ead16b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -31,20 +31,25 @@
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
 import com.android.systemui.dreams.DreamOverlayCallbackController
+import com.android.systemui.keyguard.ScreenLifecycle
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.ScreenModel
+import com.android.systemui.keyguard.shared.model.ScreenState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
@@ -58,6 +63,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -70,34 +76,41 @@
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock private lateinit var screenLifecycle: ScreenLifecycle
     @Mock private lateinit var biometricUnlockController: BiometricUnlockController
     @Mock private lateinit var dozeTransitionListener: DozeTransitionListener
     @Mock private lateinit var authController: AuthController
+    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
     @Mock private lateinit var dozeParameters: DozeParameters
     private val mainDispatcher = StandardTestDispatcher()
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
+    private lateinit var systemClock: FakeSystemClock
 
     private lateinit var underTest: KeyguardRepositoryImpl
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
+        systemClock = FakeSystemClock()
         underTest =
             KeyguardRepositoryImpl(
                 statusBarStateController,
                 wakefulnessLifecycle,
+                screenLifecycle,
                 biometricUnlockController,
                 keyguardStateController,
+                keyguardBypassController,
                 keyguardUpdateMonitor,
                 dozeTransitionListener,
                 dozeParameters,
                 authController,
                 dreamOverlayCallbackController,
-                mainDispatcher
+                mainDispatcher,
+                testScope.backgroundScope,
+                systemClock,
             )
     }
 
@@ -156,6 +169,20 @@
         }
 
     @Test
+    fun dozeTimeTick() =
+        testScope.runTest {
+            val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick)
+            assertThat(lastDozeTimeTick).isEqualTo(0L)
+
+            // WHEN dozeTimeTick updated
+            systemClock.setUptimeMillis(systemClock.uptimeMillis() + 5)
+            underTest.dozeTimeTick()
+
+            // THEN listeners were updated to the latest uptime millis
+            assertThat(systemClock.uptimeMillis()).isEqualTo(lastDozeTimeTick)
+        }
+
+    @Test
     fun isKeyguardShowing() =
         testScope.runTest {
             whenever(keyguardStateController.isShowing).thenReturn(false)
@@ -167,7 +194,7 @@
             assertThat(underTest.isKeyguardShowing()).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isShowing).thenReturn(true)
             captor.value.onKeyguardShowingChanged()
@@ -185,6 +212,20 @@
         }
 
     @Test
+    fun isBypassEnabled_disabledInController() {
+        whenever(keyguardBypassController.isBypassEnabled).thenReturn(false)
+        whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
+        assertThat(underTest.isBypassEnabled()).isFalse()
+    }
+
+    @Test
+    fun isBypassEnabled_enabledInController() {
+        whenever(keyguardBypassController.isBypassEnabled).thenReturn(true)
+        whenever(keyguardBypassController.bypassEnabled).thenReturn(true)
+        assertThat(underTest.isBypassEnabled()).isTrue()
+    }
+
+    @Test
     fun isAodAvailable() = runTest {
         val flow = underTest.isAodAvailable
         var isAodAvailable = collectLastValue(flow)
@@ -215,7 +256,7 @@
             assertThat(latest).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isOccluded).thenReturn(true)
             captor.value.onKeyguardShowingChanged()
@@ -234,26 +275,23 @@
     fun isKeyguardUnlocked() =
         testScope.runTest {
             whenever(keyguardStateController.isUnlocked).thenReturn(false)
-            var latest: Boolean? = null
-            val job = underTest.isKeyguardUnlocked.onEach { latest = it }.launchIn(this)
+            val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked)
 
             runCurrent()
-            assertThat(latest).isFalse()
+            assertThat(isKeyguardUnlocked).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isUnlocked).thenReturn(true)
             captor.value.onUnlockedChanged()
             runCurrent()
-            assertThat(latest).isTrue()
+            assertThat(isKeyguardUnlocked).isTrue()
 
             whenever(keyguardStateController.isUnlocked).thenReturn(false)
-            captor.value.onUnlockedChanged()
+            captor.value.onKeyguardShowingChanged()
             runCurrent()
-            assertThat(latest).isFalse()
-
-            job.cancel()
+            assertThat(isKeyguardUnlocked).isFalse()
         }
 
     @Test
@@ -301,6 +339,16 @@
         }
 
     @Test
+    fun isActiveDreamLockscreenHosted() =
+        testScope.runTest {
+            underTest.setIsActiveDreamLockscreenHosted(true)
+            assertThat(underTest.isActiveDreamLockscreenHosted.value).isEqualTo(true)
+
+            underTest.setIsActiveDreamLockscreenHosted(false)
+            assertThat(underTest.isActiveDreamLockscreenHosted.value).isEqualTo(false)
+        }
+
+    @Test
     fun wakefulness() =
         testScope.runTest {
             val values = mutableListOf<WakefulnessModel>()
@@ -343,8 +391,48 @@
                 )
 
             job.cancel()
+        }
+
+    @Test
+    fun screenModel() =
+        testScope.runTest {
+            val values = mutableListOf<ScreenModel>()
+            val job = underTest.screenModel.onEach(values::add).launchIn(this)
+
             runCurrent()
-            verify(wakefulnessLifecycle).removeObserver(captor.value)
+            val captor = argumentCaptor<ScreenLifecycle.Observer>()
+            verify(screenLifecycle).addObserver(captor.capture())
+
+            whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_TURNING_ON)
+            captor.value.onScreenTurningOn()
+            runCurrent()
+
+            whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_ON)
+            captor.value.onScreenTurnedOn()
+            runCurrent()
+
+            whenever(screenLifecycle.getScreenState())
+                .thenReturn(ScreenLifecycle.SCREEN_TURNING_OFF)
+            captor.value.onScreenTurningOff()
+            runCurrent()
+
+            whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_OFF)
+            captor.value.onScreenTurnedOff()
+            runCurrent()
+
+            assertThat(values.map { it.state })
+                .isEqualTo(
+                    listOf(
+                        // Initial value will be OFF
+                        ScreenState.SCREEN_OFF,
+                        ScreenState.SCREEN_TURNING_ON,
+                        ScreenState.SCREEN_ON,
+                        ScreenState.SCREEN_TURNING_OFF,
+                        ScreenState.SCREEN_OFF,
+                    )
+                )
+
+            job.cancel()
         }
 
     @Test
@@ -367,7 +455,7 @@
             assertThat(latest).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
             captor.value.onKeyguardGoingAwayChanged()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
new file mode 100644
index 0000000..bed959f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardSurfaceBehindRepositoryImplTest : SysuiTestCase() {
+    private val testScope = TestScope()
+
+    private lateinit var underTest: KeyguardSurfaceBehindRepositoryImpl
+
+    @Before
+    fun setUp() {
+        underTest = KeyguardSurfaceBehindRepositoryImpl()
+    }
+
+    @Test
+    fun testSetAnimatingSurface() {
+        testScope.runTest {
+            val values by collectValues(underTest.isAnimatingSurface)
+
+            runCurrent()
+            underTest.setAnimatingSurface(true)
+            runCurrent()
+            underTest.setAnimatingSurface(false)
+            runCurrent()
+
+            // Default (first) value should be false.
+            assertThat(values).isEqualTo(listOf(false, true, false))
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
index f974577..dcaafe8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -17,31 +17,43 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.graphics.Point
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LightRevealEffect
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RoboPilotTest
-@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
 class LightRevealScrimRepositoryTest : SysuiTestCase() {
     private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
     private lateinit var underTest: LightRevealScrimRepositoryImpl
 
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -50,112 +62,127 @@
     }
 
     @Test
-    fun nextRevealEffect_effectSwitchesBetweenDefaultAndBiometricWithNoDupes() =
-        runTest {
-            val values = mutableListOf<LightRevealEffect>()
-            val job = launch { underTest.revealEffect.collect { values.add(it) } }
+    fun nextRevealEffect_effectSwitchesBetweenDefaultAndBiometricWithNoDupes() = runTest {
+        val values = mutableListOf<LightRevealEffect>()
+        val job = launch { underTest.revealEffect.collect { values.add(it) } }
 
-            // We should initially emit the default reveal effect.
-            runCurrent()
-            values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT })
-
-            // The source and sensor locations are still null, so we should still be using the
-            // default reveal despite a biometric unlock.
-            fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
-
-            runCurrent()
-            values.assertEffectsMatchPredicates(
-                { it == DEFAULT_REVEAL_EFFECT },
+        fakeKeyguardRepository.setWakefulnessModel(
+            WakefulnessModel(
+                WakefulnessState.STARTING_TO_WAKE,
+                WakeSleepReason.OTHER,
+                WakeSleepReason.OTHER
             )
+        )
+        // We should initially emit the default reveal effect.
+        runCurrent()
+        values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT })
 
-            // We got a source but still have no sensor locations, so should be sticking with
-            // the default effect.
-            fakeKeyguardRepository.setBiometricUnlockSource(
-                BiometricUnlockSource.FINGERPRINT_SENSOR
-            )
+        // The source and sensor locations are still null, so we should still be using the
+        // default reveal despite a biometric unlock.
+        fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
 
-            runCurrent()
-            values.assertEffectsMatchPredicates(
-                { it == DEFAULT_REVEAL_EFFECT },
-            )
+        runCurrent()
+        values.assertEffectsMatchPredicates(
+            { it == DEFAULT_REVEAL_EFFECT },
+        )
 
-            // We got a location for the face sensor, but we unlocked with fingerprint.
-            val faceLocation = Point(250, 0)
-            fakeKeyguardRepository.setFaceSensorLocation(faceLocation)
+        // We got a source but still have no sensor locations, so should be sticking with
+        // the default effect.
+        fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
 
-            runCurrent()
-            values.assertEffectsMatchPredicates(
-                { it == DEFAULT_REVEAL_EFFECT },
-            )
+        runCurrent()
+        values.assertEffectsMatchPredicates(
+            { it == DEFAULT_REVEAL_EFFECT },
+        )
 
-            // Now we have fingerprint sensor locations, and wake and unlock via fingerprint.
-            val fingerprintLocation = Point(500, 500)
-            fakeKeyguardRepository.setFingerprintSensorLocation(fingerprintLocation)
-            fakeKeyguardRepository.setBiometricUnlockSource(
-                BiometricUnlockSource.FINGERPRINT_SENSOR
-            )
-            fakeKeyguardRepository.setBiometricUnlockState(
-                BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
-            )
+        // We got a location for the face sensor, but we unlocked with fingerprint.
+        val faceLocation = Point(250, 0)
+        fakeKeyguardRepository.setFaceSensorLocation(faceLocation)
 
-            // We should now have switched to the circle reveal, at the fingerprint location.
-            runCurrent()
-            values.assertEffectsMatchPredicates(
-                { it == DEFAULT_REVEAL_EFFECT },
-                {
-                    it is CircleReveal &&
-                        it.centerX == fingerprintLocation.x &&
-                        it.centerY == fingerprintLocation.y
-                },
-            )
+        runCurrent()
+        values.assertEffectsMatchPredicates(
+            { it == DEFAULT_REVEAL_EFFECT },
+        )
 
-            // Subsequent wake and unlocks should not emit duplicate, identical CircleReveals.
-            val valuesPrevSize = values.size
-            fakeKeyguardRepository.setBiometricUnlockState(
-                BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
-            )
-            fakeKeyguardRepository.setBiometricUnlockState(
-                BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
-            )
-            assertEquals(valuesPrevSize, values.size)
+        // Now we have fingerprint sensor locations, and wake and unlock via fingerprint.
+        val fingerprintLocation = Point(500, 500)
+        fakeKeyguardRepository.setFingerprintSensorLocation(fingerprintLocation)
+        fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
+        fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING)
 
-            // Non-biometric unlock, we should return to the default reveal.
-            fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+        // We should now have switched to the circle reveal, at the fingerprint location.
+        runCurrent()
+        values.assertEffectsMatchPredicates(
+            { it == DEFAULT_REVEAL_EFFECT },
+            {
+                it is CircleReveal &&
+                    it.centerX == fingerprintLocation.x &&
+                    it.centerY == fingerprintLocation.y
+            },
+        )
 
-            runCurrent()
-            values.assertEffectsMatchPredicates(
-                { it == DEFAULT_REVEAL_EFFECT },
-                {
-                    it is CircleReveal &&
-                        it.centerX == fingerprintLocation.x &&
-                        it.centerY == fingerprintLocation.y
-                },
-                { it == DEFAULT_REVEAL_EFFECT },
-            )
+        // Subsequent wake and unlocks should not emit duplicate, identical CircleReveals.
+        val valuesPrevSize = values.size
+        fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING)
+        fakeKeyguardRepository.setBiometricUnlockState(
+            BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+        )
+        assertEquals(valuesPrevSize, values.size)
 
-            // We already have a face location, so switching to face source should update the
-            // CircleReveal.
-            fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
-            runCurrent()
-            fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
-            runCurrent()
+        // Non-biometric unlock, we should return to the default reveal.
+        fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
 
-            values.assertEffectsMatchPredicates(
-                { it == DEFAULT_REVEAL_EFFECT },
-                {
-                    it is CircleReveal &&
-                        it.centerX == fingerprintLocation.x &&
-                        it.centerY == fingerprintLocation.y
-                },
-                { it == DEFAULT_REVEAL_EFFECT },
-                {
-                    it is CircleReveal &&
-                        it.centerX == faceLocation.x &&
-                        it.centerY == faceLocation.y
-                },
-            )
+        runCurrent()
+        values.assertEffectsMatchPredicates(
+            { it == DEFAULT_REVEAL_EFFECT },
+            {
+                it is CircleReveal &&
+                    it.centerX == fingerprintLocation.x &&
+                    it.centerY == fingerprintLocation.y
+            },
+            { it == DEFAULT_REVEAL_EFFECT },
+        )
 
-            job.cancel()
+        // We already have a face location, so switching to face source should update the
+        // CircleReveal.
+        fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
+        runCurrent()
+        fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+        runCurrent()
+
+        values.assertEffectsMatchPredicates(
+            { it == DEFAULT_REVEAL_EFFECT },
+            {
+                it is CircleReveal &&
+                    it.centerX == fingerprintLocation.x &&
+                    it.centerY == fingerprintLocation.y
+            },
+            { it == DEFAULT_REVEAL_EFFECT },
+            { it is CircleReveal && it.centerX == faceLocation.x && it.centerY == faceLocation.y },
+        )
+
+        job.cancel()
+    }
+
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    fun revealAmount_emitsTo1AfterAnimationStarted() =
+        runTest(UnconfinedTestDispatcher()) {
+            val value by collectLastValue(underTest.revealAmount)
+            underTest.startRevealAmountAnimator(true)
+            assertEquals(0.0f, value)
+            animatorTestRule.advanceTimeBy(500L)
+            assertEquals(1.0f, value)
+        }
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    fun revealAmount_emitsTo0AfterAnimationStartedReversed() =
+        runTest(UnconfinedTestDispatcher()) {
+            val value by collectLastValue(underTest.revealAmount)
+            underTest.startRevealAmountAnimator(false)
+            assertEquals(1.0f, value)
+            animatorTestRule.advanceTimeBy(500L)
+            assertEquals(0.0f, value)
         }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
deleted file mode 100644
index ca6b8d5..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ /dev/null
@@ -1,178 +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.keyguard.domain.interactor
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.RoboPilotTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
-import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestCoroutineScope
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RoboPilotTest
-@RunWith(AndroidJUnit4::class)
-class AlternateBouncerInteractorTest : SysuiTestCase() {
-    private lateinit var underTest: AlternateBouncerInteractor
-    private lateinit var bouncerRepository: KeyguardBouncerRepository
-    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
-    private lateinit var deviceEntryFingerprintAuthRepository:
-        FakeDeviceEntryFingerprintAuthRepository
-    @Mock private lateinit var statusBarStateController: StatusBarStateController
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var systemClock: SystemClock
-    @Mock private lateinit var bouncerLogger: TableLogBuffer
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        bouncerRepository =
-            KeyguardBouncerRepositoryImpl(
-                FakeSystemClock(),
-                TestCoroutineScope(),
-                bouncerLogger,
-            )
-        biometricSettingsRepository = FakeBiometricSettingsRepository()
-        deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
-        underTest =
-            AlternateBouncerInteractor(
-                statusBarStateController,
-                keyguardStateController,
-                bouncerRepository,
-                biometricSettingsRepository,
-                deviceEntryFingerprintAuthRepository,
-                systemClock,
-            )
-    }
-
-    @Test
-    fun canShowAlternateBouncerForFingerprint_givenCanShow() {
-        givenCanShowAlternateBouncer()
-        assertTrue(underTest.canShowAlternateBouncerForFingerprint())
-    }
-
-    @Test
-    fun canShowAlternateBouncerForFingerprint_alternateBouncerUIUnavailable() {
-        givenCanShowAlternateBouncer()
-        bouncerRepository.setAlternateBouncerUIAvailable(false)
-
-        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
-    }
-
-    @Test
-    fun canShowAlternateBouncerForFingerprint_noFingerprintsEnrolled() {
-        givenCanShowAlternateBouncer()
-        biometricSettingsRepository.setFingerprintEnrolled(false)
-
-        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
-    }
-
-    @Test
-    fun canShowAlternateBouncerForFingerprint_strongBiometricNotAllowed() {
-        givenCanShowAlternateBouncer()
-        biometricSettingsRepository.setStrongBiometricAllowed(false)
-
-        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
-    }
-
-    @Test
-    fun canShowAlternateBouncerForFingerprint_devicePolicyDoesNotAllowFingerprint() {
-        givenCanShowAlternateBouncer()
-        biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(false)
-
-        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
-    }
-
-    @Test
-    fun canShowAlternateBouncerForFingerprint_fingerprintLockedOut() {
-        givenCanShowAlternateBouncer()
-        deviceEntryFingerprintAuthRepository.setLockedOut(true)
-
-        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
-    }
-
-    @Test
-    fun show_whenCanShow() {
-        givenCanShowAlternateBouncer()
-
-        assertTrue(underTest.show())
-        assertTrue(bouncerRepository.alternateBouncerVisible.value)
-    }
-
-    @Test
-    fun canShowAlternateBouncerForFingerprint_butCanDismissLockScreen() {
-        givenCanShowAlternateBouncer()
-        whenever(keyguardStateController.isUnlocked).thenReturn(true)
-
-        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
-    }
-
-    @Test
-    fun show_whenCannotShow() {
-        givenCannotShowAlternateBouncer()
-
-        assertFalse(underTest.show())
-        assertFalse(bouncerRepository.alternateBouncerVisible.value)
-    }
-
-    @Test
-    fun hide_wasPreviouslyShowing() {
-        bouncerRepository.setAlternateVisible(true)
-
-        assertTrue(underTest.hide())
-        assertFalse(bouncerRepository.alternateBouncerVisible.value)
-    }
-
-    @Test
-    fun hide_wasNotPreviouslyShowing() {
-        bouncerRepository.setAlternateVisible(false)
-
-        assertFalse(underTest.hide())
-        assertFalse(bouncerRepository.alternateBouncerVisible.value)
-    }
-
-    private fun givenCanShowAlternateBouncer() {
-        bouncerRepository.setAlternateBouncerUIAvailable(true)
-        biometricSettingsRepository.setFingerprintEnrolled(true)
-        biometricSettingsRepository.setStrongBiometricAllowed(true)
-        biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
-        deviceEntryFingerprintAuthRepository.setLockedOut(false)
-        whenever(keyguardStateController.isUnlocked).thenReturn(false)
-    }
-
-    private fun givenCannotShowAlternateBouncer() {
-        biometricSettingsRepository.setFingerprintEnrolled(false)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt
new file mode 100644
index 0000000..3389fa9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt
@@ -0,0 +1,260 @@
+/*
+ *  Copyright (C) 2023 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
+import android.hardware.fingerprint.FingerprintManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.util.IndicationHelper
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BiometricMessageInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: BiometricMessageInteractor
+    private lateinit var testScope: TestScope
+    private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+    private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository
+
+    @Mock private lateinit var indicationHelper: IndicationHelper
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+        fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+        fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+        underTest =
+            BiometricMessageInteractor(
+                mContext.resources,
+                fingerprintAuthRepository,
+                fingerprintPropertyRepository,
+                indicationHelper,
+                keyguardUpdateMonitor,
+            )
+    }
+
+    @Test
+    fun fingerprintErrorMessage() =
+        testScope.runTest {
+            val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage)
+
+            // GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should NOT be suppressed
+            whenever(
+                    indicationHelper.shouldSuppressErrorMsg(
+                        FINGERPRINT,
+                        FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
+                    )
+                )
+                .thenReturn(false)
+
+            // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+                    msg = "test"
+                )
+            )
+
+            // THEN fingerprintErrorMessage is updated
+            assertThat(fingerprintErrorMessage?.source).isEqualTo(FINGERPRINT)
+            assertThat(fingerprintErrorMessage?.type).isEqualTo(BiometricMessageType.ERROR)
+            assertThat(fingerprintErrorMessage?.id)
+                .isEqualTo(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE)
+            assertThat(fingerprintErrorMessage?.message).isEqualTo("test")
+        }
+
+    @Test
+    fun fingerprintErrorMessage_suppressedError() =
+        testScope.runTest {
+            val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage)
+
+            // GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should be suppressed
+            whenever(
+                    indicationHelper.shouldSuppressErrorMsg(
+                        FINGERPRINT,
+                        FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
+                    )
+                )
+                .thenReturn(true)
+
+            // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+                    msg = "test"
+                )
+            )
+
+            // THEN fingerprintErrorMessage isn't update - it's still null
+            assertThat(fingerprintErrorMessage).isNull()
+        }
+
+    @Test
+    fun fingerprintHelpMessage() =
+        testScope.runTest {
+            val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage)
+
+            // GIVEN primary auth is NOT required
+            whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+                .thenReturn(true)
+
+            // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY
+            fingerprintAuthRepository.setAuthenticationStatus(
+                HelpFingerprintAuthenticationStatus(
+                    msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
+                    msg = "test"
+                )
+            )
+
+            // THEN fingerprintHelpMessage is updated
+            assertThat(fingerprintHelpMessage?.source).isEqualTo(FINGERPRINT)
+            assertThat(fingerprintHelpMessage?.type).isEqualTo(BiometricMessageType.HELP)
+            assertThat(fingerprintHelpMessage?.id)
+                .isEqualTo(FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY)
+            assertThat(fingerprintHelpMessage?.message).isEqualTo("test")
+        }
+
+    @Test
+    fun fingerprintHelpMessage_primaryAuthRequired() =
+        testScope.runTest {
+            val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage)
+
+            // GIVEN primary auth is required
+            whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+                .thenReturn(false)
+
+            // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY
+            fingerprintAuthRepository.setAuthenticationStatus(
+                HelpFingerprintAuthenticationStatus(
+                    msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
+                    msg = "test"
+                )
+            )
+
+            // THEN fingerprintHelpMessage isn't update - it's still null
+            assertThat(fingerprintHelpMessage).isNull()
+        }
+
+    @Test
+    fun fingerprintFailMessage_nonUdfps() =
+        testScope.runTest {
+            val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage)
+
+            // GIVEN primary auth is NOT required
+            whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+                .thenReturn(true)
+
+            // GIVEN rear fingerprint (not UDFPS)
+            fingerprintPropertyRepository.setProperties(
+                0,
+                SensorStrength.STRONG,
+                FingerprintSensorType.REAR,
+                mapOf()
+            )
+
+            // WHEN authentication status fail
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+            // THEN fingerprintFailMessage is updated
+            assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT)
+            assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL)
+            assertThat(fingerprintFailMessage?.id)
+                .isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
+            assertThat(fingerprintFailMessage?.message)
+                .isEqualTo(
+                    mContext.resources.getString(
+                        com.android.internal.R.string.fingerprint_error_not_match
+                    )
+                )
+        }
+
+    @Test
+    fun fingerprintFailMessage_udfps() =
+        testScope.runTest {
+            val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage)
+
+            // GIVEN primary auth is NOT required
+            whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+                .thenReturn(true)
+
+            // GIVEN UDFPS
+            fingerprintPropertyRepository.setProperties(
+                0,
+                SensorStrength.STRONG,
+                FingerprintSensorType.UDFPS_OPTICAL,
+                mapOf()
+            )
+
+            // WHEN authentication status fail
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+            // THEN fingerprintFailMessage is updated to udfps message
+            assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT)
+            assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL)
+            assertThat(fingerprintFailMessage?.id)
+                .isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
+            assertThat(fingerprintFailMessage?.message)
+                .isEqualTo(
+                    mContext.resources.getString(
+                        com.android.internal.R.string.fingerprint_udfps_error_not_match
+                    )
+                )
+        }
+
+    @Test
+    fun fingerprintFailedMessage_primaryAuthRequired() =
+        testScope.runTest {
+            val fingerprintFailedMessage by collectLastValue(underTest.fingerprintFailMessage)
+
+            // GIVEN primary auth is required
+            whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+                .thenReturn(false)
+
+            // WHEN authentication status fail
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+            // THEN fingerprintFailedMessage isn't update - it's still null
+            assertThat(fingerprintFailedMessage).isNull()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
index 069a486..6308269 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
@@ -23,10 +23,9 @@
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
-import junit.framework.Assert.assertEquals
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
@@ -48,7 +47,8 @@
     @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
 
     private lateinit var configurationRepository: FakeConfigurationRepository
-    private lateinit var systemClock: FakeSystemClock
+    private lateinit var keyguardInteractor: KeyguardInteractor
+    private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
     private lateinit var testScope: TestScope
     private lateinit var underTest: BurnInInteractor
 
@@ -56,8 +56,10 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         configurationRepository = FakeConfigurationRepository()
-        systemClock = FakeSystemClock()
-
+        KeyguardInteractorFactory.create().let {
+            keyguardInteractor = it.keyguardInteractor
+            fakeKeyguardRepository = it.repository
+        }
         whenever(burnInHelperWrapper.burnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset)
         setBurnInProgress(.65f)
 
@@ -68,26 +70,11 @@
                 burnInHelperWrapper,
                 testScope.backgroundScope,
                 configurationRepository,
-                systemClock,
+                keyguardInteractor,
             )
     }
 
     @Test
-    fun dozeTimeTick_updatesOnDozeTimeTick() =
-        testScope.runTest {
-            // Initial state set to 0
-            val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick)
-            assertEquals(0L, lastDozeTimeTick)
-
-            // WHEN dozeTimeTick updated
-            incrementUptimeMillis()
-            underTest.dozeTimeTick()
-
-            // THEN listeners were updated to the latest uptime millis
-            assertThat(systemClock.uptimeMillis()).isEqualTo(lastDozeTimeTick)
-        }
-
-    @Test
     fun udfpsBurnInOffset_updatesOnResolutionScaleChange() =
         testScope.runTest {
             val udfpsBurnInOffsetX by collectLastValue(underTest.udfpsBurnInXOffset)
@@ -111,25 +98,18 @@
             assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
 
             setBurnInProgress(.88f)
-            incrementUptimeMillis()
-            underTest.dozeTimeTick()
+            fakeKeyguardRepository.dozeTimeTick(10)
             assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
 
             setBurnInProgress(.92f)
-            incrementUptimeMillis()
-            underTest.dozeTimeTick()
+            fakeKeyguardRepository.dozeTimeTick(20)
             assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
 
             setBurnInProgress(.32f)
-            incrementUptimeMillis()
-            underTest.dozeTimeTick()
+            fakeKeyguardRepository.dozeTimeTick(30)
             assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
         }
 
-    private fun incrementUptimeMillis() {
-        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 5)
-    }
-
     private fun setBurnInProgress(progress: Float) {
         burnInProgress = progress
         whenever(burnInHelperWrapper.burnInProgressOffset()).thenReturn(burnInProgress)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
new file mode 100644
index 0000000..e2bf2f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import dagger.Lazy
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import junit.framework.Assert.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromLockscreenTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
+    private lateinit var underTest: FromLockscreenTransitionInteractor
+
+    // Override the fromLockscreenTransitionInteractor provider from the superclass so our underTest
+    // interactor is provided to any classes that need it.
+    override var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
+        Lazy {
+            underTest
+        }
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+
+        underTest =
+            FromLockscreenTransitionInteractor(
+                transitionRepository = super.transitionRepository,
+                transitionInteractor = super.transitionInteractor,
+                scope = super.testScope.backgroundScope,
+                keyguardInteractor = super.keyguardInteractor,
+                flags = FakeFeatureFlags(),
+                shadeRepository = FakeShadeRepository(),
+            )
+    }
+
+    @Test
+    fun testSurfaceBehindVisibility_nonNullOnlyForRelevantTransitions() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindVisibility)
+            runCurrent()
+
+            // Transition-specific surface visibility should be null ("don't care") initially.
+            assertEquals(
+                listOf(
+                    null,
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // LOCKSCREEN -> AOD does not have any specific surface visibility.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null,
+                    true, // Surface is made visible immediately during LOCKSCREEN -> GONE
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testSurfaceBehindModel() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindModel)
+            runCurrent()
+
+            assertEquals(
+                values,
+                listOf(
+                    null, // We should start null ("don't care").
+                )
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // LOCKSCREEN -> AOD does not have specific view params.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    value = 0.01f,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    value = 0.99f,
+                )
+            )
+            runCurrent()
+
+            assertEquals(3, values.size)
+            val model1percent = values[1]
+            val model99percent = values[2]
+
+            try {
+                // We should initially have an alpha of 0f when unlocking, so the surface is not
+                // visible
+                // while lockscreen UI animates out.
+                assertEquals(0f, model1percent!!.alpha)
+
+                // By the end it should probably be visible.
+                assertTrue(model99percent!!.alpha > 0f)
+            } catch (e: NullPointerException) {
+                fail("surfaceBehindModel was unexpectedly null.")
+            }
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
new file mode 100644
index 0000000..85bc374
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import junit.framework.Assert.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
+    private lateinit var underTest: FromPrimaryBouncerTransitionInteractor
+
+    // Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our
+    // underTest interactor is provided to any classes that need it.
+    override var fromPrimaryBouncerTransitionInteractorLazy:
+        Lazy<FromPrimaryBouncerTransitionInteractor>? =
+        Lazy {
+            underTest
+        }
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+
+        underTest =
+            FromPrimaryBouncerTransitionInteractor(
+                transitionRepository = super.transitionRepository,
+                transitionInteractor = super.transitionInteractor,
+                scope = super.testScope.backgroundScope,
+                keyguardInteractor = super.keyguardInteractor,
+                flags = FakeFeatureFlags(),
+                keyguardSecurityModel = mock(),
+            )
+    }
+
+    @Test
+    fun testSurfaceBehindVisibility() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindVisibility)
+            runCurrent()
+
+            // Transition-specific surface visibility should be null ("don't care") initially.
+            assertEquals(
+                listOf(
+                    null,
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have any specific visibility.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.01f,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null,
+                    false, // Surface is only made visible once the bouncer UI animates out.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.99f,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null,
+                    false,
+                    true, // Surface should eventually be visible.
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testSurfaceBehindModel() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindModel)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have specific view params.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.01f,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.99f,
+                )
+            )
+            runCurrent()
+
+            assertEquals(3, values.size)
+            val model1percent = values[1]
+            val model99percent = values[2]
+
+            try {
+                // We should initially have an alpha of 0f when unlocking, so the surface is not
+                // visible
+                // while lockscreen UI animates out.
+                assertEquals(0f, model1percent!!.alpha)
+
+                // By the end it should probably be visible.
+                assertTrue(model99percent!!.alpha > 0f)
+            } catch (e: NullPointerException) {
+                fail("surfaceBehindModel was unexpectedly null.")
+            }
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
new file mode 100644
index 0000000..b8a2e9d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardBlueprintInteractorTest : SysuiTestCase() {
+    private lateinit var underTest: KeyguardBlueprintInteractor
+
+    @Mock private lateinit var keyguardBlueprintRepository: KeyguardBlueprintRepository
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest = KeyguardBlueprintInteractor(keyguardBlueprintRepository)
+    }
+
+    @Test
+    fun testRefreshBlueprint() {
+        underTest.refreshBlueprint()
+        verify(keyguardBlueprintRepository).refreshBlueprint()
+    }
+
+    @Test
+    fun testTransitionToBlueprint() {
+        underTest.transitionToBlueprint("abc")
+        verify(keyguardBlueprintRepository).applyBlueprint("abc")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 71e73cbd..93f208e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.hardware.biometrics.BiometricFaceConstants
 import android.os.Handler
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -24,24 +25,30 @@
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -68,6 +75,9 @@
     private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
     private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     private lateinit var faceAuthRepository: FakeDeviceEntryFaceAuthRepository
+    private lateinit var fakeUserRepository: FakeUserRepository
+    private lateinit var fakeDeviceEntryFingerprintAuthRepository:
+        FakeDeviceEntryFingerprintAuthRepository
 
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
@@ -83,10 +93,17 @@
         faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
         keyguardTransitionInteractor =
-            KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope)
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = keyguardTransitionRepository,
+                )
+                .keyguardTransitionInteractor
 
+        fakeDeviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+        fakeUserRepository = FakeUserRepository()
         underTest =
             SystemUIKeyguardFaceAuthInteractor(
+                mContext,
                 testScope.backgroundScope,
                 dispatcher,
                 faceAuthRepository,
@@ -110,13 +127,15 @@
                     mock(KeyguardStateController::class.java),
                     bouncerRepository,
                     mock(BiometricSettingsRepository::class.java),
-                    FakeDeviceEntryFingerprintAuthRepository(),
                     FakeSystemClock(),
+                    keyguardUpdateMonitor,
                 ),
                 keyguardTransitionInteractor,
                 featureFlags,
                 FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
                 keyguardUpdateMonitor,
+                fakeDeviceEntryFingerprintAuthRepository,
+                fakeUserRepository,
             )
     }
 
@@ -141,6 +160,22 @@
         }
 
     @Test
+    fun whenFaceIsLockedOutAnyAttemptsToTriggerFaceAuthMustProvideLockoutError() =
+        testScope.runTest {
+            underTest.start()
+            val authenticationStatus = collectLastValue(underTest.authenticationStatus)
+            faceAuthRepository.setLockedOut(true)
+
+            underTest.onDeviceLifted()
+
+            val outputValue = authenticationStatus()!! as ErrorFaceAuthenticationStatus
+            assertThat(outputValue.msgId)
+                .isEqualTo(BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT)
+            assertThat(outputValue.msg).isEqualTo("Face Unlock unavailable")
+            assertThat(faceAuthRepository.runningAuthRequest.value).isNull()
+        }
+
+    @Test
     fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() =
         testScope.runTest {
             underTest.start()
@@ -181,6 +216,38 @@
         }
 
     @Test
+    fun faceAuthIsPausedWhenUserSwitchingIsInProgress() =
+        testScope.runTest {
+            underTest.start()
+
+            fakeUserRepository.setUserSwitching(false)
+            runCurrent()
+            fakeUserRepository.setUserSwitching(true)
+            runCurrent()
+
+            assertThat(faceAuthRepository.isFaceAuthPaused()).isTrue()
+        }
+
+    @Test
+    fun faceAuthIsUnpausedWhenUserSwitchingIsInComplete() =
+        testScope.runTest {
+            underTest.start()
+
+            // previously running
+            fakeUserRepository.setUserSwitching(true)
+            runCurrent()
+            fakeUserRepository.setUserSwitching(false)
+            runCurrent()
+
+            assertThat(faceAuthRepository.isFaceAuthPaused()).isFalse()
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value!!.first)
+                .isEqualTo(FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING)
+            assertThat(faceAuthRepository.runningAuthRequest.value!!.second).isEqualTo(true)
+        }
+
+    @Test
     fun faceAuthIsRequestedWhenPrimaryBouncerIsVisible() =
         testScope.runTest {
             underTest.start()
@@ -309,4 +376,15 @@
             assertThat(faceAuthRepository.runningAuthRequest.value)
                 .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false))
         }
+
+    @Test
+    fun faceUnlockIsDisabledWhenFpIsLockedOut() =
+        testScope.runTest {
+            underTest.start()
+
+            fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+            runCurrent()
+
+            assertThat(faceAuthRepository.wasDisabled).isTrue()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 0d695aa..4b09468 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -22,11 +22,12 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
 import com.google.common.truth.Truth.assertThat
@@ -50,6 +51,7 @@
     private lateinit var underTest: KeyguardInteractor
     private lateinit var repository: FakeKeyguardRepository
     private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+    private lateinit var configurationRepository: FakeConfigurationRepository
 
     @Before
     fun setUp() {
@@ -59,12 +61,14 @@
         testScope = TestScope()
         repository = FakeKeyguardRepository()
         bouncerRepository = FakeKeyguardBouncerRepository()
+        configurationRepository = FakeConfigurationRepository()
         underTest =
             KeyguardInteractor(
                 repository,
                 commandQueue,
                 featureFlags,
                 bouncerRepository,
+                configurationRepository,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
new file mode 100644
index 0000000..a3f7fc5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.media.AudioManager
+import android.media.session.MediaSessionLegacyHelper
+import android.view.KeyEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.media.controls.util.MediaSessionLegacyHelperWrapper
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+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.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardKeyEventInteractorTest : SysuiTestCase() {
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    private val actionDownVolumeDownKeyEvent =
+        KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN)
+    private val actionDownVolumeUpKeyEvent =
+        KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
+    private val backKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK)
+    private val awakeWakefulnessMode =
+        WakefulnessModel(WakefulnessState.AWAKE, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+    private val asleepWakefulnessMode =
+        WakefulnessModel(WakefulnessState.ASLEEP, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+
+    private lateinit var keyguardInteractorWithDependencies:
+        KeyguardInteractorFactory.WithDependencies
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    @Mock private lateinit var shadeController: ShadeController
+    @Mock private lateinit var mediaSessionLegacyHelperWrapper: MediaSessionLegacyHelperWrapper
+    @Mock private lateinit var mediaSessionLegacyHelper: MediaSessionLegacyHelper
+    @Mock private lateinit var backActionInteractor: BackActionInteractor
+
+    private lateinit var underTest: KeyguardKeyEventInteractor
+
+    @Before
+    fun setup() {
+        whenever(mediaSessionLegacyHelperWrapper.getHelper(any()))
+            .thenReturn(mediaSessionLegacyHelper)
+        keyguardInteractorWithDependencies = KeyguardInteractorFactory.create()
+        underTest =
+            KeyguardKeyEventInteractor(
+                context,
+                statusBarStateController,
+                keyguardInteractorWithDependencies.keyguardInteractor,
+                statusBarKeyguardViewManager,
+                shadeController,
+                mediaSessionLegacyHelperWrapper,
+                backActionInteractor,
+            )
+    }
+
+    @Test
+    fun dispatchKeyEvent_volumeKey_dozing_handlesEvents() {
+        whenever(statusBarStateController.isDozing).thenReturn(true)
+
+        assertThat(underTest.dispatchKeyEvent(actionDownVolumeDownKeyEvent)).isTrue()
+        verify(mediaSessionLegacyHelper)
+            .sendVolumeKeyEvent(
+                eq(actionDownVolumeDownKeyEvent),
+                eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+                eq(true)
+            )
+
+        assertThat(underTest.dispatchKeyEvent(actionDownVolumeUpKeyEvent)).isTrue()
+        verify(mediaSessionLegacyHelper)
+            .sendVolumeKeyEvent(
+                eq(actionDownVolumeUpKeyEvent),
+                eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+                eq(true)
+            )
+    }
+
+    @Test
+    fun dispatchKeyEvent_volumeKey_notDozing_doesNotHandleEvents() {
+        whenever(statusBarStateController.isDozing).thenReturn(false)
+
+        assertThat(underTest.dispatchKeyEvent(actionDownVolumeDownKeyEvent)).isFalse()
+        verify(mediaSessionLegacyHelper, never())
+            .sendVolumeKeyEvent(
+                eq(actionDownVolumeDownKeyEvent),
+                eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+                eq(true)
+            )
+
+        assertThat(underTest.dispatchKeyEvent(actionDownVolumeUpKeyEvent)).isFalse()
+        verify(mediaSessionLegacyHelper, never())
+            .sendVolumeKeyEvent(
+                eq(actionDownVolumeUpKeyEvent),
+                eq(AudioManager.USE_DEFAULT_STREAM_TYPE),
+                eq(true)
+            )
+    }
+
+    @Test
+    fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_collapsesShade() {
+        keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
+
+        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
+        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+        verify(shadeController).animateCollapseShadeForced()
+    }
+
+    @Test
+    fun dispatchKeyEvent_menuActionUp_interactiveShadeLocked_collapsesShade() {
+        keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
+        whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
+
+        // action down: does NOT collapse the shade
+        val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU)
+        assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+        verify(shadeController, never()).animateCollapseShadeForced()
+
+        // action up: collapses the shade
+        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
+        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+        verify(shadeController).animateCollapseShadeForced()
+    }
+
+    @Test
+    fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_neverCollapsesShade() {
+        keyguardInteractorWithDependencies.repository.setWakefulnessModel(asleepWakefulnessMode)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
+
+        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
+        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse()
+        verify(shadeController, never()).animateCollapseShadeForced()
+    }
+
+    @Test
+    fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_collapsesShade() {
+        keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+        // action down: does NOT collapse the shade
+        val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE)
+        assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+        verify(shadeController, never()).animateCollapseShadeForced()
+
+        // action up: collapses the shade
+        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE)
+        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+        verify(shadeController).animateCollapseShadeForced()
+    }
+
+    @Test
+    fun dispatchKeyEventPreIme_back_keyguard_onBackRequested() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()).thenReturn(true)
+
+        whenever(backActionInteractor.onBackRequested()).thenReturn(false)
+        assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isFalse()
+        verify(backActionInteractor).onBackRequested()
+        clearInvocations(backActionInteractor)
+
+        whenever(backActionInteractor.onBackRequested()).thenReturn(true)
+        assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isTrue()
+        verify(backActionInteractor).onBackRequested()
+    }
+
+    @Test
+    fun dispatchKeyEventPreIme_back_keyguard_SBKVMdoesNotHandle_neverOnBackRequested() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()).thenReturn(false)
+        whenever(backActionInteractor.onBackRequested()).thenReturn(true)
+
+        assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isFalse()
+        verify(backActionInteractor, never()).onBackRequested()
+    }
+
+    @Test
+    fun dispatchKeyEventPreIme_back_shade_neverOnBackRequested() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+        whenever(statusBarKeyguardViewManager.dispatchBackKeyEventPreIme()).thenReturn(true)
+        whenever(backActionInteractor.onBackRequested()).thenReturn(true)
+
+        assertThat(underTest.dispatchKeyEventPreIme(backKeyEvent)).isFalse()
+        verify(backActionInteractor, never()).onBackRequested()
+    }
+
+    @Test
+    fun interceptMediaKey_keyguard_SBKVMdoesNotHandle_doesNotHandleMediaKey() {
+        val keyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(statusBarKeyguardViewManager.interceptMediaKey(eq(keyEvent))).thenReturn(false)
+
+        assertThat(underTest.interceptMediaKey(keyEvent)).isFalse()
+        verify(statusBarKeyguardViewManager).interceptMediaKey(eq(keyEvent))
+    }
+
+    @Test
+    fun interceptMediaKey_keyguard_handleMediaKey() {
+        val keyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(statusBarKeyguardViewManager.interceptMediaKey(eq(keyEvent))).thenReturn(true)
+
+        assertThat(underTest.interceptMediaKey(keyEvent)).isTrue()
+        verify(statusBarKeyguardViewManager).interceptMediaKey(eq(keyEvent))
+    }
+
+    @Test
+    fun interceptMediaKey_shade_doesNotHandleMediaKey() {
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+
+        assertThat(
+                underTest.interceptMediaKey(
+                    KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_VOLUME_UP)
+                )
+            )
+            .isFalse()
+        verify(statusBarKeyguardViewManager, never()).interceptMediaKey(any())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index f63be61..0050d64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -194,9 +194,10 @@
             underTest.onLongPress()
             assertThat(isMenuVisible).isTrue()
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach { broadcastReceiver ->
-                broadcastReceiver.onReceive(context, Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
+            )
 
             assertThat(isMenuVisible).isFalse()
         }
@@ -291,10 +292,11 @@
                 appContext = mContext,
                 scope = testScope.backgroundScope,
                 transitionInteractor =
-                    KeyguardTransitionInteractor(
-                        keyguardTransitionRepository,
-                        testScope.backgroundScope
-                    ),
+                    KeyguardTransitionInteractorFactory.create(
+                            scope = testScope.backgroundScope,
+                            repository = keyguardTransitionRepository,
+                        )
+                        .keyguardTransitionInteractor,
                 repository = keyguardRepository,
                 logger = logger,
                 featureFlags =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 2c90d53..3858cfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -39,17 +39,13 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.any
@@ -226,7 +222,6 @@
     @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
     @Mock private lateinit var expandable: Expandable
     @Mock private lateinit var launchAnimator: DialogLaunchAnimator
-    @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
     @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
 
@@ -304,7 +299,6 @@
             )
         val featureFlags =
             FakeFeatureFlags().apply {
-                set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
                 set(Flags.FACE_AUTH_REFACTOR, true)
             }
         val testDispatcher = StandardTestDispatcher()
@@ -312,26 +306,10 @@
         underTest =
             KeyguardQuickAffordanceInteractor(
                 keyguardInteractor =
-                    KeyguardInteractor(
-                        repository = FakeKeyguardRepository(),
-                        commandQueue = commandQueue,
-                        featureFlags = featureFlags,
-                        bouncerRepository = FakeKeyguardBouncerRepository(),
-                    ),
-                registry =
-                    FakeKeyguardQuickAffordanceRegistry(
-                        mapOf(
-                            KeyguardQuickAffordancePosition.BOTTOM_START to
-                                listOf(
-                                    homeControls,
-                                ),
-                            KeyguardQuickAffordancePosition.BOTTOM_END to
-                                listOf(
-                                    quickAccessWallet,
-                                    qrCodeScanner,
-                                ),
-                        ),
-                    ),
+                    KeyguardInteractorFactory.create(
+                            featureFlags = featureFlags,
+                        )
+                        .keyguardInteractor,
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
@@ -351,6 +329,7 @@
     @Test
     fun onQuickAffordanceTriggered() =
         testScope.runTest {
+            val key = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
             setUpMocks(
                 needStrongAuthAfterBoot = needStrongAuthAfterBoot,
                 keyguardIsUnlocked = keyguardIsUnlocked,
@@ -372,11 +351,11 @@
                     KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
                 }
 
-        underTest.onQuickAffordanceTriggered(
-            configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
-            expandable = expandable,
-            slotId = "",
-        )
+            underTest.onQuickAffordanceTriggered(
+                configKey = "${KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()}::${key}",
+                expandable = expandable,
+                slotId = "",
+            )
 
             if (startActivity) {
                 if (needsToUnlockFirst) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 7cab680..e8542aad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -41,11 +41,9 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
 import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
@@ -54,7 +52,6 @@
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.mock
@@ -85,7 +82,6 @@
     @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var launchAnimator: DialogLaunchAnimator
-    @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
     @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
 
@@ -105,6 +101,15 @@
         MockitoAnnotations.initMocks(this)
 
         overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
+        overrideResource(
+            R.array.config_keyguardQuickAffordanceDefaults,
+            arrayOf(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + ":" +
+                    BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + ":" +
+                    BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+            )
+        )
 
         repository = FakeKeyguardRepository()
         repository.setKeyguardShowing(true)
@@ -167,33 +172,17 @@
             )
         featureFlags =
             FakeFeatureFlags().apply {
-                set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
                 set(Flags.FACE_AUTH_REFACTOR, true)
             }
 
+        val withDeps =
+            KeyguardInteractorFactory.create(
+                featureFlags = featureFlags,
+                repository = repository,
+            )
         underTest =
             KeyguardQuickAffordanceInteractor(
-                keyguardInteractor =
-                    KeyguardInteractor(
-                        repository = repository,
-                        commandQueue = commandQueue,
-                        featureFlags = featureFlags,
-                        bouncerRepository = FakeKeyguardBouncerRepository(),
-                    ),
-                registry =
-                    FakeKeyguardQuickAffordanceRegistry(
-                        mapOf(
-                            KeyguardQuickAffordancePosition.BOTTOM_START to
-                                listOf(
-                                    homeControls,
-                                ),
-                            KeyguardQuickAffordancePosition.BOTTOM_END to
-                                listOf(
-                                    quickAccessWallet,
-                                    qrCodeScanner,
-                                ),
-                        ),
-                    ),
+                keyguardInteractor = withDeps.keyguardInteractor,
                 lockPatternUtils = lockPatternUtils,
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
@@ -229,7 +218,9 @@
             assertThat(collectedValue())
                 .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
             val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
-            assertThat(visibleModel.configKey).isEqualTo(configKey)
+            assertThat(visibleModel.configKey).isEqualTo(
+                "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::${configKey}"
+            )
             assertThat(visibleModel.icon).isEqualTo(ICON)
             assertThat(visibleModel.icon.contentDescription)
                 .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
@@ -254,7 +245,9 @@
             assertThat(collectedValue())
                 .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
             val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
-            assertThat(visibleModel.configKey).isEqualTo(configKey)
+            assertThat(visibleModel.configKey).isEqualTo(
+                "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END}::${configKey}"
+            )
             assertThat(visibleModel.icon).isEqualTo(ICON)
             assertThat(visibleModel.icon.contentDescription)
                 .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
@@ -300,24 +293,6 @@
         }
 
     @Test
-    fun quickAffordance_hiddenWhenQuickSettingsIsVisible() =
-        testScope.runTest {
-            repository.setQuickSettingsVisible(true)
-            quickAccessWallet.setState(
-                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
-                    icon = ICON,
-                )
-            )
-
-            val collectedValue =
-                collectLastValue(
-                    underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
-                )
-
-            assertThat(collectedValue()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
-        }
-
-    @Test
     fun quickAffordance_hiddenWhenUserIsInLockdownMode() =
         testScope.runTest {
             biometricSettingsRepository.setIsUserInLockdown(true)
@@ -391,7 +366,9 @@
             assertThat(collectedValue())
                 .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
             val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
-            assertThat(visibleModel.configKey).isEqualTo(configKey)
+            assertThat(visibleModel.configKey).isEqualTo(
+                "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::${configKey}"
+            )
             assertThat(visibleModel.icon).isEqualTo(ICON)
             assertThat(visibleModel.icon.contentDescription)
                 .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
@@ -405,8 +382,6 @@
                 R.array.config_keyguardQuickAffordanceDefaults,
                 arrayOf<String>(),
             )
-
-            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
             homeControls.setState(
                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
             )
@@ -547,7 +522,6 @@
     @Test
     fun unselect_one() =
         testScope.runTest {
-            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
             homeControls.setState(
                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
             )
@@ -624,7 +598,6 @@
     @Test
     fun useLongPress_whenDocked_isFalse() =
         testScope.runTest {
-            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
             dockManager.setIsDocked(true)
 
             val useLongPress by collectLastValue(underTest.useLongPress())
@@ -635,7 +608,6 @@
     @Test
     fun useLongPress_whenNotDocked_isTrue() =
         testScope.runTest {
-            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
             dockManager.setIsDocked(false)
 
             val useLongPress by collectLastValue(underTest.useLongPress())
@@ -646,7 +618,6 @@
     @Test
     fun useLongPress_whenNotDocked_isTrue_changedTo_whenDocked_isFalse() =
         testScope.runTest {
-            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
             dockManager.setIsDocked(false)
             val firstUseLongPress by collectLastValue(underTest.useLongPress())
             runCurrent()
@@ -664,7 +635,6 @@
     @Test
     fun unselect_all() =
         testScope.runTest {
-            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
             homeControls.setState(
                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
new file mode 100644
index 0000000..fdcc66b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardSurfaceBehindInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: KeyguardSurfaceBehindInteractor
+    private lateinit var repository: FakeKeyguardSurfaceBehindRepository
+
+    @Mock
+    private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+    @Mock
+    private lateinit var fromPrimaryBouncerTransitionInteractor:
+        FromPrimaryBouncerTransitionInteractor
+
+    private val lockscreenSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.33f)
+    private val primaryBouncerSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.66f)
+
+    private val testScope = TestScope()
+
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+
+        whenever(fromLockscreenTransitionInteractor.surfaceBehindModel)
+            .thenReturn(flowOf(lockscreenSurfaceBehindModel))
+        whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindModel)
+            .thenReturn(flowOf(primaryBouncerSurfaceBehindModel))
+
+        transitionRepository = FakeKeyguardTransitionRepository()
+
+        transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                )
+                .keyguardTransitionInteractor
+
+        repository = FakeKeyguardSurfaceBehindRepository()
+        underTest =
+            KeyguardSurfaceBehindInteractor(
+                repository = repository,
+                fromLockscreenInteractor = fromLockscreenTransitionInteractor,
+                fromPrimaryBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+                transitionInteractor = transitionInteractor,
+            )
+    }
+
+    @Test
+    fun viewParamsSwitchToCorrectFlow() =
+        testScope.runTest {
+            val values by collectValues(underTest.viewParams)
+
+            // Start on the LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            // We're on LOCKSCREEN; we should be using the default params.
+            assertEquals(1, values.size)
+            assertTrue(values[0].alpha == 0f)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We're going from LOCKSCREEN -> GONE, we should be using the lockscreen interactor's
+            // surface behind model.
+            assertEquals(2, values.size)
+            assertEquals(values[1], lockscreenSurfaceBehindModel)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We're going from PRIMARY_BOUNCER -> GONE, we should be using the bouncer interactor's
+            // surface behind model.
+            assertEquals(3, values.size)
+            assertEquals(values[2], primaryBouncerSurfaceBehindModel)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // Once PRIMARY_BOUNCER -> GONE finishes, we should be using default params, which is
+            // alpha=1f when we're GONE.
+            assertEquals(4, values.size)
+            assertEquals(1f, values[3].alpha)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index fa4941c..9e9c25e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -17,9 +17,9 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import com.android.systemui.RoboPilotTest
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -54,7 +54,10 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        underTest = KeyguardTransitionInteractor(repository, testScope.backgroundScope)
+        underTest = KeyguardTransitionInteractorFactory.create(
+                scope = testScope.backgroundScope,
+                repository = repository,
+        ).keyguardTransitionInteractor
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
new file mode 100644
index 0000000..8db19ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+
+open class KeyguardTransitionInteractorTestCase : SysuiTestCase() {
+    val testDispatcher = StandardTestDispatcher()
+    val testScope = TestScope(testDispatcher)
+
+    lateinit var keyguardRepository: FakeKeyguardRepository
+    lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+    lateinit var keyguardInteractor: KeyguardInteractor
+    lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+    /**
+     * Replace these lazy providers with non-null ones if you want test dependencies to use a real
+     * instance of the interactor for the test.
+     */
+    open var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
+        null
+    open var fromPrimaryBouncerTransitionInteractorLazy:
+        Lazy<FromPrimaryBouncerTransitionInteractor>? =
+        null
+
+    open fun setUp() {
+        keyguardRepository = FakeKeyguardRepository()
+        transitionRepository = FakeKeyguardTransitionRepository()
+
+        keyguardInteractor =
+            KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor
+
+        transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    repository = transitionRepository,
+                    keyguardInteractor = keyguardInteractor,
+                    scope = testScope.backgroundScope,
+                    fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractorLazy
+                            ?: Lazy { mock() },
+                    fromPrimaryBouncerTransitionInteractor =
+                        fromPrimaryBouncerTransitionInteractorLazy ?: Lazy { mock() },
+                )
+                .also {
+                    fromLockscreenTransitionInteractorLazy = it.fromLockscreenTransitionInteractor
+                    fromPrimaryBouncerTransitionInteractorLazy =
+                        it.fromPrimaryBouncerTransitionInteractor
+                }
+                .keyguardTransitionInteractor
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 30dba23..4b221a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -19,14 +19,13 @@
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.keyguard.TestScopeProvider
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
@@ -39,7 +38,6 @@
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
@@ -58,6 +56,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -74,10 +73,10 @@
     private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var shadeRepository: ShadeRepository
     private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+    private lateinit var featureFlags: FakeFeatureFlags
 
     // Used to verify transition requests for test output
-    @Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository
-    @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
 
     private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
@@ -90,101 +89,123 @@
         FromAlternateBouncerTransitionInteractor
     private lateinit var fromPrimaryBouncerTransitionInteractor:
         FromPrimaryBouncerTransitionInteractor
+    private lateinit var fromDreamingLockscreenHostedTransitionInteractor:
+        FromDreamingLockscreenHostedTransitionInteractor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        testScope = TestScope()
+        testScope = TestScopeProvider.getTestScope()
 
         keyguardRepository = FakeKeyguardRepository()
         bouncerRepository = FakeKeyguardBouncerRepository()
         shadeRepository = FakeShadeRepository()
-        transitionRepository = FakeKeyguardTransitionRepository()
+        transitionRepository = spy(FakeKeyguardTransitionRepository())
 
         whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
 
-        val featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.FACE_AUTH_REFACTOR, true)
+                set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
+            }
+
+        transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope,
+                    repository = transitionRepository,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+                    fromPrimaryBouncerTransitionInteractor = {
+                        fromPrimaryBouncerTransitionInteractor
+                    },
+                )
+                .keyguardTransitionInteractor
+
         fromLockscreenTransitionInteractor =
             FromLockscreenTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                shadeRepository = shadeRepository,
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromLockscreenTransitionInteractor.start()
-
-        fromDreamingTransitionInteractor =
-            FromDreamingTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromDreamingTransitionInteractor.start()
-
-        fromAodTransitionInteractor =
-            FromAodTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromAodTransitionInteractor.start()
-
-        fromGoneTransitionInteractor =
-            FromGoneTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromGoneTransitionInteractor.start()
-
-        fromDozingTransitionInteractor =
-            FromDozingTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromDozingTransitionInteractor.start()
-
-        fromOccludedTransitionInteractor =
-            FromOccludedTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromOccludedTransitionInteractor.start()
-
-        fromAlternateBouncerTransitionInteractor =
-            FromAlternateBouncerTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-            )
-        fromAlternateBouncerTransitionInteractor.start()
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                    flags = featureFlags,
+                    shadeRepository = shadeRepository,
+                )
+                .apply { start() }
 
         fromPrimaryBouncerTransitionInteractor =
             FromPrimaryBouncerTransitionInteractor(
-                scope = testScope,
-                keyguardInteractor = createKeyguardInteractor(featureFlags),
-                keyguardTransitionRepository = mockTransitionRepository,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(transitionRepository, testScope.backgroundScope),
-                keyguardSecurityModel = keyguardSecurityModel,
-            )
-        fromPrimaryBouncerTransitionInteractor.start()
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                    flags = featureFlags,
+                    keyguardSecurityModel = keyguardSecurityModel,
+                )
+                .apply { start() }
+
+        fromDreamingTransitionInteractor =
+            FromDreamingTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
+
+        fromDreamingLockscreenHostedTransitionInteractor =
+            FromDreamingLockscreenHostedTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
+
+        fromAodTransitionInteractor =
+            FromAodTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
+
+        fromGoneTransitionInteractor =
+            FromGoneTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
+
+        fromDozingTransitionInteractor =
+            FromDozingTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
+
+        fromOccludedTransitionInteractor =
+            FromOccludedTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
+
+        fromAlternateBouncerTransitionInteractor =
+            FromAlternateBouncerTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
     }
 
     @Test
@@ -203,7 +224,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to PRIMARY_BOUNCER should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -230,7 +251,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -257,7 +278,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -288,7 +309,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DREAMING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -300,6 +321,38 @@
         }
 
     @Test
+    fun lockscreenToDreamingLockscreenHosted() =
+        testScope.runTest {
+            // GIVEN a device that is not dreaming or dozing
+            keyguardRepository.setDreamingWithOverlay(false)
+            keyguardRepository.setWakefulnessModel(startingToWake())
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+            )
+            runCurrent()
+
+            // GIVEN a prior transition has run to LOCKSCREEN
+            runTransition(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
+
+            // WHEN the device begins to dream and the dream is lockscreen hosted
+            keyguardRepository.setDreamingWithOverlay(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            advanceUntilIdle()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                }
+            // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
+            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
+            assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun lockscreenToDozing() =
         testScope.runTest {
             // GIVEN a device with AOD not available
@@ -315,7 +368,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -342,7 +395,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -354,6 +407,149 @@
         }
 
     @Test
+    fun dreamingLockscreenHostedToLockscreen() =
+        testScope.runTest {
+            // GIVEN a device dreaming with the lockscreen hosted dream and not dozing
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            keyguardRepository.setWakefulnessModel(startingToWake())
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+            )
+            runCurrent()
+
+            // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
+            runTransition(KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+
+            // WHEN the lockscreen hosted dream stops
+            keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+            advanceUntilIdle()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                }
+            // THEN a transition to Lockscreen should occur
+            assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    fun dreamingLockscreenHostedToGone() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
+            runTransition(KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+
+            // WHEN biometrics succeeds with wake and unlock from dream mode
+            keyguardRepository.setBiometricUnlockState(
+                BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+            )
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                }
+            // THEN a transition to Gone should occur
+            assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+            assertThat(info.to).isEqualTo(KeyguardState.GONE)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    fun dreamingLockscreenHostedToPrimaryBouncer() =
+        testScope.runTest {
+            // GIVEN a device dreaming with lockscreen hosted dream and not dozing
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            keyguardRepository.setWakefulnessModel(startingToWake())
+            runCurrent()
+
+            // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
+            runTransition(KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+
+            // WHEN the primary bouncer is set to show
+            bouncerRepository.setPrimaryShow(true)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                }
+            // THEN a transition to PRIMARY_BOUNCER should occur
+            assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    fun dreamingLockscreenHostedToDozing() =
+        testScope.runTest {
+            // GIVEN a device is dreaming with lockscreen hosted dream
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            runCurrent()
+
+            // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
+            runTransition(KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+
+            // WHEN the device begins to sleep
+            keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(from = DozeStateModel.INITIALIZED, to = DozeStateModel.DOZE)
+            )
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                }
+            // THEN a transition to DOZING should occur
+            assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    fun dreamingLockscreenHostedToOccluded() =
+        testScope.runTest {
+            // GIVEN device is dreaming with lockscreen hosted dream and not occluded
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            runCurrent()
+
+            // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
+            runTransition(KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+
+            // WHEN the keyguard is occluded and the lockscreen hosted dream stops
+            keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                }
+            // THEN a transition to OCCLUDED should occur
+            assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun dozingToLockscreen() =
         testScope.runTest {
             // GIVEN a prior transition has run to DOZING
@@ -365,7 +561,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -390,14 +586,14 @@
                 )
             )
             runCurrent()
-            reset(mockTransitionRepository)
+            reset(transitionRepository)
 
             // WHEN a signal comes that dreaming is enabled
             keyguardRepository.setDreamingWithOverlay(true)
             advanceUntilIdle()
 
             // THEN the transition is ignored
-            verify(mockTransitionRepository, never()).startTransition(any(), anyBoolean())
+            verify(transitionRepository, never()).startTransition(any(), anyBoolean())
 
             coroutineContext.cancelChildren()
         }
@@ -414,7 +610,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -441,7 +637,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -468,7 +664,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -491,7 +687,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -522,7 +718,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DREAMING should occur
             assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -534,6 +730,38 @@
         }
 
     @Test
+    fun goneToDreamingLockscreenHosted() =
+        testScope.runTest {
+            // GIVEN a device that is not dreaming or dozing
+            keyguardRepository.setDreamingWithOverlay(false)
+            keyguardRepository.setWakefulnessModel(startingToWake())
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+            )
+            runCurrent()
+
+            // GIVEN a prior transition has run to GONE
+            runTransition(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+
+            // WHEN the device begins to dream with the lockscreen hosted dream
+            keyguardRepository.setDreamingWithOverlay(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            advanceUntilIdle()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                }
+            // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
+            assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.GONE)
+            assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun alternateBouncerToPrimaryBouncer() =
         testScope.runTest {
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
@@ -545,7 +773,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to PRIMARY_BOUNCER should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -574,7 +802,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -604,7 +832,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -632,7 +860,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -660,7 +888,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AOD should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -688,7 +916,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to DOZING should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -715,7 +943,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -727,6 +955,34 @@
         }
 
     @Test
+    fun primaryBouncerToDreamingLockscreenHosted() =
+        testScope.runTest {
+            // GIVEN device dreaming with the lockscreen hosted dream and not dozing
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            keyguardRepository.setWakefulnessModel(startingToWake())
+
+            // GIVEN a prior transition has run to PRIMARY_BOUNCER
+            bouncerRepository.setPrimaryShow(true)
+            runTransition(KeyguardState.DREAMING_LOCKSCREEN_HOSTED, KeyguardState.PRIMARY_BOUNCER)
+
+            // WHEN the primary bouncer stops showing and lockscreen hosted dream still active
+            bouncerRepository.setPrimaryShow(false)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                }
+            // THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur
+            assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
+            assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun occludedToGone() =
         testScope.runTest {
             // GIVEN a device on lockscreen
@@ -746,7 +1002,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to GONE should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -775,7 +1031,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to LOCKSCREEN should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -801,7 +1057,7 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
             // THEN a transition to AlternateBouncer should occur
             assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -830,9 +1086,9 @@
 
             val info =
                 withArgCaptor<TransitionInfo> {
-                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
                 }
-            // THEN a transition to AlternateBouncer should occur
+            // THEN a transition to OCCLUDED should occur
             assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
             assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
             assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
@@ -841,6 +1097,61 @@
             coroutineContext.cancelChildren()
         }
 
+    @Test
+    fun dozingToOccluded() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DOZING
+            runTransition(KeyguardState.LOCKSCREEN, KeyguardState.DOZING)
+            runCurrent()
+
+            // WHEN the keyguard is occluded and device wakes up
+            keyguardRepository.setKeyguardOccluded(true)
+            keyguardRepository.setWakefulnessModel(startingToWake())
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                }
+            // THEN a transition to OCCLUDED should occur
+            assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DOZING)
+            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    fun aodToOccluded() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to AOD
+            runTransition(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+            runCurrent()
+
+            // WHEN the keyguard is occluded and aod ends
+            keyguardRepository.setKeyguardOccluded(true)
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(
+                    from = DozeStateModel.DOZE_AOD,
+                    to = DozeStateModel.FINISH,
+                )
+            )
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(transitionRepository).startTransition(capture(), anyBoolean())
+                }
+            // THEN a transition to OCCLUDED should occur
+            assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.AOD)
+            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
     private fun startingToWake() =
         WakefulnessModel(
             WakefulnessState.STARTING_TO_WAKE,
@@ -855,13 +1166,13 @@
             WakeSleepReason.OTHER
         )
 
-    private fun createKeyguardInteractor(featureFlags: FeatureFlags): KeyguardInteractor {
-        return KeyguardInteractor(
-            keyguardRepository,
-            commandQueue,
-            featureFlags,
-            bouncerRepository,
-        )
+    private fun createKeyguardInteractor(): KeyguardInteractor {
+        return KeyguardInteractorFactory.create(
+                featureFlags = featureFlags,
+                repository = keyguardRepository,
+                bouncerRepository = bouncerRepository,
+            )
+            .keyguardInteractor
     }
 
     private suspend fun TestScope.runTransition(from: KeyguardState, to: KeyguardState) {
@@ -892,6 +1203,6 @@
             )
         )
         runCurrent()
-        reset(mockTransitionRepository)
+        reset(transitionRepository)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 08e99dc..906d948 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -27,26 +27,40 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.statusbar.LightRevealEffect
 import com.android.systemui.statusbar.LightRevealScrim
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.Spy
 
 @SmallTest
 @RoboPilotTest
+@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class LightRevealScrimInteractorTest : SysuiTestCase() {
     private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository()
-    private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
+
+    @Spy private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
+
+    private val testScope = TestScope()
 
     private val keyguardTransitionInteractor =
-        KeyguardTransitionInteractor(fakeKeyguardTransitionRepository, TestScope().backgroundScope)
+        KeyguardTransitionInteractorFactory.create(
+                scope = testScope.backgroundScope,
+                repository = fakeKeyguardTransitionRepository,
+            )
+            .keyguardTransitionInteractor
 
     private lateinit var underTest: LightRevealScrimInteractor
 
@@ -65,9 +79,9 @@
         MockitoAnnotations.initMocks(this)
         underTest =
             LightRevealScrimInteractor(
-                fakeKeyguardTransitionRepository,
                 keyguardTransitionInteractor,
-                fakeLightRevealScrimRepository
+                fakeLightRevealScrimRepository,
+                testScope.backgroundScope
             )
     }
 
@@ -106,52 +120,36 @@
         }
 
     @Test
-    fun revealAmount_invertedWhenAppropriate() =
-        runTest(UnconfinedTestDispatcher()) {
-            val values = mutableListOf<Float>()
-            val job = underTest.revealAmount.onEach(values::add).launchIn(this)
-
+    fun lightRevealEffect_startsAnimationOnlyForDifferentStateTargets() =
+        testScope.runTest {
             fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
-                    from = KeyguardState.AOD,
-                    to = KeyguardState.LOCKSCREEN,
-                    value = 0.3f
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.OFF,
+                    to = KeyguardState.OFF
                 )
             )
-
-            assertEquals(values, listOf(0.3f))
+            runCurrent()
+            verify(fakeLightRevealScrimRepository, never()).startRevealAmountAnimator(anyBoolean())
 
             fakeKeyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.DOZING,
+                    to = KeyguardState.LOCKSCREEN
+                )
+            )
+            runCurrent()
+            verify(fakeLightRevealScrimRepository).startRevealAmountAnimator(true)
+
+            fakeKeyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
                     from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.AOD,
-                    value = 0.3f
+                    to = KeyguardState.DOZING
                 )
             )
-
-            assertEquals(values, listOf(0.3f, 0.7f))
-
-            job.cancel()
-        }
-
-    @Test
-    fun revealAmount_ignoresTransitionsThatDoNotAffectRevealAmount() =
-        runTest(UnconfinedTestDispatcher()) {
-            val values = mutableListOf<Float>()
-            val job = underTest.revealAmount.onEach(values::add).launchIn(this)
-
-            fakeKeyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(from = KeyguardState.DOZING, to = KeyguardState.AOD, value = 0.3f)
-            )
-
-            assertEquals(values, emptyList<Float>())
-
-            fakeKeyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(from = KeyguardState.AOD, to = KeyguardState.DOZING, value = 0.3f)
-            )
-
-            assertEquals(values, emptyList<Float>())
-
-            job.cancel()
+            runCurrent()
+            verify(fakeLightRevealScrimRepository).startRevealAmountAnimator(false)
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
deleted file mode 100644
index d622f1c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.interactor
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.SceneModel
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class LockscreenSceneInteractorTest : SysuiTestCase() {
-
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
-    private val sceneInteractor = utils.sceneInteractor()
-    private val authenticationInteractor =
-        utils.authenticationInteractor(
-            repository = utils.authenticationRepository(),
-        )
-    private val underTest =
-        utils.lockScreenSceneInteractor(
-            authenticationInteractor = authenticationInteractor,
-            sceneInteractor = sceneInteractor,
-            bouncerInteractor =
-                utils.bouncerInteractor(
-                    authenticationInteractor = authenticationInteractor,
-                    sceneInteractor = sceneInteractor,
-                ),
-        )
-
-    @Test
-    fun isDeviceLocked() =
-        testScope.runTest {
-            val isDeviceLocked by collectLastValue(underTest.isDeviceLocked)
-
-            authenticationInteractor.lockDevice()
-            assertThat(isDeviceLocked).isTrue()
-
-            authenticationInteractor.unlockDevice()
-            assertThat(isDeviceLocked).isFalse()
-        }
-
-    @Test
-    fun isSwipeToDismissEnabled_deviceLockedAndAuthMethodSwipe_true() =
-        testScope.runTest {
-            val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
-
-            authenticationInteractor.lockDevice()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-
-            assertThat(isSwipeToDismissEnabled).isTrue()
-        }
-
-    @Test
-    fun isSwipeToDismissEnabled_deviceUnlockedAndAuthMethodSwipe_false() =
-        testScope.runTest {
-            val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
-
-            authenticationInteractor.unlockDevice()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-
-            assertThat(isSwipeToDismissEnabled).isFalse()
-        }
-
-    @Test
-    fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.lockDevice()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
-            underTest.dismissLockscreen()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-        }
-
-    @Test
-    fun dismissLockScreen_deviceUnlocked_switchesToGone() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.unlockDevice()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
-            underTest.dismissLockscreen()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-        }
-
-    @Test
-    fun dismissLockScreen_deviceLockedWithInsecureAuthMethod_switchesToGone() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.lockDevice()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
-            underTest.dismissLockscreen()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-        }
-
-    @Test
-    fun deviceLockedInNonLockScreenScene_switchesToLockScreenScene() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            runCurrent()
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
-            runCurrent()
-            authenticationInteractor.unlockDevice()
-            runCurrent()
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-
-            authenticationInteractor.lockDevice()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-        }
-
-    @Test
-    fun deviceBiometricUnlockedInLockScreen_bypassEnabled_switchesToGone() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
-            if (!authenticationInteractor.isBypassEnabled.value) {
-                authenticationInteractor.toggleBypassEnabled()
-            }
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
-            authenticationInteractor.biometricUnlock()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-        }
-
-    @Test
-    fun deviceBiometricUnlockedInLockScreen_bypassNotEnabled_doesNotSwitch() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.lockDevice()
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
-            if (authenticationInteractor.isBypassEnabled.value) {
-                authenticationInteractor.toggleBypassEnabled()
-            }
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
-            authenticationInteractor.biometricUnlock()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-        }
-
-    @Test
-    fun switchFromLockScreenToGone_authMethodSwipe_unlocksDevice() =
-        testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            assertThat(isUnlocked).isFalse()
-
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
-
-            assertThat(isUnlocked).isTrue()
-        }
-
-    @Test
-    fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
-        testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            assertThat(isUnlocked).isFalse()
-
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
-
-            assertThat(isUnlocked).isFalse()
-        }
-
-    @Test
-    fun switchFromNonLockScreenToGone_authMethodSwipe_doesNotUnlockDevice() =
-        testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            runCurrent()
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Shade))
-            runCurrent()
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            runCurrent()
-            assertThat(isUnlocked).isFalse()
-
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
-
-            assertThat(isUnlocked).isFalse()
-        }
-
-    @Test
-    fun authMethodChangedToNone_onLockScreenScene_dismissesLockScreen() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-        }
-
-    @Test
-    fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            runCurrent()
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.QuickSettings))
-            runCurrent()
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
-
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
new file mode 100644
index 0000000..1dcb55d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -0,0 +1,352 @@
+/*
+ *  Copyright (C) 2023 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import android.hardware.biometrics.BiometricSourceType
+import android.hardware.fingerprint.FingerprintManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.util.IndicationHelper
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class OccludingAppDeviceEntryInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: OccludingAppDeviceEntryInteractor
+    private lateinit var testScope: TestScope
+    private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+    private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+    private lateinit var configurationRepository: FakeConfigurationRepository
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var trustRepository: FakeTrustRepository
+    private lateinit var powerRepository: FakePowerRepository
+
+    @Mock private lateinit var indicationHelper: IndicationHelper
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var mockedContext: Context
+    @Mock private lateinit var activityStarter: ActivityStarter
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+        biometricSettingsRepository = FakeBiometricSettingsRepository()
+        fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+        fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+        keyguardRepository = FakeKeyguardRepository()
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        configurationRepository = FakeConfigurationRepository()
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.FACE_AUTH_REFACTOR, false)
+                set(Flags.DELAY_BOUNCER, false)
+            }
+        trustRepository = FakeTrustRepository()
+        powerRepository = FakePowerRepository()
+        underTest =
+            OccludingAppDeviceEntryInteractor(
+                BiometricMessageInteractor(
+                    mContext.resources,
+                    fingerprintAuthRepository,
+                    fingerprintPropertyRepository,
+                    indicationHelper,
+                    keyguardUpdateMonitor,
+                ),
+                fingerprintAuthRepository,
+                KeyguardInteractor(
+                    keyguardRepository,
+                    commandQueue = mock(),
+                    featureFlags,
+                    bouncerRepository,
+                    configurationRepository,
+                ),
+                PrimaryBouncerInteractor(
+                    bouncerRepository,
+                    primaryBouncerView = mock(),
+                    mainHandler = mock(),
+                    keyguardStateController = mock(),
+                    keyguardSecurityModel = mock(),
+                    primaryBouncerCallbackInteractor = mock(),
+                    falsingCollector = mock(),
+                    dismissCallbackRegistry = mock(),
+                    context,
+                    keyguardUpdateMonitor,
+                    trustRepository,
+                    featureFlags,
+                    testScope.backgroundScope,
+                ),
+                AlternateBouncerInteractor(
+                    statusBarStateController = mock(),
+                    keyguardStateController = mock(),
+                    bouncerRepository,
+                    biometricSettingsRepository,
+                    FakeSystemClock(),
+                    keyguardUpdateMonitor,
+                ),
+                testScope.backgroundScope,
+                mockedContext,
+                activityStarter,
+                PowerInteractor(
+                    powerRepository,
+                    keyguardRepository,
+                    falsingCollector = mock(),
+                    screenOffAnimationController = mock(),
+                    statusBarStateController = mock(),
+                ),
+                FakeFeatureFlags().apply { set(Flags.FP_LISTEN_OCCLUDING_APPS, true) },
+            )
+    }
+
+    @Test
+    fun fingerprintSuccess_goToHomeScreen() =
+        testScope.runTest {
+            givenOnOccludingApp(true)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+            verifyGoToHomeScreen()
+        }
+
+    @Test
+    fun fingerprintSuccess_notInteractive_doesNotGoToHomeScreen() =
+        testScope.runTest {
+            givenOnOccludingApp(true)
+            powerRepository.setInteractive(false)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+            verifyNeverGoToHomeScreen()
+        }
+
+    @Test
+    fun fingerprintSuccess_notOnOccludingApp_doesNotGoToHomeScreen() =
+        testScope.runTest {
+            givenOnOccludingApp(false)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+            verifyNeverGoToHomeScreen()
+        }
+
+    @Test
+    fun lockout_goToHomeScreenOnDismissAction() =
+        testScope.runTest {
+            givenOnOccludingApp(true)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+                    "lockoutTest"
+                )
+            )
+            runCurrent()
+            verifyGoToHomeScreenOnDismiss()
+        }
+
+    @Test
+    fun lockout_notOnOccludingApp_neverGoToHomeScreen() =
+        testScope.runTest {
+            givenOnOccludingApp(false)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+                    "lockoutTest"
+                )
+            )
+            runCurrent()
+            verifyNeverGoToHomeScreen()
+        }
+
+    @Test
+    fun message_fpFailOnOccludingApp_thenNotOnOccludingApp() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+
+            givenOnOccludingApp(true)
+            givenPrimaryAuthRequired(false)
+            runCurrent()
+            // WHEN a fp failure come in
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            // THEN message set to failure
+            assertThat(message?.type).isEqualTo(BiometricMessageType.FAIL)
+
+            // GIVEN fingerprint shouldn't run
+            givenOnOccludingApp(false)
+            runCurrent()
+            // WHEN another fp failure arrives
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+            // THEN message set to null
+            assertThat(message).isNull()
+        }
+
+    @Test
+    fun message_fpErrorHelpFailOnOccludingApp() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+
+            givenOnOccludingApp(true)
+            givenPrimaryAuthRequired(false)
+            runCurrent()
+
+            // ERROR message
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    FingerprintManager.FINGERPRINT_ERROR_CANCELED,
+                    "testError",
+                )
+            )
+            assertThat(message?.source).isEqualTo(BiometricSourceType.FINGERPRINT)
+            assertThat(message?.id).isEqualTo(FingerprintManager.FINGERPRINT_ERROR_CANCELED)
+            assertThat(message?.message).isEqualTo("testError")
+            assertThat(message?.type).isEqualTo(BiometricMessageType.ERROR)
+
+            // HELP message
+            fingerprintAuthRepository.setAuthenticationStatus(
+                HelpFingerprintAuthenticationStatus(
+                    FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL,
+                    "testHelp",
+                )
+            )
+            assertThat(message?.source).isEqualTo(BiometricSourceType.FINGERPRINT)
+            assertThat(message?.id).isEqualTo(FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL)
+            assertThat(message?.message).isEqualTo("testHelp")
+            assertThat(message?.type).isEqualTo(BiometricMessageType.HELP)
+
+            // FAIL message
+            fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+            assertThat(message?.source).isEqualTo(BiometricSourceType.FINGERPRINT)
+            assertThat(message?.id)
+                .isEqualTo(KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
+            assertThat(message?.type).isEqualTo(BiometricMessageType.FAIL)
+        }
+
+    @Test
+    fun message_fpError_lockoutFilteredOut() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+
+            givenOnOccludingApp(true)
+            givenPrimaryAuthRequired(false)
+            runCurrent()
+
+            // permanent lockout error message
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT,
+                    "testPermanentLockoutMessageFiltered",
+                )
+            )
+            assertThat(message).isNull()
+
+            // temporary lockout error message
+            fingerprintAuthRepository.setAuthenticationStatus(
+                ErrorFingerprintAuthenticationStatus(
+                    FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+                    "testLockoutMessageFiltered",
+                )
+            )
+            assertThat(message).isNull()
+        }
+
+    private fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
+        powerRepository.setInteractive(true)
+        keyguardRepository.setKeyguardOccluded(isOnOccludingApp)
+        keyguardRepository.setKeyguardShowing(isOnOccludingApp)
+        bouncerRepository.setPrimaryShow(!isOnOccludingApp)
+        bouncerRepository.setAlternateVisible(!isOnOccludingApp)
+    }
+
+    private fun givenPrimaryAuthRequired(required: Boolean) {
+        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+            .thenReturn(!required)
+    }
+
+    private fun verifyGoToHomeScreen() {
+        val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(mockedContext).startActivity(intentCaptor.capture())
+
+        assertThat(intentCaptor.value.hasCategory(Intent.CATEGORY_HOME)).isTrue()
+        assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_MAIN)
+    }
+
+    private fun verifyNeverGoToHomeScreen() {
+        verify(mockedContext, never()).startActivity(any())
+        verify(activityStarter, never())
+            .dismissKeyguardThenExecute(any(OnDismissAction::class.java), isNull(), eq(false))
+    }
+
+    private fun verifyGoToHomeScreenOnDismiss() {
+        val onDimissActionCaptor = ArgumentCaptor.forClass(OnDismissAction::class.java)
+        verify(activityStarter)
+            .dismissKeyguardThenExecute(onDimissActionCaptor.capture(), isNull(), eq(false))
+        onDimissActionCaptor.value.onDismiss()
+
+        verifyGoToHomeScreen()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
deleted file mode 100644
index 2b135cc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
+++ /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.keyguard.domain.interactor
-
-import android.view.View
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.RoboPilotTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RoboPilotTest
-@RunWith(AndroidJUnit4::class)
-class PrimaryBouncerCallbackInteractorTest : SysuiTestCase() {
-    private val mPrimaryBouncerCallbackInteractor = PrimaryBouncerCallbackInteractor()
-    @Mock
-    private lateinit var mPrimaryBouncerExpansionCallback:
-        PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
-    @Mock
-    private lateinit var keyguardResetCallback:
-        PrimaryBouncerCallbackInteractor.KeyguardResetCallback
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(
-            mPrimaryBouncerExpansionCallback
-        )
-        mPrimaryBouncerCallbackInteractor.addKeyguardResetCallback(keyguardResetCallback)
-    }
-
-    @Test
-    fun testOnFullyShown() {
-        mPrimaryBouncerCallbackInteractor.dispatchFullyShown()
-        verify(mPrimaryBouncerExpansionCallback).onFullyShown()
-    }
-
-    @Test
-    fun testOnFullyHidden() {
-        mPrimaryBouncerCallbackInteractor.dispatchFullyHidden()
-        verify(mPrimaryBouncerExpansionCallback).onFullyHidden()
-    }
-
-    @Test
-    fun testOnExpansionChanged() {
-        mPrimaryBouncerCallbackInteractor.dispatchExpansionChanged(5f)
-        verify(mPrimaryBouncerExpansionCallback).onExpansionChanged(5f)
-    }
-
-    @Test
-    fun testOnVisibilityChanged() {
-        mPrimaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
-        verify(mPrimaryBouncerExpansionCallback).onVisibilityChanged(false)
-    }
-
-    @Test
-    fun testOnStartingToHide() {
-        mPrimaryBouncerCallbackInteractor.dispatchStartingToHide()
-        verify(mPrimaryBouncerExpansionCallback).onStartingToHide()
-    }
-
-    @Test
-    fun testOnStartingToShow() {
-        mPrimaryBouncerCallbackInteractor.dispatchStartingToShow()
-        verify(mPrimaryBouncerExpansionCallback).onStartingToShow()
-    }
-
-    @Test
-    fun testOnKeyguardReset() {
-        mPrimaryBouncerCallbackInteractor.dispatchReset()
-        verify(keyguardResetCallback).onKeyguardReset()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
deleted file mode 100644
index b5cb44a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ /dev/null
@@ -1,469 +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.keyguard.domain.interactor
-
-import android.hardware.biometrics.BiometricSourceType
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import android.testing.TestableResources
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.DejankUtils
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.BouncerViewDelegate
-import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
-import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.utils.os.FakeHandler
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Answers
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
-class PrimaryBouncerInteractorTest : SysuiTestCase() {
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-    private lateinit var repository: KeyguardBouncerRepository
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
-    @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
-    @Mock private lateinit var mPrimaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
-    @Mock private lateinit var falsingCollector: FalsingCollector
-    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    private lateinit var mainHandler: FakeHandler
-    private lateinit var underTest: PrimaryBouncerInteractor
-    private lateinit var resources: TestableResources
-    private lateinit var trustRepository: FakeTrustRepository
-    private lateinit var featureFlags: FakeFeatureFlags
-    private lateinit var testScope: TestScope
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
-            .thenReturn(KeyguardSecurityModel.SecurityMode.PIN)
-
-        DejankUtils.setImmediate(true)
-        testScope = TestScope()
-        mainHandler = FakeHandler(android.os.Looper.getMainLooper())
-        trustRepository = FakeTrustRepository()
-        featureFlags = FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) }
-        underTest =
-            PrimaryBouncerInteractor(
-                repository,
-                bouncerView,
-                mainHandler,
-                keyguardStateController,
-                keyguardSecurityModel,
-                mPrimaryBouncerCallbackInteractor,
-                falsingCollector,
-                dismissCallbackRegistry,
-                context,
-                keyguardUpdateMonitor,
-                trustRepository,
-                featureFlags,
-                testScope.backgroundScope,
-            )
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        whenever(repository.primaryBouncerShow.value).thenReturn(false)
-        whenever(bouncerView.delegate).thenReturn(bouncerViewDelegate)
-        resources = context.orCreateTestableResources
-    }
-
-    @Test
-    fun testShow_isScrimmed() {
-        underTest.show(true)
-        verify(repository).setKeyguardAuthenticated(null)
-        verify(repository).setPrimaryStartingToHide(false)
-        verify(repository).setPrimaryScrimmed(true)
-        verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
-        verify(repository).setPrimaryShowingSoon(true)
-        verify(keyguardStateController).notifyPrimaryBouncerShowing(true)
-        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToShow()
-        verify(repository).setPrimaryShow(true)
-        verify(repository).setPrimaryShowingSoon(false)
-        verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.VISIBLE)
-    }
-
-    @Test
-    fun testShow_isNotScrimmed() {
-        verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE)
-    }
-
-    @Test
-    fun testShow_keyguardIsDone() {
-        whenever(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
-        verify(keyguardStateController, never()).notifyPrimaryBouncerShowing(true)
-        verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
-    }
-
-    @Test
-    fun testShow_isResumed() {
-        whenever(repository.primaryBouncerShow.value).thenReturn(true)
-        whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
-            .thenReturn(KeyguardSecurityModel.SecurityMode.SimPuk)
-
-        underTest.show(true)
-        verify(repository).setPrimaryShow(false)
-        verify(repository).setPrimaryShow(true)
-    }
-
-    @Test
-    fun testHide() {
-        underTest.hide()
-        verify(falsingCollector).onBouncerHidden()
-        verify(keyguardStateController).notifyPrimaryBouncerShowing(false)
-        verify(repository).setPrimaryShowingSoon(false)
-        verify(repository).setPrimaryShow(false)
-        verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.INVISIBLE)
-        verify(repository).setPrimaryStartDisappearAnimation(null)
-        verify(repository).setPanelExpansion(EXPANSION_HIDDEN)
-    }
-
-    @Test
-    fun testExpansion() {
-        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        underTest.setPanelExpansion(0.6f)
-        verify(repository).setPanelExpansion(0.6f)
-        verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
-    }
-
-    @Test
-    fun testExpansion_fullyShown() {
-        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        underTest.setPanelExpansion(EXPANSION_VISIBLE)
-        verify(falsingCollector).onBouncerShown()
-        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
-    }
-
-    @Test
-    fun testExpansion_fullyHidden() {
-        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        underTest.setPanelExpansion(EXPANSION_HIDDEN)
-        verify(repository).setPrimaryShow(false)
-        verify(falsingCollector).onBouncerHidden()
-        verify(mPrimaryBouncerCallbackInteractor).dispatchReset()
-        verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden()
-    }
-
-    @Test
-    fun testExpansion_startingToHide() {
-        whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        underTest.setPanelExpansion(0.1f)
-        verify(repository).setPrimaryStartingToHide(true)
-        verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
-    }
-
-    @Test
-    fun testShowMessage() {
-        val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
-        underTest.showMessage("abc", null)
-        verify(repository).setShowMessage(argCaptor.capture())
-        assertThat(argCaptor.value.message).isEqualTo("abc")
-    }
-
-    @Test
-    fun testDismissAction() {
-        val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
-        val cancelAction = mock(Runnable::class.java)
-        underTest.setDismissAction(onDismissAction, cancelAction)
-        verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
-    }
-
-    @Test
-    fun testUpdateResources() {
-        underTest.updateResources()
-        verify(repository).setResourceUpdateRequests(true)
-    }
-
-    @Test
-    fun testNotifyKeyguardAuthenticated() {
-        underTest.notifyKeyguardAuthenticated(true)
-        verify(repository).setKeyguardAuthenticated(true)
-    }
-
-    @Test
-    fun testNotifyShowedMessage() {
-        underTest.onMessageShown()
-        verify(repository).setShowMessage(null)
-    }
-
-    @Test
-    fun testSetKeyguardPosition() {
-        underTest.setKeyguardPosition(0f)
-        verify(repository).setKeyguardPosition(0f)
-    }
-
-    @Test
-    fun testNotifyKeyguardAuthenticatedHandled() {
-        underTest.notifyKeyguardAuthenticatedHandled()
-        verify(repository).setKeyguardAuthenticated(null)
-    }
-
-    @Test
-    fun testNotifyUpdatedResources() {
-        underTest.notifyUpdatedResources()
-        verify(repository).setResourceUpdateRequests(false)
-    }
-
-    @Test
-    fun testSetBackButtonEnabled() {
-        underTest.setBackButtonEnabled(true)
-        verify(repository).setIsBackButtonEnabled(true)
-    }
-
-    @Test
-    fun testStartDisappearAnimation_willRunDismissFromKeyguard() {
-        whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(true)
-
-        val runnable = mock(Runnable::class.java)
-        underTest.startDisappearAnimation(runnable)
-        // End runnable should run immediately
-        verify(runnable).run()
-        // ... while the disappear animation should never be run
-        verify(repository, never()).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
-    }
-
-    @Test
-    fun testStartDisappearAnimation_willNotRunDismissFromKeyguard_() {
-        whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(false)
-
-        val runnable = mock(Runnable::class.java)
-        underTest.startDisappearAnimation(runnable)
-        verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
-    }
-
-    @Test
-    fun testIsFullShowing() {
-        whenever(repository.primaryBouncerShow.value).thenReturn(true)
-        whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        assertThat(underTest.isFullyShowing()).isTrue()
-        whenever(repository.primaryBouncerShow.value).thenReturn(false)
-        assertThat(underTest.isFullyShowing()).isFalse()
-    }
-
-    @Test
-    fun testIsScrimmed() {
-        whenever(repository.primaryBouncerScrimmed.value).thenReturn(true)
-        assertThat(underTest.isScrimmed()).isTrue()
-        whenever(repository.primaryBouncerScrimmed.value).thenReturn(false)
-        assertThat(underTest.isScrimmed()).isFalse()
-    }
-
-    @Test
-    fun testIsInTransit() {
-        whenever(repository.primaryBouncerShowingSoon.value).thenReturn(true)
-        assertThat(underTest.isInTransit()).isTrue()
-        whenever(repository.primaryBouncerShowingSoon.value).thenReturn(false)
-        assertThat(underTest.isInTransit()).isFalse()
-        whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        assertThat(underTest.isInTransit()).isTrue()
-    }
-
-    @Test
-    fun testIsAnimatingAway() {
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
-        assertThat(underTest.isAnimatingAway()).isTrue()
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        assertThat(underTest.isAnimatingAway()).isFalse()
-    }
-
-    @Test
-    fun testWillDismissWithAction() {
-        whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
-        assertThat(underTest.willDismissWithAction()).isTrue()
-        whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
-        assertThat(underTest.willDismissWithAction()).isFalse()
-    }
-
-    @Test
-    fun testSideFpsVisibility() {
-        updateSideFpsVisibilityParameters(
-            isVisible = true,
-            sfpsEnabled = true,
-            fpsDetectionRunning = true,
-            isUnlockingWithFpAllowed = true,
-            isAnimatingAway = false
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(true)
-    }
-
-    @Test
-    fun testSideFpsVisibility_notVisible() {
-        updateSideFpsVisibilityParameters(
-            isVisible = false,
-            sfpsEnabled = true,
-            fpsDetectionRunning = true,
-            isUnlockingWithFpAllowed = true,
-            isAnimatingAway = false
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(false)
-    }
-
-    @Test
-    fun testSideFpsVisibility_sfpsNotEnabled() {
-        updateSideFpsVisibilityParameters(
-            isVisible = true,
-            sfpsEnabled = false,
-            fpsDetectionRunning = true,
-            isUnlockingWithFpAllowed = true,
-            isAnimatingAway = false
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(false)
-    }
-
-    @Test
-    fun testSideFpsVisibility_fpsDetectionNotRunning() {
-        updateSideFpsVisibilityParameters(
-            isVisible = true,
-            sfpsEnabled = true,
-            fpsDetectionRunning = false,
-            isUnlockingWithFpAllowed = true,
-            isAnimatingAway = false
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(false)
-    }
-
-    @Test
-    fun testSideFpsVisibility_UnlockingWithFpNotAllowed() {
-        updateSideFpsVisibilityParameters(
-            isVisible = true,
-            sfpsEnabled = true,
-            fpsDetectionRunning = true,
-            isUnlockingWithFpAllowed = false,
-            isAnimatingAway = false
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(false)
-    }
-
-    @Test
-    fun testSideFpsVisibility_AnimatingAway() {
-        updateSideFpsVisibilityParameters(
-            isVisible = true,
-            sfpsEnabled = true,
-            fpsDetectionRunning = true,
-            isUnlockingWithFpAllowed = true,
-            isAnimatingAway = true
-        )
-        underTest.updateSideFpsVisibility()
-        verify(repository).setSideFpsShowing(false)
-    }
-
-    @Test
-    fun delayBouncerWhenFaceAuthPossible() {
-        mainHandler.setMode(FakeHandler.Mode.QUEUEING)
-
-        // GIVEN bouncer should be delayed due to face auth
-        whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
-        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
-            .thenReturn(true)
-
-        // WHEN bouncer show is requested
-        underTest.show(true)
-
-        // THEN primary show & primary showing soon aren't updated immediately
-        verify(repository, never()).setPrimaryShow(true)
-        verify(repository, never()).setPrimaryShowingSoon(false)
-
-        // WHEN all queued messages are dispatched
-        mainHandler.dispatchQueuedMessages()
-
-        // THEN primary show & primary showing soon are updated
-        verify(repository).setPrimaryShow(true)
-        verify(repository).setPrimaryShowingSoon(false)
-    }
-
-    @Test
-    fun delayBouncerWhenActiveUnlockPossible() {
-        testScope.run {
-            mainHandler.setMode(FakeHandler.Mode.QUEUEING)
-
-            // GIVEN bouncer should be delayed due to active unlock
-            trustRepository.setCurrentUserActiveUnlockAvailable(true)
-            whenever(keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState())
-                .thenReturn(true)
-            runCurrent()
-
-            // WHEN bouncer show is requested
-            underTest.show(true)
-
-            // THEN primary show & primary showing soon were scheduled to update
-            verify(repository, never()).setPrimaryShow(true)
-            verify(repository, never()).setPrimaryShowingSoon(false)
-
-            // WHEN all queued messages are dispatched
-            mainHandler.dispatchQueuedMessages()
-
-            // THEN primary show & primary showing soon are updated
-            verify(repository).setPrimaryShow(true)
-            verify(repository).setPrimaryShowingSoon(false)
-        }
-    }
-
-    private fun updateSideFpsVisibilityParameters(
-        isVisible: Boolean,
-        sfpsEnabled: Boolean,
-        fpsDetectionRunning: Boolean,
-        isUnlockingWithFpAllowed: Boolean,
-        isAnimatingAway: Boolean
-    ) {
-        whenever(repository.primaryBouncerShow.value).thenReturn(isVisible)
-        resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled)
-        whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning)
-            .thenReturn(fpsDetectionRunning)
-        whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
-            .thenReturn(isUnlockingWithFpAllowed)
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value)
-            .thenReturn(if (isAnimatingAway) Runnable {} else null)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
deleted file mode 100644
index b288fbc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ /dev/null
@@ -1,120 +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.keyguard.domain.interactor
-
-import android.os.Looper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.RoboPilotTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.utils.os.FakeHandler
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RoboPilotTest
-@RunWith(AndroidJUnit4::class)
-class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
-    private lateinit var repository: FakeKeyguardBouncerRepository
-    @Mock private lateinit var bouncerView: BouncerView
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
-    @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
-    @Mock private lateinit var falsingCollector: FalsingCollector
-    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
-    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    private val mainHandler = FakeHandler(Looper.getMainLooper())
-    private lateinit var underTest: PrimaryBouncerInteractor
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        repository = FakeKeyguardBouncerRepository()
-        underTest =
-            PrimaryBouncerInteractor(
-                repository,
-                bouncerView,
-                mainHandler,
-                keyguardStateController,
-                keyguardSecurityModel,
-                primaryBouncerCallbackInteractor,
-                falsingCollector,
-                dismissCallbackRegistry,
-                context,
-                keyguardUpdateMonitor,
-                Mockito.mock(TrustRepository::class.java),
-                FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
-                TestScope().backgroundScope,
-            )
-    }
-
-    @Test
-    fun notInteractableWhenExpansionIsBelow90Percent() = runTest {
-        val isInteractable = collectLastValue(underTest.isInteractable)
-
-        repository.setPrimaryShow(true)
-        repository.setPanelExpansion(0.15f)
-
-        assertThat(isInteractable()).isFalse()
-    }
-
-    @Test
-    fun notInteractableWhenExpansionAbove90PercentButNotVisible() = runTest {
-        val isInteractable = collectLastValue(underTest.isInteractable)
-
-        repository.setPrimaryShow(false)
-        repository.setPanelExpansion(0.05f)
-
-        assertThat(isInteractable()).isFalse()
-    }
-
-    @Test
-    fun isInteractableWhenExpansionAbove90PercentAndVisible() = runTest {
-        var isInteractable = collectLastValue(underTest.isInteractable)
-
-        repository.setPrimaryShow(true)
-        repository.setPanelExpansion(0.09f)
-
-        assertThat(isInteractable()).isTrue()
-
-        repository.setPanelExpansion(0.12f)
-        assertThat(isInteractable()).isFalse()
-
-        repository.setPanelExpansion(0f)
-        assertThat(isInteractable()).isTrue()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
new file mode 100644
index 0000000..6efec99
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsKeyguardInteractorTest : SysuiTestCase() {
+    private val burnInProgress = 1f
+    private val burnInYOffset = 20
+    private val burnInXOffset = 10
+
+    private lateinit var testScope: TestScope
+    private lateinit var configRepository: FakeConfigurationRepository
+    private lateinit var bouncerRepository: KeyguardBouncerRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var fakeCommandQueue: FakeCommandQueue
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var burnInInteractor: BurnInInteractor
+    private lateinit var shadeRepository: FakeShadeRepository
+    private lateinit var keyguardInteractor: KeyguardInteractor
+
+    @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+    @Mock private lateinit var dialogManager: SystemUIDialogManager
+
+    private lateinit var underTest: UdfpsKeyguardInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+        configRepository = FakeConfigurationRepository()
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+                set(Flags.FACE_AUTH_REFACTOR, false)
+            }
+        KeyguardInteractorFactory.create(featureFlags = featureFlags).let {
+            keyguardInteractor = it.keyguardInteractor
+            keyguardRepository = it.repository
+        }
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        shadeRepository = FakeShadeRepository()
+        fakeCommandQueue = FakeCommandQueue()
+        burnInInteractor =
+            BurnInInteractor(
+                context,
+                burnInHelper,
+                testScope.backgroundScope,
+                configRepository,
+                keyguardInteractor
+            )
+
+        underTest =
+            UdfpsKeyguardInteractor(
+                configRepository,
+                burnInInteractor,
+                keyguardInteractor,
+                shadeRepository,
+                dialogManager,
+            )
+    }
+
+    @Test
+    fun dozeChanges_updatesUdfpsAodModel() =
+        testScope.runTest {
+            val burnInOffsets by collectLastValue(underTest.burnInOffsets)
+            initializeBurnInOffsets()
+
+            // WHEN we're not dozing
+            setAwake()
+            runCurrent()
+
+            // THEN burn in offsets are 0
+            assertThat(burnInOffsets?.burnInProgress).isEqualTo(0f)
+            assertThat(burnInOffsets?.burnInYOffset).isEqualTo(0)
+            assertThat(burnInOffsets?.burnInXOffset).isEqualTo(0)
+
+            // WHEN we're in the middle of the doze amount change
+            keyguardRepository.setDozeAmount(.50f)
+            runCurrent()
+
+            // THEN burn in is updated (between 0 and the full offset)
+            assertThat(burnInOffsets?.burnInProgress).isGreaterThan(0f)
+            assertThat(burnInOffsets?.burnInYOffset).isGreaterThan(0)
+            assertThat(burnInOffsets?.burnInXOffset).isGreaterThan(0)
+            assertThat(burnInOffsets?.burnInProgress).isLessThan(burnInProgress)
+            assertThat(burnInOffsets?.burnInYOffset).isLessThan(burnInYOffset)
+            assertThat(burnInOffsets?.burnInXOffset).isLessThan(burnInXOffset)
+
+            // WHEN we're fully dozing
+            keyguardRepository.setDozeAmount(1f)
+            runCurrent()
+
+            // THEN burn in offsets are updated to final current values (for the given time)
+            assertThat(burnInOffsets?.burnInProgress).isEqualTo(burnInProgress)
+            assertThat(burnInOffsets?.burnInYOffset).isEqualTo(burnInYOffset)
+            assertThat(burnInOffsets?.burnInXOffset).isEqualTo(burnInXOffset)
+        }
+
+    @Test
+    fun dialogHideAffordances() =
+        testScope.runTest {
+            val dialogHideAffordancesRequest by
+                collectLastValue(underTest.dialogHideAffordancesRequest)
+            runCurrent()
+            val captor = argumentCaptor<SystemUIDialogManager.Listener>()
+            verify(dialogManager).registerListener(captor.capture())
+
+            captor.value.shouldHideAffordances(false)
+            assertThat(dialogHideAffordancesRequest).isEqualTo(false)
+
+            captor.value.shouldHideAffordances(true)
+            assertThat(dialogHideAffordancesRequest).isEqualTo(true)
+
+            captor.value.shouldHideAffordances(false)
+            assertThat(dialogHideAffordancesRequest).isEqualTo(false)
+        }
+
+    @Test
+    fun shadeExpansion_updates() =
+        testScope.runTest {
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            val shadeExpansion by collectLastValue(underTest.shadeExpansion)
+            assertThat(shadeExpansion).isEqualTo(0f)
+
+            shadeRepository.setUdfpsTransitionToFullShadeProgress(.5f)
+            assertThat(shadeExpansion).isEqualTo(.5f)
+
+            shadeRepository.setUdfpsTransitionToFullShadeProgress(.7f)
+            assertThat(shadeExpansion).isEqualTo(.7f)
+
+            shadeRepository.setUdfpsTransitionToFullShadeProgress(.22f)
+            assertThat(shadeExpansion).isEqualTo(.22f)
+
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+            assertThat(shadeExpansion).isEqualTo(1f)
+        }
+
+    @Test
+    fun qsProgress_updates() =
+        testScope.runTest {
+            val qsProgress by collectLastValue(underTest.qsProgress)
+            assertThat(qsProgress).isEqualTo(0f)
+
+            shadeRepository.setQsExpansion(.22f)
+            assertThat(qsProgress).isEqualTo(.44f)
+
+            shadeRepository.setQsExpansion(.5f)
+            assertThat(qsProgress).isEqualTo(1f)
+
+            shadeRepository.setQsExpansion(.7f)
+            assertThat(qsProgress).isEqualTo(1f)
+        }
+
+    private fun initializeBurnInOffsets() {
+        whenever(burnInHelper.burnInProgressOffset()).thenReturn(burnInProgress)
+        whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(true)))
+            .thenReturn(burnInXOffset)
+        whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(false)))
+            .thenReturn(burnInYOffset)
+    }
+
+    private fun setAwake() {
+        keyguardRepository.setDozeAmount(0f)
+        keyguardRepository.dozeTimeTick()
+
+        bouncerRepository.setAlternateVisible(false)
+        keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+        bouncerRepository.setPrimaryShow(false)
+        keyguardRepository.setWakefulnessModel(
+            WakefulnessModel(
+                WakefulnessState.AWAKE,
+                WakeSleepReason.POWER_BUTTON,
+                WakeSleepReason.POWER_BUTTON,
+            )
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
new file mode 100644
index 0000000..73ecae5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: WindowManagerLockscreenVisibilityInteractor
+
+    @Mock private lateinit var surfaceBehindInteractor: KeyguardSurfaceBehindInteractor
+    @Mock
+    private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+    @Mock
+    private lateinit var fromPrimaryBouncerTransitionInteractor:
+        FromPrimaryBouncerTransitionInteractor
+
+    private val lockscreenSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
+    private val primaryBouncerSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
+    private val surfaceBehindIsAnimatingFlow = MutableStateFlow(false)
+
+    private val testScope = TestScope()
+
+    private lateinit var keyguardInteractor: KeyguardInteractor
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+
+        whenever(fromLockscreenTransitionInteractor.surfaceBehindVisibility)
+            .thenReturn(lockscreenSurfaceVisibilityFlow)
+        whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindVisibility)
+            .thenReturn(primaryBouncerSurfaceVisibilityFlow)
+        whenever(surfaceBehindInteractor.isAnimatingSurface)
+            .thenReturn(surfaceBehindIsAnimatingFlow)
+
+        transitionRepository = FakeKeyguardTransitionRepository()
+
+        transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                )
+                .also { keyguardInteractor = it.keyguardInteractor }
+                .keyguardTransitionInteractor
+
+        underTest =
+            WindowManagerLockscreenVisibilityInteractor(
+                keyguardInteractor = keyguardInteractor,
+                transitionInteractor = transitionInteractor,
+                surfaceBehindInteractor = surfaceBehindInteractor,
+                fromLockscreenTransitionInteractor,
+                fromPrimaryBouncerTransitionInteractor,
+            )
+    }
+
+    @Test
+    fun surfaceBehindVisibility_switchesToCorrectFlow() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindVisibility)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false, // We should start with the surface invisible on LOCKSCREEN.
+                ),
+                values
+            )
+
+            val lockscreenSpecificSurfaceVisibility = true
+            lockscreenSurfaceVisibilityFlow.emit(lockscreenSpecificSurfaceVisibility)
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We started a transition from LOCKSCREEN, we should be using the value emitted by the
+            // lockscreenSurfaceVisibilityFlow.
+            assertEquals(
+                listOf(
+                    false,
+                    lockscreenSpecificSurfaceVisibility,
+                ),
+                values
+            )
+
+            // Go back to LOCKSCREEN, since we won't emit 'true' twice in a row.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    lockscreenSpecificSurfaceVisibility,
+                    false, // FINISHED (LOCKSCREEN)
+                ),
+                values
+            )
+
+            val bouncerSpecificVisibility = true
+            primaryBouncerSurfaceVisibilityFlow.emit(bouncerSpecificVisibility)
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We started a transition from PRIMARY_BOUNCER, we should be using the value emitted by
+            // the
+            // primaryBouncerSurfaceVisibilityFlow.
+            assertEquals(
+                listOf(
+                    false,
+                    lockscreenSpecificSurfaceVisibility,
+                    false,
+                    bouncerSpecificVisibility,
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testUsingGoingAwayAnimation_duringTransitionToGone() =
+        testScope.runTest {
+            val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false, // Not using the animation when we're just sitting on LOCKSCREEN.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(true)
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true, // Still true when we're FINISHED -> GONE, since we're still animating.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(false)
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false, // False once the animation ends.
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testNotUsingGoingAwayAnimation_evenWhenAnimating_ifStateIsNotGone() =
+        testScope.runTest {
+            val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false, // Not using the animation when we're just sitting on LOCKSCREEN.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(true)
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true, // We're happily animating while transitioning to gone.
+                ),
+                values
+            )
+
+            // Oh no, we're still surfaceBehindAnimating=true, but no longer transitioning to GONE.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false, // Despite the animator still running, this should be false.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(false)
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false, // The animator ending should have no effect.
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun lockscreenVisibility_visibleWhenGone() =
+        testScope.runTest {
+            val values by collectValues(underTest.lockscreenVisibility)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true, // Unsurprisingly, we should start with the lockscreen visible on
+                    // LOCKSCREEN.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true, // Lockscreen remains visible while we're transitioning to GONE.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true,
+                    false, // Once we're fully GONE, the lockscreen should not be visible.
+                ),
+                values
+            )
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
deleted file mode 100644
index 13e2768..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
+++ /dev/null
@@ -1,40 +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.keyguard.domain.quickaffordance
-
-import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-
-/** Fake implementation of [FakeKeyguardQuickAffordanceRegistry], for tests. */
-class FakeKeyguardQuickAffordanceRegistry(
-    private val configsByPosition:
-        Map<KeyguardQuickAffordancePosition, List<FakeKeyguardQuickAffordanceConfig>>,
-) : KeyguardQuickAffordanceRegistry<FakeKeyguardQuickAffordanceConfig> {
-
-    override fun getAll(
-        position: KeyguardQuickAffordancePosition
-    ): List<FakeKeyguardQuickAffordanceConfig> {
-        return configsByPosition.getValue(position)
-    }
-
-    override fun get(
-        key: String,
-    ): FakeKeyguardQuickAffordanceConfig {
-        return configsByPosition.values.flatten().associateBy { config -> config.key }.getValue(key)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
new file mode 100644
index 0000000..a22f603
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.testing.TestableLooper.RunWithLooper
+import android.view.RemoteAnimationTarget
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertNull
+import junit.framework.Assert.assertTrue
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.doAnswer
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWithLooper(setAsMainLooper = true)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardSurfaceBehindParamsApplierTest : SysuiTestCase() {
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
+    private lateinit var underTest: KeyguardSurfaceBehindParamsApplier
+    private lateinit var executor: FakeExecutor
+
+    @Mock private lateinit var keyguardViewController: KeyguardViewController
+
+    @Mock private lateinit var interactor: KeyguardSurfaceBehindInteractor
+
+    @Mock private lateinit var remoteAnimationTarget: RemoteAnimationTarget
+
+    private var isAnimatingSurface: Boolean? = null
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        executor = FakeExecutor(FakeSystemClock())
+        underTest =
+            KeyguardSurfaceBehindParamsApplier(
+                executor = executor,
+                keyguardViewController = keyguardViewController,
+                interactor = interactor,
+            )
+
+        doAnswer {
+                (it.arguments[0] as Boolean).let { animating -> isAnimatingSurface = animating }
+            }
+            .whenever(interactor)
+            .setAnimatingSurface(anyBoolean())
+    }
+
+    @After
+    fun tearDown() {
+        animatorTestRule.advanceTimeBy(1000.toLong())
+    }
+
+    @Test
+    fun testNotAnimating_setParamsWithNoAnimation() {
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                alpha = 0.3f,
+                translationY = 300f,
+            )
+
+        // A surface has not yet been provided, so we shouldn't have set animating to false OR true
+        // just yet.
+        assertNull(isAnimatingSurface)
+
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        // We should now explicitly not be animating the surface.
+        assertFalse(checkNotNull(isAnimatingSurface))
+    }
+
+    @Test
+    fun testAnimating_paramsThenSurfaceProvided() {
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                animateFromAlpha = 0f,
+                alpha = 0.3f,
+                animateFromTranslationY = 0f,
+                translationY = 300f,
+            )
+
+        // A surface has not yet been provided, so we shouldn't have set animating to false OR true
+        // just yet.
+        assertNull(isAnimatingSurface)
+
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        // We should now be animating the surface.
+        assertTrue(checkNotNull(isAnimatingSurface))
+    }
+
+    @Test
+    fun testAnimating_surfaceThenParamsProvided() {
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        // The default params (which do not animate) should have been applied, so we're explicitly
+        // NOT animating yet.
+        assertFalse(checkNotNull(isAnimatingSurface))
+
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                animateFromAlpha = 0f,
+                alpha = 0.3f,
+                animateFromTranslationY = 0f,
+                translationY = 300f,
+            )
+
+        // We should now be animating the surface.
+        assertTrue(checkNotNull(isAnimatingSurface))
+    }
+
+    @Test
+    fun testAnimating_thenReleased_animatingIsFalse() {
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                animateFromAlpha = 0f,
+                alpha = 0.3f,
+                animateFromTranslationY = 0f,
+                translationY = 300f,
+            )
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        assertTrue(checkNotNull(isAnimatingSurface))
+
+        underTest.notifySurfaceReleased()
+
+        // Releasing the surface should immediately cancel animators.
+        assertFalse(checkNotNull(isAnimatingSurface))
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
new file mode 100644
index 0000000..623c877
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.app.IActivityTaskManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
+    private lateinit var underTest: WindowManagerLockscreenVisibilityManager
+    private lateinit var executor: FakeExecutor
+
+    @Mock private lateinit var activityTaskManagerService: IActivityTaskManager
+
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+    @Mock private lateinit var keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        executor = FakeExecutor(FakeSystemClock())
+
+        underTest =
+            WindowManagerLockscreenVisibilityManager(
+                executor = executor,
+                activityTaskManagerService = activityTaskManagerService,
+                keyguardStateController = keyguardStateController,
+                keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator,
+            )
+    }
+
+    @Test
+    fun testLockscreenVisible_andAodVisible() {
+        underTest.setLockscreenShown(true)
+        underTest.setAodVisible(true)
+
+        verify(activityTaskManagerService).setLockScreenShown(true, true)
+        verifyNoMoreInteractions(activityTaskManagerService)
+    }
+
+    @Test
+    fun testGoingAway_whenLockscreenVisible_thenSurfaceMadeVisible() {
+        underTest.setLockscreenShown(true)
+        underTest.setAodVisible(true)
+
+        verify(activityTaskManagerService).setLockScreenShown(true, true)
+        verifyNoMoreInteractions(activityTaskManagerService)
+
+        underTest.setSurfaceBehindVisibility(true)
+
+        verify(activityTaskManagerService).keyguardGoingAway(anyInt())
+        verifyNoMoreInteractions(activityTaskManagerService)
+    }
+
+    @Test
+    fun testSurfaceVisible_whenLockscreenNotShowing_doesNotTriggerGoingAway() {
+        underTest.setLockscreenShown(false)
+        underTest.setAodVisible(false)
+
+        verify(activityTaskManagerService).setLockScreenShown(false, false)
+        verifyNoMoreInteractions(activityTaskManagerService)
+
+        underTest.setSurfaceBehindVisibility(true)
+
+        verifyNoMoreInteractions(activityTaskManagerService)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
new file mode 100644
index 0000000..bb73dc6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.withArgCaptor
+import java.io.PrintWriter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class KeyguardBlueprintCommandListenerTest : SysuiTestCase() {
+    private lateinit var keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener
+    @Mock private lateinit var commandRegistry: CommandRegistry
+    @Mock private lateinit var keyguardBlueprintRepository: KeyguardBlueprintRepository
+    @Mock private lateinit var keyguardBlueprintInteractor: KeyguardBlueprintInteractor
+    @Mock private lateinit var pw: PrintWriter
+    private lateinit var command: () -> Command
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        keyguardBlueprintCommandListener =
+            KeyguardBlueprintCommandListener(
+                commandRegistry,
+                keyguardBlueprintRepository,
+                keyguardBlueprintInteractor,
+            )
+        keyguardBlueprintCommandListener.start()
+        command =
+            withArgCaptor<() -> Command> {
+                verify(commandRegistry).registerCommand(eq("blueprint"), capture())
+            }
+    }
+
+    @Test
+    fun testHelp() {
+        command().execute(pw, listOf("help"))
+        verify(pw, atLeastOnce()).println(anyString())
+        verify(keyguardBlueprintInteractor, never()).transitionToBlueprint(anyString())
+    }
+
+    @Test
+    fun testBlank() {
+        command().execute(pw, listOf())
+        verify(pw, atLeastOnce()).println(anyString())
+        verify(keyguardBlueprintInteractor, never()).transitionToBlueprint(anyString())
+    }
+
+    @Test
+    fun testValidArg() {
+        command().execute(pw, listOf("fake"))
+        verify(keyguardBlueprintInteractor).transitionToBlueprint("fake")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
new file mode 100644
index 0000000..addb181
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.blueprints
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class DefaultKeyguardBlueprintTest : SysuiTestCase() {
+    private lateinit var underTest: DefaultKeyguardBlueprint
+    private lateinit var rootView: KeyguardRootView
+    @Mock private lateinit var defaultIndicationAreaSection: DefaultIndicationAreaSection
+    @Mock private lateinit var defaultLockIconSection: DefaultLockIconSection
+    @Mock private lateinit var defaultShortcutsSection: DefaultShortcutsSection
+    @Mock
+    private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection
+    @Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection
+    @Mock private lateinit var defaultStatusViewSection: DefaultStatusViewSection
+    @Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        rootView = KeyguardRootView(context, null)
+        underTest =
+            DefaultKeyguardBlueprint(
+                defaultIndicationAreaSection,
+                defaultLockIconSection,
+                defaultShortcutsSection,
+                defaultAmbientIndicationAreaSection,
+                defaultSettingsPopupMenuSection,
+                defaultStatusViewSection,
+                splitShadeGuidelines,
+            )
+    }
+
+    @Test
+    fun apply() {
+        val cs = ConstraintSet()
+        underTest.apply(cs)
+        verify(defaultIndicationAreaSection).apply(cs)
+        verify(defaultLockIconSection).apply(cs)
+        verify(defaultShortcutsSection).apply(cs)
+        verify(defaultAmbientIndicationAreaSection).apply(cs)
+        verify(defaultSettingsPopupMenuSection).apply(cs)
+        verify(defaultStatusViewSection).apply(cs)
+        verify(splitShadeGuidelines).apply(cs)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
new file mode 100644
index 0000000..3dcc03d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+@SmallTest
+class DefaultIndicationAreaSectionTest : SysuiTestCase() {
+    private val underTest = DefaultIndicationAreaSection(context)
+
+    @Test
+    fun apply() {
+        val cs = ConstraintSet()
+        underTest.apply(cs)
+
+        val constraint = cs.getConstraint(R.id.keyguard_indication_area)
+
+        assertThat(constraint.layout.bottomToBottom).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.mWidth).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT)
+        assertThat(constraint.layout.mHeight).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
new file mode 100644
index 0000000..379c03c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.graphics.Point
+import android.view.WindowManager
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class DefaultLockIconSectionTest : SysuiTestCase() {
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var authController: AuthController
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
+    private lateinit var underTest: DefaultLockIconSection
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            DefaultLockIconSection(keyguardUpdateMonitor, authController, windowManager, context)
+    }
+
+    @Test
+    fun apply() {
+        val cs = ConstraintSet()
+        underTest.apply(cs)
+
+        val constraint = cs.getConstraint(R.id.lock_icon_view)
+
+        assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
+    }
+
+    @Test
+    fun testCenterLockIcon() {
+        val cs = ConstraintSet()
+        underTest.centerLockIcon(Point(5, 6), 1F, 5, cs)
+
+        val constraint = cs.getConstraint(R.id.lock_icon_view)
+
+        assertThat(constraint.layout.mWidth).isEqualTo(2)
+        assertThat(constraint.layout.mHeight).isEqualTo(2)
+        assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(constraint.layout.topMargin).isEqualTo(5)
+        assertThat(constraint.layout.startMargin).isEqualTo(4)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index ab994b7..bfc6f31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
@@ -55,7 +55,12 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest = DreamingToLockscreenTransitionViewModel(interactor, mock())
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index 694539b..75c8bff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest = GoneToDreamingTransitionViewModel(interactor)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
new file mode 100644
index 0000000..a18b033
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+class KeyguardBlueprintViewModelTest : SysuiTestCase() {
+    @Mock private lateinit var keyguardBlueprintInteractor: KeyguardBlueprintInteractor
+    private lateinit var undertest: KeyguardBlueprintViewModel
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        undertest =
+            KeyguardBlueprintViewModel(keyguardBlueprintInteractor = keyguardBlueprintInteractor)
+    }
+
+    @Test
+    fun testBlueprintFlow() {
+        verify(keyguardBlueprintInteractor).blueprint
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index b6368cf..06bf7f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.animation.Expandable
@@ -40,16 +41,13 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
@@ -57,7 +55,6 @@
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.FakeSharedPreferences
@@ -96,7 +93,6 @@
     @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var launchAnimator: DialogLaunchAnimator
-    @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
     @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@@ -106,7 +102,6 @@
 
     private lateinit var testScope: TestScope
     private lateinit var repository: FakeKeyguardRepository
-    private lateinit var registry: FakeKeyguardQuickAffordanceRegistry
     private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
     private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
     private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
@@ -116,6 +111,18 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+
+        overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
+        overrideResource(
+            R.array.config_keyguardQuickAffordanceDefaults,
+            arrayOf(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + ":" +
+                    BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + ":" +
+                    BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+            )
+        )
+
         whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
             .thenReturn(RETURNED_BURN_IN_OFFSET)
 
@@ -129,38 +136,18 @@
             FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
         dockManager = DockManagerFake()
         biometricSettingsRepository = FakeBiometricSettingsRepository()
-        registry =
-            FakeKeyguardQuickAffordanceRegistry(
-                mapOf(
-                    KeyguardQuickAffordancePosition.BOTTOM_START to
-                        listOf(
-                            homeControlsQuickAffordanceConfig,
-                        ),
-                    KeyguardQuickAffordancePosition.BOTTOM_END to
-                        listOf(
-                            quickAccessWalletAffordanceConfig,
-                            qrCodeScannerAffordanceConfig,
-                        ),
-                ),
-            )
-        repository = FakeKeyguardRepository()
         val featureFlags =
             FakeFeatureFlags().apply {
-                set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
                 set(Flags.FACE_AUTH_REFACTOR, true)
                 set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
                 set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
             }
 
-        val keyguardInteractor =
-            KeyguardInteractor(
-                repository = repository,
-                commandQueue = commandQueue,
-                featureFlags = featureFlags,
-                bouncerRepository = FakeKeyguardBouncerRepository(),
-            )
+        val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+        val keyguardInteractor = withDeps.keyguardInteractor
+        repository = withDeps.repository
+
         whenever(userTracker.userHandle).thenReturn(mock())
-        whenever(userTracker.userId).thenReturn(10)
         whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
             .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
         val testDispatcher = StandardTestDispatcher()
@@ -217,10 +204,10 @@
                 appContext = mContext,
                 scope = testScope.backgroundScope,
                 transitionInteractor =
-                    KeyguardTransitionInteractor(
-                        repository = FakeKeyguardTransitionRepository(),
-                        scope = testScope.backgroundScope
-                    ),
+                    KeyguardTransitionInteractorFactory.create(
+                            scope = TestScope().backgroundScope,
+                        )
+                        .keyguardTransitionInteractor,
                 repository = repository,
                 logger = UiEventLoggerFake(),
                 featureFlags = featureFlags,
@@ -233,7 +220,6 @@
                 quickAffordanceInteractor =
                     KeyguardQuickAffordanceInteractor(
                         keyguardInteractor = keyguardInteractor,
-                        registry = registry,
                         lockPatternUtils = lockPatternUtils,
                         keyguardStateController = keyguardStateController,
                         userTracker = userTracker,
@@ -555,91 +541,6 @@
         }
 
     @Test
-    fun isIndicationAreaPadded() =
-        testScope.runTest {
-            repository.setKeyguardShowing(true)
-            val value = collectLastValue(underTest.isIndicationAreaPadded)
-
-            assertThat(value()).isFalse()
-            setUpQuickAffordanceModel(
-                position = KeyguardQuickAffordancePosition.BOTTOM_START,
-                testConfig =
-                    TestConfig(
-                        isVisible = true,
-                        isClickable = true,
-                        icon = mock(),
-                        canShowWhileLocked = true,
-                        slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
-                    )
-            )
-            assertThat(value()).isTrue()
-            setUpQuickAffordanceModel(
-                position = KeyguardQuickAffordancePosition.BOTTOM_END,
-                testConfig =
-                    TestConfig(
-                        isVisible = true,
-                        isClickable = true,
-                        icon = mock(),
-                        canShowWhileLocked = false,
-                        slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
-                    )
-            )
-            assertThat(value()).isTrue()
-            setUpQuickAffordanceModel(
-                position = KeyguardQuickAffordancePosition.BOTTOM_START,
-                testConfig =
-                    TestConfig(
-                        isVisible = false,
-                        slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
-                    )
-            )
-            assertThat(value()).isTrue()
-            setUpQuickAffordanceModel(
-                position = KeyguardQuickAffordancePosition.BOTTOM_END,
-                testConfig =
-                    TestConfig(
-                        isVisible = false,
-                        slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
-                    )
-            )
-            assertThat(value()).isFalse()
-        }
-
-    @Test
-    fun indicationAreaTranslationX() =
-        testScope.runTest {
-            val value = collectLastValue(underTest.indicationAreaTranslationX)
-
-            assertThat(value()).isEqualTo(0f)
-            repository.setClockPosition(100, 100)
-            assertThat(value()).isEqualTo(100f)
-            repository.setClockPosition(200, 100)
-            assertThat(value()).isEqualTo(200f)
-            repository.setClockPosition(200, 200)
-            assertThat(value()).isEqualTo(200f)
-            repository.setClockPosition(300, 100)
-            assertThat(value()).isEqualTo(300f)
-        }
-
-    @Test
-    fun indicationAreaTranslationY() =
-        testScope.runTest {
-            val value =
-                collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
-
-            // Negative 0 - apparently there's a difference in floating point arithmetic - FML
-            assertThat(value()).isEqualTo(-0f)
-            val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
-            assertThat(value()).isEqualTo(expected1)
-            val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
-            assertThat(value()).isEqualTo(expected2)
-            val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
-            assertThat(value()).isEqualTo(expected3)
-            val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
-            assertThat(value()).isEqualTo(expected4)
-        }
-
-    @Test
     fun isClickable_trueWhenAlphaAtThreshold() =
         testScope.runTest {
             repository.setKeyguardShowing(true)
@@ -762,11 +663,6 @@
             )
         }
 
-    private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
-        repository.setDozeAmount(dozeAmount)
-        return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
-    }
-
     private suspend fun setUpQuickAffordanceModel(
         position: KeyguardQuickAffordancePosition,
         testConfig: TestConfig,
@@ -798,7 +694,8 @@
                 KeyguardQuickAffordanceConfig.LockScreenState.Hidden
             }
         config.setState(lockScreenState)
-        return config.key
+
+        return "${position.toSlotId()}::${config.key}"
     }
 
     private fun assertQuickAffordanceViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
deleted file mode 100644
index 15707c9..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ /dev/null
@@ -1,159 +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.keyguard.ui.viewmodel
-
-import android.os.Looper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.RoboPilotTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.utils.os.FakeHandler
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RoboPilotTest
-@RunWith(AndroidJUnit4::class)
-@kotlinx.coroutines.ExperimentalCoroutinesApi
-class KeyguardBouncerViewModelTest : SysuiTestCase() {
-    lateinit var underTest: KeyguardBouncerViewModel
-    lateinit var bouncerInteractor: PrimaryBouncerInteractor
-    @Mock lateinit var bouncerView: BouncerView
-    @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
-    @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
-    @Mock private lateinit var falsingCollector: FalsingCollector
-    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
-    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
-    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    private val mainHandler = FakeHandler(Looper.getMainLooper())
-    val repository = FakeKeyguardBouncerRepository()
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        bouncerInteractor =
-            PrimaryBouncerInteractor(
-                repository,
-                bouncerView,
-                mainHandler,
-                keyguardStateController,
-                keyguardSecurityModel,
-                primaryBouncerCallbackInteractor,
-                falsingCollector,
-                dismissCallbackRegistry,
-                context,
-                keyguardUpdateMonitor,
-                Mockito.mock(TrustRepository::class.java),
-                FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
-                TestScope().backgroundScope,
-            )
-        underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
-    }
-
-    @Test
-    fun setMessage() = runTest {
-        var message: BouncerShowMessageModel? = null
-        val job = underTest.bouncerShowMessage.onEach { message = it }.launchIn(this)
-
-        repository.setShowMessage(BouncerShowMessageModel("abc", null))
-        // Run the tasks that are pending at this point of virtual time.
-        runCurrent()
-        assertThat(message?.message).isEqualTo("abc")
-        job.cancel()
-    }
-
-    @Test
-    fun shouldUpdateSideFps_show() = runTest {
-        var count = 0
-        val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
-        repository.setPrimaryShow(true)
-        // Run the tasks that are pending at this point of virtual time.
-        runCurrent()
-        assertThat(count).isEqualTo(1)
-        job.cancel()
-    }
-
-    @Test
-    fun shouldUpdateSideFps_hide() = runTest {
-        repository.setPrimaryShow(true)
-        var count = 0
-        val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
-        repository.setPrimaryShow(false)
-        // Run the tasks that are pending at this point of virtual time.
-        runCurrent()
-        assertThat(count).isEqualTo(1)
-        job.cancel()
-    }
-
-    @Test
-    fun sideFpsShowing() = runTest {
-        var sideFpsIsShowing = false
-        val job = underTest.sideFpsShowing.onEach { sideFpsIsShowing = it }.launchIn(this)
-        repository.setSideFpsShowing(true)
-        // Run the tasks that are pending at this point of virtual time.
-        runCurrent()
-        assertThat(sideFpsIsShowing).isEqualTo(true)
-        job.cancel()
-    }
-
-    @Test
-    fun isShowing() = runTest {
-        var isShowing: Boolean? = null
-        val job = underTest.isShowing.onEach { isShowing = it }.launchIn(this)
-        repository.setPrimaryShow(true)
-        // Run the tasks that are pending at this point of virtual time.
-        runCurrent()
-        assertThat(isShowing).isEqualTo(true)
-        job.cancel()
-    }
-
-    @Test
-    fun isNotShowing() = runTest {
-        var isShowing: Boolean? = null
-        val job = underTest.isShowing.onEach { isShowing = it }.launchIn(this)
-        repository.setPrimaryShow(false)
-        // Run the tasks that are pending at this point of virtual time.
-        runCurrent()
-        assertThat(isShowing).isEqualTo(false)
-        job.cancel()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
new file mode 100644
index 0000000..34d93fc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
+
+    @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+    @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
+    @Mock private lateinit var featureFlags: FeatureFlags
+
+    private lateinit var underTest: KeyguardIndicationAreaViewModel
+    private lateinit var repository: FakeKeyguardRepository
+
+    private val startButtonFlow =
+        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
+            KeyguardQuickAffordanceViewModel(
+                slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
+            )
+        )
+    private val endButtonFlow =
+        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
+            KeyguardQuickAffordanceViewModel(
+                slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
+            )
+        )
+    private val alphaFlow = MutableStateFlow<Float>(1f)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
+            .thenReturn(RETURNED_BURN_IN_OFFSET)
+
+        val withDeps = KeyguardInteractorFactory.create()
+        val keyguardInteractor = withDeps.keyguardInteractor
+        repository = withDeps.repository
+
+        val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock()
+        whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow)
+        whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow)
+        whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow)
+        underTest =
+            KeyguardIndicationAreaViewModel(
+                keyguardInteractor = keyguardInteractor,
+                bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
+                keyguardBottomAreaViewModel = bottomAreaViewModel,
+                burnInHelperWrapper = burnInHelperWrapper,
+                shortcutsCombinedViewModel = shortcutsCombinedViewModel,
+                featureFlags = featureFlags,
+            )
+    }
+
+    @Test
+    fun alpha() = runTest {
+        val value = collectLastValue(underTest.alpha)
+
+        assertThat(value()).isEqualTo(1f)
+        alphaFlow.value = 0.1f
+        assertThat(value()).isEqualTo(0.1f)
+        alphaFlow.value = 0.5f
+        assertThat(value()).isEqualTo(0.5f)
+        alphaFlow.value = 0.2f
+        assertThat(value()).isEqualTo(0.2f)
+        alphaFlow.value = 0f
+        assertThat(value()).isEqualTo(0f)
+    }
+
+    @Test
+    fun isIndicationAreaPadded() = runTest {
+        repository.setKeyguardShowing(true)
+        val value = collectLastValue(underTest.isIndicationAreaPadded)
+
+        assertThat(value()).isFalse()
+        startButtonFlow.value = startButtonFlow.value.copy(isVisible = true)
+        assertThat(value()).isTrue()
+        endButtonFlow.value = endButtonFlow.value.copy(isVisible = true)
+        assertThat(value()).isTrue()
+        startButtonFlow.value = startButtonFlow.value.copy(isVisible = false)
+        assertThat(value()).isTrue()
+        endButtonFlow.value = endButtonFlow.value.copy(isVisible = false)
+        assertThat(value()).isFalse()
+    }
+
+    @Test
+    fun indicationAreaTranslationX() = runTest {
+        val value = collectLastValue(underTest.indicationAreaTranslationX)
+
+        assertThat(value()).isEqualTo(0f)
+        repository.setClockPosition(100, 100)
+        assertThat(value()).isEqualTo(100f)
+        repository.setClockPosition(200, 100)
+        assertThat(value()).isEqualTo(200f)
+        repository.setClockPosition(200, 200)
+        assertThat(value()).isEqualTo(200f)
+        repository.setClockPosition(300, 100)
+        assertThat(value()).isEqualTo(300f)
+    }
+
+    @Test
+    fun indicationAreaTranslationY() = runTest {
+        val value = collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
+
+        // Negative 0 - apparently there's a difference in floating point arithmetic - FML
+        assertThat(value()).isEqualTo(-0f)
+        val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
+        assertThat(value()).isEqualTo(expected1)
+        val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
+        assertThat(value()).isEqualTo(expected2)
+        val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
+        assertThat(value()).isEqualTo(expected3)
+        val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
+        assertThat(value()).isEqualTo(expected4)
+    }
+
+    private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+        repository.setDozeAmount(dozeAmount)
+        return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
+    }
+
+    companion object {
+        private const val DEFAULT_BURN_IN_OFFSET = 5
+        private const val RETURNED_BURN_IN_OFFSET = 3
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
new file mode 100644
index 0000000..ef38d54
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -0,0 +1,681 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.app.admin.DevicePolicyManager
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dock.DockManagerFake
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+import kotlin.math.max
+import kotlin.math.min
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
+
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var expandable: Expandable
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var launchAnimator: DialogLaunchAnimator
+    @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+
+    private lateinit var underTest: KeyguardQuickAffordancesCombinedViewModel
+
+    private lateinit var testScope: TestScope
+    private lateinit var repository: FakeKeyguardRepository
+    private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+    private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+    private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
+    private lateinit var dockManager: DockManagerFake
+    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+    private lateinit var keyguardInteractor: KeyguardInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
+        overrideResource(
+            R.array.config_keyguardQuickAffordanceDefaults,
+            arrayOf(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + ":" +
+                    BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + ":" +
+                    BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+            )
+        )
+
+        homeControlsQuickAffordanceConfig =
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+        quickAccessWalletAffordanceConfig =
+            FakeKeyguardQuickAffordanceConfig(
+                BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+            )
+        qrCodeScannerAffordanceConfig =
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+        dockManager = DockManagerFake()
+        biometricSettingsRepository = FakeBiometricSettingsRepository()
+        val featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA, true)
+                set(Flags.FACE_AUTH_REFACTOR, true)
+                set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
+                set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
+            }
+
+        val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+        keyguardInteractor = withDeps.keyguardInteractor
+        repository = withDeps.repository
+
+        whenever(userTracker.userHandle).thenReturn(mock())
+        whenever(lockPatternUtils.getStrongAuthForUser(ArgumentMatchers.anyInt()))
+            .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
+        val testDispatcher = StandardTestDispatcher()
+        testScope = TestScope(testDispatcher)
+
+        val localUserSelectionManager =
+            KeyguardQuickAffordanceLocalUserSelectionManager(
+                context = context,
+                userFileManager =
+                mock<UserFileManager>().apply {
+                    whenever(
+                        getSharedPreferences(
+                            ArgumentMatchers.anyString(),
+                            ArgumentMatchers.anyInt(),
+                            ArgumentMatchers.anyInt(),
+                        )
+                    )
+                        .thenReturn(FakeSharedPreferences())
+                },
+                userTracker = userTracker,
+                broadcastDispatcher = fakeBroadcastDispatcher,
+            )
+        val remoteUserSelectionManager =
+            KeyguardQuickAffordanceRemoteUserSelectionManager(
+                scope = testScope.backgroundScope,
+                userTracker = userTracker,
+                clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+                userHandle = UserHandle.SYSTEM,
+            )
+        val quickAffordanceRepository =
+            KeyguardQuickAffordanceRepository(
+                appContext = context,
+                scope = testScope.backgroundScope,
+                localUserSelectionManager = localUserSelectionManager,
+                remoteUserSelectionManager = remoteUserSelectionManager,
+                userTracker = userTracker,
+                legacySettingSyncer =
+                KeyguardQuickAffordanceLegacySettingSyncer(
+                    scope = testScope.backgroundScope,
+                    backgroundDispatcher = testDispatcher,
+                    secureSettings = FakeSettings(),
+                    selectionsManager = localUserSelectionManager,
+                ),
+                configs =
+                setOf(
+                    homeControlsQuickAffordanceConfig,
+                    quickAccessWalletAffordanceConfig,
+                    qrCodeScannerAffordanceConfig,
+                ),
+                dumpManager = mock(),
+                userHandle = UserHandle.SYSTEM,
+            )
+
+        underTest = KeyguardQuickAffordancesCombinedViewModel(
+            quickAffordanceInteractor =
+            KeyguardQuickAffordanceInteractor(
+                keyguardInteractor = keyguardInteractor,
+                lockPatternUtils = lockPatternUtils,
+                keyguardStateController = keyguardStateController,
+                userTracker = userTracker,
+                activityStarter = activityStarter,
+                featureFlags = featureFlags,
+                repository = { quickAffordanceRepository },
+                launchAnimator = launchAnimator,
+                logger = logger,
+                devicePolicyManager = devicePolicyManager,
+                dockManager = dockManager,
+                biometricSettingsRepository = biometricSettingsRepository,
+                backgroundDispatcher = testDispatcher,
+                appContext = mContext,
+            ),
+            keyguardInteractor = keyguardInteractor
+        )
+    }
+
+    @Test
+    fun startButton_present_visibleModel_startsActivityOnClick() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            val latest = collectLastValue(underTest.startButton)
+
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    isActivated = true,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = testConfig,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig = testConfig,
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun startButton_hiddenWhenDevicePolicyDisablesAllKeyguardFeatures() =
+        testScope.runTest {
+            whenever(
+                devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)
+            ).thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
+            repository.setKeyguardShowing(true)
+            val latest by collectLastValue(underTest.startButton)
+
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    isActivated = true,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = testConfig,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest,
+                testConfig =
+                TestConfig(
+                    isVisible = false,
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                ),
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun startButton_inPreviewMode_visibleEvenWhenKeyguardNotShowing() =
+        testScope.runTest {
+            underTest.onPreviewSlotSelected(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+            )
+            keyguardInteractor.previewMode.value =
+                KeyguardInteractor.PreviewMode(
+                    isInPreviewMode = true,
+                    shouldHighlightSelectedAffordance = true,
+                )
+
+            repository.setKeyguardShowing(false)
+            val latest = collectLastValue(underTest.startButton)
+
+            val icon: Icon = mock()
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig =
+                    TestConfig(
+                        isVisible = true,
+                        isClickable = true,
+                        isActivated = true,
+                        icon = icon,
+                        canShowWhileLocked = false,
+                        intent = Intent("action"),
+                        slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                    ),
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = false,
+                    isActivated = false,
+                    icon = icon,
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    isSelected = true,
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                ),
+                configKey = configKey,
+            )
+            Truth.assertThat(latest()?.isSelected).isTrue()
+        }
+
+    @Test
+    fun endButton_inHiglightedPreviewMode_dimmedWhenOtherIsSelected() =
+        testScope.runTest {
+            underTest.onPreviewSlotSelected(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+            )
+            keyguardInteractor.previewMode.value =
+                KeyguardInteractor.PreviewMode(
+                    isInPreviewMode = true,
+                    shouldHighlightSelectedAffordance = true,
+                )
+
+            repository.setKeyguardShowing(false)
+            val endButton = collectLastValue(underTest.endButton)
+
+            val icon: Icon = mock()
+            setUpQuickAffordanceModel(
+                position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    isActivated = true,
+                    icon = icon,
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                ),
+            )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_END,
+                    testConfig =
+                    TestConfig(
+                        isVisible = true,
+                        isClickable = true,
+                        isActivated = true,
+                        icon = icon,
+                        canShowWhileLocked = false,
+                        intent = Intent("action"),
+                        slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
+                    ),
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = endButton(),
+                testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = false,
+                    isActivated = false,
+                    icon = icon,
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    isDimmed = true,
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
+                ),
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun endButton_present_visibleModel_doNothingOnClick() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            val latest = collectLastValue(underTest.endButton)
+
+            val config =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent =
+                    null, // This will cause it to tell the system that the click was handled.
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_END,
+                    testConfig = config,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig = config,
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun startButton_notPresent_modelIsHidden() =
+        testScope.runTest {
+            val latest = collectLastValue(underTest.startButton)
+
+            val config =
+                TestConfig(
+                    isVisible = false,
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = config,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig = config,
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun animateButtonReveal() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+
+            setUpQuickAffordanceModel(
+                position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                testConfig = testConfig,
+            )
+
+            val value = collectLastValue(underTest.startButton.map { it.animateReveal })
+
+            Truth.assertThat(value()).isFalse()
+            repository.setAnimateDozingTransitions(true)
+            Truth.assertThat(value()).isTrue()
+            repository.setAnimateDozingTransitions(false)
+            Truth.assertThat(value()).isFalse()
+        }
+
+    @Test
+    fun isClickable_trueWhenAlphaAtThreshold() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            repository.setKeyguardAlpha(
+                KeyguardQuickAffordancesCombinedViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD
+            )
+
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = testConfig,
+                )
+
+            val latest = collectLastValue(underTest.startButton)
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig = testConfig,
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun isClickable_trueWhenAlphaAboveThreshold() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            val latest = collectLastValue(underTest.startButton)
+            repository.setKeyguardAlpha(
+                min(
+                    1f,
+                    KeyguardQuickAffordancesCombinedViewModel
+                        .AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f
+                ),
+            )
+
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = testConfig,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig = testConfig,
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun isClickable_falseWhenAlphaBelowThreshold() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            val latest = collectLastValue(underTest.startButton)
+            repository.setKeyguardAlpha(
+                max(
+                    0f,
+                    KeyguardQuickAffordancesCombinedViewModel
+                        .AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f
+                ),
+            )
+
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = false,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = testConfig,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig = testConfig,
+                configKey = configKey,
+            )
+        }
+
+    @Test
+    fun isClickable_falseWhenAlphaAtZero() =
+        testScope.runTest {
+            repository.setKeyguardShowing(true)
+            val latest = collectLastValue(underTest.startButton)
+            repository.setKeyguardAlpha(0f)
+
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = false,
+                    icon = mock(),
+                    canShowWhileLocked = false,
+                    intent = Intent("action"),
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = testConfig,
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = latest(),
+                testConfig = testConfig,
+                configKey = configKey,
+            )
+        }
+
+    private suspend fun setUpQuickAffordanceModel(
+        position: KeyguardQuickAffordancePosition,
+        testConfig: TestConfig,
+    ): String {
+        val config =
+            when (position) {
+                KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
+                KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
+            }
+
+        val lockScreenState =
+            if (testConfig.isVisible) {
+                if (testConfig.intent != null) {
+                    config.onTriggeredResult =
+                        KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+                            intent = testConfig.intent,
+                            canShowWhileLocked = testConfig.canShowWhileLocked,
+                        )
+                }
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
+                    activationState =
+                    when (testConfig.isActivated) {
+                        true -> ActivationState.Active
+                        false -> ActivationState.Inactive
+                    }
+                )
+            } else {
+                KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+            }
+        config.setState(lockScreenState)
+        return "${position.toSlotId()}::${config.key}"
+    }
+
+    private fun assertQuickAffordanceViewModel(
+        viewModel: KeyguardQuickAffordanceViewModel?,
+        testConfig: TestConfig,
+        configKey: String,
+    ) {
+        checkNotNull(viewModel)
+        Truth.assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
+        Truth.assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable)
+        Truth.assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated)
+        Truth.assertThat(viewModel.isSelected).isEqualTo(testConfig.isSelected)
+        Truth.assertThat(viewModel.isDimmed).isEqualTo(testConfig.isDimmed)
+        Truth.assertThat(viewModel.slotId).isEqualTo(testConfig.slotId)
+        if (testConfig.isVisible) {
+            Truth.assertThat(viewModel.icon).isEqualTo(testConfig.icon)
+            viewModel.onClicked.invoke(
+                KeyguardQuickAffordanceViewModel.OnClickedParameters(
+                    configKey = configKey,
+                    expandable = expandable,
+                    slotId = viewModel.slotId,
+                )
+            )
+            if (testConfig.intent != null) {
+                Truth.assertThat(
+                    Mockito.mockingDetails(activityStarter).invocations
+                ).hasSize(1)
+            } else {
+                Mockito.verifyZeroInteractions(activityStarter)
+            }
+        } else {
+            Truth.assertThat(viewModel.isVisible).isFalse()
+        }
+    }
+
+    private data class TestConfig(
+        val isVisible: Boolean,
+        val isClickable: Boolean = false,
+        val isActivated: Boolean = false,
+        val icon: Icon? = null,
+        val canShowWhileLocked: Boolean = false,
+        val intent: Intent? = null,
+        val isSelected: Boolean = false,
+        val isDimmed: Boolean = false,
+        val slotId: String = ""
+    ) {
+        init {
+            check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
new file mode 100644
index 0000000..05e933b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardRootViewModelTest : SysuiTestCase() {
+
+    private lateinit var underTest: KeyguardRootViewModel
+    private lateinit var testScope: TestScope
+    private lateinit var repository: FakeKeyguardRepository
+    private lateinit var keyguardInteractor: KeyguardInteractor
+    @Mock private lateinit var keyguardQuickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        val testDispatcher = StandardTestDispatcher()
+        testScope = TestScope(testDispatcher)
+
+        val featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA, true)
+                set(Flags.FACE_AUTH_REFACTOR, true)
+            }
+
+        val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+        keyguardInteractor = withDeps.keyguardInteractor
+        repository = withDeps.repository
+
+        underTest = KeyguardRootViewModel(
+            keyguardInteractor,
+            keyguardQuickAffordancesCombinedViewModel,
+        )
+    }
+
+    @Test
+    fun alpha() =
+        testScope.runTest {
+            val value = collectLastValue(underTest.alpha)
+
+            Truth.assertThat(value()).isEqualTo(1f)
+            repository.setKeyguardAlpha(0.1f)
+            Truth.assertThat(value()).isEqualTo(0.1f)
+            repository.setKeyguardAlpha(0.5f)
+            Truth.assertThat(value()).isEqualTo(0.5f)
+            repository.setKeyguardAlpha(0.2f)
+            Truth.assertThat(value()).isEqualTo(0.2f)
+            repository.setKeyguardAlpha(0f)
+            Truth.assertThat(value()).isEqualTo(0f)
+        }
+
+    @Test
+    fun alpha_inPreviewMode_doesNotChange() =
+        testScope.runTest {
+            val value = collectLastValue(underTest.alpha)
+            underTest.enablePreviewMode(
+                initiallySelectedSlotId = null,
+                shouldHighlightSelectedAffordance = false,
+            )
+
+            Truth.assertThat(value()).isEqualTo(1f)
+            repository.setKeyguardAlpha(0.1f)
+            Truth.assertThat(value()).isEqualTo(1f)
+            repository.setKeyguardAlpha(0.5f)
+            Truth.assertThat(value()).isEqualTo(1f)
+            repository.setKeyguardAlpha(0.2f)
+            Truth.assertThat(value()).isEqualTo(1f)
+            repository.setKeyguardAlpha(0f)
+            Truth.assertThat(value()).isEqualTo(1f)
+        }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 8ba3f0f..23f243c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -19,17 +19,14 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -41,8 +38,8 @@
 @RunWith(JUnit4::class)
 class LockscreenSceneViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor =
         utils.authenticationInteractor(
@@ -52,31 +49,22 @@
     private val underTest =
         LockscreenSceneViewModel(
             applicationScope = testScope.backgroundScope,
-            interactorFactory =
-                object : LockscreenSceneInteractor.Factory {
-                    override fun create(containerName: String): LockscreenSceneInteractor {
-                        return utils.lockScreenSceneInteractor(
-                            authenticationInteractor = authenticationInteractor,
-                            sceneInteractor = sceneInteractor,
-                            bouncerInteractor =
-                                utils.bouncerInteractor(
-                                    authenticationInteractor = authenticationInteractor,
-                                    sceneInteractor = sceneInteractor,
-                                ),
-                        )
-                    }
-                },
-            containerName = CONTAINER_1
+            authenticationInteractor = authenticationInteractor,
+            bouncerInteractor =
+                utils.bouncerInteractor(
+                    authenticationInteractor = authenticationInteractor,
+                    sceneInteractor = sceneInteractor,
+                ),
         )
 
     @Test
     fun lockButtonIcon_whenLocked() =
         testScope.runTest {
             val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setUnlocked(false)
 
             assertThat((lockButtonIcon as? Icon.Resource)?.res)
                 .isEqualTo(R.drawable.ic_device_lock_on)
@@ -86,31 +74,34 @@
     fun lockButtonIcon_whenUnlocked() =
         testScope.runTest {
             val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
-            authenticationInteractor.setAuthenticationMethod(
-                AuthenticationMethodModel.Password("password")
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
             )
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setUnlocked(true)
 
             assertThat((lockButtonIcon as? Icon.Resource)?.res)
                 .isEqualTo(R.drawable.ic_device_lock_off)
         }
 
     @Test
-    fun upTransitionSceneKey_swipeToUnlockedEnabled_gone() =
+    fun upTransitionSceneKey_canSwipeToUnlock_gone() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.authenticationRepository.setLockscreenEnabled(true)
+            utils.authenticationRepository.setUnlocked(true)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
         }
 
     @Test
-    fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() =
+    fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
         }
@@ -118,9 +109,9 @@
     @Test
     fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onLockButtonClicked()
@@ -131,9 +122,9 @@
     @Test
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.unlockDevice()
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onContentClicked()
@@ -144,9 +135,9 @@
     @Test
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onContentClicked()
@@ -157,9 +148,9 @@
     @Test
     fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.unlockDevice()
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onLockButtonClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index ea17751..12fe07f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest = LockscreenToDreamingTransitionViewModel(interactor)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index bf56a98..83ae631 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest = LockscreenToOccludedTransitionViewModel(interactor)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index 34da26e..8860399 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,7 +46,12 @@
     @Before
     fun setUp() {
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest = OccludedToLockscreenTransitionViewModel(interactor)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 65b2ef7..904662e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -20,9 +20,10 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -31,8 +32,6 @@
 import com.android.systemui.util.mockito.whenever
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
@@ -55,7 +54,12 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         repository = FakeKeyguardTransitionRepository()
-        val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope)
+        val interactor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = repository,
+                )
+                .keyguardTransitionInteractor
         underTest =
             PrimaryBouncerToGoneTransitionViewModel(
                 interactor,
@@ -70,9 +74,7 @@
     @Test
     fun bouncerAlpha() =
         runTest(UnconfinedTestDispatcher()) {
-            val values = mutableListOf<Float>()
-
-            val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this)
+            val values by collectValues(underTest.bouncerAlpha)
 
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0.3f))
@@ -80,16 +82,12 @@
 
             assertThat(values.size).isEqualTo(3)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
-
-            job.cancel()
         }
 
     @Test
     fun bouncerAlpha_runDimissFromKeyguard() =
         runTest(UnconfinedTestDispatcher()) {
-            val values = mutableListOf<Float>()
-
-            val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this)
+            val values by collectValues(underTest.bouncerAlpha)
 
             whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
 
@@ -99,16 +97,52 @@
 
             assertThat(values.size).isEqualTo(3)
             values.forEach { assertThat(it).isEqualTo(0f) }
+        }
 
-            job.cancel()
+    @Test
+    fun lockscreenAlpha() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values by collectValues(underTest.lockscreenAlpha)
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(1f))
+
+            assertThat(values.size).isEqualTo(2)
+            values.forEach { assertThat(it).isEqualTo(0f) }
+        }
+
+    @Test
+    fun lockscreenAlpha_runDimissFromKeyguard() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values by collectValues(underTest.lockscreenAlpha)
+
+            whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(1f))
+
+            assertThat(values.size).isEqualTo(2)
+            values.forEach { assertThat(it).isEqualTo(1f) }
+        }
+
+    @Test
+    fun lockscreenAlpha_leaveShadeOpen() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values by collectValues(underTest.lockscreenAlpha)
+
+            whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true)
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(1f))
+
+            assertThat(values.size).isEqualTo(2)
+            values.forEach { assertThat(it).isEqualTo(1f) }
         }
 
     @Test
     fun scrimAlpha_runDimissFromKeyguard() =
         runTest(UnconfinedTestDispatcher()) {
-            val values = mutableListOf<ScrimAlpha>()
-
-            val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
+            val values by collectValues(underTest.scrimAlpha)
 
             whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
 
@@ -119,16 +153,12 @@
 
             assertThat(values.size).isEqualTo(4)
             values.forEach { assertThat(it).isEqualTo(ScrimAlpha()) }
-
-            job.cancel()
         }
 
     @Test
     fun scrimBehindAlpha_leaveShadeOpen() =
         runTest(UnconfinedTestDispatcher()) {
-            val values = mutableListOf<ScrimAlpha>()
-
-            val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
+            val values by collectValues(underTest.scrimAlpha)
 
             whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true)
 
@@ -141,16 +171,12 @@
             values.forEach {
                 assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f))
             }
-
-            job.cancel()
         }
 
     @Test
     fun scrimBehindAlpha_doNotLeaveShadeOpen() =
         runTest(UnconfinedTestDispatcher()) {
-            val values = mutableListOf<ScrimAlpha>()
-
-            val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
+            val values by collectValues(underTest.scrimAlpha)
 
             whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
 
@@ -164,8 +190,6 @@
             values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) }
             values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) }
             assertThat(values[3].behindAlpha).isEqualTo(0f)
-
-            job.cancel()
         }
 
     private fun step(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
new file mode 100644
index 0000000..bd17de3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsAodViewModelTest : SysuiTestCase() {
+    private val defaultPadding = 12
+    private lateinit var underTest: UdfpsAodViewModel
+
+    private lateinit var testScope: TestScope
+    private lateinit var configRepository: FakeConfigurationRepository
+    private lateinit var bouncerRepository: KeyguardBouncerRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var shadeRepository: FakeShadeRepository
+    private lateinit var keyguardInteractor: KeyguardInteractor
+
+    @Mock private lateinit var dialogManager: SystemUIDialogManager
+    @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding)
+        testScope = TestScope()
+        shadeRepository = FakeShadeRepository()
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+                set(Flags.FACE_AUTH_REFACTOR, false)
+            }
+        KeyguardInteractorFactory.create(
+                featureFlags = featureFlags,
+            )
+            .also {
+                keyguardInteractor = it.keyguardInteractor
+                keyguardRepository = it.repository
+                configRepository = it.configurationRepository
+                bouncerRepository = it.bouncerRepository
+            }
+        val udfpsKeyguardInteractor =
+            UdfpsKeyguardInteractor(
+                configRepository,
+                BurnInInteractor(
+                    context,
+                    burnInHelper,
+                    testScope.backgroundScope,
+                    configRepository,
+                    keyguardInteractor,
+                ),
+                keyguardInteractor,
+                shadeRepository,
+                dialogManager,
+            )
+
+        underTest =
+            UdfpsAodViewModel(
+                udfpsKeyguardInteractor,
+                context,
+            )
+    }
+
+    @Test
+    fun alphaAndVisibleUpdates_onDozeAmountChanges() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha)
+            val visible by collectLastValue(underTest.isVisible)
+
+            keyguardRepository.setDozeAmount(0f)
+            runCurrent()
+            assertThat(alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+
+            keyguardRepository.setDozeAmount(.65f)
+            runCurrent()
+            assertThat(alpha).isEqualTo(.65f)
+            assertThat(visible).isTrue()
+
+            keyguardRepository.setDozeAmount(.23f)
+            runCurrent()
+            assertThat(alpha).isEqualTo(.23f)
+            assertThat(visible).isTrue()
+
+            keyguardRepository.setDozeAmount(1f)
+            runCurrent()
+            assertThat(alpha).isEqualTo(1f)
+            assertThat(visible).isTrue()
+        }
+
+    @Test
+    fun paddingUpdates_onScaleForResolutionChanges() =
+        testScope.runTest {
+            val padding by collectLastValue(underTest.padding)
+
+            configRepository.setScaleForResolution(1f)
+            runCurrent()
+            assertThat(padding).isEqualTo(defaultPadding)
+
+            configRepository.setScaleForResolution(2f)
+            runCurrent()
+            assertThat(padding).isEqualTo(defaultPadding * 2)
+
+            configRepository.setScaleForResolution(.5f)
+            runCurrent()
+            assertThat(padding).isEqualTo((defaultPadding * .5f).toInt())
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
new file mode 100644
index 0000000..0ad14d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+/** Tests UdfpsFingerprintViewModel specific flows. */
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsFingerprintViewModelTest : SysuiTestCase() {
+    private val defaultPadding = 12
+    private lateinit var underTest: FingerprintViewModel
+
+    private lateinit var testScope: TestScope
+    private lateinit var configRepository: FakeConfigurationRepository
+    private lateinit var bouncerRepository: KeyguardBouncerRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var fakeCommandQueue: FakeCommandQueue
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var shadeRepository: FakeShadeRepository
+
+    @Mock private lateinit var burnInHelper: BurnInHelperWrapper
+    @Mock private lateinit var dialogManager: SystemUIDialogManager
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding)
+        testScope = TestScope()
+        configRepository = FakeConfigurationRepository()
+        keyguardRepository = FakeKeyguardRepository()
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        fakeCommandQueue = FakeCommandQueue()
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+                set(Flags.FACE_AUTH_REFACTOR, false)
+            }
+        bouncerRepository = FakeKeyguardBouncerRepository()
+        transitionRepository = FakeKeyguardTransitionRepository()
+        shadeRepository = FakeShadeRepository()
+        val keyguardInteractor =
+            KeyguardInteractorFactory.create(
+                    repository = keyguardRepository,
+                    featureFlags = featureFlags,
+                )
+                .keyguardInteractor
+
+        val transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                    keyguardInteractor = keyguardInteractor,
+                )
+                .keyguardTransitionInteractor
+
+        underTest =
+            FingerprintViewModel(
+                context,
+                transitionInteractor,
+                UdfpsKeyguardInteractor(
+                    configRepository,
+                    BurnInInteractor(
+                        context,
+                        burnInHelper,
+                        testScope.backgroundScope,
+                        configRepository,
+                        keyguardInteractor,
+                    ),
+                    keyguardInteractor,
+                    shadeRepository,
+                    dialogManager,
+                ),
+                keyguardInteractor,
+            )
+    }
+
+    @Test
+    fun paddingUpdates_onScaleForResolutionChanges() =
+        testScope.runTest {
+            val padding by collectLastValue(underTest.padding)
+
+            configRepository.setScaleForResolution(1f)
+            runCurrent()
+            assertThat(padding).isEqualTo(defaultPadding)
+
+            configRepository.setScaleForResolution(2f)
+            runCurrent()
+            assertThat(padding).isEqualTo(defaultPadding * 2)
+
+            configRepository.setScaleForResolution(.5f)
+            runCurrent()
+            assertThat(padding).isEqualTo((defaultPadding * .5).toInt())
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
new file mode 100644
index 0000000..edcaa1d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
@@ -0,0 +1,764 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.Utils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.wm.shell.animation.Interpolators
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+/** Tests UDFPS lockscreen view model transitions. */
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UdfpsLockscreenViewModelTest : SysuiTestCase() {
+    private val lockscreenColorResId = android.R.attr.textColorPrimary
+    private val alternateBouncerResId = com.android.internal.R.attr.materialColorOnPrimaryFixed
+    private val lockscreenColor = Utils.getColorAttrDefaultColor(context, lockscreenColorResId)
+    private val alternateBouncerColor =
+        Utils.getColorAttrDefaultColor(context, alternateBouncerResId)
+
+    @Mock private lateinit var dialogManager: SystemUIDialogManager
+
+    private lateinit var underTest: UdfpsLockscreenViewModel
+    private lateinit var testScope: TestScope
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var configRepository: FakeConfigurationRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var keyguardInteractor: KeyguardInteractor
+    private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+    private lateinit var shadeRepository: FakeShadeRepository
+    private lateinit var featureFlags: FakeFeatureFlags
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+        transitionRepository = FakeKeyguardTransitionRepository()
+        shadeRepository = FakeShadeRepository()
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
+                set(Flags.FACE_AUTH_REFACTOR, false)
+            }
+        KeyguardInteractorFactory.create(
+                featureFlags = featureFlags,
+            )
+            .also {
+                keyguardInteractor = it.keyguardInteractor
+                keyguardRepository = it.repository
+                configRepository = it.configurationRepository
+                bouncerRepository = it.bouncerRepository
+            }
+
+        val transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                    keyguardInteractor = keyguardInteractor,
+                )
+                .keyguardTransitionInteractor
+
+        underTest =
+            UdfpsLockscreenViewModel(
+                context,
+                lockscreenColorResId,
+                alternateBouncerResId,
+                transitionInteractor,
+                UdfpsKeyguardInteractor(
+                    configRepository,
+                    BurnInInteractor(
+                        context,
+                        burnInHelperWrapper = mock(),
+                        testScope.backgroundScope,
+                        configRepository,
+                        keyguardInteractor,
+                    ),
+                    keyguardInteractor,
+                    shadeRepository,
+                    dialogManager,
+                ),
+                keyguardInteractor,
+            )
+    }
+
+    @Test
+    fun goneToAodTransition() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: gone -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "goneToAodTransition",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+
+            // TransitionState.RUNNING: gone -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "goneToAodTransition",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+
+            // TransitionState.FINISHED: gone -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "goneToAodTransition",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+        }
+
+    @Test
+    fun lockscreenToAod() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+
+            // TransitionState.STARTED: lockscreen -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "lockscreenToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: lockscreen -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "lockscreenToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: lockscreen -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "lockscreenToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isFalse()
+        }
+
+    @Test
+    fun lockscreenShadeLockedToAod() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+
+            // TransitionState.STARTED: lockscreen -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "lockscreenToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+
+            // TransitionState.RUNNING: lockscreen -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "lockscreenToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+
+            // TransitionState.FINISHED: lockscreen -> AOD
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "lockscreenToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isFalse()
+        }
+
+    @Test
+    fun aodToLockscreen() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: AOD -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "aodToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isFalse()
+
+            // TransitionState.RUNNING: AOD -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "aodToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: AOD -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "aodToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+        }
+
+    @Test
+    fun lockscreenToAlternateBouncer() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+
+            // TransitionState.STARTED: lockscreen -> alternate bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "lockscreenToAlternateBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: lockscreen -> alternate bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "lockscreenToAlternateBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: lockscreen -> alternate bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "lockscreenToAlternateBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+        }
+
+    fun alternateBouncerToPrimaryBouncer() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: alternate bouncer -> primary bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "alternateBouncerToPrimaryBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: alternate bouncer -> primary bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "alternateBouncerToPrimaryBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f))
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: alternate bouncer -> primary bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "alternateBouncerToPrimaryBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isFalse()
+        }
+
+    fun alternateBouncerToAod() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: alternate bouncer -> aod
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "alternateBouncerToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: alternate bouncer -> aod
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.AOD,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "alternateBouncerToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: alternate bouncer -> aod
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "alternateBouncerToAod",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isFalse()
+        }
+
+    @Test
+    fun lockscreenToOccluded() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+
+            // TransitionState.STARTED: lockscreen -> occluded
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "lockscreenToOccluded",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: lockscreen -> occluded
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "lockscreenToOccluded",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f))
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: lockscreen -> occluded
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "lockscreenToOccluded",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isFalse()
+        }
+
+    @Test
+    fun occludedToLockscreen() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: occluded -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "occludedToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: occluded -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "occludedToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: occluded -> lockscreen
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "occludedToLockscreen",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(lockscreenColor)
+            assertThat(visible).isTrue()
+        }
+
+    @Test
+    fun qsProgressChange() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+            givenTransitionToLockscreenFinished()
+
+            // qsExpansion = 0f
+            shadeRepository.setQsExpansion(0f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(visible).isEqualTo(true)
+
+            // qsExpansion = .25
+            shadeRepository.setQsExpansion(.2f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(.6f)
+            assertThat(visible).isEqualTo(true)
+
+            // qsExpansion = .5
+            shadeRepository.setQsExpansion(.5f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isEqualTo(false)
+
+            // qsExpansion = 1
+            shadeRepository.setQsExpansion(1f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isEqualTo(false)
+        }
+
+    @Test
+    fun shadeExpansionChanged() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+            givenTransitionToLockscreenFinished()
+
+            // shadeExpansion = 0f
+            shadeRepository.setUdfpsTransitionToFullShadeProgress(0f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(visible).isEqualTo(true)
+
+            // shadeExpansion = .2
+            shadeRepository.setUdfpsTransitionToFullShadeProgress(.2f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(.8f)
+            assertThat(visible).isEqualTo(true)
+
+            // shadeExpansion = .5
+            shadeRepository.setUdfpsTransitionToFullShadeProgress(.5f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(.5f)
+            assertThat(visible).isEqualTo(true)
+
+            // shadeExpansion = 1
+            shadeRepository.setUdfpsTransitionToFullShadeProgress(1f)
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(0f)
+            assertThat(visible).isEqualTo(false)
+        }
+
+    @Test
+    fun dialogHideAffordancesRequestChanged() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            givenTransitionToLockscreenFinished()
+            runCurrent()
+            val captor = argumentCaptor<SystemUIDialogManager.Listener>()
+            Mockito.verify(dialogManager).registerListener(captor.capture())
+
+            captor.value.shouldHideAffordances(true)
+            assertThat(transition?.alpha).isEqualTo(0f)
+
+            captor.value.shouldHideAffordances(false)
+            assertThat(transition?.alpha).isEqualTo(1f)
+        }
+
+    @Test
+    fun occludedToAlternateBouncer() =
+        testScope.runTest {
+            val transition by collectLastValue(underTest.transition)
+            val visible by collectLastValue(underTest.visible)
+
+            // TransitionState.STARTED: occluded -> alternate bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "occludedToAlternateBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(0f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.RUNNING: occluded -> alternate bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    value = .6f,
+                    transitionState = TransitionState.RUNNING,
+                    ownerName = "occludedToAlternateBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale)
+                .isEqualTo(Interpolators.FAST_OUT_SLOW_IN.getInterpolation(.6f))
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+
+            // TransitionState.FINISHED: occluded -> alternate bouncer
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "occludedToAlternateBouncer",
+                )
+            )
+            runCurrent()
+            assertThat(transition?.alpha).isEqualTo(1f)
+            assertThat(transition?.scale).isEqualTo(1f)
+            assertThat(transition?.color).isEqualTo(alternateBouncerColor)
+            assertThat(visible).isTrue()
+        }
+
+    private suspend fun givenTransitionToLockscreenFinished() {
+        transitionRepository.sendTransitionStep(
+            TransitionStep(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                value = 1f,
+                transitionState = TransitionState.FINISHED,
+                ownerName = "givenTransitionToLockscreenFinished",
+            )
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/IndicationHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/IndicationHelperTest.kt
new file mode 100644
index 0000000..fd0ff9b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/IndicationHelperTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.util
+
+import android.hardware.biometrics.BiometricFaceConstants.BIOMETRIC_ERROR_POWER_PRESSED
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_CANCELED
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_TIMEOUT
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED
+import android.hardware.biometrics.BiometricSourceType
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class IndicationHelperTest : SysuiTestCase() {
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+    @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    private lateinit var underTest: IndicationHelper
+
+    @Before
+    fun setup() {
+        underTest =
+            IndicationHelper(
+                keyguardUpdateMonitor,
+            )
+    }
+
+    @Test
+    fun suppressErrorMsg_faceErrorCancelled() {
+        givenPrimaryAuthNotRequired()
+        assertTrue(underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_CANCELED))
+    }
+
+    @Test
+    fun suppressErrorMsg_faceErrorUnableToProcess() {
+        givenPrimaryAuthNotRequired()
+        assertTrue(
+            underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_UNABLE_TO_PROCESS)
+        )
+    }
+
+    @Test
+    fun suppressErrorMsg_facePrimaryAuthRequired() {
+        givenPrimaryAuthRequired()
+        assertTrue(underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_TIMEOUT))
+    }
+
+    @Test
+    fun doNotSuppressErrorMsg_facePrimaryAuthRequired_faceLockout() {
+        givenPrimaryAuthRequired()
+        assertFalse(underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_LOCKOUT))
+        assertFalse(
+            underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_LOCKOUT_PERMANENT)
+        )
+    }
+
+    @Test
+    fun suppressErrorMsg_fingerprintErrorCancelled() {
+        givenPrimaryAuthNotRequired()
+        assertTrue(
+            underTest.shouldSuppressErrorMsg(
+                BiometricSourceType.FINGERPRINT,
+                FINGERPRINT_ERROR_CANCELED
+            )
+        )
+    }
+
+    @Test
+    fun suppressErrorMsg_fingerprintErrorUserCancelled() {
+        givenPrimaryAuthNotRequired()
+        assertTrue(
+            underTest.shouldSuppressErrorMsg(
+                BiometricSourceType.FINGERPRINT,
+                FINGERPRINT_ERROR_USER_CANCELED
+            )
+        )
+    }
+
+    @Test
+    fun suppressErrorMsg_fingerprintErrorPowerPressed() {
+        givenPrimaryAuthNotRequired()
+        assertTrue(
+            underTest.shouldSuppressErrorMsg(
+                BiometricSourceType.FINGERPRINT,
+                BIOMETRIC_ERROR_POWER_PRESSED
+            )
+        )
+    }
+
+    @Test
+    fun suppressErrorMsg_fingerprintPrimaryAuthRequired() {
+        givenPrimaryAuthRequired()
+        assertTrue(
+            underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FINGERPRINT_ERROR_TIMEOUT)
+        )
+    }
+
+    @Test
+    fun doNotSuppressErrorMsg_fingerprintPrimaryAuthRequired_fingerprintLockout() {
+        givenPrimaryAuthRequired()
+        assertFalse(
+            underTest.shouldSuppressErrorMsg(
+                BiometricSourceType.FINGERPRINT,
+                FINGERPRINT_ERROR_LOCKOUT
+            )
+        )
+        assertFalse(
+            underTest.shouldSuppressErrorMsg(
+                BiometricSourceType.FACE,
+                FINGERPRINT_ERROR_LOCKOUT_PERMANENT
+            )
+        )
+    }
+
+    @Test
+    fun isFaceLockoutErrorMsgId() {
+        givenPrimaryAuthRequired()
+        assertTrue(underTest.isFaceLockoutErrorMsg(FACE_ERROR_LOCKOUT))
+        assertTrue(underTest.isFaceLockoutErrorMsg(FACE_ERROR_LOCKOUT_PERMANENT))
+        assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_TIMEOUT))
+        assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_CANCELED))
+        assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_UNABLE_TO_PROCESS))
+        assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_VENDOR))
+    }
+
+    private fun givenPrimaryAuthNotRequired() {
+        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+            .thenReturn(true)
+    }
+
+    private fun givenPrimaryAuthRequired() {
+        whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+            .thenReturn(false)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
index 0cf6d3d..b5eae5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt
@@ -2,6 +2,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.core.Logger
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
@@ -32,7 +33,8 @@
 
     @Test
     fun log_shouldSaveLogToBuffer() {
-        buffer.log("Test", LogLevel.INFO, "Some test message")
+        val logger = Logger(buffer, "Test")
+        logger.i("Some test message")
 
         val dumpedString = dumpBuffer()
 
@@ -41,8 +43,9 @@
 
     @Test
     fun log_shouldRotateIfLogBufferIsFull() {
-        buffer.log("Test", LogLevel.INFO, "This should be rotated")
-        buffer.log("Test", LogLevel.INFO, "New test message")
+        val logger = Logger(buffer, "Test")
+        logger.i("This should be rotated")
+        logger.i("New test message")
 
         val dumpedString = dumpBuffer()
 
@@ -53,7 +56,8 @@
     fun dump_writesExceptionAndStacktrace() {
         buffer = createBuffer()
         val exception = createTestException("Exception message", "TestClass")
-        buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+        val logger = Logger(buffer, "Test")
+        logger.e("Extra message", exception)
 
         val dumpedString = dumpBuffer()
 
@@ -72,7 +76,8 @@
                 "TestClass",
                 cause = createTestException("The real cause!", "TestClass")
             )
-        buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+        val logger = Logger(buffer, "Test")
+        logger.e("Extra message", exception)
 
         val dumpedString = dumpBuffer()
 
@@ -93,7 +98,8 @@
             )
         )
         exception.addSuppressed(createTestException("Second suppressed exception", "SecondClass"))
-        buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception)
+        val logger = Logger(buffer, "Test")
+        logger.e("Extra message", exception)
 
         val dumpedStr = dumpBuffer()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt b/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt
new file mode 100644
index 0000000..272d686
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.core
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogMessageImpl
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import org.mockito.Mockito.anyString
+
+/**
+ * A fake [LogBuffer] used for testing that obtains a real [LogMessage] to prevent a
+ * [NullPointerException].
+ */
+class FakeLogBuffer private constructor() {
+    class Factory private constructor() {
+        companion object {
+            fun create(): LogBuffer {
+                val logBuffer = mock<LogBuffer>()
+                whenever(
+                        logBuffer.obtain(
+                            tag = anyString(),
+                            level = any(),
+                            messagePrinter = any(),
+                            exception = nullable(),
+                        )
+                    )
+                    .thenReturn(LogMessageImpl.Factory.create())
+                return logBuffer
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt
new file mode 100644
index 0000000..ab19b3a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt
@@ -0,0 +1,140 @@
+package com.android.systemui.log.core
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogMessageImpl
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
+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.Mock
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.junit.MockitoJUnitRunner
+
+@SmallTest
+@RunWith(MockitoJUnitRunner::class)
+class LoggerTest : SysuiTestCase() {
+    @Mock private lateinit var buffer: MessageBuffer
+    private lateinit var message: LogMessage
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        whenever(buffer.obtain(any(), any(), any(), isNull())).thenAnswer {
+            message = LogMessageImpl.Factory.create()
+            return@thenAnswer message
+        }
+    }
+
+    @Test
+    fun log_shouldCommitLogMessage() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.log(LogLevel.DEBUG, { "count=$int1" }) {
+            int1 = 1
+            str1 = "test"
+            bool1 = true
+        }
+
+        assertThat(message.int1).isEqualTo(1)
+        assertThat(message.str1).isEqualTo("test")
+        assertThat(message.bool1).isEqualTo(true)
+    }
+
+    @Test
+    fun log_shouldUseCorrectLoggerTag() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.log(LogLevel.DEBUG, { "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(eq("LoggerTest"), any(), any(), nullable())
+    }
+
+    @Test
+    fun v_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.v({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.VERBOSE), any(), nullable())
+    }
+
+    @Test
+    fun v_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.v("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.VERBOSE), any(), nullable())
+    }
+
+    @Test
+    fun d_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.d({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.DEBUG), any(), nullable())
+    }
+
+    @Test
+    fun d_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.d("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.DEBUG), any(), nullable())
+    }
+
+    @Test
+    fun i_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.i({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.INFO), any(), nullable())
+    }
+
+    @Test
+    fun i_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.i("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.INFO), any(), nullable())
+    }
+
+    @Test
+    fun w_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.w({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.WARNING), any(), nullable())
+    }
+
+    @Test
+    fun w_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.w("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.WARNING), any(), nullable())
+    }
+
+    @Test
+    fun e_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.e({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.ERROR), any(), nullable())
+    }
+
+    @Test
+    fun e_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.e("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.ERROR), any(), nullable())
+    }
+
+    @Test
+    fun wtf_withMessageInitializer_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.wtf({ "count=$int1" }) { int1 = 1 }
+        verify(buffer).obtain(anyString(), eq(LogLevel.WTF), any(), nullable())
+    }
+
+    @Test
+    fun wtf_withCompileTimeMessage_shouldLogAtCorrectLevel() {
+        val logger = Logger(buffer, "LoggerTest")
+        logger.wtf("Message")
+        verify(buffer).obtain(anyString(), eq(LogLevel.WTF), any(), nullable())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
index 12f4689..83182c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
@@ -18,8 +18,8 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX
 import com.android.systemui.log.table.TableChange.Companion.MAX_STRING_LENGTH
 import com.android.systemui.util.mockito.any
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 56698e0..d1299d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -261,7 +261,6 @@
         whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(SMARTSPACE_CREATION_TIME)
         whenever(mediaSmartspaceTarget.expiryTimeMillis).thenReturn(SMARTSPACE_EXPIRY_TIME)
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
-        whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true)
         whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
         whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
         whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false)
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
index 5b8272b0..ef51e47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -30,6 +30,7 @@
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.TestScopeProvider
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
@@ -37,6 +38,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -66,7 +68,6 @@
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -132,7 +133,7 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        context.resources.configuration.locales = LocaleList(Locale.US, Locale.UK)
+        context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK))
         transitionRepository = FakeKeyguardTransitionRepository()
         mediaCarouselController =
             MediaCarouselController(
@@ -152,7 +153,11 @@
                 debugLogger,
                 mediaFlags,
                 keyguardUpdateMonitor,
-                KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
+                KeyguardTransitionInteractorFactory.create(
+                        scope = TestScopeProvider.getTestScope().backgroundScope,
+                        repository = transitionRepository,
+                    )
+                    .keyguardTransitionInteractor,
                 globalSettings
             )
         verify(configurationController).addCallback(capture(configListener))
@@ -730,13 +735,13 @@
 
     @Test
     fun testOnLocaleListChanged_playersAreAddedBack() {
-        context.resources.configuration.locales = LocaleList(Locale.US, Locale.UK, Locale.CANADA)
+        context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK, Locale.CANADA))
         testConfigurationChange(configListener.value::onLocaleListChanged)
 
         verify(pageIndicator, never()).tintList =
             ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
 
-        context.resources.configuration.locales = LocaleList(Locale.UK, Locale.US, Locale.CANADA)
+        context.resources.configuration.setLocales(LocaleList(Locale.UK, Locale.US, Locale.CANADA))
         testConfigurationChange(configListener.value::onLocaleListChanged)
 
         verify(pageIndicator).tintList =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index f902be3..9a90a5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -216,9 +216,6 @@
     @Mock private lateinit var recCardTitle: TextView
     @Mock private lateinit var coverItem: ImageView
     @Mock private lateinit var matrix: Matrix
-    private lateinit var coverItem1: ImageView
-    private lateinit var coverItem2: ImageView
-    private lateinit var coverItem3: ImageView
     private lateinit var recTitle1: TextView
     private lateinit var recTitle2: TextView
     private lateinit var recTitle3: TextView
@@ -233,8 +230,6 @@
         FakeFeatureFlags().apply {
             this.set(Flags.UMO_SURFACE_RIPPLE, false)
             this.set(Flags.UMO_TURBULENCE_NOISE, false)
-            this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true)
-            this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
         }
     @Mock private lateinit var globalSettings: GlobalSettings
 
@@ -468,21 +463,25 @@
         recSubtitle3 = TextView(context)
 
         whenever(recommendationViewHolder.recommendations).thenReturn(view)
-        whenever(recommendationViewHolder.cardIcon).thenReturn(appIcon)
-
-        // Add a recommendation item
-        coverItem1 = ImageView(context).also { it.setId(R.id.media_cover1) }
-        coverItem2 = ImageView(context).also { it.setId(R.id.media_cover2) }
-        coverItem3 = ImageView(context).also { it.setId(R.id.media_cover3) }
-
+        whenever(recommendationViewHolder.mediaAppIcons)
+            .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
+        whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
         whenever(recommendationViewHolder.mediaCoverItems)
-            .thenReturn(listOf(coverItem1, coverItem2, coverItem3))
+            .thenReturn(listOf(coverItem, coverItem, coverItem))
         whenever(recommendationViewHolder.mediaCoverContainers)
             .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
         whenever(recommendationViewHolder.mediaTitles)
             .thenReturn(listOf(recTitle1, recTitle2, recTitle3))
         whenever(recommendationViewHolder.mediaSubtitles)
             .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
+        whenever(recommendationViewHolder.mediaProgressBars)
+            .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
+        whenever(coverItem.imageMatrix).thenReturn(matrix)
+
+        // set ids for recommendation containers
+        whenever(coverContainer1.id).thenReturn(1)
+        whenever(coverContainer2.id).thenReturn(2)
+        whenever(coverContainer3.id).thenReturn(3)
 
         whenever(recommendationViewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
 
@@ -1562,7 +1561,8 @@
         verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
         val description = descriptionCaptor.value.toString()
 
-        assertThat(description).contains(REC_APP_NAME)
+        assertThat(description)
+            .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header))
     }
 
     @Test
@@ -1586,7 +1586,8 @@
         verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
         val description = descriptionCaptor.value.toString()
 
-        assertThat(description).contains(REC_APP_NAME)
+        assertThat(description)
+            .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header))
     }
 
     @Test
@@ -1793,7 +1794,7 @@
         // THEN it sends the PendingIntent without dismissing keyguard first,
         // and does not use the Intent directly (see b/271845008)
         captor.value.onClick(viewHolder.player)
-        verify(pendingIntent).send()
+        verify(pendingIntent).send(any(Bundle::class.java))
         verify(pendingIntent, never()).getIntent()
         verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any())
     }
@@ -2152,7 +2153,6 @@
 
     @Test
     fun bindRecommendation_setAfterExecutors() {
-        setupUpdatedRecommendationViewHolder()
         val albumArt = getColorIcon(Color.RED)
         val data =
             smartspaceData.copy(
@@ -2190,7 +2190,6 @@
     @Test
     fun bindRecommendationWithProgressBars() {
         useRealConstraintSets()
-        setupUpdatedRecommendationViewHolder()
         val albumArt = getColorIcon(Color.RED)
         val bundle =
             Bundle().apply {
@@ -2237,7 +2236,6 @@
     @Test
     fun bindRecommendation_carouselNotFitThreeRecs_OrientationPortrait() {
         useRealConstraintSets()
-        setupUpdatedRecommendationViewHolder()
         val albumArt = getColorIcon(Color.RED)
         val data =
             smartspaceData.copy(
@@ -2291,7 +2289,6 @@
     @Test
     fun bindRecommendation_carouselNotFitThreeRecs_OrientationLandscape() {
         useRealConstraintSets()
-        setupUpdatedRecommendationViewHolder()
         val albumArt = getColorIcon(Color.RED)
         val data =
             smartspaceData.copy(
@@ -2506,27 +2503,6 @@
         verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent))
     }
 
-    private fun setupUpdatedRecommendationViewHolder() {
-        fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
-        whenever(recommendationViewHolder.mediaAppIcons)
-            .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
-        whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
-        whenever(recommendationViewHolder.mediaCoverContainers)
-            .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
-        whenever(recommendationViewHolder.mediaCoverItems)
-            .thenReturn(listOf(coverItem, coverItem, coverItem))
-        whenever(recommendationViewHolder.mediaProgressBars)
-            .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
-        whenever(recommendationViewHolder.mediaSubtitles)
-            .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
-        whenever(coverItem.imageMatrix).thenReturn(matrix)
-
-        // set ids for recommendation containers
-        whenever(coverContainer1.id).thenReturn(1)
-        whenever(coverContainer2.id).thenReturn(2)
-        whenever(coverContainer3.id).thenReturn(3)
-    }
-
     private fun getColorIcon(color: Int): Icon {
         val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
         val canvas = Canvas(bmp)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index c9956f3..ba97df9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -201,8 +201,8 @@
         whenever(mockCopiedState.widgetStates)
             .thenReturn(
                 mutableMapOf(
-                    R.id.media_title1 to mediaTitleWidgetState,
-                    R.id.media_subtitle1 to mediaSubTitleWidgetState,
+                    R.id.media_title to mediaTitleWidgetState,
+                    R.id.media_subtitle to mediaSubTitleWidgetState,
                     R.id.media_cover1_container to mediaContainerWidgetState
                 )
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index f79c53d..db00e09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -63,7 +63,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -124,7 +123,7 @@
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
                 mMediaOutputController);
@@ -279,6 +278,21 @@
     }
 
     @Test
+    public void
+            whenNotBroadcasting_verifyLeBroadcastServiceCallBackIsUnregisteredIfProfileEnabled() {
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+                mLocalBluetoothLeBroadcast);
+        mIsBroadcasting = true;
+
+        mMediaOutputBaseDialogImpl.start();
+        verify(mLocalBluetoothLeBroadcast).registerServiceCallBack(any(), any());
+
+        mIsBroadcasting = false;
+        mMediaOutputBaseDialogImpl.stop();
+        verify(mLocalBluetoothLeBroadcast).unregisterServiceCallBack(any());
+    }
+
+    @Test
     public void refresh_checkStopText() {
         mStopText = "test_string";
         mMediaOutputBaseDialogImpl.refresh();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index f8971fd..f25454c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -70,7 +70,6 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -99,7 +98,8 @@
     private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
     private final LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
     private final MediaDevice mBluetoothMediaDevice = mock(BluetoothMediaDevice.class);
-    private final BluetoothDevice mBluetoothDevice = mock(BluetoothDevice.class);
+    private final BluetoothDevice mBluetoothFirstDevice = mock(BluetoothDevice.class);
+    private final BluetoothDevice mBluetoothSecondDevice = mock(BluetoothDevice.class);
     private final CachedBluetoothDevice mCachedBluetoothDevice = mock(CachedBluetoothDevice.class);
     private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
@@ -126,7 +126,7 @@
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
@@ -143,20 +143,20 @@
     }
 
     @Test
-    public void connectBroadcastWithActiveDevice_noBroadcastMetadata_failToAddSource() {
+    public void startBroadcastWithConnectedDevices_noBroadcastMetadata_failToAddSource() {
         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
                 mLocalBluetoothLeBroadcast);
         when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(null);
         when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
                 mLocalBluetoothLeBroadcastAssistant);
 
-        mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+        mMediaOutputBroadcastDialog.startBroadcastWithConnectedDevices();
 
         verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
     }
 
     @Test
-    public void connectBroadcastWithActiveDevice_noConnectedMediaDevice_failToAddSource() {
+    public void startBroadcastWithConnectedDevices_noConnectedMediaDevice_failToAddSource() {
         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
                 mLocalBluetoothLeBroadcast);
         when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
@@ -165,13 +165,13 @@
                 mLocalBluetoothLeBroadcastAssistant);
         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(null);
 
-        mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+        mMediaOutputBroadcastDialog.startBroadcastWithConnectedDevices();
 
         verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
     }
 
     @Test
-    public void connectBroadcastWithActiveDevice_hasBroadcastSource_failToAddSource() {
+    public void startBroadcastWithConnectedDevices_hasBroadcastSource_failToAddSource() {
         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
                 mLocalBluetoothLeBroadcast);
         when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
@@ -181,19 +181,19 @@
         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mBluetoothMediaDevice);
         when(((BluetoothMediaDevice) mBluetoothMediaDevice).getCachedDevice())
                 .thenReturn(mCachedBluetoothDevice);
-        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothFirstDevice);
         List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
         sourceList.add(mBluetoothLeBroadcastReceiveState);
-        when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothDevice)).thenReturn(
+        when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothFirstDevice)).thenReturn(
                 sourceList);
 
-        mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+        mMediaOutputBroadcastDialog.startBroadcastWithConnectedDevices();
 
         verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
     }
 
     @Test
-    public void connectBroadcastWithActiveDevice_noBroadcastSource_failToAddSource() {
+    public void startBroadcastWithConnectedDevices_noBroadcastSource_failToAddSource() {
         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
                 mLocalBluetoothLeBroadcast);
         when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
@@ -204,12 +204,16 @@
         when(mBluetoothMediaDevice.isBLEDevice()).thenReturn(true);
         when(((BluetoothMediaDevice) mBluetoothMediaDevice).getCachedDevice()).thenReturn(
                 mCachedBluetoothDevice);
-        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothFirstDevice);
         List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
-        when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothDevice)).thenReturn(
+        when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothFirstDevice)).thenReturn(
                 sourceList);
+        List<BluetoothDevice> connectedDevicesList = new ArrayList<>();
+        connectedDevicesList.add(mBluetoothFirstDevice);
+        when(mLocalBluetoothLeBroadcastAssistant.getConnectedDevices()).thenReturn(
+                connectedDevicesList);
 
-        mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+        mMediaOutputBroadcastDialog.startBroadcastWithConnectedDevices();
 
         verify(mLocalBluetoothLeBroadcastAssistant, times(1)).addSource(any(), any(), anyBoolean());
     }
@@ -361,4 +365,32 @@
 
         assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
     }
+
+    @Test
+    public void addSourceToAllConnectedDevices() {
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+                mLocalBluetoothLeBroadcast);
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
+                mLocalBluetoothLeBroadcastAssistant);
+        when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
+                mBluetoothLeBroadcastMetadata);
+        when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mBluetoothMediaDevice);
+        when(mBluetoothMediaDevice.isBLEDevice()).thenReturn(true);
+        when(((BluetoothMediaDevice) mBluetoothMediaDevice).getCachedDevice())
+                .thenReturn(mCachedBluetoothDevice);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothFirstDevice);
+        List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+        when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothFirstDevice)).thenReturn(
+                sourceList);
+        List<BluetoothDevice> connectedDevicesList = new ArrayList<>();
+        connectedDevicesList.add(mBluetoothFirstDevice);
+        connectedDevicesList.add(mBluetoothSecondDevice);
+        when(mLocalBluetoothLeBroadcastAssistant.getConnectedDevices()).thenReturn(
+                connectedDevicesList);
+
+        mMediaOutputBroadcastDialog.startBroadcastWithConnectedDevices();
+
+        verify(mLocalBluetoothLeBroadcastAssistant, times(2)).addSource(any(), any(), anyBoolean());
+    }
+
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 9f06b5f..a59ea20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -93,7 +93,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -197,7 +196,7 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
         when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
@@ -279,7 +278,7 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         mMediaOutputController.start(mCb);
@@ -309,7 +308,7 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         mMediaOutputController.start(mCb);
@@ -530,7 +529,7 @@
                 "",
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         testMediaOutputController.start(mCb);
         reset(mCb);
@@ -553,7 +552,7 @@
                 "",
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         testMediaOutputController.start(mCb);
         reset(mCb);
@@ -589,7 +588,7 @@
                 null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
@@ -606,7 +605,7 @@
                 null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
@@ -888,7 +887,7 @@
         mMediaOutputController = new MediaOutputController(mSpyContext, null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         assertThat(mMediaOutputController.getNotificationIcon()).isNull();
@@ -1080,7 +1079,7 @@
                 null,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
 
         testMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index a14ff2f..bfc8c83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -67,7 +67,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 import java.util.function.Consumer;
 
 @MediumTest
@@ -132,7 +131,7 @@
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
-                Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+                mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags, mUserTracker);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputDialog = makeTestDialog(mMediaOutputController);
@@ -257,6 +256,30 @@
     }
 
     @Test
+    public void isBroadcastSupported_noBleDeviceAndEnabledBroadcast_returnsTrue() {
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+                mLocalBluetoothLeBroadcast);
+        when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(true);
+        FeatureFlagUtils.setEnabled(mContext,
+                FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
+        when(mMediaDevice.isBLEDevice()).thenReturn(false);
+
+        assertThat(mMediaOutputDialog.isBroadcastSupported()).isTrue();
+    }
+
+    @Test
+    public void isBroadcastSupported_noBleDeviceAndDisabledBroadcast_returnsFalse() {
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+                mLocalBluetoothLeBroadcast);
+        when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(false);
+        FeatureFlagUtils.setEnabled(mContext,
+                FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
+        when(mMediaDevice.isBLEDevice()).thenReturn(false);
+
+        assertThat(mMediaOutputDialog.isBroadcastSupported()).isFalse();
+    }
+
+    @Test
     public void getBroadcastIconVisibility_isBroadcasting_returnVisible() {
         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
                 mLocalBluetoothLeBroadcast);
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
index ea25f71..c7a74da 100644
--- 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
@@ -40,6 +40,8 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.common.shared.model.Text.Companion.loadText
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.CommandQueue
@@ -111,6 +113,7 @@
     private lateinit var uiEventLogger: MediaTttSenderUiEventLogger
     private lateinit var tempViewUiEventLogger: TemporaryViewUiEventLogger
     private val defaultTimeout = context.resources.getInteger(R.integer.heads_up_notification_decay)
+    private val featureFlags = FakeFeatureFlags()
 
     @Before
     fun setUp() {
@@ -160,7 +163,9 @@
                 fakeWakeLockBuilder,
                 fakeClock,
                 tempViewUiEventLogger,
+                featureFlags
             )
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
         chipbarCoordinator.start()
 
         underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
index 01ffdcd..906420d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
@@ -26,6 +26,7 @@
 import android.view.WindowManager
 import android.view.WindowMetrics
 import androidx.core.view.WindowInsetsCompat.Type
+import androidx.lifecycle.LifecycleOwner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
@@ -41,9 +42,10 @@
 @SmallTest
 class TaskPreviewSizeProviderTest : SysuiTestCase() {
 
-    private val mockContext: Context = mock()
-    private val resources: Resources = mock()
-    private val windowManager: WindowManager = mock()
+    private val lifecycleOwner = mock<LifecycleOwner>()
+    private val mockContext = mock<Context>()
+    private val resources = mock<Resources>()
+    private val windowManager = mock<WindowManager>()
     private val sizeUpdates = arrayListOf<Rect>()
     private val testConfigurationController = FakeConfigurationController()
 
@@ -76,7 +78,7 @@
     @Test
     fun size_phoneDisplayAndRotate_emitsSizeUpdate() {
         givenDisplay(width = 400, height = 600, isTablet = false)
-        createSizeProvider()
+        createSizeProvider().also { it.onCreate(lifecycleOwner) }
 
         givenDisplay(width = 600, height = 400, isTablet = false)
         testConfigurationController.onConfigurationChanged(Configuration())
@@ -87,7 +89,7 @@
     @Test
     fun size_phoneDisplayAndRotateConfigurationChange_returnsUpdatedSize() {
         givenDisplay(width = 400, height = 600, isTablet = false)
-        val sizeProvider = createSizeProvider()
+        val sizeProvider = createSizeProvider().also { it.onCreate(lifecycleOwner) }
 
         givenDisplay(width = 600, height = 400, isTablet = false)
         testConfigurationController.onConfigurationChanged(Configuration())
@@ -95,6 +97,20 @@
         assertThat(sizeProvider.size).isEqualTo(Rect(0, 0, 150, 100))
     }
 
+    @Test
+    fun size_phoneDisplayAndRotateConfigurationChange_afterChooserDestroyed_doesNotUpdate() {
+        givenDisplay(width = 400, height = 600, isTablet = false)
+        val sizeProvider = createSizeProvider()
+        val previousSize = Rect(sizeProvider.size)
+
+        sizeProvider.onCreate(lifecycleOwner)
+        sizeProvider.onDestroy(lifecycleOwner)
+        givenDisplay(width = 600, height = 400, isTablet = false)
+        testConfigurationController.onConfigurationChanged(Configuration())
+
+        assertThat(sizeProvider.size).isEqualTo(previousSize)
+    }
+
     private fun givenTaskbarSize(size: Int) {
         val windowInsets =
             WindowInsets.Builder()
@@ -107,7 +123,7 @@
 
     private fun givenDisplay(width: Int, height: Int, isTablet: Boolean = false) {
         val bounds = Rect(0, 0, width, height)
-        val windowMetrics = WindowMetrics(bounds, null)
+        val windowMetrics = WindowMetrics(bounds, { null }, 1.0f)
         whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics)
         whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
new file mode 100644
index 0000000..bcbf666
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
+import com.android.systemui.util.mockito.whenever
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() {
+
+    @Mock private lateinit var flags: FeatureFlags
+    @Mock private lateinit var coordinator: TaskSwitcherNotificationCoordinator
+
+    private lateinit var coreStartable: MediaProjectionTaskSwitcherCoreStartable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        coreStartable = MediaProjectionTaskSwitcherCoreStartable(coordinator, flags)
+    }
+
+    @Test
+    fun start_flagEnabled_startsCoordinator() {
+        whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(true)
+
+        coreStartable.start()
+
+        verify(coordinator).start()
+    }
+
+    @Test
+    fun start_flagDisabled_doesNotStartCoordinator() {
+        whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(false)
+
+        coreStartable.start()
+
+        verifyZeroInteractions(coordinator)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
new file mode 100644
index 0000000..83932b0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.os.Binder
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() {
+
+    private val fakeActivityTaskManager = FakeActivityTaskManager()
+
+    private val dispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+
+    private val repo =
+        ActivityTaskManagerTasksRepository(
+            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+            applicationScope = testScope.backgroundScope,
+            backgroundDispatcher = dispatcher
+        )
+
+    @Test
+    fun findRunningTaskFromWindowContainerToken_noMatch_returnsNull() {
+        fakeActivityTaskManager.addRunningTasks(createTask(taskId = 1), createTask(taskId = 2))
+
+        testScope.runTest {
+            val matchingTask =
+                repo.findRunningTaskFromWindowContainerToken(windowContainerToken = Binder())
+
+            assertThat(matchingTask).isNull()
+        }
+    }
+
+    @Test
+    fun findRunningTaskFromWindowContainerToken_matchingToken_returnsTaskInfo() {
+        val expectedToken = createToken()
+        val expectedTask = createTask(taskId = 1, token = expectedToken)
+
+        fakeActivityTaskManager.addRunningTasks(
+            createTask(taskId = 2),
+            expectedTask,
+        )
+
+        testScope.runTest {
+            val actualTask =
+                repo.findRunningTaskFromWindowContainerToken(
+                    windowContainerToken = expectedToken.asBinder()
+                )
+
+            assertThat(actualTask).isEqualTo(expectedTask)
+        }
+    }
+
+    @Test
+    fun foregroundTask_returnsStreamOfTasksMovedToFront() =
+        testScope.runTest {
+            val foregroundTask by collectLastValue(repo.foregroundTask)
+
+            fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+            assertThat(foregroundTask?.taskId).isEqualTo(1)
+
+            fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 2))
+            assertThat(foregroundTask?.taskId).isEqualTo(2)
+
+            fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 3))
+            assertThat(foregroundTask?.taskId).isEqualTo(3)
+        }
+
+    @Test
+    fun foregroundTask_lastValueIsCached() =
+        testScope.runTest {
+            val foregroundTaskA by collectLastValue(repo.foregroundTask)
+            fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+            assertThat(foregroundTaskA?.taskId).isEqualTo(1)
+
+            val foregroundTaskB by collectLastValue(repo.foregroundTask)
+            assertThat(foregroundTaskB?.taskId).isEqualTo(1)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
new file mode 100644
index 0000000..1c4870b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager
+import android.app.TaskStackListener
+import android.content.Intent
+import android.window.IWindowContainerToken
+import android.window.WindowContainerToken
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakeActivityTaskManager {
+
+    private val runningTasks = mutableListOf<RunningTaskInfo>()
+    private val taskTaskListeners = mutableListOf<TaskStackListener>()
+
+    val activityTaskManager = mock<ActivityTaskManager>()
+
+    init {
+        whenever(activityTaskManager.registerTaskStackListener(any())).thenAnswer {
+            taskTaskListeners += it.arguments[0] as TaskStackListener
+            return@thenAnswer Unit
+        }
+        whenever(activityTaskManager.unregisterTaskStackListener(any())).thenAnswer {
+            taskTaskListeners -= it.arguments[0] as TaskStackListener
+            return@thenAnswer Unit
+        }
+        whenever(activityTaskManager.getTasks(any())).thenAnswer {
+            val maxNumTasks = it.arguments[0] as Int
+            return@thenAnswer runningTasks.take(maxNumTasks)
+        }
+    }
+
+    fun moveTaskToForeground(task: RunningTaskInfo) {
+        taskTaskListeners.forEach { it.onTaskMovedToFront(task) }
+    }
+
+    fun addRunningTasks(vararg tasks: RunningTaskInfo) {
+        runningTasks += tasks
+    }
+
+    companion object {
+
+        fun createTask(
+            taskId: Int,
+            token: WindowContainerToken = createToken(),
+            baseIntent: Intent = Intent()
+        ) =
+            RunningTaskInfo().apply {
+                this.taskId = taskId
+                this.token = token
+                this.baseIntent = baseIntent
+            }
+
+        fun createToken(): WindowContainerToken {
+            val realToken = object : IWindowContainerToken.Stub() {}
+            return WindowContainerToken(realToken)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
new file mode 100644
index 0000000..45f0a8c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Binder
+import android.os.IBinder
+import android.os.UserHandle
+import android.view.ContentRecordingSession
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakeMediaProjectionManager {
+
+    val mediaProjectionManager = mock<MediaProjectionManager>()
+
+    private val callbacks = mutableListOf<MediaProjectionManager.Callback>()
+
+    init {
+        whenever(mediaProjectionManager.addCallback(any(), any())).thenAnswer {
+            callbacks += it.arguments[0] as MediaProjectionManager.Callback
+            return@thenAnswer Unit
+        }
+        whenever(mediaProjectionManager.removeCallback(any())).thenAnswer {
+            callbacks -= it.arguments[0] as MediaProjectionManager.Callback
+            return@thenAnswer Unit
+        }
+    }
+
+    fun dispatchOnStart(info: MediaProjectionInfo = DEFAULT_INFO) {
+        callbacks.forEach { it.onStart(info) }
+    }
+
+    fun dispatchOnStop(info: MediaProjectionInfo = DEFAULT_INFO) {
+        callbacks.forEach { it.onStop(info) }
+    }
+
+    fun dispatchOnSessionSet(
+        info: MediaProjectionInfo = DEFAULT_INFO,
+        session: ContentRecordingSession?
+    ) {
+        callbacks.forEach { it.onRecordingSessionSet(info, session) }
+    }
+
+    companion object {
+        fun createDisplaySession(): ContentRecordingSession =
+            ContentRecordingSession.createDisplaySession(/* displayToMirror = */ 123)
+        fun createSingleTaskSession(token: IBinder = Binder()): ContentRecordingSession =
+            ContentRecordingSession.createTaskSession(token)
+
+        private const val DEFAULT_PACKAGE_NAME = "com.media.projection.test"
+        private val DEFAULT_USER_HANDLE = UserHandle.getUserHandleForUid(UserHandle.myUserId())
+        private val DEFAULT_INFO = MediaProjectionInfo(DEFAULT_PACKAGE_NAME, DEFAULT_USER_HANDLE)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
new file mode 100644
index 0000000..7bd97ce
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.os.Binder
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.view.ContentRecordingSession
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
+
+    private val dispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+
+    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+    private val fakeActivityTaskManager = FakeActivityTaskManager()
+
+    private val tasksRepo =
+        ActivityTaskManagerTasksRepository(
+            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+            applicationScope = testScope.backgroundScope,
+            backgroundDispatcher = dispatcher
+        )
+
+    private val repo =
+        MediaProjectionManagerRepository(
+            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+            handler = Handler.getMain(),
+            applicationScope = testScope.backgroundScope,
+            tasksRepository = tasksRepo
+        )
+
+    @Test
+    fun mediaProjectionState_onStart_emitsNotProjecting() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            fakeMediaProjectionManager.dispatchOnStart()
+
+            assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+        }
+
+    @Test
+    fun mediaProjectionState_onStop_emitsNotProjecting() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            fakeMediaProjectionManager.dispatchOnStop()
+
+            assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+        }
+
+    @Test
+    fun mediaProjectionState_onSessionSet_sessionNull_emitsNotProjecting() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            fakeMediaProjectionManager.dispatchOnSessionSet(session = null)
+
+            assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+        }
+
+    @Test
+    fun mediaProjectionState_onSessionSet_contentToRecordDisplay_emitsEntireScreen() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
+            )
+
+            assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+        }
+
+    @Test
+    fun mediaProjectionState_sessionSet_taskWithToken_noMatchingRunningTask_emitsEntireScreen() =
+        testScope.runTest {
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            val taskWindowContainerToken = Binder()
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = ContentRecordingSession.createTaskSession(taskWindowContainerToken)
+            )
+
+            assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
+        }
+
+    @Test
+    fun mediaProjectionState_sessionSet_taskWithToken_matchingRunningTask_emitsSingleTask() =
+        testScope.runTest {
+            val token = createToken()
+            val task = createTask(taskId = 1, token = token)
+            fakeActivityTaskManager.addRunningTasks(task)
+            val state by collectLastValue(repo.mediaProjectionState)
+            runCurrent()
+
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = ContentRecordingSession.createTaskSession(token.asBinder())
+            )
+
+            assertThat(state).isEqualTo(MediaProjectionState.SingleTask(task))
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
new file mode 100644
index 0000000..b2ebe1bc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.domain.interactor
+
+import android.content.Intent
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TaskSwitchInteractorTest : SysuiTestCase() {
+
+    private val dispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+
+    private val fakeActivityTaskManager = FakeActivityTaskManager()
+    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+
+    private val tasksRepo =
+        ActivityTaskManagerTasksRepository(
+            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+            applicationScope = testScope.backgroundScope,
+            backgroundDispatcher = dispatcher
+        )
+
+    private val mediaRepo =
+        MediaProjectionManagerRepository(
+            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+            handler = Handler.getMain(),
+            applicationScope = testScope.backgroundScope,
+            tasksRepository = tasksRepo,
+        )
+
+    private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+
+    @Test
+    fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() =
+        testScope.runTest {
+            val backgroundTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1)
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnStop()
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
+        }
+
+    @Test
+    fun taskSwitchChanges_projectingScreen_foregroundTaskChange_emitsNotProjectingTask() =
+        testScope.runTest {
+            val backgroundTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1)
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = FakeMediaProjectionManager.createDisplaySession()
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
+        }
+
+    @Test
+    fun taskSwitchChanges_projectingTask_foregroundTaskDifferent_emitsTaskChanged() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1)
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(token = projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(taskSwitchState)
+                .isEqualTo(
+                    TaskSwitchState.TaskSwitched(
+                        projectedTask = projectedTask,
+                        foregroundTask = foregroundTask
+                    )
+                )
+        }
+
+    @Test
+    fun taskSwitchChanges_projectingTask_foregroundTaskLauncher_emitsTaskUnchanged() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1, baseIntent = LAUNCHER_INTENT)
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
+        }
+
+    @Test
+    fun taskSwitchChanges_projectingTask_foregroundTaskSame_emitsTaskUnchanged() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 0)
+            val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(projectedTask)
+
+            assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
+        }
+
+    companion object {
+        private val LAUNCHER_INTENT: Intent =
+            Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
new file mode 100644
index 0000000..b396caf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.ui
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() {
+
+    private val notificationManager: NotificationManager = mock()
+
+    private val dispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+
+    private val fakeActivityTaskManager = FakeActivityTaskManager()
+    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+
+    private val tasksRepo =
+        ActivityTaskManagerTasksRepository(
+            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+            applicationScope = testScope.backgroundScope,
+            backgroundDispatcher = dispatcher
+        )
+
+    private val mediaRepo =
+        MediaProjectionManagerRepository(
+            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+            handler = Handler.getMain(),
+            applicationScope = testScope.backgroundScope,
+            tasksRepository = tasksRepo,
+        )
+
+    private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+    private val viewModel = TaskSwitcherNotificationViewModel(interactor)
+
+    private val coordinator =
+        TaskSwitcherNotificationCoordinator(
+            context,
+            notificationManager,
+            testScope.backgroundScope,
+            dispatcher,
+            viewModel
+        )
+
+    @Before
+    fun setup() {
+        coordinator.start()
+    }
+
+    @Test
+    fun showNotification() {
+        testScope.runTest {
+            switchTask()
+
+            val notification = ArgumentCaptor.forClass(Notification::class.java)
+            verify(notificationManager).notify(any(), any(), notification.capture())
+            assertNotification(notification)
+        }
+    }
+
+    @Test
+    fun hideNotification() {
+        testScope.runTest {
+            fakeMediaProjectionManager.dispatchOnStop()
+
+            verify(notificationManager).cancel(any())
+        }
+    }
+
+    @Test
+    fun notificationIdIsConsistent() {
+        testScope.runTest {
+            fakeMediaProjectionManager.dispatchOnStop()
+            val idCancel = argumentCaptor<Int>()
+            verify(notificationManager).cancel(idCancel.capture())
+
+            switchTask()
+            val idNotify = argumentCaptor<Int>()
+            verify(notificationManager).notify(any(), idNotify.capture(), any())
+
+            assertEquals(idCancel.value, idNotify.value)
+        }
+    }
+
+    private fun switchTask() {
+        val projectedTask = FakeActivityTaskManager.createTask(taskId = 1)
+        val foregroundTask = FakeActivityTaskManager.createTask(taskId = 2)
+        fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+        fakeMediaProjectionManager.dispatchOnSessionSet(
+            session =
+                FakeMediaProjectionManager.createSingleTaskSession(projectedTask.token.asBinder())
+        )
+        fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+    }
+
+    private fun assertNotification(notification: ArgumentCaptor<Notification>) {
+        val text = notification.value.extras.getCharSequence(Notification.EXTRA_TEXT)
+        assertEquals(context.getString(R.string.media_projection_task_switcher_text), text)
+
+        val actions = notification.value.actions
+        assertThat(actions).hasLength(2)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
new file mode 100644
index 0000000..7d38de4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel
+
+import android.content.Intent
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createDisplaySession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
+
+    private val dispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+
+    private val fakeActivityTaskManager = FakeActivityTaskManager()
+    private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+
+    private val tasksRepo =
+        ActivityTaskManagerTasksRepository(
+            activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+            applicationScope = testScope.backgroundScope,
+            backgroundDispatcher = dispatcher
+        )
+
+    private val mediaRepo =
+        MediaProjectionManagerRepository(
+            mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+            handler = Handler.getMain(),
+            applicationScope = testScope.backgroundScope,
+            tasksRepository = tasksRepo,
+        )
+
+    private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+
+    private val viewModel = TaskSwitcherNotificationViewModel(interactor)
+
+    @Test
+    fun uiState_notProjecting_emitsNotShowing() =
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeMediaProjectionManager.dispatchOnStop()
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_notProjecting_foregroundTaskChanged_emitsNotShowing() =
+        testScope.runTest {
+            val backgroundTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnStop()
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_projectingEntireScreen_emitsNotShowing() =
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeMediaProjectionManager.dispatchOnSessionSet(session = createDisplaySession())
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_projectingEntireScreen_foregroundTaskChanged_emitsNotShowing() =
+        testScope.runTest {
+            val backgroundTask = createTask(taskId = 0)
+            val foregroundTask = createTask(taskId = 1)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(session = createDisplaySession())
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_projectingTask_foregroundTaskChanged_different_emitsShowing() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 1)
+            val foregroundTask = createTask(taskId = 2)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(uiState)
+                .isEqualTo(TaskSwitcherNotificationUiState.Showing(projectedTask, foregroundTask))
+        }
+
+    @Test
+    fun uiState_projectingTask_foregroundTaskChanged_same_emitsNotShowing() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 1)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(projectedTask)
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    @Test
+    fun uiState_projectingTask_foregroundTaskChanged_different_taskIsLauncher_emitsNotShowing() =
+        testScope.runTest {
+            val projectedTask = createTask(taskId = 1)
+            val foregroundTask = createTask(taskId = 2, baseIntent = LAUNCHER_INTENT)
+            val uiState by collectLastValue(viewModel.uiState)
+
+            fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+            fakeMediaProjectionManager.dispatchOnSessionSet(
+                session = createSingleTaskSession(projectedTask.token.asBinder())
+            )
+            fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+            assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+        }
+
+    companion object {
+        private val LAUNCHER_INTENT: Intent =
+            Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
new file mode 100644
index 0000000..f5a70f0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.model
+
+import android.view.Display
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeDisplayTracker
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SysUiStateExtTest : SysuiTestCase() {
+
+    private val underTest = SysUiState(FakeDisplayTracker(context))
+
+    @Test
+    fun updateFlags() {
+        underTest.updateFlags(
+            Display.DEFAULT_DISPLAY,
+            1 to true,
+            2 to false,
+            3 to true,
+        )
+
+        assertThat(underTest.flags and 1).isNotEqualTo(0)
+        assertThat(underTest.flags and 2).isEqualTo(0)
+        assertThat(underTest.flags and 3).isNotEqualTo(0)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/BlendTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/BlendTest.java
deleted file mode 100644
index 5b3419d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/BlendTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.blend.Blend;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public final class BlendTest extends SysuiTestCase {
-    private static final int RED = 0xffff0000;
-    private static final int BLUE = 0xff0000ff;
-    private static final int GREEN = 0xff00ff00;
-    private static final int YELLOW = 0xffffff00;
-
-    @Test
-    public void harmonize_redToBlue() {
-        int blended = Blend.harmonize(RED, BLUE);
-        assertThat(blended).isSameColorAs(0xffFB0057);
-    }
-
-    @Test
-    public void harmonize_redToGreen() {
-        int answer = Blend.harmonize(RED, GREEN);
-        assertThat(answer).isSameColorAs(0xffD85600);
-    }
-
-    @Test
-    public void harmonize_redToYellow() {
-        int answer = Blend.harmonize(RED, YELLOW);
-        assertThat(answer).isSameColorAs(0xffD85600);
-    }
-
-    @Test
-    public void harmonize_blueToGreen() {
-        int answer = Blend.harmonize(BLUE, GREEN);
-        assertThat(answer).isSameColorAs(0xff0047A3);
-    }
-
-    @Test
-    public void harmonize_blueToRed() {
-        int answer = Blend.harmonize(BLUE, RED);
-        assertThat(answer).isSameColorAs(0xff5700DC);
-    }
-
-    @Test
-    public void harmonize_blueToYellow() {
-        int answer = Blend.harmonize(BLUE, YELLOW);
-        assertThat(answer).isSameColorAs(0xff0047A3);
-    }
-
-    @Test
-    public void harmonize_greenToBlue() {
-        int answer = Blend.harmonize(GREEN, BLUE);
-        assertThat(answer).isSameColorAs(0xff00FC94);
-    }
-
-    @Test
-    public void harmonize_greenToRed() {
-        int answer = Blend.harmonize(GREEN, RED);
-        assertThat(answer).isSameColorAs(0xffB1F000);
-    }
-
-    @Test
-    public void harmonize_greenToYellow() {
-        int answer = Blend.harmonize(GREEN, YELLOW);
-        assertThat(answer).isSameColorAs(0xffB1F000);
-    }
-
-    @Test
-    public void harmonize_yellowToBlue() {
-        int answer = Blend.harmonize(YELLOW, BLUE);
-        assertThat(answer).isSameColorAs(0xffEBFFBA);
-    }
-
-    @Test
-    public void harmonize_yellowToGreen() {
-        int answer = Blend.harmonize(YELLOW, GREEN);
-        assertThat(answer).isSameColorAs(0xffEBFFBA);
-    }
-
-    @Test
-    public void harmonize_yellowToRed() {
-        int answer = Blend.harmonize(YELLOW, RED);
-        assertThat(answer).isSameColorAs(0xffFFF6E3);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
deleted file mode 100644
index 1a35502..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
+++ /dev/null
@@ -1,210 +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.monet;
-
-import android.app.WallpaperColors;
-import android.graphics.Color;
-import android.testing.AndroidTestingRunner;
-import android.util.Log;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.graphics.cam.Cam;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class ColorSchemeTest extends SysuiTestCase {
-    @Test
-    public void testFilterTransparency() {
-        ColorScheme colorScheme = new ColorScheme(Color.TRANSPARENT, false /* darkTheme */);
-        Assert.assertEquals(colorScheme.getAllAccentColors(),
-                new ColorScheme(0xFF1b6ef3, false).getAllAccentColors());
-    }
-
-    @Test
-    public void testDontFilterOpaque() {
-        ColorScheme colorScheme = new ColorScheme(0xFFFF0000, false /* darkTheme */);
-        Assert.assertNotEquals(colorScheme.getAllAccentColors(),
-                new ColorScheme(0xFF1b6ef3, false).getAllAccentColors());
-    }
-
-    @Test
-    public void testUniqueColors() {
-        WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xffaec00a),
-                Color.valueOf(0xffaec00a), Color.valueOf(0xffaec00a));
-
-        List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors);
-        Assert.assertEquals(rankedSeedColors, List.of(0xffaec00a));
-    }
-
-    @Test
-    public void testStyleApplied() {
-        WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xffaec00a),
-                null, null);
-        // Expressive applies hue rotations to the theme color. The input theme color has hue
-        // 117, ensuring the hue changed significantly is a strong signal styles are being applied.
-        ColorScheme colorScheme = new ColorScheme(wallpaperColors, false, Style.EXPRESSIVE);
-        Assert.assertEquals(357.77, Cam.fromInt(colorScheme.getAccent1().getS500()).getHue(), 0.1);
-    }
-
-
-    @Test
-    public void testFiltersInvalidColors() {
-        WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xff5e7ea2),
-                Color.valueOf(0xff5e7ea2), Color.valueOf(0xff000000));
-
-        List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors);
-        Assert.assertEquals(rankedSeedColors, List.of(0xff5e7ea2));
-    }
-
-    @Test
-    public void testInvalidColorBecomesGBlue() {
-        WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xff000000), null,
-                null);
-
-        List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors);
-        Assert.assertEquals(rankedSeedColors, List.of(0xFF1b6ef3));
-    }
-
-    @Test
-    public void testDontFilterRRGGBB() {
-        ColorScheme colorScheme = new ColorScheme(0xFF0000, false /* darkTheme */);
-        Assert.assertEquals(colorScheme.getAllAccentColors(),
-                new ColorScheme(0xFFFF0000, false).getAllAccentColors());
-    }
-
-    @Test
-    public void testNoPopulationSignal() {
-        WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xffaec00a),
-                Color.valueOf(0xffbe0000), Color.valueOf(0xffcc040f));
-
-        List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors);
-        Assert.assertEquals(rankedSeedColors, List.of(0xffaec00a, 0xffbe0000, 0xffcc040f));
-    }
-
-    @Test
-    public void testTertiaryHueWrapsProperly() {
-        int colorInt = 0xffB3588A; // H350 C50 T50
-        ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */);
-        int tertiaryMid = colorScheme.getAccent3().getAllShades().get(
-                colorScheme.getShadeCount() / 2);
-        Cam cam = Cam.fromInt(tertiaryMid);
-        Assert.assertEquals(cam.getHue(), 50.0, 10.0);
-    }
-
-    @Test
-    public void testSpritz() {
-        int colorInt = 0xffB3588A; // H350 C50 T50
-        ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
-                Style.SPRITZ /* style */);
-        int primaryMid = colorScheme.getAccent1().getAllShades().get(
-                colorScheme.getShadeCount() / 2);
-        Cam cam = Cam.fromInt(primaryMid);
-        Assert.assertEquals(cam.getChroma(), 12.0, 1.0);
-    }
-
-    @Test
-    public void testVibrant() {
-        int colorInt = 0xffB3588A; // H350 C50 T50
-        ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
-                Style.VIBRANT /* style */);
-        int neutralMid = colorScheme.getNeutral1().getAllShades().get(
-                colorScheme.getShadeCount() / 2);
-        Cam cam = Cam.fromInt(neutralMid);
-        Assert.assertTrue("chroma was " + cam.getChroma(), Math.floor(cam.getChroma()) <= 12.0);
-    }
-
-    @Test
-    public void testExpressive() {
-        int colorInt = 0xffB3588A; // H350 C50 T50
-        ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
-                Style.EXPRESSIVE /* style */);
-        int neutralMid = colorScheme.getNeutral1().getAllShades().get(
-                colorScheme.getShadeCount() / 2);
-        Cam cam = Cam.fromInt(neutralMid);
-        Assert.assertTrue(cam.getChroma() <= 8.0);
-    }
-
-    @Test
-    public void testMonochromatic() {
-        int colorInt = 0xffB3588A; // H350 C50 T50
-        ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
-                Style.MONOCHROMATIC /* style */);
-        int neutralMid = colorScheme.getNeutral1().getAllShades().get(
-                colorScheme.getShadeCount() / 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();
-        new ColorScheme(Color.argb(0, 0, 0, 0xf), false /* darkTheme */).toString();
-        new ColorScheme(Color.argb(0xff, 0xff, 0, 0), false /* darkTheme */).toString();
-        new ColorScheme(0xFFFFFFFF, false /* darkTheme */).toString();
-
-        new ColorScheme(Color.TRANSPARENT, true /* darkTheme */).toString();
-        new ColorScheme(Color.argb(0, 0, 0, 0xf), true /* darkTheme */).toString();
-        new ColorScheme(0xFFFF0000, true /* darkTheme */).toString();
-        new ColorScheme(0xFFFFFFFF, true /* darkTheme */).toString();
-    }
-
-    /**
-     * Generate xml for SystemPaletteTest#testThemeStyles().
-     */
-    @Test
-    public void generateThemeStyles() {
-        StringBuilder xml = new StringBuilder();
-        for (int hue = 0; hue < 360; hue += 60) {
-            final int sourceColor = Cam.getInt(hue, 50f, 50f);
-            final String sourceColorHex = Integer.toHexString(sourceColor);
-
-            xml.append("    <theme color=\"").append(sourceColorHex).append("\">\n");
-
-            for (Style style : Style.values()) {
-                String styleName = style.name().toLowerCase();
-                ColorScheme colorScheme = new ColorScheme(sourceColor, false, style);
-                xml.append("        <").append(styleName).append(">");
-
-                List<String> colors = new ArrayList<>();
-
-                colorScheme.getAllHues().forEach(schemeHue -> {
-                    colors.add("ffffff");
-                    schemeHue.getAllShades().forEach(tone -> {
-                        colors.add(Integer.toHexString(tone).substring(2));
-                    });
-                });
-
-                xml.append(String.join(",", colors));
-                xml.append("</").append(styleName).append(">\n");
-            }
-            xml.append("    </theme>\n");
-        }
-        Log.d("ColorSchemeXml", xml.toString());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorUtilsTest.java
deleted file mode 100644
index 6a7f104..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorUtilsTest.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.utils.ColorUtils;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public final class ColorUtilsTest extends SysuiTestCase {
-
-    @Test
-    public void argbFromLab() {
-        assertThat(ColorUtils.argbFromLab(0.0, 0.0, 0.0)).isEqualTo(0xff000000);
-        assertThat(ColorUtils.argbFromLab(0.25, 0.0, 0.0)).isEqualTo(0xff010101);
-        assertThat(ColorUtils.argbFromLab(0.5, 0.0, 0.0)).isEqualTo(0xff020202);
-        assertThat(ColorUtils.argbFromLab(0.75, 0.0, 0.0)).isEqualTo(0xff030303);
-        assertThat(ColorUtils.argbFromLab(1.0, 0.0, 0.0)).isEqualTo(0xff040404);
-        assertThat(ColorUtils.argbFromLab(1.5, 0.0, 0.0)).isEqualTo(0xff050505);
-        assertThat(ColorUtils.argbFromLab(2.0, 0.0, 0.0)).isEqualTo(0xff070707);
-        assertThat(ColorUtils.argbFromLab(3.0, 0.0, 0.0)).isEqualTo(0xff0b0b0b);
-        assertThat(ColorUtils.argbFromLab(4.0, 0.0, 0.0)).isEqualTo(0xff0e0e0e);
-        assertThat(ColorUtils.argbFromLab(5.0, 0.0, 0.0)).isEqualTo(0xff111111);
-        assertThat(ColorUtils.argbFromLab(6.0, 0.0, 0.0)).isEqualTo(0xff131313);
-        assertThat(ColorUtils.argbFromLab(7.0, 0.0, 0.0)).isEqualTo(0xff151515);
-        assertThat(ColorUtils.argbFromLab(8.0, 0.0, 0.0)).isEqualTo(0xff181818);
-        assertThat(ColorUtils.argbFromLab(9.0, 0.0, 0.0)).isEqualTo(0xff191919);
-        assertThat(ColorUtils.argbFromLab(10.0, 0.0, 0.0)).isEqualTo(0xff1b1b1b);
-        assertThat(ColorUtils.argbFromLab(20.0, 0.0, 0.0)).isEqualTo(0xff303030);
-        assertThat(ColorUtils.argbFromLab(30.0, 0.0, 0.0)).isEqualTo(0xff474747);
-        assertThat(ColorUtils.argbFromLab(40.0, 0.0, 0.0)).isEqualTo(0xff5e5e5e);
-        assertThat(ColorUtils.argbFromLab(50.0, 0.0, 0.0)).isEqualTo(0xff777777);
-        assertThat(ColorUtils.argbFromLab(60.0, 0.0, 0.0)).isEqualTo(0xff919191);
-        assertThat(ColorUtils.argbFromLab(70.0, 0.0, 0.0)).isEqualTo(0xffababab);
-        assertThat(ColorUtils.argbFromLab(80.0, 0.0, 0.0)).isEqualTo(0xffc6c6c6);
-        assertThat(ColorUtils.argbFromLab(90.0, 0.0, 0.0)).isEqualTo(0xffe2e2e2);
-        assertThat(ColorUtils.argbFromLab(100.0, 0.0, 0.0)).isEqualTo(0xffffffff);
-    }
-
-    @Test
-    public void labFromArgb() {
-        assertThat(ColorUtils.labFromArgb(0xff000000)[0]).isWithin(0.001).of(0.0);
-        assertThat(ColorUtils.labFromArgb(0xff010101)[0]).isWithin(0.001).of(0.2741748000656514);
-        assertThat(ColorUtils.labFromArgb(0xff020202)[0]).isWithin(0.001).of(0.5483496001313029);
-        assertThat(ColorUtils.labFromArgb(0xff030303)[0]).isWithin(0.001).of(0.8225244001969543);
-        assertThat(ColorUtils.labFromArgb(0xff040404)[0]).isWithin(0.001).of(1.0966992002626057);
-        assertThat(ColorUtils.labFromArgb(0xff050505)[0]).isWithin(0.001).of(1.3708740003282571);
-        assertThat(ColorUtils.labFromArgb(0xff060606)[0]).isWithin(0.001).of(1.645048800393912);
-        assertThat(ColorUtils.labFromArgb(0xff070707)[0]).isWithin(0.001).of(1.9192236004595635);
-        assertThat(ColorUtils.labFromArgb(0xff080808)[0]).isWithin(0.001).of(2.193398400525215);
-        assertThat(ColorUtils.labFromArgb(0xff0c0c0c)[0]).isWithin(0.001).of(3.3209754491182544);
-        assertThat(ColorUtils.labFromArgb(0xff101010)[0]).isWithin(0.001).of(4.680444846419661);
-        assertThat(ColorUtils.labFromArgb(0xff181818)[0]).isWithin(0.001).of(8.248186036170349);
-        assertThat(ColorUtils.labFromArgb(0xff202020)[0]).isWithin(0.001).of(12.250030101522828);
-        assertThat(ColorUtils.labFromArgb(0xff404040)[0]).isWithin(0.001).of(27.093413739449055);
-        assertThat(ColorUtils.labFromArgb(0xff808080)[0]).isWithin(0.001).of(53.585013452169036);
-        assertThat(ColorUtils.labFromArgb(0xffffffff)[0]).isWithin(0.001).of(100.0);
-    }
-
-    @Test
-    public void argbFromLstar() {
-        assertThat(ColorUtils.argbFromLstar(0.0)).isEqualTo(0xff000000);
-        assertThat(ColorUtils.argbFromLstar(0.25)).isEqualTo(0xff010101);
-        assertThat(ColorUtils.argbFromLstar(0.5)).isEqualTo(0xff020202);
-        assertThat(ColorUtils.argbFromLstar(0.75)).isEqualTo(0xff030303);
-        assertThat(ColorUtils.argbFromLstar(1.0)).isEqualTo(0xff040404);
-        assertThat(ColorUtils.argbFromLstar(1.5)).isEqualTo(0xff050505);
-        assertThat(ColorUtils.argbFromLstar(2.0)).isEqualTo(0xff070707);
-        assertThat(ColorUtils.argbFromLstar(3.0)).isEqualTo(0xff0b0b0b);
-        assertThat(ColorUtils.argbFromLstar(4.0)).isEqualTo(0xff0e0e0e);
-        assertThat(ColorUtils.argbFromLstar(5.0)).isEqualTo(0xff111111);
-        assertThat(ColorUtils.argbFromLstar(6.0)).isEqualTo(0xff131313);
-        assertThat(ColorUtils.argbFromLstar(7.0)).isEqualTo(0xff151515);
-        assertThat(ColorUtils.argbFromLstar(8.0)).isEqualTo(0xff181818);
-        assertThat(ColorUtils.argbFromLstar(9.0)).isEqualTo(0xff191919);
-        assertThat(ColorUtils.argbFromLstar(10.0)).isEqualTo(0xff1b1b1b);
-        assertThat(ColorUtils.argbFromLstar(20.0)).isEqualTo(0xff303030);
-        assertThat(ColorUtils.argbFromLstar(30.0)).isEqualTo(0xff474747);
-        assertThat(ColorUtils.argbFromLstar(40.0)).isEqualTo(0xff5e5e5e);
-        assertThat(ColorUtils.argbFromLstar(50.0)).isEqualTo(0xff777777);
-        assertThat(ColorUtils.argbFromLstar(60.0)).isEqualTo(0xff919191);
-        assertThat(ColorUtils.argbFromLstar(70.0)).isEqualTo(0xffababab);
-        assertThat(ColorUtils.argbFromLstar(80.0)).isEqualTo(0xffc6c6c6);
-        assertThat(ColorUtils.argbFromLstar(90.0)).isEqualTo(0xffe2e2e2);
-        assertThat(ColorUtils.argbFromLstar(100.0)).isEqualTo(0xffffffff);
-    }
-
-    @Test
-    public void lstarFromArgb() {
-        assertThat(ColorUtils.lstarFromArgb(0xff000000)).isWithin(0.001).of(0.0);
-        assertThat(ColorUtils.lstarFromArgb(0xff010101)).isWithin(0.001).of(0.2741748000656518);
-        assertThat(ColorUtils.lstarFromArgb(0xff020202)).isWithin(0.001).of(0.5483496001313036);
-        assertThat(ColorUtils.lstarFromArgb(0xff030303)).isWithin(0.001).of(0.8225244001969553);
-        assertThat(ColorUtils.lstarFromArgb(0xff040404)).isWithin(0.001).of(1.0966992002626073);
-        assertThat(ColorUtils.lstarFromArgb(0xff050505)).isWithin(0.001).of(1.3708740003282587);
-        assertThat(ColorUtils.lstarFromArgb(0xff060606)).isWithin(0.001).of(1.6450488003939105);
-        assertThat(ColorUtils.lstarFromArgb(0xff070707)).isWithin(0.001).of(1.9192236004595624);
-        assertThat(ColorUtils.lstarFromArgb(0xff080808)).isWithin(0.001).of(2.1933984005252145);
-        assertThat(ColorUtils.lstarFromArgb(0xff0c0c0c)).isWithin(0.001).of(3.3209754491182557);
-        assertThat(ColorUtils.lstarFromArgb(0xff101010)).isWithin(0.001).of(4.680444846419663);
-        assertThat(ColorUtils.lstarFromArgb(0xff181818)).isWithin(0.001).of(8.248186036170349);
-        assertThat(ColorUtils.lstarFromArgb(0xff202020)).isWithin(0.001).of(12.250030101522828);
-        assertThat(ColorUtils.lstarFromArgb(0xff404040)).isWithin(0.001).of(27.093413739449055);
-        assertThat(ColorUtils.lstarFromArgb(0xff808080)).isWithin(0.001).of(53.585013452169036);
-        assertThat(ColorUtils.lstarFromArgb(0xffffffff)).isWithin(0.001).of(100.0);
-    }
-
-    @Test
-    public void yFromLstar() {
-        assertThat(ColorUtils.yFromLstar(0.0)).isWithin(1e-5).of(0.0);
-        assertThat(ColorUtils.yFromLstar(0.1)).isWithin(1e-5).of(0.0110705);
-        assertThat(ColorUtils.yFromLstar(0.2)).isWithin(1e-5).of(0.0221411);
-        assertThat(ColorUtils.yFromLstar(0.3)).isWithin(1e-5).of(0.0332116);
-        assertThat(ColorUtils.yFromLstar(0.4)).isWithin(1e-5).of(0.0442822);
-        assertThat(ColorUtils.yFromLstar(0.5)).isWithin(1e-5).of(0.0553528);
-        assertThat(ColorUtils.yFromLstar(1.0)).isWithin(1e-5).of(0.1107056);
-        assertThat(ColorUtils.yFromLstar(2.0)).isWithin(1e-5).of(0.2214112);
-        assertThat(ColorUtils.yFromLstar(3.0)).isWithin(1e-5).of(0.3321169);
-        assertThat(ColorUtils.yFromLstar(4.0)).isWithin(1e-5).of(0.4428225);
-        assertThat(ColorUtils.yFromLstar(5.0)).isWithin(1e-5).of(0.5535282);
-        assertThat(ColorUtils.yFromLstar(8.0)).isWithin(1e-5).of(0.8856451);
-        assertThat(ColorUtils.yFromLstar(10.0)).isWithin(1e-5).of(1.1260199);
-        assertThat(ColorUtils.yFromLstar(15.0)).isWithin(1e-5).of(1.9085832);
-        assertThat(ColorUtils.yFromLstar(20.0)).isWithin(1e-5).of(2.9890524);
-        assertThat(ColorUtils.yFromLstar(25.0)).isWithin(1e-5).of(4.4154767);
-        assertThat(ColorUtils.yFromLstar(30.0)).isWithin(1e-5).of(6.2359055);
-        assertThat(ColorUtils.yFromLstar(40.0)).isWithin(1e-5).of(11.2509737);
-        assertThat(ColorUtils.yFromLstar(50.0)).isWithin(1e-5).of(18.4186518);
-        assertThat(ColorUtils.yFromLstar(60.0)).isWithin(1e-5).of(28.1233342);
-        assertThat(ColorUtils.yFromLstar(70.0)).isWithin(1e-5).of(40.7494157);
-        assertThat(ColorUtils.yFromLstar(80.0)).isWithin(1e-5).of(56.6812907);
-        assertThat(ColorUtils.yFromLstar(90.0)).isWithin(1e-5).of(76.3033539);
-        assertThat(ColorUtils.yFromLstar(95.0)).isWithin(1e-5).of(87.6183294);
-        assertThat(ColorUtils.yFromLstar(99.0)).isWithin(1e-5).of(97.4360239);
-        assertThat(ColorUtils.yFromLstar(100.0)).isWithin(1e-5).of(100.0);
-    }
-
-    @Test
-    public void lstarFromY() {
-        assertThat(ColorUtils.lstarFromY(0.0)).isWithin(1e-5).of(0.0);
-        assertThat(ColorUtils.lstarFromY(0.1)).isWithin(1e-5).of(0.9032962);
-        assertThat(ColorUtils.lstarFromY(0.2)).isWithin(1e-5).of(1.8065925);
-        assertThat(ColorUtils.lstarFromY(0.3)).isWithin(1e-5).of(2.7098888);
-        assertThat(ColorUtils.lstarFromY(0.4)).isWithin(1e-5).of(3.6131851);
-        assertThat(ColorUtils.lstarFromY(0.5)).isWithin(1e-5).of(4.5164814);
-        assertThat(ColorUtils.lstarFromY(0.8856451)).isWithin(1e-5).of(8.0);
-        assertThat(ColorUtils.lstarFromY(1.0)).isWithin(1e-5).of(8.9914424);
-        assertThat(ColorUtils.lstarFromY(2.0)).isWithin(1e-5).of(15.4872443);
-        assertThat(ColorUtils.lstarFromY(3.0)).isWithin(1e-5).of(20.0438970);
-        assertThat(ColorUtils.lstarFromY(4.0)).isWithin(1e-5).of(23.6714419);
-        assertThat(ColorUtils.lstarFromY(5.0)).isWithin(1e-5).of(26.7347653);
-        assertThat(ColorUtils.lstarFromY(10.0)).isWithin(1e-5).of(37.8424304);
-        assertThat(ColorUtils.lstarFromY(15.0)).isWithin(1e-5).of(45.6341970);
-        assertThat(ColorUtils.lstarFromY(20.0)).isWithin(1e-5).of(51.8372115);
-        assertThat(ColorUtils.lstarFromY(25.0)).isWithin(1e-5).of(57.0754208);
-        assertThat(ColorUtils.lstarFromY(30.0)).isWithin(1e-5).of(61.6542222);
-        assertThat(ColorUtils.lstarFromY(40.0)).isWithin(1e-5).of(69.4695307);
-        assertThat(ColorUtils.lstarFromY(50.0)).isWithin(1e-5).of(76.0692610);
-        assertThat(ColorUtils.lstarFromY(60.0)).isWithin(1e-5).of(81.8381891);
-        assertThat(ColorUtils.lstarFromY(70.0)).isWithin(1e-5).of(86.9968642);
-        assertThat(ColorUtils.lstarFromY(80.0)).isWithin(1e-5).of(91.6848609);
-        assertThat(ColorUtils.lstarFromY(90.0)).isWithin(1e-5).of(95.9967686);
-        assertThat(ColorUtils.lstarFromY(95.0)).isWithin(1e-5).of(98.0335184);
-        assertThat(ColorUtils.lstarFromY(99.0)).isWithin(1e-5).of(99.6120372);
-        assertThat(ColorUtils.lstarFromY(100.0)).isWithin(1e-5).of(100.0);
-    }
-
-    @Test
-    public void yToLstarToY() {
-        for (double y = 0.0; y <= 100.0; y += 0.1) {
-            double lstar = ColorUtils.lstarFromY(y);
-            double reconstructedY = ColorUtils.yFromLstar(lstar);
-            assertThat(reconstructedY).isWithin(1e-8).of(y);
-        }
-    }
-
-    @Test
-    public void lstarToYToLstar() {
-        for (double lstar = 0.0; lstar <= 100.0; lstar += 0.1) {
-            double y = ColorUtils.yFromLstar(lstar);
-            double reconstructedLstar = ColorUtils.lstarFromY(y);
-            assertThat(reconstructedLstar).isWithin(1e-8).of(lstar);
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ContrastTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ContrastTest.java
deleted file mode 100644
index 8d3e401..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ContrastTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static org.junit.Assert.assertEquals;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.contrast.Contrast;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public final class ContrastTest extends SysuiTestCase {
-
-    @Test
-    public void lighter_impossibleRatioErrors() {
-        assertEquals(-1.0, Contrast.lighter(90.0, 10.0), 0.001);
-    }
-
-    @Test
-    public void lighter_outOfBoundsInputAboveErrors() {
-        assertEquals(-1.0, Contrast.lighter(110.0, 2.0), 0.001);
-    }
-
-    @Test
-    public void lighter_outOfBoundsInputBelowErrors() {
-        assertEquals(-1.0, Contrast.lighter(-10.0, 2.0), 0.001);
-    }
-
-    @Test
-    public void lighterUnsafe_returnsMaxTone() {
-        assertEquals(100, Contrast.lighterUnsafe(100.0, 2.0), 0.001);
-    }
-
-    @Test
-    public void darker_impossibleRatioErrors() {
-        assertEquals(-1.0, Contrast.darker(10.0, 20.0), 0.001);
-    }
-
-    @Test
-    public void darker_outOfBoundsInputAboveErrors() {
-        assertEquals(-1.0, Contrast.darker(110.0, 2.0), 0.001);
-    }
-
-    @Test
-    public void darker_outOfBoundsInputBelowErrors() {
-        assertEquals(-1.0, Contrast.darker(-10.0, 2.0), 0.001);
-    }
-
-    @Test
-    public void darkerUnsafe_returnsMinTone() {
-        assertEquals(0.0, Contrast.darkerUnsafe(0.0, 2.0), 0.001);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/DislikeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/DislikeTest.java
deleted file mode 100644
index bda7fcc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/DislikeTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.dislike.DislikeAnalyzer;
-import com.android.systemui.monet.hct.Hct;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public final class DislikeTest extends SysuiTestCase {
-
-    @Test
-    public void monkSkinToneScaleColorsLiked() {
-        // From https://skintone.google#/get-started
-        int[] monkSkinToneScaleColors =
-                new int[]{
-                        0xfff6ede4,
-                        0xfff3e7db,
-                        0xfff7ead0,
-                        0xffeadaba,
-                        0xffd7bd96,
-                        0xffa07e56,
-                        0xff825c43,
-                        0xff604134,
-                        0xff3a312a,
-                        0xff292420,
-                };
-        for (int color : monkSkinToneScaleColors) {
-            assertFalse(DislikeAnalyzer.isDisliked(Hct.fromInt(color)));
-        }
-    }
-
-    @Test
-    public void bileColorsDisliked() {
-        int[] unlikable =
-                new int[]{
-                        0xff95884B, 0xff716B40, 0xffB08E00, 0xff4C4308, 0xff464521,
-                };
-        for (int color : unlikable) {
-            assertTrue(DislikeAnalyzer.isDisliked(Hct.fromInt(color)));
-        }
-    }
-
-    @Test
-    public void bileColorsBecameLikable() {
-        int[] unlikable =
-                new int[]{
-                        0xff95884B, 0xff716B40, 0xffB08E00, 0xff4C4308, 0xff464521,
-                };
-        for (int color : unlikable) {
-            Hct hct = Hct.fromInt(color);
-            assertTrue(DislikeAnalyzer.isDisliked(hct));
-            Hct likable = DislikeAnalyzer.fixIfDisliked(hct);
-            assertFalse(DislikeAnalyzer.isDisliked(likable));
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/DynamicColorTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/DynamicColorTest.java
deleted file mode 100644
index 4ce32d2..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/DynamicColorTest.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
-import static org.junit.Assert.assertTrue;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.monet.contrast.Contrast;
-import com.android.systemui.monet.dynamiccolor.DynamicColor;
-import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
-import com.android.systemui.monet.dynamiccolor.ToneDeltaConstraint;
-import com.android.systemui.monet.dynamiccolor.TonePolarity;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.scheme.DynamicScheme;
-import com.android.systemui.monet.scheme.SchemeContent;
-import com.android.systemui.monet.scheme.SchemeFidelity;
-import com.android.systemui.monet.scheme.SchemeMonochrome;
-import com.android.systemui.monet.scheme.SchemeTonalSpot;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@Ignore("b/279581953")
-@SmallTest
-@RunWith(JUnit4.class)
-public final class DynamicColorTest extends SysuiTestCase{
-
-  private final MaterialDynamicColors dynamicColors = new MaterialDynamicColors();
-
-  @Test
-  public void fromArgbNoBackground_doesntChangeForContrast() {
-    final int blueArgb = 0xff0000ff;
-    final DynamicColor dynamicColor = DynamicColor.fromArgb(blueArgb);
-
-    final SchemeTonalSpot standardContrast = new SchemeTonalSpot(Hct.fromInt(blueArgb), false, 0.0);
-    assertThat(dynamicColor.getArgb(standardContrast)).isSameColorAs(blueArgb);
-
-    final SchemeTonalSpot minContrast = new SchemeTonalSpot(Hct.fromInt(blueArgb), false, -1.0);
-    assertThat(dynamicColor.getArgb(minContrast)).isSameColorAs(blueArgb);
-
-    final SchemeTonalSpot maxContrast = new SchemeTonalSpot(Hct.fromInt(blueArgb), false, 1.0);
-    assertThat(dynamicColor.getArgb(maxContrast)).isSameColorAs(blueArgb);
-  }
-
-  @Test
-  public void toneDeltaConstraintNoPreference_evaluatesCorrectly() {
-    final int blueArgb = 0xff0000ff;
-    final int redArgb = 0xffff0000;
-    final DynamicColor otherDynamicColor = DynamicColor.fromArgb(redArgb);
-    final DynamicColor dynamicColor =
-        DynamicColor.fromArgb(
-            blueArgb,
-            (s) -> 30.0,
-            null,
-            (s) -> new ToneDeltaConstraint(30, otherDynamicColor, TonePolarity.NO_PREFERENCE));
-    final SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(blueArgb), false, 0.0);
-    assertThat(dynamicColor.getArgb(scheme)).isSameColorAs(0xff0000ef);
-  }
-
-  @Test
-  public void dynamicColor_withOpacity() {
-    final DynamicColor dynamicColor =
-        new DynamicColor(
-            s -> 0.0,
-            s -> 0.0,
-            s -> s.isDark ? 100.0 : 0.0,
-            s -> s.isDark ? 0.20 : 0.12,
-            null,
-            scheme ->
-                DynamicColor.toneMinContrastDefault(
-                    (s) -> s.isDark ? 100.0 : 0.0, null, scheme, null),
-            scheme ->
-                DynamicColor.toneMaxContrastDefault(
-                    (s) -> s.isDark ? 100.0 : 0.0, null, scheme, null),
-            null);
-    final SchemeTonalSpot lightScheme = new SchemeTonalSpot(Hct.fromInt(0xff4285f4), false, 0.0);
-    assertThat(dynamicColor.getArgb(lightScheme)).isSameColorAs(0x1f000000);
-
-    final SchemeTonalSpot darkScheme = new SchemeTonalSpot(Hct.fromInt(0xff4285f4), true, 0.0);
-    assertThat(dynamicColor.getArgb(darkScheme)).isSameColorAs(0x33ffffff);
-  }
-
-  @Test
-  public void respectsContrast() {
-    final Hct[] seedColors =
-        new Hct[] {
-          Hct.fromInt(0xFFFF0000),
-          Hct.fromInt(0xFFFFFF00),
-          Hct.fromInt(0xFF00FF00),
-          Hct.fromInt(0xFF0000FF)
-        };
-
-    final double[] contrastLevels = {-1.0, -0.5, 0.0, 0.5, 1.0};
-
-    for (Hct seedColor : seedColors) {
-      for (double contrastLevel : contrastLevels) {
-        for (boolean isDark : new boolean[] {false, true}) {
-          final DynamicScheme[] schemes =
-              new DynamicScheme[] {
-                new SchemeContent(seedColor, isDark, contrastLevel),
-                new SchemeMonochrome(seedColor, isDark, contrastLevel),
-                new SchemeTonalSpot(seedColor, isDark, contrastLevel),
-                new SchemeFidelity(seedColor, isDark, contrastLevel)
-              };
-          for (final DynamicScheme scheme : schemes) {
-            assertTrue(
-                pairSatisfiesContrast(scheme, dynamicColors.onPrimary(), dynamicColors.primary()));
-            assertTrue(
-                pairSatisfiesContrast(
-                    scheme, dynamicColors.onPrimaryContainer(), dynamicColors.primaryContainer()));
-            assertTrue(
-                pairSatisfiesContrast(
-                    scheme, dynamicColors.onSecondary(), dynamicColors.secondary()));
-            assertTrue(
-                pairSatisfiesContrast(
-                    scheme,
-                    dynamicColors.onSecondaryContainer(),
-                    dynamicColors.secondaryContainer()));
-            assertTrue(
-                pairSatisfiesContrast(
-                    scheme, dynamicColors.onTertiary(), dynamicColors.tertiary()));
-            assertTrue(
-                pairSatisfiesContrast(
-                    scheme,
-                    dynamicColors.onTertiaryContainer(),
-                    dynamicColors.tertiaryContainer()));
-            assertTrue(
-                pairSatisfiesContrast(scheme, dynamicColors.onError(), dynamicColors.error()));
-            assertTrue(
-                pairSatisfiesContrast(
-                    scheme, dynamicColors.onErrorContainer(), dynamicColors.errorContainer()));
-            assertTrue(
-                pairSatisfiesContrast(
-                    scheme, dynamicColors.onBackground(), dynamicColors.background()));
-            assertTrue(
-                pairSatisfiesContrast(
-                    scheme, dynamicColors.onSurfaceVariant(), dynamicColors.surfaceVariant()));
-            assertTrue(
-                pairSatisfiesContrast(
-                    scheme, dynamicColors.inverseOnSurface(), dynamicColors.inverseSurface()));
-          }
-        }
-      }
-    }
-  }
-
-  @Test
-  public void valuesAreCorrect() {
-    // Checks that the values of certain dynamic colors match Dart results.
-    assertThat(
-            dynamicColors
-                .onPrimaryContainer()
-                .getArgb(new SchemeFidelity(Hct.fromInt(0xFFFF0000), false, 0.5)))
-        .isSameColorAs(0xFFFFE5E1);
-    assertThat(
-            dynamicColors
-                .onSecondaryContainer()
-                .getArgb(new SchemeContent(Hct.fromInt(0xFF0000FF), false, 0.5)))
-        .isSameColorAs(0xFFFFFCFF);
-    assertThat(
-            dynamicColors
-                .onTertiaryContainer()
-                .getArgb(new SchemeContent(Hct.fromInt(0xFFFFFF00), true, -0.5)))
-        .isSameColorAs(0xFF616600);
-    assertThat(
-            dynamicColors
-                .inverseSurface()
-                .getArgb(new SchemeContent(Hct.fromInt(0xFF0000FF), false, 0.0)))
-        .isSameColorAs(0xFF2F2F3B);
-    assertThat(
-            dynamicColors
-                .inversePrimary()
-                .getArgb(new SchemeContent(Hct.fromInt(0xFFFF0000), false, -0.5)))
-        .isSameColorAs(0xFFFF907F);
-    assertThat(
-            dynamicColors
-                .outlineVariant()
-                .getArgb(new SchemeContent(Hct.fromInt(0xFFFFFF00), true, 0.0)))
-        .isSameColorAs(0xFF484831);
-  }
-
-  private boolean pairSatisfiesContrast(DynamicScheme scheme, DynamicColor fg, DynamicColor bg) {
-    double fgTone = fg.getHct(scheme).getTone();
-    double bgTone = bg.getHct(scheme).getTone();
-    double minimumRequirement = scheme.contrastLevel >= 0.0 ? 4.5 : 3.0;
-    return Contrast.ratioOfTones(fgTone, bgTone) >= minimumRequirement;
-  }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/HctTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/HctTest.java
deleted file mode 100644
index e9ca8a6..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/HctTest.java
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.hct.Cam16;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.hct.ViewingConditions;
-import com.android.systemui.monet.utils.ColorUtils;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public final class HctTest extends SysuiTestCase {
-    private static final int RED = 0xffff0000;
-    private static final int GREEN = 0xff00ff00;
-    private static final int BLUE = 0xff0000ff;
-    private static final int WHITE = 0xffffffff;
-    private static final int BLACK = 0xff000000;
-    private static final int MIDGRAY = 0xff777777;
-
-    public static final ViewingConditions VIEWING_CONDITIONS_WHITE_BACKGROUND =
-            ViewingConditions.make(
-                    new double[]{
-                            ColorUtils.whitePointD65()[0],
-                            ColorUtils.whitePointD65()[1],
-                            ColorUtils.whitePointD65()[2]
-                    },
-                    (200.0 / Math.PI * ColorUtils.yFromLstar(50.0) / 100.f),
-                    100.0,
-                    2.0,
-                    false);
-
-    public static final ViewingConditions VIEWING_CONDITIONS_BLACK_BACKGROUND =
-            ViewingConditions.make(
-                    new double[]{
-                            ColorUtils.whitePointD65()[0],
-                            ColorUtils.whitePointD65()[1],
-                            ColorUtils.whitePointD65()[2]
-                    },
-                    (200.0 / Math.PI * ColorUtils.yFromLstar(50.0) / 100.f),
-                    0.0,
-                    2.0,
-                    false);
-
-    @Test
-    public void camFromArgb_red() {
-        Cam16 cam = Cam16.fromInt(RED);
-
-        assertEquals(27.408, cam.getHue(), 0.001);
-        assertEquals(113.357, cam.getChroma(), 0.001);
-        assertEquals(46.445, cam.getJ(), 0.001);
-        assertEquals(89.494, cam.getM(), 0.001);
-        assertEquals(91.889, cam.getS(), 0.001);
-        assertEquals(105.988, cam.getQ(), 0.001);
-    }
-
-    @Test
-    public void camFromArgb_green() {
-        Cam16 cam = Cam16.fromInt(GREEN);
-
-        assertEquals(142.139, cam.getHue(), 0.001);
-        assertEquals(108.410, cam.getChroma(), 0.001);
-        assertEquals(79.331, cam.getJ(), 0.001);
-        assertEquals(85.587, cam.getM(), 0.001);
-        assertEquals(78.604, cam.getS(), 0.001);
-        assertEquals(138.520, cam.getQ(), 0.001);
-    }
-
-    @Test
-    public void camFromArgb_blue() {
-        Cam16 cam = Cam16.fromInt(BLUE);
-
-        assertEquals(282.788, cam.getHue(), 0.001);
-        assertEquals(87.230, cam.getChroma(), 0.001);
-        assertEquals(25.465, cam.getJ(), 0.001);
-        assertEquals(68.867, cam.getM(), 0.001);
-        assertEquals(93.674, cam.getS(), 0.001);
-        assertEquals(78.481, cam.getQ(), 0.001);
-    }
-
-    @Test
-    public void camFromArgb_white() {
-        Cam16 cam = Cam16.fromInt(WHITE);
-
-        assertEquals(209.492, cam.getHue(), 0.001);
-        assertEquals(2.869, cam.getChroma(), 0.001);
-        assertEquals(100.0, cam.getJ(), 0.001);
-        assertEquals(2.265, cam.getM(), 0.001);
-        assertEquals(12.068, cam.getS(), 0.001);
-        assertEquals(155.521, cam.getQ(), 0.001);
-    }
-
-    @Test
-    public void camFromArgb_black() {
-        Cam16 cam = Cam16.fromInt(BLACK);
-
-        assertEquals(0.0, cam.getHue(), 0.001);
-        assertEquals(0.0, cam.getChroma(), 0.001);
-        assertEquals(0.0, cam.getJ(), 0.001);
-        assertEquals(0.0, cam.getM(), 0.001);
-        assertEquals(0.0, cam.getS(), 0.001);
-        assertEquals(0.0, cam.getQ(), 0.001);
-    }
-
-    @Test
-    public void camToArgbToCam_red() {
-        Cam16 cam = Cam16.fromInt(RED);
-        int argb = cam.toInt();
-        assertEquals(RED, argb);
-    }
-
-    @Test
-    public void camToArgbToCam_green() {
-        Cam16 cam = Cam16.fromInt(GREEN);
-        int argb = cam.toInt();
-        assertEquals(GREEN, argb);
-    }
-
-    @Test
-    public void camToArgbToCam_blue() {
-        Cam16 cam = Cam16.fromInt(BLUE);
-        int argb = cam.toInt();
-        assertEquals(BLUE, argb);
-    }
-
-    @Test
-    public void viewingConditions_default() {
-        ViewingConditions vc = ViewingConditions.DEFAULT;
-
-        assertEquals(0.184, vc.getN(), 0.001);
-        assertEquals(29.981, vc.getAw(), 0.001);
-        assertEquals(1.016, vc.getNbb(), 0.001);
-        assertEquals(1.021, vc.getRgbD()[0], 0.001);
-        assertEquals(0.986, vc.getRgbD()[1], 0.001);
-        assertEquals(0.933, vc.getRgbD()[2], 0.001);
-        assertEquals(0.789, vc.getFlRoot(), 0.001);
-    }
-
-    private boolean colorIsOnBoundary(int argb) {
-        return ColorUtils.redFromArgb(argb) == 0
-                || ColorUtils.redFromArgb(argb) == 255
-                || ColorUtils.greenFromArgb(argb) == 0
-                || ColorUtils.greenFromArgb(argb) == 255
-                || ColorUtils.blueFromArgb(argb) == 0
-                || ColorUtils.blueFromArgb(argb) == 255;
-    }
-
-    @Test
-    public void correctness() {
-        for (int hue = 15; hue < 360; hue += 30) {
-            for (int chroma = 0; chroma <= 100; chroma += 10) {
-                for (int tone = 20; tone <= 80; tone += 10) {
-                    Hct hctColor = Hct.from((double) hue, (double) chroma, (double) tone);
-
-                    if (chroma > 0) {
-                        assertEquals(
-                                "Incorrect hue for H" + hue + " C" + chroma + " T" + tone,
-                                hue,
-                                hctColor.getHue(),
-                                4.0);
-                    }
-
-                    assertTrue(
-                            "Negative chroma for H" + hue + " C" + chroma + " T" + tone,
-                            hctColor.getChroma() >= 0.0);
-
-                    assertTrue(
-                            "Chroma too high for H" + hue + " C" + chroma + " T" + tone,
-                            hctColor.getChroma() <= chroma + 2.5);
-
-                    if (hctColor.getChroma() < chroma - 2.5) {
-                        assertTrue(
-                                "Color not on boundary for non-sRGB color for H"
-                                        + hue
-                                        + " C"
-                                        + chroma
-                                        + " T"
-                                        + tone,
-                                colorIsOnBoundary(hctColor.toInt()));
-                    }
-
-                    assertEquals(tone, hctColor.getTone(), 0.5);
-                }
-            }
-        }
-    }
-
-    @Test
-    public void relativity_redInBlack() {
-        final int colorToTest = RED;
-        final Hct hct = Hct.fromInt(colorToTest);
-        assertThat(0xff9F5C51)
-                .isSameColorAs(
-                        hct.inViewingConditions(VIEWING_CONDITIONS_BLACK_BACKGROUND).toInt());
-    }
-
-    @Test
-    public void relativity_redInWhite() {
-        final int colorToTest = RED;
-        final Hct hct = Hct.fromInt(colorToTest);
-        assertThat(0xffFF5D48)
-                .isSameColorAs(
-                        hct.inViewingConditions(VIEWING_CONDITIONS_WHITE_BACKGROUND).toInt());
-    }
-
-    @Test
-    public void relativity_greenInBlack() {
-        final int colorToTest = GREEN;
-        final Hct hct = Hct.fromInt(colorToTest);
-        assertThat(0xffACD69D)
-                .isSameColorAs(
-                        hct.inViewingConditions(VIEWING_CONDITIONS_BLACK_BACKGROUND).toInt());
-    }
-
-    @Test
-    public void relativity_greenInWhite() {
-        final int colorToTest = GREEN;
-        final Hct hct = Hct.fromInt(colorToTest);
-        assertThat(0xff8EFF77)
-                .isSameColorAs(
-                        hct.inViewingConditions(VIEWING_CONDITIONS_WHITE_BACKGROUND).toInt());
-    }
-
-    @Test
-    public void relativity_blueInBlack() {
-        final int colorToTest = BLUE;
-        final Hct hct = Hct.fromInt(colorToTest);
-        assertThat(0xff343654)
-                .isSameColorAs(
-                        hct.inViewingConditions(VIEWING_CONDITIONS_BLACK_BACKGROUND).toInt());
-    }
-
-    @Test
-    public void relativity_blueInWhite() {
-        final int colorToTest = BLUE;
-        final Hct hct = Hct.fromInt(colorToTest);
-        assertThat(0xff3F49FF)
-                .isSameColorAs(
-                        hct.inViewingConditions(VIEWING_CONDITIONS_WHITE_BACKGROUND).toInt());
-    }
-
-    @Test
-    public void relativity_whiteInBlack() {
-        final int colorToTest = WHITE;
-        final Hct hct = Hct.fromInt(colorToTest);
-        assertThat(0xffFFFFFF)
-                .isSameColorAs(
-                        hct.inViewingConditions(VIEWING_CONDITIONS_BLACK_BACKGROUND).toInt());
-    }
-
-    @Test
-    public void relativity_whiteInWhite() {
-        final int colorToTest = WHITE;
-        final Hct hct = Hct.fromInt(colorToTest);
-        assertThat(0xffFFFFFF)
-                .isSameColorAs(
-                        hct.inViewingConditions(VIEWING_CONDITIONS_WHITE_BACKGROUND).toInt());
-    }
-
-    @Test
-    public void relativity_midgrayInBlack() {
-        final int colorToTest = MIDGRAY;
-        final Hct hct = Hct.fromInt(colorToTest);
-        assertThat(0xff605F5F)
-                .isSameColorAs(
-                        hct.inViewingConditions(VIEWING_CONDITIONS_BLACK_BACKGROUND).toInt());
-    }
-
-    @Test
-    public void relativity_midgrayInWhite() {
-        final int colorToTest = MIDGRAY;
-        final Hct hct = Hct.fromInt(colorToTest);
-        assertThat(0xff8E8E8E)
-                .isSameColorAs(
-                        hct.inViewingConditions(VIEWING_CONDITIONS_WHITE_BACKGROUND).toInt());
-    }
-
-    @Test
-    public void relativity_blackInBlack() {
-        final int colorToTest = BLACK;
-        final Hct hct = Hct.fromInt(colorToTest);
-        assertThat(0xff000000)
-                .isSameColorAs(
-                        hct.inViewingConditions(VIEWING_CONDITIONS_BLACK_BACKGROUND).toInt());
-    }
-
-    @Test
-    public void relativity_blackInWhite() {
-        final int colorToTest = BLACK;
-        final Hct hct = Hct.fromInt(colorToTest);
-        assertThat(0xff000000)
-                .isSameColorAs(
-                        hct.inViewingConditions(VIEWING_CONDITIONS_WHITE_BACKGROUND).toInt());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/MathUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/MathUtilsTest.java
deleted file mode 100644
index 6b8437a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/MathUtilsTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.monet;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.utils.MathUtils;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public final class MathUtilsTest extends SysuiTestCase {
-
-    @Test
-    public void signum() {
-        assertThat(MathUtils.signum(1.5)).isEqualTo(1);
-        assertThat(MathUtils.signum(0)).isEqualTo(0);
-        assertThat(MathUtils.signum(-3.0)).isEqualTo(-1);
-    }
-
-    @Test
-    public void sanitizeDegreesInt() {
-        assertThat(MathUtils.sanitizeDegreesInt(0)).isEqualTo(0);
-        assertThat(MathUtils.sanitizeDegreesInt(30)).isEqualTo(30);
-        assertThat(MathUtils.sanitizeDegreesInt(150)).isEqualTo(150);
-        assertThat(MathUtils.sanitizeDegreesInt(360)).isEqualTo(0);
-        assertThat(MathUtils.sanitizeDegreesInt(450)).isEqualTo(90);
-        assertThat(MathUtils.sanitizeDegreesInt(1000000)).isEqualTo(280);
-        assertThat(MathUtils.sanitizeDegreesInt(-10)).isEqualTo(350);
-        assertThat(MathUtils.sanitizeDegreesInt(-90)).isEqualTo(270);
-        assertThat(MathUtils.sanitizeDegreesInt(-1000000)).isEqualTo(80);
-    }
-
-    @Test
-    public void sanitizeDegreesDouble() {
-        assertThat(MathUtils.sanitizeDegreesDouble(0.0)).isWithin(0.001).of(0.0);
-        assertThat(MathUtils.sanitizeDegreesDouble(30.0)).isWithin(0.001).of(30.0);
-        assertThat(MathUtils.sanitizeDegreesDouble(150.0)).isWithin(0.001).of(150.0);
-        assertThat(MathUtils.sanitizeDegreesDouble(360.0)).isWithin(0.001).of(0.0);
-        assertThat(MathUtils.sanitizeDegreesDouble(450.0)).isWithin(0.001).of(90.0);
-        assertThat(MathUtils.sanitizeDegreesDouble(1000000.0)).isWithin(0.001).of(280.0);
-        assertThat(MathUtils.sanitizeDegreesDouble(-10.0)).isWithin(0.001).of(350.0);
-        assertThat(MathUtils.sanitizeDegreesDouble(-90.0)).isWithin(0.001).of(270.0);
-        assertThat(MathUtils.sanitizeDegreesDouble(-1000000.0)).isWithin(0.001).of(80.0);
-        assertThat(MathUtils.sanitizeDegreesDouble(100.375)).isWithin(0.001).of(100.375);
-        assertThat(MathUtils.sanitizeDegreesDouble(123456.789)).isWithin(0.001).of(336.789);
-        assertThat(MathUtils.sanitizeDegreesDouble(-200.625)).isWithin(0.001).of(159.375);
-        assertThat(MathUtils.sanitizeDegreesDouble(-123456.789)).isWithin(0.001).of(23.211);
-    }
-
-    @Test
-    public void rotationDirection() {
-        for (double from = 0.0; from < 360.0; from += 15.0) {
-            for (double to = 7.5; to < 360.0; to += 15.0) {
-                double expectedAnswer = originalRotationDirection(from, to);
-                double actualAnswer = MathUtils.rotationDirection(from, to);
-                assertThat(actualAnswer).isEqualTo(expectedAnswer);
-                assertThat(Math.abs(actualAnswer)).isEqualTo(1.0);
-            }
-        }
-    }
-
-    // Original implementation for MathUtils.rotationDirection.
-    // Included here to test equivalence with new implementation.
-    private static double originalRotationDirection(double from, double to) {
-        double a = to - from;
-        double b = to - from + 360.0;
-        double c = to - from - 360.0;
-        double aAbs = Math.abs(a);
-        double bAbs = Math.abs(b);
-        double cAbs = Math.abs(c);
-        if (aAbs <= bAbs && aAbs <= cAbs) {
-            return a >= 0.0 ? 1.0 : -1.0;
-        } else if (bAbs <= aAbs && bAbs <= cAbs) {
-            return b >= 0.0 ? 1.0 : -1.0;
-        } else {
-            return c >= 0.0 ? 1.0 : -1.0;
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/PalettesTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/PalettesTest.java
deleted file mode 100644
index 0b65fdf..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/PalettesTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.hct.Cam16;
-import com.android.systemui.monet.palettes.CorePalette;
-import com.android.systemui.monet.palettes.TonalPalette;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public final class PalettesTest extends SysuiTestCase {
-
-    @Test
-    public void tones_ofBlue() {
-        int color = 0xff0000ff;
-        Cam16 cam = Cam16.fromInt(color);
-        TonalPalette tones = TonalPalette.fromHueAndChroma(cam.getHue(), cam.getChroma());
-        assertThat(tones.tone(100)).isEqualTo(0xFFFFFFFF);
-        assertThat(tones.tone(95)).isEqualTo(0xfff1efff);
-        assertThat(tones.tone(90)).isEqualTo(0xffe0e0ff);
-        assertThat(tones.tone(80)).isEqualTo(0xffbec2ff);
-        assertThat(tones.tone(70)).isEqualTo(0xff9da3ff);
-        assertThat(tones.tone(60)).isEqualTo(0xff7c84ff);
-        assertThat(tones.tone(50)).isEqualTo(0xff5a64ff);
-        assertThat(tones.tone(40)).isEqualTo(0xff343dff);
-        assertThat(tones.tone(30)).isEqualTo(0xff0000ef);
-        assertThat(tones.tone(20)).isEqualTo(0xff0001ac);
-        assertThat(tones.tone(10)).isEqualTo(0xff00006e);
-        assertThat(tones.tone(0)).isEqualTo(0xff000000);
-    }
-
-    @Test
-    public void keyTones_ofBlue() {
-        CorePalette core = CorePalette.of(0xff0000ff);
-
-        assertThat(core.a1.tone(100)).isEqualTo(0xFFFFFFFF);
-        assertThat(core.a1.tone(95)).isEqualTo(0xfff1efff);
-        assertThat(core.a1.tone(90)).isEqualTo(0xffe0e0ff);
-        assertThat(core.a1.tone(80)).isEqualTo(0xffbec2ff);
-        assertThat(core.a1.tone(70)).isEqualTo(0xff9da3ff);
-        assertThat(core.a1.tone(60)).isEqualTo(0xff7c84ff);
-        assertThat(core.a1.tone(50)).isEqualTo(0xff5a64ff);
-        assertThat(core.a1.tone(40)).isEqualTo(0xff343dff);
-        assertThat(core.a1.tone(30)).isEqualTo(0xff0000ef);
-        assertThat(core.a1.tone(20)).isEqualTo(0xff0001ac);
-        assertThat(core.a1.tone(10)).isEqualTo(0xff00006e);
-        assertThat(core.a1.tone(0)).isEqualTo(0xff000000);
-
-        assertThat(core.a2.tone(100)).isEqualTo(0xffffffff);
-        assertThat(core.a2.tone(95)).isEqualTo(0xfff1efff);
-        assertThat(core.a2.tone(90)).isEqualTo(0xffe1e0f9);
-        assertThat(core.a2.tone(80)).isEqualTo(0xffc5c4dd);
-        assertThat(core.a2.tone(70)).isEqualTo(0xffa9a9c1);
-        assertThat(core.a2.tone(60)).isEqualTo(0xff8f8fa6);
-        assertThat(core.a2.tone(50)).isEqualTo(0xff75758b);
-        assertThat(core.a2.tone(40)).isEqualTo(0xff5c5d72);
-        assertThat(core.a2.tone(30)).isEqualTo(0xff444559);
-        assertThat(core.a2.tone(20)).isEqualTo(0xff2e2f42);
-        assertThat(core.a2.tone(10)).isEqualTo(0xff191a2c);
-        assertThat(core.a2.tone(0)).isEqualTo(0xff000000);
-    }
-
-    @Test
-    public void keyTones_contentOfBlue() {
-        CorePalette core = CorePalette.contentOf(0xff0000ff);
-
-        assertThat(core.a1.tone(100)).isEqualTo(0xffffffff);
-        assertThat(core.a1.tone(95)).isEqualTo(0xfff1efff);
-        assertThat(core.a1.tone(90)).isEqualTo(0xffe0e0ff);
-        assertThat(core.a1.tone(80)).isEqualTo(0xffbec2ff);
-        assertThat(core.a1.tone(70)).isEqualTo(0xff9da3ff);
-        assertThat(core.a1.tone(60)).isEqualTo(0xff7c84ff);
-        assertThat(core.a1.tone(50)).isEqualTo(0xff5a64ff);
-        assertThat(core.a1.tone(40)).isEqualTo(0xff343dff);
-        assertThat(core.a1.tone(30)).isEqualTo(0xff0000ef);
-        assertThat(core.a1.tone(20)).isEqualTo(0xff0001ac);
-        assertThat(core.a1.tone(10)).isEqualTo(0xff00006e);
-        assertThat(core.a1.tone(0)).isEqualTo(0xff000000);
-
-        assertThat(core.a2.tone(100)).isEqualTo(0xffffffff);
-        assertThat(core.a2.tone(95)).isEqualTo(0xfff1efff);
-        assertThat(core.a2.tone(90)).isEqualTo(0xffe0e0ff);
-        assertThat(core.a2.tone(80)).isEqualTo(0xffc1c3f4);
-        assertThat(core.a2.tone(70)).isEqualTo(0xffa5a7d7);
-        assertThat(core.a2.tone(60)).isEqualTo(0xff8b8dbb);
-        assertThat(core.a2.tone(50)).isEqualTo(0xff7173a0);
-        assertThat(core.a2.tone(40)).isEqualTo(0xff585b86);
-        assertThat(core.a2.tone(30)).isEqualTo(0xff40436d);
-        assertThat(core.a2.tone(20)).isEqualTo(0xff2a2d55);
-        assertThat(core.a2.tone(10)).isEqualTo(0xff14173f);
-        assertThat(core.a2.tone(0)).isEqualTo(0xff000000);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/QuantizerMapTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/QuantizerMapTest.java
deleted file mode 100644
index 6a282a1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/QuantizerMapTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.quantize.QuantizerMap;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Map;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public final class QuantizerMapTest extends SysuiTestCase {
-    private static final int RED = 0xffff0000;
-    private static final int GREEN = 0xff00ff00;
-    private static final int BLUE = 0xff0000ff;
-    private static final QuantizerMap quantizer = new QuantizerMap();
-
-    @Test
-    public void quantize_1R() {
-        Map<Integer, Integer> answer = quantizer.quantize(new int[]{RED}, 128).colorToCount;
-
-        assertThat(answer).hasSize(1);
-        assertThat(answer).containsEntry(RED, 1);
-    }
-
-    @Test
-    public void quantize_1G() {
-        Map<Integer, Integer> answer = quantizer.quantize(new int[]{GREEN}, 128).colorToCount;
-
-        assertThat(answer).hasSize(1);
-        assertThat(answer).containsEntry(GREEN, 1);
-    }
-
-    @Test
-    public void quantize_1B() {
-        Map<Integer, Integer> answer = quantizer.quantize(new int[]{BLUE}, 128).colorToCount;
-
-        assertThat(answer).hasSize(1);
-        assertThat(answer).containsEntry(BLUE, 1);
-    }
-
-    @Test
-    public void quantize_5B() {
-        Map<Integer, Integer> answer =
-                quantizer.quantize(new int[]{BLUE, BLUE, BLUE, BLUE, BLUE}, 128).colorToCount;
-
-        assertThat(answer).hasSize(1);
-        assertThat(answer).containsEntry(BLUE, 5);
-    }
-
-    @Test
-    public void quantize_2R3G() {
-        Map<Integer, Integer> answer =
-                quantizer.quantize(new int[]{RED, RED, GREEN, GREEN, GREEN}, 128).colorToCount;
-
-        assertThat(answer).hasSize(2);
-        assertThat(answer).containsEntry(RED, 2);
-        assertThat(answer).containsEntry(GREEN, 3);
-    }
-
-    @Test
-    public void quantize_1R1G1B() {
-        Map<Integer, Integer> answer =
-                quantizer.quantize(new int[]{RED, GREEN, BLUE}, 128).colorToCount;
-
-        assertThat(answer).hasSize(3);
-        assertThat(answer).containsEntry(RED, 1);
-        assertThat(answer).containsEntry(GREEN, 1);
-        assertThat(answer).containsEntry(BLUE, 1);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/README.md b/packages/SystemUI/tests/src/com/android/systemui/monet/README.md
deleted file mode 100644
index a3e0257..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/README.md
+++ /dev/null
@@ -1,4 +0,0 @@
-The tests in this folder are imported from Google's internal source repository.
-
-For instructions on importing, see:
-  frameworks/base/packages/SystemUI/tests/src/com/android/systemui/monet/README
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeContentTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeContentTest.java
deleted file mode 100644
index 704aed8..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeContentTest.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.scheme.SchemeContent;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@Ignore("b/279581953")
-@SmallTest
-@RunWith(JUnit4.class)
-public final class SchemeContentTest extends SysuiTestCase {
-
-    private final MaterialDynamicColors dynamicColors = new MaterialDynamicColors();
-
-    @Test
-    public void testKeyColors() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xff0000ff), false, 0.0);
-
-        assertThat(dynamicColors.primaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff080CFF);
-        assertThat(dynamicColors.secondaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff656DD3);
-        assertThat(dynamicColors.tertiaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff81009F);
-        assertThat(dynamicColors.neutralPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff767684);
-        assertThat(dynamicColors.neutralVariantPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff757589);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primary() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xFF1218FF);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primary() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xFF0001C3);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primary() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xFF000181);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xFF5660FF);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xFF2D36FF);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xFF0000E3);
-    }
-
-    @Test
-    public void lightTheme_minContrast_tertiaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, -1.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(0xFFB042CC);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_tertiaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 0.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(0xFF9221AF);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_tertiaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 1.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(0xFF73008E);
-    }
-
-    @Test
-    public void lightTheme_minContrast_objectionableTertiaryContainerLightens() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF850096), false, -1.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(0xFFD03A71);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_objectionableTertiaryContainerLightens() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF850096), false, 0.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(0xFFAC1B57);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_objectionableTertiaryContainerDarkens() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF850096), false, 1.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(0xFF870040);
-    }
-
-    @Test
-    public void lightTheme_minContrast_onPrimaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xFFCBCDFF);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_onPrimaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xFFCECFFF);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_onPrimaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xFFD6D6FF);
-    }
-
-    @Test
-    public void lightTheme_minContrast_surface() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, -1);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xFFFBF8FF);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_surface() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xFFFBF8FF);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_surface() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), false, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xFFFBF8FF);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primary() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xFF5660FF);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primary() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xFFBEC2FF);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primary() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xFFF6F4FF);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xFF0000E6);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xFF0000E6);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xFFC4C6FF);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onPrimaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xFF7A83FF);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onPrimaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xFFA4AAFF);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onPrimaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xFF0001C6);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onTertiaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, -1.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xFFCF60EA);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onTertiaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 0.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xFFEB8CFF);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onTertiaryContainer() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 1.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xFF63007B);
-    }
-
-    @Test
-    public void darkTheme_minContrast_surface() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, -1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xFF12121D);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_surface() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xFF12121D);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_surface() {
-        SchemeContent scheme = new SchemeContent(Hct.fromInt(0xFF0000ff), true, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xFF12121D);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeExpressiveTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeExpressiveTest.java
deleted file mode 100644
index 32a589d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeExpressiveTest.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.scheme.SchemeExpressive;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@Ignore("b/279581953")
-@SmallTest
-@RunWith(JUnit4.class)
-public final class SchemeExpressiveTest extends SysuiTestCase {
-
-    private final MaterialDynamicColors dynamicColors = new MaterialDynamicColors();
-
-    @Test
-    public void testKeyColors() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
-
-        assertThat(dynamicColors.primaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff35855F);
-        assertThat(dynamicColors.secondaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff8C6D8C);
-        assertThat(dynamicColors.tertiaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff806EA1);
-        assertThat(dynamicColors.neutralPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff79757F);
-        assertThat(dynamicColors.neutralVariantPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff7A7585);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primary() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xffad603c);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primary() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff924b28);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primary() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff401400);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primaryContainer() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffffdbcc);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primaryContainer() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffffdbcc);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primaryContainer() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff6f3010);
-    }
-
-    @Test
-    public void lightTheme_minContrast_onPrimaryContainer() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff99512e);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_onPrimaryContainer() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff351000);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_onPrimaryContainer() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffffd0bc);
-    }
-
-    @Test
-    public void lightTheme_minContrast_surface() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffbf8ff);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_surface() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffbf8ff);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_surface() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffbf8ff);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primary() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xffad603c);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primary() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xffffb595);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primary() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xfffff3ee);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primaryContainer() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff743413);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primaryContainer() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff743413);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primaryContainer() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffffbb9e);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onPrimaryContainer() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xfff99f75);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onPrimaryContainer() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffffdbcc);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onPrimaryContainer() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff622706);
-    }
-
-    @Test
-    public void darkTheme_minContrast_surface() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff12131a);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_surface() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff12131a);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_surface() {
-        SchemeExpressive scheme = new SchemeExpressive(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff12131a);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFidelityTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFidelityTest.java
deleted file mode 100644
index 6844a92..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFidelityTest.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.scheme.SchemeFidelity;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@Ignore("b/279581953")
-@SmallTest
-@RunWith(JUnit4.class)
-public final class SchemeFidelityTest extends SysuiTestCase {
-
-    private final MaterialDynamicColors dynamicColors = new MaterialDynamicColors();
-
-    @Test
-    public void testKeyColors() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 0.0);
-
-        assertThat(dynamicColors.primaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff080CFF);
-        assertThat(dynamicColors.secondaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff656DD3);
-        assertThat(dynamicColors.tertiaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff9D0002);
-        assertThat(dynamicColors.neutralPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff767684);
-        assertThat(dynamicColors.neutralVariantPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff757589);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primary() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff1218ff);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primary() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff0001c3);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primary() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff000181);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff5660ff);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff2d36ff);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff0000e3);
-    }
-
-    @Test
-    public void lightTheme_minContrast_tertiaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(0xffd93628);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_tertiaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(0xffb31910);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_tertiaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(0xff8c0002);
-    }
-
-    @Test
-    public void lightTheme_minContrast_objectionableTertiaryContainerLightens() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff850096), false, -1.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(0xffbcac5a);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_objectionableTertiaryContainerLightens() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff850096), false, 0.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(0xffbcac5a);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_objectionableTertiaryContainerDarkens() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff850096), false, 1.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(0xff4d4300);
-    }
-
-    @Test
-    public void lightTheme_minContrast_onPrimaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffcbcdff);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_onPrimaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffcecfff);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_onPrimaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffd6d6ff);
-    }
-
-    @Test
-    public void lightTheme_minContrast_surface() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffbf8ff);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_surface() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffbf8ff);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_surface() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffbf8ff);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primary() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff5660ff);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primary() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xffbec2ff);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primary() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xfff6f4ff);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff0000e6);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff0000e6);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffc4c6ff);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onPrimaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff7a83ff);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onPrimaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffa4aaff);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onPrimaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff0001c6);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onTertiaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xfffe513e);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onTertiaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xffFF9181);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onTertiaryContainer() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xff790001);
-    }
-
-    @Test
-    public void darkTheme_minContrast_surface() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff12121d);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_surface() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff12121d);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_surface() {
-        SchemeFidelity scheme = new SchemeFidelity(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff12121d);
-    }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFruitSaladTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFruitSaladTest.java
deleted file mode 100644
index 4bca2c4..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeFruitSaladTest.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.scheme.SchemeFruitSalad;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@Ignore("b/279581953")
-@SmallTest
-@RunWith(JUnit4.class)
-public final class SchemeFruitSaladTest extends SysuiTestCase {
-
-    private final MaterialDynamicColors dynamicColors = new MaterialDynamicColors();
-
-    @Test
-    public void testKeyColors() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 0.0);
-
-        assertThat(dynamicColors.primaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff0091C0);
-        assertThat(dynamicColors.secondaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff3A7E9E);
-        assertThat(dynamicColors.tertiaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff6E72AC);
-        assertThat(dynamicColors.neutralPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff777682);
-        assertThat(dynamicColors.neutralVariantPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff75758B);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primary() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff007ea7);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primary() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff006688);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primary() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff002635);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffC2E8FF);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffC2E8FF);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(
-                0xff004862);
-    }
-
-    @Test
-    public void lightTheme_minContrast_tertiaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffE0E0FF);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_tertiaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffE0E0FF);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_tertiaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(
-                0xFF3A3E74);
-    }
-
-    @Test
-    public void lightTheme_minContrast_onPrimaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(
-                0xff006C90);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_onPrimaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(
-                0xff001E2B);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_onPrimaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffACE1FF);
-    }
-
-    @Test
-    public void lightTheme_minContrast_surface() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffbf8ff);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_surface() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffbf8ff);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_surface() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffbf8ff);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primary() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff007EA7);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primary() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xFF76D1FF);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primary() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xFFECF7FF);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(
-                0xFF004D67);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(
-                0xFF004D67);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(
-                0xFF83D5FF);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onPrimaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(
-                0xff55C0F2);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onPrimaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffC2E8FF);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onPrimaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(
-                0xff003E54);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onTertiaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffADB0EF);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onTertiaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffe0e0ff);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onTertiaryContainer() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(
-                0xff30346A);
-    }
-
-    @Test
-    public void darkTheme_minContrast_surface() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff12131c);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_surface() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff12131c);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_surface() {
-        SchemeFruitSalad scheme = new SchemeFruitSalad(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff12131c);
-    }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeMonochromeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeMonochromeTest.java
deleted file mode 100644
index d511422..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeMonochromeTest.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.scheme.SchemeMonochrome;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@Ignore("b/279581953")
-@SmallTest
-@RunWith(JUnit4.class)
-public final class SchemeMonochromeTest extends SysuiTestCase {
-
-    private final MaterialDynamicColors dynamicColors = new MaterialDynamicColors();
-
-    @Test
-    public void testKeyColors() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 0.0);
-
-        assertThat(dynamicColors.primaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff070707);
-        assertThat(dynamicColors.secondaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff070707);
-        assertThat(dynamicColors.tertiaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff070707);
-        assertThat(dynamicColors.neutralPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff070707);
-        assertThat(dynamicColors.neutralVariantPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff070707);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primary() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, -1);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff3c3c3c);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primary() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff000000);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primary() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 1);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff000000);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primaryContainer() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, -1);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff5f5f5f);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primaryContainer() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff3b3b3b);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primaryContainer() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 1);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff3a3a3a);
-    }
-
-    @Test
-    public void lightTheme_minContrast_onPrimaryContainer() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, -1);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffd9d9d9);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_onPrimaryContainer() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffffffff);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_onPrimaryContainer() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 1);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffcdcdcd);
-    }
-
-    @Test
-    public void lightTheme_minContrast_surface() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, -1);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfff9f9f9);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_surface() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfff9f9f9);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_surface() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 1);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfff9f9f9);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primary() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, -1);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xffcccccc);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primary() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xffffffff);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primary() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 1);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xffffffff);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primaryContainer() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, -1);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffa3a3a3);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primaryContainer() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffd4d4d4);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primaryContainer() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 1);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffd5d5d5);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onPrimaryContainer() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, -1);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff393939);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onPrimaryContainer() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff000000);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onPrimaryContainer() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 1);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff404040);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onTertiaryContainer() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, -1);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xffd1d1d1);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onTertiaryContainer() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xff000000);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onTertiaryContainer() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 1);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xff393939);
-    }
-
-    @Test
-    public void darkTheme_minContrast_surface() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, -1);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff131313);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_surface() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff131313);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_surface() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 1);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff131313);
-    }
-
-    @Test
-    public void darkTheme_monochromeSpec() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primary().getHct(scheme).getTone()).isWithin(1).of(100);
-        assertThat(dynamicColors.onPrimary().getHct(scheme).getTone()).isWithin(1).of(10);
-        assertThat(dynamicColors.primaryContainer().getHct(scheme).getTone()).isWithin(1).of(85);
-        assertThat(dynamicColors.onPrimaryContainer().getHct(scheme).getTone()).isWithin(1).of(0);
-        assertThat(dynamicColors.secondary().getHct(scheme).getTone()).isWithin(1).of(80);
-        assertThat(dynamicColors.onSecondary().getHct(scheme).getTone()).isWithin(1).of(10);
-        assertThat(dynamicColors.secondaryContainer().getHct(scheme).getTone()).isWithin(1).of(30);
-        assertThat(dynamicColors.onSecondaryContainer().getHct(scheme).getTone()).isWithin(1).of(90);
-        assertThat(dynamicColors.tertiary().getHct(scheme).getTone()).isWithin(1).of(90);
-        assertThat(dynamicColors.onTertiary().getHct(scheme).getTone()).isWithin(1).of(10);
-        assertThat(dynamicColors.tertiaryContainer().getHct(scheme).getTone()).isWithin(1).of(60);
-        assertThat(dynamicColors.onTertiaryContainer().getHct(scheme).getTone()).isWithin(1).of(0);
-    }
-
-    @Test
-    public void lightTheme_monochromeSpec() {
-        SchemeMonochrome scheme = new SchemeMonochrome(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primary().getHct(scheme).getTone()).isWithin(1).of(0);
-        assertThat(dynamicColors.onPrimary().getHct(scheme).getTone()).isWithin(1).of(90);
-        assertThat(dynamicColors.primaryContainer().getHct(scheme).getTone()).isWithin(1).of(25);
-        assertThat(dynamicColors.onPrimaryContainer().getHct(scheme).getTone()).isWithin(1).of(100);
-        assertThat(dynamicColors.secondary().getHct(scheme).getTone()).isWithin(1).of(40);
-        assertThat(dynamicColors.onSecondary().getHct(scheme).getTone()).isWithin(1).of(100);
-        assertThat(dynamicColors.secondaryContainer().getHct(scheme).getTone()).isWithin(1).of(85);
-        assertThat(dynamicColors.onSecondaryContainer().getHct(scheme).getTone()).isWithin(1).of(10);
-        assertThat(dynamicColors.tertiary().getHct(scheme).getTone()).isWithin(1).of(25);
-        assertThat(dynamicColors.onTertiary().getHct(scheme).getTone()).isWithin(1).of(90);
-        assertThat(dynamicColors.tertiaryContainer().getHct(scheme).getTone()).isWithin(1).of(49);
-        assertThat(dynamicColors.onTertiaryContainer().getHct(scheme).getTone()).isWithin(1).of(100);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeNeutralTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeNeutralTest.java
deleted file mode 100644
index 4f3fc7d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeNeutralTest.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.scheme.SchemeNeutral;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@Ignore("b/279581953")
-@SmallTest
-@RunWith(JUnit4.class)
-public final class SchemeNeutralTest extends SysuiTestCase {
-
-    private final MaterialDynamicColors dynamicColors = new MaterialDynamicColors();
-
-    @Test
-    public void testKeyColors() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 0.0);
-
-        assertThat(dynamicColors.primaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff767685);
-        assertThat(dynamicColors.secondaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff777680);
-        assertThat(dynamicColors.tertiaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff75758B);
-        assertThat(dynamicColors.neutralPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff787678);
-        assertThat(dynamicColors.neutralVariantPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff787678);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primary() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff737383);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primary() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff5d5d6c);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primary() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff21212e);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primaryContainer() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffe2e1f3);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primaryContainer() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffe2e1f3);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primaryContainer() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff414250);
-    }
-
-    @Test
-    public void lightTheme_minContrast_onPrimaryContainer() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff636372);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_onPrimaryContainer() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff1a1b27);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_onPrimaryContainer() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffd9d8ea);
-    }
-
-    @Test
-    public void lightTheme_minContrast_surface() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffcf8fa);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_surface() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffcf8fa);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_surface() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffcf8fa);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primary() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff737383);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primary() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xffc6c5d6);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primary() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xfff6f4ff);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primaryContainer() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff454654);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primaryContainer() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff454654);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primaryContainer() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffcac9da);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onPrimaryContainer() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffb5b3c4);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onPrimaryContainer() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffe2e1f3);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onPrimaryContainer() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff373846);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onTertiaryContainer() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xffb3b3cb);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onTertiaryContainer() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xffe1e0f9);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onTertiaryContainer() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xff37374b);
-    }
-
-    @Test
-    public void darkTheme_minContrast_surface() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff131315);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_surface() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff131315);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_surface() {
-        SchemeNeutral scheme = new SchemeNeutral(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff131315);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeRainbowTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeRainbowTest.java
deleted file mode 100644
index ece3f9a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeRainbowTest.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.scheme.SchemeRainbow;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@Ignore("b/279581953")
-@SmallTest
-@RunWith(JUnit4.class)
-public final class SchemeRainbowTest extends SysuiTestCase {
-
-    private final MaterialDynamicColors dynamicColors = new MaterialDynamicColors();
-
-    @Test
-    public void testKeyColors() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 0.0);
-
-        assertThat(dynamicColors.primaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff696FC4);
-        assertThat(dynamicColors.secondaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff75758B);
-        assertThat(dynamicColors.tertiaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff936B84);
-        assertThat(dynamicColors.neutralPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff070707);
-        assertThat(dynamicColors.neutralVariantPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff070707);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primary() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff676DC1);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primary() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff5056A9);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primary() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff0F136A);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffE0E0FF);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffE0E0FF);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(
-                0xff34398B);
-    }
-
-    @Test
-    public void lightTheme_minContrast_tertiaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffffd8ee);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_tertiaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffffd8ee);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_tertiaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.tertiaryContainer().getArgb(scheme)).isSameColorAs(
-                0xff5A384E);
-    }
-
-    @Test
-    public void lightTheme_minContrast_onPrimaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(
-                0xff565CB0);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_onPrimaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(
-                0xff050865);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_onPrimaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffd6d6ff);
-    }
-
-    @Test
-    public void lightTheme_minContrast_surface() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfff9f9f9);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_surface() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfff9f9f9);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_surface() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfff9f9f9);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primary() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff676DC1);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primary() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xffbec2ff);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primary() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xfff6f4ff);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(
-                0xff383E8F);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(
-                0xff383E8F);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffc4c6ff);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onPrimaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffa9afff);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onPrimaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffe0e0ff);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onPrimaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(
-                0xff292f81);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onTertiaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffd5a8c3);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onTertiaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(
-                0xffffd8ee);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onTertiaryContainer() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(
-                0xff4f2e44);
-    }
-
-    @Test
-    public void darkTheme_minContrast_surface() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff131313);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_surface() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff131313);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_surface() {
-        SchemeRainbow scheme = new SchemeRainbow(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff131313);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeTest.java
deleted file mode 100644
index 859b65b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeTest.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.scheme.Scheme;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public final class SchemeTest extends SysuiTestCase {
-
-    @Test
-    public void lightScheme_ofBlue() {
-        Scheme scheme = Scheme.light(0xff0000ff);
-        assertThat(scheme.getPrimary()).isSameColorAs(0xff343DFF);
-    }
-
-    @Test
-    public void darkScheme_ofBlue() {
-        Scheme scheme = Scheme.dark(0xff0000ff);
-        assertThat(scheme.getPrimary()).isSameColorAs(0xffBEC2FF);
-    }
-
-    @Test
-    public void thirdPartyScheme_light() {
-        Scheme scheme = Scheme.light(0xff6750A4);
-        assertThat(scheme.getPrimary()).isSameColorAs(0xff6750A4);
-        assertThat(scheme.getSecondary()).isSameColorAs(0xff625B71);
-        assertThat(scheme.getTertiary()).isSameColorAs(0xff7e5260);
-        assertThat(scheme.getSurface()).isSameColorAs(0xfffffbff);
-        assertThat(scheme.getOnSurface()).isSameColorAs(0xff1c1b1e);
-    }
-
-    @Test
-    public void thirdPartyScheme_dark() {
-        Scheme scheme = Scheme.dark(0xff6750A4);
-        assertThat(scheme.getPrimary()).isSameColorAs(0xffcfbcff);
-        assertThat(scheme.getSecondary()).isSameColorAs(0xffcbc2db);
-        assertThat(scheme.getTertiary()).isSameColorAs(0xffefb8c8);
-        assertThat(scheme.getSurface()).isSameColorAs(0xff1c1b1e);
-        assertThat(scheme.getOnSurface()).isSameColorAs(0xffe6e1e6);
-    }
-
-    @Test
-    public void lightSchemeFromHighChromaColor() {
-        Scheme scheme = Scheme.light(0xfffa2bec);
-        assertThat(scheme.getPrimary()).isSameColorAs(0xffab00a2);
-        assertThat(scheme.getOnPrimary()).isSameColorAs(0xffffffff);
-        assertThat(scheme.getPrimaryContainer()).isSameColorAs(0xffffd7f3);
-        assertThat(scheme.getOnPrimaryContainer()).isSameColorAs(0xff390035);
-        assertThat(scheme.getSecondary()).isSameColorAs(0xff6e5868);
-        assertThat(scheme.getOnSecondary()).isSameColorAs(0xffffffff);
-        assertThat(scheme.getSecondaryContainer()).isSameColorAs(0xfff8daee);
-        assertThat(scheme.getOnSecondaryContainer()).isSameColorAs(0xff271624);
-        assertThat(scheme.getTertiary()).isSameColorAs(0xff815343);
-        assertThat(scheme.getOnTertiary()).isSameColorAs(0xffffffff);
-        assertThat(scheme.getTertiaryContainer()).isSameColorAs(0xffffdbd0);
-        assertThat(scheme.getOnTertiaryContainer()).isSameColorAs(0xff321207);
-        assertThat(scheme.getError()).isSameColorAs(0xffba1a1a);
-        assertThat(scheme.getOnError()).isSameColorAs(0xffffffff);
-        assertThat(scheme.getErrorContainer()).isSameColorAs(0xffffdad6);
-        assertThat(scheme.getOnErrorContainer()).isSameColorAs(0xff410002);
-        assertThat(scheme.getBackground()).isSameColorAs(0xfffffbff);
-        assertThat(scheme.getOnBackground()).isSameColorAs(0xff1f1a1d);
-        assertThat(scheme.getSurface()).isSameColorAs(0xfffffbff);
-        assertThat(scheme.getOnSurface()).isSameColorAs(0xff1f1a1d);
-        assertThat(scheme.getSurfaceVariant()).isSameColorAs(0xffeedee7);
-        assertThat(scheme.getOnSurfaceVariant()).isSameColorAs(0xff4e444b);
-        assertThat(scheme.getOutline()).isSameColorAs(0xff80747b);
-        assertThat(scheme.getOutlineVariant()).isSameColorAs(0xffd2c2cb);
-        assertThat(scheme.getShadow()).isSameColorAs(0xff000000);
-        assertThat(scheme.getScrim()).isSameColorAs(0xff000000);
-        assertThat(scheme.getInverseSurface()).isSameColorAs(0xff342f32);
-        assertThat(scheme.getInverseOnSurface()).isSameColorAs(0xfff8eef2);
-        assertThat(scheme.getInversePrimary()).isSameColorAs(0xffffabee);
-    }
-
-    @Test
-    public void darkSchemeFromHighChromaColor() {
-        Scheme scheme = Scheme.dark(0xfffa2bec);
-        assertThat(scheme.getPrimary()).isSameColorAs(0xffffabee);
-        assertThat(scheme.getOnPrimary()).isSameColorAs(0xff5c0057);
-        assertThat(scheme.getPrimaryContainer()).isSameColorAs(0xff83007b);
-        assertThat(scheme.getOnPrimaryContainer()).isSameColorAs(0xffffd7f3);
-        assertThat(scheme.getSecondary()).isSameColorAs(0xffdbbed1);
-        assertThat(scheme.getOnSecondary()).isSameColorAs(0xff3e2a39);
-        assertThat(scheme.getSecondaryContainer()).isSameColorAs(0xff554050);
-        assertThat(scheme.getOnSecondaryContainer()).isSameColorAs(0xfff8daee);
-        assertThat(scheme.getTertiary()).isSameColorAs(0xfff5b9a5);
-        assertThat(scheme.getOnTertiary()).isSameColorAs(0xff4c2619);
-        assertThat(scheme.getTertiaryContainer()).isSameColorAs(0xff663c2d);
-        assertThat(scheme.getOnTertiaryContainer()).isSameColorAs(0xffffdbd0);
-        assertThat(scheme.getError()).isSameColorAs(0xffffb4ab);
-        assertThat(scheme.getOnError()).isSameColorAs(0xff690005);
-        assertThat(scheme.getErrorContainer()).isSameColorAs(0xff93000a);
-        assertThat(scheme.getOnErrorContainer()).isSameColorAs(0xffffb4ab);
-        assertThat(scheme.getBackground()).isSameColorAs(0xff1f1a1d);
-        assertThat(scheme.getOnBackground()).isSameColorAs(0xffeae0e4);
-        assertThat(scheme.getSurface()).isSameColorAs(0xff1f1a1d);
-        assertThat(scheme.getOnSurface()).isSameColorAs(0xffeae0e4);
-        assertThat(scheme.getSurfaceVariant()).isSameColorAs(0xff4e444b);
-        assertThat(scheme.getOnSurfaceVariant()).isSameColorAs(0xffd2c2cb);
-        assertThat(scheme.getOutline()).isSameColorAs(0xff9a8d95);
-        assertThat(scheme.getOutlineVariant()).isSameColorAs(0xff4e444b);
-        assertThat(scheme.getShadow()).isSameColorAs(0xff000000);
-        assertThat(scheme.getScrim()).isSameColorAs(0xff000000);
-        assertThat(scheme.getInverseSurface()).isSameColorAs(0xffeae0e4);
-        assertThat(scheme.getInverseOnSurface()).isSameColorAs(0xff342f32);
-        assertThat(scheme.getInversePrimary()).isSameColorAs(0xffab00a2);
-    }
-
-    @Test
-    public void lightContentSchemeFromHighChromaColor() {
-        Scheme scheme = Scheme.lightContent(0xfffa2bec);
-        assertThat(scheme.getPrimary()).isSameColorAs(0xffab00a2);
-        assertThat(scheme.getOnPrimary()).isSameColorAs(0xffffffff);
-        assertThat(scheme.getPrimaryContainer()).isSameColorAs(0xffffd7f3);
-        assertThat(scheme.getOnPrimaryContainer()).isSameColorAs(0xff390035);
-        assertThat(scheme.getSecondary()).isSameColorAs(0xff7f4e75);
-        assertThat(scheme.getOnSecondary()).isSameColorAs(0xffffffff);
-        assertThat(scheme.getSecondaryContainer()).isSameColorAs(0xffffd7f3);
-        assertThat(scheme.getOnSecondaryContainer()).isSameColorAs(0xff330b2f);
-        assertThat(scheme.getTertiary()).isSameColorAs(0xff9c4323);
-        assertThat(scheme.getOnTertiary()).isSameColorAs(0xffffffff);
-        assertThat(scheme.getTertiaryContainer()).isSameColorAs(0xffffdbd0);
-        assertThat(scheme.getOnTertiaryContainer()).isSameColorAs(0xff390c00);
-        assertThat(scheme.getError()).isSameColorAs(0xffba1a1a);
-        assertThat(scheme.getOnError()).isSameColorAs(0xffffffff);
-        assertThat(scheme.getErrorContainer()).isSameColorAs(0xffffdad6);
-        assertThat(scheme.getOnErrorContainer()).isSameColorAs(0xff410002);
-        assertThat(scheme.getBackground()).isSameColorAs(0xfffffbff);
-        assertThat(scheme.getOnBackground()).isSameColorAs(0xff1f1a1d);
-        assertThat(scheme.getSurface()).isSameColorAs(0xfffffbff);
-        assertThat(scheme.getOnSurface()).isSameColorAs(0xff1f1a1d);
-        assertThat(scheme.getSurfaceVariant()).isSameColorAs(0xffeedee7);
-        assertThat(scheme.getOnSurfaceVariant()).isSameColorAs(0xff4e444b);
-        assertThat(scheme.getOutline()).isSameColorAs(0xff80747b);
-        assertThat(scheme.getOutlineVariant()).isSameColorAs(0xffd2c2cb);
-        assertThat(scheme.getShadow()).isSameColorAs(0xff000000);
-        assertThat(scheme.getScrim()).isSameColorAs(0xff000000);
-        assertThat(scheme.getInverseSurface()).isSameColorAs(0xff342f32);
-        assertThat(scheme.getInverseOnSurface()).isSameColorAs(0xfff8eef2);
-        assertThat(scheme.getInversePrimary()).isSameColorAs(0xffffabee);
-    }
-
-    @Test
-    public void darkContentSchemeFromHighChromaColor() {
-        Scheme scheme = Scheme.darkContent(0xfffa2bec);
-        assertThat(scheme.getPrimary()).isSameColorAs(0xffffabee);
-        assertThat(scheme.getOnPrimary()).isSameColorAs(0xff5c0057);
-        assertThat(scheme.getPrimaryContainer()).isSameColorAs(0xff83007b);
-        assertThat(scheme.getOnPrimaryContainer()).isSameColorAs(0xffffd7f3);
-        assertThat(scheme.getSecondary()).isSameColorAs(0xfff0b4e1);
-        assertThat(scheme.getOnSecondary()).isSameColorAs(0xff4b2145);
-        assertThat(scheme.getSecondaryContainer()).isSameColorAs(0xff64375c);
-        assertThat(scheme.getOnSecondaryContainer()).isSameColorAs(0xffffd7f3);
-        assertThat(scheme.getTertiary()).isSameColorAs(0xffffb59c);
-        assertThat(scheme.getOnTertiary()).isSameColorAs(0xff5c1900);
-        assertThat(scheme.getTertiaryContainer()).isSameColorAs(0xff7d2c0d);
-        assertThat(scheme.getOnTertiaryContainer()).isSameColorAs(0xffffdbd0);
-        assertThat(scheme.getError()).isSameColorAs(0xffffb4ab);
-        assertThat(scheme.getOnError()).isSameColorAs(0xff690005);
-        assertThat(scheme.getErrorContainer()).isSameColorAs(0xff93000a);
-        assertThat(scheme.getOnErrorContainer()).isSameColorAs(0xffffb4ab);
-        assertThat(scheme.getBackground()).isSameColorAs(0xff1f1a1d);
-        assertThat(scheme.getOnBackground()).isSameColorAs(0xffeae0e4);
-        assertThat(scheme.getSurface()).isSameColorAs(0xff1f1a1d);
-        assertThat(scheme.getOnSurface()).isSameColorAs(0xffeae0e4);
-        assertThat(scheme.getSurfaceVariant()).isSameColorAs(0xff4e444b);
-        assertThat(scheme.getOnSurfaceVariant()).isSameColorAs(0xffd2c2cb);
-        assertThat(scheme.getOutline()).isSameColorAs(0xff9a8d95);
-        assertThat(scheme.getOutlineVariant()).isSameColorAs(0xff4e444b);
-        assertThat(scheme.getShadow()).isSameColorAs(0xff000000);
-        assertThat(scheme.getScrim()).isSameColorAs(0xff000000);
-        assertThat(scheme.getInverseSurface()).isSameColorAs(0xffeae0e4);
-        assertThat(scheme.getInverseOnSurface()).isSameColorAs(0xff342f32);
-        assertThat(scheme.getInversePrimary()).isSameColorAs(0xffab00a2);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeTonalSpotTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeTonalSpotTest.java
deleted file mode 100644
index 01d199b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeTonalSpotTest.java
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.scheme.SchemeTonalSpot;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@Ignore("b/279581953")
-@SmallTest
-@RunWith(JUnit4.class)
-public final class SchemeTonalSpotTest extends SysuiTestCase {
-
-    private final MaterialDynamicColors dynamicColors = new MaterialDynamicColors();
-
-    @Test
-    public void testKeyColors() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
-
-        assertThat(dynamicColors.primaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff6E72AC);
-        assertThat(dynamicColors.secondaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff75758B);
-        assertThat(dynamicColors.tertiaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff936B84);
-        assertThat(dynamicColors.neutralPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff78767A);
-        assertThat(dynamicColors.neutralVariantPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff777680);
-    }
-
-
-    @Test
-    public void lightTheme_minContrast_primary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff6a6fb1);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff545999);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff161a59);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primaryContainer() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffe0e0ff);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primaryContainer() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffe0e0ff);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primaryContainer() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff383c7c);
-    }
-
-    @Test
-    public void lightTheme_minContrast_onPrimaryContainer() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff5a5fa0);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_onPrimaryContainer() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff0e1253);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_onPrimaryContainer() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffd6d6ff);
-    }
-
-    @Test
-    public void lightTheme_minContrast_surface() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffbf8ff);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_surface() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffbf8ff);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_surface() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffbf8ff);
-    }
-
-    @Test
-    public void lightTheme_minContrast_onSurface() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.onSurface().getArgb(scheme)).isSameColorAs(0xff5f5e65);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_onSurface() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.onSurface().getArgb(scheme)).isSameColorAs(0xff1b1b21);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_onSurface() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.onSurface().getArgb(scheme)).isSameColorAs(0xff1a1a20);
-    }
-
-    @Test
-    public void lightTheme_minContrast_onSecondary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.onSecondary().getArgb(scheme)).isSameColorAs(0xffcfcfe7);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_onSecondary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.onSecondary().getArgb(scheme)).isSameColorAs(0xffffffff);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_onSecondary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.onSecondary().getArgb(scheme)).isSameColorAs(0xffababc3);
-    }
-
-    @Test
-    public void lightTheme_minContrast_onTertiary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.onTertiary().getArgb(scheme)).isSameColorAs(0xfff3c3df);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_onTertiary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.onTertiary().getArgb(scheme)).isSameColorAs(0xffffffff);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_onTertiary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.onTertiary().getArgb(scheme)).isSameColorAs(0xffcda0bb);
-    }
-
-    @Test
-    public void lightTheme_minContrast_onError() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.onError().getArgb(scheme)).isSameColorAs(0xffffc2bb);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_onError() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.onError().getArgb(scheme)).isSameColorAs(0xffffffff);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_onError() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.onError().getArgb(scheme)).isSameColorAs(0xffff8d80);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff6a6fb1);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xffbec2ff);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xfff6f4ff);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primaryContainer() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff3c4180);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primaryContainer() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff3c4180);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primaryContainer() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffc4c6ff);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onPrimaryContainer() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffabaff7);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onPrimaryContainer() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffe0e0ff);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onPrimaryContainer() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff2e3271);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onTertiaryContainer() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xffd5a8c3);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onTertiaryContainer() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xffffd8ee);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onTertiaryContainer() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xff4f2e44);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onSecondary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onSecondary().getArgb(scheme)).isSameColorAs(0xfffffbff);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onSecondary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onSecondary().getArgb(scheme)).isSameColorAs(0xff2e2f42);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onSecondary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onSecondary().getArgb(scheme)).isSameColorAs(0xff505165);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onTertiary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onTertiary().getArgb(scheme)).isSameColorAs(0xfffffbff);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onTertiary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onTertiary().getArgb(scheme)).isSameColorAs(0xff46263b);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onTertiary() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onTertiary().getArgb(scheme)).isSameColorAs(0xff6b485f);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onError() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onError().getArgb(scheme)).isSameColorAs(0xfffffbff);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onError() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onError().getArgb(scheme)).isSameColorAs(0xff690005);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onError() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onError().getArgb(scheme)).isSameColorAs(0xffa80710);
-    }
-
-    @Test
-    public void darkTheme_minContrast_surface() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff131318);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_surface() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff131318);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_surface() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff131318);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onSurface() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onSurface().getArgb(scheme)).isSameColorAs(0xffa4a2a9);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onSurface() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onSurface().getArgb(scheme)).isSameColorAs(0xffe4e1e9);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onSurface() {
-        SchemeTonalSpot scheme = new SchemeTonalSpot(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onSurface().getArgb(scheme)).isSameColorAs(0xffe5e2ea);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeVibrantTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeVibrantTest.java
deleted file mode 100644
index 0fb53eb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/SchemeVibrantTest.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.scheme.SchemeVibrant;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@Ignore("b/279581953")
-@SmallTest
-@RunWith(JUnit4.class)
-public final class SchemeVibrantTest extends SysuiTestCase {
-
-    private final MaterialDynamicColors dynamicColors = new MaterialDynamicColors();
-
-    @Test
-    public void testKeyColors() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 0.0);
-
-        assertThat(dynamicColors.primaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff080CFF);
-        assertThat(dynamicColors.secondaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff7B7296);
-        assertThat(dynamicColors.tertiaryPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff886C9D);
-        assertThat(dynamicColors.neutralPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff777682);
-        assertThat(dynamicColors.neutralVariantPaletteKeyColor().getArgb(scheme))
-                .isSameColorAs(0xff767685);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primary() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff5660ff);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primary() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff343dff);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primary() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff000181);
-    }
-
-    @Test
-    public void lightTheme_minContrast_primaryContainer() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffe0e0ff);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_primaryContainer() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffe0e0ff);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_primaryContainer() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff0000e3);
-    }
-
-    @Test
-    public void lightTheme_minContrast_onPrimaryContainer() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff3e47ff);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_onPrimaryContainer() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff00006e);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_onPrimaryContainer() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffd6d6ff);
-    }
-
-    @Test
-    public void lightTheme_minContrast_surface() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, -1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffbf8ff);
-    }
-
-    @Test
-    public void lightTheme_standardContrast_surface() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffbf8ff);
-    }
-
-    @Test
-    public void lightTheme_maxContrast_surface() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), false, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xfffbf8ff);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primary() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xff5660ff);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primary() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xffbec2ff);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primary() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.primary().getArgb(scheme)).isSameColorAs(0xfff6f4ff);
-    }
-
-    @Test
-    public void darkTheme_minContrast_primaryContainer() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff0000ef);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_primaryContainer() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xff0000ef);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_primaryContainer() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.primaryContainer().getArgb(scheme)).isSameColorAs(0xffc4c6ff);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onPrimaryContainer() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffa9afff);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onPrimaryContainer() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xffe0e0ff);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onPrimaryContainer() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onPrimaryContainer().getArgb(scheme)).isSameColorAs(0xff0001c6);
-    }
-
-    @Test
-    public void darkTheme_minContrast_onTertiaryContainer() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xffc9a9df);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_onTertiaryContainer() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xfff2daff);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_onTertiaryContainer() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.onTertiaryContainer().getArgb(scheme)).isSameColorAs(0xff472e5b);
-    }
-
-    @Test
-    public void darkTheme_minContrast_surface() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, -1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff12131a);
-    }
-
-    @Test
-    public void darkTheme_standardContrast_surface() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 0.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff12131a);
-    }
-
-    @Test
-    public void darkTheme_maxContrast_surface() {
-        SchemeVibrant scheme = new SchemeVibrant(Hct.fromInt(0xff0000ff), true, 1.0);
-        assertThat(dynamicColors.surface().getArgb(scheme)).isSameColorAs(0xff12131a);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ScoreTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ScoreTest.java
deleted file mode 100644
index 47ad43c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ScoreTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.score.Score;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public final class ScoreTest extends SysuiTestCase {
-
-    @Test
-    public void score_picksMostChromatic() {
-        Map<Integer, Integer> colorsToPopulation = new HashMap<>();
-        colorsToPopulation.put(0xFF0000FF, 1);
-        colorsToPopulation.put(0xFFFF0000, 1);
-        colorsToPopulation.put(0xFF00FF00, 1);
-
-        List<Integer> scores = Score.score(colorsToPopulation);
-
-        assertThat(scores.get(0)).isEqualTo(0xffff0000);
-        assertThat(scores.get(1)).isEqualTo(0xff00ff00);
-        assertThat(scores.get(2)).isEqualTo(0xff0000ff);
-    }
-
-    @Test
-    public void score_usesGblueFallback() {
-        Map<Integer, Integer> colorsToPopulation = new HashMap<>();
-        colorsToPopulation.put(0xFF000000, 1);
-
-        List<Integer> scores = Score.score(colorsToPopulation);
-
-        assertThat(scores).containsExactly(0xff4285f4);
-    }
-
-    @Test
-    public void score_dedupesNearbyHues() {
-        Map<Integer, Integer> colorsToPopulation = new HashMap<>();
-        colorsToPopulation.put(0xff008772, 1);
-        colorsToPopulation.put(0xff318477, 1);
-
-        List<Integer> scores = Score.score(colorsToPopulation);
-
-        assertThat(scores).containsExactly(0xff008772);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/TemperatureCacheTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/TemperatureCacheTest.java
deleted file mode 100644
index c304e92e..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/TemperatureCacheTest.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet;
-
-import static com.android.systemui.monet.utils.ArgbSubject.assertThat;
-
-import static org.junit.Assert.assertEquals;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.monet.hct.Hct;
-import com.android.systemui.monet.temperature.TemperatureCache;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.List;
-
-@SmallTest
-@RunWith(JUnit4.class)
-public final class TemperatureCacheTest extends SysuiTestCase {
-
-    @Test
-    public void testRawTemperature() {
-        final Hct blueHct = Hct.fromInt(0xff0000ff);
-        final double blueTemp = TemperatureCache.rawTemperature(blueHct);
-        assertEquals(-1.393, blueTemp, 0.001);
-
-        final Hct redHct = Hct.fromInt(0xffff0000);
-        final double redTemp = TemperatureCache.rawTemperature(redHct);
-        assertEquals(2.351, redTemp, 0.001);
-
-        final Hct greenHct = Hct.fromInt(0xff00ff00);
-        final double greenTemp = TemperatureCache.rawTemperature(greenHct);
-        assertEquals(-0.267, greenTemp, 0.001);
-
-        final Hct whiteHct = Hct.fromInt(0xffffffff);
-        final double whiteTemp = TemperatureCache.rawTemperature(whiteHct);
-        assertEquals(-0.5, whiteTemp, 0.001);
-
-        final Hct blackHct = Hct.fromInt(0xff000000);
-        final double blackTemp = TemperatureCache.rawTemperature(blackHct);
-        assertEquals(-0.5, blackTemp, 0.001);
-    }
-
-    @Test
-    public void testComplement() {
-        final int blueComplement =
-                new TemperatureCache(Hct.fromInt(0xff0000ff)).getComplement().toInt();
-        assertThat(0xff9D0002).isSameColorAs(blueComplement);
-
-        final int redComplement = new TemperatureCache(
-                Hct.fromInt(0xffff0000)).getComplement().toInt();
-        assertThat(0xff007BFC).isSameColorAs(redComplement);
-
-        final int greenComplement =
-                new TemperatureCache(Hct.fromInt(0xff00ff00)).getComplement().toInt();
-        assertThat(0xffFFD2C9).isSameColorAs(greenComplement);
-
-        final int whiteComplement =
-                new TemperatureCache(Hct.fromInt(0xffffffff)).getComplement().toInt();
-        assertThat(0xffffffff).isSameColorAs(whiteComplement);
-
-        final int blackComplement =
-                new TemperatureCache(Hct.fromInt(0xff000000)).getComplement().toInt();
-        assertThat(0xff000000).isSameColorAs(blackComplement);
-    }
-
-    @Test
-    public void testAnalogous() {
-        final List<Hct> blueAnalogous =
-                new TemperatureCache(Hct.fromInt(0xff0000ff)).getAnalogousColors();
-        assertThat(0xff00590C).isSameColorAs(blueAnalogous.get(0).toInt());
-        assertThat(0xff00564E).isSameColorAs(blueAnalogous.get(1).toInt());
-        assertThat(0xff0000ff).isSameColorAs(blueAnalogous.get(2).toInt());
-        assertThat(0xff6700CC).isSameColorAs(blueAnalogous.get(3).toInt());
-        assertThat(0xff81009F).isSameColorAs(blueAnalogous.get(4).toInt());
-
-        final List<Hct> redAnalogous =
-                new TemperatureCache(Hct.fromInt(0xffff0000)).getAnalogousColors();
-        assertThat(0xffF60082).isSameColorAs(redAnalogous.get(0).toInt());
-        assertThat(0xffFC004C).isSameColorAs(redAnalogous.get(1).toInt());
-        assertThat(0xffff0000).isSameColorAs(redAnalogous.get(2).toInt());
-        assertThat(0xffD95500).isSameColorAs(redAnalogous.get(3).toInt());
-        assertThat(0xffAF7200).isSameColorAs(redAnalogous.get(4).toInt());
-
-        final List<Hct> greenAnalogous =
-                new TemperatureCache(Hct.fromInt(0xff00ff00)).getAnalogousColors();
-        assertThat(0xffCEE900).isSameColorAs(greenAnalogous.get(0).toInt());
-        assertThat(0xff92F500).isSameColorAs(greenAnalogous.get(1).toInt());
-        assertThat(0xff00ff00).isSameColorAs(greenAnalogous.get(2).toInt());
-        assertThat(0xff00FD6F).isSameColorAs(greenAnalogous.get(3).toInt());
-        assertThat(0xff00FAB3).isSameColorAs(greenAnalogous.get(4).toInt());
-
-        final List<Hct> blackAnalogous =
-                new TemperatureCache(Hct.fromInt(0xff000000)).getAnalogousColors();
-        assertThat(0xff000000).isSameColorAs(blackAnalogous.get(0).toInt());
-        assertThat(0xff000000).isSameColorAs(blackAnalogous.get(1).toInt());
-        assertThat(0xff000000).isSameColorAs(blackAnalogous.get(2).toInt());
-        assertThat(0xff000000).isSameColorAs(blackAnalogous.get(3).toInt());
-        assertThat(0xff000000).isSameColorAs(blackAnalogous.get(4).toInt());
-
-        final List<Hct> whiteAnalogous =
-                new TemperatureCache(Hct.fromInt(0xffffffff)).getAnalogousColors();
-        assertThat(0xffffffff).isSameColorAs(whiteAnalogous.get(0).toInt());
-        assertThat(0xffffffff).isSameColorAs(whiteAnalogous.get(1).toInt());
-        assertThat(0xffffffff).isSameColorAs(whiteAnalogous.get(2).toInt());
-        assertThat(0xffffffff).isSameColorAs(whiteAnalogous.get(3).toInt());
-        assertThat(0xffffffff).isSameColorAs(whiteAnalogous.get(4).toInt());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/utils/ArgbSubject.java b/packages/SystemUI/tests/src/com/android/systemui/monet/utils/ArgbSubject.java
deleted file mode 100644
index f3040c8..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/utils/ArgbSubject.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.monet.utils;
-
-import static com.google.common.truth.Truth.assertAbout;
-
-import com.google.common.truth.FailureMetadata;
-import com.google.common.truth.Subject;
-
-/** Enables assertions involving colors as integers to log as hex codes instead of raw integers. */
-public final class ArgbSubject extends Subject {
-
-    public static ArgbSubject assertThat(int argb) {
-        return assertAbout(ArgbSubject::new).that(argb);
-    }
-
-    private final int actual;
-
-    private ArgbSubject(FailureMetadata failureMetadata, int subject) {
-        super(failureMetadata, subject);
-        this.actual = subject;
-    }
-
-    @Override
-    protected String actualCustomStringRepresentation() {
-        return userFriendlyHexCode(actual);
-    }
-
-    public void isSameColorAs(int otherArgb) {
-        if (otherArgb != actual) {
-            failWithActual("color", userFriendlyHexCode(otherArgb));
-        }
-    }
-
-    private static String userFriendlyHexCode(int integer) {
-        String hexString = Integer.toHexString(integer);
-        if (hexString.length() > 6) {
-            return String.format("#%s", hexString.substring(hexString.length() - 6));
-        } else if (hexString.length() == 6) {
-            return String.format("#%s", hexString);
-        } else {
-            return String.format("??? #%s", hexString);
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt
deleted file mode 100644
index ceacaf9..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/data/repository/MultiShadeRepositoryTest.kt
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.data.repository
-
-import android.content.Context
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.multishade.data.model.MultiShadeInteractionModel
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeConfig
-import com.android.systemui.multishade.shared.model.ShadeId
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class MultiShadeRepositoryTest : SysuiTestCase() {
-
-    private lateinit var inputProxy: MultiShadeInputProxy
-
-    @Before
-    fun setUp() {
-        inputProxy = MultiShadeInputProxy()
-    }
-
-    @Test
-    fun proxiedInput() = runTest {
-        val underTest = create()
-        val latest: ProxiedInputModel? by collectLastValue(underTest.proxiedInput)
-
-        assertWithMessage("proxiedInput should start with null").that(latest).isNull()
-
-        inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
-        assertThat(latest).isEqualTo(ProxiedInputModel.OnTap)
-
-        inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 100f))
-        assertThat(latest).isEqualTo(ProxiedInputModel.OnDrag(0f, 100f))
-
-        inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 120f))
-        assertThat(latest).isEqualTo(ProxiedInputModel.OnDrag(0f, 120f))
-
-        inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
-        assertThat(latest).isEqualTo(ProxiedInputModel.OnDragEnd)
-    }
-
-    @Test
-    fun shadeConfig_dualShadeEnabled() = runTest {
-        overrideResource(R.bool.dual_shade_enabled, true)
-        val underTest = create()
-        val shadeConfig: ShadeConfig? by collectLastValue(underTest.shadeConfig)
-
-        assertThat(shadeConfig).isInstanceOf(ShadeConfig.DualShadeConfig::class.java)
-    }
-
-    @Test
-    fun shadeConfig_dualShadeNotEnabled() = runTest {
-        overrideResource(R.bool.dual_shade_enabled, false)
-        val underTest = create()
-        val shadeConfig: ShadeConfig? by collectLastValue(underTest.shadeConfig)
-
-        assertThat(shadeConfig).isInstanceOf(ShadeConfig.SingleShadeConfig::class.java)
-    }
-
-    @Test
-    fun forceCollapseAll() = runTest {
-        val underTest = create()
-        val forceCollapseAll: Boolean? by collectLastValue(underTest.forceCollapseAll)
-
-        assertWithMessage("forceCollapseAll should start as false!")
-            .that(forceCollapseAll)
-            .isFalse()
-
-        underTest.setForceCollapseAll(true)
-        assertThat(forceCollapseAll).isTrue()
-
-        underTest.setForceCollapseAll(false)
-        assertThat(forceCollapseAll).isFalse()
-    }
-
-    @Test
-    fun shadeInteraction() = runTest {
-        val underTest = create()
-        val shadeInteraction: MultiShadeInteractionModel? by
-            collectLastValue(underTest.shadeInteraction)
-
-        assertWithMessage("shadeInteraction should start as null!").that(shadeInteraction).isNull()
-
-        underTest.setShadeInteraction(
-            MultiShadeInteractionModel(shadeId = ShadeId.LEFT, isProxied = false)
-        )
-        assertThat(shadeInteraction)
-            .isEqualTo(MultiShadeInteractionModel(shadeId = ShadeId.LEFT, isProxied = false))
-
-        underTest.setShadeInteraction(
-            MultiShadeInteractionModel(shadeId = ShadeId.RIGHT, isProxied = true)
-        )
-        assertThat(shadeInteraction)
-            .isEqualTo(MultiShadeInteractionModel(shadeId = ShadeId.RIGHT, isProxied = true))
-
-        underTest.setShadeInteraction(null)
-        assertThat(shadeInteraction).isNull()
-    }
-
-    @Test
-    fun expansion() = runTest {
-        val underTest = create()
-        val leftExpansion: Float? by
-            collectLastValue(underTest.getShade(ShadeId.LEFT).map { it.expansion })
-        val rightExpansion: Float? by
-            collectLastValue(underTest.getShade(ShadeId.RIGHT).map { it.expansion })
-        val singleExpansion: Float? by
-            collectLastValue(underTest.getShade(ShadeId.SINGLE).map { it.expansion })
-
-        assertWithMessage("expansion should start as 0!").that(leftExpansion).isZero()
-        assertWithMessage("expansion should start as 0!").that(rightExpansion).isZero()
-        assertWithMessage("expansion should start as 0!").that(singleExpansion).isZero()
-
-        underTest.setExpansion(
-            shadeId = ShadeId.LEFT,
-            0.4f,
-        )
-        assertThat(leftExpansion).isEqualTo(0.4f)
-        assertThat(rightExpansion).isEqualTo(0f)
-        assertThat(singleExpansion).isEqualTo(0f)
-
-        underTest.setExpansion(
-            shadeId = ShadeId.RIGHT,
-            0.73f,
-        )
-        assertThat(leftExpansion).isEqualTo(0.4f)
-        assertThat(rightExpansion).isEqualTo(0.73f)
-        assertThat(singleExpansion).isEqualTo(0f)
-
-        underTest.setExpansion(
-            shadeId = ShadeId.LEFT,
-            0.1f,
-        )
-        underTest.setExpansion(
-            shadeId = ShadeId.SINGLE,
-            0.88f,
-        )
-        assertThat(leftExpansion).isEqualTo(0.1f)
-        assertThat(rightExpansion).isEqualTo(0.73f)
-        assertThat(singleExpansion).isEqualTo(0.88f)
-    }
-
-    private fun create(): MultiShadeRepository {
-        return create(
-            context = context,
-            inputProxy = inputProxy,
-        )
-    }
-
-    companion object {
-        fun create(
-            context: Context,
-            inputProxy: MultiShadeInputProxy,
-        ): MultiShadeRepository {
-            return MultiShadeRepository(
-                applicationContext = context,
-                inputProxy = inputProxy,
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt
deleted file mode 100644
index bcc99bc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.domain.interactor
-
-import android.content.Context
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.data.repository.MultiShadeRepositoryTest
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeId
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class MultiShadeInteractorTest : SysuiTestCase() {
-
-    private lateinit var testScope: TestScope
-    private lateinit var inputProxy: MultiShadeInputProxy
-
-    @Before
-    fun setUp() {
-        testScope = TestScope()
-        inputProxy = MultiShadeInputProxy()
-    }
-
-    @Test
-    fun maxShadeExpansion() =
-        testScope.runTest {
-            val underTest = create()
-            val maxShadeExpansion: Float? by collectLastValue(underTest.maxShadeExpansion)
-            assertWithMessage("maxShadeExpansion must start with 0.0!")
-                .that(maxShadeExpansion)
-                .isEqualTo(0f)
-
-            underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0.441f)
-            assertThat(maxShadeExpansion).isEqualTo(0.441f)
-
-            underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0.442f)
-            assertThat(maxShadeExpansion).isEqualTo(0.442f)
-
-            underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0f)
-            assertThat(maxShadeExpansion).isEqualTo(0.441f)
-
-            underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0f)
-            assertThat(maxShadeExpansion).isEqualTo(0f)
-        }
-
-    @Test
-    fun isAnyShadeExpanded() =
-        testScope.runTest {
-            val underTest = create()
-            val isAnyShadeExpanded: Boolean? by collectLastValue(underTest.isAnyShadeExpanded)
-            assertWithMessage("isAnyShadeExpanded must start with false!")
-                .that(isAnyShadeExpanded)
-                .isFalse()
-
-            underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0.441f)
-            assertThat(isAnyShadeExpanded).isTrue()
-
-            underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0.442f)
-            assertThat(isAnyShadeExpanded).isTrue()
-
-            underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0f)
-            assertThat(isAnyShadeExpanded).isTrue()
-
-            underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0f)
-            assertThat(isAnyShadeExpanded).isFalse()
-        }
-
-    @Test
-    fun isVisible_dualShadeConfig() =
-        testScope.runTest {
-            overrideResource(R.bool.dual_shade_enabled, true)
-            val underTest = create()
-            val isLeftShadeVisible: Boolean? by collectLastValue(underTest.isVisible(ShadeId.LEFT))
-            val isRightShadeVisible: Boolean? by
-                collectLastValue(underTest.isVisible(ShadeId.RIGHT))
-            val isSingleShadeVisible: Boolean? by
-                collectLastValue(underTest.isVisible(ShadeId.SINGLE))
-
-            assertThat(isLeftShadeVisible).isTrue()
-            assertThat(isRightShadeVisible).isTrue()
-            assertThat(isSingleShadeVisible).isFalse()
-        }
-
-    @Test
-    fun isVisible_singleShadeConfig() =
-        testScope.runTest {
-            overrideResource(R.bool.dual_shade_enabled, false)
-            val underTest = create()
-            val isLeftShadeVisible: Boolean? by collectLastValue(underTest.isVisible(ShadeId.LEFT))
-            val isRightShadeVisible: Boolean? by
-                collectLastValue(underTest.isVisible(ShadeId.RIGHT))
-            val isSingleShadeVisible: Boolean? by
-                collectLastValue(underTest.isVisible(ShadeId.SINGLE))
-
-            assertThat(isLeftShadeVisible).isFalse()
-            assertThat(isRightShadeVisible).isFalse()
-            assertThat(isSingleShadeVisible).isTrue()
-        }
-
-    @Test
-    fun isNonProxiedInputAllowed() =
-        testScope.runTest {
-            val underTest = create()
-            val isLeftShadeNonProxiedInputAllowed: Boolean? by
-                collectLastValue(underTest.isNonProxiedInputAllowed(ShadeId.LEFT))
-            assertWithMessage("isNonProxiedInputAllowed should start as true!")
-                .that(isLeftShadeNonProxiedInputAllowed)
-                .isTrue()
-
-            // Need to collect proxied input so the flows become hot as the gesture cancelation code
-            // logic sits in side the proxiedInput flow for each shade.
-            collectLastValue(underTest.proxiedInput(ShadeId.LEFT))
-            collectLastValue(underTest.proxiedInput(ShadeId.RIGHT))
-
-            // Starting a proxied interaction on the LEFT shade disallows non-proxied interaction on
-            // the
-            // same shade.
-            inputProxy.onProxiedInput(
-                ProxiedInputModel.OnDrag(xFraction = 0f, yDragAmountPx = 123f)
-            )
-            assertThat(isLeftShadeNonProxiedInputAllowed).isFalse()
-
-            // Registering the end of the proxied interaction re-allows it.
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
-            assertThat(isLeftShadeNonProxiedInputAllowed).isTrue()
-
-            // Starting a proxied interaction on the RIGHT shade force-collapses the LEFT shade,
-            // disallowing non-proxied input on the LEFT shade.
-            inputProxy.onProxiedInput(
-                ProxiedInputModel.OnDrag(xFraction = 1f, yDragAmountPx = 123f)
-            )
-            assertThat(isLeftShadeNonProxiedInputAllowed).isFalse()
-
-            // Registering the end of the interaction on the RIGHT shade re-allows it.
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
-            assertThat(isLeftShadeNonProxiedInputAllowed).isTrue()
-        }
-
-    @Test
-    fun isForceCollapsed_whenOtherShadeInteractionUnderway() =
-        testScope.runTest {
-            val underTest = create()
-            val isLeftShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
-            val isRightShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
-            val isSingleShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
-
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isLeftShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isRightShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isSingleShadeForceCollapsed)
-                .isFalse()
-
-            // Registering the start of an interaction on the RIGHT shade force-collapses the LEFT
-            // shade.
-            underTest.onUserInteractionStarted(ShadeId.RIGHT)
-            assertThat(isLeftShadeForceCollapsed).isTrue()
-            assertThat(isRightShadeForceCollapsed).isFalse()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-
-            // Registering the end of the interaction on the RIGHT shade re-allows it.
-            underTest.onUserInteractionEnded(ShadeId.RIGHT)
-            assertThat(isLeftShadeForceCollapsed).isFalse()
-            assertThat(isRightShadeForceCollapsed).isFalse()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-
-            // Registering the start of an interaction on the LEFT shade force-collapses the RIGHT
-            // shade.
-            underTest.onUserInteractionStarted(ShadeId.LEFT)
-            assertThat(isLeftShadeForceCollapsed).isFalse()
-            assertThat(isRightShadeForceCollapsed).isTrue()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-
-            // Registering the end of the interaction on the LEFT shade re-allows it.
-            underTest.onUserInteractionEnded(ShadeId.LEFT)
-            assertThat(isLeftShadeForceCollapsed).isFalse()
-            assertThat(isRightShadeForceCollapsed).isFalse()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-        }
-
-    @Test
-    fun collapseAll() =
-        testScope.runTest {
-            val underTest = create()
-            val isLeftShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
-            val isRightShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
-            val isSingleShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
-
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isLeftShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isRightShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isSingleShadeForceCollapsed)
-                .isFalse()
-
-            underTest.collapseAll()
-            assertThat(isLeftShadeForceCollapsed).isTrue()
-            assertThat(isRightShadeForceCollapsed).isTrue()
-            assertThat(isSingleShadeForceCollapsed).isTrue()
-
-            // Receiving proxied input on that's not a tap gesture, on the left-hand side resets the
-            // "collapse all". Note that now the RIGHT shade is force-collapsed because we're
-            // interacting with the LEFT shade.
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0f, 0f))
-            assertThat(isLeftShadeForceCollapsed).isFalse()
-            assertThat(isRightShadeForceCollapsed).isTrue()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-        }
-
-    @Test
-    fun onTapOutside_collapsesAll() =
-        testScope.runTest {
-            val underTest = create()
-            val isLeftShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.LEFT))
-            val isRightShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.RIGHT))
-            val isSingleShadeForceCollapsed: Boolean? by
-                collectLastValue(underTest.isForceCollapsed(ShadeId.SINGLE))
-
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isLeftShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isRightShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isSingleShadeForceCollapsed)
-                .isFalse()
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
-            assertThat(isLeftShadeForceCollapsed).isTrue()
-            assertThat(isRightShadeForceCollapsed).isTrue()
-            assertThat(isSingleShadeForceCollapsed).isTrue()
-        }
-
-    @Test
-    fun proxiedInput_ignoredWhileNonProxiedGestureUnderway() =
-        testScope.runTest {
-            val underTest = create()
-            val proxiedInput: ProxiedInputModel? by
-                collectLastValue(underTest.proxiedInput(ShadeId.RIGHT))
-            underTest.onUserInteractionStarted(shadeId = ShadeId.RIGHT)
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
-            assertThat(proxiedInput).isNull()
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.8f, 110f))
-            assertThat(proxiedInput).isNull()
-
-            underTest.onUserInteractionEnded(shadeId = ShadeId.RIGHT)
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
-            assertThat(proxiedInput).isNotNull()
-        }
-
-    private fun create(): MultiShadeInteractor {
-        return create(
-            testScope = testScope,
-            context = context,
-            inputProxy = inputProxy,
-        )
-    }
-
-    companion object {
-        fun create(
-            testScope: TestScope,
-            context: Context,
-            inputProxy: MultiShadeInputProxy,
-        ): MultiShadeInteractor {
-            return MultiShadeInteractor(
-                applicationScope = testScope.backgroundScope,
-                repository =
-                    MultiShadeRepositoryTest.create(
-                        context = context,
-                        inputProxy = inputProxy,
-                    ),
-                inputProxy = inputProxy,
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
deleted file mode 100644
index 9e54224..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
+++ /dev/null
@@ -1,529 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.multishade.domain.interactor
-
-import android.view.MotionEvent
-import android.view.ViewConfiguration
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingManagerFake
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.data.repository.MultiShadeRepository
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeId
-import com.android.systemui.shade.ShadeController
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.currentTime
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
-
-    private lateinit var underTest: MultiShadeMotionEventInteractor
-
-    private lateinit var testScope: TestScope
-    private lateinit var motionEvents: MutableSet<MotionEvent>
-    private lateinit var repository: MultiShadeRepository
-    private lateinit var interactor: MultiShadeInteractor
-    private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop
-    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
-    private lateinit var falsingManager: FalsingManagerFake
-    @Mock private lateinit var shadeController: ShadeController
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        testScope = TestScope()
-        motionEvents = mutableSetOf()
-
-        val inputProxy = MultiShadeInputProxy()
-        repository =
-            MultiShadeRepository(
-                applicationContext = context,
-                inputProxy = inputProxy,
-            )
-        interactor =
-            MultiShadeInteractor(
-                applicationScope = testScope.backgroundScope,
-                repository = repository,
-                inputProxy = inputProxy,
-            )
-        val featureFlags = FakeFeatureFlags()
-        featureFlags.set(Flags.DUAL_SHADE, true)
-        keyguardTransitionRepository = FakeKeyguardTransitionRepository()
-        falsingManager = FalsingManagerFake()
-
-        underTest =
-            MultiShadeMotionEventInteractor(
-                applicationContext = context,
-                applicationScope = testScope.backgroundScope,
-                multiShadeInteractor = interactor,
-                featureFlags = featureFlags,
-                keyguardTransitionInteractor =
-                    KeyguardTransitionInteractor(
-                        repository = keyguardTransitionRepository,
-                        scope = testScope.backgroundScope
-                    ),
-                falsingManager = falsingManager,
-                shadeController = shadeController,
-            )
-    }
-
-    @After
-    fun tearDown() {
-        motionEvents.forEach { motionEvent -> motionEvent.recycle() }
-    }
-
-    @Test
-    fun listenForIsAnyShadeExpanded_expanded_makesWindowViewVisible() =
-        testScope.runTest {
-            whenever(shadeController.isKeyguard).thenReturn(false)
-            repository.setExpansion(ShadeId.LEFT, 0.1f)
-            val expanded by collectLastValue(interactor.isAnyShadeExpanded)
-            assertThat(expanded).isTrue()
-
-            verify(shadeController).makeExpandedVisible(anyBoolean())
-        }
-
-    @Test
-    fun listenForIsAnyShadeExpanded_collapsed_makesWindowViewInvisible() =
-        testScope.runTest {
-            whenever(shadeController.isKeyguard).thenReturn(false)
-            repository.setForceCollapseAll(true)
-            val expanded by collectLastValue(interactor.isAnyShadeExpanded)
-            assertThat(expanded).isFalse()
-
-            verify(shadeController).makeExpandedInvisible()
-        }
-
-    @Test
-    fun listenForIsAnyShadeExpanded_collapsedOnKeyguard_makesWindowViewVisible() =
-        testScope.runTest {
-            whenever(shadeController.isKeyguard).thenReturn(true)
-            repository.setForceCollapseAll(true)
-            val expanded by collectLastValue(interactor.isAnyShadeExpanded)
-            assertThat(expanded).isFalse()
-
-            verify(shadeController).makeExpandedVisible(anyBoolean())
-        }
-
-    @Test
-    fun shouldIntercept_initialDown_returnsFalse() =
-        testScope.runTest {
-            assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))).isFalse()
-        }
-
-    @Test
-    fun shouldIntercept_moveBelowTouchSlop_returnsFalse() =
-        testScope.runTest {
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            y = touchSlop - 1f,
-                        )
-                    )
-                )
-                .isFalse()
-        }
-
-    @Test
-    fun shouldIntercept_moveAboveTouchSlop_returnsTrue() =
-        testScope.runTest {
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            y = touchSlop + 1f,
-                        )
-                    )
-                )
-                .isTrue()
-        }
-
-    @Test
-    fun shouldIntercept_moveAboveTouchSlop_butHorizontalFirst_returnsFalse() =
-        testScope.runTest {
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            x = touchSlop + 1f,
-                        )
-                    )
-                )
-                .isFalse()
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            y = touchSlop + 1f,
-                        )
-                    )
-                )
-                .isFalse()
-        }
-
-    @Test
-    fun shouldIntercept_up_afterMovedAboveTouchSlop_returnsTrue() =
-        testScope.runTest {
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_MOVE, y = touchSlop + 1f))
-
-            assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))).isTrue()
-        }
-
-    @Test
-    fun shouldIntercept_cancel_afterMovedAboveTouchSlop_returnsTrue() =
-        testScope.runTest {
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_MOVE, y = touchSlop + 1f))
-
-            assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_CANCEL))).isTrue()
-        }
-
-    @Test
-    fun shouldIntercept_moveAboveTouchSlopAndUp_butShadeExpanded_returnsFalse() =
-        testScope.runTest {
-            repository.setExpansion(ShadeId.LEFT, 0.1f)
-            runCurrent()
-
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            y = touchSlop + 1f,
-                        )
-                    )
-                )
-                .isFalse()
-            assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))).isFalse()
-        }
-
-    @Test
-    fun shouldIntercept_moveAboveTouchSlopAndCancel_butShadeExpanded_returnsFalse() =
-        testScope.runTest {
-            repository.setExpansion(ShadeId.LEFT, 0.1f)
-            runCurrent()
-
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            y = touchSlop + 1f,
-                        )
-                    )
-                )
-                .isFalse()
-            assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_CANCEL))).isFalse()
-        }
-
-    @Test
-    fun shouldIntercept_moveAboveTouchSlopAndUp_butBouncerShowing_returnsFalse() =
-        testScope.runTest {
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.PRIMARY_BOUNCER,
-                    value = 0.1f,
-                    transitionState = TransitionState.STARTED,
-                )
-            )
-            runCurrent()
-
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            y = touchSlop + 1f,
-                        )
-                    )
-                )
-                .isFalse()
-            assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))).isFalse()
-        }
-
-    @Test
-    fun shouldIntercept_moveAboveTouchSlopAndCancel_butBouncerShowing_returnsFalse() =
-        testScope.runTest {
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.PRIMARY_BOUNCER,
-                    value = 0.1f,
-                    transitionState = TransitionState.STARTED,
-                )
-            )
-            runCurrent()
-
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-
-            assertThat(
-                    underTest.shouldIntercept(
-                        motionEvent(
-                            MotionEvent.ACTION_MOVE,
-                            y = touchSlop + 1f,
-                        )
-                    )
-                )
-                .isFalse()
-            assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_CANCEL))).isFalse()
-        }
-
-    @Test
-    fun tap_doesNotSendProxiedInput() =
-        testScope.runTest {
-            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
-            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
-            val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
-
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))
-
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-        }
-
-    @Test
-    fun dragBelowTouchSlop_doesNotSendProxiedInput() =
-        testScope.runTest {
-            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
-            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
-            val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
-
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_MOVE, y = touchSlop - 1f))
-            underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))
-
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-        }
-
-    @Test
-    fun dragShadeAboveTouchSlopAndUp() =
-        testScope.runTest {
-            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
-            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
-            val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
-
-            underTest.shouldIntercept(
-                motionEvent(
-                    MotionEvent.ACTION_DOWN,
-                    x = 100f, // left shade
-                )
-            )
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-
-            val yDragAmountPx = touchSlop + 1f
-            val moveEvent =
-                motionEvent(
-                    MotionEvent.ACTION_MOVE,
-                    x = 100f, // left shade
-                    y = yDragAmountPx,
-                )
-            assertThat(underTest.shouldIntercept(moveEvent)).isTrue()
-            underTest.onTouchEvent(moveEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput)
-                .isEqualTo(
-                    ProxiedInputModel.OnDrag(
-                        xFraction = 0.1f,
-                        yDragAmountPx = yDragAmountPx,
-                    )
-                )
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-
-            val upEvent = motionEvent(MotionEvent.ACTION_UP)
-            assertThat(underTest.shouldIntercept(upEvent)).isTrue()
-            underTest.onTouchEvent(upEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-        }
-
-    @Test
-    fun dragShadeAboveTouchSlopAndCancel() =
-        testScope.runTest {
-            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
-            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
-            val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
-
-            underTest.shouldIntercept(
-                motionEvent(
-                    MotionEvent.ACTION_DOWN,
-                    x = 900f, // right shade
-                )
-            )
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-
-            val yDragAmountPx = touchSlop + 1f
-            val moveEvent =
-                motionEvent(
-                    MotionEvent.ACTION_MOVE,
-                    x = 900f, // right shade
-                    y = yDragAmountPx,
-                )
-            assertThat(underTest.shouldIntercept(moveEvent)).isTrue()
-            underTest.onTouchEvent(moveEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput)
-                .isEqualTo(
-                    ProxiedInputModel.OnDrag(
-                        xFraction = 0.9f,
-                        yDragAmountPx = yDragAmountPx,
-                    )
-                )
-            assertThat(singleShadeProxiedInput).isNull()
-
-            val cancelEvent = motionEvent(MotionEvent.ACTION_CANCEL)
-            assertThat(underTest.shouldIntercept(cancelEvent)).isTrue()
-            underTest.onTouchEvent(cancelEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-        }
-
-    @Test
-    fun dragUp_withUp_doesNotShowShade() =
-        testScope.runTest {
-            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
-            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
-            val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
-
-            underTest.shouldIntercept(
-                motionEvent(
-                    MotionEvent.ACTION_DOWN,
-                    x = 100f, // left shade
-                )
-            )
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-
-            val yDragAmountPx = -(touchSlop + 1f) // dragging up
-            val moveEvent =
-                motionEvent(
-                    MotionEvent.ACTION_MOVE,
-                    x = 100f, // left shade
-                    y = yDragAmountPx,
-                )
-            assertThat(underTest.shouldIntercept(moveEvent)).isFalse()
-            underTest.onTouchEvent(moveEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-
-            val upEvent = motionEvent(MotionEvent.ACTION_UP)
-            assertThat(underTest.shouldIntercept(upEvent)).isFalse()
-            underTest.onTouchEvent(upEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-        }
-
-    @Test
-    fun dragUp_withCancel_falseTouch_showsThenHidesBouncer() =
-        testScope.runTest {
-            val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT))
-            val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT))
-            val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE))
-
-            underTest.shouldIntercept(
-                motionEvent(
-                    MotionEvent.ACTION_DOWN,
-                    x = 900f, // right shade
-                )
-            )
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-
-            val yDragAmountPx = -(touchSlop + 1f) // drag up
-            val moveEvent =
-                motionEvent(
-                    MotionEvent.ACTION_MOVE,
-                    x = 900f, // right shade
-                    y = yDragAmountPx,
-                )
-            assertThat(underTest.shouldIntercept(moveEvent)).isFalse()
-            underTest.onTouchEvent(moveEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-
-            falsingManager.setIsFalseTouch(true)
-            val cancelEvent = motionEvent(MotionEvent.ACTION_CANCEL)
-            assertThat(underTest.shouldIntercept(cancelEvent)).isFalse()
-            underTest.onTouchEvent(cancelEvent, viewWidthPx = 1000)
-            assertThat(leftShadeProxiedInput).isNull()
-            assertThat(rightShadeProxiedInput).isNull()
-            assertThat(singleShadeProxiedInput).isNull()
-        }
-
-    private fun TestScope.motionEvent(
-        action: Int,
-        downTime: Long = currentTime,
-        eventTime: Long = currentTime,
-        x: Float = 0f,
-        y: Float = 0f,
-    ): MotionEvent {
-        val motionEvent = MotionEvent.obtain(downTime, eventTime, action, x, y, 0)
-        motionEvents.add(motionEvent)
-        return motionEvent
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/shared/math/MathTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/shared/math/MathTest.kt
deleted file mode 100644
index 8935309..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/shared/math/MathTest.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.multishade.shared.math
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@SmallTest
-@RunWith(JUnit4::class)
-class MathTest : SysuiTestCase() {
-
-    @Test
-    fun isZero_zero_true() {
-        assertThat(0f.isZero(epsilon = EPSILON)).isTrue()
-    }
-
-    @Test
-    fun isZero_belowPositiveEpsilon_true() {
-        assertThat((EPSILON * 0.999999f).isZero(epsilon = EPSILON)).isTrue()
-    }
-
-    @Test
-    fun isZero_aboveNegativeEpsilon_true() {
-        assertThat((EPSILON * -0.999999f).isZero(epsilon = EPSILON)).isTrue()
-    }
-
-    @Test
-    fun isZero_positiveEpsilon_false() {
-        assertThat(EPSILON.isZero(epsilon = EPSILON)).isFalse()
-    }
-
-    @Test
-    fun isZero_negativeEpsilon_false() {
-        assertThat((-EPSILON).isZero(epsilon = EPSILON)).isFalse()
-    }
-
-    @Test
-    fun isZero_positive_false() {
-        assertThat(1f.isZero(epsilon = EPSILON)).isFalse()
-    }
-
-    @Test
-    fun isZero_negative_false() {
-        assertThat((-1f).isZero(epsilon = EPSILON)).isFalse()
-    }
-
-    companion object {
-        private const val EPSILON = 0.0001f
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt
deleted file mode 100644
index 0484515..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModelTest.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.ui.viewmodel
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractorTest
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class MultiShadeViewModelTest : SysuiTestCase() {
-
-    private lateinit var testScope: TestScope
-    private lateinit var inputProxy: MultiShadeInputProxy
-
-    @Before
-    fun setUp() {
-        testScope = TestScope()
-        inputProxy = MultiShadeInputProxy()
-    }
-
-    @Test
-    fun scrim_whenDualShadeCollapsed() =
-        testScope.runTest {
-            val alpha = 0.5f
-            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
-            overrideResource(R.bool.dual_shade_enabled, true)
-
-            val underTest = create()
-            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
-            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
-
-            assertThat(scrimAlpha).isZero()
-            assertThat(isScrimEnabled).isFalse()
-        }
-
-    @Test
-    fun scrim_whenDualShadeExpanded() =
-        testScope.runTest {
-            val alpha = 0.5f
-            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
-            overrideResource(R.bool.dual_shade_enabled, true)
-            val underTest = create()
-            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
-            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
-            assertThat(scrimAlpha).isZero()
-            assertThat(isScrimEnabled).isFalse()
-
-            underTest.leftShade.onExpansionChanged(0.5f)
-            assertThat(scrimAlpha).isEqualTo(alpha * 0.5f)
-            assertThat(isScrimEnabled).isTrue()
-
-            underTest.rightShade.onExpansionChanged(1f)
-            assertThat(scrimAlpha).isEqualTo(alpha * 1f)
-            assertThat(isScrimEnabled).isTrue()
-        }
-
-    @Test
-    fun scrim_whenSingleShadeCollapsed() =
-        testScope.runTest {
-            val alpha = 0.5f
-            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
-            overrideResource(R.bool.dual_shade_enabled, false)
-
-            val underTest = create()
-            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
-            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
-
-            assertThat(scrimAlpha).isZero()
-            assertThat(isScrimEnabled).isFalse()
-        }
-
-    @Test
-    fun scrim_whenSingleShadeExpanded() =
-        testScope.runTest {
-            val alpha = 0.5f
-            overrideResource(R.dimen.dual_shade_scrim_alpha, alpha)
-            overrideResource(R.bool.dual_shade_enabled, false)
-            val underTest = create()
-            val scrimAlpha: Float? by collectLastValue(underTest.scrimAlpha)
-            val isScrimEnabled: Boolean? by collectLastValue(underTest.isScrimEnabled)
-
-            underTest.singleShade.onExpansionChanged(0.95f)
-
-            assertThat(scrimAlpha).isZero()
-            assertThat(isScrimEnabled).isFalse()
-        }
-
-    private fun create(): MultiShadeViewModel {
-        return MultiShadeViewModel(
-            viewModelScope = testScope.backgroundScope,
-            interactor =
-                MultiShadeInteractorTest.create(
-                    testScope = testScope,
-                    context = context,
-                    inputProxy = inputProxy,
-                ),
-        )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt
deleted file mode 100644
index e32aac5..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/ui/viewmodel/ShadeViewModelTest.kt
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.multishade.ui.viewmodel
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractorTest
-import com.android.systemui.multishade.shared.model.ProxiedInputModel
-import com.android.systemui.multishade.shared.model.ShadeId
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class ShadeViewModelTest : SysuiTestCase() {
-
-    private lateinit var testScope: TestScope
-    private lateinit var inputProxy: MultiShadeInputProxy
-    private var interactor: MultiShadeInteractor? = null
-
-    @Before
-    fun setUp() {
-        testScope = TestScope()
-        inputProxy = MultiShadeInputProxy()
-    }
-
-    @Test
-    fun isVisible_dualShadeConfig() =
-        testScope.runTest {
-            overrideResource(R.bool.dual_shade_enabled, true)
-            val isLeftShadeVisible: Boolean? by collectLastValue(create(ShadeId.LEFT).isVisible)
-            val isRightShadeVisible: Boolean? by collectLastValue(create(ShadeId.RIGHT).isVisible)
-            val isSingleShadeVisible: Boolean? by collectLastValue(create(ShadeId.SINGLE).isVisible)
-
-            assertThat(isLeftShadeVisible).isTrue()
-            assertThat(isRightShadeVisible).isTrue()
-            assertThat(isSingleShadeVisible).isFalse()
-        }
-
-    @Test
-    fun isVisible_singleShadeConfig() =
-        testScope.runTest {
-            overrideResource(R.bool.dual_shade_enabled, false)
-            val isLeftShadeVisible: Boolean? by collectLastValue(create(ShadeId.LEFT).isVisible)
-            val isRightShadeVisible: Boolean? by collectLastValue(create(ShadeId.RIGHT).isVisible)
-            val isSingleShadeVisible: Boolean? by collectLastValue(create(ShadeId.SINGLE).isVisible)
-
-            assertThat(isLeftShadeVisible).isFalse()
-            assertThat(isRightShadeVisible).isFalse()
-            assertThat(isSingleShadeVisible).isTrue()
-        }
-
-    @Test
-    fun isSwipingEnabled() =
-        testScope.runTest {
-            val underTest = create(ShadeId.LEFT)
-            val isSwipingEnabled: Boolean? by collectLastValue(underTest.isSwipingEnabled)
-            assertWithMessage("isSwipingEnabled should start as true!")
-                .that(isSwipingEnabled)
-                .isTrue()
-
-            // Need to collect proxied input so the flows become hot as the gesture cancelation code
-            // logic sits in side the proxiedInput flow for each shade.
-            collectLastValue(underTest.proxiedInput)
-            collectLastValue(create(ShadeId.RIGHT).proxiedInput)
-
-            // Starting a proxied interaction on the LEFT shade disallows non-proxied interaction on
-            // the
-            // same shade.
-            inputProxy.onProxiedInput(
-                ProxiedInputModel.OnDrag(xFraction = 0f, yDragAmountPx = 123f)
-            )
-            assertThat(isSwipingEnabled).isFalse()
-
-            // Registering the end of the proxied interaction re-allows it.
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
-            assertThat(isSwipingEnabled).isTrue()
-
-            // Starting a proxied interaction on the RIGHT shade force-collapses the LEFT shade,
-            // disallowing non-proxied input on the LEFT shade.
-            inputProxy.onProxiedInput(
-                ProxiedInputModel.OnDrag(xFraction = 1f, yDragAmountPx = 123f)
-            )
-            assertThat(isSwipingEnabled).isFalse()
-
-            // Registering the end of the interaction on the RIGHT shade re-allows it.
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDragEnd)
-            assertThat(isSwipingEnabled).isTrue()
-        }
-
-    @Test
-    fun isForceCollapsed_whenOtherShadeInteractionUnderway() =
-        testScope.runTest {
-            val leftShade = create(ShadeId.LEFT)
-            val rightShade = create(ShadeId.RIGHT)
-            val isLeftShadeForceCollapsed: Boolean? by collectLastValue(leftShade.isForceCollapsed)
-            val isRightShadeForceCollapsed: Boolean? by
-                collectLastValue(rightShade.isForceCollapsed)
-            val isSingleShadeForceCollapsed: Boolean? by
-                collectLastValue(create(ShadeId.SINGLE).isForceCollapsed)
-
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isLeftShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isRightShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isSingleShadeForceCollapsed)
-                .isFalse()
-
-            // Registering the start of an interaction on the RIGHT shade force-collapses the LEFT
-            // shade.
-            rightShade.onDragStarted()
-            assertThat(isLeftShadeForceCollapsed).isTrue()
-            assertThat(isRightShadeForceCollapsed).isFalse()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-
-            // Registering the end of the interaction on the RIGHT shade re-allows it.
-            rightShade.onDragEnded()
-            assertThat(isLeftShadeForceCollapsed).isFalse()
-            assertThat(isRightShadeForceCollapsed).isFalse()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-
-            // Registering the start of an interaction on the LEFT shade force-collapses the RIGHT
-            // shade.
-            leftShade.onDragStarted()
-            assertThat(isLeftShadeForceCollapsed).isFalse()
-            assertThat(isRightShadeForceCollapsed).isTrue()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-
-            // Registering the end of the interaction on the LEFT shade re-allows it.
-            leftShade.onDragEnded()
-            assertThat(isLeftShadeForceCollapsed).isFalse()
-            assertThat(isRightShadeForceCollapsed).isFalse()
-            assertThat(isSingleShadeForceCollapsed).isFalse()
-        }
-
-    @Test
-    fun onTapOutside_collapsesAll() =
-        testScope.runTest {
-            val isLeftShadeForceCollapsed: Boolean? by
-                collectLastValue(create(ShadeId.LEFT).isForceCollapsed)
-            val isRightShadeForceCollapsed: Boolean? by
-                collectLastValue(create(ShadeId.RIGHT).isForceCollapsed)
-            val isSingleShadeForceCollapsed: Boolean? by
-                collectLastValue(create(ShadeId.SINGLE).isForceCollapsed)
-
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isLeftShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isRightShadeForceCollapsed)
-                .isFalse()
-            assertWithMessage("isForceCollapsed should start as false!")
-                .that(isSingleShadeForceCollapsed)
-                .isFalse()
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnTap)
-            assertThat(isLeftShadeForceCollapsed).isTrue()
-            assertThat(isRightShadeForceCollapsed).isTrue()
-            assertThat(isSingleShadeForceCollapsed).isTrue()
-        }
-
-    @Test
-    fun proxiedInput_ignoredWhileNonProxiedGestureUnderway() =
-        testScope.runTest {
-            val underTest = create(ShadeId.RIGHT)
-            val proxiedInput: ProxiedInputModel? by collectLastValue(underTest.proxiedInput)
-            underTest.onDragStarted()
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
-            assertThat(proxiedInput).isNull()
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.8f, 110f))
-            assertThat(proxiedInput).isNull()
-
-            underTest.onDragEnded()
-
-            inputProxy.onProxiedInput(ProxiedInputModel.OnDrag(0.9f, 100f))
-            assertThat(proxiedInput).isNotNull()
-        }
-
-    private fun create(
-        shadeId: ShadeId,
-    ): ShadeViewModel {
-        return ShadeViewModel(
-            viewModelScope = testScope.backgroundScope,
-            shadeId = shadeId,
-            interactor = interactor
-                    ?: MultiShadeInteractorTest.create(
-                            testScope = testScope,
-                            context = context,
-                            inputProxy = inputProxy,
-                        )
-                        .also { interactor = it },
-        )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 9b8605d..8d306cce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -52,6 +52,7 @@
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -112,6 +113,9 @@
     EdgeBackGestureHandler mEdgeBackGestureHandler;
     @Mock
     EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
+    @Mock
+    NotificationShadeWindowController mNotificationShadeWindowController;
+
     private AccessibilityManager.AccessibilityServicesStateChangeListener
             mAccessibilityServicesStateChangeListener;
 
@@ -136,7 +140,7 @@
                 mSystemActions, mOverviewProxyService, mAssistManagerLazy,
                 () -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
                 mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker,
-                mDisplayTracker, mDumpManager, mCommandQueue);
+                mDisplayTracker, mNotificationShadeWindowController, mDumpManager, mCommandQueue);
 
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index f062ec7..cbfad56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -94,11 +94,13 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -209,6 +211,8 @@
     private EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
     @Mock
     private EdgeBackGestureHandler mEdgeBackGestureHandler;
+    @Mock
+    private NotificationShadeWindowController mNotificationShadeWindowController;
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
     private TaskStackChangeListeners mTaskStackChangeListeners =
@@ -256,7 +260,8 @@
                     () -> mock(AssistManager.class), () -> Optional.of(mCentralSurfaces),
                     mKeyguardStateController, mock(NavigationModeController.class),
                     mEdgeBackGestureHandlerFactory, mock(IWindowManager.class),
-                    mock(UserTracker.class), mock(DisplayTracker.class), mock(DumpManager.class),
+                    mock(UserTracker.class), mock(DisplayTracker.class),
+                    mNotificationShadeWindowController, mock(DumpManager.class),
                     mock(CommandQueue.class)));
             mNavigationBar = createNavBar(mContext);
             mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
@@ -339,7 +344,6 @@
         NotificationShadeWindowView mockShadeWindowView = mock(NotificationShadeWindowView.class);
         WindowInsets windowInsets = new WindowInsets.Builder().setVisible(ime(), false).build();
         doReturn(windowInsets).when(mockShadeWindowView).getRootWindowInsets();
-        doReturn(mockShadeWindowView).when(mCentralSurfaces).getNotificationShadeWindowView();
         doReturn(true).when(mockShadeWindowView).isAttachedToWindow();
         doNothing().when(defaultNavBar).checkNavBarModes();
         doNothing().when(externalNavBar).checkNavBarModes();
@@ -375,7 +379,7 @@
     @Test
     public void testSetImeWindowStatusWhenKeyguardLockingAndImeInsetsChange() {
         NotificationShadeWindowView mockShadeWindowView = mock(NotificationShadeWindowView.class);
-        doReturn(mockShadeWindowView).when(mCentralSurfaces).getNotificationShadeWindowView();
+        doReturn(mockShadeWindowView).when(mNotificationShadeWindowController).getWindowRootView();
         doReturn(true).when(mockShadeWindowView).isAttachedToWindow();
         doNothing().when(mNavigationBar).checkNavBarModes();
         mNavigationBar.init();
@@ -464,6 +468,7 @@
         when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
         return spy(new NavigationBar(
                 mNavigationBarView,
+                mock(ShadeController.class),
                 mNavigationBarFrame,
                 null,
                 context,
@@ -482,7 +487,7 @@
                 Optional.of(mock(Pip.class)),
                 Optional.of(mock(Recents.class)),
                 () -> Optional.of(mCentralSurfaces),
-                mock(ShadeController.class),
+                mock(ShadeViewController.class),
                 mock(NotificationRemoteInputManager.class),
                 mock(NotificationShadeDepthController.class),
                 mHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index 611c5b9..2d3dc58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -20,6 +20,7 @@
 import android.os.Looper
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
 import android.view.MotionEvent.ACTION_DOWN
 import android.view.MotionEvent.ACTION_MOVE
@@ -29,6 +30,8 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.plugins.NavigationEdgeBackPlugin
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -36,6 +39,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.verify
@@ -58,6 +63,7 @@
     @Mock private lateinit var latencyTracker: LatencyTracker
     @Mock private lateinit var layoutParams: WindowManager.LayoutParams
     @Mock private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback
+    private val featureFlags = FakeFeatureFlags()
 
     @Before
     fun setup() {
@@ -67,10 +73,11 @@
                 context,
                 windowManager,
                 ViewConfiguration.get(context),
-                Handler.createAsync(Looper.myLooper()),
+                Handler.createAsync(checkNotNull(Looper.myLooper())),
                 vibratorHelper,
                 configurationController,
-                latencyTracker
+                latencyTracker,
+                featureFlags
             )
         mBackPanelController.setLayoutParams(layoutParams)
         mBackPanelController.setBackCallback(backCallback)
@@ -99,6 +106,7 @@
 
     @Test
     fun handlesBackCommitted() {
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
         startTouch()
         // Move once to cross the touch slop
         continueTouch(START_X + touchSlop.toFloat() + 1)
@@ -122,7 +130,34 @@
     }
 
     @Test
+    fun handlesBackCommitted_withOneWayHapticsAPI() {
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+        startTouch()
+        // Move once to cross the touch slop
+        continueTouch(START_X + touchSlop.toFloat() + 1)
+        // Move again to cross the back trigger threshold
+        continueTouch(START_X + touchSlop + triggerThreshold + 1)
+        // Wait threshold duration and hold touch past trigger threshold
+        Thread.sleep((MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION + 1).toLong())
+        continueTouch(START_X + touchSlop + triggerThreshold + 1)
+
+        assertThat(mBackPanelController.currentState)
+            .isEqualTo(BackPanelController.GestureState.ACTIVE)
+        verify(backCallback).setTriggerBack(true)
+        testableLooper.moveTimeForward(100)
+        testableLooper.processAllMessages()
+        verify(vibratorHelper)
+            .performHapticFeedback(any(), eq(HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE))
+
+        finishTouchActionUp(START_X + touchSlop + triggerThreshold + 1)
+        assertThat(mBackPanelController.currentState)
+            .isEqualTo(BackPanelController.GestureState.COMMITTED)
+        verify(backCallback).triggerBack()
+    }
+
+    @Test
     fun handlesBackCancelled() {
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
         startTouch()
         // Move once to cross the touch slop
         continueTouch(START_X + touchSlop.toFloat() + 1)
@@ -151,6 +186,38 @@
         verify(backCallback).cancelBack()
     }
 
+    @Test
+    fun handlesBackCancelled_withOneWayHapticsAPI() {
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+        startTouch()
+        // Move once to cross the touch slop
+        continueTouch(START_X + touchSlop.toFloat() + 1)
+        // Move again to cross the back trigger threshold
+        continueTouch(
+            START_X + touchSlop + triggerThreshold -
+                mBackPanelController.params.deactivationTriggerThreshold
+        )
+        // Wait threshold duration and hold touch before trigger threshold
+        Thread.sleep((MAX_DURATION_ENTRY_BEFORE_ACTIVE_ANIMATION + 1).toLong())
+        continueTouch(
+            START_X + touchSlop + triggerThreshold -
+                mBackPanelController.params.deactivationTriggerThreshold
+        )
+        clearInvocations(backCallback)
+        Thread.sleep(MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION)
+        // Move in the opposite direction to cross the deactivation threshold and cancel back
+        continueTouch(START_X)
+
+        assertThat(mBackPanelController.currentState)
+            .isEqualTo(BackPanelController.GestureState.INACTIVE)
+        verify(backCallback).setTriggerBack(false)
+        verify(vibratorHelper)
+            .performHapticFeedback(any(), eq(HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE))
+
+        finishTouchActionUp(START_X)
+        verify(backCallback).cancelBack()
+    }
+
     private fun startTouch() {
         mBackPanelController.onMotionEvent(createMotionEvent(ACTION_DOWN, START_X, 0f))
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt
new file mode 100644
index 0000000..450aadd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.content.Context
+import android.content.Intent
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
+import kotlinx.coroutines.CoroutineDispatcher
+
+/**
+ * Fake for [NoteTaskBubblesController] as mocking suspending functions is not supported in the
+ * Android tree's version of mockito. Ideally the [NoteTaskBubblesController] should be implemented
+ * using an interface for effectively providing multiple implementations but as this fake primarily
+ * for dealing with old version of mockito there isn't any benefit in adding complexity.
+ */
+class FakeNoteTaskBubbleController(
+    unUsed1: Context,
+    unsUsed2: CoroutineDispatcher,
+    private val optionalBubbles: Optional<Bubbles>
+) : NoteTaskBubblesController(unUsed1, unsUsed2) {
+    override suspend fun areBubblesAvailable() = optionalBubbles.isPresent
+
+    override suspend fun showOrHideAppBubble(intent: Intent, userHandle: UserHandle, icon: Icon) {
+        optionalBubbles.ifPresentOrElse(
+            { bubbles -> bubbles.showOrHideAppBubble(intent, userHandle, icon) },
+            { throw IllegalAccessException() }
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
index 36b913f..bdb095a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt
@@ -22,9 +22,9 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.Companion.ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE
 import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
 import com.android.systemui.util.mockito.any
@@ -47,13 +47,9 @@
     @Rule
     @JvmField
     val activityRule =
-        ActivityTestRule<LaunchNotesRoleSettingsTrampolineActivity>(
-            /* activityFactory= */ object :
-                SingleActivityFactory<LaunchNotesRoleSettingsTrampolineActivity>(
-                    LaunchNotesRoleSettingsTrampolineActivity::class.java
-                ) {
-                override fun create(intent: Intent?) =
-                    LaunchNotesRoleSettingsTrampolineActivity(noteTaskController)
+        ActivityTestRule(
+            /* activityFactory= */ SingleActivityFactory {
+                LaunchNotesRoleSettingsTrampolineActivity(noteTaskController)
             },
             /* initialTouchMode= */ false,
             /* launchActivity= */ false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
new file mode 100644
index 0000000..baac9e0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.content.Intent
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskBubblesController.NoteTaskBubblesService
+import com.android.wm.shell.bubbles.Bubbles
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/** atest SystemUITests:NoteTaskBubblesServiceTest */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskBubblesServiceTest : SysuiTestCase() {
+
+    @Mock private lateinit var bubbles: Bubbles
+
+    private fun createServiceBinder(bubbles: Bubbles? = this.bubbles) =
+        NoteTaskBubblesService(Optional.ofNullable(bubbles)).onBind(Intent())
+            as INoteTaskBubblesService
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun areBubblesAvailable_bubblesNotNull_shouldReturnTrue() {
+        assertThat(createServiceBinder().areBubblesAvailable()).isTrue()
+    }
+
+    @Test
+    fun areBubblesAvailable_bubblesNull_shouldReturnFalse() {
+        assertThat(createServiceBinder(bubbles = null).areBubblesAvailable()).isFalse()
+    }
+
+    @Test
+    fun showOrHideAppBubble() {
+        val intent = Intent()
+        val user = UserHandle.SYSTEM
+        val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
+
+        createServiceBinder().showOrHideAppBubble(intent, user, icon)
+
+        verify(bubbles).showOrHideAppBubble(intent, user, icon)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 0954f6f..1536c17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -36,12 +36,12 @@
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
 import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED
-import android.content.pm.ShortcutInfo
 import android.content.pm.ShortcutManager
 import android.content.pm.UserInfo
 import android.graphics.drawable.Icon
 import android.os.UserHandle
 import android.os.UserManager
+import android.provider.Settings
 import androidx.test.ext.truth.content.IntentSubject.assertThat
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
@@ -56,17 +56,23 @@
 import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
-import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.SecureSettings
 import com.android.wm.shell.bubbles.Bubble
 import com.android.wm.shell.bubbles.Bubbles
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
+import kotlin.test.assertNotNull
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -81,6 +87,7 @@
 import org.mockito.MockitoAnnotations
 
 /** atest SystemUITests:NoteTaskControllerTest */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 internal class NoteTaskControllerTest : SysuiTestCase() {
@@ -98,7 +105,10 @@
     @Mock private lateinit var shortcutManager: ShortcutManager
     @Mock private lateinit var activityManager: ActivityManager
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var secureSettings: SecureSettings
     private val userTracker = FakeUserTracker()
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
 
     @Before
     fun setUp() {
@@ -106,7 +116,12 @@
 
         whenever(context.getString(R.string.note_task_button_label))
             .thenReturn(NOTE_TASK_SHORT_LABEL)
+        whenever(context.getString(eq(R.string.note_task_shortcut_long_label), any()))
+            .thenReturn(NOTE_TASK_LONG_LABEL)
         whenever(context.packageManager).thenReturn(packageManager)
+        whenever(context.createContextAsUser(any(), any())).thenReturn(context)
+        whenever(packageManager.getApplicationInfo(any(), any<Int>())).thenReturn(mock())
+        whenever(packageManager.getApplicationLabel(any())).thenReturn(NOTE_TASK_LONG_LABEL)
         whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(NOTE_TASK_INFO)
         whenever(userManager.isUserUnlocked).thenReturn(true)
         whenever(userManager.isUserUnlocked(any<Int>())).thenReturn(true)
@@ -123,6 +138,7 @@
         whenever(activityManager.getRunningTasks(anyInt())).thenReturn(emptyList())
         whenever(userManager.isManagedProfile(workUserInfo.id)).thenReturn(true)
         whenever(context.resources).thenReturn(getContext().resources)
+        whenever(secureSettings.userTracker).thenReturn(userTracker)
     }
 
     private fun createNoteTaskController(
@@ -133,7 +149,6 @@
             context = context,
             resolver = resolver,
             eventLogger = eventLogger,
-            optionalBubbles = Optional.ofNullable(bubbles),
             userManager = userManager,
             keyguardManager = keyguardManager,
             isEnabled = isEnabled,
@@ -142,6 +157,10 @@
             roleManager = roleManager,
             shortcutManager = shortcutManager,
             activityManager = activityManager,
+            secureSettings = secureSettings,
+            noteTaskBubblesController =
+                FakeNoteTaskBubbleController(context, testDispatcher, Optional.ofNullable(bubbles)),
+            applicationScope = testScope,
         )
 
     // region onBubbleExpandChanged
@@ -157,7 +176,7 @@
             )
 
         verify(eventLogger).logNoteTaskOpened(expectedInfo)
-        verifyZeroInteractions(context, bubbles, keyguardManager, userManager)
+        verifyZeroInteractions(bubbles, keyguardManager, userManager)
     }
 
     @Test
@@ -172,7 +191,7 @@
             )
 
         verify(eventLogger).logNoteTaskClosed(expectedInfo)
-        verifyZeroInteractions(context, bubbles, keyguardManager, userManager)
+        verifyZeroInteractions(bubbles, keyguardManager, userManager)
     }
 
     @Test
@@ -186,7 +205,7 @@
                 key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
             )
 
-        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+        verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
     }
 
     @Test
@@ -200,7 +219,7 @@
                 key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
             )
 
-        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+        verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
     }
 
     @Test
@@ -211,7 +230,7 @@
                 key = "any other key",
             )
 
-        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+        verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
     }
 
     @Test
@@ -222,16 +241,19 @@
                 key = Bubble.getAppBubbleKeyForApp(NOTE_TASK_INFO.packageName, NOTE_TASK_INFO.user),
             )
 
-        verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+        verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
     }
     // endregion
 
     // region showNoteTask
-    @Test
     fun showNoteTaskAsUser_keyguardIsLocked_shouldStartActivityWithExpectedUserAndLogUiEvent() {
         val user10 = UserHandle.of(/* userId= */ 10)
         val expectedInfo =
-            NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true, user = user10)
+            NOTE_TASK_INFO.copy(
+                entryPoint = TAIL_BUTTON,
+                isKeyguardLocked = true,
+                user = user10,
+            )
         whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
         whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
 
@@ -332,10 +354,54 @@
     }
 
     @Test
+    fun showNoteTask_defaultUserSet_shouldStartActivityWithExpectedUserAndLogUiEvent() {
+        whenever(
+                secureSettings.getIntForUser(
+                    /* name= */ eq(Settings.Secure.DEFAULT_NOTE_TASK_PROFILE),
+                    /* def= */ any(),
+                    /* userHandle= */ any()
+                )
+            )
+            .thenReturn(10)
+        val user10 = UserHandle.of(/* userId= */ 10)
+
+        val expectedInfo =
+            NOTE_TASK_INFO.copy(
+                entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+                isKeyguardLocked = true,
+                user = user10,
+            )
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
+        whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
+
+        createNoteTaskController()
+            .showNoteTask(
+                entryPoint = expectedInfo.entryPoint!!,
+            )
+
+        val intentCaptor = argumentCaptor<Intent>()
+        val userCaptor = argumentCaptor<UserHandle>()
+        verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+        intentCaptor.value.let { intent ->
+            assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
+            assertThat(intent.`package`).isEqualTo(NOTE_TASK_PACKAGE_NAME)
+            assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
+            assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
+                .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
+            assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
+                .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
+            assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
+        }
+        assertThat(userCaptor.value).isEqualTo(user10)
+        verify(eventLogger).logNoteTaskOpened(expectedInfo)
+        verifyZeroInteractions(bubbles)
+    }
+
+    @Test
     fun showNoteTask_bubblesIsNull_shouldDoNothing() {
         createNoteTaskController(bubbles = null).showNoteTask(entryPoint = TAIL_BUTTON)
 
-        verifyZeroInteractions(context, bubbles, eventLogger)
+        verifyZeroInteractions(bubbles, eventLogger)
     }
 
     @Test
@@ -347,14 +413,14 @@
         noteTaskController.showNoteTask(entryPoint = TAIL_BUTTON)
 
         verify(noteTaskController).showNoDefaultNotesAppToast()
-        verifyZeroInteractions(context, bubbles, eventLogger)
+        verifyZeroInteractions(bubbles, eventLogger)
     }
 
     @Test
     fun showNoteTask_flagDisabled_shouldDoNothing() {
         createNoteTaskController(isEnabled = false).showNoteTask(entryPoint = TAIL_BUTTON)
 
-        verifyZeroInteractions(context, bubbles, eventLogger)
+        verifyZeroInteractions(bubbles, eventLogger)
     }
 
     @Test
@@ -363,7 +429,7 @@
 
         createNoteTaskController().showNoteTask(entryPoint = TAIL_BUTTON)
 
-        verifyZeroInteractions(context, bubbles, eventLogger)
+        verifyZeroInteractions(bubbles, eventLogger)
     }
 
     @Test
@@ -483,7 +549,7 @@
 
         createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
-        verifyZeroInteractions(context, bubbles, eventLogger)
+        verifyZeroInteractions(bubbles, eventLogger)
     }
 
     @Test
@@ -499,7 +565,7 @@
 
         createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
 
-        verifyZeroInteractions(context, bubbles, eventLogger)
+        verifyZeroInteractions(bubbles, eventLogger)
     }
 
     @Test
@@ -557,13 +623,21 @@
     }
 
     @Test
-    fun showNoteTask_copeDevices_tailButtonEntryPoint_shouldStartBubbleInWorkProfile() {
+    fun showNoteTask_copeDevices_tailButtonEntryPoint_shouldStartBubbleInTheUserSelectedUser() {
+        whenever(
+                secureSettings.getIntForUser(
+                    /* name= */ eq(Settings.Secure.DEFAULT_NOTE_TASK_PROFILE),
+                    /* def= */ any(),
+                    /* userHandle= */ any()
+                )
+            )
+            .thenReturn(mainUserInfo.id)
         whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
         userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
 
         createNoteTaskController().showNoteTask(entryPoint = TAIL_BUTTON)
 
-        verifyNoteTaskOpenInBubbleInUser(workUserInfo.userHandle)
+        verifyNoteTaskOpenInBubbleInUser(mainUserInfo.userHandle)
     }
 
     @Test
@@ -599,7 +673,7 @@
             extras().bool(EXTRA_USE_STYLUS_MODE).isTrue()
         }
         iconCaptor.value?.let { icon ->
-            assertThat(icon).isNotNull()
+            assertNotNull(icon)
             assertThat(icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
         }
     }
@@ -611,11 +685,11 @@
 
         createNoteTaskController(isEnabled = true).onRoleHoldersChanged("NOT_NOTES", user)
 
-        verifyZeroInteractions(context)
+        verify(context, never()).startActivityAsUser(any(), any())
     }
 
     @Test
-    fun onRoleHoldersChanged_notesRole_sameUser_shouldUpdateShortcuts() {
+    fun onRoleHoldersChanged_notesRole_shouldUpdateShortcuts() {
         val user = userTracker.userHandle
         val controller = spy(createNoteTaskController())
         doNothing().whenever(controller).updateNoteTaskAsUser(any())
@@ -624,22 +698,42 @@
 
         verify(controller).updateNoteTaskAsUser(user)
     }
-
-    @Test
-    fun onRoleHoldersChanged_notesRole_differentUser_shouldUpdateShortcutsInUserProcess() {
-        // FakeUserTracker will default to UserHandle.SYSTEM.
-        val user = UserHandle.CURRENT
-
-        createNoteTaskController(isEnabled = true).onRoleHoldersChanged(ROLE_NOTES, user)
-
-        verify(context).startServiceAsUser(any(), eq(user))
-    }
     // endregion
 
     // region updateNoteTaskAsUser
     @Test
-    fun updateNoteTaskAsUser_withNotesRole_withShortcuts_shouldUpdateShortcuts() {
-        createNoteTaskController(isEnabled = true).updateNoteTaskAsUser(userTracker.userHandle)
+    fun updateNoteTaskAsUser_sameUser_shouldUpdateShortcuts() {
+        val user = UserHandle.CURRENT
+        val controller = spy(createNoteTaskController())
+        doNothing().whenever(controller).updateNoteTaskAsUserInternal(any())
+        whenever(controller.getCurrentRunningUser()).thenReturn(user)
+
+        controller.updateNoteTaskAsUser(user)
+
+        verify(controller).updateNoteTaskAsUserInternal(user)
+        verify(context, never()).startServiceAsUser(any(), any())
+    }
+
+    @Test
+    fun updateNoteTaskAsUser_differentUser_shouldUpdateShortcutsInUserProcess() {
+        val user = UserHandle.CURRENT
+        val controller = spy(createNoteTaskController(isEnabled = true))
+        doNothing().whenever(controller).updateNoteTaskAsUserInternal(any())
+        whenever(controller.getCurrentRunningUser()).thenReturn(UserHandle.SYSTEM)
+
+        controller.updateNoteTaskAsUser(user)
+
+        verify(controller, never()).updateNoteTaskAsUserInternal(any())
+        val intent = withArgCaptor { verify(context).startServiceAsUser(capture(), eq(user)) }
+        assertThat(intent).hasComponentClass(NoteTaskControllerUpdateService::class.java)
+    }
+    // endregion
+
+    // region internalUpdateNoteTaskAsUser
+    @Test
+    fun updateNoteTaskAsUserInternal_withNotesRole_withShortcuts_shouldUpdateShortcuts() {
+        createNoteTaskController(isEnabled = true)
+            .updateNoteTaskAsUserInternal(userTracker.userHandle)
 
         val actualComponent = argumentCaptor<ComponentName>()
         verify(context.packageManager)
@@ -652,27 +746,29 @@
             .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name)
         verify(shortcutManager, never()).disableShortcuts(any())
         verify(shortcutManager).enableShortcuts(listOf(SHORTCUT_ID))
-        val actualShortcuts = argumentCaptor<List<ShortcutInfo>>()
-        verify(shortcutManager).updateShortcuts(actualShortcuts.capture())
-        val actualShortcut = actualShortcuts.value.first()
-        assertThat(actualShortcut.id).isEqualTo(SHORTCUT_ID)
-        assertThat(actualShortcut.intent).run {
-            hasComponentClass(LaunchNoteTaskActivity::class.java)
-            hasAction(ACTION_CREATE_NOTE)
+        val shortcutInfo = withArgCaptor { verify(shortcutManager).updateShortcuts(capture()) }
+        with(shortcutInfo.first()) {
+            assertThat(id).isEqualTo(SHORTCUT_ID)
+            assertThat(intent).run {
+                hasComponentClass(LaunchNoteTaskActivity::class.java)
+                hasAction(ACTION_CREATE_NOTE)
+            }
+            assertThat(shortLabel).isEqualTo(NOTE_TASK_SHORT_LABEL)
+            assertThat(longLabel).isEqualTo(NOTE_TASK_LONG_LABEL)
+            assertThat(isLongLived).isEqualTo(true)
+            assertThat(icon?.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
+            assertThat(extras?.getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE))
+                .isEqualTo(NOTE_TASK_PACKAGE_NAME)
         }
-        assertThat(actualShortcut.shortLabel).isEqualTo(NOTE_TASK_SHORT_LABEL)
-        assertThat(actualShortcut.isLongLived).isEqualTo(true)
-        assertThat(actualShortcut.icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
-        assertThat(actualShortcut.extras?.getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE))
-            .isEqualTo(NOTE_TASK_PACKAGE_NAME)
     }
 
     @Test
-    fun updateNoteTaskAsUser_noNotesRole_shouldDisableShortcuts() {
+    fun updateNoteTaskAsUserInternal_noNotesRole_shouldDisableShortcuts() {
         whenever(roleManager.getRoleHoldersAsUser(ROLE_NOTES, userTracker.userHandle))
             .thenReturn(emptyList())
 
-        createNoteTaskController(isEnabled = true).updateNoteTaskAsUser(userTracker.userHandle)
+        createNoteTaskController(isEnabled = true)
+            .updateNoteTaskAsUserInternal(userTracker.userHandle)
 
         val argument = argumentCaptor<ComponentName>()
         verify(context.packageManager)
@@ -689,8 +785,9 @@
     }
 
     @Test
-    fun updateNoteTaskAsUser_flagDisabled_shouldDisableShortcuts() {
-        createNoteTaskController(isEnabled = false).updateNoteTaskAsUser(userTracker.userHandle)
+    fun updateNoteTaskAsUserInternal_flagDisabled_shouldDisableShortcuts() {
+        createNoteTaskController(isEnabled = false)
+            .updateNoteTaskAsUserInternal(userTracker.userHandle)
 
         val argument = argumentCaptor<ComponentName>()
         verify(context.packageManager)
@@ -707,18 +804,17 @@
     }
     // endregion
 
-    // startregion startNoteTaskProxyActivityForUser
+    // startregion updateNoteTaskForAllUsers
     @Test
-    fun startNoteTaskProxyActivityForUser_shouldStartLaunchNoteTaskProxyActivityWithExpectedUser() {
-        val user0 = UserHandle.of(0)
-        createNoteTaskController().startNoteTaskProxyActivityForUser(user0)
+    fun updateNoteTaskForAllUsers_shouldRunUpdateForCurrentUserAndProfiles() {
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+        val controller = spy(createNoteTaskController())
+        doNothing().whenever(controller).updateNoteTaskAsUser(any())
 
-        val intentCaptor = argumentCaptor<Intent>()
-        verify(context).startActivityAsUser(intentCaptor.capture(), eq(user0))
-        assertThat(intentCaptor.value).run {
-            hasComponentClass(LaunchNoteTaskManagedProfileProxyActivity::class.java)
-            hasFlags(FLAG_ACTIVITY_NEW_TASK)
-        }
+        controller.updateNoteTaskForCurrentUserAndManagedProfiles()
+
+        verify(controller).updateNoteTaskAsUser(mainUserInfo.userHandle)
+        verify(controller).updateNoteTaskAsUser(workUserInfo.userHandle)
     }
     // endregion
 
@@ -734,7 +830,15 @@
     }
 
     @Test
-    fun getUserForHandlingNotesTaking_cope_tailButton_shouldReturnWorkProfileUser() {
+    fun getUserForHandlingNotesTaking_cope_userSelectedWorkProfile_tailButton_shouldReturnWorkProfileUser() { // ktlint-disable max-line-length
+        whenever(
+                secureSettings.getIntForUser(
+                    /* name= */ eq(Settings.Secure.DEFAULT_NOTE_TASK_PROFILE),
+                    /* def= */ any(),
+                    /* userHandle= */ any()
+                )
+            )
+            .thenReturn(workUserInfo.id)
         whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
         userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
 
@@ -744,6 +848,24 @@
     }
 
     @Test
+    fun getUserForHandlingNotesTaking_cope_userSelectedMainProfile_tailButton_shouldReturnMainProfileUser() { // ktlint-disable max-line-length
+        whenever(
+                secureSettings.getIntForUser(
+                    /* name= */ eq(Settings.Secure.DEFAULT_NOTE_TASK_PROFILE),
+                    /* def= */ any(),
+                    /* userHandle= */ any()
+                )
+            )
+            .thenReturn(mainUserInfo.id)
+        whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
+        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
+
+        val user = createNoteTaskController().getUserForHandlingNotesTaking(TAIL_BUTTON)
+
+        assertThat(user).isEqualTo(UserHandle.of(mainUserInfo.id))
+    }
+
+    @Test
     fun getUserForHandlingNotesTaking_cope_appClip_shouldReturnCurrentUser() {
         whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
         userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
@@ -838,7 +960,8 @@
     // endregion
 
     private companion object {
-        const val NOTE_TASK_SHORT_LABEL = "Notetaking"
+        const val NOTE_TASK_SHORT_LABEL = "Note-taking"
+        const val NOTE_TASK_LONG_LABEL = "Note-taking, App"
         const val NOTE_TASK_ACTIVITY_NAME = "NoteTaskActivity"
         const val NOTE_TASK_PACKAGE_NAME = "com.android.note.app"
         const val NOTE_TASK_UID = 123456
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 4e85b6c..95bb3e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -32,9 +32,12 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.wm.shell.bubbles.Bubbles
+import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import org.junit.Before
@@ -71,19 +74,19 @@
     }
 
     private fun createUnderTest(
-        isEnabled: Boolean,
-        bubbles: Bubbles?,
+            isEnabled: Boolean,
+            bubbles: Bubbles?,
     ): NoteTaskInitializer =
-        NoteTaskInitializer(
-            controller = controller,
-            commandQueue = commandQueue,
-            optionalBubbles = Optional.ofNullable(bubbles),
-            isEnabled = isEnabled,
-            roleManager = roleManager,
-            userTracker = userTracker,
-            keyguardUpdateMonitor = keyguardMonitor,
-            backgroundExecutor = executor,
-        )
+            NoteTaskInitializer(
+                    controller = controller,
+                    commandQueue = commandQueue,
+                    optionalBubbles = Optional.ofNullable(bubbles),
+                    isEnabled = isEnabled,
+                    roleManager = roleManager,
+                    userTracker = userTracker,
+                    keyguardUpdateMonitor = keyguardMonitor,
+                    backgroundExecutor = executor,
+            )
 
     @Test
     fun initialize_withUserUnlocked() {
@@ -93,8 +96,8 @@
 
         verify(commandQueue).addCallback(any())
         verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
-        verify(controller).setNoteTaskShortcutEnabled(any(), any())
-        verify(keyguardMonitor, never()).registerCallback(any())
+        verify(controller).updateNoteTaskForCurrentUserAndManagedProfiles()
+        verify(keyguardMonitor).registerCallback(any())
     }
 
     @Test
@@ -107,6 +110,7 @@
         verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any())
         verify(controller, never()).setNoteTaskShortcutEnabled(any(), any())
         verify(keyguardMonitor).registerCallback(any())
+        assertThat(userTracker.callbacks).isNotEmpty()
     }
 
     @Test
@@ -116,12 +120,12 @@
         underTest.initialize()
 
         verifyZeroInteractions(
-            commandQueue,
-            bubbles,
-            controller,
-            roleManager,
-            userManager,
-            keyguardMonitor,
+                commandQueue,
+                bubbles,
+                controller,
+                roleManager,
+                userManager,
+                keyguardMonitor,
         )
     }
 
@@ -132,12 +136,12 @@
         underTest.initialize()
 
         verifyZeroInteractions(
-            commandQueue,
-            bubbles,
-            controller,
-            roleManager,
-            userManager,
-            keyguardMonitor,
+                commandQueue,
+                bubbles,
+                controller,
+                roleManager,
+                userManager,
+                keyguardMonitor,
         )
     }
 
@@ -146,7 +150,7 @@
         val expectedKeyEvent = KeyEvent(ACTION_DOWN, KEYCODE_STYLUS_BUTTON_TAIL)
         val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
         underTest.initialize()
-        val callback = captureArgument { verify(commandQueue).addCallback(capture()) }
+        val callback = withArgCaptor { verify(commandQueue).addCallback(capture()) }
 
         callback.handleSystemKey(expectedKeyEvent)
 
@@ -154,31 +158,49 @@
     }
 
     @Test
-    fun initialize_userUnlocked() {
+    fun initialize_userUnlocked_shouldUpdateNoteTask() {
         whenever(keyguardMonitor.isUserUnlocked(userTracker.userId)).thenReturn(false)
         val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
         underTest.initialize()
-        val callback = captureArgument { verify(keyguardMonitor).registerCallback(capture()) }
+        val callback = withArgCaptor { verify(keyguardMonitor).registerCallback(capture()) }
         whenever(keyguardMonitor.isUserUnlocked(userTracker.userId)).thenReturn(true)
 
         callback.onUserUnlocked()
-        verify(controller).setNoteTaskShortcutEnabled(any(), any())
+
+        verify(controller).updateNoteTaskForCurrentUserAndManagedProfiles()
     }
 
     @Test
-    fun initialize_onRoleHoldersChanged() {
+    fun initialize_onRoleHoldersChanged_shouldRunOnRoleHoldersChanged() {
         val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
         underTest.initialize()
-        val callback = captureArgument {
+        val callback = withArgCaptor {
             verify(roleManager)
-                .addOnRoleHoldersChangedListenerAsUser(any(), capture(), eq(UserHandle.ALL))
+                    .addOnRoleHoldersChangedListenerAsUser(any(), capture(), eq(UserHandle.ALL))
         }
 
         callback.onRoleHoldersChanged(ROLE_NOTES, userTracker.userHandle)
 
         verify(controller).onRoleHoldersChanged(ROLE_NOTES, userTracker.userHandle)
     }
-}
 
-private inline fun <reified T : Any> captureArgument(block: ArgumentCaptor<T>.() -> Unit) =
-    argumentCaptor<T>().apply(block).value
+    @Test
+    fun initialize_onProfilesChanged_shouldUpdateNoteTask() {
+        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
+        underTest.initialize()
+
+        userTracker.callbacks.first().onProfilesChanged(emptyList())
+
+        verify(controller, times(2)).updateNoteTaskForCurrentUserAndManagedProfiles()
+    }
+
+    @Test
+    fun initialize_onUserChanged_shouldUpdateNoteTask() {
+        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
+        underTest.initialize()
+
+        userTracker.callbacks.first().onUserChanged(0, mock())
+
+        verify(controller, times(2)).updateNoteTaskForCurrentUserAndManagedProfiles()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
index a0c376f..1f0f0d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
@@ -16,30 +16,23 @@
 
 package com.android.systemui.notetask.shortcut
 
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.os.UserHandle
-import android.os.UserManager
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEntryPoint
-import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
 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.never
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -48,21 +41,13 @@
 class LaunchNoteTaskActivityTest : SysuiTestCase() {
 
     @Mock lateinit var noteTaskController: NoteTaskController
-    @Mock lateinit var userManager: UserManager
-    private val userTracker: FakeUserTracker = FakeUserTracker()
 
     @Rule
     @JvmField
     val activityRule =
         ActivityTestRule<LaunchNoteTaskActivity>(
-            /* activityFactory= */ object :
-                SingleActivityFactory<LaunchNoteTaskActivity>(LaunchNoteTaskActivity::class.java) {
-                override fun create(intent: Intent?) =
-                    LaunchNoteTaskActivity(
-                        controller = noteTaskController,
-                        userManager = userManager,
-                        userTracker = userTracker
-                    )
+            /* activityFactory= */ SingleActivityFactory {
+                LaunchNoteTaskActivity(controller = noteTaskController)
             },
             /* initialTouchMode= */ false,
             /* launchActivity= */ false,
@@ -71,7 +56,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        whenever(userManager.isManagedProfile(eq(workProfileUser.id))).thenReturn(true)
     }
 
     @After
@@ -83,36 +67,7 @@
     fun startActivityOnNonWorkProfileUser_shouldLaunchNoteTask() {
         activityRule.launchActivity(/* startIntent= */ null)
 
-        verify(noteTaskController).showNoteTask(eq(NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT))
-    }
-
-    @Test
-    fun startActivityOnWorkProfileUser_shouldLaunchProxyActivity() {
-        val mainUserHandle: UserHandle = mainUser.userHandle
-        userTracker.set(listOf(mainUser, workProfileUser), selectedUserIndex = 1)
-        whenever(userManager.isManagedProfile).thenReturn(true)
-        whenever(userManager.mainUser).thenReturn(mainUserHandle)
-
-        activityRule.launchActivity(/* startIntent= */ null)
-
-        verify(noteTaskController).startNoteTaskProxyActivityForUser(eq(mainUserHandle))
-    }
-
-    @Test
-    fun startActivityOnWorkProfileUser_noMainUser_shouldNotLaunch() {
-        userTracker.set(listOf(mainUser, workProfileUser), selectedUserIndex = 1)
-        whenever(userManager.isManagedProfile).thenReturn(true)
-        whenever(userManager.mainUser).thenReturn(null)
-
-        activityRule.launchActivity(/* startIntent= */ null)
-
-        verify(noteTaskController, never()).showNoteTask(any())
-        verify(noteTaskController, never()).startNoteTaskProxyActivityForUser(any())
-    }
-
-    private companion object {
-        val mainUser = UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
-        val workProfileUser =
-            UserInfo(/* id= */ 10, /* name= */ "work", /* flags= */ UserInfo.FLAG_PROFILE)
+        verify(noteTaskController)
+            .showNoteTaskAsUser(eq(NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT), any())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivityTest.kt
deleted file mode 100644
index 6347c34..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivityTest.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.notetask.shortcut
-
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.os.UserHandle
-import android.os.UserManager
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import androidx.test.filters.SmallTest
-import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
-import com.android.dx.mockito.inline.extended.ExtendedMockito.never
-import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.notetask.NoteTaskEntryPoint
-import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
-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.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-@TestableLooper.RunWithLooper
-class LaunchNoteTaskManagedProfileProxyActivityTest : SysuiTestCase() {
-
-    @Mock lateinit var noteTaskController: NoteTaskController
-    @Mock lateinit var userManager: UserManager
-    private val userTracker = FakeUserTracker()
-
-    @Rule
-    @JvmField
-    val activityRule =
-        ActivityTestRule<LaunchNoteTaskManagedProfileProxyActivity>(
-            /* activityFactory= */ object :
-                SingleActivityFactory<LaunchNoteTaskManagedProfileProxyActivity>(
-                    LaunchNoteTaskManagedProfileProxyActivity::class.java
-                ) {
-                override fun create(intent: Intent?) =
-                    LaunchNoteTaskManagedProfileProxyActivity(
-                        controller = noteTaskController,
-                        userManager = userManager,
-                        userTracker = userTracker
-                    )
-            },
-            /* initialTouchMode= */ false,
-            /* launchActivity= */ false,
-        )
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        whenever(userManager.isManagedProfile(eq(workProfileUser.id))).thenReturn(true)
-    }
-
-    @After
-    fun tearDown() {
-        activityRule.finishActivity()
-    }
-
-    @Test
-    fun startActivity_noWorkProfileUser_shouldNotLaunchNoteTask() {
-        userTracker.set(listOf(mainUser), selectedUserIndex = 0)
-        activityRule.launchActivity(/* startIntent= */ null)
-
-        verify(noteTaskController, never()).showNoteTaskAsUser(any(), any())
-    }
-
-    @Test
-    fun startActivity_hasWorkProfileUser_shouldLaunchNoteTaskOnTheWorkProfileUser() {
-        userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUser))
-        activityRule.launchActivity(/* startIntent= */ null)
-
-        val workProfileUserHandle: UserHandle = workProfileUser.userHandle
-        verify(noteTaskController)
-            .showNoteTaskAsUser(
-                eq(NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT),
-                eq(workProfileUserHandle)
-            )
-    }
-
-    private companion object {
-        val mainUser = UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
-        val workProfileUser =
-            UserInfo(/* id= */ 10, /* name= */ "work", /* flags= */ UserInfo.FLAG_PROFILE)
-        val mainAndWorkProfileUsers = listOf(mainUser, workProfileUser)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
index bb3b3f7..a01394f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
@@ -24,7 +24,11 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.launchIn
@@ -47,6 +51,8 @@
 @RunWith(JUnit4::class)
 class PowerRepositoryImplTest : SysuiTestCase() {
 
+    private val systemClock = FakeSystemClock()
+
     @Mock private lateinit var manager: PowerManager
     @Mock private lateinit var dispatcher: BroadcastDispatcher
     @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
@@ -62,7 +68,13 @@
         isInteractive = true
         whenever(manager.isInteractive).then { isInteractive }
 
-        underTest = PowerRepositoryImpl(manager = manager, dispatcher = dispatcher)
+        underTest =
+            PowerRepositoryImpl(
+                manager,
+                context.applicationContext,
+                systemClock,
+                dispatcher,
+            )
     }
 
     @Test
@@ -160,6 +172,27 @@
             job.cancel()
         }
 
+    @Test
+    fun wakeUp_notifiesPowerManager() {
+        systemClock.setUptimeMillis(345000)
+
+        underTest.wakeUp("fakeWhy", PowerManager.WAKE_REASON_GESTURE)
+
+        val reasonCaptor = argumentCaptor<String>()
+        verify(manager)
+            .wakeUp(eq(345000L), eq(PowerManager.WAKE_REASON_GESTURE), capture(reasonCaptor))
+        assertThat(reasonCaptor.value).contains("fakeWhy")
+    }
+
+    @Test
+    fun wakeUp_usesApplicationPackageName() {
+        underTest.wakeUp("fakeWhy", PowerManager.WAKE_REASON_GESTURE)
+
+        val reasonCaptor = argumentCaptor<String>()
+        verify(manager).wakeUp(any(), any(), capture(reasonCaptor))
+        assertThat(reasonCaptor.value).contains(context.applicationContext.packageName)
+    }
+
     private fun verifyRegistered() {
         // We must verify with all arguments, even those that are optional because they have default
         // values because Mockito is forcing us to. Once we can use mockito-kotlin, we should be
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
index 31d4512..435a1f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
@@ -17,9 +17,18 @@
 
 package com.android.systemui.power.domain.interactor
 
+import android.os.PowerManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.launchIn
@@ -29,6 +38,9 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(JUnit4::class)
@@ -36,14 +48,27 @@
 
     private lateinit var underTest: PowerInteractor
     private lateinit var repository: FakePowerRepository
+    private val keyguardRepository = FakeKeyguardRepository()
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
 
     @Before
     fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
         repository =
             FakePowerRepository(
                 initialInteractive = true,
             )
-        underTest = PowerInteractor(repository = repository)
+        underTest =
+            PowerInteractor(
+                repository,
+                keyguardRepository,
+                falsingCollector,
+                screenOffAnimationController,
+                statusBarStateController,
+            )
     }
 
     @Test
@@ -72,6 +97,117 @@
             job.cancel()
         }
 
+    @Test
+    fun wakeUpIfDozing_notDozing_notWoken() {
+        whenever(statusBarStateController.isDozing).thenReturn(false)
+        whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
+
+        underTest.wakeUpIfDozing("why", PowerManager.WAKE_REASON_TAP)
+
+        assertThat(repository.lastWakeWhy).isNull()
+        assertThat(repository.lastWakeReason).isNull()
+    }
+
+    @Test
+    fun wakeUpIfDozing_notAllowed_notWoken() {
+        whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(false)
+        whenever(statusBarStateController.isDozing).thenReturn(true)
+
+        underTest.wakeUpIfDozing("why", PowerManager.WAKE_REASON_TAP)
+
+        assertThat(repository.lastWakeWhy).isNull()
+        assertThat(repository.lastWakeReason).isNull()
+    }
+
+    @Test
+    fun wakeUpIfDozing_dozingAndAllowed_wokenAndFalsingNotified() {
+        whenever(statusBarStateController.isDozing).thenReturn(true)
+        whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
+
+        underTest.wakeUpIfDozing("testReason", PowerManager.WAKE_REASON_GESTURE)
+
+        assertThat(repository.lastWakeWhy).isEqualTo("testReason")
+        assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
+        verify(falsingCollector).onScreenOnFromTouch()
+    }
+
+    @Test
+    fun wakeUpForFullScreenIntent_notGoingToSleepAndNotDozing_notWoken() {
+        keyguardRepository.setWakefulnessModel(
+            WakefulnessModel(
+                state = WakefulnessState.AWAKE,
+                lastWakeReason = WakeSleepReason.OTHER,
+                lastSleepReason = WakeSleepReason.OTHER,
+            )
+        )
+        whenever(statusBarStateController.isDozing).thenReturn(false)
+
+        underTest.wakeUpForFullScreenIntent()
+
+        assertThat(repository.lastWakeWhy).isNull()
+        assertThat(repository.lastWakeReason).isNull()
+    }
+
+    @Test
+    fun wakeUpForFullScreenIntent_startingToSleep_woken() {
+        keyguardRepository.setWakefulnessModel(
+            WakefulnessModel(
+                state = WakefulnessState.STARTING_TO_SLEEP,
+                lastWakeReason = WakeSleepReason.OTHER,
+                lastSleepReason = WakeSleepReason.OTHER,
+            )
+        )
+        whenever(statusBarStateController.isDozing).thenReturn(false)
+
+        underTest.wakeUpForFullScreenIntent()
+
+        assertThat(repository.lastWakeWhy).isNotNull()
+        assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_APPLICATION)
+    }
+
+    @Test
+    fun wakeUpForFullScreenIntent_dozing_woken() {
+        whenever(statusBarStateController.isDozing).thenReturn(true)
+        keyguardRepository.setWakefulnessModel(
+            WakefulnessModel(
+                state = WakefulnessState.AWAKE,
+                lastWakeReason = WakeSleepReason.OTHER,
+                lastSleepReason = WakeSleepReason.OTHER,
+            )
+        )
+
+        underTest.wakeUpForFullScreenIntent()
+
+        assertThat(repository.lastWakeWhy).isNotNull()
+        assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_APPLICATION)
+    }
+
+    @Test
+    fun wakeUpIfDreaming_dreaming_woken() {
+        // GIVEN device is dreaming
+        whenever(statusBarStateController.isDreaming).thenReturn(true)
+
+        // WHEN wakeUpIfDreaming is called
+        underTest.wakeUpIfDreaming("testReason", PowerManager.WAKE_REASON_GESTURE)
+
+        // THEN device is woken up
+        assertThat(repository.lastWakeWhy).isEqualTo("testReason")
+        assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
+    }
+
+    @Test
+    fun wakeUpIfDreaming_notDreaming_notWoken() {
+        // GIVEN device is not dreaming
+        whenever(statusBarStateController.isDreaming).thenReturn(false)
+
+        // WHEN wakeUpIfDreaming is called
+        underTest.wakeUpIfDreaming("why", PowerManager.WAKE_REASON_TAP)
+
+        // THEN device is not woken
+        assertThat(repository.lastWakeWhy).isNull()
+        assertThat(repository.lastWakeReason).isNull()
+    }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
new file mode 100644
index 0000000..0a8c0ab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
@@ -0,0 +1,825 @@
+/*
+ * 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.privacy
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import android.content.pm.UserInfo
+import android.os.Process.SYSTEM_UID
+import android.os.UserHandle
+import android.permission.PermissionGroupUsage
+import android.permission.PermissionManager
+import android.testing.AndroidTestingRunner
+import android.view.View
+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.appops.AppOpsController
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.privacy.logging.PrivacyLogger
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class PrivacyDialogControllerV2Test : SysuiTestCase() {
+
+    companion object {
+        private const val USER_ID = 0
+        private const val ENT_USER_ID = 10
+
+        private const val TEST_PACKAGE_NAME = "test package name"
+        private const val TEST_ATTRIBUTION_TAG = "test attribution tag"
+        private const val TEST_PROXY_LABEL = "test proxy label"
+
+        private const val PERM_CAMERA = android.Manifest.permission_group.CAMERA
+        private const val PERM_MICROPHONE = android.Manifest.permission_group.MICROPHONE
+        private const val PERM_LOCATION = android.Manifest.permission_group.LOCATION
+
+        private val TEST_INTENT = Intent("test_intent_action")
+    }
+
+    @Mock
+    private lateinit var dialog: PrivacyDialogV2
+    @Mock
+    private lateinit var permissionManager: PermissionManager
+    @Mock
+    private lateinit var packageManager: PackageManager
+    @Mock
+    private lateinit var privacyItemController: PrivacyItemController
+    @Mock
+    private lateinit var userTracker: UserTracker
+    @Mock
+    private lateinit var activityStarter: ActivityStarter
+    @Mock
+    private lateinit var privacyLogger: PrivacyLogger
+    @Mock
+    private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var appOpsController: AppOpsController
+    @Captor
+    private lateinit var dialogDismissedCaptor: ArgumentCaptor<PrivacyDialogV2.OnDialogDismissed>
+    @Captor
+    private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback>
+    @Captor
+    private lateinit var intentCaptor: ArgumentCaptor<Intent>
+    @Mock
+    private lateinit var uiEventLogger: UiEventLogger
+    @Mock
+    private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+
+    private val backgroundExecutor = FakeExecutor(FakeSystemClock())
+    private val uiExecutor = FakeExecutor(FakeSystemClock())
+    private lateinit var controller: PrivacyDialogControllerV2
+    private var nextUid: Int = 0
+
+    private val dialogProvider = object : PrivacyDialogControllerV2.DialogProvider {
+        var list: List<PrivacyDialogV2.PrivacyElement>? = null
+        var manageApp: ((String, Int, Intent) -> Unit)? = null
+        var closeApp: ((String, Int) -> Unit)? = null
+        var openPrivacyDashboard: (() -> Unit)? = null
+
+        override fun makeDialog(
+            context: Context,
+            list: List<PrivacyDialogV2.PrivacyElement>,
+            manageApp: (String, Int, Intent) -> Unit,
+            closeApp: (String, Int) -> Unit,
+            openPrivacyDashboard: () -> Unit
+        ): PrivacyDialogV2 {
+            this.list = list
+            this.manageApp = manageApp
+            this.closeApp = closeApp
+            this.openPrivacyDashboard = openPrivacyDashboard
+            return dialog
+        }
+    }
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        nextUid = 0
+        setUpDefaultMockResponses()
+
+        controller = PrivacyDialogControllerV2(
+                permissionManager,
+                packageManager,
+                privacyItemController,
+                userTracker,
+                activityStarter,
+                backgroundExecutor,
+                uiExecutor,
+                privacyLogger,
+                keyguardStateController,
+                appOpsController,
+                uiEventLogger,
+                dialogLaunchAnimator,
+                dialogProvider
+        )
+    }
+
+    @After
+    fun tearDown() {
+        FakeExecutor.exhaustExecutors(uiExecutor, backgroundExecutor)
+        dialogProvider.list = null
+        dialogProvider.manageApp = null
+        dialogProvider.closeApp = null
+        dialogProvider.openPrivacyDashboard = null
+    }
+
+    @Test
+    fun testMicMutedParameter() {
+        `when`(appOpsController.isMicMuted).thenReturn(true)
+        controller.showDialog(context)
+        backgroundExecutor.runAllReady()
+
+        verify(permissionManager).getIndicatorAppOpUsageData(true)
+    }
+
+    @Test
+    fun testPermissionManagerOnlyCalledInBackgroundThread() {
+        controller.showDialog(context)
+        verify(permissionManager, never()).getIndicatorAppOpUsageData(anyBoolean())
+        backgroundExecutor.runAllReady()
+        verify(permissionManager).getIndicatorAppOpUsageData(anyBoolean())
+    }
+
+    @Test
+    fun testPackageManagerOnlyCalledInBackgroundThread() {
+        val usage = createMockPermGroupUsage()
+        `when`(usage.isPhoneCall).thenReturn(false)
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+
+        controller.showDialog(context)
+        verify(packageManager, never()).getApplicationInfoAsUser(anyString(), anyInt(), anyInt())
+        backgroundExecutor.runAllReady()
+        verify(packageManager, atLeastOnce())
+                .getApplicationInfoAsUser(anyString(), anyInt(), anyInt())
+    }
+
+    @Test
+    fun testShowDialogShowsDialogWithoutView() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialogLaunchAnimator, never()).showFromView(any(), any(), any(), anyBoolean())
+        verify(dialog).show()
+    }
+
+    @Test
+    fun testShowDialogShowsDialogWithView() {
+        val view = View(context)
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+
+        controller.showDialog(context, view)
+        exhaustExecutors()
+
+        verify(dialogLaunchAnimator).showFromView(dialog, view)
+        verify(dialog, never()).show()
+    }
+
+    @Test
+    fun testDontShowEmptyDialog() {
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialog, never()).show()
+    }
+
+    @Test
+    fun testHideDialogDismissesDialogIfShown() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        controller.dismissDialog()
+        verify(dialog).dismiss()
+    }
+
+    @Test
+    fun testHideDialogNoopIfNotShown() {
+        controller.dismissDialog()
+        verify(dialog, never()).dismiss()
+    }
+
+    @Test
+    fun testHideDialogNoopAfterDismissed() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialog).addOnDismissListener(capture(dialogDismissedCaptor))
+
+        dialogDismissedCaptor.value.onDialogDismissed()
+        controller.dismissDialog()
+        verify(dialog, never()).dismiss()
+    }
+
+    @Test
+    fun testShowForAllUsers() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+
+        exhaustExecutors()
+        verify(dialog).setShowForAllUsers(true)
+    }
+
+    @Test
+    fun testSingleElementInList() {
+        val usage = createMockPermGroupUsage(
+                packageName = TEST_PACKAGE_NAME,
+                uid = generateUidForUser(USER_ID),
+                permissionGroupName = PERM_CAMERA,
+                lastAccessTimeMillis = 5L,
+                isActive = true,
+                isPhoneCall = false,
+                attributionTag = null,
+                proxyLabel = TEST_PROXY_LABEL
+        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            assertThat(list.get(0).type).isEqualTo(PrivacyType.TYPE_CAMERA)
+            assertThat(list.get(0).packageName).isEqualTo(TEST_PACKAGE_NAME)
+            assertThat(list.get(0).userId).isEqualTo(USER_ID)
+            assertThat(list.get(0).applicationName).isEqualTo(TEST_PACKAGE_NAME)
+            assertThat(list.get(0).attributionTag).isNull()
+            assertThat(list.get(0).attributionLabel).isNull()
+            assertThat(list.get(0).proxyLabel).isEqualTo(TEST_PROXY_LABEL)
+            assertThat(list.get(0).lastActiveTimestamp).isEqualTo(5L)
+            assertThat(list.get(0).isActive).isTrue()
+            assertThat(list.get(0).isPhoneCall).isFalse()
+            assertThat(list.get(0).isService).isFalse()
+            assertThat(list.get(0).permGroupName).isEqualTo(PERM_CAMERA)
+            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
+                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
+                    .isTrue()
+        }
+    }
+
+    private fun isIntentEqual(actual: Intent, expected: Intent): Boolean {
+        return actual.action == expected.action &&
+                actual.getStringExtra(Intent.EXTRA_PACKAGE_NAME) ==
+                expected.getStringExtra(Intent.EXTRA_PACKAGE_NAME) &&
+                actual.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle ==
+                expected.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle
+    }
+
+    @Test
+    fun testTwoElementsDifferentType_sorted() {
+        val usage_camera = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_camera",
+                permissionGroupName = PERM_CAMERA
+        )
+        val usage_microphone = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_microphone",
+                permissionGroupName = PERM_MICROPHONE
+        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_microphone, usage_camera)
+        )
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            assertThat(list).hasSize(2)
+            assertThat(list.get(0).type.compareTo(list.get(1).type)).isLessThan(0)
+        }
+    }
+
+    @Test
+    fun testTwoElementsSameType_oneActive() {
+        val usage_active = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_active",
+                isActive = true
+        )
+        val usage_recent = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_recent",
+                isActive = false
+        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_recent, usage_active)
+        )
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        assertThat(dialogProvider.list).hasSize(1)
+        assertThat(dialogProvider.list?.get(0)?.isActive).isTrue()
+    }
+
+    @Test
+    fun testTwoElementsSameType_twoActive() {
+        val usage_active = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_active",
+                isActive = true,
+                lastAccessTimeMillis = 0L
+        )
+        val usage_active_moreRecent = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_active_recent",
+                isActive = true,
+                lastAccessTimeMillis = 1L
+        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_active, usage_active_moreRecent)
+        )
+        controller.showDialog(context)
+        exhaustExecutors()
+        assertThat(dialogProvider.list).hasSize(2)
+        assertThat(dialogProvider.list?.get(0)?.lastActiveTimestamp).isEqualTo(1L)
+        assertThat(dialogProvider.list?.get(1)?.lastActiveTimestamp).isEqualTo(0L)
+    }
+
+    @Test
+    fun testManyElementsSameType_bothRecent() {
+        val usage_recent = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_recent",
+                isActive = false,
+                lastAccessTimeMillis = 0L
+        )
+        val usage_moreRecent = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_moreRecent",
+                isActive = false,
+                lastAccessTimeMillis = 1L
+        )
+        val usage_mostRecent = createMockPermGroupUsage(
+                packageName = "${TEST_PACKAGE_NAME}_mostRecent",
+                isActive = false,
+                lastAccessTimeMillis = 2L
+        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_recent, usage_mostRecent, usage_moreRecent)
+        )
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        assertThat(dialogProvider.list).hasSize(1)
+        assertThat(dialogProvider.list?.get(0)?.lastActiveTimestamp).isEqualTo(2L)
+    }
+
+    @Test
+    fun testMicAndCameraDisabled() {
+        val usage_camera = createMockPermGroupUsage(
+                permissionGroupName = PERM_CAMERA
+        )
+        val usage_microphone = createMockPermGroupUsage(
+                permissionGroupName = PERM_MICROPHONE
+        )
+        val usage_location = createMockPermGroupUsage(
+                permissionGroupName = PERM_LOCATION
+        )
+
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_camera, usage_location, usage_microphone)
+        )
+        `when`(privacyItemController.micCameraAvailable).thenReturn(false)
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        assertThat(dialogProvider.list).hasSize(1)
+        assertThat(dialogProvider.list?.get(0)?.type).isEqualTo(PrivacyType.TYPE_LOCATION)
+    }
+
+    @Test
+    fun testLocationDisabled() {
+        val usage_camera = createMockPermGroupUsage(
+                permissionGroupName = PERM_CAMERA
+        )
+        val usage_microphone = createMockPermGroupUsage(
+                permissionGroupName = PERM_MICROPHONE
+        )
+        val usage_location = createMockPermGroupUsage(
+                permissionGroupName = PERM_LOCATION
+        )
+
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_camera, usage_location, usage_microphone)
+        )
+        `when`(privacyItemController.locationAvailable).thenReturn(false)
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        assertThat(dialogProvider.list).hasSize(2)
+        dialogProvider.list?.forEach {
+            assertThat(it.type).isNotEqualTo(PrivacyType.TYPE_LOCATION)
+        }
+    }
+
+    @Test
+    fun testAllIndicatorsAvailable() {
+        val usage_camera = createMockPermGroupUsage(
+                permissionGroupName = PERM_CAMERA
+        )
+        val usage_microphone = createMockPermGroupUsage(
+                permissionGroupName = PERM_MICROPHONE
+        )
+        val usage_location = createMockPermGroupUsage(
+                permissionGroupName = PERM_LOCATION
+        )
+
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_camera, usage_location, usage_microphone)
+        )
+        `when`(privacyItemController.micCameraAvailable).thenReturn(true)
+        `when`(privacyItemController.locationAvailable).thenReturn(true)
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        assertThat(dialogProvider.list).hasSize(3)
+    }
+
+    @Test
+    fun testNoIndicatorsAvailable() {
+        val usage_camera = createMockPermGroupUsage(
+                permissionGroupName = PERM_CAMERA
+        )
+        val usage_microphone = createMockPermGroupUsage(
+                permissionGroupName = PERM_MICROPHONE
+        )
+        val usage_location = createMockPermGroupUsage(
+                permissionGroupName = PERM_LOCATION
+        )
+
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
+                listOf(usage_camera, usage_location, usage_microphone)
+        )
+        `when`(privacyItemController.micCameraAvailable).thenReturn(false)
+        `when`(privacyItemController.locationAvailable).thenReturn(false)
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialog, never()).show()
+    }
+
+    @Test
+    fun testNotCurrentUser() {
+        val usage_other = createMockPermGroupUsage(
+                uid = generateUidForUser(ENT_USER_ID + 1)
+        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+                .thenReturn(listOf(usage_other))
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialog, never()).show()
+    }
+
+    @Test
+    fun testStartActivitySuccess() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.manageApp?.invoke(TEST_PACKAGE_NAME, USER_ID, TEST_INTENT)
+        verify(activityStarter).startActivity(any(), eq(true), capture(activityStartedCaptor))
+
+        activityStartedCaptor.value.onActivityStarted(ActivityManager.START_DELIVERED_TO_TOP)
+
+        verify(dialog).dismiss()
+    }
+
+    @Test
+    fun testStartActivityFailure() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.manageApp?.invoke(TEST_PACKAGE_NAME, USER_ID, TEST_INTENT)
+        verify(activityStarter).startActivity(any(), eq(true), capture(activityStartedCaptor))
+
+        activityStartedCaptor.value.onActivityStarted(ActivityManager.START_ABORTED)
+
+        verify(dialog, never()).dismiss()
+    }
+
+    @Test
+    fun testCallOnSecondaryUser() {
+        // Calls happen in
+        val usage = createMockPermGroupUsage(uid = SYSTEM_UID, isPhoneCall = true)
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        `when`(userTracker.userProfiles).thenReturn(listOf(
+                UserInfo(ENT_USER_ID, "", 0)
+        ))
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialog).show()
+    }
+
+    @Test
+    fun testManageAppLogs() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.manageApp?.invoke(TEST_PACKAGE_NAME, USER_ID, TEST_INTENT)
+        verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
+                USER_ID, TEST_PACKAGE_NAME)
+    }
+
+    @Test
+    fun testCloseAppLogs() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.closeApp?.invoke(TEST_PACKAGE_NAME, USER_ID)
+        verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP,
+                USER_ID, TEST_PACKAGE_NAME)
+    }
+
+    @Test
+    fun testOpenPrivacyDashboardLogs() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.openPrivacyDashboard?.invoke()
+        verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_CLICK_TO_PRIVACY_DASHBOARD)
+    }
+
+    @Test
+    fun testDismissedDialogLogs() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        verify(dialog).addOnDismissListener(capture(dialogDismissedCaptor))
+
+        dialogDismissedCaptor.value.onDialogDismissed()
+
+        controller.dismissDialog()
+
+        verify(uiEventLogger, times(1)).log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
+    }
+
+    @Test
+    fun testDefaultIntent() {
+        val usage = createMockPermGroupUsage()
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
+                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
+                    .isTrue()
+            assertThat(list.get(0).isService).isFalse()
+        }
+    }
+
+    @Test
+    fun testDefaultIntentOnEnterpriseUser() {
+        val usage =
+            createMockPermGroupUsage(
+                uid = generateUidForUser(ENT_USER_ID),
+            )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
+                    controller.getDefaultManageAppPermissionsIntent(
+                        TEST_PACKAGE_NAME, ENT_USER_ID)))
+                        .isTrue()
+            assertThat(list.get(0).isService).isFalse()
+        }
+    }
+
+    @Test
+    fun testDefaultIntentOnInvalidAttributionTag() {
+        val usage = createMockPermGroupUsage(
+                attributionTag = "INVALID_ATTRIBUTION_TAG",
+                proxyLabel = TEST_PROXY_LABEL
+        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
+                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
+                    .isTrue()
+            assertThat(list.get(0).isService).isFalse()
+        }
+    }
+
+    @Test
+    fun testServiceIntentOnCorrectSubAttribution() {
+        val usage = createMockPermGroupUsage(
+                attributionTag = TEST_ATTRIBUTION_TAG,
+                attributionLabel = "TEST_LABEL"
+        )
+
+        val activityInfo = createMockActivityInfo()
+        val resolveInfo = createMockResolveInfo(activityInfo)
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>()))
+                .thenAnswer { resolveInfo }
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            val navigationIntent = list.get(0).navigationIntent!!
+            assertThat(navigationIntent.action).isEqualTo(Intent.ACTION_MANAGE_PERMISSION_USAGE)
+            assertThat(navigationIntent.getStringExtra(Intent.EXTRA_PERMISSION_GROUP_NAME))
+                    .isEqualTo(PERM_CAMERA)
+            assertThat(navigationIntent.getStringArrayExtra(Intent.EXTRA_ATTRIBUTION_TAGS))
+                    .isEqualTo(arrayOf(TEST_ATTRIBUTION_TAG.toString()))
+            assertThat(navigationIntent.getBooleanExtra(Intent.EXTRA_SHOWING_ATTRIBUTION, false))
+                    .isTrue()
+            assertThat(list.get(0).isService).isTrue()
+        }
+    }
+
+    @Test
+    fun testDefaultIntentOnMissingAttributionLabel() {
+        val usage = createMockPermGroupUsage(
+                attributionTag = TEST_ATTRIBUTION_TAG
+        )
+
+        val activityInfo = createMockActivityInfo()
+        val resolveInfo = createMockResolveInfo(activityInfo)
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>()))
+                .thenAnswer { resolveInfo }
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
+                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
+                    .isTrue()
+            assertThat(list.get(0).isService).isFalse()
+        }
+    }
+
+    @Test
+    fun testDefaultIntentOnIncorrectPermission() {
+        val usage = createMockPermGroupUsage(
+                attributionTag = TEST_ATTRIBUTION_TAG
+        )
+
+        val activityInfo = createMockActivityInfo(
+                permission = "INCORRECT_PERMISSION"
+        )
+        val resolveInfo = createMockResolveInfo(activityInfo)
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>()))
+                .thenAnswer { resolveInfo }
+        controller.showDialog(context)
+        exhaustExecutors()
+
+        dialogProvider.list?.let { list ->
+            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
+                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
+                    .isTrue()
+            assertThat(list.get(0).isService).isFalse()
+        }
+    }
+
+    private fun exhaustExecutors() {
+        FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
+    }
+
+    private fun setUpDefaultMockResponses() {
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(emptyList())
+        `when`(appOpsController.isMicMuted).thenReturn(false)
+
+        `when`(packageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenAnswer { FakeApplicationInfo(it.getArgument(0)) }
+
+        `when`(privacyItemController.locationAvailable).thenReturn(true)
+        `when`(privacyItemController.micCameraAvailable).thenReturn(true)
+
+        `when`(userTracker.userProfiles).thenReturn(listOf(
+                UserInfo(USER_ID, "", 0),
+                UserInfo(ENT_USER_ID, "", UserInfo.FLAG_MANAGED_PROFILE)
+        ))
+
+        `when`(keyguardStateController.isUnlocked).thenReturn(true)
+    }
+
+    private class FakeApplicationInfo(val label: CharSequence) : ApplicationInfo() {
+        override fun loadLabel(pm: PackageManager): CharSequence {
+            return label
+        }
+    }
+
+    private fun generateUidForUser(user: Int): Int {
+        return user * UserHandle.PER_USER_RANGE + nextUid++
+    }
+
+    private fun createMockResolveInfo(
+        activityInfo: ActivityInfo? = null
+    ): ResolveInfo {
+        val resolveInfo = mock(ResolveInfo::class.java)
+        resolveInfo.activityInfo = activityInfo
+        return resolveInfo
+    }
+
+    private fun createMockActivityInfo(
+        permission: String = android.Manifest.permission.START_VIEW_PERMISSION_USAGE,
+        className: String = "TEST_CLASS_NAME"
+    ): ActivityInfo {
+        val activityInfo = mock(ActivityInfo::class.java)
+        activityInfo.permission = permission
+        activityInfo.name = className
+        return activityInfo
+    }
+
+    private fun createMockPermGroupUsage(
+        packageName: String = TEST_PACKAGE_NAME,
+        uid: Int = generateUidForUser(USER_ID),
+        permissionGroupName: String = PERM_CAMERA,
+        lastAccessTimeMillis: Long = 0L,
+        isActive: Boolean = false,
+        isPhoneCall: Boolean = false,
+        attributionTag: CharSequence? = null,
+        attributionLabel: CharSequence? = null,
+        proxyLabel: CharSequence? = null
+    ): PermissionGroupUsage {
+        val usage = mock(PermissionGroupUsage::class.java)
+        `when`(usage.packageName).thenReturn(packageName)
+        `when`(usage.uid).thenReturn(uid)
+        `when`(usage.permissionGroupName).thenReturn(permissionGroupName)
+        `when`(usage.lastAccessTimeMillis).thenReturn(lastAccessTimeMillis)
+        `when`(usage.isActive).thenReturn(isActive)
+        `when`(usage.isPhoneCall).thenReturn(isPhoneCall)
+        `when`(usage.attributionTag).thenReturn(attributionTag)
+        `when`(usage.attributionLabel).thenReturn(attributionLabel)
+        `when`(usage.proxyLabel).thenReturn(proxyLabel)
+        return usage
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt
new file mode 100644
index 0000000..f4644a5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt
@@ -0,0 +1,322 @@
+/*
+ * 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.privacy
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class PrivacyDialogV2Test : SysuiTestCase() {
+
+    companion object {
+        private const val TEST_PACKAGE_NAME = "test_pkg"
+        private const val TEST_USER_ID = 0
+        private const val TEST_PERM_GROUP = "test_perm_group"
+
+        private val TEST_INTENT = Intent("test_intent_action")
+
+        private fun createPrivacyElement(
+            type: PrivacyType = PrivacyType.TYPE_MICROPHONE,
+            packageName: String = TEST_PACKAGE_NAME,
+            userId: Int = TEST_USER_ID,
+            applicationName: CharSequence = "App",
+            attributionTag: CharSequence? = null,
+            attributionLabel: CharSequence? = null,
+            proxyLabel: CharSequence? = null,
+            lastActiveTimestamp: Long = 0L,
+            isActive: Boolean = false,
+            isPhoneCall: Boolean = false,
+            isService: Boolean = false,
+            permGroupName: String = TEST_PERM_GROUP,
+            navigationIntent: Intent = TEST_INTENT
+        ) =
+            PrivacyDialogV2.PrivacyElement(
+                type,
+                packageName,
+                userId,
+                applicationName,
+                attributionTag,
+                attributionLabel,
+                proxyLabel,
+                lastActiveTimestamp,
+                isActive,
+                isPhoneCall,
+                isService,
+                permGroupName,
+                navigationIntent
+            )
+    }
+
+    @Mock private lateinit var manageApp: (String, Int, Intent) -> Unit
+    @Mock private lateinit var closeApp: (String, Int) -> Unit
+    @Mock private lateinit var openPrivacyDashboard: () -> Unit
+    private lateinit var dialog: PrivacyDialogV2
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @After
+    fun teardown() {
+        if (this::dialog.isInitialized) {
+            dialog.dismiss()
+        }
+    }
+
+    @Test
+    fun testManageAppCalledWithCorrectParams() {
+        val list = listOf(createPrivacyElement())
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+        dialog.show()
+
+        dialog.requireViewById<View>(R.id.privacy_dialog_manage_app_button).callOnClick()
+
+        verify(manageApp).invoke(TEST_PACKAGE_NAME, TEST_USER_ID, TEST_INTENT)
+    }
+
+    @Test
+    fun testCloseAppCalledWithCorrectParams() {
+        val list = listOf(createPrivacyElement(isActive = true))
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+        dialog.show()
+
+        dialog.requireViewById<View>(R.id.privacy_dialog_close_app_button).callOnClick()
+
+        verify(closeApp).invoke(TEST_PACKAGE_NAME, TEST_USER_ID)
+    }
+
+    @Test
+    fun testCloseAppMissingForService() {
+        val list = listOf(createPrivacyElement(isActive = true, isService = true))
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.findViewById<View>(R.id.privacy_dialog_manage_app_button)).isNotNull()
+        assertThat(dialog.findViewById<View>(R.id.privacy_dialog_close_app_button)).isNull()
+    }
+
+    @Test
+    fun testMoreButton() {
+        dialog = PrivacyDialogV2(context, emptyList(), manageApp, closeApp, openPrivacyDashboard)
+        dialog.show()
+
+        dialog.requireViewById<View>(R.id.privacy_dialog_more_button).callOnClick()
+
+        verify(openPrivacyDashboard).invoke()
+    }
+
+    @Test
+    fun testCloseButton() {
+        dialog = PrivacyDialogV2(context, emptyList(), manageApp, closeApp, openPrivacyDashboard)
+        val dismissListener = mock(PrivacyDialogV2.OnDialogDismissed::class.java)
+        dialog.addOnDismissListener(dismissListener)
+        dialog.show()
+        verify(dismissListener, never()).onDialogDismissed()
+
+        dialog.requireViewById<View>(R.id.privacy_dialog_close_button).callOnClick()
+
+        verify(dismissListener).onDialogDismissed()
+    }
+
+    @Test
+    fun testDismissListenerCalledOnDismiss() {
+        dialog = PrivacyDialogV2(context, emptyList(), manageApp, closeApp, openPrivacyDashboard)
+        val dismissListener = mock(PrivacyDialogV2.OnDialogDismissed::class.java)
+        dialog.addOnDismissListener(dismissListener)
+        dialog.show()
+        verify(dismissListener, never()).onDialogDismissed()
+
+        dialog.dismiss()
+
+        verify(dismissListener).onDialogDismissed()
+    }
+
+    @Test
+    fun testDismissListenerCalledImmediatelyIfDialogAlreadyDismissed() {
+        dialog = PrivacyDialogV2(context, emptyList(), manageApp, closeApp, openPrivacyDashboard)
+        val dismissListener = mock(PrivacyDialogV2.OnDialogDismissed::class.java)
+        dialog.show()
+        dialog.dismiss()
+
+        dialog.addOnDismissListener(dismissListener)
+
+        verify(dismissListener).onDialogDismissed()
+    }
+
+    @Test
+    fun testCorrectNumElements() {
+        val list =
+            listOf(
+                createPrivacyElement(type = PrivacyType.TYPE_CAMERA, isActive = true),
+                createPrivacyElement()
+            )
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(
+                dialog.requireViewById<ViewGroup>(R.id.privacy_dialog_items_container).childCount
+            )
+            .isEqualTo(2)
+    }
+
+    @Test
+    fun testHeaderText() {
+        val list = listOf(createPrivacyElement())
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_title).text)
+            .isEqualTo(TEST_PERM_GROUP)
+    }
+
+    @Test
+    fun testUsingText() {
+        val list = listOf(createPrivacyElement(type = PrivacyType.TYPE_CAMERA, isActive = true))
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text)
+            .isEqualTo("In use by App")
+    }
+
+    @Test
+    fun testRecentText() {
+        val list = listOf(createPrivacyElement())
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text)
+            .isEqualTo("Recently used by App")
+    }
+
+    @Test
+    fun testPhoneCall() {
+        val list = listOf(createPrivacyElement(isPhoneCall = true))
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text)
+            .isEqualTo("Recently used in phone call")
+    }
+
+    @Test
+    fun testPhoneCallNotClickable() {
+        val list = listOf(createPrivacyElement(isPhoneCall = true))
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.requireViewById<View>(R.id.privacy_dialog_item_card).isClickable)
+            .isFalse()
+        assertThat(
+                dialog
+                    .requireViewById<View>(R.id.privacy_dialog_item_header_expand_toggle)
+                    .visibility
+            )
+            .isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun testProxyLabel() {
+        val list = listOf(createPrivacyElement(proxyLabel = "proxy label"))
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text)
+            .isEqualTo("Recently used by App (proxy label)")
+    }
+
+    @Test
+    fun testSubattribution() {
+        val list =
+            listOf(
+                createPrivacyElement(
+                    attributionLabel = "For subattribution",
+                    isActive = true,
+                    isService = true
+                )
+            )
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+
+        dialog.show()
+
+        assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text)
+            .isEqualTo("In use by App (For subattribution)")
+    }
+
+    @Test
+    fun testSubattributionAndProxyLabel() {
+        val list =
+            listOf(
+                createPrivacyElement(
+                    attributionLabel = "For subattribution",
+                    proxyLabel = "proxy label",
+                    isActive = true
+                )
+            )
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+        dialog.show()
+        assertThat(dialog.requireViewById<TextView>(R.id.privacy_dialog_item_header_summary).text)
+            .isEqualTo("In use by App (For subattribution \u2022 proxy label)")
+    }
+
+    @Test
+    fun testDialogHasTitle() {
+        val list = listOf(createPrivacyElement())
+        dialog = PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+        dialog.show()
+
+        assertThat(dialog.window?.attributes?.title).isEqualTo("Microphone & Camera")
+    }
+
+    @Test
+    fun testDialogIsFullscreen() {
+        dialog = PrivacyDialogV2(context, emptyList(), manageApp, closeApp, openPrivacyDashboard)
+        dialog.show()
+
+        assertThat(dialog.window?.attributes?.width).isEqualTo(MATCH_PARENT)
+        assertThat(dialog.window?.attributes?.height).isEqualTo(MATCH_PARENT)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
index 65210d6..e905e9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
@@ -132,7 +132,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isAbleToOpenCameraApp()).isFalse();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isFalse();
     }
 
     @Test
@@ -151,7 +151,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
     }
 
     @Test
@@ -161,7 +161,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
     }
 
     @Test
@@ -171,7 +171,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
     }
 
     @Test
@@ -181,7 +181,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/abc.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
     }
 
     @Test
@@ -191,7 +191,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isAbleToOpenCameraApp()).isFalse();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isFalse();
     }
 
     @Test
@@ -201,24 +201,24 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
         mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
                 "def/.ijk", false);
         verifyActivityDetails("def/.ijk");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
         mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
                 null, false);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
-        // Once from setup + twice from this function
-        verify(mCallback, times(3)).onQRCodeScannerActivityChanged();
+        // twice from this function
+        verify(mCallback, times(2)).onQRCodeScannerActivityChanged();
     }
 
     @Test
@@ -228,7 +228,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isAbleToOpenCameraApp()).isFalse();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isFalse();
 
         mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
@@ -236,14 +236,14 @@
 
         verifyActivityDetails("def/.ijk");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
         mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
                 null, false);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isAbleToOpenCameraApp()).isFalse();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isFalse();
         verify(mCallback, times(2)).onQRCodeScannerActivityChanged();
     }
 
@@ -295,19 +295,20 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
         mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
                 UserHandle.USER_CURRENT);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAllowedOnLockScreen()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
         mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1",
                 UserHandle.USER_CURRENT);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
         // Once from setup + twice from this function
         verify(mCallback, times(3)).onQRCodeScannerPreferenceChanged();
     }
@@ -319,13 +320,13 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
+        // even if unregistered, intent and activity details are retained
         mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
                 QR_CODE_SCANNER_PREFERENCE_CHANGE);
-        verifyActivityDetails(null);
-        assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isAbleToOpenCameraApp()).isFalse();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
+        assertThat(mController.isAllowedOnLockScreen()).isTrue();
 
         // Unregister once again and make sure it affects the next register event
         mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
@@ -334,7 +335,7 @@
                 QR_CODE_SCANNER_PREFERENCE_CHANGE);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
     }
 
     @Test
@@ -344,7 +345,7 @@
                 /* enableOnLockScreen */ false);
         assertThat(mController.getIntent()).isNotNull();
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
         assertThat(getSettingsQRCodeDefaultComponent()).isNull();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
index 3620233..fa02e8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
@@ -13,9 +13,12 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.appops.AppOpsController
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.privacy.PrivacyDialogController
+import com.android.systemui.privacy.PrivacyDialogControllerV2
 import com.android.systemui.privacy.PrivacyItemController
 import com.android.systemui.privacy.logging.PrivacyLogger
 import com.android.systemui.statusbar.phone.StatusIconContainer
@@ -24,6 +27,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.Before
@@ -54,6 +58,8 @@
     @Mock
     private lateinit var privacyDialogController: PrivacyDialogController
     @Mock
+    private lateinit var privacyDialogControllerV2: PrivacyDialogControllerV2
+    @Mock
     private lateinit var privacyLogger: PrivacyLogger
     @Mock
     private lateinit var iconContainer: StatusIconContainer
@@ -69,6 +75,8 @@
     private lateinit var safetyCenterManager: SafetyCenterManager
     @Mock
     private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock
+    private lateinit var featureFlags: FeatureFlags
 
     private val uiExecutor = FakeExecutor(FakeSystemClock())
     private val backgroundExecutor = FakeExecutor(FakeSystemClock())
@@ -94,6 +102,7 @@
                 uiEventLogger,
                 privacyChip,
                 privacyDialogController,
+                privacyDialogControllerV2,
                 privacyLogger,
                 iconContainer,
                 permissionManager,
@@ -103,7 +112,8 @@
                 appOpsController,
                 broadcastDispatcher,
                 safetyCenterManager,
-                deviceProvisionedController
+                deviceProvisionedController,
+                featureFlags
         )
 
         backgroundExecutor.runAllReady()
@@ -154,17 +164,34 @@
     }
 
     @Test
-    fun testPrivacyChipClicked() {
+    fun testPrivacyChipClickedWhenNewDialogDisabledAndSafetyCenterDisabled() {
+        whenever(featureFlags.isEnabled(Flags.ENABLE_NEW_PRIVACY_DIALOG)).thenReturn(false)
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
         controller.onParentVisible()
         val captor = argumentCaptor<View.OnClickListener>()
         verify(privacyChip).setOnClickListener(capture(captor))
         captor.value.onClick(privacyChip)
         verify(privacyDialogController).showDialog(any(Context::class.java))
+        verify(privacyDialogControllerV2, never())
+            .showDialog(any(Context::class.java), any(View::class.java))
     }
 
     @Test
-    fun testSafetyCenterFlag() {
+    fun testPrivacyChipClickedWhenNewDialogEnabledAndSafetyCenterDisabled() {
+        whenever(featureFlags.isEnabled(Flags.ENABLE_NEW_PRIVACY_DIALOG)).thenReturn(true)
+        whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
+        controller.onParentVisible()
+        val captor = argumentCaptor<View.OnClickListener>()
+        verify(privacyChip).setOnClickListener(capture(captor))
+        captor.value.onClick(privacyChip)
+        verify(privacyDialogController).showDialog(any(Context::class.java))
+        verify(privacyDialogControllerV2, never())
+                .showDialog(any(Context::class.java), any(View::class.java))
+    }
+
+    @Test
+    fun testPrivacyChipClickedWhenNewDialogDisabledAndSafetyCenterEnabled() {
+        whenever(featureFlags.isEnabled(Flags.ENABLE_NEW_PRIVACY_DIALOG)).thenReturn(false)
         val receiverCaptor = argumentCaptor<BroadcastReceiver>()
         whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
         verify(broadcastDispatcher).registerReceiver(capture(receiverCaptor),
@@ -179,6 +206,28 @@
         verify(privacyChip).setOnClickListener(capture(captor))
         captor.value.onClick(privacyChip)
         verify(privacyDialogController, never()).showDialog(any(Context::class.java))
+        verify(privacyDialogControllerV2, never())
+            .showDialog(any(Context::class.java), any(View::class.java))
+    }
+
+    @Test
+    fun testPrivacyChipClickedWhenNewDialogEnabledAndSafetyCenterEnabled() {
+        whenever(featureFlags.isEnabled(Flags.ENABLE_NEW_PRIVACY_DIALOG)).thenReturn(true)
+        val receiverCaptor = argumentCaptor<BroadcastReceiver>()
+        whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
+        verify(broadcastDispatcher).registerReceiver(capture(receiverCaptor),
+                any(), any(), nullable(), anyInt(), nullable())
+        receiverCaptor.value.onReceive(
+                context,
+                Intent(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED)
+        )
+        backgroundExecutor.runAllReady()
+        controller.onParentVisible()
+        val captor = argumentCaptor<View.OnClickListener>()
+        verify(privacyChip).setOnClickListener(capture(captor))
+        captor.value.onClick(privacyChip)
+        verify(privacyDialogControllerV2).showDialog(any(Context::class.java), eq(privacyChip))
+        verify(privacyDialogController, never()).showDialog(any(Context::class.java))
     }
 
     @Test
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 aacc695..93f316e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt
@@ -49,7 +49,7 @@
         buffer.dump(PrintWriter(stringWriter), tailLength = 0)
         val actualString = stringWriter.toString()
         val expectedLogString = disableFlagsLogger.getDisableFlagsString(
-            old = null, new = state, newAfterLocalModification = state
+            new = state, newAfterLocalModification = state
         )
 
         assertThat(actualString).contains(expectedLogString)
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 87ca9df..fcda5f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -50,10 +50,8 @@
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.R;
 import com.android.systemui.SysuiBaseFragmentTest;
-import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.media.controls.ui.MediaHost;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSFragmentComponent;
@@ -169,9 +167,7 @@
     }
 
     @Test
-    public void transitionToFullShade_largeScreen_flagEnabled_alphaLargeScreenShadeInterpolator() {
-        when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
-                .thenReturn(true);
+    public void transitionToFullShade_largeScreen_alphaLargeScreenShadeInterpolator() {
         QSFragment fragment = resumeAndGetFragment();
         setIsLargeScreen();
         setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
@@ -188,25 +184,6 @@
     }
 
     @Test
-    public void transitionToFullShade_largeScreen_flagDisabled_alphaStandardInterpolator() {
-        when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
-                .thenReturn(false);
-        QSFragment fragment = resumeAndGetFragment();
-        setIsLargeScreen();
-        setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
-        boolean isTransitioningToFullShade = true;
-        float transitionProgress = 0.5f;
-        float squishinessFraction = 0.5f;
-        when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f);
-
-        fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
-                squishinessFraction);
-
-        assertThat(mQsFragmentView.getAlpha())
-                .isEqualTo(ShadeInterpolation.getContentAlpha(transitionProgress));
-    }
-
-    @Test
     public void
             transitionToFullShade_onKeyguard_noBouncer_setsAlphaUsingLinearInterpolator() {
         QSFragment fragment = resumeAndGetFragment();
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 a0d8f98..9d9d0c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -154,6 +154,21 @@
         verify(qsPanel).setCanCollapse(true)
     }
 
+    @Test
+    fun multipleListeningOnlyCallsBrightnessControllerOnce() {
+        controller.setListening(true, true)
+        controller.setListening(true, false)
+        controller.setListening(true, true)
+
+        verify(brightnessController).registerCallbacks()
+
+        controller.setListening(false, true)
+        controller.setListening(false, false)
+        controller.setListening(false, true)
+
+        verify(brightnessController).unregisterCallbacks()
+    }
+
     private fun setShouldUseSplitShade(shouldUse: Boolean) {
         testableResources.addOverride(R.bool.config_use_split_notification_shade, shouldUse)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index a60dad4..fe6c9b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -15,9 +15,11 @@
 
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
+import android.testing.TestableContext
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.ViewUtils
+import android.view.ContextThemeWrapper
 import android.view.View
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.accessibility.AccessibilityNodeInfo
@@ -55,19 +57,24 @@
 
     private lateinit var footer: View
 
+    private val themedContext = TestableContext(
+            ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+    )
+
     @Before
     @Throws(Exception::class)
     fun setup() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
+        // Apply only the values of the theme that are not defined
 
         testableLooper.runWithLooper {
-            qsPanel = QSPanel(context, null)
+            qsPanel = QSPanel(themedContext, null)
             qsPanel.mUsingMediaPlayer = true
 
             qsPanel.initialize(qsLogger)
             // QSPanel inflates a footer inside of it, mocking it here
-            footer = LinearLayout(context).apply { id = R.id.qs_footer }
+            footer = LinearLayout(themedContext).apply { id = R.id.qs_footer }
             qsPanel.addView(footer, MATCH_PARENT, 100)
             qsPanel.onFinishInflate()
             // Provides a parent with non-zero size for QSPanel
@@ -105,12 +112,12 @@
         qsPanel.tileLayout?.addTile(
                 QSPanelControllerBase.TileRecord(
                     mock(QSTile::class.java),
-                    QSTileViewImpl(context, QSIconViewImpl(context))
+                    QSTileViewImpl(themedContext, QSIconViewImpl(themedContext))
                 )
             )
 
-        val mediaView = FrameLayout(context)
-        mediaView.addView(View(context), MATCH_PARENT, 800)
+        val mediaView = FrameLayout(themedContext)
+        mediaView.addView(View(themedContext), MATCH_PARENT, 800)
 
         qsPanel.setUsingHorizontalLayout(/* horizontal */ true, mediaView, /* force */ true)
         qsPanel.measure(
@@ -135,12 +142,12 @@
         qsPanel.tileLayout?.addTile(
             QSPanelControllerBase.TileRecord(
                 mock(QSTile::class.java),
-                QSTileViewImpl(context, QSIconViewImpl(context))
+                QSTileViewImpl(themedContext, QSIconViewImpl(themedContext))
             )
         )
 
-        val mediaView = FrameLayout(context)
-        mediaView.addView(View(context), MATCH_PARENT, 800)
+        val mediaView = FrameLayout(themedContext)
+        mediaView.addView(View(themedContext), MATCH_PARENT, 800)
 
         qsPanel.setUsingHorizontalLayout(/* horizontal */ true, mediaView, /* force */ true)
         qsPanel.measure(
@@ -161,7 +168,10 @@
     @Test
     fun testBottomPadding() {
         val padding = 10
-        context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_bottom, padding)
+        themedContext.orCreateTestableResources.addOverride(
+                R.dimen.qs_panel_padding_bottom,
+                padding
+        )
         qsPanel.updatePadding()
         assertThat(qsPanel.paddingBottom).isEqualTo(padding)
     }
@@ -170,8 +180,11 @@
     fun testTopPadding() {
         val padding = 10
         val paddingCombined = 100
-        context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
-        context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, paddingCombined)
+        themedContext.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding)
+        themedContext.orCreateTestableResources.addOverride(
+                R.dimen.qs_panel_padding_top,
+                paddingCombined
+        )
 
         qsPanel.updatePadding()
         assertThat(qsPanel.paddingTop).isEqualTo(paddingCombined)
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 587c49d..9781baa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -67,8 +68,8 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserFileManager;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.FakeSharedPreferences;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -86,7 +87,6 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.List;
-import java.util.Optional;
 import java.util.concurrent.Executor;
 
 import javax.inject.Provider;
@@ -108,9 +108,9 @@
     @Mock
     private TunerService mTunerService;
     @Mock
-    private Provider<AutoTileManager> mAutoTiles;
+    private AutoTileManager mAutoTiles;
     @Mock
-    private CentralSurfaces mCentralSurfaces;
+    private ShadeController mShadeController;
     @Mock
     private QSLogger mQSLogger;
     @Mock
@@ -141,6 +141,7 @@
         mFeatureFlags = new FakeFeatureFlags();
 
         mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, false);
+        mFeatureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, false);
 
         mMainExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -161,9 +162,10 @@
         mSecureSettings = new FakeSettings();
         saveSetting("");
         mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
-                mPluginManager, mTunerService, mAutoTiles, mCentralSurfaces,
+                mPluginManager, mTunerService, () -> mAutoTiles, mShadeController,
                 mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
                 mTileLifecycleManagerFactory, mUserFileManager, mFeatureFlags);
+        mMainExecutor.runAllReady();
 
         mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
             @Override
@@ -682,6 +684,17 @@
         assertEquals(CUSTOM_TILE.getClassName(), proto.tiles[1].getComponentName().className);
     }
 
+    @Test
+    public void testUserChange_flagOn_autoTileManagerNotified() {
+        mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true);
+        int currentUser = mUserTracker.getUserId();
+        clearInvocations(mAutoTiles);
+        when(mUserTracker.getUserId()).thenReturn(currentUser + 1);
+
+        mQSTileHost.onTuningChanged(SETTING, "a,b");
+        verify(mAutoTiles).changeUser(UserHandle.of(currentUser + 1));
+    }
+
     private SharedPreferences getSharedPreferencesForUser(int user) {
         return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user);
     }
@@ -691,13 +704,13 @@
                 QSFactory defaultFactory, Executor mainExecutor,
                 PluginManager pluginManager, TunerService tunerService,
                 Provider<AutoTileManager> autoTiles,
-                CentralSurfaces centralSurfaces, QSLogger qsLogger,
+                ShadeController shadeController, QSLogger qsLogger,
                 UserTracker userTracker, SecureSettings secureSettings,
                 CustomTileStatePersister customTileStatePersister,
                 TileLifecycleManager.Factory tileLifecycleManagerFactory,
                 UserFileManager userFileManager, FeatureFlags featureFlags) {
             super(context, defaultFactory, mainExecutor, pluginManager,
-                    tunerService, autoTiles,  Optional.of(centralSurfaces), qsLogger,
+                    tunerService, autoTiles,  shadeController, qsLogger,
                     userTracker, secureSettings, customTileStatePersister,
                     tileLifecycleManagerFactory, userFileManager, featureFlags);
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 8789253..f55ef65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -26,12 +26,14 @@
 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 android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableLooper;
+import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -56,14 +58,17 @@
     private Resources mResources;
     private int mLayoutSizeForOneTile;
     private TileLayout mTileLayout; // under test
+    private Context mSpyContext;
+
 
     @Before
     public void setUp() throws Exception {
-        Context context = Mockito.spy(mContext);
-        mResources = Mockito.spy(context.getResources());
-        Mockito.when(mContext.getResources()).thenReturn(mResources);
+        mSpyContext = Mockito.spy(
+                new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_QuickSettings));
+        mResources = Mockito.spy(mSpyContext.getResources());
+        when(mSpyContext.getResources()).thenReturn(mResources);
 
-        mTileLayout = new TileLayout(context);
+        mTileLayout = new TileLayout(mSpyContext);
         // Layout needs to leave space for the tile margins. Three times the margin size is
         // sufficient for any number of columns.
         mLayoutSizeForOneTile =
@@ -73,7 +78,7 @@
     private QSPanelControllerBase.TileRecord createTileRecord() {
         return new QSPanelControllerBase.TileRecord(
                 mock(QSTile.class),
-                spy(new QSTileViewImpl(mContext, new QSIconViewImpl(mContext))));
+                spy(new QSTileViewImpl(mSpyContext, new QSIconViewImpl(mSpyContext))));
     }
 
     @Test
@@ -161,7 +166,7 @@
                 .layout(left2.capture(), top2.capture(), right2.capture(), bottom2.capture());
 
         // We assume two tiles will always fit side-by-side.
-        assertTrue(mContext.getResources().getInteger(R.integer.quick_settings_num_columns) > 1);
+        assertTrue(mSpyContext.getResources().getInteger(R.integer.quick_settings_num_columns) > 1);
 
         // left <= right, top <= bottom
         assertTrue(left1.getValue() <= right1.getValue());
@@ -218,16 +223,16 @@
 
     @Test
     public void resourcesChanged_updateResources_returnsTrue() {
-        Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+        when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
         mTileLayout.updateResources(); // setup with 1
-        Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2);
+        when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2);
 
         assertEquals(true, mTileLayout.updateResources());
     }
 
     @Test
     public void resourcesSame_updateResources_returnsFalse() {
-        Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+        when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
         mTileLayout.updateResources(); // setup with 1
 
         assertEquals(false, mTileLayout.updateResources());
@@ -250,7 +255,7 @@
         QSPanelControllerBase.TileRecord tileRecord = createTileRecord();
         mTileLayout.addTile(tileRecord);
 
-        FakeTileView tileView = new FakeTileView(mContext);
+        FakeTileView tileView = new FakeTileView(mSpyContext);
         QSTile.State state = new QSTile.State();
         state.label = "TEST LABEL";
         state.secondaryLabel = "TEST SECONDARY LABEL";
@@ -276,9 +281,10 @@
     }
 
     private void changeFontScaling(float scale) {
-        Configuration configuration = new Configuration(mContext.getResources().getConfiguration());
+        Configuration configuration =
+                new Configuration(mSpyContext.getResources().getConfiguration());
         configuration.fontScale = scale;
         // updateConfiguration could help update on both resource configuration and displayMetrics
-        mContext.getResources().updateConfiguration(configuration, null, null);
+        mSpyContext.getResources().updateConfiguration(configuration, null, null);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index 198ed4a..41240e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -35,7 +35,7 @@
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.LaunchableFrameLayout
+import com.android.systemui.animation.view.LaunchableFrameLayout
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.qs.QSTile
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 2cc6709..d647d6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -21,6 +21,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
+import android.view.ContextThemeWrapper
 import androidx.test.filters.SmallTest
 import com.android.settingslib.Utils
 import com.android.settingslib.drawable.UserIconDrawable
@@ -63,6 +64,8 @@
     private val testScope = TestScope()
     private lateinit var utils: FooterActionsTestUtils
 
+    private val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+
     @Before
     fun setUp() {
         utils = FooterActionsTestUtils(context, TestableLooper.get(this), testScope.testScheduler)
@@ -84,8 +87,14 @@
                     ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
                 )
             )
-        assertThat(settings.backgroundColor).isEqualTo(R.attr.offStateColor)
-        assertThat(settings.iconTint).isNull()
+        assertThat(settings.backgroundColor).isEqualTo(R.attr.shadeInactive)
+        assertThat(settings.iconTint)
+            .isEqualTo(
+                Utils.getColorAttrDefaultColor(
+                    themedContext,
+                    R.attr.onShadeInactiveVariant,
+                )
+            )
     }
 
     @Test
@@ -105,12 +114,12 @@
                     ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
                 )
             )
-        assertThat(power.backgroundColor).isEqualTo(com.android.internal.R.attr.colorAccent)
+        assertThat(power.backgroundColor).isEqualTo(R.attr.shadeActive)
         assertThat(power.iconTint)
             .isEqualTo(
                 Utils.getColorAttrDefaultColor(
-                    context,
-                    com.android.internal.R.attr.textColorOnAccent,
+                    themedContext,
+                    R.attr.onShadeActive,
                 ),
             )
     }
@@ -170,7 +179,7 @@
         assertThat(userSwitcher).isNotNull()
         assertThat(userSwitcher!!.icon)
             .isEqualTo(Icon.Loaded(picture, ContentDescription.Loaded("Signed in as foo")))
-        assertThat(userSwitcher.backgroundColor).isEqualTo(R.attr.offStateColor)
+        assertThat(userSwitcher.backgroundColor).isEqualTo(R.attr.shadeInactive)
 
         // Change the current user name.
         userSwitcherControllerWrapper.currentUserName = "bar"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
index 18f3837..dc0fae5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -75,6 +76,9 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        whenever(context.createContextAsUser(any(), anyInt())).thenReturn(context)
+        whenever(context.packageManager).thenReturn(packageManager)
+
         // Use the default value set in the ServiceInfo
         whenever(packageManager.getComponentEnabledSetting(any()))
             .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
@@ -86,7 +90,6 @@
         underTest =
             InstalledTilesComponentRepositoryImpl(
                 context,
-                packageManager,
                 testDispatcher,
             )
     }
@@ -224,6 +227,52 @@
             assertThat(componentNames).isEmpty()
         }
 
+    @Test
+    fun packageOnlyInSecondaryUser_noException() =
+        testScope.runTest {
+            val userId = 10
+            val secondaryUserContext: Context = mock()
+            whenever(context.userId).thenReturn(0) // System context
+            whenever(context.createContextAsUser(eq(UserHandle.of(userId)), anyInt()))
+                .thenReturn(secondaryUserContext)
+
+            val secondaryUserPackageManager: PackageManager = mock()
+            whenever(secondaryUserContext.packageManager).thenReturn(secondaryUserPackageManager)
+
+            // Use the default value set in the ServiceInfo
+            whenever(secondaryUserPackageManager.getComponentEnabledSetting(any()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+            // System User package manager throws exception if the component doesn't exist for that
+            // user
+            whenever(packageManager.getComponentEnabledSetting(TEST_COMPONENT))
+                .thenThrow(IllegalArgumentException()) // The package is not in the system user
+
+            val resolveInfo =
+                ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
+            // Both package manager should return the same (because the query is for the secondary
+            // user)
+            whenever(
+                    secondaryUserPackageManager.queryIntentServicesAsUser(
+                        matchIntent(),
+                        matchFlags(),
+                        eq(userId)
+                    )
+                )
+                .thenReturn(listOf(resolveInfo))
+            whenever(
+                    packageManager.queryIntentServicesAsUser(
+                        matchIntent(),
+                        matchFlags(),
+                        eq(userId)
+                    )
+                )
+                .thenReturn(listOf(resolveInfo))
+
+            val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+
+            assertThat(componentNames).containsExactly(TEST_COMPONENT)
+        }
+
     private fun getRegisteredReceiver(): BroadcastReceiver {
         verify(context)
             .registerReceiverAsUser(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
new file mode 100644
index 0000000..817ac61
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AutoAddableSettingListTest : SysuiTestCase() {
+
+    private val factory =
+        object : AutoAddableSetting.Factory {
+            override fun create(setting: String, spec: TileSpec): AutoAddableSetting {
+                return AutoAddableSetting(
+                    mock(),
+                    mock(),
+                    setting,
+                    spec,
+                )
+            }
+        }
+
+    @Test
+    fun correctLines_correctAutoAddables() {
+        val setting1 = "setting1"
+        val setting2 = "setting2"
+        val spec1 = TileSpec.create("spec1")
+        val spec2 = TileSpec.create(ComponentName("pkg", "cls"))
+
+        context.orCreateTestableResources.addOverride(
+            R.array.config_quickSettingsAutoAdd,
+            arrayOf(toStringLine(setting1, spec1), toStringLine(setting2, spec2))
+        )
+
+        val autoAddables = AutoAddableSettingList.parseSettingsResource(context.resources, factory)
+
+        assertThat(autoAddables)
+            .containsExactly(factory.create(setting1, spec1), factory.create(setting2, spec2))
+    }
+
+    @Test
+    fun malformedLine_ignored() {
+        val setting = "setting"
+        val spec = TileSpec.create("spec")
+
+        context.orCreateTestableResources.addOverride(
+            R.array.config_quickSettingsAutoAdd,
+            arrayOf(toStringLine(setting, spec), "bad_line")
+        )
+
+        val autoAddables = AutoAddableSettingList.parseSettingsResource(context.resources, factory)
+
+        assertThat(autoAddables).containsExactly(factory.create(setting, spec))
+    }
+
+    @Test
+    fun invalidSpec_ignored() {
+        val setting = "setting"
+        val spec = TileSpec.create("spec")
+
+        context.orCreateTestableResources.addOverride(
+            R.array.config_quickSettingsAutoAdd,
+            arrayOf(toStringLine(setting, spec), "invalid:")
+        )
+
+        val autoAddables = AutoAddableSettingList.parseSettingsResource(context.resources, factory)
+
+        assertThat(autoAddables).containsExactly(factory.create(setting, spec))
+    }
+
+    companion object {
+        private fun toStringLine(setting: String, spec: TileSpec) =
+            "$setting$SETTINGS_SEPARATOR${spec.spec}"
+        private const val SETTINGS_SEPARATOR = ":"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
new file mode 100644
index 0000000..36c3c9d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AutoAddableSettingTest : SysuiTestCase() {
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val secureSettings = FakeSettings()
+    private val underTest =
+        AutoAddableSetting(
+            secureSettings,
+            testDispatcher,
+            SETTING,
+            SPEC,
+        )
+
+    @Test
+    fun settingNotSet_noSignal() =
+        testScope.runTest {
+            val userId = 0
+            val signal by collectLastValue(underTest.autoAddSignal(userId))
+
+            assertThat(signal).isNull() // null means no emitted value
+        }
+
+    @Test
+    fun settingSetTo0_noSignal() =
+        testScope.runTest {
+            val userId = 0
+            val signal by collectLastValue(underTest.autoAddSignal(userId))
+
+            secureSettings.putIntForUser(SETTING, 0, userId)
+
+            assertThat(signal).isNull() // null means no emitted value
+        }
+
+    @Test
+    fun settingSetToNon0_signal() =
+        testScope.runTest {
+            val userId = 0
+            val signal by collectLastValue(underTest.autoAddSignal(userId))
+
+            secureSettings.putIntForUser(SETTING, 42, userId)
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun settingSetForUser_onlySignalInThatUser() =
+        testScope.runTest {
+            val signal0 by collectLastValue(underTest.autoAddSignal(0))
+            val signal1 by collectLastValue(underTest.autoAddSignal(1))
+
+            secureSettings.putIntForUser(SETTING, /* value */ 42, /* userHandle */ 1)
+
+            assertThat(signal0).isNull()
+            assertThat(signal1).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun multipleNonZeroChanges_onlyOneSignal() =
+        testScope.runTest {
+            val userId = 0
+            val signals by collectValues(underTest.autoAddSignal(userId))
+
+            secureSettings.putIntForUser(SETTING, 1, userId)
+            secureSettings.putIntForUser(SETTING, 2, userId)
+
+            assertThat(signals.size).isEqualTo(1)
+        }
+
+    @Test
+    fun strategyIfNotAdded() {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+    }
+
+    companion object {
+        private const val SETTING = "setting"
+        private val SPEC = TileSpec.create("spec")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
new file mode 100644
index 0000000..afb43c7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.CallbackController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CallbackControllerAutoAddableTest : SysuiTestCase() {
+
+    @Test
+    fun callbackAddedAndRemoved() = runTest {
+        val controller = TestableController()
+        val callback = object : TestableController.Callback {}
+        val underTest =
+            object :
+                CallbackControllerAutoAddable<TestableController.Callback, TestableController>(
+                    controller
+                ) {
+                override val description: String = ""
+                override val spec: TileSpec
+                    get() = SPEC
+
+                override fun ProducerScope<AutoAddSignal>.getCallback():
+                    TestableController.Callback {
+                    return callback
+                }
+            }
+
+        val job = launch { underTest.autoAddSignal(0).collect {} }
+        runCurrent()
+        assertThat(controller.callbacks).containsExactly(callback)
+        job.cancel()
+        runCurrent()
+        assertThat(controller.callbacks).isEmpty()
+    }
+
+    @Test
+    fun sendAddFromCallback() = runTest {
+        val controller = TestableController()
+        val underTest =
+            object :
+                CallbackControllerAutoAddable<TestableController.Callback, TestableController>(
+                    controller
+                ) {
+                override val description: String = ""
+
+                override val spec: TileSpec
+                    get() = SPEC
+
+                override fun ProducerScope<AutoAddSignal>.getCallback():
+                    TestableController.Callback {
+                    return object : TestableController.Callback {
+                        override fun change() {
+                            sendAdd()
+                        }
+                    }
+                }
+            }
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        assertThat(signal).isNull()
+
+        controller.callbacks.first().change()
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun strategyIfNotAdded() {
+        val underTest =
+            object :
+                CallbackControllerAutoAddable<TestableController.Callback, TestableController>(
+                    TestableController()
+                ) {
+                override val description: String = ""
+                override val spec: TileSpec
+                    get() = SPEC
+
+                override fun ProducerScope<AutoAddSignal>.getCallback():
+                    TestableController.Callback {
+                    return object : TestableController.Callback {}
+                }
+            }
+
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+    }
+
+    private class TestableController : CallbackController<TestableController.Callback> {
+
+        val callbacks = mutableSetOf<Callback>()
+
+        override fun addCallback(listener: Callback) {
+            callbacks.add(listener)
+        }
+
+        override fun removeCallback(listener: Callback) {
+            callbacks.remove(listener)
+        }
+
+        interface Callback {
+            fun change() {}
+        }
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create("test")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
new file mode 100644
index 0000000..a357dad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.CastTile
+import com.android.systemui.statusbar.policy.CastController
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CastAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var castController: CastController
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<CastController.Callback>
+
+    private lateinit var underTest: CastAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = CastAutoAddable(castController)
+    }
+
+    @Test
+    fun onCastDevicesChanged_noDevices_noSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(castController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onCastDevicesChanged()
+
+        assertThat(signal).isNull()
+    }
+
+    @Test
+    fun onCastDevicesChanged_deviceNotConnectedOrConnecting_noSignal() = runTest {
+        val device =
+            CastController.CastDevice().apply {
+                state = CastController.CastDevice.STATE_DISCONNECTED
+            }
+        whenever(castController.castDevices).thenReturn(listOf(device))
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(castController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onCastDevicesChanged()
+
+        assertThat(signal).isNull()
+    }
+
+    @Test
+    fun onCastDevicesChanged_someDeviceConnecting_addSignal() = runTest {
+        val disconnectedDevice =
+            CastController.CastDevice().apply {
+                state = CastController.CastDevice.STATE_DISCONNECTED
+            }
+        val connectingDevice =
+            CastController.CastDevice().apply { state = CastController.CastDevice.STATE_CONNECTING }
+        whenever(castController.castDevices)
+            .thenReturn(listOf(disconnectedDevice, connectingDevice))
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(castController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onCastDevicesChanged()
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun onCastDevicesChanged_someDeviceConnected_addSignal() = runTest {
+        val disconnectedDevice =
+            CastController.CastDevice().apply {
+                state = CastController.CastDevice.STATE_DISCONNECTED
+            }
+        val connectedDevice =
+            CastController.CastDevice().apply { state = CastController.CastDevice.STATE_CONNECTED }
+        whenever(castController.castDevices).thenReturn(listOf(disconnectedDevice, connectedDevice))
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(castController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onCastDevicesChanged()
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(CastTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
new file mode 100644
index 0000000..098ffc3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DataSaverTile
+import com.android.systemui.statusbar.policy.DataSaverController
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DataSaverAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var dataSaverController: DataSaverController
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<DataSaverController.Listener>
+
+    private lateinit var underTest: DataSaverAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = DataSaverAutoAddable(dataSaverController)
+    }
+
+    @Test
+    fun dataSaverNotEnabled_NoSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(dataSaverController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onDataSaverChanged(false)
+
+        assertThat(signal).isNull()
+    }
+
+    @Test
+    fun dataSaverEnabled_addSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(dataSaverController).addCallback(capture(callbackCaptor))
+
+        callbackCaptor.value.onDataSaverChanged(true)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(DataSaverTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
new file mode 100644
index 0000000..a2e3538
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.DeviceControlsTile
+import com.android.systemui.statusbar.policy.DeviceControlsController
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DeviceControlsAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var deviceControlsController: DeviceControlsController
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<DeviceControlsController.Callback>
+
+    private lateinit var underTest: DeviceControlsAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = DeviceControlsAutoAddable(deviceControlsController)
+    }
+
+    @Test
+    fun strategyAlways() {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+    }
+
+    @Test
+    fun onControlsUpdate_position_addSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        val position = 5
+        runCurrent()
+
+        verify(deviceControlsController).setCallback(capture(callbackCaptor))
+        callbackCaptor.value.onControlsUpdate(position)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC, position))
+        verify(deviceControlsController).removeCallback()
+    }
+
+    @Test
+    fun onControlsUpdate_nullPosition_noAddSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(deviceControlsController).setCallback(capture(callbackCaptor))
+        callbackCaptor.value.onControlsUpdate(null)
+
+        assertThat(signal).isNull()
+        verify(deviceControlsController).removeCallback()
+    }
+
+    @Test
+    fun onRemoveControlsAutoTracker_removeSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(deviceControlsController).setCallback(capture(callbackCaptor))
+        callbackCaptor.value.removeControlsAutoTracker()
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+    }
+
+    @Test
+    fun flowCancelled_removeCallback() = runTest {
+        val job = launch { underTest.autoAddSignal(0).collect() }
+        runCurrent()
+
+        job.cancel()
+        runCurrent()
+        verify(deviceControlsController).removeCallback()
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(DeviceControlsTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
new file mode 100644
index 0000000..ee96b47
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.HotspotTile
+import com.android.systemui.statusbar.policy.HotspotController
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class HotspotAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var hotspotController: HotspotController
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<HotspotController.Callback>
+
+    private lateinit var underTest: HotspotAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = HotspotAutoAddable(hotspotController)
+    }
+
+    @Test
+    fun enabled_addSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(hotspotController).addCallback(capture(callbackCaptor))
+        callbackCaptor.value.onHotspotChanged(/* enabled = */ true, /* numDevices = */ 5)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun notEnabled_noSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(hotspotController).addCallback(capture(callbackCaptor))
+        callbackCaptor.value.onHotspotChanged(/* enabled = */ false, /* numDevices = */ 0)
+
+        assertThat(signal).isNull()
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(HotspotTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
new file mode 100644
index 0000000..e03072a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.hardware.display.NightDisplayListener
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.NightDisplayListenerModule
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.NightDisplayTile
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NightDisplayAutoAddableTest : SysuiTestCase() {
+
+    @Mock(answer = Answers.RETURNS_SELF)
+    private lateinit var nightDisplayListenerBuilder: NightDisplayListenerModule.Builder
+    @Mock private lateinit var nightDisplayListener: NightDisplayListener
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<NightDisplayListener.Callback>
+
+    private lateinit var underTest: NightDisplayAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(nightDisplayListenerBuilder.build()).thenReturn(nightDisplayListener)
+    }
+
+    @Test
+    fun disabled_strategyDisabled() =
+        testWithFeatureAvailability(enabled = false) {
+            assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Disabled)
+        }
+
+    @Test
+    fun enabled_strategyIfNotAdded() = testWithFeatureAvailability {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+    }
+
+    @Test
+    fun listenerCreatedForCorrectUser() = testWithFeatureAvailability {
+        val user = 42
+        backgroundScope.launch { underTest.autoAddSignal(user).collect() }
+        runCurrent()
+
+        val inOrder = inOrder(nightDisplayListenerBuilder)
+        inOrder.verify(nightDisplayListenerBuilder).setUser(user)
+        inOrder.verify(nightDisplayListenerBuilder).build()
+    }
+
+    @Test
+    fun onCancelFlow_removeCallback() = testWithFeatureAvailability {
+        val job = launch { underTest.autoAddSignal(0).collect() }
+        runCurrent()
+        job.cancel()
+        runCurrent()
+        verify(nightDisplayListener).setCallback(null)
+    }
+
+    @Test
+    fun onActivatedTrue_addSignal() = testWithFeatureAvailability {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(nightDisplayListener).setCallback(capture(callbackCaptor))
+        callbackCaptor.value.onActivated(true)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    private fun testWithFeatureAvailability(
+        enabled: Boolean = true,
+        body: suspend TestScope.() -> TestResult
+    ) = runTest {
+        context.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_nightDisplayAvailable,
+            enabled
+        )
+        underTest = NightDisplayAutoAddable(nightDisplayListenerBuilder, context)
+        body()
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(NightDisplayTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
new file mode 100644
index 0000000..7b4a55e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.ReduceBrightColorsController
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ReduceBrightColorsAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var reduceBrightColorsController: ReduceBrightColorsController
+    @Captor
+    private lateinit var reduceBrightColorsListenerCaptor:
+        ArgumentCaptor<ReduceBrightColorsController.Listener>
+
+    private lateinit var underTest: ReduceBrightColorsAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun notAvailable_strategyDisabled() =
+        testWithFeatureAvailability(available = false) {
+            assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Disabled)
+        }
+
+    @Test
+    fun available_strategyIfNotAdded() =
+        testWithFeatureAvailability(available = true) {
+            assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+        }
+
+    @Test
+    fun activated_addSignal() = testWithFeatureAvailability {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(reduceBrightColorsController).addCallback(capture(reduceBrightColorsListenerCaptor))
+
+        reduceBrightColorsListenerCaptor.value.onActivated(true)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun notActivated_noSignal() = testWithFeatureAvailability {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+        runCurrent()
+
+        verify(reduceBrightColorsController).addCallback(capture(reduceBrightColorsListenerCaptor))
+
+        reduceBrightColorsListenerCaptor.value.onActivated(false)
+
+        assertThat(signal).isNull()
+    }
+
+    private fun testWithFeatureAvailability(
+        available: Boolean = true,
+        body: suspend TestScope.() -> TestResult
+    ) = runTest {
+        underTest = ReduceBrightColorsAutoAddable(reduceBrightColorsController, available)
+        body()
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(ReduceBrightColorsTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
new file mode 100644
index 0000000..fb35a3a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.statusbar.policy.SafetyController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SafetyCenterAutoAddableTest : SysuiTestCase() {
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Mock private lateinit var safetyController: SafetyController
+    @Mock private lateinit var packageManager: PackageManager
+    @Captor
+    private lateinit var safetyControllerListenerCaptor: ArgumentCaptor<SafetyController.Listener>
+
+    private lateinit var underTest: SafetyCenterAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        context.ensureTestableResources()
+
+        // Set these by default, will also test special cases
+        context.orCreateTestableResources.addOverride(
+            R.string.safety_quick_settings_tile_class,
+            SAFETY_TILE_CLASS_NAME
+        )
+        whenever(packageManager.permissionControllerPackageName)
+            .thenReturn(PERMISSION_CONTROLLER_PACKAGE_NAME)
+
+        underTest =
+            SafetyCenterAutoAddable(
+                safetyController,
+                packageManager,
+                context.resources,
+                testDispatcher,
+            )
+    }
+
+    @Test
+    fun strategyAlwaysTrack() =
+        testScope.runTest {
+            assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+        }
+
+    @Test
+    fun tileAlwaysAdded() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(0))
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun safetyCenterDisabled_removeSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(0))
+            runCurrent()
+
+            verify(safetyController).addCallback(capture(safetyControllerListenerCaptor))
+            safetyControllerListenerCaptor.value.onSafetyCenterEnableChanged(false)
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+        }
+
+    @Test
+    fun safetyCenterEnabled_newAddSignal() =
+        testScope.runTest {
+            val signals by collectValues(underTest.autoAddSignal(0))
+            runCurrent()
+
+            verify(safetyController).addCallback(capture(safetyControllerListenerCaptor))
+            safetyControllerListenerCaptor.value.onSafetyCenterEnableChanged(true)
+
+            assertThat(signals.size).isEqualTo(2)
+            assertThat(signals.last()).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun flowCancelled_removeListener() =
+        testScope.runTest {
+            val job = launch { underTest.autoAddSignal(0).collect() }
+            runCurrent()
+
+            verify(safetyController).addCallback(capture(safetyControllerListenerCaptor))
+
+            job.cancel()
+            runCurrent()
+            verify(safetyController).removeCallback(safetyControllerListenerCaptor.value)
+        }
+
+    @Test
+    fun emptyClassName_noSignals() =
+        testScope.runTest {
+            context.orCreateTestableResources.addOverride(
+                R.string.safety_quick_settings_tile_class,
+                ""
+            )
+            val signal by collectLastValue(underTest.autoAddSignal(0))
+            runCurrent()
+
+            verify(safetyController, never()).addCallback(any())
+
+            assertThat(signal).isNull()
+        }
+
+    companion object {
+        private const val SAFETY_TILE_CLASS_NAME = "cls"
+        private const val PERMISSION_CONTROLLER_PACKAGE_NAME = "pkg"
+        private val SPEC =
+            TileSpec.create(
+                ComponentName(PERMISSION_CONTROLLER_PACKAGE_NAME, SAFETY_TILE_CLASS_NAME)
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
new file mode 100644
index 0000000..6b250f4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.QuickAccessWalletTile
+import com.android.systemui.statusbar.policy.WalletController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class WalletAutoAddableTest : SysuiTestCase() {
+
+    @Mock private lateinit var walletController: WalletController
+
+    private lateinit var underTest: WalletAutoAddable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest = WalletAutoAddable(walletController)
+    }
+
+    @Test
+    fun strategyIfNotAdded() {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
+    }
+
+    @Test
+    fun walletPositionNull_noSignal() = runTest {
+        whenever(walletController.getWalletPosition()).thenReturn(null)
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        assertThat(signal).isNull()
+    }
+
+    @Test
+    fun walletPositionNumber_addedInThatPosition() = runTest {
+        val position = 4
+        whenever(walletController.getWalletPosition()).thenReturn(4)
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC, position))
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(QuickAccessWalletTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
new file mode 100644
index 0000000..e9f7c8ab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.pm.UserInfo
+import android.content.pm.UserInfo.FLAG_FULL
+import android.content.pm.UserInfo.FLAG_MANAGED_PROFILE
+import android.content.pm.UserInfo.FLAG_PRIMARY
+import android.content.pm.UserInfo.FLAG_PROFILE
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.WorkModeTile
+import com.android.systemui.settings.FakeUserTracker
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class WorkTileAutoAddableTest : SysuiTestCase() {
+
+    private lateinit var userTracker: FakeUserTracker
+
+    private lateinit var underTest: WorkTileAutoAddable
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        userTracker =
+            FakeUserTracker(
+                _userId = USER_INFO_0.id,
+                _userInfo = USER_INFO_0,
+                _userProfiles = listOf(USER_INFO_0)
+            )
+
+        underTest = WorkTileAutoAddable(userTracker)
+    }
+
+    @Test
+    fun changeInProfiles_hasManagedProfile_sendsAddSignal() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun changeInProfiles_noManagedProfile_sendsRemoveSignal() = runTest {
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        userTracker.set(listOf(USER_INFO_0), selectedUserIndex = 0)
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+    }
+
+    @Test
+    fun startingWithManagedProfile_sendsAddSignal() = runTest {
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun userChangeToUserWithProfile_noSignalForOriginalUser() = runTest {
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        userTracker.set(listOf(USER_INFO_1, USER_INFO_WORK), selectedUserIndex = 0)
+
+        assertThat(signal).isNotEqualTo(AutoAddSignal.Add(SPEC))
+    }
+
+    @Test
+    fun userChangeToUserWithoutProfile_noSignalForOriginalUser() = runTest {
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+        val signal by collectLastValue(underTest.autoAddSignal(0))
+
+        userTracker.set(listOf(USER_INFO_1), selectedUserIndex = 0)
+
+        assertThat(signal).isNotEqualTo(AutoAddSignal.Remove(SPEC))
+    }
+
+    @Test
+    fun strategyAlways() {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create(WorkModeTile.TILE_SPEC)
+        private val USER_INFO_0 = UserInfo(0, "", FLAG_PRIMARY or FLAG_FULL)
+        private val USER_INFO_1 = UserInfo(1, "", FLAG_FULL)
+        private val USER_INFO_WORK = UserInfo(10, "", FLAG_PROFILE or FLAG_MANAGED_PROFILE)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
new file mode 100644
index 0000000..f924b35
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.qs.pipeline.data.repository.FakeAutoAddRepository
+import com.android.systemui.qs.pipeline.domain.autoaddable.FakeAutoAddable
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class AutoAddInteractorTest : SysuiTestCase() {
+    private val testScope = TestScope()
+
+    private val autoAddRepository = FakeAutoAddRepository()
+
+    @Mock private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var currentTilesInteractor: CurrentTilesInteractor
+    @Mock private lateinit var logger: QSPipelineLogger
+    private lateinit var underTest: AutoAddInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(currentTilesInteractor.userId).thenReturn(MutableStateFlow(USER))
+    }
+
+    @Test
+    fun autoAddable_alwaysTrack_addSignal_tileAddedAndMarked() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
+            val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            val position = 3
+            fakeAutoAddable.sendAddSignal(USER, position)
+            runCurrent()
+
+            verify(currentTilesInteractor).addTile(SPEC, position)
+            assertThat(autoAddedTiles).contains(SPEC)
+        }
+
+    @Test
+    fun autoAddable_alwaysTrack_addThenRemoveSignal_tileAddedAndRemoved() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
+            val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            val position = 3
+            fakeAutoAddable.sendAddSignal(USER, position)
+            runCurrent()
+            fakeAutoAddable.sendRemoveSignal(USER)
+            runCurrent()
+
+            val inOrder = inOrder(currentTilesInteractor)
+            inOrder.verify(currentTilesInteractor).addTile(SPEC, position)
+            inOrder.verify(currentTilesInteractor).removeTiles(setOf(SPEC))
+            assertThat(autoAddedTiles).doesNotContain(SPEC)
+        }
+
+    @Test
+    fun autoAddable_alwaysTrack_addSignalWhenAddedPreviously_noop() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
+            autoAddRepository.markTileAdded(USER, SPEC)
+            runCurrent()
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            val position = 3
+            fakeAutoAddable.sendAddSignal(USER, position)
+            runCurrent()
+
+            verify(currentTilesInteractor, never()).addTile(SPEC, position)
+        }
+
+    @Test
+    fun autoAddable_disabled_noInteractionsWithCurrentTilesInteractor() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Disabled)
+            val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            val position = 3
+            fakeAutoAddable.sendAddSignal(USER, position)
+            runCurrent()
+            fakeAutoAddable.sendRemoveSignal(USER)
+            runCurrent()
+
+            verify(currentTilesInteractor, never()).addTile(any(), anyInt())
+            verify(currentTilesInteractor, never()).removeTiles(any())
+            assertThat(autoAddedTiles).doesNotContain(SPEC)
+        }
+
+    @Test
+    fun autoAddable_trackIfNotAdded_removeSignal_noop() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+            runCurrent()
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            fakeAutoAddable.sendRemoveSignal(USER)
+            runCurrent()
+
+            verify(currentTilesInteractor, never()).addTile(any(), anyInt())
+            verify(currentTilesInteractor, never()).removeTiles(any())
+        }
+
+    @Test
+    fun autoAddable_trackIfNotAdded_addSignalWhenPreviouslyAdded_noop() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+            autoAddRepository.markTileAdded(USER, SPEC)
+            runCurrent()
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            fakeAutoAddable.sendAddSignal(USER)
+            runCurrent()
+
+            verify(currentTilesInteractor, never()).addTile(any(), anyInt())
+            verify(currentTilesInteractor, never()).removeTiles(any())
+        }
+
+    @Test
+    fun autoAddable_trackIfNotAdded_addSignal_addedAndMarked() =
+        testScope.runTest {
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+            val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            val position = 3
+            fakeAutoAddable.sendAddSignal(USER, position)
+            runCurrent()
+
+            verify(currentTilesInteractor).addTile(SPEC, position)
+            assertThat(autoAddedTiles).contains(SPEC)
+        }
+
+    private fun createInteractor(autoAddables: Set<AutoAddable>): AutoAddInteractor {
+        return AutoAddInteractor(
+                autoAddables,
+                autoAddRepository,
+                dumpManager,
+                logger,
+                testScope.backgroundScope
+            )
+            .apply { init(currentTilesInteractor) }
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create("spec")
+        private val USER = 10
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index e7ad489..30cea2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -100,6 +100,7 @@
         MockitoAnnotations.initMocks(this)
 
         featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
+        featureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, true)
 
         userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
index 45783ab..6556cfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
@@ -18,8 +18,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import java.util.Optional
+import com.android.systemui.shade.ShadeController
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -31,7 +30,7 @@
 @SmallTest
 class PanelInteractorImplTest : SysuiTestCase() {
 
-    @Mock private lateinit var centralSurfaces: CentralSurfaces
+    @Mock private lateinit var shadeController: ShadeController
 
     @Before
     fun setup() {
@@ -40,37 +39,28 @@
 
     @Test
     fun openPanels_callsCentralSurfaces() {
-        val underTest = PanelInteractorImpl(Optional.of(centralSurfaces))
+        val underTest = PanelInteractorImpl(shadeController)
 
         underTest.openPanels()
 
-        verify(centralSurfaces).postAnimateOpenPanels()
+        verify(shadeController).postAnimateExpandQs()
     }
 
     @Test
     fun collapsePanels_callsCentralSurfaces() {
-        val underTest = PanelInteractorImpl(Optional.of(centralSurfaces))
+        val underTest = PanelInteractorImpl(shadeController)
 
         underTest.collapsePanels()
 
-        verify(centralSurfaces).postAnimateCollapsePanels()
+        verify(shadeController).postAnimateCollapseShade()
     }
 
     @Test
     fun forceCollapsePanels_callsCentralSurfaces() {
-        val underTest = PanelInteractorImpl(Optional.of(centralSurfaces))
+        val underTest = PanelInteractorImpl(shadeController)
 
         underTest.forceCollapsePanels()
 
-        verify(centralSurfaces).postAnimateForceCollapsePanels()
-    }
-
-    @Test
-    fun whenOptionalEmpty_doesnThrow() {
-        val underTest = PanelInteractorImpl(Optional.empty())
-
-        underTest.openPanels()
-        underTest.collapsePanels()
-        underTest.forceCollapsePanels()
+        verify(shadeController).postAnimateForceCollapseShade()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index f55d262..180fed9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -155,6 +155,28 @@
         inOrder.verify(d).stop();
     }
 
+    @Test
+    public void testAnimatorCallbackRemovedOnOldDrawable() {
+        ImageView iv = new ImageView(mContext);
+        AnimatedVectorDrawable d1 = mock(AnimatedVectorDrawable.class);
+        when(d1.getConstantState()).thenReturn(fakeConstantState(d1));
+        AnimatedVectorDrawable d2 = mock(AnimatedVectorDrawable.class);
+        when(d2.getConstantState()).thenReturn(fakeConstantState(d2));
+        State s = new State();
+        s.isTransient = true;
+
+        // When set Animatable2 d1
+        s.icon = new QSTileImpl.DrawableIcon(d1);
+        mIconView.updateIcon(iv, s, true);
+
+        // And then set Animatable2 d2
+        s.icon = new QSTileImpl.DrawableIcon(d2);
+        mIconView.updateIcon(iv, s, true);
+
+        // Then d1 has its callback cleared
+        verify(d1).clearAnimationCallbacks();
+    }
+
     private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
         return new Drawable.ConstantState() {
             @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 962b537..22b1c7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -204,6 +204,19 @@
     }
 
     @Test
+    public void testLongClick_falsing() {
+        mFalsingManager.setFalseLongTap(true);
+        mTile.longClick(null /* view */);
+        mTestableLooper.processAllMessages();
+        assertThat(mTile.mLongClicked).isFalse();
+
+        mFalsingManager.setFalseLongTap(false);
+        mTile.longClick(null /* view */);
+        mTestableLooper.processAllMessages();
+        assertThat(mTile.mLongClicked).isTrue();
+    }
+
+    @Test
     public void testSecondaryClick_Metrics() {
         mTile.secondaryClick(null /* view */);
         verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK)));
@@ -518,6 +531,7 @@
     }
     private static class TileImpl extends QSTileImpl<QSTile.BooleanState> {
         boolean mClicked;
+        boolean mLongClicked;
         int mRefreshes = 0;
 
         protected TileImpl(
@@ -551,6 +565,11 @@
         }
 
         @Override
+        protected void handleLongClick(@Nullable View view) {
+            mLongClicked = true;
+        }
+
+        @Override
         protected void handleUpdateState(BooleanState state, Object arg) {
             mRefreshes++;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 28aeba4..3c66772 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -22,6 +22,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.text.TextUtils
+import android.view.ContextThemeWrapper
 import android.view.View
 import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.TextView
@@ -386,7 +387,11 @@
         context: Context,
         icon: QSIconView,
         collapsed: Boolean
-    ) : QSTileViewImpl(context, icon, collapsed) {
+    ) : QSTileViewImpl(
+            ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings),
+            icon,
+            collapsed
+    ) {
         fun changeState(state: QSTile.State) {
             handleStateChanged(state)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
index f0e4e3a..77a4436 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
@@ -25,6 +25,7 @@
 import android.provider.Settings.Global.ZEN_MODE_OFF
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.ContextThemeWrapper
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
@@ -110,7 +111,9 @@
 
         whenever(qsHost.userId).thenReturn(DEFAULT_USER)
 
-        val wrappedContext = object : ContextWrapper(context) {
+        val wrappedContext = object : ContextWrapper(
+                ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+        ) {
             override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences {
                 return sharedPreferences
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
index ddbfca5..cbc3553 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
@@ -33,6 +33,8 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -44,8 +46,11 @@
 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.anyBoolean
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
@@ -61,6 +66,8 @@
     @Mock private lateinit var qsLogger: QSLogger
     @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
     @Mock private lateinit var uiEventLogger: QsEventLogger
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var systemClock: FakeSystemClock
@@ -69,6 +76,8 @@
 
     val featureFlags = FakeFeatureFlags()
 
+    @Captor private lateinit var argumentCaptor: ArgumentCaptor<Runnable>
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -88,11 +97,13 @@
                 statusBarStateController,
                 activityStarter,
                 qsLogger,
+                keyguardStateController,
                 dialogLaunchAnimator,
                 FakeSettings(),
                 FakeSettings(),
                 FakeSystemClock(),
                 featureFlags,
+                userTracker,
                 backgroundDelayableExecutor,
             )
         fontScalingTile.initialize()
@@ -124,15 +135,45 @@
     }
 
     @Test
-    fun clickTile_showDialog() {
+    fun clickTile_screenUnlocked_showDialogAnimationFromView() {
+        `when`(keyguardStateController.isShowing).thenReturn(false)
         val view = View(context)
         fontScalingTile.click(view)
         testableLooper.processAllMessages()
 
+        verify(activityStarter)
+            .executeRunnableDismissingKeyguard(
+                argumentCaptor.capture(),
+                eq(null),
+                eq(true),
+                eq(true),
+                eq(false)
+            )
+        argumentCaptor.value.run()
         verify(dialogLaunchAnimator).showFromView(any(), eq(view), nullable(), anyBoolean())
     }
 
     @Test
+    fun clickTile_onLockScreen_neverShowDialogAnimationFromView() {
+        `when`(keyguardStateController.isShowing).thenReturn(true)
+        val view = View(context)
+        fontScalingTile.click(view)
+        testableLooper.processAllMessages()
+
+        verify(activityStarter)
+            .executeRunnableDismissingKeyguard(
+                argumentCaptor.capture(),
+                eq(null),
+                eq(true),
+                eq(true),
+                eq(false)
+            )
+        argumentCaptor.value.run()
+        verify(dialogLaunchAnimator, never())
+            .showFromView(any(), eq(view), nullable(), anyBoolean())
+    }
+
+    @Test
     fun getLongClickIntent_getExpectedIntent() {
         val intent: Intent? = fontScalingTile.getLongClickIntent()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index 6f2d904..71aa7a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -117,7 +117,7 @@
 
     @Test
     public void testQRCodeTileUnavailable() {
-        when(mController.isAbleToOpenCameraApp()).thenReturn(false);
+        when(mController.isAbleToLaunchScannerActivity()).thenReturn(false);
         QSTile.State state = new QSTile.State();
         mTile.handleUpdateState(state, null);
         assertEquals(state.state, Tile.STATE_UNAVAILABLE);
@@ -127,7 +127,7 @@
 
     @Test
     public void testQRCodeTileAvailable() {
-        when(mController.isAbleToOpenCameraApp()).thenReturn(true);
+        when(mController.isAbleToLaunchScannerActivity()).thenReturn(true);
         QSTile.State state = new QSTile.State();
         mTile.handleUpdateState(state, null);
         assertEquals(state.state, Tile.STATE_INACTIVE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 6614392..5b30687 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -968,6 +968,7 @@
         assertThat(mInternetDialogController.mSubIdTelephonyManagerMap.get(SUB_ID)).isEqualTo(
                 mTelephonyManager);
         assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.get(SUB_ID)).isNotNull();
+        assertThat(mInternetDialogController.mCallback).isNotNull();
 
         mInternetDialogController.onStop();
 
@@ -980,6 +981,7 @@
         verify(mAccessPointController).removeAccessPointCallback(mInternetDialogController);
         verify(mConnectivityManager).unregisterNetworkCallback(
                 any(ConnectivityManager.NetworkCallback.class));
+        assertThat(mInternetDialogController.mCallback).isNull();
     }
 
     private String getResourcesString(String name) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 105387d..2cb0205 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -18,16 +18,13 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -39,8 +36,8 @@
 @RunWith(JUnit4::class)
 class QuickSettingsSceneViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor =
         utils.authenticationInteractor(
@@ -49,29 +46,19 @@
 
     private val underTest =
         QuickSettingsSceneViewModel(
-            lockscreenSceneInteractorFactory =
-                object : LockscreenSceneInteractor.Factory {
-                    override fun create(containerName: String): LockscreenSceneInteractor {
-                        return utils.lockScreenSceneInteractor(
-                            authenticationInteractor = authenticationInteractor,
-                            sceneInteractor = sceneInteractor,
-                            bouncerInteractor =
-                                utils.bouncerInteractor(
-                                    authenticationInteractor = authenticationInteractor,
-                                    sceneInteractor = sceneInteractor,
-                                ),
-                        )
-                    }
-                },
-            containerName = CONTAINER_1
+            bouncerInteractor =
+                utils.bouncerInteractor(
+                    authenticationInteractor = authenticationInteractor,
+                    sceneInteractor = sceneInteractor,
+                ),
         )
 
     @Test
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.unlockDevice()
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onContentClicked()
@@ -82,9 +69,9 @@
     @Test
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onContentClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index 355c4b6..49ece66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
 import com.android.systemui.keyguard.ScreenLifecycle
 import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -38,6 +39,7 @@
 import com.android.systemui.recents.OverviewProxyService.ACTION_QUICKSTEP
 import com.android.systemui.settings.FakeDisplayTracker
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shared.recents.IOverviewProxy
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK
 import com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_ASLEEP
@@ -48,11 +50,11 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.wm.shell.sysui.ShellInterface
 import com.google.common.util.concurrent.MoreExecutors
-import dagger.Lazy
 import java.util.Optional
 import java.util.concurrent.Executor
 import org.junit.After
@@ -81,6 +83,7 @@
     private val displayTracker = FakeDisplayTracker(mContext)
     private val fakeSystemClock = FakeSystemClock()
     private val sysUiState = SysUiState(displayTracker)
+    private val featureFlags = FakeFeatureFlags()
     private val screenLifecycle = ScreenLifecycle(dumpManager)
     private val wakefulnessLifecycle =
         WakefulnessLifecycle(mContext, null, fakeSystemClock, dumpManager)
@@ -93,6 +96,7 @@
     @Mock private lateinit var shellInterface: ShellInterface
     @Mock private lateinit var navBarController: NavigationBarController
     @Mock private lateinit var centralSurfaces: CentralSurfaces
+    @Mock private lateinit var shadeViewController: ShadeViewController
     @Mock private lateinit var navModeController: NavigationModeController
     @Mock private lateinit var statusBarWinController: NotificationShadeWindowController
     @Mock private lateinit var userTracker: UserTracker
@@ -130,11 +134,13 @@
                 executor,
                 commandQueue,
                 shellInterface,
-                Lazy { navBarController },
-                Lazy { Optional.of(centralSurfaces) },
+                { navBarController },
+                { Optional.of(centralSurfaces) },
+                { shadeViewController },
                 navModeController,
                 statusBarWinController,
                 sysUiState,
+                mock(),
                 userTracker,
                 screenLifecycle,
                 wakefulnessLifecycle,
@@ -142,6 +148,7 @@
                 displayTracker,
                 sysuiUnlockAnimationController,
                 assistUtils,
+                featureFlags,
                 dumpManager,
                 unfoldTransitionProgressForwarder
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
index d7682b2..421c355 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
@@ -26,6 +26,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -51,6 +52,7 @@
     fun retailMode_defaultFalse() =
         testScope.runTest {
             val value by collectLastValue(underTest.retailMode)
+            runCurrent()
 
             assertThat(value).isFalse()
             assertThat(underTest.inRetailMode).isFalse()
@@ -60,6 +62,7 @@
     fun retailMode_false() =
         testScope.runTest {
             val value by collectLastValue(underTest.retailMode)
+            runCurrent()
 
             globalSettings.putInt(SETTING, 0)
 
@@ -71,6 +74,7 @@
     fun retailMode_true() =
         testScope.runTest {
             val value by collectLastValue(underTest.retailMode)
+            runCurrent()
 
             globalSettings.putInt(SETTING, 1)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
new file mode 100644
index 0000000..53c04cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -0,0 +1,538 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.model.SysUiState
+import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer
+import com.android.systemui.scene.domain.startable.SceneContainerStartable
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/**
+ * Integration test cases for the Scene Framework.
+ *
+ * **Principles**
+ * * All test cases here should be done from the perspective of the view-models of the system.
+ * * Focus on happy paths, let smaller unit tests focus on failure cases.
+ * * These are _integration_ tests and, as such, are larger and harder to maintain than unit tests.
+ *   Therefore, when adding or modifying test cases, consider whether what you're testing is better
+ *   covered by a more granular unit test.
+ * * Please reuse the helper methods in this class (for example, [putDeviceToSleep] or
+ *   [emulateUserDrivenTransition]).
+ * * All tests start with the device locked and with a PIN auth method. The class offers useful
+ *   methods like [setAuthMethod], [unlockDevice], [lockDevice], etc. to help you set up a starting
+ *   state that makes more sense for your test case.
+ * * All helper methods in this class make assertions that are meant to make sure that they're only
+ *   being used when the state is as required (e.g. cannot unlock an already unlocked device, cannot
+ *   put to sleep a device that's already asleep, etc.).
+ */
+@SmallTest
+@RunWith(JUnit4::class)
+class SceneFrameworkIntegrationTest : SysuiTestCase() {
+
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
+
+    private val sceneContainerConfig = utils.fakeSceneContainerConfig()
+    private val sceneRepository =
+        utils.fakeSceneContainerRepository(
+            containerConfig = sceneContainerConfig,
+        )
+    private val sceneInteractor =
+        utils.sceneInteractor(
+            repository = sceneRepository,
+        )
+
+    private val authenticationRepository = utils.authenticationRepository()
+    private val authenticationInteractor =
+        utils.authenticationInteractor(
+            repository = authenticationRepository,
+            sceneInteractor = sceneInteractor,
+        )
+
+    private val transitionState =
+        MutableStateFlow<ObservableTransitionState>(
+            ObservableTransitionState.Idle(sceneContainerConfig.initialSceneKey)
+        )
+    private val sceneContainerViewModel =
+        SceneContainerViewModel(
+                interactor = sceneInteractor,
+            )
+            .apply { setTransitionState(transitionState) }
+
+    private val bouncerInteractor =
+        utils.bouncerInteractor(
+            authenticationInteractor = authenticationInteractor,
+            sceneInteractor = sceneInteractor,
+        )
+    private val bouncerViewModel =
+        utils.bouncerViewModel(
+            bouncerInteractor = bouncerInteractor,
+            authenticationInteractor = authenticationInteractor,
+        )
+
+    private val lockscreenSceneViewModel =
+        LockscreenSceneViewModel(
+            applicationScope = testScope.backgroundScope,
+            authenticationInteractor = authenticationInteractor,
+            bouncerInteractor = bouncerInteractor,
+        )
+
+    private val shadeSceneViewModel =
+        ShadeSceneViewModel(
+            applicationScope = testScope.backgroundScope,
+            authenticationInteractor = authenticationInteractor,
+            bouncerInteractor = bouncerInteractor,
+        )
+
+    private val keyguardRepository = utils.keyguardRepository()
+    private val keyguardInteractor =
+        utils.keyguardInteractor(
+            repository = keyguardRepository,
+        )
+
+    @Before
+    fun setUp() {
+        val featureFlags = FakeFeatureFlags().apply { set(Flags.SCENE_CONTAINER, true) }
+
+        authenticationRepository.setUnlocked(false)
+
+        val displayTracker = FakeDisplayTracker(context)
+        val sysUiState = SysUiState(displayTracker)
+        val startable =
+            SceneContainerStartable(
+                applicationScope = testScope.backgroundScope,
+                sceneInteractor = sceneInteractor,
+                authenticationInteractor = authenticationInteractor,
+                keyguardInteractor = keyguardInteractor,
+                featureFlags = featureFlags,
+                sysUiState = sysUiState,
+                displayId = displayTracker.defaultDisplayId,
+                sceneLogger = mock(),
+            )
+        startable.start()
+
+        assertWithMessage("Initial scene key mismatch!")
+            .that(sceneContainerViewModel.currentScene.value.key)
+            .isEqualTo(sceneContainerConfig.initialSceneKey)
+        assertWithMessage("Initial scene container visibility mismatch!")
+            .that(sceneContainerViewModel.isVisible.value)
+            .isTrue()
+    }
+
+    @Test
+    fun clickLockButtonAndEnterCorrectPin_unlocksDevice() =
+        testScope.runTest {
+            lockscreenSceneViewModel.onLockButtonClicked()
+            assertCurrentScene(SceneKey.Bouncer)
+            emulateUiSceneTransition()
+
+            enterPin()
+            assertCurrentScene(SceneKey.Gone)
+            emulateUiSceneTransition(
+                expectedVisible = false,
+            )
+        }
+
+    @Test
+    fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
+        testScope.runTest {
+            val upDestinationSceneKey by
+                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer)
+            emulateUserDrivenTransition(
+                to = upDestinationSceneKey,
+            )
+
+            enterPin()
+            assertCurrentScene(SceneKey.Gone)
+            emulateUiSceneTransition(
+                expectedVisible = false,
+            )
+        }
+
+    @Test
+    fun swipeUpOnLockscreen_withAuthMethodSwipe_dismissesLockscreen() =
+        testScope.runTest {
+            setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
+
+            val upDestinationSceneKey by
+                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+            emulateUserDrivenTransition(
+                to = upDestinationSceneKey,
+            )
+        }
+
+    @Test
+    fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
+        testScope.runTest {
+            val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
+            setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            // Emulate a user swipe to the shade scene.
+            emulateUserDrivenTransition(to = SceneKey.Shade)
+            assertCurrentScene(SceneKey.Shade)
+
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Lockscreen)
+            emulateUserDrivenTransition(
+                to = upDestinationSceneKey,
+            )
+        }
+
+    @Test
+    fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
+        testScope.runTest {
+            val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
+            setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            // Emulate a user swipe to dismiss the lockscreen.
+            emulateUserDrivenTransition(to = SceneKey.Gone)
+            assertCurrentScene(SceneKey.Gone)
+
+            // Emulate a user swipe to the shade scene.
+            emulateUserDrivenTransition(to = SceneKey.Shade)
+            assertCurrentScene(SceneKey.Shade)
+
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+            emulateUserDrivenTransition(
+                to = upDestinationSceneKey,
+            )
+        }
+
+    @Test
+    fun withAuthMethodNone_deviceWakeUp_skipsLockscreen() =
+        testScope.runTest {
+            setAuthMethod(AuthenticationMethodModel.None)
+            putDeviceToSleep(instantlyLockDevice = false)
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            wakeUpDevice()
+            assertCurrentScene(SceneKey.Gone)
+        }
+
+    @Test
+    fun withAuthMethodSwipe_deviceWakeUp_doesNotSkipLockscreen() =
+        testScope.runTest {
+            setAuthMethod(AuthenticationMethodModel.Swipe)
+            putDeviceToSleep(instantlyLockDevice = false)
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            wakeUpDevice()
+            assertCurrentScene(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun deviceGoesToSleep_switchesToLockscreen() =
+        testScope.runTest {
+            unlockDevice()
+            assertCurrentScene(SceneKey.Gone)
+
+            putDeviceToSleep()
+            assertCurrentScene(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun deviceGoesToSleep_wakeUp_unlock() =
+        testScope.runTest {
+            unlockDevice()
+            assertCurrentScene(SceneKey.Gone)
+            putDeviceToSleep()
+            assertCurrentScene(SceneKey.Lockscreen)
+            wakeUpDevice()
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            unlockDevice()
+            assertCurrentScene(SceneKey.Gone)
+        }
+
+    @Test
+    fun deviceWakesUpWhileUnlocked_dismissesLockscreen() =
+        testScope.runTest {
+            unlockDevice()
+            assertCurrentScene(SceneKey.Gone)
+            putDeviceToSleep(instantlyLockDevice = false)
+            assertCurrentScene(SceneKey.Lockscreen)
+            wakeUpDevice()
+            assertCurrentScene(SceneKey.Gone)
+        }
+
+    @Test
+    fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
+        testScope.runTest {
+            unlockDevice()
+            val upDestinationSceneKey by
+                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun deviceGoesToSleep_withLockTimeout_staysOnLockscreen() =
+        testScope.runTest {
+            unlockDevice()
+            assertCurrentScene(SceneKey.Gone)
+            putDeviceToSleep(instantlyLockDevice = false)
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            // Pretend like the timeout elapsed and now lock the device.
+            lockDevice()
+            assertCurrentScene(SceneKey.Lockscreen)
+        }
+
+    /**
+     * Asserts that the current scene in the view-model matches what's expected.
+     *
+     * Note that this doesn't assert what the current scene is in the UI.
+     */
+    private fun TestScope.assertCurrentScene(expected: SceneKey) {
+        runCurrent()
+        assertWithMessage("Current scene mismatch!")
+            .that(sceneContainerViewModel.currentScene.value.key)
+            .isEqualTo(expected)
+    }
+
+    /**
+     * Returns the [SceneKey] of the current scene as displayed in the UI.
+     *
+     * This can be different than the value in [SceneContainerViewModel.currentScene], by design, as
+     * the UI must gradually transition between scenes.
+     */
+    private fun getCurrentSceneInUi(): SceneKey {
+        return when (val state = transitionState.value) {
+            is ObservableTransitionState.Idle -> state.scene
+            is ObservableTransitionState.Transition -> state.fromScene
+        }
+    }
+
+    /** Updates the current authentication method and related states in the data layer. */
+    private fun TestScope.setAuthMethod(
+        authMethod: DomainLayerAuthenticationMethodModel,
+    ) {
+        // Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the
+        // lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit
+        // is not an observable that can trigger a new evaluation.
+        authenticationRepository.setLockscreenEnabled(authMethod !is AuthenticationMethodModel.None)
+        authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer())
+        if (!authMethod.isSecure) {
+            // When the auth method is not secure, the device is never considered locked.
+            authenticationRepository.setUnlocked(true)
+        }
+        runCurrent()
+    }
+
+    /**
+     * Emulates a complete transition in the UI from whatever the current scene is in the UI to
+     * whatever the current scene should be, based on the value in
+     * [SceneContainerViewModel.onSceneChanged].
+     *
+     * This should post a series of values into [transitionState] to emulate a gradual scene
+     * transition and culminate with a call to [SceneContainerViewModel.onSceneChanged].
+     *
+     * The method asserts that a transition is actually required. E.g. it will fail if the current
+     * scene in [transitionState] is already caught up with the scene in
+     * [SceneContainerViewModel.currentScene].
+     *
+     * @param expectedVisible Whether [SceneContainerViewModel.isVisible] should be set at the end
+     *   of the UI transition.
+     */
+    private fun TestScope.emulateUiSceneTransition(
+        expectedVisible: Boolean = true,
+    ) {
+        val to = sceneContainerViewModel.currentScene.value
+        val from = getCurrentSceneInUi()
+        assertWithMessage("Cannot transition to ${to.key} as the UI is already on that scene!")
+            .that(to.key)
+            .isNotEqualTo(from)
+
+        // Begin to transition.
+        val progressFlow = MutableStateFlow(0f)
+        transitionState.value =
+            ObservableTransitionState.Transition(
+                fromScene = getCurrentSceneInUi(),
+                toScene = to.key,
+                progress = progressFlow,
+            )
+        runCurrent()
+
+        // Report progress of transition.
+        while (progressFlow.value < 1f) {
+            progressFlow.value += 0.2f
+            runCurrent()
+        }
+
+        // End the transition and report the change.
+        transitionState.value = ObservableTransitionState.Idle(to.key)
+
+        sceneContainerViewModel.onSceneChanged(to)
+        runCurrent()
+
+        assertWithMessage("Visibility mismatch after scene transition from $from to ${to.key}!")
+            .that(sceneContainerViewModel.isVisible.value)
+            .isEqualTo(expectedVisible)
+    }
+
+    /**
+     * Emulates a fire-and-forget user action (a fling or back, not a pointer-tracking swipe) that
+     * causes a scene change to the [to] scene.
+     *
+     * This also includes the emulation of the resulting UI transition that culminates with the UI
+     * catching up with the requested scene change (see [emulateUiSceneTransition]).
+     *
+     * @param to The scene to transition to.
+     */
+    private fun TestScope.emulateUserDrivenTransition(
+        to: SceneKey?,
+    ) {
+        checkNotNull(to)
+
+        sceneInteractor.changeScene(SceneModel(to), "reason")
+        assertThat(sceneContainerViewModel.currentScene.value.key).isEqualTo(to)
+
+        emulateUiSceneTransition(
+            expectedVisible = to != SceneKey.Gone,
+        )
+    }
+
+    /**
+     * Locks the device immediately (without delay).
+     *
+     * Asserts the device to be lockable (e.g. that the current authentication is secure).
+     *
+     * Not to be confused with [putDeviceToSleep], which may also instantly lock the device.
+     */
+    private suspend fun TestScope.lockDevice() {
+        val authMethod = authenticationInteractor.getAuthenticationMethod()
+        assertWithMessage("The authentication method of $authMethod is not secure, cannot lock!")
+            .that(authMethod.isSecure)
+            .isTrue()
+
+        authenticationRepository.setUnlocked(false)
+        runCurrent()
+    }
+
+    /** Unlocks the device by entering the correct PIN. Ends up in the Gone scene. */
+    private fun TestScope.unlockDevice() {
+        assertWithMessage("Cannot unlock a device that's already unlocked!")
+            .that(authenticationInteractor.isUnlocked.value)
+            .isFalse()
+
+        lockscreenSceneViewModel.onLockButtonClicked()
+        runCurrent()
+        emulateUiSceneTransition()
+
+        enterPin()
+        emulateUiSceneTransition(
+            expectedVisible = false,
+        )
+    }
+
+    /**
+     * Enters the correct PIN in the bouncer UI.
+     *
+     * Asserts that the current scene is [SceneKey.Bouncer] and that the current bouncer UI is a PIN
+     * before proceeding.
+     *
+     * Does not assert that the device is locked or unlocked.
+     */
+    private fun TestScope.enterPin() {
+        assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
+            .that(getCurrentSceneInUi())
+            .isEqualTo(SceneKey.Bouncer)
+        val authMethodViewModel by collectLastValue(bouncerViewModel.authMethod)
+        assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
+            .that(authMethodViewModel)
+            .isInstanceOf(PinBouncerViewModel::class.java)
+
+        val pinBouncerViewModel = authMethodViewModel as PinBouncerViewModel
+        FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+            pinBouncerViewModel.onPinButtonClicked(digit)
+        }
+        pinBouncerViewModel.onAuthenticateButtonClicked()
+        runCurrent()
+    }
+
+    /** Changes device wakefulness state from asleep to awake, going through intermediary states. */
+    private fun TestScope.wakeUpDevice() {
+        val wakefulnessModel = keyguardRepository.wakefulness.value
+        assertWithMessage("Cannot wake up device as it's already awake!")
+            .that(wakefulnessModel.isStartingToWakeOrAwake())
+            .isFalse()
+
+        keyguardRepository.setWakefulnessModel(
+            wakefulnessModel.copy(state = WakefulnessState.STARTING_TO_WAKE)
+        )
+        runCurrent()
+        keyguardRepository.setWakefulnessModel(
+            wakefulnessModel.copy(state = WakefulnessState.AWAKE)
+        )
+        runCurrent()
+    }
+
+    /** Changes device wakefulness state from awake to asleep, going through intermediary states. */
+    private suspend fun TestScope.putDeviceToSleep(
+        instantlyLockDevice: Boolean = true,
+    ) {
+        val wakefulnessModel = keyguardRepository.wakefulness.value
+        assertWithMessage("Cannot put device to sleep as it's already asleep!")
+            .that(wakefulnessModel.isStartingToWakeOrAwake())
+            .isTrue()
+
+        keyguardRepository.setWakefulnessModel(
+            wakefulnessModel.copy(state = WakefulnessState.STARTING_TO_SLEEP)
+        )
+        runCurrent()
+        keyguardRepository.setWakefulnessModel(
+            wakefulnessModel.copy(state = WakefulnessState.ASLEEP)
+        )
+        runCurrent()
+
+        if (instantlyLockDevice) {
+            lockDevice()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index de15c77..181f8a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -22,10 +22,12 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -36,11 +38,12 @@
 class SceneContainerRepositoryTest : SysuiTestCase() {
 
     private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
 
     @Test
     fun allSceneKeys() {
         val underTest = utils.fakeSceneContainerRepository()
-        assertThat(underTest.allSceneKeys("container1"))
+        assertThat(underTest.allSceneKeys())
             .isEqualTo(
                 listOf(
                     SceneKey.QuickSettings,
@@ -52,91 +55,83 @@
             )
     }
 
-    @Test(expected = IllegalStateException::class)
-    fun allSceneKeys_noSuchContainer_throws() {
-        val underTest = utils.fakeSceneContainerRepository()
-        underTest.allSceneKeys("nonExistingContainer")
-    }
-
     @Test
-    fun currentScene() = runTest {
-        val underTest = utils.fakeSceneContainerRepository()
-        val currentScene by collectLastValue(underTest.currentScene("container1"))
-        assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+    fun desiredScene() =
+        testScope.runTest {
+            val underTest = utils.fakeSceneContainerRepository()
+            val currentScene by collectLastValue(underTest.desiredScene)
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-        underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
-        assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
-    }
+            underTest.setDesiredScene(SceneModel(SceneKey.Shade))
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
+        }
 
     @Test(expected = IllegalStateException::class)
-    fun currentScene_noSuchContainer_throws() {
-        val underTest = utils.fakeSceneContainerRepository()
-        underTest.currentScene("nonExistingContainer")
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun setCurrentScene_noSuchContainer_throws() {
-        val underTest = utils.fakeSceneContainerRepository()
-        underTest.setCurrentScene("nonExistingContainer", SceneModel(SceneKey.Shade))
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun setCurrentScene_noSuchSceneInContainer_throws() {
+    fun setDesiredScene_noSuchSceneInContainer_throws() {
         val underTest =
             utils.fakeSceneContainerRepository(
-                setOf(
-                    utils.fakeSceneContainerConfig("container1"),
-                    utils.fakeSceneContainerConfig(
-                        "container2",
-                        listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
-                    ),
-                )
+                utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)),
             )
-        underTest.setCurrentScene("container2", SceneModel(SceneKey.Shade))
+        underTest.setDesiredScene(SceneModel(SceneKey.Shade))
     }
 
     @Test
-    fun isVisible() = runTest {
-        val underTest = utils.fakeSceneContainerRepository()
-        val isVisible by collectLastValue(underTest.isVisible("container1"))
-        assertThat(isVisible).isTrue()
+    fun isVisible() =
+        testScope.runTest {
+            val underTest = utils.fakeSceneContainerRepository()
+            val isVisible by collectLastValue(underTest.isVisible)
+            assertThat(isVisible).isTrue()
 
-        underTest.setVisible("container1", false)
-        assertThat(isVisible).isFalse()
+            underTest.setVisible(false)
+            assertThat(isVisible).isFalse()
 
-        underTest.setVisible("container1", true)
-        assertThat(isVisible).isTrue()
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun isVisible_noSuchContainer_throws() {
-        val underTest = utils.fakeSceneContainerRepository()
-        underTest.isVisible("nonExistingContainer")
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun setVisible_noSuchContainer_throws() {
-        val underTest = utils.fakeSceneContainerRepository()
-        underTest.setVisible("nonExistingContainer", false)
-    }
+            underTest.setVisible(true)
+            assertThat(isVisible).isTrue()
+        }
 
     @Test
-    fun sceneTransitionProgress() = runTest {
-        val underTest = utils.fakeSceneContainerRepository()
-        val sceneTransitionProgress by
-            collectLastValue(underTest.sceneTransitionProgress("container1"))
-        assertThat(sceneTransitionProgress).isEqualTo(1f)
+    fun transitionState_defaultsToIdle() =
+        testScope.runTest {
+            val underTest = utils.fakeSceneContainerRepository()
+            val transitionState by collectLastValue(underTest.transitionState)
 
-        underTest.setSceneTransitionProgress("container1", 0.1f)
-        assertThat(sceneTransitionProgress).isEqualTo(0.1f)
+            assertThat(transitionState)
+                .isEqualTo(
+                    ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+                )
+        }
 
-        underTest.setSceneTransitionProgress("container1", 0.9f)
-        assertThat(sceneTransitionProgress).isEqualTo(0.9f)
-    }
+    @Test
+    fun transitionState_reflectsUpdates() =
+        testScope.runTest {
+            val underTest = utils.fakeSceneContainerRepository()
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(SceneKey.Lockscreen)
+                )
+            underTest.setTransitionState(transitionState)
+            val reflectedTransitionState by collectLastValue(underTest.transitionState)
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
 
-    @Test(expected = IllegalStateException::class)
-    fun sceneTransitionProgress_noSuchContainer_throws() {
-        val underTest = utils.fakeSceneContainerRepository()
-        underTest.sceneTransitionProgress("nonExistingContainer")
-    }
+            val progress = MutableStateFlow(1f)
+            transitionState.value =
+                ObservableTransitionState.Transition(
+                    fromScene = SceneKey.Lockscreen,
+                    toScene = SceneKey.Shade,
+                    progress = progress,
+                )
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
+
+            progress.value = 0.1f
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
+
+            progress.value = 0.9f
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
+
+            underTest.setTransitionState(null)
+            assertThat(reflectedTransitionState)
+                .isEqualTo(
+                    ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+                )
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index ee4f6c2..713c602 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -22,10 +22,12 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -36,40 +38,113 @@
 class SceneInteractorTest : SysuiTestCase() {
 
     private val utils = SceneTestUtils(this)
-    private val underTest = utils.sceneInteractor()
+    private val testScope = utils.testScope
+    private val repository = utils.fakeSceneContainerRepository()
+    private val underTest = utils.sceneInteractor(repository = repository)
 
     @Test
     fun allSceneKeys() {
-        assertThat(underTest.allSceneKeys("container1")).isEqualTo(utils.fakeSceneKeys())
+        assertThat(underTest.allSceneKeys()).isEqualTo(utils.fakeSceneKeys())
     }
 
     @Test
-    fun sceneTransitions() = runTest {
-        val currentScene by collectLastValue(underTest.currentScene("container1"))
-        assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+    fun changeScene() =
+        testScope.runTest {
+            val desiredScene by collectLastValue(underTest.desiredScene)
+            assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-        underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
-        assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
-    }
+            underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
+            assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Shade))
+        }
 
     @Test
-    fun sceneTransitionProgress() = runTest {
-        val progress by collectLastValue(underTest.sceneTransitionProgress("container1"))
-        assertThat(progress).isEqualTo(1f)
+    fun onSceneChanged() =
+        testScope.runTest {
+            val desiredScene by collectLastValue(underTest.desiredScene)
+            assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-        underTest.setSceneTransitionProgress("container1", 0.55f)
-        assertThat(progress).isEqualTo(0.55f)
-    }
+            underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
+            assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Shade))
+        }
 
     @Test
-    fun isVisible() = runTest {
-        val isVisible by collectLastValue(underTest.isVisible("container1"))
-        assertThat(isVisible).isTrue()
+    fun transitionState() =
+        testScope.runTest {
+            val underTest = utils.fakeSceneContainerRepository()
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(SceneKey.Lockscreen)
+                )
+            underTest.setTransitionState(transitionState)
+            val reflectedTransitionState by collectLastValue(underTest.transitionState)
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
 
-        underTest.setVisible("container1", false)
-        assertThat(isVisible).isFalse()
+            val progress = MutableStateFlow(1f)
+            transitionState.value =
+                ObservableTransitionState.Transition(
+                    fromScene = SceneKey.Lockscreen,
+                    toScene = SceneKey.Shade,
+                    progress = progress,
+                )
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
 
-        underTest.setVisible("container1", true)
-        assertThat(isVisible).isTrue()
-    }
+            progress.value = 0.1f
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
+
+            progress.value = 0.9f
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
+
+            underTest.setTransitionState(null)
+            assertThat(reflectedTransitionState)
+                .isEqualTo(
+                    ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+                )
+        }
+
+    @Test
+    fun transitioningTo() =
+        testScope.runTest {
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(underTest.desiredScene.value.key)
+                )
+            underTest.setTransitionState(transitionState)
+
+            val transitionTo by collectLastValue(underTest.transitioningTo)
+            assertThat(transitionTo).isNull()
+
+            underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
+            assertThat(transitionTo).isNull()
+
+            val progress = MutableStateFlow(0f)
+            transitionState.value =
+                ObservableTransitionState.Transition(
+                    fromScene = underTest.desiredScene.value.key,
+                    toScene = SceneKey.Shade,
+                    progress = progress,
+                )
+            assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+            progress.value = 0.5f
+            assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+            progress.value = 1f
+            assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+            transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade)
+            assertThat(transitionTo).isNull()
+        }
+
+    @Test
+    fun isVisible() =
+        testScope.runTest {
+            val isVisible by collectLastValue(underTest.isVisible)
+            assertThat(isVisible).isTrue()
+
+            underTest.setVisible(false, "reason")
+            assertThat(isVisible).isFalse()
+
+            underTest.setVisible(true, "reason")
+            assertThat(isVisible).isTrue()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
new file mode 100644
index 0000000..951cadd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -0,0 +1,344 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.startable
+
+import android.view.Display
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.model.SysUiState
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SceneContainerStartableTest : SysuiTestCase() {
+
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
+    private val sceneInteractor = utils.sceneInteractor()
+    private val featureFlags = utils.featureFlags
+    private val authenticationRepository = utils.authenticationRepository()
+    private val authenticationInteractor =
+        utils.authenticationInteractor(
+            repository = authenticationRepository,
+        )
+    private val keyguardRepository = utils.keyguardRepository()
+    private val keyguardInteractor =
+        utils.keyguardInteractor(
+            repository = keyguardRepository,
+        )
+    private val sysUiState: SysUiState = mock()
+
+    private val underTest =
+        SceneContainerStartable(
+            applicationScope = testScope.backgroundScope,
+            sceneInteractor = sceneInteractor,
+            authenticationInteractor = authenticationInteractor,
+            keyguardInteractor = keyguardInteractor,
+            featureFlags = featureFlags,
+            sysUiState = sysUiState,
+            displayId = Display.DEFAULT_DISPLAY,
+            sceneLogger = mock(),
+        )
+
+    @Test
+    fun hydrateVisibility() =
+        testScope.runTest {
+            val currentDesiredSceneKey by
+                collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            val isVisible by collectLastValue(sceneInteractor.isVisible)
+            val transitionStateFlow =
+                prepareState(
+                    isDeviceUnlocked = true,
+                    initialSceneKey = SceneKey.Gone,
+                )
+            assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone)
+            assertThat(isVisible).isTrue()
+
+            underTest.start()
+            assertThat(isVisible).isFalse()
+
+            sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason")
+            transitionStateFlow.value =
+                ObservableTransitionState.Transition(
+                    fromScene = SceneKey.Gone,
+                    toScene = SceneKey.Shade,
+                    progress = flowOf(0.5f),
+                )
+            assertThat(isVisible).isTrue()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
+            transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade)
+            assertThat(isVisible).isTrue()
+
+            sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+            transitionStateFlow.value =
+                ObservableTransitionState.Transition(
+                    fromScene = SceneKey.Shade,
+                    toScene = SceneKey.Gone,
+                    progress = flowOf(0.5f),
+                )
+            assertThat(isVisible).isTrue()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
+            transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
+            assertThat(isVisible).isFalse()
+        }
+
+    @Test
+    fun switchToLockscreenWhenDeviceLocks() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState(
+                isDeviceUnlocked = true,
+                initialSceneKey = SceneKey.Gone,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+            underTest.start()
+
+            authenticationRepository.setUnlocked(false)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun switchFromBouncerToGoneWhenDeviceUnlocked() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState(
+                isDeviceUnlocked = false,
+                initialSceneKey = SceneKey.Bouncer,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
+            underTest.start()
+
+            authenticationRepository.setUnlocked(true)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState(
+                isBypassEnabled = true,
+                initialSceneKey = SceneKey.Lockscreen,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            underTest.start()
+
+            authenticationRepository.setUnlocked(true)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun stayOnLockscreenWhenDeviceUnlocksWithBypassOff() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState(
+                isBypassEnabled = false,
+                initialSceneKey = SceneKey.Lockscreen,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            underTest.start()
+
+            authenticationRepository.setUnlocked(true)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun switchToLockscreenWhenDeviceSleepsLocked() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState(
+                isDeviceUnlocked = false,
+                initialSceneKey = SceneKey.Shade,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
+            underTest.start()
+
+            keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun hydrateSystemUiState() =
+        testScope.runTest {
+            val transitionStateFlow = prepareState()
+            underTest.start()
+            runCurrent()
+            clearInvocations(sysUiState)
+
+            listOf(
+                    SceneKey.Gone,
+                    SceneKey.Lockscreen,
+                    SceneKey.Bouncer,
+                    SceneKey.Shade,
+                    SceneKey.QuickSettings,
+                )
+                .forEachIndexed { index, sceneKey ->
+                    sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
+                    runCurrent()
+                    verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY)
+
+                    sceneInteractor.onSceneChanged(SceneModel(sceneKey), "reason")
+                    runCurrent()
+                    verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY)
+
+                    transitionStateFlow.value = ObservableTransitionState.Idle(sceneKey)
+                    runCurrent()
+                    verify(sysUiState, times(index + 1)).commitUpdate(Display.DEFAULT_DISPLAY)
+                }
+        }
+
+    @Test
+    fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState(
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.None,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            underTest.start()
+
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun stayOnLockscreenWhenDeviceStartsToWakeUp_authMethodSwipe() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState(
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.Swipe,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            underTest.start()
+
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun doesNotSwitchToGoneWhenDeviceStartsToWakeUp_authMethodSecure() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState(
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.Pin,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            underTest.start()
+
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun switchToGoneWhenDeviceStartsToWakeUp_authMethodSecure_deviceUnlocked() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState(
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.Pin,
+                isDeviceUnlocked = false,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            underTest.start()
+
+            authenticationRepository.setUnlocked(true)
+            runCurrent()
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
+    private fun prepareState(
+        isDeviceUnlocked: Boolean = false,
+        isBypassEnabled: Boolean = false,
+        initialSceneKey: SceneKey? = null,
+        authenticationMethod: AuthenticationMethodModel? = null,
+    ): MutableStateFlow<ObservableTransitionState> {
+        featureFlags.set(Flags.SCENE_CONTAINER, true)
+        authenticationRepository.setUnlocked(isDeviceUnlocked)
+        keyguardRepository.setBypassEnabled(isBypassEnabled)
+        val transitionStateFlow =
+            MutableStateFlow<ObservableTransitionState>(
+                ObservableTransitionState.Idle(SceneKey.Lockscreen)
+            )
+        sceneInteractor.setTransitionState(transitionStateFlow)
+        initialSceneKey?.let {
+            transitionStateFlow.value = ObservableTransitionState.Idle(it)
+            sceneInteractor.changeScene(SceneModel(it), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(it), "reason")
+        }
+        authenticationMethod?.let {
+            authenticationRepository.setAuthenticationMethod(authenticationMethod.toDataLayer())
+            authenticationRepository.setLockscreenEnabled(
+                authenticationMethod != AuthenticationMethodModel.None
+            )
+        }
+        return transitionStateFlow
+    }
+
+    companion object {
+        private val STARTING_TO_SLEEP =
+            WakefulnessModel(
+                state = WakefulnessState.STARTING_TO_SLEEP,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.POWER_BUTTON
+            )
+        private val STARTING_TO_WAKE =
+            WakefulnessModel(
+                state = WakefulnessState.STARTING_TO_WAKE,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.POWER_BUTTON
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index cd2f5af..88abb64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -40,7 +40,6 @@
     private val underTest =
         SceneContainerViewModel(
             interactor = interactor,
-            containerName = "container1",
         )
 
     @Test
@@ -48,10 +47,10 @@
         val isVisible by collectLastValue(underTest.isVisible)
         assertThat(isVisible).isTrue()
 
-        interactor.setVisible("container1", false)
+        interactor.setVisible(false, "reason")
         assertThat(isVisible).isFalse()
 
-        interactor.setVisible("container1", true)
+        interactor.setVisible(true, "reason")
         assertThat(isVisible).isTrue()
     }
 
@@ -65,7 +64,8 @@
         val currentScene by collectLastValue(underTest.currentScene)
         assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-        underTest.setCurrentScene(SceneModel(SceneKey.Shade))
+        underTest.onSceneChanged(SceneModel(SceneKey.Shade))
+
         assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 6e6833d..2698131bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -17,8 +17,10 @@
 package com.android.systemui.screenrecord;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 8744aa3..59b5953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -16,9 +16,11 @@
 
 package com.android.systemui.screenrecord;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
@@ -32,6 +34,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
@@ -41,7 +44,9 @@
 import com.android.systemui.media.MediaProjectionCaptureTarget;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -77,20 +82,38 @@
     private UserContextProvider mUserContextTracker;
     @Captor
     private ArgumentCaptor<Runnable> mRunnableCaptor;
-    private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil() {
-        public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
-                boolean requiresShadeOpen) {
-            action.onDismiss();
-        }
-    };
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
+    private SysuiStatusBarStateController mStatusBarStateController;
+    @Mock
+    private ActivityStarter mActivityStarter;
+
+    private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+
+    private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil(
+            mKeyguardStateController, mStatusBarStateController, mActivityStarter);
 
     private RecordingService mRecordingService;
 
+    private class RecordingServiceTestable extends RecordingService {
+        RecordingServiceTestable(
+                RecordingController controller, Executor executor,
+                Handler handler, UiEventLogger uiEventLogger,
+                NotificationManager notificationManager,
+                UserContextProvider userContextTracker, KeyguardDismissUtil keyguardDismissUtil) {
+            super(controller, executor, handler,
+                    uiEventLogger, notificationManager, userContextTracker, keyguardDismissUtil);
+            attachBaseContext(mContext);
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mHandler,
-                mUiEventLogger, mNotificationManager, mUserContextTracker, mKeyguardDismissUtil));
+        mRecordingService = Mockito.spy(new RecordingServiceTestable(mController, mExecutor,
+                mHandler, mUiEventLogger, mNotificationManager,
+                mUserContextTracker, mKeyguardDismissUtil));
 
         // Return actual context info
         doReturn(mContext).when(mRecordingService).getApplicationContext();
@@ -160,8 +183,7 @@
         Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false, null);
         mRecordingService.onStartCommand(startIntent, 0, 0);
 
-        // Then the state is set to not recording
-        verify(mController).updateState(false);
+        assertUpdateState(false);
     }
 
     @Test
@@ -179,7 +201,7 @@
 
         mRecordingService.onStopped();
 
-        verify(mController).updateState(false);
+        assertUpdateState(false);
     }
 
     @Test
@@ -235,8 +257,21 @@
         verify(mExecutor).execute(mRunnableCaptor.capture());
         mRunnableCaptor.getValue().run();
 
-        // Then the state is set to not recording and we cancel the notification
-        verify(mController).updateState(false);
+        assertUpdateState(false);
         verify(mNotificationManager).cancelAsUser(any(), anyInt(), any());
     }
+
+    private void assertUpdateState(boolean state) {
+        // Then the state is set to not recording, and we cancel the notification
+        // non SYSTEM user doesn't have the reference to the correct controller,
+        // so a broadcast is sent in case of non SYSTEM user.
+        if (UserHandle.USER_SYSTEM == mContext.getUserId()) {
+            verify(mController).updateState(state);
+        } else {
+            ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+            verify(mRecordingService).sendBroadcast(argumentCaptor.capture(), eq(PERMISSION_SELF));
+            assertEquals(RecordingController.INTENT_UPDATE_STATE,
+                    argumentCaptor.getValue().getAction());
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index 07feedf..ad6909d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -126,6 +126,7 @@
 
     private fun onSpinnerItemSelected(position: Int) {
         val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
-        spinner.onItemSelectedListener.onItemSelected(spinner, mock(), position, /* id= */ 0)
+        checkNotNull(spinner.onItemSelectedListener)
+            .onItemSelected(spinner, mock(), position, /* id= */ 0)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
index 7ba2cf7..ca4486b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
@@ -20,12 +20,14 @@
 import android.content.Context
 import android.content.Intent
 import android.net.Uri
+import androidx.test.ext.truth.content.IntentSubject.assertThat as assertThatIntent
 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 com.google.common.truth.Truth.assertWithMessage
 import org.junit.Test
 import org.mockito.Mockito.`when` as whenever
 
@@ -33,113 +35,133 @@
 class ActionIntentCreatorTest : SysuiTestCase() {
 
     @Test
-    fun testCreateShareIntent() {
+    fun testCreateShare() {
         val uri = Uri.parse("content://fake")
 
-        val output = ActionIntentCreator.createShareIntent(uri)
+        val output = ActionIntentCreator.createShare(uri)
 
-        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
-        )
+        assertThatIntent(output).hasAction(Intent.ACTION_CHOOSER)
+        assertThatIntent(output)
+            .hasFlags(
+                Intent.FLAG_ACTIVITY_NEW_TASK or
+                    Intent.FLAG_ACTIVITY_CLEAR_TASK or
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION
+            )
 
+        assertThatIntent(output).extras().parcelable<Intent>(Intent.EXTRA_INTENT).isNotNull()
         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)).isNull()
-        assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_TEXT)).isNull()
-        assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))
-            .isEqualTo(uri)
+
+        assertThatIntent(wrappedIntent).hasAction(Intent.ACTION_SEND)
+        assertThatIntent(wrappedIntent).hasData(uri)
+        assertThatIntent(wrappedIntent).hasType("image/png")
+        assertThatIntent(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_SUBJECT)
+        assertThatIntent(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_TEXT)
+        assertThatIntent(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri)
     }
 
     @Test
-    fun testCreateShareIntentWithSubject() {
+    fun testCreateShare_embeddedUserIdRemoved() {
+        val uri = Uri.parse("content://555@fake")
+
+        val output = ActionIntentCreator.createShare(uri)
+
+        assertThatIntent(output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java))
+            .hasData(Uri.parse("content://fake"))
+    }
+
+    @Test
+    fun testCreateShareWithSubject() {
         val uri = Uri.parse("content://fake")
         val subject = "Example subject"
 
-        val output = ActionIntentCreator.createShareIntentWithSubject(uri, subject)
+        val output = ActionIntentCreator.createShareWithSubject(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
-        )
+        assertThatIntent(output).hasAction(Intent.ACTION_CHOOSER)
+        assertThatIntent(output)
+            .hasFlags(
+                Intent.FLAG_ACTIVITY_NEW_TASK or
+                    Intent.FLAG_ACTIVITY_CLEAR_TASK or
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION
+            )
 
         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?.getStringExtra(Intent.EXTRA_TEXT)).isNull()
-        assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))
-            .isEqualTo(uri)
+        assertThatIntent(wrappedIntent).hasAction(Intent.ACTION_SEND)
+        assertThatIntent(wrappedIntent).hasData(uri)
+        assertThatIntent(wrappedIntent).hasType("image/png")
+        assertThatIntent(wrappedIntent).extras().string(Intent.EXTRA_SUBJECT).isEqualTo(subject)
+        assertThatIntent(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_TEXT)
+        assertThatIntent(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri)
     }
 
     @Test
-    fun testCreateShareIntentWithExtraText() {
+    fun testCreateShareWithText() {
         val uri = Uri.parse("content://fake")
         val extraText = "Extra text"
 
-        val output = ActionIntentCreator.createShareIntentWithExtraText(uri, extraText)
+        val output = ActionIntentCreator.createShareWithText(uri, extraText)
 
-        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
-        )
+        assertThatIntent(output).hasAction(Intent.ACTION_CHOOSER)
+        assertThatIntent(output)
+            .hasFlags(
+                Intent.FLAG_ACTIVITY_NEW_TASK or
+                    Intent.FLAG_ACTIVITY_CLEAR_TASK or
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION
+            )
 
         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)).isNull()
-        assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(extraText)
-        assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))
-            .isEqualTo(uri)
+        assertThatIntent(wrappedIntent).hasAction(Intent.ACTION_SEND)
+        assertThatIntent(wrappedIntent).hasData(uri)
+        assertThatIntent(wrappedIntent).hasType("image/png")
+        assertThatIntent(wrappedIntent).extras().doesNotContainKey(Intent.EXTRA_SUBJECT)
+        assertThatIntent(wrappedIntent).extras().string(Intent.EXTRA_TEXT).isEqualTo(extraText)
+        assertThatIntent(wrappedIntent).extras().parcelable<Uri>(Intent.EXTRA_STREAM).isEqualTo(uri)
     }
 
     @Test
-    fun testCreateEditIntent() {
+    fun testCreateEdit() {
         val uri = Uri.parse("content://fake")
         val context = mock<Context>()
 
-        val output = ActionIntentCreator.createEditIntent(uri, context)
+        whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("")
 
-        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)
+        val output = ActionIntentCreator.createEdit(uri, context)
+
+        assertThatIntent(output).hasAction(Intent.ACTION_EDIT)
+        assertThatIntent(output).hasData(uri)
+        assertThatIntent(output).hasType("image/png")
+        assertWithMessage("getComponent()").that(output.component).isNull()
+        assertThat(output.getStringExtra("edit_source")).isEqualTo("screenshot")
+        assertThatIntent(output)
+            .hasFlags(
+                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
+            )
     }
 
     @Test
-    fun testCreateEditIntent_withEditor() {
+    fun testCreateEdit_embeddedUserIdRemoved() {
+        val uri = Uri.parse("content://555@fake")
+        val context = mock<Context>()
+        whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("")
+
+        val output = ActionIntentCreator.createEdit(uri, context)
+
+        assertThatIntent(output).hasData(Uri.parse("content://fake"))
+    }
+
+    @Test
+    fun testCreateEdit_withEditor() {
         val uri = Uri.parse("content://fake")
         val context = mock<Context>()
-        var component = ComponentName("com.android.foo", "com.android.foo.Something")
+        val 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)
+        val output = ActionIntentCreator.createEdit(uri, context)
 
-        assertThat(output.component).isEqualTo(component)
-    }
-
-    private fun assertFlagsSet(expected: Int, observed: Int) {
-        assertThat(observed and expected).isEqualTo(expected)
+        assertThatIntent(output).hasComponent(component)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
index 67b1099..d8897e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
@@ -16,14 +16,17 @@
 
 package com.android.systemui.screenshot.appclips;
 
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
+
 import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.admin.DevicePolicyManager;
@@ -31,8 +34,6 @@
 import android.content.Intent;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -46,7 +47,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.Optional;
@@ -63,7 +63,6 @@
     @Mock private Optional<Bubbles> mOptionalBubbles;
     @Mock private Bubbles mBubbles;
     @Mock private DevicePolicyManager mDevicePolicyManager;
-    @Mock private UserManager mUserManager;
 
     private AppClipsService mAppClipsService;
 
@@ -81,51 +80,84 @@
     }
 
     @Test
+    public void flagOff_internal_shouldReturnFailed() throws RemoteException {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(false);
+
+        assertThat(getInterfaceWithRealContext()
+                .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+    }
+
+    @Test
     public void emptyBubbles_shouldReturnFalse() throws RemoteException {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(true);
+        mockForEmptyBubbles();
 
         assertThat(getInterfaceWithRealContext()
                 .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
     }
 
     @Test
+    public void emptyBubbles_internal_shouldReturnFailed() throws RemoteException {
+        mockForEmptyBubbles();
+
+        assertThat(getInterfaceWithRealContext()
+                .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+    }
+
+    @Test
     public void taskIdNotAppBubble_shouldReturnFalse() throws RemoteException {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(false);
-        when(mOptionalBubbles.get()).thenReturn(mBubbles);
-        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(false);
+        mockForTaskIdNotAppBubble();
 
         assertThat(getInterfaceWithRealContext()
                 .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
     }
 
     @Test
+    public void taskIdNotAppBubble_internal_shouldReturnWindowUnsupported() throws RemoteException {
+        mockForTaskIdNotAppBubble();
+
+        assertThat(getInterfaceWithRealContext()
+                .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED);
+    }
+
+    @Test
     public void dpmScreenshotBlocked_shouldReturnFalse() throws RemoteException {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(false);
-        when(mOptionalBubbles.get()).thenReturn(mBubbles);
-        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
-        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(true);
+        mockForScreenshotBlocked();
 
         assertThat(getInterfaceWithRealContext()
                 .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
     }
 
     @Test
+    public void dpmScreenshotBlocked_internal_shouldReturnBlockedByAdmin() throws RemoteException {
+        mockForScreenshotBlocked();
+
+        assertThat(getInterfaceWithRealContext()
+                .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN);
+    }
+
+    @Test
     public void configComponentNameNotValid_shouldReturnFalse() throws RemoteException {
-        when(mMockContext.getString(anyInt())).thenReturn(EMPTY);
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(false);
-        when(mOptionalBubbles.get()).thenReturn(mBubbles);
-        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
-        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+        mockForInvalidConfigComponentName();
 
         assertThat(getInterfaceWithMockContext()
                 .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
     }
 
     @Test
+    public void configComponentNameNotValid_internal_shouldReturnFailed() throws RemoteException {
+        mockForInvalidConfigComponentName();
+
+        assertThat(getInterfaceWithMockContext()
+                .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+    }
+
+
+    @Test
     public void allPrerequisitesSatisfy_shouldReturnTrue() throws RemoteException {
         mockToSatisfyAllPrerequisites();
 
@@ -134,28 +166,44 @@
     }
 
     @Test
-    public void isManagedProfile_shouldUseProxyConnection() throws RemoteException {
-        when(mUserManager.isManagedProfile()).thenReturn(true);
-        when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM);
-        IAppClipsService service = getInterfaceWithRealContext();
-        mAppClipsService.mProxyConnectorToMainProfile =
-                Mockito.spy(mAppClipsService.mProxyConnectorToMainProfile);
+    public void allPrerequisitesSatisfy_internal_shouldReturnSuccess() throws RemoteException {
+        mockToSatisfyAllPrerequisites();
 
-        service.canLaunchCaptureContentActivityForNote(FAKE_TASK_ID);
-
-        verify(mAppClipsService.mProxyConnectorToMainProfile).postForResult(any());
+        assertThat(getInterfaceWithRealContext()
+                .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
     }
 
-    @Test
-    public void isManagedProfile_noMainUser_shouldReturnFalse() {
-        when(mUserManager.isManagedProfile()).thenReturn(true);
-        when(mUserManager.getMainUser()).thenReturn(null);
-
-        getInterfaceWithRealContext();
-
-        assertThat(mAppClipsService.mProxyConnectorToMainProfile).isNull();
+    private void mockForEmptyBubbles() {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(true);
     }
 
+    private void mockForTaskIdNotAppBubble() {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(false);
+        when(mOptionalBubbles.get()).thenReturn(mBubbles);
+        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(false);
+    }
+
+    private void mockForScreenshotBlocked() {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(false);
+        when(mOptionalBubbles.get()).thenReturn(mBubbles);
+        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(true);
+    }
+
+    private void mockForInvalidConfigComponentName() {
+        when(mMockContext.getString(anyInt())).thenReturn(EMPTY);
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(false);
+        when(mOptionalBubbles.get()).thenReturn(mBubbles);
+        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+    }
+
+
     private void mockToSatisfyAllPrerequisites() {
         when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
         when(mOptionalBubbles.isEmpty()).thenReturn(false);
@@ -166,13 +214,13 @@
 
     private IAppClipsService getInterfaceWithRealContext() {
         mAppClipsService = new AppClipsService(getContext(), mFeatureFlags,
-                mOptionalBubbles, mDevicePolicyManager, mUserManager);
+                mOptionalBubbles, mDevicePolicyManager);
         return getInterfaceFromService(mAppClipsService);
     }
 
     private IAppClipsService getInterfaceWithMockContext() {
         mAppClipsService = new AppClipsService(mMockContext, mFeatureFlags,
-                mOptionalBubbles, mDevicePolicyManager, mUserManager);
+                mOptionalBubbles, mDevicePolicyManager);
         return getInterfaceFromService(mAppClipsService);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
index e9007ff..7fad972 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
@@ -24,22 +24,19 @@
 import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
 import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE;
 
-import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
+import static com.android.internal.infra.AndroidFuture.completedFuture;
 import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;
 import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
-import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_USE_WP_USER;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Activity;
-import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -52,20 +49,22 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.intercepting.SingleActivityFactory;
 
+import com.android.internal.infra.ServiceConnector;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IAppClipsService;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.notetask.NoteTaskController;
-import com.android.systemui.settings.UserTracker;
-import com.android.wm.shell.bubbles.Bubbles;
+
+import com.google.common.util.concurrent.MoreExecutors;
 
 import org.junit.After;
 import org.junit.Before;
@@ -75,8 +74,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.List;
-import java.util.Optional;
+import java.util.concurrent.Executor;
 
 @RunWith(AndroidTestingRunner.class)
 public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
@@ -86,25 +84,19 @@
     private static final int TEST_UID = 42;
     private static final String TEST_CALLING_PACKAGE = "test-calling-package";
 
-    @Mock
-    private DevicePolicyManager mDevicePolicyManager;
-    @Mock
-    private FeatureFlags mFeatureFlags;
-    @Mock
-    private Optional<Bubbles> mOptionalBubbles;
-    @Mock
-    private Bubbles mBubbles;
+    @Mock private ServiceConnector<IAppClipsService> mServiceConnector;
     @Mock
     private NoteTaskController mNoteTaskController;
     @Mock
     private PackageManager mPackageManager;
     @Mock
-    private UserTracker mUserTracker;
-    @Mock
     private UiEventLogger mUiEventLogger;
     @Mock
-    private UserManager mUserManager;
-
+    private BroadcastSender mBroadcastSender;
+    @Background
+    private Executor mBgExecutor;
+    @Main
+    private Executor mMainExecutor;
     @Main
     private Handler mMainHandler;
 
@@ -114,9 +106,9 @@
             new SingleActivityFactory<>(AppClipsTrampolineActivityTestable.class) {
                 @Override
                 protected AppClipsTrampolineActivityTestable create(Intent unUsed) {
-                    return new AppClipsTrampolineActivityTestable(mDevicePolicyManager,
-                            mFeatureFlags, mOptionalBubbles, mNoteTaskController, mPackageManager,
-                            mUserTracker, mUiEventLogger, mUserManager, mMainHandler);
+                    return new AppClipsTrampolineActivityTestable(mServiceConnector,
+                            mNoteTaskController, mPackageManager, mUiEventLogger, mBroadcastSender,
+                            mBgExecutor, mMainExecutor, mMainHandler);
                 }
             };
 
@@ -133,6 +125,8 @@
             mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
 
         MockitoAnnotations.initMocks(this);
+        mBgExecutor = MoreExecutors.directExecutor();
+        mMainExecutor = MoreExecutors.directExecutor();
         mMainHandler = mContext.getMainThreadHandler();
 
         mActivityIntent = new Intent(mContext, AppClipsTrampolineActivityTestable.class);
@@ -169,19 +163,9 @@
     }
 
     @Test
-    public void flagOff_shouldFinishWithResultCancel() {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(false);
-
-        mActivityRule.launchActivity(mActivityIntent);
-
-        assertThat(mActivityRule.getActivityResult().getResultCode())
-                .isEqualTo(Activity.RESULT_CANCELED);
-    }
-
-    @Test
-    public void bubblesEmpty_shouldFinishWithFailed() {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(true);
+    public void queryService_returnedFailed_shouldFinishWithFailed() {
+        when(mServiceConnector.postForResult(any()))
+                .thenReturn(completedFuture(CAPTURE_CONTENT_FOR_NOTE_FAILED));
 
         mActivityRule.launchActivity(mActivityIntent);
 
@@ -189,14 +173,13 @@
         assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
         assertThat(getStatusCodeExtra(actualResult.getResultData()))
                 .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+        assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
     }
 
     @Test
-    public void taskIdNotAppBubble_shouldFinishWithWindowModeUnsupported() {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(false);
-        when(mOptionalBubbles.get()).thenReturn(mBubbles);
-        when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(false);
+    public void queryService_returnedWindowModeUnsupported_shouldFinishWithWindowModeUnsupported() {
+        when(mServiceConnector.postForResult(any()))
+                .thenReturn(completedFuture(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED));
 
         mActivityRule.launchActivity(mActivityIntent);
 
@@ -204,15 +187,13 @@
         assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
         assertThat(getStatusCodeExtra(actualResult.getResultData()))
                 .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED);
+        assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
     }
 
     @Test
-    public void dpmScreenshotBlocked_shouldFinishWithBlockedByAdmin() {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(false);
-        when(mOptionalBubbles.get()).thenReturn(mBubbles);
-        when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(true);
-        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(true);
+    public void queryService_returnedScreenshotBlocked_shouldFinishWithBlockedByAdmin() {
+        when(mServiceConnector.postForResult(any()))
+                .thenReturn(completedFuture(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN));
 
         mActivityRule.launchActivity(mActivityIntent);
 
@@ -220,6 +201,7 @@
         assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
         assertThat(getStatusCodeExtra(actualResult.getResultData()))
                 .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN);
+        assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
     }
 
     @Test
@@ -240,6 +222,7 @@
         assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
         assertThat(getStatusCodeExtra(actualResult.getResultData()))
                 .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED);
+        assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
     }
 
     @Test
@@ -261,6 +244,7 @@
         assertThat(getStatusCodeExtra(actualResult.getResultData()))
                 .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
         assertThat(actualResult.getResultData().getData()).isEqualTo(TEST_URI);
+        assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
     }
 
     @Test
@@ -274,48 +258,9 @@
         verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_TRIGGERED, TEST_UID, TEST_CALLING_PACKAGE);
     }
 
-    @Test
-    public void startAppClipsActivity_throughWPUser_shouldStartMainUserActivity()
-            throws NameNotFoundException {
-        when(mUserManager.isManagedProfile()).thenReturn(true);
-        when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM);
-        mockToSatisfyAllPrerequisites();
-
-        AppClipsTrampolineActivityTestable activity = mActivityRule.launchActivity(mActivityIntent);
-        waitForIdleSync();
-
-        Intent actualIntent = activity.mStartedIntent;
-        assertThat(actualIntent.getComponent()).isEqualTo(
-                new ComponentName(mContext, AppClipsTrampolineActivity.class));
-        assertThat(actualIntent.getFlags()).isEqualTo(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
-        assertThat(actualIntent.getBooleanExtra(EXTRA_USE_WP_USER, false)).isTrue();
-        assertThat(activity.mStartingUser).isEqualTo(UserHandle.SYSTEM);
-    }
-
-    @Test
-    public void startAppClipsActivity_throughWPUser_noMainUser_shouldFinishWithFailed()
-            throws NameNotFoundException {
-        when(mUserManager.isManagedProfile()).thenReturn(true);
-        when(mUserManager.getMainUser()).thenReturn(null);
-
-        mockToSatisfyAllPrerequisites();
-
-        mActivityRule.launchActivity(mActivityIntent);
-        waitForIdleSync();
-
-        ActivityResult actualResult = mActivityRule.getActivityResult();
-        assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
-        assertThat(getStatusCodeExtra(actualResult.getResultData()))
-                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
-    }
-
     private void mockToSatisfyAllPrerequisites() throws NameNotFoundException {
-        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
-        when(mOptionalBubbles.isEmpty()).thenReturn(false);
-        when(mOptionalBubbles.get()).thenReturn(mBubbles);
-        when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(true);
-        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
-        when(mUserTracker.getUserProfiles()).thenReturn(List.of());
+        when(mServiceConnector.postForResult(any()))
+                .thenReturn(completedFuture(CAPTURE_CONTENT_FOR_NOTE_SUCCESS));
 
         ApplicationInfo testApplicationInfo = new ApplicationInfo();
         testApplicationInfo.uid = TEST_UID;
@@ -330,17 +275,14 @@
         Intent mStartedIntent;
         UserHandle mStartingUser;
 
-        public AppClipsTrampolineActivityTestable(DevicePolicyManager devicePolicyManager,
-                FeatureFlags flags,
-                Optional<Bubbles> optionalBubbles,
-                NoteTaskController noteTaskController,
-                PackageManager packageManager,
-                UserTracker userTracker,
-                UiEventLogger uiEventLogger,
-                UserManager userManager,
+        public AppClipsTrampolineActivityTestable(
+                ServiceConnector<IAppClipsService> serviceServiceConnector,
+                NoteTaskController noteTaskController, PackageManager packageManager,
+                UiEventLogger uiEventLogger, BroadcastSender broadcastSender,
+                @Background Executor bgExecutor, @Main Executor mainExecutor,
                 @Main Handler mainHandler) {
-            super(devicePolicyManager, flags, optionalBubbles, noteTaskController, packageManager,
-                    userTracker, uiEventLogger, userManager, mainHandler);
+            super(serviceServiceConnector, noteTaskController, packageManager, uiEventLogger,
+                    broadcastSender, bgExecutor, mainExecutor, mainHandler);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
new file mode 100644
index 0000000..2b78405
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness
+
+import android.hardware.display.DisplayManager
+import android.os.Handler
+import android.service.vr.IVrManager
+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.settings.DisplayTracker
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class BrightnessControllerTest : SysuiTestCase() {
+
+    private val executor = FakeExecutor(FakeSystemClock())
+    private val secureSettings = FakeSettings()
+    @Mock private lateinit var toggleSlider: ToggleSlider
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var displayTracker: DisplayTracker
+    @Mock private lateinit var displayManager: DisplayManager
+    @Mock private lateinit var iVrManager: IVrManager
+
+    private lateinit var testableLooper: TestableLooper
+
+    private lateinit var underTest: BrightnessController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+
+        underTest =
+            BrightnessController(
+                context,
+                toggleSlider,
+                userTracker,
+                displayTracker,
+                displayManager,
+                secureSettings,
+                iVrManager,
+                executor,
+                mock(),
+                Handler(testableLooper.looper)
+            )
+    }
+
+    @Test
+    fun registerCallbacksMultipleTimes_onlyOneRegistration() {
+        val repeats = 100
+        repeat(repeats) { underTest.registerCallbacks() }
+        val messagesProcessed = testableLooper.processMessagesNonBlocking(repeats)
+
+        verify(displayTracker).addBrightnessChangeCallback(any(), any())
+        verify(iVrManager).registerListener(any())
+
+        assertThat(messagesProcessed).isEqualTo(1)
+    }
+
+    @Test
+    fun unregisterCallbacksMultipleTimes_onlyOneUnregistration() {
+        val repeats = 100
+        underTest.registerCallbacks()
+        testableLooper.processAllMessages()
+
+        repeat(repeats) { underTest.unregisterCallbacks() }
+        val messagesProcessed = testableLooper.processMessagesNonBlocking(repeats)
+
+        verify(displayTracker).removeCallback(any())
+        verify(iVrManager).unregisterListener(any())
+
+        assertThat(messagesProcessed).isEqualTo(1)
+    }
+}
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
index 7646193..ed1397f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -18,27 +18,30 @@
 
 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 android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY
 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.settings.FakeDisplayTracker
-import com.android.systemui.settings.UserTracker
+import com.android.systemui.activity.SingleActivityFactory
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
+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.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
-import java.util.concurrent.Executor
 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.anyInt
+import org.mockito.Mockito.eq
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
@@ -47,31 +50,29 @@
 @TestableLooper.RunWithLooper
 class BrightnessDialogTest : SysuiTestCase() {
 
-    @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory
-    @Mock private lateinit var mainExecutor: Executor
-    @Mock private lateinit var backgroundHandler: Handler
     @Mock private lateinit var brightnessSliderController: BrightnessSliderController
+    @Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory
+    @Mock private lateinit var brightnessController: BrightnessController
+    @Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper
 
-    private var displayTracker = FakeDisplayTracker(mContext)
+    private val clock = FakeSystemClock()
+    private val mainExecutor = FakeExecutor(clock)
 
     @Rule
     @JvmField
     var activityRule =
         ActivityTestRule(
-            object : SingleActivityFactory<TestDialog>(TestDialog::class.java) {
-                override fun create(intent: Intent?): TestDialog {
-                    return TestDialog(
-                        userTracker,
-                        displayTracker,
-                        brightnessSliderControllerFactory,
-                        mainExecutor,
-                        backgroundHandler
-                    )
-                }
+            /* activityFactory= */ SingleActivityFactory {
+                TestDialog(
+                    brightnessSliderControllerFactory,
+                    brightnessControllerFactory,
+                    mainExecutor,
+                    accessibilityMgr
+                )
             },
-            false,
-            false
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
         )
 
     @Before
@@ -80,8 +81,7 @@
         `when`(brightnessSliderControllerFactory.create(any(), any()))
             .thenReturn(brightnessSliderController)
         `when`(brightnessSliderController.rootView).thenReturn(View(context))
-
-        activityRule.launchActivity(null)
+        `when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
     }
 
     @After
@@ -91,6 +91,7 @@
 
     @Test
     fun testGestureExclusion() {
+        activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG))
         val frame = activityRule.activity.requireViewById<View>(R.id.brightness_mirror_container)
 
         val lp = frame.layoutParams as ViewGroup.MarginLayoutParams
@@ -107,18 +108,79 @@
             .isEqualTo(Rect(-horizontalMargin, 0, frame.width + horizontalMargin, frame.height))
     }
 
+    @Test
+    fun testTimeout() {
+        `when`(
+                accessibilityMgr.getRecommendedTimeoutMillis(
+                    eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS),
+                    anyInt()
+                )
+            )
+            .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS)
+        val intent = Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG)
+        intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true)
+        activityRule.launchActivity(intent)
+
+        assertThat(activityRule.activity.isFinishing()).isFalse()
+
+        clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong())
+        assertThat(activityRule.activity.isFinishing()).isTrue()
+    }
+
+    @Test
+    fun testRestartTimeout() {
+        `when`(
+                accessibilityMgr.getRecommendedTimeoutMillis(
+                    eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS),
+                    anyInt()
+                )
+            )
+            .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS)
+        val intent = Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG)
+        intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true)
+        activityRule.launchActivity(intent)
+
+        assertThat(activityRule.activity.isFinishing()).isFalse()
+
+        clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2)
+        // Restart the timeout
+        activityRule.activity.onResume()
+
+        clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2)
+        // The dialog should not have disappeared yet
+        assertThat(activityRule.activity.isFinishing()).isFalse()
+
+        clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong() / 2)
+        assertThat(activityRule.activity.isFinishing()).isTrue()
+    }
+
+    @Test
+    fun testNoTimeoutIfNotStartedByBrightnessKey() {
+        `when`(
+                accessibilityMgr.getRecommendedTimeoutMillis(
+                    eq(BrightnessDialog.DIALOG_TIMEOUT_MILLIS),
+                    anyInt()
+                )
+            )
+            .thenReturn(BrightnessDialog.DIALOG_TIMEOUT_MILLIS)
+        activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG))
+
+        assertThat(activityRule.activity.isFinishing()).isFalse()
+
+        clock.advanceTime(BrightnessDialog.DIALOG_TIMEOUT_MILLIS.toLong())
+        assertThat(activityRule.activity.isFinishing()).isFalse()
+    }
+
     class TestDialog(
-        userTracker: UserTracker,
-        displayTracker: FakeDisplayTracker,
         brightnessSliderControllerFactory: BrightnessSliderController.Factory,
-        mainExecutor: Executor,
-        backgroundHandler: Handler
+        brightnessControllerFactory: BrightnessController.Factory,
+        mainExecutor: DelayableExecutor,
+        accessibilityMgr: AccessibilityManagerWrapper
     ) :
         BrightnessDialog(
-            userTracker,
-            displayTracker,
             brightnessSliderControllerFactory,
+            brightnessControllerFactory,
             mainExecutor,
-            backgroundHandler
+            accessibilityMgr
         )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index 3706859..0a1eca6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -62,9 +62,9 @@
             assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(R.id.begin_guide)
             assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
 
-            assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.endToEnd)
                 .isEqualTo(R.id.end_guide)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.horizontalBias)
                 .isEqualTo(1f)
 
             assertThat(getConstraint(R.id.privacy_container).layout.endToEnd)
@@ -95,9 +95,9 @@
             assertThat(getConstraint(R.id.date).layout.startToStart).isEqualTo(PARENT_ID)
             assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0.5f)
 
-            assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.endToEnd)
                 .isEqualTo(PARENT_ID)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.horizontalBias)
                 .isEqualTo(0.5f)
 
             assertThat(getConstraint(R.id.privacy_container).layout.endToEnd)
@@ -133,18 +133,15 @@
         changes()
 
         with(qqsConstraint) {
-            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+            assertThat(systemIconsAlphaConstraint).isEqualTo(1f)
         }
 
         with(qsConstraint) {
-            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+            assertThat(systemIconsAlphaConstraint).isEqualTo(1f)
         }
 
         with(largeScreenConstraint) {
-            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+            assertThat(systemIconsAlphaConstraint).isEqualTo(1f)
         }
     }
 
@@ -155,18 +152,15 @@
         changes()
 
         with(qqsConstraint) {
-            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(0f)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(0f)
+            assertThat(systemIconsAlphaConstraint).isEqualTo(0f)
         }
 
         with(qsConstraint) {
-            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+            assertThat(systemIconsAlphaConstraint).isEqualTo(1f)
         }
 
         with(largeScreenConstraint) {
-            assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
-            assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+            assertThat(systemIconsAlphaConstraint).isEqualTo(1f)
         }
     }
 
@@ -181,12 +175,13 @@
 
         with(qqsConstraint) {
             // In this case, the date is constrained on the end by a Barrier determined by either
-            // privacy or statusIcons
+            // privacy or clickableIcons
             assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.barrier)
-            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd).isEqualTo(R.id.date)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.startToEnd)
+                .isEqualTo(R.id.date)
             assertThat(getConstraint(R.id.privacy_container).layout.startToEnd).isEqualTo(R.id.date)
             assertThat(getConstraint(R.id.barrier).layout.mReferenceIds).asList().containsExactly(
-                R.id.statusIcons,
+                R.id.shade_header_system_icons,
                 R.id.privacy_container
             )
             assertThat(getConstraint(R.id.barrier).layout.mBarrierDirection).isEqualTo(START)
@@ -272,7 +267,7 @@
             assertThat(getConstraint(R.id.center_left).layout.guideBegin).isEqualTo(offsetFromEdge)
             assertThat(getConstraint(R.id.center_right).layout.guideEnd).isEqualTo(offsetFromEdge)
             assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.center_left)
-            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.startToEnd)
                 .isEqualTo(R.id.center_right)
             assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
                 .isEqualTo(R.id.center_right)
@@ -285,9 +280,9 @@
             assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_left)
             assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_right)
 
-            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.startToEnd)
                 .isNotEqualTo(R.id.center_left)
-            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.startToEnd)
                 .isNotEqualTo(R.id.center_right)
 
             assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
@@ -311,7 +306,7 @@
             assertThat(getConstraint(R.id.center_left).layout.guideEnd).isEqualTo(offsetFromEdge)
             assertThat(getConstraint(R.id.center_right).layout.guideBegin).isEqualTo(offsetFromEdge)
             assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.center_right)
-            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.startToEnd)
                 .isEqualTo(R.id.center_left)
             assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
                 .isEqualTo(R.id.center_left)
@@ -324,9 +319,9 @@
             assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_left)
             assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_right)
 
-            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.startToEnd)
                 .isNotEqualTo(R.id.center_left)
-            assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+            assertThat(getConstraint(R.id.shade_header_system_icons).layout.startToEnd)
                 .isNotEqualTo(R.id.center_right)
 
             assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
@@ -382,7 +377,8 @@
         CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()()
 
         assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
-        assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+        val shadeHeaderConstraint = qqsConstraint.getConstraint(R.id.shade_header_system_icons)
+        assertThat(shadeHeaderConstraint.layout.constrainedWidth).isTrue()
     }
 
     @Test
@@ -390,9 +386,13 @@
         CombinedShadeHeadersConstraintManagerImpl.centerCutoutConstraints(false, 10)()
 
         assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
-        assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+        val shadeHeaderConstraint = qqsConstraint.getConstraint(R.id.shade_header_system_icons)
+        assertThat(shadeHeaderConstraint.layout.constrainedWidth).isTrue()
     }
 
+    private val ConstraintSet.systemIconsAlphaConstraint
+        get() = getConstraint(R.id.shade_header_system_icons).propertySet.alpha
+
     private operator fun ConstraintsChanges.invoke() {
         qqsConstraintsChanges?.invoke(qqsConstraint)
         qsConstraintsChanges?.invoke(qsConstraint)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
new file mode 100644
index 0000000..24d62fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.os.PowerManager
+import android.testing.AndroidTestingRunner
+import android.view.MotionEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+class LockscreenHostedDreamGestureListenerTest : SysuiTestCase() {
+    @Mock private lateinit var falsingManager: FalsingManager
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var shadeLogger: ShadeLogger
+    @Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController
+    @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private lateinit var powerRepository: FakePowerRepository
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var underTest: LockscreenHostedDreamGestureListener
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        powerRepository = FakePowerRepository()
+        keyguardRepository = FakeKeyguardRepository()
+
+        underTest =
+            LockscreenHostedDreamGestureListener(
+                falsingManager,
+                PowerInteractor(
+                    powerRepository,
+                    keyguardRepository,
+                    falsingCollector,
+                    screenOffAnimationController,
+                    statusBarStateController,
+                ),
+                statusBarStateController,
+                primaryBouncerInteractor,
+                keyguardRepository,
+                shadeLogger,
+            )
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+        whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(false)
+    }
+
+    @Test
+    fun testGestureDetector_onSingleTap_whileDreaming() =
+        testScope.runTest {
+            // GIVEN device dreaming and the dream is hosted in lockscreen
+            whenever(statusBarStateController.isDreaming).thenReturn(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            testScope.runCurrent()
+
+            // GIVEN the falsing manager does NOT think the tap is a false tap
+            whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
+
+            // WHEN there's a tap
+            underTest.onSingleTapUp(upEv)
+
+            // THEN wake up device if dreaming
+            Truth.assertThat(powerRepository.lastWakeWhy).isNotNull()
+            Truth.assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_TAP)
+        }
+
+    @Test
+    fun testGestureDetector_onSingleTap_notOnKeyguard() =
+        testScope.runTest {
+            // GIVEN device dreaming and the dream is hosted in lockscreen
+            whenever(statusBarStateController.isDreaming).thenReturn(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            testScope.runCurrent()
+
+            // GIVEN shade is open
+            whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+
+            // GIVEN the falsing manager does NOT think the tap is a false tap
+            whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
+
+            // WHEN there's a tap
+            underTest.onSingleTapUp(upEv)
+
+            // THEN the falsing manager never gets a call
+            verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
+        }
+
+    @Test
+    fun testGestureDetector_onSingleTap_bouncerShown() =
+        testScope.runTest {
+            // GIVEN device dreaming and the dream is hosted in lockscreen
+            whenever(statusBarStateController.isDreaming).thenReturn(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            testScope.runCurrent()
+
+            // GIVEN bouncer is expanded
+            whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(true)
+
+            // GIVEN the falsing manager does NOT think the tap is a false tap
+            whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
+
+            // WHEN there's a tap
+            underTest.onSingleTapUp(upEv)
+
+            // THEN the falsing manager never gets a call
+            verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
+        }
+
+    @Test
+    fun testGestureDetector_onSingleTap_falsing() =
+        testScope.runTest {
+            // GIVEN device dreaming and the dream is hosted in lockscreen
+            whenever(statusBarStateController.isDreaming).thenReturn(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            testScope.runCurrent()
+
+            // GIVEN the falsing manager thinks the tap is a false tap
+            whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(true)
+
+            // WHEN there's a tap
+            underTest.onSingleTapUp(upEv)
+
+            // THEN the device doesn't wake up
+            Truth.assertThat(powerRepository.lastWakeWhy).isNull()
+            Truth.assertThat(powerRepository.lastWakeReason).isNull()
+        }
+
+    @Test
+    fun testSingleTap_notDreaming_noFalsingCheck() =
+        testScope.runTest {
+            // GIVEN device not dreaming with lockscreen hosted dream
+            whenever(statusBarStateController.isDreaming).thenReturn(false)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+            testScope.runCurrent()
+
+            // WHEN there's a tap
+            underTest.onSingleTapUp(upEv)
+
+            // THEN the falsing manager never gets a call
+            verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
+        }
+}
+
+private val upEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index fe89a14..981e44b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -19,6 +19,7 @@
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -80,6 +81,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.common.ui.view.LongPressHandlingView;
@@ -89,25 +91,27 @@
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository;
+import com.android.systemui.keyguard.KeyguardViewConfigurator;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.ui.view.KeyguardRootView;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.model.SysUiState;
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter;
@@ -116,6 +120,7 @@
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.data.repository.ShadeRepository;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.shade.transition.ShadeTransitionController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -167,6 +172,7 @@
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.time.SystemClock;
 import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -216,7 +222,7 @@
     @Mock protected StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
     @Mock protected KeyguardStateController mKeyguardStateController;
     @Mock protected DozeLog mDozeLog;
-    @Mock protected ShadeLogger mShadeLog;
+    private final ShadeLogger mShadeLog = new ShadeLogger(logcatLogBuffer());
     @Mock protected CommandQueue mCommandQueue;
     @Mock protected VibratorHelper mVibratorHelper;
     @Mock protected LatencyTracker mLatencyTracker;
@@ -253,6 +259,7 @@
     @Mock protected UserManager mUserManager;
     @Mock protected UiEventLogger mUiEventLogger;
     @Mock protected LockIconViewController mLockIconViewController;
+    @Mock protected KeyguardViewConfigurator mKeyguardViewConfigurator;
     @Mock protected KeyguardMediaController mKeyguardMediaController;
     @Mock protected NavigationModeController mNavigationModeController;
     @Mock protected NavigationBarController mNavigationBarController;
@@ -293,15 +300,17 @@
     @Mock protected LockscreenToOccludedTransitionViewModel
             mLockscreenToOccludedTransitionViewModel;
     @Mock protected GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
-
+    @Mock protected GoneToDreamingLockscreenHostedTransitionViewModel
+            mGoneToDreamingLockscreenHostedTransitionViewModel;
+    @Mock protected PrimaryBouncerToGoneTransitionViewModel
+            mPrimaryBouncerToGoneTransitionViewModel;
     @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
-    @Mock protected MultiShadeInteractor mMultiShadeInteractor;
     @Mock protected KeyguardLongPressViewModel mKeyuardLongPressViewModel;
     @Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock protected MotionEvent mDownMotionEvent;
     @Mock protected CoroutineDispatcher mMainDispatcher;
     @Mock protected KeyguardSliceViewController mKeyguardSliceViewController;
-    @Mock protected KeyguardLogger mKeyguardLogger;
+    private final KeyguardLogger mKeyguardLogger = new KeyguardLogger(logcatLogBuffer());
     @Mock protected KeyguardStatusView mKeyguardStatusView;
     @Captor
     protected ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
@@ -309,10 +318,14 @@
     @Mock protected ActivityStarter mActivityStarter;
     @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     @Mock protected ShadeRepository mShadeRepository;
+    @Mock private ShadeInteractor mShadeInteractor;
+    @Mock private JavaAdapter mJavaAdapter;
     @Mock private CastController mCastController;
+    @Mock private KeyguardRootView mKeyguardRootView;
 
     protected final int mMaxUdfpsBurnInOffsetY = 5;
     protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
+    protected FakeKeyguardRepository mFakeKeyguardRepository;
     protected KeyguardInteractor mKeyguardInteractor;
     protected NotificationPanelViewController.TouchHandler mTouchHandler;
     protected ConfigurationController mConfigurationController;
@@ -340,10 +353,12 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mMainDispatcher = getMainDispatcher();
-        mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(
-                new FakeKeyguardRepository());
-        mKeyguardInteractor = new KeyguardInteractor(new FakeKeyguardRepository(), mCommandQueue,
-                mFeatureFlags, new FakeKeyguardBouncerRepository());
+        KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps =
+                KeyguardInteractorFactory.create();
+        mFakeKeyguardRepository = keyguardInteractorDeps.getRepository();
+        mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(mFakeKeyguardRepository);
+        mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor();
+
         SystemClock systemClock = new FakeSystemClock();
         mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
                 mInteractionJankMonitor, mShadeExpansionStateManager);
@@ -361,7 +376,9 @@
                 mScreenOffAnimationController,
                 mKeyguardLogger,
                 mFeatureFlags,
-                mInteractionJankMonitor));
+                mInteractionJankMonitor,
+                mKeyguardInteractor,
+                mDumpManager));
 
         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
         when(mHeadsUpCallback.getContext()).thenReturn(mContext);
@@ -407,6 +424,10 @@
         when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
         when(mView.findViewById(R.id.keyguard_status_view))
                 .thenReturn(mock(KeyguardStatusView.class));
+        View rootView = mock(View.class);
+        when(mView.getRootView()).thenReturn(rootView);
+        when(rootView.findViewById(R.id.keyguard_status_view))
+                .thenReturn(mock(KeyguardStatusView.class));
         mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null);
         mNotificationContainerParent.addView(keyguardStatusView);
         mNotificationContainerParent.onFinishInflate();
@@ -467,6 +488,20 @@
         when(mGoneToDreamingTransitionViewModel.lockscreenTranslationY(anyInt()))
                 .thenReturn(emptyFlow());
 
+        // Gone->Dreaming lockscreen hosted
+        when(mKeyguardTransitionInteractor.getGoneToDreamingLockscreenHostedTransition())
+                .thenReturn(emptyFlow());
+        when(mGoneToDreamingLockscreenHostedTransitionViewModel.getLockscreenAlpha())
+                .thenReturn(emptyFlow());
+
+        // Dreaming lockscreen hosted->Lockscreen
+        when(mKeyguardTransitionInteractor.getDreamingLockscreenHostedToLockscreenTransition())
+                .thenReturn(emptyFlow());
+
+        // Lockscreen->Dreaming lockscreen hosted
+        when(mKeyguardTransitionInteractor.getLockscreenToDreamingLockscreenHostedTransition())
+                .thenReturn(emptyFlow());
+
         // Lockscreen->Occluded
         when(mKeyguardTransitionInteractor.getLockscreenToOccludedTransition())
                 .thenReturn(emptyFlow());
@@ -475,6 +510,10 @@
         when(mLockscreenToOccludedTransitionViewModel.lockscreenTranslationY(anyInt()))
                 .thenReturn(emptyFlow());
 
+        // Primary Bouncer->Gone
+        when(mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha())
+                .thenReturn(emptyFlow());
+
         NotificationWakeUpCoordinator coordinator =
                 new NotificationWakeUpCoordinator(
                         mDumpManager,
@@ -484,7 +523,7 @@
                         mKeyguardBypassController,
                         mDozeParameters,
                         mScreenOffAnimationController,
-                        mock(NotificationWakeUpCoordinatorLogger.class));
+                        new NotificationWakeUpCoordinatorLogger(logcatLogBuffer()));
         mConfigurationController = new ConfigurationControllerImpl(mContext);
         PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
                 mContext,
@@ -594,7 +633,6 @@
                 mNotificationStackSizeCalculator,
                 mUnlockedScreenOffAnimationController,
                 mShadeTransitionController,
-                mInteractionJankMonitor,
                 systemClock,
                 mKeyguardBottomAreaViewModel,
                 mKeyguardBottomAreaInteractor,
@@ -603,15 +641,18 @@
                 mOccludedToLockscreenTransitionViewModel,
                 mLockscreenToDreamingTransitionViewModel,
                 mGoneToDreamingTransitionViewModel,
+                mGoneToDreamingLockscreenHostedTransitionViewModel,
                 mLockscreenToOccludedTransitionViewModel,
+                mPrimaryBouncerToGoneTransitionViewModel,
                 mMainDispatcher,
                 mKeyguardTransitionInteractor,
-                () -> mMultiShadeInteractor,
                 mDumpManager,
                 mKeyuardLongPressViewModel,
                 mKeyguardInteractor,
                 mActivityStarter,
-                mKeyguardFaceAuthInteractor);
+                mKeyguardViewConfigurator,
+                mKeyguardFaceAuthInteractor,
+                mKeyguardRootView);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
                 null,
@@ -620,7 +661,7 @@
                 mHeadsUpManager);
         mNotificationPanelViewController.setTrackingStartedListener(() -> {});
         mNotificationPanelViewController.setOpenCloseListener(
-                new NotificationPanelViewController.OpenCloseListener() {
+                new OpenCloseListener() {
                     @Override
                     public void onClosingFinished() {}
 
@@ -682,6 +723,8 @@
                 mDumpManager,
                 mKeyguardFaceAuthInteractor,
                 mShadeRepository,
+                mShadeInteractor,
+                mJavaAdapter,
                 mCastController
         );
     }
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 987e09c..eb4ae1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -34,6 +34,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
@@ -58,6 +59,9 @@
 import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.R;
+import com.android.systemui.keyguard.shared.model.WakeSleepReason;
+import com.android.systemui.keyguard.shared.model.WakefulnessModel;
+import com.android.systemui.keyguard.shared.model.WakefulnessState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
@@ -741,6 +745,47 @@
     }
 
     @Test
+    public void onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_showNSSL() {
+        // GIVEN
+        mStatusBarStateController.setState(KEYGUARD);
+        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(false);
+        when(mQsController.getFullyExpanded()).thenReturn(true);
+        when(mQsController.getExpanded()).thenReturn(true);
+
+        // WHEN
+        int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
+        mNotificationPanelViewController.setExpandedHeight(transitionDistance);
+
+        // THEN
+        // We are interested in the last value of the stack alpha.
+        ArgumentCaptor<Float> alphaCaptor = ArgumentCaptor.forClass(Float.class);
+        verify(mNotificationStackScrollLayoutController, atLeastOnce())
+                .setAlpha(alphaCaptor.capture());
+        assertThat(alphaCaptor.getValue()).isEqualTo(1.0f);
+    }
+
+    @Test
+    public void onQsSetExpansionHeightCalled_qsFullyExpandedOnKeyguard_hideNSSL() {
+        // GIVEN
+        mStatusBarStateController.setState(KEYGUARD);
+        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(false);
+        when(mQsController.getFullyExpanded()).thenReturn(false);
+        when(mQsController.getExpanded()).thenReturn(true);
+
+        // WHEN
+        int transitionDistance = mNotificationPanelViewController
+                .getMaxPanelTransitionDistance() / 2;
+        mNotificationPanelViewController.setExpandedHeight(transitionDistance);
+
+        // THEN
+        // We are interested in the last value of the stack alpha.
+        ArgumentCaptor<Float> alphaCaptor = ArgumentCaptor.forClass(Float.class);
+        verify(mNotificationStackScrollLayoutController, atLeastOnce())
+                .setAlpha(alphaCaptor.capture());
+        assertThat(alphaCaptor.getValue()).isEqualTo(0.0f);
+    }
+
+    @Test
     public void testSwitchesToBigClockInSplitShadeOnAodAnimateDisabled() {
         when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false);
         mStatusBarStateController.setState(KEYGUARD);
@@ -1180,4 +1225,43 @@
         when(mUnlockedScreenOffAnimationController.isAnimationPlaying()).thenReturn(true);
         assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
     }
+
+    @Test
+    public void getFalsingThreshold_deviceNotInteractive_isQsThreshold() {
+        mFakeKeyguardRepository.setWakefulnessModel(
+                new WakefulnessModel(
+                        WakefulnessState.ASLEEP,
+                        /* lastWakeReason= */ WakeSleepReason.TAP,
+                        /* lastSleepReason= */ WakeSleepReason.POWER_BUTTON)
+        );
+        when(mQsController.getFalsingThreshold()).thenReturn(14);
+
+        assertThat(mNotificationPanelViewController.getFalsingThreshold()).isEqualTo(14);
+    }
+
+    @Test
+    public void getFalsingThreshold_lastWakeNotDueToTouch_isQsThreshold() {
+        mFakeKeyguardRepository.setWakefulnessModel(
+                new WakefulnessModel(
+                        WakefulnessState.AWAKE,
+                        /* lastWakeReason= */ WakeSleepReason.POWER_BUTTON,
+                        /* lastSleepReason= */ WakeSleepReason.POWER_BUTTON)
+        );
+        when(mQsController.getFalsingThreshold()).thenReturn(14);
+
+        assertThat(mNotificationPanelViewController.getFalsingThreshold()).isEqualTo(14);
+    }
+
+    @Test
+    public void getFalsingThreshold_lastWakeDueToTouch_greaterThanQsThreshold() {
+        mFakeKeyguardRepository.setWakefulnessModel(
+                new WakefulnessModel(
+                        WakefulnessState.AWAKE,
+                        /* lastWakeReason= */ WakeSleepReason.TAP,
+                        /* lastSleepReason= */ WakeSleepReason.POWER_BUTTON)
+        );
+        when(mQsController.getFalsingThreshold()).thenReturn(14);
+
+        assertThat(mNotificationPanelViewController.getFalsingThreshold()).isGreaterThan(14);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index 0c046e9..c68095c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -16,17 +16,23 @@
 
 package com.android.systemui.shade
 
+import android.os.VibrationEffect
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.HapticFeedbackConstants
 import android.view.View
 import android.view.ViewStub
 import androidx.test.filters.SmallTest
 import com.android.internal.util.CollectionUtils
 import com.android.keyguard.KeyguardClockSwitch.LARGE
 import com.android.systemui.R
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.statusbar.StatusBarState.KEYGUARD
 import com.android.systemui.statusbar.StatusBarState.SHADE
 import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
@@ -55,6 +61,9 @@
 
     override fun getMainDispatcher() = Dispatchers.Main.immediate
 
+    private val ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT =
+        VibrationEffect.get(VibrationEffect.EFFECT_STRENGTH_MEDIUM, false)
+
     @Test
     fun testDisableUserSwitcherAfterEnabling_returnsViewStubToTheViewHierarchy() = runTest {
         launch(Dispatchers.Main.immediate) { givenViewAttached() }
@@ -148,6 +157,43 @@
     }
 
     @Test
+    fun doubleTapRequired_onKeyguard_oneWayHapticsDisabled_usesOldVibrate() = runTest {
+        launch(Dispatchers.Main.immediate) {
+            whenever(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(false)
+            val listener = getFalsingTapListener()
+            mStatusBarStateController.setState(KEYGUARD)
+
+            listener.onAdditionalTapRequired()
+            val packageName = mView.context.packageName
+            verify(mKeyguardIndicationController).showTransientIndication(anyInt())
+            verify(mVibratorHelper)
+                .vibrate(
+                    any(),
+                    eq(packageName),
+                    eq(ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT),
+                    eq("falsing-additional-tap-required"),
+                    eq(VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES)
+                )
+        }
+        advanceUntilIdle()
+    }
+
+    @Test
+    fun doubleTapRequired_onKeyguard_oneWayHapticsEnabled_usesPerformHapticFeedback() = runTest {
+        launch(Dispatchers.Main.immediate) {
+            whenever(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true)
+            val listener = getFalsingTapListener()
+            mStatusBarStateController.setState(KEYGUARD)
+
+            listener.onAdditionalTapRequired()
+            verify(mKeyguardIndicationController).showTransientIndication(anyInt())
+            verify(mVibratorHelper)
+                .performHapticFeedback(eq(mView), eq(HapticFeedbackConstants.REJECT))
+        }
+        advanceUntilIdle()
+    }
+
+    @Test
     fun testDoubleTapRequired_ShadeLocked() = runTest {
         launch(Dispatchers.Main.immediate) {
             val listener = getFalsingTapListener()
@@ -161,6 +207,45 @@
     }
 
     @Test
+    fun doubleTapRequired_shadeLocked_oneWayHapticsDisabled_usesOldVibrate() = runTest {
+        launch(Dispatchers.Main.immediate) {
+            whenever(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(false)
+            val listener = getFalsingTapListener()
+            val packageName = mView.context.packageName
+            mStatusBarStateController.setState(SHADE_LOCKED)
+
+            listener.onAdditionalTapRequired()
+            verify(mVibratorHelper)
+                .vibrate(
+                    any(),
+                    eq(packageName),
+                    eq(ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT),
+                    eq("falsing-additional-tap-required"),
+                    eq(VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES)
+                )
+
+            verify(mTapAgainViewController).show()
+        }
+        advanceUntilIdle()
+    }
+
+    @Test
+    fun doubleTapRequired_shadeLocked_oneWayHapticsEnabled_usesPerformHapticFeedback() = runTest {
+        launch(Dispatchers.Main.immediate) {
+            whenever(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true)
+            val listener = getFalsingTapListener()
+            mStatusBarStateController.setState(SHADE_LOCKED)
+
+            listener.onAdditionalTapRequired()
+            verify(mVibratorHelper)
+                .performHapticFeedback(eq(mView), eq(HapticFeedbackConstants.REJECT))
+
+            verify(mTapAgainViewController).show()
+        }
+        advanceUntilIdle()
+    }
+
+    @Test
     fun testOnAttachRefreshStatusBarState() = runTest {
         launch(Dispatchers.Main.immediate) {
             mStatusBarStateController.setState(KEYGUARD)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
deleted file mode 100644
index 168cbb7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
+++ /dev/null
@@ -1,512 +0,0 @@
-package com.android.systemui.shade
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowInsets
-import android.view.WindowManagerPolicyConstants
-import androidx.annotation.IdRes
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.fragments.FragmentHostManager
-import com.android.systemui.fragments.FragmentService
-import com.android.systemui.navigationbar.NavigationModeController
-import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
-import com.android.systemui.plugins.qs.QS
-import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import java.util.function.Consumer
-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
-import org.mockito.Mockito.RETURNS_DEEP_STUBS
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class NotificationQSContainerControllerTest : SysuiTestCase() {
-
-    companion object {
-        const val STABLE_INSET_BOTTOM = 100
-        const val CUTOUT_HEIGHT = 50
-        const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
-        const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
-        const val NOTIFICATIONS_MARGIN = 50
-        const val SCRIM_MARGIN = 10
-        const val FOOTER_ACTIONS_INSET = 2
-        const val FOOTER_ACTIONS_PADDING = 2
-        const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
-        const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
-    }
-
-    @Mock
-    private lateinit var navigationModeController: NavigationModeController
-    @Mock
-    private lateinit var overviewProxyService: OverviewProxyService
-    @Mock
-    private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer
-    @Mock
-    private lateinit var mShadeHeaderController: ShadeHeaderController
-    @Mock
-    private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
-    @Mock
-    private lateinit var fragmentService: FragmentService
-    @Mock
-    private lateinit var fragmentHostManager: FragmentHostManager
-    @Captor
-    lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
-    @Captor
-    lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
-    @Captor
-    lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
-    @Captor
-    lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
-    @Captor
-    lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
-
-    private lateinit var controller: NotificationsQSContainerController
-    private lateinit var navigationModeCallback: ModeChangedListener
-    private lateinit var taskbarVisibilityCallback: OverviewProxyListener
-    private lateinit var windowInsetsCallback: Consumer<WindowInsets>
-    private lateinit var delayableExecutor: FakeExecutor
-    private lateinit var fakeSystemClock: FakeSystemClock
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        mContext.ensureTestableResources()
-        whenever(notificationsQSContainer.context).thenReturn(mContext)
-        whenever(notificationsQSContainer.resources).thenReturn(mContext.resources)
-        whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
-        fakeSystemClock = FakeSystemClock()
-        delayableExecutor = FakeExecutor(fakeSystemClock)
-
-        controller = NotificationsQSContainerController(
-                notificationsQSContainer,
-                navigationModeController,
-                overviewProxyService,
-                mShadeHeaderController,
-                shadeExpansionStateManager,
-                fragmentService,
-                delayableExecutor
-        )
-
-        overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
-        overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
-        overrideResource(R.bool.config_use_split_notification_shade, false)
-        overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
-        overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
-        whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
-                .thenReturn(GESTURES_NAVIGATION)
-        doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
-        doNothing().`when`(notificationsQSContainer)
-                .setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
-        doNothing().`when`(notificationsQSContainer).applyConstraints(constraintSetCaptor.capture())
-        doNothing().`when`(notificationsQSContainer)
-            .addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
-        controller.init()
-        attachStateListenerCaptor.value.onViewAttachedToWindow(notificationsQSContainer)
-
-        navigationModeCallback = navigationModeCaptor.value
-        taskbarVisibilityCallback = taskbarVisibilityCaptor.value
-        windowInsetsCallback = windowInsetsCallbackCaptor.value
-    }
-
-    @Test
-    fun testTaskbarVisibleInSplitShade() {
-        enableSplitShade()
-
-        given(taskbarVisible = true,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
-                expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
-                expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-
-        given(taskbarVisible = true,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = STABLE_INSET_BOTTOM,
-                expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
-                expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-    }
-
-    @Test
-    fun testTaskbarNotVisibleInSplitShade() {
-        // when taskbar is not visible, it means we're on the home screen
-        enableSplitShade()
-
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0,
-                expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
-                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
-                expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-    }
-
-    @Test
-    fun testTaskbarNotVisibleInSplitShadeWithCutout() {
-        enableSplitShade()
-
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withCutout())
-        then(expectedContainerPadding = CUTOUT_HEIGHT,
-            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = windowInsets().withCutout().withStableBottom())
-        then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
-                expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-    }
-
-    @Test
-    fun testTaskbarVisibleInSinglePaneShade() {
-        disableSplitShade()
-
-        given(taskbarVisible = true,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0,
-                expectedQsPadding = STABLE_INSET_BOTTOM)
-
-        given(taskbarVisible = true,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = STABLE_INSET_BOTTOM,
-                expectedQsPadding = STABLE_INSET_BOTTOM)
-    }
-
-    @Test
-    fun testTaskbarNotVisibleInSinglePaneShade() {
-        disableSplitShade()
-
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = emptyInsets())
-        then(expectedContainerPadding = 0)
-
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withCutout().withStableBottom())
-        then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
-
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
-                expectedQsPadding = STABLE_INSET_BOTTOM)
-    }
-
-    @Test
-    fun testDetailShowingInSinglePaneShade() {
-        disableSplitShade()
-        controller.setDetailShowing(true)
-
-        // always sets spacings to 0
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = 0)
-
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = emptyInsets())
-        then(expectedContainerPadding = 0,
-                expectedNotificationsMargin = 0)
-    }
-
-    @Test
-    fun testDetailShowingInSplitShade() {
-        enableSplitShade()
-        controller.setDetailShowing(true)
-
-        given(taskbarVisible = false,
-                navigationMode = GESTURES_NAVIGATION,
-                insets = windowInsets().withStableBottom())
-        then(expectedContainerPadding = 0)
-
-        // should not influence spacing
-        given(taskbarVisible = false,
-                navigationMode = BUTTONS_NAVIGATION,
-                insets = emptyInsets())
-        then(expectedContainerPadding = 0)
-    }
-
-    @Test
-    fun testNotificationsMarginBottomIsUpdated() {
-        Mockito.clearInvocations(notificationsQSContainer)
-        enableSplitShade()
-        verify(notificationsQSContainer).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
-
-        overrideResource(R.dimen.notification_panel_margin_bottom, 100)
-        disableSplitShade()
-        verify(notificationsQSContainer).setNotificationsMarginBottom(100)
-    }
-
-    @Test
-    fun testSplitShadeLayout_isAlignedToGuideline() {
-        enableSplitShade()
-        controller.updateResources()
-        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
-                .isEqualTo(R.id.qs_edge_guideline)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
-                .isEqualTo(R.id.qs_edge_guideline)
-    }
-
-    @Test
-    fun testSinglePaneLayout_childrenHaveEqualMargins() {
-        disableSplitShade()
-        controller.updateResources()
-        val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
-        val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
-        val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin
-        val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin
-        assertThat(qsStartMargin == qsEndMargin &&
-                notifStartMargin == notifEndMargin &&
-                qsStartMargin == notifStartMargin
-        ).isTrue()
-    }
-
-    @Test
-    fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
-        enableSplitShade()
-        controller.updateResources()
-        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin)
-                .isEqualTo(0)
-    }
-
-    @Test
-    fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
-        enableSplitShade()
-        controller.updateResources()
-        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
-        assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
-    }
-
-    @Test
-    fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
-        setLargeScreen()
-        val largeScreenHeaderHeight = 100
-        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
-
-        controller.updateResources()
-
-        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
-                .isEqualTo(largeScreenHeaderHeight)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
-                .isEqualTo(largeScreenHeaderHeight)
-    }
-
-    @Test
-    fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
-        setSmallScreen()
-        controller.updateResources()
-        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
-                .isEqualTo(0)
-    }
-
-    @Test
-    fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
-        disableSplitShade()
-        controller.updateResources()
-        val notificationPanelMarginHorizontal = context.resources
-                .getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
-        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
-                .isEqualTo(notificationPanelMarginHorizontal)
-        assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
-                .isEqualTo(notificationPanelMarginHorizontal)
-    }
-
-    @Test
-    fun testSinglePaneShadeLayout_isAlignedToParent() {
-        disableSplitShade()
-        controller.updateResources()
-        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
-                .isEqualTo(ConstraintSet.PARENT_ID)
-        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
-                .isEqualTo(ConstraintSet.PARENT_ID)
-    }
-
-    @Test
-    fun testAllChildrenOfNotificationContainer_haveIds() {
-        // set dimen to 0 to avoid triggering updating bottom spacing
-        overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
-        val container = NotificationsQuickSettingsContainer(context, null)
-        container.removeAllViews()
-        container.addView(newViewWithId(1))
-        container.addView(newViewWithId(View.NO_ID))
-        val controller = NotificationsQSContainerController(
-                container,
-                navigationModeController,
-                overviewProxyService,
-                mShadeHeaderController,
-                shadeExpansionStateManager,
-                fragmentService,
-                delayableExecutor
-        )
-        controller.updateConstraints()
-
-        assertThat(container.getChildAt(0).id).isEqualTo(1)
-        assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID)
-    }
-
-    @Test
-    fun testWindowInsetDebounce() {
-        disableSplitShade()
-
-        given(taskbarVisible = false,
-            navigationMode = GESTURES_NAVIGATION,
-            insets = emptyInsets(),
-            applyImmediately = false)
-        fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
-        windowInsetsCallback.accept(windowInsets().withStableBottom())
-
-        delayableExecutor.advanceClockToLast()
-        delayableExecutor.runAllReady()
-
-        verify(notificationsQSContainer, never()).setQSContainerPaddingBottom(0)
-        verify(notificationsQSContainer).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
-    }
-
-    @Test
-    fun testStartCustomizingWithDuration() {
-        controller.setCustomizerShowing(true, 100L)
-        verify(mShadeHeaderController).startCustomizingAnimation(true, 100L)
-    }
-
-    @Test
-    fun testEndCustomizingWithDuration() {
-        controller.setCustomizerShowing(true, 0L) // Only tracks changes
-        reset(mShadeHeaderController)
-
-        controller.setCustomizerShowing(false, 100L)
-        verify(mShadeHeaderController).startCustomizingAnimation(false, 100L)
-    }
-
-    @Test
-    fun testTagListenerAdded() {
-        verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(notificationsQSContainer))
-    }
-
-    @Test
-    fun testTagListenerRemoved() {
-        attachStateListenerCaptor.value.onViewDetachedFromWindow(notificationsQSContainer)
-        verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(notificationsQSContainer))
-    }
-
-    private fun disableSplitShade() {
-        setSplitShadeEnabled(false)
-    }
-
-    private fun enableSplitShade() {
-        setSplitShadeEnabled(true)
-    }
-
-    private fun setSplitShadeEnabled(enabled: Boolean) {
-        overrideResource(R.bool.config_use_split_notification_shade, enabled)
-        controller.updateResources()
-    }
-
-    private fun setSmallScreen() {
-        setLargeScreenEnabled(false)
-    }
-
-    private fun setLargeScreen() {
-        setLargeScreenEnabled(true)
-    }
-
-    private fun setLargeScreenEnabled(enabled: Boolean) {
-        overrideResource(R.bool.config_use_large_screen_shade_header, enabled)
-    }
-
-    private fun given(
-        taskbarVisible: Boolean,
-        navigationMode: Int,
-        insets: WindowInsets,
-        applyImmediately: Boolean = true
-    ) {
-        Mockito.clearInvocations(notificationsQSContainer)
-        taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
-        navigationModeCallback.onNavigationModeChanged(navigationMode)
-        windowInsetsCallback.accept(insets)
-        if (applyImmediately) {
-            delayableExecutor.advanceClockToLast()
-            delayableExecutor.runAllReady()
-        }
-    }
-
-    fun then(
-        expectedContainerPadding: Int,
-        expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
-        expectedQsPadding: Int = 0
-    ) {
-        verify(notificationsQSContainer)
-                .setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
-        verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin)
-        verify(notificationsQSContainer)
-                    .setQSContainerPaddingBottom(expectedQsPadding)
-        Mockito.clearInvocations(notificationsQSContainer)
-    }
-
-    private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
-
-    private fun emptyInsets() = mock(WindowInsets::class.java)
-
-    private fun WindowInsets.withCutout(): WindowInsets {
-        whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
-        return this
-    }
-
-    private fun WindowInsets.withStableBottom(): WindowInsets {
-        whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
-        return this
-    }
-
-    private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
-        return constraintSetCaptor.value.getConstraint(id).layout
-    }
-
-    private fun newViewWithId(id: Int): View {
-        val view = View(mContext)
-        view.id = id
-        val layoutParams = ConstraintLayout.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
-        // required as cloning ConstraintSet fails if view doesn't have layout params
-        view.layoutParams = layoutParams
-        return view
-    }
-}
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 526dc8d..cde6ac0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -126,7 +126,7 @@
                     }
             };
         mNotificationShadeWindowController.setScrimsVisibilityListener((visibility) -> {});
-        mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
+        mNotificationShadeWindowController.setWindowRootView(mNotificationShadeWindowView);
 
         mNotificationShadeWindowController.attach();
         verify(mWindowManager).addView(eq(mNotificationShadeWindowView), any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 3995e71..1edeeff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -21,32 +21,36 @@
 import android.view.MotionEvent
 import android.view.ViewGroup
 import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardMessageAreaController
 import com.android.keyguard.KeyguardSecurityContainerController
 import com.android.keyguard.LockIconViewController
 import com.android.keyguard.dagger.KeyguardBouncerComponent
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
+import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
+import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollectorFake
-import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.dock.DockManager
+import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.data.repository.MultiShadeRepository
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
-import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor
+import com.android.systemui.log.BouncerLogger
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.NotificationInsetsController
 import com.android.systemui.statusbar.NotificationShadeDepthController
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -54,9 +58,11 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.test.TestScope
@@ -72,7 +78,6 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
-import java.util.Optional
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -83,12 +88,14 @@
     @Mock private lateinit var view: NotificationShadeWindowView
     @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
     @Mock private lateinit var centralSurfaces: CentralSurfaces
+    @Mock private lateinit var backActionInteractor: BackActionInteractor
+    @Mock private lateinit var powerInteractor: PowerInteractor
     @Mock private lateinit var dockManager: DockManager
     @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
     @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
     @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
-    @Mock private lateinit var shadeController: ShadeController
+    @Mock private lateinit var shadeLogger: ShadeLogger
     @Mock private lateinit var ambientState: AmbientState
     @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
     @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@@ -99,15 +106,19 @@
     @Mock private lateinit var lockIconViewController: LockIconViewController
     @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
     @Mock private lateinit var pulsingGestureListener: PulsingGestureListener
+    @Mock
+    private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
     @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
     @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
     @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
-    @Mock private lateinit var unfoldTransitionProgressProvider:
-            Optional<UnfoldTransitionProgressProvider>
+    @Mock
+    private lateinit var unfoldTransitionProgressProvider:
+        Optional<UnfoldTransitionProgressProvider>
     @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock
     lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
+    private val notificationExpansionRepository = NotificationExpansionRepository()
 
     private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
     private lateinit var interactionEventHandler: InteractionEventHandler
@@ -132,64 +143,52 @@
         val featureFlags = FakeFeatureFlags()
         featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
         featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
-        featureFlags.set(Flags.DUAL_SHADE, false)
         featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
+        featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+        featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
 
-        val inputProxy = MultiShadeInputProxy()
         testScope = TestScope()
-        val multiShadeInteractor =
-            MultiShadeInteractor(
-                applicationScope = testScope.backgroundScope,
-                repository =
-                    MultiShadeRepository(
-                        applicationContext = context,
-                        inputProxy = inputProxy,
-                    ),
-                inputProxy = inputProxy,
-            )
         underTest =
             NotificationShadeWindowViewController(
-                lockscreenShadeTransitionController,
-                FalsingCollectorFake(),
-                sysuiStatusBarStateController,
-                dockManager,
-                notificationShadeDepthController,
-                view,
-                notificationPanelViewController,
-                ShadeExpansionStateManager(),
-                stackScrollLayoutController,
-                statusBarKeyguardViewManager,
-                statusBarWindowStateController,
-                lockIconViewController,
-                centralSurfaces,
-                notificationShadeWindowController,
-                unfoldTransitionProgressProvider,
-                keyguardUnlockAnimationController,
-                notificationInsetsController,
-                ambientState,
-                pulsingGestureListener,
-                keyguardBouncerViewModel,
-                keyguardBouncerComponentFactory,
-                keyguardTransitionInteractor,
-                primaryBouncerToGoneTransitionViewModel,
-                featureFlags,
-                { multiShadeInteractor },
-                FakeSystemClock(),
-                {
-                    MultiShadeMotionEventInteractor(
-                        applicationContext = context,
-                        applicationScope = testScope.backgroundScope,
-                        multiShadeInteractor = multiShadeInteractor,
-                        featureFlags = featureFlags,
-                        keyguardTransitionInteractor =
-                            KeyguardTransitionInteractor(
-                                repository = FakeKeyguardTransitionRepository(),
-                                scope = testScope.backgroundScope
-                            ),
-                        falsingManager = FalsingManagerFake(),
-                        shadeController = shadeController,
-                    )
-                },
+                    lockscreenShadeTransitionController,
+                    FalsingCollectorFake(),
+                    sysuiStatusBarStateController,
+                    dockManager,
+                    notificationShadeDepthController,
+                    view,
+                    notificationPanelViewController,
+                    ShadeExpansionStateManager(),
+                    stackScrollLayoutController,
+                    statusBarKeyguardViewManager,
+                    statusBarWindowStateController,
+                    lockIconViewController,
+                    centralSurfaces,
+                    backActionInteractor,
+                    powerInteractor,
+                    notificationShadeWindowController,
+                    unfoldTransitionProgressProvider,
+                    keyguardUnlockAnimationController,
+                    notificationInsetsController,
+                    ambientState,
+                    shadeLogger,
+                    pulsingGestureListener,
+                    mLockscreenHostedDreamGestureListener,
+                    keyguardBouncerViewModel,
+                    keyguardBouncerComponentFactory,
+                    mock(KeyguardMessageAreaController.Factory::class.java),
+                    keyguardTransitionInteractor,
+                    primaryBouncerToGoneTransitionViewModel,
+                    notificationExpansionRepository,
+                    featureFlags,
+                    FakeSystemClock(),
+                    BouncerMessageInteractor(
+                        FakeBouncerMessageRepository(),
+                        mock(BouncerMessageFactory::class.java),
+                        FakeUserRepository(),
+                        CountDownTimerUtil(),
+                        featureFlags
+                    ),
+                    BouncerLogger(logcatLogBuffer("BouncerLog"))
             )
         underTest.setupExpandedStatusBar()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index fe95742..829184c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -21,25 +21,28 @@
 import android.view.MotionEvent
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardMessageAreaController
 import com.android.keyguard.KeyguardSecurityContainerController
 import com.android.keyguard.LockIconViewController
 import com.android.keyguard.dagger.KeyguardBouncerComponent
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.back.domain.interactor.BackActionInteractor
+import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
+import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil
+import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.classifier.FalsingCollectorFake
-import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.dock.DockManager
+import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
-import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
-import com.android.systemui.multishade.data.repository.MultiShadeRepository
-import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
-import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor
+import com.android.systemui.log.BouncerLogger
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.DragDownHelper
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -47,6 +50,7 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -54,13 +58,13 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
@@ -70,11 +74,11 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
@@ -84,6 +88,8 @@
     @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
     @Mock private lateinit var shadeController: ShadeController
     @Mock private lateinit var centralSurfaces: CentralSurfaces
+    @Mock private lateinit var backActionInteractor: BackActionInteractor
+    @Mock private lateinit var powerInteractor: PowerInteractor
     @Mock private lateinit var dockManager: DockManager
     @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
     @Mock private lateinit var notificationStackScrollLayout: NotificationStackScrollLayout
@@ -99,7 +105,10 @@
     @Mock private lateinit var lockIconViewController: LockIconViewController
     @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
     @Mock private lateinit var ambientState: AmbientState
+    @Mock private lateinit var shadeLogger: ShadeLogger
     @Mock private lateinit var pulsingGestureListener: PulsingGestureListener
+    @Mock
+    private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
     @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
     @Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
     @Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@@ -145,20 +154,10 @@
         val featureFlags = FakeFeatureFlags()
         featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
         featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
-        featureFlags.set(Flags.DUAL_SHADE, false)
         featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
-        val inputProxy = MultiShadeInputProxy()
+        featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+        featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
         testScope = TestScope()
-        val multiShadeInteractor =
-            MultiShadeInteractor(
-                applicationScope = testScope.backgroundScope,
-                repository =
-                    MultiShadeRepository(
-                        applicationContext = context,
-                        inputProxy = inputProxy,
-                    ),
-                inputProxy = inputProxy,
-            )
         controller =
             NotificationShadeWindowViewController(
                 lockscreenShadeTransitionController,
@@ -174,34 +173,32 @@
                 statusBarWindowStateController,
                 lockIconViewController,
                 centralSurfaces,
+                backActionInteractor,
+                powerInteractor,
                 notificationShadeWindowController,
                 unfoldTransitionProgressProvider,
                 keyguardUnlockAnimationController,
                 notificationInsetsController,
                 ambientState,
+                shadeLogger,
                 pulsingGestureListener,
+                mLockscreenHostedDreamGestureListener,
                 keyguardBouncerViewModel,
                 keyguardBouncerComponentFactory,
+                Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
+                NotificationExpansionRepository(),
                 featureFlags,
-                { multiShadeInteractor },
                 FakeSystemClock(),
-                {
-                    MultiShadeMotionEventInteractor(
-                        applicationContext = context,
-                        applicationScope = testScope.backgroundScope,
-                        multiShadeInteractor = multiShadeInteractor,
-                        featureFlags = featureFlags,
-                        keyguardTransitionInteractor =
-                            KeyguardTransitionInteractor(
-                                repository = FakeKeyguardTransitionRepository(),
-                                scope = testScope.backgroundScope
-                            ),
-                        falsingManager = FalsingManagerFake(),
-                        shadeController = shadeController,
-                    )
-                },
+                BouncerMessageInteractor(
+                    FakeBouncerMessageRepository(),
+                    Mockito.mock(BouncerMessageFactory::class.java),
+                    FakeUserRepository(),
+                    CountDownTimerUtil(),
+                    featureFlags
+                ),
+                BouncerLogger(logcatLogBuffer("BouncerLog"))
             )
 
         controller.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
new file mode 100644
index 0000000..577b6e0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -0,0 +1,625 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableResources
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManagerPolicyConstants
+import androidx.annotation.IdRes
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.fragments.FragmentService
+import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.plugins.qs.QS
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+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
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/** Uses Flags.MIGRATE_NSSL set to false. If all goes well, this set of tests will be deleted. */
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
+
+    @Mock lateinit var view: NotificationsQuickSettingsContainer
+    @Mock lateinit var navigationModeController: NavigationModeController
+    @Mock lateinit var overviewProxyService: OverviewProxyService
+    @Mock lateinit var shadeHeaderController: ShadeHeaderController
+    @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+    @Mock lateinit var fragmentService: FragmentService
+    @Mock lateinit var fragmentHostManager: FragmentHostManager
+    @Mock
+    lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+
+    @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
+    @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
+    @Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
+    @Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
+    @Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
+
+    lateinit var underTest: NotificationsQSContainerController
+
+    private lateinit var fakeResources: TestableResources
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var navigationModeCallback: ModeChangedListener
+    private lateinit var taskbarVisibilityCallback: OverviewProxyListener
+    private lateinit var windowInsetsCallback: Consumer<WindowInsets>
+    private lateinit var fakeSystemClock: FakeSystemClock
+    private lateinit var delayableExecutor: FakeExecutor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        fakeSystemClock = FakeSystemClock()
+        delayableExecutor = FakeExecutor(fakeSystemClock)
+        featureFlags = FakeFeatureFlags().apply { set(Flags.MIGRATE_NSSL, false) }
+        mContext.ensureTestableResources()
+        whenever(view.context).thenReturn(mContext)
+        whenever(view.resources).thenReturn(mContext.resources)
+
+        whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
+
+        underTest =
+            NotificationsQSContainerController(
+                view,
+                navigationModeController,
+                overviewProxyService,
+                shadeHeaderController,
+                shadeExpansionStateManager,
+                fragmentService,
+                delayableExecutor,
+                featureFlags,
+                notificationStackScrollLayoutController,
+            )
+
+        overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
+        overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
+        overrideResource(R.bool.config_use_split_notification_shade, false)
+        overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
+        overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
+        whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
+            .thenReturn(GESTURES_NAVIGATION)
+        doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
+        doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
+        doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture())
+        doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
+        underTest.init()
+        attachStateListenerCaptor.value.onViewAttachedToWindow(view)
+
+        navigationModeCallback = navigationModeCaptor.value
+        taskbarVisibilityCallback = taskbarVisibilityCaptor.value
+        windowInsetsCallback = windowInsetsCallbackCaptor.value
+
+        Mockito.clearInvocations(view)
+    }
+
+    @Test
+    fun testSmallScreen_updateResources_splitShadeHeightIsSet() {
+        overrideResource(R.bool.config_use_large_screen_shade_header, false)
+        overrideResource(R.dimen.qs_header_height, 10)
+        overrideResource(R.dimen.large_screen_shade_header_height, 20)
+
+        // ensure the estimated height (would be 3 here) wouldn't impact this test case
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
+
+        underTest.updateResources()
+
+        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+        verify(view).applyConstraints(capture(captor))
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(10)
+    }
+
+    @Test
+    fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
+        overrideResource(R.bool.config_use_large_screen_shade_header, true)
+        overrideResource(R.dimen.qs_header_height, 10)
+        overrideResource(R.dimen.large_screen_shade_header_height, 20)
+
+        // ensure the estimated height (would be 3 here) wouldn't impact this test case
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
+
+        underTest.updateResources()
+
+        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+        verify(view).applyConstraints(capture(captor))
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(20)
+    }
+
+    @Test
+    fun testSmallScreen_estimatedHeightIsLargerThanDimenValue_shadeHeightIsSetToEstimatedHeight() {
+        overrideResource(R.bool.config_use_large_screen_shade_header, false)
+        overrideResource(R.dimen.qs_header_height, 10)
+        overrideResource(R.dimen.large_screen_shade_header_height, 20)
+
+        // make the estimated height (would be 15 here) larger than qs_header_height
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 5)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 5)
+
+        underTest.updateResources()
+
+        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+        verify(view).applyConstraints(capture(captor))
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(15)
+    }
+
+    @Test
+    fun testTaskbarVisibleInSplitShade() {
+        enableSplitShade()
+
+        given(
+            taskbarVisible = true,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
+            expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+
+        given(
+            taskbarVisible = true,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = STABLE_INSET_BOTTOM,
+            expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSplitShade() {
+        // when taskbar is not visible, it means we're on the home screen
+        enableSplitShade()
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+
+        given(
+            taskbarVisible = false,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
+            expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+            expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSplitShadeWithCutout() {
+        enableSplitShade()
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withCutout()
+        )
+        then(
+            expectedContainerPadding = CUTOUT_HEIGHT,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+
+        given(
+            taskbarVisible = false,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withCutout().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0,
+            expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+            expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+    }
+
+    @Test
+    fun testTaskbarVisibleInSinglePaneShade() {
+        disableSplitShade()
+
+        given(
+            taskbarVisible = true,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+        given(
+            taskbarVisible = true,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = STABLE_INSET_BOTTOM,
+            expectedQsPadding = STABLE_INSET_BOTTOM
+        )
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSinglePaneShade() {
+        disableSplitShade()
+
+        given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = emptyInsets())
+        then(expectedContainerPadding = 0)
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withCutout().withStableBottom()
+        )
+        then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+        given(
+            taskbarVisible = false,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0,
+            expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+            expectedQsPadding = STABLE_INSET_BOTTOM
+        )
+    }
+
+    @Test
+    fun testDetailShowingInSinglePaneShade() {
+        disableSplitShade()
+        underTest.setDetailShowing(true)
+
+        // always sets spacings to 0
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+
+        given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+        then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+    }
+
+    @Test
+    fun testDetailShowingInSplitShade() {
+        enableSplitShade()
+        underTest.setDetailShowing(true)
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(expectedContainerPadding = 0)
+
+        // should not influence spacing
+        given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+        then(expectedContainerPadding = 0)
+    }
+
+    @Test
+    fun testNotificationsMarginBottomIsUpdated() {
+        Mockito.clearInvocations(view)
+        enableSplitShade()
+        verify(view).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
+
+        overrideResource(R.dimen.notification_panel_margin_bottom, 100)
+        disableSplitShade()
+        verify(view).setNotificationsMarginBottom(100)
+    }
+
+    @Test
+    fun testSplitShadeLayout_isAlignedToGuideline() {
+        enableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
+            .isEqualTo(R.id.qs_edge_guideline)
+    }
+
+    @Test
+    fun testSinglePaneLayout_childrenHaveEqualMargins() {
+        disableSplitShade()
+        underTest.updateResources()
+        val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
+        val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
+        val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin
+        val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin
+        assertThat(
+                qsStartMargin == qsEndMargin &&
+                    notifStartMargin == notifEndMargin &&
+                    qsStartMargin == notifStartMargin
+            )
+            .isTrue()
+    }
+
+    @Test
+    fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
+        enableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin)
+            .isEqualTo(0)
+    }
+
+    @Test
+    fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
+        enableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+        assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
+        setLargeScreen()
+        val largeScreenHeaderHeight = 100
+        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
+
+        // ensure the estimated height (would be 30 here) wouldn't impact this test case
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
+
+        underTest.updateResources()
+
+        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
+            .isEqualTo(largeScreenHeaderHeight)
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
+            .isEqualTo(largeScreenHeaderHeight)
+    }
+
+    @Test
+    fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
+        setSmallScreen()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
+        disableSplitShade()
+        underTest.updateResources()
+        val notificationPanelMarginHorizontal =
+            mContext.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
+            .isEqualTo(notificationPanelMarginHorizontal)
+        assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
+            .isEqualTo(notificationPanelMarginHorizontal)
+    }
+
+    @Test
+    fun testSinglePaneShadeLayout_isAlignedToParent() {
+        disableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
+            .isEqualTo(ConstraintSet.PARENT_ID)
+        assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
+            .isEqualTo(ConstraintSet.PARENT_ID)
+    }
+
+    @Test
+    fun testAllChildrenOfNotificationContainer_haveIds() {
+        // set dimen to 0 to avoid triggering updating bottom spacing
+        overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
+        val container = NotificationsQuickSettingsContainer(mContext, null)
+        container.removeAllViews()
+        container.addView(newViewWithId(1))
+        container.addView(newViewWithId(View.NO_ID))
+        val controller =
+            NotificationsQSContainerController(
+                container,
+                navigationModeController,
+                overviewProxyService,
+                shadeHeaderController,
+                shadeExpansionStateManager,
+                fragmentService,
+                delayableExecutor,
+                featureFlags,
+                notificationStackScrollLayoutController,
+            )
+        controller.updateConstraints()
+
+        assertThat(container.getChildAt(0).id).isEqualTo(1)
+        assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID)
+    }
+
+    @Test
+    fun testWindowInsetDebounce() {
+        disableSplitShade()
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = emptyInsets(),
+            applyImmediately = false
+        )
+        fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
+        windowInsetsCallback.accept(windowInsets().withStableBottom())
+
+        delayableExecutor.advanceClockToLast()
+        delayableExecutor.runAllReady()
+
+        verify(view, never()).setQSContainerPaddingBottom(0)
+        verify(view).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
+    }
+
+    @Test
+    fun testStartCustomizingWithDuration() {
+        underTest.setCustomizerShowing(true, 100L)
+        verify(shadeHeaderController).startCustomizingAnimation(true, 100L)
+    }
+
+    @Test
+    fun testEndCustomizingWithDuration() {
+        underTest.setCustomizerShowing(true, 0L) // Only tracks changes
+        reset(shadeHeaderController)
+
+        underTest.setCustomizerShowing(false, 100L)
+        verify(shadeHeaderController).startCustomizingAnimation(false, 100L)
+    }
+
+    @Test
+    fun testTagListenerAdded() {
+        verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(view))
+    }
+
+    @Test
+    fun testTagListenerRemoved() {
+        attachStateListenerCaptor.value.onViewDetachedFromWindow(view)
+        verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(view))
+    }
+
+    private fun disableSplitShade() {
+        setSplitShadeEnabled(false)
+    }
+
+    private fun enableSplitShade() {
+        setSplitShadeEnabled(true)
+    }
+
+    private fun setSplitShadeEnabled(enabled: Boolean) {
+        overrideResource(R.bool.config_use_split_notification_shade, enabled)
+        underTest.updateResources()
+    }
+
+    private fun setSmallScreen() {
+        setLargeScreenEnabled(false)
+    }
+
+    private fun setLargeScreen() {
+        setLargeScreenEnabled(true)
+    }
+
+    private fun setLargeScreenEnabled(enabled: Boolean) {
+        overrideResource(R.bool.config_use_large_screen_shade_header, enabled)
+    }
+
+    private fun given(
+        taskbarVisible: Boolean,
+        navigationMode: Int,
+        insets: WindowInsets,
+        applyImmediately: Boolean = true
+    ) {
+        Mockito.clearInvocations(view)
+        taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
+        navigationModeCallback.onNavigationModeChanged(navigationMode)
+        windowInsetsCallback.accept(insets)
+        if (applyImmediately) {
+            delayableExecutor.advanceClockToLast()
+            delayableExecutor.runAllReady()
+        }
+    }
+
+    fun then(
+        expectedContainerPadding: Int,
+        expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
+        expectedQsPadding: Int = 0
+    ) {
+        verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
+        verify(view).setNotificationsMarginBottom(expectedNotificationsMargin)
+        verify(view).setQSContainerPaddingBottom(expectedQsPadding)
+        Mockito.clearInvocations(view)
+    }
+
+    private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
+
+    private fun emptyInsets() = mock(WindowInsets::class.java)
+
+    private fun WindowInsets.withCutout(): WindowInsets {
+        whenever(checkNotNull(displayCutout).safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+        return this
+    }
+
+    private fun WindowInsets.withStableBottom(): WindowInsets {
+        whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
+        return this
+    }
+
+    private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
+        return constraintSetCaptor.value.getConstraint(id).layout
+    }
+
+    private fun newViewWithId(id: Int): View {
+        val view = View(mContext)
+        view.id = id
+        val layoutParams =
+            ConstraintLayout.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT
+            )
+        // required as cloning ConstraintSet fails if view doesn't have layout params
+        view.layoutParams = layoutParams
+        return view
+    }
+
+    companion object {
+        const val STABLE_INSET_BOTTOM = 100
+        const val CUTOUT_HEIGHT = 50
+        const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+        const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+        const val NOTIFICATIONS_MARGIN = 50
+        const val SCRIM_MARGIN = 10
+        const val FOOTER_ACTIONS_INSET = 2
+        const val FOOTER_ACTIONS_PADDING = 2
+        const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
+        const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index d4751c8..405199e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -19,24 +19,47 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableResources
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManagerPolicyConstants
+import androidx.annotation.IdRes
+import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.fragments.FragmentHostManager
 import com.android.systemui.fragments.FragmentService
 import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.plugins.qs.QS
 import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
 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
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -51,19 +74,37 @@
     @Mock lateinit var shadeHeaderController: ShadeHeaderController
     @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
     @Mock lateinit var fragmentService: FragmentService
+    @Mock lateinit var fragmentHostManager: FragmentHostManager
+    @Mock
+    lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+
+    @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
+    @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
+    @Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
+    @Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
+    @Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
 
     lateinit var underTest: NotificationsQSContainerController
 
     private lateinit var fakeResources: TestableResources
-
-    private val delayableExecutor: DelayableExecutor = FakeExecutor(FakeSystemClock())
+    private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var navigationModeCallback: ModeChangedListener
+    private lateinit var taskbarVisibilityCallback: OverviewProxyListener
+    private lateinit var windowInsetsCallback: Consumer<WindowInsets>
+    private lateinit var fakeSystemClock: FakeSystemClock
+    private lateinit var delayableExecutor: FakeExecutor
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        fakeResources = TestableResources(context.resources)
+        fakeSystemClock = FakeSystemClock()
+        delayableExecutor = FakeExecutor(fakeSystemClock)
+        featureFlags = FakeFeatureFlags().apply { set(Flags.MIGRATE_NSSL, true) }
+        mContext.ensureTestableResources()
+        whenever(view.context).thenReturn(mContext)
+        whenever(view.resources).thenReturn(mContext.resources)
 
-        whenever(view.resources).thenReturn(fakeResources.resources)
+        whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
 
         underTest =
             NotificationsQSContainerController(
@@ -74,36 +115,494 @@
                 shadeExpansionStateManager,
                 fragmentService,
                 delayableExecutor,
+                featureFlags,
+                notificationStackScrollLayoutController,
             )
+
+        overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
+        overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
+        overrideResource(R.bool.config_use_split_notification_shade, false)
+        overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
+        overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
+        whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
+            .thenReturn(GESTURES_NAVIGATION)
+        doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
+        doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
+        doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture())
+        doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
+        underTest.init()
+        attachStateListenerCaptor.value.onViewAttachedToWindow(view)
+
+        navigationModeCallback = navigationModeCaptor.value
+        taskbarVisibilityCallback = taskbarVisibilityCaptor.value
+        windowInsetsCallback = windowInsetsCallbackCaptor.value
+
+        Mockito.clearInvocations(view)
     }
 
     @Test
     fun testSmallScreen_updateResources_splitShadeHeightIsSet() {
-        with(fakeResources) {
-            addOverride(R.bool.config_use_large_screen_shade_header, false)
-            addOverride(R.dimen.qs_header_height, 1)
-            addOverride(R.dimen.large_screen_shade_header_height, 2)
-        }
+        overrideResource(R.bool.config_use_large_screen_shade_header, false)
+        overrideResource(R.dimen.qs_header_height, 10)
+        overrideResource(R.dimen.large_screen_shade_header_height, 20)
+
+        // ensure the estimated height (would be 3 here) wouldn't impact this test case
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
 
         underTest.updateResources()
 
         val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
         verify(view).applyConstraints(capture(captor))
-        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(1)
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(10)
     }
 
     @Test
     fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
-        with(fakeResources) {
-            addOverride(R.bool.config_use_large_screen_shade_header, true)
-            addOverride(R.dimen.qs_header_height, 1)
-            addOverride(R.dimen.large_screen_shade_header_height, 2)
-        }
+        overrideResource(R.bool.config_use_large_screen_shade_header, true)
+        overrideResource(R.dimen.qs_header_height, 10)
+        overrideResource(R.dimen.large_screen_shade_header_height, 20)
+
+        // ensure the estimated height (would be 3 here) wouldn't impact this test case
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 1)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 1)
 
         underTest.updateResources()
 
         val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
         verify(view).applyConstraints(capture(captor))
-        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2)
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(20)
+    }
+
+    @Test
+    fun testSmallScreen_estimatedHeightIsLargerThanDimenValue_shadeHeightIsSetToEstimatedHeight() {
+        overrideResource(R.bool.config_use_large_screen_shade_header, false)
+        overrideResource(R.dimen.qs_header_height, 10)
+        overrideResource(R.dimen.large_screen_shade_header_height, 20)
+
+        // make the estimated height (would be 15 here) larger than qs_header_height
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 5)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 5)
+
+        underTest.updateResources()
+
+        val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+        verify(view).applyConstraints(capture(captor))
+        assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(15)
+    }
+
+    @Test
+    fun testTaskbarVisibleInSplitShade() {
+        enableSplitShade()
+
+        given(
+            taskbarVisible = true,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
+            expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+
+        given(
+            taskbarVisible = true,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = STABLE_INSET_BOTTOM,
+            expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSplitShade() {
+        // when taskbar is not visible, it means we're on the home screen
+        enableSplitShade()
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+
+        given(
+            taskbarVisible = false,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
+            expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+            expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSplitShadeWithCutout() {
+        enableSplitShade()
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withCutout()
+        )
+        then(
+            expectedContainerPadding = CUTOUT_HEIGHT,
+            expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+
+        given(
+            taskbarVisible = false,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withCutout().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0,
+            expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+            expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+        )
+    }
+
+    @Test
+    fun testTaskbarVisibleInSinglePaneShade() {
+        disableSplitShade()
+
+        given(
+            taskbarVisible = true,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+        given(
+            taskbarVisible = true,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = STABLE_INSET_BOTTOM,
+            expectedQsPadding = STABLE_INSET_BOTTOM
+        )
+    }
+
+    @Test
+    fun testTaskbarNotVisibleInSinglePaneShade() {
+        disableSplitShade()
+
+        given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = emptyInsets())
+        then(expectedContainerPadding = 0)
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withCutout().withStableBottom()
+        )
+        then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+        given(
+            taskbarVisible = false,
+            navigationMode = BUTTONS_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(
+            expectedContainerPadding = 0,
+            expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+            expectedQsPadding = STABLE_INSET_BOTTOM
+        )
+    }
+
+    @Test
+    fun testDetailShowingInSinglePaneShade() {
+        disableSplitShade()
+        underTest.setDetailShowing(true)
+
+        // always sets spacings to 0
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+
+        given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+        then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+    }
+
+    @Test
+    fun testDetailShowingInSplitShade() {
+        enableSplitShade()
+        underTest.setDetailShowing(true)
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = windowInsets().withStableBottom()
+        )
+        then(expectedContainerPadding = 0)
+
+        // should not influence spacing
+        given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+        then(expectedContainerPadding = 0)
+    }
+
+    @Test
+    fun testNotificationsMarginBottomIsUpdated() {
+        Mockito.clearInvocations(view)
+        enableSplitShade()
+        verify(view).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
+
+        overrideResource(R.dimen.notification_panel_margin_bottom, 100)
+        disableSplitShade()
+        verify(view).setNotificationsMarginBottom(100)
+    }
+
+    @Test
+    fun testSplitShadeLayout_isAlignedToGuideline() {
+        enableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
+    }
+
+    @Test
+    fun testSinglePaneLayout_childrenHaveEqualMargins() {
+        disableSplitShade()
+        underTest.updateResources()
+        val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
+        val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
+        assertThat(qsStartMargin == qsEndMargin).isTrue()
+    }
+
+    @Test
+    fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
+        enableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
+        enableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+        assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
+        setLargeScreen()
+        val largeScreenHeaderHeight = 100
+        overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
+
+        // ensure the estimated height (would be 30 here) wouldn't impact this test case
+        overrideResource(R.dimen.large_screen_shade_header_min_height, 10)
+        overrideResource(R.dimen.new_qs_header_non_clickable_element_height, 10)
+
+        underTest.updateResources()
+
+        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
+            .isEqualTo(largeScreenHeaderHeight)
+    }
+
+    @Test
+    fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
+        setSmallScreen()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
+    }
+
+    @Test
+    fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
+        disableSplitShade()
+        underTest.updateResources()
+        val notificationPanelMarginHorizontal =
+            mContext.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
+            .isEqualTo(notificationPanelMarginHorizontal)
+        assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
+            .isEqualTo(notificationPanelMarginHorizontal)
+    }
+
+    @Test
+    fun testSinglePaneShadeLayout_isAlignedToParent() {
+        disableSplitShade()
+        underTest.updateResources()
+        assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
+            .isEqualTo(ConstraintSet.PARENT_ID)
+    }
+
+    @Test
+    fun testAllChildrenOfNotificationContainer_haveIds() {
+        // set dimen to 0 to avoid triggering updating bottom spacing
+        overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
+        val container = NotificationsQuickSettingsContainer(mContext, null)
+        container.removeAllViews()
+        container.addView(newViewWithId(1))
+        container.addView(newViewWithId(View.NO_ID))
+        val controller =
+            NotificationsQSContainerController(
+                container,
+                navigationModeController,
+                overviewProxyService,
+                shadeHeaderController,
+                shadeExpansionStateManager,
+                fragmentService,
+                delayableExecutor,
+                featureFlags,
+                notificationStackScrollLayoutController,
+            )
+        controller.updateConstraints()
+
+        assertThat(container.getChildAt(0).id).isEqualTo(1)
+        assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID)
+    }
+
+    @Test
+    fun testWindowInsetDebounce() {
+        disableSplitShade()
+
+        given(
+            taskbarVisible = false,
+            navigationMode = GESTURES_NAVIGATION,
+            insets = emptyInsets(),
+            applyImmediately = false
+        )
+        fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
+        windowInsetsCallback.accept(windowInsets().withStableBottom())
+
+        delayableExecutor.advanceClockToLast()
+        delayableExecutor.runAllReady()
+
+        verify(view, never()).setQSContainerPaddingBottom(0)
+        verify(view).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
+    }
+
+    @Test
+    fun testStartCustomizingWithDuration() {
+        underTest.setCustomizerShowing(true, 100L)
+        verify(shadeHeaderController).startCustomizingAnimation(true, 100L)
+    }
+
+    @Test
+    fun testEndCustomizingWithDuration() {
+        underTest.setCustomizerShowing(true, 0L) // Only tracks changes
+        reset(shadeHeaderController)
+
+        underTest.setCustomizerShowing(false, 100L)
+        verify(shadeHeaderController).startCustomizingAnimation(false, 100L)
+    }
+
+    @Test
+    fun testTagListenerAdded() {
+        verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(view))
+    }
+
+    @Test
+    fun testTagListenerRemoved() {
+        attachStateListenerCaptor.value.onViewDetachedFromWindow(view)
+        verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(view))
+    }
+
+    private fun disableSplitShade() {
+        setSplitShadeEnabled(false)
+    }
+
+    private fun enableSplitShade() {
+        setSplitShadeEnabled(true)
+    }
+
+    private fun setSplitShadeEnabled(enabled: Boolean) {
+        overrideResource(R.bool.config_use_split_notification_shade, enabled)
+        underTest.updateResources()
+    }
+
+    private fun setSmallScreen() {
+        setLargeScreenEnabled(false)
+    }
+
+    private fun setLargeScreen() {
+        setLargeScreenEnabled(true)
+    }
+
+    private fun setLargeScreenEnabled(enabled: Boolean) {
+        overrideResource(R.bool.config_use_large_screen_shade_header, enabled)
+    }
+
+    private fun given(
+        taskbarVisible: Boolean,
+        navigationMode: Int,
+        insets: WindowInsets,
+        applyImmediately: Boolean = true
+    ) {
+        Mockito.clearInvocations(view)
+        taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
+        navigationModeCallback.onNavigationModeChanged(navigationMode)
+        windowInsetsCallback.accept(insets)
+        if (applyImmediately) {
+            delayableExecutor.advanceClockToLast()
+            delayableExecutor.runAllReady()
+        }
+    }
+
+    fun then(
+        expectedContainerPadding: Int,
+        expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
+        expectedQsPadding: Int = 0
+    ) {
+        verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
+        verify(view).setNotificationsMarginBottom(expectedNotificationsMargin)
+        verify(view).setQSContainerPaddingBottom(expectedQsPadding)
+        Mockito.clearInvocations(view)
+    }
+
+    private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
+
+    private fun emptyInsets() = mock(WindowInsets::class.java)
+
+    private fun WindowInsets.withCutout(): WindowInsets {
+        whenever(checkNotNull(displayCutout).safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+        return this
+    }
+
+    private fun WindowInsets.withStableBottom(): WindowInsets {
+        whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
+        return this
+    }
+
+    private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
+        return constraintSetCaptor.value.getConstraint(id).layout
+    }
+
+    private fun newViewWithId(id: Int): View {
+        val view = View(mContext)
+        view.id = id
+        val layoutParams =
+            ConstraintLayout.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT
+            )
+        // required as cloning ConstraintSet fails if view doesn't have layout params
+        view.layoutParams = layoutParams
+        return view
+    }
+
+    companion object {
+        const val STABLE_INSET_BOTTOM = 100
+        const val CUTOUT_HEIGHT = 50
+        const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+        const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+        const val NOTIFICATIONS_MARGIN = 50
+        const val SCRIM_MARGIN = 10
+        const val FOOTER_ACTIONS_INSET = 2
+        const val FOOTER_ACTIONS_PADDING = 2
+        const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
+        const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index d7c06a7..29bc64e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -25,22 +25,26 @@
 import android.view.MotionEvent
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dock.DockManager
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.DozeInteractor
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.tuner.TunerService.Tunable
 import com.android.systemui.util.mockito.eq
+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.ArgumentMatchers.anyLong
-import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -52,14 +56,12 @@
 @SmallTest
 class PulsingGestureListenerTest : SysuiTestCase() {
     @Mock
-    private lateinit var view: NotificationShadeWindowView
-    @Mock
-    private lateinit var centralSurfaces: CentralSurfaces
-    @Mock
     private lateinit var dockManager: DockManager
     @Mock
     private lateinit var falsingManager: FalsingManager
     @Mock
+    private lateinit var falsingCollector: FalsingCollector
+    @Mock
     private lateinit var ambientDisplayConfiguration: AmbientDisplayConfiguration
     @Mock
     private lateinit var tunerService: TunerService
@@ -71,7 +73,12 @@
     private lateinit var shadeLogger: ShadeLogger
     @Mock
     private lateinit var userTracker: UserTracker
+    @Mock
+    private lateinit var dozeInteractor: DozeInteractor
+    @Mock
+    private lateinit var screenOffAnimationController: ScreenOffAnimationController
 
+    private lateinit var powerRepository: FakePowerRepository
     private lateinit var tunableCaptor: ArgumentCaptor<Tunable>
     private lateinit var underTest: PulsingGestureListener
 
@@ -79,19 +86,28 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        powerRepository = FakePowerRepository()
+
         underTest = PulsingGestureListener(
-                view,
                 falsingManager,
                 dockManager,
-                centralSurfaces,
+                PowerInteractor(
+                    powerRepository,
+                    FakeKeyguardRepository(),
+                    falsingCollector,
+                    screenOffAnimationController,
+                    statusBarStateController,
+                ),
                 ambientDisplayConfiguration,
                 statusBarStateController,
                 shadeLogger,
+                dozeInteractor,
                 userTracker,
                 tunerService,
                 dumpManager
         )
         whenever(dockManager.isDocked).thenReturn(false)
+        whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
     }
 
     @Test
@@ -110,8 +126,8 @@
         underTest.onSingleTapUp(upEv)
 
         // THEN wake up device if dozing
-        verify(centralSurfaces).wakeUpIfDozing(
-                anyLong(), anyString(), eq(PowerManager.WAKE_REASON_TAP))
+        assertThat(powerRepository.lastWakeWhy).isNotNull()
+        assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_TAP)
     }
 
     @Test
@@ -130,8 +146,8 @@
         underTest.onDoubleTapEvent(upEv)
 
         // THEN wake up device if dozing
-        verify(centralSurfaces).wakeUpIfDozing(
-                anyLong(), anyString(), eq(PowerManager.WAKE_REASON_TAP))
+        assertThat(powerRepository.lastWakeWhy).isNotNull()
+        assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_TAP)
     }
 
     @Test
@@ -162,8 +178,8 @@
         underTest.onSingleTapUp(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(
-                anyLong(), anyString(), anyInt())
+        assertThat(powerRepository.lastWakeWhy).isNull()
+        assertThat(powerRepository.lastWakeReason).isNull()
     }
 
     @Test
@@ -210,8 +226,8 @@
         underTest.onDoubleTapEvent(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(
-                anyLong(), anyString(), anyInt())
+        assertThat(powerRepository.lastWakeWhy).isNull()
+        assertThat(powerRepository.lastWakeReason).isNull()
     }
 
     @Test
@@ -230,8 +246,8 @@
         underTest.onSingleTapUp(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(
-                anyLong(), anyString(), anyInt())
+        assertThat(powerRepository.lastWakeWhy).isNull()
+        assertThat(powerRepository.lastWakeReason).isNull()
     }
 
     @Test
@@ -250,8 +266,8 @@
         underTest.onDoubleTapEvent(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(
-                anyLong(), anyString(), anyInt())
+        assertThat(powerRepository.lastWakeWhy).isNull()
+        assertThat(powerRepository.lastWakeReason).isNull()
     }
 
     fun updateSettings() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
new file mode 100644
index 0000000..a2c2912
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.TestScopeProvider;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.media.controls.pipeline.MediaDataManager;
+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.qs.QSFragment;
+import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.shade.data.repository.ShadeRepository;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
+import com.android.systemui.shade.transition.ShadeTransitionController;
+import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.QsFrameTranslateController;
+import com.android.systemui.statusbar.StatusBarStateControllerImpl;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
+import com.android.systemui.statusbar.notification.stack.AmbientState;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.UserInteractor;
+import com.android.systemui.util.kotlin.JavaAdapter;
+
+import dagger.Lazy;
+
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import kotlinx.coroutines.test.TestScope;
+
+public class QuickSettingsControllerBaseTest extends SysuiTestCase {
+    protected static final float QS_FRAME_START_X = 0f;
+    protected static final int QS_FRAME_WIDTH = 1000;
+    protected static final int QS_FRAME_TOP = 0;
+    protected static final int QS_FRAME_BOTTOM = 1000;
+    protected static final int DEFAULT_HEIGHT = 1000;
+    // In split shade min = max
+    protected static final int DEFAULT_MIN_HEIGHT_SPLIT_SHADE = DEFAULT_HEIGHT;
+    protected static final int DEFAULT_MIN_HEIGHT = 300;
+
+    protected QuickSettingsController mQsController;
+
+    protected TestScope mTestScope = TestScopeProvider.getTestScope();
+
+    @Mock
+    protected Resources mResources;
+    @Mock protected KeyguardBottomAreaView mQsFrame;
+    @Mock protected KeyguardStatusBarView mKeyguardStatusBar;
+    @Mock protected QS mQs;
+    @Mock protected QSFragment mQSFragment;
+    @Mock protected Lazy<NotificationPanelViewController> mPanelViewControllerLazy;
+    @Mock protected NotificationPanelViewController mNotificationPanelViewController;
+    @Mock protected NotificationPanelView mPanelView;
+    @Mock protected ViewGroup mQsHeader;
+    @Mock protected ViewParent mPanelViewParent;
+    @Mock protected QsFrameTranslateController mQsFrameTranslateController;
+    @Mock protected ShadeTransitionController mShadeTransitionController;
+    @Mock protected PulseExpansionHandler mPulseExpansionHandler;
+    @Mock protected NotificationRemoteInputManager mNotificationRemoteInputManager;
+    @Mock protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Mock protected LightBarController mLightBarController;
+    @Mock protected NotificationStackScrollLayoutController
+            mNotificationStackScrollLayoutController;
+    @Mock protected LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+    @Mock protected NotificationShadeDepthController mNotificationShadeDepthController;
+    @Mock protected ShadeHeaderController mShadeHeaderController;
+    @Mock protected StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+    @Mock protected KeyguardStateController mKeyguardStateController;
+    @Mock protected KeyguardBypassController mKeyguardBypassController;
+    @Mock protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock protected ScrimController mScrimController;
+    @Mock protected MediaDataManager mMediaDataManager;
+    @Mock protected MediaHierarchyManager mMediaHierarchyManager;
+    @Mock protected AmbientState mAmbientState;
+    @Mock protected RecordingController mRecordingController;
+    @Mock protected FalsingManager mFalsingManager;
+    @Mock protected FalsingCollector mFalsingCollector;
+    @Mock protected AccessibilityManager mAccessibilityManager;
+    @Mock protected LockscreenGestureLogger mLockscreenGestureLogger;
+    @Mock protected MetricsLogger mMetricsLogger;
+    @Mock protected FeatureFlags mFeatureFlags;
+    @Mock protected InteractionJankMonitor mInteractionJankMonitor;
+    @Mock protected ShadeLogger mShadeLogger;
+    @Mock protected DumpManager mDumpManager;
+    @Mock protected UiEventLogger mUiEventLogger;
+    @Mock protected CastController mCastController;
+    @Mock protected DeviceProvisionedController mDeviceProvisionedController;
+    @Mock protected UserInteractor mUserInteractor;
+    protected FakeDisableFlagsRepository mDisableFlagsRepository =
+            new FakeDisableFlagsRepository();
+    protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
+
+    protected SysuiStatusBarStateController mStatusBarStateController;
+    protected ShadeInteractor mShadeInteractor;
+
+    protected Handler mMainHandler;
+    protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback;
+
+    protected final ShadeExpansionStateManager mShadeExpansionStateManager =
+            new ShadeExpansionStateManager();
+
+    protected FragmentHostManager.FragmentListener mFragmentListener;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController);
+        mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
+                mInteractionJankMonitor, mShadeExpansionStateManager);
+
+        when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
+        mShadeInteractor =
+                new ShadeInteractor(
+                        mTestScope.getBackgroundScope(),
+                        mDisableFlagsRepository,
+                        mKeyguardRepository,
+                        new FakeUserSetupRepository(),
+                        mDeviceProvisionedController,
+                        mUserInteractor
+                );
+
+        KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
+        keyguardStatusView.setId(R.id.keyguard_status_view);
+
+        when(mResources.getDimensionPixelSize(
+                R.dimen.lockscreen_shade_qs_transition_distance)).thenReturn(DEFAULT_HEIGHT);
+        when(mPanelView.getResources()).thenReturn(mResources);
+        when(mPanelView.getContext()).thenReturn(getContext());
+        when(mPanelView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
+        when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000);
+        when(mPanelView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
+        when(mQsFrame.getX()).thenReturn(QS_FRAME_START_X);
+        when(mQsFrame.getWidth()).thenReturn(QS_FRAME_WIDTH);
+        when(mQsHeader.getTop()).thenReturn(QS_FRAME_TOP);
+        when(mQsHeader.getBottom()).thenReturn(QS_FRAME_BOTTOM);
+        when(mPanelView.getY()).thenReturn((float) QS_FRAME_TOP);
+        when(mPanelView.getHeight()).thenReturn(QS_FRAME_BOTTOM);
+        when(mPanelView.findViewById(R.id.keyguard_status_view))
+                .thenReturn(mock(KeyguardStatusView.class));
+        when(mQs.getView()).thenReturn(mPanelView);
+        when(mQSFragment.getView()).thenReturn(mPanelView);
+
+        when(mNotificationRemoteInputManager.isRemoteInputActive())
+                .thenReturn(false);
+        when(mInteractionJankMonitor.begin(any(), anyInt()))
+                .thenReturn(true);
+        when(mInteractionJankMonitor.end(anyInt()))
+                .thenReturn(true);
+
+        when(mPanelView.getParent()).thenReturn(mPanelViewParent);
+        when(mQs.getHeader()).thenReturn(mQsHeader);
+
+        doAnswer(invocation -> {
+            mLockscreenShadeTransitionCallback = invocation.getArgument(0);
+            return null;
+        }).when(mLockscreenShadeTransitionController).addCallback(any());
+
+
+        mMainHandler = new Handler(Looper.getMainLooper());
+
+        mQsController = new QuickSettingsController(
+                mPanelViewControllerLazy,
+                mPanelView,
+                mQsFrameTranslateController,
+                mShadeTransitionController,
+                mPulseExpansionHandler,
+                mNotificationRemoteInputManager,
+                mShadeExpansionStateManager,
+                mStatusBarKeyguardViewManager,
+                mLightBarController,
+                mNotificationStackScrollLayoutController,
+                mLockscreenShadeTransitionController,
+                mNotificationShadeDepthController,
+                mShadeHeaderController,
+                mStatusBarTouchableRegionManager,
+                mKeyguardStateController,
+                mKeyguardBypassController,
+                mKeyguardUpdateMonitor,
+                mScrimController,
+                mMediaDataManager,
+                mMediaHierarchyManager,
+                mAmbientState,
+                mRecordingController,
+                mFalsingManager,
+                mFalsingCollector,
+                mAccessibilityManager,
+                mLockscreenGestureLogger,
+                mMetricsLogger,
+                mFeatureFlags,
+                mInteractionJankMonitor,
+                mShadeLogger,
+                mDumpManager,
+                mock(KeyguardFaceAuthInteractor.class),
+                mock(ShadeRepository.class),
+                mShadeInteractor,
+                new JavaAdapter(mTestScope.getBackgroundScope()),
+                mCastController
+        );
+        mQsController.init();
+
+        mFragmentListener = mQsController.getQsFragmentListener();
+    }
+
+    @After
+    public void tearDown() {
+        mMainHandler.removeCallbacksAndMessages(null);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index ff047aa..6d288e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -28,238 +28,32 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.res.Resources;
-import android.os.Handler;
-import android.os.Looper;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.keyguard.KeyguardStatusView;
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
-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.qs.QSFragment;
-import com.android.systemui.screenrecord.RecordingController;
-import com.android.systemui.shade.data.repository.ShadeRepository;
-import com.android.systemui.shade.transition.ShadeTransitionController;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.QsFrameTranslateController;
-import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.stack.AmbientState;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
-import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
-import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 
-import dagger.Lazy;
-
-import org.junit.After;
-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;
 
 import java.util.List;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class QuickSettingsControllerTest extends SysuiTestCase {
-
-    private static final float QS_FRAME_START_X = 0f;
-    private static final int QS_FRAME_WIDTH = 1000;
-    private static final int QS_FRAME_TOP = 0;
-    private static final int QS_FRAME_BOTTOM = 1000;
-    private static final int DEFAULT_HEIGHT = 1000;
-    // In split shade min = max
-    private static final int DEFAULT_MIN_HEIGHT_SPLIT_SHADE = DEFAULT_HEIGHT;
-    private static final int DEFAULT_MIN_HEIGHT = 300;
-
-    private QuickSettingsController mQsController;
-
-    @Mock private Resources mResources;
-    @Mock private KeyguardBottomAreaView mQsFrame;
-    @Mock private KeyguardStatusBarView mKeyguardStatusBar;
-    @Mock private QS mQs;
-    @Mock private QSFragment mQSFragment;
-    @Mock private Lazy<NotificationPanelViewController> mPanelViewControllerLazy;
-    @Mock private NotificationPanelViewController mNotificationPanelViewController;
-    @Mock private NotificationPanelView mPanelView;
-    @Mock private ViewGroup mQsHeader;
-    @Mock private ViewParent mPanelViewParent;
-    @Mock private QsFrameTranslateController mQsFrameTranslateController;
-    @Mock private ShadeTransitionController mShadeTransitionController;
-    @Mock private PulseExpansionHandler mPulseExpansionHandler;
-    @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;
-    @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock private LightBarController mLightBarController;
-    @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
-    @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
-    @Mock private NotificationShadeDepthController mNotificationShadeDepthController;
-    @Mock private ShadeHeaderController mShadeHeaderController;
-    @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
-    @Mock private KeyguardStateController mKeyguardStateController;
-    @Mock private KeyguardBypassController mKeyguardBypassController;
-    @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock private ScrimController mScrimController;
-    @Mock private MediaDataManager mMediaDataManager;
-    @Mock private MediaHierarchyManager mMediaHierarchyManager;
-    @Mock private AmbientState mAmbientState;
-    @Mock private RecordingController mRecordingController;
-    @Mock private FalsingManager mFalsingManager;
-    @Mock private FalsingCollector mFalsingCollector;
-    @Mock private AccessibilityManager mAccessibilityManager;
-    @Mock private LockscreenGestureLogger mLockscreenGestureLogger;
-    @Mock private MetricsLogger mMetricsLogger;
-    @Mock private FeatureFlags mFeatureFlags;
-    @Mock private InteractionJankMonitor mInteractionJankMonitor;
-    @Mock private ShadeLogger mShadeLogger;
-    @Mock private DumpManager mDumpManager;
-    @Mock private UiEventLogger mUiEventLogger;
-    @Mock private CastController mCastController;
-
-    private SysuiStatusBarStateController mStatusBarStateController;
-
-    private Handler mMainHandler;
-    private LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback;
-
-    private final ShadeExpansionStateManager mShadeExpansionStateManager =
-            new ShadeExpansionStateManager();
-
-    private FragmentHostManager.FragmentListener mFragmentListener;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController);
-        mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
-                mInteractionJankMonitor, mShadeExpansionStateManager);
-
-        KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
-        keyguardStatusView.setId(R.id.keyguard_status_view);
-
-        when(mResources.getDimensionPixelSize(
-                R.dimen.lockscreen_shade_qs_transition_distance)).thenReturn(DEFAULT_HEIGHT);
-        when(mPanelView.getResources()).thenReturn(mResources);
-        when(mPanelView.getContext()).thenReturn(getContext());
-        when(mPanelView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
-        when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000);
-        when(mPanelView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
-        when(mQsFrame.getX()).thenReturn(QS_FRAME_START_X);
-        when(mQsFrame.getWidth()).thenReturn(QS_FRAME_WIDTH);
-        when(mQsHeader.getTop()).thenReturn(QS_FRAME_TOP);
-        when(mQsHeader.getBottom()).thenReturn(QS_FRAME_BOTTOM);
-        when(mPanelView.getY()).thenReturn((float) QS_FRAME_TOP);
-        when(mPanelView.getHeight()).thenReturn(QS_FRAME_BOTTOM);
-        when(mPanelView.findViewById(R.id.keyguard_status_view))
-                .thenReturn(mock(KeyguardStatusView.class));
-        when(mQs.getView()).thenReturn(mPanelView);
-        when(mQSFragment.getView()).thenReturn(mPanelView);
-
-        when(mNotificationRemoteInputManager.isRemoteInputActive())
-                .thenReturn(false);
-        when(mInteractionJankMonitor.begin(any(), anyInt()))
-                .thenReturn(true);
-        when(mInteractionJankMonitor.end(anyInt()))
-                .thenReturn(true);
-
-        when(mPanelView.getParent()).thenReturn(mPanelViewParent);
-        when(mQs.getHeader()).thenReturn(mQsHeader);
-
-        doAnswer(invocation -> {
-            mLockscreenShadeTransitionCallback = invocation.getArgument(0);
-            return null;
-        }).when(mLockscreenShadeTransitionController).addCallback(any());
-
-
-        mMainHandler = new Handler(Looper.getMainLooper());
-
-        mQsController = new QuickSettingsController(
-                mPanelViewControllerLazy,
-                mPanelView,
-                mQsFrameTranslateController,
-                mShadeTransitionController,
-                mPulseExpansionHandler,
-                mNotificationRemoteInputManager,
-                mShadeExpansionStateManager,
-                mStatusBarKeyguardViewManager,
-                mLightBarController,
-                mNotificationStackScrollLayoutController,
-                mLockscreenShadeTransitionController,
-                mNotificationShadeDepthController,
-                mShadeHeaderController,
-                mStatusBarTouchableRegionManager,
-                mKeyguardStateController,
-                mKeyguardBypassController,
-                mKeyguardUpdateMonitor,
-                mScrimController,
-                mMediaDataManager,
-                mMediaHierarchyManager,
-                mAmbientState,
-                mRecordingController,
-                mFalsingManager,
-                mFalsingCollector,
-                mAccessibilityManager,
-                mLockscreenGestureLogger,
-                mMetricsLogger,
-                mFeatureFlags,
-                mInteractionJankMonitor,
-                mShadeLogger,
-                mDumpManager,
-                mock(KeyguardFaceAuthInteractor.class),
-                mock(ShadeRepository.class),
-                mCastController
-        );
-
-        mFragmentListener = mQsController.getQsFragmentListener();
-    }
-
-    @After
-    public void tearDown() {
-        mMainHandler.removeCallbacksAndMessages(null);
-    }
+public class QuickSettingsControllerTest extends QuickSettingsControllerBaseTest {
 
     @Test
     public void testCloseQsSideEffects() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..cc4a063
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.app.StatusBarManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class QuickSettingsControllerWithCoroutinesTest : QuickSettingsControllerBaseTest() {
+
+    @Test
+    fun isExpansionEnabled_dozing_false() =
+        mTestScope.runTest {
+            mKeyguardRepository.setIsDozing(true)
+            runCurrent()
+
+            assertThat(mQsController.isExpansionEnabled).isFalse()
+        }
+
+    @Test
+    fun isExpansionEnabled_notDozing_true() =
+        mTestScope.runTest {
+            mKeyguardRepository.setIsDozing(false)
+            runCurrent()
+
+            assertThat(mQsController.isExpansionEnabled).isTrue()
+        }
+
+    @Test
+    fun isExpansionEnabled_qsDisabled_false() =
+        mTestScope.runTest {
+            mDisableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    StatusBarManager.DISABLE_NONE,
+                    StatusBarManager.DISABLE2_QUICK_SETTINGS
+                )
+            runCurrent()
+
+            assertThat(mQsController.isExpansionEnabled).isFalse()
+        }
+
+    @Test
+    fun isExpansionEnabled_shadeDisabled_false() =
+        mTestScope.runTest {
+            mDisableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    StatusBarManager.DISABLE_NONE,
+                    StatusBarManager.DISABLE2_NOTIFICATION_SHADE
+                )
+            runCurrent()
+
+            assertThat(mQsController.isExpansionEnabled).isFalse()
+        }
+
+    @Test
+    fun isExpansionEnabled_qsAndShadeEnabled_true() =
+        mTestScope.runTest {
+            mDisableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(StatusBarManager.DISABLE_NONE, StatusBarManager.DISABLE2_NONE)
+            runCurrent()
+
+            assertThat(mQsController.isExpansionEnabled).isTrue()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
new file mode 100644
index 0000000..6a14a00
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import dagger.Lazy
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ShadeControllerImplTest : SysuiTestCase() {
+    @Mock private lateinit var commandQueue: CommandQueue
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    @Mock private lateinit var statusBarWindowController: StatusBarWindowController
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+    @Mock private lateinit var windowManager: WindowManager
+    @Mock private lateinit var assistManager: AssistManager
+    @Mock private lateinit var gutsManager: NotificationGutsManager
+    @Mock private lateinit var shadeViewController: ShadeViewController
+    @Mock private lateinit var nswvc: NotificationShadeWindowViewController
+    @Mock private lateinit var display: Display
+    @Mock private lateinit var touchLog: LogBuffer
+
+    private lateinit var shadeController: ShadeControllerImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(windowManager.defaultDisplay).thenReturn(display)
+        whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
+        shadeController =
+            ShadeControllerImpl(
+                commandQueue,
+                FakeExecutor(FakeSystemClock()),
+                touchLog,
+                keyguardStateController,
+                statusBarStateController,
+                statusBarKeyguardViewManager,
+                statusBarWindowController,
+                deviceProvisionedController,
+                notificationShadeWindowController,
+                windowManager,
+                Lazy { shadeViewController },
+                Lazy { assistManager },
+                Lazy { gutsManager },
+            )
+        shadeController.setNotificationShadeWindowViewController(nswvc)
+    }
+
+    @Test
+    fun testDisableNotificationShade() {
+        whenever(commandQueue.panelsEnabled()).thenReturn(false)
+
+        // Trying to open it does nothing.
+        shadeController.animateExpandShade()
+        verify(shadeViewController, never()).expandToNotifications()
+        shadeController.animateExpandQs()
+        verify(shadeViewController, never()).expand(ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    fun testEnableNotificationShade() {
+        whenever(commandQueue.panelsEnabled()).thenReturn(true)
+
+        // Can now be opened.
+        shadeController.animateExpandShade()
+        verify(shadeViewController).expandToNotifications()
+        shadeController.animateExpandQs()
+        verify(shadeViewController).expandToQs()
+    }
+
+    @Test
+    fun cancelExpansionAndCollapseShade_callsCancelCurrentTouch() {
+        // GIVEN the shade is tracking a touch
+        whenever(shadeViewController.isTracking).thenReturn(true)
+
+        // WHEN cancelExpansionAndCollapseShade is called
+        shadeController.cancelExpansionAndCollapseShade()
+
+        // VERIFY that cancelCurrentTouch is called
+        verify(nswvc).cancelCurrentTouch()
+    }
+
+    @Test
+    fun cancelExpansionAndCollapseShade_doesNotCallAnimateCollapseShade_whenCollapsed() {
+        // GIVEN the shade is tracking a touch
+        whenever(shadeViewController.isTracking).thenReturn(false)
+
+        // WHEN cancelExpansionAndCollapseShade is called
+        shadeController.cancelExpansionAndCollapseShade()
+
+        // VERIFY that cancelCurrentTouch is NOT called
+        verify(nswvc, never()).cancelCurrentTouch()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 2da2e92..8f8b840 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -29,6 +29,7 @@
 import android.view.View
 import android.view.ViewPropertyAnimator
 import android.view.WindowInsets
+import android.widget.LinearLayout
 import android.widget.TextView
 import androidx.constraintlayout.motion.widget.MotionLayout
 import androidx.constraintlayout.widget.ConstraintSet
@@ -54,6 +55,7 @@
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.phone.StatusOverlayHoverListenerFactory
 import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.statusbar.policy.NextAlarmController
@@ -122,11 +124,13 @@
     @Mock private lateinit var qsBatteryModeController: QsBatteryModeController
     @Mock private lateinit var nextAlarmController: NextAlarmController
     @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var mStatusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory
 
     @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
     var viewVisibility = View.GONE
     var viewAlpha = 1f
 
+    private val systemIcons = LinearLayout(context)
     private lateinit var shadeHeaderController: ShadeHeaderController
     private lateinit var carrierIconSlots: List<String>
     private val configurationController = FakeConfigurationController()
@@ -134,18 +138,19 @@
 
     @Before
     fun setup() {
-        whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock)
+        whenever<Clock>(view.requireViewById(R.id.clock)).thenReturn(clock)
         whenever(clock.context).thenReturn(mockedContext)
 
-        whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
+        whenever<TextView>(view.requireViewById(R.id.date)).thenReturn(date)
         whenever(date.context).thenReturn(mockedContext)
 
-        whenever<ShadeCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
+        whenever<ShadeCarrierGroup>(view.requireViewById(R.id.carrier_group)).thenReturn(carrierGroup)
 
-        whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon))
+        whenever<BatteryMeterView>(view.requireViewById(R.id.batteryRemainingIcon))
             .thenReturn(batteryMeterView)
 
-        whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
+        whenever<StatusIconContainer>(view.requireViewById(R.id.statusIcons)).thenReturn(statusIcons)
+        whenever<View>(view.requireViewById(R.id.shade_header_system_icons)).thenReturn(systemIcons)
 
         viewContext = Mockito.spy(context)
         whenever(view.context).thenReturn(viewContext)
@@ -191,6 +196,7 @@
                 qsBatteryModeController,
                 nextAlarmController,
                 activityStarter,
+                mStatusOverlayHoverListenerFactory
             )
         whenever(view.isAttachedToWindow).thenReturn(true)
         shadeHeaderController.init()
@@ -237,11 +243,22 @@
         whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false)
 
         makeShadeVisible()
+        shadeHeaderController.qsExpandedFraction = 1.0f
 
         verify(statusIcons).addIgnoredSlots(carrierIconSlots)
     }
 
     @Test
+    fun dualCarrier_enablesCarrierIconsInStatusIcons_qsExpanded() {
+        whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false)
+
+        makeShadeVisible()
+        shadeHeaderController.qsExpandedFraction = 0.0f
+
+        verify(statusIcons, times(2)).removeIgnoredSlots(carrierIconSlots)
+    }
+
+    @Test
     fun disableQS_notDisabled_visible() {
         makeShadeVisible()
         shadeHeaderController.disable(0, 0, false)
@@ -440,6 +457,17 @@
     }
 
     @Test
+    fun testLargeScreenActive_collapseActionRun_onSystemIconsClick() {
+        shadeHeaderController.largeScreenActive = true
+        var wasRun = false
+        shadeHeaderController.shadeCollapseAction = Runnable { wasRun = true }
+
+        systemIcons.performClick()
+
+        assertThat(wasRun).isTrue()
+    }
+
+    @Test
     fun testShadeExpandedFraction() {
         // View needs to be visible for this to actually take effect
         shadeHeaderController.qsVisible = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
index 4461310..dae9c97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
@@ -20,8 +20,10 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.TextView;
@@ -48,7 +50,9 @@
     @Before
     public void setUp() throws Exception {
         mTestableLooper = TestableLooper.get(this);
-        LayoutInflater inflater = LayoutInflater.from(mContext);
+        Context themedContext =
+                new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_QuickSettings);
+        LayoutInflater inflater = LayoutInflater.from(themedContext);
         mContext.ensureTestableResources();
         mTestableLooper.runWithLooper(() ->
                 mShadeCarrier = (ShadeCarrier) inflater.inflate(R.layout.shade_carrier, null));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
new file mode 100644
index 0000000..cdcd1a2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shade.data.repository
+
+import android.app.ActivityManager
+import android.app.StatusBarManager.DISABLE2_NONE
+import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
+import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
+import android.content.pm.UserInfo
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
+import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class ShadeInteractorTest : SysuiTestCase() {
+    private lateinit var underTest: ShadeInteractor
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+    private val featureFlags = FakeFeatureFlags()
+    private val userSetupRepository = FakeUserSetupRepository()
+    private val userRepository = FakeUserRepository()
+    private val disableFlagsRepository = FakeDisableFlagsRepository()
+    private val keyguardRepository = FakeKeyguardRepository()
+
+    @Mock private lateinit var manager: UserManager
+    @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var activityManager: ActivityManager
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var guestInteractor: GuestUserInteractor
+
+    private lateinit var userInteractor: UserInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        featureFlags.set(Flags.FACE_AUTH_REFACTOR, false)
+        featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+
+        val refreshUsersScheduler =
+            RefreshUsersScheduler(
+                applicationScope = testScope.backgroundScope,
+                mainDispatcher = testDispatcher,
+                repository = userRepository,
+            )
+
+        runBlocking {
+            val userInfos =
+                listOf(
+                    UserInfo(
+                        /* id= */ 0,
+                        /* name= */ "zero",
+                        /* iconPath= */ "",
+                        /* flags= */ UserInfo.FLAG_PRIMARY or
+                            UserInfo.FLAG_ADMIN or
+                            UserInfo.FLAG_FULL,
+                        UserManager.USER_TYPE_FULL_SYSTEM,
+                    ),
+                )
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+        }
+        userInteractor =
+            UserInteractor(
+                applicationContext = context,
+                repository = userRepository,
+                activityStarter = activityStarter,
+                keyguardInteractor =
+                    KeyguardInteractorFactory.create(featureFlags = featureFlags)
+                        .keyguardInteractor,
+                featureFlags = featureFlags,
+                manager = manager,
+                headlessSystemUserMode = headlessSystemUserMode,
+                applicationScope = testScope.backgroundScope,
+                telephonyInteractor =
+                    TelephonyInteractor(
+                        repository = FakeTelephonyRepository(),
+                    ),
+                broadcastDispatcher = fakeBroadcastDispatcher,
+                keyguardUpdateMonitor = keyguardUpdateMonitor,
+                backgroundDispatcher = testDispatcher,
+                activityManager = activityManager,
+                refreshUsersScheduler = refreshUsersScheduler,
+                guestUserInteractor = guestInteractor,
+                uiEventLogger = uiEventLogger,
+            )
+        underTest =
+            ShadeInteractor(
+                testScope.backgroundScope,
+                disableFlagsRepository,
+                keyguardRepository,
+                userSetupRepository,
+                deviceProvisionedController,
+                userInteractor,
+            )
+    }
+
+    @Test
+    fun isShadeEnabled_matchesDisableFlagsRepo() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isShadeEnabled)
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(disable2 = DISABLE2_NOTIFICATION_SHADE)
+            assertThat(actual).isFalse()
+
+            disableFlagsRepository.disableFlags.value = DisableFlagsModel(disable2 = DISABLE2_NONE)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_deviceNotProvisioned_false() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_userNotSetupAndSimpleUserSwitcher_false() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+
+            userSetupRepository.setUserSetup(false)
+            userRepository.setSettings(UserSwitcherSettingsModel(isSimpleUserSwitcher = true))
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_shadeNotEnabled_false() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            userSetupRepository.setUserSetup(true)
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NOTIFICATION_SHADE,
+                )
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_quickSettingsNotEnabled_false() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            userSetupRepository.setUserSetup(true)
+
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_QUICK_SETTINGS,
+                )
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_dozing_false() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            userSetupRepository.setUserSetup(true)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+
+            keyguardRepository.setIsDozing(true)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_userSetup_true() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            keyguardRepository.setIsDozing(false)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+
+            userSetupRepository.setUserSetup(true)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_notSimpleUserSwitcher_true() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            keyguardRepository.setIsDozing(false)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+
+            userRepository.setSettings(UserSwitcherSettingsModel(isSimpleUserSwitcher = false))
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_respondsToDozingUpdates() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            keyguardRepository.setIsDozing(false)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+            userSetupRepository.setUserSetup(true)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isTrue()
+
+            // WHEN dozing starts
+            keyguardRepository.setIsDozing(true)
+
+            // THEN expand is disabled
+            assertThat(actual).isFalse()
+
+            // WHEN dozing stops
+            keyguardRepository.setIsDozing(false)
+
+            // THEN expand is enabled
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_respondsToDisableUpdates() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            keyguardRepository.setIsDozing(false)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+            userSetupRepository.setUserSetup(true)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isTrue()
+
+            // WHEN QS is disabled
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_QUICK_SETTINGS,
+                )
+            // THEN expand is disabled
+            assertThat(actual).isFalse()
+
+            // WHEN QS is enabled
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+            // THEN expand is enabled
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isExpandToQsEnabled_respondsToUserUpdates() =
+        testScope.runTest {
+            whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+            keyguardRepository.setIsDozing(false)
+            disableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(
+                    disable2 = DISABLE2_NONE,
+                )
+            userSetupRepository.setUserSetup(true)
+
+            val actual by collectLastValue(underTest.isExpandToQsEnabled)
+
+            assertThat(actual).isTrue()
+
+            // WHEN the user is no longer setup
+            userSetupRepository.setUserSetup(false)
+            userRepository.setSettings(UserSwitcherSettingsModel(isSimpleUserSwitcher = true))
+
+            // THEN expand is disabled
+            assertThat(actual).isFalse()
+
+            // WHEN the user is setup again
+            userSetupRepository.setUserSetup(true)
+
+            // THEN expand is enabled
+            assertThat(actual).isTrue()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
index cbf5485..a5bd2ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -2,26 +2,16 @@
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.shade.STATE_CLOSED
-import com.android.systemui.shade.STATE_OPEN
 import com.android.systemui.shade.STATE_OPENING
 import com.android.systemui.shade.ShadeExpansionChangeEvent
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.statusbar.policy.HeadsUpManager
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -30,10 +20,6 @@
 
     @Mock private lateinit var scrimController: ScrimController
     @Mock private lateinit var dumpManager: DumpManager
-    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
-    @Mock private lateinit var headsUpManager: HeadsUpManager
-    @Mock private lateinit var featureFlags: FeatureFlags
-    private val configurationController = FakeConfigurationController()
 
     private lateinit var controller: ScrimShadeTransitionController
 
@@ -41,131 +27,25 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         context.ensureTestableResources()
-        controller =
-            ScrimShadeTransitionController(
-                configurationController,
-                dumpManager,
-                scrimController,
-                context.resources,
-                statusBarStateController,
-                headsUpManager,
-                featureFlags)
+        controller = ScrimShadeTransitionController(dumpManager, scrimController)
 
         controller.onPanelStateChanged(STATE_OPENING)
     }
 
     @Test
-    fun onPanelExpansionChanged_inSingleShade_setsFractionEqualToEventFraction() {
-        setSplitShadeEnabled(false)
-
+    fun onPanelExpansionChanged_setsFractionEqualToEventFraction() {
         controller.onPanelExpansionChanged(EXPANSION_EVENT)
 
         verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
     }
 
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_unlockedShade_setsFractionBasedOnDragDownAmount() {
-        whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
-        val scrimShadeTransitionDistance =
-            context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
-        val expectedFraction = EXPANSION_EVENT.dragDownPxAmount / scrimShadeTransitionDistance
-        verify(scrimController).setRawPanelExpansionFraction(expectedFraction)
-    }
-
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_largeDragDownAmount_fractionIsNotGreaterThan1() {
-        whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
-        val scrimShadeTransitionDistance =
-            context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(
-            EXPANSION_EVENT.copy(dragDownPxAmount = 100f * scrimShadeTransitionDistance))
-
-        verify(scrimController).setRawPanelExpansionFraction(1f)
-    }
-
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_negativeDragDownAmount_fractionIsNotLessThan0() {
-        whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(EXPANSION_EVENT.copy(dragDownPxAmount = -100f))
-
-        verify(scrimController).setRawPanelExpansionFraction(0f)
-    }
-
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_onLockedShade_setsFractionEqualToEventFraction() {
-        whenever(statusBarStateController.currentOrUpcomingState)
-            .thenReturn(StatusBarState.SHADE_LOCKED)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
-        verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
-    }
-
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_flagTrue_setsFractionEqualToEventFraction() {
-        whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
-                .thenReturn(true)
-        whenever(statusBarStateController.currentOrUpcomingState)
-            .thenReturn(StatusBarState.SHADE)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
-        verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
-    }
-
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_onKeyguard_setsFractionEqualToEventFraction() {
-        whenever(statusBarStateController.currentOrUpcomingState)
-            .thenReturn(StatusBarState.KEYGUARD)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
-        verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
-    }
-
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_panelOpen_setsFractionEqualToEventFraction() {
-        controller.onPanelStateChanged(STATE_OPEN)
-        whenever(statusBarStateController.currentOrUpcomingState)
-            .thenReturn(StatusBarState.KEYGUARD)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
-        verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
-    }
-
-    @Test
-    fun onPanelExpansionChanged_inSplitShade_panelClosed_setsFractionEqualToEventFraction() {
-        controller.onPanelStateChanged(STATE_CLOSED)
-        whenever(statusBarStateController.currentOrUpcomingState)
-            .thenReturn(StatusBarState.KEYGUARD)
-        setSplitShadeEnabled(true)
-
-        controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
-        verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
-    }
-
-    private fun setSplitShadeEnabled(enabled: Boolean) {
-        overrideResource(R.bool.config_use_split_notification_shade, enabled)
-        configurationController.notifyConfigurationChanged()
-    }
-
     companion object {
         val EXPANSION_EVENT =
             ShadeExpansionChangeEvent(
-                fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 10f)
+                fraction = 0.5f,
+                expanded = true,
+                tracking = true,
+                dragDownPxAmount = 10f
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 69d03d9..69b9525 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -18,16 +18,13 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -39,40 +36,32 @@
 @RunWith(JUnit4::class)
 class ShadeSceneViewModelTest : SysuiTestCase() {
 
-    private val testScope = TestScope()
-    private val utils = SceneTestUtils(this, testScope)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
+            sceneInteractor = sceneInteractor,
         )
 
     private val underTest =
         ShadeSceneViewModel(
             applicationScope = testScope.backgroundScope,
-            lockscreenSceneInteractorFactory =
-                object : LockscreenSceneInteractor.Factory {
-                    override fun create(containerName: String): LockscreenSceneInteractor {
-                        return utils.lockScreenSceneInteractor(
-                            authenticationInteractor = authenticationInteractor,
-                            sceneInteractor = sceneInteractor,
-                            bouncerInteractor =
-                                utils.bouncerInteractor(
-                                    authenticationInteractor = authenticationInteractor,
-                                    sceneInteractor = sceneInteractor,
-                                ),
-                        )
-                    }
-                },
-            containerName = SceneTestUtils.CONTAINER_1
+            authenticationInteractor = authenticationInteractor,
+            bouncerInteractor =
+                utils.bouncerInteractor(
+                    authenticationInteractor = authenticationInteractor,
+                    sceneInteractor = sceneInteractor,
+                ),
         )
 
     @Test
     fun upTransitionSceneKey_deviceLocked_lockScreen() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
@@ -81,8 +70,32 @@
     fun upTransitionSceneKey_deviceUnlocked_gone() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.unlockDevice()
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(true)
+
+            assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
+        testScope.runTest {
+            val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+            utils.authenticationRepository.setLockscreenEnabled(true)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
+
+            assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
+        testScope.runTest {
+            val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+            utils.authenticationRepository.setLockscreenEnabled(true)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
         }
@@ -90,9 +103,9 @@
     @Test
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.unlockDevice()
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
             underTest.onContentClicked()
@@ -103,9 +116,9 @@
     @Test
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
-            authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
-            authenticationInteractor.lockDevice()
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
             runCurrent()
 
             underTest.onContentClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt
new file mode 100644
index 0000000..f5a24ff
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shadow/DoubleShadowTextClockTest.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.shadow
+
+import android.content.Context
+import android.content.res.Resources
+import android.content.res.TypedArray
+import android.testing.AndroidTestingRunner
+import android.util.AttributeSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.shadow.DoubleShadowTextClock
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DoubleShadowTextClockTest : SysuiTestCase() {
+    @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock lateinit var resources: Resources
+
+    @Mock lateinit var attributes: TypedArray
+
+    private lateinit var context: Context
+    private var attrs: AttributeSet? = null
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        context = getContext()
+        whenever(attributes.getBoolean(R.styleable.DoubleShadowTextClock_removeTextDescent, false))
+            .thenReturn(true)
+    }
+
+    @Test
+    fun testAddingPaddingToBottomOfClockWhenConfigIsTrue() {
+        whenever(resources.getBoolean(R.bool.dream_overlay_complication_clock_bottom_padding))
+            .thenReturn(true)
+
+        val doubleShadowTextClock =
+            DoubleShadowTextClock(
+                resources = resources,
+                context = context,
+                attrs = attrs,
+                attributesInput = attributes
+            )
+        assertTrue(doubleShadowTextClock.paddingBottom > 0)
+    }
+
+    @Test
+    fun testRemovingPaddingToBottomOfClockWhenConfigIsFalse() {
+        whenever(resources.getBoolean(R.bool.dream_overlay_complication_clock_bottom_padding))
+            .thenReturn(false)
+
+        val doubleShadowTextClock =
+            DoubleShadowTextClock(
+                resources = resources,
+                context = context,
+                attrs = attrs,
+                attributesInput = attributes
+            )
+        assertTrue(doubleShadowTextClock.paddingBottom < 0)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 04c93cb..9495fdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -21,6 +21,8 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.TRANSIT_CLOCK
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockId
 import com.android.systemui.plugins.ClockMetadata
@@ -68,6 +70,7 @@
     private lateinit var fakeDefaultProvider: FakeClockPlugin
     private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
     private lateinit var registry: ClockRegistry
+    private val featureFlags = FakeFeatureFlags()
 
     companion object {
         private fun failFactory(clockId: ClockId): ClockController {
@@ -448,4 +451,44 @@
         val actual = ClockSettings.serialize(ClockSettings("ID", null))
         assertEquals(expected, actual)
     }
+
+    @Test
+    fun testTransitClockEnabled_hasTransitClock() {
+        testTransitClockFlag(true)
+    }
+
+    @Test
+    fun testTransitClockDisabled_noTransitClock() {
+        testTransitClockFlag(false)
+    }
+
+    private fun testTransitClockFlag(flag: Boolean) {
+        featureFlags.set(TRANSIT_CLOCK, flag)
+        registry.isTransitClockEnabled = featureFlags.isEnabled(TRANSIT_CLOCK)
+        val mockPluginLifecycle = mock<PluginLifecycleManager<ClockProviderPlugin>>()
+        val plugin = FakeClockPlugin()
+                .addClock("clock_1", "clock 1")
+                .addClock("DIGITAL_CLOCK_METRO", "metro clock")
+        pluginListener.onPluginLoaded(plugin, mockContext, mockPluginLifecycle)
+
+        val list = registry.getClocks()
+        if (flag) {
+            assertEquals(
+                    setOf(
+                            ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+                            ClockMetadata("clock_1", "clock 1"),
+                            ClockMetadata("DIGITAL_CLOCK_METRO", "metro clock")
+                    ),
+                    list.toSet()
+            )
+        } else {
+            assertEquals(
+                    setOf(
+                            ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+                            ClockMetadata("clock_1", "clock 1")
+                    ),
+                    list.toSet()
+            )
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
index b724175..0b1753f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
@@ -555,60 +555,6 @@
     }
 
     /**
-     * Ensures a subscription is predicated on its precondition.
-     */
-    @Test
-    public void testPrecondition() {
-        mCondition1.fakeUpdateCondition(false);
-        final Monitor.Callback callback =
-                mock(Monitor.Callback.class);
-
-        mCondition2.fakeUpdateCondition(false);
-
-        // Create a nested condition
-        mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(callback)
-                .addPrecondition(mCondition1)
-                .addCondition(mCondition2)
-                .build());
-
-        mExecutor.runAllReady();
-
-        // Ensure the nested condition callback is not called at all.
-        verify(callback, never()).onActiveChanged(anyBoolean());
-        verify(callback, never()).onConditionsChanged(anyBoolean());
-
-        // Update the condition to true and ensure that the nested condition is not triggered.
-        mCondition2.fakeUpdateCondition(true);
-        verify(callback, never()).onConditionsChanged(anyBoolean());
-        mCondition2.fakeUpdateCondition(false);
-
-        // Set precondition and make sure the inner condition becomes active and reports that
-        // conditions aren't met
-        mCondition1.fakeUpdateCondition(true);
-        mExecutor.runAllReady();
-
-        verify(callback).onActiveChanged(eq(true));
-        verify(callback).onConditionsChanged(eq(false));
-
-        Mockito.clearInvocations(callback);
-
-        // Update the condition and make sure the callback is updated.
-        mCondition2.fakeUpdateCondition(true);
-        mExecutor.runAllReady();
-
-        verify(callback).onConditionsChanged(true);
-
-        Mockito.clearInvocations(callback);
-        // Invalidate precondition and make sure callback is informed, but the last state is
-        // not affected.
-        mCondition1.fakeUpdateCondition(false);
-        mExecutor.runAllReady();
-
-        verify(callback).onActiveChanged(eq(false));
-        verify(callback, never()).onConditionsChanged(anyBoolean());
-    }
-
-    /**
      * Ensure preconditions are applied to every subscription added to a monitor.
      */
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index 58b44ae..19dc72d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -236,7 +236,8 @@
         `when`(precondition.conditionsMet()).thenReturn(true)
 
         // Given a session is created
-        val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+        val weatherView =
+            checkNotNull(controller.buildAndConnectWeatherView(fakeParent, customView))
         controller.stateChangeListener.onViewAttachedToWindow(weatherView)
         verify(smartspaceManager).createSmartspaceSession(any())
 
@@ -258,7 +259,8 @@
 
         // Given a session is created
         val customView = Mockito.mock(TestView::class.java)
-        val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+        val weatherView =
+            checkNotNull(controller.buildAndConnectWeatherView(fakeParent, customView))
         controller.stateChangeListener.onViewAttachedToWindow(weatherView)
         verify(smartspaceManager).createSmartspaceSession(any())
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index fe18fb5..280897d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -17,20 +17,19 @@
 
 package com.android.systemui.statusbar;
 
-import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
-
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 
 import android.app.ActivityManager;
 import android.app.Notification;
-import android.app.PendingIntent;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -45,7 +44,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
 
 import org.junit.After;
@@ -67,31 +65,27 @@
     private static final String TEST_PACKAGE_NAME = "test";
     private static final int TEST_UID = 0;
 
-    protected static final int TEST_MINIMUM_DISPLAY_TIME = 200;
-    protected static final int TEST_STICKY_DISPLAY_TIME = 1000;
-    protected static final int TEST_AUTO_DISMISS_TIME = 500;
+    protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
+    protected static final int TEST_AUTO_DISMISS_TIME = 600;
+    protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
     // Number of notifications to use in tests requiring multiple notifications
     private static final int TEST_NUM_NOTIFICATIONS = 4;
-    protected static final int TEST_TIMEOUT_TIME = 15000;
-    protected final Runnable TEST_TIMEOUT_RUNNABLE = () -> mTimedOut = true;
+    protected static final int TEST_TIMEOUT_TIME = 2_000;
+    protected final Runnable mTestTimeoutRunnable = () -> mTimedOut = true;
 
-    private AlertingNotificationManager mAlertingNotificationManager;
-
-    protected NotificationEntry mEntry;
     protected Handler mTestHandler;
-    private StatusBarNotification mSbn;
     protected boolean mTimedOut = false;
 
     @Mock protected ExpandableNotificationRow mRow;
 
-    private final class TestableAlertingNotificationManager extends AlertingNotificationManager {
+    private static class TestableAlertingNotificationManager extends AlertingNotificationManager {
         private AlertEntry mLastCreatedEntry;
 
         private TestableAlertingNotificationManager(Handler handler) {
-            super(mock(HeadsUpManagerLogger.class), handler);
+            super(new HeadsUpManagerLogger(logcatLogBuffer()), handler);
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mStickyDisplayTime = TEST_STICKY_DISPLAY_TIME;
             mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
+            mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
         }
 
         @Override
@@ -112,11 +106,11 @@
         }
     }
 
-    protected AlertingNotificationManager createAlertingNotificationManager(Handler handler) {
-        return new TestableAlertingNotificationManager(handler);
+    protected AlertingNotificationManager createAlertingNotificationManager() {
+        return new TestableAlertingNotificationManager(mTestHandler);
     }
 
-    protected StatusBarNotification createNewSbn(int id, Notification n) {
+    protected StatusBarNotification createSbn(int id, Notification n) {
         return new StatusBarNotification(
                 TEST_PACKAGE_NAME /* pkg */,
                 TEST_PACKAGE_NAME,
@@ -130,47 +124,53 @@
                 0 /* postTime */);
     }
 
-    protected StatusBarNotification createNewSbn(int id, Notification.Builder n) {
-        return new StatusBarNotification(
-                TEST_PACKAGE_NAME /* pkg */,
-                TEST_PACKAGE_NAME,
-                id,
-                null /* tag */,
-                TEST_UID,
-                0 /* initialPid */,
-                n.build(),
-                new UserHandle(ActivityManager.getCurrentUser()),
-                null /* overrideGroupKey */,
-                0 /* postTime */);
+    protected StatusBarNotification createSbn(int id, Notification.Builder n) {
+        return createSbn(id, n.build());
     }
 
-    protected StatusBarNotification createNewNotification(int id) {
-        Notification.Builder n = new Notification.Builder(mContext, "")
+    protected StatusBarNotification createSbn(int id) {
+        final Notification.Builder b = new Notification.Builder(mContext, "")
                 .setSmallIcon(R.drawable.ic_person)
                 .setContentTitle("Title")
                 .setContentText("Text");
-        return createNewSbn(id, n);
+        return createSbn(id, b);
     }
 
-    protected StatusBarNotification createStickySbn(int id) {
-        Notification stickyHun = new Notification.Builder(mContext, "")
-                .setSmallIcon(R.drawable.ic_person)
-                .setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
-                .build();
-        stickyHun.flags |= FLAG_FSI_REQUESTED_BUT_DENIED;
-        return createNewSbn(id, stickyHun);
+    protected NotificationEntry createEntry(int id, Notification n) {
+        return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
+    }
+
+    protected NotificationEntry createEntry(int id) {
+        return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
+    }
+
+    protected void verifyAlertingAtTime(AlertingNotificationManager anm, NotificationEntry entry,
+            boolean shouldBeAlerting, int whenToCheckAlertingMillis, String whenCondition) {
+        final Boolean[] wasAlerting = {null};
+        final Runnable checkAlerting =
+                () -> wasAlerting[0] = anm.isAlerting(entry.getKey());
+
+        mTestHandler.postDelayed(checkAlerting, whenToCheckAlertingMillis);
+        mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+        TestableLooper.get(this).processMessages(2);
+
+        assertFalse("Test timed out", mTimedOut);
+        if (shouldBeAlerting) {
+            assertTrue("Should still be alerting after " + whenCondition, wasAlerting[0]);
+        } else {
+            assertFalse("Should not still be alerting after " + whenCondition, wasAlerting[0]);
+        }
+        assertFalse("Should not still be alerting after processing",
+                anm.isAlerting(entry.getKey()));
     }
 
     @Before
     public void setUp() {
         mTestHandler = Handler.createAsync(Looper.myLooper());
-        mSbn = createNewNotification(0 /* id */);
-        mEntry = new NotificationEntryBuilder()
-                .setSbn(mSbn)
-                .build();
-        mEntry.setRow(mRow);
 
-        mAlertingNotificationManager = createAlertingNotificationManager(mTestHandler);
+        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_TIMEOUT_TIME);
     }
 
     @After
@@ -180,68 +180,81 @@
 
     @Test
     public void testShowNotification_addsEntry() {
-        mAlertingNotificationManager.showNotification(mEntry);
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
 
-        assertTrue(mAlertingNotificationManager.isAlerting(mEntry.getKey()));
-        assertTrue(mAlertingNotificationManager.hasNotifications());
-        assertEquals(mEntry, mAlertingNotificationManager.getEntry(mEntry.getKey()));
+        alm.showNotification(entry);
+
+        assertTrue(alm.isAlerting(entry.getKey()));
+        assertTrue(alm.hasNotifications());
+        assertEquals(entry, alm.getEntry(entry.getKey()));
     }
 
     @Test
     public void testShowNotification_autoDismisses() {
-        mAlertingNotificationManager.showNotification(mEntry);
-        mTestHandler.postDelayed(TEST_TIMEOUT_RUNNABLE, TEST_TIMEOUT_TIME);
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
 
-        // Wait for remove runnable and then process it immediately
-        TestableLooper.get(this).processMessages(1);
+        alm.showNotification(entry);
 
-        assertFalse("Test timed out", mTimedOut);
-        assertFalse(mAlertingNotificationManager.isAlerting(mEntry.getKey()));
+        verifyAlertingAtTime(alm, entry, false, TEST_AUTO_DISMISS_TIME * 3 / 2,
+                "auto dismiss time");
+
+        assertFalse(alm.isAlerting(entry.getKey()));
     }
 
     @Test
     public void testRemoveNotification_removeDeferred() {
-        mAlertingNotificationManager.showNotification(mEntry);
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
 
         // Try to remove but defer, since the notification has not been shown long enough.
-        mAlertingNotificationManager.removeNotification(
-                mEntry.getKey(), false /* releaseImmediately */);
+        final boolean removedImmediately = alm.removeNotification(entry.getKey(),
+                false /* releaseImmediately */);
 
-        assertTrue(mAlertingNotificationManager.isAlerting(mEntry.getKey()));
+        assertFalse(removedImmediately);
+        assertTrue(alm.isAlerting(entry.getKey()));
     }
 
     @Test
     public void testRemoveNotification_forceRemove() {
-        mAlertingNotificationManager.showNotification(mEntry);
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
 
         // Remove forcibly with releaseImmediately = true.
-        mAlertingNotificationManager.removeNotification(
-                mEntry.getKey(), true /* releaseImmediately */);
+        final boolean removedImmediately = alm.removeNotification(entry.getKey(),
+                true /* releaseImmediately */);
 
-        assertFalse(mAlertingNotificationManager.isAlerting(mEntry.getKey()));
+        assertTrue(removedImmediately);
+        assertFalse(alm.isAlerting(entry.getKey()));
     }
 
     @Test
     public void testReleaseAllImmediately() {
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
         for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
-            StatusBarNotification sbn = createNewNotification(i);
-            NotificationEntry entry = new NotificationEntryBuilder()
-                    .setSbn(sbn)
-                    .build();
+            final NotificationEntry entry = createEntry(i);
             entry.setRow(mRow);
-            mAlertingNotificationManager.showNotification(entry);
+            alm.showNotification(entry);
         }
 
-        mAlertingNotificationManager.releaseAllImmediately();
+        alm.releaseAllImmediately();
 
-        assertEquals(0, mAlertingNotificationManager.getAllEntries().count());
+        assertEquals(0, alm.getAllEntries().count());
     }
 
     @Test
     public void testCanRemoveImmediately_notShownLongEnough() {
-        mAlertingNotificationManager.showNotification(mEntry);
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
 
         // The entry has just been added so we should not remove immediately.
-        assertFalse(mAlertingNotificationManager.canRemoveImmediately(mEntry.getKey()));
+        assertFalse(alm.canRemoveImmediately(entry.getKey()));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 1643e17..b04d5d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -517,6 +517,21 @@
     }
 
     @Test
+    public void testConfirmImmersivePrompt() {
+        mCommandQueue.confirmImmersivePrompt();
+        waitForIdleSync();
+        verify(mCallbacks).confirmImmersivePrompt();
+    }
+
+    @Test
+    public void testImmersiveModeChanged() {
+        final int displayAreaId = 10;
+        mCommandQueue.immersiveModeChanged(displayAreaId, true);
+        waitForIdleSync();
+        verify(mCallbacks).immersiveModeChanged(displayAreaId, true);
+    }
+
+    @Test
     public void testShowRearDisplayDialog() {
         final int currentBaseState = 1;
         mCommandQueue.showRearDisplayDialog(currentBaseState);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
index 109f185..22c9e45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
@@ -76,5 +76,6 @@
         mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID);
 
         verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt());
+        verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
index ea822aa..a3ecde0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
@@ -75,5 +75,6 @@
         mKeyboardShortcuts.toggle(mContext, DEVICE_ID);
 
         verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt());
+        verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
new file mode 100644
index 0000000..d44846e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
+
+import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR;
+import static com.android.systemui.flags.Flags.KEYGUARD_TALKBACK_FIX;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
+import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.app.Instrumentation;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyResourcesManager;
+import android.app.trust.TrustManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Looper;
+import android.os.UserManager;
+import android.provider.DeviceConfig;
+import android.testing.TestableLooper;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.logging.KeyguardLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.FaceHelpMessageDeferral;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.keyguard.KeyguardIndication;
+import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
+import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.keyguard.util.IndicationHelper;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.util.wakelock.WakeLockFake;
+
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class KeyguardIndicationControllerBaseTest extends SysuiTestCase {
+
+    protected static final String ORGANIZATION_NAME = "organization";
+
+    protected static final ComponentName DEVICE_OWNER_COMPONENT = new ComponentName(
+            "com.android.foo",
+            "bar");
+
+    protected static final int TEST_STRING_RES = R.string.keyguard_indication_trust_unlocked;
+
+    protected String mDisclosureWithOrganization;
+    protected String mDisclosureGeneric;
+    protected String mFinancedDisclosureWithOrganization;
+
+    @Mock
+    protected DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    protected DevicePolicyResourcesManager mDevicePolicyResourcesManager;
+    @Mock
+    protected ViewGroup mIndicationArea;
+    @Mock
+    protected KeyguardStateController mKeyguardStateController;
+    @Mock
+    protected KeyguardIndicationTextView mIndicationAreaBottom;
+    @Mock
+    protected BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    protected StatusBarStateController mStatusBarStateController;
+    @Mock
+    protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock
+    protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Mock
+    protected UserManager mUserManager;
+    @Mock
+    protected IBatteryStats mIBatteryStats;
+    @Mock
+    protected DockManager mDockManager;
+    @Mock
+    protected KeyguardIndicationRotateTextViewController mRotateTextViewController;
+    @Mock
+    protected FalsingManager mFalsingManager;
+    @Mock
+    protected LockPatternUtils mLockPatternUtils;
+    @Mock
+    protected KeyguardBypassController mKeyguardBypassController;
+    @Mock
+    protected AccessibilityManager mAccessibilityManager;
+    @Mock
+    protected FaceHelpMessageDeferral mFaceHelpMessageDeferral;
+    @Mock
+    protected AlternateBouncerInteractor mAlternateBouncerInteractor;
+    @Mock
+    protected ScreenLifecycle mScreenLifecycle;
+    @Mock
+    protected AuthController mAuthController;
+    @Mock
+    protected AlarmManager mAlarmManager;
+    @Mock
+    protected UserTracker mUserTracker;
+    @Captor
+    protected ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
+    @Captor
+    protected ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
+    @Captor
+    protected ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
+    @Captor
+    protected ArgumentCaptor<KeyguardIndication> mKeyguardIndicationCaptor;
+    @Captor
+    protected ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
+    @Captor
+    protected ArgumentCaptor<KeyguardStateController.Callback>
+            mKeyguardStateControllerCallbackCaptor;
+    @Captor
+    protected ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
+    protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
+    protected KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
+    protected StatusBarStateController.StateListener mStatusBarStateListener;
+    protected ScreenLifecycle.Observer mScreenObserver;
+    protected BroadcastReceiver mBroadcastReceiver;
+    protected IndicationHelper mIndicationHelper;
+    protected FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+    protected TestableLooper mTestableLooper;
+    protected final int mCurrentUserId = 1;
+
+    protected KeyguardIndicationTextView mTextView; // AOD text
+
+    protected KeyguardIndicationController mController;
+    protected WakeLockFake.Builder mWakeLockBuilder;
+    protected WakeLockFake mWakeLock;
+    protected Instrumentation mInstrumentation;
+    protected FakeFeatureFlags mFlags;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mTestableLooper = TestableLooper.get(this);
+        mTextView = new KeyguardIndicationTextView(mContext);
+        mTextView.setAnimationsEnabled(false);
+
+        // TODO(b/259908270): remove
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+                DevicePolicyManager.ADD_ISFINANCED_DEVICE_FLAG, "true",
+                /* makeDefault= */ false);
+        mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager);
+        mContext.addMockSystemService(UserManager.class, mUserManager);
+        mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
+        mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class));
+        mDisclosureWithOrganization = mContext.getString(R.string.do_disclosure_with_name,
+                ORGANIZATION_NAME);
+        mDisclosureGeneric = mContext.getString(R.string.do_disclosure_generic);
+        mFinancedDisclosureWithOrganization = mContext.getString(
+                R.string.do_financed_disclosure_with_name, ORGANIZATION_NAME);
+
+        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+        when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_ON);
+        when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
+
+        when(mIndicationArea.findViewById(R.id.keyguard_indication_text_bottom))
+                .thenReturn(mIndicationAreaBottom);
+        when(mIndicationArea.findViewById(R.id.keyguard_indication_text)).thenReturn(mTextView);
+
+        when(mDevicePolicyManager.getResources()).thenReturn(mDevicePolicyResourcesManager);
+        when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
+                .thenReturn(DEVICE_OWNER_COMPONENT);
+        when(mDevicePolicyManager.isFinancedDevice()).thenReturn(false);
+        // TODO(b/259908270): remove
+        when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
+                .thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
+
+        when(mDevicePolicyResourcesManager.getString(anyString(), any()))
+                .thenReturn(mDisclosureGeneric);
+        when(mDevicePolicyResourcesManager.getString(anyString(), any(), anyString()))
+                .thenReturn(mDisclosureWithOrganization);
+        when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
+
+        mIndicationHelper = new IndicationHelper(mKeyguardUpdateMonitor);
+
+        mWakeLock = new WakeLockFake();
+        mWakeLockBuilder = new WakeLockFake.Builder(mContext);
+        mWakeLockBuilder.setWakeLock(mWakeLock);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mTextView.setAnimationsEnabled(true);
+        if (mController != null) {
+            mController.destroy();
+            mController = null;
+        }
+    }
+
+    protected void createController() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        mFlags = new FakeFeatureFlags();
+        mFlags.set(KEYGUARD_TALKBACK_FIX, true);
+        mFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
+        mFlags.set(FACE_AUTH_REFACTOR, false);
+        mController = new KeyguardIndicationController(
+                mContext,
+                mTestableLooper.getLooper(),
+                mWakeLockBuilder,
+                mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
+                mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
+                mUserManager, mExecutor, mExecutor, mFalsingManager,
+                mAuthController, mLockPatternUtils, mScreenLifecycle,
+                mKeyguardBypassController, mAccessibilityManager,
+                mFaceHelpMessageDeferral, mock(KeyguardLogger.class),
+                mAlternateBouncerInteractor,
+                mAlarmManager,
+                mUserTracker,
+                mock(BouncerMessageInteractor.class),
+                mFlags,
+                mIndicationHelper,
+                KeyguardInteractorFactory.create(mFlags).getKeyguardInteractor()
+        );
+        mController.init();
+        mController.setIndicationArea(mIndicationArea);
+        verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
+        mStatusBarStateListener = mStatusBarStateListenerCaptor.getValue();
+        verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiverCaptor.capture(), any());
+        mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
+        mController.mRotateTextViewController = mRotateTextViewController;
+        mController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
+        clearInvocations(mIBatteryStats);
+
+        verify(mKeyguardStateController).addCallback(
+                mKeyguardStateControllerCallbackCaptor.capture());
+        mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+
+        verify(mKeyguardUpdateMonitor).registerCallback(
+                mKeyguardUpdateMonitorCallbackCaptor.capture());
+        mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
+
+        verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
+        mScreenObserver = mScreenObserverCaptor.getValue();
+
+        mExecutor.runAllReady();
+        reset(mRotateTextViewController);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index cd0c1352..8cf005d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -11,12 +11,11 @@
  * 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.statusbar;
 
-import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
 import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
@@ -26,7 +25,6 @@
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
-import static com.android.systemui.flags.Flags.KEYGUARD_TALKBACK_FIX;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
@@ -38,7 +36,6 @@
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_OFF;
-import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
 import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_TURNING_ON;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -60,70 +57,28 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-import android.app.AlarmManager;
-import android.app.Instrumentation;
-import android.app.admin.DevicePolicyManager;
-import android.app.admin.DevicePolicyResourcesManager;
-import android.app.trust.TrustManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.graphics.Color;
 import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricSourceType;
-import android.hardware.fingerprint.FingerprintManager;
 import android.os.BatteryManager;
-import android.os.Looper;
 import android.os.RemoteException;
-import android.os.UserManager;
-import android.provider.DeviceConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityManager;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.TrustGrantFlags;
-import com.android.keyguard.logging.KeyguardLogger;
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.biometrics.FaceHelpMessageDeferral;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.KeyguardIndication;
 import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
-import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.util.wakelock.WakeLockFake;
 
-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 java.text.NumberFormat;
 import java.util.Collections;
@@ -134,201 +89,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class KeyguardIndicationControllerTest extends SysuiTestCase {
-
-    private static final String ORGANIZATION_NAME = "organization";
-
-    private static final ComponentName DEVICE_OWNER_COMPONENT = new ComponentName("com.android.foo",
-            "bar");
-
-    private static final int TEST_STRING_RES = R.string.keyguard_indication_trust_unlocked;
-
-    private String mDisclosureWithOrganization;
-    private String mDisclosureGeneric;
-    private String mFinancedDisclosureWithOrganization;
-
-    @Mock
-    private DevicePolicyManager mDevicePolicyManager;
-    @Mock
-    private DevicePolicyResourcesManager mDevicePolicyResourcesManager;
-    @Mock
-    private ViewGroup mIndicationArea;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-    @Mock
-    private KeyguardIndicationTextView mIndicationAreaBottom;
-    @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-    @Mock
-    private StatusBarStateController mStatusBarStateController;
-    @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
-    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock
-    private UserManager mUserManager;
-    @Mock
-    private IBatteryStats mIBatteryStats;
-    @Mock
-    private DockManager mDockManager;
-    @Mock
-    private KeyguardIndicationRotateTextViewController mRotateTextViewController;
-    @Mock
-    private FalsingManager mFalsingManager;
-    @Mock
-    private LockPatternUtils mLockPatternUtils;
-    @Mock
-    private KeyguardBypassController mKeyguardBypassController;
-    @Mock
-    private AccessibilityManager mAccessibilityManager;
-    @Mock
-    private FaceHelpMessageDeferral mFaceHelpMessageDeferral;
-    @Mock
-    private AlternateBouncerInteractor mAlternateBouncerInteractor;
-    @Mock
-    private ScreenLifecycle mScreenLifecycle;
-    @Mock
-    private AuthController mAuthController;
-    @Mock
-    private AlarmManager mAlarmManager;
-    @Mock
-    private UserTracker mUserTracker;
-    @Captor
-    private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
-    @Captor
-    private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
-    @Captor
-    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
-    @Captor
-    private ArgumentCaptor<KeyguardIndication> mKeyguardIndicationCaptor;
-    @Captor
-    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
-    @Captor
-    private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallbackCaptor;
-    @Captor
-    private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
-    private KeyguardStateController.Callback mKeyguardStateControllerCallback;
-    private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
-    private StatusBarStateController.StateListener mStatusBarStateListener;
-    private ScreenLifecycle.Observer mScreenObserver;
-    private BroadcastReceiver mBroadcastReceiver;
-    private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
-    private TestableLooper mTestableLooper;
-    private final int mCurrentUserId = 1;
-
-    private KeyguardIndicationTextView mTextView; // AOD text
-
-    private KeyguardIndicationController mController;
-    private WakeLockFake.Builder mWakeLockBuilder;
-    private WakeLockFake mWakeLock;
-    private Instrumentation mInstrumentation;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mTestableLooper = TestableLooper.get(this);
-        mTextView = new KeyguardIndicationTextView(mContext);
-        mTextView.setAnimationsEnabled(false);
-
-        // TODO(b/259908270): remove
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
-                DevicePolicyManager.ADD_ISFINANCED_DEVICE_FLAG, "true",
-                /* makeDefault= */ false);
-        mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager);
-        mContext.addMockSystemService(UserManager.class, mUserManager);
-        mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
-        mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class));
-        mDisclosureWithOrganization = mContext.getString(R.string.do_disclosure_with_name,
-                ORGANIZATION_NAME);
-        mDisclosureGeneric = mContext.getString(R.string.do_disclosure_generic);
-        mFinancedDisclosureWithOrganization = mContext.getString(
-                R.string.do_financed_disclosure_with_name, ORGANIZATION_NAME);
-
-        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
-        when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_ON);
-        when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
-
-        when(mIndicationArea.findViewById(R.id.keyguard_indication_text_bottom))
-                .thenReturn(mIndicationAreaBottom);
-        when(mIndicationArea.findViewById(R.id.keyguard_indication_text)).thenReturn(mTextView);
-
-        when(mDevicePolicyManager.getResources()).thenReturn(mDevicePolicyResourcesManager);
-        when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
-                .thenReturn(DEVICE_OWNER_COMPONENT);
-        when(mDevicePolicyManager.isFinancedDevice()).thenReturn(false);
-        // TODO(b/259908270): remove
-        when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
-                .thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
-
-
-        when(mDevicePolicyResourcesManager.getString(anyString(), any()))
-                .thenReturn(mDisclosureGeneric);
-        when(mDevicePolicyResourcesManager.getString(anyString(), any(), anyString()))
-                .thenReturn(mDisclosureWithOrganization);
-        when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
-
-        mWakeLock = new WakeLockFake();
-        mWakeLockBuilder = new WakeLockFake.Builder(mContext);
-        mWakeLockBuilder.setWakeLock(mWakeLock);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mTextView.setAnimationsEnabled(true);
-        if (mController != null) {
-            mController.destroy();
-            mController = null;
-        }
-    }
-
-    private void createController() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
-        FakeFeatureFlags flags = new FakeFeatureFlags();
-        flags.set(KEYGUARD_TALKBACK_FIX, true);
-        mController = new KeyguardIndicationController(
-                mContext,
-                mTestableLooper.getLooper(),
-                mWakeLockBuilder,
-                mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
-                mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
-                mUserManager, mExecutor, mExecutor, mFalsingManager,
-                mAuthController, mLockPatternUtils, mScreenLifecycle,
-                mKeyguardBypassController, mAccessibilityManager,
-                mFaceHelpMessageDeferral, mock(KeyguardLogger.class),
-                mAlternateBouncerInteractor,
-                mAlarmManager,
-                mUserTracker,
-                flags
-        );
-        mController.init();
-        mController.setIndicationArea(mIndicationArea);
-        verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
-        mStatusBarStateListener = mStatusBarStateListenerCaptor.getValue();
-        verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiverCaptor.capture(), any());
-        mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
-        mController.mRotateTextViewController = mRotateTextViewController;
-        mController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
-        clearInvocations(mIBatteryStats);
-
-        verify(mKeyguardStateController).addCallback(
-                mKeyguardStateControllerCallbackCaptor.capture());
-        mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
-
-        verify(mKeyguardUpdateMonitor).registerCallback(
-                mKeyguardUpdateMonitorCallbackCaptor.capture());
-        mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
-
-        verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
-        mScreenObserver = mScreenObserverCaptor.getValue();
-
-        mExecutor.runAllReady();
-        reset(mRotateTextViewController);
-    }
-
+public class KeyguardIndicationControllerTest extends KeyguardIndicationControllerBaseTest {
     @Test
     public void createController_setIndicationAreaAgain_destroysPreviousRotateTextViewController() {
         // GIVEN a controller with a mocked rotate text view controlller
@@ -803,36 +564,22 @@
     }
 
     @Test
-    public void transientIndication_visibleWhenDozing_ignoresFingerprintCancellation() {
+    public void transientIndication_visibleWhenDozing_ignoresFingerprintErrorMsg() {
         createController();
-
         mController.setVisible(true);
         reset(mRotateTextViewController);
+
+        // WHEN a fingerprint error user cancelled message is received
         mController.getKeyguardCallback().onBiometricError(
-                FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED, "foo",
-                BiometricSourceType.FINGERPRINT);
-        mController.getKeyguardCallback().onBiometricError(
-                FingerprintManager.FINGERPRINT_ERROR_CANCELED, "bar",
+                BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED, "foo",
                 BiometricSourceType.FINGERPRINT);
 
+        // THEN no message is shown
         verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
         verifyNoMessage(INDICATION_TYPE_TRANSIENT);
     }
 
     @Test
-    public void transientIndication_visibleWhenDozing_ignoresPowerPressed() {
-        createController();
-
-        mController.setVisible(true);
-        reset(mRotateTextViewController);
-        mController.getKeyguardCallback().onBiometricError(
-                FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, "foo",
-                BiometricSourceType.FINGERPRINT);
-
-        verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
-    }
-
-    @Test
     public void transientIndication_swipeUpToRetry() {
         createController();
         String message = mContext.getString(R.string.keyguard_retry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..cdc7520
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class KeyguardIndicationControllerWithCoroutinesTest : KeyguardIndicationControllerBaseTest() {
+    @Test
+    fun testIndicationAreaVisibility_onLockscreenHostedDreamStateChanged() =
+        runBlocking(IMMEDIATE) {
+            // GIVEN starting state for keyguard indication and wallpaper dream enabled
+            createController()
+            mFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, true)
+            mController.setVisible(true)
+
+            // THEN indication area is visible
+            verify(mIndicationArea, times(2)).visibility = View.VISIBLE
+
+            // WHEN the device is dreaming with lockscreen hosted dream
+            mController.mIsActiveDreamLockscreenHostedCallback.accept(
+                true /* isActiveDreamLockscreenHosted */
+            )
+            mExecutor.runAllReady()
+
+            // THEN the indication area is hidden
+            verify(mIndicationArea).visibility = View.GONE
+
+            // WHEN the device stops dreaming with lockscreen hosted dream
+            mController.mIsActiveDreamLockscreenHostedCallback.accept(
+                false /* isActiveDreamLockscreenHosted */
+            )
+            mExecutor.runAllReady()
+
+            // THEN indication area is set visible
+            verify(mIndicationArea, times(3)).visibility = View.VISIBLE
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
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 2351f760..4a2518a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.statusbar
 
+import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
@@ -8,14 +9,21 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.qs.QS
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper
 import com.android.systemui.statusbar.notification.stack.AmbientState
@@ -25,7 +33,13 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
 import com.android.systemui.statusbar.phone.ScrimController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
 import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import org.junit.After
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotNull
@@ -56,8 +70,11 @@
 @SmallTest
 @RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
 class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
 
+    private val testScope = TestScope(StandardTestDispatcher())
+
     lateinit var transitionController: LockscreenShadeTransitionController
     lateinit var row: ExpandableNotificationRow
     @Mock lateinit var statusbarStateController: SysuiStatusBarStateController
@@ -83,12 +100,33 @@
     @Mock lateinit var qsTransitionController: LockscreenShadeQsTransitionController
     @Mock lateinit var activityStarter: ActivityStarter
     @Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback
+    private val disableFlagsRepository = FakeDisableFlagsRepository()
+    private val keyguardRepository = FakeKeyguardRepository()
+    private val shadeInteractor = ShadeInteractor(
+        testScope.backgroundScope,
+        disableFlagsRepository,
+        keyguardRepository,
+        userSetupRepository = FakeUserSetupRepository(),
+        deviceProvisionedController = mock(),
+        userInteractor = mock(),
+    )
+    private val powerInteractor = PowerInteractor(
+        FakePowerRepository(),
+        keyguardRepository,
+        FalsingCollectorFake(),
+        screenOffAnimationController = mock(),
+        statusBarStateController = mock(),
+    )
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
     private val configurationController = FakeConfigurationController()
 
     @Before
     fun setup() {
+        // By default, have the shade enabled
+        disableFlagsRepository.disableFlags.value = DisableFlagsModel()
+        testScope.runCurrent()
+
         val helper = NotificationTestHelper(
                 mContext,
                 mDependency,
@@ -129,6 +167,8 @@
                 qsTransitionControllerFactory = { qsTransitionController },
                 activityStarter = activityStarter,
                 shadeRepository = FakeShadeRepository(),
+                shadeInteractor = shadeInteractor,
+                powerInteractor = powerInteractor,
             )
         transitionController.addCallback(transitionControllerCallback)
         whenever(nsslController.view).thenReturn(stackscroller)
@@ -203,7 +243,10 @@
 
     @Test
     fun testDontGoWhenShadeDisabled() {
-        whenever(mCentralSurfaces.isShadeDisabled).thenReturn(true)
+        disableFlagsRepository.disableFlags.value = DisableFlagsModel(
+            disable2 = DISABLE2_NOTIFICATION_SHADE,
+        )
+        testScope.runCurrent()
         transitionController.goToLockedShade(null)
         verify(statusbarStateController, never()).setState(anyInt())
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
index 724ea02..e4da53a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
@@ -47,7 +47,7 @@
         processor = MediaArtworkProcessor()
 
         val point = Point()
-        context.display.getSize(point)
+        checkNotNull(context.display).getSize(point)
         screenWidth = point.x
         screenHeight = point.y
     }
@@ -106,4 +106,4 @@
         // THEN the processed bitmap is null
         assertThat(background).isNull()
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index ced0734..764f7b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
+
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.Mockito.mock;
@@ -33,25 +35,21 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.RemoteInputControllerLogger;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 
-import dagger.Lazy;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Optional;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -69,6 +67,7 @@
     @Mock private RemoteInputUriController mRemoteInputUriController;
     @Mock private NotificationClickNotifier mClickNotifier;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+    @Mock private PowerInteractor mPowerInteractor;
 
     private TestableNotificationRemoteInputManager mRemoteInputManager;
     private NotificationEntry mEntry;
@@ -82,12 +81,12 @@
                 mLockscreenUserManager,
                 mSmartReplyController,
                 mVisibilityProvider,
-                () -> Optional.of(mock(CentralSurfaces.class)),
+                mPowerInteractor,
                 mStateController,
                 mRemoteInputUriController,
-                mock(RemoteInputControllerLogger.class),
+                new RemoteInputControllerLogger(logcatLogBuffer()),
                 mClickNotifier,
-                mock(ActionClickLogger.class),
+                new ActionClickLogger(logcatLogBuffer()),
                 mock(DumpManager.class));
         mEntry = new NotificationEntryBuilder()
                 .setPkg(TEST_PACKAGE_NAME)
@@ -140,7 +139,7 @@
                 NotificationLockscreenUserManager lockscreenUserManager,
                 SmartReplyController smartReplyController,
                 NotificationVisibilityProvider visibilityProvider,
-                Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
+                PowerInteractor powerInteractor,
                 StatusBarStateController statusBarStateController,
                 RemoteInputUriController remoteInputUriController,
                 RemoteInputControllerLogger remoteInputControllerLogger,
@@ -153,7 +152,7 @@
                     lockscreenUserManager,
                     smartReplyController,
                     visibilityProvider,
-                    centralSurfacesOptionalLazy,
+                    powerInteractor,
                     statusBarStateController,
                     remoteInputUriController,
                     remoteInputControllerLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index 1b1f4e4..8d016e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -45,6 +45,7 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
+import android.view.ViewGroup;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -65,6 +66,8 @@
 @RunWith(AndroidJUnit4.class)
 public class StatusBarIconViewTest extends SysuiTestCase {
 
+    private static final int TEST_STATUS_BAR_HEIGHT = 150;
+
     @Rule
     public ExpectedException mThrown = ExpectedException.none();
 
@@ -184,4 +187,218 @@
 
         // no crash, good
     }
+
+    @Test
+    public void testUpdateIconScale_constrainedDrawableSizeLessThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // the icon view layout size would be 60x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, dpIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 50x50. When put the drawable into iconView whose
+        // layout size is 60x150, the drawable size would not be constrained and thus keep 50x50
+        setIconDrawableWithSize(/* width= */ 50, /* height= */ 50);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN both the constrained drawable width/height are less than dpIconSize,
+        // THEN the icon is scaled down from dpIconSize to fit the dpDrawingSize
+        float scaleToFitDrawingSize = (float) dpDrawingSize / dpIconSize;
+        assertEquals(scaleToFitDrawingSize, mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_constrainedDrawableHeightLargerThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // the icon view layout size would be 60x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, dpIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 50x100. When put the drawable into iconView whose
+        // layout size is 60x150, the drawable size would not be constrained and thus keep 50x100
+        setIconDrawableWithSize(/* width= */ 50, /* height= */ 100);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN constrained drawable larger side length 100 >= dpIconSize
+        // THEN the icon is scaled down from larger side length 100 to ensure both side
+        //      length fit in dpDrawingSize.
+        float scaleToFitDrawingSize = (float) dpDrawingSize / 100;
+        assertEquals(scaleToFitDrawingSize, mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_constrainedDrawableWidthLargerThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // the icon view layout size would be 60x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, dpIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 100x50. When put the drawable into iconView whose
+        // layout size is 60x150, the drawable size would be constrained to 60x30
+        setIconDrawableWithSize(/* width= */ 100, /* height= */ 50);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN constrained drawable larger side length 60 >= dpIconSize
+        // THEN the icon is scaled down from larger side length 60 to ensure both side
+        //      length fit in dpDrawingSize.
+        float scaleToFitDrawingSize = (float) dpDrawingSize / 60;
+        assertEquals(scaleToFitDrawingSize, mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_smallerFontAndConstrainedDrawableSizeLessThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // smaller font scaling causes the spIconSize < dpIconSize
+        int spIconSize = 40;
+        // the icon view layout size would be 40x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 50x50. When put the drawable into iconView whose
+        // layout size is 40x150, the drawable size would be constrained to 40x40
+        setIconDrawableWithSize(/* width= */ 50, /* height= */ 50);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN both the constrained drawable width/height are less than dpIconSize,
+        // THEN the icon is scaled down from dpIconSize to fit the dpDrawingSize
+        float scaleToFitDrawingSize = (float) dpDrawingSize / dpIconSize;
+        // THEN the scaled icon should be scaled down further to fit spIconSize
+        float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+        assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize, mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_smallerFontAndConstrainedDrawableHeightLargerThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // smaller font scaling causes the spIconSize < dpIconSize
+        int spIconSize = 40;
+        // the icon view layout size would be 40x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 50x100. When put the drawable into iconView whose
+        // layout size is 40x150, the drawable size would be constrained to 40x80
+        setIconDrawableWithSize(/* width= */ 50, /* height= */ 100);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN constrained drawable larger side length 80 >= dpIconSize
+        // THEN the icon is scaled down from larger side length 80 to ensure both side
+        //      length fit in dpDrawingSize.
+        float scaleToFitDrawingSize = (float) dpDrawingSize / 80;
+        // THEN the scaled icon should be scaled down further to fit spIconSize
+        float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+        assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize, mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_largerFontAndConstrainedDrawableSizeLessThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // larger font scaling causes the spIconSize > dpIconSize
+        int spIconSize = 80;
+        // the icon view layout size would be 80x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 50x50. When put the drawable into iconView whose
+        // layout size is 80x150, the drawable size would not be constrained and thus keep 50x50
+        setIconDrawableWithSize(/* width= */ 50, /* height= */ 50);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN both the constrained drawable width/height are less than dpIconSize,
+        // THEN the icon is scaled down from dpIconSize to fit the dpDrawingSize
+        float scaleToFitDrawingSize = (float) dpDrawingSize / dpIconSize;
+        // THEN the scaled icon should be scaled up to fit spIconSize
+        float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+        assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize, mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_largerFontAndConstrainedDrawableHeightLargerThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // larger font scaling causes the spIconSize > dpIconSize
+        int spIconSize = 80;
+        // the icon view layout size would be 80x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 50x100. When put the drawable into iconView whose
+        // layout size is 80x150, the drawable size would not be constrained and thus keep 50x100
+        setIconDrawableWithSize(/* width= */ 50, /* height= */ 100);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN constrained drawable larger side length 100 >= dpIconSize
+        // THEN the icon is scaled down from larger side length 100 to ensure both side
+        //      length fit in dpDrawingSize.
+        float scaleToFitDrawingSize = (float) dpDrawingSize / 100;
+        // THEN the scaled icon should be scaled up to fit spIconSize
+        float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+        assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize, mIconView.getIconScale(), 0.01f);
+    }
+
+    @Test
+    public void testUpdateIconScale_largerFontAndConstrainedDrawableWidthLargerThanDpIconSize() {
+        int dpIconSize = 60;
+        int dpDrawingSize = 30;
+        // larger font scaling causes the spIconSize > dpIconSize
+        int spIconSize = 80;
+        // the icon view layout size would be 80x150
+        //   (the height is always 150 due to TEST_STATUS_BAR_HEIGHT)
+        setUpIconView(dpIconSize, dpDrawingSize, spIconSize);
+        mIconView.setNotification(mock(StatusBarNotification.class));
+        // the raw drawable size is 100x50. When put the drawable into iconView whose
+        // layout size is 80x150, the drawable size would not be constrained and thus keep 80x40
+        setIconDrawableWithSize(/* width= */ 100, /* height= */ 50);
+        mIconView.maybeUpdateIconScaleDimens();
+
+        // WHEN constrained drawable larger side length 80 >= dpIconSize
+        // THEN the icon is scaled down from larger side length 80 to ensure both side
+        //      length fit in dpDrawingSize.
+        float scaleToFitDrawingSize = (float) dpDrawingSize / 80;
+        // THEN the scaled icon should be scaled up to fit spIconSize
+        float scaleToFitSpIconSize = (float) spIconSize / dpIconSize;
+        assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize,
+                mIconView.getIconScale(), 0.01f);
+    }
+
+    /**
+     * Setup iconView dimens for testing. The result icon view layout width would
+     * be spIconSize and height would be 150.
+     *
+     * @param dpIconSize corresponding to status_bar_icon_size
+     * @param dpDrawingSize corresponding to status_bar_icon_drawing_size
+     * @param spIconSize corresponding to status_bar_icon_size_sp under different font scaling
+     */
+    private void setUpIconView(int dpIconSize, int dpDrawingSize, int spIconSize) {
+        mIconView.setIncreasedSize(false);
+        mIconView.mOriginalStatusBarIconSize = dpIconSize;
+        mIconView.mStatusBarIconDrawingSize = dpDrawingSize;
+
+        mIconView.mNewStatusBarIconSize = spIconSize;
+        mIconView.mScaleToFitNewIconSize = (float) spIconSize / dpIconSize;
+
+        // the layout width would be spIconSize + 2 * iconPadding, and we assume iconPadding
+        // is 0 here.
+        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(spIconSize, TEST_STATUS_BAR_HEIGHT);
+        mIconView.setLayoutParams(lp);
+    }
+
+    private void setIconDrawableWithSize(int width, int height) {
+        Bitmap bitmap = Bitmap.createBitmap(
+                width, height, Bitmap.Config.ARGB_8888);
+        Icon icon = Icon.createWithBitmap(bitmap);
+        mStatusBarIcon = new StatusBarIcon(UserHandle.ALL, "mockPackage",
+                icon, 0, 0, "");
+        // Since we only want to verify icon scale logic here, we directly use
+        // {@link StatusBarIconView#setImageDrawable(Drawable)} to set the image drawable
+        // to iconView instead of call {@link StatusBarIconView#set(StatusBarIcon)}. It's to prevent
+        // the icon drawable size being scaled down when internally calling
+        // {@link StatusBarIconView#getIcon(Context,Context,StatusBarIcon)}.
+        mIconView.setImageDrawable(icon.loadDrawable(mContext));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
index ad908e7..aab4bc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt
@@ -6,6 +6,8 @@
 import android.os.VibrationEffect
 import android.os.Vibrator
 import android.testing.AndroidTestingRunner
+import android.view.HapticFeedbackConstants
+import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.eq
@@ -33,6 +35,7 @@
 
     @Mock lateinit var vibrator: Vibrator
     @Mock lateinit var executor: Executor
+    @Mock lateinit var view: View
     @Captor lateinit var backgroundTaskCaptor: ArgumentCaptor<Runnable>
     lateinit var vibratorHelper: VibratorHelper
 
@@ -72,6 +75,21 @@
     }
 
     @Test
+    fun testPerformHapticFeedback() {
+        val constant = HapticFeedbackConstants.CONFIRM
+        vibratorHelper.performHapticFeedback(view, constant)
+        verify(view).performHapticFeedback(eq(constant))
+    }
+
+    @Test
+    fun testPerformHapticFeedback_withFlags() {
+        val constant = HapticFeedbackConstants.CONFIRM
+        val flag = HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
+        vibratorHelper.performHapticFeedback(view, constant, flag)
+        verify(view).performHapticFeedback(eq(constant), eq(flag))
+    }
+
+    @Test
     fun testHasVibrator() {
         assertThat(vibratorHelper.hasVibrator()).isTrue()
         verify(vibrator).hasVibrator()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt
new file mode 100644
index 0000000..cfbe8e3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.commandline
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@SmallTest
+class CommandParserTest : SysuiTestCase() {
+    private val parser = CommandParser()
+
+    @Test
+    fun registerToken_cannotReuseNames() {
+        parser.flag("-f")
+        assertThrows(IllegalArgumentException::class.java) { parser.flag("-f") }
+    }
+
+    @Test
+    fun unknownToken_throws() {
+        assertThrows(ArgParseError::class.java) { parser.parse(listOf("unknown-token")) }
+    }
+
+    @Test
+    fun parseSingleFlag_present() {
+        val flag by parser.flag("-f")
+        parser.parse(listOf("-f"))
+        assertTrue(flag)
+    }
+
+    @Test
+    fun parseSingleFlag_notPresent() {
+        val flag by parser.flag("-f")
+        parser.parse(listOf())
+        assertFalse(flag)
+    }
+
+    @Test
+    fun parseSingleOptionalParam_present() {
+        val param by parser.param("-p", valueParser = Type.Int)
+        parser.parse(listOf("-p", "123"))
+        assertThat(param).isEqualTo(123)
+    }
+
+    @Test
+    fun parseSingleOptionalParam_notPresent() {
+        val param by parser.param("-p", valueParser = Type.Int)
+        parser.parse(listOf())
+        assertThat(param).isNull()
+    }
+
+    @Test
+    fun parseSingleOptionalParam_missingArg_throws() {
+        val param by parser.param("-p", valueParser = Type.Int)
+        assertThrows(ArgParseError::class.java) { parser.parse(listOf("-p")) }
+    }
+
+    @Test
+    fun parseSingleRequiredParam_present() {
+        val param by parser.require(parser.param("-p", valueParser = Type.Int))
+        parser.parse(listOf("-p", "123"))
+        assertThat(param).isEqualTo(123)
+    }
+
+    @Test
+    fun parseSingleRequiredParam_notPresent_failsValidation() {
+        val param by parser.require(parser.param("-p", valueParser = Type.Int))
+        assertFalse(parser.parse(listOf()))
+    }
+
+    @Test
+    fun parseSingleRequiredParam_missingArg_throws() {
+        val param by parser.require(parser.param("-p", valueParser = Type.Int))
+        assertThrows(ArgParseError::class.java) { parser.parse(listOf("-p")) }
+    }
+
+    @Test
+    fun parseAsSubCommand_singleFlag_present() {
+        val flag by parser.flag("-f")
+        val args = listOf("-f").listIterator()
+        parser.parseAsSubCommand(args)
+
+        assertTrue(flag)
+    }
+
+    @Test
+    fun parseAsSubCommand_singleFlag_notPresent() {
+        val flag by parser.flag("-f")
+        val args = listOf("--other-flag").listIterator()
+        parser.parseAsSubCommand(args)
+
+        assertFalse(flag)
+    }
+
+    @Test
+    fun parseAsSubCommand_singleOptionalParam_present() {
+        val param by parser.param("-p", valueParser = Type.Int)
+        parser.parseAsSubCommand(listOf("-p", "123", "--other-arg", "321").listIterator())
+        assertThat(param).isEqualTo(123)
+    }
+
+    @Test
+    fun parseAsSubCommand_singleOptionalParam_notPresent() {
+        val param by parser.param("-p", valueParser = Type.Int)
+        parser.parseAsSubCommand(listOf("--other-arg", "321").listIterator())
+        assertThat(param).isNull()
+    }
+
+    @Test
+    fun parseAsSubCommand_singleRequiredParam_present() {
+        val param by parser.require(parser.param("-p", valueParser = Type.Int))
+        parser.parseAsSubCommand(listOf("-p", "123", "--other-arg", "321").listIterator())
+        assertThat(param).isEqualTo(123)
+    }
+
+    @Test
+    fun parseAsSubCommand_singleRequiredParam_notPresent() {
+        parser.require(parser.param("-p", valueParser = Type.Int))
+        assertFalse(parser.parseAsSubCommand(listOf("--other-arg", "321").listIterator()))
+    }
+
+    @Test
+    fun parseCommandWithSubCommand_required_provided() {
+        val topLevelFlag by parser.flag("flag", shortName = "-f")
+
+        val cmd =
+            object : ParseableCommand("test") {
+                val flag by flag("flag1")
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        parser.require(parser.subCommand(cmd))
+        parser.parse(listOf("-f", "test", "--flag1"))
+
+        assertTrue(topLevelFlag)
+        assertThat(cmd).isNotNull()
+        assertTrue(cmd.flag)
+    }
+
+    @Test
+    fun parseCommandWithSubCommand_required_notProvided() {
+        val topLevelFlag by parser.flag("-f")
+
+        val cmd =
+            object : ParseableCommand("test") {
+                val flag by parser.flag("flag1")
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        parser.require(parser.subCommand(cmd))
+
+        assertFalse(parser.parse(listOf("-f")))
+    }
+
+    @Test
+    fun flag_requiredParam_optionalParam_allProvided_failsValidation() {
+        val flag by parser.flag("-f")
+        val optionalParam by parser.param("-p", valueParser = Type.Int)
+        val requiredParam by parser.require(parser.param("-p2", valueParser = Type.Boolean))
+
+        parser.parse(
+            listOf(
+                "-f",
+                "-p",
+                "123",
+                "-p2",
+                "false",
+            )
+        )
+
+        assertTrue(flag)
+        assertThat(optionalParam).isEqualTo(123)
+        assertFalse(requiredParam)
+    }
+
+    @Test
+    fun flag_requiredParam_optionalParam_optionalExcluded() {
+        val flag by parser.flag("-f")
+        val optionalParam by parser.param("-p", valueParser = Type.Int)
+        val requiredParam by parser.require(parser.param("-p2", valueParser = Type.Boolean))
+
+        parser.parse(
+            listOf(
+                "-p2",
+                "true",
+            )
+        )
+
+        assertFalse(flag)
+        assertThat(optionalParam).isNull()
+        assertTrue(requiredParam)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt
new file mode 100644
index 0000000..e391d6b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt
@@ -0,0 +1,55 @@
+package com.android.systemui.statusbar.commandline
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@SmallTest
+class ParametersTest : SysuiTestCase() {
+    @Test
+    fun singleArgOptional_returnsNullBeforeParse() {
+        val optional by SingleArgParamOptional(longName = "longName", valueParser = Type.Int)
+        assertThat(optional).isNull()
+    }
+
+    @Test
+    fun singleArgOptional_returnsParsedValue() {
+        val param = SingleArgParamOptional(longName = "longName", valueParser = Type.Int)
+        param.parseArgsFromIter(listOf("3").listIterator())
+        val optional by param
+        assertThat(optional).isEqualTo(3)
+    }
+
+    @Test
+    fun singleArgRequired_throwsBeforeParse() {
+        val req by SingleArgParam(longName = "param", valueParser = Type.Boolean)
+        assertThrows(IllegalStateException::class.java) { req }
+    }
+
+    @Test
+    fun singleArgRequired_returnsParsedValue() {
+        val param = SingleArgParam(longName = "param", valueParser = Type.Boolean)
+        param.parseArgsFromIter(listOf("true").listIterator())
+        val req by param
+        assertTrue(req)
+    }
+
+    @Test
+    fun param_handledAfterParse() {
+        val optParam = SingleArgParamOptional(longName = "string1", valueParser = Type.String)
+        val reqParam = SingleArgParam(longName = "string2", valueParser = Type.Float)
+
+        assertFalse(optParam.handled)
+        assertFalse(reqParam.handled)
+
+        optParam.parseArgsFromIter(listOf("test").listIterator())
+        reqParam.parseArgsFromIter(listOf("1.23").listIterator())
+
+        assertTrue(optParam.handled)
+        assertTrue(reqParam.handled)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt
new file mode 100644
index 0000000..86548d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.commandline
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class ParseableCommandTest : SysuiTestCase() {
+    @Mock private lateinit var pw: PrintWriter
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    /**
+     * A little change-detector-y, but this is just a general assertion that building up a command
+     * parser via its wrapper works as expected.
+     */
+    @Test
+    fun testFactoryMethods() {
+        val mySubCommand =
+            object : ParseableCommand("subCommand") {
+                val flag by flag("flag")
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val mySubCommand2 =
+            object : ParseableCommand("subCommand2") {
+                val flag by flag("flag")
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        // Verify that the underlying parser contains the correct types
+        val myCommand =
+            object : ParseableCommand("testName") {
+                val flag by flag("flag", shortName = "f")
+                val requiredParam by
+                    param(longName = "required-param", shortName = "r", valueParser = Type.String)
+                        .required()
+                val optionalParam by
+                    param(longName = "optional-param", shortName = "o", valueParser = Type.Boolean)
+                val optionalSubCommand by subCommand(mySubCommand)
+                val requiredSubCommand by subCommand(mySubCommand2).required()
+
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val flags = myCommand.parser.flags
+        val params = myCommand.parser.params
+        val subCommands = myCommand.parser.subCommands
+
+        assertThat(flags).hasSize(2)
+        assertThat(flags[0]).isInstanceOf(Flag::class.java)
+        assertThat(flags[1]).isInstanceOf(Flag::class.java)
+
+        assertThat(params).hasSize(2)
+        val req = params.filter { it is SingleArgParam<*> }
+        val opt = params.filter { it is SingleArgParamOptional<*> }
+        assertThat(req).hasSize(1)
+        assertThat(opt).hasSize(1)
+
+        val reqSub = subCommands.filter { it is RequiredSubCommand<*> }
+        val optSub = subCommands.filter { it is OptionalSubCommand<*> }
+        assertThat(reqSub).hasSize(1)
+        assertThat(optSub).hasSize(1)
+    }
+
+    @Test
+    fun factoryMethods_enforceShortNameRules() {
+        // Short names MUST be one character long
+        assertThrows(IllegalArgumentException::class.java) {
+            val myCommand =
+                object : ParseableCommand("test-command") {
+                    val flag by flag("longName", "invalidShortName")
+
+                    override fun execute(pw: PrintWriter) {}
+                }
+        }
+
+        assertThrows(IllegalArgumentException::class.java) {
+            val myCommand =
+                object : ParseableCommand("test-command") {
+                    val param by param("longName", "invalidShortName", valueParser = Type.String)
+
+                    override fun execute(pw: PrintWriter) {}
+                }
+        }
+    }
+
+    @Test
+    fun factoryMethods_enforceLongNames_notPrefixed() {
+        // Long names must not start with "-", since they will be added
+        assertThrows(IllegalArgumentException::class.java) {
+            val myCommand =
+                object : ParseableCommand("test-command") {
+                    val flag by flag("--invalid")
+
+                    override fun execute(pw: PrintWriter) {}
+                }
+        }
+
+        assertThrows(IllegalArgumentException::class.java) {
+            val myCommand =
+                object : ParseableCommand("test-command") {
+                    val param by param("-invalid", valueParser = Type.String)
+
+                    override fun execute(pw: PrintWriter) {}
+                }
+        }
+    }
+
+    @Test
+    fun executeDoesNotPropagateExceptions() {
+        val cmd =
+            object : ParseableCommand("test-command") {
+                val flag by flag("flag")
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val throwingCommand = listOf("unknown-token")
+
+        // Given a command that would cause an ArgParseError
+        assertThrows(ArgParseError::class.java) { cmd.parser.parse(throwingCommand) }
+
+        // The parser consumes that error
+        cmd.execute(pw, throwingCommand)
+    }
+
+    @Test
+    fun executeFailingCommand_callsOnParseFailed() {
+        val cmd =
+            object : ParseableCommand("test-command") {
+                val flag by flag("flag")
+
+                var onParseFailedCalled = false
+
+                override fun execute(pw: PrintWriter) {}
+                override fun onParseFailed(error: ArgParseError) {
+                    onParseFailedCalled = true
+                }
+            }
+
+        val throwingCommand = listOf("unknown-token")
+        cmd.execute(pw, throwingCommand)
+
+        assertTrue(cmd.onParseFailedCalled)
+    }
+
+    @Test
+    fun baseCommand() {
+        val myCommand = MyCommand()
+        myCommand.execute(pw, baseCommand)
+
+        assertThat(myCommand.flag1).isFalse()
+        assertThat(myCommand.singleParam).isNull()
+    }
+
+    @Test
+    fun commandWithFlags() {
+        val command = MyCommand()
+        command.execute(pw, cmdWithFlags)
+
+        assertThat(command.flag1).isTrue()
+        assertThat(command.flag2).isTrue()
+    }
+
+    @Test
+    fun commandWithArgs() {
+        val cmd = MyCommand()
+        cmd.execute(pw, cmdWithSingleArgParam)
+
+        assertThat(cmd.singleParam).isEqualTo("single_param")
+    }
+
+    @Test
+    fun commandWithRequiredParam_provided() {
+        val cmd =
+            object : ParseableCommand(name) {
+                val singleRequiredParam: String by
+                    param(
+                            longName = "param1",
+                            shortName = "p",
+                            valueParser = Type.String,
+                        )
+                        .required()
+
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val cli = listOf("-p", "value")
+        cmd.execute(pw, cli)
+
+        assertThat(cmd.singleRequiredParam).isEqualTo("value")
+    }
+
+    @Test
+    fun commandWithRequiredParam_not_provided_throws() {
+        val cmd =
+            object : ParseableCommand(name) {
+                val singleRequiredParam by
+                    param(shortName = "p", longName = "param1", valueParser = Type.String)
+                        .required()
+
+                override fun execute(pw: PrintWriter) {}
+
+                override fun execute(pw: PrintWriter, args: List<String>) {
+                    parser.parse(args)
+                    execute(pw)
+                }
+            }
+
+        val cli = listOf("")
+        assertThrows(ArgParseError::class.java) { cmd.execute(pw, cli) }
+    }
+
+    @Test
+    fun commandWithSubCommand() {
+        val subName = "sub-command"
+        val subCmd =
+            object : ParseableCommand(subName) {
+                val singleOptionalParam: String? by param("param", valueParser = Type.String)
+
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val cmd =
+            object : ParseableCommand(name) {
+                val subCmd by subCommand(subCmd)
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        cmd.execute(pw, listOf("sub-command", "--param", "test"))
+        assertThat(cmd.subCmd?.singleOptionalParam).isEqualTo("test")
+    }
+
+    @Test
+    fun complexCommandWithSubCommands_reusedNames() {
+        val commandLine = "-f --param1 arg1 sub-command1 -f -p arg2 --param2 arg3".split(" ")
+
+        val subName = "sub-command1"
+        val subCmd =
+            object : ParseableCommand(subName) {
+                val flag1 by flag("flag", shortName = "f")
+                val param1: String? by param("param1", shortName = "p", valueParser = Type.String)
+
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        val myCommand =
+            object : ParseableCommand(name) {
+                val flag1 by flag(longName = "flag", shortName = "f")
+                val param1 by param("param1", shortName = "p", valueParser = Type.String).required()
+                val param2: String? by param(longName = "param2", valueParser = Type.String)
+                val subCommand by subCommand(subCmd)
+
+                override fun execute(pw: PrintWriter) {}
+            }
+
+        myCommand.execute(pw, commandLine)
+
+        assertThat(myCommand.flag1).isTrue()
+        assertThat(myCommand.param1).isEqualTo("arg1")
+        assertThat(myCommand.param2).isEqualTo("arg3")
+        assertThat(myCommand.subCommand).isNotNull()
+        assertThat(myCommand.subCommand?.flag1).isTrue()
+        assertThat(myCommand.subCommand?.param1).isEqualTo("arg2")
+    }
+
+    class MyCommand(
+        private val onExecute: ((MyCommand) -> Unit)? = null,
+    ) : ParseableCommand(name) {
+
+        val flag1 by flag(shortName = "f", longName = "flag1", description = "flag 1 for test")
+        val flag2 by flag(shortName = "g", longName = "flag2", description = "flag 2 for test")
+        val singleParam: String? by
+            param(
+                shortName = "a",
+                longName = "arg1",
+                valueParser = Type.String,
+            )
+
+        override fun execute(pw: PrintWriter) {
+            onExecute?.invoke(this)
+        }
+    }
+
+    companion object {
+        const val name = "my_command"
+        val baseCommand = listOf("")
+        val cmdWithFlags = listOf("-f", "--flag2")
+        val cmdWithSingleArgParam = listOf("--arg1", "single_param")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
new file mode 100644
index 0000000..759f0bc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
@@ -0,0 +1,61 @@
+package com.android.systemui.statusbar.commandline
+
+import android.graphics.Rect
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@SmallTest
+class ValueParserTest : SysuiTestCase() {
+    @Test
+    fun parseString() {
+        assertThat(Type.String.parseValue("test")).isEqualTo(Result.success("test"))
+    }
+
+    @Test
+    fun parseInt() {
+        assertThat(Type.Int.parseValue("123")).isEqualTo(Result.success(123))
+
+        assertTrue(Type.Int.parseValue("not an Int").isFailure)
+    }
+
+    @Test
+    fun parseFloat() {
+        assertThat(Type.Float.parseValue("1.23")).isEqualTo(Result.success(1.23f))
+
+        assertTrue(Type.Int.parseValue("not a Float").isFailure)
+    }
+
+    @Test
+    fun parseBoolean() {
+        assertThat(Type.Boolean.parseValue("true")).isEqualTo(Result.success(true))
+        assertThat(Type.Boolean.parseValue("false")).isEqualTo(Result.success(false))
+
+        assertTrue(Type.Boolean.parseValue("not a Boolean").isFailure)
+    }
+
+    @Test
+    fun mapToComplexType() {
+        val parseSquare = Type.Int.map { Rect(it, it, it, it) }
+
+        assertThat(parseSquare.parseValue("10")).isEqualTo(Result.success(Rect(10, 10, 10, 10)))
+    }
+
+    @Test
+    fun mapToFallibleComplexType() {
+        val fallibleParseSquare =
+            Type.Int.map {
+                if (it > 0) {
+                    Rect(it, it, it, it)
+                } else {
+                    null
+                }
+            }
+
+        assertThat(fallibleParseSquare.parseValue("10"))
+            .isEqualTo(Result.success(Rect(10, 10, 10, 10)))
+        assertTrue(fallibleParseSquare.parseValue("-10").isFailure)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
index c57b64d..5b919d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
@@ -23,6 +23,7 @@
 import androidx.lifecycle.Lifecycle
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.wifitrackerlib.WifiEntry
 import com.android.wifitrackerlib.WifiPickerTracker
@@ -31,7 +32,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyList
 import org.mockito.Captor
 import org.mockito.Mock
@@ -52,7 +52,7 @@
     private lateinit var userTracker: UserTracker
     @Mock
     private lateinit var wifiPickerTrackerFactory:
-            AccessPointControllerImpl.WifiPickerTrackerFactory
+            WifiPickerTrackerFactory
     @Mock
     private lateinit var wifiPickerTracker: WifiPickerTracker
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt
index 4b5d0a3..f1c7956 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt
@@ -37,73 +37,8 @@
     private val disableFlagsLogger = DisableFlagsLogger(disable1Flags, disable2Flags)
 
     @Test
-    fun getDisableFlagsString_oldAndNewSame_newAndUnchangedLoggedOldNotLogged() {
-        val state = DisableFlagsLogger.DisableState(
-                0b111, // ABC
-                0b01 // mN
-        )
-
-        val result = disableFlagsLogger.getDisableFlagsString(state, state)
-
-        assertThat(result).doesNotContain("Old")
-        assertThat(result).contains("ABC.mN")
-        assertThat(result).contains("(unchanged)")
-    }
-
-    @Test
-    fun getDisableFlagsString_oldAndNewDifferent_statesAndDiffLogged() {
-        val result = disableFlagsLogger.getDisableFlagsString(
-                DisableFlagsLogger.DisableState(
-                        0b111, // ABC
-                        0b01, // mN
-                ),
-                DisableFlagsLogger.DisableState(
-                        0b001, // abC
-                        0b10 // Mn
-                )
-        )
-
-        assertThat(result).contains("Old: ABC.mN")
-        assertThat(result).contains("New: abC.Mn")
-        assertThat(result).contains("(changed: ab.Mn)")
-    }
-
-    @Test
-    fun getDisableFlagsString_onlyDisable2Different_diffLoggedCorrectly() {
-        val result = disableFlagsLogger.getDisableFlagsString(
-                DisableFlagsLogger.DisableState(
-                        0b001, // abC
-                        0b01, // mN
-                ),
-                DisableFlagsLogger.DisableState(
-                        0b001, // abC
-                        0b00 // mn
-                )
-        )
-
-        assertThat(result).contains("(changed: .n)")
-    }
-
-    @Test
-    fun getDisableFlagsString_nullOld_onlyNewStateLogged() {
-        val result = disableFlagsLogger.getDisableFlagsString(
-            old = null,
-            new = DisableFlagsLogger.DisableState(
-                0b001, // abC
-                0b01, // mN
-            ),
-        )
-
-        assertThat(result).doesNotContain("Old")
-        assertThat(result).contains("abC.mN")
-        assertThat(result).doesNotContain("(")
-        assertThat(result).doesNotContain(")")
-    }
-
-    @Test
     fun getDisableFlagsString_nullLocalModification_localModNotLogged() {
         val result = disableFlagsLogger.getDisableFlagsString(
-                DisableFlagsLogger.DisableState(0, 0),
                 DisableFlagsLogger.DisableState(1, 1),
                 newAfterLocalModification = null
         )
@@ -118,9 +53,7 @@
                 0b10 // mn
         )
 
-        val result = disableFlagsLogger.getDisableFlagsString(
-                DisableFlagsLogger.DisableState(0, 0), newState, newState
-        )
+        val result = disableFlagsLogger.getDisableFlagsString(newState, newState)
 
         assertThat(result).doesNotContain("local modification")
     }
@@ -128,7 +61,6 @@
     @Test
     fun getDisableFlagsString_newAfterLocalModificationDifferent_localModAndDiffLogged() {
         val result = disableFlagsLogger.getDisableFlagsString(
-                old = DisableFlagsLogger.DisableState(0, 0),
                 new = DisableFlagsLogger.DisableState(
                         0b000, // abc
                         0b00 // mn
@@ -143,6 +75,22 @@
     }
 
     @Test
+    fun getDisableFlagsString_onlyDisable2Different_diffLoggedCorrectly() {
+        val result = disableFlagsLogger.getDisableFlagsString(
+            DisableFlagsLogger.DisableState(
+                0b001, // abC
+                0b01, // mN
+            ),
+            DisableFlagsLogger.DisableState(
+                0b001, // abC
+                0b00 // mn
+            )
+        )
+
+        assertThat(result).contains("(changed: .n)")
+    }
+
+    @Test
     fun constructor_defaultDisableFlags_noException() {
         // Just creating the logger with the default params will trigger the exception if there
         // is one.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
new file mode 100644
index 0000000..580463a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.disableflags.data.repository
+
+import android.app.StatusBarManager.DISABLE2_NONE
+import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
+import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
+import android.app.StatusBarManager.DISABLE_CLOCK
+import android.app.StatusBarManager.DISABLE_NONE
+import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS
+import android.content.res.Configuration
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class DisableFlagsRepositoryTest : SysuiTestCase() {
+
+    private lateinit var underTest: DisableFlagsRepository
+
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val commandQueue: CommandQueue = mock()
+    private val configurationController: ConfigurationController = mock()
+    private val remoteInputQuickSettingsDisabler =
+        RemoteInputQuickSettingsDisabler(
+            context,
+            commandQueue,
+            configurationController,
+        )
+    private val logBuffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10)
+    private val disableFlagsLogger = DisableFlagsLogger()
+
+    @Before
+    fun setUp() {
+        underTest =
+            DisableFlagsRepositoryImpl(
+                commandQueue,
+                DISPLAY_ID,
+                testScope.backgroundScope,
+                remoteInputQuickSettingsDisabler,
+                logBuffer,
+                disableFlagsLogger,
+            )
+    }
+
+    @Test
+    fun disableFlags_initialValue_none() {
+        assertThat(underTest.disableFlags.value)
+            .isEqualTo(DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE))
+    }
+
+    @Test
+    fun disableFlags_noSubscribers_callbackStillRegistered() =
+        testScope.runTest { verify(commandQueue).addCallback(any()) }
+
+    @Test
+    fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false)
+
+            assertThat(underTest.disableFlags.value.areNotificationAlertsEnabled()).isTrue()
+        }
+
+    @Test
+    fun disableFlags_notifAlertsDisabled_notifAlertsEnabledFalse() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_NOTIFICATION_ALERTS,
+                    DISABLE2_NONE,
+                    /* animate= */ false,
+                )
+
+            assertThat(underTest.disableFlags.value.areNotificationAlertsEnabled()).isFalse()
+        }
+
+    @Test
+    fun disableFlags_notifAlertsDisabled_differentDisplay_notifAlertsEnabledTrue() =
+        testScope.runTest {
+            val wrongDisplayId = DISPLAY_ID + 10
+
+            getCommandQueueCallback()
+                .disable(
+                    wrongDisplayId,
+                    DISABLE_NOTIFICATION_ALERTS,
+                    DISABLE2_NONE,
+                    /* animate= */ false,
+                )
+
+            // THEN our repo reports them as still enabled
+            assertThat(underTest.disableFlags.value.areNotificationAlertsEnabled()).isTrue()
+        }
+
+    @Test
+    fun disableFlags_shadeNotDisabled_shadeEnabledTrue() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false)
+
+            assertThat(underTest.disableFlags.value.isShadeEnabled()).isTrue()
+        }
+
+    @Test
+    fun disableFlags_shadeDisabled_shadeEnabledFalse() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_NONE,
+                    DISABLE2_NOTIFICATION_SHADE,
+                    /* animate= */ false,
+                )
+
+            assertThat(underTest.disableFlags.value.isShadeEnabled()).isFalse()
+        }
+
+    @Test
+    fun disableFlags_shadeDisabled_differentDisplay_shadeEnabledTrue() =
+        testScope.runTest {
+            val wrongDisplayId = DISPLAY_ID + 10
+
+            getCommandQueueCallback()
+                .disable(
+                    wrongDisplayId,
+                    DISABLE_NONE,
+                    DISABLE2_NOTIFICATION_SHADE,
+                    /* animate= */ false,
+                )
+
+            // THEN our repo reports them as still enabled
+            assertThat(underTest.disableFlags.value.isShadeEnabled()).isTrue()
+        }
+
+    @Test
+    fun disableFlags_quickSettingsNotDisabled_quickSettingsEnabledTrue() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false)
+
+            assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isTrue()
+        }
+
+    @Test
+    fun disableFlags_quickSettingsDisabled_quickSettingsEnabledFalse() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_NONE,
+                    DISABLE2_QUICK_SETTINGS,
+                    /* animate= */ false,
+                )
+
+            assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse()
+        }
+
+    @Test
+    fun disableFlags_quickSettingsDisabled_differentDisplay_quickSettingsEnabledTrue() =
+        testScope.runTest {
+            val wrongDisplayId = DISPLAY_ID + 10
+
+            getCommandQueueCallback()
+                .disable(
+                    wrongDisplayId,
+                    DISABLE_NONE,
+                    DISABLE2_QUICK_SETTINGS,
+                    /* animate= */ false,
+                )
+
+            // THEN our repo reports them as still enabled
+            assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isTrue()
+        }
+
+    @Test
+    fun disableFlags_remoteInputActive_quickSettingsEnabledFalse() =
+        testScope.runTest {
+            // WHEN remote input is set up to be active
+            val configuration = Configuration(mContext.resources.configuration)
+            configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+            mContext.orCreateTestableResources.addOverride(
+                R.bool.config_use_split_notification_shade,
+                /* value= */ false
+            )
+            remoteInputQuickSettingsDisabler.setRemoteInputActive(true)
+            remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_NONE,
+                    DISABLE2_NONE,
+                    /* animate= */ false,
+                )
+
+            // THEN quick settings is disabled (even if the disable flags don't say so)
+            assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse()
+        }
+
+    @Test
+    fun disableFlags_reactsToChanges() =
+        testScope.runTest {
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_NOTIFICATION_ALERTS,
+                    DISABLE2_NONE,
+                    /* animate= */ false,
+                )
+            assertThat(underTest.disableFlags.value.areNotificationAlertsEnabled()).isFalse()
+
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_CLOCK, // Unrelated to notifications
+                    DISABLE2_NONE,
+                    /* animate= */ false,
+                )
+            assertThat(underTest.disableFlags.value.areNotificationAlertsEnabled()).isTrue()
+
+            getCommandQueueCallback()
+                .disable(
+                    DISPLAY_ID,
+                    DISABLE_NOTIFICATION_ALERTS,
+                    DISABLE2_QUICK_SETTINGS or DISABLE2_NOTIFICATION_SHADE,
+                    /* animate= */ false,
+                )
+            assertThat(underTest.disableFlags.value.areNotificationAlertsEnabled()).isFalse()
+            assertThat(underTest.disableFlags.value.isShadeEnabled()).isFalse()
+            assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse()
+        }
+
+    private fun getCommandQueueCallback(): CommandQueue.Callbacks {
+        val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>()
+        verify(commandQueue).addCallback(callbackCaptor.capture())
+        return callbackCaptor.value
+    }
+
+    private companion object {
+        const val DISPLAY_ID = 1
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/FakeDisableFlagsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/FakeDisableFlagsRepository.kt
new file mode 100644
index 0000000..b66231c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/FakeDisableFlagsRepository.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.disableflags.data.repository
+
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeDisableFlagsRepository : DisableFlagsRepository {
+    override val disableFlags = MutableStateFlow(DisableFlagsModel())
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
new file mode 100644
index 0000000..2e223f6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+import android.content.Context
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.util.Pair
+import android.view.Gravity
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+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.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class SystemEventChipAnimationControllerTest : SysuiTestCase() {
+    private lateinit var controller: SystemEventChipAnimationController
+
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+    @Mock private lateinit var sbWindowController: StatusBarWindowController
+    @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider
+
+    private var testView = TestView(mContext)
+    private var viewCreator: ViewCreator = { testView }
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        // StatusBarWindowController is mocked. The addViewToWindow function needs to be mocked to
+        // ensure that the chip view is added to a parent view
+        whenever(sbWindowController.addViewToWindow(any(), any())).then {
+            val statusbarFake = FrameLayout(mContext)
+            statusbarFake.layout(
+                portraitArea.left,
+                portraitArea.top,
+                portraitArea.right,
+                portraitArea.bottom,
+            )
+            statusbarFake.addView(
+                it.arguments[0] as View,
+                it.arguments[1] as FrameLayout.LayoutParams
+            )
+        }
+
+        whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(Pair(insets, insets))
+        whenever(insetsProvider.getStatusBarContentAreaForCurrentRotation())
+            .thenReturn(portraitArea)
+
+        controller =
+            SystemEventChipAnimationController(
+                context = mContext,
+                statusBarWindowController = sbWindowController,
+                contentInsetsProvider = insetsProvider,
+                featureFlags = FakeFeatureFlags(),
+            )
+    }
+
+    @Test
+    fun prepareChipAnimation_lazyInitializes() {
+        // Until Dagger can do our initialization, make sure that the first chip animation calls
+        // init()
+        assertFalse(controller.initialized)
+        controller.prepareChipAnimation(viewCreator)
+        assertTrue(controller.initialized)
+    }
+
+    @Test
+    fun prepareChipAnimation_positionsChip() {
+        controller.prepareChipAnimation(viewCreator)
+        val chipRect = controller.chipBounds
+
+        // SB area = 10, 0, 990, 100
+        // chip size = 0, 0, 100, 50
+        assertThat(chipRect).isEqualTo(Rect(890, 25, 990, 75))
+    }
+
+    @Test
+    fun prepareChipAnimation_rotation_repositionsChip() {
+        controller.prepareChipAnimation(viewCreator)
+
+        // Chip has been prepared, and is located at (890, 25, 990, 75)
+        // Rotation should put it into its landscape location:
+        // SB area = 10, 0, 1990, 80
+        // chip size = 0, 0, 100, 50
+
+        whenever(insetsProvider.getStatusBarContentAreaForCurrentRotation())
+            .thenReturn(landscapeArea)
+        getInsetsListener().onStatusBarContentInsetsChanged()
+
+        val chipRect = controller.chipBounds
+        assertThat(chipRect).isEqualTo(Rect(1890, 15, 1990, 65))
+    }
+
+    /** regression test for (b/289378932) */
+    @Test
+    fun fullScreenStatusBar_positionsChipAtTop_withTopGravity() {
+        // In the case of a fullscreen status bar window, the content insets area is still correct
+        // (because it uses the dimens), but the window can be full screen. This seems to happen
+        // when launching an app from the ongoing call chip.
+
+        // GIVEN layout the status bar window fullscreen portrait
+        whenever(sbWindowController.addViewToWindow(any(), any())).then {
+            val statusbarFake = FrameLayout(mContext)
+            statusbarFake.layout(
+                fullScreenSb.left,
+                fullScreenSb.top,
+                fullScreenSb.right,
+                fullScreenSb.bottom,
+            )
+
+            val lp = it.arguments[1] as FrameLayout.LayoutParams
+            assertThat(lp.gravity and Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.TOP)
+
+            statusbarFake.addView(
+                it.arguments[0] as View,
+                lp,
+            )
+        }
+
+        // GIVEN insets provider gives the correct content area
+        whenever(insetsProvider.getStatusBarContentAreaForCurrentRotation())
+            .thenReturn(portraitArea)
+
+        // WHEN the controller lays out the chip in a fullscreen window
+        controller.prepareChipAnimation(viewCreator)
+
+        // THEN it still aligns the chip to the content area provided by the insets provider
+        val chipRect = controller.chipBounds
+        assertThat(chipRect).isEqualTo(Rect(890, 25, 990, 75))
+    }
+
+    private class TestView(context: Context) : View(context), BackgroundAnimatableView {
+        override val view: View
+            get() = this
+
+        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+            setMeasuredDimension(100, 50)
+        }
+
+        override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) {
+            setLeftTopRightBottom(l, t, r, b)
+        }
+    }
+
+    private fun getInsetsListener(): StatusBarContentInsetsChangedListener {
+        val callbackCaptor = argumentCaptor<StatusBarContentInsetsChangedListener>()
+        verify(insetsProvider).addCallback(capture(callbackCaptor))
+        return callbackCaptor.value!!
+    }
+
+    companion object {
+        private val portraitArea = Rect(10, 0, 990, 100)
+        private val landscapeArea = Rect(10, 0, 1990, 80)
+        private val fullScreenSb = Rect(10, 0, 990, 2000)
+
+        // 10px insets on both sides
+        private const val insets = 10
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
new file mode 100644
index 0000000..786856b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.events
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State.CONNECTED
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.privacy.PrivacyItemController
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class SystemEventCoordinatorTest : SysuiTestCase() {
+
+    private val fakeSystemClock = FakeSystemClock()
+    private val featureFlags = FakeFeatureFlags()
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val connectedDisplayInteractor = FakeConnectedDisplayInteractor()
+
+    @Mock lateinit var batteryController: BatteryController
+    @Mock lateinit var privacyController: PrivacyItemController
+    @Mock lateinit var scheduler: SystemStatusAnimationScheduler
+
+    private lateinit var systemEventCoordinator: SystemEventCoordinator
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        systemEventCoordinator =
+            SystemEventCoordinator(
+                    fakeSystemClock,
+                    batteryController,
+                    privacyController,
+                    context,
+                    featureFlags,
+                    TestScope(UnconfinedTestDispatcher()),
+                    connectedDisplayInteractor
+                )
+                .apply { attachScheduler(scheduler) }
+    }
+
+    @Test
+    fun startObserving_propagatesConnectedDisplayStatusEvents() =
+        testScope.runTest {
+            systemEventCoordinator.startObserving()
+
+            connectedDisplayInteractor.emit(CONNECTED)
+            connectedDisplayInteractor.emit(CONNECTED)
+
+            verify(scheduler, times(2)).onStatusEvent(any<ConnectedDisplayEvent>())
+        }
+
+    @Test
+    fun stopObserving_doesNotPropagateConnectedDisplayStatusEvents() =
+        testScope.runTest {
+            systemEventCoordinator.startObserving()
+
+            connectedDisplayInteractor.emit(CONNECTED)
+
+            verify(scheduler).onStatusEvent(any<ConnectedDisplayEvent>())
+
+            systemEventCoordinator.stopObserving()
+
+            connectedDisplayInteractor.emit(CONNECTED)
+
+            verifyNoMoreInteractions(scheduler)
+        }
+
+    class FakeConnectedDisplayInteractor : ConnectedDisplayInteractor {
+        private val flow = MutableSharedFlow<ConnectedDisplayInteractor.State>()
+        suspend fun emit(value: ConnectedDisplayInteractor.State) = flow.emit(value)
+        override val connectedDisplayState: Flow<ConnectedDisplayInteractor.State>
+            get() = flow
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 914301f..6be2fa5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -22,9 +22,9 @@
 import android.testing.TestableLooper.RunWithLooper
 import android.view.View
 import android.widget.FrameLayout
-import androidx.core.animation.AnimatorTestRule
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -69,6 +69,8 @@
 
     @Mock private lateinit var listener: SystemStatusAnimationCallback
 
+    @Mock private lateinit var logger: SystemStatusAnimationSchedulerLogger
+
     private lateinit var systemClock: FakeSystemClock
     private lateinit var chipAnimationController: SystemEventChipAnimationController
     private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
@@ -398,15 +400,16 @@
 
         // remove persistent dot
         systemStatusAnimationScheduler.removePersistentDot()
-        testScheduler.runCurrent()
+
+        // verify that the onHidePersistentDot callback is invoked
+        verify(listener, times(1)).onHidePersistentDot()
 
         // skip disappear animation
         animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
         testScheduler.runCurrent()
 
-        // verify that animationState changes to IDLE and onHidePersistentDot callback is invoked
+        // verify that animationState changes to IDLE
         assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
-        verify(listener, times(1)).onHidePersistentDot()
     }
 
     @Test
@@ -471,7 +474,6 @@
 
         // request removal of persistent dot
         systemStatusAnimationScheduler.removePersistentDot()
-        testScheduler.runCurrent()
 
         // schedule another high priority event while the event is animating out
         createAndScheduleFakePrivacyEvent()
@@ -487,6 +489,42 @@
         verify(listener, times(1)).onHidePersistentDot()
     }
 
+    @Test
+    fun testDotIsRemoved_evenIfAnimatorCallbackIsDelayed() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+
+        // create and schedule high priority event
+        createAndScheduleFakePrivacyEvent()
+
+        // skip chip animation lifecycle and fast forward to ANIMATING_OUT state
+        fastForwardAnimationToState(ANIMATING_OUT)
+        assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+        verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any())
+
+        // request removal of persistent dot
+        systemStatusAnimationScheduler.removePersistentDot()
+
+        // verify that the state is still ANIMATING_OUT
+        assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+
+        // skip disappear animation duration
+        testScheduler.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION + 1)
+        // In an old implementation this would trigger a coroutine timeout causing the
+        // onHidePersistentDot callback to be missed.
+        testScheduler.runCurrent()
+
+        // advance animator time to invoke onAnimationEnd callback
+        animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+        testScheduler.runCurrent()
+
+        // verify that onHidePersistentDot is invoked despite the animator callback being delayed
+        // (it's invoked more than DISAPPEAR_ANIMATION_DURATION after the dot removal was requested)
+        verify(listener, times(1)).onHidePersistentDot()
+        // verify that animationState is IDLE
+        assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
+    }
+
     private fun TestScope.fastForwardAnimationToState(@SystemAnimationState animationState: Int) {
         // this function should only be called directly after posting a status event
         assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
@@ -538,7 +576,8 @@
                 statusBarWindowController,
                 dumpManager,
                 systemClock,
-                CoroutineScope(StandardTestDispatcher(testScope.testScheduler))
+                CoroutineScope(StandardTestDispatcher(testScope.testScheduler)),
+                logger
             )
         // add a mock listener
         systemStatusAnimationScheduler.addCallback(listener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 2de5705..9036f22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -278,7 +278,7 @@
         `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
 
         // WHEN a connection attempt is made and view is attached
-        val view = controller.buildAndConnectView(fakeParent)
+        val view = controller.buildAndConnectView(fakeParent)!!
         controller.stateChangeListener.onViewAttachedToWindow(view)
 
         // THEN no session is created
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
index f4458bb..e66eb70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
@@ -6,7 +6,8 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.shade.NotificationShadeWindowViewController
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -14,6 +15,7 @@
 import com.android.systemui.statusbar.policy.HeadsUpUtil
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.test.TestScope
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -27,7 +29,6 @@
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
 class NotificationLaunchAnimatorControllerTest : SysuiTestCase() {
-    @Mock lateinit var notificationShadeWindowViewController: NotificationShadeWindowViewController
     @Mock lateinit var notificationListContainer: NotificationListContainer
     @Mock lateinit var headsUpManager: HeadsUpManagerPhone
     @Mock lateinit var jankMonitor: InteractionJankMonitor
@@ -36,6 +37,9 @@
     private lateinit var notificationTestHelper: NotificationTestHelper
     private lateinit var notification: ExpandableNotificationRow
     private lateinit var controller: NotificationLaunchAnimatorController
+    private val notificationExpansionRepository = NotificationExpansionRepository()
+
+    private val testScope = TestScope()
 
     private val notificationKey: String
         get() = notification.entry.sbn.key
@@ -49,7 +53,7 @@
                 NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
         notification = notificationTestHelper.createRow()
         controller = NotificationLaunchAnimatorController(
-                notificationShadeWindowViewController,
+                notificationExpansionRepository,
                 notificationListContainer,
                 headsUpManager,
                 notification,
@@ -69,6 +73,11 @@
 
         assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification))
         assertFalse(notification.entry.isExpandAnimationRunning)
+        val isExpandAnimationRunning by testScope.collectLastValue(
+            notificationExpansionRepository.isExpandAnimationRunning
+        )
+        assertFalse(isExpandAnimationRunning!!)
+
         verify(headsUpManager).removeNotification(
                 notificationKey, true /* releaseImmediately */, true /* animate */)
         verify(onFinishAnimationCallback).run()
@@ -81,6 +90,11 @@
 
         assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification))
         assertFalse(notification.entry.isExpandAnimationRunning)
+        val isExpandAnimationRunning by testScope.collectLastValue(
+            notificationExpansionRepository.isExpandAnimationRunning
+        )
+        assertFalse(isExpandAnimationRunning!!)
+
         verify(headsUpManager).removeNotification(
                 notificationKey, true /* releaseImmediately */, true /* animate */)
         verify(onFinishAnimationCallback).run()
@@ -93,6 +107,11 @@
 
         assertFalse(HeadsUpUtil.isClickedHeadsUpNotification(notification))
         assertFalse(notification.entry.isExpandAnimationRunning)
+        val isExpandAnimationRunning by testScope.collectLastValue(
+            notificationExpansionRepository.isExpandAnimationRunning
+        )
+        assertFalse(isExpandAnimationRunning!!)
+
         verify(headsUpManager).removeNotification(
                 notificationKey, true /* releaseImmediately */, false /* animate */)
         verify(onFinishAnimationCallback).run()
@@ -103,5 +122,9 @@
         controller.onIntentStarted(willAnimate = true)
 
         assertTrue(notification.entry.isExpandAnimationRunning)
+        val isExpandAnimationRunning by testScope.collectLastValue(
+            notificationExpansionRepository.isExpandAnimationRunning
+        )
+        assertTrue(isExpandAnimationRunning!!)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
index a37c386..902dd51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt
@@ -20,8 +20,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
 import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.statusbar.StatusBarState
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index da3a9f6..40edea2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -18,10 +18,11 @@
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import androidx.core.animation.AnimatorTestRule
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeViewController.Companion.WAKEUP_ANIMATION_DELAY_MS
 import com.android.systemui.statusbar.StatusBarState
@@ -59,7 +60,7 @@
     private val bypassController: KeyguardBypassController = mock()
     private val dozeParameters: DozeParameters = mock()
     private val screenOffAnimationController: ScreenOffAnimationController = mock()
-    private val logger: NotificationWakeUpCoordinatorLogger = mock()
+    private val logger = NotificationWakeUpCoordinatorLogger(logcatLogBuffer())
     private val stackScrollerController: NotificationStackScrollLayoutController = mock()
     private val wakeUpListener: NotificationWakeUpCoordinator.WakeUpListener = mock()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
index 89faa239..a56fb2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
@@ -3,7 +3,11 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -15,8 +19,9 @@
 @SmallTest
 @RunWith(JUnit4::class)
 class RoundableTest : SysuiTestCase() {
-    val targetView: View = mock()
-    val roundable = FakeRoundable(targetView)
+    private val targetView: View = mock()
+    private val featureFlags = FakeFeatureFlags()
+    private val roundable = FakeRoundable(targetView = targetView, featureFlags = featureFlags)
 
     @Test
     fun defaultConfig_shouldNotHaveRoundedCorner() {
@@ -144,16 +149,62 @@
         assertEquals(0.2f, roundable.roundableState.bottomRoundness)
     }
 
+    @Test
+    fun getCornerRadii_radius_maxed_to_height() {
+        whenever(targetView.height).thenReturn(10)
+        featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
+        roundable.requestRoundness(1f, 1f, SOURCE1)
+
+        assertCornerRadiiEquals(5f, 5f)
+    }
+
+    @Test
+    fun getCornerRadii_topRadius_maxed_to_height() {
+        whenever(targetView.height).thenReturn(5)
+        featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
+        roundable.requestRoundness(1f, 0f, SOURCE1)
+
+        assertCornerRadiiEquals(5f, 0f)
+    }
+
+    @Test
+    fun getCornerRadii_bottomRadius_maxed_to_height() {
+        whenever(targetView.height).thenReturn(5)
+        featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
+        roundable.requestRoundness(0f, 1f, SOURCE1)
+
+        assertCornerRadiiEquals(0f, 5f)
+    }
+
+    @Test
+    fun getCornerRadii_radii_kept() {
+        whenever(targetView.height).thenReturn(100)
+        featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
+        roundable.requestRoundness(1f, 1f, SOURCE1)
+
+        assertCornerRadiiEquals(MAX_RADIUS, MAX_RADIUS)
+    }
+
+    private fun assertCornerRadiiEquals(top: Float, bottom: Float) {
+        assertEquals("topCornerRadius", top, roundable.topCornerRadius)
+        assertEquals("bottomCornerRadius", bottom, roundable.bottomCornerRadius)
+    }
+
     class FakeRoundable(
         targetView: View,
         radius: Float = MAX_RADIUS,
+        featureFlags: FeatureFlags
     ) : Roundable {
         override val roundableState =
             RoundableState(
                 targetView = targetView,
                 roundable = this,
                 maxRadius = radius,
+                featureFlags = featureFlags
             )
+
+        override val clipHeight: Int
+            get() = roundableState.targetView.height
     }
 
     companion object {
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 9037df8..104b751 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
@@ -26,6 +26,7 @@
 import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
 import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
 import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN;
 import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.DISMISSED;
@@ -47,6 +48,7 @@
 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.verifyNoMoreInteractions;
@@ -120,7 +122,7 @@
 
     @Mock private IStatusBarService mStatusBarService;
     @Mock private NotifPipelineFlags mNotifPipelineFlags;
-    @Mock private NotifCollectionLogger mLogger;
+    private final NotifCollectionLogger mLogger = spy(new NotifCollectionLogger(logcatLogBuffer()));
     @Mock private LogBufferEulogizer mEulogizer;
     @Mock private Handler mMainHandler;
 
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 a869038..bfa03ee 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection;
 
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
 import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree;
 import static com.android.systemui.statusbar.notification.collection.ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS;
 
@@ -101,10 +102,10 @@
 public class ShadeListBuilderTest extends SysuiTestCase {
 
     private ShadeListBuilder mListBuilder;
-    private FakeSystemClock mSystemClock = new FakeSystemClock();
-
-    @Mock private NotifPipelineFlags mNotifPipelineFlags;
-    @Mock private ShadeListBuilderLogger mLogger;
+    private final FakeSystemClock mSystemClock = new FakeSystemClock();
+    private final NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
+    private final ShadeListBuilderLogger mLogger = new ShadeListBuilderLogger(
+            mNotifPipelineFlags, logcatLogBuffer());
     @Mock private DumpManager mDumpManager;
     @Mock private NotifCollection mNotifCollection;
     @Mock private NotificationInteractionTracker mInteractionTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
index ac9a570..3dcfcfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection.coalescer;
 
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.Mockito.clearInvocations;
@@ -62,8 +64,7 @@
 
     @Mock private NotificationListener mListenerService;
     @Mock private GroupCoalescer.BatchableNotificationHandler mListener;
-    @Mock private GroupCoalescerLogger mLogger;
-
+    private final GroupCoalescerLogger mLogger = new GroupCoalescerLogger(logcatLogBuffer());
     @Captor private ArgumentCaptor<NotificationHandler> mListenerCaptor;
 
     private final NoManSimulator mNoMan = new NoManSimulator();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
deleted file mode 100644
index bc32759..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.coordinator;
-
-import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Person;
-import android.content.Intent;
-import android.graphics.Color;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.appops.AppOpsController;
-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.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-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;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class AppOpsCoordinatorTest extends SysuiTestCase {
-    private static final String TEST_PKG = "test_pkg";
-    private static final int NOTIF_USER_ID = 0;
-
-    @Mock private ForegroundServiceController mForegroundServiceController;
-    @Mock private AppOpsController mAppOpsController;
-    @Mock private NotifPipeline mNotifPipeline;
-
-    private NotificationEntryBuilder mEntryBuilder;
-    private AppOpsCoordinator mAppOpsCoordinator;
-    private NotifFilter mForegroundFilter;
-    private NotifSectioner mFgsSection;
-
-    private FakeSystemClock mClock = new FakeSystemClock();
-    private FakeExecutor mExecutor = new FakeExecutor(mClock);
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        allowTestableLooperAsMainThread();
-
-        mAppOpsCoordinator =
-                new AppOpsCoordinator(
-                        mForegroundServiceController,
-                        mAppOpsController,
-                        mExecutor);
-
-        mEntryBuilder = new NotificationEntryBuilder()
-                .setUser(new UserHandle(NOTIF_USER_ID));
-
-        mAppOpsCoordinator.attach(mNotifPipeline);
-
-        // capture filter
-        ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
-        verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
-        mForegroundFilter = filterCaptor.getValue();
-
-        mFgsSection = mAppOpsCoordinator.getSectioner();
-    }
-
-    @Test
-    public void filterTest_disclosureUnnecessary() {
-        NotificationEntry entry = mEntryBuilder.build();
-        StatusBarNotification sbn = entry.getSbn();
-
-        // GIVEN the notification is a disclosure notification
-        when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(true);
-
-        // GIVEN the disclosure isn't needed for this user
-        when(mForegroundServiceController.isDisclosureNeededForUser(sbn.getUserId()))
-                .thenReturn(false);
-
-        // THEN filter out the notification
-        assertTrue(mForegroundFilter.shouldFilterOut(entry, 0));
-    }
-
-    @Test
-    public void testIncludeFGSInSection_importanceDefault() {
-        // GIVEN the notification represents a colorized foreground service with > min importance
-        mEntryBuilder
-                .setFlag(mContext, FLAG_FOREGROUND_SERVICE, true)
-                .setImportance(IMPORTANCE_DEFAULT)
-                .modifyNotification(mContext)
-                .setColorized(true).setColor(Color.WHITE);
-
-        // THEN the entry is in the fgs section
-        assertTrue(mFgsSection.isInSection(mEntryBuilder.build()));
-    }
-
-    @Test
-    public void testDiscludeFGSInSection_importanceMin() {
-        // GIVEN the notification represents a colorized foreground service with min importance
-        mEntryBuilder
-                .setFlag(mContext, FLAG_FOREGROUND_SERVICE, true)
-                .setImportance(IMPORTANCE_MIN)
-                .modifyNotification(mContext)
-                .setColorized(true).setColor(Color.WHITE);
-
-        // THEN the entry is NOT in the fgs section
-        assertFalse(mFgsSection.isInSection(mEntryBuilder.build()));
-    }
-
-    @Test
-    public void testDiscludeNonFGSInSection() {
-        // GIVEN the notification represents a colorized notification with high importance that
-        // is NOT a foreground service
-        mEntryBuilder
-                .setImportance(IMPORTANCE_HIGH)
-                .setFlag(mContext, FLAG_FOREGROUND_SERVICE, false)
-                .modifyNotification(mContext).setColorized(false);
-
-        // THEN the entry is NOT in the fgs section
-        assertFalse(mFgsSection.isInSection(mEntryBuilder.build()));
-    }
-
-    @Test
-    public void testIncludeCallInSection_importanceDefault() {
-        // GIVEN the notification represents a call with > min importance
-        mEntryBuilder
-                .setImportance(IMPORTANCE_DEFAULT)
-                .modifyNotification(mContext)
-                .setStyle(makeCallStyle());
-
-        // THEN the entry is in the fgs section
-        assertTrue(mFgsSection.isInSection(mEntryBuilder.build()));
-    }
-
-    @Test
-    public void testDiscludeCallInSection_importanceMin() {
-        // GIVEN the notification represents a call with min importance
-        mEntryBuilder
-                .setImportance(IMPORTANCE_MIN)
-                .modifyNotification(mContext)
-                .setStyle(makeCallStyle());
-
-        // THEN the entry is NOT in the fgs section
-        assertFalse(mFgsSection.isInSection(mEntryBuilder.build()));
-    }
-
-    private Notification.CallStyle makeCallStyle() {
-        final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
-                new Intent("action"), PendingIntent.FLAG_IMMUTABLE);
-        final Person person = new Person.Builder().setName("person").build();
-        return Notification.CallStyle.forIncomingCall(person, pendingIntent, pendingIntent);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
new file mode 100644
index 0000000..f2207af
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.UserHandle;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ColorizedFgsCoordinatorTest extends SysuiTestCase {
+
+    private static final int NOTIF_USER_ID = 0;
+    @Mock private NotifPipeline mNotifPipeline;
+
+    private NotificationEntryBuilder mEntryBuilder;
+    private ColorizedFgsCoordinator mColorizedFgsCoordinator;
+    private NotifSectioner mFgsSection;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        allowTestableLooperAsMainThread();
+
+        mColorizedFgsCoordinator = new ColorizedFgsCoordinator();
+
+        mEntryBuilder = new NotificationEntryBuilder()
+                .setUser(new UserHandle(NOTIF_USER_ID));
+
+        mColorizedFgsCoordinator.attach(mNotifPipeline);
+
+        mFgsSection = mColorizedFgsCoordinator.getSectioner();
+    }
+
+    @Test
+    public void testIncludeFGSInSection_importanceDefault() {
+        // GIVEN the notification represents a colorized foreground service with > min importance
+        mEntryBuilder
+                .setFlag(mContext, FLAG_FOREGROUND_SERVICE, true)
+                .setImportance(IMPORTANCE_DEFAULT)
+                .modifyNotification(mContext)
+                .setColorized(true).setColor(Color.WHITE);
+
+        // THEN the entry is in the fgs section
+        assertTrue(mFgsSection.isInSection(mEntryBuilder.build()));
+    }
+
+    @Test
+    public void testDiscludeFGSInSection_importanceMin() {
+        // GIVEN the notification represents a colorized foreground service with min importance
+        mEntryBuilder
+                .setFlag(mContext, FLAG_FOREGROUND_SERVICE, true)
+                .setImportance(IMPORTANCE_MIN)
+                .modifyNotification(mContext)
+                .setColorized(true).setColor(Color.WHITE);
+
+        // THEN the entry is NOT in the fgs section
+        assertFalse(mFgsSection.isInSection(mEntryBuilder.build()));
+    }
+
+    @Test
+    public void testDiscludeNonFGSInSection() {
+        // GIVEN the notification represents a colorized notification with high importance that
+        // is NOT a foreground service
+        mEntryBuilder
+                .setImportance(IMPORTANCE_HIGH)
+                .setFlag(mContext, FLAG_FOREGROUND_SERVICE, false)
+                .modifyNotification(mContext).setColorized(false);
+
+        // THEN the entry is NOT in the fgs section
+        assertFalse(mFgsSection.isInSection(mEntryBuilder.build()));
+    }
+
+    @Test
+    public void testIncludeCallInSection_importanceDefault() {
+        // GIVEN the notification represents a call with > min importance
+        mEntryBuilder
+                .setImportance(IMPORTANCE_DEFAULT)
+                .modifyNotification(mContext)
+                .setStyle(makeCallStyle());
+
+        // THEN the entry is in the fgs section
+        assertTrue(mFgsSection.isInSection(mEntryBuilder.build()));
+    }
+
+    @Test
+    public void testDiscludeCallInSection_importanceMin() {
+        // GIVEN the notification represents a call with min importance
+        mEntryBuilder
+                .setImportance(IMPORTANCE_MIN)
+                .modifyNotification(mContext)
+                .setStyle(makeCallStyle());
+
+        // THEN the entry is NOT in the fgs section
+        assertFalse(mFgsSection.isInSection(mEntryBuilder.build()));
+    }
+
+    private Notification.CallStyle makeCallStyle() {
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
+                new Intent("action"), PendingIntent.FLAG_IMMUTABLE);
+        final Person person = new Person.Builder().setName("person").build();
+        return Notification.CallStyle.forIncomingCall(person, pendingIntent, pendingIntent);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
index ed24947..f91e5a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt
@@ -21,7 +21,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -47,14 +46,11 @@
     private lateinit var onBeforeRenderListListener: OnBeforeRenderListListener
     private val keyguardStateController: KeyguardStateController = mock()
     private val pipeline: NotifPipeline = mock()
-    private val flags: NotifPipelineFlags = mock()
     private val dumpManager: DumpManager = mock()
 
     @Before
     fun setUp() {
-        whenever(flags.allowDismissOngoing()).thenReturn(true)
-
-        dismissibilityProvider = NotificationDismissibilityProviderImpl(flags, dumpManager)
+        dismissibilityProvider = NotificationDismissibilityProviderImpl(dumpManager)
         coordinator = DismissibilityCoordinator(keyguardStateController, dismissibilityProvider)
         coordinator.attach(pipeline)
         onBeforeRenderListListener = withArgCaptor {
@@ -309,57 +305,4 @@
             dismissibilityProvider.isDismissable(summary)
         )
     }
-
-    @Test
-    fun testFeatureToggleOffNonDismissibleEntry() {
-        whenever(flags.allowDismissOngoing()).thenReturn(false)
-        val entry =
-            NotificationEntryBuilder()
-                .setTag("entry")
-                .setFlag(mContext, Notification.FLAG_NO_DISMISS, true)
-                .build()
-
-        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
-        assertTrue(
-            "FLAG_NO_DISMISS should be ignored, if the feature is off",
-            dismissibilityProvider.isDismissable(entry)
-        )
-    }
-
-    @Test
-    fun testFeatureToggleOffOngoingNotifWhenPhoneIsLocked() {
-        whenever(flags.allowDismissOngoing()).thenReturn(false)
-        whenever(keyguardStateController.isUnlocked).thenReturn(false)
-        val entry =
-            NotificationEntryBuilder()
-                .setTag("entry")
-                .setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
-                .build()
-
-        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
-        assertFalse(
-            "Ongoing Notifs should NOT be dismissible, if the feature is off",
-            dismissibilityProvider.isDismissable(entry)
-        )
-    }
-
-    @Test
-    fun testFeatureToggleOffOngoingNotifWhenPhoneIsUnLocked() {
-        whenever(flags.allowDismissOngoing()).thenReturn(false)
-        whenever(keyguardStateController.isUnlocked).thenReturn(true)
-        val entry =
-            NotificationEntryBuilder()
-                .setTag("entry")
-                .setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
-                .build()
-
-        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
-
-        assertFalse(
-            "Ongoing Notifs should NOT be dismissible, if the feature is off",
-            dismissibilityProvider.isDismissable(entry)
-        )
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
new file mode 100644
index 0000000..a544cad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+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.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+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)
+class DreamCoordinatorTest : SysuiTestCase() {
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+    @Mock private lateinit var notifPipeline: NotifPipeline
+    @Mock private lateinit var filterListener: Pluggable.PluggableListener<NotifFilter>
+
+    private val keyguardRepository = FakeKeyguardRepository()
+    private var fakeEntry: NotificationEntry = NotificationEntryBuilder().build()
+    val testDispatcher = UnconfinedTestDispatcher()
+    val testScope = TestScope(testDispatcher)
+
+    private lateinit var filter: NotifFilter
+    private lateinit var statusBarListener: StatusBarStateController.StateListener
+    private lateinit var dreamCoordinator: DreamCoordinator
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+        // Build the coordinator
+        dreamCoordinator =
+            DreamCoordinator(
+                statusBarStateController,
+                testScope.backgroundScope,
+                keyguardRepository
+            )
+
+        // Attach the pipeline and capture the listeners/filters that it registers
+        dreamCoordinator.attach(notifPipeline)
+
+        filter = withArgCaptor { verify(notifPipeline).addPreGroupFilter(capture()) }
+        filter.setInvalidationListener(filterListener)
+
+        statusBarListener = withArgCaptor {
+            verify(statusBarStateController).addCallback(capture())
+        }
+    }
+
+    @Test
+    fun hideNotifications_whenDreamingAndOnKeyguard() =
+        testScope.runTest {
+            // GIVEN we are on keyguard and not dreaming
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+            runCurrent()
+
+            // THEN notifications are not filtered out
+            verifyPipelinesNotInvalidated()
+            assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+
+            // WHEN dreaming starts and the active dream is hosted in lockscreen
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            runCurrent()
+
+            // THEN pipeline is notified and notifications should all be filtered out
+            verifyPipelinesInvalidated()
+            assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+        }
+
+    @Test
+    fun showNotifications_whenDreamingAndNotOnKeyguard() =
+        testScope.runTest {
+            // GIVEN we are on the keyguard and active dream is hosted in lockscreen
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            runCurrent()
+
+            // THEN pipeline is notified and notifications are all filtered out
+            verifyPipelinesInvalidated()
+            clearPipelineInvocations()
+            assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+            // WHEN we are no longer on the keyguard
+            statusBarListener.onStateChanged(StatusBarState.SHADE)
+
+            // THEN pipeline is notified and notifications are not filtered out
+            verifyPipelinesInvalidated()
+            assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+
+    @Test
+    fun showNotifications_whenOnKeyguardAndNotDreaming() =
+        testScope.runTest {
+            // GIVEN we are on the keyguard and active dream is hosted in lockscreen
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+            runCurrent()
+
+            // THEN pipeline is notified and notifications are all filtered out
+            verifyPipelinesInvalidated()
+            clearPipelineInvocations()
+            assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+            // WHEN the lockscreen hosted dream stops
+            keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+            runCurrent()
+
+            // THEN pipeline is notified and notifications are not filtered out
+            verifyPipelinesInvalidated()
+            assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+
+    private fun verifyPipelinesInvalidated() {
+        verify(filterListener).onPluggableInvalidated(eq(filter), any())
+    }
+
+    private fun verifyPipelinesNotInvalidated() {
+        verify(filterListener, never()).onPluggableInvalidated(eq(filter), any())
+    }
+
+    private fun clearPipelineInvocations() {
+        clearInvocations(filterListener)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
index 4143647..362da0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -28,6 +29,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager
 import com.android.systemui.statusbar.notification.row.NotificationGuts
+import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -36,6 +38,7 @@
 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.initMocks
 
 @SmallTest
@@ -52,8 +55,9 @@
     @Mock private lateinit var notifGutsViewManager: NotifGutsViewManager
     @Mock private lateinit var pipeline: NotifPipeline
     @Mock private lateinit var dumpManager: DumpManager
-    @Mock private lateinit var logger: GutsCoordinatorLogger
+    private val logger = GutsCoordinatorLogger(logcatLogBuffer())
     @Mock private lateinit var lifetimeExtenderCallback: OnEndLifetimeExtensionCallback
+    @Mock private lateinit var notificationGuts: NotificationGuts
 
     @Before
     fun setUp() {
@@ -69,12 +73,13 @@
         notifLifetimeExtender.setCallback(lifetimeExtenderCallback)
         entry1 = NotificationEntryBuilder().setId(1).build()
         entry2 = NotificationEntryBuilder().setId(2).build()
+        whenever(notificationGuts.gutsContent).thenReturn(mock(GutsContent::class.java))
     }
 
     @Test
     fun testSimpleLifetimeExtension() {
         assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse()
-        notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java))
+        notifGutsViewListener.onGutsOpen(entry1, notificationGuts)
         assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue()
         notifGutsViewListener.onGutsClose(entry1)
         verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1)
@@ -84,9 +89,9 @@
     @Test
     fun testDoubleOpenLifetimeExtension() {
         assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse()
-        notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java))
+        notifGutsViewListener.onGutsOpen(entry1, notificationGuts)
         assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue()
-        notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java))
+        notifGutsViewListener.onGutsOpen(entry1, notificationGuts)
         assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue()
         notifGutsViewListener.onGutsClose(entry1)
         verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1)
@@ -97,10 +102,10 @@
     fun testTwoEntryLifetimeExtension() {
         assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse()
         assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isFalse()
-        notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java))
+        notifGutsViewListener.onGutsOpen(entry1, notificationGuts)
         assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue()
         assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isFalse()
-        notifGutsViewListener.onGutsOpen(entry2, mock(NotificationGuts::class.java))
+        notifGutsViewListener.onGutsOpen(entry2, notificationGuts)
         assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue()
         assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isTrue()
         notifGutsViewListener.onGutsClose(entry1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 2fbe871..fbd61f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.advanceTimeBy
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -32,7 +33,6 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -46,11 +46,14 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestCoroutineScheduler
@@ -62,9 +65,8 @@
 import org.mockito.ArgumentMatchers.same
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import java.util.function.Consumer
-import kotlin.time.Duration.Companion.seconds
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
@@ -75,7 +77,6 @@
     private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
     private val keyguardRepository = FakeKeyguardRepository()
     private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
-    private val notifPipelineFlags: NotifPipelineFlags = mock()
     private val notifPipeline: NotifPipeline = mock()
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
     private val statusBarStateController: StatusBarStateController = mock()
@@ -136,13 +137,8 @@
             )
             testScheduler.runCurrent()
 
-            // WHEN: The shade is expanded
-            whenever(statusBarStateController.isExpanded).thenReturn(true)
-            statusBarStateListener.onExpandedChanged(true)
-            testScheduler.runCurrent()
-
-            // THEN: The notification is still treated as "unseen" and is not filtered out.
-            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+            // THEN: We are no longer listening for shade expansions
+            verify(statusBarStateController, never()).addCallback(any())
         }
     }
 
@@ -152,6 +148,10 @@
         keyguardRepository.setKeyguardShowing(false)
         whenever(statusBarStateController.isExpanded).thenReturn(false)
         runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+
             // WHEN: A notification is posted
             val fakeEntry = NotificationEntryBuilder().build()
             collectionListener.onEntryAdded(fakeEntry)
@@ -162,6 +162,9 @@
 
             // WHEN: The keyguard is now showing
             keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD)
+            )
             testScheduler.runCurrent()
 
             // THEN: The notification is recognized as "seen" and is filtered out.
@@ -169,6 +172,9 @@
 
             // WHEN: The keyguard goes away
             keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.AOD, to = KeyguardState.GONE)
+            )
             testScheduler.runCurrent()
 
             // THEN: The notification is shown regardless
@@ -182,9 +188,10 @@
         keyguardRepository.setKeyguardShowing(false)
         whenever(statusBarStateController.isExpanded).thenReturn(true)
         runKeyguardCoordinatorTest {
-            val fakeEntry = NotificationEntryBuilder()
+            val fakeEntry =
+                NotificationEntryBuilder()
                     .setNotification(Notification.Builder(mContext, "id").setOngoing(true).build())
-                .build()
+                    .build()
             collectionListener.onEntryAdded(fakeEntry)
 
             // WHEN: The keyguard is now showing
@@ -202,11 +209,13 @@
         keyguardRepository.setKeyguardShowing(false)
         whenever(statusBarStateController.isExpanded).thenReturn(true)
         runKeyguardCoordinatorTest {
-            val fakeEntry = NotificationEntryBuilder().build().apply {
-                row = mock<ExpandableNotificationRow>().apply {
-                    whenever(isMediaRow).thenReturn(true)
+            val fakeEntry =
+                NotificationEntryBuilder().build().apply {
+                    row =
+                        mock<ExpandableNotificationRow>().apply {
+                            whenever(isMediaRow).thenReturn(true)
+                        }
                 }
-            }
             collectionListener.onEntryAdded(fakeEntry)
 
             // WHEN: The keyguard is now showing
@@ -299,14 +308,12 @@
         runKeyguardCoordinatorTest {
             // WHEN: A new notification is posted
             val fakeSummary = NotificationEntryBuilder().build()
-            val fakeChild = NotificationEntryBuilder()
+            val fakeChild =
+                NotificationEntryBuilder()
                     .setGroup(context, "group")
                     .setGroupSummary(context, false)
                     .build()
-            GroupEntryBuilder()
-                    .setSummary(fakeSummary)
-                    .addChild(fakeChild)
-                    .build()
+            GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build()
 
             collectionListener.onEntryAdded(fakeSummary)
             collectionListener.onEntryAdded(fakeChild)
@@ -331,6 +338,10 @@
         runKeyguardCoordinatorTest {
             val fakeEntry = NotificationEntryBuilder().build()
             collectionListener.onEntryAdded(fakeEntry)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
 
             // WHEN: five seconds have passed
             testScheduler.advanceTimeBy(5.seconds)
@@ -338,10 +349,16 @@
 
             // WHEN: Keyguard is no longer showing
             keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
             testScheduler.runCurrent()
 
             // WHEN: Keyguard is shown again
             keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD)
+            )
             testScheduler.runCurrent()
 
             // THEN: The notification is now recognized as "seen" and is filtered out.
@@ -354,11 +371,17 @@
         // GIVEN: Keyguard is showing, unseen notification is present
         keyguardRepository.setKeyguardShowing(true)
         runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
             val fakeEntry = NotificationEntryBuilder().build()
             collectionListener.onEntryAdded(fakeEntry)
 
             // WHEN: Keyguard is no longer showing
             keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
 
             // WHEN: Keyguard is shown again
             keyguardRepository.setKeyguardShowing(true)
@@ -369,14 +392,212 @@
         }
     }
 
+    @Test
+    fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() {
+        // GIVEN: Keyguard is showing, not dozing, unseen notification is present
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            val firstEntry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(firstEntry)
+            testScheduler.runCurrent()
+
+            // WHEN: one second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: another unseen notification is posted
+            val secondEntry = NotificationEntryBuilder().setId(2).build()
+            collectionListener.onEntryAdded(secondEntry)
+            testScheduler.runCurrent()
+
+            // WHEN: four more seconds have passed
+            testScheduler.advanceTimeBy(4.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // THEN: The first notification is considered seen and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue()
+
+            // THEN: The second notification is still considered unseen and is not filtered out
+            assertThat(unseenFilter.shouldFilterOut(secondEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThreshold() {
+        // GIVEN: Keyguard is showing, not dozing
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: a new notification is posted
+            val entry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: five more seconds have passed
+            testScheduler.advanceTimeBy(5.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is removed
+            collectionListener.onEntryRemoved(entry, 0)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is re-posted
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one more second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // THEN: The notification is considered unseen and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThreshold() {
+        // GIVEN: Keyguard is showing, not dozing
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: a new notification is posted
+            val entry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is removed
+            collectionListener.onEntryRemoved(entry, 0)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is re-posted
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one more second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // THEN: The notification is considered unseen and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+        }
+    }
+
+    @Test
+    fun unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThreshold() {
+        // GIVEN: Keyguard is showing, not dozing
+        keyguardRepository.setKeyguardShowing(true)
+        keyguardRepository.setIsDozing(false)
+        runKeyguardCoordinatorTest {
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: a new notification is posted
+            val entry = NotificationEntryBuilder().setId(1).build()
+            collectionListener.onEntryAdded(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: one second has passed
+            testScheduler.advanceTimeBy(1.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the notification is updated
+            collectionListener.onEntryUpdated(entry)
+            testScheduler.runCurrent()
+
+            // WHEN: four more seconds have passed
+            testScheduler.advanceTimeBy(4.seconds)
+            testScheduler.runCurrent()
+
+            // WHEN: the keyguard is no longer showing
+            keyguardRepository.setKeyguardShowing(false)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+            )
+            testScheduler.runCurrent()
+
+            // WHEN: Keyguard is shown again
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+            )
+            testScheduler.runCurrent()
+
+            // THEN: The notification is considered unseen and is not filtered out.
+            assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+        }
+    }
+
     private fun runKeyguardCoordinatorTest(
         testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
     ) {
         val testDispatcher = UnconfinedTestDispatcher()
         val testScope = TestScope(testDispatcher)
-        val fakeSettings = FakeSettings().apply {
-            putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
-        }
+        val fakeSettings =
+            FakeSettings().apply {
+                putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
+            }
         val seenNotificationsProvider = SeenNotificationsProviderImpl()
         val keyguardCoordinator =
             KeyguardCoordinator(
@@ -386,8 +607,7 @@
                 keyguardNotifVisibilityProvider,
                 keyguardRepository,
                 keyguardTransitionRepository,
-                mock<KeyguardCoordinatorLogger>(),
-                notifPipelineFlags,
+                KeyguardCoordinatorLogger(logcatLogBuffer()),
                 testScope.backgroundScope,
                 sectionHeaderVisibilityProvider,
                 fakeSettings,
@@ -397,11 +617,12 @@
         keyguardCoordinator.attach(notifPipeline)
         testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
             KeyguardCoordinatorTestScope(
-                keyguardCoordinator,
-                testScope,
-                seenNotificationsProvider,
-                fakeSettings,
-            ).testBlock()
+                    keyguardCoordinator,
+                    testScope,
+                    seenNotificationsProvider,
+                    fakeSettings,
+                )
+                .testBlock()
         }
     }
 
@@ -414,10 +635,9 @@
         val testScheduler: TestCoroutineScheduler
             get() = scope.testScheduler
 
-        val onStateChangeListener: Consumer<String> =
-            withArgCaptor {
-                verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
-            }
+        val onStateChangeListener: Consumer<String> = withArgCaptor {
+            verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+        }
 
         val unseenFilter: NotifFilter
             get() = keyguardCoordinator.unseenNotifFilter
@@ -426,11 +646,11 @@
             verify(notifPipeline).addCollectionListener(capture())
         }
 
-        val onHeadsUpChangedListener: OnHeadsUpChangedListener get() =
-            withArgCaptor { verify(headsUpManager).addListener(capture()) }
+        val onHeadsUpChangedListener: OnHeadsUpChangedListener
+            get() = withArgCaptor { verify(headsUpManager).addListener(capture()) }
 
-        val statusBarStateListener: StatusBarStateController.StateListener get() =
-            withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
+        val statusBarStateListener: StatusBarStateController.StateListener
+            get() = withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
 
         var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
             get() =
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 b5e77e0..548ecde 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
@@ -18,6 +18,7 @@
 
 import static android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE;
 
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
 import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
 
 import static org.junit.Assert.assertFalse;
@@ -134,7 +135,7 @@
         setSectionIsLowPriority(false);
 
         PreparationCoordinator coordinator = new PreparationCoordinator(
-                mock(PreparationCoordinatorLogger.class),
+                new PreparationCoordinatorLogger(logcatLogBuffer()),
                 mNotifInflater,
                 mErrorManager,
                 mock(NotifViewBarn.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
index 5793364..069eec2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt
@@ -21,6 +21,7 @@
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -50,7 +51,7 @@
     private lateinit var entry2: NotificationEntry
 
     @Mock private lateinit var pipeline: NotifPipeline
-    @Mock private lateinit var logger: ShadeEventCoordinatorLogger
+    private val logger = ShadeEventCoordinatorLogger(logcatLogBuffer())
     @Mock private lateinit var executor: Executor
     @Mock private lateinit var notifRemovedByUserCallback: Runnable
     @Mock private lateinit var shadeEmptiedCallback: Runnable
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
index 6eb391a..0b61a8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt
@@ -21,14 +21,15 @@
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 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
 import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 
@@ -36,7 +37,7 @@
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
 class NotifCollectionInconsistencyTrackerTest : SysuiTestCase() {
-    private val logger: NotifCollectionLogger = mock()
+    private val logger = spy(NotifCollectionLogger(logcatLogBuffer()))
     private val entry1: NotificationEntry = NotificationEntryBuilder().setId(1).build()
     private val entry2: NotificationEntry = NotificationEntryBuilder().setId(2).build()
     private val collectionSet = mutableSetOf<String>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
new file mode 100644
index 0000000..38a8f414
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+class GroupExpansionManagerTest : SysuiTestCase() {
+    private lateinit var gem: GroupExpansionManagerImpl
+
+    private val dumpManager: DumpManager = mock()
+    private val groupMembershipManager: GroupMembershipManager = mock()
+    private val featureFlags = FakeFeatureFlags()
+
+    private val pipeline: NotifPipeline = mock()
+    private lateinit var beforeRenderListListener: OnBeforeRenderListListener
+
+    private val summary1 = notificationEntry("foo", 1)
+    private val summary2 = notificationEntry("bar", 1)
+    private val entries =
+        listOf<ListEntry>(
+            GroupEntryBuilder()
+                .setSummary(summary1)
+                .setChildren(
+                    listOf(
+                        notificationEntry("foo", 2),
+                        notificationEntry("foo", 3),
+                        notificationEntry("foo", 4)
+                    )
+                )
+                .build(),
+            GroupEntryBuilder()
+                .setSummary(summary2)
+                .setChildren(
+                    listOf(
+                        notificationEntry("bar", 2),
+                        notificationEntry("bar", 3),
+                        notificationEntry("bar", 4)
+                    )
+                )
+                .build(),
+            notificationEntry("baz", 1)
+        )
+
+    private fun notificationEntry(pkg: String, id: Int) =
+        NotificationEntryBuilder().setPkg(pkg).setId(id).build().apply { row = mock() }
+
+    @Before
+    fun setUp() {
+        whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1)
+        whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2)
+
+        gem = GroupExpansionManagerImpl(dumpManager, groupMembershipManager, featureFlags)
+    }
+
+    @Test
+    fun testNotifyOnlyOnChange_enabled() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+
+        var listenerCalledCount = 0
+        gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
+
+        gem.setGroupExpanded(summary1, false)
+        Assert.assertEquals(0, listenerCalledCount)
+        gem.setGroupExpanded(summary1, true)
+        Assert.assertEquals(1, listenerCalledCount)
+        gem.setGroupExpanded(summary2, true)
+        Assert.assertEquals(2, listenerCalledCount)
+        gem.setGroupExpanded(summary1, true)
+        Assert.assertEquals(2, listenerCalledCount)
+        gem.setGroupExpanded(summary2, false)
+        Assert.assertEquals(3, listenerCalledCount)
+    }
+
+    @Test
+    fun testNotifyOnlyOnChange_disabled() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
+
+        var listenerCalledCount = 0
+        gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
+
+        gem.setGroupExpanded(summary1, false)
+        Assert.assertEquals(1, listenerCalledCount)
+        gem.setGroupExpanded(summary1, true)
+        Assert.assertEquals(2, listenerCalledCount)
+        gem.setGroupExpanded(summary2, true)
+        Assert.assertEquals(3, listenerCalledCount)
+        gem.setGroupExpanded(summary1, true)
+        Assert.assertEquals(4, listenerCalledCount)
+        gem.setGroupExpanded(summary2, false)
+        Assert.assertEquals(5, listenerCalledCount)
+    }
+
+    @Test
+    fun testSyncWithPipeline() {
+        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+        gem.attach(pipeline)
+        beforeRenderListListener = withArgCaptor {
+            verify(pipeline).addOnBeforeRenderListListener(capture())
+        }
+
+        val listener: OnGroupExpansionChangeListener = mock()
+        gem.registerGroupExpansionChangeListener(listener)
+
+        beforeRenderListListener.onBeforeRenderList(entries)
+        verify(listener, never()).onGroupExpansionChange(any(), any())
+
+        // Expand one of the groups.
+        gem.setGroupExpanded(summary1, true)
+        verify(listener).onGroupExpansionChange(summary1.row, true)
+
+        // Empty the pipeline list and verify that the group is no longer expanded.
+        beforeRenderListListener.onBeforeRenderList(emptyList())
+        verify(listener).onGroupExpansionChange(summary1.row, false)
+        verifyNoMoreInteractions(listener)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
index ac254ab..bad56a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
@@ -46,7 +47,7 @@
     private val sectionsFeatureManager: NotificationSectionsFeatureManager = mock()
     private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
     private val viewBarn: NotifViewBarn = mock()
-    private val logger: NodeSpecBuilderLogger = mock()
+    private val logger = NodeSpecBuilderLogger(mock(), logcatLogBuffer())
 
     private var rootController: NodeController = buildFakeController("rootController")
     private var headerController0: NodeController = buildFakeController("header0")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
index 6167b46..9a60272 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
@@ -22,7 +22,7 @@
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.dump.logcatLogBuffer
 import org.junit.Assert
 import org.junit.Before
 import org.junit.Test
@@ -30,6 +30,7 @@
 import org.mockito.ArgumentMatchers.isNull
 import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.matches
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 
 @SmallTest
@@ -44,7 +45,7 @@
     private val controller5 = FakeController(mContext, "Controller5")
     private val controller6 = FakeController(mContext, "Controller6")
     private val controller7 = FakeController(mContext, "Controller7")
-    private val logger: ShadeViewDifferLogger = mock()
+    private val logger = spy(ShadeViewDifferLogger(logcatLogBuffer()))
 
     @Before
     fun setUp() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepositoryTest.kt
new file mode 100644
index 0000000..f28d9ab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepositoryTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+class NotificationExpansionRepositoryTest : SysuiTestCase() {
+    private val underTest = NotificationExpansionRepository()
+
+    @Test
+    fun setIsExpandAnimationRunning_startsAsFalse() = runTest {
+        val latest by collectLastValue(underTest.isExpandAnimationRunning)
+
+        assertThat(latest).isFalse()
+    }
+
+    @Test
+    fun setIsExpandAnimationRunning_false_emitsTrue() = runTest {
+        val latest by collectLastValue(underTest.isExpandAnimationRunning)
+
+        underTest.setIsExpandAnimationRunning(true)
+
+        assertThat(latest).isTrue()
+    }
+
+    @Test
+    fun setIsExpandAnimationRunning_false_emitsFalse() = runTest {
+        val latest by collectLastValue(underTest.isExpandAnimationRunning)
+        underTest.setIsExpandAnimationRunning(true)
+
+        // WHEN the animation is no longer running
+        underTest.setIsExpandAnimationRunning(false)
+
+        // THEN the flow emits false
+        assertThat(latest).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt
new file mode 100644
index 0000000..fe49016
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import android.app.StatusBarManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepositoryImpl
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class NotificationsInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: NotificationsInteractor
+
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val commandQueue: CommandQueue = mock()
+    private val logBuffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10)
+    private val disableFlagsLogger = DisableFlagsLogger()
+    private lateinit var disableFlagsRepository: DisableFlagsRepository
+
+    @Before
+    fun setUp() {
+        disableFlagsRepository =
+            DisableFlagsRepositoryImpl(
+                commandQueue,
+                DISPLAY_ID,
+                testScope.backgroundScope,
+                mock(),
+                logBuffer,
+                disableFlagsLogger,
+            )
+        underTest = NotificationsInteractor(disableFlagsRepository)
+    }
+
+    @Test
+    fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() {
+        val callback = getCommandQueueCallback()
+
+        callback.disable(
+            DISPLAY_ID,
+            StatusBarManager.DISABLE_NONE,
+            StatusBarManager.DISABLE2_NONE,
+            /* animate= */ false
+        )
+
+        assertThat(underTest.areNotificationAlertsEnabled()).isTrue()
+    }
+
+    @Test
+    fun disableFlags_notifAlertsDisabled_notifAlertsEnabledFalse() {
+        val callback = getCommandQueueCallback()
+
+        callback.disable(
+            DISPLAY_ID,
+            StatusBarManager.DISABLE_NOTIFICATION_ALERTS,
+            StatusBarManager.DISABLE2_NONE,
+            /* animate= */ false
+        )
+
+        assertThat(underTest.areNotificationAlertsEnabled()).isFalse()
+    }
+
+    private fun getCommandQueueCallback(): CommandQueue.Callbacks {
+        val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>()
+        verify(commandQueue).addCallback(callbackCaptor.capture())
+        return callbackCaptor.value
+    }
+
+    private companion object {
+        const val DISPLAY_ID = 1
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
index ca65987..04ffab3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.statusbar.notification.interruption;
 
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
@@ -50,7 +53,8 @@
     private HeadsUpViewBinder mViewBinder;
     @Mock private NotificationMessagingUtil mNotificationMessagingUtil;
     @Mock private RowContentBindStage mBindStage;
-    @Mock private HeadsUpViewBinderLogger mLogger;
+    private final HeadsUpViewBinderLogger mLogger = spy(
+            new HeadsUpViewBinderLogger(logcatLogBuffer()));
     @Mock private NotificationEntry mEntry;
     @Mock private ExpandableNotificationRow mRow;
 
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 d3e5816..daa45db 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
@@ -426,23 +426,8 @@
     }
 
     @Test
-    public void testShouldHeadsUp_oldWhen_flagDisabled() throws Exception {
-        ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(false);
-
-        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
-        entry.getSbn().getNotification().when = makeWhenHoursAgo(25);
-
-        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
-
-        verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
-        verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
-    }
-
-    @Test
     public void testShouldHeadsUp_oldWhen_whenNow() throws Exception {
         ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
 
@@ -455,7 +440,6 @@
     @Test
     public void testShouldHeadsUp_oldWhen_whenRecent() throws Exception {
         ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
         entry.getSbn().getNotification().when = makeWhenHoursAgo(13);
@@ -469,7 +453,6 @@
     @Test
     public void testShouldHeadsUp_oldWhen_whenZero() throws Exception {
         ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
         entry.getSbn().getNotification().when = 0L;
@@ -484,7 +467,6 @@
     @Test
     public void testShouldHeadsUp_oldWhen_whenNegative() throws Exception {
         ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
         entry.getSbn().getNotification().when = -1L;
@@ -498,7 +480,6 @@
     @Test
     public void testShouldHeadsUp_oldWhen_hasFullScreenIntent() throws Exception {
         ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
         long when = makeWhenHoursAgo(25);
 
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silent= */ false);
@@ -514,7 +495,6 @@
     @Test
     public void testShouldHeadsUp_oldWhen_isForegroundService() throws Exception {
         ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
         long when = makeWhenHoursAgo(25);
 
         NotificationEntry entry = createFgsNotification(IMPORTANCE_HIGH);
@@ -530,7 +510,6 @@
     @Test
     public void testShouldNotHeadsUp_oldWhen() throws Exception {
         ensureStateForHeadsUpWhenAwake();
-        when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
         long when = makeWhenHoursAgo(25);
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt
new file mode 100644
index 0000000..47c5e5b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.notification.stack.StackStateLogger
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class StackStateLoggerTest : SysuiTestCase() {
+    private val logBufferCounter = LogBufferCounter()
+    private lateinit var logger: StackStateLogger
+
+    @Before
+    fun setup() {
+        logger = StackStateLogger(logBufferCounter.logBuffer, logBufferCounter.logBuffer)
+    }
+
+    @Test
+    fun groupChildRemovalEvent() {
+        logger.groupChildRemovalEventProcessed(KEY)
+        verifyDidLog(1)
+        logger.groupChildRemovalAnimationEnded(KEY)
+        verifyDidLog(1)
+    }
+
+    class LogBufferCounter {
+        val recentLogs = mutableListOf<Pair<String, LogLevel>>()
+        val tracker =
+            object : LogcatEchoTracker {
+                override val logInBackgroundThread: Boolean = false
+                override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = false
+                override fun isTagLoggable(tagName: String, level: LogLevel): Boolean {
+                    recentLogs.add(tagName to level)
+                    return true
+                }
+            }
+        val logBuffer =
+            LogBuffer(name = "test", maxSize = 1, logcatEchoTracker = tracker, systrace = false)
+
+        fun verifyDidLog(times: Int) {
+            Truth.assertThat(recentLogs).hasSize(times)
+            recentLogs.clear()
+        }
+    }
+
+    private fun verifyDidLog(times: Int) {
+        logBufferCounter.verifyDidLog(times)
+    }
+
+    companion object {
+        private val KEY = "PACKAGE_NAME"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index c2f1f61..0cc0b98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -17,6 +17,10 @@
 
 package com.android.systemui.statusbar.notification.row
 
+import android.app.Notification
+import android.net.Uri
+import android.os.UserHandle
+import android.os.UserHandle.USER_ALL
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
@@ -24,18 +28,24 @@
 import com.android.internal.statusbar.IStatusBarService
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.SbnBuilder
 import com.android.systemui.statusbar.SmartReplyController
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider
 import com.android.systemui.statusbar.notification.collection.render.FakeNodeController
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
 import com.android.systemui.statusbar.notification.logging.NotificationLogger
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
+import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -44,9 +54,9 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.time.SystemClock
 import com.android.systemui.wmshell.BubblesManager
-import java.util.Optional
 import junit.framework.Assert
 import org.junit.After
 import org.junit.Before
@@ -54,8 +64,11 @@
 import org.junit.runner.RunWith
 import org.mockito.Mockito
 import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
+import java.util.Optional
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -69,7 +82,7 @@
     private val activableNotificationViewController: ActivatableNotificationViewController = mock()
     private val rivSubComponentFactory: RemoteInputViewSubcomponent.Factory = mock()
     private val metricsLogger: MetricsLogger = mock()
-    private val logBufferLogger: NotificationRowLogger = mock()
+    private val logBufferLogger = NotificationRowLogger(logcatLogBuffer(), logcatLogBuffer())
     private val listContainer: NotificationListContainer = mock()
     private val childrenContainer: NotificationChildrenContainer = mock()
     private val smartReplyConstants: SmartReplyConstants = mock()
@@ -91,10 +104,10 @@
     private val featureFlags: FeatureFlags = mock()
     private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock()
     private val bubblesManager: BubblesManager = mock()
+    private val settingsController: NotificationSettingsController = mock()
     private val dragController: ExpandableNotificationRowDragController = mock()
     private val dismissibilityProvider: NotificationDismissibilityProvider = mock()
     private val statusBarService: IStatusBarService = mock()
-
     private lateinit var controller: ExpandableNotificationRowController
 
     @Before
@@ -107,6 +120,7 @@
                 rivSubComponentFactory,
                 metricsLogger,
                 logBufferLogger,
+                NotificationChildrenContainerLogger(logcatLogBuffer()),
                 listContainer,
                 smartReplyConstants,
                 smartReplyController,
@@ -130,11 +144,16 @@
                 featureFlags,
                 peopleNotificationIdentifier,
                 Optional.of(bubblesManager),
+                settingsController,
                 dragController,
                 dismissibilityProvider,
                 statusBarService
             )
         whenever(view.childrenContainer).thenReturn(childrenContainer)
+
+        val notification = Notification.Builder(mContext).build()
+        val sbn = SbnBuilder().setNotification(notification).build()
+        whenever(view.entry).thenReturn(NotificationEntryBuilder().setSbn(sbn).build())
     }
 
     @After
@@ -147,13 +166,13 @@
         whenever(view.isParentDismissed).thenReturn(true)
 
         Assert.assertTrue(controller.offerToKeepInParentForAnimation())
-        Mockito.verify(view).setKeepInParentForDismissAnimation(true)
+        verify(view).setKeepInParentForDismissAnimation(true)
     }
 
     @Test
     fun offerKeepInParent_parentNotDismissed() {
         Assert.assertFalse(controller.offerToKeepInParentForAnimation())
-        Mockito.verify(view, never()).setKeepInParentForDismissAnimation(anyBoolean())
+        verify(view, never()).setKeepInParentForDismissAnimation(anyBoolean())
     }
 
     @Test
@@ -163,7 +182,7 @@
         whenever(view.keepInParentForDismissAnimation()).thenReturn(true)
 
         Assert.assertTrue(controller.removeFromParentIfKeptForAnimation())
-        Mockito.verify(parentView).removeChildNotification(view)
+        verify(parentView).removeChildNotification(view)
     }
 
     @Test
@@ -184,9 +203,9 @@
         controller.removeChild(childNodeController, /* isTransfer= */ true)
 
         // VERIFY the listContainer is not notified
-        Mockito.verify(childView).isChangingPosition = eq(true)
-        Mockito.verify(view).removeChildNotification(eq(childView))
-        Mockito.verify(listContainer, never()).notifyGroupChildRemoved(any(), any())
+        verify(childView).isChangingPosition = eq(true)
+        verify(view).removeChildNotification(eq(childView))
+        verify(listContainer, never()).notifyGroupChildRemoved(any(), any())
     }
 
     @Test
@@ -198,8 +217,78 @@
         controller.removeChild(childNodeController, /* isTransfer= */ false)
 
         // VERIFY the listContainer is passed the childrenContainer for transient animations
-        Mockito.verify(childView, never()).isChangingPosition = any()
-        Mockito.verify(view).removeChildNotification(eq(childView))
-        Mockito.verify(listContainer).notifyGroupChildRemoved(eq(childView), eq(childrenContainer))
+        verify(childView, never()).isChangingPosition = any()
+        verify(view).removeChildNotification(eq(childView))
+        verify(listContainer).notifyGroupChildRemoved(eq(childView), eq(childrenContainer))
+    }
+
+    @Test
+    fun registerSettingsListener_forBubbles() {
+        controller.init(mock(NotificationEntry::class.java))
+        val viewStateObserver = withArgCaptor {
+            verify(view).addOnAttachStateChangeListener(capture());
+        }
+        viewStateObserver.onViewAttachedToWindow(view);
+        verify(settingsController).addCallback(any(), any());
+    }
+
+    @Test
+    fun unregisterSettingsListener_forBubbles() {
+        controller.init(mock(NotificationEntry::class.java))
+        val viewStateObserver = withArgCaptor {
+            verify(view).addOnAttachStateChangeListener(capture());
+        }
+        viewStateObserver.onViewDetachedFromWindow(view);
+        verify(settingsController).removeCallback(any(), any());
+    }
+
+    @Test
+    fun settingsListener_invalidUri() {
+        controller.mSettingsListener.onSettingChanged(Uri.EMPTY, view.entry.sbn.userId, "1")
+
+        verify(view, never()).getPrivateLayout()
+    }
+
+    @Test
+    fun settingsListener_invalidUserId() {
+        controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, "1")
+        controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, null)
+
+        verify(view, never()).getPrivateLayout()
+    }
+
+    @Test
+    fun settingsListener_validUserId() {
+        val childView: NotificationContentView = mock()
+        whenever(view.privateLayout).thenReturn(childView)
+
+        controller.mSettingsListener.onSettingChanged(
+                BUBBLES_SETTING_URI, view.entry.sbn.userId, "1")
+        verify(childView).setBubblesEnabledForUser(true)
+
+        controller.mSettingsListener.onSettingChanged(
+                BUBBLES_SETTING_URI, view.entry.sbn.userId, "9")
+        verify(childView).setBubblesEnabledForUser(false)
+    }
+
+    @Test
+    fun settingsListener_userAll() {
+        val childView: NotificationContentView = mock()
+        whenever(view.privateLayout).thenReturn(childView)
+
+        val notification = Notification.Builder(mContext).build()
+        val sbn = SbnBuilder().setNotification(notification)
+                .setUser(UserHandle.of(USER_ALL))
+                .build()
+        whenever(view.entry).thenReturn(NotificationEntryBuilder()
+                .setSbn(sbn)
+                .setUser(UserHandle.of(USER_ALL))
+                .build())
+
+        controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 9, "1")
+        verify(childView).setBubblesEnabledForUser(true)
+
+        controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 1, "0")
+        verify(childView).setBubblesEnabledForUser(false)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index 915924f..bdc8135 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -108,7 +108,7 @@
 
         mRow.doDragCallback(0, 0);
         verify(controller).startDragAndDrop(mRow);
-        verify(mShadeController).animateCollapsePanels(eq(0), eq(true),
+        verify(mShadeController).animateCollapseShade(eq(0), eq(true),
                 eq(false), anyFloat());
         verify(mNotificationPanelLogger, times(1)).logNotificationDrag(any());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 4c83194..ac8b42a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -27,6 +27,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -87,6 +88,7 @@
 @RunWithLooper
 public class ExpandableNotificationRowTest extends SysuiTestCase {
 
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private NotificationTestHelper mNotificationTestHelper;
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
 
@@ -96,12 +98,10 @@
         mNotificationTestHelper = new NotificationTestHelper(
                 mContext,
                 mDependency,
-                TestableLooper.get(this));
+                TestableLooper.get(this),
+                mFeatureFlags);
         mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
-
-        FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
-        fakeFeatureFlags.set(Flags.SENSITIVE_REVEAL_ANIM, false);
-        mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
+        mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM);
     }
 
     @Test
@@ -183,6 +183,14 @@
     }
 
     @Test
+    public void testSetSensitiveOnNotifRowNotifiesOfHeightChange_withOtherFlagValue()
+            throws Exception {
+        FakeFeatureFlags flags = mFeatureFlags;
+        flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
+        testSetSensitiveOnNotifRowNotifiesOfHeightChange();
+    }
+
+    @Test
     public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception {
         // GIVEN a sensitive notification row that's currently redacted
         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
@@ -199,10 +207,19 @@
         // WHEN the row is set to no longer be sensitive
         row.setSensitive(false, true);
 
+        boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
         // VERIFY that the height change listener is invoked
         assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout());
         assertThat(row.getIntrinsicHeight()).isGreaterThan(0);
-        verify(listener).onHeightChanged(eq(row), eq(false));
+        verify(listener).onHeightChanged(eq(row), eq(expectAnimation));
+    }
+
+    @Test
+    public void testSetSensitiveOnGroupRowNotifiesOfHeightChange_withOtherFlagValue()
+            throws Exception {
+        FakeFeatureFlags flags = mFeatureFlags;
+        flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
+        testSetSensitiveOnGroupRowNotifiesOfHeightChange();
     }
 
     @Test
@@ -222,10 +239,19 @@
         // WHEN the row is set to no longer be sensitive
         group.setSensitive(false, true);
 
+        boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
         // VERIFY that the height change listener is invoked
         assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout());
         assertThat(group.getIntrinsicHeight()).isGreaterThan(0);
-        verify(listener).onHeightChanged(eq(group), eq(false));
+        verify(listener).onHeightChanged(eq(group), eq(expectAnimation));
+    }
+
+    @Test
+    public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange_withOtherFlagValue()
+            throws Exception {
+        FakeFeatureFlags flags = mFeatureFlags;
+        flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
+        testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange();
     }
 
     @Test
@@ -254,7 +280,7 @@
         assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0);
         assertThat(publicRow.getPrivateLayout().getMinHeight())
                 .isEqualTo(publicRow.getPublicLayout().getMinHeight());
-        verify(listener, never()).onHeightChanged(eq(publicRow), eq(false));
+        verify(listener, never()).onHeightChanged(eq(publicRow), anyBoolean());
     }
 
     private void measureAndLayout(ExpandableNotificationRow row) {
@@ -733,6 +759,80 @@
         verify(lowPriVectorDrawable, times(1)).start();
     }
 
+    @Test
+    public void isExpanded_hideSensitive_sensitiveNotExpanded()
+            throws Exception {
+        // GIVEN
+        final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+        row.setUserExpanded(true);
+        row.setOnKeyguard(false);
+        row.setSensitive(/* sensitive= */true, /* hideSensitive= */false);
+        row.setHideSensitive(/* hideSensitive= */true, /* animated= */false,
+                /* delay= */0L, /* duration= */0L);
+
+        // THEN
+        assertThat(row.isExpanded()).isFalse();
+    }
+
+    @Test
+    public void isExpanded_hideSensitive_nonSensitiveExpanded()
+            throws Exception {
+        // GIVEN
+        final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+        row.setUserExpanded(true);
+        row.setOnKeyguard(false);
+        row.setSensitive(/* sensitive= */true, /* hideSensitive= */false);
+        row.setHideSensitive(/* hideSensitive= */false, /* animated= */false,
+                /* delay= */0L, /* duration= */0L);
+
+        // THEN
+        assertThat(row.isExpanded()).isTrue();
+    }
+
+    @Test
+    public void onDisappearAnimationFinished_shouldSetFalse_headsUpAnimatingAway()
+            throws Exception {
+        final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+
+        // Initial state: suppose heads up animation in progress
+        row.setHeadsUpAnimatingAway(true);
+        assertThat(row.isHeadsUpAnimatingAway()).isTrue();
+
+        // on disappear animation ends
+        row.onAppearAnimationFinished(/* wasAppearing = */ false);
+        assertThat(row.isHeadsUpAnimatingAway()).isFalse();
+    }
+
+    @Test
+    public void onHUNAppear_cancelAppearDrawing_shouldResetAnimationState() throws Exception {
+        final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+
+        row.performAddAnimation(/* delay */ 0, /* duration */ 1000, /* isHeadsUpAppear */ true);
+
+        waitForIdleSync();
+        assertThat(row.isDrawingAppearAnimation()).isTrue();
+
+        row.cancelAppearDrawing();
+
+        waitForIdleSync();
+        assertThat(row.isDrawingAppearAnimation()).isFalse();
+    }
+
+    @Test
+    public void onHUNDisappear_cancelAppearDrawing_shouldResetAnimationState() throws Exception {
+        final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+
+        row.performAddAnimation(/* delay */ 0, /* duration */ 1000, /* isHeadsUpAppear */ false);
+
+        waitForIdleSync();
+        assertThat(row.isDrawingAppearAnimation()).isTrue();
+
+        row.cancelAppearDrawing();
+
+        waitForIdleSync();
+        assertThat(row.isDrawingAppearAnimation()).isFalse();
+    }
+
     private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable,
             Drawable rightIconDrawable) {
         ImageView iconView = mock(ImageView.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
index bdd82fd..cf5b3cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
+
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -61,7 +63,7 @@
 
         mBindPipeline = new NotifBindPipeline(
                 collection,
-                mock(NotifBindPipelineLogger.class),
+                new NotifBindPipelineLogger(logcatLogBuffer()),
                 TestableLooper.get(this).getLooper());
         mBindPipeline.setStage(mStage);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
new file mode 100644
index 0000000..d5612e8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.util.AttributeSet
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertNotNull
+import junit.framework.Assert.assertNull
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+/** Tests for [NotifLayoutInflaterFactory] */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotifLayoutInflaterFactoryTest : SysuiTestCase() {
+
+    @Mock private lateinit var attrs: AttributeSet
+
+    @Before
+    fun before() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun onCreateView_notMatchingViews_returnNull() {
+        // GIVEN
+        val layoutInflaterFactory =
+            createNotifLayoutInflaterFactoryImpl(
+                setOf(
+                    createReplacementViewFactory("TextView") { context, attrs ->
+                        FrameLayout(context)
+                    }
+                )
+            )
+
+        // WHEN
+        val createView = layoutInflaterFactory.onCreateView("ImageView", mContext, attrs)
+
+        // THEN
+        assertNull(createView)
+    }
+
+    @Test
+    fun onCreateView_matchingViews_returnReplacementView() {
+        // GIVEN
+        val layoutInflaterFactory =
+            createNotifLayoutInflaterFactoryImpl(
+                setOf(
+                    createReplacementViewFactory("TextView") { context, attrs ->
+                        FrameLayout(context)
+                    }
+                )
+            )
+
+        // WHEN
+        val createView = layoutInflaterFactory.onCreateView("TextView", mContext, attrs)
+
+        // THEN
+        assertNotNull(createView)
+        assertEquals(requireNotNull(createView)::class.java, FrameLayout::class.java)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun onCreateView_multipleFactory_throwIllegalStateException() {
+        // GIVEN
+        val layoutInflaterFactory =
+            createNotifLayoutInflaterFactoryImpl(
+                setOf(
+                    createReplacementViewFactory("TextView") { context, attrs ->
+                        FrameLayout(context)
+                    },
+                    createReplacementViewFactory("TextView") { context, attrs ->
+                        LinearLayout(context)
+                    }
+                )
+            )
+
+        // WHEN
+        layoutInflaterFactory.onCreateView("TextView", mContext, attrs)
+    }
+
+    private fun createNotifLayoutInflaterFactoryImpl(
+        replacementViewFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory>
+    ) = NotifLayoutInflaterFactory(DumpManager(), replacementViewFactories)
+
+    private fun createReplacementViewFactory(
+        replacementName: String,
+        createView: (context: Context, attrs: AttributeSet) -> View
+    ) =
+        object : NotifRemoteViewsFactory {
+            override fun instantiate(
+                parent: View?,
+                name: String,
+                context: Context,
+                attrs: AttributeSet
+            ): View? =
+                if (replacementName == name) {
+                    createView(context, attrs)
+                } else {
+                    null
+                }
+        }
+}
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 3face35..f55b0a8 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
@@ -92,6 +92,7 @@
     @Mock private ConversationNotificationProcessor mConversationNotificationProcessor;
     @Mock private InflatedSmartReplyState mInflatedSmartReplyState;
     @Mock private InflatedSmartReplyViewHolder mInflatedSmartReplies;
+    @Mock private NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
 
     private final SmartReplyStateInflater mSmartReplyStateInflater =
             new SmartReplyStateInflater() {
@@ -130,7 +131,8 @@
                 mConversationNotificationProcessor,
                 mock(MediaFeatureFlag.class),
                 mock(Executor.class),
-                mSmartReplyStateInflater);
+                mSmartReplyStateInflater,
+                mNotifLayoutInflaterFactory);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 0b90ebe..c4baa69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -250,6 +250,9 @@
             .thenReturn(actionListMarginTarget)
         view.setContainingNotification(mockContainingNotification)
 
+        // Given: controller says bubbles are enabled for the user
+        view.setBubblesEnabledForUser(true);
+
         // When: call NotificationContentView.setExpandedChild() to set the expandedChild
         view.expandedChild = mockExpandedChild
 
@@ -305,6 +308,12 @@
         // NotificationEntry, which should show bubble button
         view.onNotificationUpdated(createMockNotificationEntry(true))
 
+        // Then: no bubble yet
+        assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
+
+        // Given: controller says bubbles are enabled for the user
+        view.setBubblesEnabledForUser(true);
+
         // Then: bottom margin of actionListMarginTarget should not change, still be 20
         assertEquals(0, getMarginBottom(actionListMarginTarget))
     }
@@ -405,7 +414,6 @@
             val userMock: UserHandle = mock()
             whenever(this.sbn).thenReturn(sbnMock)
             whenever(sbnMock.user).thenReturn(userMock)
-            doReturn(showButton).whenever(view).shouldShowBubbleButton(this)
         }
 
     private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 90adabf..596e9a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -62,6 +62,7 @@
 import android.graphics.drawable.Icon;
 import android.os.Handler;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -132,6 +133,8 @@
     @Mock
     private PackageManager mMockPackageManager;
     @Mock
+    private UserManager mUserManager;
+    @Mock
     private OnUserInteractionCallback mOnUserInteractionCallback;
     @Mock
     private BubblesManager mBubblesManager;
@@ -238,6 +241,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -262,6 +266,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -314,6 +319,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -339,6 +345,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -363,6 +370,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -398,6 +406,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -423,6 +432,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -452,6 +462,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -476,6 +487,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -504,6 +516,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -532,6 +545,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -563,6 +577,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -600,6 +615,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -628,6 +644,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -663,6 +680,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -691,6 +709,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -735,6 +754,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -778,6 +798,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -822,6 +843,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -860,6 +882,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -896,6 +919,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -936,6 +960,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -967,6 +992,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -996,6 +1022,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1033,6 +1060,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1069,6 +1097,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1104,6 +1133,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1143,6 +1173,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1173,6 +1204,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1198,6 +1230,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1219,11 +1252,13 @@
 
     @Test
     public void testSelectPriorityRequestsPinPeopleTile() {
+        when(mUserManager.isSameProfileGroup(anyInt(), anyInt())).thenReturn(true);
         //WHEN channel is default importance
         mNotificationChannel.setImportantConversation(false);
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1250,10 +1285,45 @@
     }
 
     @Test
+    public void testSelectPriorityRequestsPinPeopleTile_noMultiuser() {
+        when(mUserManager.isSameProfileGroup(anyInt(), anyInt())).thenReturn(false);
+        //WHEN channel is default importance
+        mNotificationChannel.setImportantConversation(false);
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mMockPackageManager,
+                mUserManager,
+                mPeopleSpaceWidgetManager,
+                mMockINotificationManager,
+                mOnUserInteractionCallback,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                mBubbleMetadata,
+                null,
+                mIconFactory,
+                mContext,
+                true,
+                mTestHandler,
+                mTestHandler, null, Optional.of(mBubblesManager),
+                mShadeController);
+
+        // WHEN user clicks "priority"
+        mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
+
+        // and then done
+        mNotificationInfo.findViewById(R.id.done).performClick();
+
+        // No widget prompt; on a secondary user
+        verify(mPeopleSpaceWidgetManager, never()).requestPinAppWidget(any(), any());
+    }
+
+    @Test
     public void testSelectDefaultDoesNotRequestPinPeopleTile() {
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1288,6 +1358,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 3cefc99..705d52b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -52,6 +52,7 @@
 import android.graphics.Color;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
@@ -137,6 +138,8 @@
     @Mock private HeadsUpManagerPhone mHeadsUpManagerPhone;
     @Mock private ActivityStarter mActivityStarter;
 
+    @Mock private UserManager mUserManager;
+
     @Before
     public void setUp() {
         mTestableLooper = TestableLooper.get(this);
@@ -147,7 +150,7 @@
 
         mGutsManager = new NotificationGutsManager(mContext, mHandler, mHandler,
                 mAccessibilityManager,
-                mHighPriorityProvider, mINotificationManager,
+                mHighPriorityProvider, mINotificationManager, mUserManager,
                 mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
                 mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
                 Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
new file mode 100644
index 0000000..614995b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.app.ActivityManager
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.provider.Settings.Secure
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.notification.row.NotificationSettingsController.Listener
+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 com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.SecureSettings
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class NotificationSettingsControllerTest : SysuiTestCase() {
+
+    val setting1: String = Secure.NOTIFICATION_BUBBLES
+    val setting2: String = Secure.ACCESSIBILITY_ENABLED
+    val settingUri1: Uri = Secure.getUriFor(setting1)
+    val settingUri2: Uri = Secure.getUriFor(setting2)
+
+    @Mock
+    private lateinit var userTracker: UserTracker
+    private lateinit var mainHandler: Handler
+    private lateinit var backgroundHandler: Handler
+    private lateinit var testableLooper: TestableLooper
+    @Mock
+    private lateinit var secureSettings: SecureSettings
+    @Mock
+    private lateinit var dumpManager: DumpManager
+
+    @Captor
+    private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
+    @Captor
+    private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
+
+    private lateinit var controller: NotificationSettingsController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+        mainHandler = Handler(testableLooper.looper)
+        backgroundHandler = Handler(testableLooper.looper)
+        allowTestableLooperAsMainThread()
+        controller =
+                NotificationSettingsController(
+                        userTracker,
+                        mainHandler,
+                        backgroundHandler,
+                        secureSettings,
+                        dumpManager
+                )
+    }
+
+    @After
+    fun tearDown() {
+        disallowTestableLooperAsMainThread()
+    }
+
+    @Test
+    fun creationRegistersCallbacks() {
+        verify(userTracker).addCallback(any(), any())
+        verify(dumpManager).registerNormalDumpable(anyString(), eq(controller))
+    }
+    @Test
+    fun updateContentObserverRegistration_onUserChange_noSettingsListeners() {
+        verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any())
+        val userCallback = userTrackerCallbackCaptor.value
+        val userId = 9
+
+        // When: User is changed
+        userCallback.onUserChanged(userId, context)
+
+        // Validate: Nothing to do, since we aren't monitoring settings
+        verify(secureSettings, never()).unregisterContentObserver(any())
+        verify(secureSettings, never()).registerContentObserverForUser(
+                any(Uri::class.java), anyBoolean(), any(), anyInt())
+    }
+    @Test
+    fun updateContentObserverRegistration_onUserChange_withSettingsListeners() {
+        // When: someone is listening to a setting
+        controller.addCallback(settingUri1,
+                Mockito.mock(Listener::class.java))
+
+        verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any())
+        val userCallback = userTrackerCallbackCaptor.value
+        val userId = 9
+
+        // Then: User is changed
+        userCallback.onUserChanged(userId, context)
+
+        // Validate: The tracker is unregistered and re-registered with the new user
+        verify(secureSettings).unregisterContentObserver(any())
+        verify(secureSettings).registerContentObserverForUser(
+                eq(settingUri1), eq(false), any(), eq(userId))
+    }
+
+    @Test
+    fun addCallback_onlyFirstForUriRegistersObserver() {
+        controller.addCallback(settingUri1,
+                Mockito.mock(Listener::class.java))
+        verify(secureSettings).registerContentObserverForUser(
+                eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser()))
+
+        controller.addCallback(settingUri1,
+                Mockito.mock(Listener::class.java))
+        verify(secureSettings).registerContentObserverForUser(
+                any(Uri::class.java), anyBoolean(), any(), anyInt())
+    }
+
+    @Test
+    fun addCallback_secondUriRegistersObserver() {
+        controller.addCallback(settingUri1,
+                Mockito.mock(Listener::class.java))
+        verify(secureSettings).registerContentObserverForUser(
+                eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser()))
+
+        controller.addCallback(settingUri2,
+                Mockito.mock(Listener::class.java))
+        verify(secureSettings).registerContentObserverForUser(
+                eq(settingUri2), eq(false), any(), eq(ActivityManager.getCurrentUser()))
+        verify(secureSettings).registerContentObserverForUser(
+                eq(settingUri1), anyBoolean(), any(), anyInt())
+    }
+
+    @Test
+    fun removeCallback_lastUnregistersObserver() {
+        val listenerSetting1 : Listener = mock()
+        val listenerSetting2 : Listener = mock()
+        controller.addCallback(settingUri1, listenerSetting1)
+        verify(secureSettings).registerContentObserverForUser(
+                eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser()))
+
+        controller.addCallback(settingUri2, listenerSetting2)
+        verify(secureSettings).registerContentObserverForUser(
+                eq(settingUri2), anyBoolean(), any(), anyInt())
+
+        controller.removeCallback(settingUri2, listenerSetting2)
+        verify(secureSettings, never()).unregisterContentObserver(any())
+
+        controller.removeCallback(settingUri1, listenerSetting1)
+        verify(secureSettings).unregisterContentObserver(any())
+    }
+
+    @Test
+    fun addCallback_updatesCurrentValue() {
+        whenever(secureSettings.getStringForUser(
+                setting1, ActivityManager.getCurrentUser())).thenReturn("9")
+        whenever(secureSettings.getStringForUser(
+                setting2, ActivityManager.getCurrentUser())).thenReturn("5")
+
+        val listenerSetting1a : Listener = mock()
+        val listenerSetting1b : Listener = mock()
+        val listenerSetting2 : Listener = mock()
+
+        controller.addCallback(settingUri1, listenerSetting1a)
+        controller.addCallback(settingUri1, listenerSetting1b)
+        controller.addCallback(settingUri2, listenerSetting2)
+
+        testableLooper.processAllMessages()
+
+        verify(listenerSetting1a).onSettingChanged(
+                settingUri1, ActivityManager.getCurrentUser(), "9")
+        verify(listenerSetting1b).onSettingChanged(
+                settingUri1, ActivityManager.getCurrentUser(), "9")
+        verify(listenerSetting2).onSettingChanged(
+                settingUri2, ActivityManager.getCurrentUser(), "5")
+    }
+
+    @Test
+    fun removeCallback_noMoreUpdates() {
+        whenever(secureSettings.getStringForUser(
+                setting1, ActivityManager.getCurrentUser())).thenReturn("9")
+
+        val listenerSetting1a : Listener = mock()
+        val listenerSetting1b : Listener = mock()
+
+        // First, register
+        controller.addCallback(settingUri1, listenerSetting1a)
+        controller.addCallback(settingUri1, listenerSetting1b)
+        testableLooper.processAllMessages()
+
+        verify(secureSettings).registerContentObserverForUser(
+                any(Uri::class.java), anyBoolean(), capture(settingsObserverCaptor), anyInt())
+        verify(listenerSetting1a).onSettingChanged(
+                settingUri1, ActivityManager.getCurrentUser(), "9")
+        verify(listenerSetting1b).onSettingChanged(
+                settingUri1, ActivityManager.getCurrentUser(), "9")
+        Mockito.clearInvocations(listenerSetting1b)
+        Mockito.clearInvocations(listenerSetting1a)
+
+        // Remove one of them
+        controller.removeCallback(settingUri1, listenerSetting1a)
+
+        // On update, only remaining listener should get the callback
+        settingsObserverCaptor.value.onChange(false, settingUri1)
+        testableLooper.processAllMessages()
+
+        verify(listenerSetting1a, never()).onSettingChanged(
+                settingUri1, ActivityManager.getCurrentUser(), "9")
+        verify(listenerSetting1b).onSettingChanged(
+                settingUri1, ActivityManager.getCurrentUser(), "9")
+    }
+
+}
\ No newline at end of file
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 813bae8..1ab2b38 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
@@ -21,6 +21,7 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
 
 import static org.junit.Assert.assertEquals;
@@ -48,12 +49,16 @@
 import android.view.LayoutInflater;
 import android.widget.RemoteViews;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.TestableDependency;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.media.controls.util.MediaFeatureFlag;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -76,7 +81,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
-import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
+import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
@@ -90,6 +95,7 @@
 
 import org.mockito.ArgumentCaptor;
 
+import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -130,14 +136,24 @@
     private final NotificationDismissibilityProvider mDismissibilityProvider;
     public final Runnable mFutureDismissalRunnable;
     private @InflationFlag int mDefaultInflationFlags;
-    private FeatureFlags mFeatureFlags;
+    private final FakeFeatureFlags mFeatureFlags;
 
     public NotificationTestHelper(
             Context context,
             TestableDependency dependency,
             TestableLooper testLooper) {
+        this(context, dependency, testLooper, new FakeFeatureFlags());
+    }
+
+    public NotificationTestHelper(
+            Context context,
+            TestableDependency dependency,
+            TestableLooper testLooper,
+            @NonNull FakeFeatureFlags featureFlags) {
         mContext = context;
         mTestLooper = testLooper;
+        mFeatureFlags = Objects.requireNonNull(featureFlags);
+        dependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
         dependency.injectMockDependency(NotificationMediaManager.class);
         dependency.injectMockDependency(NotificationShadeWindowController.class);
         dependency.injectMockDependency(MediaOutputDialogFactory.class);
@@ -158,17 +174,18 @@
                 mock(ConversationNotificationProcessor.class),
                 mock(MediaFeatureFlag.class),
                 mock(Executor.class),
-                new MockSmartReplyInflater());
+                new MockSmartReplyInflater(),
+                mock(NotifLayoutInflaterFactory.class));
         contentBinder.setInflateSynchronously(true);
         mBindStage = new RowContentBindStage(contentBinder,
                 mock(NotifInflationErrorManager.class),
-                mock(RowContentBindStageLogger.class));
+                new RowContentBindStageLogger(logcatLogBuffer()));
 
         CommonNotifCollection collection = mock(CommonNotifCollection.class);
 
         mBindPipeline = new NotifBindPipeline(
                 collection,
-                mock(NotifBindPipelineLogger.class),
+                new NotifBindPipelineLogger(logcatLogBuffer()),
                 mTestLooper.getLooper());
         mBindPipeline.setStage(mBindStage);
 
@@ -182,17 +199,12 @@
         mFutureDismissalRunnable = mock(Runnable.class);
         when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt()))
                 .thenReturn(mFutureDismissalRunnable);
-        mFeatureFlags = mock(FeatureFlags.class);
     }
 
     public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) {
         mDefaultInflationFlags = defaultInflationFlags;
     }
 
-    public void setFeatureFlags(FeatureFlags featureFlags) {
-        mFeatureFlags = featureFlags;
-    }
-
     public ExpandableNotificationRowLogger getMockLogger() {
         return mMockLogger;
     }
@@ -526,6 +538,10 @@
             @InflationFlag int extraInflationFlags,
             int importance)
             throws Exception {
+        // NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be
+        //  set, but we do not want to override an existing value that is needed by a specific test.
+        mFeatureFlags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS);
+
         LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
                 mContext.LAYOUT_INFLATER_SERVICE);
         mRow = (ExpandableNotificationRow) inflater.inflate(
@@ -581,6 +597,7 @@
                 mock(NotificationGutsManager.class),
                 mDismissibilityProvider,
                 mock(MetricsLogger.class),
+                new NotificationChildrenContainerLogger(logcatLogBuffer()),
                 mock(SmartReplyConstants.class),
                 mock(SmartReplyController.class),
                 mFeatureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
index 7c99568..32f0fe7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
@@ -68,7 +69,7 @@
         mRowContentBindStage = new RowContentBindStage(
                 mBinder,
                 mock(NotifInflationErrorManager.class),
-                mock(RowContentBindStageLogger.class));
+                new RowContentBindStageLogger(logcatLogBuffer()));
         mRowContentBindStage.createStageParams(mEntry);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt
new file mode 100644
index 0000000..d46763d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.text.PrecomputedText
+import android.text.TextPaint
+import android.widget.TextView
+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
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class TextPrecomputerTest : SysuiTestCase() {
+
+    private lateinit var textPrecomputer: TextPrecomputer
+
+    private lateinit var textView: TextView
+
+    @Before
+    fun before() {
+        textPrecomputer = object : TextPrecomputer {}
+        textView = TextView(mContext)
+    }
+
+    @Test
+    fun precompute_returnRunnable() {
+        // WHEN
+        val precomputeResult = textPrecomputer.precompute(textView, TEXT)
+
+        // THEN
+        assertThat(precomputeResult).isInstanceOf(Runnable::class.java)
+    }
+
+    @Test
+    fun precomputeRunnable_anyText_setPrecomputedText() {
+        // WHEN
+        textPrecomputer.precompute(textView, TEXT).run()
+
+        // THEN
+        assertThat(textView.text).isInstanceOf(PrecomputedText::class.java)
+    }
+
+    @Test
+    fun precomputeRunnable_differentPrecomputedTextConfig_notSetPrecomputedText() {
+        // GIVEN
+        val precomputedTextRunnable =
+            textPrecomputer.precompute(textView, TEXT, logException = false)
+
+        // WHEN
+        textView.textMetricsParams = PrecomputedText.Params.Builder(PAINT).build()
+        precomputedTextRunnable.run()
+
+        // THEN
+        assertThat(textView.text).isInstanceOf(String::class.java)
+    }
+
+    @Test
+    fun precomputeRunnable_nullText_setNull() {
+        // GIVEN
+        textView.text = TEXT
+        val precomputedTextRunnable = textPrecomputer.precompute(textView, null)
+
+        // WHEN
+        precomputedTextRunnable.run()
+
+        // THEN
+        assertThat(textView.text).isEqualTo("")
+    }
+
+    private companion object {
+        private val PAINT = TextPaint()
+        private const val TEXT = "Example Notification Test"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
index 944eb2d..8881f42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
@@ -22,21 +22,23 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.util.mockito.any
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.Mockito.isNull
 import org.mockito.Mockito.verify
 
@@ -46,15 +48,28 @@
 
     private val keyguardRepository = FakeKeyguardRepository()
     private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
-    private val centralSurfaces: CentralSurfaces = mock()
-    private val systemClock = FakeSystemClock()
+
+    private val screenOffAnimationController =
+        mock<ScreenOffAnimationController>().also {
+            whenever(it.allowWakeUpIfDozing()).thenReturn(true)
+        }
+    private val statusBarStateController: StatusBarStateController = mock()
+    private val powerRepository = FakePowerRepository()
+    private val powerInteractor =
+        PowerInteractor(
+            powerRepository,
+            keyguardRepository,
+            FalsingCollectorFake(),
+            screenOffAnimationController,
+            statusBarStateController,
+        )
+
     private val keyguardTransitionController: LockscreenShadeTransitionController = mock()
     private val underTest =
         NotificationShelfInteractor(
             keyguardRepository,
             deviceEntryFaceAuthRepository,
-            centralSurfaces,
-            systemClock,
+            powerInteractor,
             keyguardTransitionController,
         )
 
@@ -107,10 +122,12 @@
 
     @Test
     fun goToLockedShadeFromShelf_wakesUpFromDoze() {
+        whenever(statusBarStateController.isDozing).thenReturn(true)
+
         underTest.goToLockedShadeFromShelf()
 
-        verify(centralSurfaces)
-            .wakeUpIfDozing(anyLong(), any(), eq(PowerManager.WAKE_REASON_GESTURE))
+        assertThat(powerRepository.lastWakeReason).isNotNull()
+        assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
index e9a8f3f..6221f3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -24,23 +24,26 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.accessibility.data.repository.FakeAccessibilityRepository
 import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
+import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModel
 import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.util.mockito.any
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.verify
@@ -54,14 +57,24 @@
     @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
 
     // mocks
-    @Mock private lateinit var centralSurfaces: CentralSurfaces
     @Mock private lateinit var keyguardTransitionController: LockscreenShadeTransitionController
+    @Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
 
     // fakes
     private val keyguardRepository = FakeKeyguardRepository()
     private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
-    private val systemClock = FakeSystemClock()
     private val a11yRepo = FakeAccessibilityRepository()
+    private val powerRepository = FakePowerRepository()
+    private val powerInteractor by lazy {
+        PowerInteractor(
+            powerRepository,
+            keyguardRepository,
+            FalsingCollectorFake(),
+            screenOffAnimationController,
+            statusBarStateController,
+        )
+    }
 
     // real impls
     private val a11yInteractor = AccessibilityInteractor(a11yRepo)
@@ -70,13 +83,17 @@
         NotificationShelfInteractor(
             keyguardRepository,
             deviceEntryFaceAuthRepository,
-            centralSurfaces,
-            systemClock,
+            powerInteractor,
             keyguardTransitionController,
         )
     }
     private val underTest by lazy { NotificationShelfViewModel(interactor, activatableViewModel) }
 
+    @Before
+    fun setUp() {
+        whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
+    }
+
     @Test
     fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest {
         val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
@@ -126,10 +143,12 @@
 
     @Test
     fun onClicked_goesToLockedShade() {
+        whenever(statusBarStateController.isDozing).thenReturn(true)
+
         underTest.onShelfClicked()
 
-        verify(centralSurfaces)
-            .wakeUpIfDozing(ArgumentMatchers.anyLong(), any(), eq(PowerManager.WAKE_REASON_GESTURE))
+        assertThat(powerRepository.lastWakeReason).isNotNull()
+        assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
         verify(keyguardTransitionController).goToLockedShade(Mockito.isNull(), eq(true))
     }
 }
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 09382ec..3d75288 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
@@ -20,7 +20,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -42,7 +41,6 @@
     private val bypassController = StackScrollAlgorithm.BypassController { false }
     private val statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
     private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
-    private val featureFlags = mock<FeatureFlags>()
 
     private lateinit var sut: AmbientState
 
@@ -55,8 +53,7 @@
                 sectionProvider,
                 bypassController,
                 statusBarKeyguardViewManager,
-                largeScreenShadeInterpolator,
-                featureFlags
+                largeScreenShadeInterpolator
             )
     }
 
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 f38881c..4b145d8 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,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -65,7 +64,6 @@
     @Mock private SectionHeaderController mPeopleHeaderController;
     @Mock private SectionHeaderController mAlertingHeaderController;
     @Mock private SectionHeaderController mSilentHeaderController;
-    @Mock private FeatureFlags mFeatureFlag;
 
     private NotificationSectionsManager mSectionsManager;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 05b70eb..1dc0ab0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -1,12 +1,15 @@
 package com.android.systemui.statusbar.notification.stack
 
 import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
+import android.view.LayoutInflater
+import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
@@ -14,12 +17,12 @@
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState
 import com.android.systemui.util.mockito.mock
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
+import org.junit.Assume.assumeTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -34,35 +37,47 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
-class NotificationShelfTest : SysuiTestCase() {
+open class NotificationShelfTest : SysuiTestCase() {
 
-    @Mock private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator
-    @Mock private lateinit var flags: FeatureFlags
+    open val useShelfRefactor: Boolean = false
+    open val useSensitiveReveal: Boolean = false
+    private val flags = FakeFeatureFlags()
 
-    private val shelf = NotificationShelf(
-            context,
-            /* attrs */ null,
-            /* showNotificationShelf */true
-    )
-    private val shelfState = shelf.viewState as NotificationShelf.ShelfState
-    private val ambientState = mock(AmbientState::class.java)
-    private val hostLayoutController: NotificationStackScrollLayoutController = mock()
-    private val notificationTestHelper by lazy {
-        allowTestableLooperAsMainThread()
-        NotificationTestHelper(
-                mContext,
-                mDependency,
-                TestableLooper.get(this))
-    }
+    @Mock
+    private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator
+    @Mock
+    private lateinit var ambientState: AmbientState
+    @Mock
+    private lateinit var hostLayoutController: NotificationStackScrollLayoutController
+    @Mock
+    private lateinit var hostLayout: NotificationStackScrollLayout
+    @Mock
+    private lateinit var roundnessManager: NotificationRoundnessManager
+
+    private lateinit var shelf: NotificationShelf
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        mDependency.injectTestDependency(FeatureFlags::class.java, flags)
+        flags.set(Flags.NOTIFICATION_SHELF_REFACTOR, useShelfRefactor)
+        flags.set(Flags.SENSITIVE_REVEAL_ANIM, useSensitiveReveal)
+        flags.setDefault(Flags.IMPROVED_HUN_ANIMATIONS)
+        val root = FrameLayout(context)
+        shelf = LayoutInflater.from(root.context)
+                .inflate(/* resource = */ R.layout.status_bar_notification_shelf,
+                    /* root = */root,
+                    /* attachToRoot = */false) as NotificationShelf
+
         whenever(ambientState.largeScreenShadeInterpolator).thenReturn(largeScreenShadeInterpolator)
-        whenever(ambientState.featureFlags).thenReturn(flags)
-        shelf.bind(ambientState, /* hostLayoutController */ hostLayoutController)
-        shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5)
         whenever(ambientState.isSmallScreen).thenReturn(true)
+
+        if (useShelfRefactor) {
+            shelf.bind(ambientState, hostLayout, roundnessManager)
+        } else {
+            shelf.bind(ambientState, hostLayoutController)
+        }
+        shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5)
     }
 
     @Test
@@ -311,9 +326,8 @@
     }
 
     @Test
-    fun updateState_flagTrue_largeScreen_expansionChanging_shelfAlphaUpdated_largeScreenValue() {
+    fun updateState_largeScreen_expansionChanging_shelfAlphaUpdated_largeScreenValue() {
         val expansionFraction = 0.6f
-        whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(true)
         whenever(ambientState.isSmallScreen).thenReturn(false)
         whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
             .thenReturn(0.123f)
@@ -325,20 +339,6 @@
     }
 
     @Test
-    fun updateState_flagFalse_largeScreen_expansionChanging_shelfAlphaUpdated_standardValue() {
-        val expansionFraction = 0.6f
-        whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(false)
-        whenever(ambientState.isSmallScreen).thenReturn(false)
-        whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
-            .thenReturn(0.123f)
-
-        updateState_expansionChanging_shelfAlphaUpdated(
-            expansionFraction = expansionFraction,
-            expectedAlpha = ShadeInterpolation.getContentAlpha(expansionFraction)
-        )
-    }
-
-    @Test
     fun updateState_expansionChangingWhileBouncerInTransit_shelfAlphaUpdated() {
         whenever(ambientState.isBouncerInTransit).thenReturn(true)
 
@@ -358,6 +358,127 @@
         )
     }
 
+    @Test
+    fun updateState_withNullLastVisibleBackgroundChild_hideShelf() {
+        // GIVEN
+        assumeTrue(useSensitiveReveal)
+        whenever(ambientState.stackY).thenReturn(100f)
+        whenever(ambientState.stackHeight).thenReturn(100f)
+        val paddingBetweenElements =
+            context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
+        val endOfStack = 200f + paddingBetweenElements
+        whenever(ambientState.isShadeExpanded).thenReturn(true)
+        val lastVisibleBackgroundChild = mock<ExpandableView>()
+        val expandableViewState = ExpandableViewState()
+        whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState)
+        val stackScrollAlgorithmState = StackScrollAlgorithmState()
+        stackScrollAlgorithmState.firstViewInShelf = mock()
+
+        whenever(ambientState.lastVisibleBackgroundChild).thenReturn(null)
+
+        // WHEN
+        shelf.updateState(stackScrollAlgorithmState, ambientState)
+
+        // THEN
+        val shelfState = shelf.viewState as NotificationShelf.ShelfState
+        assertEquals(true, shelfState.hidden)
+        assertEquals(endOfStack, shelfState.yTranslation)
+    }
+
+    @Test
+    fun updateState_withNullFirstViewInShelf_hideShelf() {
+        // GIVEN
+        assumeTrue(useSensitiveReveal)
+        whenever(ambientState.stackY).thenReturn(100f)
+        whenever(ambientState.stackHeight).thenReturn(100f)
+        val paddingBetweenElements =
+            context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
+        val endOfStack = 200f + paddingBetweenElements
+        whenever(ambientState.isShadeExpanded).thenReturn(true)
+        val lastVisibleBackgroundChild = mock<ExpandableView>()
+        val expandableViewState = ExpandableViewState()
+        whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState)
+        whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild)
+        val stackScrollAlgorithmState = StackScrollAlgorithmState()
+
+        stackScrollAlgorithmState.firstViewInShelf = null
+
+        // WHEN
+        shelf.updateState(stackScrollAlgorithmState, ambientState)
+
+        // THEN
+        val shelfState = shelf.viewState as NotificationShelf.ShelfState
+        assertEquals(true, shelfState.hidden)
+        assertEquals(endOfStack, shelfState.yTranslation)
+    }
+
+    @Test
+    fun updateState_withCollapsedShade_hideShelf() {
+        // GIVEN
+        assumeTrue(useSensitiveReveal)
+        whenever(ambientState.stackY).thenReturn(100f)
+        whenever(ambientState.stackHeight).thenReturn(100f)
+        val paddingBetweenElements =
+            context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
+        val endOfStack = 200f + paddingBetweenElements
+        val lastVisibleBackgroundChild = mock<ExpandableView>()
+        val expandableViewState = ExpandableViewState()
+        whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState)
+        whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild)
+        val stackScrollAlgorithmState = StackScrollAlgorithmState()
+        stackScrollAlgorithmState.firstViewInShelf = mock()
+
+        whenever(ambientState.isShadeExpanded).thenReturn(false)
+
+        // WHEN
+        shelf.updateState(stackScrollAlgorithmState, ambientState)
+
+        // THEN
+        val shelfState = shelf.viewState as NotificationShelf.ShelfState
+        assertEquals(true, shelfState.hidden)
+        assertEquals(endOfStack, shelfState.yTranslation)
+    }
+
+    @Test
+    fun updateState_withHiddenSectionBeforeShelf_hideShelf() {
+        // GIVEN
+        assumeTrue(useSensitiveReveal)
+        whenever(ambientState.stackY).thenReturn(100f)
+        whenever(ambientState.stackHeight).thenReturn(100f)
+        val paddingBetweenElements =
+            context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
+        val endOfStack = 200f + paddingBetweenElements
+        whenever(ambientState.isShadeExpanded).thenReturn(true)
+        val lastVisibleBackgroundChild = mock<ExpandableView>()
+        val expandableViewState = ExpandableViewState()
+        whenever(lastVisibleBackgroundChild.viewState).thenReturn(expandableViewState)
+        val stackScrollAlgorithmState = StackScrollAlgorithmState()
+        whenever(ambientState.lastVisibleBackgroundChild).thenReturn(lastVisibleBackgroundChild)
+
+        val ssaVisibleChild = mock<ExpandableView>()
+        val ssaVisibleChildState = ExpandableViewState()
+        ssaVisibleChildState.hidden = true
+        whenever(ssaVisibleChild.viewState).thenReturn(ssaVisibleChildState)
+
+        val ssaVisibleChild1 = mock<ExpandableView>()
+        val ssaVisibleChildState1 = ExpandableViewState()
+        ssaVisibleChildState1.hidden = true
+        whenever(ssaVisibleChild1.viewState).thenReturn(ssaVisibleChildState1)
+
+        stackScrollAlgorithmState.visibleChildren.add(ssaVisibleChild)
+        stackScrollAlgorithmState.visibleChildren.add(ssaVisibleChild1)
+        whenever(ambientState.isExpansionChanging).thenReturn(true)
+        stackScrollAlgorithmState.firstViewInShelf = ssaVisibleChild1
+
+        // WHEN
+        shelf.updateState(stackScrollAlgorithmState, ambientState)
+
+        // THEN
+        val shelfState = shelf.viewState as NotificationShelf.ShelfState
+        assertEquals(true, shelfState.hidden)
+        assertEquals(endOfStack, shelfState.yTranslation)
+    }
+
     private fun setFractionToShade(fraction: Float) {
         whenever(ambientState.fractionToShade).thenReturn(fraction)
     }
@@ -381,3 +502,25 @@
         assertEquals(expectedAlpha, shelf.viewState.alpha)
     }
 }
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationShelfWithRefactorTest : NotificationShelfTest() {
+    override val useShelfRefactor: Boolean = true
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationShelfWithSensitiveRevealTest : NotificationShelfTest() {
+    override val useSensitiveReveal: Boolean = true
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationShelfWithBothFlagsTest : NotificationShelfTest() {
+    override val useShelfRefactor: Boolean = true
+    override val useSensitiveReveal: Boolean = true
+}
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 cd0550e..6f431be 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
@@ -24,6 +25,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
@@ -32,10 +34,14 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import android.content.res.Resources;
 import android.metrics.LogMaker;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.View;
+import android.view.ViewTreeObserver;
 
 import androidx.test.filters.SmallTest;
 
@@ -44,10 +50,16 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
 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.keyguard.data.repository.KeyguardTransitionRepository;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -71,12 +83,12 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifStats;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
 import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
@@ -103,10 +115,13 @@
  * Tests for {@link NotificationStackScrollLayoutController}.
  */
 @SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner.class)
 public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
 
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     @Mock private NotificationGutsManager mNotificationGutsManager;
+    @Mock private NotificationsController mNotificationsController;
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
@@ -119,6 +134,8 @@
     @Mock private KeyguardMediaController mKeyguardMediaController;
     @Mock private SysuiStatusBarStateController mSysuiStatusBarStateController;
     @Mock private KeyguardBypassController mKeyguardBypassController;
+    @Mock private KeyguardInteractor mKeyguardInteractor;
+    @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
     @Mock private MetricsLogger mMetricsLogger;
     @Mock private DumpManager mDumpManager;
@@ -126,7 +143,6 @@
     @Mock(answer = Answers.RETURNS_SELF)
     private NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder;
     @Mock private NotificationSwipeHelper mNotificationSwipeHelper;
-    @Mock private CentralSurfaces mCentralSurfaces;
     @Mock private ScrimController mScrimController;
     @Mock private GroupExpansionManager mGroupExpansionManager;
     @Mock private SectionHeaderController mSilentHeaderController;
@@ -139,14 +155,16 @@
     @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
     @Mock private ShadeController mShadeController;
     @Mock private InteractionJankMonitor mJankMonitor;
-    @Mock private StackStateLogger mStackLogger;
-    @Mock private NotificationStackScrollLogger mLogger;
+    private final StackStateLogger mStackLogger = new StackStateLogger(logcatLogBuffer(),
+            logcatLogBuffer());
+    private final NotificationStackScrollLogger mLogger = new NotificationStackScrollLogger(
+            logcatLogBuffer(), logcatLogBuffer(), logcatLogBuffer());
     @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
-    @Mock private FeatureFlags mFeatureFlags;
     @Mock private NotificationTargetsHelper mNotificationTargetsHelper;
     @Mock private SecureSettings mSecureSettings;
     @Mock private NotificationIconAreaController mIconAreaController;
     @Mock private ActivityStarter mActivityStarter;
+    @Mock private KeyguardTransitionRepository mKeyguardTransitionRepo;
 
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -158,9 +176,13 @@
 
     @Before
     public void setUp() {
+        allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
 
+        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, false);
+
         when(mNotificationSwipeHelperBuilder.build()).thenReturn(mNotificationSwipeHelper);
+        when(mKeyguardTransitionRepo.getTransitions()).thenReturn(emptyFlow());
     }
 
     @Test
@@ -259,28 +281,84 @@
     }
 
     @Test
-    public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() {
+    public void testUpdateEmptyShadeView_bouncerShowing_flagOff_hideEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
 
-        when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
+        // WHEN the flag is off and *only* CentralSurfaces has bouncer as showing
+        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, false);
+        mController.setBouncerShowingFromCentralSurfaces(true);
+        when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false);
+
         setupShowEmptyShadeViewState(true);
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
+
+        // THEN the CentralSurfaces value is used. Since the bouncer is showing, we hide the empty
+        // view.
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ false,
                 /* areNotificationsHiddenInShade= */ false);
     }
 
     @Test
-    public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() {
+    public void testUpdateEmptyShadeView_bouncerShowing_flagOn_hideEmptyView() {
         when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
         initController(/* viewIsAttached= */ true);
 
-        when(mCentralSurfaces.isBouncerShowing()).thenReturn(false);
+        // WHEN the flag is on and *only* PrimaryBouncerInteractor has bouncer as showing
+        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
+        when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true);
+        mController.setBouncerShowingFromCentralSurfaces(false);
+
         setupShowEmptyShadeViewState(true);
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
+
+        // THEN the PrimaryBouncerInteractor value is used. Since the bouncer is showing, we
+        // hide the empty view.
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+                /* visible= */ false,
+                /* areNotificationsHiddenInShade= */ false);
+    }
+
+    @Test
+    public void testUpdateEmptyShadeView_bouncerNotShowing_flagOff_showEmptyView() {
+        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
+        initController(/* viewIsAttached= */ true);
+
+        // WHEN the flag is off and *only* CentralSurfaces has bouncer as not showing
+        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, false);
+        mController.setBouncerShowingFromCentralSurfaces(false);
+        when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true);
+
+        setupShowEmptyShadeViewState(true);
+        reset(mNotificationStackScrollLayout);
+        mController.updateShowEmptyShadeView();
+
+        // THEN the CentralSurfaces value is used. Since the bouncer isn't showing, we can show the
+        // empty view.
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(
+                /* visible= */ true,
+                /* areNotificationsHiddenInShade= */ false);
+    }
+
+    @Test
+    public void testUpdateEmptyShadeView_bouncerNotShowing_flagOn_showEmptyView() {
+        when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
+        initController(/* viewIsAttached= */ true);
+
+        // WHEN the flag is on and *only* PrimaryBouncerInteractor has bouncer as not showing
+        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
+        when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false);
+        mController.setBouncerShowingFromCentralSurfaces(true);
+
+        setupShowEmptyShadeViewState(true);
+        reset(mNotificationStackScrollLayout);
+        mController.updateShowEmptyShadeView();
+
+        // THEN the PrimaryBouncerInteractor value is used. Since the bouncer isn't showing, we
+        // can show the empty view.
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ true,
                 /* areNotificationsHiddenInShade= */ false);
@@ -540,17 +618,33 @@
                 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
     }
 
+    @Test
+    public void updateEmptyShadeView_onKeyguardTransitionToAod_hidesView() {
+        initController(/* viewIsAttached= */ true);
+        mController.onKeyguardTransitionChanged(
+                new TransitionStep(
+                        /* from= */ KeyguardState.GONE,
+                        /* to= */ KeyguardState.AOD));
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean());
+    }
+
     private LogMaker logMatcher(int category, int type) {
         return argThat(new LogMatcher(category, type));
     }
 
     private void setupShowEmptyShadeViewState(boolean toShow) {
         if (toShow) {
-            when(mSysuiStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(SHADE);
+            mController.onKeyguardTransitionChanged(
+                    new TransitionStep(
+                            /* from= */ KeyguardState.LOCKSCREEN,
+                            /* to= */ KeyguardState.GONE));
             mController.setQsFullScreen(false);
             mController.getView().removeAllViews();
         } else {
-            when(mSysuiStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
+            mController.onKeyguardTransitionChanged(
+                    new TransitionStep(
+                            /* from= */ KeyguardState.GONE,
+                            /* to= */ KeyguardState.AOD));
             mController.setQsFullScreen(true);
             mController.getView().addContainerView(mock(ExpandableNotificationRow.class));
         }
@@ -558,11 +652,14 @@
 
     private void initController(boolean viewIsAttached) {
         when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(viewIsAttached);
-
+        ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);
+        when(mNotificationStackScrollLayout.getViewTreeObserver())
+                .thenReturn(viewTreeObserver);
         mController = new NotificationStackScrollLayoutController(
                 mNotificationStackScrollLayout,
                 true,
                 mNotificationGutsManager,
+                mNotificationsController,
                 mVisibilityProvider,
                 mHeadsUpManager,
                 mNotificationRoundnessManager,
@@ -573,6 +670,9 @@
                 mSysuiStatusBarStateController,
                 mKeyguardMediaController,
                 mKeyguardBypassController,
+                mKeyguardInteractor,
+                mPrimaryBouncerInteractor,
+                mKeyguardTransitionRepo,
                 mZenModeController,
                 mNotificationLockscreenUserManager,
                 Optional.<NotificationListViewModel>empty(),
@@ -582,7 +682,6 @@
                 new FalsingManagerFake(),
                 mResources,
                 mNotificationSwipeHelperBuilder,
-                mCentralSurfaces,
                 mScrimController,
                 mGroupExpansionManager,
                 mSilentHeaderController,
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 40e7e8d..72fcdec 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
@@ -68,7 +68,9 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.EmptyShadeView;
@@ -79,9 +81,9 @@
 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;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.FooterView;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -106,12 +108,13 @@
 @TestableLooper.RunWithLooper
 public class NotificationStackScrollLayoutTest extends SysuiTestCase {
 
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private NotificationStackScrollLayout mStackScroller;  // Normally test this
     private NotificationStackScrollLayout mStackScrollerInternal;  // See explanation below
     private AmbientState mAmbientState;
     private TestableResources mTestableResources;
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
-    @Mock private CentralSurfaces mCentralSurfaces;
+    @Mock private NotificationsController mNotificationsController;
     @Mock private SysuiStatusBarStateController mBarState;
     @Mock private GroupMembershipManager mGroupMembershipManger;
     @Mock private GroupExpansionManager mGroupExpansionManager;
@@ -129,7 +132,6 @@
     @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
-    @Mock private FeatureFlags mFeatureFlags;
 
     @Before
     public void setUp() throws Exception {
@@ -143,11 +145,25 @@
                 mNotificationSectionsManager,
                 mBypassController,
                 mStatusBarKeyguardViewManager,
-                mLargeScreenShadeInterpolator,
-                mFeatureFlags
+                mLargeScreenShadeInterpolator
         ));
 
+        // Register the debug flags we use
+        assertFalse(Flags.NSSL_DEBUG_LINES.getDefault());
+        assertFalse(Flags.NSSL_DEBUG_REMOVE_ANIMATION.getDefault());
+        mFeatureFlags.set(Flags.NSSL_DEBUG_LINES, false);
+        mFeatureFlags.set(Flags.NSSL_DEBUG_REMOVE_ANIMATION, false);
+
+        // Register the feature flags we use
+        // TODO: Ideally we wouldn't need to set these unless a test actually reads them,
+        //  and then we would test both configurations, but currently they are all read
+        //  in the constructor.
+        mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM);
+        mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
+        mFeatureFlags.setDefault(Flags.NOTIFICATION_SHELF_REFACTOR);
+
         // Inject dependencies before initializing the layout
+        mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
         mDependency.injectTestDependency(SysuiStatusBarStateController.class, mBarState);
         mDependency.injectMockDependency(ShadeController.class);
         mDependency.injectTestDependency(
@@ -176,13 +192,18 @@
         mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper,
                 mNotificationStackSizeCalculator);
         mStackScroller = spy(mStackScrollerInternal);
-        mStackScroller.setShelfController(notificationShelfController);
-        mStackScroller.setCentralSurfaces(mCentralSurfaces);
+        if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+            mStackScroller.setShelfController(notificationShelfController);
+        }
+        mStackScroller.setNotificationsController(mNotificationsController);
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
         when(mStackScrollLayoutController.getNotificationRoundnessManager())
                 .thenReturn(mNotificationRoundnessManager);
         mStackScroller.setController(mStackScrollLayoutController);
+        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+            mStackScroller.setShelf(mNotificationShelf);
+        }
 
         doNothing().when(mGroupExpansionManager).collapseGroups();
         doNothing().when(mExpandHelper).cancelImmediately();
@@ -899,7 +920,6 @@
     @Test
     public void testWindowInsetAnimationProgress_updatesBottomInset() {
         int bottomImeInset = 100;
-        mStackScrollerInternal.setAnimatedInsetsEnabled(true);
         WindowInsets windowInsets = new WindowInsets.Builder()
                 .setInsets(ime(), Insets.of(0, 0, 0, bottomImeInset)).build();
         ArrayList<WindowInsetsAnimation> windowInsetsAnimations = new ArrayList<>();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index df65c09..85a2bdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -47,6 +47,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
@@ -83,7 +84,7 @@
     private Handler mHandler;
     private ExpandableNotificationRow mNotificationRow;
     private Runnable mFalsingCheck;
-    private FeatureFlags mFeatureFlags;
+    private final FeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
     private static final int FAKE_ROW_WIDTH = 20;
     private static final int FAKE_ROW_HEIGHT = 20;
@@ -96,7 +97,6 @@
         mCallback = mock(NotificationSwipeHelper.NotificationCallback.class);
         mListener = mock(NotificationMenuRowPlugin.OnMenuEventListener.class);
         mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
-        mFeatureFlags = mock(FeatureFlags.class);
         mSwipeHelper = spy(new NotificationSwipeHelper(
                 mContext.getResources(),
                 ViewConfiguration.get(mContext),
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
index 45725ce..e30947c 100644
--- 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
@@ -18,6 +18,7 @@
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
 class NotificationTargetsHelperTest : SysuiTestCase() {
+    private val featureFlags = FakeFeatureFlags()
     lateinit var notificationTestHelper: NotificationTestHelper
     private val sectionsManager: NotificationSectionsManager = mock()
     private val stackScrollLayout: NotificationStackScrollLayout = mock()
@@ -26,10 +27,10 @@
     fun setUp() {
         allowTestableLooperAsMainThread()
         notificationTestHelper =
-            NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+            NotificationTestHelper(mContext, mDependency, TestableLooper.get(this), featureFlags)
     }
 
-    private fun notificationTargetsHelper() = NotificationTargetsHelper(FakeFeatureFlags())
+    private fun notificationTargetsHelper() = NotificationTargetsHelper(featureFlags)
 
     @Test
     fun targetsForFirstNotificationInGroup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 88b1eb3..987861d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -8,8 +8,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.EmptyShadeView
 import com.android.systemui.statusbar.NotificationShelf
@@ -46,7 +44,6 @@
     private val dumpManager = mock<DumpManager>()
     private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
     private val notificationShelf = mock<NotificationShelf>()
-    private val featureFlags = mock<FeatureFlags>()
     private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
         layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
     }
@@ -57,7 +54,6 @@
             /* bypassController */ { false },
             mStatusBarKeyguardViewManager,
             largeScreenShadeInterpolator,
-            featureFlags,
         )
 
     private val testableResources = mContext.getOrCreateTestableResources()
@@ -191,12 +187,10 @@
     }
 
     @Test
-    fun resetViewStates_flagTrue_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() {
+    fun resetViewStates_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() {
         val expansionFraction = 0.6f
         val surfaceAlpha = 123f
         ambientState.isSmallScreen = false
-        whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
-                .thenReturn(true)
         whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
         whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
             .thenReturn(surfaceAlpha)
@@ -208,23 +202,6 @@
     }
 
     @Test
-    fun resetViewStates_flagFalse_largeScreen_expansionChanging_alphaUpdated_standardValue() {
-        val expansionFraction = 0.6f
-        val surfaceAlpha = 123f
-        ambientState.isSmallScreen = false
-        whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
-                .thenReturn(false)
-        whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
-        whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
-            .thenReturn(surfaceAlpha)
-
-        resetViewStates_expansionChanging_notificationAlphaUpdated(
-            expansionFraction = expansionFraction,
-            expectedAlpha = getContentAlpha(expansionFraction),
-        )
-    }
-
-    @Test
     fun expansionChanging_largeScreen_bouncerInTransit_alphaUpdated_bouncerValues() {
         ambientState.isSmallScreen = false
         whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
@@ -835,6 +812,74 @@
         )
     }
 
+    // region shouldPinHunToBottomOfExpandedQs
+    @Test
+    fun shouldHunBeVisibleWhenScrolled_mustStayOnScreenFalse_false() {
+        assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+            /* mustStayOnScreen= */false,
+            /* headsUpIsVisible= */false,
+            /* showingPulsing= */false,
+            /* isOnKeyguard=*/false,
+            /*headsUpOnKeyguard=*/false
+        )).isFalse()
+    }
+
+    @Test
+    fun shouldPinHunToBottomOfExpandedQs_headsUpIsVisible_false() {
+        assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+            /* mustStayOnScreen= */true,
+            /* headsUpIsVisible= */true,
+            /* showingPulsing= */false,
+            /* isOnKeyguard=*/false,
+            /*headsUpOnKeyguard=*/false
+        )).isFalse()
+    }
+
+    @Test
+    fun shouldHunBeVisibleWhenScrolled_showingPulsing_false() {
+        assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+            /* mustStayOnScreen= */true,
+            /* headsUpIsVisible= */false,
+            /* showingPulsing= */true,
+            /* isOnKeyguard=*/false,
+            /* headsUpOnKeyguard= */false
+        )).isFalse()
+    }
+
+    @Test
+    fun shouldHunBeVisibleWhenScrolled_isOnKeyguard_false() {
+        assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+            /* mustStayOnScreen= */true,
+            /* headsUpIsVisible= */false,
+            /* showingPulsing= */false,
+            /* isOnKeyguard=*/true,
+            /* headsUpOnKeyguard= */false
+        )).isFalse()
+    }
+
+    @Test
+    fun shouldHunBeVisibleWhenScrolled_isNotOnKeyguard_true() {
+        assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+            /* mustStayOnScreen= */true,
+            /* headsUpIsVisible= */false,
+            /* showingPulsing= */false,
+            /* isOnKeyguard=*/false,
+            /* headsUpOnKeyguard= */false
+        )).isTrue()
+    }
+
+    @Test
+    fun shouldHunBeVisibleWhenScrolled_headsUpOnKeyguard_true() {
+        assertThat(stackScrollAlgorithm.shouldHunBeVisibleWhenScrolled(
+            /* mustStayOnScreen= */true,
+            /* headsUpIsVisible= */false,
+            /* showingPulsing= */false,
+            /* isOnKeyguard=*/true,
+            /* headsUpOnKeyguard= */true
+        )).isTrue()
+    }
+    // endregion
+
     private fun createHunViewMock(
             isShadeOpen: Boolean,
             fullyVisible: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
new file mode 100644
index 0000000..7bbb094
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.stack.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SharedNotificationContainerInteractorTest : SysuiTestCase() {
+    private lateinit var configurationRepository: FakeConfigurationRepository
+    private lateinit var underTest: SharedNotificationContainerInteractor
+
+    @Before
+    fun setUp() {
+        configurationRepository = FakeConfigurationRepository()
+        underTest =
+            SharedNotificationContainerInteractor(
+                configurationRepository,
+                mContext,
+            )
+    }
+
+    @Test
+    fun validateConfigValues() = runTest {
+        overrideResource(R.bool.config_use_split_notification_shade, true)
+        overrideResource(R.bool.config_use_large_screen_shade_header, false)
+        overrideResource(R.dimen.notification_panel_margin_horizontal, 0)
+        overrideResource(R.dimen.notification_panel_margin_bottom, 10)
+        overrideResource(R.dimen.notification_panel_margin_top, 10)
+        overrideResource(R.dimen.large_screen_shade_header_height, 0)
+
+        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+        configurationRepository.onAnyConfigurationChange()
+        runCurrent()
+
+        val lastDimens = dimens()!!
+
+        assertThat(lastDimens.useSplitShade).isTrue()
+        assertThat(lastDimens.useLargeScreenHeader).isFalse()
+        assertThat(lastDimens.marginHorizontal).isEqualTo(0)
+        assertThat(lastDimens.marginBottom).isGreaterThan(0)
+        assertThat(lastDimens.marginTop).isGreaterThan(0)
+        assertThat(lastDimens.marginTopLargeScreen).isEqualTo(0)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
new file mode 100644
index 0000000..afd9954
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SharedNotificationContainerViewModelTest : SysuiTestCase() {
+    private lateinit var configurationRepository: FakeConfigurationRepository
+    private lateinit var sharedNotificationContainerInteractor:
+        SharedNotificationContainerInteractor
+    private lateinit var underTest: SharedNotificationContainerViewModel
+
+    @Before
+    fun setUp() {
+        configurationRepository = FakeConfigurationRepository()
+        sharedNotificationContainerInteractor =
+            SharedNotificationContainerInteractor(
+                configurationRepository,
+                mContext,
+            )
+        underTest = SharedNotificationContainerViewModel(sharedNotificationContainerInteractor)
+    }
+
+    @Test
+    fun validateMarginStartInSplitShade() = runTest {
+        overrideResource(R.bool.config_use_split_notification_shade, true)
+        overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
+
+        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+        configurationRepository.onAnyConfigurationChange()
+        runCurrent()
+
+        val lastDimens = dimens()!!
+
+        assertThat(lastDimens.marginStart).isEqualTo(0)
+    }
+
+    @Test
+    fun validateMarginStart() = runTest {
+        overrideResource(R.bool.config_use_split_notification_shade, false)
+        overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
+
+        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+        configurationRepository.onAnyConfigurationChange()
+        runCurrent()
+
+        val lastDimens = dimens()!!
+
+        assertThat(lastDimens.marginStart).isEqualTo(20)
+    }
+
+    @Test
+    fun validateMarginEnd() = runTest {
+        overrideResource(R.dimen.notification_panel_margin_horizontal, 50)
+
+        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+        configurationRepository.onAnyConfigurationChange()
+        runCurrent()
+
+        val lastDimens = dimens()!!
+
+        assertThat(lastDimens.marginEnd).isEqualTo(50)
+    }
+
+    @Test
+    fun validateMarginBottom() = runTest {
+        overrideResource(R.dimen.notification_panel_margin_bottom, 50)
+
+        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+        configurationRepository.onAnyConfigurationChange()
+        runCurrent()
+
+        val lastDimens = dimens()!!
+
+        assertThat(lastDimens.marginBottom).isEqualTo(50)
+    }
+
+    @Test
+    fun validateMarginTopWithLargeScreenHeader() = runTest {
+        overrideResource(R.bool.config_use_large_screen_shade_header, true)
+        overrideResource(R.dimen.large_screen_shade_header_height, 50)
+        overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+        configurationRepository.onAnyConfigurationChange()
+        runCurrent()
+
+        val lastDimens = dimens()!!
+
+        assertThat(lastDimens.marginTop).isEqualTo(50)
+    }
+
+    @Test
+    fun validateMarginTop() = runTest {
+        overrideResource(R.bool.config_use_large_screen_shade_header, false)
+        overrideResource(R.dimen.large_screen_shade_header_height, 50)
+        overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+        configurationRepository.onAnyConfigurationChange()
+        runCurrent()
+
+        val lastDimens = dimens()!!
+
+        assertThat(lastDimens.marginTop).isEqualTo(0)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index 4a30800..68f2728 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -30,7 +30,9 @@
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -64,10 +66,12 @@
     @Mock private lateinit var biometricUnlockController: BiometricUnlockController
     @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator
     @Mock private lateinit var shadeController: ShadeController
+    @Mock private lateinit var shadeViewController: ShadeViewController
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
     @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager
     @Mock private lateinit var statusBarWindowController: StatusBarWindowController
+    @Mock private lateinit var notifShadeWindowController: NotificationShadeWindowController
     @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@@ -89,9 +93,12 @@
                 Lazy { biometricUnlockController },
                 Lazy { keyguardViewMediator },
                 Lazy { shadeController },
+                Lazy { shadeViewController },
                 Lazy { statusBarKeyguardViewManager },
+                Lazy { notifShadeWindowController },
                 activityLaunchAnimator,
                 context,
+                DISPLAY_ID,
                 lockScreenUserManager,
                 statusBarWindowController,
                 wakefulnessLifecycle,
@@ -271,4 +278,8 @@
         mainExecutor.runAllReady()
         verify(statusBarStateController).setLeaveOpenOnKeyguardHide(true)
     }
+
+    private companion object {
+        private const val DISPLAY_ID = 0
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index e680a4e..e4a2236 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -34,6 +34,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
@@ -51,6 +52,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dagger.NightDisplayListenerModule;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSHost;
@@ -111,6 +113,8 @@
     @Mock private DataSaverController mDataSaverController;
     @Mock private ManagedProfileController mManagedProfileController;
     @Mock private NightDisplayListener mNightDisplayListener;
+    @Mock(answer = Answers.RETURNS_SELF)
+    private NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
     @Mock private ReduceBrightColorsController mReduceBrightColorsController;
     @Mock private DeviceControlsController mDeviceControlsController;
     @Mock private WalletController mWalletController;
@@ -151,6 +155,7 @@
                 .thenReturn(TEST_CUSTOM_SAFETY_PKG);
         Context context = Mockito.spy(mContext);
         when(context.getPackageManager()).thenReturn(mPackageManager);
+        when(mNightDisplayListenerBuilder.build()).thenReturn(mNightDisplayListener);
 
         mAutoTileManager = createAutoTileManager(context);
         mAutoTileManager.init();
@@ -167,7 +172,7 @@
             HotspotController hotspotController,
             DataSaverController dataSaverController,
             ManagedProfileController managedProfileController,
-            NightDisplayListener nightDisplayListener,
+            NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
             CastController castController,
             ReduceBrightColorsController reduceBrightColorsController,
             DeviceControlsController deviceControlsController,
@@ -180,7 +185,7 @@
                 hotspotController,
                 dataSaverController,
                 managedProfileController,
-                nightDisplayListener,
+                mNightDisplayListenerBuilder,
                 castController,
                 reduceBrightColorsController,
                 deviceControlsController,
@@ -191,7 +196,7 @@
 
     private AutoTileManager createAutoTileManager(Context context) {
         return createAutoTileManager(context, mAutoAddTrackerBuilder, mHotspotController,
-                mDataSaverController, mManagedProfileController, mNightDisplayListener,
+                mDataSaverController, mManagedProfileController, mNightDisplayListenerBuilder,
                 mCastController, mReduceBrightColorsController, mDeviceControlsController,
                 mWalletController, mSafetyController, mIsReduceBrightColorsAvailable);
     }
@@ -204,7 +209,7 @@
         HotspotController hC = mock(HotspotController.class);
         DataSaverController dSC = mock(DataSaverController.class);
         ManagedProfileController mPC = mock(ManagedProfileController.class);
-        NightDisplayListener nDS = mock(NightDisplayListener.class);
+        NightDisplayListenerModule.Builder nDSB = mock(NightDisplayListenerModule.Builder.class);
         CastController cC = mock(CastController.class);
         ReduceBrightColorsController rBC = mock(ReduceBrightColorsController.class);
         DeviceControlsController dCC = mock(DeviceControlsController.class);
@@ -212,14 +217,14 @@
         SafetyController sC = mock(SafetyController.class);
 
         AutoTileManager manager =
-                createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDS, cC, rBC,
+                createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDSB, cC, rBC,
                         dCC, wC, sC, true);
 
         verify(tracker, never()).initialize();
         verify(hC, never()).addCallback(any());
         verify(dSC, never()).addCallback(any());
         verify(mPC, never()).addCallback(any());
-        verify(nDS, never()).setCallback(any());
+        verifyNoMoreInteractions(nDSB);
         verify(cC, never()).addCallback(any());
         verify(rBC, never()).addCallback(any());
         verify(dCC, never()).setCallback(any());
@@ -615,6 +620,15 @@
         createAutoTileManager(mContext).destroy();
     }
 
+    @Test
+    public void testUserChange_newNightDisplayListenerCreated() {
+        UserHandle newUser = UserHandle.of(1000);
+        mAutoTileManager.changeUser(newUser);
+        InOrder inOrder = inOrder(mNightDisplayListenerBuilder);
+        inOrder.verify(mNightDisplayListenerBuilder).setUser(newUser.getIdentifier());
+        inOrder.verify(mNightDisplayListenerBuilder).build();
+    }
+
     // Will only notify if it's listening
     private void changeValue(String key, int value) {
         mSecureSettings.putIntForUser(key, value, USER);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 7acf398..481f7f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -39,6 +41,8 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.testing.TestableResources;
+import android.view.HapticFeedbackConstants;
+import android.view.ViewRootImpl;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.LatencyTracker;
@@ -47,6 +51,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -118,12 +123,18 @@
     private VibratorHelper mVibratorHelper;
     @Mock
     private BiometricUnlockLogger mLogger;
+    @Mock
+    private ViewRootImpl mViewRootImpl;
     private final FakeSystemClock mSystemClock = new FakeSystemClock();
+    private FakeFeatureFlags mFeatureFlags;
     private BiometricUnlockController mBiometricUnlockController;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mFeatureFlags = new FakeFeatureFlags();
+        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
         TestableResources res = getContext().getOrCreateTestableResources();
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
@@ -142,11 +153,13 @@
                 mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
                 mAuthController, mStatusBarStateController,
                 mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,
-                mSystemClock
+                mSystemClock,
+                mFeatureFlags
         );
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.addListener(mBiometricUnlockEventsListener);
         when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker);
+        when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(mViewRootImpl);
     }
 
     @Test
@@ -418,7 +431,25 @@
 
     @Test
     public void onFPFailureNoHaptics_notInteractive_showLockScreen() {
-        // GIVEN no vibrator and device is dreaming
+        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, true);
+
+        // GIVEN no vibrator and device is not interactive
+        when(mVibratorHelper.hasVibrator()).thenReturn(false);
+        when(mUpdateMonitor.isDeviceInteractive()).thenReturn(false);
+        when(mUpdateMonitor.isDreaming()).thenReturn(false);
+
+        // WHEN FP fails
+        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+
+        // THEN wakeup the device
+        verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
+    }
+
+    @Test
+    public void onFPFailureNoHaptics_notInteractive_showLockScreen_doNotListenOccludingApps() {
+        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
+
+        // GIVEN no vibrator and device is not interactive
         when(mVibratorHelper.hasVibrator()).thenReturn(false);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(false);
         when(mUpdateMonitor.isDreaming()).thenReturn(false);
@@ -432,6 +463,24 @@
 
     @Test
     public void onFPFailureNoHaptics_dreaming_showLockScreen() {
+        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, true);
+
+        // GIVEN no vibrator and device is dreaming
+        when(mVibratorHelper.hasVibrator()).thenReturn(false);
+        when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
+        when(mUpdateMonitor.isDreaming()).thenReturn(true);
+
+        // WHEN FP fails
+        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+
+        // THEN never wakeup the device
+        verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+    }
+
+    @Test
+    public void onFPFailureNoHaptics_dreaming_showLockScreen_doNotListeOccludingApps() {
+        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
+
         // GIVEN no vibrator and device is dreaming
         when(mVibratorHelper.hasVibrator()).thenReturn(false);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
@@ -484,6 +533,31 @@
     }
 
     @Test
+    public void onSideFingerprintSuccess_oldPowerButtonPress_playOneWayHaptic() {
+        // GIVEN oneway haptics is enabled
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+        // GIVEN side fingerprint enrolled, last wake reason was power button
+        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+        when(mWakefulnessLifecycle.getLastWakeReason())
+                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
+
+        // GIVEN last wake time was 500ms ago
+        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
+        mSystemClock.advanceTime(500);
+
+        // WHEN biometric fingerprint succeeds
+        givenFingerprintModeUnlockCollapsing();
+        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
+                true);
+
+        // THEN vibrate the device
+        verify(mVibratorHelper).performHapticFeedback(
+                any(),
+                eq(HapticFeedbackConstants.CONFIRM)
+        );
+    }
+
+    @Test
     public void onSideFingerprintSuccess_recentGestureWakeUp_playHaptic() {
         // GIVEN side fingerprint enrolled, wakeup just happened
         when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
@@ -503,6 +577,30 @@
     }
 
     @Test
+    public void onSideFingerprintSuccess_recentGestureWakeUp_playOnewayHaptic() {
+        //GIVEN oneway haptics is enabled
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+        // GIVEN side fingerprint enrolled, wakeup just happened
+        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
+
+        // GIVEN last wake reason was from a gesture
+        when(mWakefulnessLifecycle.getLastWakeReason())
+                .thenReturn(PowerManager.WAKE_REASON_GESTURE);
+
+        // WHEN biometric fingerprint succeeds
+        givenFingerprintModeUnlockCollapsing();
+        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
+                true);
+
+        // THEN vibrate the device
+        verify(mVibratorHelper).performHapticFeedback(
+                any(),
+                eq(HapticFeedbackConstants.CONFIRM)
+        );
+    }
+
+    @Test
     public void onSideFingerprintFail_alwaysPlaysHaptic() {
         // GIVEN side fingerprint enrolled, last wake reason was recent power button
         when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
@@ -518,6 +616,26 @@
     }
 
     @Test
+    public void onSideFingerprintFail_alwaysPlaysOneWayHaptic() {
+        // GIVEN oneway haptics is enabled
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+        // GIVEN side fingerprint enrolled, last wake reason was recent power button
+        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+        when(mWakefulnessLifecycle.getLastWakeReason())
+                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
+        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
+
+        // WHEN biometric fingerprint fails
+        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+
+        // THEN always vibrate the device
+        verify(mVibratorHelper).performHapticFeedback(
+                any(),
+                eq(HapticFeedbackConstants.REJECT)
+        );
+    }
+
+    @Test
     public void onFingerprintDetect_showBouncer() {
         // WHEN fingerprint detect occurs
         mBiometricUnlockController.onBiometricDetected(UserHandle.USER_CURRENT,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 3870d99..8545b89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -18,9 +18,9 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
@@ -145,38 +145,33 @@
 
     @Test
     public void testDisableNotificationShade() {
-        when(mCentralSurfaces.getDisabled1()).thenReturn(StatusBarManager.DISABLE_NONE);
-        when(mCentralSurfaces.getDisabled2()).thenReturn(StatusBarManager.DISABLE_NONE);
+        // Start with nothing disabled
+        mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
+                StatusBarManager.DISABLE2_NONE, false);
+
         when(mCommandQueue.panelsEnabled()).thenReturn(false);
+        // WHEN the new disable flags have the shade disabled
         mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
                 StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
 
-        verify(mCentralSurfaces).updateQsExpansionEnabled();
+        // THEN the shade is collapsed
         verify(mShadeController).animateCollapseShade();
-
-        // Trying to open it does nothing.
-        mSbcqCallbacks.animateExpandNotificationsPanel();
-        verify(mShadeViewController, never()).expandToNotifications();
-        mSbcqCallbacks.animateExpandSettingsPanel(null);
-        verify(mShadeViewController, never()).expand(anyBoolean());
     }
 
     @Test
     public void testEnableNotificationShade() {
-        when(mCentralSurfaces.getDisabled1()).thenReturn(StatusBarManager.DISABLE_NONE);
-        when(mCentralSurfaces.getDisabled2())
-                .thenReturn(StatusBarManager.DISABLE2_NOTIFICATION_SHADE);
+        // Start with the shade disabled
+        mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
+                StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
+        reset(mShadeController);
+
         when(mCommandQueue.panelsEnabled()).thenReturn(true);
+        // WHEN the new disable flags have the shade enabled
         mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
                 StatusBarManager.DISABLE2_NONE, false);
-        verify(mCentralSurfaces).updateQsExpansionEnabled();
-        verify(mShadeController, never()).animateCollapseShade();
 
-        // Can now be opened.
-        mSbcqCallbacks.animateExpandNotificationsPanel();
-        verify(mShadeViewController).expandToNotifications();
-        mSbcqCallbacks.animateExpandSettingsPanel(null);
-        verify(mShadeViewController).expandToQs();
+        // THEN the shade is not collapsed
+        verify(mShadeController, never()).animateCollapseShade();
     }
 
     @Test
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 7702630..39b2948 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
@@ -29,7 +29,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
@@ -98,7 +97,9 @@
 import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.back.domain.interactor.BackActionInteractor;
 import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.charging.WiredChargingRippleController;
 import com.android.systemui.classifier.FalsingCollectorFake;
@@ -113,8 +114,8 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
+import com.android.systemui.log.LogBuffer;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.notetask.NoteTaskController;
 import com.android.systemui.plugins.ActivityStarter;
@@ -122,13 +123,13 @@
 import com.android.systemui.plugins.PluginDependencyProvider;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.recents.ScreenPinningRequest;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.shade.CameraLauncher;
 import com.android.systemui.shade.NotificationPanelView;
 import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeController;
@@ -145,6 +146,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarState;
@@ -158,6 +160,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
@@ -250,7 +253,6 @@
     @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private StatusBarSignalPolicy mStatusBarSignalPolicy;
-    @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private AssistManager mAssistManager;
     @Mock private NotificationGutsManager mNotificationGutsManager;
@@ -259,6 +261,7 @@
     @Mock private AccessibilityFloatingMenuController mAccessibilityFloatingMenuController;
     @Mock private SysuiColorExtractor mColorExtractor;
     private WakefulnessLifecycle mWakefulnessLifecycle;
+    @Mock private PowerInteractor mPowerInteractor;
     @Mock private ColorExtractor.GradientColors mGradientColors;
     @Mock private PulseExpansionHandler mPulseExpansionHandler;
     @Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
@@ -273,10 +276,14 @@
     @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
     @Mock private NotificationIconAreaController mNotificationIconAreaController;
     @Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+    @Mock private Lazy<NotificationShadeWindowViewController>
+            mNotificationShadeWindowViewControllerLazy;
+    @Mock private NotificationShelfController mNotificationShelfController;
     @Mock private DozeParameters mDozeParameters;
     @Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
     @Mock private LockscreenWallpaper mLockscreenWallpaper;
     @Mock private DozeServiceHost mDozeServiceHost;
+    @Mock private BackActionInteractor mBackActionInteractor;
     @Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
     @Mock private VolumeComponent mVolumeComponent;
     @Mock private CommandQueue mCommandQueue;
@@ -287,7 +294,6 @@
     @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
     @Mock private ScreenPinningRequest mScreenPinningRequest;
     @Mock private PluginDependencyProvider mPluginDependencyProvider;
-    @Mock private KeyguardDismissUtil mKeyguardDismissUtil;
     @Mock private ExtensionController mExtensionController;
     @Mock private UserInfoControllerImpl mUserInfoControllerImpl;
     @Mock private PhoneStatusBarPolicy mPhoneStatusBarPolicy;
@@ -424,10 +430,10 @@
         when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper);
         when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
         when(mCameraLauncherLazy.get()).thenReturn(mCameraLauncher);
+        when(mNotificationShadeWindowViewControllerLazy.get())
+                .thenReturn(mNotificationShadeWindowViewController);
 
         when(mStatusBarComponentFactory.create()).thenReturn(mCentralSurfacesComponent);
-        when(mCentralSurfacesComponent.getNotificationShadeWindowViewController()).thenReturn(
-                mNotificationShadeWindowViewController);
         doAnswer(invocation -> {
             ((Runnable) invocation.getArgument(0)).run();
             return null;
@@ -435,16 +441,19 @@
 
         mShadeController = spy(new ShadeControllerImpl(
                 mCommandQueue,
+                mMainExecutor,
+                mock(LogBuffer.class),
                 mKeyguardStateController,
                 mStatusBarStateController,
                 mStatusBarKeyguardViewManager,
                 mStatusBarWindowController,
+                mDeviceProvisionedController,
                 mNotificationShadeWindowController,
                 mContext.getSystemService(WindowManager.class),
+                () -> mNotificationPanelViewController,
                 () -> mAssistManager,
                 () -> mNotificationGutsManager
         ));
-        mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
         mShadeController.setNotificationShadeWindowViewController(
                 mNotificationShadeWindowViewController);
         mShadeController.setNotificationPresenter(mNotificationPresenter);
@@ -484,14 +493,17 @@
                 mMetricsLogger,
                 mShadeLogger,
                 mUiBgExecutor,
+                mNotificationPanelViewController,
                 mNotificationMediaManager,
                 mLockscreenUserManager,
                 mRemoteInputManager,
+                mQuickSettingsController,
                 mUserSwitcherController,
                 mBatteryController,
                 mColorExtractor,
                 mScreenLifecycle,
                 mWakefulnessLifecycle,
+                mPowerInteractor,
                 mStatusBarStateController,
                 Optional.of(mBubbles),
                 () -> mNoteTaskController,
@@ -501,13 +513,20 @@
                 () -> mAssistManager,
                 configurationController,
                 mNotificationShadeWindowController,
+                mNotificationShadeWindowViewControllerLazy,
+                mNotificationShelfController,
+                mStackScrollerController,
+                mNotificationPresenter,
+                new NotificationExpansionRepository(),
                 mDozeParameters,
                 mScrimController,
                 mLockscreenWallpaperLazy,
                 mBiometricUnlockControllerLazy,
                 mAuthRippleController,
                 mDozeServiceHost,
-                mPowerManager, mScreenPinningRequest,
+                mBackActionInteractor,
+                mPowerManager,
+                mScreenPinningRequest,
                 mDozeScrimController,
                 mVolumeComponent,
                 mCommandQueue,
@@ -519,7 +538,6 @@
                 mInitController,
                 new Handler(TestableLooper.get(this).getLooper()),
                 mPluginDependencyProvider,
-                mKeyguardDismissUtil,
                 mExtensionController,
                 mUserInfoControllerImpl,
                 mPhoneStatusBarPolicy,
@@ -574,16 +592,11 @@
         when(mKeyguardViewMediator.getViewMediatorCallback()).thenReturn(
                 mKeyguardVieMediatorCallback);
 
-        // TODO: we should be able to call mCentralSurfaces.start() and have all the below values
-        // initialized automatically and make NPVC private.
-        mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView;
-        mCentralSurfaces.mShadeSurface = mNotificationPanelViewController;
-        mCentralSurfaces.mQsController = mQuickSettingsController;
+        // TODO(b/277764509): we should be able to call mCentralSurfaces.start() and have all the
+        //  below values initialized automatically.
         mCentralSurfaces.mDozeScrimController = mDozeScrimController;
-        mCentralSurfaces.mPresenter = mNotificationPresenter;
         mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController;
         mCentralSurfaces.mBarService = mBarService;
-        mCentralSurfaces.mStackScroller = mStackScroller;
         mCentralSurfaces.mGestureWakeLock = mPowerManager.newWakeLock(
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "sysui:GestureWakeLock");
         mCentralSurfaces.startKeyguard();
@@ -814,16 +827,14 @@
      */
     @Test
     public void testPredictiveBackCallback_invocationCollapsesPanel() {
-        mCentralSurfaces.setNotificationShadeWindowViewController(
-                mNotificationShadeWindowViewController);
         mCentralSurfaces.handleVisibleToUserChanged(true);
         verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
                 eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
                 mOnBackInvokedCallback.capture());
 
-        when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true);
+        when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true);
         mOnBackInvokedCallback.getValue().onBackInvoked();
-        verify(mShadeController).animateCollapseShade();
+        verify(mBackActionInteractor).onBackRequested();
     }
 
     /**
@@ -832,8 +843,6 @@
      */
     @Test
     public void testPredictiveBackAnimation_progressMaxScalesPanel() {
-        mCentralSurfaces.setNotificationShadeWindowViewController(
-                mNotificationShadeWindowViewController);
         mCentralSurfaces.handleVisibleToUserChanged(true);
         verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
                 eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
@@ -841,6 +850,7 @@
 
         OnBackAnimationCallback onBackAnimationCallback =
                 (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
+        when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true);
         when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true);
 
         BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 100.0f, 1.0f, BackEvent.EDGE_LEFT);
@@ -854,8 +864,6 @@
      */
     @Test
     public void testPredictiveBackAnimation_progressMinScalesPanel() {
-        mCentralSurfaces.setNotificationShadeWindowViewController(
-                mNotificationShadeWindowViewController);
         mCentralSurfaces.handleVisibleToUserChanged(true);
         verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
                 eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
@@ -863,6 +871,7 @@
 
         OnBackAnimationCallback onBackAnimationCallback =
                 (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
+        when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true);
         when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true);
 
         BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 10.0f, 0.0f, BackEvent.EDGE_LEFT);
@@ -1062,7 +1071,7 @@
         // GIVEN device occluded and panel is NOT expanded
         mCentralSurfaces.setBarStateForTest(SHADE); // occluding on LS has StatusBarState = SHADE
         when(mKeyguardStateController.isOccluded()).thenReturn(true);
-        mCentralSurfaces.mPanelExpanded = false;
+        when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false);
 
         mCentralSurfaces.updateScrimController();
 
@@ -1076,7 +1085,7 @@
         // GIVEN device occluded and qs IS expanded
         mCentralSurfaces.setBarStateForTest(SHADE); // occluding on LS has StatusBarState = SHADE
         when(mKeyguardStateController.isOccluded()).thenReturn(true);
-        mCentralSurfaces.mPanelExpanded = true;
+        when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true);
 
         mCentralSurfaces.updateScrimController();
 
@@ -1145,32 +1154,6 @@
     }
 
     @Test
-    public void collapseShade_callsanimateCollapseShade_whenExpanded() {
-        // GIVEN the shade is expanded
-        mCentralSurfaces.onShadeExpansionFullyChanged(true);
-        mCentralSurfaces.setBarStateForTest(SHADE);
-
-        // WHEN collapseShade is called
-        mCentralSurfaces.collapseShade();
-
-        // VERIFY that animateCollapseShade is called
-        verify(mShadeController).animateCollapseShade();
-    }
-
-    @Test
-    public void collapseShade_doesNotCallanimateCollapseShade_whenCollapsed() {
-        // GIVEN the shade is collapsed
-        mCentralSurfaces.onShadeExpansionFullyChanged(false);
-        mCentralSurfaces.setBarStateForTest(SHADE);
-
-        // WHEN collapseShade is called
-        mCentralSurfaces.collapseShade();
-
-        // VERIFY that animateCollapseShade is NOT called
-        verify(mShadeController, never()).animateCollapseShade();
-    }
-
-    @Test
     public void deviceStateChange_unfolded_shadeOpen_setsLeaveOpenOnKeyguardHide() {
         setFoldedStates(FOLD_STATE_FOLDED);
         setGoToSleepStates(FOLD_STATE_FOLDED);
@@ -1266,34 +1249,6 @@
     }
 
     @Test
-    public void dozing_wakeUp() throws RemoteException {
-        // GIVEN can wakeup when dozing & is dozing
-        when(mScreenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true);
-        setDozing(true);
-
-        // WHEN wakeup is requested
-        final int wakeReason = PowerManager.WAKE_REASON_TAP;
-        mCentralSurfaces.wakeUpIfDozing(0, "", wakeReason);
-
-        // THEN power manager receives wakeup
-        verify(mPowerManagerService).wakeUp(eq(0L), eq(wakeReason), anyString(), anyString());
-    }
-
-    @Test
-    public void notDozing_noWakeUp() throws RemoteException {
-        // GIVEN can wakeup when dozing and NOT dozing
-        when(mScreenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true);
-        setDozing(false);
-
-        // WHEN wakeup is requested
-        final int wakeReason = PowerManager.WAKE_REASON_TAP;
-        mCentralSurfaces.wakeUpIfDozing(0, "", wakeReason);
-
-        // THEN power manager receives wakeup
-        verify(mPowerManagerService, never()).wakeUp(anyLong(), anyInt(), anyString(), anyString());
-    }
-
-    @Test
     public void frpLockedDevice_shadeDisabled() {
         when(mDeviceProvisionedController.isFrpActive()).thenReturn(true);
         when(mDozeServiceHost.isPulsing()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index 6155e3c..56d2397 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -212,13 +212,13 @@
     @Test
     fun localeListChanged_listenerNotified() {
         val config = mContext.resources.configuration
-        config.locales = LocaleList(Locale.CANADA, Locale.GERMANY)
+        config.setLocales(LocaleList(Locale.CANADA, Locale.GERMANY))
         mConfigurationController.onConfigurationChanged(config)
 
         val listener = createAndAddListener()
 
         // WHEN the locales are updated
-        config.locales = LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE)
+        config.setLocales(LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE))
         mConfigurationController.onConfigurationChanged(config)
 
         // THEN the listener is notified
@@ -274,6 +274,23 @@
     }
 
     @Test
+    fun orientationUpdated_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.orientation = Configuration.ORIENTATION_LANDSCAPE
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the orientation is updated
+        config.orientation = Configuration.ORIENTATION_PORTRAIT
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.orientationChanged).isTrue()
+    }
+
+
+    @Test
     fun multipleUpdates_listenerNotifiedOfAll() {
         val config = mContext.resources.configuration
         config.densityDpi = 14
@@ -325,6 +342,7 @@
         var themeChanged = false
         var localeListChanged = false
         var layoutDirectionChanged = false
+        var orientationChanged = false
 
         override fun onConfigChanged(newConfig: Configuration?) {
             changedConfig = newConfig
@@ -350,6 +368,9 @@
         override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
             layoutDirectionChanged = true
         }
+        override fun onOrientationChanged(orientation: Int) {
+            orientationChanged = true
+        }
 
         fun assertNoMethodsCalled() {
             assertThat(densityOrFontScaleChanged).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 57037e0..ff6f40d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -43,7 +43,6 @@
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor;
 import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.ShadeViewController;
@@ -96,7 +95,6 @@
     @Mock private BiometricUnlockController mBiometricUnlockController;
     @Mock private AuthController mAuthController;
     @Mock private DozeHost.Callback mCallback;
-    @Mock private BurnInInteractor mBurnInInteractor;
 
     @Mock private DozeInteractor mDozeInteractor;
     @Before
@@ -108,8 +106,7 @@
                 () -> mAssistManager, mDozeScrimController,
                 mKeyguardUpdateMonitor, mPulseExpansionHandler,
                 mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
-                mAuthController, mNotificationIconAreaController, mDozeInteractor,
-                mBurnInInteractor);
+                mAuthController, mNotificationIconAreaController, mDozeInteractor);
 
         mDozeServiceHost.initialize(
                 mCentralSurfaces,
@@ -234,11 +231,11 @@
         verifyZeroInteractions(mDozeInteractor);
     }
     @Test
-    public void dozeTimeTickSentTBurnInInteractor() {
+    public void dozeTimeTickSentToDozeInteractor() {
         // WHEN dozeTimeTick
         mDozeServiceHost.dozeTimeTick();
 
-        // THEN burnInInteractor's dozeTimeTick is updated
-        verify(mBurnInInteractor).dozeTimeTick();
+        // THEN dozeInteractor's dozeTimeTick is updated
+        verify(mDozeInteractor).dozeTimeTick();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 2f1e372..ec6286b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -75,6 +75,7 @@
     private HeadsUpManagerPhone mHeadsUpManager;
     private View mOperatorNameView;
     private StatusBarStateController mStatusbarStateController;
+    private PhoneStatusBarTransitions mPhoneStatusBarTransitions;
     private KeyguardBypassController mBypassController;
     private NotificationWakeUpCoordinator mWakeUpCoordinator;
     private KeyguardStateController mKeyguardStateController;
@@ -95,6 +96,7 @@
         mHeadsUpManager = mock(HeadsUpManagerPhone.class);
         mOperatorNameView = new View(mContext);
         mStatusbarStateController = mock(StatusBarStateController.class);
+        mPhoneStatusBarTransitions = mock(PhoneStatusBarTransitions.class);
         mBypassController = mock(KeyguardBypassController.class);
         mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
         mKeyguardStateController = mock(KeyguardStateController.class);
@@ -105,6 +107,7 @@
                 mock(NotificationIconAreaController.class),
                 mHeadsUpManager,
                 mStatusbarStateController,
+                mPhoneStatusBarTransitions,
                 mBypassController,
                 mWakeUpCoordinator,
                 mDarkIconDispatcher,
@@ -188,6 +191,7 @@
                 mock(NotificationIconAreaController.class),
                 mHeadsUpManager,
                 mStatusbarStateController,
+                mPhoneStatusBarTransitions,
                 mBypassController,
                 mWakeUpCoordinator,
                 mDarkIconDispatcher,
@@ -283,4 +287,18 @@
                 /* delta = */ 0.001
         );
     }
+
+    @Test
+    public void onHeadsUpStateChanged_true_transitionsNotified() {
+        mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, true);
+
+        verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(true);
+    }
+
+    @Test
+    public void onHeadsUpStateChanged_false_transitionsNotified() {
+        mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, false);
+
+        verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(false);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 6fda56c..bb20d18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
@@ -30,13 +32,13 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
@@ -59,9 +61,8 @@
 public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest {
     @Rule public MockitoRule rule = MockitoJUnit.rule();
 
-    private HeadsUpManagerPhone mHeadsUpManager;
-
-    @Mock private HeadsUpManagerLogger mHeadsUpManagerLogger;
+    private final HeadsUpManagerLogger mHeadsUpManagerLogger = new HeadsUpManagerLogger(
+            logcatLogBuffer());
     @Mock private GroupMembershipManager mGroupManager;
     @Mock private VisualStabilityProvider mVSProvider;
     @Mock private StatusBarStateController mStatusBarStateController;
@@ -104,21 +105,8 @@
         }
     }
 
-    protected AlertingNotificationManager createAlertingNotificationManager() {
-        return mHeadsUpManager;
-    }
-
-    @Before
-    public void setUp() {
-        AccessibilityManagerWrapper accessibilityMgr =
-                mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
-        when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
-                .thenReturn(TEST_AUTO_DISMISS_TIME);
-        when(mVSProvider.isReorderingAllowed()).thenReturn(true);
-        mDependency.injectMockDependency(NotificationShadeWindowController.class);
-        super.setUp();
-
-        mHeadsUpManager = new TestableHeadsUpManagerPhone(
+    private HeadsUpManagerPhone createHeadsUpManagerPhone() {
+        return new TestableHeadsUpManagerPhone(
                 mContext,
                 mHeadsUpManagerLogger,
                 mGroupManager,
@@ -133,71 +121,95 @@
         );
     }
 
+    @Override
+    protected AlertingNotificationManager createAlertingNotificationManager() {
+        return createHeadsUpManagerPhone();
+    }
+
+    @Before
+    @Override
+    public void setUp() {
+        final AccessibilityManagerWrapper accessibilityMgr =
+                mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
+        when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
+                .thenReturn(TEST_AUTO_DISMISS_TIME);
+        when(mVSProvider.isReorderingAllowed()).thenReturn(true);
+        mDependency.injectMockDependency(NotificationShadeWindowController.class);
+        mContext.getOrCreateTestableResources().addOverride(
+                R.integer.ambient_notification_extension_time, 500);
+
+        super.setUp();
+    }
+
     @After
+    @Override
     public void tearDown() {
-        mTestHandler.removeCallbacksAndMessages(null);
+        super.tearDown();
     }
 
     @Test
     public void testSnooze() {
-        mHeadsUpManager.showNotification(mEntry);
+        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
 
-        mHeadsUpManager.snooze();
+        hmp.showNotification(entry);
+        hmp.snooze();
 
-        assertTrue(mHeadsUpManager.isSnoozed(mEntry.getSbn().getPackageName()));
+        assertTrue(hmp.isSnoozed(entry.getSbn().getPackageName()));
     }
 
     @Test
     public void testSwipedOutNotification() {
-        mHeadsUpManager.showNotification(mEntry);
-        mHeadsUpManager.addSwipedOutNotification(mEntry.getKey());
+        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        hmp.showNotification(entry);
+        hmp.addSwipedOutNotification(entry.getKey());
 
         // Remove should succeed because the notification is swiped out
-        mHeadsUpManager.removeNotification(mEntry.getKey(), false /* releaseImmediately */);
+        final boolean removedImmediately = hmp.removeNotification(entry.getKey(),
+                /* releaseImmediately = */ false);
 
-        assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+        assertTrue(removedImmediately);
+        assertFalse(hmp.isAlerting(entry.getKey()));
     }
 
     @Test
     public void testCanRemoveImmediately_swipedOut() {
-        mHeadsUpManager.showNotification(mEntry);
-        mHeadsUpManager.addSwipedOutNotification(mEntry.getKey());
+        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        hmp.showNotification(entry);
+        hmp.addSwipedOutNotification(entry.getKey());
 
         // Notification is swiped so it can be immediately removed.
-        assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.getKey()));
+        assertTrue(hmp.canRemoveImmediately(entry.getKey()));
     }
 
     @Ignore("b/141538055")
     @Test
     public void testCanRemoveImmediately_notTopEntry() {
-        NotificationEntry laterEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(1))
-                .build();
+        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+        final NotificationEntry earlierEntry = createEntry(/* id = */ 0);
+        final NotificationEntry laterEntry = createEntry(/* id = */ 1);
         laterEntry.setRow(mRow);
-        mHeadsUpManager.showNotification(mEntry);
-        mHeadsUpManager.showNotification(laterEntry);
+
+        hmp.showNotification(earlierEntry);
+        hmp.showNotification(laterEntry);
 
         // Notification is "behind" a higher priority notification so we can remove it immediately.
-        assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.getKey()));
+        assertTrue(hmp.canRemoveImmediately(earlierEntry.getKey()));
     }
 
-
     @Test
     public void testExtendHeadsUp() {
-        mHeadsUpManager.showNotification(mEntry);
-        Runnable pastNormalTimeRunnable =
-                () -> mLivesPastNormalTime = mHeadsUpManager.isAlerting(mEntry.getKey());
-        mTestHandler.postDelayed(pastNormalTimeRunnable,
-                TEST_AUTO_DISMISS_TIME + mHeadsUpManager.mExtensionTime / 2);
-        mTestHandler.postDelayed(TEST_TIMEOUT_RUNNABLE, TEST_TIMEOUT_TIME);
+        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
 
-        mHeadsUpManager.extendHeadsUp();
+        hmp.showNotification(entry);
+        hmp.extendHeadsUp();
 
-        // Wait for normal time runnable and extended remove runnable and process them on arrival.
-        TestableLooper.get(this).processMessages(2);
-
-        assertFalse("Test timed out", mTimedOut);
-        assertTrue("Pulse was not extended", mLivesPastNormalTime);
-        assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+        final int pastNormalTimeMillis = TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2;
+        verifyAlertingAtTime(hmp, entry, true, pastNormalTimeMillis, "normal time");
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 3e90ed9..4aac841 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -42,6 +42,8 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
@@ -135,6 +137,19 @@
     }
 
     @Test
+    fun onFaceAuthEnabledChanged_notifiesBypassEnabledListeners() {
+        initKeyguardBypassController()
+        val bypassListener = mock(KeyguardBypassController.OnBypassStateChangedListener::class.java)
+        val callback = ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java)
+
+        keyguardBypassController.registerOnBypassStateChangedListener(bypassListener)
+        verify(keyguardStateController).addCallback(callback.capture())
+
+        callback.value.onFaceAuthEnabledChanged()
+        verify(bypassListener).onBypassStateChanged(anyBoolean())
+    }
+
+    @Test
     fun configDevicePostureClosed_matchState_isPostureAllowedForFaceAuth_returnTrue() {
         defaultConfigPostureClosed()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
new file mode 100644
index 0000000..b0aa2d3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class KeyguardDismissUtilTest extends SysuiTestCase {
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
+    private SysuiStatusBarStateController mStatusBarStateController;
+    @Mock
+    private ActivityStarter mActivityStarter;
+    @Mock
+    private ActivityStarter.OnDismissAction mAction;
+
+    private KeyguardDismissUtil mKeyguardDismissUtil;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mKeyguardDismissUtil = new KeyguardDismissUtil(
+                mKeyguardStateController, mStatusBarStateController, mActivityStarter);
+    }
+
+    @Test
+    public void testSetLeaveOpenOnKeyguardHideWhenKeyGuardStateControllerIsShowing() {
+        doReturn(true).when(mKeyguardStateController).isShowing();
+
+        mKeyguardDismissUtil.executeWhenUnlocked(mAction, true /* requiresShadeOpen */,
+                true /* afterKeyguardGone */);
+
+        verify(mStatusBarStateController).setLeaveOpenOnKeyguardHide(true);
+
+        verify(mActivityStarter).dismissKeyguardThenExecute(mAction, null, true);
+
+    }
+
+    @Test
+    public void testSetLeaveOpenOnKeyguardHideWhenKeyGuardStateControllerIsNotShowing() {
+        doReturn(false).when(mKeyguardStateController).isShowing();
+
+        mKeyguardDismissUtil.executeWhenUnlocked(mAction, true /* requiresShadeOpen */,
+                true /* afterKeyguardGone */);
+
+        //no interaction with mStatusBarStateController
+        verify(mStatusBarStateController, times(0)).setLeaveOpenOnKeyguardHide(true);
+
+        verify(mActivityStarter).dismissKeyguardThenExecute(mAction, null, true);
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index e838a480..d100c687 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -122,6 +122,7 @@
     @Mock private KeyguardLogger mLogger;
 
     @Mock private NotificationMediaManager mNotificationMediaManager;
+    @Mock private StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
 
     private TestShadeViewStateProvider mShadeViewStateProvider;
     private KeyguardStatusBarView mKeyguardStatusBarView;
@@ -171,7 +172,8 @@
                 mCommandQueue,
                 mFakeExecutor,
                 mLogger,
-                mNotificationMediaManager
+                mNotificationMediaManager,
+                mStatusOverlayHoverListenerFactory
         );
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 6a4b3c5..df3c1e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -22,7 +22,6 @@
 
 import static junit.framework.Assert.assertTrue;
 
-import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -45,8 +44,6 @@
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -65,20 +62,13 @@
 
     private static final GradientColors COLORS_LIGHT = makeColors(Color.WHITE);
     private static final GradientColors COLORS_DARK = makeColors(Color.BLACK);
-    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private LightBarTransitionsController mLightBarTransitionsController;
     private LightBarTransitionsController mNavBarController;
     private SysuiDarkIconDispatcher mStatusBarIconController;
     private LightBarController mLightBarController;
 
-    /** Allow testing with NEW_LIGHT_BAR_LOGIC flag in different states */
-    protected boolean testNewLightBarLogic() {
-        return false;
-    }
-
     @Before
     public void setup() {
-        mFeatureFlags.set(Flags.NEW_LIGHT_BAR_LOGIC, testNewLightBarLogic());
         mStatusBarIconController = mock(SysuiDarkIconDispatcher.class);
         mNavBarController = mock(LightBarTransitionsController.class);
         when(mNavBarController.supportsIconTintForNavMode(anyInt())).thenReturn(true);
@@ -90,7 +80,6 @@
                 mStatusBarIconController,
                 mock(BatteryController.class),
                 mock(NavigationModeController.class),
-                mFeatureFlags,
                 mock(DumpManager.class),
                 new FakeDisplayTracker(mContext));
     }
@@ -211,8 +200,6 @@
 
     @Test
     public void validateNavBarChangesUpdateIcons() {
-        assumeTrue(testNewLightBarLogic());  // Only run in the new suite
-
         // On the launcher in dark mode buttons are light
         mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_DARK);
         mLightBarController.onNavigationBarAppearanceChanged(
@@ -251,8 +238,6 @@
 
     @Test
     public void navBarHasDarkIconsInLockedShade_lightMode() {
-        assumeTrue(testNewLightBarLogic());  // Only run in the new suite
-
         // On the locked shade QS in light mode buttons are light
         mLightBarController.setScrimState(ScrimState.SHADE_LOCKED, 1f, COLORS_LIGHT);
         mLightBarController.onNavigationBarAppearanceChanged(
@@ -287,8 +272,6 @@
 
     @Test
     public void navBarHasLightIconsInLockedShade_darkMode() {
-        assumeTrue(testNewLightBarLogic());  // Only run in the new suite
-
         // On the locked shade QS in light mode buttons are light
         mLightBarController.setScrimState(ScrimState.SHADE_LOCKED, 1f, COLORS_DARK);
         mLightBarController.onNavigationBarAppearanceChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt
deleted file mode 100644
index d9c2cfa..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.phone
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.flags.Flags.NEW_LIGHT_BAR_LOGIC
-
-/**
- * This file only needs to live as long as [NEW_LIGHT_BAR_LOGIC] does. When we delete that flag, we
- * can roll this back into the old test.
- */
-@SmallTest
-class LightBarControllerWithNewLogicTest : LightBarControllerTest() {
-    override fun testNewLightBarLogic(): Boolean = true
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt
new file mode 100644
index 0000000..47671fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.app.WallpaperManager
+import android.content.pm.UserInfo
+import android.os.Looper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.os.FakeHandler
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class LockscreenWallpaperTest : SysuiTestCase() {
+
+    private lateinit var underTest: LockscreenWallpaper
+
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val userRepository = FakeUserRepository()
+
+    private val wallpaperManager: WallpaperManager = mock()
+
+    @Before
+    fun setUp() {
+        whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false)
+        whenever(wallpaperManager.isWallpaperSupported).thenReturn(true)
+        underTest =
+            LockscreenWallpaper(
+                /* wallpaperManager= */ wallpaperManager,
+                /* iWallpaperManager= */ mock(),
+                /* keyguardUpdateMonitor= */ mock(),
+                /* dumpManager= */ mock(),
+                /* mediaManager= */ mock(),
+                /* mainHandler= */ FakeHandler(Looper.getMainLooper()),
+                /* javaAdapter= */ JavaAdapter(testScope.backgroundScope),
+                /* userRepository= */ userRepository,
+                /* userTracker= */ mock(),
+            )
+        underTest.start()
+    }
+
+    @Test
+    fun getBitmap_matchesUserIdFromUserRepo() =
+        testScope.runTest {
+            val info = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0)
+            userRepository.setUserInfos(listOf(info))
+            userRepository.setSelectedUserInfo(info)
+
+            underTest.bitmap
+
+            verify(wallpaperManager).getWallpaperFile(any(), eq(5))
+        }
+
+    @Test
+    fun getBitmap_usesOldUserIfNewUserInProgress() =
+        testScope.runTest {
+            val info5 = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0)
+            val info6 = UserInfo(/* id= */ 6, /* name= */ "id6", /* flags= */ 0)
+            userRepository.setUserInfos(listOf(info5, info6))
+            userRepository.setSelectedUserInfo(info5)
+
+            // WHEN the selection of user 6 is only in progress
+            userRepository.setSelectedUserInfo(
+                info6,
+                selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS
+            )
+
+            underTest.bitmap
+
+            // THEN we still use user 5 for wallpaper selection
+            verify(wallpaperManager).getWallpaperFile(any(), eq(5))
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index b80b825..c282c1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -21,6 +21,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
@@ -49,7 +51,7 @@
     fun calculateWidthFor_oneIcon_widthForOneIcon() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
 
         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 1f),
                 /* actual= */ 30f)
@@ -59,7 +61,7 @@
     fun calculateWidthFor_fourIcons_widthForFourIcons() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
 
         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 4f),
                 /* actual= */ 60f)
@@ -69,7 +71,7 @@
     fun calculateWidthFor_fiveIcons_widthForFourIcons() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 5f),
                 /* actual= */ 60f)
     }
@@ -78,7 +80,7 @@
     fun calculateIconXTranslations_shortShelfOneIcon_atCorrectXWithoutOverflowDot() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
 
         val icon = mockStatusBarIcon()
         iconContainer.addView(icon)
@@ -99,7 +101,7 @@
     fun calculateIconXTranslations_shortShelfFourIcons_atCorrectXWithoutOverflowDot() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
 
         val iconOne = mockStatusBarIcon()
         val iconTwo = mockStatusBarIcon()
@@ -128,7 +130,7 @@
     fun calculateIconXTranslations_shortShelfFiveIcons_atCorrectXWithOverflowDot() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10);
+        iconContainer.setIconSize(10)
 
         val iconOne = mockStatusBarIcon()
         val iconTwo = mockStatusBarIcon()
@@ -154,6 +156,55 @@
     }
 
     @Test
+    fun calculateIconXTranslations_givenWidthEnoughForThreeIcons_atCorrectXWithoutOverflowDot() {
+        iconContainer.setActualPaddingStart(0f)
+        iconContainer.setActualPaddingEnd(0f)
+        iconContainer.setActualLayoutWidth(30)
+        iconContainer.setIconSize(10)
+
+        val iconOne = mockStatusBarIcon()
+        val iconTwo = mockStatusBarIcon()
+        val iconThree = mockStatusBarIcon()
+
+        iconContainer.addView(iconOne)
+        iconContainer.addView(iconTwo)
+        iconContainer.addView(iconThree)
+        assertEquals(3, iconContainer.childCount)
+
+        iconContainer.calculateIconXTranslations()
+        assertEquals(0f, iconContainer.getIconState(iconOne).xTranslation)
+        assertEquals(10f, iconContainer.getIconState(iconTwo).xTranslation)
+        assertEquals(20f, iconContainer.getIconState(iconThree).xTranslation)
+        assertFalse(iconContainer.areIconsOverflowing())
+    }
+
+    @Test
+    fun calculateIconXTranslations_givenWidthNotEnoughForFourIcons_atCorrectXWithOverflowDot() {
+        iconContainer.setActualPaddingStart(0f)
+        iconContainer.setActualPaddingEnd(0f)
+        iconContainer.setActualLayoutWidth(35)
+        iconContainer.setIconSize(10)
+
+        val iconOne = mockStatusBarIcon()
+        val iconTwo = mockStatusBarIcon()
+        val iconThree = mockStatusBarIcon()
+        val iconFour = mockStatusBarIcon()
+
+        iconContainer.addView(iconOne)
+        iconContainer.addView(iconTwo)
+        iconContainer.addView(iconThree)
+        iconContainer.addView(iconFour)
+        assertEquals(4, iconContainer.childCount)
+
+        iconContainer.calculateIconXTranslations()
+        assertEquals(0f, iconContainer.getIconState(iconOne).xTranslation)
+        assertEquals(10f, iconContainer.getIconState(iconTwo).xTranslation)
+        assertEquals(STATE_DOT, iconContainer.getIconState(iconThree).visibleState)
+        assertEquals(STATE_HIDDEN, iconContainer.getIconState(iconFour).visibleState)
+        assertTrue(iconContainer.areIconsOverflowing())
+    }
+
+    @Test
     fun shouldForceOverflow_appearingAboveSpeedBump_true() {
         val forceOverflow = iconContainer.shouldForceOverflow(
                 /* i= */ 1,
@@ -161,7 +212,7 @@
                 /* iconAppearAmount= */ 1f,
                 /* maxVisibleIcons= */ 5
         )
-        assertTrue(forceOverflow);
+        assertTrue(forceOverflow)
     }
 
     @Test
@@ -172,7 +223,7 @@
                 /* iconAppearAmount= */ 0f,
                 /* maxVisibleIcons= */ 5
         )
-        assertTrue(forceOverflow);
+        assertTrue(forceOverflow)
     }
 
     @Test
@@ -183,7 +234,7 @@
                 /* iconAppearAmount= */ 0f,
                 /* maxVisibleIcons= */ 5
         )
-        assertFalse(forceOverflow);
+        assertFalse(forceOverflow)
     }
 
     @Test
@@ -210,6 +261,17 @@
     }
 
     @Test
+    fun isOverflowing_lastChildXGreaterThanDotX_true() {
+        val isOverflowing = iconContainer.isOverflowing(
+                /* isLastChild= */ true,
+                /* translationX= */ 9f,
+                /* layoutEnd= */ 10f,
+                /* iconSize= */ 2f,
+        )
+        assertTrue(isOverflowing)
+    }
+
+    @Test
     fun isOverflowing_lastChildXGreaterThanLayoutEnd_true() {
         val isOverflowing = iconContainer.isOverflowing(
                 /* isLastChild= */ true,
@@ -253,7 +315,7 @@
         assertTrue(isOverflowing)
     }
 
-    private fun mockStatusBarIcon() : StatusBarIconView {
+    private fun mockStatusBarIcon(): StatusBarIconView {
         val iconView = mock(StatusBarIconView::class.java)
         whenever(iconView.width).thenReturn(10)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 6b18169..52f642d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -18,6 +18,7 @@
 
 import android.app.AlarmManager
 import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResourcesManager
 import android.content.SharedPreferences
 import android.os.UserManager
 import android.telecom.TelecomManager
@@ -27,6 +28,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
 import com.android.systemui.privacy.PrivacyItemController
 import com.android.systemui.privacy.logging.PrivacyLogger
 import com.android.systemui.screenrecord.RecordingController
@@ -46,9 +49,18 @@
 import com.android.systemui.statusbar.policy.ZenModeController
 import com.android.systemui.util.RingerModeTracker
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.time.DateFormatUtil
 import com.android.systemui.util.time.FakeSystemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -57,6 +69,9 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
@@ -64,11 +79,14 @@
 
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 class PhoneStatusBarPolicyTest : SysuiTestCase() {
 
     companion object {
         private const val ALARM_SLOT = "alarm"
+        private const val CONNECTED_DISPLAY_SLOT = "connected_display"
+        private const val MANAGED_PROFILE_SLOT = "managed_profile"
     }
 
     @Mock private lateinit var iconController: StatusBarIconController
@@ -90,6 +108,7 @@
     @Mock private lateinit var userManager: UserManager
     @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var devicePolicyManagerResources: DevicePolicyResourcesManager
     @Mock private lateinit var recordingController: RecordingController
     @Mock private lateinit var telecomManager: TelecomManager
     @Mock private lateinit var sharedPreferences: SharedPreferences
@@ -102,6 +121,9 @@
     private lateinit var alarmCallbackCaptor:
         ArgumentCaptor<NextAlarmController.NextAlarmChangeCallback>
 
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val fakeConnectedDisplayStateProvider = FakeConnectedDisplayStateProvider()
+
     private lateinit var executor: FakeExecutor
     private lateinit var statusBarPolicy: PhoneStatusBarPolicy
     private lateinit var testableLooper: TestableLooper
@@ -115,6 +137,12 @@
             com.android.internal.R.string.status_bar_alarm_clock,
             ALARM_SLOT
         )
+        context.orCreateTestableResources.addOverride(
+            com.android.internal.R.string.status_bar_managed_profile,
+            MANAGED_PROFILE_SLOT
+        )
+        whenever(devicePolicyManager.resources).thenReturn(devicePolicyManagerResources)
+        whenever(devicePolicyManagerResources.getString(anyString(), any())).thenReturn("")
         statusBarPolicy = createStatusBarPolicy()
     }
 
@@ -164,6 +192,87 @@
         verify(iconController).setIconVisibility(ALARM_SLOT, true)
     }
 
+    @Test
+    fun testAppTransitionFinished_doesNotShowManagedProfileIcon() {
+        whenever(userManager.isManagedProfile(anyInt())).thenReturn(false)
+        whenever(keyguardStateController.isShowing).thenReturn(false)
+
+        statusBarPolicy.appTransitionFinished(0)
+        // The above call posts to bgExecutor and then back to mainExecutor
+        executor.advanceClockToLast()
+        executor.runAllReady()
+        executor.advanceClockToLast()
+        executor.runAllReady()
+
+        verify(iconController, never()).setIconVisibility(MANAGED_PROFILE_SLOT, true)
+    }
+
+    @Test
+    fun testAppTransitionFinished_showsManagedProfileIcon() {
+        whenever(userManager.isManagedProfile(anyInt())).thenReturn(true)
+        whenever(keyguardStateController.isShowing).thenReturn(false)
+
+        statusBarPolicy.appTransitionFinished(0)
+        // The above call posts to bgExecutor and then back to mainExecutor
+        executor.advanceClockToLast()
+        executor.runAllReady()
+        executor.advanceClockToLast()
+        executor.runAllReady()
+
+        verify(iconController).setIconVisibility(MANAGED_PROFILE_SLOT, true)
+    }
+
+    @Test
+    fun connectedDisplay_connected_iconShown() =
+        testScope.runTest {
+            statusBarPolicy.init()
+            clearInvocations(iconController)
+
+            fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+            runCurrent()
+
+            verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+        }
+
+    @Test
+    fun connectedDisplay_disconnected_iconHidden() =
+        testScope.runTest {
+            statusBarPolicy.init()
+            clearInvocations(iconController)
+
+            fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED)
+
+            verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false)
+        }
+
+    @Test
+    fun connectedDisplay_disconnectedThenConnected_iconShown() =
+        testScope.runTest {
+            statusBarPolicy.init()
+            clearInvocations(iconController)
+
+            fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+            fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED)
+            fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+
+            inOrder(iconController).apply {
+                verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+                verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false)
+                verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+            }
+        }
+
+    @Test
+    fun connectedDisplay_connectSecureDisplay_iconShown() =
+        testScope.runTest {
+            statusBarPolicy.init()
+            clearInvocations(iconController)
+
+            fakeConnectedDisplayStateProvider.emit(State.CONNECTED_SECURE)
+
+            verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
+        }
+
     private fun createAlarmInfo(): AlarmManager.AlarmClockInfo {
         return AlarmManager.AlarmClockInfo(10L, null)
     }
@@ -200,7 +309,16 @@
             dateFormatUtil,
             ringerModeTracker,
             privacyItemController,
-            privacyLogger
+            privacyLogger,
+            fakeConnectedDisplayStateProvider,
+            JavaAdapter(testScope.backgroundScope)
         )
     }
+
+    private class FakeConnectedDisplayStateProvider : ConnectedDisplayInteractor {
+        private val flow = MutableSharedFlow<State>()
+        suspend fun emit(value: State) = flow.emit(value)
+        override val connectedDisplayState: Flow<State>
+            get() = flow
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt
new file mode 100644
index 0000000..4af1b24
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.testing.TestableLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT
+import com.android.systemui.util.mockito.argumentCaptor
+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.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class PhoneStatusBarTransitionsTest : SysuiTestCase() {
+
+    // PhoneStatusBarView does a lot of non-standard things when inflating, so just use mocks.
+    private val batteryView = mock<View>()
+    private val statusIcons = mock<View>()
+    private val startIcons = mock<View>()
+    private val statusBarView =
+        mock<PhoneStatusBarView>().apply {
+            whenever(this.context).thenReturn(mContext)
+            whenever(this.findViewById<View>(R.id.battery)).thenReturn(batteryView)
+            whenever(this.findViewById<View>(R.id.statusIcons)).thenReturn(statusIcons)
+            whenever(this.findViewById<View>(R.id.status_bar_start_side_except_heads_up))
+                .thenReturn(startIcons)
+        }
+    private val backgroundView = mock<View>().apply { whenever(this.context).thenReturn(mContext) }
+
+    private val underTest: PhoneStatusBarTransitions by lazy {
+        PhoneStatusBarTransitions(statusBarView, backgroundView).also {
+            // The views' alphas will be set when PhoneStatusBarTransitions is created and we want
+            // to ignore those in the tests, so clear those verifications here.
+            reset(batteryView)
+            reset(statusIcons)
+            reset(startIcons)
+        }
+    }
+
+    @Before
+    fun setUp() {
+        context.orCreateTestableResources.addOverride(
+            R.dimen.status_bar_icon_drawing_alpha,
+            RESOURCE_ALPHA,
+        )
+    }
+
+    @Test
+    fun transitionTo_lightsOutMode_batteryTranslucent() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+        val alpha = batteryView.capturedAlpha()
+        assertThat(alpha).isGreaterThan(0)
+        assertThat(alpha).isLessThan(1)
+    }
+
+    @Test
+    fun transitionTo_lightsOutMode_statusIconsHidden() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+        assertThat(statusIcons.capturedAlpha()).isEqualTo(0)
+    }
+
+    @Test
+    fun transitionTo_lightsOutMode_startIconsHidden() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+    }
+
+    @Test
+    fun transitionTo_lightsOutTransparentMode_batteryTranslucent() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+        val alpha = batteryView.capturedAlpha()
+        assertThat(alpha).isGreaterThan(0)
+        assertThat(alpha).isLessThan(1)
+    }
+
+    @Test
+    fun transitionTo_lightsOutTransparentMode_statusIconsHidden() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+        assertThat(statusIcons.capturedAlpha()).isEqualTo(0)
+    }
+
+    @Test
+    fun transitionTo_lightsOutTransparentMode_startIconsHidden() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+    }
+
+    @Test
+    fun transitionTo_translucentMode_batteryIconShown() {
+        underTest.transitionTo(/* mode= */ MODE_TRANSLUCENT, /* animate= */ false)
+
+        assertThat(batteryView.capturedAlpha()).isEqualTo(1)
+    }
+
+    @Test
+    fun transitionTo_semiTransparentMode_statusIconsShown() {
+        underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+
+        assertThat(statusIcons.capturedAlpha()).isEqualTo(1)
+    }
+
+    @Test
+    fun transitionTo_transparentMode_startIconsShown() {
+        // Transparent is the default, so we need to switch to a different mode first
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.transitionTo(/* mode= */ MODE_TRANSPARENT, /* animate= */ false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+    }
+
+    @Test
+    fun transitionTo_opaqueMode_batteryIconUsesResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+        assertThat(batteryView.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    @Test
+    fun transitionTo_opaqueMode_statusIconsUseResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+        assertThat(statusIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    @Test
+    fun transitionTo_opaqueMode_startIconsUseResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_true_semiTransparentMode_startIconsShown() {
+        underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(true)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_true_opaqueMode_startIconsUseResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(true)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    /** Regression test for b/291173113. */
+    @Test
+    fun onHeadsUpStateChanged_true_lightsOutMode_startIconsUseResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(true)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_false_semiTransparentMode_startIconsShown() {
+        underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_false_opaqueMode_startIconsUseResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_false_lightsOutMode_startIconsHidden() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+    }
+
+    private fun View.capturedAlpha(): Float {
+        val captor = argumentCaptor<Float>()
+        verify(this).alpha = captor.capture()
+        return captor.value
+    }
+
+    private companion object {
+        const val RESOURCE_ALPHA = 0.34f
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 2d96e59..0b31523 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.ShadeControllerImpl
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
@@ -50,6 +51,7 @@
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 import java.util.Optional
+import javax.inject.Provider
 
 @SmallTest
 class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@@ -67,12 +69,16 @@
     @Mock
     private lateinit var configurationController: ConfigurationController
     @Mock
+    private lateinit var mStatusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory
+    @Mock
     private lateinit var userChipViewModel: StatusBarUserChipViewModel
     @Mock
     private lateinit var centralSurfacesImpl: CentralSurfacesImpl
     @Mock
     private lateinit var shadeControllerImpl: ShadeControllerImpl
     @Mock
+    private lateinit var windowRootView: Provider<WindowRootView>
+    @Mock
     private lateinit var shadeLogger: ShadeLogger
     @Mock
     private lateinit var viewUtil: ViewUtil
@@ -140,8 +146,6 @@
     @Test
     fun handleTouchEventFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
-        `when`(centralSurfacesImpl.shadeViewController)
-                .thenReturn(shadeViewController)
         `when`(shadeViewController.isViewEnabled).thenReturn(false)
         val returnVal = view.onTouchEvent(
                 MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
@@ -152,8 +156,6 @@
     @Test
     fun handleTouchEventFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
-        `when`(centralSurfacesImpl.shadeViewController)
-                .thenReturn(shadeViewController)
         `when`(shadeViewController.isViewEnabled).thenReturn(false)
         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
 
@@ -165,8 +167,6 @@
     @Test
     fun handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
-        `when`(centralSurfacesImpl.shadeViewController)
-                .thenReturn(shadeViewController)
         `when`(shadeViewController.isViewEnabled).thenReturn(true)
         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0)
 
@@ -178,8 +178,6 @@
     @Test
     fun handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
-        `when`(centralSurfacesImpl.shadeViewController)
-                .thenReturn(shadeViewController)
         `when`(shadeViewController.isFullyCollapsed).thenReturn(true)
         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
 
@@ -204,9 +202,12 @@
             userChipViewModel,
             centralSurfacesImpl,
             shadeControllerImpl,
+            shadeViewController,
+            windowRootView,
             shadeLogger,
             viewUtil,
-            configurationController
+            configurationController,
+            mStatusOverlayHoverListenerFactory
         ).create(view).also {
             it.init()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 7fc0228..0dc1d9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -59,15 +59,14 @@
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -78,9 +77,11 @@
 import com.android.systemui.statusbar.policy.FakeConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.utils.os.FakeHandler;
+import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository;
 
 import com.google.common.truth.Expect;
 
@@ -101,6 +102,7 @@
 import java.util.Map;
 
 import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.test.TestScope;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -114,6 +116,9 @@
     private final LargeScreenShadeInterpolator
             mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
 
+    private final TestScope mTestScope = TestScopeProvider.getTestScope();
+    private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
+
     private ScrimController mScrimController;
     private ScrimView mScrimBehind;
     private ScrimView mNotificationsScrim;
@@ -137,13 +142,13 @@
     @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
     @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
     @Mock private CoroutineDispatcher mMainDispatcher;
     @Mock private TypedArray mMockTypedArray;
 
     // TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
     //   event-dispatch-on-registration pattern caused some of these unit tests to fail.)
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock private FeatureFlags mFeatureFlags;
 
     private static class AnimatorListener implements Animator.AnimatorListener {
         private int mNumStarts;
@@ -275,20 +280,25 @@
                 mDockManager,
                 mConfigurationController,
                 new FakeExecutor(new FakeSystemClock()),
+                mJavaAdapter,
                 mScreenOffAnimationController,
                 mKeyguardUnlockAnimationController,
                 mStatusBarKeyguardViewManager,
                 mPrimaryBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
+                mWallpaperRepository,
                 mMainDispatcher,
-                mLinearLargeScreenShadeInterpolator,
-                mFeatureFlags);
+                mLinearLargeScreenShadeInterpolator);
+        mScrimController.start();
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
         mScrimController.setAnimatorListener(mAnimatorListener);
 
         mScrimController.setHasBackdrop(false);
-        mScrimController.setWallpaperSupportsAmbientMode(false);
+
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         finishAnimationsImmediately();
     }
@@ -389,7 +399,9 @@
 
     @Test
     public void transitionToAod_withAodWallpaper() {
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
 
@@ -411,7 +423,9 @@
     @Test
     public void transitionToAod_withAodWallpaperAndLockScreenWallpaper() {
         mScrimController.setHasBackdrop(true);
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
 
@@ -428,7 +442,9 @@
 
     @Test
     public void setHasBackdrop_withAodWallpaperAndAlbumArt() {
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
         mScrimController.setHasBackdrop(true);
@@ -541,7 +557,9 @@
         // Pre-condition
         // Need to go to AoD first because PULSING doesn't change
         // the back scrim opacity - otherwise it would hide AoD wallpapers.
-        mScrimController.setWallpaperSupportsAmbientMode(false);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
         assertScrimAlpha(Map.of(
@@ -733,9 +751,7 @@
     }
 
     @Test
-    public void transitionToUnlocked_nonClippedQs_flagTrue_followsLargeScreensInterpolator() {
-        when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
-                .thenReturn(true);
+    public void transitionToUnlocked_nonClippedQs_followsLargeScreensInterpolator() {
         mScrimController.setClipsQsScrim(false);
         mScrimController.setRawPanelExpansionFraction(0f);
         mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -773,48 +789,6 @@
                 mScrimBehind, OPAQUE));
     }
 
-
-    @Test
-    public void transitionToUnlocked_nonClippedQs_flagFalse() {
-        when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
-                .thenReturn(false);
-        mScrimController.setClipsQsScrim(false);
-        mScrimController.setRawPanelExpansionFraction(0f);
-        mScrimController.transitionTo(ScrimState.UNLOCKED);
-        finishAnimationsImmediately();
-        assertScrimAlpha(Map.of(
-                mScrimInFront, TRANSPARENT,
-                mNotificationsScrim, TRANSPARENT,
-                mScrimBehind, TRANSPARENT));
-
-        assertScrimTinted(Map.of(
-                mNotificationsScrim, false,
-                mScrimInFront, false,
-                mScrimBehind, true
-        ));
-
-        // Back scrim should be visible after start dragging
-        mScrimController.setRawPanelExpansionFraction(0.29f);
-        assertScrimAlpha(Map.of(
-                mScrimInFront, TRANSPARENT,
-                mNotificationsScrim, TRANSPARENT,
-                mScrimBehind, SEMI_TRANSPARENT));
-
-        // Back scrim should be opaque at 30%
-        mScrimController.setRawPanelExpansionFraction(0.3f);
-        assertScrimAlpha(Map.of(
-                mScrimInFront, TRANSPARENT,
-                mNotificationsScrim, TRANSPARENT,
-                mScrimBehind, OPAQUE));
-
-        // Then, notification scrim should fade in
-        mScrimController.setRawPanelExpansionFraction(0.31f);
-        assertScrimAlpha(Map.of(
-                mScrimInFront, TRANSPARENT,
-                mNotificationsScrim, SEMI_TRANSPARENT,
-                mScrimBehind, OPAQUE));
-    }
-
     @Test
     public void scrimStateCallback() {
         mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -1013,19 +987,22 @@
                 mDockManager,
                 mConfigurationController,
                 new FakeExecutor(new FakeSystemClock()),
+                mJavaAdapter,
                 mScreenOffAnimationController,
                 mKeyguardUnlockAnimationController,
                 mStatusBarKeyguardViewManager,
                 mPrimaryBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
+                mWallpaperRepository,
                 mMainDispatcher,
-                mLinearLargeScreenShadeInterpolator,
-                mFeatureFlags);
+                mLinearLargeScreenShadeInterpolator);
+        mScrimController.start();
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
         mScrimController.setAnimatorListener(mAnimatorListener);
         mScrimController.setHasBackdrop(false);
-        mScrimController.setWallpaperSupportsAmbientMode(false);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false);
+        mTestScope.getTestScheduler().runCurrent();
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         finishAnimationsImmediately();
 
@@ -1150,7 +1127,9 @@
 
     @Test
     public void testWillHideAodWallpaper() {
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
         mScrimController.transitionTo(ScrimState.KEYGUARD);
@@ -1161,7 +1140,8 @@
     public void testWillHideDockedWallpaper() {
         mAlwaysOnEnabled = false;
         when(mDockManager.isDocked()).thenReturn(true);
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
 
         mScrimController.transitionTo(ScrimState.AOD);
 
@@ -1210,7 +1190,9 @@
 
     @Test
     public void testHidesShowWhenLockedActivity() {
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.setKeyguardOccluded(true);
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
@@ -1227,7 +1209,9 @@
 
     @Test
     public void testHidesShowWhenLockedActivity_whenAlreadyInAod() {
-        mScrimController.setWallpaperSupportsAmbientMode(true);
+        mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true);
+        mTestScope.getTestScheduler().runCurrent();
+
         mScrimController.transitionTo(ScrimState.AOD);
         finishAnimationsImmediately();
         assertScrimAlpha(Map.of(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index 1759fb7..210c5ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -463,10 +463,10 @@
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
             mock(DumpManager::class.java))
 
-        configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
+        configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
         val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
 
-        configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
+        configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 800, 600))
 
         // WHEN: get insets on the second display
         val secondDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -482,14 +482,14 @@
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
             mock(DumpManager::class.java))
 
-        configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
+        configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
         val firstDisplayInsetsFirstCall = provider
             .getStatusBarContentAreaForRotation(ROTATION_NONE)
 
-        configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
+        configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 800, 600))
         provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
 
-        configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
+        configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
 
         // WHEN: get insets on the first display again
         val firstDisplayInsetsSecondCall = provider
@@ -577,4 +577,4 @@
                 " expected=$expected actual=$actual",
                 expected.equals(actual))
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 8aaa57f..085ec27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -15,8 +15,6 @@
 package com.android.systemui.statusbar.phone;
 
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
-import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
-import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
 
 import static junit.framework.Assert.assertTrue;
 
@@ -40,16 +38,13 @@
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.StatusBarMobileView;
-import com.android.systemui.statusbar.StatusBarWifiView;
 import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
 import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
 import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
@@ -60,23 +55,27 @@
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidTestingRunner.class)
-@RunWithLooper
+@RunWithLooper(setAsMainLooper = true)
 @SmallTest
 public class StatusBarIconControllerTest extends LeakCheckedTest {
 
     private MobileContextProvider mMobileContextProvider = mock(MobileContextProvider.class);
+    private MobileUiAdapter mMobileUiAdapter = mock(MobileUiAdapter.class);
+    private MobileIconsViewModel mMobileIconsViewModel = mock(MobileIconsViewModel.class);
 
     @Before
     public void setup() {
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
         // For testing, ignore context overrides
         when(mMobileContextProvider.getMobileContextForSub(anyInt(), any())).thenReturn(mContext);
+        when(mMobileUiAdapter.getMobileIconsViewModel()).thenReturn(mMobileIconsViewModel);
     }
 
     @Test
     public void testSetCalledOnAdd_IconManager() {
         LinearLayout layout = new LinearLayout(mContext);
-        TestIconManager manager = new TestIconManager(layout, mMobileContextProvider);
+        TestIconManager manager =
+                new TestIconManager(layout, mMobileUiAdapter, mMobileContextProvider);
         testCallOnAdd_forManager(manager);
     }
 
@@ -86,9 +85,8 @@
         TestDarkIconManager manager = new TestDarkIconManager(
                 layout,
                 StatusBarLocation.HOME,
-                mock(StatusBarPipelineFlags.class),
                 mock(WifiUiAdapter.class),
-                mock(MobileUiAdapter.class),
+                mMobileUiAdapter,
                 mMobileContextProvider,
                 mock(DarkIconDispatcher.class));
         testCallOnAdd_forManager(manager);
@@ -156,22 +154,10 @@
         assertTrue("Expected StatusBarIconView",
                 (manager.getViewAt(0) instanceof StatusBarIconView));
 
-        holder = holderForType(TYPE_WIFI);
-        manager.onIconAdded(1, "test_wifi", false, holder);
-        assertTrue(manager.getViewAt(1) instanceof StatusBarWifiView);
-
-        holder = holderForType(TYPE_MOBILE);
-        manager.onIconAdded(2, "test_mobile", false, holder);
-        assertTrue(manager.getViewAt(2) instanceof StatusBarMobileView);
     }
 
     private StatusBarIconHolder holderForType(int type) {
         switch (type) {
-            case TYPE_MOBILE:
-                return StatusBarIconHolder.fromMobileIconState(mock(MobileIconState.class));
-
-            case TYPE_WIFI:
-                return StatusBarIconHolder.fromWifiIconState(mock(WifiIconState.class));
 
             case TYPE_ICON:
             default:
@@ -185,14 +171,12 @@
         TestDarkIconManager(
                 LinearLayout group,
                 StatusBarLocation location,
-                StatusBarPipelineFlags statusBarPipelineFlags,
                 WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider contextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(group,
                     location,
-                    statusBarPipelineFlags,
                     wifiUiAdapter,
                     mobileUiAdapter,
                     contextProvider,
@@ -212,30 +196,18 @@
 
             return mock;
         }
-
-        @Override
-        protected StatusBarWifiView addWifiIcon(int index, String slot, WifiIconState state) {
-            StatusBarWifiView mock = mock(StatusBarWifiView.class);
-            mGroup.addView(mock, index);
-            return mock;
-        }
-
-        @Override
-        protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
-            StatusBarMobileView mock = mock(StatusBarMobileView.class);
-            mGroup.addView(mock, index);
-
-            return mock;
-        }
     }
 
     private static class TestIconManager extends IconManager implements TestableIconManager {
-        TestIconManager(ViewGroup group, MobileContextProvider contextProvider) {
+        TestIconManager(
+                ViewGroup group,
+                MobileUiAdapter adapter,
+                MobileContextProvider contextProvider
+        ) {
             super(group,
                     StatusBarLocation.HOME,
-                    mock(StatusBarPipelineFlags.class),
                     mock(WifiUiAdapter.class),
-                    mock(MobileUiAdapter.class),
+                    adapter,
                     contextProvider);
         }
 
@@ -252,21 +224,6 @@
 
             return mock;
         }
-
-        @Override
-        protected StatusBarWifiView addWifiIcon(int index, String slot, WifiIconState state) {
-            StatusBarWifiView mock = mock(StatusBarWifiView.class);
-            mGroup.addView(mock, index);
-            return mock;
-        }
-
-        @Override
-        protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
-            StatusBarMobileView mock = mock(StatusBarMobileView.class);
-            mGroup.addView(mock, index);
-
-            return mock;
-        }
     }
 
     private interface TestableIconManager {
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 131cc07..0da7360 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
@@ -16,8 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
+import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -35,6 +35,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
 import android.service.trust.TrustAgentService;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -63,16 +65,18 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.bouncer.ui.BouncerView;
+import com.android.systemui.bouncer.ui.BouncerViewDelegate;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dreams.DreamOverlayStateController;
 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.AlternateBouncerInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
 import com.android.systemui.plugins.ActivityStarter;
@@ -137,6 +141,7 @@
     @Mock private BouncerView mBouncerView;
     @Mock private BouncerViewDelegate mBouncerViewDelegate;
     @Mock private OnBackAnimationCallback mBouncerViewDelegateBackCallback;
+    @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
     @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
     @Mock private WindowInsetsController mWindowInsetsController;
     @Mock private TaskbarDelegate mTaskbarDelegate;
@@ -170,7 +175,7 @@
                 .isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM))
                 .thenReturn(true);
 
-        when(mCentralSurfaces.getNotificationShadeWindowView())
+        when(mNotificationShadeWindowController.getWindowRootView())
                 .thenReturn(mNotificationShadeWindowView);
         when(mNotificationShadeWindowView.getWindowInsetsController())
                 .thenReturn(mWindowInsetsController);
@@ -186,7 +191,7 @@
                         mDreamOverlayStateController,
                         mock(NavigationModeController.class),
                         mock(DockManager.class),
-                        mock(NotificationShadeWindowController.class),
+                        mNotificationShadeWindowController,
                         mKeyguardStateController,
                         mock(NotificationMediaManager.class),
                         mKeyguardMessageAreaFactory,
@@ -200,7 +205,10 @@
                         mBouncerView,
                         mAlternateBouncerInteractor,
                         mUdfpsOverlayInteractor,
-                        mActivityStarter) {
+                        mActivityStarter,
+                        mock(KeyguardTransitionInteractor.class),
+                        StandardTestDispatcher(null, null),
+                        () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -648,21 +656,21 @@
     @Test
     public void testReportBouncerOnDreamWhenVisible() {
         mBouncerExpansionCallback.onVisibilityChanged(true);
-        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+        assertFalse(mStatusBarKeyguardViewManager.isBouncerShowingOverDream());
         Mockito.clearInvocations(mCentralSurfaces);
         when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
         mBouncerExpansionCallback.onVisibilityChanged(true);
-        verify(mCentralSurfaces).setBouncerShowingOverDream(true);
+        assertTrue(mStatusBarKeyguardViewManager.isBouncerShowingOverDream());
     }
 
     @Test
     public void testReportBouncerOnDreamWhenNotVisible() {
         mBouncerExpansionCallback.onVisibilityChanged(false);
-        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+        assertFalse(mStatusBarKeyguardViewManager.isBouncerShowingOverDream());
         Mockito.clearInvocations(mCentralSurfaces);
         when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
         mBouncerExpansionCallback.onVisibilityChanged(false);
-        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+        assertFalse(mStatusBarKeyguardViewManager.isBouncerShowingOverDream());
     }
 
     @Test
@@ -700,7 +708,10 @@
                         mBouncerView,
                         mAlternateBouncerInteractor,
                         mUdfpsOverlayInteractor,
-                        mActivityStarter) {
+                        mActivityStarter,
+                        mock(KeyguardTransitionInteractor.class),
+                        StandardTestDispatcher(null, null),
+                        () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -738,6 +749,16 @@
     }
 
     @Test
+    public void testResetBouncerAnimatingAway() {
+        reset(mPrimaryBouncerInteractor);
+        when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true);
+
+        mStatusBarKeyguardViewManager.reset(true);
+
+        verify(mPrimaryBouncerInteractor, never()).hide();
+    }
+
+    @Test
     public void handleDispatchTouchEvent_alternateBouncerNotVisible() {
         mStatusBarKeyguardViewManager.addCallback(mCallback);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index d6ae2b7..3151ad1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -20,6 +20,9 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
+
+import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.AdditionalAnswers.answerVoid;
 import static org.mockito.ArgumentMatchers.any;
@@ -40,6 +43,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ResolveInfo;
 import android.os.Handler;
@@ -61,23 +65,28 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.assist.AssistManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.classifier.FalsingCollectorFake;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.data.repository.FakePowerRepository;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.ShadeControllerImpl;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.NotificationClickNotifier;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -109,6 +118,9 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
 
+    private static final int DISPLAY_ID = 0;
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+
     @Mock
     private AssistManager mAssistManager;
     @Mock
@@ -117,13 +129,12 @@
     private NotificationClickNotifier mClickNotifier;
     @Mock
     private StatusBarStateController mStatusBarStateController;
+    @Mock private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock
     private NotificationRemoteInputManager mRemoteInputManager;
     @Mock
-    private CentralSurfaces mCentralSurfaces;
-    @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
     private NotificationInterruptStateProvider mNotificationInterruptStateProvider;
@@ -149,6 +160,8 @@
     private ActivityLaunchAnimator mActivityLaunchAnimator;
     @Mock
     private InteractionJankMonitor mJankMonitor;
+    private FakePowerRepository mPowerRepository;
+    private PowerInteractor mPowerInteractor;
     @Mock
     private UserTracker mUserTracker;
     private final FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -198,16 +211,25 @@
         when(mUserTracker.getUserHandle()).thenReturn(
                 UserHandle.of(ActivityManager.getCurrentUser()));
 
+        mPowerRepository = new FakePowerRepository();
+        mPowerInteractor = new PowerInteractor(
+                mPowerRepository,
+                new FakeKeyguardRepository(),
+                new FalsingCollectorFake(),
+                mScreenOffAnimationController,
+                mStatusBarStateController);
+
         HeadsUpManagerPhone headsUpManager = mock(HeadsUpManagerPhone.class);
         NotificationLaunchAnimatorControllerProvider notificationAnimationProvider =
                 new NotificationLaunchAnimatorControllerProvider(
-                        mock(NotificationShadeWindowViewController.class), mock(
-                        NotificationListContainer.class),
+                        new NotificationExpansionRepository(),
+                        mock(NotificationListContainer.class),
                         headsUpManager,
                         mJankMonitor);
         mNotificationActivityStarter =
                 new StatusBarNotificationActivityStarter(
                         getContext(),
+                        DISPLAY_ID,
                         mHandler,
                         mUiBgExecutor,
                         mVisibilityProvider,
@@ -228,15 +250,16 @@
                         mock(StatusBarRemoteInputCallback.class),
                         mActivityIntentHelper,
                         mock(MetricsLogger.class),
-                        mock(StatusBarNotificationActivityStarterLogger.class),
+                        new StatusBarNotificationActivityStarterLogger(logcatLogBuffer()),
                         mOnUserInteractionCallback,
-                        mCentralSurfaces,
                         mock(NotificationPresenter.class),
                         mock(ShadeViewController.class),
+                        mock(NotificationShadeWindowController.class),
                         mActivityLaunchAnimator,
                         notificationAnimationProvider,
                         mock(LaunchFullScreenIntentProvider.class),
-                        mock(FeatureFlags.class),
+                        mPowerInteractor,
+                        mFeatureFlags,
                         mUserTracker
                 );
 
@@ -272,7 +295,7 @@
         notification.flags |= Notification.FLAG_AUTO_CANCEL;
 
         when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mCentralSurfaces.isOccluded()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
 
         // When
         mNotificationActivityStarter.onNotificationClicked(entry, mNotificationRow);
@@ -338,7 +361,7 @@
         // Given
         sbn.getNotification().contentIntent = null;
         when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mCentralSurfaces.isOccluded()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
 
         // When
         mNotificationActivityStarter.onNotificationClicked(entry, mBubbleNotificationRow);
@@ -366,7 +389,7 @@
         // Given
         sbn.getNotification().contentIntent = mContentIntent;
         when(mKeyguardStateController.isShowing()).thenReturn(true);
-        when(mCentralSurfaces.isOccluded()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
 
         // When
         mNotificationActivityStarter.onNotificationClicked(entry, mBubbleNotificationRow);
@@ -389,10 +412,12 @@
     @Test
     public void testOnFullScreenIntentWhenDozing_wakeUpDevice() {
         // GIVEN entry that can has a full screen intent that can show
+        PendingIntent fullScreenIntent = PendingIntent.getActivity(mContext, 1,
+                new Intent("fake_full_screen"), PendingIntent.FLAG_IMMUTABLE);
         Notification.Builder nb = new Notification.Builder(mContext, "a")
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
-                .setFullScreenIntent(mock(PendingIntent.class), true);
+                .setFullScreenIntent(fullScreenIntent, true);
         StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0,
                 "tag" + System.currentTimeMillis(), 0, 0,
                 nb.build(), new UserHandle(0), null, 0);
@@ -400,11 +425,13 @@
         when(entry.getImportance()).thenReturn(NotificationManager.IMPORTANCE_HIGH);
         when(entry.getSbn()).thenReturn(sbn);
 
-        // WHEN
+        // WHEN the intent is launched while dozing
+        when(mStatusBarStateController.isDozing()).thenReturn(true);
         mNotificationActivityStarter.launchFullScreenIntent(entry);
 
         // THEN display should try wake up for the full screen intent
-        verify(mCentralSurfaces).wakeUpForFullScreenIntent();
+        assertThat(mPowerRepository.getLastWakeReason()).isNotNull();
+        assertThat(mPowerRepository.getLastWakeWhy()).isNotNull();
     }
 
     @Test
@@ -414,6 +441,7 @@
         // GIVEN entry that can has a full screen intent that can show
         PendingIntent mockFullScreenIntent = mock(PendingIntent.class);
         when(mockFullScreenIntent.getCreatorUid()).thenReturn(kTestUid);
+        when(mockFullScreenIntent.getIntent()).thenReturn(new Intent("fake_full_screen"));
         ResolveInfo resolveInfo = new ResolveInfo();
         resolveInfo.activityInfo = new ActivityInfo();
         resolveInfo.activityInfo.name = kTestActivityName;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index fdfe028..34c4ac1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -32,19 +32,17 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.testing.FakeMetricsLogger;
-import com.android.systemui.ForegroundServiceNotificationListener;
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeNotificationPresenter;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -52,10 +50,10 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-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.collection.render.NotifShadeEventSource;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -80,24 +78,21 @@
     private CommandQueue mCommandQueue;
     private FakeMetricsLogger mMetricsLogger;
     private final ShadeController mShadeController = mock(ShadeController.class);
-    private final CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class);
+    private final NotificationsInteractor mNotificationsInteractor =
+            mock(NotificationsInteractor.class);
     private final KeyguardStateController mKeyguardStateController =
             mock(KeyguardStateController.class);
-    private final NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
     private final InitController mInitController = new InitController();
 
     @Before
     public void setup() {
         mMetricsLogger = new FakeMetricsLogger();
-        LockscreenGestureLogger lockscreenGestureLogger = new LockscreenGestureLogger(
-                mMetricsLogger);
         mCommandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext));
         mDependency.injectTestDependency(StatusBarStateController.class,
                 mock(SysuiStatusBarStateController.class));
         mDependency.injectTestDependency(ShadeController.class, mShadeController);
         mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class);
         mDependency.injectMockDependency(NotificationShadeWindowController.class);
-        mDependency.injectMockDependency(ForegroundServiceNotificationListener.class);
 
         NotificationShadeWindowView notificationShadeWindowView =
                 mock(NotificationShadeWindowView.class);
@@ -108,8 +103,6 @@
         when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
 
         ShadeViewController shadeViewController = mock(ShadeViewController.class);
-        when(shadeViewController.getShadeNotificationPresenter())
-                .thenReturn(mock(ShadeNotificationPresenter.class));
         mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(
                 mContext,
                 shadeViewController,
@@ -122,20 +115,18 @@
                 mock(NotificationShadeWindowController.class),
                 mock(DynamicPrivacyController.class),
                 mKeyguardStateController,
-                mock(KeyguardIndicationController.class),
-                mCentralSurfaces,
+                mNotificationsInteractor,
                 mock(LockscreenShadeTransitionController.class),
+                mock(PowerInteractor.class),
                 mCommandQueue,
                 mock(NotificationLockscreenUserManager.class),
                 mock(SysuiStatusBarStateController.class),
                 mock(NotifShadeEventSource.class),
                 mock(NotificationMediaManager.class),
                 mock(NotificationGutsManager.class),
-                lockscreenGestureLogger,
                 mInitController,
                 mNotificationInterruptStateProvider,
                 mock(NotificationRemoteInputManager.class),
-                mNotifPipelineFlags,
                 mock(NotificationRemoteInputManager.Callback.class),
                 mock(NotificationListContainer.class));
         mInitController.executePostInitTasks();
@@ -207,7 +198,6 @@
 
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mKeyguardStateController.isOccluded()).thenReturn(false);
-        when(mCentralSurfaces.isOccluded()).thenReturn(false);
         assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
     }
 
@@ -235,9 +225,9 @@
                 .setTag("a")
                 .setNotification(n)
                 .build();
-        when(mCentralSurfaces.areNotificationAlertsDisabled()).thenReturn(true);
+        when(mNotificationsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
 
-        assertTrue("CentralSurfaces alerts disabled shouldn't allow interruptions",
+        assertTrue("When alerts aren't enabled, interruptions are suppressed",
                 mInterruptSuppressor.suppressInterruptions(entry));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
new file mode 100644
index 0000000..63508e1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.PaintDrawable
+import android.os.SystemClock
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroupOverlay
+import android.widget.LinearLayout
+import androidx.annotation.ColorInt
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class StatusOverlayHoverListenerTest : SysuiTestCase() {
+
+    private val viewOverlay = mock<ViewGroupOverlay>()
+    private val overlayCaptor = argumentCaptor<Drawable>()
+    private val darkDispatcher = mock<SysuiDarkIconDispatcher>()
+    private val darkChange: MutableStateFlow<DarkChange> = MutableStateFlow(DarkChange.EMPTY)
+
+    private val factory =
+        StatusOverlayHoverListenerFactory(
+            context.resources,
+            FakeConfigurationController(),
+            darkDispatcher
+        )
+    private val view = TestableStatusContainer(context, viewOverlay)
+
+    private lateinit var looper: TestableLooper
+
+    @Before
+    fun setUp() {
+        looper = TestableLooper.get(this)
+        whenever(darkDispatcher.darkChangeFlow()).thenReturn(darkChange)
+    }
+
+    @Test
+    fun onHoverStarted_addsOverlay() {
+        view.setUpHoverListener()
+
+        view.hoverStarted()
+
+        assertThat(overlayDrawable).isNotNull()
+    }
+
+    @Test
+    fun onHoverEnded_removesOverlay() {
+        view.setUpHoverListener()
+
+        view.hoverStarted() // stopped callback will be called only if hover has started
+        view.hoverStopped()
+
+        verify(viewOverlay).clear()
+    }
+
+    @Test
+    fun onHoverStarted_overlayHasLightColor() {
+        view.setUpHoverListener()
+
+        view.hoverStarted()
+
+        assertThat(overlayColor)
+            .isEqualTo(context.resources.getColor(R.color.status_bar_icons_hover_color_light))
+    }
+
+    @Test
+    fun onDarkAwareHoverStarted_withBlackIcons_overlayHasDarkColor() {
+        view.setUpDarkAwareHoverListener()
+        setIconsTint(Color.BLACK)
+
+        view.hoverStarted()
+
+        assertThat(overlayColor)
+            .isEqualTo(context.resources.getColor(R.color.status_bar_icons_hover_color_dark))
+    }
+
+    @Test
+    fun onHoverStarted_withBlackIcons_overlayHasLightColor() {
+        view.setUpHoverListener()
+        setIconsTint(Color.BLACK)
+
+        view.hoverStarted()
+
+        assertThat(overlayColor)
+            .isEqualTo(context.resources.getColor(R.color.status_bar_icons_hover_color_light))
+    }
+
+    @Test
+    fun onDarkAwareHoverStarted_withWhiteIcons_overlayHasLightColor() {
+        view.setUpDarkAwareHoverListener()
+        setIconsTint(Color.WHITE)
+
+        view.hoverStarted()
+
+        assertThat(overlayColor)
+            .isEqualTo(context.resources.getColor(R.color.status_bar_icons_hover_color_light))
+    }
+
+    private fun View.setUpHoverListener() {
+        setOnHoverListener(factory.createListener(view))
+        attachView(view)
+    }
+
+    private fun View.setUpDarkAwareHoverListener() {
+        setOnHoverListener(factory.createDarkAwareListener(view))
+        attachView(view)
+    }
+
+    private fun attachView(view: View) {
+        ViewUtils.attachView(view)
+        // attaching is async so processAllMessages is required for view.repeatWhenAttached to run
+        looper.processAllMessages()
+    }
+
+    private val overlayDrawable: Drawable
+        get() {
+            verify(viewOverlay).add(overlayCaptor.capture())
+            return overlayCaptor.value
+        }
+
+    private val overlayColor
+        get() = (overlayDrawable as PaintDrawable).paint.color
+
+    private fun setIconsTint(@ColorInt color: Int) {
+        // passing empty ArrayList is equivalent to just accepting passed color as icons color
+        darkChange.value = DarkChange(/* areas= */ ArrayList(), /* darkIntensity= */ 1f, color)
+    }
+
+    private fun TestableStatusContainer.hoverStarted() {
+        injectHoverEvent(hoverEvent(MotionEvent.ACTION_HOVER_ENTER))
+    }
+
+    private fun TestableStatusContainer.hoverStopped() {
+        injectHoverEvent(hoverEvent(MotionEvent.ACTION_HOVER_EXIT))
+    }
+
+    class TestableStatusContainer(context: Context, private val mockOverlay: ViewGroupOverlay) :
+        LinearLayout(context) {
+
+        fun injectHoverEvent(event: MotionEvent) = dispatchHoverEvent(event)
+
+        override fun getOverlay() = mockOverlay
+    }
+
+    private fun hoverEvent(action: Int): MotionEvent {
+        return MotionEvent.obtain(
+            /* downTime= */ SystemClock.uptimeMillis(),
+            /* eventTime= */ SystemClock.uptimeMillis(),
+            /* action= */ action,
+            /* x= */ 0f,
+            /* y= */ 0f,
+            /* metaState= */ 0
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index ea534bb..e76f26d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.StatusBarStateControllerImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.eq
@@ -65,6 +66,8 @@
     @Mock
     private lateinit var shadeViewController: ShadeViewController
     @Mock
+    private lateinit var notifShadeWindowController: NotificationShadeWindowController
+    @Mock
     private lateinit var lightRevealScrim: LightRevealScrim
     @Mock
     private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@@ -89,13 +92,12 @@
                 keyguardStateController,
                 dagger.Lazy<DozeParameters> { dozeParameters },
                 globalSettings,
+                dagger.Lazy<NotificationShadeWindowController> { notifShadeWindowController },
                 interactionJankMonitor,
                 powerManager,
                 handler = handler
         )
-        controller.initialize(centralSurfaces, lightRevealScrim)
-        `when`(centralSurfaces.shadeViewController).thenReturn(
-            shadeViewController)
+        controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
 
         // Screen off does not run if the panel is expanded, so we should say it's collapsed to test
         // screen off.
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 9bc49ae..87d813c 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
@@ -50,7 +50,6 @@
         val actualString = stringWriter.toString()
         val expectedLogString =
             disableFlagsLogger.getDisableFlagsString(
-                old = null,
                 new = state,
                 newAfterLocalModification = null,
             )
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 9b1d93b..b8f2cab 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
@@ -18,10 +18,6 @@
 
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.RUNNING_CHIP_ANIM;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -49,12 +45,12 @@
 import android.view.ViewPropertyAnimator;
 import android.widget.FrameLayout;
 
-import androidx.core.animation.Animator;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.R;
 import com.android.systemui.SysuiBaseFragmentTest;
+import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.log.LogBuffer;
@@ -85,6 +81,7 @@
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -139,6 +136,8 @@
     private StatusBarWindowStateController mStatusBarWindowStateController;
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Rule
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
 
     private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>();
 
@@ -172,7 +171,6 @@
 
     @Test
     public void testDisableSystemInfo_systemAnimationIdle_doesHide() {
-        when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
@@ -192,24 +190,26 @@
     public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() {
         // GIVEN the status bar hides the system info via disable flags, while there is no event
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
-        when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
 
+        // WHEN the system event animation starts
+        fragment.onSystemEventAnimationBegin().start();
+
+        // THEN the view remains invisible during the animation
+        assertEquals(0f, getEndSideContentView().getAlpha(), 0.01);
+        mAnimatorTestRule.advanceTimeBy(500);
+        assertEquals(0f, getEndSideContentView().getAlpha(), 0.01);
+
         // WHEN the disable flags are cleared during a system event animation
-        when(mAnimationScheduler.getAnimationState()).thenReturn(RUNNING_CHIP_ANIM);
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        // THEN the view is made visible again, but still low alpha
-        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        // THEN the view remains invisible
         assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
 
         // WHEN the system event animation finishes
-        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
-        Animator anim = fragment.onSystemEventAnimationFinish(false);
-        anim.start();
-        processAllMessages();
-        anim.end();
+        fragment.onSystemEventAnimationFinish(false).start();
+        mAnimatorTestRule.advanceTimeBy(500);
 
         // THEN the system info is full alpha
         assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
@@ -219,20 +219,15 @@
     public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() {
         // GIVEN the status bar hides the system info via disable flags, while there is no event
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
-        when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE);
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
 
         // WHEN the system event animation finishes
-        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
-        Animator anim = fragment.onSystemEventAnimationFinish(false);
-        anim.start();
-        processAllMessages();
-        anim.end();
+        fragment.onSystemEventAnimationFinish(false).start();
+        mAnimatorTestRule.advanceTimeBy(500);
 
-        // THEN the system info is at full alpha, but still INVISIBLE (since the disable flag is
-        // still set)
-        assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
+        // THEN the system info remains invisible (since the disable flag is still set)
+        assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -241,15 +236,14 @@
     public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() {
         // GIVEN the status bar is not disabled
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
-        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN);
-        // WHEN the system event animation begins
-        Animator anim = fragment.onSystemEventAnimationBegin();
-        anim.start();
-        processAllMessages();
-        anim.end();
+        assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
 
-        // THEN the system info is visible but alpha 0
-        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        // WHEN the system event animation begins
+        fragment.onSystemEventAnimationBegin().start();
+        mAnimatorTestRule.advanceTimeBy(500);
+
+        // THEN the system info is invisible
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
         assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
     }
 
@@ -257,25 +251,21 @@
     public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() {
         // GIVEN the status bar is not disabled
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
-        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN);
-        // WHEN the system event animation begins
-        Animator anim = fragment.onSystemEventAnimationBegin();
-        anim.start();
-        processAllMessages();
-        anim.end();
+        assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
 
-        // THEN the system info is visible but alpha 0
-        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        // WHEN the system event animation begins
+        fragment.onSystemEventAnimationBegin().start();
+        mAnimatorTestRule.advanceTimeBy(500);
+
+        // THEN the system info is invisible
+        assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
         assertEquals(0, getEndSideContentView().getAlpha(), 0.01);
 
         // WHEN the system event animation finishes
-        when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT);
-        anim = fragment.onSystemEventAnimationFinish(false);
-        anim.start();
-        processAllMessages();
-        anim.end();
+        fragment.onSystemEventAnimationFinish(false).start();
+        mAnimatorTestRule.advanceTimeBy(500);
 
-        // THEN the syste info is full alpha and VISIBLE
+        // THEN the system info is full alpha and VISIBLE
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
         assertEquals(1, getEndSideContentView().getAlpha(), 0.01);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
new file mode 100644
index 0000000..2ce060c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.fragment
+
+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.animation.AnimatorTestRule
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_SOURCE_1 = 1
+private const val TEST_SOURCE_2 = 2
+private const val TEST_ANIMATION_DURATION = 100L
+private const val INITIAL_ALPHA = 1f
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class MultiSourceMinAlphaControllerTest : SysuiTestCase() {
+
+    private val view = View(context)
+    private val multiSourceMinAlphaController =
+        MultiSourceMinAlphaController(view, initialAlpha = INITIAL_ALPHA)
+
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
+    @Before
+    fun setup() {
+        multiSourceMinAlphaController.reset()
+    }
+
+    @Test
+    fun testSetAlpha() {
+        multiSourceMinAlphaController.setAlpha(alpha = 0.5f, sourceId = TEST_SOURCE_1)
+        assertEquals(0.5f, view.alpha)
+    }
+
+    @Test
+    fun testAnimateToAlpha() {
+        multiSourceMinAlphaController.animateToAlpha(
+            alpha = 0.5f,
+            sourceId = TEST_SOURCE_1,
+            duration = TEST_ANIMATION_DURATION
+        )
+        animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION)
+        assertEquals(0.5f, view.alpha)
+    }
+
+    @Test
+    fun testReset() {
+        multiSourceMinAlphaController.animateToAlpha(
+            alpha = 0.5f,
+            sourceId = TEST_SOURCE_1,
+            duration = TEST_ANIMATION_DURATION
+        )
+        multiSourceMinAlphaController.setAlpha(alpha = 0.7f, sourceId = TEST_SOURCE_2)
+        multiSourceMinAlphaController.reset()
+        // advance time to ensure that animators are cancelled when the controller is reset
+        animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION)
+        assertEquals(INITIAL_ALPHA, view.alpha)
+    }
+
+    @Test
+    fun testMinOfTwoSourcesIsApplied() {
+        multiSourceMinAlphaController.setAlpha(alpha = 0f, sourceId = TEST_SOURCE_1)
+        multiSourceMinAlphaController.setAlpha(alpha = 0.5f, sourceId = TEST_SOURCE_2)
+        assertEquals(0f, view.alpha)
+        multiSourceMinAlphaController.setAlpha(alpha = 1f, sourceId = TEST_SOURCE_1)
+        assertEquals(0.5f, view.alpha)
+    }
+
+    @Test
+    fun testSetAlphaForSameSourceCancelsAnimator() {
+        multiSourceMinAlphaController.animateToAlpha(
+            alpha = 0f,
+            sourceId = TEST_SOURCE_1,
+            duration = TEST_ANIMATION_DURATION
+        )
+        animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION / 2)
+        multiSourceMinAlphaController.setAlpha(alpha = 1f, sourceId = TEST_SOURCE_1)
+        animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION / 2)
+        // verify that animation was cancelled and the setAlpha call overrides the alpha value of
+        // the animation
+        assertEquals(1f, view.alpha)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
index 6e3af26..932c4a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
@@ -152,13 +152,11 @@
         }
 
     private fun sendConfig(subId: Int) {
-        fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-            receiver.onReceive(
-                context,
-                Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
-                    .putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, subId)
-            )
-        }
+        fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+            context,
+            Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
+                .putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, subId),
+        )
     }
 
     companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 6306a36..50ee6a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -55,6 +55,8 @@
     override val networkName =
         MutableStateFlow<NetworkNameModel>(NetworkNameModel.Default("default"))
 
+    override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
+
     fun setDataEnabled(enabled: Boolean) {
         _dataEnabled.value = enabled
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
index 441186a..a251c28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
@@ -20,6 +20,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -319,6 +320,14 @@
             job.cancel()
         }
 
+    @Test
+    fun isAllowedDuringAirplaneMode_alwaysTrue() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode)
+
+            assertThat(latest).isTrue()
+        }
+
     private companion object {
         const val SUB_ID = 123
         const val NET_ID = 456
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index b701fbd..3dd2eaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -22,6 +22,7 @@
 import android.telephony.TelephonyManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -84,7 +85,11 @@
     @Before
     fun setUp() {
         mobileRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer)
-        carrierMergedRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer)
+        carrierMergedRepo =
+            FakeMobileConnectionRepository(SUB_ID, tableLogBuffer).apply {
+                // Mimicks the real carrier merged repository
+                this.isAllowedDuringAirplaneMode.value = true
+            }
 
         whenever(
                 mobileFactory.build(
@@ -300,6 +305,24 @@
         }
 
     @Test
+    fun isAllowedDuringAirplaneMode_updatesWhenCarrierMergedUpdates() =
+        testScope.runTest {
+            initializeRepo(startingIsCarrierMerged = false)
+
+            val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode)
+
+            assertThat(latest).isFalse()
+
+            underTest.setIsCarrierMerged(true)
+
+            assertThat(latest).isTrue()
+
+            underTest.setIsCarrierMerged(false)
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
     fun factory_reusesLogBuffersForSameConnection() =
         testScope.runTest {
             val realLoggerFactory =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index d1df6e3..1ff737b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -53,6 +53,7 @@
 import androidx.test.filters.SmallTest
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
@@ -379,9 +380,10 @@
             var latest: Int? = null
             val job = underTest.carrierId.onEach { latest = it }.launchIn(this)
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(context, carrierIdIntent(carrierId = 4321))
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                carrierIdIntent(carrierId = 4321),
+            )
 
             assertThat(latest).isEqualTo(4321)
 
@@ -653,10 +655,7 @@
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
 
             val intent = spnIntent()
-
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(context, intent)
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
 
             assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
 
@@ -670,17 +669,13 @@
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
 
             val intent = spnIntent()
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(context, intent)
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
             assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
 
             // WHEN an intent with a different subId is sent
             val wrongSubIntent = spnIntent(subId = 101)
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(context, wrongSubIntent)
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, wrongSubIntent)
 
             // THEN the previous intent's name is still used
             assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
@@ -695,9 +690,7 @@
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
 
             val intent = spnIntent()
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(context, intent)
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
             assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
 
             val intentWithoutInfo =
@@ -706,9 +699,7 @@
                     showPlmn = false,
                 )
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(context, intentWithoutInfo)
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intentWithoutInfo)
 
             assertThat(latest).isEqualTo(DEFAULT_NAME)
 
@@ -822,6 +813,14 @@
             job.cancel()
         }
 
+    @Test
+    fun isAllowedDuringAirplaneMode_alwaysFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode)
+
+            assertThat(latest).isFalse()
+        }
+
     private inline fun <reified T> getTelephonyCallbackForType(): T {
         return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 9aea70f..c8b6f13d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -73,7 +73,6 @@
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Assert.assertThrows
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
@@ -160,6 +159,7 @@
                 mock(),
                 mock(),
                 FakeExecutor(FakeSystemClock()),
+                dispatcher,
                 testScope.backgroundScope,
                 mock(),
             )
@@ -588,11 +588,10 @@
         }
 
     @Test
-    fun testConnectionRepository_invalidSubId_throws() =
+    fun testConnectionRepository_invalidSubId_doesNotThrow() =
         testScope.runTest {
-            assertThrows(IllegalArgumentException::class.java) {
-                underTest.getRepoForSubId(SUB_1_ID)
-            }
+            underTest.getRepoForSubId(SUB_1_ID)
+            // No exception
         }
 
     @Test
@@ -626,23 +625,17 @@
 
             assertThat(latest).isEqualTo(INVALID_SUBSCRIPTION_ID)
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(
-                    context,
-                    Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
-                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
-                )
-            }
+            val intent2 =
+                Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+                    .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent2)
 
             assertThat(latest).isEqualTo(SUB_2_ID)
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(
-                    context,
-                    Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
-                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
-                )
-            }
+            val intent1 =
+                Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+                    .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent1)
 
             assertThat(latest).isEqualTo(SUB_1_ID)
         }
@@ -1096,12 +1089,10 @@
             assertThat(configFromContext.showAtLeast3G).isTrue()
 
             // WHEN the change event is fired
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(
-                    context,
-                    Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
-                )
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED),
+            )
 
             // THEN the config is updated
             assertThat(latest!!.areEqual(configFromContext)).isTrue()
@@ -1120,12 +1111,10 @@
             assertThat(configFromContext.showAtLeast3G).isTrue()
 
             // WHEN the change event is fired
-            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
-                receiver.onReceive(
-                    context,
-                    Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
-                )
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED),
+            )
 
             // WHEN collection starts AFTER the broadcast is sent out
             val latest by collectLastValue(underTest.defaultDataSubRatConfig)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index c4e4193..8d1da69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -77,6 +77,8 @@
 
     override val isForceHidden = MutableStateFlow(false)
 
+    override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
+
     fun setIsEmergencyOnly(emergency: Boolean) {
         _isEmergencyOnly.value = emergency
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index c276865..58d3804 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -23,6 +23,7 @@
 import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType
@@ -473,6 +474,18 @@
             job.cancel()
         }
 
+    @Test
+    fun isAllowedDuringAirplaneMode_matchesRepo() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode)
+
+            connectionRepository.isAllowedDuringAirplaneMode.value = true
+            assertThat(latest).isTrue()
+
+            connectionRepository.isAllowedDuringAirplaneMode.value = false
+            assertThat(latest).isFalse()
+        }
+
     private fun createInteractor(
         overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
     ) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
index 4aa48d6..755aaa6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
@@ -54,7 +54,7 @@
     @Test
     fun collectionStarted_dumpHasInfo() {
         val view = TextView(context)
-        val viewModel = QsMobileIconViewModel(commonViewModel, flags)
+        val viewModel = QsMobileIconViewModel(commonViewModel)
 
         underTest.logCollectionStarted(view, viewModel)
 
@@ -66,8 +66,8 @@
     fun collectionStarted_multipleViews_dumpHasInfo() {
         val view = TextView(context)
         val view2 = TextView(context)
-        val viewModel = QsMobileIconViewModel(commonViewModel, flags)
-        val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags)
+        val viewModel = QsMobileIconViewModel(commonViewModel)
+        val viewModel2 = KeyguardMobileIconViewModel(commonViewModel)
 
         underTest.logCollectionStarted(view, viewModel)
         underTest.logCollectionStarted(view2, viewModel2)
@@ -81,8 +81,8 @@
     fun collectionStopped_dumpHasInfo() {
         val view = TextView(context)
         val view2 = TextView(context)
-        val viewModel = QsMobileIconViewModel(commonViewModel, flags)
-        val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags)
+        val viewModel = QsMobileIconViewModel(commonViewModel)
+        val viewModel2 = KeyguardMobileIconViewModel(commonViewModel)
 
         underTest.logCollectionStarted(view, viewModel)
         underTest.logCollectionStarted(view2, viewModel2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index 7420db2..59fc0ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -235,7 +235,6 @@
 
     @Test
     fun onDarkChanged_iconHasNewColor() {
-        whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
         val view =
             ModernStatusBarMobileView.constructAndBind(
                 context,
@@ -257,7 +256,6 @@
 
     @Test
     fun setStaticDrawableColor_iconHasNewColor() {
-        whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
         val view =
             ModernStatusBarMobileView.constructAndBind(
                 context,
@@ -298,7 +296,7 @@
                 constants,
                 testScope.backgroundScope,
             )
-        viewModel = QsMobileIconViewModel(viewModelCommon, statusBarPipelineFlags)
+        viewModel = QsMobileIconViewModel(viewModelCommon)
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index d5fb577..e59d90f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -86,9 +86,9 @@
                 testScope.backgroundScope,
             )
 
-        homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags, mock())
-        qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
-        keyguardIcon = KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+        homeIcon = HomeMobileIconViewModel(commonImpl, mock())
+        qsIcon = QsMobileIconViewModel(commonImpl)
+        keyguardIcon = KeyguardMobileIconViewModel(commonImpl)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index b5ab29d..72feec7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -116,12 +116,13 @@
         }
 
     @Test
-    fun isVisible_airplane_false() =
+    fun isVisible_airplaneAndNotAllowed_false() =
         testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
 
             airplaneModeRepository.setIsAirplaneMode(true)
+            interactor.isAllowedDuringAirplaneMode.value = false
             interactor.isForceHidden.value = false
 
             assertThat(latest).isFalse()
@@ -129,6 +130,22 @@
             job.cancel()
         }
 
+    /** Regression test for b/291993542. */
+    @Test
+    fun isVisible_airplaneButAllowed_true() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+            airplaneModeRepository.setIsAirplaneMode(true)
+            interactor.isAllowedDuringAirplaneMode.value = true
+            interactor.isForceHidden.value = false
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
     @Test
     fun isVisible_forceHidden_false() =
         testScope.runTest {
@@ -157,7 +174,7 @@
             airplaneModeRepository.setIsAirplaneMode(true)
             assertThat(latest).isFalse()
 
-            airplaneModeRepository.setIsAirplaneMode(false)
+            interactor.isAllowedDuringAirplaneMode.value = true
             assertThat(latest).isTrue()
 
             interactor.isForceHidden.value = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index 6301fa0..842d548 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -48,7 +48,11 @@
 
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
         val interactor =
-            KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope)
+            KeyguardTransitionInteractorFactory.create(
+                    scope = TestScope().backgroundScope,
+                    repository = keyguardTransitionRepository,
+                )
+                .keyguardTransitionInteractor
         underTest = CollapsedStatusBarViewModelImpl(interactor, testScope.backgroundScope)
     }
 
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 1bf431b..4f7bb72 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
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.pipeline.wifi.data.repository
 
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryHelper.ACTIVITY_DEFAULT
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -34,6 +34,8 @@
         MutableStateFlow(WifiNetworkModel.Inactive)
     override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
 
+    override val secondaryNetworks = MutableStateFlow<List<WifiNetworkModel>>(emptyList())
+
     private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
     override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
index 30b95ef..dbaa29b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
@@ -18,7 +18,9 @@
 
 import android.net.ConnectivityManager
 import android.net.wifi.WifiManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
@@ -43,6 +45,7 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
@@ -50,6 +53,8 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class WifiRepositorySwitcherTest : SysuiTestCase() {
     private lateinit var underTest: WifiRepositorySwitcher
     private lateinit var realImpl: WifiRepositoryImpl
@@ -83,6 +88,7 @@
                 logger,
                 tableLogger,
                 mainExecutor,
+                testDispatcher,
                 testScope.backgroundScope,
                 wifiManager,
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
index 9cf08c0..206ac1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
@@ -16,15 +16,20 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 
 @SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class DisabledWifiRepositoryTest : SysuiTestCase() {
 
     private lateinit var underTest: DisabledWifiRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index dc68180..bea1154 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
 
+import android.content.Intent
 import android.net.ConnectivityManager
 import android.net.Network
 import android.net.NetworkCapabilities
@@ -31,9 +32,11 @@
 import android.net.wifi.WifiManager.TrafficStateCallback
 import android.net.wifi.WifiManager.UNKNOWN_SSID
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -46,34 +49,34 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
-import org.mockito.ArgumentMatchers.anyInt
+import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
+/**
+ * Note: Any new tests added here may also need to be added to [WifiRepositoryViaTrackerLibTest].
+ */
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class WifiRepositoryImplTest : SysuiTestCase() {
 
     private lateinit var underTest: WifiRepositoryImpl
 
-    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock private lateinit var logger: WifiInputLogger
     @Mock private lateinit var tableLogger: TableLogBuffer
     @Mock private lateinit var connectivityManager: ConnectivityManager
@@ -81,20 +84,12 @@
     private lateinit var executor: Executor
     private lateinit var connectivityRepository: ConnectivityRepository
 
-    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val dispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(dispatcher)
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        whenever(
-                broadcastDispatcher.broadcastFlow(
-                    any(),
-                    nullable(),
-                    anyInt(),
-                    nullable(),
-                )
-            )
-            .thenReturn(flowOf(Unit))
         executor = FakeExecutor(FakeSystemClock())
 
         connectivityRepository =
@@ -117,6 +112,7 @@
             whenever(wifiManager.isWifiEnabled).thenReturn(true)
 
             underTest = createRepo()
+            testScope.runCurrent()
 
             assertThat(underTest.isWifiEnabled.value).isTrue()
         }
@@ -124,146 +120,113 @@
     @Test
     fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() =
         testScope.runTest {
-            // We need to call launch on the flows so that they start updating
-            val networkJob = underTest.wifiNetwork.launchIn(this)
-            val enabledJob = underTest.isWifiEnabled.launchIn(this)
+            collectLastValue(underTest.wifiNetwork)
+            val latest by collectLastValue(underTest.isWifiEnabled)
 
             whenever(wifiManager.isWifiEnabled).thenReturn(true)
             getNetworkCallback()
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
 
-            assertThat(underTest.isWifiEnabled.value).isTrue()
+            assertThat(latest).isTrue()
 
             whenever(wifiManager.isWifiEnabled).thenReturn(false)
             getNetworkCallback()
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
 
-            assertThat(underTest.isWifiEnabled.value).isFalse()
-
-            networkJob.cancel()
-            enabledJob.cancel()
+            assertThat(latest).isFalse()
         }
 
     @Test
     fun isWifiEnabled_networkLost_valueUpdated() =
         testScope.runTest {
-            // We need to call launch on the flows so that they start updating
-            val networkJob = underTest.wifiNetwork.launchIn(this)
-            val enabledJob = underTest.isWifiEnabled.launchIn(this)
+            collectLastValue(underTest.wifiNetwork)
+            val latest by collectLastValue(underTest.isWifiEnabled)
 
             whenever(wifiManager.isWifiEnabled).thenReturn(true)
             getNetworkCallback().onLost(NETWORK)
 
-            assertThat(underTest.isWifiEnabled.value).isTrue()
+            assertThat(latest).isTrue()
 
             whenever(wifiManager.isWifiEnabled).thenReturn(false)
             getNetworkCallback().onLost(NETWORK)
 
-            assertThat(underTest.isWifiEnabled.value).isFalse()
-
-            networkJob.cancel()
-            enabledJob.cancel()
+            assertThat(latest).isFalse()
         }
 
     @Test
     fun isWifiEnabled_intentsReceived_valueUpdated() =
         testScope.runTest {
-            val intentFlow = MutableSharedFlow<Unit>()
-            whenever(
-                    broadcastDispatcher.broadcastFlow(
-                        any(),
-                        nullable(),
-                        anyInt(),
-                        nullable(),
-                    )
-                )
-                .thenReturn(intentFlow)
-            underTest = createRepo()
-
-            val job = underTest.isWifiEnabled.launchIn(this)
+            val latest by collectLastValue(underTest.isWifiEnabled)
 
             whenever(wifiManager.isWifiEnabled).thenReturn(true)
-            intentFlow.emit(Unit)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
+            )
 
-            assertThat(underTest.isWifiEnabled.value).isTrue()
+            assertThat(latest).isTrue()
 
             whenever(wifiManager.isWifiEnabled).thenReturn(false)
-            intentFlow.emit(Unit)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
+            )
 
-            assertThat(underTest.isWifiEnabled.value).isFalse()
-
-            job.cancel()
+            assertThat(latest).isFalse()
         }
 
     @Test
     fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() =
         testScope.runTest {
-            val intentFlow = MutableSharedFlow<Unit>()
-            whenever(
-                    broadcastDispatcher.broadcastFlow(
-                        any(),
-                        nullable(),
-                        anyInt(),
-                        nullable(),
-                    )
-                )
-                .thenReturn(intentFlow)
-            underTest = createRepo()
-
-            val networkJob = underTest.wifiNetwork.launchIn(this)
-            val enabledJob = underTest.isWifiEnabled.launchIn(this)
+            collectLastValue(underTest.wifiNetwork)
+            val latest by collectLastValue(underTest.isWifiEnabled)
 
             whenever(wifiManager.isWifiEnabled).thenReturn(false)
-            intentFlow.emit(Unit)
-            assertThat(underTest.isWifiEnabled.value).isFalse()
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
+            )
+            assertThat(latest).isFalse()
 
             whenever(wifiManager.isWifiEnabled).thenReturn(true)
             getNetworkCallback().onLost(NETWORK)
-            assertThat(underTest.isWifiEnabled.value).isTrue()
+            assertThat(latest).isTrue()
 
             whenever(wifiManager.isWifiEnabled).thenReturn(false)
             getNetworkCallback()
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-            assertThat(underTest.isWifiEnabled.value).isFalse()
+            assertThat(latest).isFalse()
 
             whenever(wifiManager.isWifiEnabled).thenReturn(true)
-            intentFlow.emit(Unit)
-            assertThat(underTest.isWifiEnabled.value).isTrue()
-
-            networkJob.cancel()
-            enabledJob.cancel()
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
+            )
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun isWifiDefault_initiallyGetsDefault() =
-        testScope.runTest {
-            val job = underTest.isWifiDefault.launchIn(this)
-
-            assertThat(underTest.isWifiDefault.value).isFalse()
-
-            job.cancel()
-        }
+        testScope.runTest { assertThat(underTest.isWifiDefault.value).isFalse() }
 
     @Test
     fun isWifiDefault_wifiNetwork_isTrue() =
         testScope.runTest {
-            val job = underTest.isWifiDefault.launchIn(this)
+            val latest by collectLastValue(underTest.isWifiDefault)
 
             val wifiInfo = mock<WifiInfo>().apply { whenever(this.ssid).thenReturn(SSID) }
 
             getDefaultNetworkCallback()
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
 
-            assertThat(underTest.isWifiDefault.value).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     /** Regression test for b/266628069. */
     @Test
     fun isWifiDefault_transportInfoIsNotWifi_andNoWifiTransport_false() =
         testScope.runTest {
-            val job = underTest.isWifiDefault.launchIn(this)
+            val latest by collectLastValue(underTest.isWifiDefault)
 
             val transportInfo =
                 VpnTransportInfo(
@@ -280,16 +243,14 @@
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
 
-            assertThat(underTest.isWifiDefault.value).isFalse()
-
-            job.cancel()
+            assertThat(latest).isFalse()
         }
 
     /** Regression test for b/266628069. */
     @Test
     fun isWifiDefault_transportInfoIsNotWifi_butHasWifiTransport_true() =
         testScope.runTest {
-            val job = underTest.isWifiDefault.launchIn(this)
+            val latest by collectLastValue(underTest.isWifiDefault)
 
             val transportInfo =
                 VpnTransportInfo(
@@ -306,15 +267,13 @@
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
 
-            assertThat(underTest.isWifiDefault.value).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun isWifiDefault_carrierMergedViaCellular_isTrue() =
         testScope.runTest {
-            val job = underTest.isWifiDefault.launchIn(this)
+            val latest by collectLastValue(underTest.isWifiDefault)
 
             val carrierMergedInfo =
                 mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -328,15 +287,13 @@
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
-            assertThat(underTest.isWifiDefault.value).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun isWifiDefault_carrierMergedViaCellular_withVcnTransport_isTrue() =
         testScope.runTest {
-            val job = underTest.isWifiDefault.launchIn(this)
+            val latest by collectLastValue(underTest.isWifiDefault)
 
             val capabilities =
                 mock<NetworkCapabilities>().apply {
@@ -347,15 +304,13 @@
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
-            assertThat(underTest.isWifiDefault.value).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun isWifiDefault_carrierMergedViaWifi_isTrue() =
         testScope.runTest {
-            val job = underTest.isWifiDefault.launchIn(this)
+            val latest by collectLastValue(underTest.isWifiDefault)
 
             val carrierMergedInfo =
                 mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -369,15 +324,13 @@
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
-            assertThat(underTest.isWifiDefault.value).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun isWifiDefault_carrierMergedViaWifi_withVcnTransport_isTrue() =
         testScope.runTest {
-            val job = underTest.isWifiDefault.launchIn(this)
+            val latest by collectLastValue(underTest.isWifiDefault)
 
             val capabilities =
                 mock<NetworkCapabilities>().apply {
@@ -388,15 +341,13 @@
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
-            assertThat(underTest.isWifiDefault.value).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun isWifiDefault_cellularAndWifiTransports_usesCellular_isTrue() =
         testScope.runTest {
-            val job = underTest.isWifiDefault.launchIn(this)
+            val latest by collectLastValue(underTest.isWifiDefault)
 
             val capabilities =
                 mock<NetworkCapabilities>().apply {
@@ -407,15 +358,13 @@
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
-            assertThat(underTest.isWifiDefault.value).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun isWifiDefault_cellularNotVcnNetwork_isFalse() =
         testScope.runTest {
-            val job = underTest.isWifiDefault.launchIn(this)
+            val latest by collectLastValue(underTest.isWifiDefault)
 
             val capabilities =
                 mock<NetworkCapabilities>().apply {
@@ -425,15 +374,13 @@
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
-            assertThat(underTest.isWifiDefault.value).isFalse()
-
-            job.cancel()
+            assertThat(latest).isFalse()
         }
 
     @Test
     fun isWifiDefault_isCarrierMergedViaUnderlyingWifi_isTrue() =
         testScope.runTest {
-            val job = underTest.isWifiDefault.launchIn(this)
+            val latest by collectLastValue(underTest.isWifiDefault)
 
             val underlyingNetwork = mock<Network>()
             val carrierMergedInfo =
@@ -460,15 +407,13 @@
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
 
             // THEN the wifi network is carrier merged, so wifi is default
-            assertThat(underTest.isWifiDefault.value).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun isWifiDefault_isCarrierMergedViaUnderlyingCellular_isTrue() =
         testScope.runTest {
-            val job = underTest.isWifiDefault.launchIn(this)
+            val latest by collectLastValue(underTest.isWifiDefault)
 
             val underlyingCarrierMergedNetwork = mock<Network>()
             val carrierMergedInfo =
@@ -494,46 +439,38 @@
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
 
             // THEN the wifi network is carrier merged, so wifi is default
-            assertThat(underTest.isWifiDefault.value).isTrue()
-
-            job.cancel()
+            assertThat(latest).isTrue()
         }
 
     @Test
     fun isWifiDefault_wifiNetworkLost_isFalse() =
         testScope.runTest {
-            val job = underTest.isWifiDefault.launchIn(this)
+            val latest by collectLastValue(underTest.isWifiDefault)
 
             // First, add a network
             getDefaultNetworkCallback()
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-            assertThat(underTest.isWifiDefault.value).isTrue()
+            assertThat(latest).isTrue()
 
             // WHEN the network is lost
             getDefaultNetworkCallback().onLost(NETWORK)
 
             // THEN we update to false
-            assertThat(underTest.isWifiDefault.value).isFalse()
-
-            job.cancel()
+            assertThat(latest).isFalse()
         }
 
     @Test
     fun wifiNetwork_initiallyGetsDefault() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT)
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             val wifiInfo =
                 mock<WifiInfo>().apply {
@@ -549,15 +486,32 @@
             val latestActive = latest as WifiNetworkModel.Active
             assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
             assertThat(latestActive.ssid).isEqualTo(SSID)
+        }
 
-            job.cancel()
+    @Test
+    fun wifiNetwork_neverHasHotspot() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiInfo =
+                mock<WifiInfo>().apply {
+                    whenever(this.ssid).thenReturn(SSID)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
+            val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
+
+            getNetworkCallback()
+                .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
+
+            assertThat(latest is WifiNetworkModel.Active).isTrue()
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE)
         }
 
     @Test
     fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             val wifiInfo =
                 mock<WifiInfo>().apply {
@@ -569,15 +523,12 @@
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
 
             assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_isCarrierMergedViaUnderlyingWifi_flowHasCarrierMerged() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             val underlyingNetwork = mock<Network>()
             val carrierMergedInfo =
@@ -606,15 +557,12 @@
 
             // THEN the wifi network is carrier merged
             assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_isCarrierMergedViaUnderlyingCellular_flowHasCarrierMerged() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             val underlyingCarrierMergedNetwork = mock<Network>()
             val carrierMergedInfo =
@@ -644,15 +592,12 @@
 
             // THEN the wifi network is carrier merged
             assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             val wifiInfo =
                 mock<WifiInfo>().apply {
@@ -668,15 +613,12 @@
                 )
 
             assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java)
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_isCarrierMerged_getsCorrectValues() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             val rssi = -57
             val wifiInfo =
@@ -703,15 +645,12 @@
             assertThat(latestCarrierMerged.level).isEqualTo(2)
             // numberOfLevels = maxSignalLevel + 1
             assertThat(latestCarrierMerged.numberOfLevels).isEqualTo(6)
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_notValidated_networkNotValidated() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             getNetworkCallback()
                 .onCapabilitiesChanged(
@@ -720,15 +659,12 @@
                 )
 
             assertThat((latest as WifiNetworkModel.Active).isValidated).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_validated_networkValidated() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             getNetworkCallback()
                 .onCapabilitiesChanged(
@@ -737,15 +673,12 @@
                 )
 
             assertThat((latest as WifiNetworkModel.Active).isValidated).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             val wifiInfo =
                 mock<WifiInfo>().apply {
@@ -757,16 +690,13 @@
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
 
             assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
-            job.cancel()
         }
 
     /** Regression test for b/266628069. */
     @Test
     fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             val transportInfo =
                 VpnTransportInfo(
@@ -777,15 +707,12 @@
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(transportInfo))
 
             assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             val capabilities =
                 mock<NetworkCapabilities>().apply {
@@ -799,15 +726,12 @@
             val latestActive = latest as WifiNetworkModel.Active
             assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
             assertThat(latestActive.ssid).isEqualTo(SSID)
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             val wifiInfo =
                 mock<WifiInfo>().apply {
@@ -823,15 +747,12 @@
             getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
             assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             val capabilities =
                 mock<NetworkCapabilities>().apply {
@@ -842,15 +763,12 @@
             getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
 
             assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_cellularAndWifiTransports_usesCellular() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             val capabilities =
                 mock<NetworkCapabilities>().apply {
@@ -865,15 +783,12 @@
             val latestActive = latest as WifiNetworkModel.Active
             assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
             assertThat(latestActive.ssid).isEqualTo(SSID)
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             // Start with the original network
             getNetworkCallback()
@@ -898,15 +813,12 @@
             val latestActive = latest as WifiNetworkModel.Active
             assertThat(latestActive.networkId).isEqualTo(newNetworkId)
             assertThat(latestActive.ssid).isEqualTo(newSsid)
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             // Start with the original network
             getNetworkCallback()
@@ -931,15 +843,12 @@
             val latestActive = latest as WifiNetworkModel.Active
             assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
             assertThat(latestActive.ssid).isEqualTo(SSID)
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_newNetworkCapabilities_flowHasNewData() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             val wifiInfo =
                 mock<WifiInfo>().apply {
@@ -974,30 +883,24 @@
             assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
             assertThat(latestActive.ssid).isEqualTo(newSsid)
             assertThat(latestActive.isValidated).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             // WHEN we receive #onLost without any #onCapabilitiesChanged beforehand
             getNetworkCallback().onLost(NETWORK)
 
             // THEN there's no crash and we still have no network
             assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_currentActiveNetworkLost_flowHasNoNetwork() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             getNetworkCallback()
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
@@ -1008,16 +911,13 @@
 
             // THEN we update to no network
             assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
-            job.cancel()
         }
 
     /** Possible regression test for b/278618530. */
     @Test
     fun wifiNetwork_currentCarrierMergedNetworkLost_flowHasNoNetwork() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             val wifiInfo =
                 mock<WifiInfo>().apply {
@@ -1035,15 +935,12 @@
 
             // THEN we update to no network
             assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             getNetworkCallback()
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
@@ -1058,15 +955,12 @@
             val latestActive = latest as WifiNetworkModel.Active
             assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
             assertThat(latestActive.ssid).isEqualTo(SSID)
-
-            job.cancel()
         }
 
     @Test
     fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() =
         testScope.runTest {
-            var latest: WifiNetworkModel? = null
-            val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiNetwork)
 
             getNetworkCallback()
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
@@ -1083,16 +977,13 @@
 
             // THEN we still have the new network
             assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(newNetworkId)
-
-            job.cancel()
         }
 
     /** Regression test for b/244173280. */
     @Test
     fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() =
         testScope.runTest {
-            var latest1: WifiNetworkModel? = null
-            val job1 = underTest.wifiNetwork.onEach { latest1 = it }.launchIn(this)
+            val latest1 by collectLastValue(underTest.wifiNetwork)
 
             getNetworkCallback()
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
@@ -1103,23 +994,40 @@
             assertThat(latest1Active.ssid).isEqualTo(SSID)
 
             // WHEN we add a second subscriber after having already emitted a value
-            var latest2: WifiNetworkModel? = null
-            val job2 = underTest.wifiNetwork.onEach { latest2 = it }.launchIn(this)
+            val latest2 by collectLastValue(underTest.wifiNetwork)
 
             // THEN the second subscribe receives the already-emitted value
             assertThat(latest2 is WifiNetworkModel.Active).isTrue()
             val latest2Active = latest2 as WifiNetworkModel.Active
             assertThat(latest2Active.networkId).isEqualTo(NETWORK_ID)
             assertThat(latest2Active.ssid).isEqualTo(SSID)
+        }
 
-            job1.cancel()
-            job2.cancel()
+    @Test
+    fun secondaryNetworks_alwaysEmpty() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.secondaryNetworks)
+            collectLastValue(underTest.wifiNetwork)
+
+            // Even WHEN we do have non-primary wifi info
+            val wifiInfo =
+                mock<WifiInfo>().apply {
+                    whenever(this.ssid).thenReturn(SSID)
+                    whenever(this.isPrimary).thenReturn(false)
+                }
+            val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
+
+            getNetworkCallback()
+                .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
+
+            // THEN the secondary networks list is empty because this repo doesn't support it
+            assertThat(latest).isEmpty()
         }
 
     @Test
     fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
         testScope.runTest {
-            val job = underTest.wifiNetwork.launchIn(this)
+            collectLastValue(underTest.wifiNetwork)
 
             val wifiInfo =
                 mock<WifiInfo>().apply {
@@ -1132,14 +1040,12 @@
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
 
             assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun isWifiConnectedWithValidSsid_carrierMergedNetwork_false() =
         testScope.runTest {
-            val job = underTest.wifiNetwork.launchIn(this)
+            collectLastValue(underTest.wifiNetwork)
 
             val wifiInfo =
                 mock<WifiInfo>().apply {
@@ -1149,16 +1055,15 @@
 
             getNetworkCallback()
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+            testScope.runCurrent()
 
             assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun isWifiConnectedWithValidSsid_invalidNetwork_false() =
         testScope.runTest {
-            val job = underTest.wifiNetwork.launchIn(this)
+            collectLastValue(underTest.wifiNetwork)
 
             val wifiInfo =
                 mock<WifiInfo>().apply {
@@ -1172,16 +1077,15 @@
                     NETWORK,
                     createWifiNetworkCapabilities(wifiInfo),
                 )
+            testScope.runCurrent()
 
             assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() =
         testScope.runTest {
-            val job = underTest.wifiNetwork.launchIn(this)
+            collectLastValue(underTest.wifiNetwork)
 
             val wifiInfo =
                 mock<WifiInfo>().apply {
@@ -1191,16 +1095,15 @@
 
             getNetworkCallback()
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+            testScope.runCurrent()
 
             assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() =
         testScope.runTest {
-            val job = underTest.wifiNetwork.launchIn(this)
+            collectLastValue(underTest.wifiNetwork)
 
             val wifiInfo =
                 mock<WifiInfo>().apply {
@@ -1210,16 +1113,15 @@
 
             getNetworkCallback()
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+            testScope.runCurrent()
 
             assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() =
         testScope.runTest {
-            val job = underTest.wifiNetwork.launchIn(this)
+            collectLastValue(underTest.wifiNetwork)
 
             val wifiInfo =
                 mock<WifiInfo>().apply {
@@ -1229,16 +1131,15 @@
 
             getNetworkCallback()
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+            testScope.runCurrent()
 
             assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun isWifiConnectedWithValidSsid_activeToInactive_trueToFalse() =
         testScope.runTest {
-            val job = underTest.wifiNetwork.launchIn(this)
+            collectLastValue(underTest.wifiNetwork)
 
             // Start with active
             val wifiInfo =
@@ -1248,99 +1149,92 @@
                 }
             getNetworkCallback()
                 .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+            testScope.runCurrent()
+
             assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
 
             // WHEN the network is lost
             getNetworkCallback().onLost(NETWORK)
+            testScope.runCurrent()
 
             // THEN the isWifiConnected updates
             assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun wifiActivity_callbackGivesNone_activityFlowHasNone() =
         testScope.runTest {
-            var latest: DataActivityModel? = null
-            val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiActivity)
 
             getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE)
 
             assertThat(latest)
                 .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
-
-            job.cancel()
         }
 
     @Test
     fun wifiActivity_callbackGivesIn_activityFlowHasIn() =
         testScope.runTest {
-            var latest: DataActivityModel? = null
-            val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiActivity)
 
             getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN)
 
             assertThat(latest)
                 .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false))
-
-            job.cancel()
         }
 
     @Test
     fun wifiActivity_callbackGivesOut_activityFlowHasOut() =
         testScope.runTest {
-            var latest: DataActivityModel? = null
-            val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiActivity)
 
             getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT)
 
             assertThat(latest)
                 .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true))
-
-            job.cancel()
         }
 
     @Test
     fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() =
         testScope.runTest {
-            var latest: DataActivityModel? = null
-            val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.wifiActivity)
 
             getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT)
 
             assertThat(latest)
                 .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
-
-            job.cancel()
         }
 
     private fun createRepo(): WifiRepositoryImpl {
         return WifiRepositoryImpl(
-            broadcastDispatcher,
+            fakeBroadcastDispatcher,
             connectivityManager,
             connectivityRepository,
             logger,
             tableLogger,
             executor,
+            dispatcher,
             testScope.backgroundScope,
             wifiManager,
         )
     }
 
     private fun getTrafficStateCallback(): TrafficStateCallback {
+        testScope.runCurrent()
         val callbackCaptor = argumentCaptor<TrafficStateCallback>()
         verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
         return callbackCaptor.value!!
     }
 
     private fun getNetworkCallback(): ConnectivityManager.NetworkCallback {
+        testScope.runCurrent()
         val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
         verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture())
         return callbackCaptor.value!!
     }
 
     private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+        testScope.runCurrent()
         val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
         verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
         return callbackCaptor.value!!
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
new file mode 100644
index 0000000..662e36a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
@@ -0,0 +1,1162 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
+
+import android.net.wifi.WifiManager
+import android.net.wifi.WifiManager.UNKNOWN_SSID
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.wifitrackerlib.HotspotNetworkEntry
+import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType
+import com.android.wifitrackerlib.MergedCarrierEntry
+import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
+import com.android.wifitrackerlib.WifiPickerTracker
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+/**
+ * Note: Most of these tests are duplicates of [WifiRepositoryImplTest] tests.
+ *
+ * Any new tests added here may also need to be added to [WifiRepositoryImplTest].
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {
+
+    // Using lazy means that the class will only be constructed once it's fetched. Because the
+    // repository internally sets some values on construction, we need to set up some test
+    // parameters (like feature flags) *before* construction. Using lazy allows us to do that setup
+    // inside each test case without needing to manually recreate the repository.
+    private val underTest: WifiRepositoryViaTrackerLib by lazy {
+        WifiRepositoryViaTrackerLib(
+            featureFlags,
+            testScope.backgroundScope,
+            executor,
+            wifiPickerTrackerFactory,
+            wifiManager,
+            logger,
+            tableLogger,
+        )
+    }
+
+    private val executor = FakeExecutor(FakeSystemClock())
+    private val logger = LogBuffer("name", maxSize = 100, logcatEchoTracker = mock())
+    private val featureFlags = FakeFeatureFlags()
+    private val tableLogger = mock<TableLogBuffer>()
+    private val wifiManager =
+        mock<WifiManager>().apply { whenever(this.maxSignalLevel).thenReturn(10) }
+    private val wifiPickerTrackerFactory = mock<WifiPickerTrackerFactory>()
+    private val wifiPickerTracker = mock<WifiPickerTracker>()
+
+    private val callbackCaptor = argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
+
+    private val dispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+
+    @Before
+    fun setUp() {
+        featureFlags.set(Flags.INSTANT_TETHER, false)
+        featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, false)
+        whenever(wifiPickerTrackerFactory.create(any(), capture(callbackCaptor)))
+            .thenReturn(wifiPickerTracker)
+    }
+
+    @Test
+    fun wifiPickerTrackerCreation_scansDisabled() =
+        testScope.runTest {
+            collectLastValue(underTest.wifiNetwork)
+            testScope.runCurrent()
+
+            verify(wifiPickerTracker).disableScanning()
+        }
+
+    @Test
+    fun isWifiEnabled_enabled_true() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isWifiEnabled)
+
+            whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_ENABLED)
+            getCallback().onWifiStateChanged()
+
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun isWifiEnabled_enabling_false() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isWifiEnabled)
+
+            whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_ENABLING)
+            getCallback().onWifiStateChanged()
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isWifiEnabled_disabling_true() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isWifiEnabled)
+
+            whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_DISABLING)
+            getCallback().onWifiStateChanged()
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isWifiEnabled_disabled_false() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isWifiEnabled)
+
+            whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_DISABLED)
+            getCallback().onWifiStateChanged()
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isWifiEnabled_respondsToUpdates() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isWifiEnabled)
+            executor.runAllReady()
+
+            whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_ENABLED)
+            getCallback().onWifiStateChanged()
+
+            assertThat(latest).isTrue()
+
+            whenever(wifiPickerTracker.wifiState).thenReturn(WifiManager.WIFI_STATE_DISABLED)
+            getCallback().onWifiStateChanged()
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isWifiDefault_initiallyGetsDefault() =
+        testScope.runTest { assertThat(underTest.isWifiDefault.value).isFalse() }
+
+    @Test
+    fun isWifiDefault_wifiNetwork_isTrue() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isWifiDefault)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply { whenever(this.isDefaultNetwork).thenReturn(true) }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun isWifiDefault_carrierMerged_isTrue() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isWifiDefault)
+
+            val wifiEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isDefaultNetwork).thenReturn(true)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun isWifiDefault_wifiNetworkNotDefault_isFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isWifiDefault)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply { whenever(this.isDefaultNetwork).thenReturn(false) }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isWifiDefault_carrierMergedNotDefault_isFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isWifiDefault)
+
+            val wifiEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isDefaultNetwork).thenReturn(false)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isWifiDefault_noWifiNetwork_isFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isWifiDefault)
+
+            // First, add a network
+            val wifiEntry =
+                mock<WifiEntry>().apply { whenever(this.isDefaultNetwork).thenReturn(true) }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isTrue()
+
+            // WHEN the network is lost
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+            getCallback().onWifiEntriesChanged()
+
+            // THEN we update to false
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun wifiNetwork_initiallyGetsDefault() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT)
+        }
+
+    @Test
+    fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(3)
+                    whenever(this.title).thenReturn(TITLE)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest is WifiNetworkModel.Active).isTrue()
+            val latestActive = latest as WifiNetworkModel.Active
+            assertThat(latestActive.level).isEqualTo(3)
+            assertThat(latestActive.ssid).isEqualTo(TITLE)
+        }
+
+    @Test
+    fun accessPointInfo_alwaysFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(3)
+                    whenever(this.title).thenReturn(TITLE)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest is WifiNetworkModel.Active).isTrue()
+            val latestActive = latest as WifiNetworkModel.Active
+            assertThat(latestActive.isPasspointAccessPoint).isFalse()
+            assertThat(latestActive.isOnlineSignUpForPasspointAccessPoint).isFalse()
+            assertThat(latestActive.passpointProviderFriendlyName).isNull()
+        }
+
+    @Test
+    fun wifiNetwork_unreachableLevel_inactiveNetwork() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(WIFI_LEVEL_UNREACHABLE)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+        }
+
+    @Test
+    fun wifiNetwork_levelTooHigh_inactiveNetwork() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(WIFI_LEVEL_MAX + 1)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+        }
+
+    @Test
+    fun wifiNetwork_levelTooLow_inactiveNetwork() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(WIFI_LEVEL_MIN - 1)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+        }
+
+    @Test
+    fun wifiNetwork_levelIsMax_activeNetworkWithMaxLevel() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(WIFI_LEVEL_MAX)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isInstanceOf(WifiNetworkModel.Active::class.java)
+            assertThat((latest as WifiNetworkModel.Active).level).isEqualTo(WIFI_LEVEL_MAX)
+        }
+
+    @Test
+    fun wifiNetwork_levelIsMin_activeNetworkWithMinLevel() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(WIFI_LEVEL_MIN)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isInstanceOf(WifiNetworkModel.Active::class.java)
+            assertThat((latest as WifiNetworkModel.Active).level).isEqualTo(WIFI_LEVEL_MIN)
+        }
+
+    @Test
+    fun wifiNetwork_notHotspot_none() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_unknown() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_UNKNOWN)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.UNKNOWN)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_phone() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_PHONE)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.PHONE)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_tablet() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_TABLET)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.TABLET)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_laptop() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_LAPTOP)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.LAPTOP)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_watch() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_WATCH)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.WATCH)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_auto() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_AUTO)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.AUTO)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_invalid() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(1234)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.INVALID)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_flagOff_valueNotUsed() =
+        testScope.runTest {
+            // WHEN the flag is off
+            featureFlags.set(Flags.INSTANT_TETHER, false)
+
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_WATCH)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            // THEN NONE is always used, even if the wifi entry does have a hotspot device type
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE)
+        }
+
+    @Test
+    fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(3)
+                    whenever(this.subscriptionId).thenReturn(567)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+            val latestMerged = latest as WifiNetworkModel.CarrierMerged
+            assertThat(latestMerged.level).isEqualTo(3)
+            assertThat(latestMerged.subscriptionId).isEqualTo(567)
+        }
+
+    @Test
+    fun wifiNetwork_isCarrierMerged_getsMaxSignalLevel() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiManager.maxSignalLevel).thenReturn(5)
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+            val latestMerged = latest as WifiNetworkModel.CarrierMerged
+            // numberOfLevels = maxSignalLevel + 1
+            assertThat(latestMerged.numberOfLevels).isEqualTo(6)
+        }
+
+    @Test
+    fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java)
+        }
+
+    @Test
+    fun wifiNetwork_notValidated_networkNotValidated() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.hasInternetAccess()).thenReturn(false)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).isValidated).isFalse()
+        }
+
+    @Test
+    fun wifiNetwork_validated_networkValidated() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.hasInternetAccess()).thenReturn(true)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).isValidated).isTrue()
+        }
+
+    @Test
+    fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(false) }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+        }
+
+    @Test
+    fun wifiNetwork_nonPrimaryCarrierMergedNetworkAdded_flowHasNoNetwork() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(false)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+        }
+
+    @Test
+    fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            // Start with the original network
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(3)
+                    whenever(this.title).thenReturn("AB")
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest is WifiNetworkModel.Active).isTrue()
+            var latestActive = latest as WifiNetworkModel.Active
+            assertThat(latestActive.level).isEqualTo(3)
+            assertThat(latestActive.ssid).isEqualTo("AB")
+
+            // WHEN we update to a new primary network
+            val newWifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(4)
+                    whenever(this.title).thenReturn("CD")
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(newWifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            // THEN we use the new network
+            assertThat(latest is WifiNetworkModel.Active).isTrue()
+            latestActive = latest as WifiNetworkModel.Active
+            assertThat(latestActive.level).isEqualTo(4)
+            assertThat(latestActive.ssid).isEqualTo("CD")
+        }
+
+    @Test
+    fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            // WHEN we receive a null network without any networks beforehand
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+            getCallback().onWifiEntriesChanged()
+
+            // THEN there's no crash and we still have no network
+            assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+        }
+
+    @Test
+    fun wifiNetwork_currentActiveNetworkLost_flowHasNoNetwork() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.title).thenReturn(TITLE)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).ssid).isEqualTo(TITLE)
+
+            // WHEN we lose our current network
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+            getCallback().onWifiEntriesChanged()
+
+            // THEN we update to no network
+            assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+        }
+
+    /** Possible regression test for b/278618530. */
+    @Test
+    fun wifiNetwork_currentCarrierMergedNetworkLost_flowHasNoNetwork() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(3)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+            assertThat((latest as WifiNetworkModel.CarrierMerged).level).isEqualTo(3)
+
+            // WHEN we lose our current network
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+            getCallback().onWifiEntriesChanged()
+
+            // THEN we update to no network
+            assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+        }
+
+    /** Regression test for b/244173280. */
+    @Test
+    fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() =
+        testScope.runTest {
+            val latest1 by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(1)
+                    whenever(this.title).thenReturn(TITLE)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest1 is WifiNetworkModel.Active).isTrue()
+            val latest1Active = latest1 as WifiNetworkModel.Active
+            assertThat(latest1Active.level).isEqualTo(1)
+            assertThat(latest1Active.ssid).isEqualTo(TITLE)
+
+            // WHEN we add a second subscriber after having already emitted a value
+            val latest2 by collectLastValue(underTest.wifiNetwork)
+
+            // THEN the second subscribe receives the already-emitted value
+            assertThat(latest2 is WifiNetworkModel.Active).isTrue()
+            val latest2Active = latest2 as WifiNetworkModel.Active
+            assertThat(latest2Active.level).isEqualTo(1)
+            assertThat(latest2Active.ssid).isEqualTo(TITLE)
+        }
+
+    @Test
+    fun secondaryNetworks_activeEntriesEmpty_isEmpty() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf())
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isEmpty()
+        }
+
+    @Test
+    fun secondaryNetworks_oneActiveEntry_hasOne() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val wifiEntry = mock<WifiEntry>()
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(wifiEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(1)
+        }
+
+    @Test
+    fun secondaryNetworks_multipleActiveEntries_hasMultiple() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val wifiEntry1 = mock<WifiEntry>()
+            val wifiEntry2 = mock<WifiEntry>()
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(wifiEntry1, wifiEntry2))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(2)
+        }
+
+    @Test
+    fun secondaryNetworks_mapsToInactive() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val inactiveEntry =
+                mock<WifiEntry>().apply { whenever(this.level).thenReturn(WIFI_LEVEL_UNREACHABLE) }
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(inactiveEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.Inactive::class.java)
+        }
+
+    @Test
+    fun secondaryNetworks_mapsToActive() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val activeEntry = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(activeEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.Active::class.java)
+            assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+        }
+
+    @Test
+    fun secondaryNetworks_mapsToCarrierMerged() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val carrierMergedEntry =
+                mock<MergedCarrierEntry>().apply { whenever(this.level).thenReturn(3) }
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(carrierMergedEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.CarrierMerged::class.java)
+            assertThat((latest!![0] as WifiNetworkModel.CarrierMerged).level).isEqualTo(3)
+        }
+
+    @Test
+    fun secondaryNetworks_mapsMultipleInOrder() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val activeEntry = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+            val carrierMergedEntry =
+                mock<MergedCarrierEntry>().apply { whenever(this.level).thenReturn(3) }
+            whenever(wifiPickerTracker.activeWifiEntries)
+                .thenReturn(listOf(activeEntry, carrierMergedEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.Active::class.java)
+            assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+            assertThat(latest!![1]).isInstanceOf(WifiNetworkModel.CarrierMerged::class.java)
+            assertThat((latest!![1] as WifiNetworkModel.CarrierMerged).level).isEqualTo(3)
+        }
+
+    @Test
+    fun secondaryNetworks_filtersOutConnectedEntry() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val connectedEntry = mock<WifiEntry>().apply { whenever(this.level).thenReturn(1) }
+            val secondaryEntry1 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+            val secondaryEntry2 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(3) }
+            // WHEN the active list has both a primary and secondary networks
+            whenever(wifiPickerTracker.activeWifiEntries)
+                .thenReturn(listOf(connectedEntry, secondaryEntry1, secondaryEntry2))
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(connectedEntry)
+
+            getCallback().onWifiEntriesChanged()
+
+            // THEN only the secondary networks are included
+            assertThat(latest).hasSize(2)
+            assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+            assertThat((latest!![1] as WifiNetworkModel.Active).level).isEqualTo(3)
+        }
+
+    @Test
+    fun secondaryNetworks_noConnectedEntry_hasAllActiveEntries() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val secondaryEntry1 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+            val secondaryEntry2 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(3) }
+            whenever(wifiPickerTracker.activeWifiEntries)
+                .thenReturn(listOf(secondaryEntry1, secondaryEntry2))
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(2)
+            assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+            assertThat((latest!![1] as WifiNetworkModel.Active).level).isEqualTo(3)
+        }
+
+    @Test
+    fun secondaryNetworks_filtersOutPrimaryNetwork() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val primaryEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(1)
+                }
+            val secondaryEntry1 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+            val secondaryEntry2 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(3) }
+            // WHEN the active list has both a primary and secondary networks
+            whenever(wifiPickerTracker.activeWifiEntries)
+                .thenReturn(listOf(secondaryEntry1, primaryEntry, secondaryEntry2))
+
+            getCallback().onWifiEntriesChanged()
+
+            // THEN only the secondary networks are included
+            assertThat(latest).hasSize(2)
+            assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+            assertThat((latest!![1] as WifiNetworkModel.Active).level).isEqualTo(3)
+        }
+
+    @Test
+    fun secondaryNetworks_flagOff_noNetworks() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, false)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val wifiEntry = mock<WifiEntry>()
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(wifiEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isEmpty()
+        }
+
+    @Test
+    fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
+        testScope.runTest {
+            collectLastValue(underTest.wifiNetwork)
+
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+        }
+
+    @Test
+    fun isWifiConnectedWithValidSsid_nonPrimaryNetwork_false() =
+        testScope.runTest {
+            collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(false) }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+            testScope.runCurrent()
+
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+        }
+
+    @Test
+    fun isWifiConnectedWithValidSsid_carrierMergedNetwork_false() =
+        testScope.runTest {
+            collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+            testScope.runCurrent()
+
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+        }
+
+    @Test
+    fun isWifiConnectedWithValidSsid_invalidNetwork_false() =
+        testScope.runTest {
+            collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+            testScope.runCurrent()
+
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+        }
+
+    @Test
+    fun isWifiConnectedWithValidSsid_activeNetwork_nullTitle_false() =
+        testScope.runTest {
+            collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.title).thenReturn(null)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+            testScope.runCurrent()
+
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+        }
+
+    @Test
+    fun isWifiConnectedWithValidSsid_activeNetwork_unknownTitle_false() =
+        testScope.runTest {
+            collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.title).thenReturn(UNKNOWN_SSID)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+            testScope.runCurrent()
+
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+        }
+
+    @Test
+    fun isWifiConnectedWithValidSsid_activeNetwork_validTitle_true() =
+        testScope.runTest {
+            collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.title).thenReturn("fakeSsid")
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+            testScope.runCurrent()
+
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
+        }
+
+    @Test
+    fun isWifiConnectedWithValidSsid_activeToInactive_trueToFalse() =
+        testScope.runTest {
+            collectLastValue(underTest.wifiNetwork)
+
+            // Start with active
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.title).thenReturn("fakeSsid")
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+            testScope.runCurrent()
+
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
+
+            // WHEN the network is lost
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+            getCallback().onWifiEntriesChanged()
+            testScope.runCurrent()
+
+            // THEN the isWifiConnected updates
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+        }
+
+    @Test
+    fun wifiActivity_callbackGivesNone_activityFlowHasNone() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiActivity)
+
+            getTrafficStateCallback()
+                .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE)
+
+            assertThat(latest)
+                .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+        }
+
+    @Test
+    fun wifiActivity_callbackGivesIn_activityFlowHasIn() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiActivity)
+
+            getTrafficStateCallback()
+                .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN)
+
+            assertThat(latest)
+                .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false))
+        }
+
+    @Test
+    fun wifiActivity_callbackGivesOut_activityFlowHasOut() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiActivity)
+
+            getTrafficStateCallback()
+                .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT)
+
+            assertThat(latest)
+                .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true))
+        }
+
+    @Test
+    fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiActivity)
+
+            getTrafficStateCallback()
+                .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT)
+
+            assertThat(latest)
+                .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
+        }
+
+    private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback {
+        testScope.runCurrent()
+        return callbackCaptor.value
+    }
+
+    private fun getTrafficStateCallback(): WifiManager.TrafficStateCallback {
+        testScope.runCurrent()
+        val callbackCaptor = argumentCaptor<WifiManager.TrafficStateCallback>()
+        verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
+        return callbackCaptor.value!!
+    }
+
+    private fun createHotspotWithType(@DeviceType type: Int): HotspotNetworkEntry {
+        return mock<HotspotNetworkEntry>().apply {
+            whenever(this.isPrimaryNetwork).thenReturn(true)
+            whenever(this.deviceType).thenReturn(type)
+        }
+    }
+
+    private companion object {
+        const val TITLE = "AB"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index fc2277b..6fe88c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -17,7 +17,9 @@
 package com.android.systemui.statusbar.pipeline.wifi.domain.interactor
 
 import android.net.wifi.WifiManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -25,18 +27,22 @@
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 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.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.yield
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class WifiInteractorImplTest : SysuiTestCase() {
 
     private lateinit var underTest: WifiInteractor
@@ -44,6 +50,8 @@
     private lateinit var connectivityRepository: FakeConnectivityRepository
     private lateinit var wifiRepository: FakeWifiRepository
 
+    private val testScope = TestScope()
+
     @Before
     fun setUp() {
         connectivityRepository = FakeConnectivityRepository()
@@ -53,11 +61,12 @@
 
     @Test
     fun ssid_unavailableNetwork_outputsNull() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             wifiRepository.setWifiNetwork(WifiNetworkModel.Unavailable)
 
             var latest: String? = "default"
             val job = underTest.ssid.onEach { latest = it }.launchIn(this)
+            runCurrent()
 
             assertThat(latest).isNull()
 
@@ -66,11 +75,12 @@
 
     @Test
     fun ssid_inactiveNetwork_outputsNull() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
 
             var latest: String? = "default"
             val job = underTest.ssid.onEach { latest = it }.launchIn(this)
+            runCurrent()
 
             assertThat(latest).isNull()
 
@@ -79,13 +89,14 @@
 
     @Test
     fun ssid_carrierMergedNetwork_outputsNull() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             wifiRepository.setWifiNetwork(
                 WifiNetworkModel.CarrierMerged(networkId = 1, subscriptionId = 2, level = 1)
             )
 
             var latest: String? = "default"
             val job = underTest.ssid.onEach { latest = it }.launchIn(this)
+            runCurrent()
 
             assertThat(latest).isNull()
 
@@ -94,7 +105,7 @@
 
     @Test
     fun ssid_isPasspointAccessPoint_outputsPasspointName() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             wifiRepository.setWifiNetwork(
                 WifiNetworkModel.Active(
                     networkId = 1,
@@ -106,6 +117,7 @@
 
             var latest: String? = null
             val job = underTest.ssid.onEach { latest = it }.launchIn(this)
+            runCurrent()
 
             assertThat(latest).isEqualTo("friendly")
 
@@ -114,7 +126,7 @@
 
     @Test
     fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             wifiRepository.setWifiNetwork(
                 WifiNetworkModel.Active(
                     networkId = 1,
@@ -126,6 +138,7 @@
 
             var latest: String? = null
             val job = underTest.ssid.onEach { latest = it }.launchIn(this)
+            runCurrent()
 
             assertThat(latest).isEqualTo("friendly")
 
@@ -134,7 +147,7 @@
 
     @Test
     fun ssid_unknownSsid_outputsNull() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             wifiRepository.setWifiNetwork(
                 WifiNetworkModel.Active(
                     networkId = 1,
@@ -145,6 +158,7 @@
 
             var latest: String? = "default"
             val job = underTest.ssid.onEach { latest = it }.launchIn(this)
+            runCurrent()
 
             assertThat(latest).isNull()
 
@@ -153,7 +167,7 @@
 
     @Test
     fun ssid_validSsid_outputsSsid() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             wifiRepository.setWifiNetwork(
                 WifiNetworkModel.Active(
                     networkId = 1,
@@ -164,6 +178,7 @@
 
             var latest: String? = null
             val job = underTest.ssid.onEach { latest = it }.launchIn(this)
+            runCurrent()
 
             assertThat(latest).isEqualTo("MyAwesomeWifiNetwork")
 
@@ -172,7 +187,7 @@
 
     @Test
     fun isEnabled_matchesRepoIsEnabled() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isEnabled.onEach { latest = it }.launchIn(this)
 
@@ -193,7 +208,7 @@
 
     @Test
     fun isDefault_matchesRepoIsDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isDefault.onEach { latest = it }.launchIn(this)
 
@@ -214,7 +229,7 @@
 
     @Test
     fun wifiNetwork_matchesRepoWifiNetwork() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val wifiNetwork =
                 WifiNetworkModel.Active(
                     networkId = 45,
@@ -227,6 +242,7 @@
 
             var latest: WifiNetworkModel? = null
             val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+            runCurrent()
 
             assertThat(latest).isEqualTo(wifiNetwork)
 
@@ -235,7 +251,7 @@
 
     @Test
     fun activity_matchesRepoWifiActivity() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: DataActivityModel? = null
             val job = underTest.activity.onEach { latest = it }.launchIn(this)
 
@@ -259,11 +275,12 @@
 
     @Test
     fun isForceHidden_repoHasWifiHidden_outputsTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
 
             var latest: Boolean? = null
             val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+            runCurrent()
 
             assertThat(latest).isTrue()
 
@@ -272,16 +289,15 @@
 
     @Test
     fun isForceHidden_repoDoesNotHaveWifiHidden_outputsFalse() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             connectivityRepository.setForceHiddenIcons(setOf())
 
             var latest: Boolean? = null
             val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+            runCurrent()
 
             assertThat(latest).isFalse()
 
             job.cancel()
         }
 }
-
-private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
index 4e0c309..ba035be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
@@ -136,7 +136,8 @@
                 networkId = 5,
                 isValidated = true,
                 level = 3,
-                ssid = "Test SSID"
+                ssid = "Test SSID",
+                hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.LAPTOP,
             )
 
         activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger)
@@ -146,6 +147,7 @@
         assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
         assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
         assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+        assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "LAPTOP"))
     }
     @Test
     fun logDiffs_activeToInactive_resetsAllActiveFields() {
@@ -165,6 +167,7 @@
         assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
         assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
         assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
+        assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "null"))
     }
 
     @Test
@@ -175,7 +178,8 @@
                 networkId = 5,
                 isValidated = true,
                 level = 3,
-                ssid = "Test SSID"
+                ssid = "Test SSID",
+                hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.AUTO,
             )
         val prevVal =
             WifiNetworkModel.CarrierMerged(
@@ -191,6 +195,7 @@
         assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
         assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
         assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+        assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "AUTO"))
     }
     @Test
     fun logDiffs_activeToCarrierMerged_logsAllFields() {
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 0d51af2..3f49935 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
@@ -31,7 +31,6 @@
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 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
@@ -46,7 +45,6 @@
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel.Companion.viewModelForLocation
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -64,7 +62,6 @@
 
     private lateinit var testableLooper: TestableLooper
 
-    @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
@@ -110,7 +107,6 @@
         viewModel =
             viewModelForLocation(
                 viewModelCommon,
-                statusBarPipelineFlags,
                 StatusBarLocation.HOME,
             )
     }
@@ -199,7 +195,6 @@
 
     @Test
     fun onDarkChanged_iconHasNewColor() {
-        whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
         val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
         ViewUtils.attachView(view)
         testableLooper.processAllMessages()
@@ -215,7 +210,6 @@
 
     @Test
     fun setStaticDrawableColor_iconHasNewColor() {
-        whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
         val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
         ViewUtils.attachView(view)
         testableLooper.processAllMessages()
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 0e303b2..bdeba2a 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
@@ -16,11 +16,13 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
 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
@@ -37,28 +39,23 @@
 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel.Companion.viewModelForLocation
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.yield
-import org.junit.After
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class WifiViewModelTest : SysuiTestCase() {
 
     private lateinit var underTest: WifiViewModel
 
-    @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
@@ -68,7 +65,7 @@
     private lateinit var interactor: WifiInteractor
     private lateinit var airplaneModeViewModel: AirplaneModeViewModel
     private val shouldShowSignalSpacerProviderFlow = MutableStateFlow(false)
-    private lateinit var scope: CoroutineScope
+    private val testScope = TestScope()
 
     @Before
     fun setUp() {
@@ -78,7 +75,6 @@
         wifiRepository = FakeWifiRepository()
         wifiRepository.setIsWifiEnabled(true)
         interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
-        scope = CoroutineScope(IMMEDIATE)
         airplaneModeViewModel =
             AirplaneModeViewModelImpl(
                 AirplaneModeInteractor(
@@ -86,17 +82,12 @@
                     connectivityRepository,
                 ),
                 tableLogBuffer,
-                scope,
+                testScope.backgroundScope,
             )
 
         createAndSetViewModel()
     }
 
-    @After
-    fun tearDown() {
-        scope.cancel()
-    }
-
     // See [WifiViewModelIconParameterizedTest] for additional view model tests.
 
     // Note on testing: [WifiViewModel] exposes 3 different instances of
@@ -106,106 +97,65 @@
 
     @Test
     fun wifiIcon_allLocationViewModelsReceiveSameData() =
-        runBlocking(IMMEDIATE) {
-            val home =
-                viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.HOME)
-            val keyguard =
-                viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.KEYGUARD)
-            val qs = viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.QS)
+        testScope.runTest {
+            val home = viewModelForLocation(underTest, StatusBarLocation.HOME)
+            val keyguard = viewModelForLocation(underTest, StatusBarLocation.KEYGUARD)
+            val qs = viewModelForLocation(underTest, StatusBarLocation.QS)
 
-            var latestHome: WifiIcon? = null
-            val jobHome = home.wifiIcon.onEach { latestHome = it }.launchIn(this)
-
-            var latestKeyguard: WifiIcon? = null
-            val jobKeyguard = keyguard.wifiIcon.onEach { latestKeyguard = it }.launchIn(this)
-
-            var latestQs: WifiIcon? = null
-            val jobQs = qs.wifiIcon.onEach { latestQs = it }.launchIn(this)
+            val latestHome by collectLastValue(home.wifiIcon)
+            val latestKeyguard by collectLastValue(keyguard.wifiIcon)
+            val latestQs by collectLastValue(qs.wifiIcon)
 
             wifiRepository.setWifiNetwork(
                 WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1)
             )
-            yield()
 
             assertThat(latestHome).isInstanceOf(WifiIcon.Visible::class.java)
             assertThat(latestHome).isEqualTo(latestKeyguard)
             assertThat(latestKeyguard).isEqualTo(latestQs)
-
-            jobHome.cancel()
-            jobKeyguard.cancel()
-            jobQs.cancel()
         }
 
     @Test
     fun activity_showActivityConfigFalse_outputsFalse() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
             createAndSetViewModel()
             wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
-            var activityIn: Boolean? = null
-            val activityInJob =
-                underTest.isActivityInViewVisible.onEach { activityIn = it }.launchIn(this)
-
-            var activityOut: Boolean? = null
-            val activityOutJob =
-                underTest.isActivityOutViewVisible.onEach { activityOut = it }.launchIn(this)
-
-            var activityContainer: Boolean? = null
-            val activityContainerJob =
-                underTest.isActivityContainerVisible
-                    .onEach { activityContainer = it }
-                    .launchIn(this)
+            val activityIn by collectLastValue(underTest.isActivityInViewVisible)
+            val activityOut by collectLastValue(underTest.isActivityOutViewVisible)
+            val activityContainer by collectLastValue(underTest.isActivityContainerVisible)
 
             // Verify that on launch, we receive false.
             assertThat(activityIn).isFalse()
             assertThat(activityOut).isFalse()
             assertThat(activityContainer).isFalse()
-
-            activityInJob.cancel()
-            activityOutJob.cancel()
-            activityContainerJob.cancel()
         }
 
     @Test
     fun activity_showActivityConfigFalse_noUpdatesReceived() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
             createAndSetViewModel()
             wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
-            var activityIn: Boolean? = null
-            val activityInJob =
-                underTest.isActivityInViewVisible.onEach { activityIn = it }.launchIn(this)
-
-            var activityOut: Boolean? = null
-            val activityOutJob =
-                underTest.isActivityOutViewVisible.onEach { activityOut = it }.launchIn(this)
-
-            var activityContainer: Boolean? = null
-            val activityContainerJob =
-                underTest.isActivityContainerVisible
-                    .onEach { activityContainer = it }
-                    .launchIn(this)
+            val activityIn by collectLastValue(underTest.isActivityInViewVisible)
+            val activityOut by collectLastValue(underTest.isActivityOutViewVisible)
+            val activityContainer by collectLastValue(underTest.isActivityContainerVisible)
 
             // WHEN we update the repo to have activity
             val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
             wifiRepository.setWifiActivity(activity)
-            yield()
 
             // THEN we didn't update to the new activity (because our config is false)
             assertThat(activityIn).isFalse()
             assertThat(activityOut).isFalse()
             assertThat(activityContainer).isFalse()
-
-            activityInJob.cancel()
-            activityOutJob.cancel()
-            activityContainerJob.cancel()
         }
 
     @Test
     fun activity_nullSsid_outputsFalse() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
             createAndSetViewModel()
 
@@ -213,292 +163,212 @@
                 WifiNetworkModel.Active(NETWORK_ID, ssid = null, level = 1)
             )
 
-            var activityIn: Boolean? = null
-            val activityInJob =
-                underTest.isActivityInViewVisible.onEach { activityIn = it }.launchIn(this)
-
-            var activityOut: Boolean? = null
-            val activityOutJob =
-                underTest.isActivityOutViewVisible.onEach { activityOut = it }.launchIn(this)
-
-            var activityContainer: Boolean? = null
-            val activityContainerJob =
-                underTest.isActivityContainerVisible
-                    .onEach { activityContainer = it }
-                    .launchIn(this)
+            val activityIn by collectLastValue(underTest.isActivityInViewVisible)
+            val activityOut by collectLastValue(underTest.isActivityOutViewVisible)
+            val activityContainer by collectLastValue(underTest.isActivityContainerVisible)
 
             // WHEN we update the repo to have activity
             val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
             wifiRepository.setWifiActivity(activity)
-            yield()
 
             // THEN we still output false because our network's SSID is null
             assertThat(activityIn).isFalse()
             assertThat(activityOut).isFalse()
             assertThat(activityContainer).isFalse()
-
-            activityInJob.cancel()
-            activityOutJob.cancel()
-            activityContainerJob.cancel()
         }
 
     @Test
     fun activity_allLocationViewModelsReceiveSameData() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
             createAndSetViewModel()
             wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
-            val home =
-                viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.HOME)
-            val keyguard =
-                viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.KEYGUARD)
-            val qs = viewModelForLocation(underTest, statusBarPipelineFlags, StatusBarLocation.QS)
+            val home = viewModelForLocation(underTest, StatusBarLocation.HOME)
+            val keyguard = viewModelForLocation(underTest, StatusBarLocation.KEYGUARD)
+            val qs = viewModelForLocation(underTest, StatusBarLocation.QS)
 
-            var latestHome: Boolean? = null
-            val jobHome = home.isActivityInViewVisible.onEach { latestHome = it }.launchIn(this)
-
-            var latestKeyguard: Boolean? = null
-            val jobKeyguard =
-                keyguard.isActivityInViewVisible.onEach { latestKeyguard = it }.launchIn(this)
-
-            var latestQs: Boolean? = null
-            val jobQs = qs.isActivityInViewVisible.onEach { latestQs = it }.launchIn(this)
+            val latestHome by collectLastValue(home.isActivityInViewVisible)
+            val latestKeyguard by collectLastValue(keyguard.isActivityInViewVisible)
+            val latestQs by collectLastValue(qs.isActivityInViewVisible)
 
             val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
             wifiRepository.setWifiActivity(activity)
-            yield()
 
             assertThat(latestHome).isTrue()
             assertThat(latestKeyguard).isTrue()
             assertThat(latestQs).isTrue()
-
-            jobHome.cancel()
-            jobKeyguard.cancel()
-            jobQs.cancel()
         }
 
     @Test
     fun activityIn_hasActivityInTrue_outputsTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
             createAndSetViewModel()
             wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
-            var latest: Boolean? = null
-            val job = underTest.isActivityInViewVisible.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isActivityInViewVisible)
 
             val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
             wifiRepository.setWifiActivity(activity)
-            yield()
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun activityIn_hasActivityInFalse_outputsFalse() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
             createAndSetViewModel()
             wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
-            var latest: Boolean? = null
-            val job = underTest.isActivityInViewVisible.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isActivityInViewVisible)
 
             val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
             wifiRepository.setWifiActivity(activity)
-            yield()
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun activityOut_hasActivityOutTrue_outputsTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
             createAndSetViewModel()
             wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
-            var latest: Boolean? = null
-            val job = underTest.isActivityOutViewVisible.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isActivityOutViewVisible)
 
             val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
             wifiRepository.setWifiActivity(activity)
-            yield()
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun activityOut_hasActivityOutFalse_outputsFalse() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
             createAndSetViewModel()
             wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
-            var latest: Boolean? = null
-            val job = underTest.isActivityOutViewVisible.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isActivityOutViewVisible)
 
             val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
             wifiRepository.setWifiActivity(activity)
-            yield()
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun activityContainer_hasActivityInTrue_outputsTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
             createAndSetViewModel()
             wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
-            var latest: Boolean? = null
-            val job = underTest.isActivityContainerVisible.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isActivityContainerVisible)
 
             val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
             wifiRepository.setWifiActivity(activity)
-            yield()
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun activityContainer_hasActivityOutTrue_outputsTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
             createAndSetViewModel()
             wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
-            var latest: Boolean? = null
-            val job = underTest.isActivityContainerVisible.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isActivityContainerVisible)
 
             val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
             wifiRepository.setWifiActivity(activity)
-            yield()
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun activityContainer_inAndOutTrue_outputsTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
             createAndSetViewModel()
             wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
-            var latest: Boolean? = null
-            val job = underTest.isActivityContainerVisible.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isActivityContainerVisible)
 
             val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
             wifiRepository.setWifiActivity(activity)
-            yield()
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun activityContainer_inAndOutFalse_outputsFalse() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
             createAndSetViewModel()
             wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
-            var latest: Boolean? = null
-            val job = underTest.isActivityContainerVisible.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.isActivityContainerVisible)
 
             val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
             wifiRepository.setWifiActivity(activity)
-            yield()
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun airplaneSpacer_notAirplaneMode_outputsFalse() =
-        runBlocking(IMMEDIATE) {
-            var latest: Boolean? = null
-            val job = underTest.isAirplaneSpacerVisible.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isAirplaneSpacerVisible)
 
             airplaneModeRepository.setIsAirplaneMode(false)
-            yield()
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun airplaneSpacer_airplaneForceHidden_outputsFalse() =
-        runBlocking(IMMEDIATE) {
-            var latest: Boolean? = null
-            val job = underTest.isAirplaneSpacerVisible.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isAirplaneSpacerVisible)
 
             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.isAirplaneSpacerVisible.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isAirplaneSpacerVisible)
 
             airplaneModeRepository.setIsAirplaneMode(true)
-            yield()
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun signalSpacer_firstSubNotShowingNetworkTypeIcon_outputsFalse() =
-        runBlocking(IMMEDIATE) {
-            var latest: Boolean? = null
-            val job = underTest.isSignalSpacerVisible.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isSignalSpacerVisible)
 
             shouldShowSignalSpacerProviderFlow.value = false
-            yield()
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun signalSpacer_firstSubIsShowingNetworkTypeIcon_outputsTrue() =
-        runBlocking(IMMEDIATE) {
-            var latest: Boolean? = null
-            val job = underTest.isSignalSpacerVisible.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isSignalSpacerVisible)
 
             shouldShowSignalSpacerProviderFlow.value = true
-            yield()
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     private fun createAndSetViewModel() {
@@ -513,7 +383,7 @@
                 context,
                 tableLogBuffer,
                 interactor,
-                scope,
+                testScope.backgroundScope,
                 wifiConstants,
             )
     }
@@ -524,5 +394,3 @@
             WifiNetworkModel.Active(NETWORK_ID, ssid = "AB", level = 1)
     }
 }
-
-private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index c886f9b..cdeb592 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -29,6 +29,9 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Intent;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
 import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -56,6 +59,9 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -65,8 +71,10 @@
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private DemoModeController mDemoModeController;
     @Mock private View mView;
+    @Mock private UsbPort mUsbPort;
+    @Mock private UsbManager mUsbManager;
+    @Mock private UsbPortStatus mUsbPortStatus;
     private BatteryControllerImpl mBatteryController;
-
     private MockitoSession mMockitoSession;
 
     @Before
@@ -255,4 +263,38 @@
 
         Assert.assertFalse(mBatteryController.isBatteryDefender());
     }
+
+    @Test
+    public void complianceChanged_complianceIncompatible_outputsTrue() {
+        mContext.addMockSystemService(UsbManager.class, mUsbManager);
+        setupIncompatibleCharging();
+        Intent intent = new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
+
+        mBatteryController.onReceive(getContext(), intent);
+
+        Assert.assertTrue(mBatteryController.isIncompatibleCharging());
+    }
+
+    @Test
+    public void complianceChanged_emptyComplianceWarnings_outputsFalse() {
+        mContext.addMockSystemService(UsbManager.class, mUsbManager);
+        setupIncompatibleCharging();
+        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]);
+        Intent intent = new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
+
+        mBatteryController.onReceive(getContext(), intent);
+
+        Assert.assertFalse(mBatteryController.isIncompatibleCharging());
+    }
+
+    private void setupIncompatibleCharging() {
+        final List<UsbPort> usbPorts = new ArrayList<>();
+        usbPorts.add(mUsbPort);
+        when(mUsbManager.getPorts()).thenReturn(usbPorts);
+        when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
+        when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
+        when(mUsbPortStatus.isConnected()).thenReturn(true);
+        when(mUsbPortStatus.getComplianceWarnings())
+                .thenReturn(new int[]{UsbPortStatus.COMPLIANCE_WARNING_OTHER});
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index a797e03..e5bbead 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
+
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertEquals;
@@ -26,6 +30,7 @@
 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.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -37,7 +42,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
-import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -45,6 +49,7 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -60,17 +65,12 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class HeadsUpManagerTest extends AlertingNotificationManagerTest {
-    private static final int TEST_A11Y_AUTO_DISMISS_TIME = 600;
-    private static final int TEST_A11Y_TIMEOUT_TIME = 5_000;
+    private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
+    private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
+    private static final int TEST_A11Y_TIMEOUT_TIME = 3_000;
 
-    private HeadsUpManager mHeadsUpManager;
-    private boolean mLivesPastNormalTime;
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
-    @Mock private HeadsUpManager.HeadsUpEntry mAlertEntry;
-    @Mock private NotificationEntry mEntry;
-    @Mock private StatusBarNotification mSbn;
-    @Mock private Notification mNotification;
-    @Mock private HeadsUpManagerLogger mLogger;
+    private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
     @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
 
     private final class TestableHeadsUpManager extends HeadsUpManager {
@@ -80,220 +80,412 @@
                 AccessibilityManagerWrapper accessibilityManagerWrapper,
                 UiEventLogger uiEventLogger) {
             super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
+            mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME;
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mStickyDisplayTime = TEST_STICKY_DISPLAY_TIME;
             mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
+            mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
         }
     }
 
-    protected AlertingNotificationManager createAlertingNotificationManager() {
-        return mHeadsUpManager;
+    private HeadsUpManager createHeadsUpManager() {
+        return new TestableHeadsUpManager(mContext, mLogger, mTestHandler, mAccessibilityMgr,
+                mUiEventLoggerFake);
     }
 
+    @Override
+    protected AlertingNotificationManager createAlertingNotificationManager() {
+        return createHeadsUpManager();
+    }
+
+    private NotificationEntry createStickyEntry(int id) {
+        final Notification notif = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
+                .build();
+        return createEntry(id, notif);
+    }
+
+    private NotificationEntry createStickyForSomeTimeEntry(int id) {
+        final Notification notif = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setFlag(FLAG_FSI_REQUESTED_BUT_DENIED, true)
+                .build();
+        return createEntry(id, notif);
+    }
+
+    private PendingIntent createFullScreenIntent() {
+        return PendingIntent.getActivity(
+                getContext(), 0, new Intent(getContext(), this.getClass()),
+                PendingIntent.FLAG_MUTABLE_UNAUDITED);
+    }
+
+    private NotificationEntry createFullScreenIntentEntry(int id) {
+        final Notification notif = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setFullScreenIntent(createFullScreenIntent(), /* highPriority */ true)
+                .build();
+        return createEntry(id, notif);
+    }
+
+
+    private void useAccessibilityTimeout(boolean use) {
+        if (use) {
+            doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
+                    .getRecommendedTimeoutMillis(anyInt(), anyInt());
+        } else {
+            when(mAccessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt())).then(
+                    i -> i.getArgument(0));
+        }
+    }
+
+
     @Before
+    @Override
     public void setUp() {
         initMocks(this);
-        when(mEntry.getSbn()).thenReturn(mSbn);
-        when(mSbn.getNotification()).thenReturn(mNotification);
         super.setUp();
-        mHeadsUpManager = new TestableHeadsUpManager(mContext, mLogger, mTestHandler,
-                mAccessibilityMgr, mUiEventLoggerFake);
+
+        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
+
+        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_AUTO_DISMISS_TIME).isLessThan(
+                TEST_TIMEOUT_TIME);
+        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(
+                TEST_TIMEOUT_TIME);
+        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_A11Y_AUTO_DISMISS_TIME).isLessThan(
+                TEST_A11Y_TIMEOUT_TIME);
     }
 
     @After
+    @Override
     public void tearDown() {
-        mTestHandler.removeCallbacksAndMessages(null);
+        super.tearDown();
     }
 
     @Test
     public void testHunRemovedLogging() {
-        mAlertEntry.mEntry = mEntry;
-        mHeadsUpManager.onAlertEntryRemoved(mAlertEntry);
-        verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(mEntry));
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = mock(HeadsUpManager.HeadsUpEntry.class);
+        headsUpEntry.mEntry = notifEntry;
+
+        hum.onAlertEntryRemoved(headsUpEntry);
+
+        verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry));
     }
 
     @Test
     public void testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() {
-        // Set up NotifEntry with FSI
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
-        notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
-                getContext(), 0, new Intent(getContext(), this.getClass()),
-                PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
 
         // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
-        mHeadsUpManager.showNotification(notifEntry);
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+        hum.showNotification(notifEntry);
+
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
         headsUpEntry.wasUnpinned = false;
 
-        assertTrue(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+        assertTrue(hum.shouldHeadsUpBecomePinned(notifEntry));
     }
 
     @Test
     public void testShouldHeadsUpBecomePinned_wasUnpinned_false() {
-        // Set up NotifEntry with FSI
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
-        notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
-                getContext(), 0, new Intent(getContext(), this.getClass()),
-                PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
 
         // Add notifEntry to ANM mAlertEntries map and make it unpinned
-        mHeadsUpManager.showNotification(notifEntry);
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+        hum.showNotification(notifEntry);
+
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
         headsUpEntry.wasUnpinned = true;
 
-        assertFalse(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+        assertFalse(hum.shouldHeadsUpBecomePinned(notifEntry));
     }
 
     @Test
     public void testShouldHeadsUpBecomePinned_noFSI_false() {
-        // Set up NotifEntry with no FSI
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
 
-        assertFalse(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+        assertFalse(hum.shouldHeadsUpBecomePinned(entry));
     }
 
+
+    @Test
+    public void testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        final int pastJustAutoDismissMillis =
+                TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME;
+        verifyAlertingAtTime(hum, entry, true, pastJustAutoDismissMillis, "just auto dismiss");
+    }
+
+
+    @Test
+    public void testShowNotification_autoDismissesWithDefaultTimeout() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+        verifyAlertingAtTime(hum, entry, false, pastDefaultTimeoutMillis, "default timeout");
+    }
+
+
+    @Test
+    public void testShowNotification_stickyForSomeTime_autoDismissesWithStickyTimeout() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2;
+        verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout");
+    }
+
+
+    @Test
+    public void testShowNotification_sticky_neverAutoDismisses() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createStickyEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        final int pastLongestAutoDismissMillis =
+                TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME;
+        final Boolean[] wasAlerting = {null};
+        final Runnable checkAlerting =
+                () -> wasAlerting[0] = hum.isAlerting(entry.getKey());
+        mTestHandler.postDelayed(checkAlerting, pastLongestAutoDismissMillis);
+        TestableLooper.get(this).processMessages(1);
+
+        assertTrue("Should still be alerting past longest auto-dismiss", wasAlerting[0]);
+        assertTrue("Should still be alerting after processing",
+                hum.isAlerting(entry.getKey()));
+    }
+
+
     @Test
     public void testShowNotification_autoDismissesWithAccessibilityTimeout() {
-        doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
-                .getRecommendedTimeoutMillis(anyInt(), anyInt());
-        mHeadsUpManager.showNotification(mEntry);
-        Runnable pastNormalTimeRunnable =
-                () -> mLivesPastNormalTime = mHeadsUpManager.isAlerting(mEntry.getKey());
-        mTestHandler.postDelayed(pastNormalTimeRunnable,
-                        (TEST_A11Y_AUTO_DISMISS_TIME + TEST_AUTO_DISMISS_TIME) / 2);
-        mTestHandler.postDelayed(TEST_TIMEOUT_RUNNABLE, TEST_A11Y_TIMEOUT_TIME);
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+        useAccessibilityTimeout(true);
 
-        TestableLooper.get(this).processMessages(2);
+        hum.showNotification(entry);
 
-        assertFalse("Test timed out", mTimedOut);
-        assertTrue("Heads up should live long enough", mLivesPastNormalTime);
-        assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+        final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+        verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout");
+    }
+
+
+    @Test
+    public void testShowNotification_stickyForSomeTime_autoDismissesWithAccessibilityTimeout() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
+        useAccessibilityTimeout(true);
+
+        hum.showNotification(entry);
+
+        final int pastStickyTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+        verifyAlertingAtTime(hum, entry, true, pastStickyTimeoutMillis, "sticky timeout");
+    }
+
+
+    @Test
+    public void testRemoveNotification_beforeMinimumDisplayTime() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        // Try to remove but defer, since the notification has not been shown long enough.
+        final boolean removedImmediately = hum.removeNotification(
+                entry.getKey(), false /* releaseImmediately */);
+
+        assertFalse("HUN should not be removed before minimum display time", removedImmediately);
+        assertTrue("HUN should still be alerting before minimum display time",
+                hum.isAlerting(entry.getKey()));
+
+        final int pastMinimumDisplayTimeMillis =
+                (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2;
+        verifyAlertingAtTime(hum, entry, false, pastMinimumDisplayTimeMillis,
+                "minimum display time");
+    }
+
+
+    @Test
+    public void testRemoveNotification_afterMinimumDisplayTime() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        // After the minimum display time:
+        // 1. Check whether the notification is still alerting.
+        // 2. Try to remove it and check whether the remove succeeded.
+        // 3. Check whether it is still alerting after trying to remove it.
+        final Boolean[] livedPastMinimumDisplayTime = {null};
+        final Boolean[] removedAfterMinimumDisplayTime = {null};
+        final Boolean[] livedPastRemoveAfterMinimumDisplayTime = {null};
+        final Runnable pastMinimumDisplayTimeRunnable = () -> {
+            livedPastMinimumDisplayTime[0] = hum.isAlerting(entry.getKey());
+            removedAfterMinimumDisplayTime[0] = hum.removeNotification(
+                    entry.getKey(), /* releaseImmediately = */ false);
+            livedPastRemoveAfterMinimumDisplayTime[0] = hum.isAlerting(entry.getKey());
+        };
+        final int pastMinimumDisplayTimeMillis =
+                (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2;
+        mTestHandler.postDelayed(pastMinimumDisplayTimeRunnable, pastMinimumDisplayTimeMillis);
+        // Wait until the minimum display time has passed before attempting removal.
+        TestableLooper.get(this).processMessages(1);
+
+        assertTrue("HUN should live past minimum display time",
+                livedPastMinimumDisplayTime[0]);
+        assertTrue("HUN should be removed immediately past minimum display time",
+                removedAfterMinimumDisplayTime[0]);
+        assertFalse("HUN should not live after being removed past minimum display time",
+                livedPastRemoveAfterMinimumDisplayTime[0]);
+        assertFalse(hum.isAlerting(entry.getKey()));
+    }
+
+
+    @Test
+    public void testRemoveNotification_releaseImmediately() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        hum.showNotification(entry);
+
+        // Remove forcibly with releaseImmediately = true.
+        final boolean removedImmediately = hum.removeNotification(
+                entry.getKey(), /* releaseImmediately = */ true);
+
+        assertTrue(removedImmediately);
+        assertFalse(hum.isAlerting(entry.getKey()));
     }
 
 
     @Test
     public void testIsSticky_rowPinnedAndExpanded_true() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
-
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
         when(mRow.isPinned()).thenReturn(true);
         notifEntry.setRow(mRow);
 
-        mHeadsUpManager.showNotification(notifEntry);
+        hum.showNotification(notifEntry);
 
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
         headsUpEntry.setExpanded(true);
 
-        assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+        assertTrue(hum.isSticky(notifEntry.getKey()));
     }
 
     @Test
     public void testIsSticky_remoteInputActive_true() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
 
-        mHeadsUpManager.showNotification(notifEntry);
+        hum.showNotification(notifEntry);
 
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
-
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
         headsUpEntry.remoteInputActive = true;
 
-        assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+        assertTrue(hum.isSticky(notifEntry.getKey()));
     }
 
     @Test
     public void testIsSticky_hasFullScreenIntent_true() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
 
-        mHeadsUpManager.showNotification(notifEntry);
+        hum.showNotification(notifEntry);
 
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
-
-        notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
-                getContext(), 0, new Intent(getContext(), this.getClass()),
-                PendingIntent.FLAG_MUTABLE_UNAUDITED);
-
-        assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+        assertTrue(hum.isSticky(notifEntry.getKey()));
     }
 
+
     @Test
-    public void testIsSticky_stickyAndNotDemoted_true() {
-        NotificationEntry alertEntry = new NotificationEntryBuilder()
-                .setSbn(createStickySbn(0))
-                .build();
+    public void testIsSticky_stickyForSomeTime_false() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
 
-        mHeadsUpManager.showNotification(alertEntry);
+        hum.showNotification(entry);
 
-        assertTrue(mHeadsUpManager.isSticky(alertEntry.getKey()));
+        assertFalse(hum.isSticky(entry.getKey()));
     }
 
+
     @Test
     public void testIsSticky_false() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
 
-        mHeadsUpManager.showNotification(notifEntry);
+        hum.showNotification(notifEntry);
 
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
         headsUpEntry.setExpanded(false);
         headsUpEntry.remoteInputActive = false;
 
-        assertFalse(mHeadsUpManager.isSticky(notifEntry.getKey()));
+        assertFalse(hum.isSticky(notifEntry.getKey()));
     }
 
     @Test
     public void testCompareTo_withNullEntries() {
-        NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
-        mHeadsUpManager.showNotification(alertEntry);
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
 
-        assertThat(mHeadsUpManager.compare(alertEntry, null)).isLessThan(0);
-        assertThat(mHeadsUpManager.compare(null, alertEntry)).isGreaterThan(0);
-        assertThat(mHeadsUpManager.compare(null, null)).isEqualTo(0);
+        hum.showNotification(alertEntry);
+
+        assertThat(hum.compare(alertEntry, null)).isLessThan(0);
+        assertThat(hum.compare(null, alertEntry)).isGreaterThan(0);
+        assertThat(hum.compare(null, null)).isEqualTo(0);
     }
 
     @Test
     public void testCompareTo_withNonAlertEntries() {
-        NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag("nae1").build();
-        NotificationEntry nonAlertEntry2 = new NotificationEntryBuilder().setTag("nae2").build();
-        NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
-        mHeadsUpManager.showNotification(alertEntry);
+        final HeadsUpManager hum = createHeadsUpManager();
 
-        assertThat(mHeadsUpManager.compare(alertEntry, nonAlertEntry1)).isLessThan(0);
-        assertThat(mHeadsUpManager.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0);
-        assertThat(mHeadsUpManager.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0);
+        final NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag(
+                "nae1").build();
+        final NotificationEntry nonAlertEntry2 = new NotificationEntryBuilder().setTag(
+                "nae2").build();
+        final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
+        hum.showNotification(alertEntry);
+
+        assertThat(hum.compare(alertEntry, nonAlertEntry1)).isLessThan(0);
+        assertThat(hum.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0);
+        assertThat(hum.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0);
     }
 
     @Test
     public void testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
-        HeadsUpManager.HeadsUpEntry ongoingCall = mHeadsUpManager.new HeadsUpEntry();
+        final HeadsUpManager hum = createHeadsUpManager();
+
+        final HeadsUpManager.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry();
         ongoingCall.setEntry(new NotificationEntryBuilder()
-                .setSbn(createNewSbn(0,
+                .setSbn(createSbn(/* id = */ 0,
                         new Notification.Builder(mContext, "")
                                 .setCategory(Notification.CATEGORY_CALL)
                                 .setOngoing(true)))
                 .build());
 
-        HeadsUpManager.HeadsUpEntry activeRemoteInput = mHeadsUpManager.new HeadsUpEntry();
-        activeRemoteInput.setEntry(new NotificationEntryBuilder()
-                .setSbn(createNewNotification(1))
-                .build());
+        final HeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
+        activeRemoteInput.setEntry(createEntry(/* id = */ 1));
         activeRemoteInput.remoteInputActive = true;
 
         assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -302,20 +494,20 @@
 
     @Test
     public void testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() {
-        HeadsUpManager.HeadsUpEntry incomingCall = mHeadsUpManager.new HeadsUpEntry();
-        Person person = new Person.Builder().setName("person").build();
-        PendingIntent intent = mock(PendingIntent.class);
+        final HeadsUpManager hum = createHeadsUpManager();
+
+        final HeadsUpManager.HeadsUpEntry incomingCall = hum.new HeadsUpEntry();
+        final Person person = new Person.Builder().setName("person").build();
+        final PendingIntent intent = mock(PendingIntent.class);
         incomingCall.setEntry(new NotificationEntryBuilder()
-                .setSbn(createNewSbn(0,
+                .setSbn(createSbn(/* id = */ 0,
                         new Notification.Builder(mContext, "")
                                 .setStyle(Notification.CallStyle
                                         .forIncomingCall(person, intent, intent))))
                 .build());
 
-        HeadsUpManager.HeadsUpEntry activeRemoteInput = mHeadsUpManager.new HeadsUpEntry();
-        activeRemoteInput.setEntry(new NotificationEntryBuilder()
-                .setSbn(createNewNotification(1))
-                .build());
+        final HeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
+        activeRemoteInput.setEntry(createEntry(/* id = */ 1));
         activeRemoteInput.remoteInputActive = true;
 
         assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -324,22 +516,18 @@
 
     @Test
     public void testPinEntry_logsPeek() {
-        // Needs full screen intent in order to be pinned
-        final PendingIntent fullScreenIntent = PendingIntent.getActivity(mContext, 0,
-                new Intent().setPackage(mContext.getPackageName()), PendingIntent.FLAG_MUTABLE);
+        final HeadsUpManager hum = createHeadsUpManager();
 
-        HeadsUpManager.HeadsUpEntry entryToPin = mHeadsUpManager.new HeadsUpEntry();
-        entryToPin.setEntry(new NotificationEntryBuilder()
-                .setSbn(createNewSbn(0,
-                        new Notification.Builder(mContext, "")
-                                .setFullScreenIntent(fullScreenIntent, true)))
-                .build());
+        // Needs full screen intent in order to be pinned
+        final HeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry();
+        entryToPin.setEntry(createFullScreenIntentEntry(/* id = */ 0));
+
         // Note: the standard way to show a notification would be calling showNotification rather
         // than onAlertEntryAdded. However, in practice showNotification in effect adds
         // the notification and then updates it; in order to not log twice, the entry needs
         // to have a functional ExpandableNotificationRow that can keep track of whether it's
         // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
-        mHeadsUpManager.onAlertEntryAdded(entryToPin);
+        hum.onAlertEntryAdded(entryToPin);
 
         assertEquals(1, mUiEventLoggerFake.numLogs());
         assertEquals(HeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
@@ -348,14 +536,15 @@
 
     @Test
     public void testSetUserActionMayIndirectlyRemove() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
 
-        mHeadsUpManager.showNotification(notifEntry);
-        assertFalse(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+        hum.showNotification(notifEntry);
 
-        mHeadsUpManager.setUserActionMayIndirectlyRemove(notifEntry);
-        assertTrue(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+        assertFalse(hum.canRemoveImmediately(notifEntry.getKey()));
+
+        hum.setUserActionMayIndirectlyRemove(notifEntry);
+
+        assertTrue(hum.canRemoveImmediately(notifEntry.getKey()));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index d787ada..cae892f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.hardware.biometrics.BiometricSourceType;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -35,6 +36,7 @@
 import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 
 import dagger.Lazy;
@@ -43,6 +45,7 @@
 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;
 
@@ -65,6 +68,11 @@
     private Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
     @Mock
     private KeyguardUpdateMonitorLogger mLogger;
+    @Mock
+    private FeatureFlags mFeatureFlags;
+
+    @Captor
+    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mUpdateCallbackCaptor;
 
     @Before
     public void setup() {
@@ -75,7 +83,8 @@
                 mLockPatternUtils,
                 mKeyguardUnlockAnimationControllerLazy,
                 mLogger,
-                mDumpManager);
+                mDumpManager,
+                mFeatureFlags);
     }
 
     @Test
@@ -84,6 +93,23 @@
     }
 
     @Test
+    public void testFaceAuthEnabledChanged_calledWhenFaceEnrollmentStateChanges() {
+        KeyguardStateController.Callback callback = mock(KeyguardStateController.Callback.class);
+
+        when(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(anyInt())).thenReturn(false);
+        verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
+        mKeyguardStateController.addCallback(callback);
+        assertThat(mKeyguardStateController.isFaceAuthEnabled()).isFalse();
+
+        when(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(anyInt())).thenReturn(true);
+        mUpdateCallbackCaptor.getValue().onBiometricEnrollmentStateChanged(
+                BiometricSourceType.FACE);
+
+        assertThat(mKeyguardStateController.isFaceAuthEnabled()).isTrue();
+        verify(callback).onFaceAuthEnabledChanged();
+    }
+
+    @Test
     public void testIsShowing() {
         assertThat(mKeyguardStateController.isShowing()).isFalse();
         mKeyguardStateController.notifyKeyguardState(true /* showing */, false /* occluded */);
@@ -177,16 +203,14 @@
     @Test
     public void testOnEnabledTrustAgentsChangedCallback() {
         final Random random = new Random();
-        final ArgumentCaptor<KeyguardUpdateMonitorCallback> updateCallbackCaptor =
-                ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
 
-        verify(mKeyguardUpdateMonitor).registerCallback(updateCallbackCaptor.capture());
+        verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture());
         final KeyguardStateController.Callback stateCallback =
                 mock(KeyguardStateController.Callback.class);
         mKeyguardStateController.addCallback(stateCallback);
 
         when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
-        updateCallbackCaptor.getValue().onEnabledTrustAgentsChanged(random.nextInt());
+        mUpdateCallbackCaptor.getValue().onEnabledTrustAgentsChanged(random.nextInt());
         verify(stateCallback).onUnlockedChanged();
     }
 }
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 391c8ca..79feb41 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
@@ -62,7 +62,6 @@
 import android.window.WindowOnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
-import androidx.core.animation.AnimatorTestRule;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
@@ -70,6 +69,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.AnimatorTestRule;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -82,7 +82,7 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.ClassRule;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -102,6 +102,8 @@
             "com.android.sysuitest.dummynotificationsender";
     private static final int DUMMY_MESSAGE_APP_ID = Process.LAST_APPLICATION_UID - 1;
 
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+
     @Mock private RemoteInputController mController;
     @Mock private ShortcutManager mShortcutManager;
     @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
@@ -109,8 +111,8 @@
     private BlockingQueueIntentReceiver mReceiver;
     private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
 
-    @ClassRule
-    public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+    @Rule
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
 
     @Before
     public void setUp() throws Exception {
@@ -453,8 +455,7 @@
     private RemoteInputViewController bindController(
             RemoteInputView view,
             NotificationEntry entry) {
-        FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
-        fakeFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true);
+        mFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true);
         RemoteInputViewControllerImpl viewController = new RemoteInputViewControllerImpl(
                 view,
                 entry,
@@ -462,7 +463,7 @@
                 mController,
                 mShortcutManager,
                 mUiEventLoggerFake,
-                fakeFeatureFlags
+                mFeatureFlags
                 );
         viewController.bind();
         return viewController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 3b0d512..ae38958e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -51,9 +51,11 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -107,14 +109,15 @@
 
     private SmartReplyInflaterImpl mSmartReplyInflater;
     private SmartActionInflaterImpl mSmartActionInflater;
+    private KeyguardDismissUtil mKeyguardDismissUtil;
 
     @Mock private SmartReplyConstants mConstants;
     @Mock private ActivityStarter mActivityStarter;
     @Mock private HeadsUpManager mHeadsUpManager;
     @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;
     @Mock private SmartReplyController mSmartReplyController;
-
-    private final KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil();
+    @Mock private  KeyguardStateController mKeyguardStateController;
+    @Mock private  SysuiStatusBarStateController mStatusBarStateController;
 
     @Before
     public void setUp() {
@@ -122,12 +125,15 @@
         mReceiver = new BlockingQueueIntentReceiver();
         mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION),
                 Context.RECEIVER_EXPORTED_UNAUDITED);
-        mKeyguardDismissUtil.setDismissHandler((action, unused, afterKgGone) -> action.onDismiss());
+
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectMockDependency(ShadeController.class);
         mDependency.injectMockDependency(NotificationRemoteInputManager.class);
         mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
         mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
+        mDependency.injectTestDependency(KeyguardStateController.class, mKeyguardStateController);
+        mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
+
 
         // Any number of replies are fine.
         when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0);
@@ -153,6 +159,13 @@
 
         mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
 
+        mKeyguardDismissUtil = new KeyguardDismissUtil(
+                mKeyguardStateController, mStatusBarStateController, mActivityStarter) {
+            public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
+                    boolean requiresShadeOpen, boolean afterKeyguardGone) {
+                action.onDismiss();
+            }
+        };
         mSmartReplyInflater = new SmartReplyInflaterImpl(
                 mConstants,
                 mKeyguardDismissUtil,
@@ -185,7 +198,17 @@
 
     @Test
     public void testSendSmartReply_keyguardCancelled() throws InterruptedException {
-        mKeyguardDismissUtil.setDismissHandler((action, unused, afterKgGone) -> { });
+        mKeyguardDismissUtil = new KeyguardDismissUtil(
+                mKeyguardStateController, mStatusBarStateController, mActivityStarter) {
+            public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
+                    boolean requiresShadeOpen, boolean afterKeyguardGone) { }};
+        mSmartReplyInflater = new SmartReplyInflaterImpl(
+                mConstants,
+                mKeyguardDismissUtil,
+                mNotificationRemoteInputManager,
+                mSmartReplyController,
+                mContext);
+
         setSmartReplies(TEST_CHOICES);
 
         mView.getChildAt(2).performClick();
@@ -196,9 +219,20 @@
     @Test
     public void testSendSmartReply_waitsForKeyguard() throws InterruptedException {
         AtomicReference<OnDismissAction> actionRef = new AtomicReference<>();
+        mKeyguardDismissUtil = new KeyguardDismissUtil(
+                mKeyguardStateController, mStatusBarStateController, mActivityStarter) {
+            public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
+                    boolean requiresShadeOpen, boolean afterKeyguardGone) {
+                actionRef.set(action);
+            }
+        };
+        mSmartReplyInflater = new SmartReplyInflaterImpl(
+                mConstants,
+                mKeyguardDismissUtil,
+                mNotificationRemoteInputManager,
+                mSmartReplyController,
+                mContext);
 
-        mKeyguardDismissUtil.setDismissHandler((action, unused, afterKgGone)
-                -> actionRef.set(action));
         setSmartReplies(TEST_CHOICES);
 
         mView.getChildAt(2).performClick();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index c06dbdc..7f3d4b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -14,6 +14,7 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
@@ -24,10 +25,10 @@
 
 import android.app.NotificationManager;
 import android.os.Handler;
-import android.os.Looper;
 import android.provider.Settings;
 import android.service.notification.ZenModeConfig;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.filters.SmallTest;
@@ -45,6 +46,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -61,9 +65,10 @@
     DumpManager mDumpManager;
     @Mock
     UserTracker mUserTracker;
-
     private ZenModeControllerImpl mController;
 
+    private final FakeSettings mGlobalSettings = new FakeSettings();
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -72,10 +77,10 @@
 
         mController = new ZenModeControllerImpl(
                 mContext,
-                Handler.createAsync(Looper.myLooper()),
+                Handler.createAsync(TestableLooper.get(this).getLooper()),
                 mBroadcastDispatcher,
                 mDumpManager,
-                new FakeSettings(),
+                mGlobalSettings,
                 mUserTracker);
     }
 
@@ -131,4 +136,48 @@
         mController.addCallback(null);
         mController.fireConfigChanged(null);
     }
+
+    @Test
+    public void testModeChange() {
+        List<Integer> states = List.of(
+                Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                Settings.Global.ZEN_MODE_NO_INTERRUPTIONS,
+                Settings.Global.ZEN_MODE_ALARMS,
+                Settings.Global.ZEN_MODE_ALARMS
+        );
+
+        for (Integer state : states) {
+            mGlobalSettings.putInt(Settings.Global.ZEN_MODE, state);
+            TestableLooper.get(this).processAllMessages();
+            assertEquals(state.intValue(), mController.getZen());
+        }
+    }
+
+    @Test
+    public void testModeChange_callbackNotified() {
+        final AtomicInteger currentState = new AtomicInteger(-1);
+
+        ZenModeController.Callback callback = new Callback() {
+            @Override
+            public void onZenChanged(int zen) {
+                currentState.set(zen);
+            }
+        };
+
+        mController.addCallback(callback);
+
+        List<Integer> states = List.of(
+                Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                Settings.Global.ZEN_MODE_NO_INTERRUPTIONS,
+                Settings.Global.ZEN_MODE_ALARMS,
+                Settings.Global.ZEN_MODE_ALARMS
+        );
+
+        for (Integer state : states) {
+            mGlobalSettings.putInt(Settings.Global.ZEN_MODE, state);
+            TestableLooper.get(this).processAllMessages();
+            assertEquals(state.intValue(), currentState.get());
+        }
+
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
index 90821bd..d212c02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
@@ -126,6 +126,16 @@
     }
 
     @Test
+    fun updateBatteryState_capacitySame_inputDeviceChanges_updatesInputDeviceId() {
+        stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
+        stylusUsiPowerUi.updateBatteryState(1, FixedCapacityBatteryState(0.1f))
+
+        assertThat(stylusUsiPowerUi.inputDeviceId).isEqualTo(1)
+        verify(notificationManager, times(1))
+            .notify(eq(R.string.stylus_battery_low_percentage), any())
+    }
+
+    @Test
     fun updateBatteryState_existingNotification_capacityAboveThreshold_cancelsNotification() {
         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
         stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.8f))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt
new file mode 100644
index 0000000..f1fadf6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.shaders
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SolidColorShaderTest : SysuiTestCase() {
+
+    @Test
+    fun compilesShader() {
+        SolidColorShader(Color.RED)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt
new file mode 100644
index 0000000..64ea6a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.shaders
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SparkleShaderTest : SysuiTestCase() {
+
+    private lateinit var sparkleShader: SparkleShader
+
+    @Test
+    fun compilesSparkleShader() {
+        sparkleShader =
+            SparkleShader().apply {
+                setPixelateAmount(
+                    context.resources.displayMetrics.density *
+                        SparkleShader.DEFAULT_SPARKLE_PIXELATE_AMOUNT
+                )
+                setColor(Color.RED)
+                setTime(0.01f)
+                setLumaMatteColor(Color.WHITE)
+            }
+    }
+}
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
index 03834e0..8c21fac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -21,6 +21,7 @@
 import android.os.VibrationEffect
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
@@ -41,6 +42,8 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.shared.model.TintedIcon
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -91,6 +94,7 @@
     private lateinit var fakeExecutor: FakeExecutor
     private lateinit var uiEventLoggerFake: UiEventLoggerFake
     private lateinit var uiEventLogger: TemporaryViewUiEventLogger
+    private val featureFlags = FakeFeatureFlags()
 
     @Before
     fun setUp() {
@@ -127,8 +131,10 @@
                 fakeWakeLockBuilder,
                 fakeClock,
                 uiEventLogger,
+                featureFlags
             )
         underTest.start()
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
     }
 
     @Test
@@ -488,6 +494,23 @@
             )
     }
 
+    @Test
+    fun displayView_oneWayHapticsEnabled_usesPerformHapticFeedback() {
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+        val constant: Int = HapticFeedbackConstants.CONFIRM
+        underTest.displayView(
+            createChipbarInfo(
+                Icon.Resource(R.id.check_box, null),
+                Text.Loaded("text"),
+                endItem = null,
+                vibrationEffect = null,
+                vibrationConstant = constant
+            )
+        )
+
+        verify(vibratorHelper).performHapticFeedback(any(), eq(constant))
+    }
+
     /** Regression test for b/266119467. */
     @Test
     fun displayView_animationFailure_viewsStillBecomeVisible() {
@@ -706,12 +729,14 @@
         endItem: ChipbarEndItem?,
         vibrationEffect: VibrationEffect? = null,
         allowSwipeToDismiss: Boolean = false,
+        vibrationConstant: Int = HapticFeedbackConstants.NO_HAPTICS,
     ): ChipbarInfo {
         return ChipbarInfo(
             TintedIcon(startIcon, tint = null),
             text,
             endItem,
             vibrationEffect,
+            vibrationConstant,
             allowSwipeToDismiss,
             windowTitle = WINDOW_TITLE,
             wakeReason = WAKE_REASON,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 09ac0e3..c454b45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -959,8 +959,8 @@
         // All dynamic colors were added twice: light and dark them
         // All fixed colors were added once
         verify(dynamic, times(
-                DynamicColors.ALL_DYNAMIC_COLORS_MAPPED.size() * 2
-                        + DynamicColors.FIXED_COLORS_MAPPED.size())
+                DynamicColors.allDynamicColorsMapped(false).size() * 2
+                        + DynamicColors.getFixedColorsMapped(false).size())
         ).setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index 27957ed..7593e84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -18,6 +18,8 @@
 
 import static android.view.accessibility.AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
 
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -28,6 +30,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.Application;
@@ -36,6 +39,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Parcel;
@@ -61,7 +65,6 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
 
@@ -74,6 +77,8 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
 
+import java.util.Arrays;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -106,8 +111,7 @@
     @Mock private IAccessibilityManager mAccessibilityManager;
     @Mock private PluginManager mPluginManager;
     @Mock private DumpManager mDumpManager;
-    @Mock private ToastLogger mToastLogger;
-    @Mock private FeatureFlags mFeatureFlags;
+    private final ToastLogger mToastLogger = spy(new ToastLogger(logcatLogBuffer()));
     @Mock private PackageManager mPackageManager;
 
     @Mock private ITransientNotificationCallback mCallback;
@@ -400,6 +404,18 @@
         verify(mToastLogger, never()).logOnHideToast(PACKAGE_NAME_1, TOKEN_1.toString());
     }
 
+    @Test
+    public void testShowToast_invalidDisplayId_logsAndSkipsToast() {
+        int invalidDisplayId = getInvalidDisplayId();
+
+        mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+                mCallback, invalidDisplayId);
+
+        verify(mToastLogger).logOnSkipToastForInvalidDisplay(PACKAGE_NAME_1, TOKEN_1.toString(),
+                invalidDisplayId);
+        verifyZeroInteractions(mWindowManager);
+    }
+
     private View verifyWmAddViewAndAttachToParent() {
         ArgumentCaptor<View> viewCaptor = ArgumentCaptor.forClass(View.class);
         verify(mWindowManager).addView(viewCaptor.capture(), any());
@@ -416,4 +432,10 @@
             return null;
         };
     }
+
+    private int getInvalidDisplayId() {
+        return Arrays.stream(
+                mContext.getSystemService(DisplayManager.class).getDisplays())
+                .map(Display::getDisplayId).max(Integer::compare).get() + 1;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
index d9ee081..7f990a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -28,12 +28,10 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.shade.ShadeFoldAnimator
 import com.android.systemui.shade.ShadeViewController
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.unfold.util.FoldableDeviceStates
@@ -80,8 +78,6 @@
 
     @Mock lateinit var viewTreeObserver: ViewTreeObserver
 
-    @Mock private lateinit var commandQueue: CommandQueue
-
     @Mock lateinit var shadeFoldAnimator: ShadeFoldAnimator
 
     @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
@@ -105,21 +101,15 @@
         whenever(viewGroup.viewTreeObserver).thenReturn(viewTreeObserver)
         whenever(wakefulnessLifecycle.lastSleepReason)
             .thenReturn(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD)
-        whenever(centralSurfaces.shadeViewController).thenReturn(shadeViewController)
         whenever(shadeFoldAnimator.startFoldToAodAnimation(any(), any(), any())).then {
             val onActionStarted = it.arguments[0] as Runnable
             onActionStarted.run()
         }
 
-        keyguardRepository = FakeKeyguardRepository()
         val featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
-        val keyguardInteractor =
-            KeyguardInteractor(
-                repository = keyguardRepository,
-                commandQueue = commandQueue,
-                featureFlags = featureFlags,
-                bouncerRepository = FakeKeyguardBouncerRepository(),
-            )
+        val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+        val keyguardInteractor = withDeps.keyguardInteractor
+        keyguardRepository = withDeps.repository
 
         // Needs to be run on the main thread
         runBlocking(IMMEDIATE) {
@@ -133,7 +123,7 @@
                         latencyTracker,
                         { keyguardInteractor },
                     )
-                    .apply { initialize(centralSurfaces, lightRevealScrim) }
+                    .apply { initialize(centralSurfaces, shadeViewController, lightRevealScrim) }
 
             verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index bf54d42..aa49287 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -20,6 +20,7 @@
 import android.content.res.Configuration
 import android.content.res.Resources
 import android.os.Handler
+import android.os.Looper
 import android.testing.AndroidTestingRunner
 import androidx.core.util.Consumer
 import androidx.test.filters.SmallTest
@@ -38,6 +39,7 @@
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.fail
 import java.util.concurrent.Executor
 import org.junit.Before
 import org.junit.Test
@@ -55,16 +57,20 @@
 
     @Mock private lateinit var activityTypeProvider: ActivityManagerActivityTypeProvider
 
-    @Mock private lateinit var handler: Handler
-
     @Mock private lateinit var rotationChangeProvider: RotationChangeProvider
 
     @Mock private lateinit var unfoldKeyguardVisibilityProvider: UnfoldKeyguardVisibilityProvider
 
     @Mock private lateinit var resources: Resources
 
+    @Mock private lateinit var handler: Handler
+
+    @Mock private lateinit var mainLooper: Looper
+
     @Mock private lateinit var context: Context
 
+    @Mock private lateinit var thread: Thread
+
     @Captor private lateinit var rotationListener: ArgumentCaptor<RotationListener>
 
     private val foldProvider = TestFoldProvider()
@@ -89,6 +95,11 @@
                 override val halfFoldedTimeoutMillis: Int
                     get() = HALF_OPENED_TIMEOUT_MILLIS.toInt()
             }
+        whenever(mainLooper.isCurrentThread).thenReturn(true)
+        whenever(handler.looper).thenReturn(mainLooper)
+        whenever(mainLooper.isCurrentThread).thenReturn(true)
+        whenever(mainLooper.thread).thenReturn(thread)
+        whenever(thread.name).thenReturn("backgroundThread")
         whenever(context.resources).thenReturn(resources)
         whenever(context.mainExecutor).thenReturn(mContext.mainExecutor)
 
@@ -435,6 +446,26 @@
     }
 
     @Test
+    fun startOnlyOnce_whenStartTriggeredThrice_startOnlyOnce() {
+        foldStateProvider.start()
+        foldStateProvider.start()
+        foldStateProvider.start()
+
+        assertThat(foldProvider.getNumberOfCallbacks()).isEqualTo(1)
+    }
+
+    @Test(expected = AssertionError::class)
+    fun startMethod_whileNotOnMainThread_throwsException() {
+        whenever(mainLooper.isCurrentThread).thenReturn(true)
+        try {
+            foldStateProvider.start()
+            fail("Should have thrown AssertionError: should be called from the main thread.")
+        } catch (e: AssertionError) {
+            assertThat(e.message).contains("backgroundThread")
+        }
+    }
+
+    @Test
     fun startClosingEvent_whileNotOnKeyguard_triggersAfterThreshold() {
         setKeyguardVisibility(visible = false)
         setInitialHingeAngle(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
@@ -658,6 +689,10 @@
         fun notifyFolded(isFolded: Boolean) {
             callbacks.forEach { it.onFoldUpdated(isFolded) }
         }
+
+        fun getNumberOfCallbacks(): Int{
+            return callbacks.size
+        }
     }
 
     private class TestScreenOnStatusProvider : ScreenStatusProvider {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
index b30c20d..b04eb01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
@@ -25,8 +25,8 @@
 import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
 import androidx.test.filters.SmallTest
 import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.SingleActivityFactory
 import com.google.common.truth.Truth.assertThat
 
 import javax.inject.Inject
@@ -49,19 +49,17 @@
 
     open class UsbPermissionActivityTestable @Inject constructor (
         val message: UsbAudioWarningDialogMessage
-    )
-        : UsbPermissionActivity(UsbAudioWarningDialogMessage())
+    ) : UsbPermissionActivity(UsbAudioWarningDialogMessage())
 
     @Rule
     @JvmField
-    var activityRule = ActivityTestRule<UsbPermissionActivityTestable>(
-            object : SingleActivityFactory<UsbPermissionActivityTestable>(
-                    UsbPermissionActivityTestable::class.java
-            ) {
-                    override fun create(intent: Intent?): UsbPermissionActivityTestable {
-                        return UsbPermissionActivityTestable(mMessage)
-                    }
-            }, false, false)
+    var activityRule = ActivityTestRule(
+            /* activityFactory= */ SingleActivityFactory {
+                UsbPermissionActivityTestable(mMessage)
+            },
+            /* initialTouchMode= */ false,
+            /* launchActivity= */ false,
+    )
 
     private val activityIntent = Intent(mContext, UsbPermissionActivityTestable::class.java)
             .apply {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 079fbcd..0c28cbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -26,6 +26,8 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
@@ -224,6 +226,40 @@
     }
 
     @Test
+    fun userTrackerCallback_updatesSelectionStatus() = runSelfCancelingTest {
+        underTest = create(this)
+        var selectedUser: SelectedUserModel? = null
+        underTest.selectedUser.onEach { selectedUser = it }.launchIn(this)
+        setUpUsers(count = 2, selectedIndex = 1)
+
+        // WHEN the user is changing
+        tracker.onUserChanging(userId = 1)
+
+        // THEN the selection status is IN_PROGRESS
+        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+
+        // WHEN the user has finished changing
+        tracker.onUserChanged(userId = 1)
+
+        // THEN the selection status is COMPLETE
+        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE)
+
+        tracker.onProfileChanged()
+        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE)
+
+        setUpUsers(count = 2, selectedIndex = 0)
+
+        tracker.onUserChanging(userId = 0)
+        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+
+        // WHEN a profile change occurs while a user is changing
+        tracker.onProfileChanged()
+
+        // THEN the selection status remains as IN_PROGRESS
+        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+    }
+
+    @Test
     fun userSwitchingInProgress_registersUserTrackerCallback() = runSelfCancelingTest {
         underTest = create(this)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index ca83d49..5a54aff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -19,10 +19,12 @@
 
 import android.app.ActivityManager
 import android.app.admin.DevicePolicyManager
+import android.content.Context
 import android.content.Intent
 import android.content.pm.UserInfo
 import android.graphics.Bitmap
 import android.graphics.drawable.Drawable
+import android.os.Process
 import android.os.UserHandle
 import android.os.UserManager
 import android.provider.Settings
@@ -39,12 +41,10 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -64,7 +64,9 @@
 import junit.framework.Assert.assertNotNull
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -78,6 +80,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -97,16 +100,19 @@
     @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
     @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
-    @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
     private lateinit var underTest: UserInteractor
 
+    private lateinit var spyContext: Context
     private lateinit var testScope: TestScope
     private lateinit var userRepository: FakeUserRepository
+    private lateinit var keyguardReply: KeyguardInteractorFactory.WithDependencies
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var telephonyRepository: FakeTelephonyRepository
+    private lateinit var testDispatcher: TestDispatcher
     private lateinit var featureFlags: FakeFeatureFlags
+    private lateinit var refreshUsersScheduler: RefreshUsersScheduler
 
     @Before
     fun setUp() {
@@ -126,63 +132,36 @@
                 set(Flags.FULL_SCREEN_USER_SWITCHER, false)
                 set(Flags.FACE_AUTH_REFACTOR, true)
             }
+        spyContext = spy(context)
+        keyguardReply = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+        keyguardRepository = keyguardReply.repository
         userRepository = FakeUserRepository()
-        keyguardRepository = FakeKeyguardRepository()
         telephonyRepository = FakeTelephonyRepository()
-        val testDispatcher = StandardTestDispatcher()
+        testDispatcher = StandardTestDispatcher()
         testScope = TestScope(testDispatcher)
-        val refreshUsersScheduler =
+        refreshUsersScheduler =
             RefreshUsersScheduler(
                 applicationScope = testScope.backgroundScope,
                 mainDispatcher = testDispatcher,
                 repository = userRepository,
             )
-        underTest =
-            UserInteractor(
-                applicationContext = context,
-                repository = userRepository,
-                activityStarter = activityStarter,
-                keyguardInteractor =
-                    KeyguardInteractor(
-                        repository = keyguardRepository,
-                        commandQueue = commandQueue,
-                        featureFlags = featureFlags,
-                        bouncerRepository = FakeKeyguardBouncerRepository(),
-                    ),
-                manager = manager,
-                headlessSystemUserMode = headlessSystemUserMode,
-                applicationScope = testScope.backgroundScope,
-                telephonyInteractor =
-                    TelephonyInteractor(
-                        repository = telephonyRepository,
-                    ),
-                broadcastDispatcher = fakeBroadcastDispatcher,
-                keyguardUpdateMonitor = keyguardUpdateMonitor,
-                backgroundDispatcher = testDispatcher,
-                activityManager = activityManager,
-                refreshUsersScheduler = refreshUsersScheduler,
-                guestUserInteractor =
-                    GuestUserInteractor(
-                        applicationContext = context,
-                        applicationScope = testScope.backgroundScope,
-                        mainDispatcher = testDispatcher,
-                        backgroundDispatcher = testDispatcher,
-                        manager = manager,
-                        repository = userRepository,
-                        deviceProvisionedController = deviceProvisionedController,
-                        devicePolicyManager = devicePolicyManager,
-                        refreshUsersScheduler = refreshUsersScheduler,
-                        uiEventLogger = uiEventLogger,
-                        resumeSessionReceiver = resumeSessionReceiver,
-                        resetOrExitSessionReceiver = resetOrExitSessionReceiver,
-                    ),
-                uiEventLogger = uiEventLogger,
-                featureFlags = featureFlags,
-            )
     }
 
     @Test
-    fun testKeyguardUpdateMonitor_onKeyguardGoingAway() =
+    fun createUserInteractor_processUser_noSecondaryService() {
+        createUserInteractor()
+        verify(spyContext, never()).startServiceAsUser(any(), any())
+    }
+
+    @Test
+    fun createUserInteractor_nonProcessUser_startsSecondaryService() {
+        createUserInteractor(false /* startAsProcessUser */)
+        verify(spyContext).startServiceAsUser(any(), any())
+    }
+
+    @Test
+    fun testKeyguardUpdateMonitor_onKeyguardGoingAway() {
+        createUserInteractor()
         testScope.runTest {
             val argumentCaptor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
             verify(keyguardUpdateMonitor).registerCallback(argumentCaptor.capture())
@@ -192,9 +171,11 @@
             val lastValue = collectLastValue(underTest.dialogDismissRequests)
             assertNotNull(lastValue)
         }
+    }
 
     @Test
-    fun onRecordSelected_user() =
+    fun onRecordSelected_user() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -209,9 +190,11 @@
             verify(activityManager).switchUser(userInfos[1].id)
             Unit
         }
+    }
 
     @Test
-    fun onRecordSelected_switchToGuestUser() =
+    fun onRecordSelected_switchToGuestUser() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = true)
             userRepository.setUserInfos(userInfos)
@@ -225,9 +208,11 @@
             verify(activityManager).switchUser(userInfos.last().id)
             Unit
         }
+    }
 
     @Test
-    fun onRecordSelected_switchToRestrictedUser() =
+    fun onRecordSelected_switchToRestrictedUser() {
+        createUserInteractor()
         testScope.runTest {
             var userInfos = createUserInfos(count = 2, includeGuest = false).toMutableList()
             userInfos.add(
@@ -250,9 +235,11 @@
             verify(activityManager).switchUser(userInfos.last().id)
             Unit
         }
+    }
 
     @Test
-    fun onRecordSelected_enterGuestMode() =
+    fun onRecordSelected_enterGuestMode() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -270,9 +257,11 @@
             verify(manager).createGuest(any())
             Unit
         }
+    }
 
     @Test
-    fun onRecordSelected_action() =
+    fun onRecordSelected_action() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = true)
             userRepository.setUserInfos(userInfos)
@@ -286,9 +275,11 @@
             verify(dialogShower, never()).dismiss()
             verify(activityStarter).startActivity(any(), anyBoolean())
         }
+    }
 
     @Test
-    fun users_switcherEnabled() =
+    fun users_switcherEnabled() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = true)
             userRepository.setUserInfos(userInfos)
@@ -299,9 +290,11 @@
 
             assertUsers(models = value(), count = 3, includeGuest = true)
         }
+    }
 
     @Test
-    fun users_switchesToSecondUser() =
+    fun users_switchesToSecondUser() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -313,9 +306,11 @@
 
             assertUsers(models = value(), count = 2, selectedIndex = 1)
         }
+    }
 
     @Test
-    fun users_switcherNotEnabled() =
+    fun users_switcherNotEnabled() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -325,9 +320,11 @@
             val value = collectLastValue(underTest.users)
             assertUsers(models = value(), count = 1)
         }
+    }
 
     @Test
-    fun selectedUser() =
+    fun selectedUser() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -340,9 +337,11 @@
             userRepository.setSelectedUserInfo(userInfos[1])
             assertUser(value(), id = 1, isSelected = true)
         }
+    }
 
     @Test
-    fun actions_deviceUnlocked() =
+    fun actions_deviceUnlocked() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
 
@@ -364,9 +363,11 @@
                     )
                 )
         }
+    }
 
     @Test
-    fun actions_deviceUnlocked_fullScreen() =
+    fun actions_deviceUnlocked_fullScreen() {
+        createUserInteractor()
         testScope.runTest {
             featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 2, includeGuest = false)
@@ -387,9 +388,11 @@
                     )
                 )
         }
+    }
 
     @Test
-    fun actions_deviceUnlockedUserNotPrimary_emptyList() =
+    fun actions_deviceUnlockedUserNotPrimary_emptyList() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -400,9 +403,11 @@
 
             assertThat(value()).isEqualTo(emptyList<UserActionModel>())
         }
+    }
 
     @Test
-    fun actions_deviceUnlockedUserIsGuest_emptyList() =
+    fun actions_deviceUnlockedUserIsGuest_emptyList() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = true)
             assertThat(userInfos[1].isGuest).isTrue()
@@ -414,9 +419,11 @@
 
             assertThat(value()).isEqualTo(emptyList<UserActionModel>())
         }
+    }
 
     @Test
-    fun actions_deviceLockedAddFromLockscreenSet_fullList() =
+    fun actions_deviceLockedAddFromLockscreenSet_fullList() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -440,9 +447,11 @@
                     )
                 )
         }
+    }
 
     @Test
-    fun actions_deviceLockedAddFromLockscreenSet_fullList_fullScreen() =
+    fun actions_deviceLockedAddFromLockscreenSet_fullList_fullScreen() {
+        createUserInteractor()
         testScope.runTest {
             featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 2, includeGuest = false)
@@ -467,9 +476,11 @@
                     )
                 )
         }
+    }
 
     @Test
-    fun actions_deviceLocked_onlymanageUserIsShown() =
+    fun actions_deviceLocked_onlymanageUserIsShown() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -480,9 +491,11 @@
 
             assertThat(value()).isEqualTo(listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT))
         }
+    }
 
     @Test
-    fun executeAction_addUser_dismissesDialogAndStartsActivity() =
+    fun executeAction_addUser_dismissesDialogAndStartsActivity() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -494,9 +507,11 @@
                 .log(MultiUserActionsEvent.CREATE_USER_FROM_USER_SWITCHER)
             underTest.onDialogShown()
         }
+    }
 
     @Test
-    fun executeAction_addSupervisedUser_dismissesDialogAndStartsActivity() =
+    fun executeAction_addSupervisedUser_dismissesDialogAndStartsActivity() {
+        createUserInteractor()
         testScope.runTest {
             underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
 
@@ -508,9 +523,11 @@
                 .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
             assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
         }
+    }
 
     @Test
-    fun executeAction_navigateToManageUsers() =
+    fun executeAction_navigateToManageUsers() {
+        createUserInteractor()
         testScope.runTest {
             underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
 
@@ -518,9 +535,11 @@
             verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
             assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
         }
+    }
 
     @Test
-    fun executeAction_guestMode() =
+    fun executeAction_guestMode() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -556,9 +575,11 @@
                 )
             verify(activityManager).switchUser(guestUserInfo.id)
         }
+    }
 
     @Test
-    fun selectUser_alreadySelectedGuestReSelected_exitGuestDialog() =
+    fun selectUser_alreadySelectedGuestReSelected_exitGuestDialog() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = true)
             val guestUserInfo = userInfos[1]
@@ -577,9 +598,11 @@
                 .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
             verify(dialogShower, never()).dismiss()
         }
+    }
 
     @Test
-    fun selectUser_currentlyGuestNonGuestSelected_exitGuestDialog() =
+    fun selectUser_currentlyGuestNonGuestSelected_exitGuestDialog() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = true)
             val guestUserInfo = userInfos[1]
@@ -595,9 +618,11 @@
                 .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
             verify(dialogShower, never()).dismiss()
         }
+    }
 
     @Test
-    fun selectUser_notCurrentlyGuest_switchesUsers() =
+    fun selectUser_notCurrentlyGuest_switchesUsers() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -611,9 +636,11 @@
             verify(activityManager).switchUser(userInfos[1].id)
             verify(dialogShower).dismiss()
         }
+    }
 
     @Test
-    fun telephonyCallStateChanges_refreshesUsers() =
+    fun telephonyCallStateChanges_refreshesUsers() {
+        createUserInteractor()
         testScope.runTest {
             runCurrent()
 
@@ -624,9 +651,11 @@
 
             assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
         }
+    }
 
     @Test
-    fun userSwitchedBroadcast() =
+    fun userSwitchedBroadcast() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -641,81 +670,84 @@
 
             userRepository.setSelectedUserInfo(userInfos[1])
             runCurrent()
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_SWITCHED)
-                        .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
-                )
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                spyContext,
+                Intent(Intent.ACTION_USER_SWITCHED)
+                    .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
+            )
             runCurrent()
 
             verify(callback1, atLeastOnce()).onUserStateChanged()
             verify(callback2, atLeastOnce()).onUserStateChanged()
             assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
             assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+            verify(spyContext).startServiceAsUser(any(), eq(UserHandle.of(userInfos[1].id)))
         }
+    }
 
     @Test
-    fun userInfoChangedBroadcast() =
+    fun userInfoChangedBroadcast() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
             userRepository.setSelectedUserInfo(userInfos[0])
+            runCurrent()
             val refreshUsersCallCount = userRepository.refreshUsersCallCount
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_INFO_CHANGED),
-                )
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                spyContext,
+                Intent(Intent.ACTION_USER_INFO_CHANGED),
+            )
 
             runCurrent()
 
             assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
         }
+    }
 
     @Test
-    fun systemUserUnlockedBroadcast_refreshUsers() =
+    fun systemUserUnlockedBroadcast_refreshUsers() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
             userRepository.setSelectedUserInfo(userInfos[0])
+            runCurrent()
             val refreshUsersCallCount = userRepository.refreshUsersCallCount
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_UNLOCKED)
-                        .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
-                )
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                spyContext,
+                Intent(Intent.ACTION_USER_UNLOCKED)
+                    .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
+            )
             runCurrent()
 
             assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
         }
+    }
 
     @Test
-    fun nonSystemUserUnlockedBroadcast_doNotRefreshUsers() =
+    fun nonSystemUserUnlockedBroadcast_doNotRefreshUsers() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
             userRepository.setSelectedUserInfo(userInfos[0])
             val refreshUsersCallCount = userRepository.refreshUsersCallCount
 
-            fakeBroadcastDispatcher.registeredReceivers.forEach {
-                it.onReceive(
-                    context,
-                    Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
-                )
-            }
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                spyContext,
+                Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
+            )
 
             assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
         }
+    }
 
     @Test
-    fun userRecords() =
+    fun userRecords() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = false)
             userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
@@ -739,9 +771,11 @@
                     ),
             )
         }
+    }
 
     @Test
-    fun userRecordsFullScreen() =
+    fun userRecordsFullScreen() {
+        createUserInteractor()
         testScope.runTest {
             featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
             val userInfos = createUserInfos(count = 3, includeGuest = false)
@@ -766,9 +800,11 @@
                     ),
             )
         }
+    }
 
     @Test
-    fun selectedUserRecord() =
+    fun selectedUserRecord() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = true)
             userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
@@ -784,9 +820,11 @@
                 isSwitchToEnabled = true,
             )
         }
+    }
 
     @Test
-    fun users_secondaryUser_guestUserCanBeSwitchedTo() =
+    fun users_secondaryUser_guestUserCanBeSwitchedTo() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = true)
             userRepository.setUserInfos(userInfos)
@@ -797,9 +835,11 @@
             assertThat(res()?.size == 3).isTrue()
             assertThat(res()?.find { it.isGuest }).isNotNull()
         }
+    }
 
     @Test
-    fun users_secondaryUser_noGuestAction() =
+    fun users_secondaryUser_noGuestAction() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = true)
             userRepository.setUserInfos(userInfos)
@@ -809,9 +849,11 @@
             val res = collectLastValue(underTest.actions)
             assertThat(res()?.find { it == UserActionModel.ENTER_GUEST_MODE }).isNull()
         }
+    }
 
     @Test
-    fun users_secondaryUser_noGuestUserRecord() =
+    fun users_secondaryUser_noGuestUserRecord() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = true)
             userRepository.setUserInfos(userInfos)
@@ -820,9 +862,11 @@
 
             assertThat(underTest.userRecords.value.find { it.isGuest }).isNull()
         }
+    }
 
     @Test
-    fun showUserSwitcher_fullScreenDisabled_showsDialogSwitcher() =
+    fun showUserSwitcher_fullScreenDisabled_showsDialogSwitcher() {
+        createUserInteractor()
         testScope.runTest {
             val expandable = mock<Expandable>()
             underTest.showUserSwitcher(expandable)
@@ -836,9 +880,11 @@
             underTest.onDialogShown()
             assertThat(dialogRequest()).isNull()
         }
+    }
 
     @Test
-    fun showUserSwitcher_fullScreenEnabled_launchesFullScreenDialog() =
+    fun showUserSwitcher_fullScreenEnabled_launchesFullScreenDialog() {
+        createUserInteractor()
         testScope.runTest {
             featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
 
@@ -854,9 +900,11 @@
             underTest.onDialogShown()
             assertThat(dialogRequest()).isNull()
         }
+    }
 
     @Test
-    fun users_secondaryUser_managedProfileIsNotIncluded() =
+    fun users_secondaryUser_managedProfileIsNotIncluded() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList()
             userInfos.add(
@@ -874,9 +922,11 @@
             val res = collectLastValue(underTest.users)
             assertThat(res()?.size == 3).isTrue()
         }
+    }
 
     @Test
-    fun currentUserIsNotPrimaryAndUserSwitcherIsDisabled() =
+    fun currentUserIsNotPrimaryAndUserSwitcherIsDisabled() {
+        createUserInteractor()
         testScope.runTest {
             val userInfos = createUserInfos(count = 2, includeGuest = false)
             userRepository.setUserInfos(userInfos)
@@ -885,9 +935,11 @@
             val selectedUser = collectLastValue(underTest.selectedUser)
             assertThat(selectedUser()).isNotNull()
         }
+    }
 
     @Test
-    fun userRecords_isActionAndNoUsersUnlocked_actionIsDisabled() =
+    fun userRecords_isActionAndNoUsersUnlocked_actionIsDisabled() {
+        createUserInteractor()
         testScope.runTest {
             keyguardRepository.setKeyguardShowing(true)
             whenever(manager.getUserSwitchability(any()))
@@ -907,9 +959,11 @@
                 .filter { it.info == null }
                 .forEach { action -> assertThat(action.isSwitchToEnabled).isFalse() }
         }
+    }
 
     @Test
-    fun userRecords_isActionAndNoUsersUnlocked_actionIsDisabled_HeadlessMode() =
+    fun userRecords_isActionAndNoUsersUnlocked_actionIsDisabled_HeadlessMode() {
+        createUserInteractor()
         testScope.runTest {
             keyguardRepository.setKeyguardShowing(true)
             whenever(headlessSystemUserMode.isHeadlessSystemUserMode()).thenReturn(true)
@@ -929,6 +983,7 @@
                 .filter { it.info == null }
                 .forEach { action -> assertThat(action.isSwitchToEnabled).isFalse() }
         }
+    }
 
     private fun assertUsers(
         models: List<UserModel>?,
@@ -1021,6 +1076,53 @@
             .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
     }
 
+    private fun createUserInteractor(startAsProcessUser: Boolean = true) {
+        val processUserId = Process.myUserHandle().identifier
+        val startUserId = if (startAsProcessUser) processUserId else (processUserId + 1)
+        runBlocking {
+            val userInfo =
+                createUserInfo(id = startUserId, name = "user_$startUserId", isPrimary = true)
+            userRepository.setUserInfos(listOf(userInfo))
+            userRepository.setSelectedUserInfo(userInfo)
+        }
+        underTest =
+            UserInteractor(
+                applicationContext = spyContext,
+                repository = userRepository,
+                activityStarter = activityStarter,
+                keyguardInteractor = keyguardReply.keyguardInteractor,
+                manager = manager,
+                headlessSystemUserMode = headlessSystemUserMode,
+                applicationScope = testScope.backgroundScope,
+                telephonyInteractor =
+                    TelephonyInteractor(
+                        repository = telephonyRepository,
+                    ),
+                broadcastDispatcher = fakeBroadcastDispatcher,
+                keyguardUpdateMonitor = keyguardUpdateMonitor,
+                backgroundDispatcher = testDispatcher,
+                activityManager = activityManager,
+                refreshUsersScheduler = refreshUsersScheduler,
+                guestUserInteractor =
+                    GuestUserInteractor(
+                        applicationContext = spyContext,
+                        applicationScope = testScope.backgroundScope,
+                        mainDispatcher = testDispatcher,
+                        backgroundDispatcher = testDispatcher,
+                        manager = manager,
+                        repository = userRepository,
+                        deviceProvisionedController = deviceProvisionedController,
+                        devicePolicyManager = devicePolicyManager,
+                        refreshUsersScheduler = refreshUsersScheduler,
+                        uiEventLogger = uiEventLogger,
+                        resumeSessionReceiver = resumeSessionReceiver,
+                        resetOrExitSessionReceiver = resetOrExitSessionReceiver,
+                    ),
+                uiEventLogger = uiEventLogger,
+                featureFlags = featureFlags,
+            )
+    }
+
     private fun createUserInfos(
         count: Int,
         includeGuest: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index fd8c6c7..2433e12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -32,11 +32,8 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -53,6 +50,7 @@
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.advanceUntilIdle
@@ -80,13 +78,11 @@
     @Mock private lateinit var uiEventLogger: UiEventLogger
     @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
-    @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
     private lateinit var underTest: StatusBarUserChipViewModel
 
     private val userRepository = FakeUserRepository()
-    private val keyguardRepository = FakeKeyguardRepository()
     private lateinit var guestUserInteractor: GuestUserInteractor
     private lateinit var refreshUsersScheduler: RefreshUsersScheduler
 
@@ -242,6 +238,10 @@
                 set(Flags.FULL_SCREEN_USER_SWITCHER, false)
                 set(Flags.FACE_AUTH_REFACTOR, true)
             }
+        runBlocking {
+            userRepository.setUserInfos(listOf(USER_0))
+            userRepository.setSelectedUserInfo(USER_0)
+        }
         return StatusBarUserChipViewModel(
             context = context,
             interactor =
@@ -250,12 +250,8 @@
                     repository = userRepository,
                     activityStarter = activityStarter,
                     keyguardInteractor =
-                        KeyguardInteractor(
-                            repository = keyguardRepository,
-                            commandQueue = commandQueue,
-                            featureFlags = featureFlags,
-                            bouncerRepository = FakeKeyguardBouncerRepository(),
-                        ),
+                        KeyguardInteractorFactory.create(featureFlags = featureFlags)
+                            .keyguardInteractor,
                     featureFlags = featureFlags,
                     manager = manager,
                     headlessSystemUserMode = headlessSystemUserMode,
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 9155084..8c88f95 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
@@ -30,11 +30,9 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -79,7 +77,6 @@
     @Mock private lateinit var uiEventLogger: UiEventLogger
     @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
-    @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
     private lateinit var underTest: UserSwitcherViewModel
@@ -105,6 +102,20 @@
         testScope = TestScope(testDispatcher)
         userRepository = FakeUserRepository()
         runBlocking {
+            val userInfos =
+                listOf(
+                    UserInfo(
+                        /* id= */ 0,
+                        /* name= */ "zero",
+                        /* iconPath= */ "",
+                        /* flags= */ UserInfo.FLAG_PRIMARY or
+                            UserInfo.FLAG_ADMIN or
+                            UserInfo.FLAG_FULL,
+                        UserManager.USER_TYPE_FULL_SYSTEM,
+                    ),
+                )
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
             userRepository.setSettings(
                 UserSwitcherSettingsModel(
                     isUserSwitcherEnabled = true,
@@ -112,7 +123,6 @@
             )
         }
 
-        keyguardRepository = FakeKeyguardRepository()
         val refreshUsersScheduler =
             RefreshUsersScheduler(
                 applicationScope = testScope.backgroundScope,
@@ -140,38 +150,35 @@
                 set(Flags.FULL_SCREEN_USER_SWITCHER, false)
                 set(Flags.FACE_AUTH_REFACTOR, true)
             }
+        val reply = KeyguardInteractorFactory.create(featureFlags = featureFlags)
+        keyguardRepository = reply.repository
+
         underTest =
             UserSwitcherViewModel(
-                    userInteractor =
-                        UserInteractor(
-                            applicationContext = context,
-                            repository = userRepository,
-                            activityStarter = activityStarter,
-                            keyguardInteractor =
-                                KeyguardInteractor(
-                                    repository = keyguardRepository,
-                                    commandQueue = commandQueue,
-                                    featureFlags = featureFlags,
-                                    bouncerRepository = FakeKeyguardBouncerRepository(),
-                                ),
-                            featureFlags = featureFlags,
-                            manager = manager,
-                            headlessSystemUserMode = headlessSystemUserMode,
-                            applicationScope = testScope.backgroundScope,
-                            telephonyInteractor =
-                                TelephonyInteractor(
-                                    repository = FakeTelephonyRepository(),
-                                ),
-                            broadcastDispatcher = fakeBroadcastDispatcher,
-                            keyguardUpdateMonitor = keyguardUpdateMonitor,
-                            backgroundDispatcher = testDispatcher,
-                            activityManager = activityManager,
-                            refreshUsersScheduler = refreshUsersScheduler,
-                            guestUserInteractor = guestUserInteractor,
-                            uiEventLogger = uiEventLogger,
-                        ),
-                    guestUserInteractor = guestUserInteractor,
-                )
+                userInteractor =
+                    UserInteractor(
+                        applicationContext = context,
+                        repository = userRepository,
+                        activityStarter = activityStarter,
+                        keyguardInteractor = reply.keyguardInteractor,
+                        featureFlags = featureFlags,
+                        manager = manager,
+                        headlessSystemUserMode = headlessSystemUserMode,
+                        applicationScope = testScope.backgroundScope,
+                        telephonyInteractor =
+                            TelephonyInteractor(
+                                repository = FakeTelephonyRepository(),
+                            ),
+                        broadcastDispatcher = fakeBroadcastDispatcher,
+                        keyguardUpdateMonitor = keyguardUpdateMonitor,
+                        backgroundDispatcher = testDispatcher,
+                        activityManager = activityManager,
+                        refreshUsersScheduler = refreshUsersScheduler,
+                        guestUserInteractor = guestUserInteractor,
+                        uiEventLogger = uiEventLogger,
+                    ),
+                guestUserInteractor = guestUserInteractor,
+            )
     }
 
     @Test
@@ -323,7 +330,7 @@
             setUsers(count = 2)
             val isFinishRequested = mutableListOf<Boolean>()
             val job =
-                    launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+                launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
             assertThat(isFinishRequested.last()).isFalse()
 
             underTest.onCancelButtonClicked()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
index 2662da2..1404a4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
@@ -16,43 +16,128 @@
 
 package com.android.systemui.util
 
-import android.test.suitebuilder.annotation.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import androidx.test.ext.junit.runners.AndroidJUnit4
+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
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class ListenerSetTest : SysuiTestCase() {
+open class ListenerSetTest : SysuiTestCase() {
 
-    var runnableSet: ListenerSet<Runnable> = ListenerSet()
+    private val runnableSet: IListenerSet<Runnable> = makeRunnableListenerSet()
 
-    @Before
-    fun setup() {
-        runnableSet = ListenerSet()
-    }
+    open fun makeRunnableListenerSet(): IListenerSet<Runnable> = ListenerSet()
 
     @Test
     fun addIfAbsent_doesNotDoubleAdd() {
         // setup & preconditions
         val runnable1 = Runnable { }
         val runnable2 = Runnable { }
-        assertThat(runnableSet.toList()).isEmpty()
+        assertThat(runnableSet).isEmpty()
 
         // Test that an element can be added
         assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
-        assertThat(runnableSet.toList()).containsExactly(runnable1)
+        assertThat(runnableSet).containsExactly(runnable1)
 
         // Test that a second element can be added
         assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
-        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+        assertThat(runnableSet).containsExactly(runnable1, runnable2)
 
         // Test that re-adding the first element does nothing and returns false
         assertThat(runnableSet.addIfAbsent(runnable1)).isFalse()
-        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+        assertThat(runnableSet).containsExactly(runnable1, runnable2)
+    }
+
+    @Test
+    fun isEmpty_changes() {
+        val runnable = Runnable { }
+        assertThat(runnableSet).isEmpty()
+        assertThat(runnableSet.isEmpty()).isTrue()
+        assertThat(runnableSet.isNotEmpty()).isFalse()
+
+        assertThat(runnableSet.addIfAbsent(runnable)).isTrue()
+        assertThat(runnableSet).isNotEmpty()
+        assertThat(runnableSet.isEmpty()).isFalse()
+        assertThat(runnableSet.isNotEmpty()).isTrue()
+
+        assertThat(runnableSet.remove(runnable)).isTrue()
+        assertThat(runnableSet).isEmpty()
+        assertThat(runnableSet.isEmpty()).isTrue()
+        assertThat(runnableSet.isNotEmpty()).isFalse()
+    }
+
+    @Test
+    fun size_changes() {
+        assertThat(runnableSet).isEmpty()
+        assertThat(runnableSet.size).isEqualTo(0)
+
+        assertThat(runnableSet.addIfAbsent(Runnable { })).isTrue()
+        assertThat(runnableSet.size).isEqualTo(1)
+
+        assertThat(runnableSet.addIfAbsent(Runnable { })).isTrue()
+        assertThat(runnableSet.size).isEqualTo(2)
+    }
+
+    @Test
+    fun contains_worksAsExpected() {
+        val runnable1 = Runnable { }
+        val runnable2 = Runnable { }
+        assertThat(runnableSet).isEmpty()
+        assertThat(runnable1 in runnableSet).isFalse()
+        assertThat(runnable2 in runnableSet).isFalse()
+        assertThat(runnableSet).doesNotContain(runnable1)
+        assertThat(runnableSet).doesNotContain(runnable2)
+
+        assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
+        assertThat(runnable1 in runnableSet).isTrue()
+        assertThat(runnable2 in runnableSet).isFalse()
+        assertThat(runnableSet).contains(runnable1)
+        assertThat(runnableSet).doesNotContain(runnable2)
+
+        assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
+        assertThat(runnable1 in runnableSet).isTrue()
+        assertThat(runnable2 in runnableSet).isTrue()
+        assertThat(runnableSet).contains(runnable1)
+        assertThat(runnableSet).contains(runnable2)
+
+        assertThat(runnableSet.remove(runnable1)).isTrue()
+        assertThat(runnable1 in runnableSet).isFalse()
+        assertThat(runnable2 in runnableSet).isTrue()
+        assertThat(runnableSet).doesNotContain(runnable1)
+        assertThat(runnableSet).contains(runnable2)
+    }
+
+    @Test
+    fun containsAll_worksAsExpected() {
+        val runnable1 = Runnable { }
+        val runnable2 = Runnable { }
+
+        assertThat(runnableSet).isEmpty()
+        assertThat(runnableSet.containsAll(listOf())).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable1))).isFalse()
+        assertThat(runnableSet.containsAll(listOf(runnable2))).isFalse()
+        assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isFalse()
+
+        assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
+        assertThat(runnableSet.containsAll(listOf())).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable1))).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable2))).isFalse()
+        assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isFalse()
+
+        assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
+        assertThat(runnableSet.containsAll(listOf())).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable1))).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable2))).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isTrue()
+
+        assertThat(runnableSet.remove(runnable1)).isTrue()
+        assertThat(runnableSet.containsAll(listOf())).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable1))).isFalse()
+        assertThat(runnableSet.containsAll(listOf(runnable2))).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isFalse()
     }
 
     @Test
@@ -60,22 +145,22 @@
         // setup and preconditions
         val runnable1 = Runnable { }
         val runnable2 = Runnable { }
-        assertThat(runnableSet.toList()).isEmpty()
+        assertThat(runnableSet).isEmpty()
         runnableSet.addIfAbsent(runnable1)
         runnableSet.addIfAbsent(runnable2)
-        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+        assertThat(runnableSet).containsExactly(runnable1, runnable2)
 
         // Test that removing the first runnable only removes that one runnable
         assertThat(runnableSet.remove(runnable1)).isTrue()
-        assertThat(runnableSet.toList()).containsExactly(runnable2)
+        assertThat(runnableSet).containsExactly(runnable2)
 
         // Test that removing a non-present runnable does not error
         assertThat(runnableSet.remove(runnable1)).isFalse()
-        assertThat(runnableSet.toList()).containsExactly(runnable2)
+        assertThat(runnableSet).containsExactly(runnable2)
 
         // Test that removing the other runnable succeeds
         assertThat(runnableSet.remove(runnable2)).isTrue()
-        assertThat(runnableSet.toList()).isEmpty()
+        assertThat(runnableSet).isEmpty()
     }
 
     @Test
@@ -92,17 +177,17 @@
         val runnable2 = Runnable {
             runnablesCalled.add(2)
         }
-        assertThat(runnableSet.toList()).isEmpty()
+        assertThat(runnableSet).isEmpty()
         runnableSet.addIfAbsent(runnable1)
         runnableSet.addIfAbsent(runnable2)
-        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+        assertThat(runnableSet).containsExactly(runnable1, runnable2)
 
         // Test that both runnables are called and 1 was removed
         for (runnable in runnableSet) {
             runnable.run()
         }
         assertThat(runnablesCalled).containsExactly(1, 2)
-        assertThat(runnableSet.toList()).containsExactly(runnable2)
+        assertThat(runnableSet).containsExactly(runnable2)
     }
 
     @Test
@@ -120,16 +205,16 @@
         val runnable2 = Runnable {
             runnablesCalled.add(2)
         }
-        assertThat(runnableSet.toList()).isEmpty()
+        assertThat(runnableSet).isEmpty()
         runnableSet.addIfAbsent(runnable1)
         runnableSet.addIfAbsent(runnable2)
-        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+        assertThat(runnableSet).containsExactly(runnable1, runnable2)
 
         // Test that both original runnables are called and 99 was added but not called
         for (runnable in runnableSet) {
             runnable.run()
         }
         assertThat(runnablesCalled).containsExactly(1, 2)
-        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2, runnable99)
+        assertThat(runnableSet).containsExactly(runnable1, runnable2, runnable99)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/NamedListenerSetTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/NamedListenerSetTest.kt
new file mode 100644
index 0000000..c89e317
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/NamedListenerSetTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NamedListenerSetTest : ListenerSetTest() {
+    override fun makeRunnableListenerSet(): IListenerSet<Runnable> = NamedListenerSet()
+
+    private val runnableSet = NamedListenerSet(NamedRunnable::name)
+
+    class NamedRunnable(val name: String, private val block: () -> Unit = {}) : Runnable {
+        override fun run() = block()
+    }
+
+    @Test
+    fun addIfAbsent_addsMultipleWithSameName_onlyIfInstanceIsAbsent() {
+        // setup & preconditions
+        val runnable1 = NamedRunnable("A")
+        val runnable2 = NamedRunnable("A")
+        assertThat(runnableSet).isEmpty()
+
+        // Test that an element can be added
+        assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
+        assertThat(runnableSet).containsExactly(runnable1)
+
+        // Test that a second element can be added, even with the same name
+        assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
+        assertThat(runnableSet).containsExactly(runnable1, runnable2)
+
+        // Test that re-adding the first element does nothing and returns false
+        assertThat(runnableSet.addIfAbsent(runnable1)).isFalse()
+        assertThat(runnableSet).containsExactly(runnable1, runnable2)
+    }
+
+    @Test
+    fun forEachNamed_includesCorrectNames() {
+        val runnable1 = NamedRunnable("A")
+        val runnable2 = NamedRunnable("X")
+        val runnable3 = NamedRunnable("X")
+        assertThat(runnableSet).isEmpty()
+
+        assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
+        assertThat(runnableSet.toNamedPairs())
+            .containsExactly(
+                "A" to runnable1,
+            )
+
+        assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
+        assertThat(runnableSet.toNamedPairs())
+            .containsExactly(
+                "A" to runnable1,
+                "X" to runnable2,
+            )
+
+        assertThat(runnableSet.addIfAbsent(runnable3)).isTrue()
+        assertThat(runnableSet.toNamedPairs())
+            .containsExactly(
+                "A" to runnable1,
+                "X" to runnable2,
+                "X" to runnable3,
+            )
+
+        assertThat(runnableSet.remove(runnable1)).isTrue()
+        assertThat(runnableSet.toNamedPairs())
+            .containsExactly(
+                "X" to runnable2,
+                "X" to runnable3,
+            )
+
+        assertThat(runnableSet.remove(runnable2)).isTrue()
+        assertThat(runnableSet.toNamedPairs())
+            .containsExactly(
+                "X" to runnable3,
+            )
+    }
+
+    /**
+     * This private method uses [NamedListenerSet.forEachNamed] to produce a list of pairs in order
+     * to validate that method.
+     */
+    private fun <T : Any> NamedListenerSet<T>.toNamedPairs() =
+        sequence { forEachNamed { name, listener -> yield(name to listener) } }.toList()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
index d8e418a..b13cb72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
@@ -26,6 +26,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -56,6 +57,7 @@
     private lateinit var viewRootImpl: ViewRootImpl
     @Mock
     private lateinit var windowToken: IBinder
+    private val wallpaperRepository = FakeWallpaperRepository()
 
     @JvmField
     @Rule
@@ -69,7 +71,7 @@
         `when`(root.windowToken).thenReturn(windowToken)
         `when`(root.isAttachedToWindow).thenReturn(true)
 
-        wallaperController = WallpaperController(wallpaperManager)
+        wallaperController = WallpaperController(wallpaperManager, wallpaperRepository)
 
         wallaperController.rootView = root
     }
@@ -90,9 +92,9 @@
 
     @Test
     fun setUnfoldTransitionZoom_defaultUnfoldTransitionIsDisabled_doesNotUpdateWallpaperZoom() {
-        wallaperController.onWallpaperInfoUpdated(createWallpaperInfo(
+        wallpaperRepository.wallpaperInfo.value = createWallpaperInfo(
             useDefaultTransition = false
-        ))
+        )
 
         wallaperController.setUnfoldTransitionZoom(0.5f)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 0663004..69d7586 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -40,7 +40,6 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.CaptioningManager;
 
 import androidx.test.filters.SmallTest;
 
@@ -64,6 +63,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.Executor;
+
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 @TestableLooper.RunWithLooper
@@ -96,8 +97,6 @@
     @Mock
     private WakefulnessLifecycle mWakefullnessLifcycle;
     @Mock
-    private CaptioningManager mCaptioningManager;
-    @Mock
     private KeyguardManager mKeyguardManager;
     @Mock
     private ActivityManager mActivityManager;
@@ -117,6 +116,7 @@
         when(mRingerModeLiveData.getValue()).thenReturn(-1);
         when(mRingerModeInternalLiveData.getValue()).thenReturn(-1);
         when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+        when(mUserTracker.getUserContext()).thenReturn(mContext);
         // Enable group volume adjustments
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions,
@@ -127,7 +127,7 @@
         mVolumeController = new TestableVolumeDialogControllerImpl(mContext,
                 mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
                 mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
-                mPackageManager, mWakefullnessLifcycle, mCaptioningManager, mKeyguardManager,
+                mPackageManager, mWakefullnessLifcycle, mKeyguardManager,
                 mActivityManager, mUserTracker, mDumpManager, mCallback);
         mVolumeController.setEnableDialogs(true, true);
     }
@@ -219,6 +219,11 @@
         verify(mRingerModeInternalLiveData).observeForever(any());
     }
 
+    @Test
+    public void testAddCallbackWithUserTracker() {
+        verify(mUserTracker).addCallback(any(UserTracker.Callback.class), any(Executor.class));
+    }
+
     static class TestableVolumeDialogControllerImpl extends VolumeDialogControllerImpl {
         private final WakefulnessLifecycle.Observer mWakefullessLifecycleObserver;
 
@@ -234,7 +239,6 @@
                 AccessibilityManager accessibilityManager,
                 PackageManager packageManager,
                 WakefulnessLifecycle wakefulnessLifecycle,
-                CaptioningManager captioningManager,
                 KeyguardManager keyguardManager,
                 ActivityManager activityManager,
                 UserTracker userTracker,
@@ -242,7 +246,7 @@
                 C callback) {
             super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
                     notificationManager, optionalVibrator, iAudioService, accessibilityManager,
-                    packageManager, wakefulnessLifecycle, captioningManager, keyguardManager,
+                    packageManager, wakefulnessLifecycle, keyguardManager,
                     activityManager, userTracker, dumpManager);
             mCallbacks = callback;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 8f725be..21e4f5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -16,14 +16,22 @@
 
 package com.android.systemui.volume;
 
+import static android.media.AudioManager.RINGER_MODE_NORMAL;
+import static android.media.AudioManager.RINGER_MODE_SILENT;
+import static android.media.AudioManager.RINGER_MODE_VIBRATE;
+
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
 import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
 import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotSame;
 import static junit.framework.Assert.assertTrue;
 
+import static org.junit.Assume.assumeNotNull;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -51,6 +59,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialogController;
@@ -76,13 +85,14 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class VolumeDialogImplTest extends SysuiTestCase {
-
     VolumeDialogImpl mDialog;
     View mActiveRinger;
     View mDrawerContainer;
     View mDrawerVibrate;
     View mDrawerMute;
     View mDrawerNormal;
+    CaptionsToggleImageButton mODICaptionsIcon;
+
     private TestableLooper mTestableLooper;
     private ConfigurationController mConfigurationController;
     private int mOriginalOrientation;
@@ -117,6 +127,9 @@
         }
     };
 
+    private FakeFeatureFlags mFeatureFlags;
+    private int mLongestHideShowAnimationDuration = 250;
+
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -128,10 +141,19 @@
         when(mPostureController.getDevicePosture())
                 .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);
 
+        int hideDialogDuration = mContext.getResources()
+                .getInteger(R.integer.config_dialogHideAnimationDurationMs);
+        int showDialogDuration = mContext.getResources()
+                .getInteger(R.integer.config_dialogShowAnimationDurationMs);
+
+        mLongestHideShowAnimationDuration = Math.max(hideDialogDuration, showDialogDuration);
+
         mOriginalOrientation = mContext.getResources().getConfiguration().orientation;
 
         mConfigurationController = new FakeConfigurationController();
 
+        mFeatureFlags = new FakeFeatureFlags();
+
         mDialog = new VolumeDialogImpl(
                 getContext(),
                 mVolumeDialogController,
@@ -142,10 +164,12 @@
                 mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor,
+                false,
                 mCsdWarningDialogFactory,
                 mPostureController,
                 mTestableLooper.getLooper(),
-                mDumpManager);
+                mDumpManager,
+                mFeatureFlags);
         mDialog.init(0, null);
         State state = createShellState();
         mDialog.onStateChangedH(state);
@@ -156,6 +180,7 @@
         mDrawerVibrate = mDrawerContainer.findViewById(R.id.volume_drawer_vibrate);
         mDrawerMute = mDrawerContainer.findViewById(R.id.volume_drawer_mute);
         mDrawerNormal = mDrawerContainer.findViewById(R.id.volume_drawer_normal);
+        mODICaptionsIcon = mDialog.getDialogView().findViewById(R.id.odi_captions_icon);
 
         Prefs.putInt(mContext,
                 Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT,
@@ -253,6 +278,7 @@
 
     @Test
     public void testVibrateOnRingerChangedToVibrate() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
         final State initialSilentState = new State();
         initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
 
@@ -273,7 +299,30 @@
     }
 
     @Test
+    public void testControllerDoesNotVibrateOnRingerChangedToVibrate_OnewayAPI_On() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+        final State initialSilentState = new State();
+        initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
+
+        final State vibrateState = new State();
+        vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+
+        // change ringer to silent
+        mDialog.onStateChangedH(initialSilentState);
+
+        // expected: shouldn't call vibrate yet
+        verify(mVolumeDialogController, never()).vibrate(any());
+
+        // changed ringer to vibrate
+        mDialog.onStateChangedH(vibrateState);
+
+        // expected: vibrate method of controller is not used
+        verify(mVolumeDialogController, never()).vibrate(any());
+    }
+
+    @Test
     public void testNoVibrateOnRingerInitialization() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
         final State initialUnsetState = new State();
         initialUnsetState.ringerModeInternal = -1;
 
@@ -291,7 +340,27 @@
     }
 
     @Test
+    public void testControllerDoesNotVibrateOnRingerInitialization_OnewayAPI_On() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+        final State initialUnsetState = new State();
+        initialUnsetState.ringerModeInternal = -1;
+
+        // ringer not initialized yet:
+        mDialog.onStateChangedH(initialUnsetState);
+
+        final State vibrateState = new State();
+        vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+
+        // changed ringer to vibrate
+        mDialog.onStateChangedH(vibrateState);
+
+        // shouldn't call vibrate on the controller either
+        verify(mVolumeDialogController, never()).vibrate(any());
+    }
+
+    @Test
     public void testSelectVibrateFromDrawer() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
         final State initialUnsetState = new State();
         initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
         mDialog.onStateChangedH(initialUnsetState);
@@ -305,7 +374,23 @@
     }
 
     @Test
+    public void testSelectVibrateFromDrawer_OnewayAPI_On() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+        final State initialUnsetState = new State();
+        initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL;
+        mDialog.onStateChangedH(initialUnsetState);
+
+        mActiveRinger.performClick();
+        mDrawerVibrate.performClick();
+
+        // Make sure we've actually changed the ringer mode.
+        verify(mVolumeDialogController, times(1)).setRingerMode(
+                AudioManager.RINGER_MODE_VIBRATE, false);
+    }
+
+    @Test
     public void testSelectMuteFromDrawer() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
         final State initialUnsetState = new State();
         initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
         mDialog.onStateChangedH(initialUnsetState);
@@ -319,7 +404,23 @@
     }
 
     @Test
+    public void testSelectMuteFromDrawer_OnewayAPI_On() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+        final State initialUnsetState = new State();
+        initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL;
+        mDialog.onStateChangedH(initialUnsetState);
+
+        mActiveRinger.performClick();
+        mDrawerMute.performClick();
+
+        // Make sure we've actually changed the ringer mode.
+        verify(mVolumeDialogController, times(1)).setRingerMode(
+                AudioManager.RINGER_MODE_SILENT, false);
+    }
+
+    @Test
     public void testSelectNormalFromDrawer() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
         final State initialUnsetState = new State();
         initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
         mDialog.onStateChangedH(initialUnsetState);
@@ -332,6 +433,21 @@
                 AudioManager.RINGER_MODE_NORMAL, false);
     }
 
+    @Test
+    public void testSelectNormalFromDrawer_OnewayAPI_On() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+        final State initialUnsetState = new State();
+        initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+        mDialog.onStateChangedH(initialUnsetState);
+
+        mActiveRinger.performClick();
+        mDrawerNormal.performClick();
+
+        // Make sure we've actually changed the ringer mode.
+        verify(mVolumeDialogController, times(1)).setRingerMode(
+                RINGER_MODE_NORMAL, false);
+    }
+
     /**
      * Ideally we would look at the ringer ImageView and check its assigned drawable id, but that
      * API does not exist. So we do the next best thing; we check the cached icon id.
@@ -378,10 +494,12 @@
                 mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor,
+                false,
                 mCsdWarningDialogFactory,
                 devicePostureController,
                 mTestableLooper.getLooper(),
-                mDumpManager
+                mDumpManager,
+                mFeatureFlags
         );
         dialog.init(0 , null);
 
@@ -397,6 +515,8 @@
 
         int gravity = dialog.getWindowGravity();
         assertEquals(Gravity.TOP, gravity & Gravity.VERTICAL_GRAVITY_MASK);
+
+        cleanUp(dialog);
     }
 
     @Test
@@ -415,10 +535,12 @@
                 mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor,
+                false,
                 mCsdWarningDialogFactory,
                 devicePostureController,
                 mTestableLooper.getLooper(),
-                mDumpManager
+                mDumpManager,
+                mFeatureFlags
         );
         dialog.init(0, null);
 
@@ -433,6 +555,8 @@
 
         int gravity = dialog.getWindowGravity();
         assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK);
+
+        cleanUp(dialog);
     }
 
     @Test
@@ -451,10 +575,12 @@
                 mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor,
+                false,
                 mCsdWarningDialogFactory,
                 devicePostureController,
                 mTestableLooper.getLooper(),
-                mDumpManager
+                mDumpManager,
+                mFeatureFlags
         );
         dialog.init(0, null);
 
@@ -469,6 +595,8 @@
 
         int gravity = dialog.getWindowGravity();
         assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK);
+
+        cleanUp(dialog);
     }
 
     @Test
@@ -489,18 +617,21 @@
                 mVolumePanelFactory,
                 mActivityStarter,
                 mInteractionJankMonitor,
+                false,
                 mCsdWarningDialogFactory,
                 mPostureController,
                 mTestableLooper.getLooper(),
-                mDumpManager
+                mDumpManager,
+                mFeatureFlags
         );
         dialog.init(0, null);
 
         verify(mPostureController, never()).removeCallback(any());
-
         dialog.destroy();
 
         verify(mPostureController).removeCallback(any());
+
+        cleanUp(dialog);
     }
 
     private void setOrientation(int orientation) {
@@ -511,16 +642,125 @@
         }
     }
 
+    private enum RingerDrawerState {INIT, OPEN, CLOSE}
+
+    @Test
+    public void ringerModeNormal_ringerContainerDescribesItsState() {
+        assertRingerContainerDescribesItsState(RINGER_MODE_NORMAL, RingerDrawerState.INIT);
+    }
+
+    @Test
+    public void ringerModeSilent_ringerContainerDescribesItsState() {
+        assertRingerContainerDescribesItsState(RINGER_MODE_SILENT, RingerDrawerState.INIT);
+    }
+
+    @Test
+    public void ringerModeVibrate_ringerContainerDescribesItsState() {
+        assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.INIT);
+    }
+
+    @Test
+    public void ringerModeNormal_openDrawer_ringerContainerDescribesItsState() {
+        assertRingerContainerDescribesItsState(RINGER_MODE_NORMAL, RingerDrawerState.OPEN);
+    }
+
+    @Test
+    public void ringerModeSilent_openDrawer_ringerContainerDescribesItsState() {
+        assertRingerContainerDescribesItsState(RINGER_MODE_SILENT, RingerDrawerState.OPEN);
+    }
+
+    @Test
+    public void ringerModeVibrate_openDrawer_ringerContainerDescribesItsState() {
+        assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.OPEN);
+    }
+
+    @Test
+    public void ringerModeNormal_closeDrawer_ringerContainerDescribesItsState() {
+        assertRingerContainerDescribesItsState(RINGER_MODE_NORMAL, RingerDrawerState.CLOSE);
+    }
+
+    @Test
+    public void ringerModeSilent_closeDrawer_ringerContainerDescribesItsState() {
+        assertRingerContainerDescribesItsState(RINGER_MODE_SILENT, RingerDrawerState.CLOSE);
+    }
+
+    @Test
+    public void ringerModeVibrate_closeDrawer_ringerContainerDescribesItsState() {
+        assertRingerContainerDescribesItsState(RINGER_MODE_VIBRATE, RingerDrawerState.CLOSE);
+    }
+
+    @Test
+    public void testOnCaptionEnabledStateChanged_checkBeforeSwitchTrue_setCaptionsEnabledState() {
+        ArgumentCaptor<VolumeDialogController.Callbacks> controllerCallbackCapture =
+                ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class);
+        verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any());
+        VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue();
+
+        callbacks.onCaptionEnabledStateChanged(true, true);
+        verify(mVolumeDialogController).setCaptionsEnabledState(eq(false));
+    }
+
+    @Test
+    public void testOnCaptionEnabledStateChanged_checkBeforeSwitchFalse_getCaptionsEnabledTrue() {
+        ArgumentCaptor<VolumeDialogController.Callbacks> controllerCallbackCapture =
+                ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class);
+        verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any());
+        VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue();
+
+        callbacks.onCaptionEnabledStateChanged(true, false);
+        assertTrue(mODICaptionsIcon.getCaptionsEnabled());
+    }
+
+    /**
+     * The content description should include ringer state, and the correct one.
+     */
+    private void assertRingerContainerDescribesItsState(int ringerMode,
+            RingerDrawerState drawerState) {
+        State state = createShellState();
+        state.ringerModeInternal = ringerMode;
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+        mDialog.onStateChangedH(state);
+
+        mDialog.show(SHOW_REASON_UNKNOWN);
+
+        if (drawerState != RingerDrawerState.INIT) {
+            // in both cases we first open the drawer
+            mDialog.toggleRingerDrawer(true);
+
+            if (drawerState == RingerDrawerState.CLOSE) {
+                mDialog.toggleRingerDrawer(false);
+            }
+        }
+
+        String ringerContainerDescription = mDialog.getSelectedRingerContainerDescription();
+        assumeNotNull(ringerContainerDescription);
+
+        String ringerDescription = mContext.getString(
+                mDialog.getStringDescriptionResourceForRingerMode(ringerMode));
+
+        if (drawerState == RingerDrawerState.OPEN) {
+            assertEquals(ringerDescription, ringerContainerDescription);
+        } else {
+            assertNotSame(ringerDescription, ringerContainerDescription);
+            assertTrue(ringerContainerDescription.startsWith(ringerDescription));
+        }
+    }
+
     @After
     public void teardown() {
-        if (mDialog != null) {
-            mDialog.clearInternalHandlerAfterTest();
-        }
+        cleanUp(mDialog);
         setOrientation(mOriginalOrientation);
+        mTestableLooper.moveTimeForward(mLongestHideShowAnimationDuration);
         mTestableLooper.processAllMessages();
         reset(mPostureController);
     }
 
+    private void cleanUp(VolumeDialogImpl dialog) {
+        if (dialog != null) {
+            dialog.clearInternalHandlerAfterTest();
+        }
+    }
+
 /*
     @Test
     public void testContentDescriptions() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
index 8e4f184..53e5e7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
@@ -188,6 +188,25 @@
     }
 
     @Test
+    public void queryWalletCards_walletEnabled_queryMultipleCards() {
+        mController.queryWalletCards(mCardsRetriever, 5);
+
+        verify(mQuickAccessWalletClient)
+                .getWalletCards(
+                        eq(MoreExecutors.directExecutor()), mRequestCaptor.capture(),
+                        eq(mCardsRetriever));
+
+        GetWalletCardsRequest request = mRequestCaptor.getValue();
+        assertEquals(5, mRequestCaptor.getValue().getMaxCards());
+        assertEquals(
+                mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_width),
+                request.getCardWidthPx());
+        assertEquals(
+                mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_height),
+                request.getCardHeightPx());
+    }
+
+    @Test
     public void queryWalletCards_walletFeatureNotAvailable_noQuery() {
         when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(false);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt
index 3901d72..d5bdb59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt
@@ -215,7 +215,7 @@
         cards: List<WalletCard> = emptyList(),
         shouldFail: Boolean = false
     ) {
-        whenever(walletController.queryWalletCards(any())).thenAnswer { invocation ->
+        whenever(walletController.queryWalletCards(any(), anyInt())).thenAnswer { invocation ->
             with(
                 invocation.arguments[0] as QuickAccessWalletClient.OnWalletCardsRetrievedCallback
             ) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
new file mode 100644
index 0000000..fe5024f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Fake implementation of the wallpaper repository. */
+class FakeWallpaperRepository : WallpaperRepository {
+    override val wallpaperInfo = MutableStateFlow<WallpaperInfo?>(null)
+    override val wallpaperSupportsAmbientMode = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
new file mode 100644
index 0000000..f8b096a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallpapers.data.repository
+
+import android.app.WallpaperInfo
+import android.app.WallpaperManager
+import android.content.Intent
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class WallpaperRepositoryImplTest : SysuiTestCase() {
+
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val userRepository = FakeUserRepository()
+    private val wallpaperManager: WallpaperManager = mock()
+
+    private val underTest: WallpaperRepositoryImpl by lazy {
+        WallpaperRepositoryImpl(
+            testScope.backgroundScope,
+            fakeBroadcastDispatcher,
+            userRepository,
+            wallpaperManager,
+            context,
+        )
+    }
+
+    @Before
+    fun setUp() {
+        whenever(wallpaperManager.isWallpaperSupported).thenReturn(true)
+        context.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
+            true,
+        )
+    }
+
+    @Test
+    fun wallpaperInfo_nullInfo() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperInfo)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(null)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun wallpaperInfo_hasInfoFromManager() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperInfo)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            assertThat(latest).isEqualTo(UNSUPPORTED_WP)
+        }
+
+    @Test
+    fun wallpaperInfo_initialValueIsFetched() =
+        testScope.runTest {
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+                .thenReturn(SUPPORTED_WP)
+            userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP))
+            userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+            // WHEN the repo initially starts up (underTest is lazy), then it fetches the current
+            // value for the wallpaper
+            assertThat(underTest.wallpaperInfo.value).isEqualTo(SUPPORTED_WP)
+        }
+
+    @Test
+    fun wallpaperInfo_updatesOnUserChanged() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperInfo)
+
+            val user3 = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
+            val user3Wp = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(user3.id)).thenReturn(user3Wp)
+
+            val user4 = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
+            val user4Wp = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(user4.id)).thenReturn(user4Wp)
+
+            userRepository.setUserInfos(listOf(user3, user4))
+
+            // WHEN user3 is selected
+            userRepository.setSelectedUserInfo(user3)
+
+            // THEN user3's wallpaper is used
+            assertThat(latest).isEqualTo(user3Wp)
+
+            // WHEN the user is switched to user4
+            userRepository.setSelectedUserInfo(user4)
+
+            // THEN user4's wallpaper is used
+            assertThat(latest).isEqualTo(user4Wp)
+        }
+
+    @Test
+    fun wallpaperInfo_doesNotUpdateOnUserChanging() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperInfo)
+
+            val user3 = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
+            val user3Wp = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(user3.id)).thenReturn(user3Wp)
+
+            val user4 = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
+            val user4Wp = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(user4.id)).thenReturn(user4Wp)
+
+            userRepository.setUserInfos(listOf(user3, user4))
+
+            // WHEN user3 is selected
+            userRepository.setSelectedUserInfo(user3)
+
+            // THEN user3's wallpaper is used
+            assertThat(latest).isEqualTo(user3Wp)
+
+            // WHEN the user has started switching to user4 but hasn't finished yet
+            userRepository.selectedUser.value =
+                SelectedUserModel(user4, SelectionStatus.SELECTION_IN_PROGRESS)
+
+            // THEN the wallpaper still matches user3
+            assertThat(latest).isEqualTo(user3Wp)
+        }
+
+    @Test
+    fun wallpaperInfo_updatesOnIntent() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperInfo)
+
+            val wp1 = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1)
+
+            assertThat(latest).isEqualTo(wp1)
+
+            // WHEN the info is new and a broadcast is sent
+            val wp2 = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp2)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            // THEN the flow updates
+            assertThat(latest).isEqualTo(wp2)
+        }
+
+    @Test
+    fun wallpaperInfo_wallpaperNotSupported_alwaysNull() =
+        testScope.runTest {
+            whenever(wallpaperManager.isWallpaperSupported).thenReturn(false)
+
+            val latest by collectLastValue(underTest.wallpaperInfo)
+            assertThat(latest).isNull()
+
+            // Even WHEN there *is* current wallpaper
+            val wp1 = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            // THEN the value is still null because wallpaper isn't supported
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun wallpaperInfo_deviceDoesNotSupportAmbientWallpaper_alwaysFalse() =
+        testScope.runTest {
+            context.orCreateTestableResources.addOverride(
+                com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
+                false
+            )
+
+            val latest by collectLastValue(underTest.wallpaperInfo)
+            assertThat(latest).isNull()
+
+            // Even WHEN there *is* current wallpaper
+            val wp1 = mock<WallpaperInfo>()
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            // THEN the value is still null because wallpaper isn't supported
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_nullInfo_false() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(null)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_infoDoesNotSupport_false() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_infoSupports_true() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_initialValueIsFetched_true() =
+        testScope.runTest {
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+                .thenReturn(SUPPORTED_WP)
+            userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP))
+            userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+            // WHEN the repo initially starts up (underTest is lazy), then it fetches the current
+            // value for the wallpaper
+            assertThat(underTest.wallpaperSupportsAmbientMode.value).isTrue()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_initialValueIsFetched_false() =
+        testScope.runTest {
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id))
+                .thenReturn(UNSUPPORTED_WP)
+            userRepository.setUserInfos(listOf(USER_WITH_UNSUPPORTED_WP))
+            userRepository.setSelectedUserInfo(USER_WITH_UNSUPPORTED_WP)
+
+            // WHEN the repo initially starts up (underTest is lazy), then it fetches the current
+            // value for the wallpaper
+            assertThat(underTest.wallpaperSupportsAmbientMode.value).isFalse()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_updatesOnUserChanged() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+                .thenReturn(SUPPORTED_WP)
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id))
+                .thenReturn(UNSUPPORTED_WP)
+            userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP, USER_WITH_UNSUPPORTED_WP))
+
+            // WHEN a user with supported wallpaper is selected
+            userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+            // THEN it's true
+            assertThat(latest).isTrue()
+
+            // WHEN the user is switched to a user with unsupported wallpaper
+            userRepository.setSelectedUserInfo(USER_WITH_UNSUPPORTED_WP)
+
+            // THEN it's false
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_doesNotUpdateOnUserChanging() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+                .thenReturn(SUPPORTED_WP)
+            whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_UNSUPPORTED_WP.id))
+                .thenReturn(UNSUPPORTED_WP)
+            userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP, USER_WITH_UNSUPPORTED_WP))
+
+            // WHEN a user with supported wallpaper is selected
+            userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+            // THEN it's true
+            assertThat(latest).isTrue()
+
+            // WHEN the user has started switching to a user with unsupported wallpaper but hasn't
+            // finished yet
+            userRepository.selectedUser.value =
+                SelectedUserModel(USER_WITH_UNSUPPORTED_WP, SelectionStatus.SELECTION_IN_PROGRESS)
+
+            // THEN it still matches the old user
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_updatesOnIntent() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
+
+            assertThat(latest).isFalse()
+
+            // WHEN the info now supports ambient mode and a broadcast is sent
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            // THEN the flow updates
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_wallpaperNotSupported_alwaysFalse() =
+        testScope.runTest {
+            whenever(wallpaperManager.isWallpaperSupported).thenReturn(false)
+
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+            assertThat(latest).isFalse()
+
+            // Even WHEN the current wallpaper *does* support ambient mode
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            // THEN the value is still false because wallpaper isn't supported
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun wallpaperSupportsAmbientMode_deviceDoesNotSupportAmbientWallpaper_alwaysFalse() =
+        testScope.runTest {
+            context.orCreateTestableResources.addOverride(
+                com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
+                false
+            )
+
+            val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
+            assertThat(latest).isFalse()
+
+            // Even WHEN the current wallpaper *does* support ambient mode
+            whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
+
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+                context,
+                Intent(Intent.ACTION_WALLPAPER_CHANGED),
+            )
+
+            // THEN the value is still false because the device doesn't support it
+            assertThat(latest).isFalse()
+        }
+
+    private companion object {
+        val USER_WITH_UNSUPPORTED_WP = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
+        val UNSUPPORTED_WP =
+            mock<WallpaperInfo>().apply { whenever(this.supportsAmbientMode()).thenReturn(false) }
+
+        val USER_WITH_SUPPORTED_WP = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
+        val SUPPORTED_WP =
+            mock<WallpaperInfo>().apply { whenever(this.supportsAmbientMode()).thenReturn(true) }
+    }
+}
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 17ada0d..e10a80c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -35,9 +35,11 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -90,7 +92,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -133,25 +135,29 @@
 import com.android.wm.shell.bubbles.BubbleDataRepository;
 import com.android.wm.shell.bubbles.BubbleEntry;
 import com.android.wm.shell.bubbles.BubbleLogger;
+import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubbleStackView;
 import com.android.wm.shell.bubbles.BubbleViewInfoTask;
+import com.android.wm.shell.bubbles.BubbleViewProvider;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.bubbles.StackEducationViewKt;
+import com.android.wm.shell.bubbles.properties.BubbleProperties;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.Transitions;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -167,7 +173,6 @@
 import java.util.List;
 import java.util.Optional;
 
-@Ignore("b/292153259")
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -273,13 +278,11 @@
     @Mock
     private TaskStackListenerImpl mTaskStackListener;
     @Mock
-    private ShellTaskOrganizer mShellTaskOrganizer;
-    @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
     private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock
-    private TaskViewTransitions mTaskViewTransitions;
+    Transitions mTransitions;
     @Mock
     private Optional<OneHandedController> mOneHandedOptional;
     @Mock
@@ -291,6 +294,9 @@
     @Mock
     private Icon mAppBubbleIcon;
 
+    private ShellTaskOrganizer mShellTaskOrganizer;
+    private TaskViewTransitions mTaskViewTransitions;
+
     private TestableBubblePositioner mPositioner;
 
     private BubbleData mBubbleData;
@@ -298,12 +304,21 @@
     private TestableLooper mTestableLooper;
 
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
     private UserHandle mUser0;
 
+    private FakeBubbleProperties mBubbleProperties;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            doReturn(true).when(mTransitions).isRegistered();
+        }
+        mTaskViewTransitions = new TaskViewTransitions(mTransitions);
+
         mTestableLooper = TestableLooper.get(this);
 
         // For the purposes of this test, just run everything synchronously
@@ -321,7 +336,7 @@
                 mColorExtractor, mDumpManager, mKeyguardStateController,
                 mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
                 mShadeWindowLogger);
-        mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
+        mNotificationShadeWindowController.setWindowRootView(mNotificationShadeWindowView);
         mNotificationShadeWindowController.attach();
 
         mAppBubbleIntent = new Intent(mContext, BubblesTestActivity.class);
@@ -361,7 +376,14 @@
                         mock(UiEventLogger.class),
                         mock(UserTracker.class)
                 );
-        when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
+
+        mShellTaskOrganizer = new ShellTaskOrganizer(mock(ShellInit.class),
+                mock(ShellCommandHandler.class),
+                null,
+                Optional.empty(),
+                Optional.empty(),
+                syncExecutor);
+        mBubbleProperties = new FakeBubbleProperties();
         mBubbleController = new TestableBubbleController(
                 mContext,
                 mShellInit,
@@ -386,7 +408,8 @@
                 mock(Handler.class),
                 mTaskViewTransitions,
                 mock(SyncTransactionQueue.class),
-                mock(IWindowManager.class));
+                mock(IWindowManager.class),
+                mBubbleProperties);
         mBubbleController.setExpandListener(mBubbleExpandListener);
         spyOn(mBubbleController);
 
@@ -406,7 +429,7 @@
                 mCommonNotifCollection,
                 mNotifPipeline,
                 mSysUiState,
-                mock(FeatureFlags.class),
+                mFeatureFlags,
                 mNotifPipelineFlags,
                 syncExecutor);
         mBubblesManager.addNotifCallback(mNotifCallback);
@@ -415,7 +438,8 @@
         mNotificationTestHelper = new NotificationTestHelper(
                 mContext,
                 mDependency,
-                TestableLooper.get(this));
+                TestableLooper.get(this),
+                mFeatureFlags);
         mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
         mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
         mNonBubbleNotifRow = mNotificationTestHelper.createRow();
@@ -477,7 +501,7 @@
     public void testAddBubble() {
         mBubbleController.updateBubble(mBubbleEntry);
         assertTrue(mBubbleController.hasBubbles());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -485,7 +509,7 @@
         assertFalse(mBubbleController.hasBubbles());
         mBubbleController.updateBubble(mBubbleEntry);
         assertTrue(mBubbleController.hasBubbles());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -500,7 +524,7 @@
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
         verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
 
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -563,7 +587,7 @@
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
         assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
 
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -582,7 +606,7 @@
         mBubbleData.setExpanded(true);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Make sure the notif is suppressed
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -591,11 +615,10 @@
         mBubbleController.collapseStack();
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
         assertStackCollapsed();
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
-    @Ignore("Currently broken.")
     public void testCollapseAfterChangingExpandedBubble() {
         // Mark it as a bubble and add it explicitly
         mEntryListener.onEntryAdded(mRow);
@@ -614,7 +637,7 @@
         assertStackExpanded();
         verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
                 true, mRow2.getKey());
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Last added is the one that is expanded
         assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey());
@@ -639,7 +662,7 @@
         // Collapse
         mBubbleController.collapseStack();
         assertStackCollapsed();
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -659,7 +682,7 @@
         mBubbleData.setExpanded(true);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Notif is suppressed after expansion
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -684,7 +707,7 @@
         mBubbleData.setExpanded(true);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Notif is suppressed after expansion
         assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -713,7 +736,7 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
 
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey());
@@ -744,7 +767,7 @@
         // We should be collapsed
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
         assertFalse(mBubbleController.hasBubbles());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -757,7 +780,7 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
 
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
         assertStackExpanded();
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
 
@@ -772,7 +795,7 @@
         // We should be collapsed
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
         assertFalse(mBubbleController.hasBubbles());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
 
@@ -790,7 +813,7 @@
         verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
                 mRow.getKey());
         assertStackCollapsed();
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -806,7 +829,7 @@
         verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
                 mRow.getKey());
         assertStackExpanded();
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -823,7 +846,7 @@
         // Dot + flyout is hidden because notif is suppressed
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -845,7 +868,7 @@
         // Dot + flyout is hidden because notif is suppressed
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
         assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -1126,7 +1149,7 @@
         assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2User11.getKey())).isNotNull();
 
         // Would have loaded bubbles twice because of user switch
-        verify(mDataRepository, times(2)).loadBubbles(anyInt(), any());
+        verify(mDataRepository, times(2)).loadBubbles(anyInt(), anyList(), any());
     }
 
     @Test
@@ -1182,7 +1205,7 @@
         mEntryListener.onEntryRemoved(mRow2, REASON_APP_CANCEL);
         assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
 
-        verify(mDataRepository, times(1)).loadBubbles(anyInt(), any());
+        verify(mDataRepository, times(1)).loadBubbles(anyInt(), anyList(), any());
     }
 
     /**
@@ -1250,11 +1273,11 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
         assertStackExpanded();
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Show the menu
         stackView.showManageMenu(true);
-        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, true /* manageMenuExpanded */);
         assertTrue(stackView.isManageMenuSettingsVisible());
         assertTrue(stackView.isManageMenuDontBubbleVisible());
     }
@@ -1268,11 +1291,11 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
         assertStackExpanded();
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Show the menu
         stackView.showManageMenu(true);
-        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, true /* manageMenuExpanded */);
         assertFalse(stackView.isManageMenuSettingsVisible());
         assertFalse(stackView.isManageMenuDontBubbleVisible());
     }
@@ -1286,15 +1309,15 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
         assertStackExpanded();
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Show the menu
         stackView.showManageMenu(true);
-        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, true /* manageMenuExpanded */);
 
         // Hide the menu
         stackView.showManageMenu(false);
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -1306,16 +1329,16 @@
         BubbleStackView stackView = mBubbleController.getStackView();
         mBubbleData.setExpanded(true);
         assertStackExpanded();
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, false /* manageMenuExpanded */);
 
         // Show the menu
         stackView.showManageMenu(true);
-        assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
+        assertSysuiStates(true /* stackExpanded */, true /* manageMenuExpanded */);
 
         // Collapse the stack
         mBubbleData.setExpanded(false);
 
-        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
+        assertSysuiStates(false /* stackExpanded */, false /* manageMenuExpanded */);
     }
 
     @Test
@@ -1577,7 +1600,7 @@
 
         NotificationListenerService.RankingMap rankingMap =
                 mock(NotificationListenerService.RankingMap.class);
-        when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() });
+        when(rankingMap.getOrderedKeys()).thenReturn(new String[]{mBubbleEntry.getKey()});
         mBubbleController.onRankingUpdated(rankingMap, entryDataByKey);
 
         // Should no longer be in the stack
@@ -1828,8 +1851,7 @@
     }
 
     @Test
-    public void testCreateBubbleFromOngoingNotification_OngoingDismissalEnabled() {
-        when(mNotifPipelineFlags.allowDismissOngoing()).thenReturn(true);
+    public void testCreateBubbleFromOngoingNotification() {
         NotificationEntry notif = new NotificationEntryBuilder()
                 .setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
                 .setCanBubble(true)
@@ -1842,8 +1864,7 @@
 
 
     @Test
-    public void testCreateBubbleFromNoDismissNotification_OngoingDismissalEnabled() {
-        when(mNotifPipelineFlags.allowDismissOngoing()).thenReturn(true);
+    public void testCreateBubbleFromNoDismissNotification() {
         NotificationEntry notif = new NotificationEntryBuilder()
                 .setFlag(mContext, Notification.FLAG_NO_DISMISS, true)
                 .setCanBubble(true)
@@ -1855,37 +1876,110 @@
     }
 
     @Test
-    public void testCreateBubbleFromOngoingNotification_OngoingDismissalDisabled() {
-        NotificationEntry notif = new NotificationEntryBuilder()
-                .setFlag(mContext, Notification.FLAG_ONGOING_EVENT, true)
-                .setCanBubble(true)
-                .build();
+    public void registerBubbleBarListener_barDisabled_largeScreen_shouldBeIgnored() {
+        mBubbleProperties.mIsBubbleBarEnabled = false;
+        mPositioner.setIsLargeScreen(true);
+        mEntryListener.onEntryAdded(mRow);
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertTrue(mBubbleController.hasBubbles());
 
-        BubbleEntry bubble = mBubblesManager.notifToBubbleEntry(notif);
+        assertStackMode();
 
-        assertFalse(
-                "Ongoing Notifis should be dismissable, if the feature is off",
-                bubble.isDismissable()
-        );
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        assertStackMode();
+
+        assertThat(mBubbleController.getStackView().getBubbleCount()).isEqualTo(1);
     }
 
-
     @Test
-    public void testCreateBubbleFromNoDismissNotification_OngoingDismissalDisabled() {
-        NotificationEntry notif = new NotificationEntryBuilder()
-                .setFlag(mContext, Notification.FLAG_NO_DISMISS, true)
-                .setCanBubble(true)
-                .build();
+    public void registerBubbleBarListener_barEnabled_smallScreen_shouldBeIgnored() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(false);
+        mEntryListener.onEntryAdded(mRow);
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertTrue(mBubbleController.hasBubbles());
 
-        BubbleEntry bubble = mBubblesManager.notifToBubbleEntry(notif);
+        assertStackMode();
 
-        assertTrue(
-                "FLAG_NO_DISMISS should be ignored, if the feature is off",
-                bubble.isDismissable()
-        );
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        assertStackMode();
+
+        assertThat(mBubbleController.getStackView().getBubbleCount()).isEqualTo(1);
     }
 
-        /** Creates a bubble using the userId and package. */
+    @Test
+    public void registerBubbleBarListener_switchToBarAndBackToStack() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        mEntryListener.onEntryAdded(mRow);
+        mBubbleController.updateBubble(mBubbleEntry);
+        assertTrue(mBubbleController.hasBubbles());
+
+        assertStackMode();
+
+        assertThat(mBubbleData.getBubbles()).hasSize(1);
+        assertBubbleIsInflatedForStack(mBubbleData.getBubbles().get(0));
+        assertBubbleIsInflatedForStack(mBubbleData.getOverflow());
+
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        assertBarMode();
+
+        assertThat(mBubbleData.getBubbles()).hasSize(1);
+        assertBubbleIsInflatedForBar(mBubbleData.getBubbles().get(0));
+        assertBubbleIsInflatedForBar(mBubbleData.getOverflow());
+
+        mBubbleController.unregisterBubbleStateListener();
+
+        assertStackMode();
+
+        assertThat(mBubbleData.getBubbles()).hasSize(1);
+        assertBubbleIsInflatedForStack(mBubbleData.getBubbles().get(0));
+        assertBubbleIsInflatedForStack(mBubbleData.getOverflow());
+    }
+
+    @Test
+    public void switchBetweenBarAndStack_noBubbles_shouldBeIgnored() {
+        mBubbleProperties.mIsBubbleBarEnabled = false;
+        mPositioner.setIsLargeScreen(true);
+        assertFalse(mBubbleController.hasBubbles());
+
+        assertNoBubbleContainerViews();
+
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        assertNoBubbleContainerViews();
+
+        mBubbleController.unregisterBubbleStateListener();
+
+        assertNoBubbleContainerViews();
+    }
+
+    @Test
+    public void bubbleBarBubbleExpandedAndCollapsed() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        mEntryListener.onEntryAdded(mRow);
+        mBubbleController.updateBubble(mBubbleEntry);
+
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), 500, 1000);
+
+        assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+
+        mBubbleController.collapseStack();
+
+        assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
+    }
+
+    /** Creates a bubble using the userId and package. */
     private Bubble createBubble(int userId, String pkg) {
         final UserHandle userHandle = new UserHandle(userId);
         NotificationEntry workEntry = new NotificationEntryBuilder()
@@ -2001,6 +2095,44 @@
         assertFalse(mBubbleController.getImplCachedState().isStackExpanded());
     }
 
+    /** Asserts that both the bubble stack and bar views don't exist. */
+    private void assertNoBubbleContainerViews() {
+        assertThat(mBubbleController.getStackView()).isNull();
+        assertThat(mBubbleController.getLayerView()).isNull();
+    }
+
+    /** Asserts that the stack is created and the bar is null. */
+    private void assertStackMode() {
+        assertThat(mBubbleController.getStackView()).isNotNull();
+        assertThat(mBubbleController.getLayerView()).isNull();
+    }
+
+    /** Asserts that the given bubble has the stack expanded view inflated. */
+    private void assertBubbleIsInflatedForStack(BubbleViewProvider b) {
+        assertThat(b.getIconView()).isNotNull();
+        assertThat(b.getExpandedView()).isNotNull();
+        assertThat(b.getBubbleBarExpandedView()).isNull();
+    }
+
+    /** Asserts that the bar is created and the stack is null. */
+    private void assertBarMode() {
+        assertThat(mBubbleController.getStackView()).isNull();
+        assertThat(mBubbleController.getLayerView()).isNotNull();
+    }
+
+    /** Asserts that the given bubble has the bar expanded view inflated. */
+    private void assertBubbleIsInflatedForBar(BubbleViewProvider b) {
+        // the icon view should be inflated for the overflow but not for other bubbles when showing
+        // in the bar
+        if (b instanceof Bubble) {
+            assertThat(b.getIconView()).isNull();
+        } else if (b instanceof BubbleOverflow) {
+            assertThat(b.getIconView()).isNotNull();
+        }
+        assertThat(b.getExpandedView()).isNull();
+        assertThat(b.getBubbleBarExpandedView()).isNotNull();
+    }
+
     /**
      * Asserts that a bubble notification is suppressed from the shade and also validates the cached
      * state is updated.
@@ -2030,4 +2162,19 @@
         assertThat(mSysUiStateBubblesExpanded).isEqualTo(stackExpanded);
         assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded);
     }
+
+    private static class FakeBubbleStateListener implements Bubbles.BubbleStateListener {
+        @Override
+        public void onBubbleStateChange(BubbleBarUpdate update) {
+        }
+    }
+
+    private static class FakeBubbleProperties implements BubbleProperties {
+        boolean mIsBubbleBarEnabled = false;
+
+        @Override
+        public boolean isBubbleBarEnabled() {
+            return mIsBubbleBarEnabled;
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 14c3f3c..5855347 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -31,6 +31,7 @@
 import com.android.wm.shell.bubbles.BubbleDataRepository;
 import com.android.wm.shell.bubbles.BubbleLogger;
 import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.properties.BubbleProperties;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
@@ -74,13 +75,14 @@
             Handler shellMainHandler,
             TaskViewTransitions taskViewTransitions,
             SyncTransactionQueue syncQueue,
-            IWindowManager wmService) {
+            IWindowManager wmService,
+            BubbleProperties bubbleProperties) {
         super(context, shellInit, shellCommandHandler, shellController, data, Runnable::run,
                 floatingContentCoordinator, dataRepository, statusBarService, windowManager,
                 windowManagerShellWrapper, userManager, launcherApps, bubbleLogger,
                 taskStackListener, shellTaskOrganizer, positioner, displayController,
                 oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler,
-                new SyncExecutor(), taskViewTransitions, syncQueue, wmService);
+                new SyncExecutor(), taskViewTransitions, syncQueue, wmService, bubbleProperties);
         setInflateSynchronously(true);
         onInit();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java
index 6edc373..047dc65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubblePositioner.java
@@ -27,6 +27,7 @@
 
 public class TestableBubblePositioner extends BubblePositioner {
     private int mMaxBubbles;
+    private boolean mIsLargeScreen = false;
 
     public TestableBubblePositioner(Context context,
             WindowManager windowManager) {
@@ -46,4 +47,13 @@
     public int getMaxBubbles() {
         return mMaxBubbles;
     }
+
+    public void setIsLargeScreen(boolean isLargeScreen) {
+        mIsLargeScreen = isLargeScreen;
+    }
+
+    @Override
+    public boolean isLargeScreen() {
+        return mIsLargeScreen;
+    }
 }
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 9de7a87..ef0adbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -34,7 +34,6 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 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;
@@ -76,7 +75,6 @@
     @Mock SplitScreen mSplitScreen;
     @Mock OneHanded mOneHanded;
     @Mock WakefulnessLifecycle mWakefulnessLifecycle;
-    @Mock ProtoTracer mProtoTracer;
     @Mock UserTracker mUserTracker;
     @Mock ShellExecutor mSysUiMainExecutor;
     @Mock NoteTaskInitializer mNoteTaskInitializer;
@@ -99,7 +97,6 @@
                 mKeyguardUpdateMonitor,
                 mScreenLifecycle,
                 mSysUiState,
-                mProtoTracer,
                 mWakefulnessLifecycle,
                 mUserTracker,
                 displayTracker,
diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorIsolationWorkaroundRule.kt b/packages/SystemUI/tests/utils/src/android/animation/AnimatorIsolationWorkaroundRule.kt
new file mode 100644
index 0000000..b74ddae
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorIsolationWorkaroundRule.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.animation
+
+import android.os.Looper
+import android.util.Log
+import com.android.systemui.util.test.TestExceptionDeferrer
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * This rule is intended to be used by System UI tests that are otherwise blocked from using
+ * animators because of [PlatformAnimatorIsolationRule]. It is preferred that test authors use
+ * [AnimatorTestRule], as that rule allows test authors to step through animations and removes the
+ * need for tests to handle multiple threads. However, many System UI tests were written before this
+ * was conceivable, so this rule is intended to support those legacy tests.
+ */
+class AnimatorIsolationWorkaroundRule(
+    private val requiredLooper: Looper? = Looper.getMainLooper(),
+) : TestRule {
+    private inner class IsolationWorkaroundHandler(ruleThread: Thread) : AnimationHandler() {
+        private val exceptionDeferrer = TestExceptionDeferrer(TAG, ruleThread)
+        private val addedCallbacks = mutableSetOf<AnimationFrameCallback>()
+
+        fun tearDownAndThrowDeferred() {
+            addedCallbacks.forEach { super.removeCallback(it) }
+            exceptionDeferrer.throwDeferred()
+        }
+
+        override fun addAnimationFrameCallback(callback: AnimationFrameCallback?, delay: Long) {
+            checkLooper()
+            if (callback != null) {
+                addedCallbacks.add(callback)
+            }
+            super.addAnimationFrameCallback(callback, delay)
+        }
+
+        override fun addOneShotCommitCallback(callback: AnimationFrameCallback?) {
+            checkLooper()
+            super.addOneShotCommitCallback(callback)
+        }
+
+        override fun removeCallback(callback: AnimationFrameCallback?) {
+            super.removeCallback(callback)
+        }
+
+        override fun setProvider(provider: AnimationFrameCallbackProvider?) {
+            checkLooper()
+            super.setProvider(provider)
+        }
+
+        override fun autoCancelBasedOn(objectAnimator: ObjectAnimator?) {
+            checkLooper()
+            super.autoCancelBasedOn(objectAnimator)
+        }
+
+        private fun checkLooper() {
+            exceptionDeferrer.check(requiredLooper == null || Looper.myLooper() == requiredLooper) {
+                "Animations are being registered on a different looper than the expected one!" +
+                    " expected=$requiredLooper actual=${Looper.myLooper()}"
+            }
+        }
+    }
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return object : Statement() {
+            @Throws(Throwable::class)
+            override fun evaluate() {
+                val workaroundHandler = IsolationWorkaroundHandler(Thread.currentThread())
+                val prevInstance = AnimationHandler.setTestHandler(workaroundHandler)
+                check(PlatformAnimatorIsolationRule.isIsolatingHandler(prevInstance)) {
+                    "AnimatorIsolationWorkaroundRule must be used within " +
+                        "PlatformAnimatorIsolationRule, but test handler was $prevInstance"
+                }
+                try {
+                    base.evaluate()
+                    val count = AnimationHandler.getAnimationCount()
+                    if (count > 0) {
+                        Log.w(TAG, "Animations still running: $count")
+                    }
+                } finally {
+                    val handlerAtEnd = AnimationHandler.setTestHandler(prevInstance)
+                    check(workaroundHandler == handlerAtEnd) {
+                        "Test handler was altered: expected=$workaroundHandler actual=$handlerAtEnd"
+                    }
+                    // Pass or fail, errors caught here should be the reason the test fails
+                    workaroundHandler.tearDownAndThrowDeferred()
+                }
+            }
+        }
+    }
+
+    private companion object {
+        private const val TAG = "AnimatorIsolationWorkaroundRule"
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
new file mode 100644
index 0000000..41dbc14
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.animation;
+
+import android.animation.AnimationHandler.AnimationFrameCallback;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.AndroidRuntimeException;
+import android.view.Choreographer;
+
+import com.android.internal.util.Preconditions;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * JUnit {@link TestRule} that can be used to run {@link Animator}s without actually waiting for the
+ * duration of the animation. This also helps the test to be written in a deterministic manner.
+ *
+ * Create an instance of {@code AnimatorTestRule} and specify it as a {@link org.junit.Rule}
+ * of the test class. Use {@link #advanceTimeBy(long)} to advance animators that have been started.
+ * Note that {@link #advanceTimeBy(long)} should be called from the same thread you have used to
+ * start the animator.
+ *
+ * <pre>
+ * {@literal @}SmallTest
+ * {@literal @}RunWith(AndroidJUnit4.class)
+ * public class SampleAnimatorTest {
+ *
+ *     {@literal @}Rule
+ *     public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+ *
+ *     {@literal @}UiThreadTest
+ *     {@literal @}Test
+ *     public void sample() {
+ *         final ValueAnimator animator = ValueAnimator.ofInt(0, 1000);
+ *         animator.setDuration(1000L);
+ *         assertThat(animator.getAnimatedValue(), is(0));
+ *         animator.start();
+ *         mAnimatorTestRule.advanceTimeBy(500L);
+ *         assertThat(animator.getAnimatedValue(), is(500));
+ *     }
+ * }
+ * </pre>
+ */
+public final class AnimatorTestRule implements TestRule {
+
+    private final Object mLock = new Object();
+    private final TestHandler mTestHandler = new TestHandler();
+    /**
+     * initializing the start time with {@link SystemClock#uptimeMillis()} reduces the discrepancies
+     * with various internals of classes like ValueAnimator which can sometimes read that clock via
+     * {@link android.view.animation.AnimationUtils#currentAnimationTimeMillis()}.
+     */
+    private final long mStartTime = SystemClock.uptimeMillis();
+    private long mTotalTimeDelta = 0;
+
+    @NonNull
+    @Override
+    public Statement apply(@NonNull final Statement base, @NonNull Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                AnimationHandler objAtStart = AnimationHandler.setTestHandler(mTestHandler);
+                try {
+                    base.evaluate();
+                } finally {
+                    AnimationHandler objAtEnd = AnimationHandler.setTestHandler(objAtStart);
+                    if (mTestHandler != objAtEnd) {
+                        // pass or fail, inner logic not restoring the handler needs to be reported.
+                        // noinspection ThrowFromFinallyBlock
+                        throw new IllegalStateException("Test handler was altered: expected="
+                                + mTestHandler + " actual=" + objAtEnd);
+                    }
+                }
+            }
+        };
+    }
+
+    /**
+     * If any new {@link Animator}s have been registered since the last time the frame time was
+     * advanced, initialize them with the current frame time.  Failing to do this will result in the
+     * animations beginning on the *next* advancement instead, so this is done automatically for
+     * test authors inside of {@link #advanceTimeBy}.  However this is exposed in case authors want
+     * to validate operations performed by onStart listeners.
+     * <p>
+     * NOTE: This is only required of the platform ValueAnimator because its start() method calls
+     * {@link AnimationHandler#addAnimationFrameCallback} BEFORE it calls startAnimation(), so this
+     * rule can't synchronously trigger the callback at that time.
+     */
+    public void initNewAnimators() {
+        requireLooper("AnimationTestRule#initNewAnimators()");
+        long currentTime = getCurrentTime();
+        List<AnimationFrameCallback> newCallbacks = new ArrayList<>(mTestHandler.mNewCallbacks);
+        mTestHandler.mNewCallbacks.clear();
+        for (AnimationFrameCallback newCallback : newCallbacks) {
+            newCallback.doAnimationFrame(currentTime);
+        }
+    }
+
+    /**
+     * Advances the animation clock by the given amount of delta in milliseconds. This call will
+     * produce an animation frame to all the ongoing animations. This method needs to be
+     * called on the same thread as {@link Animator#start()}.
+     *
+     * @param timeDelta the amount of milliseconds to advance
+     */
+    public void advanceTimeBy(long timeDelta) {
+        advanceTimeBy(timeDelta, null);
+    }
+
+    /**
+     * Advances the animation clock by the given amount of delta in milliseconds. This call will
+     * produce an animation frame to all the ongoing animations. This method needs to be
+     * called on the same thread as {@link Animator#start()}.
+     * <p>
+     * This method is not for test authors, but for rule authors to ensure that multiple animators
+     * can be advanced in sync.
+     *
+     * @param timeDelta      the amount of milliseconds to advance
+     * @param preFrameAction a consumer to be passed the timeDelta following the time advancement
+     *                       but prior to the frame production.
+     */
+    public void advanceTimeBy(long timeDelta, @Nullable Consumer<Long> preFrameAction) {
+        Preconditions.checkArgumentNonnegative(timeDelta, "timeDelta must not be negative");
+        requireLooper("AnimationTestRule#advanceTimeBy(long)");
+        if (timeDelta == 0) {
+            // If time is not being advanced, all animators will get a tick; don't double tick these
+            mTestHandler.mNewCallbacks.clear();
+        } else {
+            // before advancing time, start new animators with the current time
+            initNewAnimators();
+        }
+        synchronized (mLock) {
+            // advance time
+            mTotalTimeDelta += timeDelta;
+        }
+        if (preFrameAction != null) {
+            preFrameAction.accept(timeDelta);
+            // After letting other code run, clear any new callbacks to avoid double-ticking them
+            mTestHandler.mNewCallbacks.clear();
+        }
+        // produce a frame
+        mTestHandler.doFrame();
+    }
+
+    /**
+     * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a
+     * different time than the time tracked by {@link SystemClock} This method needs to be called on
+     * the same thread as {@link Animator#start()}.
+     */
+    public long getCurrentTime() {
+        requireLooper("AnimationTestRule#getCurrentTime()");
+        synchronized (mLock) {
+            return mStartTime + mTotalTimeDelta;
+        }
+    }
+
+    private static void requireLooper(String method) {
+        if (Looper.myLooper() == null) {
+            throw new AndroidRuntimeException(method + " may only be called on Looper threads");
+        }
+    }
+
+    private class TestHandler extends AnimationHandler {
+        public final TestProvider mTestProvider = new TestProvider();
+        private final List<AnimationFrameCallback> mNewCallbacks = new ArrayList<>();
+
+        TestHandler() {
+            setProvider(mTestProvider);
+        }
+
+        public void doFrame() {
+            mTestProvider.animateFrame();
+            mTestProvider.commitFrame();
+        }
+
+        @Override
+        public void addAnimationFrameCallback(AnimationFrameCallback callback, long delay) {
+            // NOTE: using the delay is infeasible because the AnimationHandler uses
+            //  SystemClock.uptimeMillis(); -- If we fix this to use an overridable method, then we
+            //  could fix this for tests.
+            super.addAnimationFrameCallback(callback, 0);
+            if (delay <= 0) {
+                mNewCallbacks.add(callback);
+            }
+        }
+
+        @Override
+        public void removeCallback(AnimationFrameCallback callback) {
+            super.removeCallback(callback);
+            mNewCallbacks.remove(callback);
+        }
+    }
+
+    private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider {
+        private long mFrameDelay = 10;
+        private Choreographer.FrameCallback mFrameCallback = null;
+        private final List<Runnable> mCommitCallbacks = new ArrayList<>();
+
+        public void animateFrame() {
+            Choreographer.FrameCallback frameCallback = mFrameCallback;
+            mFrameCallback = null;
+            if (frameCallback != null) {
+                frameCallback.doFrame(getFrameTime());
+            }
+        }
+
+        public void commitFrame() {
+            List<Runnable> commitCallbacks = new ArrayList<>(mCommitCallbacks);
+            mCommitCallbacks.clear();
+            for (Runnable commitCallback : commitCallbacks) {
+                commitCallback.run();
+            }
+        }
+
+        @Override
+        public void postFrameCallback(Choreographer.FrameCallback callback) {
+            assert mFrameCallback == null;
+            mFrameCallback = callback;
+        }
+
+        @Override
+        public void postCommitCallback(Runnable runnable) {
+            mCommitCallbacks.add(runnable);
+        }
+
+        @Override
+        public void setFrameDelay(long delay) {
+            mFrameDelay = delay;
+        }
+
+        @Override
+        public long getFrameDelay() {
+            return mFrameDelay;
+        }
+
+        @Override
+        public long getFrameTime() {
+            return getCurrentTime();
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/android/animation/PlatformAnimatorIsolationRule.kt b/packages/SystemUI/tests/utils/src/android/animation/PlatformAnimatorIsolationRule.kt
new file mode 100644
index 0000000..43a26f3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/animation/PlatformAnimatorIsolationRule.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.animation
+
+import com.android.systemui.util.test.TestExceptionDeferrer
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * This rule is used by [com.android.systemui.SysuiTestCase] to fail any test which attempts to
+ * start a Platform [Animator] without using [android.animation.AnimatorTestRule].
+ *
+ * TODO(b/291645410): enable this; currently this causes hundreds of test failures.
+ */
+class PlatformAnimatorIsolationRule : TestRule {
+
+    private class IsolatingAnimationHandler(ruleThread: Thread) : AnimationHandler() {
+        private val exceptionDeferrer = TestExceptionDeferrer(TAG, ruleThread)
+        override fun addOneShotCommitCallback(callback: AnimationFrameCallback?) = onError()
+        override fun removeCallback(callback: AnimationFrameCallback?) = onError()
+        override fun setProvider(provider: AnimationFrameCallbackProvider?) = onError()
+        override fun autoCancelBasedOn(objectAnimator: ObjectAnimator?) = onError()
+        override fun addAnimationFrameCallback(callback: AnimationFrameCallback?, delay: Long) =
+            onError()
+
+        private fun onError() =
+            exceptionDeferrer.fail(
+                "Test's animations are not isolated! " +
+                    "Did you forget to add an AnimatorTestRule to your test class?"
+            )
+
+        fun throwDeferred() = exceptionDeferrer.throwDeferred()
+    }
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return object : Statement() {
+            @Throws(Throwable::class)
+            override fun evaluate() {
+                val isolationHandler = IsolatingAnimationHandler(Thread.currentThread())
+                val originalHandler = AnimationHandler.setTestHandler(isolationHandler)
+                try {
+                    base.evaluate()
+                } finally {
+                    val handlerAtEnd = AnimationHandler.setTestHandler(originalHandler)
+                    check(isolationHandler == handlerAtEnd) {
+                        "Test handler was altered: expected=$isolationHandler actual=$handlerAtEnd"
+                    }
+                    // Pass or fail, a deferred exception should be the failure reason
+                    isolationHandler.throwDeferred()
+                }
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "PlatformAnimatorIsolationRule"
+
+        fun isIsolatingHandler(handler: AnimationHandler?): Boolean =
+            handler is IsolatingAnimationHandler
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt b/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt
new file mode 100644
index 0000000..7a97029
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.core.animation
+
+import com.android.systemui.util.test.TestExceptionDeferrer
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * This rule is used by [com.android.systemui.SysuiTestCase] to fail any test which attempts to
+ * start an AndroidX [Animator] without using [androidx.core.animation.AnimatorTestRule].
+ */
+class AndroidXAnimatorIsolationRule : TestRule {
+
+    private class IsolatingAnimationHandler(ruleThread: Thread) : AnimationHandler(null) {
+        private val exceptionDeferrer = TestExceptionDeferrer(TAG, ruleThread)
+        override fun addAnimationFrameCallback(callback: AnimationFrameCallback?) = onError()
+        override fun removeCallback(callback: AnimationFrameCallback?) = onError()
+        override fun onAnimationFrame(frameTime: Long) = onError()
+        override fun setFrameDelay(frameDelay: Long) = onError()
+
+        private fun onError() =
+            exceptionDeferrer.fail(
+                "Test's animations are not isolated! " +
+                    "Did you forget to add an AnimatorTestRule to your test class?"
+            )
+
+        fun throwDeferred() = exceptionDeferrer.throwDeferred()
+    }
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return object : Statement() {
+            @Throws(Throwable::class)
+            override fun evaluate() {
+                val isolationHandler = IsolatingAnimationHandler(Thread.currentThread())
+                AnimationHandler.setTestHandler(isolationHandler)
+                try {
+                    base.evaluate()
+                } finally {
+                    AnimationHandler.setTestHandler(null)
+                    // Pass or fail, a deferred exception should be the failure reason
+                    isolationHandler.throwDeferred()
+                }
+            }
+        }
+    }
+
+    private companion object {
+        private const val TAG = "AndroidXAnimatorIsolationRule"
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
index 9179efc..e470406 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
@@ -57,8 +57,7 @@
 
     @Before
     public void sysuiSetup() throws ExecutionException, InterruptedException {
-        SystemUIInitializer initializer =
-                SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
+        SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext);
         initializer.init(true);
         mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
         Dependency.setInstance(mDependency);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 8bbd58d..28b7d41 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -33,6 +33,7 @@
 import android.testing.TestableLooper;
 import android.util.Log;
 
+import androidx.core.animation.AndroidXAnimatorIsolationRule;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.UiDevice;
 
@@ -52,6 +53,7 @@
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.ClassRule;
 import org.junit.Rule;
 import org.mockito.Mockito;
 
@@ -69,6 +71,12 @@
     private static final String TAG = "SysuiTestCase";
 
     private Handler mHandler;
+
+    // set the lowest order so it's the outermost rule
+    @ClassRule(order = Integer.MIN_VALUE)
+    public static AndroidXAnimatorIsolationRule mAndroidXAnimatorIsolationRule =
+            new AndroidXAnimatorIsolationRule();
+
     @Rule
     public SysuiTestableContext mContext = new SysuiTestableContext(
             InstrumentationRegistry.getContext(), getLeakCheck());
@@ -90,8 +98,7 @@
         if (isRobolectricTest()) {
             mContext = mContext.createDefaultDisplayContext();
         }
-        SystemUIInitializer initializer =
-                SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
+        SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext);
         initializer.init(true);
         mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
         Dependency.setInstance(mDependency);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt
new file mode 100644
index 0000000..5a92fb1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/SingleActivityFactory.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.activity
+
+import android.app.Activity
+import android.content.Intent
+import androidx.test.runner.intercepting.SingleActivityFactory
+
+/**
+ * Builds a new [SingleActivityFactory] which delegating any call of [SingleActivityFactory.create]
+ * to the [instantiate] parameter.
+ *
+ * For more details, see [SingleActivityFactory].
+ */
+inline fun <reified T : Activity> SingleActivityFactory(
+    crossinline instantiate: (intent: Intent?) -> T,
+): SingleActivityFactory<T> {
+    return object : SingleActivityFactory<T>(T::class.java) {
+        override fun create(intent: Intent?): T = instantiate(intent)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
new file mode 100644
index 0000000..0ced19e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.animation
+
+import java.util.function.Consumer
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * A rule that wraps both [androidx.core.animation.AnimatorTestRule] and
+ * [android.animation.AnimatorTestRule] such that the clocks of the two animation handlers can be
+ * advanced together.
+ */
+class AnimatorTestRule : TestRule {
+    private val androidxRule = androidx.core.animation.AnimatorTestRule()
+    private val platformRule = android.animation.AnimatorTestRule()
+    private val advanceAndroidXTimeBy =
+        Consumer<Long> { timeDelta -> androidxRule.advanceTimeBy(timeDelta) }
+
+    /**
+     * Chain is for simplicity not to force a particular order; order should not matter, because
+     * each rule affects a different AnimationHandler classes, and no callbacks to code under test
+     * should be triggered by these rules
+     */
+    private val ruleChain = RuleChain.emptyRuleChain().around(androidxRule).around(platformRule)
+
+    override fun apply(base: Statement, description: Description): Statement =
+        ruleChain.apply(base, description)
+
+    /**
+     * Advances the animation clock by the given amount of delta in milliseconds. This call will
+     * produce an animation frame to all the ongoing animations.
+     *
+     * @param timeDelta the amount of milliseconds to advance
+     */
+    fun advanceTimeBy(timeDelta: Long) {
+        // NOTE: To avoid errors with order, we have to ensure that we advance the time within both
+        //  rules before either rule does its frame output. Failing to do this could cause the
+        //  animation from one to start later than the other.
+        platformRule.advanceTimeBy(timeDelta, advanceAndroidXTimeBy)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
new file mode 100644
index 0000000..7c98df6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.authentication.data.repository
+
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternView
+import com.android.internal.widget.LockscreenCredential
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
+import com.android.systemui.authentication.shared.model.AuthenticationResultModel
+import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeAuthenticationRepository(
+    private val currentTime: () -> Long,
+) : AuthenticationRepository {
+
+    private val _isAutoConfirmEnabled = MutableStateFlow(false)
+    override val isAutoConfirmEnabled: StateFlow<Boolean> = _isAutoConfirmEnabled.asStateFlow()
+
+    private val _isUnlocked = MutableStateFlow(false)
+    override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow()
+
+    override val hintedPinLength: Int = HINTING_PIN_LENGTH
+
+    private val _isPatternVisible = MutableStateFlow(true)
+    override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow()
+
+    private val _throttling = MutableStateFlow(AuthenticationThrottlingModel())
+    override val throttling: StateFlow<AuthenticationThrottlingModel> = _throttling.asStateFlow()
+
+    private val _authenticationMethod =
+        MutableStateFlow<AuthenticationMethodModel>(DEFAULT_AUTHENTICATION_METHOD)
+    override val authenticationMethod: StateFlow<AuthenticationMethodModel> =
+        _authenticationMethod.asStateFlow()
+
+    private var isLockscreenEnabled = true
+    private var failedAttemptCount = 0
+    private var throttlingEndTimestamp = 0L
+    private var credentialOverride: List<Any>? = null
+    private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode()
+
+    override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
+        return authenticationMethod.value
+    }
+
+    fun setAuthenticationMethod(authenticationMethod: AuthenticationMethodModel) {
+        _authenticationMethod.value = authenticationMethod
+        securityMode = authenticationMethod.toSecurityMode()
+    }
+
+    fun overrideCredential(pin: List<Int>) {
+        credentialOverride = pin
+    }
+
+    override suspend fun isLockscreenEnabled(): Boolean {
+        return isLockscreenEnabled
+    }
+
+    override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
+        failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1
+        _isUnlocked.value = isSuccessful
+    }
+
+    override suspend fun getPinLength(): Int {
+        return (credentialOverride ?: DEFAULT_PIN).size
+    }
+
+    override suspend fun getFailedAuthenticationAttemptCount(): Int {
+        return failedAttemptCount
+    }
+
+    override suspend fun getThrottlingEndTimestamp(): Long {
+        return throttlingEndTimestamp
+    }
+
+    override fun setThrottling(throttlingModel: AuthenticationThrottlingModel) {
+        _throttling.value = throttlingModel
+    }
+
+    fun setUnlocked(isUnlocked: Boolean) {
+        _isUnlocked.value = isUnlocked
+    }
+
+    fun setAutoConfirmEnabled(isEnabled: Boolean) {
+        _isAutoConfirmEnabled.value = isEnabled
+    }
+
+    fun setLockscreenEnabled(isLockscreenEnabled: Boolean) {
+        this.isLockscreenEnabled = isLockscreenEnabled
+    }
+
+    override suspend fun setThrottleDuration(durationMs: Int) {
+        throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
+    }
+
+    override suspend fun checkCredential(
+        credential: LockscreenCredential
+    ): AuthenticationResultModel {
+        val expectedCredential = credentialOverride ?: getExpectedCredential(securityMode)
+        val isSuccessful =
+            when {
+                credential.type != getCurrentCredentialType(securityMode) -> false
+                credential.type == LockPatternUtils.CREDENTIAL_TYPE_PIN ->
+                    credential.isPin && credential.matches(expectedCredential)
+                credential.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ->
+                    credential.isPassword && credential.matches(expectedCredential)
+                credential.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN ->
+                    credential.isPattern && credential.matches(expectedCredential)
+                else -> error("Unexpected credential type ${credential.type}!")
+            }
+
+        return if (
+            isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1
+        ) {
+            AuthenticationResultModel(
+                isSuccessful = isSuccessful,
+                throttleDurationMs = 0,
+            )
+        } else {
+            AuthenticationResultModel(
+                isSuccessful = false,
+                throttleDurationMs = THROTTLE_DURATION_MS,
+            )
+        }
+    }
+
+    private fun getExpectedCredential(securityMode: SecurityMode): List<Any> {
+        return when (val credentialType = getCurrentCredentialType(securityMode)) {
+            LockPatternUtils.CREDENTIAL_TYPE_PIN -> credentialOverride ?: DEFAULT_PIN
+            LockPatternUtils.CREDENTIAL_TYPE_PASSWORD -> "password".toList()
+            LockPatternUtils.CREDENTIAL_TYPE_PATTERN -> PATTERN.toCells()
+            else -> error("Unsupported credential type $credentialType!")
+        }
+    }
+
+    companion object {
+        val DEFAULT_AUTHENTICATION_METHOD = AuthenticationMethodModel.Pin
+        val PATTERN =
+            listOf(
+                AuthenticationPatternCoordinate(2, 0),
+                AuthenticationPatternCoordinate(2, 1),
+                AuthenticationPatternCoordinate(2, 2),
+                AuthenticationPatternCoordinate(1, 1),
+                AuthenticationPatternCoordinate(0, 0),
+                AuthenticationPatternCoordinate(0, 1),
+                AuthenticationPatternCoordinate(0, 2),
+            )
+        const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5
+        const val THROTTLE_DURATION_MS = 30000
+        const val HINTING_PIN_LENGTH = 6
+        val DEFAULT_PIN = buildList { repeat(HINTING_PIN_LENGTH) { add(it + 1) } }
+
+        private fun AuthenticationMethodModel.toSecurityMode(): SecurityMode {
+            return when (this) {
+                is AuthenticationMethodModel.Pin -> SecurityMode.PIN
+                is AuthenticationMethodModel.Password -> SecurityMode.Password
+                is AuthenticationMethodModel.Pattern -> SecurityMode.Pattern
+                is AuthenticationMethodModel.None -> SecurityMode.None
+            }
+        }
+
+        @LockPatternUtils.CredentialType
+        private fun getCurrentCredentialType(
+            securityMode: SecurityMode,
+        ): Int {
+            return when (securityMode) {
+                SecurityMode.PIN,
+                SecurityMode.SimPin,
+                SecurityMode.SimPuk -> LockPatternUtils.CREDENTIAL_TYPE_PIN
+                SecurityMode.Password -> LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+                SecurityMode.Pattern -> LockPatternUtils.CREDENTIAL_TYPE_PATTERN
+                SecurityMode.None -> LockPatternUtils.CREDENTIAL_TYPE_NONE
+                else -> error("Unsupported SecurityMode $securityMode!")
+            }
+        }
+
+        private fun LockscreenCredential.matches(expectedCredential: List<Any>): Boolean {
+            @Suppress("UNCHECKED_CAST")
+            return when {
+                isPin ->
+                    credential.map { byte -> byte.toInt().toChar() - '0' } == expectedCredential
+                isPassword -> credential.map { byte -> byte.toInt().toChar() } == expectedCredential
+                isPattern ->
+                    credential.contentEquals(
+                        LockPatternUtils.patternToByteArray(
+                            expectedCredential as List<LockPatternView.Cell>
+                        )
+                    )
+                else -> error("Unsupported credential type $type!")
+            }
+        }
+
+        private fun List<AuthenticationPatternCoordinate>.toCells(): List<LockPatternView.Cell> {
+            return map { coordinate -> LockPatternView.Cell.of(coordinate.y, coordinate.x) }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
new file mode 100644
index 0000000..2ef1be7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeFacePropertyRepository : FacePropertyRepository {
+    private val faceSensorInfo = MutableStateFlow<FaceSensorInfo?>(null)
+    override val sensorInfo: Flow<FaceSensorInfo?>
+        get() = faceSensorInfo
+
+    fun setSensorInfo(value: FaceSensorInfo?) {
+        faceSensorInfo.value = value
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeBouncerMessageRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeBouncerMessageRepository.kt
new file mode 100644
index 0000000..d9b926d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeBouncerMessageRepository.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.data.repository
+
+import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeBouncerMessageRepository : BouncerMessageRepository {
+    private val _primaryAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val primaryAuthMessage: StateFlow<BouncerMessageModel?>
+        get() = _primaryAuthMessage
+
+    private val _faceAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val faceAcquisitionMessage: StateFlow<BouncerMessageModel?>
+        get() = _faceAcquisitionMessage
+    private val _fingerprintAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val fingerprintAcquisitionMessage: StateFlow<BouncerMessageModel?>
+        get() = _fingerprintAcquisitionMessage
+    private val _customMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val customMessage: StateFlow<BouncerMessageModel?>
+        get() = _customMessage
+    private val _biometricAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val biometricAuthMessage: StateFlow<BouncerMessageModel?>
+        get() = _biometricAuthMessage
+    private val _authFlagsMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val authFlagsMessage: StateFlow<BouncerMessageModel?>
+        get() = _authFlagsMessage
+
+    private val _biometricLockedOutMessage = MutableStateFlow<BouncerMessageModel?>(null)
+    override val biometricLockedOutMessage: Flow<BouncerMessageModel?>
+        get() = _biometricLockedOutMessage
+
+    override fun setPrimaryAuthMessage(value: BouncerMessageModel?) {
+        _primaryAuthMessage.value = value
+    }
+
+    override fun setFaceAcquisitionMessage(value: BouncerMessageModel?) {
+        _faceAcquisitionMessage.value = value
+    }
+
+    override fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?) {
+        _fingerprintAcquisitionMessage.value = value
+    }
+
+    override fun setCustomMessage(value: BouncerMessageModel?) {
+        _customMessage.value = value
+    }
+
+    fun setBiometricAuthMessage(value: BouncerMessageModel?) {
+        _biometricAuthMessage.value = value
+    }
+
+    fun setAuthFlagsMessage(value: BouncerMessageModel?) {
+        _authFlagsMessage.value = value
+    }
+
+    fun setBiometricLockedOutMessage(value: BouncerMessageModel?) {
+        _biometricLockedOutMessage.value = value
+    }
+
+    override fun clearMessage() {
+        _primaryAuthMessage.value = null
+        _faceAcquisitionMessage.value = null
+        _fingerprintAcquisitionMessage.value = null
+        _customMessage.value = null
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
new file mode 100644
index 0000000..10529e6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
@@ -0,0 +1,97 @@
+package com.android.systemui.bouncer.data.repository
+
+import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [KeyguardRepository] */
+class FakeKeyguardBouncerRepository : KeyguardBouncerRepository {
+    private val _primaryBouncerShow = MutableStateFlow(false)
+    override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
+    private val _primaryBouncerShowingSoon = MutableStateFlow(false)
+    override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
+    private val _primaryBouncerStartingToHide = MutableStateFlow(false)
+    override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
+    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
+    override val primaryBouncerStartingDisappearAnimation =
+        _primaryBouncerDisappearAnimation.asStateFlow()
+    private val _primaryBouncerScrimmed = MutableStateFlow(false)
+    override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
+    private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncerConstants.EXPANSION_HIDDEN)
+    override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
+    private val _keyguardPosition = MutableStateFlow(0f)
+    override val keyguardPosition = _keyguardPosition.asStateFlow()
+    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
+    override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
+    private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
+    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
+    override val showMessage = _showMessage.asStateFlow()
+    private val _resourceUpdateRequests = MutableStateFlow(false)
+    override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+    private val _isAlternateBouncerVisible = MutableStateFlow(false)
+    override val alternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
+    override var lastAlternateBouncerVisibleTime: Long = 0L
+    private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
+    override val alternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow()
+    private val _sideFpsShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val sideFpsShowing: StateFlow<Boolean> = _sideFpsShowing.asStateFlow()
+
+    override fun setPrimaryScrimmed(isScrimmed: Boolean) {
+        _primaryBouncerScrimmed.value = isScrimmed
+    }
+
+    override fun setAlternateVisible(isVisible: Boolean) {
+        _isAlternateBouncerVisible.value = isVisible
+    }
+
+    override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+        _isAlternateBouncerUIAvailable.value = isAvailable
+    }
+
+    override fun setPrimaryShow(isShowing: Boolean) {
+        _primaryBouncerShow.value = isShowing
+    }
+
+    override fun setPrimaryShowingSoon(showingSoon: Boolean) {
+        _primaryBouncerShowingSoon.value = showingSoon
+    }
+
+    override fun setPrimaryStartingToHide(startingToHide: Boolean) {
+        _primaryBouncerStartingToHide.value = startingToHide
+    }
+
+    override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
+        _primaryBouncerDisappearAnimation.value = runnable
+    }
+
+    override fun setPanelExpansion(panelExpansion: Float) {
+        _panelExpansionAmount.value = panelExpansion
+    }
+
+    override fun setKeyguardPosition(keyguardPosition: Float) {
+        _keyguardPosition.value = keyguardPosition
+    }
+
+    override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
+        _resourceUpdateRequests.value = willUpdateResources
+    }
+
+    override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
+        _showMessage.value = bouncerShowMessageModel
+    }
+
+    override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+        _keyguardAuthenticated.value = keyguardAuthenticated
+    }
+
+    override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
+        _isBackButtonEnabled.value = isBackButtonEnabled
+    }
+
+    override fun setSideFpsShowing(isShowing: Boolean) {
+        _sideFpsShowing.value = isShowing
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index f19e191..21a5eb7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -36,9 +36,9 @@
  * A fake instance of [BroadcastDispatcher] for tests.
  *
  * Important: The *real* broadcast dispatcher will only send intents to receivers if the intent
- * matches the [IntentFilter] that the [BroadcastReceiver] was registered with. This fake class does
- * *not* do that matching by default. Use [sendIntentToMatchingReceiversOnly] to get the same
- * matching behavior as the real broadcast dispatcher.
+ * matches the [IntentFilter] that the [BroadcastReceiver] was registered with. This fake class
+ * exposes [sendIntentToMatchingReceiversOnly] to get the same matching behavior as the real
+ * broadcast dispatcher.
  */
 class FakeBroadcastDispatcher(
     context: SysuiTestableContext,
@@ -63,9 +63,6 @@
 
     private val receivers: MutableSet<InternalReceiver> = ConcurrentHashMap.newKeySet()
 
-    val registeredReceivers: Set<BroadcastReceiver>
-        get() = receivers.map { it.receiver }.toSet()
-
     override fun registerReceiverWithHandler(
         receiver: BroadcastReceiver,
         filter: IntentFilter,
@@ -115,11 +112,15 @@
         }
     }
 
+    val numReceiversRegistered: Int
+        get() = receivers.size
+
     fun cleanUpReceivers(testName: String) {
-        registeredReceivers.forEach {
-            Log.i(testName, "Receiver not unregistered from dispatcher: $it")
+        receivers.forEach {
+            val receiver = it.receiver
+            Log.i(testName, "Receiver not unregistered from dispatcher: $receiver")
             if (shouldFailOnLeakedReceiver) {
-                throw IllegalStateException("Receiver not unregistered from dispatcher: $it")
+                throw IllegalStateException("Receiver not unregistered from dispatcher: $receiver")
             }
         }
         receivers.clear()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
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 b94f816e..013dbb4 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
@@ -19,51 +19,77 @@
 import java.io.PrintWriter
 
 class FakeFeatureFlags : FeatureFlags {
-    private val booleanFlags = mutableMapOf<Int, Boolean>()
-    private val stringFlags = mutableMapOf<Int, String>()
-    private val intFlags = mutableMapOf<Int, Int>()
-    private val knownFlagNames = mutableMapOf<Int, String>()
-    private val flagListeners = mutableMapOf<Int, MutableSet<FlagListenable.Listener>>()
-    private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>()
+    private val booleanFlags = mutableMapOf<String, Boolean>()
+    private val stringFlags = mutableMapOf<String, String>()
+    private val intFlags = mutableMapOf<String, Int>()
+    private val knownFlagNames = mutableMapOf<String, String>()
+    private val flagListeners = mutableMapOf<String, MutableSet<FlagListenable.Listener>>()
+    private val listenerflagNames = mutableMapOf<FlagListenable.Listener, MutableSet<String>>()
 
     init {
         FlagsFactory.knownFlags.forEach { entry: Map.Entry<String, Flag<*>> ->
-            knownFlagNames[entry.value.id] = entry.key
+            knownFlagNames[entry.value.name] = entry.key
         }
     }
 
     fun set(flag: BooleanFlag, value: Boolean) {
-        if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
+        if (booleanFlags.put(flag.name, value)?.let { value != it } != false) {
             notifyFlagChanged(flag)
         }
     }
 
     fun set(flag: ResourceBooleanFlag, value: Boolean) {
-        if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
+        if (booleanFlags.put(flag.name, value)?.let { value != it } != false) {
             notifyFlagChanged(flag)
         }
     }
 
     fun set(flag: SysPropBooleanFlag, value: Boolean) {
-        if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
+        if (booleanFlags.put(flag.name, value)?.let { value != it } != false) {
             notifyFlagChanged(flag)
         }
     }
 
     fun set(flag: StringFlag, value: String) {
-        if (stringFlags.put(flag.id, value)?.let { value != it } == null) {
+        if (stringFlags.put(flag.name, value)?.let { value != it } == null) {
             notifyFlagChanged(flag)
         }
     }
 
     fun set(flag: ResourceStringFlag, value: String) {
-        if (stringFlags.put(flag.id, value)?.let { value != it } == null) {
+        if (stringFlags.put(flag.name, value)?.let { value != it } == null) {
             notifyFlagChanged(flag)
         }
     }
 
+    /**
+     * Set the given flag's default value if no other value has been set.
+     *
+     * REMINDER: You should always test your code with your flag in both configurations, so
+     *  generally you should be setting a particular value.  This method should be reserved for
+     *  situations where the flag needs to be read (e.g. in the class constructor), but its
+     *  value shouldn't affect the actual test cases. In those cases, it's mildly safer to use
+     *  this method than to hard-code `false` or `true` because then at least if you're wrong,
+     *  and the flag value *does* matter, you'll notice when the flag is flipped and tests
+     *  start failing.
+     */
+    fun setDefault(flag: BooleanFlag) = booleanFlags.putIfAbsent(flag.name, flag.default)
+
+    /**
+     * Set the given flag's default value if no other value has been set.
+     *
+     * REMINDER: You should always test your code with your flag in both configurations, so
+     *  generally you should be setting a particular value.  This method should be reserved for
+     *  situations where the flag needs to be read (e.g. in the class constructor), but its
+     *  value shouldn't affect the actual test cases. In those cases, it's mildly safer to use
+     *  this method than to hard-code `false` or `true` because then at least if you're wrong,
+     *  and the flag value *does* matter, you'll notice when the flag is flipped and tests
+     *  start failing.
+     */
+    fun setDefault(flag: SysPropBooleanFlag) = booleanFlags.putIfAbsent(flag.name, flag.default)
+
     private fun notifyFlagChanged(flag: Flag<*>) {
-        flagListeners[flag.id]?.let { listeners ->
+        flagListeners[flag.name]?.let { listeners ->
             listeners.forEach { listener ->
                 listener.onFlagChanged(
                     object : FlagListenable.FlagEvent {
@@ -75,30 +101,30 @@
         }
     }
 
-    override fun isEnabled(flag: UnreleasedFlag): Boolean = requireBooleanValue(flag.id)
+    override fun isEnabled(flag: UnreleasedFlag): Boolean = requireBooleanValue(flag.name)
 
-    override fun isEnabled(flag: ReleasedFlag): Boolean = requireBooleanValue(flag.id)
+    override fun isEnabled(flag: ReleasedFlag): Boolean = requireBooleanValue(flag.name)
 
-    override fun isEnabled(flag: ResourceBooleanFlag): Boolean = requireBooleanValue(flag.id)
+    override fun isEnabled(flag: ResourceBooleanFlag): Boolean = requireBooleanValue(flag.name)
 
-    override fun isEnabled(flag: SysPropBooleanFlag): Boolean = requireBooleanValue(flag.id)
+    override fun isEnabled(flag: SysPropBooleanFlag): Boolean = requireBooleanValue(flag.name)
 
-    override fun getString(flag: StringFlag): String = requireStringValue(flag.id)
+    override fun getString(flag: StringFlag): String = requireStringValue(flag.name)
 
-    override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.id)
+    override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.name)
 
-    override fun getInt(flag: IntFlag): Int = requireIntValue(flag.id)
+    override fun getInt(flag: IntFlag): Int = requireIntValue(flag.name)
 
-    override fun getInt(flag: ResourceIntFlag): Int = requireIntValue(flag.id)
+    override fun getInt(flag: ResourceIntFlag): Int = requireIntValue(flag.name)
 
     override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {
-        flagListeners.getOrPut(flag.id) { mutableSetOf() }.add(listener)
-        listenerFlagIds.getOrPut(listener) { mutableSetOf() }.add(flag.id)
+        flagListeners.getOrPut(flag.name) { mutableSetOf() }.add(listener)
+        listenerflagNames.getOrPut(listener) { mutableSetOf() }.add(flag.name)
     }
 
     override fun removeListener(listener: FlagListenable.Listener) {
-        listenerFlagIds.remove(listener)?.let {
-                flagIds -> flagIds.forEach {
+        listenerflagNames.remove(listener)?.let {
+                flagNames -> flagNames.forEach {
                         id -> flagListeners[id]?.remove(listener)
                 }
         }
@@ -108,22 +134,22 @@
         // no-op
     }
 
-    private fun flagName(flagId: Int): String {
-        return knownFlagNames[flagId] ?: "UNKNOWN(id=$flagId)"
+    private fun flagName(flagName: String): String {
+        return knownFlagNames[flagName] ?: "UNKNOWN($flagName)"
     }
 
-    private fun requireBooleanValue(flagId: Int): Boolean {
-        return booleanFlags[flagId]
-            ?: error("Flag ${flagName(flagId)} was accessed as boolean but not specified.")
+    private fun requireBooleanValue(flagName: String): Boolean {
+        return booleanFlags[flagName]
+            ?: error("Flag ${flagName(flagName)} was accessed as boolean but not specified.")
     }
 
-    private fun requireStringValue(flagId: Int): String {
-        return stringFlags[flagId]
-            ?: error("Flag ${flagName(flagId)} was accessed as string but not specified.")
+    private fun requireStringValue(flagName: String): String {
+        return stringFlags[flagName]
+            ?: error("Flag ${flagName(flagName)} was accessed as string but not specified.")
     }
 
-    private fun requireIntValue(flagId: Int): Int {
-        return intFlags[flagId]
-            ?: error("Flag ${flagName(flagId)} was accessed as int but not specified.")
+    private fun requireIntValue(flagName: String): Int {
+        return intFlags[flagName]
+            ?: error("Flag ${flagName(flagName)} was accessed as int but not specified.")
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index 4aaf347..e91e955 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -22,32 +22,24 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
 
 class FakeBiometricSettingsRepository : BiometricSettingsRepository {
+    private val _isFingerprintEnrolledAndEnabled = MutableStateFlow(false)
+    override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean>
+        get() = _isFingerprintEnrolledAndEnabled
 
-    private val _isFingerprintEnrolled = MutableStateFlow<Boolean>(false)
-    override val isFingerprintEnrolled: StateFlow<Boolean> = _isFingerprintEnrolled.asStateFlow()
+    private val _isFingerprintAuthCurrentlyAllowed = MutableStateFlow(false)
+    override val isFingerprintAuthCurrentlyAllowed: StateFlow<Boolean>
+        get() = _isFingerprintAuthCurrentlyAllowed
 
-    private val _isFaceEnrolled = MutableStateFlow(false)
-    override val isFaceEnrolled: Flow<Boolean>
-        get() = _isFaceEnrolled
+    private val _isFaceAuthEnrolledAndEnabled = MutableStateFlow(false)
+    override val isFaceAuthEnrolledAndEnabled: Flow<Boolean>
+        get() = _isFaceAuthEnrolledAndEnabled
 
-    private val _isFaceAuthEnabled = MutableStateFlow(false)
-    override val isFaceAuthenticationEnabled: Flow<Boolean>
-        get() = _isFaceAuthEnabled
-
-    private val _isStrongBiometricAllowed = MutableStateFlow(false)
-    override val isStrongBiometricAllowed = _isStrongBiometricAllowed.asStateFlow()
-
-    private val _isNonStrongBiometricAllowed = MutableStateFlow(false)
-    override val isNonStrongBiometricAllowed: StateFlow<Boolean>
-        get() = _isNonStrongBiometricAllowed
-
-    private val _isFingerprintEnabledByDevicePolicy = MutableStateFlow(false)
-    override val isFingerprintEnabledByDevicePolicy =
-        _isFingerprintEnabledByDevicePolicy.asStateFlow()
+    private val _isFaceAuthCurrentlyAllowed = MutableStateFlow(false)
+    override val isFaceAuthCurrentlyAllowed: Flow<Boolean>
+        get() = _isFaceAuthCurrentlyAllowed
 
     private val _isFaceAuthSupportedInCurrentPosture = MutableStateFlow(false)
     override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
@@ -60,34 +52,32 @@
     override val authenticationFlags: Flow<AuthenticationFlags>
         get() = _authFlags
 
-    fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) {
-        _isFingerprintEnrolled.value = isFingerprintEnrolled
-    }
-
-    fun setStrongBiometricAllowed(isStrongBiometricAllowed: Boolean) {
-        _isStrongBiometricAllowed.value = isStrongBiometricAllowed
-    }
-
-    fun setFingerprintEnabledByDevicePolicy(isFingerprintEnabledByDevicePolicy: Boolean) {
-        _isFingerprintEnabledByDevicePolicy.value = isFingerprintEnabledByDevicePolicy
-    }
-
     fun setAuthenticationFlags(value: AuthenticationFlags) {
         _authFlags.value = value
     }
 
-    fun setFaceEnrolled(isFaceEnrolled: Boolean) {
-        _isFaceEnrolled.value = isFaceEnrolled
+    fun setIsFingerprintAuthEnrolledAndEnabled(value: Boolean) {
+        _isFingerprintEnrolledAndEnabled.value = value
+        _isFingerprintAuthCurrentlyAllowed.value = _isFingerprintAuthCurrentlyAllowed.value && value
+    }
+
+    fun setIsFingerprintAuthCurrentlyAllowed(value: Boolean) {
+        _isFingerprintAuthCurrentlyAllowed.value = value
+    }
+
+    fun setIsFaceAuthEnrolledAndEnabled(value: Boolean) {
+        _isFaceAuthEnrolledAndEnabled.value = value
+        _isFaceAuthCurrentlyAllowed.value = _isFaceAuthCurrentlyAllowed.value && value
+    }
+
+    fun setIsFaceAuthCurrentlyAllowed(value: Boolean) {
+        _isFaceAuthCurrentlyAllowed.value = value
     }
 
     fun setIsFaceAuthSupportedInCurrentPosture(value: Boolean) {
         _isFaceAuthSupportedInCurrentPosture.value = value
     }
 
-    fun setIsFaceAuthEnabled(enabled: Boolean) {
-        _isFaceAuthEnabled.value = enabled
-    }
-
     fun setIsUserInLockdown(value: Boolean) {
         if (value) {
             setAuthenticationFlags(
@@ -106,8 +96,4 @@
             )
         }
     }
-
-    fun setIsNonStrongBiometricAllowed(value: Boolean) {
-        _isNonStrongBiometricAllowed.value = value
-    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBouncerMessageRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBouncerMessageRepository.kt
deleted file mode 100644
index b03b4ba..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBouncerMessageRepository.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.data.repository
-
-import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
-import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-
-class FakeBouncerMessageRepository : BouncerMessageRepository {
-    private val _primaryAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val primaryAuthMessage: StateFlow<BouncerMessageModel?>
-        get() = _primaryAuthMessage
-
-    private val _faceAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val faceAcquisitionMessage: StateFlow<BouncerMessageModel?>
-        get() = _faceAcquisitionMessage
-    private val _fingerprintAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val fingerprintAcquisitionMessage: StateFlow<BouncerMessageModel?>
-        get() = _fingerprintAcquisitionMessage
-    private val _customMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val customMessage: StateFlow<BouncerMessageModel?>
-        get() = _customMessage
-    private val _biometricAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val biometricAuthMessage: StateFlow<BouncerMessageModel?>
-        get() = _biometricAuthMessage
-    private val _authFlagsMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val authFlagsMessage: StateFlow<BouncerMessageModel?>
-        get() = _authFlagsMessage
-
-    private val _biometricLockedOutMessage = MutableStateFlow<BouncerMessageModel?>(null)
-    override val biometricLockedOutMessage: Flow<BouncerMessageModel?>
-        get() = _biometricLockedOutMessage
-
-    override fun setPrimaryAuthMessage(value: BouncerMessageModel?) {
-        _primaryAuthMessage.value = value
-    }
-
-    override fun setFaceAcquisitionMessage(value: BouncerMessageModel?) {
-        _faceAcquisitionMessage.value = value
-    }
-
-    override fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?) {
-        _fingerprintAcquisitionMessage.value = value
-    }
-
-    override fun setCustomMessage(value: BouncerMessageModel?) {
-        _customMessage.value = value
-    }
-
-    fun setBiometricAuthMessage(value: BouncerMessageModel?) {
-        _biometricAuthMessage.value = value
-    }
-
-    fun setAuthFlagsMessage(value: BouncerMessageModel?) {
-        _authFlagsMessage.value = value
-    }
-
-    fun setBiometricLockedOutMessage(value: BouncerMessageModel?) {
-        _biometricLockedOutMessage.value = value
-    }
-
-    override fun clearMessage() {
-        _primaryAuthMessage.value = null
-        _faceAcquisitionMessage.value = null
-        _fingerprintAcquisitionMessage.value = null
-        _customMessage.value = null
-    }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index 738f09d..1e1dc4f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.keyguard.data.repository
 
 import com.android.keyguard.FaceAuthUiEvent
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -27,21 +27,28 @@
 
 class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
 
+    private var _wasDisabled: Boolean = false
+
+    val wasDisabled: Boolean
+        get() = _wasDisabled
+
     override val isAuthenticated = MutableStateFlow(false)
     override val canRunFaceAuth = MutableStateFlow(false)
-    private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null)
-    override val authenticationStatus: Flow<AuthenticationStatus> =
+    private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null)
+    override val authenticationStatus: Flow<FaceAuthenticationStatus> =
         _authenticationStatus.filterNotNull()
-    fun setAuthenticationStatus(status: AuthenticationStatus) {
+    fun setAuthenticationStatus(status: FaceAuthenticationStatus) {
         _authenticationStatus.value = status
     }
-    private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
-    override val detectionStatus: Flow<DetectionStatus>
+    private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null)
+    override val detectionStatus: Flow<FaceDetectionStatus>
         get() = _detectionStatus.filterNotNull()
-    fun setDetectionStatus(status: DetectionStatus) {
+    fun setDetectionStatus(status: FaceDetectionStatus) {
         _detectionStatus.value = status
     }
-    override val isLockedOut = MutableStateFlow(false)
+
+    private val _isLockedOut = MutableStateFlow(false)
+    override val isLockedOut = _isLockedOut
     private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
     val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
         _runningAuthRequest.asStateFlow()
@@ -50,12 +57,32 @@
     override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning
 
     override val isBypassEnabled = MutableStateFlow(false)
+    override fun lockoutFaceAuth() {
+        _wasDisabled = true
+    }
+
+    private val faceAuthPaused = MutableStateFlow(false)
+    override fun pauseFaceAuth() {
+        faceAuthPaused.value = true
+    }
+
+    override fun resumeFaceAuth() {
+        faceAuthPaused.value = false
+    }
+
+    fun isFaceAuthPaused(): Boolean {
+        return faceAuthPaused.value
+    }
 
     override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
         _runningAuthRequest.value = uiEvent to fallbackToDetection
         _isAuthRunning.value = true
     }
 
+    fun setLockedOut(value: Boolean) {
+        _isLockedOut.value = value
+    }
+
     override fun cancel() {
         _isAuthRunning.value = false
         _runningAuthRequest.value = null
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
index 4bfd3d6..38791ca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -17,32 +17,38 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
 
 class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository {
     private val _isLockedOut = MutableStateFlow(false)
     override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow()
-
-    private val _isRunning = MutableStateFlow(false)
-    override val isRunning: Flow<Boolean>
-        get() = _isRunning
-
-    private var fpSensorType = MutableStateFlow<BiometricType?>(null)
-    override val availableFpSensorType: Flow<BiometricType?>
-        get() = fpSensorType
-
     fun setLockedOut(lockedOut: Boolean) {
         _isLockedOut.value = lockedOut
     }
 
+    private val _isRunning = MutableStateFlow(false)
+    override val isRunning: Flow<Boolean>
+        get() = _isRunning
     fun setIsRunning(value: Boolean) {
         _isRunning.value = value
     }
 
+    private var fpSensorType = MutableStateFlow<BiometricType?>(null)
+    override val availableFpSensorType: Flow<BiometricType?>
+        get() = fpSensorType
     fun setAvailableFpSensorType(value: BiometricType?) {
         fpSensorType.value = value
     }
+
+    private var _authenticationStatus = MutableStateFlow<FingerprintAuthenticationStatus?>(null)
+    override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
+        get() = _authenticationStatus.filterNotNull()
+    fun setAuthenticationStatus(status: FingerprintAuthenticationStatus) {
+        _authenticationStatus.value = status
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
deleted file mode 100644
index 8a6d2aa..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
+++ /dev/null
@@ -1,114 +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.keyguard.data.repository
-
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
-import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Fake implementation of [KeyguardRepository] */
-class FakeKeyguardBouncerRepository : KeyguardBouncerRepository {
-    private val _primaryBouncerShow = MutableStateFlow(false)
-    override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
-    private val _primaryBouncerShowingSoon = MutableStateFlow(false)
-    override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
-    private val _primaryBouncerStartingToHide = MutableStateFlow(false)
-    override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
-    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
-    override val primaryBouncerStartingDisappearAnimation =
-        _primaryBouncerDisappearAnimation.asStateFlow()
-    private val _primaryBouncerScrimmed = MutableStateFlow(false)
-    override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
-    private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
-    override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
-    private val _keyguardPosition = MutableStateFlow(0f)
-    override val keyguardPosition = _keyguardPosition.asStateFlow()
-    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
-    override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
-    private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
-    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
-    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
-    override val showMessage = _showMessage.asStateFlow()
-    private val _resourceUpdateRequests = MutableStateFlow(false)
-    override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
-    private val _isAlternateBouncerVisible = MutableStateFlow(false)
-    override val alternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
-    override var lastAlternateBouncerVisibleTime: Long = 0L
-    private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
-    override val alternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow()
-    private val _sideFpsShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
-    override val sideFpsShowing: StateFlow<Boolean> = _sideFpsShowing.asStateFlow()
-
-    override fun setPrimaryScrimmed(isScrimmed: Boolean) {
-        _primaryBouncerScrimmed.value = isScrimmed
-    }
-
-    override fun setAlternateVisible(isVisible: Boolean) {
-        _isAlternateBouncerVisible.value = isVisible
-    }
-
-    override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
-        _isAlternateBouncerUIAvailable.value = isAvailable
-    }
-
-    override fun setPrimaryShow(isShowing: Boolean) {
-        _primaryBouncerShow.value = isShowing
-    }
-
-    override fun setPrimaryShowingSoon(showingSoon: Boolean) {
-        _primaryBouncerShowingSoon.value = showingSoon
-    }
-
-    override fun setPrimaryStartingToHide(startingToHide: Boolean) {
-        _primaryBouncerStartingToHide.value = startingToHide
-    }
-
-    override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
-        _primaryBouncerDisappearAnimation.value = runnable
-    }
-
-    override fun setPanelExpansion(panelExpansion: Float) {
-        _panelExpansionAmount.value = panelExpansion
-    }
-
-    override fun setKeyguardPosition(keyguardPosition: Float) {
-        _keyguardPosition.value = keyguardPosition
-    }
-
-    override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
-        _resourceUpdateRequests.value = willUpdateResources
-    }
-
-    override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
-        _showMessage.value = bouncerShowMessageModel
-    }
-
-    override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
-        _keyguardAuthenticated.value = keyguardAuthenticated
-    }
-
-    override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
-        _isBackButtonEnabled.value = isBackButtonEnabled
-    }
-
-    override fun setSideFpsShowing(isShowing: Boolean) {
-        _sideFpsShowing.value = isShowing
-    }
-}
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 b52a768..faebcaa 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
@@ -22,6 +22,9 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.keyguard.shared.model.ScreenModel
+import com.android.systemui.keyguard.shared.model.ScreenState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
@@ -48,7 +51,7 @@
     override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
 
     private val _isKeyguardUnlocked = MutableStateFlow(false)
-    override val isKeyguardUnlocked: Flow<Boolean> = _isKeyguardUnlocked
+    override val isKeyguardUnlocked: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
 
     private val _isKeyguardOccluded = MutableStateFlow(false)
     override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
@@ -56,6 +59,9 @@
     private val _isDozing = MutableStateFlow(false)
     override val isDozing: StateFlow<Boolean> = _isDozing
 
+    private val _dozeTimeTick = MutableStateFlow<Long>(0L)
+    override val dozeTimeTick = _dozeTimeTick
+
     private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
     override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
 
@@ -68,6 +74,9 @@
     private val _isDreamingWithOverlay = MutableStateFlow(false)
     override val isDreamingWithOverlay: Flow<Boolean> = _isDreamingWithOverlay
 
+    private val _isActiveDreamLockscreenHosted = MutableStateFlow(false)
+    override val isActiveDreamLockscreenHosted: StateFlow<Boolean> = _isActiveDreamLockscreenHosted
+
     private val _dozeAmount = MutableStateFlow(0f)
     override val linearDozeAmount: Flow<Float> = _dozeAmount
 
@@ -81,7 +90,10 @@
         MutableStateFlow(
             WakefulnessModel(WakefulnessState.ASLEEP, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
         )
-    override val wakefulness: Flow<WakefulnessModel> = _wakefulnessModel
+    override val wakefulness = _wakefulnessModel
+
+    private val _screenModel = MutableStateFlow(ScreenModel(ScreenState.SCREEN_OFF))
+    override val screenModel = _screenModel
 
     private val _isUdfpsSupported = MutableStateFlow(false)
 
@@ -103,6 +115,20 @@
     private val _isQuickSettingsVisible = MutableStateFlow(false)
     override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow()
 
+    private val _keyguardAlpha = MutableStateFlow(1f)
+    override val keyguardAlpha: StateFlow<Float> = _keyguardAlpha
+
+    private val _keyguardRootViewVisibility =
+        MutableStateFlow(
+            KeyguardRootViewVisibilityState(
+                0,
+                goingToFullShade = false,
+                occlusionTransitionRunning = false
+            )
+        )
+    override val keyguardRootViewVisibility: Flow<KeyguardRootViewVisibilityState> =
+        _keyguardRootViewVisibility.asStateFlow()
+
     override fun setQuickSettingsVisible(isVisible: Boolean) {
         _isQuickSettingsVisible.value = isVisible
     }
@@ -111,10 +137,16 @@
         return _isKeyguardShowing.value
     }
 
+    private var _isBypassEnabled = false
+    override fun isBypassEnabled(): Boolean {
+        return _isBypassEnabled
+    }
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.tryEmit(animate)
     }
 
+    @Deprecated("Deprecated as part of b/278057014")
     override fun setBottomAreaAlpha(alpha: Float) {
         _bottomAreaAlpha.value = alpha
     }
@@ -139,6 +171,14 @@
         _isDozing.value = isDozing
     }
 
+    override fun dozeTimeTick() {
+        _dozeTimeTick.value = _dozeTimeTick.value + 1
+    }
+
+    fun dozeTimeTick(millis: Long) {
+        _dozeTimeTick.value = millis
+    }
+
     override fun setLastDozeTapToWakePosition(position: Point) {
         _lastDozeTapToWakePosition.value = position
     }
@@ -155,6 +195,10 @@
         _isDreamingWithOverlay.value = isDreaming
     }
 
+    override fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean) {
+        _isActiveDreamLockscreenHosted.value = isLockscreenHosted
+    }
+
     fun setDozeAmount(dozeAmount: Float) {
         _dozeAmount.value = dozeAmount
     }
@@ -187,7 +231,36 @@
         _statusBarState.value = state
     }
 
+    fun setKeyguardUnlocked(isUnlocked: Boolean) {
+        _isKeyguardUnlocked.value = isUnlocked
+    }
+
+    fun setBypassEnabled(isEnabled: Boolean) {
+        _isBypassEnabled = isEnabled
+    }
+
+    fun setScreenModel(screenModel: ScreenModel) {
+        _screenModel.value = screenModel
+    }
+
     override fun isUdfpsSupported(): Boolean {
         return _isUdfpsSupported.value
     }
+
+    override fun setKeyguardAlpha(alpha: Float) {
+        _keyguardAlpha.value = alpha
+    }
+
+    override fun setKeyguardVisibility(
+        statusBarState: Int,
+        goingToFullShade: Boolean,
+        occlusionTransitionRunning: Boolean
+    ) {
+        _keyguardRootViewVisibility.value =
+            KeyguardRootViewVisibilityState(
+                statusBarState,
+                goingToFullShade,
+                occlusionTransitionRunning
+            )
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt
new file mode 100644
index 0000000..823f29a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeKeyguardSurfaceBehindRepository : KeyguardSurfaceBehindRepository {
+    private val _isAnimatingSurface = MutableStateFlow(false)
+    override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
+
+    override fun setAnimatingSurface(animating: Boolean) {
+        _isAnimatingSurface.value = animating
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
index 7c22604..b24b95e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.data.repository
 
 import com.android.systemui.statusbar.LightRevealEffect
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 /** Fake implementation of [LightRevealScrimRepository] */
@@ -30,4 +31,15 @@
     fun setRevealEffect(effect: LightRevealEffect) {
         _revealEffect.tryEmit(effect)
     }
+
+    private val _revealAmount: MutableStateFlow<Float> = MutableStateFlow(0.0f)
+    override val revealAmount: Flow<Float> = _revealAmount
+
+    override fun startRevealAmountAnimator(reveal: Boolean) {
+        if (reveal) {
+            _revealAmount.value = 1.0f
+        } else {
+            _revealAmount.value = 0.0f
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
new file mode 100644
index 0000000..f13c98d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+
+/**
+ * Simply put, I got tired of adding a constructor argument and then having to tweak dozens of
+ * files. This should alleviate some of the burden by providing defaults for testing.
+ */
+object KeyguardInteractorFactory {
+
+    @JvmOverloads
+    @JvmStatic
+    fun create(
+        featureFlags: FakeFeatureFlags = createFakeFeatureFlags(),
+        repository: FakeKeyguardRepository = FakeKeyguardRepository(),
+        commandQueue: FakeCommandQueue = FakeCommandQueue(),
+        bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
+        configurationRepository: FakeConfigurationRepository = FakeConfigurationRepository(),
+    ): WithDependencies {
+        return WithDependencies(
+            repository = repository,
+            commandQueue = commandQueue,
+            featureFlags = featureFlags,
+            bouncerRepository = bouncerRepository,
+            configurationRepository = configurationRepository,
+            KeyguardInteractor(
+                repository = repository,
+                commandQueue = commandQueue,
+                featureFlags = featureFlags,
+                bouncerRepository = bouncerRepository,
+                configurationRepository = configurationRepository,
+            )
+        )
+    }
+
+    /** Provide defaults, otherwise tests will throw an error */
+    fun createFakeFeatureFlags(): FakeFeatureFlags {
+        return FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) }
+    }
+
+    data class WithDependencies(
+        val repository: FakeKeyguardRepository,
+        val commandQueue: FakeCommandQueue,
+        val featureFlags: FakeFeatureFlags,
+        val bouncerRepository: FakeKeyguardBouncerRepository,
+        val configurationRepository: FakeConfigurationRepository,
+        val keyguardInteractor: KeyguardInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
new file mode 100644
index 0000000..23faaf3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Helper to create a new KeyguardTransitionInteractor in a way that doesn't require modifying 20+
+ * tests whenever we add a constructor param.
+ */
+object KeyguardTransitionInteractorFactory {
+    @JvmOverloads
+    @JvmStatic
+    fun create(
+        scope: CoroutineScope,
+        repository: KeyguardTransitionRepository = FakeKeyguardTransitionRepository(),
+        keyguardInteractor: KeyguardInteractor =
+            KeyguardInteractorFactory.create().keyguardInteractor,
+        fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor> = Lazy {
+            mock<FromLockscreenTransitionInteractor>()
+        },
+        fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor> =
+            Lazy {
+                mock<FromPrimaryBouncerTransitionInteractor>()
+            },
+    ): WithDependencies {
+        return WithDependencies(
+            repository = repository,
+            keyguardInteractor = keyguardInteractor,
+            fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+            fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
+            KeyguardTransitionInteractor(
+                scope = scope,
+                repository = repository,
+                keyguardInteractor = { keyguardInteractor },
+                fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+                fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
+            )
+        )
+    }
+
+    data class WithDependencies(
+        val repository: KeyguardTransitionRepository,
+        val keyguardInteractor: KeyguardInteractor,
+        val fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor>,
+        val fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor>,
+        val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
index 15465f4..3334f3e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.power.data.repository
 
+import android.os.PowerManager
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -28,7 +29,15 @@
     private val _isInteractive = MutableStateFlow(initialInteractive)
     override val isInteractive: Flow<Boolean> = _isInteractive.asStateFlow()
 
+    var lastWakeWhy: String? = null
+    var lastWakeReason: Int? = null
+
     fun setInteractive(value: Boolean) {
         _isInteractive.value = value
     }
+
+    override fun wakeUp(why: String, @PowerManager.WakeReason wakeReason: Int) {
+        lastWakeWhy = why
+        lastWakeReason = wakeReason
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt
new file mode 100644
index 0000000..9ea079f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeAutoAddRepository : AutoAddRepository {
+
+    private val autoAddedTilesPerUser = mutableMapOf<Int, MutableStateFlow<Set<TileSpec>>>()
+
+    override fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> {
+        return getFlow(userId)
+    }
+
+    override suspend fun markTileAdded(userId: Int, spec: TileSpec) {
+        if (spec == TileSpec.Invalid) return
+        with(getFlow(userId)) { value = value.toMutableSet().apply { add(spec) } }
+    }
+
+    override suspend fun unmarkTileAdded(userId: Int, spec: TileSpec) {
+        with(getFlow(userId)) { value = value.toMutableSet().apply { remove(spec) } }
+    }
+
+    private fun getFlow(userId: Int): MutableStateFlow<Set<TileSpec>> =
+        autoAddedTilesPerUser.getOrPut(userId) { MutableStateFlow(emptySet()) }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt
new file mode 100644
index 0000000..ebdd6fd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+
+class FakeAutoAddable(
+    private val spec: TileSpec,
+    override val autoAddTracking: AutoAddTracking,
+) : AutoAddable {
+
+    private val signalsPerUser = mutableMapOf<Int, MutableStateFlow<AutoAddSignal?>>()
+    private fun getFlow(userId: Int): MutableStateFlow<AutoAddSignal?> =
+        signalsPerUser.getOrPut(userId) { MutableStateFlow(null) }
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return getFlow(userId).asStateFlow().filterNotNull()
+    }
+
+    suspend fun sendRemoveSignal(userId: Int) {
+        getFlow(userId).value = AutoAddSignal.Remove(spec)
+    }
+
+    suspend fun sendAddSignal(userId: Int, position: Int = POSITION_AT_END) {
+        getFlow(userId).value = AutoAddSignal.Add(spec, position)
+    }
+
+    override val description: String
+        get() = "FakeAutoAddable($spec)"
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index be3d54a..f0e1111 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -16,21 +16,41 @@
 
 package com.android.systemui.scene
 
+import android.content.pm.UserInfo
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
-import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.bouncer.data.repo.BouncerRepository
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
+import com.android.systemui.bouncer.data.repository.BouncerRepository
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
-import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
-import com.android.systemui.scene.data.model.SceneContainerConfig
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.scene.data.repository.SceneContainerRepository
 import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.currentTime
 
 /**
  * Utilities for creating scene container framework related repositories, interactors, and
@@ -39,19 +59,45 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class SceneTestUtils(
     test: SysuiTestCase,
-    private val testScope: TestScope? = null,
 ) {
+    val testDispatcher = StandardTestDispatcher()
+    val testScope = TestScope(testDispatcher)
+    val featureFlags =
+        FakeFeatureFlags().apply {
+            set(Flags.SCENE_CONTAINER, true)
+            set(Flags.FACE_AUTH_REFACTOR, false)
+        }
+    private val userRepository: UserRepository by lazy {
+        FakeUserRepository().apply {
+            val users = listOf(UserInfo(/* id=  */ 0, "name", /* flags= */ 0))
+            setUserInfos(users)
+            runBlocking { setSelectedUserInfo(users.first()) }
+        }
+    }
+
+    val authenticationRepository: FakeAuthenticationRepository by lazy {
+        FakeAuthenticationRepository(
+            currentTime = { testScope.currentTime },
+        )
+    }
+    val keyguardRepository: FakeKeyguardRepository by lazy {
+        FakeKeyguardRepository().apply {
+            setWakefulnessModel(
+                WakefulnessModel(
+                    WakefulnessState.AWAKE,
+                    WakeSleepReason.OTHER,
+                    WakeSleepReason.OTHER,
+                )
+            )
+        }
+    }
 
     private val context = test.context
 
     fun fakeSceneContainerRepository(
-        containerConfigurations: Set<SceneContainerConfig> =
-            setOf(
-                fakeSceneContainerConfig(CONTAINER_1),
-                fakeSceneContainerConfig(CONTAINER_2),
-            )
+        containerConfig: SceneContainerConfig = fakeSceneContainerConfig(),
     ): SceneContainerRepository {
-        return SceneContainerRepository(containerConfigurations)
+        return SceneContainerRepository(applicationScope(), containerConfig)
     }
 
     fun fakeSceneKeys(): List<SceneKey> {
@@ -65,44 +111,55 @@
     }
 
     fun fakeSceneContainerConfig(
-        name: String,
         sceneKeys: List<SceneKey> = fakeSceneKeys(),
     ): SceneContainerConfig {
         return SceneContainerConfig(
-            name = name,
             sceneKeys = sceneKeys,
             initialSceneKey = SceneKey.Lockscreen,
         )
     }
 
-    fun sceneInteractor(): SceneInteractor {
+    fun sceneInteractor(
+        repository: SceneContainerRepository = fakeSceneContainerRepository()
+    ): SceneInteractor {
         return SceneInteractor(
-            repository = fakeSceneContainerRepository(),
+            applicationScope = applicationScope(),
+            repository = repository,
+            logger = mock(),
         )
     }
 
-    fun authenticationRepository(): AuthenticationRepository {
-        return AuthenticationRepositoryImpl()
+    fun authenticationRepository(): FakeAuthenticationRepository {
+        return authenticationRepository
     }
 
     fun authenticationInteractor(
         repository: AuthenticationRepository,
+        sceneInteractor: SceneInteractor = sceneInteractor(),
     ): AuthenticationInteractor {
         return AuthenticationInteractor(
             applicationScope = applicationScope(),
             repository = repository,
+            backgroundDispatcher = testDispatcher,
+            userRepository = userRepository,
+            keyguardRepository = keyguardRepository,
+            sceneInteractor = sceneInteractor,
+            clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
         )
     }
 
-    private fun applicationScope(): CoroutineScope {
-        return checkNotNull(testScope) {
-                """
-                TestScope not initialized, please create a TestScope and inject it into
-                SceneTestUtils.
-            """
-                    .trimIndent()
-            }
-            .backgroundScope
+    fun keyguardRepository(): FakeKeyguardRepository {
+        return keyguardRepository
+    }
+
+    fun keyguardInteractor(repository: KeyguardRepository): KeyguardInteractor {
+        return KeyguardInteractor(
+            repository = repository,
+            commandQueue = FakeCommandQueue(),
+            featureFlags = featureFlags,
+            bouncerRepository = FakeKeyguardBouncerRepository(),
+            configurationRepository = FakeConfigurationRepository()
+        )
     }
 
     fun bouncerInteractor(
@@ -115,47 +172,39 @@
             repository = BouncerRepository(),
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
-            containerName = CONTAINER_1,
+            featureFlags = featureFlags,
         )
     }
 
     fun bouncerViewModel(
         bouncerInteractor: BouncerInteractor,
+        authenticationInteractor: AuthenticationInteractor,
     ): BouncerViewModel {
         return BouncerViewModel(
             applicationContext = context,
             applicationScope = applicationScope(),
-            interactorFactory =
-                object : BouncerInteractor.Factory {
-                    override fun create(containerName: String): BouncerInteractor {
-                        return bouncerInteractor
-                    }
-                },
-            containerName = CONTAINER_1,
+            bouncerInteractor = bouncerInteractor,
+            authenticationInteractor = authenticationInteractor,
+            featureFlags = featureFlags,
         )
     }
 
-    fun lockScreenSceneInteractor(
-        authenticationInteractor: AuthenticationInteractor,
-        sceneInteractor: SceneInteractor,
-        bouncerInteractor: BouncerInteractor,
-    ): LockscreenSceneInteractor {
-        return LockscreenSceneInteractor(
-            applicationScope = applicationScope(),
-            authenticationInteractor = authenticationInteractor,
-            bouncerInteractorFactory =
-                object : BouncerInteractor.Factory {
-                    override fun create(containerName: String): BouncerInteractor {
-                        return bouncerInteractor
-                    }
-                },
-            sceneInteractor = sceneInteractor,
-            containerName = CONTAINER_1,
-        )
+    private fun applicationScope(): CoroutineScope {
+        return testScope.backgroundScope
     }
 
     companion object {
-        const val CONTAINER_1 = "container1"
-        const val CONTAINER_2 = "container2"
+        fun DomainLayerAuthenticationMethodModel.toDataLayer(): DataLayerAuthenticationMethodModel {
+            return when (this) {
+                DomainLayerAuthenticationMethodModel.None -> DataLayerAuthenticationMethodModel.None
+                DomainLayerAuthenticationMethodModel.Swipe ->
+                    DataLayerAuthenticationMethodModel.None
+                DomainLayerAuthenticationMethodModel.Pin -> DataLayerAuthenticationMethodModel.Pin
+                DomainLayerAuthenticationMethodModel.Password ->
+                    DataLayerAuthenticationMethodModel.Password
+                DomainLayerAuthenticationMethodModel.Pattern ->
+                    DataLayerAuthenticationMethodModel.Pattern
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 61e5b5f..51ee0c0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -19,18 +19,28 @@
 
 import android.content.pm.UserInfo
 import android.os.UserHandle
+import com.android.systemui.user.data.model.SelectedUserModel
+import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.yield
 
 class FakeUserRepository : UserRepository {
     companion object {
         // User id to represent a non system (human) user id. We presume this is the main user.
         private const val MAIN_USER_ID = 10
+
+        private val DEFAULT_SELECTED_USER = 0
+        private val DEFAULT_SELECTED_USER_INFO =
+            UserInfo(
+                /* id= */ DEFAULT_SELECTED_USER,
+                /* name= */ "default selected user",
+                /* flags= */ 0,
+            )
     }
 
     private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
@@ -40,8 +50,11 @@
     private val _userInfos = MutableStateFlow<List<UserInfo>>(emptyList())
     override val userInfos: Flow<List<UserInfo>> = _userInfos.asStateFlow()
 
-    private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
-    override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
+    override val selectedUser =
+        MutableStateFlow(
+            SelectedUserModel(DEFAULT_SELECTED_USER_INFO, SelectionStatus.SELECTION_COMPLETE)
+        )
+    override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
 
     private val _userSwitchingInProgress = MutableStateFlow(false)
     override val userSwitchingInProgress: Flow<Boolean>
@@ -72,7 +85,7 @@
     }
 
     override fun getSelectedUserInfo(): UserInfo {
-        return checkNotNull(_selectedUserInfo.value)
+        return selectedUser.value.userInfo
     }
 
     override fun isSimpleUserSwitcher(): Boolean {
@@ -87,12 +100,15 @@
         _userInfos.value = infos
     }
 
-    suspend fun setSelectedUserInfo(userInfo: UserInfo) {
+    suspend fun setSelectedUserInfo(
+        userInfo: UserInfo,
+        selectionStatus: SelectionStatus = SelectionStatus.SELECTION_COMPLETE,
+    ) {
         check(_userInfos.value.contains(userInfo)) {
             "Cannot select the following user, it is not in the list of user infos: $userInfo!"
         }
 
-        _selectedUserInfo.value = userInfo
+        selectedUser.value = SelectedUserModel(userInfo, selectionStatus)
         yield()
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/test/TestExceptionDeferrer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/test/TestExceptionDeferrer.kt
new file mode 100644
index 0000000..90281ca
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/test/TestExceptionDeferrer.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.test
+
+import android.util.Log
+
+/**
+ * Helper class that intercepts test errors which may be occurring on the wrong thread, and saves
+ * them so that they can be rethrown back on the correct thread.
+ */
+class TestExceptionDeferrer(private val tag: String, private val testThread: Thread) {
+    private val deferredErrors = mutableListOf<IllegalStateException>()
+
+    /** Ensure the [value] is `true`; otherwise [fail] with the produced [message] */
+    fun check(value: Boolean, message: () -> Any?) {
+        if (value) return
+        fail(message().toString())
+    }
+
+    /**
+     * If the [Thread.currentThread] is the [testThread], then [error], otherwise [Log] and defer
+     * the error until [throwDeferred] is called.
+     */
+    fun fail(message: String) {
+        if (testThread == Thread.currentThread()) {
+            error(message)
+        } else {
+            val exception = IllegalStateException(message)
+            Log.e(tag, "Deferring error: ", exception)
+            deferredErrors.add(exception)
+        }
+    }
+
+    /** If any [fail] or failed [check] has happened, throw the first one. */
+    fun throwDeferred() {
+        deferredErrors.firstOrNull()?.let { firstError ->
+            Log.e(tag, "Deferred errors: ${deferredErrors.size}")
+            deferredErrors.clear()
+            throw firstError
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index c664c99..03e3423 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -20,8 +20,6 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 
 import java.util.List;
 
@@ -62,18 +60,10 @@
     }
 
     @Override
-    public void setWifiIcon(String slot, WifiIconState state) {
-    }
-
-    @Override
     public void setNewWifiIcon() {
     }
 
     @Override
-    public void setMobileIcons(String slot, List<MobileIconState> states) {
-    }
-
-    @Override
     public void setNewMobileIconSubIds(List<Integer> subIds) {
     }
 
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 8c5244e..6743515 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -62,6 +62,7 @@
     private val hingeAngleListener = HingeAngleListener()
     private val screenListener = ScreenStatusListener()
     private val foldStateListener = FoldStateListener()
+    private val mainLooper = handler.looper
     private val timeoutRunnable = Runnable { cancelAnimation() }
     private val rotationListener = RotationListener {
         if (isTransitionInProgress) cancelAnimation()
@@ -77,22 +78,28 @@
     private var isFolded = false
     private var isScreenOn = false
     private var isUnfoldHandled = true
+    private var isStarted = false
 
     override fun start() {
+        assertMainThread()
+        if (isStarted) return
         foldProvider.registerCallback(foldStateListener, mainExecutor)
         screenStatusProvider.addCallback(screenListener)
         hingeAngleProvider.addCallback(hingeAngleListener)
         rotationChangeProvider.addCallback(rotationListener)
         activityTypeProvider.init()
+        isStarted = true
     }
 
     override fun stop() {
+        assertMainThread()
         screenStatusProvider.removeCallback(screenListener)
         foldProvider.unregisterCallback(foldStateListener)
         hingeAngleProvider.removeCallback(hingeAngleListener)
         hingeAngleProvider.stop()
         rotationChangeProvider.removeCallback(rotationListener)
         activityTypeProvider.uninit()
+        isStarted = false
     }
 
     override fun addCallback(listener: FoldUpdatesListener) {
@@ -292,6 +299,14 @@
             onHingeAngle(angle)
         }
     }
+
+    private fun assertMainThread() {
+        check(mainLooper.isCurrentThread) {
+            ("should be called from the main thread." +
+                    " sMainLooper.threadName=" + mainLooper.thread.name +
+                    " Thread.currentThread()=" + Thread.currentThread().name)
+        }
+    }
 }
 
 fun @receiver:FoldUpdate Int.name() =
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 308e2cf..3406102 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -359,11 +359,7 @@
         final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE);
         final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE);
         final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE);
-
-        // If we restored separate lock imagery, the system wallpaper should be
-        // applied as system-only; but if there's no separate lock image, make
-        // sure to apply the restored system wallpaper as both.
-        final int sysWhich = FLAG_SYSTEM | (lockImageStage.exists() ? 0 : FLAG_LOCK);
+        boolean lockImageStageExists = lockImageStage.exists();
 
         try {
             // First parse the live component name so that we know for logging if we care about
@@ -371,14 +367,31 @@
             ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
             mSystemHasLiveComponent = wpService != null;
 
+            ComponentName kwpService = null;
+            boolean lockscreenLiveWallpaper = mWallpaperManager.isLockscreenLiveWallpaperEnabled();
+            if (lockscreenLiveWallpaper) {
+                kwpService = parseWallpaperComponent(infoStage, "kwp");
+            }
+            mLockHasLiveComponent = kwpService != null;
+            boolean separateLockWallpaper = mLockHasLiveComponent || lockImageStage.exists();
+
+            // if there's no separate lock wallpaper, apply the system wallpaper to both screens.
+            final int sysWhich = separateLockWallpaper ? FLAG_SYSTEM : FLAG_SYSTEM | FLAG_LOCK;
+
             // It is valid for the imagery to be absent; it means that we were not permitted
             // to back up the original image on the source device, or there was no user-supplied
             // wallpaper image present.
-            restoreFromStage(imageStage, infoStage, "wp", sysWhich);
-            restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK);
+            if (!lockscreenLiveWallpaper) restoreFromStage(imageStage, infoStage, "wp", sysWhich);
+            if (lockImageStageExists) {
+                restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK);
+            }
+            if (lockscreenLiveWallpaper) restoreFromStage(imageStage, infoStage, "wp", sysWhich);
 
             // And reset to the wallpaper service we should be using
-            updateWallpaperComponent(wpService, !lockImageStage.exists());
+            if (lockscreenLiveWallpaper && mLockHasLiveComponent) {
+                updateWallpaperComponent(kwpService, false, FLAG_LOCK);
+            }
+            updateWallpaperComponent(wpService, !lockImageStageExists, sysWhich);
         } catch (Exception e) {
             Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage());
             mEventLogger.onRestoreException(e);
@@ -397,9 +410,21 @@
     }
 
     @VisibleForTesting
-    void updateWallpaperComponent(ComponentName wpService, boolean applyToLock) throws IOException {
+    void updateWallpaperComponent(ComponentName wpService, boolean applyToLock, int which)
+            throws IOException {
+        boolean lockscreenLiveWallpaper = mWallpaperManager.isLockscreenLiveWallpaperEnabled();
         if (servicePackageExists(wpService)) {
             Slog.i(TAG, "Using wallpaper service " + wpService);
+            if (lockscreenLiveWallpaper) {
+                mWallpaperManager.setWallpaperComponentWithFlags(wpService, which);
+                if ((which & FLAG_LOCK) != 0) {
+                    mEventLogger.onLockLiveWallpaperRestored(wpService);
+                }
+                if ((which & FLAG_SYSTEM) != 0) {
+                    mEventLogger.onSystemLiveWallpaperRestored(wpService);
+                }
+                return;
+            }
             mWallpaperManager.setWallpaperComponent(wpService);
             if (applyToLock) {
                 // We have a live wallpaper and no static lock image,
@@ -414,7 +439,7 @@
             // in reports from users
             if (wpService != null) {
                 // TODO(b/268471749): Handle delayed case
-                applyComponentAtInstall(wpService, applyToLock);
+                applyComponentAtInstall(wpService, applyToLock, which);
                 Slog.w(TAG, "Wallpaper service " + wpService + " isn't available. "
                         + " Will try to apply later");
             }
@@ -437,7 +462,8 @@
                     // And log the success
                     if ((which & FLAG_SYSTEM) > 0) {
                         mEventLogger.onSystemImageWallpaperRestored();
-                    } else {
+                    }
+                    if ((which & FLAG_LOCK) > 0) {
                         mEventLogger.onLockImageWallpaperRestored();
                     }
                 }
@@ -460,7 +486,8 @@
     private void logRestoreError(int which, String error) {
         if ((which & FLAG_SYSTEM) == FLAG_SYSTEM) {
             mEventLogger.onSystemImageWallpaperRestoreFailed(error);
-        } else if ((which & FLAG_LOCK) == FLAG_LOCK) {
+        }
+        if ((which & FLAG_LOCK) == FLAG_LOCK) {
             mEventLogger.onLockImageWallpaperRestoreFailed(error);
         }
     }
@@ -552,16 +579,21 @@
         // Intentionally blank
     }
 
-    private void applyComponentAtInstall(ComponentName componentName, boolean applyToLock) {
-        PackageMonitor packageMonitor = getWallpaperPackageMonitor(componentName, applyToLock);
+    private void applyComponentAtInstall(ComponentName componentName, boolean applyToLock,
+            int which) {
+        PackageMonitor packageMonitor = getWallpaperPackageMonitor(
+                componentName, applyToLock, which);
         packageMonitor.register(getBaseContext(), null, UserHandle.ALL, true);
     }
 
     @VisibleForTesting
-    PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, boolean applyToLock) {
+    PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, boolean applyToLock,
+            int which) {
         return new PackageMonitor() {
             @Override
             public void onPackageAdded(String packageName, int uid) {
+                boolean lockscreenLiveWallpaper =
+                        mWallpaperManager.isLockscreenLiveWallpaperEnabled();
                 if (!isDeviceInRestore()) {
                     // We don't want to reapply the wallpaper outside a restore.
                     unregister();
@@ -582,16 +614,29 @@
 
                 if (componentName.getPackageName().equals(packageName)) {
                     Slog.d(TAG, "Applying component " + componentName);
-                    boolean sysResult = mWallpaperManager.setWallpaperComponent(componentName);
+                    boolean success = lockscreenLiveWallpaper
+                            ? mWallpaperManager.setWallpaperComponentWithFlags(componentName, which)
+                            : mWallpaperManager.setWallpaperComponent(componentName);
                     WallpaperEventLogger logger = new WallpaperEventLogger(
                             mBackupManager.getDelayedRestoreLogger());
-                    if (sysResult) {
-                        logger.onSystemLiveWallpaperRestored(componentName);
+                    if (success) {
+                        if (!lockscreenLiveWallpaper || (which & FLAG_SYSTEM) != 0) {
+                            logger.onSystemLiveWallpaperRestored(componentName);
+                        }
+                        if (lockscreenLiveWallpaper && (which & FLAG_LOCK) != 0) {
+                            logger.onLockLiveWallpaperRestored(componentName);
+                        }
                     } else {
-                        logger.onSystemLiveWallpaperRestoreFailed(
-                                WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
+                        if (!lockscreenLiveWallpaper || (which & FLAG_SYSTEM) != 0) {
+                            logger.onSystemLiveWallpaperRestoreFailed(
+                                    WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
+                        }
+                        if (lockscreenLiveWallpaper && (which & FLAG_LOCK) != 0) {
+                            logger.onLockLiveWallpaperRestoreFailed(
+                                    WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
+                        }
                     }
-                    if (applyToLock) {
+                    if (applyToLock && !lockscreenLiveWallpaper) {
                         try {
                             mWallpaperManager.clear(FLAG_LOCK);
                             logger.onLockLiveWallpaperRestored(componentName);
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 9b07ad4..dc1126e 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -117,6 +117,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        when(mWallpaperManager.isLockscreenLiveWallpaperEnabled()).thenReturn(true);
         when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_SYSTEM))).thenReturn(true);
         when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_LOCK))).thenReturn(true);
 
@@ -364,14 +365,23 @@
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true);
+                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
-
-        verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
-        verify(mWallpaperManager, times(1)).clear(eq(FLAG_LOCK));
+        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
+            verify(mWallpaperManager, times(1))
+                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
+            verify(mWallpaperManager, never())
+                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
+            verify(mWallpaperManager, never())
+                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
+            verify(mWallpaperManager, never()).clear(anyInt());
+        } else {
+            verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
+            verify(mWallpaperManager, times(1)).clear(eq(FLAG_LOCK));
+        }
     }
 
     @Test
@@ -380,14 +390,24 @@
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ false);
+                /* applyToLock */ false, FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
 
-        verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
-        verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
+        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
+            verify(mWallpaperManager, times(1))
+                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
+            verify(mWallpaperManager, never())
+                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
+            verify(mWallpaperManager, never())
+                    .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
+            verify(mWallpaperManager, never()).clear(anyInt());
+        } else {
+            verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
+            verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
+        }
     }
 
     @Test
@@ -396,7 +416,7 @@
         mWallpaperBackupAgent.mIsDeviceInRestore = false;
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true);
+                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
@@ -412,7 +432,7 @@
         mWallpaperBackupAgent.mIsDeviceInRestore = false;
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true);
+                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate "wrong" wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(/* packageName */"",
@@ -614,6 +634,13 @@
                 mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
         assertThat(result).isNotNull();
         assertThat(result.getSuccessCount()).isEqualTo(1);
+
+        if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
+            result = getLoggingResult(WALLPAPER_IMG_LOCK,
+                    mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+            assertThat(result).isNotNull();
+            assertThat(result.getSuccessCount()).isEqualTo(1);
+        }
     }
 
     @Test
@@ -649,19 +676,20 @@
     }
 
     @Test
-    public void testOnRestore_lockWallpaperImgMissingAndNoLive_logsFailure() throws Exception {
+    public void testOnRestore_wallpaperImgMissingAndNoLive_logsFailure() throws Exception {
         mockStagedWallpaperFile(WALLPAPER_INFO_STAGE);
-        mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE);
         mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
                 BackupAnnotations.OperationType.RESTORE);
 
         mWallpaperBackupAgent.onRestoreFinished();
 
-        DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK,
-                mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
-        assertThat(result).isNotNull();
-        assertThat(result.getFailCount()).isEqualTo(1);
-        assertThat(result.getErrors()).containsKey(ERROR_NO_WALLPAPER);
+        for (String wallpaper: List.of(WALLPAPER_IMG_LOCK, WALLPAPER_IMG_SYSTEM)) {
+            DataTypeResult result = getLoggingResult(wallpaper,
+                    mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+            assertThat(result).isNotNull();
+            assertThat(result.getFailCount()).isEqualTo(1);
+            assertThat(result.getErrors()).containsKey(ERROR_NO_WALLPAPER);
+        }
     }
 
     @Test
@@ -722,13 +750,15 @@
     public void testUpdateWallpaperComponent_delayedRestore_logsSuccess() throws Exception {
         mWallpaperBackupAgent.mIsDeviceInRestore = true;
         when(mWallpaperManager.setWallpaperComponent(any())).thenReturn(true);
+        when(mWallpaperManager.setWallpaperComponentWithFlags(any(), eq(FLAG_LOCK | FLAG_SYSTEM)))
+                .thenReturn(true);
         BackupRestoreEventLogger logger = new BackupRestoreEventLogger(
                 BackupAnnotations.OperationType.RESTORE);
         when(mBackupManager.getDelayedRestoreLogger()).thenReturn(logger);
         mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true);
+                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
@@ -752,7 +782,7 @@
         mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true);
+                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
                 /* uid */0);
@@ -774,7 +804,7 @@
         mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
 
         mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
-                /* applyToLock */ true);
+                /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
 
         // Imitate wallpaper component installation.
         mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
@@ -909,8 +939,9 @@
 
         @Override
         PackageMonitor getWallpaperPackageMonitor(ComponentName componentName,
-                boolean applyToLock) {
-            mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(componentName, applyToLock);
+                boolean applyToLock, int which) {
+            mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(
+                    componentName, applyToLock, which);
             return mWallpaperPackageMonitor;
         }
 
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index c2ebddf..502ee4d 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -75,6 +75,7 @@
 import android.util.Size;
 import android.view.Surface;
 
+import androidx.annotation.NonNull;
 import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl;
 import androidx.camera.extensions.impl.AutoPreviewExtenderImpl;
 import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl;
@@ -204,7 +205,7 @@
      * A per-process global camera extension manager instance, to track and
      * initialize/release extensions depending on client activity.
      */
-    private static final class CameraExtensionManagerGlobal {
+    private static final class CameraExtensionManagerGlobal implements IBinder.DeathRecipient {
         private static final String TAG = "CameraExtensionManagerGlobal";
         private final int EXTENSION_DELAY_MS = 1000;
 
@@ -212,8 +213,9 @@
         private final HandlerThread mHandlerThread;
         private final Object mLock = new Object();
 
-        private long mCurrentClientCount = 0;
-        private ArraySet<Long> mActiveClients = new ArraySet<>();
+        private ArraySet<IBinder> mActiveClients = new ArraySet<>();
+        private HashMap<IBinder, ArraySet<IBinder.DeathRecipient>> mClientDeathRecipient =
+                new HashMap<>();
         private IInitializeSessionCallback mInitializeCb = null;
 
         // Singleton instance
@@ -314,8 +316,20 @@
             return GLOBAL_CAMERA_MANAGER;
         }
 
-        public long registerClient(Context ctx) {
+        public boolean registerClient(Context ctx, IBinder token) {
             synchronized (mLock) {
+                if (mActiveClients.contains(token)) {
+                    Log.e(TAG, "Failed to register existing client!");
+                    return false;
+                }
+
+                try {
+                    token.linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to link to binder token!");
+                    return false;
+                }
+
                 if (INIT_API_SUPPORTED) {
                     if (mActiveClients.isEmpty()) {
                         InitializerFuture status = new InitializerFuture();
@@ -327,43 +341,76 @@
                                     TimeUnit.MILLISECONDS);
                         } catch (TimeoutException e) {
                             Log.e(TAG, "Timed out while initializing camera extensions!");
-                            return -1;
+                            return false;
                         }
                         if (!initSuccess) {
                             Log.e(TAG, "Failed while initializing camera extensions!");
-                            return -1;
+                            return false;
                         }
                     }
                 }
 
-                long ret = mCurrentClientCount;
-                mCurrentClientCount++;
-                if (mCurrentClientCount < 0) {
-                    mCurrentClientCount = 0;
-                }
-                mActiveClients.add(ret);
+                mActiveClients.add(token);
+                mClientDeathRecipient.put(token, new ArraySet<>());
 
-                return ret;
+                return true;
             }
         }
 
-        public void unregisterClient(long clientId) {
+        public void unregisterClient(IBinder token) {
             synchronized (mLock) {
-                if (mActiveClients.remove(clientId) && mActiveClients.isEmpty() &&
-                        INIT_API_SUPPORTED) {
-                    InitializerFuture status = new InitializerFuture();
-                    InitializerImpl.deinit(new ReleaseHandler(status),
-                            new HandlerExecutor(mHandler));
-                    boolean releaseSuccess;
-                    try {
-                        releaseSuccess = status.get(EXTENSION_DELAY_MS, TimeUnit.MILLISECONDS);
-                    } catch (TimeoutException e) {
-                        Log.e(TAG, "Timed out while releasing camera extensions!");
-                        return;
+                if (mActiveClients.remove(token)) {
+                    token.unlinkToDeath(this, 0);
+                    mClientDeathRecipient.remove(token);
+                    if (mActiveClients.isEmpty() && INIT_API_SUPPORTED) {
+                        InitializerFuture status = new InitializerFuture();
+                        InitializerImpl.deinit(new ReleaseHandler(status),
+                                new HandlerExecutor(mHandler));
+                        boolean releaseSuccess;
+                        try {
+                            releaseSuccess = status.get(EXTENSION_DELAY_MS, TimeUnit.MILLISECONDS);
+                        } catch (TimeoutException e) {
+                            Log.e(TAG, "Timed out while releasing camera extensions!");
+                            return;
+                        }
+                        if (!releaseSuccess) {
+                            Log.e(TAG, "Failed while releasing camera extensions!");
+                        }
                     }
-                    if (!releaseSuccess) {
-                        Log.e(TAG, "Failed while releasing camera extensions!");
-                    }
+                }
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            // Do nothing, handled below
+        }
+
+        @Override
+        public void binderDied(@NonNull IBinder who) {
+            synchronized (mLock) {
+                if (mClientDeathRecipient.containsKey(who)) {
+                    mClientDeathRecipient.get(who).stream().forEach(
+                            recipient -> recipient.binderDied(who));
+                }
+                unregisterClient(who);
+            }
+        }
+
+        public void registerDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
+            synchronized (mLock) {
+                if (mClientDeathRecipient.containsKey(token)) {
+                    ArraySet<IBinder.DeathRecipient> recipients = mClientDeathRecipient.get(token);
+                    recipients.add(recipient);
+                }
+            }
+        }
+
+        public void unregisterDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
+            synchronized (mLock) {
+                if (mClientDeathRecipient.containsKey(token)) {
+                    ArraySet<IBinder.DeathRecipient> recipients = mClientDeathRecipient.get(token);
+                    recipients.remove(recipient);
                 }
             }
         }
@@ -406,21 +453,35 @@
     /**
      * @hide
      */
-    private static long registerClient(Context ctx) {
+    private static boolean registerClient(Context ctx, IBinder token) {
         if (!EXTENSIONS_PRESENT) {
-            return -1;
+            return false;
         }
-        return CameraExtensionManagerGlobal.get().registerClient(ctx);
+        return CameraExtensionManagerGlobal.get().registerClient(ctx, token);
     }
 
     /**
      * @hide
      */
-    public static void unregisterClient(long clientId) {
+    public static void unregisterClient(IBinder token) {
         if (!EXTENSIONS_PRESENT) {
             return;
         }
-        CameraExtensionManagerGlobal.get().unregisterClient(clientId);
+        CameraExtensionManagerGlobal.get().unregisterClient(token);
+    }
+
+    /**
+     * @hide
+     */
+    private static void registerDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
+        CameraExtensionManagerGlobal.get().registerDeathRecipient(token, recipient);
+    }
+
+    /**
+     * @hide
+     */
+    private static void unregisterDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) {
+        CameraExtensionManagerGlobal.get().unregisterDeathRecipient(token, recipient);
     }
 
     /**
@@ -649,13 +710,14 @@
 
     private class CameraExtensionsProxyServiceStub extends ICameraExtensionsProxyService.Stub {
         @Override
-        public long registerClient() {
-            return CameraExtensionsProxyService.registerClient(CameraExtensionsProxyService.this);
+        public boolean registerClient(IBinder token) {
+            return CameraExtensionsProxyService.registerClient(CameraExtensionsProxyService.this,
+                    token);
         }
 
         @Override
-        public void unregisterClient(long clientId) {
-            CameraExtensionsProxyService.unregisterClient(clientId);
+        public void unregisterClient(IBinder token) {
+            CameraExtensionsProxyService.unregisterClient(token);
         }
 
         private boolean checkCameraPermission() {
@@ -1192,16 +1254,18 @@
         }
     }
 
-    private class SessionProcessorImplStub extends ISessionProcessorImpl.Stub {
+    private class SessionProcessorImplStub extends ISessionProcessorImpl.Stub implements
+            IBinder.DeathRecipient {
         private final SessionProcessorImpl mSessionProcessor;
         private String mCameraId = null;
+        private IBinder mToken;
 
         public SessionProcessorImplStub(SessionProcessorImpl sessionProcessor) {
             mSessionProcessor = sessionProcessor;
         }
 
         @Override
-        public CameraSessionConfig initSession(String cameraId,
+        public CameraSessionConfig initSession(IBinder token, String cameraId,
                 Map<String, CameraMetadataNative> charsMapNative, OutputSurface previewSurface,
                 OutputSurface imageCaptureSurface, OutputSurface postviewSurface) {
             OutputSurfaceImplStub outputPreviewSurfaceImpl =
@@ -1253,12 +1317,14 @@
             ret.sessionParameter = initializeParcelableMetadata(
                     sessionConfig.getSessionParameters(), cameraId);
             mCameraId = cameraId;
-
+            mToken = token;
+            CameraExtensionsProxyService.registerDeathRecipient(mToken, this);
             return ret;
         }
 
         @Override
-        public void deInitSession() {
+        public void deInitSession(IBinder token) {
+            CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this);
             mSessionProcessor.deInitSession();
         }
 
@@ -1330,6 +1396,11 @@
 
             return null;
         }
+
+        @Override
+        public void binderDied() {
+            mSessionProcessor.deInitSession();
+        }
     }
 
     private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl {
@@ -1395,24 +1466,31 @@
         }
     }
 
-    private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub {
+    private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub implements
+            IBinder.DeathRecipient {
         private final PreviewExtenderImpl mPreviewExtender;
         private String mCameraId = null;
+        private boolean mSessionEnabled;
+        private IBinder mToken;
 
         public PreviewExtenderImplStub(PreviewExtenderImpl previewExtender) {
             mPreviewExtender = previewExtender;
         }
 
         @Override
-        public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) {
+        public void onInit(IBinder token, String cameraId,
+                CameraMetadataNative cameraCharacteristics) {
             mCameraId = cameraId;
             CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics);
             mCameraManager.registerDeviceStateListener(chars);
             mPreviewExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this);
+            mToken = token;
+            CameraExtensionsProxyService.registerDeathRecipient(mToken, this);
         }
 
         @Override
-        public void onDeInit() {
+        public void onDeInit(IBinder token) {
+            CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this);
             mPreviewExtender.onDeInit();
         }
 
@@ -1423,11 +1501,13 @@
 
         @Override
         public CaptureStageImpl onEnableSession() {
+            mSessionEnabled = true;
             return initializeParcelable(mPreviewExtender.onEnableSession(), mCameraId);
         }
 
         @Override
         public CaptureStageImpl onDisableSession() {
+            mSessionEnabled = false;
             return initializeParcelable(mPreviewExtender.onDisableSession(), mCameraId);
         }
 
@@ -1516,26 +1596,41 @@
             }
             return null;
         }
+
+        @Override
+        public void binderDied() {
+            if (mSessionEnabled) {
+                mPreviewExtender.onDisableSession();
+            }
+            mPreviewExtender.onDeInit();
+        }
     }
 
-    private class ImageCaptureExtenderImplStub extends IImageCaptureExtenderImpl.Stub {
+    private class ImageCaptureExtenderImplStub extends IImageCaptureExtenderImpl.Stub implements
+            IBinder.DeathRecipient {
         private final ImageCaptureExtenderImpl mImageExtender;
         private String mCameraId = null;
+        private boolean mSessionEnabled;
+        private IBinder mToken;
 
         public ImageCaptureExtenderImplStub(ImageCaptureExtenderImpl imageExtender) {
             mImageExtender = imageExtender;
         }
 
         @Override
-        public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) {
+        public void onInit(IBinder token, String cameraId,
+                CameraMetadataNative cameraCharacteristics) {
             CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics);
             mCameraManager.registerDeviceStateListener(chars);
             mImageExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this);
             mCameraId = cameraId;
+            mToken = token;
+            CameraExtensionsProxyService.registerDeathRecipient(mToken, this);
         }
 
         @Override
-        public void onDeInit() {
+        public void onDeInit(IBinder token) {
+            CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this);
             mImageExtender.onDeInit();
         }
 
@@ -1564,11 +1659,13 @@
 
         @Override
         public CaptureStageImpl onEnableSession() {
+            mSessionEnabled = true;
             return initializeParcelable(mImageExtender.onEnableSession(), mCameraId);
         }
 
         @Override
         public CaptureStageImpl onDisableSession() {
+            mSessionEnabled = false;
             return initializeParcelable(mImageExtender.onDisableSession(), mCameraId);
         }
 
@@ -1737,6 +1834,14 @@
 
             return null;
         }
+
+        @Override
+        public void binderDied() {
+            if (mSessionEnabled) {
+                mImageExtender.onDisableSession();
+            }
+            mImageExtender.onDeInit();
+        }
     }
 
     private class ProcessResultCallback implements ProcessResultImpl {
diff --git a/services/Android.bp b/services/Android.bp
index b0a0e5e..f732940 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -110,7 +110,6 @@
         ":services.profcollect-sources",
         ":services.restrictions-sources",
         ":services.searchui-sources",
-        ":services.selectiontoolbar-sources",
         ":services.smartspace-sources",
         ":services.soundtrigger-sources",
         ":services.systemcaptions-sources",
@@ -159,6 +158,7 @@
         "services.coverage",
         "services.credentials",
         "services.devicepolicy",
+        "services.flags",
         "services.midi",
         "services.musicsearch",
         "services.net",
@@ -168,7 +168,6 @@
         "services.profcollect",
         "services.restrictions",
         "services.searchui",
-        "services.selectiontoolbar",
         "services.smartspace",
         "services.soundtrigger",
         "services.systemcaptions",
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index b1cdc50..ba3d434 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -319,6 +319,10 @@
                     FullScreenMagnificationController::onUserContextChanged,
                     FullScreenMagnificationController.this, mDisplayId);
             mControllerCtx.getHandler().sendMessage(m);
+
+            synchronized (mLock) {
+                refreshThumbnail();
+            }
         }
 
         @Override
@@ -344,7 +348,7 @@
                     mMagnificationRegion.set(magnified);
                     mMagnificationRegion.getBounds(mMagnificationBounds);
 
-                    refreshThumbnail(getScale(), getCenterX(), getCenterY());
+                    refreshThumbnail();
 
                     // It's possible that our magnification spec is invalid with the new bounds.
                     // Adjust the current spec's offsets if necessary.
@@ -602,13 +606,13 @@
         }
 
         @GuardedBy("mLock")
-        void refreshThumbnail(float scale, float centerX, float centerY) {
+        void refreshThumbnail() {
             if (mMagnificationThumbnail != null) {
                 mMagnificationThumbnail.setThumbnailBounds(
                         mMagnificationBounds,
-                        scale,
-                        centerX,
-                        centerY
+                        getScale(),
+                        getCenterX(),
+                        getCenterY()
                 );
             }
         }
@@ -627,7 +631,7 @@
                 // We call refreshThumbnail when the thumbnail is just created to set current
                 // magnification bounds to thumbnail. It to prevent the thumbnail size has not yet
                 // updated properly and thus shows with huge size. (b/276314641)
-                refreshThumbnail(getScale(), getCenterX(), getCenterY());
+                refreshThumbnail();
             }
         }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 87fbee7..effd873 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -172,14 +172,18 @@
     }
 
     @Override
-    public void onPerformScaleAction(int displayId, float scale) {
+    public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
         if (getFullScreenMagnificationController().isActivated(displayId)) {
             getFullScreenMagnificationController().setScaleAndCenter(displayId, scale,
                     Float.NaN, Float.NaN, false, MAGNIFICATION_GESTURE_HANDLER_ID);
-            getFullScreenMagnificationController().persistScale(displayId);
+            if (updatePersistence) {
+                getFullScreenMagnificationController().persistScale(displayId);
+            }
         } else if (getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId)) {
             getWindowMagnificationMgr().setScale(displayId, scale);
-            getWindowMagnificationMgr().persistScale(displayId);
+            if (updatePersistence) {
+                getWindowMagnificationMgr().persistScale(displayId);
+            }
         }
     }
 
@@ -502,6 +506,10 @@
     @Override
     public void onSourceBoundsChanged(int displayId, Rect bounds) {
         if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_WINDOW)) {
+            // notify sysui the magnification scale changed on window magnifier
+            mWindowMagnificationMgr.onUserMagnificationScaleChanged(
+                    mUserId, displayId, getWindowMagnificationMgr().getScale(displayId));
+
             final MagnificationConfig config = new MagnificationConfig.Builder()
                     .setMode(MAGNIFICATION_MODE_WINDOW)
                     .setActivated(getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId))
@@ -516,6 +524,10 @@
     public void onFullScreenMagnificationChanged(int displayId, @NonNull Region region,
             @NonNull MagnificationConfig config) {
         if (shouldNotifyMagnificationChange(displayId, MAGNIFICATION_MODE_FULLSCREEN)) {
+            // notify sysui the magnification scale changed on fullscreen magnifier
+            mWindowMagnificationMgr.onUserMagnificationScaleChanged(
+                    mUserId, displayId, config.getScale());
+
             mAms.notifyMagnificationChanged(displayId, region, config);
         }
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java
index 8e1aa38..41c3dcb 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java
@@ -16,6 +16,9 @@
 
 package com.android.server.accessibility.magnification;
 
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE;
+
 import android.content.Context;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -37,8 +40,9 @@
 
     @VisibleForTesting
     protected static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
-    public static final float MIN_SCALE = 1.0f;
-    public static final float MAX_SCALE = 8.0f;
+
+    public static final float MIN_SCALE = SCALE_MIN_VALUE;
+    public static final float MAX_SCALE = SCALE_MAX_VALUE;
 
     private final Context mContext;
     // Stores the scale for non-default displays.
@@ -134,6 +138,6 @@
     }
 
     static float constrainScale(float scale) {
-        return MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+        return MathUtils.constrain(scale, SCALE_MIN_VALUE, SCALE_MAX_VALUE);
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
index 03fa93d..a7bdd5a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
@@ -99,15 +99,17 @@
             Log.d(LOG_TAG, "setThumbnailBounds " + currentBounds);
         }
         mHandler.post(() -> {
-            mWindowBounds = currentBounds;
-            setBackgroundBounds();
+            refreshBackgroundBounds(currentBounds);
             if (mVisible) {
                 updateThumbnailMainThread(scale, centerX, centerY);
             }
         });
     }
 
-    private void setBackgroundBounds() {
+    @MainThread
+    private void refreshBackgroundBounds(Rect currentBounds) {
+        mWindowBounds = currentBounds;
+
         Point magnificationBoundary = getMagnificationThumbnailPadding(mContext);
         mThumbnailWidth = (int) (mWindowBounds.width() / BG_ASPECT_RATIO);
         mThumbnailHeight = (int) (mWindowBounds.height() / BG_ASPECT_RATIO);
@@ -117,6 +119,10 @@
         mBackgroundParams.height = mThumbnailHeight;
         mBackgroundParams.x = initX;
         mBackgroundParams.y = initY;
+
+        if (mVisible) {
+            mWindowManager.updateViewLayout(mThumbnailLayout, mBackgroundParams);
+        }
     }
 
     @MainThread
@@ -264,21 +270,16 @@
             mThumbnailView.setScaleX(scaleDown);
             mThumbnailView.setScaleY(scaleDown);
         }
-        float thumbnailWidth;
-        float thumbnailHeight;
-        if (mThumbnailView.getWidth() == 0 || mThumbnailView.getHeight() == 0) {
-            // if the thumbnail view size is not updated correctly, we just use the cached values.
-            thumbnailWidth = mThumbnailWidth;
-            thumbnailHeight = mThumbnailHeight;
-        } else {
-            thumbnailWidth = mThumbnailView.getWidth();
-            thumbnailHeight = mThumbnailView.getHeight();
-        }
-        if (!Float.isNaN(centerX)) {
+
+        if (!Float.isNaN(centerX)
+                && !Float.isNaN(centerY)
+                && mThumbnailWidth > 0
+                && mThumbnailHeight > 0
+        ) {
             var padding = mThumbnailView.getPaddingTop();
             var ratio = 1f / BG_ASPECT_RATIO;
-            var centerXScaled = centerX * ratio - (thumbnailWidth / 2f + padding);
-            var centerYScaled = centerY * ratio - (thumbnailHeight / 2f + padding);
+            var centerXScaled = centerX * ratio - (mThumbnailWidth / 2f + padding);
+            var centerYScaled = centerY * ratio - (mThumbnailHeight / 2f + padding);
 
             if (DEBUG) {
                 Log.d(
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
index 1202cfa..6c6394f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
@@ -201,6 +201,22 @@
         return true;
     }
 
+    boolean onUserMagnificationScaleChanged(int userId, int displayId, float scale) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+            mTrace.logTrace(TAG + ".onMagnificationScaleUpdated",
+                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId);
+        }
+        try {
+            mConnection.onUserMagnificationScaleChanged(userId, displayId, scale);
+        } catch (RemoteException e) {
+            if (DBG) {
+                Slog.e(TAG, "Error calling onMagnificationScaleUpdated()", e);
+            }
+            return false;
+        }
+        return true;
+    }
+
     boolean setConnectionCallback(IWindowMagnificationConnectionCallback connectionCallback) {
         if (mTrace.isA11yTracingEnabledForTypes(
                 FLAGS_WINDOW_MAGNIFICATION_CONNECTION
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index d07db3f..816f22f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -167,8 +167,9 @@
          *
          * @param displayId The logical display id.
          * @param scale the target scale, or {@link Float#NaN} to leave unchanged
+         * @param updatePersistence whether the scale should be persisted
          */
-        void onPerformScaleAction(int displayId, float scale);
+        void onPerformScaleAction(int displayId, float scale, boolean updatePersistence);
 
         /**
          * Called when the accessibility action is performed.
@@ -827,6 +828,20 @@
     }
 
     /**
+     * Notify System UI the magnification scale on the specified display for userId is changed.
+     *
+     * @param userId the user id.
+     * @param displayId the logical display id.
+     * @param scale magnification scale.
+     */
+    public boolean onUserMagnificationScaleChanged(int userId, int displayId, float scale) {
+        synchronized (mLock) {
+            return mConnectionWrapper != null
+                    && mConnectionWrapper.onUserMagnificationScaleChanged(userId, displayId, scale);
+        }
+    }
+
+    /**
      * Returns the screen-relative X coordinate of the center of the magnified bounds.
      *
      * @param displayId The logical display id
@@ -963,14 +978,15 @@
         }
 
         @Override
-        public void onPerformScaleAction(int displayId, float scale) {
+        public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
             if (mTrace.isA11yTracingEnabledForTypes(
                     FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
                 mTrace.logTrace(TAG + "ConnectionCallback.onPerformScaleAction",
                         FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
-                        "displayId=" + displayId + ";scale=" + scale);
+                        "displayId=" + displayId + ";scale=" + scale
+                                + ";updatePersistence=" + updatePersistence);
             }
-            mCallback.onPerformScaleAction(displayId, scale);
+            mCallback.onPerformScaleAction(displayId, scale, updatePersistence);
         }
 
         @Override
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index fb26f42..d6702b7 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -134,6 +134,7 @@
 import android.service.autofill.CompositeUserData;
 import android.service.autofill.Dataset;
 import android.service.autofill.Dataset.DatasetEligibleReason;
+import android.service.autofill.Field;
 import android.service.autofill.FieldClassification;
 import android.service.autofill.FieldClassification.Match;
 import android.service.autofill.FieldClassificationUserData;
@@ -189,6 +190,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -1780,11 +1782,11 @@
      */
     private static class DatasetComputationContainer {
         // List of all autofill ids that have a corresponding datasets
-        Set<AutofillId> mAutofillIds = new ArraySet<>();
+        Set<AutofillId> mAutofillIds = new LinkedHashSet<>();
         // Set of datasets. Kept separately, to be able to be used directly for composing
         // FillResponse.
         Set<Dataset> mDatasets = new LinkedHashSet<>();
-        ArrayMap<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new ArrayMap<>();
+        Map<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new LinkedHashMap<>();
 
         public String toString() {
             final StringBuilder builder = new StringBuilder("DatasetComputationContainer[");
@@ -1822,7 +1824,7 @@
                 // for now to keep safe. TODO(b/266379948): Revisit this logic.
 
                 Set<Dataset> datasets = c2.mAutofillIdToDatasetMap.get(id);
-                Set<Dataset> copyDatasets = new ArraySet<>(datasets);
+                Set<Dataset> copyDatasets = new LinkedHashSet<>(datasets);
                 c1.mAutofillIds.add(id);
                 c1.mAutofillIdToDatasetMap.put(id, copyDatasets);
                 c1.mDatasets.addAll(copyDatasets);
@@ -1838,6 +1840,13 @@
         }
     }
 
+    /**
+     * Computes datasets that are eligible to be shown based on provider detections.
+     * Datasets are populated in the provided container for them to be later merged with the
+     * PCC eligible datasets based on preference strategy.
+     * @param response
+     * @param container
+     */
     private void computeDatasetsForProviderAndUpdateContainer(
             FillResponse response, DatasetComputationContainer container) {
         @DatasetEligibleReason int globalPickReason = PICK_REASON_UNKNOWN;
@@ -1849,9 +1858,9 @@
         }
         List<Dataset> datasets = response.getDatasets();
         if (datasets == null) return;
-        ArrayMap<AutofillId, Set<Dataset>> autofillIdToDatasetMap = new ArrayMap<>();
+        Map<AutofillId, Set<Dataset>> autofillIdToDatasetMap = new LinkedHashMap<>();
         Set<Dataset> eligibleDatasets = new LinkedHashSet<>();
-        Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
+        Set<AutofillId> eligibleAutofillIds = new LinkedHashSet<>();
         for (Dataset dataset : response.getDatasets()) {
             if (dataset.getFieldIds() == null || dataset.getFieldIds().isEmpty()) continue;
             @DatasetEligibleReason int pickReason = globalPickReason;
@@ -1927,7 +1936,7 @@
                 eligibleAutofillIds.add(id);
                 Set<Dataset> datasetForIds = autofillIdToDatasetMap.get(id);
                 if (datasetForIds == null) {
-                    datasetForIds = new ArraySet<>();
+                    datasetForIds = new LinkedHashSet<>();
                 }
                 datasetForIds.add(dataset);
                 autofillIdToDatasetMap.put(id, datasetForIds);
@@ -1938,23 +1947,30 @@
         container.mAutofillIds = eligibleAutofillIds;
     }
 
+    /**
+     * Computes datasets that are eligible to be shown based on PCC detections.
+     * Datasets are populated in the provided container for them to be later merged with the
+     * provider eligible datasets based on preference strategy.
+     * @param response
+     * @param container
+     */
     private void computeDatasetsForPccAndUpdateContainer(
             FillResponse response, DatasetComputationContainer container) {
         List<Dataset> datasets = response.getDatasets();
         if (datasets == null) return;
 
         synchronized (mLock) {
-            ArrayMap<String, Set<AutofillId>> hintsToAutofillIdMap =
+            Map<String, Set<AutofillId>> hintsToAutofillIdMap =
                     mClassificationState.mHintsToAutofillIdMap;
 
             // TODO(266379948): Handle group hints too.
-            ArrayMap<String, Set<AutofillId>> groupHintsToAutofillIdMap =
+            Map<String, Set<AutofillId>> groupHintsToAutofillIdMap =
                     mClassificationState.mGroupHintsToAutofillIdMap;
 
-            ArrayMap<AutofillId, Set<Dataset>> map = new ArrayMap<>();
+            Map<AutofillId, Set<Dataset>> map = new LinkedHashMap<>();
 
             Set<Dataset> eligibleDatasets = new LinkedHashSet<>();
-            Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
+            Set<AutofillId> eligibleAutofillIds = new LinkedHashSet<>();
 
             for (int i = 0; i < datasets.size(); i++) {
 
@@ -1970,13 +1986,35 @@
                 ArrayList<InlinePresentation> fieldInlinePresentations = new ArrayList<>();
                 ArrayList<InlinePresentation> fieldInlineTooltipPresentations = new ArrayList<>();
                 ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>();
-                Set<AutofillId> datasetAutofillIds = new ArraySet<>();
+                Set<AutofillId> datasetAutofillIds = new LinkedHashSet<>();
+
+                boolean isDatasetAvailable = false;
+                Set<AutofillId> additionalDatasetAutofillIds = new LinkedHashSet<>();
+                Set<AutofillId> additionalEligibleAutofillIds = new LinkedHashSet<>();
 
                 for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) {
                     if (dataset.getAutofillDatatypes().get(j) == null) {
+                        // TODO : revisit pickReason logic
                         if (dataset.getFieldIds() != null && dataset.getFieldIds().get(j) != null) {
                             pickReason = PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
                         }
+                        // Check if the autofill id at this index is detected by PCC.
+                        // If not, add that id here, otherwise, we can have duplicates when later
+                        // merging with provider datasets.
+                        // Howover, this doesn't make datasetAvailable for PCC on its own.
+                        // For that, there has to be a datatype detected by PCC, and the dataset
+                        // for that datatype provided by the provider.
+                        AutofillId autofillId = dataset.getFieldIds().get(j);
+                        if (!mClassificationState.mClassificationCombinedHintsMap
+                                .containsKey(autofillId)) {
+                            additionalEligibleAutofillIds.add(autofillId);
+                            additionalDatasetAutofillIds.add(autofillId);
+                            // For each of the field, copy over values.
+                            copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues,
+                                    fieldPresentations, fieldDialogPresentations,
+                                    fieldInlinePresentations, fieldInlineTooltipPresentations,
+                                    fieldFilters);
+                        }
                         continue;
                     }
                     String hint = dataset.getAutofillDatatypes().get(j);
@@ -1984,22 +2022,18 @@
                     if (hintsToAutofillIdMap.containsKey(hint)) {
                         ArrayList<AutofillId> tempIds =
                                 new ArrayList<>(hintsToAutofillIdMap.get(hint));
-
+                        if (tempIds.isEmpty()) {
+                            continue;
+                        }
+                        isDatasetAvailable = true;
                         for (AutofillId autofillId : tempIds) {
                             eligibleAutofillIds.add(autofillId);
                             datasetAutofillIds.add(autofillId);
                             // For each of the field, copy over values.
-                            fieldIds.add(autofillId);
-                            fieldValues.add(dataset.getFieldValues().get(j));
-                            //  TODO(b/266379948): might need to make it more efficient by not
-                            //  copying over value if it didn't exist. This would require creating
-                            //  a getter for the presentations arraylist.
-                            fieldPresentations.add(dataset.getFieldPresentation(j));
-                            fieldDialogPresentations.add(dataset.getFieldDialogPresentation(j));
-                            fieldInlinePresentations.add(dataset.getFieldInlinePresentation(j));
-                            fieldInlineTooltipPresentations.add(
-                                    dataset.getFieldInlineTooltipPresentation(j));
-                            fieldFilters.add(dataset.getFilter(j));
+                            copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues,
+                                    fieldPresentations, fieldDialogPresentations,
+                                    fieldInlinePresentations, fieldInlineTooltipPresentations,
+                                    fieldFilters);
                         }
                     }
                     // TODO(b/266379948):  handle the case:
@@ -2008,34 +2042,38 @@
                     // TODO(b/266379948):  also handle the case where there could be more types in
                     // the dataset, provided by the provider, however, they aren't applicable.
                 }
-                Dataset newDataset =
-                        new Dataset(
-                                fieldIds,
-                                fieldValues,
-                                fieldPresentations,
-                                fieldDialogPresentations,
-                                fieldInlinePresentations,
-                                fieldInlineTooltipPresentations,
-                                fieldFilters,
-                                new ArrayList<>(),
-                                dataset.getFieldContent(),
-                                null,
-                                null,
-                                null,
-                                null,
-                                dataset.getId(),
-                                dataset.getAuthentication());
-                newDataset.setEligibleReasonReason(pickReason);
-                eligibleDatasets.add(newDataset);
-                Set<Dataset> newDatasets;
-                for (AutofillId autofillId : datasetAutofillIds) {
-                    if (map.containsKey(autofillId)) {
-                        newDatasets = map.get(autofillId);
-                    } else {
-                        newDatasets = new ArraySet<>();
+                if (isDatasetAvailable) {
+                    datasetAutofillIds.addAll(additionalDatasetAutofillIds);
+                    eligibleAutofillIds.addAll(additionalEligibleAutofillIds);
+                    Dataset newDataset =
+                            new Dataset(
+                                    fieldIds,
+                                    fieldValues,
+                                    fieldPresentations,
+                                    fieldDialogPresentations,
+                                    fieldInlinePresentations,
+                                    fieldInlineTooltipPresentations,
+                                    fieldFilters,
+                                    new ArrayList<>(),
+                                    dataset.getFieldContent(),
+                                    null,
+                                    null,
+                                    null,
+                                    null,
+                                    dataset.getId(),
+                                    dataset.getAuthentication());
+                    newDataset.setEligibleReasonReason(pickReason);
+                    eligibleDatasets.add(newDataset);
+                    Set<Dataset> newDatasets;
+                    for (AutofillId autofillId : datasetAutofillIds) {
+                        if (map.containsKey(autofillId)) {
+                            newDatasets = map.get(autofillId);
+                        } else {
+                            newDatasets = new LinkedHashSet<>();
+                        }
+                        newDatasets.add(newDataset);
+                        map.put(autofillId, newDatasets);
                     }
-                    newDatasets.add(newDataset);
-                    map.put(autofillId, newDatasets);
                 }
             }
             container.mAutofillIds = eligibleAutofillIds;
@@ -2044,6 +2082,31 @@
         }
     }
 
+    private void copyFieldsFromDataset(
+            Dataset dataset,
+            int index,
+            AutofillId autofillId,
+            ArrayList<AutofillId> fieldIds,
+            ArrayList<AutofillValue> fieldValues,
+            ArrayList<RemoteViews> fieldPresentations,
+            ArrayList<RemoteViews> fieldDialogPresentations,
+            ArrayList<InlinePresentation> fieldInlinePresentations,
+            ArrayList<InlinePresentation> fieldInlineTooltipPresentations,
+            ArrayList<Dataset.DatasetFieldFilter> fieldFilters) {
+        // copy over values
+        fieldIds.add(autofillId);
+        fieldValues.add(dataset.getFieldValues().get(index));
+        //  TODO(b/266379948): might need to make it more efficient by not
+        //  copying over value if it didn't exist. This would require creating
+        //  a getter for the presentations arraylist.
+        fieldPresentations.add(dataset.getFieldPresentation(index));
+        fieldDialogPresentations.add(dataset.getFieldDialogPresentation(index));
+        fieldInlinePresentations.add(dataset.getFieldInlinePresentation(index));
+        fieldInlineTooltipPresentations.add(
+                dataset.getFieldInlineTooltipPresentation(index));
+        fieldFilters.add(dataset.getFilter(index));
+    }
+
     // FillServiceCallbacks
     @Override
     @SuppressWarnings("GuardedBy")
@@ -2401,6 +2464,21 @@
         }
     }
 
+    @Override
+    public void requestHideFillUiWhenDestroyed(AutofillId id) {
+        synchronized (mLock) {
+            // NOTE: We allow this call in a destroyed state as the UI is
+            // asked to go away after we get destroyed, so let it do that.
+            try {
+                mClient.requestHideFillUiWhenDestroyed(this.id, id);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error requesting to hide fill UI", e);
+            }
+
+            mInlineSessionController.hideInlineSuggestionsUiLocked(id);
+        }
+    }
+
     // AutoFillUiCallback
     @Override
     public void cancelSession() {
@@ -2562,10 +2640,7 @@
                     if (sDebug) Slog.d(TAG,  "Updating client state from auth dataset");
                     mClientState = newClientState;
                 }
-                Dataset dataset = (Dataset) result;
-                FillResponse temp = new FillResponse.Builder().addDataset(dataset).build();
-                temp = getEffectiveFillResponse(temp);
-                dataset = temp.getDatasets().get(0);
+                Dataset dataset = getEffectiveDatasetForAuthentication((Dataset) result);
                 final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx);
                 if (!isAuthResultDatasetEphemeral(oldDataset, data)) {
                     authenticatedResponse.getDatasets().set(datasetIdx, dataset);
@@ -2591,6 +2666,39 @@
         }
     }
 
+    Dataset getEffectiveDatasetForAuthentication(Dataset authenticatedDataset) {
+        FillResponse response = new FillResponse.Builder().addDataset(authenticatedDataset).build();
+        response = getEffectiveFillResponse(response);
+        if (DBG) {
+            Slog.d(TAG, "DBG: authenticated effective response: " + response);
+        }
+        if (response == null || response.getDatasets().size() == 0) {
+            Log.wtf(TAG, "No datasets in fill response on authentication. response = "
+                    + (response == null ? "null" : response.toString()));
+            return authenticatedDataset;
+        }
+        List<Dataset> datasets = response.getDatasets();
+        Dataset result = response.getDatasets().get(0);
+        if (datasets.size() > 1) {
+            Dataset.Builder builder = new Dataset.Builder();
+            for (Dataset dataset : datasets) {
+                if (!dataset.getFieldIds().isEmpty()) {
+                    for (int i = 0; i < dataset.getFieldIds().size(); i++) {
+                        builder.setField(dataset.getFieldIds().get(i),
+                                new Field.Builder().setValue(dataset.getFieldValues().get(i))
+                                        .build());
+                    }
+                }
+            }
+            result = builder.setId(authenticatedDataset.getId()).build();
+        }
+
+        if (DBG) {
+            Slog.d(TAG, "DBG: authenticated effective dataset after auth: " + result);
+        }
+        return result;
+    }
+
     /**
      * Returns whether the dataset returned from the authentication result is ephemeral or not.
      * See {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index f92d38d..d479dfb 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -95,6 +95,7 @@
         void requestShowFillUi(AutofillId id, int width, int height,
                 IAutofillWindowPresenter presenter);
         void requestHideFillUi(AutofillId id);
+        void requestHideFillUiWhenDestroyed(AutofillId id);
         void startIntentSenderAndFinishSession(IntentSender intentSender);
         void startIntentSender(IntentSender intentSender, Intent intent);
         void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent);
@@ -289,6 +290,13 @@
                 }
 
                 @Override
+                public void requestHideFillUiWhenDestroyed() {
+                    if (mCallback != null) {
+                        mCallback.requestHideFillUiWhenDestroyed(focusedId);
+                    }
+                }
+
+                @Override
                 public void startIntentSender(IntentSender intentSender) {
                     if (mCallback != null) {
                         mCallback.startIntentSenderAndFinishSession(intentSender);
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index cdfe7bb..cdd9ef4 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -91,6 +91,7 @@
         void requestShowFillUi(int width, int height,
                 IAutofillWindowPresenter windowPresenter);
         void requestHideFillUi();
+        void requestHideFillUiWhenDestroyed();
         void startIntentSender(IntentSender intentSender);
         void dispatchUnhandledKey(KeyEvent keyEvent);
         void cancelSession();
@@ -482,7 +483,7 @@
         }
         mCallback.onDestroy();
         if (notifyClient) {
-            mCallback.requestHideFillUi();
+            mCallback.requestHideFillUiWhenDestroyed();
         }
         mDestroyed = true;
     }
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 995e557..d5aee92 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -2859,8 +2859,7 @@
             if (DEBUG) {
                 Slog.d(
                         TAG,
-                        addUserIdToLogMessage(
-                                mUserId, "Starting backup confirmation UI, token=" + token));
+                        addUserIdToLogMessage(mUserId, "Starting backup confirmation UI"));
             }
             if (!startConfirmationUi(token, FullBackup.FULL_BACKUP_INTENT_ACTION)) {
                 Slog.e(
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 77275e0..4ace391 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -48,7 +48,6 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.Signature;
 import android.net.MacAddress;
 import android.os.Binder;
 import android.os.Bundle;
@@ -56,16 +55,9 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
-import android.util.Log;
-import android.util.PackageUtils;
 import android.util.Slog;
 
-import com.android.internal.util.ArrayUtils;
-
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 /**
  * Class responsible for handling incoming {@link AssociationRequest}s.
@@ -447,31 +439,6 @@
     };
 
     private boolean mayAssociateWithoutPrompt(@NonNull String packageName, @UserIdInt int userId) {
-        // Below we check if the requesting package is allowlisted (usually by the OEM) for creating
-        // CDM associations without user confirmation (prompt).
-        // For this we'll check to config arrays:
-        // - com.android.internal.R.array.config_companionDevicePackages
-        // and
-        // - com.android.internal.R.array.config_companionDeviceCerts.
-        // Both arrays are expected to contain similar number of entries.
-        // config_companionDevicePackages contains package names of the allowlisted packages.
-        // config_companionDeviceCerts contains SHA256 digests of the signatures of the
-        // corresponding packages.
-        // If a package may be signed with one of several certificates, its package name would
-        // appear multiple times in the config_companionDevicePackages, with different entries
-        // (one for each of the valid signing certificates) at the corresponding positions in
-        // config_companionDeviceCerts.
-        final String[] allowlistedPackages = mContext.getResources()
-                .getStringArray(com.android.internal.R.array.config_companionDevicePackages);
-        if (!ArrayUtils.contains(allowlistedPackages, packageName)) {
-            if (DEBUG) {
-                Log.d(TAG, packageName + " is not allowlisted for creating associations "
-                        + "without user confirmation (prompt)");
-                Log.v(TAG, "Allowlisted packages=" + Arrays.toString(allowlistedPackages));
-            }
-            return false;
-        }
-
         // Throttle frequent associations
         final long now = System.currentTimeMillis();
         final List<AssociationInfo> associationForPackage =
@@ -491,40 +458,6 @@
             }
         }
 
-        final String[] allowlistedPackagesSignatureDigests = mContext.getResources()
-                .getStringArray(com.android.internal.R.array.config_companionDeviceCerts);
-        final Set<String> allowlistedSignatureDigestsForRequestingPackage = new HashSet<>();
-        for (int i = 0; i < allowlistedPackages.length; i++) {
-            if (allowlistedPackages[i].equals(packageName)) {
-                final String digest = allowlistedPackagesSignatureDigests[i].replaceAll(":", "");
-                allowlistedSignatureDigestsForRequestingPackage.add(digest);
-            }
-        }
-
-        final Signature[] requestingPackageSignatures = mPackageManager.getPackage(packageName)
-                .getSigningDetails().getSignatures();
-        final String[] requestingPackageSignatureDigests =
-                PackageUtils.computeSignaturesSha256Digests(requestingPackageSignatures);
-
-        boolean requestingPackageSignatureAllowlisted = false;
-        for (String signatureDigest : requestingPackageSignatureDigests) {
-            if (allowlistedSignatureDigestsForRequestingPackage.contains(signatureDigest)) {
-                requestingPackageSignatureAllowlisted = true;
-                break;
-            }
-        }
-
-        if (!requestingPackageSignatureAllowlisted) {
-            Slog.w(TAG, "Certificate mismatch for allowlisted package " + packageName);
-            if (DEBUG) {
-                Log.d(TAG, "  > allowlisted signatures for " + packageName + ": ["
-                        + String.join(", ", allowlistedSignatureDigestsForRequestingPackage)
-                        + "]");
-                Log.d(TAG, "  > actual signatures for " + packageName + ": "
-                        + Arrays.toString(requestingPackageSignatureDigests));
-            }
-        }
-
-        return requestingPackageSignatureAllowlisted;
+        return PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName);
     }
 }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 611541f..9d6283b8 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -244,7 +244,8 @@
         mCompanionAppController = new CompanionApplicationController(
                 context, mAssociationStore, mDevicePresenceMonitor);
         mTransportManager = new CompanionTransportManager(context, mAssociationStore);
-        mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mAssociationStore,
+        mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
+                mPackageManagerInternal, mAssociationStore,
                 mSystemDataTransferRequestStore, mTransportManager);
         // TODO(b/279663946): move context sync to a dedicated system service
         mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java
index 3ab4aa8..db40fc4 100644
--- a/services/companion/java/com/android/server/companion/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/PackageUtils.java
@@ -20,6 +20,7 @@
 import static android.content.pm.PackageManager.GET_CONFIGURATIONS;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 
+import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
 import static com.android.server.companion.CompanionDeviceManagerService.TAG;
 
 import android.Manifest;
@@ -35,20 +36,28 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.PackageInfoFlags;
 import android.content.pm.PackageManager.ResolveInfoFlags;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.Signature;
 import android.os.Binder;
+import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Utility methods for working with {@link PackageInfo}-s.
  */
-final class PackageUtils {
+public final class PackageUtils {
     private static final Intent COMPANION_SERVICE_INTENT =
             new Intent(CompanionDeviceService.SERVICE_INTERFACE);
     private static final String PROPERTY_PRIMARY_TAG =
@@ -141,4 +150,69 @@
             return false;
         }
     }
+
+    /**
+     * Check if the package is allowlisted in the overlay config.
+     * For this we'll check to config arrays:
+     *   - com.android.internal.R.array.config_companionDevicePackages
+     * and
+     *   - com.android.internal.R.array.config_companionDeviceCerts.
+     * Both arrays are expected to contain similar number of entries.
+     * config_companionDevicePackages contains package names of the allowlisted packages.
+     * config_companionDeviceCerts contains SHA256 digests of the signatures of the
+     * corresponding packages.
+     * If a package is signed with one of several certificates, its package name would
+     * appear multiple times in the config_companionDevicePackages, with different entries
+     * (one for each of the valid signing certificates) at the corresponding positions in
+     * config_companionDeviceCerts.
+     */
+    public static boolean isPackageAllowlisted(Context context,
+            PackageManagerInternal packageManagerInternal, @NonNull String packageName) {
+        final String[] allowlistedPackages = context.getResources()
+                .getStringArray(com.android.internal.R.array.config_companionDevicePackages);
+        if (!ArrayUtils.contains(allowlistedPackages, packageName)) {
+            if (DEBUG) {
+                Log.d(TAG, packageName + " is not allowlisted.");
+            }
+            return false;
+        }
+
+        final String[] allowlistedPackagesSignatureDigests = context.getResources()
+                .getStringArray(com.android.internal.R.array.config_companionDeviceCerts);
+        final Set<String> allowlistedSignatureDigestsForRequestingPackage = new HashSet<>();
+        for (int i = 0; i < allowlistedPackages.length; i++) {
+            if (allowlistedPackages[i].equals(packageName)) {
+                final String digest = allowlistedPackagesSignatureDigests[i].replaceAll(":", "");
+                allowlistedSignatureDigestsForRequestingPackage.add(digest);
+            }
+        }
+
+        final Signature[] requestingPackageSignatures = packageManagerInternal.getPackage(
+                        packageName)
+                .getSigningDetails().getSignatures();
+        final String[] requestingPackageSignatureDigests =
+                android.util.PackageUtils.computeSignaturesSha256Digests(
+                        requestingPackageSignatures);
+
+        boolean requestingPackageSignatureAllowlisted = false;
+        for (String signatureDigest : requestingPackageSignatureDigests) {
+            if (allowlistedSignatureDigestsForRequestingPackage.contains(signatureDigest)) {
+                requestingPackageSignatureAllowlisted = true;
+                break;
+            }
+        }
+
+        if (!requestingPackageSignatureAllowlisted) {
+            Slog.w(TAG, "Certificate mismatch for allowlisted package " + packageName);
+            if (DEBUG) {
+                Log.d(TAG, "  > allowlisted signatures for " + packageName + ": ["
+                        + String.join(", ", allowlistedSignatureDigestsForRequestingPackage)
+                        + "]");
+                Log.d(TAG, "  > actual signatures for " + packageName + ": "
+                        + Arrays.toString(requestingPackageSignatureDigests));
+            }
+        }
+
+        return requestingPackageSignatureAllowlisted;
+    }
 }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index dd7d38f..800a3d9 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -38,6 +38,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -50,6 +51,7 @@
 
 import com.android.server.companion.AssociationStore;
 import com.android.server.companion.CompanionDeviceManagerService;
+import com.android.server.companion.PackageUtils;
 import com.android.server.companion.PermissionsUtils;
 import com.android.server.companion.transport.CompanionTransportManager;
 
@@ -80,6 +82,7 @@
                     ".CompanionDeviceDataTransferActivity");
 
     private final Context mContext;
+    private final PackageManagerInternal mPackageManager;
     private final AssociationStore mAssociationStore;
     private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
     private final CompanionTransportManager mTransportManager;
@@ -87,10 +90,12 @@
     private final ExecutorService mExecutor;
 
     public SystemDataTransferProcessor(CompanionDeviceManagerService service,
+            PackageManagerInternal packageManager,
             AssociationStore associationStore,
             SystemDataTransferRequestStore systemDataTransferRequestStore,
             CompanionTransportManager transportManager) {
         mContext = service.getContext();
+        mPackageManager = packageManager;
         mAssociationStore = associationStore;
         mSystemDataTransferRequestStore = systemDataTransferRequestStore;
         mTransportManager = transportManager;
@@ -131,6 +136,11 @@
      */
     public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
             @UserIdInt int userId, int associationId) {
+        if (PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName)) {
+            Slog.i(LOG_TAG, "User consent Intent should be skipped. Returning null.");
+            return null;
+        }
+
         final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
 
         Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId
@@ -174,23 +184,29 @@
         final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
 
         // Check if the request has been consented by the user.
-        List<SystemDataTransferRequest> storedRequests =
-                mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
-                        associationId);
-        boolean hasConsented = false;
-        for (SystemDataTransferRequest storedRequest : storedRequests) {
-            if (storedRequest instanceof PermissionSyncRequest && storedRequest.isUserConsented()) {
-                hasConsented = true;
-                break;
+        if (PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName)) {
+            Slog.i(LOG_TAG, "Skip user consent check due to the same OEM package.");
+        } else {
+            List<SystemDataTransferRequest> storedRequests =
+                    mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
+                            associationId);
+            boolean hasConsented = false;
+            for (SystemDataTransferRequest storedRequest : storedRequests) {
+                if (storedRequest instanceof PermissionSyncRequest
+                        && storedRequest.isUserConsented()) {
+                    hasConsented = true;
+                    break;
+                }
             }
-        }
-        if (!hasConsented) {
-            String message = "User " + userId + " hasn't consented permission sync.";
-            Slog.e(LOG_TAG, message);
-            try {
-                callback.onError(message);
-            } catch (RemoteException ignored) { }
-            return;
+            if (!hasConsented) {
+                String message = "User " + userId + " hasn't consented permission sync.";
+                Slog.e(LOG_TAG, message);
+                try {
+                    callback.onError(message);
+                } catch (RemoteException ignored) {
+                }
+                return;
+            }
         }
 
         // Start permission sync
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index cd2f844..cf7eb51 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -983,8 +983,9 @@
         }
 
         if (virtualDisplayWrapper == null) {
-            throw new IllegalStateException(
-                    "Virtual device doesn't have a virtual display with ID " + displayId);
+            Slog.w(TAG, "Virtual device " + mDeviceId + " doesn't have a virtual display with ID "
+                    + displayId);
+            return;
         }
 
         final long ident = Binder.clearCallingIdentity();
@@ -1094,8 +1095,8 @@
     }
 
     void onEnteringPipBlocked(int uid) {
-        showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_pip_blocked,
-                Toast.LENGTH_LONG, mContext.getMainLooper());
+        // Do nothing. ActivityRecord#checkEnterPictureInPictureState logs that the display does not
+        // support PiP.
     }
 
     void playSoundEffect(int effectType) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 61fc32d..315972c 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -42,6 +42,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityThread;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.app.assist.ActivityId;
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
@@ -50,6 +51,7 @@
 import android.content.pm.ActivityPresentationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.os.Binder;
@@ -69,6 +71,7 @@
 import android.provider.DeviceConfig.Properties;
 import android.provider.Settings;
 import android.service.contentcapture.ActivityEvent.ActivityEventType;
+import android.service.contentcapture.ContentCaptureServiceInfo;
 import android.service.contentcapture.IDataShareCallback;
 import android.service.contentcapture.IDataShareReadAdapter;
 import android.service.voice.VoiceInteractionManagerInternal;
@@ -79,6 +82,7 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.view.contentcapture.ContentCaptureCondition;
+import android.view.contentcapture.ContentCaptureEvent;
 import android.view.contentcapture.ContentCaptureHelper;
 import android.view.contentcapture.ContentCaptureManager;
 import android.view.contentcapture.DataRemovalRequest;
@@ -88,11 +92,17 @@
 import android.view.contentcapture.IDataShareWriteAdapter;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AbstractRemoteService;
 import com.android.internal.infra.GlobalWhitelistState;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
+import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionConsentManager;
+import com.android.server.contentprotection.ContentProtectionPackageManager;
+import com.android.server.contentprotection.RemoteContentProtectionService;
 import com.android.server.infra.AbstractMasterSystemService;
 import com.android.server.infra.FrameworkResourcesServiceNameResolver;
 
@@ -117,7 +127,7 @@
  * with other sources to provide contextual data in other areas of the system
  * such as Autofill.
  */
-public final class ContentCaptureManagerService extends
+public class ContentCaptureManagerService extends
         AbstractMasterSystemService<ContentCaptureManagerService, ContentCapturePerUserService> {
 
     private static final String TAG = ContentCaptureManagerService.class.getSimpleName();
@@ -163,13 +173,35 @@
     private boolean mDisabledByDeviceConfig;
 
     // Device-config settings that are cached and passed back to apps
-    @GuardedBy("mLock") int mDevCfgLoggingLevel;
-    @GuardedBy("mLock") int mDevCfgMaxBufferSize;
-    @GuardedBy("mLock") int mDevCfgIdleFlushingFrequencyMs;
-    @GuardedBy("mLock") int mDevCfgTextChangeFlushingFrequencyMs;
-    @GuardedBy("mLock") int mDevCfgLogHistorySize;
-    @GuardedBy("mLock") int mDevCfgIdleUnbindTimeoutMs;
-    @GuardedBy("mLock") boolean mDevCfgDisableFlushForViewTreeAppearing;
+    @GuardedBy("mLock")
+    int mDevCfgLoggingLevel;
+
+    @GuardedBy("mLock")
+    int mDevCfgMaxBufferSize;
+
+    @GuardedBy("mLock")
+    int mDevCfgIdleFlushingFrequencyMs;
+
+    @GuardedBy("mLock")
+    int mDevCfgTextChangeFlushingFrequencyMs;
+
+    @GuardedBy("mLock")
+    int mDevCfgLogHistorySize;
+
+    @GuardedBy("mLock")
+    int mDevCfgIdleUnbindTimeoutMs;
+
+    @GuardedBy("mLock")
+    boolean mDevCfgDisableFlushForViewTreeAppearing;
+
+    @GuardedBy("mLock")
+    boolean mDevCfgEnableContentProtectionReceiver;
+
+    @GuardedBy("mLock")
+    int mDevCfgContentProtectionAppsBlocklistSize;
+
+    @GuardedBy("mLock")
+    int mDevCfgContentProtectionBufferSize;
 
     private final Executor mDataShareExecutor = Executors.newCachedThreadPool();
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -183,6 +215,12 @@
     final GlobalContentCaptureOptions mGlobalContentCaptureOptions =
             new GlobalContentCaptureOptions();
 
+    @Nullable private final ComponentName mContentProtectionServiceComponentName;
+
+    @Nullable private final ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
+
+    @Nullable private final ContentProtectionConsentManager mContentProtectionConsentManager;
+
     public ContentCaptureManagerService(@NonNull Context context) {
         super(context, new FrameworkResourcesServiceNameResolver(context,
                 com.android.internal.R.string.config_defaultContentCaptureService),
@@ -220,6 +258,23 @@
                     mServiceNameResolver.getServiceName(userId),
                     mServiceNameResolver.isTemporary(userId));
         }
+
+        if (getEnableContentProtectionReceiverLocked()) {
+            mContentProtectionServiceComponentName = getContentProtectionServiceComponentName();
+            if (mContentProtectionServiceComponentName != null) {
+                mContentProtectionBlocklistManager = createContentProtectionBlocklistManager();
+                mContentProtectionBlocklistManager.updateBlocklist(
+                        mDevCfgContentProtectionAppsBlocklistSize);
+                mContentProtectionConsentManager = createContentProtectionConsentManager();
+            } else {
+                mContentProtectionBlocklistManager = null;
+                mContentProtectionConsentManager = null;
+            }
+        } else {
+            mContentProtectionServiceComponentName = null;
+            mContentProtectionBlocklistManager = null;
+            mContentProtectionConsentManager = null;
+        }
     }
 
     @Override // from AbstractMasterSystemService
@@ -362,6 +417,11 @@
                 case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT:
                 case ContentCaptureManager
                         .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING:
+                case ContentCaptureManager
+                        .DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER:
+                case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE:
+                case ContentCaptureManager
+                        .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE:
                     setFineTuneParamsFromDeviceConfig();
                     return;
                 default:
@@ -370,41 +430,84 @@
         }
     }
 
-    private void setFineTuneParamsFromDeviceConfig() {
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void setFineTuneParamsFromDeviceConfig() {
         synchronized (mLock) {
-            mDevCfgMaxBufferSize = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE,
-                    ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE);
-            mDevCfgIdleFlushingFrequencyMs = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY,
-                    ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS);
-            mDevCfgTextChangeFlushingFrequencyMs = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY,
-                    ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS);
-            mDevCfgLogHistorySize = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE, 20);
-            mDevCfgIdleUnbindTimeoutMs = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT,
-                    (int) AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS);
-            mDevCfgDisableFlushForViewTreeAppearing = DeviceConfig.getBoolean(
-                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
-                    ContentCaptureManager
-                        .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
-                    false);
+            mDevCfgMaxBufferSize =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager.DEVICE_CONFIG_PROPERTY_MAX_BUFFER_SIZE,
+                            ContentCaptureManager.DEFAULT_MAX_BUFFER_SIZE);
+            mDevCfgIdleFlushingFrequencyMs =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_FLUSH_FREQUENCY,
+                            ContentCaptureManager.DEFAULT_IDLE_FLUSHING_FREQUENCY_MS);
+            mDevCfgTextChangeFlushingFrequencyMs =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager
+                                    .DEVICE_CONFIG_PROPERTY_TEXT_CHANGE_FLUSH_FREQUENCY,
+                            ContentCaptureManager.DEFAULT_TEXT_CHANGE_FLUSHING_FREQUENCY_MS);
+            mDevCfgLogHistorySize =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager.DEVICE_CONFIG_PROPERTY_LOG_HISTORY_SIZE,
+                            20);
+            mDevCfgIdleUnbindTimeoutMs =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT,
+                            (int) AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS);
+            mDevCfgDisableFlushForViewTreeAppearing =
+                    DeviceConfig.getBoolean(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager
+                                    .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING,
+                            false);
+            mDevCfgEnableContentProtectionReceiver =
+                    DeviceConfig.getBoolean(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager
+                                    .DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
+                            ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER);
+            mDevCfgContentProtectionAppsBlocklistSize =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager
+                                    .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE,
+                            ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE);
+            // mContentProtectionBlocklistManager.updateBlocklist not called on purpose here to keep
+            // it immutable at this point
+            mDevCfgContentProtectionBufferSize =
+                    DeviceConfig.getInt(
+                            DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+                            ContentCaptureManager
+                                    .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE,
+                            ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE);
             if (verbose) {
-                Slog.v(TAG, "setFineTuneParamsFromDeviceConfig(): "
-                        + "bufferSize=" + mDevCfgMaxBufferSize
-                        + ", idleFlush=" + mDevCfgIdleFlushingFrequencyMs
-                        + ", textFluxh=" + mDevCfgTextChangeFlushingFrequencyMs
-                        + ", logHistory=" + mDevCfgLogHistorySize
-                        + ", idleUnbindTimeoutMs=" + mDevCfgIdleUnbindTimeoutMs
-                        + ", disableFlushForViewTreeAppearing="
-                        + mDevCfgDisableFlushForViewTreeAppearing);
+                Slog.v(
+                        TAG,
+                        "setFineTuneParamsFromDeviceConfig(): "
+                                + "bufferSize="
+                                + mDevCfgMaxBufferSize
+                                + ", idleFlush="
+                                + mDevCfgIdleFlushingFrequencyMs
+                                + ", textFluxh="
+                                + mDevCfgTextChangeFlushingFrequencyMs
+                                + ", logHistory="
+                                + mDevCfgLogHistorySize
+                                + ", idleUnbindTimeoutMs="
+                                + mDevCfgIdleUnbindTimeoutMs
+                                + ", disableFlushForViewTreeAppearing="
+                                + mDevCfgDisableFlushForViewTreeAppearing
+                                + ", enableContentProtectionReceiver="
+                                + mDevCfgEnableContentProtectionReceiver
+                                + ", contentProtectionAppsBlocklistSize="
+                                + mDevCfgContentProtectionAppsBlocklistSize
+                                + ", contentProtectionBufferSize="
+                                + mDevCfgContentProtectionBufferSize);
             }
         }
     }
@@ -645,24 +748,148 @@
 
         final String prefix2 = prefix + "  ";
 
-        pw.print(prefix); pw.print("Users disabled by Settings: "); pw.println(mDisabledBySettings);
-        pw.print(prefix); pw.println("DeviceConfig Settings: ");
-        pw.print(prefix2); pw.print("disabled: "); pw.println(mDisabledByDeviceConfig);
-        pw.print(prefix2); pw.print("loggingLevel: "); pw.println(mDevCfgLoggingLevel);
-        pw.print(prefix2); pw.print("maxBufferSize: "); pw.println(mDevCfgMaxBufferSize);
-        pw.print(prefix2); pw.print("idleFlushingFrequencyMs: ");
+        pw.print(prefix);
+        pw.print("Users disabled by Settings: ");
+        pw.println(mDisabledBySettings);
+        pw.print(prefix);
+        pw.println("DeviceConfig Settings: ");
+        pw.print(prefix2);
+        pw.print("disabled: ");
+        pw.println(mDisabledByDeviceConfig);
+        pw.print(prefix2);
+        pw.print("loggingLevel: ");
+        pw.println(mDevCfgLoggingLevel);
+        pw.print(prefix2);
+        pw.print("maxBufferSize: ");
+        pw.println(mDevCfgMaxBufferSize);
+        pw.print(prefix2);
+        pw.print("idleFlushingFrequencyMs: ");
         pw.println(mDevCfgIdleFlushingFrequencyMs);
-        pw.print(prefix2); pw.print("textChangeFlushingFrequencyMs: ");
+        pw.print(prefix2);
+        pw.print("textChangeFlushingFrequencyMs: ");
         pw.println(mDevCfgTextChangeFlushingFrequencyMs);
-        pw.print(prefix2); pw.print("logHistorySize: "); pw.println(mDevCfgLogHistorySize);
-        pw.print(prefix2); pw.print("idleUnbindTimeoutMs: ");
+        pw.print(prefix2);
+        pw.print("logHistorySize: ");
+        pw.println(mDevCfgLogHistorySize);
+        pw.print(prefix2);
+        pw.print("idleUnbindTimeoutMs: ");
         pw.println(mDevCfgIdleUnbindTimeoutMs);
-        pw.print(prefix2); pw.print("disableFlushForViewTreeAppearing: ");
+        pw.print(prefix2);
+        pw.print("disableFlushForViewTreeAppearing: ");
         pw.println(mDevCfgDisableFlushForViewTreeAppearing);
-        pw.print(prefix); pw.println("Global Options:");
+        pw.print(prefix2);
+        pw.print("enableContentProtectionReceiver: ");
+        pw.println(mDevCfgEnableContentProtectionReceiver);
+        pw.print(prefix2);
+        pw.print("contentProtectionAppsBlocklistSize: ");
+        pw.println(mDevCfgContentProtectionAppsBlocklistSize);
+        pw.print(prefix2);
+        pw.print("contentProtectionBufferSize: ");
+        pw.println(mDevCfgContentProtectionBufferSize);
+        pw.print(prefix);
+        pw.println("Global Options:");
         mGlobalContentCaptureOptions.dump(prefix2, pw);
     }
 
+    /**
+     * Used by the constructor in order to be able to override the value in the tests.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @GuardedBy("mLock")
+    protected boolean getEnableContentProtectionReceiverLocked() {
+        return mDevCfgEnableContentProtectionReceiver;
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() {
+        return new ContentProtectionBlocklistManager(
+                new ContentProtectionPackageManager(getContext()));
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    protected ContentProtectionConsentManager createContentProtectionConsentManager() {
+        // Same handler as used by AbstractMasterSystemService
+        return new ContentProtectionConsentManager(
+                BackgroundThread.getHandler(),
+                getContext().getContentResolver(),
+                LocalServices.getService(DevicePolicyManagerInternal.class));
+    }
+
+    @Nullable
+    private ComponentName getContentProtectionServiceComponentName() {
+        String flatComponentName = getContentProtectionServiceFlatComponentName();
+        return ComponentName.unflattenFromString(flatComponentName);
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @Nullable
+    protected String getContentProtectionServiceFlatComponentName() {
+        return getContext()
+                .getString(com.android.internal.R.string.config_defaultContentProtectionService);
+    }
+
+    /**
+     * Can also throw runtime exceptions such as {@link SecurityException}.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    protected ContentCaptureServiceInfo createContentProtectionServiceInfo(
+            @NonNull ComponentName componentName) throws PackageManager.NameNotFoundException {
+        return new ContentCaptureServiceInfo(
+                getContext(), componentName, /* isTemp= */ false, UserHandle.getCallingUserId());
+    }
+
+    @Nullable
+    private RemoteContentProtectionService createRemoteContentProtectionService() {
+        if (mContentProtectionServiceComponentName == null) {
+            // This case should not be possible but make sure
+            return null;
+        }
+        synchronized (mLock) {
+            if (!mDevCfgEnableContentProtectionReceiver) {
+                return null;
+            }
+        }
+
+        // Check permissions by trying to construct {@link ContentCaptureServiceInfo}
+        try {
+            createContentProtectionServiceInfo(mContentProtectionServiceComponentName);
+        } catch (Exception ex) {
+            // Swallow, exception was already logged
+            return null;
+        }
+
+        return createRemoteContentProtectionService(mContentProtectionServiceComponentName);
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    protected RemoteContentProtectionService createRemoteContentProtectionService(
+            @NonNull ComponentName componentName) {
+        return new RemoteContentProtectionService(
+                getContext(),
+                componentName,
+                UserHandle.getCallingUserId(),
+                isBindInstantServiceAllowed());
+    }
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    protected ContentCaptureManagerServiceStub getContentCaptureManagerServiceStub() {
+        return mContentCaptureManagerServiceStub;
+    }
+
     final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
 
         @Override
@@ -896,6 +1123,19 @@
         public void setDefaultServiceEnabled(@UserIdInt int userId, boolean enabled) {
             ContentCaptureManagerService.this.setDefaultServiceEnabled(userId, enabled);
         }
+
+        @Override
+        public void onLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
+            RemoteContentProtectionService service = createRemoteContentProtectionService();
+            if (service == null) {
+                return;
+            }
+            try {
+                service.onLoginDetected(events);
+            } catch (Exception ex) {
+                Slog.e(TAG, "Failed to call remote service", ex);
+            }
+        }
     }
 
     private final class LocalService extends ContentCaptureManagerInternal {
@@ -984,14 +1224,21 @@
         @GuardedBy("mGlobalWhitelistStateLock")
         public ContentCaptureOptions getOptions(@UserIdInt int userId,
                 @NonNull String packageName) {
-            boolean packageWhitelisted;
+            boolean isContentCaptureReceiverEnabled;
+            boolean isContentProtectionReceiverEnabled;
             ArraySet<ComponentName> whitelistedComponents = null;
+
             synchronized (mGlobalWhitelistStateLock) {
-                packageWhitelisted = isWhitelisted(userId, packageName);
-                if (!packageWhitelisted) {
-                    // Full package is not allowlisted: check individual components first
+                isContentCaptureReceiverEnabled =
+                        isContentCaptureReceiverEnabled(userId, packageName);
+                isContentProtectionReceiverEnabled =
+                        isContentProtectionReceiverEnabled(userId, packageName);
+
+                if (!isContentCaptureReceiverEnabled) {
+                    // Full package is not allowlisted: check individual components next
                     whitelistedComponents = getWhitelistedComponents(userId, packageName);
-                    if (whitelistedComponents == null
+                    if (!isContentProtectionReceiverEnabled
+                            && whitelistedComponents == null
                             && packageName.equals(mServicePackages.get(userId))) {
                         // No components allowlisted either, but let it go because it's the
                         // service's own package
@@ -1010,7 +1257,9 @@
                 }
             }
 
-            if (!packageWhitelisted && whitelistedComponents == null) {
+            if (!isContentCaptureReceiverEnabled
+                    && !isContentProtectionReceiverEnabled
+                    && whitelistedComponents == null) {
                 // No can do!
                 if (verbose) {
                     Slog.v(TAG, "getOptionsForPackage(" + packageName + "): not whitelisted");
@@ -1019,11 +1268,19 @@
             }
 
             synchronized (mLock) {
-                final ContentCaptureOptions options = new ContentCaptureOptions(mDevCfgLoggingLevel,
-                        mDevCfgMaxBufferSize, mDevCfgIdleFlushingFrequencyMs,
-                        mDevCfgTextChangeFlushingFrequencyMs, mDevCfgLogHistorySize,
-                        mDevCfgDisableFlushForViewTreeAppearing,
-                        whitelistedComponents);
+                final ContentCaptureOptions options =
+                        new ContentCaptureOptions(
+                                mDevCfgLoggingLevel,
+                                mDevCfgMaxBufferSize,
+                                mDevCfgIdleFlushingFrequencyMs,
+                                mDevCfgTextChangeFlushingFrequencyMs,
+                                mDevCfgLogHistorySize,
+                                mDevCfgDisableFlushForViewTreeAppearing,
+                                isContentCaptureReceiverEnabled || whitelistedComponents != null,
+                                new ContentCaptureOptions.ContentProtectionOptions(
+                                        isContentProtectionReceiverEnabled,
+                                        mDevCfgContentProtectionBufferSize),
+                                whitelistedComponents);
                 if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options);
                 return options;
             }
@@ -1042,6 +1299,39 @@
                 }
             }
         }
+
+        @Override // from GlobalWhitelistState
+        public boolean isWhitelisted(@UserIdInt int userId, @NonNull String packageName) {
+            return isContentCaptureReceiverEnabled(userId, packageName)
+                    || isContentProtectionReceiverEnabled(userId, packageName);
+        }
+
+        @Override // from GlobalWhitelistState
+        public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) {
+            return super.isWhitelisted(userId, componentName)
+                    || isContentProtectionReceiverEnabled(userId, componentName.getPackageName());
+        }
+
+        private boolean isContentCaptureReceiverEnabled(
+                @UserIdInt int userId, @NonNull String packageName) {
+            return super.isWhitelisted(userId, packageName);
+        }
+
+        private boolean isContentProtectionReceiverEnabled(
+                @UserIdInt int userId, @NonNull String packageName) {
+            if (mContentProtectionServiceComponentName == null
+                    || mContentProtectionBlocklistManager == null
+                    || mContentProtectionConsentManager == null) {
+                return false;
+            }
+            synchronized (mLock) {
+                if (!mDevCfgEnableContentProtectionReceiver) {
+                    return false;
+                }
+            }
+            return mContentProtectionConsentManager.isConsentGranted(userId)
+                    && mContentProtectionBlocklistManager.isAllowed(packageName);
+        }
     }
 
     private static class DataShareCallbackDelegate extends IDataShareCallback.Stub {
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
new file mode 100644
index 0000000..a0fd28b
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageInfo;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Manages whether the content protection is enabled for an app using a blocklist.
+ *
+ * @hide
+ */
+public class ContentProtectionBlocklistManager {
+
+    private static final String TAG = "ContentProtectionBlocklistManager";
+
+    private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
+            "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
+
+    @NonNull private final ContentProtectionPackageManager mContentProtectionPackageManager;
+
+    @Nullable private Set<String> mPackageNameBlocklist;
+
+    public ContentProtectionBlocklistManager(
+            @NonNull ContentProtectionPackageManager contentProtectionPackageManager) {
+        mContentProtectionPackageManager = contentProtectionPackageManager;
+    }
+
+    public boolean isAllowed(@NonNull String packageName) {
+        if (mPackageNameBlocklist == null) {
+            // List not loaded or failed to load, don't run on anything
+            return false;
+        }
+        if (mPackageNameBlocklist.contains(packageName)) {
+            return false;
+        }
+        PackageInfo packageInfo = mContentProtectionPackageManager.getPackageInfo(packageName);
+        if (packageInfo == null) {
+            return false;
+        }
+        if (!mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo)) {
+            return false;
+        }
+        if (mContentProtectionPackageManager.isSystemApp(packageInfo)) {
+            return false;
+        }
+        if (mContentProtectionPackageManager.isUpdatedSystemApp(packageInfo)) {
+            return false;
+        }
+        return true;
+    }
+
+    public void updateBlocklist(int blocklistSize) {
+        Slog.i(TAG, "Blocklist size updating to: " + blocklistSize);
+        mPackageNameBlocklist = readPackageNameBlocklist(blocklistSize);
+    }
+
+    @Nullable
+    private Set<String> readPackageNameBlocklist(int blocklistSize) {
+        if (blocklistSize <= 0) {
+            // Explicitly requested an empty blocklist
+            return Collections.emptySet();
+        }
+        List<String> lines = readLinesFromRawFile(PACKAGE_NAME_BLOCKLIST_FILENAME);
+        if (lines == null) {
+            return null;
+        }
+        return lines.stream().limit(blocklistSize).collect(Collectors.toSet());
+    }
+
+    @VisibleForTesting
+    @Nullable
+    protected List<String> readLinesFromRawFile(@NonNull String filename) {
+        try (FileReader fileReader = new FileReader(filename);
+                BufferedReader bufferedReader = new BufferedReader(fileReader)) {
+            return bufferedReader
+                    .lines()
+                    .map(line -> line.trim())
+                    .filter(line -> !line.isBlank())
+                    .collect(Collectors.toList());
+        } catch (Exception ex) {
+            Slog.e(TAG, "Failed to read: " + filename, ex);
+            return null;
+        }
+    }
+}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java
new file mode 100644
index 0000000..2eb758c
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionConsentManager.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Manages consent for content protection.
+ *
+ * @hide
+ */
+public class ContentProtectionConsentManager {
+
+    private static final String TAG = "ContentProtectionConsentManager";
+
+    private static final String KEY_PACKAGE_VERIFIER_USER_CONSENT = "package_verifier_user_consent";
+
+    @NonNull private final ContentResolver mContentResolver;
+
+    @NonNull private final DevicePolicyManagerInternal mDevicePolicyManagerInternal;
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull
+    public final ContentObserver mContentObserver;
+
+    private volatile boolean mCachedPackageVerifierConsent;
+
+    public ContentProtectionConsentManager(
+            @NonNull Handler handler,
+            @NonNull ContentResolver contentResolver,
+            @NonNull DevicePolicyManagerInternal devicePolicyManagerInternal) {
+        mContentResolver = contentResolver;
+        mDevicePolicyManagerInternal = devicePolicyManagerInternal;
+        mContentObserver = new SettingsObserver(handler);
+
+        contentResolver.registerContentObserver(
+                Settings.Global.getUriFor(KEY_PACKAGE_VERIFIER_USER_CONSENT),
+                /* notifyForDescendants= */ false,
+                mContentObserver,
+                UserHandle.USER_ALL);
+        mCachedPackageVerifierConsent = isPackageVerifierConsentGranted();
+    }
+
+    /**
+     * Returns true if all the consents are granted
+     */
+    public boolean isConsentGranted(@UserIdInt int userId) {
+        return mCachedPackageVerifierConsent && !isUserOrganizationManaged(userId);
+    }
+
+    private boolean isPackageVerifierConsentGranted() {
+        // Not always cached internally
+        return Settings.Global.getInt(
+                        mContentResolver, KEY_PACKAGE_VERIFIER_USER_CONSENT, /* def= */ 0)
+                >= 1;
+    }
+
+    private boolean isUserOrganizationManaged(@UserIdInt int userId) {
+        // Cached internally
+        return mDevicePolicyManagerInternal.isUserOrganizationManaged(userId);
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) {
+            final String property = uri.getLastPathSegment();
+            if (property == null) {
+                return;
+            }
+            switch (property) {
+                case KEY_PACKAGE_VERIFIER_USER_CONSENT:
+                    mCachedPackageVerifierConsent = isPackageVerifierConsentGranted();
+                    return;
+                default:
+                    Slog.w(TAG, "Ignoring unexpected property: " + property);
+            }
+        }
+    }
+}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
new file mode 100644
index 0000000..4ebac07
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManager.PackageInfoFlags;
+import android.util.Slog;
+
+import java.util.Arrays;
+
+/**
+ * Basic package manager for content protection using content capture.
+ *
+ * @hide
+ */
+public class ContentProtectionPackageManager {
+
+    private static final String TAG = "ContentProtectionPackageManager";
+
+    private static final PackageInfoFlags PACKAGE_INFO_FLAGS =
+            PackageInfoFlags.of(PackageManager.GET_PERMISSIONS);
+
+    @NonNull private final PackageManager mPackageManager;
+
+    public ContentProtectionPackageManager(@NonNull Context context) {
+        mPackageManager = context.getPackageManager();
+    }
+
+    @Nullable
+    public PackageInfo getPackageInfo(@NonNull String packageName) {
+        try {
+            return mPackageManager.getPackageInfo(packageName, PACKAGE_INFO_FLAGS);
+        } catch (NameNotFoundException ex) {
+            Slog.w(TAG, "Package info not found for: " + packageName, ex);
+            return null;
+        }
+    }
+
+    public boolean isSystemApp(@NonNull PackageInfo packageInfo) {
+        return packageInfo.applicationInfo != null && isSystemApp(packageInfo.applicationInfo);
+    }
+
+    private boolean isSystemApp(@NonNull ApplicationInfo applicationInfo) {
+        return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
+    public boolean isUpdatedSystemApp(@NonNull PackageInfo packageInfo) {
+        return packageInfo.applicationInfo != null
+                && isUpdatedSystemApp(packageInfo.applicationInfo);
+    }
+
+    private boolean isUpdatedSystemApp(@NonNull ApplicationInfo applicationInfo) {
+        return (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+    }
+
+    public boolean hasRequestedInternetPermissions(@NonNull PackageInfo packageInfo) {
+        return packageInfo.requestedPermissions != null
+                && Arrays.asList(packageInfo.requestedPermissions)
+                        .contains(Manifest.permission.INTERNET);
+    }
+}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java
new file mode 100644
index 0000000..dd5545d
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.service.contentcapture.ContentCaptureService;
+import android.service.contentcapture.IContentProtectionService;
+import android.util.Slog;
+import android.view.contentcapture.ContentCaptureEvent;
+
+import com.android.internal.infra.ServiceConnector;
+
+import java.time.Duration;
+
+/**
+ * Connector for the remote content protection service.
+ *
+ * @hide
+ */
+public class RemoteContentProtectionService
+        extends ServiceConnector.Impl<IContentProtectionService> {
+
+    private static final String TAG = RemoteContentProtectionService.class.getSimpleName();
+
+    private static final Duration AUTO_DISCONNECT_TIMEOUT = Duration.ofSeconds(3);
+
+    @NonNull private final ComponentName mComponentName;
+
+    public RemoteContentProtectionService(
+            @NonNull Context context,
+            @NonNull ComponentName componentName,
+            int userId,
+            boolean bindAllowInstant) {
+        super(
+                context,
+                new Intent(ContentCaptureService.PROTECTION_SERVICE_INTERFACE)
+                        .setComponent(componentName),
+                bindAllowInstant ? Context.BIND_ALLOW_INSTANT : 0,
+                userId,
+                IContentProtectionService.Stub::asInterface);
+        mComponentName = componentName;
+    }
+
+    @Override // from ServiceConnector.Impl
+    protected long getAutoDisconnectTimeoutMs() {
+        return AUTO_DISCONNECT_TIMEOUT.toMillis();
+    }
+
+    @Override // from ServiceConnector.Impl
+    protected void onServiceConnectionStatusChanged(
+            @NonNull IContentProtectionService service, boolean isConnected) {
+        Slog.i(
+                TAG,
+                "Connection status for: "
+                        + mComponentName
+                        + " changed to: "
+                        + (isConnected ? "connected" : "disconnected"));
+    }
+
+    public void onLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
+        run(service -> service.onLoginDetected(events));
+    }
+}
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index 27215b2..cbacee6 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -44,7 +44,7 @@
     private static final String TAG = "DynamicSystemService";
     private static final long MINIMUM_SD_MB = (30L << 10);
     private static final int GSID_ROUGH_TIMEOUT_MS = 8192;
-    private static final String PATH_DEFAULT = "/data/gsi/";
+    private static final String PATH_DEFAULT = "/data/gsi/dsu/";
     private Context mContext;
     private String mInstallPath, mDsuSlot;
     private volatile IGsiService mGsiService;
@@ -226,9 +226,7 @@
         IGsiService gsiService = getGsiService();
         if (enable) {
             try {
-                if (mDsuSlot == null) {
-                    mDsuSlot = gsiService.getActiveDsuSlot();
-                }
+                getActiveDsuSlot();
                 GsiServiceCallback callback = new GsiServiceCallback();
                 synchronized (callback) {
                     gsiService.enableGsiAsync(oneShot, mDsuSlot, callback);
@@ -287,4 +285,15 @@
 
         return getGsiService().suggestScratchSize();
     }
+
+    @Override
+    @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+    public String getActiveDsuSlot() throws RemoteException {
+        super.getActiveDsuSlot_enforcePermission();
+
+        if (mDsuSlot == null) {
+            mDsuSlot = getGsiService().getActiveDsuSlot();
+        }
+        return mDsuSlot;
+    }
 }
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 6fd6afe..754a7ed 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -159,9 +159,10 @@
 
     private int getAllowedUid() {
         final UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
-        final int mainUserId = umInternal.getMainUserId();
+        int mainUserId = umInternal.getMainUserId();
         if (mainUserId < 0) {
-            return -1;
+            // If main user is not defined. Use the SYSTEM user instead.
+            mainUserId = UserHandle.USER_SYSTEM;
         }
         String allowedPackage = mContext.getResources()
                 .getString(R.string.config_persistentDataPackageName);
diff --git a/services/core/java/com/android/server/WallpaperUpdateReceiver.java b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
index 9917892..2812233 100644
--- a/services/core/java/com/android/server/WallpaperUpdateReceiver.java
+++ b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
@@ -88,7 +88,7 @@
         } else {
             //live wallpaper
             ComponentName currCN = info.getComponent();
-            ComponentName defaultCN = WallpaperManager.getDefaultWallpaperComponent(context);
+            ComponentName defaultCN = WallpaperManager.getCmfDefaultWallpaperComponent(context);
             if (!currCN.equals(defaultCN)) {
                 return true;
             }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d5f41a1..66ea4d0 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -256,7 +256,7 @@
 public final class ActiveServices {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActiveServices" : TAG_AM;
     private static final String TAG_MU = TAG + POSTFIX_MU;
-    private static final String TAG_SERVICE = TAG + POSTFIX_SERVICE;
+    static final String TAG_SERVICE = TAG + POSTFIX_SERVICE;
     private static final String TAG_SERVICE_EXECUTING = TAG + POSTFIX_SERVICE_EXECUTING;
 
     private static final boolean DEBUG_DELAYED_SERVICE = DEBUG_SERVICE;
@@ -850,8 +850,7 @@
         // Service.startForeground()), at that point we will consult the BFSL check and the timeout
         // and make the necessary decisions.
         setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r, userId,
-                backgroundStartPrivileges, false /* isBindService */,
-                !fgRequired /* isStartService */);
+                backgroundStartPrivileges, false /* isBindService */);
 
         if (!mAm.mUserController.exists(r.userId)) {
             Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
@@ -894,7 +893,7 @@
 
         if (fgRequired) {
             logFgsBackgroundStart(r);
-            if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) {
+            if (!r.isFgsAllowedStart() && isBgFgsRestrictionEnabled(r)) {
                 String msg = "startForegroundService() not allowed due to "
                         + "mAllowStartForeground false: service "
                         + r.shortInstanceName;
@@ -1060,7 +1059,7 @@
             // Use that as a shortcut if possible to avoid having to recheck all the conditions.
             final boolean whileInUseAllowsUiJobScheduling =
                     ActivityManagerService.doesReasonCodeAllowSchedulingUserInitiatedJobs(
-                            r.mAllowWhileInUsePermissionInFgsReason);
+                            r.getFgsAllowWIU());
             r.updateAllowUiJobScheduling(whileInUseAllowsUiJobScheduling
                     || mAm.canScheduleUserInitiatedJobs(callingUid, callingPid, callingPackage));
         } else {
@@ -2178,12 +2177,12 @@
                         // on a SHORT_SERVICE FGS.
 
                         // See if the app could start an FGS or not.
-                        r.mAllowStartForeground = REASON_DENIED;
+                        r.clearFgsAllowStart();
                         setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
                                 r.appInfo.uid, r.intent.getIntent(), r, r.userId,
                                 BackgroundStartPrivileges.NONE,
-                                false /* isBindService */, false /* isStartService */);
-                        if (r.mAllowStartForeground == REASON_DENIED) {
+                                false /* isBindService */);
+                        if (!r.isFgsAllowedStart()) {
                             Slog.w(TAG_SERVICE, "FGS type change to/from SHORT_SERVICE: "
                                     + " BFSL DENIED.");
                         } else {
@@ -2191,13 +2190,13 @@
                                 Slog.w(TAG_SERVICE, "FGS type change to/from SHORT_SERVICE: "
                                         + " BFSL Allowed: "
                                         + PowerExemptionManager.reasonCodeToString(
-                                                r.mAllowStartForeground));
+                                                r.getFgsAllowStart()));
                             }
                         }
 
                         final boolean fgsStartAllowed =
                                 !isBgFgsRestrictionEnabledForService
-                                        || (r.mAllowStartForeground != REASON_DENIED);
+                                        || r.isFgsAllowedStart();
 
                         if (fgsStartAllowed) {
                             if (isNewTypeShortFgs) {
@@ -2246,7 +2245,7 @@
                                 setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
                                         r.appInfo.uid, r.intent.getIntent(), r, r.userId,
                                         BackgroundStartPrivileges.NONE,
-                                        false /* isBindService */, false /* isStartService */);
+                                        false /* isBindService */);
                                 final String temp = "startForegroundDelayMs:" + delayMs;
                                 if (r.mInfoAllowStartForeground != null) {
                                     r.mInfoAllowStartForeground += "; " + temp;
@@ -2266,20 +2265,21 @@
                         setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
                                 r.appInfo.uid, r.intent.getIntent(), r, r.userId,
                                 BackgroundStartPrivileges.NONE,
-                                false /* isBindService */, false /* isStartService */);
+                                false /* isBindService */);
                     }
 
                     // If the foreground service is not started from TOP process, do not allow it to
                     // have while-in-use location/camera/microphone access.
-                    if (!r.mAllowWhileInUsePermissionInFgs) {
+                    if (!r.isFgsAllowedWIU()) {
                         Slog.w(TAG,
                                 "Foreground service started from background can not have "
                                         + "location/camera/microphone access: service "
                                         + r.shortInstanceName);
                     }
+                    r.maybeLogFgsLogicChange();
                     if (!bypassBfslCheck) {
                         logFgsBackgroundStart(r);
-                        if (r.mAllowStartForeground == REASON_DENIED
+                        if (!r.isFgsAllowedStart()
                                 && isBgFgsRestrictionEnabledForService) {
                             final String msg = "Service.startForeground() not allowed due to "
                                     + "mAllowStartForeground false: service "
@@ -2378,9 +2378,9 @@
                         // The logging of FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER event could
                         // be deferred, make a copy of mAllowStartForeground and
                         // mAllowWhileInUsePermissionInFgs.
-                        r.mAllowStartForegroundAtEntering = r.mAllowStartForeground;
+                        r.mAllowStartForegroundAtEntering = r.getFgsAllowStart();
                         r.mAllowWhileInUsePermissionInFgsAtEntering =
-                                r.mAllowWhileInUsePermissionInFgs;
+                                r.isFgsAllowedWIU();
                         r.mStartForegroundCount++;
                         r.mFgsEnterTime = SystemClock.uptimeMillis();
                         if (!stopProcStatsOp) {
@@ -2414,7 +2414,7 @@
                     // Even if the service is already a FGS, we need to update the notification,
                     // so we need to call it again.
                     signalForegroundServiceObserversLocked(r);
-                    r.postNotification();
+                    r.postNotification(true);
                     if (r.app != null) {
                         updateServiceForegroundLocked(psr, true);
                     }
@@ -2472,7 +2472,7 @@
                 } else if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
                     // if it's been deferred, force to visibility
                     if (!r.mFgsNotificationShown) {
-                        r.postNotification();
+                        r.postNotification(false);
                     }
                     dropFgsNotificationStateLocked(r);
                     if ((flags & Service.STOP_FOREGROUND_DETACH) != 0) {
@@ -2501,12 +2501,12 @@
                         FGS_STOP_REASON_STOP_FOREGROUND,
                         FGS_TYPE_POLICY_CHECK_UNKNOWN);
 
-                // foregroundServiceType is used in logFGSStateChangeLocked(), so we can't clear it
-                // earlier.
-                r.foregroundServiceType = 0;
                 synchronized (mFGSLogger) {
                     mFGSLogger.logForegroundServiceStop(r.appInfo.uid, r);
                 }
+                // foregroundServiceType is used in logFGSStateChangeLocked(), so we can't clear it
+                // earlier.
+                r.foregroundServiceType = 0;
                 r.mFgsNotificationWasDeferred = false;
                 signalForegroundServiceObserversLocked(r);
                 resetFgsRestrictionLocked(r);
@@ -2558,7 +2558,7 @@
                 policy.getForegroundServiceTypePolicyInfo(type, defaultToType);
         final @ForegroundServicePolicyCheckCode int code = policy.checkForegroundServiceTypePolicy(
                 mAm.mContext, r.packageName, r.app.uid, r.app.getPid(),
-                r.mAllowWhileInUsePermissionInFgs, policyInfo);
+                r.isFgsAllowedWIU(), policyInfo);
         RuntimeException exception = null;
         switch (code) {
             case FGS_TYPE_POLICY_CHECK_DEPRECATED: {
@@ -2916,7 +2916,7 @@
                         // in the interval, so we lazy check whether we still need to show
                         // the notification.
                         if (r.isForeground && r.app != null) {
-                            r.postNotification();
+                            r.postNotification(true);
                             r.mFgsNotificationShown = true;
                         } else {
                             if (DEBUG_FOREGROUND_SERVICE) {
@@ -3742,8 +3742,7 @@
                 }
             }
             setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, userId,
-                    BackgroundStartPrivileges.NONE, true /* isBindService */,
-                    false /* isStartService */);
+                    BackgroundStartPrivileges.NONE, true /* isBindService */);
 
             if (s.app != null) {
                 ProcessServiceRecord servicePsr = s.app.mServices;
@@ -5068,7 +5067,7 @@
             boolean whileRestarting, boolean permissionsReviewRequired, boolean packageFrozen,
             boolean enqueueOomAdj)
             throws TransactionTooLargeException {
-        if (r.app != null && r.app.getThread() != null) {
+        if (r.app != null && r.app.isThreadReady()) {
             sendServiceArgsLocked(r, execInFg, false);
             return null;
         }
@@ -5140,7 +5139,7 @@
                 final IApplicationThread thread = app.getThread();
                 final int pid = app.getPid();
                 final UidRecord uidRecord = app.getUidRecord();
-                if (thread != null) {
+                if (app.isThreadReady()) {
                     try {
                         if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
@@ -5172,7 +5171,7 @@
                     final int pid = app.getPid();
                     final UidRecord uidRecord = app.getUidRecord();
                     r.isolationHostProc = app;
-                    if (thread != null) {
+                    if (app.isThreadReady()) {
                         try {
                             if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
@@ -5339,7 +5338,7 @@
             thread.scheduleCreateService(r, r.serviceInfo,
                     null /* compatInfo (unused but need to keep method signature) */,
                     app.mState.getReportedProcState());
-            r.postNotification();
+            r.postNotification(false);
             created = true;
         } catch (DeadObjectException e) {
             Slog.w(TAG, "Application dead when creating service " + r);
@@ -5572,7 +5571,7 @@
 
         boolean oomAdjusted = false;
         // Tell the service that it has been unbound.
-        if (r.app != null && r.app.getThread() != null) {
+        if (r.app != null && r.app.isThreadReady()) {
             for (int i = r.bindings.size() - 1; i >= 0; i--) {
                 IntentBindRecord ibr = r.bindings.valueAt(i);
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bringing down binding " + ibr
@@ -5714,7 +5713,7 @@
             mAm.mBatteryStatsService.noteServiceStopLaunch(r.appInfo.uid, r.name.getPackageName(),
                     r.name.getClassName());
             stopServiceAndUpdateAllowlistManagerLocked(r);
-            if (r.app.getThread() != null) {
+            if (r.app.isThreadReady()) {
                 // Bump the process to the top of LRU list
                 mAm.updateLruProcessLocked(r.app, false, null);
                 updateServiceForegroundLocked(r.app.mServices, false);
@@ -5878,7 +5877,7 @@
         if (!c.serviceDead) {
             if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Disconnecting binding " + b.intent
                     + ": shouldUnbind=" + b.intent.hasBound);
-            if (s.app != null && s.app.getThread() != null && b.intent.apps.size() == 0
+            if (s.app != null && s.app.isThreadReady() && b.intent.apps.size() == 0
                     && b.intent.hasBound) {
                 try {
                     bumpServiceExecutingLocked(s, false, "unbind", OOM_ADJ_REASON_UNBIND_SERVICE);
@@ -6380,7 +6379,7 @@
                     sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true,
                             sr.getLastStartId(), baseIntent, null, 0, null, null,
                             ActivityManager.PROCESS_STATE_UNKNOWN));
-                    if (sr.app != null && sr.app.getThread() != null) {
+                    if (sr.app != null && sr.app.isThreadReady()) {
                         // We always run in the foreground, since this is called as
                         // part of the "remove task" UI operation.
                         try {
@@ -7441,54 +7440,80 @@
      * @param callingUid caller app's uid.
      * @param intent intent to start/bind service.
      * @param r the service to start.
-     * @param isStartService True if it's called from Context.startService().
-     *                       False if it's called from Context.startForegroundService() or
-     *                       Service.startForeground().
+     * @param isBindService True if it's called from bindService().
      * @return true if allow, false otherwise.
      */
     private void setFgsRestrictionLocked(String callingPackage,
             int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
-            BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService,
-            boolean isStartService) {
-        // Check DeviceConfig flag.
-        if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
-            if (!r.mAllowWhileInUsePermissionInFgs) {
-                // BGFGS start restrictions are disabled. We're allowing while-in-use permissions.
-                // Note REASON_OTHER since there's no other suitable reason.
-                r.mAllowWhileInUsePermissionInFgsReason = REASON_OTHER;
-            }
-            r.mAllowWhileInUsePermissionInFgs = true;
+            BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) {
+
+        @ReasonCode int allowWIU;
+        @ReasonCode int allowStart;
+
+        // If called from bindService(), do not update the actual fields, but instead
+        // keep it in a separate set of fields.
+        if (isBindService) {
+            allowWIU = r.mAllowWIUInBindService;
+            allowStart = r.mAllowStartInBindService;
+        } else {
+            allowWIU = r.mAllowWhileInUsePermissionInFgsReasonNoBinding;
+            allowStart = r.mAllowStartForegroundNoBinding;
         }
 
-        if (!r.mAllowWhileInUsePermissionInFgs
-                || (r.mAllowStartForeground == REASON_DENIED)) {
+        // Check DeviceConfig flag.
+        if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
+            if (allowWIU == REASON_DENIED) {
+                // BGFGS start restrictions are disabled. We're allowing while-in-use permissions.
+                // Note REASON_OTHER since there's no other suitable reason.
+                allowWIU = REASON_OTHER;
+            }
+        }
+
+        if ((allowWIU == REASON_DENIED)
+                || (allowStart == REASON_DENIED)) {
             @ReasonCode final int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
                     callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges);
             // We store them to compare the old and new while-in-use logics to each other.
             // (They're not used for any other purposes.)
-            if (!r.mAllowWhileInUsePermissionInFgs) {
-                r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED);
-                r.mAllowWhileInUsePermissionInFgsReason = allowWhileInUse;
+            if (allowWIU == REASON_DENIED) {
+                allowWIU = allowWhileInUse;
             }
-            if (r.mAllowStartForeground == REASON_DENIED) {
-                r.mAllowStartForeground = shouldAllowFgsStartForegroundWithBindingCheckLocked(
+            if (allowStart == REASON_DENIED) {
+                allowStart = shouldAllowFgsStartForegroundWithBindingCheckLocked(
                         allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
                         backgroundStartPrivileges, isBindService);
             }
         }
+
+        if (isBindService) {
+            r.mAllowWIUInBindService = allowWIU;
+            r.mAllowStartInBindService = allowStart;
+        } else {
+            r.mAllowWhileInUsePermissionInFgsReasonNoBinding = allowWIU;
+            r.mAllowStartForegroundNoBinding = allowStart;
+
+            // Also do a binding client check, unless called from bindService().
+            if (r.mAllowWIUByBindings == REASON_DENIED) {
+                r.mAllowWIUByBindings =
+                        shouldAllowFgsWhileInUsePermissionByBindingsLocked(callingUid);
+            }
+            if (r.mAllowStartByBindings == REASON_DENIED) {
+                r.mAllowStartByBindings = r.mAllowWIUByBindings;
+            }
+        }
     }
 
     /**
      * Reset various while-in-use and BFSL related information.
      */
     void resetFgsRestrictionLocked(ServiceRecord r) {
-        r.mAllowWhileInUsePermissionInFgs = false;
-        r.mAllowWhileInUsePermissionInFgsReason = REASON_DENIED;
-        r.mAllowStartForeground = REASON_DENIED;
+        r.clearFgsAllowWIU();
+        r.clearFgsAllowStart();
+
         r.mInfoAllowStartForeground = null;
         r.mInfoTempFgsAllowListReason = null;
         r.mLoggedInfoAllowStartForeground = false;
-        r.updateAllowUiJobScheduling(r.mAllowWhileInUsePermissionInFgs);
+        r.updateAllowUiJobScheduling(r.isFgsAllowedWIU());
     }
 
     boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) {
@@ -8060,10 +8085,10 @@
         */
         if (!r.mLoggedInfoAllowStartForeground) {
             final String msg = "Background started FGS: "
-                    + ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ")
+                    + (r.isFgsAllowedStart() ? "Allowed " : "Disallowed ")
                     + r.mInfoAllowStartForeground
                     + (r.isShortFgs() ? " (Called on SHORT_SERVICE)" : "");
-            if (r.mAllowStartForeground != REASON_DENIED) {
+            if (r.isFgsAllowedStart()) {
                 if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
                         mAm.mConstants.mFgsStartAllowedLogSampleRate)) {
                     Slog.wtfQuiet(TAG, msg);
@@ -8103,8 +8128,8 @@
             allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering;
             fgsStartReasonCode = r.mAllowStartForegroundAtEntering;
         } else {
-            allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgs;
-            fgsStartReasonCode = r.mAllowStartForeground;
+            allowWhileInUsePermissionInFgs = r.isFgsAllowedWIU();
+            fgsStartReasonCode = r.getFgsAllowStart();
         }
         final int callerTargetSdkVersion = r.mRecentCallerApplicationInfo != null
                 ? r.mRecentCallerApplicationInfo.targetSdkVersion : 0;
@@ -8141,7 +8166,13 @@
                 mAm.getUidStateLocked(r.mRecentCallingUid),
                 mAm.getUidProcessCapabilityLocked(r.mRecentCallingUid),
                 0,
-                0);
+                0,
+                r.mAllowWhileInUsePermissionInFgsReasonNoBinding,
+                r.mAllowWIUInBindService,
+                r.mAllowWIUByBindings,
+                r.mAllowStartForegroundNoBinding,
+                r.mAllowStartInBindService,
+                r.mAllowStartByBindings);
 
         int event = 0;
         if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
@@ -8293,8 +8324,7 @@
         r.mFgsEnterTime = SystemClock.uptimeMillis();
         r.foregroundServiceType = options.mForegroundServiceTypes;
         setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId,
-                BackgroundStartPrivileges.NONE,  false /* isBindService */,
-                false /* isStartService */);
+                BackgroundStartPrivileges.NONE,  false /* isBindService */);
         final ProcessServiceRecord psr = callerApp.mServices;
         final boolean newService = psr.startService(r);
         // updateOomAdj.
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 8c31209..de6522e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1027,7 +1027,7 @@
     private static final String KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION =
             "enable_wait_for_finish_attach_application";
 
-    private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = false;
+    private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = true;
 
     /** @see #KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION */
     public volatile boolean mEnableWaitForFinishAttachApplication =
@@ -1347,7 +1347,7 @@
         updateForegroundServiceStartsLoggingEnabled();
     }
 
-    private void loadDeviceConfigConstants() {
+    void loadDeviceConfigConstants() {
         mOnDeviceConfigChangedListener.onPropertiesChanged(
                 DeviceConfig.getProperties(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER));
         mOnDeviceConfigChangedForComponentAliasListener.onPropertiesChanged(
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8089dcf..aadb416 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -559,6 +559,10 @@
     // How long we wait for a launched process to attach to the activity manager
     // before we decide it's never going to come up for real.
     static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+    // How long we wait for a launched process to complete its app startup before we ANR.
+    static final int BIND_APPLICATION_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
     // How long we wait to kill an application zygote, after the last process using
     // it has gone away.
     static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000;
@@ -1624,6 +1628,7 @@
     static final int UPDATE_CACHED_APP_HIGH_WATERMARK = 79;
     static final int ADD_UID_TO_OBSERVER_MSG = 80;
     static final int REMOVE_UID_FROM_OBSERVER_MSG = 81;
+    static final int BIND_APPLICATION_TIMEOUT_MSG = 82;
 
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
 
@@ -1976,6 +1981,16 @@
                 case UPDATE_CACHED_APP_HIGH_WATERMARK: {
                     mAppProfiler.mCachedAppsWatermarkData.updateCachedAppsSnapshot((long) msg.obj);
                 } break;
+                case BIND_APPLICATION_TIMEOUT_MSG: {
+                    ProcessRecord app = (ProcessRecord) msg.obj;
+
+                    final String anrMessage;
+                    synchronized (app) {
+                        anrMessage = "Process " + app + " failed to complete startup";
+                    }
+
+                    mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage));
+                } break;
             }
         }
     }
@@ -4595,14 +4610,8 @@
         EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
 
         synchronized (mProcLock) {
-            app.mState.setCurAdj(ProcessList.INVALID_ADJ);
-            app.mState.setSetAdj(ProcessList.INVALID_ADJ);
-            app.mState.setVerifiedAdj(ProcessList.INVALID_ADJ);
-            mOomAdjuster.setAttachingSchedGroupLSP(app);
-            app.mState.setForcingToImportant(null);
+            mOomAdjuster.setAttachingProcessStatesLSP(app);
             clearProcessForegroundLocked(app);
-            app.mState.setHasShownUi(false);
-            app.mState.setCached(false);
             app.setDebugging(false);
             app.setKilledByAm(false);
             app.setKilled(false);
@@ -4756,6 +4765,12 @@
                         app.getDisabledCompatChanges(), serializedSystemFontMap,
                         app.getStartElapsedTime(), app.getStartUptime());
             }
+
+            Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_MSG);
+            msg.obj = app;
+            mHandler.sendMessageDelayed(msg, BIND_APPLICATION_TIMEOUT);
+            mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+
             if (profilerInfo != null) {
                 profilerInfo.closeFd();
                 profilerInfo = null;
@@ -4769,8 +4784,14 @@
                 app.makeActive(thread, mProcessStats);
                 checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");
             }
+            app.setPendingFinishAttach(true);
+
             updateLruProcessLocked(app, false, null);
             checkTime(startTime, "attachApplicationLocked: after updateLruProcessLocked");
+
+            updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
+            checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked");
+
             final long now = SystemClock.uptimeMillis();
             synchronized (mAppProfiler.mProfilerLock) {
                 app.mProfile.setLastRequestedGc(now);
@@ -4786,8 +4807,6 @@
 
             if (!mConstants.mEnableWaitForFinishAttachApplication) {
                 finishAttachApplicationInner(startSeq, callingUid, pid);
-            } else {
-                app.setPendingFinishAttach(true);
             }
         } catch (Exception e) {
             // We need kill the process group here. (b/148588589)
@@ -4826,7 +4845,7 @@
         }
 
         if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) {
-            mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+            mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_MSG, app);
         } else {
             Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
                     + ". Uid: " + uid);
@@ -19622,7 +19641,7 @@
             for (Display display : allDisplays) {
                 int displayId = display.getDisplayId();
                 // TODO(b/247592632): check other properties like isSecure or proper display type
-                if (display.isValid()
+                if (display.isValid() && ((display.getFlags() & Display.FLAG_PRIVATE) == 0)
                         && (allowOnDefaultDisplay || displayId != Display.DEFAULT_DISPLAY)) {
                     displayIds[numberValidDisplays++] = displayId;
                 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 569f55f..6801d27 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -3699,14 +3699,11 @@
             if (foregroundActivities) {
                 try {
                     int prcState = mIam.getUidProcessState(uid, "android");
-                    ProcessRecord topApp = mInternal.getTopApp();
-                    if (topApp == null) {
-                        mPw.println("No top app found");
+
+                    if (prcState == ProcessStateEnum.TOP) {
+                        mPw.println("New foreground process: " + pid);
                     } else {
-                        int topPid = topApp.getPid();
-                        if (prcState == ProcessStateEnum.TOP && topPid == pid) {
-                            mPw.println("New foreground process: " + pid);
-                        }
+                        mPw.println("No top app found");
                     }
                     mPw.flush();
                 } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 0fcec6f..fc8175b 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -1144,9 +1144,6 @@
             } else if (mProcessPersistent) {
                 mRunnableAt = runnableAt + constants.DELAY_PERSISTENT_PROC_MILLIS;
                 mRunnableAtReason = REASON_PERSISTENT;
-            } else if (UserHandle.isCore(uid)) {
-                mRunnableAt = runnableAt;
-                mRunnableAtReason = REASON_CORE_UID;
             } else if (mCountOrdered > 0) {
                 mRunnableAt = runnableAt;
                 mRunnableAtReason = REASON_CONTAINS_ORDERED;
@@ -1193,6 +1190,9 @@
                 // is already cached, they'll be deferred on the line above
                 mRunnableAt = runnableAt;
                 mRunnableAtReason = REASON_CONTAINS_RESULT_TO;
+            } else if (UserHandle.isCore(uid)) {
+                mRunnableAt = runnableAt;
+                mRunnableAtReason = REASON_CORE_UID;
             } else {
                 mRunnableAt = runnableAt + constants.DELAY_NORMAL_MILLIS;
                 mRunnableAtReason = REASON_NORMAL;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index f420619..5d0fefc 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -249,6 +249,7 @@
     private static final int MSG_CHECK_HEALTH = 5;
     private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 6;
     private static final int MSG_PROCESS_FREEZABLE_CHANGED = 7;
+    private static final int MSG_UID_STATE_CHANGED = 8;
 
     private void enqueueUpdateRunningList() {
         mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -295,6 +296,19 @@
                 }
                 return true;
             }
+            case MSG_UID_STATE_CHANGED: {
+                final int uid = (int) msg.obj;
+                final int procState = msg.arg1;
+                synchronized (mService) {
+                    if (procState == ActivityManager.PROCESS_STATE_TOP) {
+                        mUidForeground.put(uid, true);
+                    } else {
+                        mUidForeground.delete(uid);
+                    }
+                    refreshProcessQueuesLocked(uid);
+                }
+                return true;
+            }
         }
         return false;
     };
@@ -672,7 +686,7 @@
     @Override
     public void onProcessFreezableChangedLocked(@NonNull ProcessRecord app) {
         mLocalHandler.removeMessages(MSG_PROCESS_FREEZABLE_CHANGED, app);
-        mLocalHandler.sendMessage(mHandler.obtainMessage(MSG_PROCESS_FREEZABLE_CHANGED, app));
+        mLocalHandler.obtainMessage(MSG_PROCESS_FREEZABLE_CHANGED, app).sendToTarget();
     }
 
     @Override
@@ -1601,14 +1615,9 @@
             @Override
             public void onUidStateChanged(int uid, int procState, long procStateSeq,
                     int capability) {
-                synchronized (mService) {
-                    if (procState == ActivityManager.PROCESS_STATE_TOP) {
-                        mUidForeground.put(uid, true);
-                    } else {
-                        mUidForeground.delete(uid);
-                    }
-                    refreshProcessQueuesLocked(uid);
-                }
+                mLocalHandler.removeMessages(MSG_UID_STATE_CHANGED, uid);
+                mLocalHandler.obtainMessage(MSG_UID_STATE_CHANGED, procState, 0, uid)
+                        .sendToTarget();
             }
         }, ActivityManager.UID_OBSERVER_PROCSTATE,
                 ActivityManager.PROCESS_STATE_TOP, "android");
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index e588a9e..be514c5 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -238,7 +238,7 @@
     private static final String ATRACE_COMPACTION_TRACK = "Compaction";
     private static final String ATRACE_FREEZER_TRACK = "Freezer";
 
-    private static final int FREEZE_BINDER_TIMEOUT_MS = 100;
+    private static final int FREEZE_BINDER_TIMEOUT_MS = 0;
     private static final int FREEZE_DEADLOCK_TIMEOUT_MS = 1000;
 
     @VisibleForTesting static final boolean ENABLE_FILE_COMPACT = false;
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 7482e64..182205a 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -30,6 +30,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -161,6 +162,12 @@
                 WidgetFlags.MAGNIFIER_ASPECT_RATIO_DEFAULT));
 
         sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
+                SystemUiDeviceConfigFlags.KEY_REMOTEVIEWS_ADAPTER_CONVERSION, boolean.class,
+                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT));
+
+        sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
                 TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU,
                 TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class,
                 TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 38e7371..f6859d1 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -141,6 +141,10 @@
         // grab the appropriate types
         final IntArray apiTypes =
                 convertFgsTypeToApiTypes(record.foregroundServiceType);
+        if (apiTypes.size() == 0) {
+            Slog.w(TAG, "Foreground service start for UID: "
+                    + uid + " does not have any types");
+        }
         // now we need to iterate through the types
         // and insert the new record as needed
         final IntArray apiTypesFound = new IntArray();
@@ -201,6 +205,9 @@
         // and also clean up the start calls stack by UID
         final IntArray apiTypes = convertFgsTypeToApiTypes(record.foregroundServiceType);
         final UidState uidState = mUids.get(uid);
+        if (apiTypes.size() == 0) {
+            Slog.w(TAG, "FGS stop call for: " + uid + " has no types!");
+        }
         if (uidState == null) {
             Slog.w(TAG, "FGS stop call being logged with no start call for UID for UID "
                     + uid
@@ -460,16 +467,17 @@
     public void logFgsApiEvent(ServiceRecord r, int fgsState,
             @FgsApiState int apiState,
             @ForegroundServiceApiType int apiType, long timestamp) {
-        long apiDurationBeforeFgsStart = r.createRealTime - timestamp;
-        long apiDurationAfterFgsEnd = timestamp - r.mFgsExitTime;
+        long apiDurationBeforeFgsStart = 0;
+        long apiDurationAfterFgsEnd = 0;
         UidState uidState = mUids.get(r.appInfo.uid);
-        if (uidState != null) {
-            if (uidState.mFirstFgsTimeStamp.contains(apiType)) {
-                apiDurationBeforeFgsStart = uidState.mFirstFgsTimeStamp.get(apiType) - timestamp;
-            }
-            if (uidState.mLastFgsTimeStamp.contains(apiType)) {
-                apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
-            }
+        if (uidState == null) {
+            return;
+        }
+        if (uidState.mFirstFgsTimeStamp.contains(apiType)) {
+            apiDurationBeforeFgsStart = uidState.mFirstFgsTimeStamp.get(apiType) - timestamp;
+        }
+        if (uidState.mLastFgsTimeStamp.contains(apiType)) {
+            apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
         }
         final int[] apiTypes = new int[1];
         apiTypes[0] = apiType;
@@ -479,8 +487,8 @@
                 r.appInfo.uid,
                 r.shortInstanceName,
                 fgsState, // FGS State
-                r.mAllowWhileInUsePermissionInFgs, // allowWhileInUsePermissionInFgs
-                r.mAllowStartForeground, // fgsStartReasonCode
+                r.isFgsAllowedWIU(), // allowWhileInUsePermissionInFgs
+                r.getFgsAllowStart(), // fgsStartReasonCode
                 r.appInfo.targetSdkVersion,
                 r.mRecentCallingUid,
                 0, // callerTargetSdkVersion
@@ -506,7 +514,13 @@
                 ActivityManager.PROCESS_STATE_UNKNOWN,
                 ActivityManager.PROCESS_CAPABILITY_NONE,
                 apiDurationBeforeFgsStart,
-                apiDurationAfterFgsEnd);
+                apiDurationAfterFgsEnd,
+                r.mAllowWhileInUsePermissionInFgsReasonNoBinding,
+                r.mAllowWIUInBindService,
+                r.mAllowWIUByBindings,
+                r.mAllowStartForegroundNoBinding,
+                r.mAllowStartInBindService,
+                r.mAllowStartByBindings);
     }
 
     /**
@@ -519,10 +533,11 @@
             @ForegroundServiceApiType int apiType, long timestamp) {
         long apiDurationAfterFgsEnd = 0;
         UidState uidState = mUids.get(uid);
-        if (uidState != null) {
-            if (uidState.mLastFgsTimeStamp.contains(apiType)) {
-                apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
-            }
+        if (uidState == null) {
+            return;
+        }
+        if (uidState.mLastFgsTimeStamp.contains(apiType)) {
+            apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
         }
         final int[] apiTypes = new int[1];
         apiTypes[0] = apiType;
@@ -557,7 +572,13 @@
                 ActivityManager.PROCESS_STATE_UNKNOWN,
                 ActivityManager.PROCESS_CAPABILITY_NONE,
                 0,
-                apiDurationAfterFgsEnd);
+                apiDurationAfterFgsEnd,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0);
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index f21ad22..459c6ff 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1293,12 +1293,19 @@
         for (int i = numLru - 1; i >= 0; i--) {
             ProcessRecord app = lruList.get(i);
             final ProcessStateRecord state = app.mState;
-            if (!app.isKilledByAm() && app.getThread() != null && !app.isPendingFinishAttach()) {
+            if (!app.isKilledByAm() && app.getThread() != null) {
                 // We don't need to apply the update for the process which didn't get computed
                 if (state.getCompletedAdjSeq() == mAdjSeq) {
                     applyOomAdjLSP(app, true, now, nowElapsed, oomAdjReason);
                 }
 
+                if (app.isPendingFinishAttach()) {
+                    // Avoid trimming processes that are still initializing. If they aren't
+                    // hosting any components yet because they may be unfairly killed.
+                    // We however apply the oom scores set at #setAttachingProcessStatesLSP.
+                    continue;
+                }
+
                 final ProcessServiceRecord psr = app.mServices;
                 // Count the number of process types.
                 switch (state.getCurProcState()) {
@@ -1706,6 +1713,11 @@
         }
     }
 
+    private boolean isScreenOnOrAnimatingLocked(ProcessStateRecord state) {
+        return mService.mWakefulness.get() == PowerManagerInternal.WAKEFULNESS_AWAKE
+                || state.isRunningRemoteAnimation();
+    }
+
     @GuardedBy({"mService", "mProcLock"})
     private boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj,
             ProcessRecord topApp, boolean doingAll, long now, boolean cycleReEval,
@@ -1787,8 +1799,7 @@
                 state.setSystemNoUi(false);
             }
             if (!state.isSystemNoUi()) {
-                if (mService.mWakefulness.get() == PowerManagerInternal.WAKEFULNESS_AWAKE
-                        || state.isRunningRemoteAnimation()) {
+                if (isScreenOnOrAnimatingLocked(state)) {
                     // screen on or animating, promote UI
                     state.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
                     state.setCurrentSchedulingGroup(SCHED_GROUP_TOP_APP);
@@ -2211,7 +2222,7 @@
 
             if (s.isForeground) {
                 final int fgsType = s.foregroundServiceType;
-                if (s.mAllowWhileInUsePermissionInFgs) {
+                if (s.isFgsAllowedWIU()) {
                     capabilityFromFGS |=
                             (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
                                     != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
@@ -2806,6 +2817,18 @@
             capability &= ~PROCESS_CAPABILITY_BFSL;
         }
 
+
+        if (app.isPendingFinishAttach()) {
+            // If the app is still starting up. We reset the computations to the
+            // hardcoded values in setAttachingProcessStatesLSP. This ensures that the app keeps
+            // hard-coded default 'startup' oom scores while starting up. When it finishes startup,
+            // we'll recompute oom scores based on it's actual hosted compoenents.
+            setAttachingProcessStatesLSP(app);
+            state.setAdjSeq(mAdjSeq);
+            state.setCompletedAdjSeq(state.getAdjSeq());
+            return false;
+        }
+
         // Do final modification to adj.  Everything we do between here and applying
         // the final setAdj must be done in this function, because we will also use
         // it when computing the final cached adj later.  Note that we don't need to
@@ -3243,8 +3266,11 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    void setAttachingSchedGroupLSP(ProcessRecord app) {
+    void setAttachingProcessStatesLSP(ProcessRecord app) {
         int initialSchedGroup = SCHED_GROUP_DEFAULT;
+        int initialProcState = PROCESS_STATE_CACHED_EMPTY;
+        int initialCapability =  PROCESS_CAPABILITY_NONE;
+        boolean initialCached = true;
         final ProcessStateRecord state = app.mState;
         // If the process has been marked as foreground, it is starting as the top app (with
         // Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread.
@@ -3259,14 +3285,27 @@
                 } else {
                     setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
                 }
-                initialSchedGroup = SCHED_GROUP_TOP_APP;
+                if (isScreenOnOrAnimatingLocked(state)) {
+                    initialSchedGroup = SCHED_GROUP_TOP_APP;
+                    initialProcState = PROCESS_STATE_TOP;
+                }
+                initialCapability = PROCESS_CAPABILITY_ALL;
+                initialCached = false;
             } catch (Exception e) {
                 Slog.w(TAG, "Failed to pre-set top priority to " + app + " " + e);
             }
         }
 
-        state.setSetSchedGroup(initialSchedGroup);
         state.setCurrentSchedulingGroup(initialSchedGroup);
+        state.setCurProcState(initialProcState);
+        state.setCurRawProcState(initialProcState);
+        state.setCurCapability(initialCapability);
+        state.setCached(initialCached);
+
+        state.setCurAdj(ProcessList.FOREGROUND_APP_ADJ);
+        state.setCurRawAdj(ProcessList.FOREGROUND_APP_ADJ);
+        state.setForcingToImportant(null);
+        state.setHasShownUi(false);
     }
 
     // ONLY used for unit testing in OomAdjusterTests.java
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index 7ee96aa..a20623c 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -26,6 +26,7 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.PendingIntent;
 import android.app.PendingIntentStats;
@@ -126,6 +127,18 @@
                 }
             }
             Bundle.setDefusable(bOptions, true);
+            ActivityOptions opts = ActivityOptions.fromBundle(bOptions);
+            if (opts != null && opts.getPendingIntentBackgroundActivityStartMode()
+                    != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+                Slog.wtf(TAG, "Resetting option setPendingIntentBackgroundActivityStartMode("
+                        + opts.getPendingIntentBackgroundActivityStartMode()
+                        + ") to SYSTEM_DEFINED from the options provided by the pending "
+                        + "intent creator ("
+                        + packageName
+                        + ") because this option is meant for the pending intent sender");
+                opts.setPendingIntentBackgroundActivityStartMode(
+                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
+            }
 
             final boolean noCreate = (flags & PendingIntent.FLAG_NO_CREATE) != 0;
             final boolean cancelCurrent = (flags & PendingIntent.FLAG_CANCEL_CURRENT) != 0;
@@ -135,7 +148,7 @@
 
             PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, featureId,
                     token, resultWho, requestCode, intents, resolvedTypes, flags,
-                    SafeActivityOptions.fromBundle(bOptions), userId);
+                    new SafeActivityOptions(opts), userId);
             WeakReference<PendingIntentRecord> ref;
             ref = mIntentSenderRecords.get(key);
             PendingIntentRecord rec = ref != null ? ref.get() : null;
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 202d407..65ca5d3 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -42,7 +42,6 @@
 import android.os.IBinder;
 import android.os.PowerWhitelistManager;
 import android.os.PowerWhitelistManager.ReasonCode;
-import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.TransactionTooLargeException;
@@ -384,14 +383,6 @@
             })
     public static BackgroundStartPrivileges getDefaultBackgroundStartPrivileges(
             int callingUid, @Nullable String callingPackage) {
-        if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
-            // We temporarily allow BAL for system processes, while we verify that all valid use
-            // cases are opted in explicitly to grant their BAL permission.
-            // Background: In many cases devices are running additional apps that share UID with
-            // the system. If one of these apps targets a lower SDK the change is not active, but
-            // as soon as that app is upgraded (or removed) BAL would be blocked. (b/283138430)
-            return BackgroundStartPrivileges.ALLOW_BAL;
-        }
         boolean isChangeEnabledForApp = callingPackage != null ? CompatChanges.isChangeEnabled(
                 DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER, callingPackage,
                 UserHandle.getUserHandleForUid(callingUid)) : CompatChanges.isChangeEnabled(
@@ -457,6 +448,20 @@
             // can specify a consistent launch mode even if the PendingIntent is immutable
             final ActivityOptions opts = ActivityOptions.fromBundle(options);
             if (opts != null) {
+                if (opts.getPendingIntentCreatorBackgroundActivityStartMode()
+                        != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+                    Slog.wtf(TAG,
+                            "Resetting option "
+                                    + "setPendingIntentCreatorBackgroundActivityStartMode("
+                                    + opts.getPendingIntentCreatorBackgroundActivityStartMode()
+                                    + ") to SYSTEM_DEFINED from the options provided by the "
+                                    + "pending intent sender ("
+                                    + key.packageName
+                                    + ") because this option is meant for the pending intent "
+                                    + "creator");
+                    opts.setPendingIntentCreatorBackgroundActivityStartMode(
+                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
+                }
                 finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
             }
 
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index e498384..753fdae 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -51,11 +51,11 @@
 import com.android.internal.annotations.CompositeRWLock;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.expresslog.Counter;
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.os.anr.AnrLatencyTracker;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.expresslog.Counter;
 import com.android.server.ResourcePressureUtil;
 import com.android.server.criticalevents.CriticalEventLog;
 import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot;
@@ -456,6 +456,11 @@
         String currentPsiState = ResourcePressureUtil.currentPsiState();
         latencyTracker.currentPsiStateReturned();
         report.append(currentPsiState);
+        // The 'processCpuTracker' variable is a shared resource that might be initialized and
+        // updated in a different thread. In order to prevent thread visibility issues, which
+        // can occur when one thread does not immediately see the changes made to
+        // 'processCpuTracker' by another thread, it is necessary to use synchronization whenever
+        // 'processCpuTracker' is accessed or modified.
         ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
 
         // We push the native pids collection task to the helper thread through
@@ -517,12 +522,16 @@
             }
             mService.updateCpuStatsNow();
             mService.mAppProfiler.printCurrentCpuState(report, anrTime);
-            info.append(processCpuTracker.printCurrentLoad());
+            synchronized (processCpuTracker) {
+                info.append(processCpuTracker.printCurrentLoad());
+            }
             info.append(report);
         }
         report.append(tracesFileException.getBuffer());
 
-        info.append(processCpuTracker.printCurrentState(anrTime));
+        synchronized (processCpuTracker) {
+            info.append(processCpuTracker.printCurrentState(anrTime));
+        }
 
         Slog.e(TAG, info.toString());
         if (tracesFile == null) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c5776d8..acd9771 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1827,6 +1827,7 @@
 
             if (debuggableFlag) {
                 runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
+                runtimeFlags |= Zygote.DEBUG_ENABLE_PTRACE;
                 runtimeFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE;
                 // Also turn on CheckJNI for debuggable apps. It's quite
                 // awkward to turn on otherwise.
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index f532122c1..f6acc41 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -770,6 +770,11 @@
     }
 
     @GuardedBy("mService")
+    boolean isThreadReady() {
+        return mThread != null && !mPendingFinishAttach;
+    }
+
+    @GuardedBy("mService")
     long getStartSeq() {
         return mStartSeq;
     }
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 50fe6d7..8a8e2af 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -21,9 +21,11 @@
 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE;
 import static android.os.PowerExemptionManager.REASON_DENIED;
+import static android.os.PowerExemptionManager.reasonCodeToString;
 import static android.os.Process.INVALID_UID;
 
 import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.server.am.ActiveServices.TAG_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -172,11 +174,11 @@
     private BackgroundStartPrivileges mBackgroundStartPrivilegesByStartMerged =
             BackgroundStartPrivileges.NONE;
 
-    // allow while-in-use permissions in foreground service or not.
+    // Reason code for allow while-in-use permissions in foreground service.
+    // If it's not DENIED, while-in-use permissions are allowed.
     // while-in-use permissions in FGS started from background might be restricted.
-    boolean mAllowWhileInUsePermissionInFgs;
     @PowerExemptionManager.ReasonCode
-    int mAllowWhileInUsePermissionInFgsReason = REASON_DENIED;
+    int mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
 
     // A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state.
     boolean mAllowWhileInUsePermissionInFgsAtEntering;
@@ -205,15 +207,121 @@
 
     // allow the service becomes foreground service? Service started from background may not be
     // allowed to become a foreground service.
-    @PowerExemptionManager.ReasonCode int mAllowStartForeground = REASON_DENIED;
+    @PowerExemptionManager.ReasonCode
+    int mAllowStartForegroundNoBinding = REASON_DENIED;
     // A copy of mAllowStartForeground's value when the service is entering FGS state.
-    @PowerExemptionManager.ReasonCode int mAllowStartForegroundAtEntering = REASON_DENIED;
+    @PowerExemptionManager.ReasonCode
+    int mAllowStartForegroundAtEntering = REASON_DENIED;
     // Debug info why mAllowStartForeground is allowed or denied.
     String mInfoAllowStartForeground;
     // Debug info if mAllowStartForeground is allowed because of a temp-allowlist.
     ActivityManagerService.FgsTempAllowListItem mInfoTempFgsAllowListReason;
     // Is the same mInfoAllowStartForeground string has been logged before? Used for dedup.
     boolean mLoggedInfoAllowStartForeground;
+
+    @PowerExemptionManager.ReasonCode
+    int mAllowWIUInBindService = REASON_DENIED;
+
+    @PowerExemptionManager.ReasonCode
+    int mAllowWIUByBindings = REASON_DENIED;
+
+    @PowerExemptionManager.ReasonCode
+    int mAllowStartInBindService = REASON_DENIED;
+
+    @PowerExemptionManager.ReasonCode
+    int mAllowStartByBindings = REASON_DENIED;
+
+    @PowerExemptionManager.ReasonCode
+    int getFgsAllowWIU() {
+        return mAllowWhileInUsePermissionInFgsReasonNoBinding != REASON_DENIED
+                ? mAllowWhileInUsePermissionInFgsReasonNoBinding
+                : mAllowWIUInBindService;
+    }
+
+    boolean isFgsAllowedWIU() {
+        return getFgsAllowWIU() != REASON_DENIED;
+    }
+
+    @PowerExemptionManager.ReasonCode
+    int getFgsAllowStart() {
+        return mAllowStartForegroundNoBinding != REASON_DENIED
+                ? mAllowStartForegroundNoBinding
+                : mAllowStartInBindService;
+    }
+
+    boolean isFgsAllowedStart() {
+        return getFgsAllowStart() != REASON_DENIED;
+    }
+
+    void clearFgsAllowWIU() {
+        mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
+        mAllowWIUInBindService = REASON_DENIED;
+        mAllowWIUByBindings = REASON_DENIED;
+    }
+
+    void clearFgsAllowStart() {
+        mAllowStartForegroundNoBinding = REASON_DENIED;
+        mAllowStartInBindService = REASON_DENIED;
+        mAllowStartByBindings = REASON_DENIED;
+    }
+
+    @PowerExemptionManager.ReasonCode
+    int reasonOr(@PowerExemptionManager.ReasonCode int first,
+            @PowerExemptionManager.ReasonCode int second) {
+        return first != REASON_DENIED ? first : second;
+    }
+
+    boolean allowedChanged(@PowerExemptionManager.ReasonCode int first,
+            @PowerExemptionManager.ReasonCode int second) {
+        return (first == REASON_DENIED) != (second == REASON_DENIED);
+    }
+
+    String changeMessage(@PowerExemptionManager.ReasonCode int first,
+            @PowerExemptionManager.ReasonCode int second) {
+        return reasonOr(first, second) == REASON_DENIED ? "DENIED"
+                : ("ALLOWED ("
+                + reasonCodeToString(first)
+                + "+"
+                + reasonCodeToString(second)
+                + ")");
+    }
+
+    private String getFgsInfoForWtf() {
+        return " cmp: " + this.getComponentName().toShortString()
+                + " sdk: " + this.appInfo.targetSdkVersion
+                ;
+    }
+
+    void maybeLogFgsLogicChange() {
+        final int origWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
+                mAllowWIUInBindService);
+        final int newWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
+                mAllowWIUByBindings);
+        final int origStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartInBindService);
+        final int newStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+
+        final boolean wiuChanged = allowedChanged(origWiu, newWiu);
+        final boolean startChanged = allowedChanged(origStart, newStart);
+
+        if (!wiuChanged && !startChanged) {
+            return;
+        }
+        final String message = "FGS logic changed:"
+                + (wiuChanged ? " [WIU changed]" : "")
+                + (startChanged ? " [BFSL changed]" : "")
+                + " OW:" // Orig-WIU
+                + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding,
+                        mAllowWIUInBindService)
+                + " NW:" // New-WIU
+                + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding, mAllowWIUByBindings)
+                + " OS:" // Orig-start
+                + changeMessage(mAllowStartForegroundNoBinding, mAllowStartInBindService)
+                + " NS:" // New-start
+                + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings)
+                + getFgsInfoForWtf();
+        Slog.wtf(TAG_SERVICE, message);
+    }
+
     // The number of times Service.startForeground() is called, after this service record is
     // created. (i.e. due to "bound" or "start".) It never decreases, even when stopForeground()
     // is called.
@@ -502,7 +610,7 @@
         ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now);
         proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg);
         proto.write(ServiceRecordProto.ALLOW_WHILE_IN_USE_PERMISSION_IN_FGS,
-                mAllowWhileInUsePermissionInFgs);
+                isFgsAllowedWIU());
 
         if (startRequested || delayedStop || lastStartId != 0) {
             long startToken = proto.start(ServiceRecordProto.START);
@@ -618,7 +726,13 @@
             pw.println(mBackgroundStartPrivilegesByStartMerged);
         }
         pw.print(prefix); pw.print("mAllowWhileInUsePermissionInFgsReason=");
-        pw.println(PowerExemptionManager.reasonCodeToString(mAllowWhileInUsePermissionInFgsReason));
+        pw.println(PowerExemptionManager.reasonCodeToString(
+                mAllowWhileInUsePermissionInFgsReasonNoBinding));
+
+        pw.print(prefix); pw.print("mAllowWIUInBindService=");
+        pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUInBindService));
+        pw.print(prefix); pw.print("mAllowWIUByBindings=");
+        pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUByBindings));
 
         pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling);
         pw.print(prefix); pw.print("recentCallingPackage=");
@@ -626,7 +740,12 @@
         pw.print(prefix); pw.print("recentCallingUid=");
         pw.println(mRecentCallingUid);
         pw.print(prefix); pw.print("allowStartForeground=");
-        pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForeground));
+        pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForegroundNoBinding));
+        pw.print(prefix); pw.print("mAllowStartInBindService=");
+        pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartInBindService));
+        pw.print(prefix); pw.print("mAllowStartByBindings=");
+        pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartByBindings));
+
         pw.print(prefix); pw.print("startForegroundCount=");
         pw.println(mStartForegroundCount);
         pw.print(prefix); pw.print("infoAllowStartForeground=");
@@ -1212,7 +1331,7 @@
         });
     }
 
-    public void postNotification() {
+    public void postNotification(boolean byForegroundService) {
         if (isForeground && foregroundNoti != null && app != null) {
             final int appUid = appInfo.uid;
             final int appPid = app.getPid();
@@ -1320,7 +1439,7 @@
                         }
                         nm.enqueueNotification(localPackageName, localPackageName,
                                 appUid, appPid, null, localForegroundId, localForegroundNoti,
-                                userId);
+                                userId, byForegroundService /* byForegroundService */);
 
                         foregroundNoti = localForegroundNoti; // save it for amending next time
 
diff --git a/services/core/java/com/android/server/am/StackTracesDumpHelper.java b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
index cf69b53..ba0fd17 100644
--- a/services/core/java/com/android/server/am/StackTracesDumpHelper.java
+++ b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
@@ -464,28 +464,31 @@
             latencyTracker.processCpuTrackerMethodsCalled();
         }
         ArrayList<Integer> extraPids = new ArrayList<>();
-        processCpuTracker.init();
+        synchronized (processCpuTracker) {
+            processCpuTracker.init();
+        }
         try {
             Thread.sleep(200);
         } catch (InterruptedException ignored) {
         }
 
-        processCpuTracker.update();
+        synchronized (processCpuTracker) {
+            processCpuTracker.update();
+            // We'll take the stack crawls of just the top apps using CPU.
+            final int workingStatsNumber = processCpuTracker.countWorkingStats();
+            for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) {
+                ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
+                if (lastPids.indexOfKey(stats.pid) >= 0) {
+                    if (DEBUG_ANR) {
+                        Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
+                    }
 
-        // We'll take the stack crawls of just the top apps using CPU.
-        final int workingStatsNumber = processCpuTracker.countWorkingStats();
-        for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) {
-            ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
-            if (lastPids.indexOfKey(stats.pid) >= 0) {
-                if (DEBUG_ANR) {
-                    Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
+                    extraPids.add(stats.pid);
+                } else {
+                    Slog.i(TAG,
+                            "Skipping next CPU consuming process, not a java proc: "
+                            + stats.pid);
                 }
-
-                extraPids.add(stats.pid);
-            } else {
-                Slog.i(TAG,
-                        "Skipping next CPU consuming process, not a java proc: "
-                        + stats.pid);
             }
         }
         if (latencyTracker != null) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 76a994e..99c2f8a 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1972,7 +1972,7 @@
     }
 
     private void dismissUserSwitchDialog(Runnable onDismissed) {
-        mInjector.dismissUserSwitchingDialog(onDismissed);
+        mUiHandler.post(() -> mInjector.dismissUserSwitchingDialog(onDismissed));
     }
 
     private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index ca15dd7..80d14a2 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -40,6 +40,7 @@
 import android.app.GameState;
 import android.app.IGameManagerService;
 import android.app.IGameModeListener;
+import android.app.IGameStateListener;
 import android.app.StatsManager;
 import android.app.UidObserver;
 import android.content.BroadcastReceiver;
@@ -148,6 +149,7 @@
     private final Object mLock = new Object();
     private final Object mDeviceConfigLock = new Object();
     private final Object mGameModeListenerLock = new Object();
+    private final Object mGameStateListenerLock = new Object();
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     final Handler mHandler;
     private final PackageManager mPackageManager;
@@ -164,6 +166,8 @@
     // listener to caller uid map
     @GuardedBy("mGameModeListenerLock")
     private final ArrayMap<IGameModeListener, Integer> mGameModeListeners = new ArrayMap<>();
+    @GuardedBy("mGameStateListenerLock")
+    private final ArrayMap<IGameStateListener, Integer> mGameStateListeners = new ArrayMap<>();
     @Nullable
     private final GameServiceController mGameServiceController;
     private final Object mUidObserverLock = new Object();
@@ -342,6 +346,9 @@
                         if (mHandler.hasMessages(CANCEL_GAME_LOADING_MODE)) {
                             mHandler.removeMessages(CANCEL_GAME_LOADING_MODE);
                         }
+                        Slog.v(TAG, String.format(
+                                "Game loading power mode %s (game state change isLoading=%b)",
+                                        isLoading ? "ON" : "OFF", isLoading));
                         mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, isLoading);
                         if (isLoading) {
                             int loadingBoostDuration = getLoadingBoostDuration(packageName, userId);
@@ -352,9 +359,20 @@
                                     loadingBoostDuration);
                         }
                     }
+                    synchronized (mGameStateListenerLock) {
+                        for (IGameStateListener listener : mGameStateListeners.keySet()) {
+                            try {
+                                listener.onGameStateChanged(packageName, gameState, userId);
+                            } catch (RemoteException ex) {
+                                Slog.w(TAG, "Cannot notify game state change for listener added by "
+                                        + mGameStateListeners.get(listener));
+                            }
+                        }
+                    }
                     break;
                 }
                 case CANCEL_GAME_LOADING_MODE: {
+                    Slog.v(TAG, "Game loading power mode OFF (loading boost ended)");
                     mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false);
                     break;
                 }
@@ -1265,6 +1283,7 @@
                 // instruction.
                 mHandler.removeMessages(CANCEL_GAME_LOADING_MODE);
             } else {
+                Slog.v(TAG, "Game loading power mode ON (loading boost on game start)");
                 mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, true);
             }
 
@@ -1474,6 +1493,43 @@
     }
 
     /**
+     * Adds a game state listener.
+     */
+    @Override
+    public void addGameStateListener(@NonNull IGameStateListener listener) {
+        try {
+            final IBinder listenerBinder = listener.asBinder();
+            listenerBinder.linkToDeath(new DeathRecipient() {
+                @Override public void binderDied() {
+                    removeGameStateListenerUnchecked(listener);
+                    listenerBinder.unlinkToDeath(this, 0 /*flags*/);
+                }
+            }, 0 /*flags*/);
+            synchronized (mGameStateListenerLock) {
+                mGameStateListeners.put(listener, Binder.getCallingUid());
+            }
+        } catch (RemoteException ex) {
+            Slog.e(TAG,
+                    "Failed to link death recipient for IGameStateListener from caller "
+                            + Binder.getCallingUid() + ", abandoned its listener registration", ex);
+        }
+    }
+
+    /**
+     * Removes a game state listener.
+     */
+    @Override
+    public void removeGameStateListener(@NonNull IGameStateListener listener) {
+        removeGameStateListenerUnchecked(listener);
+    }
+
+    private void removeGameStateListenerUnchecked(IGameStateListener listener) {
+        synchronized (mGameStateListenerLock) {
+            mGameStateListeners.remove(listener);
+        }
+    }
+
+    /**
      * Notified when boot is completed.
      */
     @VisibleForTesting
@@ -1504,6 +1560,10 @@
                 }
             }
         }, new IntentFilter(Intent.ACTION_SHUTDOWN));
+        Slog.v(TAG, "Game loading power mode OFF (game manager service start/restart)");
+        mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false);
+        Slog.v(TAG, "Game power mode OFF (game manager service start/restart)");
+        mPowerManagerInternal.setPowerMode(Mode.GAME, false);
     }
 
     private void sendUserMessage(int userId, int what, String eventForLog, int delayMillis) {
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
new file mode 100644
index 0000000..247094f
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.audio;
+
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
+import static android.media.AudioSystem.DEVICE_NONE;
+import static android.media.AudioSystem.isBluetoothDevice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.Objects;
+
+/**
+ * Class representing all devices that were previously or are currently connected. Data is
+ * persisted in {@link android.provider.Settings.Secure}
+ */
+/*package*/ final class AdiDeviceState {
+    private static final String TAG = "AS.AdiDeviceState";
+
+    private static final String SETTING_FIELD_SEPARATOR = ",";
+
+    @AudioDeviceInfo.AudioDeviceType
+    private final int mDeviceType;
+
+    private final int mInternalDeviceType;
+
+    @NonNull
+    private final String mDeviceAddress;
+
+    /** Unique device id from internal device type and address. */
+    private final Pair<Integer, String> mDeviceId;
+
+    @AudioManager.AudioDeviceCategory
+    private int mAudioDeviceCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN;
+
+    private boolean mSAEnabled;
+    private boolean mHasHeadTracker = false;
+    private boolean mHeadTrackerEnabled;
+
+    /**
+     * Constructor
+     *
+     * @param deviceType external audio device type
+     * @param internalDeviceType if not set pass {@link DEVICE_NONE}, in this case the
+     *                           default conversion of the external type will be used
+     * @param address must be non-null for wireless devices
+     * @throws NullPointerException if a null address is passed for a wireless device
+     */
+    AdiDeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType,
+                        int internalDeviceType,
+                        @Nullable String address) {
+        mDeviceType = deviceType;
+        if (internalDeviceType != DEVICE_NONE) {
+            mInternalDeviceType = internalDeviceType;
+        } else {
+            mInternalDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType);
+
+        }
+        mDeviceAddress = isBluetoothDevice(mInternalDeviceType) ? Objects.requireNonNull(
+                address) : "";
+
+        mDeviceId = new Pair<>(mInternalDeviceType, mDeviceAddress);
+    }
+
+    public Pair<Integer, String> getDeviceId() {
+        return mDeviceId;
+    }
+
+    @AudioDeviceInfo.AudioDeviceType
+    public int getDeviceType() {
+        return mDeviceType;
+    }
+
+    public int getInternalDeviceType() {
+        return mInternalDeviceType;
+    }
+
+    @NonNull
+    public String getDeviceAddress() {
+        return mDeviceAddress;
+    }
+
+    public void setSAEnabled(boolean sAEnabled) {
+        mSAEnabled = sAEnabled;
+    }
+
+    public boolean isSAEnabled() {
+        return mSAEnabled;
+    }
+
+    public void setHeadTrackerEnabled(boolean headTrackerEnabled) {
+        mHeadTrackerEnabled = headTrackerEnabled;
+    }
+
+    public boolean isHeadTrackerEnabled() {
+        return mHeadTrackerEnabled;
+    }
+
+    public void setHasHeadTracker(boolean hasHeadTracker) {
+        mHasHeadTracker = hasHeadTracker;
+    }
+
+
+    public boolean hasHeadTracker() {
+        return mHasHeadTracker;
+    }
+
+    @AudioDeviceInfo.AudioDeviceType
+    public int getAudioDeviceCategory() {
+        return mAudioDeviceCategory;
+    }
+
+    public void setAudioDeviceCategory(@AudioDeviceInfo.AudioDeviceType int audioDeviceCategory) {
+        mAudioDeviceCategory = audioDeviceCategory;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        // type check and cast
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final AdiDeviceState sads = (AdiDeviceState) obj;
+        return mDeviceType == sads.mDeviceType
+                && mInternalDeviceType == sads.mInternalDeviceType
+                && mDeviceAddress.equals(sads.mDeviceAddress)  // NonNull
+                && mSAEnabled == sads.mSAEnabled
+                && mHasHeadTracker == sads.mHasHeadTracker
+                && mHeadTrackerEnabled == sads.mHeadTrackerEnabled
+                && mAudioDeviceCategory == sads.mAudioDeviceCategory;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDeviceType, mInternalDeviceType, mDeviceAddress, mSAEnabled,
+                mHasHeadTracker, mHeadTrackerEnabled, mAudioDeviceCategory);
+    }
+
+    @Override
+    public String toString() {
+        return "type: " + mDeviceType + "internal type: " + mInternalDeviceType
+                + " addr: " + mDeviceAddress + " bt audio type: "
+                + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory)
+                + " enabled: " + mSAEnabled + " HT: " + mHasHeadTracker
+                + " HTenabled: " + mHeadTrackerEnabled;
+    }
+
+    public String toPersistableString() {
+        return (new StringBuilder().append(mDeviceType)
+                .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress)
+                .append(SETTING_FIELD_SEPARATOR).append(mSAEnabled ? "1" : "0")
+                .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0")
+                .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0")
+                .append(SETTING_FIELD_SEPARATOR).append(mInternalDeviceType)
+                .append(SETTING_FIELD_SEPARATOR).append(mAudioDeviceCategory)
+                .toString());
+    }
+
+    /**
+     * Gets the max size (including separators) when persisting the elements with
+     * {@link AdiDeviceState#toPersistableString()}.
+     */
+    public static int getPeristedMaxSize() {
+        return 36;  /* (mDeviceType)2 + (mDeviceAddresss)17 + (mInternalDeviceType)9 + (mSAEnabled)1
+                           + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1
+                           + (SETTINGS_FIELD_SEPARATOR)5 */
+    }
+
+    @Nullable
+    public static AdiDeviceState fromPersistedString(@Nullable String persistedString) {
+        if (persistedString == null) {
+            return null;
+        }
+        if (persistedString.isEmpty()) {
+            return null;
+        }
+        String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR);
+        // we may have 5 fields for the legacy AdiDeviceState and 6 containing the internal
+        // device type
+        if (fields.length < 5 || fields.length > 7) {
+            // different number of fields may mean corruption, ignore those settings
+            // newly added fields are optional (mInternalDeviceType, mBtAudioDeviceCategory)
+            return null;
+        }
+        try {
+            final int deviceType = Integer.parseInt(fields[0]);
+            int internalDeviceType = -1;
+            if (fields.length >= 6) {
+                internalDeviceType = Integer.parseInt(fields[5]);
+            }
+            int audioDeviceCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN;
+            if (fields.length == 7) {
+                audioDeviceCategory = Integer.parseInt(fields[6]);
+            }
+            final AdiDeviceState deviceState = new AdiDeviceState(deviceType,
+                    internalDeviceType, fields[1]);
+            deviceState.setHasHeadTracker(Integer.parseInt(fields[2]) == 1);
+            deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1);
+            deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1);
+            deviceState.setAudioDeviceCategory(audioDeviceCategory);
+            return deviceState;
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "unable to parse setting for AdiDeviceState: " + persistedString, e);
+            return null;
+        }
+    }
+
+    public AudioDeviceAttributes getAudioDeviceAttributes() {
+        return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+                mDeviceType, mDeviceAddress);
+    }
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 29a1941..9f9e2eb 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -30,6 +30,8 @@
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
 import android.media.BluetoothProfileConnectionInfo;
@@ -50,6 +52,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.PrintWriterPrinter;
@@ -60,6 +63,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -69,8 +73,11 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 
 
-/** @hide */
-/*package*/ final class AudioDeviceBroker {
+/**
+ * @hide
+ * (non final for mocking/spying)
+ */
+public class AudioDeviceBroker {
 
     private static final String TAG = "AS.AudioDeviceBroker";
 
@@ -289,37 +296,38 @@
      * @param on
      * @param eventSource for logging purposes
      */
-    /*package*/ void setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) {
+    /*package*/ void setSpeakerphoneOn(
+            IBinder cb, int uid, boolean on, boolean isPrivileged, String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid);
+            Log.v(TAG, "setSpeakerphoneOn, on: " + on + " uid: " + uid);
         }
         postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
-                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
-                on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
+                cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
+                on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false, isPrivileged));
     }
 
     /**
      * Select device for use for communication use cases.
      * @param cb Client binder for death detection
-     * @param pid Client pid
+     * @param uid Client uid
      * @param device Device selected or null to unselect.
      * @param eventSource for logging purposes
      */
 
     private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000;
 
-    /*package*/ boolean setCommunicationDevice(
-            IBinder cb, int pid, AudioDeviceInfo device, String eventSource) {
+    /*package*/ boolean setCommunicationDevice(IBinder cb, int uid, AudioDeviceInfo device,
+                                               boolean isPrivileged, String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "setCommunicationDevice, device: " + device + ", pid: " + pid);
+            Log.v(TAG, "setCommunicationDevice, device: " + device + ", uid: " + uid);
         }
 
         AudioDeviceAttributes deviceAttr =
                 (device != null) ? new AudioDeviceAttributes(device) : null;
-        CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, pid, deviceAttr,
-                device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true);
+        CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, uid, deviceAttr,
+                device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true, isPrivileged);
         postSetCommunicationDeviceForClient(deviceInfo);
         boolean status;
         synchronized (deviceInfo) {
@@ -353,7 +361,7 @@
             Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo);
         }
         if (!deviceInfo.mOn) {
-            CommunicationRouteClient client = getCommunicationRouteClientForPid(deviceInfo.mPid);
+            CommunicationRouteClient client = getCommunicationRouteClientForUid(deviceInfo.mUid);
             if (client == null || (deviceInfo.mDevice != null
                     && !deviceInfo.mDevice.equals(client.getDevice()))) {
                 return false;
@@ -361,22 +369,23 @@
         }
 
         AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null;
-        setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mPid, device,
-                deviceInfo.mScoAudioMode, deviceInfo.mEventSource);
+        setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mUid, device,
+                deviceInfo.mScoAudioMode, deviceInfo.mIsPrivileged, deviceInfo.mEventSource);
         return true;
     }
 
     @GuardedBy("mDeviceStateLock")
     /*package*/ void setCommunicationRouteForClient(
-                            IBinder cb, int pid, AudioDeviceAttributes device,
-                            int scoAudioMode, String eventSource) {
+                            IBinder cb, int uid, AudioDeviceAttributes device,
+                            int scoAudioMode, boolean isPrivileged, String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "setCommunicationRouteForClient: device: " + device);
+            Log.v(TAG, "setCommunicationRouteForClient: device: " + device
+                    + ", eventSource: " + eventSource);
         }
         AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
-                                        "setCommunicationRouteForClient for pid: " + pid
-                                        + " device: " + device
+                                        "setCommunicationRouteForClient for uid: " + uid
+                                        + " device: " + device + " isPrivileged: " + isPrivileged
                                         + " from API: " + eventSource)).printLog(TAG));
 
         final boolean wasBtScoRequested = isBluetoothScoRequested();
@@ -385,16 +394,18 @@
 
         // Save previous client route in case of failure to start BT SCO audio
         AudioDeviceAttributes prevClientDevice = null;
-        client = getCommunicationRouteClientForPid(pid);
+        boolean prevPrivileged = false;
+        client = getCommunicationRouteClientForUid(uid);
         if (client != null) {
             prevClientDevice = client.getDevice();
+            prevPrivileged = client.isPrivileged();
         }
 
         if (device != null) {
-            client = addCommunicationRouteClient(cb, pid, device);
+            client = addCommunicationRouteClient(cb, uid, device, isPrivileged);
             if (client == null) {
-                Log.w(TAG, "setCommunicationRouteForClient: could not add client for pid: "
-                        + pid + " and device: " + device);
+                Log.w(TAG, "setCommunicationRouteForClient: could not add client for uid: "
+                        + uid + " and device: " + device);
             }
         } else {
             client = removeCommunicationRouteClient(cb, true);
@@ -406,11 +417,11 @@
         boolean isBtScoRequested = isBluetoothScoRequested();
         if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) {
             if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
-                Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for pid: "
-                        + pid);
+                Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: "
+                        + uid);
                 // clean up or restore previous client selection
                 if (prevClientDevice != null) {
-                    addCommunicationRouteClient(cb, pid, prevClientDevice);
+                    addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged);
                 } else {
                     removeCommunicationRouteClient(cb, true);
                 }
@@ -424,7 +435,7 @@
         // LE Audio it stays the same and we must trigger the proper stream volume alignment, if
         // LE Audio communication device is activated after the audio system has already switched to
         // MODE_IN_CALL mode.
-        if (isBluetoothLeAudioRequested()) {
+        if (isBluetoothLeAudioRequested() && device != null) {
             final int streamType = mAudioService.getBluetoothContextualVolumeStream();
             final int leAudioVolIndex = getVssVolumeForDevice(streamType, device.getInternalType());
             final int leAudioMaxVolIndex = getMaxVssVolumeForStream(streamType);
@@ -447,11 +458,12 @@
     @GuardedBy("mDeviceStateLock")
     private CommunicationRouteClient topCommunicationRouteClient() {
         for (CommunicationRouteClient crc : mCommunicationRouteClients) {
-            if (crc.getPid() == mAudioModeOwner.mPid) {
+            if (crc.getUid() == mAudioModeOwner.mUid) {
                 return crc;
             }
         }
-        if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0) {
+        if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0
+                && mCommunicationRouteClients.get(0).isActive()) {
             return mCommunicationRouteClients.get(0);
         }
         return null;
@@ -491,14 +503,48 @@
     };
 
     /*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) {
+        return isValidCommunicationDeviceType(device.getType());
+    }
+
+    private static boolean isValidCommunicationDeviceType(int deviceType) {
         for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
-            if (device.getType() == type) {
+            if (deviceType == type) {
                 return true;
             }
         }
         return false;
     }
 
+    /*package */
+    void postCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) {
+        if (!isValidCommunicationDeviceType(
+                AudioDeviceInfo.convertInternalDeviceToDeviceType(device.getInternalType()))) {
+            return;
+        }
+        sendLMsgNoDelay(MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL, SENDMSG_QUEUE, device);
+    }
+
+    @GuardedBy("mDeviceStateLock")
+    void onCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) {
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "onCheckCommunicationDeviceRemoval device: " + device.toString());
+        }
+        for (CommunicationRouteClient crc : mCommunicationRouteClients) {
+            if (device.equals(crc.getDevice())) {
+                if (AudioService.DEBUG_COMM_RTE) {
+                    Log.v(TAG, "onCheckCommunicationDeviceRemoval removing client: "
+                            + crc.toString());
+                }
+                // Cancelling the route for this client will remove it from the stack and update
+                // the communication route.
+                CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(
+                        crc.getBinder(), crc.getUid(), device, false,
+                        BtHelper.SCO_MODE_UNDEFINED, "onCheckCommunicationDeviceRemoval",
+                        false, crc.isPrivileged());
+                postSetCommunicationDeviceForClient(deviceInfo);
+            }
+        }
+    }
     /* package */ static List<AudioDeviceInfo> getAvailableCommunicationDevices() {
         ArrayList<AudioDeviceInfo> commDevices = new ArrayList<>();
         AudioDeviceInfo[] allDevices =
@@ -1107,26 +1153,26 @@
         sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info);
     }
 
-    /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode,
-                @NonNull String eventSource) {
+    /*package*/ void startBluetoothScoForClient(IBinder cb, int uid, int scoAudioMode,
+                                                boolean isPrivileged, @NonNull String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "startBluetoothScoForClient, pid: " + pid);
+            Log.v(TAG, "startBluetoothScoForClient, uid: " + uid);
         }
         postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
-                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
-                true, scoAudioMode, eventSource, false));
+                cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+                true, scoAudioMode, eventSource, false, isPrivileged));
     }
 
     /*package*/ void stopBluetoothScoForClient(
-                        IBinder cb, int pid, @NonNull String eventSource) {
+                        IBinder cb, int uid, boolean isPrivileged, @NonNull String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "stopBluetoothScoForClient, pid: " + pid);
+            Log.v(TAG, "stopBluetoothScoForClient, uid: " + uid);
         }
         postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
-                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
-                false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
+                cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+                false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false, isPrivileged));
     }
 
     /*package*/ int setPreferredDevicesForStrategySync(int strategy,
@@ -1367,22 +1413,24 @@
 
     /*package*/ static final class CommunicationDeviceInfo {
         final @NonNull IBinder mCb; // Identifies the requesting client for death handler
-        final int mPid; // Requester process ID
+        final int mUid; // Requester UID
         final @Nullable AudioDeviceAttributes mDevice; // Device being set or reset.
         final boolean mOn; // true if setting, false if resetting
         final int mScoAudioMode; // only used for SCO: requested audio mode
+        final boolean mIsPrivileged; // true if the client app has MODIFY_PHONE_STATE permission
         final @NonNull String mEventSource; // caller identifier for logging
         boolean mWaitForStatus; // true if the caller waits for a completion status (API dependent)
         boolean mStatus = false; // completion status only used if mWaitForStatus is true
 
-        CommunicationDeviceInfo(@NonNull IBinder cb, int pid,
+        CommunicationDeviceInfo(@NonNull IBinder cb, int uid,
                 @Nullable AudioDeviceAttributes device, boolean on, int scoAudioMode,
-                @NonNull String eventSource, boolean waitForStatus) {
+                @NonNull String eventSource, boolean waitForStatus, boolean isPrivileged) {
             mCb = cb;
-            mPid = pid;
+            mUid = uid;
             mDevice = device;
             mOn = on;
             mScoAudioMode = scoAudioMode;
+            mIsPrivileged = isPrivileged;
             mEventSource = eventSource;
             mWaitForStatus = waitForStatus;
         }
@@ -1401,16 +1449,17 @@
             }
 
             return mCb.equals(((CommunicationDeviceInfo) o).mCb)
-                    && mPid == ((CommunicationDeviceInfo) o).mPid;
+                    && mUid == ((CommunicationDeviceInfo) o).mUid;
         }
 
         @Override
         public String toString() {
             return "CommunicationDeviceInfo mCb=" + mCb.toString()
-                    + " mPid=" + mPid
+                    + " mUid=" + mUid
                     + " mDevice=[" + (mDevice != null ? mDevice.toString() : "null") + "]"
                     + " mOn=" + mOn
                     + " mScoAudioMode=" + mScoAudioMode
+                    + " mIsPrivileged=" + mIsPrivileged
                     + " mEventSource=" + mEventSource
                     + " mWaitForStatus=" + mWaitForStatus
                     + " mStatus=" + mStatus;
@@ -1440,7 +1489,7 @@
         }
     }
 
-    /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes,
+    /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes,
                                 boolean connect, @Nullable BluetoothDevice btDevice) {
         synchronized (mDeviceStateLock) {
             return mDeviceInventory.handleDeviceConnection(
@@ -1507,8 +1556,7 @@
 
         pw.println("\n" + prefix + "Communication route clients:");
         mCommunicationRouteClients.forEach((cl) -> {
-            pw.println("  " + prefix + "pid: " + cl.getPid() + " device: "
-                        + cl.getDevice() + " cb: " + cl.getBinder()); });
+            pw.println("  " + prefix + cl.toString()); });
 
         pw.println("\n" + prefix + "Computed Preferred communication device: "
                 +  preferredCommunicationDevice());
@@ -1850,6 +1898,18 @@
                     final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
                     BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
                 } break;
+
+                case MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL: {
+                    synchronized (mSetModeLock) {
+                        synchronized (mDeviceStateLock) {
+                            onCheckCommunicationDeviceRemoval((AudioDeviceAttributes) msg.obj);
+                        }
+                    }
+                } break;
+
+                case MSG_PERSIST_AUDIO_DEVICE_SETTINGS:
+                    onPersistAudioDeviceSettings();
+                    break;
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
             }
@@ -1926,6 +1986,9 @@
     private static final int MSG_IL_BTLEAUDIO_TIMEOUT = 49;
 
     private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;
+    private static final int MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL = 53;
+
+    private static final int MSG_PERSIST_AUDIO_DEVICE_SETTINGS = 54;
 
     private static boolean isMessageHandledUnderWakelock(int msgId) {
         switch(msgId) {
@@ -2101,13 +2164,20 @@
 
     private class CommunicationRouteClient implements IBinder.DeathRecipient {
         private final IBinder mCb;
-        private final int mPid;
+        private final int mUid;
+        private final boolean mIsPrivileged;
         private AudioDeviceAttributes mDevice;
+        private boolean mPlaybackActive;
+        private boolean mRecordingActive;
 
-        CommunicationRouteClient(IBinder cb, int pid, AudioDeviceAttributes device) {
+        CommunicationRouteClient(IBinder cb, int uid, AudioDeviceAttributes device,
+                                 boolean isPrivileged) {
             mCb = cb;
-            mPid = pid;
+            mUid = uid;
             mDevice = device;
+            mIsPrivileged = isPrivileged;
+            mPlaybackActive = mAudioService.isPlaybackActiveForUid(uid);
+            mRecordingActive = mAudioService.isRecordingActiveForUid(uid);
         }
 
         public boolean registerDeathRecipient() {
@@ -2138,13 +2208,38 @@
             return mCb;
         }
 
-        int getPid() {
-            return mPid;
+        int getUid() {
+            return mUid;
+        }
+
+        boolean isPrivileged() {
+            return mIsPrivileged;
         }
 
         AudioDeviceAttributes getDevice() {
             return mDevice;
         }
+
+        public void setPlaybackActive(boolean active) {
+            mPlaybackActive = active;
+        }
+
+        public void setRecordingActive(boolean active) {
+            mRecordingActive = active;
+        }
+
+        public boolean isActive() {
+            return mIsPrivileged || mRecordingActive || mPlaybackActive;
+        }
+
+        @Override
+        public String toString() {
+            return "[CommunicationRouteClient: mUid: " + mUid
+                    + " mDevice: " + mDevice.toString()
+                    + " mIsPrivileged: " + mIsPrivileged
+                    + " mPlaybackActive: " + mPlaybackActive
+                    + " mRecordingActive: " + mRecordingActive + "]";
+        }
     }
 
     // @GuardedBy("mSetModeLock")
@@ -2154,8 +2249,9 @@
             return;
         }
         Log.w(TAG, "Communication client died");
-        setCommunicationRouteForClient(client.getBinder(), client.getPid(), null,
-                BtHelper.SCO_MODE_UNDEFINED, "onCommunicationRouteClientDied");
+        setCommunicationRouteForClient(client.getBinder(), client.getUid(), null,
+                BtHelper.SCO_MODE_UNDEFINED, client.isPrivileged(),
+                "onCommunicationRouteClientDied");
     }
 
     /**
@@ -2242,8 +2338,8 @@
                     + crc + " eventSource: " + eventSource);
         }
         if (crc != null) {
-            setCommunicationRouteForClient(crc.getBinder(), crc.getPid(), crc.getDevice(),
-                    BtHelper.SCO_MODE_UNDEFINED, eventSource);
+            setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(),
+                    BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource);
         }
     }
 
@@ -2267,6 +2363,7 @@
         dispatchCommunicationDevice();
     }
 
+    @GuardedBy("mDeviceStateLock")
     private CommunicationRouteClient removeCommunicationRouteClient(
                     IBinder cb, boolean unregister) {
         for (CommunicationRouteClient cl : mCommunicationRouteClients) {
@@ -2282,11 +2379,12 @@
     }
 
     @GuardedBy("mDeviceStateLock")
-    private CommunicationRouteClient addCommunicationRouteClient(
-                    IBinder cb, int pid, AudioDeviceAttributes device) {
+    private CommunicationRouteClient addCommunicationRouteClient(IBinder cb, int uid,
+                AudioDeviceAttributes device, boolean isPrivileged) {
         // always insert new request at first position
         removeCommunicationRouteClient(cb, true);
-        CommunicationRouteClient client = new CommunicationRouteClient(cb, pid, device);
+        CommunicationRouteClient client =
+                new CommunicationRouteClient(cb, uid, device, isPrivileged);
         if (client.registerDeathRecipient()) {
             mCommunicationRouteClients.add(0, client);
             return client;
@@ -2295,9 +2393,9 @@
     }
 
     @GuardedBy("mDeviceStateLock")
-    private CommunicationRouteClient getCommunicationRouteClientForPid(int pid) {
+    private CommunicationRouteClient getCommunicationRouteClientForUid(int uid) {
         for (CommunicationRouteClient cl : mCommunicationRouteClients) {
-            if (cl.getPid() == pid) {
+            if (cl.getUid() == uid) {
                 return cl;
             }
         }
@@ -2330,6 +2428,45 @@
         return device;
     }
 
+    void updateCommunicationRouteClientsActivity(
+            List<AudioPlaybackConfiguration> playbackConfigs,
+            List<AudioRecordingConfiguration> recordConfigs) {
+        synchronized (mSetModeLock) {
+            synchronized (mDeviceStateLock) {
+                boolean updateCommunicationRoute = false;
+                for (CommunicationRouteClient crc : mCommunicationRouteClients) {
+                    boolean wasActive = crc.isActive();
+                    if (playbackConfigs != null) {
+                        crc.setPlaybackActive(false);
+                        for (AudioPlaybackConfiguration config : playbackConfigs) {
+                            if (config.getClientUid() == crc.getUid()
+                                    && config.isActive()) {
+                                crc.setPlaybackActive(true);
+                                break;
+                            }
+                        }
+                    }
+                    if (recordConfigs != null) {
+                        crc.setRecordingActive(false);
+                        for (AudioRecordingConfiguration config : recordConfigs) {
+                            if (config.getClientUid() == crc.getUid()
+                                    && !config.isClientSilenced()) {
+                                crc.setRecordingActive(true);
+                                break;
+                            }
+                        }
+                    }
+                    if (wasActive != crc.isActive()) {
+                        updateCommunicationRoute = true;
+                    }
+                }
+                if (updateCommunicationRoute) {
+                    postUpdateCommunicationRouteClient("updateCommunicationRouteClientsActivity");
+                }
+            }
+        }
+    }
+
     @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
         synchronized (mDeviceStateLock) {
             return mDeviceInventory.getDeviceSensorUuid(device);
@@ -2344,4 +2481,105 @@
                 info.getId(),
                 null /*mixerAttributes*/);
     }
+
+    /**
+     * post a message to persist the audio device settings.
+     * Message is delayed by 1s on purpose in case of successive changes in quick succession (at
+     * init time for instance)
+     * Note this method is made public to work around a Mockito bug where it needs to be public
+     * in order to be mocked by a test a the same package
+     * (see https://code.google.com/archive/p/mockito/issues/127)
+     */
+    public void persistAudioDeviceSettings() {
+        sendMsg(MSG_PERSIST_AUDIO_DEVICE_SETTINGS, SENDMSG_REPLACE, /*delay*/ 1000);
+    }
+
+    void onPersistAudioDeviceSettings() {
+        final String deviceSettings = mDeviceInventory.getDeviceSettings();
+        Log.v(TAG, "saving AdiDeviceState: " + deviceSettings);
+        final SettingsAdapter settings = mAudioService.getSettings();
+        boolean res = settings.putSecureStringForUser(mAudioService.getContentResolver(),
+                Settings.Secure.AUDIO_DEVICE_INVENTORY,
+                deviceSettings, UserHandle.USER_CURRENT);
+        if (!res) {
+            Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings);
+        }
+    }
+
+    void onReadAudioDeviceSettings() {
+        final SettingsAdapter settingsAdapter = mAudioService.getSettings();
+        final ContentResolver contentResolver = mAudioService.getContentResolver();
+        String settings = settingsAdapter.getSecureStringForUser(contentResolver,
+                Settings.Secure.AUDIO_DEVICE_INVENTORY, UserHandle.USER_CURRENT);
+        if (settings == null) {
+            Log.i(TAG, "reading AdiDeviceState from legacy key"
+                    + Settings.Secure.SPATIAL_AUDIO_ENABLED);
+            // legacy string format for key SPATIAL_AUDIO_ENABLED has the same order of fields like
+            // the strings for key AUDIO_DEVICE_INVENTORY. This will ensure to construct valid
+            // device settings when calling {@link #setDeviceSettings()}
+            settings = settingsAdapter.getSecureStringForUser(contentResolver,
+                    Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT);
+            if (settings == null) {
+                Log.i(TAG, "no AdiDeviceState stored with legacy key");
+            } else if (!settings.equals("")) {
+                // Delete old key value and update the new key
+                if (!settingsAdapter.putSecureStringForUser(contentResolver,
+                        Settings.Secure.SPATIAL_AUDIO_ENABLED,
+                        /*value=*/"",
+                        UserHandle.USER_CURRENT)) {
+                    Log.w(TAG, "cannot erase the legacy AdiDeviceState with key "
+                            + Settings.Secure.SPATIAL_AUDIO_ENABLED);
+                }
+                if (!settingsAdapter.putSecureStringForUser(contentResolver,
+                        Settings.Secure.AUDIO_DEVICE_INVENTORY,
+                        settings,
+                        UserHandle.USER_CURRENT)) {
+                    Log.e(TAG, "error updating the new AdiDeviceState with key "
+                            + Settings.Secure.AUDIO_DEVICE_INVENTORY);
+                }
+            }
+        }
+
+        if (settings != null && !settings.equals("")) {
+            setDeviceSettings(settings);
+        }
+    }
+
+    void setDeviceSettings(String settings) {
+        mDeviceInventory.setDeviceSettings(settings);
+    }
+
+    /** Test only method. */
+    String getDeviceSettings() {
+        return mDeviceInventory.getDeviceSettings();
+    }
+
+    Collection<AdiDeviceState> getImmutableDeviceInventory() {
+        return mDeviceInventory.getImmutableDeviceInventory();
+    }
+
+    void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState) {
+        mDeviceInventory.addOrUpdateDeviceSAStateInInventory(deviceState);
+    }
+
+    void addOrUpdateBtAudioDeviceCategoryInInventory(AdiDeviceState deviceState) {
+        mDeviceInventory.addOrUpdateAudioDeviceCategoryInInventory(deviceState);
+    }
+
+    @Nullable
+    AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada,
+            int canonicalType) {
+        return mDeviceInventory.findDeviceStateForAudioDeviceAttributes(ada, canonicalType);
+    }
+
+    @Nullable
+    AdiDeviceState findBtDeviceStateForAddress(String address, boolean isBle) {
+        return mDeviceInventory.findBtDeviceStateForAddress(address, isBle);
+    }
+
+    //------------------------------------------------
+    // for testing purposes only
+    void clearDeviceInventory() {
+        mDeviceInventory.clearDeviceInventory();
+    }
 }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 0c7f11f..13e3fc7 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -15,6 +15,10 @@
  */
 package com.android.server.audio;
 
+import static android.media.AudioSystem.DEVICE_OUT_ALL_A2DP_SET;
+import static android.media.AudioSystem.DEVICE_OUT_ALL_BLE_SET;
+import static android.media.AudioSystem.isBluetoothDevice;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
@@ -59,11 +63,13 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
@@ -78,12 +84,111 @@
 
     private static final String TAG = "AS.AudioDeviceInventory";
 
+    private static final String SETTING_DEVICE_SEPARATOR_CHAR = "|";
+    private static final String SETTING_DEVICE_SEPARATOR = "\\|";
+
     // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices
     private final Object mDevicesLock = new Object();
 
     //Audio Analytics ids.
     private static final String mMetricsId = "audio.device.";
 
+    private final Object mDeviceInventoryLock = new Object();
+
+    @GuardedBy("mDeviceInventoryLock")
+    private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>();
+
+    Collection<AdiDeviceState> getImmutableDeviceInventory() {
+        synchronized (mDeviceInventoryLock) {
+            return mDeviceInventory.values();
+        }
+    }
+
+    /**
+     * Adds a new AdiDeviceState or updates the spatial audio related properties of the matching
+     * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list.
+     * @param deviceState the device to update
+     */
+    void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState) {
+        synchronized (mDeviceInventoryLock) {
+            mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, (oldState, newState) -> {
+                oldState.setHasHeadTracker(newState.hasHeadTracker());
+                oldState.setHeadTrackerEnabled(newState.isHeadTrackerEnabled());
+                oldState.setSAEnabled(newState.isSAEnabled());
+                return oldState;
+            });
+        }
+    }
+
+    /**
+     * Adds a new AdiDeviceState or updates the audio device cateogory of the matching
+     * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list.
+     * @param deviceState the device to update
+     */
+    void addOrUpdateAudioDeviceCategoryInInventory(AdiDeviceState deviceState) {
+        synchronized (mDeviceInventoryLock) {
+            mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, (oldState, newState) -> {
+                oldState.setAudioDeviceCategory(newState.getAudioDeviceCategory());
+                return oldState;
+            });
+        }
+    }
+
+    /**
+     * Finds the BT device that matches the passed {@code address}. Currently, this method only
+     * returns a valid device for A2DP and BLE devices.
+     *
+     * @param address MAC address of BT device
+     * @param isBle true if the device is BLE, false for A2DP
+     * @return the found {@link AdiDeviceState} or {@code null} otherwise.
+     */
+    @Nullable
+    AdiDeviceState findBtDeviceStateForAddress(String address, boolean isBle) {
+        synchronized (mDeviceInventoryLock) {
+            final Set<Integer> deviceSet = isBle ? DEVICE_OUT_ALL_BLE_SET : DEVICE_OUT_ALL_A2DP_SET;
+            for (Integer internalType : deviceSet) {
+                AdiDeviceState deviceState = mDeviceInventory.get(
+                        new Pair<>(internalType, address));
+                if (deviceState != null) {
+                    return deviceState;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Finds the device state that matches the passed {@link AudioDeviceAttributes} and device
+     * type. Note: currently this method only returns a valid device for A2DP and BLE devices.
+     *
+     * @param ada attributes of device to match
+     * @param canonicalDeviceType external device type to match
+     * @return the found {@link AdiDeviceState} matching a cached A2DP or BLE device or
+     *         {@code null} otherwise.
+     */
+    @Nullable
+    AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada,
+            int canonicalDeviceType) {
+        final boolean isWireless = isBluetoothDevice(ada.getInternalType());
+        synchronized (mDeviceInventoryLock) {
+            for (AdiDeviceState deviceState : mDeviceInventory.values()) {
+                if (deviceState.getDeviceType() == canonicalDeviceType
+                        && (!isWireless || ada.getAddress().equals(
+                        deviceState.getDeviceAddress()))) {
+                    return deviceState;
+                }
+            }
+        }
+        return null;
+    }
+
+    /** Clears all cached {@link AdiDeviceState}'s. */
+    void clearDeviceInventory() {
+        synchronized (mDeviceInventoryLock) {
+            mDeviceInventory.clear();
+        }
+    }
+
     // List of connected devices
     // Key for map created from DeviceInfo.makeDeviceListKey()
     @GuardedBy("mDevicesLock")
@@ -341,6 +446,12 @@
         mAppliedPresetRolesInt.forEach((key, devices) -> {
             pw.println("  " + prefix + "preset: " + key.first
                     +  " role:" + key.second + " devices:" + devices); });
+        pw.println("\ndevices:\n");
+        synchronized (mDeviceInventoryLock) {
+            for (AdiDeviceState device : mDeviceInventory.values()) {
+                pw.println("\t" + device + "\n");
+            }
+        }
     }
 
     //------------------------------------------------------------
@@ -1185,11 +1296,11 @@
             AudioDeviceInfo[] connectedDevices = AudioManager.getDevicesStatic(
                     AudioManager.GET_DEVICES_ALL);
 
-            Iterator<Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole =
+            Iterator<Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole =
                     rolesMap.entrySet().iterator();
 
             while (itRole.hasNext()) {
-                Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry =
+                Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry =
                         itRole.next();
                 Pair<Integer, Integer> keyRole = entry.getKey();
                 Iterator<AudioDeviceAttributes> itDev = rolesMap.get(keyRole).iterator();
@@ -1198,7 +1309,7 @@
 
                     AudioDeviceInfo device = Stream.of(connectedDevices)
                             .filter(d -> d.getInternalType() == ada.getInternalType())
-                            .filter(d -> (!AudioSystem.isBluetoothDevice(d.getInternalType())
+                            .filter(d -> (!isBluetoothDevice(d.getInternalType())
                                             || (d.getAddress().equals(ada.getAddress()))))
                             .findFirst()
                             .orElse(null);
@@ -1245,8 +1356,9 @@
      * @param btDevice the corresponding Bluetooth device when relevant.
      * @return false if an error was reported by AudioSystem
      */
-    /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect,
-            boolean isForTesting, @Nullable BluetoothDevice btDevice) {
+    /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes,
+                                               boolean connect, boolean isForTesting,
+                                               @Nullable BluetoothDevice btDevice) {
         int device = attributes.getInternalType();
         String address = attributes.getAddress();
         String deviceName = attributes.getName();
@@ -1297,6 +1409,7 @@
                         AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
                 // always remove even if disconnection failed
                 mConnectedDevices.remove(deviceKey);
+                mDeviceBroker.postCheckCommunicationDeviceRemoval(attributes);
                 status = true;
             }
             if (status) {
@@ -1619,7 +1732,7 @@
         }
 
         for (DeviceInfo di : mConnectedDevices.values()) {
-            if (!AudioSystem.isBluetoothDevice(di.mDeviceType)) {
+            if (!isBluetoothDevice(di.mDeviceType)) {
                 continue;
             }
             AudioDeviceAttributes ada =
@@ -1733,7 +1846,7 @@
         }
         HashSet<String> processedAddresses = new HashSet<>(0);
         for (DeviceInfo di : mConnectedDevices.values()) {
-            if (!AudioSystem.isBluetoothDevice(di.mDeviceType)
+            if (!isBluetoothDevice(di.mDeviceType)
                     || processedAddresses.contains(di.mDeviceAddress)) {
                 continue;
             }
@@ -1743,7 +1856,7 @@
                         + di.mDeviceAddress + ", preferredProfiles: " + preferredProfiles);
             }
             for (DeviceInfo di2 : mConnectedDevices.values()) {
-                if (!AudioSystem.isBluetoothDevice(di2.mDeviceType)
+                if (!isBluetoothDevice(di2.mDeviceType)
                         || !di.mDeviceAddress.equals(di2.mDeviceAddress)) {
                     continue;
                 }
@@ -1801,8 +1914,9 @@
 
         // device to remove was visible by APM, update APM
         mDeviceBroker.clearAvrcpAbsoluteVolumeSupported();
-        final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
+        AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+        final int res = mAudioSystem.setDeviceConnectionState(ada,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE, a2dpCodec);
 
         if (res != AudioSystem.AUDIO_STATUS_OK) {
@@ -1816,11 +1930,13 @@
                     "A2DP device addr=" + address + " made unavailable")).printLog(TAG));
         }
         mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+
         // Remove A2DP routes as well
         setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/);
         mmi.record();
         updateBluetoothPreferredModes_l(null /*connectedDevice*/);
         purgeDevicesRoles_l();
+        mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
     }
 
     @GuardedBy("mDevicesLock")
@@ -1855,12 +1971,14 @@
 
     @GuardedBy("mDevicesLock")
     private void makeA2dpSrcUnavailable(String address) {
-        mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
+        AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
+        mAudioSystem.setDeviceConnectionState(ada,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE,
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.remove(
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
+        mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
     }
 
     @GuardedBy("mDevicesLock")
@@ -1893,8 +2011,9 @@
 
     @GuardedBy("mDevicesLock")
     private void makeHearingAidDeviceUnavailable(String address) {
-        mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                AudioSystem.DEVICE_OUT_HEARING_AID, address),
+        AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                AudioSystem.DEVICE_OUT_HEARING_AID, address);
+        mAudioSystem.setDeviceConnectionState(ada,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE,
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.remove(
@@ -1906,6 +2025,7 @@
                 .set(MediaMetrics.Property.DEVICE,
                         AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID))
                 .record();
+        mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
     }
 
     /**
@@ -1950,12 +2070,18 @@
     @GuardedBy("mDevicesLock")
     private void makeLeAudioDeviceAvailable(
             AudioDeviceBroker.BtDeviceInfo btInfo, int streamType, String eventSource) {
-        final String address = btInfo.mDevice.getAddress();
-        final String name = BtHelper.getName(btInfo.mDevice);
         final int volumeIndex = btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10;
         final int device = btInfo.mAudioSystemDevice;
 
         if (device != AudioSystem.DEVICE_NONE) {
+            final String address = btInfo.mDevice.getAddress();
+            String name = BtHelper.getName(btInfo.mDevice);
+
+            // The BT Stack does not provide a name for LE Broadcast devices
+            if (device == AudioSystem.DEVICE_OUT_BLE_BROADCAST && name.equals("")) {
+                name = "Broadcast";
+            }
+
             /* Audio Policy sees Le Audio similar to A2DP. Let's make sure
              * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
              */
@@ -2002,9 +2128,10 @@
 
     @GuardedBy("mDevicesLock")
     private void makeLeAudioDeviceUnavailableNow(String address, int device) {
+        AudioDeviceAttributes ada = null;
         if (device != AudioSystem.DEVICE_NONE) {
-            final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                    device, address),
+            ada = new AudioDeviceAttributes(device, address);
+            final int res = AudioSystem.setDeviceConnectionState(ada,
                     AudioSystem.DEVICE_STATE_UNAVAILABLE,
                     AudioSystem.AUDIO_FORMAT_DEFAULT);
 
@@ -2024,6 +2151,9 @@
         setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
         updateBluetoothPreferredModes_l(null /*connectedDevice*/);
         purgeDevicesRoles_l();
+        if (ada != null) {
+            mDeviceBroker.postCheckCommunicationDeviceRemoval(ada);
+        }
     }
 
     @GuardedBy("mDevicesLock")
@@ -2359,6 +2489,42 @@
         }
     }
 
+    /*package*/ String getDeviceSettings() {
+        int deviceCatalogSize = 0;
+        synchronized (mDeviceInventoryLock) {
+            deviceCatalogSize = mDeviceInventory.size();
+
+            final StringBuilder settingsBuilder = new StringBuilder(
+                            deviceCatalogSize * AdiDeviceState.getPeristedMaxSize());
+
+            Iterator<AdiDeviceState> iterator = mDeviceInventory.values().iterator();
+            if (iterator.hasNext()) {
+                settingsBuilder.append(iterator.next().toPersistableString());
+            }
+            while (iterator.hasNext()) {
+                settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR);
+                settingsBuilder.append(iterator.next().toPersistableString());
+            }
+            return settingsBuilder.toString();
+        }
+    }
+
+    /*package*/ void setDeviceSettings(String settings) {
+        clearDeviceInventory();
+        String[] devSettings = TextUtils.split(Objects.requireNonNull(settings),
+                SETTING_DEVICE_SEPARATOR);
+        // small list, not worth overhead of Arrays.stream(devSettings)
+        for (String setting : devSettings) {
+            AdiDeviceState devState = AdiDeviceState.fromPersistedString(setting);
+            // Note if the device is not compatible with spatialization mode or the device
+            // type is not canonical, it will be ignored in {@link SpatializerHelper}.
+            if (devState != null) {
+                addOrUpdateDeviceSAStateInInventory(devState);
+                addOrUpdateAudioDeviceCategoryInInventory(devState);
+            }
+        }
+    }
+
     //----------------------------------------------------------
     // For tests only
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 53ed38e..6a73c2b 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -19,6 +19,14 @@
 import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
 import static android.Manifest.permission.REMOTE_AUDIO_PLAYBACK;
 import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT;
+import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
+import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER;
+import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
+import static android.media.AudioManager.DEVICE_OUT_BLE_HEADSET;
+import static android.media.AudioManager.DEVICE_OUT_BLE_SPEAKER;
+import static android.media.AudioManager.DEVICE_OUT_BLUETOOTH_A2DP;
 import static android.media.AudioManager.RINGER_MODE_NORMAL;
 import static android.media.AudioManager.RINGER_MODE_SILENT;
 import static android.media.AudioManager.RINGER_MODE_VIBRATE;
@@ -91,6 +99,7 @@
 import android.media.AudioFormat;
 import android.media.AudioHalVersionInfo;
 import android.media.AudioManager;
+import android.media.AudioManager.AudioDeviceCategory;
 import android.media.AudioManagerInternal;
 import android.media.AudioMixerAttributes;
 import android.media.AudioPlaybackConfiguration;
@@ -381,7 +390,6 @@
     private static final int MSG_DISPATCH_AUDIO_MODE = 40;
     private static final int MSG_ROUTING_UPDATED = 41;
     private static final int MSG_INIT_HEADTRACKING_SENSORS = 42;
-    private static final int MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS = 43;
     private static final int MSG_ADD_ASSISTANT_SERVICE_UID = 44;
     private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45;
     private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46;
@@ -403,6 +411,7 @@
     private static final int MSG_DISABLE_AUDIO_FOR_UID = 100;
     private static final int MSG_INIT_STREAMS_VOLUMES = 101;
     private static final int MSG_INIT_SPATIALIZER = 102;
+    private static final int MSG_INIT_ADI_DEVICE_STATES = 103;
 
     // end of messages handled under wakelock
 
@@ -1021,6 +1030,8 @@
         mAudioPolicy = audioPolicy;
         mPlatformType = AudioSystem.getPlatformType(context);
 
+        mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
+
         mIsSingleVolume = AudioSystem.isSingleVolume(context);
 
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -1033,13 +1044,14 @@
 
         mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase));
 
-        final boolean binauralEnabledDefault = SystemProperties.getBoolean(
+        boolean binauralEnabledDefault = SystemProperties.getBoolean(
                 "ro.audio.spatializer_binaural_enabled_default", true);
-        final boolean transauralEnabledDefault = SystemProperties.getBoolean(
+        boolean transauralEnabledDefault = SystemProperties.getBoolean(
                 "ro.audio.spatializer_transaural_enabled_default", true);
-        final boolean headTrackingEnabledDefault = mContext.getResources().getBoolean(
+        boolean headTrackingEnabledDefault = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default);
-        mSpatializerHelper = new SpatializerHelper(this, mAudioSystem,
+
+        mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, mDeviceBroker,
                 binauralEnabledDefault, transauralEnabledDefault, headTrackingEnabledDefault);
 
         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
@@ -1207,8 +1219,6 @@
         mUseFixedVolume = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_useFixedVolume);
 
-        mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
-
         mRecordMonitor = new RecordingActivityMonitor(mContext);
         mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true);
 
@@ -1250,6 +1260,8 @@
         // done with service initialization, continue additional work in our Handler thread
         queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES,
                 0 /* arg1 */,  0 /* arg2 */, null /* obj */,  0 /* delay */);
+        queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_ADI_DEVICE_STATES,
+                0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
         queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_SPATIALIZER,
                 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
     }
@@ -4262,22 +4274,41 @@
         // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
         // and request an audio mode update immediately. Upon any other change, queue the message
         // and request an audio mode update after a grace period.
+        updateAudioModeHandlers(
+                configs /* playbackConfigs */, null /* recordConfigs */);
+        mDeviceBroker.updateCommunicationRouteClientsActivity(
+                configs /* playbackConfigs */, null /* recordConfigs */);
+    }
+
+    void updateAudioModeHandlers(List<AudioPlaybackConfiguration> playbackConfigs,
+                                 List<AudioRecordingConfiguration> recordConfigs) {
         synchronized (mDeviceBroker.mSetModeLock) {
             boolean updateAudioMode = false;
             int existingMsgPolicy = SENDMSG_QUEUE;
             int delay = CHECK_MODE_FOR_UID_PERIOD_MS;
             for (SetModeDeathHandler h : mSetModeDeathHandlers) {
                 boolean wasActive = h.isActive();
-                h.setPlaybackActive(false);
-                for (AudioPlaybackConfiguration config : configs) {
-                    final int usage = config.getAudioAttributes().getUsage();
-                    if (config.getClientUid() == h.getUid()
-                            && (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
+                if (playbackConfigs != null) {
+                    h.setPlaybackActive(false);
+                    for (AudioPlaybackConfiguration config : playbackConfigs) {
+                        final int usage = config.getAudioAttributes().getUsage();
+                        if (config.getClientUid() == h.getUid()
+                                && (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
                                 || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
-                            && config.getPlayerState()
-                                == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-                        h.setPlaybackActive(true);
-                        break;
+                                && config.isActive()) {
+                            h.setPlaybackActive(true);
+                            break;
+                        }
+                    }
+                }
+                if (recordConfigs != null) {
+                    h.setRecordingActive(false);
+                    for (AudioRecordingConfiguration config : recordConfigs) {
+                        if (config.getClientUid() == h.getUid() && !config.isClientSilenced()
+                                && config.getAudioSource() == AudioSource.VOICE_COMMUNICATION) {
+                            h.setRecordingActive(true);
+                            break;
+                        }
                     }
                 }
                 if (wasActive != h.isActive()) {
@@ -4315,38 +4346,10 @@
         // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
         // and request an audio mode update immediately. Upon any other change, queue the message
         // and request an audio mode update after a grace period.
-        synchronized (mDeviceBroker.mSetModeLock) {
-            boolean updateAudioMode = false;
-            int existingMsgPolicy = SENDMSG_QUEUE;
-            int delay = CHECK_MODE_FOR_UID_PERIOD_MS;
-            for (SetModeDeathHandler h : mSetModeDeathHandlers) {
-                boolean wasActive = h.isActive();
-                h.setRecordingActive(false);
-                for (AudioRecordingConfiguration config : configs) {
-                    if (config.getClientUid() == h.getUid()
-                            && config.getAudioSource() == AudioSource.VOICE_COMMUNICATION) {
-                        h.setRecordingActive(true);
-                        break;
-                    }
-                }
-                if (wasActive != h.isActive()) {
-                    updateAudioMode = true;
-                    if (h.isActive() && h == getAudioModeOwnerHandler()) {
-                        existingMsgPolicy = SENDMSG_REPLACE;
-                        delay = 0;
-                    }
-                }
-            }
-            if (updateAudioMode) {
-                sendMsg(mAudioHandler,
-                        MSG_UPDATE_AUDIO_MODE,
-                        existingMsgPolicy,
-                        AudioSystem.MODE_CURRENT,
-                        android.os.Process.myPid(),
-                        mContext.getPackageName(),
-                        delay);
-            }
-        }
+        updateAudioModeHandlers(
+                null /* playbackConfigs */, configs /* recordConfigs */);
+        mDeviceBroker.updateCommunicationRouteClientsActivity(
+                null /* playbackConfigs */, configs /* recordConfigs */);
     }
 
     private void dumpAudioMode(PrintWriter pw) {
@@ -6299,10 +6302,12 @@
                             ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
                     .record();
         }
-
+        final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
         final long ident = Binder.clearCallingIdentity();
         try {
-            return mDeviceBroker.setCommunicationDevice(cb, pid, device, eventSource);
+            return mDeviceBroker.setCommunicationDevice(cb, uid, device, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -6348,6 +6353,9 @@
         if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) {
             return;
         }
+        final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
 
         // for logging only
         final int uid = Binder.getCallingUid();
@@ -6363,9 +6371,10 @@
                 .set(MediaMetrics.Property.STATE, on
                         ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
                 .record();
+
         final long ident = Binder.clearCallingIdentity();
         try {
-            mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource);
+            mDeviceBroker.setSpeakerphoneOn(cb, uid, on, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -6490,7 +6499,7 @@
                 .set(MediaMetrics.Property.SCO_AUDIO_MODE,
                         BtHelper.scoAudioModeToString(scoAudioMode))
                 .record();
-        startBluetoothScoInt(cb, pid, scoAudioMode, eventSource);
+        startBluetoothScoInt(cb, uid, scoAudioMode, eventSource);
 
     }
 
@@ -6513,10 +6522,10 @@
                 .set(MediaMetrics.Property.SCO_AUDIO_MODE,
                         BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_VIRTUAL_CALL))
                 .record();
-        startBluetoothScoInt(cb, pid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
+        startBluetoothScoInt(cb, uid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
     }
 
-    void startBluetoothScoInt(IBinder cb, int pid, int scoAudioMode, @NonNull String eventSource) {
+    void startBluetoothScoInt(IBinder cb, int uid, int scoAudioMode, @NonNull String eventSource) {
         MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
                 .set(MediaMetrics.Property.EVENT, "startBluetoothScoInt")
                 .set(MediaMetrics.Property.SCO_AUDIO_MODE,
@@ -6527,9 +6536,13 @@
             mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission or systemReady").record();
             return;
         }
+        final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
         final long ident = Binder.clearCallingIdentity();
         try {
-            mDeviceBroker.startBluetoothScoForClient(cb, pid, scoAudioMode, eventSource);
+            mDeviceBroker.startBluetoothScoForClient(
+                    cb, uid, scoAudioMode, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -6547,9 +6560,12 @@
         final String eventSource =  new StringBuilder("stopBluetoothSco()")
                 .append(") from u/pid:").append(uid).append("/")
                 .append(pid).toString();
+        final boolean isPrivileged = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
         final long ident = Binder.clearCallingIdentity();
         try {
-            mDeviceBroker.stopBluetoothScoForClient(cb, pid, eventSource);
+            mDeviceBroker.stopBluetoothScoForClient(cb, uid, isPrivileged, eventSource);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -6567,6 +6583,10 @@
         return mContentResolver;
     }
 
+    /*package*/ SettingsAdapter getSettings() {
+        return mSettings;
+    }
+
     ///////////////////////////////////////////////////////////////////////////
     // Internal methods
     ///////////////////////////////////////////////////////////////////////////
@@ -7322,7 +7342,7 @@
         if (pkgName == null) {
             pkgName = "";
         }
-        if (device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) {
+        if (device.getType() == TYPE_BLUETOOTH_A2DP) {
             avrcpSupportsAbsoluteVolume(device.getAddress(),
                     deviceVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
             return;
@@ -9200,6 +9220,11 @@
                     mAudioEventWakeLock.release();
                     break;
 
+                case MSG_INIT_ADI_DEVICE_STATES:
+                    onInitAdiDeviceStates();
+                    mAudioEventWakeLock.release();
+                    break;
+
                 case MSG_INIT_SPATIALIZER:
                     onInitSpatializer();
                     mAudioEventWakeLock.release();
@@ -9209,10 +9234,6 @@
                     mSpatializerHelper.onInitSensors();
                     break;
 
-                case MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS:
-                    onPersistSpatialAudioDeviceSettings();
-                    break;
-
                 case MSG_RESET_SPATIALIZER:
                     mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect);
                     break;
@@ -9284,8 +9305,8 @@
                             break;
                         }
                         boolean wasActive = h.isActive();
-                        h.setPlaybackActive(mPlaybackMonitor.isPlaybackActiveForUid(h.getUid()));
-                        h.setRecordingActive(mRecordMonitor.isRecordingActiveForUid(h.getUid()));
+                        h.setPlaybackActive(isPlaybackActiveForUid(h.getUid()));
+                        h.setRecordingActive(isRecordingActiveForUid(h.getUid()));
                         if (wasActive != h.isActive()) {
                             onUpdateAudioMode(AudioSystem.MODE_CURRENT, android.os.Process.myPid(),
                                     mContext.getPackageName(), false /*force*/);
@@ -10268,42 +10289,17 @@
                 /*arg1*/ 0, /*arg2*/ 0, TAG, /*delay*/ 0);
     }
 
+    void onInitAdiDeviceStates() {
+        mDeviceBroker.onReadAudioDeviceSettings();
+        mSoundDoseHelper.initCachedAudioDeviceCategories(
+                mDeviceBroker.getImmutableDeviceInventory());
+    }
+
     void onInitSpatializer() {
-        final String settings = mSettings.getSecureStringForUser(mContentResolver,
-                Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT);
-        if (settings == null) {
-            Log.e(TAG, "error reading spatial audio device settings");
-        }
-        mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect, settings);
+        mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
         mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
     }
 
-    /**
-     * post a message to persist the spatial audio device settings.
-     * Message is delayed by 1s on purpose in case of successive changes in quick succession (at
-     * init time for instance)
-     * Note this method is made public to work around a Mockito bug where it needs to be public
-     * in order to be mocked by a test a the same package
-     * (see https://code.google.com/archive/p/mockito/issues/127)
-     */
-    public void persistSpatialAudioDeviceSettings() {
-        sendMsg(mAudioHandler,
-                MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS,
-                SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0, TAG,
-                /*delay*/ 1000);
-    }
-
-    void onPersistSpatialAudioDeviceSettings() {
-        final String settings = mSpatializerHelper.getSADeviceSettings();
-        Log.v(TAG, "saving spatial audio device settings: " + settings);
-        boolean res = mSettings.putSecureStringForUser(mContentResolver,
-                Settings.Secure.SPATIAL_AUDIO_ENABLED,
-                settings, UserHandle.USER_CURRENT);
-        if (!res) {
-            Log.e(TAG, "error saving spatial audio device settings: " + settings);
-        }
-    }
-
     //==========================================================================================
 
     // camera sound is forced if any of the resources corresponding to one active SIM
@@ -10693,6 +10689,51 @@
         return mSoundDoseHelper.isCsdEnabled();
     }
 
+    @Override
+    @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    public void setBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle,
+            @AudioDeviceCategory int btAudioDeviceCategory) {
+        super.setBluetoothAudioDeviceCategory_enforcePermission();
+
+        final String addr = Objects.requireNonNull(address);
+
+        AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress(addr, isBle);
+
+        int internalType = !isBle ? DEVICE_OUT_BLUETOOTH_A2DP
+                : ((btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES)
+                        ? DEVICE_OUT_BLE_HEADSET : DEVICE_OUT_BLE_SPEAKER);
+        int deviceType = !isBle ? TYPE_BLUETOOTH_A2DP
+                : ((btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES) ? TYPE_BLE_HEADSET
+                        : TYPE_BLE_SPEAKER);
+
+        if (deviceState == null) {
+            deviceState = new AdiDeviceState(deviceType, internalType, addr);
+        }
+
+        deviceState.setAudioDeviceCategory(btAudioDeviceCategory);
+
+        mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory(deviceState);
+        mDeviceBroker.persistAudioDeviceSettings();
+
+        mSoundDoseHelper.setAudioDeviceCategory(addr, internalType,
+                btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES);
+    }
+
+    @Override
+    @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    @AudioDeviceCategory
+    public int getBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle) {
+        super.getBluetoothAudioDeviceCategory_enforcePermission();
+
+        final AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress(
+                Objects.requireNonNull(address), isBle);
+        if (deviceState == null) {
+            return AUDIO_DEVICE_CATEGORY_UNKNOWN;
+        }
+
+        return deviceState.getAudioDeviceCategory();
+    }
+
     //==========================================================================================
     // Hdmi CEC:
     // - System audio mode:
@@ -12378,6 +12419,16 @@
         }
     }
 
+    /* package */
+    boolean isPlaybackActiveForUid(int uid) {
+        return mPlaybackMonitor.isPlaybackActiveForUid(uid);
+    }
+
+    /* package */
+    boolean isRecordingActiveForUid(int uid) {
+        return mRecordMonitor.isRecordingActiveForUid(uid);
+    }
+
     //======================
     // Audio device management
     //======================
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 50ffbcb9..23a0782 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -957,6 +957,7 @@
             players = (HashMap<Integer, AudioPlaybackConfiguration>) mPlayers.clone();
         }
         mFadingManager.unfadeOutUid(uid, players);
+        mDuckingManager.unduckUid(uid, players);
     }
 
     //=================================================================
@@ -988,21 +989,16 @@
     }
 
     List<AudioPlaybackConfiguration> getActivePlaybackConfigurations(boolean isPrivileged) {
-        synchronized(mPlayers) {
+        synchronized (mPlayerLock) {
             if (isPrivileged) {
                 return new ArrayList<AudioPlaybackConfiguration>(mPlayers.values());
             } else {
-                final List<AudioPlaybackConfiguration> configsPublic;
-                synchronized (mPlayerLock) {
-                    configsPublic = anonymizeForPublicConsumption(
+                return anonymizeForPublicConsumption(
                             new ArrayList<AudioPlaybackConfiguration>(mPlayers.values()));
-                }
-                return configsPublic;
             }
         }
     }
 
-
     /**
      * Inner class to track clients that want to be notified of playback updates
      */
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 652ea52..4332fdd 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -227,8 +227,8 @@
         synchronized (mRecordStates) {
             for (RecordingState state : mRecordStates) {
                 // Note: isActiveConfiguration() == true => state.getConfig() != null
-                if (state.isActiveConfiguration()
-                        && state.getConfig().getClientUid() == uid) {
+                if (state.isActiveConfiguration() && state.getConfig().getClientUid() == uid
+                        && !state.getConfig().isClientSilenced()) {
                     return true;
                 }
             }
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 01af3a8..851c5c3 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -16,6 +16,9 @@
 
 package com.android.server.audio;
 
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
+
 import static com.android.server.audio.AudioService.MAX_STREAM_VOLUME;
 import static com.android.server.audio.AudioService.MIN_STREAM_VOLUME;
 import static com.android.server.audio.AudioService.MSG_SET_DEVICE_VOLUME;
@@ -57,6 +60,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -189,6 +193,9 @@
 
     private final AtomicBoolean mEnableCsd = new AtomicBoolean(false);
 
+    private ArrayList<ISoundDose.AudioDeviceCategory> mCachedAudioDeviceCategories =
+            new ArrayList<>();
+
     private final Object mCsdStateLock = new Object();
 
     private final AtomicReference<ISoundDose> mSoundDose = new AtomicReference<>();
@@ -487,6 +494,43 @@
         return false;
     }
 
+    void setAudioDeviceCategory(String address, int internalAudioType, boolean isHeadphone) {
+        if (!mEnableCsd.get()) {
+            return;
+        }
+
+        final ISoundDose soundDose = mSoundDose.get();
+        if (soundDose == null) {
+            Log.w(TAG, "Sound dose interface not initialized");
+            return;
+        }
+
+        try {
+            final ISoundDose.AudioDeviceCategory audioDeviceCategory =
+                    new ISoundDose.AudioDeviceCategory();
+            audioDeviceCategory.address = address;
+            audioDeviceCategory.internalAudioType = internalAudioType;
+            audioDeviceCategory.csdCompatible = isHeadphone;
+            soundDose.setAudioDeviceCategory(audioDeviceCategory);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception while forcing the internal MEL computation", e);
+        }
+    }
+
+    void initCachedAudioDeviceCategories(Collection<AdiDeviceState> deviceStates) {
+        for (final AdiDeviceState state : deviceStates) {
+            if (state.getAudioDeviceCategory() != AUDIO_DEVICE_CATEGORY_UNKNOWN) {
+                final ISoundDose.AudioDeviceCategory audioDeviceCategory =
+                        new ISoundDose.AudioDeviceCategory();
+                audioDeviceCategory.address = state.getDeviceAddress();
+                audioDeviceCategory.internalAudioType = state.getInternalDeviceType();
+                audioDeviceCategory.csdCompatible =
+                        state.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES;
+                mCachedAudioDeviceCategories.add(audioDeviceCategory);
+            }
+        }
+    }
+
     /*package*/ int safeMediaVolumeIndex(int device) {
         final int vol = mSafeMediaVolumeDevices.get(device);
         if (vol == SAFE_MEDIA_VOLUME_UNINITIALIZED) {
@@ -810,6 +854,16 @@
 
         Log.v(TAG, "Initializing sound dose");
 
+        try {
+            if (mCachedAudioDeviceCategories.size() > 0) {
+                soundDose.initCachedAudioDeviceCategories(mCachedAudioDeviceCategories.toArray(
+                        new ISoundDose.AudioDeviceCategory[0]));
+                mCachedAudioDeviceCategories.clear();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception while forcing the internal MEL computation", e);
+        }
+
         synchronized (mCsdStateLock) {
             if (mGlobalTimeOffsetInSecs == GLOBAL_TIME_OFFSET_UNINITIALIZED) {
                 mGlobalTimeOffsetInSecs = System.currentTimeMillis() / 1000L;
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 462c938..496bdf4 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.server.audio;
 
+import static android.media.AudioSystem.isBluetoothDevice;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -47,13 +49,13 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.utils.EventLogger;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
-import java.util.Objects;
 import java.util.UUID;
 
 /**
@@ -73,11 +75,12 @@
 
     private final @NonNull AudioSystemAdapter mASA;
     private final @NonNull AudioService mAudioService;
+    private final @NonNull AudioDeviceBroker mDeviceBroker;
     private @Nullable SensorManager mSensorManager;
 
     //------------------------------------------------------------
 
-    private static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
+    /*package*/ static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
         {
             append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
             append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
@@ -98,13 +101,6 @@
         }
     };
 
-    private static final int[] WIRELESS_TYPES = { AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
-            AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
-            AudioDeviceInfo.TYPE_BLE_HEADSET,
-            AudioDeviceInfo.TYPE_BLE_SPEAKER,
-            AudioDeviceInfo.TYPE_BLE_BROADCAST
-    };
-
     // Spatializer state machine
     /*package*/ static final int STATE_UNINITIALIZED = 0;
     /*package*/ static final int STATE_NOT_SUPPORTED = 1;
@@ -114,10 +110,15 @@
     /*package*/ static final int STATE_DISABLED_AVAILABLE = 6;
     private int mState = STATE_UNINITIALIZED;
 
+    @VisibleForTesting boolean mBinauralEnabledDefault;
+    @VisibleForTesting boolean mTransauralEnabledDefault;
+    @VisibleForTesting boolean mHeadTrackingEnabledDefault;
+
     private boolean mFeatureEnabled = false;
     /** current level as reported by native Spatializer in callback */
     private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
     private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+
     private boolean mTransauralSupported = false;
     private boolean mBinauralSupported = false;
     private boolean mIsHeadTrackingSupported = false;
@@ -160,31 +161,21 @@
      */
     private final ArrayList<Integer> mSACapableDeviceTypes = new ArrayList<>(0);
 
-    /**
-     * List of devices where Spatial Audio is possible. Each device can be enabled or disabled
-     * (== user choice to use or not)
-     */
-    @GuardedBy("this")
-    private final ArrayList<SADeviceState> mSADevices = new ArrayList<>(0);
-
     //------------------------------------------------------
     // initialization
-    @SuppressWarnings("StaticAssignmentInConstructor")
     SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa,
-            boolean binauralEnabledDefault,
-            boolean transauralEnabledDefault,
-            boolean headTrackingEnabledDefault) {
+            @NonNull AudioDeviceBroker deviceBroker, boolean binauralEnabledDefault,
+            boolean transauralEnabledDefault, boolean headTrackingEnabledDefault) {
         mAudioService = mother;
         mASA = asa;
-        // "StaticAssignmentInConstructor" warning is suppressed as the SpatializerHelper being
-        // constructed here is the factory for SADeviceState, thus SADeviceState and its
-        // private static field sHeadTrackingEnabledDefault should never be accessed directly.
-        SADeviceState.sBinauralEnabledDefault = binauralEnabledDefault;
-        SADeviceState.sTransauralEnabledDefault = transauralEnabledDefault;
-        SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault;
+        mDeviceBroker = deviceBroker;
+
+        mBinauralEnabledDefault = binauralEnabledDefault;
+        mTransauralEnabledDefault = transauralEnabledDefault;
+        mHeadTrackingEnabledDefault = headTrackingEnabledDefault;
     }
 
-    synchronized void init(boolean effectExpected, @Nullable String settings) {
+    synchronized void init(boolean effectExpected) {
         loglogi("init effectExpected=" + effectExpected);
         if (!effectExpected) {
             loglogi("init(): setting state to STATE_NOT_SUPPORTED due to effect not expected");
@@ -288,10 +279,11 @@
                 }
             }
 
-            // When initialized from AudioService, the settings string will be non-null.
-            // Saved settings need to be applied after spatialization support is initialized above.
-            if (settings != null) {
-                setSADeviceSettings(settings);
+            // Log the saved device states that are compatible with SA
+            for (AdiDeviceState deviceState : mDeviceBroker.getImmutableDeviceInventory()) {
+                if (isSADevice(deviceState)) {
+                    logDeviceState(deviceState, "setSADeviceSettings");
+                }
             }
 
             // for both transaural / binaural, we are not forcing enablement as the init() method
@@ -331,7 +323,7 @@
         mState = STATE_UNINITIALIZED;
         mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
         mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
-        init(true, null /* settings */);
+        init(/*effectExpected=*/true);
         setSpatializerEnabledInt(featureEnabled);
     }
 
@@ -372,7 +364,7 @@
         final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
 
         // is media routed to a new device?
-        if (isWireless(currentDevice.getType())) {
+        if (isBluetoothDevice(currentDevice.getInternalType())) {
             addWirelessDeviceIfNew(currentDevice);
         }
 
@@ -520,8 +512,8 @@
     synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() {
         // build unionOf(mCompatibleAudioDevices, mEnabledDevice) - mDisabledAudioDevices
         ArrayList<AudioDeviceAttributes> compatList = new ArrayList<>();
-        for (SADeviceState deviceState : mSADevices) {
-            if (deviceState.mEnabled) {
+        for (AdiDeviceState deviceState : mDeviceBroker.getImmutableDeviceInventory()) {
+            if (deviceState.isSAEnabled() && isSADevice(deviceState)) {
                 compatList.add(deviceState.getAudioDeviceAttributes());
             }
         }
@@ -548,31 +540,50 @@
             return;
         }
         loglogi("addCompatibleAudioDevice: dev=" + ada);
-        final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
-        SADeviceState deviceUpdated = null; // non-null on update.
+        final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+        initSAState(deviceState);
+        AdiDeviceState updatedDevice = null; // non-null on update.
         if (deviceState != null) {
-            if (forceEnable && !deviceState.mEnabled) {
-                deviceUpdated = deviceState;
-                deviceUpdated.mEnabled = true;
+            if (forceEnable && !deviceState.isSAEnabled()) {
+                updatedDevice = deviceState;
+                updatedDevice.setSAEnabled(true);
             }
         } else {
             // When adding, force the device type to be a canonical one.
-            final int canonicalDeviceType = getCanonicalDeviceType(ada.getType());
+            final int canonicalDeviceType = getCanonicalDeviceType(ada.getType(),
+                    ada.getInternalType());
             if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) {
                 Log.e(TAG, "addCompatibleAudioDevice with incompatible AudioDeviceAttributes "
                         + ada);
                 return;
             }
-            deviceUpdated = new SADeviceState(canonicalDeviceType, ada.getAddress());
-            mSADevices.add(deviceUpdated);
+            updatedDevice = new AdiDeviceState(canonicalDeviceType, ada.getInternalType(),
+                    ada.getAddress());
+            initSAState(updatedDevice);
+            mDeviceBroker.addOrUpdateDeviceSAStateInInventory(updatedDevice);
         }
-        if (deviceUpdated != null) {
+        if (updatedDevice != null) {
             onRoutingUpdated();
-            mAudioService.persistSpatialAudioDeviceSettings();
-            logDeviceState(deviceUpdated, "addCompatibleAudioDevice");
+            mDeviceBroker.persistAudioDeviceSettings();
+            logDeviceState(updatedDevice, "addCompatibleAudioDevice");
         }
     }
 
+    private void initSAState(AdiDeviceState device) {
+        if (device == null) {
+            return;
+        }
+
+        int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(device.getDeviceType(),
+                Integer.MIN_VALUE);
+        device.setSAEnabled(spatMode == SpatializationMode.SPATIALIZER_BINAURAL
+                ? mBinauralEnabledDefault
+                : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
+                        ? mTransauralEnabledDefault
+                        : false);
+        device.setHeadTrackerEnabled(mHeadTrackingEnabledDefault);
+    }
+
     private static final String METRICS_DEVICE_PREFIX = "audio.spatializer.device.";
 
     // Device logging is accomplished in the Java Audio Service level.
@@ -580,29 +591,30 @@
     //
     // There may be different devices with the same device type (aliasing).
     // We always send the full device state info on each change.
-    private void logDeviceState(SADeviceState deviceState, String event) {
+    static void logDeviceState(AdiDeviceState deviceState, String event) {
         final int deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
-                deviceState.mDeviceType);
+                deviceState.getDeviceType());
         final String deviceName = AudioSystem.getDeviceName(deviceType);
         new MediaMetrics.Item(METRICS_DEVICE_PREFIX + deviceName)
-            .set(MediaMetrics.Property.ADDRESS, deviceState.mDeviceAddress)
-            .set(MediaMetrics.Property.ENABLED, deviceState.mEnabled ? "true" : "false")
-            .set(MediaMetrics.Property.EVENT, TextUtils.emptyIfNull(event))
-            .set(MediaMetrics.Property.HAS_HEAD_TRACKER,
-                    deviceState.mHasHeadTracker ? "true" : "false") // this may be updated later.
-            .set(MediaMetrics.Property.HEAD_TRACKER_ENABLED,
-                    deviceState.mHeadTrackerEnabled ? "true" : "false")
-            .record();
+                .set(MediaMetrics.Property.ADDRESS, deviceState.getDeviceAddress())
+                .set(MediaMetrics.Property.ENABLED, deviceState.isSAEnabled() ? "true" : "false")
+                .set(MediaMetrics.Property.EVENT, TextUtils.emptyIfNull(event))
+                .set(MediaMetrics.Property.HAS_HEAD_TRACKER,
+                        deviceState.hasHeadTracker() ? "true"
+                                : "false") // this may be updated later.
+                .set(MediaMetrics.Property.HEAD_TRACKER_ENABLED,
+                        deviceState.isHeadTrackerEnabled() ? "true" : "false")
+                .record();
     }
 
     synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
         loglogi("removeCompatibleAudioDevice: dev=" + ada);
 
-        final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
-        if (deviceState != null && deviceState.mEnabled) {
-            deviceState.mEnabled = false;
+        final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+        if (deviceState != null && deviceState.isSAEnabled()) {
+            deviceState.setSAEnabled(false);
             onRoutingUpdated();
-            mAudioService.persistSpatialAudioDeviceSettings();
+            mDeviceBroker.persistAudioDeviceSettings();
             logDeviceState(deviceState, "removeCompatibleAudioDevice");
         }
     }
@@ -611,8 +623,9 @@
      * Returns a possibly aliased device type which is used
      * for spatial audio settings (or TYPE_UNKNOWN  if it doesn't exist).
      */
-    private static @AudioDeviceInfo.AudioDeviceType int getCanonicalDeviceType(int deviceType) {
-        if (isWireless(deviceType)) return deviceType;
+    @AudioDeviceInfo.AudioDeviceType
+    private static int getCanonicalDeviceType(int deviceType, int internalDeviceType) {
+        if (isBluetoothDevice(internalDeviceType)) return deviceType;
 
         final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
         if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) {
@@ -629,18 +642,9 @@
      */
     @GuardedBy("this")
     @Nullable
-    private SADeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) {
-        final int deviceType = ada.getType();
-        final boolean isWireless = isWireless(deviceType);
-        final int canonicalDeviceType = getCanonicalDeviceType(deviceType);
-
-        for (SADeviceState deviceState : mSADevices) {
-            if (deviceState.mDeviceType == canonicalDeviceType
-                    && (!isWireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
-                return deviceState;
-            }
-        }
-        return null;
+    private AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) {
+        return mDeviceBroker.findDeviceStateForAudioDeviceAttributes(ada,
+                getCanonicalDeviceType(ada.getType(), ada.getInternalType()));
     }
 
     /**
@@ -662,14 +666,14 @@
             Log.e(TAG, "no spatialization mode found for device type:" + deviceType);
             return new Pair<>(false, false);
         }
-        final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+        final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
         if (deviceState == null) {
             // no matching device state?
             Log.i(TAG, "no spatialization device state found for Spatial Audio device:" + ada);
             return new Pair<>(false, false);
         }
         // found the matching device state.
-        return new Pair<>(deviceState.mEnabled, true /* available */);
+        return new Pair<>(deviceState.isSAEnabled(), true /* available */);
     }
 
     private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) {
@@ -678,16 +682,19 @@
         }
         if (findDeviceStateForAudioDeviceAttributes(ada) == null) {
             // wireless device types should be canonical, but we translate to be sure.
-            final int canonicalDeviceType = getCanonicalDeviceType((ada.getType()));
+            final int canonicalDeviceType = getCanonicalDeviceType(ada.getType(),
+                    ada.getInternalType());
             if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) {
                 Log.e(TAG, "addWirelessDeviceIfNew with incompatible AudioDeviceAttributes "
                         + ada);
                 return;
             }
-            final SADeviceState deviceState =
-                    new SADeviceState(canonicalDeviceType, ada.getAddress());
-            mSADevices.add(deviceState);
-            mAudioService.persistSpatialAudioDeviceSettings();
+            final AdiDeviceState deviceState =
+                    new AdiDeviceState(canonicalDeviceType, ada.getInternalType(),
+                            ada.getAddress());
+            initSAState(deviceState);
+            mDeviceBroker.addOrUpdateDeviceSAStateInInventory(deviceState);
+            mDeviceBroker.persistAudioDeviceSettings();
             logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later.
         }
     }
@@ -756,6 +763,12 @@
         return false;
     }
 
+    private boolean isSADevice(AdiDeviceState deviceState) {
+        return deviceState.getDeviceType() == getCanonicalDeviceType(deviceState.getDeviceType(),
+                deviceState.getInternalDeviceType()) && isDeviceCompatibleWithSpatializationModes(
+                deviceState.getAudioDeviceAttributes());
+    }
+
     synchronized void setFeatureEnabled(boolean enabled) {
         loglogi("setFeatureEnabled(" + enabled + ") was featureEnabled:" + mFeatureEnabled);
         if (mFeatureEnabled == enabled) {
@@ -768,7 +781,7 @@
                 return;
             }
             if (mState == STATE_UNINITIALIZED) {
-                init(true, null /* settings */);
+                init(true);
             }
             setSpatializerEnabledInt(true);
         } else {
@@ -1137,16 +1150,16 @@
             Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled
                     + " for " + ada);
         }
-        final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+        final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
         if (deviceState == null) return;
-        if (!deviceState.mHasHeadTracker) {
+        if (!deviceState.hasHeadTracker()) {
             Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled
                     + " device:" + ada + " on a device without headtracker");
             return;
         }
         Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada);
-        deviceState.mHeadTrackerEnabled = enabled;
-        mAudioService.persistSpatialAudioDeviceSettings();
+        deviceState.setHeadTrackerEnabled(enabled);
+        mDeviceBroker.persistAudioDeviceSettings();
         logDeviceState(deviceState, "setHeadTrackerEnabled");
 
         // check current routing to see if it affects the headtracking mode
@@ -1170,8 +1183,8 @@
             Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada);
             return false;
         }
-        final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
-        return deviceState != null && deviceState.mHasHeadTracker;
+        final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+        return deviceState != null && deviceState.hasHeadTracker();
     }
 
     /**
@@ -1184,14 +1197,14 @@
             Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada);
             return false;
         }
-        final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+        final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
         if (deviceState != null) {
-            if (!deviceState.mHasHeadTracker) {
-                deviceState.mHasHeadTracker = true;
-                mAudioService.persistSpatialAudioDeviceSettings();
+            if (!deviceState.hasHeadTracker()) {
+                deviceState.setHasHeadTracker(true);
+                mDeviceBroker.persistAudioDeviceSettings();
                 logDeviceState(deviceState, "setHasHeadTracker");
             }
-            return deviceState.mHeadTrackerEnabled;
+            return deviceState.isHeadTrackerEnabled();
         }
         Log.e(TAG, "setHasHeadTracker: device not found for:" + ada);
         return false;
@@ -1202,9 +1215,9 @@
             Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada);
             return false;
         }
-        final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+        final AdiDeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
         return deviceState != null
-                && deviceState.mHasHeadTracker && deviceState.mHeadTrackerEnabled;
+                && deviceState.hasHeadTracker() && deviceState.isHeadTrackerEnabled();
     }
 
     synchronized boolean isHeadTrackerAvailable() {
@@ -1543,144 +1556,6 @@
         pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural:"
                 + mTransauralSupported);
         pw.println("\tmSpatOutput:" + mSpatOutput);
-        pw.println("\tdevices:");
-        for (SADeviceState device : mSADevices) {
-            pw.println("\t\t" + device);
-        }
-    }
-
-    /*package*/ static final class SADeviceState {
-        private static boolean sBinauralEnabledDefault = true;
-        private static boolean sTransauralEnabledDefault = true;
-        private static boolean sHeadTrackingEnabledDefault = false;
-        final @AudioDeviceInfo.AudioDeviceType int mDeviceType;
-        final @NonNull String mDeviceAddress;
-        boolean mEnabled;
-        boolean mHasHeadTracker = false;
-        boolean mHeadTrackerEnabled;
-        static final String SETTING_FIELD_SEPARATOR = ",";
-        static final String SETTING_DEVICE_SEPARATOR_CHAR = "|";
-        static final String SETTING_DEVICE_SEPARATOR = "\\|";
-
-        /**
-         * Constructor
-         * @param deviceType
-         * @param address must be non-null for wireless devices
-         * @throws NullPointerException if a null address is passed for a wireless device
-         */
-        SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address) {
-            mDeviceType = deviceType;
-            mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : "";
-            final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
-            mEnabled = spatMode == SpatializationMode.SPATIALIZER_BINAURAL
-                    ? sBinauralEnabledDefault
-                    : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
-                            ? sTransauralEnabledDefault
-                            : false;
-            mHeadTrackerEnabled = sHeadTrackingEnabledDefault;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if (obj == null) {
-                return false;
-            }
-            // type check and cast
-            if (getClass() != obj.getClass()) {
-                return false;
-            }
-            final SADeviceState sads = (SADeviceState) obj;
-            return mDeviceType == sads.mDeviceType
-                    && mDeviceAddress.equals(sads.mDeviceAddress)
-                    && mEnabled == sads.mEnabled
-                    && mHasHeadTracker == sads.mHasHeadTracker
-                    && mHeadTrackerEnabled == sads.mHeadTrackerEnabled;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mDeviceType, mDeviceAddress, mEnabled, mHasHeadTracker,
-                    mHeadTrackerEnabled);
-        }
-
-        @Override
-        public String toString() {
-            return "type: " + mDeviceType + " addr: " + mDeviceAddress + " enabled: " + mEnabled
-                    + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled;
-        }
-
-        String toPersistableString() {
-            return (new StringBuilder().append(mDeviceType)
-                    .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress)
-                    .append(SETTING_FIELD_SEPARATOR).append(mEnabled ? "1" : "0")
-                    .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0")
-                    .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0")
-                    .toString());
-        }
-
-        static @Nullable SADeviceState fromPersistedString(@Nullable String persistedString) {
-            if (persistedString == null) {
-                return null;
-            }
-            if (persistedString.isEmpty()) {
-                return null;
-            }
-            String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR);
-            if (fields.length != 5) {
-                // expecting all fields, fewer may mean corruption, ignore those settings
-                return null;
-            }
-            try {
-                final int deviceType = Integer.parseInt(fields[0]);
-                final SADeviceState deviceState = new SADeviceState(deviceType, fields[1]);
-                deviceState.mEnabled = Integer.parseInt(fields[2]) == 1;
-                deviceState.mHasHeadTracker = Integer.parseInt(fields[3]) == 1;
-                deviceState.mHeadTrackerEnabled = Integer.parseInt(fields[4]) == 1;
-                return deviceState;
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "unable to parse setting for SADeviceState: " + persistedString, e);
-                return null;
-            }
-        }
-
-        public AudioDeviceAttributes getAudioDeviceAttributes() {
-            return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
-                    mDeviceType, mDeviceAddress == null ? "" : mDeviceAddress);
-        }
-
-    }
-
-    /*package*/ synchronized String getSADeviceSettings() {
-        // expected max size of each String for each SADeviceState is 25 (accounting for separator)
-        final StringBuilder settingsBuilder = new StringBuilder(mSADevices.size() * 25);
-        for (int i = 0; i < mSADevices.size(); i++) {
-            settingsBuilder.append(mSADevices.get(i).toPersistableString());
-            if (i != mSADevices.size() - 1) {
-                settingsBuilder.append(SADeviceState.SETTING_DEVICE_SEPARATOR_CHAR);
-            }
-        }
-        return settingsBuilder.toString();
-    }
-
-    /*package*/ synchronized void setSADeviceSettings(@NonNull String persistedSettings) {
-        String[] devSettings = TextUtils.split(Objects.requireNonNull(persistedSettings),
-                SADeviceState.SETTING_DEVICE_SEPARATOR);
-        // small list, not worth overhead of Arrays.stream(devSettings)
-        for (String setting : devSettings) {
-            SADeviceState devState = SADeviceState.fromPersistedString(setting);
-            // Note if the device is not compatible with spatialization mode
-            // or the device type is not canonical, it is ignored.
-            if (devState != null
-                    && devState.mDeviceType == getCanonicalDeviceType(devState.mDeviceType)
-                    && isDeviceCompatibleWithSpatializationModes(
-                            devState.getAudioDeviceAttributes())) {
-                mSADevices.add(devState);
-                logDeviceState(devState, "setSADeviceSettings");
-            }
-        }
     }
 
     private static String spatStateString(int state) {
@@ -1702,15 +1577,6 @@
         }
     }
 
-    private static boolean isWireless(@AudioDeviceInfo.AudioDeviceType int deviceType) {
-        for (int type : WIRELESS_TYPES) {
-            if (type == deviceType) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private int getHeadSensorHandleUpdateTracker() {
         int headHandle = -1;
         if (sRoutingDevices.isEmpty()) {
@@ -1780,11 +1646,6 @@
 
     //------------------------------------------------
     // for testing purposes only
-
-    /*package*/ void clearSADevices() {
-        mSADevices.clear();
-    }
-
     /*package*/ synchronized void forceStateForTest(int state) {
         mState = state;
     }
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index cb5e7f1..2ae3118 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -150,6 +150,10 @@
     // Timestamp when hardware authentication occurred
     private long mAuthenticatedTimeMs;
 
+    @NonNull
+    private final OperationContextExt mOperationContext;
+
+
     AuthSession(@NonNull Context context,
             @NonNull BiometricContext biometricContext,
             @NonNull IStatusBarService statusBarService,
@@ -215,6 +219,7 @@
         mFingerprintSensorProperties = fingerprintSensorProperties;
         mCancelled = false;
         mBiometricFrameworkStatsLogger = logger;
+        mOperationContext = new OperationContextExt(true /* isBP */);
 
         try {
             mClientReceiver.asBinder().linkToDeath(this, 0 /* flags */);
@@ -581,6 +586,8 @@
         } else {
             Slog.d(TAG, "delaying fingerprint sensor start");
         }
+
+        mBiometricContext.updateContext(mOperationContext, isCrypto());
     }
 
     // call once anytime after onDialogAnimatedIn() to indicate it's appropriate to start the
@@ -743,12 +750,12 @@
                         + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
                         + ", RequireConfirmation: " + mPreAuthInfo.confirmationRequested
                         + ", State: " + FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED
-                        + ", Latency: " + latency);
+                        + ", Latency: " + latency
+                        + ", SessionId: " + mOperationContext.getId());
             }
 
             mBiometricFrameworkStatsLogger.authenticate(
-                    mBiometricContext.updateContext(new OperationContextExt(true /* isBP */),
-                            isCrypto()),
+                    mOperationContext,
                     statsModality(),
                     BiometricsProtoEnums.ACTION_UNKNOWN,
                     BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
@@ -780,13 +787,13 @@
                         + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
                         + ", Reason: " + reason
                         + ", Error: " + error
-                        + ", Latency: " + latency);
+                        + ", Latency: " + latency
+                        + ", SessionId: " + mOperationContext.getId());
             }
             // Auth canceled
             if (error != 0) {
                 mBiometricFrameworkStatsLogger.error(
-                        mBiometricContext.updateContext(new OperationContextExt(true /* isBP */),
-                                isCrypto()),
+                        mOperationContext,
                         statsModality(),
                         BiometricsProtoEnums.ACTION_AUTHENTICATE,
                         BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStats.java b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
new file mode 100644
index 0000000..e109cc8
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+/**
+ * Utility class for on-device biometric authentication data, including total authentication,
+ * rejections, and the number of sent enrollment notifications.
+ */
+public class AuthenticationStats {
+
+    private static final float FRR_NOT_ENOUGH_ATTEMPTS = -1.0f;
+
+    private final int mUserId;
+    private int mTotalAttempts;
+    private int mRejectedAttempts;
+    private int mEnrollmentNotifications;
+    private final int mModality;
+
+    public AuthenticationStats(final int userId, int totalAttempts, int rejectedAttempts,
+            int enrollmentNotifications, final int modality) {
+        mUserId = userId;
+        mTotalAttempts = totalAttempts;
+        mRejectedAttempts = rejectedAttempts;
+        mEnrollmentNotifications = enrollmentNotifications;
+        mModality = modality;
+    }
+
+    public AuthenticationStats(final int userId, final int modality) {
+        mUserId = userId;
+        mTotalAttempts = 0;
+        mRejectedAttempts = 0;
+        mEnrollmentNotifications = 0;
+        mModality = modality;
+    }
+
+    public int getUserId() {
+        return mUserId;
+    }
+
+    public int getTotalAttempts() {
+        return mTotalAttempts;
+    }
+
+    public int getRejectedAttempts() {
+        return mRejectedAttempts;
+    }
+
+    public int getEnrollmentNotifications() {
+        return mEnrollmentNotifications;
+    }
+
+    public int getModality() {
+        return mModality;
+    }
+
+    /** Calculate FRR. */
+    public float getFrr() {
+        if (mTotalAttempts > 0) {
+            return mRejectedAttempts / (float) mTotalAttempts;
+        } else {
+            return FRR_NOT_ENOUGH_ATTEMPTS;
+        }
+    }
+
+    /** Update total authentication attempts and rejections. */
+    public void authenticate(boolean authenticated) {
+        if (!authenticated) {
+            mRejectedAttempts++;
+        }
+        mTotalAttempts++;
+    }
+
+    /** Reset total authentication attempts and rejections. */
+    public void resetData() {
+        mTotalAttempts = 0;
+        mRejectedAttempts = 0;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (!(obj instanceof AuthenticationStats)) {
+            return false;
+        }
+
+        AuthenticationStats target = (AuthenticationStats) obj;
+        return this.getUserId() == target.getUserId()
+                && this.getTotalAttempts()
+                == target.getTotalAttempts()
+                && this.getRejectedAttempts()
+                == target.getRejectedAttempts()
+                && this.getEnrollmentNotifications()
+                == target.getEnrollmentNotifications()
+                && this.getModality() == target.getModality();
+    }
+
+    @Override
+    public int hashCode() {
+        return String.format("userId: %d, totalAttempts: %d, rejectedAttempts: %d, "
+                + "enrollmentNotifications: %d, modality: %d", mUserId, mTotalAttempts,
+                mRejectedAttempts, mEnrollmentNotifications, mModality).hashCode();
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
new file mode 100644
index 0000000..97e5c6f
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.UserHandle;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.sensors.BiometricNotification;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Calculate and collect on-device False Rejection Rates (FRR).
+ * FRR = All [given biometric modality] unlock failures / all [given biometric modality] unlock
+ * attempts.
+ */
+public class AuthenticationStatsCollector {
+
+    private static final String TAG = "AuthenticationStatsCollector";
+
+    // The minimum number of attempts that will calculate the FRR and trigger the notification.
+    private static final int MINIMUM_ATTEMPTS = 500;
+    // Upload the data every 50 attempts (average number of daily authentications).
+    private static final int AUTHENTICATION_UPLOAD_INTERVAL = 50;
+    // The maximum number of eligible biometric enrollment notification can be sent.
+    private static final int MAXIMUM_ENROLLMENT_NOTIFICATIONS = 2;
+
+    @NonNull private final Context mContext;
+
+    private final float mThreshold;
+    private final int mModality;
+
+    @NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap;
+
+    // TODO(b/295582896): Find a way to make this NonNull
+    @Nullable private AuthenticationStatsPersister mAuthenticationStatsPersister;
+    @NonNull private BiometricNotification mBiometricNotification;
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(@NonNull Context context, @NonNull Intent intent) {
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+            if (userId != UserHandle.USER_NULL
+                    && intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
+                onUserRemoved(userId);
+            }
+        }
+    };
+
+    public AuthenticationStatsCollector(@NonNull Context context, int modality,
+            @NonNull BiometricNotification biometricNotification) {
+        mContext = context;
+        mThreshold = context.getResources()
+                .getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1);
+        mUserAuthenticationStatsMap = new HashMap<>();
+        mModality = modality;
+        mBiometricNotification = biometricNotification;
+
+        context.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_USER_REMOVED));
+    }
+
+    private void initializeUserAuthenticationStatsMap() {
+        mAuthenticationStatsPersister = new AuthenticationStatsPersister(mContext);
+        for (AuthenticationStats stats : mAuthenticationStatsPersister.getAllFrrStats(mModality)) {
+            mUserAuthenticationStatsMap.put(stats.getUserId(), stats);
+        }
+    }
+
+    /** Update total authentication and rejected attempts. */
+    public void authenticate(int userId, boolean authenticated) {
+        // SharedPreference is not ready when starting system server, initialize
+        // mUserAuthenticationStatsMap in authentication to ensure SharedPreference
+        // is ready for application use.
+        if (mUserAuthenticationStatsMap.isEmpty()) {
+            initializeUserAuthenticationStatsMap();
+        }
+        // Check if this is a new user.
+        if (!mUserAuthenticationStatsMap.containsKey(userId)) {
+            mUserAuthenticationStatsMap.put(userId, new AuthenticationStats(userId, mModality));
+        }
+
+        AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+
+        authenticationStats.authenticate(authenticated);
+
+        persistDataIfNeeded(userId);
+        sendNotificationIfNeeded(userId);
+    }
+
+    /** Check if a notification should be sent after a calculation cycle. */
+    private void sendNotificationIfNeeded(int userId) {
+        AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+        if (authenticationStats.getTotalAttempts() < MINIMUM_ATTEMPTS) {
+            return;
+        }
+
+        // Don't send notification if FRR below the threshold.
+        if (authenticationStats.getEnrollmentNotifications() >= MAXIMUM_ENROLLMENT_NOTIFICATIONS
+                || authenticationStats.getFrr() < mThreshold) {
+            authenticationStats.resetData();
+            return;
+        }
+
+        authenticationStats.resetData();
+
+        final PackageManager packageManager = mContext.getPackageManager();
+
+        // Don't send notification to single-modality devices.
+        if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
+                || !packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
+            return;
+        }
+
+        final FaceManager faceManager = mContext.getSystemService(FaceManager.class);
+        final boolean hasEnrolledFace = faceManager.hasEnrolledTemplates(userId);
+
+        final FingerprintManager fingerprintManager = mContext
+                .getSystemService(FingerprintManager.class);
+        final boolean hasEnrolledFingerprint = fingerprintManager.hasEnrolledTemplates(userId);
+
+        // Don't send notification when both face and fingerprint are enrolled.
+        if (hasEnrolledFace && hasEnrolledFingerprint) {
+            return;
+        }
+        if (hasEnrolledFace && !hasEnrolledFingerprint) {
+            mBiometricNotification.sendFpEnrollNotification(mContext);
+        } else if (!hasEnrolledFace && hasEnrolledFingerprint) {
+            mBiometricNotification.sendFaceEnrollNotification(mContext);
+        }
+    }
+
+    private void persistDataIfNeeded(int userId) {
+        AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+        if (authenticationStats.getTotalAttempts() % AUTHENTICATION_UPLOAD_INTERVAL == 0) {
+            mAuthenticationStatsPersister.persistFrrStats(authenticationStats.getUserId(),
+                    authenticationStats.getTotalAttempts(),
+                    authenticationStats.getRejectedAttempts(),
+                    authenticationStats.getEnrollmentNotifications(),
+                    authenticationStats.getModality());
+        }
+    }
+
+    private void onUserRemoved(final int userId) {
+        if (mAuthenticationStatsPersister == null) {
+            initializeUserAuthenticationStatsMap();
+        }
+        mUserAuthenticationStatsMap.remove(userId);
+        mAuthenticationStatsPersister.removeFrrStats(userId);
+    }
+
+    /**
+     * Only being used in tests. Callers should not make any changes to the returned
+     * authentication stats.
+     *
+     * @return AuthenticationStats of the user, or null if the stats doesn't exist.
+     */
+    @Nullable
+    @VisibleForTesting
+    AuthenticationStats getAuthenticationStatsForUser(int userId) {
+        return mUserAuthenticationStatsMap.getOrDefault(userId, null);
+    }
+
+    @VisibleForTesting
+    void setAuthenticationStatsForUser(int userId, AuthenticationStats authenticationStats) {
+        mUserAuthenticationStatsMap.put(userId, authenticationStats);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
new file mode 100644
index 0000000..21e93a8
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.os.Environment;
+import android.util.Slog;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Persists and retrieves stats for Biometric Authentication.
+ * Authentication stats include userId, total attempts, rejected attempts,
+ * and the number of sent enrollment notifications.
+ * Data are stored in SharedPreferences in a form of a set of JSON objects,
+ * where it's one element per user.
+ */
+public class AuthenticationStatsPersister {
+
+    private static final String TAG = "AuthenticationStatsPersister";
+    private static final String FILE_NAME = "authentication_stats";
+    private static final String USER_ID = "user_id";
+    private static final String FACE_ATTEMPTS = "face_attempts";
+    private static final String FACE_REJECTIONS = "face_rejections";
+    private static final String FINGERPRINT_ATTEMPTS = "fingerprint_attempts";
+    private static final String FINGERPRINT_REJECTIONS = "fingerprint_rejections";
+    private static final String ENROLLMENT_NOTIFICATIONS = "enrollment_notifications";
+    private static final String KEY = "frr_stats";
+
+    @NonNull private final SharedPreferences mSharedPreferences;
+
+    AuthenticationStatsPersister(@NonNull Context context) {
+        // The package info in the context isn't initialized in the way it is for normal apps,
+        // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
+        // build the path manually below using the same policy that appears in ContextImpl.
+        final File prefsFile = new File(Environment.getDataSystemDeDirectory(), FILE_NAME);
+        mSharedPreferences = context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Get all frr data from SharedPreference.
+     */
+    public List<AuthenticationStats> getAllFrrStats(int modality) {
+        List<AuthenticationStats> authenticationStatsList = new ArrayList<>();
+        for (String frrStats : readFrrStats()) {
+            try {
+                JSONObject frrStatsJson = new JSONObject(frrStats);
+                if (modality == BiometricsProtoEnums.MODALITY_FACE) {
+                    authenticationStatsList.add(new AuthenticationStats(
+                            getIntValue(frrStatsJson, USER_ID, -1 /* defaultValue */),
+                            getIntValue(frrStatsJson, FACE_ATTEMPTS),
+                            getIntValue(frrStatsJson, FACE_REJECTIONS),
+                            getIntValue(frrStatsJson, ENROLLMENT_NOTIFICATIONS),
+                            modality));
+                } else if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
+                    authenticationStatsList.add(new AuthenticationStats(
+                            getIntValue(frrStatsJson, USER_ID, -1 /* defaultValue */),
+                            getIntValue(frrStatsJson, FINGERPRINT_ATTEMPTS),
+                            getIntValue(frrStatsJson, FINGERPRINT_REJECTIONS),
+                            getIntValue(frrStatsJson, ENROLLMENT_NOTIFICATIONS),
+                            modality));
+                }
+            } catch (JSONException e) {
+                Slog.w(TAG, String.format("Unable to resolve authentication stats JSON: %s",
+                        frrStats));
+            }
+        }
+        return authenticationStatsList;
+    }
+
+    /**
+     * Remove frr data for a specific user.
+     */
+    public void removeFrrStats(int userId) {
+        try {
+            // Copy into a new HashSet to allow modification.
+            Set<String> frrStatsSet = new HashSet<>(readFrrStats());
+
+            // Remove the old authentication stat for the user if it exists.
+            for (Iterator<String> iterator = frrStatsSet.iterator(); iterator.hasNext();) {
+                String frrStats = iterator.next();
+                JSONObject frrStatJson = new JSONObject(frrStats);
+                if (getValue(frrStatJson, USER_ID).equals(String.valueOf(userId))) {
+                    iterator.remove();
+                    break;
+                }
+            }
+
+            mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply();
+        } catch (JSONException ignored) {
+        }
+    }
+
+    /**
+     * Persist frr data for a specific user.
+     */
+    public void persistFrrStats(int userId, int totalAttempts, int rejectedAttempts,
+            int enrollmentNotifications, int modality) {
+        try {
+            // Copy into a new HashSet to allow modification.
+            Set<String> frrStatsSet = new HashSet<>(readFrrStats());
+
+            // Remove the old authentication stat for the user if it exists.
+            JSONObject frrStatJson = null;
+            for (Iterator<String> iterator = frrStatsSet.iterator(); iterator.hasNext();) {
+                String frrStats = iterator.next();
+                frrStatJson = new JSONObject(frrStats);
+                if (getValue(frrStatJson, USER_ID).equals(String.valueOf(userId))) {
+                    iterator.remove();
+                    break;
+                }
+            }
+
+            // If there's existing frr stats in the file, we want to update the stats for the given
+            // modality and keep the stats for other modalities.
+            if (frrStatJson != null) {
+                frrStatsSet.add(buildFrrStats(frrStatJson, totalAttempts, rejectedAttempts,
+                        enrollmentNotifications, modality));
+            } else {
+                frrStatsSet.add(buildFrrStats(userId, totalAttempts, rejectedAttempts,
+                        enrollmentNotifications, modality));
+            }
+
+            mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply();
+
+        } catch (JSONException e) {
+            Slog.e(TAG, "Unable to persist authentication stats");
+        }
+    }
+
+    private Set<String> readFrrStats() {
+        return mSharedPreferences.getStringSet(KEY, Set.of());
+    }
+
+    // Update frr stats for existing frrStats JSONObject and build the new string.
+    private String buildFrrStats(JSONObject frrStats, int totalAttempts, int rejectedAttempts,
+            int enrollmentNotifications, int modality) throws JSONException {
+        if (modality == BiometricsProtoEnums.MODALITY_FACE) {
+            return frrStats
+                    .put(FACE_ATTEMPTS, totalAttempts)
+                    .put(FACE_REJECTIONS, rejectedAttempts)
+                    .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
+                    .toString();
+        } else if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
+            return frrStats
+                    .put(FINGERPRINT_ATTEMPTS, totalAttempts)
+                    .put(FINGERPRINT_REJECTIONS, rejectedAttempts)
+                    .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
+                    .toString();
+        } else {
+            return frrStats.toString();
+        }
+    }
+
+    // Build string for new user and new authentication stats.
+    private String buildFrrStats(int userId, int totalAttempts, int rejectedAttempts,
+            int enrollmentNotifications, int modality)
+            throws JSONException {
+        if (modality == BiometricsProtoEnums.MODALITY_FACE) {
+            return new JSONObject()
+                    .put(USER_ID, userId)
+                    .put(FACE_ATTEMPTS, totalAttempts)
+                    .put(FACE_REJECTIONS, rejectedAttempts)
+                    .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
+                    .toString();
+        } else if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
+            return new JSONObject()
+                    .put(USER_ID, userId)
+                    .put(FINGERPRINT_ATTEMPTS, totalAttempts)
+                    .put(FINGERPRINT_REJECTIONS, rejectedAttempts)
+                    .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
+                    .toString();
+        } else {
+            return "";
+        }
+    }
+
+    private String getValue(JSONObject jsonObject, String key) throws JSONException {
+        return jsonObject.has(key) ? jsonObject.getString(key) : "";
+    }
+
+    private int getIntValue(JSONObject jsonObject, String key) throws JSONException {
+        return getIntValue(jsonObject, key, 0 /* defaultValue */);
+    }
+
+    private int getIntValue(JSONObject jsonObject, String key, int defaultValue)
+            throws JSONException {
+        return jsonObject.has(key) ? jsonObject.getInt(key) : defaultValue;
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 279aaf9..1898b80 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1308,10 +1308,13 @@
                                 .getString(R.string.biometric_dialog_default_subtitle));
                     } else if (hasEligibleFingerprintSensor) {
                         promptInfo.setSubtitle(getContext()
-                                .getString(R.string.biometric_dialog_fingerprint_subtitle));
+                                .getString(R.string.fingerprint_dialog_default_subtitle));
                     } else if (hasEligibleFaceSensor) {
                         promptInfo.setSubtitle(getContext()
-                                .getString(R.string.biometric_dialog_face_subtitle));
+                                .getString(R.string.face_dialog_default_subtitle));
+                    } else {
+                        promptInfo.setSubtitle(getContext()
+                                .getString(R.string.screen_lock_dialog_default_subtitle));
                     }
                 }
 
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 06417d7..f51b62d 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -66,7 +66,6 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 
-import java.util.ArrayList;
 import java.util.List;
 
 public class Utils {
@@ -98,33 +97,6 @@
     }
 
     /**
-     * Get the enabled HAL instances. If virtual is enabled and available it will be returned as
-     * the only instance, otherwise all other instances will be returned.
-     *
-     * @param context system context
-     * @param declaredInstances known instances
-     * @return filtered list of enabled instances
-     */
-    @NonNull
-    public static List<String> filterAvailableHalInstances(@NonNull Context context,
-            @NonNull List<String> declaredInstances) {
-        if (declaredInstances.size() <= 1) {
-            return declaredInstances;
-        }
-
-        final int virtualAt = declaredInstances.indexOf("virtual");
-        if (isVirtualEnabled(context) && virtualAt != -1) {
-            return List.of(declaredInstances.get(virtualAt));
-        }
-
-        declaredInstances = new ArrayList<>(declaredInstances);
-        if (virtualAt != -1) {
-            declaredInstances.remove(virtualAt);
-        }
-        return declaredInstances;
-    }
-
-    /**
      * Combines {@link PromptInfo#setDeviceCredentialAllowed(boolean)} with
      * {@link PromptInfo#setAuthenticators(int)}, as the former is not flexible enough.
      */
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index c76a2e3..87037af 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -27,6 +27,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.Utils;
 
 /**
@@ -41,6 +42,7 @@
     private final int mStatsAction;
     private final int mStatsClient;
     private final BiometricFrameworkStatsLogger mSink;
+    @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
     @NonNull private final ALSProbe mALSProbe;
 
     private long mFirstAcquireTimeMs;
@@ -49,7 +51,8 @@
     /** Get a new logger with all unknown fields (for operations that do not require logs). */
     public static BiometricLogger ofUnknown(@NonNull Context context) {
         return new BiometricLogger(context, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN,
+                null /* AuthenticationStatsCollector */);
     }
 
     /**
@@ -64,26 +67,32 @@
      * @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants.
      */
     public BiometricLogger(
-            @NonNull Context context, int statsModality, int statsAction, int statsClient) {
+            @NonNull Context context, int statsModality, int statsAction, int statsClient,
+            AuthenticationStatsCollector authenticationStatsCollector) {
         this(statsModality, statsAction, statsClient,
                 BiometricFrameworkStatsLogger.getInstance(),
+                authenticationStatsCollector,
                 context.getSystemService(SensorManager.class));
     }
 
     @VisibleForTesting
     BiometricLogger(
             int statsModality, int statsAction, int statsClient,
-            BiometricFrameworkStatsLogger logSink, SensorManager sensorManager) {
+            BiometricFrameworkStatsLogger logSink,
+            @NonNull AuthenticationStatsCollector statsCollector,
+            SensorManager sensorManager) {
         mStatsModality = statsModality;
         mStatsAction = statsAction;
         mStatsClient = statsClient;
         mSink = logSink;
+        mAuthenticationStatsCollector = statsCollector;
         mALSProbe = new ALSProbe(sensorManager);
     }
 
     /** Creates a new logger with the action replaced with the new action. */
     public BiometricLogger swapAction(@NonNull Context context, int statsAction) {
-        return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient);
+        return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient,
+                null /* AuthenticationStatsCollector */);
     }
 
     /** Disable logging metrics and only log critical events, such as system health issues. */
@@ -192,10 +201,13 @@
     public void logOnAuthenticated(Context context, OperationContextExt operationContext,
             boolean authenticated, boolean requireConfirmation, int targetUserId,
             boolean isBiometricPrompt) {
+        // Do not log metrics when fingerprint enrollment reason is ENROLL_FIND_SENSOR
         if (!mShouldLogMetrics) {
             return;
         }
 
+        mAuthenticationStatsCollector.authenticate(targetUserId, authenticated);
+
         int authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__UNKNOWN;
         if (!authenticated) {
             authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__REJECTED;
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotification.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotification.java
new file mode 100644
index 0000000..90e1860
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotification.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.content.Context;
+
+/**
+ * Interface for biometrics to send notifications.
+ */
+public interface BiometricNotification {
+
+    /**
+     * Sends a face enrollment notification.
+     */
+    void sendFaceEnrollNotification(@NonNull Context context);
+
+    /**
+     * Sends a fingerprint enrollment notification.
+     */
+    void sendFpEnrollNotification(@NonNull Context context);
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationImpl.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationImpl.java
new file mode 100644
index 0000000..7b42046
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationImpl.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.content.Context;
+
+import com.android.server.biometrics.AuthenticationStatsCollector;
+
+/**
+ * Implementation to send biometric notifications for {@link AuthenticationStatsCollector}.
+ */
+public class BiometricNotificationImpl implements BiometricNotification {
+
+    @Override
+    public void sendFaceEnrollNotification(@NonNull Context context) {
+        BiometricNotificationUtils.showFaceEnrollNotification(context);
+    }
+
+    @Override
+    public void sendFpEnrollNotification(@NonNull Context context) {
+        BiometricNotificationUtils.showFingerprintEnrollNotification(context);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
index f516a49..2ff695d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
@@ -35,9 +35,22 @@
 public class BiometricNotificationUtils {
 
     private static final String TAG = "BiometricNotificationUtils";
-    private static final String RE_ENROLL_NOTIFICATION_TAG = "FaceService";
-    private static final String BAD_CALIBRATION_NOTIFICATION_TAG = "FingerprintService";
+    private static final String FACE_RE_ENROLL_NOTIFICATION_TAG = "FaceReEnroll";
+    private static final String FACE_ENROLL_NOTIFICATION_TAG = "FaceEnroll";
+    private static final String FINGERPRINT_ENROLL_NOTIFICATION_TAG = "FingerprintEnroll";
+    private static final String BAD_CALIBRATION_NOTIFICATION_TAG = "FingerprintBadCalibration";
     private static final String KEY_RE_ENROLL_FACE = "re_enroll_face_unlock";
+    private static final String FACE_SETTINGS_ACTION = "android.settings.FACE_SETTINGS";
+    private static final String FINGERPRINT_SETTINGS_ACTION =
+            "android.settings.FINGERPRINT_SETTINGS";
+    private static final String FACE_ENROLL_ACTION = "android.settings.FACE_ENROLL";
+    private static final String FINGERPRINT_ENROLL_ACTION = "android.settings.FINGERPRINT_ENROLL";
+    private static final String SETTINGS_PACKAGE = "com.android.settings";
+    private static final String FACE_ENROLL_CHANNEL = "FaceEnrollNotificationChannel";
+    private static final String FACE_RE_ENROLL_CHANNEL = "FaceReEnrollNotificationChannel";
+    private static final String FINGERPRINT_ENROLL_CHANNEL = "FingerprintEnrollNotificationChannel";
+    private static final String FINGERPRINT_BAD_CALIBRATION_CHANNEL =
+            "FingerprintBadCalibrationNotificationChannel";
     private static final int NOTIFICATION_ID = 1;
     private static final long NOTIFICATION_INTERVAL_MS = 24 * 60 * 60 * 1000;
     private static long sLastAlertTime = 0;
@@ -56,18 +69,67 @@
         final String content =
                 context.getString(R.string.face_recalibrate_notification_content);
 
-        final Intent intent = new Intent("android.settings.FACE_SETTINGS");
-        intent.setPackage("com.android.settings");
+        final Intent intent = new Intent(FACE_SETTINGS_ACTION);
+        intent.setPackage(SETTINGS_PACKAGE);
         intent.putExtra(KEY_RE_ENROLL_FACE, true);
 
         final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
                 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
                 null /* options */, UserHandle.CURRENT);
 
-        final String channelName = "FaceEnrollNotificationChannel";
+        showNotificationHelper(context, name, title, content, pendingIntent, FACE_RE_ENROLL_CHANNEL,
+                FACE_RE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET);
+    }
 
-        showNotificationHelper(context, name, title, content, pendingIntent, channelName,
-                RE_ENROLL_NOTIFICATION_TAG);
+    /**
+     * Shows a face enrollment notification.
+     */
+    public static void showFaceEnrollNotification(@NonNull Context context) {
+
+        final String name =
+                context.getString(R.string.device_unlock_notification_name);
+        final String title =
+                context.getString(R.string.alternative_unlock_setup_notification_title);
+        final String content =
+                context.getString(R.string.alternative_face_setup_notification_content);
+
+        final Intent intent = new Intent(FACE_ENROLL_ACTION);
+        intent.setPackage(SETTINGS_PACKAGE);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
+
+        final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
+                0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
+                null /* options */, UserHandle.CURRENT);
+
+        showNotificationHelper(context, name, title, content, pendingIntent, FACE_ENROLL_CHANNEL,
+                FACE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_PUBLIC);
+    }
+
+    /**
+     * Shows a fingerprint enrollment notification.
+     */
+    public static void showFingerprintEnrollNotification(@NonNull Context context) {
+
+        final String name =
+                context.getString(R.string.device_unlock_notification_name);
+        final String title =
+                context.getString(R.string.alternative_unlock_setup_notification_title);
+        final String content =
+                context.getString(R.string.alternative_fp_setup_notification_content);
+
+        final Intent intent = new Intent(FINGERPRINT_ENROLL_ACTION);
+        intent.setPackage(SETTINGS_PACKAGE);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
+
+        final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
+                0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
+                null /* options */, UserHandle.CURRENT);
+
+        showNotificationHelper(context, name, title, content, pendingIntent,
+                FINGERPRINT_ENROLL_CHANNEL, FINGERPRINT_ENROLL_NOTIFICATION_TAG,
+                Notification.VISIBILITY_PUBLIC);
     }
 
     /**
@@ -93,22 +155,21 @@
         final String content =
                 context.getString(R.string.fingerprint_recalibrate_notification_content);
 
-        final Intent intent = new Intent("android.settings.FINGERPRINT_SETTINGS");
-        intent.setPackage("com.android.settings");
+        final Intent intent = new Intent(FINGERPRINT_SETTINGS_ACTION);
+        intent.setPackage(SETTINGS_PACKAGE);
 
         final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
                 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
                 null /* options */, UserHandle.CURRENT);
 
-        final String channelName = "FingerprintBadCalibrationNotificationChannel";
-
-        showNotificationHelper(context, name, title, content, pendingIntent, channelName,
-                BAD_CALIBRATION_NOTIFICATION_TAG);
+        showNotificationHelper(context, name, title, content, pendingIntent,
+                FINGERPRINT_BAD_CALIBRATION_CHANNEL, BAD_CALIBRATION_NOTIFICATION_TAG,
+                Notification.VISIBILITY_SECRET);
     }
 
     private static void showNotificationHelper(Context context, String name, String title,
                 String content, PendingIntent pendingIntent, String channelName,
-                String notificationTag) {
+                String notificationTag, int visibility) {
         final NotificationManager notificationManager =
                 context.getSystemService(NotificationManager.class);
         final NotificationChannel channel = new NotificationChannel(channelName, name,
@@ -123,7 +184,7 @@
                 .setAutoCancel(true)
                 .setCategory(Notification.CATEGORY_SYSTEM)
                 .setContentIntent(pendingIntent)
-                .setVisibility(Notification.VISIBILITY_SECRET)
+                .setVisibility(visibility)
                 .build();
 
         notificationManager.createNotificationChannel(channel);
@@ -134,10 +195,30 @@
     /**
      * Cancels a face re-enrollment notification
      */
-    public static void cancelReEnrollNotification(@NonNull Context context) {
+    public static void cancelFaceReEnrollNotification(@NonNull Context context) {
         final NotificationManager notificationManager =
                 context.getSystemService(NotificationManager.class);
-        notificationManager.cancelAsUser(RE_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
+        notificationManager.cancelAsUser(FACE_RE_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
+                UserHandle.CURRENT);
+    }
+
+    /**
+     * Cancels a face enrollment notification
+     */
+    public static void cancelFaceEnrollNotification(@NonNull Context context) {
+        final NotificationManager notificationManager =
+                context.getSystemService(NotificationManager.class);
+        notificationManager.cancelAsUser(FACE_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
+                UserHandle.CURRENT);
+    }
+
+    /**
+     * Cancels a fingerprint enrollment notification
+     */
+    public static void cancelFingerprintEnrollNotification(@NonNull Context context) {
+        final NotificationManager notificationManager =
+                context.getSystemService(NotificationManager.class);
+        notificationManager.cancelAsUser(FINGERPRINT_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID,
                 UserHandle.CURRENT);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 78c3808..8a54ae5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -580,7 +580,7 @@
         }
         final BiometricSchedulerOperation operation = mCurrentOperation;
         mHandler.postDelayed(() -> {
-            if (operation == mCurrentOperation) {
+            if (operation == mCurrentOperation && !operation.isFinished()) {
                 Counter.logIncrement("biometric.value_scheduler_watchdog_triggered_count");
                 clearScheduler();
             }
diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
index 969a174..aeb6b6e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
+++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
 import android.os.RemoteException;
@@ -44,7 +43,6 @@
 
     @NonNull private final Optional<IUdfpsOverlayController> mUdfpsOverlayController;
     @NonNull private final Optional<ISidefpsController> mSidefpsController;
-    @NonNull private final Optional<IUdfpsOverlay> mUdfpsOverlay;
 
     /**
      * Create an overlay controller for each modality.
@@ -54,11 +52,9 @@
      */
     public SensorOverlays(
             @Nullable IUdfpsOverlayController udfpsOverlayController,
-            @Nullable ISidefpsController sidefpsController,
-            @Nullable IUdfpsOverlay udfpsOverlay) {
+            @Nullable ISidefpsController sidefpsController) {
         mUdfpsOverlayController = Optional.ofNullable(udfpsOverlayController);
         mSidefpsController = Optional.ofNullable(sidefpsController);
-        mUdfpsOverlay = Optional.ofNullable(udfpsOverlay);
     }
 
     /**
@@ -94,14 +90,6 @@
                 Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e);
             }
         }
-
-        if (mUdfpsOverlay.isPresent()) {
-            try {
-                mUdfpsOverlay.get().show(client.getRequestId(), sensorId, reason);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception when showing the new UDFPS overlay", e);
-            }
-        }
     }
 
     /**
@@ -125,14 +113,6 @@
                 Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
             }
         }
-
-        if (mUdfpsOverlay.isPresent()) {
-            try {
-                mUdfpsOverlay.get().hide(sensorId);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception when hiding the new udfps overlay", e);
-            }
-        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 722c9af..f55cf05 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -109,7 +109,8 @@
     public void start(@NonNull ClientMonitorCallback callback) {
         super.start(callback);
 
-        BiometricNotificationUtils.cancelReEnrollNotification(getContext());
+        BiometricNotificationUtils.cancelFaceEnrollNotification(getContext());
+        BiometricNotificationUtils.cancelFaceReEnrollNotification(getContext());
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index a501647..28f0a4d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -49,12 +49,14 @@
 import android.view.Surface;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricNotificationImpl;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -112,6 +114,8 @@
     private final BiometricContext mBiometricContext;
     @NonNull
     private final AuthSessionCoordinator mAuthSessionCoordinator;
+    @NonNull
+    private final AuthenticationStatsCollector mAuthenticationStatsCollector;
     @Nullable
     private IFace mDaemon;
 
@@ -149,6 +153,17 @@
             @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull BiometricContext biometricContext) {
+        this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
+                biometricContext, null /* daemon */);
+    }
+
+    @VisibleForTesting FaceProvider(@NonNull Context context,
+            @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull SensorProps[] props,
+            @NonNull String halInstanceName,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull BiometricContext biometricContext,
+            IFace daemon) {
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
         mHalInstanceName = halInstanceName;
@@ -160,6 +175,10 @@
         mTaskStackListener = new BiometricTaskStackListener();
         mBiometricContext = biometricContext;
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
+        mDaemon = daemon;
+
+        mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+                BiometricsProtoEnums.MODALITY_FACE, new BiometricNotificationImpl());
 
         for (SensorProps prop : props) {
             final int sensorId = prop.commonProps.sensorId;
@@ -271,7 +290,8 @@
                     mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFaceSensors.get(sensorId).getAuthenticatorIds());
 
@@ -329,7 +349,8 @@
             final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
                     mFaceSensors.get(sensorId).getLazySession(), userId, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFaceSensors.get(sensorId).getAuthenticatorIds(), callback);
             scheduleForSensor(sensorId, client);
@@ -360,7 +381,8 @@
                     mFaceSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext);
             scheduleForSensor(sensorId, client);
         });
@@ -374,7 +396,8 @@
                     mFaceSensors.get(sensorId).getLazySession(), token, userId,
                     opPackageName, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, challenge);
             scheduleForSensor(sensorId, client);
         });
@@ -395,7 +418,8 @@
                     opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
                     ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, maxTemplatesPerUser, debugConsent);
             scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
                             mBiometricStateCallback, new ClientMonitorCallback() {
@@ -431,7 +455,8 @@
             final FaceDetectClient client = new FaceDetectClient(mContext,
                     mFaceSensors.get(sensorId).getLazySession(),
                     token, id, callback, options,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric);
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
@@ -459,7 +484,8 @@
                     mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
                     callback, operationId, restricted, options, cookie,
                     false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
                     mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(),
                     allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
@@ -528,7 +554,8 @@
                     new ClientMonitorCallbackConverter(receiver), faceIds, userId,
                     opPackageName, FaceUtils.getInstance(sensorId), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFaceSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
@@ -542,7 +569,8 @@
                     mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, hardwareAuthToken,
                     mFaceSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
                     Utils.getCurrentStrength(sensorId));
@@ -612,7 +640,8 @@
                             mFaceSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext,
                             FaceUtils.getInstance(sensorId),
                             mFaceSensors.get(sensorId).getAuthenticatorIds());
@@ -624,9 +653,10 @@
         });
     }
 
-    private BiometricLogger createLogger(int statsAction, int statsClient) {
+    private BiometricLogger createLogger(int statsAction, int statsClient,
+            AuthenticationStatsCollector authenticationStatsCollector) {
         return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
-                statsAction, statsClient);
+                statsAction, statsClient, authenticationStatsCollector);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 1e33c96..8086261 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -52,6 +52,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -61,7 +62,7 @@
 import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.BiometricNotificationUtils;
+import com.android.server.biometrics.sensors.BiometricNotificationImpl;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -124,6 +125,7 @@
     @Nullable private IBiometricsFace mDaemon;
     @NonNull private final HalResultController mHalResultController;
     @NonNull private final BiometricContext mBiometricContext;
+    @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     private int mCurrentUserId = UserHandle.USER_NULL;
@@ -364,6 +366,9 @@
             mCurrentUserId = UserHandle.USER_NULL;
         });
 
+        mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+                BiometricsProtoEnums.MODALITY_FACE, new BiometricNotificationImpl());
+
         try {
             ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
         } catch (RemoteException e) {
@@ -554,7 +559,7 @@
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
                     opPackageName, mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, sSystemClock.millis());
             mGeneratedChallengeCache = client;
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -586,7 +591,7 @@
             final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
                     mLazyDaemon, token, userId, opPackageName, mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
@@ -610,14 +615,12 @@
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
-            BiometricNotificationUtils.cancelReEnrollNotification(mContext);
-
             final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
                     ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext);
 
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -677,7 +680,8 @@
             final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
                     mLazyDaemon, token, requestId, receiver, operationId, restricted,
                     options, cookie, false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric, mLockoutTracker,
                     mUsageStats, allowBackgroundAuthentication,
                     Utils.getCurrentStrength(mSensorId));
@@ -713,7 +717,7 @@
                     new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
                     FaceUtils.getLegacyInstance(mSensorId), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -731,7 +735,7 @@
                     opPackageName,
                     FaceUtils.getLegacyInstance(mSensorId), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -750,7 +754,7 @@
             final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
                     mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, hardwareAuthToken);
             mScheduler.scheduleClientMonitor(client);
         });
@@ -821,7 +825,7 @@
             final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
                     mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext,
                     FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback,
@@ -953,7 +957,7 @@
         final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
                 mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
                 createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                        BiometricsProtoEnums.CLIENT_UNKNOWN),
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                 mBiometricContext, hasEnrolled, mAuthenticatorIds);
         mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
             @Override
@@ -968,9 +972,10 @@
         });
     }
 
-    private BiometricLogger createLogger(int statsAction, int statsClient) {
+    private BiometricLogger createLogger(int statsAction, int statsClient,
+            AuthenticationStatsCollector authenticationStatsCollector) {
         return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
-                statsAction, statsClient);
+                statsAction, statsClient, authenticationStatsCollector);
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 16d2f7a..27b9c79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -33,6 +33,7 @@
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -71,6 +72,14 @@
                 .getIntArray(R.array.config_face_acquire_vendor_enroll_ignorelist);
     }
 
+    @Override
+    public void start(@NonNull ClientMonitorCallback callback) {
+        super.start(callback);
+
+        BiometricNotificationUtils.cancelFaceEnrollNotification(getContext());
+        BiometricNotificationUtils.cancelFaceReEnrollNotification(getContext());
+    }
+
     @NonNull
     @Override
     protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 3d6a156..7cc6940 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -54,7 +54,6 @@
 import android.hardware.fingerprint.IFingerprintService;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
 import android.os.Build;
@@ -872,19 +871,22 @@
             super.registerAuthenticators_enforcePermission();
 
             mRegistry.registerAll(() -> {
-                final List<ServiceProvider> providers = new ArrayList<>();
-                providers.addAll(getHidlProviders(hidlSensors));
                 List<String> aidlSensors = new ArrayList<>();
                 final String[] instances = mAidlInstanceNameSupplier.get();
                 if (instances != null) {
                     aidlSensors.addAll(Lists.newArrayList(instances));
                 }
-                providers.addAll(getAidlProviders(
-                        Utils.filterAvailableHalInstances(getContext(), aidlSensors)));
+
+                final Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
+                        filteredInstances = filterAvailableHalInstances(hidlSensors, aidlSensors);
+
+                final List<ServiceProvider> providers = new ArrayList<>();
+                providers.addAll(getHidlProviders(filteredInstances.first));
+                providers.addAll(getAidlProviders(filteredInstances.second));
+
                 return providers;
             });
         }
-
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void addAuthenticatorsRegisteredCallback(
@@ -913,7 +915,6 @@
             }
             provider.onPointerDown(requestId, sensorId, pc);
         }
-
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
 
@@ -964,16 +965,6 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
-        public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
-            super.setUdfpsOverlay_enforcePermission();
-
-            for (ServiceProvider provider : mRegistry.getProviders()) {
-                provider.setUdfpsOverlay(controller);
-            }
-        }
-
-        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
-        @Override
         public void onPowerPressed() {
             super.onPowerPressed_enforcePermission();
 
@@ -1050,6 +1041,33 @@
         });
     }
 
+    private Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
+                filterAvailableHalInstances(
+            @NonNull List<FingerprintSensorPropertiesInternal> hidlInstances,
+            @NonNull List<String> aidlInstances) {
+        if ((hidlInstances.size() + aidlInstances.size()) <= 1) {
+            return new Pair(hidlInstances, aidlInstances);
+        }
+
+        final int virtualAt = aidlInstances.indexOf("virtual");
+        if (Utils.isVirtualEnabled(getContext())) {
+            if (virtualAt != -1) {
+                //only virtual instance should be returned
+                return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt)));
+            } else {
+                Slog.e(TAG, "Could not find virtual interface while it is enabled");
+                return new Pair(hidlInstances, aidlInstances);
+            }
+        } else {
+            //remove virtual instance
+            aidlInstances = new ArrayList<>(aidlInstances);
+            if (virtualAt != -1) {
+                aidlInstances.remove(virtualAt);
+            }
+            return new Pair(hidlInstances, aidlInstances);
+        }
+    }
+
     @NonNull
     private List<ServiceProvider> getHidlProviders(
             @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
@@ -1184,4 +1202,15 @@
             }
         }
     }
+
+    void simulateVhalFingerDown() {
+        if (Utils.isVirtualEnabled(getContext())) {
+            Slog.i(TAG, "Simulate virtual HAL finger down event");
+            final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+            if (provider != null) {
+                provider.second.simulateVhalFingerDown(UserHandle.getCallingUserId(),
+                        provider.first);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java
index 636413f..dc6a63f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java
@@ -45,6 +45,8 @@
                     return doHelp();
                 case "sync":
                     return doSync();
+                case "fingerdown":
+                    return doSimulateVhalFingerDown();
                 default:
                     getOutPrintWriter().println("Unrecognized command: " + cmd);
             }
@@ -62,6 +64,8 @@
         pw.println("      Print this help text.");
         pw.println("  sync");
         pw.println("      Sync enrollments now (virtualized sensors only).");
+        pw.println("  fingerdown");
+        pw.println("      Simulate finger down event (virtualized sensors only).");
     }
 
     private int doHelp() {
@@ -73,4 +77,9 @@
         mService.syncEnrollmentsNow();
         return 0;
     }
+
+    private int doSimulateVhalFingerDown() {
+        mService.simulateVhalFingerDown();
+        return 0;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 26701c1..a15d1a4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -28,7 +28,6 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 
@@ -134,12 +133,6 @@
 
     void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
 
-    /**
-     * Sets udfps overlay
-     * @param controller udfps overlay
-     */
-    void setUdfpsOverlay(@NonNull IUdfpsOverlay controller);
-
     void onPowerPressed();
 
     /**
@@ -157,4 +150,11 @@
      * @param sensorId sensor ID of the associated operation
      */
     default void scheduleWatchdog(int sensorId) {}
+
+    /**
+     * Simulate fingerprint down touch event for virtual HAL
+     * @param userId user ID
+     * @param sensorId sensor ID
+     */
+    default void simulateVhalFingerDown(int userId, int sensorId) {};
 }
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 3fc36b6..54d1faa 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
@@ -30,7 +30,6 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Build;
 import android.os.Handler;
@@ -112,7 +111,6 @@
             @NonNull LockoutCache lockoutCache,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
-            @Nullable IUdfpsOverlay udfpsOverlay,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull Handler handler,
@@ -137,8 +135,7 @@
                 false /* shouldVibrate */,
                 biometricStrength);
         setRequestId(requestId);
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                sidefpsController, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mSensorProps = sensorProps;
         mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
         mHandler = handler;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 46f62d3..51a9385 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -22,7 +22,6 @@
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -59,15 +58,13 @@
             @NonNull FingerprintAuthenticateOptions options,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
-            @Nullable IUdfpsOverlay udfpsOverlay,
             boolean isStrongBiometric) {
         super(context, lazyDaemon, token, listener, options.getUserId(),
                 options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
                 true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                null /* sideFpsController*/, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/);
         mOptions = options;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index d35469c..46ff6b4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -29,7 +29,6 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.IBinder;
@@ -87,7 +86,6 @@
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
-            @Nullable IUdfpsOverlay udfpsOverlay,
             int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
         // UDFPS haptics occur when an image is acquired (instead of when the result is known)
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
@@ -95,8 +93,7 @@
                 biometricContext);
         setRequestId(requestId);
         mSensorProps = sensorProps;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                sidefpsController, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mMaxTemplatesPerUser = maxTemplatesPerUser;
 
         mALSProbeCallback = getLogger().getAmbientLightProbe(true /* startWithClient */);
@@ -107,6 +104,13 @@
         }
     }
 
+    @Override
+    public void start(@NonNull ClientMonitorCallback callback) {
+        super.start(callback);
+
+        BiometricNotificationUtils.cancelFingerprintEnrollNotification(getContext());
+    }
+
     @NonNull
     @Override
     protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
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 3e33ef6..5f4b894 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
@@ -43,7 +43,6 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
 import android.os.Handler;
@@ -58,12 +57,14 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricNotificationImpl;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -122,8 +123,8 @@
     @Nullable private IFingerprint mDaemon;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
-    @Nullable private IUdfpsOverlay mUdfpsOverlay;
     private AuthSessionCoordinator mAuthSessionCoordinator;
+    @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
         @Override
@@ -160,6 +161,17 @@
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext) {
+        this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
+                gestureAvailabilityDispatcher, biometricContext, null /* daemon */);
+    }
+
+    @VisibleForTesting FingerprintProvider(@NonNull Context context,
+            @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull SensorProps[] props, @NonNull String halInstanceName,
+            @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+            @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+            @NonNull BiometricContext biometricContext,
+            IFingerprint daemon) {
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
         mHalInstanceName = halInstanceName;
@@ -170,6 +182,10 @@
         mTaskStackListener = new BiometricTaskStackListener();
         mBiometricContext = biometricContext;
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
+        mDaemon = daemon;
+
+        mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+                BiometricsProtoEnums.MODALITY_FINGERPRINT, new BiometricNotificationImpl());
 
         final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
 
@@ -328,7 +344,8 @@
                             mFingerprintSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext,
                             mFingerprintSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client);
@@ -353,7 +370,8 @@
                     mContext, mFingerprintSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, hardwareAuthToken,
                     mFingerprintSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
                     Utils.getCurrentStrength(sensorId));
@@ -370,7 +388,7 @@
                             mFingerprintSensors.get(sensorId).getLazySession(), token,
                             new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
                             sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                             mBiometricContext);
             scheduleForSensor(sensorId, client);
         });
@@ -385,7 +403,8 @@
                             mFingerprintSensors.get(sensorId).getLazySession(), token,
                             userId, opPackageName, sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext, challenge);
             scheduleForSensor(sensorId, client);
         });
@@ -405,10 +424,10 @@
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFingerprintSensors.get(sensorId).getSensorProperties(),
-                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+                    mUdfpsOverlayController, mSidefpsController,
                     maxTemplatesPerUser, enrollReason);
             scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
                     mBiometricStateCallback, new ClientMonitorCallback() {
@@ -445,9 +464,9 @@
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
                     mFingerprintSensors.get(sensorId).getLazySession(), token, id, callback,
                     options,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
-                    mBiometricContext,
-                    mUdfpsOverlayController, mUdfpsOverlay, isStrongBiometric);
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
+                    mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
 
@@ -468,10 +487,11 @@
                     mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
                     callback, operationId, restricted, options, cookie,
                     false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(),
-                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+                    mUdfpsOverlayController, mSidefpsController,
                     allowBackgroundAuthentication,
                     mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
                     Utils.getCurrentStrength(sensorId),
@@ -557,7 +577,8 @@
                     new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFingerprintSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
@@ -579,7 +600,8 @@
                             mFingerprintSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext,
                             FingerprintUtils.getInstance(sensorId),
                             mFingerprintSensors.get(sensorId).getAuthenticatorIds());
@@ -591,9 +613,10 @@
         });
     }
 
-    private BiometricLogger createLogger(int statsAction, int statsClient) {
+    private BiometricLogger createLogger(int statsAction, int statsClient,
+            AuthenticationStatsCollector authenticationStatsCollector) {
         return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                statsAction, statsClient);
+                statsAction, statsClient, authenticationStatsCollector);
     }
 
     @Override
@@ -626,7 +649,8 @@
                     new FingerprintInvalidationClient(mContext,
                             mFingerprintSensors.get(sensorId).getLazySession(), userId, sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext,
                             mFingerprintSensors.get(sensorId).getAuthenticatorIds(), callback);
             scheduleForSensor(sensorId, client);
@@ -707,11 +731,6 @@
     }
 
     @Override
-    public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
-        mUdfpsOverlay = controller;
-    }
-
-    @Override
     public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
             boolean clearSchedulerBuffer) {
         if (mFingerprintSensors.contains(sensorId)) {
@@ -845,4 +864,18 @@
         }
         biometricScheduler.startWatchdog();
     }
+
+    @Override
+    public void simulateVhalFingerDown(int userId, int sensorId) {
+        Slog.d(getTag(), "Simulate virtual HAL finger down event");
+        final AidlSession session = mFingerprintSensors.get(sensorId).getSessionForUser(userId);
+        final PointerContext pc = new PointerContext();
+        try {
+            session.getSession().onPointerDownWithContext(pc);
+            session.getSession().onUiReady();
+            session.getSession().onPointerUpWithContext(pc);
+        } catch (RemoteException e) {
+            Slog.e(getTag(), "failed hal operation ", e);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 1cbbf89..d0b71fc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -40,7 +40,6 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Handler;
 import android.os.IBinder;
@@ -53,6 +52,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -66,6 +66,7 @@
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricNotificationImpl;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -123,8 +124,8 @@
     @NonNull private final HalResultController mHalResultController;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
-    @Nullable private IUdfpsOverlay mUdfpsOverlay;
     @NonNull private final BiometricContext mBiometricContext;
+    @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     private int mCurrentUserId = UserHandle.USER_NULL;
@@ -353,6 +354,9 @@
             mCurrentUserId = UserHandle.USER_NULL;
         });
 
+        mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+                BiometricsProtoEnums.MODALITY_FINGERPRINT, new BiometricNotificationImpl());
+
         try {
             ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
         } catch (RemoteException e) {
@@ -499,7 +503,8 @@
                 new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
                         mContext.getOpPackageName(), mSensorProperties.sensorId,
                         createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
                         mBiometricContext,
                         this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
         mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -546,7 +551,8 @@
             final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
                     userId, mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, mLockoutTracker);
             mScheduler.scheduleClientMonitor(client);
         });
@@ -561,7 +567,8 @@
                             new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
                             mSensorProperties.sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext);
             mScheduler.scheduleClientMonitor(client);
         });
@@ -575,7 +582,8 @@
                     mContext, mLazyDaemon, token, userId, opPackageName,
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext);
             mScheduler.scheduleClientMonitor(client);
         });
@@ -596,10 +604,8 @@
                     FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
-                    mBiometricContext,
-                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
-                    enrollReason);
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
+                    mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -643,9 +649,9 @@
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
                     mLazyDaemon, token, id, listener, options,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
-                    mBiometricContext, mUdfpsOverlayController, mUdfpsOverlay,
-                    isStrongBiometric);
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
+                    mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
 
@@ -665,10 +671,11 @@
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
                     mContext, mLazyDaemon, token, requestId, listener, operationId,
                     restricted, options, cookie, false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mLockoutTracker,
-                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+                    mUdfpsOverlayController, mSidefpsController,
                     allowBackgroundAuthentication, mSensorProperties,
                     Utils.getCurrentStrength(mSensorId));
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
@@ -711,7 +718,7 @@
                     userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -731,7 +738,7 @@
                     FingerprintUtils.getLegacyInstance(mSensorId),
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -746,7 +753,7 @@
                     mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext,
                     FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, callback);
@@ -767,9 +774,10 @@
                 mBiometricStateCallback));
     }
 
-    private BiometricLogger createLogger(int statsAction, int statsClient) {
+    private BiometricLogger createLogger(int statsAction, int statsClient,
+            AuthenticationStatsCollector authenticationStatsCollector) {
         return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                statsAction, statsClient);
+                statsAction, statsClient, authenticationStatsCollector);
     }
 
     @Override
@@ -856,11 +864,6 @@
     }
 
     @Override
-    public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
-        mUdfpsOverlay = controller;
-    }
-
-    @Override
     public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
             boolean clearSchedulerBuffer) {
         final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 2a62338..9966e91 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -30,7 +30,6 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -83,7 +82,6 @@
             @NonNull LockoutFrameworkImpl lockoutTracker,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
-            @Nullable IUdfpsOverlay udfpsOverlay,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @Authenticators.Types int sensorStrength) {
@@ -93,8 +91,7 @@
                 false /* shouldVibrate */, sensorStrength);
         setRequestId(requestId);
         mLockoutFrameworkImpl = lockoutTracker;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                sidefpsController, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
         mSensorProps = sensorProps;
         mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index ed0a201..0d7f9f2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -26,7 +26,6 @@
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -67,13 +66,12 @@
             @NonNull FingerprintAuthenticateOptions options,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
-            @Nullable IUdfpsOverlay udfpsOverlay, boolean isStrongBiometric) {
+            boolean isStrongBiometric) {
         super(context, lazyDaemon, token, listener, options.getUserId(),
                 options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
                 true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                null /* sideFpsController */, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
         mIsStrongBiometric = isStrongBiometric;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index c2b7944..382e7e2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -27,7 +27,6 @@
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -69,14 +68,12 @@
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
-            @Nullable IUdfpsOverlay udfpsOverlay,
             @FingerprintManager.EnrollReason int enrollReason) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
                 biometricContext);
         setRequestId(requestId);
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
-                sidefpsController, udfpsOverlay);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
 
         mEnrollReason = enrollReason;
         if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
@@ -84,6 +81,13 @@
         }
     }
 
+    @Override
+    public void start(@NonNull ClientMonitorCallback callback) {
+        super.start(callback);
+
+        BiometricNotificationUtils.cancelFingerprintEnrollNotification(getContext());
+    }
+
     @NonNull
     @Override
     protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 0b04159..f8f0088 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -613,16 +613,26 @@
 
         @Override
         public boolean isCameraDisabled(int userId) {
-            DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
-            if (dpm == null) {
-                Slog.e(TAG, "Failed to get the device policy manager service");
+            if (Binder.getCallingUid() != Process.CAMERASERVER_UID) {
+                Slog.e(TAG, "Calling UID: " + Binder.getCallingUid()
+                        + " doesn't match expected camera service UID!");
                 return false;
             }
+            final long ident = Binder.clearCallingIdentity();
             try {
-                return dpm.getCameraDisabled(null, userId);
-            } catch (Exception e) {
-                e.printStackTrace();
-                return false;
+                DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+                if (dpm == null) {
+                    Slog.e(TAG, "Failed to get the device policy manager service");
+                    return false;
+                }
+                try {
+                    return dpm.getCameraDisabled(null, userId);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    return false;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
             }
         }
     };
diff --git a/services/core/java/com/android/server/cpu/CpuInfoReader.java b/services/core/java/com/android/server/cpu/CpuInfoReader.java
index 70d7bde..984ad1d 100644
--- a/services/core/java/com/android/server/cpu/CpuInfoReader.java
+++ b/services/core/java/com/android/server/cpu/CpuInfoReader.java
@@ -203,15 +203,15 @@
                 continue;
             }
             if (dynamicPolicyInfo.curCpuFreqKHz == CpuInfo.MISSING_FREQUENCY
-                    || staticPolicyInfo.maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
+                    || dynamicPolicyInfo.maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
                 Slogf.w(TAG, "Current and maximum CPU frequency information mismatch/missing for"
                         + " policy ID %d", policyId);
                 continue;
             }
-            if (dynamicPolicyInfo.curCpuFreqKHz > staticPolicyInfo.maxCpuFreqKHz) {
+            if (dynamicPolicyInfo.curCpuFreqKHz > dynamicPolicyInfo.maxCpuFreqKHz) {
                 Slogf.w(TAG, "Current CPU frequency (%d) is greater than maximum CPU frequency"
                         + " (%d) for policy ID (%d). Skipping CPU frequency policy",
-                        dynamicPolicyInfo.curCpuFreqKHz,  staticPolicyInfo.maxCpuFreqKHz, policyId);
+                        dynamicPolicyInfo.curCpuFreqKHz, dynamicPolicyInfo.maxCpuFreqKHz, policyId);
                 continue;
             }
             for (int coreIdx = 0; coreIdx < staticPolicyInfo.relatedCpuCores.size(); coreIdx++) {
@@ -234,7 +234,8 @@
                 if (dynamicPolicyInfo.affectedCpuCores.indexOf(relatedCpuCore) < 0) {
                     cpuInfoByCpus.append(relatedCpuCore, new CpuInfo(relatedCpuCore,
                             cpusetCategories, /* isOnline= */false, CpuInfo.MISSING_FREQUENCY,
-                            staticPolicyInfo.maxCpuFreqKHz, CpuInfo.MISSING_FREQUENCY, usageStats));
+                            dynamicPolicyInfo.maxCpuFreqKHz, CpuInfo.MISSING_FREQUENCY,
+                            usageStats));
                     continue;
                 }
                 // If a CPU core is online, it must have the usage stats. When the usage stats is
@@ -245,7 +246,7 @@
                     continue;
                 }
                 CpuInfo cpuInfo = new CpuInfo(relatedCpuCore, cpusetCategories, /* isOnline= */true,
-                        dynamicPolicyInfo.curCpuFreqKHz, staticPolicyInfo.maxCpuFreqKHz,
+                        dynamicPolicyInfo.curCpuFreqKHz, dynamicPolicyInfo.maxCpuFreqKHz,
                         dynamicPolicyInfo.avgTimeInStateCpuFreqKHz, usageStats);
                 cpuInfoByCpus.append(relatedCpuCore, cpuInfo);
                 if (DEBUG) {
@@ -423,12 +424,6 @@
         for (int i = 0; i < mCpuFreqPolicyDirsById.size(); i++) {
             int policyId = mCpuFreqPolicyDirsById.keyAt(i);
             File policyDir = mCpuFreqPolicyDirsById.valueAt(i);
-            long maxCpuFreqKHz = readCpuFreqKHz(new File(policyDir, MAX_SCALING_FREQ_FILE));
-            if (maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
-                Slogf.w(TAG, "Missing max CPU frequency information at %s",
-                        policyDir.getAbsolutePath());
-                continue;
-            }
             File cpuCoresFile = new File(policyDir, RELATED_CPUS_FILE);
             IntArray relatedCpuCores = readCpuCores(cpuCoresFile);
             if (relatedCpuCores == null || relatedCpuCores.size() == 0) {
@@ -436,8 +431,7 @@
                         cpuCoresFile.getAbsolutePath());
                 continue;
             }
-            StaticPolicyInfo staticPolicyInfo = new StaticPolicyInfo(maxCpuFreqKHz,
-                    relatedCpuCores);
+            StaticPolicyInfo staticPolicyInfo = new StaticPolicyInfo(relatedCpuCores);
             mStaticPolicyInfoById.append(policyId, staticPolicyInfo);
             if (DEBUG) {
                 Slogf.d(TAG, "Added static policy info %s for policy id %d", staticPolicyInfo,
@@ -464,8 +458,14 @@
                 Slogf.e(TAG, "Failed to read CPU cores from %s", cpuCoresFile.getAbsolutePath());
                 continue;
             }
+            long maxCpuFreqKHz = readCpuFreqKHz(new File(policyDir, MAX_SCALING_FREQ_FILE));
+            if (maxCpuFreqKHz == CpuInfo.MISSING_FREQUENCY) {
+                Slogf.w(TAG, "Missing max CPU frequency information at %s",
+                        policyDir.getAbsolutePath());
+                continue;
+            }
             DynamicPolicyInfo dynamicPolicyInfo = new DynamicPolicyInfo(curCpuFreqKHz,
-                    avgTimeInStateCpuFreqKHz, affectedCpuCores);
+                    maxCpuFreqKHz, avgTimeInStateCpuFreqKHz, affectedCpuCores);
             dynamicPolicyInfoById.append(policyId, dynamicPolicyInfo);
             if (DEBUG) {
                 Slogf.d(TAG, "Read dynamic policy info %s for policy id %d", dynamicPolicyInfo,
@@ -593,9 +593,12 @@
             List<String> lines = Files.readAllLines(file.toPath());
             IntArray cpuCores = new IntArray(0);
             for (int i = 0; i < lines.size(); i++) {
-                String line = lines.get(i);
-                String[] pairs = line.contains(",") ? line.trim().split(",")
-                        : line.trim().split(" ");
+                String line = lines.get(i).trim();
+                if (line.isEmpty()) {
+                    continue;
+                }
+                String[] pairs = line.contains(",") ? line.split(",")
+                        : line.split(" ");
                 for (int j = 0; j < pairs.length; j++) {
                     String[] minMaxPairs = pairs[j].split("-");
                     if (minMaxPairs.length >= 2) {
@@ -615,6 +618,9 @@
                 }
             }
             return cpuCores;
+        } catch (NumberFormatException e) {
+            Slogf.e(TAG, e, "Failed to read CPU cores from %s due to incorrect file format",
+                    file.getAbsolutePath());
         } catch (Exception e) {
             Slogf.e(TAG, e, "Failed to read CPU cores from %s", file.getAbsolutePath());
         }
@@ -889,29 +895,28 @@
     }
 
     private static final class StaticPolicyInfo {
-        public final long maxCpuFreqKHz;
         public final IntArray relatedCpuCores;
 
-        StaticPolicyInfo(long maxCpuFreqKHz, IntArray relatedCpuCores) {
-            this.maxCpuFreqKHz = maxCpuFreqKHz;
+        StaticPolicyInfo(IntArray relatedCpuCores) {
             this.relatedCpuCores = relatedCpuCores;
         }
 
         @Override
         public String toString() {
-            return "StaticPolicyInfo{maxCpuFreqKHz = " + maxCpuFreqKHz + ", relatedCpuCores = "
-                    + relatedCpuCores + '}';
+            return "StaticPolicyInfo{relatedCpuCores = " + relatedCpuCores + '}';
         }
     }
 
     private static final class DynamicPolicyInfo {
         public final long curCpuFreqKHz;
+        public final long maxCpuFreqKHz;
         public final long avgTimeInStateCpuFreqKHz;
         public final IntArray affectedCpuCores;
 
-        DynamicPolicyInfo(long curCpuFreqKHz, long avgTimeInStateCpuFreqKHz,
+        DynamicPolicyInfo(long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz,
                 IntArray affectedCpuCores) {
             this.curCpuFreqKHz = curCpuFreqKHz;
+            this.maxCpuFreqKHz = maxCpuFreqKHz;
             this.avgTimeInStateCpuFreqKHz = avgTimeInStateCpuFreqKHz;
             this.affectedCpuCores = affectedCpuCores;
         }
@@ -919,6 +924,7 @@
         @Override
         public String toString() {
             return "DynamicPolicyInfo{curCpuFreqKHz = " + curCpuFreqKHz
+                    + ", maxCpuFreqKHz = " + maxCpuFreqKHz
                     + ", avgTimeInStateCpuFreqKHz = " + avgTimeInStateCpuFreqKHz
                     + ", affectedCpuCores = " + affectedCpuCores + '}';
         }
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 5ca2961..4bfc090 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -18,31 +18,44 @@
 
 import android.hardware.display.BrightnessInfo;
 import android.os.IBinder;
+import android.provider.DeviceConfigInterface;
+
+import com.android.server.display.feature.DeviceConfigParameterProvider;
 
 import java.io.PrintWriter;
 import java.util.function.BooleanSupplier;
 
 class BrightnessRangeController {
 
-    private static final boolean NBM_FEATURE_FLAG = true;
-
     private final HighBrightnessModeController mHbmController;
     private final NormalBrightnessModeController mNormalBrightnessModeController =
             new NormalBrightnessModeController();
 
     private final Runnable mModeChangeCallback;
+    private final boolean mUseNbmController;
+
 
     BrightnessRangeController(HighBrightnessModeController hbmController,
             Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig) {
+        this(hbmController, modeChangeCallback, displayDeviceConfig,
+                new DeviceConfigParameterProvider(DeviceConfigInterface.REAL));
+    }
+
+    BrightnessRangeController(HighBrightnessModeController hbmController,
+            Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig,
+            DeviceConfigParameterProvider configParameterProvider) {
         mHbmController = hbmController;
         mModeChangeCallback = modeChangeCallback;
+        mUseNbmController = configParameterProvider.isNormalBrightnessControllerFeatureEnabled();
         mNormalBrightnessModeController.resetNbmData(displayDeviceConfig.getLuxThrottlingData());
     }
 
-
     void dump(PrintWriter pw) {
+        pw.println("BrightnessRangeController:");
+        pw.println("  mUseNormalBrightnessController=" + mUseNbmController);
         mHbmController.dump(pw);
         mNormalBrightnessModeController.dump(pw);
+
     }
 
     void onAmbientLuxChange(float ambientLux) {
@@ -92,7 +105,7 @@
 
 
     float getCurrentBrightnessMax() {
-        if (NBM_FEATURE_FLAG && mHbmController.getHighBrightnessMode()
+        if (mUseNbmController && mHbmController.getHighBrightnessMode()
                 == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) {
             return Math.min(mHbmController.getCurrentBrightnessMax(),
                     mNormalBrightnessModeController.getCurrentBrightnessMax());
@@ -113,7 +126,7 @@
     }
 
     private void applyChanges(BooleanSupplier nbmChangesFunc, Runnable hbmChangesFunc) {
-        if (NBM_FEATURE_FLAG) {
+        if (mUseNbmController) {
             boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean();
             hbmChangesFunc.run();
             // if nbm transition changed - trigger callback
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
index de42370..651828b 100644
--- a/services/core/java/com/android/server/display/BrightnessSetting.java
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -40,6 +40,7 @@
 
     private final LogicalDisplay mLogicalDisplay;
 
+    private int mUserSerial;
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
         @Override
         public void handleMessage(Message msg) {
@@ -56,13 +57,15 @@
     @GuardedBy("mSyncRoot")
     private float mBrightness;
 
-    BrightnessSetting(@NonNull PersistentDataStore persistentDataStore,
+    BrightnessSetting(int userSerial,
+            @NonNull PersistentDataStore persistentDataStore,
             @NonNull LogicalDisplay logicalDisplay,
             DisplayManagerService.SyncRoot syncRoot) {
         mPersistentDataStore = persistentDataStore;
         mLogicalDisplay = logicalDisplay;
+        mUserSerial = userSerial;
         mBrightness = mPersistentDataStore.getBrightness(
-                mLogicalDisplay.getPrimaryDisplayDeviceLocked());
+                mLogicalDisplay.getPrimaryDisplayDeviceLocked(), userSerial);
         mSyncRoot = syncRoot;
     }
 
@@ -96,8 +99,13 @@
         mListeners.remove(l);
     }
 
+    /** Sets the user serial for the brightness setting */
+    public void setUserSerial(int userSerial) {
+        mUserSerial = userSerial;
+    }
+
     /**
-     * Sets the brigtness and broadcasts the change to the listeners.
+     * Sets the brightness and broadcasts the change to the listeners.
      * @param brightness The value to which the brightness is to be set.
      */
     public void setBrightness(float brightness) {
@@ -112,7 +120,8 @@
             // changed.
             if (brightness != mBrightness) {
                 mPersistentDataStore.setBrightness(mLogicalDisplay.getPrimaryDisplayDeviceLocked(),
-                        brightness);
+                        brightness, mUserSerial
+                );
             }
             mBrightness = brightness;
             int toSend = Float.floatToIntBits(mBrightness);
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
index cfdcd63..59844e1 100644
--- a/services/core/java/com/android/server/display/BrightnessThrottler.java
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -22,7 +22,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.display.BrightnessInfo;
-import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.IThermalEventListener;
@@ -38,17 +37,25 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.utils.DeviceConfigParsingUtils;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Executor;
+import java.util.function.BiFunction;
+import java.util.function.Function;
 
 /**
  * This class monitors various conditions, such as skin temperature throttling status, and limits
  * the allowed brightness range accordingly.
+ *
+ * @deprecated will be replaced by
+ * {@link com.android.server.display.brightness.clamper.BrightnessThermalClamper}
  */
+@Deprecated
 class BrightnessThrottler {
     private static final String TAG = "BrightnessThrottler";
     private static final boolean DEBUG = false;
@@ -63,7 +70,7 @@
     private final Runnable mThrottlingChangeCallback;
     private final SkinThermalStatusObserver mSkinThermalStatusObserver;
     private final DeviceConfigListener mDeviceConfigListener;
-    private final DeviceConfigInterface mDeviceConfig;
+    private final DeviceConfigParameterProvider mConfigParameterProvider;
 
     private int mThrottlingStatus;
 
@@ -93,8 +100,21 @@
     // time the underlying display device changes.
     // This map is indexed by uniqueDisplayId, to provide maps for throttlingId -> throttlingData.
     // HashMap< uniqueDisplayId, HashMap< throttlingDataId, ThermalBrightnessThrottlingData >>
-    private final HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>>
-            mThermalBrightnessThrottlingDataOverride = new HashMap<>(1);
+    private final Map<String, Map<String, ThermalBrightnessThrottlingData>>
+            mThermalBrightnessThrottlingDataOverride = new HashMap<>();
+
+    private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
+        try {
+            int status = DeviceConfigParsingUtils.parseThermalStatus(key);
+            float brightnessPoint = DeviceConfigParsingUtils.parseBrightness(value);
+            return new ThrottlingLevel(status, brightnessPoint);
+        } catch (IllegalArgumentException iae) {
+            return null;
+        }
+    };
+
+    private final Function<List<ThrottlingLevel>, ThermalBrightnessThrottlingData>
+            mDataSetMapper = ThermalBrightnessThrottlingData::create;
 
     BrightnessThrottler(Handler handler, Runnable throttlingChangeCallback, String uniqueDisplayId,
             String throttlingDataId,
@@ -118,7 +138,7 @@
         mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
 
         mUniqueDisplayId = uniqueDisplayId;
-        mDeviceConfig = injector.getDeviceConfig();
+        mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig());
         mDeviceConfigListener = new DeviceConfigListener();
         mThermalBrightnessThrottlingDataId = throttlingDataId;
         mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap;
@@ -145,7 +165,7 @@
 
     void stop() {
         mSkinThermalStatusObserver.stopObserving();
-        mDeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListener);
+        mConfigParameterProvider.removeOnPropertiesChangedListener(mDeviceConfigListener);
         // We're asked to stop throttling, so reset brightness restrictions.
         mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
         mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
@@ -248,12 +268,6 @@
         mSkinThermalStatusObserver.dump(pw);
     }
 
-    private String getThermalBrightnessThrottlingDataString() {
-        return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA,
-                /* defaultValue= */ null);
-    }
-
     // The brightness throttling data id may or may not be specified in the string that is passed
     // in, if there is none specified, we assume it is for the default case. Each string passed in
     // here must be for one display and one throttling id.
@@ -263,78 +277,15 @@
     // 456,2,moderate,0.9,critical,0.7,id_2
     // displayId, number, <state, val> * number
     // displayId, <number, <state, val> * number>, throttlingId
-    private boolean parseAndAddData(@NonNull String strArray,
-            @NonNull HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>>
-                    displayIdToThrottlingIdToBtd) {
-        boolean validConfig = true;
-        String[] items = strArray.split(",");
-        int i = 0;
-
-        try {
-            String uniqueDisplayId = items[i++];
-
-            // number of throttling points
-            int noOfThrottlingPoints = Integer.parseInt(items[i++]);
-            List<ThrottlingLevel> throttlingLevels = new ArrayList<>(noOfThrottlingPoints);
-
-            // throttling level and point
-            for (int j = 0; j < noOfThrottlingPoints; j++) {
-                String severity = items[i++];
-                int status = parseThermalStatus(severity);
-                float brightnessPoint = parseBrightness(items[i++]);
-                throttlingLevels.add(new ThrottlingLevel(status, brightnessPoint));
-            }
-
-            String throttlingDataId = (i < items.length) ? items[i++] : DEFAULT_ID;
-            ThermalBrightnessThrottlingData throttlingLevelsData =
-                    DisplayDeviceConfig.ThermalBrightnessThrottlingData.create(throttlingLevels);
-
-            // Add throttlingLevelsData to inner map where necessary.
-            HashMap<String, ThermalBrightnessThrottlingData> throttlingMapForDisplay =
-                    displayIdToThrottlingIdToBtd.get(uniqueDisplayId);
-            if (throttlingMapForDisplay == null) {
-                throttlingMapForDisplay = new HashMap<>();
-                throttlingMapForDisplay.put(throttlingDataId, throttlingLevelsData);
-                displayIdToThrottlingIdToBtd.put(uniqueDisplayId, throttlingMapForDisplay);
-            } else if (throttlingMapForDisplay.containsKey(throttlingDataId)) {
-                Slog.e(TAG, "Throttling data for display " + uniqueDisplayId
-                        + "contains duplicate throttling ids: '" + throttlingDataId + "'");
-                return false;
-            } else {
-                throttlingMapForDisplay.put(throttlingDataId, throttlingLevelsData);
-            }
-        } catch (NumberFormatException | IndexOutOfBoundsException
-                | UnknownThermalStatusException e) {
-            Slog.e(TAG, "Throttling data is invalid array: '" + strArray + "'", e);
-            validConfig = false;
-        }
-
-        if (i != items.length) {
-            validConfig = false;
-        }
-        return validConfig;
-    }
-
     private void loadThermalBrightnessThrottlingDataFromDeviceConfig() {
-        HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
-                new HashMap<>(1);
-        mThermalBrightnessThrottlingDataString = getThermalBrightnessThrottlingDataString();
-        boolean validConfig = true;
+        mThermalBrightnessThrottlingDataString =
+                mConfigParameterProvider.getBrightnessThrottlingData();
         mThermalBrightnessThrottlingDataOverride.clear();
         if (mThermalBrightnessThrottlingDataString != null) {
-            String[] throttlingDataSplits = mThermalBrightnessThrottlingDataString.split(";");
-            for (String s : throttlingDataSplits) {
-                if (!parseAndAddData(s, tempThrottlingData)) {
-                    validConfig = false;
-                    break;
-                }
-            }
-
-            if (validConfig) {
-                mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData);
-                tempThrottlingData.clear();
-            }
-
+            Map<String, Map<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
+                    DeviceConfigParsingUtils.parseDeviceConfigMap(
+                    mThermalBrightnessThrottlingDataString, mDataPointMapper, mDataSetMapper);
+            mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData);
         } else {
             Slog.w(TAG, "DeviceConfig ThermalBrightnessThrottlingData is null");
         }
@@ -390,8 +341,7 @@
         public Executor mExecutor = new HandlerExecutor(mDeviceConfigHandler);
 
         public void startListening() {
-            mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    mExecutor, this);
+            mConfigParameterProvider.addOnPropertiesChangedListener(mExecutor, this);
         }
 
         @Override
@@ -401,42 +351,6 @@
         }
     }
 
-    private float parseBrightness(String intVal) throws NumberFormatException {
-        float value = Float.parseFloat(intVal);
-        if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) {
-            throw new NumberFormatException("Brightness constraint value out of bounds.");
-        }
-        return value;
-    }
-
-    @PowerManager.ThermalStatus private int parseThermalStatus(@NonNull String value)
-            throws UnknownThermalStatusException {
-        switch (value) {
-            case "none":
-                return PowerManager.THERMAL_STATUS_NONE;
-            case "light":
-                return PowerManager.THERMAL_STATUS_LIGHT;
-            case "moderate":
-                return PowerManager.THERMAL_STATUS_MODERATE;
-            case "severe":
-                return PowerManager.THERMAL_STATUS_SEVERE;
-            case "critical":
-                return PowerManager.THERMAL_STATUS_CRITICAL;
-            case "emergency":
-                return PowerManager.THERMAL_STATUS_EMERGENCY;
-            case "shutdown":
-                return PowerManager.THERMAL_STATUS_SHUTDOWN;
-            default:
-                throw new UnknownThermalStatusException("Invalid Thermal Status: " + value);
-        }
-    }
-
-    private static class UnknownThermalStatusException extends Exception {
-        UnknownThermalStatusException(String message) {
-            super(message);
-        }
-    }
-
     private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
         private final Injector mInjector;
         private final Handler mHandler;
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index f84a58c..472c1f5 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -22,7 +22,6 @@
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.view.Display;
 import android.view.DisplayAddress;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -38,6 +37,7 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.math.BigInteger;
 
 import javax.xml.datatype.DatatypeConfigurationException;
 
@@ -115,13 +115,16 @@
                 Slog.i(TAG, "Display layout config not found: " + configFile);
                 return;
             }
-            int leadDisplayId = Display.DEFAULT_DISPLAY;
             for (com.android.server.display.config.layout.Layout l : layouts.getLayout()) {
                 final int state = l.getState().intValue();
                 final Layout layout = createLayout(state);
                 for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
                     assert layout != null;
                     int position = getPosition(d.getPosition());
+                    BigInteger leadDisplayPhysicalId = d.getLeadDisplayAddress();
+                    DisplayAddress leadDisplayAddress = leadDisplayPhysicalId == null ? null
+                            : DisplayAddress.fromPhysicalDisplayId(
+                                    leadDisplayPhysicalId.longValue());
                     layout.createDisplayLocked(
                             DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
                             d.isDefaultDisplay(),
@@ -129,11 +132,12 @@
                             d.getDisplayGroup(),
                             mIdProducer,
                             position,
-                            leadDisplayId,
+                            leadDisplayAddress,
                             d.getBrightnessThrottlingMapId(),
                             d.getRefreshRateZoneId(),
                             d.getRefreshRateThermalThrottlingMapId());
                 }
+                layout.postProcessLocked();
             }
         } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
             Slog.e(TAG, "Encountered an error while reading/parsing display layout config file: "
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index e27182f..1c1b69b 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import android.text.TextUtils;
+
 import com.android.server.display.brightness.BrightnessReason;
 
 import java.util.Objects;
@@ -29,12 +31,17 @@
     private final float mSdrBrightness;
     private final BrightnessReason mBrightnessReason;
     private final String mDisplayBrightnessStrategyName;
+    private final boolean mShouldUseAutoBrightness;
+
+    private final boolean mIsSlowChange;
 
     private DisplayBrightnessState(Builder builder) {
-        this.mBrightness = builder.getBrightness();
-        this.mSdrBrightness = builder.getSdrBrightness();
-        this.mBrightnessReason = builder.getBrightnessReason();
-        this.mDisplayBrightnessStrategyName = builder.getDisplayBrightnessStrategyName();
+        mBrightness = builder.getBrightness();
+        mSdrBrightness = builder.getSdrBrightness();
+        mBrightnessReason = builder.getBrightnessReason();
+        mDisplayBrightnessStrategyName = builder.getDisplayBrightnessStrategyName();
+        mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness();
+        mIsSlowChange = builder.isSlowChange();
     }
 
     /**
@@ -66,6 +73,20 @@
         return mDisplayBrightnessStrategyName;
     }
 
+    /**
+     * @return {@code true} if the device is set up to run auto-brightness.
+     */
+    public boolean getShouldUseAutoBrightness() {
+        return mShouldUseAutoBrightness;
+    }
+
+    /**
+     * @return {@code true} if the should transit to new state slowly
+     */
+    public boolean isSlowChange() {
+        return mIsSlowChange;
+    }
+
     @Override
     public String toString() {
         StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -75,6 +96,10 @@
         stringBuilder.append(getSdrBrightness());
         stringBuilder.append("\n    brightnessReason:");
         stringBuilder.append(getBrightnessReason());
+        stringBuilder.append("\n    shouldUseAutoBrightness:");
+        stringBuilder.append(getShouldUseAutoBrightness());
+        stringBuilder.append("\n    isSlowChange:");
+        stringBuilder.append(mIsSlowChange);
         return stringBuilder.toString();
     }
 
@@ -91,28 +116,28 @@
             return false;
         }
 
-        DisplayBrightnessState
-                displayBrightnessState = (DisplayBrightnessState) other;
+        DisplayBrightnessState otherState = (DisplayBrightnessState) other;
 
-        if (mBrightness != displayBrightnessState.getBrightness()) {
-            return false;
-        }
-        if (mSdrBrightness != displayBrightnessState.getSdrBrightness()) {
-            return false;
-        }
-        if (!mBrightnessReason.equals(displayBrightnessState.getBrightnessReason())) {
-            return false;
-        }
-        if (!mDisplayBrightnessStrategyName.equals(
-                displayBrightnessState.getDisplayBrightnessStrategyName())) {
-            return false;
-        }
-        return true;
+        return mBrightness == otherState.getBrightness()
+                && mSdrBrightness == otherState.getSdrBrightness()
+                && mBrightnessReason.equals(otherState.getBrightnessReason())
+                && TextUtils.equals(mDisplayBrightnessStrategyName,
+                        otherState.getDisplayBrightnessStrategyName())
+                && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness()
+                && mIsSlowChange == otherState.isSlowChange();
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason);
+        return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
+                mShouldUseAutoBrightness, mIsSlowChange);
+    }
+
+    /**
+     * Helper methods to create builder
+     */
+    public static Builder builder() {
+        return new Builder();
     }
 
     /**
@@ -123,6 +148,25 @@
         private float mSdrBrightness;
         private BrightnessReason mBrightnessReason = new BrightnessReason();
         private String mDisplayBrightnessStrategyName;
+        private boolean mShouldUseAutoBrightness;
+        private boolean mIsSlowChange;
+
+        /**
+         * Create a builder starting with the values from the specified {@link
+         * DisplayBrightnessState}.
+         *
+         * @param state The state from which to initialize.
+         */
+        public static Builder from(DisplayBrightnessState state) {
+            Builder builder = new Builder();
+            builder.setBrightness(state.getBrightness());
+            builder.setSdrBrightness(state.getSdrBrightness());
+            builder.setBrightnessReason(state.getBrightnessReason());
+            builder.setDisplayBrightnessStrategyName(state.getDisplayBrightnessStrategyName());
+            builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness());
+            builder.setIsSlowChange(state.isSlowChange());
+            return builder;
+        }
 
         /**
          * Gets the brightness
@@ -200,6 +244,36 @@
         }
 
         /**
+         * See {@link DisplayBrightnessState#getShouldUseAutoBrightness}.
+         */
+        public Builder setShouldUseAutoBrightness(boolean shouldUseAutoBrightness) {
+            this.mShouldUseAutoBrightness = shouldUseAutoBrightness;
+            return this;
+        }
+
+        /**
+         * See {@link DisplayBrightnessState#getShouldUseAutoBrightness}.
+         */
+        public boolean getShouldUseAutoBrightness() {
+            return mShouldUseAutoBrightness;
+        }
+
+        /**
+         * See {@link DisplayBrightnessState#isSlowChange()}.
+         */
+        public Builder setIsSlowChange(boolean shouldUseAutoBrightness) {
+            this.mIsSlowChange = shouldUseAutoBrightness;
+            return this;
+        }
+
+        /**
+         * See {@link DisplayBrightnessState#isSlowChange()}.
+         */
+        public boolean isSlowChange() {
+            return mIsSlowChange;
+        }
+
+        /**
          * This is used to construct an immutable DisplayBrightnessState object from its builder
          */
         public DisplayBrightnessState build() {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 7ccfb44..e5965ef 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -16,6 +16,9 @@
 
 package com.android.server.display;
 
+import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
+import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -458,7 +461,7 @@
 
     public static final String QUIRK_CAN_SET_BRIGHTNESS_VIA_HWC = "canSetBrightnessViaHwc";
 
-    static final String DEFAULT_ID = "default";
+    public static final String DEFAULT_ID = "default";
 
     private static final float BRIGHTNESS_DEFAULT = 0.5f;
     private static final String ETC_DIR = "etc";
@@ -475,7 +478,7 @@
     private static final int DEFAULT_REFRESH_RATE_IN_HBM = 0;
     private static final int DEFAULT_LOW_REFRESH_RATE = 60;
     private static final int DEFAULT_HIGH_REFRESH_RATE = 0;
-    private static final int[] DEFAULT_BRIGHTNESS_THRESHOLDS = new int[]{};
+    private static final float[] DEFAULT_BRIGHTNESS_THRESHOLDS = new float[]{};
 
     private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f};
     private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f};
@@ -515,7 +518,9 @@
     private final SensorData mScreenOffBrightnessSensor = new SensorData();
 
     // The details of the proximity sensor associated with this display.
-    private final SensorData mProximitySensor = new SensorData();
+    // Is null when no sensor should be used for that display
+    @Nullable
+    private SensorData mProximitySensor = new SensorData();
 
     private final List<RefreshRateLimitation> mRefreshRateLimitations =
             new ArrayList<>(2 /*initialCapacity*/);
@@ -689,9 +694,14 @@
      * prevent flicker, we only support higher refresh rates if the display brightness is above a
      * threshold. For example, no higher refresh rate if display brightness <= disp0 && ambient
      * brightness <= amb0 || display brightness <= disp1 && ambient brightness <= amb1
+     *
+     * Brightness thresholds are paired with lux thresholds - they both have to be met.
+     *
+     * A negative brightness or lux value means that only one threshold should be used - e.g. if
+     * the brightness value is negative, only the lux threshold is applied.
      */
-    private int[] mLowDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
-    private int[] mLowAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+    private float[] mLowDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+    private float[] mLowAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
 
     /**
      * The display uses different gamma curves for different refresh rates. It's hard for panel
@@ -703,9 +713,14 @@
      * have lower display brightness requirements for the flickering is visible. For example, fixed
      * refresh rate if display brightness >= disp0 && ambient brightness >= amb0 || display
      * brightness >= disp1 && ambient brightness >= amb1
+     *
+     * Brightness thresholds are paired with lux thresholds - they both have to be met.
+     *
+     * A negative brightness or lux value means that only one threshold should be used - e.g. if
+     * the brightness value is negative, only the lux threshold is applied.
      */
-    private int[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
-    private int[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+    private float[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
+    private float[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
 
     private final HashMap<String, ThermalBrightnessThrottlingData>
             mThermalBrightnessThrottlingDataMapByThrottlingId = new HashMap<>();
@@ -1337,7 +1352,8 @@
         return mScreenOffBrightnessSensor;
     }
 
-    SensorData getProximitySensor() {
+    @Nullable
+    public SensorData getProximitySensor() {
         return mProximitySensor;
     }
 
@@ -1490,36 +1506,44 @@
     /**
      * @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
+     * allowed.
+     *
+     * A negative threshold value means that only the lux threshold is applied.
      */
-    public int[] getLowDisplayBrightnessThresholds() {
+    public float[] getLowDisplayBrightnessThresholds() {
         return mLowDisplayBrightnessThresholds;
     }
 
     /**
      * @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
+     * allowed.
+     *
+     * A negative threshold value means that only the display brightness threshold is applied.
      */
-    public int[] getLowAmbientBrightnessThresholds() {
+    public float[] getLowAmbientBrightnessThresholds() {
         return mLowAmbientBrightnessThresholds;
     }
 
     /**
      * @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
+     * allowed.
+     *
+     * A negative threshold value means that only the lux threshold is applied.
      */
-    public int[] getHighDisplayBrightnessThresholds() {
+    public float[] getHighDisplayBrightnessThresholds() {
         return mHighDisplayBrightnessThresholds;
     }
 
     /**
      * @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
+     * allowed.
+     *
+     * A negative threshold value means that only the display brightness threshold is applied.
      */
-    public int[] getHighAmbientBrightnessThresholds() {
+    public float[] getHighAmbientBrightnessThresholds() {
         return mHighAmbientBrightnessThresholds;
     }
 
@@ -2141,34 +2165,46 @@
      */
     private void loadLowerBrightnessThresholds(BlockingZoneConfig lowerBlockingZoneConfig) {
         if (lowerBlockingZoneConfig == null) {
-            mLowDisplayBrightnessThresholds = mContext.getResources().getIntArray(
+            int[] lowDisplayBrightnessThresholdsInt = mContext.getResources().getIntArray(
                 R.array.config_brightnessThresholdsOfPeakRefreshRate);
-            mLowAmbientBrightnessThresholds = mContext.getResources().getIntArray(
+            int[] lowAmbientBrightnessThresholdsInt = mContext.getResources().getIntArray(
                 R.array.config_ambientThresholdsOfPeakRefreshRate);
-            if (mLowDisplayBrightnessThresholds == null || mLowAmbientBrightnessThresholds == null
-                    || mLowDisplayBrightnessThresholds.length
-                    != mLowAmbientBrightnessThresholds.length) {
+            if (lowDisplayBrightnessThresholdsInt == null
+                    || lowAmbientBrightnessThresholdsInt == null
+                    || lowDisplayBrightnessThresholdsInt.length
+                    != lowAmbientBrightnessThresholdsInt.length) {
                 throw new RuntimeException("display low brightness threshold array and ambient "
                     + "brightness threshold array have different length: "
-                    + "mLowDisplayBrightnessThresholds="
-                    + Arrays.toString(mLowDisplayBrightnessThresholds)
-                    + ", mLowAmbientBrightnessThresholds="
-                    + Arrays.toString(mLowAmbientBrightnessThresholds));
+                    + "lowDisplayBrightnessThresholdsInt="
+                    + Arrays.toString(lowDisplayBrightnessThresholdsInt)
+                    + ", lowAmbientBrightnessThresholdsInt="
+                    + Arrays.toString(lowAmbientBrightnessThresholdsInt));
             }
+
+            mLowDisplayBrightnessThresholds =
+                    displayBrightnessThresholdsIntToFloat(lowDisplayBrightnessThresholdsInt);
+            mLowAmbientBrightnessThresholds =
+                    ambientBrightnessThresholdsIntToFloat(lowAmbientBrightnessThresholdsInt);
         } else {
             List<DisplayBrightnessPoint> lowerThresholdDisplayBrightnessPoints =
                     lowerBlockingZoneConfig.getBlockingZoneThreshold().getDisplayBrightnessPoint();
             int size = lowerThresholdDisplayBrightnessPoints.size();
-            mLowDisplayBrightnessThresholds = new int[size];
-            mLowAmbientBrightnessThresholds = new int[size];
+            mLowDisplayBrightnessThresholds = new float[size];
+            mLowAmbientBrightnessThresholds = new float[size];
             for (int i = 0; i < size; i++) {
-                // We are explicitly casting this value to an integer to be able to reuse the
-                // existing DisplayBrightnessPoint type. It is fine to do this because the round off
-                // will have the negligible and unnoticeable impact on the loaded thresholds.
-                mLowDisplayBrightnessThresholds[i] = (int) lowerThresholdDisplayBrightnessPoints
-                    .get(i).getNits().floatValue();
+                float thresholdNits = lowerThresholdDisplayBrightnessPoints
+                        .get(i).getNits().floatValue();
+                if (thresholdNits < 0) {
+                    // A negative value means that there's no threshold
+                    mLowDisplayBrightnessThresholds[i] = thresholdNits;
+                } else {
+                    float thresholdBacklight = mNitsToBacklightSpline.interpolate(thresholdNits);
+                    mLowDisplayBrightnessThresholds[i] =
+                            mBacklightToBrightnessSpline.interpolate(thresholdBacklight);
+                }
+
                 mLowAmbientBrightnessThresholds[i] = lowerThresholdDisplayBrightnessPoints
-                    .get(i).getLux().intValue();
+                    .get(i).getLux().floatValue();
             }
         }
     }
@@ -2180,34 +2216,46 @@
      */
     private void loadHigherBrightnessThresholds(BlockingZoneConfig blockingZoneConfig) {
         if (blockingZoneConfig == null) {
-            mHighDisplayBrightnessThresholds = mContext.getResources().getIntArray(
+            int[] highDisplayBrightnessThresholdsInt = mContext.getResources().getIntArray(
                 R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
-            mHighAmbientBrightnessThresholds = mContext.getResources().getIntArray(
+            int[] highAmbientBrightnessThresholdsInt = mContext.getResources().getIntArray(
                 R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
-            if (mHighAmbientBrightnessThresholds == null || mHighDisplayBrightnessThresholds == null
-                    || mHighAmbientBrightnessThresholds.length
-                    != mHighDisplayBrightnessThresholds.length) {
+            if (highDisplayBrightnessThresholdsInt == null
+                    || highAmbientBrightnessThresholdsInt == null
+                    || highDisplayBrightnessThresholdsInt.length
+                    != highAmbientBrightnessThresholdsInt.length) {
                 throw new RuntimeException("display high brightness threshold array and ambient "
                     + "brightness threshold array have different length: "
-                    + "mHighDisplayBrightnessThresholds="
-                    + Arrays.toString(mHighDisplayBrightnessThresholds)
-                    + ", mHighAmbientBrightnessThresholds="
-                    + Arrays.toString(mHighAmbientBrightnessThresholds));
+                    + "highDisplayBrightnessThresholdsInt="
+                    + Arrays.toString(highDisplayBrightnessThresholdsInt)
+                    + ", highAmbientBrightnessThresholdsInt="
+                    + Arrays.toString(highAmbientBrightnessThresholdsInt));
             }
+
+            mHighDisplayBrightnessThresholds =
+                    displayBrightnessThresholdsIntToFloat(highDisplayBrightnessThresholdsInt);
+            mHighAmbientBrightnessThresholds =
+                    ambientBrightnessThresholdsIntToFloat(highAmbientBrightnessThresholdsInt);
         } else {
             List<DisplayBrightnessPoint> higherThresholdDisplayBrightnessPoints =
                     blockingZoneConfig.getBlockingZoneThreshold().getDisplayBrightnessPoint();
             int size = higherThresholdDisplayBrightnessPoints.size();
-            mHighDisplayBrightnessThresholds = new int[size];
-            mHighAmbientBrightnessThresholds = new int[size];
+            mHighDisplayBrightnessThresholds = new float[size];
+            mHighAmbientBrightnessThresholds = new float[size];
             for (int i = 0; i < size; i++) {
-                // We are explicitly casting this value to an integer to be able to reuse the
-                // existing DisplayBrightnessPoint type. It is fine to do this because the round off
-                // will have the negligible and unnoticeable impact on the loaded thresholds.
-                mHighDisplayBrightnessThresholds[i] = (int) higherThresholdDisplayBrightnessPoints
-                    .get(i).getNits().floatValue();
+                float thresholdNits = higherThresholdDisplayBrightnessPoints
+                        .get(i).getNits().floatValue();
+                if (thresholdNits < 0) {
+                    // A negative value means that there's no threshold
+                    mHighDisplayBrightnessThresholds[i] = thresholdNits;
+                } else {
+                    float thresholdBacklight = mNitsToBacklightSpline.interpolate(thresholdNits);
+                    mHighDisplayBrightnessThresholds[i] =
+                            mBacklightToBrightnessSpline.interpolate(thresholdBacklight);
+                }
+
                 mHighAmbientBrightnessThresholds[i] = higherThresholdDisplayBrightnessPoints
-                    .get(i).getLux().intValue();
+                    .get(i).getLux().floatValue();
             }
         }
     }
@@ -2563,46 +2611,51 @@
     private void loadAmbientLightSensorFromDdc(DisplayConfiguration config) {
         final SensorDetails sensorDetails = config.getLightSensor();
         if (sensorDetails != null) {
-            mAmbientLightSensor.type = sensorDetails.getType();
-            mAmbientLightSensor.name = sensorDetails.getName();
-            final RefreshRateRange rr = sensorDetails.getRefreshRate();
-            if (rr != null) {
-                mAmbientLightSensor.minRefreshRate = rr.getMinimum().floatValue();
-                mAmbientLightSensor.maxRefreshRate = rr.getMaximum().floatValue();
-            }
+            loadSensorData(sensorDetails, mAmbientLightSensor);
         } else {
             loadAmbientLightSensorFromConfigXml();
         }
     }
 
     private void setProxSensorUnspecified() {
-        mProximitySensor.name = null;
-        mProximitySensor.type = null;
+        mProximitySensor = new SensorData();
     }
 
     private void loadScreenOffBrightnessSensorFromDdc(DisplayConfiguration config) {
         final SensorDetails sensorDetails = config.getScreenOffBrightnessSensor();
         if (sensorDetails != null) {
-            mScreenOffBrightnessSensor.type = sensorDetails.getType();
-            mScreenOffBrightnessSensor.name = sensorDetails.getName();
+            loadSensorData(sensorDetails, mScreenOffBrightnessSensor);
         }
     }
 
     private void loadProxSensorFromDdc(DisplayConfiguration config) {
         SensorDetails sensorDetails = config.getProxSensor();
         if (sensorDetails != null) {
-            mProximitySensor.name = sensorDetails.getName();
-            mProximitySensor.type = sensorDetails.getType();
-            final RefreshRateRange rr = sensorDetails.getRefreshRate();
-            if (rr != null) {
-                mProximitySensor.minRefreshRate = rr.getMinimum().floatValue();
-                mProximitySensor.maxRefreshRate = rr.getMaximum().floatValue();
+            String name = sensorDetails.getName();
+            String type = sensorDetails.getType();
+            if ("".equals(name) && "".equals(type)) {
+                // <proxSensor> with empty values to the config means no sensor should be used
+                mProximitySensor = null;
+            } else {
+                mProximitySensor = new SensorData();
+                loadSensorData(sensorDetails, mProximitySensor);
             }
         } else {
             setProxSensorUnspecified();
         }
     }
 
+    private void loadSensorData(@NonNull SensorDetails sensorDetails,
+            @NonNull SensorData sensorData) {
+        sensorData.name = sensorDetails.getName();
+        sensorData.type = sensorDetails.getType();
+        final RefreshRateRange rr = sensorDetails.getRefreshRate();
+        if (rr != null) {
+            sensorData.minRefreshRate = rr.getMinimum().floatValue();
+            sensorData.maxRefreshRate = rr.getMaximum().floatValue();
+        }
+    }
+
     private void loadBrightnessChangeThresholdsFromXml() {
         loadBrightnessChangeThresholds(/* config= */ null);
     }
@@ -3119,11 +3172,15 @@
     public static class ThermalBrightnessThrottlingData {
         public List<ThrottlingLevel> throttlingLevels;
 
-        static class ThrottlingLevel {
+        /**
+         * thermal status to brightness cap holder
+         */
+        public static class ThrottlingLevel {
             public @PowerManager.ThermalStatus int thermalStatus;
             public float brightness;
 
-            ThrottlingLevel(@PowerManager.ThermalStatus int thermalStatus, float brightness) {
+            public ThrottlingLevel(
+                    @PowerManager.ThermalStatus int thermalStatus, float brightness) {
                 this.thermalStatus = thermalStatus;
                 this.brightness = brightness;
             }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index da8eb23..898a3c4 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -24,7 +24,6 @@
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
 import static android.hardware.display.DisplayManager.EventsMask;
-import static android.hardware.display.DisplayManager.HDR_OUTPUT_CONTROL_FLAG;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
@@ -42,7 +41,6 @@
 import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPORTED;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.ROOT_UID;
-import static android.provider.DeviceConfig.NAMESPACE_DISPLAY_MANAGER;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -116,7 +114,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -152,10 +150,12 @@
 import com.android.server.UiThread;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.display.DisplayDeviceConfig.SensorData;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.display.layout.Layout;
 import com.android.server.display.mode.DisplayModeDirector;
 import com.android.server.display.utils.SensorUtils;
 import com.android.server.input.InputManagerInternal;
+import com.android.server.utils.FoldSettingWrapper;
 import com.android.server.wm.SurfaceAnimationThread;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -506,6 +506,8 @@
 
     private final BrightnessSynchronizer mBrightnessSynchronizer;
 
+    private final DeviceConfigParameterProvider mConfigParameterProvider;
+
     /**
      * Applications use {@link android.view.Display#getRefreshRate} and
      * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate.
@@ -539,7 +541,8 @@
         mUiHandler = UiThread.getHandler();
         mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
         mLogicalDisplayMapper = new LogicalDisplayMapper(mContext, mDisplayDeviceRepo,
-                new LogicalDisplayListener(), mSyncRoot, mHandler);
+                new LogicalDisplayListener(), mSyncRoot, mHandler,
+                new FoldSettingWrapper(mContext.getContentResolver()));
         mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
         mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
         Resources resources = mContext.getResources();
@@ -558,6 +561,7 @@
         mWideColorSpace = colorSpaces[1];
         mOverlayProperties = SurfaceControl.getOverlaySupport();
         mSystemReady = false;
+        mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
     }
 
     public void setupSchedulerPolicies() {
@@ -653,6 +657,12 @@
                             logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(),
                             userSerial);
                     dpc.setBrightnessConfiguration(config, /* shouldResetShortTermModel= */ true);
+                    // change the brightness value according to the selected user.
+                    final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked();
+                    if (device != null) {
+                        dpc.setBrightness(
+                                mPersistentDataStore.getBrightness(device, userSerial), userSerial);
+                    }
                 }
                 dpc.onSwitchUser(newUserId);
             });
@@ -688,11 +698,11 @@
         synchronized (mSyncRoot) {
             mSafeMode = safeMode;
             mSystemReady = true;
-            mIsHdrOutputControlEnabled = isDeviceConfigHdrOutputControlEnabled();
-            DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_DISPLAY_MANAGER,
-                    BackgroundThread.getExecutor(),
+            mIsHdrOutputControlEnabled =
+                    mConfigParameterProvider.isHdrOutputControlFeatureEnabled();
+            mConfigParameterProvider.addOnPropertiesChangedListener(BackgroundThread.getExecutor(),
                     properties -> mIsHdrOutputControlEnabled =
-                            isDeviceConfigHdrOutputControlEnabled());
+                            mConfigParameterProvider.isHdrOutputControlFeatureEnabled());
             // Just in case the top inset changed before the system was ready. At this point, any
             // relevant configuration should be in place.
             recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY));
@@ -723,12 +733,6 @@
         mContext.registerReceiver(mIdleModeReceiver, filter);
     }
 
-    private boolean isDeviceConfigHdrOutputControlEnabled() {
-        return DeviceConfig.getBoolean(NAMESPACE_DISPLAY_MANAGER,
-                HDR_OUTPUT_CONTROL_FLAG,
-                true);
-    }
-
     @VisibleForTesting
     Handler getDisplayHandler() {
         return mHandler;
@@ -1542,6 +1546,7 @@
                 if (displayId != Display.INVALID_DISPLAY && virtualDevice != null && dwpc != null) {
                     mDisplayWindowPolicyControllers.put(
                             displayId, Pair.create(virtualDevice, dwpc));
+                    Slog.d(TAG, "Virtual Display: successfully created virtual display");
                 }
             }
 
@@ -1588,19 +1593,25 @@
                     if (!getProjectionService().setContentRecordingSession(session, projection)) {
                         // Unable to start mirroring, so release VirtualDisplay. Projection service
                         // handles stopping the projection.
+                        Slog.w(TAG, "Content Recording: failed to start mirroring - "
+                                + "releasing virtual display " + displayId);
                         releaseVirtualDisplayInternal(callback.asBinder());
                         return Display.INVALID_DISPLAY;
                     } else if (projection != null) {
                         // Indicate that this projection has been used to record, and can't be used
                         // again.
+                        Slog.d(TAG, "Content Recording: notifying MediaProjection of successful"
+                                + " VirtualDisplay creation.");
                         projection.notifyVirtualDisplayCreated(displayId);
                     }
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Unable to tell MediaProjectionManagerService to set the "
                             + "content recording session", e);
+                    return displayId;
                 }
+                Slog.d(TAG, "Virtual Display: successfully set up virtual display "
+                        + displayId);
             }
-
             return displayId;
         } finally {
             Binder.restoreCallingIdentity(secondToken);
@@ -1624,10 +1635,13 @@
             return -1;
         }
 
+
+        Slog.d(TAG, "Virtual Display: creating DisplayDevice with VirtualDisplayAdapter");
         DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked(
                 callback, projection, callingUid, packageName, surface, flags,
                 virtualDisplayConfig);
         if (device == null) {
+            Slog.w(TAG, "Virtual Display: VirtualDisplayAdapter failed to create DisplayDevice");
             return -1;
         }
 
@@ -1705,6 +1719,7 @@
 
             DisplayDevice device =
                     mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
+            Slog.d(TAG, "Virtual Display: Display Device released");
             if (device != null) {
                 // TODO: multi-display - handle virtual displays the same as other display adapters.
                 mDisplayDeviceRepo.onDisplayDeviceEvent(device,
@@ -3135,8 +3150,9 @@
             mBrightnessTracker = new BrightnessTracker(mContext, null);
         }
 
-        final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore,
-                display, mSyncRoot);
+        final int userSerial = getUserManager().getUserSerialNumber(mContext.getUserId());
+        final BrightnessSetting brightnessSetting = new BrightnessSetting(userSerial,
+                mPersistentDataStore, display, mSyncRoot);
         final DisplayPowerControllerInterface displayPowerController;
 
         // If display is internal and has a HighBrightnessModeMetadata mapping, use that.
@@ -3146,13 +3162,7 @@
         // with the corresponding displaydevice.
         HighBrightnessModeMetadata hbmMetadata =
                 mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(display);
-        if (hbmMetadata == null) {
-            Slog.wtf(TAG, "High Brightness Mode Metadata is null in DisplayManagerService for "
-                    + "display: " + display.getDisplayIdLocked());
-            return null;
-        }
-        if (DeviceConfig.getBoolean("display_manager",
-                "use_newly_structured_display_power_controller", true)) {
+        if (mConfigParameterProvider.isNewPowerControllerFeatureEnabled()) {
             displayPowerController = new DisplayPowerController2(
                     mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
                     mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
@@ -4253,6 +4263,13 @@
     }
 
     @VisibleForTesting
+    void overrideSensorManager(SensorManager sensorManager) {
+        synchronized (mSyncRoot) {
+            mSensorManager = sensorManager;
+        }
+    }
+
+    @VisibleForTesting
     final class LocalService extends DisplayManagerInternal {
 
         @Override
@@ -4505,7 +4522,7 @@
                 }
                 final DisplayDeviceConfig config = device.getDisplayDeviceConfig();
                 SensorData sensorData = config.getProximitySensor();
-                if (sensorData.matches(sensorName, sensorType)) {
+                if (sensorData != null && sensorData.matches(sensorName, sensorType)) {
                     return new RefreshRateRange(sensorData.minRefreshRate,
                             sensorData.maxRefreshRate);
                 }
@@ -4621,6 +4638,22 @@
         }
 
         @Override
+        public AmbientLightSensorData getAmbientLightSensorData(int displayId) {
+            synchronized (mSyncRoot) {
+                final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
+                if (display == null) {
+                    return null;
+                }
+                final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+                if (device == null) {
+                    return null;
+                }
+                SensorData data = device.getDisplayDeviceConfig().getAmbientLightSensor();
+                return new AmbientLightSensorData(data.name, data.type);
+            }
+        }
+
+        @Override
         public IntArray getDisplayGroupIds() {
             Set<Integer> visitedIds = new ArraySet<>();
             IntArray displayGroupIds = new IntArray();
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 92f6d45..59b8871 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -141,6 +141,9 @@
     private static final int MSG_STATSD_HBM_BRIGHTNESS = 13;
     private static final int MSG_SWITCH_USER = 14;
     private static final int MSG_BOOT_COMPLETED = 15;
+    private static final int MSG_SET_DWBC_STRONG_MODE = 16;
+    private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 17;
+    private static final int MSG_SET_DWBC_LOGGING_ENABLED = 18;
 
     private static final int PROXIMITY_UNKNOWN = -1;
     private static final int PROXIMITY_NEGATIVE = 0;
@@ -436,6 +439,7 @@
     private final boolean mSkipScreenOnBrightnessRamp;
 
     // Display white balance components.
+    // Critical methods must be called on DPC handler thread.
     @Nullable
     private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
     @Nullable
@@ -446,6 +450,8 @@
     private float[] mNitsRange;
 
     private final BrightnessRangeController mBrightnessRangeController;
+
+    @Nullable
     private final HighBrightnessModeMetadata mHighBrightnessModeMetadata;
 
     private final BrightnessThrottler mBrightnessThrottler;
@@ -512,6 +518,9 @@
     // of the lead display that this DPC should follow.
     private float mBrightnessToFollow;
 
+    // Indicates whether we should ramp slowly to the brightness value to follow.
+    private boolean mBrightnessToFollowSlowChange;
+
     // The last auto brightness adjustment that was set by the user and not temporary. Set to
     // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
     private float mAutoBrightnessAdjustment;
@@ -677,9 +686,9 @@
         DisplayWhiteBalanceController displayWhiteBalanceController = null;
         if (mDisplayId == Display.DEFAULT_DISPLAY) {
             try {
+                displayWhiteBalanceController = injector.getDisplayWhiteBalanceController(
+                        mHandler, mSensorManager, resources);
                 displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
-                displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler,
-                        mSensorManager, resources);
                 displayWhiteBalanceSettings.setCallbacks(this);
                 displayWhiteBalanceController.setCallbacks(this);
             } catch (Exception e) {
@@ -715,7 +724,9 @@
 
         setUpAutoBrightness(resources, handler);
 
-        mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic();
+        mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic()
+                && !resources.getBoolean(
+                  com.android.internal.R.bool.config_displayColorFadeDisabled);
         mColorFadeFadesConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_animateScreenLights);
 
@@ -812,7 +823,8 @@
     }
 
     @Override
-    public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
+    public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
+            boolean slowChange) {
         mBrightnessRangeController.onAmbientLuxChange(ambientLux);
         if (nits < 0) {
             mBrightnessToFollow = leadDisplayBrightness;
@@ -825,6 +837,7 @@
                 mBrightnessToFollow = leadDisplayBrightness;
             }
         }
+        mBrightnessToFollowSlowChange = slowChange;
         sendUpdatePowerState();
     }
 
@@ -842,7 +855,7 @@
             mDisplayBrightnessFollowers.remove(follower.getDisplayId());
             mHandler.postAtTime(() -> follower.setBrightnessToFollow(
                     PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
-                    /* ambientLux= */ 0), mClock.uptimeMillis());
+                    /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
         }
     }
 
@@ -852,7 +865,7 @@
             DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
             mHandler.postAtTime(() -> follower.setBrightnessToFollow(
                     PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
-                    /* ambientLux= */ 0), mClock.uptimeMillis());
+                    /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
         }
         mDisplayBrightnessFollowers.clear();
     }
@@ -1020,10 +1033,6 @@
             Message msg = mHandler.obtainMessage(MSG_STOP);
             mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
 
-            if (mDisplayWhiteBalanceController != null) {
-                mDisplayWhiteBalanceController.setEnabled(false);
-            }
-
             if (mAutomaticBrightnessController != null) {
                 mAutomaticBrightnessController.stop();
             }
@@ -1253,7 +1262,7 @@
             float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
             if (userNits >= 0) {
                 userBrightness = mInteractiveModeBrightnessMapper.convertToFloatScale(userNits);
-                if (userBrightness == PowerManager.BRIGHTNESS_INVALID_FLOAT) {
+                if (Float.isNaN(userBrightness)) {
                     userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
                 }
             }
@@ -1329,9 +1338,11 @@
                 mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
             }
         }
-        if (mDisplayWhiteBalanceController != null) {
-            mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
-        }
+
+        Message msg = mHandler.obtainMessage();
+        msg.what = MSG_SET_DWBC_STRONG_MODE;
+        msg.arg1 = isIdle ? 1 : 0;
+        mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
     }
 
     private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
@@ -1400,8 +1411,13 @@
         if (mScreenOffBrightnessSensorController != null) {
             mScreenOffBrightnessSensorController.stop();
         }
+
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setEnabled(false);
+        }
     }
 
+    // Call from handler thread
     private void updatePowerState() {
         Trace.traceBegin(Trace.TRACE_TAG_POWER,
                 "DisplayPowerController#updatePowerState");
@@ -1559,6 +1575,7 @@
         final int oldState = mPowerState.getScreenState();
         animateScreenStateChange(state, performScreenOffTransition);
         state = mPowerState.getScreenState();
+        boolean slowChange = false;
 
         if (state == Display.STATE_OFF) {
             brightnessState = PowerManager.BRIGHTNESS_OFF_FLOAT;
@@ -1567,6 +1584,7 @@
 
         if (Float.isNaN(brightnessState) && isValidBrightnessValue(mBrightnessToFollow)) {
             brightnessState = mBrightnessToFollow;
+            slowChange = mBrightnessToFollowSlowChange;
             mBrightnessReasonTemp.setReason(BrightnessReason.REASON_FOLLOWER);
         }
 
@@ -1583,12 +1601,12 @@
                 mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
         final boolean autoBrightnessEnabled = mUseAutoBrightness
                 && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
-                && Float.isNaN(brightnessState)
-                && mAutomaticBrightnessController != null
-                && mBrightnessReasonTemp.getReason() != BrightnessReason.REASON_FOLLOWER;
+                && mBrightnessReasonTemp.getReason() != BrightnessReason.REASON_OVERRIDE
+                && mAutomaticBrightnessController != null;
         final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
                 && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
         final int autoBrightnessState = autoBrightnessEnabled
+                    && mBrightnessReasonTemp.getReason() != BrightnessReason.REASON_FOLLOWER
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
                 : autoBrightnessDisabledDueToDisplayOff
                         ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
@@ -1648,9 +1666,11 @@
                     mShouldResetShortTermModel);
             mShouldResetShortTermModel = false;
         }
-        mBrightnessRangeController.setAutoBrightnessEnabled(mUseAutoBrightness
+        mBrightnessRangeController.setAutoBrightnessEnabled(autoBrightnessEnabled
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
-                : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
+                : autoBrightnessDisabledDueToDisplayOff
+                        ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
+                        : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
 
         if (mBrightnessTracker != null) {
             mBrightnessTracker.setShouldCollectColorSample(mBrightnessConfiguration != null
@@ -1661,7 +1681,6 @@
         float rawBrightnessState = brightnessState;
 
         // Apply auto-brightness.
-        boolean slowChange = false;
         if (Float.isNaN(brightnessState)) {
             float newAutoBrightnessAdjustment = autoBrightnessAdjustment;
             if (autoBrightnessEnabled) {
@@ -1740,6 +1759,14 @@
             mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
         }
 
+        float ambientLux = mAutomaticBrightnessController == null ? 0
+                : mAutomaticBrightnessController.getAmbientLux();
+        for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
+            DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
+            follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState),
+                    ambientLux, slowChange);
+        }
+
         // Now that a desired brightness has been calculated, apply brightness throttling. The
         // dimming and low power transformations that follow can only dim brightness further.
         //
@@ -1762,14 +1789,6 @@
             mAppliedThrottling = false;
         }
 
-        float ambientLux = mAutomaticBrightnessController == null ? 0
-                : mAutomaticBrightnessController.getAmbientLux();
-        for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
-            DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
-            follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState),
-                    ambientLux);
-        }
-
         if (updateScreenBrightnessSetting) {
             // Tell the rest of the system about the new brightness in case we had to change it
             // for things like auto-brightness or high-brightness-mode. Note that we do this
@@ -2050,6 +2069,32 @@
         }
     }
 
+    private void setDwbcOverride(float cct) {
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
+            // The ambient color temperature override is only applied when the ambient color
+            // temperature changes or is updated, so it doesn't necessarily change the screen color
+            // temperature immediately. So, let's make it!
+            // We can call this directly, since we're already on the handler thread.
+            updatePowerState();
+        }
+    }
+
+    private void setDwbcStrongMode(int arg) {
+        if (mDisplayWhiteBalanceController != null) {
+            final boolean isIdle = (arg == 1);
+            mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
+        }
+    }
+
+    private void setDwbcLoggingEnabled(int arg) {
+        if (mDisplayWhiteBalanceController != null) {
+            final boolean shouldEnable = (arg == 1);
+            mDisplayWhiteBalanceController.setLoggingEnabled(shouldEnable);
+            mDisplayWhiteBalanceSettings.setLoggingEnabled(shouldEnable);
+        }
+    }
+
     @Override
     public void updateBrightness() {
         sendUpdatePowerState();
@@ -2157,8 +2202,9 @@
         final DisplayDeviceConfig.HighBrightnessModeData hbmData =
                 ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
         final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
-        return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken,
-                displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
+        return mInjector.getHighBrightnessModeController(mHandler, info.width, info.height,
+                displayToken, displayUniqueId, PowerManager.BRIGHTNESS_MIN,
+                PowerManager.BRIGHTNESS_MAX, hbmData,
                 new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
                     @Override
                     public float getHdrBrightnessFromSdr(
@@ -2244,8 +2290,17 @@
             if (!reportOnly && mPowerState.getScreenState() != state
                     && readyToUpdateDisplayState()) {
                 Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
-                // TODO(b/153319140) remove when we can get this from the above trace invocation
-                SystemProperties.set("debug.tracing.screen_state", String.valueOf(state));
+
+                String propertyKey = "debug.tracing.screen_state";
+                String propertyValue = String.valueOf(state);
+                try {
+                    // TODO(b/153319140) remove when we can get this from the above trace invocation
+                    SystemProperties.set(propertyKey, propertyValue);
+                } catch (RuntimeException e) {
+                    Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
+                            + " value=" + propertyValue + " " + e.getMessage());
+                }
+
                 mPowerState.setScreenState(state);
                 // Tell battery stats about the transition.
                 noteScreenState(state);
@@ -2294,29 +2349,23 @@
     }
 
     private void loadAmbientLightSensor() {
-        DisplayDeviceConfig.SensorData lightSensor = mDisplayDeviceConfig.getAmbientLightSensor();
         final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
                 ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK;
-        mLightSensor = SensorUtils.findSensor(mSensorManager, lightSensor.type, lightSensor.name,
-                fallbackType);
+        mLightSensor = SensorUtils.findSensor(mSensorManager,
+                mDisplayDeviceConfig.getAmbientLightSensor(), fallbackType);
     }
 
     private void loadScreenOffBrightnessSensor() {
-        DisplayDeviceConfig.SensorData screenOffBrightnessSensor =
-                mDisplayDeviceConfig.getScreenOffBrightnessSensor();
         mScreenOffBrightnessSensor = SensorUtils.findSensor(mSensorManager,
-                screenOffBrightnessSensor.type, screenOffBrightnessSensor.name,
-                SensorUtils.NO_FALLBACK);
+                mDisplayDeviceConfig.getScreenOffBrightnessSensor(), SensorUtils.NO_FALLBACK);
     }
 
     private void loadProximitySensor() {
         if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT || mDisplayId != Display.DEFAULT_DISPLAY) {
             return;
         }
-        final DisplayDeviceConfig.SensorData proxSensor =
-                mDisplayDeviceConfig.getProximitySensor();
-        mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
-                Sensor.TYPE_PROXIMITY);
+        mProximitySensor = SensorUtils.findSensor(mSensorManager,
+                mDisplayDeviceConfig.getProximitySensor(), Sensor.TYPE_PROXIMITY);
         if (mProximitySensor != null) {
             mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
                     TYPICAL_PROXIMITY_THRESHOLD);
@@ -2344,8 +2393,17 @@
         }
         if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) {
             Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
-            // TODO(b/153319140) remove when we can get this from the above trace invocation
-            SystemProperties.set("debug.tracing.screen_brightness", String.valueOf(target));
+
+            String propertyKey = "debug.tracing.screen_brightness";
+            String propertyValue = String.valueOf(target);
+            try {
+                // TODO(b/153319140) remove when we can get this from the above trace invocation
+                SystemProperties.set(propertyKey, propertyValue);
+            } catch (RuntimeException e) {
+                Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
+                        + " value=" + propertyValue + " " + e.getMessage());
+            }
+
             noteScreenBrightness(target);
         }
     }
@@ -2666,9 +2724,10 @@
     }
 
     @Override
-    public void setBrightness(float brightnessValue) {
+    public void setBrightness(float brightnessValue, int userSerial) {
         // Update the setting, which will eventually call back into DPC to have us actually update
         // the display with the new value.
+        mBrightnessSetting.setUserSerial(userSerial);
         mBrightnessSetting.setBrightness(brightnessValue);
         if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
             float nits = convertToNits(brightnessValue);
@@ -2948,6 +3007,7 @@
                 + mPendingScreenBrightnessSetting);
         pw.println("  mTemporaryScreenBrightness=" + mTemporaryScreenBrightness);
         pw.println("  mBrightnessToFollow=" + mBrightnessToFollow);
+        pw.println("  mBrightnessToFollowSlowChange=" + mBrightnessToFollowSlowChange);
         pw.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
         pw.println("  mBrightnessReason=" + mBrightnessReason);
         pw.println("  mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
@@ -3326,6 +3386,19 @@
                     mBootCompleted = true;
                     updatePowerState();
                     break;
+
+                case MSG_SET_DWBC_STRONG_MODE:
+                    setDwbcStrongMode(msg.arg1);
+                    break;
+
+                case MSG_SET_DWBC_COLOR_OVERRIDE:
+                    final float cct = Float.intBitsToFloat(msg.arg1);
+                    setDwbcOverride(cct);
+                    break;
+
+                case MSG_SET_DWBC_LOGGING_ENABLED:
+                    setDwbcLoggingEnabled(msg.arg1);
+                    break;
             }
         }
     }
@@ -3393,21 +3466,18 @@
 
     @Override
     public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
-        if (mDisplayWhiteBalanceController != null) {
-            mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
-            mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
-        }
+        Message msg = mHandler.obtainMessage();
+        msg.what = MSG_SET_DWBC_LOGGING_ENABLED;
+        msg.arg1 = enabled ? 1 : 0;
+        msg.sendToTarget();
     }
 
     @Override
     public void setAmbientColorTemperatureOverride(float cct) {
-        if (mDisplayWhiteBalanceController != null) {
-            mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
-            // The ambient color temperature override is only applied when the ambient color
-            // temperature changes or is updated, so it doesn't necessarily change the screen color
-            // temperature immediately. So, let's make it!
-            sendUpdatePowerState();
-        }
+        Message msg = mHandler.obtainMessage();
+        msg.what = MSG_SET_DWBC_COLOR_OVERRIDE;
+        msg.arg1 = Float.floatToIntBits(cct);
+        msg.sendToTarget();
     }
 
     @VisibleForTesting
@@ -3527,6 +3597,23 @@
                     brightnessMapper
             );
         }
+
+        HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
+                int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
+                float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
+                HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
+                Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
+                Context context) {
+            return new HighBrightnessModeController(handler, width, height, displayToken,
+                    displayUniqueId, brightnessMin, brightnessMax, hbmData, hdrBrightnessCfg,
+                    hbmChangeCallback, hbmMetadata, context);
+        }
+
+        DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+                SensorManager sensorManager, Resources resources) {
+            return DisplayWhiteBalanceFactory.create(handler,
+                    sensorManager, resources);
+        }
     }
 
     static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index e26be68..4db8777 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -48,6 +48,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.FloatProperty;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.MutableFloat;
@@ -64,7 +65,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.RingBuffer;
 import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
@@ -73,6 +73,7 @@
 import com.android.server.display.brightness.BrightnessReason;
 import com.android.server.display.brightness.BrightnessUtils;
 import com.android.server.display.brightness.DisplayBrightnessController;
+import com.android.server.display.brightness.clamper.BrightnessClamperController;
 import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
 import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
 import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
@@ -141,6 +142,9 @@
     private static final int MSG_STATSD_HBM_BRIGHTNESS = 11;
     private static final int MSG_SWITCH_USER = 12;
     private static final int MSG_BOOT_COMPLETED = 13;
+    private static final int MSG_SET_DWBC_STRONG_MODE = 14;
+    private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 15;
+    private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
 
     private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
 
@@ -343,7 +347,7 @@
     private boolean mDozing;
 
     private boolean mAppliedDimming;
-    private boolean mAppliedLowPower;
+
     private boolean mAppliedThrottling;
 
     // Reason for which the brightness was last changed. See {@link BrightnessReason} for more
@@ -367,6 +371,7 @@
     private final boolean mSkipScreenOnBrightnessRamp;
 
     // Display white balance components.
+    // Critical methods must be called on DPC2 handler thread.
     @Nullable
     private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
     @Nullable
@@ -380,6 +385,8 @@
 
     private final BrightnessThrottler mBrightnessThrottler;
 
+    private final BrightnessClamperController mBrightnessClamperController;
+
     private final Runnable mOnBrightnessChangeRunnable;
 
     private final BrightnessEvent mLastBrightnessEvent;
@@ -489,7 +496,6 @@
                 mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
                 () -> updatePowerState(), mDisplayId, mSensorManager);
         mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
-        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(context, mDisplayId);
         mTag = "DisplayPowerController2[" + mDisplayId + "]";
         mThermalBrightnessThrottlingDataId =
                 logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
@@ -551,16 +557,25 @@
                         mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault,
                         brightnessSetting, () -> postBrightnessChangeRunnable(),
                         new HandlerExecutor(mHandler));
+
+        mBrightnessClamperController = new BrightnessClamperController(mHandler,
+                modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData(
+                mUniqueDisplayId,
+                mThermalBrightnessThrottlingDataId,
+                mDisplayDeviceConfig
+        ));
         // Seed the cached brightness
         saveBrightnessInfo(getScreenBrightnessSetting());
+        mAutomaticBrightnessStrategy =
+                mDisplayBrightnessController.getAutomaticBrightnessStrategy();
 
         DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null;
         DisplayWhiteBalanceController displayWhiteBalanceController = null;
         if (mDisplayId == Display.DEFAULT_DISPLAY) {
             try {
+                displayWhiteBalanceController = mInjector.getDisplayWhiteBalanceController(
+                        mHandler, mSensorManager, resources);
                 displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
-                displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler,
-                        mSensorManager, resources);
                 displayWhiteBalanceSettings.setCallbacks(this);
                 displayWhiteBalanceController.setCallbacks(this);
             } catch (Exception e) {
@@ -596,7 +611,9 @@
 
         setUpAutoBrightness(resources, handler);
 
-        mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic();
+        mColorFadeEnabled = mInjector.isColorFadeEnabled()
+                && !resources.getBoolean(
+                  com.android.internal.R.bool.config_displayColorFadeDisabled);
         mColorFadeFadesConfig = resources.getBoolean(
                 R.bool.config_animateScreenLights);
 
@@ -779,6 +796,10 @@
         final String thermalBrightnessThrottlingDataId =
                 mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
 
+        mBrightnessClamperController.onDisplayChanged(
+                new BrightnessClamperController.DisplayDeviceData(mUniqueDisplayId,
+                        mThermalBrightnessThrottlingDataId, config));
+
         mHandler.postAtTime(() -> {
             boolean changed = false;
             if (mDisplayDevice != device) {
@@ -832,10 +853,6 @@
             Message msg = mHandler.obtainMessage(MSG_STOP);
             mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
 
-            if (mDisplayWhiteBalanceController != null) {
-                mDisplayWhiteBalanceController.setEnabled(false);
-            }
-
             if (mAutomaticBrightnessController != null) {
                 mAutomaticBrightnessController.stop();
             }
@@ -1066,7 +1083,7 @@
             float userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
             if (userNits >= 0) {
                 userBrightness = mInteractiveModeBrightnessMapper.convertToFloatScale(userNits);
-                if (userBrightness == PowerManager.BRIGHTNESS_INVALID_FLOAT) {
+                if (Float.isNaN(userBrightness)) {
                     userBrightness = BrightnessMappingStrategy.NO_USER_BRIGHTNESS;
                 }
             }
@@ -1146,9 +1163,10 @@
                 mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
             }
         }
-        if (mDisplayWhiteBalanceController != null) {
-            mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
-        }
+        Message msg = mHandler.obtainMessage();
+        msg.what = MSG_SET_DWBC_STRONG_MODE;
+        msg.arg1 = isIdle ? 1 : 0;
+        mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
     }
 
     private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
@@ -1184,6 +1202,7 @@
         mDisplayPowerProximityStateController.cleanup();
         mBrightnessRangeController.stop();
         mBrightnessThrottler.stop();
+        mBrightnessClamperController.stop();
         mHandler.removeCallbacksAndMessages(null);
 
         // Release any outstanding wakelocks we're still holding because of pending messages.
@@ -1202,8 +1221,13 @@
         if (mScreenOffBrightnessSensorController != null) {
             mScreenOffBrightnessSensorController.stop();
         }
+
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setEnabled(false);
+        }
     }
 
+    // Call from handler thread
     private void updatePowerState() {
         Trace.traceBegin(Trace.TRACE_TAG_POWER,
                 "DisplayPowerController#updatePowerState");
@@ -1254,14 +1278,6 @@
         int state = mDisplayStateController
                 .updateDisplayState(mPowerRequest, mIsEnabled, mIsInTransition);
 
-        if (mScreenOffBrightnessSensorController != null) {
-            mScreenOffBrightnessSensorController
-                    .setLightSensorEnabled(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
-                    && mIsEnabled && (state == Display.STATE_OFF || (state == Display.STATE_DOZE
-                    && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
-                    && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
-        }
-
         // Initialize things the first time the power state is changed.
         if (mustInitialize) {
             initialize(readyToUpdateDisplayState() ? state : Display.STATE_UNKNOWN);
@@ -1272,6 +1288,7 @@
         // actual state instead of the desired one.
         animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition());
         state = mPowerState.getScreenState();
+
         final boolean userSetBrightnessChanged = mDisplayBrightnessController
                 .updateUserSetScreenBrightness();
 
@@ -1280,6 +1297,18 @@
         float brightnessState = displayBrightnessState.getBrightness();
         float rawBrightnessState = displayBrightnessState.getBrightness();
         mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
+        boolean slowChange = displayBrightnessState.isSlowChange();
+
+        // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
+        // doesn't yet have a valid lux value to use with auto-brightness.
+        if (mScreenOffBrightnessSensorController != null) {
+            mScreenOffBrightnessSensorController
+                    .setLightSensorEnabled(displayBrightnessState.getShouldUseAutoBrightness()
+                    && mIsEnabled && (state == Display.STATE_OFF
+                    || (state == Display.STATE_DOZE
+                    && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
+                    && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
+        }
 
         // Take note if the short term model was already active before applying the current
         // request changes.
@@ -1287,7 +1316,7 @@
                 mAutomaticBrightnessStrategy.isShortTermModelActive();
         mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
                 mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
-                brightnessState, mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
+                mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
                 mDisplayBrightnessController.getLastUserSetScreenBrightness(),
                 userSetBrightnessChanged);
 
@@ -1297,15 +1326,16 @@
                 && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
                 || userSetBrightnessChanged);
 
-        mBrightnessRangeController.setAutoBrightnessEnabled(mAutomaticBrightnessStrategy
-                .shouldUseAutoBrightness()
+        mBrightnessRangeController.setAutoBrightnessEnabled(
+                mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
-                : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
+                : mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff()
+                        ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
+                        : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
 
         boolean updateScreenBrightnessSetting = false;
         float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
         // Apply auto-brightness.
-        boolean slowChange = false;
         int brightnessAdjustmentFlags = 0;
         if (Float.isNaN(brightnessState)) {
             if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
@@ -1317,15 +1347,19 @@
                             .getRawAutomaticScreenBrightness();
                     brightnessState = clampScreenBrightness(brightnessState);
                     // slowly adapt to auto-brightness
+                    // TODO(b/253226419): slowChange should be decided by strategy.updateBrightness
                     slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
                             && !mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged();
                     brightnessAdjustmentFlags =
                             mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags();
                     updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+                    mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
                     mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
                     if (mScreenOffBrightnessSensorController != null) {
                         mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
                     }
+                } else {
+                    mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
                 }
             }
         } else {
@@ -1333,6 +1367,7 @@
             // to clamping so that they don't go beyond the current max as specified by HBM
             // Controller.
             brightnessState = clampScreenBrightness(brightnessState);
+            mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
         }
 
         // Use default brightness when dozing unless overridden.
@@ -1372,6 +1407,15 @@
             mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
         }
 
+        float ambientLux = mAutomaticBrightnessController == null ? 0
+                : mAutomaticBrightnessController.getAmbientLux();
+        for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
+            DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
+            follower.setBrightnessToFollow(rawBrightnessState,
+                    mDisplayBrightnessController.convertToNits(rawBrightnessState),
+                    ambientLux, slowChange);
+        }
+
         // Now that a desired brightness has been calculated, apply brightness throttling. The
         // dimming and low power transformations that follow can only dim brightness further.
         //
@@ -1394,15 +1438,6 @@
             mAppliedThrottling = false;
         }
 
-        float ambientLux = mAutomaticBrightnessController == null ? 0
-                : mAutomaticBrightnessController.getAmbientLux();
-        for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
-            DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
-            follower.setBrightnessToFollow(rawBrightnessState,
-                    mDisplayBrightnessController.convertToNits(rawBrightnessState),
-                    ambientLux);
-        }
-
         if (updateScreenBrightnessSetting) {
             // Tell the rest of the system about the new brightness in case we had to change it
             // for things like auto-brightness or high-brightness-mode. Note that we do this
@@ -1430,24 +1465,13 @@
             slowChange = false;
             mAppliedDimming = false;
         }
-        // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
-        // as long as it is above the minimum threshold.
-        if (mPowerRequest.lowPowerMode) {
-            if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
-                final float brightnessFactor =
-                        Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1);
-                final float lowPowerBrightnessFloat = (brightnessState * brightnessFactor);
-                brightnessState = Math.max(lowPowerBrightnessFloat, PowerManager.BRIGHTNESS_MIN);
-                mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_LOW_POWER);
-            }
-            if (!mAppliedLowPower) {
-                slowChange = false;
-            }
-            mAppliedLowPower = true;
-        } else if (mAppliedLowPower) {
-            slowChange = false;
-            mAppliedLowPower = false;
-        }
+
+        DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest,
+                brightnessState, slowChange);
+
+        brightnessState = clampedState.getBrightness();
+        slowChange = clampedState.isSlowChange();
+        mBrightnessReasonTemp.addModifier(clampedState.getBrightnessReason().getModifier());
 
         // The current brightness to use has been calculated at this point, and HbmController should
         // be notified so that it can accurately calculate HDR or HBM levels. We specifically do it
@@ -1549,7 +1573,7 @@
 
             notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
                     wasShortTermModelActive, mAutomaticBrightnessStrategy.isAutoBrightnessEnabled(),
-                    brightnessIsTemporary);
+                    brightnessIsTemporary, displayBrightnessState.getShouldUseAutoBrightness());
 
             // We save the brightness info *after* the brightness setting has been changed and
             // adjustments made so that the brightness info reflects the latest value.
@@ -1593,8 +1617,8 @@
         mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
         mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState
                 .getDisplayBrightnessStrategyName());
-        mTempBrightnessEvent.setAutomaticBrightnessEnabled(mAutomaticBrightnessStrategy
-                .shouldUseAutoBrightness());
+        mTempBrightnessEvent.setAutomaticBrightnessEnabled(
+                displayBrightnessState.getShouldUseAutoBrightness());
         // Temporary is what we use during slider interactions. We avoid logging those so that
         // we don't spam logcat when the slider is being used.
         boolean tempToTempTransition =
@@ -1691,6 +1715,32 @@
         }
     }
 
+    private void setDwbcOverride(float cct) {
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
+            // The ambient color temperature override is only applied when the ambient color
+            // temperature changes or is updated, so it doesn't necessarily change the screen color
+            // temperature immediately. So, let's make it!
+            // We can call this directly, since we're already on the handler thread.
+            updatePowerState();
+        }
+    }
+
+    private void setDwbcStrongMode(int arg) {
+        if (mDisplayWhiteBalanceController != null) {
+            final boolean isIdle = (arg == 1);
+            mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
+        }
+    }
+
+    private void setDwbcLoggingEnabled(int arg) {
+        if (mDisplayWhiteBalanceController != null) {
+            final boolean enabled = (arg == 1);
+            mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
+            mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
+        }
+    }
+
     @Override
     public void updateBrightness() {
         sendUpdatePowerState();
@@ -1804,9 +1854,9 @@
         final DisplayDeviceConfig.HighBrightnessModeData hbmData =
                 ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
         final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
-        return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken,
-                displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
-                (sdrBrightness, maxDesiredHdrSdrRatio) ->
+        return mInjector.getHighBrightnessModeController(mHandler, info.width, info.height,
+                displayToken, displayUniqueId, PowerManager.BRIGHTNESS_MIN,
+                PowerManager.BRIGHTNESS_MAX, hbmData, (sdrBrightness, maxDesiredHdrSdrRatio) ->
                         mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness,
                                 maxDesiredHdrSdrRatio), modeChangeCallback, hbmMetadata, mContext);
     }
@@ -1886,8 +1936,17 @@
             if (!reportOnly && mPowerState.getScreenState() != state
                     && readyToUpdateDisplayState()) {
                 Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
-                // TODO(b/153319140) remove when we can get this from the above trace invocation
-                SystemProperties.set("debug.tracing.screen_state", String.valueOf(state));
+
+                String propertyKey = "debug.tracing.screen_state";
+                String propertyValue = String.valueOf(state);
+                try {
+                    // TODO(b/153319140) remove when we can get this from the above trace invocation
+                    SystemProperties.set(propertyKey, propertyValue);
+                } catch (RuntimeException e) {
+                    Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
+                            + " value=" + propertyValue + " " + e.getMessage());
+                }
+
                 mPowerState.setScreenState(state);
                 // Tell battery stats about the transition.
                 noteScreenState(state);
@@ -1936,19 +1995,15 @@
     }
 
     private void loadAmbientLightSensor() {
-        DisplayDeviceConfig.SensorData lightSensor = mDisplayDeviceConfig.getAmbientLightSensor();
         final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
                 ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK;
-        mLightSensor = SensorUtils.findSensor(mSensorManager, lightSensor.type, lightSensor.name,
-                fallbackType);
+        mLightSensor = SensorUtils.findSensor(mSensorManager,
+                mDisplayDeviceConfig.getAmbientLightSensor(), fallbackType);
     }
 
     private void loadScreenOffBrightnessSensor() {
-        DisplayDeviceConfig.SensorData screenOffBrightnessSensor =
-                mDisplayDeviceConfig.getScreenOffBrightnessSensor();
         mScreenOffBrightnessSensor = SensorUtils.findSensor(mSensorManager,
-                screenOffBrightnessSensor.type, screenOffBrightnessSensor.name,
-                SensorUtils.NO_FALLBACK);
+                mDisplayDeviceConfig.getScreenOffBrightnessSensor(), SensorUtils.NO_FALLBACK);
     }
 
     private float clampScreenBrightness(float value) {
@@ -1966,8 +2021,17 @@
         }
         if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) {
             Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
-            // TODO(b/153319140) remove when we can get this from the above trace invocation
-            SystemProperties.set("debug.tracing.screen_brightness", String.valueOf(target));
+
+            String propertyKey = "debug.tracing.screen_brightness";
+            String propertyValue = String.valueOf(target);
+            try {
+                // TODO(b/153319140) remove when we can get this from the above trace invocation
+                SystemProperties.set(propertyKey, propertyValue);
+            } catch (RuntimeException e) {
+                Slog.e(mTag, "Failed to set a system property: key=" + propertyKey
+                        + " value=" + propertyValue + " " + e.getMessage());
+            }
+
             noteScreenBrightness(target);
         }
     }
@@ -2174,8 +2238,8 @@
     }
 
     @Override
-    public void setBrightness(float brightnessValue) {
-        mDisplayBrightnessController.setBrightness(brightnessValue);
+    public void setBrightness(float brightnessValue, int userSerial) {
+        mDisplayBrightnessController.setBrightness(brightnessValue, userSerial);
     }
 
     @Override
@@ -2189,17 +2253,19 @@
     }
 
     @Override
-    public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
+    public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
+            boolean slowChange) {
         mBrightnessRangeController.onAmbientLuxChange(ambientLux);
         if (nits < 0) {
-            mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness);
+            mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness, slowChange);
         } else {
             float brightness = mDisplayBrightnessController.convertToFloatScale(nits);
             if (BrightnessUtils.isValidBrightnessValue(brightness)) {
-                mDisplayBrightnessController.setBrightnessToFollow(brightness);
+                mDisplayBrightnessController.setBrightnessToFollow(brightness, slowChange);
             } else {
                 // The device does not support nits
-                mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness);
+                mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness,
+                        slowChange);
             }
         }
         sendUpdatePowerState();
@@ -2207,7 +2273,7 @@
 
     private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
             boolean wasShortTermModelActive, boolean autobrightnessEnabled,
-            boolean brightnessIsTemporary) {
+            boolean brightnessIsTemporary, boolean shouldUseAutoBrightness) {
 
         final float brightnessInNits =
                 mDisplayBrightnessController.convertToAdjustedNits(brightness);
@@ -2222,7 +2288,7 @@
                 || mAutomaticBrightnessController.isInIdleMode()
                 || !autobrightnessEnabled
                 || mBrightnessTracker == null
-                || !mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
+                || !shouldUseAutoBrightness
                 || brightnessInNits < 0.0f) {
             return;
         }
@@ -2261,7 +2327,7 @@
             mDisplayBrightnessFollowers.remove(follower.getDisplayId());
             mHandler.postAtTime(() -> follower.setBrightnessToFollow(
                     PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
-                    /* ambientLux= */ 0), mClock.uptimeMillis());
+                    /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
         }
     }
 
@@ -2271,7 +2337,7 @@
             DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
             mHandler.postAtTime(() -> follower.setBrightnessToFollow(
                     PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
-                    /* ambientLux= */ 0), mClock.uptimeMillis());
+                    /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
         }
         mDisplayBrightnessFollowers.clear();
     }
@@ -2329,7 +2395,6 @@
         pw.println("  mPowerRequest=" + mPowerRequest);
         pw.println("  mBrightnessReason=" + mBrightnessReason);
         pw.println("  mAppliedDimming=" + mAppliedDimming);
-        pw.println("  mAppliedLowPower=" + mAppliedLowPower);
         pw.println("  mAppliedThrottling=" + mAppliedThrottling);
         pw.println("  mDozing=" + mDozing);
         pw.println("  mSkipRampState=" + skipRampStateToString(mSkipRampState));
@@ -2369,6 +2434,10 @@
 
         dumpRbcEvents(pw);
 
+        if (mScreenOffBrightnessSensorController != null) {
+            mScreenOffBrightnessSensorController.dump(pw);
+        }
+
         if (mBrightnessRangeController != null) {
             mBrightnessRangeController.dump(pw);
         }
@@ -2398,6 +2467,11 @@
         if (mDisplayStateController != null) {
             mDisplayStateController.dumpsys(pw);
         }
+
+        pw.println();
+        if (mBrightnessClamperController != null) {
+            mBrightnessClamperController.dump(ipw);
+        }
     }
 
 
@@ -2716,6 +2790,19 @@
                     mBootCompleted = true;
                     updatePowerState();
                     break;
+
+                case MSG_SET_DWBC_STRONG_MODE:
+                    setDwbcStrongMode(msg.arg1);
+                    break;
+
+                case MSG_SET_DWBC_COLOR_OVERRIDE:
+                    final float cct = Float.intBitsToFloat(msg.arg1);
+                    setDwbcOverride(cct);
+                    break;
+
+                case MSG_SET_DWBC_LOGGING_ENABLED:
+                    setDwbcLoggingEnabled(msg.arg1);
+                    break;
             }
         }
     }
@@ -2766,21 +2853,18 @@
 
     @Override
     public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
-        if (mDisplayWhiteBalanceController != null) {
-            mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
-            mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
-        }
+        Message msg = mHandler.obtainMessage();
+        msg.what = MSG_SET_DWBC_LOGGING_ENABLED;
+        msg.arg1 = enabled ? 1 : 0;
+        msg.sendToTarget();
     }
 
     @Override
     public void setAmbientColorTemperatureOverride(float cct) {
-        if (mDisplayWhiteBalanceController != null) {
-            mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
-            // The ambient color temperature override is only applied when the ambient color
-            // temperature changes or is updated, so it doesn't necessarily change the screen color
-            // temperature immediately. So, let's make it!
-            sendUpdatePowerState();
-        }
+        Message msg = mHandler.obtainMessage();
+        msg.what = MSG_SET_DWBC_COLOR_OVERRIDE;
+        msg.arg1 = Float.floatToIntBits(cct);
+        msg.sendToTarget();
     }
 
     /** Functional interface for providing time. */
@@ -2892,6 +2976,27 @@
                     brightnessMapper
             );
         }
+
+        HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
+                int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
+                float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
+                HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
+                Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
+                Context context) {
+            return new HighBrightnessModeController(handler, width, height, displayToken,
+                    displayUniqueId, brightnessMin, brightnessMax, hbmData, hdrBrightnessCfg,
+                    hbmChangeCallback, hbmMetadata, context);
+        }
+
+        boolean isColorFadeEnabled() {
+            return !ActivityManager.isLowRamDeviceStatic();
+        }
+
+        DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+                SensorManager sensorManager, Resources resources) {
+            return DisplayWhiteBalanceFactory.create(handler,
+                    sensorManager, resources);
+        }
     }
 
     static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 73edb97..e3108c9 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -29,7 +29,7 @@
  * An interface to manage the display's power state and brightness
  */
 public interface DisplayPowerControllerInterface {
-
+    int DEFAULT_USER_SERIAL = -1;
     /**
      * Notified when the display is changed.
      *
@@ -98,7 +98,17 @@
      * Set the screen brightness of the associated display
      * @param brightness The value to which the brightness is to be set
      */
-    void setBrightness(float brightness);
+    default void setBrightness(float brightness) {
+        setBrightness(brightness, DEFAULT_USER_SERIAL);
+    }
+
+    /**
+     * Set the screen brightness of the associated display
+     * @param brightness The value to which the brightness is to be set
+     * @param userSerial The user for which the brightness value is to be set. Use userSerial = -1,
+     * if brightness needs to be updated for the current user.
+     */
+    void setBrightness(float brightness, int userSerial);
 
     /**
      * Checks if the proximity sensor is available
@@ -191,8 +201,10 @@
      * @param nits The brightness value in nits if the device supports nits. Set to a negative
      *             number otherwise.
      * @param ambientLux The lux value that will be passed to {@link HighBrightnessModeController}
+     * @param slowChange Indicates whether we should slowly animate to the given brightness value.
      */
-    void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux);
+    void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
+            boolean slowChange);
 
     /**
      * Add an additional display that will copy the brightness value from this display. This is used
diff --git a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
index c074786..882c02f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
@@ -358,10 +358,8 @@
         if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT || mDisplayId != Display.DEFAULT_DISPLAY) {
             return;
         }
-        final DisplayDeviceConfig.SensorData proxSensor =
-                mDisplayDeviceConfig.getProximitySensor();
-        mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
-                Sensor.TYPE_PROXIMITY);
+        mProximitySensor = SensorUtils.findSensor(mSensorManager,
+                mDisplayDeviceConfig.getProximitySensor(), Sensor.TYPE_PROXIMITY);
         if (mProximitySensor != null) {
             mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
                     TYPICAL_PROXIMITY_THRESHOLD);
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 11160a5..c04c279 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.hardware.display.BrightnessInfo;
@@ -75,6 +76,8 @@
     private final Injector mInjector;
 
     private HdrListener mHdrListener;
+
+    @Nullable
     private HighBrightnessModeData mHbmData;
     private HdrBrightnessDeviceConfig mHdrBrightnessCfg;
     private IBinder mRegisteredDisplayToken;
@@ -107,7 +110,9 @@
      * If HBM is currently running, this is the start time and set of all events,
      * for the current HBM session.
      */
-    private HighBrightnessModeMetadata mHighBrightnessModeMetadata = null;
+    @Nullable
+    private HighBrightnessModeMetadata mHighBrightnessModeMetadata;
+
     HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken,
             String displayUniqueId, float brightnessMin, float brightnessMax,
             HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg,
@@ -310,23 +315,29 @@
         pw.println("  mBrightnessMax=" + mBrightnessMax);
         pw.println("  remainingTime=" + calculateRemainingTime(mClock.uptimeMillis()));
         pw.println("  mIsTimeAvailable= " + mIsTimeAvailable);
-        pw.println("  mRunningStartTimeMillis="
-                + TimeUtils.formatUptime(mHighBrightnessModeMetadata.getRunningStartTimeMillis()));
         pw.println("  mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode);
         pw.println("  width*height=" + mWidth + "*" + mHeight);
-        pw.println("  mEvents=");
-        final long currentTime = mClock.uptimeMillis();
-        long lastStartTime = currentTime;
-        long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
-        if (runningStartTimeMillis != -1) {
-            lastStartTime = dumpHbmEvent(pw, new HbmEvent(runningStartTimeMillis, currentTime));
-        }
-        for (HbmEvent event : mHighBrightnessModeMetadata.getHbmEventQueue()) {
-            if (lastStartTime > event.getEndTimeMillis()) {
-                pw.println("    event: [normal brightness]: "
-                        + TimeUtils.formatDuration(lastStartTime - event.getEndTimeMillis()));
+
+        if (mHighBrightnessModeMetadata != null) {
+            pw.println("  mRunningStartTimeMillis="
+                    + TimeUtils.formatUptime(
+                    mHighBrightnessModeMetadata.getRunningStartTimeMillis()));
+            pw.println("  mEvents=");
+            final long currentTime = mClock.uptimeMillis();
+            long lastStartTime = currentTime;
+            long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
+            if (runningStartTimeMillis != -1) {
+                lastStartTime = dumpHbmEvent(pw, new HbmEvent(runningStartTimeMillis, currentTime));
             }
-            lastStartTime = dumpHbmEvent(pw, event);
+            for (HbmEvent event : mHighBrightnessModeMetadata.getHbmEventQueue()) {
+                if (lastStartTime > event.getEndTimeMillis()) {
+                    pw.println("    event: [normal brightness]: "
+                            + TimeUtils.formatDuration(lastStartTime - event.getEndTimeMillis()));
+                }
+                lastStartTime = dumpHbmEvent(pw, event);
+            }
+        } else {
+            pw.println("  mHighBrightnessModeMetadata=null");
         }
     }
 
@@ -353,7 +364,7 @@
     }
 
     private boolean deviceSupportsHbm() {
-        return mHbmData != null;
+        return mHbmData != null && mHighBrightnessModeMetadata != null;
     }
 
     private long calculateRemainingTime(long currentTime) {
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeMetadataMapper.java b/services/core/java/com/android/server/display/HighBrightnessModeMetadataMapper.java
index 76702d3..9e6f0eb9 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeMetadataMapper.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeMetadataMapper.java
@@ -41,6 +41,9 @@
                     + display.getDisplayIdLocked());
             return null;
         }
+        if (device.getDisplayDeviceConfig().getHighBrightnessModeData() == null) {
+            return null;
+        }
 
         final String uniqueId = device.getUniqueId();
 
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 7701bc6..89d865e 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -496,7 +496,7 @@
         private void loadDisplayDeviceConfig() {
             // Load display device config
             final Context context = getOverlayContext();
-            mDisplayDeviceConfig = DisplayDeviceConfig.create(context, mPhysicalDisplayId,
+            mDisplayDeviceConfig = mInjector.createDisplayDeviceConfig(context, mPhysicalDisplayId,
                     mIsFirstDisplay);
 
             // Load brightness HWC quirk
@@ -1336,6 +1336,11 @@
         public SurfaceControlProxy getSurfaceControlProxy() {
             return new SurfaceControlProxy();
         }
+
+        public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
+                long physicalDisplayId, boolean isFirstDisplay) {
+            return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay);
+        }
     }
 
     public interface DisplayEventListener {
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index d01b03f..26f8029 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -42,6 +42,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.layout.DisplayIdProducer;
 import com.android.server.display.layout.Layout;
+import com.android.server.utils.FoldSettingWrapper;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -142,6 +143,7 @@
     private final Listener mListener;
     private final DisplayManagerService.SyncRoot mSyncRoot;
     private final LogicalDisplayMapperHandler mHandler;
+    private final FoldSettingWrapper mFoldSettingWrapper;
     private final PowerManager mPowerManager;
 
     /**
@@ -189,21 +191,23 @@
 
     LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
             @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
-            @NonNull Handler handler) {
+            @NonNull Handler handler, FoldSettingWrapper foldSettingWrapper) {
         this(context, repo, listener, syncRoot, handler,
                 new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY
-                        : sNextNonDefaultDisplayId++));
+                        : sNextNonDefaultDisplayId++), foldSettingWrapper);
     }
 
     LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
             @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
-            @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap) {
+            @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap,
+            FoldSettingWrapper foldSettingWrapper) {
         mSyncRoot = syncRoot;
         mPowerManager = context.getSystemService(PowerManager.class);
         mInteractive = mPowerManager.isInteractive();
         mHandler = new LogicalDisplayMapperHandler(handler.getLooper());
         mDisplayDeviceRepo = repo;
         mListener = listener;
+        mFoldSettingWrapper = foldSettingWrapper;
         mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
         mSupportsConcurrentInternalDisplays = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_supportsConcurrentInternalDisplays);
@@ -531,9 +535,10 @@
      * Returns if the device should be put to sleep or not.
      *
      * Includes a check to verify that the device state that we are moving to, {@code pendingState},
-     * is the same as the physical state of the device, {@code baseState}. Different values for
-     * these parameters indicate a device state override is active, and we shouldn't put the device
-     * to sleep to provide a better user experience.
+     * is the same as the physical state of the device, {@code baseState}. Also if the
+     * 'Stay Awake On Fold' is not enabled. Different values for these parameters indicate a device
+     * state override is active, and we shouldn't put the device to sleep to provide a better user
+     * experience.
      *
      * @param pendingState device state we are moving to
      * @param currentState device state we are currently in
@@ -551,7 +556,7 @@
                 && mDeviceStatesOnWhichToSleep.get(pendingState)
                 && !mDeviceStatesOnWhichToSleep.get(currentState)
                 && !isOverrideActive
-                && isInteractive && isBootCompleted;
+                && isInteractive && isBootCompleted && !mFoldSettingWrapper.shouldStayAwakeOnFold();
     }
 
     private boolean areAllTransitioningDisplaysOffLocked() {
diff --git a/services/core/java/com/android/server/display/NormalBrightnessModeController.java b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
index bd5ed23..135ebd8 100644
--- a/services/core/java/com/android/server/display/NormalBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/NormalBrightnessModeController.java
@@ -42,14 +42,6 @@
         return recalculateMaxBrightness();
     }
 
-    void dump(PrintWriter pw) {
-        pw.println("NormalBrightnessModeController:");
-        pw.println("  mAutoBrightnessEnabled=" + mAutoBrightnessEnabled);
-        pw.println("  mAmbientLux=" + mAmbientLux);
-        pw.println("  mMaxBrightness=" + mMaxBrightness);
-        pw.println("  mMaxBrightnessLimits=" + mMaxBrightnessLimits);
-    }
-
     boolean setAutoBrightnessState(int state) {
         boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
         if (isEnabled != mAutoBrightnessEnabled) {
@@ -69,6 +61,14 @@
         return recalculateMaxBrightness();
     }
 
+    void dump(PrintWriter pw) {
+        pw.println("NormalBrightnessModeController:");
+        pw.println("  mAutoBrightnessEnabled=" + mAutoBrightnessEnabled);
+        pw.println("  mAmbientLux=" + mAmbientLux);
+        pw.println("  mMaxBrightness=" + mMaxBrightness);
+        pw.println("  mMaxBrightnessLimits=" + mMaxBrightnessLimits);
+    }
+
     private boolean recalculateMaxBrightness() {
         float foundAmbientBoundary = Float.MAX_VALUE;
         float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index 6d6ed72..2d7792d 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -133,6 +133,7 @@
 
     private static final String TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY =
             "brightness-nits-for-default-display";
+    public static final int DEFAULT_USER_ID = -1;
 
     // Remembered Wifi display devices.
     private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
@@ -294,7 +295,7 @@
         return false;
     }
 
-    public float getBrightness(DisplayDevice device) {
+    public float getBrightness(DisplayDevice device, int userSerial) {
         if (device == null || !device.hasStableUniqueId()) {
             return Float.NaN;
         }
@@ -302,10 +303,10 @@
         if (state == null) {
             return Float.NaN;
         }
-        return state.getBrightness();
+        return state.getBrightness(userSerial);
     }
 
-    public boolean setBrightness(DisplayDevice displayDevice, float brightness) {
+    public boolean setBrightness(DisplayDevice displayDevice, float brightness, int userSerial) {
         if (displayDevice == null || !displayDevice.hasStableUniqueId()) {
             return false;
         }
@@ -314,7 +315,7 @@
             return false;
         }
         final DisplayState state = getDisplayState(displayDeviceUniqueId, true);
-        if (state.setBrightness(brightness)) {
+        if (state.setBrightness(brightness, userSerial)) {
             setDirty();
             return true;
         }
@@ -611,6 +612,7 @@
             state.saveToXml(serializer);
             serializer.endTag(null, TAG_DISPLAY);
         }
+
         serializer.endTag(null, TAG_DISPLAY_STATES);
         serializer.startTag(null, TAG_STABLE_DEVICE_VALUES);
         mStableDeviceValues.saveToXml(serializer);
@@ -649,7 +651,8 @@
 
     private static final class DisplayState {
         private int mColorMode;
-        private float mBrightness = Float.NaN;
+
+        private SparseArray<Float> mPerUserBrightness = new SparseArray<>();
         private int mWidth;
         private int mHeight;
         private float mRefreshRate;
@@ -670,16 +673,25 @@
             return mColorMode;
         }
 
-        public boolean setBrightness(float brightness) {
-            if (brightness == mBrightness) {
+        public boolean setBrightness(float brightness, int userSerial) {
+            // Remove the default user brightness, before setting a new user-specific value.
+            // This is a one-time operation, required to restructure the config after user-specific
+            // brightness was introduced.
+            mPerUserBrightness.remove(DEFAULT_USER_ID);
+
+            if (getBrightness(userSerial) == brightness) {
                 return false;
             }
-            mBrightness = brightness;
+            mPerUserBrightness.set(userSerial, brightness);
             return true;
         }
 
-        public float getBrightness() {
-            return mBrightness;
+        public float getBrightness(int userSerial) {
+            float brightness = mPerUserBrightness.get(userSerial, Float.NaN);
+            if (Float.isNaN(brightness)) {
+                brightness = mPerUserBrightness.get(DEFAULT_USER_ID, Float.NaN);
+            }
+            return brightness;
         }
 
         public boolean setBrightnessConfiguration(BrightnessConfiguration configuration,
@@ -729,12 +741,7 @@
                         mColorMode = Integer.parseInt(value);
                         break;
                     case TAG_BRIGHTNESS_VALUE:
-                        String brightness = parser.nextText();
-                        try {
-                            mBrightness = Float.parseFloat(brightness);
-                        } catch (NumberFormatException e) {
-                            mBrightness = Float.NaN;
-                        }
+                        loadBrightnessFromXml(parser);
                         break;
                     case TAG_BRIGHTNESS_CONFIGURATIONS:
                         mDisplayBrightnessConfigurations.loadFromXml(parser);
@@ -760,11 +767,12 @@
             serializer.text(Integer.toString(mColorMode));
             serializer.endTag(null, TAG_COLOR_MODE);
 
-            serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
-            if (!Float.isNaN(mBrightness)) {
-                serializer.text(Float.toString(mBrightness));
+            for (int i = 0; i < mPerUserBrightness.size(); i++) {
+                serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
+                serializer.attributeInt(null, ATTR_USER_SERIAL, mPerUserBrightness.keyAt(i));
+                serializer.text(Float.toString(mPerUserBrightness.valueAt(i)));
+                serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
             }
-            serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
 
             serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
             mDisplayBrightnessConfigurations.saveToXml(serializer);
@@ -785,12 +793,33 @@
 
         public void dump(final PrintWriter pw, final String prefix) {
             pw.println(prefix + "ColorMode=" + mColorMode);
-            pw.println(prefix + "BrightnessValue=" + mBrightness);
+            pw.println(prefix + "BrightnessValues: ");
+            for (int i = 0; i < mPerUserBrightness.size(); i++) {
+                pw.println("User: " + mPerUserBrightness.keyAt(i)
+                        + " Value: " + mPerUserBrightness.valueAt(i));
+            }
             pw.println(prefix + "DisplayBrightnessConfigurations: ");
             mDisplayBrightnessConfigurations.dump(pw, prefix);
             pw.println(prefix + "Resolution=" + mWidth + " " + mHeight);
             pw.println(prefix + "RefreshRate=" + mRefreshRate);
         }
+
+        private void loadBrightnessFromXml(TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            int userSerial;
+            try {
+                userSerial = parser.getAttributeInt(null, ATTR_USER_SERIAL);
+            } catch (NumberFormatException | XmlPullParserException e) {
+                userSerial = DEFAULT_USER_ID;
+                Slog.e(TAG, "Failed to read user serial", e);
+            }
+            String brightness = parser.nextText();
+            try {
+                mPerUserBrightness.set(userSerial, Float.parseFloat(brightness));
+            } catch (NumberFormatException nfe) {
+                Slog.e(TAG, "Failed to read brightness", nfe);
+            }
+        }
     }
 
     private static final class StableDeviceValues {
diff --git a/services/core/java/com/android/server/display/TEST_MAPPING b/services/core/java/com/android/server/display/TEST_MAPPING
index c4a566f..5e4e270 100644
--- a/services/core/java/com/android/server/display/TEST_MAPPING
+++ b/services/core/java/com/android/server/display/TEST_MAPPING
@@ -1,15 +1,7 @@
 {
     "presubmit": [
         {
-            "name": "FrameworksMockingServicesTests",
-            "options": [
-                {"include-filter": "com.android.server.display"},
-                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-                {"exclude-annotation": "androidx.test.filters.FlakyTest"}
-            ]
-        },
-        {
-            "name": "FrameworksServicesTests",
+            "name": "DisplayServiceTests",
             "options": [
                 {"include-filter": "com.android.server.display"},
                 {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
@@ -31,5 +23,14 @@
                 }
             ]
         }
+    ],
+    "postsubmit": [
+        {
+            "name": "DisplayServiceTests",
+            "options": [
+                {"include-filter": "com.android.server.display"},
+                {"exclude-annotation": "org.junit.Ignore"}
+            ]
+        }
     ]
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 4f7a2ba..9f480b6 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -141,9 +141,12 @@
         try {
             if (projection != null) {
                 projection.registerCallback(mediaProjectionCallback);
+                Slog.d(TAG, "Virtual Display: registered media projection callback for new "
+                        + "VirtualDisplayDevice");
             }
             appToken.linkToDeath(device, 0);
         } catch (RemoteException ex) {
+            Slog.e(TAG, "Virtual Display: error while setting up VirtualDisplayDevice", ex);
             mVirtualDisplayDevices.remove(appToken);
             device.destroyLocked(false);
             return null;
@@ -439,6 +442,7 @@
         }
 
         public void stopLocked() {
+            Slog.d(TAG, "Virtual Display: stopping device " + mName);
             setSurfaceLocked(null);
             mStopped = true;
         }
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessUtils.java b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
index 3fae224..8bf675c 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
@@ -54,6 +54,16 @@
     public static DisplayBrightnessState constructDisplayBrightnessState(
             int brightnessChangeReason, float brightness, float sdrBrightness,
             String displayBrightnessStrategyName) {
+        return constructDisplayBrightnessState(brightnessChangeReason, brightness, sdrBrightness,
+                displayBrightnessStrategyName, /* slowChange= */ false);
+    }
+
+    /**
+     * A utility to construct the DisplayBrightnessState
+     */
+    public static DisplayBrightnessState constructDisplayBrightnessState(
+            int brightnessChangeReason, float brightness, float sdrBrightness,
+            String displayBrightnessStrategyName, boolean slowChange) {
         BrightnessReason brightnessReason = new BrightnessReason();
         brightnessReason.setReason(brightnessChangeReason);
         return new DisplayBrightnessState.Builder()
@@ -61,6 +71,7 @@
                 .setSdrBrightness(sdrBrightness)
                 .setBrightnessReason(brightnessReason)
                 .setDisplayBrightnessStrategyName(displayBrightnessStrategyName)
+                .setIsSlowChange(slowChange)
                 .build();
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 7574de8..d6f0098 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -29,6 +29,7 @@
 import com.android.server.display.AutomaticBrightnessController;
 import com.android.server.display.BrightnessSetting;
 import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
 
 import java.io.PrintWriter;
@@ -38,6 +39,8 @@
  * display. Applies the chosen brightness.
  */
 public final class DisplayBrightnessController {
+    private static final int DEFAULT_USER_SERIAL = -1;
+
     // The ID of the display tied to this DisplayBrightnessController
     private final int mDisplayId;
 
@@ -132,11 +135,21 @@
     public DisplayBrightnessState updateBrightness(
             DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
             int targetDisplayState) {
+
+        DisplayBrightnessState state;
         synchronized (mLock) {
             mDisplayBrightnessStrategy = mDisplayBrightnessStrategySelector.selectStrategy(
                     displayPowerRequest, targetDisplayState);
-            return mDisplayBrightnessStrategy.updateBrightness(displayPowerRequest);
+            state = mDisplayBrightnessStrategy.updateBrightness(displayPowerRequest);
         }
+
+        // This is a temporary measure until AutomaticBrightnessStrategy works as a traditional
+        // strategy.
+        // TODO: Remove when AutomaticBrightnessStrategy is populating the values directly.
+        if (state != null) {
+            state = addAutomaticBrightnessState(state);
+        }
+        return state;
     }
 
     /**
@@ -151,10 +164,10 @@
     /**
      * Sets the brightness to follow
      */
-    public void setBrightnessToFollow(Float brightnessToFollow) {
+    public void setBrightnessToFollow(float brightnessToFollow, boolean slowChange) {
         synchronized (mLock) {
             mDisplayBrightnessStrategySelector.getFollowerDisplayBrightnessStrategy()
-                    .setBrightnessToFollow(brightnessToFollow);
+                    .setBrightnessToFollow(brightnessToFollow, slowChange);
         }
     }
 
@@ -274,8 +287,16 @@
      * Notifies the brightnessSetting to persist the supplied brightness value.
      */
     public void setBrightness(float brightnessValue) {
+        setBrightness(brightnessValue, DEFAULT_USER_SERIAL);
+    }
+
+    /**
+     * Notifies the brightnessSetting to persist the supplied brightness value for a user.
+     */
+    public void setBrightness(float brightnessValue, int userSerial) {
         // Update the setting, which will eventually call back into DPC to have us actually
         // update the display with the new value.
+        mBrightnessSetting.setUserSerial(userSerial);
         mBrightnessSetting.setBrightness(brightnessValue);
         if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
             float nits = convertToNits(brightnessValue);
@@ -312,6 +333,13 @@
     }
 
     /**
+     * TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy.
+     */
+    public AutomaticBrightnessStrategy getAutomaticBrightnessStrategy() {
+        return mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy();
+    }
+
+    /**
      * Convert a brightness float scale value to a nit value. Adjustments, such as RBC, are not
      * applied. This is used when storing the brightness in nits for the default display and when
      * passing the brightness value to follower displays.
@@ -415,6 +443,18 @@
         }
     }
 
+    /**
+     * TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy.
+     */
+    private DisplayBrightnessState addAutomaticBrightnessState(DisplayBrightnessState state) {
+        AutomaticBrightnessStrategy autoStrat = getAutomaticBrightnessStrategy();
+
+        DisplayBrightnessState.Builder builder = DisplayBrightnessState.Builder.from(state);
+        builder.setShouldUseAutoBrightness(
+                autoStrat != null && autoStrat.shouldUseAutoBrightness());
+        return builder.build();
+    }
+
     @GuardedBy("mLock")
     private void setTemporaryBrightnessLocked(float temporaryBrightness) {
         mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 02ca2d3..45f1be0 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -25,6 +25,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
 import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
 import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
@@ -60,6 +61,8 @@
     private final FollowerBrightnessStrategy mFollowerBrightnessStrategy;
     // The brightness strategy used to manage the brightness state when the request is invalid.
     private final InvalidBrightnessStrategy mInvalidBrightnessStrategy;
+    // Controls brightness when automatic (adaptive) brightness is running.
+    private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
 
     // We take note of the old brightness strategy so that we can know when the strategy changes.
     private String mOldBrightnessStrategyName;
@@ -81,6 +84,7 @@
         mBoostBrightnessStrategy = injector.getBoostBrightnessStrategy();
         mFollowerBrightnessStrategy = injector.getFollowerBrightnessStrategy(displayId);
         mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
+        mAutomaticBrightnessStrategy = injector.getAutomaticBrightnessStrategy(context, displayId);
         mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
                 R.bool.config_allowAutoBrightnessWhileDozing);
         mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
@@ -130,6 +134,10 @@
         return mFollowerBrightnessStrategy;
     }
 
+    public AutomaticBrightnessStrategy getAutomaticBrightnessStrategy() {
+        return mAutomaticBrightnessStrategy;
+    }
+
     /**
      * Returns a boolean flag indicating if the light sensor is to be used to decide the screen
      * brightness when dozing
@@ -198,5 +206,9 @@
         InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
             return new InvalidBrightnessStrategy();
         }
+
+        AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context, int displayId) {
+            return new AutomaticBrightnessStrategy(context, displayId);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
new file mode 100644
index 0000000..54a280f
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import android.annotation.NonNull;
+import android.os.PowerManager;
+
+import java.io.PrintWriter;
+
+/**
+ * Provides max allowed brightness
+ */
+abstract class BrightnessClamper<T> {
+
+    protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+    protected boolean mIsActive = false;
+
+    float getBrightnessCap() {
+        return mBrightnessCap;
+    }
+
+    boolean isActive() {
+        return mIsActive;
+    }
+
+    void dump(PrintWriter writer) {
+        writer.println("BrightnessClamper:" + getType());
+        writer.println(" mBrightnessCap: " + mBrightnessCap);
+        writer.println(" mIsActive: " + mIsActive);
+    }
+
+    @NonNull
+    abstract Type getType();
+
+    abstract void onDeviceConfigChanged();
+
+    abstract void onDisplayChanged(T displayData);
+
+    abstract void stop();
+
+    enum Type {
+        THERMAL
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
new file mode 100644
index 0000000..f19d00b
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static com.android.server.display.brightness.clamper.BrightnessClamper.Type;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.PowerManager;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Clampers controller, all in DisplayControllerHandler
+ */
+public class BrightnessClamperController {
+
+    private static final boolean THERMAL_ENABLED = false;
+
+    private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
+    private final Handler mHandler;
+    private final ClamperChangeListener mClamperChangeListenerExternal;
+
+    private final Executor mExecutor;
+    private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers = new ArrayList<>();
+
+    private final List<BrightnessModifier> mModifiers = new ArrayList<>();
+    private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
+            properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
+    private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+    @Nullable
+    private Type mClamperType = null;
+
+    public BrightnessClamperController(Handler handler,
+            ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
+        this(new Injector(), handler, clamperChangeListener, data);
+    }
+
+    @VisibleForTesting
+    BrightnessClamperController(Injector injector, Handler handler,
+            ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
+        mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+        mHandler = handler;
+        mClamperChangeListenerExternal = clamperChangeListener;
+        mExecutor = new HandlerExecutor(handler);
+
+        Runnable clamperChangeRunnableInternal = this::recalculateBrightnessCap;
+
+        ClamperChangeListener clamperChangeListenerInternal = () -> {
+            if (!mHandler.hasCallbacks(clamperChangeRunnableInternal)) {
+                mHandler.post(clamperChangeRunnableInternal);
+            }
+        };
+
+        if (THERMAL_ENABLED) {
+            mClampers.add(
+                    new BrightnessThermalClamper(handler, clamperChangeListenerInternal, data));
+        }
+        mModifiers.add(new BrightnessLowPowerModeModifier());
+        start();
+    }
+
+    /**
+     * Should be called when display changed. Forwards the call to individual clampers
+     */
+    public void onDisplayChanged(DisplayDeviceData data) {
+        mClampers.forEach(clamper -> clamper.onDisplayChanged(data));
+    }
+
+    /**
+     * Applies clamping
+     * Called in DisplayControllerHandler
+     */
+    public DisplayBrightnessState clamp(DisplayManagerInternal.DisplayPowerRequest request,
+            float brightnessValue, boolean slowChange) {
+        float cappedBrightness = Math.min(brightnessValue, mBrightnessCap);
+
+        DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
+        builder.setIsSlowChange(slowChange);
+        builder.setBrightness(cappedBrightness);
+
+        for (int i = 0; i < mModifiers.size(); i++) {
+            mModifiers.get(i).apply(request, builder);
+        }
+        return builder.build();
+    }
+
+    /**
+     * Used to dump ClampersController state.
+     */
+    public void dump(PrintWriter writer) {
+        writer.println("BrightnessClampersController:");
+        writer.println("  mBrightnessCap: " + mBrightnessCap);
+        writer.println("  mClamperType: " + mClamperType);
+        IndentingPrintWriter ipw = new IndentingPrintWriter(writer, "    ");
+        mClampers.forEach(clamper -> clamper.dump(ipw));
+        mModifiers.forEach(modifier -> modifier.dump(ipw));
+    }
+
+    /**
+     * This method should be called when the ClamperController is no longer in use.
+     * Called in DisplayControllerHandler
+     */
+    public void stop() {
+        mDeviceConfigParameterProvider.removeOnPropertiesChangedListener(
+                mOnPropertiesChangedListener);
+        mClampers.forEach(BrightnessClamper::stop);
+    }
+
+
+    // Called in DisplayControllerHandler
+    private void recalculateBrightnessCap() {
+        float brightnessCap = PowerManager.BRIGHTNESS_MAX;
+        Type clamperType = null;
+
+        BrightnessClamper<?> minClamper = mClampers.stream()
+                .filter(BrightnessClamper::isActive)
+                .min((clamper1, clamper2) -> Float.compare(clamper1.getBrightnessCap(),
+                        clamper2.getBrightnessCap())).orElse(null);
+
+        if (minClamper != null) {
+            brightnessCap = minClamper.getBrightnessCap();
+            clamperType = minClamper.getType();
+        }
+
+        if (mBrightnessCap != brightnessCap || mClamperType != clamperType) {
+            mBrightnessCap = brightnessCap;
+            mClamperType = clamperType;
+            mClamperChangeListenerExternal.onChanged();
+        }
+    }
+
+    private void start() {
+        if (!mClampers.isEmpty()) {
+            mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
+                    mExecutor, mOnPropertiesChangedListener);
+        }
+    }
+
+    /**
+     * Clampers change listener
+     */
+    public interface ClamperChangeListener {
+        /**
+         * Notifies that clamper state changed
+         */
+        void onChanged();
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+            return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
+        }
+    }
+
+    /**
+     * Data for clampers
+     */
+    public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData {
+        @NonNull
+        private final String mUniqueDisplayId;
+        @NonNull
+        private final String mThermalThrottlingDataId;
+
+        private final DisplayDeviceConfig mDisplayDeviceConfig;
+
+        public DisplayDeviceData(@NonNull String uniqueDisplayId,
+                @NonNull String thermalThrottlingDataId,
+                @NonNull DisplayDeviceConfig displayDeviceConfig) {
+            mUniqueDisplayId = uniqueDisplayId;
+            mThermalThrottlingDataId = thermalThrottlingDataId;
+            mDisplayDeviceConfig = displayDeviceConfig;
+        }
+
+
+        @NonNull
+        @Override
+        public String getUniqueDisplayId() {
+            return mUniqueDisplayId;
+        }
+
+        @NonNull
+        @Override
+        public String getThermalThrottlingDataId() {
+            return mThermalThrottlingDataId;
+        }
+
+        @Nullable
+        @Override
+        public ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData() {
+            return mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId().get(
+                    mThermalThrottlingDataId);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
new file mode 100644
index 0000000..f48ad2f
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import java.io.PrintWriter;
+
+class BrightnessLowPowerModeModifier implements BrightnessModifier {
+
+    private boolean mAppliedLowPower = false;
+
+    @Override
+    public void apply(DisplayManagerInternal.DisplayPowerRequest request,
+            DisplayBrightnessState.Builder stateBuilder) {
+        // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
+        // as long as it is above the minimum threshold.
+        if (request.lowPowerMode) {
+            float value = stateBuilder.getBrightness();
+            if (value > PowerManager.BRIGHTNESS_MIN) {
+                final float brightnessFactor =
+                        Math.min(request.screenLowPowerBrightnessFactor, 1);
+                final float lowPowerBrightnessFloat = Math.max((value * brightnessFactor),
+                        PowerManager.BRIGHTNESS_MIN);
+                stateBuilder.setBrightness(lowPowerBrightnessFloat);
+                stateBuilder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_LOW_POWER);
+            }
+            if (!mAppliedLowPower) {
+                stateBuilder.setIsSlowChange(false);
+            }
+            mAppliedLowPower = true;
+        } else if (mAppliedLowPower) {
+            stateBuilder.setIsSlowChange(false);
+            mAppliedLowPower = false;
+        }
+    }
+
+    @Override
+    public void dump(PrintWriter pw) {
+        pw.println("BrightnessLowPowerModeModifier:");
+        pw.println("  mAppliedLowPower=" + mAppliedLowPower);
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
new file mode 100644
index 0000000..3a33df6
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import android.hardware.display.DisplayManagerInternal;
+
+import com.android.server.display.DisplayBrightnessState;
+
+import java.io.PrintWriter;
+
+/**
+ * Modifies current brightness based on request
+ */
+interface BrightnessModifier {
+
+    void apply(DisplayManagerInternal.DisplayPowerRequest request,
+            DisplayBrightnessState.Builder builder);
+
+    void dump(PrintWriter pw);
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
new file mode 100644
index 0000000..8ae962b
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static  com.android.server.display.DisplayDeviceConfig.DEFAULT_ID;
+import static com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Temperature;
+import android.provider.DeviceConfigInterface;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.utils.DeviceConfigParsingUtils;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+
+class BrightnessThermalClamper extends
+        BrightnessClamper<BrightnessThermalClamper.ThermalData> {
+
+    private static final String TAG = "BrightnessThermalClamper";
+
+    @Nullable
+    private final IThermalService mThermalService;
+    @NonNull
+    private final DeviceConfigParameterProvider mConfigParameterProvider;
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final ClamperChangeListener mChangelistener;
+    // data from DeviceConfig, for all displays, for all dataSets
+    // mapOf(uniqueDisplayId to mapOf(dataSetId to ThermalBrightnessThrottlingData))
+    @NonNull
+    private Map<String, Map<String, ThermalBrightnessThrottlingData>>
+            mThermalThrottlingDataOverride = Map.of();
+    // data from DisplayDeviceConfig, for particular display+dataSet
+    @Nullable
+    private ThermalBrightnessThrottlingData mThermalThrottlingDataFromDeviceConfig = null;
+    // Active data, if mDataOverride contains data for mUniqueDisplayId, mDataId, then use it,
+    // otherwise mDataFromDeviceConfig
+    @Nullable
+    private ThermalBrightnessThrottlingData mThermalThrottlingDataActive = null;
+    private boolean mStarted = false;
+    @Nullable
+    private String mUniqueDisplayId = null;
+    @Nullable
+    private String mDataId = null;
+    @Temperature.ThrottlingStatus
+    private int mThrottlingStatus = Temperature.THROTTLING_NONE;
+
+    private final IThermalEventListener mThermalEventListener = new IThermalEventListener.Stub() {
+        @Override
+        public void notifyThrottling(Temperature temperature) {
+            @Temperature.ThrottlingStatus int status = temperature.getStatus();
+            mHandler.post(() -> thermalStatusChanged(status));
+        }
+    };
+
+    private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
+        try {
+            int status = DeviceConfigParsingUtils.parseThermalStatus(key);
+            float brightnessPoint = DeviceConfigParsingUtils.parseBrightness(value);
+            return new ThrottlingLevel(status, brightnessPoint);
+        } catch (IllegalArgumentException iae) {
+            return null;
+        }
+    };
+
+    private final Function<List<ThrottlingLevel>, ThermalBrightnessThrottlingData>
+            mDataSetMapper = ThermalBrightnessThrottlingData::create;
+
+
+    BrightnessThermalClamper(Handler handler, ClamperChangeListener listener,
+            ThermalData thermalData) {
+        this(new Injector(), handler, listener, thermalData);
+    }
+
+    @VisibleForTesting
+    BrightnessThermalClamper(Injector injector, Handler handler,
+            ClamperChangeListener listener, ThermalData thermalData) {
+        mThermalService = injector.getThermalService();
+        mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+        mHandler = handler;
+        mChangelistener = listener;
+        mHandler.post(() -> {
+            setDisplayData(thermalData);
+            loadOverrideData();
+            start();
+        });
+
+    }
+
+    @Override
+    @NonNull
+    Type getType() {
+        return Type.THERMAL;
+    }
+
+    @Override
+    void onDeviceConfigChanged() {
+        mHandler.post(() -> {
+            loadOverrideData();
+            recalculateActiveData();
+        });
+    }
+
+    @Override
+    void onDisplayChanged(ThermalData data) {
+        mHandler.post(() -> {
+            setDisplayData(data);
+            recalculateActiveData();
+        });
+    }
+
+    @Override
+    void stop() {
+        if (!mStarted) {
+            return;
+        }
+        try {
+            mThermalService.unregisterThermalEventListener(mThermalEventListener);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to unregister thermal status listener", e);
+        }
+        mStarted = false;
+    }
+
+    @Override
+    void dump(PrintWriter writer) {
+        writer.println("BrightnessThermalClamper:");
+        writer.println("  mStarted: " + mStarted);
+        if (mThermalService != null) {
+            writer.println("  ThermalService available");
+        } else {
+            writer.println("  ThermalService not available");
+        }
+        writer.println("  mThrottlingStatus: " + mThrottlingStatus);
+        writer.println("  mUniqueDisplayId: " + mUniqueDisplayId);
+        writer.println("  mDataId: " + mDataId);
+        writer.println("  mDataOverride: " + mThermalThrottlingDataOverride);
+        writer.println("  mDataFromDeviceConfig: " + mThermalThrottlingDataFromDeviceConfig);
+        writer.println("  mDataActive: " + mThermalThrottlingDataActive);
+        super.dump(writer);
+    }
+
+    private void recalculateActiveData() {
+        if (mUniqueDisplayId == null || mDataId == null) {
+            return;
+        }
+        mThermalThrottlingDataActive = mThermalThrottlingDataOverride
+                .getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId,
+                        mThermalThrottlingDataFromDeviceConfig);
+
+        recalculateBrightnessCap();
+    }
+
+    private void loadOverrideData() {
+        String throttlingDataOverride = mConfigParameterProvider.getBrightnessThrottlingData();
+        mThermalThrottlingDataOverride = DeviceConfigParsingUtils.parseDeviceConfigMap(
+                throttlingDataOverride, mDataPointMapper, mDataSetMapper);
+    }
+
+    private void setDisplayData(@NonNull ThermalData data) {
+        mUniqueDisplayId = data.getUniqueDisplayId();
+        mDataId = data.getThermalThrottlingDataId();
+        mThermalThrottlingDataFromDeviceConfig = data.getThermalBrightnessThrottlingData();
+        if (mThermalThrottlingDataFromDeviceConfig == null && !DEFAULT_ID.equals(mDataId)) {
+            Slog.wtf(TAG,
+                    "Thermal throttling data is missing for thermalThrottlingDataId=" + mDataId);
+        }
+    }
+
+    private void recalculateBrightnessCap() {
+        float brightnessCap = PowerManager.BRIGHTNESS_MAX;
+        boolean isActive = false;
+
+        if (mThermalThrottlingDataActive != null) {
+            // Throttling levels are sorted by increasing severity
+            for (ThrottlingLevel level : mThermalThrottlingDataActive.throttlingLevels) {
+                if (level.thermalStatus <= mThrottlingStatus) {
+                    brightnessCap = level.brightness;
+                    isActive = true;
+                } else {
+                    // Throttling levels that are greater than the current status are irrelevant
+                    break;
+                }
+            }
+        }
+
+        if (brightnessCap  != mBrightnessCap || mIsActive != isActive) {
+            mBrightnessCap = brightnessCap;
+            mIsActive = isActive;
+            mChangelistener.onChanged();
+        }
+    }
+
+    private void thermalStatusChanged(@Temperature.ThrottlingStatus int status) {
+        if (mThrottlingStatus != status) {
+            mThrottlingStatus = status;
+            recalculateBrightnessCap();
+        }
+    }
+
+    private void start() {
+        if (mThermalService == null) {
+            Slog.e(TAG, "Could not observe thermal status. Service not available");
+            return;
+        }
+        try {
+            // We get a callback immediately upon registering so there's no need to query
+            // for the current value.
+            mThermalService.registerThermalEventListenerWithType(mThermalEventListener,
+                    Temperature.TYPE_SKIN);
+            mStarted = true;
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to register thermal status listener", e);
+        }
+    }
+
+    interface ThermalData {
+        @NonNull
+        String getUniqueDisplayId();
+
+        @NonNull
+        String getThermalThrottlingDataId();
+
+        @Nullable
+        ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData();
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        IThermalService getThermalService() {
+            return IThermalService.Stub.asInterface(
+                    ServiceManager.getService(Context.THERMAL_SERVICE));
+        }
+
+        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+            return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 0e885dc..bcd5259 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -73,6 +73,9 @@
     // the user has enabled the auto-brightness from the settings, it is disabled because the
     // display is off
     private boolean mIsAutoBrightnessEnabled = false;
+    // Indicates if auto-brightness is disabled due to the display being off. Needed for metric
+    // purposes.
+    private boolean mAutoBrightnessDisabledDueToDisplayOff;
     // If the auto-brightness model for the last manual changes done by the user.
     private boolean mIsShortTermModelActive = false;
 
@@ -96,24 +99,21 @@
      * AutomaticBrightnessController accounting for any manual changes made by the user.
      */
     public void setAutoBrightnessState(int targetDisplayState,
-            boolean allowAutoBrightnessWhileDozingConfig,
-            float brightnessState, int brightnessReason, int policy,
+            boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy,
             float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) {
         final boolean autoBrightnessEnabledInDoze =
                 allowAutoBrightnessWhileDozingConfig
                         && Display.isDozeState(targetDisplayState);
         mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
                 && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
-                && (Float.isNaN(brightnessState)
-                || brightnessReason == BrightnessReason.REASON_TEMPORARY
-                || brightnessReason == BrightnessReason.REASON_BOOST)
-                && mAutomaticBrightnessController != null
-                && brightnessReason != BrightnessReason.REASON_FOLLOWER;
-        final boolean autoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
+                && brightnessReason != BrightnessReason.REASON_OVERRIDE
+                && mAutomaticBrightnessController != null;
+        mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
                 && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
         final int autoBrightnessState = mIsAutoBrightnessEnabled
+                    && brightnessReason != BrightnessReason.REASON_FOLLOWER
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
-                : autoBrightnessDisabledDueToDisplayOff
+                : mAutoBrightnessDisabledDueToDisplayOff
                         ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
                         : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
 
@@ -125,6 +125,10 @@
         return mIsAutoBrightnessEnabled;
     }
 
+    public boolean isAutoBrightnessDisabledDueToDisplayOff() {
+        return mAutoBrightnessDisabledDueToDisplayOff;
+    }
+
     /**
      * Updates the {@link BrightnessConfiguration} that is currently being used by the associated
      * display.
@@ -287,8 +291,6 @@
         mAutoBrightnessAdjustmentReasonsFlags = isTemporaryAutoBrightnessAdjustmentApplied()
                 ? BrightnessReason.ADJUSTMENT_AUTO_TEMP
                 : BrightnessReason.ADJUSTMENT_AUTO;
-        mAppliedAutoBrightness = BrightnessUtils.isValidBrightnessValue(brightnessState)
-                || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT;
         float newAutoBrightnessAdjustment =
                 (mAutomaticBrightnessController != null)
                         ? mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()
@@ -345,8 +347,7 @@
     /**
      * Sets if the auto-brightness is applied on the latest brightness change.
      */
-    @VisibleForTesting
-    void setAutoBrightnessApplied(boolean autoBrightnessApplied) {
+    public void setAutoBrightnessApplied(boolean autoBrightnessApplied) {
         mAppliedAutoBrightness = autoBrightnessApplied;
     }
 
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
index 090ec13..585f576 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
@@ -37,9 +37,13 @@
     // Set to PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no brightness to follow set.
     private float mBrightnessToFollow;
 
+    // Indicates whether we should ramp slowly to the brightness value to follow.
+    private boolean mBrightnessToFollowSlowChange;
+
     public FollowerBrightnessStrategy(int displayId) {
         mDisplayId = displayId;
         mBrightnessToFollow = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mBrightnessToFollowSlowChange = false;
     }
 
     @Override
@@ -48,7 +52,7 @@
         // Todo(b/241308599): Introduce a validator class and add validations before setting
         // the brightness
         return BrightnessUtils.constructDisplayBrightnessState(BrightnessReason.REASON_FOLLOWER,
-                mBrightnessToFollow, mBrightnessToFollow, getName());
+                mBrightnessToFollow, mBrightnessToFollow, getName(), mBrightnessToFollowSlowChange);
     }
 
     @Override
@@ -60,8 +64,12 @@
         return mBrightnessToFollow;
     }
 
-    public void setBrightnessToFollow(float brightnessToFollow) {
+    /**
+     * Updates brightness value and brightness slowChange flag
+     **/
+    public void setBrightnessToFollow(float brightnessToFollow, boolean slowChange) {
         mBrightnessToFollow = brightnessToFollow;
+        mBrightnessToFollowSlowChange = slowChange;
     }
 
     /**
@@ -71,5 +79,6 @@
         writer.println("FollowerBrightnessStrategy:");
         writer.println("  mDisplayId=" + mDisplayId);
         writer.println("  mBrightnessToFollow:" + mBrightnessToFollow);
+        writer.println("  mBrightnessToFollowSlowChange:" + mBrightnessToFollowSlowChange);
     }
 }
diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
index 0310383..23ffe59 100644
--- a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
+++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java
@@ -16,9 +16,14 @@
 
 package com.android.server.display.feature;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.hardware.display.DisplayManager;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfigInterface;
+import android.util.Slog;
+
+import com.android.server.display.utils.DeviceConfigParsingUtils;
 
 import java.util.concurrent.Executor;
 
@@ -36,15 +41,167 @@
         mDeviceConfig = deviceConfig;
     }
 
+    // feature: revamping_display_power_controller_feature
+    // parameter: use_newly_structured_display_power_controller
+    public boolean isNewPowerControllerFeatureEnabled() {
+        return mDeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_NEW_POWER_CONTROLLER, true);
+    }
+
+    // feature: hdr_output_control
+    // parameter: enable_hdr_output_control
+    public boolean isHdrOutputControlFeatureEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.HDR_OUTPUT_CONTROL_FLAG, true);
+    }
+
+    // feature: flexible_brightness_range_feature
+    // parameter: normal_brightness_mode_controller_enabled
+    public boolean isNormalBrightnessControllerFeatureEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER, false);
+    }
+
     public boolean isDisableScreenWakeLocksWhileCachedFeatureEnabled() {
         return mDeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                 DisplayManager.DeviceConfig.KEY_DISABLE_SCREEN_WAKE_LOCKS_WHILE_CACHED, true);
     }
 
+    // feature: smooth_display_feature
+    // parameter: peak_refresh_rate_default
+    public float getPeakRefreshRateDefault() {
+        return mDeviceConfig.getFloat(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1);
+    }
+
+    // Test parameters
+    // usage e.g.: adb shell device_config put display_manager refresh_rate_in_hbm_sunlight 90
+
+    // allows to customize brightness throttling data
+    public String getBrightnessThrottlingData() {
+        return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, null);
+    }
+
+    public int getRefreshRateInHbmSunlight() {
+        return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, -1);
+    }
+
+    public int getRefreshRateInHbmHdr() {
+        return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR, -1);
+    }
+
+
+    public int getRefreshRateInHighZone() {
+        return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE, -1);
+    }
+
+    public int getRefreshRateInLowZone() {
+        return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1);
+    }
+
+    /**
+     * Get the high ambient brightness thresholds for the configured refresh rate zone. The values
+     * are paired with brightness thresholds.
+     *
+     * A negative value means that only the display brightness threshold should be used.
+     *
+     * Return null if no such property or wrong format (not comma separated integers).
+     */
+    @Nullable
+    public float[] getHighAmbientBrightnessThresholds() {
+        return DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat(
+                getIntArrayProperty(DisplayManager.DeviceConfig
+                        .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS));
+    }
+
+    /**
+     * Get the high display brightness thresholds for the configured refresh rate zone. The values
+     * are paired with lux thresholds.
+     *
+     * A negative value means that only the ambient threshold should be used.
+     *
+     * Return null if no such property or wrong format (not comma separated integers).
+     */
+    @Nullable
+    public float[] getHighDisplayBrightnessThresholds() {
+        return DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat(
+                getIntArrayProperty(DisplayManager.DeviceConfig
+                        .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS));
+    }
+
+    /**
+     * Get the low display brightness thresholds for the configured refresh rate zone. The values
+     * are paired with lux thresholds.
+     *
+     * A negative value means that only the ambient threshold should be used.
+     *
+     * Return null if no such property or wrong format (not comma separated integers).
+     */
+    @Nullable
+    public float[] getLowDisplayBrightnessThresholds() {
+        return DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat(
+                getIntArrayProperty(DisplayManager.DeviceConfig
+                        .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS));
+    }
+
+    /**
+     * Get the low ambient brightness thresholds for the configured refresh rate zone. The values
+     * are paired with brightness thresholds.
+     *
+     * A negative value means that only the display brightness threshold should be used.
+     *
+     * Return null if no such property or wrong format (not comma separated integers).
+     */
+    @Nullable
+    public float[] getLowAmbientBrightnessThresholds() {
+        return DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat(
+                getIntArrayProperty(DisplayManager.DeviceConfig
+                        .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS));
+    }
+
     /** add property change listener to DeviceConfig */
     public void addOnPropertiesChangedListener(Executor executor,
             DeviceConfig.OnPropertiesChangedListener listener) {
         mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                 executor, listener);
     }
+
+    /** remove property change listener from DeviceConfig */
+    public void removeOnPropertiesChangedListener(
+            DeviceConfig.OnPropertiesChangedListener listener) {
+        mDeviceConfig.removeOnPropertiesChangedListener(listener);
+    }
+
+    @Nullable
+    private int[] getIntArrayProperty(String prop) {
+        String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop,
+                null);
+
+        if (strArray != null) {
+            return parseIntArray(strArray);
+        }
+        return null;
+    }
+
+    @Nullable
+    private int[] parseIntArray(@NonNull String strArray) {
+        String[] items = strArray.split(",");
+        int[] array = new int[items.length];
+
+        try {
+            for (int i = 0; i < array.length; i++) {
+                array[i] = Integer.parseInt(items[i]);
+            }
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e);
+            array = null;
+        }
+
+        return array;
+    }
 }
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index b55d7d5..d9ec3de 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -22,6 +22,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.view.DisplayAddress;
 
@@ -77,7 +79,7 @@
             DisplayIdProducer idProducer) {
         createDisplayLocked(address, /* isDefault= */ true, /* isEnabled= */ true,
                 DEFAULT_DISPLAY_GROUP_NAME, idProducer, POSITION_UNKNOWN,
-                NO_LEAD_DISPLAY, /* brightnessThrottlingMapId= */ null,
+                /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null,
                 /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
     }
 
@@ -90,19 +92,20 @@
      * @param displayGroupName Name of the display group to which the display is assigned.
      * @param idProducer Produces the logical display id.
      * @param position Indicates the position this display is facing in this layout.
-     * @param leadDisplayId Display that this one follows (-1 if none).
+     * @param leadDisplayAddress Address of a display that this one follows ({@code null} if none).
      * @param brightnessThrottlingMapId Name of which brightness throttling policy should be used.
      * @param refreshRateZoneId Layout limited refresh rate zone name.
      * @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling
      *                                          policy should be used.
-
+     *
      * @exception IllegalArgumentException When a default display owns a display group other than
      *            DEFAULT_DISPLAY_GROUP.
      */
     public void createDisplayLocked(
             @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
-            String displayGroupName, DisplayIdProducer idProducer, int position, int leadDisplayId,
-            String brightnessThrottlingMapId, @Nullable String refreshRateZoneId,
+            String displayGroupName, DisplayIdProducer idProducer, int position,
+            @Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId,
+            @Nullable String refreshRateZoneId,
             @Nullable String refreshRateThermalThrottlingMapId) {
         if (contains(address)) {
             Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
@@ -115,21 +118,27 @@
             return;
         }
 
-        // Assign a logical display ID and create the new display.
-        // Note that the logical display ID is saved into the layout, so when switching between
-        // different layouts, a logical display can be destroyed and later recreated with the
-        // same logical display ID.
         if (displayGroupName == null) {
             displayGroupName = DEFAULT_DISPLAY_GROUP_NAME;
         }
         if (isDefault && !displayGroupName.equals(DEFAULT_DISPLAY_GROUP_NAME)) {
             throw new IllegalArgumentException("Default display should own DEFAULT_DISPLAY_GROUP");
         }
+        if (isDefault && leadDisplayAddress != null) {
+            throw new IllegalArgumentException("Default display cannot have a lead display");
+        }
+        if (address.equals(leadDisplayAddress)) {
+            throw new IllegalArgumentException("Lead display address cannot be the same as display "
+                    + " address");
+        }
+        // Assign a logical display ID and create the new display.
+        // Note that the logical display ID is saved into the layout, so when switching between
+        // different layouts, a logical display can be destroyed and later recreated with the
+        // same logical display ID.
         final int logicalDisplayId = idProducer.getId(isDefault);
-        leadDisplayId = isDefault ? NO_LEAD_DISPLAY : leadDisplayId;
 
         final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName,
-                brightnessThrottlingMapId, position, leadDisplayId, refreshRateZoneId,
+                brightnessThrottlingMapId, position, leadDisplayAddress, refreshRateZoneId,
                 refreshRateThermalThrottlingMapId);
 
         mDisplays.add(display);
@@ -146,6 +155,43 @@
     }
 
     /**
+     * Applies post-processing to displays to make sure the information of each display is
+     * up-to-date.
+     *
+     * <p>At creation of a display, lead display is specified by display address. At post
+     * processing, we convert it to logical display ID.
+     */
+    public void postProcessLocked() {
+        for (int i = 0; i < mDisplays.size(); i++) {
+            Display display = mDisplays.get(i);
+            if (display.getLogicalDisplayId() == DEFAULT_DISPLAY) {
+                display.setLeadDisplayId(NO_LEAD_DISPLAY);
+                continue;
+            }
+            DisplayAddress leadDisplayAddress = display.getLeadDisplayAddress();
+            if (leadDisplayAddress == null) {
+                display.setLeadDisplayId(NO_LEAD_DISPLAY);
+                continue;
+            }
+            Display leadDisplay = getByAddress(leadDisplayAddress);
+            if (leadDisplay == null) {
+                throw new IllegalArgumentException("Cannot find a lead display whose address is "
+                        + leadDisplayAddress);
+            }
+            if (!TextUtils.equals(display.getDisplayGroupName(),
+                    leadDisplay.getDisplayGroupName())) {
+                throw new IllegalArgumentException("Lead display(" + leadDisplay + ") should be in "
+                        + "the same display group of the display(" + display + ")");
+            }
+            if (hasCyclicLeadDisplay(display)) {
+                throw new IllegalArgumentException("Display(" + display + ") has a cyclic lead "
+                        + "display");
+            }
+            display.setLeadDisplayId(leadDisplay.getLogicalDisplayId());
+        }
+    }
+
+    /**
      * @param address The address to check.
      *
      * @return True if the specified address is used in this layout.
@@ -208,6 +254,20 @@
         return mDisplays.size();
     }
 
+    private boolean hasCyclicLeadDisplay(Display display) {
+        ArraySet<Display> visited = new ArraySet<>();
+
+        while (display != null) {
+            if (visited.contains(display)) {
+                return true;
+            }
+            visited.add(display);
+            DisplayAddress leadDisplayAddress = display.getLeadDisplayAddress();
+            display = leadDisplayAddress == null ? null : getByAddress(leadDisplayAddress);
+        }
+        return false;
+    }
+
     /**
      * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
      */
@@ -240,8 +300,9 @@
         @Nullable
         private final String mThermalBrightnessThrottlingMapId;
 
-        // The ID of the lead display that this display will follow in a layout. -1 means no lead.
-        private final int mLeadDisplayId;
+        // The address of the lead display that is specified in display-layout-configuration.
+        @Nullable
+        private final DisplayAddress mLeadDisplayAddress;
 
         // Refresh rate zone id for specific layout
         @Nullable
@@ -250,9 +311,13 @@
         @Nullable
         private final String mThermalRefreshRateThrottlingMapId;
 
+        // The ID of the lead display that this display will follow in a layout. -1 means no lead.
+        // This is determined using {@code mLeadDisplayAddress}.
+        private int mLeadDisplayId;
+
         private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,
                 @NonNull String displayGroupName, String brightnessThrottlingMapId, int position,
-                int leadDisplayId, @Nullable String refreshRateZoneId,
+                @Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId,
                 @Nullable String refreshRateThermalThrottlingMapId) {
             mAddress = address;
             mLogicalDisplayId = logicalDisplayId;
@@ -260,9 +325,10 @@
             mDisplayGroupName = displayGroupName;
             mPosition = position;
             mThermalBrightnessThrottlingMapId = brightnessThrottlingMapId;
+            mLeadDisplayAddress = leadDisplayAddress;
             mRefreshRateZoneId = refreshRateZoneId;
             mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId;
-            mLeadDisplayId = leadDisplayId;
+            mLeadDisplayId = NO_LEAD_DISPLAY;
         }
 
         @Override
@@ -276,6 +342,7 @@
                     + ", mThermalBrightnessThrottlingMapId: " + mThermalBrightnessThrottlingMapId
                     + ", mRefreshRateZoneId: " + mRefreshRateZoneId
                     + ", mLeadDisplayId: " + mLeadDisplayId
+                    + ", mLeadDisplayAddress: " + mLeadDisplayAddress
                     + ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId
                     + "}";
         }
@@ -297,6 +364,7 @@
                     otherDisplay.mThermalBrightnessThrottlingMapId)
                     && Objects.equals(otherDisplay.mRefreshRateZoneId, this.mRefreshRateZoneId)
                     && this.mLeadDisplayId == otherDisplay.mLeadDisplayId
+                    && Objects.equals(mLeadDisplayAddress, otherDisplay.mLeadDisplayAddress)
                     && Objects.equals(mThermalRefreshRateThrottlingMapId,
                     otherDisplay.mThermalRefreshRateThrottlingMapId);
         }
@@ -309,9 +377,10 @@
             result = 31 * result + mLogicalDisplayId;
             result = 31 * result + mDisplayGroupName.hashCode();
             result = 31 * result + mAddress.hashCode();
-            result = 31 * result + mThermalBrightnessThrottlingMapId.hashCode();
+            result = 31 * result + Objects.hashCode(mThermalBrightnessThrottlingMapId);
             result = 31 * result + Objects.hashCode(mRefreshRateZoneId);
             result = 31 * result + mLeadDisplayId;
+            result = 31 * result + Objects.hashCode(mLeadDisplayAddress);
             result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId);
             return result;
         }
@@ -360,8 +429,20 @@
             return mLeadDisplayId;
         }
 
+        /**
+         * @return Display address of the display that this one follows.
+         */
+        @Nullable
+        public DisplayAddress getLeadDisplayAddress() {
+            return mLeadDisplayAddress;
+        }
+
         public String getRefreshRateThermalThrottlingMapId() {
             return mThermalRefreshRateThrottlingMapId;
         }
+
+        private void setLeadDisplayId(int id) {
+            mLeadDisplayId = id;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 1889578..82755b6 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -18,8 +18,9 @@
 
 import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
 import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE;
-import static android.os.PowerManager.BRIGHTNESS_INVALID;
+import static android.os.PowerManager.BRIGHTNESS_INVALID_FLOAT;
 
+import android.annotation.IntegerRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ContentResolver;
@@ -41,6 +42,7 @@
 import android.os.IThermalService;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -68,8 +70,10 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.display.utils.AmbientFilter;
 import com.android.server.display.utils.AmbientFilterFactory;
+import com.android.server.display.utils.DeviceConfigParsingUtils;
 import com.android.server.display.utils.SensorUtils;
 import com.android.server.sensors.SensorManagerInternal;
 import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
@@ -84,6 +88,8 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.concurrent.Callable;
+import java.util.function.Function;
+import java.util.function.IntSupplier;
 
 /**
  * The DisplayModeDirector is responsible for determining what modes are allowed to be automatically
@@ -117,7 +123,7 @@
     private final SensorObserver mSensorObserver;
     private final HbmObserver mHbmObserver;
     private final SkinThermalStatusObserver mSkinThermalStatusObserver;
-    private final DeviceConfigInterface mDeviceConfig;
+    private final DeviceConfigParameterProvider mConfigParameterProvider;
     private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
 
     @GuardedBy("mLock")
@@ -157,7 +163,7 @@
         mSupportedModesByDisplay = new SparseArray<>();
         mDefaultModeByDisplay = new SparseArray<>();
         mAppRequestObserver = new AppRequestObserver();
-        mDeviceConfig = injector.getDeviceConfig();
+        mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig());
         mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
         mSettingsObserver = new SettingsObserver(context, handler);
         mBrightnessObserver = new BrightnessObserver(context, handler, injector);
@@ -681,9 +687,9 @@
         synchronized (mLock) {
             mDefaultDisplayDeviceConfig = displayDeviceConfig;
             mSettingsObserver.setRefreshRates(displayDeviceConfig,
-                /* attemptLoadingFromDeviceConfig= */ true);
+                /* attemptReadFromFeatureParams= */ true);
             mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
-                /* attemptLoadingFromDeviceConfig= */ true);
+                /* attemptReadFromFeatureParams= */ true);
             mBrightnessObserver.reloadLightSensor(displayDeviceConfig);
             mHbmObserver.setupHdrRefreshRates(displayDeviceConfig);
         }
@@ -892,7 +898,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED: {
-                    Pair<int[], int[]> thresholds = (Pair<int[], int[]>) msg.obj;
+                    Pair<float[], float[]> thresholds = (Pair<float[], float[]>) msg.obj;
                     mBrightnessObserver.onDeviceConfigLowBrightnessThresholdsChanged(
                             thresholds.first, thresholds.second);
                     break;
@@ -906,7 +912,7 @@
                 }
 
                 case MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED: {
-                    Pair<int[], int[]> thresholds = (Pair<int[], int[]>) msg.obj;
+                    Pair<float[], float[]> thresholds = (Pair<float[], float[]>) msg.obj;
 
                     mBrightnessObserver.onDeviceConfigHighBrightnessThresholdsChanged(
                             thresholds.first, thresholds.second);
@@ -1087,7 +1093,7 @@
             // reading from the DeviceConfig is an intensive IO operation and having it in the
             // startup phase where we thrive to keep the latency very low has significant impact.
             setRefreshRates(/* displayDeviceConfig= */ null,
-                /* attemptLoadingFromDeviceConfig= */ false);
+                /* attemptReadFromFeatureParams= */ false);
         }
 
         /**
@@ -1095,8 +1101,8 @@
          * if missing from DisplayDeviceConfig, and finally fallback to config.xml.
          */
         public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            setDefaultPeakRefreshRate(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+                boolean attemptReadFromFeatureParams) {
+            setDefaultPeakRefreshRate(displayDeviceConfig, attemptReadFromFeatureParams);
             mDefaultRefreshRate =
                     (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
                         R.integer.config_defaultRefreshRate)
@@ -1113,9 +1119,9 @@
             cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/,
                     this);
 
-            Float deviceConfigDefaultPeakRefresh =
-                    mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
-            if (deviceConfigDefaultPeakRefresh != null) {
+            float deviceConfigDefaultPeakRefresh =
+                    mConfigParameterProvider.getPeakRefreshRateDefault();
+            if (deviceConfigDefaultPeakRefresh != -1) {
                 mDefaultPeakRefreshRate = deviceConfigDefaultPeakRefresh;
             }
 
@@ -1137,7 +1143,7 @@
             synchronized (mLock) {
                 if (defaultPeakRefreshRate == null) {
                     setDefaultPeakRefreshRate(mDefaultDisplayDeviceConfig,
-                            /* attemptLoadingFromDeviceConfig= */ false);
+                            /* attemptReadFromFeatureParams= */ false);
                     updateRefreshRateSettingLocked();
                 } else if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) {
                     mDefaultPeakRefreshRate = defaultPeakRefreshRate;
@@ -1171,18 +1177,17 @@
         }
 
         private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            Float defaultPeakRefreshRate = null;
+                boolean attemptReadFromFeatureParams) {
+            float defaultPeakRefreshRate = -1;
 
-            if (attemptLoadingFromDeviceConfig) {
+            if (attemptReadFromFeatureParams) {
                 try {
-                    defaultPeakRefreshRate =
-                        mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
+                    defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault();
                 } catch (Exception exception) {
                     // Do nothing
                 }
             }
-            if (defaultPeakRefreshRate == null) {
+            if (defaultPeakRefreshRate == -1) {
                 defaultPeakRefreshRate =
                         (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
                                 R.integer.config_defaultPeakRefreshRate)
@@ -1485,10 +1490,32 @@
     @VisibleForTesting
     public class BrightnessObserver implements DisplayManager.DisplayListener {
         private static final int LIGHT_SENSOR_RATE_MS = 250;
-        private int[] mLowDisplayBrightnessThresholds;
-        private int[] mLowAmbientBrightnessThresholds;
-        private int[] mHighDisplayBrightnessThresholds;
-        private int[] mHighAmbientBrightnessThresholds;
+
+        /**
+         * Brightness thresholds for the low zone. Paired with lux thresholds.
+         *
+         * A negative value means that only the lux threshold should be applied.
+         */
+        private float[] mLowDisplayBrightnessThresholds;
+        /**
+         * Lux thresholds for the low zone. Paired with brightness thresholds.
+         *
+         * A negative value means that only the display brightness threshold should be applied.
+         */
+        private float[] mLowAmbientBrightnessThresholds;
+
+        /**
+         * Brightness thresholds for the high zone. Paired with lux thresholds.
+         *
+         * A negative value means that only the lux threshold should be applied.
+         */
+        private float[] mHighDisplayBrightnessThresholds;
+        /**
+         * Lux thresholds for the high zone. Paired with brightness thresholds.
+         *
+         * A negative value means that only the display brightness threshold should be applied.
+         */
+        private float[] mHighAmbientBrightnessThresholds;
         // valid threshold if any item from the array >= 0
         private boolean mShouldObserveDisplayLowChange;
         private boolean mShouldObserveAmbientLowChange;
@@ -1506,7 +1533,8 @@
         // Take it as low brightness before valid sensor data comes
         private float mAmbientLux = -1.0f;
         private AmbientFilter mAmbientFilter;
-        private int mBrightness = -1;
+
+        private float mBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
 
         private final Context mContext;
         private final Injector mInjector;
@@ -1528,7 +1556,7 @@
             mHandler = handler;
             mInjector = injector;
             updateBlockingZoneThresholds(/* displayDeviceConfig= */ null,
-                /* attemptLoadingFromDeviceConfig= */ false);
+                /* attemptReadFromFeatureParams= */ false);
             mRefreshRateInHighZone = context.getResources().getInteger(
                     R.integer.config_fixedRefreshRateInHighZone);
         }
@@ -1537,29 +1565,29 @@
          * 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,
-                boolean attemptLoadingFromDeviceConfig) {
-            loadLowBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
-            loadHighBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+        public void updateBlockingZoneThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptReadFromFeatureParams) {
+            loadLowBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams);
+            loadHighBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams);
         }
 
         @VisibleForTesting
-        int[] getLowDisplayBrightnessThreshold() {
+        float[] getLowDisplayBrightnessThresholds() {
             return mLowDisplayBrightnessThresholds;
         }
 
         @VisibleForTesting
-        int[] getLowAmbientBrightnessThreshold() {
+        float[] getLowAmbientBrightnessThresholds() {
             return mLowAmbientBrightnessThresholds;
         }
 
         @VisibleForTesting
-        int[] getHighDisplayBrightnessThreshold() {
+        float[] getHighDisplayBrightnessThresholds() {
             return mHighDisplayBrightnessThresholds;
         }
 
         @VisibleForTesting
-        int[] getHighAmbientBrightnessThreshold() {
+        float[] getHighAmbientBrightnessThresholds() {
             return mHighAmbientBrightnessThresholds;
         }
 
@@ -1579,20 +1607,22 @@
             return mRefreshRateInLowZone;
         }
 
-        private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            loadRefreshRateInHighZone(displayDeviceConfig, attemptLoadingFromDeviceConfig);
-            loadRefreshRateInLowZone(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+        private void loadLowBrightnessThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig,
+                boolean attemptReadFromFeatureParams) {
+            loadRefreshRateInHighZone(displayDeviceConfig, attemptReadFromFeatureParams);
+            loadRefreshRateInLowZone(displayDeviceConfig, attemptReadFromFeatureParams);
             mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
-                    () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
+                    () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(),
                     () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
                     R.array.config_brightnessThresholdsOfPeakRefreshRate,
-                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
+                    displayDeviceConfig, attemptReadFromFeatureParams,
+                    DeviceConfigParsingUtils::displayBrightnessThresholdsIntToFloat);
             mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
-                    () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
+                    () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(),
                     () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
                     R.array.config_ambientThresholdsOfPeakRefreshRate,
-                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
+                    displayDeviceConfig, attemptReadFromFeatureParams,
+                    DeviceConfigParsingUtils::ambientBrightnessThresholdsIntToFloat);
             if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) {
                 throw new RuntimeException("display low brightness threshold array and ambient "
                         + "brightness threshold array have different length: "
@@ -1604,55 +1634,57 @@
         }
 
         private void loadRefreshRateInLowZone(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            int refreshRateInLowZone =
-                    (displayDeviceConfig == null) ? mContext.getResources().getInteger(
-                        R.integer.config_defaultRefreshRateInZone)
-                        : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate();
-            if (attemptLoadingFromDeviceConfig) {
+                boolean attemptReadFromFeatureParams) {
+            int refreshRateInLowZone = -1;
+            if (attemptReadFromFeatureParams) {
                 try {
-                    refreshRateInLowZone = mDeviceConfig.getInt(
-                        DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                        DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE,
-                        refreshRateInLowZone);
+                    refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone();
                 } catch (Exception exception) {
                     // Do nothing
                 }
             }
+            if (refreshRateInLowZone == -1) {
+                refreshRateInLowZone = (displayDeviceConfig == null)
+                        ? mContext.getResources().getInteger(
+                                R.integer.config_defaultRefreshRateInZone)
+                        : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate();
+            }
             mRefreshRateInLowZone = refreshRateInLowZone;
         }
 
         private void loadRefreshRateInHighZone(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
-            int refreshRateInHighZone =
-                    (displayDeviceConfig == null) ? mContext.getResources().getInteger(
-                        R.integer.config_fixedRefreshRateInHighZone) : displayDeviceConfig
-                        .getDefaultHighBlockingZoneRefreshRate();
-            if (attemptLoadingFromDeviceConfig) {
+                boolean attemptReadFromFeatureParams) {
+            int refreshRateInHighZone = -1;
+            if (attemptReadFromFeatureParams) {
                 try {
-                    refreshRateInHighZone = mDeviceConfig.getInt(
-                        DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                        DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE,
-                        refreshRateInHighZone);
+                    refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone();
                 } catch (Exception exception) {
                     // Do nothing
                 }
             }
+            if (refreshRateInHighZone == -1) {
+                refreshRateInHighZone = (displayDeviceConfig == null)
+                        ? mContext.getResources().getInteger(
+                                R.integer.config_fixedRefreshRateInHighZone)
+                        : displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate();
+            }
             mRefreshRateInHighZone = refreshRateInHighZone;
         }
 
         private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
-                boolean attemptLoadingFromDeviceConfig) {
+                boolean attemptReadFromFeatureParams) {
             mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
-                    () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
+                    () -> mConfigParameterProvider.getHighDisplayBrightnessThresholds(),
                     () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
                     R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
-                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
+                    displayDeviceConfig, attemptReadFromFeatureParams,
+                    DeviceConfigParsingUtils::displayBrightnessThresholdsIntToFloat);
             mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
-                    () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
+                    () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(),
                     () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
                     R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
-                    displayDeviceConfig, attemptLoadingFromDeviceConfig);
+                    displayDeviceConfig, attemptReadFromFeatureParams,
+                    DeviceConfigParsingUtils::ambientBrightnessThresholdsIntToFloat);
             if (mHighDisplayBrightnessThresholds.length
                     != mHighAmbientBrightnessThresholds.length) {
                 throw new RuntimeException("display high brightness threshold array and ambient "
@@ -1664,27 +1696,27 @@
             }
         }
 
-        private int[] loadBrightnessThresholds(
-                Callable<int[]> loadFromDeviceConfigDisplaySettingsCallable,
-                Callable<int[]> loadFromDisplayDeviceConfigCallable,
+        private float[] loadBrightnessThresholds(
+                Callable<float[]> loadFromDeviceConfigDisplaySettingsCallable,
+                Callable<float[]> loadFromDisplayDeviceConfigCallable,
                 int brightnessThresholdOfFixedRefreshRateKey,
-                DisplayDeviceConfig displayDeviceConfig, boolean attemptLoadingFromDeviceConfig) {
-            int[] brightnessThresholds = null;
+                DisplayDeviceConfig displayDeviceConfig, boolean attemptReadFromFeatureParams,
+                Function<int[], float[]> conversion) {
+            float[] brightnessThresholds = null;
 
-            if (attemptLoadingFromDeviceConfig) {
+            if (attemptReadFromFeatureParams) {
                 try {
-                    brightnessThresholds =
-                        loadFromDeviceConfigDisplaySettingsCallable.call();
+                    brightnessThresholds = loadFromDeviceConfigDisplaySettingsCallable.call();
                 } catch (Exception exception) {
                     // Do nothing
                 }
             }
             if (brightnessThresholds == null) {
                 try {
-                    brightnessThresholds =
-                            (displayDeviceConfig == null) ? mContext.getResources().getIntArray(
-                                    brightnessThresholdOfFixedRefreshRateKey)
-                                    : loadFromDisplayDeviceConfigCallable.call();
+                    brightnessThresholds = displayDeviceConfig == null ? conversion.apply(
+                            mContext.getResources().getIntArray(
+                                    brightnessThresholdOfFixedRefreshRateKey)) :
+                            loadFromDisplayDeviceConfigCallable.call();
                 } catch (Exception e) {
                     Slog.e(TAG, "Unexpectedly failed to load display brightness threshold");
                     e.printStackTrace();
@@ -1693,32 +1725,15 @@
             return brightnessThresholds;
         }
 
-        /**
-         * @return the display brightness thresholds for the low brightness zones
-         */
-        @VisibleForTesting
-        int[] getLowDisplayBrightnessThresholds() {
-            return mLowDisplayBrightnessThresholds;
-        }
-
-        /**
-         * @return the ambient brightness thresholds for the low brightness zones
-         */
-        @VisibleForTesting
-        int[] getLowAmbientBrightnessThresholds() {
-            return mLowAmbientBrightnessThresholds;
-        }
-
         private void observe(SensorManager sensorManager) {
             mSensorManager = sensorManager;
             mBrightness = getBrightness(Display.DEFAULT_DISPLAY);
 
             // DeviceConfig is accessible after system ready.
-            int[] lowDisplayBrightnessThresholds =
-                    mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds();
-            int[] lowAmbientBrightnessThresholds =
-                    mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds();
-
+            float[] lowDisplayBrightnessThresholds =
+                    mConfigParameterProvider.getLowDisplayBrightnessThresholds();
+            float[] lowAmbientBrightnessThresholds =
+                    mConfigParameterProvider.getLowAmbientBrightnessThresholds();
             if (lowDisplayBrightnessThresholds != null && lowAmbientBrightnessThresholds != null
                     && lowDisplayBrightnessThresholds.length
                     == lowAmbientBrightnessThresholds.length) {
@@ -1726,11 +1741,10 @@
                 mLowAmbientBrightnessThresholds = lowAmbientBrightnessThresholds;
             }
 
-            int[] highDisplayBrightnessThresholds =
-                    mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds();
-            int[] highAmbientBrightnessThresholds =
-                    mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds();
-
+            float[] highDisplayBrightnessThresholds =
+                    mConfigParameterProvider.getHighDisplayBrightnessThresholds();
+            float[] highAmbientBrightnessThresholds =
+                    mConfigParameterProvider.getHighAmbientBrightnessThresholds();
             if (highDisplayBrightnessThresholds != null && highAmbientBrightnessThresholds != null
                     && highDisplayBrightnessThresholds.length
                     == highAmbientBrightnessThresholds.length) {
@@ -1738,14 +1752,12 @@
                 mHighAmbientBrightnessThresholds = highAmbientBrightnessThresholds;
             }
 
-            final int refreshRateInLowZone = mDeviceConfigDisplaySettings
-                    .getRefreshRateInLowZone();
+            final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone();
             if (refreshRateInLowZone != -1) {
                 mRefreshRateInLowZone = refreshRateInLowZone;
             }
 
-            final int refreshRateInHighZone = mDeviceConfigDisplaySettings
-                    .getRefreshRateInHighZone();
+            final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone();
             if (refreshRateInHighZone != -1) {
                 mRefreshRateInHighZone = refreshRateInHighZone;
             }
@@ -1787,8 +1799,8 @@
             }
         }
 
-        private void onDeviceConfigLowBrightnessThresholdsChanged(int[] displayThresholds,
-                int[] ambientThresholds) {
+        private void onDeviceConfigLowBrightnessThresholdsChanged(float[] displayThresholds,
+                float[] ambientThresholds) {
             if (displayThresholds != null && ambientThresholds != null
                     && displayThresholds.length == ambientThresholds.length) {
                 mLowDisplayBrightnessThresholds = displayThresholds;
@@ -1799,15 +1811,17 @@
                     displayDeviceConfig = mDefaultDisplayDeviceConfig;
                 }
                 mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
-                        () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
+                        () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(),
                         () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
                         R.array.config_brightnessThresholdsOfPeakRefreshRate,
-                        displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false,
+                        DeviceConfigParsingUtils::displayBrightnessThresholdsIntToFloat);
                 mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
-                        () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
+                        () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(),
                         () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
                         R.array.config_ambientThresholdsOfPeakRefreshRate,
-                        displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false,
+                        DeviceConfigParsingUtils::ambientBrightnessThresholdsIntToFloat);
             }
             restartObserver();
         }
@@ -1822,7 +1836,7 @@
                 // from there.
                 synchronized (mLock) {
                     loadRefreshRateInLowZone(mDefaultDisplayDeviceConfig,
-                            /* attemptLoadingFromDeviceConfig= */ false);
+                            /* attemptReadFromFeatureParams= */ false);
                 }
                 restartObserver();
             } else if (refreshRate != mRefreshRateInLowZone) {
@@ -1831,8 +1845,8 @@
             }
         }
 
-        private void onDeviceConfigHighBrightnessThresholdsChanged(int[] displayThresholds,
-                int[] ambientThresholds) {
+        private void onDeviceConfigHighBrightnessThresholdsChanged(float[] displayThresholds,
+                float[] ambientThresholds) {
             if (displayThresholds != null && ambientThresholds != null
                     && displayThresholds.length == ambientThresholds.length) {
                 mHighDisplayBrightnessThresholds = displayThresholds;
@@ -1843,15 +1857,17 @@
                     displayDeviceConfig = mDefaultDisplayDeviceConfig;
                 }
                 mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
-                        () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
+                        () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(),
                         () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
                         R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
-                        displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false,
+                        DeviceConfigParsingUtils::displayBrightnessThresholdsIntToFloat);
                 mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
-                        () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
+                        () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(),
                         () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
                         R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
-                        displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false);
+                        displayDeviceConfig, /* attemptReadFromFeatureParams= */ false,
+                        DeviceConfigParsingUtils::ambientBrightnessThresholdsIntToFloat);
             }
             restartObserver();
         }
@@ -1866,7 +1882,7 @@
                 // from there.
                 synchronized (mLock) {
                     loadRefreshRateInHighZone(mDefaultDisplayDeviceConfig,
-                            /* attemptLoadingFromDeviceConfig= */ false);
+                            /* attemptReadFromFeatureParams= */ false);
                 }
                 restartObserver();
             } else if (refreshRate != mRefreshRateInHighZone) {
@@ -1886,11 +1902,11 @@
             pw.println("    mShouldObserveAmbientLowChange: " + mShouldObserveAmbientLowChange);
             pw.println("    mRefreshRateInLowZone: " + mRefreshRateInLowZone);
 
-            for (int d : mLowDisplayBrightnessThresholds) {
+            for (float d : mLowDisplayBrightnessThresholds) {
                 pw.println("    mDisplayLowBrightnessThreshold: " + d);
             }
 
-            for (int d : mLowAmbientBrightnessThresholds) {
+            for (float d : mLowAmbientBrightnessThresholds) {
                 pw.println("    mAmbientLowBrightnessThreshold: " + d);
             }
 
@@ -1898,11 +1914,11 @@
             pw.println("    mShouldObserveAmbientHighChange: " + mShouldObserveAmbientHighChange);
             pw.println("    mRefreshRateInHighZone: " + mRefreshRateInHighZone);
 
-            for (int d : mHighDisplayBrightnessThresholds) {
+            for (float d : mHighDisplayBrightnessThresholds) {
                 pw.println("    mDisplayHighBrightnessThresholds: " + d);
             }
 
-            for (int d : mHighAmbientBrightnessThresholds) {
+            for (float d : mHighAmbientBrightnessThresholds) {
                 pw.println("    mAmbientHighBrightnessThresholds: " + d);
             }
 
@@ -1931,9 +1947,9 @@
 
                 // We don't support multiple display blocking zones yet, so only handle
                 // brightness changes for the default display for now.
-                int brightness = getBrightness(displayId);
+                float brightness = getBrightness(displayId);
                 synchronized (mLock) {
-                    if (brightness != mBrightness) {
+                    if (!BrightnessSynchronizer.floatEquals(brightness, mBrightness)) {
                         mBrightness = brightness;
                         onBrightnessChangedLocked();
                     }
@@ -2012,8 +2028,8 @@
          * Checks to see if at least one value is positive, in which case it is necessary to listen
          * to value changes.
          */
-        private boolean hasValidThreshold(int[] a) {
-            for (int d: a) {
+        private boolean hasValidThreshold(float[] a) {
+            for (float d: a) {
                 if (d >= 0) {
                     return true;
                 }
@@ -2022,10 +2038,18 @@
             return false;
         }
 
-        private boolean isInsideLowZone(int brightness, float lux) {
+        /**
+         * Check if we're in the low zone where higher refresh rates aren't allowed to prevent
+         * flickering.
+         * @param brightness The brightness value or a negative value meaning that only the lux
+         *                   threshold should be applied
+         * @param lux The lux value. If negative, only the brightness threshold is applied
+         * @return True if we're in the low zone
+         */
+        private boolean isInsideLowZone(float brightness, float lux) {
             for (int i = 0; i < mLowDisplayBrightnessThresholds.length; i++) {
-                int disp = mLowDisplayBrightnessThresholds[i];
-                int ambi = mLowAmbientBrightnessThresholds[i];
+                float disp = mLowDisplayBrightnessThresholds[i];
+                float ambi = mLowAmbientBrightnessThresholds[i];
 
                 if (disp >= 0 && ambi >= 0) {
                     if (brightness <= disp && lux <= ambi) {
@@ -2045,10 +2069,18 @@
             return false;
         }
 
-        private boolean isInsideHighZone(int brightness, float lux) {
+        /**
+         * Check if we're in the high zone where higher refresh rates aren't allowed to prevent
+         * flickering.
+         * @param brightness The brightness value or a negative value meaning that only the lux
+         *                   threshold should be applied
+         * @param lux The lux value. If negative, only the brightness threshold is applied
+         * @return True if we're in the high zone
+         */
+        private boolean isInsideHighZone(float brightness, float lux) {
             for (int i = 0; i < mHighDisplayBrightnessThresholds.length; i++) {
-                int disp = mHighDisplayBrightnessThresholds[i];
-                int ambi = mHighAmbientBrightnessThresholds[i];
+                float disp = mHighDisplayBrightnessThresholds[i];
+                float ambi = mHighAmbientBrightnessThresholds[i];
 
                 if (disp >= 0 && ambi >= 0) {
                     if (brightness >= disp && lux >= ambi) {
@@ -2075,7 +2107,7 @@
             Vote refreshRateVote = null;
             Vote refreshRateSwitchingVote = null;
 
-            if (mBrightness < 0) {
+            if (Float.isNaN(mBrightness)) {
                 // Either the setting isn't available or we shouldn't be observing yet anyways.
                 // Either way, just bail out since there's nothing we can do here.
                 return;
@@ -2191,13 +2223,18 @@
             return mDefaultDisplayState == Display.STATE_ON;
         }
 
-        private int getBrightness(int displayId) {
+        /**
+         * Get the brightness value for a display
+         * @param displayId The ID of the display
+         * @return The brightness value
+         */
+        private float getBrightness(int displayId) {
             final BrightnessInfo info = mInjector.getBrightnessInfo(displayId);
             if (info != null) {
-                return BrightnessSynchronizer.brightnessFloatToInt(info.adjustedBrightness);
+                return info.adjustedBrightness;
             }
 
-            return BRIGHTNESS_INVALID;
+            return BRIGHTNESS_INVALID_FLOAT;
         }
 
         private final class LightSensorEventListener implements SensorEventListener {
@@ -2285,7 +2322,7 @@
                 }
             }
 
-            private boolean isDifferentZone(float lux1, float lux2, int[] luxThresholds) {
+            private boolean isDifferentZone(float lux1, float lux2, float[] luxThresholds) {
                 for (final float boundary : luxThresholds) {
                     // Test each boundary. See if the current value and the new value are at
                     // different sides.
@@ -2675,113 +2712,55 @@
 
     private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener {
         public void startListening() {
-            mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+            mConfigParameterProvider.addOnPropertiesChangedListener(
                     BackgroundThread.getExecutor(), this);
         }
 
-        /*
-         * Return null if no such property or wrong format (not comma separated integers).
-         */
-        public int[] getLowDisplayBrightnessThresholds() {
-            return getIntArrayProperty(
-                    DisplayManager.DeviceConfig
-                            .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS);
+        private int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) {
+            return getRefreshRate(
+                    () -> mConfigParameterProvider.getRefreshRateInHbmHdr(),
+                    () -> displayDeviceConfig.getDefaultRefreshRateInHbmHdr(),
+                    R.integer.config_defaultRefreshRateInHbmHdr,
+                    displayDeviceConfig
+            );
         }
 
-        /*
-         * Return null if no such property or wrong format (not comma separated integers).
-         */
-        public int[] getLowAmbientBrightnessThresholds() {
-            return getIntArrayProperty(
-                    DisplayManager.DeviceConfig
-                            .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS);
+        private int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) {
+            return getRefreshRate(
+                    () -> mConfigParameterProvider.getRefreshRateInHbmSunlight(),
+                    () -> displayDeviceConfig.getDefaultRefreshRateInHbmSunlight(),
+                    R.integer.config_defaultRefreshRateInHbmSunlight,
+                    displayDeviceConfig
+            );
         }
 
-        public int getRefreshRateInLowZone() {
-            return mDeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1);
-
-        }
-
-        /*
-         * Return null if no such property or wrong format (not comma separated integers).
-         */
-        public int[] getHighDisplayBrightnessThresholds() {
-            return getIntArrayProperty(
-                    DisplayManager.DeviceConfig
-                            .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS);
-        }
-
-        /*
-         * Return null if no such property or wrong format (not comma separated integers).
-         */
-        public int[] getHighAmbientBrightnessThresholds() {
-            return getIntArrayProperty(
-                    DisplayManager.DeviceConfig
-                            .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS);
-        }
-
-        public int getRefreshRateInHighZone() {
-            return mDeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE,
-                    -1);
-        }
-
-        public int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) {
-            int refreshRate =
-                    (displayDeviceConfig == null) ? mContext.getResources().getInteger(
-                            R.integer.config_defaultRefreshRateInHbmHdr)
-                            : displayDeviceConfig.getDefaultRefreshRateInHbmHdr();
+        private int getRefreshRate(IntSupplier fromConfigPram, IntSupplier fromDisplayDeviceConfig,
+                @IntegerRes int configKey, DisplayDeviceConfig displayDeviceConfig) {
+            int refreshRate = -1;
             try {
-                refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR,
-                    refreshRate);
-            } catch (NullPointerException e) {
+                refreshRate = fromConfigPram.getAsInt();
+            } catch (NullPointerException npe) {
                 // Do Nothing
             }
-            return refreshRate;
-        }
-
-        public int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) {
-            int refreshRate =
-                    (displayDeviceConfig == null) ? mContext.getResources()
-                        .getInteger(R.integer.config_defaultRefreshRateInHbmSunlight)
-                        : displayDeviceConfig.getDefaultRefreshRateInHbmSunlight();
-            try {
-                refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT,
-                    refreshRate);
-            } catch (NullPointerException e) {
-                // Do Nothing
+            if (refreshRate == -1) {
+                refreshRate = (displayDeviceConfig == null)
+                                ? mContext.getResources().getInteger(configKey)
+                                : fromDisplayDeviceConfig.getAsInt();
             }
             return refreshRate;
         }
 
-        /*
-         * Return null if no such property
-         */
-        public Float getDefaultPeakRefreshRate() {
-            float defaultPeakRefreshRate = mDeviceConfig.getFloat(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1);
-
-            if (defaultPeakRefreshRate == -1) {
-                return null;
-            }
-            return defaultPeakRefreshRate;
-        }
-
         @Override
         public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
-            Float defaultPeakRefreshRate = getDefaultPeakRefreshRate();
+            float defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault();
             mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED,
-                    defaultPeakRefreshRate).sendToTarget();
+                    defaultPeakRefreshRate == -1 ? null : defaultPeakRefreshRate).sendToTarget();
 
-            int[] lowDisplayBrightnessThresholds = getLowDisplayBrightnessThresholds();
-            int[] lowAmbientBrightnessThresholds = getLowAmbientBrightnessThresholds();
-            final int refreshRateInLowZone = getRefreshRateInLowZone();
+            float[] lowDisplayBrightnessThresholds =
+                    mConfigParameterProvider.getLowDisplayBrightnessThresholds();
+            float[] lowAmbientBrightnessThresholds =
+                    mConfigParameterProvider.getLowAmbientBrightnessThresholds();
+            final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone();
 
             mHandler.obtainMessage(MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED,
                     new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds))
@@ -2790,9 +2769,11 @@
             mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone,
                     0).sendToTarget();
 
-            int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds();
-            int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds();
-            final int refreshRateInHighZone = getRefreshRateInHighZone();
+            float[] highDisplayBrightnessThresholds =
+                    mConfigParameterProvider.getHighDisplayBrightnessThresholds();
+            float[] highAmbientBrightnessThresholds =
+                    mConfigParameterProvider.getHighAmbientBrightnessThresholds();
+            final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone();
 
             mHandler.obtainMessage(MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED,
                     new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds))
@@ -2814,33 +2795,6 @@
                     .sendToTarget();
             }
         }
-
-        private int[] getIntArrayProperty(String prop) {
-            String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop,
-                    null);
-
-            if (strArray != null) {
-                return parseIntArray(strArray);
-            }
-
-            return null;
-        }
-
-        private int[] parseIntArray(@NonNull String strArray) {
-            String[] items = strArray.split(",");
-            int[] array = new int[items.length];
-
-            try {
-                for (int i = 0; i < array.length; i++) {
-                    array[i] = Integer.parseInt(items[i]);
-                }
-            } catch (NumberFormatException e) {
-                Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e);
-                array = null;
-            }
-
-            return array;
-        }
     }
 
     interface Injector {
diff --git a/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java b/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java
new file mode 100644
index 0000000..a7aee31
--- /dev/null
+++ b/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.PowerManager;
+import android.util.Slog;
+
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.DisplayDeviceConfig;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * Provides utility methods for DeviceConfig string parsing
+ */
+public class DeviceConfigParsingUtils {
+    private static final String TAG = "DeviceConfigParsingUtils";
+
+    /**
+     * Parses map from device config
+     * Data format:
+     * (displayId:String,numberOfPoints:Int,(state:T,value:Float){numberOfPoints},
+     * dataSetId:String)?;)+
+     * result : mapOf(displayId to mapOf(dataSetId to V))
+     */
+    @NonNull
+    public static <T, V> Map<String, Map<String, V>> parseDeviceConfigMap(
+            @Nullable String data,
+            @NonNull BiFunction<String, String, T> dataPointMapper,
+            @NonNull Function<List<T>, V> dataSetMapper) {
+        if (data == null) {
+            return Map.of();
+        }
+        Map<String, Map<String, V>> result = new HashMap<>();
+        String[] dataSets = data.split(";"); // by displayId + dataSetId
+        for (String dataSet : dataSets) {
+            String[] items = dataSet.split(",");
+            int noOfItems = items.length;
+            // Validate number of items, at least: displayId,1,key1,value1
+            if (noOfItems < 4) {
+                Slog.e(TAG, "Invalid dataSet(not enough items):" + dataSet, new Throwable());
+                return Map.of();
+            }
+            int i = 0;
+            String uniqueDisplayId = items[i++];
+
+            String numberOfPointsString = items[i++];
+            int numberOfPoints;
+            try {
+                numberOfPoints = Integer.parseInt(numberOfPointsString);
+            } catch (NumberFormatException nfe) {
+                Slog.e(TAG, "Invalid dataSet(invalid number of points):" + dataSet, nfe);
+                return Map.of();
+            }
+            // Validate number of itmes based on numberOfPoints:
+            // displayId,numberOfPoints,(key,value) x numberOfPoints,dataSetId(optional)
+            int expectedMinItems = 2 + numberOfPoints * 2;
+            if (noOfItems < expectedMinItems || noOfItems > expectedMinItems + 1) {
+                Slog.e(TAG, "Invalid dataSet(wrong number of points):" + dataSet, new Throwable());
+                return Map.of();
+            }
+            // Construct data points
+            List<T> dataPoints = new ArrayList<>();
+            for (int j = 0; j < numberOfPoints; j++) {
+                String key = items[i++];
+                String value = items[i++];
+                T dataPoint = dataPointMapper.apply(key, value);
+                if (dataPoint == null) {
+                    Slog.e(TAG,
+                            "Invalid dataPoint ,key=" + key + ",value=" + value + ",dataSet="
+                                    + dataSet, new Throwable());
+                    return Map.of();
+                }
+                dataPoints.add(dataPoint);
+            }
+            // Construct dataSet
+            V dataSetMapped = dataSetMapper.apply(dataPoints);
+            if (dataSetMapped == null) {
+                Slog.e(TAG, "Invalid dataSetMapped dataPoints=" + dataPoints + ",dataSet="
+                        + dataSet, new Throwable());
+                return Map.of();
+            }
+            // Get dataSetId and dataSets map for displayId
+            String dataSetId = (i < items.length) ? items[i] : DisplayDeviceConfig.DEFAULT_ID;
+            Map<String, V> byDisplayId = result.computeIfAbsent(uniqueDisplayId,
+                    k -> new HashMap<>());
+
+            // Try to store dataSet in datasets for display
+            if (byDisplayId.put(dataSetId, dataSetMapped) != null) {
+                Slog.e(TAG, "Duplicate dataSetId=" + dataSetId + ",data=" + data, new Throwable());
+                return Map.of();
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Parses thermal string value from device config
+     */
+    @PowerManager.ThermalStatus
+    public static int parseThermalStatus(@NonNull String value) throws IllegalArgumentException {
+        switch (value) {
+            case "none":
+                return PowerManager.THERMAL_STATUS_NONE;
+            case "light":
+                return PowerManager.THERMAL_STATUS_LIGHT;
+            case "moderate":
+                return PowerManager.THERMAL_STATUS_MODERATE;
+            case "severe":
+                return PowerManager.THERMAL_STATUS_SEVERE;
+            case "critical":
+                return PowerManager.THERMAL_STATUS_CRITICAL;
+            case "emergency":
+                return PowerManager.THERMAL_STATUS_EMERGENCY;
+            case "shutdown":
+                return PowerManager.THERMAL_STATUS_SHUTDOWN;
+            default:
+                throw new IllegalArgumentException("Invalid Thermal Status: " + value);
+        }
+    }
+
+    /**
+     * Parses brightness value from device config
+     */
+    public static float parseBrightness(String stringVal) throws IllegalArgumentException {
+        float value = Float.parseFloat(stringVal);
+        if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) {
+            throw new IllegalArgumentException("Brightness value out of bounds: " + stringVal);
+        }
+        return value;
+    }
+
+    /**
+     * Convert display brightness thresholds to a float array.
+     * @param thresholdsInt The int array of the thresholds in the range [0, 255]
+     * @return The float array of the thresholds
+     */
+    public static float[] displayBrightnessThresholdsIntToFloat(int[] thresholdsInt) {
+        if (thresholdsInt == null) {
+            return null;
+        }
+
+        float[] thresholds = new float[thresholdsInt.length];
+        for (int i = 0; i < thresholds.length; i++) {
+            if (thresholdsInt[i] < 0) {
+                // A negative value means that there's no threshold
+                thresholds[i] = thresholdsInt[i];
+            } else {
+                thresholds[i] = BrightnessSynchronizer.brightnessIntToFloat(thresholdsInt[i]);
+            }
+        }
+        return thresholds;
+    }
+
+    /**
+     * Convert ambient brightness thresholds to a float array.
+     * @param thresholdsInt The int array of the thresholds
+     * @return The float array of the thresholds
+     */
+    public static float[] ambientBrightnessThresholdsIntToFloat(int[] thresholdsInt) {
+        if (thresholdsInt == null) {
+            return null;
+        }
+
+        float[] thresholds = new float[thresholdsInt.length];
+        for (int i = 0; i < thresholds.length; i++) {
+            thresholds[i] = thresholdsInt[i];
+        }
+        return thresholds;
+    }
+}
diff --git a/services/core/java/com/android/server/display/utils/SensorUtils.java b/services/core/java/com/android/server/display/utils/SensorUtils.java
index 48bc46c..56321cd 100644
--- a/services/core/java/com/android/server/display/utils/SensorUtils.java
+++ b/services/core/java/com/android/server/display/utils/SensorUtils.java
@@ -16,10 +16,13 @@
 
 package com.android.server.display.utils;
 
+import android.annotation.Nullable;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
 import android.text.TextUtils;
 
+import com.android.server.display.DisplayDeviceConfig;
+
 import java.util.List;
 
 /**
@@ -29,17 +32,26 @@
     public static final int NO_FALLBACK = 0;
 
     /**
+     * Finds the specified sensor for SensorData from DisplayDeviceConfig.
+     */
+    @Nullable
+    public static Sensor findSensor(@Nullable SensorManager sensorManager,
+            @Nullable DisplayDeviceConfig.SensorData sensorData, int fallbackType) {
+        if (sensorData == null) {
+            return null;
+        } else {
+            return findSensor(sensorManager, sensorData.type, sensorData.name, fallbackType);
+        }
+    }
+    /**
      * Finds the specified sensor by type and name using SensorManager.
      */
-    public static Sensor findSensor(SensorManager sensorManager, String sensorType,
-            String sensorName, int fallbackType) {
+    @Nullable
+    public static Sensor findSensor(@Nullable SensorManager sensorManager,
+            @Nullable String sensorType, @Nullable String sensorName, int fallbackType) {
         if (sensorManager == null) {
             return null;
         }
-
-        if ("".equals(sensorName) && "".equals(sensorType)) {
-            return null;
-        }
         final boolean isNameSpecified = !TextUtils.isEmpty(sensorName);
         final boolean isTypeSpecified = !TextUtils.isEmpty(sensorType);
         if (isNameSpecified || isTypeSpecified) {
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 5b772fc..4ad26c4 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -37,8 +37,11 @@
  * - Uses the AmbientColorTemperatureSensor to detect changes in the ambient color temperature;
  * - Uses the AmbientColorTemperatureFilter to average these changes over time, filter out the
  *   noise, and arrive at an estimate of the actual ambient color temperature;
- * - Uses the DisplayWhiteBalanceThrottler to decide whether the display color tempearture should
+ * - Uses the DisplayWhiteBalanceThrottler to decide whether the display color temperature should
  *   be updated, suppressing changes that are too frequent or too minor.
+ *
+ *   Calls to this class must happen on the DisplayPowerController(2) handler, to ensure
+ *   values do not get out of sync.
  */
 public class DisplayWhiteBalanceController implements
         AmbientSensor.AmbientBrightnessSensor.Callbacks,
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index da93d0b..0e8a5fb 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -16,13 +16,13 @@
 
 package com.android.server.dreams;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
 import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER;
 import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS;
 
 import android.app.ActivityTaskManager;
 import android.app.BroadcastOptions;
+import android.app.IAppTask;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -229,6 +229,27 @@
     }
 
     /**
+     * Provides an appTask for the dream with token {@code dreamToken}, so that the dream controller
+     * can stop the dream task when necessary.
+     */
+    void setDreamAppTask(Binder dreamToken, IAppTask appTask) {
+        if (mCurrentDream == null || mCurrentDream.mToken != dreamToken
+                || mCurrentDream.mAppTask != null) {
+            Slog.e(TAG, "Illegal dream activity start. mCurrentDream.mToken = "
+                    + mCurrentDream.mToken + ", illegal dreamToken = " + dreamToken
+                    + ". Ending this dream activity.");
+            try {
+                appTask.finishAndRemoveTask();
+            } catch (RemoteException | RuntimeException e) {
+                Slog.e(TAG, "Unable to stop illegal dream activity.");
+            }
+            return;
+        }
+
+        mCurrentDream.mAppTask = appTask;
+    }
+
+    /**
      * Sends a user activity signal to PowerManager to stop the screen from turning off immediately
      * if there hasn't been any user interaction in a while.
      */
@@ -329,8 +350,14 @@
                     mSentStartBroadcast = false;
                 }
 
-                mActivityTaskManager.removeRootTasksWithActivityTypes(
-                        new int[] {ACTIVITY_TYPE_DREAM});
+                if (mCurrentDream != null && mCurrentDream.mAppTask != null) {
+                    // Finish the dream task in case it hasn't finished by itself already.
+                    try {
+                        mCurrentDream.mAppTask.finishAndRemoveTask();
+                    } catch (RemoteException | RuntimeException e) {
+                        Slog.e(TAG, "Unable to stop dream activity.");
+                    }
+                }
 
                 mListener.onDreamStopped(dream.mToken);
             }
@@ -390,6 +417,7 @@
         public final boolean mIsPreviewMode;
         public final boolean mCanDoze;
         public final int mUserId;
+        public IAppTask mAppTask;
 
         public PowerManager.WakeLock mWakeLock;
         public boolean mBound;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 41651fd..d88fe8a 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -27,6 +27,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.IAppTask;
 import android.app.TaskInfo;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -37,6 +38,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
 import android.hardware.display.AmbientDisplayConfiguration;
@@ -116,6 +118,7 @@
     private final PowerManagerInternal mPowerManagerInternal;
     private final PowerManager.WakeLock mDozeWakeLock;
     private final ActivityTaskManagerInternal mAtmInternal;
+    private final PackageManagerInternal mPmInternal;
     private final UserManager mUserManager;
     private final UiEventLogger mUiEventLogger;
     private final DreamUiEventLogger mDreamUiEventLogger;
@@ -203,9 +206,7 @@
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            synchronized (mLock) {
-                updateWhenToDreamSettings();
-            }
+            updateWhenToDreamSettings();
         }
     }
 
@@ -218,6 +219,7 @@
         mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mPowerManagerInternal = getLocalService(PowerManagerInternal.class);
         mAtmInternal = getLocalService(ActivityTaskManagerInternal.class);
+        mPmInternal = getLocalService(PackageManagerInternal.class);
         mUserManager = context.getSystemService(UserManager.class);
         mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, DOZE_WAKE_LOCK_TAG);
         mDozeConfig = new AmbientDisplayConfiguration(mContext);
@@ -257,15 +259,7 @@
             if (Build.IS_DEBUGGABLE) {
                 SystemProperties.addChangeCallback(mSystemPropertiesChanged);
             }
-            mContext.registerReceiver(new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    writePulseGestureEnabled();
-                    synchronized (mLock) {
-                        stopDreamLocked(false /*immediate*/, "user switched");
-                    }
-                }
-            }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
+
             mContext.getContentResolver().registerContentObserver(
                     Settings.Secure.getUriFor(Settings.Secure.DOZE_DOUBLE_TAP_GESTURE), false,
                     mDozeEnabledObserver, UserHandle.USER_ALL);
@@ -303,6 +297,18 @@
         }
     }
 
+    @Override
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+        updateWhenToDreamSettings();
+
+        mHandler.post(() -> {
+            writePulseGestureEnabled();
+            synchronized (mLock) {
+                stopDreamLocked(false /*immediate*/, "user switched");
+            }
+        });
+    }
+
     private void dumpInternal(PrintWriter pw) {
         synchronized (mLock) {
             pw.println("DREAM MANAGER (dumpsys dreams)");
@@ -318,6 +324,7 @@
             pw.println("mWhenToDream=" + mWhenToDream);
             pw.println("mKeepDreamingWhenUnpluggingDefault=" + mKeepDreamingWhenUnpluggingDefault);
             pw.println("getDozeComponent()=" + getDozeComponent());
+            pw.println("mDreamOverlayServiceName=" + mDreamOverlayServiceName.flattenToString());
             pw.println();
 
             DumpUtils.dumpAsync(mHandler, (pw1, prefix) -> mController.dump(pw1), pw, "", 200);
@@ -1061,6 +1068,64 @@
                 Binder.restoreCallingIdentity(ident);
             }
         }
+
+        @Override // Binder call
+        public void startDreamActivity(@NonNull Intent intent) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            // We post here, because startDreamActivity and setDreamAppTask have to run
+            // synchronously and DreamController#setDreamAppTask has to run on mHandler.
+            mHandler.post(() -> {
+                final Binder dreamToken;
+                final String dreamPackageName;
+                synchronized (mLock) {
+                    if (mCurrentDream == null) {
+                        Slog.e(TAG, "Attempt to start DreamActivity, but the device is not "
+                                + "dreaming. Aborting without starting the DreamActivity.");
+                        return;
+                    }
+                    dreamToken = mCurrentDream.token;
+                    dreamPackageName = mCurrentDream.name.getPackageName();
+                }
+
+                if (!canLaunchDreamActivity(dreamPackageName, intent.getPackage(),
+                            callingUid)) {
+                    Slog.e(TAG, "The dream activity can be started only when the device is dreaming"
+                            + " and only by the active dream package.");
+                    return;
+                }
+
+                final IAppTask appTask = mAtmInternal.startDreamActivity(intent, callingUid,
+                        callingPid);
+                if (appTask == null) {
+                    Slog.e(TAG, "Could not start dream activity.");
+                    stopDreamInternal(true, "DreamActivity not started");
+                    return;
+                }
+                mController.setDreamAppTask(dreamToken, appTask);
+            });
+        }
+
+        boolean canLaunchDreamActivity(String dreamPackageName, String packageName,
+                int callingUid) {
+            if (dreamPackageName == null || packageName == null) {
+                Slog.e(TAG, "Cannot launch dream activity due to invalid state. dream component= "
+                        + dreamPackageName + ", packageName=" + packageName);
+                return false;
+            }
+            if (!mPmInternal.isSameApp(packageName, callingUid, UserHandle.getUserId(callingUid))) {
+                Slog.e(TAG, "Cannot launch dream activity because package="
+                        + packageName + " does not match callingUid=" + callingUid);
+                return false;
+            }
+            if (packageName.equals(dreamPackageName)) {
+                return true;
+            }
+            Slog.e(TAG, "Dream packageName does not match active dream. Package " + packageName
+                    + " does not match " + dreamPackageName);
+            return false;
+        }
+
     }
 
     private final class LocalService extends DreamManagerInternal {
diff --git a/services/core/java/com/android/server/input/AmbientKeyboardBacklightController.java b/services/core/java/com/android/server/input/AmbientKeyboardBacklightController.java
new file mode 100644
index 0000000..ce86849
--- /dev/null
+++ b/services/core/java/com/android/server/input/AmbientKeyboardBacklightController.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.annotation.MainThread;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.display.utils.SensorUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for managing the keyboard
+ * backlight based on ambient light sensor.
+ */
+final class AmbientKeyboardBacklightController implements DisplayManager.DisplayListener,
+        SensorEventListener {
+
+    private static final String TAG = "KbdBacklightController";
+
+    // To enable these logs, run:
+    // 'adb shell setprop log.tag.KbdBacklightController DEBUG' (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // Number of light sensor responses required to overcome temporal hysteresis.
+    @VisibleForTesting
+    public static final int HYSTERESIS_THRESHOLD = 2;
+
+    private static final int MSG_BRIGHTNESS_CALLBACK = 0;
+    private static final int MSG_SETUP_DISPLAY_AND_SENSOR = 1;
+
+    private static final Object sAmbientControllerLock = new Object();
+
+    private final Context mContext;
+    private final Handler mHandler;
+
+    @Nullable
+    @GuardedBy("sAmbientControllerLock")
+    private Sensor mLightSensor;
+    @GuardedBy("sAmbientControllerLock")
+    private String mCurrentDefaultDisplayUniqueId;
+
+    // List of currently registered ambient backlight listeners
+    @GuardedBy("sAmbientControllerLock")
+    private final List<AmbientKeyboardBacklightListener> mAmbientKeyboardBacklightListeners =
+            new ArrayList<>();
+
+    private BrightnessStep[] mBrightnessSteps;
+    private int mCurrentBrightnessStepIndex;
+    private HysteresisState mHysteresisState;
+    private int mHysteresisCount = 0;
+    private float mSmoothingConstant;
+    private int mSmoothedLux;
+    private int mSmoothedLuxAtLastAdjustment;
+
+    private enum HysteresisState {
+        // The most-recent mSmoothedLux matched mSmoothedLuxAtLastAdjustment.
+        STABLE,
+        // The most-recent mSmoothedLux was less than mSmoothedLuxAtLastAdjustment.
+        DECREASING,
+        // The most-recent mSmoothedLux was greater than mSmoothedLuxAtLastAdjustment.
+        INCREASING,
+        // The brightness should be adjusted immediately after the next sensor reading.
+        IMMEDIATE,
+    }
+
+    AmbientKeyboardBacklightController(Context context, Looper looper) {
+        mContext = context;
+        mHandler = new Handler(looper, this::handleMessage);
+        initConfiguration();
+    }
+
+    public void systemRunning() {
+        mHandler.sendEmptyMessage(MSG_SETUP_DISPLAY_AND_SENSOR);
+        DisplayManager displayManager = Objects.requireNonNull(
+                mContext.getSystemService(DisplayManager.class));
+        displayManager.registerDisplayListener(this, mHandler);
+    }
+
+    public void registerAmbientBacklightListener(AmbientKeyboardBacklightListener listener) {
+        synchronized (sAmbientControllerLock) {
+            if (mAmbientKeyboardBacklightListeners.contains(listener)) {
+                throw new IllegalStateException(
+                        "AmbientKeyboardBacklightListener was already registered, listener = "
+                                + listener);
+            }
+            if (mAmbientKeyboardBacklightListeners.isEmpty()) {
+                // Add sensor listener when we add the first ambient backlight listener.
+                addSensorListener(mLightSensor);
+            }
+            mAmbientKeyboardBacklightListeners.add(listener);
+        }
+    }
+
+    public void unregisterAmbientBacklightListener(AmbientKeyboardBacklightListener listener) {
+        synchronized (sAmbientControllerLock) {
+            if (!mAmbientKeyboardBacklightListeners.contains(listener)) {
+                throw new IllegalStateException(
+                        "AmbientKeyboardBacklightListener was never registered, listener = "
+                                + listener);
+            }
+            mAmbientKeyboardBacklightListeners.remove(listener);
+            if (mAmbientKeyboardBacklightListeners.isEmpty()) {
+                removeSensorListener(mLightSensor);
+            }
+        }
+    }
+
+    private void sendBrightnessAdjustment(int brightnessValue) {
+        Message msg = Message.obtain(mHandler, MSG_BRIGHTNESS_CALLBACK, brightnessValue);
+        mHandler.sendMessage(msg);
+    }
+
+    @MainThread
+    private void handleBrightnessCallback(int brightnessValue) {
+        synchronized (sAmbientControllerLock) {
+            for (AmbientKeyboardBacklightListener listener : mAmbientKeyboardBacklightListeners) {
+                listener.onKeyboardBacklightValueChanged(brightnessValue);
+            }
+        }
+    }
+
+    @MainThread
+    private void handleAmbientLuxChange(float rawLux) {
+        if (rawLux < 0) {
+            Slog.w(TAG, "Light sensor doesn't have valid value");
+            return;
+        }
+        updateSmoothedLux(rawLux);
+
+        if (mHysteresisState != HysteresisState.IMMEDIATE
+                && mSmoothedLux == mSmoothedLuxAtLastAdjustment) {
+            mHysteresisState = HysteresisState.STABLE;
+            return;
+        }
+
+        int newStepIndex = Math.max(0, mCurrentBrightnessStepIndex);
+        int numSteps = mBrightnessSteps.length;
+
+        if (mSmoothedLux > mSmoothedLuxAtLastAdjustment) {
+            if (mHysteresisState != HysteresisState.IMMEDIATE
+                    && mHysteresisState != HysteresisState.INCREASING) {
+                if (DEBUG) {
+                    Slog.d(TAG, "ALS transitioned to brightness increasing state");
+                }
+                mHysteresisState = HysteresisState.INCREASING;
+                mHysteresisCount = 0;
+            }
+            for (; newStepIndex < numSteps; newStepIndex++) {
+                if (mSmoothedLux < mBrightnessSteps[newStepIndex].mIncreaseLuxThreshold) {
+                    break;
+                }
+            }
+        } else if (mSmoothedLux < mSmoothedLuxAtLastAdjustment) {
+            if (mHysteresisState != HysteresisState.IMMEDIATE
+                    && mHysteresisState != HysteresisState.DECREASING) {
+                if (DEBUG) {
+                    Slog.d(TAG, "ALS transitioned to brightness decreasing state");
+                }
+                mHysteresisState = HysteresisState.DECREASING;
+                mHysteresisCount = 0;
+            }
+            for (; newStepIndex >= 0; newStepIndex--) {
+                if (mSmoothedLux > mBrightnessSteps[newStepIndex].mDecreaseLuxThreshold) {
+                    break;
+                }
+            }
+        }
+
+        if (mHysteresisState == HysteresisState.IMMEDIATE) {
+            mCurrentBrightnessStepIndex = newStepIndex;
+            mSmoothedLuxAtLastAdjustment = mSmoothedLux;
+            mHysteresisState = HysteresisState.STABLE;
+            mHysteresisCount = 0;
+            sendBrightnessAdjustment(mBrightnessSteps[newStepIndex].mBrightnessValue);
+            return;
+        }
+
+        if (newStepIndex == mCurrentBrightnessStepIndex) {
+            return;
+        }
+
+        mHysteresisCount++;
+        if (DEBUG) {
+            Slog.d(TAG, "Incremented hysteresis count to " + mHysteresisCount + " (lux went from "
+                    + mSmoothedLuxAtLastAdjustment + " to " + mSmoothedLux + ")");
+        }
+        if (mHysteresisCount >= HYSTERESIS_THRESHOLD) {
+            mCurrentBrightnessStepIndex = newStepIndex;
+            mSmoothedLuxAtLastAdjustment = mSmoothedLux;
+            mHysteresisCount = 1;
+            sendBrightnessAdjustment(mBrightnessSteps[newStepIndex].mBrightnessValue);
+        }
+    }
+
+    @MainThread
+    private void handleDisplayChange() {
+        DisplayManagerInternal displayManagerInternal = LocalServices.getService(
+                DisplayManagerInternal.class);
+        DisplayInfo displayInfo = displayManagerInternal.getDisplayInfo(Display.DEFAULT_DISPLAY);
+        synchronized (sAmbientControllerLock) {
+            if (Objects.equals(mCurrentDefaultDisplayUniqueId, displayInfo.uniqueId)) {
+                return;
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "Default display changed: resetting the light sensor");
+            }
+            // Keep track of current default display
+            mCurrentDefaultDisplayUniqueId = displayInfo.uniqueId;
+            // Clear all existing sensor listeners
+            if (!mAmbientKeyboardBacklightListeners.isEmpty()) {
+                removeSensorListener(mLightSensor);
+            }
+            mLightSensor = getAmbientLightSensor(
+                    displayManagerInternal.getAmbientLightSensorData(Display.DEFAULT_DISPLAY));
+            // Re-add sensor listeners if required;
+            if (!mAmbientKeyboardBacklightListeners.isEmpty()) {
+                addSensorListener(mLightSensor);
+            }
+        }
+    }
+
+    private Sensor getAmbientLightSensor(
+            DisplayManagerInternal.AmbientLightSensorData ambientSensor) {
+        SensorManager sensorManager = Objects.requireNonNull(
+                mContext.getSystemService(SensorManager.class));
+        if (DEBUG) {
+            Slog.d(TAG, "Ambient Light sensor data: " + ambientSensor);
+        }
+        return SensorUtils.findSensor(sensorManager, ambientSensor.sensorType,
+                ambientSensor.sensorName, Sensor.TYPE_LIGHT);
+    }
+
+    private void updateSmoothedLux(float rawLux) {
+        // For the first sensor reading, use raw lux value directly without smoothing.
+        if (mHysteresisState == HysteresisState.IMMEDIATE) {
+            mSmoothedLux = (int) rawLux;
+        } else {
+            mSmoothedLux =
+                    (int) (mSmoothingConstant * rawLux + (1 - mSmoothingConstant) * mSmoothedLux);
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "Current smoothed lux from ALS = " + mSmoothedLux);
+        }
+    }
+
+    @VisibleForTesting
+    public void addSensorListener(@Nullable Sensor sensor) {
+        SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
+        if (sensorManager == null || sensor == null) {
+            return;
+        }
+        // Reset values before registering listener
+        reset();
+        sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, mHandler);
+        if (DEBUG) {
+            Slog.d(TAG, "Registering ALS listener");
+        }
+    }
+
+    private void removeSensorListener(@Nullable Sensor sensor) {
+        SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
+        if (sensorManager == null || sensor == null) {
+            return;
+        }
+        sensorManager.unregisterListener(this, sensor);
+        if (DEBUG) {
+            Slog.d(TAG, "Unregistering ALS listener");
+        }
+    }
+
+    private void initConfiguration() {
+        Resources res = mContext.getResources();
+        int[] brightnessValueArray = res.getIntArray(
+                com.android.internal.R.array.config_autoKeyboardBacklightBrightnessValues);
+        int[] decreaseThresholdArray = res.getIntArray(
+                com.android.internal.R.array.config_autoKeyboardBacklightDecreaseLuxThreshold);
+        int[] increaseThresholdArray = res.getIntArray(
+                com.android.internal.R.array.config_autoKeyboardBacklightIncreaseLuxThreshold);
+        if (brightnessValueArray.length != decreaseThresholdArray.length
+                || decreaseThresholdArray.length != increaseThresholdArray.length) {
+            throw new IllegalArgumentException(
+                    "The config files for auto keyboard backlight brightness must contain arrays "
+                            + "of equal lengths");
+        }
+        final int size = brightnessValueArray.length;
+        mBrightnessSteps = new BrightnessStep[size];
+        for (int i = 0; i < size; i++) {
+            int increaseThreshold =
+                    increaseThresholdArray[i] < 0 ? Integer.MAX_VALUE : increaseThresholdArray[i];
+            int decreaseThreshold =
+                    decreaseThresholdArray[i] < 0 ? Integer.MIN_VALUE : decreaseThresholdArray[i];
+            mBrightnessSteps[i] = new BrightnessStep(brightnessValueArray[i], increaseThreshold,
+                    decreaseThreshold);
+        }
+
+        int numSteps = mBrightnessSteps.length;
+        if (numSteps == 0 || mBrightnessSteps[0].mDecreaseLuxThreshold != Integer.MIN_VALUE
+                || mBrightnessSteps[numSteps - 1].mIncreaseLuxThreshold != Integer.MAX_VALUE) {
+            throw new IllegalArgumentException(
+                    "The config files for auto keyboard backlight brightness must contain arrays "
+                            + "of length > 0 and have -1 or Integer.MIN_VALUE as lower bound for "
+                            + "decrease thresholds and -1 or Integer.MAX_VALUE as upper bound for "
+                            + "increase thresholds");
+        }
+
+        final TypedValue smoothingConstantValue = new TypedValue();
+        res.getValue(
+                com.android.internal.R.dimen.config_autoKeyboardBrightnessSmoothingConstant,
+                smoothingConstantValue,
+                true /*resolveRefs*/);
+        mSmoothingConstant = smoothingConstantValue.getFloat();
+        if (mSmoothingConstant <= 0.0 || mSmoothingConstant > 1.0) {
+            throw new IllegalArgumentException(
+                    "The config files for auto keyboard backlight brightness must contain "
+                            + "smoothing constant in range (0.0, 1.0].");
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "Brightness steps: " + Arrays.toString(mBrightnessSteps)
+                    + " Smoothing constant = " + mSmoothingConstant);
+        }
+    }
+
+    private void reset() {
+        mHysteresisState = HysteresisState.IMMEDIATE;
+        mSmoothedLux = 0;
+        mSmoothedLuxAtLastAdjustment = 0;
+        mCurrentBrightnessStepIndex = -1;
+    }
+
+    private boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_BRIGHTNESS_CALLBACK:
+                handleBrightnessCallback((int) msg.obj);
+                return true;
+            case MSG_SETUP_DISPLAY_AND_SENSOR:
+                handleDisplayChange();
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void onSensorChanged(SensorEvent event) {
+        handleAmbientLuxChange(event.values[0]);
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+        handleDisplayChange();
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        handleDisplayChange();
+    }
+
+    @Override
+    public void onDisplayChanged(int displayId) {
+        handleDisplayChange();
+    }
+
+    public interface AmbientKeyboardBacklightListener {
+        /**
+         * @param value between [0, 255] to which keyboard backlight needs to be set according
+         *              to Ambient light sensor.
+         */
+        void onKeyboardBacklightValueChanged(int value);
+    }
+
+    private static class BrightnessStep {
+        private final int mBrightnessValue;
+        private final int mIncreaseLuxThreshold;
+        private final int mDecreaseLuxThreshold;
+
+        private BrightnessStep(int brightnessValue, int increaseLuxThreshold,
+                int decreaseLuxThreshold) {
+            mBrightnessValue = brightnessValue;
+            mIncreaseLuxThreshold = increaseLuxThreshold;
+            mDecreaseLuxThreshold = decreaseLuxThreshold;
+        }
+
+        @Override
+        public String toString() {
+            return "BrightnessStep{" + "mBrightnessValue=" + mBrightnessValue
+                    + ", mIncreaseThreshold=" + mIncreaseLuxThreshold + ", mDecreaseThreshold="
+                    + mDecreaseLuxThreshold + '}';
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index ff9ce6f..38a0d37 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -46,7 +46,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -102,8 +101,9 @@
     @GuardedBy("mLock")
     private BluetoothBatteryManager.BluetoothBatteryListener mBluetoothBatteryListener;
 
-    BatteryController(Context context, NativeInputManagerService nativeService, Looper looper) {
-        this(context, nativeService, looper, new UEventManager() {},
+    BatteryController(Context context, NativeInputManagerService nativeService, Looper looper,
+            UEventManager uEventManager) {
+        this(context, nativeService, looper, uEventManager,
                 new LocalBluetoothBatteryManager(context, looper));
     }
 
@@ -567,7 +567,7 @@
         private BluetoothAdapter.OnMetadataChangedListener mBluetoothMetadataListener;
 
         @Nullable
-        private UEventBatteryListener mUEventBatteryListener;
+        private BatteryController.UEventBatteryListener mUEventBatteryListener;
 
         DeviceMonitor(int deviceId) {
             mState = new State(deviceId);
@@ -630,7 +630,7 @@
                 return;
             }
             final int deviceId = mState.deviceId;
-            mUEventBatteryListener = new UEventBatteryListener() {
+            mUEventBatteryListener = new BatteryController.UEventBatteryListener() {
                 @Override
                 public void onBatteryUEvent(long eventTime) {
                     handleUEventNotification(deviceId, eventTime);
@@ -898,40 +898,25 @@
         }
     }
 
-    // An interface used to change the API of UEventObserver to a more test-friendly format.
     @VisibleForTesting
-    interface UEventManager {
-
-        @VisibleForTesting
-        abstract class UEventBatteryListener {
-            private final UEventObserver mObserver = new UEventObserver() {
-                @Override
-                public void onUEvent(UEvent event) {
-                    final long eventTime = SystemClock.uptimeMillis();
-                    if (DEBUG) {
-                        Slog.d(TAG,
-                                "UEventListener: Received UEvent: "
-                                        + event + " eventTime: " + eventTime);
-                    }
-                    if (!"CHANGE".equalsIgnoreCase(event.get("ACTION"))
-                            || !"POWER_SUPPLY".equalsIgnoreCase(event.get("SUBSYSTEM"))) {
-                        // Disregard any UEvents that do not correspond to battery changes.
-                        return;
-                    }
-                    UEventBatteryListener.this.onBatteryUEvent(eventTime);
-                }
-            };
-
-            public abstract void onBatteryUEvent(long eventTime);
+    abstract static class UEventBatteryListener extends UEventManager.UEventListener {
+        @Override
+        public void onUEvent(UEventObserver.UEvent event) {
+            final long eventTime = SystemClock.uptimeMillis();
+            if (DEBUG) {
+                Slog.d(TAG,
+                        "UEventListener: Received UEvent: "
+                                + event + " eventTime: " + eventTime);
+            }
+            if (!"CHANGE".equalsIgnoreCase(event.get("ACTION"))
+                    || !"POWER_SUPPLY".equalsIgnoreCase(event.get("SUBSYSTEM"))) {
+                // Disregard any UEvents that do not correspond to battery changes.
+                return;
+            }
+            UEventBatteryListener.this.onBatteryUEvent(eventTime);
         }
 
-        default void addListener(UEventBatteryListener listener, String match) {
-            listener.mObserver.startObserving(match);
-        }
-
-        default void removeListener(UEventBatteryListener listener) {
-            listener.mObserver.stopObserving();
-        }
+        public abstract void onBatteryUEvent(long eventTime);
     }
 
     // An interface used to change the API of adding a bluetooth battery listener to a more
diff --git a/services/core/java/com/android/server/input/InputFeatureFlagProvider.java b/services/core/java/com/android/server/input/InputFeatureFlagProvider.java
new file mode 100644
index 0000000..a646d1e
--- /dev/null
+++ b/services/core/java/com/android/server/input/InputFeatureFlagProvider.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.sysprop.InputProperties;
+
+import java.util.Optional;
+
+/**
+ * A component of {@link InputManagerService} responsible for managing the input sysprop flags
+ *
+ * @hide
+ */
+@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+public final class InputFeatureFlagProvider {
+
+    // To disable Keyboard backlight control via Framework, run:
+    // 'adb shell setprop persist.input.keyboard_backlight_control.enabled false' (requires restart)
+    private static final boolean KEYBOARD_BACKLIGHT_CONTROL_ENABLED =
+            InputProperties.enable_keyboard_backlight_control().orElse(true);
+
+    // To disable Framework controlled keyboard backlight animation run:
+    // adb shell setprop persist.input.keyboard.backlight_animation.enabled false (requires restart)
+    private static final boolean KEYBOARD_BACKLIGHT_ANIMATION_ENABLED =
+            InputProperties.enable_keyboard_backlight_animation().orElse(false);
+
+    // To disable Custom keyboard backlight levels support via IDC files run:
+    // adb shell setprop persist.input.keyboard.backlight_custom_levels.enabled false (requires
+    // restart)
+    private static final boolean KEYBOARD_BACKLIGHT_CUSTOM_LEVELS_ENABLED =
+            InputProperties.enable_keyboard_backlight_custom_levels().orElse(true);
+
+    // To disable als based ambient keyboard backlight control run:
+    // adb shell setprop persist.input.keyboard.ambient_backlight_control.enabled false (requires
+    // restart)
+    private static final boolean AMBIENT_KEYBOARD_BACKLIGHT_CONTROL_ENABLED =
+            InputProperties.enable_ambient_keyboard_backlight_control().orElse(true);
+
+    private static Optional<Boolean> sKeyboardBacklightControlOverride = Optional.empty();
+    private static Optional<Boolean> sKeyboardBacklightAnimationOverride = Optional.empty();
+    private static Optional<Boolean> sKeyboardBacklightCustomLevelsOverride = Optional.empty();
+    private static Optional<Boolean> sAmbientKeyboardBacklightControlOverride = Optional.empty();
+
+    public static boolean isKeyboardBacklightControlEnabled() {
+        return sKeyboardBacklightControlOverride.orElse(KEYBOARD_BACKLIGHT_CONTROL_ENABLED);
+    }
+
+    public static boolean isKeyboardBacklightAnimationEnabled() {
+        return sKeyboardBacklightAnimationOverride.orElse(KEYBOARD_BACKLIGHT_ANIMATION_ENABLED);
+    }
+
+    public static boolean isKeyboardBacklightCustomLevelsEnabled() {
+        return sKeyboardBacklightCustomLevelsOverride.orElse(
+                KEYBOARD_BACKLIGHT_CUSTOM_LEVELS_ENABLED);
+    }
+
+    public static boolean isAmbientKeyboardBacklightControlEnabled() {
+        return sAmbientKeyboardBacklightControlOverride.orElse(
+                AMBIENT_KEYBOARD_BACKLIGHT_CONTROL_ENABLED);
+    }
+
+    public static void setKeyboardBacklightControlEnabled(boolean enabled) {
+        sKeyboardBacklightControlOverride = Optional.of(enabled);
+    }
+
+    public static void setKeyboardBacklightAnimationEnabled(boolean enabled) {
+        sKeyboardBacklightAnimationOverride = Optional.of(enabled);
+    }
+
+    public static void setKeyboardBacklightCustomLevelsEnabled(boolean enabled) {
+        sKeyboardBacklightCustomLevelsOverride = Optional.of(enabled);
+    }
+
+    public static void setAmbientKeyboardBacklightControlEnabled(boolean enabled) {
+        sAmbientKeyboardBacklightControlOverride = Optional.of(enabled);
+    }
+
+    /**
+     * Clears all input feature flag overrides.
+     */
+    public static void clearOverrides() {
+        sKeyboardBacklightControlOverride = Optional.empty();
+        sKeyboardBacklightAnimationOverride = Optional.empty();
+        sKeyboardBacklightCustomLevelsOverride = Optional.empty();
+        sAmbientKeyboardBacklightControlOverride = Optional.empty();
+    }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 4cb22db..5c80291 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -74,7 +74,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.os.vibrator.StepSegment;
@@ -117,6 +116,7 @@
 import com.android.server.LocalServices;
 import com.android.server.Watchdog;
 import com.android.server.input.InputManagerInternal.LidSwitchCallback;
+import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
 
 import libcore.io.IoUtils;
@@ -160,17 +160,14 @@
     private static final AdditionalDisplayInputProperties
             DEFAULT_ADDITIONAL_DISPLAY_INPUT_PROPERTIES = new AdditionalDisplayInputProperties();
 
-    // To disable Keyboard backlight control via Framework, run:
-    // 'adb shell setprop persist.input.keyboard_backlight_control.enabled false' (requires restart)
-    private static final boolean KEYBOARD_BACKLIGHT_CONTROL_ENABLED = SystemProperties.getBoolean(
-            "persist.input.keyboard.backlight_control.enabled", true);
-
     private final NativeInputManagerService mNative;
 
     private final Context mContext;
     private final InputManagerHandler mHandler;
     private DisplayManagerInternal mDisplayManagerInternal;
 
+    private InputMethodManagerInternal mInputMethodManagerInternal;
+
     // Context cache used for loading pointer resources.
     private Context mPointerIconDisplayContext;
 
@@ -399,10 +396,12 @@
     static class Injector {
         private final Context mContext;
         private final Looper mLooper;
+        private final UEventManager mUEventManager;
 
-        Injector(Context context, Looper looper) {
+        Injector(Context context, Looper looper, UEventManager uEventManager) {
             mContext = context;
             mLooper = looper;
+            mUEventManager = uEventManager;
         }
 
         Context getContext() {
@@ -413,6 +412,10 @@
             return mLooper;
         }
 
+        UEventManager getUEventManager() {
+            return mUEventManager;
+        }
+
         NativeInputManagerService getNativeService(InputManagerService service) {
             return new NativeInputManagerService.NativeImpl(service, mLooper.getQueue());
         }
@@ -423,7 +426,7 @@
     }
 
     public InputManagerService(Context context) {
-        this(new Injector(context, DisplayThread.get().getLooper()));
+        this(new Injector(context, DisplayThread.get().getLooper(), new UEventManager() {}));
     }
 
     @VisibleForTesting
@@ -438,11 +441,12 @@
         mSettingsObserver = new InputSettingsObserver(mContext, mHandler, this, mNative);
         mKeyboardLayoutManager = new KeyboardLayoutManager(mContext, mNative, mDataStore,
                 injector.getLooper());
-        mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
-        mKeyboardBacklightController =
-                KEYBOARD_BACKLIGHT_CONTROL_ENABLED ? new KeyboardBacklightController(mContext,
-                        mNative, mDataStore, injector.getLooper())
-                        : new KeyboardBacklightControllerInterface() {};
+        mBatteryController = new BatteryController(mContext, mNative, injector.getLooper(),
+                injector.getUEventManager());
+        mKeyboardBacklightController = InputFeatureFlagProvider.isKeyboardBacklightControlEnabled()
+                ? new KeyboardBacklightController(mContext, mNative, mDataStore,
+                        injector.getLooper(), injector.getUEventManager())
+                : new KeyboardBacklightControllerInterface() {};
         mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
 
         mUseDevInputEventForAudioJack =
@@ -509,6 +513,8 @@
         }
 
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+        mInputMethodManagerInternal =
+                LocalServices.getService(InputMethodManagerInternal.class);
 
         mSettingsObserver.registerAndUpdate();
 
@@ -2789,6 +2795,13 @@
                         yPosition)).sendToTarget();
     }
 
+    // Native callback.
+    @SuppressWarnings("unused")
+    boolean isInputMethodConnectionActive() {
+        return mInputMethodManagerInternal != null
+                && mInputMethodManagerInternal.isAnyInputConnectionActive();
+    }
+
     /**
      * Callback interface implemented by the Window Manager.
      */
@@ -2799,6 +2812,11 @@
         void notifyConfigurationChanged();
 
         /**
+         * This callback is invoked when the pointer location changes.
+         */
+        void notifyPointerLocationChanged(boolean pointerLocationEnabled);
+
+        /**
          * This callback is invoked when the camera lens cover switch changes state.
          * @param whenNanos the time when the change occurred
          * @param lensCovered true is the lens is covered
@@ -3380,6 +3398,10 @@
         }
     }
 
+    void updatePointerLocationEnabled(boolean enabled) {
+        mWindowManagerCallbacks.notifyPointerLocationChanged(enabled);
+    }
+
     void updateFocusEventDebugViewEnabled(boolean enabled) {
         FocusEventDebugView view;
         synchronized (mFocusEventDebugViewLock) {
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 651063e..a608b4f 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -67,6 +67,8 @@
                         (reason) -> updateTouchpadRightClickZoneEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
                         (reason) -> updateShowTouches()),
+                Map.entry(Settings.System.getUriFor(Settings.System.POINTER_LOCATION),
+                        (reason) -> updatePointerLocation()),
                 Map.entry(
                         Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON),
                         (reason) -> updateAccessibilityLargePointer()),
@@ -115,41 +117,45 @@
         return setting != 0;
     }
 
-    private int getPointerSpeedValue(String settingName) {
-        int speed = Settings.System.getIntForUser(mContext.getContentResolver(),
-                settingName, InputSettings.DEFAULT_POINTER_SPEED, UserHandle.USER_CURRENT);
+    private int constrainPointerSpeedValue(int speed) {
         return Math.min(Math.max(speed, InputSettings.MIN_POINTER_SPEED),
                 InputSettings.MAX_POINTER_SPEED);
     }
 
     private void updateMousePointerSpeed() {
-        mNative.setPointerSpeed(getPointerSpeedValue(Settings.System.POINTER_SPEED));
+        int speed = Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.POINTER_SPEED, InputSettings.DEFAULT_POINTER_SPEED,
+                UserHandle.USER_CURRENT);
+        mNative.setPointerSpeed(constrainPointerSpeedValue(speed));
     }
 
     private void updateTouchpadPointerSpeed() {
         mNative.setTouchpadPointerSpeed(
-                getPointerSpeedValue(Settings.System.TOUCHPAD_POINTER_SPEED));
+                constrainPointerSpeedValue(InputSettings.getTouchpadPointerSpeed(mContext)));
     }
 
     private void updateTouchpadNaturalScrollingEnabled() {
         mNative.setTouchpadNaturalScrollingEnabled(
-                getBoolean(Settings.System.TOUCHPAD_NATURAL_SCROLLING, true));
+                InputSettings.useTouchpadNaturalScrolling(mContext));
     }
 
     private void updateTouchpadTapToClickEnabled() {
-        mNative.setTouchpadTapToClickEnabled(
-                getBoolean(Settings.System.TOUCHPAD_TAP_TO_CLICK, true));
+        mNative.setTouchpadTapToClickEnabled(InputSettings.useTouchpadTapToClick(mContext));
     }
 
     private void updateTouchpadRightClickZoneEnabled() {
-        mNative.setTouchpadRightClickZoneEnabled(
-                getBoolean(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE, false));
+        mNative.setTouchpadRightClickZoneEnabled(InputSettings.useTouchpadRightClickZone(mContext));
     }
 
     private void updateShowTouches() {
         mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
     }
 
+    private void updatePointerLocation() {
+        mService.updatePointerLocationEnabled(
+                getBoolean(Settings.System.POINTER_LOCATION, false));
+    }
+
     private void updateShowKeyPresses() {
         mService.updateFocusEventDebugViewEnabled(
                 getBoolean(Settings.System.SHOW_KEY_PRESSES, false));
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index 48c346a..c3205af 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -16,7 +16,9 @@
 
 package com.android.server.input;
 
+import android.animation.ValueAnimator;
 import android.annotation.BinderThread;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Color;
 import android.hardware.input.IKeyboardBacklightListener;
@@ -45,6 +47,7 @@
 import java.util.Arrays;
 import java.util.Objects;
 import java.util.OptionalInt;
+import java.util.TreeSet;
 
 /**
  * A thread-safe component of {@link InputManagerService} responsible for managing the keyboard
@@ -69,7 +72,11 @@
     private static final int MSG_NOTIFY_USER_INACTIVITY = 5;
     private static final int MSG_INTERACTIVE_STATE_CHANGED = 6;
     private static final int MAX_BRIGHTNESS = 255;
-    private static final int NUM_BRIGHTNESS_CHANGE_STEPS = 10;
+    private static final int DEFAULT_NUM_BRIGHTNESS_CHANGE_STEPS = 10;
+    @VisibleForTesting
+    static final int MAX_BRIGHTNESS_CHANGE_STEPS = 10;
+    private static final long TRANSITION_ANIMATION_DURATION_MILLIS =
+            Duration.ofSeconds(1).toMillis();
 
     private static final String UEVENT_KEYBOARD_BACKLIGHT_TAG = "kbd_backlight";
 
@@ -77,7 +84,8 @@
     static final long USER_INACTIVITY_THRESHOLD_MILLIS = Duration.ofSeconds(30).toMillis();
 
     @VisibleForTesting
-    static final int[] BRIGHTNESS_VALUE_FOR_LEVEL = new int[NUM_BRIGHTNESS_CHANGE_STEPS + 1];
+    static final int[] DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL =
+            new int[DEFAULT_NUM_BRIGHTNESS_CHANGE_STEPS + 1];
 
     private final Context mContext;
     private final NativeInputManagerService mNative;
@@ -85,6 +93,8 @@
     @GuardedBy("mDataStore")
     private final PersistentDataStore mDataStore;
     private final Handler mHandler;
+    private final AnimatorFactory mAnimatorFactory;
+    private final UEventManager mUEventManager;
     // Always access on handler thread or need to lock this for synchronization.
     private final SparseArray<KeyboardBacklightState> mKeyboardBacklights = new SparseArray<>(1);
     // Maintains state if all backlights should be on or turned off
@@ -97,22 +107,38 @@
     private final SparseArray<KeyboardBacklightListenerRecord> mKeyboardBacklightListenerRecords =
             new SparseArray<>();
 
+    private final AmbientKeyboardBacklightController mAmbientController;
+    @Nullable
+    private AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener mAmbientListener;
+
+    private int mAmbientBacklightValue = 0;
+
     static {
         // Fixed brightness levels to avoid issues when converting back and forth from the
         // device brightness range to [0-255]
-        // Levels are: 0, 25, 51, ..., 255
-        for (int i = 0; i <= NUM_BRIGHTNESS_CHANGE_STEPS; i++) {
-            BRIGHTNESS_VALUE_FOR_LEVEL[i] = (int) Math.floor(
-                    ((float) i * MAX_BRIGHTNESS) / NUM_BRIGHTNESS_CHANGE_STEPS);
+        // Levels are: 0, 51, ..., 255
+        for (int i = 0; i <= DEFAULT_NUM_BRIGHTNESS_CHANGE_STEPS; i++) {
+            DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[i] = (int) Math.floor(
+                    ((float) i * MAX_BRIGHTNESS) / DEFAULT_NUM_BRIGHTNESS_CHANGE_STEPS);
         }
     }
 
     KeyboardBacklightController(Context context, NativeInputManagerService nativeService,
-            PersistentDataStore dataStore, Looper looper) {
+            PersistentDataStore dataStore, Looper looper, UEventManager uEventManager) {
+        this(context, nativeService, dataStore, looper, ValueAnimator::ofInt, uEventManager);
+    }
+
+    @VisibleForTesting
+    KeyboardBacklightController(Context context, NativeInputManagerService nativeService,
+            PersistentDataStore dataStore, Looper looper, AnimatorFactory animatorFactory,
+            UEventManager uEventManager) {
         mContext = context;
         mNative = nativeService;
         mDataStore = dataStore;
         mHandler = new Handler(looper, this::handleMessage);
+        mAnimatorFactory = animatorFactory;
+        mAmbientController = new AmbientKeyboardBacklightController(context, looper);
+        mUEventManager = uEventManager;
     }
 
     @Override
@@ -129,13 +155,17 @@
         // We want to observe creation of such LED nodes since they might be created after device
         // FD created and InputDevice creation logic doesn't initialize LED nodes which leads to
         // backlight not working.
-        UEventObserver observer = new UEventObserver() {
+        mUEventManager.addListener(new UEventManager.UEventListener() {
             @Override
-            public void onUEvent(UEvent event) {
+            public void onUEvent(UEventObserver.UEvent event) {
                 onKeyboardBacklightUEvent(event);
             }
-        };
-        observer.startObserving(UEVENT_KEYBOARD_BACKLIGHT_TAG);
+        }, UEVENT_KEYBOARD_BACKLIGHT_TAG);
+
+        if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
+            // Start ambient backlight controller
+            mAmbientController.systemRunning();
+        }
     }
 
     @Override
@@ -168,43 +198,89 @@
         if (inputDevice == null || state == null) {
             return;
         }
-        Light keyboardBacklight = state.mLight;
         // Follow preset levels of brightness defined in BRIGHTNESS_LEVELS
-        final int currBrightnessLevel = state.mBrightnessLevel;
+        final int currBrightnessLevel;
+        if (state.mUseAmbientController) {
+            int index = Arrays.binarySearch(state.mBrightnessValueForLevel, mAmbientBacklightValue);
+            // Set current level to the lower bound of the ambient value in the brightness array.
+            if (index < 0) {
+                int lowerBound = Math.max(0, -(index + 1) - 1);
+                currBrightnessLevel =
+                        direction == Direction.DIRECTION_UP ? lowerBound : lowerBound + 1;
+            } else {
+                currBrightnessLevel = index;
+            }
+        } else {
+            currBrightnessLevel = state.mBrightnessLevel;
+        }
         final int newBrightnessLevel;
         if (direction == Direction.DIRECTION_UP) {
-            newBrightnessLevel = Math.min(currBrightnessLevel + 1, NUM_BRIGHTNESS_CHANGE_STEPS);
+            newBrightnessLevel = Math.min(currBrightnessLevel + 1,
+                    state.getNumBrightnessChangeSteps());
         } else {
             newBrightnessLevel = Math.max(currBrightnessLevel - 1, 0);
         }
-        updateBacklightState(deviceId, keyboardBacklight, newBrightnessLevel,
-                true /* isTriggeredByKeyPress */);
 
+        state.setBrightnessLevel(newBrightnessLevel);
+
+        // Might need to stop listening to ALS since user has manually selected backlight
+        // level through keyboard up/down button
+        updateAmbientLightListener();
+
+        maybeBackupBacklightBrightness(inputDevice, state.mLight,
+                state.mBrightnessValueForLevel[newBrightnessLevel]);
+
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "Changing state from " + state.mBrightnessLevel + " to " + newBrightnessLevel);
+        }
+
+        synchronized (mKeyboardBacklightListenerRecords) {
+            for (int i = 0; i < mKeyboardBacklightListenerRecords.size(); i++) {
+                IKeyboardBacklightState callbackState = new IKeyboardBacklightState();
+                callbackState.brightnessLevel = newBrightnessLevel;
+                callbackState.maxBrightnessLevel = state.getNumBrightnessChangeSteps();
+                mKeyboardBacklightListenerRecords.valueAt(i).notifyKeyboardBacklightChanged(
+                        deviceId, callbackState, true);
+            }
+        }
+    }
+
+    private void maybeBackupBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight,
+            int brightnessValue) {
+        // Don't back up or restore when ALS based keyboard backlight is enabled
+        if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
+            return;
+        }
         synchronized (mDataStore) {
             try {
                 mDataStore.setKeyboardBacklightBrightness(inputDevice.getDescriptor(),
                         keyboardBacklight.getId(),
-                        BRIGHTNESS_VALUE_FOR_LEVEL[newBrightnessLevel]);
+                        brightnessValue);
             } finally {
                 mDataStore.saveIfNeeded();
             }
         }
     }
 
-    private void restoreBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight) {
+    private void maybeRestoreBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight) {
+        // Don't back up or restore when ALS based keyboard backlight is enabled
+        if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
+            return;
+        }
+        KeyboardBacklightState state = mKeyboardBacklights.get(inputDevice.getId());
         OptionalInt brightness;
         synchronized (mDataStore) {
             brightness = mDataStore.getKeyboardBacklightBrightness(
                     inputDevice.getDescriptor(), keyboardBacklight.getId());
         }
-        if (brightness.isPresent()) {
+        if (state != null && brightness.isPresent()) {
             int brightnessValue = Math.max(0, Math.min(MAX_BRIGHTNESS, brightness.getAsInt()));
-            int index = Arrays.binarySearch(BRIGHTNESS_VALUE_FOR_LEVEL, brightnessValue);
-            if (index < 0) {
-                index = Math.min(NUM_BRIGHTNESS_CHANGE_STEPS, -(index + 1));
+            int newLevel = Arrays.binarySearch(state.mBrightnessValueForLevel, brightnessValue);
+            if (newLevel < 0) {
+                newLevel = Math.min(state.getNumBrightnessChangeSteps(), -(newLevel + 1));
             }
-            updateBacklightState(inputDevice.getId(), keyboardBacklight, index,
-                    false /* isTriggeredByKeyPress */);
+            state.setBrightnessLevel(newLevel);
             if (DEBUG) {
                 Slog.d(TAG, "Restoring brightness level " + brightness.getAsInt());
             }
@@ -217,14 +293,10 @@
         if (!mIsInteractive) {
             return;
         }
-        if (!mIsBacklightOn) {
-            mIsBacklightOn = true;
-            for (int i = 0; i < mKeyboardBacklights.size(); i++) {
-                int deviceId = mKeyboardBacklights.keyAt(i);
-                KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
-                updateBacklightState(deviceId, state.mLight, state.mBrightnessLevel,
-                        false /* isTriggeredByKeyPress */);
-            }
+        mIsBacklightOn = true;
+        for (int i = 0; i < mKeyboardBacklights.size(); i++) {
+            KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
+            state.onBacklightStateChanged();
         }
         mHandler.removeMessages(MSG_NOTIFY_USER_INACTIVITY);
         mHandler.sendEmptyMessageAtTime(MSG_NOTIFY_USER_INACTIVITY,
@@ -232,14 +304,10 @@
     }
 
     private void handleUserInactivity() {
-        if (mIsBacklightOn) {
-            mIsBacklightOn = false;
-            for (int i = 0; i < mKeyboardBacklights.size(); i++) {
-                int deviceId = mKeyboardBacklights.keyAt(i);
-                KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
-                updateBacklightState(deviceId, state.mLight, state.mBrightnessLevel,
-                        false /* isTriggeredByKeyPress */);
-            }
+        mIsBacklightOn = false;
+        for (int i = 0; i < mKeyboardBacklights.size(); i++) {
+            KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
+            state.onBacklightStateChanged();
         }
     }
 
@@ -253,6 +321,16 @@
         } else {
             handleUserInactivity();
         }
+        updateAmbientLightListener();
+    }
+
+    @VisibleForTesting
+    public void handleAmbientLightValueChanged(int brightnessValue) {
+        mAmbientBacklightValue = brightnessValue;
+        for (int i = 0; i < mKeyboardBacklights.size(); i++) {
+            KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
+            state.onAmbientBacklightValueChanged();
+        }
     }
 
     private boolean handleMessage(Message msg) {
@@ -285,12 +363,14 @@
     @Override
     public void onInputDeviceAdded(int deviceId) {
         onInputDeviceChanged(deviceId);
+        updateAmbientLightListener();
     }
 
     @VisibleForTesting
     @Override
     public void onInputDeviceRemoved(int deviceId) {
         mKeyboardBacklights.remove(deviceId);
+        updateAmbientLightListener();
     }
 
     @VisibleForTesting
@@ -310,8 +390,8 @@
             return;
         }
         // The keyboard backlight was added or changed.
-        mKeyboardBacklights.put(deviceId, new KeyboardBacklightState(keyboardBacklight));
-        restoreBacklightBrightness(inputDevice, keyboardBacklight);
+        mKeyboardBacklights.put(deviceId, new KeyboardBacklightState(deviceId, keyboardBacklight));
+        maybeRestoreBacklightBrightness(inputDevice, keyboardBacklight);
     }
 
     private InputDevice getInputDevice(int deviceId) {
@@ -372,33 +452,6 @@
         }
     }
 
-    private void updateBacklightState(int deviceId, Light light, int brightnessLevel,
-            boolean isTriggeredByKeyPress) {
-        KeyboardBacklightState state = mKeyboardBacklights.get(deviceId);
-        if (state == null) {
-            return;
-        }
-
-        mNative.setLightColor(deviceId, light.getId(),
-                mIsBacklightOn ? Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[brightnessLevel], 0, 0, 0)
-                        : 0);
-        if (DEBUG) {
-            Slog.d(TAG, "Changing state from " + state.mBrightnessLevel + " to " + brightnessLevel
-                    + "(isBacklightOn = " + mIsBacklightOn + ")");
-        }
-        state.mBrightnessLevel = brightnessLevel;
-
-        synchronized (mKeyboardBacklightListenerRecords) {
-            for (int i = 0; i < mKeyboardBacklightListenerRecords.size(); i++) {
-                IKeyboardBacklightState callbackState = new IKeyboardBacklightState();
-                callbackState.brightnessLevel = brightnessLevel;
-                callbackState.maxBrightnessLevel = NUM_BRIGHTNESS_CHANGE_STEPS;
-                mKeyboardBacklightListenerRecords.valueAt(i).notifyKeyboardBacklightChanged(
-                        deviceId, callbackState, isTriggeredByKeyPress);
-            }
-        }
-    }
-
     private void onKeyboardBacklightListenerDied(int pid) {
         synchronized (mKeyboardBacklightListenerRecords) {
             mKeyboardBacklightListenerRecords.remove(pid);
@@ -416,6 +469,25 @@
         }
     }
 
+    private void updateAmbientLightListener() {
+        if (!InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
+            return;
+        }
+        boolean needToListenAmbientLightSensor = false;
+        for (int i = 0; i < mKeyboardBacklights.size(); i++) {
+            needToListenAmbientLightSensor |= mKeyboardBacklights.valueAt(i).mUseAmbientController;
+        }
+        needToListenAmbientLightSensor &= mIsInteractive;
+        if (needToListenAmbientLightSensor && mAmbientListener == null) {
+            mAmbientListener = this::handleAmbientLightValueChanged;
+            mAmbientController.registerAmbientBacklightListener(mAmbientListener);
+        }
+        if (!needToListenAmbientLightSensor && mAmbientListener != null) {
+            mAmbientController.unregisterAmbientBacklightListener(mAmbientListener);
+            mAmbientListener = null;
+        }
+    }
+
     private static boolean isValidBacklightNodePath(String devPath) {
         if (TextUtils.isEmpty(devPath)) {
             return false;
@@ -436,10 +508,7 @@
     @Override
     public void dump(PrintWriter pw) {
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
-        ipw.println(
-                TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights, isBacklightOn = "
-                        + mIsBacklightOn);
-
+        ipw.println(TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights");
         ipw.increaseIndent();
         for (int i = 0; i < mKeyboardBacklights.size(); i++) {
             KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
@@ -478,12 +547,99 @@
         }
     }
 
-    private static class KeyboardBacklightState {
+    private class KeyboardBacklightState {
+        private final int mDeviceId;
         private final Light mLight;
         private int mBrightnessLevel;
+        private ValueAnimator mAnimator;
+        private final int[] mBrightnessValueForLevel;
+        private boolean mUseAmbientController =
+                InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled();
 
-        KeyboardBacklightState(Light light) {
+        KeyboardBacklightState(int deviceId, Light light) {
+            mDeviceId = deviceId;
             mLight = light;
+            mBrightnessValueForLevel = setupBrightnessLevels();
+        }
+
+        private int[] setupBrightnessLevels() {
+            if (!InputFeatureFlagProvider.isKeyboardBacklightCustomLevelsEnabled()) {
+                return DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL;
+            }
+            int[] customLevels = mLight.getPreferredBrightnessLevels();
+            if (customLevels == null || customLevels.length == 0) {
+                return DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL;
+            }
+            TreeSet<Integer> brightnessLevels = new TreeSet<>();
+            brightnessLevels.add(0);
+            for (int level : customLevels) {
+                if (level > 0 && level < MAX_BRIGHTNESS) {
+                    brightnessLevels.add(level);
+                }
+            }
+            brightnessLevels.add(MAX_BRIGHTNESS);
+            int brightnessChangeSteps = brightnessLevels.size() - 1;
+            if (brightnessChangeSteps > MAX_BRIGHTNESS_CHANGE_STEPS) {
+                return DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL;
+            }
+            int[] result = new int[brightnessLevels.size()];
+            int index = 0;
+            for (int val : brightnessLevels) {
+                result[index++] = val;
+            }
+            return result;
+        }
+
+        private int getNumBrightnessChangeSteps() {
+            return mBrightnessValueForLevel.length - 1;
+        }
+
+        private void onBacklightStateChanged() {
+            int toValue = mUseAmbientController ? mAmbientBacklightValue
+                    : mBrightnessValueForLevel[mBrightnessLevel];
+            setBacklightValue(mIsBacklightOn ? toValue : 0);
+        }
+        private void setBrightnessLevel(int brightnessLevel) {
+            // Once we manually set level, disregard ambient light controller
+            mUseAmbientController = false;
+            if (mIsBacklightOn) {
+                setBacklightValue(mBrightnessValueForLevel[brightnessLevel]);
+            }
+            mBrightnessLevel = brightnessLevel;
+        }
+
+        private void onAmbientBacklightValueChanged() {
+            if (mIsBacklightOn && mUseAmbientController) {
+                setBacklightValue(mAmbientBacklightValue);
+            }
+        }
+
+        private void cancelAnimation() {
+            if (mAnimator != null && mAnimator.isRunning()) {
+                mAnimator.cancel();
+            }
+        }
+
+        private void setBacklightValue(int toValue) {
+            int fromValue = Color.alpha(mNative.getLightColor(mDeviceId, mLight.getId()));
+            if (fromValue == toValue) {
+                return;
+            }
+            if (InputFeatureFlagProvider.isKeyboardBacklightAnimationEnabled()) {
+                startAnimation(fromValue, toValue);
+            } else {
+                mNative.setLightColor(mDeviceId, mLight.getId(), Color.argb(toValue, 0, 0, 0));
+            }
+        }
+
+        private void startAnimation(int fromValue, int toValue) {
+            // Cancel any ongoing animation before starting a new one
+            cancelAnimation();
+            mAnimator = mAnimatorFactory.makeIntAnimator(fromValue, toValue);
+            mAnimator.addUpdateListener(
+                    (animation) -> mNative.setLightColor(mDeviceId, mLight.getId(),
+                            Color.argb((int) animation.getAnimatedValue(), 0, 0, 0)));
+            mAnimator.setDuration(TRANSITION_ANIMATION_DURATION_MILLIS).start();
         }
 
         @Override
@@ -493,4 +649,9 @@
                     + "}";
         }
     }
+
+    @VisibleForTesting
+    interface AnimatorFactory {
+        ValueAnimator makeIntAnimator(int from, int to);
+    }
 }
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 6ec4022..7a8de34 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -16,6 +16,11 @@
 
 package com.android.server.input;
 
+import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT;
+import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE;
+import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER;
+import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
@@ -24,6 +29,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.settings.SettingsEnums;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -67,6 +73,8 @@
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.XmlUtils;
+import com.android.server.input.KeyboardMetricsCollector.KeyboardConfigurationEvent;
+import com.android.server.input.KeyboardMetricsCollector.LayoutSelectionCriteria;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 
 import libcore.io.Streams;
@@ -118,7 +126,7 @@
     // This cache stores "best-matched" layouts so that we don't need to run the matching
     // algorithm repeatedly.
     @GuardedBy("mKeyboardLayoutCache")
-    private final Map<String, String> mKeyboardLayoutCache = new ArrayMap<>();
+    private final Map<String, KeyboardLayoutInfo> mKeyboardLayoutCache = new ArrayMap<>();
     private final Object mImeInfoLock = new Object();
     @Nullable
     @GuardedBy("mImeInfoLock")
@@ -194,7 +202,8 @@
                         setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
                     }
                 }
-                config.setCurrentLayout(layout);
+                config.setCurrentLayout(
+                        new KeyboardLayoutInfo(layout, LAYOUT_SELECTION_CRITERIA_USER));
                 if (layout == null) {
                     // In old settings show notification always until user manually selects a
                     // layout in the settings.
@@ -205,18 +214,19 @@
             final InputDeviceIdentifier identifier = inputDevice.getIdentifier();
             final String key = getLayoutDescriptor(identifier);
             Set<String> selectedLayouts = new HashSet<>();
-            for (ImeInfo imeInfo : getImeInfoListForLayoutMapping()) {
+            List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping();
+            List<KeyboardLayoutInfo> layoutInfoList = new ArrayList<>();
+            boolean hasMissingLayout = false;
+            for (ImeInfo imeInfo : imeInfoList) {
                 // Check if the layout has been previously configured
-                String layout = getKeyboardLayoutForInputDeviceInternal(identifier,
-                        new ImeInfo(imeInfo.mUserId, imeInfo.mImeSubtypeHandle,
-                                imeInfo.mImeSubtype));
-                if (layout == null) {
-                    // If even one layout not configured properly, we need to ask user to configure
-                    // the keyboard properly from the Settings.
-                    selectedLayouts.clear();
-                    break;
+                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(identifier,
+                        imeInfo);
+                boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null;
+                if (!noLayoutFound) {
+                    selectedLayouts.add(layoutInfo.mDescriptor);
                 }
-                selectedLayouts.add(layout);
+                layoutInfoList.add(layoutInfo);
+                hasMissingLayout |= noLayoutFound;
             }
 
             if (DEBUG) {
@@ -225,24 +235,38 @@
                                 + selectedLayouts);
             }
 
+            // If even one layout not configured properly, we need to ask user to configure
+            // the keyboard properly from the Settings.
+            if (hasMissingLayout) {
+                selectedLayouts.clear();
+            }
+
             config.setConfiguredLayouts(selectedLayouts);
 
             // Update current layout: If there is a change then need to reload.
             synchronized (mImeInfoLock) {
-                String layout = getKeyboardLayoutForInputDeviceInternal(
+                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
                         inputDevice.getIdentifier(), mCurrentImeInfo);
-                if (!Objects.equals(layout, config.getCurrentLayout())) {
-                    config.setCurrentLayout(layout);
+                if (!Objects.equals(layoutInfo, config.getCurrentLayout())) {
+                    config.setCurrentLayout(layoutInfo);
                     mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
                 }
             }
 
             synchronized (mDataStore) {
                 try {
+                    boolean isFirstConfiguration = !mDataStore.hasInputDeviceEntry(key);
                     if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
                         // Need to show the notification only if layout selection changed
                         // from the previous configuration
                         needToShowNotification = true;
+
+                        // Logging keyboard configuration data to statsd only if the
+                        // configuration changed from the previous configuration. Currently
+                        // only logging for New Settings UI where we are using IME to decide
+                        // the layout information.
+                        logKeyboardConfigurationEvent(inputDevice, imeInfoList, layoutInfoList,
+                                isFirstConfiguration);
                     }
                 } finally {
                     mDataStore.saveIfNeeded();
@@ -401,7 +425,7 @@
 
     @AnyThread
     @Nullable
-    public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
+    public KeyboardLayout getKeyboardLayout(@NonNull String keyboardLayoutDescriptor) {
         Objects.requireNonNull(keyboardLayoutDescriptor,
                 "keyboardLayoutDescriptor must not be null");
 
@@ -749,8 +773,9 @@
         String keyboardLayoutDescriptor;
         if (useNewSettingsUi()) {
             synchronized (mImeInfoLock) {
-                keyboardLayoutDescriptor = getKeyboardLayoutForInputDeviceInternal(identifier,
+                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(identifier,
                         mCurrentImeInfo);
+                keyboardLayoutDescriptor = layoutInfo == null ? null : layoutInfo.mDescriptor;
             }
         } else {
             keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
@@ -787,13 +812,13 @@
             return null;
         }
         InputMethodSubtypeHandle subtypeHandle = InputMethodSubtypeHandle.of(imeInfo, imeSubtype);
-        String layout = getKeyboardLayoutForInputDeviceInternal(identifier,
+        KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(identifier,
                 new ImeInfo(userId, subtypeHandle, imeSubtype));
         if (DEBUG) {
             Slog.d(TAG, "getKeyboardLayoutForInputDevice() " + identifier.toString() + ", userId : "
-                    + userId + ", subtypeHandle = " + subtypeHandle + " -> " + layout);
+                    + userId + ", subtypeHandle = " + subtypeHandle + " -> " + layoutInfo);
         }
-        return layout;
+        return layoutInfo != null ? layoutInfo.mDescriptor : null;
     }
 
     @AnyThread
@@ -924,11 +949,11 @@
             for (int i = 0; i < mConfiguredKeyboards.size(); i++) {
                 InputDevice inputDevice = Objects.requireNonNull(
                         getInputDevice(mConfiguredKeyboards.keyAt(i)));
-                String layout = getKeyboardLayoutForInputDeviceInternal(inputDevice.getIdentifier(),
-                        mCurrentImeInfo);
+                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
+                        inputDevice.getIdentifier(), mCurrentImeInfo);
                 KeyboardConfiguration config = mConfiguredKeyboards.valueAt(i);
-                if (!Objects.equals(layout, config.getCurrentLayout())) {
-                    config.setCurrentLayout(layout);
+                if (!Objects.equals(layoutInfo, config.getCurrentLayout())) {
+                    config.setCurrentLayout(layoutInfo);
                     mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
                     return;
                 }
@@ -937,39 +962,40 @@
     }
 
     @Nullable
-    private String getKeyboardLayoutForInputDeviceInternal(InputDeviceIdentifier identifier,
-            @Nullable ImeInfo imeInfo) {
+    private KeyboardLayoutInfo getKeyboardLayoutForInputDeviceInternal(
+            InputDeviceIdentifier identifier, @Nullable ImeInfo imeInfo) {
         InputDevice inputDevice = getInputDevice(identifier);
         if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
             return null;
         }
         String key = createLayoutKey(identifier, imeInfo);
-        String layout;
         synchronized (mDataStore) {
-            layout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key);
-        }
-        if (layout == null) {
-            synchronized (mKeyboardLayoutCache) {
-                // Check Auto-selected layout cache to see if layout had been previously selected
-                if (mKeyboardLayoutCache.containsKey(key)) {
-                    layout = mKeyboardLayoutCache.get(key);
-                } else {
-                    // NOTE: This list is already filtered based on IME Script code
-                    KeyboardLayout[] layoutList = getKeyboardLayoutListForInputDeviceInternal(
-                            identifier, imeInfo);
-                    // Call auto-matching algorithm to find the best matching layout
-                    layout = getDefaultKeyboardLayoutBasedOnImeInfo(inputDevice, imeInfo,
-                            layoutList);
-                    mKeyboardLayoutCache.put(key, layout);
-                }
+            String layout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key);
+            if (layout != null) {
+                return new KeyboardLayoutInfo(layout, LAYOUT_SELECTION_CRITERIA_USER);
             }
         }
-        return layout;
+
+        synchronized (mKeyboardLayoutCache) {
+            // Check Auto-selected layout cache to see if layout had been previously selected
+            if (mKeyboardLayoutCache.containsKey(key)) {
+                return mKeyboardLayoutCache.get(key);
+            } else {
+                // NOTE: This list is already filtered based on IME Script code
+                KeyboardLayout[] layoutList = getKeyboardLayoutListForInputDeviceInternal(
+                        identifier, imeInfo);
+                // Call auto-matching algorithm to find the best matching layout
+                KeyboardLayoutInfo layoutInfo =
+                        getDefaultKeyboardLayoutBasedOnImeInfo(inputDevice, imeInfo, layoutList);
+                mKeyboardLayoutCache.put(key, layoutInfo);
+                return layoutInfo;
+            }
+        }
     }
 
     @Nullable
-    private static String getDefaultKeyboardLayoutBasedOnImeInfo(InputDevice inputDevice,
-            @Nullable ImeInfo imeInfo, KeyboardLayout[] layoutList) {
+    private static KeyboardLayoutInfo getDefaultKeyboardLayoutBasedOnImeInfo(
+            InputDevice inputDevice, @Nullable ImeInfo imeInfo, KeyboardLayout[] layoutList) {
         Arrays.sort(layoutList);
 
         // Check <VendorID, ProductID> matching for explicitly declared custom KCM files.
@@ -982,7 +1008,8 @@
                                     + "vendor and product Ids. " + inputDevice.getIdentifier()
                                     + " : " + layout.getDescriptor());
                 }
-                return layout.getDescriptor();
+                return new KeyboardLayoutInfo(layout.getDescriptor(),
+                        LAYOUT_SELECTION_CRITERIA_DEVICE);
             }
         }
 
@@ -999,7 +1026,7 @@
                                     + "HW information (Language tag and Layout type). "
                                     + inputDevice.getIdentifier() + " : " + layoutDesc);
                 }
-                return layoutDesc;
+                return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_DEVICE);
             }
         }
 
@@ -1021,7 +1048,10 @@
                             + "IME locale matching. " + inputDevice.getIdentifier() + " : "
                             + layoutDesc);
         }
-        return layoutDesc;
+        if (layoutDesc != null) {
+            return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD);
+        }
+        return null;
     }
 
     @Nullable
@@ -1140,6 +1170,8 @@
 
         if (targetDevice != null) {
             intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, targetDevice.getIdentifier());
+            intent.putExtra(
+                    Settings.EXTRA_ENTRYPOINT, SettingsEnums.KEYBOARD_CONFIGURED_NOTIFICATION);
         }
 
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
@@ -1227,6 +1259,26 @@
         }
     }
 
+    private void logKeyboardConfigurationEvent(@NonNull InputDevice inputDevice,
+            @NonNull List<ImeInfo> imeInfoList, @NonNull List<KeyboardLayoutInfo> layoutInfoList,
+            boolean isFirstConfiguration) {
+        if (imeInfoList.isEmpty() || layoutInfoList.isEmpty()) {
+            return;
+        }
+        KeyboardConfigurationEvent.Builder configurationEventBuilder =
+                new KeyboardConfigurationEvent.Builder(inputDevice).setIsFirstTimeConfiguration(
+                        isFirstConfiguration);
+        for (int i = 0; i < imeInfoList.size(); i++) {
+            KeyboardLayoutInfo layoutInfo = layoutInfoList.get(i);
+            boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null;
+            configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype,
+                    noLayoutFound ? null : getKeyboardLayout(layoutInfo.mDescriptor),
+                    noLayoutFound ? LAYOUT_SELECTION_CRITERIA_DEFAULT
+                            : layoutInfo.mSelectionCriteria);
+        }
+        KeyboardMetricsCollector.logKeyboardConfiguredAtom(configurationEventBuilder.build());
+    }
+
     private boolean handleMessage(Message msg) {
         switch (msg.what) {
             case MSG_UPDATE_EXISTING_DEVICES:
@@ -1409,7 +1461,7 @@
 
         // If null, it means no layout is selected for the device.
         @Nullable
-        private String mCurrentLayout;
+        private KeyboardLayoutInfo mCurrentLayout;
 
         private boolean hasConfiguredLayouts() {
             return mConfiguredLayouts != null && !mConfiguredLayouts.isEmpty();
@@ -1425,15 +1477,42 @@
         }
 
         @Nullable
-        private String getCurrentLayout() {
+        private KeyboardLayoutInfo getCurrentLayout() {
             return mCurrentLayout;
         }
 
-        private void setCurrentLayout(String currentLayout) {
+        private void setCurrentLayout(KeyboardLayoutInfo currentLayout) {
             mCurrentLayout = currentLayout;
         }
     }
 
+    private static class KeyboardLayoutInfo {
+        @Nullable
+        private final String mDescriptor;
+        @LayoutSelectionCriteria
+        private final int mSelectionCriteria;
+
+        private KeyboardLayoutInfo(@Nullable String descriptor,
+                @LayoutSelectionCriteria int selectionCriteria) {
+            mDescriptor = descriptor;
+            mSelectionCriteria = selectionCriteria;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof KeyboardLayoutInfo) {
+                return Objects.equals(mDescriptor, ((KeyboardLayoutInfo) obj).mDescriptor)
+                        && mSelectionCriteria == ((KeyboardLayoutInfo) obj).mSelectionCriteria;
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * mSelectionCriteria + mDescriptor.hashCode();
+        }
+    }
+
     private interface KeyboardLayoutVisitor {
         void visitKeyboardLayout(Resources resources,
                 int keyboardLayoutResId, KeyboardLayout layout);
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
new file mode 100644
index 0000000..08e5977
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -0,0 +1,627 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.hardware.input.KeyboardLayout;
+import android.icu.util.ULocale;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.KeyboardConfiguredProto.KeyboardLayoutConfig;
+import com.android.internal.os.KeyboardConfiguredProto.RepeatedKeyboardLayoutConfig;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Collect Keyboard metrics
+ */
+public final class KeyboardMetricsCollector {
+    private static final String TAG = "KeyboardMetricCollector";
+
+    // To enable these logs, run: 'adb shell setprop log.tag.KeyboardMetricCollector DEBUG'
+    // (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    @Retention(SOURCE)
+    @IntDef(prefix = {"LAYOUT_SELECTION_CRITERIA_"}, value = {
+            LAYOUT_SELECTION_CRITERIA_UNSPECIFIED,
+            LAYOUT_SELECTION_CRITERIA_USER,
+            LAYOUT_SELECTION_CRITERIA_DEVICE,
+            LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+            LAYOUT_SELECTION_CRITERIA_DEFAULT
+    })
+    public @interface LayoutSelectionCriteria {
+    }
+
+    /** Unspecified layout selection criteria */
+    public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0;
+
+    /** Manual selection by user */
+    public static final int LAYOUT_SELECTION_CRITERIA_USER = 1;
+
+    /** Auto-detection based on device provided language tag and layout type */
+    public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2;
+
+    /** Auto-detection based on IME provided language tag and layout type */
+    public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3;
+
+    /** Default selection */
+    public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4;
+
+    @VisibleForTesting
+    static final String DEFAULT_LAYOUT_NAME = "Default";
+
+    @VisibleForTesting
+    public static final String DEFAULT_LANGUAGE_TAG = "None";
+
+    public enum KeyboardLogEvent {
+        UNSPECIFIED(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED,
+                "INVALID_KEYBOARD_EVENT"),
+        HOME(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME,
+                "HOME"),
+        RECENT_APPS(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS,
+                "RECENT_APPS"),
+        BACK(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK,
+                "BACK"),
+        APP_SWITCH(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__APP_SWITCH,
+                "APP_SWITCH"),
+        LAUNCH_ASSISTANT(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_ASSISTANT,
+                "LAUNCH_ASSISTANT"),
+        LAUNCH_VOICE_ASSISTANT(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_VOICE_ASSISTANT,
+                "LAUNCH_VOICE_ASSISTANT"),
+        LAUNCH_SYSTEM_SETTINGS(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SYSTEM_SETTINGS,
+                "LAUNCH_SYSTEM_SETTINGS"),
+        TOGGLE_NOTIFICATION_PANEL(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_NOTIFICATION_PANEL,
+                "TOGGLE_NOTIFICATION_PANEL"),
+        TOGGLE_TASKBAR(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR,
+                "TOGGLE_TASKBAR"),
+        TAKE_SCREENSHOT(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT,
+                "TAKE_SCREENSHOT"),
+        OPEN_SHORTCUT_HELPER(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER,
+                "OPEN_SHORTCUT_HELPER"),
+        BRIGHTNESS_UP(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_UP,
+                "BRIGHTNESS_UP"),
+        BRIGHTNESS_DOWN(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_DOWN,
+                "BRIGHTNESS_DOWN"),
+        KEYBOARD_BACKLIGHT_UP(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_UP,
+                "KEYBOARD_BACKLIGHT_UP"),
+        KEYBOARD_BACKLIGHT_DOWN(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_DOWN,
+                "KEYBOARD_BACKLIGHT_DOWN"),
+        KEYBOARD_BACKLIGHT_TOGGLE(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_TOGGLE,
+                "KEYBOARD_BACKLIGHT_TOGGLE"),
+        VOLUME_UP(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_UP,
+                "VOLUME_UP"),
+        VOLUME_DOWN(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_DOWN,
+                "VOLUME_DOWN"),
+        VOLUME_MUTE(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_MUTE,
+                "VOLUME_MUTE"),
+        ALL_APPS(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ALL_APPS,
+                "ALL_APPS"),
+        LAUNCH_SEARCH(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SEARCH,
+                "LAUNCH_SEARCH"),
+        LANGUAGE_SWITCH(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LANGUAGE_SWITCH,
+                "LANGUAGE_SWITCH"),
+        ACCESSIBILITY_ALL_APPS(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ACCESSIBILITY_ALL_APPS,
+                "ACCESSIBILITY_ALL_APPS"),
+        TOGGLE_CAPS_LOCK(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK,
+                "TOGGLE_CAPS_LOCK"),
+        SYSTEM_MUTE(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE,
+                "SYSTEM_MUTE"),
+        SPLIT_SCREEN_NAVIGATION(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION,
+                "SPLIT_SCREEN_NAVIGATION"),
+        TRIGGER_BUG_REPORT(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT,
+                "TRIGGER_BUG_REPORT"),
+        LOCK_SCREEN(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LOCK_SCREEN,
+                "LOCK_SCREEN"),
+        OPEN_NOTES(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_NOTES,
+                "OPEN_NOTES"),
+        TOGGLE_POWER(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_POWER,
+                "TOGGLE_POWER"),
+        SYSTEM_NAVIGATION(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_NAVIGATION,
+                "SYSTEM_NAVIGATION"),
+        SLEEP(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SLEEP,
+                "SLEEP"),
+        WAKEUP(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__WAKEUP,
+                "WAKEUP"),
+        MEDIA_KEY(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MEDIA_KEY,
+                "MEDIA_KEY"),
+        LAUNCH_DEFAULT_BROWSER(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER,
+                "LAUNCH_DEFAULT_BROWSER"),
+        LAUNCH_DEFAULT_EMAIL(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL,
+                "LAUNCH_DEFAULT_EMAIL"),
+        LAUNCH_DEFAULT_CONTACTS(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS,
+                "LAUNCH_DEFAULT_CONTACTS"),
+        LAUNCH_DEFAULT_CALENDAR(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR,
+                "LAUNCH_DEFAULT_CALENDAR"),
+        LAUNCH_DEFAULT_CALCULATOR(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR,
+                "LAUNCH_DEFAULT_CALCULATOR"),
+        LAUNCH_DEFAULT_MUSIC(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC,
+                "LAUNCH_DEFAULT_MUSIC"),
+        LAUNCH_DEFAULT_MAPS(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS,
+                "LAUNCH_DEFAULT_MAPS"),
+        LAUNCH_DEFAULT_MESSAGING(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING,
+                "LAUNCH_DEFAULT_MESSAGING"),
+        LAUNCH_DEFAULT_GALLERY(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY,
+                "LAUNCH_DEFAULT_GALLERY"),
+        LAUNCH_DEFAULT_FILES(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES,
+                "LAUNCH_DEFAULT_FILES"),
+        LAUNCH_DEFAULT_WEATHER(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER,
+                "LAUNCH_DEFAULT_WEATHER"),
+        LAUNCH_DEFAULT_FITNESS(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS,
+                "LAUNCH_DEFAULT_FITNESS"),
+        LAUNCH_APPLICATION_BY_PACKAGE_NAME(
+                FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME,
+                "LAUNCH_APPLICATION_BY_PACKAGE_NAME");
+
+        private final int mValue;
+        private final String mName;
+
+        private static final SparseArray<KeyboardLogEvent> VALUE_TO_ENUM_MAP = new SparseArray<>();
+
+        static {
+            for (KeyboardLogEvent type : KeyboardLogEvent.values()) {
+                VALUE_TO_ENUM_MAP.put(type.mValue, type);
+            }
+        }
+
+        KeyboardLogEvent(int enumValue, String enumName) {
+            mValue = enumValue;
+            mName = enumName;
+        }
+
+        public int getIntValue() {
+            return mValue;
+        }
+
+        /**
+         * Convert int value to corresponding KeyboardLogEvent enum. If can't find any matching
+         * value will return {@code null}
+         */
+        @Nullable
+        public static KeyboardLogEvent from(int value) {
+            return VALUE_TO_ENUM_MAP.get(value);
+        }
+
+        /**
+         * Find KeyboardLogEvent corresponding to volume up/down/mute key events.
+         */
+        @Nullable
+        public static KeyboardLogEvent getVolumeEvent(int keycode) {
+            switch (keycode) {
+                case KeyEvent.KEYCODE_VOLUME_DOWN:
+                    return VOLUME_DOWN;
+                case KeyEvent.KEYCODE_VOLUME_UP:
+                    return VOLUME_UP;
+                case KeyEvent.KEYCODE_VOLUME_MUTE:
+                    return VOLUME_MUTE;
+                default:
+                    return null;
+            }
+        }
+
+        /**
+         * Find KeyboardLogEvent corresponding to brightness up/down key events.
+         */
+        @Nullable
+        public static KeyboardLogEvent getBrightnessEvent(int keycode) {
+            switch (keycode) {
+                case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
+                    return BRIGHTNESS_DOWN;
+                case KeyEvent.KEYCODE_BRIGHTNESS_UP:
+                    return BRIGHTNESS_UP;
+                default:
+                    return null;
+            }
+        }
+
+        /**
+         * Find KeyboardLogEvent corresponding to intent filter category. Returns
+         * {@code null if no matching event found}
+         */
+        @Nullable
+        public static KeyboardLogEvent getLogEventFromIntent(Intent intent) {
+            Intent selectorIntent = intent.getSelector();
+            if (selectorIntent != null) {
+                Set<String> selectorCategories = selectorIntent.getCategories();
+                if (selectorCategories != null && !selectorCategories.isEmpty()) {
+                    for (String intentCategory : selectorCategories) {
+                        KeyboardLogEvent logEvent = getEventFromSelectorCategory(intentCategory);
+                        if (logEvent == null) {
+                            continue;
+                        }
+                        return logEvent;
+                    }
+                }
+            }
+
+            Set<String> intentCategories = intent.getCategories();
+            if (intentCategories == null || intentCategories.isEmpty()
+                    || !intentCategories.contains(Intent.CATEGORY_LAUNCHER)) {
+                return null;
+            }
+            if (intent.getComponent() == null) {
+                return null;
+            }
+
+            // TODO(b/280423320): Add new field package name associated in the
+            //  KeyboardShortcutEvent atom and log it accordingly.
+            return LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+        }
+
+        @Nullable
+        private static KeyboardLogEvent getEventFromSelectorCategory(String category) {
+            switch (category) {
+                case Intent.CATEGORY_APP_BROWSER:
+                    return LAUNCH_DEFAULT_BROWSER;
+                case Intent.CATEGORY_APP_EMAIL:
+                    return LAUNCH_DEFAULT_EMAIL;
+                case Intent.CATEGORY_APP_CONTACTS:
+                    return LAUNCH_DEFAULT_CONTACTS;
+                case Intent.CATEGORY_APP_CALENDAR:
+                    return LAUNCH_DEFAULT_CALENDAR;
+                case Intent.CATEGORY_APP_CALCULATOR:
+                    return LAUNCH_DEFAULT_CALCULATOR;
+                case Intent.CATEGORY_APP_MUSIC:
+                    return LAUNCH_DEFAULT_MUSIC;
+                case Intent.CATEGORY_APP_MAPS:
+                    return LAUNCH_DEFAULT_MAPS;
+                case Intent.CATEGORY_APP_MESSAGING:
+                    return LAUNCH_DEFAULT_MESSAGING;
+                case Intent.CATEGORY_APP_GALLERY:
+                    return LAUNCH_DEFAULT_GALLERY;
+                case Intent.CATEGORY_APP_FILES:
+                    return LAUNCH_DEFAULT_FILES;
+                case Intent.CATEGORY_APP_WEATHER:
+                    return LAUNCH_DEFAULT_WEATHER;
+                case Intent.CATEGORY_APP_FITNESS:
+                    return LAUNCH_DEFAULT_FITNESS;
+                default:
+                    return null;
+            }
+        }
+    }
+
+    /**
+     * Log keyboard system shortcuts for the proto
+     * {@link com.android.os.input.KeyboardSystemsEventReported}
+     * defined in "stats/atoms/input/input_extension_atoms.proto"
+     */
+    public static void logKeyboardSystemsEventReportedAtom(@Nullable InputDevice inputDevice,
+            @Nullable KeyboardLogEvent keyboardSystemEvent, int modifierState, int... keyCodes) {
+        // Logging Keyboard system event only for an external HW keyboard. We should not log events
+        // for virtual keyboards or internal Key events.
+        if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
+            return;
+        }
+        int vendorId = inputDevice.getVendorId();
+        int productId = inputDevice.getProductId();
+        if (keyboardSystemEvent == null) {
+            Slog.w(TAG, "Invalid keyboard event logging, keycode = " + Arrays.toString(keyCodes)
+                    + ", modifier state = " + modifierState);
+            return;
+        }
+        FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
+                vendorId, productId, keyboardSystemEvent.getIntValue(), keyCodes, modifierState);
+
+        if (DEBUG) {
+            Slog.d(TAG, "Logging Keyboard system event: " + keyboardSystemEvent.mName);
+        }
+    }
+
+    /**
+     * Function to log the KeyboardConfigured
+     * {@link com.android.os.input.KeyboardConfigured} atom
+     *
+     * @param event {@link KeyboardConfigurationEvent} contains information about keyboard
+     *              configuration. Use {@link KeyboardConfigurationEvent.Builder} to create the
+     *              configuration event to log.
+     */
+    public static void logKeyboardConfiguredAtom(KeyboardConfigurationEvent event) {
+        // Creating proto to log nested field KeyboardLayoutConfig in atom
+        ProtoOutputStream proto = new ProtoOutputStream();
+
+        for (LayoutConfiguration layoutConfiguration : event.getLayoutConfigurations()) {
+            addKeyboardLayoutConfigurationToProto(proto, layoutConfiguration);
+        }
+        // Push the atom to Statsd
+        FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_CONFIGURED,
+                event.isFirstConfiguration(), event.getVendorId(), event.getProductId(),
+                proto.getBytes());
+
+        if (DEBUG) {
+            Slog.d(TAG, "Logging Keyboard configuration event: " + event);
+        }
+    }
+
+    /**
+     * Populate the KeyboardLayoutConfig proto which is a repeated proto
+     * in the RepeatedKeyboardLayoutConfig proto with values from the
+     * {@link LayoutConfiguration} class
+     * The proto definitions can be found at:
+     * "frameworks/proto_logging/stats/atoms/input/input_extension_atoms.proto"
+     *
+     * @param proto Representing the nested proto RepeatedKeyboardLayoutConfig
+     * @param layoutConfiguration Class containing the fields for populating the
+     * KeyboardLayoutConfig proto
+     */
+    private static void addKeyboardLayoutConfigurationToProto(ProtoOutputStream proto,
+            LayoutConfiguration layoutConfiguration) {
+        // Start a new KeyboardLayoutConfig proto.
+        long keyboardLayoutConfigToken = proto.start(
+                RepeatedKeyboardLayoutConfig.KEYBOARD_LAYOUT_CONFIG);
+        proto.write(KeyboardLayoutConfig.KEYBOARD_LANGUAGE_TAG,
+                layoutConfiguration.keyboardLanguageTag);
+        proto.write(KeyboardLayoutConfig.KEYBOARD_LAYOUT_TYPE,
+                layoutConfiguration.keyboardLayoutType);
+        proto.write(KeyboardLayoutConfig.KEYBOARD_LAYOUT_NAME,
+                layoutConfiguration.keyboardLayoutName);
+        proto.write(KeyboardLayoutConfig.LAYOUT_SELECTION_CRITERIA,
+                layoutConfiguration.layoutSelectionCriteria);
+        proto.write(KeyboardLayoutConfig.IME_LANGUAGE_TAG,
+                layoutConfiguration.imeLanguageTag);
+        proto.write(KeyboardLayoutConfig.IME_LAYOUT_TYPE,
+                layoutConfiguration.imeLayoutType);
+        proto.end(keyboardLayoutConfigToken);
+    }
+
+    /**
+     * Class representing the proto KeyboardLayoutConfig defined in
+     * "frameworks/proto_logging/stats/atoms/input/input_extension_atoms.proto
+     *
+     * @see com.android.os.input.KeyboardConfigured
+     */
+    public static class KeyboardConfigurationEvent {
+
+        private final InputDevice mInputDevice;
+        private final boolean mIsFirstConfiguration;
+        private final List<LayoutConfiguration> mLayoutConfigurations;
+
+        private KeyboardConfigurationEvent(InputDevice inputDevice, boolean isFirstConfiguration,
+                List<LayoutConfiguration> layoutConfigurations) {
+            mInputDevice = inputDevice;
+            mIsFirstConfiguration = isFirstConfiguration;
+            mLayoutConfigurations = layoutConfigurations;
+        }
+
+        public int getVendorId() {
+            return mInputDevice.getVendorId();
+        }
+
+        public int getProductId() {
+            return mInputDevice.getProductId();
+        }
+
+        public boolean isFirstConfiguration() {
+            return mIsFirstConfiguration;
+        }
+
+        public List<LayoutConfiguration> getLayoutConfigurations() {
+            return mLayoutConfigurations;
+        }
+
+        @Override
+        public String toString() {
+            return "InputDevice = {VendorId = " + Integer.toHexString(getVendorId())
+                    + ", ProductId = " + Integer.toHexString(getProductId())
+                    + "}, isFirstConfiguration = " + mIsFirstConfiguration
+                    + ", LayoutConfigurations = " + mLayoutConfigurations;
+        }
+
+        /**
+         * Builder class to help create {@link KeyboardConfigurationEvent}.
+         */
+        public static class Builder {
+            @NonNull
+            private final InputDevice mInputDevice;
+            private boolean mIsFirstConfiguration;
+            private final List<InputMethodSubtype> mImeSubtypeList = new ArrayList<>();
+            private final List<KeyboardLayout> mSelectedLayoutList = new ArrayList<>();
+            private final List<Integer> mLayoutSelectionCriteriaList = new ArrayList<>();
+
+            public Builder(@NonNull InputDevice inputDevice) {
+                Objects.requireNonNull(inputDevice, "InputDevice provided should not be null");
+                mInputDevice = inputDevice;
+            }
+
+            /**
+             * Set whether this is the first time this keyboard is configured.
+             */
+            public Builder setIsFirstTimeConfiguration(boolean isFirstTimeConfiguration) {
+                mIsFirstConfiguration = isFirstTimeConfiguration;
+                return this;
+            }
+
+            /**
+             * Adds keyboard layout configuration info for a particular IME subtype language
+             */
+            public Builder addLayoutSelection(@NonNull InputMethodSubtype imeSubtype,
+                    @Nullable KeyboardLayout selectedLayout,
+                    @LayoutSelectionCriteria int layoutSelectionCriteria) {
+                Objects.requireNonNull(imeSubtype, "IME subtype provided should not be null");
+                if (!isValidSelectionCriteria(layoutSelectionCriteria)) {
+                    throw new IllegalStateException("Invalid layout selection criteria");
+                }
+                mImeSubtypeList.add(imeSubtype);
+                mSelectedLayoutList.add(selectedLayout);
+                mLayoutSelectionCriteriaList.add(layoutSelectionCriteria);
+                return this;
+            }
+
+            /**
+             * Creates {@link KeyboardConfigurationEvent} from the provided information
+             */
+            public KeyboardConfigurationEvent build() {
+                int size = mImeSubtypeList.size();
+                if (size == 0) {
+                    throw new IllegalStateException("Should have at least one configuration");
+                }
+                List<LayoutConfiguration> configurationList = new ArrayList<>();
+                for (int i = 0; i < size; i++) {
+                    KeyboardLayout selectedLayout = mSelectedLayoutList.get(i);
+                    @LayoutSelectionCriteria int layoutSelectionCriteria =
+                            mLayoutSelectionCriteriaList.get(i);
+                    InputMethodSubtype imeSubtype = mImeSubtypeList.get(i);
+                    String keyboardLanguageTag = mInputDevice.getKeyboardLanguageTag();
+                    keyboardLanguageTag = TextUtils.isEmpty(keyboardLanguageTag)
+                            ? DEFAULT_LANGUAGE_TAG : keyboardLanguageTag;
+                    int keyboardLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
+                            mInputDevice.getKeyboardLayoutType());
+
+                    ULocale pkLocale = imeSubtype.getPhysicalKeyboardHintLanguageTag();
+                    String imeLanguageTag = pkLocale != null ? pkLocale.toLanguageTag()
+                            : imeSubtype.getCanonicalizedLanguageTag();
+                    imeLanguageTag = TextUtils.isEmpty(imeLanguageTag) ? DEFAULT_LANGUAGE_TAG
+                            : imeLanguageTag;
+                    int imeLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
+                            imeSubtype.getPhysicalKeyboardHintLayoutType());
+
+                    // Sanitize null values
+                    String keyboardLayoutName =
+                            selectedLayout == null ? DEFAULT_LAYOUT_NAME
+                                    : selectedLayout.getLabel();
+
+                    configurationList.add(
+                            new LayoutConfiguration(keyboardLayoutType, keyboardLanguageTag,
+                                    keyboardLayoutName, layoutSelectionCriteria,
+                                    imeLayoutType, imeLanguageTag));
+                }
+                return new KeyboardConfigurationEvent(mInputDevice, mIsFirstConfiguration,
+                        configurationList);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    static class LayoutConfiguration {
+        // This should match enum values defined in "frameworks/base/core/res/res/values/attrs.xml"
+        public final int keyboardLayoutType;
+        public final String keyboardLanguageTag;
+        public final String keyboardLayoutName;
+        @LayoutSelectionCriteria
+        public final int layoutSelectionCriteria;
+        public final int imeLayoutType;
+        public final String imeLanguageTag;
+
+        private LayoutConfiguration(int keyboardLayoutType, String keyboardLanguageTag,
+                String keyboardLayoutName, @LayoutSelectionCriteria int layoutSelectionCriteria,
+                int imeLayoutType, String imeLanguageTag) {
+            this.keyboardLayoutType = keyboardLayoutType;
+            this.keyboardLanguageTag = keyboardLanguageTag;
+            this.keyboardLayoutName = keyboardLayoutName;
+            this.layoutSelectionCriteria = layoutSelectionCriteria;
+            this.imeLayoutType = imeLayoutType;
+            this.imeLanguageTag = imeLanguageTag;
+        }
+
+        @Override
+        public String toString() {
+            return "{keyboardLanguageTag = " + keyboardLanguageTag + " keyboardLayoutType = "
+                    + KeyboardLayout.LayoutType.getLayoutNameFromValue(keyboardLayoutType)
+                    + " keyboardLayoutName = " + keyboardLayoutName + " layoutSelectionCriteria = "
+                    + getStringForSelectionCriteria(layoutSelectionCriteria)
+                    + "imeLanguageTag = " + imeLanguageTag + " imeLayoutType = "
+                    + KeyboardLayout.LayoutType.getLayoutNameFromValue(imeLayoutType) + "}";
+        }
+    }
+
+    private static String getStringForSelectionCriteria(
+            @LayoutSelectionCriteria int layoutSelectionCriteria) {
+        switch (layoutSelectionCriteria) {
+            case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED:
+                return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED";
+            case LAYOUT_SELECTION_CRITERIA_USER:
+                return "LAYOUT_SELECTION_CRITERIA_USER";
+            case LAYOUT_SELECTION_CRITERIA_DEVICE:
+                return "LAYOUT_SELECTION_CRITERIA_DEVICE";
+            case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD:
+                return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD";
+            case LAYOUT_SELECTION_CRITERIA_DEFAULT:
+                return "LAYOUT_SELECTION_CRITERIA_DEFAULT";
+            default:
+                return "INVALID_CRITERIA";
+        }
+    }
+
+    private static boolean isValidSelectionCriteria(int layoutSelectionCriteria) {
+        return layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER
+                || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE
+                || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+                || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT;
+    }
+}
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index bce210d..31083fd 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -101,6 +101,10 @@
         }
     }
 
+    public boolean hasInputDeviceEntry(String inputDeviceDescriptor) {
+        return getInputDeviceState(inputDeviceDescriptor) != null;
+    }
+
     public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) {
         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
         if (state == null) {
diff --git a/services/core/java/com/android/server/input/UEventManager.java b/services/core/java/com/android/server/input/UEventManager.java
new file mode 100644
index 0000000..17d87e4
--- /dev/null
+++ b/services/core/java/com/android/server/input/UEventManager.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.os.UEventObserver;
+
+/** An interface used to change the API of UEventObserver to a more test-friendly format. */
+interface UEventManager {
+
+    abstract class UEventListener {
+        private final UEventObserver mObserver = new UEventObserver() {
+            @Override
+            public void onUEvent(UEvent event) {
+                UEventListener.this.onUEvent(event);
+            }
+        };
+
+        public abstract void onUEvent(UEventObserver.UEvent event);
+    }
+
+    default void addListener(UEventListener listener, String match) {
+        listener.mObserver.startObserving(match);
+    }
+
+    default void removeListener(UEventListener listener) {
+        listener.mObserver.stopObserving();
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index a1b67e1..f1698dd 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -37,6 +37,7 @@
 import android.util.EventLog;
 import android.util.Slog;
 import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.annotations.GuardedBy;
@@ -75,7 +76,8 @@
     @GuardedBy("ImfLock.class")
     @Override
     public void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
-            int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
         if (curMethod != null) {
             if (DEBUG) {
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 6a0550b..aa99dab 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -291,6 +291,10 @@
         reset(false /* reinitializing */);
     }
 
+    void setInkWindowInitializer(Runnable inkWindowInitializer) {
+        mInkWindowInitRunnable = inkWindowInitializer;
+    }
+
     private void reset(boolean reinitializing) {
         if (mHandwritingEventReceiver != null) {
             mHandwritingEventReceiver.dispose();
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index c53f1a5..b12a816 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -30,6 +30,7 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodSubtype;
 import android.window.ImeOnBackInvokedDispatcher;
 
@@ -198,8 +199,8 @@
 
     // TODO(b/192412909): Convert this back to void method
     @AnyThread
-    boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, int flags,
-            ResultReceiver resultReceiver) {
+    boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
+            @InputMethod.ShowFlags int flags, ResultReceiver resultReceiver) {
         try {
             mTarget.showSoftInput(showInputToken, statsToken, flags, resultReceiver);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
index 27f6a89..29fa369 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java
@@ -21,6 +21,7 @@
 import android.os.IBinder;
 import android.os.ResultReceiver;
 import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethod;
 
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 
@@ -34,13 +35,13 @@
      *
      * @param showInputToken A token that represents the requester to show IME.
      * @param statsToken     A token that tracks the progress of an IME request.
-     * @param showFlags      Provides additional operating flags to show IME.
      * @param resultReceiver If non-null, this will be called back to the caller when
      *                       it has processed request to tell what it has done.
      * @param reason         The reason for requesting to show IME.
      */
     default void performShowIme(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
-            int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {}
+            @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {}
 
     /**
      * Performs hiding IME to the given window
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index f012d91..2e0274b 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -23,6 +23,7 @@
 import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_FORCED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.MotionEvent.TOOL_TYPE_UNKNOWN;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
@@ -44,6 +45,7 @@
 import android.util.Printer;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
+import android.view.MotionEvent;
 import android.view.WindowManager;
 import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethod;
@@ -221,17 +223,21 @@
 
     /**
      * Called when {@link InputMethodManagerService} is processing the show IME request.
-     * @param statsToken The token for tracking this show request
-     * @param showFlags The additional operation flags to indicate whether this show request mode is
-     *                  implicit or explicit.
-     * @return {@code true} when the computer has proceed this show request operation.
+     *
+     * @param statsToken The token for tracking this show request.
+     * @return {@code true} when the show request can proceed.
      */
-    boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken, int showFlags) {
+    boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken,
+            @InputMethodManager.ShowFlags int showFlags) {
         if (mPolicy.mA11yRequestingNoSoftKeyboard || mPolicy.mImeHiddenByDisplayPolicy) {
             ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
             return false;
         }
         ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
+        // We only "set" the state corresponding to the flags, as this will be reset
+        // in clearImeShowFlags during a hide request.
+        // Thus, we keep the strongest values set (e.g. an implicit show right after
+        // an explicit show will still be considered explicit, likewise for forced).
         if ((showFlags & InputMethodManager.SHOW_FORCED) != 0) {
             mRequestedShowExplicitly = true;
             mShowForced = true;
@@ -243,12 +249,12 @@
 
     /**
      * Called when {@link InputMethodManagerService} is processing the hide IME request.
-     * @param statsToken The token for tracking this hide request
-     * @param hideFlags The additional operation flags to indicate whether this hide request mode is
-     *                  implicit or explicit.
-     * @return {@code true} when the computer has proceed this hide request operations.
+     *
+     * @param statsToken The token for tracking this hide request.
+     * @return {@code true} when the hide request can proceed.
      */
-    boolean canHideIme(@NonNull ImeTracker.Token statsToken, int hideFlags) {
+    boolean canHideIme(@NonNull ImeTracker.Token statsToken,
+            @InputMethodManager.HideFlags int hideFlags) {
         if ((hideFlags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
                 && (mRequestedShowExplicitly || mShowForced)) {
             if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
@@ -264,13 +270,31 @@
         return true;
     }
 
-    int getImeShowFlags() {
+    /**
+     * Returns the show flags for IME. This translates from {@link InputMethodManager.ShowFlags}
+     * to {@link InputMethod.ShowFlags}.
+     */
+    @InputMethod.ShowFlags
+    int getShowFlagsForInputMethodServiceOnly() {
         int flags = 0;
         if (mShowForced) {
             flags |= InputMethod.SHOW_FORCED | InputMethod.SHOW_EXPLICIT;
         } else if (mRequestedShowExplicitly) {
             flags |= InputMethod.SHOW_EXPLICIT;
-        } else {
+        }
+        return flags;
+    }
+
+    /**
+     * Returns the show flags for IMM. This translates from {@link InputMethod.ShowFlags}
+     * to {@link InputMethodManager.ShowFlags}.
+     */
+    @InputMethodManager.ShowFlags
+    int getShowFlags() {
+        int flags = 0;
+        if (mShowForced) {
+            flags |= InputMethodManager.SHOW_FORCED;
+        } else if (!mRequestedShowExplicitly) {
             flags |= InputMethodManager.SHOW_IMPLICIT;
         }
         return flags;
@@ -329,7 +353,8 @@
 
     void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
         final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
-        if (state != null && newState.hasEditorFocused()) {
+        if (state != null && newState.hasEditorFocused()
+                && newState.mToolType != MotionEvent.TOOL_TYPE_STYLUS) {
             // Inherit the last requested IME visible state when the target window is still
             // focused with an editor.
             newState.setRequestedImeVisible(state.mRequestedImeVisible);
@@ -630,14 +655,23 @@
      * A class that represents the current state of the IME target window.
      */
     static class ImeTargetWindowState {
+
         ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, int windowFlags,
                 boolean imeFocusChanged, boolean hasFocusedEditor,
                 boolean isStartInputByGainFocus) {
+            this(softInputModeState, windowFlags, imeFocusChanged, hasFocusedEditor,
+                    isStartInputByGainFocus, TOOL_TYPE_UNKNOWN);
+        }
+
+        ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, int windowFlags,
+                boolean imeFocusChanged, boolean hasFocusedEditor,
+                boolean isStartInputByGainFocus, @MotionEvent.ToolType int toolType) {
             mSoftInputModeState = softInputModeState;
             mWindowFlags = windowFlags;
             mImeFocusChanged = imeFocusChanged;
             mHasFocusedEditor = hasFocusedEditor;
             mIsStartInputByGainFocus = isStartInputByGainFocus;
+            mToolType = toolType;
         }
 
         /**
@@ -648,6 +682,11 @@
         private final int mWindowFlags;
 
         /**
+         * {@link MotionEvent#getToolType(int)} that was used to click editor.
+         */
+        private final int mToolType;
+
+        /**
          * {@code true} means the IME focus changed from the previous window, {@code false}
          * otherwise.
          */
@@ -696,6 +735,10 @@
             return mWindowFlags;
         }
 
+        int getToolType() {
+            return mToolType;
+        }
+
         private void setImeDisplayId(int imeDisplayId) {
             mImeDisplayId = imeDisplayId;
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index ba9e280..5308336 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -40,6 +40,7 @@
 import android.view.WindowManager;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -295,7 +296,12 @@
                     }
                     if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
                     final InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
+                    boolean supportsStylusHwChanged =
+                            mSupportsStylusHw != info.supportsStylusHandwriting();
                     mSupportsStylusHw = info.supportsStylusHandwriting();
+                    if (supportsStylusHwChanged) {
+                        InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
+                    }
                     mService.initializeImeLocked(mCurMethod, mCurToken);
                     mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
                     mService.reRequestCurrentClientSessionLocked();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 8c7658e..08503cb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -186,6 +186,12 @@
     public abstract void switchKeyboardLayout(int direction);
 
     /**
+     * Returns true if any InputConnection is currently active.
+     * {@hide}
+     */
+    public abstract boolean isAnyInputConnectionActive();
+
+    /**
      * Fake implementation of {@link InputMethodManagerInternal}.  All the methods do nothing.
      */
     private static final InputMethodManagerInternal NOP =
@@ -268,6 +274,11 @@
                 @Override
                 public void switchKeyboardLayout(int direction) {
                 }
+
+                @Override
+                public boolean isAnyInputConnectionActive() {
+                    return false;
+                }
             };
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 57f8d14..2a617c5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -48,6 +48,7 @@
 
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
+import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
 import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
 import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed;
 
@@ -1174,6 +1175,8 @@
                     Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, userId);
             resolver.registerContentObserver(Settings.Secure.getUriFor(
                     Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE), false, this, userId);
+            resolver.registerContentObserver(Settings.Secure.getUriFor(
+                    STYLUS_HANDWRITING_ENABLED), false, this);
             mRegistered = true;
         }
 
@@ -1182,6 +1185,8 @@
                     Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
             final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
                     Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
+            final Uri stylusHandwritingEnabledUri = Settings.Secure.getUriFor(
+                    STYLUS_HANDWRITING_ENABLED);
             synchronized (ImfLock.class) {
                 if (showImeUri.equals(uri)) {
                     mMenuController.updateKeyboardFromSettingsLocked();
@@ -1199,6 +1204,8 @@
                         showCurrentInputImplicitLocked(mCurFocusedWindow,
                                 SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE);
                     }
+                } else if (stylusHandwritingEnabledUri.equals(uri)) {
+                    InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
                 } else {
                     boolean enabledChanged = false;
                     String newEnabled = mSettings.getEnabledInputMethodsStr();
@@ -2362,11 +2369,32 @@
             mCurVirtualDisplayToScreenMatrix = null;
             ImeTracker.forLogging().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
             mCurStatsToken = null;
-
             mMenuController.hideInputMethodMenuLocked();
         }
     }
 
+    /**
+     * Called when {@link #resetCurrentMethodAndClientLocked(int)} invoked for clean-up states
+     * before unbinding the current method.
+     */
+    @GuardedBy("ImfLock.class")
+    void onUnbindCurrentMethodByReset() {
+        final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
+                mCurFocusedWindow);
+        if (winState != null && !winState.isRequestedImeVisible()
+                && !mVisibilityStateComputer.isInputShown()) {
+            // Normally, the focus window will apply the IME visibility state to
+            // WindowManager when the IME has applied it. But it would be too late when
+            // switching IMEs in between different users. (Since the focused IME will
+            // first unbind the service to switch to bind the next user of the IME
+            // service, that wouldn't make the attached IME token validity check in time)
+            // As a result, we have to notify WM to apply IME visibility before clearing the
+            // binding states in the first place.
+            mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, mCurStatsToken,
+                    STATE_HIDE_IME);
+        }
+    }
+
     /** {@code true} when a {@link ClientState} has attached from starting the input connection. */
     @GuardedBy("ImfLock.class")
     boolean hasAttachedClient() {
@@ -2439,7 +2467,7 @@
             final ImeTracker.Token statsToken = mCurStatsToken;
             mCurStatsToken = null;
             showCurrentInputLocked(mCurFocusedWindow, statsToken,
-                    mVisibilityStateComputer.getImeShowFlags(),
+                    mVisibilityStateComputer.getShowFlags(),
                     null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
         }
 
@@ -2449,6 +2477,9 @@
                 curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
         final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
                 createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
+        if (mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
+            mHwController.setInkWindowInitializer(new InkWindowInitializer());
+        }
         return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
                 session.mSession, accessibilityInputMethodSessions,
                 (session.mChannel != null ? session.mChannel.dup() : null),
@@ -2838,6 +2869,8 @@
     @GuardedBy("ImfLock.class")
     void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
         setSelectedMethodIdLocked(null);
+        // Callback before clean-up binding states.
+        onUnbindCurrentMethodByReset();
         mBindingController.unbindCurrentMethod();
         unbindCurrentClientLocked(unbindClientReason);
     }
@@ -3050,7 +3083,12 @@
     @GuardedBy("ImfLock.class")
     private boolean shouldShowImeSwitcherLocked(int visibility) {
         if (!mShowOngoingImeSwitcherForPhones) return false;
+        // When the IME switcher dialog is shown, the IME switcher button should be hidden.
         if (mMenuController.getSwitchingDialogLocked() != null) return false;
+        // When we are switching IMEs, the IME switcher button should be hidden.
+        if (!Objects.equals(getCurIdLocked(), getSelectedMethodIdLocked())) {
+            return false;
+        }
         if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
                 && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId())) {
             return false;
@@ -3205,7 +3243,12 @@
             } else {
                 vis &= ~InputMethodService.IME_VISIBLE_IMPERCEPTIBLE;
             }
-            // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked().
+            if (mMenuController.getSwitchingDialogLocked() != null
+                    || !Objects.equals(getCurIdLocked(), getSelectedMethodIdLocked())) {
+                // When the IME switcher dialog is shown, or we are switching IMEs,
+                // the back button should be in the default state (as if the IME is not shown).
+                backDisposition = InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING;
+            }
             final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
             if (mStatusBarManagerInternal != null) {
                 mStatusBarManagerInternal.setImeWindowStatus(mCurTokenDisplayId,
@@ -3360,8 +3403,9 @@
 
     @Override
     public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, int flags, int lastClickTooType,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+            int lastClickTooType, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
         int uid = Binder.getCallingUid();
         ImeTracing.getInstance().triggerManagerServiceDump(
@@ -3534,15 +3578,17 @@
 
     @GuardedBy("ImfLock.class")
     boolean showCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
-            int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @InputMethodManager.ShowFlags int flags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         return showCurrentInputLocked(windowToken, statsToken, flags,
                 MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason);
     }
 
     @GuardedBy("ImfLock.class")
     private boolean showCurrentInputLocked(IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+            int lastClickToolType, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
             statsToken = createStatsTokenForFocusedClient(true /* show */,
@@ -3573,7 +3619,8 @@
                 curMethod.updateEditorToolType(lastClickToolType);
             }
             mVisibilityApplier.performShowIme(windowToken, statsToken,
-                    mVisibilityStateComputer.getImeShowFlags(), resultReceiver, reason);
+                    mVisibilityStateComputer.getShowFlagsForInputMethodServiceOnly(),
+                    resultReceiver, reason);
             mVisibilityStateComputer.setInputShown(true);
             return true;
         } else {
@@ -3585,8 +3632,8 @@
 
     @Override
     public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
-            @Nullable ImeTracker.Token statsToken, int flags, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
+            @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         int uid = Binder.getCallingUid();
         ImeTracing.getInstance().triggerManagerServiceDump(
                 "InputMethodManagerService#hideSoftInput");
@@ -3616,7 +3663,8 @@
 
     @GuardedBy("ImfLock.class")
     boolean hideCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
-            int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @InputMethodManager.HideFlags int flags, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
             statsToken = createStatsTokenForFocusedClient(false /* show */,
@@ -3828,11 +3876,14 @@
         final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
         final boolean startInputByWinGainedFocus =
                 (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0;
+        final int toolType = editorInfo != null
+                ? editorInfo.getInitialToolType() : MotionEvent.TOOL_TYPE_UNKNOWN;
 
         // Init the focused window state (e.g. whether the editor has focused or IME focus has
         // changed from another window).
-        final ImeTargetWindowState windowState = new ImeTargetWindowState(softInputMode,
-                windowFlags, !sameWindowFocused, isTextEditor, startInputByWinGainedFocus);
+        final ImeTargetWindowState windowState = new ImeTargetWindowState(
+                softInputMode, windowFlags, !sameWindowFocused, isTextEditor,
+                startInputByWinGainedFocus, toolType);
         mVisibilityStateComputer.setWindowState(windowToken, windowState);
 
         if (sameWindowFocused && isTextEditor) {
@@ -4803,7 +4854,7 @@
     }
 
     @BinderThread
-    private void hideMySoftInput(@NonNull IBinder token, int flags,
+    private void hideMySoftInput(@NonNull IBinder token, @InputMethodManager.HideFlags int flags,
             @SoftInputShowHideReason int reason) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
@@ -4825,7 +4876,7 @@
     }
 
     @BinderThread
-    private void showMySoftInput(@NonNull IBinder token, int flags) {
+    private void showMySoftInput(@NonNull IBinder token, @InputMethodManager.ShowFlags int flags) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput");
             synchronized (ImfLock.class) {
@@ -5769,7 +5820,7 @@
                 // input target changed, in case seeing the dialog dismiss flickering during
                 // the next focused window starting the input connection.
                 if (mLastImeTargetWindow != mCurFocusedWindow) {
-                    mMenuController.hideInputMethodMenu();
+                    mMenuController.hideInputMethodMenuLocked();
                 }
             }
         }
@@ -5888,6 +5939,14 @@
                 }
             }
         }
+
+        /**
+         * Returns true if any InputConnection is currently active.
+         */
+        @Override
+        public boolean isAnyInputConnectionActive() {
+            return mCurInputConnection != null;
+        }
     }
 
     @BinderThread
@@ -6784,8 +6843,8 @@
 
         @BinderThread
         @Override
-        public void hideMySoftInput(int flags, @SoftInputShowHideReason int reason,
-                AndroidFuture future /* T=Void */) {
+        public void hideMySoftInput(@InputMethodManager.HideFlags int flags,
+                @SoftInputShowHideReason int reason, AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked")
             final AndroidFuture<Void> typedFuture = future;
             try {
@@ -6798,7 +6857,8 @@
 
         @BinderThread
         @Override
-        public void showMySoftInput(int flags, AndroidFuture future /* T=Void */) {
+        public void showMySoftInput(@InputMethodManager.ShowFlags int flags,
+                AndroidFuture future /* T=Void */) {
             @SuppressWarnings("unchecked")
             final AndroidFuture<Void> typedFuture = future;
             try {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index c212e8e..c2ef83d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -175,13 +175,13 @@
                     int subtypeId = mSubtypeIds[which];
                     adapter.mCheckedItem = which;
                     adapter.notifyDataSetChanged();
-                    hideInputMethodMenu();
                     if (im != null) {
                         if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
                             subtypeId = NOT_A_SUBTYPE_ID;
                         }
                         mService.setInputMethodLocked(im.getId(), subtypeId);
                     }
+                    hideInputMethodMenuLocked();
                 }
             };
             mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
@@ -220,12 +220,18 @@
         }
     }
 
+    /**
+     * Hides the input method switcher menu.
+     */
     void hideInputMethodMenu() {
         synchronized (ImfLock.class) {
             hideInputMethodMenuLocked();
         }
     }
 
+    /**
+     * Hides the input method switcher menu, synchronised version of {@link #hideInputMethodMenu}.
+     */
     @GuardedBy("ImfLock.class")
     void hideInputMethodMenuLocked() {
         if (DEBUG) Slog.v(TAG, "Hide switching menu");
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 5f78374..93c66a1 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -52,6 +52,7 @@
 import android.os.ShellCallback;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.Pair;
@@ -169,6 +170,8 @@
 
     private SensorPrivacyManagerInternal mSensorPrivacyManagerInternal;
 
+    private UserManager mUserManager = null;
+
     private final Map<Integer, AtomicLong> mLastRestartTimestampMap = new HashMap<>();
 
     /**
@@ -491,6 +494,14 @@
             return;
         }
 
+        if (mUserManager == null) {
+            mUserManager = mContext.getSystemService(UserManager.class);
+            if (mUserManager == null) {
+                Log.e(TAG, "Unable to get the UserManager service");
+                return;
+            }
+        }
+
         sendMicrophoneDisableSettingUpdateForCurrentUser();
         if (mSensorPrivacyManagerInternal == null) {
             Log.e(TAG, "Unable to add a sensor privacy listener for all users");
@@ -499,8 +510,9 @@
 
         mSensorPrivacyManagerInternal.addSensorPrivacyListenerForAllUsers(
                 SensorPrivacyManager.Sensors.MICROPHONE, (userId, enabled) -> {
-                    if (userId == getCurrentUserId()) {
-                        Log.d(TAG, "User: " + userId + "mic privacy: " + enabled);
+                    // If we are in HSUM mode, any user can change the microphone setting
+                    if (mUserManager.isHeadlessSystemUserMode() || userId == getCurrentUserId()) {
+                        Log.d(TAG, "User: " + userId + " mic privacy: " + enabled);
                         sendMicrophoneDisableSettingUpdate(enabled);
                     }
                 });
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 48d46df..0616f4e 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -31,7 +31,6 @@
 import static android.os.UserHandle.USER_SYSTEM;
 
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
-import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 import static com.android.internal.widget.LockPatternUtils.CURRENT_LSKF_BASED_PROTECTOR_ID_KEY;
@@ -40,8 +39,12 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.USER_REPAIR_MODE;
 import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE;
+import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
 import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
+import static com.android.internal.widget.LockPatternUtils.isSpecialUserId;
+import static com.android.internal.widget.LockPatternUtils.pinOrPasswordQualityToCredentialType;
 import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
 import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_STRONG;
 import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_WEAK;
@@ -278,8 +281,6 @@
     protected IGateKeeperService mGateKeeperService;
     protected IAuthSecret mAuthSecretService;
 
-    private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
-
     /**
      * The UIDs that are used for system credential storage in keystore.
      */
@@ -311,6 +312,7 @@
             if (phase == PHASE_ACTIVITY_MANAGER_READY) {
                 mLockSettingsService.migrateOldDataAfterSystemReady();
                 mLockSettingsService.loadEscrowData();
+                mLockSettingsService.deleteRepairModePersistentDataIfNeeded();
             }
         }
 
@@ -541,7 +543,7 @@
         }
 
         public boolean isGsiRunning() {
-            return SystemProperties.getInt(GSI_RUNNING_PROP, 0) > 0;
+            return LockPatternUtils.isGsiRunning();
         }
 
         public FingerprintManager getFingerprintManager() {
@@ -953,6 +955,16 @@
         return success;
     }
 
+    @VisibleForTesting
+    void deleteRepairModePersistentDataIfNeeded() {
+        if (!LockPatternUtils.isRepairModeSupported(mContext)
+                || LockPatternUtils.isRepairModeActive(mContext)
+                || mInjector.isGsiRunning()) {
+            return;
+        }
+        mStorage.deleteRepairModePersistentData();
+    }
+
     // This is called when Weaver is guaranteed to be available (if the device supports Weaver).
     // It does any synthetic password related work that was delayed from earlier in the boot.
     private void onThirdPartyAppsStarted() {
@@ -1283,8 +1295,8 @@
      * {@link #CREDENTIAL_TYPE_PASSWORD}
      */
     private int getCredentialTypeInternal(int userId) {
-        if (userId == USER_FRP) {
-            return getFrpCredentialType();
+        if (isSpecialUserId(userId)) {
+            return mSpManager.getSpecialUserCredentialType(userId);
         }
         synchronized (mSpManager) {
             final long protectorId = getCurrentLskfBasedProtectorId(userId);
@@ -1300,29 +1312,6 @@
         }
     }
 
-    private int getFrpCredentialType() {
-        PersistentData data = mStorage.readPersistentDataBlock();
-        if (data.type != PersistentData.TYPE_SP_GATEKEEPER &&
-                data.type != PersistentData.TYPE_SP_WEAVER) {
-            return CREDENTIAL_TYPE_NONE;
-        }
-        int credentialType = SyntheticPasswordManager.getFrpCredentialType(data.payload);
-        if (credentialType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
-            return credentialType;
-        }
-        return pinOrPasswordQualityToCredentialType(data.qualityForUi);
-    }
-
-    private static int pinOrPasswordQualityToCredentialType(int quality) {
-        if (LockPatternUtils.isQualityAlphabeticPassword(quality)) {
-            return CREDENTIAL_TYPE_PASSWORD;
-        }
-        if (LockPatternUtils.isQualityNumericPin(quality)) {
-            return CREDENTIAL_TYPE_PIN;
-        }
-        throw new IllegalArgumentException("Quality is neither Pin nor password: " + quality);
-    }
-
     private boolean isUserSecure(int userId) {
         return getCredentialTypeInternal(userId) != CREDENTIAL_TYPE_NONE;
     }
@@ -1564,8 +1553,8 @@
      * unlock operation.
      */
     private void sendCredentialsOnUnlockIfRequired(LockscreenCredential credential, int userId) {
-        // Don't send credentials during the factory reset protection flow.
-        if (userId == USER_FRP) {
+        // Don't send credentials during the special user flow.
+        if (isSpecialUserId(userId)) {
             return;
         }
 
@@ -2189,15 +2178,19 @@
             Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
             return VerifyCredentialResponse.ERROR;
         }
+        if (userId == USER_REPAIR_MODE && !LockPatternUtils.isRepairModeActive(mContext)) {
+            Slog.e(TAG, "Repair mode is not active on the device.");
+            return VerifyCredentialResponse.ERROR;
+        }
         Slogf.i(TAG, "Verifying lockscreen credential for user %d", userId);
 
         final AuthenticationResult authResult;
         VerifyCredentialResponse response;
 
         synchronized (mSpManager) {
-            if (userId == USER_FRP) {
-                return mSpManager.verifyFrpCredential(getGateKeeperService(), credential,
-                        progressCallback);
+            if (isSpecialUserId(userId)) {
+                return mSpManager.verifySpecialUserCredential(userId, getGateKeeperService(),
+                        credential, progressCallback);
             }
 
             long protectorId = getCurrentLskfBasedProtectorId(userId);
@@ -2206,6 +2199,12 @@
             response = authResult.gkResponse;
 
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+                if ((flags & VERIFY_FLAG_WRITE_REPAIR_MODE_PW) != 0) {
+                    if (!mSpManager.writeRepairModeCredentialLocked(protectorId, userId)) {
+                        Slog.e(TAG, "Failed to write repair mode credential");
+                        return VerifyCredentialResponse.ERROR;
+                    }
+                }
                 // credential has matched
                 mBiometricDeferredQueue.addPendingLockoutResetForUser(userId,
                         authResult.syntheticPassword.deriveGkPassword());
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 2fa637e..1c5ecb7 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -19,7 +19,7 @@
 import static android.content.Context.USER_SERVICE;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.isSpecialUserId;
 
 import android.annotation.Nullable;
 import android.app.admin.DevicePolicyManager;
@@ -90,6 +90,9 @@
 
     private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
 
+    private static final String REPAIR_MODE_DIRECTORY = "repair-mode/";
+    private static final String REPAIR_MODE_PERSISTENT_FILE = "pst";
+
     private static final Object DEFAULT = new Object();
 
     private static final String[] SETTINGS_TO_BACKUP = new String[] {
@@ -390,6 +393,29 @@
         }
     }
 
+    @VisibleForTesting
+    File getRepairModePersistentDataFile() {
+        final File directory = new File(Environment.getMetadataDirectory(), REPAIR_MODE_DIRECTORY);
+        return new File(directory, REPAIR_MODE_PERSISTENT_FILE);
+    }
+
+    public PersistentData readRepairModePersistentData() {
+        final byte[] data = readFile(getRepairModePersistentDataFile());
+        if (data == null) {
+            return PersistentData.NONE;
+        }
+        return PersistentData.fromBytes(data);
+    }
+
+    public void writeRepairModePersistentData(int persistentType, int userId, byte[] payload) {
+        writeFile(getRepairModePersistentDataFile(),
+                PersistentData.toBytes(persistentType, userId, /* qualityForUi= */0, payload));
+    }
+
+    public void deleteRepairModePersistentData() {
+        deleteFile(getRepairModePersistentDataFile());
+    }
+
     /**
      * Writes the synthetic password state file for the given user ID, protector ID, and state name.
      * If the file already exists, then it is atomically replaced.
@@ -510,7 +536,8 @@
     }
 
     public void setString(String key, String value, int userId) {
-        Preconditions.checkArgument(userId != USER_FRP, "cannot store lock settings for FRP user");
+        Preconditions.checkArgument(!isSpecialUserId(userId),
+                "cannot store lock settings for special user: %d", userId);
 
         writeKeyValue(key, value, userId);
         if (ArrayUtils.contains(SETTINGS_TO_BACKUP, key)) {
@@ -535,7 +562,7 @@
     }
 
     public String getString(String key, String defaultValue, int userId) {
-        if (userId == USER_FRP) {
+        if (isSpecialUserId(userId)) {
             return null;
         }
         return readKeyValue(key, defaultValue, userId);
@@ -583,6 +610,17 @@
         }
     }
 
+    /**
+     * Provides a concrete data structure to represent the minimal information from
+     * a user's LSKF-based SP protector that is needed to verify the user's LSKF,
+     * in combination with the corresponding Gatekeeper enrollment or Weaver slot.
+     * It can be stored in {@link com.android.server.PersistentDataBlockService} for
+     * FRP to live across factory resets not initiated via the Settings UI.
+     * Written to {@link #REPAIR_MODE_PERSISTENT_FILE} to support verification for
+     * exiting repair mode, since the device runs with an empty data partition in
+     * repair mode and the same credential be provided to exit repair mode is
+     * required.
+     */
     public static class PersistentData {
         static final byte VERSION_1 = 1;
         static final int VERSION_1_HEADER_SIZE = 1 + 1 + 4 + 4;
@@ -685,6 +723,19 @@
             }
             pw.decreaseIndent();
         }
+        // Dump repair mode file states
+        final File repairModeFile = getRepairModePersistentDataFile();
+        if (repairModeFile.exists()) {
+            pw.println(TextUtils.formatSimple("Repair Mode [%s]:", repairModeFile.getParent()));
+            pw.increaseIndent();
+            pw.println(TextUtils.formatSimple("%6d %s %s", repairModeFile.length(),
+                    LockSettingsService.timestampToString(repairModeFile.lastModified()),
+                    repairModeFile.getName()));
+            final PersistentData data = readRepairModePersistentData();
+            pw.println(TextUtils.formatSimple("type: %d, user id: %d, payload size: %d",
+                    data.type, data.userId, data.payload != null ? data.payload.length : 0));
+            pw.decreaseIndent();
+        }
     }
 
     static class DatabaseHelper extends SQLiteOpenHelper {
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 66f862a..7349088 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -16,9 +16,14 @@
 
 package com.android.server.locksettings;
 
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
 import static com.android.internal.widget.LockPatternUtils.PIN_LENGTH_UNAVAILABLE;
+import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+import static com.android.internal.widget.LockPatternUtils.USER_REPAIR_MODE;
+import static com.android.internal.widget.LockPatternUtils.pinOrPasswordQualityToCredentialType;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -718,11 +723,30 @@
         return PasswordData.fromBytes(passwordData).credentialType;
     }
 
-    static int getFrpCredentialType(byte[] payload) {
-        if (payload == null) {
+    int getSpecialUserCredentialType(int userId) {
+        final PersistentData data = getSpecialUserPersistentData(userId);
+        if (data.type != PersistentData.TYPE_SP_GATEKEEPER
+                && data.type != PersistentData.TYPE_SP_WEAVER) {
+            return CREDENTIAL_TYPE_NONE;
+        }
+        if (data.payload == null) {
             return LockPatternUtils.CREDENTIAL_TYPE_NONE;
         }
-        return PasswordData.fromBytes(payload).credentialType;
+        final int credentialType = PasswordData.fromBytes(data.payload).credentialType;
+        if (credentialType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
+            return credentialType;
+        }
+        return pinOrPasswordQualityToCredentialType(data.qualityForUi);
+    }
+
+    private PersistentData getSpecialUserPersistentData(int userId) {
+        if (userId == USER_FRP) {
+            return mStorage.readPersistentDataBlock();
+        }
+        if (userId == USER_REPAIR_MODE) {
+            return mStorage.readRepairModePersistentData();
+        }
+        throw new IllegalArgumentException("Unknown special user id " + userId);
     }
 
     /**
@@ -1005,10 +1029,10 @@
         return sizeOfCredential;
     }
 
-    public VerifyCredentialResponse verifyFrpCredential(IGateKeeperService gatekeeper,
-            LockscreenCredential userCredential,
+    public VerifyCredentialResponse verifySpecialUserCredential(int sourceUserId,
+            IGateKeeperService gatekeeper, LockscreenCredential userCredential,
             ICheckCredentialProgressCallback progressCallback) {
-        PersistentData persistentData = mStorage.readPersistentDataBlock();
+        final PersistentData persistentData = getSpecialUserPersistentData(sourceUserId);
         if (persistentData.type == PersistentData.TYPE_SP_GATEKEEPER) {
             PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
             byte[] stretchedLskf = stretchLskf(userCredential, pwd);
@@ -1019,13 +1043,13 @@
                         0 /* challenge */, pwd.passwordHandle,
                         stretchedLskfToGkPassword(stretchedLskf));
             } catch (RemoteException e) {
-                Slog.e(TAG, "FRP verifyChallenge failed", e);
+                Slog.e(TAG, "Persistent data credential verifyChallenge failed", e);
                 return VerifyCredentialResponse.ERROR;
             }
             return VerifyCredentialResponse.fromGateKeeperResponse(response);
         } else if (persistentData.type == PersistentData.TYPE_SP_WEAVER) {
             if (!isWeaverAvailable()) {
-                Slog.e(TAG, "No weaver service to verify SP-based FRP credential");
+                Slog.e(TAG, "No weaver service to verify SP-based persistent data credential");
                 return VerifyCredentialResponse.ERROR;
             }
             PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
@@ -1114,6 +1138,57 @@
         }
     }
 
+    /**
+     * Writes the user's synthetic password data to the repair mode file.
+     *
+     * @param protectorId current LSKF based protectorId
+     * @param userId user id of the user
+     */
+    public boolean writeRepairModeCredentialLocked(long protectorId, int userId) {
+        if (!shouldWriteRepairModeCredential(userId)) {
+            return false;
+        }
+        final byte[] data = loadState(PASSWORD_DATA_NAME, protectorId, userId);
+        if (data == null) {
+            Slogf.w(TAG, "Password data not found for user %d", userId);
+            return false;
+        }
+        final PasswordData pwd = PasswordData.fromBytes(data);
+        if (isNoneCredential(pwd)) {
+            Slogf.w(TAG, "User %d has NONE credential", userId);
+            return false;
+        }
+        Slogf.d(TAG, "Writing repair mode credential tied to user %d", userId);
+        final int weaverSlot = loadWeaverSlot(protectorId, userId);
+        if (weaverSlot != INVALID_WEAVER_SLOT) {
+            // write weaver password
+            mStorage.writeRepairModePersistentData(
+                    PersistentData.TYPE_SP_WEAVER, weaverSlot, pwd.toBytes());
+        } else {
+            // write gatekeeper password
+            mStorage.writeRepairModePersistentData(
+                    PersistentData.TYPE_SP_GATEKEEPER, userId, pwd.toBytes());
+        }
+        return true;
+    }
+
+    private boolean shouldWriteRepairModeCredential(int userId) {
+        final UserInfo userInfo = mUserManager.getUserInfo(userId);
+        if (!LockPatternUtils.canUserEnterRepairMode(mContext, userInfo)) {
+            Slogf.w(TAG, "User %d can't enter repair mode", userId);
+            return false;
+        }
+        if (LockPatternUtils.isRepairModeActive(mContext)) {
+            Slog.w(TAG, "Can't write repair mode credential while repair mode is already active");
+            return false;
+        }
+        if (LockPatternUtils.isGsiRunning()) {
+            Slog.w(TAG, "Can't write repair mode credential while GSI is running");
+            return false;
+        }
+        return true;
+    }
+
     private ArrayMap<Integer, ArrayMap<Long, TokenData>> tokenMap = new ArrayMap<>();
 
     /**
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index 497ed03..fee54f5 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -28,6 +28,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.os.Build;
+import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.ILogd;
 import android.os.Looper;
@@ -518,7 +519,15 @@
             Slog.d(TAG, "Approving log access: " + request);
         }
         try {
-            getLogdService().approve(request.mUid, request.mGid, request.mPid, request.mFd);
+            try {
+                getLogdService().approve(request.mUid, request.mGid, request.mPid, request.mFd);
+            } catch (DeadObjectException e) {
+                // This can happen if logd restarts, so force getting a new connection
+                // to logd and try once more.
+                Slog.w(TAG, "Logd connection no longer valid while approving, trying once more.");
+                mLogdService = null;
+                getLogdService().approve(request.mUid, request.mGid, request.mPid, request.mFd);
+            }
             Integer activeCount = mActiveLogAccessCount.getOrDefault(client, 0);
             mActiveLogAccessCount.put(client, activeCount + 1);
         } catch (RemoteException e) {
@@ -531,7 +540,15 @@
             Slog.d(TAG, "Declining log access: " + request);
         }
         try {
-            getLogdService().decline(request.mUid, request.mGid, request.mPid, request.mFd);
+            try {
+                getLogdService().decline(request.mUid, request.mGid, request.mPid, request.mFd);
+            } catch (DeadObjectException e) {
+                // This can happen if logd restarts, so force getting a new connection
+                // to logd and try once more.
+                Slog.w(TAG, "Logd connection no longer valid while declining, trying once more.");
+                mLogdService = null;
+                getLogdService().decline(request.mUid, request.mGid, request.mPid, request.mFd);
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "Fails to call remote functions", e);
         }
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 38aac46..802a7f2 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -153,7 +153,7 @@
         mHandler = new Handler(Looper.getMainLooper());
         mClock = injector.createClock();
         mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>();
-        mCallbackDelegate = new CallbackDelegate();
+        mCallbackDelegate = new CallbackDelegate(injector.createCallbackLooper());
         mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mPackageManager = mContext.getPackageManager();
@@ -188,6 +188,11 @@
         Clock createClock() {
             return SystemClock::uptimeMillis;
         }
+
+        /** Creates the {@link Looper} to be used when notifying callbacks. */
+        Looper createCallbackLooper() {
+            return Looper.getMainLooper();
+        }
     }
 
     @Override
@@ -220,6 +225,7 @@
         mMediaRouter.rebindAsUser(to.getUserIdentifier());
         synchronized (mLock) {
             if (mProjectionGrant != null) {
+                Slog.d(TAG, "Content Recording: Stopped MediaProjection due to user switching");
                 mProjectionGrant.stop();
             }
         }
@@ -255,6 +261,8 @@
         }
 
         synchronized (mLock) {
+            Slog.d(TAG,
+                    "Content Recording: Stopped MediaProjection due to foreground service change");
             if (mProjectionGrant != null) {
                 mProjectionGrant.stop();
             }
@@ -263,6 +271,8 @@
 
     private void startProjectionLocked(final MediaProjection projection) {
         if (mProjectionGrant != null) {
+            Slog.d(TAG, "Content Recording: Stopped MediaProjection to start new "
+                    + "incoming projection");
             mProjectionGrant.stop();
         }
         if (mMediaRouteInfo != null) {
@@ -274,12 +284,15 @@
     }
 
     private void stopProjectionLocked(final MediaProjection projection) {
+        Slog.d(TAG, "Content Recording: Stopped active MediaProjection and "
+                + "dispatching stop to callbacks");
         mProjectionToken = null;
         mProjectionGrant = null;
         dispatchStop(projection);
     }
 
-    private void addCallback(final IMediaProjectionWatcherCallback callback) {
+    @VisibleForTesting
+    void addCallback(final IMediaProjectionWatcherCallback callback) {
         IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
             @Override
             public void binderDied() {
@@ -326,6 +339,12 @@
         mCallbackDelegate.dispatchStop(projection);
     }
 
+    private void dispatchSessionSet(
+            @NonNull MediaProjectionInfo projectionInfo,
+            @Nullable ContentRecordingSession session) {
+        mCallbackDelegate.dispatchSession(projectionInfo, session);
+    }
+
     /**
      * Returns {@code true} when updating the current mirroring session on WM succeeded, and
      * {@code false} otherwise.
@@ -339,6 +358,13 @@
             if (!setSessionSucceeded) {
                 // Unable to start mirroring, so tear down this projection.
                 if (mProjectionGrant != null) {
+                    String projectionType = incomingSession != null
+                            ? ContentRecordingSession.recordContentToString(
+                                    incomingSession.getContentToRecord()) : "none";
+                    Slog.w(TAG, "Content Recording: Stopped MediaProjection due to failing to set "
+                            + "ContentRecordingSession - id= "
+                            + mProjectionGrant.getVirtualDisplayId() + "type=" + projectionType);
+
                     mProjectionGrant.stop();
                 }
                 return false;
@@ -346,6 +372,7 @@
             if (mProjectionGrant != null) {
                 // Cache the session details.
                 mProjectionGrant.mSession = incomingSession;
+                dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession);
             }
             return true;
         }
@@ -451,6 +478,9 @@
                     // The grant may now be null if setting the session failed.
                     if (mProjectionGrant != null) {
                         // Always stop the projection.
+                        Slog.w(TAG, "Content Recording: Stopped MediaProjection due to user "
+                                + "consent result of CANCEL - "
+                                + "id= " + mProjectionGrant.getVirtualDisplayId());
                         mProjectionGrant.stop();
                     }
                     break;
@@ -659,6 +689,7 @@
             try {
                 synchronized (mLock) {
                     if (mProjectionGrant != null) {
+                        Slog.d(TAG, "Content Recording: Stopping active projection");
                         mProjectionGrant.stop();
                     }
                 }
@@ -869,6 +900,10 @@
                     MEDIA_PROJECTION_TOKEN_EVENT_CREATED);
         }
 
+        int getVirtualDisplayId() {
+            return mVirtualDisplayId;
+        }
+
         @Override // Binder call
         public boolean canProjectVideo() {
             return mType == MediaProjectionManager.TYPE_MIRRORING ||
@@ -945,12 +980,11 @@
                 registerCallback(mCallback);
                 try {
                     mToken = callback.asBinder();
-                    mDeathEater = new IBinder.DeathRecipient() {
-                        @Override
-                        public void binderDied() {
-                            mCallbackDelegate.remove(callback);
-                            stop();
-                        }
+                    mDeathEater = () -> {
+                        Slog.d(TAG, "Content Recording: MediaProjection stopped by Binder death - "
+                                + "id= " + mVirtualDisplayId);
+                        mCallbackDelegate.remove(callback);
+                        stop();
                     };
                     mToken.linkToDeath(mDeathEater, 0);
                 } catch (RemoteException e) {
@@ -1020,6 +1054,9 @@
                         Binder.restoreCallingIdentity(token);
                     }
                 }
+                Slog.d(TAG, "Content Recording: handling stopping this projection token"
+                        + " createTime= " + mCreateTimeMs
+                        + " countStarts= " + mCountStarts);
                 stopProjectionLocked(this);
                 mToken.unlinkToDeath(mDeathEater, 0);
                 mToken = null;
@@ -1145,6 +1182,8 @@
                 if ((type & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
                     mMediaRouteInfo = info;
                     if (mProjectionGrant != null) {
+                        Slog.d(TAG, "Content Recording: Stopped MediaProjection due to "
+                                + "route type of REMOTE_DISPLAY not selected");
                         mProjectionGrant.stop();
                     }
                 }
@@ -1168,8 +1207,8 @@
         private Handler mHandler;
         private final Object mLock = new Object();
 
-        public CallbackDelegate() {
-            mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
+        CallbackDelegate(Looper callbackLooper) {
+            mHandler = new Handler(callbackLooper, null, true /*async*/);
             mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>();
             mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>();
         }
@@ -1232,6 +1271,16 @@
             }
         }
 
+        public void dispatchSession(
+                @NonNull MediaProjectionInfo projectionInfo,
+                @Nullable ContentRecordingSession session) {
+            synchronized (mLock) {
+                for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) {
+                    mHandler.post(new WatcherSessionCallback(callback, projectionInfo, session));
+                }
+            }
+        }
+
         public void dispatchResize(MediaProjection projection, int width, int height) {
             if (projection == null) {
                 Slog.e(TAG,
@@ -1306,7 +1355,7 @@
             try {
                 mCallback.onStart(mInfo);
             } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to notify media projection has stopped", e);
+                Slog.w(TAG, "Failed to notify media projection has started", e);
             }
         }
     }
@@ -1348,6 +1397,29 @@
         }
     }
 
+    private static final class WatcherSessionCallback implements Runnable {
+        private final IMediaProjectionWatcherCallback mCallback;
+        private final MediaProjectionInfo mProjectionInfo;
+        private final ContentRecordingSession mSession;
+
+        WatcherSessionCallback(
+                @NonNull IMediaProjectionWatcherCallback callback,
+                @NonNull MediaProjectionInfo projectionInfo,
+                @Nullable ContentRecordingSession session) {
+            mCallback = callback;
+            mProjectionInfo = projectionInfo;
+            mSession = session;
+        }
+
+        @Override
+        public void run() {
+            try {
+                mCallback.onRecordingSessionSet(mProjectionInfo, mSession);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to notify content recording session changed", e);
+            }
+        }
+    }
 
     private static String typeToString(int type) {
         switch (type) {
@@ -1357,7 +1429,8 @@
                 return "TYPE_MIRRORING";
             case MediaProjectionManager.TYPE_PRESENTATION:
                 return "TYPE_PRESENTATION";
+            default:
+                return Integer.toString(type);
         }
-        return Integer.toString(type);
     }
 }
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 030c96e..bfc4f53 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -251,6 +251,11 @@
     }
 
     @Override
+    protected boolean allowRebindForParentUser() {
+        return true;
+    }
+
+    @Override
     protected String getRequiredPermission() {
         return null;
     }
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 12fc263..5dd958b 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1371,7 +1371,9 @@
     protected void rebindServices(boolean forceRebind, int userToRebind) {
         if (DEBUG) Slog.d(TAG, "rebindServices " + forceRebind + " " + userToRebind);
         IntArray userIds = mUserProfiles.getCurrentProfileIds();
-        if (userToRebind != USER_ALL) {
+        boolean rebindAllCurrentUsers = mUserProfiles.isProfileUser(userToRebind)
+                && allowRebindForParentUser();
+        if (userToRebind != USER_ALL && !rebindAllCurrentUsers) {
             userIds = new IntArray(1);
             userIds.add(userToRebind);
         }
@@ -1758,6 +1760,13 @@
         return true;
     }
 
+    /**
+     * Returns true if services in the parent user should be rebound
+     *  when rebindServices is called with a profile userId.
+     * Must be false for NotificationAssistants.
+     */
+    protected abstract boolean allowRebindForParentUser();
+
     public class ManagedServiceInfo implements IBinder.DeathRecipient {
         public IInterface service;
         public ComponentName component;
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 446c4f7..0e76dfb 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -25,7 +25,7 @@
 import android.content.IntentFilter;
 import android.telecom.TelecomManager;
 
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.NotificationMessagingUtil;
 
 import java.util.Comparator;
@@ -34,21 +34,23 @@
 /**
  * Sorts notifications individually into attention-relevant order.
  */
-public class NotificationComparator
-        implements Comparator<NotificationRecord> {
+class NotificationComparator implements Comparator<NotificationRecord> {
 
     private final Context mContext;
     private final NotificationMessagingUtil mMessagingUtil;
-    private final boolean mSortByInterruptiveness;
     private String mDefaultPhoneApp;
 
+    /**
+     * Lock that must be held during a sort() call that uses this {@link Comparator}, AND to make
+     * any changes to the state of this object that could affect the results of {@link #compare}.
+     */
+    public final Object mStateLock = new Object();
+
     public NotificationComparator(Context context) {
         mContext = context;
         mContext.registerReceiver(mPhoneAppBroadcastReceiver,
                 new IntentFilter(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED));
-        mMessagingUtil = new NotificationMessagingUtil(mContext);
-        mSortByInterruptiveness = !SystemUiSystemPropertiesFlags.getResolver().isEnabled(
-                SystemUiSystemPropertiesFlags.NotificationFlags.NO_SORT_BY_INTERRUPTIVENESS);
+        mMessagingUtil = new NotificationMessagingUtil(mContext, mStateLock);
     }
 
     @Override
@@ -139,14 +141,6 @@
             return -1 * Integer.compare(leftPriority, rightPriority);
         }
 
-        if (mSortByInterruptiveness) {
-            final boolean leftInterruptive = left.isInterruptive();
-            final boolean rightInterruptive = right.isInterruptive();
-            if (leftInterruptive != rightInterruptive) {
-                return -1 * Boolean.compare(leftInterruptive, rightInterruptive);
-            }
-        }
-
         // then break ties by time, most recent first
         return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs());
     }
@@ -224,8 +218,13 @@
     private final BroadcastReceiver mPhoneAppBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            mDefaultPhoneApp =
-                    intent.getStringExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME);
+            BackgroundThread.getExecutor().execute(() -> {
+                synchronized (mStateLock) {
+                    mDefaultPhoneApp =
+                            intent.getStringExtra(
+                                    TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME);
+                }
+            });
         }
     };
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index 919fc71..c240bcb 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -27,6 +27,9 @@
     NotificationChannelGroup getNotificationChannelGroup(String pkg, int uid, String channelId);
     void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid,
             String tag, int id, Notification notification, int userId);
+    void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid,
+            String tag, int id, Notification notification, int userId,
+            boolean byForegroundService);
     void cancelNotification(String pkg, String basePkg, int callingUid, int callingPid,
             String tag, int id, int userId);
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 82892de..15a8c0f 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -118,7 +118,6 @@
 import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
@@ -322,7 +321,6 @@
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.PermissionPolicyInternal;
-import com.android.server.powerstats.StatsPullAtomCallbackImpl;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.utils.Slogf;
@@ -1022,6 +1020,7 @@
         }
 
         mAssistants.resetDefaultAssistantsIfNecessary();
+        mPreferencesHelper.syncChannelsBypassingDnd();
     }
 
     @VisibleForTesting
@@ -1222,8 +1221,7 @@
                 }
             }
 
-            int mustNotHaveFlags = mFlagResolver.isEnabled(ALLOW_DISMISS_ONGOING)
-                    ? FLAG_NO_DISMISS : FLAG_ONGOING_EVENT;
+            int mustNotHaveFlags = FLAG_NO_DISMISS;
             cancelNotification(callingUid, callingPid, pkg, tag, id,
                     /* mustHaveFlags= */ 0,
                     /* mustNotHaveFlags= */ mustNotHaveFlags,
@@ -1715,7 +1713,6 @@
                 return;
             }
 
-            boolean queryRestart = false;
             boolean queryRemove = false;
             boolean packageChanged = false;
             boolean cancelNotifications = true;
@@ -1727,7 +1724,6 @@
                     || (queryRemove=action.equals(Intent.ACTION_PACKAGE_REMOVED))
                     || action.equals(Intent.ACTION_PACKAGE_RESTARTED)
                     || (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED))
-                    || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
                     || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)
                     || action.equals(Intent.ACTION_PACKAGES_SUSPENDED)
                     || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)
@@ -1768,10 +1764,6 @@
                         cancelNotifications = false;
                         unhideNotifications = true;
                     }
-
-                } else if (queryRestart) {
-                    pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
-                    uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)};
                 } else {
                     Uri uri = intent.getData();
                     if (uri == null) {
@@ -1809,7 +1801,7 @@
                     if (cancelNotifications) {
                         for (String pkgName : pkgList) {
                             cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, null, 0, 0,
-                                    !queryRestart, changeUserId, reason, null);
+                                    changeUserId, reason);
                         }
                     } else if (hideNotifications && uidList != null && (uidList.length > 0)) {
                         hideNotificationsForPackages(pkgList, uidList);
@@ -1843,14 +1835,14 @@
             } else if (action.equals(Intent.ACTION_USER_STOPPED)) {
                 int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                 if (userHandle >= 0) {
-                    cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
-                            REASON_USER_STOPPED, null);
+                    cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
+                            REASON_USER_STOPPED);
                 }
             } else if (action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) {
                 int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                 if (userHandle >= 0 && !mDpm.isKeepProfilesRunningEnabled()) {
-                    cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
-                            REASON_PROFILE_TURNED_OFF, null);
+                    cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
+                            REASON_PROFILE_TURNED_OFF);
                     mSnoozeHelper.clearData(userHandle);
                 }
             } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
@@ -1868,6 +1860,7 @@
                     mConditionProviders.onUserSwitched(userId);
                     mListeners.onUserSwitched(userId);
                     mZenModeHelper.onUserSwitched(userId);
+                    mPreferencesHelper.syncChannelsBypassingDnd();
                 }
                 // assistant is the only thing that cares about managed profiles specifically
                 mAssistants.onUserSwitched(userId);
@@ -2344,6 +2337,7 @@
                 mRankingHandler,
                 mZenModeHelper,
                 mPermissionHelper,
+                mPermissionManager,
                 mNotificationChannelLogger,
                 mAppOps,
                 new SysUiStatsEvent.BuilderFactory(),
@@ -2467,7 +2461,6 @@
         pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
-        pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
         pkgFilter.addDataScheme("package");
         getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, pkgFilter, null,
                 null);
@@ -2498,6 +2491,16 @@
         getContext().registerReceiver(mReviewNotificationPermissionsReceiver,
                 ReviewNotificationPermissionsReceiver.getFilter(),
                 Context.RECEIVER_NOT_EXPORTED);
+
+        mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null,
+                new AppOpsManager.OnOpChangedInternalListener() {
+                    @Override
+                    public void onOpChanged(@NonNull String op, @NonNull String packageName,
+                            int userId) {
+                        mHandler.post(
+                                () -> handleNotificationPermissionChange(packageName, userId));
+                    }
+                });
     }
 
     /**
@@ -2528,7 +2531,8 @@
                 }
                 enqueueNotificationInternal(r.getSbn().getPackageName(), r.getSbn().getOpPkg(),
                         r.getSbn().getUid(), r.getSbn().getInitialPid(), r.getSbn().getTag(),
-                        r.getSbn().getId(),  r.getSbn().getNotification(), userId, muteOnReturn);
+                        r.getSbn().getId(),  r.getSbn().getNotification(), userId, muteOnReturn,
+                        false /* byForegroundService */);
             } catch (Exception e) {
                 Slog.e(TAG, "Cannot un-snooze notification", e);
             }
@@ -2854,17 +2858,17 @@
             boolean fromListener) {
         if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
             // cancel
-            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
-                    UserHandle.getUserId(uid), REASON_CHANNEL_BANNED,
-                    null);
+            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0,
+                    UserHandle.getUserId(uid), REASON_CHANNEL_BANNED
+            );
             if (isUidSystemOrPhone(uid)) {
                 IntArray profileIds = mUserProfiles.getCurrentProfileIds();
                 int N = profileIds.size();
                 for (int i = 0; i < N; i++) {
                     int profileId = profileIds.get(i);
-                    cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
-                            profileId, REASON_CHANNEL_BANNED,
-                            null);
+                    cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0,
+                            profileId, REASON_CHANNEL_BANNED
+                    );
                 }
             }
         }
@@ -3288,6 +3292,11 @@
         return new MultiRateLimiter.Builder(getContext()).addRateLimits(TOAST_RATE_LIMITS).build();
     }
 
+    protected int checkComponentPermission(String permission, int uid, int owningUid,
+            boolean exported) {
+        return ActivityManager.checkComponentPermission(permission, uid, owningUid, exported);
+    }
+
     @VisibleForTesting
     final IBinder mService = new INotificationManager.Stub() {
         // Toasts
@@ -3518,7 +3527,8 @@
         public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
                 Notification notification, int userId) throws RemoteException {
             enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
-                    Binder.getCallingPid(), tag, id, notification, userId);
+                    Binder.getCallingPid(), tag, id, notification, userId,
+                    false /* byForegroundService */);
         }
 
         @Override
@@ -3538,7 +3548,7 @@
             // Don't allow the app to cancel active FGS or UIJ notifications
             cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
                     pkg, null, 0, FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB,
-                    true, userId, REASON_APP_CANCEL_ALL, null);
+                    userId, REASON_APP_CANCEL_ALL);
         }
 
         @Override
@@ -3564,13 +3574,9 @@
                     .setPackageName(pkg)
                     .setSubtype(enabled ? 1 : 0));
             mNotificationChannelLogger.logAppNotificationsAllowed(uid, pkg, enabled);
-            // Now, cancel any outstanding notifications that are part of a just-disabled app
-            if (!enabled) {
-                cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
-                        UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
-            }
 
-            handleSavePolicyFile();
+            // Outstanding notifications from this package will be cancelled as soon as we get the
+            // callback from AppOpsManager.
         }
 
         /**
@@ -4029,8 +4035,8 @@
             }
             enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId);
             enforceDeletingChannelHasNoUserInitiatedJob(pkg, callingUser, channelId);
-            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
-                    callingUser, REASON_CHANNEL_REMOVED, null);
+            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0,
+                    callingUser, REASON_CHANNEL_REMOVED);
             boolean previouslyExisted = mPreferencesHelper.deleteNotificationChannel(
                     pkg, callingUid, channelId, callingUid, isSystemOrSystemUi);
             if (previouslyExisted) {
@@ -4084,9 +4090,8 @@
                 for (int i = 0; i < deletedChannels.size(); i++) {
                     final NotificationChannel deletedChannel = deletedChannels.get(i);
                     cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0,
-                            true,
-                            userId, REASON_CHANNEL_REMOVED,
-                            null);
+                            userId, REASON_CHANNEL_REMOVED
+                    );
                     mListeners.notifyNotificationChannelChanged(pkg,
                             UserHandle.getUserHandleForUid(callingUid),
                             deletedChannel,
@@ -4255,8 +4260,8 @@
             checkCallerIsSystem();
             // Cancel posted notifications
             final int userId = UserHandle.getUserId(uid);
-            cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0, true,
-                    UserHandle.getUserId(Binder.getCallingUid()), REASON_CLEAR_DATA, null);
+            cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0,
+                    UserHandle.getUserId(Binder.getCallingUid()), REASON_CLEAR_DATA);
 
             // Zen
             packagesChanged |=
@@ -5246,10 +5251,11 @@
         }
 
         private boolean checkPolicyAccess(String pkg) {
+            final int uid;
             try {
-                int uid = getContext().getPackageManager().getPackageUidAsUser(pkg,
+                uid = getContext().getPackageManager().getPackageUidAsUser(pkg,
                         UserHandle.getCallingUserId());
-                if (PackageManager.PERMISSION_GRANTED == ActivityManager.checkComponentPermission(
+                if (PackageManager.PERMISSION_GRANTED == checkComponentPermission(
                         android.Manifest.permission.MANAGE_NOTIFICATIONS, uid,
                         -1, true)) {
                     return true;
@@ -5260,8 +5266,8 @@
             //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
             return checkPackagePolicyAccess(pkg)
                     || mListeners.isComponentEnabledForPackage(pkg)
-                    || (mDpm != null && (mDpm.isActiveProfileOwner(Binder.getCallingUid())
-                                || mDpm.isActiveDeviceOwner(Binder.getCallingUid())));
+                    || (mDpm != null && (mDpm.isActiveProfileOwner(uid)
+                                || mDpm.isActiveDeviceOwner(uid)));
         }
 
         @Override
@@ -5894,6 +5900,23 @@
         }
     };
 
+    private void handleNotificationPermissionChange(String pkg, @UserIdInt int userId) {
+        if (!mUmInternal.isUserInitialized(userId)) {
+            return; // App-op "updates" are sent when starting a new user the first time.
+        }
+        int uid = mPackageManagerInternal.getPackageUid(pkg, 0, userId);
+        if (uid == INVALID_UID) {
+            Log.e(TAG, String.format("No uid found for %s, %s!", pkg, userId));
+            return;
+        }
+        boolean hasPermission = mPermissionHelper.hasPermission(uid);
+        if (!hasPermission) {
+            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, /* channelId= */ null,
+                    /* mustHaveFlags= */ 0, /* mustNotHaveFlags= */ 0, userId,
+                    REASON_PACKAGE_BANNED);
+        }
+    }
+
     protected void checkNotificationListenerAccess() {
         if (!isCallerSystemOrPhone()) {
             getContext().enforceCallingPermission(
@@ -6074,7 +6097,7 @@
             }
             if (summaryRecord != null && checkDisqualifyingFeatures(userId, uid,
                     summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord,
-                    true)) {
+                    true, false)) {
                 return summaryRecord;
             }
         }
@@ -6403,7 +6426,15 @@
         public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid,
                 String tag, int id, Notification notification, int userId) {
             enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
-                    userId);
+                    userId, false /* byForegroundService */);
+        }
+
+        @Override
+        public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid,
+                String tag, int id, Notification notification, int userId,
+                boolean byForegroundService) {
+            enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
+                    userId, byForegroundService);
         }
 
         @Override
@@ -6581,19 +6612,19 @@
 
     void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
             final int callingPid, final String tag, final int id, final Notification notification,
-            int incomingUserId) {
+            int incomingUserId, boolean byForegroundService) {
         enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
-                incomingUserId, false);
+                incomingUserId, false /* postSilently */, byForegroundService);
     }
 
     void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
             final int callingPid, final String tag, final int id, final Notification notification,
-            int incomingUserId, boolean postSilently) {
+            int incomingUserId, boolean postSilently, boolean byForegroundService) {
         PostNotificationTracker tracker = acquireWakeLockForPost(pkg, callingUid);
         boolean enqueued = false;
         try {
             enqueued = enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id,
-                    notification, incomingUserId, postSilently, tracker);
+                    notification, incomingUserId, postSilently, tracker, byForegroundService);
         } finally {
             if (!enqueued) {
                 tracker.cancel();
@@ -6624,10 +6655,10 @@
      * @return True if we successfully processed the notification and handed off the task of
      * enqueueing it to a background thread; false otherwise.
      */
-    private boolean enqueueNotificationInternal(final String pkg, final String opPkg,
+    private boolean enqueueNotificationInternal(final String pkg, final String opPkg,  //HUI
             final int callingUid, final int callingPid, final String tag, final int id,
             final Notification notification, int incomingUserId, boolean postSilently,
-            PostNotificationTracker tracker) {
+            PostNotificationTracker tracker, boolean byForegroundService) {
         if (DBG) {
             Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
                     + " notification=" + notification);
@@ -6773,10 +6804,12 @@
                 mPreferencesHelper.hasUserDemotedInvalidMsgApp(pkg, notificationUid));
 
         if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
-                r.getSbn().getOverrideGroupKey() != null)) {
+                r.getSbn().getOverrideGroupKey() != null, byForegroundService)) {
             return false;
         }
 
+        mUsageStats.registerEnqueuedByAppAndAccepted(pkg);
+
         if (info != null) {
             // Cache the shortcut synchronously after the associated notification is posted in case
             // the app unpublishes this shortcut immediately after posting the notification. If the
@@ -6828,9 +6861,9 @@
                 mPreferencesHelper.deleteConversations(pkg, uid, shortcuts,
                         /* callingUid */ Process.SYSTEM_UID, /* is system */ true);
         for (String channelId : deletedChannelIds) {
-            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
-                    UserHandle.getUserId(uid), REASON_CHANNEL_REMOVED,
-                    null);
+            cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0,
+                    UserHandle.getUserId(uid), REASON_CHANNEL_REMOVED
+            );
         }
         handleSavePolicyFile();
     }
@@ -6871,13 +6904,11 @@
         }
 
         // Only notifications that can be non-dismissible can have the flag FLAG_NO_DISMISS
-        if (mFlagResolver.isEnabled(ALLOW_DISMISS_ONGOING)) {
-            if (((notification.flags & FLAG_ONGOING_EVENT) > 0)
-                    && canBeNonDismissible(ai, notification)) {
-                notification.flags |= FLAG_NO_DISMISS;
-            } else {
-                notification.flags &= ~FLAG_NO_DISMISS;
-            }
+        if (((notification.flags & FLAG_ONGOING_EVENT) > 0)
+                && canBeNonDismissible(ai, notification)) {
+            notification.flags |= FLAG_NO_DISMISS;
+        } else {
+            notification.flags &= ~FLAG_NO_DISMISS;
         }
 
         int canColorize = getContext().checkPermission(
@@ -7193,7 +7224,7 @@
      * Has side effects.
      */
     boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag,
-            NotificationRecord r, boolean isAutogroup) {
+            NotificationRecord r, boolean isAutogroup, boolean byForegroundService) {
         Notification n = r.getNotification();
         final String pkg = r.getSbn().getPackageName();
         final boolean isSystemNotification =
@@ -7215,11 +7246,12 @@
                             + " cannot create notifications");
                 }
 
-                // rate limit updates that aren't completed progress notifications
-                if (mNotificationsByKey.get(r.getSbn().getKey()) != null
-                        && !r.getNotification().hasCompletedProgress()
-                        && !isAutogroup) {
-
+                // Rate limit updates that aren't completed progress notifications
+                // Search for the original one in the posted and not-yet-posted (enqueued) lists.
+                boolean isUpdate = mNotificationsByKey.get(r.getSbn().getKey()) != null
+                        || findNotificationByListLocked(mEnqueuedNotifications, r.getSbn().getKey())
+                        != null;
+                if (isUpdate && !r.getNotification().hasCompletedProgress() && !isAutogroup) {
                     final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
                     if (appEnqueueRate > mMaxPackageEnqueueRate) {
                         mUsageStats.registerOverRateQuota(pkg);
@@ -7283,7 +7315,8 @@
         if (n.isStyle(Notification.CallStyle.class)) {
             boolean hasFullScreenIntent = n.fullScreenIntent != null;
             boolean requestedFullScreenIntent = (n.flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0;
-            if (!n.isFgsOrUij() && !hasFullScreenIntent && !requestedFullScreenIntent) {
+            if (!n.isFgsOrUij() && !hasFullScreenIntent && !requestedFullScreenIntent
+                    && !byForegroundService) {
                 throw new IllegalArgumentException(r.getKey() + " Not posted."
                         + " CallStyle notifications must be for a foreground service or"
                         + " user initated job or use a fullScreenIntent.");
@@ -7844,15 +7877,8 @@
             boolean posted = false;
             synchronized (mNotificationLock) {
                 try {
-                    NotificationRecord r = null;
-                    int N = mEnqueuedNotifications.size();
-                    for (int i = 0; i < N; i++) {
-                        final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
-                        if (Objects.equals(key, enqueued.getKey())) {
-                            r = enqueued;
-                            break;
-                        }
-                    }
+                    NotificationRecord r = findNotificationByListLocked(mEnqueuedNotifications,
+                            key);
                     if (r == null) {
                         Slog.i(TAG, "Cannot find enqueued record for key: " + key);
                         return false;
@@ -9511,14 +9537,19 @@
      * Determine whether the userId applies to the notification in question, either because
      * they match exactly, or one of them is USER_ALL (which is treated as a wildcard).
      */
-    private boolean notificationMatchesUserId(NotificationRecord r, int userId) {
-        return
+    private static boolean notificationMatchesUserId(NotificationRecord r, int userId,
+            boolean isAutogroupSummary) {
+        if (isAutogroupSummary) {
+            return r.getUserId() == userId;
+        } else {
+            return
                 // looking for USER_ALL notifications? match everything
-                   userId == UserHandle.USER_ALL
-                // a notification sent to USER_ALL matches any query
-                || r.getUserId() == UserHandle.USER_ALL
-                // an exact user match
-                || r.getUserId() == userId;
+                userId == UserHandle.USER_ALL
+                        // a notification sent to USER_ALL matches any query
+                        || r.getUserId() == UserHandle.USER_ALL
+                        // an exact user match
+                        || r.getUserId() == userId;
+        }
     }
 
     /**
@@ -9527,31 +9558,24 @@
      * because it matches one of the users profiles.
      */
     private boolean notificationMatchesCurrentProfiles(NotificationRecord r, int userId) {
-        return notificationMatchesUserId(r, userId)
+        return notificationMatchesUserId(r, userId, false)
                 || mUserProfiles.isCurrentProfile(r.getUserId());
     }
 
     /**
      * Cancels all notifications from a given package that have all of the
-     * {@code mustHaveFlags}.
+     * {@code mustHaveFlags} and none of the {@code mustNotHaveFlags}.
      */
-    void cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, String channelId,
-            int mustHaveFlags, int mustNotHaveFlags, boolean doit, int userId, int reason,
-            ManagedServiceInfo listener) {
+    void cancelAllNotificationsInt(int callingUid, int callingPid, String pkg,
+            @Nullable String channelId, int mustHaveFlags, int mustNotHaveFlags, int userId,
+            int reason) {
         final long cancellationElapsedTimeMs = SystemClock.elapsedRealtime();
         mHandler.post(new Runnable() {
             @Override
             public void run() {
-                String listenerName = listener == null ? null : listener.component.toShortString();
                 EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
                         pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
-                        listenerName);
-
-                // Why does this parameter exist? Do we actually want to execute the above if doit
-                // is false?
-                if (!doit) {
-                    return;
-                }
+                        /* listener= */ null);
 
                 synchronized (mNotificationLock) {
                     FlagChecker flagChecker = (int flags) -> {
@@ -9563,14 +9587,15 @@
                         }
                         return true;
                     };
-                    cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
-                            pkg, true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
+                    cancelAllNotificationsByListLocked(mNotificationList, pkg,
+                            true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
                             false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
-                            listenerName, true /* wasPosted */, cancellationElapsedTimeMs);
-                    cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
-                            callingPid, pkg, true /*nullPkgIndicatesUserSwitch*/, channelId,
-                            flagChecker, false /*includeCurrentProfiles*/, userId,
-                            false /*sendDelete*/, reason, listenerName, false /* wasPosted */,
+                            null /* listenerName */, true /* wasPosted */,
+                            cancellationElapsedTimeMs);
+                    cancelAllNotificationsByListLocked(mEnqueuedNotifications, pkg,
+                            true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
+                            false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
+                            null /* listenerName */, false /* wasPosted */,
                             cancellationElapsedTimeMs);
                     mSnoozeHelper.cancel(userId, pkg);
                 }
@@ -9585,9 +9610,9 @@
 
     @GuardedBy("mNotificationLock")
     private void cancelAllNotificationsByListLocked(ArrayList<NotificationRecord> notificationList,
-            int callingUid, int callingPid, String pkg, boolean nullPkgIndicatesUserSwitch,
-            String channelId, FlagChecker flagChecker, boolean includeCurrentProfiles, int userId,
-            boolean sendDelete, int reason, String listenerName, boolean wasPosted,
+            @Nullable String pkg, boolean nullPkgIndicatesUserSwitch, @Nullable String channelId,
+            FlagChecker flagChecker, boolean includeCurrentProfiles, int userId, boolean sendDelete,
+            int reason, String listenerName, boolean wasPosted,
             @ElapsedRealtimeLong long cancellationElapsedTimeMs) {
         Set<String> childNotifications = null;
         for (int i = notificationList.size() - 1; i >= 0; --i) {
@@ -9596,7 +9621,7 @@
                 if (!notificationMatchesCurrentProfiles(r, userId)) {
                     continue;
                 }
-            } else if (!notificationMatchesUserId(r, userId)) {
+            } else if (!notificationMatchesUserId(r, userId, false)) {
                 continue;
             }
             // Don't remove notifications to all, if there's no package name specified
@@ -9704,12 +9729,12 @@
                         return true;
                     };
 
-                    cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
+                    cancelAllNotificationsByListLocked(mNotificationList,
                             null, false /*nullPkgIndicatesUserSwitch*/, null, flagChecker,
                             includeCurrentProfiles, userId, true /*sendDelete*/, reason,
                             listenerName, true, cancellationElapsedTimeMs);
-                    cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
-                            callingPid, null, false /*nullPkgIndicatesUserSwitch*/, null,
+                    cancelAllNotificationsByListLocked(mEnqueuedNotifications,
+                            null, false /*nullPkgIndicatesUserSwitch*/, null,
                             flagChecker, includeCurrentProfiles, userId, true /*sendDelete*/,
                             reason, listenerName, false, cancellationElapsedTimeMs);
                     mSnoozeHelper.cancel(userId, includeCurrentProfiles);
@@ -9834,7 +9859,7 @@
         final int len = list.size();
         for (int i = 0; i < len; i++) {
             NotificationRecord r = list.get(i);
-            if (notificationMatchesUserId(r, userId) && r.getGroupKey().equals(groupKey)
+            if (notificationMatchesUserId(r, userId, false) && r.getGroupKey().equals(groupKey)
                     && r.getSbn().getPackageName().equals(pkg)) {
                 records.add(r);
             }
@@ -9870,14 +9895,14 @@
         return null;
     }
 
-    @GuardedBy("mNotificationLock")
-    private NotificationRecord findNotificationByListLocked(ArrayList<NotificationRecord> list,
-            String pkg, String tag, int id, int userId) {
+    @Nullable
+    private static NotificationRecord findNotificationByListLocked(
+            ArrayList<NotificationRecord> list, String pkg, String tag, int id, int userId) {
         final int len = list.size();
         for (int i = 0; i < len; i++) {
             NotificationRecord r = list.get(i);
-            if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id &&
-                    TextUtils.equals(r.getSbn().getTag(), tag)
+            if (notificationMatchesUserId(r, userId, (r.getFlags() & GroupHelper.BASE_FLAGS) != 0)
+                    && r.getSbn().getId() == id && TextUtils.equals(r.getSbn().getTag(), tag)
                     && r.getSbn().getPackageName().equals(pkg)) {
                 return r;
             }
@@ -9885,15 +9910,14 @@
         return null;
     }
 
-    @GuardedBy("mNotificationLock")
-    private List<NotificationRecord> findNotificationsByListLocked(
+    private static List<NotificationRecord> findNotificationsByListLocked(
             ArrayList<NotificationRecord> list, String pkg, String tag, int id, int userId) {
         List<NotificationRecord> matching = new ArrayList<>();
         final int len = list.size();
         for (int i = 0; i < len; i++) {
             NotificationRecord r = list.get(i);
-            if (notificationMatchesUserId(r, userId) && r.getSbn().getId() == id &&
-                    TextUtils.equals(r.getSbn().getTag(), tag)
+            if (notificationMatchesUserId(r, userId, false) && r.getSbn().getId() == id
+                    && TextUtils.equals(r.getSbn().getTag(), tag)
                     && r.getSbn().getPackageName().equals(pkg)) {
                 matching.add(r);
             }
@@ -9901,9 +9925,9 @@
         return matching;
     }
 
-    @GuardedBy("mNotificationLock")
-    private NotificationRecord findNotificationByListLocked(ArrayList<NotificationRecord> list,
-            String key) {
+    @Nullable
+    private static NotificationRecord findNotificationByListLocked(
+            ArrayList<NotificationRecord> list, String key) {
         final int N = list.size();
         for (int i = 0; i < N; i++) {
             if (key.equals(list.get(i).getKey())) {
@@ -9913,29 +9937,6 @@
         return null;
     }
 
-    /**
-     * There may be multiple records that match your criteria. For instance if there have been
-     * multiple notifications posted which are enqueued for the same pkg, tag, id, userId. This
-     * method will find all of them in the given list
-     * @return
-     */
-    @GuardedBy("mNotificationLock")
-    private List<NotificationRecord> findEnqueuedNotificationsForCriteria(
-            String pkg, String tag, int id, int userId) {
-        final ArrayList<NotificationRecord> records = new ArrayList<>();
-        final int n = mEnqueuedNotifications.size();
-        for (int i = 0; i < n; i++) {
-            NotificationRecord r = mEnqueuedNotifications.get(i);
-            if (notificationMatchesUserId(r, userId)
-                    && r.getSbn().getId() == id
-                    && TextUtils.equals(r.getSbn().getTag(), tag)
-                    && r.getSbn().getPackageName().equals(pkg)) {
-                records.add(r);
-            }
-        }
-        return records;
-    }
-
     @GuardedBy("mNotificationLock")
     int indexOfNotificationLocked(String key) {
         final int N = mNotificationList.size();
@@ -10526,6 +10527,11 @@
         }
 
         @Override
+        protected boolean allowRebindForParentUser() {
+            return false;
+        }
+
+        @Override
         protected String getRequiredPermission() {
             // only signature/privileged apps can be bound.
             return android.Manifest.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE;
@@ -11070,6 +11076,11 @@
         }
 
         @Override
+        protected boolean allowRebindForParentUser() {
+            return true;
+        }
+
+        @Override
         public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) {
             super.onPackagesChanged(removingPackage, pkgList, uidList);
 
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index c9a6c63..100c638 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -25,8 +25,7 @@
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
 
 import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.IActivityManager;
+import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.Person;
@@ -46,6 +45,8 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.provider.Settings;
@@ -96,12 +97,12 @@
     // the period after which a notification is updated where it can make sound
     private static final int MAX_SOUND_DELAY_MS = 2000;
     private final StatusBarNotification sbn;
-    IActivityManager mAm;
-    UriGrantsManagerInternal mUgmInternal;
+    private final UriGrantsManagerInternal mUgmInternal;
     final int mTargetSdkVersion;
     final int mOriginalFlags;
     private final Context mContext;
-
+    private KeyguardManager mKeyguardManager;
+    private final PowerManager mPowerManager;
     NotificationUsageStats.SingleNotificationStats stats;
     boolean isCanceled;
     IBinder permissionOwner;
@@ -220,7 +221,6 @@
         this.sbn = sbn;
         mTargetSdkVersion = LocalServices.getService(PackageManagerInternal.class)
                 .getPackageTargetSdkVersion(sbn.getPackageName());
-        mAm = ActivityManager.getService();
         mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
         mOriginalFlags = sbn.getNotification().flags;
         mRankingTimeMs = calculateRankingTimeMs(0L);
@@ -228,6 +228,8 @@
         mUpdateTimeMs = mCreationTimeMs;
         mInterruptionTimeMs = mCreationTimeMs;
         mContext = context;
+        mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
+        mPowerManager = mContext.getSystemService(PowerManager.class);
         stats = new NotificationUsageStats.SingleNotificationStats();
         mChannel = channel;
         mPreChannelsNotification = isPreChannelsNotification();
@@ -1382,18 +1384,27 @@
      * Collect all {@link Uri} that should have permission granted to whoever
      * will be rendering it.
      */
-    protected void calculateGrantableUris() {
-        final Notification notification = getNotification();
-        notification.visitUris((uri) -> {
-            visitGrantableUri(uri, false, false);
-        });
+    private void calculateGrantableUris() {
+        Trace.beginSection("NotificationRecord.calculateGrantableUris");
+        try {
+            // We can't grant URI permissions from system.
+            final int sourceUid = getSbn().getUid();
+            if (sourceUid == android.os.Process.SYSTEM_UID) return;
 
-        if (notification.getChannelId() != null) {
-            NotificationChannel channel = getChannel();
-            if (channel != null) {
-                visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
-                        & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
+            final Notification notification = getNotification();
+            notification.visitUris((uri) -> {
+                visitGrantableUri(uri, false, false);
+            });
+
+            if (notification.getChannelId() != null) {
+                NotificationChannel channel = getChannel();
+                if (channel != null) {
+                    visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
+                            & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
+                }
             }
+        } finally {
+            Trace.endSection();
         }
     }
 
@@ -1408,13 +1419,14 @@
     private void visitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound) {
         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
 
-        // We can't grant Uri permissions from system
-        final int sourceUid = getSbn().getUid();
-        if (sourceUid == android.os.Process.SYSTEM_UID) return;
+        if (mGrantableUris != null && mGrantableUris.contains(uri)) {
+            return; // already verified this URI
+        }
 
+        final int sourceUid = getSbn().getUid();
         final long ident = Binder.clearCallingIdentity();
         try {
-            // This will throw SecurityException if caller can't grant
+            // This will throw a SecurityException if the caller can't grant.
             mUgmInternal.checkGrantUriPermission(sourceUid, null,
                     ContentProvider.getUriWithoutUserId(uri),
                     Intent.FLAG_GRANT_READ_URI_PERMISSION,
@@ -1619,6 +1631,22 @@
         return mPhoneNumbers;
     }
 
+    boolean isLocked() {
+        return getKeyguardManager().isKeyguardLocked()
+                || !mPowerManager.isInteractive();  // Unlocked AOD
+    }
+
+    /**
+     * For some early {@link NotificationRecord}, {@link KeyguardManager} can be {@code null} in
+     * the constructor. Retrieve it again if it is null.
+     */
+    private KeyguardManager getKeyguardManager() {
+        if (mKeyguardManager == null) {
+            mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
+        }
+        return mKeyguardManager;
+    }
+
     @VisibleForTesting
     static final class Light {
         public final int color;
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index f26d56e..b015a72 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -32,9 +32,12 @@
 import android.service.notification.NotificationStats;
 import android.util.Log;
 
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.FrameworkStatsLog;
 
 import java.util.ArrayList;
 import java.util.Objects;
@@ -492,6 +495,8 @@
         final boolean is_foreground_service;
         final long timeout_millis;
         final boolean is_non_dismissible;
+        final int fsi_state;
+        final boolean is_locked;
         @DurationMillisLong long post_duration_millis; // Not final; calculated at the end.
 
         NotificationReported(NotificationRecordPair p,
@@ -522,6 +527,20 @@
             this.is_foreground_service = NotificationRecordLogger.isForegroundService(p.r);
             this.timeout_millis = p.r.getSbn().getNotification().getTimeoutAfter();
             this.is_non_dismissible = NotificationRecordLogger.isNonDismissible(p.r);
+
+            final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver()
+                    .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
+
+            final boolean hasFullScreenIntent =
+                    p.r.getSbn().getNotification().fullScreenIntent != null;
+
+            final boolean hasFsiRequestedButDeniedFlag =  (p.r.getSbn().getNotification().flags
+                    & Notification.FLAG_FSI_REQUESTED_BUT_DENIED) != 0;
+
+            this.fsi_state = NotificationRecordLogger.getFsiState(isStickyHunFlagEnabled,
+                    hasFullScreenIntent, hasFsiRequestedButDeniedFlag, eventType);
+
+            this.is_locked = p.r.isLocked();
         }
     }
 
@@ -550,7 +569,6 @@
     }
 
     /**
-     * @param r NotificationRecord
      * @return Whether the notification is a non-dismissible notification.
      */
     static boolean isNonDismissible(@NonNull NotificationRecord r) {
@@ -559,4 +577,28 @@
         }
         return (r.getNotification().flags & Notification.FLAG_NO_DISMISS) != 0;
     }
+
+    /**
+     * @return FrameworkStatsLog enum of the state of the full screen intent posted with this
+     * notification.
+     */
+    static int getFsiState(boolean isStickyHunFlagEnabled,
+                           boolean hasFullScreenIntent,
+                           boolean hasFsiRequestedButDeniedFlag,
+                           NotificationReportedEvent eventType) {
+
+        if (!isStickyHunFlagEnabled
+                || eventType == NotificationReportedEvent.NOTIFICATION_UPDATED) {
+            // Zeroes in protos take zero bandwidth, but non-zero numbers take bandwidth,
+            // so we should log 0 when possible.
+            return 0;
+        }
+        if (hasFullScreenIntent) {
+            return FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__FSI_ALLOWED;
+        }
+        if (hasFsiRequestedButDeniedFlag) {
+            return FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__FSI_DENIED;
+        }
+        return FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__NO_FSI;
+    }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index feb75ef..9da0e98 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -75,7 +75,9 @@
                 notificationReported.is_foreground_service,
                 notificationReported.timeout_millis,
                 notificationReported.is_non_dismissible,
-                notificationReported.post_duration_millis);
+                notificationReported.post_duration_millis,
+                notificationReported.fsi_state,
+                notificationReported.is_locked);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index ffe33a8..e960f4b 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -16,23 +16,16 @@
 
 package com.android.server.notification;
 
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-
 import android.app.Notification;
-import android.content.ContentValues;
 import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteFullException;
-import android.database.sqlite.SQLiteOpenHelper;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.server.notification.NotificationManagerService.DumpFilter;
 
@@ -42,8 +35,6 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -63,7 +54,6 @@
     private static final String TAG = "NotificationUsageStats";
 
     private static final boolean ENABLE_AGGREGATED_IN_MEMORY_STATS = true;
-    private static final boolean ENABLE_SQLITE_LOG = true;
     private static final AggregatedStats[] EMPTY_AGGREGATED_STATS = new AggregatedStats[0];
     private static final String DEVICE_GLOBAL_STATS = "__global"; // packages start with letters
     private static final int MSG_EMIT = 1;
@@ -73,12 +63,15 @@
     public static final int FOUR_HOURS = 1000 * 60 * 60 * 4;
     private static final long EMIT_PERIOD = DEBUG ? TEN_SECONDS : FOUR_HOURS;
 
-    // Guarded by synchronized(this).
+    @GuardedBy("this")
     private final Map<String, AggregatedStats> mStats = new HashMap<>();
+    @GuardedBy("this")
     private final ArrayDeque<AggregatedStats[]> mStatsArrays = new ArrayDeque<>();
+    @GuardedBy("this")
     private ArraySet<String> mStatExpiredkeys = new ArraySet<>();
     private final Context mContext;
     private final Handler mHandler;
+    @GuardedBy("this")
     private long mLastEmitTime;
 
     public NotificationUsageStats(Context context) {
@@ -105,11 +98,7 @@
      */
     public synchronized float getAppEnqueueRate(String packageName) {
         AggregatedStats stats = getOrCreateAggregatedStatsLocked(packageName);
-        if (stats != null) {
-            return stats.getEnqueueRate(SystemClock.elapsedRealtime());
-        } else {
-            return 0f;
-        }
+        return stats.getEnqueueRate(SystemClock.elapsedRealtime());
     }
 
     /**
@@ -117,11 +106,7 @@
      */
     public synchronized boolean isAlertRateLimited(String packageName) {
         AggregatedStats stats = getOrCreateAggregatedStatsLocked(packageName);
-        if (stats != null) {
-            return stats.isAlertRateLimited();
-        } else {
-            return false;
-        }
+        return stats.isAlertRateLimited();
     }
 
     /**
@@ -136,16 +121,32 @@
     }
 
     /**
+     * Called when a notification that was enqueued by an app is effectively enqueued to be
+     * posted. This is after rate checking, to update the rate.
+     *
+     * <p>Note that if we updated the arrival estimate <em>before</em> checking it, then an app
+     * enqueueing at slightly above the acceptable rate would never get their notifications
+     * accepted; updating afterwards allows the rate to dip below the threshold and thus lets
+     * through some of them.
+     */
+    public synchronized void registerEnqueuedByAppAndAccepted(String packageName) {
+        final long now = SystemClock.elapsedRealtime();
+        AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(packageName);
+        for (AggregatedStats stats : aggregatedStatsArray) {
+            stats.updateInterarrivalEstimate(now);
+        }
+        releaseAggregatedStatsLocked(aggregatedStatsArray);
+    }
+
+    /**
      * Called when a notification has been posted.
      */
     public synchronized void registerPostedByApp(NotificationRecord notification) {
-        final long now = SystemClock.elapsedRealtime();
-        notification.stats.posttimeElapsedMs = now;
+        notification.stats.posttimeElapsedMs = SystemClock.elapsedRealtime();
 
         AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
         for (AggregatedStats stats : aggregatedStatsArray) {
             stats.numPostedByApp++;
-            stats.updateInterarrivalEstimate(now);
             stats.countApiUse(notification);
             stats.numUndecoratedRemoteViews += (notification.hasUndecoratedRemoteView() ? 1 : 0);
         }
@@ -161,7 +162,6 @@
         AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
         for (AggregatedStats stats : aggregatedStatsArray) {
             stats.numUpdatedByApp++;
-            stats.updateInterarrivalEstimate(SystemClock.elapsedRealtime());
             stats.countApiUse(notification);
         }
         releaseAggregatedStatsLocked(aggregatedStatsArray);
@@ -257,12 +257,12 @@
         }
     }
 
-    // Locked by this.
+    @GuardedBy("this")
     private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) {
         return getAggregatedStatsLocked(record.getSbn().getPackageName());
     }
 
-    // Locked by this.
+    @GuardedBy("this")
     private AggregatedStats[] getAggregatedStatsLocked(String packageName) {
         if (!ENABLE_AGGREGATED_IN_MEMORY_STATS) {
             return EMPTY_AGGREGATED_STATS;
@@ -277,7 +277,7 @@
         return array;
     }
 
-    // Locked by this.
+    @GuardedBy("this")
     private void releaseAggregatedStatsLocked(AggregatedStats[] array) {
         for(int i = 0; i < array.length; i++) {
             array[i] = null;
@@ -285,7 +285,7 @@
         mStatsArrays.offer(array);
     }
 
-    // Locked by this.
+    @GuardedBy("this")
     private AggregatedStats getOrCreateAggregatedStatsLocked(String key) {
         AggregatedStats result = mStats.get(key);
         if (result == null) {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 4399a3c..3f799dc 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -30,6 +30,9 @@
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -41,6 +44,7 @@
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
+import android.content.AttributionSource;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -53,6 +57,7 @@
 import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
+import android.permission.PermissionManager;
 import android.provider.Settings;
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.NotificationListenerService;
@@ -72,6 +77,8 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
@@ -157,6 +164,9 @@
     static final int DEFAULT_BUBBLE_PREFERENCE = BUBBLE_PREFERENCE_NONE;
     static final boolean DEFAULT_MEDIA_NOTIFICATION_FILTERING = true;
 
+    private static final int NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_APP = 0;
+    private static final int NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_USER = 1;
+
     /**
      * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
      * fields.
@@ -183,6 +193,7 @@
     private final RankingHandler mRankingHandler;
     private final ZenModeHelper mZenModeHelper;
     private final PermissionHelper mPermissionHelper;
+    private final PermissionManager mPermissionManager;
     private final NotificationChannelLogger mNotificationChannelLogger;
     private final AppOpsManager mAppOps;
 
@@ -191,14 +202,14 @@
     private SparseBooleanArray mLockScreenShowNotifications;
     private SparseBooleanArray mLockScreenPrivateNotifications;
     private boolean mIsMediaNotificationFilteringEnabled = DEFAULT_MEDIA_NOTIFICATION_FILTERING;
-    private boolean mAreChannelsBypassingDnd;
+    private boolean mCurrentUserHasChannelsBypassingDnd;
     private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
     private boolean mShowReviewPermissionsNotification;
 
     private boolean mAllowInvalidShortcuts = false;
 
     public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
-            ZenModeHelper zenHelper, PermissionHelper permHelper,
+            ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
             NotificationChannelLogger notificationChannelLogger,
             AppOpsManager appOpsManager,
             SysUiStatsEvent.BuilderFactory statsEventBuilderFactory,
@@ -207,6 +218,7 @@
         mZenModeHelper = zenHelper;
         mRankingHandler = rankingHandler;
         mPermissionHelper = permHelper;
+        mPermissionManager = permManager;
         mPm = pm;
         mNotificationChannelLogger = notificationChannelLogger;
         mAppOps = appOpsManager;
@@ -218,7 +230,6 @@
         updateBadgingEnabled();
         updateBubblesEnabled();
         updateMediaNotificationFilteringEnabled();
-        syncChannelsBypassingDnd(Process.SYSTEM_UID, true);  // init comes from system
     }
 
     public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId)
@@ -881,7 +892,7 @@
             r.groups.put(group.getId(), group);
         }
         if (needsDndChange) {
-            updateChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+            updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
     }
 
@@ -960,7 +971,7 @@
                         existing.setBypassDnd(bypassDnd);
                         needsPolicyFileChange = true;
 
-                        if (bypassDnd != mAreChannelsBypassingDnd
+                        if (bypassDnd != mCurrentUserHasChannelsBypassingDnd
                                 || previousExistingImportance != existing.getImportance()) {
                             needsDndChange = true;
                         }
@@ -1019,7 +1030,7 @@
                 }
 
                 r.channels.put(channel.getId(), channel);
-                if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
+                if (channel.canBypassDnd() != mCurrentUserHasChannelsBypassingDnd) {
                     needsDndChange = true;
                 }
                 MetricsLogger.action(getChannelLog(channel, pkg).setType(
@@ -1029,7 +1040,7 @@
         }
 
         if (needsDndChange) {
-            updateChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+            updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
 
         return needsPolicyFileChange;
@@ -1101,26 +1112,129 @@
             if (!channel.equals(updatedChannel)) {
                 // only log if there are real changes
                 MetricsLogger.action(getChannelLog(updatedChannel, pkg)
-                        .setSubtype(fromUser ? 1 : 0));
+                        .setSubtype(fromUser ? NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_USER
+                                : NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_APP));
                 mNotificationChannelLogger.logNotificationChannelModified(updatedChannel, uid, pkg,
                         NotificationChannelLogger.getLoggingImportance(channel), fromUser);
                 changed = true;
             }
 
-            if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
+            if (fromUser && SystemUiSystemPropertiesFlags.getResolver().isEnabled(
+                    NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS)) {
+                updateChildrenConversationChannels(r, channel, updatedChannel);
+                // No need to update changed or needsDndChanged as the child channel(s) cannot be
+                // relevantly affected without the parent channel already having been.
+            }
+
+            if (updatedChannel.canBypassDnd() != mCurrentUserHasChannelsBypassingDnd
                     || channel.getImportance() != updatedChannel.getImportance()) {
                 needsDndChange = true;
                 changed = true;
             }
         }
         if (needsDndChange) {
-            updateChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+            updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
         if (changed) {
             updateConfig();
         }
     }
 
+    /**
+     * Updates conversation channels after user changes to their parent channel. See
+     * {@link #maybeUpdateChildConversationChannel}.
+     */
+    @GuardedBy("mPackagePreferences")
+    private void updateChildrenConversationChannels(@NonNull PackagePreferences packagePreferences,
+            @NonNull NotificationChannel oldParent, @NonNull NotificationChannel updatedParent) {
+        if (oldParent.equals(updatedParent)) {
+            return;
+        }
+        if (oldParent.isConversation()) {
+            return; // Can't have children.
+        }
+        for (NotificationChannel channel : packagePreferences.channels.values()) {
+            // Include deleted -- otherwise they will have old settings if later resurrected.
+            // Include demoted -- still attached to their parents.
+            if (channel.isConversation()
+                    && oldParent.getId().equals(channel.getParentChannelId())) {
+                maybeUpdateChildConversationChannel(packagePreferences.pkg, packagePreferences.uid,
+                        channel, oldParent, updatedParent);
+            }
+        }
+    }
+
+    /**
+     * Apply the diff between {@code oldParent} and {@code updatedParent} to the child
+     * {@code conversation} channel. Only fields that are not locked on the conversation channel
+     * (see {@link NotificationChannel#LOCKABLE_FIELDS }) will be updated (so that we don't override
+     * previous explicit user choices).
+     *
+     * <p>This will also log the change as if it was {@code fromUser=true}.
+     */
+    @GuardedBy("mPackagePreferences")
+    private void maybeUpdateChildConversationChannel(String pkg, int uid,
+            @NonNull NotificationChannel conversation, @NonNull NotificationChannel oldParent,
+            @NonNull NotificationChannel updatedParent) {
+        boolean changed = false;
+        int oldLoggingImportance = NotificationChannelLogger.getLoggingImportance(conversation);
+
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_PRIORITY) == 0
+                && oldParent.canBypassDnd() != updatedParent.canBypassDnd()) {
+            conversation.setBypassDnd(updatedParent.canBypassDnd());
+            changed = true;
+        }
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_VISIBILITY) == 0
+                && oldParent.getLockscreenVisibility()
+                != updatedParent.getLockscreenVisibility()) {
+            conversation.setLockscreenVisibility(updatedParent.getLockscreenVisibility());
+            changed = true;
+        }
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
+                && oldParent.getImportance() != updatedParent.getImportance()) {
+            conversation.setImportance(updatedParent.getImportance());
+            changed = true;
+        }
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_LIGHTS) == 0
+                && (oldParent.shouldShowLights() != updatedParent.shouldShowLights()
+                || oldParent.getLightColor() != updatedParent.getLightColor())) {
+            conversation.enableLights(updatedParent.shouldShowLights());
+            conversation.setLightColor(updatedParent.getLightColor());
+            changed = true;
+        }
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_SOUND) == 0
+                && !Objects.equals(oldParent.getSound(), updatedParent.getSound())) {
+            conversation.setSound(updatedParent.getSound(), updatedParent.getAudioAttributes());
+            changed = true;
+        }
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0
+                && (!Arrays.equals(oldParent.getVibrationPattern(),
+                updatedParent.getVibrationPattern())
+                || oldParent.shouldVibrate() != updatedParent.shouldVibrate())) {
+            // enableVibration must be 2nd because setVibrationPattern may toggle it.
+            conversation.setVibrationPattern(updatedParent.getVibrationPattern());
+            conversation.enableVibration(updatedParent.shouldVibrate());
+            changed = true;
+        }
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_SHOW_BADGE) == 0
+                && oldParent.canShowBadge() != updatedParent.canShowBadge()) {
+            conversation.setShowBadge(updatedParent.canShowBadge());
+            changed = true;
+        }
+        if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_ALLOW_BUBBLE) == 0
+                && oldParent.getAllowBubbles() != updatedParent.getAllowBubbles()) {
+            conversation.setAllowBubbles(updatedParent.getAllowBubbles());
+            changed = true;
+        }
+
+        if (changed) {
+            MetricsLogger.action(getChannelLog(conversation, pkg).setSubtype(
+                    NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_USER));
+            mNotificationChannelLogger.logNotificationChannelModified(conversation, uid, pkg,
+                    oldLoggingImportance, /* fromUser= */ true);
+        }
+    }
+
     @Override
     public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
             boolean includeDeleted) {
@@ -1206,7 +1320,7 @@
             }
         }
         if (channelBypassedDnd) {
-            updateChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+            updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
         return deletedChannel;
     }
@@ -1423,7 +1537,7 @@
             }
         }
         if (groupBypassedDnd) {
-            updateChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+            updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
         return deletedChannels;
     }
@@ -1570,8 +1684,8 @@
                 }
             }
         }
-        if (!deletedChannelIds.isEmpty() && mAreChannelsBypassingDnd) {
-            updateChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+        if (!deletedChannelIds.isEmpty() && mCurrentUserHasChannelsBypassingDnd) {
+            updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
         return deletedChannelIds;
     }
@@ -1673,21 +1787,28 @@
     }
 
     /**
-     * Syncs {@link #mAreChannelsBypassingDnd} with the current user's notification policy before
-     * updating
+     * Syncs {@link #mCurrentUserHasChannelsBypassingDnd} with the current user's notification
+     * policy before updating. Must be called:
+     * <ul>
+     *     <li>On system init, after channels and DND configurations are loaded.</li>
+     *     <li>When the current user changes, after the corresponding DND config is loaded.</li>
+     * </ul>
      */
-    private void syncChannelsBypassingDnd(int callingUid, boolean fromSystemOrSystemUi) {
-        mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
-                & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
+    void syncChannelsBypassingDnd() {
+        mCurrentUserHasChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
+                & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
 
-        updateChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+        updateCurrentUserHasChannelsBypassingDnd(/* callingUid= */ Process.SYSTEM_UID,
+                /* fromSystemOrSystemUi= */ true);
     }
 
     /**
-     * Updates the user's NotificationPolicy based on whether the current userId
-     * has channels bypassing DND
+     * Updates the user's NotificationPolicy based on whether the current userId has channels
+     * bypassing DND. It should be called whenever a channel is created, updated, or deleted, or
+     * when the current user is switched.
      */
-    private void updateChannelsBypassingDnd(int callingUid, boolean fromSystemOrSystemUi) {
+    private void updateCurrentUserHasChannelsBypassingDnd(int callingUid,
+            boolean fromSystemOrSystemUi) {
         ArraySet<Pair<String, Integer>> candidatePkgs = new ArraySet<>();
 
         final int currentUserId = getCurrentUser();
@@ -1702,7 +1823,7 @@
 
                 for (NotificationChannel channel : r.channels.values()) {
                     if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) {
-                        candidatePkgs.add(new Pair(r.pkg, r.uid));
+                        candidatePkgs.add(new Pair<>(r.pkg, r.uid));
                         break;
                     }
                 }
@@ -1715,9 +1836,9 @@
             }
         }
         boolean haveBypassingApps = candidatePkgs.size() > 0;
-        if (mAreChannelsBypassingDnd != haveBypassingApps) {
-            mAreChannelsBypassingDnd = haveBypassingApps;
-            updateZenPolicy(mAreChannelsBypassingDnd, callingUid, fromSystemOrSystemUi);
+        if (mCurrentUserHasChannelsBypassingDnd != haveBypassingApps) {
+            mCurrentUserHasChannelsBypassingDnd = haveBypassingApps;
+            updateZenPolicy(mCurrentUserHasChannelsBypassingDnd, callingUid, fromSystemOrSystemUi);
         }
     }
 
@@ -1754,7 +1875,7 @@
     }
 
     public boolean areChannelsBypassingDnd() {
-        return mAreChannelsBypassingDnd;
+        return mCurrentUserHasChannelsBypassingDnd;
     }
 
     /**
@@ -1829,8 +1950,8 @@
         }
     }
 
-    @VisibleForTesting
-    void lockFieldsForUpdateLocked(NotificationChannel original, NotificationChannel update) {
+    private void lockFieldsForUpdateLocked(NotificationChannel original,
+            NotificationChannel update) {
         if (original.canBypassDnd() != update.canBypassDnd()) {
             update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
         }
@@ -2027,6 +2148,43 @@
     }
 
     /**
+     * @return State of the full screen intent permission for this package.
+     */
+    @VisibleForTesting
+    int getFsiState(String pkg, int uid, boolean requestedFSIPermission, boolean isFlagEnabled) {
+        if (!isFlagEnabled) {
+            return 0;
+        }
+        if (!requestedFSIPermission) {
+            return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+        }
+        final AttributionSource attributionSource =
+                new AttributionSource.Builder(uid).setPackageName(pkg).build();
+
+        final int result = mPermissionManager.checkPermissionForPreflight(
+                android.Manifest.permission.USE_FULL_SCREEN_INTENT, attributionSource);
+
+        if (result == PermissionManager.PERMISSION_GRANTED) {
+            return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
+        }
+        return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
+    }
+
+    /**
+     * @return True if the current full screen intent permission state for this package was set by
+     * the user.
+     */
+    @VisibleForTesting
+    boolean isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags,
+                                   boolean isStickyHunFlagEnabled) {
+        if (!isStickyHunFlagEnabled
+                || fsiState == PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED) {
+            return false;
+        }
+        return (currentPermissionFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
+    }
+
+    /**
      * Fills out {@link PackageNotificationPreferences} proto and wraps it in a {@link StatsEvent}.
      */
     public void pullPackagePreferencesStats(List<StatsEvent> events,
@@ -2070,7 +2228,33 @@
 
                 event.writeInt(r.visibility);
                 event.writeInt(r.lockedAppFields);
-                event.writeBoolean(importanceIsUserSet);  // optional bool user_set_importance = 5;
+
+                // optional bool user_set_importance = 5;
+                event.writeBoolean(importanceIsUserSet);
+
+                // optional FsiState fsi_state = 6;
+                final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver()
+                        .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
+
+                final boolean requestedFSIPermission = mPermissionHelper.hasRequestedPermission(
+                        android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg, r.uid);
+
+                final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission,
+                        isStickyHunFlagEnabled);
+
+                event.writeInt(fsiState);
+
+                // optional bool is_fsi_permission_user_set = 7;
+                final int currentPermissionFlags = mPm.getPermissionFlags(
+                        android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg,
+                        UserHandle.getUserHandleForUid(r.uid));
+
+                final boolean isUserSet =
+                        isFsiPermissionUserSet(r.pkg, r.uid, fsiState, currentPermissionFlags,
+                                isStickyHunFlagEnabled);
+
+                event.writeBoolean(isUserSet);
+
                 events.add(event.build());
             }
         }
@@ -2424,7 +2608,12 @@
                             for (NotificationChannel channel : r.channels.values()) {
                                 if (!channel.isSoundRestored()) {
                                     Uri uri = channel.getSound();
-                                    Uri restoredUri = channel.restoreSoundUri(mContext, uri, true);
+                                    Uri restoredUri =
+                                            channel.restoreSoundUri(
+                                                    mContext,
+                                                    uri,
+                                                    true,
+                                                    channel.getAudioAttributes().getUsage());
                                     if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(
                                             restoredUri)) {
                                         Log.w(TAG,
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index b4347e1..773d10b 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -103,8 +103,11 @@
             notificationList.get(i).setGlobalSortKey(null);
         }
 
-        // rank each record individually
-        Collections.sort(notificationList, mPreliminaryComparator);
+        // Rank each record individually.
+        // Lock comparator state for consistent compare() results.
+        synchronized (mPreliminaryComparator.mStateLock) {
+            notificationList.sort(mPreliminaryComparator);
+        }
 
         synchronized (mProxyByGroupTmp) {
             // record individual ranking result and nominate proxies for each group
diff --git a/services/core/java/com/android/server/notification/RateEstimator.java b/services/core/java/com/android/server/notification/RateEstimator.java
index a2f93dc..eda96ac 100644
--- a/services/core/java/com/android/server/notification/RateEstimator.java
+++ b/services/core/java/com/android/server/notification/RateEstimator.java
@@ -22,9 +22,10 @@
  *
  * {@hide}
  */
-public class RateEstimator {
-    private static final double RATE_ALPHA = 0.8;
+class RateEstimator {
+    private static final double RATE_ALPHA = 0.7;
     private static final double MINIMUM_DT = 0.0005;
+
     private Long mLastEventTime;
     private double mInterarrivalTime;
 
@@ -34,18 +35,12 @@
     }
 
     /** Update the estimate to account for an event that just happened. */
-    public float update(long now) {
-        float rate;
-        if (mLastEventTime == null) {
-            // No last event time, rate is zero.
-            rate = 0f;
-        } else {
+    public void update(long now) {
+        if (mLastEventTime != null) {
             // Calculate the new inter-arrival time based on last event time.
             mInterarrivalTime = getInterarrivalEstimate(now);
-            rate = (float) (1.0 / mInterarrivalTime);
         }
         mLastEventTime = now;
-        return rate;
     }
 
     /** @return the estimated rate if there were a new event right now. */
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index 5b7b0c1..f56a67c 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -55,7 +55,7 @@
 
     public ZenModeFiltering(Context context) {
         mContext = context;
-        mMessagingUtil = new NotificationMessagingUtil(mContext);
+        mMessagingUtil = new NotificationMessagingUtil(mContext, null);
     }
 
     public ZenModeFiltering(Context context, NotificationMessagingUtil messagingUtil) {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 36a0b0c..1f5bd3e 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -75,6 +75,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
 import com.android.internal.logging.MetricsLogger;
@@ -108,30 +109,34 @@
     static final int RULE_LIMIT_PER_PACKAGE = 100;
 
     // pkg|userId => uid
-    protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>();
+    @VisibleForTesting protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>();
 
     private final Context mContext;
     private final H mHandler;
     private final SettingsObserver mSettingsObserver;
     private final AppOpsManager mAppOps;
-    @VisibleForTesting protected final NotificationManager mNotificationManager;
+    private final NotificationManager mNotificationManager;
     private final SysUiStatsEvent.BuilderFactory mStatsEventBuilderFactory;
-    @VisibleForTesting protected ZenModeConfig mDefaultConfig;
+    private ZenModeConfig mDefaultConfig;
     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
     private final ZenModeFiltering mFiltering;
-    protected final RingerModeDelegate mRingerModeDelegate = new
+    private final RingerModeDelegate mRingerModeDelegate = new
             RingerModeDelegate();
     @VisibleForTesting protected final ZenModeConditions mConditions;
-    Object mConfigsLock = new Object();
+    private final Object mConfigsArrayLock = new Object();
+    @GuardedBy("mConfigsArrayLock")
     @VisibleForTesting final SparseArray<ZenModeConfig> mConfigs = new SparseArray<>();
     private final Metrics mMetrics = new Metrics();
     private final ConditionProviders.Config mServiceConfig;
-    private SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver;
-    @VisibleForTesting protected ZenModeEventLogger mZenModeEventLogger;
+    private final SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver;
+    private final ZenModeEventLogger mZenModeEventLogger;
 
     @VisibleForTesting protected int mZenMode;
     @VisibleForTesting protected NotificationManager.Policy mConsolidatedPolicy;
     private int mUser = UserHandle.USER_SYSTEM;
+
+    private final Object mConfigLock = new Object();
+    @GuardedBy("mConfigLock")
     @VisibleForTesting protected ZenModeConfig mConfig;
     @VisibleForTesting protected AudioManagerInternal mAudioManager;
     protected PackageManager mPm;
@@ -159,7 +164,7 @@
         mDefaultConfig = readDefaultConfig(mContext.getResources());
         updateDefaultAutomaticRuleNames();
         mConfig = mDefaultConfig.copy();
-        synchronized (mConfigsLock) {
+        synchronized (mConfigsArrayLock) {
             mConfigs.put(UserHandle.USER_SYSTEM, mConfig);
         }
         mConsolidatedPolicy = mConfig.toNotificationPolicy();
@@ -186,7 +191,7 @@
     public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
             ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity,
             int callingUid) {
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConsolidatedPolicy,
                     userHandle, extras, validator, contactsTimeoutMs, timeoutAffinity,
                     callingUid);
@@ -206,7 +211,7 @@
     }
 
     public boolean shouldIntercept(NotificationRecord record) {
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             return mFiltering.shouldIntercept(mZenMode, mConsolidatedPolicy, record);
         }
     }
@@ -221,7 +226,7 @@
 
     public void initZenMode() {
         if (DEBUG) Log.d(TAG, "initZenMode");
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             // "update" config to itself, which will have no effect in the case where a config
             // was read in via XML, but will initialize zen mode if nothing was read in and the
             // config remains the default.
@@ -250,7 +255,7 @@
     public void onUserRemoved(int user) {
         if (user < UserHandle.USER_SYSTEM) return;
         if (DEBUG) Log.d(TAG, "onUserRemoved u=" + user);
-        synchronized (mConfigsLock) {
+        synchronized (mConfigsArrayLock) {
             mConfigs.remove(user);
         }
     }
@@ -268,7 +273,7 @@
         mUser = user;
         if (DEBUG) Log.d(TAG, reason + " u=" + user);
         ZenModeConfig config = null;
-        synchronized (mConfigsLock) {
+        synchronized (mConfigsArrayLock) {
             if (mConfigs.get(user) != null) {
                 config = mConfigs.get(user).copy();
             }
@@ -278,7 +283,7 @@
             config = mDefaultConfig.copy();
             config.user = user;
         }
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             setConfigLocked(config, null, reason, Process.SYSTEM_UID, true);
         }
         cleanUpZenRules();
@@ -314,7 +319,7 @@
 
     public List<ZenRule> getZenRules() {
         List<ZenRule> rules = new ArrayList<>();
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return rules;
             for (ZenRule rule : mConfig.automaticRules.values()) {
                 if (canManageAutomaticZenRule(rule)) {
@@ -327,7 +332,7 @@
 
     public AutomaticZenRule getAutomaticZenRule(String id) {
         ZenRule rule;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return null;
              rule = mConfig.automaticRules.get(id);
         }
@@ -364,7 +369,7 @@
         }
 
         ZenModeConfig newConfig;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) {
                 throw new AndroidRuntimeException("Could not create rule");
             }
@@ -387,7 +392,7 @@
     public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
             String reason, int callingUid, boolean fromSystemOrSystemUi) {
         ZenModeConfig newConfig;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return false;
             if (DEBUG) {
                 Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
@@ -419,7 +424,7 @@
     public boolean removeAutomaticZenRule(String id, String reason, int callingUid,
             boolean fromSystemOrSystemUi) {
         ZenModeConfig newConfig;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return false;
             newConfig = mConfig.copy();
             ZenRule ruleToRemove = newConfig.automaticRules.get(id);
@@ -450,7 +455,7 @@
     public boolean removeAutomaticZenRules(String packageName, String reason, int callingUid,
             boolean fromSystemOrSystemUi) {
         ZenModeConfig newConfig;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return false;
             newConfig = mConfig.copy();
             for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
@@ -467,7 +472,7 @@
     public void setAutomaticZenRuleState(String id, Condition condition, int callingUid,
             boolean fromSystemOrSystemUi) {
         ZenModeConfig newConfig;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return;
 
             newConfig = mConfig.copy();
@@ -481,7 +486,7 @@
     public void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition, int callingUid,
             boolean fromSystemOrSystemUi) {
         ZenModeConfig newConfig;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return;
             newConfig = mConfig.copy();
 
@@ -491,6 +496,7 @@
         }
     }
 
+    @GuardedBy("mConfigLock")
     private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules,
             Condition condition, int callingUid, boolean fromSystemOrSystemUi) {
         if (rules == null || rules.isEmpty()) return;
@@ -538,7 +544,7 @@
             return 0;
         }
         int count = 0;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             for (ZenRule rule : mConfig.automaticRules.values()) {
                 if (cn.equals(rule.component) || cn.equals(rule.configurationActivity)) {
                     count++;
@@ -555,7 +561,7 @@
             return 0;
         }
         int count = 0;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             for (ZenRule rule : mConfig.automaticRules.values()) {
                 if (pkg.equals(rule.getPkg())) {
                     count++;
@@ -588,19 +594,23 @@
 
     protected void updateDefaultZenRules(int callingUid, boolean fromSystemOrSystemUi) {
         updateDefaultAutomaticRuleNames();
-        for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) {
-            ZenRule currRule = mConfig.automaticRules.get(defaultRule.id);
-            // if default rule wasn't user-modified nor enabled, use localized name
-            // instead of previous system name
-            if (currRule != null && !currRule.modified && !currRule.enabled
-                    && !defaultRule.name.equals(currRule.name)) {
-                if (canManageAutomaticZenRule(currRule)) {
-                    if (DEBUG) Slog.d(TAG, "Locale change - updating default zen rule name "
-                            + "from " + currRule.name + " to " + defaultRule.name);
-                    // update default rule (if locale changed, name of rule will change)
-                    currRule.name = defaultRule.name;
-                    updateAutomaticZenRule(defaultRule.id, createAutomaticZenRule(currRule),
-                            "locale changed", callingUid, fromSystemOrSystemUi);
+        synchronized (mConfigLock) {
+            for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) {
+                ZenRule currRule = mConfig.automaticRules.get(defaultRule.id);
+                // if default rule wasn't user-modified nor enabled, use localized name
+                // instead of previous system name
+                if (currRule != null && !currRule.modified && !currRule.enabled
+                        && !defaultRule.name.equals(currRule.name)) {
+                    if (canManageAutomaticZenRule(currRule)) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Locale change - updating default zen rule name "
+                                    + "from " + currRule.name + " to " + defaultRule.name);
+                        }
+                        // update default rule (if locale changed, name of rule will change)
+                        currRule.name = defaultRule.name;
+                        updateAutomaticZenRule(defaultRule.id, createAutomaticZenRule(currRule),
+                                "locale changed", callingUid, fromSystemOrSystemUi);
+                    }
                 }
             }
         }
@@ -686,7 +696,7 @@
     private void setManualZenMode(int zenMode, Uri conditionId, String reason, String caller,
             boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) {
         ZenModeConfig newConfig;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig == null) return;
             if (!Global.isValidZenMode(zenMode)) return;
             if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
@@ -715,7 +725,7 @@
 
     void dump(ProtoOutputStream proto) {
         proto.write(ZenModeProto.ZEN_MODE, mZenMode);
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             if (mConfig.manualRule != null) {
                 mConfig.manualRule.dumpDebug(proto, ZenModeProto.ENABLED_ACTIVE_CONDITIONS);
             }
@@ -737,14 +747,14 @@
         pw.println(Global.zenModeToString(mZenMode));
         pw.print(prefix);
         pw.println("mConsolidatedPolicy=" + mConsolidatedPolicy.toString());
-        synchronized(mConfigsLock) {
+        synchronized (mConfigsArrayLock) {
             final int N = mConfigs.size();
             for (int i = 0; i < N; i++) {
                 dump(pw, prefix, "mConfigs[u=" + mConfigs.keyAt(i) + "]", mConfigs.valueAt(i));
             }
         }
         pw.print(prefix); pw.print("mUser="); pw.println(mUser);
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             dump(pw, prefix, "mConfig", mConfig);
         }
 
@@ -833,7 +843,7 @@
                         Settings.Secure.ZEN_SETTINGS_UPDATED, 1, userId);
             }
             if (DEBUG) Log.d(TAG, reason);
-            synchronized (mConfig) {
+            synchronized (mConfigLock) {
                 setConfigLocked(config, null, reason, Process.SYSTEM_UID, true);
             }
         }
@@ -841,7 +851,7 @@
 
     public void writeXml(TypedXmlSerializer out, boolean forBackup, Integer version, int userId)
             throws IOException {
-        synchronized (mConfigsLock) {
+        synchronized (mConfigsArrayLock) {
             final int n = mConfigs.size();
             for (int i = 0; i < n; i++) {
                 if (forBackup && mConfigs.keyAt(i) != userId) {
@@ -856,7 +866,9 @@
      * @return user-specified default notification policy for priority only do not disturb
      */
     public Policy getNotificationPolicy() {
-        return getNotificationPolicy(mConfig);
+        synchronized (mConfigLock) {
+            return getNotificationPolicy(mConfig);
+        }
     }
 
     private static Policy getNotificationPolicy(ZenModeConfig config) {
@@ -867,8 +879,8 @@
      * Sets the global notification policy used for priority only do not disturb
      */
     public void setNotificationPolicy(Policy policy, int callingUid, boolean fromSystemOrSystemUi) {
-        if (policy == null || mConfig == null) return;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
+            if (policy == null || mConfig == null) return;
             final ZenModeConfig newConfig = mConfig.copy();
             newConfig.applyNotificationPolicy(policy);
             setConfigLocked(newConfig, null, "setNotificationPolicy", callingUid,
@@ -881,7 +893,7 @@
      */
     private void cleanUpZenRules() {
         long currentTime = System.currentTimeMillis();
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             final ZenModeConfig newConfig = mConfig.copy();
             if (newConfig.automaticRules != null) {
                 for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
@@ -906,7 +918,7 @@
      * @return a copy of the zen mode configuration
      */
     public ZenModeConfig getConfig() {
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             return mConfig.copy();
         }
     }
@@ -918,7 +930,8 @@
         return mConsolidatedPolicy.copy();
     }
 
-    public boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,
+    @GuardedBy("mConfigLock")
+    private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent,
             String reason, int callingUid, boolean fromSystemOrSystemUi) {
         return setConfigLocked(config, reason, triggeringComponent, true /*setRingerMode*/,
                 callingUid, fromSystemOrSystemUi);
@@ -926,11 +939,12 @@
 
     public void setConfig(ZenModeConfig config, ComponentName triggeringComponent, String reason,
             int callingUid, boolean fromSystemOrSystemUi) {
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
             setConfigLocked(config, triggeringComponent, reason, callingUid, fromSystemOrSystemUi);
         }
     }
 
+    @GuardedBy("mConfigLock")
     private boolean setConfigLocked(ZenModeConfig config, String reason,
             ComponentName triggeringComponent, boolean setRingerMode, int callingUid,
             boolean fromSystemOrSystemUi) {
@@ -942,7 +956,7 @@
             }
             if (config.user != mUser) {
                 // simply store away for background users
-                synchronized (mConfigsLock) {
+                synchronized (mConfigsArrayLock) {
                     mConfigs.put(config.user, config);
                 }
                 if (DEBUG) Log.d(TAG, "setConfigLocked: store config for user " + config.user);
@@ -951,7 +965,7 @@
             // handle CPS backed conditions - danger! may modify config
             mConditions.evaluateConfig(config, null, false /*processSubscriptions*/);
 
-            synchronized (mConfigsLock) {
+            synchronized (mConfigsArrayLock) {
                 mConfigs.put(config.user, config);
             }
             if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable());
@@ -979,6 +993,7 @@
      * Carries out a config update (if needed) and (re-)evaluates the zen mode value afterwards.
      * If logging is enabled, will also request logging of the outcome of this change if needed.
      */
+    @GuardedBy("mConfigLock")
     private void updateConfigAndZenModeLocked(ZenModeConfig config, String reason,
             boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) {
         final boolean logZenModeEvents = mFlagResolver.isEnabled(
@@ -993,7 +1008,7 @@
         }
         final String val = Integer.toString(config.hashCode());
         Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
-        evaluateZenMode(reason, setRingerMode);
+        evaluateZenModeLocked(reason, setRingerMode);
         // After all changes have occurred, log if requested
         if (logZenModeEvents) {
             ZenModeEventLogger.ZenModeInfo newInfo = new ZenModeEventLogger.ZenModeInfo(
@@ -1025,7 +1040,8 @@
     }
 
     @VisibleForTesting
-    protected void evaluateZenMode(String reason, boolean setRingerMode) {
+    @GuardedBy("mConfigLock")
+    protected void evaluateZenModeLocked(String reason, boolean setRingerMode) {
         if (DEBUG) Log.d(TAG, "evaluateZenMode");
         if (mConfig == null) return;
         final int policyHashBefore = mConsolidatedPolicy == null ? 0
@@ -1056,8 +1072,8 @@
     }
 
     private int computeZenMode() {
-        if (mConfig == null) return Global.ZEN_MODE_OFF;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
+            if (mConfig == null) return Global.ZEN_MODE_OFF;
             if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
             int zen = Global.ZEN_MODE_OFF;
             for (ZenRule automaticRule : mConfig.automaticRules.values()) {
@@ -1094,8 +1110,8 @@
     }
 
     private void updateConsolidatedPolicy(String reason) {
-        if (mConfig == null) return;
-        synchronized (mConfig) {
+        synchronized (mConfigLock) {
+            if (mConfig == null) return;
             ZenPolicy policy = new ZenPolicy();
             if (mConfig.manualRule != null) {
                 applyCustomPolicy(policy, mConfig.manualRule);
@@ -1293,7 +1309,7 @@
      * Generate pulled atoms about do not disturb configurations.
      */
     public void pullRules(List<StatsEvent> events) {
-        synchronized (mConfigsLock) {
+        synchronized (mConfigsArrayLock) {
             final int numConfigs = mConfigs.size();
             for (int i = 0; i < numConfigs; i++) {
                 final int user = mConfigs.keyAt(i);
@@ -1319,6 +1335,7 @@
         }
     }
 
+    @GuardedBy("mConfigsArrayLock")
     private void ruleToProtoLocked(int user, ZenRule rule, boolean isManualRule,
             List<StatsEvent> events) {
         // Make the ID safe.
@@ -1389,7 +1406,7 @@
 
             if (mZenMode == Global.ZEN_MODE_OFF
                     || (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
-                    && !ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(mConfig))) {
+                    && !areAllPriorityOnlyRingerSoundsMuted())) {
                 // in priority only with ringer not muted, save ringer mode changes
                 // in dnd off, save ringer mode changes
                 setPreviousRingerModeSetting(ringerModeNew);
@@ -1410,8 +1427,7 @@
                             && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
                             || mZenMode == Global.ZEN_MODE_ALARMS
                             || (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
-                            && ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(
-                            mConfig)))) {
+                            && areAllPriorityOnlyRingerSoundsMuted()))) {
                         newZen = Global.ZEN_MODE_OFF;
                     } else if (mZenMode != Global.ZEN_MODE_OFF) {
                         ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
@@ -1430,6 +1446,12 @@
             return ringerModeExternalOut;
         }
 
+        private boolean areAllPriorityOnlyRingerSoundsMuted() {
+            synchronized (mConfigLock) {
+                return ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(mConfig);
+            }
+        }
+
         @Override
         public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
                 int ringerModeInternal, VolumePolicy policy) {
@@ -1633,7 +1655,7 @@
         private void emitRules() {
             final long now = SystemClock.elapsedRealtime();
             final long since = (now - mRuleCountLogTime);
-            synchronized (mConfig) {
+            synchronized (mConfigLock) {
                 int numZenRules = mConfig.automaticRules.size();
                 if (mNumZenRules != numZenRules
                         || since > MINIMUM_LOG_PERIOD_MS) {
@@ -1651,7 +1673,7 @@
         private void emitDndType() {
             final long now = SystemClock.elapsedRealtime();
             final long since = (now - mTypeLogTimeMs);
-            synchronized (mConfig) {
+            synchronized (mConfigLock) {
                 boolean dndOn = mZenMode != Global.ZEN_MODE_OFF;
                 int zenType = !dndOn ? DND_OFF
                         : (mConfig.manualRule != null) ? DND_ON_MANUAL : DND_ON_AUTOMATIC;
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 0605345..3ba307b 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -22,6 +22,7 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
+import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
@@ -64,6 +65,8 @@
     private static final int LOCAL_LOG_SIZE = 20;
     private static final String TAG = "BugreportManagerService";
     private static final boolean DEBUG = false;
+    private static final String ROLE_SYSTEM_AUTOMOTIVE_PROJECTION =
+            "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION";
 
     private static final String BUGREPORT_SERVICE = "bugreportd";
     private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
@@ -326,11 +329,22 @@
 
         // To gain access through the DUMP permission, the OEM has to allow this package explicitly
         // via sysconfig and privileged permissions.
-        if (mBugreportAllowlistedPackages.contains(callingPackage)
-                && mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
-                        == PackageManager.PERMISSION_GRANTED) {
+        boolean allowlisted = mBugreportAllowlistedPackages.contains(callingPackage);
+        if (!allowlisted) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                allowlisted = mContext.getSystemService(RoleManager.class).getRoleHolders(
+                        ROLE_SYSTEM_AUTOMOTIVE_PROJECTION).contains(callingPackage);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        if (allowlisted && mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.DUMP) == PackageManager.PERMISSION_GRANTED) {
             return;
         }
+
         // For carrier privileges, this can include user-installed apps. This is essentially a
         // function of the current active SIM(s) in the device to let carrier apps through.
         final long token = Binder.clearCallingIdentity();
@@ -346,7 +360,8 @@
 
         String message =
                 callingPackage
-                        + " does not hold the DUMP permission or is not bugreport-whitelisted "
+                        + " does not hold the DUMP permission or is not bugreport-whitelisted or "
+                        + "does not have an allowed role "
                         + (checkCarrierPrivileges ? "and does not have carrier privileges " : "")
                         + "to request a bugreport";
         Slog.w(TAG, message);
diff --git a/services/core/java/com/android/server/pm/DefaultAppProvider.java b/services/core/java/com/android/server/pm/DefaultAppProvider.java
index c18d0e9..fc61451 100644
--- a/services/core/java/com/android/server/pm/DefaultAppProvider.java
+++ b/services/core/java/com/android/server/pm/DefaultAppProvider.java
@@ -24,14 +24,10 @@
 import android.os.UserHandle;
 import android.util.Slog;
 
-import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.CollectionUtils;
 import com.android.server.FgThread;
 
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -70,27 +66,19 @@
      * Set the package name of the default browser.
      *
      * @param packageName package name of the default browser, or {@code null} to unset
-     * @param async whether the operation should be asynchronous
      * @param userId the user ID
-     * @return whether the default browser was successfully set.
      */
-    public boolean setDefaultBrowser(@Nullable String packageName, boolean async,
-            @UserIdInt int userId) {
-        if (userId == UserHandle.USER_ALL) {
-            return false;
-        }
+    public void setDefaultBrowser(@Nullable String packageName, @UserIdInt int userId) {
         final RoleManager roleManager = mRoleManagerSupplier.get();
         if (roleManager == null) {
-            return false;
+            return;
         }
         final UserHandle user = UserHandle.of(userId);
         final Executor executor = FgThread.getExecutor();
-        final AndroidFuture<Void> future = new AndroidFuture<>();
         final Consumer<Boolean> callback = successful -> {
-            if (successful) {
-                future.complete(null);
-            } else {
-                future.completeExceptionally(new RuntimeException());
+            if (!successful) {
+                Slog.e(PackageManagerService.TAG, "Failed to set default browser to "
+                        + packageName);
             }
         };
         final long identity = Binder.clearCallingIdentity();
@@ -102,19 +90,9 @@
                 roleManager.clearRoleHoldersAsUser(RoleManager.ROLE_BROWSER, 0, user, executor,
                         callback);
             }
-            if (!async) {
-                try {
-                    future.get(5, TimeUnit.SECONDS);
-                } catch (InterruptedException | ExecutionException | TimeoutException e) {
-                    Slog.e(PackageManagerService.TAG, "Exception while setting default browser: "
-                            + packageName, e);
-                    return false;
-                }
-            }
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
-        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 5f52c16..c1d5af5 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -610,7 +610,8 @@
                     PackageManager.UNINSTALL_REASON_UNKNOWN,
                     null /*harmfulAppWarning*/,
                     null /*splashScreenTheme*/,
-                    0 /*firstInstallTime*/);
+                    0 /*firstInstallTime*/,
+                    PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
         }
         mPm.mSettings.writeKernelMappingLPr(ps);
     }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index ae169318..fbdf750 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -86,7 +86,10 @@
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -127,6 +130,9 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 /**
  * Service that manages requests and callbacks for launchers that support
@@ -215,7 +221,8 @@
 
         final LauncherAppsServiceInternal mInternal;
 
-        private RemoteCallbackList<IDumpCallback> mDumpCallbacks = new RemoteCallbackList<>();
+        @NonNull
+        private final RemoteCallbackList<IDumpCallback> mDumpCallbacks = new RemoteCallbackList<>();
 
         public LauncherAppsImpl(Context context) {
             mContext = context;
@@ -1462,46 +1469,124 @@
                     getActivityOptionsForLauncher(opts), user.getIdentifier());
         }
 
+        @Override
+        public void onShellCommand(FileDescriptor in, @NonNull FileDescriptor out,
+                @NonNull FileDescriptor err, @Nullable String[] args, ShellCallback cb,
+                @Nullable ResultReceiver receiver) {
+            final int callingUid = injectBinderCallingUid();
+            if (!(callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID)) {
+                throw new SecurityException("Caller must be shell");
+            }
+
+            final long token = injectClearCallingIdentity();
+            try {
+                int status = (new LauncherAppsShellCommand())
+                        .exec(this, in, out, err, args, cb, receiver);
+                if (receiver != null) {
+                    receiver.send(status, null);
+                }
+            } finally {
+                injectRestoreCallingIdentity(token);
+            }
+        }
+
+        /** Handles Shell commands for LauncherAppsService */
+        private class LauncherAppsShellCommand extends ShellCommand {
+            @Override
+            public int onCommand(@Nullable String cmd) {
+                if ("dump-view-hierarchies".equals(cmd)) {
+                    dumpViewCaptureDataToShell();
+                    return 0;
+                } else {
+                    return handleDefaultCommands(cmd);
+                }
+            }
+
+            private void dumpViewCaptureDataToShell() {
+                try (ZipOutputStream zipOs = new ZipOutputStream(getRawOutputStream())) {
+                    forEachViewCaptureWindow((fileName, is) -> {
+                        try {
+                            zipOs.putNextEntry(new ZipEntry("FS" + fileName));
+                            is.transferTo(zipOs);
+                            zipOs.closeEntry();
+                        } catch (IOException e) {
+                            getErrPrintWriter().write("Failed to output " + fileName
+                                    + " data to shell: " + e.getMessage());
+                        }
+                    });
+                } catch (IOException e) {
+                    getErrPrintWriter().write("Failed to create or close zip output stream: "
+                            + e.getMessage());
+                }
+            }
+
+            @Override
+            public void onHelp() {
+                final PrintWriter pw = getOutPrintWriter();
+                pw.println("Usage: cmd launcherapps COMMAND [options ...]");
+                pw.println();
+                pw.println("cmd launcherapps dump-view-hierarchies");
+                pw.println("    Output captured view hierarchies. Files will be generated in ");
+                pw.println("    `"  + WM_TRACE_DIR + "`. After pulling the data to your device,");
+                pw.println("     you can upload / visualize it at `go/winscope`.");
+                pw.println();
+            }
+        }
 
         /**
          * Using a pipe, outputs view capture data to the wmtrace dir
          */
-        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
+                @Nullable String[] args) {
             super.dump(fd, pw, args);
 
             // Before the wmtrace directory is picked up by dumpstate service, some processes need
             // to write their data to that location. They can do that via these dumpCallbacks.
-            int i = mDumpCallbacks.beginBroadcast();
-            while (i > 0) {
-                i--;
-                dumpDataToWmTrace((String) mDumpCallbacks.getBroadcastCookie(i) + "_" + i,
-                        mDumpCallbacks.getBroadcastItem(i));
+            forEachViewCaptureWindow(this::dumpViewCaptureDataToWmTrace);
+        }
+
+        private void dumpViewCaptureDataToWmTrace(@NonNull String fileName,
+                @NonNull InputStream is) {
+            Path outPath = Paths.get(fileName);
+            try {
+                Files.copy(is, outPath, StandardCopyOption.REPLACE_EXISTING);
+                Files.setPosixFilePermissions(outPath, WM_TRACE_FILE_PERMISSIONS);
+            } catch (IOException e) {
+                Log.d(TAG, "failed to write data to " + fileName + " in wmtrace dir", e);
+            }
+        }
+
+        /**
+         * IDumpCallback.onDump alerts the in-process ViewCapture instance to start sending data
+         * to LauncherAppsService via the pipe's input provided. This data (as well as an output
+         * file name) is provided to the consumer via an InputStream to output where it wants (for
+         * example, the winscope trace directory or the shell's stdout).
+         */
+        private void forEachViewCaptureWindow(
+                @NonNull BiConsumer<String, InputStream> outputtingConsumer) {
+            for (int i = mDumpCallbacks.beginBroadcast() - 1; i >= 0; i--) {
+                String packageName = (String) mDumpCallbacks.getBroadcastCookie(i);
+                String fileName = WM_TRACE_DIR + packageName + "_" + i + VC_FILE_SUFFIX;
+
+                try {
+                    // Order is important here. OnDump needs to be called before the BiConsumer
+                    // accepts & starts blocking on reading the input stream.
+                    ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+                    mDumpCallbacks.getBroadcastItem(i).onDump(pipe[1]);
+
+                    InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0]);
+                    outputtingConsumer.accept(fileName, is);
+                    is.close();
+                } catch (Exception e) {
+                    Log.d(TAG, "failed to pipe view capture data", e);
+                }
             }
             mDumpCallbacks.finishBroadcast();
         }
 
-        private void dumpDataToWmTrace(String name, IDumpCallback cb) {
-            ParcelFileDescriptor[] pipe;
-            try {
-                pipe = ParcelFileDescriptor.createPipe();
-                cb.onDump(pipe[1]);
-            } catch (IOException | RemoteException e) {
-                Log.d(TAG, "failed to pipe view capture data", e);
-                return;
-            }
-
-            Path path = Paths.get(WM_TRACE_DIR + Paths.get(name + VC_FILE_SUFFIX).getFileName());
-            try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0])) {
-                Files.copy(is, path, StandardCopyOption.REPLACE_EXISTING);
-                Files.setPosixFilePermissions(path, WM_TRACE_FILE_PERMISSIONS);
-            } catch (IOException e) {
-                Log.d(TAG, "failed to write data to file in wmtrace dir", e);
-            }
-        }
-
         @RequiresPermission(READ_FRAME_BUFFER)
         @Override
-        public void registerDumpCallback(IDumpCallback cb) {
+        public void registerDumpCallback(@NonNull IDumpCallback cb) {
             int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
             if (PERMISSION_GRANTED == status) {
                 String name = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
@@ -1513,7 +1598,7 @@
 
         @RequiresPermission(READ_FRAME_BUFFER)
         @Override
-        public void unRegisterDumpCallback(IDumpCallback cb) {
+        public void unRegisterDumpCallback(@NonNull IDumpCallback cb) {
             int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
             if (PERMISSION_GRANTED == status) {
                 mDumpCallbacks.unregister(cb);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f2b62ea..e5dc688 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -30,6 +30,7 @@
 import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
 import static android.os.Process.INVALID_UID;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
@@ -3535,6 +3536,18 @@
         // within these users.
         mPermissionManager.restoreDelayedRuntimePermissions(packageName, userId);
 
+        // Restore default browser setting if it is now installed.
+        String defaultBrowser;
+        synchronized (mLock) {
+            defaultBrowser = mSettings.getPendingDefaultBrowserLPr(userId);
+        }
+        if (Objects.equals(packageName, defaultBrowser)) {
+            mDefaultAppProvider.setDefaultBrowser(packageName, userId);
+            synchronized (mLock) {
+                mSettings.removePendingDefaultBrowserLPw(userId);
+            }
+        }
+
         // Persistent preferred activity might have came into effect due to this
         // install.
         mPreferredActivityHelper.updateDefaultHomeNotLocked(snapshotComputer(), userId);
@@ -5240,6 +5253,20 @@
         }
 
         @Override
+        @PackageManager.UserMinAspectRatio
+        public int getUserMinAspectRatio(@NonNull String packageName, int userId) {
+            final Computer snapshot = snapshotComputer();
+            final int callingUid = Binder.getCallingUid();
+            snapshot.enforceCrossUserPermission(
+                    callingUid, userId, false /* requireFullPermission */,
+                    false /* checkShell */, "getUserMinAspectRatio");
+            final PackageStateInternal packageState = snapshot
+                    .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId);
+            return packageState == null ? USER_MIN_ASPECT_RATIO_UNSET
+                    : packageState.getUserStateOrDefault(userId).getMinAspectRatio();
+        }
+
+        @Override
         public Bundle getSuspendedPackageAppExtras(String packageName, int userId) {
             final int callingUid = Binder.getCallingUid();
             final Computer snapshot = snapshot();
@@ -6202,6 +6229,32 @@
             return true;
         }
 
+        @android.annotation.EnforcePermission(android.Manifest.permission.INSTALL_PACKAGES)
+        @Override
+        public void setUserMinAspectRatio(@NonNull String packageName, int userId,
+                @PackageManager.UserMinAspectRatio int aspectRatio) {
+            setUserMinAspectRatio_enforcePermission();
+            final int callingUid = Binder.getCallingUid();
+            final Computer snapshot = snapshotComputer();
+            snapshot.enforceCrossUserPermission(callingUid, userId,
+                    false /* requireFullPermission */, false /* checkShell */,
+                    "setUserMinAspectRatio");
+            enforceOwnerRights(snapshot, packageName, callingUid);
+
+            final PackageStateInternal packageState = snapshot
+                    .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId);
+            if (packageState == null) {
+                return;
+            }
+
+            if (packageState.getUserStateOrDefault(userId).getMinAspectRatio() == aspectRatio) {
+                return;
+            }
+
+            commitPackageStateMutation(null, packageName, state ->
+                    state.userState(userId).setMinAspectRatio(aspectRatio));
+        }
+
         @Override
         @SuppressWarnings("GuardedBy")
         public void setRuntimePermissionsVersion(int version, @UserIdInt int userId) {
@@ -6692,7 +6745,7 @@
         @Override
         public String removeLegacyDefaultBrowserPackageName(int userId) {
             synchronized (mLock) {
-                return mSettings.removeDefaultBrowserPackageNameLPw(userId);
+                return mSettings.removePendingDefaultBrowserLPw(userId);
             }
         }
 
@@ -7529,8 +7582,13 @@
                 callback);
     }
 
-    void setDefaultBrowser(@Nullable String packageName, boolean async, @UserIdInt int userId) {
-        mDefaultAppProvider.setDefaultBrowser(packageName, async, userId);
+    @Nullable
+    String getDefaultBrowser(@UserIdInt int userId) {
+        return mDefaultAppProvider.getDefaultBrowser(userId);
+    }
+
+    void setDefaultBrowser(@Nullable String packageName, @UserIdInt int userId) {
+        mDefaultAppProvider.setDefaultBrowser(packageName, userId);
     }
 
     PackageUsage getPackageUsage() {
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 7fda092..3e9ccac 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -337,7 +337,7 @@
             final long previousFirstInstallTime =
                     replacedPkgSetting.getUserStateOrDefault(userId).getFirstInstallTimeMillis();
             if (previousFirstInstallTime != 0) {
-                modifyUserState(userId).setFirstInstallTime(previousFirstInstallTime);
+                modifyUserState(userId).setFirstInstallTimeMillis(previousFirstInstallTime);
             }
         }
         onChanged();
@@ -352,10 +352,10 @@
         if (userId == UserHandle.USER_ALL) {
             int userStateCount = mUserStates.size();
             for (int i = 0; i < userStateCount; i++) {
-                mUserStates.valueAt(i).setFirstInstallTime(firstInstallTime);
+                mUserStates.valueAt(i).setFirstInstallTimeMillis(firstInstallTime);
             }
         } else {
-            modifyUserState(userId).setFirstInstallTime(firstInstallTime);
+            modifyUserState(userId).setFirstInstallTimeMillis(firstInstallTime);
         }
         onChanged();
         return this;
@@ -875,7 +875,7 @@
             ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
             int installReason, int uninstallReason,
             String harmfulAppWarning, String splashScreenTheme,
-            long firstInstallTime) {
+            long firstInstallTime, int aspectRatio) {
         modifyUserState(userId)
                 .setSuspendParams(suspendParams)
                 .setCeDataInode(ceDataInode)
@@ -894,7 +894,8 @@
                 .setVirtualPreload(virtualPreload)
                 .setHarmfulAppWarning(harmfulAppWarning)
                 .setSplashScreenTheme(splashScreenTheme)
-                .setFirstInstallTime(firstInstallTime);
+                .setFirstInstallTimeMillis(firstInstallTime)
+                .setMinAspectRatio(aspectRatio);
         onChanged();
     }
 
@@ -912,7 +913,7 @@
                         ? null : otherState.getDisabledComponentsNoCopy().untrackedStorage(),
                 otherState.getInstallReason(), otherState.getUninstallReason(),
                 otherState.getHarmfulAppWarning(), otherState.getSplashScreenTheme(),
-                otherState.getFirstInstallTimeMillis());
+                otherState.getFirstInstallTimeMillis(), otherState.getMinAspectRatio());
     }
 
     WatchedArraySet<String> getEnabledComponents(int userId) {
diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
index 214a8b8..76e7070 100644
--- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -557,9 +557,8 @@
             serializer.startDocument(null, true);
             serializer.startTag(null, TAG_DEFAULT_APPS);
 
-            synchronized (mPm.mLock) {
-                mPm.mSettings.writeDefaultAppsLPr(serializer, userId);
-            }
+            final String defaultBrowser = mPm.getDefaultBrowser(userId);
+            Settings.writeDefaultApps(serializer, defaultBrowser);
 
             serializer.endTag(null, TAG_DEFAULT_APPS);
             serializer.endDocument();
@@ -584,14 +583,19 @@
             parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
             restoreFromXml(parser, userId, TAG_DEFAULT_APPS,
                     (parser1, userId1) -> {
-                        final String defaultBrowser;
-                        synchronized (mPm.mLock) {
-                            mPm.mSettings.readDefaultAppsLPw(parser1, userId1);
-                            defaultBrowser = mPm.mSettings.removeDefaultBrowserPackageNameLPw(
-                                    userId1);
-                        }
+                        final String defaultBrowser = Settings.readDefaultApps(parser1);
                         if (defaultBrowser != null) {
-                            mPm.setDefaultBrowser(defaultBrowser, false, userId1);
+                            final PackageStateInternal packageState = mPm.snapshotComputer()
+                                    .getPackageStateInternal(defaultBrowser);
+                            if (packageState != null
+                                    && packageState.getUserStateOrDefault(userId1).isInstalled()) {
+                                mPm.setDefaultBrowser(defaultBrowser, userId1);
+                            } else {
+                                synchronized (mPm.mLock) {
+                                    mPm.mSettings.setPendingDefaultBrowserLPw(defaultBrowser,
+                                            userId1);
+                                }
+                            }
                         }
                     });
         } catch (Exception e) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index aaf13eb..677a5d1 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -351,6 +351,7 @@
     private static final String ATTR_VIRTUAL_PRELOAD = "virtual-preload";
     private static final String ATTR_HARMFUL_APP_WARNING = "harmful-app-warning";
     private static final String ATTR_SPLASH_SCREEN_THEME = "splash-screen-theme";
+    private static final String ATTR_MIN_ASPECT_RATIO = "min-aspect-ratio";
 
     private static final String ATTR_PACKAGE_NAME = "packageName";
     private static final String ATTR_BUILD_FINGERPRINT = "buildFingerprint";
@@ -516,9 +517,11 @@
     private final WatchedArrayMap<String, String> mRenamedPackages =
             new WatchedArrayMap<String, String>();
 
-    // For every user, it is used to find the package name of the default Browser App.
+    // For every user, it is used to find the package name of the default browser app pending to be
+    // applied, either on first boot after upgrade, or after backup & restore but before app is
+    // installed.
     @Watched
-    final WatchedSparseArray<String> mDefaultBrowserApp = new WatchedSparseArray<String>();
+    final WatchedSparseArray<String> mPendingDefaultBrowser = new WatchedSparseArray<>();
 
     // TODO(b/161161364): This seems unused, and is probably not relevant in the new API, but should
     //  verify.
@@ -591,7 +594,7 @@
         mAppIds.registerObserver(mObserver);
         mRenamedPackages.registerObserver(mObserver);
         mNextAppLinkGeneration.registerObserver(mObserver);
-        mDefaultBrowserApp.registerObserver(mObserver);
+        mPendingDefaultBrowser.registerObserver(mObserver);
         mPendingPackages.registerObserver(mObserver);
         mPastSignatures.registerObserver(mObserver);
         mKeySetRefs.registerObserver(mObserver);
@@ -786,7 +789,7 @@
 
         mRenamedPackages.snapshot(r.mRenamedPackages);
         mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration);
-        mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp);
+        mPendingDefaultBrowser.snapshot(r.mPendingDefaultBrowser);
         // mReadMessages
         mPendingPackages = r.mPendingPackagesSnapshot.snapshot();
         mPendingPackagesSnapshot = new SnapshotCache.Sealed<>();
@@ -1122,7 +1125,8 @@
                                 PackageManager.UNINSTALL_REASON_UNKNOWN,
                                 null /*harmfulAppWarning*/,
                                 null /*splashscreenTheme*/,
-                                0 /*firstInstallTime*/
+                                0 /*firstInstallTime*/,
+                                PackageManager.USER_MIN_ASPECT_RATIO_UNSET
                         );
                     }
                 }
@@ -1502,8 +1506,16 @@
         return cpir;
     }
 
-    String removeDefaultBrowserPackageNameLPw(int userId) {
-        return (userId == UserHandle.USER_ALL) ? null : mDefaultBrowserApp.removeReturnOld(userId);
+    String getPendingDefaultBrowserLPr(int userId) {
+        return mPendingDefaultBrowser.get(userId);
+    }
+
+    void setPendingDefaultBrowserLPw(String defaultBrowser, int userId) {
+        mPendingDefaultBrowser.put(userId, defaultBrowser);
+    }
+
+    String removePendingDefaultBrowserLPw(int userId) {
+        return mPendingDefaultBrowser.removeReturnOld(userId);
     }
 
     private File getUserSystemDirectory(int userId) {
@@ -1686,6 +1698,19 @@
 
     void readDefaultAppsLPw(XmlPullParser parser, int userId)
             throws XmlPullParserException, IOException {
+        String defaultBrowser = readDefaultApps(parser);
+        if (defaultBrowser != null) {
+            mPendingDefaultBrowser.put(userId, defaultBrowser);
+        }
+    }
+
+    /**
+     * @return the package name for the default browser app, or {@code null} if none.
+     */
+    @Nullable
+    static String readDefaultApps(@NonNull XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        String defaultBrowser = null;
         int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1695,8 +1720,7 @@
             }
             String tagName = parser.getName();
             if (tagName.equals(TAG_DEFAULT_BROWSER)) {
-                String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
-                mDefaultBrowserApp.put(userId, packageName);
+                defaultBrowser = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
             } else if (tagName.equals(TAG_DEFAULT_DIALER)) {
                 // Ignored.
             } else {
@@ -1706,6 +1730,7 @@
                 XmlUtils.skipCurrentTag(parser);
             }
         }
+        return defaultBrowser;
     }
 
     void readBlockUninstallPackagesLPw(TypedXmlPullParser parser, int userId)
@@ -1776,7 +1801,8 @@
                                     PackageManager.UNINSTALL_REASON_UNKNOWN,
                                     null /*harmfulAppWarning*/,
                                     null /* splashScreenTheme*/,
-                                    0 /*firstInstallTime*/
+                                    0 /*firstInstallTime*/,
+                                    PackageManager.USER_MIN_ASPECT_RATIO_UNSET
                             );
                         }
                         return;
@@ -1871,6 +1897,9 @@
                                 ATTR_SPLASH_SCREEN_THEME);
                         final long firstInstallTime = parser.getAttributeLongHex(null,
                                 ATTR_FIRST_INSTALL_TIME, 0);
+                        final int minAspectRatio = parser.getAttributeInt(null,
+                                ATTR_MIN_ASPECT_RATIO,
+                                PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
 
                         ArraySet<String> enabledComponents = null;
                         ArraySet<String> disabledComponents = null;
@@ -1947,7 +1976,8 @@
                                 enabledCaller, enabledComponents, disabledComponents, installReason,
                                 uninstallReason, harmfulAppWarning, splashScreenTheme,
                                 firstInstallTime != 0 ? firstInstallTime :
-                                        origFirstInstallTimes.getOrDefault(name, 0L));
+                                        origFirstInstallTimes.getOrDefault(name, 0L),
+                                minAspectRatio);
 
                         mDomainVerificationManager.setLegacyUserState(name, userId, verifState);
                     } else if (tagName.equals("preferred-activities")) {
@@ -2080,8 +2110,13 @@
 
     void writeDefaultAppsLPr(XmlSerializer serializer, int userId)
             throws IllegalArgumentException, IllegalStateException, IOException {
+        String defaultBrowser = mPendingDefaultBrowser.get(userId);
+        writeDefaultApps(serializer, defaultBrowser);
+    }
+
+    static void writeDefaultApps(@NonNull XmlSerializer serializer, @Nullable String defaultBrowser)
+            throws IllegalArgumentException, IllegalStateException, IOException {
         serializer.startTag(null, TAG_DEFAULT_APPS);
-        String defaultBrowser = mDefaultBrowserApp.get(userId);
         if (!TextUtils.isEmpty(defaultBrowser)) {
             serializer.startTag(null, TAG_DEFAULT_BROWSER);
             serializer.attribute(null, ATTR_PACKAGE_NAME, defaultBrowser);
@@ -2242,6 +2277,11 @@
                             serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME,
                                     ustate.getSplashScreenTheme());
                         }
+                        if (ustate.getMinAspectRatio()
+                                != PackageManager.USER_MIN_ASPECT_RATIO_UNSET) {
+                            serializer.attributeInt(null, ATTR_MIN_ASPECT_RATIO,
+                                    ustate.getMinAspectRatio());
+                        }
                         if (ustate.isSuspended()) {
                             for (int i = 0; i < ustate.getSuspendParams().size(); i++) {
                                 final String suspendingPackage = ustate.getSuspendParams().keyAt(i);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 5b3514c..dd434fbe 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.pm;
 
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
 
 import android.Manifest.permission;
@@ -24,6 +25,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IUidObserver;
 import android.app.IUriGrantsManager;
@@ -35,6 +37,7 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentProvider;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -1925,11 +1928,32 @@
         }
         if (shortcut.getIcon() != null) {
             ShortcutInfo.validateIcon(shortcut.getIcon());
+            validateIconURI(shortcut);
         }
 
         shortcut.replaceFlags(shortcut.getFlags() & ShortcutInfo.FLAG_LONG_LIVED);
     }
 
+    // Validates the calling process has permission to access shortcut icon's image uri
+    private void validateIconURI(@NonNull final ShortcutInfo si) {
+        final int callingUid = injectBinderCallingUid();
+        final Icon icon = si.getIcon();
+        if (icon == null) {
+            // There's no icon in this shortcut, nothing to validate here.
+            return;
+        }
+        int iconType = icon.getType();
+        if (iconType != Icon.TYPE_URI && iconType != Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+            // The icon is not URI-based, nothing to validate.
+            return;
+        }
+        final Uri uri = icon.getUri();
+        mUriGrantsManagerInternal.checkGrantUriPermission(callingUid, si.getPackage(),
+                ContentProvider.getUriWithoutUserId(uri),
+                Intent.FLAG_GRANT_READ_URI_PERMISSION,
+                ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(callingUid)));
+    }
+
     private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) {
         fixUpIncomingShortcutInfo(shortcut, forUpdate, /*forPinRequest=*/ false);
     }
@@ -4407,8 +4431,11 @@
             return;
         }
         try {
+            ActivityOptions options = ActivityOptions.makeBasic()
+                    .setPendingIntentBackgroundActivityStartMode(
+                            MODE_BACKGROUND_ACTIVITY_START_DENIED);
             intentSender.sendIntent(mContext, /* code= */ 0, extras,
-                    /* onFinished=*/ null, /* handler= */ null);
+                    /* onFinished=*/ null, /* handler= */ null, null, options.toBundle());
         } catch (SendIntentException e) {
             Slog.w(TAG, "sendIntent failed().", e);
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7e88e13..cab90d2 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1037,7 +1037,7 @@
                 final UserData userData = mUsers.valueAt(i);
                 final int userId = userData.info.id;
                 if (userId != currentUser && userData.info.isFull() && !userData.info.partial
-                        && !mRemovingUserIds.get(userId)) {
+                        && userData.info.isEnabled() && !mRemovingUserIds.get(userId)) {
                     final long userEnteredTime = userData.mLastEnteredForegroundTimeMillis;
                     if (userEnteredTime > latestEnteredTime) {
                         latestEnteredTime = userEnteredTime;
@@ -2650,15 +2650,15 @@
     @Override
     public void setDefaultGuestRestrictions(Bundle restrictions) {
         checkManageUsersPermission("setDefaultGuestRestrictions");
+        final List<UserInfo> guests = getGuestUsers();
+        synchronized (mRestrictionsLock) {
+            for (int i = 0; i < guests.size(); i++) {
+                updateUserRestrictionsInternalLR(restrictions, guests.get(i).id);
+            }
+        }
         synchronized (mGuestRestrictions) {
             mGuestRestrictions.clear();
             mGuestRestrictions.putAll(restrictions);
-            final List<UserInfo> guests = getGuestUsers();
-            for (int i = 0; i < guests.size(); i++) {
-                synchronized (mRestrictionsLock) {
-                    updateUserRestrictionsInternalLR(mGuestRestrictions, guests.get(i).id);
-                }
-            }
         }
         synchronized (mPackagesLock) {
             writeUserListLP();
@@ -2927,14 +2927,14 @@
             Preconditions.checkState(mCachedEffectiveUserRestrictions.getRestrictions(userId)
                     != newBaseRestrictions);
 
-            if (mBaseUserRestrictions.updateRestrictions(userId, newBaseRestrictions)) {
+            if (mBaseUserRestrictions.updateRestrictions(userId, new Bundle(newBaseRestrictions))) {
                 scheduleWriteUser(userId);
             }
         }
 
         final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
 
-        mCachedEffectiveUserRestrictions.updateRestrictions(userId, effective);
+        mCachedEffectiveUserRestrictions.updateRestrictions(userId, new Bundle(effective));
 
         // Apply the new restrictions.
         if (DBG) {
@@ -5589,8 +5589,14 @@
         }
     }
 
-    @GuardedBy("mUsersLock")
     @VisibleForTesting
+    void addRemovingUserId(@UserIdInt int userId) {
+        synchronized (mUsersLock) {
+            addRemovingUserIdLocked(userId);
+        }
+    }
+
+    @GuardedBy("mUsersLock")
     void addRemovingUserIdLocked(@UserIdInt int userId) {
         // We remember deleted user IDs to prevent them from being
         // reused during the current boot; they can still be reused
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index 91a25db3..3d056e8 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -377,6 +377,8 @@
         private final int mUninstallReason;
         @Nullable
         private final String mSplashScreenTheme;
+        @PackageManager.UserMinAspectRatio
+        private final int mMinAspectRatio;
         private final long mFirstInstallTimeMillis;
 
         private UserStateImpl(@NonNull PackageUserState userState) {
@@ -392,6 +394,7 @@
             mSharedLibraryOverlayPaths = userState.getSharedLibraryOverlayPaths();
             mUninstallReason = userState.getUninstallReason();
             mSplashScreenTheme = userState.getSplashScreenTheme();
+            mMinAspectRatio = userState.getMinAspectRatio();
             setBoolean(Booleans.HIDDEN, userState.isHidden());
             setBoolean(Booleans.INSTALLED, userState.isInstalled());
             setBoolean(Booleans.INSTANT_APP, userState.isInstantApp());
@@ -543,6 +546,11 @@
         }
 
         @DataClass.Generated.Member
+        public @PackageManager.UserMinAspectRatio int getMinAspectRatio() {
+            return mMinAspectRatio;
+        }
+
+        @DataClass.Generated.Member
         public long getFirstInstallTimeMillis() {
             return mFirstInstallTimeMillis;
         }
@@ -554,10 +562,10 @@
         }
 
         @DataClass.Generated(
-                time = 1671671043891L,
+                time = 1687938966108L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
-                inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final  long mFirstInstallTimeMillis\npublic static  com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+                inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate final  long mFirstInstallTimeMillis\npublic static  com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
         @Deprecated
         private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
index 2048d65..f75d214 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
@@ -217,4 +217,12 @@
      */
     @Nullable
     String getSplashScreenTheme();
+
+    /**
+     * @return the min aspect ratio setting of the package which by default is unset
+     * unless it has been set by the user
+     * @hide
+     */
+    @PackageManager.UserMinAspectRatio
+    int getMinAspectRatio();
 }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
index 73fb672..1fb12a8 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
@@ -136,6 +136,11 @@
     }
 
     @Override
+    public @PackageManager.UserMinAspectRatio int getMinAspectRatio() {
+        return PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+    }
+
+    @Override
     public long getFirstInstallTimeMillis() {
         return 0;
     }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index ed4aab9..d911ac1 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm.pkg;
 
+import android.annotation.CurrentTimeMillisLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
@@ -61,6 +62,7 @@
     private int mDistractionFlags;
     private boolean mInstantApp;
     private boolean mVirtualPreload;
+    @PackageManager.EnabledState
     private int mEnabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
     @PackageManager.InstallReason
     private int mInstallReason = PackageManager.INSTALL_REASON_UNKNOWN;
@@ -81,6 +83,9 @@
     @Nullable
     private String mSplashScreenTheme;
 
+    @PackageManager.UserMinAspectRatio
+    private int mMinAspectRatio = PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+
     /**
      * Suspending package to suspend params
      */
@@ -90,7 +95,7 @@
     @Nullable
     private WatchedArrayMap<ComponentName, Pair<String, Integer>> mComponentLabelIconOverrideMap;
 
-    private long mFirstInstallTime;
+    private @CurrentTimeMillisLong long mFirstInstallTimeMillis;
 
     // TODO(b/239050028): Remove, enforce notifying parent through PMS commit method
     @Nullable
@@ -144,10 +149,11 @@
         mHarmfulAppWarning = other.mHarmfulAppWarning;
         mLastDisableAppCaller = other.mLastDisableAppCaller;
         mSplashScreenTheme = other.mSplashScreenTheme;
+        mMinAspectRatio = other.mMinAspectRatio;
         mSuspendParams = other.mSuspendParams == null ? null : other.mSuspendParams.snapshot();
         mComponentLabelIconOverrideMap = other.mComponentLabelIconOverrideMap == null
                 ? null : other.mComponentLabelIconOverrideMap.snapshot();
-        mFirstInstallTime = other.mFirstInstallTime;
+        mFirstInstallTimeMillis = other.mFirstInstallTimeMillis;
         mSnapshot = new SnapshotCache.Sealed<>();
     }
 
@@ -506,6 +512,19 @@
     }
 
     /**
+     * Sets user min aspect ratio override value
+     * @see PackageManager.UserMinAspectRatio
+     */
+    public @NonNull PackageUserStateImpl setMinAspectRatio(
+            @PackageManager.UserMinAspectRatio int value) {
+        mMinAspectRatio = value;
+        com.android.internal.util.AnnotationValidations.validate(
+                PackageManager.UserMinAspectRatio.class, null, mMinAspectRatio);
+        onChanged();
+        return this;
+    }
+
+    /**
      * Suspending package to suspend params
      */
     public @NonNull PackageUserStateImpl setSuspendParams(
@@ -538,8 +557,8 @@
         return this;
     }
 
-    public @NonNull PackageUserStateImpl setFirstInstallTime(long value) {
-        mFirstInstallTime = value;
+    public @NonNull PackageUserStateImpl setFirstInstallTimeMillis(long value) {
+        mFirstInstallTimeMillis = value;
         onChanged();
         return this;
     }
@@ -643,7 +662,7 @@
     }
 
     @DataClass.Generated.Member
-    public int getEnabledState() {
+    public @PackageManager.EnabledState int getEnabledState() {
         return mEnabledState;
     }
 
@@ -677,6 +696,11 @@
         return mSplashScreenTheme;
     }
 
+    @DataClass.Generated.Member
+    public @PackageManager.UserMinAspectRatio int getMinAspectRatio() {
+        return mMinAspectRatio;
+    }
+
     /**
      * Suspending package to suspend params
      */
@@ -691,8 +715,8 @@
     }
 
     @DataClass.Generated.Member
-    public long getFirstInstallTimeMillis() {
-        return mFirstInstallTime;
+    public @CurrentTimeMillisLong long getFirstInstallTimeMillis() {
+        return mFirstInstallTimeMillis;
     }
 
     @DataClass.Generated.Member
@@ -764,9 +788,10 @@
                 && Objects.equals(mOverlayPaths, that.mOverlayPaths)
                 && Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths)
                 && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme)
+                && mMinAspectRatio == that.mMinAspectRatio
                 && Objects.equals(mSuspendParams, that.mSuspendParams)
                 && Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap)
-                && mFirstInstallTime == that.mFirstInstallTime
+                && mFirstInstallTimeMillis == that.mFirstInstallTimeMillis
                 && watchableEquals(that.mWatchable)
                 && snapshotEquals(that.mSnapshot);
     }
@@ -796,19 +821,20 @@
         _hash = 31 * _hash + Objects.hashCode(mOverlayPaths);
         _hash = 31 * _hash + Objects.hashCode(mSharedLibraryOverlayPaths);
         _hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme);
+        _hash = 31 * _hash + mMinAspectRatio;
         _hash = 31 * _hash + Objects.hashCode(mSuspendParams);
         _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap);
-        _hash = 31 * _hash + Long.hashCode(mFirstInstallTime);
+        _hash = 31 * _hash + Long.hashCode(mFirstInstallTimeMillis);
         _hash = 31 * _hash + watchableHashCode();
         _hash = 31 * _hash + snapshotHashCode();
         return _hash;
     }
 
     @DataClass.Generated(
-            time = 1668033772891L,
+            time = 1687938397579L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
-            inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate  long mCeDataInode\nprivate  boolean mInstalled\nprivate  boolean mStopped\nprivate  boolean mNotLaunched\nprivate  boolean mHidden\nprivate  int mDistractionFlags\nprivate  boolean mInstantApp\nprivate  boolean mVirtualPreload\nprivate  int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate  long mFirstInstallTime\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate  void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic  com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic  com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTime(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate  boolean watchableEquals(com.android.server.utils.Watchable)\nprivate  int watchableHashCode()\nprivate  boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate  int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+            inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate  long mCeDataInode\nprivate  boolean mInstalled\nprivate  boolean mStopped\nprivate  boolean mNotLaunched\nprivate  boolean mHidden\nprivate  int mDistractionFlags\nprivate  boolean mInstantApp\nprivate  boolean mVirtualPreload\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate  void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic  com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic  com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate  boolean watchableEquals(com.android.server.utils.Watchable)\nprivate  int watchableHashCode()\nprivate  boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate  int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 8125b0f..8430cf7 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -439,6 +439,16 @@
                 }
                 return null;
             }
+
+            @NonNull
+            @Override
+            public PackageUserStateWrite setMinAspectRatio(
+                    @PackageManager.UserMinAspectRatio int aspectRatio) {
+                if (mUserState != null) {
+                    mUserState.setMinAspectRatio(aspectRatio);
+                }
+                return this;
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
index 11d6d97..0c6c672 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
@@ -22,6 +22,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.overlay.OverlayPaths;
 
+import com.android.server.pm.pkg.PackageUserStateImpl;
 import com.android.server.pm.pkg.SuspendParams;
 
 public interface PackageUserStateWrite {
@@ -68,4 +69,8 @@
     @NonNull
     PackageUserStateWrite setComponentLabelIcon(@NonNull ComponentName componentName,
             @Nullable String nonLocalizedLabel, @Nullable Integer icon);
+
+    /** @see PackageUserStateImpl#setMinAspectRatio(int) */
+    @NonNull
+    PackageUserStateWrite setMinAspectRatio(@PackageManager.UserMinAspectRatio int aspectRatio);
 }
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 784e177..69cc125 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.policy;
 
+import android.annotation.SuppressLint;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
@@ -23,6 +24,8 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.res.XmlResourceParser;
+import android.hardware.input.InputManager;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -30,11 +33,14 @@
 import android.util.LongSparseArray;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 
 import com.android.internal.policy.IShortcutService;
 import com.android.internal.util.XmlUtils;
+import com.android.server.input.KeyboardMetricsCollector;
+import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -88,11 +94,13 @@
     }
 
     private final Context mContext;
+    private final Handler mHandler;
     private boolean mSearchKeyShortcutPending = false;
     private boolean mConsumeSearchKeyUp = true;
 
-    ModifierShortcutManager(Context context) {
+    ModifierShortcutManager(Context context, Handler handler) {
         mContext = context;
+        mHandler = handler;
         loadShortcuts();
     }
 
@@ -273,11 +281,13 @@
      * Handle the shortcut to {@link Intent}
      *
      * @param kcm the {@link KeyCharacterMap} associated with the keyboard device.
-     * @param keyCode The key code of the event.
+     * @param keyEvent The key event.
      * @param metaState The meta key modifier state.
      * @return True if invoked the shortcut, otherwise false.
      */
-    private boolean handleIntentShortcut(KeyCharacterMap kcm, int keyCode, int metaState) {
+    @SuppressLint("MissingPermission")
+    private boolean handleIntentShortcut(KeyCharacterMap kcm, KeyEvent keyEvent, int metaState) {
+        final int keyCode = keyEvent.getKeyCode();
         // Shortcuts are invoked through Search+key, so intercept those here
         // Any printing key that is chorded with Search should be consumed
         // even if no shortcut was invoked.  This prevents text from being
@@ -307,6 +317,7 @@
                             + "keyCode=" + KeyEvent.keyCodeToString(keyCode) + ","
                             + " category=" + category);
                 }
+                logKeyboardShortcut(keyEvent, KeyboardLogEvent.getLogEventFromIntent(intent));
                 return true;
             } else {
                 return false;
@@ -323,11 +334,24 @@
                         + "the activity to which it is registered was not found: "
                         + "META+ or SEARCH" + KeyEvent.keyCodeToString(keyCode));
             }
+            logKeyboardShortcut(keyEvent, KeyboardLogEvent.getLogEventFromIntent(shortcutIntent));
             return true;
         }
         return false;
     }
 
+    private void logKeyboardShortcut(KeyEvent event, KeyboardLogEvent logEvent) {
+        mHandler.post(() -> handleKeyboardLogging(event, logEvent));
+    }
+
+    private void handleKeyboardLogging(KeyEvent event, KeyboardLogEvent logEvent) {
+        final InputManager inputManager = mContext.getSystemService(InputManager.class);
+        final InputDevice inputDevice = inputManager != null
+                ? inputManager.getInputDevice(event.getDeviceId()) : null;
+        KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(inputDevice,
+                logEvent, event.getMetaState(), event.getKeyCode());
+    }
+
     /**
      * Handle the shortcut from {@link KeyEvent}
      *
@@ -360,7 +384,7 @@
         }
 
         final KeyCharacterMap kcm = event.getKeyCharacterMap();
-        if (handleIntentShortcut(kcm, keyCode, metaState)) {
+        if (handleIntentShortcut(kcm, event, metaState)) {
             return true;
         }
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index a88e2b0..ca64792 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -91,6 +91,7 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RecentTaskInfo;
 import android.app.ActivityManagerInternal;
@@ -124,6 +125,7 @@
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiPlaybackClient;
 import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
+import android.hardware.input.InputManager;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioSystem;
@@ -205,6 +207,7 @@
 import com.android.internal.policy.TransitionAnimation;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.AccessibilityManagerInternal;
 import com.android.server.ExtconStateObserver;
@@ -215,6 +218,8 @@
 import com.android.server.UiThread;
 import com.android.server.display.BrightnessUtils;
 import com.android.server.input.InputManagerInternal;
+import com.android.server.input.KeyboardMetricsCollector;
+import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.KeyCombinationManager.TwoKeysCombinationRule;
@@ -405,6 +410,7 @@
     ActivityManagerInternal mActivityManagerInternal;
     ActivityTaskManagerInternal mActivityTaskManagerInternal;
     AutofillManagerInternal mAutofillManagerInternal;
+    InputManager mInputManager;
     InputManagerInternal mInputManagerInternal;
     DreamManagerInternal mDreamManagerInternal;
     PowerManagerInternal mPowerManagerInternal;
@@ -536,10 +542,6 @@
     int mShortPressOnSleepBehavior;
     int mShortPressOnWindowBehavior;
     int mPowerVolUpBehavior;
-    int mShortPressOnStemPrimaryBehavior;
-    int mDoublePressOnStemPrimaryBehavior;
-    int mTriplePressOnStemPrimaryBehavior;
-    int mLongPressOnStemPrimaryBehavior;
     boolean mStylusButtonsEnabled = true;
     boolean mHasSoftInput = false;
     boolean mHapticTextHandleEnabled;
@@ -553,6 +555,12 @@
     int mSearchKeyBehavior;
     ComponentName mSearchKeyTargetActivity;
 
+    // Key Behavior - Stem Primary
+    private int mShortPressOnStemPrimaryBehavior;
+    private int mDoublePressOnStemPrimaryBehavior;
+    private int mTriplePressOnStemPrimaryBehavior;
+    private int mLongPressOnStemPrimaryBehavior;
+
     private boolean mHandleVolumeKeysInWM;
 
     private boolean mPendingKeyguardOccluded;
@@ -595,8 +603,8 @@
     // What we do when the user double-taps on home
     private int mDoubleTapOnHomeBehavior;
 
-    // Whether to lock the device after the next app transition has finished.
-    boolean mLockAfterAppTransitionFinished;
+    // Whether to lock the device after the next dreaming transition has finished.
+    private boolean mLockAfterDreamingTransitionFinished;
 
     // Allowed theater mode wake actions
     private boolean mAllowTheaterModeWakeFromKey;
@@ -627,6 +635,8 @@
 
     SettingsObserver mSettingsObserver;
     ModifierShortcutManager mModifierShortcutManager;
+    /** Currently fully consumed key codes per device */
+    private final SparseArray<Set<Integer>> mConsumedKeysForDevice = new SparseArray<>();
     PowerManager.WakeLock mBroadcastWakeLock;
     PowerManager.WakeLock mPowerKeyWakeLock;
     boolean mHavePendingMediaKeyRepeatWithWakeLock;
@@ -676,6 +686,7 @@
     private static final int MSG_LAUNCH_ASSIST = 23;
     private static final int MSG_RINGER_TOGGLE_CHORD = 24;
     private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 25;
+    private static final int MSG_LOG_KEYBOARD_SYSTEM_EVENT = 26;
 
     private class PolicyHandler extends Handler {
         @Override
@@ -752,6 +763,9 @@
                 case MSG_SWITCH_KEYBOARD_LAYOUT:
                     handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
                     break;
+                case MSG_LOG_KEYBOARD_SYSTEM_EVENT:
+                    handleKeyboardSystemEvent(KeyboardLogEvent.from(msg.arg1), (KeyEvent) msg.obj);
+                    break;
             }
         }
     }
@@ -818,7 +832,6 @@
 
         @Override public void onChange(boolean selfChange) {
             updateSettings();
-            updateRotation(false);
         }
     }
 
@@ -1098,7 +1111,7 @@
         synchronized (mLock) {
             // If the setting to lock instantly on power button press is true, then set the flag to
             // lock after the dream transition has finished.
-            mLockAfterAppTransitionFinished =
+            mLockAfterDreamingTransitionFinished =
                     mLockPatternUtils.getPowerButtonInstantlyLocks(mCurrentUserId);
         }
 
@@ -1751,8 +1764,11 @@
     }
 
     private void launchAllAppsViaA11y() {
-        getAccessibilityManagerInternal().performSystemAction(
-                AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
+        AccessibilityManagerInternal accessibilityManager = getAccessibilityManagerInternal();
+        if (accessibilityManager != null) {
+            accessibilityManager.performSystemAction(
+                    AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
+        }
     }
 
     private void toggleNotificationPanel() {
@@ -1809,7 +1825,7 @@
             mDisplayId = displayId;
         }
 
-        int handleHomeButton(IBinder focusedToken, KeyEvent event) {
+        boolean handleHomeButton(IBinder focusedToken, KeyEvent event) {
             final boolean keyguardOn = keyguardOn();
             final int repeatCount = event.getRepeatCount();
             final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
@@ -1823,6 +1839,7 @@
             // If we have released the home key, and didn't do anything else
             // while it was pressed, then it is time to go home!
             if (!down) {
+                logKeyboardSystemsEvent(event, KeyboardLogEvent.HOME);
                 if (mDisplayId == DEFAULT_DISPLAY) {
                     cancelPreloadRecentApps();
                 }
@@ -1830,12 +1847,12 @@
                 mHomePressed = false;
                 if (mHomeConsumed) {
                     mHomeConsumed = false;
-                    return -1;
+                    return true;
                 }
 
                 if (canceled) {
                     Log.i(TAG, "Ignoring HOME; event canceled.");
-                    return -1;
+                    return true;
                 }
 
                 // Delay handling home if a double-tap is possible.
@@ -1847,13 +1864,13 @@
                         mHomeDoubleTapPending = true;
                         mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,
                                 ViewConfiguration.getDoubleTapTimeout());
-                        return -1;
+                        return true;
                     }
                 }
 
                 // Post to main thread to avoid blocking input pipeline.
                 mHandler.post(() -> handleShortPressOnHome(mDisplayId));
-                return -1;
+                return true;
             }
 
             final KeyInterceptionInfo info =
@@ -1865,12 +1882,12 @@
                         || (info.layoutParamsType == TYPE_NOTIFICATION_SHADE
                         && isKeyguardShowing())) {
                     // the "app" is keyguard, so give it the key
-                    return 0;
+                    return false;
                 }
                 for (int t : WINDOW_TYPES_WHERE_HOME_DOESNT_WORK) {
                     if (info.layoutParamsType == t) {
                         // don't do anything, but also don't pass it to the app
-                        return -1;
+                        return true;
                     }
                 }
             }
@@ -1895,7 +1912,7 @@
                             event.getEventTime()));
                 }
             }
-            return -1;
+            return true;
         }
 
         private void handleDoubleTapOnHome() {
@@ -1994,6 +2011,21 @@
         Supplier<GlobalActions> getGlobalActionsFactory() {
             return () -> new GlobalActions(mContext, mWindowManagerFuncs);
         }
+
+        KeyguardServiceDelegate getKeyguardServiceDelegate() {
+            return new KeyguardServiceDelegate(mContext,
+                    new StateCallback() {
+                        @Override
+                        public void onTrustedChanged() {
+                            mWindowManagerFuncs.notifyKeyguardTrustedChanged();
+                        }
+
+                        @Override
+                        public void onShowingChanged() {
+                            mWindowManagerFuncs.onKeyguardShowingAndNotOccludedChanged();
+                        }
+                    });
+        }
     }
 
     /** {@inheritDoc} */
@@ -2009,6 +2041,7 @@
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+        mInputManager = mContext.getSystemService(InputManager.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
@@ -2078,7 +2111,7 @@
         mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler);
         mSettingsObserver = new SettingsObserver(mHandler);
         mSettingsObserver.observe();
-        mModifierShortcutManager = new ModifierShortcutManager(mContext);
+        mModifierShortcutManager = new ModifierShortcutManager(mContext, mHandler);
         mUiMode = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_defaultUiModeType);
         mHomeIntent =  new Intent(Intent.ACTION_MAIN, null);
@@ -2232,37 +2265,28 @@
                         true /* notifyOccluded */);
 
                 synchronized (mLock) {
-                    mLockAfterAppTransitionFinished = false;
+                    mLockAfterDreamingTransitionFinished = false;
                 }
             }
 
             @Override
             public void onAppTransitionFinishedLocked(IBinder token) {
                 synchronized (mLock) {
-                    if (!mLockAfterAppTransitionFinished) {
-                        return;
+                    final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
+                    // check both isDreaming and mLockAfterDreamingTransitionFinished before lockNow
+                    // so it won't relock after dreaming has stopped
+                    if (dreamManagerInternal != null && dreamManagerInternal.isDreaming()
+                            && mLockAfterDreamingTransitionFinished) {
+                        lockNow(null);
                     }
-                    mLockAfterAppTransitionFinished = false;
+                    mLockAfterDreamingTransitionFinished = false;
                 }
-
-                lockNow(null);
             }
         });
 
         mKeyguardDrawnTimeout = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_keyguardDrawnTimeout);
-        mKeyguardDelegate = new KeyguardServiceDelegate(mContext,
-                new StateCallback() {
-                    @Override
-                    public void onTrustedChanged() {
-                        mWindowManagerFuncs.notifyKeyguardTrustedChanged();
-                    }
-
-                    @Override
-                    public void onShowingChanged() {
-                        mWindowManagerFuncs.onKeyguardShowingAndNotOccludedChanged();
-                    }
-                });
+        mKeyguardDelegate = injector.getKeyguardServiceDelegate();
         initKeyCombinationRules();
         initSingleKeyGestureRules();
         mSideFpsEventHandler = new SideFpsEventHandler(mContext, mHandler, mPowerManager);
@@ -2287,6 +2311,7 @@
                             cancelPendingScreenshotChordAction();
                         }
                     });
+
             if (mHasFeatureWatch) {
                 mKeyCombinationManager.addRule(
                         new TwoKeysCombinationRule(KEYCODE_POWER, KEYCODE_STEM_PRIMARY) {
@@ -2906,29 +2931,62 @@
             WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
         };
 
+    /**
+     * Log the keyboard shortcuts without blocking the current thread.
+     *
+     * We won't log keyboard events when the input device is null
+     * or when it is virtual.
+     */
+    private void handleKeyboardSystemEvent(KeyboardLogEvent keyboardLogEvent, KeyEvent event) {
+        final InputDevice inputDevice = mInputManager.getInputDevice(event.getDeviceId());
+        KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(inputDevice,
+                keyboardLogEvent, event.getMetaState(), event.getKeyCode());
+        event.recycle();
+    }
+
+    private void logKeyboardSystemsEventOnActionUp(KeyEvent event,
+            KeyboardLogEvent keyboardSystemEvent) {
+        if (event.getAction() != KeyEvent.ACTION_UP) {
+            return;
+        }
+        logKeyboardSystemsEvent(event, keyboardSystemEvent);
+    }
+
+    private void logKeyboardSystemsEventOnActionDown(KeyEvent event,
+            KeyboardLogEvent keyboardSystemEvent) {
+        if (event.getAction() != KeyEvent.ACTION_DOWN) {
+            return;
+        }
+        logKeyboardSystemsEvent(event, keyboardSystemEvent);
+    }
+
+    private void logKeyboardSystemsEvent(KeyEvent event, KeyboardLogEvent keyboardSystemEvent) {
+        KeyEvent eventToLog = KeyEvent.obtain(event);
+        mHandler.obtainMessage(MSG_LOG_KEYBOARD_SYSTEM_EVENT, keyboardSystemEvent.getIntValue(), 0,
+                eventToLog).sendToTarget();
+    }
+
     // TODO(b/117479243): handle it in InputPolicy
+    // TODO (b/283241997): Add the remaining keyboard shortcut logging after refactoring
     /** {@inheritDoc} */
     @Override
     public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
             int policyFlags) {
-        final boolean keyguardOn = keyguardOn();
         final int keyCode = event.getKeyCode();
-        final int repeatCount = event.getRepeatCount();
-        final int metaState = event.getMetaState();
         final int flags = event.getFlags();
-        final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
-        final boolean canceled = event.isCanceled();
-        final int displayId = event.getDisplayId();
-        final long key_consumed = -1;
-        final long key_not_consumed = 0;
+        final long keyConsumed = -1;
+        final long keyNotConsumed = 0;
+        final int deviceId = event.getDeviceId();
 
         if (DEBUG_INPUT) {
-            Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
-                    + repeatCount + " keyguardOn=" + keyguardOn + " canceled=" + canceled);
+            Log.d(TAG,
+                    "interceptKeyTi keyCode=" + keyCode + " action=" + event.getAction()
+                            + " repeatCount=" + event.getRepeatCount() + " keyguardOn="
+                            + keyguardOn() + " canceled=" + event.isCanceled());
         }
 
         if (mKeyCombinationManager.isKeyConsumed(event)) {
-            return key_consumed;
+            return keyConsumed;
         }
 
         if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
@@ -2939,8 +2997,54 @@
             }
         }
 
-        // Cancel any pending meta actions if we see any other keys being pressed between the down
-        // of the meta key and its corresponding up.
+        Set<Integer> consumedKeys = mConsumedKeysForDevice.get(deviceId);
+        if (consumedKeys == null) {
+            consumedKeys = new HashSet<>();
+            mConsumedKeysForDevice.put(deviceId, consumedKeys);
+        }
+
+        if (interceptSystemKeysAndShortcuts(focusedToken, event)
+                && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+            consumedKeys.add(keyCode);
+            return keyConsumed;
+        }
+
+        boolean needToConsumeKey = consumedKeys.contains(keyCode);
+        if (event.getAction() == KeyEvent.ACTION_UP || event.isCanceled()) {
+            consumedKeys.remove(keyCode);
+            if (consumedKeys.isEmpty()) {
+                mConsumedKeysForDevice.remove(deviceId);
+            }
+        }
+
+        return needToConsumeKey ? keyConsumed : keyNotConsumed;
+    }
+
+    // You can only start consuming the key gesture if ACTION_DOWN and repeat count
+    // is 0. If you start intercepting the key halfway, then key will not be consumed
+    // and will be sent to apps for processing too.
+    // e.g. If a certain combination is only handled on ACTION_UP (i.e.
+    // interceptShortcut() returns true only for ACTION_UP), then since we already
+    // sent the ACTION_DOWN events to the application, we MUST also send the
+    // ACTION_UP to the application.
+    // So, to ensure that your intercept logic works properly, and we don't send any
+    // conflicting events to application, make sure to consume the event on
+    // ACTION_DOWN even if you want to do something on ACTION_UP. This is essential
+    // to maintain event parity and to not have incomplete key gestures.
+    @SuppressLint("MissingPermission")
+    private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) {
+        final boolean keyguardOn = keyguardOn();
+        final int keyCode = event.getKeyCode();
+        final int repeatCount = event.getRepeatCount();
+        final int metaState = event.getMetaState();
+        final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+        final boolean canceled = event.isCanceled();
+        final int displayId = event.getDisplayId();
+        final int deviceId = event.getDeviceId();
+        final boolean firstDown = down && repeatCount == 0;
+
+        // Cancel any pending meta actions if we see any other keys being pressed between the
+        // down of the meta key and its corresponding up.
         if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) {
             mPendingMetaAction = false;
         }
@@ -2954,46 +3058,49 @@
                 dismissKeyboardShortcutsMenu();
                 mPendingMetaAction = false;
                 mPendingCapsLockToggle = false;
-                return key_consumed;
+                return true;
             }
         }
 
-        switch(keyCode) {
+        switch (keyCode) {
             case KeyEvent.KEYCODE_HOME:
                 return handleHomeShortcuts(displayId, focusedToken, event);
             case KeyEvent.KEYCODE_MENU:
                 // Hijack modified menu keys for debugging features
                 final int chordBug = KeyEvent.META_SHIFT_ON;
 
-                if (down && repeatCount == 0) {
-                    if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) {
-                        Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
-                        mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
-                                null, null, null, 0, null, null);
-                        return key_consumed;
-                    }
+                if (mEnableShiftMenuBugReports && firstDown
+                        && (metaState & chordBug) == chordBug) {
+                    Intent intent = new Intent(Intent.ACTION_BUG_REPORT);
+                    mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT,
+                            null, null, null, 0, null, null);
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TRIGGER_BUG_REPORT);
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_RECENT_APPS:
-                if (down && repeatCount == 0) {
+                if (firstDown) {
                     showRecentApps(false /* triggeredFromAltTab */);
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.RECENT_APPS);
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_APP_SWITCH:
                 if (!keyguardOn) {
-                    if (down && repeatCount == 0) {
+                    if (firstDown) {
                         preloadRecentApps();
                     } else if (!down) {
                         toggleRecentApps();
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.APP_SWITCH);
                     }
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_A:
-                if (down && event.isMetaPressed()) {
+                if (firstDown && event.isMetaPressed()) {
                     launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
-                            event.getDeviceId(),
-                            event.getEventTime(), AssistUtils.INVOCATION_TYPE_UNKNOWN);
-                    return key_consumed;
+                            deviceId, event.getEventTime(),
+                            AssistUtils.INVOCATION_TYPE_UNKNOWN);
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_ASSISTANT);
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_H:
@@ -3003,67 +3110,83 @@
                 }
                 break;
             case KeyEvent.KEYCODE_I:
-                if (down && event.isMetaPressed()) {
+                if (firstDown && event.isMetaPressed()) {
                     showSystemSettings();
-                    return key_consumed;
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_SYSTEM_SETTINGS);
+                    return true;
+                }
+                break;
+            case KeyEvent.KEYCODE_L:
+                if (firstDown && event.isMetaPressed()) {
+                    lockNow(null /* options */);
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LOCK_SCREEN);
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_N:
-                if (down && event.isMetaPressed()) {
+                if (firstDown && event.isMetaPressed()) {
                     if (event.isCtrlPressed()) {
                         sendSystemKeyToStatusBarAsync(event);
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.OPEN_NOTES);
                     } else {
                         toggleNotificationPanel();
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
                     }
-                    return key_consumed;
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_S:
-                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
-                    return key_consumed;
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TAKE_SCREENSHOT);
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_T:
-                if (down && event.isMetaPressed()) {
+                if (firstDown && event.isMetaPressed()) {
                     toggleTaskbar();
-                    return key_consumed;
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_TASKBAR);
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_UP:
-                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                     if (statusbar != null) {
                         statusbar.goToFullscreenFromSplit();
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                        return true;
                     }
-                    return key_consumed;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_LEFT:
-                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     enterStageSplitFromRunningApp(true /* leftOrTop */);
-                    return key_consumed;
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_RIGHT:
-                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     enterStageSplitFromRunningApp(false /* leftOrTop */);
-                    return key_consumed;
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_SLASH:
-                if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) {
+                if (firstDown && event.isMetaPressed() && !keyguardOn) {
                     toggleKeyboardShortcutsMenu(event.getDeviceId());
-                    return key_consumed;
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.OPEN_SHORTCUT_HELPER);
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_ASSIST:
                 Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing");
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_VOICE_ASSIST:
                 Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in"
                         + " interceptKeyBeforeQueueing");
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_VIDEO_APP_1:
             case KeyEvent.KEYCODE_VIDEO_APP_2:
             case KeyEvent.KEYCODE_VIDEO_APP_3:
@@ -3081,7 +3204,7 @@
             case KeyEvent.KEYCODE_DEMO_APP_3:
             case KeyEvent.KEYCODE_DEMO_APP_4:
                 Slog.wtf(TAG, "KEYCODE_APP_X should be handled in interceptKeyBeforeQueueing");
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_BRIGHTNESS_UP:
             case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
                 if (down) {
@@ -3118,23 +3241,31 @@
                             minLinearBrightness, maxLinearBrightness);
                     mDisplayManager.setBrightness(screenDisplayId, adjustedLinearBrightness);
 
-                    startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
-                            UserHandle.CURRENT_OR_SELF);
+                    Intent intent = new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+                    intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true);
+                    startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.getBrightnessEvent(keyCode));
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
                 if (down) {
                     mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId());
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.KEYBOARD_BACKLIGHT_DOWN);
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
                 if (down) {
                     mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId());
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.KEYBOARD_BACKLIGHT_UP);
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
                 // TODO: Add logic
-                return key_consumed;
+                if (!down) {
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.KEYBOARD_BACKLIGHT_TOGGLE);
+                }
+                return true;
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_MUTE:
@@ -3142,7 +3273,7 @@
                     // On TVs or when the configuration is enabled, volume keys never
                     // go to the foreground app.
                     dispatchDirectAudioEvent(event);
-                    return key_consumed;
+                    return true;
                 }
 
                 // If the device is in VR mode and keys are "internal" (e.g. on the side of the
@@ -3151,26 +3282,25 @@
                 if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) {
                     final InputDevice d = event.getDevice();
                     if (d != null && !d.isExternal()) {
-                        return key_consumed;
+                        return true;
                     }
                 }
                 break;
             case KeyEvent.KEYCODE_TAB:
-                if (down && event.isMetaPressed()) {
-                    if (!keyguardOn && isUserSetupComplete()) {
+                if (firstDown && !keyguardOn && isUserSetupComplete()) {
+                    if (event.isMetaPressed()) {
                         showRecentApps(false);
-                        return key_consumed;
-                    }
-                } else if (down && repeatCount == 0) {
-                    // Display task switcher for ALT-TAB.
-                    if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.RECENT_APPS);
+                        return true;
+                    } else if (mRecentAppsHeldModifiers == 0) {
                         final int shiftlessModifiers =
                                 event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
                         if (KeyEvent.metaStateHasModifiers(
                                 shiftlessModifiers, KeyEvent.META_ALT_ON)) {
                             mRecentAppsHeldModifiers = shiftlessModifiers;
                             showRecentApps(true);
-                            return key_consumed;
+                            logKeyboardSystemsEvent(event, KeyboardLogEvent.RECENT_APPS);
+                            return true;
                         }
                     }
                 }
@@ -3181,19 +3311,22 @@
                     Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.ALL_APPS);
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_NOTIFICATION:
                 if (!down) {
                     toggleNotificationPanel();
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL);
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_SEARCH:
-                if (down && repeatCount == 0 && !keyguardOn()) {
-                    switch(mSearchKeyBehavior) {
+                if (firstDown && !keyguardOn) {
+                    switch (mSearchKeyBehavior) {
                         case SEARCH_BEHAVIOR_TARGET_ACTIVITY: {
                             launchTargetSearchActivity();
-                            return key_consumed;
+                            logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_SEARCH);
+                            return true;
                         }
                         case SEARCH_BEHAVIOR_DEFAULT_SEARCH:
                         default:
@@ -3202,21 +3335,20 @@
                 }
                 break;
             case KeyEvent.KEYCODE_LANGUAGE_SWITCH:
-                if (down && repeatCount == 0) {
+                if (firstDown) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
                     sendSwitchKeyboardLayout(event, direction);
-                    return key_consumed;
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LANGUAGE_SWITCH);
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_SPACE:
                 // Handle keyboard layout switching. (META + SPACE)
-                if ((metaState & KeyEvent.META_META_MASK) == 0) {
-                    return key_not_consumed;
-                }
-                if (down && repeatCount == 0) {
+                if (firstDown && event.isMetaPressed()) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
                     sendSwitchKeyboardLayout(event, direction);
-                    return key_consumed;
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LANGUAGE_SWITCH);
+                    return true;
                 }
                 break;
             case KeyEvent.KEYCODE_META_LEFT:
@@ -3234,14 +3366,16 @@
                     if (mPendingCapsLockToggle) {
                         mInputManagerInternal.toggleCapsLock(event.getDeviceId());
                         mPendingCapsLockToggle = false;
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_CAPS_LOCK);
                     } else if (mPendingMetaAction) {
                         if (!canceled) {
                             launchAllAppsViaA11y();
+                            logKeyboardSystemsEvent(event, KeyboardLogEvent.ACCESSIBILITY_ALL_APPS);
                         }
                         mPendingMetaAction = false;
                     }
                 }
-                return key_consumed;
+                return true;
             case KeyEvent.KEYCODE_ALT_LEFT:
             case KeyEvent.KEYCODE_ALT_RIGHT:
                 if (down) {
@@ -3257,41 +3391,41 @@
                             && (metaState & mRecentAppsHeldModifiers) == 0) {
                         mRecentAppsHeldModifiers = 0;
                         hideRecentApps(true, false);
-                        return key_consumed;
+                        return true;
                     }
 
                     // Toggle Caps Lock on META-ALT.
                     if (mPendingCapsLockToggle) {
                         mInputManagerInternal.toggleCapsLock(event.getDeviceId());
                         mPendingCapsLockToggle = false;
-                        return key_consumed;
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_CAPS_LOCK);
+                        return true;
                     }
                 }
                 break;
+            case KeyEvent.KEYCODE_CAPS_LOCK:
+                if (!down) {
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.TOGGLE_CAPS_LOCK);
+                }
+                break;
             case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY:
             case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY:
             case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
             case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL:
                 Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in"
                         + " interceptKeyBeforeQueueing");
-                return key_consumed;
+                return true;
         }
-
         if (isValidGlobalKey(keyCode)
                 && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
-            return key_consumed;
+            return true;
         }
 
         // Reserve all the META modifier combos for system behavior
-        if ((metaState & KeyEvent.META_META_ON) != 0) {
-            return key_consumed;
-        }
-
-        // Let the application handle the key.
-        return key_not_consumed;
+        return (metaState & KeyEvent.META_META_ON) != 0;
     }
 
-    private int handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
+    private boolean handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
         // First we always handle the home key here, so applications
         // can never break it, although if keyguard is on, we do let
         // it handle it, because that gives us the correct 5 second
@@ -4117,6 +4251,7 @@
         // Handle special keys.
         switch (keyCode) {
             case KeyEvent.KEYCODE_BACK: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.BACK);
                 if (down) {
                     mBackKeyHandled = false;
                 } else {
@@ -4134,6 +4269,8 @@
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
+                logKeyboardSystemsEventOnActionDown(event,
+                        KeyboardLogEvent.getVolumeEvent(keyCode));
                 if (down) {
                     sendSystemKeyToStatusBarAsync(event);
 
@@ -4234,6 +4371,7 @@
             }
 
             case KeyEvent.KEYCODE_TV_POWER: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.TOGGLE_POWER);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false; // wake-up will be handled separately
                 if (down && hdmiControlManager != null) {
@@ -4243,6 +4381,7 @@
             }
 
             case KeyEvent.KEYCODE_POWER: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.TOGGLE_POWER);
                 EventLogTags.writeInterceptPower(
                         KeyEvent.actionToString(event.getAction()),
                         mPowerKeyHandled ? 1 : 0,
@@ -4265,12 +4404,14 @@
             case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT:
                 // fall through
             case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.SYSTEM_NAVIGATION);
                 result &= ~ACTION_PASS_TO_USER;
                 interceptSystemNavigationKey(event);
                 break;
             }
 
             case KeyEvent.KEYCODE_SLEEP: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.SLEEP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false;
                 if (!mPowerManager.isInteractive()) {
@@ -4285,6 +4426,7 @@
             }
 
             case KeyEvent.KEYCODE_SOFT_SLEEP: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.SLEEP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false;
                 if (!down) {
@@ -4294,6 +4436,7 @@
             }
 
             case KeyEvent.KEYCODE_WAKEUP: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.WAKEUP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = true;
                 break;
@@ -4302,6 +4445,7 @@
             case KeyEvent.KEYCODE_MUTE:
                 result &= ~ACTION_PASS_TO_USER;
                 if (down && event.getRepeatCount() == 0) {
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.SYSTEM_MUTE);
                     toggleMicrophoneMuteFromKey();
                 }
                 break;
@@ -4316,6 +4460,7 @@
             case KeyEvent.KEYCODE_MEDIA_RECORD:
             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
             case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
+                logKeyboardSystemsEventOnActionUp(event, KeyboardLogEvent.MEDIA_KEY);
                 if (MediaSessionLegacyHelper.getHelper(mContext).isGlobalPriorityActive()) {
                     // If the global session is active pass all media keys to it
                     // instead of the active window.
@@ -4360,6 +4505,7 @@
                             0 /* unused */, event.getEventTime() /* eventTime */);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_ASSISTANT);
                 }
                 result &= ~ACTION_PASS_TO_USER;
                 break;
@@ -4370,6 +4516,7 @@
                     Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.LAUNCH_VOICE_ASSISTANT);
                 }
                 result &= ~ACTION_PASS_TO_USER;
                 break;
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 9c3b38a..b999bbb3 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -314,7 +314,9 @@
             if (eventTime < mLastDownTime + mActiveRule.getVeryLongPressTimeoutMs()) {
                 mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
             } else {
-                mHandledByLongPress = mActiveRule.supportVeryLongPress();
+                // If long press or very long press (~3.5s) had been handled, we should skip the
+                // short press behavior.
+                mHandledByLongPress |= mActiveRule.supportVeryLongPress();
             }
         }
 
diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING
index cf1bfc3..fbfe291 100644
--- a/services/core/java/com/android/server/power/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/TEST_MAPPING
@@ -20,6 +20,7 @@
       "name": "FrameworksServicesTests",
       "options": [
         {"include-filter": "com.android.server.power"},
+        {"exclude-filter": "com.android.server.power.BatteryStatsTests"},
         {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"}
       ]
@@ -38,7 +39,8 @@
     {
       "name": "FrameworksServicesTests",
       "options": [
-        {"include-filter": "com.android.server.power"}
+        {"include-filter": "com.android.server.power"},
+        {"exclude-filter": "com.android.server.power.BatteryStatsTests"}
       ]
     }
   ]
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index efd8b6d..a512331 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -140,6 +140,20 @@
     boolean showShutdownUi(boolean isReboot, String requestString);
 
     /**
+     * Notify system UI the immersive prompt should be dismissed as confirmed, and the confirmed
+     * status should be saved without user clicking on the button. This could happen when a user
+     * swipe on the edge with the confirmation prompt showing.
+     */
+    void confirmImmersivePrompt();
+
+    /**
+     * Notify System UI that the system get into or exit immersive mode.
+     * @param rootDisplayAreaId The changed display area Id.
+     * @param isImmersiveMode {@code true} if the display area get into immersive mode.
+     */
+    void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode);
+
+    /**
      * Show a rotation suggestion that a user may approve to rotate the screen.
      *
      * @param rotation rotation suggestion
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 044d30b..40e9c13 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -27,6 +27,7 @@
 import static android.app.StatusBarManager.SessionFlags;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
 
 import android.Manifest;
@@ -346,9 +347,10 @@
 
         @Override
         public void showScreenPinningRequest(int taskId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showScreenPinningRequest(taskId);
+                    bar.showScreenPinningRequest(taskId);
                 } catch (RemoteException e) {
                 }
             }
@@ -356,9 +358,10 @@
 
         @Override
         public void showAssistDisclosure() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showAssistDisclosure();
+                    bar.showAssistDisclosure();
                 } catch (RemoteException e) {
                 }
             }
@@ -366,9 +369,10 @@
 
         @Override
         public void startAssist(Bundle args) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.startAssist(args);
+                    bar.startAssist(args);
                 } catch (RemoteException e) {
                 }
             }
@@ -376,9 +380,10 @@
 
         @Override
         public void onCameraLaunchGestureDetected(int source) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.onCameraLaunchGestureDetected(source);
+                    bar.onCameraLaunchGestureDetected(source);
                 } catch (RemoteException e) {
                 }
             }
@@ -392,9 +397,10 @@
         @Override
         public void onEmergencyActionLaunchGestureDetected() {
             if (SPEW) Slog.d(TAG, "Launching emergency action");
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.onEmergencyActionLaunchGestureDetected();
+                    bar.onEmergencyActionLaunchGestureDetected();
                 } catch (RemoteException e) {
                     if (SPEW) Slog.d(TAG, "Failed to launch emergency action");
                 }
@@ -409,9 +415,10 @@
         @Override
         public void toggleSplitScreen() {
             enforceStatusBarService();
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.toggleSplitScreen();
+                    bar.toggleSplitScreen();
                 } catch (RemoteException ex) {}
             }
         }
@@ -419,27 +426,30 @@
         @Override
         public void appTransitionFinished(int displayId) {
             enforceStatusBarService();
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.appTransitionFinished(displayId);
+                    bar.appTransitionFinished(displayId);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void toggleTaskbar() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.toggleTaskbar();
+                    bar.toggleTaskbar();
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void toggleRecentApps() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.toggleRecentApps();
+                    bar.toggleRecentApps();
                 } catch (RemoteException ex) {}
             }
         }
@@ -453,45 +463,50 @@
 
         @Override
         public void preloadRecentApps() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.preloadRecentApps();
+                    bar.preloadRecentApps();
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void cancelPreloadRecentApps() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.cancelPreloadRecentApps();
+                    bar.cancelPreloadRecentApps();
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void showRecentApps(boolean triggeredFromAltTab) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showRecentApps(triggeredFromAltTab);
+                    bar.showRecentApps(triggeredFromAltTab);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.hideRecentApps(triggeredFromAltTab, triggeredFromHomeKey);
+                    bar.hideRecentApps(triggeredFromAltTab, triggeredFromHomeKey);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void collapsePanels() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.animateCollapsePanels();
+                    bar.animateCollapsePanels();
                 } catch (RemoteException ex) {
                 }
             }
@@ -499,18 +514,20 @@
 
         @Override
         public void dismissKeyboardShortcutsMenu() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.dismissKeyboardShortcutsMenu();
+                    bar.dismissKeyboardShortcutsMenu();
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void toggleKeyboardShortcutsMenu(int deviceId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.toggleKeyboardShortcutsMenu(deviceId);
+                    bar.toggleKeyboardShortcutsMenu(deviceId);
                 } catch (RemoteException ex) {}
             }
         }
@@ -536,9 +553,10 @@
 
         @Override
         public void showChargingAnimation(int batteryLevel) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showWirelessChargingAnimation(batteryLevel);
+                    bar.showWirelessChargingAnimation(batteryLevel);
                 } catch (RemoteException ex){
                 }
             }
@@ -546,7 +564,8 @@
 
         @Override
         public void showPictureInPictureMenu() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
                     mBar.showPictureInPictureMenu();
                 } catch (RemoteException ex) {}
@@ -555,27 +574,30 @@
 
         @Override
         public void setWindowState(int displayId, int window, int state) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.setWindowState(displayId, window, state);
+                    bar.setWindowState(displayId, window, state);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void appTransitionPending(int displayId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.appTransitionPending(displayId);
+                    bar.appTransitionPending(displayId);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void appTransitionCancelled(int displayId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.appTransitionCancelled(displayId);
+                    bar.appTransitionCancelled(displayId);
                 } catch (RemoteException ex) {}
             }
         }
@@ -583,9 +605,10 @@
         @Override
         public void appTransitionStarting(int displayId, long statusBarAnimationsStartTime,
                 long statusBarAnimationsDuration) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.appTransitionStarting(
+                    bar.appTransitionStarting(
                             displayId, statusBarAnimationsStartTime, statusBarAnimationsDuration);
                 } catch (RemoteException ex) {}
             }
@@ -593,9 +616,10 @@
 
         @Override
         public void setTopAppHidesStatusBar(boolean hidesStatusBar) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.setTopAppHidesStatusBar(hidesStatusBar);
+                    bar.setTopAppHidesStatusBar(hidesStatusBar);
                 } catch (RemoteException ex) {}
             }
         }
@@ -605,15 +629,41 @@
             if (!mContext.getResources().getBoolean(R.bool.config_showSysuiShutdown)) {
                 return false;
             }
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showShutdownUi(isReboot, reason);
+                    bar.showShutdownUi(isReboot, reason);
                     return true;
                 } catch (RemoteException ex) {}
             }
             return false;
         }
 
+        @Override
+        public void confirmImmersivePrompt() {
+            if (mBar == null) {
+                return;
+            }
+            try {
+                mBar.confirmImmersivePrompt();
+            } catch (RemoteException ex) {
+            }
+        }
+
+        @Override
+        public void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) {
+            if (mBar == null) {
+                return;
+            }
+            if (!CLIENT_TRANSIENT) {
+                // Only call from here when the client transient is not enabled.
+                try {
+                    mBar.immersiveModeChanged(rootDisplayAreaId, isImmersiveMode);
+                } catch (RemoteException ex) {
+                }
+            }
+        }
+
         // TODO(b/118592525): support it per display if necessary.
         @Override
         public void onProposedRotationChanged(int rotation, boolean isValid) {
@@ -626,18 +676,20 @@
 
         @Override
         public void onDisplayReady(int displayId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.onDisplayReady(displayId);
+                    bar.onDisplayReady(displayId);
                 } catch (RemoteException ex) {}
             }
         }
 
         @Override
         public void onRecentsAnimationStateChanged(boolean running) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.onRecentsAnimationStateChanged(running);
+                    bar.onRecentsAnimationStateChanged(running);
                 } catch (RemoteException ex) {}
             }
 
@@ -651,9 +703,10 @@
             getUiState(displayId).setBarAttributes(appearance, appearanceRegions,
                     navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                     letterboxDetails);
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
+                    bar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
                             navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                             letterboxDetails);
                 } catch (RemoteException ex) { }
@@ -664,9 +717,10 @@
         public void showTransient(int displayId, @InsetsType int types,
                 boolean isGestureOnSystemBar) {
             getUiState(displayId).showTransient(types);
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showTransient(displayId, types, isGestureOnSystemBar);
+                    bar.showTransient(displayId, types, isGestureOnSystemBar);
                 } catch (RemoteException ex) { }
             }
         }
@@ -674,9 +728,10 @@
         @Override
         public void abortTransient(int displayId, @InsetsType int types) {
             getUiState(displayId).clearTransient(types);
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.abortTransient(displayId, types);
+                    bar.abortTransient(displayId, types);
                 } catch (RemoteException ex) { }
             }
         }
@@ -685,9 +740,10 @@
         public void showToast(int uid, String packageName, IBinder token, CharSequence text,
                 IBinder windowToken, int duration,
                 @Nullable ITransientNotificationCallback callback, int displayId) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showToast(uid, packageName, token, text, windowToken, duration, callback,
+                    bar.showToast(uid, packageName, token, text, windowToken, duration, callback,
                             displayId);
                 } catch (RemoteException ex) { }
             }
@@ -695,18 +751,20 @@
 
         @Override
         public void hideToast(String packageName, IBinder token) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.hideToast(packageName, token);
+                    bar.hideToast(packageName, token);
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
         public boolean requestWindowMagnificationConnection(boolean request) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.requestWindowMagnificationConnection(request);
+                    bar.requestWindowMagnificationConnection(request);
                     return true;
                 } catch (RemoteException ex) { }
             }
@@ -715,9 +773,10 @@
 
         @Override
         public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.setNavigationBarLumaSamplingEnabled(displayId, enable);
+                    bar.setNavigationBarLumaSamplingEnabled(displayId, enable);
                 } catch (RemoteException ex) { }
             }
         }
@@ -727,45 +786,50 @@
             synchronized (mLock) {
                 mUdfpsRefreshRateRequestCallback = callback;
             }
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.setUdfpsRefreshRateCallback(callback);
+                    bar.setUdfpsRefreshRateCallback(callback);
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
         public void showRearDisplayDialog(int currentBaseState) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showRearDisplayDialog(currentBaseState);
+                    bar.showRearDisplayDialog(currentBaseState);
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
         public void goToFullscreenFromSplit() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.goToFullscreenFromSplit();
+                    bar.goToFullscreenFromSplit();
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
         public void enterStageSplitFromRunningApp(boolean leftOrTop) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.enterStageSplitFromRunningApp(leftOrTop);
+                    bar.enterStageSplitFromRunningApp(leftOrTop);
                 } catch (RemoteException ex) { }
             }
         }
 
         @Override
         public void showMediaOutputSwitcher(String packageName) {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showMediaOutputSwitcher(packageName);
+                    bar.showMediaOutputSwitcher(packageName);
                 } catch (RemoteException ex) {
                 }
             }
@@ -788,9 +852,10 @@
 
         @Override
         public void showGlobalActions() {
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.showGlobalActionsMenu();
+                    bar.showGlobalActionsMenu();
                 } catch (RemoteException ex) {}
             }
         }
@@ -1113,9 +1178,10 @@
         if (!state.disableEquals(net1, net2)) {
             state.setDisabled(net1, net2);
             mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.disable(displayId, net1, net2);
+                    bar.disable(displayId, net1, net2);
                 } catch (RemoteException ex) {
                 }
             }
@@ -1173,9 +1239,10 @@
             //Slog.d(TAG, "setIcon slot=" + slot + " index=" + index + " icon=" + icon);
             mIcons.put(slot, icon);
 
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.setIcon(slot, icon);
+                    bar.setIcon(slot, icon);
                 } catch (RemoteException ex) {
                 }
             }
@@ -1194,9 +1261,10 @@
             if (icon.visible != visibility) {
                 icon.visible = visibility;
 
-                if (mBar != null) {
+                IStatusBar bar = mBar;
+                if (bar != null) {
                     try {
-                        mBar.setIcon(slot, icon);
+                        bar.setIcon(slot, icon);
                     } catch (RemoteException ex) {
                     }
                 }
@@ -1211,9 +1279,10 @@
         synchronized (mIcons) {
             mIcons.remove(slot);
 
-            if (mBar != null) {
+            IStatusBar bar = mBar;
+            if (bar != null) {
                 try {
-                    mBar.removeIcon(slot);
+                    bar.removeIcon(slot);
                 } catch (RemoteException ex) {
                 }
             }
@@ -1815,9 +1884,10 @@
     @Override
     public void showInattentiveSleepWarning() {
         enforceStatusBarService();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.showInattentiveSleepWarning();
+                bar.showInattentiveSleepWarning();
             } catch (RemoteException ex) {
             }
         }
@@ -1826,9 +1896,10 @@
     @Override
     public void dismissInattentiveSleepWarning(boolean animated) {
         enforceStatusBarService();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.dismissInattentiveSleepWarning(animated);
+                bar.dismissInattentiveSleepWarning(animated);
             } catch (RemoteException ex) {
             }
         }
@@ -1837,9 +1908,10 @@
     @Override
     public void suppressAmbientDisplay(boolean suppress) {
         enforceStatusBarService();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.suppressAmbientDisplay(suppress);
+                bar.suppressAmbientDisplay(suppress);
             } catch (RemoteException ex) {
             }
         }
@@ -1913,9 +1985,10 @@
                 }
             }
         }
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.requestTileServiceListeningState(componentName);
+                bar.requestTileServiceListeningState(componentName);
             } catch (RemoteException e) {
                 Slog.e(TAG, "requestTileServiceListeningState", e);
             }
@@ -2028,9 +2101,10 @@
 
         CharSequence appName = r.serviceInfo.applicationInfo
                 .loadLabel(mContext.getPackageManager());
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.requestAddTile(componentName, appName, label, icon, proxyCallback);
+                bar.requestAddTile(componentName, appName, label, icon, proxyCallback);
                 return;
             } catch (RemoteException e) {
                 Slog.e(TAG, "requestAddTile", e);
@@ -2052,9 +2126,10 @@
 
     private void cancelRequestAddTileInternal(String packageName) {
         clearTileAddRequest(packageName);
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.cancelRequestAddTile(packageName);
+                bar.cancelRequestAddTile(packageName);
             } catch (RemoteException e) {
                 Slog.e(TAG, "requestAddTile", e);
             }
@@ -2183,9 +2258,10 @@
             @Nullable IUndoMediaTransferCallback undoCallback
     ) {
         enforceMediaContentControl();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.updateMediaTapToTransferSenderDisplay(displayState, routeInfo, undoCallback);
+                bar.updateMediaTapToTransferSenderDisplay(displayState, routeInfo, undoCallback);
             } catch (RemoteException e) {
                 Slog.e(TAG, "updateMediaTapToTransferSenderDisplay", e);
             }
@@ -2206,9 +2282,10 @@
             @Nullable Icon appIcon,
             @Nullable CharSequence appName) {
         enforceMediaContentControl();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.updateMediaTapToTransferReceiverDisplay(
+                bar.updateMediaTapToTransferReceiverDisplay(
                         displayState, routeInfo, appIcon, appName);
             } catch (RemoteException e) {
                 Slog.e(TAG, "updateMediaTapToTransferReceiverDisplay", e);
@@ -2232,9 +2309,10 @@
             @NonNull INearbyMediaDevicesProvider provider
     ) {
         enforceMediaContentControl();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.registerNearbyMediaDevicesProvider(provider);
+                bar.registerNearbyMediaDevicesProvider(provider);
             } catch (RemoteException e) {
                 Slog.e(TAG, "registerNearbyMediaDevicesProvider", e);
             }
@@ -2257,9 +2335,10 @@
             @NonNull INearbyMediaDevicesProvider provider
     ) {
         enforceMediaContentControl();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.unregisterNearbyMediaDevicesProvider(provider);
+                bar.unregisterNearbyMediaDevicesProvider(provider);
             } catch (RemoteException e) {
                 Slog.e(TAG, "unregisterNearbyMediaDevicesProvider", e);
             }
@@ -2270,9 +2349,10 @@
     @Override
     public void showRearDisplayDialog(int currentState) {
         enforceControlDeviceStatePermission();
-        if (mBar != null) {
+        IStatusBar bar = mBar;
+        if (bar != null) {
             try {
-                mBar.showRearDisplayDialog(currentState);
+                bar.showRearDisplayDialog(currentState);
             } catch (RemoteException e) {
                 Slog.e(TAG, "showRearDisplayDialog", e);
             }
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index bab4886..bed69fc 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1401,7 +1401,7 @@
         for (int i = 0; i < mActiveAgents.size(); i++) {
             AgentInfo info = mActiveAgents.valueAt(i);
             if (info.userId == userId) {
-                if (info.agent.isTrustableOrWaitingForDowngrade()) {
+                if (info.agent.isManagingTrust()) {
                     return true;
                 }
             }
diff --git a/services/core/java/com/android/server/utils/AlarmQueue.java b/services/core/java/com/android/server/utils/AlarmQueue.java
index 09ba195..83605ae 100644
--- a/services/core/java/com/android/server/utils/AlarmQueue.java
+++ b/services/core/java/com/android/server/utils/AlarmQueue.java
@@ -151,6 +151,10 @@
     @GuardedBy("mLock")
     @ElapsedRealtimeLong
     private long mTriggerTimeElapsed = NOT_SCHEDULED;
+    /** The last time an alarm went off (ie. the last time {@link #onAlarm()} was called}). */
+    @GuardedBy("mLock")
+    @ElapsedRealtimeLong
+    private long mLastFireTimeElapsed;
 
     /**
      * @param alarmTag               The tag to use when scheduling the alarm with AlarmManager.
@@ -284,7 +288,7 @@
     /** Sets an alarm with {@link AlarmManager} for the earliest alarm in the queue after now. */
     @GuardedBy("mLock")
     private void setNextAlarmLocked() {
-        setNextAlarmLocked(mInjector.getElapsedRealtime());
+        setNextAlarmLocked(mLastFireTimeElapsed + mMinTimeBetweenAlarmsMs);
     }
 
     /**
@@ -334,6 +338,7 @@
         final ArraySet<K> expired = new ArraySet<>();
         synchronized (mLock) {
             final long nowElapsed = mInjector.getElapsedRealtime();
+            mLastFireTimeElapsed = nowElapsed;
             while (mAlarmPriorityQueue.size() > 0) {
                 final Pair<K, Long> alarm = mAlarmPriorityQueue.peek();
                 if (alarm.second <= nowElapsed) {
diff --git a/services/core/java/com/android/server/utils/FoldSettingWrapper.java b/services/core/java/com/android/server/utils/FoldSettingWrapper.java
new file mode 100644
index 0000000..97a1ac0
--- /dev/null
+++ b/services/core/java/com/android/server/utils/FoldSettingWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.utils;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+/**
+ * A wrapper class for the {@link Settings.System#STAY_AWAKE_ON_FOLD} setting.
+ *
+ * This class provides a convenient way to access the {@link Settings.System#STAY_AWAKE_ON_FOLD}
+ * setting for testing.
+ */
+public class FoldSettingWrapper {
+    private final ContentResolver mContentResolver;
+
+    public FoldSettingWrapper(ContentResolver contentResolver) {
+        mContentResolver = contentResolver;
+    }
+
+    /**
+     * Returns whether the device should remain awake after folding.
+     */
+    public boolean shouldStayAwakeOnFold() {
+        try {
+            return (Settings.System.getIntForUser(
+                    mContentResolver,
+                    Settings.System.STAY_AWAKE_ON_FOLD,
+                    0) == 1);
+        } catch (Settings.SettingNotFoundException e) {
+            return false;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 49b125c..b773ade 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -81,7 +81,7 @@
         if (DEBUG) {
             Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
                     + Integer.toHexString(wallpaper.mWhich)
-                    + " to " + wallpaper.cropFile.getName()
+                    + " to " + wallpaper.getCropFile().getName()
                     + " crop=(" + cropHint.width() + 'x' + cropHint.height()
                     + ") dim=(" + wpData.mWidth + 'x' + wpData.mHeight + ')');
         }
@@ -89,7 +89,7 @@
         // Analyse the source; needed in multiple cases
         BitmapFactory.Options options = new BitmapFactory.Options();
         options.inJustDecodeBounds = true;
-        BitmapFactory.decodeFile(wallpaper.wallpaperFile.getAbsolutePath(), options);
+        BitmapFactory.decodeFile(wallpaper.getWallpaperFile().getAbsolutePath(), options);
         if (options.outWidth <= 0 || options.outHeight <= 0) {
             Slog.w(TAG, "Invalid wallpaper data");
             success = false;
@@ -154,11 +154,10 @@
                 //  may be we can try to remove this optimized way in the future,
                 //  that means, we will always go into the 'else' block.
 
-                success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile);
+                success = FileUtils.copyFile(wallpaper.getWallpaperFile(), wallpaper.getCropFile());
 
                 if (!success) {
-                    wallpaper.cropFile.delete();
-                    // TODO: fall back to default wallpaper in this case
+                    wallpaper.getCropFile().delete();
                 }
 
                 if (DEBUG) {
@@ -226,7 +225,7 @@
 
                     //Create a record file and will delete if ImageDecoder work well.
                     final String recordName =
-                            (wallpaper.wallpaperFile.getName().equals(WALLPAPER)
+                            (wallpaper.getWallpaperFile().getName().equals(WALLPAPER)
                                     ? RECORD_FILE : RECORD_LOCK_FILE);
                     final File record = new File(getWallpaperDir(wallpaper.userId), recordName);
                     record.createNewFile();
@@ -234,7 +233,7 @@
                             + ", record name =" + record.getName());
 
                     final ImageDecoder.Source srcData =
-                            ImageDecoder.createSource(wallpaper.wallpaperFile);
+                            ImageDecoder.createSource(wallpaper.getWallpaperFile());
                     final int sampleSize = scale;
                     Bitmap cropped = ImageDecoder.decodeBitmap(srcData, (decoder, info, src) -> {
                         decoder.setTargetSampleSize(sampleSize);
@@ -257,7 +256,7 @@
                                     + " h=" + finalCrop.getHeight());
                         }
 
-                        f = new FileOutputStream(wallpaper.cropFile);
+                        f = new FileOutputStream(wallpaper.getCropFile());
                         bos = new BufferedOutputStream(f, 32 * 1024);
                         finalCrop.compress(Bitmap.CompressFormat.PNG, 100, bos);
                         // don't rely on the implicit flush-at-close when noting success
@@ -277,11 +276,11 @@
 
         if (!success) {
             Slog.e(TAG, "Unable to apply new wallpaper");
-            wallpaper.cropFile.delete();
+            wallpaper.getCropFile().delete();
         }
 
-        if (wallpaper.cropFile.exists()) {
-            boolean didRestorecon = SELinux.restorecon(wallpaper.cropFile.getAbsoluteFile());
+        if (wallpaper.getCropFile().exists()) {
+            boolean didRestorecon = SELinux.restorecon(wallpaper.getCropFile().getAbsoluteFile());
             if (DEBUG) {
                 Slog.v(TAG, "restorecon() of crop file returned " + didRestorecon);
             }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index d87fca4..b0b66cf 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -40,10 +40,7 @@
  */
 class WallpaperData {
 
-    int userId;
-
-    final File wallpaperFile;   // source image
-    final File cropFile;        // eventual destination
+    final int userId;
 
     /**
      * True while the client is writing a new wallpaper
@@ -133,14 +130,13 @@
      */
     final Rect cropHint = new Rect(0, 0, 0, 0);
 
+    // map of which -> File
+    private final SparseArray<File> mWallpaperFiles = new SparseArray<>();
+    private final SparseArray<File> mCropFiles = new SparseArray<>();
+
     WallpaperData(int userId, @SetWallpaperFlags int wallpaperType) {
         this.userId = userId;
         this.mWhich = wallpaperType;
-        File wallpaperDir = getWallpaperDir(userId);
-        String wallpaperFileName = (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_ORIG : WALLPAPER;
-        String cropFileName = (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_CROP : WALLPAPER_CROP;
-        this.wallpaperFile = new File(wallpaperDir, wallpaperFileName);
-        this.cropFile = new File(wallpaperDir, cropFileName);
     }
 
     /**
@@ -154,8 +150,6 @@
      */
     WallpaperData(WallpaperData source) {
         this.userId = source.userId;
-        this.wallpaperFile = source.wallpaperFile;
-        this.cropFile = source.cropFile;
         this.wallpaperComponent = source.wallpaperComponent;
         this.mWhich = source.mWhich;
         this.wallpaperId = source.wallpaperId;
@@ -169,6 +163,25 @@
         }
     }
 
+    File getWallpaperFile() {
+        String fileName = mWhich == FLAG_LOCK ? WALLPAPER_LOCK_ORIG : WALLPAPER;
+        return getFile(mWallpaperFiles, fileName);
+    }
+
+    File getCropFile() {
+        String fileName = mWhich == FLAG_LOCK ? WALLPAPER_LOCK_CROP : WALLPAPER_CROP;
+        return getFile(mCropFiles, fileName);
+    }
+
+    private File getFile(SparseArray<File> map, String fileName) {
+        File result = map.get(mWhich);
+        if (result == null) {
+            result = new File(getWallpaperDir(userId), fileName);
+            map.put(userId, result);
+        }
+        return result;
+    }
+
     @Override
     public String toString() {
         StringBuilder out = new StringBuilder(defaultString(this));
@@ -177,7 +190,7 @@
         out.append(", which: ");
         out.append(mWhich);
         out.append(", file mod: ");
-        out.append(wallpaperFile != null ? wallpaperFile.lastModified() : "null");
+        out.append(getWallpaperFile() != null ? getWallpaperFile().lastModified() : "null");
         if (connection == null) {
             out.append(", no connection");
         } else {
@@ -202,10 +215,10 @@
 
     // Called during initialization of a given user's wallpaper bookkeeping
     boolean cropExists() {
-        return cropFile.exists();
+        return getCropFile().exists();
     }
 
     boolean sourceExists() {
-        return wallpaperFile.exists();
+        return getWallpaperFile().exists();
     }
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 3e498d7..c54e3bd 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -87,7 +87,7 @@
         mImageWallpaper = ComponentName.unflattenFromString(
                 context.getResources().getString(R.string.image_wallpaper_component));
         mIsLockscreenLiveWallpaperEnabled =
-                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", false);
+                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true);
     }
 
     private JournaledFile makeJournaledFile(int userId) {
@@ -542,12 +542,12 @@
                     }
 
                     res = r.openRawResource(resId);
-                    if (wallpaper.wallpaperFile.exists()) {
-                        wallpaper.wallpaperFile.delete();
-                        wallpaper.cropFile.delete();
+                    if (wallpaper.getWallpaperFile().exists()) {
+                        wallpaper.getWallpaperFile().delete();
+                        wallpaper.getCropFile().delete();
                     }
-                    fos = new FileOutputStream(wallpaper.wallpaperFile);
-                    cos = new FileOutputStream(wallpaper.cropFile);
+                    fos = new FileOutputStream(wallpaper.getWallpaperFile());
+                    cos = new FileOutputStream(wallpaper.getCropFile());
 
                     byte[] buffer = new byte[32768];
                     int amt;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index cd3d603..309a9c0 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -366,6 +366,7 @@
                     if (lockedWallpaper != null) {
                         detachWallpaperLocked(lockedWallpaper);
                     }
+                    clearWallpaperBitmaps(mWallpaper.userId, FLAG_LOCK);
                     mLockWallpaperMap.remove(wallpaper.userId);
                     notifyColorsWhich |= FLAG_LOCK;
                 }
@@ -669,8 +670,8 @@
             // Not having a wallpaperComponent means it's a lock screen wallpaper.
             final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent)
                     || wallpaper.wallpaperComponent == null;
-            if (imageWallpaper && wallpaper.cropFile != null && wallpaper.cropFile.exists()) {
-                cropFile = wallpaper.cropFile.getAbsolutePath();
+            if (imageWallpaper && wallpaper.getCropFile().exists()) {
+                cropFile = wallpaper.getCropFile().getAbsolutePath();
             } else if (imageWallpaper && !wallpaper.cropExists() && !wallpaper.sourceExists()) {
                 defaultImageWallpaper = true;
             }
@@ -1417,6 +1418,10 @@
                 }
             }
 
+            synchronized (mLock) {
+                saveSettingsLocked(mNewWallpaper.userId);
+            }
+
             if (DEBUG) {
                 Slog.v(TAG, "--- wallpaper changed --");
                 Slog.v(TAG, "new sysWp: " + mWallpaperMap.get(mCurrentUserId));
@@ -1588,7 +1593,7 @@
         mShuttingDown = false;
         mImageWallpaper = ComponentName.unflattenFromString(
                 context.getResources().getString(R.string.image_wallpaper_component));
-        mDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(context);
+        mDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(context);
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mIPackageManager = AppGlobals.getPackageManager();
@@ -1604,7 +1609,7 @@
                 mWallpaperCropper);
 
         mIsLockscreenLiveWallpaperEnabled =
-                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", false);
+                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true);
         mIsMultiCropEnabled =
                 SystemProperties.getBoolean("persist.wm.debug.wallpaper_multi_crop", false);
         LocalServices.addService(WallpaperManagerInternal.class, new LocalService());
@@ -1775,21 +1780,23 @@
             if (record.exists()) {
                 Slog.w(TAG, "User:" + userID + ", wallpaper tyep = " + type
                         + ", wallpaper fail detect!! reset to default wallpaper");
-                clearWallpaperData(userID, type);
+                clearWallpaperBitmaps(userID, type);
                 record.delete();
             }
         });
     }
 
-    private void clearWallpaperData(int userID, int wallpaperType) {
+    private void clearWallpaperBitmaps(int userID, int wallpaperType) {
         final WallpaperData wallpaper = new WallpaperData(userID, wallpaperType);
-        if (wallpaper.sourceExists()) {
-            wallpaper.wallpaperFile.delete();
-        }
-        if (wallpaper.cropExists()) {
-            wallpaper.cropFile.delete();
-        }
+        clearWallpaperBitmaps(wallpaper);
+    }
 
+    private boolean clearWallpaperBitmaps(WallpaperData wallpaper) {
+        boolean sourceExists = wallpaper.sourceExists();
+        boolean cropExists = wallpaper.cropExists();
+        if (sourceExists) wallpaper.getWallpaperFile().delete();
+        if (cropExists) wallpaper.getCropFile().delete();
+        return sourceExists || cropExists;
     }
 
     @Override
@@ -1966,10 +1973,7 @@
 
         // files from the previous static wallpaper may still be stored in memory.
         // delete them in order to show the default wallpaper.
-        if (wallpaper.wallpaperFile.exists()) {
-            wallpaper.wallpaperFile.delete();
-            wallpaper.cropFile.delete();
-        }
+        clearWallpaperBitmaps(wallpaper);
 
         bindWallpaperComponentLocked(mImageWallpaper, true, false, fallback, reply);
         if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = true;
@@ -1988,7 +1992,11 @@
 
         WallpaperData data = null;
         synchronized (mLock) {
-            clearWallpaperLocked(false, which, userId, null);
+            if (mIsLockscreenLiveWallpaperEnabled) {
+                clearWallpaperLocked(callingPackage, false, which, userId);
+            } else {
+                clearWallpaperLocked(false, which, userId, null);
+            }
 
             if (which == FLAG_LOCK) {
                 data = mLockWallpaperMap.get(userId);
@@ -2005,7 +2013,64 @@
         }
     }
 
-    void clearWallpaperLocked(boolean defaultFailed, int which, int userId, IRemoteCallback reply) {
+    private void clearWallpaperLocked(String callingPackage, boolean defaultFailed,
+            int which, int userId) {
+
+        // Might need to bring it in the first time to establish our rewrite
+        if (!mWallpaperMap.contains(userId)) {
+            loadSettingsLocked(userId, false, FLAG_LOCK | FLAG_SYSTEM);
+        }
+        final WallpaperData wallpaper = mWallpaperMap.get(userId);
+        final WallpaperData lockWallpaper = mLockWallpaperMap.get(userId);
+        if (which == FLAG_LOCK && lockWallpaper == null) {
+            // It's already gone; we're done.
+            if (DEBUG) {
+                Slog.i(TAG, "Lock wallpaper already cleared");
+            }
+            return;
+        }
+
+        RuntimeException e = null;
+        try {
+            if (userId != mCurrentUserId && !hasCrossUserPermission()) return;
+
+            final ComponentName component;
+            final int finalWhich;
+
+            if ((which & FLAG_LOCK) > 0 && lockWallpaper != null) {
+                clearWallpaperBitmaps(lockWallpaper);
+            }
+            if ((which & FLAG_SYSTEM) > 0) {
+                clearWallpaperBitmaps(wallpaper);
+            }
+
+            // lock only case: set the system wallpaper component to both screens
+            if (which == FLAG_LOCK) {
+                component = wallpaper.wallpaperComponent;
+                finalWhich = FLAG_LOCK | FLAG_SYSTEM;
+            } else {
+                component = defaultFailed ? mImageWallpaper : null;
+                finalWhich = which;
+            }
+
+            boolean success = withCleanCallingIdentity(() -> setWallpaperComponent(
+                    component, callingPackage, finalWhich, userId));
+            if (success) return;
+        } catch (IllegalArgumentException e1) {
+            e = e1;
+        }
+
+        // This can happen if the default wallpaper component doesn't
+        // exist. This should be a system configuration problem, but
+        // let's not let it crash the system and just live with no
+        // wallpaper.
+        Slog.e(TAG, "Default wallpaper component not found!", e);
+        withCleanCallingIdentity(() -> clearWallpaperComponentLocked(wallpaper));
+    }
+
+    // TODO(b/266818039) remove this version of the method
+    private void clearWallpaperLocked(boolean defaultFailed, int which, int userId,
+            IRemoteCallback reply) {
         if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
             throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to clear");
         }
@@ -2034,9 +2099,7 @@
 
         final long ident = Binder.clearCallingIdentity();
         try {
-            if (wallpaper.wallpaperFile.exists()) {
-                wallpaper.wallpaperFile.delete();
-                wallpaper.cropFile.delete();
+            if (clearWallpaperBitmaps(wallpaper)) {
                 if (which == FLAG_LOCK) {
                     mLockWallpaperMap.remove(userId);
                     final IWallpaperManagerCallback cb = mKeyguardListener;
@@ -2331,13 +2394,13 @@
                     wallpaper.callbacks.register(cb);
                 }
 
-                File fileToReturn = getCropped ? wallpaper.cropFile : wallpaper.wallpaperFile;
+                File result = getCropped ? wallpaper.getCropFile() : wallpaper.getWallpaperFile();
 
-                if (!fileToReturn.exists()) {
+                if (!result.exists()) {
                     return null;
                 }
 
-                return ParcelFileDescriptor.open(fileToReturn, MODE_READ_ONLY);
+                return ParcelFileDescriptor.open(result, MODE_READ_ONLY);
             } catch (FileNotFoundException e) {
                 /* Shouldn't happen as we check to see if the file exists */
                 Slog.w(TAG, "Error getting wallpaper", e);
@@ -2832,6 +2895,7 @@
         checkPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT);
         final long ident = Binder.clearCallingIdentity();
         try {
+            List<WallpaperData> pendingColorExtraction = new ArrayList<>();
             synchronized (mLock) {
                 WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
                 WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
@@ -2867,7 +2931,7 @@
                             // Need to extract colors again to re-calculate dark hints after
                             // applying dimming.
                             wp.mIsColorExtractedFromDim = true;
-                            notifyWallpaperColorsChanged(wp, wp.mWhich);
+                            pendingColorExtraction.add(wp);
                             changed = true;
                         }
                     }
@@ -2899,6 +2963,9 @@
                     }
                 }
             }
+            for (WallpaperData wp: pendingColorExtraction) {
+                notifyWallpaperColorsChanged(wp, wp.mWhich);
+            }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -3101,19 +3168,24 @@
 
         // Migrate the bitmap files outright; no need to copy
         try {
-            Os.rename(sysWP.wallpaperFile.getAbsolutePath(), lockWP.wallpaperFile.getAbsolutePath());
-            Os.rename(sysWP.cropFile.getAbsolutePath(), lockWP.cropFile.getAbsolutePath());
+            if (!mIsLockscreenLiveWallpaperEnabled || sysWP.getWallpaperFile().exists()) {
+                Os.rename(sysWP.getWallpaperFile().getAbsolutePath(),
+                        lockWP.getWallpaperFile().getAbsolutePath());
+            }
+            if (!mIsLockscreenLiveWallpaperEnabled || sysWP.getCropFile().exists()) {
+                Os.rename(sysWP.getCropFile().getAbsolutePath(),
+                        lockWP.getCropFile().getAbsolutePath());
+            }
             mLockWallpaperMap.put(userId, lockWP);
             if (mIsLockscreenLiveWallpaperEnabled) {
-                SELinux.restorecon(lockWP.wallpaperFile);
+                SELinux.restorecon(lockWP.getWallpaperFile());
                 mLastLockWallpaper = lockWP;
             }
             return true;
         } catch (ErrnoException e) {
             // can happen when migrating default wallpaper (which is not stored in wallpaperFile)
             Slog.w(TAG, "Couldn't migrate system wallpaper: " + e.getMessage());
-            lockWP.wallpaperFile.delete();
-            lockWP.cropFile.delete();
+            clearWallpaperBitmaps(lockWP);
             return false;
         }
     }
@@ -3130,11 +3202,11 @@
                         FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
                         -1, -1);
             }
-            ParcelFileDescriptor fd = ParcelFileDescriptor.open(wallpaper.wallpaperFile,
+            ParcelFileDescriptor fd = ParcelFileDescriptor.open(wallpaper.getWallpaperFile(),
                     MODE_CREATE|MODE_READ_WRITE|MODE_TRUNCATE);
-            if (!SELinux.restorecon(wallpaper.wallpaperFile)) {
+            if (!SELinux.restorecon(wallpaper.getWallpaperFile())) {
                 Slog.w(TAG, "restorecon failed for wallpaper file: " +
-                        wallpaper.wallpaperFile.getPath());
+                        wallpaper.getWallpaperFile().getPath());
                 return null;
             }
             wallpaper.name = name;
@@ -3145,7 +3217,7 @@
             // Nullify field to require new computation
             wallpaper.primaryColors = null;
             Slog.v(TAG, "updateWallpaperBitmapLocked() : id=" + wallpaper.wallpaperId
-                    + " name=" + name + " file=" + wallpaper.wallpaperFile.getName());
+                    + " name=" + name + " file=" + wallpaper.getWallpaperFile().getName());
             return fd;
         } catch (FileNotFoundException e) {
             Slog.w(TAG, "Error setting wallpaper", e);
@@ -3169,16 +3241,17 @@
     }
 
     @VisibleForTesting
-    void setWallpaperComponent(ComponentName name, String callingPackage,
+    boolean setWallpaperComponent(ComponentName name, String callingPackage,
             @SetWallpaperFlags int which, int userId) {
         if (mIsLockscreenLiveWallpaperEnabled) {
-            setWallpaperComponentInternal(name, callingPackage, which, userId);
+            return setWallpaperComponentInternal(name, callingPackage, which, userId);
         } else {
             setWallpaperComponentInternalLegacy(name, callingPackage, which, userId);
+            return true;
         }
     }
 
-    private void setWallpaperComponentInternal(ComponentName name, String callingPackage,
+    private boolean setWallpaperComponentInternal(ComponentName name, String callingPackage,
             @SetWallpaperFlags int which, int userIdIn) {
         if (DEBUG) {
             Slog.v(TAG, "Setting new live wallpaper: which=" + which + ", component: " + name);
@@ -3189,6 +3262,7 @@
         checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
 
         boolean shouldNotifyColors = false;
+        boolean bindSuccess;
         final WallpaperData newWallpaper;
 
         synchronized (mLock) {
@@ -3237,7 +3311,7 @@
                  */
                 boolean forceRebind = same && systemIsBoth && which == FLAG_SYSTEM;
 
-                boolean bindSuccess = bindWallpaperComponentLocked(name, /* force */
+                bindSuccess = bindWallpaperComponentLocked(name, /* force */
                         forceRebind, /* fromUser */ true, newWallpaper, callback);
                 if (bindSuccess) {
                     if (!same) {
@@ -3256,6 +3330,11 @@
                             });
                         }
                     }
+                    boolean lockBitmapCleared = false;
+                    if (!mImageWallpaper.equals(newWallpaper.wallpaperComponent)) {
+                        clearWallpaperBitmaps(newWallpaper);
+                        lockBitmapCleared = newWallpaper.mWhich == FLAG_LOCK;
+                    }
                     newWallpaper.wallpaperId = makeWallpaperIdLocked();
                     notifyCallbacksLocked(newWallpaper);
                     shouldNotifyColors = true;
@@ -3272,6 +3351,9 @@
                                 updateEngineFlags(newWallpaper);
                             }
                         }
+                        if (!lockBitmapCleared) {
+                            clearWallpaperBitmaps(newWallpaper.userId, FLAG_LOCK);
+                        }
                         mLockWallpaperMap.remove(newWallpaper.userId);
                     }
                 }
@@ -3284,6 +3366,7 @@
             notifyWallpaperColorsChanged(newWallpaper, which);
             notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM);
         }
+        return bindSuccess;
     }
 
     // TODO(b/266818039) Remove this method
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index f300113..b3ae2ee 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1280,45 +1280,53 @@
                 }
 
                 void drawIfNeeded(SurfaceControl.Transaction t) {
+                    // Drawing variables (alpha, dirty rect, and bounds) access is synchronized
+                    // using WindowManagerGlobalLock. Grab copies of these values before
+                    // drawing on the canvas so that drawing can be performed outside of the lock.
+                    int alpha;
+                    Rect drawingRect = null;
+                    Region drawingBounds = null;
                     synchronized (mService.mGlobalLock) {
                         if (!mInvalidated) {
                             return;
                         }
                         mInvalidated = false;
-                        if (mAlpha > 0) {
-                            Canvas canvas = null;
-                            try {
-                                // Empty dirty rectangle means unspecified.
-                                if (mDirtyRect.isEmpty()) {
-                                    mBounds.getBounds(mDirtyRect);
-                                }
-                                mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth);
-                                canvas = mSurface.lockCanvas(mDirtyRect);
-                                if (DEBUG_VIEWPORT_WINDOW) {
-                                    Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect);
-                                }
-                            } catch (IllegalArgumentException iae) {
-                                /* ignore */
-                            } catch (Surface.OutOfResourcesException oore) {
-                                /* ignore */
-                            }
-                            if (canvas == null) {
-                                return;
-                            }
-                            if (DEBUG_VIEWPORT_WINDOW) {
-                                Slog.i(LOG_TAG, "Bounds: " + mBounds);
-                            }
-                            canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
-                            mPaint.setAlpha(mAlpha);
-                            Path path = mBounds.getBoundaryPath();
-                            canvas.drawPath(path, mPaint);
 
-                            mSurface.unlockCanvasAndPost(canvas);
-                            t.show(mSurfaceControl);
-                        } else {
-                            t.hide(mSurfaceControl);
+                        alpha = mAlpha;
+                        if (alpha > 0) {
+                            drawingBounds = new Region(mBounds);
+                            // Empty dirty rectangle means unspecified.
+                            if (mDirtyRect.isEmpty()) {
+                                mBounds.getBounds(mDirtyRect);
+                            }
+                            mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth);
+                            drawingRect = new Rect(mDirtyRect);
+                            if (DEBUG_VIEWPORT_WINDOW) {
+                                Slog.i(LOG_TAG, "ViewportWindow bounds: " + mBounds);
+                                Slog.i(LOG_TAG, "ViewportWindow dirty rect: " + mDirtyRect);
+                            }
                         }
                     }
+
+                    // Draw without holding WindowManagerGlobalLock.
+                    if (alpha > 0) {
+                        Canvas canvas = null;
+                        try {
+                            canvas = mSurface.lockCanvas(drawingRect);
+                        } catch (IllegalArgumentException | OutOfResourcesException e) {
+                            /* ignore */
+                        }
+                        if (canvas == null) {
+                            return;
+                        }
+                        canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
+                        mPaint.setAlpha(alpha);
+                        canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
+                        mSurface.unlockCanvasAndPost(canvas);
+                        t.show(mSurfaceControl);
+                    } else {
+                        t.hide(mSurfaceControl);
+                    }
                 }
 
                 void releaseSurface() {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f6fa51e..d0f86c0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -108,7 +108,6 @@
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.Display.COLOR_MODE_DEFAULT;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
@@ -225,7 +224,11 @@
 import static com.android.server.wm.IdentifierProto.USER_ID;
 import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
 import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE;
+import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
@@ -2676,12 +2679,19 @@
     private boolean transferSplashScreenIfNeeded() {
         if (finishing || !mHandleExitSplashScreen || mStartingSurface == null
                 || mStartingWindow == null
-                || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH) {
+                || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH
+                // skip copy splash screen to client if it was resized
+                || (mStartingData != null && mStartingData.mResizedFromTransfer)) {
             return false;
         }
         if (isTransferringSplashScreen()) {
             return true;
         }
+        // Only do transfer after transaction has done when starting window exist.
+        if (mStartingData != null && mStartingData.mWaitForSyncTransactionCommit) {
+            mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT;
+            return true;
+        }
         requestCopySplashScreen();
         return isTransferringSplashScreen();
     }
@@ -2782,6 +2792,9 @@
             } else if (isEmbedded()) {
                 associateStartingWindowWithTaskIfNeeded();
             }
+            if (mTransitionController.isCollecting()) {
+                mStartingData.mTransitionId = mTransitionController.getCollectingTransitionId();
+            }
         }
     }
 
@@ -2839,11 +2852,14 @@
         if (mStartingData == null) {
             return;
         }
-        mStartingData.mWaitForSyncTransactionCommit = false;
-        if (mStartingData.mRemoveAfterTransaction) {
-            mStartingData.mRemoveAfterTransaction = false;
-            removeStartingWindowAnimation(mStartingData.mPrepareRemoveAnimation);
+        final StartingData lastData = mStartingData;
+        lastData.mWaitForSyncTransactionCommit = false;
+        if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_REMOVE_DIRECTLY) {
+            removeStartingWindowAnimation(lastData.mPrepareRemoveAnimation);
+        } else if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_COPY_TO_CLIENT) {
+            removeStartingWindow();
         }
+        lastData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
     }
 
     void removeStartingWindowAnimation(boolean prepareAnimation) {
@@ -2870,7 +2886,7 @@
         if (mStartingData != null) {
             if (mStartingData.mWaitForSyncTransactionCommit
                     || mTransitionController.inCollectingTransition(startingWindow)) {
-                mStartingData.mRemoveAfterTransaction = true;
+                mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY;
                 mStartingData.mPrepareRemoveAnimation = prepareAnimation;
                 return;
             }
@@ -4560,6 +4576,26 @@
         }
         task.forAllActivities(fromActivity -> {
             if (fromActivity == this) return true;
+            // The snapshot starting window could remove itself when receive resized request without
+            // redraw, so transfer it to a different size activity could only cause flicker.
+            // By schedule remove snapshot starting window, the remove process will happen when
+            // transition ready, transition ready means the app window is drawn.
+            final StartingData tmpStartingData = fromActivity.mStartingData;
+            if (tmpStartingData != null && tmpStartingData.mAssociatedTask == null
+                    && mTransitionController.isCollecting(fromActivity)
+                    && tmpStartingData instanceof SnapshotStartingData) {
+                final Rect fromBounds = fromActivity.getBounds();
+                final Rect myBounds = getBounds();
+                if (!fromBounds.equals(myBounds)) {
+                    // Mark as no animation, so these changes won't merge into playing transition.
+                    if (mTransitionController.inPlayingTransition(fromActivity)) {
+                        mTransitionController.setNoAnimation(this);
+                        mTransitionController.setNoAnimation(fromActivity);
+                    }
+                    fromActivity.removeStartingWindow();
+                    return true;
+                }
+            }
             return !fromActivity.isVisibleRequested() && transferStartingWindow(fromActivity);
         });
     }
@@ -4668,26 +4704,11 @@
     }
 
     /**
-     * @return Whether we are allowed to show non-starting windows at the moment. We disallow
-     *         showing windows while the transition animation is playing in case we have windows
-     *         that have wide-color-gamut color mode set to avoid jank in the middle of the
-     *         animation.
+     * @return Whether we are allowed to show non-starting windows at the moment.
      */
     boolean canShowWindows() {
-        final boolean drawn = mTransitionController.isShellTransitionsEnabled()
+        return mTransitionController.isShellTransitionsEnabled()
                 ? mSyncState != SYNC_STATE_WAITING_FOR_DRAW : allDrawn;
-        final boolean animating = mTransitionController.isShellTransitionsEnabled()
-                ? mTransitionController.inPlayingTransition(this)
-                : isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION);
-        return drawn && !(animating && hasNonDefaultColorWindow());
-    }
-
-    /**
-     * @return true if we have a window that has a non-default color mode set; false otherwise.
-     */
-    private boolean hasNonDefaultColorWindow() {
-        return forAllWindows(ws -> ws.mAttrs.getColorMode() != COLOR_MODE_DEFAULT,
-                true /* topToBottom */);
     }
 
     @Override
@@ -5640,13 +5661,6 @@
         final DisplayContent displayContent = getDisplayContent();
         if (!visible) {
             mImeInsetsFrozenUntilStartInput = true;
-            if (usingShellTransitions) {
-                final WindowState wallpaperTarget =
-                        displayContent.mWallpaperController.getWallpaperTarget();
-                if (wallpaperTarget != null && wallpaperTarget.mActivityRecord == this) {
-                    displayContent.mWallpaperController.hideWallpapers(wallpaperTarget);
-                }
-            }
         }
 
         if (!displayContent.mClosingApps.contains(this)
@@ -7619,7 +7633,8 @@
     @Override
     void prepareSurfaces() {
         final boolean show = isVisible() || isAnimating(PARENTS,
-                ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
+                ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
+                        | ANIMATION_TYPE_PREDICT_BACK);
 
         if (mSurfaceControl != null) {
             if (show && !mLastSurfaceShowing) {
@@ -9388,6 +9403,9 @@
         if (info.applicationInfo == null) {
             return info.getMinAspectRatio();
         }
+        if (mLetterboxUiController.shouldApplyUserMinAspectRatioOverride()) {
+            return mLetterboxUiController.getUserMinAspectRatio();
+        }
         if (!mLetterboxUiController.shouldOverrideMinAspectRatio()) {
             return info.getMinAspectRatio();
         }
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 105b2bb..148bf9b 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -16,16 +16,14 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.SnapshotController.ACTIVITY_CLOSE;
-import static com.android.server.wm.SnapshotController.ACTIVITY_OPEN;
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
-import static com.android.server.wm.SnapshotController.TASK_OPEN;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.os.Environment;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -35,7 +33,6 @@
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
-import com.android.server.wm.SnapshotController.TransitionState;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -62,12 +59,6 @@
     static final String SNAPSHOTS_DIRNAME = "activity_snapshots";
 
     /**
-     * The pending activities which should capture snapshot when process transition finish.
-     */
-    @VisibleForTesting
-    final ArraySet<ActivityRecord> mPendingCaptureActivity = new ArraySet<>();
-
-    /**
      * The pending activities which should remove snapshot from memory when process transition
      * finish.
      */
@@ -86,6 +77,10 @@
     @VisibleForTesting
     final ArraySet<ActivityRecord> mPendingLoadActivity = new ArraySet<>();
 
+    private final ArraySet<ActivityRecord> mOnBackPressedActivities = new ArraySet<>();
+
+    private final ArrayList<ActivityRecord> mTmpBelowActivities = new ArrayList<>();
+    private final ArrayList<WindowContainer> mTmpTransitionParticipants = new ArrayList<>();
     private final SnapshotPersistQueue mSnapshotPersistQueue;
     private final PersistInfoProvider mPersistInfoProvider;
     private final AppSnapshotLoader mSnapshotLoader;
@@ -117,20 +112,6 @@
         setSnapshotEnabled(snapshotEnabled);
     }
 
-    void systemReady() {
-        if (shouldDisableSnapshots()) {
-            return;
-        }
-        mService.mSnapshotController.registerTransitionStateConsumer(
-                ACTIVITY_OPEN, this::handleOpenActivityTransition);
-        mService.mSnapshotController.registerTransitionStateConsumer(
-                ACTIVITY_CLOSE, this::handleCloseActivityTransition);
-        mService.mSnapshotController.registerTransitionStateConsumer(
-                TASK_OPEN, this::handleOpenTaskTransition);
-        mService.mSnapshotController.registerTransitionStateConsumer(
-                TASK_CLOSE, this::handleCloseTaskTransition);
-    }
-
     @Override
     protected float initSnapshotScale() {
         final float config = mService.mContext.getResources().getFloat(
@@ -173,6 +154,7 @@
 
                         @Override
                         void write() {
+                            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cleanUpUserFiles");
                             final File file = mPersistInfoProvider.getDirectory(userId);
                             if (file.exists()) {
                                 final File[] contents = file.listFiles();
@@ -182,15 +164,30 @@
                                     }
                                 }
                             }
+                            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                         }
                     });
         }
     }
 
+    void addOnBackPressedActivity(ActivityRecord ar) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
+        mOnBackPressedActivities.add(ar);
+    }
+
+    void clearOnBackPressedActivities() {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
+        mOnBackPressedActivities.clear();
+    }
+
     /**
-     * Prepare to handle on transition start. Clear all temporary fields.
+     * Prepare to collect any change for snapshots processing. Clear all temporary fields.
      */
-    void preTransitionStart() {
+    void beginSnapshotProcess() {
         if (shouldDisableSnapshots()) {
             return;
         }
@@ -198,18 +195,22 @@
     }
 
     /**
-     * on transition start has notified, start process data.
+     * End collect any change for snapshots processing, start process data.
      */
-    void postTransitionStart() {
+    void endSnapshotProcess() {
         if (shouldDisableSnapshots()) {
             return;
         }
-        onCommitTransition();
+        for (int i = mOnBackPressedActivities.size() - 1; i >= 0; --i) {
+            handleActivityTransition(mOnBackPressedActivities.valueAt(i));
+        }
+        mOnBackPressedActivities.clear();
+        mTmpTransitionParticipants.clear();
+        postProcess();
     }
 
     @VisibleForTesting
     void resetTmpFields() {
-        mPendingCaptureActivity.clear();
         mPendingRemoveActivity.clear();
         mPendingDeleteActivity.clear();
         mPendingLoadActivity.clear();
@@ -218,31 +219,13 @@
     /**
      * Start process all pending activities for a transition.
      */
-    private void onCommitTransition() {
+    private void postProcess() {
         if (DEBUG) {
-            Slog.d(TAG, "ActivitySnapshotController#onCommitTransition result:"
-                    + " capture " + mPendingCaptureActivity
+            Slog.d(TAG, "ActivitySnapshotController#postProcess result:"
                     + " remove " + mPendingRemoveActivity
                     + " delete " + mPendingDeleteActivity
                     + " load " + mPendingLoadActivity);
         }
-        // task snapshots
-        for (int i = mPendingCaptureActivity.size() - 1; i >= 0; i--) {
-            recordSnapshot(mPendingCaptureActivity.valueAt(i));
-        }
-        // clear mTmpRemoveActivity from cache
-        for (int i = mPendingRemoveActivity.size() - 1; i >= 0; i--) {
-            final ActivityRecord ar = mPendingRemoveActivity.valueAt(i);
-            final int code = getSystemHashCode(ar);
-            mCache.onIdRemoved(code);
-        }
-        // clear snapshot on cache and delete files
-        for (int i = mPendingDeleteActivity.size() - 1; i >= 0; i--) {
-            final ActivityRecord ar = mPendingDeleteActivity.valueAt(i);
-            final int code = getSystemHashCode(ar);
-            mCache.onIdRemoved(code);
-            removeIfUserSavedFileExist(code, ar.mUserId);
-        }
         // load snapshot to cache
         for (int i = mPendingLoadActivity.size() - 1; i >= 0; i--) {
             final ActivityRecord ar = mPendingLoadActivity.valueAt(i);
@@ -258,6 +241,8 @@
                             new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider) {
                                 @Override
                                 void write() {
+                                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+                                            "load_activity_snapshot");
                                     final TaskSnapshot snapshot = mSnapshotLoader.loadTask(code,
                                             userId, false /* loadLowResolutionBitmap */);
                                     synchronized (mService.getWindowManagerLock()) {
@@ -265,16 +250,36 @@
                                             mCache.putSnapshot(ar, snapshot);
                                         }
                                     }
+                                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                                 }
                             });
                 }
             }
         }
+        // clear mTmpRemoveActivity from cache
+        for (int i = mPendingRemoveActivity.size() - 1; i >= 0; i--) {
+            final ActivityRecord ar = mPendingRemoveActivity.valueAt(i);
+            final int code = getSystemHashCode(ar);
+            mCache.onIdRemoved(code);
+        }
+        // clear snapshot on cache and delete files
+        for (int i = mPendingDeleteActivity.size() - 1; i >= 0; i--) {
+            final ActivityRecord ar = mPendingDeleteActivity.valueAt(i);
+            final int code = getSystemHashCode(ar);
+            mCache.onIdRemoved(code);
+            removeIfUserSavedFileExist(code, ar.mUserId);
+        }
         // don't keep any reference
         resetTmpFields();
     }
 
-    private void recordSnapshot(ActivityRecord activity) {
+    void recordSnapshot(ActivityRecord activity) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "ActivitySnapshotController#recordSnapshot " + activity);
+        }
         final TaskSnapshot snapshot = recordSnapshotInner(activity, false /* allowSnapshotHome */);
         if (snapshot != null) {
             final int code = getSystemHashCode(activity);
@@ -285,15 +290,20 @@
     /**
      * Called when the visibility of an app changes outside the regular app transition flow.
      */
-    void notifyAppVisibilityChanged(ActivityRecord appWindowToken, boolean visible) {
+    void notifyAppVisibilityChanged(ActivityRecord ar, boolean visible) {
         if (shouldDisableSnapshots()) {
             return;
         }
+        final Task task = ar.getTask();
+        if (task == null) {
+            return;
+        }
+        // Doesn't need to capture activity snapshot when it converts from translucent.
         if (!visible) {
             resetTmpFields();
-            addBelowTopActivityIfExist(appWindowToken.getTask(), mPendingRemoveActivity,
+            addBelowActivityIfExist(ar, mPendingRemoveActivity, false,
                     "remove-snapshot");
-            onCommitTransition();
+            postProcess();
         }
     }
 
@@ -301,65 +311,146 @@
         return System.identityHashCode(activity);
     }
 
-    void handleOpenActivityTransition(TransitionState<ActivityRecord> transitionState) {
-        ArraySet<ActivityRecord> participant = transitionState.getParticipant(false /* open */);
-        for (ActivityRecord ar : participant) {
-            mPendingCaptureActivity.add(ar);
-            // remove the snapshot for the one below close
-            final ActivityRecord below = ar.getTask().getActivityBelow(ar);
-            if (below != null) {
-                mPendingRemoveActivity.add(below);
+    @VisibleForTesting
+    void handleTransitionFinish(@NonNull ArrayList<WindowContainer> windows) {
+        mTmpTransitionParticipants.clear();
+        mTmpTransitionParticipants.addAll(windows);
+        for (int i = mTmpTransitionParticipants.size() - 1; i >= 0; --i) {
+            final WindowContainer next = mTmpTransitionParticipants.get(i);
+            if (next.asTask() != null) {
+                handleTaskTransition(next.asTask());
+            } else if (next.asTaskFragment() != null) {
+                final TaskFragment tf = next.asTaskFragment();
+                final ActivityRecord ar = tf.getTopMostActivity();
+                if (ar != null) {
+                    handleActivityTransition(ar);
+                }
+            } else if (next.asActivityRecord() != null) {
+                handleActivityTransition(next.asActivityRecord());
             }
         }
     }
 
-    void handleCloseActivityTransition(TransitionState<ActivityRecord> transitionState) {
-        ArraySet<ActivityRecord> participant = transitionState.getParticipant(true /* open */);
-        for (ActivityRecord ar : participant) {
+    private void handleActivityTransition(@NonNull ActivityRecord ar) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
+        if (ar.isVisibleRequested()) {
             mPendingDeleteActivity.add(ar);
             // load next one if exists.
-            final ActivityRecord below = ar.getTask().getActivityBelow(ar);
-            if (below != null) {
-                mPendingLoadActivity.add(below);
-            }
+            addBelowActivityIfExist(ar, mPendingLoadActivity, true, "load-snapshot");
+        } else {
+            // remove the snapshot for the one below close
+            addBelowActivityIfExist(ar, mPendingRemoveActivity, true, "remove-snapshot");
         }
     }
 
-    void handleCloseTaskTransition(TransitionState<Task> closeTaskTransitionRecord) {
-        ArraySet<Task> participant = closeTaskTransitionRecord.getParticipant(false /* open */);
-        for (Task close : participant) {
-            // this is close task transition
-            // remove the N - 1 from cache
-            addBelowTopActivityIfExist(close, mPendingRemoveActivity, "remove-snapshot");
+    private void handleTaskTransition(Task task) {
+        if (shouldDisableSnapshots()) {
+            return;
         }
-    }
-
-    void handleOpenTaskTransition(TransitionState<Task> openTaskTransitionRecord) {
-        ArraySet<Task> participant = openTaskTransitionRecord.getParticipant(true /* open */);
-        for (Task open : participant) {
-            // this is close task transition
-            // remove the N - 1 from cache
-            addBelowTopActivityIfExist(open, mPendingLoadActivity, "load-snapshot");
+        final ActivityRecord topActivity = task.getTopMostActivity();
+        if (topActivity == null) {
+            return;
+        }
+        if (task.isVisibleRequested()) {
+            // this is open task transition
+            // load the N - 1 to cache
+            addBelowActivityIfExist(topActivity, mPendingLoadActivity, true, "load-snapshot");
             // Move the activities to top of mSavedFilesInOrder, so when purge happen, there
             // will trim the persisted files from the most non-accessed.
-            adjustSavedFileOrder(open);
+            adjustSavedFileOrder(task);
+        } else {
+            // this is close task transition
+            // remove the N - 1 from cache
+            addBelowActivityIfExist(topActivity, mPendingRemoveActivity, true, "remove-snapshot");
         }
     }
 
-    // Add the top -1 activity to a set if it exists.
-    private void addBelowTopActivityIfExist(Task task, ArraySet<ActivityRecord> set,
-            String debugMessage) {
-        final ActivityRecord topActivity = task.getTopMostActivity();
-        if (topActivity != null) {
-            final ActivityRecord below = task.getActivityBelow(topActivity);
-            if (below != null) {
-                set.add(below);
-                if (DEBUG) {
-                    Slog.d(TAG, "ActivitySnapshotController#addBelowTopActivityIfExist "
-                            + below + " from " + debugMessage);
-                }
+    /**
+     * Add the top -1 activity to a set if it exists.
+     * @param inTransition true if the activity must participant in transition.
+     */
+    private void addBelowActivityIfExist(ActivityRecord currentActivity,
+            ArraySet<ActivityRecord> set, boolean inTransition, String debugMessage) {
+        getActivityBelow(currentActivity, inTransition, mTmpBelowActivities);
+        for (int i = mTmpBelowActivities.size() - 1; i >= 0; --i) {
+            set.add(mTmpBelowActivities.get(i));
+            if (DEBUG) {
+                Slog.d(TAG, "ActivitySnapshotController#addBelowTopActivityIfExist "
+                        + mTmpBelowActivities.get(i) + " from " + debugMessage);
             }
         }
+        mTmpBelowActivities.clear();
+    }
+
+    private void getActivityBelow(ActivityRecord currentActivity, boolean inTransition,
+            ArrayList<ActivityRecord> result) {
+        final Task currentTask = currentActivity.getTask();
+        if (currentTask == null) {
+            return;
+        }
+        final ActivityRecord initPrev = currentTask.getActivityBelow(currentActivity);
+        if (initPrev == null) {
+            return;
+        }
+        final TaskFragment currTF = currentActivity.getTaskFragment();
+        final TaskFragment prevTF = initPrev.getTaskFragment();
+        final TaskFragment prevAdjacentTF = prevTF != null
+                ? prevTF.getAdjacentTaskFragment() : null;
+        if (currTF == prevTF && currTF != null || prevAdjacentTF == null) {
+            // Current activity and previous one is in the same task fragment, or
+            // previous activity is not in a task fragment, or
+            // previous activity's task fragment doesn't adjacent to any others.
+            if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) {
+                result.add(initPrev);
+            }
+            return;
+        }
+
+        if (prevAdjacentTF == currTF) {
+            // previous activity A is adjacent to current activity B.
+            // Try to find anyone below previous activityA, which are C and D if exists.
+            // A | B
+            // C (| D)
+            getActivityBelow(initPrev, inTransition, result);
+        } else {
+            // previous activity C isn't adjacent to current activity A.
+            // A
+            // B | C
+            final Task prevAdjacentTask = prevAdjacentTF.getTask();
+            if (prevAdjacentTask == currentTask) {
+                final int currentIndex = currTF != null
+                        ? currentTask.mChildren.indexOf(currTF)
+                        : currentTask.mChildren.indexOf(currentActivity);
+                final int prevAdjacentIndex =
+                        prevAdjacentTask.mChildren.indexOf(prevAdjacentTF);
+                // prevAdjacentTF already above currentActivity
+                if (prevAdjacentIndex > currentIndex) {
+                    return;
+                }
+            }
+            if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) {
+                result.add(initPrev);
+            }
+            // prevAdjacentTF is adjacent to another one
+            final ActivityRecord prevAdjacentActivity = prevAdjacentTF.getTopMostActivity();
+            if (prevAdjacentActivity != null && (!inTransition
+                    || isInParticipant(prevAdjacentActivity, mTmpTransitionParticipants))) {
+                result.add(prevAdjacentActivity);
+            }
+        }
+    }
+
+    static boolean isInParticipant(ActivityRecord ar,
+            ArrayList<WindowContainer> transitionParticipants) {
+        for (int i = transitionParticipants.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = transitionParticipants.get(i);
+            if (ar == wc || ar.isDescendantOf(wc)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     private void adjustSavedFileOrder(Task nextTopTask) {
@@ -376,6 +467,9 @@
 
     @Override
     void onAppRemoved(ActivityRecord activity) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
         super.onAppRemoved(activity);
         final int code = getSystemHashCode(activity);
         removeIfUserSavedFileExist(code, activity.mUserId);
@@ -386,6 +480,9 @@
 
     @Override
     void onAppDied(ActivityRecord activity) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
         super.onAppDied(activity);
         final int code = getSystemHashCode(activity);
         removeIfUserSavedFileExist(code, activity.mUserId);
@@ -440,7 +537,7 @@
     private void removeIfUserSavedFileExist(int code, int userId) {
         final UserSavedFile usf = getUserFiles(userId).get(code);
         if (usf != null) {
-            mUserSavedFiles.remove(code);
+            mUserSavedFiles.get(userId).remove(code);
             mSavedFilesInOrder.remove(usf);
             mPersister.removeSnap(code, userId);
         }
@@ -490,11 +587,13 @@
                     new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider) {
                         @Override
                         void write() {
+                            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activity_remove_files");
                             for (int i = files.size() - 1; i >= 0; --i) {
                                 final UserSavedFile usf = files.get(i);
                                 mSnapshotPersistQueue.deleteSnapshot(
                                         usf.mFileId, usf.mUserId, mPersistInfoProvider);
                             }
+                            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                         }
                     });
         }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index a0a45e6..3c650e3 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -898,6 +898,11 @@
         }
         mLastStartReason = request.reason;
         mLastStartActivityTimeMs = System.currentTimeMillis();
+        // Reset the ActivityRecord#mCurrentLaunchCanTurnScreenOn state of last start activity in
+        // case the state is not yet consumed during rapid activity launch.
+        if (mLastStartActivityRecord != null) {
+            mLastStartActivityRecord.setCurrentLaunchCanTurnScreenOn(false);
+        }
         mLastStartActivityRecord = null;
 
         final IApplicationThread caller = request.caller;
@@ -1583,21 +1588,17 @@
                 newTransition = null;
             }
         }
-        if (isTransientLaunch) {
-            if (forceTransientTransition) {
-                transitionController.collect(mLastStartActivityRecord);
-                transitionController.collect(mPriorAboveTask);
-            }
-            // `started` isn't guaranteed to be the actual relevant activity, so we must wait
-            // until after we launched to identify the relevant activity.
+        if (forceTransientTransition) {
+            transitionController.collect(mLastStartActivityRecord);
+            transitionController.collect(mPriorAboveTask);
+            // If keyguard is active and occluded, the transient target won't be moved to front
+            // to be collected, so set transient again after it is collected.
             transitionController.setTransientLaunch(mLastStartActivityRecord, mPriorAboveTask);
-            if (forceTransientTransition) {
-                final DisplayContent dc = mLastStartActivityRecord.getDisplayContent();
-                // update wallpaper target to TransientHide
-                dc.mWallpaperController.adjustWallpaperWindows();
-                // execute transition because there is no change
-                transitionController.setReady(dc, true /* ready */);
-            }
+            final DisplayContent dc = mLastStartActivityRecord.getDisplayContent();
+            // update wallpaper target to TransientHide
+            dc.mWallpaperController.adjustWallpaperWindows();
+            // execute transition because there is no change
+            transitionController.setReady(dc, true /* ready */);
         }
         if (!userLeaving) {
             // no-user-leaving implies not entering PiP.
@@ -1686,7 +1687,9 @@
             }
             // When running transient transition, the transient launch target should keep on top.
             // So disallow the transient hide activity to move itself to front, e.g. trampoline.
-            if (!mAvoidMoveToFront && r.mTransitionController.isTransientHide(targetTask)) {
+            if (!mAvoidMoveToFront && (mService.mHomeProcess == null
+                    || mService.mHomeProcess.mUid != realCallingUid)
+                    && r.mTransitionController.isTransientHide(targetTask)) {
                 mAvoidMoveToFront = true;
             }
             mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask());
@@ -1705,6 +1708,7 @@
                     activity.destroyIfPossible("Removes redundant singleInstance");
                 }
             }
+            recordTransientLaunchIfNeeded(targetTaskTop);
             // Recycle the target task for this launch.
             startResult = recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants);
             if (startResult != START_SUCCESS) {
@@ -1736,6 +1740,9 @@
             addOrReparentStartingActivity(targetTask, "adding to task");
         }
 
+        // After activity is attached to task, but before actual start
+        recordTransientLaunchIfNeeded(mLastStartActivityRecord);
+
         if (!mAvoidMoveToFront && mDoResume) {
             mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask);
             if (!mTargetRootTask.isTopRootTaskInDisplayArea() && mService.isDreaming()
@@ -1832,6 +1839,14 @@
         return START_SUCCESS;
     }
 
+    private void recordTransientLaunchIfNeeded(ActivityRecord r) {
+        if (r == null || !mTransientLaunch) return;
+        final TransitionController controller = r.mTransitionController;
+        if (controller.isCollecting() && !controller.isTransientCollect(r)) {
+            controller.setTransientLaunch(r, mPriorAboveTask);
+        }
+    }
+
     /** Returns the leaf task where the target activity may be placed. */
     private Task computeTargetTask() {
         if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
@@ -2017,6 +2032,13 @@
         }
 
         // ASM rules have failed. Log why
+        return logAsmFailureAndCheckFeatureEnabled(r, newTask, targetTask, shouldBlockActivityStart,
+                taskToFront);
+    }
+
+    private boolean logAsmFailureAndCheckFeatureEnabled(ActivityRecord r, boolean newTask,
+            Task targetTask, boolean shouldBlockActivityStart, boolean taskToFront) {
+        // ASM rules have failed. Log why
         ActivityRecord targetTopActivity = targetTask == null ? null
                 : targetTask.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
 
@@ -2026,6 +2048,13 @@
                         ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_SAME_TASK
                         :  FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_DIFFERENT_TASK);
 
+        boolean blockActivityStartAndFeatureEnabled = ActivitySecurityModelFeatureFlags
+                .shouldRestrictActivitySwitch(mCallingUid)
+                && shouldBlockActivityStart;
+
+        String asmDebugInfo = getDebugInfoForActivitySecurity("Launch", r, targetTask,
+                targetTopActivity, blockActivityStartAndFeatureEnabled, /*taskToFront*/taskToFront);
+
         FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
                 /* caller_uid */
                 mSourceRecord != null ? mSourceRecord.getUid() : mCallingUid,
@@ -2054,24 +2083,21 @@
                 targetTask != null && mSourceRecord != null
                         && !targetTask.equals(mSourceRecord.getTask()) && targetTask.isVisible(),
                 /* bal_code */
-                mBalCode
+                mBalCode,
+                /* task_stack */
+                asmDebugInfo
         );
 
-        boolean blockActivityStartAndFeatureEnabled = ActivitySecurityModelFeatureFlags
-                    .shouldRestrictActivitySwitch(mCallingUid)
-                && shouldBlockActivityStart;
-
         String launchedFromPackageName = r.launchedFromPackage;
         if (ActivitySecurityModelFeatureFlags.shouldShowToast(mCallingUid)) {
             String toastText = ActivitySecurityModelFeatureFlags.DOC_LINK
                     + (blockActivityStartAndFeatureEnabled ? " blocked " : " would block ")
                     + getApplicationLabel(mService.mContext.getPackageManager(),
-                        launchedFromPackageName);
+                    launchedFromPackageName);
             UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
                     toastText, Toast.LENGTH_LONG).show());
 
-            logDebugInfoForActivitySecurity("Launch", r, targetTask, targetTopActivity,
-                    blockActivityStartAndFeatureEnabled, /* taskToFront */ taskToFront);
+            Slog.i(TAG, asmDebugInfo);
         }
 
         if (blockActivityStartAndFeatureEnabled) {
@@ -2089,7 +2115,7 @@
     }
 
     /** Only called when an activity launch may be blocked, which should happen very rarely */
-    private void logDebugInfoForActivitySecurity(String action, ActivityRecord r, Task targetTask,
+    private String getDebugInfoForActivitySecurity(String action, ActivityRecord r, Task targetTask,
             ActivityRecord targetTopActivity, boolean blockActivityStartAndFeatureEnabled,
             boolean taskToFront) {
         final String prefix = "[ASM] ";
@@ -2150,7 +2176,7 @@
         joiner.add(prefix + "BalCode: " + balCodeToString(mBalCode));
 
         joiner.add(prefix + "------ Activity Security " + action + " Debug Logging End ------");
-        Slog.i(TAG, joiner.toString());
+        return joiner.toString();
     }
 
     /**
@@ -2324,7 +2350,7 @@
                             + ActivitySecurityModelFeatureFlags.DOC_LINK,
                     Toast.LENGTH_LONG).show());
 
-            logDebugInfoForActivitySecurity("Clear Top", mStartActivity, targetTask, targetTaskTop,
+            getDebugInfoForActivitySecurity("Clear Top", mStartActivity, targetTask, targetTaskTop,
                     shouldBlockActivityStart, /* taskToFront */ true);
         }
     }
@@ -2928,15 +2954,6 @@
             }
         }
 
-        // If the matching task is already in the adjacent task of the launch target. Adjust to use
-        // the adjacent task as its launch target. So the existing task will be launched into the
-        // closer one and won't be reparent redundantly.
-        final Task adjacentTargetTask = mTargetRootTask.getAdjacentTask();
-        if (adjacentTargetTask != null && intentActivity.isDescendantOf(adjacentTargetTask)
-                && intentTask.isOnTop()) {
-            mTargetRootTask = adjacentTargetTask;
-        }
-
         // If the target task is not in the front, then we need to bring it to the front...
         // except...  well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have
         // the same behavior as if a new instance was being started, which means not bringing it
@@ -2977,7 +2994,9 @@
                     // should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
                     final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
                             && intentActivity.inMultiWindowMode()
-                            && intentActivity == mTargetRootTask.topRunningActivity();
+                            && intentActivity == mTargetRootTask.topRunningActivity()
+                            && !intentActivity.mTransitionController.isTransientHide(
+                                    mTargetRootTask);
                     // We only want to move to the front, if we aren't going to launch on a
                     // different root task. If we launch on a different root task, we will put the
                     // task on top there.
@@ -3092,7 +3111,18 @@
         } else {
             TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null;
             if (candidateTf == null) {
-                final ActivityRecord top = task.topRunningActivity(false /* focusableOnly */);
+                // Puts the activity on the top-most non-isolated navigation TF, unless the
+                // activity is launched from the same TF.
+                final TaskFragment sourceTaskFragment =
+                        mSourceRecord != null ? mSourceRecord.getTaskFragment() : null;
+                final ActivityRecord top = task.getActivity(r -> {
+                    if (!r.canBeTopRunning()) {
+                        return false;
+                    }
+                    final TaskFragment taskFragment = r.getTaskFragment();
+                    return !taskFragment.isIsolatedNav() || (sourceTaskFragment != null
+                            && sourceTaskFragment == taskFragment);
+                });
                 if (top != null) {
                     candidateTf = top.getTaskFragment();
                 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 32f1f42..a2547fd 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -23,6 +23,7 @@
 import android.app.AppProtoEnums;
 import android.app.BackgroundStartPrivileges;
 import android.app.IActivityManager;
+import android.app.IAppTask;
 import android.app.IApplicationThread;
 import android.app.ITaskStackListener;
 import android.app.ProfilerInfo;
@@ -308,6 +309,12 @@
     public abstract void notifyActiveDreamChanged(@Nullable ComponentName activeDreamComponent);
 
     /**
+     * Starts a dream activity in the DreamService's process.
+     */
+    public abstract IAppTask startDreamActivity(@NonNull Intent intent, int callingUid,
+            int callingPid);
+
+    /**
      * Set a uid that is allowed to bypass stopped app switches, launching an app
      * whenever it wants.
      *
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 47b51ac..fcf6587 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -546,6 +546,7 @@
      */
     private volatile long mLastStopAppSwitchesTime;
 
+    @GuardedBy("itself")
     private final List<AnrController> mAnrController = new ArrayList<>();
     IActivityController mController = null;
     boolean mControllerIsAMonkey = false;
@@ -733,7 +734,7 @@
     private boolean mShowDialogs = true;
 
     /** Set if we are shutting down the system, similar to sleeping. */
-    boolean mShuttingDown = false;
+    volatile boolean mShuttingDown;
 
     /**
      * We want to hold a wake lock while running a voice interaction session, since
@@ -1411,29 +1412,39 @@
 
             final long origId = Binder.clearCallingIdentity();
             // TODO(b/64750076): Check if calling pid should really be -1.
-            final int res = getActivityStartController()
-                    .obtainStarter(intent, "startNextMatchingActivity")
-                    .setCaller(r.app.getThread())
-                    .setResolvedType(r.resolvedType)
-                    .setActivityInfo(aInfo)
-                    .setResultTo(resultTo != null ? resultTo.token : null)
-                    .setResultWho(resultWho)
-                    .setRequestCode(requestCode)
-                    .setCallingPid(-1)
-                    .setCallingUid(r.launchedFromUid)
-                    .setCallingPackage(r.launchedFromPackage)
-                    .setCallingFeatureId(r.launchedFromFeatureId)
-                    .setRealCallingPid(-1)
-                    .setRealCallingUid(r.launchedFromUid)
-                    .setActivityOptions(options)
-                    .execute();
-            Binder.restoreCallingIdentity(origId);
+            try {
+                if (options == null) {
+                    options = new SafeActivityOptions(ActivityOptions.makeBasic());
+                }
 
-            r.finishing = wasFinishing;
-            if (res != ActivityManager.START_SUCCESS) {
-                return false;
+                // Fixes b/230492947
+                // Prevents background activity launch through #startNextMatchingActivity
+                // An activity going into the background could still go back to the foreground
+                // if the intent used matches both:
+                // - the activity in the background
+                // - a second activity.
+                options.getOptions(r).setAvoidMoveToFront();
+                final int res = getActivityStartController()
+                        .obtainStarter(intent, "startNextMatchingActivity")
+                        .setCaller(r.app.getThread())
+                        .setResolvedType(r.resolvedType)
+                        .setActivityInfo(aInfo)
+                        .setResultTo(resultTo != null ? resultTo.token : null)
+                        .setResultWho(resultWho)
+                        .setRequestCode(requestCode)
+                        .setCallingPid(-1)
+                        .setCallingUid(r.launchedFromUid)
+                        .setCallingPackage(r.launchedFromPackage)
+                        .setCallingFeatureId(r.launchedFromFeatureId)
+                        .setRealCallingPid(-1)
+                        .setRealCallingUid(r.launchedFromUid)
+                        .setActivityOptions(options)
+                        .execute();
+                r.finishing = wasFinishing;
+                return res == ActivityManager.START_SUCCESS;
+            } finally {
+                Binder.restoreCallingIdentity(origId);
             }
-            return true;
         }
     }
 
@@ -1456,23 +1467,8 @@
         return false;
     }
 
-    private void enforceCallerIsDream(String callerPackageName) {
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            if (!canLaunchDreamActivity(callerPackageName)) {
-                throw new SecurityException("The dream activity can be started only when the device"
-                        + " is dreaming and only by the active dream package.");
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
-    @Override
-    public boolean startDreamActivity(@NonNull Intent intent) {
-        assertPackageMatchesCallingUid(intent.getPackage());
-        enforceCallerIsDream(intent.getPackage());
-
+    private IAppTask startDreamActivityInternal(@NonNull Intent intent, int callingUid,
+            int callingPid) {
         final ActivityInfo a = new ActivityInfo();
         a.theme = com.android.internal.R.style.Theme_Dream;
         a.exported = true;
@@ -1490,7 +1486,7 @@
         options.setLaunchActivityType(ACTIVITY_TYPE_DREAM);
 
         synchronized (mGlobalLock) {
-            final WindowProcessController process = mProcessMap.getProcess(Binder.getCallingPid());
+            final WindowProcessController process = mProcessMap.getProcess(callingPid);
 
             a.packageName = process.mInfo.packageName;
             a.applicationInfo = process.mInfo;
@@ -1498,26 +1494,25 @@
             a.uiOptions = process.mInfo.uiOptions;
             a.taskAffinity = "android:" + a.packageName + "/dream";
 
-            final int callingUid = Binder.getCallingUid();
-            final int callingPid = Binder.getCallingPid();
 
-            final long origId = Binder.clearCallingIdentity();
-            try {
-                getActivityStartController().obtainStarter(intent, "dream")
-                        .setCallingUid(callingUid)
-                        .setCallingPid(callingPid)
-                        .setCallingPackage(intent.getPackage())
-                        .setActivityInfo(a)
-                        .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options))
-                        // To start the dream from background, we need to start it from a persistent
-                        // system process. Here we set the real calling uid to the system server uid
-                        .setRealCallingUid(Binder.getCallingUid())
-                        .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
-                        .execute();
-                return true;
-            } finally {
-                Binder.restoreCallingIdentity(origId);
-            }
+            final ActivityRecord[] outActivity = new ActivityRecord[1];
+            getActivityStartController().obtainStarter(intent, "dream")
+                    .setCallingUid(callingUid)
+                    .setCallingPid(callingPid)
+                    .setCallingPackage(intent.getPackage())
+                    .setActivityInfo(a)
+                    .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options))
+                    .setOutActivity(outActivity)
+                    // To start the dream from background, we need to start it from a persistent
+                    // system process. Here we set the real calling uid to the system server uid
+                    .setRealCallingUid(Binder.getCallingUid())
+                    .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL)
+                    .execute();
+
+            final ActivityRecord started = outActivity[0];
+            final IAppTask appTask = started == null ? null :
+                    new AppTaskImpl(this, started.getTask().mTaskId, callingUid);
+            return appTask;
         }
     }
 
@@ -2288,14 +2283,14 @@
 
     /** Register an {@link AnrController} to control the ANR dialog behavior */
     public void registerAnrController(AnrController controller) {
-        synchronized (mGlobalLock) {
+        synchronized (mAnrController) {
             mAnrController.add(controller);
         }
     }
 
     /** Unregister an {@link AnrController} */
     public void unregisterAnrController(AnrController controller) {
-        synchronized (mGlobalLock) {
+        synchronized (mAnrController) {
             mAnrController.remove(controller);
         }
     }
@@ -2311,7 +2306,7 @@
         }
 
         final ArrayList<AnrController> controllers;
-        synchronized (mGlobalLock) {
+        synchronized (mAnrController) {
             controllers = new ArrayList<>(mAnrController);
         }
 
@@ -3024,9 +3019,11 @@
         // Set to activity manager directly to make sure the state can be seen by the subsequent
         // update of scheduling group.
         proc.setRunningAnimationUnsafe();
-        mH.removeMessages(H.UPDATE_PROCESS_ANIMATING_STATE, proc);
-        mH.sendMessageDelayed(mH.obtainMessage(H.UPDATE_PROCESS_ANIMATING_STATE, proc),
+        mH.sendMessage(mH.obtainMessage(H.ADD_WAKEFULNESS_ANIMATING_REASON, proc));
+        mH.removeMessages(H.REMOVE_WAKEFULNESS_ANIMATING_REASON, proc);
+        mH.sendMessageDelayed(mH.obtainMessage(H.REMOVE_WAKEFULNESS_ANIMATING_REASON, proc),
                 DOZE_ANIMATING_STATE_RETAIN_TIME_MS);
+        Trace.instant(TRACE_TAG_WINDOW_MANAGER, "requestWakefulnessAnimating");
     }
 
     @Override
@@ -5651,9 +5648,10 @@
 
     final class H extends Handler {
         static final int REPORT_TIME_TRACKER_MSG = 1;
-        static final int UPDATE_PROCESS_ANIMATING_STATE = 2;
         static final int END_POWER_MODE_UNKNOWN_VISIBILITY_MSG = 3;
         static final int RESUME_FG_APP_SWITCH_MSG = 4;
+        static final int ADD_WAKEFULNESS_ANIMATING_REASON = 5;
+        static final int REMOVE_WAKEFULNESS_ANIMATING_REASON = 6;
 
         static final int FIRST_ACTIVITY_TASK_MSG = 100;
         static final int FIRST_SUPERVISOR_TASK_MSG = 200;
@@ -5670,13 +5668,23 @@
                     tracker.deliverResult(mContext);
                 }
                 break;
-                case UPDATE_PROCESS_ANIMATING_STATE: {
+                case ADD_WAKEFULNESS_ANIMATING_REASON: {
                     final WindowProcessController proc = (WindowProcessController) msg.obj;
                     synchronized (mGlobalLock) {
-                        proc.updateRunningRemoteOrRecentsAnimation();
+                        proc.addAnimatingReason(
+                                WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
                     }
                 }
                 break;
+                case REMOVE_WAKEFULNESS_ANIMATING_REASON: {
+                    final WindowProcessController proc = (WindowProcessController) msg.obj;
+                    synchronized (mGlobalLock) {
+                        proc.removeAnimatingReason(
+                                WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
+                    }
+                    Trace.instant(TRACE_TAG_WINDOW_MANAGER, "finishWakefulnessAnimating");
+                }
+                break;
                 case END_POWER_MODE_UNKNOWN_VISIBILITY_MSG: {
                     synchronized (mGlobalLock) {
                         mRetainPowerModeAndTopProcessState = false;
@@ -5915,6 +5923,11 @@
         }
 
         @Override
+        public IAppTask startDreamActivity(@NonNull Intent intent, int callingUid, int callingPid) {
+            return startDreamActivityInternal(intent, callingUid, callingPid);
+        }
+
+        @Override
         public void setAllowAppSwitches(@NonNull String type, int uid, int userId) {
             if (!mAmInternal.isUserRunning(userId, ActivityManager.FLAG_OR_STOPPED)) {
                 return;
@@ -6024,15 +6037,13 @@
 
         @Override
         public boolean isShuttingDown() {
-            synchronized (mGlobalLock) {
-                return mShuttingDown;
-            }
+            return mShuttingDown;
         }
 
         @Override
         public boolean shuttingDown(boolean booted, int timeout) {
+            mShuttingDown = true;
             synchronized (mGlobalLock) {
-                mShuttingDown = true;
                 mRootWindowContainer.prepareForShutdown();
                 updateEventDispatchingLocked(booted);
                 notifyTaskPersisterLocked(null, true);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index b34ae19..cc130c4 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1752,7 +1752,9 @@
                 /* multi_window */
                 false,
                 /* bal_code */
-                -1
+                -1,
+                /* task_stack */
+                null
         );
 
         boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags
@@ -1891,7 +1893,7 @@
         // DestroyActivityItem may be called first.
         final ActivityRecord top = task.getTopMostActivity();
         if (top != null && top.finishing && !top.mAppStopped && top.lastVisibleTime > 0
-                && !task.mKillProcessesOnDestroyed) {
+                && !task.mKillProcessesOnDestroyed && top.hasProcess()) {
             task.mKillProcessesOnDestroyed = true;
             mHandler.sendMessageDelayed(
                     mHandler.obtainMessage(KILL_TASK_PROCESSES_TIMEOUT_MSG, task),
@@ -2201,7 +2203,8 @@
             displaySwapping |= s.isDisplaySleepingAndSwapping();
             ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b "
                     + "finishing=%s", s, s.nowVisible, animating, s.finishing);
-            if ((!animating && !displaySwapping) || mService.mShuttingDown) {
+            if ((!animating && !displaySwapping) || mService.mShuttingDown
+                    || s.getRootTask().isForceHiddenForPinnedTask()) {
                 if (!processPausingActivities && s.isState(PAUSING)) {
                     // Defer processing pausing activities in this iteration and reschedule
                     // a delayed idle to reprocess it again
@@ -2370,6 +2373,10 @@
         final ActivityRecord prevTopActivity = mTopResumedActivity;
         final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
         if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) {
+            if (topRootTask == null) {
+                // There's no focused task and there won't have any resumed activity either.
+                scheduleTopResumedActivityStateLossIfNeeded();
+            }
             if (mService.isSleepingLocked()) {
                 // There won't be a next resumed activity. The top process should still be updated
                 // according to the current top focused activity.
@@ -2379,16 +2386,7 @@
         }
 
         // Ask previous activity to release the top state.
-        final boolean prevActivityReceivedTopState =
-                prevTopActivity != null && !mTopResumedActivityWaitingForPrev;
-        // mTopResumedActivityWaitingForPrev == true at this point would mean that an activity
-        // before the prevTopActivity one hasn't reported back yet. So server never sent the top
-        // resumed state change message to prevTopActivity.
-        if (prevActivityReceivedTopState
-                && prevTopActivity.scheduleTopResumedActivityChanged(false /* onTop */)) {
-            scheduleTopResumedStateLossTimeout(prevTopActivity);
-            mTopResumedActivityWaitingForPrev = true;
-        }
+        scheduleTopResumedActivityStateLossIfNeeded();
 
         // Update the current top activity.
         mTopResumedActivity = topRootTask.getTopResumedActivity();
@@ -2413,6 +2411,23 @@
         mService.updateTopApp(mTopResumedActivity);
     }
 
+    /** Schedule current top resumed activity state loss */
+    private void scheduleTopResumedActivityStateLossIfNeeded() {
+        if (mTopResumedActivity == null) {
+            return;
+        }
+
+        // mTopResumedActivityWaitingForPrev == true at this point would mean that an activity
+        // before the prevTopActivity one hasn't reported back yet. So server never sent the top
+        // resumed state change message to prevTopActivity.
+        if (!mTopResumedActivityWaitingForPrev
+                && mTopResumedActivity.scheduleTopResumedActivityChanged(false /* onTop */)) {
+            scheduleTopResumedStateLossTimeout(mTopResumedActivity);
+            mTopResumedActivityWaitingForPrev = true;
+            mTopResumedActivity = null;
+        }
+    }
+
     /** Schedule top resumed state change if previous top activity already reported back. */
     private void scheduleTopResumedActivityStateIfNeeded() {
         if (mTopResumedActivity != null && !mTopResumedActivityWaitingForPrev) {
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index f7ccc0d..0273a30 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -16,7 +16,9 @@
 
 package com.android.server.wm;
 
+import android.annotation.NonNull;
 import android.annotation.UiThread;
+import android.annotation.WorkerThread;
 import android.app.AlertDialog;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -30,14 +32,18 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.DisplayMetrics;
 import android.util.Slog;
 import android.util.Xml;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.IoThread;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -45,9 +51,7 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Manages warning dialogs shown during application lifecycle.
@@ -61,11 +65,12 @@
     public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04;
     public static final int FLAG_HIDE_DEPRECATED_ABI = 0x08;
 
-    private final HashMap<String, Integer> mPackageFlags = new HashMap<>();
+    @GuardedBy("mPackageFlags")
+    private final ArrayMap<String, Integer> mPackageFlags = new ArrayMap<>();
 
     private final ActivityTaskManagerService mAtm;
     private final Context mUiContext;
-    private final ConfigHandler mHandler;
+    private final WriteConfigTask mWriteConfigTask;
     private final UiHandler mUiHandler;
     private final AtomicFile mConfigFile;
 
@@ -75,30 +80,20 @@
     private DeprecatedAbiDialog mDeprecatedAbiDialog;
 
     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
-    private HashSet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
-            new HashSet<>();
+    private final ArraySet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
+            new ArraySet<>();
 
     /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
     void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) {
         mAlwaysShowUnsupportedCompileSdkWarningActivities.add(activity);
     }
 
-    /**
-     * Creates a new warning dialog manager.
-     * <p>
-     * <strong>Note:</strong> Must be called from the ActivityManagerService thread.
-     *
-     * @param atm
-     * @param uiContext
-     * @param handler
-     * @param uiHandler
-     * @param systemDir
-     */
+    /** Creates a new warning dialog manager. */
     public AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler,
             Handler uiHandler, File systemDir) {
         mAtm = atm;
         mUiContext = uiContext;
-        mHandler = new ConfigHandler(handler.getLooper());
+        mWriteConfigTask = new WriteConfigTask();
         mUiHandler = new UiHandler(uiHandler.getLooper());
         mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME), "warnings-config");
 
@@ -256,8 +251,9 @@
         mUiHandler.hideDialogsForPackage(name);
 
         synchronized (mPackageFlags) {
-            mPackageFlags.remove(name);
-            mHandler.scheduleWrite();
+            if (mPackageFlags.remove(name) != null) {
+                mWriteConfigTask.schedule();
+            }
         }
     }
 
@@ -425,7 +421,7 @@
                 } else {
                     mPackageFlags.remove(name);
                 }
-                mHandler.scheduleWrite();
+                mWriteConfigTask.schedule();
             }
         }
     }
@@ -556,46 +552,30 @@
         }
     }
 
-    /**
-     * Handles messages on the ActivityTaskManagerService thread.
-     */
-    private final class ConfigHandler extends Handler {
-        private static final int MSG_WRITE = 1;
-
-        private static final int DELAY_MSG_WRITE = 10000;
-
-        public ConfigHandler(Looper looper) {
-            super(looper, null, true);
-        }
+    private final class WriteConfigTask implements Runnable {
+        private static final long WRITE_CONFIG_DELAY_MS = 10000;
+        final AtomicReference<ArrayMap<String, Integer>> mPendingPackageFlags =
+                new AtomicReference<>();
 
         @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_WRITE:
-                    writeConfigToFileAmsThread();
-                    break;
+        public void run() {
+            final ArrayMap<String, Integer> packageFlags = mPendingPackageFlags.getAndSet(null);
+            if (packageFlags != null) {
+                writeConfigToFile(packageFlags);
             }
         }
 
-        public void scheduleWrite() {
-            removeMessages(MSG_WRITE);
-            sendEmptyMessageDelayed(MSG_WRITE, DELAY_MSG_WRITE);
+        @GuardedBy("mPackageFlags")
+        void schedule() {
+            if (mPendingPackageFlags.getAndSet(new ArrayMap<>(mPackageFlags)) == null) {
+                IoThread.getHandler().postDelayed(this, WRITE_CONFIG_DELAY_MS);
+            }
         }
     }
 
-    /**
-     * Writes the configuration file.
-     * <p>
-     * <strong>Note:</strong> Should be called from the ActivityManagerService thread unless you
-     * don't care where you're doing I/O operations. But you <i>do</i> care, don't you?
-     */
-    private void writeConfigToFileAmsThread() {
-        // Create a shallow copy so that we don't have to synchronize on config.
-        final HashMap<String, Integer> packageFlags;
-        synchronized (mPackageFlags) {
-            packageFlags = new HashMap<>(mPackageFlags);
-        }
-
+    /** Writes the configuration file. */
+    @WorkerThread
+    private void writeConfigToFile(@NonNull ArrayMap<String, Integer> packageFlags) {
         FileOutputStream fos = null;
         try {
             fos = mConfigFile.startWrite();
@@ -605,9 +585,9 @@
             out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
             out.startTag(null, "packages");
 
-            for (Map.Entry<String, Integer> entry : packageFlags.entrySet()) {
-                String pkg = entry.getKey();
-                int mode = entry.getValue();
+            for (int i = 0; i < packageFlags.size(); i++) {
+                final String pkg = packageFlags.keyAt(i);
+                final int mode = packageFlags.valueAt(i);
                 if (mode == 0) {
                     continue;
                 }
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 0115877..2eceecc 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -33,6 +33,7 @@
 
 import com.android.internal.R;
 
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.function.Consumer;
@@ -93,15 +94,12 @@
     /** Whether the start transaction of the transition is committed (by shell). */
     private boolean mIsStartTransactionCommitted;
 
-    /** Whether all windows should wait for the start transaction. */
-    private boolean mAlwaysWaitForStartTransaction;
-
     /** Whether the target windows have been requested to sync their draw transactions. */
     private boolean mIsSyncDrawRequested;
 
     private SeamlessRotator mRotator;
 
-    private final int mOriginalRotation;
+    private int mOriginalRotation;
     private final boolean mHasScreenRotationAnimation;
 
     AsyncRotationController(DisplayContent displayContent) {
@@ -147,15 +145,6 @@
         if (mTransitionOp == OP_LEGACY) {
             mIsStartTransactionCommitted = true;
         } else if (displayContent.mTransitionController.isCollecting(displayContent)) {
-            final Transition transition =
-                    mDisplayContent.mTransitionController.getCollectingTransition();
-            if (transition != null) {
-                final BLASTSyncEngine.SyncGroup syncGroup =
-                        mDisplayContent.mWmService.mSyncEngine.getSyncSet(transition.getSyncId());
-                if (syncGroup != null && syncGroup.mSyncMethod == BLASTSyncEngine.METHOD_BLAST) {
-                    mAlwaysWaitForStartTransaction = true;
-                }
-            }
             keepAppearanceInPreviousRotation();
         }
     }
@@ -236,7 +225,15 @@
      * operation directly to avoid waiting until timeout.
      */
     void updateTargetWindows() {
-        if (mTransitionOp == OP_LEGACY || !mIsStartTransactionCommitted) return;
+        if (mTransitionOp == OP_LEGACY) return;
+        if (!mIsStartTransactionCommitted) {
+            if (mTimeoutRunnable == null && !mDisplayContent.hasTopFixedRotationLaunchingApp()
+                    && !mDisplayContent.isRotationChanging() && !mDisplayContent.inTransition()) {
+                Slog.d(TAG, "Cancel for no change");
+                mDisplayContent.finishAsyncRotationIfPossible();
+            }
+            return;
+        }
         for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
             final Operation op = mTargetWindowTokens.valueAt(i);
             if (op.mIsCompletionPending || op.mAction == Operation.ACTION_SEAMLESS) {
@@ -279,10 +276,12 @@
             // The previous animation leash will be dropped when preparing fade-in animation, so
             // simply apply new animation without restoring the transformation.
             fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM);
-        } else if (op.mAction == Operation.ACTION_SEAMLESS && mRotator != null
+        } else if (op.mAction == Operation.ACTION_SEAMLESS
                 && op.mLeash != null && op.mLeash.isValid()) {
             if (DEBUG) Slog.d(TAG, "finishOp undo seamless " + windowToken.getTopChild());
-            mRotator.setIdentityMatrix(windowToken.getSyncTransaction(), op.mLeash);
+            final SurfaceControl.Transaction t = windowToken.getSyncTransaction();
+            t.setMatrix(op.mLeash, 1, 0, 0, 1);
+            t.setPosition(op.mLeash, 0, 0);
         }
     }
 
@@ -365,6 +364,32 @@
         }
     }
 
+    /**
+     * Re-initialize the states if the current display rotation has changed to a different rotation.
+     * This is mainly for seamless rotation to update the transform based on new rotation.
+     */
+    void updateRotation() {
+        if (mRotator == null) return;
+        final int currentRotation = mDisplayContent.getWindowConfiguration().getRotation();
+        if (mOriginalRotation == currentRotation) {
+            return;
+        }
+        Slog.d(TAG, "Update original rotation " + currentRotation);
+        mOriginalRotation = currentRotation;
+        mDisplayContent.forAllWindows(w -> {
+            if (w.mForceSeamlesslyRotate && w.mHasSurface
+                    && !mTargetWindowTokens.containsKey(w.mToken)) {
+                final Operation op = new Operation(Operation.ACTION_SEAMLESS);
+                op.mLeash = w.mToken.mSurfaceControl;
+                mTargetWindowTokens.put(w.mToken, op);
+            }
+        }, true /* traverseTopToBottom */);
+        mRotator = null;
+        mIsStartTransactionCommitted = false;
+        mIsSyncDrawRequested = false;
+        keepAppearanceInPreviousRotation();
+    }
+
     private void scheduleTimeout() {
         if (mTimeoutRunnable == null) {
             mTimeoutRunnable = () -> {
@@ -589,7 +614,17 @@
      * start transaction of rotation transition is applied.
      */
     private boolean canDrawBeforeStartTransaction(Operation op) {
-        return !mAlwaysWaitForStartTransaction && op.mAction != Operation.ACTION_SEAMLESS;
+        return op.mAction != Operation.ACTION_SEAMLESS;
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "AsyncRotationController");
+        prefix += "  ";
+        pw.println(prefix + "mTransitionOp=" + mTransitionOp);
+        pw.println(prefix + "mIsStartTransactionCommitted=" + mIsStartTransactionCommitted);
+        pw.println(prefix + "mIsSyncDrawRequested=" + mIsSyncDrawRequested);
+        pw.println(prefix + "mOriginalRotation=" + mOriginalRotation);
+        pw.println(prefix + "mTargetWindowTokens=" + mTargetWindowTokens);
     }
 
     /** The operation to control the rotation appearance associated with window token. */
@@ -619,5 +654,10 @@
         Operation(@Action int action) {
             mAction = action;
         }
+
+        @Override
+        public String toString() {
+            return "Operation{a=" + mAction + " pending=" + mIsCompletionPending + '}';
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 0c196d7..993c016 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -26,7 +26,9 @@
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
 import static com.android.server.wm.BackNavigationProto.ANIMATION_IN_PROGRESS;
+import static com.android.server.wm.BackNavigationProto.ANIMATION_RUNNING;
 import static com.android.server.wm.BackNavigationProto.LAST_BACK_TYPE;
+import static com.android.server.wm.BackNavigationProto.MAIN_OPEN_ACTIVITY;
 import static com.android.server.wm.BackNavigationProto.SHOW_WALLPAPER;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
 
@@ -50,6 +52,7 @@
 import android.window.BackAnimationAdapter;
 import android.window.BackNavigationInfo;
 import android.window.IBackAnimationFinishedCallback;
+import android.window.IWindowlessStartingSurfaceCallback;
 import android.window.OnBackInvokedCallbackInfo;
 import android.window.TaskSnapshot;
 
@@ -73,6 +76,8 @@
     private @BackNavigationInfo.BackTargetType int mLastBackType;
     private boolean mShowWallpaper;
     private Runnable mPendingAnimation;
+
+    private boolean mBackAnimationRunning;
     private final NavigationMonitor mNavigationMonitor = new NavigationMonitor();
 
     private AnimationHandler mAnimationHandler;
@@ -474,7 +479,7 @@
                 final ActivityRecord ar = openApps.valueAt(i);
                 if (mAnimationHandler.isTarget(ar, true /* open */)) {
                     openApps.removeAt(i);
-                    mAnimationHandler.mOpenTransitionTargetMatch = true;
+                    mAnimationHandler.markStartingSurfaceMatch();
                 }
             }
             for (int i = closeApps.size() - 1; i >= 0; --i) {
@@ -583,8 +588,9 @@
      * The closing target should only exist in close list, but the opening target can be either in
      * open or close list.
      */
-    void onTransactionReady(Transition transition, ArrayList<Transition.ChangeInfo> targets) {
-        if (!isMonitoringTransition()) {
+    void onTransactionReady(Transition transition, ArrayList<Transition.ChangeInfo> targets,
+            SurfaceControl.Transaction startTransaction) {
+        if (!isMonitoringTransition() || targets.isEmpty()) {
             return;
         }
         for (int i = targets.size() - 1; i >= 0; --i) {
@@ -613,6 +619,17 @@
                 Slog.e(TAG, "Gesture animation is applied on another transition?");
             }
             mWaitTransitionFinish = transition;
+            // Flag target matches to defer remove the splash screen.
+            for (int i = mTmpOpenApps.size() - 1; i >= 0; --i) {
+                final WindowContainer wc = mTmpOpenApps.get(i);
+                if (mAnimationHandler.isTarget(wc, true /* open */)) {
+                    mAnimationHandler.markStartingSurfaceMatch();
+                    break;
+                }
+            }
+            // Because the target will reparent to transition root, so it cannot be controlled by
+            // animation leash. Hide the close target when transition starts.
+            startTransaction.hide(mAnimationHandler.mCloseAdaptor.mTarget.getSurfaceControl());
         }
         mTmpOpenApps.clear();
         mTmpCloseApps.clear();
@@ -633,6 +650,7 @@
         mAnimationHandler.clearBackAnimateTarget();
         mNavigationMonitor.stopMonitorTransition();
         mWaitTransitionFinish = null;
+        mBackAnimationRunning = false;
     }
 
     /**
@@ -717,11 +735,7 @@
         // This will be set before transition happen, to know whether the real opening target
         // exactly match animating target. When target match, reparent the starting surface to
         // the opening target like starting window do.
-        private boolean mOpenTransitionTargetMatch;
-        // The starting surface task Id. Used to clear the starting surface if the animation has
-        // request one during animating.
-        private int mRequestedStartingSurfaceTaskId;
-        private SurfaceControl mStartingSurface;
+        private boolean mStartingSurfaceTargetMatch;
         private ActivityRecord mOpenActivity;
 
         AnimationHandler(WindowManagerService wms) {
@@ -765,8 +779,8 @@
                 return;
             }
 
-            mCloseAdaptor = createAdaptor(closeTarget, false /* isOpen */);
-            mOpenAdaptor = createAdaptor(open, true /* isOpen */);
+            mCloseAdaptor = createAdaptor(closeTarget, false, mSwitchType);
+            mOpenAdaptor = createAdaptor(open, true, mSwitchType);
             mOpenActivity = openActivity;
             if (mCloseAdaptor.mAnimationTarget == null || mOpenAdaptor.mAnimationTarget == null) {
                 Slog.w(TAG, "composeNewAnimations fail, skip");
@@ -774,8 +788,8 @@
             }
         }
 
-        boolean composeAnimations(@NonNull WindowContainer close, @NonNull WindowContainer open,
-                ActivityRecord openActivity) {
+        private boolean composeAnimations(@NonNull WindowContainer close,
+                @NonNull WindowContainer open, ActivityRecord openActivity) {
             if (mComposed || mWaitTransition) {
                 Slog.e(TAG, "Previous animation is running " + this);
                 return false;
@@ -805,28 +819,6 @@
                     .isSupportWindowlessStartingSurface();
         }
 
-        void createStartingSurface(TaskSnapshot snapshot) {
-            if (!mComposed) {
-                return;
-            }
-
-            final ActivityRecord topActivity = getTopOpenActivity();
-            if (topActivity == null) {
-                Slog.e(TAG, "createStartingSurface fail, no open activity: " + this);
-                return;
-            }
-            // TODO (b/257857570) draw snapshot by starting surface.
-        }
-
-        private ActivityRecord getTopOpenActivity() {
-            if (mSwitchType == ACTIVITY_SWITCH) {
-                return mOpenAdaptor.mTarget.asActivityRecord();
-            } else if (mSwitchType == TASK_SWITCH) {
-                return mOpenAdaptor.mTarget.asTask().getTopNonFinishingActivity();
-            }
-            return null;
-        }
-
         boolean containTarget(ArrayList<WindowContainer> wcs, boolean open) {
             for (int i = wcs.size() - 1; i >= 0; --i) {
                 if (isTarget(wcs.get(i), open)) {
@@ -860,13 +852,13 @@
             if (!mComposed) {
                 return;
             }
-            cleanUpWindowlessSurface();
 
             if (mCloseAdaptor != null) {
                 mCloseAdaptor.mTarget.cancelAnimation();
                 mCloseAdaptor = null;
             }
             if (mOpenAdaptor != null) {
+                mOpenAdaptor.cleanUpWindowlessSurface(mStartingSurfaceTargetMatch);
                 mOpenAdaptor.mTarget.cancelAnimation();
                 mOpenAdaptor = null;
             }
@@ -875,36 +867,16 @@
             }
         }
 
-        private void cleanUpWindowlessSurface() {
-            final ActivityRecord ar = getTopOpenActivity();
-            if (ar == null) {
-                Slog.w(TAG, "finishPresentAnimations without top activity: " + this);
-            }
-            final SurfaceControl.Transaction pendingT = ar != null ? ar.getPendingTransaction()
-                    : mOpenAdaptor.mTarget.getPendingTransaction();
-            // ensure open target is visible before cancel animation.
-            mOpenTransitionTargetMatch &= ar != null;
-            if (mOpenTransitionTargetMatch) {
-                pendingT.show(ar.getSurfaceControl());
-            }
-            if (mRequestedStartingSurfaceTaskId != 0) {
-                // If open target match, reparent to open activity
-                if (mStartingSurface != null && mOpenTransitionTargetMatch) {
-                    pendingT.reparent(mStartingSurface, ar.getSurfaceControl());
-                }
-                // remove starting surface.
-                mStartingSurface = null;
-                // TODO (b/257857570) draw snapshot by starting surface.
-                mRequestedStartingSurfaceTaskId = 0;
-            }
+        void markStartingSurfaceMatch() {
+            mStartingSurfaceTargetMatch = true;
+            mOpenAdaptor.reparentWindowlessSurfaceToTarget();
         }
 
         void clearBackAnimateTarget() {
             finishPresentAnimations();
             mComposed = false;
             mWaitTransition = false;
-            mOpenTransitionTargetMatch = false;
-            mRequestedStartingSurfaceTaskId = 0;
+            mStartingSurfaceTargetMatch = false;
             mSwitchType = UNKNOWN;
             mOpenActivity = null;
         }
@@ -935,9 +907,9 @@
         }
 
         private static BackWindowAnimationAdaptor createAdaptor(
-                WindowContainer target, boolean isOpen) {
+                WindowContainer target, boolean isOpen, int switchType) {
             final BackWindowAnimationAdaptor adaptor =
-                    new BackWindowAnimationAdaptor(target, isOpen);
+                    new BackWindowAnimationAdaptor(target, isOpen, switchType);
             final SurfaceControl.Transaction pt = target.getPendingTransaction();
             target.startAnimation(pt, adaptor, false /* hidden */, ANIMATION_TYPE_PREDICT_BACK);
             // Workaround to show TaskFragment which can be hide in Transitions and won't show
@@ -957,11 +929,19 @@
             private final WindowContainer mTarget;
             private final boolean mIsOpen;
             private RemoteAnimationTarget mAnimationTarget;
+            private final int mSwitchType;
 
-            BackWindowAnimationAdaptor(WindowContainer closeTarget, boolean isOpen) {
-                mBounds.set(closeTarget.getBounds());
-                mTarget = closeTarget;
+            // The starting surface task Id. Used to clear the starting surface if the animation has
+            // requested one during animating.
+            private int mRequestedStartingSurfaceId = INVALID_TASK_ID;
+            private SurfaceControl mStartingSurface;
+
+            BackWindowAnimationAdaptor(WindowContainer target, boolean isOpen,
+                    int switchType) {
+                mBounds.set(target.getBounds());
+                mTarget = target;
                 mIsOpen = isOpen;
+                mSwitchType = switchType;
             }
             @Override
             public boolean getShowWallpaper() {
@@ -979,6 +959,8 @@
             public void onAnimationCancelled(SurfaceControl animationLeash) {
                 if (mCapturedLeash == animationLeash) {
                     mCapturedLeash = null;
+                    mRequestedStartingSurfaceId = INVALID_TASK_ID;
+                    mStartingSurface = null;
                 }
             }
 
@@ -1009,8 +991,15 @@
                     return mAnimationTarget;
                 }
                 Task t = mTarget.asTask();
-                final ActivityRecord r = t != null ? t.getTopNonFinishingActivity()
-                        : mTarget.asActivityRecord();
+                ActivityRecord r = null;
+                if (t == null && mTarget.asTaskFragment() != null) {
+                    t = mTarget.asTaskFragment().getTask();
+                    r = mTarget.asTaskFragment().getTopNonFinishingActivity();
+                }
+                if (r == null) {
+                    r = t != null ? t.getTopNonFinishingActivity()
+                            : mTarget.asActivityRecord();
+                }
                 if (t == null && r != null) {
                     t = r.getTask();
                 }
@@ -1037,6 +1026,77 @@
                         r.checkEnterPictureInPictureAppOpsState());
                 return mAnimationTarget;
             }
+
+            void createStartingSurface() {
+                if (!mIsOpen) {
+                    return;
+                }
+                final Task openTask = mSwitchType == TASK_SWITCH
+                        ? mTarget.asTask() : mSwitchType == ACTIVITY_SWITCH
+                        ? mTarget.asActivityRecord().getTask() : null;
+                if (openTask == null) {
+                    return;
+                }
+                final ActivityRecord mainActivity = mSwitchType == ACTIVITY_SWITCH
+                        ? mTarget.asActivityRecord()
+                        : openTask.getTopNonFinishingActivity();
+                if (mainActivity == null) {
+                    return;
+                }
+                final TaskSnapshot snapshot = getSnapshot(mTarget);
+                mRequestedStartingSurfaceId = openTask.mAtmService.mTaskOrganizerController
+                        .addWindowlessStartingSurface(openTask, mainActivity,
+                                mAnimationTarget.leash, snapshot,
+                                new IWindowlessStartingSurfaceCallback.Stub() {
+                            // Once the starting surface has been created in shell, it will call
+                            // onSurfaceAdded to pass the created surface to core, so if a
+                            // transition is triggered by the back gesture, there doesn't need to
+                            // create another starting surface for the opening target, just reparent
+                            // the starting surface to the opening target.
+                            // Note if cleanUpWindowlessSurface happen prior than onSurfaceAdded
+                            // called, there won't be able to reparent the starting surface on
+                            // opening target. But if that happens and transition target is matched,
+                            // the app window should already draw.
+                                    @Override
+                                    public void onSurfaceAdded(SurfaceControl sc) {
+                                        synchronized (mTarget.mWmService.mGlobalLock) {
+                                            if (mRequestedStartingSurfaceId != INVALID_TASK_ID) {
+                                                mStartingSurface = sc;
+                                            }
+                                        }
+                                    }
+                                });
+            }
+
+            // When back gesture has triggered and transition target matches navigation target,
+            // reparent the starting surface to the opening target as it's starting window.
+            void reparentWindowlessSurfaceToTarget() {
+                if (mRequestedStartingSurfaceId == INVALID_TASK_ID) {
+                    return;
+                }
+                // If open target matches, reparent to open activity or task
+                if (mStartingSurface != null && mStartingSurface.isValid()) {
+                    mTarget.getPendingTransaction()
+                            .reparent(mStartingSurface, mTarget.getSurfaceControl());
+                    // remove starting surface.
+                    mStartingSurface = null;
+                }
+            }
+
+            /**
+             * Ask shell to clear the starting surface.
+             * @param openTransitionMatch if true, shell will play the remove starting window
+             *                            animation, otherwise remove it directly.
+             */
+            void cleanUpWindowlessSurface(boolean openTransitionMatch) {
+                if (mRequestedStartingSurfaceId == INVALID_TASK_ID) {
+                    return;
+                }
+                mTarget.mWmService.mAtmService.mTaskOrganizerController
+                        .removeWindowlessStartingSurface(mRequestedStartingSurfaceId,
+                                !openTransitionMatch);
+                mRequestedStartingSurfaceId = INVALID_TASK_ID;
+            }
         }
 
         ScheduleAnimationBuilder prepareAnimation(int backType, BackAnimationAdapter adapter,
@@ -1089,15 +1149,13 @@
 
             /**
              * Apply preview strategy on the opening target
-             * @param open The opening target.
+             * @param openAnimationAdaptor The animator who can create starting surface.
              * @param visibleOpenActivity  The visible activity in opening target.
-             * @return If the preview strategy is launch behind, returns the Activity that has
-             *         launchBehind set, or null otherwise.
              */
-            private void applyPreviewStrategy(WindowContainer open,
+            private void applyPreviewStrategy(BackWindowAnimationAdaptor openAnimationAdaptor,
                     ActivityRecord visibleOpenActivity) {
                 if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind) {
-                    createStartingSurface(getSnapshot(open));
+                    openAnimationAdaptor.createStartingSurface();
                     return;
                 }
                 setLaunchBehind(visibleOpenActivity);
@@ -1119,7 +1177,9 @@
                 if (!composeAnimations(mCloseTarget, mOpenTarget, openActivity)) {
                     return null;
                 }
-                applyPreviewStrategy(mOpenTarget, openActivity);
+                mCloseTarget.mTransitionController.mSnapshotController
+                        .mActivitySnapshotController.clearOnBackPressedActivities();
+                applyPreviewStrategy(mOpenAdaptor, openActivity);
 
                 final IBackAnimationFinishedCallback callback = makeAnimationFinishedCallback();
                 final RemoteAnimationTarget[] targets = getAnimationTargets();
@@ -1164,6 +1224,8 @@
             // Call it again to make sure the activity could be visible while handling the pending
             // animation.
             activity.commitVisibility(true, true);
+            activity.mTransitionController.mSnapshotController
+                    .mActivitySnapshotController.addOnBackPressedActivity(activity);
         }
         activity.mLaunchTaskBehind = true;
 
@@ -1190,6 +1252,9 @@
         // Restore the launch-behind state.
         activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token);
         activity.mLaunchTaskBehind = false;
+        // Ignore all change
+        activity.mTransitionController.mSnapshotController
+                .mActivitySnapshotController.clearOnBackPressedActivities();
         ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
                 "Setting Activity.mLauncherTaskBehind to false. Activity=%s",
                 activity);
@@ -1220,6 +1285,7 @@
         if (mPendingAnimation != null) {
             mPendingAnimation.run();
             mPendingAnimation = null;
+            mBackAnimationRunning = true;
         }
     }
 
@@ -1236,9 +1302,6 @@
     }
 
     static TaskSnapshot getSnapshot(@NonNull WindowContainer w) {
-        if (!isScreenshotEnabled()) {
-            return null;
-        }
         if (w.asTask() != null) {
             final Task task = w.asTask();
             return  task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
@@ -1247,8 +1310,8 @@
         }
 
         if (w.asActivityRecord() != null) {
-            // TODO (b/259497289) return TaskSnapshot when feature complete.
-            return null;
+            final ActivityRecord ar = w.asActivityRecord();
+            return ar.mWmService.mSnapshotController.mActivitySnapshotController.getSnapshot(ar);
         }
         return null;
     }
@@ -1270,6 +1333,12 @@
         proto.write(ANIMATION_IN_PROGRESS, mBackAnimationInProgress);
         proto.write(LAST_BACK_TYPE, mLastBackType);
         proto.write(SHOW_WALLPAPER, mShowWallpaper);
+        if (mAnimationHandler.mOpenActivity != null) {
+            mAnimationHandler.mOpenActivity.writeNameToProto(proto, MAIN_OPEN_ACTIVITY);
+        } else {
+            proto.write(MAIN_OPEN_ACTIVITY, "");
+        }
+        proto.write(ANIMATION_RUNNING, mBackAnimationRunning);
         proto.end(token);
     }
 }
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 6a7e764..5aa7c97 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -87,7 +87,7 @@
     private int mLastOrientation = ORIENTATION_UNDEFINED;
 
     ContentRecorder(@NonNull DisplayContent displayContent) {
-        this(displayContent, new RemoteMediaProjectionManagerWrapper());
+        this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId));
     }
 
     @VisibleForTesting
@@ -166,7 +166,7 @@
                                     + "%d to new bounds %s and/or orientation %d.",
                             mDisplayContent.getDisplayId(), recordedContentBounds,
                             recordedContentOrientation);
-                    updateMirroredSurface(mDisplayContent.mWmService.mTransactionFactory.get(),
+                    updateMirroredSurface(mRecordedWindowContainer.getSyncTransaction(),
                             recordedContentBounds, surfaceSize);
                 } else {
                     // If the surface removed, do nothing. We will handle this via onDisplayChanged
@@ -325,6 +325,7 @@
                         .reparent(mDisplayContent.getOverlayLayer(), null);
         // Retrieve the size of the DisplayArea to mirror.
         updateMirroredSurface(transaction, mRecordedWindowContainer.getBounds(), surfaceSize);
+        transaction.apply();
 
         // Notify the client about the visibility of the mirrored region, now that we have begun
         // capture.
@@ -481,8 +482,7 @@
                 .setMatrix(mRecordedSurface, scale, 0 /* dtdx */, 0 /* dtdy */, scale)
                 // Position needs to be updated when the mirrored DisplayArea has changed, since
                 // the content will no longer be centered in the output surface.
-                .setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */)
-                .apply();
+                .setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */);
         mLastRecordedBounds = new Rect(recordedContentBounds);
         // Request to notify the client about the resize.
         mMediaProjectionManager.notifyActiveProjectionCapturedContentResized(
@@ -556,8 +556,14 @@
 
     private static final class RemoteMediaProjectionManagerWrapper implements
             MediaProjectionManagerWrapper {
+
+        private final int mDisplayId;
         @Nullable private IMediaProjectionManager mIMediaProjectionManager = null;
 
+        RemoteMediaProjectionManagerWrapper(int displayId) {
+            mDisplayId = displayId;
+        }
+
         @Override
         public void stopActiveProjection() {
             fetchMediaProjectionManager();
@@ -565,12 +571,15 @@
                 return;
             }
             try {
+                ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
+                        "Content Recording: stopping active projection for display %d",
+                        mDisplayId);
                 mIMediaProjectionManager.stopActiveProjection();
             } catch (RemoteException e) {
                 ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
                         "Content Recording: Unable to tell MediaProjectionManagerService to stop "
-                                + "the active projection: %s",
-                        e);
+                                + "the active projection for display %d: %s",
+                        mDisplayId, e);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index f24ba5a..b589085 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -117,10 +117,11 @@
             }
             incomingDisplayContent.setContentRecordingSession(incomingSession);
             // Updating scenario: Explicitly ask ContentRecorder to update, since no config or
-            // display change will trigger an update from the DisplayContent.
-            if (hasSessionUpdatedWithConsent) {
-                incomingDisplayContent.updateRecording();
-            }
+            // display change will trigger an update from the DisplayContent. There exists a
+            // scenario where a DisplayContent is created, but it's ContentRecordingSession hasn't
+            // been set yet due to a race condition. On creation, updateRecording fails to start
+            // recording, so now this call guarantees recording will be started from somewhere.
+            incomingDisplayContent.updateRecording();
         }
         // Takeover and stopping scenario: stop recording on the pre-existing session.
         if (mSession != null && !hasSessionUpdatedWithConsent) {
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index e91c9d4..4180cd2 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -39,10 +39,11 @@
             TAG_WITH_CLASS_NAME ? "DesktopModeLaunchParamsModifier" : TAG_ATM;
     private static final boolean DEBUG = false;
 
-    // Desktop mode feature flag.
-    static final boolean DESKTOP_MODE_SUPPORTED = SystemProperties.getBoolean(
-            "persist.wm.debug.desktop_mode", false) || SystemProperties.getBoolean(
-            "persist.wm.debug.desktop_mode_2", false);
+    // Desktop mode feature flags.
+    private static final boolean DESKTOP_MODE_PROTO1_SUPPORTED =
+            SystemProperties.getBoolean("persist.wm.debug.desktop_mode", false);
+    private static final boolean DESKTOP_MODE_PROTO2_SUPPORTED =
+            SystemProperties.getBoolean("persist.wm.debug.desktop_mode_2", false);
     // Override default freeform task width when desktop mode is enabled. In dips.
     private static final int DESKTOP_MODE_DEFAULT_WIDTH_DP = SystemProperties.getInt(
             "persist.wm.debug.desktop_mode.default_width", 840);
@@ -76,22 +77,37 @@
             appendLog("task null, skipping");
             return RESULT_SKIP;
         }
-        if (phase != PHASE_BOUNDS) {
-            appendLog("not in bounds phase, skipping");
+        if (!task.isActivityTypeStandardOrUndefined()) {
+            appendLog("not standard or undefined activity type, skipping");
             return RESULT_SKIP;
         }
-        if (!task.isActivityTypeStandard()) {
-            appendLog("not standard activity type, skipping");
-            return RESULT_SKIP;
-        }
-        if (!currentParams.mBounds.isEmpty()) {
-            appendLog("currentParams has bounds set, not overriding");
+        if (phase < PHASE_WINDOWING_MODE) {
+            appendLog("not in windowing mode or bounds phase, skipping");
             return RESULT_SKIP;
         }
 
         // Copy over any values
         outParams.set(currentParams);
 
+        // In Proto2, trampoline task launches of an existing background task can result in the
+        // previous windowing mode to be restored even if the desktop mode state has changed.
+        // Let task launches inherit the windowing mode from the source task if available, which
+        // should have the desired windowing mode set by WM Shell. See b/286929122.
+        if (DESKTOP_MODE_PROTO2_SUPPORTED && source != null && source.getTask() != null) {
+            final Task sourceTask = source.getTask();
+            outParams.mWindowingMode = sourceTask.getWindowingMode();
+            appendLog("inherit-from-source=" + outParams.mWindowingMode);
+        }
+
+        if (phase == PHASE_WINDOWING_MODE) {
+            return RESULT_DONE;
+        }
+
+        if (!currentParams.mBounds.isEmpty()) {
+            appendLog("currentParams has bounds set, not overriding");
+            return RESULT_SKIP;
+        }
+
         // Update width and height with default desktop mode values
         float density = (float) task.getConfiguration().densityDpi / DENSITY_DEFAULT;
         final int width = (int) (DESKTOP_MODE_DEFAULT_WIDTH_DP * density + 0.5f);
@@ -123,4 +139,9 @@
     private void outputLog() {
         if (DEBUG) Slog.d(TAG, mLogBuilder.toString());
     }
+
+    /** Whether desktop mode is supported. */
+    static boolean isDesktopModeSupported() {
+        return DESKTOP_MODE_PROTO1_SUPPORTED || DESKTOP_MODE_PROTO2_SUPPORTED;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ef19eef..ee3014c 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1065,7 +1065,7 @@
         if (obscuredChanged && w.isVisible() && mWallpaperController.isWallpaperTarget(w)) {
             // This is the wallpaper target and its obscured state changed... make sure the
             // current wallpaper's visibility has been updated accordingly.
-            mWallpaperController.updateWallpaperVisibility();
+            mWallpaperController.updateWallpaperTokens(mDisplayContent.isKeyguardLocked());
         }
 
         w.handleWindowMovedIfNeeded();
@@ -1214,8 +1214,7 @@
         mDisplayRotationCompatPolicy =
                 // Not checking DeviceConfig value here to allow enabling via DeviceConfig
                 // without the need to restart the device.
-                mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                            /* checkDeviceConfig */ false)
+                mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime()
                         ? new DisplayRotationCompatPolicy(this) : null;
         mRotationReversionController = new DisplayRotationReversionController(this);
 
@@ -1714,9 +1713,7 @@
     }
 
     private int getMinimalTaskSizeDp() {
-        final Context displayConfigurationContext =
-                mAtmService.mContext.createConfigurationContext(getConfiguration());
-        final Resources res = displayConfigurationContext.getResources();
+        final Resources res = getDisplayUiContext().getResources();
         final TypedValue value = new TypedValue();
         res.getValue(R.dimen.default_minimal_size_resizable_task, value, true /* resolveRefs */);
         final int valueUnit = ((value.data >> COMPLEX_UNIT_SHIFT) & COMPLEX_UNIT_MASK);
@@ -1938,7 +1935,7 @@
         } else if (mFixedRotationLaunchingApp != null && r == null) {
             mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
             // Keep async rotation controller if the next transition of display is requested.
-            if (!mTransitionController.isCollecting(this)) {
+            if (!mTransitionController.hasCollectingRotationChange(this, getRotation())) {
                 finishAsyncRotationIfPossible();
             }
         }
@@ -2717,6 +2714,7 @@
         if (mDisplayPolicy != null) {
             mDisplayPolicy.onConfigurationChanged();
             mPinnedTaskController.onPostDisplayConfigurationChanged();
+            mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
         }
         // Update IME parent if needed.
         updateImeParent();
@@ -2753,6 +2751,10 @@
     void onAppTransitionDone() {
         super.onAppTransitionDone();
         mWmService.mWindowsChanged = true;
+        onTransitionFinished();
+    }
+
+    void onTransitionFinished() {
         // If the transition finished callback cannot match the token for some reason, make sure the
         // rotated state is cleared if it is already invisible.
         if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.isVisibleRequested()
@@ -2858,7 +2860,6 @@
 
     void onDisplayInfoChanged() {
         updateDisplayFrames(false /* notifyInsetsChange */);
-        mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
         mInputMonitor.layoutInputConsumers(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
         mDisplayPolicy.onDisplayInfoChanged(mDisplayInfo);
     }
@@ -3453,6 +3454,9 @@
                 if (mFixedRotationLaunchingApp != null) {
                     setSeamlessTransitionForFixedRotation(controller.getCollectingTransition());
                 }
+            } else if (mAsyncRotationController != null && !isRotationChanging()) {
+                Slog.i(TAG, "Finish AsyncRotation for previous intermediate change");
+                finishAsyncRotationIfPossible();
             }
             return;
         }
@@ -3460,6 +3464,11 @@
                 this, this, null /* remoteTransition */, displayChange);
         if (t != null) {
             mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+            if (mAsyncRotationController != null) {
+                // Give a chance to update the transform if the current rotation is changed when
+                // some windows haven't finished previous rotation.
+                mAsyncRotationController.updateRotation();
+            }
             if (mFixedRotationLaunchingApp != null) {
                 // A fixed-rotation transition is done, then continue to start a seamless display
                 // transition.
@@ -3621,6 +3630,9 @@
         if (mFixedRotationLaunchingApp != null) {
             pw.println("  mFixedRotationLaunchingApp=" + mFixedRotationLaunchingApp);
         }
+        if (mAsyncRotationController != null) {
+            mAsyncRotationController.dump(pw, prefix);
+        }
 
         pw.println();
         pw.print(prefix + "mHoldScreenWindow="); pw.print(mHoldScreenWindow);
@@ -3704,6 +3716,7 @@
         mInputMonitor.dump(pw, "  ");
         pw.println();
         mInsetsStateController.dump(prefix, pw);
+        mInsetsPolicy.dump(prefix, pw);
         mDwpcHelper.dump(prefix, pw);
         pw.println();
     }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index a1a9a08..354b0db 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -22,6 +22,7 @@
 import static android.view.InsetsFrameProvider.SOURCE_CONTAINER_BOUNDS;
 import static android.view.InsetsFrameProvider.SOURCE_DISPLAY;
 import static android.view.InsetsFrameProvider.SOURCE_FRAME;
+import static android.view.ViewRootImpl.CLIENT_IMMERSIVE_CONFIRMATION;
 import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -38,6 +39,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
@@ -194,6 +196,8 @@
     private final ScreenshotHelper mScreenshotHelper;
 
     private final Object mServiceAcquireLock = new Object();
+    private long mPanicTime;
+    private final long mPanicThresholdMs;
     private StatusBarManagerInternal mStatusBarManagerInternal;
 
     @Px
@@ -246,6 +250,8 @@
     private volatile boolean mKeyguardDrawComplete;
     private volatile boolean mWindowManagerDrawComplete;
 
+    private boolean mImmersiveConfirmationWindowExists;
+
     private WindowState mStatusBar = null;
     private volatile WindowState mNotificationShade;
     private WindowState mNavigationBar = null;
@@ -271,13 +277,13 @@
     private WindowState mSystemUiControllingWindow;
 
     // Candidate window to determine the color of navigation bar. The window needs to be top
-    // fullscreen-app windows or dim layers that are intersecting with the window frame of status
-    // bar.
+    // fullscreen-app windows or dim layers that are intersecting with the window frame of
+    // navigation bar.
     private WindowState mNavBarColorWindowCandidate;
 
-    // The window to determine opacity and background of translucent navigation bar. The window
-    // needs to be opaque.
-    private WindowState mNavBarBackgroundWindow;
+    // Candidate window to determine opacity and background of translucent navigation bar.
+    // The window frame must intersect the frame of navigation bar.
+    private WindowState mNavBarBackgroundWindowCandidate;
 
     /**
      * A collection of {@link AppearanceRegion} to indicate that which region of status bar applies
@@ -327,8 +333,6 @@
     private WindowState mTopFullscreenOpaqueWindowState;
     private boolean mTopIsFullscreen;
     private int mNavBarOpacityMode = NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED;
-    private boolean mForceConsumeSystemBars;
-    private boolean mForceShowSystemBars;
 
     /**
      * Windows that provides gesture insets. If multiple windows provide gesture insets at the same
@@ -404,6 +408,7 @@
         mCanSystemBarsBeShownByUser = !r.getBoolean(
                 R.bool.config_remoteInsetsControllerControlsSystemBars) || r.getBoolean(
                 R.bool.config_remoteInsetsControllerSystemBarsCanBeShownByUserAction);
+        mPanicThresholdMs = r.getInteger(R.integer.config_immersive_mode_confirmation_panic);
 
         mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
@@ -625,8 +630,12 @@
         };
         displayContent.mAppTransition.registerListenerLocked(mAppTransitionListener);
         displayContent.mTransitionController.registerLegacyListener(mAppTransitionListener);
-        mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext, looper,
-                mService.mVrModeEnabled, mCanSystemBarsBeShownByUser);
+        if (CLIENT_TRANSIENT || CLIENT_IMMERSIVE_CONFIRMATION) {
+            mImmersiveModeConfirmation = null;
+        } else {
+            mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext, looper,
+                    mService.mVrModeEnabled, mCanSystemBarsBeShownByUser);
+        }
 
         // TODO: Make it can take screenshot on external display
         mScreenshotHelper = displayContent.isDefaultDisplay
@@ -961,12 +970,6 @@
         if (!win.mSession.mCanSetUnrestrictedGestureExclusion) {
             attrs.privateFlags &= ~PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
         }
-
-        final InsetsSourceProvider provider = win.getControllableInsetProvider();
-        if (provider != null && provider.getSource().insetsRoundedCornerFrame()
-                != attrs.insetsRoundedCornerFrame) {
-            provider.getSource().setInsetsRoundedCornerFrame(attrs.insetsRoundedCornerFrame);
-        }
     }
 
     /**
@@ -1083,6 +1086,9 @@
                 mNavigationBar = win;
                 break;
         }
+        if ((attrs.privateFlags & PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW) != 0) {
+            mImmersiveConfirmationWindowExists = true;
+        }
         if (attrs.providedInsets != null) {
             for (int i = attrs.providedInsets.length - 1; i >= 0; i--) {
                 final InsetsFrameProvider provider = attrs.providedInsets[i];
@@ -1104,9 +1110,11 @@
                 } else {
                     overrideProviders = null;
                 }
-                mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(
-                        provider.getId(), provider.getType()).setWindowContainer(
-                                win, frameProvider, overrideProviders);
+                final InsetsSourceProvider sourceProvider = mDisplayContent
+                        .getInsetsStateController().getOrCreateSourceProvider(provider.getId(),
+                                provider.getType());
+                sourceProvider.getSource().setFlags(provider.getFlags());
+                sourceProvider.setWindowContainer(win, frameProvider, overrideProviders);
                 mInsetsSourceWindowsExceptIme.add(win);
             }
         }
@@ -1240,6 +1248,9 @@
             }
         }
         mInsetsSourceWindowsExceptIme.remove(win);
+        if ((win.mAttrs.privateFlags & PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW) != 0) {
+            mImmersiveConfirmationWindowExists = false;
+        }
     }
 
     WindowState getStatusBar() {
@@ -1290,18 +1301,10 @@
         return ANIMATION_STYLEABLE;
     }
 
-    /**
-     * @return true if the system bars are forced to be consumed
-     */
+    // TODO (b/277891341): Remove this and related usages. This has been replaced by
+    //                     InsetsSource#FLAG_FORCE_CONSUMING.
     public boolean areSystemBarsForcedConsumedLw() {
-        return mForceConsumeSystemBars;
-    }
-
-    /**
-     * @return true if the system bars are forced to stay visible
-     */
-    public boolean areSystemBarsForcedShownLw() {
-        return mForceShowSystemBars;
+        return false;
     }
 
     /**
@@ -1387,7 +1390,7 @@
         mBottomGestureHost = null;
         mTopFullscreenOpaqueWindowState = null;
         mNavBarColorWindowCandidate = null;
-        mNavBarBackgroundWindow = null;
+        mNavBarBackgroundWindowCandidate = null;
         mStatusBarAppearanceRegionList.clear();
         mLetterboxDetails.clear();
         mStatusBarBackgroundWindows.clear();
@@ -1514,8 +1517,8 @@
                     mNavBarColorWindowCandidate = win;
                     addSystemBarColorApp(win);
                 }
-                if (mNavBarBackgroundWindow == null) {
-                    mNavBarBackgroundWindow = win;
+                if (mNavBarBackgroundWindowCandidate == null) {
+                    mNavBarBackgroundWindowCandidate = win;
                 }
             }
 
@@ -1531,20 +1534,32 @@
             }
         } else if (win.isDimming()) {
             if (mStatusBar != null) {
-                if (addStatusBarAppearanceRegionsForDimmingWindow(
-                        win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
-                        mStatusBar.getFrame(), win.getBounds(), win.getFrame())) {
+                // If the dim window is below status bar window, we should update the appearance
+                // region if needed. Otherwise, leave it as it is.
+                final int statusBarLayer = mStatusBar.mToken.getWindowLayerFromType();
+                final int targetWindowLayer = win.mToken.getWindowLayerFromType();
+                if (targetWindowLayer < statusBarLayer
+                        && addStatusBarAppearanceRegionsForDimmingWindow(
+                                win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
+                                mStatusBar.getFrame(), win.getBounds(), win.getFrame())) {
                     addSystemBarColorApp(win);
                 }
             }
             if (isOverlappingWithNavBar(win) && mNavBarColorWindowCandidate == null) {
                 mNavBarColorWindowCandidate = win;
+                addSystemBarColorApp(win);
             }
-        } else if (appWindow && attached == null && mNavBarColorWindowCandidate == null
+        } else if (appWindow && attached == null
+                && (mNavBarColorWindowCandidate == null || mNavBarBackgroundWindowCandidate == null)
                 && win.getFrame().contains(
                         getBarContentFrameForWindow(win, Type.navigationBars()))) {
-            mNavBarColorWindowCandidate = win;
-            addSystemBarColorApp(win);
+            if (mNavBarColorWindowCandidate == null) {
+                mNavBarColorWindowCandidate = win;
+                addSystemBarColorApp(win);
+            }
+            if (mNavBarBackgroundWindowCandidate == null) {
+                mNavBarBackgroundWindowCandidate = win;
+            }
         }
     }
 
@@ -1686,7 +1701,8 @@
      * @return Whether the top fullscreen app hides the given type of system bar.
      */
     boolean topAppHidesSystemBar(@InsetsType int type) {
-        if (mTopFullscreenOpaqueWindowState == null || mForceShowSystemBars) {
+        if (mTopFullscreenOpaqueWindowState == null
+                || getInsetsPolicy().areTypesForciblyShowing(type)) {
             return false;
         }
         return !mTopFullscreenOpaqueWindowState.isRequestedVisible(type);
@@ -2172,7 +2188,11 @@
                 }
             }
         }
-        mImmersiveModeConfirmation.confirmCurrentPrompt();
+        if (CLIENT_IMMERSIVE_CONFIRMATION || CLIENT_TRANSIENT) {
+            mStatusBarManagerInternal.confirmImmersivePrompt();
+        } else {
+            mImmersiveModeConfirmation.confirmCurrentPrompt();
+        }
     }
 
     boolean isKeyguardShowing() {
@@ -2222,7 +2242,8 @@
 
         // Immersive mode confirmation should never affect the system bar visibility, otherwise
         // it will unhide the navigation bar and hide itself.
-        if (winCandidate.getAttrs().token == mImmersiveModeConfirmation.getWindowToken()) {
+        if ((winCandidate.getAttrs().privateFlags
+                & PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW) != 0) {
             if (mNotificationShade != null && mNotificationShade.canReceiveKeys()) {
                 // Let notification shade control the system bar visibility.
                 winCandidate = mNotificationShade;
@@ -2282,8 +2303,8 @@
                 && Arrays.equals(mLastLetterboxDetails, letterboxDetails)) {
             return;
         }
-        if (mDisplayContent.isDefaultDisplay && mLastFocusIsFullscreen != isFullscreen
-                && ((mLastAppearance ^ appearance) & APPEARANCE_LOW_PROFILE_BARS) != 0) {
+        if (mDisplayContent.isDefaultDisplay && (mLastFocusIsFullscreen != isFullscreen
+                || ((mLastAppearance ^ appearance) & APPEARANCE_LOW_PROFILE_BARS) != 0)) {
             mService.mInputManager.setSystemUiLightsOut(
                     isFullscreen || (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0);
         }
@@ -2363,14 +2384,7 @@
         final boolean freeformRootTaskVisible =
                 defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM);
 
-        // We need to force showing system bars when adjacent tasks or freeform roots visible.
-        mForceShowSystemBars = adjacentTasksVisible || freeformRootTaskVisible;
-        // We need to force the consumption of the system bars if they are force shown or if they
-        // are controlled by a remote insets controller.
-        mForceConsumeSystemBars = mForceShowSystemBars
-                || getInsetsPolicy().remoteInsetsControllerControlsSystemBars(win)
-                || getInsetsPolicy().forcesShowingNavigationBars(win);
-        mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
+        getInsetsPolicy().updateSystemBars(win, adjacentTasksVisible, freeformRootTaskVisible);
 
         final boolean topAppHidesStatusBar = topAppHidesSystemBar(Type.statusBars());
         if (getStatusBar() != null) {
@@ -2397,9 +2411,16 @@
             // The immersive confirmation window should be attached to the immersive window root.
             final RootDisplayArea root = win.getRootDisplayArea();
             final int rootDisplayAreaId = root == null ? FEATURE_UNDEFINED : root.mFeatureId;
-            mImmersiveModeConfirmation.immersiveModeChangedLw(rootDisplayAreaId, isImmersiveMode,
-                    mService.mPolicy.isUserSetupComplete(),
-                    isNavBarEmpty(disableFlags));
+            if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) {
+                mImmersiveModeConfirmation.immersiveModeChangedLw(rootDisplayAreaId,
+                        isImmersiveMode,
+                        mService.mPolicy.isUserSetupComplete(),
+                        isNavBarEmpty(disableFlags));
+            } else {
+                // TODO (b/277290737): Move this to the client side, instead of using a proxy.
+                callStatusBarSafely(statusBar -> statusBar.immersiveModeChanged(rootDisplayAreaId,
+                        isImmersiveMode));
+            }
         }
 
         // Show transient bars for panic if needed.
@@ -2484,7 +2505,7 @@
         return win.isFullyTransparentBarAllowed(getBarContentFrameForWindow(win, type));
     }
 
-    private boolean drawsBarBackground(WindowState win) {
+    private static boolean drawsBarBackground(WindowState win) {
         if (win == null) {
             return true;
         }
@@ -2524,7 +2545,14 @@
      */
     private int configureNavBarOpacity(int appearance, boolean multiWindowTaskVisible,
             boolean freeformRootTaskVisible) {
-        final boolean drawBackground = drawsBarBackground(mNavBarBackgroundWindow);
+        final WindowState navBackgroundWin = chooseNavigationBackgroundWindow(
+                mNavBarBackgroundWindowCandidate,
+                mDisplayContent.mInputMethodWindow,
+                mNavigationBarPosition);
+        final boolean drawBackground = navBackgroundWin != null
+                // There is no app window showing underneath nav bar. (e.g., The screen is locked.)
+                // Let system windows (ex: notification shade) draw nav bar background.
+                || mNavBarBackgroundWindowCandidate == null;
 
         if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) {
             if (drawBackground) {
@@ -2544,7 +2572,7 @@
             }
         }
 
-        if (!isFullyTransparentAllowed(mNavBarBackgroundWindow, Type.navigationBars())) {
+        if (!isFullyTransparentAllowed(navBackgroundWin, Type.navigationBars())) {
             appearance |= APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
         }
 
@@ -2555,11 +2583,26 @@
         return appearance & ~APPEARANCE_OPAQUE_NAVIGATION_BARS;
     }
 
+    @VisibleForTesting
+    @Nullable
+    static WindowState chooseNavigationBackgroundWindow(WindowState candidate,
+            WindowState imeWindow, @NavigationBarPosition int navBarPosition) {
+        if (imeWindow != null && imeWindow.isVisible() && navBarPosition == NAV_BAR_BOTTOM
+                && drawsBarBackground(imeWindow)) {
+            return imeWindow;
+        }
+        if (drawsBarBackground(candidate)) {
+            return candidate;
+        }
+        return null;
+    }
+
     private boolean isImmersiveMode(WindowState win) {
         if (win == null) {
             return false;
         }
-        if (win == getNotificationShade() || win.isActivityTypeDream()) {
+        if (win.mPolicy.getWindowLayerLw(win) > win.mPolicy.getWindowLayerFromTypeLw(
+                WindowManager.LayoutParams.TYPE_STATUS_BAR) || win.isActivityTypeDream()) {
             return false;
         }
         return getInsetsPolicy().hasHiddenSources(Type.navigationBars());
@@ -2590,16 +2633,39 @@
     void onPowerKeyDown(boolean isScreenOn) {
         // Detect user pressing the power button in panic when an application has
         // taken over the whole screen.
-        boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(isScreenOn,
-                SystemClock.elapsedRealtime(), isImmersiveMode(mSystemUiControllingWindow),
-                isNavBarEmpty(mLastDisableFlags));
+        boolean panic = false;
+        if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) {
+            panic = mImmersiveModeConfirmation.onPowerKeyDown(isScreenOn,
+                    SystemClock.elapsedRealtime(), isImmersiveMode(mSystemUiControllingWindow),
+                    isNavBarEmpty(mLastDisableFlags));
+        } else {
+            panic = isPowerKeyDownPanic(isScreenOn, SystemClock.elapsedRealtime(),
+                    isImmersiveMode(mSystemUiControllingWindow), isNavBarEmpty(mLastDisableFlags));
+        }
         if (panic) {
             mHandler.post(mHiddenNavPanic);
         }
     }
 
+    private boolean isPowerKeyDownPanic(boolean isScreenOn, long time, boolean inImmersiveMode,
+            boolean navBarEmpty) {
+        if (!isScreenOn && (time - mPanicTime < mPanicThresholdMs)) {
+            // turning the screen back on within the panic threshold
+            return !mImmersiveConfirmationWindowExists;
+        }
+        if (isScreenOn && inImmersiveMode && !navBarEmpty) {
+            // turning the screen off, remember if we were in immersive mode
+            mPanicTime = time;
+        } else {
+            mPanicTime = 0;
+        }
+        return false;
+    }
+
     void onVrStateChangedLw(boolean enabled) {
-        mImmersiveModeConfirmation.onVrStateChangedLw(enabled);
+        if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) {
+            mImmersiveModeConfirmation.onVrStateChangedLw(enabled);
+        }
     }
 
     /**
@@ -2612,7 +2678,9 @@
      *                      {@link ActivityManager#LOCK_TASK_MODE_PINNED}.
      */
     public void onLockTaskStateChangedLw(int lockTaskState) {
-        mImmersiveModeConfirmation.onLockTaskModeChangedLw(lockTaskState);
+        if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) {
+            mImmersiveModeConfirmation.onLockTaskModeChangedLw(lockTaskState);
+        }
     }
 
     /** Called when a {@link android.os.PowerManager#USER_ACTIVITY_EVENT_TOUCH} is sent. */
@@ -2629,7 +2697,11 @@
     }
 
     boolean onSystemUiSettingsChanged() {
-        return mImmersiveModeConfirmation.onSettingChanged(mService.mCurrentUserId);
+        if (CLIENT_TRANSIENT || CLIENT_IMMERSIVE_CONFIRMATION) {
+            return false;
+        } else {
+            return mImmersiveModeConfirmation.onSettingChanged(mService.mCurrentUserId);
+        }
     }
 
     /**
@@ -2727,9 +2799,9 @@
             pw.print(prefix); pw.print("mNavBarColorWindowCandidate=");
             pw.println(mNavBarColorWindowCandidate);
         }
-        if (mNavBarBackgroundWindow != null) {
-            pw.print(prefix); pw.print("mNavBarBackgroundWindow=");
-            pw.println(mNavBarBackgroundWindow);
+        if (mNavBarBackgroundWindowCandidate != null) {
+            pw.print(prefix); pw.print("mNavBarBackgroundWindowCandidate=");
+            pw.println(mNavBarBackgroundWindowCandidate);
         }
         if (mLastStatusBarAppearanceRegions != null) {
             pw.print(prefix); pw.println("mLastStatusBarAppearanceRegions=");
@@ -2843,7 +2915,9 @@
         mDisplayContent.mTransitionController.unregisterLegacyListener(mAppTransitionListener);
         mHandler.post(mGestureNavigationSettingsObserver::unregister);
         mHandler.post(mForceShowNavBarSettingsObserver::unregister);
-        mImmersiveModeConfirmation.release();
+        if (!CLIENT_TRANSIENT && !CLIENT_IMMERSIVE_CONFIRMATION) {
+            mImmersiveModeConfirmation.release();
+        }
         if (mService.mPointerLocationEnabled) {
             setPointerLocationEnabled(false);
         }
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index b681c19..ed3fad0 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -303,8 +303,7 @@
             mOrientationListener.setCurrentRotation(mRotation);
             mSettingsObserver = new SettingsObserver(uiHandler);
             mSettingsObserver.observe();
-            if (mSupportAutoRotation && mContext.getResources().getBoolean(
-                    R.bool.config_windowManagerHalfFoldAutoRotateOverride)) {
+            if (mSupportAutoRotation && isFoldable(mContext)) {
                 mFoldController = new FoldController();
             } else {
                 mFoldController = null;
@@ -314,6 +313,10 @@
         }
     }
 
+    private static boolean isFoldable(Context context) {
+        return context.getResources().getIntArray(R.array.config_foldedDeviceStates).length > 0;
+    }
+
     @VisibleForTesting
     @Nullable
     DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy(
@@ -1463,11 +1466,6 @@
             return false;
         }
 
-        // Do not show rotation choice when fold controller blocks rotation sensor
-        if (mFoldController != null && mFoldController.shouldIgnoreSensorRotation()) {
-            return false;
-        }
-
         // Don't show rotation choice if we are in tabletop or book modes.
         if (isTabletopAutoRotateOverrideEnabled()) return false;
 
@@ -1775,8 +1773,11 @@
         private SensorEventListener mHingeAngleSensorEventListener;
         private final Set<Integer> mTabletopRotations;
         private final Runnable mActivityBoundsUpdateCallback;
+        private final boolean mAllowHalfFoldAutoRotationOverride;
 
         FoldController() {
+            mAllowHalfFoldAutoRotationOverride = mContext.getResources().getBoolean(
+                    R.bool.config_windowManagerHalfFoldAutoRotateOverride);
             mTabletopRotations = new ArraySet<>();
             int[] tabletop_rotations = mContext.getResources().getIntArray(
                     R.array.config_deviceTabletopRotations);
@@ -1894,12 +1895,14 @@
         }
 
         boolean overrideFrozenRotation() {
-            return mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED;
+            return mAllowHalfFoldAutoRotationOverride
+                    && mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED;
         }
 
         boolean shouldRevertOverriddenRotation() {
             // When transitioning to open.
-            return mDeviceState == DeviceStateController.DeviceState.OPEN
+            return mAllowHalfFoldAutoRotationOverride
+                    && mDeviceState == DeviceStateController.DeviceState.OPEN
                     && !mShouldIgnoreSensorRotation // Ignore if the hinge angle still moving
                     && mInHalfFoldTransition
                     && mDisplayContent.getRotationReversionController().isOverrideActive(
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 2b34bb2..f96f99d 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -336,8 +336,7 @@
      * </ul>
      */
     private boolean isTreatmentEnabledForDisplay() {
-        return mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                    /* checkDeviceConfig */ true)
+        return mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled()
                 && mDisplayContent.getIgnoreOrientationRequest()
                 // TODO(b/225928882): Support camera compat rotation for external displays
                 && mDisplayContent.getDisplay().getType() == TYPE_INTERNAL;
diff --git a/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java
index 74494dd..de70c4d 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java
@@ -44,7 +44,7 @@
             @NonNull final DisplayRotation displayRotation,
             @NonNull final DisplayContent displayContent) {
         if (!letterboxConfiguration
-                .isDisplayRotationImmersiveAppCompatPolicyEnabled(/* checkDeviceConfig */ false)) {
+                .isDisplayRotationImmersiveAppCompatPolicyEnabledAtBuildTime()) {
             return null;
         }
 
@@ -87,8 +87,7 @@
      * @return {@code true}, if there is a need to lock screen rotation, {@code false} otherwise.
      */
     boolean isRotationLockEnforced(@Surface.Rotation final int proposedRotation) {
-        if (!mLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled(
-                /* checkDeviceConfig */ true)) {
+        if (!mLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled()) {
             return false;
         }
         synchronized (mDisplayContent.mWmService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 2583514..4a3d0c1 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -181,7 +181,11 @@
                     }
                 } finally {
                     if (surface != null) {
-                        surface.release();
+                        try (final SurfaceControl.Transaction transaction =
+                                mService.mTransactionFactory.get()) {
+                            transaction.remove(surface);
+                            transaction.apply();
+                        }
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 44d6768..98027bb 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -23,6 +23,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowStateProto.IDENTIFIER;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -217,10 +218,10 @@
                     mHostWindowState.mInputWindowHandle.getInputApplicationHandle());
         }
 
-        InputChannel openInputChannel() {
+        void openInputChannel(@NonNull InputChannel outInputChannel) {
             final String name = toString();
             mInputChannel = mWmService.mInputManager.createInputChannel(name);
-            return mInputChannel;
+            mInputChannel.copyTo(outInputChannel);
         }
 
         void onRemoved() {
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index 56edde0..b2ba9d1 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -19,11 +19,13 @@
 import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
 
 import android.animation.ArgbEvaluator;
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
@@ -40,7 +42,6 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -48,7 +49,6 @@
 import android.util.Slog;
 import android.view.Display;
 import android.view.Gravity;
-import android.view.IWindowManager;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -56,7 +56,6 @@
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.Button;
@@ -95,6 +94,10 @@
      */
     @Nullable
     private Context mWindowContext;
+    /**
+     * The root display area feature id that the {@link #mWindowContext} is attaching to.
+     */
+    private int mWindowContextRootDisplayAreaId = FEATURE_UNDEFINED;
     // Local copy of vr mode enabled state, to avoid calling into VrManager with
     // the lock held.
     private boolean mVrModeEnabled;
@@ -206,12 +209,15 @@
     private void handleHide() {
         if (mClingWindow != null) {
             if (DEBUG) Slog.d(TAG, "Hiding immersive mode confirmation");
-            // We don't care which root display area the window manager is specifying for removal.
-            try {
-                getWindowManager(FEATURE_UNDEFINED).removeView(mClingWindow);
-            } catch (WindowManager.InvalidDisplayException e) {
-                Slog.w(TAG, "Fail to hide the immersive confirmation window because of " + e);
-                return;
+            if (mWindowManager != null) {
+                try {
+                    mWindowManager.removeView(mClingWindow);
+                } catch (WindowManager.InvalidDisplayException e) {
+                    Slog.w(TAG, "Fail to hide the immersive confirmation window because of "
+                            + e);
+                }
+                mWindowManager = null;
+                mWindowContext = null;
             }
             mClingWindow = null;
         }
@@ -229,7 +235,8 @@
         lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~Type.statusBars());
         // Trusted overlay so touches outside the touchable area are allowed to pass through
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
-                | WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+                | WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+                | WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
         lp.setTitle("ImmersiveModeConfirmation");
         lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation;
         lp.token = getWindowToken();
@@ -394,26 +401,18 @@
      * @return the WindowManager specifying with the {@code rootDisplayAreaId} to attach the
      *         confirmation window.
      */
-    private WindowManager getWindowManager(int rootDisplayAreaId) {
-        if (mWindowManager == null || mWindowContext == null) {
-            // Create window context to specify the RootDisplayArea
-            final Bundle options = getOptionsForWindowContext(rootDisplayAreaId);
-            mWindowContext = mContext.createWindowContext(
-                    IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, options);
-            mWindowManager = mWindowContext.getSystemService(WindowManager.class);
-            return mWindowManager;
+    @NonNull
+    private WindowManager createWindowManager(int rootDisplayAreaId) {
+        if (mWindowManager != null) {
+            throw new IllegalStateException(
+                    "Must not create a new WindowManager while there is an existing one");
         }
-
-        // Update the window context and window manager to specify the RootDisplayArea
+        // Create window context to specify the RootDisplayArea
         final Bundle options = getOptionsForWindowContext(rootDisplayAreaId);
-        final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
-        try {
-            wms.attachWindowContextToDisplayArea(mWindowContext.getWindowContextToken(),
-                    IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, mContext.getDisplayId(), options);
-        }  catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
-
+        mWindowContextRootDisplayAreaId = rootDisplayAreaId;
+        mWindowContext = mContext.createWindowContext(
+                IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, options);
+        mWindowManager = mWindowContext.getSystemService(WindowManager.class);
         return mWindowManager;
     }
 
@@ -434,14 +433,23 @@
     }
 
     private void handleShow(int rootDisplayAreaId) {
+        if (mClingWindow != null) {
+            if (rootDisplayAreaId == mWindowContextRootDisplayAreaId) {
+                if (DEBUG) Slog.d(TAG, "Immersive mode confirmation has already been shown");
+                return;
+            } else {
+                // Hide the existing confirmation before show a new one in the new root.
+                if (DEBUG) Slog.d(TAG, "Immersive mode confirmation was shown in a different root");
+                handleHide();
+            }
+        }
+
         if (DEBUG) Slog.d(TAG, "Showing immersive mode confirmation");
-
         mClingWindow = new ClingWindowView(mContext, mConfirm);
-
         // show the confirmation
-        WindowManager.LayoutParams lp = getClingWindowLayoutParams();
+        final WindowManager.LayoutParams lp = getClingWindowLayoutParams();
         try {
-            getWindowManager(rootDisplayAreaId).addView(mClingWindow, lp);
+            createWindowManager(rootDisplayAreaId).addView(mClingWindow, lp);
         } catch (WindowManager.InvalidDisplayException e) {
             Slog.w(TAG, "Fail to show the immersive confirmation window because of " + e);
         }
@@ -469,6 +477,9 @@
 
         @Override
         public void handleMessage(Message msg) {
+            if (CLIENT_TRANSIENT) {
+                return;
+            }
             switch(msg.what) {
                 case SHOW:
                     handleShow(msg.arg1);
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 0a47fe0..73fdfe0 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -25,6 +25,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.gui.StalledTransactionInfo;
 import android.os.Debug;
 import android.os.IBinder;
 import android.util.Slog;
@@ -96,7 +97,7 @@
     @Override
     public void notifyNoFocusedWindowAnr(@NonNull InputApplicationHandle applicationHandle) {
         TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchNoFocusedWindow(
-                timeoutMessage("Application does not have a focused window"));
+                timeoutMessage(OptionalInt.empty(), "Application does not have a focused window"));
         mService.mAnrController.notifyAppUnresponsive(applicationHandle, timeoutRecord);
     }
 
@@ -104,7 +105,7 @@
     public void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
             String reason) {
         TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive(
-                timeoutMessage(reason));
+                timeoutMessage(pid, reason));
         mService.mAnrController.notifyWindowUnresponsive(token, pid, timeoutRecord);
     }
 
@@ -128,6 +129,21 @@
         }
     }
 
+    /** Notifies that the pointer location configuration has changed. */
+    @Override
+    public void notifyPointerLocationChanged(boolean pointerLocationEnabled) {
+        if (mService.mPointerLocationEnabled == pointerLocationEnabled) {
+            return;
+        }
+
+        synchronized (mService.mGlobalLock) {
+            mService.mPointerLocationEnabled = pointerLocationEnabled;
+            mService.mRoot.forAllDisplayPolicies(
+                    p -> p.setPointerLocationEnabled(mService.mPointerLocationEnabled)
+            );
+        }
+    }
+
     /** Notifies that the lid switch changed state. */
     @Override
     public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
@@ -339,11 +355,21 @@
         mService.mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen);
     }
 
-    private String timeoutMessage(String reason) {
-        if (reason == null) {
-            return "Input dispatching timed out";
+    private String timeoutMessage(OptionalInt pid, String reason) {
+        String message = (reason == null) ? "Input dispatching timed out."
+                : String.format("Input dispatching timed out (%s).", reason);
+        if (pid.isEmpty()) {
+            return message;
         }
-        return "Input dispatching timed out (" + reason + ")";
+        StalledTransactionInfo stalledTransactionInfo =
+                SurfaceControl.getStalledTransactionInfo(pid.getAsInt());
+        if (stalledTransactionInfo == null) {
+            return message;
+        }
+        return String.format("%s Buffer processing for the associated surface is stuck due to an "
+                + "unsignaled fence (window=%s, bufferId=0x%016X, frameNumber=%s). This "
+                + "potentially indicates a GPU hang.", message, stalledTransactionInfo.layerName,
+                stalledTransactionInfo.bufferId, stalledTransactionInfo.frameNumber);
     }
 
     void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index daa823c..835c92d 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -21,12 +21,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
-import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
-import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
-import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsSource.ID_IME;
-import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
@@ -39,16 +34,13 @@
 import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.res.Resources;
+import android.os.Handler;
+import android.os.IBinder;
 import android.util.SparseArray;
-import android.view.InsetsAnimationControlCallbacks;
-import android.view.InsetsAnimationControlImpl;
-import android.view.InsetsAnimationControlRunner;
 import android.view.InsetsController;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
-import android.view.InsetsSourceControl;
 import android.view.InsetsState;
-import android.view.InternalInsetsAnimationController;
 import android.view.SurfaceControl;
 import android.view.SyncRtSurfaceTransactionApplier;
 import android.view.WindowInsets;
@@ -56,14 +48,16 @@
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation;
 import android.view.WindowInsetsAnimation.Bounds;
-import android.view.WindowInsetsAnimationControlListener;
 import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.DisplayThread;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
+import java.io.PrintWriter;
+import java.util.List;
+
 /**
  * Policy that implements who gets control over the windows generating insets.
  */
@@ -77,47 +71,19 @@
     private final DisplayContent mDisplayContent;
     private final DisplayPolicy mPolicy;
 
-    /** For resetting visibilities of insets sources. */
-    private final InsetsControlTarget mDummyControlTarget = new InsetsControlTarget() {
+    /** Used to show system bars transiently. This won't affect the layout. */
+    private final InsetsControlTarget mTransientControlTarget;
 
-        @Override
-        public void notifyInsetsControlChanged() {
-            boolean hasLeash = false;
-            final InsetsSourceControl[] controls =
-                    mStateController.getControlsForDispatch(this);
-            if (controls == null) {
-                return;
-            }
-            for (InsetsSourceControl control : controls) {
-                if (isTransient(control.getType())) {
-                    // The visibilities of transient bars will be handled with animations.
-                    continue;
-                }
-                final SurfaceControl leash = control.getLeash();
-                if (leash != null) {
-                    hasLeash = true;
-
-                    // We use alpha to control the visibility here which aligns the logic at
-                    // SurfaceAnimator.createAnimationLeash
-                    final boolean visible =
-                            (control.getType() & WindowInsets.Type.defaultVisible()) != 0;
-                    mDisplayContent.getPendingTransaction().setAlpha(leash, visible ? 1f : 0f);
-                }
-            }
-            if (hasLeash) {
-                mDisplayContent.scheduleAnimation();
-            }
-        }
-    };
+    /** Used to show system bars permanently. This will affect the layout. */
+    private final InsetsControlTarget mPermanentControlTarget;
 
     private WindowState mFocusedWin;
     private final BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
     private final BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR);
     private @InsetsType int mShowingTransientTypes;
-    private boolean mAnimatingShown;
+    private @InsetsType int mForcedShowingTypes;
 
     private final boolean mHideNavBarForKeyboard;
-    private final float[] mTmpFloat9 = new float[9];
 
     InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) {
         mStateController = stateController;
@@ -125,9 +91,12 @@
         mPolicy = displayContent.getDisplayPolicy();
         final Resources r = mPolicy.getContext().getResources();
         mHideNavBarForKeyboard = r.getBoolean(R.bool.config_hideNavBarForKeyboard);
+        mTransientControlTarget = new ControlTarget(
+                stateController, displayContent.mWmService.mH, "TransientControlTarget");
+        mPermanentControlTarget = new ControlTarget(
+                stateController, displayContent.mWmService.mH, "PermanentControlTarget");
     }
 
-
     /** Updates the target which can control system bars. */
     void updateBarControlTarget(@Nullable WindowState focusedWin) {
         if (mFocusedWin != focusedWin) {
@@ -142,13 +111,13 @@
         final WindowState topApp = mPolicy.getTopFullscreenOpaqueWindow();
         mStateController.onBarControlTargetChanged(
                 statusControlTarget,
-                statusControlTarget == mDummyControlTarget
+                statusControlTarget == mTransientControlTarget
                         ? getStatusControlTarget(focusedWin, true /* fake */)
                         : statusControlTarget == notificationShade
                                 ? getStatusControlTarget(topApp, true /* fake */)
                                 : null,
                 navControlTarget,
-                navControlTarget == mDummyControlTarget
+                navControlTarget == mTransientControlTarget
                         ? getNavControlTarget(focusedWin, true /* fake */)
                         : navControlTarget == notificationShade
                                 ? getNavControlTarget(topApp, true /* fake */)
@@ -198,19 +167,19 @@
                     mFocusedWin,
                     (showingTransientTypes & (Type.statusBars() | Type.navigationBars())) != 0,
                     isGestureOnSystemBar);
-
-            // The leashes can be created while updating bar control target. The surface transaction
-            // of the new leashes might not be applied yet. The callback posted here ensures we can
-            // get the valid leashes because the surface transaction will be applied in the next
-            // animation frame which will be triggered if a new leash is created.
-            mDisplayContent.mWmService.mAnimator.getChoreographer().postFrameCallback(time -> {
-                synchronized (mDisplayContent.mWmService.mGlobalLock) {
-                    startAnimation(true /* show */, null /* callback */);
-                }
-            });
         }
     }
 
+    @VisibleForTesting
+    InsetsControlTarget getTransientControlTarget() {
+        return  mTransientControlTarget;
+    }
+
+    @VisibleForTesting
+    InsetsControlTarget getPermanentControlTarget() {
+        return  mPermanentControlTarget;
+    }
+
     void hideTransient() {
         if (mShowingTransientTypes == 0) {
             return;
@@ -221,23 +190,8 @@
                 /* areVisible= */ false,
                 /* wereRevealedFromSwipeOnSystemBar= */ false);
 
-        startAnimation(false /* show */, () -> {
-            synchronized (mDisplayContent.mWmService.mGlobalLock) {
-                final SparseArray<InsetsSourceProvider> providers =
-                        mStateController.getSourceProviders();
-                for (int i = providers.size() - 1; i >= 0; i--) {
-                    final InsetsSourceProvider provider = providers.valueAt(i);
-                    if (!isTransient(provider.getSource().getType())) {
-                        continue;
-                    }
-                    // We are about to clear mShowingTransientTypes, we don't want the transient bar
-                    // can cause insets on the client. Restore the client visibility.
-                    provider.setClientVisible(false);
-                }
-                mShowingTransientTypes = 0;
-                updateBarControlTarget(mFocusedWin);
-            }
-        });
+        mShowingTransientTypes = 0;
+        updateBarControlTarget(mFocusedWin);
     }
 
     boolean isTransient(@InsetsType int type) {
@@ -440,8 +394,8 @@
         return originalState;
     }
 
-    void onInsetsModified(InsetsControlTarget caller) {
-        mStateController.onInsetsModified(caller);
+    void onRequestedVisibleTypesChanged(InsetsControlTarget caller) {
+        mStateController.onRequestedVisibleTypesChanged(caller);
         checkAbortTransient(caller);
         updateBarControlTarget(mFocusedWin);
     }
@@ -500,7 +454,7 @@
     private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin,
             boolean fake) {
         if (!fake && isTransient(Type.statusBars())) {
-            return mDummyControlTarget;
+            return mTransientControlTarget;
         }
         final WindowState notificationShade = mPolicy.getNotificationShade();
         if (focusedWin == notificationShade) {
@@ -514,16 +468,16 @@
                     component, focusedWin.getRequestedVisibleTypes());
             return mDisplayContent.mRemoteInsetsControlTarget;
         }
-        if (mPolicy.areSystemBarsForcedShownLw()) {
+        if (areTypesForciblyShowing(Type.statusBars())) {
             // Status bar is forcibly shown. We don't want the client to control the status bar, and
             // we will dispatch the real visibility of status bar to the client.
-            return null;
+            return mPermanentControlTarget;
         }
         if (forceShowsStatusBarTransiently() && !fake) {
             // Status bar is forcibly shown transiently, and its new visibility won't be
             // dispatched to the client so that we can keep the layout stable. We will dispatch the
             // fake control to the client, so that it can re-show the bar during this scenario.
-            return mDummyControlTarget;
+            return mTransientControlTarget;
         }
         if (!canBeTopFullscreenOpaqueWindow(focusedWin)
                 && mPolicy.topAppHidesSystemBar(Type.statusBars())
@@ -554,7 +508,7 @@
             return null;
         }
         if (!fake && isTransient(Type.navigationBars())) {
-            return mDummyControlTarget;
+            return mTransientControlTarget;
         }
         if (focusedWin == mPolicy.getNotificationShade()) {
             // Notification shade has control anyways, no reason to force anything.
@@ -567,13 +521,6 @@
                 return focusedWin;
             }
         }
-        if (forcesShowingNavigationBars(focusedWin)) {
-            // When "force show navigation bar" is enabled, it means both force visible is true, and
-            // we are in 3-button navigation. In this mode, the navigation bar is forcibly shown
-            // when activity type is ACTIVITY_TYPE_STANDARD which means Launcher or Recent could
-            // still control the navigation bar in this mode.
-            return null;
-        }
         if (remoteInsetsControllerControlsSystemBars(focusedWin)) {
             ComponentName component = focusedWin.mActivityRecord != null
                     ? focusedWin.mActivityRecord.mActivityComponent : null;
@@ -581,16 +528,16 @@
                     component, focusedWin.getRequestedVisibleTypes());
             return mDisplayContent.mRemoteInsetsControlTarget;
         }
-        if (mPolicy.areSystemBarsForcedShownLw()) {
+        if (areTypesForciblyShowing(Type.navigationBars())) {
             // Navigation bar is forcibly shown. We don't want the client to control the navigation
             // bar, and we will dispatch the real visibility of navigation bar to the client.
-            return null;
+            return mPermanentControlTarget;
         }
         if (forceShowsNavigationBarTransiently() && !fake) {
             // Navigation bar is forcibly shown transiently, and its new visibility won't be
             // dispatched to the client so that we can keep the layout stable. We will dispatch the
             // fake control to the client, so that it can re-show the bar during this scenario.
-            return mDummyControlTarget;
+            return mTransientControlTarget;
         }
         final WindowState notificationShade = mPolicy.getNotificationShade();
         if (!canBeTopFullscreenOpaqueWindow(focusedWin)
@@ -603,7 +550,32 @@
         return focusedWin;
     }
 
-    boolean forcesShowingNavigationBars(WindowState win) {
+    boolean areTypesForciblyShowing(@InsetsType int types) {
+        return (mForcedShowingTypes & types) == types;
+    }
+
+    void updateSystemBars(WindowState win, boolean inSplitScreenMode, boolean inFreeformMode) {
+        mForcedShowingTypes = (inSplitScreenMode || inFreeformMode)
+                ? (Type.statusBars() | Type.navigationBars())
+                : forceShowingNavigationBars(win)
+                        ? Type.navigationBars()
+                        : 0;
+
+        // The client app won't be able to control these types of system bars. Here makes the client
+        // forcibly consume these types to prevent the app content from getting obscured.
+        mStateController.setForcedConsumingTypes(
+                mForcedShowingTypes | (remoteInsetsControllerControlsSystemBars(win)
+                        ? (Type.statusBars() | Type.navigationBars())
+                        : 0));
+
+        updateBarControlTarget(win);
+    }
+
+    private boolean forceShowingNavigationBars(WindowState win) {
+        // When "force show navigation bar" is enabled, it means both force visible is true, and
+        // we are in 3-button navigation. In this mode, the navigation bar is forcibly shown
+        // when activity type is ACTIVITY_TYPE_STANDARD which means Launcher or Recent could
+        // still control the navigation bar in this mode.
         return mPolicy.isForceShowNavigationBarEnabled() && win != null
                 && win.getActivityType() == ACTIVITY_TYPE_STANDARD;
     }
@@ -642,34 +614,6 @@
                 && (win.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
     }
 
-    @VisibleForTesting
-    void startAnimation(boolean show, Runnable callback) {
-        @InsetsType int typesReady = 0;
-        final SparseArray<InsetsSourceControl> controlsReady = new SparseArray<>();
-        final InsetsSourceControl[] controls =
-                mStateController.getControlsForDispatch(mDummyControlTarget);
-        if (controls == null) {
-            if (callback != null) {
-                DisplayThread.getHandler().post(callback);
-            }
-            return;
-        }
-        for (InsetsSourceControl control : controls) {
-            if (isTransient(control.getType()) && control.getLeash() != null) {
-                typesReady |= control.getType();
-                controlsReady.put(control.getId(), new InsetsSourceControl(control));
-            }
-        }
-        controlAnimationUnchecked(typesReady, controlsReady, show, callback);
-    }
-
-    private void controlAnimationUnchecked(int typesReady,
-            SparseArray<InsetsSourceControl> controls, boolean show, Runnable callback) {
-        InsetsPolicyAnimationControlListener listener =
-                new InsetsPolicyAnimationControlListener(show, callback, typesReady);
-        listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show);
-    }
-
     private void dispatchTransientSystemBarsVisibilityChanged(
             @Nullable WindowState focusedWindow,
             boolean areVisible,
@@ -696,6 +640,21 @@
                         wereRevealedFromSwipeOnSystemBar);
     }
 
+    void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "InsetsPolicy");
+        prefix = prefix + "  ";
+        pw.println(prefix + "status: " + StatusBarManager.windowStateToString(mStatusBar.mState));
+        pw.println(prefix + "nav: " + StatusBarManager.windowStateToString(mNavBar.mState));
+        if (mShowingTransientTypes != 0) {
+            pw.println(prefix + "mShowingTransientTypes="
+                    + WindowInsets.Type.toString(mShowingTransientTypes));
+        }
+        if (mForcedShowingTypes != 0) {
+            pw.println(prefix + "mForcedShowingTypes="
+                    + WindowInsets.Type.toString(mForcedShowingTypes));
+        }
+    }
+
     private class BarWindow {
 
         private final int mId;
@@ -725,105 +684,138 @@
         }
     }
 
-    private class InsetsPolicyAnimationControlListener extends
-            InsetsController.InternalAnimationControlListener {
-        Runnable mFinishCallback;
-        InsetsPolicyAnimationControlCallbacks mControlCallbacks;
+    private static class ControlTarget implements InsetsControlTarget {
 
-        InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
-            super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
-                    false /* disable */, 0 /* floatingImeBottomInsets */,
-                    null /* loggingListener */, null /* jankContext */);
-            mFinishCallback = finishCallback;
-            mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
+        private final InsetsState mState = new InsetsState();
+        private final InsetsController mInsetsController;
+        private final InsetsStateController mStateController;
+        private final String mName;
+
+        ControlTarget(InsetsStateController stateController, Handler handler, String name) {
+            mStateController = stateController;
+            mInsetsController = new InsetsController(new Host(handler, name));
+            mName = name;
         }
 
         @Override
-        protected void onAnimationFinish() {
-            super.onAnimationFinish();
-            if (mFinishCallback != null) {
-                DisplayThread.getHandler().post(mFinishCallback);
-            }
+        public void notifyInsetsControlChanged() {
+            mState.set(mStateController.getRawInsetsState(), true /* copySources */);
+            mInsetsController.onStateChanged(mState);
+            mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this));
         }
 
-        private class InsetsPolicyAnimationControlCallbacks implements
-                InsetsAnimationControlCallbacks {
-            private InsetsAnimationControlImpl mAnimationControl = null;
-            private InsetsPolicyAnimationControlListener mListener;
+        @Override
+        public String toString() {
+            return mName;
+        }
+    }
 
-            InsetsPolicyAnimationControlCallbacks(InsetsPolicyAnimationControlListener listener) {
-                mListener = listener;
+    private static class Host implements InsetsController.Host {
+
+        private final float[] mTmpFloat9 = new float[9];
+        private final Handler mHandler;
+        private final String mName;
+
+        Host(Handler handler, String name) {
+            mHandler = handler;
+            mName = name;
+        }
+
+        @Override
+        public Handler getHandler() {
+            return mHandler;
+        }
+
+        @Override
+        public void notifyInsetsChanged() { }
+
+        @Override
+        public void dispatchWindowInsetsAnimationPrepare(
+                @NonNull WindowInsetsAnimation animation) { }
+
+        @Override
+        public Bounds dispatchWindowInsetsAnimationStart(
+                @NonNull WindowInsetsAnimation animation,
+                @NonNull Bounds bounds) {
+            return bounds;
+        }
+
+        @Override
+        public WindowInsets dispatchWindowInsetsAnimationProgress(
+                @NonNull WindowInsets insets,
+                @NonNull List<WindowInsetsAnimation> runningAnimations) {
+            return insets;
+        }
+
+        @Override
+        public void dispatchWindowInsetsAnimationEnd(
+                @NonNull WindowInsetsAnimation animation) { }
+
+        @Override
+        public void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... p) {
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            for (int i = p.length - 1; i >= 0; i--) {
+                SyncRtSurfaceTransactionApplier.applyParams(t, p[i], mTmpFloat9);
             }
+            t.apply();
+            t.close();
+        }
 
-            private void controlAnimationUnchecked(int typesReady,
-                    SparseArray<InsetsSourceControl> controls, boolean show) {
-                if (typesReady == 0) {
-                    // nothing to animate.
-                    return;
-                }
-                mAnimatingShown = show;
+        @Override
+        public void updateRequestedVisibleTypes(int types) { }
 
-                final InsetsState state = mFocusedWin.getInsetsState();
+        @Override
+        public boolean hasAnimationCallbacks() {
+            return false;
+        }
 
-                // We are about to playing the default animation. Passing a null frame indicates
-                // the controlled types should be animated regardless of the frame.
-                mAnimationControl = new InsetsAnimationControlImpl(controls,
-                        null /* frame */, state, mListener, typesReady, this,
-                        mListener.getDurationMs(), getInsetsInterpolator(),
-                        show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show
-                                ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
-                                : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
-                        null /* translator */, null /* statsToken */);
-                SurfaceAnimationThread.getHandler().post(
-                        () -> mListener.onReady(mAnimationControl, typesReady));
-            }
+        @Override
+        public void setSystemBarsAppearance(int appearance, int mask) { }
 
-            /** Called on SurfaceAnimationThread without global WM lock held. */
-            @Override
-            public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) {
-                if (mAnimationControl.applyChangeInsets(null /* outState */)) {
-                    mAnimationControl.finish(mAnimatingShown);
-                }
-            }
+        @Override
+        public int getSystemBarsAppearance() {
+            return 0;
+        }
 
-            @Override
-            public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) {
-                // Nothing's needed here. Finish steps is handled in the listener
-                // onAnimationFinished callback.
-            }
+        @Override
+        public void setSystemBarsBehavior(int behavior) { }
 
-            /** Called on SurfaceAnimationThread without global WM lock held. */
-            @Override
-            public void applySurfaceParams(
-                    final SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
-                SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-                for (int i = params.length - 1; i >= 0; i--) {
-                    SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i];
-                    applyParams(t, surfaceParams, mTmpFloat9);
-                }
-                t.apply();
-                t.close();
-            }
+        @Override
+        public int getSystemBarsBehavior() {
+            return BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+        }
 
-            // Since we don't push applySurfaceParams to a Handler-queue we don't need
-            // to push release in this case.
-            @Override
-            public void releaseSurfaceControlFromRt(SurfaceControl sc) {
-                sc.release();
-            }
+        @Override
+        public void releaseSurfaceControlFromRt(SurfaceControl surfaceControl) {
+            surfaceControl.release();
+        }
 
-            @Override
-            public <T extends InsetsAnimationControlRunner & InternalInsetsAnimationController>
-            void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types,
-                    WindowInsetsAnimation animation,
-                    Bounds bounds) {
-            }
+        @Override
+        public void addOnPreDrawRunnable(Runnable r) { }
 
-            @Override
-            public void reportPerceptible(int types, boolean perceptible) {
-                // No-op for now - only client windows report perceptibility for now, with policy
-                // controllers assumed to always be perceptible.
-            }
+        @Override
+        public void postInsetsAnimationCallback(Runnable r) { }
+
+        @Override
+        public InputMethodManager getInputMethodManager() {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public String getRootViewTitle() {
+            return mName;
+        }
+
+        @Override
+        public int dipToPx(int dips) {
+            return 0;
+        }
+
+        @Nullable
+        @Override
+        public IBinder getWindowToken() {
+            return null;
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 5e2618b..02f5c21 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -40,6 +40,7 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsSource;
+import android.view.InsetsSource.Flags;
 import android.view.InsetsSourceControl;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -81,6 +82,9 @@
     private final Rect mSourceFrame = new Rect();
     private final Rect mLastSourceFrame = new Rect();
     private @NonNull Insets mInsetsHint = Insets.NONE;
+    private boolean mInsetsHintStale = true;
+    private @Flags int mFlagsFromFrameProvider;
+    private @Flags int mFlagsFromServer;
 
     private final Consumer<Transaction> mSetLeashPositionConsumer = t -> {
         if (mControl != null) {
@@ -175,7 +179,7 @@
         if (windowContainer == null) {
             setServerVisible(false);
             mSource.setVisibleFrame(null);
-            mSource.setInsetsRoundedCornerFrame(false);
+            mSource.setFlags(0, 0xffffffff);
             mSourceFrame.setEmpty();
         } else {
             mWindowContainer.getInsetsSourceProviders().put(mSource.getId(), this);
@@ -189,6 +193,16 @@
         }
     }
 
+    boolean setFlags(@Flags int flags, @Flags int mask) {
+        mFlagsFromServer = (mFlagsFromServer & ~mask) | (flags & mask);
+        final @Flags int mergedFlags = mFlagsFromFrameProvider | mFlagsFromServer;
+        if (mSource.getFlags() != mergedFlags) {
+            mSource.setFlags(mergedFlags);
+            return true;
+        }
+        return false;
+    }
+
     /**
      * The source frame can affect the layout of other windows, so this should be called once the
      * window container gets laid out.
@@ -217,13 +231,17 @@
 
         mSourceFrame.set(frame);
         if (mFrameProvider != null) {
-            final int flags = mFrameProvider.apply(
+            mFlagsFromFrameProvider = mFrameProvider.apply(
                     mWindowContainer.getDisplayContent().mDisplayFrames,
                     mWindowContainer,
                     mSourceFrame);
-            mSource.setFlags(flags);
+            mSource.setFlags(mFlagsFromFrameProvider | mFlagsFromServer);
         }
         updateSourceFrameForServerVisibility();
+        if (!mLastSourceFrame.equals(mSourceFrame)) {
+            mLastSourceFrame.set(mSourceFrame);
+            mInsetsHintStale = true;
+        }
 
         if (mOverrideFrameProviders != null) {
             // Not necessary to clear the mOverrideFrames here. It will be cleared every time the
@@ -270,6 +288,27 @@
         }
     }
 
+    void onWindowContainerBoundsChanged() {
+        mInsetsHintStale = true;
+    }
+
+    @VisibleForTesting
+    Insets getInsetsHint() {
+        if (!mServerVisible) {
+            return mInsetsHint;
+        }
+        final WindowState win = mWindowContainer.asWindowState();
+        if (win != null && win.mGivenInsetsPending) {
+            return mInsetsHint;
+        }
+        if (mInsetsHintStale) {
+            final Rect bounds = mWindowContainer.getBounds();
+            mInsetsHint = mSource.calculateInsets(bounds, true /* ignoreVisibility */);
+            mInsetsHintStale = false;
+        }
+        return mInsetsHint;
+    }
+
     /** @return A new source computed by the specified window frame in the given display frames. */
     InsetsSource createSimulatedSource(DisplayFrames displayFrames, Rect frame) {
         final InsetsSource source = new InsetsSource(mSource);
@@ -325,15 +364,10 @@
                     mSetLeashPositionConsumer.accept(t);
                 }
             }
-            if (mServerVisible && !mLastSourceFrame.equals(mSource.getFrame())) {
-                final Insets insetsHint = mSource.calculateInsets(
-                        mWindowContainer.getBounds(), true /* ignoreVisibility */);
-                if (!insetsHint.equals(mControl.getInsetsHint())) {
-                    changed = true;
-                    mControl.setInsetsHint(insetsHint);
-                    mInsetsHint = insetsHint;
-                }
-                mLastSourceFrame.set(mSource.getFrame());
+            final Insets insetsHint = getInsetsHint();
+            if (!mControl.getInsetsHint().equals(insetsHint)) {
+                mControl.setInsetsHint(insetsHint);
+                changed = true;
             }
             if (changed) {
                 mStateController.notifyControlChanged(mControlTarget);
@@ -466,7 +500,7 @@
         mControlTarget = target;
         updateVisibility();
         mControl = new InsetsSourceControl(mSource.getId(), mSource.getType(), leash,
-                mClientVisible, surfacePosition, mInsetsHint);
+                mClientVisible, surfacePosition, getInsetsHint());
 
         ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
                 "InsetsSource Control %s for target %s", mControl, mControlTarget);
@@ -574,6 +608,14 @@
             pw.print(prefix + "mControl=");
             mControl.dump("", pw);
         }
+        if (mControllable) {
+            pw.print(prefix + "mInsetsHint=");
+            pw.print(mInsetsHint);
+            if (mInsetsHintStale) {
+                pw.print(" stale");
+            }
+            pw.println();
+        }
         pw.print(prefix);
         pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
         pw.println();
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 249ead0..081ebe0 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.InsetsSource.ID_IME;
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
@@ -85,6 +86,8 @@
         }
     };
 
+    private @InsetsType int mForcedConsumingTypes;
+
     InsetsStateController(DisplayContent displayContent) {
         mDisplayContent = displayContent;
     }
@@ -122,6 +125,11 @@
         provider = id == ID_IME
                 ? new ImeInsetsSourceProvider(source, this, mDisplayContent)
                 : new InsetsSourceProvider(source, this, mDisplayContent);
+        provider.setFlags(
+                (mForcedConsumingTypes & type) != 0
+                        ? FLAG_FORCE_CONSUMING
+                        : 0,
+                FLAG_FORCE_CONSUMING);
         mProviders.put(id, provider);
         return provider;
     }
@@ -137,6 +145,24 @@
         }
     }
 
+    void setForcedConsumingTypes(@InsetsType int types) {
+        if (mForcedConsumingTypes != types) {
+            mForcedConsumingTypes = types;
+            boolean changed = false;
+            for (int i = mProviders.size() - 1; i >= 0; i--) {
+                final InsetsSourceProvider provider = mProviders.valueAt(i);
+                changed |= provider.setFlags(
+                        (types & provider.getSource().getType()) != 0
+                                ? FLAG_FORCE_CONSUMING
+                                : 0,
+                        FLAG_FORCE_CONSUMING);
+            }
+            if (changed) {
+                notifyInsetsChanged();
+            }
+        }
+    }
+
     /**
      * Called when a layout pass has occurred.
      */
@@ -190,7 +216,7 @@
         }
     }
 
-    void onInsetsModified(InsetsControlTarget caller) {
+    void onRequestedVisibleTypesChanged(InsetsControlTarget caller) {
         boolean changed = false;
         for (int i = mProviders.size() - 1; i >= 0; i--) {
             changed |= mProviders.valueAt(i).updateClientVisibility(caller);
@@ -352,7 +378,7 @@
             // to the clients, so that the clients can change the current visibilities to the
             // requested visibilities with animations.
             for (int i = newControlTargets.size() - 1; i >= 0; i--) {
-                onInsetsModified(newControlTargets.valueAt(i));
+                onRequestedVisibleTypesChanged(newControlTargets.valueAt(i));
             }
             newControlTargets.clear();
         });
@@ -391,6 +417,10 @@
         for (int i = mProviders.size() - 1; i >= 0; i--) {
             mProviders.valueAt(i).dump(pw, prefix + "  ");
         }
+        if (mForcedConsumingTypes != 0) {
+            pw.println(prefix + "mForcedConsumingTypes="
+                    + WindowInsets.Type.toString(mForcedConsumingTypes));
+        }
     }
 
     void dumpDebug(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel) {
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index e74e5787..91bb8d0 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -64,7 +64,7 @@
     void registerDefaultModifiers(ActivityTaskSupervisor supervisor) {
         // {@link TaskLaunchParamsModifier} handles window layout preferences.
         registerModifier(new TaskLaunchParamsModifier(supervisor));
-        if (DesktopModeLaunchParamsModifier.DESKTOP_MODE_SUPPORTED) {
+        if (DesktopModeLaunchParamsModifier.isDesktopModeSupported()) {
             // {@link DesktopModeLaunchParamsModifier} handles default task size changes
             registerModifier(new DesktopModeLaunchParamsModifier());
         }
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index bf511adf0..2394da9 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -433,7 +433,7 @@
             final byte[] data = saveParamsToXml();
 
             final File launchParamFolder = getLaunchParamFolder(mUserId);
-            if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdirs()) {
+            if (!launchParamFolder.isDirectory() && !launchParamFolder.mkdir()) {
                 Slog.w(TAG, "Failed to create folder for " + mUserId);
                 return;
             }
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 3551370..f9fa9e6 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -20,7 +20,6 @@
 import static android.view.SurfaceControl.HIDDEN;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_LETTERBOX_BACKGROUND;
 
-import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -256,11 +255,11 @@
         private final GestureDetector mDoubleTapDetector;
         private final DoubleTapListener mDoubleTapListener;
 
-        TapEventReceiver(InputChannel inputChannel, Context context) {
+        TapEventReceiver(InputChannel inputChannel, WindowManagerService wmService) {
             super(inputChannel, UiThread.getHandler().getLooper());
-            mDoubleTapListener = new DoubleTapListener();
+            mDoubleTapListener = new DoubleTapListener(wmService);
             mDoubleTapDetector = new GestureDetector(
-                    context, mDoubleTapListener, UiThread.getHandler());
+                    wmService.mContext, mDoubleTapListener, UiThread.getHandler());
         }
 
         @Override
@@ -271,14 +270,24 @@
     }
 
     private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
+        private final WindowManagerService mWmService;
+
+        private DoubleTapListener(WindowManagerService wmService) {
+            mWmService = wmService;
+        }
+
         @Override
         public boolean onDoubleTapEvent(MotionEvent e) {
-            if (e.getAction() == MotionEvent.ACTION_UP) {
-                mDoubleTapCallbackX.accept((int) e.getRawX());
-                mDoubleTapCallbackY.accept((int) e.getRawY());
-                return true;
+            synchronized (mWmService.mGlobalLock) {
+                // This check prevents late events to be handled in case the Letterbox has been
+                // already destroyed and so mOuter.isEmpty() is true.
+                if (!mOuter.isEmpty() && e.getAction() == MotionEvent.ACTION_UP) {
+                    mDoubleTapCallbackX.accept((int) e.getRawX());
+                    mDoubleTapCallbackY.accept((int) e.getRawY());
+                    return true;
+                }
+                return false;
             }
-            return false;
         }
     }
 
@@ -294,7 +303,7 @@
             mWmService = win.mWmService;
             final String name = namePrefix + (win.mActivityRecord != null ? win.mActivityRecord : win);
             mClientChannel = mWmService.mInputManager.createInputChannel(name);
-            mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService.mContext);
+            mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService);
 
             mToken = mClientChannel.getToken();
 
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 067a18c..9e3a611 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -18,11 +18,6 @@
 
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ALLOW_IGNORE_ORIENTATION_REQUEST;
-import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_CAMERA_COMPAT_TREATMENT;
-import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_COMPAT_FAKE_FOCUS;
-import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
-import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -44,6 +39,64 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxConfiguration" : TAG_ATM;
 
+    // Whether camera compatibility treatment is enabled.
+    // See DisplayRotationCompatPolicy for context.
+    private static final String KEY_ENABLE_CAMERA_COMPAT_TREATMENT =
+            "enable_compat_camera_treatment";
+
+    private static final boolean DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT = true;
+
+    // Whether enabling rotation compat policy for immersive apps that prevents auto
+    // rotation into non-optimal screen orientation while in fullscreen. This is needed
+    // because immersive apps, such as games, are often not optimized for all
+    // orientations and can have a poor UX when rotated. Additionally, some games rely
+    // on sensors for the gameplay so  users can trigger such rotations accidentally
+    // when auto rotation is on.
+    private static final String KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
+            "enable_display_rotation_immersive_app_compat_policy";
+
+    private static final boolean DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
+            true;
+
+    // Whether ignore orientation request is allowed
+    private static final String KEY_ALLOW_IGNORE_ORIENTATION_REQUEST =
+            "allow_ignore_orientation_request";
+
+    private static final boolean DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST = true;
+
+    // Whether sending compat fake focus is enabled for unfocused apps in splitscreen.
+    // Some game engines wait to get focus before drawing the content of the app so
+    // this needs  to be used otherwise the apps get blacked out when they are resumed
+    // and do not have focus yet.
+    private static final String KEY_ENABLE_COMPAT_FAKE_FOCUS = "enable_compat_fake_focus";
+
+    private static final boolean DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS = true;
+
+    // Whether translucent activities policy is enabled
+    private static final String KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY =
+            "enable_letterbox_translucent_activity";
+
+    private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY = true;
+
+    // Whether per-app user aspect ratio override settings is enabled
+    private static final String KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS =
+            "enable_app_compat_user_aspect_ratio_settings";
+
+    // TODO(b/288142656): Enable user aspect ratio settings by default.
+    private static final boolean DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_SETTINGS = false;
+
+    // Whether per-app fullscreen user aspect ratio override option is enabled
+    private static final String KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN =
+            "enable_app_compat_user_aspect_ratio_fullscreen";
+    private static final boolean DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_FULLSCREEN = true;
+
+    // Whether the letterbox wallpaper style is enabled by default
+    private static final String KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER =
+            "enable_letterbox_background_wallpaper";
+
+    // TODO(b/290048978): Enable wallpaper as default letterbox background.
+    private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER = false;
+
     /**
      * Override of aspect ratio for fixed orientation letterboxing that is set via ADB with
      * set-fixed-orientation-letterbox-aspect-ratio or via {@link
@@ -60,9 +113,16 @@
 
     /** Enum for Letterbox background type. */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({LETTERBOX_BACKGROUND_SOLID_COLOR, LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND,
-            LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING, LETTERBOX_BACKGROUND_WALLPAPER})
+    @IntDef({LETTERBOX_BACKGROUND_OVERRIDE_UNSET,
+            LETTERBOX_BACKGROUND_SOLID_COLOR,
+            LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND,
+            LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING,
+            LETTERBOX_BACKGROUND_WALLPAPER})
     @interface LetterboxBackgroundType {};
+
+    /** No letterbox background style set. Using the one defined by DeviceConfig. */
+    static final int LETTERBOX_BACKGROUND_OVERRIDE_UNSET = -1;
+
     /** Solid background using color specified in R.color.config_letterboxBackgroundColor. */
     static final int LETTERBOX_BACKGROUND_SOLID_COLOR = 0;
 
@@ -142,14 +202,14 @@
     @Nullable private Integer mLetterboxBackgroundColorResourceIdOverride;
 
     @LetterboxBackgroundType
-    private int mLetterboxBackgroundType;
+    private final int mLetterboxBackgroundType;
 
-    // Blur radius for LETTERBOX_BACKGROUND_WALLPAPER option in mLetterboxBackgroundType.
+    // Blur radius for LETTERBOX_BACKGROUND_WALLPAPER option from getLetterboxBackgroundType().
     // Values <= 0 are ignored and 0 is used instead.
-    private int mLetterboxBackgroundWallpaperBlurRadius;
+    private int mLetterboxBackgroundWallpaperBlurRadiusPx;
 
     // Alpha of a black scrim shown over wallpaper letterbox background when
-    // LETTERBOX_BACKGROUND_WALLPAPER option is selected for mLetterboxBackgroundType.
+    // LETTERBOX_BACKGROUND_WALLPAPER option is returned from getLetterboxBackgroundType().
     // Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead.
     private float mLetterboxBackgroundWallpaperDarkScrimAlpha;
 
@@ -205,26 +265,24 @@
     // unresizable apps
     private boolean mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox;
 
-    // Whether letterboxing strategy is enabled for translucent activities. If {@value false}
-    // all the feature is disabled
-    private boolean mTranslucentLetterboxingEnabled;
-
     // Allows to enable letterboxing strategy for translucent activities ignoring flags.
     private boolean mTranslucentLetterboxingOverrideEnabled;
 
-    // Whether sending compat fake focus is enabled for unfocused apps in splitscreen. Some game
-    // engines wait to get focus before drawing the content of the app so this needs to be used
-    // otherwise the apps get blacked out when they are resumed and do not have focus yet.
-    private boolean mIsCompatFakeFocusEnabled;
+    // Allows to enable user aspect ratio settings ignoring flags.
+    private boolean mUserAppAspectRatioSettingsOverrideEnabled;
+
+    // Allows to enable fullscreen option in user aspect ratio settings ignoring flags.
+    private boolean mUserAppAspectRatioFullscreenOverrideEnabled;
+
+    // The override for letterbox background type in case it's different from
+    // LETTERBOX_BACKGROUND_OVERRIDE_UNSET
+    @LetterboxBackgroundType
+    private int mLetterboxBackgroundTypeOverride = LETTERBOX_BACKGROUND_OVERRIDE_UNSET;
 
     // Whether we should use split screen aspect ratio for the activity when camera compat treatment
     // is enabled and activity is connected to the camera in fullscreen.
     private final boolean mIsCameraCompatSplitScreenAspectRatioEnabled;
 
-    // Whether camera compatibility treatment is enabled.
-    // See DisplayRotationCompatPolicy for context.
-    private final boolean mIsCameraCompatTreatmentEnabled;
-
     // Whether activity "refresh" in camera compatibility treatment is enabled.
     // See RefreshCallbackItem for context.
     private boolean mIsCameraCompatTreatmentRefreshEnabled = true;
@@ -240,41 +298,32 @@
     // LetterboxUiController#shouldIgnoreRequestedOrientation for details.
     private final boolean mIsPolicyForIgnoringRequestedOrientationEnabled;
 
-    // Whether enabling rotation compat policy for immersive apps that prevents auto rotation
-    // into non-optimal screen orientation while in fullscreen. This is needed because immersive
-    // apps, such as games, are often not optimized for all orientations and can have a poor UX
-    // when rotated. Additionally, some games rely on sensors for the gameplay so users can trigger
-    // such rotations accidentally when auto rotation is on.
-    private final boolean mIsDisplayRotationImmersiveAppCompatPolicyEnabled;
-
     // Flags dynamically updated with {@link android.provider.DeviceConfig}.
-    @NonNull private final LetterboxConfigurationDeviceConfig mDeviceConfig;
+    @NonNull private final SynchedDeviceConfig mDeviceConfig;
 
     LetterboxConfiguration(@NonNull final Context systemUiContext) {
-        this(systemUiContext,
-                new LetterboxConfigurationPersister(systemUiContext,
-                        () -> readLetterboxHorizontalReachabilityPositionFromConfig(
-                                systemUiContext, /* forBookMode */ false),
-                        () -> readLetterboxVerticalReachabilityPositionFromConfig(
-                                systemUiContext, /* forTabletopMode */ false),
-                        () -> readLetterboxHorizontalReachabilityPositionFromConfig(
-                                systemUiContext, /* forBookMode */ true),
-                        () -> readLetterboxVerticalReachabilityPositionFromConfig(
-                                systemUiContext, /* forTabletopMode */ true)));
+        this(systemUiContext, new LetterboxConfigurationPersister(
+                () -> readLetterboxHorizontalReachabilityPositionFromConfig(
+                        systemUiContext, /* forBookMode */ false),
+                () -> readLetterboxVerticalReachabilityPositionFromConfig(
+                        systemUiContext, /* forTabletopMode */ false),
+                () -> readLetterboxHorizontalReachabilityPositionFromConfig(
+                        systemUiContext, /* forBookMode */ true),
+                () -> readLetterboxVerticalReachabilityPositionFromConfig(
+                        systemUiContext, /* forTabletopMode */ true)));
     }
 
     @VisibleForTesting
     LetterboxConfiguration(@NonNull final Context systemUiContext,
             @NonNull final LetterboxConfigurationPersister letterboxConfigurationPersister) {
         mContext = systemUiContext;
-        mDeviceConfig = new LetterboxConfigurationDeviceConfig(systemUiContext.getMainExecutor());
 
         mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
                 R.dimen.config_fixedOrientationLetterboxAspectRatio);
+        mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
         mLetterboxActivityCornersRadius = mContext.getResources().getInteger(
                 R.integer.config_letterboxActivityCornersRadius);
-        mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
-        mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize(
+        mLetterboxBackgroundWallpaperBlurRadiusPx = mContext.getResources().getDimensionPixelSize(
                 R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
         mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat(
                 R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
@@ -305,36 +354,44 @@
         mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox = mContext.getResources()
                 .getBoolean(R.bool
                         .config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled);
-        mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean(
-                R.bool.config_letterboxIsEnabledForTranslucentActivities);
-        mIsCameraCompatTreatmentEnabled = mContext.getResources().getBoolean(
-                R.bool.config_isWindowManagerCameraCompatTreatmentEnabled);
         mIsCameraCompatSplitScreenAspectRatioEnabled = mContext.getResources().getBoolean(
                 R.bool.config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled);
-        mIsCompatFakeFocusEnabled = mContext.getResources().getBoolean(
-                R.bool.config_isCompatFakeFocusEnabled);
         mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled);
-        mIsDisplayRotationImmersiveAppCompatPolicyEnabled = mContext.getResources().getBoolean(
-                R.bool.config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled);
-        mDeviceConfig.updateFlagActiveStatus(
-                /* isActive */ mIsCameraCompatTreatmentEnabled,
-                /* key */ KEY_ENABLE_CAMERA_COMPAT_TREATMENT);
-        mDeviceConfig.updateFlagActiveStatus(
-                /* isActive */ mIsDisplayRotationImmersiveAppCompatPolicyEnabled,
-                /* key */ KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY);
-        mDeviceConfig.updateFlagActiveStatus(
-                /* isActive */ true,
-                /* key */ KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
-        mDeviceConfig.updateFlagActiveStatus(
-                /* isActive */ mIsCompatFakeFocusEnabled,
-                /* key */ KEY_ENABLE_COMPAT_FAKE_FOCUS);
-        mDeviceConfig.updateFlagActiveStatus(
-                /* isActive */ mTranslucentLetterboxingEnabled,
-                /* key */ KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY);
 
         mLetterboxConfigurationPersister = letterboxConfigurationPersister;
         mLetterboxConfigurationPersister.start();
+
+        mDeviceConfig = SynchedDeviceConfig.builder(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                        systemUiContext.getMainExecutor())
+                .addDeviceConfigEntry(KEY_ENABLE_CAMERA_COMPAT_TREATMENT,
+                        DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT,
+                        mContext.getResources().getBoolean(
+                                R.bool.config_isWindowManagerCameraCompatTreatmentEnabled))
+                .addDeviceConfigEntry(KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
+                        DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
+                        mContext.getResources().getBoolean(R.bool
+                                .config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled))
+                .addDeviceConfigEntry(KEY_ALLOW_IGNORE_ORIENTATION_REQUEST,
+                        DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST, /* enabled */ true)
+                .addDeviceConfigEntry(KEY_ENABLE_COMPAT_FAKE_FOCUS,
+                        DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS,
+                        mContext.getResources().getBoolean(R.bool.config_isCompatFakeFocusEnabled))
+                .addDeviceConfigEntry(KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY,
+                        DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY,
+                        mContext.getResources().getBoolean(
+                                R.bool.config_letterboxIsEnabledForTranslucentActivities))
+                .addDeviceConfigEntry(KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS,
+                        DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_SETTINGS,
+                        mContext.getResources().getBoolean(
+                                R.bool.config_appCompatUserAppAspectRatioSettingsIsEnabled))
+                .addDeviceConfigEntry(KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER,
+                        DEFAULT_VALUE_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER, /* enabled */ true)
+                .addDeviceConfigEntry(KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN,
+                        DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_FULLSCREEN,
+                        mContext.getResources().getBoolean(
+                                R.bool.config_appCompatUserAppAspectRatioFullscreenIsEnabled))
+                .build();
     }
 
     /**
@@ -342,7 +399,7 @@
      * via {@link android.provider.DeviceConfig}.
      */
     boolean isIgnoreOrientationRequestAllowed() {
-        return mDeviceConfig.getFlag(KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
+        return mDeviceConfig.getFlagValue(KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
     }
 
     /**
@@ -472,25 +529,39 @@
     }
 
     /**
-     * Gets {@link LetterboxBackgroundType} specified in {@link
-     * com.android.internal.R.integer.config_letterboxBackgroundType} or over via ADB command.
+     * Gets {@link LetterboxBackgroundType} specified via ADB command or the default one.
      */
     @LetterboxBackgroundType
     int getLetterboxBackgroundType() {
-        return mLetterboxBackgroundType;
+        return mLetterboxBackgroundTypeOverride != LETTERBOX_BACKGROUND_OVERRIDE_UNSET
+                ? mLetterboxBackgroundTypeOverride
+                : getDefaultLetterboxBackgroundType();
     }
 
-    /** Sets letterbox background type. */
-    void setLetterboxBackgroundType(@LetterboxBackgroundType int backgroundType) {
-        mLetterboxBackgroundType = backgroundType;
+    /** Overrides the letterbox background type. */
+    void setLetterboxBackgroundTypeOverride(@LetterboxBackgroundType int backgroundType) {
+        mLetterboxBackgroundTypeOverride = backgroundType;
     }
 
     /**
-     * Resets cletterbox background type to {@link
-     * com.android.internal.R.integer.config_letterboxBackgroundType}.
+     * Resets letterbox background type value depending on the
+     * {@link #KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER} built time and runtime flags.
+     *
+     * <p>If enabled, the letterbox background type value is set toZ
+     * {@link #LETTERBOX_BACKGROUND_WALLPAPER}. When disabled the letterbox background type value
+     * comes from {@link R.integer.config_letterboxBackgroundType}.
      */
     void resetLetterboxBackgroundType() {
-        mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
+        mLetterboxBackgroundTypeOverride = LETTERBOX_BACKGROUND_OVERRIDE_UNSET;
+    }
+
+    // Returns KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER if the DeviceConfig flag is enabled
+    // or the value in com.android.internal.R.integer.config_letterboxBackgroundType if the flag
+    // is disabled.
+    @LetterboxBackgroundType
+    private int getDefaultLetterboxBackgroundType() {
+        return mDeviceConfig.getFlagValue(KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER)
+                ? LETTERBOX_BACKGROUND_WALLPAPER : mLetterboxBackgroundType;
     }
 
     /** Returns a string representing the given {@link LetterboxBackgroundType}. */
@@ -523,7 +594,7 @@
 
     /**
      * Overrides alpha of a black scrim shown over wallpaper for {@link
-     * #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link mLetterboxBackgroundType}.
+     * #LETTERBOX_BACKGROUND_WALLPAPER} option returned from {@link getLetterboxBackgroundType()}.
      *
      * <p>If given value is < 0 or >= 1, both it and a value of {@link
      * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha} are ignored
@@ -550,33 +621,33 @@
     }
 
     /**
-     * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in
-     * {@link mLetterboxBackgroundType}.
+     * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option from
+     * {@link getLetterboxBackgroundType()}.
      *
      * <p> If given value <= 0, both it and a value of {@link
      * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius} are ignored
      * and 0 is used instead.
      */
-    void setLetterboxBackgroundWallpaperBlurRadius(int radius) {
-        mLetterboxBackgroundWallpaperBlurRadius = radius;
+    void setLetterboxBackgroundWallpaperBlurRadiusPx(int radius) {
+        mLetterboxBackgroundWallpaperBlurRadiusPx = radius;
     }
 
     /**
-     * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
-     * mLetterboxBackgroundType} to {@link
+     * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option returned by {@link
+     * getLetterboxBackgroundType()} to {@link
      * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius}.
      */
-    void resetLetterboxBackgroundWallpaperBlurRadius() {
-        mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize(
+    void resetLetterboxBackgroundWallpaperBlurRadiusPx() {
+        mLetterboxBackgroundWallpaperBlurRadiusPx = mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
     }
 
     /**
-     * Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
-     * mLetterboxBackgroundType}.
+     * Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option returned by {@link
+     * getLetterboxBackgroundType()}.
      */
-    int getLetterboxBackgroundWallpaperBlurRadius() {
-        return mLetterboxBackgroundWallpaperBlurRadius;
+    int getLetterboxBackgroundWallpaperBlurRadiusPx() {
+        return mLetterboxBackgroundWallpaperBlurRadiusPx;
     }
 
     /*
@@ -1049,28 +1120,21 @@
     }
 
     boolean isTranslucentLetterboxingEnabled() {
-        return mTranslucentLetterboxingOverrideEnabled || (mTranslucentLetterboxingEnabled
-                && mDeviceConfig.getFlag(KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY));
-    }
-
-    void setTranslucentLetterboxingEnabled(boolean translucentLetterboxingEnabled) {
-        mTranslucentLetterboxingEnabled = translucentLetterboxingEnabled;
+        return mTranslucentLetterboxingOverrideEnabled
+                || mDeviceConfig.getFlagValue(KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY);
     }
 
     void setTranslucentLetterboxingOverrideEnabled(
             boolean translucentLetterboxingOverrideEnabled) {
         mTranslucentLetterboxingOverrideEnabled = translucentLetterboxingOverrideEnabled;
-        setTranslucentLetterboxingEnabled(translucentLetterboxingOverrideEnabled);
     }
 
     /**
      * Resets whether we use the constraints override strategy for letterboxing when dealing
-     * with translucent activities {@link R.bool.config_letterboxIsEnabledForTranslucentActivities}.
+     * with translucent activities
+     * {@link mDeviceConfig.getFlagValue(KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY)}.
      */
     void resetTranslucentLetterboxingEnabled() {
-        final boolean newValue = mContext.getResources().getBoolean(
-                R.bool.config_letterboxIsEnabledForTranslucentActivities);
-        setTranslucentLetterboxingEnabled(newValue);
         setTranslucentLetterboxingOverrideEnabled(false);
     }
 
@@ -1100,15 +1164,7 @@
 
     /** Whether fake sending focus is enabled for unfocused apps in splitscreen */
     boolean isCompatFakeFocusEnabled() {
-        return mIsCompatFakeFocusEnabled && mDeviceConfig.getFlag(KEY_ENABLE_COMPAT_FAKE_FOCUS);
-    }
-
-    /**
-     * Overrides whether fake sending focus is enabled for unfocused apps in splitscreen
-     */
-    @VisibleForTesting
-    void setIsCompatFakeFocusEnabled(boolean enabled) {
-        mIsCompatFakeFocusEnabled = enabled;
+        return mDeviceConfig.getFlagValue(KEY_ENABLE_COMPAT_FAKE_FOCUS);
     }
 
     /**
@@ -1128,10 +1184,20 @@
         return mIsCameraCompatSplitScreenAspectRatioEnabled;
     }
 
-    /** Whether camera compatibility treatment is enabled. */
-    boolean isCameraCompatTreatmentEnabled(boolean checkDeviceConfig) {
-        return mIsCameraCompatTreatmentEnabled && (!checkDeviceConfig
-                || mDeviceConfig.getFlag(KEY_ENABLE_CAMERA_COMPAT_TREATMENT));
+    /**
+     * @return Whether camera compatibility treatment is currently enabled.
+     */
+    boolean isCameraCompatTreatmentEnabled() {
+        return mDeviceConfig.getFlagValue(KEY_ENABLE_CAMERA_COMPAT_TREATMENT);
+    }
+
+    /**
+     * @return Whether camera compatibility treatment is enabled at build time. This is used when
+     * we need to safely initialize a component before the {@link DeviceConfig} flag value is
+     * available.
+     */
+    boolean isCameraCompatTreatmentEnabledAtBuildTime() {
+        return mDeviceConfig.isBuildTimeFlagEnabled(KEY_ENABLE_CAMERA_COMPAT_TREATMENT);
     }
 
     /** Whether camera compatibility refresh is enabled. */
@@ -1177,18 +1243,65 @@
 
     /**
      * Checks whether rotation compat policy for immersive apps that prevents auto rotation
-     * into non-optimal screen orientation while in fullscreen is enabled.
+     * into non-optimal screen orientation while in fullscreen is enabled at build time. This is
+     * used when we need to safely initialize a component before the {@link DeviceConfig} flag
+     * value is available.
      *
      * <p>This is needed because immersive apps, such as games, are often not optimized for all
      * orientations and can have a poor UX when rotated. Additionally, some games rely on sensors
      * for the gameplay so users can trigger such rotations accidentally when auto rotation is on.
-     *
-     * @param checkDeviceConfig whether should check both static config and a dynamic property
-     *        from {@link DeviceConfig} or only static value.
      */
-    boolean isDisplayRotationImmersiveAppCompatPolicyEnabled(final boolean checkDeviceConfig) {
-        return mIsDisplayRotationImmersiveAppCompatPolicyEnabled && (!checkDeviceConfig
-                || mDeviceConfig.getFlag(KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY));
+    boolean isDisplayRotationImmersiveAppCompatPolicyEnabledAtBuildTime() {
+        return mDeviceConfig.isBuildTimeFlagEnabled(
+                KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY);
     }
 
+    /**
+     * Checks whether rotation compat policy for immersive apps that prevents auto rotation
+     * into non-optimal screen orientation while in fullscreen is currently enabled.
+     *
+     * <p>This is needed because immersive apps, such as games, are often not optimized for all
+     * orientations and can have a poor UX when rotated. Additionally, some games rely on sensors
+     * for the gameplay so users can trigger such rotations accidentally when auto rotation is on.
+     */
+    boolean isDisplayRotationImmersiveAppCompatPolicyEnabled() {
+        return mDeviceConfig.getFlagValue(KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY);
+    }
+
+    /**
+     * Whether per-app user aspect ratio override settings is enabled
+     */
+    boolean isUserAppAspectRatioSettingsEnabled() {
+        return mUserAppAspectRatioSettingsOverrideEnabled
+                || mDeviceConfig.getFlagValue(KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS);
+    }
+
+    void setUserAppAspectRatioSettingsOverrideEnabled(boolean enabled) {
+        mUserAppAspectRatioSettingsOverrideEnabled = enabled;
+    }
+
+    /**
+     * Resets whether per-app user aspect ratio override settings is enabled
+     * {@code mDeviceConfig.getFlagValue(KEY_ENABLE_USER_ASPECT_RATIO_SETTINGS)}.
+     */
+    void resetUserAppAspectRatioSettingsEnabled() {
+        setUserAppAspectRatioSettingsOverrideEnabled(false);
+    }
+
+    /**
+     * Whether fullscreen option in per-app user aspect ratio settings is enabled
+     */
+    boolean isUserAppAspectRatioFullscreenEnabled() {
+        return isUserAppAspectRatioSettingsEnabled()
+                && (mUserAppAspectRatioFullscreenOverrideEnabled
+                    || mDeviceConfig.getFlagValue(KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN));
+    }
+
+    void setUserAppAspectRatioFullscreenOverrideEnabled(boolean enabled) {
+        mUserAppAspectRatioFullscreenOverrideEnabled = enabled;
+    }
+
+    void resetUserAppAspectRatioFullscreenEnabled() {
+        setUserAppAspectRatioFullscreenOverrideEnabled(false);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
deleted file mode 100644
index a739e42..0000000
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
+++ /dev/null
@@ -1,180 +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.server.wm;
-
-import android.annotation.NonNull;
-import android.provider.DeviceConfig;
-import android.util.ArraySet;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/**
- * Utility class that caches {@link DeviceConfig} flags for app compat features and listens
- * to updates by implementing {@link DeviceConfig.OnPropertiesChangedListener}.
- */
-final class LetterboxConfigurationDeviceConfig
-        implements DeviceConfig.OnPropertiesChangedListener {
-
-    static final String KEY_ENABLE_CAMERA_COMPAT_TREATMENT = "enable_compat_camera_treatment";
-    private static final boolean DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT = true;
-
-    static final String KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
-            "enable_display_rotation_immersive_app_compat_policy";
-    private static final boolean DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
-            true;
-
-    static final String KEY_ALLOW_IGNORE_ORIENTATION_REQUEST =
-            "allow_ignore_orientation_request";
-    private static final boolean DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST = true;
-
-    static final String KEY_ENABLE_COMPAT_FAKE_FOCUS = "enable_compat_fake_focus";
-    private static final boolean DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS = true;
-
-    static final String KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY =
-            "enable_letterbox_translucent_activity";
-
-    private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY = true;
-
-    @VisibleForTesting
-    static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of(
-            KEY_ENABLE_CAMERA_COMPAT_TREATMENT,
-            DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT,
-            KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
-            DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
-            KEY_ALLOW_IGNORE_ORIENTATION_REQUEST,
-            DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST,
-            KEY_ENABLE_COMPAT_FAKE_FOCUS,
-            DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS,
-            KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY,
-            DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY
-    );
-
-    // Whether camera compatibility treatment is enabled.
-    // See DisplayRotationCompatPolicy for context.
-    private boolean mIsCameraCompatTreatmentEnabled =
-            DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT;
-
-    // Whether enabling rotation compat policy for immersive apps that prevents auto rotation
-    // into non-optimal screen orientation while in fullscreen. This is needed because immersive
-    // apps, such as games, are often not optimized for all orientations and can have a poor UX
-    // when rotated. Additionally, some games rely on sensors for the gameplay so users can trigger
-    // such rotations accidentally when auto rotation is on.
-    private boolean mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
-            DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
-
-    // Whether enabling ignoreOrientationRequest is allowed on the device.
-    private boolean mIsAllowIgnoreOrientationRequest =
-            DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST;
-
-    // Whether sending compat fake focus for split screen resumed activities is enabled. This is
-    // needed because some game engines wait to get focus before drawing the content of the app
-    // which isn't guaranteed by default in multi-window modes.
-    private boolean mIsCompatFakeFocusAllowed = DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS;
-
-    // Whether the letterbox strategy for transparent activities is allowed
-    private boolean mIsTranslucentLetterboxingAllowed =
-            DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY;
-
-    // Set of active device configs that need to be updated in
-    // DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged.
-    private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>();
-
-    LetterboxConfigurationDeviceConfig(@NonNull final Executor executor) {
-        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
-                executor, /* onPropertiesChangedListener */ this);
-    }
-
-    @Override
-    public void onPropertiesChanged(@NonNull final DeviceConfig.Properties properties) {
-        for (int i = mActiveDeviceConfigsSet.size() - 1; i >= 0; i--) {
-            String key = mActiveDeviceConfigsSet.valueAt(i);
-            // Reads the new configuration, if the device config properties contain the key.
-            if (properties.getKeyset().contains(key)) {
-                readAndSaveValueFromDeviceConfig(key);
-            }
-        }
-    }
-
-    /**
-     * Adds {@code key} to a set of flags that can be updated from the server if
-     * {@code isActive} is {@code true} and read it's current value from {@link DeviceConfig}.
-     */
-    void updateFlagActiveStatus(boolean isActive, String key) {
-        if (!isActive) {
-            return;
-        }
-        mActiveDeviceConfigsSet.add(key);
-        readAndSaveValueFromDeviceConfig(key);
-    }
-
-    /**
-     * Returns values of the {@code key} flag.
-     *
-     * @throws AssertionError {@code key} isn't recognised.
-     */
-    boolean getFlag(String key) {
-        switch (key) {
-            case KEY_ENABLE_CAMERA_COMPAT_TREATMENT:
-                return mIsCameraCompatTreatmentEnabled;
-            case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
-                return mIsDisplayRotationImmersiveAppCompatPolicyEnabled;
-            case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
-                return mIsAllowIgnoreOrientationRequest;
-            case KEY_ENABLE_COMPAT_FAKE_FOCUS:
-                return mIsCompatFakeFocusAllowed;
-            case KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY:
-                return mIsTranslucentLetterboxingAllowed;
-            default:
-                throw new AssertionError("Unexpected flag name: " + key);
-        }
-    }
-
-    private void readAndSaveValueFromDeviceConfig(String key) {
-        Boolean defaultValue = sKeyToDefaultValueMap.get(key);
-        if (defaultValue == null) {
-            throw new AssertionError("Haven't found default value for flag: " + key);
-        }
-        switch (key) {
-            case KEY_ENABLE_CAMERA_COMPAT_TREATMENT:
-                mIsCameraCompatTreatmentEnabled = getDeviceConfig(key, defaultValue);
-                break;
-            case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
-                mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
-                        getDeviceConfig(key, defaultValue);
-                break;
-            case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
-                mIsAllowIgnoreOrientationRequest = getDeviceConfig(key, defaultValue);
-                break;
-            case KEY_ENABLE_COMPAT_FAKE_FOCUS:
-                mIsCompatFakeFocusAllowed = getDeviceConfig(key, defaultValue);
-                break;
-            case KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY:
-                mIsTranslucentLetterboxingAllowed = getDeviceConfig(key, defaultValue);
-                break;
-            default:
-                throw new AssertionError("Unexpected flag name: " + key);
-        }
-    }
-
-    private boolean getDeviceConfig(String key, boolean defaultValue) {
-        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
-                key, defaultValue);
-    }
-}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
index 7563397..38aa903 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
@@ -23,7 +23,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.Context;
 import android.os.Environment;
 import android.os.StrictMode;
 import android.os.StrictMode.ThreadPolicy;
@@ -53,10 +52,8 @@
     private static final String TAG =
             TAG_WITH_CLASS_NAME ? "LetterboxConfigurationPersister" : TAG_WM;
 
-    @VisibleForTesting
-    static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config";
+    private static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config";
 
-    private final Context mContext;
     private final Supplier<Integer> mDefaultHorizontalReachabilitySupplier;
     private final Supplier<Integer> mDefaultVerticalReachabilitySupplier;
     private final Supplier<Integer> mDefaultBookModeReachabilitySupplier;
@@ -97,36 +94,32 @@
     @NonNull
     private final PersisterQueue mPersisterQueue;
 
-    LetterboxConfigurationPersister(Context systemUiContext,
-            Supplier<Integer> defaultHorizontalReachabilitySupplier,
-            Supplier<Integer> defaultVerticalReachabilitySupplier,
-            Supplier<Integer> defaultBookModeReachabilitySupplier,
-            Supplier<Integer> defaultTabletopModeReachabilitySupplier) {
-        this(systemUiContext, defaultHorizontalReachabilitySupplier,
-                defaultVerticalReachabilitySupplier,
-                defaultBookModeReachabilitySupplier,
-                defaultTabletopModeReachabilitySupplier,
+    LetterboxConfigurationPersister(
+            @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier,
+            @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier,
+            @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier,
+            @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier) {
+        this(defaultHorizontalReachabilitySupplier, defaultVerticalReachabilitySupplier,
+                defaultBookModeReachabilitySupplier, defaultTabletopModeReachabilitySupplier,
                 Environment.getDataSystemDirectory(), new PersisterQueue(),
-                /* completionCallback */ null);
+                /* completionCallback */ null, LETTERBOX_CONFIGURATION_FILENAME);
     }
 
     @VisibleForTesting
-    LetterboxConfigurationPersister(Context systemUiContext,
-            Supplier<Integer> defaultHorizontalReachabilitySupplier,
-            Supplier<Integer> defaultVerticalReachabilitySupplier,
-            Supplier<Integer> defaultBookModeReachabilitySupplier,
-            Supplier<Integer> defaultTabletopModeReachabilitySupplier,
-            File configFolder,
-            PersisterQueue persisterQueue, @Nullable Consumer<String> completionCallback) {
-        mContext = systemUiContext.createDeviceProtectedStorageContext();
+    LetterboxConfigurationPersister(
+            @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier,
+            @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier,
+            @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier,
+            @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier,
+            @NonNull File configFolder, @NonNull PersisterQueue persisterQueue,
+            @Nullable Consumer<String> completionCallback,
+            @NonNull String letterboxConfigurationFileName) {
         mDefaultHorizontalReachabilitySupplier = defaultHorizontalReachabilitySupplier;
         mDefaultVerticalReachabilitySupplier = defaultVerticalReachabilitySupplier;
-        mDefaultBookModeReachabilitySupplier =
-                defaultBookModeReachabilitySupplier;
-        mDefaultTabletopModeReachabilitySupplier =
-                defaultTabletopModeReachabilitySupplier;
+        mDefaultBookModeReachabilitySupplier = defaultBookModeReachabilitySupplier;
+        mDefaultTabletopModeReachabilitySupplier = defaultTabletopModeReachabilitySupplier;
         mCompletionCallback = completionCallback;
-        final File prefFiles = new File(configFolder, LETTERBOX_CONFIGURATION_FILENAME);
+        final File prefFiles = new File(configFolder, letterboxConfigurationFileName);
         mConfigurationFile = new AtomicFile(prefFiles);
         mPersisterQueue = persisterQueue;
         runWithDiskReadsThreadPolicy(this::readCurrentConfiguration);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index d83c861..01786be 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -37,9 +37,17 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
 import static android.content.pm.ActivityInfo.isFixedOrientation;
 import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
 import static android.content.pm.ActivityInfo.screenOrientationToString;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
@@ -55,6 +63,8 @@
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
 import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
 
@@ -103,6 +113,7 @@
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.RemoteException;
 import android.util.Slog;
 import android.view.InsetsSource;
 import android.view.InsetsState;
@@ -206,6 +217,11 @@
     @Nullable
     private final Boolean mBooleanPropertyAllowForceResizeOverride;
 
+    @Nullable
+    private final Boolean mBooleanPropertyAllowUserAspectRatioOverride;
+    @Nullable
+    private final Boolean mBooleanPropertyAllowUserAspectRatioFullscreenOverride;
+
     /*
      * WindowContainerListener responsible to make translucent activities inherit
      * constraints from the first opaque activity beneath them. It's null for not
@@ -237,6 +253,10 @@
     // Counter for ActivityRecord#setRequestedOrientation
     private int mSetOrientationRequestCounter = 0;
 
+    // The min aspect ratio override set by user
+    @PackageManager.UserMinAspectRatio
+    private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
+
     // The CompatDisplayInsets of the opaque activity beneath the translucent one.
     private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets;
 
@@ -294,18 +314,15 @@
                         PROPERTY_COMPAT_ENABLE_FAKE_FOCUS);
         mBooleanPropertyCameraCompatAllowForceRotation =
                 readComponentProperty(packageManager, mActivityRecord.packageName,
-                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                                /* checkDeviceConfig */ true),
+                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(),
                         PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION);
         mBooleanPropertyCameraCompatAllowRefresh =
                 readComponentProperty(packageManager, mActivityRecord.packageName,
-                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                                /* checkDeviceConfig */ true),
+                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(),
                         PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH);
         mBooleanPropertyCameraCompatEnableRefreshViaPause =
                 readComponentProperty(packageManager, mActivityRecord.packageName,
-                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                                /* checkDeviceConfig */ true),
+                        () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(),
                         PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
 
         mBooleanPropertyAllowOrientationOverride =
@@ -325,6 +342,15 @@
                         /* gatingCondition */ null,
                         PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
 
+        mBooleanPropertyAllowUserAspectRatioOverride =
+                readComponentProperty(packageManager, mActivityRecord.packageName,
+                        () -> mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled(),
+                        PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+        mBooleanPropertyAllowUserAspectRatioFullscreenOverride =
+                readComponentProperty(packageManager, mActivityRecord.packageName,
+                        () -> mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled(),
+                        PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
+
         mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION);
         mIsOverrideToPortraitOrientationEnabled =
                 isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT);
@@ -634,6 +660,10 @@
 
     @ScreenOrientation
     int overrideOrientationIfNeeded(@ScreenOrientation int candidate) {
+        if (shouldApplyUserFullscreenOverride()) {
+            return SCREEN_ORIENTATION_USER;
+        }
+
         // In some cases (e.g. Kids app) we need to map the candidate orientation to some other
         // orientation.
         candidate = mActivityRecord.mWmService.mapOrientationRequest(candidate);
@@ -697,7 +727,7 @@
     boolean shouldRefreshActivityForCameraCompat() {
         return shouldEnableWithOptOutOverrideAndProperty(
                 /* gatingCondition */ () -> mLetterboxConfiguration
-                        .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
+                        .isCameraCompatTreatmentEnabled(),
                 mIsOverrideCameraCompatDisableRefreshEnabled,
                 mBooleanPropertyCameraCompatAllowRefresh);
     }
@@ -719,7 +749,7 @@
     boolean shouldRefreshActivityViaPauseForCameraCompat() {
         return shouldEnableWithOverrideAndProperty(
                 /* gatingCondition */ () -> mLetterboxConfiguration
-                        .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
+                        .isCameraCompatTreatmentEnabled(),
                 mIsOverrideCameraCompatEnableRefreshViaPauseEnabled,
                 mBooleanPropertyCameraCompatEnableRefreshViaPause);
     }
@@ -738,7 +768,7 @@
     boolean shouldForceRotateForCameraCompat() {
         return shouldEnableWithOptOutOverrideAndProperty(
                 /* gatingCondition */ () -> mLetterboxConfiguration
-                        .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
+                        .isCameraCompatTreatmentEnabled(),
                 mIsOverrideCameraCompatDisableForceRotationEnabled,
                 mBooleanPropertyCameraCompatAllowForceRotation);
     }
@@ -897,7 +927,7 @@
                         this::shouldLetterboxHaveRoundedCorners,
                         this::getLetterboxBackgroundColor,
                         this::hasWallpaperBackgroundForLetterbox,
-                        this::getLetterboxWallpaperBlurRadius,
+                        this::getLetterboxWallpaperBlurRadiusPx,
                         this::getLetterboxWallpaperDarkScrimAlpha,
                         this::handleHorizontalDoubleTap,
                         this::handleVerticalDoubleTap,
@@ -939,8 +969,10 @@
             final Rect innerFrame = hasInheritedLetterboxBehavior()
                     ? mActivityRecord.getBounds() : w.getFrame();
             mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint);
-            // We need to notify Shell that letterbox position has changed.
-            mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
+            if (mDoubleTapEvent) {
+                // We need to notify Shell that letterbox position has changed.
+                mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
+            }
         } else if (mLetterbox != null) {
             mLetterbox.hide();
         }
@@ -1062,7 +1094,7 @@
 
     private float getDefaultMinAspectRatioForUnresizableApps() {
         if (!mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled()
-                || mActivityRecord.getDisplayContent() == null) {
+                || mActivityRecord.getDisplayArea() == null) {
             return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
                     > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
                             ? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
@@ -1074,8 +1106,8 @@
 
     float getSplitScreenAspectRatio() {
         // Getting the same aspect ratio that apps get in split screen.
-        final DisplayContent displayContent = mActivityRecord.getDisplayContent();
-        if (displayContent == null) {
+        final DisplayArea displayArea = mActivityRecord.getDisplayArea();
+        if (displayArea == null) {
             return getDefaultMinAspectRatioForUnresizableApps();
         }
         int dividerWindowWidth =
@@ -1083,7 +1115,7 @@
         int dividerInsets =
                 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
         int dividerSize = dividerWindowWidth - dividerInsets * 2;
-        final Rect bounds = new Rect(displayContent.getWindowConfiguration().getAppBounds());
+        final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
         if (bounds.width() >= bounds.height()) {
             bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
             bounds.right = bounds.centerX();
@@ -1094,14 +1126,79 @@
         return computeAspectRatio(bounds);
     }
 
+    boolean shouldApplyUserMinAspectRatioOverride() {
+        if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
+                || !mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
+                || mActivityRecord.mDisplayContent == null
+                || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
+            return false;
+        }
+
+        mUserAspectRatio = getUserMinAspectRatioOverrideCode();
+
+        return mUserAspectRatio != USER_MIN_ASPECT_RATIO_UNSET
+                && mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
+    }
+
+    boolean shouldApplyUserFullscreenOverride() {
+        if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
+                || FALSE.equals(mBooleanPropertyAllowUserAspectRatioFullscreenOverride)
+                || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()
+                || mActivityRecord.mDisplayContent == null
+                || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
+            return false;
+        }
+
+        mUserAspectRatio = getUserMinAspectRatioOverrideCode();
+
+        return mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
+    }
+
+    float getUserMinAspectRatio() {
+        switch (mUserAspectRatio) {
+            case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
+                return getDisplaySizeMinAspectRatio();
+            case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
+                return getSplitScreenAspectRatio();
+            case USER_MIN_ASPECT_RATIO_16_9:
+                return 16 / 9f;
+            case USER_MIN_ASPECT_RATIO_4_3:
+                return 4 / 3f;
+            case USER_MIN_ASPECT_RATIO_3_2:
+                return 3 / 2f;
+            default:
+                throw new AssertionError("Unexpected user min aspect ratio override: "
+                        + mUserAspectRatio);
+        }
+    }
+
+    @VisibleForTesting
+    int getUserMinAspectRatioOverrideCode() {
+        try {
+            return mActivityRecord.mAtmService.getPackageManager()
+                    .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " + this, e);
+        }
+        return mUserAspectRatio;
+    }
+
+    private float getDisplaySizeMinAspectRatio() {
+        final DisplayArea displayArea = mActivityRecord.getDisplayArea();
+        if (displayArea == null) {
+            return mActivityRecord.info.getMinAspectRatio();
+        }
+        final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
+        return computeAspectRatio(bounds);
+    }
+
     private float getDefaultMinAspectRatio() {
-        final DisplayContent displayContent = mActivityRecord.getDisplayContent();
-        if (displayContent == null
+        if (mActivityRecord.getDisplayArea() == null
                 || !mLetterboxConfiguration
                     .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) {
             return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
         }
-        return computeAspectRatio(new Rect(displayContent.getBounds()));
+        return getDisplaySizeMinAspectRatio();
     }
 
     Resources getResources() {
@@ -1147,6 +1244,7 @@
                                 ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT
                                 : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
             logLetterboxPositionChange(changeToLog);
+            mDoubleTapEvent = true;
         } else if (mLetterbox.getInnerFrame().right < x) {
             // Moving to the next stop on the right side of the app window: left > center > right.
             mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop(
@@ -1157,8 +1255,8 @@
                                 ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT
                                 : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
             logLetterboxPositionChange(changeToLog);
+            mDoubleTapEvent = true;
         }
-        mDoubleTapEvent = true;
         // TODO(197549949): Add animation for transition.
         mActivityRecord.recomputeConfiguration();
     }
@@ -1186,6 +1284,7 @@
                                 ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP
                                 : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
             logLetterboxPositionChange(changeToLog);
+            mDoubleTapEvent = true;
         } else if (mLetterbox.getInnerFrame().bottom < y) {
             // Moving to the next stop on the bottom side of the app window: top > center > bottom.
             mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop(
@@ -1196,8 +1295,8 @@
                                 ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM
                                 : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
             logLetterboxPositionChange(changeToLog);
+            mDoubleTapEvent = true;
         }
-        mDoubleTapEvent = true;
         // TODO(197549949): Add animation for transition.
         mActivityRecord.recomputeConfiguration();
     }
@@ -1318,7 +1417,7 @@
             case LETTERBOX_BACKGROUND_WALLPAPER:
                 if (hasWallpaperBackgroundForLetterbox()) {
                     // Color is used for translucent scrim that dims wallpaper.
-                    return Color.valueOf(Color.BLACK);
+                    return mLetterboxConfiguration.getLetterboxBackgroundColor();
                 }
                 Slog.w(TAG, "Wallpaper option is selected for letterbox background but "
                         + "blur is not supported by a device or not supported in the current "
@@ -1428,7 +1527,8 @@
         for (int i = state.sourceSize() - 1; i >= 0; i--) {
             final InsetsSource source = state.sourceAt(i);
             if (source.getType() == WindowInsets.Type.navigationBars()
-                    && source.insetsRoundedCornerFrame() && source.isVisible()) {
+                    && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)
+                    && source.isVisible()) {
                 return source;
             }
         }
@@ -1474,10 +1574,10 @@
                         // Don't use wallpaper as a background if letterboxed for display cutout.
                         && isLetterboxedNotForDisplayCutout(mainWindow)
                         // Check that dark scrim alpha or blur radius are provided
-                        && (getLetterboxWallpaperBlurRadius() > 0
+                        && (getLetterboxWallpaperBlurRadiusPx() > 0
                                 || getLetterboxWallpaperDarkScrimAlpha() > 0)
                         // Check that blur is supported by a device if blur radius is provided.
-                        && (getLetterboxWallpaperBlurRadius() <= 0
+                        && (getLetterboxWallpaperBlurRadiusPx() <= 0
                                 || isLetterboxWallpaperBlurSupported());
         if (mShowWallpaperForLetterboxBackground != wallpaperShouldBeShown) {
             mShowWallpaperForLetterboxBackground = wallpaperShouldBeShown;
@@ -1485,9 +1585,9 @@
         }
     }
 
-    private int getLetterboxWallpaperBlurRadius() {
-        int blurRadius = mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius();
-        return blurRadius < 0 ? 0 : blurRadius;
+    private int getLetterboxWallpaperBlurRadiusPx() {
+        int blurRadius = mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadiusPx();
+        return Math.max(blurRadius, 0);
     }
 
     private float getLetterboxWallpaperDarkScrimAlpha() {
@@ -1537,7 +1637,7 @@
             pw.println(prefix + "  letterboxBackgroundWallpaperDarkScrimAlpha="
                     + getLetterboxWallpaperDarkScrimAlpha());
             pw.println(prefix + "  letterboxBackgroundWallpaperBlurRadius="
-                    + getLetterboxWallpaperBlurRadius());
+                    + getLetterboxWallpaperBlurRadiusPx());
         }
 
         pw.println(prefix + "  isHorizontalReachabilityEnabled="
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index e47787e..4faaf51 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -682,6 +682,26 @@
         }
     }
 
+    /**
+     * Removes the oldest recent task that is compatible with the given one. This is possible if
+     * the task windowing mode changed after being added to the Recents.
+     */
+    void removeCompatibleRecentTask(Task task) {
+        final int taskIndex = mTasks.indexOf(task);
+        if (taskIndex < 0) {
+            return;
+        }
+
+        final int candidateIndex = findRemoveIndexForTask(task, false /* includingSelf */);
+        if (candidateIndex == -1) {
+            // Nothing to trim
+            return;
+        }
+
+        final Task taskToRemove = taskIndex > candidateIndex ? task : mTasks.get(candidateIndex);
+        remove(taskToRemove);
+    }
+
     void removeTasksByPackageName(String packageName, int userId) {
         for (int i = mTasks.size() - 1; i >= 0; --i) {
             final Task task = mTasks.get(i);
@@ -1104,6 +1124,10 @@
                         // front unless overridden by the provided activity options
                         mTasks.remove(taskIndex);
                         mTasks.add(0, task);
+                        if (taskIndex != 0) {
+                            // Only notify when position changes
+                            mTaskNotificationController.notifyTaskListUpdated();
+                        }
 
                         if (DEBUG_RECENTS) {
                             Slog.d(TAG_RECENTS, "addRecent: moving to top " + task
@@ -1536,6 +1560,10 @@
      * list (if any).
      */
     private int findRemoveIndexForAddTask(Task task) {
+        return findRemoveIndexForTask(task, true /* includingSelf */);
+    }
+
+    private int findRemoveIndexForTask(Task task, boolean includingSelf) {
         final int recentsCount = mTasks.size();
         final Intent intent = task.intent;
         final boolean document = intent != null && intent.isDocument();
@@ -1552,7 +1580,7 @@
                         task.affinity != null && task.affinity.equals(t.affinity);
                 final boolean sameIntent = intent != null && intent.filterEquals(trIntent);
                 boolean multiTasksAllowed = false;
-                final int flags = intent.getFlags();
+                final int flags = intent != null ? intent.getFlags() : 0;
                 if ((flags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NEW_DOCUMENT)) != 0
                         && (flags & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
                     multiTasksAllowed = true;
@@ -1591,6 +1619,8 @@
                     // existing task
                     continue;
                 }
+            } else if (!includingSelf) {
+                continue;
             }
             return i;
         }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 7b10c63..b49c5fb 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -687,11 +687,11 @@
     @Override
     public void updateRequestedVisibleTypes(IWindow window, @InsetsType int requestedVisibleTypes) {
         synchronized (mService.mGlobalLock) {
-            final WindowState windowState = mService.windowForClientLocked(this, window,
+            final WindowState win = mService.windowForClientLocked(this, window,
                     false /* throwOnError */);
-            if (windowState != null) {
-                windowState.setRequestedVisibleTypes(requestedVisibleTypes);
-                windowState.getDisplayContent().getInsetsPolicy().onInsetsModified(windowState);
+            if (win != null) {
+                win.setRequestedVisibleTypes(requestedVisibleTypes);
+                win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index badcfa9..37f9730 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -16,185 +16,36 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
-import android.annotation.IntDef;
-import android.util.ArraySet;
-import android.util.Slog;
-import android.util.SparseArray;
+import android.os.Trace;
 import android.view.WindowManager;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.function.Consumer;
 
 /**
  * Integrates common functionality from TaskSnapshotController and ActivitySnapshotController.
  */
 class SnapshotController {
-    private static final boolean DEBUG = false;
-    private static final String TAG = AbsAppSnapshotController.TAG;
-
-    static final int ACTIVITY_OPEN = 1;
-    static final int ACTIVITY_CLOSE = 2;
-    static final int TASK_OPEN = 4;
-    static final int TASK_CLOSE = 8;
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            value = {ACTIVITY_OPEN,
-                    ACTIVITY_CLOSE,
-                    TASK_OPEN,
-                    TASK_CLOSE})
-    @interface TransitionStateType {}
-
     private final SnapshotPersistQueue mSnapshotPersistQueue;
     final TaskSnapshotController mTaskSnapshotController;
     final ActivitySnapshotController mActivitySnapshotController;
 
-    private final ArraySet<Task> mTmpCloseTasks = new ArraySet<>();
-    private final ArraySet<Task> mTmpOpenTasks = new ArraySet<>();
-
-    private final SparseArray<TransitionState> mTmpOpenCloseRecord = new SparseArray<>();
-    private final ArraySet<Integer> mTmpAnalysisRecord = new ArraySet<>();
-    private final SparseArray<ArrayList<Consumer<TransitionState>>> mTransitionStateConsumer =
-            new SparseArray<>();
-    private int mActivatedType;
-
-    private final ActivityOrderCheck mActivityOrderCheck = new ActivityOrderCheck();
-    private final ActivityOrderCheck.AnalysisResult mResultHandler = (type, close, open) -> {
-        addTransitionRecord(type, true/* open */, open);
-        addTransitionRecord(type, false/* open */, close);
-    };
-
-    private static class ActivityOrderCheck {
-        private ActivityRecord mOpenActivity;
-        private ActivityRecord mCloseActivity;
-        private int mOpenIndex = -1;
-        private int mCloseIndex = -1;
-
-        private void reset() {
-            mOpenActivity = null;
-            mCloseActivity = null;
-            mOpenIndex = -1;
-            mCloseIndex = -1;
-        }
-
-        private void setTarget(boolean open, ActivityRecord ar, int index) {
-            if (open) {
-                mOpenActivity = ar;
-                mOpenIndex = index;
-            } else {
-                mCloseActivity = ar;
-                mCloseIndex = index;
-            }
-        }
-
-        void analysisOrder(ArraySet<ActivityRecord> closeApps,
-                ArraySet<ActivityRecord> openApps, Task task, AnalysisResult result) {
-            for (int j = closeApps.size() - 1; j >= 0; j--) {
-                final ActivityRecord ar = closeApps.valueAt(j);
-                if (ar.getTask() == task) {
-                    setTarget(false, ar, task.mChildren.indexOf(ar));
-                    break;
-                }
-            }
-            for (int j = openApps.size() - 1; j >= 0; j--) {
-                final ActivityRecord ar = openApps.valueAt(j);
-                if (ar.getTask() == task) {
-                    setTarget(true, ar, task.mChildren.indexOf(ar));
-                    break;
-                }
-            }
-            if (mOpenIndex > mCloseIndex && mCloseIndex != -1) {
-                result.onCheckResult(ACTIVITY_OPEN, mCloseActivity, mOpenActivity);
-            } else if (mOpenIndex < mCloseIndex && mOpenIndex != -1) {
-                result.onCheckResult(ACTIVITY_CLOSE, mCloseActivity, mOpenActivity);
-            }
-            reset();
-        }
-        private interface AnalysisResult {
-            void onCheckResult(@TransitionStateType int type,
-                    ActivityRecord close, ActivityRecord open);
-        }
-    }
-
-    private void addTransitionRecord(int type, boolean open, WindowContainer target) {
-        TransitionState record = mTmpOpenCloseRecord.get(type);
-        if (record == null) {
-            record =  new TransitionState();
-            mTmpOpenCloseRecord.set(type, record);
-        }
-        record.addParticipant(target, open);
-        mTmpAnalysisRecord.add(type);
-    }
-
-    private void clearRecord() {
-        mTmpOpenCloseRecord.clear();
-        mTmpAnalysisRecord.clear();
-    }
-
-    static class TransitionState<TYPE extends WindowContainer> {
-        private final ArraySet<TYPE> mOpenParticipant = new ArraySet<>();
-        private final ArraySet<TYPE> mCloseParticipant = new ArraySet<>();
-
-        void addParticipant(TYPE target, boolean open) {
-            final ArraySet<TYPE> participant = open
-                    ? mOpenParticipant : mCloseParticipant;
-            participant.add(target);
-        }
-
-        ArraySet<TYPE> getParticipant(boolean open) {
-            return open ? mOpenParticipant : mCloseParticipant;
-        }
-    }
-
     SnapshotController(WindowManagerService wms) {
         mSnapshotPersistQueue = new SnapshotPersistQueue();
         mTaskSnapshotController = new TaskSnapshotController(wms, mSnapshotPersistQueue);
         mActivitySnapshotController = new ActivitySnapshotController(wms, mSnapshotPersistQueue);
     }
 
-    void registerTransitionStateConsumer(@TransitionStateType int type,
-            Consumer<TransitionState> consumer) {
-        ArrayList<Consumer<TransitionState>> consumers = mTransitionStateConsumer.get(type);
-        if (consumers == null) {
-            consumers = new ArrayList<>();
-            mTransitionStateConsumer.set(type, consumers);
-        }
-        if (!consumers.contains(consumer)) {
-            consumers.add(consumer);
-        }
-        mActivatedType |= type;
-    }
-
-    void unregisterTransitionStateConsumer(int type, Consumer<TransitionState> consumer) {
-        final ArrayList<Consumer<TransitionState>> consumers = mTransitionStateConsumer.get(type);
-        if (consumers == null) {
-            return;
-        }
-        consumers.remove(consumer);
-        if (consumers.size() == 0) {
-            mActivatedType &= ~type;
-        }
-    }
-
-    private boolean hasTransitionStateConsumer(@TransitionStateType int type) {
-        return (mActivatedType & type) != 0;
-    }
-
     void systemReady() {
         mSnapshotPersistQueue.systemReady();
-        mTaskSnapshotController.systemReady();
-        mActivitySnapshotController.systemReady();
     }
 
     void setPause(boolean paused) {
@@ -212,47 +63,69 @@
     }
 
     void notifyAppVisibilityChanged(ActivityRecord appWindowToken, boolean visible) {
-        if (!visible && hasTransitionStateConsumer(TASK_CLOSE)) {
-            final Task task = appWindowToken.getTask();
-            if (task == null || task.isVisibleRequested()) {
-                return;
+        mActivitySnapshotController.notifyAppVisibilityChanged(appWindowToken, visible);
+    }
+
+    // For legacy transition, which won't support activity snapshot
+    void onTransitionStarting(DisplayContent displayContent) {
+        mTaskSnapshotController.handleClosingApps(displayContent.mClosingApps);
+    }
+
+    // For shell transition, record snapshots before transaction start.
+    void onTransactionReady(@WindowManager.TransitionType int type,
+            ArrayList<Transition.ChangeInfo> changeInfos) {
+        final boolean isTransitionOpen = isTransitionOpen(type);
+        final boolean isTransitionClose = isTransitionClose(type);
+        if (!isTransitionOpen && !isTransitionClose && type < TRANSIT_FIRST_CUSTOM) {
+            return;
+        }
+        for (int i = changeInfos.size() - 1; i >= 0; --i) {
+            Transition.ChangeInfo info = changeInfos.get(i);
+            // Intentionally skip record snapshot for changes originated from PiP.
+            if (info.mWindowingMode == WINDOWING_MODE_PINNED) continue;
+            if (info.mContainer.asTask() != null && !info.mContainer.isVisibleRequested()) {
+                mTaskSnapshotController.recordSnapshot(info.mContainer.asTask(),
+                        false /* allowSnapshotHome */);
             }
-            // close task transition
-            addTransitionRecord(TASK_CLOSE, false /*open*/, task);
-            mActivitySnapshotController.preTransitionStart();
-            notifyTransition(TASK_CLOSE);
-            mActivitySnapshotController.postTransitionStart();
-            clearRecord();
+            // Won't need to capture activity snapshot in close transition.
+            if (isTransitionClose) {
+                continue;
+            }
+            if (info.mContainer.asActivityRecord() != null
+                    || info.mContainer.asTaskFragment() != null) {
+                final TaskFragment tf = info.mContainer.asTaskFragment();
+                final ActivityRecord ar = tf != null ? tf.getTopMostActivity()
+                        : info.mContainer.asActivityRecord();
+                final boolean taskVis = ar != null && ar.getTask().isVisibleRequested();
+                if (ar != null && !ar.isVisibleRequested() && taskVis) {
+                    mActivitySnapshotController.recordSnapshot(ar);
+                }
+            }
         }
     }
 
-    // For legacy transition
-    void onTransitionStarting(DisplayContent displayContent) {
-        handleAppTransition(displayContent.mClosingApps, displayContent.mOpeningApps);
-    }
-
-    // For shell transition, adapt to legacy transition.
-    void onTransitionReady(@WindowManager.TransitionType int type,
-            ArraySet<WindowContainer> participants) {
+    void onTransitionFinish(@WindowManager.TransitionType int type,
+            ArrayList<Transition.ChangeInfo> changeInfos) {
         final boolean isTransitionOpen = isTransitionOpen(type);
         final boolean isTransitionClose = isTransitionClose(type);
         if (!isTransitionOpen && !isTransitionClose && type < TRANSIT_FIRST_CUSTOM
-                || (mActivatedType == 0)) {
+                || (changeInfos.isEmpty())) {
             return;
         }
-        final ArraySet<ActivityRecord> openingApps = new ArraySet<>();
-        final ArraySet<ActivityRecord> closingApps = new ArraySet<>();
-
-        for (int i = participants.size() - 1; i >= 0; --i) {
-            final ActivityRecord ar = participants.valueAt(i).asActivityRecord();
-            if (ar == null || ar.getTask() == null) continue;
-            if (ar.isVisibleRequested()) {
-                openingApps.add(ar);
-            } else {
-                closingApps.add(ar);
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SnapshotController_analysis");
+        mActivitySnapshotController.beginSnapshotProcess();
+        final ArrayList<WindowContainer> windows = new ArrayList<>();
+        for (int i = changeInfos.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = changeInfos.get(i).mContainer;
+            if (wc.asTask() == null && wc.asTaskFragment() == null
+                    && wc.asActivityRecord() == null) {
+                continue;
             }
+            windows.add(wc);
         }
-        handleAppTransition(closingApps, openingApps);
+        mActivitySnapshotController.handleTransitionFinish(windows);
+        mActivitySnapshotController.endSnapshotProcess();
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
     private static boolean isTransitionOpen(int type) {
@@ -262,78 +135,6 @@
         return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
     }
 
-    @VisibleForTesting
-    void handleAppTransition(ArraySet<ActivityRecord> closingApps,
-            ArraySet<ActivityRecord> openApps) {
-        if (mActivatedType == 0) {
-            return;
-        }
-        analysisTransition(closingApps, openApps);
-        mActivitySnapshotController.preTransitionStart();
-        for (Integer transitionType : mTmpAnalysisRecord) {
-            notifyTransition(transitionType);
-        }
-        mActivitySnapshotController.postTransitionStart();
-        clearRecord();
-    }
-
-    private void notifyTransition(int transitionType) {
-        final TransitionState record = mTmpOpenCloseRecord.get(transitionType);
-        final ArrayList<Consumer<TransitionState>> consumers =
-                mTransitionStateConsumer.get(transitionType);
-        for (Consumer<TransitionState> consumer : consumers) {
-            consumer.accept(record);
-        }
-    }
-
-    private void analysisTransition(ArraySet<ActivityRecord> closingApps,
-            ArraySet<ActivityRecord> openingApps) {
-        getParticipantTasks(closingApps, mTmpCloseTasks, false /* isOpen */);
-        getParticipantTasks(openingApps, mTmpOpenTasks, true /* isOpen */);
-        if (DEBUG) {
-            Slog.d(TAG, "AppSnapshotController#analysisTransition participants"
-                    + " mTmpCloseTasks " + mTmpCloseTasks
-                    + " mTmpOpenTasks " + mTmpOpenTasks);
-        }
-        for (int i = mTmpCloseTasks.size() - 1; i >= 0; i--) {
-            final Task closeTask = mTmpCloseTasks.valueAt(i);
-            if (mTmpOpenTasks.contains(closeTask)) {
-                if (hasTransitionStateConsumer(ACTIVITY_OPEN)
-                        || hasTransitionStateConsumer(ACTIVITY_CLOSE)) {
-                    mActivityOrderCheck.analysisOrder(closingApps, openingApps, closeTask,
-                            mResultHandler);
-                }
-            } else if (hasTransitionStateConsumer(TASK_CLOSE)) {
-                // close task transition
-                addTransitionRecord(TASK_CLOSE, false /*open*/, closeTask);
-            }
-        }
-        if (hasTransitionStateConsumer(TASK_OPEN)) {
-            for (int i = mTmpOpenTasks.size() - 1; i >= 0; i--) {
-                final Task openTask = mTmpOpenTasks.valueAt(i);
-                if (!mTmpCloseTasks.contains(openTask)) {
-                    // this is open task transition
-                    addTransitionRecord(TASK_OPEN, true /*open*/, openTask);
-                }
-            }
-        }
-        mTmpCloseTasks.clear();
-        mTmpOpenTasks.clear();
-    }
-
-    private void getParticipantTasks(ArraySet<ActivityRecord> activityRecords, ArraySet<Task> tasks,
-            boolean isOpen) {
-        for (int i = activityRecords.size() - 1; i >= 0; i--) {
-            final ActivityRecord activity = activityRecords.valueAt(i);
-            final Task task = activity.getTask();
-            if (task == null) continue;
-
-            if (isOpen == activity.isVisibleRequested()) {
-                tasks.add(task);
-            }
-        }
-    }
-
     void dump(PrintWriter pw, String prefix) {
         mTaskSnapshotController.dump(pw, prefix);
         mActivitySnapshotController.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 58e1c54..f4f641f 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.graphics.Bitmap.CompressFormat.JPEG;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -26,6 +27,7 @@
 import android.graphics.Bitmap;
 import android.os.Process;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.window.TaskSnapshot;
@@ -249,6 +251,7 @@
 
         @Override
         void write() {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "StoreWriteQueueItem");
             if (!mPersistInfoProvider.createDirectory(mUserId)) {
                 Slog.e(TAG, "Unable to create snapshot directory for user dir="
                         + mPersistInfoProvider.getDirectory(mUserId));
@@ -263,6 +266,7 @@
             if (failed) {
                 deleteSnapshot(mId, mUserId, mPersistInfoProvider);
             }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
         boolean writeProto() {
@@ -373,7 +377,9 @@
 
         @Override
         void write() {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "DeleteWriteQueueItem");
             deleteSnapshot(mId, mUserId, mPersistInfoProvider);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index cff86ad..a23547e 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import android.annotation.IntDef;
+
 import com.android.server.wm.StartingSurfaceController.StartingSurface;
 
 /**
@@ -23,6 +25,20 @@
  */
 public abstract class StartingData {
 
+    /** Nothing need to do after transaction */
+    static final int AFTER_TRANSACTION_IDLE = 0;
+    /** Remove the starting window directly after transaction done. */
+    static final int AFTER_TRANSACTION_REMOVE_DIRECTLY = 1;
+    /** Do copy splash screen to client after transaction done. */
+    static final int AFTER_TRANSACTION_COPY_TO_CLIENT = 2;
+
+    @IntDef(prefix = { "AFTER_TRANSACTION" }, value = {
+            AFTER_TRANSACTION_IDLE,
+            AFTER_TRANSACTION_REMOVE_DIRECTLY,
+            AFTER_TRANSACTION_COPY_TO_CLIENT,
+    })
+    @interface AfterTransaction {}
+
     protected final WindowManagerService mService;
     protected final int mTypeParams;
 
@@ -38,6 +54,10 @@
      */
     Task mAssociatedTask;
 
+
+    /** Whether the starting window is resized from transfer across activities. */
+    boolean mResizedFromTransfer;
+
     /** Whether the starting window is drawn. */
     boolean mIsDisplayed;
 
@@ -56,11 +76,14 @@
      * This starting window should be removed after applying the start transaction of transition,
      * which ensures the app window has shown.
      */
-    boolean mRemoveAfterTransaction;
+    @AfterTransaction int mRemoveAfterTransaction;
 
     /** Whether to prepare the removal animation. */
     boolean mPrepareRemoveAnimation;
 
+    /** Non-zero if this starting window is added in a collecting transition. */
+    int mTransitionId;
+
     protected StartingData(WindowManagerService service, int typeParams) {
         mService = service;
         mTypeParams = typeParams;
diff --git a/services/core/java/com/android/server/wm/SynchedDeviceConfig.java b/services/core/java/com/android/server/wm/SynchedDeviceConfig.java
new file mode 100644
index 0000000..c2e819e
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SynchedDeviceConfig.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * Utility class that caches {@link DeviceConfig} flags and listens to updates by implementing
+ * {@link DeviceConfig.OnPropertiesChangedListener}.
+ */
+final class SynchedDeviceConfig implements DeviceConfig.OnPropertiesChangedListener {
+
+    private final String mNamespace;
+    private final Executor mExecutor;
+
+    private final Map<String, SynchedDeviceConfigEntry> mDeviceConfigEntries;
+
+    /**
+     * @param namespace The namespace for the {@link DeviceConfig}
+     * @param executor  The {@link Executor} implementation to use when receiving updates
+     * @return the Builder implementation for the SynchedDeviceConfig
+     */
+    @NonNull
+    static SynchedDeviceConfigBuilder builder(@NonNull String namespace,
+            @NonNull Executor executor) {
+        return new SynchedDeviceConfigBuilder(namespace, executor);
+    }
+
+    private SynchedDeviceConfig(@NonNull String namespace, @NonNull Executor executor,
+            @NonNull Map<String, SynchedDeviceConfigEntry> deviceConfigEntries) {
+        mNamespace = namespace;
+        mExecutor = executor;
+        mDeviceConfigEntries = deviceConfigEntries;
+    }
+
+    @Override
+    public void onPropertiesChanged(@NonNull final DeviceConfig.Properties properties) {
+        for (SynchedDeviceConfigEntry entry : mDeviceConfigEntries.values()) {
+            if (properties.getKeyset().contains(entry.mFlagKey)) {
+                entry.updateValue(properties.getBoolean(entry.mFlagKey, entry.mDefaultValue));
+            }
+        }
+    }
+
+    /**
+     * Builds the {@link SynchedDeviceConfig} and start listening to the {@link DeviceConfig}
+     * updates.
+     *
+     * @return The {@link SynchedDeviceConfig}
+     */
+    @NonNull
+    private SynchedDeviceConfig start() {
+        DeviceConfig.addOnPropertiesChangedListener(mNamespace,
+                mExecutor, /* onPropertiesChangedListener */ this);
+        return this;
+    }
+
+    /**
+     * Requests a {@link DeviceConfig} update for all the flags
+     */
+    @NonNull
+    private SynchedDeviceConfig updateFlags() {
+        mDeviceConfigEntries.forEach((key, entry) -> entry.updateValue(
+                isDeviceConfigFlagEnabled(key, entry.mDefaultValue)));
+        return this;
+    }
+
+    /**
+     * Returns values of the {@code key} flag with the following criteria:
+     *
+     * <ul>
+     *     <li>{@code false} if the build time flag is disabled.
+     *     <li>{@code defaultValue} if the build time flag is enabled and no {@link DeviceConfig}
+     *          updates happened
+     *     <li>Last value from {@link DeviceConfig} in case of updates.
+     * </ul>
+     *
+     * @throws IllegalArgumentException {@code key} isn't recognised.
+     */
+    boolean getFlagValue(@NonNull String key) {
+        return findEntry(key).map(SynchedDeviceConfigEntry::getValue)
+                .orElseThrow(() -> new IllegalArgumentException("Unexpected flag name: " + key));
+    }
+
+    /**
+     * @return {@code true} if the flag for the given {@code key} was enabled at build time.
+     */
+    boolean isBuildTimeFlagEnabled(@NonNull String key) {
+        return findEntry(key).map(SynchedDeviceConfigEntry::isBuildTimeFlagEnabled)
+                .orElseThrow(() -> new IllegalArgumentException("Unexpected flag name: " + key));
+    }
+
+    private boolean isDeviceConfigFlagEnabled(@NonNull String key, boolean defaultValue) {
+        return DeviceConfig.getBoolean(mNamespace, key, defaultValue);
+    }
+
+    @NonNull
+    private Optional<SynchedDeviceConfigEntry> findEntry(@NonNull String key) {
+        return Optional.ofNullable(mDeviceConfigEntries.get(key));
+    }
+
+    static class SynchedDeviceConfigBuilder {
+
+        private final String mNamespace;
+        private final Executor mExecutor;
+
+        private final Map<String, SynchedDeviceConfigEntry> mDeviceConfigEntries =
+                new ConcurrentHashMap<>();
+
+        private SynchedDeviceConfigBuilder(@NonNull String namespace, @NonNull Executor executor) {
+            mNamespace = namespace;
+            mExecutor = executor;
+        }
+
+        @NonNull
+        SynchedDeviceConfigBuilder addDeviceConfigEntry(@NonNull String key,
+                boolean defaultValue, boolean enabled) {
+            if (mDeviceConfigEntries.containsKey(key)) {
+                throw new AssertionError("Key already present: " + key);
+            }
+            mDeviceConfigEntries.put(key,
+                    new SynchedDeviceConfigEntry(key, defaultValue, enabled));
+            return this;
+        }
+
+        @NonNull
+        SynchedDeviceConfig build() {
+            return new SynchedDeviceConfig(mNamespace, mExecutor,
+                    mDeviceConfigEntries).updateFlags().start();
+        }
+    }
+
+    /**
+     * Contains all the information related to an entry to be managed by DeviceConfig
+     */
+    private static class SynchedDeviceConfigEntry {
+
+        // The key of the specific configuration flag
+        private final String mFlagKey;
+
+        // The value of the flag at build time.
+        private final boolean mBuildTimeFlagEnabled;
+
+        // The initial value of the flag when mBuildTimeFlagEnabled is true.
+        private final boolean mDefaultValue;
+
+        // The current value of the flag when mBuildTimeFlagEnabled is true.
+        private volatile boolean mOverrideValue;
+
+        private SynchedDeviceConfigEntry(@NonNull String flagKey, boolean defaultValue,
+                boolean enabled) {
+            mFlagKey = flagKey;
+            mOverrideValue = mDefaultValue = defaultValue;
+            mBuildTimeFlagEnabled = enabled;
+        }
+
+        @NonNull
+        private void updateValue(boolean newValue) {
+            mOverrideValue = newValue;
+        }
+
+        private boolean getValue() {
+            return mBuildTimeFlagEnabled && mOverrideValue;
+        }
+
+        private boolean isBuildTimeFlagEnabled() {
+            return mBuildTimeFlagEnabled;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
index 878b33f..1007357 100644
--- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -20,6 +20,8 @@
 import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
 import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
 import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
+import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT;
+import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
 
 import android.annotation.NonNull;
 import android.content.Context;
@@ -59,6 +61,12 @@
     private static final int SWIPE_FROM_RIGHT = 3;
     private static final int SWIPE_FROM_LEFT = 4;
 
+    private static final int TRACKPAD_SWIPE_NONE = 0;
+    private static final int TRACKPAD_SWIPE_FROM_TOP = 1;
+    private static final int TRACKPAD_SWIPE_FROM_BOTTOM = 2;
+    private static final int TRACKPAD_SWIPE_FROM_RIGHT = 3;
+    private static final int TRACKPAD_SWIPE_FROM_LEFT = 4;
+
     private final Context mContext;
     private final Handler mHandler;
     private int mDisplayCutoutTouchableRegionSize;
@@ -207,6 +215,25 @@
                 break;
             case MotionEvent.ACTION_MOVE:
                 if (mSwipeFireable) {
+                    int trackpadSwipe = detectTrackpadThreeFingerSwipe(event);
+                    mSwipeFireable = trackpadSwipe == TRACKPAD_SWIPE_NONE;
+                    if (!mSwipeFireable) {
+                        if (trackpadSwipe == TRACKPAD_SWIPE_FROM_TOP) {
+                            if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop from trackpad");
+                            mCallbacks.onSwipeFromTop();
+                        } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_BOTTOM) {
+                            if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom from trackpad");
+                            mCallbacks.onSwipeFromBottom();
+                        } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_RIGHT) {
+                            if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight from trackpad");
+                            mCallbacks.onSwipeFromRight();
+                        } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_LEFT) {
+                            if (DEBUG) Slog.d(TAG, "Firing onSwipeFromLeft from trackpad");
+                            mCallbacks.onSwipeFromLeft();
+                        }
+                        break;
+                    }
+
                     final int swipe = detectSwipe(event);
                     mSwipeFireable = swipe == SWIPE_NONE;
                     if (swipe == SWIPE_FROM_TOP) {
@@ -300,6 +327,31 @@
         return mDownPointers - 1;
     }
 
+    private int detectTrackpadThreeFingerSwipe(MotionEvent move) {
+        if (!isTrackpadThreeFingerSwipe(move)) {
+            return TRACKPAD_SWIPE_NONE;
+        }
+
+        float dx = move.getX() - mDownX[0];
+        float dy = move.getY() - mDownY[0];
+        if (Math.abs(dx) < Math.abs(dy)) {
+            if (Math.abs(dy) > mSwipeDistanceThreshold) {
+                return dy > 0 ? TRACKPAD_SWIPE_FROM_TOP : TRACKPAD_SWIPE_FROM_BOTTOM;
+            }
+        } else {
+            if (Math.abs(dx) > mSwipeDistanceThreshold) {
+                return dx > 0 ? TRACKPAD_SWIPE_FROM_LEFT : TRACKPAD_SWIPE_FROM_RIGHT;
+            }
+        }
+
+        return TRACKPAD_SWIPE_NONE;
+    }
+
+    private static boolean isTrackpadThreeFingerSwipe(MotionEvent event) {
+        return event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+                && event.getAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3;
+    }
+
     private int detectSwipe(MotionEvent move) {
         final int historySize = move.getHistorySize();
         final int pointerCount = move.getPointerCount();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ceb80d6..0f0189e61 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -49,6 +49,7 @@
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.SurfaceControl.METADATA_TASK_ID;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
@@ -1271,6 +1272,10 @@
         if (isPersistable) {
             mLastTimeMoved = System.currentTimeMillis();
         }
+        if (toTop && inRecents) {
+            // If task is in recents, ensure it is at the top
+            mTaskSupervisor.mRecentTasks.add(this);
+        }
     }
 
     // Close up recents linked list.
@@ -1842,6 +1847,9 @@
                 td.setEnsureStatusBarContrastWhenTransparent(
                         atd.getEnsureStatusBarContrastWhenTransparent());
             }
+            if (td.getStatusBarAppearance() == 0) {
+                td.setStatusBarAppearance(atd.getStatusBarAppearance());
+            }
             if (td.getNavigationBarColor() == 0) {
                 td.setNavigationBarColor(atd.getNavigationBarColor());
                 td.setEnsureNavigationBarContrastWhenTransparent(
@@ -2858,7 +2866,7 @@
                     getDisplayContent().getInsetsStateController().getRawInsetsState();
             for (int i = state.sourceSize() - 1; i >= 0; i--) {
                 final InsetsSource source = state.sourceAt(i);
-                if (source.insetsRoundedCornerFrame()) {
+                if (source.hasFlags(FLAG_INSETS_ROUNDED_CORNER)) {
                     animationBounds.inset(source.calculateVisibleInsets(animationBounds));
                 }
             }
@@ -2870,8 +2878,8 @@
             // No need to check if allowed if it's leaving dragResize
             if (dragResizing
                     && !(getRootTask().getWindowingMode() == WINDOWING_MODE_FREEFORM)) {
-                throw new IllegalArgumentException("Drag resize not allow for root task id="
-                        + getRootTaskId());
+                Slog.e(TAG, "Drag resize isn't allowed for root task id=" + getRootTaskId());
+                return;
             }
             mDragResizing = dragResizing;
             resetDragResizingChangeReported();
@@ -3451,6 +3459,8 @@
         info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
         info.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
         info.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
+        info.isUserFullscreenOverrideEnabled = top != null
+                && top.mLetterboxUiController.shouldApplyUserFullscreenOverride();
         info.isFromLetterboxDoubleTap = top != null && top.mLetterboxUiController.isFromDoubleTap();
         if (info.isLetterboxDoubleTapEnabled) {
             info.topActivityLetterboxWidth = top.getBounds().width();
@@ -3465,6 +3475,11 @@
                         top.mLetterboxUiController.getLetterboxPositionForVerticalReachability();
             }
         }
+        // User Aspect Ratio Settings is enabled if the app is not in SCM
+        info.topActivityEligibleForUserAspectRatioButton =
+                mWmService.mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
+                        && top != null && !info.topActivityInSizeCompat;
+        info.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed();
     }
 
     /**
@@ -4491,6 +4506,10 @@
         return mForceHiddenFlags != 0;
     }
 
+    boolean isForceHiddenForPinnedTask() {
+        return (mForceHiddenFlags & FLAG_FORCE_HIDDEN_FOR_PINNED_TASK) != 0;
+    }
+
     @Override
     protected boolean isForceTranslucent() {
         return mForceTranslucent;
@@ -5238,17 +5257,21 @@
             // Ensure that we do not trigger entering PiP an activity on the root pinned task.
             return;
         }
-        final boolean isTransient = opts != null && opts.getTransientLaunch();
-        final Task targetRootTask = toFrontTask != null
-                ? toFrontTask.getRootTask() : toFrontActivity.getRootTask();
-        if (targetRootTask != null && (targetRootTask.isActivityTypeAssistant() || isTransient)) {
-            // Ensure the task/activity being brought forward is not the assistant and is not
-            // transient. In the case of transient-launch, we want to wait until the end of the
-            // transition and only allow switch if the transient launch was committed.
+        final Task targetRootTask = toFrontTask != null ? toFrontTask.getRootTask()
+                : toFrontActivity != null ? toFrontActivity.getRootTask() : null;
+        if (targetRootTask == null) {
+            Slog.e(TAG, "No root task for enter pip, both to front task and activity are null?");
             return;
         }
-        pipCandidate.supportsEnterPipOnTaskSwitch = true;
+        final boolean isTransient = opts != null && opts.getTransientLaunch()
+                || (targetRootTask.mTransitionController.isTransientHide(targetRootTask));
 
+        // Ensure the task/activity being brought forward is not the assistant and is not transient
+        // nor transient hide target. In the case of transient-launch, we want to wait until the end
+        // of the transition and only allow to enter pip on task switch after the transient launch
+        // was committed.
+        pipCandidate.supportsEnterPipOnTaskSwitch = !targetRootTask.isActivityTypeAssistant()
+                && !isTransient;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 0c1f33c..f340c26 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -329,6 +329,15 @@
      */
     private boolean mDelayLastActivityRemoval;
 
+    /**
+     * Whether the activity navigation should be isolated. That is, Activities cannot be launched
+     * on an isolated TaskFragment, unless the activity is launched from an Activity in the same
+     * isolated TaskFragment, or explicitly requested to be launched to.
+     * <p>
+     * Note that only an embedded TaskFragment can be isolated.
+     */
+    private boolean mIsolatedNav;
+
     final Point mLastSurfaceSize = new Point();
 
     private final Rect mTmpBounds = new Rect();
@@ -481,6 +490,19 @@
         return mAnimationParams;
     }
 
+    /** @see #mIsolatedNav */
+    void setIsolatedNav(boolean isolatedNav) {
+        if (!isEmbedded()) {
+            return;
+        }
+        mIsolatedNav = isolatedNav;
+    }
+
+    /** @see #mIsolatedNav */
+    boolean isIsolatedNav() {
+        return isEmbedded() && mIsolatedNav;
+    }
+
     TaskFragment getAdjacentTaskFragment() {
         return mAdjacentTaskFragment;
     }
@@ -1020,7 +1042,7 @@
         final WindowContainer<?> parent = getParent();
         final Task thisTask = asTask();
         if (thisTask != null && parent.asTask() == null
-                && mTransitionController.isTransientHide(thisTask)) {
+                && mTransitionController.isTransientVisible(thisTask)) {
             // Keep transient-hide root tasks visible. Non-root tasks still follow standard rule.
             return TASK_FRAGMENT_VISIBILITY_VISIBLE;
         }
@@ -1162,7 +1184,7 @@
     }
 
     final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
-            boolean deferPause) {
+            boolean skipPause) {
         ActivityRecord next = topRunningActivity(true /* focusableOnly */);
         if (next == null || !next.canResumeByCompat()) {
             return false;
@@ -1170,11 +1192,9 @@
 
         next.delayedResume = false;
 
-        // If we are currently pausing an activity, then don't do anything until that is done.
-        final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
-        if (!allPausedComplete) {
-            ProtoLog.v(WM_DEBUG_STATES,
-                    "resumeTopActivity: Skip resume: some activity pausing.");
+        if (!skipPause && !mRootWindowContainer.allPausedActivitiesComplete()) {
+            // If we aren't skipping pause, then we have to wait for currently pausing activities.
+            ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivity: Skip resume: some activity pausing.");
             return false;
         }
 
@@ -1238,7 +1258,7 @@
             lastResumed = lastFocusedRootTask.getTopResumedActivity();
         }
 
-        boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);
+        boolean pausing = !skipPause && taskDisplayArea.pauseBackTasks(next);
         if (mResumedActivity != null) {
             ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Pausing %s", mResumedActivity);
             pausing |= startPausing(mTaskSupervisor.mUserLeaving, false /* uiSleeping */,
@@ -3034,7 +3054,8 @@
     @Override
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         super.dump(pw, prefix, dumpAll);
-        pw.println(prefix + "bounds=" + getBounds().toShortString());
+        pw.println(prefix + "bounds=" + getBounds().toShortString()
+                + (mIsolatedNav ? ", isolatedNav" : ""));
         final String doublePrefix = prefix + "  ";
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final WindowContainer<?> child = mChildren.get(i);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index cdb4ad6..b72d027 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -790,12 +790,8 @@
     }
 
     boolean isSupportWindowlessStartingSurface() {
-        // Enable after ag/20426257
         final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
-        if (lastOrganizer == null) {
-            return false;
-        }
-        return false;
+        return lastOrganizer != null;
     }
     /**
      * Notify the shell ({@link com.android.wm.shell.ShellTaskOrganizer} that the client has
diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java
index 29c192c..f882b9b 100644
--- a/services/core/java/com/android/server/wm/TaskPersister.java
+++ b/services/core/java/com/android/server/wm/TaskPersister.java
@@ -509,7 +509,7 @@
 
     private static boolean createParentDirectory(String filePath) {
         File parentDir = new File(filePath).getParentFile();
-        return parentDir.exists() || parentDir.mkdirs();
+        return parentDir.isDirectory() || parentDir.mkdir();
     }
 
     private static class TaskWriteQueueItem implements PersisterQueue.WriteQueueItem {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index c747c09..4eb4290 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -77,13 +76,6 @@
         setSnapshotEnabled(snapshotEnabled);
     }
 
-    void systemReady() {
-        if (!shouldDisableSnapshots()) {
-            mService.mSnapshotController.registerTransitionStateConsumer(TASK_CLOSE,
-                    this::handleTaskClose);
-        }
-    }
-
     static PersistInfoProvider createPersistInfoProvider(WindowManagerService service,
             BaseAppSnapshotPersister.DirectoryResolver resolver) {
         final float highResTaskSnapshotScale = service.mContext.getResources().getFloat(
@@ -116,20 +108,23 @@
                 enableLowResSnapshots, lowResScaleFactor, use16BitFormat);
     }
 
-    void handleTaskClose(SnapshotController.TransitionState<Task> closeTaskTransitionRecord) {
+    // Still needed for legacy transition.(AppTransitionControllerTest)
+    void handleClosingApps(ArraySet<ActivityRecord> closingApps) {
         if (shouldDisableSnapshots()) {
             return;
         }
+        // We need to take a snapshot of the task if and only if all activities of the task are
+        // either closing or hidden.
         mTmpTasks.clear();
-        final ArraySet<Task> tasks = closeTaskTransitionRecord.getParticipant(false /* open */);
-        if (mService.mAtmService.getTransitionController().isShellTransitionsEnabled()) {
-            mTmpTasks.addAll(tasks);
-        } else {
-            for (Task task : tasks) {
-                getClosingTasksInner(task, mTmpTasks);
-            }
+        for (int i = closingApps.size() - 1; i >= 0; i--) {
+            final ActivityRecord activity = closingApps.valueAt(i);
+            final Task task = activity.getTask();
+            if (task == null) continue;
+
+            getClosingTasksInner(task, mTmpTasks);
         }
         snapshotTasks(mTmpTasks);
+        mTmpTasks.clear();
         mSkipClosingAppSnapshotTasks.clear();
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index cd15119..3e8c017 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
+import android.os.Trace;
 import android.util.ArraySet;
 import android.window.TaskSnapshot;
 
@@ -102,6 +105,7 @@
 
         @Override
         void write() {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RemoveObsoleteFilesQueueItem");
             final ArraySet<Integer> newPersistedTaskIds;
             synchronized (mLock) {
                 newPersistedTaskIds = new ArraySet<>(mPersistedTaskIdsSinceLastRemoveObsolete);
@@ -120,6 +124,7 @@
                     }
                 }
             }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
         @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 65c5c9b..f1fb17b 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -78,6 +78,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -359,22 +360,30 @@
         mTransientLaunches.put(activity, restoreBelow);
         setTransientLaunchToChanges(activity);
 
-        if (restoreBelow != null) {
-            final Task transientRootTask = activity.getRootTask();
+        final Task transientRootTask = activity.getRootTask();
+        final WindowContainer<?> parent = restoreBelow != null ? restoreBelow.getParent()
+                : (transientRootTask != null ? transientRootTask.getParent() : null);
+        if (parent != null) {
             // Collect all visible tasks which can be occluded by the transient activity to
             // make sure they are in the participants so their visibilities can be updated when
             // finishing transition.
-            ((WindowContainer<?>) restoreBelow.getParent()).forAllTasks(t -> {
+            parent.forAllTasks(t -> {
+                // Skip transient-launch task
+                if (t == transientRootTask) return false;
                 if (t.isVisibleRequested() && !t.isAlwaysOnTop()
                         && !t.getWindowConfiguration().tasksAreFloating()) {
-                    if (t.isRootTask() && t != transientRootTask) {
+                    if (t.isRootTask()) {
                         mTransientHideTasks.add(t);
                     }
                     if (t.isLeafTask()) {
                         collect(t);
                     }
                 }
-                return t == restoreBelow;
+                return restoreBelow != null
+                        // Stop at the restoreBelow task
+                        ? t == restoreBelow
+                        // Or stop at the last visible task if no restore-below (new task)
+                        : (t.isRootTask() && t.fillsParent());
             });
             // Add FLAG_ABOVE_TRANSIENT_LAUNCH to the tree of transient-hide tasks,
             // so ChangeInfo#hasChanged() can return true to report the transition info.
@@ -398,6 +407,36 @@
         return false;
     }
 
+    /** Returns {@code true} if the task should keep visible if this is a transient transition. */
+    boolean isTransientVisible(@NonNull Task task) {
+        if (mTransientLaunches == null) return false;
+        int occludedCount = 0;
+        final int numTransient = mTransientLaunches.size();
+        for (int i = numTransient - 1; i >= 0; --i) {
+            final Task transientRoot = mTransientLaunches.keyAt(i).getRootTask();
+            if (transientRoot == null) continue;
+            final WindowContainer<?> rootParent = transientRoot.getParent();
+            if (rootParent == null || rootParent.getTopChild() == transientRoot) continue;
+            final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor
+                    .mOpaqueActivityHelper.getOpaqueActivity(rootParent);
+            if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) {
+                occludedCount++;
+            }
+        }
+        if (occludedCount == numTransient) {
+            for (int i = mTransientLaunches.size() - 1; i >= 0; --i) {
+                if (mTransientLaunches.keyAt(i).isDescendantOf(task)) {
+                    // Keep transient activity visible until transition finished, so it won't pause
+                    // with transient-hide tasks that may delay resuming the next top.
+                    return true;
+                }
+            }
+            // Let transient-hide activities pause before transition is finished.
+            return false;
+        }
+        return isInTransientHide(task);
+    }
+
     boolean canApplyDim(@NonNull Task task) {
         if (mTransientLaunches == null) return true;
         final Dimmer dimmer = task.getDimmer();
@@ -606,14 +645,16 @@
             }
         }
         if (mParticipants.contains(wc)) return;
-        // Wallpaper is like in a static drawn state unless display may have changes, so exclude
-        // the case to reduce transition latency waiting for the unchanged wallpaper to redraw.
-        final boolean needSync = (!isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent))
-                // Transient-hide may be hidden later, so no need to request redraw.
-                && !isInTransientHide(wc);
-        if (needSync) {
+        // Transient-hide may be hidden later, so no need to request redraw.
+        if (!isInTransientHide(wc)) {
             mSyncEngine.addToSyncSet(mSyncId, wc);
         }
+        if (wc.asWindowToken() != null && wc.asWindowToken().mRoundedCornerOverlay) {
+            // Only need to sync the transaction (SyncSet) without ChangeInfo because cutout and
+            // rounded corner overlay never need animations. Especially their surfaces may be put
+            // in root (null, see WindowToken#makeSurface()) that cannot reparent.
+            return;
+        }
         ChangeInfo info = mChanges.get(wc);
         if (info == null) {
             info = new ChangeInfo(wc);
@@ -918,20 +959,25 @@
         }
         // Need to update layers on involved displays since they were all paused while
         // the animation played. This puts the layers back into the correct order.
-        mController.mBuildingFinishLayers = true;
-        try {
-            for (int i = displays.size() - 1; i >= 0; --i) {
-                if (displays.valueAt(i) == null) continue;
-                displays.valueAt(i).assignChildLayers(t);
-            }
-        } finally {
-            mController.mBuildingFinishLayers = false;
+        for (int i = displays.size() - 1; i >= 0; --i) {
+            if (displays.valueAt(i) == null) continue;
+            updateDisplayLayers(displays.valueAt(i), t);
         }
+
         for (int i = 0; i < info.getRootCount(); ++i) {
             t.reparent(info.getRoot(i).getLeash(), null);
         }
     }
 
+    private static void updateDisplayLayers(DisplayContent dc, SurfaceControl.Transaction t) {
+        dc.mTransitionController.mBuildingFinishLayers = true;
+        try {
+            dc.assignChildLayers(t);
+        } finally {
+            dc.mTransitionController.mBuildingFinishLayers = false;
+        }
+    }
+
     /**
      * Build a transaction that cleans-up transition-only surfaces (transition root and snapshots).
      * This will ALWAYS be applied on transition finish just in-case
@@ -1145,8 +1191,6 @@
                                         "  Skipping post-transition snapshot for task %d",
                                         task.mTaskId);
                             }
-                            snapController.mActivitySnapshotController
-                                    .notifyAppVisibilityChanged(ar, false /* visible */);
                         }
                         ar.commitVisibility(false /* visible */, false /* performLayout */,
                                 true /* fromTransition */);
@@ -1180,16 +1224,6 @@
                 hasParticipatedDisplay = true;
                 continue;
             }
-            final WallpaperWindowToken wt = participant.asWallpaperToken();
-            if (wt != null) {
-                final boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(wt);
-                if (!visibleAtTransitionEnd && !wt.isVisibleRequested()) {
-                    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
-                            "  Commit wallpaper becoming invisible: %s", wt);
-                    wt.commitVisibility(false /* visible */);
-                }
-                continue;
-            }
             final Task tr = participant.asTask();
             if (tr != null && tr.isVisibleRequested() && tr.inPinnedWindowingMode()) {
                 final ActivityRecord top = tr.getTopNonFinishingActivity();
@@ -1209,6 +1243,20 @@
                 }
             }
         }
+        // Commit wallpaper visibility after activity, because usually the wallpaper target token is
+        // an activity, and wallpaper's visibility is depends on activity's visibility.
+        for (int i = mParticipants.size() - 1; i >= 0; --i) {
+            final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
+            if (wt == null) continue;
+            final WindowState target = wt.mDisplayContent.mWallpaperController.getWallpaperTarget();
+            final boolean isTargetInvisible = target == null || !target.mToken.isVisible();
+            if (isTargetInvisible || (!wt.isVisibleRequested()
+                    && !mVisibleAtTransitionEndTokens.contains(wt))) {
+                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                        "  Commit wallpaper becoming invisible: %s", wt);
+                wt.commitVisibility(false /* visible */);
+            }
+        }
         if (committedSomeInvisible) {
             mController.onCommittedInvisibles();
         }
@@ -1283,6 +1331,7 @@
             if (asyncRotationController != null && containsChangeFor(dc, mTargets)) {
                 asyncRotationController.onTransitionFinished();
             }
+            dc.onTransitionFinished();
             if (hasParticipatedDisplay && dc.mDisplayRotationCompatPolicy != null) {
                 final ChangeInfo changeInfo = mChanges.get(dc);
                 if (changeInfo != null
@@ -1338,6 +1387,7 @@
         // Handle back animation if it's already started.
         mController.mAtm.mBackNavigationController.onTransitionFinish(mTargets, this);
         mController.mFinishingTransition = null;
+        mController.mSnapshotController.onTransitionFinish(mType, mTargets);
     }
 
     void abort() {
@@ -1453,7 +1503,8 @@
         mTargets = calculateTargets(mParticipants, mChanges);
 
         // Check whether the participants were animated from back navigation.
-        mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets);
+        mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets,
+                transaction);
         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
         info.setDebugId(mSyncId);
         mController.assignTrack(this, info);
@@ -1541,16 +1592,7 @@
         // transferred. If transition is transient, IME won't be moved during the transition and
         // the tasks are still live, so we take the snapshot at the end of the transition instead.
         if (mTransientLaunches == null) {
-            for (int i = mParticipants.size() - 1; i >= 0; --i) {
-                final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
-                if (ar == null || ar.getTask() == null
-                        || ar.getTask().isVisibleRequested()) continue;
-                final ChangeInfo change = mChanges.get(ar);
-                // Intentionally skip record snapshot for changes originated from PiP.
-                if (change != null && change.mWindowingMode == WINDOWING_MODE_PINNED) continue;
-                mController.mSnapshotController.mTaskSnapshotController.recordSnapshot(
-                        ar.getTask(), false /* allowSnapshotHome */);
-            }
+            mController.mSnapshotController.onTransactionReady(mType, mTargets);
         }
 
         // This is non-null only if display has changes. It handles the visible windows that don't
@@ -2299,8 +2341,9 @@
             final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
             // Don't include wallpapers since they are in a different DA.
             if (isWallpaper(wc)) continue;
-            final int endDisplayId = getDisplayId(wc);
-            if (endDisplayId < 0) continue;
+            final DisplayContent dc = wc.getDisplayContent();
+            if (dc == null) continue;
+            final int endDisplayId = dc.getDisplayId();
 
             // Check if Root was already created for this display with a higher-Z window
             if (outInfo.findRootIndex(endDisplayId) >= 0) continue;
@@ -2322,6 +2365,9 @@
             final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
                     "Transition Root: " + leashReference.getName()).build();
             rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots");
+            // Update layers to start transaction because we prevent assignment during collect, so
+            // the layer of transition root can be correct.
+            updateDisplayLayers(dc, startT);
             startT.setLayer(rootLeash, leashReference.getLastLayer());
             outInfo.addRootLeash(endDisplayId, rootLeash,
                     ancestor.getBounds().left, ancestor.getBounds().top);
@@ -2697,6 +2743,26 @@
         });
     }
 
+    /**
+     * Returns {@code true} if the transition and the corresponding transaction should be applied
+     * on display thread. Currently, this only checks for display rotation change because the order
+     * of dispatching the new display info will be after requesting the windows to sync drawing.
+     * That avoids potential flickering of screen overlays (e.g. cutout, rounded corner). Also,
+     * because the display thread has a higher priority, it is faster to perform the configuration
+     * changes and window hierarchy traversal.
+     */
+    boolean shouldApplyOnDisplayThread() {
+        for (int i = mParticipants.size() - 1; i >= 0; --i) {
+            final DisplayContent dc = mParticipants.valueAt(i).asDisplayContent();
+            if (dc == null) continue;
+            final ChangeInfo changeInfo = mChanges.get(dc);
+            if (changeInfo != null && changeInfo.mRotation != dc.getRotation()) {
+                return Looper.myLooper() != mController.mAtm.mWindowManager.mH.getLooper();
+            }
+        }
+        return false;
+    }
+
     /** Applies the new configuration for the changed displays. */
     void applyDisplayChangeIfNeeded() {
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index b05c6b4..48cca32 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -477,15 +477,22 @@
         if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) {
             return true;
         }
-        for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
-            if (mWaitingTransitions.get(i).isInTransientHide(task)) return true;
-        }
         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
             if (mPlayingTransitions.get(i).isInTransientHide(task)) return true;
         }
         return false;
     }
 
+    boolean isTransientVisible(@NonNull Task task) {
+        if (mCollectingTransition != null && mCollectingTransition.isTransientVisible(task)) {
+            return true;
+        }
+        for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+            if (mPlayingTransitions.get(i).isTransientVisible(task)) return true;
+        }
+        return false;
+    }
+
     boolean canApplyDim(@Nullable Task task) {
         if (task == null) {
             // Always allow non-activity window.
@@ -551,7 +558,9 @@
             return wc.asWindowState() == null;
         }
         // Always allow WindowState to assign layers since it won't affect transition.
-        return wc.asWindowState() != null || !isPlaying();
+        return wc.asWindowState() != null || (!isPlaying()
+                // Don't assign task while collecting.
+                && !(wc.asTask() != null && isCollecting()));
     }
 
     @WindowConfiguration.WindowingMode
@@ -571,6 +580,17 @@
     }
 
     /**
+     * Returns {@code true} if the window container is in the collecting transition, and its
+     * collected rotation is different from the target rotation.
+     */
+    boolean hasCollectingRotationChange(@NonNull WindowContainer<?> wc, int targetRotation) {
+        final Transition transition = mCollectingTransition;
+        if (transition == null || !transition.mParticipants.contains(wc)) return false;
+        final Transition.ChangeInfo changeInfo = transition.mChanges.get(wc);
+        return changeInfo != null && changeInfo.mRotation != targetRotation;
+    }
+
+    /**
      * @see #requestTransitionIfNeeded(int, int, WindowContainer, WindowContainer, RemoteTransition)
      */
     @Nullable
@@ -597,15 +617,6 @@
     /** Sets the sync method for the display change. */
     private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
             @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) {
-        final int startRotation = displayChange.getStartRotation();
-        final int endRotation = displayChange.getEndRotation();
-        if (startRotation != endRotation && (startRotation + endRotation) % 2 == 0) {
-            // 180 degrees rotation change may not change screen size. So the clients may draw
-            // some frames before and after the display projection transaction is applied by the
-            // remote player. That may cause some buffers to show in different rotation. So use
-            // sync method to pause clients drawing until the projection transaction is applied.
-            mSyncEngine.setSyncMethod(displayTransition.getSyncId(), BLASTSyncEngine.METHOD_BLAST);
-        }
         final Rect startBounds = displayChange.getStartAbsBounds();
         final Rect endBounds = displayChange.getEndAbsBounds();
         if (startBounds == null || endBounds == null) return;
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index af8fb02..c59d2d3 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -145,6 +145,27 @@
         }
     }
 
+    void logRemovingStartingWindow(@NonNull StartingData startingData) {
+        if (startingData.mTransitionId == 0) {
+            return;
+        }
+        try {
+            final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
+            final long protoToken = outputStream
+                    .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+            outputStream.write(com.android.server.wm.shell.Transition.ID,
+                    startingData.mTransitionId);
+            outputStream.write(
+                    com.android.server.wm.shell.Transition.STARTING_WINDOW_REMOVE_TIME_NS,
+                    SystemClock.elapsedRealtimeNanos());
+            outputStream.end(protoToken);
+
+            mTraceBuffer.add(outputStream);
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
+        }
+    }
+
     private void dumpTransitionTargetsToProto(ProtoOutputStream outputStream,
             Transition transition, ArrayList<ChangeInfo> targets) {
         Trace.beginSection("TransitionTracer#dumpTransitionTargetsToProto");
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index eb7b8c2..00bedcd 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -247,7 +247,7 @@
                 resources.getBoolean(
                         com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay);
         mIsLockscreenLiveWallpaperEnabled =
-                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", false);
+                SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true);
     }
 
     void resetLargestDisplay(Display display) {
@@ -308,29 +308,12 @@
         }
     }
 
-    private boolean shouldWallpaperBeVisible(WindowState wallpaperTarget) {
-        if (DEBUG_WALLPAPER) {
-            Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + " prev="
-                    + mPrevWallpaperTarget);
-        }
-        return wallpaperTarget != null || mPrevWallpaperTarget != null;
-    }
-
     boolean isWallpaperTargetAnimating() {
         return mWallpaperTarget != null && mWallpaperTarget.isAnimating(TRANSITION | PARENTS)
                 && (mWallpaperTarget.mActivityRecord == null
                         || !mWallpaperTarget.mActivityRecord.isWaitingForTransitionStart());
     }
 
-    void updateWallpaperVisibility() {
-        final boolean visible = shouldWallpaperBeVisible(mWallpaperTarget);
-
-        for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
-            final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
-            token.setVisibility(visible);
-        }
-    }
-
     /**
      * Make one wallpaper visible, according to {@attr showHome}.
      * This is called during the keyguard unlocking transition
@@ -801,11 +784,20 @@
         result.setWallpaperTarget(wallpaperTarget);
     }
 
+    public void updateWallpaperTokens(boolean keyguardLocked) {
+        if (DEBUG_WALLPAPER) {
+            Slog.v(TAG, "Wallpaper vis: target " + mWallpaperTarget + " prev="
+                    + mPrevWallpaperTarget);
+        }
+        updateWallpaperTokens(mWallpaperTarget != null || mPrevWallpaperTarget != null,
+                keyguardLocked);
+    }
+
     /**
      * Change the visibility of the top wallpaper to {@param visibility} and hide all the others.
      */
-    private void updateWallpaperTokens(boolean visibility, boolean locked) {
-        WindowState topWallpaper = mFindResults.getTopWallpaper(locked);
+    private void updateWallpaperTokens(boolean visibility, boolean keyguardLocked) {
+        WindowState topWallpaper = mFindResults.getTopWallpaper(keyguardLocked);
         WallpaperWindowToken topWallpaperToken =
                 topWallpaper == null ? null : topWallpaper.mToken.asWallpaperToken();
         for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 5ea8f65..8e0ad0d 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -170,7 +170,7 @@
                 linkFixedRotationTransform(wallpaperTarget.mToken);
             }
         }
-        if (mTransitionController.isShellTransitionsEnabled()) {
+        if (mTransitionController.inTransition(this)) {
             // If wallpaper is in transition, setVisible() will be called from commitVisibility()
             // when finishing transition. Otherwise commitVisibility() is already called from above
             // setVisibility().
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0152666..dae61da 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -81,7 +81,9 @@
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.os.Trace;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
 import android.util.Pools;
@@ -174,6 +176,9 @@
      */
     protected SparseArray<InsetsSourceProvider> mInsetsSourceProviders = null;
 
+    @Nullable
+    private ArrayMap<IBinder, DeathRecipient> mInsetsOwnerDeathRecipientMap;
+
     // List of children for this window container. List is in z-order as the children appear on
     // screen with the top-most window container at the tail of the list.
     protected final WindowList<E> mChildren = new WindowList<E>();
@@ -419,11 +424,12 @@
      * Adds an {@link InsetsFrameProvider} which describes what insets should be provided to
      * this {@link WindowContainer} and its children.
      *
-     * @param provider describes the insets types and the frames.
+     * @param provider describes the insets type and the frame.
+     * @param owner owns the insets source which only exists when the owner is alive.
      */
-    void addLocalInsetsFrameProvider(InsetsFrameProvider provider) {
-        if (provider == null) {
-            throw new IllegalArgumentException("Insets type not specified.");
+    void addLocalInsetsFrameProvider(InsetsFrameProvider provider, IBinder owner) {
+        if (provider == null || owner == null) {
+            throw new IllegalArgumentException("Insets provider or owner not specified.");
         }
         if (mDisplayContent == null) {
             // This is possible this container is detached when WM shell is responding to a previous
@@ -432,10 +438,26 @@
             Slog.w(TAG, "Can't add insets frame provider when detached. " + this);
             return;
         }
+
+        if (mInsetsOwnerDeathRecipientMap == null) {
+            mInsetsOwnerDeathRecipientMap = new ArrayMap<>();
+        }
+        DeathRecipient deathRecipient = mInsetsOwnerDeathRecipientMap.get(owner);
+        if (deathRecipient == null) {
+            deathRecipient = new DeathRecipient(owner);
+            try {
+                owner.linkToDeath(deathRecipient, 0);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to add source for " + provider + " since the owner has died.");
+                return;
+            }
+            mInsetsOwnerDeathRecipientMap.put(owner, deathRecipient);
+        }
+        final int id = provider.getId();
+        deathRecipient.addSourceId(id);
         if (mLocalInsetsSources == null) {
             mLocalInsetsSources = new SparseArray<>();
         }
-        final int id = provider.getId();
         if (mLocalInsetsSources.get(id) != null) {
             if (DEBUG) {
                 Slog.d(TAG, "The local insets source for this " + provider
@@ -448,27 +470,77 @@
         mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
     }
 
-    void removeLocalInsetsFrameProvider(InsetsFrameProvider provider) {
-        if (provider == null) {
-            throw new IllegalArgumentException("Insets type not specified.");
-        }
-        if (mLocalInsetsSources == null) {
-            return;
+    private class DeathRecipient implements IBinder.DeathRecipient {
+
+        private final IBinder mOwner;
+        private final ArraySet<Integer> mSourceIds = new ArraySet<>();
+
+        DeathRecipient(IBinder owner) {
+            mOwner = owner;
         }
 
-        final int id = provider.getId();
-        if (mLocalInsetsSources.get(id) == null) {
-            if (DEBUG) {
-                Slog.d(TAG, "Given " + provider + " doesn't have a local insets source.");
+        void addSourceId(int id) {
+            mSourceIds.add(id);
+        }
+
+        void removeSourceId(int id) {
+            mSourceIds.remove(id);
+        }
+
+        boolean hasSource() {
+            return !mSourceIds.isEmpty();
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mWmService.mGlobalLock) {
+                boolean changed = false;
+                for (int i = mSourceIds.size() - 1; i >= 0; i--) {
+                    changed |= removeLocalInsetsSource(mSourceIds.valueAt(i));
+                }
+                mSourceIds.clear();
+                mOwner.unlinkToDeath(this, 0);
+                mInsetsOwnerDeathRecipientMap.remove(mOwner);
+                if (changed && mDisplayContent != null) {
+                    mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
+                }
             }
-            return;
         }
-        mLocalInsetsSources.remove(id);
+    }
 
-        // Update insets if this window is attached.
-        if (mDisplayContent != null) {
+    void removeLocalInsetsFrameProvider(InsetsFrameProvider provider, IBinder owner) {
+        if (provider == null || owner == null) {
+            throw new IllegalArgumentException("Insets provider or owner not specified.");
+        }
+        final int id = provider.getId();
+        if (removeLocalInsetsSource(id) && mDisplayContent != null) {
             mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
         }
+        if (mInsetsOwnerDeathRecipientMap == null) {
+            return;
+        }
+        final DeathRecipient deathRecipient = mInsetsOwnerDeathRecipientMap.get(owner);
+        if (deathRecipient == null) {
+            return;
+        }
+        deathRecipient.removeSourceId(id);
+        if (!deathRecipient.hasSource()) {
+            owner.unlinkToDeath(deathRecipient, 0);
+            mInsetsOwnerDeathRecipientMap.remove(owner);
+        }
+    }
+
+    private boolean removeLocalInsetsSource(int id) {
+        if (mLocalInsetsSources == null) {
+            return false;
+        }
+        if (mLocalInsetsSources.removeReturnOld(id) == null) {
+            if (DEBUG) {
+                Slog.d(TAG, "Given id " + Integer.toHexString(id) + " doesn't exist.");
+            }
+            return false;
+        }
+        return true;
     }
 
     /**
@@ -1071,6 +1143,9 @@
     }
 
     void onResize() {
+        if (mControllableInsetProvider != null) {
+            mControllableInsetProvider.onWindowContainerBoundsChanged();
+        }
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowContainer wc = mChildren.get(i);
             wc.onParentResize();
@@ -1090,6 +1165,9 @@
     }
 
     void onMovedByResize() {
+        if (mControllableInsetProvider != null) {
+            mControllableInsetProvider.onWindowContainerBoundsChanged();
+        }
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowContainer wc = mChildren.get(i);
             wc.onMovedByResize();
@@ -4141,7 +4219,7 @@
                     getDisplayContent().getInsetsStateController().getSourceProviders();
             for (int i = providers.size(); i >= 0; i--) {
                 final InsetsSourceProvider insetProvider = providers.valueAt(i);
-                if (!insetProvider.getSource().insetsRoundedCornerFrame()) {
+                if (!insetProvider.getSource().hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
                     return;
                 }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1068700..3a19a3b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -763,8 +763,6 @@
                 Settings.Secure.getUriFor(Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS);
         private final Uri mPolicyControlUri =
                 Settings.Global.getUriFor(Settings.Global.POLICY_CONTROL);
-        private final Uri mPointerLocationUri =
-                Settings.System.getUriFor(Settings.System.POINTER_LOCATION);
         private final Uri mForceDesktopModeOnExternalDisplaysUri = Settings.Global.getUriFor(
                         Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS);
         private final Uri mFreeformWindowUri = Settings.Global.getUriFor(
@@ -792,7 +790,6 @@
             resolver.registerContentObserver(mImmersiveModeConfirmationsUri, false, this,
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(mPolicyControlUri, false, this, UserHandle.USER_ALL);
-            resolver.registerContentObserver(mPointerLocationUri, false, this, UserHandle.USER_ALL);
             resolver.registerContentObserver(mForceDesktopModeOnExternalDisplaysUri, false, this,
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(mFreeformWindowUri, false, this, UserHandle.USER_ALL);
@@ -816,11 +813,6 @@
                 return;
             }
 
-            if (mPointerLocationUri.equals(uri)) {
-                updatePointerLocation();
-                return;
-            }
-
             if (mForceDesktopModeOnExternalDisplaysUri.equals(uri)) {
                 updateForceDesktopModeOnExternalDisplays();
                 return;
@@ -869,7 +861,6 @@
 
         void loadSettings() {
             updateSystemUiSettings(false /* handleChange */);
-            updatePointerLocation();
             updateMaximumObscuringOpacityForTouch();
         }
 
@@ -900,21 +891,6 @@
             }
         }
 
-        void updatePointerLocation() {
-            ContentResolver resolver = mContext.getContentResolver();
-            final boolean enablePointerLocation = Settings.System.getIntForUser(resolver,
-                    Settings.System.POINTER_LOCATION, 0, UserHandle.USER_CURRENT) != 0;
-
-            if (mPointerLocationEnabled == enablePointerLocation) {
-                return;
-            }
-            mPointerLocationEnabled = enablePointerLocation;
-            synchronized (mGlobalLock) {
-                mRoot.forAllDisplayPolicies(
-                        p -> p.setPointerLocationEnabled(mPointerLocationEnabled));
-            }
-        }
-
         void updateForceDesktopModeOnExternalDisplays() {
             ContentResolver resolver = mContext.getContentResolver();
             final boolean enableForceDesktopMode = Settings.Global.getInt(resolver,
@@ -4539,7 +4515,8 @@
                     return;
                 }
                 dc.mRemoteInsetsControlTarget.setRequestedVisibleTypes(requestedVisibleTypes);
-                dc.getInsetsStateController().onInsetsModified(dc.mRemoteInsetsControlTarget);
+                dc.getInsetsStateController().onRequestedVisibleTypesChanged(
+                        dc.mRemoteInsetsControlTarget);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -7174,15 +7151,45 @@
 
     @Override
     public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
-        mContext.enforceCallingOrSelfPermission(REGISTER_WINDOW_MANAGER_LISTENERS,
-                "requestAppKeyboardShortcuts");
+        enforceRegisterWindowManagerListenersPermission("requestAppKeyboardShortcuts");
 
+        WindowState focusedWindow = getFocusedWindow();
+        if (focusedWindow == null || focusedWindow.mClient == null) {
+            notifyReceiverWithEmptyBundle(receiver);
+            return;
+        }
         try {
-            WindowState focusedWindow = getFocusedWindow();
-            if (focusedWindow != null && focusedWindow.mClient != null) {
-                getFocusedWindow().mClient.requestAppKeyboardShortcuts(receiver, deviceId);
-            }
+            focusedWindow.mClient.requestAppKeyboardShortcuts(receiver, deviceId);
         } catch (RemoteException e) {
+            notifyReceiverWithEmptyBundle(receiver);
+        }
+    }
+
+    @Override
+    public void requestImeKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
+        enforceRegisterWindowManagerListenersPermission("requestImeKeyboardShortcuts");
+
+        WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
+        if (imeWindow == null || imeWindow.mClient == null) {
+            notifyReceiverWithEmptyBundle(receiver);
+            return;
+        }
+        try {
+            imeWindow.mClient.requestAppKeyboardShortcuts(receiver, deviceId);
+        } catch (RemoteException e) {
+            notifyReceiverWithEmptyBundle(receiver);
+        }
+    }
+
+    private void enforceRegisterWindowManagerListenersPermission(String message) {
+        mContext.enforceCallingOrSelfPermission(REGISTER_WINDOW_MANAGER_LISTENERS, message);
+    }
+
+    private static void notifyReceiverWithEmptyBundle(IResultReceiver receiver) {
+        try {
+            receiver.send(0, Bundle.EMPTY);
+        } catch (RemoteException e) {
+            ProtoLog.e(WM_ERROR, "unable to call receiver for empty keyboard shortcuts");
         }
     }
 
@@ -8260,6 +8267,13 @@
         @Override
         public void addTrustedTaskOverlay(int taskId,
                 SurfaceControlViewHost.SurfacePackage overlay) {
+            if (overlay == null) {
+                throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
+            } else if (overlay.getSurfaceControl() == null
+                    || !overlay.getSurfaceControl().isValid()) {
+                throw new IllegalArgumentException(
+                        "Invalid overlay surfacecontrol passed in for task=" + taskId);
+            }
             synchronized (mGlobalLock) {
                 final Task task = mRoot.getRootTask(taskId);
                 if (task == null) {
@@ -8272,6 +8286,13 @@
         @Override
         public void removeTrustedTaskOverlay(int taskId,
                 SurfaceControlViewHost.SurfacePackage overlay) {
+            if (overlay == null) {
+                throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
+            } else if (overlay.getSurfaceControl() == null
+                    || !overlay.getSurfaceControl().isValid()) {
+                throw new IllegalArgumentException(
+                        "Invalid overlay surfacecontrol passed in for task=" + taskId);
+            }
             synchronized (mGlobalLock) {
                 final Task task = mRoot.getRootTask(taskId);
                 if (task == null) {
@@ -8737,24 +8758,22 @@
         final int sanitizedType = sanitizeWindowType(session, displayId, windowToken, type);
         final InputApplicationHandle applicationHandle;
         final String name;
-        final InputChannel clientChannel;
+        Objects.requireNonNull(outInputChannel);
         synchronized (mGlobalLock) {
             EmbeddedWindowController.EmbeddedWindow win =
                     new EmbeddedWindowController.EmbeddedWindow(session, this, window,
                             mInputToWindowMap.get(hostInputToken), callingUid, callingPid,
                             sanitizedType, displayId, focusGrantToken, inputHandleName,
                             (flags & FLAG_NOT_FOCUSABLE) == 0);
-            clientChannel = win.openInputChannel();
-            mEmbeddedWindowController.add(clientChannel.getToken(), win);
+            win.openInputChannel(outInputChannel);
+            mEmbeddedWindowController.add(outInputChannel.getToken(), win);
             applicationHandle = win.getApplicationHandle();
             name = win.toString();
         }
 
-        updateInputChannel(clientChannel.getToken(), callingUid, callingPid, displayId, surface,
+        updateInputChannel(outInputChannel.getToken(), callingUid, callingPid, displayId, surface,
                 name, applicationHandle, flags, privateFlags, inputFeatures, sanitizedType,
                 null /* region */, window);
-
-        clientChannel.copyTo(outInputChannel);
     }
 
     boolean transferEmbeddedTouchFocusToHost(IWindow embeddedWindow) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index a153708..ceebb27 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -41,6 +41,7 @@
 import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.Pair;
+import android.util.TypedValue;
 import android.view.Display;
 import android.view.IWindow;
 import android.view.IWindowManager;
@@ -728,7 +729,7 @@
             return -1;
         }
         synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setLetterboxBackgroundType(backgroundType);
+            mLetterboxConfiguration.setLetterboxBackgroundTypeOverride(backgroundType);
         }
         return 0;
     }
@@ -770,10 +771,10 @@
 
     private int runSetLetterboxBackgroundWallpaperBlurRadius(PrintWriter pw)
             throws RemoteException {
-        final int radius;
+        final int radiusDp;
         try {
             String arg = getNextArgRequired();
-            radius = Integer.parseInt(arg);
+            radiusDp = Integer.parseInt(arg);
         } catch (NumberFormatException  e) {
             getErrPrintWriter().println("Error: blur radius format " + e);
             return -1;
@@ -783,7 +784,9 @@
             return -1;
         }
         synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setLetterboxBackgroundWallpaperBlurRadius(radius);
+            final int radiusPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    radiusDp, mInternal.mContext.getResources().getDisplayMetrics());
+            mLetterboxConfiguration.setLetterboxBackgroundWallpaperBlurRadiusPx(radiusPx);
         }
         return 0;
     }
@@ -1006,13 +1009,20 @@
                     runSetBooleanFlag(pw, mLetterboxConfiguration
                             ::setTranslucentLetterboxingOverrideEnabled);
                     break;
+                case "--isUserAppAspectRatioSettingsEnabled":
+                    runSetBooleanFlag(pw, mLetterboxConfiguration
+                            ::setUserAppAspectRatioSettingsOverrideEnabled);
+                    break;
+                case "--isUserAppAspectRatioFullscreenEnabled":
+                    runSetBooleanFlag(pw, mLetterboxConfiguration
+                            ::setUserAppAspectRatioFullscreenOverrideEnabled);
+                    break;
                 case "--isCameraCompatRefreshEnabled":
-                    runSetBooleanFlag(pw, enabled -> mLetterboxConfiguration
-                            .setCameraCompatRefreshEnabled(enabled));
+                    runSetBooleanFlag(pw, mLetterboxConfiguration::setCameraCompatRefreshEnabled);
                     break;
                 case "--isCameraCompatRefreshCycleThroughStopEnabled":
-                    runSetBooleanFlag(pw, enabled -> mLetterboxConfiguration
-                            .setCameraCompatRefreshCycleThroughStopEnabled(enabled));
+                    runSetBooleanFlag(pw,
+                            mLetterboxConfiguration::setCameraCompatRefreshCycleThroughStopEnabled);
                     break;
                 default:
                     getErrPrintWriter().println(
@@ -1047,7 +1057,7 @@
                         mLetterboxConfiguration.resetLetterboxBackgroundColor();
                         break;
                     case "wallpaperBlurRadius":
-                        mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius();
+                        mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadiusPx();
                         break;
                     case "wallpaperDarkScrimAlpha":
                         mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
@@ -1084,6 +1094,12 @@
                     case "isTranslucentLetterboxingEnabled":
                         mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
                         break;
+                    case "isUserAppAspectRatioSettingsEnabled":
+                        mLetterboxConfiguration.resetUserAppAspectRatioSettingsEnabled();
+                        break;
+                    case "isUserAppAspectRatioFullscreenEnabled":
+                        mLetterboxConfiguration.resetUserAppAspectRatioFullscreenEnabled();
+                        break;
                     case "isCameraCompatRefreshEnabled":
                         mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
                         break;
@@ -1182,7 +1198,7 @@
             mLetterboxConfiguration.resetLetterboxActivityCornersRadius();
             mLetterboxConfiguration.resetLetterboxBackgroundType();
             mLetterboxConfiguration.resetLetterboxBackgroundColor();
-            mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius();
+            mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadiusPx();
             mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
             mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
             mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled();
@@ -1194,6 +1210,8 @@
             mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
             mLetterboxConfiguration.resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
             mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
+            mLetterboxConfiguration.resetUserAppAspectRatioSettingsEnabled();
+            mLetterboxConfiguration.resetUserAppAspectRatioFullscreenEnabled();
             mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
             mLetterboxConfiguration.resetCameraCompatRefreshCycleThroughStopEnabled();
         }
@@ -1249,22 +1267,21 @@
                     + mLetterboxConfiguration.isCameraCompatRefreshEnabled());
             pw.println("    Refresh using \"stopped -> resumed\" cycle: "
                     + mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled());
-
             pw.println("Background type: "
                     + LetterboxConfiguration.letterboxBackgroundTypeToString(
                             mLetterboxConfiguration.getLetterboxBackgroundType()));
             pw.println("    Background color: " + Integer.toHexString(
                     mLetterboxConfiguration.getLetterboxBackgroundColor().toArgb()));
             pw.println("    Wallpaper blur radius: "
-                    + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius());
+                    + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadiusPx());
             pw.println("    Wallpaper dark scrim alpha: "
                     + mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha());
-
-            if (mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
-                pw.println("Letterboxing for translucent activities: enabled");
-            } else {
-                pw.println("Letterboxing for translucent activities: disabled");
-            }
+            pw.println("Is letterboxing for translucent activities enabled: "
+                    + mLetterboxConfiguration.isTranslucentLetterboxingEnabled());
+            pw.println("Is the user aspect ratio settings enabled: "
+                    + mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled());
+            pw.println("Is the fullscreen option in user aspect ratio settings enabled: "
+                    + mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled());
         }
         return 0;
     }
@@ -1462,6 +1479,10 @@
         pw.println("        unresizable apps.");
         pw.println("      --isTranslucentLetterboxingEnabled [true|1|false|0]");
         pw.println("        Whether letterboxing for translucent activities is enabled.");
+        pw.println("      --isUserAppAspectRatioSettingsEnabled [true|1|false|0]");
+        pw.println("        Whether user aspect ratio settings are enabled.");
+        pw.println("      --isUserAppAspectRatioFullscreenEnabled [true|1|false|0]");
+        pw.println("        Whether user aspect ratio fullscreen option is enabled.");
         pw.println("      --isCameraCompatRefreshEnabled [true|1|false|0]");
         pw.println("        Whether camera compatibility refresh is enabled.");
         pw.println("      --isCameraCompatRefreshCycleThroughStopEnabled [true|1|false|0]");
@@ -1473,7 +1494,7 @@
         pw.println("      |horizontalPositionMultiplier|verticalPositionMultiplier");
         pw.println("      |isHorizontalReachabilityEnabled|isVerticalReachabilityEnabled");
         pw.println("      |isEducationEnabled||defaultPositionMultiplierForHorizontalReachability");
-        pw.println("      |isTranslucentLetterboxingEnabled");
+        pw.println("      |isTranslucentLetterboxingEnabled|isUserAppAspectRatioSettingsEnabled");
         pw.println("      |defaultPositionMultiplierForVerticalReachability]");
         pw.println("    Resets overrides to default values for specified properties separated");
         pw.println("    by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 027ab97..9375b29 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -24,11 +24,13 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
 import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS;
 import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN;
@@ -307,6 +309,14 @@
                                 applyTransaction(wct, -1 /* syncId */, nextTransition, caller,
                                         deferred);
                                 if (needsSetReady) {
+                                    // TODO(b/294925498): Remove this once we have accurate ready
+                                    //                    tracking.
+                                    if (hasActivityLaunch(wct) && !mService.mRootWindowContainer
+                                            .allPausedActivitiesComplete()) {
+                                        // WCT is launching an activity, so we need to wait for its
+                                        // lifecycle events.
+                                        return;
+                                    }
                                     nextTransition.setAllReady();
                                 }
                             });
@@ -321,9 +331,18 @@
                     applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller);
                     return transition.getToken();
                 }
-                transition.start();
                 transition.mLogger.mStartWCT = wct;
-                applyTransaction(wct, -1 /*syncId*/, transition, caller);
+                if (transition.shouldApplyOnDisplayThread()) {
+                    mService.mH.post(() -> {
+                        synchronized (mService.mGlobalLock) {
+                            transition.start();
+                            applyTransaction(wct, -1 /* syncId */, transition, caller);
+                        }
+                    });
+                } else {
+                    transition.start();
+                    applyTransaction(wct, -1 /* syncId */, transition, caller);
+                }
                 // Since the transition is already provided, it means WMCore is determining the
                 // "readiness lifecycle" outside the provided transaction, so don't set ready here.
                 return transition.getToken();
@@ -333,6 +352,15 @@
         }
     }
 
+    private static boolean hasActivityLaunch(WindowContainerTransaction wct) {
+        for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
+            if (wct.getHierarchyOps().get(i).getType() == HIERARCHY_OP_TYPE_LAUNCH_TASK) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter,
             @NonNull IWindowContainerTransactionCallback callback,
@@ -371,18 +399,13 @@
     }
 
     @Override
-    public int finishTransition(@NonNull IBinder transitionToken,
-            @Nullable WindowContainerTransaction t,
-            @Nullable IWindowContainerTransactionCallback callback) {
+    public void finishTransition(@NonNull IBinder transitionToken,
+            @Nullable WindowContainerTransaction t) {
         enforceTaskPermission("finishTransition()");
         final CallerInfo caller = new CallerInfo();
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                int syncId = -1;
-                if (t != null && callback != null) {
-                    syncId = startSyncWithOrganizer(callback);
-                }
                 final Transition transition = Transition.fromBinder(transitionToken);
                 // apply the incoming transaction before finish in case it alters the visibility
                 // of the participants.
@@ -391,14 +414,10 @@
                     // changes of the transition participants will only set visible-requested
                     // and still let finishTransition handle the participants.
                     mTransitionController.mFinishingTransition = transition;
-                    applyTransaction(t, syncId, null /*transition*/, caller, transition);
+                    applyTransaction(t, -1 /* syncId */, null /*transition*/, caller, transition);
                 }
                 mTransitionController.finishTransition(transition);
                 mTransitionController.mFinishingTransition = null;
-                if (syncId >= 0) {
-                    setSyncReady(syncId);
-                }
-                return syncId;
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -1088,7 +1107,8 @@
                             + container);
                     break;
                 }
-                container.addLocalInsetsFrameProvider(hop.getInsetsFrameProvider());
+                container.addLocalInsetsFrameProvider(
+                        hop.getInsetsFrameProvider(), hop.getInsetsFrameOwner());
                 break;
             }
             case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER: {
@@ -1098,7 +1118,8 @@
                                     + container);
                     break;
                 }
-                container.removeLocalInsetsFrameProvider(hop.getInsetsFrameProvider());
+                container.removeLocalInsetsFrameProvider(
+                        hop.getInsetsFrameProvider(), hop.getInsetsFrameOwner());
                 break;
             }
             case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: {
@@ -1331,6 +1352,24 @@
                 taskFragment.setAnimationParams(animationParams);
                 break;
             }
+            case OP_TYPE_REORDER_TO_FRONT: {
+                final Task task = taskFragment.getTask();
+                if (task != null) {
+                    final TaskFragment topTaskFragment = task.getTaskFragment(
+                            tf -> tf.asTask() == null);
+                    if (topTaskFragment != null && topTaskFragment != taskFragment) {
+                        final int index = task.mChildren.indexOf(topTaskFragment);
+                        task.mChildren.remove(taskFragment);
+                        task.mChildren.add(index, taskFragment);
+                    }
+                }
+                break;
+            }
+            case OP_TYPE_SET_ISOLATED_NAVIGATION: {
+                final boolean isolatedNav = operation.isIsolatedNav();
+                taskFragment.setIsolatedNav(isolatedNav);
+                break;
+            }
         }
         return effects;
     }
@@ -1590,6 +1629,7 @@
         final int count = tasksToReparent.size();
         for (int i = 0; i < count; ++i) {
             final Task task = tasksToReparent.get(i);
+            final int prevWindowingMode = task.getWindowingMode();
             if (syncId >= 0) {
                 addToSyncSet(syncId, task);
             }
@@ -1603,6 +1643,12 @@
                         hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
                         false /*moveParents*/, "processChildrenTaskReparentHierarchyOp");
             }
+            // Trim the compatible Recent task (if any) after the Task is reparented and now has
+            // a different windowing mode, in order to prevent redundant Recent tasks after
+            // reparenting.
+            if (prevWindowingMode != task.getWindowingMode()) {
+                mService.mTaskSupervisor.mRecentTasks.removeCompatibleRecentTask(task);
+            }
         }
 
         if (transition != null) transition.collect(newParent);
@@ -1611,9 +1657,18 @@
     }
 
     private int setAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
-        final TaskFragment root1 = WindowContainer.fromBinder(hop.getContainer()).asTaskFragment();
-        final TaskFragment root2 =
-                WindowContainer.fromBinder(hop.getAdjacentRoot()).asTaskFragment();
+        final WindowContainer wc1 = WindowContainer.fromBinder(hop.getContainer());
+        if (wc1 == null || !wc1.isAttached()) {
+            Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc1);
+            return TRANSACT_EFFECTS_NONE;
+        }
+        final TaskFragment root1 = wc1.asTaskFragment();
+        final WindowContainer wc2 = WindowContainer.fromBinder(hop.getAdjacentRoot());
+        if (wc2 == null || !wc2.isAttached()) {
+            Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc2);
+            return TRANSACT_EFFECTS_NONE;
+        }
+        final TaskFragment root2 = wc2.asTaskFragment();
         if (!root1.mCreatedByOrganizer || !root2.mCreatedByOrganizer) {
             throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
                     + " organizer root1=" + root1 + " root2=" + root2);
@@ -1626,7 +1681,12 @@
     }
 
     private int clearAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
-        final TaskFragment root = WindowContainer.fromBinder(hop.getContainer()).asTaskFragment();
+        final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+        if (wc == null || !wc.isAttached()) {
+            Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc);
+            return TRANSACT_EFFECTS_NONE;
+        }
+        final TaskFragment root = wc.asTaskFragment();
         if (!root.mCreatedByOrganizer) {
             throw new IllegalArgumentException("clearAdjacentRootsHierarchyOp: Not created by"
                     + " organizer root=" + root);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index d7d2b4e..caec45c5 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -48,6 +48,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -75,7 +76,6 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
-import android.view.IRemoteAnimationRunner;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -87,6 +87,8 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -249,11 +251,30 @@
     @Nullable
     private ArrayMap<ActivityRecord, int[]> mRemoteActivities;
 
-    /** Whether our process is currently running a {@link RecentsAnimation} */
-    private boolean mRunningRecentsAnimation;
+    /**
+     * It can be set for a running transition player ({@link android.window.ITransitionPlayer}) or
+     * remote animators (running {@link android.window.IRemoteTransition}).
+     */
+    static final int ANIMATING_REASON_REMOTE_ANIMATION = 1;
+    /** It is set for wakefulness transition. */
+    static final int ANIMATING_REASON_WAKEFULNESS_CHANGE = 1 << 1;
+    /** Whether the legacy {@link RecentsAnimation} is running. */
+    static final int ANIMATING_REASON_LEGACY_RECENT_ANIMATION = 1 << 2;
 
-    /** Whether our process is currently running a {@link IRemoteAnimationRunner} */
-    private boolean mRunningRemoteAnimation;
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            ANIMATING_REASON_REMOTE_ANIMATION,
+            ANIMATING_REASON_WAKEFULNESS_CHANGE,
+            ANIMATING_REASON_LEGACY_RECENT_ANIMATION,
+    })
+    @interface AnimatingReason {}
+
+    /**
+     * Non-zero if this process is currently running an important animation. This should be never
+     * set for system server.
+     */
+    @AnimatingReason
+    private int mAnimatingReasons;
 
     // The bits used for mActivityStateFlags.
     private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16;
@@ -1847,30 +1868,45 @@
     }
 
     void setRunningRecentsAnimation(boolean running) {
-        if (mRunningRecentsAnimation == running) {
-            return;
+        if (running) {
+            addAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
+        } else {
+            removeAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
         }
-        mRunningRecentsAnimation = running;
-        updateRunningRemoteOrRecentsAnimation();
     }
 
     void setRunningRemoteAnimation(boolean running) {
-        if (mRunningRemoteAnimation == running) {
-            return;
+        if (running) {
+            addAnimatingReason(ANIMATING_REASON_REMOTE_ANIMATION);
+        } else {
+            removeAnimatingReason(ANIMATING_REASON_REMOTE_ANIMATION);
         }
-        mRunningRemoteAnimation = running;
-        updateRunningRemoteOrRecentsAnimation();
     }
 
-    void updateRunningRemoteOrRecentsAnimation() {
+    void addAnimatingReason(@AnimatingReason int reason) {
+        final int prevReasons = mAnimatingReasons;
+        mAnimatingReasons |= reason;
+        if (prevReasons == 0) {
+            setAnimating(true);
+        }
+    }
+
+    void removeAnimatingReason(@AnimatingReason int reason) {
+        final int prevReasons = mAnimatingReasons;
+        mAnimatingReasons &= ~reason;
+        if (prevReasons != 0 && mAnimatingReasons == 0) {
+            setAnimating(false);
+        }
+    }
+
+    /** Applies the animating state to activity manager for updating process priority. */
+    private void setAnimating(boolean animating) {
         // Posting on handler so WM lock isn't held when we call into AM.
-        mAtm.mH.sendMessage(PooledLambda.obtainMessage(
-                WindowProcessListener::setRunningRemoteAnimation, mListener,
-                isRunningRemoteTransition()));
+        mAtm.mH.post(() -> mListener.setRunningRemoteAnimation(animating));
     }
 
     boolean isRunningRemoteTransition() {
-        return mRunningRecentsAnimation || mRunningRemoteAnimation;
+        return (mAnimatingReasons & ANIMATING_REASON_REMOTE_ANIMATION) != 0;
     }
 
     /** Adjusts scheduling group for animation. This method MUST NOT be called inside WM lock. */
@@ -1924,6 +1960,21 @@
         pw.println(prefix + " mLastReportedConfiguration=" + (mHasCachedConfiguration
                 ? ("(cached) " + mLastReportedConfiguration) : mLastReportedConfiguration));
 
+        final int animatingReasons = mAnimatingReasons;
+        if (animatingReasons != 0) {
+            pw.print(prefix + " mAnimatingReasons=");
+            if ((animatingReasons & ANIMATING_REASON_REMOTE_ANIMATION) != 0) {
+                pw.print("remote-animation|");
+            }
+            if ((animatingReasons & ANIMATING_REASON_WAKEFULNESS_CHANGE) != 0) {
+                pw.print("wakefulness|");
+            }
+            if ((animatingReasons & ANIMATING_REASON_LEGACY_RECENT_ANIMATION) != 0) {
+                pw.print("legacy-recents");
+            }
+            pw.println();
+        }
+
         final int stateFlags = mActivityStateFlags;
         if (stateFlags != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) {
             pw.print(prefix + " mActivityStateFlags=");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 06978a5..c09e6a3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1445,6 +1445,8 @@
             Slog.v(TAG_WM, "Win " + this + " config changed: " + getConfiguration());
         }
 
+        final boolean dragResizingChanged = !mDragResizingChangeReported && isDragResizeChanged();
+
         final boolean attachedFrameChanged = LOCAL_LAYOUT
                 && mLayoutAttached && getParentWindow().frameChanged();
 
@@ -1458,6 +1460,7 @@
         if (didFrameInsetsChange
                 || configChanged
                 || insetsChanged
+                || dragResizingChanged
                 || shouldSendRedrawForSync()
                 || attachedFrameChanged) {
             ProtoLog.v(WM_DEBUG_RESIZE,
@@ -1478,7 +1481,8 @@
 
             // Reset the drawn state if the window need to redraw for the change, so the transition
             // can wait until it has finished drawing to start.
-            if ((configChanged || getOrientationChanging()) && isVisibleRequested()) {
+            if ((configChanged || getOrientationChanging() || dragResizingChanged)
+                    && isVisibleRequested()) {
                 winAnimator.mDrawState = DRAW_PENDING;
                 if (mActivityRecord != null) {
                     mActivityRecord.clearAllDrawn();
@@ -1931,7 +1935,9 @@
         }
         final ActivityRecord atoken = mActivityRecord;
         if (atoken != null) {
-            return ((!isParentWindowHidden() && atoken.isVisible())
+            final boolean isVisible = isStartingWindowAssociatedToTask()
+                    ? mStartingData.mAssociatedTask.isVisible() : atoken.isVisible();
+            return ((!isParentWindowHidden() && isVisible)
                     || isAnimationRunningSelfOrParent());
         }
         final WallpaperWindowToken wtoken = mToken.asWallpaperToken();
@@ -2326,6 +2332,15 @@
             // IME surface association. (e.g. Attach IME surface on the display instead of the
             // app when the app bounds being letterboxed.)
             mDisplayContent.updateImeControlTarget(isImeLayeringTarget() /* updateImeParent */);
+            // Fix the starting window to task when Activity has changed.
+            if (mStartingData != null && mStartingData.mAssociatedTask == null
+                    && mTempConfiguration.windowConfiguration.getRotation()
+                            == selfConfiguration.windowConfiguration.getRotation()
+                    && !mTempConfiguration.windowConfiguration.getBounds().equals(getBounds())) {
+                mStartingData.mResizedFromTransfer = true;
+                // Lock the starting window to task, so it won't resize from transfer anymore.
+                mActivityRecord.associateStartingWindowWithTaskIfNeeded();
+            }
         }
     }
 
@@ -2397,7 +2412,7 @@
         ProtoLog.v(WM_DEBUG_ADD_REMOVE,
                 "removeIfPossible: %s callers=%s", this, Debug.getCallers(5));
 
-        final boolean startingWindow = mAttrs.type == TYPE_APPLICATION_STARTING;
+        final boolean startingWindow = mStartingData != null;
         if (startingWindow) {
             ProtoLog.d(WM_DEBUG_STARTING_WINDOW, "Starting window removed %s", this);
             // Cancel the remove starting window animation on shell. The main window might changed
@@ -2411,6 +2426,7 @@
                     return false;
                 }, true);
             }
+            mTransitionController.mTransitionTracer.logRemovingStartingWindow(mStartingData);
         } else if (mAttrs.type == TYPE_BASE_APPLICATION
                 && isSelfAnimating(0, ANIMATION_TYPE_STARTING_REVEAL)) {
             // Cancel the remove starting window animation in case the binder dead before remove
@@ -3898,7 +3914,7 @@
      * LetterboxUiController#shouldShowLetterboxUi} for more context.
      */
     boolean areAppWindowBoundsLetterboxed() {
-        return mActivityRecord != null
+        return mActivityRecord != null && !isStartingWindowAssociatedToTask()
                 && (mActivityRecord.areBoundsLetterboxed() || isLetterboxedForDisplayCutout());
     }
 
@@ -5662,6 +5678,12 @@
         }
         if (mIsWallpaper) {
             // TODO(b/233286785): Add sync support to wallpaper.
+            return true;
+        }
+        if (mActivityRecord != null && mViewVisibility != View.VISIBLE
+                && mWinAnimator.mAttrType != TYPE_BASE_APPLICATION
+                && mWinAnimator.mAttrType != TYPE_APPLICATION_STARTING) {
+            // Skip sync for invisible app windows which are not managed by activity lifecycle.
             return false;
         }
         // In the WindowContainer implementation we immediately mark ready
@@ -5699,8 +5721,7 @@
             // window becomes visible while the sync group is still active.
             return true;
         }
-        if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mWinAnimator.mDrawState == HAS_DRAWN
-                && !mRedrawForSyncReported && !mWmService.mResizingWindows.contains(this)) {
+        if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mLastConfigReportedToClient && isDrawn()) {
             // Complete the sync state immediately for a drawn window that doesn't need to redraw.
             onSyncFinishedDrawing();
         }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index f0d718a..9d39165 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -108,6 +108,7 @@
     jmethodID notifySensorEvent;
     jmethodID notifySensorAccuracy;
     jmethodID notifyStylusGestureStarted;
+    jmethodID isInputMethodConnectionActive;
     jmethodID notifyVibratorState;
     jmethodID filterInputEvent;
     jmethodID interceptKeyBeforeQueueing;
@@ -279,7 +280,7 @@
     base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name);
     base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
                                                                    const std::string& name,
-                                                                   int32_t pid);
+                                                                   gui::Pid pid);
     status_t removeInputChannel(const sp<IBinder>& connectionToken);
     status_t pilferPointers(const sp<IBinder>& token);
 
@@ -322,6 +323,7 @@
 
     TouchAffineTransformation getTouchAffineTransformation(JNIEnv* env, jfloatArray matrixArr);
     void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override;
+    bool isInputMethodConnectionActive() override;
 
     /* --- InputDispatcherPolicyInterface implementation --- */
 
@@ -330,9 +332,9 @@
     void notifyConfigurationChanged(nsecs_t when) override;
     // ANR-related callbacks -- start
     void notifyNoFocusedWindowAnr(const std::shared_ptr<InputApplicationHandle>& handle) override;
-    void notifyWindowUnresponsive(const sp<IBinder>& token, std::optional<int32_t> pid,
+    void notifyWindowUnresponsive(const sp<IBinder>& token, std::optional<gui::Pid> pid,
                                   const std::string& reason) override;
-    void notifyWindowResponsive(const sp<IBinder>& token, std::optional<int32_t> pid) override;
+    void notifyWindowResponsive(const sp<IBinder>& token, std::optional<gui::Pid> pid) override;
     // ANR-related callbacks -- end
     void notifyInputChannelBroken(const sp<IBinder>& token) override;
     void notifyFocusChanged(const sp<IBinder>& oldToken, const sp<IBinder>& newToken) override;
@@ -355,6 +357,8 @@
     void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
     void setPointerCapture(const PointerCaptureRequest& request) override;
     void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
+    void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+                                 const std::set<gui::Uid>& uids) override;
 
     /* --- PointerControllerPolicyInterface implementation --- */
 
@@ -539,7 +543,7 @@
 }
 
 base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor(
-        int32_t displayId, const std::string& name, int32_t pid) {
+        int32_t displayId, const std::string& name, gui::Pid pid) {
     ATRACE_CALL();
     return mInputManager->getDispatcher().createInputMonitor(displayId, name, pid);
 }
@@ -883,7 +887,7 @@
 }
 
 void NativeInputManager::notifyWindowUnresponsive(const sp<IBinder>& token,
-                                                  std::optional<int32_t> pid,
+                                                  std::optional<gui::Pid> pid,
                                                   const std::string& reason) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
     ALOGD("notifyWindowUnresponsive");
@@ -897,12 +901,12 @@
     ScopedLocalRef<jstring> reasonObj(env, env->NewStringUTF(reason.c_str()));
 
     env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowUnresponsive, tokenObj,
-                        pid.value_or(0), pid.has_value(), reasonObj.get());
+                        pid.value_or(gui::Pid{0}).val(), pid.has_value(), reasonObj.get());
     checkAndClearExceptionFromCallback(env, "notifyWindowUnresponsive");
 }
 
 void NativeInputManager::notifyWindowResponsive(const sp<IBinder>& token,
-                                                std::optional<int32_t> pid) {
+                                                std::optional<gui::Pid> pid) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
     ALOGD("notifyWindowResponsive");
 #endif
@@ -914,7 +918,7 @@
     jobject tokenObj = javaObjectForIBinder(env, token);
 
     env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyWindowResponsive, tokenObj,
-                        pid.value_or(0), pid.has_value());
+                        pid.value_or(gui::Pid{0}).val(), pid.has_value());
     checkAndClearExceptionFromCallback(env, "notifyWindowResponsive");
 }
 
@@ -966,6 +970,15 @@
     checkAndClearExceptionFromCallback(env, "notifyDropWindow");
 }
 
+void NativeInputManager::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+                                                 const std::set<gui::Uid>& uids) {
+    static const bool ENABLE_INPUT_DEVICE_USAGE_METRICS =
+            sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true);
+    if (!ENABLE_INPUT_DEVICE_USAGE_METRICS) return;
+
+    mInputManager->getMetricsCollector().notifyDeviceInteraction(deviceId, timestamp, uids);
+}
+
 void NativeInputManager::notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
                                            InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
                                            const std::vector<float>& values) {
@@ -1295,6 +1308,14 @@
     checkAndClearExceptionFromCallback(env, "notifyStylusGestureStarted");
 }
 
+bool NativeInputManager::isInputMethodConnectionActive() {
+    JNIEnv* env = jniEnv();
+    const jboolean result =
+            env->CallBooleanMethod(mServiceObj, gServiceClassInfo.isInputMethodConnectionActive);
+    checkAndClearExceptionFromCallback(env, "isInputMethodConnectionActive");
+    return result;
+}
+
 bool NativeInputManager::filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) {
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
@@ -1596,8 +1617,8 @@
 }
 
 PointerIconStyle NativeInputManager::getDefaultStylusIconId() {
-    // TODO: add resource for default stylus icon and change this
-    return PointerIconStyle::TYPE_CROSSHAIR;
+    // Use the empty icon as the default pointer icon for a hovering stylus.
+    return PointerIconStyle::TYPE_NULL;
 }
 
 PointerIconStyle NativeInputManager::getCustomPointerIconId() {
@@ -1808,7 +1829,7 @@
     std::string name = nameChars.c_str();
 
     base::Result<std::unique_ptr<InputChannel>> inputChannel =
-            im->createInputMonitor(displayId, name, pid);
+            im->createInputMonitor(displayId, name, gui::Pid{pid});
 
     if (!inputChannel.ok()) {
         std::string message = inputChannel.error().message();
@@ -1853,7 +1874,8 @@
                                      jint pid, jint uid, jboolean hasPermission, jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    return im->getInputManager()->getDispatcher().setInTouchMode(inTouchMode, pid, uid,
+    return im->getInputManager()->getDispatcher().setInTouchMode(inTouchMode, gui::Pid{pid},
+                                                                 gui::Uid{static_cast<uid_t>(uid)},
                                                                  hasPermission, displayId);
 }
 
@@ -1869,7 +1891,7 @@
                                    jint timeoutMillis, jint policyFlags) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    const std::optional<int32_t> targetUid = injectIntoUid ? std::make_optional(uid) : std::nullopt;
+    const auto targetUid = injectIntoUid ? std::make_optional<gui::Uid>(uid) : std::nullopt;
     // static_cast is safe because the value was already checked at the Java layer
     InputEventInjectionSync mode = static_cast<InputEventInjectionSync>(syncMode);
 
@@ -2214,13 +2236,25 @@
             jCapability |= env->GetStaticIntField(gLightClassInfo.clazz,
                                                   gLightClassInfo.lightCapabilityColorRgb);
         }
+
+        ScopedLocalRef<jintArray> jPreferredBrightnessLevels{env};
+        if (!lightInfo.preferredBrightnessLevels.empty()) {
+            std::vector<int32_t> vec;
+            for (auto it : lightInfo.preferredBrightnessLevels) {
+              vec.push_back(ftl::to_underlying(it));
+            }
+            jPreferredBrightnessLevels.reset(env->NewIntArray(vec.size()));
+            env->SetIntArrayRegion(jPreferredBrightnessLevels.get(), 0, vec.size(), vec.data());
+        }
+
         ScopedLocalRef<jobject> lightObj(env,
                                          env->NewObject(gLightClassInfo.clazz,
                                                         gLightClassInfo.constructor,
                                                         static_cast<jint>(lightInfo.id),
                                                         env->NewStringUTF(lightInfo.name.c_str()),
                                                         static_cast<jint>(lightInfo.ordinal),
-                                                        jTypeId, jCapability));
+                                                        jTypeId, jCapability,
+                                                        jPreferredBrightnessLevels.get()));
         // Add light object to list
         env->CallBooleanMethod(jLights, gArrayListClassInfo.add, lightObj.get());
     }
@@ -2719,6 +2753,9 @@
     GET_METHOD_ID(gServiceClassInfo.notifyStylusGestureStarted, clazz, "notifyStylusGestureStarted",
                   "(IJ)V");
 
+    GET_METHOD_ID(gServiceClassInfo.isInputMethodConnectionActive, clazz,
+                  "isInputMethodConnectionActive", "()Z");
+
     GET_METHOD_ID(gServiceClassInfo.notifyVibratorState, clazz, "notifyVibratorState", "(IZ)V");
 
     GET_METHOD_ID(gServiceClassInfo.notifyNoFocusedWindowAnr, clazz, "notifyNoFocusedWindowAnr",
@@ -2846,7 +2883,7 @@
     FIND_CLASS(gLightClassInfo.clazz, "android/hardware/lights/Light");
     gLightClassInfo.clazz = jclass(env->NewGlobalRef(gLightClassInfo.clazz));
     GET_METHOD_ID(gLightClassInfo.constructor, gLightClassInfo.clazz, "<init>",
-                  "(ILjava/lang/String;III)V");
+                  "(ILjava/lang/String;III[I)V");
 
     gLightClassInfo.clazz = jclass(env->NewGlobalRef(gLightClassInfo.clazz));
     gLightClassInfo.lightTypeInput =
diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
index ce022e9..57b5d00 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -53,6 +53,7 @@
             <xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" />
             <xs:element name="brightnessThrottlingMapId" type="xs:string" minOccurs="0" maxOccurs="1" />
             <xs:element name="refreshRateThermalThrottlingMapId" type="xs:string" minOccurs="0" />
+            <xs:element name="leadDisplayAddress" type="xs:nonNegativeInteger" minOccurs="0" maxOccurs="1" />
         </xs:sequence>
         <xs:attribute name="enabled" type="xs:boolean" use="optional" />
         <xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" />
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index 42a800d..2d4f7a4 100644
--- a/services/core/xsd/display-layout-config/schema/current.txt
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -6,6 +6,7 @@
     method public java.math.BigInteger getAddress();
     method public String getBrightnessThrottlingMapId();
     method public String getDisplayGroup();
+    method public java.math.BigInteger getLeadDisplayAddress();
     method public String getPosition();
     method public String getRefreshRateThermalThrottlingMapId();
     method public String getRefreshRateZoneId();
@@ -16,6 +17,7 @@
     method public void setDefaultDisplay(boolean);
     method public void setDisplayGroup(String);
     method public void setEnabled(boolean);
+    method public void setLeadDisplayAddress(java.math.BigInteger);
     method public void setPosition(String);
     method public void setRefreshRateThermalThrottlingMapId(String);
     method public void setRefreshRateZoneId(String);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 51e4fe3..f3c2de6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -13599,6 +13599,9 @@
                     : SecurityLog.TAG_USER_RESTRICTION_REMOVED;
             SecurityLog.writeEvent(eventTag, caller.getPackageName(), caller.getUserId(), key);
         }
+
+        Slogf.i(LOG_TAG, "Changing user restriction %s to: %b caller: %s",
+                key, enabled, caller.toString());
     }
 
     private void saveUserRestrictionsLocked(int userId) {
@@ -14843,17 +14846,23 @@
                 "Caller is not managed profile owner or device owner;"
                         + " only managed profile owner or device owner may control the preferential"
                         + " network service");
-        synchronized (getLockObject()) {
-            final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
-                    caller.getUserId());
-            if (!requiredAdmin.mPreferentialNetworkServiceConfigs.equals(
-                    preferentialNetworkServiceConfigs)) {
-                requiredAdmin.mPreferentialNetworkServiceConfigs =
-                        new ArrayList<>(preferentialNetworkServiceConfigs);
-                saveSettingsLocked(caller.getUserId());
+
+        try {
+            updateNetworkPreferenceForUser(caller.getUserId(), preferentialNetworkServiceConfigs);
+            synchronized (getLockObject()) {
+                final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
+                        caller.getUserId());
+                if (!requiredAdmin.mPreferentialNetworkServiceConfigs.equals(
+                        preferentialNetworkServiceConfigs)) {
+                    requiredAdmin.mPreferentialNetworkServiceConfigs =
+                            new ArrayList<>(preferentialNetworkServiceConfigs);
+                    saveSettingsLocked(caller.getUserId());
+                }
             }
+        } catch (Exception e) {
+            Slogf.e(LOG_TAG, "Failed to set preferential network service configs");
+            throw e;
         }
-        updateNetworkPreferenceForUser(caller.getUserId(), preferentialNetworkServiceConfigs);
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_PREFERENTIAL_NETWORK_SERVICE_ENABLED)
                 .setBoolean(preferentialNetworkServiceConfigs
@@ -16221,6 +16230,11 @@
         }
 
         @Override
+        public ComponentName getDeviceOwnerComponent(boolean callingUserOnly) {
+            return DevicePolicyManagerService.this.getDeviceOwnerComponent(callingUserOnly);
+        }
+
+        @Override
         public int getDeviceOwnerUserId() {
             return DevicePolicyManagerService.this.getDeviceOwnerUserId();
         }
diff --git a/services/flags/Android.bp b/services/flags/Android.bp
new file mode 100644
index 0000000..2d0337d
--- /dev/null
+++ b/services/flags/Android.bp
@@ -0,0 +1,17 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library_static {
+    name: "services.flags",
+    defaults: ["platform_service_defaults"],
+    srcs: [
+        "java/**/*.java",
+    ],
+    libs: ["services.core"],
+}
diff --git a/services/flags/OWNERS b/services/flags/OWNERS
new file mode 100644
index 0000000..3925b5c
--- /dev/null
+++ b/services/flags/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 1306523
+
+mankoff@google.com
+
+pixel@google.com
+dsandler@android.com
diff --git a/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java b/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java
new file mode 100644
index 0000000..0db3287
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import android.annotation.NonNull;
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.os.BackgroundThread;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * Handles DynamicFlags for {@link FeatureFlagsBinder}.
+ *
+ * Dynamic flags are simultaneously simpler and more complicated than process stable flags. We can
+ * return whatever value is last known for a flag is, without too much worry about the flags
+ * changing (they are dynamic after all). However, we have to alert all the relevant clients
+ * about those flag changes, and need to be able to restore to a default value if the flag gets
+ * reset/erased during runtime.
+ */
+class DynamicFlagBinderDelegate {
+
+    private final FlagOverrideStore mFlagStore;
+    private final FlagCache<DynamicFlagData> mDynamicFlags = new FlagCache<>();
+    private final Map<Integer, Set<IFeatureFlagsCallback>> mCallbacks = new HashMap<>();
+    private static final Function<Integer, Set<IFeatureFlagsCallback>> NEW_CALLBACK_SET =
+            k -> new HashSet<>();
+
+    private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener =
+            new DeviceConfig.OnPropertiesChangedListener() {
+                @Override
+                public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+                    String ns = properties.getNamespace();
+                    for (String name : properties.getKeyset()) {
+                        // Don't alert for flags we don't care about.
+                        // Don't alert for flags that have been overridden locally.
+                        if (!mDynamicFlags.contains(ns, name) || mFlagStore.contains(ns, name)) {
+                            continue;
+                        }
+                        mFlagChangeCallback.onFlagChanged(
+                                ns, name, properties.getString(name, null));
+                    }
+                }
+            };
+
+    private final FlagOverrideStore.FlagChangeCallback mFlagChangeCallback =
+            (namespace, name, value) -> {
+                // Don't bother with callbacks for non-dynamic flags.
+                if (!mDynamicFlags.contains(namespace, name)) {
+                    return;
+                }
+
+                // Don't bother with callbacks if nothing changed.
+                // Handling erasure (null) is special, as we may be restoring back to a value
+                // we were already at.
+                DynamicFlagData data = mDynamicFlags.getOrNull(namespace, name);
+                if (data == null) {
+                    return;  // shouldn't happen, but better safe than sorry.
+                }
+                if (value == null) {
+                    if (data.getValue().equals(data.getDefaultValue())) {
+                        return;
+                    }
+                    value = data.getDefaultValue();
+                } else if (data.getValue().equals(value)) {
+                    return;
+                }
+                data.setValue(value);
+
+                final Set<IFeatureFlagsCallback> cbCopy;
+                synchronized (mCallbacks) {
+                    cbCopy = new HashSet<>();
+
+                    for (Integer pid : mCallbacks.keySet()) {
+                        if (data.containsPid(pid)) {
+                            cbCopy.addAll(mCallbacks.get(pid));
+                        }
+                    }
+                }
+                SyncableFlag sFlag = new SyncableFlag(namespace, name, value, true);
+                cbCopy.forEach(cb -> {
+                    try {
+                        cb.onFlagChange(sFlag);
+                    } catch (RemoteException e) {
+                        Slog.w(
+                                FeatureFlagsService.TAG,
+                                "Failed to communicate flag change to client.");
+                    }
+                });
+            };
+
+    DynamicFlagBinderDelegate(FlagOverrideStore flagStore) {
+        mFlagStore = flagStore;
+        mFlagStore.setChangeCallback(mFlagChangeCallback);
+    }
+
+    SyncableFlag syncDynamicFlag(int pid, SyncableFlag sf) {
+        if (!sf.isDynamic()) {
+            return sf;
+        }
+
+        String ns = sf.getNamespace();
+        String name = sf.getName();
+
+        // Dynamic flags don't need any special threading or synchronization considerations.
+        // We simply give them whatever the current value is.
+        // However, we do need to keep track of dynamic flags, so that we can alert
+        // about changes coming in from adb, DeviceConfig, or other sources.
+        // And also so that we can keep flags relatively consistent across processes.
+
+        DynamicFlagData data = mDynamicFlags.getOrNull(ns, name);
+        String value = getFlagValue(ns, name, sf.getValue());
+        // DeviceConfig listeners are per-namespace.
+        if (!mDynamicFlags.containsNamespace(ns)) {
+            DeviceConfig.addOnPropertiesChangedListener(
+                    ns, BackgroundThread.getExecutor(), mDeviceConfigListener);
+        }
+        data.addClientPid(pid);
+        data.setValue(value);
+        // Store the default value so that if an override gets erased, we can restore
+        // to something.
+        data.setDefaultValue(sf.getValue());
+
+        return new SyncableFlag(sf.getNamespace(), sf.getName(), value, true);
+    }
+
+
+    void registerCallback(int pid, IFeatureFlagsCallback callback) {
+        // Always add callback so that we don't end up with a possible race/leak.
+        // We remove the callback directly if we fail to call #linkToDeath.
+        // If we tried to add the callback after we linked, then we could end up in a
+        // scenario where we link, then the binder dies, firing our BinderGriever which tries
+        // to remove the callback (which has not yet been added), then finally we add the
+        // callback, creating a leak.
+        Set<IFeatureFlagsCallback> callbacks;
+        synchronized (mCallbacks) {
+            callbacks = mCallbacks.computeIfAbsent(pid, NEW_CALLBACK_SET);
+            callbacks.add(callback);
+        }
+        try {
+            callback.asBinder().linkToDeath(new BinderGriever(pid), 0);
+        } catch (RemoteException e) {
+            Slog.e(
+                    FeatureFlagsService.TAG,
+                    "Failed to link to binder death. Callback not registered.");
+            synchronized (mCallbacks) {
+                callbacks.remove(callback);
+            }
+        }
+    }
+
+    void unregisterCallback(int pid, IFeatureFlagsCallback callback) {
+        // No need to unlink, since the BinderGriever will essentially be a no-op.
+        // We would have to track our BinderGriever's in a map otherwise.
+        synchronized (mCallbacks) {
+            Set<IFeatureFlagsCallback> callbacks =
+                    mCallbacks.computeIfAbsent(pid, NEW_CALLBACK_SET);
+            callbacks.remove(callback);
+        }
+    }
+
+    String getFlagValue(String namespace, String name, String defaultValue) {
+        // If we already have a value cached, just use that.
+        String value = null;
+        DynamicFlagData data = mDynamicFlags.getOrNull(namespace, name);
+        if (data != null) {
+            value = data.getValue();
+        } else {
+            // Put the value in the cache for future reference.
+            data = new DynamicFlagData(namespace, name);
+            mDynamicFlags.setIfChanged(namespace, name, data);
+        }
+        // If we're not in a release build, flags can be overridden locally on device.
+        if (!Build.IS_USER && value == null) {
+            value = mFlagStore.get(namespace, name);
+        }
+        // If we still don't have a value, maybe DeviceConfig does?
+        // Fallback to sf.getValue() here as well.
+        if (value == null) {
+            value = DeviceConfig.getString(namespace, name, defaultValue);
+        }
+
+        return value;
+    }
+
+    private static class DynamicFlagData {
+        private final String mNamespace;
+        private final String mName;
+        private final Set<Integer> mPids = new HashSet<>();
+        private String mValue;
+        private String mDefaultValue;
+
+        private DynamicFlagData(String namespace, String name) {
+            mNamespace = namespace;
+            mName = name;
+        }
+
+        String getValue() {
+            return mValue;
+        }
+
+        void setValue(String value) {
+            mValue = value;
+        }
+
+        String getDefaultValue() {
+            return mDefaultValue;
+        }
+
+        void setDefaultValue(String value) {
+            mDefaultValue = value;
+        }
+
+        void addClientPid(int pid) {
+            mPids.add(pid);
+        }
+
+        boolean containsPid(int pid) {
+            return mPids.contains(pid);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other == null || !(other instanceof DynamicFlagData)) {
+                return false;
+            }
+
+            DynamicFlagData o = (DynamicFlagData) other;
+
+            return mName.equals(o.mName) && mNamespace.equals(o.mNamespace)
+                    && mValue.equals(o.mValue) && mDefaultValue.equals(o.mDefaultValue);
+        }
+
+        @Override
+        public int hashCode() {
+            return mName.hashCode() + mNamespace.hashCode()
+              + mValue.hashCode() + mDefaultValue.hashCode();
+        }
+    }
+
+
+    private class BinderGriever implements IBinder.DeathRecipient {
+        private final int mPid;
+
+        private BinderGriever(int pid) {
+            mPid = pid;
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mCallbacks) {
+                mCallbacks.remove(mPid);
+            }
+        }
+    }
+}
diff --git a/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java b/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java
new file mode 100644
index 0000000..1fa8532
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.flags.IFeatureFlags;
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+
+import com.android.internal.flags.CoreFlags;
+import com.android.server.flags.FeatureFlagsService.PermissionsChecker;
+
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+class FeatureFlagsBinder extends IFeatureFlags.Stub {
+    private final FlagOverrideStore mFlagStore;
+    private final FlagsShellCommand mShellCommand;
+    private final FlagCache<String> mFlagCache = new FlagCache<>();
+    private final DynamicFlagBinderDelegate mDynamicFlagDelegate;
+    private final PermissionsChecker mPermissionsChecker;
+
+    FeatureFlagsBinder(
+            FlagOverrideStore flagStore,
+            FlagsShellCommand shellCommand,
+            PermissionsChecker permissionsChecker) {
+        mFlagStore = flagStore;
+        mShellCommand = shellCommand;
+        mDynamicFlagDelegate = new DynamicFlagBinderDelegate(flagStore);
+        mPermissionsChecker = permissionsChecker;
+    }
+
+    @Override
+    public void registerCallback(IFeatureFlagsCallback callback) {
+        mDynamicFlagDelegate.registerCallback(getCallingPid(), callback);
+    }
+
+    @Override
+    public void unregisterCallback(IFeatureFlagsCallback callback) {
+        mDynamicFlagDelegate.unregisterCallback(getCallingPid(), callback);
+    }
+
+    // Note: The internals of this method should be kept in sync with queryFlags
+    // as they both should return identical results. The difference is that this method
+    // caches any values it receives and/or reads, whereas queryFlags does not.
+
+    @Override
+    public List<SyncableFlag> syncFlags(List<SyncableFlag> incomingFlags) {
+        int pid = getCallingPid();
+        List<SyncableFlag> outputFlags = new ArrayList<>();
+
+        boolean hasFullSyncPrivileges = false;
+        SecurityException permissionFailureException = null;
+        try {
+            assertSyncPermission();
+            hasFullSyncPrivileges = true;
+        } catch (SecurityException e) {
+            permissionFailureException = e;
+        }
+
+        for (SyncableFlag sf : incomingFlags) {
+            if (!hasFullSyncPrivileges && !CoreFlags.isCoreFlag(sf)) {
+                throw permissionFailureException;
+            }
+
+            String ns = sf.getNamespace();
+            String name = sf.getName();
+            SyncableFlag outFlag;
+            if (sf.isDynamic()) {
+                outFlag = mDynamicFlagDelegate.syncDynamicFlag(pid, sf);
+            } else {
+                synchronized (mFlagCache) {
+                    String value = mFlagCache.getOrNull(ns, name);
+                    if (value == null) {
+                        String overrideValue = Build.IS_USER ? null : mFlagStore.get(ns, name);
+                        value = overrideValue != null ? overrideValue : sf.getValue();
+                        mFlagCache.setIfChanged(ns, name, value);
+                    }
+                    outFlag = new SyncableFlag(sf.getNamespace(), sf.getName(), value, false);
+                }
+            }
+            outputFlags.add(outFlag);
+        }
+        return outputFlags;
+    }
+
+    @Override
+    public void overrideFlag(SyncableFlag flag) {
+        assertWritePermission();
+        mFlagStore.set(flag.getNamespace(), flag.getName(), flag.getValue());
+    }
+
+    @Override
+    public void resetFlag(SyncableFlag flag) {
+        assertWritePermission();
+        mFlagStore.erase(flag.getNamespace(), flag.getName());
+    }
+
+    @Override
+    public List<SyncableFlag> queryFlags(List<SyncableFlag> incomingFlags) {
+        assertSyncPermission();
+        List<SyncableFlag> outputFlags = new ArrayList<>();
+        for (SyncableFlag sf : incomingFlags) {
+            String ns = sf.getNamespace();
+            String name = sf.getName();
+            String value;
+            String storeValue = mFlagStore.get(ns, name);
+            boolean overridden  = storeValue != null;
+
+            if (sf.isDynamic()) {
+                value = mDynamicFlagDelegate.getFlagValue(ns, name, sf.getValue());
+            } else {
+                value = mFlagCache.getOrNull(ns, name);
+                if (value == null) {
+                    value = Build.IS_USER ? null : storeValue;
+                    if (value == null) {
+                        value = sf.getValue();
+                    }
+                }
+            }
+            outputFlags.add(new SyncableFlag(
+                    sf.getNamespace(), sf.getName(), value, sf.isDynamic(), overridden));
+        }
+
+        return outputFlags;
+    }
+
+    private void assertSyncPermission() {
+        mPermissionsChecker.assertSyncPermission();
+        clearCallingIdentity();
+    }
+
+    private void assertWritePermission() {
+        mPermissionsChecker.assertWritePermission();
+        clearCallingIdentity();
+    }
+
+
+    @SystemApi
+    public int handleShellCommand(
+            @NonNull ParcelFileDescriptor in,
+            @NonNull ParcelFileDescriptor out,
+            @NonNull ParcelFileDescriptor err,
+            @NonNull String[] args) {
+        FileOutputStream fout = new FileOutputStream(out.getFileDescriptor());
+        FileOutputStream ferr = new FileOutputStream(err.getFileDescriptor());
+
+        return mShellCommand.process(args, fout, ferr);
+    }
+}
diff --git a/services/flags/java/com/android/server/flags/FeatureFlagsService.java b/services/flags/java/com/android/server/flags/FeatureFlagsService.java
new file mode 100644
index 0000000..93b9e9e
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FeatureFlagsService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.flags;
+
+import static android.Manifest.permission.SYNC_FLAGS;
+import static android.Manifest.permission.WRITE_FLAGS;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.flags.FeatureFlags;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+/**
+ * A service that manages syncing {@link android.flags.FeatureFlags} across processes.
+ *
+ * This service holds flags stable for at least the lifetime of a process, meaning that if
+ * a process comes online with a flag set to true, any other process that connects here and
+ * tries to read the same flag will also receive the flag as true. The flag will remain stable
+ * until either all of the interested processes have died, or the device restarts.
+ *
+ * TODO(279054964): Add to dumpsys
+ * @hide
+ */
+public class FeatureFlagsService extends SystemService {
+
+    static final String TAG = "FeatureFlagsService";
+    private final FlagOverrideStore mFlagStore;
+    private final FlagsShellCommand mShellCommand;
+
+    /**
+     * Initializes the system service.
+     *
+     * @param context The system server context.
+     */
+    public FeatureFlagsService(Context context) {
+        super(context);
+        mFlagStore = new FlagOverrideStore(
+                new GlobalSettingsProxy(context.getContentResolver()));
+        mShellCommand = new FlagsShellCommand(mFlagStore);
+    }
+
+    @Override
+    public void onStart() {
+        Slog.d(TAG, "Started Feature Flag Service");
+        FeatureFlagsBinder service = new FeatureFlagsBinder(
+                mFlagStore, mShellCommand, new PermissionsChecker(getContext()));
+        publishBinderService(
+                Context.FEATURE_FLAGS_SERVICE, service);
+        publishLocalService(FeatureFlags.class, new FeatureFlags(service));
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        super.onBootPhase(phase);
+
+        if (phase == PHASE_SYSTEM_SERVICES_READY) {
+            // Immediately sync our core flags so that they get locked in. We don't want third-party
+            // apps to override them, and syncing immediately is the easiest way to prevent that.
+            FeatureFlags.getInstance().sync();
+        }
+    }
+
+    /**
+     * Delegate for checking flag permissions.
+     */
+    @VisibleForTesting
+    public static class PermissionsChecker {
+        private final Context mContext;
+
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public PermissionsChecker(Context context) {
+            mContext = context;
+        }
+
+        /**
+         * Ensures that the caller has {@link SYNC_FLAGS} permission.
+         */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public void assertSyncPermission() {
+            if (mContext.checkCallingOrSelfPermission(SYNC_FLAGS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException(
+                        "Non-core flag queried. Requires SYNC_FLAGS permission!");
+            }
+        }
+
+        /**
+         * Ensures that the caller has {@link WRITE_FLAGS} permission.
+         */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public void assertWritePermission() {
+            if (mContext.checkCallingPermission(WRITE_FLAGS) != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Requires WRITE_FLAGS permission!");
+            }
+        }
+    }
+}
diff --git a/services/flags/java/com/android/server/flags/FlagCache.java b/services/flags/java/com/android/server/flags/FlagCache.java
new file mode 100644
index 0000000..cee1578
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FlagCache.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Threadsafe cache of values that stores the supplied default on cache miss.
+ *
+ * @param <V> The type of value to store.
+ */
+public class FlagCache<V> {
+    private final Function<String, HashMap<String, V>> mNewHashMap = k -> new HashMap<>();
+
+    // Cache is organized first by namespace, then by name. All values are stored as strings.
+    final Map<String, Map<String, V>> mCache = new HashMap<>();
+
+    FlagCache() {
+    }
+
+    /**
+     * Returns true if the namespace exists in the cache already.
+     */
+    boolean containsNamespace(String namespace) {
+        synchronized (mCache) {
+            return mCache.containsKey(namespace);
+        }
+    }
+
+    /**
+     * Returns true if the value is stored in the cache.
+     */
+    boolean contains(String namespace, String name) {
+        synchronized (mCache) {
+            Map<String, V> nsCache = mCache.get(namespace);
+            return nsCache != null && nsCache.containsKey(name);
+        }
+    }
+
+    /**
+     * Sets the value if it is different from what is currently stored.
+     *
+     * If the value is not set, or the current value is null, it will store the value and
+     * return true.
+     *
+     * @return True if the value was set. False if the value is the same.
+     */
+    boolean setIfChanged(String namespace, String name, V value) {
+        synchronized (mCache) {
+            Map<String, V> nsCache = mCache.computeIfAbsent(namespace, mNewHashMap);
+            V curValue = nsCache.get(name);
+            if (curValue == null || !curValue.equals(value)) {
+                nsCache.put(name, value);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Gets the current value from the cache, setting it if it is currently absent.
+     *
+     * @return The value that is now in the cache after the call to the method.
+     */
+    V getOrSet(String namespace, String name, V defaultValue) {
+        synchronized (mCache) {
+            Map<String, V> nsCache = mCache.computeIfAbsent(namespace, mNewHashMap);
+            V value = nsCache.putIfAbsent(name, defaultValue);
+            return value == null ? defaultValue : value;
+        }
+    }
+
+    /**
+     * Gets the current value from the cache, returning null if not present.
+     *
+     * @return The value that is now in the cache if there is one.
+     */
+    V getOrNull(String namespace, String name) {
+        synchronized (mCache) {
+            Map<String, V> nsCache = mCache.get(namespace);
+            if (nsCache == null) {
+                return null;
+            }
+            return nsCache.get(name);
+        }
+    }
+}
diff --git a/services/flags/java/com/android/server/flags/FlagOverrideStore.java b/services/flags/java/com/android/server/flags/FlagOverrideStore.java
new file mode 100644
index 0000000..b1ddc7e6
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FlagOverrideStore.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import android.database.Cursor;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Persistent storage for the {@link FeatureFlagsService}.
+ *
+ * The implementation stores data in Settings.<store> (generally {@link Settings.Global}
+ * is expected).
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class FlagOverrideStore {
+    private static final String KEYNAME_PREFIX = "flag|";
+    private static final String NAMESPACE_NAME_SEPARATOR = ".";
+
+    private final SettingsProxy mSettingsProxy;
+
+    private FlagChangeCallback mCallback;
+
+    FlagOverrideStore(SettingsProxy settingsProxy) {
+        mSettingsProxy = settingsProxy;
+    }
+
+    void setChangeCallback(FlagChangeCallback callback) {
+        mCallback = callback;
+    }
+
+    /** Returns true if a non-null value is in the store. */
+    boolean contains(String namespace, String name) {
+        return get(namespace, name) != null;
+    }
+
+    /** Put a value in the store. */
+    @VisibleForTesting
+    public void set(String namespace, String name, String value) {
+        mSettingsProxy.putString(getPropName(namespace, name), value);
+        mCallback.onFlagChanged(namespace, name, value);
+    }
+
+    /** Read a value out of the store. */
+    @VisibleForTesting
+    public String get(String namespace, String name) {
+        return mSettingsProxy.getString(getPropName(namespace, name));
+    }
+
+    /** Erase a value from the store. */
+    @VisibleForTesting
+    public void erase(String namespace, String name) {
+        set(namespace, name, null);
+    }
+
+    Map<String, Map<String, String>> getFlags() {
+        return getFlagsForNamespace(null);
+    }
+
+    Map<String, Map<String, String>> getFlagsForNamespace(String namespace) {
+        Cursor c = mSettingsProxy.getContentResolver().query(
+                Settings.Global.CONTENT_URI,
+                new String[]{Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE},
+                null, // Doesn't support a "LIKE" query
+                null,
+                null
+        );
+
+        if (c == null) {
+            return Map.of();
+        }
+        int keynamePrefixLength = KEYNAME_PREFIX.length();
+        Map<String, Map<String, String>> results = new HashMap<>();
+        while (c.moveToNext()) {
+            String key = c.getString(0);
+            if (!key.startsWith(KEYNAME_PREFIX)
+                    || key.indexOf(NAMESPACE_NAME_SEPARATOR, keynamePrefixLength) < 0) {
+                continue;
+            }
+            String value = c.getString(1);
+            if (value == null || value.isEmpty()) {
+                continue;
+            }
+            String ns = key.substring(keynamePrefixLength, key.indexOf(NAMESPACE_NAME_SEPARATOR));
+            if (namespace != null && !namespace.equals(ns)) {
+                continue;
+            }
+            String name = key.substring(key.indexOf(NAMESPACE_NAME_SEPARATOR) + 1);
+            results.putIfAbsent(ns, new HashMap<>());
+            results.get(ns).put(name, value);
+        }
+        c.close();
+        return results;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    static String getPropName(String namespace, String name) {
+        return KEYNAME_PREFIX + namespace + NAMESPACE_NAME_SEPARATOR + name;
+    }
+
+    interface FlagChangeCallback {
+        void onFlagChanged(String namespace, String name, String value);
+    }
+}
diff --git a/services/flags/java/com/android/server/flags/FlagsShellCommand.java b/services/flags/java/com/android/server/flags/FlagsShellCommand.java
new file mode 100644
index 0000000..b7896ee
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FlagsShellCommand.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastPrintWriter;
+
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Process command line input for the flags service.
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class FlagsShellCommand {
+    private final FlagOverrideStore mFlagStore;
+
+    FlagsShellCommand(FlagOverrideStore flagStore) {
+        mFlagStore = flagStore;
+    }
+
+    /**
+     * Interpret the command supplied in the constructor.
+     *
+     * @return Zero on success or non-zero on error.
+     */
+    public int process(
+            String[] args,
+            OutputStream out,
+            OutputStream err) {
+        PrintWriter outPw = new FastPrintWriter(out);
+        PrintWriter errPw = new FastPrintWriter(err);
+
+        if (args.length == 0) {
+            return printHelp(outPw);
+        }
+        switch (args[0].toLowerCase(Locale.ROOT)) {
+            case "help":
+                return printHelp(outPw);
+            case "list":
+                return listCmd(args, outPw, errPw);
+            case "set":
+                return setCmd(args, outPw, errPw);
+            case "get":
+                return getCmd(args, outPw, errPw);
+            case "erase":
+                return eraseCmd(args, outPw, errPw);
+            default:
+                return unknownCmd(outPw);
+        }
+    }
+
+    private int printHelp(PrintWriter outPw) {
+        outPw.println("Feature Flags command, allowing listing, setting, getting, and erasing of");
+        outPw.println("local flag overrides on a device.");
+        outPw.println();
+        outPw.println("Commands:");
+        outPw.println("  list [namespace]");
+        outPw.println("    List all flag overrides. Namespace is optional.");
+        outPw.println();
+        outPw.println("  get <namespace> <name>");
+        outPw.println("    Return the string value of a specific flag, or <unset>");
+        outPw.println();
+        outPw.println("  set <namespace> <name> <value>");
+        outPw.println("    Set a specific flag");
+        outPw.println();
+        outPw.println("  erase <namespace> <name>");
+        outPw.println("    Unset a specific flag");
+        outPw.flush();
+        return 0;
+    }
+
+    private int listCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+        if (!validateNumArguments(args, 0, 1, args[0], errPw)) {
+            errPw.println("Expected `" + args[0] + " [namespace]`");
+            errPw.flush();
+            return -1;
+        }
+        Map<String, Map<String, String>> overrides;
+        if (args.length == 2) {
+            overrides = mFlagStore.getFlagsForNamespace(args[1]);
+        } else {
+            overrides = mFlagStore.getFlags();
+        }
+        if (overrides.isEmpty()) {
+            outPw.println("No overrides set");
+        } else {
+            int longestNamespaceLen = "namespace".length();
+            int longestFlagLen = "flag".length();
+            int longestValLen = "value".length();
+            for (Map.Entry<String, Map<String, String>> namespace : overrides.entrySet()) {
+                longestNamespaceLen = Math.max(longestNamespaceLen, namespace.getKey().length());
+                for (Map.Entry<String, String> flag : namespace.getValue().entrySet()) {
+                    longestFlagLen = Math.max(longestFlagLen, flag.getKey().length());
+                    longestValLen = Math.max(longestValLen, flag.getValue().length());
+                }
+            }
+            outPw.print(String.format("%-" + longestNamespaceLen + "s", "namespace"));
+            outPw.print(' ');
+            outPw.print(String.format("%-" + longestFlagLen + "s", "flag"));
+            outPw.print(' ');
+            outPw.println("value");
+            for (int i = 0; i < longestNamespaceLen; i++) {
+                outPw.print('=');
+            }
+            outPw.print(' ');
+            for (int i = 0; i < longestFlagLen; i++) {
+                outPw.print('=');
+            }
+            outPw.print(' ');
+            for (int i = 0; i < longestValLen; i++) {
+                outPw.print('=');
+            }
+            outPw.println();
+            for (Map.Entry<String, Map<String, String>> namespace : overrides.entrySet()) {
+                for (Map.Entry<String, String> flag : namespace.getValue().entrySet()) {
+                    outPw.print(
+                            String.format("%-" + longestNamespaceLen + "s", namespace.getKey()));
+                    outPw.print(' ');
+                    outPw.print(String.format("%-" + longestFlagLen + "s", flag.getKey()));
+                    outPw.print(' ');
+                    outPw.println(flag.getValue());
+                }
+            }
+        }
+        outPw.flush();
+        return 0;
+    }
+
+    private int setCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+        if (!validateNumArguments(args, 3, args[0], errPw)) {
+            errPw.println("Expected `" + args[0] + " <namespace> <name> <value>`");
+            errPw.flush();
+            return -1;
+        }
+        mFlagStore.set(args[1], args[2], args[3]);
+        outPw.println("Flag " + args[1] + "." + args[2] + " is now " + args[3]);
+        outPw.flush();
+        return 0;
+    }
+
+    private int getCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+        if (!validateNumArguments(args, 2, args[0], errPw)) {
+            errPw.println("Expected `" + args[0] + " <namespace> <name>`");
+            errPw.flush();
+            return -1;
+        }
+
+        String value = mFlagStore.get(args[1], args[2]);
+        outPw.print(args[1] + "." + args[2] + " is ");
+        if (value == null || value.isEmpty()) {
+            outPw.println("<unset>");
+        } else {
+            outPw.println("\"" + value.translateEscapes() + "\"");
+        }
+        outPw.flush();
+        return 0;
+    }
+
+    private int eraseCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+        if (!validateNumArguments(args, 2, args[0], errPw)) {
+            errPw.println("Expected `" + args[0] + " <namespace> <name>`");
+            errPw.flush();
+            return -1;
+        }
+        mFlagStore.erase(args[1], args[2]);
+        outPw.println("Erased " + args[1] + "." + args[2]);
+        return 0;
+    }
+
+    private int unknownCmd(PrintWriter outPw) {
+        outPw.println("This command is unknown.");
+        printHelp(outPw);
+        outPw.flush();
+        return -1;
+    }
+
+    private boolean validateNumArguments(
+            String[] args, int exactly, String cmdName, PrintWriter errPw) {
+        return validateNumArguments(args, exactly, exactly, cmdName, errPw);
+    }
+
+    private boolean validateNumArguments(
+            String[] args, int min, int max, String cmdName, PrintWriter errPw) {
+        int len = args.length - 1; // Discount the command itself.
+        if (len < min) {
+            errPw.println(
+                    "Less than " + min + " arguments provided for \"" + cmdName + "\" command.");
+            return false;
+        } else if (len > max) {
+            errPw.println(
+                    "More than " + max + " arguments provided for \"" + cmdName + "\" command.");
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java b/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java
new file mode 100644
index 0000000..acb7bb5
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.provider.Settings;
+
+class GlobalSettingsProxy implements SettingsProxy {
+    private final ContentResolver mContentResolver;
+
+    GlobalSettingsProxy(ContentResolver contentResolver) {
+        mContentResolver = contentResolver;
+    }
+
+    @Override
+    public ContentResolver getContentResolver() {
+        return mContentResolver;
+    }
+
+    @Override
+    public Uri getUriFor(String name) {
+        return Settings.Global.getUriFor(name);
+    }
+
+    @Override
+    public String getStringForUser(String name, int userHandle) {
+        return Settings.Global.getStringForUser(mContentResolver, name, userHandle);
+    }
+
+    @Override
+    public boolean putString(String name, String value, boolean overrideableByRestore) {
+        throw new UnsupportedOperationException(
+                "This method only exists publicly for Settings.System and Settings.Secure");
+    }
+
+    @Override
+    public boolean putStringForUser(String name, String value, int userHandle) {
+        return Settings.Global.putStringForUser(mContentResolver, name, value, userHandle);
+    }
+
+    @Override
+    public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
+            int userHandle, boolean overrideableByRestore) {
+        return Settings.Global.putStringForUser(
+                mContentResolver, name, value, tag, makeDefault, userHandle,
+                overrideableByRestore);
+    }
+
+    @Override
+    public boolean putString(String name, String value, String tag, boolean makeDefault) {
+        return Settings.Global.putString(mContentResolver, name, value, tag, makeDefault);
+    }
+}
diff --git a/services/flags/java/com/android/server/flags/SettingsProxy.java b/services/flags/java/com/android/server/flags/SettingsProxy.java
new file mode 100644
index 0000000..c6e85d5
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/SettingsProxy.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings;
+
+/**
+ * Wrapper class meant to enable hermetic testing of {@link Settings}.
+ *
+ * Implementations of this class are expected to be constructed with a {@link ContentResolver} or,
+ * otherwise have access to an implicit one. All the proxy methods in this class exclude
+ * {@link ContentResolver} from their signature and rely on an internally defined one instead.
+ *
+ * Most methods in the {@link Settings} classes have default implementations defined.
+ * Implementations of this interfac need only concern themselves with getting and putting Strings.
+ * They should also override any methods for a class they are proxying that _are not_ defined, and
+ * throw an appropriate {@link UnsupportedOperationException}. For instance, {@link Settings.Global}
+ * does not define {@link #putString(String, String, boolean)}, so an implementation of this
+ * interface that proxies through to it should throw an exception when that method is called.
+ *
+ * This class adds in the following helpers as well:
+ *  - {@link #getBool(String)}
+ *  - {@link #putBool(String, boolean)}
+ *  - {@link #registerContentObserver(Uri, ContentObserver)}
+ *
+ * ... and similar variations for all of those.
+ */
+public interface SettingsProxy {
+
+    /**
+     * Returns the {@link ContentResolver} this instance uses.
+     */
+    ContentResolver getContentResolver();
+
+    /**
+     * Construct the content URI for a particular name/value pair,
+     * useful for monitoring changes with a ContentObserver.
+     * @param name to look up in the table
+     * @return the corresponding content URI, or null if not present
+     */
+    Uri getUriFor(String name);
+
+    /**See {@link Settings.Secure#getString(ContentResolver, String)} */
+    String getStringForUser(String name, int userHandle);
+
+    /**See {@link Settings.Secure#putString(ContentResolver, String, String, boolean)} */
+    boolean putString(String name, String value, boolean overrideableByRestore);
+
+    /** See {@link Settings.Secure#putStringForUser(ContentResolver, String, String, int)} */
+    boolean putStringForUser(String name, String value, int userHandle);
+
+    /**
+     * See {@link Settings.Secure#putStringForUser(ContentResolver, String, String, String, boolean,
+     * int, boolean)}
+     */
+    boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag,
+            boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore);
+
+    /** See {@link Settings.Secure#putString(ContentResolver, String, String, String, boolean)} */
+    boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag,
+            boolean makeDefault);
+
+    /**
+     * Returns the user id for the associated {@link ContentResolver}.
+     */
+    default int getUserId() {
+        return getContentResolver().getUserId();
+    }
+
+    /** See {@link Settings.Secure#getString(ContentResolver, String)} */
+    default String getString(String name) {
+        return getStringForUser(name, getUserId());
+    }
+
+    /** See {@link Settings.Secure#putString(ContentResolver, String, String)} */
+    default boolean putString(String name, String value) {
+        return putStringForUser(name, value, getUserId());
+    }
+    /** See {@link Settings.Secure#getIntForUser(ContentResolver, String, int, int)} */
+    default int getIntForUser(String name, int def, int userHandle) {
+        String v = getStringForUser(name, userHandle);
+        try {
+            return v != null ? Integer.parseInt(v) : def;
+        } catch (NumberFormatException e) {
+            return def;
+        }
+    }
+
+    /** See {@link Settings.Secure#getInt(ContentResolver, String)}  */
+    default int getInt(String name) throws Settings.SettingNotFoundException {
+        return getIntForUser(name, getUserId());
+    }
+
+    /** See {@link Settings.Secure#getIntForUser(ContentResolver, String, int)} */
+    default int getIntForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        String v = getStringForUser(name, userHandle);
+        try {
+            return Integer.parseInt(v);
+        } catch (NumberFormatException e) {
+            throw new Settings.SettingNotFoundException(name);
+        }
+    }
+
+    /** See {@link Settings.Secure#putInt(ContentResolver, String, int)} */
+    default boolean putInt(String name, int value) {
+        return putIntForUser(name, value, getUserId());
+    }
+
+    /** See {@link Settings.Secure#putIntForUser(ContentResolver, String, int, int)} */
+    default boolean putIntForUser(String name, int value, int userHandle) {
+        return putStringForUser(name, Integer.toString(value), userHandle);
+    }
+
+    /**
+     * Convenience function for retrieving a single settings value
+     * as a boolean.  Note that internally setting values are always
+     * stored as strings; this function converts the string to a boolean
+     * for you. The default value will be returned if the setting is
+     * not defined or not a boolean.
+     *
+     * @param name The name of the setting to retrieve.
+     * @param def Value to return if the setting is not defined.
+     *
+     * @return The setting's current value, or 'def' if it is not defined
+     * or not a valid boolean.
+     */
+    default boolean getBool(String name, boolean def) {
+        return getBoolForUser(name, def, getUserId());
+    }
+
+    /** See {@link #getBool(String, boolean)}. */
+    default boolean getBoolForUser(String name, boolean def, int userHandle) {
+        return getIntForUser(name, def ? 1 : 0, userHandle) != 0;
+    }
+
+    /**
+     * Convenience function for retrieving a single settings value
+     * as a boolean.  Note that internally setting values are always
+     * stored as strings; this function converts the string to a boolean
+     * for you.
+     * <p>
+     * This version does not take a default value.  If the setting has not
+     * been set, or the string value is not a number,
+     * it throws {@link Settings.SettingNotFoundException}.
+     *
+     * @param name The name of the setting to retrieve.
+     *
+     * @throws Settings.SettingNotFoundException Thrown if a setting by the given
+     * name can't be found or the setting value is not a boolean.
+     *
+     * @return The setting's current value.
+     */
+    default boolean getBool(String name) throws Settings.SettingNotFoundException {
+        return getBoolForUser(name, getUserId());
+    }
+
+    /** See {@link #getBool(String)}. */
+    default boolean getBoolForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        return getIntForUser(name, userHandle) != 0;
+    }
+
+    /**
+     * Convenience function for updating a single settings value as a
+     * boolean. This will either create a new entry in the table if the
+     * given name does not exist, or modify the value of the existing row
+     * with that name.  Note that internally setting values are always
+     * stored as strings, so this function converts the given value to a
+     * string before storing it.
+     *
+     * @param name The name of the setting to modify.
+     * @param value The new value for the setting.
+     * @return true if the value was set, false on database errors
+     */
+    default boolean putBool(String name, boolean value) {
+        return putBoolForUser(name, value, getUserId());
+    }
+
+    /** See {@link #putBool(String, boolean)}. */
+    default boolean putBoolForUser(String name, boolean value, int userHandle) {
+        return putIntForUser(name, value ? 1 : 0, userHandle);
+    }
+
+    /** See {@link Settings.Secure#getLong(ContentResolver, String, long)}  */
+    default long getLong(String name, long def) {
+        return getLongForUser(name, def, getUserId());
+    }
+
+    /** See {@link Settings.Secure#getLongForUser(ContentResolver, String, long, int)}  */
+    default long getLongForUser(String name, long def, int userHandle) {
+        String valString = getStringForUser(name, userHandle);
+        long value;
+        try {
+            value = valString != null ? Long.parseLong(valString) : def;
+        } catch (NumberFormatException e) {
+            value = def;
+        }
+        return value;
+    }
+
+    /** See {@link Settings.Secure#getLong(ContentResolver, String)}  */
+    default long getLong(String name) throws Settings.SettingNotFoundException {
+        return getLongForUser(name, getUserId());
+    }
+
+    /** See {@link Settings.Secure#getLongForUser(ContentResolver, String, int)}  */
+    default long getLongForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        String valString = getStringForUser(name, userHandle);
+        try {
+            return Long.parseLong(valString);
+        } catch (NumberFormatException e) {
+            throw new Settings.SettingNotFoundException(name);
+        }
+    }
+
+    /** See {@link Settings.Secure#putLong(ContentResolver, String, long)} */
+    default boolean putLong(String name, long value) {
+        return putLongForUser(name, value, getUserId());
+    }
+
+    /** See {@link Settings.Secure#putLongForUser(ContentResolver, String, long, int)}  */
+    default boolean putLongForUser(String name, long value, int userHandle) {
+        return putStringForUser(name, Long.toString(value), userHandle);
+    }
+
+    /** See {@link Settings.Secure#getFloat(ContentResolver, String, float)} */
+    default float getFloat(String name, float def) {
+        return getFloatForUser(name, def, getUserId());
+    }
+
+    /** See {@link Settings.Secure#getFloatForUser(ContentResolver, String, int)} */
+    default float getFloatForUser(String name, float def, int userHandle) {
+        String v = getStringForUser(name, userHandle);
+        try {
+            return v != null ? Float.parseFloat(v) : def;
+        } catch (NumberFormatException e) {
+            return def;
+        }
+    }
+
+
+    /** See {@link Settings.Secure#getFloat(ContentResolver, String)}  */
+    default float getFloat(String name) throws Settings.SettingNotFoundException {
+        return getFloatForUser(name, getUserId());
+    }
+
+    /** See {@link Settings.Secure#getFloatForUser(ContentResolver, String, int)}   */
+    default float getFloatForUser(String name, int userHandle)
+            throws Settings.SettingNotFoundException {
+        String v = getStringForUser(name, userHandle);
+        if (v == null) {
+            throw new Settings.SettingNotFoundException(name);
+        }
+        try {
+            return Float.parseFloat(v);
+        } catch (NumberFormatException e) {
+            throw new Settings.SettingNotFoundException(name);
+        }
+    }
+
+    /** See {@link Settings.Secure#putFloat(ContentResolver, String, float)} */
+    default boolean putFloat(String name, float value) {
+        return putFloatForUser(name, value, getUserId());
+    }
+
+    /** See {@link Settings.Secure#putFloatForUser(ContentResolver, String, float, int)} */
+    default boolean putFloatForUser(String name, float value, int userHandle) {
+        return putStringForUser(name, Float.toString(value), userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+     *
+     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+     */
+    default void registerContentObserver(String name, ContentObserver settingsObserver) {
+        registerContentObserver(getUriFor(name), settingsObserver);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+     */
+    default void registerContentObserver(Uri uri, ContentObserver settingsObserver) {
+        registerContentObserverForUser(uri, settingsObserver, getUserId());
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.
+     *
+     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+     */
+    default void registerContentObserver(String name, boolean notifyForDescendants,
+            ContentObserver settingsObserver) {
+        registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+     */
+    default void registerContentObserver(Uri uri, boolean notifyForDescendants,
+            ContentObserver settingsObserver) {
+        registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId());
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     *
+     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+     */
+    default void registerContentObserverForUser(
+            String name, ContentObserver settingsObserver, int userHandle) {
+        registerContentObserverForUser(
+                getUriFor(name), settingsObserver, userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     */
+    default void registerContentObserverForUser(
+            Uri uri, ContentObserver settingsObserver, int userHandle) {
+        registerContentObserverForUser(
+                uri, false, settingsObserver, userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     *
+     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+     */
+    default void registerContentObserverForUser(
+            String name, boolean notifyForDescendants, ContentObserver settingsObserver,
+            int userHandle) {
+        registerContentObserverForUser(
+                getUriFor(name), notifyForDescendants, settingsObserver, userHandle);
+    }
+
+    /**
+     * Convenience wrapper around
+     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+     */
+    default void registerContentObserverForUser(
+            Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
+            int userHandle) {
+        getContentResolver().registerContentObserver(
+                uri, notifyForDescendants, settingsObserver, userHandle);
+    }
+
+    /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */
+    default void unregisterContentObserver(ContentObserver settingsObserver) {
+        getContentResolver().unregisterContentObserver(settingsObserver);
+    }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index af84180..3a7cb67 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -132,6 +132,7 @@
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.dreams.DreamManagerService;
 import com.android.server.emergency.EmergencyAffordanceService;
+import com.android.server.flags.FeatureFlagsService;
 import com.android.server.gpu.GpuService;
 import com.android.server.grammaticalinflection.GrammaticalInflectionService;
 import com.android.server.graphics.fonts.FontManagerService;
@@ -346,8 +347,6 @@
             "com.android.server.contentcapture.ContentCaptureManagerService";
     private static final String TRANSLATION_MANAGER_SERVICE_CLASS =
             "com.android.server.translation.TranslationManagerService";
-    private static final String SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS =
-            "com.android.server.selectiontoolbar.SelectionToolbarManagerService";
     private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS =
             "com.android.server.musicrecognition.MusicRecognitionManagerService";
     private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
@@ -434,6 +433,10 @@
                     + "OnDevicePersonalizationSystemService$Lifecycle";
     private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS =
             "com.android.server.deviceconfig.DeviceConfigInit$Lifecycle";
+    private static final String DEVICE_LOCK_SERVICE_CLASS =
+            "com.android.server.devicelock.DeviceLockService";
+    private static final String DEVICE_LOCK_APEX_PATH =
+            "/apex/com.android.devicelock/javalib/service-devicelock.jar";
 
     private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
 
@@ -1111,6 +1114,12 @@
         mSystemServiceManager.startService(DeviceIdentifiersPolicyService.class);
         t.traceEnd();
 
+        // Starts a service for reading runtime flag overrides, and keeping processes
+        // in sync with one another.
+        t.traceBegin("StartFeatureFlagsService");
+        mSystemServiceManager.startService(FeatureFlagsService.class);
+        t.traceEnd();
+
         // Uri Grants Manager.
         t.traceBegin("UriGrantsManagerService");
         mSystemServiceManager.startService(UriGrantsManagerService.Lifecycle.class);
@@ -2695,11 +2704,6 @@
             Slog.d(TAG, "TranslationService not defined by OEM");
         }
 
-        // Selection toolbar service
-        t.traceBegin("StartSelectionToolbarManagerService");
-        mSystemServiceManager.startService(SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS);
-        t.traceEnd();
-
         // NOTE: ClipboardService depends on ContentCapture and Autofill
         t.traceBegin("StartClipboardService");
         mSystemServiceManager.startService(ClipboardService.class);
@@ -2864,6 +2868,13 @@
         mSystemServiceManager.startService(HEALTHCONNECT_MANAGER_SERVICE_CLASS);
         t.traceEnd();
 
+        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_LOCK)) {
+            t.traceBegin("DeviceLockService");
+            mSystemServiceManager.startServiceFromJar(DEVICE_LOCK_SERVICE_CLASS,
+                    DEVICE_LOCK_APEX_PATH);
+            t.traceEnd();
+        }
+
         // These are needed to propagate to the runnable below.
         final NetworkManagementService networkManagementF = networkManagement;
         final NetworkPolicyManagerService networkPolicyF = networkPolicy;
@@ -3255,6 +3266,12 @@
                 Slog.d(TAG, "ContentCaptureService disabled because resource is not overlaid");
                 return;
             }
+            if (!deviceHasConfigString(context, R.string.config_defaultContentProtectionService)) {
+                Slog.d(
+                        TAG,
+                        "ContentProtectionService disabled because resource is not overlaid,"
+                            + " ContentCaptureService still enabled");
+            }
         }
 
         t.traceBegin("StartContentCaptureService");
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 4aba30a..fe979b6 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -16,7 +16,9 @@
 
 package com.android.server.midi;
 
+import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothUuid;
 import android.content.BroadcastReceiver;
@@ -48,6 +50,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.EventLog;
 import android.util.Log;
 
@@ -81,6 +84,11 @@
 //   2. synchronized (mDeviceConnections)
 //TODO Introduce a single lock object to lock the whole state and avoid the requirement above.
 
+// All users should be able to connect to USB and Bluetooth MIDI devices.
+// All users can create can install an app that provides, a Virtual MIDI Device Service.
+// Users can not open virtual MIDI devices created by other users.
+// getDevices() surfaces devices that can be opened by that user.
+// openDevice() rejects devices that are cannot be opened by that user.
 public class MidiService extends IMidiManager.Stub {
 
     public static class Lifecycle extends SystemService {
@@ -97,10 +105,21 @@
         }
 
         @Override
+        @RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS},
+                anyOf = {Manifest.permission.QUERY_USERS,
+                Manifest.permission.CREATE_USERS,
+                Manifest.permission.MANAGE_USERS})
+        public void onUserStarting(@NonNull TargetUser user) {
+            mMidiService.onStartOrUnlockUser(user, false /* matchDirectBootUnaware */);
+        }
+
+        @Override
+        @RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS},
+                anyOf = {Manifest.permission.QUERY_USERS,
+                Manifest.permission.CREATE_USERS,
+                Manifest.permission.MANAGE_USERS})
         public void onUserUnlocking(@NonNull TargetUser user) {
-            if (user.getUserIdentifier()  == UserHandle.USER_SYSTEM) {
-                mMidiService.onUnlockUser();
-            }
+            mMidiService.onStartOrUnlockUser(user, true /* matchDirectBootUnaware */);
         }
     }
 
@@ -134,6 +153,7 @@
     private int mNextDeviceId = 1;
 
     private final PackageManager mPackageManager;
+    private final UserManager mUserManager;
 
     private static final String MIDI_LEGACY_STRING = "MIDI 1.0";
     private static final String MIDI_UNIVERSAL_STRING = "MIDI 2.0";
@@ -159,21 +179,24 @@
     private final HashSet<ParcelUuid> mNonMidiUUIDs = new HashSet<ParcelUuid>();
 
     // PackageMonitor for listening to package changes
+    // uid is the uid of the package so use getChangingUserId() to fetch the userId.
     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
         @Override
+        @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
         public void onPackageAdded(String packageName, int uid) {
-            addPackageDeviceServers(packageName);
+            addPackageDeviceServers(packageName, getChangingUserId());
         }
 
         @Override
+        @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
         public void onPackageModified(String packageName) {
-            removePackageDeviceServers(packageName);
-            addPackageDeviceServers(packageName);
+            removePackageDeviceServers(packageName, getChangingUserId());
+            addPackageDeviceServers(packageName, getChangingUserId());
         }
 
         @Override
         public void onPackageRemoved(String packageName, int uid) {
-            removePackageDeviceServers(packageName);
+            removePackageDeviceServers(packageName, getChangingUserId());
         }
     };
 
@@ -202,6 +225,10 @@
             return mUid;
         }
 
+        private int getUserId() {
+            return UserHandle.getUserId(mUid);
+        }
+
         public void addListener(IMidiDeviceListener listener) {
             if (mListeners.size() >= MAX_LISTENERS_PER_CLIENT) {
                 throw new SecurityException(
@@ -219,8 +246,12 @@
             }
         }
 
-        public void addDeviceConnection(Device device, IMidiDeviceOpenCallback callback) {
-            Log.d(TAG, "addDeviceConnection() device:" + device);
+        @RequiresPermission(anyOf = {Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                Manifest.permission.INTERACT_ACROSS_USERS,
+                Manifest.permission.INTERACT_ACROSS_PROFILES})
+        public void addDeviceConnection(Device device, IMidiDeviceOpenCallback callback,
+                int userId) {
+            Log.d(TAG, "addDeviceConnection() device:" + device + " userId:" + userId);
             if (mDeviceConnections.size() >= MAX_CONNECTIONS_PER_CLIENT) {
                 Log.i(TAG, "too many MIDI connections for UID = " + mUid);
                 throw new SecurityException(
@@ -228,7 +259,7 @@
             }
             DeviceConnection connection = new DeviceConnection(device, this, callback);
             mDeviceConnections.put(connection.getToken(), connection);
-            device.addDeviceConnection(connection);
+            device.addDeviceConnection(connection, userId);
         }
 
         // called from MidiService.closeDevice()
@@ -251,8 +282,8 @@
         }
 
         public void deviceAdded(Device device) {
-            // ignore private devices that our client cannot access
-            if (!device.isUidAllowed(mUid)) return;
+            // ignore devices that our client cannot access
+            if (!device.isUidAllowed(mUid) || !device.isUserIdAllowed(getUserId())) return;
 
             MidiDeviceInfo deviceInfo = device.getDeviceInfo();
             try {
@@ -265,8 +296,8 @@
         }
 
         public void deviceRemoved(Device device) {
-            // ignore private devices that our client cannot access
-            if (!device.isUidAllowed(mUid)) return;
+            // ignore devices that our client cannot access
+            if (!device.isUidAllowed(mUid) || !device.isUserIdAllowed(getUserId())) return;
 
             MidiDeviceInfo deviceInfo = device.getDeviceInfo();
             try {
@@ -279,8 +310,8 @@
         }
 
         public void deviceStatusChanged(Device device, MidiDeviceStatus status) {
-            // ignore private devices that our client cannot access
-            if (!device.isUidAllowed(mUid)) return;
+            // ignore devices that our client cannot access
+            if (!device.isUidAllowed(mUid) || !device.isUserIdAllowed(getUserId())) return;
 
             try {
                 for (IMidiDeviceListener listener : mListeners.values()) {
@@ -354,6 +385,8 @@
         private final ServiceInfo mServiceInfo;
         // UID of device implementation
         private final int mUid;
+        // User Id of the app. Only used for virtual devices
+        private final int mUserId;
 
         // ServiceConnection for implementing Service (virtual devices only)
         // mServiceConnection is non-null when connected or attempting to connect to the service
@@ -375,10 +408,11 @@
         private AtomicInteger mTotalOutputBytes = new AtomicInteger();
 
         public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo,
-                ServiceInfo serviceInfo, int uid) {
+                ServiceInfo serviceInfo, int uid, int userId) {
             mDeviceInfo = deviceInfo;
             mServiceInfo = serviceInfo;
             mUid = uid;
+            mUserId = userId;
             mBluetoothDevice = (BluetoothDevice)deviceInfo.getProperties().getParcelable(
                     MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE, android.bluetooth.BluetoothDevice.class);;
             setDeviceServer(server);
@@ -388,6 +422,7 @@
             mBluetoothDevice = bluetoothDevice;
             mServiceInfo = null;
             mUid = mBluetoothServiceUid;
+            mUserId = UserHandle.getUserId(mUid);
         }
 
         private void setDeviceServer(IMidiDeviceServer server) {
@@ -468,11 +503,22 @@
             return mUid;
         }
 
+        public int getUserId() {
+            return mUserId;
+        }
+
         public boolean isUidAllowed(int uid) {
             return (!mDeviceInfo.isPrivate() || mUid == uid);
         }
 
-        public void addDeviceConnection(DeviceConnection connection) {
+        public boolean isUserIdAllowed(int userId) {
+            return (mDeviceInfo.getType() != MidiDeviceInfo.TYPE_VIRTUAL || mUserId == userId);
+        }
+
+        @RequiresPermission(anyOf = {Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                Manifest.permission.INTERACT_ACROSS_USERS,
+                Manifest.permission.INTERACT_ACROSS_PROFILES})
+        public void addDeviceConnection(DeviceConnection connection, int userId) {
             Log.d(TAG, "addDeviceConnection() [A] connection:" + connection);
             synchronized (mDeviceConnections) {
                 mDeviceConnectionsAdded.incrementAndGet();
@@ -537,8 +583,8 @@
                                 new ComponentName(mServiceInfo.packageName, mServiceInfo.name));
                     }
 
-                    if (!mContext.bindService(intent, mServiceConnection,
-                            Context.BIND_AUTO_CREATE)) {
+                    if (!mContext.bindServiceAsUser(intent, mServiceConnection,
+                            Context.BIND_AUTO_CREATE, UserHandle.of(mUserId))) {
                         Log.e(TAG, "Unable to bind service: " + intent);
                         setDeviceServer(null);
                         mServiceConnection = null;
@@ -886,6 +932,8 @@
     public MidiService(Context context) {
         mContext = context;
         mPackageManager = context.getPackageManager();
+        mUserManager = mContext.getSystemService(UserManager.class);
+        mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
 
         // TEMPORARY - Disable BTL-MIDI
         //FIXME - b/25689266
@@ -913,32 +961,41 @@
         // mNonMidiUUIDs.add(BluetoothUuid.BATTERY);
     }
 
-    private void onUnlockUser() {
-        mPackageMonitor.register(mContext, null, true);
-
+    @RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS},
+            anyOf = {Manifest.permission.QUERY_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.MANAGE_USERS})
+    private void onStartOrUnlockUser(TargetUser user, boolean matchDirectBootUnaware) {
+        Log.d(TAG, "onStartOrUnlockUser " + user.getUserIdentifier() + " matchDirectBootUnaware: "
+                + matchDirectBootUnaware);
         Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
-        List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServices(intent,
-                PackageManager.GET_META_DATA);
+        int resolveFlags = PackageManager.GET_META_DATA;
+        if (matchDirectBootUnaware) {
+            resolveFlags |= PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+        }
+        List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServicesAsUser(intent,
+                resolveFlags, user.getUserIdentifier());
         if (resolveInfos != null) {
             int count = resolveInfos.size();
             for (int i = 0; i < count; i++) {
                 ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
                 if (serviceInfo != null) {
-                    addPackageDeviceServer(serviceInfo);
+                    addPackageDeviceServer(serviceInfo, user.getUserIdentifier());
                 }
             }
         }
 
-        PackageInfo info;
-        try {
-            info = mPackageManager.getPackageInfo(MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE, 0);
-        } catch (PackageManager.NameNotFoundException e) {
-            info = null;
-        }
-        if (info != null && info.applicationInfo != null) {
-            mBluetoothServiceUid = info.applicationInfo.uid;
-        } else {
-            mBluetoothServiceUid = -1;
+        if (user.getUserIdentifier() == mUserManager.getMainUser().getIdentifier()) {
+            PackageInfo info;
+            try {
+                info = mPackageManager.getPackageInfoAsUser(
+                        MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE, 0, user.getUserIdentifier());
+            } catch (PackageManager.NameNotFoundException e) {
+                info = null;
+            }
+            if (info != null && info.applicationInfo != null) {
+                mBluetoothServiceUid = info.applicationInfo.uid;
+            }
         }
     }
 
@@ -960,10 +1017,11 @@
 
     // Inform listener of the status of all known devices.
     private void updateStickyDeviceStatus(int uid, IMidiDeviceListener listener) {
+        int userId = UserHandle.getUserId(uid);
         synchronized (mDevicesByInfo) {
             for (Device device : mDevicesByInfo.values()) {
-                // ignore private devices that our client cannot access
-                if (device.isUidAllowed(uid)) {
+                // ignore devices that our client cannot access
+                if (device.isUidAllowed(uid) && device.isUserIdAllowed(userId)) {
                     try {
                         MidiDeviceStatus status = device.getDeviceStatus();
                         if (status != null) {
@@ -989,10 +1047,11 @@
     public MidiDeviceInfo[] getDevicesForTransport(int transport) {
         ArrayList<MidiDeviceInfo> deviceInfos = new ArrayList<MidiDeviceInfo>();
         int uid = Binder.getCallingUid();
+        int userId = getCallingUserId();
 
         synchronized (mDevicesByInfo) {
             for (Device device : mDevicesByInfo.values()) {
-                if (device.isUidAllowed(uid)) {
+                if (device.isUidAllowed(uid) && device.isUserIdAllowed(userId)) {
                     // UMP devices have protocols that are not PROTOCOL_UNKNOWN
                     if (transport == MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS) {
                         if (device.getDeviceInfo().getDefaultProtocol()
@@ -1029,6 +1088,9 @@
             if (!device.isUidAllowed(Binder.getCallingUid())) {
                 throw new SecurityException("Attempt to open private device with wrong UID");
             }
+            if (!device.isUserIdAllowed(getCallingUserId())) {
+                throw new SecurityException("Attempt to open virtual device with wrong user id");
+            }
         }
 
         if (deviceInfo.getType() == MidiDeviceInfo.TYPE_USB) {
@@ -1044,7 +1106,7 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             Log.i(TAG, "addDeviceConnection() [B] device:" + device);
-            client.addDeviceConnection(device, callback);
+            client.addDeviceConnection(device, callback, getCallingUserId());
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -1106,7 +1168,7 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             Log.i(TAG, "addDeviceConnection() [C] device:" + device);
-            client.addDeviceConnection(device, callback);
+            client.addDeviceConnection(device, callback, getCallingUserId());
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -1124,6 +1186,7 @@
             int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
             Bundle properties, int type, int defaultProtocol) {
         int uid = Binder.getCallingUid();
+        int userId = getCallingUserId();
         if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) {
             throw new SecurityException("only system can create USB devices");
         } else if (type == MidiDeviceInfo.TYPE_BLUETOOTH && uid != mBluetoothServiceUid) {
@@ -1133,7 +1196,7 @@
         synchronized (mDevicesByInfo) {
             return addDeviceLocked(type, numInputPorts, numOutputPorts, inputPortNames,
                     outputPortNames, properties, server, null, false, uid,
-                    defaultProtocol);
+                    defaultProtocol, userId);
         }
     }
 
@@ -1210,7 +1273,8 @@
     private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts,
             String[] inputPortNames, String[] outputPortNames, Bundle properties,
             IMidiDeviceServer server, ServiceInfo serviceInfo,
-            boolean isPrivate, int uid, int defaultProtocol) {
+            boolean isPrivate, int uid, int defaultProtocol, int userId) {
+        Log.d(TAG, "addDeviceLocked()" + uid + " type:" + type);
 
         // Limit the number of devices per app.
         int deviceCountForApp = 0;
@@ -1250,7 +1314,7 @@
             }
         }
         if (device == null) {
-            device = new Device(server, deviceInfo, serviceInfo, uid);
+            device = new Device(server, deviceInfo, serviceInfo, uid, userId);
         }
         mDevicesByInfo.put(deviceInfo, device);
         if (bluetoothDevice != null) {
@@ -1281,12 +1345,14 @@
         }
     }
 
-    private void addPackageDeviceServers(String packageName) {
+    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+    private void addPackageDeviceServers(String packageName, int userId) {
         PackageInfo info;
 
         try {
-            info = mPackageManager.getPackageInfo(packageName,
-                    PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+            info = mPackageManager.getPackageInfoAsUser(packageName,
+                    PackageManager.GET_SERVICES | PackageManager.GET_META_DATA
+                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "handlePackageUpdate could not find package " + packageName, e);
             return;
@@ -1295,13 +1361,14 @@
         ServiceInfo[] services = info.services;
         if (services == null) return;
         for (int i = 0; i < services.length; i++) {
-            addPackageDeviceServer(services[i]);
+            addPackageDeviceServer(services[i], userId);
         }
     }
 
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
 
-    private void addPackageDeviceServer(ServiceInfo serviceInfo) {
+    private void addPackageDeviceServer(ServiceInfo serviceInfo, int userId) {
+        Log.d(TAG, "addPackageDeviceServer()" + userId);
         XmlResourceParser parser = null;
 
         try {
@@ -1404,8 +1471,8 @@
 
                             int uid;
                             try {
-                                ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
-                                        serviceInfo.packageName, 0);
+                                ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
+                                        serviceInfo.packageName, 0, userId);
                                 uid = appInfo.uid;
                             } catch (PackageManager.NameNotFoundException e) {
                                 Log.e(TAG, "could not fetch ApplicationInfo for "
@@ -1419,7 +1486,7 @@
                                         inputPortNames.toArray(EMPTY_STRING_ARRAY),
                                         outputPortNames.toArray(EMPTY_STRING_ARRAY),
                                         properties, null, serviceInfo, isPrivate, uid,
-                                        MidiDeviceInfo.PROTOCOL_UNKNOWN);
+                                        MidiDeviceInfo.PROTOCOL_UNKNOWN, userId);
                             }
                             // setting properties to null signals that we are no longer
                             // processing a <device>
@@ -1437,12 +1504,13 @@
         }
     }
 
-    private void removePackageDeviceServers(String packageName) {
+    private void removePackageDeviceServers(String packageName, int userId) {
         synchronized (mDevicesByInfo) {
             Iterator<Device> iterator = mDevicesByInfo.values().iterator();
             while (iterator.hasNext()) {
                 Device device = iterator.next();
-                if (packageName.equals(device.getPackageName())) {
+                if (packageName.equals(device.getPackageName())
+                        && (device.getUserId() == userId)) {
                     iterator.remove();
                     removeDeviceLocked(device);
                 }
@@ -1571,4 +1639,11 @@
     String extractUsbDeviceTag(String propertyName) {
         return propertyName.substring(propertyName.length() - MIDI_LEGACY_STRING.length());
     }
+
+    /**
+     * @return the user id of the calling user.
+     */
+    private int getCallingUserId() {
+        return UserHandle.getUserId(Binder.getCallingUid());
+    }
 }
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index b620407..f5360eb 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -30,6 +30,7 @@
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetEvent;
 import android.app.prediction.AppTargetId;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentFilter;
 import android.content.pm.ShortcutInfo;
@@ -39,6 +40,7 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ChooserActivity;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -63,6 +65,7 @@
     private static final String REMOTE_APP_PREDICTOR_KEY = "remote_app_predictor";
     private final IntentFilter mIntentFilter;
     private final AppPredictor mRemoteAppPredictor;
+    @Nullable private final String mChooserActivity;
 
     ShareTargetPredictor(@NonNull AppPredictionContext predictionContext,
             @NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
@@ -81,6 +84,9 @@
         } else {
             mRemoteAppPredictor = null;
         }
+        ComponentName component = ComponentName.unflattenFromString(
+                context.getResources().getString(R.string.config_chooserActivity));
+        mChooserActivity = (component == null) ? null : component.getShortClassName();
     }
 
     /** Reports chosen history of direct/app share targets. */
@@ -138,7 +144,7 @@
         SharesheetModelScorer.computeScoreForAppShare(shareTargets,
                 getShareEventType(mIntentFilter), getPredictionContext().getPredictedTargetCount(),
                 System.currentTimeMillis(), getDataManager(),
-                mCallingUserId);
+                mCallingUserId, mChooserActivity);
         Collections.sort(shareTargets, (t1, t2) -> -Float.compare(t1.getScore(), t2.getScore()));
         List<AppTarget> appTargetList = new ArrayList<>();
         for (ShareTarget shareTarget : shareTargets) {
diff --git a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
index c77843c..b2f1e21 100644
--- a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
+++ b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
@@ -26,7 +26,6 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.ChooserActivity;
 import com.android.server.people.data.AppUsageStatsData;
 import com.android.server.people.data.DataManager;
 import com.android.server.people.data.Event;
@@ -55,8 +54,6 @@
     private static final float FREQUENTLY_USED_APP_SCORE_INITIAL_DECAY = 0.3F;
     @VisibleForTesting
     static final float FOREGROUND_APP_WEIGHT = 0F;
-    @VisibleForTesting
-    static final String CHOOSER_ACTIVITY = ChooserActivity.class.getSimpleName();
 
     // Keep constructor private to avoid class being instantiated.
     private SharesheetModelScorer() {
@@ -169,13 +166,14 @@
      */
     static void computeScoreForAppShare(List<ShareTargetPredictor.ShareTarget> shareTargets,
             int shareEventType, int targetsLimit, long now, @NonNull DataManager dataManager,
-            @UserIdInt int callingUserId) {
+            @UserIdInt int callingUserId, @Nullable String chooserActivity) {
         computeScore(shareTargets, shareEventType, now);
-        postProcess(shareTargets, targetsLimit, dataManager, callingUserId);
+        postProcess(shareTargets, targetsLimit, dataManager, callingUserId, chooserActivity);
     }
 
     private static void postProcess(List<ShareTargetPredictor.ShareTarget> shareTargets,
-            int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+            int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId,
+            @Nullable String chooserActivity) {
         // Populates a map which key is package name and value is list of shareTargets descended
         // on total score.
         Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap = new ArrayMap<>();
@@ -192,7 +190,7 @@
             }
             targetsList.add(index, shareTarget);
         }
-        promoteForegroundApp(shareTargetMap, dataManager, callingUserId);
+        promoteForegroundApp(shareTargetMap, dataManager, callingUserId, chooserActivity);
         promoteMostChosenAndFrequentlyUsedApps(shareTargetMap, targetsLimit, dataManager,
                 callingUserId);
     }
@@ -272,9 +270,10 @@
      */
     private static void promoteForegroundApp(
             Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap,
-            @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+            @NonNull DataManager dataManager, @UserIdInt int callingUserId,
+            @Nullable String chooserActivity) {
         String sharingForegroundApp = findSharingForegroundApp(shareTargetMap, dataManager,
-                callingUserId);
+                callingUserId, chooserActivity);
         if (sharingForegroundApp != null) {
             ShareTargetPredictor.ShareTarget target = shareTargetMap.get(sharingForegroundApp).get(
                     0);
@@ -297,7 +296,8 @@
     @Nullable
     private static String findSharingForegroundApp(
             Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap,
-            @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+            @NonNull DataManager dataManager, @UserIdInt int callingUserId,
+            @Nullable String chooserActivity) {
         String sharingForegroundApp = null;
         long now = System.currentTimeMillis();
         List<UsageEvents.Event> events = dataManager.queryAppMovingToForegroundEvents(
@@ -306,8 +306,8 @@
         for (int i = events.size() - 1; i >= 0; i--) {
             String className = events.get(i).getClassName();
             String packageName = events.get(i).getPackageName();
-            if (packageName == null || (className != null && className.contains(CHOOSER_ACTIVITY))
-                    || packageName.contains(CHOOSER_ACTIVITY)) {
+            if (packageName == null || (className != null && chooserActivity != null
+                    && className.contains(chooserActivity))) {
                 continue;
             }
             if (sourceApp == null) {
diff --git a/services/selectiontoolbar/Android.bp b/services/selectiontoolbar/Android.bp
deleted file mode 100644
index cc6405f..0000000
--- a/services/selectiontoolbar/Android.bp
+++ /dev/null
@@ -1,22 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-filegroup {
-    name: "services.selectiontoolbar-sources",
-    srcs: ["java/**/*.java"],
-    path: "java",
-    visibility: ["//frameworks/base/services"],
-}
-
-java_library_static {
-    name: "services.selectiontoolbar",
-    defaults: ["platform_service_defaults"],
-    srcs: [":services.selectiontoolbar-sources"],
-    libs: ["services.core"],
-}
diff --git a/services/selectiontoolbar/OWNERS b/services/selectiontoolbar/OWNERS
deleted file mode 100644
index ed9425c..0000000
--- a/services/selectiontoolbar/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /core/java/android/view/selectiontoolbar/OWNERS
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java
deleted file mode 100644
index ae4227b..0000000
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java
+++ /dev/null
@@ -1,84 +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.server.selectiontoolbar;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.IBinder;
-import android.service.selectiontoolbar.ISelectionToolbarRenderService;
-import android.service.selectiontoolbar.SelectionToolbarRenderService;
-import android.util.Slog;
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.ShowInfo;
-
-import com.android.internal.infra.AbstractRemoteService;
-import com.android.internal.infra.ServiceConnector;
-
-final class RemoteSelectionToolbarRenderService extends
-        ServiceConnector.Impl<ISelectionToolbarRenderService> {
-    private static final String TAG = "RemoteSelectionToolbarRenderService";
-
-    private static final long TIMEOUT_IDLE_UNBIND_MS =
-            AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
-
-    private final ComponentName mComponentName;
-    private final IBinder mRemoteCallback;
-
-    RemoteSelectionToolbarRenderService(Context context, ComponentName serviceName, int userId,
-            IBinder callback) {
-        super(context, new Intent(SelectionToolbarRenderService.SERVICE_INTERFACE).setComponent(
-                serviceName), 0, userId, ISelectionToolbarRenderService.Stub::asInterface);
-        mComponentName = serviceName;
-        mRemoteCallback = callback;
-        // Bind right away.
-        connect();
-    }
-
-    @Override // from AbstractRemoteService
-    protected long getAutoDisconnectTimeoutMs() {
-        return TIMEOUT_IDLE_UNBIND_MS;
-    }
-
-    @Override // from ServiceConnector.Impl
-    protected void onServiceConnectionStatusChanged(ISelectionToolbarRenderService service,
-            boolean connected) {
-        try {
-            if (connected) {
-                service.onConnected(mRemoteCallback);
-            }
-        } catch (Exception e) {
-            Slog.w(TAG, "Exception calling onConnected().", e);
-        }
-    }
-
-    public ComponentName getComponentName() {
-        return mComponentName;
-    }
-
-    public void onShow(int callingUid, ShowInfo showInfo, ISelectionToolbarCallback callback) {
-        run((s) -> s.onShow(callingUid, showInfo, callback));
-    }
-
-    public void onHide(long widgetToken) {
-        run((s) -> s.onHide(widgetToken));
-    }
-
-    public void onDismiss(int callingUid, long widgetToken) {
-        run((s) -> s.onDismiss(callingUid, widgetToken));
-    }
-}
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerService.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerService.java
deleted file mode 100644
index 3bdf55c..0000000
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerService.java
+++ /dev/null
@@ -1,104 +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.server.selectiontoolbar;
-
-import android.content.Context;
-import android.util.Slog;
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.ISelectionToolbarManager;
-import android.view.selectiontoolbar.ShowInfo;
-
-import com.android.internal.util.DumpUtils;
-import com.android.server.infra.AbstractMasterSystemService;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * Entry point service for selection toolbar management.
- */
-public final class SelectionToolbarManagerService extends
-        AbstractMasterSystemService<SelectionToolbarManagerService,
-                SelectionToolbarManagerServiceImpl> {
-
-    private static final String TAG = "SelectionToolbarManagerService";
-
-    @Override
-    public void onStart() {
-        publishBinderService(Context.SELECTION_TOOLBAR_SERVICE,
-                new SelectionToolbarManagerService.SelectionToolbarManagerServiceStub());
-    }
-
-    public SelectionToolbarManagerService(Context context) {
-        super(context, new SelectionToolbarServiceNameResolver(), /* disallowProperty= */
-                null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
-    }
-
-    @Override
-    protected SelectionToolbarManagerServiceImpl newServiceLocked(int resolvedUserId,
-            boolean disabled) {
-        return new SelectionToolbarManagerServiceImpl(this, mLock, resolvedUserId);
-    }
-
-    final class SelectionToolbarManagerServiceStub extends ISelectionToolbarManager.Stub {
-
-        @Override
-        public void showToolbar(ShowInfo showInfo, ISelectionToolbarCallback callback, int userId) {
-            synchronized (mLock) {
-                SelectionToolbarManagerServiceImpl service = getServiceForUserLocked(userId);
-                if (service != null) {
-                    service.showToolbar(showInfo, callback);
-                } else {
-                    Slog.v(TAG, "showToolbar(): no service for " + userId);
-                }
-            }
-        }
-
-        @Override
-        public void hideToolbar(long widgetToken, int userId) {
-            synchronized (mLock) {
-                SelectionToolbarManagerServiceImpl service = getServiceForUserLocked(userId);
-                if (service != null) {
-                    service.hideToolbar(widgetToken);
-                } else {
-                    Slog.v(TAG, "hideToolbar(): no service for " + userId);
-                }
-            }
-        }
-
-        @Override
-        public void dismissToolbar(long widgetToken, int userId) {
-            synchronized (mLock) {
-                SelectionToolbarManagerServiceImpl service = getServiceForUserLocked(userId);
-                if (service != null) {
-                    service.dismissToolbar(widgetToken);
-                } else {
-                    Slog.v(TAG, "dismissToolbar(): no service for " + userId);
-                }
-            }
-        }
-
-        @Override
-        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
-
-            synchronized (mLock) {
-                dumpLocked("", pw);
-            }
-        }
-    }
-}
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
deleted file mode 100644
index c8d153a..0000000
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
+++ /dev/null
@@ -1,151 +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.server.selectiontoolbar;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.AppGlobals;
-import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.service.selectiontoolbar.ISelectionToolbarRenderServiceCallback;
-import android.util.Slog;
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.ShowInfo;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.server.LocalServices;
-import com.android.server.infra.AbstractPerUserSystemService;
-import com.android.server.input.InputManagerInternal;
-
-final class SelectionToolbarManagerServiceImpl extends
-        AbstractPerUserSystemService<SelectionToolbarManagerServiceImpl,
-                SelectionToolbarManagerService> {
-
-    private static final String TAG = "SelectionToolbarManagerServiceImpl";
-
-    @GuardedBy("mLock")
-    @Nullable
-    private RemoteSelectionToolbarRenderService mRemoteService;
-
-    InputManagerInternal mInputManagerInternal;
-    private final SelectionToolbarRenderServiceRemoteCallback mRemoteServiceCallback =
-            new SelectionToolbarRenderServiceRemoteCallback();
-
-    protected SelectionToolbarManagerServiceImpl(@NonNull SelectionToolbarManagerService master,
-            @NonNull Object lock, int userId) {
-        super(master, lock, userId);
-        mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
-        updateRemoteServiceLocked();
-    }
-
-    @GuardedBy("mLock")
-    @Override // from PerUserSystemService
-    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
-            throws PackageManager.NameNotFoundException {
-        return getServiceInfoOrThrow(serviceComponent, mUserId);
-    }
-
-    @GuardedBy("mLock")
-    @Override // from PerUserSystemService
-    protected boolean updateLocked(boolean disabled) {
-        final boolean enabledChanged = super.updateLocked(disabled);
-        updateRemoteServiceLocked();
-        return enabledChanged;
-    }
-
-    /**
-     * Updates the reference to the remote service.
-     */
-    @GuardedBy("mLock")
-    private void updateRemoteServiceLocked() {
-        if (mRemoteService != null) {
-            Slog.d(TAG, "updateRemoteService(): destroying old remote service");
-            mRemoteService.unbind();
-            mRemoteService = null;
-        }
-    }
-
-    @GuardedBy("mLock")
-    void showToolbar(ShowInfo showInfo, ISelectionToolbarCallback callback) {
-        final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked();
-        if (remoteService != null) {
-            remoteService.onShow(Binder.getCallingUid(), showInfo, callback);
-        }
-    }
-
-    @GuardedBy("mLock")
-    void hideToolbar(long widgetToken) {
-        final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked();
-        if (remoteService != null) {
-            remoteService.onHide(widgetToken);
-        }
-    }
-
-    @GuardedBy("mLock")
-    void dismissToolbar(long widgetToken) {
-        final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked();
-        if (remoteService != null) {
-            remoteService.onDismiss(Binder.getCallingUid(), widgetToken);
-        }
-    }
-
-    @GuardedBy("mLock")
-    @Nullable
-    private RemoteSelectionToolbarRenderService ensureRemoteServiceLocked() {
-        if (mRemoteService == null) {
-            final String serviceName = getComponentNameLocked();
-            final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
-            mRemoteService = new RemoteSelectionToolbarRenderService(getContext(), serviceComponent,
-                    mUserId, mRemoteServiceCallback);
-        }
-        return mRemoteService;
-    }
-
-    private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, @UserIdInt int userId)
-            throws PackageManager.NameNotFoundException {
-        int flags = PackageManager.GET_META_DATA;
-
-        ServiceInfo si = null;
-        try {
-            si = AppGlobals.getPackageManager().getServiceInfo(comp, flags, userId);
-        } catch (RemoteException e) {
-        }
-        if (si == null) {
-            throw new PackageManager.NameNotFoundException("Could not get serviceInfo for "
-                    + comp.flattenToShortString());
-        }
-        return si;
-    }
-
-    private void transferTouchFocus(IBinder source, IBinder target) {
-        mInputManagerInternal.transferTouchFocus(source, target);
-    }
-
-    private final class SelectionToolbarRenderServiceRemoteCallback extends
-            ISelectionToolbarRenderServiceCallback.Stub {
-
-        @Override
-        public void transferTouch(IBinder source, IBinder target) {
-            transferTouchFocus(source, target);
-        }
-    }
-}
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java
deleted file mode 100644
index 99b0f25..0000000
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java
+++ /dev/null
@@ -1,45 +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.server.selectiontoolbar;
-
-import android.service.selectiontoolbar.DefaultSelectionToolbarRenderService;
-
-import com.android.server.infra.ServiceNameResolver;
-
-import java.io.PrintWriter;
-
-final class SelectionToolbarServiceNameResolver implements ServiceNameResolver {
-
-    // TODO: move to SysUi or ExtServices
-    private static final String SELECTION_TOOLBAR_SERVICE_NAME =
-            "android/" + DefaultSelectionToolbarRenderService.class.getName();
-
-    @Override
-    public String getDefaultServiceName(int userId) {
-        return SELECTION_TOOLBAR_SERVICE_NAME;
-    }
-
-    @Override
-    public void dumpShort(PrintWriter pw) {
-        pw.print("service="); pw.print(SELECTION_TOOLBAR_SERVICE_NAME);
-    }
-
-    @Override
-    public void dumpShort(PrintWriter pw, int userId) {
-        pw.print("defaultService="); pw.print(getDefaultServiceName(userId));
-    }
-}
diff --git a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
index 212ec14..bef56ce 100644
--- a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
@@ -17,7 +17,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.frameworks.inputmethodtests">
 
-    <uses-sdk android:targetSdkVersion="31" />
     <queries>
         <intent>
             <action android:name="android.view.InputMethod" />
diff --git a/services/tests/InputMethodSystemServerTests/TEST_MAPPING b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
index 77e32a7..cedbfd2b 100644
--- a/services/tests/InputMethodSystemServerTests/TEST_MAPPING
+++ b/services/tests/InputMethodSystemServerTests/TEST_MAPPING
@@ -9,5 +9,16 @@
         {"exclude-annotation": "org.junit.Ignore"}
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "FrameworksImeTests",
+      "options": [
+        {"include-filter": "com.android.inputmethodservice"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    }
   ]
 }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
index 0104f71..b7de749 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
@@ -18,8 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.inputmethod.imetests">
 
-    <uses-sdk android:targetSdkVersion="31" />
-
     <!-- Permissions required for granting and logging -->
     <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
     <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 898658e..e8acb06 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -20,6 +20,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.fail;
+
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -45,6 +47,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -56,9 +59,9 @@
     private static final String EDIT_TEXT_DESC = "Input box";
     private static final long TIMEOUT_IN_SECONDS = 3;
     private static final String ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
-            "settings put secure show_ime_with_hard_keyboard 1";
+            "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 1";
     private static final String DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD =
-            "settings put secure show_ime_with_hard_keyboard 0";
+            "settings put secure " + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD + " 0";
 
     private Instrumentation mInstrumentation;
     private UiDevice mUiDevice;
@@ -82,29 +85,19 @@
         mUiDevice.freezeRotation();
         mUiDevice.setOrientationNatural();
         // Waits for input binding ready.
-        eventually(
-                () -> {
-                    mInputMethodService =
-                            InputMethodServiceWrapper.getInputMethodServiceWrapperForTesting();
-                    assertThat(mInputMethodService).isNotNull();
+        eventually(() -> {
+            mInputMethodService =
+                    InputMethodServiceWrapper.getInputMethodServiceWrapperForTesting();
+            assertThat(mInputMethodService).isNotNull();
 
-                    // The editor won't bring up keyboard by default.
-                    assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
-                    assertThat(mInputMethodService.getCurrentInputViewStarted()).isFalse();
-                });
-        // Save the original value of show_ime_with_hard_keyboard in Settings.
+            // The editor won't bring up keyboard by default.
+            assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
+            assertThat(mInputMethodService.getCurrentInputViewStarted()).isFalse();
+        });
+        // Save the original value of show_ime_with_hard_keyboard from Settings.
         mShowImeWithHardKeyboardEnabled = Settings.Secure.getInt(
                 mInputMethodService.getContentResolver(),
                 Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0;
-        // Disable showing Ime with hard keyboard because it is the precondition the for most test
-        // cases
-        if (mShowImeWithHardKeyboardEnabled) {
-            executeShellCommand(DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
-        }
-        mInputMethodService.getResources().getConfiguration().keyboard =
-                Configuration.KEYBOARD_NOKEYS;
-        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
-                Configuration.HARDKEYBOARDHIDDEN_YES;
     }
 
     @After
@@ -112,82 +105,141 @@
         mUiDevice.unfreezeRotation();
         executeShellCommand("ime disable " + mInputMethodId);
         // Change back the original value of show_ime_with_hard_keyboard in Settings.
-        executeShellCommand(mShowImeWithHardKeyboardEnabled ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD
+        executeShellCommand(mShowImeWithHardKeyboardEnabled
+                ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD
                 : DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
     }
 
+    /**
+     * This checks that the IME can be shown and hidden by user actions
+     * (i.e. tapping on an EditText, tapping the Home button).
+     */
     @Test
-    public void testShowHideKeyboard_byUserAction() throws InterruptedException {
+    public void testShowHideKeyboard_byUserAction() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
         // Performs click on editor box to bring up the soft keyboard.
         Log.i(TAG, "Click on EditText.");
-        verifyInputViewStatus(() -> clickOnEditorText(), true /* inputViewStarted */);
-
-        // Press back key to hide soft keyboard.
-        Log.i(TAG, "Press back");
         verifyInputViewStatus(
-                () -> assertThat(mUiDevice.pressHome()).isTrue(), false /* inputViewStarted */);
+                () -> clickOnEditorText(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        // Press home key to hide soft keyboard.
+        Log.i(TAG, "Press home");
+        verifyInputViewStatus(
+                () -> assertThat(mUiDevice.pressHome()).isTrue(),
+                true /* expected */,
+                false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
+    /**
+     * This checks that the IME can be shown and hidden using the WindowInsetsController APIs.
+     */
     @Test
-    public void testShowHideKeyboard_byApi() throws InterruptedException {
+    public void testShowHideKeyboard_byApi() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
         // Triggers to show IME via public API.
         verifyInputViewStatus(
                 () -> assertThat(mActivity.showImeWithWindowInsetsController()).isTrue(),
+                true /* expected */,
                 true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
 
         // Triggers to hide IME via public API.
         verifyInputViewStatusOnMainSync(
-                () -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                () -> assertThat(mActivity.hideImeWithWindowInsetsController()).isTrue(),
+                true /* expected */,
                 false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
+    /**
+     * This checks the result of calling IMS#requestShowSelf and IMS#requestHideSelf.
+     */
     @Test
-    public void testShowHideSelf() throws InterruptedException {
-        // IME requests to show itself without any flags: expect shown.
+    public void testShowHideSelf() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        // IME request to show itself without any flags, expect shown.
         Log.i(TAG, "Call IMS#requestShowSelf(0)");
         verifyInputViewStatusOnMainSync(
-                () -> mInputMethodService.requestShowSelf(0), true /* inputViewStarted */);
+                () -> mInputMethodService.requestShowSelf(0 /* flags */),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
 
-        // IME requests to hide itself with flag: HIDE_IMPLICIT_ONLY, expect not hide (shown).
+        // IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect not hide (shown).
         Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
         verifyInputViewStatusOnMainSync(
                 () -> mInputMethodService.requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY),
+                false /* expected */,
                 true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
 
-        // IME request to hide itself without any flags: expect hidden.
+        // IME request to hide itself without any flags, expect hidden.
         Log.i(TAG, "Call IMS#requestHideSelf(0)");
         verifyInputViewStatusOnMainSync(
-                () -> mInputMethodService.requestHideSelf(0), false /* inputViewStarted */);
+                () -> mInputMethodService.requestHideSelf(0 /* flags */),
+                true /* expected */,
+                false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
 
-        // IME request to show itself with flag SHOW_IMPLICIT: expect shown.
+        // IME request to show itself with flag SHOW_IMPLICIT, expect shown.
         Log.i(TAG, "Call IMS#requestShowSelf(InputMethodManager.SHOW_IMPLICIT)");
         verifyInputViewStatusOnMainSync(
                 () -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT),
+                true /* expected */,
                 true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
 
-        // IME request to hide itself with flag: HIDE_IMPLICIT_ONLY, expect hidden.
+        // IME request to hide itself with flag HIDE_IMPLICIT_ONLY, expect hidden.
         Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
         verifyInputViewStatusOnMainSync(
                 () -> mInputMethodService.requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY),
+                true /* expected */,
                 false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
+    /**
+     * This checks the return value of IMS#onEvaluateInputViewShown,
+     * when show_ime_with_hard_keyboard is enabled.
+     */
     @Test
     public void testOnEvaluateInputViewShown_showImeWithHardKeyboard() throws Exception {
-        executeShellCommand(ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
-        mInstrumentation.waitForIdleSync();
+        setShowImeWithHardKeyboard(true /* enabled */);
 
-        // Simulate connecting a hard keyboard
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_QWERTY;
         mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
                 Configuration.HARDKEYBOARDHIDDEN_NO;
+        eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue());
 
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_NOKEYS;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_NO;
+        eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue());
+
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_QWERTY;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
         eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue());
     }
 
+    /**
+     * This checks the return value of IMSonEvaluateInputViewShown,
+     * when show_ime_with_hard_keyboard is disabled.
+     */
     @Test
-    public void testOnEvaluateInputViewShown_disableShowImeWithHardKeyboard() {
+    public void testOnEvaluateInputViewShown_disableShowImeWithHardKeyboard() throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_QWERTY;
         mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
@@ -196,6 +248,8 @@
 
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_NOKEYS;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_NO;
         eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue());
 
         mInputMethodService.getResources().getConfiguration().keyboard =
@@ -205,149 +259,386 @@
         eventually(() -> assertThat(mInputMethodService.onEvaluateInputViewShown()).isTrue());
     }
 
+    /**
+     * This checks that any (implicit or explicit) show request,
+     * when IMS#onEvaluateInputViewShown returns false, results in the IME not being shown.
+     */
     @Test
     public void testShowSoftInput_disableShowImeWithHardKeyboard() throws Exception {
-        // Simulate connecting a hard keyboard
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Simulate connecting a hard keyboard.
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_QWERTY;
         mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
                 Configuration.HARDKEYBOARDHIDDEN_NO;
+
         // When InputMethodService#onEvaluateInputViewShown() returns false, the Ime should not be
         // shown no matter what the show flag is.
         verifyInputViewStatusOnMainSync(() -> assertThat(
                 mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                false /* expected */,
                 false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
+
         verifyInputViewStatusOnMainSync(
                 () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                false /* expected */,
                 false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
+    /**
+     * This checks that an explicit show request results in the IME being shown.
+     */
     @Test
     public void testShowSoftInputExplicitly() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
         // When InputMethodService#onEvaluateInputViewShown() returns true and flag is EXPLICIT, the
         // Ime should be shown.
         verifyInputViewStatusOnMainSync(
                 () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
                 true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
     }
 
+    /**
+     * This checks that an implicit show request results in the IME being shown.
+     */
     @Test
     public void testShowSoftInputImplicitly() throws Exception {
-        // When InputMethodService#onEvaluateInputViewShown() returns true and flag is IMPLICIT, the
-        // Ime should be shown.
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        // When InputMethodService#onEvaluateInputViewShown() returns true and flag is IMPLICIT,
+        // the IME should be shown.
         verifyInputViewStatusOnMainSync(() -> assertThat(
                 mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                true /* expected */,
                 true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
     }
 
+    /**
+     * This checks that an explicit show request when the IME is not previously shown,
+     * and it should be shown in fullscreen mode, results in the IME being shown.
+     */
     @Test
-    public void testShowSoftInputImplicitly_fullScreenMode() throws Exception {
-        // When keyboard is off, InputMethodService#onEvaluateInputViewShown returns true, flag is
-        // IMPLICIT and InputMethodService#onEvaluateFullScreenMode returns true, the Ime should not
-        // be shown.
+    public void testShowSoftInputExplicitly_fullScreenMode() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        // Set orientation landscape to enable fullscreen mode.
         setOrientation(2);
         eventually(() -> assertThat(mUiDevice.isNaturalOrientation()).isFalse());
-        // Wait for the TestActivity to be recreated
+        // Wait for the TestActivity to be recreated.
         eventually(() ->
                 assertThat(TestActivity.getLastCreatedInstance()).isNotEqualTo(mActivity));
-        // Get the new TestActivity
+        // Get the new TestActivity.
         mActivity = TestActivity.getLastCreatedInstance();
         assertThat(mActivity).isNotNull();
         InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
-        // Wait for the new EditText to be served by InputMethodManager
-        eventually(() ->
-                assertThat(imm.hasActiveInputConnection(mActivity.getEditText())).isTrue());
+        // Wait for the new EditText to be served by InputMethodManager.
+        eventually(() -> assertThat(
+                imm.hasActiveInputConnection(mActivity.getEditText())).isTrue());
+
         verifyInputViewStatusOnMainSync(() -> assertThat(
-                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
-                false /* inputViewStarted */);
+                        mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
     }
 
+    /**
+     * This checks that an implicit show request when the IME is not previously shown,
+     * and it should be shown in fullscreen mode, results in the IME not being shown.
+     */
+    @Test
+    public void testShowSoftInputImplicitly_fullScreenMode() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        // Set orientation landscape to enable fullscreen mode.
+        setOrientation(2);
+        eventually(() -> assertThat(mUiDevice.isNaturalOrientation()).isFalse());
+        // Wait for the TestActivity to be recreated.
+        eventually(() ->
+                assertThat(TestActivity.getLastCreatedInstance()).isNotEqualTo(mActivity));
+        // Get the new TestActivity.
+        mActivity = TestActivity.getLastCreatedInstance();
+        assertThat(mActivity).isNotNull();
+        InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
+        // Wait for the new EditText to be served by InputMethodManager.
+        eventually(() -> assertThat(
+                imm.hasActiveInputConnection(mActivity.getEditText())).isTrue());
+
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                false /* expected */,
+                false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
+    }
+
+    /**
+     * This checks that an explicit show request when a hard keyboard is connected,
+     * results in the IME being shown.
+     */
+    @Test
+    public void testShowSoftInputExplicitly_withHardKeyboard() throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Simulate connecting a hard keyboard.
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_QWERTY;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+    }
+
+    /**
+     * This checks that an implicit show request when a hard keyboard is connected,
+     * results in the IME not being shown.
+     */
     @Test
     public void testShowSoftInputImplicitly_withHardKeyboard() throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Simulate connecting a hard keyboard.
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_QWERTY;
-        // When connecting to a hard keyboard and the flag is IMPLICIT, the Ime should not be shown.
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
         verifyInputViewStatusOnMainSync(() -> assertThat(
                 mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                false /* expected */,
                 false /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
+    /**
+     * This checks that an explicit show request followed by connecting a hard keyboard
+     * and a configuration change, still results in the IME being shown.
+     */
     @Test
-    public void testConfigurationChanged_withKeyboardShownExplicitly() throws InterruptedException {
+    public void testShowSoftInputExplicitly_thenConfigurationChanged() throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Start with no hard keyboard.
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_NOKEYS;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
         verifyInputViewStatusOnMainSync(
                 () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
                 true /* inputViewStarted */);
-        // Simulate a fake configuration change to avoid triggering the recreation of TestActivity.
-        mInputMethodService.getResources().getConfiguration().orientation =
-                Configuration.ORIENTATION_LANDSCAPE;
-        verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged(
-                mInputMethodService.getResources().getConfiguration()),
-                true /* inputViewStarted */);
-    }
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
 
-    @Test
-    public void testConfigurationChanged_withKeyboardShownImplicitly() throws InterruptedException {
-        verifyInputViewStatusOnMainSync(() -> assertThat(
-                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
-                true /* inputViewStarted */);
-        // Simulate a fake configuration change to avoid triggering the recreation of TestActivity.
-        mInputMethodService.getResources().getConfiguration().orientation =
-                Configuration.ORIENTATION_LANDSCAPE;
+        // Simulate connecting a hard keyboard.
         mInputMethodService.getResources().getConfiguration().keyboard =
                 Configuration.KEYBOARD_QWERTY;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        // Simulate a fake configuration change to avoid triggering the recreation of TestActivity.
+        mInputMethodService.getResources().getConfiguration().orientation =
+                Configuration.ORIENTATION_LANDSCAPE;
+
+        verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged(
+                mInputMethodService.getResources().getConfiguration()),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+    }
+
+    /**
+     * This checks that an implicit show request followed by connecting a hard keyboard
+     * and a configuration change, does not trigger IMS#onFinishInputView,
+     * but results in the IME being hidden.
+     */
+    @Test
+    public void testShowSoftInputImplicitly_thenConfigurationChanged() throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Start with no hard keyboard.
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_NOKEYS;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        // Simulate connecting a hard keyboard.
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_QWERTY;
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        // Simulate a fake configuration change to avoid triggering the recreation of TestActivity.
+        mInputMethodService.getResources().getConfiguration().orientation =
+                Configuration.ORIENTATION_LANDSCAPE;
 
         // Normally, IMS#onFinishInputView will be called when finishing the input view by the user.
         // But if IMS#hideWindow is called when receiving a new configuration change, we don't
         // expect that it's user-driven to finish the lifecycle of input view with
         // IMS#onFinishInputView, because the input view will be re-initialized according to the
-        // last mShowSoftRequested state. So in this case we treat the input view is still alive.
+        // last #mShowInputRequested state. So in this case we treat the input view as still alive.
         verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged(
-                                mInputMethodService.getResources().getConfiguration()),
+                mInputMethodService.getResources().getConfiguration()),
+                true /* expected */,
                 true /* inputViewStarted */);
         assertThat(mInputMethodService.isInputViewShown()).isFalse();
     }
 
-    private void verifyInputViewStatus(Runnable runnable, boolean inputViewStarted)
-            throws InterruptedException {
-        verifyInputViewStatusInternal(runnable, inputViewStarted, false /*runOnMainSync*/);
+    /**
+     * This checks that an explicit show request directly followed by an implicit show request,
+     * while a hardware keyboard is connected, still results in the IME being shown
+     * (i.e. the implicit show request is treated as explicit).
+     */
+    @Test
+    public void testShowSoftInputExplicitly_thenShowSoftInputImplicitly_withHardKeyboard()
+            throws Exception {
+        setShowImeWithHardKeyboard(false /* enabled */);
+
+        // Simulate connecting a hard keyboard.
+        mInputMethodService.getResources().getConfiguration().keyboard =
+                Configuration.KEYBOARD_QWERTY;
+        mInputMethodService.getResources().getConfiguration().hardKeyboardHidden =
+                Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        // Explicit show request.
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                        mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        // Implicit show request.
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_IMPLICIT)).isTrue(),
+                false /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        // Simulate a fake configuration change to avoid triggering the recreation of TestActivity.
+        // This should now consider the implicit show request, but keep the state from the
+        // explicit show request, and thus not hide the keyboard.
+        verifyInputViewStatusOnMainSync(() -> mInputMethodService.onConfigurationChanged(
+                        mInputMethodService.getResources().getConfiguration()),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
     }
 
-    private void verifyInputViewStatusOnMainSync(Runnable runnable, boolean inputViewStarted)
-            throws InterruptedException {
-        verifyInputViewStatusInternal(runnable, inputViewStarted, true /*runOnMainSync*/);
+    /**
+     * This checks that a forced show request directly followed by an explicit show request,
+     * and then a hide not always request, still results in the IME being shown
+     * (i.e. the explicit show request retains the forced state).
+     */
+    @Test
+    public void testShowSoftInputForced_testShowSoftInputExplicitly_thenHideSoftInputNotAlways()
+            throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                mActivity.showImeWithInputMethodManager(InputMethodManager.SHOW_FORCED)).isTrue(),
+                true /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        verifyInputViewStatusOnMainSync(() -> assertThat(
+                        mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                false /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+
+        verifyInputViewStatusOnMainSync(() ->
+                        mActivity.hideImeWithInputMethodManager(InputMethodManager.HIDE_NOT_ALWAYS),
+                false /* expected */,
+                true /* inputViewStarted */);
+        assertThat(mInputMethodService.isInputViewShown()).isTrue();
     }
 
+    /**
+     * This checks that the IME fullscreen mode state is updated after changing orientation.
+     */
+    @Test
+    public void testFullScreenMode() throws Exception {
+        setShowImeWithHardKeyboard(true /* enabled */);
+
+        Log.i(TAG, "Set orientation natural");
+        verifyFullscreenMode(() -> setOrientation(0),
+                false /* expected */,
+                true /* orientationPortrait */);
+
+        Log.i(TAG, "Set orientation left");
+        verifyFullscreenMode(() -> setOrientation(1),
+                true /* expected */,
+                false /* orientationPortrait */);
+
+        Log.i(TAG, "Set orientation right");
+        verifyFullscreenMode(() -> setOrientation(2),
+                false /* expected */,
+                false /* orientationPortrait */);
+    }
+
+    private void verifyInputViewStatus(
+            Runnable runnable, boolean expected, boolean inputViewStarted)
+            throws InterruptedException {
+        verifyInputViewStatusInternal(runnable, expected, inputViewStarted,
+                false /* runOnMainSync */);
+    }
+
+    private void verifyInputViewStatusOnMainSync(
+            Runnable runnable, boolean expected, boolean inputViewStarted)
+            throws InterruptedException {
+        verifyInputViewStatusInternal(runnable, expected, inputViewStarted,
+                true /* runOnMainSync */);
+    }
+
+    /**
+     * Verifies the status of the Input View after executing the given runnable.
+     *
+     * @param runnable the runnable to execute for showing or hiding the IME.
+     * @param expected whether the runnable is expected to trigger the signal.
+     * @param inputViewStarted the expected state of the Input View after executing the runnable.
+     * @param runOnMainSync whether to execute the runnable on the main thread.
+     */
     private void verifyInputViewStatusInternal(
-            Runnable runnable, boolean inputViewStarted, boolean runOnMainSync)
+            Runnable runnable, boolean expected, boolean inputViewStarted, boolean runOnMainSync)
             throws InterruptedException {
         CountDownLatch signal = new CountDownLatch(1);
         mInputMethodService.setCountDownLatchForTesting(signal);
-        // Runnable to trigger onStartInputView()/ onFinishInputView()
+        // Runnable to trigger onStartInputView() / onFinishInputView() / onConfigurationChanged()
         if (runOnMainSync) {
             mInstrumentation.runOnMainSync(runnable);
         } else {
             runnable.run();
         }
-        // Waits for onStartInputView() to finish.
         mInstrumentation.waitForIdleSync();
-        signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        boolean completed = signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        if (expected && !completed) {
+            fail("Timed out waiting for"
+                    + " onStartInputView() / onFinishInputView() / onConfigurationChanged()");
+        } else if (!expected && completed) {
+            fail("Unexpected call"
+                    + " onStartInputView() / onFinishInputView() / onConfigurationChanged()");
+        }
         // Input is not finished.
         assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
         assertThat(mInputMethodService.getCurrentInputViewStarted()).isEqualTo(inputViewStarted);
     }
 
-    @Test
-    public void testFullScreenMode() throws Exception {
-        Log.i(TAG, "Set orientation natural");
-        verifyFullscreenMode(() -> setOrientation(0), true /* orientationPortrait */);
-
-        Log.i(TAG, "Set orientation left");
-        verifyFullscreenMode(() -> setOrientation(1), false /* orientationPortrait */);
-
-        Log.i(TAG, "Set orientation right");
-        verifyFullscreenMode(() -> setOrientation(2), false /* orientationPortrait */);
-    }
-
     private void setOrientation(int orientation) {
         // Simple wrapper for catching RemoteException.
         try {
@@ -366,7 +657,15 @@
         }
     }
 
-    private void verifyFullscreenMode(Runnable runnable, boolean orientationPortrait)
+    /**
+     * Verifies the IME fullscreen mode state after executing the given runnable.
+     *
+     * @param runnable the runnable to execute for setting the orientation.
+     * @param expected whether the runnable is expected to trigger the signal.
+     * @param orientationPortrait whether the orientation is expected to be portrait.
+     */
+    private void verifyFullscreenMode(
+            Runnable runnable, boolean expected, boolean orientationPortrait)
             throws InterruptedException {
         CountDownLatch signal = new CountDownLatch(1);
         mInputMethodService.setCountDownLatchForTesting(signal);
@@ -379,7 +678,12 @@
         }
         // Waits for onConfigurationChanged() to finish.
         mInstrumentation.waitForIdleSync();
-        signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        boolean completed = signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        if (expected && !completed) {
+            fail("Timed out waiting for onConfigurationChanged()");
+        } else if (!expected && completed) {
+            fail("Unexpected call onConfigurationChanged()");
+        }
 
         clickOnEditorText();
         eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isTrue());
@@ -416,7 +720,21 @@
         return mTargetPackageName + "/" + INPUT_METHOD_SERVICE_NAME;
     }
 
-    private String executeShellCommand(String cmd) throws Exception {
+    /**
+     * Sets the value of show_ime_with_hard_keyboard, only if it is different to the default value.
+     *
+     * @param enabled the value to be set.
+     */
+    private void setShowImeWithHardKeyboard(boolean enabled) throws IOException {
+        if (mShowImeWithHardKeyboardEnabled != enabled) {
+            executeShellCommand(enabled
+                    ? ENABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD
+                    : DISABLE_SHOW_IME_WITH_HARD_KEYBOARD_CMD);
+            mInstrumentation.waitForIdleSync();
+        }
+    }
+
+    private String executeShellCommand(String cmd) throws IOException {
         Log.i(TAG, "Run command: " + cmd);
         return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
                 .executeShellCommand(cmd);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index a6ada4d..3199e06 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -20,6 +20,7 @@
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
 
 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
+import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER;
 import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
@@ -39,10 +40,10 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.view.Display;
-import android.view.inputmethod.InputMethodManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.inputmethod.InputBindResult;
 import com.android.internal.inputmethod.StartInputFlags;
 import com.android.internal.inputmethod.StartInputReason;
@@ -75,9 +76,9 @@
     public void testPerformShowIme() throws Exception {
         synchronized (ImfLock.class) {
             mVisibilityApplier.performShowIme(new Binder() /* showInputToken */,
-                    null /* statsToken */, InputMethodManager.SHOW_IMPLICIT, null, SHOW_SOFT_INPUT);
+                    null /* statsToken */, 0 /* showFlags */, null, SHOW_SOFT_INPUT);
         }
-        verifyShowSoftInput(false, true, InputMethodManager.SHOW_IMPLICIT);
+        verifyShowSoftInput(false, true, 0 /* showFlags */);
     }
 
     @Test
@@ -124,7 +125,7 @@
     @Test
     public void testApplyImeVisibility_showImeImplicit() throws Exception {
         mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME_IMPLICIT);
-        verifyShowSoftInput(true, true, InputMethodManager.SHOW_IMPLICIT);
+        verifyShowSoftInput(true, true, 0 /* showFlags */);
     }
 
     @Test
@@ -165,6 +166,29 @@
         verify(mMockImeTargetVisibilityPolicy).removeImeScreenshot(eq(Display.DEFAULT_DISPLAY));
     }
 
+    @Test
+    public void testApplyImeVisibility_hideImeWhenUnbinding() {
+        mInputMethodManagerService.setAttachedClientForTesting(null);
+        startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+        ExtendedMockito.spyOn(mVisibilityApplier);
+
+        synchronized (ImfLock.class) {
+            // Simulate the system hides the IME when switching IME services in different users.
+            // (e.g. unbinding the IME from the current user to the profile user)
+            final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked();
+            mInputMethodManagerService.hideCurrentInputLocked(mWindowToken, null, 0, null,
+                    HIDE_SWITCH_USER);
+            mInputMethodManagerService.onUnbindCurrentMethodByReset();
+
+            // Expects applyImeVisibility() -> hideIme() will be called to notify WM for syncing
+            // the IME hidden state.
+            verify(mVisibilityApplier).applyImeVisibility(eq(mWindowToken), any(),
+                    eq(STATE_HIDE_IME));
+            verify(mInputMethodManagerService.mWindowManagerInternal).hideIme(
+                    eq(mWindowToken), eq(displayIdToShowIme), eq(null));
+        }
+    }
+
     private InputBindResult startInputOrWindowGainedFocus(IBinder windowToken, int softInputMode) {
         return mInputMethodManagerService.startInputOrWindowGainedFocus(
                 StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */,
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index a38c162..fae5f86 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -106,7 +106,7 @@
     @Test
     public void testRequestImeVisibility_showExplicit() {
         initImeTargetWindowState(mWindowToken);
-        boolean res = mComputer.onImeShowFlags(null, 0 /* show explicit */);
+        boolean res = mComputer.onImeShowFlags(null, 0 /* showFlags */);
         mComputer.requestImeVisibility(mWindowToken, res);
 
         final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
@@ -118,6 +118,34 @@
         assertThat(mComputer.mRequestedShowExplicitly).isTrue();
     }
 
+    /**
+     * This checks that the state after an explicit show request does not get reset during
+     * a subsequent implicit show request, without an intermediary hide request.
+     */
+    @Test
+    public void testRequestImeVisibility_showExplicit_thenShowImplicit() {
+        initImeTargetWindowState(mWindowToken);
+        mComputer.onImeShowFlags(null, 0 /* showFlags */);
+        assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+
+        mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT);
+        assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+    }
+
+    /**
+     * This checks that the state after a forced show request does not get reset during
+     * a subsequent explicit show request, without an intermediary hide request.
+     */
+    @Test
+    public void testRequestImeVisibility_showForced_thenShowExplicit() {
+        initImeTargetWindowState(mWindowToken);
+        mComputer.onImeShowFlags(null, InputMethodManager.SHOW_FORCED);
+        assertThat(mComputer.mShowForced).isTrue();
+
+        mComputer.onImeShowFlags(null, 0 /* showFlags */);
+        assertThat(mComputer.mShowForced).isTrue();
+    }
+
     @Test
     public void testRequestImeVisibility_showImplicit_a11yNoImePolicy() {
         // Precondition: set AccessibilityService#SHOW_MODE_HIDDEN policy
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
index 42d373b..e87a34e 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
@@ -162,7 +163,10 @@
             assertThat(mBindingController.getCurToken()).isNotNull();
         }
         // Wait for onServiceConnected()
-        mCountDownLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        boolean completed = mCountDownLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        if (!completed) {
+            fail("Timed out waiting for onServiceConnected()");
+        }
 
         // Verify onServiceConnected() is called and bound successfully.
         synchronized (ImfLock.class) {
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
index 8d0e0c4..e1fd2b3 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
@@ -43,6 +43,8 @@
     },
     export_package_resources: true,
     sdk_version: "current",
+
+    certificate: "platform",
 }
 
 android_library {
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
index 996322d..cf7d660 100644
--- a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
@@ -18,8 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.apps.inputmethod.simpleime">
 
-    <uses-sdk android:targetSdkVersion="31" />
-
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
     <application android:debuggable="true"
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
index e33ca77..b7a0cf3 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
@@ -138,6 +138,14 @@
     }
 
     @Test
+    public void testGetUserMinAspectRatio_withCrossUserId() {
+        final int crossUserId = UserHandle.myUserId() + 1;
+        assertThrows(SecurityException.class,
+                () -> mIPackageManager.getUserMinAspectRatio(
+                        mInstrumentation.getContext().getPackageName(), crossUserId));
+    }
+
+    @Test
     public void testIsPackageSignedByKeySet_cannotDetectCrossUserPkg() throws Exception {
         final KeySet keySet = mIPackageManager.getSigningKeySet(mContext.getPackageName());
         assertThrows(IllegalArgumentException.class,
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
index 435f0d7..a805e5c 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -20,6 +20,7 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
 
 import static java.lang.reflect.Modifier.isFinal;
@@ -138,7 +139,7 @@
                 .setSecondaryCpuAbiString("secondaryCpuAbiString")
                 .setCpuAbiOverrideString("cpuAbiOverrideString")
                 .build();
-        pri.populateUsers(new int[] {
+        pri.populateUsers(new int[]{
                 1, 2, 3, 4, 5
         }, setting);
         Assert.assertNotNull(pri.mBroadcastUsers);
@@ -150,7 +151,7 @@
         pri.mBroadcastUsers = null;
         final int EXCLUDED_USER_ID = 4;
         setting.setInstantApp(true, EXCLUDED_USER_ID);
-        pri.populateUsers(new int[] {
+        pri.populateUsers(new int[]{
                 1, 2, 3, EXCLUDED_USER_ID, 5
         }, setting);
         Assert.assertNotNull(pri.mBroadcastUsers);
@@ -164,8 +165,8 @@
 
     @Test
     public void testPartitions() {
-        String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" };
-        String[] appdir = { "app", "priv-app" };
+        String[] partitions = {"system", "vendor", "odm", "oem", "product", "system_ext"};
+        String[] appdir = {"app", "priv-app"};
         for (int i = 0; i < partitions.length; i++) {
             final ScanPartition scanPartition =
                     PackageManagerService.SYSTEM_PARTITIONS.get(i);
@@ -425,10 +426,10 @@
     private String displayName(Method m) {
         String r = m.getName();
         String p = Arrays.toString(m.getGenericParameterTypes())
-                   .replaceAll("([a-zA-Z0-9]+\\.)+", "")
-                   .replace("class ", "")
-                   .replaceAll("^\\[", "(")
-                   .replaceAll("\\]$", ")");
+                .replaceAll("([a-zA-Z0-9]+\\.)+", "")
+                .replace("class ", "")
+                .replaceAll("^\\[", "(")
+                .replaceAll("\\]$", ")");
         return r + p;
     }
 
@@ -612,4 +613,22 @@
             runShellCommand("pm uninstall " + TEST_PKG_NAME);
         }
     }
+
+    @Test
+    public void testSetUserMinAspectRatio_samePackage_succeeds() throws Exception {
+        mIPackageManager.setUserMinAspectRatio(PACKAGE_NAME, UserHandle.myUserId(),
+                PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
+        // Invoking setUserMinAspectRatio on the same package shouldn't get any exception.
+    }
+
+    @Test
+    public void testSetUserMinAspectRatio_differentPackage_fails() {
+        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
+        runShellCommand("pm install " + testApk);
+        assertThrows(SecurityException.class, () -> {
+            mIPackageManager.setUserMinAspectRatio(TEST_PKG_NAME, UserHandle.myUserId(),
+                    PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
+        });
+        runShellCommand("pm uninstall " + TEST_PKG_NAME);
+    }
 }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 836f858..16fb012 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -971,7 +971,7 @@
         origPkgSetting01.setUserState(0, 100, 1, true, false, false, false, 0, null, false,
                 false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}),
                 new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning",
-                "splashScreenTheme", 1000L);
+                "splashScreenTheme", 1000L, PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
         final PersistableBundle appExtras1 = createPersistableBundle(
                 PACKAGE_NAME_1, 1L, 0.01, true, "appString1");
         final PersistableBundle launcherExtras1 = createPersistableBundle(
@@ -1638,7 +1638,8 @@
                 : oldUserState.getSharedLibraryOverlayPaths() == null)
                 && userState.getSplashScreenTheme().equals(
                         oldUserState.getSplashScreenTheme())
-                && userState.getUninstallReason() == oldUserState.getUninstallReason();
+                && userState.getUninstallReason() == oldUserState.getUninstallReason()
+                && userState.getMinAspectRatio() == oldUserState.getMinAspectRatio();
     }
 
     private SharedUserSetting createSharedUserSetting(Settings settings, String userName,
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
new file mode 100644
index 0000000..a421db0
--- /dev/null
+++ b/services/tests/displayservicetests/Android.bp
@@ -0,0 +1,52 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "DisplayServiceTests",
+
+    srcs: [
+        "src/**/*.java",
+        ":extended-mockito-rule-sources",
+    ],
+
+    libs: [
+        "android.test.mock",
+    ],
+
+    static_libs: [
+        "androidx.test.ext.junit",
+        "frameworks-base-testutils",
+        "junit",
+        "junit-params",
+        "mockingservicestests-utils-mockito",
+        "platform-compat-test-rules",
+        "platform-test-annotations",
+        "services.core",
+        "servicestests-utils",
+        "testables",
+    ],
+
+    defaults: [
+        "modules-utils-testable-device-config-defaults",
+    ],
+
+    platform_apis: true,
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
+
+    certificate: "platform",
+
+    dxflags: ["--multi-dex"],
+
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
new file mode 100644
index 0000000..55fde00
--- /dev/null
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.frameworks.displayservicetests">
+
+    <!-- Permissions -->
+    <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
+    <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
+    <uses-permission android:name="android.permission.DEVICE_POWER" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
+    <!-- Permissions needed for DisplayTransformManagerTest -->
+    <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+    <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+
+    <application android:debuggable="true"
+                 android:testOnly="true">
+        <uses-library android:name="android.test.mock" android:required="true" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Display Service Tests"
+        android:targetPackage="com.android.frameworks.displayservicetests" />
+</manifest>
diff --git a/services/tests/displayservicetests/AndroidTest.xml b/services/tests/displayservicetests/AndroidTest.xml
new file mode 100644
index 0000000..2985f98
--- /dev/null
+++ b/services/tests/displayservicetests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<configuration description="Runs Display Service Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="DisplayServiceTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="DisplayServiceTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.frameworks.displayservicetests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/services/tests/servicestests/src/com/android/server/display/OWNERS b/services/tests/displayservicetests/OWNERS
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/OWNERS
rename to services/tests/displayservicetests/OWNERS
diff --git a/services/tests/displayservicetests/TEST_MAPPING b/services/tests/displayservicetests/TEST_MAPPING
new file mode 100644
index 0000000..477860d
--- /dev/null
+++ b/services/tests/displayservicetests/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "frameworks/base/services/core/java/com/android/server/display"
+    }
+  ]
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
new file mode 100644
index 0000000..7333bc7
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright 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.server.display;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.hardware.display.AmbientBrightnessDayStats;
+import android.os.SystemClock;
+import android.os.UserManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AmbientBrightnessStatsTrackerTest {
+
+    private TestInjector mTestInjector;
+
+    @Before
+    public void setUp() {
+        mTestInjector = new TestInjector();
+    }
+
+    @Test
+    public void testBrightnessStatsTrackerOverSingleDay() {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        float[] expectedStats;
+        // Test case where no user data
+        userStats = statsTracker.getUserStats(0);
+        assertNull(userStats);
+        // Test after adding some user data
+        statsTracker.start();
+        statsTracker.add(0, 0);
+        mTestInjector.incrementTime(1000);
+        statsTracker.stop();
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(1, userStats.size());
+        assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 1;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+        // Test after adding some more user data
+        statsTracker.start();
+        statsTracker.add(0, 0.05f);
+        mTestInjector.incrementTime(1000);
+        statsTracker.add(0, 0.2f);
+        mTestInjector.incrementTime(1500);
+        statsTracker.add(0, 50000);
+        mTestInjector.incrementTime(2500);
+        statsTracker.stop();
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(1, userStats.size());
+        assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 2;
+        expectedStats[1] = 1.5f;
+        expectedStats[11] = 2.5f;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+    }
+
+    @Test
+    public void testBrightnessStatsTrackerOverMultipleDays() {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        float[] expectedStats;
+        // Add data for day 1
+        statsTracker.start();
+        statsTracker.add(0, 0.05f);
+        mTestInjector.incrementTime(1000);
+        statsTracker.add(0, 0.2f);
+        mTestInjector.incrementTime(1500);
+        statsTracker.add(0, 1);
+        mTestInjector.incrementTime(2500);
+        statsTracker.stop();
+        // Add data for day 2
+        mTestInjector.incrementDate(1);
+        statsTracker.start();
+        statsTracker.add(0, 0);
+        mTestInjector.incrementTime(3500);
+        statsTracker.add(0, 5);
+        mTestInjector.incrementTime(5000);
+        statsTracker.stop();
+        // Test that the data is tracked as expected
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(2, userStats.size());
+        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 1;
+        expectedStats[1] = 1.5f;
+        expectedStats[3] = 2.5f;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+        assertEquals(mTestInjector.getLocalDate(), userStats.get(1).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 3.5f;
+        expectedStats[4] = 5;
+        assertArrayEquals(expectedStats, userStats.get(1).getStats(), 0);
+    }
+
+    @Test
+    public void testBrightnessStatsTrackerOverMultipleUsers() {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        float[] expectedStats;
+        // Add data for user 1
+        statsTracker.start();
+        statsTracker.add(0, 0.05f);
+        mTestInjector.incrementTime(1000);
+        statsTracker.add(0, 0.2f);
+        mTestInjector.incrementTime(1500);
+        statsTracker.add(0, 1);
+        mTestInjector.incrementTime(2500);
+        statsTracker.stop();
+        // Add data for user 2
+        mTestInjector.incrementDate(1);
+        statsTracker.start();
+        statsTracker.add(1, 0);
+        mTestInjector.incrementTime(3500);
+        statsTracker.add(1, 5);
+        mTestInjector.incrementTime(5000);
+        statsTracker.stop();
+        // Test that the data is tracked as expected
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(1, userStats.size());
+        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 1;
+        expectedStats[1] = 1.5f;
+        expectedStats[3] = 2.5f;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+        userStats = statsTracker.getUserStats(1);
+        assertEquals(1, userStats.size());
+        assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 3.5f;
+        expectedStats[4] = 5;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+    }
+
+    @Test
+    public void testBrightnessStatsTrackerOverMaxDays() {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        // Add 10 extra days of data over the buffer limit
+        for (int i = 0; i < AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK + 10; i++) {
+            mTestInjector.incrementDate(1);
+            statsTracker.start();
+            statsTracker.add(0, 10);
+            mTestInjector.incrementTime(1000);
+            statsTracker.add(0, 20);
+            mTestInjector.incrementTime(1000);
+            statsTracker.stop();
+        }
+        // Assert that we are only tracking last "MAX_DAYS_TO_TRACK"
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK, userStats.size());
+        LocalDate runningDate = mTestInjector.getLocalDate();
+        for (int i = AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK - 1; i >= 0; i--) {
+            assertEquals(runningDate, userStats.get(i).getLocalDate());
+            runningDate = runningDate.minusDays(1);
+        }
+    }
+
+    @Test
+    public void testReadAmbientBrightnessStats() throws IOException {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        LocalDate date = mTestInjector.getLocalDate();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        String statsFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
+                        + "<ambient-brightness-stats>\r\n"
+                        // Old stats that shouldn't be read
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\""
+                        + date.minusDays(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK)
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
+                        + "0.0,0.0,0.0\" />\r\n"
+                        // Valid stats that should get read
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\""
+                        + date.minusDays(1)
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
+                        + "0.0,0.0,0.0\" />\r\n"
+                        // Valid stats that should get read
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\"" + date
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"0.0,0.0,0.0,0.0,4.482,0.0,0.0,0.0,0.0,"
+                        + "0.0\" />\r\n"
+                        + "</ambient-brightness-stats>";
+        statsTracker.readStats(getInputStream(statsFile));
+        userStats = statsTracker.getUserStats(0);
+        assertEquals(2, userStats.size());
+        assertEquals(new AmbientBrightnessDayStats(date.minusDays(1),
+                new float[]{0, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000},
+                new float[]{1.088f, 0, 0.726f, 0, 25.868f, 0, 0, 0, 0, 0}), userStats.get(0));
+        assertEquals(new AmbientBrightnessDayStats(date,
+                new float[]{0, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000},
+                new float[]{0, 0, 0, 0, 4.482f, 0, 0, 0, 0, 0}), userStats.get(1));
+    }
+
+    @Test
+    public void testFailedReadAmbientBrightnessStatsWithException() {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        LocalDate date = mTestInjector.getLocalDate();
+        String statsFile;
+        // Test with parse error
+        statsFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
+                        + "<ambient-brightness-stats>\r\n"
+                        // Incorrect since bucket boundaries not parsable
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\"" + date
+                        + "\" bucket-boundaries=\"asdf,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
+                        + "0.0,0.0,0.0\" />\r\n"
+                        + "</ambient-brightness-stats>";
+        try {
+            statsTracker.readStats(getInputStream(statsFile));
+        } catch (IOException e) {
+            // Expected
+        }
+        assertNull(statsTracker.getUserStats(0));
+        // Test with incorrect data (bucket boundaries length not equal to stats length)
+        statsFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
+                        + "<ambient-brightness-stats>\r\n"
+                        // Correct data
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\""
+                        + date.minusDays(1)
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"0.0,0.0,0.0,0.0,4.482,0.0,0.0,0.0,0.0,"
+                        + "0.0\" />\r\n"
+                        // Incorrect data
+                        + "<ambient-brightness-day-stats user=\"10\" local-date=\"" + date
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,1000.0,"
+                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
+                        + "0.0,0.0,0.0\" />\r\n"
+                        + "</ambient-brightness-stats>";
+        try {
+            statsTracker.readStats(getInputStream(statsFile));
+        } catch (Exception e) {
+            // Expected
+        }
+        assertNull(statsTracker.getUserStats(0));
+        // Test with missing attribute
+        statsFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
+                        + "<ambient-brightness-stats>\r\n"
+                        + "<ambientBrightnessDayStats user=\"10\" local-date=\"" + date
+                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
+                        + "3000.0,10000.0\" />\r\n"
+                        + "</ambient-brightness-stats>";
+        try {
+            statsTracker.readStats(getInputStream(statsFile));
+        } catch (Exception e) {
+            // Expected
+        }
+        assertNull(statsTracker.getUserStats(0));
+    }
+
+    @Test
+    public void testWriteThenReadAmbientBrightnessStats() throws IOException {
+        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
+        ArrayList<AmbientBrightnessDayStats> userStats;
+        float[] expectedStats;
+        // Generate some placeholder data
+        // Data: very old which should not be read
+        statsTracker.start();
+        statsTracker.add(0, 0.05f);
+        mTestInjector.incrementTime(1000);
+        statsTracker.add(0, 0.2f);
+        mTestInjector.incrementTime(1500);
+        statsTracker.add(0, 1);
+        mTestInjector.incrementTime(2500);
+        statsTracker.stop();
+        // Data: day 1 user 1
+        mTestInjector.incrementDate(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK - 1);
+        statsTracker.start();
+        statsTracker.add(0, 0.05f);
+        mTestInjector.incrementTime(1000);
+        statsTracker.add(0, 0.2f);
+        mTestInjector.incrementTime(1500);
+        statsTracker.add(0, 1);
+        mTestInjector.incrementTime(2500);
+        statsTracker.stop();
+        // Data: day 1 user 2
+        statsTracker.start();
+        statsTracker.add(1, 0);
+        mTestInjector.incrementTime(3500);
+        statsTracker.add(1, 5);
+        mTestInjector.incrementTime(5000);
+        statsTracker.stop();
+        // Data: day 2 user 1
+        mTestInjector.incrementDate(1);
+        statsTracker.start();
+        statsTracker.add(0, 0);
+        mTestInjector.incrementTime(3500);
+        statsTracker.add(0, 50000);
+        mTestInjector.incrementTime(5000);
+        statsTracker.stop();
+        // Write them
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        statsTracker.writeStats(baos);
+        baos.flush();
+        // Read them back and assert that it's the same
+        ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray());
+        AmbientBrightnessStatsTracker newStatsTracker = getTestStatsTracker();
+        newStatsTracker.readStats(input);
+        userStats = newStatsTracker.getUserStats(0);
+        assertEquals(2, userStats.size());
+        // Check day 1 user 1
+        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 1;
+        expectedStats[1] = 1.5f;
+        expectedStats[3] = 2.5f;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+        // Check day 2 user 1
+        assertEquals(mTestInjector.getLocalDate(), userStats.get(1).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 3.5f;
+        expectedStats[11] = 5;
+        assertArrayEquals(expectedStats, userStats.get(1).getStats(), 0);
+        userStats = newStatsTracker.getUserStats(1);
+        assertEquals(1, userStats.size());
+        // Check day 1 user 2
+        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
+        expectedStats = getEmptyStatsArray();
+        expectedStats[0] = 3.5f;
+        expectedStats[4] = 5;
+        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
+    }
+
+    @Test
+    public void testTimer() {
+        AmbientBrightnessStatsTracker.Timer timer = new AmbientBrightnessStatsTracker.Timer(
+                () -> mTestInjector.elapsedRealtimeMillis());
+        assertEquals(0, timer.totalDurationSec(), 0);
+        mTestInjector.incrementTime(1000);
+        assertEquals(0, timer.totalDurationSec(), 0);
+        assertFalse(timer.isRunning());
+        // Start timer
+        timer.start();
+        assertTrue(timer.isRunning());
+        assertEquals(0, timer.totalDurationSec(), 0);
+        mTestInjector.incrementTime(1000);
+        assertTrue(timer.isRunning());
+        assertEquals(1, timer.totalDurationSec(), 0);
+        // Reset timer
+        timer.reset();
+        assertEquals(0, timer.totalDurationSec(), 0);
+        assertFalse(timer.isRunning());
+        // Start again
+        timer.start();
+        assertTrue(timer.isRunning());
+        assertEquals(0, timer.totalDurationSec(), 0);
+        mTestInjector.incrementTime(2000);
+        assertTrue(timer.isRunning());
+        assertEquals(2, timer.totalDurationSec(), 0);
+        // Reset again
+        timer.reset();
+        assertEquals(0, timer.totalDurationSec(), 0);
+        assertFalse(timer.isRunning());
+    }
+
+    private class TestInjector extends AmbientBrightnessStatsTracker.Injector {
+
+        private long mElapsedRealtimeMillis = SystemClock.elapsedRealtime();
+        private LocalDate mLocalDate = LocalDate.now();
+
+        public void incrementTime(long timeMillis) {
+            mElapsedRealtimeMillis += timeMillis;
+        }
+
+        public void incrementDate(int numDays) {
+            mLocalDate = mLocalDate.plusDays(numDays);
+        }
+
+        @Override
+        public long elapsedRealtimeMillis() {
+            return mElapsedRealtimeMillis;
+        }
+
+        @Override
+        public int getUserSerialNumber(UserManager userManager, int userId) {
+            return userId + 10;
+        }
+
+        @Override
+        public int getUserId(UserManager userManager, int userSerialNumber) {
+            return userSerialNumber - 10;
+        }
+
+        @Override
+        public LocalDate getLocalDate() {
+            return mLocalDate;
+        }
+    }
+
+    private AmbientBrightnessStatsTracker getTestStatsTracker() {
+        return new AmbientBrightnessStatsTracker(
+                InstrumentationRegistry.getContext().getSystemService(UserManager.class),
+                mTestInjector);
+    }
+
+    private float[] getEmptyStatsArray() {
+        return new float[AmbientBrightnessStatsTracker.BUCKET_BOUNDARIES_FOR_NEW_STATS.length];
+    }
+
+    private InputStream getInputStream(String data) {
+        return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
new file mode 100644
index 0000000..ee7826f
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -0,0 +1,710 @@
+/*
+ * Copyright 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.display;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.hardware.display.BrightnessConfiguration;
+import android.os.PowerManager;
+import android.util.MathUtils;
+import android.util.Spline;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BrightnessMappingStrategyTest {
+
+    private static final float[] LUX_LEVELS = {
+        0,
+        5,
+        20,
+        40,
+        100,
+        325,
+        600,
+        1250,
+        2200,
+        4000,
+        5000
+    };
+
+    private static final int[] LUX_LEVELS_IDLE = {
+        0,
+        10,
+        40,
+        80,
+        200,
+        655,
+        1200,
+        2500,
+        4400,
+        8000,
+        10000
+    };
+
+    private static final float[] DISPLAY_LEVELS_NITS = {
+        13.25f,
+        54.0f,
+        78.85f,
+        105.02f,
+        132.7f,
+        170.12f,
+        212.1f,
+        265.2f,
+        335.8f,
+        415.2f,
+        478.5f,
+    };
+
+    private static final float[] DISPLAY_LEVELS_NITS_IDLE = {
+        23.25f,
+        64.0f,
+        88.85f,
+        115.02f,
+        142.7f,
+        180.12f,
+        222.1f,
+        275.2f,
+        345.8f,
+        425.2f,
+        468.5f,
+    };
+
+    private static final int[] DISPLAY_LEVELS_BACKLIGHT = {
+        9,
+        30,
+        45,
+        62,
+        78,
+        96,
+        119,
+        146,
+        178,
+        221,
+        255
+    };
+
+    private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
+    private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f };
+    private static final float[] DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT = { 0.03149606299f, 1.0f };
+
+    private static final float[] EMPTY_FLOAT_ARRAY = new float[0];
+    private static final int[] EMPTY_INT_ARRAY = new int[0];
+
+    private static final float MAXIMUM_GAMMA = 3.0f;
+
+    private static final float[] GAMMA_CORRECTION_LUX = {
+        0,
+        100,
+        1000,
+        2500,
+        4000,
+        4900,
+        5000
+    };
+    private static final float[] GAMMA_CORRECTION_NITS = {
+        1.0f,
+        10.55f,
+        96.5f,
+        239.75f,
+        383.0f,
+        468.95f,
+        478.5f,
+    };
+    private static final Spline GAMMA_CORRECTION_SPLINE = Spline.createSpline(
+            new float[] { 0.0f, 100.0f, 1000.0f, 2500.0f, 4000.0f, 4900.0f, 5000.0f },
+            new float[] { 0.0475f, 0.0475f, 0.2225f, 0.5140f, 0.8056f, 0.9805f, 1.0f });
+
+    private static final float TOLERANCE = 0.0001f;
+
+    @Mock
+    DisplayWhiteBalanceController mMockDwbc;
+
+    @Test
+    public void testSimpleStrategyMappingAtControlPoints() {
+        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        DisplayDeviceConfig ddc = createDdc();
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNotNull("BrightnessMappingStrategy should not be null", simple);
+        for (int i = 0; i < LUX_LEVELS.length; i++) {
+            final float expectedLevel = MathUtils.map(PowerManager.BRIGHTNESS_OFF + 1,
+                    PowerManager.BRIGHTNESS_ON, PowerManager.BRIGHTNESS_MIN,
+                    PowerManager.BRIGHTNESS_MAX, DISPLAY_LEVELS_BACKLIGHT[i]);
+            assertEquals(expectedLevel,
+                    simple.getBrightness(LUX_LEVELS[i]), 0.0001f /*tolerance*/);
+        }
+    }
+
+    @Test
+    public void testSimpleStrategyMappingBetweenControlPoints() {
+        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        DisplayDeviceConfig ddc = createDdc();
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNotNull("BrightnessMappingStrategy should not be null", simple);
+        for (int i = 1; i < LUX_LEVELS.length; i++) {
+            final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
+            final float backlight = simple.getBrightness(lux) * PowerManager.BRIGHTNESS_ON;
+            assertTrue("Desired brightness should be between adjacent control points.",
+                    backlight > DISPLAY_LEVELS_BACKLIGHT[i - 1]
+                            && backlight < DISPLAY_LEVELS_BACKLIGHT[i]);
+        }
+    }
+
+    @Test
+    public void testSimpleStrategyIgnoresNewConfiguration() {
+        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        DisplayDeviceConfig ddc = createDdc();
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+
+        final float[] lux = { 0f, 1f };
+        final float[] nits = { 0, PowerManager.BRIGHTNESS_ON };
+
+        BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
+                .build();
+        strategy.setBrightnessConfiguration(config);
+        assertNotEquals(1.0f, strategy.getBrightness(1f), 0.0001f /*tolerance*/);
+    }
+
+    @Test
+    public void testSimpleStrategyIgnoresNullConfiguration() {
+        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        DisplayDeviceConfig ddc = createDdc();
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+
+        strategy.setBrightnessConfiguration(null);
+        final int n = DISPLAY_LEVELS_BACKLIGHT.length;
+        final float expectedBrightness =
+                (float) DISPLAY_LEVELS_BACKLIGHT[n - 1] / PowerManager.BRIGHTNESS_ON;
+        assertEquals(expectedBrightness,
+                strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/);
+    }
+
+    @Test
+    public void testPhysicalStrategyMappingAtControlPoints() {
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT,
+                LUX_LEVELS, DISPLAY_LEVELS_NITS);
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNotNull("BrightnessMappingStrategy should not be null", physical);
+        for (int i = 0; i < LUX_LEVELS.length; i++) {
+            final float expectedLevel = MathUtils.map(DISPLAY_RANGE_NITS[0], DISPLAY_RANGE_NITS[1],
+                    DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT[0],
+                    DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT[1],
+                    DISPLAY_LEVELS_NITS[i]);
+            assertEquals(expectedLevel,
+                    physical.getBrightness(LUX_LEVELS[i]),
+                    0.0001f /*tolerance*/);
+        }
+    }
+
+    @Test
+    public void testPhysicalStrategyMappingBetweenControlPoints() {
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
+                LUX_LEVELS, DISPLAY_LEVELS_NITS);
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNotNull("BrightnessMappingStrategy should not be null", physical);
+        Spline brightnessToNits =
+                Spline.createSpline(BACKLIGHT_RANGE_ZERO_TO_ONE, DISPLAY_RANGE_NITS);
+        for (int i = 1; i < LUX_LEVELS.length; i++) {
+            final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2.0f;
+            final float brightness = physical.getBrightness(lux);
+            final float nits = brightnessToNits.interpolate(brightness);
+            assertTrue("Desired brightness should be between adjacent control points: " + nits,
+                    nits > DISPLAY_LEVELS_NITS[i - 1] && nits < DISPLAY_LEVELS_NITS[i]);
+        }
+    }
+
+    @Test
+    public void testPhysicalStrategyUsesNewConfigurations() {
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+
+        final float[] lux = {0f, 1f};
+        final float[] nits = {
+                DISPLAY_RANGE_NITS[0],
+                DISPLAY_RANGE_NITS[DISPLAY_RANGE_NITS.length - 1]
+        };
+
+        BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
+                .build();
+        strategy.setBrightnessConfiguration(config);
+        assertEquals(1.0f, strategy.getBrightness(1f), 0.0001f /*tolerance*/);
+
+        // Check that null returns us to the default configuration.
+        strategy.setBrightnessConfiguration(null);
+        final int n = DISPLAY_LEVELS_NITS.length;
+        final float expectedBrightness = DISPLAY_LEVELS_NITS[n - 1] / DISPLAY_RANGE_NITS[1];
+        assertEquals(expectedBrightness,
+                strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/);
+    }
+
+    @Test
+    public void testPhysicalStrategyRecalculateSplines() {
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length];
+        for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) {
+            adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f;
+        }
+
+        // Default
+        assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
+                TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
+                TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[0],
+                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[1],
+                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
+
+        // Adjustment is turned on
+        strategy.recalculateSplines(true, adjustedNits50p);
+        assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
+                TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
+                TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[0] / 2,
+                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[1] / 2,
+                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
+
+        // Adjustment is turned off
+        strategy.recalculateSplines(false, adjustedNits50p);
+        assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
+                TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
+                TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[0],
+                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
+        assertEquals(DISPLAY_RANGE_NITS[1],
+                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
+    }
+
+    @Test
+    public void testDefaultStrategyIsPhysical() {
+        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy);
+    }
+
+    @Test
+    public void testNonStrictlyIncreasingLuxLevelsFails() {
+        final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length);
+        final int idx = lux.length / 2;
+        float tmp = lux[idx];
+        lux[idx] = lux[idx + 1];
+        lux[idx + 1] = tmp;
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(strategy);
+
+        // And make sure we get the same result even if it's monotone but not increasing.
+        lux[idx] = lux[idx + 1];
+        ddc = createDdc(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux,
+                DISPLAY_LEVELS_NITS);
+        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(strategy);
+    }
+
+    @Test
+    public void testDifferentNumberOfControlPointValuesFails() {
+        //Extra lux level
+        final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length + 1);
+        // Make sure it's strictly increasing so that the only failure is the differing array
+        // lengths
+        lux[lux.length - 1] = lux[lux.length - 2] + 1;
+        Resources res = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(strategy);
+
+        res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(strategy);
+
+        // Extra backlight level
+        final int[] backlight = Arrays.copyOf(
+                DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length + 1);
+        backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
+        res = createResources(backlight);
+        ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, EMPTY_FLOAT_ARRAY);
+        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(strategy);
+
+        // Extra nits level
+        final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length + 1);
+        nits[nits.length - 1] = nits[nits.length - 2] + 1;
+        res = createResources(EMPTY_INT_ARRAY);
+        ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, nits);
+        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(strategy);
+    }
+
+    @Test
+    public void testPhysicalStrategyRequiresNitsMapping() {
+        Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
+        DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY /*nitsRange*/);
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(physical);
+
+        res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
+        physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(physical);
+
+        res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
+        physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
+        assertNull(physical);
+    }
+
+    @Test
+    public void testStrategiesAdaptToUserDataPoint() {
+        Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
+                LUX_LEVELS, DISPLAY_LEVELS_NITS);
+        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
+        ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
+        res = createResources(DISPLAY_LEVELS_BACKLIGHT);
+        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
+    }
+
+    @Test
+    public void testIdleModeConfigLoadsCorrectly() {
+        Resources res = createResourcesIdle(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
+
+        // Create an idle mode bms
+        // This will fail if it tries to fetch the wrong configuration.
+        BrightnessMappingStrategy bms = BrightnessMappingStrategy.createForIdleMode(res, ddc,
+                mMockDwbc);
+        assertNotNull("BrightnessMappingStrategy should not be null", bms);
+
+        // Ensure that the config is the one we set
+        // Ensure that the lux -> brightness -> nits path works. ()
+        for (int i = 0; i < DISPLAY_LEVELS_NITS_IDLE.length; i++) {
+            assertEquals(LUX_LEVELS_IDLE[i], bms.getDefaultConfig().getCurve().first[i], TOLERANCE);
+            assertEquals(DISPLAY_LEVELS_NITS_IDLE[i], bms.getDefaultConfig().getCurve().second[i],
+                    TOLERANCE);
+            assertEquals(bms.convertToNits(bms.getBrightness(LUX_LEVELS_IDLE[i])),
+                    DISPLAY_LEVELS_NITS_IDLE[i], TOLERANCE);
+        }
+    }
+
+    private static void assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy strategy) {
+        // Save out all of the initial brightness data for comparison after reset.
+        float[] initialBrightnessLevels = new float[LUX_LEVELS.length];
+        for (int i = 0; i < LUX_LEVELS.length; i++) {
+            initialBrightnessLevels[i] = strategy.getBrightness(LUX_LEVELS[i]);
+        }
+
+        // Add a data point in the middle of the curve where the user has set the brightness max
+        final int idx = LUX_LEVELS.length / 2;
+        strategy.addUserDataPoint(LUX_LEVELS[idx], 1.0f);
+
+        // Then make sure that all control points after the middle lux level are also set to max...
+        for (int i = idx; i < LUX_LEVELS.length; i++) {
+            assertEquals(strategy.getBrightness(LUX_LEVELS[idx]), 1.0, 0.0001f /*tolerance*/);
+        }
+
+        // ...and that all control points before the middle lux level are strictly less than the
+        // previous one still.
+        float prevBrightness = strategy.getBrightness(LUX_LEVELS[idx]);
+        for (int i = idx - 1; i >= 0; i--) {
+            float brightness = strategy.getBrightness(LUX_LEVELS[i]);
+            assertTrue("Brightness levels must be monotonic after adapting to user data",
+                    prevBrightness >= brightness);
+            prevBrightness = brightness;
+        }
+
+        // Now reset the curve and make sure we go back to the initial brightness levels recorded
+        // before adding the user data point.
+        strategy.clearUserDataPoints();
+        for (int i = 0; i < LUX_LEVELS.length; i++) {
+            assertEquals(initialBrightnessLevels[i], strategy.getBrightness(LUX_LEVELS[i]),
+                    0.0001f /*tolerance*/);
+        }
+
+        // Now set the middle of the lux range to something just above the minimum.
+        float minBrightness = strategy.getBrightness(LUX_LEVELS[0]);
+        strategy.addUserDataPoint(LUX_LEVELS[idx], minBrightness + 0.0001f);
+
+        // Then make sure the curve is still monotonic.
+        prevBrightness = 0f;
+        for (float lux : LUX_LEVELS) {
+            float brightness = strategy.getBrightness(lux);
+            assertTrue("Brightness levels must be monotonic after adapting to user data",
+                    prevBrightness <= brightness);
+            prevBrightness = brightness;
+        }
+
+        // And that the lowest lux level still gives the absolute minimum brightness. This should
+        // be true assuming that there are more than two lux levels in the curve since we picked a
+        // brightness just barely above the minimum for the middle of the curve.
+        minBrightness = (float) MathUtils.pow(minBrightness, MAXIMUM_GAMMA); // Gamma correction.
+        assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/);
+    }
+
+    private Resources createResources(int[] brightnessLevelsBacklight) {
+        return createResources(brightnessLevelsBacklight, EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
+    }
+
+    private Resources createResourcesIdle(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
+        return createResources(EMPTY_INT_ARRAY,
+                luxLevelsIdle, brightnessLevelsNitsIdle);
+    }
+
+    private Resources createResources(int[] brightnessLevelsBacklight, int[] luxLevelsIdle,
+            float[] brightnessLevelsNitsIdle) {
+
+        Resources mockResources = mock(Resources.class);
+        if (luxLevelsIdle.length > 0) {
+            int[] luxLevelsIdleResource = Arrays.copyOfRange(luxLevelsIdle, 1,
+                    luxLevelsIdle.length);
+            when(mockResources.getIntArray(
+                    com.android.internal.R.array.config_autoBrightnessLevelsIdle))
+                    .thenReturn(luxLevelsIdleResource);
+        }
+
+        when(mockResources.getIntArray(
+                com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
+                .thenReturn(brightnessLevelsBacklight);
+
+        TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle);
+        when(mockResources.obtainTypedArray(
+                com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle))
+                .thenReturn(mockBrightnessLevelNitsIdle);
+
+        when(mockResources.getInteger(
+                com.android.internal.R.integer.config_screenBrightnessSettingMinimum))
+                .thenReturn(1);
+        when(mockResources.getInteger(
+                com.android.internal.R.integer.config_screenBrightnessSettingMaximum))
+                .thenReturn(255);
+        when(mockResources.getFraction(
+                com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1))
+                .thenReturn(MAXIMUM_GAMMA);
+        return mockResources;
+    }
+
+    private DisplayDeviceConfig createDdc() {
+        return createDdc(DISPLAY_RANGE_NITS);
+    }
+
+    private DisplayDeviceConfig createDdc(float[] nitsArray) {
+        return createDdc(nitsArray, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT);
+    }
+
+    private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray) {
+        DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
+        when(mockDdc.getNits()).thenReturn(nitsArray);
+        when(mockDdc.getBrightness()).thenReturn(backlightArray);
+        when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS);
+        when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
+        return mockDdc;
+    }
+
+    private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray,
+            float[] luxLevelsFloat, float[] brightnessLevelsNits) {
+        DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
+        when(mockDdc.getNits()).thenReturn(nitsArray);
+        when(mockDdc.getBrightness()).thenReturn(backlightArray);
+        when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevelsFloat);
+        when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(brightnessLevelsNits);
+        return mockDdc;
+    }
+
+    private TypedArray createFloatTypedArray(float[] vals) {
+        TypedArray mockArray = mock(TypedArray.class);
+        when(mockArray.length()).thenAnswer(invocation -> {
+            return vals.length;
+        });
+        when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
+            final float def = (float) invocation.getArguments()[1];
+            if (vals == null) {
+                return def;
+            }
+            int idx = (int) invocation.getArguments()[0];
+            if (idx >= 0 && idx < vals.length) {
+                return vals[idx];
+            } else {
+                return def;
+            }
+        });
+        return mockArray;
+    }
+
+    // Gamma correction tests.
+    // x0 = 100   y0 = ~0.01
+    // x1 = 1000  y1 = ~0.20
+    // x2 = 2500  y2 = ~0.50
+    // x3 = 4000  y3 = ~0.80
+    // x4 = 4900  y4 = ~0.99
+
+    @Test
+    public void testGammaCorrectionLowChangeAtCenter() {
+        // If we set a user data point at (x2, y2^0.5), i.e. gamma = 0.5, it should bump the rest
+        // of the spline accordingly.
+        final int x1 = 1000;
+        final int x2 = 2500;
+        final int x3 = 4000;
+        final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
+        final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
+        final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
+
+        Resources resources = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
+                GAMMA_CORRECTION_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
+                mMockDwbc);
+        // Let's start with a validity check:
+        assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
+        assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
+        assertEquals(y3, strategy.getBrightness(x3), 0.0001f /* tolerance */);
+        // OK, let's roll:
+        float gamma = 0.5f;
+        strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma));
+        assertEquals(MathUtils.pow(y1, gamma), strategy.getBrightness(x1), 0.0001f /* tolerance */);
+        assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */);
+        assertEquals(MathUtils.pow(y3, gamma), strategy.getBrightness(x3), 0.0001f /* tolerance */);
+        // The adjustment should be +0.6308 (manual calculation).
+        assertEquals(+0.6308f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
+    }
+
+    @Test
+    public void testGammaCorrectionHighChangeAtCenter() {
+        // This time we set a user data point at (x2, y2^0.25), i.e. gamma = 0.3 (the minimum),
+        // which should bump the rest of the spline accordingly, and further correct x2 to hit
+        // y2^0.25 (not y2^0.3).
+        final int x1 = 1000;
+        final int x2 = 2500;
+        final int x3 = 4000;
+        final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
+        final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
+        final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
+        Resources resources = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
+                GAMMA_CORRECTION_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
+                mMockDwbc);
+        // Validity check:
+        assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
+        assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
+        assertEquals(y3, strategy.getBrightness(x3), 0.0001f /* tolerance */);
+        // Let's roll:
+        float gamma = 0.25f;
+        final float minGamma = 1.0f / MAXIMUM_GAMMA;
+        strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma));
+        assertEquals(MathUtils.pow(y1, minGamma),
+                strategy.getBrightness(x1), 0.0001f /* tolerance */);
+        assertEquals(MathUtils.pow(y2, gamma),
+                strategy.getBrightness(x2), 0.0001f /* tolerance */);
+        assertEquals(MathUtils.pow(y3, minGamma),
+                strategy.getBrightness(x3), 0.0001f /* tolerance */);
+        // The adjustment should be +1.0 (maximum adjustment).
+        assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
+    }
+
+    @Test
+    public void testGammaCorrectionExtremeChangeAtCenter() {
+        // Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we
+        // just make sure the adjustment reflects the change.
+        Resources resources = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
+                GAMMA_CORRECTION_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
+                mMockDwbc);
+        assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
+        strategy.addUserDataPoint(2500, 1.0f);
+        assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
+        strategy.addUserDataPoint(2500, 0.0f);
+        assertEquals(-1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
+    }
+
+    @Test
+    public void testGammaCorrectionChangeAtEdges() {
+        // The algorithm behaves differently at the edges, because gamma correction there tends to
+        // be extreme. If we add a user data point at (x0, y0+0.3), the adjustment should be 0.3,
+        // resulting in a gamma of 3**-0.6 = ~0.52.
+        final int x0 = 100;
+        final int x2 = 2500;
+        final int x4 = 4900;
+        final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0);
+        final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
+        final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4);
+        Resources resources = createResources(EMPTY_INT_ARRAY);
+        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
+                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
+                GAMMA_CORRECTION_NITS);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
+                mMockDwbc);
+        // Validity, as per tradition:
+        assertEquals(y0, strategy.getBrightness(x0), 0.0001f /* tolerance */);
+        assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
+        assertEquals(y4, strategy.getBrightness(x4), 0.0001f /* tolerance */);
+        // Rollin':
+        float adjustment = 0.3f;
+        float gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment);
+        strategy.addUserDataPoint(x0, y0 + adjustment);
+        assertEquals(y0 + adjustment, strategy.getBrightness(x0), 0.0001f /* tolerance */);
+        assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */);
+        assertEquals(MathUtils.pow(y4, gamma), strategy.getBrightness(x4), 0.0001f /* tolerance */);
+        assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
+        // Similarly, if we set a user data point at (x4, 1.0), the adjustment should be 1 - y4.
+        adjustment = 1.0f - y4;
+        gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment);
+        strategy.addUserDataPoint(x4, 1.0f);
+        assertEquals(MathUtils.pow(y0, gamma), strategy.getBrightness(x0), 0.0001f /* tolerance */);
+        assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */);
+        assertEquals(1.0f, strategy.getBrightness(x4), 0.0001f /* tolerance */);
+        assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/BrightnessSynchronizerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/BrightnessSynchronizerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
new file mode 100644
index 0000000..8faaf59
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -0,0 +1,493 @@
+/*
+ * 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.display;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.BrightnessInfo;
+import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Temperature;
+import android.os.Temperature.ThrottlingStatus;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.server.display.BrightnessThrottler.Injector;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.mode.DisplayModeDirectorTest;
+
+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 java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BrightnessThrottlerTest {
+    private static final float EPSILON = 0.000001f;
+
+    private Handler mHandler;
+    private TestLooper mTestLooper;
+
+    @Mock IThermalService mThermalServiceMock;
+    @Mock Injector mInjectorMock;
+
+    DisplayModeDirectorTest.FakeDeviceConfig mDeviceConfigFake;
+
+    @Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mInjectorMock.getThermalService()).thenReturn(mThermalServiceMock);
+        mTestLooper = new TestLooper();
+        mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() {
+            @Override
+            public boolean handleMessage(Message msg) {
+                return true;
+            }
+        });
+        mDeviceConfigFake = new DisplayModeDirectorTest.FakeDeviceConfig();
+        when(mInjectorMock.getDeviceConfig()).thenReturn(mDeviceConfigFake);
+
+    }
+
+    /////////////////
+    // Test Methods
+    /////////////////
+
+    @Test
+    public void testThermalBrightnessThrottlingData() {
+        List<ThrottlingLevel> singleLevel = new ArrayList<>();
+        singleLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
+
+        List<ThrottlingLevel> validLevels = new ArrayList<>();
+        validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f));
+        validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
+
+        List<ThrottlingLevel> unsortedThermalLevels = new ArrayList<>();
+        unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f));
+        unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f));
+
+        List<ThrottlingLevel> unsortedBrightnessLevels = new ArrayList<>();
+        unsortedBrightnessLevels.add(
+                new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f));
+        unsortedBrightnessLevels.add(
+                new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f));
+
+        List<ThrottlingLevel> unsortedLevels = new ArrayList<>();
+        unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
+        unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f));
+
+        List<ThrottlingLevel> invalidLevel = new ArrayList<>();
+        invalidLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+                PowerManager.BRIGHTNESS_MAX + EPSILON));
+
+        // Test invalid data
+        ThermalBrightnessThrottlingData data;
+        data = ThermalBrightnessThrottlingData.create((List<ThrottlingLevel>) null);
+        assertEquals(data, null);
+        data = ThermalBrightnessThrottlingData.create(new ArrayList<ThrottlingLevel>());
+        assertEquals(data, null);
+        data = ThermalBrightnessThrottlingData.create(unsortedThermalLevels);
+        assertEquals(data, null);
+        data = ThermalBrightnessThrottlingData.create(unsortedBrightnessLevels);
+        assertEquals(data, null);
+        data = ThermalBrightnessThrottlingData.create(unsortedLevels);
+        assertEquals(data, null);
+        data = ThermalBrightnessThrottlingData.create(invalidLevel);
+        assertEquals(data, null);
+
+        // Test valid data
+        data = ThermalBrightnessThrottlingData.create(singleLevel);
+        assertNotEquals(data, null);
+        assertThrottlingLevelsEquals(singleLevel, data.throttlingLevels);
+
+        data = ThermalBrightnessThrottlingData.create(validLevels);
+        assertNotEquals(data, null);
+        assertThrottlingLevelsEquals(validLevels, data.throttlingLevels);
+    }
+
+    @Test
+    public void testThermalThrottlingUnsupported() {
+        final BrightnessThrottler throttler = createThrottlerUnsupported();
+        assertFalse(throttler.deviceSupportsThrottling());
+
+        // Thermal listener shouldn't be registered if throttling is unsupported
+        verify(mInjectorMock, never()).getThermalService();
+
+        // Ensure that brightness is uncapped when the device doesn't support throttling
+        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+    }
+
+    @Test
+    public void testThermalThrottlingSingleLevel() throws Exception {
+        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+                0.25f);
+
+        List<ThrottlingLevel> levels = new ArrayList<>();
+        levels.add(level);
+        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
+        final BrightnessThrottler throttler = createThrottlerSupported(data);
+        assertTrue(throttler.deviceSupportsThrottling());
+
+        verify(mThermalServiceMock).registerThermalEventListenerWithType(
+                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+        // Set status too low to trigger throttling
+        listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+        assertFalse(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+
+        // Set status just high enough to trigger throttling
+        listener.notifyThrottling(getSkinTemp(level.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Set status more than high enough to trigger throttling
+        listener.notifyThrottling(getSkinTemp(level.thermalStatus + 1));
+        mTestLooper.dispatchAll();
+        assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Return to the lower throttling level
+        listener.notifyThrottling(getSkinTemp(level.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Cool down
+        listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+        assertFalse(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
+                throttler.getBrightnessMaxReason());
+    }
+
+    @Test
+    public void testThermalThrottlingMultiLevel() throws Exception {
+        final ThrottlingLevel levelLo = new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE,
+                0.62f);
+        final ThrottlingLevel levelHi = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+                0.25f);
+
+        List<ThrottlingLevel> levels = new ArrayList<>();
+        levels.add(levelLo);
+        levels.add(levelHi);
+        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
+        final BrightnessThrottler throttler = createThrottlerSupported(data);
+        assertTrue(throttler.deviceSupportsThrottling());
+
+        verify(mThermalServiceMock).registerThermalEventListenerWithType(
+                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+        // Set status too low to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+        assertFalse(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+
+        // Set status just high enough to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Set status to an intermediate throttling level
+        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1));
+        mTestLooper.dispatchAll();
+        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Set status to the highest configured throttling level
+        listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Set status to exceed the highest configured throttling level
+        listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus + 1));
+        mTestLooper.dispatchAll();
+        assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Return to an intermediate throttling level
+        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1));
+        mTestLooper.dispatchAll();
+        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Return to the lowest configured throttling level
+        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
+                throttler.getBrightnessMaxReason());
+
+        // Cool down
+        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+        assertFalse(throttler.isThrottled());
+        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
+    }
+
+    @Test public void testUpdateThermalThrottlingData() throws Exception {
+        // Initialise brightness throttling levels
+        // Ensure that they are overridden by setting the data through device config.
+        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+                0.25f);
+        List<ThrottlingLevel> levels = new ArrayList<>();
+        levels.add(level);
+        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,0.4");
+        final BrightnessThrottler throttler = createThrottlerSupported(data);
+
+        verify(mThermalServiceMock).registerThermalEventListenerWithType(
+                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.4f);
+
+        // Set new (valid) data from device config
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,0.8");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f);
+
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
+                "123,1,critical,0.75;123,1,critical,0.99,id_2");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.75f);
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
+                "123,1,critical,0.8,default;123,1,critical,0.99,id_2");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f);
+    }
+
+    @Test public void testInvalidThrottlingStrings() throws Exception {
+        // Initialise brightness throttling levels
+        // Ensure that they are not overridden by invalid data through device config.
+        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+                0.25f);
+        List<ThrottlingLevel> levels = new ArrayList<>();
+        levels.add(level);
+        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
+        final BrightnessThrottler throttler = createThrottlerSupported(data);
+        verify(mThermalServiceMock).registerThermalEventListenerWithType(
+                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+        // None of these are valid so shouldn't override the original data
+
+        // Not the current id
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("321,1,critical,0.4");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Incorrect number
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,0,critical,0.4");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Incorrect number
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,2,critical,0.4");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid level
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,invalid,0.4");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid brightness
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,none");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid brightness
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,-3");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid format
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("invalid string");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid format
+        mDeviceConfigFake.setThermalBrightnessThrottlingData("");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid string format
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
+                "123,default,1,critical,0.75,1,critical,0.99");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid level string and number string
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
+                "123,1,1,critical,0.75,id_2,1,critical,0.99");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+        // Invalid format - (two default ids for same display)
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
+                "123,1,critical,0.75,default;123,1,critical,0.99");
+        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+    }
+
+    private void testThermalThrottling(BrightnessThrottler throttler,
+            IThermalEventListener listener, float tooLowCap, float tooHighCap) throws Exception {
+        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+                tooHighCap);
+
+        // Set status too low to trigger throttling
+        listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(tooLowCap, throttler.getBrightnessCap(), 0f);
+        assertFalse(throttler.isThrottled());
+
+        // Set status high enough to trigger throttling
+        listener.notifyThrottling(getSkinTemp(level.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(tooHighCap, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+    }
+
+    @Test public void testMultipleConfigPoints() throws Exception {
+        // Initialise brightness throttling levels
+        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+                0.25f);
+        List<ThrottlingLevel> levels = new ArrayList<>();
+        levels.add(level);
+        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
+
+        // These are identical to the string set below
+        final ThrottlingLevel levelSevere = new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE,
+                0.9f);
+        final ThrottlingLevel levelCritical = new ThrottlingLevel(
+                PowerManager.THERMAL_STATUS_CRITICAL, 0.5f);
+        final ThrottlingLevel levelEmergency = new ThrottlingLevel(
+                PowerManager.THERMAL_STATUS_EMERGENCY, 0.1f);
+
+        mDeviceConfigFake.setThermalBrightnessThrottlingData(
+                "123,3,severe,0.9,critical,0.5,emergency,0.1");
+        final BrightnessThrottler throttler = createThrottlerSupported(data);
+
+        verify(mThermalServiceMock).registerThermalEventListenerWithType(
+                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+        // Ensure that the multiple levels set via the string through the device config correctly
+        // override the original display device config ones.
+
+        // levelSevere
+        // Set status too low to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelSevere.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+        assertFalse(throttler.isThrottled());
+
+        // Set status high enough to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelSevere.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(0.9f, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+
+        // levelCritical
+        // Set status too low to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelCritical.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(0.9f, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+
+        // Set status high enough to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelCritical.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(0.5f, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+
+        //levelEmergency
+        // Set status too low to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelEmergency.thermalStatus - 1));
+        mTestLooper.dispatchAll();
+        assertEquals(0.5f, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+
+        // Set status high enough to trigger throttling
+        listener.notifyThrottling(getSkinTemp(levelEmergency.thermalStatus));
+        mTestLooper.dispatchAll();
+        assertEquals(0.1f, throttler.getBrightnessCap(), 0f);
+        assertTrue(throttler.isThrottled());
+    }
+
+    private void assertThrottlingLevelsEquals(
+            List<ThrottlingLevel> expected,
+            List<ThrottlingLevel> actual) {
+        assertEquals(expected.size(), actual.size());
+
+        for (int i = 0; i < expected.size(); i++) {
+            ThrottlingLevel expectedLevel = expected.get(i);
+            ThrottlingLevel actualLevel = actual.get(i);
+
+            assertEquals(expectedLevel.thermalStatus, actualLevel.thermalStatus);
+            assertEquals(expectedLevel.brightness, actualLevel.brightness, 0.0f);
+        }
+    }
+
+    private BrightnessThrottler createThrottlerUnsupported() {
+        return new BrightnessThrottler(mInjectorMock, mHandler, mHandler,
+                /* throttlingChangeCallback= */ () -> {}, /* uniqueDisplayId= */ null,
+                /* thermalThrottlingDataId= */ null,
+                /* thermalThrottlingDataMap= */ new HashMap<>(1));
+    }
+
+    private BrightnessThrottler createThrottlerSupported(ThermalBrightnessThrottlingData data) {
+        assertNotNull(data);
+        HashMap<String, ThermalBrightnessThrottlingData> throttlingDataMap = new HashMap<>(1);
+        throttlingDataMap.put("default", data);
+        return new BrightnessThrottler(mInjectorMock, mHandler, BackgroundThread.getHandler(),
+                () -> {}, "123", "default", throttlingDataMap);
+    }
+
+    private Temperature getSkinTemp(@ThrottlingStatus int status) {
+        return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
new file mode 100644
index 0000000..44c7dec
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -0,0 +1,1247 @@
+/*
+ * Copyright 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.display;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+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;
+
+import android.app.ActivityTaskManager.RootTaskInfo;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ParceledListSlice;
+import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.display.AmbientBrightnessDayStats;
+import android.hardware.display.BrightnessChangeEvent;
+import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.ColorDisplayManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayedContentSample;
+import android.hardware.display.DisplayedContentSamplingAttributes;
+import android.hardware.input.InputSensorInfo;
+import android.os.BatteryManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.MessageQueue;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.AtomicFile;
+import android.view.Display;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BrightnessTrackerTest {
+    private static final float DEFAULT_INITIAL_BRIGHTNESS = 2.5f;
+    private static final boolean DEFAULT_COLOR_SAMPLING_ENABLED = true;
+    private static final String DEFAULT_DISPLAY_ID = "123";
+    private static final float FLOAT_DELTA = 0.01f;
+
+    @Mock private InputSensorInfo mInputSensorInfoMock;
+
+    private BrightnessTracker mTracker;
+    private TestInjector mInjector;
+    private Sensor mLightSensorFake;
+
+    private static Object sHandlerLock = new Object();
+    private static Handler sHandler;
+    private static HandlerThread sThread =
+            new HandlerThread("brightness.test", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+
+    private int mDefaultNightModeColorTemperature;
+    private float mRbcOffsetFactor;
+
+    private static Handler ensureHandler() {
+        synchronized (sHandlerLock) {
+            if (sHandler == null) {
+                sThread.start();
+                sHandler = new Handler(sThread.getLooper());
+            }
+            return sHandler;
+        }
+    }
+
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mInjector = new TestInjector(ensureHandler());
+        mLightSensorFake = new Sensor(mInputSensorInfoMock);
+
+        mTracker = new BrightnessTracker(InstrumentationRegistry.getContext(), mInjector);
+        mTracker.setLightSensor(mLightSensorFake);
+        mDefaultNightModeColorTemperature =
+                InstrumentationRegistry.getContext().getResources().getInteger(
+                R.integer.config_nightDisplayColorTemperatureDefault);
+        mRbcOffsetFactor = InstrumentationRegistry.getContext()
+                .getSystemService(ColorDisplayManager.class).getReduceBrightColorsOffsetFactor();
+    }
+
+    @Test
+    public void testStartStopTrackerScreenOnOff() {
+        mInjector.mInteractive = false;
+        startTracker(mTracker);
+        assertNull(mInjector.mSensorListener);
+        assertNotNull(mInjector.mBroadcastReceiver);
+        assertTrue(mInjector.mIdleScheduled);
+        mInjector.sendScreenChange(/* screenOn= */ true);
+        assertNotNull(mInjector.mSensorListener);
+        assertTrue(mInjector.mColorSamplingEnabled);
+
+        mInjector.sendScreenChange(/* screenOn= */ false);
+        assertNull(mInjector.mSensorListener);
+        assertFalse(mInjector.mColorSamplingEnabled);
+
+        // Turn screen on while brightness mode is manual
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
+        mInjector.sendScreenChange(/* screenOn= */ true);
+        assertNull(mInjector.mSensorListener);
+        assertFalse(mInjector.mColorSamplingEnabled);
+
+        // Set brightness mode to automatic while screen is off.
+        mInjector.sendScreenChange(/* screenOn= */ false);
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
+        assertNull(mInjector.mSensorListener);
+        assertFalse(mInjector.mColorSamplingEnabled);
+
+        // Turn on screen while brightness mode is automatic.
+        mInjector.sendScreenChange(/* screenOn= */ true);
+        assertNotNull(mInjector.mSensorListener);
+        assertTrue(mInjector.mColorSamplingEnabled);
+
+        mTracker.stop();
+        assertNull(mInjector.mSensorListener);
+        assertNull(mInjector.mBroadcastReceiver);
+        assertFalse(mInjector.mIdleScheduled);
+        assertFalse(mInjector.mColorSamplingEnabled);
+    }
+
+    @Test
+    public void testModifyBrightnessConfiguration() {
+        mInjector.mInteractive = true;
+        // Start with tracker not listening for color samples.
+        startTracker(mTracker, DEFAULT_INITIAL_BRIGHTNESS, /* collectColorSamples= */ false);
+        assertFalse(mInjector.mColorSamplingEnabled);
+
+        // Update brightness config to enabled color sampling.
+        mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true);
+        mInjector.waitForHandler();
+        assertTrue(mInjector.mColorSamplingEnabled);
+
+        // Update brightness config to disable color sampling.
+        mTracker.setShouldCollectColorSample(/* collectColorSamples= */ false);
+        mInjector.waitForHandler();
+        assertFalse(mInjector.mColorSamplingEnabled);
+
+        // Pretend screen is off, update config to turn on color sampling.
+        mInjector.sendScreenChange(/* screenOn= */ false);
+        mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true);
+        mInjector.waitForHandler();
+        assertFalse(mInjector.mColorSamplingEnabled);
+
+        // Pretend screen is on.
+        mInjector.sendScreenChange(/* screenOn= */ true);
+        assertTrue(mInjector.mColorSamplingEnabled);
+
+        mTracker.stop();
+        assertFalse(mInjector.mColorSamplingEnabled);
+    }
+
+    @Test
+    public void testNoColorSampling_WrongPixelFormat() {
+        mInjector.mDefaultSamplingAttributes =
+                new DisplayedContentSamplingAttributes(
+                        0x23,
+                        mInjector.mDefaultSamplingAttributes.getDataspace(),
+                        mInjector.mDefaultSamplingAttributes.getComponentMask());
+        startTracker(mTracker);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
+    }
+
+    @Test
+    public void testNoColorSampling_MissingComponent() {
+        mInjector.mDefaultSamplingAttributes =
+                new DisplayedContentSamplingAttributes(
+                        mInjector.mDefaultSamplingAttributes.getPixelFormat(),
+                        mInjector.mDefaultSamplingAttributes.getDataspace(),
+                        0x2);
+        startTracker(mTracker);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
+    }
+
+    @Test
+    public void testNoColorSampling_NoSupport() {
+        mInjector.mDefaultSamplingAttributes = null;
+        startTracker(mTracker);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
+    }
+
+    @Test
+    public void testColorSampling_FrameRateChange() {
+        startTracker(mTracker);
+        assertTrue(mInjector.mColorSamplingEnabled);
+        assertNotNull(mInjector.mDisplayListener);
+        int noFramesSampled = mInjector.mNoColorSamplingFrames;
+        mInjector.mFrameRate = 120.0f;
+        // Wrong display
+        mInjector.mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY + 10);
+        assertEquals(noFramesSampled, mInjector.mNoColorSamplingFrames);
+        // Correct display
+        mInjector.mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY);
+        assertEquals(noFramesSampled * 2, mInjector.mNoColorSamplingFrames);
+    }
+
+    @Test
+    public void testAdaptiveOnOff() {
+        mInjector.mInteractive = true;
+        mInjector.mIsBrightnessModeAutomatic = false;
+        startTracker(mTracker);
+        assertNull(mInjector.mSensorListener);
+        assertNotNull(mInjector.mBroadcastReceiver);
+        assertNotNull(mInjector.mContentObserver);
+        assertTrue(mInjector.mIdleScheduled);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
+
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
+        assertNotNull(mInjector.mSensorListener);
+        assertTrue(mInjector.mColorSamplingEnabled);
+        assertNotNull(mInjector.mDisplayListener);
+
+        SensorEventListener listener = mInjector.mSensorListener;
+        DisplayManager.DisplayListener displayListener = mInjector.mDisplayListener;
+        mInjector.mSensorListener = null;
+        mInjector.mColorSamplingEnabled = false;
+        mInjector.mDisplayListener = null;
+        // Duplicate notification
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
+        // Sensor shouldn't have been registered as it was already registered.
+        assertNull(mInjector.mSensorListener);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
+        mInjector.mDisplayListener = displayListener;
+        mInjector.mColorSamplingEnabled = true;
+
+        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
+        assertNull(mInjector.mSensorListener);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
+
+        mTracker.stop();
+        assertNull(mInjector.mSensorListener);
+        assertNull(mInjector.mBroadcastReceiver);
+        assertNull(mInjector.mContentObserver);
+        assertFalse(mInjector.mIdleScheduled);
+        assertFalse(mInjector.mColorSamplingEnabled);
+        assertNull(mInjector.mDisplayListener);
+    }
+
+    @Test
+    public void testBrightnessEvent() {
+        final float brightness = 0.5f;
+        final String displayId = "1234";
+
+        startTracker(mTracker);
+        final long sensorTime = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+        final long currentTime = mInjector.currentTimeMillis();
+        notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1.0f},
+                new long[] {sensorTime});
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+        mTracker.stop();
+
+        assertEquals(1, events.size());
+        BrightnessChangeEvent event = events.get(0);
+        assertEquals(currentTime, event.timeStamp);
+        assertEquals(displayId, event.uniqueDisplayId);
+        assertEquals(1, event.luxValues.length);
+        assertEquals(1.0f, event.luxValues[0], FLOAT_DELTA);
+        assertEquals(currentTime - TimeUnit.SECONDS.toMillis(2),
+                event.luxTimestamps[0]);
+        assertEquals(brightness, event.brightness, FLOAT_DELTA);
+        assertEquals(DEFAULT_INITIAL_BRIGHTNESS, event.lastBrightness, FLOAT_DELTA);
+
+        // System had no data so these should all be at defaults.
+        assertEquals(Float.NaN, event.batteryLevel, 0.0);
+        assertFalse(event.nightMode);
+        assertEquals(mDefaultNightModeColorTemperature, event.colorTemperature);
+    }
+
+    @Test
+    public void testMultipleBrightnessEvents() {
+        final float brightnessOne = 0.2f;
+        final float brightnessTwo = 0.4f;
+        final float brightnessThree = 0.6f;
+        final float brightnessFour = 0.3f;
+        final String displayId = "1234";
+        final float[] luxValues = new float[]{1.0f};
+
+        startTracker(mTracker);
+        final long sensorTime = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
+        final long sensorTime2 = sensorTime + TimeUnit.SECONDS.toMillis(20);
+        final long sensorTime3 = sensorTime2 + TimeUnit.SECONDS.toMillis(30);
+        final long sensorTime4 = sensorTime3 + TimeUnit.SECONDS.toMillis(40);
+        final long originalTime = mInjector.currentTimeMillis();
+
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+        notifyBrightnessChanged(mTracker, brightnessOne, displayId, luxValues,
+                new long[] {sensorTime});
+
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(20));
+        notifyBrightnessChanged(mTracker, brightnessTwo, displayId, luxValues,
+                new long[] {sensorTime2});
+
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(30));
+        notifyBrightnessChanged(mTracker, brightnessThree, displayId, luxValues,
+                new long[] {sensorTime3});
+
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(40));
+        notifyBrightnessChanged(mTracker, brightnessFour, displayId, luxValues,
+                new long[] {sensorTime4});
+        mTracker.stop();
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+        assertEquals(4, events.size());
+        BrightnessChangeEvent eventOne = events.get(0);
+        assertEquals(brightnessOne, eventOne.brightness, FLOAT_DELTA);
+        assertEquals(originalTime,
+                eventOne.luxTimestamps[0]);
+
+        BrightnessChangeEvent eventTwo = events.get(1);
+        assertEquals(brightnessTwo, eventTwo.brightness, FLOAT_DELTA);
+        assertEquals(originalTime + TimeUnit.SECONDS.toMillis(20),
+                eventTwo.luxTimestamps[0]);
+
+        BrightnessChangeEvent eventThree = events.get(2);
+        assertEquals(brightnessThree, eventThree.brightness, FLOAT_DELTA);
+        assertEquals(originalTime + TimeUnit.SECONDS.toMillis(50),
+                eventThree.luxTimestamps[0]);
+
+        BrightnessChangeEvent eventFour = events.get(3);
+        assertEquals(brightnessFour, eventFour.brightness, FLOAT_DELTA);
+        assertEquals(originalTime + TimeUnit.SECONDS.toMillis(90),
+                eventFour.luxTimestamps[0]);
+    }
+
+    @Test
+    public void testBrightnessFullPopulatedEvent() {
+        final int initialBrightness = 230;
+        final int brightness = 130;
+        final String displayId = "1234";
+
+        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
+        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3333);
+
+        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40);
+
+        startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED);
+        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
+                batteryChangeEvent(30, 60));
+        final long currentTime = mInjector.currentTimeMillis();
+        notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1000.0f},
+                new long[] {TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos())});
+        List<BrightnessChangeEvent> eventsNoPackage =
+                mTracker.getEvents(0, false).getList();
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+        mTracker.stop();
+
+        assertEquals(1, events.size());
+        BrightnessChangeEvent event = events.get(0);
+        assertEquals(event.timeStamp, currentTime);
+        assertEquals(displayId, event.uniqueDisplayId);
+        assertArrayEquals(new float[] {1000.0f}, event.luxValues, FLOAT_DELTA);
+        assertArrayEquals(new long[] {currentTime}, event.luxTimestamps);
+        assertEquals(brightness, event.brightness, FLOAT_DELTA);
+        assertEquals(initialBrightness, event.lastBrightness, FLOAT_DELTA);
+        assertEquals(0.5, event.batteryLevel, FLOAT_DELTA);
+        assertTrue(event.nightMode);
+        assertEquals(3333, event.colorTemperature);
+        assertTrue(event.reduceBrightColors);
+        assertEquals(40, event.reduceBrightColorsStrength);
+        assertEquals(brightness * mRbcOffsetFactor, event.reduceBrightColorsOffset, FLOAT_DELTA);
+        assertEquals("a.package", event.packageName);
+        assertEquals(0, event.userId);
+        assertArrayEquals(new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, event.colorValueBuckets);
+        assertEquals(10000, event.colorSampleDuration);
+
+        assertEquals(1, eventsNoPackage.size());
+        assertNull(eventsNoPackage.get(0).packageName);
+    }
+
+    @Test
+    public void testIgnoreAutomaticBrightnessChange() {
+        final int initialBrightness = 30;
+        startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED);
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
+
+        final int systemUpdatedBrightness = 20;
+        notifyBrightnessChanged(mTracker, systemUpdatedBrightness, /* userInitiated= */ false,
+                /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ false,
+                /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID);
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+        // No events because we filtered out our change.
+        assertEquals(0, events.size());
+
+        final int firstUserUpdateBrightness = 20;
+        // Then change comes from somewhere else so we shouldn't filter.
+        notifyBrightnessChanged(mTracker, firstUserUpdateBrightness);
+
+        // and with a different brightness value.
+        final int secondUserUpdateBrightness = 34;
+        notifyBrightnessChanged(mTracker, secondUserUpdateBrightness);
+        events = mTracker.getEvents(0, true).getList();
+
+        assertEquals(2, events.size());
+        // First event is change from system update (20) to first user update (20)
+        assertEquals(systemUpdatedBrightness, events.get(0).lastBrightness, FLOAT_DELTA);
+        assertEquals(firstUserUpdateBrightness, events.get(0).brightness, FLOAT_DELTA);
+        // Second event is from first to second user update.
+        assertEquals(firstUserUpdateBrightness, events.get(1).lastBrightness, FLOAT_DELTA);
+        assertEquals(secondUserUpdateBrightness, events.get(1).brightness, FLOAT_DELTA);
+
+        mTracker.stop();
+    }
+
+    @Test
+    public void testLimitedBufferSize() {
+        startTracker(mTracker);
+
+        for (int brightness = 0; brightness <= 255; ++brightness) {
+            mInjector.incrementTime(TimeUnit.SECONDS.toNanos(1));
+            notifyBrightnessChanged(mTracker, brightness);
+        }
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+        mTracker.stop();
+
+        // Should be capped at 100 events, and they should be the most recent 100.
+        assertEquals(100, events.size());
+        for (int i = 0; i < events.size(); i++) {
+            BrightnessChangeEvent event = events.get(i);
+            assertEquals(156 + i, event.brightness, FLOAT_DELTA);
+        }
+    }
+
+    @Test
+    public void testReadEvents() throws Exception {
+        BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
+                mInjector);
+        mInjector.mCurrentTimeMillis = System.currentTimeMillis();
+        long someTimeAgo = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(12);
+        long twoMonthsAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60);
+        // 3 Events in the file but one too old to read.
+        String eventFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<events>\n"
+                + "<event nits=\"194.2\" timestamp=\""
+                + Long.toString(someTimeAgo) + "\" packageName=\""
+                + "com.example.app\" user=\"10\" "
+                + "lastNits=\"32.333\" "
+                + "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\" "
+                + "reduceBrightColors=\"false\" reduceBrightColorsStrength=\"40\" "
+                + "reduceBrightColorsOffset=\"0\"\n"
+                + "uniqueDisplayId=\"123\""
+                + "lux=\"32.2,31.1\" luxTimestamps=\""
+                + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\""
+                + "defaultConfig=\"true\" powerSaveFactor=\"0.5\" userPoint=\"true\" />"
+                + "<event nits=\"71\" timestamp=\""
+                + Long.toString(someTimeAgo) + "\" packageName=\""
+                + "com.android.anapp\" user=\"11\" "
+                + "lastNits=\"32\" "
+                + "batteryLevel=\"0.5\" nightMode=\"true\" colorTemperature=\"3235\" "
+                + "reduceBrightColors=\"true\" reduceBrightColorsStrength=\"40\" "
+                + "reduceBrightColorsOffset=\"0\"\n"
+                + "uniqueDisplayId=\"456\""
+                + "lux=\"132.2,131.1\" luxTimestamps=\""
+                + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\""
+                + "colorSampleDuration=\"3456\" colorValueBuckets=\"123,598,23,19\"/>"
+                // Event that is too old so shouldn't show up.
+                + "<event nits=\"142\" timestamp=\""
+                + Long.toString(twoMonthsAgo) + "\" packageName=\""
+                + "com.example.app\" user=\"10\" "
+                + "lastNits=\"32\" "
+                + "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\" "
+                + "reduceBrightColors=\"false\" reduceBrightColorsStrength=\"40\" "
+                + "reduceBrightColorsOffset=\"0\"\n"
+                + "uniqueDisplayId=\"789\""
+                + "lux=\"32.2,31.1\" luxTimestamps=\""
+                + Long.toString(twoMonthsAgo) + "," + Long.toString(twoMonthsAgo) + "\"/>"
+                + "</events>";
+        tracker.readEventsLocked(getInputStream(eventFile));
+        List<BrightnessChangeEvent> events = tracker.getEvents(0, true).getList();
+        assertEquals(1, events.size());
+        BrightnessChangeEvent event = events.get(0);
+        assertEquals(someTimeAgo, event.timeStamp);
+        assertEquals(194.2, event.brightness, FLOAT_DELTA);
+        assertEquals("123", event.uniqueDisplayId);
+        assertArrayEquals(new float[] {32.2f, 31.1f}, event.luxValues, FLOAT_DELTA);
+        assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps);
+        assertEquals(32.333, event.lastBrightness, FLOAT_DELTA);
+        assertEquals(0, event.userId);
+        assertFalse(event.nightMode);
+        assertFalse(event.reduceBrightColors);
+        assertEquals(1.0f, event.batteryLevel, FLOAT_DELTA);
+        assertEquals("com.example.app", event.packageName);
+        assertTrue(event.isDefaultBrightnessConfig);
+        assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA);
+        assertTrue(event.isUserSetBrightness);
+        assertNull(event.colorValueBuckets);
+
+        events = tracker.getEvents(1, true).getList();
+        assertEquals(1, events.size());
+        event = events.get(0);
+        assertEquals(someTimeAgo, event.timeStamp);
+        assertEquals(71, event.brightness, FLOAT_DELTA);
+        assertEquals("456", event.uniqueDisplayId);
+        assertArrayEquals(new float[] {132.2f, 131.1f}, event.luxValues, FLOAT_DELTA);
+        assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps);
+        assertEquals(32, event.lastBrightness, FLOAT_DELTA);
+        assertEquals(1, event.userId);
+        assertTrue(event.nightMode);
+        assertEquals(3235, event.colorTemperature);
+        assertTrue(event.reduceBrightColors);
+        assertEquals(0.5f, event.batteryLevel, FLOAT_DELTA);
+        assertEquals("com.android.anapp", event.packageName);
+        // Not present in the event so default to false.
+        assertFalse(event.isDefaultBrightnessConfig);
+        assertEquals(1.0, event.powerBrightnessFactor, FLOAT_DELTA);
+        assertFalse(event.isUserSetBrightness);
+        assertEquals(3456L, event.colorSampleDuration);
+        assertArrayEquals(new long[] {123L, 598L, 23L, 19L}, event.colorValueBuckets);
+
+        // Pretend user 1 is a profile of user 0.
+        mInjector.mProfiles = new int[]{0, 1};
+        events = tracker.getEvents(0, true).getList();
+        // Both events should now be returned.
+        assertEquals(2, events.size());
+        BrightnessChangeEvent userZeroEvent;
+        BrightnessChangeEvent userOneEvent;
+        if (events.get(0).userId == 0) {
+            userZeroEvent = events.get(0);
+            userOneEvent = events.get(1);
+        } else {
+            userZeroEvent = events.get(1);
+            userOneEvent = events.get(0);
+        }
+        assertEquals(0, userZeroEvent.userId);
+        assertEquals("com.example.app", userZeroEvent.packageName);
+        assertEquals(1, userOneEvent.userId);
+        // Events from user 1 should have the package name redacted
+        assertNull(userOneEvent.packageName);
+    }
+
+    @Test
+    public void testFailedRead() {
+        String someTimeAgo =
+                Long.toString(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(12));
+        mInjector.mCurrentTimeMillis = System.currentTimeMillis();
+
+        BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
+                mInjector);
+        String eventFile = "junk in the file";
+        try {
+            tracker.readEventsLocked(getInputStream(eventFile));
+        } catch (IOException e) {
+            // Expected;
+        }
+        assertEquals(0, tracker.getEvents(0, true).getList().size());
+
+        // Missing lux value.
+        eventFile =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                        + "<events>\n"
+                        + "<event nits=\"194\" timestamp=\"" + someTimeAgo + "\" packageName=\""
+                        + "com.example.app\" user=\"10\" "
+                        + "batteryLevel=\"0.7\" nightMode=\"false\" colorTemperature=\"0\" />\n"
+                        + "</events>";
+        try {
+            tracker.readEventsLocked(getInputStream(eventFile));
+        } catch (IOException e) {
+            // Expected;
+        }
+        assertEquals(0, tracker.getEvents(0, true).getList().size());
+    }
+
+    @Test
+    public void testWriteThenRead() throws Exception {
+        final int brightness = 20;
+        final String displayId = "1234";
+
+        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
+        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
+
+        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40);
+
+        startTracker(mTracker);
+        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
+                batteryChangeEvent(30, 100));
+        final long elapsedTime1 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
+        final long currentTime1 = mInjector.currentTimeMillis();
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+        final long elapsedTime2 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
+        final long currentTime2 = mInjector.currentTimeMillis();
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(3));
+        notifyBrightnessChanged(mTracker, brightness, /* userInitiated= */ true,
+                /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ true,
+                /* isDefaultBrightnessConfig= */ false, displayId, new float[] {2000.0f, 3000.0f},
+                new long[] {elapsedTime1, elapsedTime2});
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mTracker.writeEventsLocked(baos);
+        mTracker.stop();
+
+        baos.flush();
+        ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray());
+        BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
+                mInjector);
+        tracker.readEventsLocked(input);
+        List<BrightnessChangeEvent> events = tracker.getEvents(0, true).getList();
+
+        assertEquals(1, events.size());
+        BrightnessChangeEvent event = events.get(0);
+        assertEquals(displayId, event.uniqueDisplayId);
+        assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA);
+        assertArrayEquals(new long[] {currentTime1, currentTime2}, event.luxTimestamps);
+        assertEquals(brightness, event.brightness, FLOAT_DELTA);
+        assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
+        assertTrue(event.nightMode);
+        assertEquals(3339, event.colorTemperature);
+        assertTrue(event.reduceBrightColors);
+        assertEquals(40, event.reduceBrightColorsStrength);
+        assertEquals(brightness * mRbcOffsetFactor, event.reduceBrightColorsOffset, FLOAT_DELTA);
+        assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA);
+        assertTrue(event.isUserSetBrightness);
+        assertFalse(event.isDefaultBrightnessConfig);
+        assertArrayEquals(new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, event.colorValueBuckets);
+        assertEquals(10000, event.colorSampleDuration);
+    }
+
+    @Test
+    public void testParcelUnParcel() {
+        Parcel parcel = Parcel.obtain();
+        BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder();
+        builder.setBrightness(23f);
+        builder.setTimeStamp(345L);
+        builder.setPackageName("com.example");
+        builder.setUserId(12);
+        builder.setUniqueDisplayId("9876");
+        float[] luxValues = new float[2];
+        luxValues[0] = 3000.0f;
+        luxValues[1] = 4000.0f;
+        builder.setLuxValues(luxValues);
+        long[] luxTimestamps = new long[2];
+        luxTimestamps[0] = 325L;
+        luxTimestamps[1] = 315L;
+        builder.setLuxTimestamps(luxTimestamps);
+        builder.setBatteryLevel(0.7f);
+        builder.setNightMode(false);
+        builder.setColorTemperature(345);
+        builder.setReduceBrightColors(false);
+        builder.setReduceBrightColorsStrength(40);
+        builder.setReduceBrightColorsOffset(20f);
+        builder.setLastBrightness(50f);
+        builder.setColorValues(new long[] {23, 34, 45}, 1000L);
+        BrightnessChangeEvent event = builder.build();
+
+        event.writeToParcel(parcel, 0);
+        byte[] parceled = parcel.marshall();
+        parcel.recycle();
+
+        parcel = Parcel.obtain();
+        parcel.unmarshall(parceled, 0, parceled.length);
+        parcel.setDataPosition(0);
+
+        BrightnessChangeEvent event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+        assertEquals(event.brightness, event2.brightness, FLOAT_DELTA);
+        assertEquals(event.timeStamp, event2.timeStamp);
+        assertEquals(event.packageName, event2.packageName);
+        assertEquals(event.userId, event2.userId);
+        assertEquals(event.uniqueDisplayId, event2.uniqueDisplayId);
+        assertArrayEquals(event.luxValues, event2.luxValues, FLOAT_DELTA);
+        assertArrayEquals(event.luxTimestamps, event2.luxTimestamps);
+        assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA);
+        assertEquals(event.nightMode, event2.nightMode);
+        assertEquals(event.colorTemperature, event2.colorTemperature);
+        assertEquals(event.reduceBrightColors, event2.reduceBrightColors);
+        assertEquals(event.reduceBrightColorsStrength, event2.reduceBrightColorsStrength);
+        assertEquals(event.reduceBrightColorsOffset, event2.reduceBrightColorsOffset, FLOAT_DELTA);
+        assertEquals(event.lastBrightness, event2.lastBrightness, FLOAT_DELTA);
+        assertArrayEquals(event.colorValueBuckets, event2.colorValueBuckets);
+        assertEquals(event.colorSampleDuration, event2.colorSampleDuration);
+
+        parcel = Parcel.obtain();
+        builder.setBatteryLevel(Float.NaN);
+        event = builder.build();
+        event.writeToParcel(parcel, 0);
+        parceled = parcel.marshall();
+        parcel.recycle();
+
+        parcel = Parcel.obtain();
+        parcel.unmarshall(parceled, 0, parceled.length);
+        parcel.setDataPosition(0);
+        event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel);
+        assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA);
+    }
+
+    @Test
+    public void testNonNullAmbientStats() {
+        // getAmbientBrightnessStats should return an empty list rather than null when
+        // tracker isn't started or hasn't collected any data.
+        ParceledListSlice<AmbientBrightnessDayStats> slice = mTracker.getAmbientBrightnessStats(0);
+        assertNotNull(slice);
+        assertTrue(slice.getList().isEmpty());
+        startTracker(mTracker);
+        slice = mTracker.getAmbientBrightnessStats(0);
+        assertNotNull(slice);
+        assertTrue(slice.getList().isEmpty());
+    }
+
+    @Test
+    public void testBackgroundHandlerDelay() {
+        final int brightness = 20;
+
+        // Setup tracker.
+        startTracker(mTracker);
+        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+
+        // Block handler from running.
+        final CountDownLatch latch = new CountDownLatch(1);
+        mInjector.mHandler.post(
+                () -> {
+                    try {
+                        latch.await();
+                    } catch (InterruptedException e) {
+                        fail(e.getMessage());
+                    }
+                });
+
+        // Send an event.
+        long eventTime = mInjector.currentTimeMillis();
+        mTracker.notifyBrightnessChanged(brightness, /* userInitiated= */ true,
+                /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
+                /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID, new float[10],
+                new long[10]);
+
+        // Time passes before handler can run.
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+
+        // Let the handler run.
+        latch.countDown();
+        mInjector.waitForHandler();
+
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+        mTracker.stop();
+
+        // Check event was recorded with time it was sent rather than handler ran.
+        assertEquals(1, events.size());
+        BrightnessChangeEvent event = events.get(0);
+        assertEquals(eventTime, event.timeStamp);
+    }
+
+    @Test
+    public void testDisplayIdChange() {
+        float firstBrightness = 0.5f;
+        float secondBrightness = 0.75f;
+        String firstDisplayId = "123";
+        String secondDisplayId = "456";
+
+        startTracker(mTracker);
+        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
+
+        notifyBrightnessChanged(mTracker, firstBrightness, firstDisplayId);
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
+        assertEquals(1, events.size());
+        BrightnessChangeEvent firstEvent = events.get(0);
+        assertEquals(firstDisplayId, firstEvent.uniqueDisplayId);
+        assertEquals(firstBrightness, firstEvent.brightness, 0.001f);
+
+        notifyBrightnessChanged(mTracker, secondBrightness, secondDisplayId);
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
+        events = mTracker.getEvents(0, true).getList();
+        assertEquals(2, events.size());
+        BrightnessChangeEvent secondEvent = events.get(1);
+        assertEquals(secondDisplayId, secondEvent.uniqueDisplayId);
+        assertEquals(secondBrightness, secondEvent.brightness, 0.001f);
+
+        mTracker.stop();
+    }
+
+    @Test
+    public void testLightSensorChange() {
+        // verify the tracker started correctly and a listener registered
+        startTracker(mTracker);
+        assertNotNull(mInjector.mSensorListener);
+        assertEquals(mInjector.mLightSensor, mLightSensorFake);
+
+        // Setting the sensor to null should stop the registered listener.
+        mTracker.setLightSensor(null);
+        mInjector.waitForHandler();
+        assertNull(mInjector.mSensorListener);
+        assertNull(mInjector.mLightSensor);
+
+        // Resetting sensor should start listener again
+        mTracker.setLightSensor(mLightSensorFake);
+        mInjector.waitForHandler();
+        assertNotNull(mInjector.mSensorListener);
+        assertEquals(mInjector.mLightSensor, mLightSensorFake);
+
+        Sensor secondSensor = new Sensor(mInputSensorInfoMock);
+        // Setting a different listener should keep things working
+        mTracker.setLightSensor(secondSensor);
+        mInjector.waitForHandler();
+        assertNotNull(mInjector.mSensorListener);
+        assertEquals(mInjector.mLightSensor, secondSensor);
+    }
+
+    @Test
+    public void testSetLightSensorDoesntStartListener() {
+        mTracker.setLightSensor(mLightSensorFake);
+        assertNull(mInjector.mSensorListener);
+    }
+
+    @Test
+    public void testNullLightSensorWontRegister() {
+        mTracker.setLightSensor(null);
+        startTracker(mTracker);
+        assertNull(mInjector.mSensorListener);
+        assertNull(mInjector.mLightSensor);
+    }
+
+    @Test
+    public void testOnlyOneReceiverRegistered() {
+        assertNull(mInjector.mLightSensor);
+        assertNull(mInjector.mSensorListener);
+        assertNull(mInjector.mContentObserver);
+        assertNull(mInjector.mBroadcastReceiver);
+        assertFalse(mInjector.mIdleScheduled);
+        startTracker(mTracker, 0.3f, false);
+
+        assertNotNull(mInjector.mLightSensor);
+        assertNotNull(mInjector.mSensorListener);
+        assertNotNull(mInjector.mContentObserver);
+        assertNotNull(mInjector.mBroadcastReceiver);
+        assertTrue(mInjector.mIdleScheduled);
+        Sensor registeredLightSensor = mInjector.mLightSensor;
+        SensorEventListener registeredSensorListener = mInjector.mSensorListener;
+        ContentObserver registeredContentObserver = mInjector.mContentObserver;
+        BroadcastReceiver registeredBroadcastReceiver = mInjector.mBroadcastReceiver;
+
+        mTracker.start(0.3f);
+        assertSame(registeredLightSensor, mInjector.mLightSensor);
+        assertSame(registeredSensorListener, mInjector.mSensorListener);
+        assertSame(registeredContentObserver, mInjector.mContentObserver);
+        assertSame(registeredBroadcastReceiver, mInjector.mBroadcastReceiver);
+
+        mTracker.stop();
+        assertNull(mInjector.mLightSensor);
+        assertNull(mInjector.mSensorListener);
+        assertNull(mInjector.mContentObserver);
+        assertNull(mInjector.mBroadcastReceiver);
+        assertFalse(mInjector.mIdleScheduled);
+
+        // 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));
+    }
+
+    private Intent batteryChangeEvent(int level, int scale) {
+        Intent intent = new Intent();
+        intent.setAction(Intent.ACTION_BATTERY_CHANGED);
+        intent.putExtra(BatteryManager.EXTRA_LEVEL, level);
+        intent.putExtra(BatteryManager.EXTRA_SCALE, scale);
+        return intent;
+    }
+
+    private SensorEvent createSensorEvent(float lux) {
+        SensorEvent event;
+        try {
+            Constructor<SensorEvent> constr =
+                    SensorEvent.class.getDeclaredConstructor(Integer.TYPE);
+            constr.setAccessible(true);
+            event = constr.newInstance(1);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        event.values[0] = lux;
+        event.timestamp = mInjector.mElapsedRealtimeNanos;
+
+        return event;
+    }
+
+    private void startTracker(BrightnessTracker tracker) {
+        startTracker(tracker, DEFAULT_INITIAL_BRIGHTNESS,  DEFAULT_COLOR_SAMPLING_ENABLED);
+    }
+
+    private void startTracker(BrightnessTracker tracker, float initialBrightness,
+            boolean collectColorSamples) {
+        tracker.start(initialBrightness);
+        tracker.setShouldCollectColorSample(collectColorSamples);
+        mInjector.waitForHandler();
+    }
+
+    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness) {
+        notifyBrightnessChanged(tracker, brightness, DEFAULT_DISPLAY_ID);
+    }
+
+    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+            String displayId) {
+        notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true,
+                /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
+                /* isDefaultBrightnessConfig= */ false, displayId, new float[10], new long[10]);
+    }
+
+    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+            String displayId, float[] luxValues, long[] luxTimestamps) {
+        notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true,
+                /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
+                /* isDefaultBrightnessConfig= */ false, displayId, luxValues, luxTimestamps);
+    }
+
+    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+            boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
+            boolean isDefaultBrightnessConfig, String displayId) {
+        tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
+                isUserSetBrightness, isDefaultBrightnessConfig, displayId, new float[10],
+                new long[10]);
+        mInjector.waitForHandler();
+    }
+
+    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+            boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
+            boolean isDefaultBrightnessConfig, String displayId, float[] luxValues,
+            long[] luxTimestamps) {
+        tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
+                isUserSetBrightness, isDefaultBrightnessConfig, displayId, luxValues,
+                luxTimestamps);
+        mInjector.waitForHandler();
+    }
+
+    private BrightnessConfiguration buildBrightnessConfiguration(boolean collectColorSamples) {
+        BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(
+                /* lux= */ new float[] {0f, 10f, 100f},
+                /* nits= */ new float[] {1f, 90f, 100f});
+        builder.setShouldCollectColorSamples(collectColorSamples);
+        return builder.build();
+    }
+
+    private static final class Idle implements MessageQueue.IdleHandler {
+        private boolean mIdle;
+
+        @Override
+        public boolean queueIdle() {
+            synchronized (this) {
+                mIdle = true;
+                notifyAll();
+            }
+            return false;
+        }
+
+        public synchronized void waitForIdle() {
+            while (!mIdle) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+
+    private class TestInjector extends BrightnessTracker.Injector {
+        SensorEventListener mSensorListener;
+        Sensor mLightSensor;
+        BroadcastReceiver mBroadcastReceiver;
+        DisplayManager.DisplayListener mDisplayListener;
+        Map<String, Integer> mSecureIntSettings = new HashMap<>();
+        long mCurrentTimeMillis = System.currentTimeMillis();
+        long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
+        Handler mHandler;
+        boolean mIdleScheduled;
+        boolean mInteractive = true;
+        int[] mProfiles;
+        ContentObserver mContentObserver;
+        boolean mIsBrightnessModeAutomatic = true;
+        boolean mColorSamplingEnabled = false;
+        DisplayedContentSamplingAttributes mDefaultSamplingAttributes =
+                new DisplayedContentSamplingAttributes(0x37, 0, 0x4);
+        float mFrameRate = 60.0f;
+        int mNoColorSamplingFrames;
+
+
+        public TestInjector(Handler handler) {
+            mHandler = handler;
+        }
+
+        void incrementTime(long timeMillis) {
+            mCurrentTimeMillis += timeMillis;
+            mElapsedRealtimeNanos += TimeUnit.MILLISECONDS.toNanos(timeMillis);
+        }
+
+        void setBrightnessMode(boolean isBrightnessModeAutomatic) {
+            mIsBrightnessModeAutomatic = isBrightnessModeAutomatic;
+            mContentObserver.dispatchChange(false, null);
+            waitForHandler();
+        }
+
+        void sendScreenChange(boolean screenOn) {
+            mInteractive = screenOn;
+            Intent intent = new Intent();
+            intent.setAction(screenOn ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
+            mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(), intent);
+            waitForHandler();
+        }
+
+        void waitForHandler() {
+            Idle idle = new Idle();
+            mHandler.getLooper().getQueue().addIdleHandler(idle);
+            mHandler.post(() -> {});
+            idle.waitForIdle();
+        }
+
+        @Override
+        public void registerSensorListener(Context context,
+                SensorEventListener sensorListener, Sensor lightSensor, Handler handler) {
+            mSensorListener = sensorListener;
+            mLightSensor = lightSensor;
+        }
+
+        @Override
+        public void unregisterSensorListener(Context context,
+                SensorEventListener sensorListener) {
+            mSensorListener = null;
+            mLightSensor = null;
+        }
+
+        @Override
+        public void registerBrightnessModeObserver(ContentResolver resolver,
+                ContentObserver settingsObserver) {
+            mContentObserver = settingsObserver;
+        }
+
+        @Override
+        public void unregisterBrightnessModeObserver(Context context,
+                ContentObserver settingsObserver) {
+            mContentObserver = null;
+        }
+
+        @Override
+        public void registerReceiver(Context context,
+                BroadcastReceiver shutdownReceiver, IntentFilter shutdownFilter) {
+            mBroadcastReceiver = shutdownReceiver;
+        }
+
+        @Override
+        public void unregisterReceiver(Context context,
+                BroadcastReceiver broadcastReceiver) {
+            assertEquals(mBroadcastReceiver, broadcastReceiver);
+            mBroadcastReceiver = null;
+        }
+
+        @Override
+        public Handler getBackgroundHandler() {
+            return mHandler;
+        }
+
+        @Override
+        public boolean isBrightnessModeAutomatic(ContentResolver resolver) {
+            return mIsBrightnessModeAutomatic;
+        }
+
+        @Override
+        public int getSecureIntForUser(ContentResolver resolver, String setting, int defaultValue,
+                int userId) {
+            Integer value = mSecureIntSettings.get(setting);
+            if (value == null) {
+                return defaultValue;
+            } else {
+                return value;
+            }
+        }
+
+        @Override
+        public AtomicFile getFile(String filename) {
+            // Don't have the test write / read from anywhere.
+            return null;
+        }
+
+        @Override
+        public AtomicFile getLegacyFile(String filename) {
+            // Don't have the test write / read from anywhere.
+            return null;
+        }
+
+        @Override
+        public long currentTimeMillis() {
+            return mCurrentTimeMillis;
+        }
+
+        @Override
+        public long elapsedRealtimeNanos() {
+            return mElapsedRealtimeNanos;
+        }
+
+        @Override
+        public int getUserSerialNumber(UserManager userManager, int userId) {
+            return userId + 10;
+        }
+
+        @Override
+        public int getUserId(UserManager userManager, int userSerialNumber) {
+            return userSerialNumber - 10;
+        }
+
+        @Override
+        public int[] getProfileIds(UserManager userManager, int userId) {
+            if (mProfiles != null) {
+                return mProfiles;
+            } else {
+                return new int[]{userId};
+            }
+        }
+
+        @Override
+        public RootTaskInfo getFocusedStack() throws RemoteException {
+            RootTaskInfo focusedStack = new RootTaskInfo();
+            focusedStack.userId = 0;
+            focusedStack.topActivity = new ComponentName("a.package", "a.class");
+            return focusedStack;
+        }
+
+        @Override
+        public void scheduleIdleJob(Context context) {
+            // Don't actually schedule jobs during unit tests.
+            mIdleScheduled = true;
+        }
+
+        @Override
+        public void cancelIdleJob(Context context) {
+            mIdleScheduled = false;
+        }
+
+        @Override
+        public boolean isInteractive(Context context) {
+            return mInteractive;
+        }
+
+        @Override
+        public int getNightDisplayColorTemperature(Context context) {
+            return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
+                    mDefaultNightModeColorTemperature);
+        }
+
+        @Override
+        public boolean isNightDisplayActivated(Context context) {
+            return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_ACTIVATED,
+                    0) == 1;
+        }
+
+        @Override
+        public int getReduceBrightColorsStrength(Context context) {
+            return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL,
+                    0);
+        }
+
+        @Override
+        public boolean isReduceBrightColorsActivated(Context context) {
+            return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+                    0) == 1;
+        }
+
+        @Override
+        public DisplayedContentSample sampleColor(int noFramesToSample) {
+            return new DisplayedContentSample(600L,
+                    null,
+                    null,
+                     new long[] {1, 10, 100, 1000, 300, 30, 10, 1},
+                    null);
+        }
+
+        @Override
+        public float getFrameRate(Context context) {
+            return mFrameRate;
+        }
+
+        @Override
+        public DisplayedContentSamplingAttributes getSamplingAttributes() {
+            return mDefaultSamplingAttributes;
+        }
+
+        @Override
+        public boolean enableColorSampling(boolean enable, int noFrames) {
+            mColorSamplingEnabled = enable;
+            mNoColorSamplingFrames = noFrames;
+            return true;
+        }
+
+        @Override
+        public void registerDisplayListener(Context context,
+                DisplayManager.DisplayListener listener, Handler handler) {
+            mDisplayListener = listener;
+        }
+
+        @Override
+        public void unRegisterDisplayListener(Context context,
+                DisplayManager.DisplayListener listener) {
+            mDisplayListener = null;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java b/services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DensityMappingTest.java b/services/tests/displayservicetests/src/com/android/server/display/DensityMappingTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/DensityMappingTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/DensityMappingTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
new file mode 100644
index 0000000..4b124ca
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -0,0 +1,377 @@
+/*
+ * 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.display;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+
+import android.view.DisplayAddress;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.layout.DisplayIdProducer;
+import com.android.server.display.layout.Layout;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+
+@SmallTest
+public class DeviceStateToLayoutMapTest {
+    private DeviceStateToLayoutMap mDeviceStateToLayoutMap;
+
+    @Mock DisplayIdProducer mDisplayIdProducerMock;
+
+    @Before
+    public void setUp() throws IOException {
+        MockitoAnnotations.initMocks(this);
+
+        Mockito.when(mDisplayIdProducerMock.getId(false)).thenReturn(1);
+
+        setupDeviceStateToLayoutMap();
+    }
+
+    //////////////////
+    // Test Methods //
+    //////////////////
+
+    @Test
+    public void testInitialState() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(0);
+
+        Layout testLayout = new Layout();
+        createDefaultDisplay(testLayout, 123456L);
+        createNonDefaultDisplay(testLayout, 78910L, /* enabled= */ false, /* group= */ null,
+                /* leadDisplayAddress= */ null);
+        createNonDefaultDisplay(testLayout, 98765L, /* enabled= */ true, /* group= */ "group1",
+                /* leadDisplayAddress= */ null);
+        createNonDefaultDisplay(testLayout, 786L, /* enabled= */ false, /* group= */ "group2",
+                /* leadDisplayAddress= */ null);
+        createNonDefaultDisplay(testLayout, 1092L, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(78910L));
+        testLayout.postProcessLocked();
+
+        assertEquals(testLayout, configLayout);
+    }
+
+    @Test
+    public void testSwitchedState() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(1);
+
+        Layout testLayout = new Layout();
+        createDefaultDisplay(testLayout, 78910L);
+        createNonDefaultDisplay(testLayout, 123456L, /* enabled= */ false, /* group= */ null,
+                /* leadDisplayAddress= */ null);
+        testLayout.postProcessLocked();
+
+        assertEquals(testLayout, configLayout);
+    }
+
+    @Test
+    public void testThermalBrightnessThrottlingMapId() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(2);
+
+        assertEquals("concurrent1", configLayout.getAt(0).getThermalBrightnessThrottlingMapId());
+        assertEquals("concurrent2", configLayout.getAt(1).getThermalBrightnessThrottlingMapId());
+    }
+
+    @Test
+    public void testRearDisplayLayout() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(2);
+
+        assertEquals(Layout.Display.POSITION_FRONT, configLayout.getAt(0).getPosition());
+        assertEquals(Layout.Display.POSITION_REAR, configLayout.getAt(1).getPosition());
+    }
+
+    @Test
+    public void testRefreshRateZoneId() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(3);
+
+        assertEquals("test1", configLayout.getAt(0).getRefreshRateZoneId());
+        assertNull(configLayout.getAt(1).getRefreshRateZoneId());
+    }
+
+    @Test
+    public void testThermalRefreshRateThrottlingMapId() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(4);
+
+        assertEquals("test2", configLayout.getAt(0).getRefreshRateThermalThrottlingMapId());
+        assertNull(configLayout.getAt(1).getRefreshRateThermalThrottlingMapId());
+    }
+
+    @Test
+    public void testWholeStateConfig() {
+        Layout configLayout = mDeviceStateToLayoutMap.get(99);
+
+        Layout testLayout = new Layout();
+        testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(345L),
+                /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+                mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,
+                /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness1",
+                /* refreshRateZoneId= */ "zone1",
+                /* refreshRateThermalThrottlingMapId= */ "rr1");
+        testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L),
+                /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1",
+                mDisplayIdProducerMock, Layout.Display.POSITION_REAR,
+                /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness2",
+                /* refreshRateZoneId= */ "zone2",
+                /* refreshRateThermalThrottlingMapId= */ "rr2");
+        testLayout.postProcessLocked();
+
+        assertEquals(testLayout, configLayout);
+    }
+
+    @Test
+    public void testLeadDisplayAddress() {
+        Layout layout = new Layout();
+        createNonDefaultDisplay(layout, 111L, /* enabled= */ true, /* group= */ null,
+                /* leadDisplayAddress= */ null);
+        createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(111L));
+
+        layout.postProcessLocked();
+
+        com.android.server.display.layout.Layout.Display display111 =
+                layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(111));
+        com.android.server.display.layout.Layout.Display display222 =
+                layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(222));
+        assertEquals(display111.getLeadDisplayId(), layout.NO_LEAD_DISPLAY);
+        assertEquals(display222.getLeadDisplayId(), display111.getLogicalDisplayId());
+    }
+
+    @Test
+    public void testLeadDisplayAddress_defaultDisplay() {
+        Layout layout = new Layout();
+        createDefaultDisplay(layout, 123456L);
+
+        layout.postProcessLocked();
+
+        com.android.server.display.layout.Layout.Display display =
+                layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(123456));
+        assertEquals(display.getLeadDisplayId(), layout.NO_LEAD_DISPLAY);
+    }
+
+    @Test
+    public void testLeadDisplayAddress_noLeadDisplay() {
+        Layout layout = new Layout();
+        createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+                /* leadDisplayAddress= */ null);
+
+        layout.postProcessLocked();
+
+        com.android.server.display.layout.Layout.Display display =
+                layout.getByAddress(DisplayAddress.fromPhysicalDisplayId(222));
+        assertEquals(display.getLeadDisplayId(), layout.NO_LEAD_DISPLAY);
+    }
+
+    @Test
+    public void testLeadDisplayAddress_selfLeadDisplayForNonDefaultDisplay() {
+        Layout layout = new Layout();
+
+        assertThrows("Expected Layout to throw IllegalArgumentException when the display points out"
+                + " itself as a lead display",
+                IllegalArgumentException.class,
+                () -> layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(123L),
+                    /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+                    mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,
+                    DisplayAddress.fromPhysicalDisplayId(123L),
+                    /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+                    /* refreshRateThermalThrottlingMapId= */ null));
+    }
+
+    @Test
+    public void testLeadDisplayAddress_wrongLeadDisplayForDefaultDisplay() {
+        Layout layout = new Layout();
+
+        assertThrows("Expected Layout to throw IllegalArgumentException when the default display "
+                + "has a lead display",
+                IllegalArgumentException.class,
+                () -> layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(123L),
+                    /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+                    mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,
+                    DisplayAddress.fromPhysicalDisplayId(987L),
+                    /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+                    /* refreshRateThermalThrottlingMapId= */ null));
+    }
+
+    @Test
+    public void testLeadDisplayAddress_notExistingLeadDisplayForNonDefaultDisplay() {
+        Layout layout = new Layout();
+        createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(111L));
+
+        assertThrows("Expected Layout to throw IllegalArgumentException when a lead display doesn't"
+                + " exist", IllegalArgumentException.class, () -> layout.postProcessLocked());
+    }
+
+    @Test
+    public void testLeadDisplayAddress_leadDisplayInDifferentDisplayGroup() {
+        Layout layout = new Layout();
+        createNonDefaultDisplay(layout, 111, /* enabled= */ true, /* group= */ "group1",
+                /* leadDisplayAddress= */ null);
+        createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ "group2",
+                DisplayAddress.fromPhysicalDisplayId(111L));
+
+        assertThrows("Expected Layout to throw IllegalArgumentException when pointing to a lead "
+                + "display in the different group",
+                IllegalArgumentException.class, () -> layout.postProcessLocked());
+    }
+
+    @Test
+    public void testLeadDisplayAddress_cyclicLeadDisplay() {
+        Layout layout = new Layout();
+        createNonDefaultDisplay(layout, 111, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(222L));
+        createNonDefaultDisplay(layout, 222L, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(333L));
+        createNonDefaultDisplay(layout, 333L, /* enabled= */ true, /* group= */ null,
+                DisplayAddress.fromPhysicalDisplayId(222L));
+
+        assertThrows("Expected Layout to throw IllegalArgumentException when pointing to a lead "
+                + "display in the different group",
+                IllegalArgumentException.class, () -> layout.postProcessLocked());
+    }
+
+    ////////////////////
+    // Helper Methods //
+    ////////////////////
+
+    private void createDefaultDisplay(Layout layout, long id) {
+        layout.createDefaultDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id),
+                mDisplayIdProducerMock);
+    }
+
+    private void createNonDefaultDisplay(Layout layout, long id, boolean enabled, String group,
+            DisplayAddress leadDisplayAddress) {
+        layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id), /* isDefault= */ false,
+                enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
+                leadDisplayAddress, /* brightnessThrottlingMapId= */ null,
+                /* refreshRateZoneId= */ null,
+                /* refreshRateThermalThrottlingMapId= */ null);
+    }
+
+    private void setupDeviceStateToLayoutMap() throws IOException {
+        Path tempFile = Files.createTempFile("device_state_layout_map", ".tmp");
+        Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+        mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(mDisplayIdProducerMock,
+                tempFile.toFile());
+    }
+
+    private String getContent() {
+        return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                +  "<layouts>\n"
+                +    "<layout>\n"
+                +      "<state>0</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+                +        "<address>123456</address>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"false\">\n"
+                +        "<address>78910</address>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"true\" displayGroup=\"group1\">\n"
+                +        "<address>98765</address>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"false\" displayGroup=\"group2\">\n"
+                +        "<address>786</address>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"true\">\n"
+                +        "<address>1092</address>\n"
+                +        "<leadDisplayAddress>78910</leadDisplayAddress>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+
+                +    "<layout>\n"
+                +      "<state>1</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+                +        "<address>78910</address>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"false\">\n"
+                +        "<address>123456</address>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+
+                +    "<layout>\n"
+                +      "<state>2</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+                +        "<address>345</address>\n"
+                +        "<position>front</position>\n"
+                +        "<brightnessThrottlingMapId>concurrent1</brightnessThrottlingMapId>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"true\">\n"
+                +        "<address>678</address>\n"
+                +        "<position>rear</position>\n"
+                +        "<brightnessThrottlingMapId>concurrent2</brightnessThrottlingMapId>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+
+                +    "<layout>\n"
+                +      "<state>3</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\" "
+                +                                               "refreshRateZoneId=\"test1\">\n"
+                +        "<address>345</address>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"true\">\n"
+                +        "<address>678</address>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+
+                +    "<layout>\n"
+                +      "<state>4</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\" >\n"
+                +        "<address>345</address>\n"
+                +        "<refreshRateThermalThrottlingMapId>"
+                +          "test2"
+                +        "</refreshRateThermalThrottlingMapId>"
+                +      "</display>\n"
+                +      "<display enabled=\"true\">\n"
+                +        "<address>678</address>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+                +    "<layout>\n"
+                +      "<state>99</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\" "
+                +                                          "refreshRateZoneId=\"zone1\">\n"
+                +         "<address>345</address>\n"
+                +         "<position>front</position>\n"
+                +         "<brightnessThrottlingMapId>brightness1</brightnessThrottlingMapId>\n"
+                +         "<refreshRateThermalThrottlingMapId>"
+                +           "rr1"
+                +         "</refreshRateThermalThrottlingMapId>"
+                +       "</display>\n"
+                +       "<display enabled=\"false\" displayGroup=\"group1\" "
+                +                                           "refreshRateZoneId=\"zone2\">\n"
+                +         "<address>678</address>\n"
+                +         "<position>rear</position>\n"
+                +         "<brightnessThrottlingMapId>brightness2</brightnessThrottlingMapId>\n"
+                +         "<refreshRateThermalThrottlingMapId>"
+                +           "rr2"
+                +         "</refreshRateThermalThrottlingMapId>"
+                +       "</display>\n"
+                +     "</layout>\n"
+                +   "</layouts>\n";
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
new file mode 100644
index 0000000..7e69357
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.display;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayBrightnessStateTest {
+    private static final float FLOAT_DELTA = 0.001f;
+
+    private DisplayBrightnessState.Builder mDisplayBrightnessStateBuilder;
+
+    @Before
+    public void before() {
+        mDisplayBrightnessStateBuilder = new DisplayBrightnessState.Builder();
+    }
+
+    @Test
+    public void validateAllDisplayBrightnessStateFieldsAreSetAsExpected() {
+        float brightness = 0.3f;
+        float sdrBrightness = 0.2f;
+        boolean shouldUseAutoBrightness = true;
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
+        brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED);
+        DisplayBrightnessState displayBrightnessState = mDisplayBrightnessStateBuilder
+                .setBrightness(brightness)
+                .setSdrBrightness(sdrBrightness)
+                .setBrightnessReason(brightnessReason)
+                .setShouldUseAutoBrightness(shouldUseAutoBrightness)
+                .build();
+
+        assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
+        assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA);
+        assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason);
+        assertEquals(displayBrightnessState.getShouldUseAutoBrightness(), shouldUseAutoBrightness);
+        assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState));
+    }
+
+    @Test
+    public void testFrom() {
+        BrightnessReason reason = new BrightnessReason();
+        reason.setReason(BrightnessReason.REASON_MANUAL);
+        reason.setModifier(BrightnessReason.MODIFIER_DIMMED);
+        DisplayBrightnessState state1 = new DisplayBrightnessState.Builder()
+                .setBrightnessReason(reason)
+                .setBrightness(0.26f)
+                .setSdrBrightness(0.23f)
+                .setShouldUseAutoBrightness(false)
+                .build();
+        DisplayBrightnessState state2 = DisplayBrightnessState.Builder.from(state1).build();
+        assertEquals(state1, state2);
+    }
+
+    private String getString(DisplayBrightnessState displayBrightnessState) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("DisplayBrightnessState:")
+                .append("\n    brightness:")
+                .append(displayBrightnessState.getBrightness())
+                .append("\n    sdrBrightness:")
+                .append(displayBrightnessState.getSdrBrightness())
+                .append("\n    brightnessReason:")
+                .append(displayBrightnessState.getBrightnessReason())
+                .append("\n    shouldUseAutoBrightness:")
+                .append(displayBrightnessState.getShouldUseAutoBrightness())
+                .append("\n    isSlowChange:")
+                .append(displayBrightnessState.isSlowChange());
+        return sb.toString();
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
new file mode 100644
index 0000000..da7a6a1
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -0,0 +1,991 @@
+/*
+ * 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.display;
+
+
+import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
+import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Temperature;
+import android.util.SparseArray;
+import android.util.Spline;
+import android.view.SurfaceControl;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.server.display.config.ThermalStatus;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SmallTest
+@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 DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE = 55;
+    private static final int DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE = 95;
+    private static final int DEFAULT_REFRESH_RATE_IN_HBM_HDR = 90;
+    private static final int DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT = 100;
+    private static final int[] LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE =
+            new int[]{10, 30, -1};
+    private static final int[] LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{-1, 1, 21};
+    private static final int[] HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{160, -1};
+    private static final int[] HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{-1, 30000};
+    private static final float[] NITS = {2, 500, 800};
+    private static final float[] BRIGHTNESS = {0, 0.62f, 1};
+    private static final Spline NITS_TO_BRIGHTNESS_SPLINE =
+            Spline.createSpline(NITS, BRIGHTNESS);
+
+    private DisplayDeviceConfig mDisplayDeviceConfig;
+    private static final float ZERO_DELTA = 0.0f;
+    private static final float SMALL_DELTA = 0.0001f;
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private Resources mResources;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getResources()).thenReturn(mResources);
+        mockDeviceConfigs();
+    }
+
+    @Test
+    public void testConfigValuesFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        assertEquals(mDisplayDeviceConfig.getName(), "Example Display");
+        assertEquals(mDisplayDeviceConfig.getAmbientHorizonLong(), 5000);
+        assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, ZERO_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getBrightness(), BRIGHTNESS, ZERO_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getNits(), NITS, ZERO_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getBacklight(), BRIGHTNESS, ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
+        assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
+        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
+                float[]{0.0f, 50.0f, 80.0f}, ZERO_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
+                float[]{45.32f, 75.43f}, ZERO_DELTA);
+
+        // Test thresholds
+        assertEquals(10, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(),
+                ZERO_DELTA);
+        assertEquals(20, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
+                ZERO_DELTA);
+        assertEquals(30, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
+        assertEquals(40, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+        assertEquals(0.1f, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
+        assertEquals(0.2f, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
+        assertEquals(0.3f, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
+        assertEquals(0.4f, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 0.10f, 0.20f},
+                mDisplayDeviceConfig.getScreenBrighteningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{9, 10, 11},
+                mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 0.11f, 0.21f},
+                mDisplayDeviceConfig.getScreenDarkeningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{11, 12, 13},
+                mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 100, 200},
+                mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{13, 14, 15},
+                mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 300, 400},
+                mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{15, 16, 17},
+                mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 0.12f, 0.22f},
+                mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{17, 18, 19},
+                mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 0.13f, 0.23f},
+                mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{19, 20, 21},
+                mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 500, 600},
+                mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{21, 22, 23},
+                mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 700, 800},
+                mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{23, 24, 25},
+                mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+
+        assertEquals(75, mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate());
+        assertEquals(90, mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate());
+        assertEquals(85, mDisplayDeviceConfig.getDefaultPeakRefreshRate());
+        assertEquals(45, mDisplayDeviceConfig.getDefaultRefreshRate());
+        assertEquals(2, mDisplayDeviceConfig.getRefreshRangeProfiles().size());
+        assertEquals(60, mDisplayDeviceConfig.getRefreshRange("test1").min, SMALL_DELTA);
+        assertEquals(60, mDisplayDeviceConfig.getRefreshRange("test1").max, SMALL_DELTA);
+        assertEquals(80, mDisplayDeviceConfig.getRefreshRange("test2").min, SMALL_DELTA);
+        assertEquals(90, mDisplayDeviceConfig.getRefreshRange("test2").max, SMALL_DELTA);
+        assertEquals(82, mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr());
+        assertEquals(83, mDisplayDeviceConfig.getDefaultRefreshRateInHbmSunlight());
+
+        assertEquals("sensor_12345",
+                mDisplayDeviceConfig.getScreenOffBrightnessSensor().type);
+        assertEquals("Sensor 12345",
+                mDisplayDeviceConfig.getScreenOffBrightnessSensor().name);
+
+        assertArrayEquals(new int[]{-1, 10, 20, 30, 40},
+                mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux());
+
+        List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel>
+                defaultThrottlingLevels = new ArrayList<>();
+        defaultThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.4f
+        ));
+        defaultThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.3f
+        ));
+        defaultThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.2f
+        ));
+        defaultThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.1f
+        ));
+        defaultThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.05f
+        ));
+        defaultThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.025f
+        ));
+
+        DisplayDeviceConfig.ThermalBrightnessThrottlingData defaultThrottlingData =
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData(defaultThrottlingLevels);
+
+        List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel>
+                concurrentThrottlingLevels = new ArrayList<>();
+        concurrentThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.2f
+        ));
+        concurrentThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.15f
+        ));
+        concurrentThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.1f
+        ));
+        concurrentThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.05f
+        ));
+        concurrentThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.025f
+        ));
+        concurrentThrottlingLevels.add(
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
+                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.0125f
+        ));
+        DisplayDeviceConfig.ThermalBrightnessThrottlingData concurrentThrottlingData =
+                new DisplayDeviceConfig.ThermalBrightnessThrottlingData(concurrentThrottlingLevels);
+
+        HashMap<String, DisplayDeviceConfig.ThermalBrightnessThrottlingData> throttlingDataMap =
+                new HashMap<>(2);
+        throttlingDataMap.put("default", defaultThrottlingData);
+        throttlingDataMap.put("concurrent", concurrentThrottlingData);
+
+        assertEquals(throttlingDataMap,
+                mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
+
+        assertNotNull(mDisplayDeviceConfig.getHostUsiVersion());
+        assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2);
+        assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMinorVersion(), 0);
+
+        // Max desired Hdr/SDR ratio upper-bounds the HDR brightness.
+        assertEquals(1.0f,
+                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, Float.POSITIVE_INFINITY),
+                ZERO_DELTA);
+        assertEquals(0.62f,
+                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.0f),
+                ZERO_DELTA);
+        assertEquals(0.77787f,
+                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.25f),
+                SMALL_DELTA);
+
+        // Todo: Add asserts for DensityMapping,
+        // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
+    }
+
+    @Test
+    public void testConfigValuesFromConfigResource() {
+        setupDisplayDeviceConfigFromConfigResourceFile();
+        assertNull(mDisplayDeviceConfig.getName());
+        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
+                float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
+                float[]{0.0f, 0.0f, 110.0f, 500.0f}, ZERO_DELTA);
+
+        // Test thresholds
+        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
+                ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+        assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
+        assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+
+        // screen levels will be considered "old screen brightness scale"
+        // and therefore will divide by 255
+        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+                mDisplayDeviceConfig.getScreenBrighteningLevels(), SMALL_DELTA);
+        assertArrayEquals(new float[]{35, 36, 37},
+                mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+                mDisplayDeviceConfig.getScreenDarkeningLevels(), SMALL_DELTA);
+        assertArrayEquals(new float[]{37, 38, 39},
+                mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 30, 31},
+                mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{27, 28, 29},
+                mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 30, 31},
+                mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{29, 30, 31},
+                mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+                mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), SMALL_DELTA);
+        assertArrayEquals(new float[]{35, 36, 37},
+                mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
+                mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), SMALL_DELTA);
+        assertArrayEquals(new float[]{37, 38, 39},
+                mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+
+        assertArrayEquals(new float[]{0, 30, 31},
+                mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{27, 28, 29},
+                mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0, 30, 31},
+                mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
+        assertArrayEquals(new float[]{29, 30, 31},
+                mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+        assertEquals(mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(),
+                DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
+        assertEquals(mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(),
+                DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
+        assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
+        assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE);
+        assertEquals(0, mDisplayDeviceConfig.getRefreshRangeProfiles().size());
+        assertEquals(mDisplayDeviceConfig.getDefaultRefreshRateInHbmSunlight(),
+                DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT);
+        assertEquals(mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr(),
+                DEFAULT_REFRESH_RATE_IN_HBM_HDR);
+
+        // Todo: Add asserts for ThermalBrightnessThrottlingData, DensityMapping,
+        // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
+    }
+
+    @Test
+    public void testThermalRefreshRateThrottlingFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        SparseArray<SurfaceControl.RefreshRateRange> defaultMap =
+                mDisplayDeviceConfig.getThermalRefreshRateThrottlingData(null);
+        assertNotNull(defaultMap);
+        assertEquals(2, defaultMap.size());
+        assertEquals(30, defaultMap.get(Temperature.THROTTLING_CRITICAL).min, SMALL_DELTA);
+        assertEquals(60, defaultMap.get(Temperature.THROTTLING_CRITICAL).max, SMALL_DELTA);
+        assertEquals(0, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).min, SMALL_DELTA);
+        assertEquals(30, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).max, SMALL_DELTA);
+
+        SparseArray<SurfaceControl.RefreshRateRange> testMap =
+                mDisplayDeviceConfig.getThermalRefreshRateThrottlingData("test");
+        assertNotNull(testMap);
+        assertEquals(1, testMap.size());
+        assertEquals(60, testMap.get(Temperature.THROTTLING_EMERGENCY).min, SMALL_DELTA);
+        assertEquals(90, testMap.get(Temperature.THROTTLING_EMERGENCY).max, SMALL_DELTA);
+    }
+
+    @Test
+    public void testValidLuxThrottling() throws Exception {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
+                mDisplayDeviceConfig.getLuxThrottlingData();
+        assertEquals(2, luxThrottlingData.size());
+
+        Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
+                DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
+        assertEquals(2, adaptiveOnBrightnessPoints.size());
+        assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
+        assertEquals(0.5f, adaptiveOnBrightnessPoints.get(5000f), SMALL_DELTA);
+
+        Map<Float, Float> adaptiveOffBrightnessPoints = luxThrottlingData.get(
+                DisplayDeviceConfig.BrightnessLimitMapType.DEFAULT);
+        assertEquals(2, adaptiveOffBrightnessPoints.size());
+        assertEquals(0.35f, adaptiveOffBrightnessPoints.get(1500f), SMALL_DELTA);
+        assertEquals(0.55f, adaptiveOffBrightnessPoints.get(5500f), SMALL_DELTA);
+    }
+
+    @Test
+    public void testInvalidLuxThrottling() throws Exception {
+        setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getInvalidLuxThrottling()));
+
+        Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
+                mDisplayDeviceConfig.getLuxThrottlingData();
+        assertEquals(1, luxThrottlingData.size());
+
+        Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
+                DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
+        assertEquals(1, adaptiveOnBrightnessPoints.size());
+        assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
+    }
+
+    @Test
+    public void testBlockingZoneThresholdsFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        assertArrayEquals(new float[]{ NITS_TO_BRIGHTNESS_SPLINE.interpolate(50),
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(300),
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(300), -1},
+                mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(), SMALL_DELTA);
+        assertArrayEquals(new float[]{50, 60, -1, 60},
+                mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(), ZERO_DELTA);
+        assertArrayEquals(new float[]{ NITS_TO_BRIGHTNESS_SPLINE.interpolate(80),
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(100),
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(100), -1},
+                mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(), SMALL_DELTA);
+        assertArrayEquals(new float[]{70, 80, -1, 80},
+                mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), ZERO_DELTA);
+    }
+
+    @Test
+    public void testBlockingZoneThresholdsFromConfigResource() {
+        setupDisplayDeviceConfigFromConfigResourceFile();
+
+        assertArrayEquals(displayBrightnessThresholdsIntToFloat(
+                LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE),
+                mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(), SMALL_DELTA);
+        assertArrayEquals(ambientBrightnessThresholdsIntToFloat(
+                LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE),
+                mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(), ZERO_DELTA);
+        assertArrayEquals(displayBrightnessThresholdsIntToFloat(
+                HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE),
+                mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(), SMALL_DELTA);
+        assertArrayEquals(ambientBrightnessThresholdsIntToFloat(
+                HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE),
+                mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), ZERO_DELTA);
+    }
+
+    private String getValidLuxThrottling() {
+        return "<luxThrottling>\n"
+                + "    <brightnessLimitMap>\n"
+                + "        <type>adaptive</type>\n"
+                + "        <map>\n"
+                + "            <point>"
+                + "                <first>1000</first>\n"
+                + "                <second>0.3</second>\n"
+                + "            </point>"
+                + "            <point>"
+                + "                <first>5000</first>\n"
+                + "                <second>0.5</second>\n"
+                + "            </point>"
+                + "        </map>\n"
+                + "    </brightnessLimitMap>\n"
+                + "    <brightnessLimitMap>\n"
+                + "        <type>default</type>\n"
+                + "        <map>\n"
+                + "            <point>"
+                + "                <first>1500</first>\n"
+                + "                <second>0.35</second>\n"
+                + "            </point>"
+                + "            <point>"
+                + "                <first>5500</first>\n"
+                + "                <second>0.55</second>\n"
+                + "            </point>"
+                + "        </map>\n"
+                + "    </brightnessLimitMap>\n"
+                + "</luxThrottling>";
+    }
+
+    private String getInvalidLuxThrottling() {
+        return "<luxThrottling>\n"
+                + "    <brightnessLimitMap>\n"
+                + "        <type>adaptive</type>\n"
+                + "        <map>\n"
+                + "            <point>"
+                + "                <first>1000</first>\n"
+                + "                <second>0.3</second>\n"
+                + "            </point>"
+                + "            <point>" // second > hbm.transitionPoint, skipped
+                + "                <first>1500</first>\n"
+                + "                <second>0.9</second>\n"
+                + "            </point>"
+                + "            <point>" // same lux value, skipped
+                + "                <first>1000</first>\n"
+                + "                <second>0.5</second>\n"
+                + "            </point>"
+                + "        </map>\n"
+                + "    </brightnessLimitMap>\n"
+                + "    <brightnessLimitMap>\n" // Same type, skipped
+                + "        <type>adaptive</type>\n"
+                + "        <map>\n"
+                + "            <point>"
+                + "                <first>2000</first>\n"
+                + "                <second>0.35</second>\n"
+                + "            </point>"
+                + "            <point>"
+                + "                <first>6000</first>\n"
+                + "                <second>0.55</second>\n"
+                + "            </point>"
+                + "        </map>\n"
+                + "    </brightnessLimitMap>\n"
+                + "    <brightnessLimitMap>\n" // Invalid points only, skipped
+                + "        <type>default</type>\n"
+                + "        <map>\n"
+                + "            <point>"
+                + "                <first>2500</first>\n"
+                + "                <second>0.99</second>\n"
+                + "            </point>"
+                + "        </map>\n"
+                + "    </brightnessLimitMap>\n"
+                + "</luxThrottling>";
+    }
+
+    private String getRefreshThermalThrottlingMaps() {
+        return "<refreshRateThrottlingMap>\n"
+               + "    <refreshRateThrottlingPoint>\n"
+               + "        <thermalStatus>critical</thermalStatus>\n"
+               + "        <refreshRateRange>\n"
+               + "            <minimum>30</minimum>\n"
+               + "            <maximum>60</maximum>\n"
+               + "        </refreshRateRange>\n"
+               + "    </refreshRateThrottlingPoint>\n"
+               + "    <refreshRateThrottlingPoint>\n"
+               + "        <thermalStatus>shutdown</thermalStatus>\n"
+               + "        <refreshRateRange>\n"
+               + "            <minimum>0</minimum>\n"
+               + "            <maximum>30</maximum>\n"
+               + "        </refreshRateRange>\n"
+               + "    </refreshRateThrottlingPoint>\n"
+               + "</refreshRateThrottlingMap>\n"
+               + "<refreshRateThrottlingMap id=\"test\">\n"
+               + "    <refreshRateThrottlingPoint>\n"
+               + "        <thermalStatus>emergency</thermalStatus>\n"
+               + "        <refreshRateRange>\n"
+               + "            <minimum>60</minimum>\n"
+               + "            <maximum>90</maximum>\n"
+               + "        </refreshRateRange>\n"
+               + "    </refreshRateThrottlingPoint>\n"
+               + "</refreshRateThrottlingMap>\n";
+    }
+
+    private String getContent() {
+        return getContent(getValidLuxThrottling());
+    }
+
+    private String getContent(String brightnessCapConfig) {
+        return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<displayConfiguration>\n"
+                +   "<name>Example Display</name>"
+                +   "<screenBrightnessMap>\n"
+                +       "<point>\n"
+                +           "<value>" + BRIGHTNESS[0] + "</value>\n"
+                +           "<nits>" + NITS[0] + "</nits>\n"
+                +       "</point>\n"
+                +       "<point>\n"
+                +           "<value>" + BRIGHTNESS[1] + "</value>\n"
+                +           "<nits>" + NITS[1] + "</nits>\n"
+                +       "</point>\n"
+                +       "<point>\n"
+                +           "<value>" + BRIGHTNESS[2] + "</value>\n"
+                +           "<nits>" + NITS[2] + "</nits>\n"
+                +       "</point>\n"
+                +   "</screenBrightnessMap>\n"
+                +   "<autoBrightness>\n"
+                +       "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n"
+                +       "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n"
+                +       "<displayBrightnessMapping>\n"
+                +            "<displayBrightnessPoint>\n"
+                +                "<lux>50</lux>\n"
+                +                "<nits>45.32</nits>\n"
+                +            "</displayBrightnessPoint>\n"
+                +            "<displayBrightnessPoint>\n"
+                +                "<lux>80</lux>\n"
+                +                "<nits>75.43</nits>\n"
+                +            "</displayBrightnessPoint>\n"
+                +       "</displayBrightnessMapping>\n"
+                +   "</autoBrightness>\n"
+                +   "<highBrightnessMode enabled=\"true\">\n"
+                +       "<transitionPoint>0.62</transitionPoint>\n"
+                +       "<minimumLux>10000</minimumLux>\n"
+                +       "<timing>\n"
+                +           "<!-- allow for 5 minutes out of every 30 minutes -->\n"
+                +           "<timeWindowSecs>1800</timeWindowSecs>\n"
+                +           "<timeMaxSecs>300</timeMaxSecs>\n"
+                +           "<timeMinSecs>60</timeMinSecs>\n"
+                +       "</timing>\n"
+                +       "<refreshRate>\n"
+                +           "<minimum>120</minimum>\n"
+                +           "<maximum>120</maximum>\n"
+                +       "</refreshRate>\n"
+                +       "<thermalStatusLimit>light</thermalStatusLimit>\n"
+                +       "<allowInLowPowerMode>false</allowInLowPowerMode>\n"
+                +       "<sdrHdrRatioMap>\n"
+                +            "<point>\n"
+                +                "<sdrNits>2.000</sdrNits>\n"
+                +                "<hdrRatio>4.000</hdrRatio>\n"
+                +            "</point>\n"
+                +            "<point>\n"
+                +                "<sdrNits>500.0</sdrNits>\n"
+                +                "<hdrRatio>1.6</hdrRatio>\n"
+                +            "</point>\n"
+                +       "</sdrHdrRatioMap>\n"
+                +   "</highBrightnessMode>\n"
+                + brightnessCapConfig
+                +   "<screenOffBrightnessSensor>\n"
+                +       "<type>sensor_12345</type>\n"
+                +       "<name>Sensor 12345</name>\n"
+                +   "</screenOffBrightnessSensor>\n"
+                +   "<ambientBrightnessChangeThresholds>\n"
+                +       "<brighteningThresholds>\n"
+                +           "<minimum>10</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>13</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>100</threshold><percentage>14</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>200</threshold><percentage>15</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</brighteningThresholds>\n"
+                +       "<darkeningThresholds>\n"
+                +           "<minimum>30</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>15</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>300</threshold><percentage>16</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>400</threshold><percentage>17</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</darkeningThresholds>\n"
+                +   "</ambientBrightnessChangeThresholds>\n"
+                +   "<displayBrightnessChangeThresholds>\n"
+                +       "<brighteningThresholds>\n"
+                +           "<minimum>0.1</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold>\n"
+                +                   "<percentage>9</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.10</threshold>\n"
+                +                   "<percentage>10</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.20</threshold>\n"
+                +                   "<percentage>11</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</brighteningThresholds>\n"
+                +       "<darkeningThresholds>\n"
+                +           "<minimum>0.3</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>11</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.11</threshold><percentage>12</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.21</threshold><percentage>13</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</darkeningThresholds>\n"
+                +   "</displayBrightnessChangeThresholds>\n"
+                +   "<ambientBrightnessChangeThresholdsIdle>\n"
+                +       "<brighteningThresholds>\n"
+                +           "<minimum>20</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>21</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>500</threshold><percentage>22</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>600</threshold><percentage>23</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</brighteningThresholds>\n"
+                +       "<darkeningThresholds>\n"
+                +           "<minimum>40</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>23</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>700</threshold><percentage>24</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>800</threshold><percentage>25</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</darkeningThresholds>\n"
+                +   "</ambientBrightnessChangeThresholdsIdle>\n"
+                +   "<displayBrightnessChangeThresholdsIdle>\n"
+                +       "<brighteningThresholds>\n"
+                +           "<minimum>0.2</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>17</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.12</threshold><percentage>18</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.22</threshold><percentage>19</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</brighteningThresholds>\n"
+                +       "<darkeningThresholds>\n"
+                +           "<minimum>0.4</minimum>\n"
+                +           "<brightnessThresholdPoints>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0</threshold><percentage>19</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.13</threshold><percentage>20</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +               "<brightnessThresholdPoint>\n"
+                +                   "<threshold>0.23</threshold><percentage>21</percentage>\n"
+                +               "</brightnessThresholdPoint>\n"
+                +           "</brightnessThresholdPoints>\n"
+                +       "</darkeningThresholds>\n"
+                +   "</displayBrightnessChangeThresholdsIdle>\n"
+                +   "<screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease> "
+                +   "<screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease>  "
+                +   "<screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease>"
+                +   "<screenBrightnessRampSlowIncrease>0.04</screenBrightnessRampSlowIncrease>"
+                +   "<screenBrightnessRampIncreaseMaxMillis>"
+                +       "2000"
+                +   "</screenBrightnessRampIncreaseMaxMillis>"
+                +   "<screenBrightnessRampDecreaseMaxMillis>"
+                +       "3000"
+                +   "</screenBrightnessRampDecreaseMaxMillis>"
+                +   "<ambientLightHorizonLong>5000</ambientLightHorizonLong>\n"
+                +   "<ambientLightHorizonShort>50</ambientLightHorizonShort>\n"
+                +   "<screenBrightnessRampIncreaseMaxMillis>"
+                +       "2000"
+                +   "</screenBrightnessRampIncreaseMaxMillis>\n"
+                +   "<thermalThrottling>\n"
+                +       "<brightnessThrottlingMap>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>light</thermalStatus>\n"
+                +               "<brightness>0.4</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>moderate</thermalStatus>\n"
+                +               "<brightness>0.3</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>severe</thermalStatus>\n"
+                +               "<brightness>0.2</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>critical</thermalStatus>\n"
+                +               "<brightness>0.1</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>emergency</thermalStatus>\n"
+                +               "<brightness>0.05</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>shutdown</thermalStatus>\n"
+                +               "<brightness>0.025</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +       "</brightnessThrottlingMap>\n"
+                +       "<brightnessThrottlingMap id=\"concurrent\">\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>light</thermalStatus>\n"
+                +               "<brightness>0.2</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>moderate</thermalStatus>\n"
+                +               "<brightness>0.15</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>severe</thermalStatus>\n"
+                +               "<brightness>0.1</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>critical</thermalStatus>\n"
+                +               "<brightness>0.05</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>emergency</thermalStatus>\n"
+                +               "<brightness>0.025</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +           "<brightnessThrottlingPoint>\n"
+                +               "<thermalStatus>shutdown</thermalStatus>\n"
+                +               "<brightness>0.0125</brightness>\n"
+                +           "</brightnessThrottlingPoint>\n"
+                +       "</brightnessThrottlingMap>\n"
+                +  getRefreshThermalThrottlingMaps()
+                +   "</thermalThrottling>\n"
+                +   "<refreshRate>\n"
+                +       "<defaultRefreshRate>45</defaultRefreshRate>\n"
+                +       "<defaultPeakRefreshRate>85</defaultPeakRefreshRate>\n"
+                +       "<refreshRateZoneProfiles>"
+                +           "<refreshRateZoneProfile id=\"test1\">"
+                +               "<refreshRateRange>\n"
+                +                   "<minimum>60</minimum>\n"
+                +                   "<maximum>60</maximum>\n"
+                +               "</refreshRateRange>\n"
+                +           "</refreshRateZoneProfile>\n"
+                +           "<refreshRateZoneProfile id=\"test2\">"
+                +               "<refreshRateRange>\n"
+                +                   "<minimum>80</minimum>\n"
+                +                   "<maximum>90</maximum>\n"
+                +               "</refreshRateRange>\n"
+                +           "</refreshRateZoneProfile>\n"
+                +       "</refreshRateZoneProfiles>"
+                +       "<defaultRefreshRateInHbmHdr>82</defaultRefreshRateInHbmHdr>\n"
+                +       "<defaultRefreshRateInHbmSunlight>83</defaultRefreshRateInHbmSunlight>\n"
+                +       "<lowerBlockingZoneConfigs>\n"
+                +           "<defaultRefreshRate>75</defaultRefreshRate>\n"
+                +           "<blockingZoneThreshold>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>50</lux>\n"
+                +                   "<nits>50</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>60</lux>\n"
+                +                   "<nits>300</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>-1</lux>\n"
+                +                   "<nits>300</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>60</lux>\n"
+                +                   "<nits>-1</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +           "</blockingZoneThreshold>\n"
+                +       "</lowerBlockingZoneConfigs>\n"
+                +       "<higherBlockingZoneConfigs>\n"
+                +           "<defaultRefreshRate>90</defaultRefreshRate>\n"
+                +           "<blockingZoneThreshold>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>70</lux>\n"
+                +                   "<nits>80</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>80</lux>\n"
+                +                   "<nits>100</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>-1</lux>\n"
+                +                   "<nits>100</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +               "<displayBrightnessPoint>\n"
+                +                   "<lux>80</lux>\n"
+                +                   "<nits>-1</nits>\n"
+                +               "</displayBrightnessPoint>\n"
+                +           "</blockingZoneThreshold>\n"
+                +       "</higherBlockingZoneConfigs>\n"
+                +   "</refreshRate>\n"
+                +   "<screenOffBrightnessSensorValueToLux>\n"
+                +       "<item>-1</item>\n"
+                +       "<item>10</item>\n"
+                +       "<item>20</item>\n"
+                +       "<item>30</item>\n"
+                +       "<item>40</item>\n"
+                +   "</screenOffBrightnessSensorValueToLux>\n"
+                +   "<usiVersion>\n"
+                +       "<majorVersion>2</majorVersion>\n"
+                +       "<minorVersion>0</minorVersion>\n"
+                +   "</usiVersion>\n"
+                + "</displayConfiguration>\n";
+    }
+
+    private void mockDeviceConfigs() {
+        when(mResources.getFloat(com.android.internal.R.dimen
+                .config_screenBrightnessSettingDefaultFloat)).thenReturn(0.5f);
+        when(mResources.getFloat(com.android.internal.R.dimen
+                .config_screenBrightnessSettingMaximumFloat)).thenReturn(1.0f);
+    }
+
+    private void setupDisplayDeviceConfigFromDisplayConfigFile() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile(getContent());
+    }
+
+    private void setupDisplayDeviceConfigFromDisplayConfigFile(String content) throws IOException {
+        Path tempFile = Files.createTempFile("display_config", ".tmp");
+        Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8));
+        mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
+        mDisplayDeviceConfig.initFromFile(tempFile.toFile());
+    }
+
+    private void setupDisplayDeviceConfigFromConfigResourceFile() {
+        TypedArray screenBrightnessNits = createFloatTypedArray(new float[]{2.0f, 250.0f, 650.0f});
+        when(mResources.obtainTypedArray(
+                com.android.internal.R.array.config_screenBrightnessNits))
+                .thenReturn(screenBrightnessNits);
+        TypedArray screenBrightnessBacklight = createFloatTypedArray(new
+                float[]{0.0f, 120.0f, 255.0f});
+        when(mResources.obtainTypedArray(
+                com.android.internal.R.array.config_screenBrightnessBacklight))
+                .thenReturn(screenBrightnessBacklight);
+        when(mResources.getIntArray(com.android.internal.R.array
+                .config_screenBrightnessBacklight)).thenReturn(new int[]{0, 120, 255});
+
+        when(mResources.getIntArray(com.android.internal.R.array
+                .config_autoBrightnessLevels)).thenReturn(new int[]{30, 80});
+        when(mResources.getIntArray(com.android.internal.R.array
+                .config_autoBrightnessDisplayValuesNits)).thenReturn(new int[]{25, 55});
+
+        TypedArray screenBrightnessLevelNits = createFloatTypedArray(new
+                float[]{2.0f, 200.0f, 600.0f});
+        when(mResources.obtainTypedArray(
+                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
+                .thenReturn(screenBrightnessLevelNits);
+        int[] screenBrightnessLevelLux = new int[]{0, 110, 500};
+        when(mResources.getIntArray(
+                com.android.internal.R.array.config_autoBrightnessLevels))
+                .thenReturn(screenBrightnessLevelLux);
+
+        // Thresholds
+        // Config.xml requires the levels arrays to be of length N and the thresholds arrays to be
+        // of length N+1
+        when(mResources.getIntArray(com.android.internal.R.array.config_ambientThresholdLevels))
+                .thenReturn(new int[]{30, 31});
+        when(mResources.getIntArray(com.android.internal.R.array.config_screenThresholdLevels))
+                .thenReturn(new int[]{42, 43});
+        when(mResources.getIntArray(
+                com.android.internal.R.array.config_ambientBrighteningThresholds))
+                .thenReturn(new int[]{270, 280, 290});
+        when(mResources.getIntArray(com.android.internal.R.array.config_ambientDarkeningThresholds))
+                .thenReturn(new int[]{290, 300, 310});
+        when(mResources.getIntArray(R.array.config_screenBrighteningThresholds))
+                .thenReturn(new int[]{350, 360, 370});
+        when(mResources.getIntArray(R.array.config_screenDarkeningThresholds))
+                .thenReturn(new int[]{370, 380, 390});
+
+        // Configs related to refresh rates and blocking zones
+        when(mResources.getInteger(R.integer.config_defaultPeakRefreshRate))
+                .thenReturn(DEFAULT_PEAK_REFRESH_RATE);
+        when(mResources.getInteger(R.integer.config_defaultRefreshRate))
+                .thenReturn(DEFAULT_REFRESH_RATE);
+        when(mResources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+            .thenReturn(DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
+        when(mResources.getInteger(R.integer.config_defaultRefreshRateInZone))
+            .thenReturn(DEFAULT_LOW_BLOCKING_ZONE_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);
+        when(mResources.getInteger(
+            R.integer.config_defaultRefreshRateInHbmHdr))
+            .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_HDR);
+        when(mResources.getInteger(
+            R.integer.config_defaultRefreshRateInHbmSunlight))
+            .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT);
+
+        mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
+    }
+
+    private TypedArray createFloatTypedArray(float[] vals) {
+        TypedArray mockArray = mock(TypedArray.class);
+        when(mockArray.length()).thenAnswer(invocation -> {
+            return vals.length;
+        });
+        when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
+            final float def = (float) invocation.getArguments()[1];
+            if (vals == null) {
+                return def;
+            }
+            int idx = (int) invocation.getArguments()[0];
+            if (idx >= 0 && idx < vals.length) {
+                return vals[idx];
+            } else {
+                return def;
+            }
+        });
+        return mockArray;
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
new file mode 100644
index 0000000..d16c9c5
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -0,0 +1,2319 @@
+/*
+ * 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.display;
+
+import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
+import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
+import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
+
+import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+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.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PropertyInvalidatedCache;
+import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.IVirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceManager;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.Curve;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayViewport;
+import android.hardware.display.DisplayedContentSample;
+import android.hardware.display.DisplayedContentSamplingAttributes;
+import android.hardware.display.HdrConversionMode;
+import android.hardware.display.IDisplayManagerCallback;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
+import android.media.projection.IMediaProjection;
+import android.media.projection.IMediaProjectionManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.MessageQueue;
+import android.os.Process;
+import android.os.RemoteException;
+import android.view.ContentRecordingSession;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.DisplayEventReceiver;
+import android.view.DisplayInfo;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.window.DisplayWindowPolicyController;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+import com.android.server.display.DisplayManagerService.DeviceStateListener;
+import com.android.server.display.DisplayManagerService.SyncRoot;
+import com.android.server.input.InputManagerInternal;
+import com.android.server.lights.LightsManager;
+import com.android.server.sensors.SensorManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.LongStream;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayManagerServiceTest {
+    private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
+    private static final long SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS = 10;
+
+    private static final float FLOAT_TOLERANCE = 0.01f;
+
+    private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display";
+    private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests";
+    private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
+                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    private Context mContext;
+
+    private int mHdrConversionMode;
+
+    private int mPreferredHdrOutputType;
+
+    private final DisplayManagerService.Injector mShortMockedInjector =
+            new DisplayManagerService.Injector() {
+                @Override
+                VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot,
+                        Context context, Handler handler, DisplayAdapter.Listener listener) {
+                    return mMockVirtualDisplayAdapter;
+                }
+
+                @Override
+                LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
+                        Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+                    return new LocalDisplayAdapter(syncRoot, context, handler,
+                            displayAdapterListener, new LocalDisplayAdapter.Injector() {
+                        @Override
+                        public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
+                            return mSurfaceControlProxy;
+                        }
+                    });
+                }
+
+                @Override
+                long getDefaultDisplayDelayTimeout() {
+                    return SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS;
+                }
+            };
+
+    class BasicInjector extends DisplayManagerService.Injector {
+        @Override
+        IMediaProjectionManager getProjectionService() {
+            return mMockProjectionService;
+        }
+
+        @Override
+        VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
+                Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+            return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
+                    new VirtualDisplayAdapter.SurfaceControlDisplayFactory() {
+                        @Override
+                        public IBinder createDisplay(String name, boolean secure,
+                                float requestedRefreshRate) {
+                            return mMockDisplayToken;
+                        }
+
+                        @Override
+                        public void destroyDisplay(IBinder displayToken) {
+                        }
+                    });
+        }
+
+        @Override
+        LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
+                Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+            return new LocalDisplayAdapter(syncRoot, context, handler,
+                    displayAdapterListener, new LocalDisplayAdapter.Injector() {
+                        @Override
+                        public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
+                            return mSurfaceControlProxy;
+                        }
+                    });
+        }
+
+        @Override
+        int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
+                int[] autoHdrTypes) {
+            mHdrConversionMode = conversionMode;
+            mPreferredHdrOutputType = preferredHdrOutputType;
+            return Display.HdrCapabilities.HDR_TYPE_INVALID;
+        }
+
+        @Override
+        int[] getSupportedHdrOutputTypes() {
+            return new int[]{};
+        }
+
+        @Override
+        int[] getHdrOutputTypesWithLatency() {
+            return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION};
+        }
+
+        boolean getHdrOutputConversionSupport() {
+            return true;
+        }
+    }
+
+    private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
+
+    @Mock IMediaProjectionManager mMockProjectionService;
+    @Mock IVirtualDeviceManager mIVirtualDeviceManager;
+    @Mock InputManagerInternal mMockInputManagerInternal;
+    @Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
+    @Mock IVirtualDisplayCallback.Stub mMockAppToken;
+    @Mock IVirtualDisplayCallback.Stub mMockAppToken2;
+    @Mock WindowManagerInternal mMockWindowManagerInternal;
+    @Mock LightsManager mMockLightsManager;
+    @Mock VirtualDisplayAdapter mMockVirtualDisplayAdapter;
+    @Mock LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy;
+    @Mock IBinder mMockDisplayToken;
+    @Mock SensorManagerInternal mMockSensorManagerInternal;
+
+    @Mock SensorManager mSensorManager;
+
+    @Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
+
+    @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        LocalServices.removeServiceForTest(InputManagerInternal.class);
+        LocalServices.addService(InputManagerInternal.class, mMockInputManagerInternal);
+        LocalServices.removeServiceForTest(WindowManagerInternal.class);
+        LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerInternal);
+        LocalServices.removeServiceForTest(LightsManager.class);
+        LocalServices.addService(LightsManager.class, mMockLightsManager);
+        LocalServices.removeServiceForTest(SensorManagerInternal.class);
+        LocalServices.addService(SensorManagerInternal.class, mMockSensorManagerInternal);
+        LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+        LocalServices.addService(
+                VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);
+        // TODO: b/287945043
+        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+
+        VirtualDeviceManager vdm = new VirtualDeviceManager(mIVirtualDeviceManager, mContext);
+        when(mContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
+        // Disable binder caches in this process.
+        PropertyInvalidatedCache.disableForTestMode();
+        setUpDisplay();
+    }
+
+    private void setUpDisplay() {
+        long[] ids = new long[] {100};
+        when(mSurfaceControlProxy.getPhysicalDisplayIds()).thenReturn(ids);
+        when(mSurfaceControlProxy.getPhysicalDisplayToken(anyLong()))
+                .thenReturn(mMockDisplayToken);
+        SurfaceControl.StaticDisplayInfo staticDisplayInfo = new SurfaceControl.StaticDisplayInfo();
+        staticDisplayInfo.isInternal = true;
+        when(mSurfaceControlProxy.getStaticDisplayInfo(anyLong()))
+                .thenReturn(staticDisplayInfo);
+        SurfaceControl.DynamicDisplayInfo dynamicDisplayMode =
+                new SurfaceControl.DynamicDisplayInfo();
+        SurfaceControl.DisplayMode displayMode = new SurfaceControl.DisplayMode();
+        displayMode.width = 100;
+        displayMode.height = 200;
+        displayMode.supportedHdrTypes = new int[]{1, 2};
+        dynamicDisplayMode.supportedDisplayModes = new SurfaceControl.DisplayMode[] {displayMode};
+        when(mSurfaceControlProxy.getDynamicDisplayInfo(anyLong()))
+                .thenReturn(dynamicDisplayMode);
+        when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(mMockDisplayToken))
+                .thenReturn(new SurfaceControl.DesiredDisplayModeSpecs());
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_sentToInputManager() throws RemoteException {
+        // This is to update the display device config such that DisplayManagerService can ignore
+        // the usage of SensorManager, which is available only after the PowerManagerService
+        // is ready.
+        resetConfigToIgnoreSensorManager(mContext);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.systemReady(false /* safeMode */);
+        displayManager.windowManagerAndInputReady();
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        String uniqueId = "uniqueId --- Test";
+        String uniqueIdPrefix = UNIQUE_ID_PREFIX + mContext.getPackageName() + ":";
+        int width = 600;
+        int height = 800;
+        int dpi = 320;
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setUniqueId(uniqueId);
+        builder.setFlags(flags);
+        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
+                null /* projection */, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+        ArgumentCaptor<List<DisplayViewport>> viewportCaptor = ArgumentCaptor.forClass(List.class);
+        verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
+        List<DisplayViewport> viewports = viewportCaptor.getValue();
+
+        // Expect to receive at least 2 viewports: at least 1 internal, and 1 virtual
+        assertTrue(viewports.size() >= 2);
+
+        DisplayViewport virtualViewport = null;
+        DisplayViewport internalViewport = null;
+        for (int i = 0; i < viewports.size(); i++) {
+            DisplayViewport v = viewports.get(i);
+            switch (v.type) {
+                case DisplayViewport.VIEWPORT_INTERNAL: {
+                    // If more than one internal viewport, this will get overwritten several times,
+                    // which for the purposes of this test is fine.
+                    internalViewport = v;
+                    assertTrue(internalViewport.valid);
+                    break;
+                }
+                case DisplayViewport.VIEWPORT_EXTERNAL: {
+                    // External view port is present for auto devices in the form of instrument
+                    // cluster.
+                    break;
+                }
+                case DisplayViewport.VIEWPORT_VIRTUAL: {
+                    virtualViewport = v;
+                    break;
+                }
+            }
+        }
+        // INTERNAL viewport gets created upon access.
+        assertNotNull(internalViewport);
+        assertNotNull(virtualViewport);
+
+        // VIRTUAL
+        assertEquals(height, virtualViewport.deviceHeight);
+        assertEquals(width, virtualViewport.deviceWidth);
+        assertEquals(uniqueIdPrefix + uniqueId, virtualViewport.uniqueId);
+        assertEquals(displayId, virtualViewport.displayId);
+    }
+
+    @Test
+    public void testPhysicalViewports() {
+        // This is to update the display device config such that DisplayManagerService can ignore
+        // the usage of SensorManager, which is available only after the PowerManagerService
+        // is ready.
+        resetConfigToIgnoreSensorManager(mContext);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.systemReady(false /* safeMode */);
+        displayManager.windowManagerAndInputReady();
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        final int[] displayIds = bs.getDisplayIds(/* includeDisabled= */ true);
+        final int size = displayIds.length;
+        assertTrue(size > 0);
+
+        Map<Integer, Integer> expectedDisplayTypeToViewPortTypeMapping = Map.of(
+                Display.TYPE_INTERNAL, DisplayViewport.VIEWPORT_INTERNAL,
+                Display.TYPE_EXTERNAL, DisplayViewport.VIEWPORT_EXTERNAL
+        );
+        for (int i = 0; i < size; i++) {
+            DisplayInfo info = bs.getDisplayInfo(displayIds[i]);
+            assertTrue(expectedDisplayTypeToViewPortTypeMapping.keySet().contains(info.type));
+        }
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+        ArgumentCaptor<List<DisplayViewport>> viewportCaptor = ArgumentCaptor.forClass(List.class);
+        verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
+        List<DisplayViewport> viewports = viewportCaptor.getValue();
+
+        // Due to the nature of foldables, we may have a different number of viewports than
+        // displays, just verify there's at least one.
+        final int viewportSize = viewports.size();
+        assertTrue(viewportSize > 0);
+
+        // Now verify that each viewport's displayId is valid.
+        Arrays.sort(displayIds);
+        for (int i = 0; i < viewportSize; i++) {
+            DisplayViewport viewport = viewports.get(i);
+            assertNotNull(viewport);
+            DisplayInfo displayInfo = bs.getDisplayInfo(viewport.displayId);
+            assertTrue(expectedDisplayTypeToViewPortTypeMapping.get(displayInfo.type)
+                    == viewport.type);
+            assertTrue(viewport.valid);
+            assertTrue(Arrays.binarySearch(displayIds, viewport.displayId) >= 0);
+        }
+    }
+
+    @Test
+    public void testCreateVirtualDisplayRotatesWithContent() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        String uniqueId = "uniqueId --- Rotates With Content Test";
+        int width = 600;
+        int height = 800;
+        int dpi = 320;
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setFlags(flags);
+        builder.setUniqueId(uniqueId);
+        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
+                null /* projection */, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0);
+    }
+
+    @Test
+    public void testCreateVirtualDisplayOwnFocus() throws RemoteException {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        String uniqueId = "uniqueId --- Own Focus Test";
+        int width = 600;
+        int height = 800;
+        int dpi = 320;
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
+                | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+                PackageManager.PERMISSION_GRANTED);
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setFlags(flags);
+        builder.setUniqueId(uniqueId);
+        int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
+                /* projection= */ null, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0);
+    }
+
+    @Test
+    public void testCreateVirtualDisplayOwnFocus_nonTrustedDisplay() throws RemoteException {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        String uniqueId = "uniqueId --- Own Focus Test -- nonTrustedDisplay";
+        int width = 600;
+        int height = 800;
+        int dpi = 320;
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setFlags(flags);
+        builder.setUniqueId(uniqueId);
+        int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
+                /* projection= */ null, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) == 0);
+    }
+
+    /**
+     * Tests that the virtual display is created along-side the default display.
+     */
+    @Test
+    public void testStartVirtualDisplayWithDefaultDisplay_Succeeds() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+    }
+
+    /**
+     * Tests that we send the device state to window manager
+     */
+    @Test
+    public void testOnStateChanged_sendsStateChangedEventToWm() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+        displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+        DeviceStateListener listener = displayManager.new DeviceStateListener();
+        Handler handler = displayManager.getDisplayHandler();
+        IDisplayManagerCallback displayChangesCallback = registerDisplayChangeCallback(
+                displayManager);
+
+        listener.onStateChanged(123);
+        waitForIdleHandler(handler);
+
+        InOrder inOrder = inOrder(mMockWindowManagerInternal, displayChangesCallback);
+        // Verify there are no display events before WM call
+        inOrder.verify(displayChangesCallback, never()).onDisplayEvent(anyInt(), anyInt());
+        inOrder.verify(mMockWindowManagerInternal).onDisplayManagerReceivedDeviceState(123);
+    }
+
+    /**
+     * Tests that there should be a display change notification to WindowManager to update its own
+     * internal state for things like display cutout when nonOverrideDisplayInfo is changed.
+     */
+    @Test
+    public void testShouldNotifyChangeWhenNonOverrideDisplayInfoChanged() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        // Add the FakeDisplayDevice
+        FakeDisplayDevice displayDevice = new FakeDisplayDevice();
+        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+        displayDeviceInfo.width = 100;
+        displayDeviceInfo.height = 200;
+        displayDeviceInfo.supportedModes = new Display.Mode[1];
+        displayDeviceInfo.supportedModes[0] = new Display.Mode(1, 100, 200, 60f);
+        displayDeviceInfo.modeId = 1;
+        final Rect zeroRect = new Rect();
+        displayDeviceInfo.displayCutout = new DisplayCutout(
+                Insets.of(0, 10, 0, 0),
+                zeroRect, new Rect(0, 0, 10, 10), zeroRect, zeroRect);
+        displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
+        displayDeviceInfo.address = new TestUtils.TestDisplayAddress();
+        displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+
+        // Find the display id of the added FakeDisplayDevice
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        int displayId = getDisplayIdForDisplayDevice(displayManager, bs, displayDevice);
+        // Setup override DisplayInfo
+        DisplayInfo overrideInfo = bs.getDisplayInfo(displayId);
+        displayManager.setDisplayInfoOverrideFromWindowManagerInternal(displayId, overrideInfo);
+
+        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(
+                displayManager, bs, displayDevice);
+
+        // Simulate DisplayDevice change
+        DisplayDeviceInfo displayDeviceInfo2 = new DisplayDeviceInfo();
+        displayDeviceInfo2.copyFrom(displayDeviceInfo);
+        displayDeviceInfo2.displayCutout = null;
+        displayDevice.setDisplayDeviceInfo(displayDeviceInfo2);
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED);
+
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+        assertTrue(callback.mDisplayChangedCalled);
+    }
+
+    /**
+     * Tests that we get a Runtime exception when we cannot initialize the default display.
+     */
+    @Test
+    public void testStartVirtualDisplayWithDefDisplay_NoDefaultDisplay() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+
+        try {
+            displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+        } catch (RuntimeException e) {
+            return;
+        }
+        fail("Expected DisplayManager to throw RuntimeException when it cannot initialize the"
+                + " default display");
+    }
+
+    /**
+     * Tests that we get a Runtime exception when we cannot initialize the virtual display.
+     */
+    @Test
+    public void testStartVirtualDisplayWithDefDisplay_NoVirtualDisplayAdapter() throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext,
+                new DisplayManagerService.Injector() {
+                    @Override
+                    VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot,
+                            Context context, Handler handler, DisplayAdapter.Listener listener) {
+                        return null;  // return null for the adapter.  This should cause a failure.
+                    }
+
+                    @Override
+                    long getDefaultDisplayDelayTimeout() {
+                        return SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS;
+                    }
+                });
+        try {
+            displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+        } catch (RuntimeException e) {
+            return;
+        }
+        fail("Expected DisplayManager to throw RuntimeException when it cannot initialize the"
+                + " virtual display adapter");
+    }
+
+    /**
+     * Tests that an exception is raised for too dark a brightness configuration.
+     */
+    @Test
+    public void testTooDarkBrightnessConfigurationThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        Curve minimumBrightnessCurve = displayManager.getMinimumBrightnessCurveInternal();
+        float[] lux = minimumBrightnessCurve.getX();
+        float[] minimumNits = minimumBrightnessCurve.getY();
+        float[] nits = new float[minimumNits.length];
+        // For every control point, assert that making it slightly lower than the minimum throws an
+        // exception.
+        for (int i = 0; i < nits.length; i++) {
+            for (int j = 0; j < nits.length; j++) {
+                nits[j] = minimumNits[j];
+                if (j == i) {
+                    nits[j] -= 0.1f;
+                }
+                if (nits[j] < 0) {
+                    nits[j] = 0;
+                }
+            }
+            BrightnessConfiguration config =
+                    new BrightnessConfiguration.Builder(lux, nits).build();
+            Exception thrown = null;
+            try {
+                displayManager.validateBrightnessConfiguration(config);
+            } catch (IllegalArgumentException e) {
+                thrown = e;
+            }
+            assertNotNull("Building too dark a brightness configuration must throw an exception");
+        }
+    }
+
+    /**
+     * Tests that no exception is raised for not too dark a brightness configuration.
+     */
+    @Test
+    public void testBrightEnoughBrightnessConfigurationDoesNotThrowException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        Curve minimumBrightnessCurve = displayManager.getMinimumBrightnessCurveInternal();
+        float[] lux = minimumBrightnessCurve.getX();
+        float[] nits = minimumBrightnessCurve.getY();
+        BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits).build();
+        displayManager.validateBrightnessConfiguration(config);
+    }
+
+    /**
+     * Tests that null brightness configurations are alright.
+     */
+    @Test
+    public void testNullBrightnessConfiguration() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        displayManager.validateBrightnessConfiguration(null);
+    }
+
+    /**
+     * Tests that collection of display color sampling results are sensible.
+     */
+    @Test
+    public void testDisplayedContentSampling() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        registerDefaultDisplays(displayManager);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(0);
+        assertNotNull(ddi);
+
+        DisplayedContentSamplingAttributes attr =
+                displayManager.getDisplayedContentSamplingAttributesInternal(0);
+        if (attr == null) return; //sampling not supported on device, skip remainder of test.
+
+        boolean enabled = displayManager.setDisplayedContentSamplingEnabledInternal(0, true, 0, 0);
+        assertTrue(enabled);
+
+        displayManager.setDisplayedContentSamplingEnabledInternal(0, false, 0, 0);
+        DisplayedContentSample sample = displayManager.getDisplayedContentSampleInternal(0, 0, 0);
+        assertNotNull(sample);
+
+        long numPixels = ddi.width * ddi.height * sample.getNumFrames();
+        long[] samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL0);
+        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+
+        samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL1);
+        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+
+        samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL2);
+        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+
+        samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL3);
+        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
+    }
+
+    /**
+     * Tests that the virtual display is created with
+     * {@link VirtualDisplayConfig.Builder#setDisplayIdToMirror(int)}
+     */
+    @Test
+    @FlakyTest(bugId = 127687569)
+    public void testCreateVirtualDisplay_displayIdToMirror() throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        DisplayManagerService.LocalService localDisplayManager = displayManager.new LocalService();
+
+        final String uniqueId = "uniqueId --- displayIdToMirrorTest";
+        final int width = 600;
+        final int height = 800;
+        final int dpi = 320;
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setUniqueId(uniqueId);
+        final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        // The second virtual display requests to mirror the first virtual display.
+        final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
+        when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2);
+        final VirtualDisplayConfig.Builder builder2 = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi).setUniqueId(uniqueId2);
+        builder2.setUniqueId(uniqueId2);
+        builder2.setDisplayIdToMirror(firstDisplayId);
+        final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
+                mMockAppToken2 /* callback */, null /* projection */,
+                PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+        // The displayId to mirror should be a default display if there is none initially.
+        assertEquals(localDisplayManager.getDisplayIdToMirror(firstDisplayId),
+                Display.DEFAULT_DISPLAY);
+        assertEquals(localDisplayManager.getDisplayIdToMirror(secondDisplayId),
+                firstDisplayId);
+    }
+
+    /** Tests that the virtual device is created in a device display group. */
+    @Test
+    public void createVirtualDisplay_addsDisplaysToDeviceDisplayGroups() throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+
+        registerDefaultDisplays(displayManager);
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+        when(virtualDevice.getDeviceId()).thenReturn(1);
+        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+        // Create a first virtual display. A display group should be created for this display on the
+        // virtual device.
+        final VirtualDisplayConfig.Builder builder1 =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setUniqueId("uniqueId --- device display group 1");
+
+        int displayId1 =
+                localService.createVirtualDisplay(
+                        builder1.build(),
+                        mMockAppToken /* callback */,
+                        virtualDevice /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
+
+        // Create a second virtual display. This should be added to the previously created display
+        // group.
+        final VirtualDisplayConfig.Builder builder2 =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setUniqueId("uniqueId --- device display group 1");
+
+        int displayId2 =
+                localService.createVirtualDisplay(
+                        builder2.build(),
+                        mMockAppToken /* callback */,
+                        virtualDevice /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
+
+        assertEquals(
+                "Both displays should be added to the same displayGroup.",
+                displayGroupId1,
+                displayGroupId2);
+    }
+
+    /**
+     * Tests that the virtual display is not added to the device display group when
+     * VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is set.
+     */
+    @Test
+    public void createVirtualDisplay_addsDisplaysToOwnDisplayGroups() throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+
+        registerDefaultDisplays(displayManager);
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+        when(virtualDevice.getDeviceId()).thenReturn(1);
+        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+
+        // Create a first virtual display. A display group should be created for this display on the
+        // virtual device.
+        final VirtualDisplayConfig.Builder builder1 =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setUniqueId("uniqueId --- device display group");
+
+        int displayId1 =
+                localService.createVirtualDisplay(
+                        builder1.build(),
+                        mMockAppToken /* callback */,
+                        virtualDevice /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
+
+        // Create a second virtual display. With the flag VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
+        // the display should not be added to the previously created display group.
+        final VirtualDisplayConfig.Builder builder2 =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
+                        .setUniqueId("uniqueId --- own display group");
+
+        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+
+        int displayId2 =
+                localService.createVirtualDisplay(
+                        builder2.build(),
+                        mMockAppToken /* callback */,
+                        virtualDevice /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
+
+        assertNotEquals(
+                "Display 1 should be in the device display group and display 2 in its own display"
+                        + " group.",
+                displayGroupId1,
+                displayGroupId2);
+    }
+
+    @Test
+    public void displaysInDeviceOrOwnDisplayGroupShouldPreserveAlwaysUnlockedFlag()
+            throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+
+        registerDefaultDisplays(displayManager);
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+        when(virtualDevice.getDeviceId()).thenReturn(1);
+        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+
+        // Allow an ALWAYS_UNLOCKED display to be created.
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        // Create a virtual display in a device display group.
+        final VirtualDisplayConfig deviceDisplayGroupDisplayConfig =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setUniqueId("uniqueId --- device display group 1")
+                        .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
+                        .build();
+
+        int deviceDisplayGroupDisplayId =
+                localService.createVirtualDisplay(
+                        deviceDisplayGroupDisplayConfig,
+                        mMockAppToken /* callback */,
+                        virtualDevice /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        // Check that FLAG_ALWAYS_UNLOCKED is set.
+        assertNotEquals(
+                "FLAG_ALWAYS_UNLOCKED should be set for displays created in a device display"
+                        + " group.",
+                (displayManager.getDisplayDeviceInfoInternal(deviceDisplayGroupDisplayId).flags
+                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
+                0);
+
+        // Create a virtual display in its own display group.
+        final VirtualDisplayConfig ownDisplayGroupConfig =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setUniqueId("uniqueId --- own display group 1")
+                        .setFlags(
+                                VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
+                                        | VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
+                        .build();
+
+        int ownDisplayGroupDisplayId =
+                localService.createVirtualDisplay(
+                        ownDisplayGroupConfig,
+                        mMockAppToken /* callback */,
+                        virtualDevice /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        // Check that FLAG_ALWAYS_UNLOCKED is set.
+        assertNotEquals(
+                "FLAG_ALWAYS_UNLOCKED should be set for displays created in their own display"
+                        + " group.",
+                (displayManager.getDisplayDeviceInfoInternal(ownDisplayGroupDisplayId).flags
+                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
+                0);
+
+        // Create a virtual display in a device display group.
+        final VirtualDisplayConfig defaultDisplayGroupConfig =
+                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+                        .setUniqueId("uniqueId --- default display group 1")
+                        .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
+                        .build();
+
+        int defaultDisplayGroupDisplayId =
+                localService.createVirtualDisplay(
+                        defaultDisplayGroupConfig,
+                        mMockAppToken /* callback */,
+                        null /* virtualDeviceToken */,
+                        mock(DisplayWindowPolicyController.class),
+                        PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        // Check that FLAG_ALWAYS_UNLOCKED is not set.
+        assertEquals(
+                "FLAG_ALWAYS_UNLOCKED should not be set for displays created in the default"
+                        + " display group.",
+                (displayManager.getDisplayDeviceInfoInternal(defaultDisplayGroupDisplayId).flags
+                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
+                0);
+    }
+
+    @Test
+    public void testGetDisplayIdToMirror() throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        DisplayManagerService.LocalService localDisplayManager = displayManager.new LocalService();
+
+        final String uniqueId = "uniqueId --- displayIdToMirrorTest";
+        final int width = 600;
+        final int height = 800;
+        final int dpi = 320;
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi)
+                .setUniqueId(uniqueId)
+                .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+        final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        // The second virtual display requests to mirror the first virtual display.
+        final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
+        when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2);
+        final VirtualDisplayConfig.Builder builder2 = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi)
+                .setUniqueId(uniqueId2)
+                .setWindowManagerMirroringEnabled(true);
+        final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
+                mMockAppToken2 /* callback */, null /* projection */,
+                PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+        // The displayId to mirror should be a invalid since the display had flag OWN_CONTENT_ONLY
+        assertEquals(localDisplayManager.getDisplayIdToMirror(firstDisplayId),
+                Display.INVALID_DISPLAY);
+        // The second display has mirroring managed by WindowManager so the mirror displayId should
+        // be invalid.
+        assertEquals(localDisplayManager.getDisplayIdToMirror(secondDisplayId),
+                Display.INVALID_DISPLAY);
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_isValidProjection_notValid()
+            throws RemoteException {
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        IMediaProjection projection = mock(IMediaProjection.class);
+        doReturn(false).when(projection).isValid();
+        when(mMockProjectionService
+                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
+                .thenReturn(true);
+        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- isValid false");
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        // Pass in a non-null projection.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, projection, PACKAGE_NAME);
+
+        // VirtualDisplay is created for mirroring.
+        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
+                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
+        // But mirroring doesn't begin.
+        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
+                mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
+        ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
+        assertThat(session.isWaitingForConsent()).isTrue();
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_setContentRecordingSessionSuccess()
+            throws RemoteException {
+        final int displayToRecord = 50;
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        IMediaProjection projection = mock(IMediaProjection.class);
+        doReturn(true).when(projection).isValid();
+        when(mMockProjectionService
+                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
+                .thenReturn(true);
+        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- setContentRecordingSession true");
+        builder.setDisplayIdToMirror(displayToRecord);
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, projection, PACKAGE_NAME);
+
+        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
+                mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
+        ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
+        assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_DISPLAY);
+        assertThat(session.getVirtualDisplayId()).isEqualTo(displayId);
+        assertThat(session.getDisplayToRecord()).isEqualTo(displayToRecord);
+        assertThat(session.isWaitingForConsent()).isFalse();
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_setContentRecordingSessionFail() throws RemoteException {
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        IMediaProjection projection = mock(IMediaProjection.class);
+        doReturn(true).when(projection).isValid();
+        when(mMockProjectionService
+                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
+                .thenReturn(false);
+        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, projection, PACKAGE_NAME);
+
+        assertThat(displayId).isEqualTo(Display.INVALID_DISPLAY);
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_setContentRecordingSession_taskSession()
+            throws RemoteException {
+        final int displayToRecord = 50;
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        IMediaProjection projection = mock(IMediaProjection.class);
+        doReturn(true).when(projection).isValid();
+        when(mMockProjectionService
+                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
+                .thenReturn(true);
+        doReturn(mock(IBinder.class)).when(projection).getLaunchCookie();
+        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+        builder.setDisplayIdToMirror(displayToRecord);
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, projection, PACKAGE_NAME);
+
+        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
+                mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
+        ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
+        assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_TASK);
+        assertThat(session.getVirtualDisplayId()).isEqualTo(displayId);
+        assertThat(session.getTokenToRecord()).isNotNull();
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noFlags()
+            throws RemoteException {
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        // Set no flags for the VirtualDisplay.
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        // Pass in a null projection.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+        // VirtualDisplay is created but not for mirroring.
+        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+        verify(mMockProjectionService, never()).setContentRecordingSession(
+                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noMirroringFlag()
+            throws RemoteException {
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        // Set a non-mirroring flag for the VirtualDisplay.
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+        builder.setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        // Pass in a null projection.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+        // VirtualDisplay is created but not for mirroring.
+        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+        verify(mMockProjectionService, never()).setContentRecordingSession(
+                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
+    }
+
+    @Test
+    public void testCreateVirtualDisplay_setContentRecordingSession_projection_noMirroringFlag()
+            throws RemoteException {
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        IMediaProjection projection = mock(IMediaProjection.class);
+        doReturn(true).when(projection).isValid();
+        when(mMockProjectionService
+                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
+                .thenReturn(true);
+        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
+
+        // Set no flags for the VirtualDisplay.
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.windowManagerAndInputReady();
+
+        // Pass in a non-null projection.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, projection, PACKAGE_NAME);
+
+        // VirtualDisplay is created for mirroring.
+        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
+                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
+    }
+
+    /**
+     * Tests that the virtual display is created with
+     * {@link VirtualDisplayConfig.Builder#setSurface(Surface)}
+     */
+    @Test
+    @FlakyTest(bugId = 127687569)
+    public void testCreateVirtualDisplay_setSurface() throws Exception {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+
+        final String uniqueId = "uniqueId --- setSurface";
+        final int width = 600;
+        final int height = 800;
+        final int dpi = 320;
+        final Surface surface = new Surface();
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setSurface(surface);
+        builder.setUniqueId(uniqueId);
+        final int displayId = binderService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+
+        assertEquals(displayManager.getVirtualDisplaySurfaceInternal(mMockAppToken), surface);
+    }
+
+    /**
+     * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when the permission
+     * ADD_TRUSTED_DISPLAY is granted.
+     */
+    @Test
+    public void testOwnDisplayGroup_allowCreationWithAddTrustedDisplayPermission()
+            throws RemoteException {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+                PackageManager.PERMISSION_GRANTED);
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP);
+        builder.setUniqueId("uniqueId --- OWN_DISPLAY_GROUP");
+
+        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
+                null /* projection */, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+    }
+
+    /**
+     * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is blocked when the permission
+     * ADD_TRUSTED_DISPLAY is denied.
+     */
+    @Test
+    public void testOwnDisplayGroup_withoutAddTrustedDisplayPermission_throwsSecurityException() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP);
+        builder.setUniqueId("uniqueId --- OWN_DISPLAY_GROUP");
+
+        try {
+            bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
+                    null /* projection */, PACKAGE_NAME);
+            fail("Creating virtual display with VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP without "
+                    + "ADD_TRUSTED_DISPLAY permission should throw SecurityException.");
+        } catch (SecurityException e) {
+            // SecurityException is expected
+        }
+    }
+
+    /**
+     * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when called with
+     * a virtual device, even if ADD_TRUSTED_DISPLAY is not granted.
+     */
+    @Test
+    public void testOwnDisplayGroup_allowCreationWithVirtualDevice()  throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+
+        registerDefaultDisplays(displayManager);
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+                PackageManager.PERMISSION_DENIED);
+
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+        builder.setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP);
+        builder.setUniqueId("uniqueId --- OWN_DISPLAY_GROUP");
+
+        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+        when(virtualDevice.getDeviceId()).thenReturn(1);
+        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+
+        int displayId = localService.createVirtualDisplay(builder.build(),
+                mMockAppToken /* callback */, virtualDevice /* virtualDeviceToken */,
+                mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+    }
+
+    /**
+     * Tests that there is a display change notification if the frame rate override
+     * list is updated.
+     */
+    @Test
+    public void testShouldNotifyChangeWhenDisplayInfoFrameRateOverrideChanged() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+                displayManagerBinderService, displayDevice);
+
+        int myUid = Process.myUid();
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
+                });
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
+                        new DisplayEventReceiver.FrameRateOverride(1234, 30f),
+                });
+        assertFalse(callback.mDisplayChangedCalled);
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(myUid, 20f),
+                        new DisplayEventReceiver.FrameRateOverride(1234, 30f),
+                        new DisplayEventReceiver.FrameRateOverride(5678, 30f),
+                });
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(1234, 30f),
+                        new DisplayEventReceiver.FrameRateOverride(5678, 30f),
+                });
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(5678, 30f),
+                });
+        assertFalse(callback.mDisplayChangedCalled);
+    }
+
+    /**
+     * Tests that the DisplayInfo is updated correctly with a frame rate override
+     */
+    @Test
+    public void testDisplayInfoFrameRateOverride() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid(), 20f),
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid() + 1, 30f)
+                });
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+
+        // Changing the mode to 30Hz should not override the refresh rate to 20Hz anymore
+        // as 20 is not a divider of 30.
+        updateModeId(displayManager, displayDevice, 2);
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(30f, displayInfo.getRefreshRate(), 0.01f);
+    }
+
+    /**
+     * Tests that the frame rate override is returning the correct value from
+     * DisplayInfo#getRefreshRate
+     */
+    @Test
+    public void testDisplayInfoNonNativeFrameRateOverride() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid(), 20f)
+                });
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+    }
+
+    /**
+     * Tests that the mode reflects the frame rate override is in compat mode
+     */
+    @Test
+    @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public  void testDisplayInfoFrameRateOverrideModeCompat() throws Exception {
+        testDisplayInfoFrameRateOverrideModeCompat(/*compatChangeEnabled*/ false);
+    }
+
+    /**
+     * Tests that the mode reflects the physical display refresh rate when not in compat mode.
+     */
+    @Test
+    @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public  void testDisplayInfoFrameRateOverrideMode() throws Exception {
+        testDisplayInfoFrameRateOverrideModeCompat(/*compatChangeEnabled*/ true);
+    }
+
+    /**
+     * Tests that the mode reflects the frame rate override is in compat mode and accordingly to the
+     * allowNonNativeRefreshRateOverride policy.
+     */
+    @Test
+    @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public void testDisplayInfoNonNativeFrameRateOverrideModeCompat() throws Exception {
+        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ false);
+    }
+
+    /**
+     * Tests that the mode reflects the physical display refresh rate when not in compat mode.
+     */
+    @Test
+    @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public void testDisplayInfoNonNativeFrameRateOverrideMode() throws Exception {
+        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ true);
+    }
+
+    /**
+     * Tests that there is a display change notification if the render frame rate is updated
+     */
+    @Test
+    public void testShouldNotifyChangeWhenDisplayInfoRenderFrameRateChanged() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+                displayManagerBinderService, displayDevice);
+
+        updateRenderFrameRate(displayManager, displayDevice, 30f);
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+
+        updateRenderFrameRate(displayManager, displayDevice, 30f);
+        assertFalse(callback.mDisplayChangedCalled);
+
+        updateRenderFrameRate(displayManager, displayDevice, 20f);
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+    }
+
+    /**
+     * Tests that the DisplayInfo is updated correctly with a render frame rate
+     */
+    @Test
+    public void testDisplayInfoRenderFrameRate() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateRenderFrameRate(displayManager, displayDevice, 20f);
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+    }
+
+    /**
+     * Tests that the mode reflects the render frame rate is in compat mode
+     */
+    @Test
+    @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public  void testDisplayInfoRenderFrameRateModeCompat() throws Exception {
+        testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ false);
+    }
+
+    /**
+     * Tests that the mode reflects the physical display refresh rate when not in compat mode.
+     */
+    @Test
+    @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public  void testDisplayInfoRenderFrameRateMode() throws Exception {
+        testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ true);
+    }
+
+    /**
+     * Tests that EVENT_DISPLAY_ADDED is sent when a display is added.
+     */
+    @Test
+    public void testShouldNotifyDisplayAdded_WhenNewDisplayDeviceIsAdded() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+
+        // register display listener callback
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        displayManagerBinderService.registerCallbackWithEventMask(
+                callback, STANDARD_DISPLAY_EVENTS);
+
+        waitForIdleHandler(handler);
+
+        createFakeDisplayDevice(displayManager, new float[]{60f});
+
+        waitForIdleHandler(handler);
+
+        assertFalse(callback.mDisplayChangedCalled);
+        assertFalse(callback.mDisplayRemovedCalled);
+        assertTrue(callback.mDisplayAddedCalled);
+    }
+
+    /**
+     * Tests that EVENT_DISPLAY_ADDED is not sent when a display is added and the
+     * client has a callback which is not subscribed to this event type.
+     */
+    @Test
+    public void testShouldNotNotifyDisplayAdded_WhenClientIsNotSubscribed() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+
+        // register display listener callback
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        long allEventsExceptDisplayAdded = STANDARD_DISPLAY_EVENTS
+                & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED;
+        displayManagerBinderService.registerCallbackWithEventMask(callback,
+                allEventsExceptDisplayAdded);
+
+        waitForIdleHandler(handler);
+
+        createFakeDisplayDevice(displayManager, new float[]{60f});
+
+        waitForIdleHandler(handler);
+
+        assertFalse(callback.mDisplayChangedCalled);
+        assertFalse(callback.mDisplayRemovedCalled);
+        assertFalse(callback.mDisplayAddedCalled);
+    }
+
+    /**
+     * Tests that EVENT_DISPLAY_REMOVED is sent when a display is removed.
+     */
+    @Test
+    public void testShouldNotifyDisplayRemoved_WhenDisplayDeviceIsRemoved() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f});
+
+        waitForIdleHandler(handler);
+
+        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(
+                displayManager, displayManagerBinderService, displayDevice);
+
+        waitForIdleHandler(handler);
+
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
+
+        waitForIdleHandler(handler);
+
+        assertFalse(callback.mDisplayChangedCalled);
+        assertTrue(callback.mDisplayRemovedCalled);
+        assertFalse(callback.mDisplayAddedCalled);
+    }
+
+    /**
+     * Tests that EVENT_DISPLAY_REMOVED is not sent when a display is added and the
+     * client has a callback which is not subscribed to this event type.
+     */
+    @Test
+    public void testShouldNotNotifyDisplayRemoved_WhenClientIsNotSubscribed() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f});
+
+        waitForIdleHandler(handler);
+
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        long allEventsExceptDisplayRemoved = STANDARD_DISPLAY_EVENTS
+                & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+        displayManagerBinderService.registerCallbackWithEventMask(callback,
+                allEventsExceptDisplayRemoved);
+
+        waitForIdleHandler(handler);
+
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
+
+        waitForIdleHandler(handler);
+
+        assertFalse(callback.mDisplayChangedCalled);
+        assertFalse(callback.mDisplayRemovedCalled);
+        assertFalse(callback.mDisplayAddedCalled);
+    }
+
+
+
+    @Test
+    public void testSettingTwoBrightnessConfigurationsOnMultiDisplay() {
+        Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+
+        // get the first two internal displays
+        Display[] displays = displayManager.getDisplays();
+        Display internalDisplayOne = null;
+        Display internalDisplayTwo = null;
+        for (Display display : displays) {
+            if (display.getType() == Display.TYPE_INTERNAL) {
+                if (internalDisplayOne == null) {
+                    internalDisplayOne = display;
+                } else {
+                    internalDisplayTwo = display;
+                    break;
+                }
+            }
+        }
+
+        // return if there are fewer than 2 displays on this device
+        if (internalDisplayOne == null || internalDisplayTwo == null) {
+            return;
+        }
+
+        final String uniqueDisplayIdOne = internalDisplayOne.getUniqueId();
+        final String uniqueDisplayIdTwo = internalDisplayTwo.getUniqueId();
+
+        BrightnessConfiguration configOne =
+                new BrightnessConfiguration.Builder(
+                        new float[]{0.0f, 12345.0f}, new float[]{15.0f, 400.0f})
+                        .setDescription("model:1").build();
+        BrightnessConfiguration configTwo =
+                new BrightnessConfiguration.Builder(
+                        new float[]{0.0f, 6789.0f}, new float[]{12.0f, 300.0f})
+                        .setDescription("model:2").build();
+
+        displayManager.setBrightnessConfigurationForDisplay(configOne,
+                uniqueDisplayIdOne);
+        displayManager.setBrightnessConfigurationForDisplay(configTwo,
+                uniqueDisplayIdTwo);
+
+        BrightnessConfiguration configFromOne =
+                displayManager.getBrightnessConfigurationForDisplay(uniqueDisplayIdOne);
+        BrightnessConfiguration configFromTwo =
+                displayManager.getBrightnessConfigurationForDisplay(uniqueDisplayIdTwo);
+
+        assertNotNull(configFromOne);
+        assertEquals(configOne, configFromOne);
+        assertEquals(configTwo, configFromTwo);
+
+    }
+
+    @Test
+    public void testHdrConversionModeEquals() {
+        assertEquals(
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2));
+        assertNotEquals(
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 3));
+        assertEquals(
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM),
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+        assertNotEquals(
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+    }
+
+    @Test
+    public void testCreateHdrConversionMode_withInvalidArguments_throwsException() {
+        assertThrows(
+                "preferredHdrOutputType must not be set if the conversion mode is "
+                        + "HDR_CONVERSION_PASSTHROUGH",
+                IllegalArgumentException.class,
+                () -> new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH,
+                        Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION));
+    }
+
+    @Test
+    public void testSetHdrConversionModeInternal_withInvalidArguments_throwsException() {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        assertThrows("Expected DisplayManager to throw IllegalArgumentException when "
+                        + "preferredHdrOutputType is set and the conversion mode is "
+                        + "HDR_CONVERSION_SYSTEM",
+                IllegalArgumentException.class,
+                () -> displayManager.setHdrConversionModeInternal(new HdrConversionMode(
+                        HdrConversionMode.HDR_CONVERSION_SYSTEM,
+                        Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION)));
+    }
+
+    @Test
+    public void testSetAndGetHdrConversionModeInternal() {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        final HdrConversionMode mode = new HdrConversionMode(
+                HdrConversionMode.HDR_CONVERSION_FORCE,
+                Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION);
+        displayManager.setHdrConversionModeInternal(mode);
+        assertEquals(mode, displayManager.getHdrConversionModeSettingInternal());
+        assertEquals(mode.getConversionMode(), mHdrConversionMode);
+        assertEquals(mode.getPreferredHdrOutputType(), mPreferredHdrOutputType);
+    }
+
+    @Test
+    public void testHdrConversionMode_withMinimalPostProcessing() {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+
+        final HdrConversionMode mode = new HdrConversionMode(
+                HdrConversionMode.HDR_CONVERSION_FORCE,
+                Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION);
+        displayManager.setHdrConversionModeInternal(mode);
+        assertEquals(mode, displayManager.getHdrConversionModeSettingInternal());
+
+        displayManager.setMinimalPostProcessingAllowed(true);
+        displayManager.setDisplayPropertiesInternal(displayId, false /* hasContent */,
+                30.0f /* requestedRefreshRate */,
+                displayDevice.getDisplayDeviceInfoLocked().modeId /* requestedModeId */,
+                30.0f /* requestedMinRefreshRate */, 120.0f /* requestedMaxRefreshRate */,
+                true /* preferMinimalPostProcessing */, false /* disableHdrConversion */,
+                true /* inTraversal */);
+
+        assertEquals(new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH),
+                displayManager.getHdrConversionModeInternal());
+
+        displayManager.setDisplayPropertiesInternal(displayId, false /* hasContent */,
+                30.0f /* requestedRefreshRate */,
+                displayDevice.getDisplayDeviceInfoLocked().modeId /* requestedModeId */,
+                30.0f /* requestedMinRefreshRate */, 120.0f /* requestedMaxRefreshRate */,
+                false /* preferMinimalPostProcessing */, false /* disableHdrConversion */,
+                true /* inTraversal */);
+        assertEquals(mode, displayManager.getHdrConversionModeInternal());
+    }
+
+    @Test
+    public void testReturnsRefreshRateForDisplayAndSensor_proximitySensorSet() {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        displayManager.overrideSensorManager(mSensorManager);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+        displayDevice.mDisplayDeviceConfig = mMockDisplayDeviceConfig;
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+
+        String testSensorName = "testName";
+        String testSensorType = "testType";
+        Sensor testSensor = TestUtils.createSensor(testSensorType, testSensorName);
+
+        DisplayDeviceConfig.SensorData sensorData = new DisplayDeviceConfig.SensorData();
+        sensorData.type = testSensorType;
+        sensorData.name = testSensorName;
+        sensorData.minRefreshRate = 10f;
+        sensorData.maxRefreshRate = 100f;
+
+        when(mMockDisplayDeviceConfig.getProximitySensor()).thenReturn(sensorData);
+        when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(Collections.singletonList(
+                testSensor));
+
+        SurfaceControl.RefreshRateRange result = localService.getRefreshRateForDisplayAndSensor(
+                displayId, testSensorName, testSensorType);
+
+        assertNotNull(result);
+        assertEquals(result.min, sensorData.minRefreshRate, FLOAT_TOLERANCE);
+        assertEquals(result.max, sensorData.maxRefreshRate, FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testReturnsRefreshRateForDisplayAndSensor_proximitySensorNotSet() {
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        displayManager.overrideSensorManager(mSensorManager);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+        displayDevice.mDisplayDeviceConfig = mMockDisplayDeviceConfig;
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+
+        String testSensorName = "testName";
+        String testSensorType = "testType";
+        Sensor testSensor = TestUtils.createSensor(testSensorType, testSensorName);
+
+        DisplayDeviceConfig.SensorData sensorData = new DisplayDeviceConfig.SensorData();
+        sensorData.type = testSensorType;
+        sensorData.name = testSensorName;
+        sensorData.minRefreshRate = 10f;
+        sensorData.maxRefreshRate = 100f;
+
+        when(mMockDisplayDeviceConfig.getProximitySensor()).thenReturn(null);
+        when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(Collections.singletonList(
+                testSensor));
+
+        SurfaceControl.RefreshRateRange result = localService.getRefreshRateForDisplayAndSensor(
+                displayId, testSensorName, testSensorType);
+
+        assertNull(result);
+    }
+
+    private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled)
+            throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid(), 20f),
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid() + 1, 30f)
+                });
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+        Display.Mode expectedMode;
+        if (compatChangeEnabled) {
+            expectedMode = new Display.Mode(1, 100, 200, 60f);
+        } else {
+            expectedMode = new Display.Mode(3, 100, 200, 20f);
+        }
+        assertEquals(expectedMode, displayInfo.getMode());
+    }
+
+    private void testDisplayInfoRenderFrameRateModeCompat(boolean compatChangeEnabled)
+            throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateRenderFrameRate(displayManager, displayDevice, 20f);
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+        Display.Mode expectedMode;
+        if (compatChangeEnabled) {
+            expectedMode = new Display.Mode(1, 100, 200, 60f);
+        } else {
+            expectedMode = new Display.Mode(3, 100, 200, 20f);
+        }
+        assertEquals(expectedMode, displayInfo.getMode());
+    }
+
+    private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateFrameRateOverride(displayManager, displayDevice,
+                new DisplayEventReceiver.FrameRateOverride[]{
+                        new DisplayEventReceiver.FrameRateOverride(
+                                Process.myUid(), 20f)
+                });
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        Display.Mode expectedMode;
+        if (compatChangeEnabled) {
+            expectedMode = new Display.Mode(1, 100, 200, 60f);
+        } else {
+            expectedMode = new Display.Mode(255, 100, 200, 20f);
+        }
+        assertEquals(expectedMode, displayInfo.getMode());
+    }
+
+    private int getDisplayIdForDisplayDevice(
+            DisplayManagerService displayManager,
+            DisplayManagerService.BinderService displayManagerBinderService,
+            FakeDisplayDevice displayDevice) {
+
+        final int[] displayIds = displayManagerBinderService.getDisplayIds(
+                /* includeDisabled= */ true);
+        assertTrue(displayIds.length > 0);
+        int displayId = Display.INVALID_DISPLAY;
+        for (int i = 0; i < displayIds.length; i++) {
+            DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayIds[i]);
+            if (displayDevice.getDisplayDeviceInfoLocked().equals(ddi)) {
+                displayId = displayIds[i];
+                break;
+            }
+        }
+        assertFalse(displayId == Display.INVALID_DISPLAY);
+        return displayId;
+    }
+
+    private void updateDisplayDeviceInfo(DisplayManagerService displayManager,
+            FakeDisplayDevice displayDevice,
+            DisplayDeviceInfo displayDeviceInfo) {
+        displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED);
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+    }
+
+    private void updateFrameRateOverride(DisplayManagerService displayManager,
+            FakeDisplayDevice displayDevice,
+            DisplayEventReceiver.FrameRateOverride[] frameRateOverrides) {
+        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+        displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
+        displayDeviceInfo.frameRateOverrides = frameRateOverrides;
+        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
+    }
+
+    private void updateRenderFrameRate(DisplayManagerService displayManager,
+            FakeDisplayDevice displayDevice,
+            float renderFrameRate) {
+        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+        displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
+        displayDeviceInfo.renderFrameRate = renderFrameRate;
+        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
+    }
+
+    private void updateModeId(DisplayManagerService displayManager,
+            FakeDisplayDevice displayDevice,
+            int modeId) {
+        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+        displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
+        displayDeviceInfo.modeId = modeId;
+        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
+    }
+
+    private IDisplayManagerCallback registerDisplayChangeCallback(
+            DisplayManagerService displayManager) {
+        IDisplayManagerCallback displayChangesCallback = mock(IDisplayManagerCallback.class);
+        when(displayChangesCallback.asBinder()).thenReturn(new Binder());
+        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+        binderService.registerCallback(displayChangesCallback);
+        return displayChangesCallback;
+    }
+
+    private FakeDisplayManagerCallback registerDisplayListenerCallback(
+            DisplayManagerService displayManager,
+            DisplayManagerService.BinderService displayManagerBinderService,
+            FakeDisplayDevice displayDevice) {
+        // Find the display id of the added FakeDisplayDevice
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+
+        Handler handler = displayManager.getDisplayHandler();
+        waitForIdleHandler(handler);
+
+        // register display listener callback
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(displayId);
+        displayManagerBinderService.registerCallbackWithEventMask(
+                callback, STANDARD_DISPLAY_EVENTS);
+        return callback;
+    }
+
+    private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
+            float[] refreshRates) {
+        FakeDisplayDevice displayDevice = new FakeDisplayDevice();
+        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+        int width = 100;
+        int height = 200;
+        displayDeviceInfo.supportedModes = new Display.Mode[refreshRates.length];
+        for (int i = 0; i < refreshRates.length; i++) {
+            displayDeviceInfo.supportedModes[i] =
+                    new Display.Mode(i + 1, width, height, refreshRates[i]);
+        }
+        displayDeviceInfo.modeId = 1;
+        displayDeviceInfo.renderFrameRate = displayDeviceInfo.supportedModes[0].getRefreshRate();
+        displayDeviceInfo.width = width;
+        displayDeviceInfo.height = height;
+        final Rect zeroRect = new Rect();
+        displayDeviceInfo.displayCutout = new DisplayCutout(
+                Insets.of(0, 10, 0, 0),
+                zeroRect, new Rect(0, 0, 10, 10), zeroRect, zeroRect);
+        displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
+        displayDeviceInfo.address = new TestUtils.TestDisplayAddress();
+        displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+        return displayDevice;
+    }
+
+    private void registerDefaultDisplays(DisplayManagerService displayManager) {
+        Handler handler = displayManager.getDisplayHandler();
+        // Would prefer to call displayManager.onStart() directly here but it performs binderService
+        // registration which triggers security exceptions when running from a test.
+        handler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS);
+        waitForIdleHandler(handler);
+    }
+
+    private void waitForIdleHandler(Handler handler) {
+        waitForIdleHandler(handler, Duration.ofSeconds(1));
+    }
+
+    private void waitForIdleHandler(Handler handler, Duration timeout) {
+        final MessageQueue queue = handler.getLooper().getQueue();
+        final CountDownLatch latch = new CountDownLatch(1);
+        queue.addIdleHandler(() -> {
+            latch.countDown();
+            // Remove idle handler
+            return false;
+        });
+        try {
+            latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("Interrupted unexpectedly: " + e);
+        }
+    }
+
+    private void resetConfigToIgnoreSensorManager(Context context) {
+        final Resources res = Mockito.spy(context.getResources());
+        doReturn(new int[]{-1}).when(res).getIntArray(R.array
+                .config_ambientThresholdsOfPeakRefreshRate);
+        doReturn(new int[]{-1}).when(res).getIntArray(R.array
+                .config_brightnessThresholdsOfPeakRefreshRate);
+        doReturn(new int[]{-1}).when(res).getIntArray(R.array
+                .config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
+        doReturn(new int[]{-1}).when(res).getIntArray(R.array
+                .config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+
+        when(context.getResources()).thenReturn(res);
+    }
+
+    private class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub {
+        int mDisplayId;
+        boolean mDisplayAddedCalled = false;
+        boolean mDisplayChangedCalled = false;
+        boolean mDisplayRemovedCalled = false;
+
+        FakeDisplayManagerCallback(int displayId) {
+            mDisplayId = displayId;
+        }
+
+        FakeDisplayManagerCallback() {
+            mDisplayId = -1;
+        }
+
+        @Override
+        public void onDisplayEvent(int displayId, int event) {
+            if (mDisplayId != -1 && displayId != mDisplayId) {
+                return;
+            }
+
+            if (event == DisplayManagerGlobal.EVENT_DISPLAY_ADDED) {
+                mDisplayAddedCalled = true;
+            }
+
+            if (event == DisplayManagerGlobal.EVENT_DISPLAY_CHANGED) {
+                mDisplayChangedCalled = true;
+            }
+
+            if (event == DisplayManagerGlobal.EVENT_DISPLAY_REMOVED) {
+                mDisplayRemovedCalled = true;
+            }
+        }
+
+        public void clear() {
+            mDisplayAddedCalled = false;
+            mDisplayChangedCalled = false;
+            mDisplayRemovedCalled = false;
+        }
+    }
+
+    private class FakeDisplayDevice extends DisplayDevice {
+        private DisplayDeviceInfo mDisplayDeviceInfo;
+
+        FakeDisplayDevice() {
+            super(null, null, "", mContext);
+        }
+
+        public void setDisplayDeviceInfo(DisplayDeviceInfo displayDeviceInfo) {
+            mDisplayDeviceInfo = displayDeviceInfo;
+        }
+
+        @Override
+        public boolean hasStableUniqueId() {
+            return false;
+        }
+
+        @Override
+        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+            return mDisplayDeviceInfo;
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
new file mode 100644
index 0000000..c39bb56
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -0,0 +1,1403 @@
+/*
+ * 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.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.testing.TestableContext;
+import android.util.FloatProperty;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.ExtendedMockitoRule;
+import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.layout.Layout;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerController2Test {
+    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+    private static final String UNIQUE_ID = "unique_id_test123";
+    private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1;
+    private static final String FOLLOWER_UNIQUE_ID = "unique_id_456";
+    private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
+    private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
+    private static final float PROX_SENSOR_MAX_RANGE = 5;
+    private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
+    private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
+    private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE = 0.1f;
+    private static final float BRIGHTNESS_RAMP_RATE_SLOW_INCREASE = 0.2f;
+
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+    private Handler mHandler;
+    private DisplayPowerControllerHolder mHolder;
+    private Sensor mProxSensor;
+
+    @Mock
+    private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
+    @Mock
+    private SensorManager mSensorManagerMock;
+    @Mock
+    private DisplayBlanker mDisplayBlankerMock;
+    @Mock
+    private BrightnessTracker mBrightnessTrackerMock;
+    @Mock
+    private WindowManagerPolicy mWindowManagerPolicyMock;
+    @Mock
+    private PowerManager mPowerManagerMock;
+    @Mock
+    private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
+    @Mock
+    private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
+    @Captor
+    private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule =
+            new ExtendedMockitoRule.Builder(this)
+                    .setStrictness(Strictness.LENIENT)
+                    .spyStatic(SystemProperties.class)
+                    .spyStatic(BatteryStatsService.class)
+                    .build();
+
+    @Before
+    public void setUp() throws Exception {
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+        mHandler = new Handler(mTestLooper.getLooper());
+
+        // Set some settings to minimize unexpected events and have a consistent starting state
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+        Settings.System.putFloatForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0, UserHandle.USER_CURRENT);
+
+        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
+                mCdsiMock);
+
+        mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
+
+        doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
+                SystemProperties.set(anyString(), any()));
+        doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+
+        setUpSensors();
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+    }
+
+    @Test
+    public void testReleaseProxSuspendBlockersOnExit() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 5));
+        advanceTime(1);
+
+        // two times, one for unfinished business and one for proximity
+        verify(mHolder.wakelockController, times(2)).acquireWakelock(
+                WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
+        verify(mHolder.wakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+
+        mHolder.dpc.stop();
+        advanceTime(1);
+        // two times, one for unfinished business and one for proximity
+        verify(mHolder.wakelockController, times(2)).acquireWakelock(
+                WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
+        verify(mHolder.wakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+    }
+
+    @Test
+    public void testScreenOffBecauseOfProximity() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        // Send a positive proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+        advanceTime(1);
+
+        // The display should have been turned off
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+        clearInvocations(mHolder.displayPowerState);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        // Send a negative proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor,
+                (int) PROX_SENSOR_MAX_RANGE + 1));
+        // Advance time by less than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+        advanceTime(1);
+
+        // The prox sensor is debounced so the display should not have been turned back on yet
+        verify(mHolder.displayPowerState, never()).setScreenState(Display.STATE_ON);
+
+        // Advance time by more than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+        advanceTime(1000);
+
+        // The display should have been turned back on
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+    }
+
+    @Test
+    public void testScreenOffBecauseOfProximity_ProxSensorGone() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        // Send a positive proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+        advanceTime(1);
+
+        // The display should have been turned off
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        // The display device changes and we no longer have a prox sensor
+        reset(mSensorManagerMock);
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+
+        advanceTime(1); // Run updatePowerState
+
+        // The display should have been turned back on and the listener should have been
+        // unregistered
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+        verify(mSensorManagerMock).unregisterListener(listener);
+    }
+
+    @Test
+    public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        final DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState
+        advanceTime(1);
+
+        verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
+                eq(mProxSensor), anyInt(), any(Handler.class));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        // Test different float scale values
+        float leadBrightness = 0.3f;
+        float followerBrightness = 0.4f;
+        float nits = 300;
+        when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(followerBrightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
+        listener.onBrightnessChanged(leadBrightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), anyFloat());
+        verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
+                anyFloat());
+
+        clearInvocations(mHolder.animator, followerDpc.animator);
+
+        // Test the same float scale value
+        float brightness = 0.6f;
+        nits = 600;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        float brightness = 0.3f;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        float brightness = 0.3f;
+        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        float brightness = 0.3f;
+        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_AutomaticBrightness() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        float leadBrightness = 0.1f;
+        float rawLeadBrightness = 0.3f;
+        float followerBrightness = 0.4f;
+        float nits = 300;
+        float ambientLux = 3000;
+        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
+                .thenReturn(rawLeadBrightness);
+        when(mHolder.automaticBrightnessController
+                .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+                .thenReturn(leadBrightness);
+        when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
+                .thenReturn(nits);
+        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(followerBrightness);
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        // One triggered by handleBrightnessModeChange, another triggered by setBrightnessToFollow
+        verify(followerDpc.hbmController, times(2)).onAmbientLuxChange(ambientLux);
+        verify(followerDpc.animator, times(2)).animateTo(eq(followerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
+        when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness);
+        clearInvocations(mHolder.animator, followerDpc.animator);
+
+        leadBrightness = 0.05f;
+        rawLeadBrightness = 0.2f;
+        followerBrightness = 0.3f;
+        nits = 200;
+        ambientLux = 2000;
+        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
+                .thenReturn(rawLeadBrightness);
+        when(mHolder.automaticBrightnessController
+                .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+                .thenReturn(leadBrightness);
+        when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
+                .thenReturn(nits);
+        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(followerBrightness);
+
+        mHolder.dpc.updateBrightness();
+        advanceTime(1); // Run updatePowerState
+
+        // The second time, the animation rate should be slow
+        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE));
+        verify(followerDpc.hbmController).onAmbientLuxChange(ambientLux);
+        verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
+        DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
+                FOLLOWER_UNIQUE_ID);
+        DisplayPowerControllerHolder secondFollowerDpc = createDisplayPowerController(
+                SECOND_FOLLOWER_DISPLAY_ID, SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(secondFollowerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        secondFollowerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        // Set the initial brightness on the DPC we're going to remove so we have a fixed value for
+        // it to return to.
+        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(followerDpc.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+        final float initialFollowerBrightness = 0.3f;
+        when(followerDpc.brightnessSetting.getBrightness()).thenReturn(initialFollowerBrightness);
+        followerListener.onBrightnessChanged(initialFollowerBrightness);
+        advanceTime(1);
+        verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(followerDpc.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerDpc.dpc);
+        clearInvocations(followerDpc.animator);
+
+        // Validate both followers are correctly registered and receiving brightness updates
+        float brightness = 0.6f;
+        float nits = 600;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(secondFollowerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        clearInvocations(mHolder.animator, followerDpc.animator, secondFollowerDpc.animator);
+
+        // Remove the first follower and validate it goes back to its original brightness.
+        mHolder.dpc.removeDisplayBrightnessFollower(followerDpc.dpc);
+        advanceTime(1);
+        verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+
+        when(followerDpc.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+        clearInvocations(followerDpc.animator);
+
+        // Change the brightness of the lead display and validate only the second follower responds
+        brightness = 0.7f;
+        nits = 700;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat());
+        verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
+        DisplayPowerControllerHolder followerHolder =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        DisplayPowerControllerHolder secondFollowerHolder =
+                createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
+                        SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(secondFollowerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for
+        // it to return to.
+        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener secondFollowerListener =
+                listenerCaptor.getValue();
+        final float initialFollowerBrightness = 0.3f;
+        when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
+                initialFollowerBrightness);
+        when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn(
+                initialFollowerBrightness);
+        followerListener.onBrightnessChanged(initialFollowerBrightness);
+        secondFollowerListener.onBrightnessChanged(initialFollowerBrightness);
+        advanceTime(1);
+        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(followerHolder.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+        when(secondFollowerHolder.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
+        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
+        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+
+        // Validate both followers are correctly registered and receiving brightness updates
+        float brightness = 0.6f;
+        float nits = 600;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(followerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(secondFollowerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
+
+        // Stop the lead DPC and validate that the followers go back to their original brightness.
+        mHolder.dpc.stop();
+        advanceTime(1);
+        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+    }
+
+    @Test
+    public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
+        // We should still set screen state for the default display
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+        verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
+
+        mHolder = createDisplayPowerController(42, UNIQUE_ID);
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+        verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
+
+        mHolder.dpc.onBootCompleted();
+        advanceTime(1); // Run updatePowerState
+        verify(mHolder.displayPowerState).setScreenState(anyInt());
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(true);
+
+        // The display turns on and we use the brightness value recommended by
+        // ScreenOffBrightnessSensorController
+        clearInvocations(mHolder.screenOffBrightnessSensorController);
+        float brightness = 0.14f;
+        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
+                .thenReturn(brightness);
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .getAutomaticScreenBrightness();
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(true);
+
+        // The display turns on and we use the brightness value recommended by
+        // ScreenOffBrightnessSensorController
+        clearInvocations(mHolder.screenOffBrightnessSensorController);
+        float brightness = 0.14f;
+        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
+                .thenReturn(brightness);
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .getAutomaticScreenBrightness();
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
+        // Tests are set up with manual brightness by default, so no need to set it here.
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsOn() {
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsAFollower() {
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, /* leadDisplayId= */ 42);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController).stop();
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_DisplayIsOn() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController)
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_DisplayIsInDoze() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController)
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_ManualBrightnessMode() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        // One triggered by the test, the other by handleBrightnessModeChange
+        verify(mHolder.automaticBrightnessController, times(2)).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController, times(2))
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_DisplayIsOff() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_OFF,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController).setAutoBrightnessEnabled(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_DisplayIsInDoze() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController).setAutoBrightnessEnabled(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_FollowerDisplay() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        mHolder.dpc.setBrightnessToFollow(0.3f, -1, 0, /* slowChange= */ false);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        // One triggered by the test, the other by handleBrightnessModeChange
+        verify(mHolder.automaticBrightnessController, times(2)).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ false
+        );
+
+        // HBM should be allowed for the follower display
+        verify(mHolder.hbmController)
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+    }
+
+    @Test
+    public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
+        float brightness = 0.3f;
+        float nits = 500;
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
+                true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+
+        mHolder.dpc.setBrightness(brightness);
+        verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
+
+        float newBrightness = 0.4f;
+        when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+        when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(newBrightness);
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+        // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
+        verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
+        float lux = 2000;
+        float brightness = 0.4f;
+        float nits = 500;
+        when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
+        when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
+        when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
+        when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1);
+        clearInvocations(mHolder.injector);
+
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        advanceTime(1);
+
+        verify(mHolder.injector).getAutomaticBrightnessController(
+                any(AutomaticBrightnessController.Callbacks.class),
+                any(Looper.class),
+                eq(mSensorManagerMock),
+                any(),
+                eq(mHolder.brightnessMappingStrategy),
+                anyInt(),
+                anyFloat(),
+                anyFloat(),
+                anyFloat(),
+                anyInt(),
+                anyInt(),
+                anyLong(),
+                anyLong(),
+                anyBoolean(),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                eq(mContext),
+                any(BrightnessRangeController.class),
+                any(BrightnessThrottler.class),
+                isNull(),
+                anyInt(),
+                anyInt(),
+                eq(lux),
+                eq(brightness)
+        );
+    }
+
+    @Test
+    public void testUpdateBrightnessThrottlingDataId() {
+        mHolder.display.getDisplayInfoLocked().thermalBrightnessThrottlingDataId =
+                "throttling-data-id";
+        clearInvocations(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig());
+
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig())
+                .getThermalBrightnessThrottlingDataMapByThrottlingId();
+    }
+
+    @Test
+    public void testDwbcCallsHappenOnHandler() {
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+        mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+        verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true);
+
+        // dispatch handler looper
+        advanceTime(1);
+        verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true);
+    }
+
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    private void setUpSensors() throws Exception {
+        mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
+                PROX_SENSOR_MAX_RANGE);
+        Sensor screenOffBrightnessSensor = TestUtils.createSensor(
+                Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
+    }
+
+    private SensorEventListener getSensorEventListener(Sensor sensor) {
+        verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
+                eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
+        return mSensorEventListenerCaptor.getValue();
+    }
+
+    private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
+            DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
+            boolean isEnabled) {
+        DisplayInfo info = new DisplayInfo();
+        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+        deviceInfo.uniqueId = uniqueId;
+
+        when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+        when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
+        when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+        when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
+        when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
+        when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+        when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+        when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
+        when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        name = null;
+                    }
+                });
+        when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+        when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
+        when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData());
+        when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_LIGHT;
+                        name = null;
+                    }
+                });
+        when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
+                .thenReturn(new int[0]);
+        when(displayDeviceConfigMock.getBrightnessRampFastDecrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE);
+        when(displayDeviceConfigMock.getBrightnessRampFastIncrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_INCREASE);
+        when(displayDeviceConfigMock.getBrightnessRampSlowDecrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE);
+        when(displayDeviceConfigMock.getBrightnessRampSlowIncrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE);
+    }
+
+    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+            String uniqueId) {
+        return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
+    }
+
+    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+            String uniqueId, boolean isEnabled) {
+        final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
+        final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
+        final AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        final WakelockController wakelockController = mock(WakelockController.class);
+        final BrightnessMappingStrategy brightnessMappingStrategy =
+                mock(BrightnessMappingStrategy.class);
+        final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
+        final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
+                mock(ScreenOffBrightnessSensorController.class);
+        final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
+
+        when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+
+        TestInjector injector = spy(new TestInjector(displayPowerState, animator,
+                automaticBrightnessController, wakelockController, brightnessMappingStrategy,
+                hysteresisLevels, screenOffBrightnessSensorController, hbmController));
+
+        final LogicalDisplay display = mock(LogicalDisplay.class);
+        final DisplayDevice device = mock(DisplayDevice.class);
+        final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
+        final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
+        final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
+
+        setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
+
+        final DisplayPowerController2 dpc = new DisplayPowerController2(
+                mContext, injector, mDisplayPowerCallbacksMock, mHandler,
+                mSensorManagerMock, mDisplayBlankerMock, display,
+                mBrightnessTrackerMock, brightnessSetting, () -> {},
+                hbmMetadata, /* bootCompleted= */ false);
+
+        return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
+                animator, automaticBrightnessController, wakelockController,
+                screenOffBrightnessSensorController, hbmController, hbmMetadata,
+                brightnessMappingStrategy, injector);
+    }
+
+    /**
+     * A class for holding a DisplayPowerController under test and all the mocks specifically
+     * related to it.
+     */
+    private static class DisplayPowerControllerHolder {
+        public final DisplayPowerController2 dpc;
+        public final LogicalDisplay display;
+        public final DisplayPowerState displayPowerState;
+        public final BrightnessSetting brightnessSetting;
+        public final DualRampAnimator<DisplayPowerState> animator;
+        public final AutomaticBrightnessController automaticBrightnessController;
+        public final WakelockController wakelockController;
+        public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
+        public final HighBrightnessModeController hbmController;
+        public final HighBrightnessModeMetadata hbmMetadata;
+        public final BrightnessMappingStrategy brightnessMappingStrategy;
+        public final DisplayPowerController2.Injector injector;
+
+        DisplayPowerControllerHolder(DisplayPowerController2 dpc, LogicalDisplay display,
+                DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
+                DualRampAnimator<DisplayPowerState> animator,
+                AutomaticBrightnessController automaticBrightnessController,
+                WakelockController wakelockController,
+                ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
+                HighBrightnessModeController hbmController,
+                HighBrightnessModeMetadata hbmMetadata,
+                BrightnessMappingStrategy brightnessMappingStrategy,
+                DisplayPowerController2.Injector injector) {
+            this.dpc = dpc;
+            this.display = display;
+            this.displayPowerState = displayPowerState;
+            this.brightnessSetting = brightnessSetting;
+            this.animator = animator;
+            this.automaticBrightnessController = automaticBrightnessController;
+            this.wakelockController = wakelockController;
+            this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
+            this.hbmController = hbmController;
+            this.hbmMetadata = hbmMetadata;
+            this.brightnessMappingStrategy = brightnessMappingStrategy;
+            this.injector = injector;
+        }
+    }
+
+    private class TestInjector extends DisplayPowerController2.Injector {
+        private final DisplayPowerState mDisplayPowerState;
+        private final DualRampAnimator<DisplayPowerState> mAnimator;
+        private final AutomaticBrightnessController mAutomaticBrightnessController;
+        private final WakelockController mWakelockController;
+        private final BrightnessMappingStrategy mBrightnessMappingStrategy;
+        private final HysteresisLevels mHysteresisLevels;
+        private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
+        private final HighBrightnessModeController mHighBrightnessModeController;
+
+        TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
+                AutomaticBrightnessController automaticBrightnessController,
+                WakelockController wakelockController,
+                BrightnessMappingStrategy brightnessMappingStrategy,
+                HysteresisLevels hysteresisLevels,
+                ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
+                HighBrightnessModeController highBrightnessModeController) {
+            mDisplayPowerState = dps;
+            mAnimator = animator;
+            mAutomaticBrightnessController = automaticBrightnessController;
+            mWakelockController = wakelockController;
+            mBrightnessMappingStrategy = brightnessMappingStrategy;
+            mHysteresisLevels = hysteresisLevels;
+            mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
+            mHighBrightnessModeController = highBrightnessModeController;
+        }
+
+        @Override
+        DisplayPowerController2.Clock getClock() {
+            return mClock::now;
+        }
+
+        @Override
+        DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+                int displayId, int displayState) {
+            return mDisplayPowerState;
+        }
+
+        @Override
+        DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+                FloatProperty<DisplayPowerState> firstProperty,
+                FloatProperty<DisplayPowerState> secondProperty) {
+            return mAnimator;
+        }
+
+        @Override
+        WakelockController getWakelockController(int displayId,
+                DisplayPowerCallbacks displayPowerCallbacks) {
+            return mWakelockController;
+        }
+
+        @Override
+        DisplayPowerProximityStateController getDisplayPowerProximityStateController(
+                WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
+                Looper looper, Runnable nudgeUpdatePowerState, int displayId,
+                SensorManager sensorManager) {
+            return new DisplayPowerProximityStateController(wakelockController,
+                    displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
+                    sensorManager,
+                    new DisplayPowerProximityStateController.Injector() {
+                        @Override
+                        DisplayPowerProximityStateController.Clock createClock() {
+                            return mClock::now;
+                        }
+                    });
+        }
+
+        @Override
+        AutomaticBrightnessController getAutomaticBrightnessController(
+                AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+                SensorManager sensorManager, Sensor lightSensor,
+                BrightnessMappingStrategy interactiveModeBrightnessMapper,
+                int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+                float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+                long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+                boolean resetAmbientLuxAfterWarmUpConfig,
+                HysteresisLevels ambientBrightnessThresholds,
+                HysteresisLevels screenBrightnessThresholds,
+                HysteresisLevels ambientBrightnessThresholdsIdle,
+                HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+                BrightnessRangeController brightnessRangeController,
+                BrightnessThrottler brightnessThrottler,
+                BrightnessMappingStrategy idleModeBrightnessMapper,
+                int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
+                float userBrightness) {
+            return mAutomaticBrightnessController;
+        }
+
+        @Override
+        BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+                DisplayDeviceConfig displayDeviceConfig,
+                DisplayWhiteBalanceController displayWhiteBalanceController) {
+            return mBrightnessMappingStrategy;
+        }
+
+        @Override
+        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+                float[] darkeningThresholdLevels, float minDarkeningThreshold,
+                float minBrighteningThreshold) {
+            return mHysteresisLevels;
+        }
+
+        @Override
+        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+                float[] darkeningThresholdLevels, float minDarkeningThreshold,
+                float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+            return mHysteresisLevels;
+        }
+
+        @Override
+        ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
+                SensorManager sensorManager, Sensor lightSensor, Handler handler,
+                ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
+                BrightnessMappingStrategy brightnessMapper) {
+            return mScreenOffBrightnessSensorController;
+        }
+
+        @Override
+        HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
+                int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
+                float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
+                HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
+                Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
+                Context context) {
+            return mHighBrightnessModeController;
+        }
+
+        @Override
+        DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+                SensorManager sensorManager, Resources resources) {
+            return mDisplayWhiteBalanceControllerMock;
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
new file mode 100644
index 0000000..0544376
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -0,0 +1,1375 @@
+/*
+ * 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.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.testing.TestableContext;
+import android.util.FloatProperty;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.ExtendedMockitoRule;
+import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.layout.Layout;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerControllerTest {
+    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+    private static final String UNIQUE_ID = "unique_id_test123";
+    private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1;
+    private static final String FOLLOWER_UNIQUE_ID = "unique_id_456";
+    private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
+    private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
+    private static final float PROX_SENSOR_MAX_RANGE = 5;
+    private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
+    private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
+    private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE = 0.1f;
+    private static final float BRIGHTNESS_RAMP_RATE_SLOW_INCREASE = 0.2f;
+
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+    private Handler mHandler;
+    private DisplayPowerControllerHolder mHolder;
+    private Sensor mProxSensor;
+
+    @Mock
+    private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
+    @Mock
+    private SensorManager mSensorManagerMock;
+    @Mock
+    private DisplayBlanker mDisplayBlankerMock;
+    @Mock
+    private BrightnessTracker mBrightnessTrackerMock;
+    @Mock
+    private WindowManagerPolicy mWindowManagerPolicyMock;
+    @Mock
+    private PowerManager mPowerManagerMock;
+    @Mock
+    private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
+    @Mock
+    private DisplayWhiteBalanceController mDisplayWhiteBalanceControllerMock;
+    @Captor
+    private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
+
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule =
+            new ExtendedMockitoRule.Builder(this)
+                    .setStrictness(Strictness.LENIENT)
+                    .spyStatic(SystemProperties.class)
+                    .spyStatic(BatteryStatsService.class)
+                    .build();
+
+    @Before
+    public void setUp() throws Exception {
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+        mHandler = new Handler(mTestLooper.getLooper());
+
+        // Set some settings to minimize unexpected events and have a consistent starting state
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+        Settings.System.putFloatForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0, UserHandle.USER_CURRENT);
+
+        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
+                mCdsiMock);
+
+        mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
+
+        doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
+                SystemProperties.set(anyString(), any()));
+        doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
+
+        setUpSensors();
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+    }
+
+    @Test
+    public void testReleaseProxSuspendBlockersOnExit() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 5));
+        advanceTime(1);
+
+        // two times, one for unfinished business and one for proximity
+        verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
+                mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+        verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
+                mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+
+        mHolder.dpc.stop();
+        advanceTime(1);
+
+        // two times, one for unfinished business and one for proximity
+        verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
+                mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+        verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
+                mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+    }
+
+    @Test
+    public void testScreenOffBecauseOfProximity() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        // Send a positive proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+        advanceTime(1);
+
+        // The display should have been turned off
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+        clearInvocations(mHolder.displayPowerState);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        // Send a negative proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor,
+                (int) PROX_SENSOR_MAX_RANGE + 1));
+        // Advance time by less than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+        advanceTime(1);
+
+        // The prox sensor is debounced so the display should not have been turned back on yet
+        verify(mHolder.displayPowerState, never()).setScreenState(Display.STATE_ON);
+
+        // Advance time by more than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
+        advanceTime(1000);
+
+        // The display should have been turned back on
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+    }
+
+    @Test
+    public void testScreenOffBecauseOfProximity_ProxSensorGone() throws Exception {
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // Send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(mProxSensor);
+        assertNotNull(listener);
+
+        // Send a positive proximity event
+        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
+        advanceTime(1);
+
+        // The display should have been turned off
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
+
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        // The display device changes and we no longer have a prox sensor
+        reset(mSensorManagerMock);
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+
+        advanceTime(1); // Run updatePowerState
+
+        // The display should have been turned back on and the listener should have been
+        // unregistered
+        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
+        verify(mSensorManagerMock).unregisterListener(listener);
+    }
+
+    @Test
+    public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        // send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState
+        advanceTime(1);
+
+        verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
+                eq(mProxSensor), anyInt(), any(Handler.class));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        // Test different float scale values
+        float leadBrightness = 0.3f;
+        float followerBrightness = 0.4f;
+        float nits = 300;
+        when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(followerBrightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
+        listener.onBrightnessChanged(leadBrightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness);
+        clearInvocations(mHolder.animator, followerDpc.animator);
+
+        // Test the same float scale value
+        float brightness = 0.6f;
+        nits = 600;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        float brightness = 0.3f;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        float brightness = 0.3f;
+        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        float brightness = 0.3f;
+        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
+                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowers_AutomaticBrightness() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        DisplayPowerControllerHolder followerDpc =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        float leadBrightness = 0.1f;
+        float rawLeadBrightness = 0.3f;
+        float followerBrightness = 0.4f;
+        float nits = 300;
+        float ambientLux = 3000;
+        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
+                .thenReturn(rawLeadBrightness);
+        when(mHolder.automaticBrightnessController
+                .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+                .thenReturn(leadBrightness);
+        when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
+                .thenReturn(nits);
+        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(followerBrightness);
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        // One triggered by handleBrightnessModeChange, another triggered by setBrightnessToFollow
+        verify(followerDpc.hbmController, times(2)).onAmbientLuxChange(ambientLux);
+        verify(followerDpc.animator, times(2)).animateTo(eq(followerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
+        when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(followerBrightness);
+        clearInvocations(mHolder.animator, followerDpc.animator);
+
+        leadBrightness = 0.05f;
+        rawLeadBrightness = 0.2f;
+        followerBrightness = 0.3f;
+        nits = 200;
+        ambientLux = 2000;
+        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
+                .thenReturn(rawLeadBrightness);
+        when(mHolder.automaticBrightnessController
+                .getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+                .thenReturn(leadBrightness);
+        when(mHolder.automaticBrightnessController.convertToNits(rawLeadBrightness))
+                .thenReturn(nits);
+        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(followerBrightness);
+
+        mHolder.dpc.updateBrightness();
+        advanceTime(1); // Run updatePowerState
+
+        // The second time, the animation rate should be slow
+        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE));
+        verify(followerDpc.hbmController).onAmbientLuxChange(ambientLux);
+        verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
+        DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
+                FOLLOWER_UNIQUE_ID);
+        DisplayPowerControllerHolder secondFollowerDpc = createDisplayPowerController(
+                SECOND_FOLLOWER_DISPLAY_ID, SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(secondFollowerDpc.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        secondFollowerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        // Set the initial brightness on the DPC we're going to remove so we have a fixed value for
+        // it to return to.
+        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(followerDpc.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+        final float initialFollowerBrightness = 0.3f;
+        when(followerDpc.brightnessSetting.getBrightness()).thenReturn(initialFollowerBrightness);
+        followerListener.onBrightnessChanged(initialFollowerBrightness);
+        advanceTime(1);
+        verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(followerDpc.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
+        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerDpc.dpc);
+        clearInvocations(followerDpc.animator);
+
+        // Validate both followers are correctly registered and receiving brightness updates
+        float brightness = 0.6f;
+        float nits = 600;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(followerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(secondFollowerDpc.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        clearInvocations(mHolder.animator, followerDpc.animator, secondFollowerDpc.animator);
+
+        // Remove the first follower and validate it goes back to its original brightness.
+        mHolder.dpc.removeDisplayBrightnessFollower(followerDpc.dpc);
+        advanceTime(1);
+        verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+
+        when(followerDpc.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+        clearInvocations(followerDpc.animator);
+
+        // Change the brightness of the lead display and validate only the second follower responds
+        brightness = 0.7f;
+        nits = 700;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerDpc.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat());
+        verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+    }
+
+    @Test
+    public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
+        DisplayPowerControllerHolder followerHolder =
+                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+        DisplayPowerControllerHolder secondFollowerHolder =
+                createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
+                        SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(followerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(secondFollowerHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+        // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for
+        // it to return to.
+        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+        verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+        BrightnessSetting.BrightnessSettingListener secondFollowerListener =
+                listenerCaptor.getValue();
+        final float initialFollowerBrightness = 0.3f;
+        when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
+                initialFollowerBrightness);
+        when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn(
+                initialFollowerBrightness);
+        followerListener.onBrightnessChanged(initialFollowerBrightness);
+        secondFollowerListener.onBrightnessChanged(initialFollowerBrightness);
+        advanceTime(1);
+        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(followerHolder.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+        when(secondFollowerHolder.displayPowerState.getScreenBrightness())
+                .thenReturn(initialFollowerBrightness);
+
+        mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
+        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
+        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+
+        // Validate both followers are correctly registered and receiving brightness updates
+        float brightness = 0.6f;
+        float nits = 600;
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(brightness);
+        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+        listener.onBrightnessChanged(brightness);
+        advanceTime(1); // Send messages, run updatePowerState
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+        verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(followerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        when(secondFollowerHolder.displayPowerState.getScreenBrightness()).thenReturn(brightness);
+        clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
+
+        // Stop the lead DPC and validate that the followers go back to their original brightness.
+        mHolder.dpc.stop();
+        advanceTime(1);
+        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE));
+        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+    }
+
+    @Test
+    public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
+        // We should still set screen state for the default display
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+        verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
+
+        mHolder = createDisplayPowerController(42, UNIQUE_ID);
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+        verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
+
+        mHolder.dpc.onBootCompleted();
+        advanceTime(1); // Run updatePowerState
+        verify(mHolder.displayPowerState).setScreenState(anyInt());
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(true);
+
+        // The display turns on and we use the brightness value recommended by
+        // ScreenOffBrightnessSensorController
+        clearInvocations(mHolder.screenOffBrightnessSensorController);
+        float brightness = 0.14f;
+        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
+                .thenReturn(brightness);
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .getAutomaticScreenBrightness();
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(true);
+
+        // The display turns on and we use the brightness value recommended by
+        // ScreenOffBrightnessSensorController
+        clearInvocations(mHolder.screenOffBrightnessSensorController);
+        float brightness = 0.14f;
+        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
+                .thenReturn(brightness);
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .getAutomaticScreenBrightness();
+        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
+        // Tests are set up with manual brightness by default, so no need to set it here.
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsOn() {
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsAFollower() {
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, /* leadDisplayId= */ 42);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
+                .setLightSensorEnabled(false);
+    }
+
+    @Test
+    public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.screenOffBrightnessSensorController).stop();
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_DisplayIsOn() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController)
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_DisplayIsInDoze() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController)
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_ManualBrightnessMode() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        // One triggered by the test, the other by handleBrightnessModeChange
+        verify(mHolder.automaticBrightnessController, times(2)).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController, times(2))
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_DisplayIsOff() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_OFF;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_OFF,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController).setAutoBrightnessEnabled(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_DisplayIsInDoze() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_DOZE,
+                /* shouldResetShortTermModel= */ false
+        );
+        verify(mHolder.hbmController).setAutoBrightnessEnabled(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+    }
+
+    @Test
+    public void testAutoBrightnessDisabled_FollowerDisplay() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        mHolder.dpc.setBrightnessToFollow(0.3f, -1, 0, /* slowChange= */ false);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        // One triggered by the test, the other by handleBrightnessModeChange
+        verify(mHolder.automaticBrightnessController, times(2)).configure(
+                AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+                /* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                /* userChangedBrightness= */ false, /* adjustment= */ 0,
+                /* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+                /* shouldResetShortTermModel= */ false
+        );
+
+        // HBM should be allowed for the follower display
+        verify(mHolder.hbmController)
+                .setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED);
+    }
+
+    @Test
+    public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
+        float brightness = 0.3f;
+        float nits = 500;
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
+                true);
+
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+
+        mHolder.dpc.setBrightness(brightness);
+        verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
+
+        float newBrightness = 0.4f;
+        when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+        when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(newBrightness);
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+        // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
+        verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
+        float lux = 2000;
+        float brightness = 0.4f;
+        float nits = 500;
+        when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
+        when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
+        when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
+        when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1);
+        clearInvocations(mHolder.injector);
+
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        advanceTime(1);
+
+        verify(mHolder.injector).getAutomaticBrightnessController(
+                any(AutomaticBrightnessController.Callbacks.class),
+                any(Looper.class),
+                eq(mSensorManagerMock),
+                any(),
+                eq(mHolder.brightnessMappingStrategy),
+                anyInt(),
+                anyFloat(),
+                anyFloat(),
+                anyFloat(),
+                anyInt(),
+                anyInt(),
+                anyLong(),
+                anyLong(),
+                anyBoolean(),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                any(HysteresisLevels.class),
+                eq(mContext),
+                any(BrightnessRangeController.class),
+                any(BrightnessThrottler.class),
+                isNull(),
+                anyInt(),
+                anyInt(),
+                eq(lux),
+                eq(brightness)
+        );
+    }
+
+    @Test
+    public void testUpdateBrightnessThrottlingDataId() {
+        mHolder.display.getDisplayInfoLocked().thermalBrightnessThrottlingDataId =
+                "throttling-data-id";
+        clearInvocations(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig());
+
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig())
+                .getThermalBrightnessThrottlingDataMapByThrottlingId();
+    }
+
+    @Test
+    public void testDwbcCallsHappenOnHandler() {
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+        mHolder.dpc.setAutomaticScreenBrightnessMode(true);
+        verify(mDisplayWhiteBalanceControllerMock, never()).setStrongModeEnabled(true);
+
+        // dispatch handler looper
+        advanceTime(1);
+        verify(mDisplayWhiteBalanceControllerMock, times(1)).setStrongModeEnabled(true);
+    }
+
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    private void setUpSensors() throws Exception {
+        mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
+                PROX_SENSOR_MAX_RANGE);
+        Sensor screenOffBrightnessSensor = TestUtils.createSensor(
+                Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
+    }
+
+    private SensorEventListener getSensorEventListener(Sensor sensor) {
+        verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
+                eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
+        return mSensorEventListenerCaptor.getValue();
+    }
+
+    private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
+            DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
+            boolean isEnabled) {
+        DisplayInfo info = new DisplayInfo();
+        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+        deviceInfo.uniqueId = uniqueId;
+
+        when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+        when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
+        when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+        when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
+        when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
+        when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+        when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+        when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
+        when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        name = null;
+                    }
+                });
+        when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+        when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
+        when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData());
+        when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_LIGHT;
+                        name = null;
+                    }
+                });
+        when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
+                .thenReturn(new int[0]);
+        when(displayDeviceConfigMock.getBrightnessRampFastDecrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE);
+        when(displayDeviceConfigMock.getBrightnessRampFastIncrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_INCREASE);
+        when(displayDeviceConfigMock.getBrightnessRampSlowDecrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_DECREASE);
+        when(displayDeviceConfigMock.getBrightnessRampSlowIncrease())
+                .thenReturn(BRIGHTNESS_RAMP_RATE_SLOW_INCREASE);
+    }
+
+    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+            String uniqueId) {
+        return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
+    }
+
+    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+            String uniqueId, boolean isEnabled) {
+        final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
+        final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
+        final AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        final BrightnessMappingStrategy brightnessMappingStrategy =
+                mock(BrightnessMappingStrategy.class);
+        final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
+        final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
+                mock(ScreenOffBrightnessSensorController.class);
+        final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
+
+        when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+
+        DisplayPowerController.Injector injector = spy(new TestInjector(displayPowerState, animator,
+                automaticBrightnessController, brightnessMappingStrategy, hysteresisLevels,
+                screenOffBrightnessSensorController, hbmController));
+
+        final LogicalDisplay display = mock(LogicalDisplay.class);
+        final DisplayDevice device = mock(DisplayDevice.class);
+        final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
+        final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
+        final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
+
+        setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
+
+        final DisplayPowerController dpc = new DisplayPowerController(
+                mContext, injector, mDisplayPowerCallbacksMock, mHandler,
+                mSensorManagerMock, mDisplayBlankerMock, display,
+                mBrightnessTrackerMock, brightnessSetting, () -> {},
+                hbmMetadata, /* bootCompleted= */ false);
+
+        return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
+                animator, automaticBrightnessController, screenOffBrightnessSensorController,
+                hbmController, hbmMetadata, brightnessMappingStrategy, injector);
+    }
+
+    /**
+     * A class for holding a DisplayPowerController under test and all the mocks specifically
+     * related to it.
+     */
+    private static class DisplayPowerControllerHolder {
+        public final DisplayPowerController dpc;
+        public final LogicalDisplay display;
+        public final DisplayPowerState displayPowerState;
+        public final BrightnessSetting brightnessSetting;
+        public final DualRampAnimator<DisplayPowerState> animator;
+        public final AutomaticBrightnessController automaticBrightnessController;
+        public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
+        public final HighBrightnessModeController hbmController;
+        public final HighBrightnessModeMetadata hbmMetadata;
+        public final BrightnessMappingStrategy brightnessMappingStrategy;
+        public final DisplayPowerController.Injector injector;
+
+        DisplayPowerControllerHolder(DisplayPowerController dpc, LogicalDisplay display,
+                DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
+                DualRampAnimator<DisplayPowerState> animator,
+                AutomaticBrightnessController automaticBrightnessController,
+                ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
+                HighBrightnessModeController hbmController,
+                HighBrightnessModeMetadata hbmMetadata,
+                BrightnessMappingStrategy brightnessMappingStrategy,
+                DisplayPowerController.Injector injector) {
+            this.dpc = dpc;
+            this.display = display;
+            this.displayPowerState = displayPowerState;
+            this.brightnessSetting = brightnessSetting;
+            this.animator = animator;
+            this.automaticBrightnessController = automaticBrightnessController;
+            this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
+            this.hbmController = hbmController;
+            this.hbmMetadata = hbmMetadata;
+            this.brightnessMappingStrategy = brightnessMappingStrategy;
+            this.injector = injector;
+        }
+    }
+
+    private class TestInjector extends DisplayPowerController.Injector {
+        private final DisplayPowerState mDisplayPowerState;
+        private final DualRampAnimator<DisplayPowerState> mAnimator;
+        private final AutomaticBrightnessController mAutomaticBrightnessController;
+        private final BrightnessMappingStrategy mBrightnessMappingStrategy;
+        private final HysteresisLevels mHysteresisLevels;
+        private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
+        private final HighBrightnessModeController mHighBrightnessModeController;
+
+        TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
+                AutomaticBrightnessController automaticBrightnessController,
+                BrightnessMappingStrategy brightnessMappingStrategy,
+                HysteresisLevels hysteresisLevels,
+                ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
+                HighBrightnessModeController highBrightnessModeController) {
+            mDisplayPowerState = dps;
+            mAnimator = animator;
+            mAutomaticBrightnessController = automaticBrightnessController;
+            mBrightnessMappingStrategy = brightnessMappingStrategy;
+            mHysteresisLevels = hysteresisLevels;
+            mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
+            mHighBrightnessModeController = highBrightnessModeController;
+        }
+
+        @Override
+        DisplayPowerController.Clock getClock() {
+            return mClock::now;
+        }
+
+        @Override
+        DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+                int displayId, int displayState) {
+            return mDisplayPowerState;
+        }
+
+        @Override
+        DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+                FloatProperty<DisplayPowerState> firstProperty,
+                FloatProperty<DisplayPowerState> secondProperty) {
+            return mAnimator;
+        }
+
+        @Override
+        AutomaticBrightnessController getAutomaticBrightnessController(
+                AutomaticBrightnessController.Callbacks callbacks, Looper looper,
+                SensorManager sensorManager, Sensor lightSensor,
+                BrightnessMappingStrategy interactiveModeBrightnessMapper,
+                int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
+                float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
+                long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
+                boolean resetAmbientLuxAfterWarmUpConfig,
+                HysteresisLevels ambientBrightnessThresholds,
+                HysteresisLevels screenBrightnessThresholds,
+                HysteresisLevels ambientBrightnessThresholdsIdle,
+                HysteresisLevels screenBrightnessThresholdsIdle, Context context,
+                BrightnessRangeController brightnessRangeController,
+                BrightnessThrottler brightnessThrottler,
+                BrightnessMappingStrategy idleModeBrightnessMapper,
+                int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
+                float userBrightness) {
+            return mAutomaticBrightnessController;
+        }
+
+        @Override
+        BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
+                DisplayDeviceConfig displayDeviceConfig,
+                DisplayWhiteBalanceController displayWhiteBalanceController) {
+            return mBrightnessMappingStrategy;
+        }
+
+        @Override
+        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+                float[] darkeningThresholdLevels, float minDarkeningThreshold,
+                float minBrighteningThreshold) {
+            return mHysteresisLevels;
+        }
+
+        @Override
+        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
+                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
+                float[] darkeningThresholdLevels, float minDarkeningThreshold,
+                float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
+            return mHysteresisLevels;
+        }
+
+        @Override
+        ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
+                SensorManager sensorManager, Sensor lightSensor, Handler handler,
+                ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
+                BrightnessMappingStrategy brightnessMapper) {
+            return mScreenOffBrightnessSensorController;
+        }
+
+        @Override
+        HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
+                int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
+                float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
+                HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
+                Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
+                Context context) {
+            return mHighBrightnessModeController;
+        }
+
+        @Override
+        DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
+                SensorManager sensorManager, Resources resources) {
+            return mDisplayWhiteBalanceControllerMock;
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
new file mode 100644
index 0000000..534a708
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
@@ -0,0 +1,453 @@
+/*
+ * 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.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.test.TestLooper;
+import android.view.Display;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerProximityStateControllerTest {
+    @Mock
+    WakelockController mWakelockController;
+
+    @Mock
+    DisplayDeviceConfig mDisplayDeviceConfig;
+
+    @Mock
+    Runnable mNudgeUpdatePowerState;
+
+    @Mock
+    SensorManager mSensorManager;
+
+    private Sensor mProximitySensor;
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+    private SensorEventListener mSensorEventListener;
+    private DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+
+    @Before
+    public void before() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        // This is kept null because currently there is no way to define a sensor
+                        // name in TestUtils
+                        name = null;
+                    }
+                });
+        setUpProxSensor();
+        DisplayPowerProximityStateController.Injector injector =
+                new DisplayPowerProximityStateController.Injector() {
+                    @Override
+                    DisplayPowerProximityStateController.Clock createClock() {
+                        return mClock::now;
+                    }
+                };
+        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+                mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
+                mSensorManager, injector);
+        mSensorEventListener = mDisplayPowerProximityStateController.getProximitySensorListener();
+    }
+
+    @Test
+    public void updatePendingProximityRequestsWorksAsExpectedWhenPending() {
+        // Set the system to pending wait for proximity
+        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        assertTrue(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Update the pending proximity wait request
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertTrue(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+    }
+
+    @Test
+    public void updatePendingProximityRequestsWorksAsExpectedWhenNotPending() {
+        // Will not wait or be in the pending wait state of not already pending
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+    }
+
+    @Test
+    public void updatePendingProximityRequestsWorksAsExpectedWhenPendingAndProximityIgnored()
+            throws Exception {
+        // Set the system to the state where it will ignore proximity unless changed
+        enableProximitySensor();
+        emitAndValidatePositiveProximityEvent();
+        mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+        advanceTime();
+        assertTrue(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        verify(mNudgeUpdatePowerState, times(2)).run();
+
+        // Do not set the system to pending wait for proximity
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Set the system to pending wait for proximity. But because the proximity is being
+        // ignored, it will not wait or not set the pending wait
+        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+    }
+
+    @Test
+    public void cleanupDisablesTheProximitySensor() {
+        enableProximitySensor();
+        mDisplayPowerProximityStateController.cleanup();
+        verify(mSensorManager).unregisterListener(
+                mSensorEventListener);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    @Test
+    public void isProximitySensorAvailableReturnsTrueWhenAvailable() {
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void isProximitySensorAvailableReturnsFalseWhenNotAvailableAndNoDefault() {
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = null;
+                        name = null;
+                    }
+                });
+        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+                mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
+                mSensorManager, null);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void isProximitySensorAvailableReturnsTrueWhenNotAvailableAndHasDefault()
+            throws Exception {
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = null;
+                        name = null;
+                    }
+                });
+        when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
+                TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity"));
+        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+                mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
+                mSensorManager, null);
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void isProximitySensorAvailableReturnsFalseWhenNotAvailableHasDefaultNonDefaultDisplay()
+            throws Exception {
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = null;
+                        name = null;
+                    }
+                });
+        when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
+                TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity"));
+        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+                mNudgeUpdatePowerState, 1,
+                mSensorManager, null);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void isProximitySensorAvailableReturnsTrueWhenNoSensorConfigured() throws Exception {
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(null);
+        when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
+                TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY));
+
+        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+                mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
+                mSensorManager, null);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception {
+        DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class);
+        when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        name = null;
+                    }
+                });
+        Sensor newProxSensor = TestUtils.createSensor(
+                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f);
+        when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(newProxSensor));
+        mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(
+                updatedDisplayDeviceConfig);
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+    }
+
+    @Test
+    public void setPendingWaitForNegativeProximityLockedWorksAsExpected() {
+        // Doesn't do anything not asked to wait
+        assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                false));
+        assertFalse(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Sets pending wait negative proximity if not already waiting
+        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        assertTrue(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+        // Will not set pending wait negative proximity if already waiting
+        assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+                true));
+        assertTrue(
+                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+    }
+
+    @Test
+    public void evaluateProximityStateWhenRequestedUseOfProximitySensor() throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Again evaluate the proximity state, with system having positive proximity
+        setScreenOffBecauseOfPositiveProximityState();
+    }
+
+    @Test
+    public void evaluateProximityStateWhenScreenOffBecauseOfPositiveProximity() throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Again evaluate the proximity state, with system having positive proximity
+        setScreenOffBecauseOfPositiveProximityState();
+
+        // Set the system to pending wait for proximity
+        mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(true);
+        // Update the pending proximity wait request
+        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+
+        // Start ignoring proximity sensor
+        mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+        // Re-evaluate the proximity state, such that the system is detecting the positive
+        // proximity, and screen is off because of that
+        when(mWakelockController.getOnProximityNegativeRunnable()).thenReturn(mock(Runnable.class));
+        mDisplayPowerProximityStateController.updateProximityState(mock(
+                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+        assertTrue(
+                mDisplayPowerProximityStateController
+                        .shouldSkipRampBecauseOfProximityChangeToNegative());
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
+    }
+
+    @Test
+    public void evaluateProximityStateWhenDisplayIsTurningOff() throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Again evaluate the proximity state, with system having positive proximity
+        setScreenOffBecauseOfPositiveProximityState();
+
+        // Re-evaluate the proximity state, such that the system is detecting the positive
+        // proximity, and screen is off because of that
+        mDisplayPowerProximityStateController.updateProximityState(mock(
+                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_OFF);
+        verify(mSensorManager).unregisterListener(
+                mSensorEventListener);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    @Test
+    public void evaluateProximityStateNotWaitingForNegativeProximityAndNotUsingProxSensor()
+            throws Exception {
+        // Enable the proximity sensor
+        enableProximitySensor();
+
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        emitAndValidatePositiveProximityEvent();
+
+        // Re-evaluate the proximity state, such that the system is detecting the positive
+        // proximity, and screen is off because of that
+        mDisplayPowerProximityStateController.updateProximityState(mock(
+                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+        verify(mSensorManager).unregisterListener(
+                mSensorEventListener);
+        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    private void advanceTime() {
+        mClock.fastForward(1);
+        mTestLooper.dispatchAll();
+    }
+
+    private void setUpProxSensor() throws Exception {
+        mProximitySensor = TestUtils.createSensor(
+                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 5.0f);
+        when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(mProximitySensor));
+    }
+
+    private void emitAndValidatePositiveProximityEvent() throws Exception {
+        // Emit a positive proximity event to move the system to a state to mimic a scenario
+        // where the system is in positive proximity
+        when(mWakelockController.releaseWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+        mSensorEventListener.onSensorChanged(TestUtils.createSensorEvent(mProximitySensor, 4));
+        verify(mSensorManager).registerListener(mSensorEventListener,
+                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+                mDisplayPowerProximityStateController.getHandler());
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertEquals(mDisplayPowerProximityStateController.getProximity(),
+                DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+        verify(mNudgeUpdatePowerState).run();
+        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+    }
+
+    // Call evaluateProximityState with the request for using the proximity sensor. This will
+    // register the proximity sensor listener, which will be needed for mocking positive
+    // proximity scenarios.
+    private void enableProximitySensor() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.useProximitySensor = true;
+        mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+                Display.STATE_ON);
+        verify(mSensorManager).registerListener(
+                mSensorEventListener,
+                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+                mDisplayPowerProximityStateController.getHandler());
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+        verifyZeroInteractions(mWakelockController);
+    }
+
+    private void setScreenOffBecauseOfPositiveProximityState() {
+        // Prepare a request to indicate that the proximity sensor is to be used
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.useProximitySensor = true;
+
+        Runnable onProximityPositiveRunnable = mock(Runnable.class);
+        when(mWakelockController.getOnProximityPositiveRunnable()).thenReturn(
+                onProximityPositiveRunnable);
+
+        mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+                Display.STATE_ON);
+        verify(mSensorManager).registerListener(
+                mSensorEventListener,
+                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+                mDisplayPowerProximityStateController.getHandler());
+        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+        assertTrue(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+        verify(mWakelockController).acquireWakelock(
+                WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/HbmEventTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/HbmEventTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/HbmEventTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
new file mode 100644
index 0000000..76e6ec7
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -0,0 +1,756 @@
+/*
+ * 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.server.display;
+
+import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
+import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
+import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
+
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
+import static com.android.server.display.DisplayDeviceConfig.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
+import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.display.BrightnessInfo;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.test.TestLooper;
+import android.test.mock.MockContentResolver;
+import android.util.MathUtils;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
+import com.android.server.display.HighBrightnessModeController.Injector;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HighBrightnessModeControllerTest {
+
+    private static final int MINIMUM_LUX = 100;
+    private static final float TRANSITION_POINT = 0.763f;
+    private static final long TIME_WINDOW_MILLIS = 55 * 1000;
+    private static final long TIME_ALLOWED_IN_WINDOW_MILLIS = 12 * 1000;
+    private static final long TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS = 5 * 1000;
+    private static final boolean ALLOW_IN_LOW_POWER_MODE = false;
+
+    private static final float DEFAULT_MIN = 0.01f;
+    private static final float DEFAULT_MAX = 0.80f;
+
+    private static final int DISPLAY_WIDTH = 900;
+    private static final int DISPLAY_HEIGHT = 1600;
+
+    private static final float EPSILON = 0.000001f;
+
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+    private Handler mHandler;
+    private Binder mDisplayToken;
+    private String mDisplayUniqueId;
+    private Context mContextSpy;
+    private HighBrightnessModeMetadata mHighBrightnessModeMetadata;
+
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+    @Mock Injector mInjectorMock;
+    @Mock HighBrightnessModeController.HdrBrightnessDeviceConfig mHdrBrightnessDeviceConfigMock;
+
+    private static final HighBrightnessModeData DEFAULT_HBM_DATA =
+            new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS,
+                    TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS,
+                    ALLOW_IN_LOW_POWER_MODE, HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT);
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+        mDisplayToken = null;
+        mDisplayUniqueId = "unique_id";
+
+        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
+        when(mContextSpy.getContentResolver()).thenReturn(resolver);
+    }
+
+    /////////////////
+    // Test Methods
+    /////////////////
+
+    @Test
+    public void testNoHbmData() {
+        initHandler(null);
+        final HighBrightnessModeController hbmc = new HighBrightnessModeController(
+                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
+                mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {},
+                null, mContextSpy);
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
+        assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
+    }
+
+    @Test
+    public void testNoHbmData_Enabled() {
+        initHandler(null);
+        final HighBrightnessModeController hbmc = new HighBrightnessModeController(
+                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
+                mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {},
+                null, mContextSpy);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
+        assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
+    }
+
+    @Test
+    public void testOffByDefault() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_NoLux() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_LowLux() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_HighLux() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+    }
+
+    @Test
+    public void testAutoBrightnessEnabled_HighLux_ThenDisable() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_DISABLED);
+
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testWithinHighRange_thenOverTime_thenEarnBackTime() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+
+        // Verify we are in HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        // Use up all the time in the window.
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS);
+
+        // Verify we are not out of HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        // Shift time so that the HBM event is at the beginning of the current window
+        advanceTime(TIME_WINDOW_MILLIS - TIME_ALLOWED_IN_WINDOW_MILLIS);
+        // Shift time again so that we are just below the minimum allowable
+        advanceTime(TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS - 1);
+
+        // Verify we are not out of HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        // Advance the necessary millisecond
+        advanceTime(1);
+
+        // Verify we are allowed HBM again.
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+    }
+
+    @Test
+    public void testInHBM_ThenLowLux() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+
+        // Verify we are in HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
+
+        // Verify we are in HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        hbmc.onAmbientLuxChange(1);
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1);
+
+        // Verify we are out of HBM
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+
+    }
+
+    @Test
+    public void testInHBM_TestMultipleEvents_DueToAutoBrightness() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
+
+        // Verify we are in HBM
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT - 0.01f);
+        advanceTime(1);
+
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
+
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        advanceTime(2);
+
+        // Now we should be out again.
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testInHBM_TestMultipleEvents_DueToLux() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+
+        // Go into HBM for half the allowed window
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        // Move lux below threshold (ending first event);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT);
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+
+        // Move up some amount of time so that there's still time in the window even after a
+        // second event.
+        advanceTime((TIME_WINDOW_MILLIS - TIME_ALLOWED_IN_WINDOW_MILLIS) / 2);
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+
+        // Go into HBM for just under the second half of allowed window
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 1);
+        advanceTime((TIME_ALLOWED_IN_WINDOW_MILLIS / 2) - 1);
+
+        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
+
+        // Now exhaust the time
+        advanceTime(2);
+        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
+    }
+
+    @Test
+    public void testHbmIsNotTurnedOffInHighThermalState() throws Exception {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+
+        // Disabled thermal throttling
+        hbmc.onBrightnessChanged(/*brightness=*/ 1f, /*unthrottledBrightness*/ 1f,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+
+        assertFalse(hbmc.isThermalThrottlingActive());
+
+        // Try to go into HBM mode
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        advanceTime(1);
+
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+
+        // Enable thermal throttling
+        hbmc.onBrightnessChanged(/*brightness=*/ TRANSITION_POINT - 0.01f,
+                /*unthrottledBrightness*/ 1f, BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL);
+        advanceTime(10);
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        assertTrue(hbmc.isThermalThrottlingActive());
+
+        // Disabled thermal throttling
+        hbmc.onBrightnessChanged(/*brightness=*/ 1f, /*unthrottledBrightness*/ 1f,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+        advanceTime(1);
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        assertFalse(hbmc.isThermalThrottlingActive());
+    }
+
+    @Test
+    public void testHdrRequires50PercentOfScreen() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+
+        final int layerWidth = DISPLAY_WIDTH;
+        final int smallLayerHeight = DISPLAY_HEIGHT / 2 - 1; // height to use for <50%
+        final int largeLayerHeight = DISPLAY_HEIGHT / 2 + 1; // height to use for >50%
+
+        // ensure hdr doesn't turn on if layer is too small
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                layerWidth, smallLayerHeight, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
+
+        // Now check with layer larger than 50%
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                layerWidth, largeLayerHeight, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
+    }
+
+    @Test
+    public void testHdrRespectsMaxDesiredHdrSdrRatio() {
+        final HighBrightnessModeController hbmc = new TestHbmBuilder()
+                .setClock(new OffsettableClock())
+                .setHdrBrightnessConfig(mHdrBrightnessDeviceConfigMock)
+                .build();
+
+        // Passthrough return the max desired hdr/sdr ratio
+        when(mHdrBrightnessDeviceConfigMock.getHdrBrightnessFromSdr(anyFloat(), anyFloat()))
+                .thenAnswer(i -> i.getArgument(1));
+
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 2.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(2.0f, hbmc.getHdrBrightnessValue(), EPSILON);
+
+        // The hdr ratio cannot be less than 1.
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 0.5f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(1.0f, hbmc.getHdrBrightnessValue(), EPSILON);
+
+        // The hdr ratio can be as much as positive infinity
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/,
+                Float.POSITIVE_INFINITY /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(Float.POSITIVE_INFINITY, hbmc.getHdrBrightnessValue(), 0.0);
+    }
+
+
+    @Test
+    public void testHdrTrumpsSunlight() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+
+        // Turn on sunlight
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        assertEquals(DEFAULT_MAX, hbmc.getCurrentBrightnessMax(), EPSILON);
+
+        // turn on hdr
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
+        assertEquals(TRANSITION_POINT, hbmc.getCurrentBrightnessMax(), EPSILON);
+    }
+
+    @Test
+    public void testHdrBrightnessLimitSameAsNormalLimit() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+
+        // Check limit when HBM is off
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
+        assertEquals(TRANSITION_POINT, hbmc.getCurrentBrightnessMax(), EPSILON);
+
+        // Check limit with HBM is set to HDR
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
+        assertEquals(TRANSITION_POINT, hbmc.getCurrentBrightnessMax(), EPSILON);
+    }
+
+    @Test
+    public void testHdrBrightnessScaledNormalBrightness() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
+
+        // verify things are scaled for 0.5f
+        float brightness = 0.5f;
+        float expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT,
+                DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range
+        hbmcOnBrightnessChanged(hbmc, brightness);
+        advanceTime(0);
+        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
+
+        // Try another value
+        brightness = 0.33f;
+        expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT,
+                DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range
+        hbmcOnBrightnessChanged(hbmc, brightness);
+        advanceTime(0);
+        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
+
+        // Try the min value
+        brightness = DEFAULT_MIN;
+        expectedHdrBrightness = DEFAULT_MIN;
+        hbmcOnBrightnessChanged(hbmc, brightness);
+        advanceTime(0);
+        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
+
+        // Try the max value
+        brightness = TRANSITION_POINT;
+        expectedHdrBrightness = DEFAULT_MAX;
+        hbmcOnBrightnessChanged(hbmc, brightness);
+        advanceTime(0);
+        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
+    }
+
+    @Test
+    public void testHbmStats_StateChange() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT);
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
+
+        // Verify Stats HBM_ON_HDR
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/,
+                0, 0, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+
+        // Verify Stats HBM_OFF
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+
+        // Verify Stats HBM_ON_SUNLIGHT
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmc.onAmbientLuxChange(1);
+        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1);
+
+        // Verify Stats HBM_OFF
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP));
+    }
+
+    @Test
+    public void testHbmStats_NbmHdrNoReport() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
+
+        // Verify Stats HBM_ON_HDR not report
+        verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
+                anyInt());
+    }
+
+    @Test
+    public void testHbmStats_HighLuxLowBrightnessNoReport() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
+        advanceTime(0);
+        // verify in HBM sunlight mode
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+
+        // Verify Stats HBM_ON_SUNLIGHT not report
+        verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                anyInt());
+    }
+
+    // Test reporting of thermal throttling when triggered externally through
+    // HighBrightnessModeController.onBrightnessChanged()
+    @Test
+    public void testHbmStats_ExternalThermalOff() throws Exception {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+        final float hbmBrightness = TRANSITION_POINT + 0.01f;
+        final float nbmBrightness = TRANSITION_POINT - 0.01f;
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        // Brightness is unthrottled, HBM brightness granted
+        hbmc.onBrightnessChanged(hbmBrightness, hbmBrightness,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+        advanceTime(1);
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        // Brightness is thermally throttled, HBM brightness denied (NBM brightness granted)
+        hbmc.onBrightnessChanged(nbmBrightness, hbmBrightness,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL);
+        advanceTime(1);
+        // We expect HBM mode to remain set to sunlight, indicating that HBMC *allows* this mode.
+        // However, we expect the HBM state reported by HBMC to be off, since external thermal
+        // throttling (reported to HBMC through onBrightnessChanged()) lowers brightness to below
+        // the HBM transition point.
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT));
+    }
+
+    @Test
+    public void testHbmStats_TimeOut() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(0);
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        // Use up all the time in the window.
+        advanceTime(TIME_WINDOW_MILLIS + 1);
+
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT));
+    }
+
+    @Test
+    public void testHbmStats_DisplayOff() {
+        final HighBrightnessModeController hbmc = createDefaultHbm();
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(0);
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF));
+    }
+
+    @Test
+    public void testHbmStats_HdrPlaying() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(0);
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
+        advanceTime(0);
+
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING));
+    }
+
+    @Test
+    public void tetHbmStats_LowRequestedBrightness() {
+        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
+        final int displayStatsId = mDisplayUniqueId.hashCode();
+
+        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
+        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
+        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
+        advanceTime(0);
+        // verify in HBM sunlight mode
+        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
+        // verify HBM_ON_SUNLIGHT
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
+
+        hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
+        // verify HBM_SV_OFF due to LOW_REQUESTED_BRIGHTNESS
+        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
+                eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
+                eq(FrameworkStatsLog
+                        .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS));
+    }
+
+    private void assertState(HighBrightnessModeController hbmc,
+            float brightnessMin, float brightnessMax, int hbmMode) {
+        assertEquals(brightnessMin, hbmc.getCurrentBrightnessMin(), EPSILON);
+        assertEquals(brightnessMax, hbmc.getCurrentBrightnessMax(), EPSILON);
+        assertEquals(hbmMode, hbmc.getHighBrightnessMode());
+    }
+
+    private class TestHbmBuilder {
+        OffsettableClock mClock;
+        HighBrightnessModeController.HdrBrightnessDeviceConfig mHdrBrightnessCfg;
+
+        TestHbmBuilder setClock(OffsettableClock clock) {
+            mClock = clock;
+            return this;
+        }
+
+        TestHbmBuilder setHdrBrightnessConfig(
+                HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg
+        ) {
+            mHdrBrightnessCfg = hdrBrightnessCfg;
+            return this;
+        }
+
+        HighBrightnessModeController build() {
+            initHandler(mClock);
+            if (mHighBrightnessModeMetadata == null) {
+                mHighBrightnessModeMetadata = new HighBrightnessModeMetadata();
+            }
+            return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH,
+                    DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX,
+                    DEFAULT_HBM_DATA, mHdrBrightnessCfg, () -> {}, mHighBrightnessModeMetadata,
+                    mContextSpy);
+        }
+
+    }
+
+    private HighBrightnessModeController createDefaultHbm() {
+        return new TestHbmBuilder().build();
+    }
+
+    // Creates instance with standard initialization values.
+    private HighBrightnessModeController createDefaultHbm(OffsettableClock clock) {
+        return new TestHbmBuilder().setClock(clock).build();
+    }
+
+    private void initHandler(OffsettableClock clock) {
+        mClock = clock != null ? clock : new OffsettableClock.Stopped();
+        when(mInjectorMock.getClock()).thenReturn(mClock::now);
+        mTestLooper = new TestLooper(mClock::now);
+        mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() {
+            @Override
+            public boolean handleMessage(Message msg) {
+                return true;
+            }
+        });
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    private void hbmcOnBrightnessChanged(HighBrightnessModeController hbmc, float brightness) {
+        hbmc.onBrightnessChanged(brightness, brightness, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
new file mode 100644
index 0000000..7e7ccf7
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class HighBrightnessModeMetadataMapperTest {
+
+    @Mock
+    private LogicalDisplay mDisplayMock;
+
+    @Mock
+    private DisplayDevice mDeviceMock;
+
+    @Mock
+    private DisplayDeviceConfig mDdcMock;
+
+    @Mock
+    private DisplayDeviceConfig.HighBrightnessModeData mHbmDataMock;
+
+    private HighBrightnessModeMetadataMapper mHighBrightnessModeMetadataMapper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDeviceMock);
+        when(mDeviceMock.getDisplayDeviceConfig()).thenReturn(mDdcMock);
+        when(mDdcMock.getHighBrightnessModeData()).thenReturn(mHbmDataMock);
+        mHighBrightnessModeMetadataMapper = new HighBrightnessModeMetadataMapper();
+    }
+
+    @Test
+    public void testGetHighBrightnessModeMetadata_NoDisplayDevice() {
+        when(mDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(null);
+        assertNull(mHighBrightnessModeMetadataMapper
+                .getHighBrightnessModeMetadataLocked(mDisplayMock));
+    }
+
+    @Test
+    public void testGetHighBrightnessModeMetadata_NoHBMData() {
+        when(mDdcMock.getHighBrightnessModeData()).thenReturn(null);
+        assertNull(mHighBrightnessModeMetadataMapper
+                .getHighBrightnessModeMetadataLocked(mDisplayMock));
+    }
+
+    @Test
+    public void testGetHighBrightnessModeMetadata_NewDisplay() {
+        HighBrightnessModeMetadata hbmMetadata =
+                mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(mDisplayMock);
+        assertNotNull(hbmMetadata);
+        assertTrue(hbmMetadata.getHbmEventQueue().isEmpty());
+        assertTrue(hbmMetadata.getRunningStartTimeMillis() < 0);
+    }
+
+    @Test
+    public void testGetHighBrightnessModeMetadata_Modify() {
+        HighBrightnessModeMetadata hbmMetadata =
+                mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(mDisplayMock);
+        assertNotNull(hbmMetadata);
+        assertTrue(hbmMetadata.getHbmEventQueue().isEmpty());
+        assertTrue(hbmMetadata.getRunningStartTimeMillis() < 0);
+
+        // Modify the metadata
+        long startTimeMillis = 100;
+        long endTimeMillis = 200;
+        long setTime = 300;
+        hbmMetadata.addHbmEvent(new HbmEvent(startTimeMillis, endTimeMillis));
+        hbmMetadata.setRunningStartTimeMillis(setTime);
+
+        hbmMetadata =
+                mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(mDisplayMock);
+
+        assertEquals(1, hbmMetadata.getHbmEventQueue().size());
+        assertEquals(startTimeMillis,
+                hbmMetadata.getHbmEventQueue().getFirst().getStartTimeMillis());
+        assertEquals(endTimeMillis, hbmMetadata.getHbmEventQueue().getFirst().getEndTimeMillis());
+        assertEquals(setTime, hbmMetadata.getRunningStartTimeMillis());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
new file mode 100644
index 0000000..aa0a2fe
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -0,0 +1,1317 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.RefreshRateRange;
+import android.view.SurfaceControl.RefreshRateRanges;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.internal.R;
+import com.android.server.LocalServices;
+import com.android.server.display.LocalDisplayAdapter.BacklightAdapter;
+import com.android.server.display.mode.DisplayModeDirector;
+import com.android.server.lights.LightsManager;
+import com.android.server.lights.LogicalLight;
+
+import com.google.common.truth.Truth;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LocalDisplayAdapterTest {
+    private static final Long DISPLAY_MODEL = Long.valueOf(0xAAAAAAAAL);
+    private static final int PORT_A = 0;
+    private static final int PORT_B = 0x80;
+    private static final int PORT_C = 0xFF;
+    private static final float REFRESH_RATE = 60f;
+    private static final RefreshRateRange REFRESH_RATE_RANGE =
+            new RefreshRateRange(REFRESH_RATE, REFRESH_RATE);
+    private static final RefreshRateRanges REFRESH_RATE_RANGES =
+            new RefreshRateRanges(REFRESH_RATE_RANGE, REFRESH_RATE_RANGE);
+
+    private static final long HANDLER_WAIT_MS = 100;
+
+    private static final int[] HDR_TYPES = new int[]{1, 2};
+
+    private StaticMockitoSession mMockitoSession;
+
+    private LocalDisplayAdapter mAdapter;
+
+    @Mock
+    private DisplayManagerService.SyncRoot mMockedSyncRoot;
+    @Mock
+    private Context mMockedContext;
+    @Mock
+    private Resources mMockedResources;
+    @Mock
+    private LightsManager mMockedLightsManager;
+    @Mock
+    private LogicalLight mMockedBacklight;
+
+    private Handler mHandler;
+
+    private TestListener mListener = new TestListener();
+
+    private LinkedList<DisplayAddress.Physical> mAddresses = new LinkedList<>();
+
+    private Injector mInjector;
+
+    @Mock
+    private LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy;
+    private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
+    private static final int[] BACKLIGHT_RANGE = { 1, 255 };
+    private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f };
+
+    @Before
+    public void setUp() throws Exception {
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+        mHandler = new Handler(Looper.getMainLooper());
+        doReturn(mMockedResources).when(mMockedContext).getResources();
+        LocalServices.removeServiceForTest(LightsManager.class);
+        LocalServices.addService(LightsManager.class, mMockedLightsManager);
+        mInjector = new Injector();
+        when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true);
+        mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler,
+                mListener, mInjector);
+        spyOn(mAdapter);
+        doReturn(mMockedContext).when(mAdapter).getOverlayContext();
+
+        TypedArray mockNitsRange = createFloatTypedArray(DISPLAY_RANGE_NITS);
+        when(mMockedResources.obtainTypedArray(R.array.config_screenBrightnessNits))
+                .thenReturn(mockNitsRange);
+        when(mMockedResources.getIntArray(R.array.config_screenBrightnessBacklight))
+                .thenReturn(BACKLIGHT_RANGE);
+        when(mMockedResources.getFloat(com.android.internal.R.dimen
+                .config_screenBrightnessSettingMinimumFloat))
+                .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[0]);
+        when(mMockedResources.getFloat(com.android.internal.R.dimen
+                .config_screenBrightnessSettingMaximumFloat))
+                .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[1]);
+        when(mMockedResources.getStringArray(R.array.config_displayUniqueIdArray))
+                .thenReturn(new String[]{});
+        TypedArray mockArray = mock(TypedArray.class);
+        when(mockArray.length()).thenReturn(0);
+        when(mMockedResources.obtainTypedArray(R.array.config_maskBuiltInDisplayCutoutArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.obtainTypedArray(R.array.config_waterfallCutoutArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerRadiusArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerTopRadiusArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerBottomRadiusArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.obtainTypedArray(
+                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
+                .thenReturn(mockArray);
+        when(mMockedResources.getIntArray(
+                com.android.internal.R.array.config_autoBrightnessLevels))
+                .thenReturn(new int[]{});
+        when(mMockedResources.obtainTypedArray(R.array.config_displayShapeArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray))
+                .thenReturn(mockArray);
+        when(mMockedResources.getIntArray(
+            com.android.internal.R.array.config_brightnessThresholdsOfPeakRefreshRate))
+            .thenReturn(new int[]{});
+        when(mMockedResources.getIntArray(
+            com.android.internal.R.array.config_ambientThresholdsOfPeakRefreshRate))
+            .thenReturn(new int[]{});
+        when(mMockedResources.getIntArray(
+            com.android.internal.R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+            .thenReturn(new int[]{});
+        when(mMockedResources.getIntArray(
+            com.android.internal.R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+            .thenReturn(new int[]{});
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
+    }
+
+    /**
+     * Confirm that display is marked as private when it is listed in
+     * com.android.internal.R.array.config_localPrivateDisplayPorts.
+     */
+    @Test
+    public void testPrivateDisplay() throws Exception {
+        setUpDisplay(new FakeDisplay(PORT_A));
+        setUpDisplay(new FakeDisplay(PORT_B));
+        setUpDisplay(new FakeDisplay(PORT_C));
+        updateAvailableDisplays();
+
+        doReturn(new int[]{ PORT_B }).when(mMockedResources)
+                .getIntArray(com.android.internal.R.array.config_localPrivateDisplayPorts);
+        mAdapter.registerLocked();
+
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        // This should be public
+        assertDisplayPrivateFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(),
+                PORT_A, false);
+        // This should be private
+        assertDisplayPrivateFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(),
+                PORT_B, true);
+        // This should be public
+        assertDisplayPrivateFlag(mListener.addedDisplays.get(2).getDisplayDeviceInfoLocked(),
+                PORT_C, false);
+    }
+
+    @Test
+    public void testSupportedDisplayModesGetOverriddenWhenDisplayIsUpdated()
+            throws InterruptedException {
+        SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 0);
+        displayMode.supportedHdrTypes = new int[0];
+        FakeDisplay display = new FakeDisplay(PORT_A, new SurfaceControl.DisplayMode[]{displayMode},
+                0, 0);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        Display.Mode[] supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+        Assert.assertEquals(1, supportedModes.length);
+        Assert.assertEquals(0, supportedModes[0].getSupportedHdrTypes().length);
+
+        displayMode.supportedHdrTypes = new int[]{3, 2};
+        display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{displayMode};
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+
+        Assert.assertEquals(1, supportedModes.length);
+        assertArrayEquals(new int[]{2, 3}, supportedModes[0].getSupportedHdrTypes());
+    }
+
+    /**
+     * Confirm that all local displays are public when config_localPrivateDisplayPorts is empty.
+     */
+    @Test
+    public void testPublicDisplaysForNoConfigLocalPrivateDisplayPorts() throws Exception {
+        setUpDisplay(new FakeDisplay(PORT_A));
+        setUpDisplay(new FakeDisplay(PORT_C));
+        updateAvailableDisplays();
+
+        // config_localPrivateDisplayPorts is null
+        mAdapter.registerLocked();
+
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        // This should be public
+        assertDisplayPrivateFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(),
+                PORT_A, false);
+        // This should be public
+        assertDisplayPrivateFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(),
+                PORT_C, false);
+    }
+
+    private static void assertDisplayPrivateFlag(
+            DisplayDeviceInfo info, int expectedPort, boolean shouldBePrivate) {
+        final DisplayAddress.Physical address = (DisplayAddress.Physical) info.address;
+        assertNotNull(address);
+        assertEquals(expectedPort, address.getPort());
+        assertEquals(DISPLAY_MODEL, address.getModel());
+        assertEquals(shouldBePrivate, (info.flags & DisplayDeviceInfo.FLAG_PRIVATE) != 0);
+    }
+
+    /**
+     * Confirm that external display uses physical density.
+     */
+    @Test
+    public void testDpiValues() throws Exception {
+        // needs default one always
+        setUpDisplay(new FakeDisplay(PORT_A));
+        setUpDisplay(new FakeDisplay(PORT_B));
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertDisplayDpi(
+                mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), PORT_A, 100, 100,
+                16000);
+        assertDisplayDpi(
+                mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), PORT_B, 100, 100,
+                16000);
+    }
+
+    private static class DisplayModeWrapper {
+        public SurfaceControl.DisplayMode mode;
+        public float[] expectedAlternativeRefreshRates;
+
+        DisplayModeWrapper(SurfaceControl.DisplayMode mode,
+                float[] expectedAlternativeRefreshRates) {
+            this.mode = mode;
+            this.expectedAlternativeRefreshRates = expectedAlternativeRefreshRates;
+        }
+    }
+
+    /**
+     * Updates the <code>display</code> using the given <code>modes</code> and then checks if the
+     * <code>expectedAlternativeRefreshRates</code> are present for each of the
+     * <code>modes</code>.
+     */
+    private void testAlternativeRefreshRatesCommon(FakeDisplay display,
+                DisplayModeWrapper[] wrappedModes)
+            throws InterruptedException {
+        // Update the display.
+        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[wrappedModes.length];
+        for (int i = 0; i < wrappedModes.length; i++) {
+            modes[i] = wrappedModes[i].mode;
+        }
+        display.dynamicInfo.supportedDisplayModes = modes;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.changedDisplays.size()).isGreaterThan(0);
+
+        // Verify the supported modes are updated accordingly.
+        DisplayDevice displayDevice =
+                mListener.changedDisplays.get(mListener.changedDisplays.size() - 1);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        Display.Mode[] supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+        assertThat(supportedModes.length).isEqualTo(modes.length);
+
+        for (int i = 0; i < wrappedModes.length; i++) {
+            assertModeIsSupported(supportedModes, modes[i],
+                    wrappedModes[i].expectedAlternativeRefreshRates);
+        }
+    }
+
+    @Test
+    public void testAfterDisplayChange_AlternativeRefreshRatesAreUpdated() throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[]{24f, 50f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(1, 1920, 1080, 50f, 0), new float[]{24f, 60f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(2, 1920, 1080, 24f, 0), new float[]{50f, 60f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(3, 3840, 2160, 60f, 0), new float[]{24f, 50f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(4, 3840, 2160, 50f, 0), new float[]{24f, 60f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(5, 3840, 2160, 24f, 0), new float[]{50f, 60f}),
+        });
+
+        testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[]{50f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(1, 1920, 1080, 50f, 0), new float[]{60f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(2, 1920, 1080, 24f, 1), new float[0]),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(3, 3840, 2160, 60f, 2), new float[0]),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(4, 3840, 2160, 50f, 3), new float[]{24f}),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(5, 3840, 2160, 24f, 3), new float[]{50f}),
+        });
+
+        testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[0]),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(1, 1920, 1080, 50f, 1), new float[0]),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(2, 1920, 1080, 24f, 2), new float[0]),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(3, 3840, 2160, 60f, 3), new float[0]),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(4, 3840, 2160, 50f, 4), new float[0]),
+                new DisplayModeWrapper(
+                        createFakeDisplayMode(5, 3840, 2160, 24f, 5), new float[0]),
+        });
+    }
+
+    @Test
+    public void testAfterDisplayChange_DefaultDisplayModeIsUpdated() throws Exception {
+        SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
+        SurfaceControl.DisplayMode[] modes =
+                new SurfaceControl.DisplayMode[]{displayMode};
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
+
+        Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode)).isTrue();
+
+        // Set the display mode to an unsupported mode
+        SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 120f);
+        mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
+                new Display.Mode(displayMode2.width, displayMode2.height,
+                        displayMode2.refreshRate));
+        updateAvailableDisplays();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode2)).isFalse();
+
+        // Change the display
+        modes = new SurfaceControl.DisplayMode[]{displayMode, displayMode2};
+        display.dynamicInfo.supportedDisplayModes = modes;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2);
+
+        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode2)).isTrue();
+    }
+
+    @Test
+    public void testAfterDisplayChange_DisplayModesAreUpdated() throws Exception {
+        SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
+        SurfaceControl.DisplayMode[] modes =
+                new SurfaceControl.DisplayMode[]{displayMode};
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
+
+        Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode)).isTrue();
+
+        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(matches(activeMode, displayMode)).isTrue();
+
+        // Change the display
+        SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(1, 3840, 2160, 60f);
+        modes = new SurfaceControl.DisplayMode[]{displayMode, addedDisplayInfo};
+        display.dynamicInfo.supportedDisplayModes = modes;
+        display.dynamicInfo.activeDisplayModeId = 1;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo);
+
+        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(matches(activeMode, addedDisplayInfo)).isTrue();
+
+        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, addedDisplayInfo)).isTrue();
+    }
+
+    @Test
+    public void testAfterDisplayChange_ActiveModeIsUpdated() throws Exception {
+        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
+                createFakeDisplayMode(0, 1920, 1080, 60f),
+                createFakeDisplayMode(1, 1920, 1080, 50f)
+        };
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0, 60f);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+                .getDisplayDeviceInfoLocked();
+
+        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+
+        // Change the display
+        display.dynamicInfo.activeDisplayModeId = 1;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(1920, 1080, 50f)).isTrue();
+    }
+
+    @Test
+    public void testAfterDisplayChange_RenderFrameRateIsUpdated() throws Exception {
+        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
+                createFakeDisplayMode(0, 1920, 1080, 60f),
+        };
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0,
+                /* renderFrameRate */30f);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+                .getDisplayDeviceInfoLocked();
+
+        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+        assertEquals(Float.floatToIntBits(30f),
+                Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+
+        // Change the render frame rate
+        display.dynamicInfo.renderFrameRate = 60f;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+        assertEquals(Float.floatToIntBits(60f),
+                Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+    }
+
+    @Test
+    public void testAfterDisplayChange_HdrCapabilitiesAreUpdated() throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        Display.HdrCapabilities initialHdrCapabilities = new Display.HdrCapabilities(new int[0],
+                1000, 1000, 0);
+        display.dynamicInfo.hdrCapabilities = initialHdrCapabilities;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.hdrCapabilities).isEqualTo(initialHdrCapabilities);
+
+        // Change the display
+        Display.HdrCapabilities changedHdrCapabilities = new Display.HdrCapabilities(
+                new int[Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS], 1000, 1000, 0);
+        display.dynamicInfo.hdrCapabilities = changedHdrCapabilities;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.hdrCapabilities).isEqualTo(changedHdrCapabilities);
+    }
+
+    @Test
+    public void testAfterDisplayChange_AllmSupportIsUpdated() throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        display.dynamicInfo.autoLowLatencyModeSupported = true;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+                .getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.allmSupported).isTrue();
+
+        // Change the display
+        display.dynamicInfo.autoLowLatencyModeSupported = false;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.allmSupported).isFalse();
+    }
+
+    @Test
+    public void testAfterDisplayStateChanges_committedSetAfterState() throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        // Turn off.
+        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_OFF, 0,
+                0);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+        mListener.changedDisplays.clear();
+        assertThat(displayDevice.getDisplayDeviceInfoLocked().state).isEqualTo(Display.STATE_OFF);
+        assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState).isNotEqualTo(
+                Display.STATE_OFF);
+        verify(mSurfaceControlProxy, never()).setDisplayPowerMode(display.token, Display.STATE_OFF);
+
+        // Execute powerstate change.
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+
+        // Verify that committed triggered a new change event and is set correctly.
+        verify(mSurfaceControlProxy, never()).setDisplayPowerMode(display.token, Display.STATE_OFF);
+        // We expect at least 1 update for the state change, but
+        // could get a second update for the initial brightness change if a nits mapping
+        // is available
+        assertThat(mListener.changedDisplays.size()).isAnyOf(1, 2);
+        assertThat(displayDevice.getDisplayDeviceInfoLocked().state).isEqualTo(Display.STATE_OFF);
+        assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState).isEqualTo(
+                Display.STATE_OFF);
+    }
+
+    @Test
+    public void testAfterDisplayChange_GameContentTypeSupportIsUpdated() throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        display.dynamicInfo.gameContentTypeSupported = true;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+                .getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.gameContentTypeSupported).isTrue();
+
+        // Change the display
+        display.dynamicInfo.gameContentTypeSupported = false;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.gameContentTypeSupported).isFalse();
+    }
+
+    @Test
+    public void testAfterDisplayChange_ColorModesAreUpdated() throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        final int[] initialColorModes = new int[]{Display.COLOR_MODE_BT709};
+        display.dynamicInfo.supportedColorModes = initialColorModes;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+                .getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.colorMode).isEqualTo(Display.COLOR_MODE_BT709);
+        assertThat(displayDeviceInfo.supportedColorModes).isEqualTo(initialColorModes);
+
+        // Change the display
+        final int[] changedColorModes = new int[]{Display.COLOR_MODE_DEFAULT};
+        display.dynamicInfo.supportedColorModes = changedColorModes;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.colorMode).isEqualTo(Display.COLOR_MODE_DEFAULT);
+        assertThat(displayDeviceInfo.supportedColorModes).isEqualTo(changedColorModes);
+    }
+
+    @Test
+    public void testDisplayChange_withStaleDesiredDisplayModeSpecs() throws Exception {
+        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
+                createFakeDisplayMode(0, 1920, 1080, 60f),
+                createFakeDisplayMode(1, 1920, 1080, 50f)
+        };
+        final int activeMode = 0;
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode, 60f);
+        display.desiredDisplayModeSpecs.defaultMode = 1;
+
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        int baseModeId = Arrays.stream(displayDevice.getDisplayDeviceInfoLocked().supportedModes)
+                .filter(mode -> mode.getRefreshRate() == 60f)
+                .findFirst()
+                .get()
+                .getModeId();
+
+        displayDevice.setDesiredDisplayModeSpecsLocked(
+                new DisplayModeDirector.DesiredDisplayModeSpecs(
+                        /*baseModeId*/ baseModeId,
+                        /*allowGroupSwitching*/ false,
+                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
+                ));
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        verify(mSurfaceControlProxy).setDesiredDisplayModeSpecs(display.token,
+                new SurfaceControl.DesiredDisplayModeSpecs(
+                        /* baseModeId */ 0,
+                        /* allowGroupSwitching */ false,
+                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
+                ));
+
+        // Change the display
+        display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{
+                createFakeDisplayMode(2, 1920, 1080, 60f)
+        };
+        display.dynamicInfo.activeDisplayModeId = 2;
+        // SurfaceFlinger can return a stale defaultMode. Make sure this doesn't crash.
+        display.desiredDisplayModeSpecs.defaultMode = 1;
+
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+
+        baseModeId = displayDevice.getDisplayDeviceInfoLocked().supportedModes[0].getModeId();
+
+        // The traversal request will call setDesiredDisplayModeSpecsLocked on the display device
+        displayDevice.setDesiredDisplayModeSpecsLocked(
+                new DisplayModeDirector.DesiredDisplayModeSpecs(
+                        /*baseModeId*/ baseModeId,
+                        /*allowGroupSwitching*/ false,
+                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
+                ));
+
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        // Verify that this will reapply the desired modes.
+        verify(mSurfaceControlProxy).setDesiredDisplayModeSpecs(display.token,
+                new SurfaceControl.DesiredDisplayModeSpecs(
+                        /* baseModeId */ 2,
+                        /* allowGroupSwitching */ false,
+                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
+                ));
+    }
+
+    @Test
+    public void testBacklightAdapter_withSurfaceControlSupport() {
+        final Binder displayToken = new Binder();
+
+        when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(true);
+
+        // Test as default display
+        BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/,
+                mSurfaceControlProxy);
+        ba.setBacklight(0.514f, 100f, 0.614f, 500f);
+        verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.514f, 100f, 0.614f, 500f);
+
+        // Test as not default display
+        BacklightAdapter ba2 = new BacklightAdapter(displayToken, false /*isDefault*/,
+                mSurfaceControlProxy);
+        ba2.setBacklight(0.323f, 101f, 0.723f, 601f);
+        verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.323f, 101f, 0.723f, 601f);
+    }
+
+    @Test
+    public void testBacklightAdapter_withoutSourceControlSupport_defaultDisplay() {
+        final Binder displayToken = new Binder();
+        when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(false);
+        doReturn(mMockedBacklight).when(mMockedLightsManager)
+                .getLight(LightsManager.LIGHT_ID_BACKLIGHT);
+
+        BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/,
+                mSurfaceControlProxy);
+        ba.setBacklight(1f, 1f, 0.123f, 1f);
+        verify(mMockedBacklight).setBrightness(0.123f);
+    }
+
+    @Test
+    public void testBacklightAdapter_withoutSourceControlSupport_nonDefaultDisplay() {
+        final Binder displayToken = new Binder();
+        when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(false);
+        doReturn(mMockedBacklight).when(mMockedLightsManager)
+                .getLight(LightsManager.LIGHT_ID_BACKLIGHT);
+
+        BacklightAdapter ba = new BacklightAdapter(displayToken, false /*isDefault*/,
+                mSurfaceControlProxy);
+        ba.setBacklight(0.456f, 1f, 1f, 1f);
+
+        // Adapter does not forward any brightness in this case.
+        verify(mMockedBacklight, never()).setBrightness(anyFloat());
+    }
+
+    @Test
+    public void testGetSystemPreferredDisplayMode() throws Exception {
+        SurfaceControl.DisplayMode displayMode1 = createFakeDisplayMode(0, 1920, 1080, 60f);
+        // system preferred mode
+        SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 3840, 2160, 60f);
+        // user preferred mode
+        SurfaceControl.DisplayMode displayMode3 = createFakeDisplayMode(2, 1920, 1080, 30f);
+
+        SurfaceControl.DisplayMode[] modes =
+                new SurfaceControl.DisplayMode[]{displayMode1, displayMode2, displayMode3};
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, 1);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+        Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode1)).isTrue();
+
+        // Set the user preferred display mode
+        mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
+                new Display.Mode(
+                        displayMode3.width, displayMode3.height, displayMode3.refreshRate));
+        updateAvailableDisplays();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode3)).isTrue();
+
+        // clear the user preferred mode
+        mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(null);
+        updateAvailableDisplays();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(matches(defaultMode, displayMode2)).isTrue();
+
+        // Change the display and add new system preferred mode
+        SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(3, 2340, 1080, 20f);
+        modes = new SurfaceControl.DisplayMode[]{
+                displayMode1, displayMode2, displayMode3, addedDisplayInfo};
+        display.dynamicInfo.supportedDisplayModes = modes;
+        display.dynamicInfo.preferredBootDisplayMode = 3;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(3);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode1);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo);
+
+        assertThat(matches(displayDevice.getSystemPreferredDisplayModeLocked(), addedDisplayInfo))
+                .isTrue();
+    }
+
+    @Test
+    public void testHdrSdrRatio_notifiesOnChange() throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        // Turn on / initialize
+        assumeTrue(displayDevice.getDisplayDeviceConfig().hasSdrToHdrRatioSpline());
+        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+                0);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        mListener.changedDisplays.clear();
+
+        assertEquals(1.0f, displayDevice.getDisplayDeviceInfoLocked().hdrSdrRatio, 0.001f);
+
+        // HDR time!
+        Runnable goHdrRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 1f,
+                0);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        // Display state didn't change, no listeners should have happened
+        assertThat(mListener.changedDisplays.size()).isEqualTo(0);
+
+        // Execute hdr change.
+        goHdrRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        // Display state didn't change, expect to only get the HDR/SDR ratio change notification
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        final float expectedRatio = DISPLAY_RANGE_NITS[1] / DISPLAY_RANGE_NITS[0];
+        assertEquals(expectedRatio, displayDevice.getDisplayDeviceInfoLocked().hdrSdrRatio,
+                0.001f);
+    }
+
+    @Test
+    public void test_getDisplayDeviceInfoLocked_internalDisplay_usesCutoutAndCorners()
+            throws Exception {
+        setupCutoutAndRoundedCorners();
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        display.info.isInternal = true;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        // Turn on / initialize
+        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+                0);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        mListener.changedDisplays.clear();
+
+        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(info.displayCutout).isNotNull();
+        assertThat(info.displayCutout.getBoundingRectTop()).isEqualTo(new Rect(507, 33, 573, 99));
+        assertThat(info.roundedCorners).isNotNull();
+        assertThat(info.roundedCorners.getRoundedCorner(0).getRadius()).isEqualTo(5);
+    }
+
+    @Test public void test_getDisplayDeviceInfoLocked_externalDisplay_doesNotUseCutoutOrCorners()
+            throws Exception {
+        setupCutoutAndRoundedCorners();
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        display.info.isInternal = false;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        // Turn on / initialize
+        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+                0);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        mListener.changedDisplays.clear();
+
+        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(info.displayCutout).isNull();
+        assertThat(info.roundedCorners).isNull();
+    }
+
+    private void setupCutoutAndRoundedCorners() {
+        String sampleCutout = "M 507,66\n"
+                + "a 33,33 0 1 0 66,0 33,33 0 1 0 -66,0\n"
+                + "Z\n"
+                + "@left\n";
+        // Setup some default cutout
+        when(mMockedResources.getString(
+                com.android.internal.R.string.config_mainBuiltInDisplayCutout))
+                .thenReturn(sampleCutout);
+        when(mMockedResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.rounded_corner_radius)).thenReturn(5);
+    }
+
+    private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
+                                  float expectedXdpi,
+                                  float expectedYDpi,
+                                  int expectedDensityDpi) {
+        final DisplayAddress.Physical physical = (DisplayAddress.Physical) info.address;
+        assertNotNull(physical);
+        assertEquals(expectedPort, physical.getPort());
+        assertEquals(expectedXdpi, info.xDpi, 0.01);
+        assertEquals(expectedYDpi, info.yDpi, 0.01);
+        assertEquals(expectedDensityDpi, info.densityDpi);
+    }
+
+    private Display.Mode getModeById(DisplayDeviceInfo displayDeviceInfo, int modeId) {
+        return Arrays.stream(displayDeviceInfo.supportedModes)
+                .filter(mode -> mode.getModeId() == modeId)
+                .findFirst()
+                .get();
+    }
+
+    private void assertModeIsSupported(Display.Mode[] supportedModes,
+            SurfaceControl.DisplayMode mode) {
+        assertThat(Arrays.stream(supportedModes).anyMatch(
+                x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue();
+    }
+
+    private void assertModeIsSupported(Display.Mode[] supportedModes,
+            SurfaceControl.DisplayMode mode, float[] alternativeRefreshRates) {
+        float[] sortedAlternativeRates =
+                Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length);
+        Arrays.sort(sortedAlternativeRates);
+
+        String message = "Expected " + mode + " with alternativeRefreshRates = "
+                + Arrays.toString(alternativeRefreshRates) + " to be in list of supported modes = "
+                + Arrays.toString(supportedModes);
+        Truth.assertWithMessage(message)
+            .that(Arrays.stream(supportedModes)
+                .anyMatch(x -> x.matches(mode.width, mode.height, mode.refreshRate)
+                        && Arrays.equals(x.getAlternativeRefreshRates(), sortedAlternativeRates)))
+                .isTrue();
+    }
+
+
+    private static class FakeDisplay {
+        public final DisplayAddress.Physical address;
+        public final IBinder token = new Binder();
+        public final SurfaceControl.StaticDisplayInfo info;
+        public SurfaceControl.DynamicDisplayInfo dynamicInfo =
+                new SurfaceControl.DynamicDisplayInfo();
+        {
+            dynamicInfo.supportedColorModes = new int[]{ Display.COLOR_MODE_DEFAULT };
+            dynamicInfo.hdrCapabilities = new Display.HdrCapabilities(new int[0],
+                    1000, 1000, 0);
+        }
+
+        public SurfaceControl.DesiredDisplayModeSpecs desiredDisplayModeSpecs =
+                new SurfaceControl.DesiredDisplayModeSpecs(
+                        /* defaultMode */ 0,
+                        /* allowGroupSwitching */ false,
+                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
+                );
+
+        private FakeDisplay(int port) {
+            address = createDisplayAddress(port);
+            info = createFakeDisplayInfo();
+            dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{
+                    createFakeDisplayMode(0, 800, 600, 60f)
+            };
+            dynamicInfo.activeDisplayModeId = 0;
+        }
+
+        private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
+                float renderFrameRate) {
+            address = createDisplayAddress(port);
+            info = createFakeDisplayInfo();
+            dynamicInfo.supportedDisplayModes = modes;
+            dynamicInfo.activeDisplayModeId = activeMode;
+            dynamicInfo.renderFrameRate = renderFrameRate;
+        }
+
+        private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
+                int preferredMode) {
+            address = createDisplayAddress(port);
+            info = createFakeDisplayInfo();
+            dynamicInfo.supportedDisplayModes = modes;
+            dynamicInfo.activeDisplayModeId = activeMode;
+            dynamicInfo.preferredBootDisplayMode = preferredMode;
+        }
+
+    }
+
+    private void setUpDisplay(FakeDisplay display) {
+        mAddresses.add(display.address);
+        when(mSurfaceControlProxy.getPhysicalDisplayToken(display.address.getPhysicalDisplayId()))
+                .thenReturn(display.token);
+        when(mSurfaceControlProxy.getStaticDisplayInfo(display.address.getPhysicalDisplayId()))
+                .thenReturn(display.info);
+        when(mSurfaceControlProxy.getDynamicDisplayInfo(display.address.getPhysicalDisplayId()))
+                .thenReturn(display.dynamicInfo);
+        when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(display.token))
+                .thenReturn(display.desiredDisplayModeSpecs);
+    }
+
+    private void updateAvailableDisplays() {
+        long[] ids = new long[mAddresses.size()];
+        int i = 0;
+        for (DisplayAddress.Physical address : mAddresses) {
+            ids[i] = address.getPhysicalDisplayId();
+            i++;
+        }
+        when(mSurfaceControlProxy.getPhysicalDisplayIds()).thenReturn(ids);
+    }
+
+    private static DisplayAddress.Physical createDisplayAddress(int port) {
+        return DisplayAddress.fromPortAndModel(port, DISPLAY_MODEL);
+    }
+
+    private static SurfaceControl.StaticDisplayInfo createFakeDisplayInfo() {
+        final SurfaceControl.StaticDisplayInfo info = new SurfaceControl.StaticDisplayInfo();
+        info.density = 100;
+        return info;
+    }
+
+    private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
+            float refreshRate) {
+        return createFakeDisplayMode(id, width, height, refreshRate, /* group */ 0);
+    }
+
+    private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
+            float refreshRate, int group) {
+        final SurfaceControl.DisplayMode mode = new SurfaceControl.DisplayMode();
+        mode.id = id;
+        mode.width = width;
+        mode.height = height;
+        mode.refreshRate = refreshRate;
+        mode.xDpi = 100;
+        mode.yDpi = 100;
+        mode.group = group;
+        mode.supportedHdrTypes = HDR_TYPES;
+        return mode;
+    }
+
+    private static void waitForHandlerToComplete(Handler handler, long waitTimeMs)
+            throws InterruptedException {
+        final CountDownLatch fence = new CountDownLatch(1);
+        handler.post(fence::countDown);
+        assertTrue(fence.await(waitTimeMs, TimeUnit.MILLISECONDS));
+    }
+
+    private class HotplugTransmitter {
+        private final Handler mHandler;
+        private final LocalDisplayAdapter.DisplayEventListener mListener;
+
+        HotplugTransmitter(Looper looper, LocalDisplayAdapter.DisplayEventListener listener) {
+            mHandler = new Handler(looper);
+            mListener = listener;
+        }
+
+        public void sendHotplug(FakeDisplay display, boolean connected)
+                throws InterruptedException {
+            mHandler.post(() -> mListener.onHotplug(/* timestampNanos = */ 0,
+                    display.address.getPhysicalDisplayId(), connected));
+            waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        }
+    }
+
+    private class Injector extends LocalDisplayAdapter.Injector {
+        private HotplugTransmitter mTransmitter;
+        @Override
+        public void setDisplayEventListenerLocked(Looper looper,
+                LocalDisplayAdapter.DisplayEventListener listener) {
+            mTransmitter = new HotplugTransmitter(looper, listener);
+        }
+
+        public HotplugTransmitter getTransmitter() {
+            return mTransmitter;
+        }
+
+        @Override
+        public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
+            return mSurfaceControlProxy;
+        }
+
+        // Instead of using DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay)
+        // we should use DisplayDeviceConfig.create(context, isFirstDisplay) for the test to ensure
+        // that real device DisplayDeviceConfig is not loaded for FakeDisplay and we are getting
+        // consistent behaviour. Please also note that context passed to this method, is
+        // mMockContext and values will be loaded from mMockResources.
+        @Override
+        public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
+                long physicalDisplayId, boolean isFirstDisplay) {
+            return DisplayDeviceConfig.create(context, isFirstDisplay);
+        }
+    }
+
+    private class TestListener implements DisplayAdapter.Listener {
+        public ArrayList<DisplayDevice> addedDisplays = new ArrayList<>();
+        public ArrayList<DisplayDevice> changedDisplays = new ArrayList<>();
+        public boolean traversalRequested = false;
+
+        @Override
+        public void onDisplayDeviceEvent(DisplayDevice device, int event) {
+            if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED) {
+                addedDisplays.add(device);
+            } else if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED) {
+                changedDisplays.add(device);
+            }
+        }
+
+        @Override
+        public void onTraversalRequested() {
+            traversalRequested = true;
+        }
+    }
+
+    private TypedArray createFloatTypedArray(float[] vals) {
+        TypedArray mockArray = mock(TypedArray.class);
+        when(mockArray.length()).thenAnswer(invocation -> {
+            return vals.length;
+        });
+        when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
+            final float def = (float) invocation.getArguments()[1];
+            if (vals == null) {
+                return def;
+            }
+            int idx = (int) invocation.getArguments()[0];
+            if (idx >= 0 && idx < vals.length) {
+                return vals[idx];
+            } else {
+                return def;
+            }
+        });
+        return mockArray;
+    }
+
+    private boolean matches(Display.Mode a, SurfaceControl.DisplayMode b) {
+        return a.getPhysicalWidth() == b.width && a.getPhysicalHeight() == b.height
+                && Float.floatToIntBits(a.getRefreshRate()) == Float.floatToIntBits(b.refreshRate);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
new file mode 100644
index 0000000..0fe6e64
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -0,0 +1,994 @@
+/*
+ * 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.server.display;
+
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.DEFAULT_DISPLAY_GROUP;
+import static android.view.Display.FLAG_REAR;
+import static android.view.Display.TYPE_EXTERNAL;
+import static android.view.Display.TYPE_INTERNAL;
+import static android.view.Display.TYPE_VIRTUAL;
+
+import static com.android.server.display.DeviceStateToLayoutMap.STATE_DEFAULT;
+import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED;
+import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED;
+import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
+import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
+import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
+import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PropertyInvalidatedCache;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.test.TestLooper;
+import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.DisplayInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.layout.DisplayIdProducer;
+import com.android.server.display.layout.Layout;
+import com.android.server.utils.FoldSettingWrapper;
+
+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.Spy;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LogicalDisplayMapperTest {
+    private static int sUniqueTestDisplayId = 0;
+    private static final int DEVICE_STATE_CLOSED = 0;
+    private static final int DEVICE_STATE_OPEN = 2;
+    private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
+    private static final File NON_EXISTING_FILE = new File("/non_existing_folder/should_not_exist");
+
+    private DisplayDeviceRepository mDisplayDeviceRepo;
+    private LogicalDisplayMapper mLogicalDisplayMapper;
+    private TestLooper mLooper;
+    private Handler mHandler;
+    private PowerManager mPowerManager;
+
+    private final DisplayIdProducer mIdProducer = (isDefault) ->
+            isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
+
+    @Mock LogicalDisplayMapper.Listener mListenerMock;
+    @Mock Context mContextMock;
+    @Mock FoldSettingWrapper mFoldSettingWrapperMock;
+    @Mock Resources mResourcesMock;
+    @Mock IPowerManager mIPowerManagerMock;
+    @Mock IThermalService mIThermalServiceMock;
+    @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy =
+            new DeviceStateToLayoutMap(mIdProducer, NON_EXISTING_FILE);
+
+    @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
+
+    @Before
+    public void setUp() {
+        // Share classloader to allow package private access.
+        System.setProperty("dexmaker.share_classloader", "true");
+        MockitoAnnotations.initMocks(this);
+
+        mDisplayDeviceRepo = new DisplayDeviceRepository(
+                new DisplayManagerService.SyncRoot(),
+                new PersistentDataStore(new PersistentDataStore.Injector() {
+                    @Override
+                    public InputStream openRead() {
+                        return null;
+                    }
+
+                    @Override
+                    public OutputStream startWrite() {
+                        return null;
+                    }
+
+                    @Override
+                    public void finishWrite(OutputStream os, boolean success) {}
+                }));
+
+        // Disable binder caches in this process.
+        PropertyInvalidatedCache.disableForTestMode();
+
+        mPowerManager = new PowerManager(mContextMock, mIPowerManagerMock, mIThermalServiceMock,
+                null);
+
+        when(mContextMock.getSystemServiceName(PowerManager.class))
+                .thenReturn(Context.POWER_SERVICE);
+        when(mFoldSettingWrapperMock.shouldStayAwakeOnFold()).thenReturn(false);
+        when(mContextMock.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
+        when(mContextMock.getResources()).thenReturn(mResourcesMock);
+        when(mResourcesMock.getBoolean(
+                com.android.internal.R.bool.config_supportsConcurrentInternalDisplays))
+                .thenReturn(true);
+        when(mResourcesMock.getIntArray(
+                com.android.internal.R.array.config_deviceStatesOnWhichToWakeUp))
+                .thenReturn(new int[]{1, 2});
+        when(mResourcesMock.getIntArray(
+                com.android.internal.R.array.config_deviceStatesOnWhichToSleep))
+                .thenReturn(new int[]{0});
+
+        mLooper = new TestLooper();
+        mHandler = new Handler(mLooper.getLooper());
+        mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
+                mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
+                mDeviceStateToLayoutMapSpy, mFoldSettingWrapperMock);
+    }
+
+
+    /////////////////
+    // Test Methods
+    /////////////////
+
+    @Test
+    public void testDisplayDeviceAddAndRemove_Internal() {
+        DisplayDevice device = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        // add
+        LogicalDisplay displayAdded = add(device);
+        assertEquals(info(displayAdded).address, info(device).address);
+        assertEquals(DEFAULT_DISPLAY, id(displayAdded));
+
+        // remove
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
+        verify(mListenerMock).onLogicalDisplayEventLocked(
+                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
+        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
+        assertEquals(DEFAULT_DISPLAY, id(displayRemoved));
+        assertEquals(displayAdded, displayRemoved);
+    }
+
+    @Test
+    public void testDisplayDeviceAddAndRemove_NonInternalTypes() {
+        testDisplayDeviceAddAndRemove_NonInternal(TYPE_EXTERNAL);
+        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_WIFI);
+        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_OVERLAY);
+        testDisplayDeviceAddAndRemove_NonInternal(TYPE_VIRTUAL);
+        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_UNKNOWN);
+
+        // Call the internal test again, just to verify that adding non-internal displays
+        // doesn't affect the ability for an internal display to become the default display.
+        testDisplayDeviceAddAndRemove_Internal();
+    }
+
+    @Test
+    public void testDisplayDeviceAdd_TwoInternalOneDefault() {
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800, 0);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        LogicalDisplay display1 = add(device1);
+        assertEquals(info(display1).address, info(device1).address);
+        assertNotEquals(DEFAULT_DISPLAY, id(display1));
+
+        LogicalDisplay display2 = add(device2);
+        assertEquals(info(display2).address, info(device2).address);
+        assertEquals(DEFAULT_DISPLAY, id(display2));
+    }
+
+    @Test
+    public void testDisplayDeviceAdd_TwoInternalBothDefault() {
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        LogicalDisplay display1 = add(device1);
+        assertEquals(info(display1).address, info(device1).address);
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        LogicalDisplay display2 = add(device2);
+        assertEquals(info(display2).address, info(device2).address);
+        // Despite the flags, we can only have one default display
+        assertNotEquals(DEFAULT_DISPLAY, id(display2));
+    }
+
+    @Test
+    public void testDisplayDeviceAddAndRemove_OneExternalDefault() {
+        DisplayDevice device = createDisplayDevice(TYPE_EXTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        // add
+        LogicalDisplay displayAdded = add(device);
+        assertEquals(info(displayAdded).address, info(device).address);
+        assertEquals(Display.DEFAULT_DISPLAY, id(displayAdded));
+
+        // remove
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
+        verify(mListenerMock).onLogicalDisplayEventLocked(
+                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
+        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
+        assertEquals(DEFAULT_DISPLAY, id(displayRemoved));
+        assertEquals(displayAdded, displayRemoved);
+    }
+
+    @Test
+    public void testDisplayDeviceAddAndRemove_SwitchDefault() {
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        LogicalDisplay display1 = add(device1);
+        assertEquals(info(display1).address, info(device1).address);
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        LogicalDisplay display2 = add(device2);
+        assertEquals(info(display2).address, info(device2).address);
+        // We can only have one default display
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        // remove
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device1, DISPLAY_DEVICE_EVENT_REMOVED);
+
+        verify(mListenerMock).onLogicalDisplayEventLocked(
+                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
+        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
+        // Display 1 is still the default logical display
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+        // The logical displays had their devices swapped and Display 2 was removed
+        assertEquals(display2, displayRemoved);
+        assertEquals(info(display1).address, info(device2).address);
+    }
+
+    @Test
+    public void testGetDisplayIdsLocked() {
+        add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+        add(createDisplayDevice(TYPE_EXTERNAL, 600, 800, 0));
+        add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
+
+        int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID,
+                /* includeDisabled= */ true);
+        assertEquals(3, ids.length);
+        Arrays.sort(ids);
+        assertEquals(DEFAULT_DISPLAY, ids[0]);
+    }
+
+    @Test
+    public void testGetDisplayInfoForStateLocked_defaultLayout() {
+        final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        add(device1);
+        add(device2);
+
+        Layout layout1 = new Layout();
+        createDefaultDisplay(layout1, device1);
+        createNonDefaultDisplay(layout1, device2, /* enabled= */ true, /* group= */ null);
+        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
+        assertThat(layout1.size()).isEqualTo(2);
+        final int logicalId2 = layout1.getByAddress(info(device2).address).getLogicalDisplayId();
+
+        final DisplayInfo displayInfoDefault = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                STATE_DEFAULT, DEFAULT_DISPLAY);
+        assertThat(displayInfoDefault.displayId).isEqualTo(DEFAULT_DISPLAY);
+        assertThat(displayInfoDefault.logicalWidth).isEqualTo(width(device1));
+        assertThat(displayInfoDefault.logicalHeight).isEqualTo(height(device1));
+
+        final DisplayInfo displayInfoOther = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                STATE_DEFAULT, logicalId2);
+        assertThat(displayInfoOther).isNotNull();
+        assertThat(displayInfoOther.displayId).isEqualTo(logicalId2);
+        assertThat(displayInfoOther.logicalWidth).isEqualTo(width(device2));
+        assertThat(displayInfoOther.logicalHeight).isEqualTo(height(device2));
+    }
+
+    @Test
+    public void testGetDisplayInfoForStateLocked_multipleLayouts() {
+        final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        final DisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 700, 800,
+                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+
+        add(device1);
+        add(device2);
+        add(device3);
+
+        Layout layout1 = new Layout();
+        createDefaultDisplay(layout1, device1);
+        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
+
+        final int layoutState2 = 2;
+        Layout layout2 = new Layout();
+        createNonDefaultDisplay(layout2, device2, /* enabled= */ true, /* group= */ null);
+        // Device3 is the default display.
+        createDefaultDisplay(layout2, device3);
+        when(mDeviceStateToLayoutMapSpy.get(layoutState2)).thenReturn(layout2);
+        assertThat(layout2.size()).isEqualTo(2);
+        final int logicalId2 = layout2.getByAddress(info(device2).address).getLogicalDisplayId();
+
+        // Default layout.
+        final DisplayInfo displayInfoLayout1Default =
+                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                        STATE_DEFAULT, DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout1Default.displayId).isEqualTo(DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout1Default.logicalWidth).isEqualTo(width(device1));
+        assertThat(displayInfoLayout1Default.logicalHeight).isEqualTo(height(device1));
+
+        // Second layout, where device3 is the default display.
+        final DisplayInfo displayInfoLayout2Default =
+                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                        layoutState2, DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout2Default.displayId).isEqualTo(DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout2Default.logicalWidth).isEqualTo(width(device3));
+        assertThat(displayInfoLayout2Default.logicalHeight).isEqualTo(height(device3));
+
+        final DisplayInfo displayInfoLayout2Other =
+                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                        layoutState2, logicalId2);
+        assertThat(displayInfoLayout2Other).isNotNull();
+        assertThat(displayInfoLayout2Other.displayId).isEqualTo(logicalId2);
+        assertThat(displayInfoLayout2Other.logicalWidth).isEqualTo(width(device2));
+        assertThat(displayInfoLayout2Other.logicalHeight).isEqualTo(height(device2));
+    }
+
+    @Test
+    public void testGetDisplayInfoForStateLocked_multipleDisplayGroups() {
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device3 = createDisplayDevice(TYPE_INTERNAL, 700, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device4 = createDisplayDevice(TYPE_INTERNAL, 400, 600,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        Layout layout = new Layout();
+        createDefaultDisplay(layout, device1);
+        createNonDefaultDisplay(layout, device2, /* enabled= */  true, /* group= */ "group1");
+        createNonDefaultDisplay(layout, device3, /* enabled= */  true, /* group= */ "group1");
+        createNonDefaultDisplay(layout, device4, /* enabled= */  true, /* group= */ "group2");
+
+        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout);
+
+        LogicalDisplay display1 = add(device1);
+        LogicalDisplay display2 = add(device2);
+        LogicalDisplay display3 = add(device3);
+        LogicalDisplay display4 = add(device4);
+
+        int displayGroupId1 =
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1));
+        int displayGroupId2 =
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2));
+        int displayGroupId3 =
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3));
+        int displayGroupId4 =
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display4));
+        assertThat(displayGroupId1).isEqualTo(DEFAULT_DISPLAY_GROUP);
+        assertThat(displayGroupId2).isNotEqualTo(DEFAULT_DISPLAY_GROUP);
+        assertThat(displayGroupId2).isEqualTo(displayGroupId3);
+        assertThat(displayGroupId3).isNotEqualTo(DEFAULT_DISPLAY_GROUP);
+        assertThat(displayGroupId2).isNotEqualTo(displayGroupId4);
+    }
+
+    @Test
+    public void testSingleDisplayGroup() {
+        LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+        LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
+        LogicalDisplay display3 = add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
+
+        assertEquals(DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
+        assertEquals(DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
+        assertEquals(DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
+    }
+
+    @Test
+    public void testMultipleDisplayGroups() {
+        LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+        LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
+
+
+        TestDisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 600, 800,
+                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+        LogicalDisplay display3 = add(device3);
+
+        assertEquals(DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
+        assertEquals(DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
+        assertNotEquals(DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
+
+        // Now switch it back to the default group by removing the flag and issuing an update
+        DisplayDeviceInfo info = device3.getSourceInfo();
+        info.flags = info.flags & ~DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
+
+        // Verify the new group is correct.
+        assertEquals(DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
+    }
+
+    @Test
+    public void testDevicesAreAddedToDeviceDisplayGroups() {
+        // Create the default internal display of the device.
+        LogicalDisplay defaultDisplay =
+                add(
+                        createDisplayDevice(
+                                Display.TYPE_INTERNAL,
+                                600,
+                                800,
+                                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+
+        // Create 3 virtual displays associated with a first virtual device.
+        int deviceId1 = 1;
+        TestDisplayDevice display1 =
+                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display1", 600, 800, 0);
+        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display1, deviceId1);
+        LogicalDisplay virtualDevice1Display1 = add(display1);
+
+        TestDisplayDevice display2 =
+                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display2", 600, 800, 0);
+        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display2, deviceId1);
+        LogicalDisplay virtualDevice1Display2 = add(display2);
+
+        TestDisplayDevice display3 =
+                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display3", 600, 800, 0);
+        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display3, deviceId1);
+        LogicalDisplay virtualDevice1Display3 = add(display3);
+
+        // Create another 3 virtual displays associated with a second virtual device.
+        int deviceId2 = 2;
+        TestDisplayDevice display4 =
+                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice2Display1", 600, 800, 0);
+        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display4, deviceId2);
+        LogicalDisplay virtualDevice2Display1 = add(display4);
+
+        TestDisplayDevice display5 =
+                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice2Display2", 600, 800, 0);
+        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display5, deviceId2);
+        LogicalDisplay virtualDevice2Display2 = add(display5);
+
+        // The final display is created with FLAG_OWN_DISPLAY_GROUP set.
+        TestDisplayDevice display6 =
+                createDisplayDevice(
+                        Display.TYPE_VIRTUAL,
+                        "virtualDevice2Display3",
+                        600,
+                        800,
+                        DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display6, deviceId2);
+        LogicalDisplay virtualDevice2Display3 = add(display6);
+
+        // Verify that the internal display is in the default display group.
+        assertEquals(
+                DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(defaultDisplay)));
+
+        // Verify that all the displays for virtual device 1 are in the same (non-default) display
+        // group.
+        int virtualDevice1DisplayGroupId =
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+                        id(virtualDevice1Display1));
+        assertNotEquals(DEFAULT_DISPLAY_GROUP, virtualDevice1DisplayGroupId);
+        assertEquals(
+                virtualDevice1DisplayGroupId,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+                        id(virtualDevice1Display2)));
+        assertEquals(
+                virtualDevice1DisplayGroupId,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+                        id(virtualDevice1Display3)));
+
+        // The first 2 displays for virtual device 2 should be in the same non-default group.
+        int virtualDevice2DisplayGroupId =
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+                        id(virtualDevice2Display1));
+        assertNotEquals(DEFAULT_DISPLAY_GROUP, virtualDevice2DisplayGroupId);
+        assertEquals(
+                virtualDevice2DisplayGroupId,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+                        id(virtualDevice2Display2)));
+        // virtualDevice2Display3 was created with FLAG_OWN_DISPLAY_GROUP and shouldn't be grouped
+        // with other displays of this device or be in the default display group.
+        assertNotEquals(
+                virtualDevice2DisplayGroupId,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+                        id(virtualDevice2Display3)));
+        assertNotEquals(
+                DEFAULT_DISPLAY_GROUP,
+                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
+                        id(virtualDevice2Display3)));
+    }
+
+    @Test
+    public void testDeviceShouldBeWoken() {
+        assertTrue(mLogicalDisplayMapper.shouldDeviceBeWoken(DEVICE_STATE_OPEN,
+                DEVICE_STATE_CLOSED,
+                /* isInteractive= */false,
+                /* isBootCompleted= */true));
+    }
+
+    @Test
+    public void testDeviceShouldNotBeWoken() {
+        assertFalse(mLogicalDisplayMapper.shouldDeviceBeWoken(DEVICE_STATE_CLOSED,
+                DEVICE_STATE_OPEN,
+                /* isInteractive= */false,
+                /* isBootCompleted= */true));
+    }
+
+    @Test
+    public void testDeviceShouldBePutToSleep() {
+        assertTrue(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
+                DEVICE_STATE_OPEN,
+                /* isOverrideActive= */false,
+                /* isInteractive= */true,
+                /* isBootCompleted= */true));
+    }
+
+    @Test
+    public void testDeviceShouldNotSleepWhenFoldSettingTrue() {
+        when(mFoldSettingWrapperMock.shouldStayAwakeOnFold()).thenReturn(true);
+
+        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
+                DEVICE_STATE_OPEN,
+                /* isOverrideActive= */false,
+                /* isInteractive= */true,
+                /* isBootCompleted= */true));
+    }
+
+    @Test
+    public void testDeviceShouldNotBePutToSleep() {
+        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_OPEN,
+                DEVICE_STATE_CLOSED,
+                /* isOverrideActive= */false,
+                /* isInteractive= */true,
+                /* isBootCompleted= */true));
+        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
+                INVALID_DEVICE_STATE,
+                /* isOverrideActive= */false,
+                /* isInteractive= */true,
+                /* isBootCompleted= */true));
+    }
+
+    @Test
+    public void testDeviceShouldNotBePutToSleepDifferentBaseState() {
+        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
+                DEVICE_STATE_OPEN,
+                /* isOverrideActive= */true,
+                /* isInteractive= */true,
+                /* isBootCompleted= */true));
+    }
+
+    @Test
+    public void testDeviceStateLocked() {
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        Layout layout = new Layout();
+        layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
+                /* isDefault= */ true, /* isEnabled= */ true, /* displayGroup= */ null,
+                mIdProducer, POSITION_UNKNOWN,
+                /* leadDisplayAddress= */ null,
+                /* brightnessThrottlingMapId= */ "concurrent",
+                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+        layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
+                /* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null,
+                mIdProducer, POSITION_UNKNOWN,
+                /* leadDisplayAddress= */ null,
+                /* brightnessThrottlingMapId= */ "concurrent",
+                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
+        when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
+
+        layout = new Layout();
+        createNonDefaultDisplay(layout, device1, /* enabled= */ false, /* group= */ null);
+        createDefaultDisplay(layout, device2);
+        when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout);
+        when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout);
+
+        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(4);
+
+        LogicalDisplay display1 = add(device1);
+        assertEquals(info(display1).address, info(device1).address);
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        LogicalDisplay display2 = add(device2);
+        assertEquals(info(display2).address, info(device2).address);
+        // We can only have one default display
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+        advanceTime(1000);
+        // The new state is not applied until the boot is completed
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+
+        mLogicalDisplayMapper.onBootCompleted();
+        advanceTime(1000);
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+        assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device1)
+                .getLeadDisplayIdLocked());
+        assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device2)
+                .getLeadDisplayIdLocked());
+        assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device1)
+                .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
+        assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2)
+                .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
+
+        mLogicalDisplayMapper.setDeviceStateLocked(1, false);
+        advanceTime(1000);
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
+                mLogicalDisplayMapper.getDisplayLocked(device1)
+                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
+        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
+                mLogicalDisplayMapper.getDisplayLocked(device2)
+                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
+
+        mLogicalDisplayMapper.setDeviceStateLocked(2, false);
+        advanceTime(1000);
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
+        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
+        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
+                mLogicalDisplayMapper.getDisplayLocked(device1)
+                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
+        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
+                mLogicalDisplayMapper.getDisplayLocked(device2)
+                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
+    }
+
+    @Test
+    public void testEnabledAndDisabledDisplays() {
+        DisplayAddress displayAddressOne = new TestUtils.TestDisplayAddress();
+        DisplayAddress displayAddressTwo = new TestUtils.TestDisplayAddress();
+        DisplayAddress displayAddressThree = new TestUtils.TestDisplayAddress();
+
+        TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, "one",
+                TYPE_INTERNAL, 600, 800, DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, "two",
+                TYPE_INTERNAL, 200, 800, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+        TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, "three",
+                TYPE_INTERNAL, 600, 900, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+
+        Layout threeDevicesEnabledLayout = new Layout();
+        createDefaultDisplay(threeDevicesEnabledLayout, displayAddressOne);
+        createNonDefaultDisplay(threeDevicesEnabledLayout, displayAddressTwo,
+                /* enabled= */ true, /* group= */ null);
+        createNonDefaultDisplay(threeDevicesEnabledLayout, displayAddressThree,
+                /* enabled= */ true, /* group= */ null);
+
+        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT))
+                .thenReturn(threeDevicesEnabledLayout);
+
+        LogicalDisplay display1 = add(device1);
+        LogicalDisplay display2 = add(device2);
+        LogicalDisplay display3 = add(device3);
+
+        // ensure 3 displays are returned
+        int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID, false);
+        assertEquals(3, ids.length);
+        Arrays.sort(ids);
+        assertEquals(DEFAULT_DISPLAY, ids[0]);
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1,
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device2,
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device3,
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+                threeDevicesEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+                threeDevicesEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+                threeDevicesEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+
+        Layout oneDeviceEnabledLayout = new Layout();
+        createDefaultDisplay(oneDeviceEnabledLayout, displayAddressOne);
+        createNonDefaultDisplay(oneDeviceEnabledLayout, displayAddressTwo,
+                /* enabled= */ false, /* group= */ null);
+        createNonDefaultDisplay(oneDeviceEnabledLayout, displayAddressThree,
+                /* enabled= */ false, /* group= */ null);
+
+        when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout);
+        when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout);
+
+        // 1) Set the new state
+        // 2) Mark the displays as STATE_OFF so that it can continue with transition
+        // 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state
+        // 4) Dispatch handler events.
+        mLogicalDisplayMapper.onBootCompleted();
+        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
+        advanceTime(1000);
+        final int[] allDisplayIds = mLogicalDisplayMapper.getDisplayIdsLocked(
+                Process.SYSTEM_UID, false);
+        if (allDisplayIds.length != 1) {
+            throw new RuntimeException("Displays: \n"
+                    + mLogicalDisplayMapper.getDisplayLocked(device1).toString()
+                    + "\n" + mLogicalDisplayMapper.getDisplayLocked(device2).toString()
+                    + "\n" + mLogicalDisplayMapper.getDisplayLocked(device3).toString());
+        }
+        // ensure only one display is returned
+        assertEquals(1, allDisplayIds.length);
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1,
+                /* includeDisabled= */ false));
+        assertNull(mLogicalDisplayMapper.getDisplayLocked(device2,
+                /* includeDisabled= */ false));
+        assertNull(mLogicalDisplayMapper.getDisplayLocked(device3,
+                /* includeDisabled= */ false));
+        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
+                oneDeviceEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+        assertNull(mLogicalDisplayMapper.getDisplayLocked(
+                oneDeviceEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+        assertNull(mLogicalDisplayMapper.getDisplayLocked(
+                oneDeviceEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(),
+                /* includeDisabled= */ false));
+
+        // Now do it again to go back to state 1
+        mLogicalDisplayMapper.setDeviceStateLocked(1, false);
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
+        advanceTime(1000);
+        final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked(
+                Process.SYSTEM_UID, false);
+
+        // ensure all three displays are returned
+        assertEquals(3, threeDisplaysEnabled.length);
+    }
+
+    @Test
+    public void testCreateNewLogicalDisplay() {
+        DisplayDevice device1 = createDisplayDevice(TYPE_EXTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
+        LogicalDisplay display1 = add(device1);
+
+        assertTrue(display1.isEnabledLocked());
+
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(2);
+        LogicalDisplay display2 = add(device2);
+
+        assertFalse(display2.isEnabledLocked());
+    }
+    @Test
+    public void testDisplayFlagRear() {
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_REAR);
+
+        Layout layout = new Layout();
+        layout.createDefaultDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
+                mIdProducer);
+        layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
+                /* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null,
+                mIdProducer, POSITION_REAR, /* leadDisplayAddress= */ null,
+                /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+                /* refreshRateThermalThrottlingMapId= */null);
+        when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
+
+        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
+
+        LogicalDisplay display1 = add(device1);
+        assertEquals(info(display1).address, info(device1).address);
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        LogicalDisplay display2 = add(device2);
+        assertEquals(info(display2).address, info(device2).address);
+        // We can only have one default display
+        assertEquals(DEFAULT_DISPLAY, id(display1));
+
+        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
+        advanceTime(1000);
+        mLogicalDisplayMapper.onBootCompleted();
+        advanceTime(1000);
+
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
+        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
+
+        assertEquals(POSITION_UNKNOWN,
+                mLogicalDisplayMapper.getDisplayLocked(device1).getDevicePositionLocked());
+        assertEquals(POSITION_REAR,
+                mLogicalDisplayMapper.getDisplayLocked(device2).getDevicePositionLocked());
+    }
+
+    /////////////////
+    // Helper Methods
+    /////////////////
+
+    private void createDefaultDisplay(Layout layout, DisplayDevice device) {
+        createDefaultDisplay(layout, info(device).address);
+    }
+
+    private void createDefaultDisplay(Layout layout, DisplayAddress address) {
+        layout.createDefaultDisplayLocked(address, mIdProducer);
+    }
+
+    private void createNonDefaultDisplay(Layout layout, DisplayDevice device, boolean enabled,
+            String group) {
+        createNonDefaultDisplay(layout, info(device).address, enabled, group);
+    }
+
+    private void createNonDefaultDisplay(Layout layout, DisplayAddress address, boolean enabled,
+            String group) {
+        layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer,
+                Layout.Display.POSITION_UNKNOWN, /* leadDisplayAddress= */ null,
+                /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
+                /* refreshRateThermalThrottlingMapId= */ null);
+    }
+
+    private void advanceTime(long timeMs) {
+        mLooper.moveTimeForward(1000);
+        mLooper.dispatchAll();
+    }
+
+    private TestDisplayDevice createDisplayDevice(int type, int width, int height, int flags) {
+        return createDisplayDevice(
+                new TestUtils.TestDisplayAddress(), /*  uniqueId */ "", type, width, height, flags);
+    }
+
+    private TestDisplayDevice createDisplayDevice(
+            int type, String uniqueId, int width, int height, int flags) {
+        return createDisplayDevice(
+                new TestUtils.TestDisplayAddress(), uniqueId, type, width, height, flags);
+    }
+
+    private TestDisplayDevice createDisplayDevice(
+            DisplayAddress address, String uniqueId, int type, int width, int height, int flags) {
+        TestDisplayDevice device = new TestDisplayDevice();
+        DisplayDeviceInfo displayDeviceInfo = device.getSourceInfo();
+        displayDeviceInfo.type = type;
+        displayDeviceInfo.uniqueId = uniqueId;
+        displayDeviceInfo.width = width;
+        displayDeviceInfo.height = height;
+        displayDeviceInfo.flags = flags;
+        displayDeviceInfo.supportedModes = new Display.Mode[1];
+        displayDeviceInfo.supportedModes[0] = new Display.Mode(1, width, height, 60f);
+        displayDeviceInfo.modeId = 1;
+        displayDeviceInfo.address = address;
+        return device;
+    }
+
+    private DisplayDeviceInfo info(DisplayDevice device) {
+        return device.getDisplayDeviceInfoLocked();
+    }
+
+    private int width(DisplayDevice device) {
+        return info(device).width;
+    }
+
+    private int height(DisplayDevice device) {
+        return info(device).height;
+    }
+
+    private DisplayInfo info(LogicalDisplay display) {
+        return display.getDisplayInfoLocked();
+    }
+
+    private int id(LogicalDisplay display) {
+        return display.getDisplayIdLocked();
+    }
+
+    private LogicalDisplay add(DisplayDevice device) {
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_ADDED);
+        ArgumentCaptor<LogicalDisplay> displayCaptor =
+                ArgumentCaptor.forClass(LogicalDisplay.class);
+        verify(mListenerMock).onLogicalDisplayEventLocked(
+                displayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_ADDED));
+        clearInvocations(mListenerMock);
+        return displayCaptor.getValue();
+    }
+
+    private void testDisplayDeviceAddAndRemove_NonInternal(int type) {
+        DisplayDevice device = createDisplayDevice(type, 600, 800, 0);
+
+        // add
+        LogicalDisplay displayAdded = add(device);
+        assertEquals(info(displayAdded).address, info(device).address);
+        assertNotEquals(DEFAULT_DISPLAY, id(displayAdded));
+
+        // remove
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
+        verify(mListenerMock).onLogicalDisplayEventLocked(
+                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
+        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
+        assertNotEquals(DEFAULT_DISPLAY, id(displayRemoved));
+    }
+
+    class TestDisplayDevice extends DisplayDevice {
+        private DisplayDeviceInfo mInfo;
+        private DisplayDeviceInfo mSentInfo;
+        private int mState;
+
+        TestDisplayDevice() {
+            super(null, null, "test_display_" + sUniqueTestDisplayId++, mContextMock);
+            mInfo = new DisplayDeviceInfo();
+        }
+
+        @Override
+        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+            if (mSentInfo == null) {
+                mSentInfo = new DisplayDeviceInfo();
+                mSentInfo.copyFrom(mInfo);
+            }
+            return mSentInfo;
+        }
+
+        @Override
+        public void applyPendingDisplayDeviceInfoChangesLocked() {
+            mSentInfo = null;
+        }
+
+        @Override
+        public boolean hasStableUniqueId() {
+            return true;
+        }
+
+        public DisplayDeviceInfo getSourceInfo() {
+            return mInfo;
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
new file mode 100644
index 0000000..2065479
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -0,0 +1,290 @@
+/*
+ * 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.server.display;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PropertyInvalidatedCache;
+import android.graphics.Point;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.layout.Layout;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+@SmallTest
+public class LogicalDisplayTest {
+    private static final int DISPLAY_ID = 0;
+    private static final int LAYER_STACK = 0;
+    private static final int DISPLAY_WIDTH = 100;
+    private static final int DISPLAY_HEIGHT = 200;
+    private static final int MODE_ID = 1;
+
+    private LogicalDisplay mLogicalDisplay;
+    private DisplayDevice mDisplayDevice;
+    private DisplayDeviceRepository mDeviceRepo;
+    private final DisplayDeviceInfo mDisplayDeviceInfo = new DisplayDeviceInfo();
+
+    @Before
+    public void setUp() {
+        // Share classloader to allow package private access.
+        System.setProperty("dexmaker.share_classloader", "true");
+        mDisplayDevice = mock(DisplayDevice.class);
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
+
+        mDisplayDeviceInfo.copyFrom(new DisplayDeviceInfo());
+        mDisplayDeviceInfo.width = DISPLAY_WIDTH;
+        mDisplayDeviceInfo.height = DISPLAY_HEIGHT;
+        mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
+        mDisplayDeviceInfo.modeId = MODE_ID;
+        mDisplayDeviceInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
+                DISPLAY_WIDTH, DISPLAY_HEIGHT, /* refreshRate= */ 60)};
+        when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(mDisplayDeviceInfo);
+
+        // Disable binder caches in this process.
+        PropertyInvalidatedCache.disableForTestMode();
+
+        mDeviceRepo = new DisplayDeviceRepository(
+                new DisplayManagerService.SyncRoot(),
+                new PersistentDataStore(new PersistentDataStore.Injector() {
+                    @Override
+                    public InputStream openRead() {
+                        return null;
+                    }
+
+                    @Override
+                    public OutputStream startWrite() {
+                        return null;
+                    }
+
+                    @Override
+                    public void finishWrite(OutputStream os, boolean success) {}
+                }));
+        mDeviceRepo.onDisplayDeviceEvent(mDisplayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+    }
+
+    @Test
+    public void testGetDisplayPosition() {
+        Point expectedPosition = new Point();
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
+
+        expectedPosition.set(20, 40);
+        mLogicalDisplay.setDisplayOffsetsLocked(20, 40);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
+
+        expectedPosition.set(40, -20);
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = DISPLAY_HEIGHT;
+        displayInfo.logicalHeight = DISPLAY_WIDTH;
+        displayInfo.rotation = Surface.ROTATION_90;
+        mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
+    }
+
+    // TODO: b/288880734 - fix test after display tests migration
+    @Test
+    @Ignore
+    public void testDisplayInputFlags() {
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
+        reset(t);
+
+        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        verify(t).setDisplayFlags(any(), eq(0));
+        reset(t);
+
+        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_VIRTUAL;
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
+        reset(t);
+
+        mLogicalDisplay.setEnabledLocked(false);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        verify(t).setDisplayFlags(any(), eq(0));
+        reset(t);
+
+        mLogicalDisplay.setEnabledLocked(true);
+        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
+        reset(t);
+    }
+
+    @Test
+    public void testRearDisplaysArePresentationDisplaysThatDestroyContentOnRemoval() {
+        // Assert that the display isn't a presentation display by default, with a default remove
+        // mode
+        assertEquals(0, mLogicalDisplay.getDisplayInfoLocked().flags);
+        assertEquals(Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY,
+                mLogicalDisplay.getDisplayInfoLocked().removeMode);
+
+        // Update position and test to see that it's been updated to a rear, presentation display
+        // that destroys content on removal
+        mLogicalDisplay.setDevicePositionLocked(Layout.Display.POSITION_REAR);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertEquals(Display.FLAG_REAR | Display.FLAG_PRESENTATION,
+                mLogicalDisplay.getDisplayInfoLocked().flags);
+        assertEquals(Display.REMOVE_MODE_DESTROY_CONTENT,
+                mLogicalDisplay.getDisplayInfoLocked().removeMode);
+
+        // And then check the unsetting the position resets both
+        mLogicalDisplay.setDevicePositionLocked(Layout.Display.POSITION_UNKNOWN);
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertEquals(0, mLogicalDisplay.getDisplayInfoLocked().flags);
+        assertEquals(Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY,
+                mLogicalDisplay.getDisplayInfoLocked().removeMode);
+    }
+
+    @Test
+    public void testUpdateLayoutLimitedRefreshRate() {
+        SurfaceControl.RefreshRateRange layoutLimitedRefreshRate =
+                new SurfaceControl.RefreshRateRange(0, 120);
+        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
+        mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(layoutLimitedRefreshRate);
+        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
+        // Display info should only be updated when updateLocked is called
+        assertEquals(info2, info1);
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
+        assertNotEquals(info3, info2);
+        assertEquals(layoutLimitedRefreshRate, info3.layoutLimitedRefreshRate);
+    }
+
+    @Test
+    public void testUpdateLayoutLimitedRefreshRate_setsDirtyFlag() {
+        SurfaceControl.RefreshRateRange layoutLimitedRefreshRate =
+                new SurfaceControl.RefreshRateRange(0, 120);
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(layoutLimitedRefreshRate);
+        assertTrue(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+    }
+
+    @Test
+    public void testUpdateRefreshRateThermalThrottling() {
+        SparseArray<SurfaceControl.RefreshRateRange> refreshRanges = new SparseArray<>();
+        refreshRanges.put(0, new SurfaceControl.RefreshRateRange(0, 120));
+        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
+        mLogicalDisplay.updateThermalRefreshRateThrottling(refreshRanges);
+        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
+        // Display info should only be updated when updateLocked is called
+        assertEquals(info2, info1);
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
+        assertNotEquals(info3, info2);
+        assertTrue(refreshRanges.contentEquals(info3.thermalRefreshRateThrottling));
+    }
+
+    @Test
+    public void testUpdateRefreshRateThermalThrottling_setsDirtyFlag() {
+        SparseArray<SurfaceControl.RefreshRateRange> refreshRanges = new SparseArray<>();
+        refreshRanges.put(0, new SurfaceControl.RefreshRateRange(0, 120));
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateThermalRefreshRateThrottling(refreshRanges);
+        assertTrue(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+    }
+
+    @Test
+    public void testUpdateDisplayGroupIdLocked() {
+        int newId = 999;
+        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
+        mLogicalDisplay.updateDisplayGroupIdLocked(newId);
+        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
+        // Display info should only be updated when updateLocked is called
+        assertEquals(info2, info1);
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
+        assertNotEquals(info3, info2);
+        assertEquals(newId, info3.displayGroupId);
+    }
+
+    @Test
+    public void testUpdateDisplayGroupIdLocked_setsDirtyFlag() {
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateDisplayGroupIdLocked(99);
+        assertTrue(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+    }
+
+    @Test
+    public void testSetThermalBrightnessThrottlingDataId() {
+        String brightnessThrottlingDataId = "throttling_data_id";
+        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
+        mLogicalDisplay.setThermalBrightnessThrottlingDataIdLocked(brightnessThrottlingDataId);
+        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
+        // Display info should only be updated when updateLocked is called
+        assertEquals(info2, info1);
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
+        assertNotEquals(info3, info2);
+        assertEquals(brightnessThrottlingDataId, info3.thermalBrightnessThrottlingDataId);
+    }
+
+    @Test
+    public void testSetThermalBrightnessThrottlingDataId_setsDirtyFlag() {
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.setThermalBrightnessThrottlingDataIdLocked("99");
+        assertTrue(mLogicalDisplay.isDirtyLocked());
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        assertFalse(mLogicalDisplay.isDirtyLocked());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
new file mode 100644
index 0000000..9f91916
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright 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.display;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.hardware.display.BrightnessConfiguration;
+import android.os.Handler;
+import android.os.test.TestLooper;
+import android.util.Pair;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PersistentDataStoreTest {
+    private PersistentDataStore mDataStore;
+    private TestInjector mInjector;
+    private TestLooper mTestLooper;
+
+    @Before
+    public void setUp() {
+        mInjector = new TestInjector();
+        mTestLooper = new TestLooper();
+        Handler handler = new Handler(mTestLooper.getLooper());
+        mDataStore = new PersistentDataStore(mInjector, handler);
+    }
+
+    @Test
+    public void testLoadBrightness() {
+        final String uniqueDisplayId = "test:123";
+        final DisplayDevice testDisplayDevice = new DisplayDevice(
+                null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+
+        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<display-manager-state>\n"
+                + "  <display-states>\n"
+                + "    <display unique-id=\"test:123\">\n"
+                + "      <brightness-value user-serial=\"1\">0.1</brightness-value>\n"
+                + "      <brightness-value user-serial=\"2\">0.2</brightness-value>\n"
+                + "    </display>\n"
+                + "  </display-states>\n"
+                + "</display-manager-state>\n";
+
+        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+        mInjector.setReadStream(is);
+        mDataStore.loadIfNeeded();
+
+        float brightness = mDataStore.getBrightness(testDisplayDevice, 1);
+        assertEquals(0.1, brightness, 0.01);
+
+        brightness = mDataStore.getBrightness(testDisplayDevice, 2);
+        assertEquals(0.2, brightness, 0.01);
+    }
+
+    @Test
+    public void testSetBrightness_brightnessTagWithNoUserId_updatesToBrightnessTagWithUserId() {
+        final String uniqueDisplayId = "test:123";
+        final DisplayDevice testDisplayDevice =
+                new DisplayDevice(null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+
+        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<display-manager-state>\n"
+                + "  <display-states>\n"
+                + "    <color-mode>0</color-mode>\n"
+                + "    <display unique-id=\"test:123\">\n"
+                + "      <brightness-value>0.5</brightness-value>\n"
+                + "    </display>\n"
+                + "  </display-states>\n"
+                + "</display-manager-state>\n";
+
+        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+        mInjector.setReadStream(is);
+        mDataStore.loadIfNeeded();
+
+        float user1Brightness = mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+        float user2Brightness = mDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+        assertEquals(0.5, user1Brightness, 0.01);
+        assertEquals(0.5, user2Brightness, 0.01);
+
+        // Override the value for user 2. Default user must have been removed.
+        mDataStore.setBrightness(testDisplayDevice, 0.2f, 2 /* userSerial */  /* brightness*/);
+
+        user1Brightness = mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+        user2Brightness = mDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+        assertTrue(Float.isNaN(user1Brightness));
+        assertEquals(0.2f, user2Brightness, 0.01);
+
+        // Override the value for user 1. User-specific brightness values should co-exist.
+        mDataStore.setBrightness(testDisplayDevice, 0.1f, 1 /* userSerial */  /* brightness*/);
+        user1Brightness = mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+        user2Brightness = mDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+        assertEquals(0.1f, user1Brightness, 0.01);
+        assertEquals(0.2f, user2Brightness, 0.01);
+
+        // Validate saveIfNeeded writes user-specific brightnes.
+        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();
+
+        user1Brightness = newDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */);
+        user2Brightness = newDataStore.getBrightness(testDisplayDevice, 2 /* userSerial */);
+        float unknownUserBrightness =
+                newDataStore.getBrightness(testDisplayDevice, 999 /* userSerial */);
+        assertEquals(0.1f, user1Brightness, 0.01);
+        assertEquals(0.2f, user2Brightness, 0.01);
+        assertTrue(Float.isNaN(unknownUserBrightness));
+    }
+
+    @Test
+    public void testLoadingBrightnessConfigurations() {
+        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<display-manager-state>\n"
+                + "  <brightness-configurations>\n"
+                + "    <brightness-configuration"
+                + "         user-serial=\"1\""
+                + "         package-name=\"example.com\""
+                + "         timestamp=\"123456\">\n"
+                + "      <brightness-curve description=\"something\">\n"
+                + "        <brightness-point lux=\"0\" nits=\"13.25\"/>\n"
+                + "        <brightness-point lux=\"25\" nits=\"35.94\"/>\n"
+                + "      </brightness-curve>\n"
+                + "    </brightness-configuration>\n"
+                + "    <brightness-configuration user-serial=\"3\">\n"
+                + "      <brightness-curve>\n"
+                + "        <brightness-point lux=\"0\" nits=\"13.25\"/>\n"
+                + "        <brightness-point lux=\"10.2\" nits=\"15\"/>\n"
+                + "      </brightness-curve>\n"
+                + "    </brightness-configuration>\n"
+                + "  </brightness-configurations>\n"
+                + "</display-manager-state>\n";
+        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+        mInjector.setReadStream(is);
+        mDataStore.loadIfNeeded();
+        BrightnessConfiguration config = mDataStore.getBrightnessConfiguration(1 /*userSerial*/);
+        Pair<float[], float[]> curve = config.getCurve();
+        float[] expectedLux = { 0f, 25f };
+        float[] expectedNits = { 13.25f, 35.94f };
+        assertArrayEquals(expectedLux, curve.first, "lux");
+        assertArrayEquals(expectedNits, curve.second, "nits");
+        assertEquals("something", config.getDescription());
+
+        config = mDataStore.getBrightnessConfiguration(3 /*userSerial*/);
+        curve = config.getCurve();
+        expectedLux = new float[] { 0f, 10.2f };
+        expectedNits = new float[] { 13.25f, 15f };
+        assertArrayEquals(expectedLux, curve.first, "lux");
+        assertArrayEquals(expectedNits, curve.second, "nits");
+        assertNull(config.getDescription());
+    }
+
+    @Test
+    public void testBrightnessConfigWithInvalidCurveIsIgnored() {
+        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<display-manager-state>\n"
+                + "  <brightness-configurations>\n"
+                + "    <brightness-configuration user-serial=\"0\">\n"
+                + "      <brightness-curve>\n"
+                + "        <brightness-point lux=\"1\" nits=\"13.25\"/>\n"
+                + "        <brightness-point lux=\"25\" nits=\"35.94\"/>\n"
+                + "      </brightness-curve>\n"
+                + "    </brightness-configuration>\n"
+                + "  </brightness-configurations>\n"
+                + "</display-manager-state>\n";
+        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+        mInjector.setReadStream(is);
+        mDataStore.loadIfNeeded();
+        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
+    }
+
+    @Test
+    public void testBrightnessConfigWithInvalidFloatsIsIgnored() {
+        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<display-manager-state>\n"
+                + "  <brightness-configurations>\n"
+                + "    <brightness-configuration user-serial=\"0\">\n"
+                + "      <brightness-curve>\n"
+                + "        <brightness-point lux=\"0\" nits=\"13.25\"/>\n"
+                + "        <brightness-point lux=\"0xFF\" nits=\"foo\"/>\n"
+                + "      </brightness-curve>\n"
+                + "    </brightness-configuration>\n"
+                + "  </brightness-configurations>\n"
+                + "</display-manager-state>\n";
+        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+        mInjector.setReadStream(is);
+        mDataStore.loadIfNeeded();
+        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
+    }
+
+    @Test
+    public void testEmptyBrightnessConfigurationsDoesntCrash() {
+        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<display-manager-state>\n"
+                + "  <brightness-configurations />\n"
+                + "</display-manager-state>\n";
+        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
+        mInjector.setReadStream(is);
+        mDataStore.loadIfNeeded();
+        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
+    }
+
+    @Test
+    public void testStoreAndReloadOfDisplayBrightnessConfigurations() throws InterruptedException {
+        final String uniqueDisplayId = "test:123";
+        int userSerial = 0;
+        String packageName = "pdsTestPackage";
+        final float[] lux = { 0f, 10f };
+        final float[] nits = {1f, 100f };
+        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
+                .setDescription("a description")
+                .build();
+        mDataStore.loadIfNeeded();
+        assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
+                userSerial));
+
+        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+
+        mDataStore.setBrightnessConfigurationForDisplayLocked(config, testDisplayDevice, userSerial,
+                packageName);
+
+        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.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
+                userSerial));
+        assertEquals(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
+                userSerial), newDataStore.getBrightnessConfigurationForDisplayLocked(
+                        uniqueDisplayId, userSerial));
+    }
+
+    @Test
+    public void testSetBrightnessConfigurationFailsWithUnstableId() {
+        final String uniqueDisplayId = "test:123";
+        int userSerial = 0;
+        String packageName = "pdsTestPackage";
+        final float[] lux = { 0f, 10f };
+        final float[] nits = {1f, 100f };
+        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
+                .setDescription("a description")
+                .build();
+        mDataStore.loadIfNeeded();
+        assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
+                userSerial));
+
+        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return false;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+
+        assertFalse(mDataStore.setBrightnessConfigurationForDisplayLocked(
+                config, testDisplayDevice, userSerial, packageName));
+    }
+
+    @Test
+    public void testStoreAndReloadOfBrightnessConfigurations() throws InterruptedException {
+        final float[] lux = { 0f, 10f };
+        final float[] nits = {1f, 100f };
+        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
+                .setDescription("a description")
+                .build();
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        String packageName = context.getPackageName();
+
+        mDataStore.loadIfNeeded();
+        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
+        mDataStore.setBrightnessConfigurationForUser(config, 0, packageName);
+
+        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.getBrightnessConfiguration(0 /*userSerial*/));
+        assertEquals(mDataStore.getBrightnessConfiguration(0 /*userSerial*/),
+                newDataStore.getBrightnessConfiguration(0 /*userSerial*/));
+    }
+
+    @Test
+    public void testNullBrightnessConfiguration() {
+        final float[] lux = { 0f, 10f };
+        final float[] nits = {1f, 100f };
+        int userSerial = 0;
+        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
+                .setDescription("a description")
+                .build();
+        mDataStore.loadIfNeeded();
+        assertNull(mDataStore.getBrightnessConfiguration(userSerial));
+
+        mDataStore.setBrightnessConfigurationForUser(config, userSerial, "packagename");
+        assertNotNull(mDataStore.getBrightnessConfiguration(userSerial));
+
+        mDataStore.setBrightnessConfigurationForUser(null, userSerial, "packagename");
+        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);
+    }
+
+    @Test
+    public void testBrightnessInitialisesWithInvalidFloat() {
+        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;
+            }
+        };
+
+        // Set any value which initialises Display state
+        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();
+        assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice, 1 /* userSerial */)));
+    }
+
+    @Test
+    public void testStoreAndRestoreBrightnessNitsForDefaultDisplay() {
+        float brightnessNitsForDefaultDisplay = 190;
+        mDataStore.loadIfNeeded();
+        mDataStore.setBrightnessNitsForDefaultDisplay(brightnessNitsForDefaultDisplay);
+
+        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();
+        assertEquals(brightnessNitsForDefaultDisplay,
+                mDataStore.getBrightnessNitsForDefaultDisplay(), 0);
+        assertEquals(brightnessNitsForDefaultDisplay,
+                newDataStore.getBrightnessNitsForDefaultDisplay(), 0);
+    }
+
+    @Test
+    public void testInitialBrightnessNitsForDefaultDisplay() {
+        mDataStore.loadIfNeeded();
+        assertEquals(-1, mDataStore.getBrightnessNitsForDefaultDisplay(), 0);
+    }
+
+    public class TestInjector extends PersistentDataStore.Injector {
+        private InputStream mReadStream;
+        private OutputStream mWriteStream;
+
+        private boolean mWasSuccessful;
+
+        @Override
+        public InputStream openRead() throws FileNotFoundException {
+            if (mReadStream != null) {
+                return mReadStream;
+            } else {
+                throw new FileNotFoundException();
+            }
+        }
+
+        @Override
+        public OutputStream startWrite() {
+            return mWriteStream;
+        }
+
+        @Override
+        public void finishWrite(OutputStream os, boolean success) {
+            mWasSuccessful = success;
+            try {
+                os.close();
+            } catch (IOException e) {
+                // This method can't throw IOException since the super implementation doesn't, so
+                // we just wrap it in a RuntimeException so we end up crashing the test all the
+                // same.
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void setReadStream(InputStream is) {
+            mReadStream = is;
+        }
+
+        public void setWriteStream(OutputStream os) {
+            mWriteStream = os;
+        }
+
+        public boolean wasWriteSuccessful() {
+            return mWasSuccessful;
+        }
+    }
+
+    private static void assertArrayEquals(float[] expected, float[] actual, String name) {
+        assertEquals("Expected " + name + " arrays to be the same length!",
+                expected.length, actual.length);
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals("Expected " + name + " arrays to be equivalent when value " + i
+                    + "differs", expected[i], actual[i], 0.01 /*tolerance*/);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java b/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java
new file mode 100644
index 0000000..8b45145
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.input.InputSensorInfo;
+import android.os.Parcel;
+import android.os.SystemClock;
+import android.view.DisplayAddress;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public final class TestUtils {
+
+    public static SensorEvent createSensorEvent(Sensor sensor, int value) throws Exception {
+        final Constructor<SensorEvent> constructor =
+                SensorEvent.class.getDeclaredConstructor(int.class);
+        constructor.setAccessible(true);
+        final SensorEvent event = constructor.newInstance(1);
+        event.sensor = sensor;
+        event.values[0] = value;
+        event.timestamp = SystemClock.elapsedRealtimeNanos();
+        return event;
+    }
+
+
+    public static void setSensorType(Sensor sensor, int type, String strType) throws Exception {
+        Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
+        setter.setAccessible(true);
+        setter.invoke(sensor, type);
+        if (strType != null) {
+            Field f = sensor.getClass().getDeclaredField("mStringType");
+            f.setAccessible(true);
+            f.set(sensor, strType);
+        }
+    }
+
+    public static void setMaximumRange(Sensor sensor, float maximumRange) throws Exception {
+        Method setter = Sensor.class.getDeclaredMethod("setRange", Float.TYPE, Float.TYPE);
+        setter.setAccessible(true);
+        setter.invoke(sensor, maximumRange, 1);
+    }
+
+    public static Sensor createSensor(int type, String strType) throws Exception {
+        Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+        constr.setAccessible(true);
+        Sensor sensor = constr.newInstance();
+        setSensorType(sensor, type, strType);
+        return sensor;
+    }
+
+    public static Sensor createSensor(int type, String strType, float maximumRange)
+            throws Exception {
+        Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+        constr.setAccessible(true);
+        Sensor sensor = constr.newInstance();
+        setSensorType(sensor, type, strType);
+        setMaximumRange(sensor, maximumRange);
+        return sensor;
+    }
+
+    public static Sensor createSensor(String type, String name) {
+        return new Sensor(new InputSensorInfo(
+                name, "vendor", 0, 0, 0, 1f, 1f, 1, 1, 1, 1,
+                type, "", 0, 0, 0));
+    }
+
+    /**
+     * Create a custom {@link DisplayAddress} to ensure we're not relying on any specific
+     * display-address implementation in our code. Intentionally uses default object (reference)
+     * equality rules.
+     */
+    public static class TestDisplayAddress extends DisplayAddress {
+        @Override
+        public void writeToParcel(Parcel out, int flags) { }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
new file mode 100644
index 0000000..c4f4838
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -0,0 +1,387 @@
+/*
+ * 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.display.brightness;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.HandlerExecutor;
+import android.os.PowerManager;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.BrightnessSetting;
+import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
+
+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 final class DisplayBrightnessControllerTest {
+    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+    private static final float DEFAULT_BRIGHTNESS = 0.15f;
+
+    @Mock
+    private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private BrightnessSetting mBrightnessSetting;
+    @Mock
+    private Runnable mOnBrightnessChangeRunnable;
+
+    @Mock
+    private HandlerExecutor mBrightnessChangeExecutor;
+
+    private final DisplayBrightnessController.Injector mInjector = new
+            DisplayBrightnessController.Injector() {
+        @Override
+        DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(
+                Context context, int displayId) {
+            return mDisplayBrightnessStrategySelector;
+        }
+    };
+
+    private DisplayBrightnessController mDisplayBrightnessController;
+
+    @Before
+    public void before() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mBrightnessSetting.getBrightness()).thenReturn(Float.NaN);
+        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(-1f);
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
+                .thenReturn(true);
+        mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector,
+                DISPLAY_ID, DEFAULT_BRIGHTNESS, mBrightnessSetting, mOnBrightnessChangeRunnable,
+                mBrightnessChangeExecutor);
+    }
+
+    @Test
+    public void testIfFirstScreenBrightnessIsDefault() {
+        assertEquals(DEFAULT_BRIGHTNESS, mDisplayBrightnessController.getCurrentBrightness(),
+                /* delta= */ 0.0f);
+    }
+
+    @Test
+    public void testUpdateBrightness() {
+        DisplayPowerRequest displayPowerRequest = mock(DisplayPowerRequest.class);
+        DisplayBrightnessStrategy displayBrightnessStrategy = mock(DisplayBrightnessStrategy.class);
+        int targetDisplayState = Display.STATE_DOZE;
+        when(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                targetDisplayState)).thenReturn(displayBrightnessStrategy);
+        mDisplayBrightnessController.updateBrightness(displayPowerRequest, targetDisplayState);
+        verify(displayBrightnessStrategy).updateBrightness(displayPowerRequest);
+        assertEquals(mDisplayBrightnessController.getCurrentDisplayBrightnessStrategy(),
+                displayBrightnessStrategy);
+    }
+
+    @Test
+    public void isAllowAutoBrightnessWhileDozingConfigDelegatesToDozeBrightnessStrategy() {
+        mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig();
+        verify(mDisplayBrightnessStrategySelector).isAllowAutoBrightnessWhileDozingConfig();
+    }
+
+    @Test
+    public void setTemporaryBrightness() {
+        float temporaryBrightness = 0.4f;
+        TemporaryBrightnessStrategy temporaryBrightnessStrategy = mock(
+                TemporaryBrightnessStrategy.class);
+        when(mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()).thenReturn(
+                temporaryBrightnessStrategy);
+        mDisplayBrightnessController.setTemporaryBrightness(temporaryBrightness);
+        verify(temporaryBrightnessStrategy).setTemporaryScreenBrightness(temporaryBrightness);
+    }
+
+    @Test
+    public void setCurrentScreenBrightness() {
+        // Current Screen brightness is set as expected when a different value than the current
+        // is set
+        float currentScreenBrightness = 0.4f;
+        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentScreenBrightness);
+        assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
+                currentScreenBrightness, /* delta= */ 0.0f);
+        verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
+
+        // No change to the current screen brightness is same as the existing one
+        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentScreenBrightness);
+        verifyNoMoreInteractions(mBrightnessChangeExecutor);
+    }
+
+    @Test
+    public void setPendingScreenBrightnessSetting() {
+        float pendingScreenBrightness = 0.4f;
+        mDisplayBrightnessController.setPendingScreenBrightness(pendingScreenBrightness);
+        assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
+                pendingScreenBrightness, /* delta= */ 0.0f);
+    }
+
+    @Test
+    public void updateUserSetScreenBrightness() {
+        // No brightness is set if the pending brightness is invalid
+        mDisplayBrightnessController.setPendingScreenBrightness(Float.NaN);
+        assertFalse(mDisplayBrightnessController.updateUserSetScreenBrightness());
+
+        // user set brightness is not set if the current and the pending brightness are same.
+        float currentBrightness = 0.4f;
+        TemporaryBrightnessStrategy temporaryBrightnessStrategy = mock(
+                TemporaryBrightnessStrategy.class);
+        when(mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()).thenReturn(
+                temporaryBrightnessStrategy);
+        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentBrightness);
+        mDisplayBrightnessController.setPendingScreenBrightness(currentBrightness);
+        mDisplayBrightnessController.setTemporaryBrightness(currentBrightness);
+        assertFalse(mDisplayBrightnessController.updateUserSetScreenBrightness());
+        verify(temporaryBrightnessStrategy).setTemporaryScreenBrightness(
+                PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
+                PowerManager.BRIGHTNESS_INVALID_FLOAT, /* delta= */ 0.0f);
+
+        // user set brightness is set as expected
+        currentBrightness = 0.4f;
+        float pendingScreenBrightness = 0.3f;
+        float temporaryScreenBrightness = 0.2f;
+        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentBrightness);
+        mDisplayBrightnessController.setPendingScreenBrightness(pendingScreenBrightness);
+        mDisplayBrightnessController.setTemporaryBrightness(temporaryScreenBrightness);
+        assertTrue(mDisplayBrightnessController.updateUserSetScreenBrightness());
+        assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
+                pendingScreenBrightness, /* delta= */ 0.0f);
+        assertEquals(mDisplayBrightnessController.getLastUserSetScreenBrightness(),
+                pendingScreenBrightness, /* delta= */ 0.0f);
+        verify(mBrightnessChangeExecutor, times(2))
+                .execute(mOnBrightnessChangeRunnable);
+        verify(temporaryBrightnessStrategy, times(2))
+                .setTemporaryScreenBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
+                PowerManager.BRIGHTNESS_INVALID_FLOAT, /* delta= */ 0.0f);
+    }
+
+    @Test
+    public void registerBrightnessSettingChangeListener() {
+        BrightnessSetting.BrightnessSettingListener brightnessSettingListener = mock(
+                BrightnessSetting.BrightnessSettingListener.class);
+        mDisplayBrightnessController.registerBrightnessSettingChangeListener(
+                brightnessSettingListener);
+        verify(mBrightnessSetting).registerListener(brightnessSettingListener);
+        assertEquals(mDisplayBrightnessController.getBrightnessSettingListener(),
+                brightnessSettingListener);
+    }
+
+    @Test
+    public void getScreenBrightnessSetting() {
+        // getScreenBrightnessSetting returns the value relayed by BrightnessSetting, if the
+        // valid is valid and in range
+        float brightnessSetting = 0.2f;
+        when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
+        assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), brightnessSetting,
+                /* delta= */ 0.0f);
+
+        // getScreenBrightnessSetting value is clamped if BrightnessSetting returns value beyond max
+        brightnessSetting = 1.1f;
+        when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
+        assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), 1.0f,
+                /* delta= */ 0.0f);
+
+        // getScreenBrightnessSetting returns default value is BrightnessSetting returns invalid
+        // value.
+        brightnessSetting = Float.NaN;
+        when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
+        assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), DEFAULT_BRIGHTNESS,
+                /* delta= */ 0.0f);
+    }
+
+    @Test
+    public void setBrightnessSetsInBrightnessSetting() {
+        float brightnessValue = 0.3f;
+        mDisplayBrightnessController.setBrightness(brightnessValue);
+        verify(mBrightnessSetting).setBrightness(brightnessValue);
+    }
+
+    @Test
+    public void updateScreenBrightnessSetting() {
+        // This interaction happens in the constructor itself
+        verify(mBrightnessSetting).getBrightness();
+
+        // Sets the appropriate value when valid, and not equal to the current brightness
+        float brightnessValue = 0.3f;
+        mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
+        assertEquals(mDisplayBrightnessController.getCurrentBrightness(), brightnessValue,
+                0.0f);
+        verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
+        verify(mBrightnessSetting).setBrightness(brightnessValue);
+        verify(mBrightnessSetting).setUserSerial(anyInt());
+
+        // Does nothing if the value is invalid
+        mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN);
+        verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting);
+
+        // Does nothing if the value is same as the current brightness
+        brightnessValue = 0.2f;
+        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(brightnessValue);
+        verify(mBrightnessChangeExecutor, times(2))
+                .execute(mOnBrightnessChangeRunnable);
+        mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
+        verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting);
+    }
+
+    @Test
+    public void testConvertToNits() {
+        final float brightness = 0.5f;
+        final float nits = 300;
+        final float adjustedNits = 200;
+
+        // ABC is null
+        assertEquals(-1f, mDisplayBrightnessController.convertToNits(brightness),
+                /* delta= */ 0);
+        assertEquals(-1f, mDisplayBrightnessController.convertToAdjustedNits(brightness),
+                /* delta= */ 0);
+
+        AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        when(automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        when(automaticBrightnessController.convertToAdjustedNits(brightness))
+                .thenReturn(adjustedNits);
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+
+        assertEquals(nits, mDisplayBrightnessController.convertToNits(brightness), /* delta= */ 0);
+        assertEquals(adjustedNits, mDisplayBrightnessController.convertToAdjustedNits(brightness),
+                /* delta= */ 0);
+    }
+
+    @Test
+    public void testConvertToFloatScale() {
+        float brightness = 0.5f;
+        float nits = 300;
+
+        // ABC is null
+        assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                mDisplayBrightnessController.convertToFloatScale(nits), /* delta= */ 0);
+
+        AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+
+        assertEquals(brightness, mDisplayBrightnessController.convertToFloatScale(nits),
+                /* delta= */ 0);
+    }
+
+    @Test
+    public void stop() {
+        BrightnessSetting.BrightnessSettingListener brightnessSettingListener = mock(
+                BrightnessSetting.BrightnessSettingListener.class);
+        mDisplayBrightnessController.registerBrightnessSettingChangeListener(
+                brightnessSettingListener);
+        mDisplayBrightnessController.stop();
+        verify(mBrightnessSetting).unregisterListener(brightnessSettingListener);
+    }
+
+    @Test
+    public void testLoadNitBasedBrightnessSetting() {
+        // When the nits value is valid, the brightness is set from the old default display nits
+        // value
+        float nits = 200f;
+        float brightness = 0.3f;
+        AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
+        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+        verify(mBrightnessSetting).setBrightness(brightness);
+        assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
+        clearInvocations(automaticBrightnessController, mBrightnessSetting);
+
+        // When the nits value is invalid, the brightness is resumed from where it was last set
+        nits = -1;
+        brightness = 0.4f;
+        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
+        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+        when(mBrightnessSetting.getBrightness()).thenReturn(brightness);
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+        verify(mBrightnessSetting, never()).setBrightness(brightness);
+        assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
+        clearInvocations(automaticBrightnessController, mBrightnessSetting);
+
+        // When the display is a non-default display, the brightness is resumed from where it was
+        // last set
+        int nonDefaultDisplayId = 1;
+        mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector,
+                nonDefaultDisplayId, DEFAULT_BRIGHTNESS, mBrightnessSetting,
+                mOnBrightnessChangeRunnable, mBrightnessChangeExecutor);
+        brightness = 0.5f;
+        when(mBrightnessSetting.getBrightness()).thenReturn(brightness);
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+        assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
+        verifyZeroInteractions(automaticBrightnessController);
+        verify(mBrightnessSetting, never()).getBrightnessNitsForDefaultDisplay();
+        verify(mBrightnessSetting, never()).setBrightness(brightness);
+    }
+
+    @Test
+    public void testChangeBrightnessNitsWhenUserChanges() {
+        float brightnessValue1 = 0.3f;
+        float nits1 = 200f;
+        float brightnessValue2 = 0.5f;
+        float nits2 = 300f;
+        AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        when(automaticBrightnessController.convertToNits(brightnessValue1)).thenReturn(nits1);
+        when(automaticBrightnessController.convertToNits(brightnessValue2)).thenReturn(nits2);
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+
+        mDisplayBrightnessController.setBrightness(brightnessValue1, 1 /* user-serial */);
+        verify(mBrightnessSetting).setUserSerial(1);
+        verify(mBrightnessSetting).setBrightness(brightnessValue1);
+        verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits1);
+
+        mDisplayBrightnessController.setBrightness(brightnessValue2, 2 /* user-serial */);
+        verify(mBrightnessSetting).setUserSerial(2);
+        verify(mBrightnessSetting).setBrightness(brightnessValue2);
+        verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits2);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
new file mode 100644
index 0000000..8497dab
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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.display.brightness;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
+import android.view.Display;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
+import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
+import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
+import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
+import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy;
+import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy;
+import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayBrightnessStrategySelectorTest {
+    private static final boolean DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING = false;
+    private static final int DISPLAY_ID = 1;
+
+    @Mock
+    private ScreenOffBrightnessStrategy mScreenOffBrightnessModeStrategy;
+    @Mock
+    private DozeBrightnessStrategy mDozeBrightnessModeStrategy;
+    @Mock
+    private OverrideBrightnessStrategy mOverrideBrightnessStrategy;
+    @Mock
+    private TemporaryBrightnessStrategy mTemporaryBrightnessStrategy;
+    @Mock
+    private BoostBrightnessStrategy mBoostBrightnessStrategy;
+    @Mock
+    private InvalidBrightnessStrategy mInvalidBrightnessStrategy;
+    @Mock
+    private FollowerBrightnessStrategy mFollowerBrightnessStrategy;
+    @Mock
+    private Resources mResources;
+
+    private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
+    private Context mContext;
+
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+    @Before
+    public void before() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContext);
+        when(mContext.getContentResolver()).thenReturn(contentResolver);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mInvalidBrightnessStrategy.getName()).thenReturn("InvalidBrightnessStrategy");
+        DisplayBrightnessStrategySelector.Injector injector =
+                new DisplayBrightnessStrategySelector.Injector() {
+                    @Override
+                    ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() {
+                        return mScreenOffBrightnessModeStrategy;
+                    }
+
+                    @Override
+                    DozeBrightnessStrategy getDozeBrightnessStrategy() {
+                        return mDozeBrightnessModeStrategy;
+                    }
+
+                    @Override
+                    OverrideBrightnessStrategy getOverrideBrightnessStrategy() {
+                        return mOverrideBrightnessStrategy;
+                    }
+
+                    @Override
+                    TemporaryBrightnessStrategy getTemporaryBrightnessStrategy() {
+                        return mTemporaryBrightnessStrategy;
+                    }
+
+                    @Override
+                    BoostBrightnessStrategy getBoostBrightnessStrategy() {
+                        return mBoostBrightnessStrategy;
+                    }
+
+                    @Override
+                    FollowerBrightnessStrategy getFollowerBrightnessStrategy(int displayId) {
+                        return mFollowerBrightnessStrategy;
+                    }
+
+                    @Override
+                    InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
+                        return mInvalidBrightnessStrategy;
+                    }
+                };
+        mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+                injector, DISPLAY_ID);
+
+    }
+
+    @Test
+    public void selectStrategySelectsDozeStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
+                DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_DOZE), mDozeBrightnessModeStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsScreenOffStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_OFF), mScreenOffBrightnessModeStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsOverrideStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.screenBrightnessOverride = 0.4f;
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_ON), mOverrideBrightnessStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsTemporaryStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(0.3f);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_ON), mTemporaryBrightnessStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsBoostStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.boostScreenBrightness = true;
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+        displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_ON), mBoostBrightnessStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsInvalidStrategyWhenNoStrategyIsValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        displayPowerRequest.screenBrightnessOverride = Float.NaN;
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
+        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_ON), mInvalidBrightnessStrategy);
+    }
+
+    @Test
+    public void selectStrategySelectsFollowerStrategyWhenValid() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+                DisplayManagerInternal.DisplayPowerRequest.class);
+        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f);
+        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+                Display.STATE_ON), mFollowerBrightnessStrategy);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifierTest.java
new file mode 100644
index 0000000..266f5c1
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifierTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class BrightnessLowPowerModeModifierTest {
+    private static final float FLOAT_TOLERANCE = 0.001f;
+    private static final float DEFAULT_BRIGHTNESS = 0.5f;
+    private static final float LOW_POWER_BRIGHTNESS_FACTOR = 0.8f;
+    private static final float EXPECTED_LOW_POWER_BRIGHTNESS =
+            DEFAULT_BRIGHTNESS * LOW_POWER_BRIGHTNESS_FACTOR;
+    private final DisplayPowerRequest mRequest = new DisplayPowerRequest();
+    private final DisplayBrightnessState.Builder mBuilder = prepareBuilder();
+    private BrightnessLowPowerModeModifier mClamper;
+
+    @Before
+    public void setUp() {
+        mClamper = new BrightnessLowPowerModeModifier();
+        mRequest.screenLowPowerBrightnessFactor = LOW_POWER_BRIGHTNESS_FACTOR;
+        mRequest.lowPowerMode = true;
+    }
+
+    @Test
+    public void testApply_lowPowerModeOff() {
+        mRequest.lowPowerMode = false;
+
+        mClamper.apply(mRequest, mBuilder);
+
+        assertEquals(DEFAULT_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0, mBuilder.getBrightnessReason().getModifier());
+        assertTrue(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_lowPowerModeOn() {
+        mClamper.apply(mRequest, mBuilder);
+
+        assertEquals(EXPECTED_LOW_POWER_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(BrightnessReason.MODIFIER_LOW_POWER,
+                mBuilder.getBrightnessReason().getModifier());
+        assertFalse(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_lowPowerModeOnAndLowPowerBrightnessFactorHigh() {
+        mRequest.screenLowPowerBrightnessFactor = 1.1f;
+
+        mClamper.apply(mRequest, mBuilder);
+
+        assertEquals(DEFAULT_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(BrightnessReason.MODIFIER_LOW_POWER,
+                mBuilder.getBrightnessReason().getModifier());
+        assertFalse(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_lowPowerModeOnAndMinBrightness() {
+        mBuilder.setBrightness(0.0f);
+        mClamper.apply(mRequest, mBuilder);
+
+        assertEquals(0.0f, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0, mBuilder.getBrightnessReason().getModifier());
+        assertFalse(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_lowPowerModeOnAndLowPowerAlreadyApplied() {
+        mClamper.apply(mRequest, mBuilder);
+        DisplayBrightnessState.Builder builder = prepareBuilder();
+
+        mClamper.apply(mRequest, builder);
+
+        assertEquals(EXPECTED_LOW_POWER_BRIGHTNESS, builder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(BrightnessReason.MODIFIER_LOW_POWER,
+                builder.getBrightnessReason().getModifier());
+        assertTrue(builder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_lowPowerModeOffAfterLowPowerOn() {
+        mClamper.apply(mRequest, mBuilder);
+        mRequest.lowPowerMode = false;
+        DisplayBrightnessState.Builder builder = prepareBuilder();
+
+        mClamper.apply(mRequest, builder);
+
+        assertEquals(DEFAULT_BRIGHTNESS, builder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0, builder.getBrightnessReason().getModifier());
+        assertFalse(builder.isSlowChange());
+    }
+
+    private DisplayBrightnessState.Builder prepareBuilder() {
+        DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
+        builder.setBrightness(DEFAULT_BRIGHTNESS);
+        builder.setIsSlowChange(true);
+        return builder;
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
new file mode 100644
index 0000000..37d0f62
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.display.DisplayManager;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.Temperature;
+import android.provider.DeviceConfig;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
+import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.testutils.FakeDeviceConfigInterface;
+import com.android.server.testutils.TestHandler;
+
+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;
+
+import java.util.List;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@RunWith(JUnitParamsRunner.class)
+public class BrightnessThermalClamperTest {
+
+    private static final float FLOAT_TOLERANCE = 0.001f;
+
+    private static final String DISPLAY_ID = "displayId";
+    @Mock
+    private IThermalService mMockThermalService;
+    @Mock
+    private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
+
+    private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
+            new FakeDeviceConfigInterface();
+    private final TestHandler mTestHandler = new TestHandler(null);
+    private BrightnessThermalClamper mClamper;
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mClamper = new BrightnessThermalClamper(new TestInjector(), mTestHandler,
+                mMockClamperChangeListener, new TestThermalData());
+        mTestHandler.flush();
+    }
+
+    @Test
+    public void testTypeIsThermal() {
+        assertEquals(BrightnessClamper.Type.THERMAL, mClamper.getType());
+    }
+
+    @Test
+    public void testNoThrottlingData() {
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+    @Keep
+    private static Object[][] testThrottlingData() {
+        // throttlingLevels, throttlingStatus, expectedActive, expectedBrightness
+        return new Object[][] {
+                // no throttling data
+                {List.of(), Temperature.THROTTLING_LIGHT, false, PowerManager.BRIGHTNESS_MAX},
+                // throttlingStatus < min throttling data
+                {List.of(
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+                        Temperature.THROTTLING_LIGHT, false, PowerManager.BRIGHTNESS_MAX},
+                // throttlingStatus = min throttling data
+                {List.of(
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+                        Temperature.THROTTLING_MODERATE, true, 0.5f},
+                // throttlingStatus between min and max throttling data
+                {List.of(
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+                        Temperature.THROTTLING_SEVERE, true, 0.5f},
+                // throttlingStatus = max throttling data
+                {List.of(
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+                        Temperature.THROTTLING_CRITICAL, true, 0.1f},
+                // throttlingStatus > max throttling data
+                {List.of(
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
+                        new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
+                        Temperature.THROTTLING_EMERGENCY, true, 0.1f},
+        };
+    }
+    @Test
+    @Parameters(method = "testThrottlingData")
+    public void testNotifyThrottlingAfterOnDisplayChange(List<ThrottlingLevel> throttlingLevels,
+            @Temperature.ThrottlingStatus int throttlingStatus,
+            boolean expectedActive, float expectedBrightness) throws RemoteException {
+        IThermalEventListener thermalEventListener = captureThermalEventListener();
+        mClamper.onDisplayChanged(new TestThermalData(throttlingLevels));
+        mTestHandler.flush();
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        thermalEventListener.notifyThrottling(createTemperature(throttlingStatus));
+        mTestHandler.flush();
+        assertEquals(expectedActive, mClamper.isActive());
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    @Parameters(method = "testThrottlingData")
+    public void testOnDisplayChangeAfterNotifyThrottlng(List<ThrottlingLevel> throttlingLevels,
+            @Temperature.ThrottlingStatus int throttlingStatus,
+            boolean expectedActive, float expectedBrightness) throws RemoteException {
+        IThermalEventListener thermalEventListener = captureThermalEventListener();
+        thermalEventListener.notifyThrottling(createTemperature(throttlingStatus));
+        mTestHandler.flush();
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        mClamper.onDisplayChanged(new TestThermalData(throttlingLevels));
+        mTestHandler.flush();
+        assertEquals(expectedActive, mClamper.isActive());
+        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testOverrideData() throws RemoteException {
+        IThermalEventListener thermalEventListener = captureThermalEventListener();
+        thermalEventListener.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE));
+        mTestHandler.flush();
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        mClamper.onDisplayChanged(new TestThermalData(
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f))));
+        mTestHandler.flush();
+        assertTrue(mClamper.isActive());
+        assertEquals(0.5f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        overrideThrottlingData("displayId,1,emergency,0.4");
+        mClamper.onDeviceConfigChanged();
+        mTestHandler.flush();
+
+        assertFalse(mClamper.isActive());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+
+        overrideThrottlingData("displayId,1,moderate,0.4");
+        mClamper.onDeviceConfigChanged();
+        mTestHandler.flush();
+
+        assertTrue(mClamper.isActive());
+        assertEquals(0.4f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
+    }
+
+    private IThermalEventListener captureThermalEventListener() throws RemoteException {
+        ArgumentCaptor<IThermalEventListener> captor = ArgumentCaptor.forClass(
+                IThermalEventListener.class);
+        verify(mMockThermalService).registerThermalEventListenerWithType(captor.capture(), eq(
+                Temperature.TYPE_SKIN));
+        return captor.getValue();
+    }
+
+    private Temperature createTemperature(@Temperature.ThrottlingStatus int status) {
+        return new Temperature(100, Temperature.TYPE_SKIN, "test_temperature", status);
+    }
+
+    private void overrideThrottlingData(String data) {
+        mFakeDeviceConfigInterface.putProperty(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, data);
+    }
+
+    private class TestInjector extends BrightnessThermalClamper.Injector {
+        @Override
+        IThermalService getThermalService() {
+            return mMockThermalService;
+        }
+
+        @Override
+        DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
+            return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
+        }
+    }
+
+    private static class TestThermalData implements BrightnessThermalClamper.ThermalData {
+
+        private final String mUniqueDisplayId;
+        private final String mDataId;
+        private final ThermalBrightnessThrottlingData mData;
+
+        private TestThermalData() {
+            this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null);
+        }
+
+        private TestThermalData(List<ThrottlingLevel> data) {
+            this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data);
+        }
+        private TestThermalData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) {
+            mUniqueDisplayId = uniqueDisplayId;
+            mDataId = dataId;
+            mData = ThermalBrightnessThrottlingData.create(data);
+        }
+        @NonNull
+        @Override
+        public String getUniqueDisplayId() {
+            return mUniqueDisplayId;
+        }
+
+        @NonNull
+        @Override
+        public String getThermalThrottlingDataId() {
+            return mDataId;
+        }
+
+        @Nullable
+        @Override
+        public ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData() {
+            return mData;
+        }
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
new file mode 100644
index 0000000..b652576
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.display.brightness.strategy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+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.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.view.Display;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.BrightnessReason;
+
+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.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AutomaticBrightnessStrategyTest {
+    private static final int DISPLAY_ID = 0;
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+    @Mock
+    private AutomaticBrightnessController mAutomaticBrightnessController;
+
+    private BrightnessConfiguration mBrightnessConfiguration;
+    private float mDefaultScreenAutoBrightnessAdjustment;
+    private Context mContext;
+    private AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+
+    @Before
+    public void before() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+        when(mContext.getContentResolver()).thenReturn(resolver);
+        mDefaultScreenAutoBrightnessAdjustment = Settings.System.getFloat(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Float.NaN);
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.5f);
+        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, DISPLAY_ID);
+
+        mBrightnessConfiguration = new BrightnessConfiguration.Builder(
+                new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
+        when(mAutomaticBrightnessController.hasUserDataPoints()).thenReturn(true);
+        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+                mAutomaticBrightnessController);
+        mAutomaticBrightnessStrategy.setBrightnessConfiguration(mBrightnessConfiguration,
+                true);
+    }
+
+    @After
+    public void after() {
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, mDefaultScreenAutoBrightnessAdjustment);
+    }
+
+    @Test
+    public void testAutoBrightnessState_AutoBrightnessDisabled() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(false);
+        int targetDisplayState = Display.STATE_ON;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, 0.5f,
+                        false, policy, true);
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void testAutoBrightnessState_DisplayIsOff() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_OFF;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, 0.5f,
+                        false, policy, true);
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesNotAllow() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_DOZE;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, 0.5f,
+                        false, policy, true);
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void testAutoBrightnessState_BrightnessReasonIsOverride() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_ON;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_OVERRIDE;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, 0.5f,
+                        false, policy, true);
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_DOZE;
+        boolean allowAutoBrightnessWhileDozing = true;
+        int brightnessReason = BrightnessReason.REASON_DOZE;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, 0.4f,
+                        true, policy, true);
+        assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void testAutoBrightnessState_DisplayIsOn() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        int targetDisplayState = Display.STATE_ON;
+        boolean allowAutoBrightnessWhileDozing = false;
+        int brightnessReason = BrightnessReason.REASON_UNKNOWN;
+        float lastUserSetBrightness = 0.2f;
+        boolean userSetBrightnessChanged = true;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        float pendingBrightnessAdjustment = 0.1f;
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+                allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
+                userSetBrightnessChanged);
+        verify(mAutomaticBrightnessController)
+                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+                        mBrightnessConfiguration,
+                        lastUserSetBrightness,
+                        userSetBrightnessChanged, pendingBrightnessAdjustment,
+                        true, policy, true);
+        assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
+        assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
+    }
+
+    @Test
+    public void accommodateUserBrightnessChangesWorksAsExpected() {
+        // Verify the state if automaticBrightnessController is configured.
+        assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
+        boolean userSetBrightnessChanged = true;
+        float lastUserSetScreenBrightness = 0.2f;
+        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        BrightnessConfiguration brightnessConfiguration = new BrightnessConfiguration.Builder(
+                new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
+        int autoBrightnessState = AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+        float temporaryAutoBrightnessAdjustments = 0.4f;
+        mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
+        setTemporaryAutoBrightnessAdjustment(temporaryAutoBrightnessAdjustments);
+        mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
+                lastUserSetScreenBrightness, policy, brightnessConfiguration,
+                autoBrightnessState);
+        verify(mAutomaticBrightnessController).configure(autoBrightnessState,
+                brightnessConfiguration,
+                lastUserSetScreenBrightness,
+                userSetBrightnessChanged, temporaryAutoBrightnessAdjustments,
+                /* userChangedAutoBrightnessAdjustment= */ false, policy,
+                /* shouldResetShortTermModel= */ true);
+        assertTrue(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
+        assertFalse(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
+        assertTrue(mAutomaticBrightnessStrategy.isShortTermModelActive());
+        // Verify the state when automaticBrightnessController is not configured
+        setTemporaryAutoBrightnessAdjustment(Float.NaN);
+        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(null);
+        mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
+        mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
+                lastUserSetScreenBrightness, policy, brightnessConfiguration,
+                autoBrightnessState);
+        assertFalse(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
+        assertTrue(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
+        assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
+    }
+
+    @Test
+    public void adjustAutomaticBrightnessStateIfValid() throws Settings.SettingNotFoundException {
+        float brightnessState = 0.3f;
+        float autoBrightnessAdjustment = 0.2f;
+        when(mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()).thenReturn(
+                autoBrightnessAdjustment);
+        mAutomaticBrightnessStrategy.adjustAutomaticBrightnessStateIfValid(brightnessState);
+        assertEquals(autoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(autoBrightnessAdjustment, Settings.System.getFloatForUser(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+                UserHandle.USER_CURRENT), 0.0f);
+        assertEquals(BrightnessReason.ADJUSTMENT_AUTO,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
+        float invalidBrightness = -0.5f;
+        mAutomaticBrightnessStrategy
+                .adjustAutomaticBrightnessStateIfValid(invalidBrightness);
+        assertEquals(autoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(0,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
+    }
+
+    @Test
+    public void updatePendingAutoBrightnessAdjustments() {
+        // Verify the state when the pendingAutoBrightnessAdjustments are not present
+        setPendingAutoBrightnessAdjustment(Float.NaN);
+        assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+        assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+        // Verify the state when the pendingAutoBrightnessAdjustments are present, but
+        // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are the same
+        float autoBrightnessAdjustment = 0.3f;
+        setPendingAutoBrightnessAdjustment(autoBrightnessAdjustment);
+        setAutoBrightnessAdjustment(autoBrightnessAdjustment);
+        assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+        assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
+                0.0f);
+        // Verify the state when the pendingAutoBrightnessAdjustments are present, and
+        // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are not the same
+        float pendingAutoBrightnessAdjustment = 0.2f;
+        setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustment);
+        setTemporaryAutoBrightnessAdjustment(0.1f);
+        assertTrue(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+        assertTrue(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+        assertEquals(pendingAutoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
+                0.0f);
+        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(),
+                0.0f);
+    }
+
+    @Test
+    public void setAutomaticBrightnessWorksAsExpected() {
+        float automaticScreenBrightness = 0.3f;
+        AutomaticBrightnessController automaticBrightnessController = mock(
+                AutomaticBrightnessController.class);
+        when(automaticBrightnessController.getAutomaticScreenBrightness(any(BrightnessEvent.class)))
+                .thenReturn(automaticScreenBrightness);
+        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+                automaticBrightnessController);
+        assertEquals(automaticScreenBrightness,
+                mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
+                        new BrightnessEvent(DISPLAY_ID)), 0.0f);
+    }
+
+    @Test
+    public void shouldUseAutoBrightness() {
+        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+        assertTrue(mAutomaticBrightnessStrategy.shouldUseAutoBrightness());
+    }
+
+    @Test
+    public void setPendingAutoBrightnessAdjustments() throws Settings.SettingNotFoundException {
+        float pendingAutoBrightnessAdjustments = 0.3f;
+        setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustments);
+        assertEquals(pendingAutoBrightnessAdjustments,
+                mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(), 0.0f);
+        assertEquals(pendingAutoBrightnessAdjustments, Settings.System.getFloatForUser(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+                UserHandle.USER_CURRENT), 0.0f);
+    }
+
+    @Test
+    public void setTemporaryAutoBrightnessAdjustment() {
+        float temporaryAutoBrightnessAdjustment = 0.3f;
+        mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
+                temporaryAutoBrightnessAdjustment);
+        assertEquals(temporaryAutoBrightnessAdjustment,
+                mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(), 0.0f);
+    }
+
+    @Test
+    public void setAutoBrightnessApplied() {
+        mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
+        assertTrue(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
+    }
+
+    @Test
+    public void testVerifyNoAutoBrightnessAdjustmentsArePopulatedForNonDefaultDisplay() {
+        int newDisplayId = 1;
+        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, newDisplayId);
+        mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(0.3f);
+        assertEquals(0.5f, mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(),
+                0.0f);
+    }
+
+    private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
+        Settings.System.putFloat(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
+        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+    }
+
+    private void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
+        mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
+                temporaryAutoBrightnessAdjustment);
+    }
+
+    private void setAutoBrightnessAdjustment(float autoBrightnessAdjustment) {
+        mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(autoBrightnessAdjustment);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
new file mode 100644
index 0000000..d8569f7
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.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.server.display.brightness.strategy;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.view.Display;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FollowerBrightnessStrategyTest {
+    private FollowerBrightnessStrategy mFollowerBrightnessStrategy;
+
+    @Before
+    public void before() {
+        mFollowerBrightnessStrategy = new FollowerBrightnessStrategy(Display.DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testUpdateBrightness() {
+        DisplayManagerInternal.DisplayPowerRequest
+                displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
+        float brightnessToFollow = 0.2f;
+        boolean slowChange = true;
+        mFollowerBrightnessStrategy.setBrightnessToFollow(brightnessToFollow, slowChange);
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_FOLLOWER);
+        DisplayBrightnessState expectedDisplayBrightnessState =
+                new DisplayBrightnessState.Builder()
+                        .setBrightness(brightnessToFollow)
+                        .setBrightnessReason(brightnessReason)
+                        .setSdrBrightness(brightnessToFollow)
+                        .setDisplayBrightnessStrategyName(mFollowerBrightnessStrategy.getName())
+                        .setIsSlowChange(slowChange)
+                        .build();
+        DisplayBrightnessState updatedDisplayBrightnessState =
+                mFollowerBrightnessStrategy.updateBrightness(displayPowerRequest);
+        assertEquals(expectedDisplayBrightnessState, updatedDisplayBrightnessState);
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/AppSaturationControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/AppSaturationControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/CctEvaluatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/CctEvaluatorTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
new file mode 100644
index 0000000..c7c09b5
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java
@@ -0,0 +1,1406 @@
+/*
+ * 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.display.color;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.hardware.display.ColorDisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.Time;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.provider.Settings.System;
+import android.test.mock.MockContentResolver;
+import android.view.Display;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.twilight.TwilightListener;
+import com.android.server.twilight.TwilightManager;
+import com.android.server.twilight.TwilightState;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class ColorDisplayServiceTest {
+
+    private Context mContext;
+    private int mUserId;
+
+    private MockTwilightManager mTwilightManager;
+    private DisplayTransformManager mDisplayTransformManager;
+    private DisplayManagerInternal mDisplayManagerInternal;
+
+    private ColorDisplayService mCds;
+    private ColorDisplayService.BinderService mBinderService;
+
+    private Resources mResourcesSpy;
+
+    private static final int[] MINIMAL_COLOR_MODES = new int[] {
+        ColorDisplayManager.COLOR_MODE_NATURAL,
+        ColorDisplayManager.COLOR_MODE_BOOSTED,
+    };
+
+    @Before
+    public void setUp() {
+        mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+        doReturn(mContext).when(mContext).getApplicationContext();
+
+        final Resources res = Mockito.spy(mContext.getResources());
+        doReturn(MINIMAL_COLOR_MODES).when(res).getIntArray(R.array.config_availableColorModes);
+        doReturn(true).when(res).getBoolean(R.bool.config_nightDisplayAvailable);
+        doReturn(true).when(res).getBoolean(R.bool.config_displayWhiteBalanceAvailable);
+        when(mContext.getResources()).thenReturn(res);
+        mResourcesSpy = res;
+
+        mUserId = ActivityManager.getCurrentUser();
+
+        final MockContentResolver cr = new MockContentResolver(mContext);
+        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        doReturn(cr).when(mContext).getContentResolver();
+
+        final AlarmManager am = Mockito.mock(AlarmManager.class);
+        doReturn(am).when(mContext).getSystemService(Context.ALARM_SERVICE);
+
+        mTwilightManager = new MockTwilightManager();
+        LocalServices.addService(TwilightManager.class, mTwilightManager);
+
+        mDisplayTransformManager = Mockito.mock(DisplayTransformManager.class);
+        doReturn(true).when(mDisplayTransformManager).needsLinearColorMatrix();
+        LocalServices.addService(DisplayTransformManager.class, mDisplayTransformManager);
+
+        mDisplayManagerInternal = Mockito.mock(DisplayManagerInternal.class);
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal);
+
+        mCds = new ColorDisplayService(mContext);
+        mBinderService = mCds.new BinderService();
+        LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
+                mCds.new ColorDisplayServiceInternal());
+    }
+
+    @After
+    public void tearDown() {
+        /*
+         * Wait for internal {@link Handler} to finish processing pending messages, so that test
+         * code can safelyremove {@link DisplayTransformManager} mock from {@link LocalServices}.
+         */
+        mCds.mHandler.runWithScissors(() -> { /* nop */ }, /* timeout */ 1000);
+        mCds = null;
+
+        LocalServices.removeServiceForTest(TwilightManager.class);
+        mTwilightManager = null;
+
+        LocalServices.removeServiceForTest(DisplayTransformManager.class);
+
+        mUserId = UserHandle.USER_NULL;
+        mContext = null;
+
+        FakeSettingsProvider.clearSettingsProvider();
+
+        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOffAfterNight_turnsOff() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOffBeforeNight_turnsOff() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, -180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOffDuringNight_turnsOff() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOffInFuture_turnsOff() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOnAfterNight_turnsOn() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOnBeforeNight_turnsOff() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, -180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOnDuringNight_turnsOff() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedAfterNight_ifOnInFuture_turnsOff() {
+        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOffAfterNight_turnsOff() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, 180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOffBeforeNight_turnsOff() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOffDuringNight_turnsOff() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOffInPast_turnsOff() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOnAfterNight_turnsOff() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, 180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOnBeforeNight_turnsOff() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOnDuringNight_turnsOff() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedBeforeNight_ifOnInPast_turnsOn() {
+        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOffAfterNight_turnsOn() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOffBeforeNight_turnsOn() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOffDuringNightInFuture_turnsOn() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOffDuringNightInPast_turnsOff() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOnAfterNight_turnsOn() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOnBeforeNight_turnsOn() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOnDuringNightInFuture_turnsOn() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void customSchedule_whenStartedDuringNight_ifOnDuringNightInPast_turnsOn() {
+        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOffAfterNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOffBeforeNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOffDuringNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOffInFuture_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOnAfterNight_turnsOn() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOnBeforeNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOnDuringNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedAfterNight_ifOnInFuture_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOffAfterNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOffBeforeNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOffDuringNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOffInPast_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOnAfterNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 180 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOnBeforeNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOnDuringNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedBeforeNight_ifOnInPast_turnsOn() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOffAfterNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOffBeforeNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOffDuringNightInFuture_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOffDuringNightInPast_turnsOff() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOnAfterNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOnBeforeNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOnDuringNightInFuture_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenStartedDuringNight_ifOnDuringNightInPast_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        startService();
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOffAfterNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOffBeforeNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -180 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOffDuringNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOffInFuture_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOnAfterNight_turnsOn() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOnBeforeNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -180 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOnDuringNight_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedAfterNight_ifOnInFuture_turnsOff() {
+        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOffAfterNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 180 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOffBeforeNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOffDuringNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOffInPast_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOnAfterNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 180 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOnBeforeNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOnDuringNight_turnsOff() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedBeforeNight_ifOnInPast_turnsOn() {
+        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOffAfterNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOffBeforeNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOffDuringNightInFuture_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOffDuringNightInPast_turnsOff() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(false /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(false /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOnAfterNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOnBeforeNight_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOnDuringNightInFuture_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void twilightSchedule_whenRebootedDuringNight_ifOnDuringNightInPast_turnsOn() {
+        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        final TwilightState state = mTwilightManager.getLastTwilightState();
+        mTwilightManager.setTwilightState(null);
+
+        startService();
+        assertActivated(true /* activated */);
+
+        mTwilightManager.setTwilightState(state);
+        assertActivated(true /* activated */);
+    }
+
+    @Test
+    public void accessibility_colorInversion_transformActivated() {
+        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
+            return;
+        }
+
+        setAccessibilityColorInversion(true);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+
+        startService();
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        assertActiveColorMode(mContext.getResources().getInteger(
+                R.integer.config_accessibilityColorMode));
+    }
+
+    @Test
+    public void accessibility_colorCorrection_transformActivated() {
+        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
+            return;
+        }
+
+        setAccessibilityColorCorrection(true);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+
+        startService();
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        assertActiveColorMode(mContext.getResources().getInteger(
+                R.integer.config_accessibilityColorMode));
+    }
+
+    @Test
+    public void accessibility_all_transformActivated() {
+        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
+            return;
+        }
+
+        setAccessibilityColorCorrection(true);
+        setAccessibilityColorInversion(true);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+
+        startService();
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        assertActiveColorMode(mContext.getResources().getInteger(
+                R.integer.config_accessibilityColorMode));
+    }
+
+    @Test
+    public void accessibility_none_transformActivated() {
+        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
+            return;
+        }
+
+        setAccessibilityColorCorrection(false);
+        setAccessibilityColorInversion(false);
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+
+        startService();
+        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        assertActiveColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+    }
+
+    @Test
+    public void displayWhiteBalance_enabled() {
+        setDisplayWhiteBalanceEnabled(true);
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+        mBinderService.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        startService();
+        assertDwbActive(true);
+    }
+
+    @Test
+    public void displayWhiteBalance_disabledAfterNightDisplayEnabled() {
+        setDisplayWhiteBalanceEnabled(true);
+        startService();
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        /* Since we are using FakeSettingsProvider which could not trigger observer change,
+         * force an update here.*/
+        mCds.updateDisplayWhiteBalanceStatus();
+        assertDwbActive(false);
+    }
+
+    @Test
+    public void displayWhiteBalance_enabledAfterNightDisplayDisabled() {
+        setDisplayWhiteBalanceEnabled(true);
+        startService();
+        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
+
+        mCds.updateDisplayWhiteBalanceStatus();
+        assertDwbActive(false);
+
+        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
+        mCds.updateDisplayWhiteBalanceStatus();
+        assertDwbActive(true);
+    }
+
+    @Test
+    public void displayWhiteBalance_enabledAfterLinearColorModeSelected() {
+        if (!isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) {
+            return;
+        }
+        setDisplayWhiteBalanceEnabled(true);
+        mBinderService.setColorMode(ColorDisplayManager.COLOR_MODE_SATURATED);
+        startService();
+        assertDwbActive(false);
+
+        mBinderService.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        mCds.updateDisplayWhiteBalanceStatus();
+        assertDwbActive(true);
+    }
+
+    @Test
+    public void displayWhiteBalance_disabledWhileAccessibilityColorCorrectionEnabled() {
+        setDisplayWhiteBalanceEnabled(true);
+        setAccessibilityColorCorrection(true);
+        startService();
+        assertDwbActive(false);
+
+        setAccessibilityColorCorrection(false);
+        mCds.updateDisplayWhiteBalanceStatus();
+        assertDwbActive(true);
+    }
+
+    @Test
+    public void displayWhiteBalance_disabledWhileAccessibilityColorInversionEnabled() {
+        setDisplayWhiteBalanceEnabled(true);
+        setAccessibilityColorInversion(true);
+        startService();
+        assertDwbActive(false);
+
+        setAccessibilityColorInversion(false);
+        mCds.updateDisplayWhiteBalanceStatus();
+        assertDwbActive(true);
+    }
+
+    @Test
+    public void compositionColorSpaces_noResources() {
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
+            .thenReturn(new int[] {});
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
+            .thenReturn(new int[] {});
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        startService();
+        verify(mDisplayTransformManager).setColorMode(
+                eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_INVALID));
+    }
+
+    @Test
+    public void compositionColorSpaces_invalidResources() {
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
+                .thenReturn(new int[] {
+                        ColorDisplayManager.COLOR_MODE_NATURAL,
+                        // Missing second color mode
+                });
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
+                .thenReturn(new int[] {
+                        Display.COLOR_MODE_SRGB,
+                        Display.COLOR_MODE_DISPLAY_P3
+                });
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        startService();
+        verify(mDisplayTransformManager).setColorMode(
+                eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_INVALID));
+    }
+
+    @Test
+    public void compositionColorSpaces_validResources_validColorMode() {
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
+                .thenReturn(new int[] {
+                        ColorDisplayManager.COLOR_MODE_NATURAL
+                });
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
+                .thenReturn(new int[] {
+                        Display.COLOR_MODE_SRGB,
+                });
+        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
+        startService();
+        verify(mDisplayTransformManager).setColorMode(
+                eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_SRGB));
+    }
+
+    @Test
+    public void compositionColorSpaces_validResources_invalidColorMode() {
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
+                .thenReturn(new int[] {
+                        ColorDisplayManager.COLOR_MODE_NATURAL
+                });
+        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
+                .thenReturn(new int[] {
+                        Display.COLOR_MODE_SRGB,
+                });
+        setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED);
+        startService();
+        verify(mDisplayTransformManager).setColorMode(
+                eq(ColorDisplayManager.COLOR_MODE_BOOSTED), any(), eq(Display.COLOR_MODE_INVALID));
+    }
+
+    @Test
+    public void getColorMode_noAvailableModes_returnsNotSet() {
+        when(mResourcesSpy.getIntArray(R.array.config_availableColorModes))
+                    .thenReturn(new int[] {});
+        startService();
+        verify(mDisplayTransformManager, never()).setColorMode(anyInt(), any(), anyInt());
+        assertThat(mBinderService.getColorMode()).isEqualTo(-1);
+    }
+
+    /**
+     * Configures Night display to use a custom schedule.
+     *
+     * @param startTimeOffset the offset relative to now to activate Night display (in minutes)
+     * @param endTimeOffset the offset relative to now to deactivate Night display (in minutes)
+     */
+    private void setAutoModeCustom(int startTimeOffset, int endTimeOffset) {
+        mBinderService.setNightDisplayAutoMode(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
+        mBinderService.setNightDisplayCustomStartTime(
+                new Time(getLocalTimeRelativeToNow(startTimeOffset)));
+        mBinderService
+                .setNightDisplayCustomEndTime(new Time(getLocalTimeRelativeToNow(endTimeOffset)));
+    }
+
+    /**
+     * Configures Night display to use the twilight schedule.
+     *
+     * @param sunsetOffset the offset relative to now for sunset (in minutes)
+     * @param sunriseOffset the offset relative to now for sunrise (in minutes)
+     */
+    private void setAutoModeTwilight(int sunsetOffset, int sunriseOffset) {
+        mBinderService.setNightDisplayAutoMode(ColorDisplayManager.AUTO_MODE_TWILIGHT);
+        mTwilightManager.setTwilightState(
+                getTwilightStateRelativeToNow(sunsetOffset, sunriseOffset));
+    }
+
+    /**
+     * Configures the Night display activated state.
+     *
+     * @param activated {@code true} if Night display should be activated
+     * @param lastActivatedTimeOffset the offset relative to now to record that Night display was
+     * activated (in minutes)
+     */
+    private void setNightDisplayActivated(boolean activated, int lastActivatedTimeOffset) {
+        mBinderService.setNightDisplayActivated(activated);
+        Secure.putStringForUser(mContext.getContentResolver(),
+                Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+                LocalDateTime.now().plusMinutes(lastActivatedTimeOffset).toString(),
+                mUserId);
+    }
+
+    /**
+     * Configures the Accessibility color correction setting state.
+     *
+     * @param state {@code true} if color inversion should be activated
+     */
+    private void setAccessibilityColorCorrection(boolean state) {
+        Secure.putIntForUser(mContext.getContentResolver(),
+                Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, state ? 1 : 0, mUserId);
+    }
+
+    /**
+     * Configures the Accessibility color inversion setting state.
+     *
+     * @param state {@code true} if color inversion should be activated
+     */
+    private void setAccessibilityColorInversion(boolean state) {
+        Secure.putIntForUser(mContext.getContentResolver(),
+                Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, state ? 1 : 0, mUserId);
+    }
+
+    /**
+     * Configures the Display White Balance setting state.
+     *
+     * @param enabled {@code true} if display white balance should be enabled
+     */
+    private void setDisplayWhiteBalanceEnabled(boolean enabled) {
+        Secure.putIntForUser(mContext.getContentResolver(),
+                Secure.DISPLAY_WHITE_BALANCE_ENABLED, enabled ? 1 : 0, mUserId);
+    }
+
+    /**
+     * Configures color mode.
+     */
+    private void setColorMode(int colorMode) {
+        mBinderService.setColorMode(colorMode);
+    }
+
+    /**
+     * Returns whether the color mode is valid on the device the tests are running on.
+     */
+    private boolean isColorModeValid(int mode) {
+        final int[] availableColorModes = mContext.getResources().getIntArray(
+                R.array.config_availableColorModes);
+        if (availableColorModes != null) {
+            for (int availableMode : availableColorModes) {
+                if (mode == availableMode) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Convenience method to start {@link #mCds}.
+     */
+    private void startService() {
+        Secure.putIntForUser(mContext.getContentResolver(), Secure.USER_SETUP_COMPLETE, 1, mUserId);
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+            mCds.onUserChanged(mUserId);
+        });
+    }
+
+    /**
+     * Convenience method for asserting whether Night display should be activated.
+     *
+     * @param activated the expected activated state of Night display
+     */
+    private void assertActivated(boolean activated) {
+        assertWithMessage("Incorrect Night display activated state")
+                .that(mBinderService.isNightDisplayActivated())
+                .isEqualTo(activated);
+    }
+
+    /**
+     * Convenience method for asserting that the active color mode matches expectation.
+     *
+     * @param mode the expected active color mode.
+     */
+    private void assertActiveColorMode(int mode) {
+        assertWithMessage("Unexpected color mode setting")
+                .that(mBinderService.getColorMode())
+                .isEqualTo(mode);
+    }
+
+    /**
+     * Convenience method for asserting that the user chosen color mode matches expectation.
+     *
+     * @param mode the expected color mode setting.
+     */
+    private void assertUserColorMode(int mode) {
+        final int actualMode = System.getIntForUser(mContext.getContentResolver(),
+                System.DISPLAY_COLOR_MODE, -1, mUserId);
+        assertWithMessage("Unexpected color mode setting")
+                .that(actualMode)
+                .isEqualTo(mode);
+    }
+
+    /**
+     * Convenience method for asserting that the DWB active status matches expectation.
+     *
+     * @param enabled the expected active status.
+     */
+    private void assertDwbActive(boolean enabled) {
+        assertWithMessage("Incorrect Display White Balance state")
+                .that(mCds.mDisplayWhiteBalanceTintController.isActivated())
+                .isEqualTo(enabled);
+    }
+
+    /**
+     * Convenience for making a {@link LocalTime} instance with an offset relative to now.
+     *
+     * @param offsetMinutes the offset relative to now (in minutes)
+     * @return the LocalTime instance
+     */
+    private static LocalTime getLocalTimeRelativeToNow(int offsetMinutes) {
+        final Calendar c = Calendar.getInstance();
+        c.add(Calendar.MINUTE, offsetMinutes);
+        return LocalTime.of(c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE));
+    }
+
+    /**
+     * Convenience for making a {@link TwilightState} instance with sunrise/sunset relative to now.
+     *
+     * @param sunsetOffset the offset relative to now for sunset (in minutes)
+     * @param sunriseOffset the offset relative to now for sunrise (in minutes)
+     * @return the TwilightState instance
+     */
+    private static TwilightState getTwilightStateRelativeToNow(int sunsetOffset,
+            int sunriseOffset) {
+        final LocalTime sunset = getLocalTimeRelativeToNow(sunsetOffset);
+        final LocalTime sunrise = getLocalTimeRelativeToNow(sunriseOffset);
+
+        final LocalDateTime now = LocalDateTime.now();
+        final ZoneId zoneId = ZoneId.systemDefault();
+
+        long sunsetMillis = ColorDisplayService.getDateTimeBefore(sunset, now)
+                .atZone(zoneId)
+                .toInstant()
+                .toEpochMilli();
+        long sunriseMillis = ColorDisplayService.getDateTimeBefore(sunrise, now)
+                .atZone(zoneId)
+                .toInstant()
+                .toEpochMilli();
+        if (sunsetMillis < sunriseMillis) {
+            sunsetMillis = ColorDisplayService.getDateTimeAfter(sunset, now)
+                    .atZone(zoneId)
+                    .toInstant()
+                    .toEpochMilli();
+        } else {
+            sunriseMillis = ColorDisplayService.getDateTimeAfter(sunrise, now)
+                    .atZone(zoneId)
+                    .toInstant()
+                    .toEpochMilli();
+        }
+
+        return new TwilightState(sunriseMillis, sunsetMillis);
+    }
+
+    private static class MockTwilightManager implements TwilightManager {
+
+        private final Map<TwilightListener, Handler> mListeners = new HashMap<>();
+        private TwilightState mTwilightState;
+
+        /**
+         * Updates the TwilightState and notifies any registered listeners.
+         *
+         * @param state the new TwilightState to use
+         */
+        void setTwilightState(TwilightState state) {
+            synchronized (mListeners) {
+                mTwilightState = state;
+
+                final CountDownLatch latch = new CountDownLatch(mListeners.size());
+                for (Map.Entry<TwilightListener, Handler> entry : mListeners.entrySet()) {
+                    entry.getValue().post(new Runnable() {
+                        @Override
+                        public void run() {
+                            entry.getKey().onTwilightStateChanged(state);
+                            latch.countDown();
+                        }
+                    });
+                }
+
+                try {
+                    latch.await(5, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+
+        @Override
+        public void registerListener(@NonNull TwilightListener listener, @NonNull Handler handler) {
+            synchronized (mListeners) {
+                mListeners.put(listener, handler);
+            }
+        }
+
+        @Override
+        public void unregisterListener(@NonNull TwilightListener listener) {
+            synchronized (mListeners) {
+                mListeners.remove(listener);
+            }
+        }
+
+        @Override
+        public TwilightState getLastTwilightState() {
+            return mTwilightState;
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayTransformManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/color/DisplayTransformManagerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
new file mode 100644
index 0000000..c280349
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.color;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.R;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.Arrays;
+
+public class DisplayWhiteBalanceTintControllerTest {
+    @Mock
+    private Context mMockedContext;
+    @Mock
+    private Resources mMockedResources;
+    @Mock
+    private DisplayManagerInternal mDisplayManagerInternal;
+
+    private MockitoSession mSession;
+    private Resources mResources;
+    IBinder mDisplayToken;
+    DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
+
+    @Before
+    public void setUp() {
+        DisplayManagerInternal displayManagerInternal = mock(DisplayManagerInternal.class);
+        mDisplayWhiteBalanceTintController =
+                new DisplayWhiteBalanceTintController(displayManagerInternal);
+        mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true);
+        mDisplayWhiteBalanceTintController.setActivated(true);
+
+        mSession = ExtendedMockito.mockitoSession()
+                .initMocks(this)
+                .mockStatic(SurfaceControl.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+
+        mResources = InstrumentationRegistry.getContext().getResources();
+        // These Resources are common to all tests.
+        doReturn(4000)
+                .when(mMockedResources)
+                .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin);
+        doReturn(8000)
+                .when(mMockedResources)
+                .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax);
+        doReturn(6500)
+                .when(mMockedResources)
+                .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
+        doReturn(new String[] {"0.950456", "1.000000", "1.089058"})
+                .when(mMockedResources)
+                .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
+        doReturn(6500)
+                .when(mMockedResources)
+                .getInteger(R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
+        doReturn(new int[] {0})
+                .when(mMockedResources)
+                .getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
+        doReturn(new int[] {20})
+                .when(mMockedResources)
+                .getIntArray(R.array.config_displayWhiteBalanceDisplayRangeMinimums);
+
+        doReturn(mMockedResources).when(mMockedContext).getResources();
+
+        mDisplayToken = new Binder();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mSession != null) {
+            mSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void displayWhiteBalance_setTemperatureOverMax() {
+        final int max = mDisplayWhiteBalanceTintController.mTemperatureMax;
+        mDisplayWhiteBalanceTintController.setMatrix(max + 1);
+        assertWithMessage("Unexpected temperature set")
+                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+                .isEqualTo(max);
+    }
+
+    @Test
+    public void displayWhiteBalance_setTemperatureBelowMin() {
+        final int min = mDisplayWhiteBalanceTintController.mTemperatureMin;
+        mDisplayWhiteBalanceTintController.setMatrix(min - 1);
+        assertWithMessage("Unexpected temperature set")
+                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+                .isEqualTo(min);
+    }
+
+    @Test
+    public void displayWhiteBalance_setValidTemperature() {
+        final int colorTemperature = (mDisplayWhiteBalanceTintController.mTemperatureMin
+                + mDisplayWhiteBalanceTintController.mTemperatureMax) / 2;
+        mDisplayWhiteBalanceTintController.setMatrix(colorTemperature);
+
+        assertWithMessage("Unexpected temperature set")
+                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+                .isEqualTo(colorTemperature);
+    }
+
+    @Test
+    public void displayWhiteBalance_setMatrixValidDwbCalculation() {
+        float[] currentMatrix = mDisplayWhiteBalanceTintController.getMatrix();
+        float[] oldMatrix = Arrays.copyOf(currentMatrix, currentMatrix.length);
+
+        mDisplayWhiteBalanceTintController
+                .setMatrix(mDisplayWhiteBalanceTintController.mCurrentColorTemperature + 1);
+        assertWithMessage("DWB matrix did not change when setting a new temperature")
+                .that(Arrays.equals(oldMatrix, currentMatrix))
+                .isFalse();
+    }
+
+    @Test
+    public void displayWhiteBalance_setMatrixInvalidDwbCalculation() {
+        Arrays.fill(mDisplayWhiteBalanceTintController.mDisplayNominalWhiteXYZ, 0);
+        mDisplayWhiteBalanceTintController
+            .setMatrix(mDisplayWhiteBalanceTintController.mCurrentColorTemperature + 1);
+        assertWithMessage("DWB matrix not set to identity after an invalid DWB calculation")
+                .that(Arrays.equals(mDisplayWhiteBalanceTintController.getMatrix(),
+                    new float[] {
+                        1, 0, 0, 0,
+                        0, 1, 0, 0,
+                        0, 0, 1, 0,
+                        0, 0, 0, 1
+                    })
+                ).isTrue();
+    }
+
+
+    /**
+     * Setup should succeed when SurfaceControl setup results in a valid color transform.
+     */
+    @Test
+    public void displayWhiteBalance_setupWithSurfaceControl() {
+        // Make SurfaceControl return sRGB primaries
+        SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+        displayPrimaries.red = new SurfaceControl.CieXyz();
+        displayPrimaries.red.X = 0.412315f;
+        displayPrimaries.red.Y = 0.212600f;
+        displayPrimaries.red.Z = 0.019327f;
+        displayPrimaries.green = new SurfaceControl.CieXyz();
+        displayPrimaries.green.X = 0.357600f;
+        displayPrimaries.green.Y = 0.715200f;
+        displayPrimaries.green.Z = 0.119200f;
+        displayPrimaries.blue = new SurfaceControl.CieXyz();
+        displayPrimaries.blue.X = 0.180500f;
+        displayPrimaries.blue.Y = 0.072200f;
+        displayPrimaries.blue.Z = 0.950633f;
+        displayPrimaries.white = new SurfaceControl.CieXyz();
+        displayPrimaries.white.X = 0.950456f;
+        displayPrimaries.white.Y = 1.000000f;
+        displayPrimaries.white.Z = 1.089058f;
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
+
+        setUpTintController();
+        assertWithMessage("Setup with valid SurfaceControl failed")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isTrue();
+    }
+
+    /**
+     * Setup should fail when SurfaceControl setup results in an invalid color transform.
+     */
+    @Test
+    public void displayWhiteBalance_setupWithInvalidSurfaceControlData() {
+        // Make SurfaceControl return invalid display primaries
+        SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+        displayPrimaries.red = new SurfaceControl.CieXyz();
+        displayPrimaries.green = new SurfaceControl.CieXyz();
+        displayPrimaries.blue = new SurfaceControl.CieXyz();
+        displayPrimaries.white = new SurfaceControl.CieXyz();
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
+
+        setUpTintController();
+        assertWithMessage("Setup with invalid SurfaceControl succeeded")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isFalse();
+    }
+
+    /**
+     * Setup should succeed when SurfaceControl setup fails and Resources result in a valid color
+     * transform.
+     */
+    @Test
+    public void displayWhiteBalance_setupWithResources() {
+        // Use default (valid) Resources
+        doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries))
+                .when(mMockedResources)
+                .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
+        // Make SurfaceControl setup fail
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
+
+        setUpTintController();
+        assertWithMessage("Setup with valid Resources failed")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isTrue();
+    }
+
+    /**
+     * Setup should fail when SurfaceControl setup fails and Resources result in an invalid color
+     * transform.
+     */
+    @Test
+    public void displayWhiteBalance_setupWithInvalidResources() {
+        // Use Resources with invalid color data
+        doReturn(new String[] {
+                "0", "0", "0", // Red X, Y, Z
+                "0", "0", "0", // Green X, Y, Z
+                "0", "0", "0", // Blue X, Y, Z
+                "0", "0", "0", // White X, Y, Z
+        })
+                .when(mMockedResources)
+                .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
+        // Make SurfaceControl setup fail
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
+
+        setUpTintController();
+        assertWithMessage("Setup with invalid Resources succeeded")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isFalse();
+    }
+
+    /**
+     * Matrix should match the precalculated one for given cct and display primaries.
+     */
+    @Test
+    public void displayWhiteBalance_getAndSetMatrix_validateTransformMatrix() {
+        SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+        displayPrimaries.red = new SurfaceControl.CieXyz();
+        displayPrimaries.red.X = 0.412315f;
+        displayPrimaries.red.Y = 0.212600f;
+        displayPrimaries.red.Z = 0.019327f;
+        displayPrimaries.green = new SurfaceControl.CieXyz();
+        displayPrimaries.green.X = 0.357600f;
+        displayPrimaries.green.Y = 0.715200f;
+        displayPrimaries.green.Z = 0.119200f;
+        displayPrimaries.blue = new SurfaceControl.CieXyz();
+        displayPrimaries.blue.X = 0.180500f;
+        displayPrimaries.blue.Y = 0.072200f;
+        displayPrimaries.blue.Z = 0.950633f;
+        displayPrimaries.white = new SurfaceControl.CieXyz();
+        displayPrimaries.white.X = 0.950456f;
+        displayPrimaries.white.Y = 1.000000f;
+        displayPrimaries.white.Z = 1.089058f;
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
+
+        setUpTintController();
+        assertWithMessage("Setup with valid SurfaceControl failed")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isTrue();
+
+        final int cct = 6500;
+        mDisplayWhiteBalanceTintController.setMatrix(cct);
+        mDisplayWhiteBalanceTintController.setAppliedCct(
+                mDisplayWhiteBalanceTintController.getTargetCct());
+
+        assertWithMessage("Failed to set temperature")
+                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+                .isEqualTo(cct);
+        float[] matrixDwb = mDisplayWhiteBalanceTintController.getMatrix();
+        final float[] expectedMatrixDwb = {
+                0.971848f,   -0.001421f,  0.000491f, 0.0f,
+                0.028193f,    0.945798f,  0.003207f, 0.0f,
+                -0.000042f,  -0.000989f,  0.988659f, 0.0f,
+                0.0f,         0.0f,       0.0f,      1.0f
+        };
+        assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+                1e-6f /* tolerance */);
+    }
+
+    /**
+     * Matrix should match the precalculated one for given cct and display primaries.
+     */
+    @Test
+    public void displayWhiteBalance_targetApplied_validateTransformMatrix() {
+        SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+        displayPrimaries.red = new SurfaceControl.CieXyz();
+        displayPrimaries.red.X = 0.412315f;
+        displayPrimaries.red.Y = 0.212600f;
+        displayPrimaries.red.Z = 0.019327f;
+        displayPrimaries.green = new SurfaceControl.CieXyz();
+        displayPrimaries.green.X = 0.357600f;
+        displayPrimaries.green.Y = 0.715200f;
+        displayPrimaries.green.Z = 0.119200f;
+        displayPrimaries.blue = new SurfaceControl.CieXyz();
+        displayPrimaries.blue.X = 0.180500f;
+        displayPrimaries.blue.Y = 0.072200f;
+        displayPrimaries.blue.Z = 0.950633f;
+        displayPrimaries.white = new SurfaceControl.CieXyz();
+        displayPrimaries.white.X = 0.950456f;
+        displayPrimaries.white.Y = 1.000000f;
+        displayPrimaries.white.Z = 1.089058f;
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
+
+        setUpTintController();
+        assertWithMessage("Setup with valid SurfaceControl failed")
+                .that(mDisplayWhiteBalanceTintController.mSetUp)
+                .isTrue();
+
+        final int cct = 6500;
+        mDisplayWhiteBalanceTintController.setTargetCct(cct);
+        final float[] matrixDwb = mDisplayWhiteBalanceTintController.computeMatrixForCct(cct);
+        mDisplayWhiteBalanceTintController.setAppliedCct(cct);
+
+        assertWithMessage("Failed to set temperature")
+                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+                .isEqualTo(cct);
+        final float[] expectedMatrixDwb = {
+                0.971848f,   -0.001421f,  0.000491f, 0.0f,
+                0.028193f,    0.945798f,  0.003207f, 0.0f,
+                -0.000042f,  -0.000989f,  0.988659f, 0.0f,
+                0.0f,         0.0f,       0.0f,      1.0f
+        };
+        assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+                1e-6f /* tolerance */);
+    }
+
+    private void setUpTintController() {
+        mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
+                mDisplayManagerInternal);
+        mDisplayWhiteBalanceTintController.setUp(mMockedContext, true);
+        mDisplayWhiteBalanceTintController.setActivated(true);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
new file mode 100644
index 0000000..15f13cd
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -0,0 +1,2921 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode;
+
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
+
+import static com.android.server.display.mode.Vote.INVALID_SIZE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+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;
+import static org.mockito.Mockito.when;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
+import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.Temperature;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.SurfaceControl.RefreshRateRange;
+import android.view.SurfaceControl.RefreshRateRanges;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.LocalServices;
+import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.TestUtils;
+import com.android.server.display.mode.DisplayModeDirector.BrightnessObserver;
+import com.android.server.display.mode.DisplayModeDirector.DesiredDisplayModeSpecs;
+import com.android.server.sensors.SensorManagerInternal;
+import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
+import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.testutils.FakeDeviceConfigInterface;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class DisplayModeDirectorTest {
+    // The tolerance within which we consider something approximately equals.
+    private static final String TAG = "DisplayModeDirectorTest";
+    private static final boolean DEBUG = false;
+    private static final float FLOAT_TOLERANCE = 0.01f;
+    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
+    private static final int MODE_ID = 1;
+    private static final float TRANSITION_POINT = 0.763f;
+
+    private static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
+
+    private Context mContext;
+    private FakesInjector mInjector;
+    private Handler mHandler;
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+    @Mock
+    public StatusBarManagerInternal mStatusBarMock;
+    @Mock
+    public SensorManagerInternal mSensorManagerInternalMock;
+    @Mock
+    public DisplayManagerInternal mDisplayManagerInternalMock;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+        when(mContext.getContentResolver()).thenReturn(resolver);
+        mInjector = spy(new FakesInjector());
+        mHandler = new Handler(Looper.getMainLooper());
+
+        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+        LocalServices.addService(StatusBarManagerInternal.class, mStatusBarMock);
+        LocalServices.removeServiceForTest(SensorManagerInternal.class);
+        LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+    }
+
+    private DisplayModeDirector createDirectorFromRefreshRateArray(
+            float[] refreshRates, int baseModeId) {
+        return createDirectorFromRefreshRateArray(refreshRates, baseModeId, refreshRates[0]);
+    }
+
+    private DisplayModeDirector createDirectorFromRefreshRateArray(
+            float[] refreshRates, int baseModeId, float defaultRefreshRate) {
+        Display.Mode[] modes = new Display.Mode[refreshRates.length];
+        Display.Mode defaultMode = null;
+        for (int i = 0; i < refreshRates.length; i++) {
+            modes[i] = new Display.Mode(
+                    /*modeId=*/baseModeId + i, /*width=*/1000, /*height=*/1000, refreshRates[i]);
+            if (refreshRates[i] == defaultRefreshRate) {
+                defaultMode = modes[i];
+            }
+        }
+        assertThat(defaultMode).isNotNull();
+        return createDirectorFromModeArray(modes, defaultMode);
+    }
+
+    private DisplayModeDirector createDirectorFromModeArray(Display.Mode[] modes,
+            Display.Mode defaultMode) {
+        DisplayModeDirector director =
+                new DisplayModeDirector(mContext, mHandler, mInjector);
+        director.setLoggingEnabled(true);
+        SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+        supportedModesByDisplay.put(DISPLAY_ID, modes);
+        director.injectSupportedModesByDisplay(supportedModesByDisplay);
+        SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<>();
+        defaultModesByDisplay.put(DISPLAY_ID, defaultMode);
+        director.injectDefaultModeByDisplay(defaultModesByDisplay);
+        return director;
+    }
+
+    private DisplayModeDirector createDirectorFromFpsRange(int minFps, int maxFps) {
+        int numRefreshRates = maxFps - minFps + 1;
+        float[] refreshRates = new float[numRefreshRates];
+        for (int i = 0; i < numRefreshRates; i++) {
+            refreshRates[i] = minFps + i;
+        }
+        return createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/minFps,
+                /*defaultRefreshRate=*/minFps);
+    }
+
+    @Test
+    public void testDisplayModeVoting() {
+        // With no votes present, DisplayModeDirector should allow any refresh rate.
+        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
+        DesiredDisplayModeSpecs modeSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(modeSpecs.baseModeId).isEqualTo(60);
+        assertThat(modeSpecs.primary.physical.min).isEqualTo(0f);
+        assertThat(modeSpecs.primary.physical.max).isEqualTo(Float.POSITIVE_INFINITY);
+        assertThat(modeSpecs.primary.render.min).isEqualTo(0f);
+        assertThat(modeSpecs.primary.render.max).isEqualTo(Float.POSITIVE_INFINITY);
+        assertThat(modeSpecs.appRequest.physical.min).isEqualTo(0f);
+        assertThat(modeSpecs.appRequest.physical.max).isEqualTo(Float.POSITIVE_INFINITY);
+        assertThat(modeSpecs.appRequest.render.min).isEqualTo(0f);
+        assertThat(modeSpecs.appRequest.render.max).isEqualTo(Float.POSITIVE_INFINITY);
+
+        int numPriorities =  Vote.MAX_PRIORITY - Vote.MIN_PRIORITY + 1;
+
+        // Ensure vote priority works as expected. As we add new votes with higher priority, they
+        // should take precedence over lower priority votes.
+        {
+            int minFps = 60;
+            int maxFps = 90;
+            director = createDirectorFromFpsRange(60, 90);
+            assertTrue(2 * numPriorities < maxFps - minFps + 1);
+            SparseArray<Vote> votes = new SparseArray<>();
+            SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+            votesByDisplay.put(DISPLAY_ID, votes);
+            for (int i = 0; i < numPriorities; i++) {
+                int priority = Vote.MIN_PRIORITY + i;
+                votes.put(priority, Vote.forPhysicalRefreshRates(minFps + i, maxFps - i));
+                director.injectVotesByDisplay(votesByDisplay);
+                modeSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+                assertThat(modeSpecs.baseModeId).isEqualTo(minFps + i);
+                assertThat(modeSpecs.primary.physical.min)
+                        .isEqualTo((float) (minFps + i));
+                assertThat(modeSpecs.primary.physical.max)
+                        .isEqualTo((float) (maxFps - i));
+                assertThat(modeSpecs.primary.render.min).isZero();
+                assertThat(modeSpecs.primary.render.max)
+                        .isEqualTo((float) (maxFps - i));
+                if (priority >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF) {
+                    assertThat(modeSpecs.appRequest.physical.min)
+                            .isEqualTo((float) (minFps + i));
+                    assertThat(modeSpecs.appRequest.physical.max)
+                            .isEqualTo((float) (maxFps - i));
+                    assertThat(modeSpecs.appRequest.render.min).isZero();
+                    assertThat(modeSpecs.appRequest.render.max).isEqualTo(
+                            (float) (maxFps - i));
+                } else {
+                    assertThat(modeSpecs.appRequest.physical.min).isZero();
+                    assertThat(modeSpecs.appRequest.physical.max).isPositiveInfinity();
+                    assertThat(modeSpecs.appRequest.render.min).isZero();
+                    assertThat(modeSpecs.appRequest.render.max).isPositiveInfinity();
+                }
+
+            }
+        }
+
+        // Ensure lower priority votes are able to influence the final decision, even in the
+        // presence of higher priority votes.
+        {
+            assertTrue(numPriorities >= 2);
+            director = createDirectorFromFpsRange(60, 90);
+            SparseArray<Vote> votes = new SparseArray<>();
+            SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+            votesByDisplay.put(DISPLAY_ID, votes);
+            votes.put(Vote.MAX_PRIORITY, Vote.forPhysicalRefreshRates(65, 85));
+            votes.put(Vote.MIN_PRIORITY, Vote.forPhysicalRefreshRates(70, 80));
+            director.injectVotesByDisplay(votesByDisplay);
+            modeSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+            assertThat(modeSpecs.baseModeId).isEqualTo(70);
+            assertThat(modeSpecs.primary.physical.min).isEqualTo(70f);
+            assertThat(modeSpecs.primary.physical.max).isEqualTo(80f);
+        }
+    }
+
+    @Test
+    public void testVotingWithFloatingPointErrors() {
+        DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        float error = FLOAT_TOLERANCE / 4;
+        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(0, 60));
+        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE,
+                Vote.forPhysicalRefreshRates(60 + error, 60 + error));
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forPhysicalRefreshRates(60 - error, 60 - error));
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
+
+        votes.clear();
+        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(0, 60 - error));
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(60 + error, Float.POSITIVE_INFINITY));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
+    }
+
+    @Test
+    public void testFlickerHasLowerPriorityThanUserAndRangeIsSingle() {
+        assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE
+                < Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
+        assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE
+                < Vote.PRIORITY_APP_REQUEST_SIZE);
+
+        assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH
+                > Vote.PRIORITY_LOW_POWER_MODE);
+
+        Display.Mode[] modes = new Display.Mode[4];
+        modes[0] = new Display.Mode(
+                /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
+        modes[1] = new Display.Mode(
+                /*modeId=*/2, /*width=*/2000, /*height=*/2000, 60);
+        modes[2] = new Display.Mode(
+                /*modeId=*/3, /*width=*/1000, /*height=*/1000, 90);
+        modes[3] = new Display.Mode(
+                /*modeId=*/4, /*width=*/2000, /*height=*/2000, 90);
+
+        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        Display.Mode appRequestedMode = modes[1];
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
+        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
+                appRequestedMode.getPhysicalHeight()));
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(60, 60));
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(2);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.physical.max)
+                .isWithin(FLOAT_TOLERANCE).of(desiredSpecs.primary.physical.min);
+
+        votes.clear();
+        appRequestedMode = modes[3];
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
+        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
+                appRequestedMode.getPhysicalHeight()));
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(90, 90));
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(4);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.physical.max)
+                .isWithin(FLOAT_TOLERANCE).of(desiredSpecs.primary.physical.min);
+
+        votes.clear();
+        appRequestedMode = modes[3];
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
+        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
+                appRequestedMode.getPhysicalHeight()));
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(60, 60));
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(4);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.physical.max)
+                .isWithin(FLOAT_TOLERANCE).of(desiredSpecs.primary.physical.min);
+
+        votes.clear();
+        appRequestedMode = modes[1];
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
+        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
+                appRequestedMode.getPhysicalHeight()));
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(90, 90));
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(2);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.physical.max)
+                .isWithin(FLOAT_TOLERANCE).of(desiredSpecs.primary.physical.min);
+    }
+
+    @Test
+    public void testLPMHasHigherPriorityThanUser() {
+        assertTrue(Vote.PRIORITY_LOW_POWER_MODE
+                > Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
+        assertTrue(Vote.PRIORITY_LOW_POWER_MODE
+                > Vote.PRIORITY_APP_REQUEST_SIZE);
+
+        Display.Mode[] modes = new Display.Mode[4];
+        modes[0] = new Display.Mode(
+                /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
+        modes[1] = new Display.Mode(
+                /*modeId=*/2, /*width=*/2000, /*height=*/2000, 60);
+        modes[2] = new Display.Mode(
+                /*modeId=*/3, /*width=*/1000, /*height=*/1000, 90);
+        modes[3] = new Display.Mode(
+                /*modeId=*/4, /*width=*/2000, /*height=*/2000, 90);
+
+        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        Display.Mode appRequestedMode = modes[1];
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
+        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
+                appRequestedMode.getPhysicalHeight()));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(60, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(2);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+
+        votes.clear();
+        appRequestedMode = modes[3];
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
+        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
+                appRequestedMode.getPhysicalHeight()));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(4);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(90);
+
+        votes.clear();
+        appRequestedMode = modes[3];
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
+        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
+                appRequestedMode.getPhysicalHeight()));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(60, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(2);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+
+        votes.clear();
+        appRequestedMode = modes[1];
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
+        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
+                appRequestedMode.getPhysicalHeight()));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(4);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(90);
+    }
+
+    @Test
+    public void testAppRequestRefreshRateRange() {
+        // Confirm that the app request range doesn't include flicker or min refresh rate settings,
+        // but does include everything else.
+        assertTrue(
+                Vote.PRIORITY_FLICKER_REFRESH_RATE
+                        < Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
+        assertTrue(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE
+                < Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
+        assertTrue(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE
+                >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
+
+        Display.Mode[] modes = new Display.Mode[3];
+        modes[0] = new Display.Mode(
+                /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60);
+        modes[1] = new Display.Mode(
+                /*modeId=*/75, /*width=*/2000, /*height=*/2000, 75);
+        modes[2] = new Display.Mode(
+                /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90);
+
+        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(60, 60));
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
+        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
+
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(90, Float.POSITIVE_INFINITY));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.physical.max).isAtLeast(90f);
+        assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
+        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
+
+        Display.Mode appRequestedMode = modes[1];
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
+        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
+                appRequestedMode.getPhysicalHeight()));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(75);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(75);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(75);
+        assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
+        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
+    }
+
+    void verifySpecsWithRefreshRateSettings(DisplayModeDirector director, float minFps,
+            float peakFps, float defaultFps, RefreshRateRanges primary,
+            RefreshRateRanges appRequest) {
+        DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(
+                minFps, peakFps, defaultFps);
+        assertThat(specs.primary).isEqualTo(primary);
+        assertThat(specs.appRequest).isEqualTo(appRequest);
+    }
+
+    @Test
+    public void testSpecsFromRefreshRateSettings() {
+        // Confirm that, with varying settings for min, peak, and default refresh rate,
+        // DesiredDisplayModeSpecs is calculated correctly.
+        float[] refreshRates = {30.f, 60.f, 90.f, 120.f, 150.f};
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/0);
+
+        float inf = Float.POSITIVE_INFINITY;
+        RefreshRateRange rangeAll = new RefreshRateRange(0, inf);
+        RefreshRateRange range0to60 = new RefreshRateRange(0, 60);
+        RefreshRateRange range0to90 = new RefreshRateRange(0, 90);
+        RefreshRateRange range0to120 = new RefreshRateRange(0, 120);
+        RefreshRateRange range60to90 = new RefreshRateRange(60, 90);
+        RefreshRateRange range90to90 = new RefreshRateRange(90, 90);
+        RefreshRateRange range90to120 = new RefreshRateRange(90, 120);
+        RefreshRateRange range60toInf = new RefreshRateRange(60, inf);
+        RefreshRateRange range90toInf = new RefreshRateRange(90, inf);
+
+        RefreshRateRanges frameRateAll = new RefreshRateRanges(rangeAll, rangeAll);
+        RefreshRateRanges frameRate90toInf = new RefreshRateRanges(range90toInf, range90toInf);
+        RefreshRateRanges frameRate0to60 = new RefreshRateRanges(rangeAll, range0to60);
+        RefreshRateRanges frameRate0to90 = new RefreshRateRanges(rangeAll, range0to90);
+        RefreshRateRanges frameRate0to120 = new RefreshRateRanges(rangeAll, range0to120);
+        RefreshRateRanges frameRate60to90 = new RefreshRateRanges(range60toInf, range60to90);
+        RefreshRateRanges frameRate90to90 = new RefreshRateRanges(range90toInf, range90to90);
+        RefreshRateRanges frameRate90to120 = new RefreshRateRanges(range90toInf, range90to120);
+
+        verifySpecsWithRefreshRateSettings(director, 0, 0, 0, frameRateAll, frameRateAll);
+        verifySpecsWithRefreshRateSettings(director, 0, 0, 90, frameRate0to90, frameRateAll);
+        verifySpecsWithRefreshRateSettings(director, 0, 90, 0, frameRate0to90, frameRate0to90);
+        verifySpecsWithRefreshRateSettings(director, 0, 90, 60, frameRate0to60, frameRate0to90);
+        verifySpecsWithRefreshRateSettings(director, 0, 90, 120, frameRate0to90,
+                frameRate0to90);
+        verifySpecsWithRefreshRateSettings(director, 90, 0, 0, frameRate90toInf, frameRateAll);
+        verifySpecsWithRefreshRateSettings(director, 90, 0, 120, frameRate90to120,
+                frameRateAll);
+        verifySpecsWithRefreshRateSettings(director, 90, 0, 60, frameRate90toInf, frameRateAll);
+        verifySpecsWithRefreshRateSettings(director, 90, 120, 0, frameRate90to120,
+                frameRate0to120);
+        verifySpecsWithRefreshRateSettings(director, 90, 60, 0, frameRate90to90,
+                frameRate0to90);
+        verifySpecsWithRefreshRateSettings(director, 60, 120, 90, frameRate60to90,
+                frameRate0to120);
+    }
+
+    void verifyBrightnessObserverCall(DisplayModeDirector director, float minFps, float peakFps,
+            float defaultFps, float brightnessObserverMin, float brightnessObserverMax) {
+        BrightnessObserver brightnessObserver = mock(BrightnessObserver.class);
+        director.injectBrightnessObserver(brightnessObserver);
+        director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(minFps, peakFps, defaultFps);
+        verify(brightnessObserver)
+                .onRefreshRateSettingChangedLocked(brightnessObserverMin, brightnessObserverMax);
+    }
+
+    @Test
+    public void testBrightnessObserverCallWithRefreshRateSettings() {
+        // Confirm that, with varying settings for min, peak, and default refresh rate, we make the
+        // correct call to the brightness observer.
+        float[] refreshRates = {60.f, 90.f, 120.f};
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/0);
+        verifyBrightnessObserverCall(director, 0, 0, 0, 0, 0);
+        verifyBrightnessObserverCall(director, 0, 0, 90, 0, 90);
+        verifyBrightnessObserverCall(director, 0, 90, 0, 0, 90);
+        verifyBrightnessObserverCall(director, 0, 90, 60, 0, 60);
+        verifyBrightnessObserverCall(director, 90, 90, 0, 90, 90);
+        verifyBrightnessObserverCall(director, 120, 90, 0, 120, 90);
+    }
+
+    @Test
+    public void testVotingWithAlwaysRespectAppRequest() {
+        Display.Mode[] modes = new Display.Mode[3];
+        modes[0] = new Display.Mode(
+                /*modeId=*/50, /*width=*/1000, /*height=*/1000, 50);
+        modes[1] = new Display.Mode(
+                /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60);
+        modes[2] = new Display.Mode(
+                /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90);
+
+        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]);
+
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(0, 60));
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(60, 90));
+        Display.Mode appRequestedMode = modes[2];
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
+        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(60, 60));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+
+        assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse();
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
+
+        director.setShouldAlwaysRespectAppRequestedMode(true);
+        assertThat(director.shouldAlwaysRespectAppRequestedMode()).isTrue();
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isAtMost(50);
+        assertThat(desiredSpecs.primary.physical.max).isAtLeast(90);
+        assertThat(desiredSpecs.primary.render.min).isAtMost(50);
+        assertThat(desiredSpecs.primary.render.max).isAtLeast(90);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
+
+        director.setShouldAlwaysRespectAppRequestedMode(false);
+        assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse();
+
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
+    }
+
+    @Test
+    public void testVotingWithSwitchingTypeNone() {
+        DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(30, 90));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+
+        director.injectVotesByDisplay(votesByDisplay);
+        assertThat(director.getModeSwitchingType())
+                .isNotEqualTo(DisplayManager.SWITCHING_TYPE_NONE);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(30);
+
+        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_NONE);
+        assertThat(director.getModeSwitchingType())
+                .isEqualTo(DisplayManager.SWITCHING_TYPE_NONE);
+
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(30);
+    }
+
+    @Test
+    public void testVotingWithSwitchingTypeRenderFrameRateOnly() {
+        DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(30, 90));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+
+        director.injectVotesByDisplay(votesByDisplay);
+        assertThat(director.getModeSwitchingType())
+                .isNotEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(30);
+
+        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+        assertThat(director.getModeSwitchingType())
+                .isEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(30);
+
+        assertThat(desiredSpecs.baseModeId).isEqualTo(30);
+    }
+
+    @Test
+    public void testVotingWithSwitchingTypeRenderFrameRateOnlyRenderRateIsNotPhysicalRefreshRate() {
+        DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(30, 90));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+
+        director.injectVotesByDisplay(votesByDisplay);
+        assertThat(director.getModeSwitchingType())
+                .isNotEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
+
+        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+        assertThat(director.getModeSwitchingType())
+                .isEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+
+        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
+    }
+
+    @Test
+    public void testVotingWithSwitchingTypeWithinGroups() {
+        DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
+
+        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+        assertThat(director.getModeSwitchingType())
+                .isEqualTo(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.allowGroupSwitching).isFalse();
+    }
+
+    @Test
+    public void testVotingWithSwitchingTypeWithinAndAcrossGroups() {
+        DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
+
+        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+        assertThat(director.getModeSwitchingType())
+                .isEqualTo(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.allowGroupSwitching).isTrue();
+    }
+
+    @Test
+    public void testDefaultDisplayModeIsSelectedIfAvailable() {
+        final float[] refreshRates = new float[]{24f, 25f, 30f, 60f, 90f};
+        final int defaultModeId = 3;
+        DisplayModeDirector director = createDirectorFromRefreshRateArray(
+                refreshRates, /*baseModeId=*/0, refreshRates[defaultModeId]);
+
+        DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(specs.baseModeId).isEqualTo(defaultModeId);
+    }
+
+    @Test
+    public void testStaleAppRequestSize() {
+        DisplayModeDirector director =
+                new DisplayModeDirector(mContext, mHandler, mInjector);
+        Display.Mode[] modes = new Display.Mode[] {
+                new Display.Mode(1, 1280, 720, 60),
+        };
+        Display.Mode defaultMode = modes[0];
+
+        // Inject supported modes
+        SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+        supportedModesByDisplay.put(DISPLAY_ID, modes);
+        director.injectSupportedModesByDisplay(supportedModesByDisplay);
+
+        // Inject default mode
+        SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<>();
+        defaultModesByDisplay.put(DISPLAY_ID, defaultMode);
+        director.injectDefaultModeByDisplay(defaultModesByDisplay);
+
+        // Inject votes
+        SparseArray<Vote> votes = new SparseArray<>();
+        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(1920, 1080));
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(60));
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        director.injectVotesByDisplay(votesByDisplay);
+
+        director.setShouldAlwaysRespectAppRequestedMode(true);
+
+        // We should return the only available mode
+        DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(specs.baseModeId).isEqualTo(defaultMode.getModeId());
+    }
+
+    @Test
+    public void testBrightnessObserverGetsUpdatedRefreshRatesForZone() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
+        SensorManager sensorManager = createMockSensorManager(createLightSensor());
+
+        final int initialRefreshRate = 60;
+        mInjector.getDeviceConfig().setRefreshRateInLowZone(initialRefreshRate);
+        director.start(sensorManager);
+        assertThat(director.getBrightnessObserver().getRefreshRateInLowZone())
+                .isEqualTo(initialRefreshRate);
+
+        final int updatedRefreshRate = 90;
+        mInjector.getDeviceConfig().setRefreshRateInLowZone(updatedRefreshRate);
+        // Need to wait for the property change to propagate to the main thread.
+        waitForIdleSync();
+        assertThat(director.getBrightnessObserver().getRefreshRateInLowZone())
+                .isEqualTo(updatedRefreshRate);
+    }
+
+    @Test
+    public void testBrightnessObserverThresholdsInZone() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
+        SensorManager sensorManager = createMockSensorManager(createLightSensor());
+
+        final int[] initialDisplayThresholds = { 10 };
+        final int[] initialAmbientThresholds = { 20 };
+
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setLowDisplayBrightnessThresholds(initialDisplayThresholds);
+        config.setLowAmbientBrightnessThresholds(initialAmbientThresholds);
+        director.start(sensorManager);
+
+        float[] expectedDisplayThresholds = { BrightnessSynchronizer.brightnessIntToFloat(10) };
+        float[] expectedAmbientThresholds = { 20 };
+        assertThat(director.getBrightnessObserver().getLowDisplayBrightnessThresholds())
+                .isEqualTo(expectedDisplayThresholds);
+        assertThat(director.getBrightnessObserver().getLowAmbientBrightnessThresholds())
+                .isEqualTo(expectedAmbientThresholds);
+
+        final int[] updatedDisplayThresholds = { 9, 14 };
+        final int[] updatedAmbientThresholds = { -1, 19 };
+        config.setLowDisplayBrightnessThresholds(updatedDisplayThresholds);
+        config.setLowAmbientBrightnessThresholds(updatedAmbientThresholds);
+        // Need to wait for the property change to propagate to the main thread.
+        waitForIdleSync();
+
+        expectedDisplayThresholds = new float[]{ BrightnessSynchronizer.brightnessIntToFloat(9),
+                BrightnessSynchronizer.brightnessIntToFloat(14) };
+        expectedAmbientThresholds = new float[]{ -1, 19 };
+        assertThat(director.getBrightnessObserver().getLowDisplayBrightnessThresholds())
+                .isEqualTo(expectedDisplayThresholds);
+        assertThat(director.getBrightnessObserver().getLowAmbientBrightnessThresholds())
+                .isEqualTo(expectedAmbientThresholds);
+    }
+
+    @Test
+    public void setBrightness_doesNotLockFpsIfSmoothDisplayIsOff() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(60); // set smooth display ON
+        director.getSettingsObserver().setDefaultRefreshRate(90);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setRefreshRateInLowZone(90);
+        config.setLowDisplayBrightnessThresholds(new int[] { 10 });
+        config.setLowAmbientBrightnessThresholds(new int[] { 20 });
+
+        director.start(createMockSensorManager(createLightSensor()));
+
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class),
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                        | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
+        setBrightness(10, 10, displayListener);
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertThat(vote).isNull();
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNull();
+    }
+
+    @Test
+    public void setBrightness_locksFpsIfSmoothDisplayIsOn() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90); // set smooth display ON
+        director.getSettingsObserver().setDefaultRefreshRate(90);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setRefreshRateInLowZone(90);
+        config.setLowDisplayBrightnessThresholds(new int[] { 10 });
+        config.setLowAmbientBrightnessThresholds(new int[] { 20 });
+
+        director.start(createMockSensorManager(createLightSensor()));
+
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class),
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                        | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
+        setBrightness(10, 10, displayListener);
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/);
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNotNull();
+        assertThat(vote.disableRefreshRateSwitching).isTrue();
+    }
+
+    @Test
+    public void testLockFpsForLowZone() throws Exception {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90);
+        director.getSettingsObserver().setDefaultRefreshRate(90);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setRefreshRateInLowZone(90);
+        config.setLowDisplayBrightnessThresholds(new int[] { 10 });
+        config.setLowAmbientBrightnessThresholds(new int[] { 20 });
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+
+        director.start(sensorManager);
+
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                  ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class),
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
+        ArgumentCaptor<SensorEventListener> sensorListenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+                .registerListener(
+                        sensorListenerCaptor.capture(),
+                        eq(lightSensor),
+                        anyInt(),
+                        any(Handler.class));
+        SensorEventListener sensorListener = sensorListenerCaptor.getValue();
+
+        setBrightness(10, 10, displayListener);
+        // Sensor reads 20 lux,
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 20 /*lux*/));
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/);
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNotNull();
+        assertThat(vote.disableRefreshRateSwitching).isTrue();
+
+        // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
+        // parameter to the necessary threshold
+        setBrightness(10, 125, displayListener);
+        // Sensor reads 1000 lux,
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000 /*lux*/));
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertThat(vote).isNull();
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNull();
+    }
+
+    @Test
+    public void testLockFpsForHighZone() throws Exception {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90 /*fps*/);
+        director.getSettingsObserver().setDefaultRefreshRate(90);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setRefreshRateInHighZone(60);
+        config.setHighDisplayBrightnessThresholds(new int[] { 255 });
+        config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+
+        director.start(sensorManager);
+
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                  ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class),
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensor),
+                        anyInt(),
+                        any(Handler.class));
+        SensorEventListener sensorListener = listenerCaptor.getValue();
+
+        setBrightness(100, 100, displayListener);
+        // Sensor reads 2000 lux,
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertThat(vote).isNull();
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNull();
+
+        // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
+        // parameter to the necessary threshold
+        setBrightness(100, 255, displayListener);
+        // Sensor reads 9000 lux,
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000));
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/);
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNotNull();
+        assertThat(vote.disableRefreshRateSwitching).isTrue();
+    }
+
+    @Test
+    public void testSensorRegistration() {
+        // First, configure brightness zones or DMD won't register for sensor data.
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setRefreshRateInHighZone(60);
+        config.setHighDisplayBrightnessThresholds(new int[] { 255 });
+        config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90 /*fps*/);
+        director.getSettingsObserver().setDefaultRefreshRate(90);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+
+        director.start(sensorManager);
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensor),
+                        anyInt(),
+                        any(Handler.class));
+
+        // Display state changed from On to Doze
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_DOZE);
+        verify(sensorManager)
+                .unregisterListener(listenerCaptor.capture());
+
+        // Display state changed from Doze to On
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+        verify(sensorManager, times(2))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensor),
+                        anyInt(),
+                        any(Handler.class));
+
+    }
+
+    @Test
+    public void testUdfpsListenerGetsRegistered() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0);
+        verify(mStatusBarMock, never()).setUdfpsRefreshRateCallback(any());
+
+        director.onBootCompleted();
+        verify(mStatusBarMock).setUdfpsRefreshRateCallback(eq(director.getUdpfsObserver()));
+    }
+
+    @Test
+    public void testGbhmVotesFor60hz() throws Exception {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0);
+        director.start(createMockSensorManager());
+        director.onBootCompleted();
+        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
+                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
+        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
+
+        // Should be no vote initially
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
+        assertNull(vote);
+    }
+
+    @Test
+    public void testAppRequestMinRefreshRate() {
+        // Confirm that the app min request range doesn't include flicker or min refresh rate
+        // settings but does include everything else.
+        assertTrue(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE
+                >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
+
+        Display.Mode[] modes = new Display.Mode[3];
+        modes[0] = new Display.Mode(
+                /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60);
+        modes[1] = new Display.Mode(
+                /*modeId=*/75, /*width=*/1000, /*height=*/1000, 75);
+        modes[2] = new Display.Mode(
+                /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90);
+
+        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[1]);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(60, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
+        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
+
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(90, Float.POSITIVE_INFINITY));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.physical.max).isAtLeast(90f);
+        assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
+        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
+
+        votes.put(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
+                Vote.forRenderFrameRates(75, Float.POSITIVE_INFINITY));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(75);
+        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
+    }
+
+    @Test
+    public void testAppRequestMaxRefreshRate() {
+        // Confirm that the app max request range doesn't include flicker or min refresh rate
+        // settings but does include everything else.
+        assertTrue(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE
+                >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
+
+        Display.Mode[] modes = new Display.Mode[3];
+        modes[0] = new Display.Mode(
+                /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60);
+        modes[1] = new Display.Mode(
+                /*modeId=*/75, /*width=*/1000, /*height=*/1000, 75);
+        modes[2] = new Display.Mode(
+                /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90);
+
+        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[1]);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(60, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.render.min).isZero();
+        assertThat(desiredSpecs.primary.render.max).isAtMost(60);
+        assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
+        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
+        assertThat(desiredSpecs.appRequest.render.min).isZero();
+        assertThat(desiredSpecs.appRequest.render.max).isAtLeast(90f);
+
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(90, Float.POSITIVE_INFINITY));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.render.max).isAtLeast(90f);
+        assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
+        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
+        assertThat(desiredSpecs.appRequest.render.min).isAtMost(60f);
+        assertThat(desiredSpecs.appRequest.render.max).isAtLeast(90f);
+
+        votes.put(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
+                Vote.forRenderFrameRates(0, 75));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(75);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(75);
+        assertThat(desiredSpecs.primary.render.min).isZero();
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(75);
+        assertThat(desiredSpecs.appRequest.physical.min).isZero();
+        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
+        assertThat(desiredSpecs.appRequest.render.min).isZero();
+        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(75);
+    }
+
+    @Test
+    public void testAppRequestObserver_modeId() {
+        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
+        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 0, 0);
+
+        Vote appRequestRefreshRate =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
+        assertNotNull(appRequestRefreshRate);
+        assertThat(appRequestRefreshRate.refreshRateRanges.physical.min).isZero();
+        assertThat(appRequestRefreshRate.refreshRateRanges.physical.max).isPositiveInfinity();
+        assertThat(appRequestRefreshRate.refreshRateRanges.render.min).isZero();
+        assertThat(appRequestRefreshRate.refreshRateRanges.render.max).isPositiveInfinity();
+        assertThat(appRequestRefreshRate.disableRefreshRateSwitching).isFalse();
+        assertThat(appRequestRefreshRate.appRequestBaseModeRefreshRate)
+                .isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE);
+        assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE);
+
+        Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
+        assertNotNull(appRequestSize);
+        assertThat(appRequestSize.refreshRateRanges.physical.min).isZero();
+        assertThat(appRequestSize.refreshRateRanges.physical.max).isPositiveInfinity();
+        assertThat(appRequestSize.refreshRateRanges.render.min).isZero();
+        assertThat(appRequestSize.refreshRateRanges.render.max).isPositiveInfinity();
+        assertThat(appRequestSize.disableRefreshRateSwitching).isFalse();
+        assertThat(appRequestSize.appRequestBaseModeRefreshRate).isZero();
+        assertThat(appRequestSize.height).isEqualTo(1000);
+        assertThat(appRequestSize.width).isEqualTo(1000);
+
+        Vote appRequestRefreshRateRange =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
+        assertNull(appRequestRefreshRateRange);
+
+        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 90, 0, 0);
+
+        appRequestRefreshRate =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
+        assertNotNull(appRequestRefreshRate);
+        assertThat(appRequestRefreshRate.refreshRateRanges.physical.min).isZero();
+        assertThat(appRequestRefreshRate.refreshRateRanges.physical.max).isPositiveInfinity();
+        assertThat(appRequestRefreshRate.refreshRateRanges.render.min).isZero();
+        assertThat(appRequestRefreshRate.refreshRateRanges.render.max).isPositiveInfinity();
+        assertThat(appRequestRefreshRate.disableRefreshRateSwitching).isFalse();
+        assertThat(appRequestRefreshRate.appRequestBaseModeRefreshRate)
+                .isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE);
+        assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE);
+
+        appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
+        assertNotNull(appRequestSize);
+        assertThat(appRequestSize.refreshRateRanges.physical.min).isZero();
+        assertThat(appRequestSize.refreshRateRanges.physical.max).isPositiveInfinity();
+        assertThat(appRequestSize.refreshRateRanges.render.min).isZero();
+        assertThat(appRequestSize.refreshRateRanges.render.max).isPositiveInfinity();
+        assertThat(appRequestSize.height).isEqualTo(1000);
+        assertThat(appRequestSize.width).isEqualTo(1000);
+
+        appRequestRefreshRateRange =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
+        assertNull(appRequestRefreshRateRange);
+    }
+
+    @Test
+    public void testAppRequestObserver_minRefreshRate() {
+        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
+        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 60, 0);
+        Vote appRequestRefreshRate =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
+        assertNull(appRequestRefreshRate);
+
+        Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
+        assertNull(appRequestSize);
+
+        Vote appRequestRefreshRateRange =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
+        assertNotNull(appRequestRefreshRateRange);
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
+                .isPositiveInfinity();
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min)
+                .isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max).isAtLeast(90);
+        assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE);
+        assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE);
+
+        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 90, 0);
+        appRequestRefreshRate =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
+        assertNull(appRequestRefreshRate);
+
+        appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
+        assertNull(appRequestSize);
+
+        appRequestRefreshRateRange =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
+        assertNotNull(appRequestRefreshRateRange);
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
+                .isPositiveInfinity();
+
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min)
+                .isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max).isAtLeast(90);
+        assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE);
+        assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE);
+    }
+
+    @Test
+    public void testAppRequestObserver_maxRefreshRate() {
+        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
+        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 0, 90);
+        Vote appRequestRefreshRate =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
+        assertNull(appRequestRefreshRate);
+
+        Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
+        assertNull(appRequestSize);
+
+        Vote appRequestRefreshRateRange =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
+        assertNotNull(appRequestRefreshRateRange);
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
+                .isPositiveInfinity();
+
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min).isZero();
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max)
+                .isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE);
+        assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE);
+
+        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 0, 60);
+        appRequestRefreshRate =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
+        assertNull(appRequestRefreshRate);
+
+        appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
+        assertNull(appRequestSize);
+
+        appRequestRefreshRateRange =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
+        assertNotNull(appRequestRefreshRateRange);
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
+                .isPositiveInfinity();
+
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min).isZero();
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max)
+                .isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE);
+        assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE);
+    }
+
+    @Test
+    public void testAppRequestObserver_invalidRefreshRateRange() {
+        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
+        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 90, 60);
+        Vote appRequestRefreshRate =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
+        assertNull(appRequestRefreshRate);
+
+        Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
+        assertNull(appRequestSize);
+
+        Vote appRequestRefreshRateRange =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
+        assertNull(appRequestRefreshRateRange);
+    }
+
+    @Test
+    public void testAppRequestObserver_modeIdAndRefreshRateRange() {
+        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
+        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 90, 90);
+
+        Vote appRequestRefreshRate =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
+        assertNotNull(appRequestRefreshRate);
+        assertThat(appRequestRefreshRate.refreshRateRanges.physical.min).isZero();
+        assertThat(appRequestRefreshRate.refreshRateRanges.physical.max).isPositiveInfinity();
+        assertThat(appRequestRefreshRate.refreshRateRanges.render.min).isZero();
+        assertThat(appRequestRefreshRate.refreshRateRanges.render.max).isPositiveInfinity();
+        assertThat(appRequestRefreshRate.disableRefreshRateSwitching).isFalse();
+        assertThat(appRequestRefreshRate.appRequestBaseModeRefreshRate)
+                .isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE);
+        assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE);
+
+        Vote appRequestSize =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
+        assertNotNull(appRequestSize);
+        assertThat(appRequestSize.refreshRateRanges.physical.min).isZero();
+        assertThat(appRequestSize.refreshRateRanges.physical.max).isPositiveInfinity();
+        assertThat(appRequestSize.refreshRateRanges.render.min).isZero();
+        assertThat(appRequestSize.refreshRateRanges.render.max).isPositiveInfinity();
+        assertThat(appRequestSize.height).isEqualTo(1000);
+        assertThat(appRequestSize.width).isEqualTo(1000);
+
+        Vote appRequestRefreshRateRange =
+                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
+        assertNotNull(appRequestRefreshRateRange);
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
+                .isPositiveInfinity();
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min)
+                .isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max)
+                .isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE);
+        assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE);
+    }
+
+    @Test
+    public void testAppRequestsIsTheDefaultMode() {
+        Display.Mode[] modes = new Display.Mode[2];
+        modes[0] = new Display.Mode(
+                /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
+        modes[1] = new Display.Mode(
+                /*modeId=*/2, /*width=*/1000, /*height=*/1000, 90);
+
+        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(1);
+        assertThat(desiredSpecs.primary.physical.min).isAtMost(60);
+        assertThat(desiredSpecs.primary.physical.max).isAtLeast(90);
+
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        Display.Mode appRequestedMode = modes[1];
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
+        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
+                appRequestedMode.getPhysicalHeight()));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(2);
+        assertThat(desiredSpecs.primary.physical.min).isAtMost(60);
+        assertThat(desiredSpecs.primary.physical.max).isAtLeast(90);
+    }
+
+    @Test
+    public void testDisableRefreshRateSwitchingVote() {
+        DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(90, Float.POSITIVE_INFINITY));
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(50);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(50);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(50);
+
+        votes.clear();
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE,
+                Vote.forPhysicalRefreshRates(70, Float.POSITIVE_INFINITY));
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(80, Float.POSITIVE_INFINITY));
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 90));
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(80);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(80);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(80);
+
+        votes.clear();
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE,
+                Vote.forPhysicalRefreshRates(90, Float.POSITIVE_INFINITY));
+        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(80, Float.POSITIVE_INFINITY));
+        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 90));
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
+    }
+
+    @Test
+    @Parameters({
+            "true",
+            "false"
+    })
+    public void testBaseModeIdInPrimaryRange(boolean supportsFrameRateOverride) {
+        when(mInjector.supportsFrameRateOverride()).thenReturn(supportsFrameRateOverride);
+        DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(70));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        if (supportsFrameRateOverride) {
+            assertThat(desiredSpecs.baseModeId).isEqualTo(70);
+        } else {
+            assertThat(desiredSpecs.baseModeId).isEqualTo(50);
+
+        }
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+
+        votes.clear();
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(55));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(55);
+
+        votes.clear();
+        votes.put(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
+                Vote.forRenderFrameRates(0, 52));
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(55));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        if (supportsFrameRateOverride) {
+            assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(52);
+        } else {
+            assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        }
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(55);
+
+        votes.clear();
+        votes.put(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
+                Vote.forRenderFrameRates(0, 58));
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(55));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(58);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(55);
+    }
+
+    @Test
+    public void testStaleAppVote() {
+        Display.Mode[] modes = new Display.Mode[4];
+        modes[0] = new Display.Mode(
+                /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
+        modes[1] = new Display.Mode(
+                /*modeId=*/2, /*width=*/2000, /*height=*/2000, 60);
+        modes[2] = new Display.Mode(
+                /*modeId=*/3, /*width=*/1000, /*height=*/1000, 90);
+        modes[3] = new Display.Mode(
+                /*modeId=*/4, /*width=*/2000, /*height=*/2000, 90);
+
+        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        Display.Mode appRequestedMode = modes[3];
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
+        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
+                appRequestedMode.getPhysicalHeight()));
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(4);
+
+        // Change mode Id's to simulate that a hotplug has occurred.
+        Display.Mode[] newModes = new Display.Mode[4];
+        newModes[0] = new Display.Mode(
+                /*modeId=*/5, /*width=*/1000, /*height=*/1000, 60);
+        newModes[1] = new Display.Mode(
+                /*modeId=*/6, /*width=*/2000, /*height=*/2000, 60);
+        newModes[2] = new Display.Mode(
+                /*modeId=*/7, /*width=*/1000, /*height=*/1000, 90);
+        newModes[3] = new Display.Mode(
+                /*modeId=*/8, /*width=*/2000, /*height=*/2000, 90);
+
+        SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+        supportedModesByDisplay.put(DISPLAY_ID, newModes);
+        director.injectSupportedModesByDisplay(supportedModesByDisplay);
+        SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<>();
+        defaultModesByDisplay.put(DISPLAY_ID, newModes[0]);
+        director.injectDefaultModeByDisplay(defaultModesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(8);
+    }
+
+    @Test
+    @Parameters({
+            "true",
+            "false"
+    })
+    public void testRefreshRateIsSubsetOfFrameRate(boolean supportsFrameRateOverride) {
+        when(mInjector.supportsFrameRateOverride()).thenReturn(supportsFrameRateOverride);
+        DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+
+
+        votes.put(Vote.PRIORITY_UDFPS, Vote.forPhysicalRefreshRates(90, 120));
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
+        assertThat(desiredSpecs.appRequest.render.min).isZero();
+        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
+
+        votes.clear();
+        votes.put(Vote.PRIORITY_UDFPS, Vote.forPhysicalRefreshRates(90, 120));
+        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(0, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
+        assertThat(desiredSpecs.appRequest.render.min).isZero();
+        if (supportsFrameRateOverride) {
+            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        } else {
+            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
+        }
+
+        votes.clear();
+        votes.put(Vote.PRIORITY_UDFPS, Vote.forPhysicalRefreshRates(90, 120));
+        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(60, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
+        if (supportsFrameRateOverride) {
+            assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(60);
+            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        } else {
+            assertThat(desiredSpecs.appRequest.render.min).isZero();
+            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
+        }
+
+        votes.clear();
+        votes.put(Vote.PRIORITY_UDFPS, Vote.forPhysicalRefreshRates(90, 120));
+        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(140, 140));
+        director.injectVotesByDisplay(votesByDisplay);
+        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
+        assertThat(desiredSpecs.appRequest.render.min).isZero();
+        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
+    }
+
+    @Test
+    public void testRenderFrameRateIsAchievableByPhysicalRefreshRate() {
+        DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+
+
+        votes.put(Vote.PRIORITY_UDFPS, Vote.forPhysicalRefreshRates(120, 120));
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90));
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(120);
+        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
+        assertThat(desiredSpecs.appRequest.render.min).isZero();
+        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
+    }
+
+    @Test
+    @Parameters({
+            "true",
+            "false"
+    })
+    public void testRenderFrameRateIncludesPhysicalRefreshRate(boolean supportsFrameRateOverride) {
+        when(mInjector.supportsFrameRateOverride()).thenReturn(supportsFrameRateOverride);
+        DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+
+        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
+        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
+                Vote.forRenderFrameRates(0, 30));
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.appRequest.physical.min).isZero();
+        assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
+        assertThat(desiredSpecs.appRequest.render.min).isZero();
+        if (supportsFrameRateOverride) {
+            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(30);
+        } else {
+            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
+        }
+    }
+
+    @Test
+    public void testRenderFrameRateIsDroppedIfLowerPriorityThenBaseModeRefreshRate() {
+        DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        votes.put(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
+                Vote.forRenderFrameRates(120, 120));
+        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                Vote.forBaseModeRefreshRate(90));
+        votes.put(Vote.PRIORITY_PROXIMITY, Vote.forPhysicalRefreshRates(60, 120));
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(120);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
+    }
+
+    @Test
+    public void testRenderFrameRateIsAchievableWhenSwitchingTypeNone() {
+        Display.Mode[] modes = new Display.Mode[2];
+        modes[0] = new Display.Mode(
+                /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60);
+        modes[1] = new Display.Mode(
+                /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90);
+
+        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[1]);
+        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_NONE);
+
+        SparseArray<Vote> votes = new SparseArray<>();
+        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+        votesByDisplay.put(DISPLAY_ID, votes);
+        votes.put(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
+                Vote.forRenderFrameRates(0, 60));
+        director.injectVotesByDisplay(votesByDisplay);
+        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
+        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
+    }
+
+    @Test
+    public void testProximitySensorVoting() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.f, 90.f}, 0);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<ProximityActiveListener> ProximityCaptor =
+                ArgumentCaptor.forClass(ProximityActiveListener.class);
+        verify(mSensorManagerInternalMock).addProximityActiveListener(any(Executor.class),
+                ProximityCaptor.capture());
+        ProximityActiveListener proximityListener = ProximityCaptor.getValue();
+
+        ArgumentCaptor<DisplayListener> DisplayCaptor =
+                ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector, times(2)).registerDisplayListener(DisplayCaptor.capture(),
+                any(Handler.class),
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_ADDED
+                        | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+        DisplayListener displayListener = DisplayCaptor.getAllValues().get(0);
+
+        // Verify that there is no proximity vote initially
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_PROXIMITY);
+        assertNull(vote);
+
+        when(mDisplayManagerInternalMock.getRefreshRateForDisplayAndSensor(eq(DISPLAY_ID), eq(null),
+                  eq(Sensor.STRING_TYPE_PROXIMITY))).thenReturn(new RefreshRateRange(60, 60));
+
+        when(mInjector.isDozeState(any(Display.class))).thenReturn(false);
+
+        // Set the proximity to active and verify that we added a vote.
+        proximityListener.onProximityActive(true);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_PROXIMITY);
+        assertVoteForPhysicalRefreshRate(vote, 60.f);
+
+        // Set the display state to doze and verify that the vote is gone
+        when(mInjector.isDozeState(any(Display.class))).thenReturn(true);
+        displayListener.onDisplayAdded(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_PROXIMITY);
+        assertNull(vote);
+
+        // Set the display state to on and verify that we added the vote back.
+        when(mInjector.isDozeState(any(Display.class))).thenReturn(false);
+        displayListener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_PROXIMITY);
+        assertVoteForPhysicalRefreshRate(vote, 60.f);
+
+        // Set the display state to doze and verify that the vote is gone
+        when(mInjector.isDozeState(any(Display.class))).thenReturn(true);
+        displayListener.onDisplayAdded(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_PROXIMITY);
+        assertNull(vote);
+
+        // Remove the display to cause the doze state to be removed
+        displayListener.onDisplayRemoved(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_PROXIMITY);
+        assertVoteForPhysicalRefreshRate(vote, 60.f);
+
+        // Turn prox off and verify vote is gone.
+        proximityListener.onProximityActive(false);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_PROXIMITY);
+        assertNull(vote);
+    }
+
+    @Test
+    public void testHbmVoting_forHdr() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+        final int hbmRefreshRate = 72;
+
+        // Specify limitation before starting DisplayModeDirector to avoid waiting on property
+        // propagation
+        mInjector.getDeviceConfig().setRefreshRateInHbmHdr(hbmRefreshRate);
+
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<DisplayListener> captor =
+                  ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
+                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+        DisplayListener listener = captor.getValue();
+
+        // Specify Limitation
+        when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn(
+                List.of(new RefreshRateLimitation(
+                        DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
+                        60.f, 60.f)));
+
+        // Verify that there is no HBM vote initially
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn on HBM, with brightness in the HBM range
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
+                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
+                    BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertVoteForPhysicalRefreshRate(vote, hbmRefreshRate);
+
+        // Turn on HBM, with brightness below the HBM range
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
+                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
+                    BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn off HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT,
+                    BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn on HBM, with brightness in the HBM range
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
+                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
+                    BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertVoteForPhysicalRefreshRate(vote, hbmRefreshRate);
+
+        // Turn off HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn on HBM, with brightness below the HBM range
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
+                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
+                    BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn off HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+    }
+
+    @Test
+    public void testHbmObserverGetsUpdatedRefreshRateInHbmSunlight() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
+
+        final int initialRefreshRate = 60;
+        mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(initialRefreshRate);
+        director.start(createMockSensorManager());
+        assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight())
+                .isEqualTo(initialRefreshRate);
+
+        final int updatedRefreshRate = 90;
+        mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(updatedRefreshRate);
+        // Need to wait for the property change to propagate to the main thread.
+        waitForIdleSync();
+        assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight())
+                .isEqualTo(updatedRefreshRate);
+    }
+
+    @Test
+    public void testHbmObserverGetsUpdatedRefreshRateInHbmHdr() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
+
+        final int initialRefreshRate = 60;
+        mInjector.getDeviceConfig().setRefreshRateInHbmHdr(initialRefreshRate);
+        director.start(createMockSensorManager());
+        assertThat(director.getHbmObserver().getRefreshRateInHbmHdr())
+                .isEqualTo(initialRefreshRate);
+
+        final int updatedRefreshRate = 90;
+        mInjector.getDeviceConfig().setRefreshRateInHbmHdr(updatedRefreshRate);
+        // Need to wait for the property change to propagate to the main thread.
+        waitForIdleSync();
+        assertThat(director.getHbmObserver().getRefreshRateInHbmHdr())
+                .isEqualTo(updatedRefreshRate);
+    }
+
+    @Test
+    public void testHbmVoting_forSunlight() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<DisplayListener> captor =
+                  ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
+                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+        DisplayListener listener = captor.getValue();
+
+        final int initialRefreshRate = 60;
+        // Specify Limitation
+        when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn(
+                List.of(new RefreshRateLimitation(
+                        DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
+                        initialRefreshRate, initialRefreshRate)));
+
+        // Verify that there is no HBM vote initially
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn on HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertVoteForPhysicalRefreshRate(vote, initialRefreshRate);
+
+        // Change refresh rate vote value through DeviceConfig, ensure it takes precedence
+        final int updatedRefreshRate = 90;
+        mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(updatedRefreshRate);
+        // Need to wait for the property change to propagate to the main thread.
+        waitForIdleSync();
+        assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight())
+                .isEqualTo(updatedRefreshRate);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertVoteForPhysicalRefreshRate(vote, updatedRefreshRate);
+
+        // Turn off HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn HBM on again and ensure the updated vote value stuck
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertVoteForPhysicalRefreshRate(vote, updatedRefreshRate);
+
+        // Reset DeviceConfig refresh rate, ensure vote falls back to the initial value
+        mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(0);
+        // Need to wait for the property change to propagate to the main thread.
+        waitForIdleSync();
+        assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight()).isEqualTo(0);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertVoteForPhysicalRefreshRate(vote, initialRefreshRate);
+
+        // Turn off HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+    }
+
+    @Test
+    public void testHbmVoting_forSunlight_NoLimitation() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<DisplayListener> captor =
+                  ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
+                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+        DisplayListener listener = captor.getValue();
+
+        // Specify Limitation for different display
+        when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID + 1)).thenReturn(
+                List.of(new RefreshRateLimitation(
+                        DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
+                        60.f, 60.f)));
+
+        // Verify that there is no HBM vote initially
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn on HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn off HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+    }
+
+    @Test
+    public void testHbmVoting_HbmUnsupported() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<DisplayListener> captor =
+                  ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
+                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+        DisplayListener listener = captor.getValue();
+
+        // Specify Limitation
+        when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn(
+                List.of(new RefreshRateLimitation(
+                        DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
+                        60.0f, 60.0f)));
+
+        // Verify that there is no HBM vote initially
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn on HBM when HBM is supported; expect a valid transition point and a vote.
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertVoteForPhysicalRefreshRate(vote, 60.0f);
+
+        // Turn off HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn on Sunlight HBM when HBM is unsupported; expect an invalid transition point and
+        // no vote.
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+                    HBM_TRANSITION_POINT_INVALID, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn on HDR HBM when HBM is unsupported; expect an invalid transition point and
+        // no vote.
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
+                    HBM_TRANSITION_POINT_INVALID, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn off HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+    }
+
+    private void setHbmAndAssertRefreshRate(
+            DisplayModeDirector director, DisplayListener listener, int mode, float rr) {
+        when(mInjector.getBrightnessInfo(DISPLAY_ID))
+                .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode, TRANSITION_POINT,
+                    BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+
+        final Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        if (Float.isNaN(rr)) {
+            assertNull(vote);
+        } else {
+            assertVoteForPhysicalRefreshRate(vote, rr);
+        }
+    }
+
+    @Test
+    public void testHbmVoting_forSunlightAndHdr() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+
+        // Specify HDR limitation before starting DisplayModeDirector to avoid waiting on property
+        // propagation
+        final int hdrRr = 60;
+        mInjector.getDeviceConfig().setRefreshRateInHbmHdr(hdrRr);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+        DisplayListener listener = captor.getValue();
+
+        // Specify Sunlight limitations
+        final float sunlightRr = 90.0f;
+        when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID))
+                .thenReturn(List.of(new RefreshRateLimitation(
+                        DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, sunlightRr,
+                        sunlightRr)));
+
+        // Verify that there is no HBM vote initially
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Verify all state transitions
+        setHbmAndAssertRefreshRate(
+                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, sunlightRr);
+        setHbmAndAssertRefreshRate(
+                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, hdrRr);
+        setHbmAndAssertRefreshRate(
+                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, Float.NaN);
+        setHbmAndAssertRefreshRate(
+                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, hdrRr);
+        setHbmAndAssertRefreshRate(
+                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, sunlightRr);
+        setHbmAndAssertRefreshRate(
+                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, Float.NaN);
+    }
+
+    @Test
+    public void testHbmVoting_RemovedDisplay() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<DisplayListener> captor =
+                  ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
+                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+        DisplayListener listener = captor.getValue();
+
+        // Specify Limitation for different display
+        when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn(
+                List.of(new RefreshRateLimitation(
+                        DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
+                        60.f, 60.f)));
+
+        // Verify that there is no HBM vote initially
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+
+        // Turn on HBM
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertVoteForPhysicalRefreshRate(vote, 60.f);
+
+        // Turn off HBM
+        listener.onDisplayRemoved(DISPLAY_ID);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+        assertNull(vote);
+    }
+
+    @Test
+    public void testSkinTemperature() throws RemoteException {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<IThermalEventListener> thermalEventListener =
+                ArgumentCaptor.forClass(IThermalEventListener.class);
+
+        verify(mInjector).registerThermalServiceListener(thermalEventListener.capture());
+        final IThermalEventListener listener = thermalEventListener.getValue();
+
+        // Verify that there is no skin temperature vote initially.
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
+        assertNull(vote);
+
+        // Set the skin temperature to critical and verify that we added a vote.
+        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
+        BackgroundThread.getHandler().runWithScissors(() -> { }, 500 /*timeout*/);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
+        assertVoteForRenderFrameRateRange(vote, 0f, 60.f);
+
+        // Set the skin temperature to severe and verify that the vote is gone.
+        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_SEVERE));
+        BackgroundThread.getHandler().runWithScissors(() -> { }, 500 /*timeout*/);
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
+        assertNull(vote);
+    }
+
+    @Test
+    public void testNotifyDefaultDisplayDeviceUpdated() {
+        Resources resources = mock(Resources.class);
+        when(mContext.getResources()).thenReturn(resources);
+        when(resources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
+            .thenReturn(75);
+        when(resources.getInteger(R.integer.config_defaultRefreshRate))
+            .thenReturn(45);
+        when(resources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+            .thenReturn(65);
+        when(resources.getInteger(R.integer.config_defaultRefreshRateInZone))
+            .thenReturn(85);
+        when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr))
+            .thenReturn(95);
+        when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight))
+            .thenReturn(100);
+        when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+            .thenReturn(new int[]{5});
+        when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+            .thenReturn(new int[]{10});
+        when(
+            resources.getIntArray(R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+            .thenReturn(new int[]{250});
+        when(
+            resources.getIntArray(R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+            .thenReturn(new int[]{7000});
+        when(resources.getInteger(
+            com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
+            .thenReturn(3);
+        ArgumentCaptor<TypedValue> valueArgumentCaptor = ArgumentCaptor.forClass(TypedValue.class);
+        doAnswer((Answer<Void>) invocation -> {
+            valueArgumentCaptor.getValue().type = 4;
+            valueArgumentCaptor.getValue().data = 13;
+            return null;
+        }).when(resources).getValue(eq(com.android.internal.R.dimen
+                .config_displayWhiteBalanceBrightnessFilterIntercept),
+                valueArgumentCaptor.capture(), eq(true));
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        SensorManager sensorManager = createMockSensorManager(createLightSensor());
+        director.start(sensorManager);
+        // We don't expect any interaction with DeviceConfig when the director is initialized
+        // because we explicitly avoid doing this as this can lead to a latency spike in the
+        // startup of DisplayManagerService
+        // Verify all the loaded values are from config.xml
+        assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 45, 0.0);
+        assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 75,
+                0.0);
+        assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 65);
+        assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 85);
+        assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 95);
+        assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 100);
+        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThresholds(),
+                new float[]{ BrightnessSynchronizer.brightnessIntToFloat(250) }, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThresholds(),
+                new float[]{7000}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThresholds(),
+                new float[]{ BrightnessSynchronizer.brightnessIntToFloat(5) }, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThresholds(),
+                new float[]{10}, /* delta= */ 0);
+
+
+        // Notify that the default display is updated, such that DisplayDeviceConfig has new values
+        DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
+        when(displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50);
+        when(displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55);
+        when(displayDeviceConfig.getDefaultRefreshRate()).thenReturn(60);
+        when(displayDeviceConfig.getDefaultPeakRefreshRate()).thenReturn(65);
+        when(displayDeviceConfig.getLowDisplayBrightnessThresholds())
+                .thenReturn(new float[]{0.025f});
+        when(displayDeviceConfig.getLowAmbientBrightnessThresholds())
+                .thenReturn(new float[]{30});
+        when(displayDeviceConfig.getHighDisplayBrightnessThresholds())
+                .thenReturn(new float[]{0.21f});
+        when(displayDeviceConfig.getHighAmbientBrightnessThresholds())
+                .thenReturn(new float[]{2100});
+        when(displayDeviceConfig.getDefaultRefreshRateInHbmHdr()).thenReturn(65);
+        when(displayDeviceConfig.getDefaultRefreshRateInHbmSunlight()).thenReturn(75);
+        director.defaultDisplayDeviceUpdated(displayDeviceConfig);
+
+        // Verify the new values are from the freshly loaded DisplayDeviceConfig.
+        assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
+        assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65,
+                0.0);
+        assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 55);
+        assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 50);
+        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThresholds(),
+                new float[]{0.21f}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThresholds(),
+                new float[]{2100}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThresholds(),
+                new float[]{0.025f}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThresholds(),
+                new float[]{30}, /* delta= */ 0);
+        assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 65);
+        assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 75);
+
+        // Notify that the default display is updated, such that DeviceConfig has new values
+        FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setDefaultPeakRefreshRate(60);
+        config.setRefreshRateInHighZone(65);
+        config.setRefreshRateInLowZone(70);
+        config.setLowAmbientBrightnessThresholds(new int[]{20});
+        config.setLowDisplayBrightnessThresholds(new int[]{10});
+        config.setHighDisplayBrightnessThresholds(new int[]{255});
+        config.setHighAmbientBrightnessThresholds(new int[]{8000});
+        config.setRefreshRateInHbmHdr(70);
+        config.setRefreshRateInHbmSunlight(80);
+        // Need to wait for the property change to propagate to the main thread.
+        waitForIdleSync();
+
+        // Verify the values are loaded from the DeviceConfig.
+        assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
+        assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 60,
+                0.0);
+        assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 65);
+        assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 70);
+        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThresholds(),
+                new float[]{ BrightnessSynchronizer.brightnessIntToFloat(255) }, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThresholds(),
+                new float[]{8000}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThresholds(),
+                new float[]{ BrightnessSynchronizer.brightnessIntToFloat(10) }, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThresholds(),
+                new float[]{20}, /* delta= */ 0);
+        assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 70);
+        assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 80);
+
+        // Reset the DeviceConfig
+        config.setDefaultPeakRefreshRate(null);
+        config.setRefreshRateInHighZone(null);
+        config.setRefreshRateInLowZone(null);
+        config.setLowAmbientBrightnessThresholds(new int[]{});
+        config.setLowDisplayBrightnessThresholds(new int[]{});
+        config.setHighDisplayBrightnessThresholds(new int[]{});
+        config.setHighAmbientBrightnessThresholds(new int[]{});
+        config.setRefreshRateInHbmHdr(null);
+        config.setRefreshRateInHbmSunlight(null);
+        waitForIdleSync();
+
+        // verify the new values now fallback to DisplayDeviceConfig
+        assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
+        assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65,
+                0.0);
+        assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 55);
+        assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 50);
+        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThresholds(),
+                new float[]{0.21f}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThresholds(),
+                new float[]{2100}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThresholds(),
+                new float[]{0.025f}, /* delta= */ 0);
+        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThresholds(),
+                new float[]{30}, /* delta= */ 0);
+        assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 65);
+        assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 75);
+    }
+
+    @Test
+    public void testSensorReloadOnDeviceSwitch() throws Exception {
+        // First, configure brightness zones or DMD won't register for sensor data.
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setRefreshRateInHighZone(60);
+        config.setHighDisplayBrightnessThresholds(new int[] { 255 });
+        config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90 /*fps*/);
+        director.getSettingsObserver().setDefaultRefreshRate(90);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        Sensor lightSensorOne = TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        Sensor lightSensorTwo = TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        SensorManager sensorManager = createMockSensorManager(lightSensorOne, lightSensorTwo);
+        when(sensorManager.getDefaultSensor(5)).thenReturn(lightSensorOne, lightSensorTwo);
+        director.start(sensorManager);
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensorOne),
+                        anyInt(),
+                        any(Handler.class));
+
+        DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class);
+        when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50);
+        when(ddcMock.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55);
+        when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new float[]{0.025f});
+        when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new float[]{30});
+        when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new float[]{0.21f});
+        when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new float[]{2100});
+
+        Resources resMock = mock(Resources.class);
+        when(resMock.getInteger(
+                com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
+                .thenReturn(3);
+        ArgumentCaptor<TypedValue> valueArgumentCaptor = ArgumentCaptor.forClass(TypedValue.class);
+        doAnswer((Answer<Void>) invocation -> {
+            valueArgumentCaptor.getValue().type = 4;
+            valueArgumentCaptor.getValue().data = 13;
+            return null;
+        }).when(resMock).getValue(anyInt(), valueArgumentCaptor.capture(), eq(true));
+        when(mContext.getResources()).thenReturn(resMock);
+
+        director.defaultDisplayDeviceUpdated(ddcMock);
+
+        verify(sensorManager).unregisterListener(any(SensorEventListener.class));
+        verify(sensorManager).registerListener(any(SensorEventListener.class),
+                eq(lightSensorTwo), anyInt(), any(Handler.class));
+    }
+
+    @Test
+    public void testAuthenticationPossibleSetsPhysicalRateRangesToMax() throws RemoteException {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        // don't call director.start(createMockSensorManager());
+        // DisplayObserver will reset mSupportedModesByDisplay
+        director.onBootCompleted();
+        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
+                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
+        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
+
+        captor.getValue().onAuthenticationPossible(DISPLAY_ID, true);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE);
+        assertThat(vote.refreshRateRanges.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(vote.refreshRateRanges.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+    }
+
+    @Test
+    public void testAuthenticationPossibleUnsetsVote() throws RemoteException {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+        director.onBootCompleted();
+        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
+                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
+        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
+        captor.getValue().onAuthenticationPossible(DISPLAY_ID, true);
+        captor.getValue().onAuthenticationPossible(DISPLAY_ID, false);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE);
+        assertNull(vote);
+    }
+
+    @Test
+    public void testUdfpsRequestSetsPhysicalRateRangesToMax() throws RemoteException {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        // don't call director.start(createMockSensorManager());
+        // DisplayObserver will reset mSupportedModesByDisplay
+        director.onBootCompleted();
+        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
+                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
+        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
+
+        captor.getValue().onRequestEnabled(DISPLAY_ID);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
+        assertThat(vote.refreshRateRanges.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(vote.refreshRateRanges.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
+    }
+
+    @Test
+    public void testUdfpsRequestUnsetsUnsetsVote() throws RemoteException {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+        director.onBootCompleted();
+        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
+                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
+        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
+        captor.getValue().onRequestEnabled(DISPLAY_ID);
+        captor.getValue().onRequestDisabled(DISPLAY_ID);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
+        assertNull(vote);
+    }
+
+    @Test
+    public void testUpdateLayoutLimitedRefreshRate_validDisplayInfo() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
+        float refreshRate = 60;
+        mInjector.mDisplayInfo.layoutLimitedRefreshRate =
+                new RefreshRateRange(refreshRate, refreshRate);
+        displayListener.onDisplayChanged(DISPLAY_ID);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
+        assertVoteForPhysicalRefreshRate(vote, /* refreshRate= */ refreshRate);
+
+        mInjector.mDisplayInfo.layoutLimitedRefreshRate = null;
+        displayListener.onDisplayChanged(DISPLAY_ID);
+
+        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
+        assertNull(vote);
+    }
+
+    @Test
+    public void testUpdateLayoutLimitedRefreshRate_invalidDisplayInfo() {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
+        mInjector.mDisplayInfo.layoutLimitedRefreshRate = new RefreshRateRange(10, 10);
+        mInjector.mDisplayInfoValid = false;
+        displayListener.onDisplayChanged(DISPLAY_ID);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
+        assertNull(vote);
+    }
+
+    private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
+        return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
+    }
+
+    private void assertVoteForPhysicalRefreshRate(Vote vote, float refreshRate) {
+        assertThat(vote).isNotNull();
+        final RefreshRateRange expectedRange = new RefreshRateRange(refreshRate, refreshRate);
+        assertThat(vote.refreshRateRanges.physical).isEqualTo(expectedRange);
+    }
+
+    private void assertVoteForRenderFrameRateRange(
+            Vote vote, float frameRateLow, float frameRateHigh) {
+        assertThat(vote).isNotNull();
+        final RefreshRateRange expectedRange =
+                new RefreshRateRange(frameRateLow, frameRateHigh);
+        assertThat(vote.refreshRateRanges.render).isEqualTo(expectedRange);
+    }
+
+    public static class FakeDeviceConfig extends FakeDeviceConfigInterface {
+        @Override
+        public String getProperty(String namespace, String name) {
+            Preconditions.checkArgument(DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace));
+            return super.getProperty(namespace, name);
+        }
+
+        @Override
+        public void addOnPropertiesChangedListener(
+                String namespace,
+                Executor executor,
+                DeviceConfig.OnPropertiesChangedListener listener) {
+            Preconditions.checkArgument(DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace));
+            super.addOnPropertiesChangedListener(namespace, executor, listener);
+        }
+
+        void setRefreshRateInLowZone(Integer fps) {
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_LOW_ZONE,
+                    String.valueOf(fps));
+        }
+
+        void setRefreshRateInHbmSunlight(Integer fps) {
+            putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, String.valueOf(fps));
+        }
+
+        void setRefreshRateInHbmHdr(Integer fps) {
+            putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps));
+        }
+
+        public void setThermalBrightnessThrottlingData(String brightnessThrottlingData) {
+            putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    KEY_BRIGHTNESS_THROTTLING_DATA, brightnessThrottlingData);
+        }
+
+        void setLowDisplayBrightnessThresholds(int[] brightnessThresholds) {
+            String thresholds = toPropertyValue(brightnessThresholds);
+
+            if (DEBUG) {
+                Slog.e(TAG, "Brightness Thresholds = " + thresholds);
+            }
+
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS,
+                    thresholds);
+        }
+
+        void setLowAmbientBrightnessThresholds(int[] ambientThresholds) {
+            String thresholds = toPropertyValue(ambientThresholds);
+
+            if (DEBUG) {
+                Slog.e(TAG, "Ambient Thresholds = " + thresholds);
+            }
+
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS,
+                    thresholds);
+        }
+
+        void setRefreshRateInHighZone(Integer fps) {
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_HIGH_ZONE,
+                    String.valueOf(fps));
+        }
+
+        void setDefaultPeakRefreshRate(Integer fps) {
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_PEAK_REFRESH_RATE_DEFAULT,
+                    String.valueOf(fps));
+        }
+
+        void setHighDisplayBrightnessThresholds(int[] brightnessThresholds) {
+            String thresholds = toPropertyValue(brightnessThresholds);
+
+            if (DEBUG) {
+                Slog.e(TAG, "Brightness Thresholds = " + thresholds);
+            }
+
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS,
+                    thresholds);
+        }
+
+        void setHighAmbientBrightnessThresholds(int[] ambientThresholds) {
+            String thresholds = toPropertyValue(ambientThresholds);
+
+            if (DEBUG) {
+                Slog.e(TAG, "Ambient Thresholds = " + thresholds);
+            }
+
+            putPropertyAndNotify(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS,
+                    thresholds);
+        }
+
+        @NonNull
+        private static String toPropertyValue(@NonNull int[] intArray) {
+            return Arrays.stream(intArray)
+                    .mapToObj(Integer::toString)
+                    .collect(Collectors.joining(","));
+        }
+    }
+
+    private void setBrightness(int brightness, int adjustedBrightness, DisplayListener listener) {
+        float floatBri = BrightnessSynchronizer.brightnessIntToFloat(brightness);
+        float floatAdjBri = BrightnessSynchronizer.brightnessIntToFloat(adjustedBrightness);
+
+        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+                new BrightnessInfo(floatBri, floatAdjBri, 0.0f, 1.0f,
+                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT,
+                    BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+        listener.onDisplayChanged(DISPLAY_ID);
+    }
+
+    private void setPeakRefreshRate(float fps) {
+        Settings.System.putFloat(mContext.getContentResolver(), Settings.System.PEAK_REFRESH_RATE,
+                 fps);
+        mInjector.notifyPeakRefreshRateChanged();
+        waitForIdleSync();
+    }
+
+    private static SensorManager createMockSensorManager(Sensor... sensors) {
+        SensorManager sensorManager = mock(SensorManager.class);
+        when(sensorManager.getSensorList(anyInt())).then((invocation) -> {
+            List<Sensor> requestedSensors = new ArrayList<>();
+            int type = invocation.getArgument(0);
+            for (Sensor sensor : sensors) {
+                if (sensor.getType() == type || type == Sensor.TYPE_ALL) {
+                    requestedSensors.add(sensor);
+                }
+            }
+            return requestedSensors;
+        });
+
+        when(sensorManager.getDefaultSensor(anyInt())).then((invocation) -> {
+            int type = invocation.getArgument(0);
+            for (Sensor sensor : sensors) {
+                if (sensor.getType() == type) {
+                    return sensor;
+                }
+            }
+            return null;
+        });
+        return sensorManager;
+    }
+
+    private static Sensor createLightSensor() {
+        try {
+            return TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        } catch (Exception e) {
+            // There's nothing we can do if this fails, just throw a RuntimeException so that we
+            // don't have to mark every function that might call this as throwing Exception
+            throw new RuntimeException("Failed to create a light sensor", e);
+        }
+    }
+
+    private void waitForIdleSync() {
+        mHandler.runWithScissors(() -> { }, 500 /*timeout*/);
+    }
+
+    public static class FakesInjector implements DisplayModeDirector.Injector {
+        private final FakeDeviceConfig mDeviceConfig;
+        private final DisplayInfo mDisplayInfo;
+        private final Display mDisplay;
+        private boolean mDisplayInfoValid = true;
+        private ContentObserver mBrightnessObserver;
+        private ContentObserver mPeakRefreshRateObserver;
+
+        FakesInjector() {
+            mDeviceConfig = new FakeDeviceConfig();
+            mDisplayInfo = new DisplayInfo();
+            mDisplayInfo.defaultModeId = MODE_ID;
+            mDisplayInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
+                    800, 600, /* refreshRate= */ 60)};
+            mDisplay = createDisplay(DISPLAY_ID);
+        }
+
+        @NonNull
+        public FakeDeviceConfig getDeviceConfig() {
+            return mDeviceConfig;
+        }
+
+        @Override
+        public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
+                @NonNull ContentObserver observer) {
+            mPeakRefreshRateObserver = observer;
+        }
+
+        @Override
+        public void registerDisplayListener(DisplayListener listener, Handler handler) {}
+
+        @Override
+        public void registerDisplayListener(DisplayListener listener, Handler handler, long flag) {}
+
+        @Override
+        public Display getDisplay(int displayId) {
+            return mDisplay;
+        }
+
+        @Override
+        public Display[] getDisplays() {
+            return new Display[] { mDisplay };
+        }
+
+        @Override
+        public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) {
+            displayInfo.copyFrom(mDisplayInfo);
+            return mDisplayInfoValid;
+        }
+
+        @Override
+        public BrightnessInfo getBrightnessInfo(int displayId) {
+            return null;
+        }
+
+        @Override
+        public boolean isDozeState(Display d) {
+            return false;
+        }
+
+        @Override
+        public boolean registerThermalServiceListener(IThermalEventListener listener) {
+            return true;
+        }
+
+        @Override
+        public boolean supportsFrameRateOverride() {
+            return true;
+        }
+
+        protected Display createDisplay(int id) {
+            return new Display(DisplayManagerGlobal.getInstance(), id, mDisplayInfo,
+                    ApplicationProvider.getApplicationContext().getResources());
+        }
+
+        void notifyPeakRefreshRateChanged() {
+            if (mPeakRefreshRateObserver != null) {
+                mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/,
+                        PEAK_REFRESH_RATE_URI);
+            }
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/state/DisplayStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/state/DisplayStateControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/display/utils/AmbientFilterTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/AmbientFilterTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/utils/AmbientFilterTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/utils/AmbientFilterTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java
new file mode 100644
index 0000000..83858cf
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.utils;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+
+import android.os.PowerManager;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.annotations.Keep;
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.DisplayDeviceConfig;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class DeviceConfigParsingUtilsTest {
+    private static final String VALID_DATA_STRING = "display1,1,key1,value1";
+    private static final float FLOAT_TOLERANCE = 0.001f;
+
+    private final BiFunction<String, String, Pair<String, String>> mDataPointToPair = Pair::create;
+    private final Function<List<Pair<String, String>>, List<Pair<String, String>>>
+            mDataSetIdentity = (dataSet) -> dataSet;
+
+    @Keep
+    private static Object[][] parseDeviceConfigMapData() {
+        // dataString, expectedMap
+        return new Object[][]{
+                // null
+                {null, Map.of()},
+                // empty string
+                {"", Map.of()},
+                // 1 display, 1 incomplete data point
+                {"display1,1,key1", Map.of()},
+                // 1 display,2 data points required, only 1 present
+                {"display1,2,key1,value1", Map.of()},
+                // 1 display, 1 data point, dataSetId and some extra data
+                {"display1,1,key1,value1,setId1,extraData", Map.of()},
+                // 1 display, random string instead of number of data points
+                {"display1,one,key1,value1", Map.of()},
+                // 1 display, 1 data point no dataSetId
+                {VALID_DATA_STRING, Map.of("display1", Map.of(DisplayDeviceConfig.DEFAULT_ID,
+                        List.of(Pair.create("key1", "value1"))))},
+                // 1 display, 1 data point, dataSetId
+                {"display1,1,key1,value1,setId1", Map.of("display1", Map.of("setId1",
+                        List.of(Pair.create("key1", "value1"))))},
+                // 1 display, 2 data point, dataSetId
+                {"display1,2,key1,value1,key2,value2,setId1", Map.of("display1", Map.of("setId1",
+                        List.of(Pair.create("key1", "value1"), Pair.create("key2", "value2"))))},
+        };
+    }
+
+    @Test
+    @Parameters(method = "parseDeviceConfigMapData")
+    public void testParseDeviceConfigMap(String dataString,
+            Map<String, Map<String, List<Pair<String, String>>>> expectedMap) {
+        Map<String, Map<String, List<Pair<String, String>>>> result =
+                DeviceConfigParsingUtils.parseDeviceConfigMap(dataString, mDataPointToPair,
+                        mDataSetIdentity);
+
+        assertEquals(expectedMap, result);
+    }
+
+    @Test
+    public void testDataPointMapperReturnsNull() {
+        Map<String, Map<String, List<Pair<String, String>>>> result =
+                DeviceConfigParsingUtils.parseDeviceConfigMap(VALID_DATA_STRING, (s1, s2) -> null,
+                        mDataSetIdentity);
+
+        assertEquals(Map.of(), result);
+    }
+
+    @Test
+    public void testDataSetMapperReturnsNull() {
+        Map<String, Map<String, List<Pair<String, String>>>> result =
+                DeviceConfigParsingUtils.parseDeviceConfigMap(VALID_DATA_STRING, mDataPointToPair,
+                        (dataSet) -> null);
+
+        assertEquals(Map.of(), result);
+    }
+
+    @Keep
+    private static Object[][] parseThermalStatusData() {
+        // thermalStatusString, expectedThermalStatus
+        return new Object[][]{
+                {"none", PowerManager.THERMAL_STATUS_NONE},
+                {"light", PowerManager.THERMAL_STATUS_LIGHT},
+                {"moderate", PowerManager.THERMAL_STATUS_MODERATE},
+                {"severe", PowerManager.THERMAL_STATUS_SEVERE},
+                {"critical", PowerManager.THERMAL_STATUS_CRITICAL},
+                {"emergency", PowerManager.THERMAL_STATUS_EMERGENCY},
+                {"shutdown", PowerManager.THERMAL_STATUS_SHUTDOWN},
+        };
+    }
+
+    @Test
+    @Parameters(method = "parseThermalStatusData")
+    public void testParseThermalStatus(String thermalStatusString,
+            @PowerManager.ThermalStatus int expectedThermalStatus) {
+        int result = DeviceConfigParsingUtils.parseThermalStatus(thermalStatusString);
+
+        assertEquals(expectedThermalStatus, result);
+    }
+
+    @Test
+    public void testParseThermalStatus_illegalStatus() {
+        Throwable result = assertThrows(IllegalArgumentException.class,
+                () -> DeviceConfigParsingUtils.parseThermalStatus("invalid_status"));
+
+        assertEquals("Invalid Thermal Status: invalid_status", result.getMessage());
+    }
+
+    @Test
+    public void testParseBrightness() {
+        float result = DeviceConfigParsingUtils.parseBrightness("0.65");
+
+        assertEquals(0.65, result, FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testParseBrightness_lessThanMin() {
+        Throwable result = assertThrows(IllegalArgumentException.class,
+                () -> DeviceConfigParsingUtils.parseBrightness("-0.65"));
+
+        assertEquals("Brightness value out of bounds: -0.65", result.getMessage());
+    }
+
+    @Test
+    public void testParseBrightness_moreThanMax() {
+        Throwable result = assertThrows(IllegalArgumentException.class,
+                () -> DeviceConfigParsingUtils.parseBrightness("1.65"));
+
+        assertEquals("Brightness value out of bounds: 1.65", result.getMessage());
+    }
+
+    @Test
+    public void testDisplayBrightnessThresholdsIntToFloat_Null() {
+        assertNull(DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat(null));
+    }
+
+    @Test
+    public void testDisplayBrightnessThresholdsIntToFloat() {
+        assertArrayEquals(new float[]{ BrightnessSynchronizer.brightnessIntToFloat(155), -1,
+                        BrightnessSynchronizer.brightnessIntToFloat(170) },
+                DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat(
+                        new int[]{ 155, -1, 170 }), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testAmbientBrightnessThresholdsIntToFloat_Null() {
+        assertNull(DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat(null));
+    }
+
+    @Test
+    public void testAmbientBrightnessThresholdsIntToFloat() {
+        assertArrayEquals(new float[]{ 1700, 20000, -1 },
+                DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat(
+                        new int[]{ 1700, 20000, -1 }), FLOAT_TOLERANCE);
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
new file mode 100644
index 0000000..4494b0c
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.input.InputSensorInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.display.DisplayDeviceConfig.SensorData;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.List;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class SensorUtilsTest {
+
+    private static final String TEST_SENSOR_NAME = "test_sensor_name";
+    private static final String TEST_SENSOR_TYPE = "test_sensor_type";
+    private static final Sensor TEST_SENSOR = createSensor();
+    @Mock
+    private SensorManager mSensorManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testNoSensorData() {
+        Sensor result = SensorUtils.findSensor(mSensorManager, null, Sensor.TYPE_LIGHT);
+        assertNull(result);
+    }
+
+    @Test
+    public void testNoSensorManager() {
+        Sensor result = SensorUtils.findSensor(null, new SensorData(), Sensor.TYPE_LIGHT);
+        assertNull(result);
+    }
+
+    @Keep
+    private static Object[][] findSensorData() {
+        // sensorName, sensorType, fallbackType, allSensors, defaultSensor, expectedResult
+        return new Object[][]{
+                // no data, no default
+                {null, null, Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, null},
+                // matching name, matching type, no default
+                {TEST_SENSOR_NAME, TEST_SENSOR_TYPE, Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, TEST_SENSOR},
+                // matching name, no default
+                {TEST_SENSOR_NAME, null, Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, TEST_SENSOR},
+                // not matching name, no default
+                {"not_matching_name", null, Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, null},
+                // matching type, no default
+                {null, TEST_SENSOR_TYPE, Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, TEST_SENSOR},
+                // not matching type, no default
+                {null, "not_matching_type", Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, null},
+                // not matching type, matching name, no default
+                {TEST_SENSOR_NAME, "not_matching_type", Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, null},
+                // not matching name, matching type, no default
+                {"not_matching_name", TEST_SENSOR_TYPE, Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, null},
+                // not matching type, not matching name, no default
+                {"not_matching_name", "not_matching_type", Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), null, null},
+                // not matching type, not matching name, with default
+                {"not_matching_name", "not_matching_type", Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), TEST_SENSOR, TEST_SENSOR},
+                // no data, with default
+                {null, null, Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), TEST_SENSOR, TEST_SENSOR},
+                // empty data, with default
+                {"", "", Sensor.TYPE_LIGHT,
+                        Collections.singletonList(TEST_SENSOR), TEST_SENSOR, TEST_SENSOR},
+                // empty data, with default, no fallback
+                {"", "", SensorUtils.NO_FALLBACK,
+                        Collections.singletonList(TEST_SENSOR), TEST_SENSOR, null},
+        };
+    }
+
+    @Test
+    @Parameters(method = "findSensorData")
+    public void testFindSensor(@Nullable String sensorName, @Nullable String sensorType,
+            int fallbackType, List<Sensor> allSensors, @Nullable Sensor defaultSensor,
+            @Nullable Sensor expectedResult) {
+        when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(allSensors);
+        when(mSensorManager.getDefaultSensor(fallbackType)).thenReturn(defaultSensor);
+
+        SensorData sensorData = new SensorData();
+        sensorData.name = sensorName;
+        sensorData.type = sensorType;
+
+        Sensor result = SensorUtils.findSensor(mSensorManager, sensorData, fallbackType);
+
+        assertEquals(expectedResult, result);
+    }
+
+    private static Sensor createSensor() {
+        return new Sensor(new InputSensorInfo(
+                TEST_SENSOR_NAME, "vendor", 0, 0, 0, 1f, 1f, 1, 1, 1, 1,
+                TEST_SENSOR_TYPE, "", 0, 0, 0));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java
rename to services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
new file mode 100644
index 0000000..f975b6f
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.whitebalance;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.TypedValue;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.R;
+import com.android.server.LocalServices;
+import com.android.server.display.TestUtils;
+import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.utils.AmbientFilter;
+import com.android.server.display.utils.AmbientFilterStubber;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public final class AmbientLuxTest {
+
+    private static final float ALLOWED_ERROR_DELTA = 0.001f;
+    private static final int AMBIENT_COLOR_TYPE = 20705;
+    private static final String AMBIENT_COLOR_TYPE_STR = "colorSensoryDensoryDoc";
+    private static final float LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE = 5432.1f;
+    private static final float HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE = 3456.7f;
+
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+    private Sensor mLightSensor;
+    private Sensor mAmbientColorSensor;
+    private ContextWrapper mContextSpy;
+    private Resources mResourcesSpy;
+
+    @Mock private SensorManager mSensorManagerMock;
+
+    @Mock private TypedArray mBrightnesses;
+    @Mock private TypedArray mBiases;
+    @Mock private TypedArray mHighLightBrightnesses;
+    @Mock private TypedArray mHighLightBiases;
+    @Mock private TypedArray mAmbientColorTemperatures;
+    @Mock private TypedArray mDisplayColorTemperatures;
+    @Mock private TypedArray mStrongAmbientColorTemperatures;
+    @Mock private TypedArray mStrongDisplayColorTemperatures;
+    @Mock private ColorDisplayService.ColorDisplayServiceInternal mColorDisplayServiceInternalMock;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, null);
+        mAmbientColorSensor = TestUtils.createSensor(AMBIENT_COLOR_TYPE, AMBIENT_COLOR_TYPE_STR);
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+        mResourcesSpy = spy(mContextSpy.getResources());
+        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+        when(mSensorManagerMock.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(mLightSensor);
+        final List<Sensor> sensorList = ImmutableList.of(mLightSensor, mAmbientColorSensor);
+        when(mSensorManagerMock.getSensorList(Sensor.TYPE_ALL)).thenReturn(sensorList);
+        when(mResourcesSpy.getString(
+                R.string.config_displayWhiteBalanceColorTemperatureSensorName))
+                .thenReturn(AMBIENT_COLOR_TYPE_STR);
+        when(mResourcesSpy.getInteger(
+                R.integer.config_displayWhiteBalanceDecreaseDebounce))
+                .thenReturn(0);
+        when(mResourcesSpy.getInteger(
+                R.integer.config_displayWhiteBalanceIncreaseDebounce))
+                .thenReturn(0);
+        mockResourcesFloat(R.dimen.config_displayWhiteBalanceLowLightAmbientColorTemperature,
+                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE);
+        mockResourcesFloat(R.dimen.config_displayWhiteBalanceHighLightAmbientColorTemperature,
+                HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceAmbientColorTemperatures))
+                .thenReturn(mAmbientColorTemperatures);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceDisplayColorTemperatures))
+                .thenReturn(mDisplayColorTemperatures);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceStrongAmbientColorTemperatures))
+                .thenReturn(mStrongAmbientColorTemperatures);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceStrongDisplayColorTemperatures))
+                .thenReturn(mStrongDisplayColorTemperatures);
+
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceLowLightAmbientBrightnesses))
+                .thenReturn(mBrightnesses);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceLowLightAmbientBiases))
+                .thenReturn(mBiases);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceHighLightAmbientBrightnesses))
+                .thenReturn(mHighLightBrightnesses);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceHighLightAmbientBiases))
+                .thenReturn(mHighLightBiases);
+        mockThrottler();
+        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+        LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
+                mColorDisplayServiceInternalMock);
+    }
+
+    @Test
+    public void testCalculateAdjustedBrightnessNits() {
+        doReturn(0.9f).when(mColorDisplayServiceInternalMock).getDisplayWhiteBalanceLuminance();
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float adjustedNits = controller.calculateAdjustedBrightnessNits(500f);
+        assertEquals(/* expected= */ 550f, adjustedNits, /* delta= */ 0.001);
+    }
+
+    @Test
+    public void testNoSpline() throws Exception {
+        setBrightnesses();
+        setBiases();
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    ambientColorTemperature, 0.001);
+        }
+    }
+
+    @Test
+    public void testSpline_OneSegment() throws Exception {
+        final float lowerBrightness = 10.0f;
+        final float upperBrightness = 50.0f;
+        setBrightnesses(lowerBrightness, upperBrightness);
+        setBiases(0.0f, 1.0f);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
+            setEstimatedBrightnessAndUpdate(controller,
+                    mix(lowerBrightness, upperBrightness, t));
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, t), 0.001);
+        }
+
+        setEstimatedBrightnessAndUpdate(controller, 0.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature,
+                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
+
+        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
+    }
+
+    @Test
+    public void testSpline_TwoSegments() throws Exception {
+        final float brightness0 = 10.0f;
+        final float brightness1 = 50.0f;
+        final float brightness2 = 60.0f;
+        setBrightnesses(brightness0, brightness1, brightness2);
+        final float bias0 = 0.0f;
+        final float bias1 = 0.25f;
+        final float bias2 = 1.0f;
+        setBiases(bias0, bias1, bias2);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
+            float luxOverride = mix(brightness0, brightness1, t);
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            float bias = mix(bias0, bias1, t);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, bias), 0.001);
+        }
+
+        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
+            float luxOverride = mix(brightness1, brightness2, t);
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            float bias = mix(bias1, bias2, t);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, bias), 0.001);
+        }
+
+        setEstimatedBrightnessAndUpdate(controller, 0.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature,
+                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
+
+        setEstimatedBrightnessAndUpdate(controller, brightness2 + 1.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
+    }
+
+    @Test
+    public void testSpline_VerticalSegment() throws Exception {
+        final float lowerBrightness = 10.0f;
+        final float upperBrightness = 10.0f;
+        setBrightnesses(lowerBrightness, upperBrightness);
+        setBiases(0.0f, 1.0f);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        setEstimatedBrightnessAndUpdate(controller, 0.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature,
+                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
+
+        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
+    }
+
+    @Test
+    public void testSpline_InvalidEndBias() throws Exception {
+        setBrightnesses(10.0f, 1000.0f);
+        setBiases(0.0f, 2.0f);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    ambientColorTemperature, 0.001);
+        }
+    }
+
+    @Test
+    public void testSpline_InvalidBeginBias() throws Exception {
+        setBrightnesses(10.0f, 1000.0f);
+        setBiases(0.1f, 1.0f);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    ambientColorTemperature, 0.001);
+        }
+    }
+
+    @Test
+    public void testSpline_OneSegmentHighLight() throws Exception {
+        final float lowerBrightness = 10.0f;
+        final float upperBrightness = 50.0f;
+        setHighLightBrightnesses(lowerBrightness, upperBrightness);
+        setHighLightBiases(0.0f, 1.0f);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
+            setEstimatedBrightnessAndUpdate(controller,
+                    mix(lowerBrightness, upperBrightness, t));
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    mix(HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, 1.0f - t),
+                    0.001);
+        }
+
+        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature,
+                HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
+
+        setEstimatedBrightnessAndUpdate(controller, 0.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
+    }
+
+    @Test
+    public void testSpline_TwoSegmentsHighLight() throws Exception {
+        final float brightness0 = 10.0f;
+        final float brightness1 = 50.0f;
+        final float brightness2 = 60.0f;
+        setHighLightBrightnesses(brightness0, brightness1, brightness2);
+        final float bias0 = 0.0f;
+        final float bias1 = 0.25f;
+        final float bias2 = 1.0f;
+        setHighLightBiases(bias0, bias1, bias2);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 6000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
+            float luxOverride = mix(brightness0, brightness1, t);
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            float bias = mix(bias0, bias1, t);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    mix(HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, 1.0f - bias),
+                    0.01);
+        }
+
+        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
+            float luxOverride = mix(brightness1, brightness2, t);
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            float bias = mix(bias1, bias2, t);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    mix(HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, 1.0f - bias),
+                    0.01);
+        }
+
+        setEstimatedBrightnessAndUpdate(controller, brightness2 + 1.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature,
+                HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
+
+        setEstimatedBrightnessAndUpdate(controller, 0.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
+    }
+
+    @Test
+    public void testSpline_InvalidCombinations() throws Exception {
+        setBrightnesses(100.0f, 200.0f);
+        setBiases(0.0f, 1.0f);
+        setHighLightBrightnesses(150.0f, 250.0f);
+        setHighLightBiases(0.0f, 1.0f);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = 8000.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
+            setEstimatedBrightnessAndUpdate(controller, luxOverride);
+            assertEquals(controller.mPendingAmbientColorTemperature,
+                    ambientColorTemperature, 0.001);
+        }
+    }
+
+    @Test
+    public void testStrongMode() {
+        final float lowerBrightness = 10.0f;
+        final float upperBrightness = 50.0f;
+        setBrightnesses(lowerBrightness, upperBrightness);
+        setBiases(0.0f, 1.0f);
+        final int ambientColorTempLow = 6000;
+        final int ambientColorTempHigh = 8000;
+        final int displayColorTempLow = 6400;
+        final int displayColorTempHigh = 7400;
+        setStrongAmbientColorTemperatures(ambientColorTempLow, ambientColorTempHigh);
+        setStrongDisplayColorTemperatures(displayColorTempLow, displayColorTempHigh);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        controller.setStrongModeEnabled(true);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float ambientTempFraction = 0.0f; ambientTempFraction <= 1.0f;
+                ambientTempFraction += 0.1f) {
+            final float ambientTemp =
+                    (ambientColorTempHigh - ambientColorTempLow) * ambientTempFraction
+                            + ambientColorTempLow;
+            setEstimatedColorTemperature(controller, ambientTemp);
+            for (float brightnessFraction = 0.0f; brightnessFraction <= 1.0f;
+                    brightnessFraction += 0.1f) {
+                setEstimatedBrightnessAndUpdate(controller,
+                        mix(lowerBrightness, upperBrightness, brightnessFraction));
+                assertEquals(controller.mPendingAmbientColorTemperature,
+                        mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE,
+                                mix(displayColorTempLow, displayColorTempHigh, ambientTempFraction),
+                                brightnessFraction),
+                        ALLOWED_ERROR_DELTA);
+            }
+        }
+    }
+
+    @Test
+    public void testLowLight_DefaultAmbient() throws Exception {
+        final float lowerBrightness = 10.0f;
+        final float upperBrightness = 50.0f;
+        setBrightnesses(lowerBrightness, upperBrightness);
+        setBiases(0.0f, 1.0f);
+
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        final float ambientColorTemperature = -1.0f;
+        setEstimatedColorTemperature(controller, ambientColorTemperature);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
+
+        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
+            setEstimatedBrightnessAndUpdate(controller,
+                    mix(lowerBrightness, upperBrightness, t));
+            assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature,
+                        0.001);
+        }
+
+        setEstimatedBrightnessAndUpdate(controller, 0.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
+
+        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
+        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
+    }
+
+    @Test
+    public void testWhiteBalance_updateWithEmptyFilter() throws Exception {
+        setAmbientColorTemperatures(5300.0f, 6000.0f, 7000.0f, 8000.0f);
+        setDisplayColorTemperatures(6300.0f, 6400.0f, 6850.0f, 7450.0f);
+        DisplayWhiteBalanceController controller =
+                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
+        controller.updateAmbientColorTemperature();
+        assertEquals(-1.0f, controller.mPendingAmbientColorTemperature, 0);
+    }
+
+    void mockThrottler() {
+        when(mResourcesSpy.getInteger(
+                R.integer.config_displayWhiteBalanceDecreaseDebounce)).thenReturn(0);
+        when(mResourcesSpy.getInteger(
+                R.integer.config_displayWhiteBalanceIncreaseDebounce)).thenReturn(0);
+        TypedArray base = mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceBaseThresholds);
+        TypedArray inc = mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceIncreaseThresholds);
+        TypedArray dec = mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceDecreaseThresholds);
+        base = spy(base);
+        inc = spy(inc);
+        dec = spy(dec);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceBaseThresholds)).thenReturn(base);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceIncreaseThresholds)).thenReturn(inc);
+        when(mResourcesSpy.obtainTypedArray(
+                R.array.config_displayWhiteBalanceDecreaseThresholds)).thenReturn(dec);
+        setFloatArrayResource(base, new float[]{0.0f});
+        setFloatArrayResource(inc, new float[]{0.0f});
+        setFloatArrayResource(dec, new float[]{0.0f});
+    }
+
+    private void mockResourcesFloat(int id, float floatValue) {
+        doAnswer(new Answer<Void>() {
+            public Void answer(InvocationOnMock invocation) {
+                TypedValue value = (TypedValue) invocation.getArgument(1);
+                value.type = TypedValue.TYPE_FLOAT;
+                value.data = Float.floatToIntBits(floatValue);
+                return null;
+            }
+        }).when(mResourcesSpy).getValue(
+                eq(id),
+                any(TypedValue.class), eq(true));
+    }
+
+    private void setEstimatedColorTemperature(DisplayWhiteBalanceController controller,
+                                              float ambientColorTemperature) {
+        AmbientFilter colorTemperatureFilter = spy(new AmbientFilterStubber());
+        controller.mColorTemperatureFilter = colorTemperatureFilter;
+        when(colorTemperatureFilter.getEstimate(anyLong())).thenReturn(ambientColorTemperature);
+    }
+
+    private void setEstimatedBrightnessAndUpdate(DisplayWhiteBalanceController controller,
+                                                 float brightness) {
+        when(controller.mBrightnessFilter.getEstimate(anyLong())).thenReturn(brightness);
+        controller.updateAmbientColorTemperature();
+    }
+
+    private void setBrightnesses(float... vals) {
+        setFloatArrayResource(mBrightnesses, vals);
+    }
+
+    private void setBiases(float... vals) {
+        setFloatArrayResource(mBiases, vals);
+    }
+
+    private void setHighLightBrightnesses(float... vals) {
+        setFloatArrayResource(mHighLightBrightnesses, vals);
+    }
+
+    private void setHighLightBiases(float... vals) {
+        setFloatArrayResource(mHighLightBiases, vals);
+    }
+
+    private void setAmbientColorTemperatures(float... vals) {
+        setFloatArrayResource(mAmbientColorTemperatures, vals);
+    }
+
+    private void setDisplayColorTemperatures(float... vals) {
+        setFloatArrayResource(mDisplayColorTemperatures, vals);
+    }
+
+    private void setStrongAmbientColorTemperatures(float... vals) {
+        setFloatArrayResource(mStrongAmbientColorTemperatures, vals);
+    }
+
+    private void setStrongDisplayColorTemperatures(float... vals) {
+        setFloatArrayResource(mStrongDisplayColorTemperatures, vals);
+    }
+
+    private void setFloatArrayResource(TypedArray array, float[] vals) {
+        when(array.length()).thenReturn(vals.length);
+        for (int i = 0; i < vals.length; i++) {
+            when(array.getFloat(i, Float.NaN)).thenReturn(vals[i]);
+        }
+    }
+
+    private TypedArray createTypedArray() throws Exception {
+        TypedArray mockArray = mock(TypedArray.class);
+        return mockArray;
+    }
+
+    private static float mix(float a, float b, float t) {
+        return (1.0f - t) * a + t * b;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientSensorTest.java
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 101498a..bde02e8 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -115,3 +115,14 @@
         "android.test.runner",
     ],
 }
+
+filegroup {
+    name: "extended-mockito-rule-sources",
+    srcs: [
+        "src/com/android/server/ExtendedMockitoRule.java",
+        "src/com/android/server/Visitor.java",
+    ],
+    visibility: [
+        "//frameworks/base/services/tests/displayservicetests",
+    ],
+}
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/affected_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/affected_cpus
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/affected_cpus
@@ -0,0 +1 @@
+
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/related_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/related_cpus
new file mode 100644
index 0000000..c227083
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/related_cpus
@@ -0,0 +1 @@
+0
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_cur_freq
new file mode 100644
index 0000000..dadd973
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_cur_freq
@@ -0,0 +1 @@
+1230000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_max_freq
new file mode 100644
index 0000000..a93d6f7
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_max_freq
@@ -0,0 +1 @@
+2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/stats/time_in_state b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/stats/time_in_state
new file mode 100644
index 0000000..5121f666
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/stats/time_in_state
@@ -0,0 +1,4 @@
+200000 500
+350000 500
+500000 1000
+2500000 100
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/affected_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/affected_cpus
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/affected_cpus
@@ -0,0 +1 @@
+1
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/related_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/related_cpus
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/related_cpus
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_cur_freq
new file mode 100644
index 0000000..2bc4ce9
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_cur_freq
@@ -0,0 +1 @@
+1450000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_max_freq
new file mode 100644
index 0000000..c754f1a
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_max_freq
@@ -0,0 +1 @@
+2800000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/stats/time_in_state b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/stats/time_in_state
new file mode 100644
index 0000000..7ed12cf
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/stats/time_in_state
@@ -0,0 +1,4 @@
+200000 500
+350000 500
+500000 1000
+2800000 100
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/affected_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/affected_cpus
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/affected_cpus
@@ -0,0 +1 @@
+0
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/related_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/related_cpus
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/related_cpus
@@ -0,0 +1 @@
+
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_cur_freq
new file mode 100644
index 0000000..dadd973
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_cur_freq
@@ -0,0 +1 @@
+1230000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_max_freq
new file mode 100644
index 0000000..a93d6f7
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_max_freq
@@ -0,0 +1 @@
+2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/stats/time_in_state b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/stats/time_in_state
new file mode 100644
index 0000000..5121f666
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/stats/time_in_state
@@ -0,0 +1,4 @@
+200000 500
+350000 500
+500000 1000
+2500000 100
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/affected_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/affected_cpus
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/affected_cpus
@@ -0,0 +1 @@
+1
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/related_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/related_cpus
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/related_cpus
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_cur_freq
new file mode 100644
index 0000000..2bc4ce9
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_cur_freq
@@ -0,0 +1 @@
+1450000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_max_freq
new file mode 100644
index 0000000..c754f1a
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_max_freq
@@ -0,0 +1 @@
+2800000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/stats/time_in_state b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/stats/time_in_state
new file mode 100644
index 0000000..7ed12cf
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/stats/time_in_state
@@ -0,0 +1,4 @@
+200000 500
+350000 500
+500000 1000
+2800000 100
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq
index a93d6f7..d17275f 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy0/scaling_max_freq
@@ -1 +1 @@
-2500000
+2600000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_max_freq
index c754f1a..79c41c7 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_max_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy1/scaling_max_freq
@@ -1 +1 @@
-2800000
+2900000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/scaling_max_freq
index deebb18..526a717 100644
--- a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/scaling_max_freq
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_time_in_state_2/policy2/scaling_max_freq
@@ -1 +1 @@
-2000000
+2100000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/background/cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/background/cpus
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/background/cpus
@@ -0,0 +1 @@
+
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/top-app/cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/top-app/cpus
new file mode 100644
index 0000000..40c7bb2
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/top-app/cpus
@@ -0,0 +1 @@
+0-3
diff --git a/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java b/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java
index 353341e..82e02ca 100644
--- a/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java
+++ b/services/tests/mockingservicestests/src/android/service/games/GameSessionTrampolineActivityTest.java
@@ -47,11 +47,14 @@
 
 import com.android.internal.infra.AndroidFuture;
 
+import com.google.common.util.concurrent.Uninterruptibles;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Duration;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -62,6 +65,8 @@
 @Presubmit
 public class GameSessionTrampolineActivityTest {
 
+    private static final Duration TEST_ACTIVITY_OPEN_DURATION = Duration.ofSeconds(5);
+
     @Before
     public void setUp() {
         setAlwaysFinishActivities(false);
@@ -145,10 +150,15 @@
             startTestActivityViaGameSessionTrampolineActivity() {
         Intent testActivityIntent = new Intent();
         testActivityIntent.setClass(getInstrumentation().getTargetContext(), TestActivity.class);
+        sleep(TEST_ACTIVITY_OPEN_DURATION);
 
         return startGameSessionTrampolineActivity(testActivityIntent);
     }
 
+    private static void sleep(Duration sleepDuration) {
+        Uninterruptibles.sleepUninterruptibly(sleepDuration.toMillis(), TimeUnit.MILLISECONDS);
+    }
+
     private static AndroidFuture<GameSessionActivityResult> startGameSessionTrampolineActivity(
             Intent targetIntent) {
         AndroidFuture<GameSessionActivityResult> resultFuture = new AndroidFuture<>();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index 7c5d96e..1fd7c4a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -131,6 +131,7 @@
 
         mRealAms = new ActivityManagerService(
                 new TestInjector(mContext), mServiceThreadRule.getThread());
+        mRealAms.mConstants.loadDeviceConfigConstants();
         mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
         mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
         mRealAms.mAtmInternal = mActivityTaskManagerInt;
@@ -195,7 +196,9 @@
             Log.v(TAG, "Intercepting bindApplication() for "
                     + Arrays.toString(invocation.getArguments()));
             if (!wedge) {
-                mRealAms.finishAttachApplication(0);
+                if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) {
+                    mRealAms.finishAttachApplication(0);
+                }
             }
             return null;
         }).when(thread).bindApplication(
@@ -237,9 +240,10 @@
      */
     @Test
     public void testNormal() throws Exception {
-        ProcessRecord app = startProcessAndWait(false);
-
-        verify(app, never()).killLocked(any(), anyInt(), anyBoolean());
+        if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) {
+            ProcessRecord app = startProcessAndWait(false);
+            verify(app, never()).killLocked(any(), anyInt(), anyBoolean());
+        }
     }
 
     /**
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 6bcc14e..a147098 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -620,6 +620,28 @@
         assertEquals(BroadcastProcessQueue.REASON_CORE_UID, queue.getRunnableAtReason());
     }
 
+    @Test
+    public void testRunnableAt_freezableCoreUid() {
+        final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                "com.android.bluetooth", Process.BLUETOOTH_UID);
+
+        // Mark the process as freezable
+        queue.setProcessAndUidState(mProcess, false, true);
+        final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+        final BroadcastOptions options = BroadcastOptions.makeWithDeferUntilActive(true);
+        final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick, options,
+                List.of(makeMockRegisteredReceiver()), false);
+        enqueueOrReplaceBroadcast(queue, timeTickRecord, 0);
+
+        assertEquals(Long.MAX_VALUE, queue.getRunnableAt());
+        assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER,
+                queue.getRunnableAtReason());
+
+        queue.setProcessAndUidState(mProcess, false, false);
+        assertThat(queue.getRunnableAt()).isEqualTo(timeTickRecord.enqueueTime);
+        assertEquals(BroadcastProcessQueue.REASON_CORE_UID, queue.getRunnableAtReason());
+    }
+
     /**
      * Verify that a cached process that would normally be delayed becomes
      * immediately runnable when the given broadcast is enqueued.
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 73eb237..410ae35 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -125,6 +125,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.UnaryOperator;
@@ -327,7 +328,7 @@
                 eq(ActivityManager.PROCESS_STATE_LAST_ACTIVITY), any());
 
         mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
-        mConstants.TIMEOUT = 100;
+        mConstants.TIMEOUT = 200;
         mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
         mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
 
@@ -707,6 +708,9 @@
     private void waitForIdle() throws Exception {
         mLooper.release();
         mQueue.waitForIdle(LOG_WRITER_INFO);
+        final CountDownLatch latch = new CountDownLatch(1);
+        mHandlerThread.getThreadHandler().post(latch::countDown);
+        latch.await();
         mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
                 .acquireLooperManager(mHandlerThread.getLooper()));
     }
@@ -2342,6 +2346,7 @@
 
         mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
                 ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+        waitForIdle();
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
@@ -2375,6 +2380,7 @@
 
         mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
                 ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+        waitForIdle();
 
         final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index c6a914b..9545a8a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -1970,6 +1970,36 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoOne_PendingFinishAttach() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+        app.setPendingFinishAttach(true);
+        app.mState.setHasForegroundActivities(false);
+
+        sService.mOomAdjuster.setAttachingProcessStatesLSP(app);
+        updateOomAdj(app);
+
+        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, FOREGROUND_APP_ADJ,
+                SCHED_GROUP_DEFAULT);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testUpdateOomAdj_DoOne_TopApp_PendingFinishAttach() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+        app.setPendingFinishAttach(true);
+        app.mState.setHasForegroundActivities(true);
+
+        sService.mOomAdjuster.setAttachingProcessStatesLSP(app);
+        updateOomAdj(app);
+
+        assertProcStates(app, PROCESS_STATE_TOP, FOREGROUND_APP_ADJ,
+                SCHED_GROUP_TOP_APP);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_UidIdle_StopService() {
         final ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 212a243..6906dec 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -52,6 +52,7 @@
 import android.app.GameModeInfo;
 import android.app.GameState;
 import android.app.IGameModeListener;
+import android.app.IGameStateListener;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -1578,6 +1579,71 @@
         assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
     }
 
+    @Test
+    public void testAddGameStateListener() throws Exception {
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        mockDeviceConfigAll();
+        startUser(gameManagerService, USER_ID_1);
+
+        IGameStateListener mockListener = Mockito.mock(IGameStateListener.class);
+        IBinder binder = Mockito.mock(IBinder.class);
+        when(mockListener.asBinder()).thenReturn(binder);
+        gameManagerService.addGameStateListener(mockListener);
+        verify(binder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
+
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+        GameState gameState = new GameState(true, GameState.MODE_NONE);
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
+        mTestLooper.dispatchAll();
+        verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt());
+
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
+        gameState = new GameState(true, GameState.MODE_NONE);
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
+        mTestLooper.dispatchAll();
+        verify(mockListener).onGameStateChanged(mPackageName, gameState, USER_ID_1);
+        reset(mockListener);
+
+        gameState = new GameState(false, GameState.MODE_CONTENT);
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        mTestLooper.dispatchAll();
+        verify(mockListener).onGameStateChanged(mPackageName, gameState, USER_ID_1);
+        reset(mockListener);
+
+        mDeathRecipientCaptor.getValue().binderDied();
+        verify(binder).unlinkToDeath(eq(mDeathRecipientCaptor.getValue()), anyInt());
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
+        mTestLooper.dispatchAll();
+        verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt());
+    }
+
+    @Test
+    public void testRemoveGameStateListener() throws Exception {
+        mockModifyGameModeGranted();
+        GameManagerService gameManagerService =
+                new GameManagerService(mMockContext, mTestLooper.getLooper());
+        mockDeviceConfigAll();
+        startUser(gameManagerService, USER_ID_1);
+
+        IGameStateListener mockListener = Mockito.mock(IGameStateListener.class);
+        IBinder binder = Mockito.mock(IBinder.class);
+        when(mockListener.asBinder()).thenReturn(binder);
+
+        gameManagerService.addGameStateListener(mockListener);
+        gameManagerService.removeGameStateListener(mockListener);
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
+        GameState gameState = new GameState(false, GameState.MODE_CONTENT);
+        gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+        assertTrue(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
+        mTestLooper.dispatchAll();
+        verify(mockListener, never()).onGameStateChanged(anyString(), any(), anyInt());
+    }
+
     private List<String> readGameModeInterventionList() throws Exception {
         final File interventionFile = new File(InstrumentationRegistry.getContext().getFilesDir(),
                 "system/game_mode_intervention.list");
@@ -2091,6 +2157,14 @@
     }
 
     @Test
+    public void testResetGamePowerMode() {
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        gameManagerService.onBootCompleted();
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+    }
+
+    @Test
     public void testNotifyGraphicsEnvironmentSetup() {
         String configString = "mode=2,loadingBoost=2000";
         when(DeviceConfig.getProperty(anyString(), anyString()))
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
index 04f6f8b..2fbe8aa 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
@@ -48,6 +48,11 @@
     private static final String TAG = CpuInfoReaderTest.class.getSimpleName();
     private static final String ROOT_DIR_NAME = "CpuInfoReaderTest";
     private static final String VALID_CPUSET_DIR = "valid_cpuset";
+    private static final String VALID_CPUSET_WITH_EMPTY_CPUS = "valid_cpuset_with_empty_cpus";
+    private static final String VALID_CPUFREQ_WITH_EMPTY_AFFECTED_CPUS =
+            "valid_cpufreq_with_empty_affected_cpus";
+    private static final String VALID_CPUFREQ_WITH_EMPTY_RELATED_CPUS =
+            "valid_cpufreq_with_empty_related_cpus";
     private static final String VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR =
             "valid_cpufreq_with_time_in_state";
     private static final String VALID_CPUFREQ_WITH_TIME_IN_STATE_2_DIR =
@@ -142,8 +147,8 @@
         expectedCpuInfos.clear();
         expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
                 FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
-                /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 419_354,
-                /* normalizedAvailableCpuFreqKHz= */ 2_425_919,
+                /* maxCpuFreqKHz= */ 2_600_000, /* avgTimeInStateCpuFreqKHz= */ 419_354,
+                /* normalizedAvailableCpuFreqKHz= */ 2_525_919,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
                         /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
                         /* idleTimeMillis= */ 110_000_000, /* iowaitTimeMillis= */ 1_100_000,
@@ -152,8 +157,8 @@
                         /* guestNiceTimeMillis= */ 0)));
         expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
                 FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 2_800_000,
-                /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 429_032,
-                /* normalizedAvailableCpuFreqKHz= */ 2_403_009,
+                /* maxCpuFreqKHz= */ 2_900_000, /* avgTimeInStateCpuFreqKHz= */ 429_032,
+                /* normalizedAvailableCpuFreqKHz= */ 2_503_009,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 900_000,
                         /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000,
                         /* idleTimeMillis= */ 1_000_000, /* iowaitTimeMillis= */ 90_000,
@@ -163,8 +168,8 @@
         expectedCpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2,
                 FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
                 /* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000,
-                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 403_225,
-                /* normalizedAvailableCpuFreqKHz= */ 1_688_209,
+                /* maxCpuFreqKHz= */ 2_100_000, /* avgTimeInStateCpuFreqKHz= */ 403_225,
+                /* normalizedAvailableCpuFreqKHz= */ 1_788_209,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000,
                         /* niceTimeMillis= */ 2_000_000, /* systemTimeMillis= */ 0,
                         /* idleTimeMillis= */ 10_000_000, /* iowaitTimeMillis= */ 1_000_000,
@@ -174,7 +179,7 @@
         expectedCpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3,
                 FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND,
                 /* isOnline= */ false, /* curCpuFreqKHz= */ MISSING_FREQUENCY,
-                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
+                /* maxCpuFreqKHz= */ 2_100_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY,
                 /* normalizedAvailableCpuFreqKHz= */ MISSING_FREQUENCY,
                 new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 2_000_000,
                         /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 1_000_000,
@@ -403,6 +408,104 @@
     }
 
     @Test
+    public void testReadCpuInfoWithEmptyRelatedCpus() throws Exception {
+        CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
+                getCacheFile(VALID_CPUFREQ_WITH_EMPTY_RELATED_CPUS),
+                getCacheFile(VALID_PROC_STAT));
+
+        SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
+        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos = new SparseArray<>();
+
+        expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
+                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
+                /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
+                /* normalizedAvailableCpuFreqKHz= */ 2_693_525,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
+                        /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
+                        /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
+                        /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
+                        /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+
+        compareCpuInfos("CPU infos with policy 0 containing an empty related_cpus file",
+                expectedCpuInfos, actualCpuInfos);
+    }
+
+    @Test
+    public void testReadCpuInfoWithEmptyCpusetCpus() throws Exception {
+        CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_WITH_EMPTY_CPUS),
+                getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR),
+                getCacheFile(VALID_PROC_STAT));
+
+        SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
+        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos = new SparseArray<>();
+        expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
+                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
+                /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 488_095,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
+                        /* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
+                        /* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
+                        /* irqTimeMillis= */ 8_146_740, /* softirqTimeMillis= */ 428_970,
+                        /* stealTimeMillis= */ 81_950, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
+                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
+                /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
+                        /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
+                        /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
+                        /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
+                        /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        expectedCpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2,
+                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
+                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
+                        /* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
+                        /* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
+                        /* irqTimeMillis= */ 14_796_940, /* softirqTimeMillis= */ 1_478_130,
+                        /* stealTimeMillis= */ 88_780, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+        expectedCpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3,
+                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
+                /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
+                /* normalizedAvailableCpuFreqKHz= */ 1_907_125,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
+                        /* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
+                        /* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
+                        /* irqTimeMillis= */ 8_136_740, /* softirqTimeMillis= */ 438_970,
+                        /* stealTimeMillis= */ 71_950, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+
+        compareCpuInfos("CPU infos with empty background cpu set", expectedCpuInfos,
+                actualCpuInfos);
+    }
+
+    @Test
+    public void testReadCpuInfoWithEmptyAffectedCpus() throws Exception {
+        CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
+                getCacheFile(VALID_CPUFREQ_WITH_EMPTY_AFFECTED_CPUS),
+                getCacheFile(VALID_PROC_STAT));
+
+        SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
+        SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos = new SparseArray<>();
+
+        expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
+                FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
+                /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
+                /* normalizedAvailableCpuFreqKHz= */ 2_693_525,
+                new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
+                        /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
+                        /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
+                        /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
+                        /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
+                        /* guestNiceTimeMillis= */ 0)));
+
+        compareCpuInfos("CPU infos with policy 0 containing an empty affected_cpus file",
+                expectedCpuInfos, actualCpuInfos);
+    }
+
+    @Test
     public void testReadCpuInfoWithEmptyProcStat() throws Exception {
         File emptyFile = getCacheFile(EMPTY_FILE);
         assertWithMessage("Create empty file %s", emptyFile).that(emptyFile.createNewFile())
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
deleted file mode 100644
index 50996d7..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ /dev/null
@@ -1,67 +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.server.display;
-
-import static org.junit.Assert.assertEquals;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.display.brightness.BrightnessReason;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class DisplayBrightnessStateTest {
-    private static final float FLOAT_DELTA = 0.001f;
-
-    private DisplayBrightnessState.Builder mDisplayBrightnessStateBuilder;
-
-    @Before
-    public void before() {
-        mDisplayBrightnessStateBuilder = new DisplayBrightnessState.Builder();
-    }
-
-    @Test
-    public void validateAllDisplayBrightnessStateFieldsAreSetAsExpected() {
-        float brightness = 0.3f;
-        float sdrBrightness = 0.2f;
-        BrightnessReason brightnessReason = new BrightnessReason();
-        brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
-        brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED);
-        DisplayBrightnessState displayBrightnessState =
-                mDisplayBrightnessStateBuilder.setBrightness(brightness).setSdrBrightness(
-                        sdrBrightness).setBrightnessReason(brightnessReason).build();
-
-        assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
-        assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA);
-        assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason);
-        assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState));
-    }
-
-    private String getString(DisplayBrightnessState displayBrightnessState) {
-        StringBuilder sb = new StringBuilder();
-        sb.append("DisplayBrightnessState:");
-        sb.append("\n    brightness:" + displayBrightnessState.getBrightness());
-        sb.append("\n    sdrBrightness:" + displayBrightnessState.getSdrBrightness());
-        sb.append("\n    brightnessReason:" + displayBrightnessState.getBrightnessReason());
-        return sb.toString();
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
deleted file mode 100644
index 4f98dca..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ /dev/null
@@ -1,1106 +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.server.display;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.Sensor;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.SystemProperties;
-import android.os.test.TestLooper;
-import android.provider.Settings;
-import android.testing.TestableContext;
-import android.util.FloatProperty;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.server.ExtendedMockitoRule;
-import com.android.server.LocalServices;
-import com.android.server.am.BatteryStatsService;
-import com.android.server.display.RampAnimator.DualRampAnimator;
-import com.android.server.display.brightness.BrightnessEvent;
-import com.android.server.display.color.ColorDisplayService;
-import com.android.server.display.layout.Layout;
-import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
-import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.testutils.OffsettableClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.quality.Strictness;
-import org.mockito.stubbing.Answer;
-
-import java.util.List;
-
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class DisplayPowerController2Test {
-    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
-    private static final String UNIQUE_ID = "unique_id_test123";
-    private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1;
-    private static final String FOLLOWER_UNIQUE_ID = "unique_id_456";
-    private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
-    private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
-    private static final float PROX_SENSOR_MAX_RANGE = 5;
-
-    private OffsettableClock mClock;
-    private TestLooper mTestLooper;
-    private Handler mHandler;
-    private DisplayPowerControllerHolder mHolder;
-    private Sensor mProxSensor;
-
-    @Mock
-    private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
-    @Mock
-    private SensorManager mSensorManagerMock;
-    @Mock
-    private DisplayBlanker mDisplayBlankerMock;
-    @Mock
-    private BrightnessTracker mBrightnessTrackerMock;
-    @Mock
-    private WindowManagerPolicy mWindowManagerPolicyMock;
-    @Mock
-    private PowerManager mPowerManagerMock;
-    @Mock
-    private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
-
-    @Captor
-    private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
-
-    @Rule
-    public final TestableContext mContext = new TestableContext(
-            InstrumentationRegistry.getInstrumentation().getContext());
-
-    @Rule
-    public final ExtendedMockitoRule mExtendedMockitoRule =
-            new ExtendedMockitoRule.Builder(this)
-                    .setStrictness(Strictness.LENIENT)
-                    .spyStatic(SystemProperties.class)
-                    .spyStatic(BatteryStatsService.class)
-                    .build();
-
-    @Before
-    public void setUp() throws Exception {
-        mClock = new OffsettableClock.Stopped();
-        mTestLooper = new TestLooper(mClock::now);
-        mHandler = new Handler(mTestLooper.getLooper());
-
-        // Put the system into manual brightness by default, just to minimize unexpected events and
-        // have a consistent starting state
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
-
-        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
-        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
-                mCdsiMock);
-
-        mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
-
-        doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
-                SystemProperties.set(anyString(), any()));
-        doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
-
-        setUpSensors();
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-    }
-
-    @After
-    public void tearDown() {
-        LocalServices.removeServiceForTest(WindowManagerPolicy.class);
-        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
-    }
-
-    @Test
-    public void testReleaseProxSuspendBlockersOnExit() throws Exception {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState to start listener for the prox sensor
-        advanceTime(1);
-
-        SensorEventListener listener = getSensorEventListener(mProxSensor);
-        assertNotNull(listener);
-
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 5));
-        advanceTime(1);
-
-        // two times, one for unfinished business and one for proximity
-        verify(mHolder.wakelockController, times(2)).acquireWakelock(
-                WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
-        verify(mHolder.wakelockController).acquireWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
-
-        mHolder.dpc.stop();
-        advanceTime(1);
-        // two times, one for unfinished business and one for proximity
-        verify(mHolder.wakelockController, times(2)).acquireWakelock(
-                WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
-        verify(mHolder.wakelockController).acquireWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
-    }
-
-    @Test
-    public void testScreenOffBecauseOfProximity() throws Exception {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState to start listener for the prox sensor
-        advanceTime(1);
-
-        SensorEventListener listener = getSensorEventListener(mProxSensor);
-        assertNotNull(listener);
-
-        // Send a positive proximity event
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
-        advanceTime(1);
-
-        // The display should have been turned off
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
-
-        clearInvocations(mHolder.displayPowerState);
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
-        // Send a negative proximity event
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor,
-                (int) PROX_SENSOR_MAX_RANGE + 1));
-        // Advance time by less than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
-        advanceTime(1);
-
-        // The prox sensor is debounced so the display should not have been turned back on yet
-        verify(mHolder.displayPowerState, never()).setScreenState(Display.STATE_ON);
-
-        // Advance time by more than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
-        advanceTime(1000);
-
-        // The display should have been turned back on
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
-    }
-
-    @Test
-    public void testScreenOffBecauseOfProximity_ProxSensorGone() throws Exception {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState to start listener for the prox sensor
-        advanceTime(1);
-
-        SensorEventListener listener = getSensorEventListener(mProxSensor);
-        assertNotNull(listener);
-
-        // Send a positive proximity event
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
-        advanceTime(1);
-
-        // The display should have been turned off
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
-
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
-        // The display device changes and we no longer have a prox sensor
-        reset(mSensorManagerMock);
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-
-        advanceTime(1); // Run updatePowerState
-
-        // The display should have been turned back on and the listener should have been
-        // unregistered
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
-        verify(mSensorManagerMock).unregisterListener(listener);
-    }
-
-    @Test
-    public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        final DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-        followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState
-        advanceTime(1);
-
-        verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
-                eq(mProxSensor), anyInt(), any(Handler.class));
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        // Test different float scale values
-        float leadBrightness = 0.3f;
-        float followerBrightness = 0.4f;
-        float nits = 300;
-        when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(followerBrightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
-        listener.onBrightnessChanged(leadBrightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
-                anyFloat());
-
-        clearInvocations(mHolder.animator, followerDpc.animator);
-
-        // Test the same float scale value
-        float brightness = 0.6f;
-        nits = 600;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        float brightness = 0.3f;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
-                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        float brightness = 0.3f;
-        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        float brightness = 0.3f;
-        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
-                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_AutomaticBrightness() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        final float brightness = 0.4f;
-        final float nits = 300;
-        final float ambientLux = 3000;
-        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
-                .thenReturn(brightness);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness())
-                .thenReturn(0.3f);
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        DisplayPowerController2 followerDpc = mock(DisplayPowerController2.class);
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(followerDpc).setBrightnessToFollow(brightness, nits, ambientLux);
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
-        DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
-                FOLLOWER_UNIQUE_ID);
-        DisplayPowerControllerHolder secondFollowerDpc = createDisplayPowerController(
-                SECOND_FOLLOWER_DISPLAY_ID, SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        secondFollowerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        // Set the initial brightness on the DPC we're going to remove so we have a fixed value for
-        // it to return to.
-        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(followerDpc.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
-        final float initialFollowerBrightness = 0.3f;
-        when(followerDpc.brightnessSetting.getBrightness()).thenReturn(initialFollowerBrightness);
-        followerListener.onBrightnessChanged(initialFollowerBrightness);
-        advanceTime(1);
-        verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerDpc.dpc);
-        clearInvocations(followerDpc.animator);
-
-        // Validate both followers are correctly registered and receiving brightness updates
-        float brightness = 0.6f;
-        float nits = 600;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-
-        clearInvocations(mHolder.animator, followerDpc.animator, secondFollowerDpc.animator);
-
-        // Remove the first follower and validate it goes back to its original brightness.
-        mHolder.dpc.removeDisplayBrightnessFollower(followerDpc.dpc);
-        advanceTime(1);
-        verify(followerDpc.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        clearInvocations(followerDpc.animator);
-
-        // Change the brightness of the lead display and validate only the second follower responds
-        brightness = 0.7f;
-        nits = 700;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(secondFollowerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat());
-        verify(secondFollowerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
-        DisplayPowerControllerHolder followerHolder =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-        DisplayPowerControllerHolder secondFollowerHolder =
-                createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
-                        SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for
-        // it to return to.
-        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
-        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener secondFollowerListener =
-                listenerCaptor.getValue();
-        final float initialFollowerBrightness = 0.3f;
-        when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
-                initialFollowerBrightness);
-        when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn(
-                initialFollowerBrightness);
-        followerListener.onBrightnessChanged(initialFollowerBrightness);
-        secondFollowerListener.onBrightnessChanged(initialFollowerBrightness);
-        advanceTime(1);
-        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
-        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
-        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
-
-        // Validate both followers are correctly registered and receiving brightness updates
-        float brightness = 0.6f;
-        float nits = 600;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-
-        clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
-
-        // Stop the lead DPC and validate that the followers go back to their original brightness.
-        mHolder.dpc.stop();
-        advanceTime(1);
-        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
-    }
-
-    @Test
-    public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
-        // We should still set screen state for the default display
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
-
-        mHolder = createDisplayPowerController(42, UNIQUE_ID);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
-
-        mHolder.dpc.onBootCompleted();
-        advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState).setScreenState(anyInt());
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(true);
-
-        // The display turns on and we use the brightness value recommended by
-        // ScreenOffBrightnessSensorController
-        clearInvocations(mHolder.screenOffBrightnessSensorController);
-        float brightness = 0.14f;
-        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
-                .thenReturn(brightness);
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .getAutomaticScreenBrightness();
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(true);
-
-        // The display turns on and we use the brightness value recommended by
-        // ScreenOffBrightnessSensorController
-        clearInvocations(mHolder.screenOffBrightnessSensorController);
-        float brightness = 0.14f;
-        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
-                .thenReturn(brightness);
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .getAutomaticScreenBrightness();
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
-        // Tests are set up with manual brightness by default, so no need to set it here.
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsOn() {
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsAFollower() {
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, /* leadDisplayId= */ 42);
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
-        // New display device
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController).stop();
-    }
-
-    @Test
-    public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
-        float brightness = 0.3f;
-        float nits = 500;
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
-                true);
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-
-        mHolder.dpc.setBrightness(brightness);
-        verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
-
-        float newBrightness = 0.4f;
-        when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
-        when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(newBrightness);
-        // New display device
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-        // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
-        verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
-        float lux = 2000;
-        float brightness = 0.4f;
-        float nits = 500;
-        when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
-        when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
-        when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
-        when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1);
-        clearInvocations(mHolder.injector);
-
-        // New display device
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        advanceTime(1);
-
-        verify(mHolder.injector).getAutomaticBrightnessController(
-                any(AutomaticBrightnessController.Callbacks.class),
-                any(Looper.class),
-                eq(mSensorManagerMock),
-                any(),
-                eq(mHolder.brightnessMappingStrategy),
-                anyInt(),
-                anyFloat(),
-                anyFloat(),
-                anyFloat(),
-                anyInt(),
-                anyInt(),
-                anyLong(),
-                anyLong(),
-                anyBoolean(),
-                any(HysteresisLevels.class),
-                any(HysteresisLevels.class),
-                any(HysteresisLevels.class),
-                any(HysteresisLevels.class),
-                eq(mContext),
-                any(BrightnessRangeController.class),
-                any(BrightnessThrottler.class),
-                isNull(),
-                anyInt(),
-                anyInt(),
-                eq(lux),
-                eq(brightness)
-        );
-    }
-
-    @Test
-    public void testUpdateBrightnessThrottlingDataId() {
-        mHolder.display.getDisplayInfoLocked().thermalBrightnessThrottlingDataId =
-                "throttling-data-id";
-        clearInvocations(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig());
-
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig())
-                .getThermalBrightnessThrottlingDataMapByThrottlingId();
-    }
-
-    /**
-     * Creates a mock and registers it to {@link LocalServices}.
-     */
-    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
-        LocalServices.removeServiceForTest(clazz);
-        LocalServices.addService(clazz, mock);
-    }
-
-    private void advanceTime(long timeMs) {
-        mClock.fastForward(timeMs);
-        mTestLooper.dispatchAll();
-    }
-
-    private void setUpSensors() throws Exception {
-        mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
-                PROX_SENSOR_MAX_RANGE);
-        Sensor screenOffBrightnessSensor = TestUtils.createSensor(
-                Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
-        when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
-                .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
-    }
-
-    private SensorEventListener getSensorEventListener(Sensor sensor) {
-        verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
-                eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
-        return mSensorEventListenerCaptor.getValue();
-    }
-
-    private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
-            DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
-            boolean isEnabled) {
-        DisplayInfo info = new DisplayInfo();
-        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
-        deviceInfo.uniqueId = uniqueId;
-
-        when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
-        when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
-        when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
-        when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
-        when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
-        when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
-        when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
-        when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
-        when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_PROXIMITY;
-                        name = null;
-                    }
-                });
-        when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
-        when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
-        when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData());
-        when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_LIGHT;
-                        name = null;
-                    }
-                });
-        when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
-                .thenReturn(new int[0]);
-    }
-
-    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
-            String uniqueId) {
-        return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
-    }
-
-    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
-            String uniqueId, boolean isEnabled) {
-        final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
-        final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
-        final AutomaticBrightnessController automaticBrightnessController =
-                mock(AutomaticBrightnessController.class);
-        final WakelockController wakelockController = mock(WakelockController.class);
-        final BrightnessMappingStrategy brightnessMappingStrategy =
-                mock(BrightnessMappingStrategy.class);
-        final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
-        final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
-                mock(ScreenOffBrightnessSensorController.class);
-
-        TestInjector injector = spy(new TestInjector(displayPowerState, animator,
-                automaticBrightnessController, wakelockController, brightnessMappingStrategy,
-                hysteresisLevels, screenOffBrightnessSensorController));
-
-        final LogicalDisplay display = mock(LogicalDisplay.class);
-        final DisplayDevice device = mock(DisplayDevice.class);
-        final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
-        final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
-        final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
-
-        setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
-
-        final DisplayPowerController2 dpc = new DisplayPowerController2(
-                mContext, injector, mDisplayPowerCallbacksMock, mHandler,
-                mSensorManagerMock, mDisplayBlankerMock, display,
-                mBrightnessTrackerMock, brightnessSetting, () -> {},
-                hbmMetadata, /* bootCompleted= */ false);
-
-        return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
-                animator, automaticBrightnessController, wakelockController,
-                screenOffBrightnessSensorController, hbmMetadata, brightnessMappingStrategy,
-                injector);
-    }
-
-    /**
-     * A class for holding a DisplayPowerController under test and all the mocks specifically
-     * related to it.
-     */
-    private static class DisplayPowerControllerHolder {
-        public final DisplayPowerController2 dpc;
-        public final LogicalDisplay display;
-        public final DisplayPowerState displayPowerState;
-        public final BrightnessSetting brightnessSetting;
-        public final DualRampAnimator<DisplayPowerState> animator;
-        public final AutomaticBrightnessController automaticBrightnessController;
-        public final WakelockController wakelockController;
-        public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
-        public final HighBrightnessModeMetadata hbmMetadata;
-        public final BrightnessMappingStrategy brightnessMappingStrategy;
-        public final DisplayPowerController2.Injector injector;
-
-        DisplayPowerControllerHolder(DisplayPowerController2 dpc, LogicalDisplay display,
-                DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
-                DualRampAnimator<DisplayPowerState> animator,
-                AutomaticBrightnessController automaticBrightnessController,
-                WakelockController wakelockController,
-                ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
-                HighBrightnessModeMetadata hbmMetadata,
-                BrightnessMappingStrategy brightnessMappingStrategy,
-                DisplayPowerController2.Injector injector) {
-            this.dpc = dpc;
-            this.display = display;
-            this.displayPowerState = displayPowerState;
-            this.brightnessSetting = brightnessSetting;
-            this.animator = animator;
-            this.automaticBrightnessController = automaticBrightnessController;
-            this.wakelockController = wakelockController;
-            this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
-            this.hbmMetadata = hbmMetadata;
-            this.brightnessMappingStrategy = brightnessMappingStrategy;
-            this.injector = injector;
-        }
-    }
-
-    private class TestInjector extends DisplayPowerController2.Injector {
-        private final DisplayPowerState mDisplayPowerState;
-        private final DualRampAnimator<DisplayPowerState> mAnimator;
-        private final AutomaticBrightnessController mAutomaticBrightnessController;
-        private final WakelockController mWakelockController;
-        private final BrightnessMappingStrategy mBrightnessMappingStrategy;
-        private final HysteresisLevels mHysteresisLevels;
-        private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
-
-        TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
-                AutomaticBrightnessController automaticBrightnessController,
-                WakelockController wakelockController,
-                BrightnessMappingStrategy brightnessMappingStrategy,
-                HysteresisLevels hysteresisLevels,
-                ScreenOffBrightnessSensorController screenOffBrightnessSensorController) {
-            mDisplayPowerState = dps;
-            mAnimator = animator;
-            mAutomaticBrightnessController = automaticBrightnessController;
-            mWakelockController = wakelockController;
-            mBrightnessMappingStrategy = brightnessMappingStrategy;
-            mHysteresisLevels = hysteresisLevels;
-            mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
-        }
-
-        @Override
-        DisplayPowerController2.Clock getClock() {
-            return mClock::now;
-        }
-
-        @Override
-        DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
-                int displayId, int displayState) {
-            return mDisplayPowerState;
-        }
-
-        @Override
-        DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
-                FloatProperty<DisplayPowerState> firstProperty,
-                FloatProperty<DisplayPowerState> secondProperty) {
-            return mAnimator;
-        }
-
-        @Override
-        WakelockController getWakelockController(int displayId,
-                DisplayPowerCallbacks displayPowerCallbacks) {
-            return mWakelockController;
-        }
-
-        @Override
-        DisplayPowerProximityStateController getDisplayPowerProximityStateController(
-                WakelockController wakelockController, DisplayDeviceConfig displayDeviceConfig,
-                Looper looper, Runnable nudgeUpdatePowerState, int displayId,
-                SensorManager sensorManager) {
-            return new DisplayPowerProximityStateController(wakelockController,
-                    displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
-                    sensorManager,
-                    new DisplayPowerProximityStateController.Injector() {
-                        @Override
-                        DisplayPowerProximityStateController.Clock createClock() {
-                            return mClock::now;
-                        }
-                    });
-        }
-
-        @Override
-        AutomaticBrightnessController getAutomaticBrightnessController(
-                AutomaticBrightnessController.Callbacks callbacks, Looper looper,
-                SensorManager sensorManager, Sensor lightSensor,
-                BrightnessMappingStrategy interactiveModeBrightnessMapper,
-                int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
-                float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
-                long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
-                boolean resetAmbientLuxAfterWarmUpConfig,
-                HysteresisLevels ambientBrightnessThresholds,
-                HysteresisLevels screenBrightnessThresholds,
-                HysteresisLevels ambientBrightnessThresholdsIdle,
-                HysteresisLevels screenBrightnessThresholdsIdle, Context context,
-                BrightnessRangeController brightnessRangeController,
-                BrightnessThrottler brightnessThrottler,
-                BrightnessMappingStrategy idleModeBrightnessMapper,
-                int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
-                float userBrightness) {
-            return mAutomaticBrightnessController;
-        }
-
-        @Override
-        BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
-                DisplayDeviceConfig displayDeviceConfig,
-                DisplayWhiteBalanceController displayWhiteBalanceController) {
-            return mBrightnessMappingStrategy;
-        }
-
-        @Override
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold) {
-            return mHysteresisLevels;
-        }
-
-        @Override
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
-            return mHysteresisLevels;
-        }
-
-        @Override
-        ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
-                SensorManager sensorManager, Sensor lightSensor, Handler handler,
-                ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
-                BrightnessMappingStrategy brightnessMapper) {
-            return mScreenOffBrightnessSensorController;
-        }
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
deleted file mode 100644
index a93640b..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ /dev/null
@@ -1,1080 +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.server.display;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.Sensor;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.SystemProperties;
-import android.os.test.TestLooper;
-import android.provider.Settings;
-import android.testing.TestableContext;
-import android.util.FloatProperty;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.server.ExtendedMockitoRule;
-import com.android.server.LocalServices;
-import com.android.server.am.BatteryStatsService;
-import com.android.server.display.RampAnimator.DualRampAnimator;
-import com.android.server.display.brightness.BrightnessEvent;
-import com.android.server.display.color.ColorDisplayService;
-import com.android.server.display.layout.Layout;
-import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
-import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.testutils.OffsettableClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.quality.Strictness;
-import org.mockito.stubbing.Answer;
-
-import java.util.List;
-
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class DisplayPowerControllerTest {
-    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
-    private static final String UNIQUE_ID = "unique_id_test123";
-    private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1;
-    private static final String FOLLOWER_UNIQUE_ID = "unique_id_456";
-    private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
-    private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
-    private static final float PROX_SENSOR_MAX_RANGE = 5;
-
-    private OffsettableClock mClock;
-    private TestLooper mTestLooper;
-    private Handler mHandler;
-    private DisplayPowerControllerHolder mHolder;
-    private Sensor mProxSensor;
-
-    @Mock
-    private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
-    @Mock
-    private SensorManager mSensorManagerMock;
-    @Mock
-    private DisplayBlanker mDisplayBlankerMock;
-    @Mock
-    private BrightnessTracker mBrightnessTrackerMock;
-    @Mock
-    private WindowManagerPolicy mWindowManagerPolicyMock;
-    @Mock
-    private PowerManager mPowerManagerMock;
-    @Mock
-    private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
-
-    @Captor
-    private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
-
-    @Rule
-    public final TestableContext mContext = new TestableContext(
-            InstrumentationRegistry.getInstrumentation().getContext());
-
-    @Rule
-    public final ExtendedMockitoRule mExtendedMockitoRule =
-            new ExtendedMockitoRule.Builder(this)
-                    .setStrictness(Strictness.LENIENT)
-                    .spyStatic(SystemProperties.class)
-                    .spyStatic(BatteryStatsService.class)
-                    .build();
-
-    @Before
-    public void setUp() throws Exception {
-        mClock = new OffsettableClock.Stopped();
-        mTestLooper = new TestLooper(mClock::now);
-        mHandler = new Handler(mTestLooper.getLooper());
-
-        // Put the system into manual brightness by default, just to minimize unexpected events and
-        // have a consistent starting state
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
-
-
-        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
-        addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
-                mCdsiMock);
-
-        mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
-
-        doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
-                SystemProperties.set(anyString(), any()));
-        doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
-
-        setUpSensors();
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-    }
-
-    @After
-    public void tearDown() {
-        LocalServices.removeServiceForTest(WindowManagerPolicy.class);
-        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
-    }
-
-    @Test
-    public void testReleaseProxSuspendBlockersOnExit() throws Exception {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState to start listener for the prox sensor
-        advanceTime(1);
-
-        SensorEventListener listener = getSensorEventListener(mProxSensor);
-        assertNotNull(listener);
-
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 5));
-        advanceTime(1);
-
-        // two times, one for unfinished business and one for proximity
-        verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
-                mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
-        verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
-                mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
-
-        mHolder.dpc.stop();
-        advanceTime(1);
-
-        // two times, one for unfinished business and one for proximity
-        verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
-                mHolder.dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
-        verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
-                mHolder.dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
-    }
-
-    @Test
-    public void testScreenOffBecauseOfProximity() throws Exception {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState to start listener for the prox sensor
-        advanceTime(1);
-
-        SensorEventListener listener = getSensorEventListener(mProxSensor);
-        assertNotNull(listener);
-
-        // Send a positive proximity event
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
-        advanceTime(1);
-
-        // The display should have been turned off
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
-
-        clearInvocations(mHolder.displayPowerState);
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
-        // Send a negative proximity event
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor,
-                (int) PROX_SENSOR_MAX_RANGE + 1));
-        // Advance time by less than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
-        advanceTime(1);
-
-        // The prox sensor is debounced so the display should not have been turned back on yet
-        verify(mHolder.displayPowerState, never()).setScreenState(Display.STATE_ON);
-
-        // Advance time by more than PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY
-        advanceTime(1000);
-
-        // The display should have been turned back on
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
-    }
-
-    @Test
-    public void testScreenOffBecauseOfProximity_ProxSensorGone() throws Exception {
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // Send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        mHolder.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState to start listener for the prox sensor
-        advanceTime(1);
-
-        SensorEventListener listener = getSensorEventListener(mProxSensor);
-        assertNotNull(listener);
-
-        // Send a positive proximity event
-        listener.onSensorChanged(TestUtils.createSensorEvent(mProxSensor, /* value= */ 1));
-        advanceTime(1);
-
-        // The display should have been turned off
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_OFF);
-
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
-        // The display device changes and we no longer have a prox sensor
-        reset(mSensorManagerMock);
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-
-        advanceTime(1); // Run updatePowerState
-
-        // The display should have been turned back on and the listener should have been
-        // unregistered
-        verify(mHolder.displayPowerState).setScreenState(Display.STATE_ON);
-        verify(mSensorManagerMock).unregisterListener(listener);
-    }
-
-    @Test
-    public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        // send a display power request
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        dpr.useProximitySensor = true;
-        followerDpc.dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
-
-        // Run updatePowerState
-        advanceTime(1);
-
-        verify(mSensorManagerMock, never()).registerListener(any(SensorEventListener.class),
-                eq(mProxSensor), anyInt(), any(Handler.class));
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        // Test different float scale values
-        float leadBrightness = 0.3f;
-        float followerBrightness = 0.4f;
-        float nits = 300;
-        when(mHolder.automaticBrightnessController.convertToNits(leadBrightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(followerBrightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(leadBrightness);
-        listener.onBrightnessChanged(leadBrightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
-                anyFloat());
-
-        clearInvocations(mHolder.animator, followerDpc.animator);
-
-        // Test the same float scale value
-        float brightness = 0.6f;
-        nits = 600;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        float brightness = 0.3f;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(300f);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
-                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        float brightness = 0.3f;
-        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
-        DisplayPowerControllerHolder followerDpc =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerDpc.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc.dpc);
-
-        float brightness = 0.3f;
-        when(mHolder.automaticBrightnessController.convertToNits(anyFloat())).thenReturn(-1f);
-        when(followerDpc.automaticBrightnessController.convertToFloatScale(anyFloat()))
-                .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowers_AutomaticBrightness() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-        final float brightness = 0.4f;
-        final float nits = 300;
-        final float ambientLux = 3000;
-        when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness())
-                .thenReturn(brightness);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness())
-                .thenReturn(0.3f);
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        DisplayPowerController followerDpc = mock(DisplayPowerController.class);
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerDpc);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(followerDpc).setBrightnessToFollow(brightness, nits, ambientLux);
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
-        DisplayPowerControllerHolder followerHolder =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-        DisplayPowerControllerHolder secondFollowerHolder =
-                createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
-                        SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        // Set the initial brightness on the DPC we're going to remove so we have a fixed value for
-        // it to return to.
-        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
-        final float initialFollowerBrightness = 0.3f;
-        when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
-                initialFollowerBrightness);
-        followerListener.onBrightnessChanged(initialFollowerBrightness);
-        advanceTime(1);
-        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
-        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
-        clearInvocations(followerHolder.animator);
-
-        // Validate both followers are correctly registered and receiving brightness updates
-        float brightness = 0.6f;
-        float nits = 600;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-
-        clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
-
-        // Remove the first follower and validate it goes back to its original brightness.
-        mHolder.dpc.removeDisplayBrightnessFollower(followerHolder.dpc);
-        advanceTime(1);
-        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        clearInvocations(followerHolder.animator);
-
-        // Change the brightness of the lead display and validate only the second follower responds
-        brightness = 0.7f;
-        nits = 700;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerHolder.animator, never()).animateTo(anyFloat(), anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
-        DisplayPowerControllerHolder followerHolder =
-                createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
-        DisplayPowerControllerHolder secondFollowerHolder =
-                createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
-                        SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
-                ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
-
-        // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for
-        // it to return to.
-        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
-        listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
-        verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
-        BrightnessSetting.BrightnessSettingListener secondFollowerListener =
-                listenerCaptor.getValue();
-        final float initialFollowerBrightness = 0.3f;
-        when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
-                initialFollowerBrightness);
-        when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn(
-                initialFollowerBrightness);
-        followerListener.onBrightnessChanged(initialFollowerBrightness);
-        secondFollowerListener.onBrightnessChanged(initialFollowerBrightness);
-        advanceTime(1);
-        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-
-        mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
-        mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
-        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
-
-        // Validate both followers are correctly registered and receiving brightness updates
-        float brightness = 0.6f;
-        float nits = 600;
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(brightness);
-        when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
-        listener.onBrightnessChanged(brightness);
-        advanceTime(1); // Send messages, run updatePowerState
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-
-        clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
-
-        // Stop the lead DPC and validate that the followers go back to their original brightness.
-        mHolder.dpc.stop();
-        advanceTime(1);
-        verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness),
-                anyFloat(), anyFloat());
-        clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
-    }
-
-    @Test
-    public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
-        // We should still set screen state for the default display
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
-
-        mHolder = createDisplayPowerController(42, UNIQUE_ID);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
-
-        mHolder.dpc.onBootCompleted();
-        advanceTime(1); // Run updatePowerState
-        verify(mHolder.displayPowerState).setScreenState(anyInt());
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(true);
-
-        // The display turns on and we use the brightness value recommended by
-        // ScreenOffBrightnessSensorController
-        clearInvocations(mHolder.screenOffBrightnessSensorController);
-        float brightness = 0.14f;
-        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
-                .thenReturn(brightness);
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .getAutomaticScreenBrightness();
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(true);
-
-        // The display turns on and we use the brightness value recommended by
-        // ScreenOffBrightnessSensorController
-        clearInvocations(mHolder.screenOffBrightnessSensorController);
-        float brightness = 0.14f;
-        when(mHolder.screenOffBrightnessSensorController.getAutomaticScreenBrightness())
-                .thenReturn(brightness);
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
-        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
-                any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .getAutomaticScreenBrightness();
-        verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
-        // Tests are set up with manual brightness by default, so no need to set it here.
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
-
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsOn() {
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
-
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testSetScreenOffBrightnessSensorDisabled_DisplayIsAFollower() {
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        dpr.policy = DisplayPowerRequest.POLICY_OFF;
-
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, /* leadDisplayId= */ 42);
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController, atLeastOnce())
-                .setLightSensorEnabled(false);
-    }
-
-    @Test
-    public void testStopScreenOffBrightnessSensorControllerWhenDisplayDeviceChanges() {
-        // New display device
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.screenOffBrightnessSensorController).stop();
-    }
-
-    @Test
-    public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
-        float brightness = 0.3f;
-        float nits = 500;
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
-                true);
-
-        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
-        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-
-        mHolder.dpc.setBrightness(brightness);
-        verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
-
-        float newBrightness = 0.4f;
-        when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
-        when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
-                .thenReturn(newBrightness);
-        // New display device
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-        // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
-        verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
-    }
-
-    @Test
-    public void testShortTermModelPersistsWhenDisplayDeviceChanges() {
-        float lux = 2000;
-        float brightness = 0.4f;
-        float nits = 500;
-        when(mHolder.brightnessMappingStrategy.getUserLux()).thenReturn(lux);
-        when(mHolder.brightnessMappingStrategy.getUserBrightness()).thenReturn(brightness);
-        when(mHolder.brightnessMappingStrategy.convertToNits(brightness)).thenReturn(nits);
-        when(mHolder.brightnessMappingStrategy.convertToFloatScale(nits)).thenReturn(brightness);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1);
-        clearInvocations(mHolder.injector);
-
-        // New display device
-        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
-                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        advanceTime(1);
-
-        verify(mHolder.injector).getAutomaticBrightnessController(
-                any(AutomaticBrightnessController.Callbacks.class),
-                any(Looper.class),
-                eq(mSensorManagerMock),
-                any(),
-                eq(mHolder.brightnessMappingStrategy),
-                anyInt(),
-                anyFloat(),
-                anyFloat(),
-                anyFloat(),
-                anyInt(),
-                anyInt(),
-                anyLong(),
-                anyLong(),
-                anyBoolean(),
-                any(HysteresisLevels.class),
-                any(HysteresisLevels.class),
-                any(HysteresisLevels.class),
-                any(HysteresisLevels.class),
-                eq(mContext),
-                any(BrightnessRangeController.class),
-                any(BrightnessThrottler.class),
-                isNull(),
-                anyInt(),
-                anyInt(),
-                eq(lux),
-                eq(brightness)
-        );
-    }
-
-    @Test
-    public void testUpdateBrightnessThrottlingDataId() {
-        mHolder.display.getDisplayInfoLocked().thermalBrightnessThrottlingDataId =
-                "throttling-data-id";
-        clearInvocations(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig());
-
-        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
-        DisplayPowerRequest dpr = new DisplayPowerRequest();
-        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1); // Run updatePowerState
-
-        verify(mHolder.display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig())
-                .getThermalBrightnessThrottlingDataMapByThrottlingId();
-    }
-
-    /**
-     * Creates a mock and registers it to {@link LocalServices}.
-     */
-    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
-        LocalServices.removeServiceForTest(clazz);
-        LocalServices.addService(clazz, mock);
-    }
-
-    private void advanceTime(long timeMs) {
-        mClock.fastForward(timeMs);
-        mTestLooper.dispatchAll();
-    }
-
-    private void setUpSensors() throws Exception {
-        mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY,
-                PROX_SENSOR_MAX_RANGE);
-        Sensor screenOffBrightnessSensor = TestUtils.createSensor(
-                Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
-        when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
-                .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor));
-    }
-
-    private SensorEventListener getSensorEventListener(Sensor sensor) {
-        verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
-                eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
-        return mSensorEventListenerCaptor.getValue();
-    }
-
-    private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock,
-            DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock,
-            boolean isEnabled) {
-        DisplayInfo info = new DisplayInfo();
-        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
-        deviceInfo.uniqueId = uniqueId;
-
-        when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
-        when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock);
-        when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
-        when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled);
-        when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false);
-        when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
-        when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
-        when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
-        when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_PROXIMITY;
-                        name = null;
-                    }
-                });
-        when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
-        when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
-        when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData());
-        when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_LIGHT;
-                        name = null;
-                    }
-                });
-        when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
-                .thenReturn(new int[0]);
-    }
-
-    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
-            String uniqueId) {
-        return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
-    }
-
-    private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
-            String uniqueId, boolean isEnabled) {
-        final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
-        final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
-        final AutomaticBrightnessController automaticBrightnessController =
-                mock(AutomaticBrightnessController.class);
-        final BrightnessMappingStrategy brightnessMappingStrategy =
-                mock(BrightnessMappingStrategy.class);
-        final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
-        final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
-                mock(ScreenOffBrightnessSensorController.class);
-
-        DisplayPowerController.Injector injector = spy(new TestInjector(displayPowerState, animator,
-                automaticBrightnessController, brightnessMappingStrategy, hysteresisLevels,
-                screenOffBrightnessSensorController));
-
-        final LogicalDisplay display = mock(LogicalDisplay.class);
-        final DisplayDevice device = mock(DisplayDevice.class);
-        final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
-        final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
-        final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
-
-        setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
-
-        final DisplayPowerController dpc = new DisplayPowerController(
-                mContext, injector, mDisplayPowerCallbacksMock, mHandler,
-                mSensorManagerMock, mDisplayBlankerMock, display,
-                mBrightnessTrackerMock, brightnessSetting, () -> {},
-                hbmMetadata, /* bootCompleted= */ false);
-
-        return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
-                animator, automaticBrightnessController, screenOffBrightnessSensorController,
-                hbmMetadata, brightnessMappingStrategy, injector);
-    }
-
-    /**
-     * A class for holding a DisplayPowerController under test and all the mocks specifically
-     * related to it.
-     */
-    private static class DisplayPowerControllerHolder {
-        public final DisplayPowerController dpc;
-        public final LogicalDisplay display;
-        public final DisplayPowerState displayPowerState;
-        public final BrightnessSetting brightnessSetting;
-        public final DualRampAnimator<DisplayPowerState> animator;
-        public final AutomaticBrightnessController automaticBrightnessController;
-        public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
-        public final HighBrightnessModeMetadata hbmMetadata;
-        public final BrightnessMappingStrategy brightnessMappingStrategy;
-        public final DisplayPowerController.Injector injector;
-
-        DisplayPowerControllerHolder(DisplayPowerController dpc, LogicalDisplay display,
-                DisplayPowerState displayPowerState, BrightnessSetting brightnessSetting,
-                DualRampAnimator<DisplayPowerState> animator,
-                AutomaticBrightnessController automaticBrightnessController,
-                ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
-                HighBrightnessModeMetadata hbmMetadata,
-                BrightnessMappingStrategy brightnessMappingStrategy,
-                DisplayPowerController.Injector injector) {
-            this.dpc = dpc;
-            this.display = display;
-            this.displayPowerState = displayPowerState;
-            this.brightnessSetting = brightnessSetting;
-            this.animator = animator;
-            this.automaticBrightnessController = automaticBrightnessController;
-            this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
-            this.hbmMetadata = hbmMetadata;
-            this.brightnessMappingStrategy = brightnessMappingStrategy;
-            this.injector = injector;
-        }
-    }
-
-    private class TestInjector extends DisplayPowerController.Injector {
-        private final DisplayPowerState mDisplayPowerState;
-        private final DualRampAnimator<DisplayPowerState> mAnimator;
-        private final AutomaticBrightnessController mAutomaticBrightnessController;
-        private final BrightnessMappingStrategy mBrightnessMappingStrategy;
-        private final HysteresisLevels mHysteresisLevels;
-        private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
-
-        TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
-                AutomaticBrightnessController automaticBrightnessController,
-                BrightnessMappingStrategy brightnessMappingStrategy,
-                HysteresisLevels hysteresisLevels,
-                ScreenOffBrightnessSensorController screenOffBrightnessSensorController) {
-            mDisplayPowerState = dps;
-            mAnimator = animator;
-            mAutomaticBrightnessController = automaticBrightnessController;
-            mBrightnessMappingStrategy = brightnessMappingStrategy;
-            mHysteresisLevels = hysteresisLevels;
-            mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
-        }
-
-        @Override
-        DisplayPowerController.Clock getClock() {
-            return mClock::now;
-        }
-
-        @Override
-        DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
-                int displayId, int displayState) {
-            return mDisplayPowerState;
-        }
-
-        @Override
-        DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
-                FloatProperty<DisplayPowerState> firstProperty,
-                FloatProperty<DisplayPowerState> secondProperty) {
-            return mAnimator;
-        }
-
-        @Override
-        AutomaticBrightnessController getAutomaticBrightnessController(
-                AutomaticBrightnessController.Callbacks callbacks, Looper looper,
-                SensorManager sensorManager, Sensor lightSensor,
-                BrightnessMappingStrategy interactiveModeBrightnessMapper,
-                int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
-                float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
-                long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
-                boolean resetAmbientLuxAfterWarmUpConfig,
-                HysteresisLevels ambientBrightnessThresholds,
-                HysteresisLevels screenBrightnessThresholds,
-                HysteresisLevels ambientBrightnessThresholdsIdle,
-                HysteresisLevels screenBrightnessThresholdsIdle, Context context,
-                BrightnessRangeController brightnessRangeController,
-                BrightnessThrottler brightnessThrottler,
-                BrightnessMappingStrategy idleModeBrightnessMapper,
-                int ambientLightHorizonShort, int ambientLightHorizonLong, float userLux,
-                float userBrightness) {
-            return mAutomaticBrightnessController;
-        }
-
-        @Override
-        BrightnessMappingStrategy getInteractiveModeBrightnessMapper(Resources resources,
-                DisplayDeviceConfig displayDeviceConfig,
-                DisplayWhiteBalanceController displayWhiteBalanceController) {
-            return mBrightnessMappingStrategy;
-        }
-
-        @Override
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold) {
-            return mHysteresisLevels;
-        }
-
-        @Override
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
-            return mHysteresisLevels;
-        }
-
-        @Override
-        ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
-                SensorManager sensorManager, Sensor lightSensor, Handler handler,
-                ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
-                BrightnessMappingStrategy brightnessMapper) {
-            return mScreenOffBrightnessSensorController;
-        }
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
deleted file mode 100644
index 5b0b989..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
+++ /dev/null
@@ -1,402 +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.server.display;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.hardware.Sensor;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.hardware.display.DisplayManagerInternal;
-import android.os.test.TestLooper;
-import android.view.Display;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.testutils.OffsettableClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public final class DisplayPowerProximityStateControllerTest {
-    @Mock
-    WakelockController mWakelockController;
-
-    @Mock
-    DisplayDeviceConfig mDisplayDeviceConfig;
-
-    @Mock
-    Runnable mNudgeUpdatePowerState;
-
-    @Mock
-    SensorManager mSensorManager;
-
-    private Sensor mProximitySensor;
-    private OffsettableClock mClock;
-    private TestLooper mTestLooper;
-    private SensorEventListener mSensorEventListener;
-    private DisplayPowerProximityStateController mDisplayPowerProximityStateController;
-
-    @Before
-    public void before() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mClock = new OffsettableClock.Stopped();
-        mTestLooper = new TestLooper(mClock::now);
-        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_PROXIMITY;
-                        // This is kept null because currently there is no way to define a sensor
-                        // name in TestUtils
-                        name = null;
-                    }
-                });
-        setUpProxSensor();
-        DisplayPowerProximityStateController.Injector injector =
-                new DisplayPowerProximityStateController.Injector() {
-                    @Override
-                    DisplayPowerProximityStateController.Clock createClock() {
-                        return mClock::now;
-                    }
-                };
-        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
-                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
-                mNudgeUpdatePowerState, 0,
-                mSensorManager, injector);
-        mSensorEventListener = mDisplayPowerProximityStateController.getProximitySensorListener();
-    }
-
-    @Test
-    public void updatePendingProximityRequestsWorksAsExpectedWhenPending() {
-        // Set the system to pending wait for proximity
-        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
-                true));
-        assertTrue(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-
-        // Update the pending proximity wait request
-        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
-        assertTrue(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
-        assertFalse(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-    }
-
-    @Test
-    public void updatePendingProximityRequestsWorksAsExpectedWhenNotPending() {
-        // Will not wait or be in the pending wait state of not already pending
-        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
-        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
-        assertFalse(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-    }
-
-    @Test
-    public void updatePendingProximityRequestsWorksAsExpectedWhenPendingAndProximityIgnored()
-            throws Exception {
-        // Set the system to the state where it will ignore proximity unless changed
-        enableProximitySensor();
-        emitAndValidatePositiveProximityEvent();
-        mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
-        advanceTime(1);
-        assertTrue(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
-        verify(mNudgeUpdatePowerState, times(2)).run();
-
-        // Do not set the system to pending wait for proximity
-        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
-        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
-        assertFalse(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-
-        // Set the system to pending wait for proximity. But because the proximity is being
-        // ignored, it will not wait or not set the pending wait
-        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
-                true));
-        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
-        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
-        assertFalse(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-    }
-
-    @Test
-    public void cleanupDisablesTheProximitySensor() {
-        enableProximitySensor();
-        mDisplayPowerProximityStateController.cleanup();
-        verify(mSensorManager).unregisterListener(
-                mSensorEventListener);
-        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
-        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
-        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
-        assertEquals(mDisplayPowerProximityStateController.getProximity(),
-                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
-        when(mWakelockController.releaseWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
-        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
-    }
-
-    @Test
-    public void isProximitySensorAvailableReturnsTrueWhenAvailable() {
-        assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
-    }
-
-    @Test
-    public void isProximitySensorAvailableReturnsFalseWhenNotAvailable() {
-        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = null;
-                        name = null;
-                    }
-                });
-        mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
-                mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
-                mNudgeUpdatePowerState, 1,
-                mSensorManager, null);
-        assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
-    }
-
-    @Test
-    public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception {
-        DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class);
-        when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_PROXIMITY;
-                        name = null;
-                    }
-                });
-        Sensor newProxSensor = TestUtils.createSensor(
-                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f);
-        when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
-                .thenReturn(List.of(newProxSensor));
-        mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(
-                updatedDisplayDeviceConfig);
-        assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
-    }
-
-    @Test
-    public void setPendingWaitForNegativeProximityLockedWorksAsExpected() {
-        // Doesn't do anything not asked to wait
-        assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
-                false));
-        assertFalse(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-
-        // Sets pending wait negative proximity if not already waiting
-        assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
-                true));
-        assertTrue(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-
-        // Will not set pending wait negative proximity if already waiting
-        assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
-                true));
-        assertTrue(
-                mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
-
-    }
-
-    @Test
-    public void evaluateProximityStateWhenRequestedUseOfProximitySensor() throws Exception {
-        // Enable the proximity sensor
-        enableProximitySensor();
-
-        // Emit a positive proximity event to move the system to a state to mimic a scenario
-        // where the system is in positive proximity
-        emitAndValidatePositiveProximityEvent();
-
-        // Again evaluate the proximity state, with system having positive proximity
-        setScreenOffBecauseOfPositiveProximityState();
-    }
-
-    @Test
-    public void evaluateProximityStateWhenScreenOffBecauseOfPositiveProximity() throws Exception {
-        // Enable the proximity sensor
-        enableProximitySensor();
-
-        // Emit a positive proximity event to move the system to a state to mimic a scenario
-        // where the system is in positive proximity
-        emitAndValidatePositiveProximityEvent();
-
-        // Again evaluate the proximity state, with system having positive proximity
-        setScreenOffBecauseOfPositiveProximityState();
-
-        // Set the system to pending wait for proximity
-        mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(true);
-        // Update the pending proximity wait request
-        mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
-
-        // Start ignoring proximity sensor
-        mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
-        // Re-evaluate the proximity state, such that the system is detecting the positive
-        // proximity, and screen is off because of that
-        when(mWakelockController.getOnProximityNegativeRunnable()).thenReturn(mock(Runnable.class));
-        mDisplayPowerProximityStateController.updateProximityState(mock(
-                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
-        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
-        assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
-        assertTrue(
-                mDisplayPowerProximityStateController
-                        .shouldSkipRampBecauseOfProximityChangeToNegative());
-        verify(mWakelockController).acquireWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
-    }
-
-    @Test
-    public void evaluateProximityStateWhenDisplayIsTurningOff() throws Exception {
-        // Enable the proximity sensor
-        enableProximitySensor();
-
-        // Emit a positive proximity event to move the system to a state to mimic a scenario
-        // where the system is in positive proximity
-        emitAndValidatePositiveProximityEvent();
-
-        // Again evaluate the proximity state, with system having positive proximity
-        setScreenOffBecauseOfPositiveProximityState();
-
-        // Re-evaluate the proximity state, such that the system is detecting the positive
-        // proximity, and screen is off because of that
-        mDisplayPowerProximityStateController.updateProximityState(mock(
-                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_OFF);
-        verify(mSensorManager).unregisterListener(
-                mSensorEventListener);
-        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
-        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
-        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
-        assertEquals(mDisplayPowerProximityStateController.getProximity(),
-                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
-        when(mWakelockController.releaseWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
-        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
-    }
-
-    @Test
-    public void evaluateProximityStateNotWaitingForNegativeProximityAndNotUsingProxSensor()
-            throws Exception {
-        // Enable the proximity sensor
-        enableProximitySensor();
-
-        // Emit a positive proximity event to move the system to a state to mimic a scenario
-        // where the system is in positive proximity
-        emitAndValidatePositiveProximityEvent();
-
-        // Re-evaluate the proximity state, such that the system is detecting the positive
-        // proximity, and screen is off because of that
-        mDisplayPowerProximityStateController.updateProximityState(mock(
-                DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
-        verify(mSensorManager).unregisterListener(
-                mSensorEventListener);
-        assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
-        assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
-        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
-        assertEquals(mDisplayPowerProximityStateController.getProximity(),
-                DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
-        when(mWakelockController.releaseWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
-        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
-    }
-
-    private void advanceTime(long timeMs) {
-        mClock.fastForward(timeMs);
-        mTestLooper.dispatchAll();
-    }
-
-    private void setUpProxSensor() throws Exception {
-        mProximitySensor = TestUtils.createSensor(
-                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 5.0f);
-        when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
-                .thenReturn(List.of(mProximitySensor));
-    }
-
-    private void emitAndValidatePositiveProximityEvent() throws Exception {
-        // Emit a positive proximity event to move the system to a state to mimic a scenario
-        // where the system is in positive proximity
-        when(mWakelockController.releaseWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
-        mSensorEventListener.onSensorChanged(TestUtils.createSensorEvent(mProximitySensor, 4));
-        verify(mSensorManager).registerListener(mSensorEventListener,
-                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
-                mDisplayPowerProximityStateController.getHandler());
-        verify(mWakelockController).acquireWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
-        assertEquals(mDisplayPowerProximityStateController.getPendingProximity(),
-                DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
-        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
-        assertEquals(mDisplayPowerProximityStateController.getProximity(),
-                DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
-        verify(mNudgeUpdatePowerState).run();
-        assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
-    }
-
-    // Call evaluateProximityState with the request for using the proximity sensor. This will
-    // register the proximity sensor listener, which will be needed for mocking positive
-    // proximity scenarios.
-    private void enableProximitySensor() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        displayPowerRequest.useProximitySensor = true;
-        mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
-                Display.STATE_ON);
-        verify(mSensorManager).registerListener(
-                mSensorEventListener,
-                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
-                mDisplayPowerProximityStateController.getHandler());
-        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
-        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
-        assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
-        verifyZeroInteractions(mWakelockController);
-    }
-
-    private void setScreenOffBecauseOfPositiveProximityState() {
-        // Prepare a request to indicate that the proximity sensor is to be used
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        displayPowerRequest.useProximitySensor = true;
-
-        Runnable onProximityPositiveRunnable = mock(Runnable.class);
-        when(mWakelockController.getOnProximityPositiveRunnable()).thenReturn(
-                onProximityPositiveRunnable);
-
-        mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
-                Display.STATE_ON);
-        verify(mSensorManager).registerListener(
-                mSensorEventListener,
-                mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
-                mDisplayPowerProximityStateController.getHandler());
-        assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
-        assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
-        assertTrue(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
-        verify(mWakelockController).acquireWakelock(
-                WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java b/services/tests/mockingservicestests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
deleted file mode 100644
index d9fbba5..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import org.junit.Before;
-import org.junit.Test;
-
-public class HighBrightnessModeMetadataMapperTest {
-
-    private HighBrightnessModeMetadataMapper mHighBrightnessModeMetadataMapper;
-
-    @Before
-    public void setUp() {
-        mHighBrightnessModeMetadataMapper = new HighBrightnessModeMetadataMapper();
-    }
-
-    @Test
-    public void testGetHighBrightnessModeMetadata() {
-        // Display device is null
-        final LogicalDisplay display = mock(LogicalDisplay.class);
-        when(display.getPrimaryDisplayDeviceLocked()).thenReturn(null);
-        assertNull(mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(display));
-
-        // No HBM metadata stored for this display yet
-        final DisplayDevice device = mock(DisplayDevice.class);
-        when(display.getPrimaryDisplayDeviceLocked()).thenReturn(device);
-        HighBrightnessModeMetadata hbmMetadata =
-                mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(display);
-        assertTrue(hbmMetadata.getHbmEventQueue().isEmpty());
-        assertTrue(hbmMetadata.getRunningStartTimeMillis() < 0);
-
-        // Modify the metadata
-        long startTimeMillis = 100;
-        long endTimeMillis = 200;
-        long setTime = 300;
-        hbmMetadata.addHbmEvent(new HbmEvent(startTimeMillis, endTimeMillis));
-        hbmMetadata.setRunningStartTimeMillis(setTime);
-        hbmMetadata =
-                mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(display);
-        assertEquals(1, hbmMetadata.getHbmEventQueue().size());
-        assertEquals(startTimeMillis,
-                hbmMetadata.getHbmEventQueue().getFirst().getStartTimeMillis());
-        assertEquals(endTimeMillis, hbmMetadata.getHbmEventQueue().getFirst().getEndTimeMillis());
-        assertEquals(setTime, hbmMetadata.getRunningStartTimeMillis());
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
deleted file mode 100644
index f89f73c9..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ /dev/null
@@ -1,1306 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Rect;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.view.Display;
-import android.view.DisplayAddress;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.RefreshRateRange;
-import android.view.SurfaceControl.RefreshRateRanges;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-import com.android.internal.R;
-import com.android.server.LocalServices;
-import com.android.server.display.LocalDisplayAdapter.BacklightAdapter;
-import com.android.server.display.mode.DisplayModeDirector;
-import com.android.server.lights.LightsManager;
-import com.android.server.lights.LogicalLight;
-
-import com.google.common.truth.Truth;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.quality.Strictness;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class LocalDisplayAdapterTest {
-    private static final Long DISPLAY_MODEL = Long.valueOf(0xAAAAAAAAL);
-    private static final int PORT_A = 0;
-    private static final int PORT_B = 0x80;
-    private static final int PORT_C = 0xFF;
-    private static final float REFRESH_RATE = 60f;
-    private static final RefreshRateRange REFRESH_RATE_RANGE =
-            new RefreshRateRange(REFRESH_RATE, REFRESH_RATE);
-    private static final RefreshRateRanges REFRESH_RATE_RANGES =
-            new RefreshRateRanges(REFRESH_RATE_RANGE, REFRESH_RATE_RANGE);
-
-    private static final long HANDLER_WAIT_MS = 100;
-
-    private static final int[] HDR_TYPES = new int[]{1, 2};
-
-    private StaticMockitoSession mMockitoSession;
-
-    private LocalDisplayAdapter mAdapter;
-
-    @Mock
-    private DisplayManagerService.SyncRoot mMockedSyncRoot;
-    @Mock
-    private Context mMockedContext;
-    @Mock
-    private Resources mMockedResources;
-    @Mock
-    private LightsManager mMockedLightsManager;
-    @Mock
-    private LogicalLight mMockedBacklight;
-
-    private Handler mHandler;
-
-    private TestListener mListener = new TestListener();
-
-    private LinkedList<DisplayAddress.Physical> mAddresses = new LinkedList<>();
-
-    private Injector mInjector;
-
-    @Mock
-    private LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy;
-    private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
-    private static final int[] BACKLIGHT_RANGE = { 1, 255 };
-    private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f };
-
-    @Before
-    public void setUp() throws Exception {
-        mMockitoSession = mockitoSession()
-                .initMocks(this)
-                .strictness(Strictness.LENIENT)
-                .startMocking();
-        mHandler = new Handler(Looper.getMainLooper());
-        doReturn(mMockedResources).when(mMockedContext).getResources();
-        LocalServices.removeServiceForTest(LightsManager.class);
-        LocalServices.addService(LightsManager.class, mMockedLightsManager);
-        mInjector = new Injector();
-        when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true);
-        mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler,
-                mListener, mInjector);
-        spyOn(mAdapter);
-        doReturn(mMockedContext).when(mAdapter).getOverlayContext();
-
-        TypedArray mockNitsRange = createFloatTypedArray(DISPLAY_RANGE_NITS);
-        when(mMockedResources.obtainTypedArray(R.array.config_screenBrightnessNits))
-                .thenReturn(mockNitsRange);
-        when(mMockedResources.getIntArray(R.array.config_screenBrightnessBacklight))
-                .thenReturn(BACKLIGHT_RANGE);
-        when(mMockedResources.getFloat(com.android.internal.R.dimen
-                .config_screenBrightnessSettingMinimumFloat))
-                .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[0]);
-        when(mMockedResources.getFloat(com.android.internal.R.dimen
-                .config_screenBrightnessSettingMaximumFloat))
-                .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[1]);
-        when(mMockedResources.getStringArray(R.array.config_displayUniqueIdArray))
-                .thenReturn(new String[]{});
-        TypedArray mockArray = mock(TypedArray.class);
-        when(mockArray.length()).thenReturn(0);
-        when(mMockedResources.obtainTypedArray(R.array.config_maskBuiltInDisplayCutoutArray))
-                .thenReturn(mockArray);
-        when(mMockedResources.obtainTypedArray(R.array.config_waterfallCutoutArray))
-                .thenReturn(mockArray);
-        when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerRadiusArray))
-                .thenReturn(mockArray);
-        when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerTopRadiusArray))
-                .thenReturn(mockArray);
-        when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerBottomRadiusArray))
-                .thenReturn(mockArray);
-        when(mMockedResources.obtainTypedArray(
-                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
-                .thenReturn(mockArray);
-        when(mMockedResources.getIntArray(
-                com.android.internal.R.array.config_autoBrightnessLevels))
-                .thenReturn(new int[]{});
-        when(mMockedResources.obtainTypedArray(R.array.config_displayShapeArray))
-                .thenReturn(mockArray);
-        when(mMockedResources.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray))
-                .thenReturn(mockArray);
-        when(mMockedResources.getIntArray(
-            com.android.internal.R.array.config_brightnessThresholdsOfPeakRefreshRate))
-            .thenReturn(new int[]{});
-        when(mMockedResources.getIntArray(
-            com.android.internal.R.array.config_ambientThresholdsOfPeakRefreshRate))
-            .thenReturn(new int[]{});
-        when(mMockedResources.getIntArray(
-            com.android.internal.R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
-            .thenReturn(new int[]{});
-        when(mMockedResources.getIntArray(
-            com.android.internal.R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
-            .thenReturn(new int[]{});
-    }
-
-    @After
-    public void tearDown() {
-        if (mMockitoSession != null) {
-            mMockitoSession.finishMocking();
-        }
-    }
-
-    /**
-     * Confirm that display is marked as private when it is listed in
-     * com.android.internal.R.array.config_localPrivateDisplayPorts.
-     */
-    @Test
-    public void testPrivateDisplay() throws Exception {
-        setUpDisplay(new FakeDisplay(PORT_A));
-        setUpDisplay(new FakeDisplay(PORT_B));
-        setUpDisplay(new FakeDisplay(PORT_C));
-        updateAvailableDisplays();
-
-        doReturn(new int[]{ PORT_B }).when(mMockedResources)
-                .getIntArray(com.android.internal.R.array.config_localPrivateDisplayPorts);
-        mAdapter.registerLocked();
-
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        // This should be public
-        assertDisplayPrivateFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(),
-                PORT_A, false);
-        // This should be private
-        assertDisplayPrivateFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(),
-                PORT_B, true);
-        // This should be public
-        assertDisplayPrivateFlag(mListener.addedDisplays.get(2).getDisplayDeviceInfoLocked(),
-                PORT_C, false);
-    }
-
-    @Test
-    public void testSupportedDisplayModesGetOverriddenWhenDisplayIsUpdated()
-            throws InterruptedException {
-        SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 0);
-        displayMode.supportedHdrTypes = new int[0];
-        FakeDisplay display = new FakeDisplay(PORT_A, new SurfaceControl.DisplayMode[]{displayMode},
-                0, 0);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        Display.Mode[] supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
-        Assert.assertEquals(1, supportedModes.length);
-        Assert.assertEquals(0, supportedModes[0].getSupportedHdrTypes().length);
-
-        displayMode.supportedHdrTypes = new int[]{3, 2};
-        display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{displayMode};
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
-
-        Assert.assertEquals(1, supportedModes.length);
-        assertArrayEquals(new int[]{2, 3}, supportedModes[0].getSupportedHdrTypes());
-    }
-
-    /**
-     * Confirm that all local displays are public when config_localPrivateDisplayPorts is empty.
-     */
-    @Test
-    public void testPublicDisplaysForNoConfigLocalPrivateDisplayPorts() throws Exception {
-        setUpDisplay(new FakeDisplay(PORT_A));
-        setUpDisplay(new FakeDisplay(PORT_C));
-        updateAvailableDisplays();
-
-        // config_localPrivateDisplayPorts is null
-        mAdapter.registerLocked();
-
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        // This should be public
-        assertDisplayPrivateFlag(mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(),
-                PORT_A, false);
-        // This should be public
-        assertDisplayPrivateFlag(mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(),
-                PORT_C, false);
-    }
-
-    private static void assertDisplayPrivateFlag(
-            DisplayDeviceInfo info, int expectedPort, boolean shouldBePrivate) {
-        final DisplayAddress.Physical address = (DisplayAddress.Physical) info.address;
-        assertNotNull(address);
-        assertEquals(expectedPort, address.getPort());
-        assertEquals(DISPLAY_MODEL, address.getModel());
-        assertEquals(shouldBePrivate, (info.flags & DisplayDeviceInfo.FLAG_PRIVATE) != 0);
-    }
-
-    /**
-     * Confirm that external display uses physical density.
-     */
-    @Test
-    public void testDpiValues() throws Exception {
-        // needs default one always
-        setUpDisplay(new FakeDisplay(PORT_A));
-        setUpDisplay(new FakeDisplay(PORT_B));
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertDisplayDpi(
-                mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked(), PORT_A, 100, 100,
-                16000);
-        assertDisplayDpi(
-                mListener.addedDisplays.get(1).getDisplayDeviceInfoLocked(), PORT_B, 100, 100,
-                16000);
-    }
-
-    private static class DisplayModeWrapper {
-        public SurfaceControl.DisplayMode mode;
-        public float[] expectedAlternativeRefreshRates;
-
-        DisplayModeWrapper(SurfaceControl.DisplayMode mode,
-                float[] expectedAlternativeRefreshRates) {
-            this.mode = mode;
-            this.expectedAlternativeRefreshRates = expectedAlternativeRefreshRates;
-        }
-    }
-
-    /**
-     * Updates the <code>display</code> using the given <code>modes</code> and then checks if the
-     * <code>expectedAlternativeRefreshRates</code> are present for each of the
-     * <code>modes</code>.
-     */
-    private void testAlternativeRefreshRatesCommon(FakeDisplay display,
-                DisplayModeWrapper[] wrappedModes)
-            throws InterruptedException {
-        // Update the display.
-        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[wrappedModes.length];
-        for (int i = 0; i < wrappedModes.length; i++) {
-            modes[i] = wrappedModes[i].mode;
-        }
-        display.dynamicInfo.supportedDisplayModes = modes;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.changedDisplays.size()).isGreaterThan(0);
-
-        // Verify the supported modes are updated accordingly.
-        DisplayDevice displayDevice =
-                mListener.changedDisplays.get(mListener.changedDisplays.size() - 1);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        Display.Mode[] supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
-        assertThat(supportedModes.length).isEqualTo(modes.length);
-
-        for (int i = 0; i < wrappedModes.length; i++) {
-            assertModeIsSupported(supportedModes, modes[i],
-                    wrappedModes[i].expectedAlternativeRefreshRates);
-        }
-    }
-
-    @Test
-    public void testAfterDisplayChange_AlternativeRefreshRatesAreUpdated() throws Exception {
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[]{24f, 50f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(1, 1920, 1080, 50f, 0), new float[]{24f, 60f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(2, 1920, 1080, 24f, 0), new float[]{50f, 60f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(3, 3840, 2160, 60f, 0), new float[]{24f, 50f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(4, 3840, 2160, 50f, 0), new float[]{24f, 60f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(5, 3840, 2160, 24f, 0), new float[]{50f, 60f}),
-        });
-
-        testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[]{50f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(1, 1920, 1080, 50f, 0), new float[]{60f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(2, 1920, 1080, 24f, 1), new float[0]),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(3, 3840, 2160, 60f, 2), new float[0]),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(4, 3840, 2160, 50f, 3), new float[]{24f}),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(5, 3840, 2160, 24f, 3), new float[]{50f}),
-        });
-
-        testAlternativeRefreshRatesCommon(display, new DisplayModeWrapper[] {
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(0, 1920, 1080, 60f, 0), new float[0]),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(1, 1920, 1080, 50f, 1), new float[0]),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(2, 1920, 1080, 24f, 2), new float[0]),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(3, 3840, 2160, 60f, 3), new float[0]),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(4, 3840, 2160, 50f, 4), new float[0]),
-                new DisplayModeWrapper(
-                        createFakeDisplayMode(5, 3840, 2160, 24f, 5), new float[0]),
-        });
-    }
-
-    @Test
-    public void testAfterDisplayChange_DefaultDisplayModeIsUpdated() throws Exception {
-        SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
-        SurfaceControl.DisplayMode[] modes =
-                new SurfaceControl.DisplayMode[]{displayMode};
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
-                0).getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
-
-        Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, displayMode)).isTrue();
-
-        // Set the display mode to an unsupported mode
-        SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 120f);
-        mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
-                new Display.Mode(displayMode2.width, displayMode2.height,
-                        displayMode2.refreshRate));
-        updateAvailableDisplays();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, displayMode2)).isFalse();
-
-        // Change the display
-        modes = new SurfaceControl.DisplayMode[]{displayMode, displayMode2};
-        display.dynamicInfo.supportedDisplayModes = modes;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = mListener.addedDisplays.get(0).getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2);
-
-        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, displayMode2)).isTrue();
-    }
-
-    @Test
-    public void testAfterDisplayChange_DisplayModesAreUpdated() throws Exception {
-        SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
-        SurfaceControl.DisplayMode[] modes =
-                new SurfaceControl.DisplayMode[]{displayMode};
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
-                0).getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
-
-        Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, displayMode)).isTrue();
-
-        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
-        assertThat(matches(activeMode, displayMode)).isTrue();
-
-        // Change the display
-        SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(1, 3840, 2160, 60f);
-        modes = new SurfaceControl.DisplayMode[]{displayMode, addedDisplayInfo};
-        display.dynamicInfo.supportedDisplayModes = modes;
-        display.dynamicInfo.activeDisplayModeId = 1;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo);
-
-        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
-        assertThat(matches(activeMode, addedDisplayInfo)).isTrue();
-
-        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, addedDisplayInfo)).isTrue();
-    }
-
-    @Test
-    public void testAfterDisplayChange_ActiveModeIsUpdated() throws Exception {
-        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
-                createFakeDisplayMode(0, 1920, 1080, 60f),
-                createFakeDisplayMode(1, 1920, 1080, 50f)
-        };
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0, 60f);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
-                .getDisplayDeviceInfoLocked();
-
-        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
-        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
-
-        // Change the display
-        display.dynamicInfo.activeDisplayModeId = 1;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
-        assertThat(activeMode.matches(1920, 1080, 50f)).isTrue();
-    }
-
-    @Test
-    public void testAfterDisplayChange_RenderFrameRateIsUpdated() throws Exception {
-        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
-                createFakeDisplayMode(0, 1920, 1080, 60f),
-        };
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0,
-                /* renderFrameRate */30f);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
-                .getDisplayDeviceInfoLocked();
-
-        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
-        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
-        assertEquals(Float.floatToIntBits(30f),
-                Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
-
-        // Change the render frame rate
-        display.dynamicInfo.renderFrameRate = 60f;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
-        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
-        assertEquals(Float.floatToIntBits(60f),
-                Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
-    }
-
-    @Test
-    public void testAfterDisplayChange_HdrCapabilitiesAreUpdated() throws Exception {
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        Display.HdrCapabilities initialHdrCapabilities = new Display.HdrCapabilities(new int[0],
-                1000, 1000, 0);
-        display.dynamicInfo.hdrCapabilities = initialHdrCapabilities;
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
-                0).getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.hdrCapabilities).isEqualTo(initialHdrCapabilities);
-
-        // Change the display
-        Display.HdrCapabilities changedHdrCapabilities = new Display.HdrCapabilities(
-                new int[Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS], 1000, 1000, 0);
-        display.dynamicInfo.hdrCapabilities = changedHdrCapabilities;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.hdrCapabilities).isEqualTo(changedHdrCapabilities);
-    }
-
-    @Test
-    public void testAfterDisplayChange_AllmSupportIsUpdated() throws Exception {
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        display.dynamicInfo.autoLowLatencyModeSupported = true;
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
-                .getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.allmSupported).isTrue();
-
-        // Change the display
-        display.dynamicInfo.autoLowLatencyModeSupported = false;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.allmSupported).isFalse();
-    }
-
-    @Test
-    public void testAfterDisplayStateChanges_committedSetAfterState() throws Exception {
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
-
-        // Turn off.
-        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_OFF, 0,
-                0);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-        mListener.changedDisplays.clear();
-        assertThat(displayDevice.getDisplayDeviceInfoLocked().state).isEqualTo(Display.STATE_OFF);
-        assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState).isNotEqualTo(
-                Display.STATE_OFF);
-        verify(mSurfaceControlProxy, never()).setDisplayPowerMode(display.token, Display.STATE_OFF);
-
-        // Execute powerstate change.
-        changeStateRunnable.run();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-
-        // Verify that committed triggered a new change event and is set correctly.
-        verify(mSurfaceControlProxy, never()).setDisplayPowerMode(display.token, Display.STATE_OFF);
-        // We expect at least 1 update for the state change, but
-        // could get a second update for the initial brightness change if a nits mapping
-        // is available
-        assertThat(mListener.changedDisplays.size()).isAnyOf(1, 2);
-        assertThat(displayDevice.getDisplayDeviceInfoLocked().state).isEqualTo(Display.STATE_OFF);
-        assertThat(displayDevice.getDisplayDeviceInfoLocked().committedState).isEqualTo(
-                Display.STATE_OFF);
-    }
-
-    @Test
-    public void testAfterDisplayChange_GameContentTypeSupportIsUpdated() throws Exception {
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        display.dynamicInfo.gameContentTypeSupported = true;
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
-                .getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.gameContentTypeSupported).isTrue();
-
-        // Change the display
-        display.dynamicInfo.gameContentTypeSupported = false;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.gameContentTypeSupported).isFalse();
-    }
-
-    @Test
-    public void testAfterDisplayChange_ColorModesAreUpdated() throws Exception {
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        final int[] initialColorModes = new int[]{Display.COLOR_MODE_BT709};
-        display.dynamicInfo.supportedColorModes = initialColorModes;
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
-                .getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.colorMode).isEqualTo(Display.COLOR_MODE_BT709);
-        assertThat(displayDeviceInfo.supportedColorModes).isEqualTo(initialColorModes);
-
-        // Change the display
-        final int[] changedColorModes = new int[]{Display.COLOR_MODE_DEFAULT};
-        display.dynamicInfo.supportedColorModes = changedColorModes;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.colorMode).isEqualTo(Display.COLOR_MODE_DEFAULT);
-        assertThat(displayDeviceInfo.supportedColorModes).isEqualTo(changedColorModes);
-    }
-
-    @Test
-    public void testDisplayChange_withStaleDesiredDisplayModeSpecs() throws Exception {
-        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
-                createFakeDisplayMode(0, 1920, 1080, 60f),
-                createFakeDisplayMode(1, 1920, 1080, 50f)
-        };
-        final int activeMode = 0;
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode, 60f);
-        display.desiredDisplayModeSpecs.defaultMode = 1;
-
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
-
-        int baseModeId = Arrays.stream(displayDevice.getDisplayDeviceInfoLocked().supportedModes)
-                .filter(mode -> mode.getRefreshRate() == 60f)
-                .findFirst()
-                .get()
-                .getModeId();
-
-        displayDevice.setDesiredDisplayModeSpecsLocked(
-                new DisplayModeDirector.DesiredDisplayModeSpecs(
-                        /*baseModeId*/ baseModeId,
-                        /*allowGroupSwitching*/ false,
-                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
-                ));
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        verify(mSurfaceControlProxy).setDesiredDisplayModeSpecs(display.token,
-                new SurfaceControl.DesiredDisplayModeSpecs(
-                        /* baseModeId */ 0,
-                        /* allowGroupSwitching */ false,
-                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
-                ));
-
-        // Change the display
-        display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{
-                createFakeDisplayMode(2, 1920, 1080, 60f)
-        };
-        display.dynamicInfo.activeDisplayModeId = 2;
-        // SurfaceFlinger can return a stale defaultMode. Make sure this doesn't crash.
-        display.desiredDisplayModeSpecs.defaultMode = 1;
-
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-
-        baseModeId = displayDevice.getDisplayDeviceInfoLocked().supportedModes[0].getModeId();
-
-        // The traversal request will call setDesiredDisplayModeSpecsLocked on the display device
-        displayDevice.setDesiredDisplayModeSpecsLocked(
-                new DisplayModeDirector.DesiredDisplayModeSpecs(
-                        /*baseModeId*/ baseModeId,
-                        /*allowGroupSwitching*/ false,
-                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
-                ));
-
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        // Verify that this will reapply the desired modes.
-        verify(mSurfaceControlProxy).setDesiredDisplayModeSpecs(display.token,
-                new SurfaceControl.DesiredDisplayModeSpecs(
-                        /* baseModeId */ 2,
-                        /* allowGroupSwitching */ false,
-                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
-                ));
-    }
-
-    @Test
-    public void testBacklightAdapter_withSurfaceControlSupport() {
-        final Binder displayToken = new Binder();
-
-        when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(true);
-
-        // Test as default display
-        BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/,
-                mSurfaceControlProxy);
-        ba.setBacklight(0.514f, 100f, 0.614f, 500f);
-        verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.514f, 100f, 0.614f, 500f);
-
-        // Test as not default display
-        BacklightAdapter ba2 = new BacklightAdapter(displayToken, false /*isDefault*/,
-                mSurfaceControlProxy);
-        ba2.setBacklight(0.323f, 101f, 0.723f, 601f);
-        verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.323f, 101f, 0.723f, 601f);
-    }
-
-    @Test
-    public void testBacklightAdapter_withoutSourceControlSupport_defaultDisplay() {
-        final Binder displayToken = new Binder();
-        when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(false);
-        doReturn(mMockedBacklight).when(mMockedLightsManager)
-                .getLight(LightsManager.LIGHT_ID_BACKLIGHT);
-
-        BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/,
-                mSurfaceControlProxy);
-        ba.setBacklight(1f, 1f, 0.123f, 1f);
-        verify(mMockedBacklight).setBrightness(0.123f);
-    }
-
-    @Test
-    public void testBacklightAdapter_withoutSourceControlSupport_nonDefaultDisplay() {
-        final Binder displayToken = new Binder();
-        when(mSurfaceControlProxy.getDisplayBrightnessSupport(displayToken)).thenReturn(false);
-        doReturn(mMockedBacklight).when(mMockedLightsManager)
-                .getLight(LightsManager.LIGHT_ID_BACKLIGHT);
-
-        BacklightAdapter ba = new BacklightAdapter(displayToken, false /*isDefault*/,
-                mSurfaceControlProxy);
-        ba.setBacklight(0.456f, 1f, 1f, 1f);
-
-        // Adapter does not forward any brightness in this case.
-        verify(mMockedBacklight, never()).setBrightness(anyFloat());
-    }
-
-    @Test
-    public void testGetSystemPreferredDisplayMode() throws Exception {
-        SurfaceControl.DisplayMode displayMode1 = createFakeDisplayMode(0, 1920, 1080, 60f);
-        // system preferred mode
-        SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 3840, 2160, 60f);
-        // user preferred mode
-        SurfaceControl.DisplayMode displayMode3 = createFakeDisplayMode(2, 1920, 1080, 30f);
-
-        SurfaceControl.DisplayMode[] modes =
-                new SurfaceControl.DisplayMode[]{displayMode1, displayMode2, displayMode3};
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, 1);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays).isEmpty();
-
-        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
-                0).getDisplayDeviceInfoLocked();
-        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
-        Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, displayMode1)).isTrue();
-
-        // Set the user preferred display mode
-        mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
-                new Display.Mode(
-                        displayMode3.width, displayMode3.height, displayMode3.refreshRate));
-        updateAvailableDisplays();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        displayDeviceInfo = mListener.addedDisplays.get(
-                0).getDisplayDeviceInfoLocked();
-        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, displayMode3)).isTrue();
-
-        // clear the user preferred mode
-        mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(null);
-        updateAvailableDisplays();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        displayDeviceInfo = mListener.addedDisplays.get(
-                0).getDisplayDeviceInfoLocked();
-        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
-        assertThat(matches(defaultMode, displayMode2)).isTrue();
-
-        // Change the display and add new system preferred mode
-        SurfaceControl.DisplayMode addedDisplayInfo = createFakeDisplayMode(3, 2340, 1080, 20f);
-        modes = new SurfaceControl.DisplayMode[]{
-                displayMode1, displayMode2, displayMode3, addedDisplayInfo};
-        display.dynamicInfo.supportedDisplayModes = modes;
-        display.dynamicInfo.preferredBootDisplayMode = 3;
-        setUpDisplay(display);
-        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-
-        assertTrue(mListener.traversalRequested);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        assertThat(mListener.changedDisplays.size()).isEqualTo(3);
-
-        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
-        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
-        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
-
-        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode1);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2);
-        assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo);
-
-        assertThat(matches(displayDevice.getSystemPreferredDisplayModeLocked(), addedDisplayInfo))
-                .isTrue();
-    }
-
-    @Test
-    public void testHdrSdrRatio_notifiesOnChange() throws Exception {
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
-
-        // Turn on / initialize
-        assumeTrue(displayDevice.getDisplayDeviceConfig().hasSdrToHdrRatioSpline());
-        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
-                0);
-        changeStateRunnable.run();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        mListener.changedDisplays.clear();
-
-        assertEquals(1.0f, displayDevice.getDisplayDeviceInfoLocked().hdrSdrRatio, 0.001f);
-
-        // HDR time!
-        Runnable goHdrRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 1f,
-                0);
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        // Display state didn't change, no listeners should have happened
-        assertThat(mListener.changedDisplays.size()).isEqualTo(0);
-
-        // Execute hdr change.
-        goHdrRunnable.run();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        // Display state didn't change, expect to only get the HDR/SDR ratio change notification
-        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
-
-        final float expectedRatio = DISPLAY_RANGE_NITS[1] / DISPLAY_RANGE_NITS[0];
-        assertEquals(expectedRatio, displayDevice.getDisplayDeviceInfoLocked().hdrSdrRatio,
-                0.001f);
-    }
-
-    @Test
-    public void test_getDisplayDeviceInfoLocked_internalDisplay_usesCutoutAndCorners()
-            throws Exception {
-        setupCutoutAndRoundedCorners();
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        display.info.isInternal = true;
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
-
-        // Turn on / initialize
-        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
-                0);
-        changeStateRunnable.run();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        mListener.changedDisplays.clear();
-
-        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
-
-        assertThat(info.displayCutout).isNotNull();
-        assertThat(info.displayCutout.getBoundingRectTop()).isEqualTo(new Rect(507, 33, 573, 99));
-        assertThat(info.roundedCorners).isNotNull();
-        assertThat(info.roundedCorners.getRoundedCorner(0).getRadius()).isEqualTo(5);
-    }
-
-    @Test public void test_getDisplayDeviceInfoLocked_externalDisplay_doesNotUseCutoutOrCorners()
-            throws Exception {
-        setupCutoutAndRoundedCorners();
-        FakeDisplay display = new FakeDisplay(PORT_A);
-        display.info.isInternal = false;
-        setUpDisplay(display);
-        updateAvailableDisplays();
-        mAdapter.registerLocked();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
-        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
-
-        // Turn on / initialize
-        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
-                0);
-        changeStateRunnable.run();
-        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        mListener.changedDisplays.clear();
-
-        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
-
-        assertThat(info.displayCutout).isNull();
-        assertThat(info.roundedCorners).isNull();
-    }
-
-    private void setupCutoutAndRoundedCorners() {
-        String sampleCutout = "M 507,66\n"
-                + "a 33,33 0 1 0 66,0 33,33 0 1 0 -66,0\n"
-                + "Z\n"
-                + "@left\n";
-        // Setup some default cutout
-        when(mMockedResources.getString(
-                com.android.internal.R.string.config_mainBuiltInDisplayCutout))
-                .thenReturn(sampleCutout);
-        when(mMockedResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.rounded_corner_radius)).thenReturn(5);
-    }
-
-    private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
-                                  float expectedXdpi,
-                                  float expectedYDpi,
-                                  int expectedDensityDpi) {
-        final DisplayAddress.Physical physical = (DisplayAddress.Physical) info.address;
-        assertNotNull(physical);
-        assertEquals(expectedPort, physical.getPort());
-        assertEquals(expectedXdpi, info.xDpi, 0.01);
-        assertEquals(expectedYDpi, info.yDpi, 0.01);
-        assertEquals(expectedDensityDpi, info.densityDpi);
-    }
-
-    private Display.Mode getModeById(DisplayDeviceInfo displayDeviceInfo, int modeId) {
-        return Arrays.stream(displayDeviceInfo.supportedModes)
-                .filter(mode -> mode.getModeId() == modeId)
-                .findFirst()
-                .get();
-    }
-
-    private void assertModeIsSupported(Display.Mode[] supportedModes,
-            SurfaceControl.DisplayMode mode) {
-        assertThat(Arrays.stream(supportedModes).anyMatch(
-                x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue();
-    }
-
-    private void assertModeIsSupported(Display.Mode[] supportedModes,
-            SurfaceControl.DisplayMode mode, float[] alternativeRefreshRates) {
-        float[] sortedAlternativeRates =
-                Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length);
-        Arrays.sort(sortedAlternativeRates);
-
-        String message = "Expected " + mode + " with alternativeRefreshRates = "
-                + Arrays.toString(alternativeRefreshRates) + " to be in list of supported modes = "
-                + Arrays.toString(supportedModes);
-        Truth.assertWithMessage(message)
-            .that(Arrays.stream(supportedModes)
-                .anyMatch(x -> x.matches(mode.width, mode.height, mode.refreshRate)
-                        && Arrays.equals(x.getAlternativeRefreshRates(), sortedAlternativeRates)))
-                .isTrue();
-    }
-
-
-    private static class FakeDisplay {
-        public final DisplayAddress.Physical address;
-        public final IBinder token = new Binder();
-        public final SurfaceControl.StaticDisplayInfo info;
-        public SurfaceControl.DynamicDisplayInfo dynamicInfo =
-                new SurfaceControl.DynamicDisplayInfo();
-        {
-            dynamicInfo.supportedColorModes = new int[]{ Display.COLOR_MODE_DEFAULT };
-            dynamicInfo.hdrCapabilities = new Display.HdrCapabilities(new int[0],
-                    1000, 1000, 0);
-        }
-
-        public SurfaceControl.DesiredDisplayModeSpecs desiredDisplayModeSpecs =
-                new SurfaceControl.DesiredDisplayModeSpecs(
-                        /* defaultMode */ 0,
-                        /* allowGroupSwitching */ false,
-                        REFRESH_RATE_RANGES, REFRESH_RATE_RANGES
-                );
-
-        private FakeDisplay(int port) {
-            address = createDisplayAddress(port);
-            info = createFakeDisplayInfo();
-            dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{
-                    createFakeDisplayMode(0, 800, 600, 60f)
-            };
-            dynamicInfo.activeDisplayModeId = 0;
-        }
-
-        private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
-                float renderFrameRate) {
-            address = createDisplayAddress(port);
-            info = createFakeDisplayInfo();
-            dynamicInfo.supportedDisplayModes = modes;
-            dynamicInfo.activeDisplayModeId = activeMode;
-            dynamicInfo.renderFrameRate = renderFrameRate;
-        }
-
-        private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
-                int preferredMode) {
-            address = createDisplayAddress(port);
-            info = createFakeDisplayInfo();
-            dynamicInfo.supportedDisplayModes = modes;
-            dynamicInfo.activeDisplayModeId = activeMode;
-            dynamicInfo.preferredBootDisplayMode = preferredMode;
-        }
-
-    }
-
-    private void setUpDisplay(FakeDisplay display) {
-        mAddresses.add(display.address);
-        when(mSurfaceControlProxy.getPhysicalDisplayToken(display.address.getPhysicalDisplayId()))
-                .thenReturn(display.token);
-        when(mSurfaceControlProxy.getStaticDisplayInfo(display.address.getPhysicalDisplayId()))
-                .thenReturn(display.info);
-        when(mSurfaceControlProxy.getDynamicDisplayInfo(display.address.getPhysicalDisplayId()))
-                .thenReturn(display.dynamicInfo);
-        when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(display.token))
-                .thenReturn(display.desiredDisplayModeSpecs);
-    }
-
-    private void updateAvailableDisplays() {
-        long[] ids = new long[mAddresses.size()];
-        int i = 0;
-        for (DisplayAddress.Physical address : mAddresses) {
-            ids[i] = address.getPhysicalDisplayId();
-            i++;
-        }
-        when(mSurfaceControlProxy.getPhysicalDisplayIds()).thenReturn(ids);
-    }
-
-    private static DisplayAddress.Physical createDisplayAddress(int port) {
-        return DisplayAddress.fromPortAndModel(port, DISPLAY_MODEL);
-    }
-
-    private static SurfaceControl.StaticDisplayInfo createFakeDisplayInfo() {
-        final SurfaceControl.StaticDisplayInfo info = new SurfaceControl.StaticDisplayInfo();
-        info.density = 100;
-        return info;
-    }
-
-    private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
-            float refreshRate) {
-        return createFakeDisplayMode(id, width, height, refreshRate, /* group */ 0);
-    }
-
-    private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
-            float refreshRate, int group) {
-        final SurfaceControl.DisplayMode mode = new SurfaceControl.DisplayMode();
-        mode.id = id;
-        mode.width = width;
-        mode.height = height;
-        mode.refreshRate = refreshRate;
-        mode.xDpi = 100;
-        mode.yDpi = 100;
-        mode.group = group;
-        mode.supportedHdrTypes = HDR_TYPES;
-        return mode;
-    }
-
-    private static void waitForHandlerToComplete(Handler handler, long waitTimeMs)
-            throws InterruptedException {
-        final CountDownLatch fence = new CountDownLatch(1);
-        handler.post(fence::countDown);
-        assertTrue(fence.await(waitTimeMs, TimeUnit.MILLISECONDS));
-    }
-
-    private class HotplugTransmitter {
-        private final Handler mHandler;
-        private final LocalDisplayAdapter.DisplayEventListener mListener;
-
-        HotplugTransmitter(Looper looper, LocalDisplayAdapter.DisplayEventListener listener) {
-            mHandler = new Handler(looper);
-            mListener = listener;
-        }
-
-        public void sendHotplug(FakeDisplay display, boolean connected)
-                throws InterruptedException {
-            mHandler.post(() -> mListener.onHotplug(/* timestampNanos = */ 0,
-                    display.address.getPhysicalDisplayId(), connected));
-            waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
-        }
-    }
-
-    private class Injector extends LocalDisplayAdapter.Injector {
-        private HotplugTransmitter mTransmitter;
-        @Override
-        public void setDisplayEventListenerLocked(Looper looper,
-                LocalDisplayAdapter.DisplayEventListener listener) {
-            mTransmitter = new HotplugTransmitter(looper, listener);
-        }
-
-        public HotplugTransmitter getTransmitter() {
-            return mTransmitter;
-        }
-
-        @Override
-        public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
-            return mSurfaceControlProxy;
-        }
-    }
-
-    private class TestListener implements DisplayAdapter.Listener {
-        public ArrayList<DisplayDevice> addedDisplays = new ArrayList<>();
-        public ArrayList<DisplayDevice> changedDisplays = new ArrayList<>();
-        public boolean traversalRequested = false;
-
-        @Override
-        public void onDisplayDeviceEvent(DisplayDevice device, int event) {
-            if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED) {
-                addedDisplays.add(device);
-            } else if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED) {
-                changedDisplays.add(device);
-            }
-        }
-
-        @Override
-        public void onTraversalRequested() {
-            traversalRequested = true;
-        }
-    }
-
-    private TypedArray createFloatTypedArray(float[] vals) {
-        TypedArray mockArray = mock(TypedArray.class);
-        when(mockArray.length()).thenAnswer(invocation -> {
-            return vals.length;
-        });
-        when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
-            final float def = (float) invocation.getArguments()[1];
-            if (vals == null) {
-                return def;
-            }
-            int idx = (int) invocation.getArguments()[0];
-            if (idx >= 0 && idx < vals.length) {
-                return vals[idx];
-            } else {
-                return def;
-            }
-        });
-        return mockArray;
-    }
-
-    private boolean matches(Display.Mode a, SurfaceControl.DisplayMode b) {
-        return a.getPhysicalWidth() == b.width && a.getPhysicalHeight() == b.height
-                && Float.floatToIntBits(a.getRefreshRate()) == Float.floatToIntBits(b.refreshRate);
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/OWNERS b/services/tests/mockingservicestests/src/com/android/server/display/OWNERS
deleted file mode 100644
index 6ce1ee4..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/display/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
deleted file mode 100644
index 3faf394..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display.color;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.display.DisplayManagerInternal;
-import android.os.Binder;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.CieXyz;
-import android.view.SurfaceControl.DisplayPrimaries;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.R;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-@RunWith(AndroidJUnit4.class)
-public class DisplayWhiteBalanceTintControllerTest {
-    @Mock
-    private Context mMockedContext;
-    @Mock
-    private Resources mMockedResources;
-    @Mock
-    private DisplayManagerInternal mDisplayManagerInternal;
-
-    private MockitoSession mSession;
-    private Resources mResources;
-    IBinder mDisplayToken;
-    DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
-
-    @Before
-    public void setUp() {
-        mSession = ExtendedMockito.mockitoSession()
-                .initMocks(this)
-                .mockStatic(SurfaceControl.class)
-                .strictness(Strictness.LENIENT)
-                .startMocking();
-
-        mResources = InstrumentationRegistry.getContext().getResources();
-        // These Resources are common to all tests.
-        doReturn(4000)
-            .when(mMockedResources)
-            .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin);
-        doReturn(8000)
-            .when(mMockedResources)
-            .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax);
-        doReturn(6500)
-            .when(mMockedResources)
-            .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
-        doReturn(new String[] {"0.950456", "1.000000", "1.089058"})
-                .when(mMockedResources)
-                .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
-        doReturn(6500)
-                .when(mMockedResources)
-                .getInteger(R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
-        doReturn(new int[] {0})
-                .when(mMockedResources)
-                .getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
-        doReturn(new int[] {20})
-                .when(mMockedResources)
-                .getIntArray(R.array.config_displayWhiteBalanceDisplayRangeMinimums);
-
-        doReturn(mMockedResources).when(mMockedContext).getResources();
-
-        mDisplayToken = new Binder();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (mSession != null) {
-            mSession.finishMocking();
-        }
-    }
-
-    /**
-     * Setup should succeed when SurfaceControl setup results in a valid color transform.
-     */
-    @Test
-    public void displayWhiteBalance_setupWithSurfaceControl() {
-        // Make SurfaceControl return sRGB primaries
-        DisplayPrimaries displayPrimaries = new DisplayPrimaries();
-        displayPrimaries.red = new CieXyz();
-        displayPrimaries.red.X = 0.412315f;
-        displayPrimaries.red.Y = 0.212600f;
-        displayPrimaries.red.Z = 0.019327f;
-        displayPrimaries.green = new CieXyz();
-        displayPrimaries.green.X = 0.357600f;
-        displayPrimaries.green.Y = 0.715200f;
-        displayPrimaries.green.Z = 0.119200f;
-        displayPrimaries.blue = new CieXyz();
-        displayPrimaries.blue.X = 0.180500f;
-        displayPrimaries.blue.Y = 0.072200f;
-        displayPrimaries.blue.Z = 0.950633f;
-        displayPrimaries.white = new CieXyz();
-        displayPrimaries.white.X = 0.950456f;
-        displayPrimaries.white.Y = 1.000000f;
-        displayPrimaries.white.Z = 1.089058f;
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
-                .thenReturn(displayPrimaries);
-
-        setUpTintController();
-        assertWithMessage("Setup with valid SurfaceControl failed")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isTrue();
-    }
-
-    /**
-     * Setup should fail when SurfaceControl setup results in an invalid color transform.
-     */
-    @Test
-    public void displayWhiteBalance_setupWithInvalidSurfaceControlData() {
-        // Make SurfaceControl return invalid display primaries
-        DisplayPrimaries displayPrimaries = new DisplayPrimaries();
-        displayPrimaries.red = new CieXyz();
-        displayPrimaries.green = new CieXyz();
-        displayPrimaries.blue = new CieXyz();
-        displayPrimaries.white = new CieXyz();
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
-                .thenReturn(displayPrimaries);
-
-        setUpTintController();
-        assertWithMessage("Setup with invalid SurfaceControl succeeded")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isFalse();
-    }
-
-    /**
-     * Setup should succeed when SurfaceControl setup fails and Resources result in a valid color
-     * transform.
-     */
-    @Test
-    public void displayWhiteBalance_setupWithResources() {
-        // Use default (valid) Resources
-        doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries))
-            .when(mMockedResources)
-            .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
-        // Make SurfaceControl setup fail
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
-
-        setUpTintController();
-        assertWithMessage("Setup with valid Resources failed")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isTrue();
-    }
-
-    /**
-     * Setup should fail when SurfaceControl setup fails and Resources result in an invalid color
-     * transform.
-     */
-    @Test
-    public void displayWhiteBalance_setupWithInvalidResources() {
-        // Use Resources with invalid color data
-        doReturn(new String[] {
-                "0", "0", "0", // Red X, Y, Z
-                "0", "0", "0", // Green X, Y, Z
-                "0", "0", "0", // Blue X, Y, Z
-                "0", "0", "0", // White X, Y, Z
-            })
-            .when(mMockedResources)
-            .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
-        // Make SurfaceControl setup fail
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
-
-        setUpTintController();
-        assertWithMessage("Setup with invalid Resources succeeded")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isFalse();
-    }
-
-    /**
-     * Matrix should match the precalculated one for given cct and display primaries.
-     */
-    @Test
-    public void displayWhiteBalance_getAndSetMatrix_validateTransformMatrix() {
-        DisplayPrimaries displayPrimaries = new DisplayPrimaries();
-        displayPrimaries.red = new CieXyz();
-        displayPrimaries.red.X = 0.412315f;
-        displayPrimaries.red.Y = 0.212600f;
-        displayPrimaries.red.Z = 0.019327f;
-        displayPrimaries.green = new CieXyz();
-        displayPrimaries.green.X = 0.357600f;
-        displayPrimaries.green.Y = 0.715200f;
-        displayPrimaries.green.Z = 0.119200f;
-        displayPrimaries.blue = new CieXyz();
-        displayPrimaries.blue.X = 0.180500f;
-        displayPrimaries.blue.Y = 0.072200f;
-        displayPrimaries.blue.Z = 0.950633f;
-        displayPrimaries.white = new CieXyz();
-        displayPrimaries.white.X = 0.950456f;
-        displayPrimaries.white.Y = 1.000000f;
-        displayPrimaries.white.Z = 1.089058f;
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
-                .thenReturn(displayPrimaries);
-
-        setUpTintController();
-        assertWithMessage("Setup with valid SurfaceControl failed")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isTrue();
-
-        final int cct = 6500;
-        mDisplayWhiteBalanceTintController.setMatrix(cct);
-        mDisplayWhiteBalanceTintController.setAppliedCct(
-                mDisplayWhiteBalanceTintController.getTargetCct());
-
-        assertWithMessage("Failed to set temperature")
-                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
-                .isEqualTo(cct);
-        float[] matrixDwb = mDisplayWhiteBalanceTintController.getMatrix();
-        final float[] expectedMatrixDwb = {
-            0.971848f,   -0.001421f,  0.000491f, 0.0f,
-            0.028193f,    0.945798f,  0.003207f, 0.0f,
-            -0.000042f,  -0.000989f,  0.988659f, 0.0f,
-            0.0f,         0.0f,       0.0f,      1.0f
-        };
-        assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
-            1e-6f /* tolerance */);
-    }
-
-    /**
-     * Matrix should match the precalculated one for given cct and display primaries.
-     */
-    @Test
-    public void displayWhiteBalance_targetApplied_validateTransformMatrix() {
-        DisplayPrimaries displayPrimaries = new DisplayPrimaries();
-        displayPrimaries.red = new CieXyz();
-        displayPrimaries.red.X = 0.412315f;
-        displayPrimaries.red.Y = 0.212600f;
-        displayPrimaries.red.Z = 0.019327f;
-        displayPrimaries.green = new CieXyz();
-        displayPrimaries.green.X = 0.357600f;
-        displayPrimaries.green.Y = 0.715200f;
-        displayPrimaries.green.Z = 0.119200f;
-        displayPrimaries.blue = new CieXyz();
-        displayPrimaries.blue.X = 0.180500f;
-        displayPrimaries.blue.Y = 0.072200f;
-        displayPrimaries.blue.Z = 0.950633f;
-        displayPrimaries.white = new CieXyz();
-        displayPrimaries.white.X = 0.950456f;
-        displayPrimaries.white.Y = 1.000000f;
-        displayPrimaries.white.Z = 1.089058f;
-        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
-                .thenReturn(displayPrimaries);
-
-        setUpTintController();
-        assertWithMessage("Setup with valid SurfaceControl failed")
-                .that(mDisplayWhiteBalanceTintController.mSetUp)
-                .isTrue();
-
-        final int cct = 6500;
-        mDisplayWhiteBalanceTintController.setTargetCct(cct);
-        final float[] matrixDwb = mDisplayWhiteBalanceTintController.computeMatrixForCct(cct);
-        mDisplayWhiteBalanceTintController.setAppliedCct(cct);
-
-        assertWithMessage("Failed to set temperature")
-                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
-                .isEqualTo(cct);
-        final float[] expectedMatrixDwb = {
-                0.971848f,   -0.001421f,  0.000491f, 0.0f,
-                0.028193f,    0.945798f,  0.003207f, 0.0f,
-                -0.000042f,  -0.000989f,  0.988659f, 0.0f,
-                0.0f,         0.0f,       0.0f,      1.0f
-        };
-        assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
-                1e-6f /* tolerance */);
-    }
-
-    private void setUpTintController() {
-        mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
-                mDisplayManagerInternal);
-        mDisplayWhiteBalanceTintController.setUp(mMockedContext, true);
-        mDisplayWhiteBalanceTintController.setActivated(true);
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
new file mode 100644
index 0000000..c02cbd1
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.dreams;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.content.ContextWrapper;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.os.PowerManagerInternal;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+/**
+ * Collection of tests for exercising the {@link DreamManagerService} lifecycle.
+ */
+public class DreamManagerServiceMockingTest {
+    private ContextWrapper mContextSpy;
+    private Resources mResourcesSpy;
+
+    @Mock
+    private ActivityManagerInternal mActivityManagerInternalMock;
+
+    @Mock
+    private PowerManagerInternal mPowerManagerInternalMock;
+
+    @Mock
+    private UserManager mUserManagerMock;
+
+    private MockitoSession mMockitoSession;
+
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+        mResourcesSpy = spy(mContextSpy.getResources());
+        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+
+        addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
+        addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+
+        when(mContextSpy.getSystemService(UserManager.class)).thenReturn(mUserManagerMock);
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .mockStatic(Settings.Secure.class)
+                .startMocking();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mMockitoSession.finishMocking();
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.removeServiceForTest(PowerManagerInternal.class);
+    }
+
+    private DreamManagerService createService() {
+        return new DreamManagerService(mContextSpy);
+    }
+
+    @Test
+    public void testSettingsQueryUserChange() {
+        final DreamManagerService service = createService();
+
+        final SystemService.TargetUser from =
+                new SystemService.TargetUser(mock(UserInfo.class));
+        final SystemService.TargetUser to =
+                new SystemService.TargetUser(mock(UserInfo.class));
+
+        service.onUserSwitching(from, to);
+
+        verify(() -> Settings.Secure.getIntForUser(any(),
+                eq(Settings.Secure.SCREENSAVER_ENABLED),
+                anyInt(),
+                eq(UserHandle.USER_CURRENT)));
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index e7b3e6f..617e4eb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -325,6 +325,83 @@
                 () -> mUmi.getBootUser(/* waitUntilSet= */ false));
     }
 
+    @Test
+    public void testGetPreviousFullUserToEnterForeground() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        assertWithMessage("getPreviousFullUserToEnterForeground")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(OTHER_USER_ID);
+    }
+
+    @Test
+    public void testGetPreviousFullUserToEnterForeground_SkipsCurrentUser() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        mockCurrentUser(OTHER_USER_ID);
+        assertWithMessage("getPreviousFullUserToEnterForeground should skip current user")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void testGetPreviousFullUserToEnterForeground_SkipsNonFullUsers() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        mUsers.get(OTHER_USER_ID).info.flags &= ~UserInfo.FLAG_FULL;
+        assertWithMessage("getPreviousFullUserToEnterForeground should skip non-full users")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void testGetPreviousFullUserToEnterForeground_SkipsPartialUsers() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        mUsers.get(OTHER_USER_ID).info.partial = true;
+        assertWithMessage("getPreviousFullUserToEnterForeground should skip partial users")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void testGetPreviousFullUserToEnterForeground_SkipsDisabledUsers() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        mUsers.get(OTHER_USER_ID).info.flags |= UserInfo.FLAG_DISABLED;
+        assertWithMessage("getPreviousFullUserToEnterForeground should skip disabled users")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void testGetPreviousFullUserToEnterForeground_SkipsRemovingUsers() throws Exception {
+        addUser(USER_ID);
+        setLastForegroundTime(USER_ID, 1_000_000L);
+        addUser(OTHER_USER_ID);
+        setLastForegroundTime(OTHER_USER_ID, 2_000_000L);
+
+        mUms.addRemovingUserId(OTHER_USER_ID);
+        assertWithMessage("getPreviousFullUserToEnterForeground should skip removing users")
+                .that(mUms.getPreviousFullUserToEnterForeground())
+                .isEqualTo(USER_ID);
+    }
+
     private void mockCurrentUser(@UserIdInt int userId) {
         mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
index a3a49d70..f3aa427 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
@@ -259,6 +259,29 @@
     }
 
     @Test
+    public void testMinTimeBetweenAlarms_freshAlarm() {
+        final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 5 * MINUTE_IN_MILLIS);
+        final long fixedTimeElapsed = mInjector.getElapsedRealtime();
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        final String pkg1 = "com.android.test.1";
+        final String pkg2 = "com.android.test.2";
+        alarmQueue.addAlarm(pkg1, fixedTimeElapsed + MINUTE_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(fixedTimeElapsed + MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any());
+
+        advanceElapsedClock(MINUTE_IN_MILLIS);
+
+        alarmQueue.onAlarm();
+        // Minimum of 5 minutes between alarms, so the next alarm should be 5 minutes after the
+        // first.
+        alarmQueue.addAlarm(pkg2, fixedTimeElapsed + 2 * MINUTE_IN_MILLIS);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact(
+                anyInt(), eq(fixedTimeElapsed + 6 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any());
+    }
+
+    @Test
     public void testOnAlarm() {
         final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0);
         final long nowElapsed = mInjector.getElapsedRealtime();
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 206af5b..eefe5af 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -31,7 +31,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
-import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_CROP;
 
 import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertEquals;
@@ -92,7 +91,6 @@
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -175,12 +173,12 @@
         sImageWallpaperComponentName = ComponentName.unflattenFromString(
                 sContext.getResources().getString(R.string.image_wallpaper_component));
         // Mock default wallpaper as image wallpaper if there is no pre-defined default wallpaper.
-        sDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(sContext);
+        sDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(sContext);
 
         if (sDefaultWallpaperComponent == null) {
             sDefaultWallpaperComponent = sImageWallpaperComponentName;
             doReturn(sImageWallpaperComponentName).when(() ->
-                    WallpaperManager.getDefaultWallpaperComponent(any()));
+                    WallpaperManager.getCmfDefaultWallpaperComponent(any()));
         } else {
             sContext.addMockService(sDefaultWallpaperComponent, sWallpaperService);
         }
@@ -276,10 +274,10 @@
             assertEquals(testUserId, newWallpaperData.userId);
 
             WallpaperData wallpaperData = mService.getWallpaperSafeLocked(testUserId, which);
-            assertEquals(wallpaperData.cropFile.getAbsolutePath(),
-                    newWallpaperData.cropFile.getAbsolutePath());
-            assertEquals(wallpaperData.wallpaperFile.getAbsolutePath(),
-                    newWallpaperData.wallpaperFile.getAbsolutePath());
+            assertEquals(wallpaperData.getCropFile().getAbsolutePath(),
+                    newWallpaperData.getCropFile().getAbsolutePath());
+            assertEquals(wallpaperData.getWallpaperFile().getAbsolutePath(),
+                    newWallpaperData.getWallpaperFile().getAbsolutePath());
         }
     }
 
@@ -300,10 +298,8 @@
 
     /**
      * Tests setWallpaperComponent and clearWallpaper should work as expected.
-     * TODO ignored since the assumption never passes. to be investigated.
      */
     @Test
-    @Ignore("b/264533465")
     public void testSetThenClearComponent() {
         // Skip if there is no pre-defined default wallpaper component.
         assumeThat(sDefaultWallpaperComponent,
@@ -528,7 +524,8 @@
     @Test
     public void getWallpaperWithFeature_getCropped_returnsCropFile() throws Exception {
         File cropSystemWallpaperFile =
-                new File(WallpaperUtils.getWallpaperDir(USER_SYSTEM), WALLPAPER_CROP);
+                new WallpaperData(USER_SYSTEM, FLAG_SYSTEM).getCropFile();
+        cropSystemWallpaperFile.getParentFile().mkdirs();
         cropSystemWallpaperFile.createNewFile();
         try (FileOutputStream outputStream = new FileOutputStream(cropSystemWallpaperFile)) {
             outputStream.write("Crop system wallpaper".getBytes());
@@ -550,7 +547,8 @@
     @Test
     public void getWallpaperWithFeature_notGetCropped_returnsOriginalFile() throws Exception {
         File originalSystemWallpaperFile =
-                new File(WallpaperUtils.getWallpaperDir(USER_SYSTEM), WALLPAPER);
+                new WallpaperData(USER_SYSTEM, FLAG_SYSTEM).getWallpaperFile();
+        originalSystemWallpaperFile.getParentFile().mkdirs();
         originalSystemWallpaperFile.createNewFile();
         try (FileOutputStream outputStream = new FileOutputStream(originalSystemWallpaperFile)) {
             outputStream.write("Original system wallpaper".getBytes());
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 16e1b45..0530f89 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -28,11 +28,13 @@
         "services.accessibility",
         "services.appwidget",
         "services.autofill",
+        "services.contentcapture",
         "services.backup",
         "services.companion",
         "services.core",
         "services.credentials",
         "services.devicepolicy",
+        "services.flags",
         "services.net",
         "services.people",
         "services.usage",
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 107dde2..fa0a971 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -110,6 +110,7 @@
     <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" />
     <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
     <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
+    <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
 
     <queries>
         <package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index e6ef044..5b5c8d4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -202,6 +203,7 @@
         assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_0));
         assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_1));
 
+        // Once for each display on unregister
         verify(mMockThumbnail, times(2)).hideThumbnail();
     }
 
@@ -543,7 +545,11 @@
         // The first time is triggered when the thumbnail is just created.
         // The second time is triggered when the magnification region changed.
         verify(mMockThumbnail, times(2)).setThumbnailBounds(
-                any(), anyFloat(), anyFloat(), anyFloat());
+                /* currentBounds= */ any(),
+                /* scale= */ anyFloat(),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
     }
 
     @Test
@@ -681,6 +687,9 @@
         checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId);
         assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2));
         checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId);
+
+        // Once on init before it's activated and once for reset
+        verify(mMockThumbnail, times(2)).hideThumbnail();
     }
 
     @Test
@@ -783,6 +792,9 @@
         mMessageCapturingHandler.sendAllMessages();
         checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_0);
         checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_1);
+
+        // Twice for each display: once on init before it's activated and once for screen off
+        verify(mMockThumbnail, times(4)).hideThumbnail();
     }
 
     @Test
@@ -847,6 +859,15 @@
         mMessageCapturingHandler.sendAllMessages();
         checkActivatedAndMagnifying(
                 /* activated= */ expectedActivated, /* magnifying= */ false, displayId);
+
+        if (expectedActivated) {
+            verify(mMockThumbnail, times(2)).setThumbnailBounds(
+                    /* currentBounds= */ any(),
+                    /* scale= */ anyFloat(),
+                    /* centerX= */ anyFloat(),
+                    /* centerY= */ anyFloat()
+            );
+        }
     }
 
     @Test
@@ -950,6 +971,13 @@
                 INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
         assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
         verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
+
+        verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds(
+                /* currentBounds= */ any(),
+                eq(scale),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
     }
 
     @Test
@@ -984,6 +1012,13 @@
                 INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
         assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
         verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
+
+        verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds(
+                /* currentBounds= */ any(),
+                eq(scale),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
     }
 
     @Test
@@ -1246,6 +1281,13 @@
         callbacks.onImeWindowVisibilityChanged(true);
         mMessageCapturingHandler.sendAllMessages();
         verify(mRequestObserver).onImeWindowVisibilityChanged(eq(DISPLAY_0), eq(true));
+
+        verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds(
+                /* currentBounds= */ any(),
+                /* scale= */ anyFloat(),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
     }
 
     @Test
@@ -1270,6 +1312,15 @@
         mFullScreenMagnificationController.onUserContextChanged(DISPLAY_0);
 
         verify(mRequestObserver).onFullScreenMagnificationActivationState(eq(DISPLAY_0), eq(false));
+        verify(mMockThumbnail).setThumbnailBounds(
+                /* currentBounds= */ any(),
+                /* scale= */ anyFloat(),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
+
+        // Once on init before it's activated and once for reset
+        verify(mMockThumbnail, times(2)).hideThumbnail();
     }
 
     @Test
@@ -1281,6 +1332,12 @@
 
         assertEquals(1.0f, mFullScreenMagnificationController.getScale(DISPLAY_0), 0);
         assertTrue(mFullScreenMagnificationController.isActivated(DISPLAY_0));
+        verify(mMockThumbnail).setThumbnailBounds(
+                /* currentBounds= */ any(),
+                /* scale= */ anyFloat(),
+                /* centerX= */ anyFloat(),
+                /* centerY= */ anyFloat()
+        );
     }
 
     private void setScaleToMagnifying() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 2d4bf14..32d0c98 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -44,8 +44,10 @@
 import android.graphics.PointF;
 import android.os.Handler;
 import android.os.Message;
+import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.provider.Settings;
 import android.testing.TestableContext;
 import android.util.DebugUtils;
 import android.view.InputDevice;
@@ -140,8 +142,6 @@
     @Mock
     WindowMagnificationPromptController mWindowMagnificationPromptController;
     @Mock
-    AccessibilityManagerService mMockAccessibilityManagerService;
-    @Mock
     AccessibilityTraceManager mMockTraceManager;
 
     @Rule
@@ -153,6 +153,8 @@
 
     private long mLastDownTime = Integer.MIN_VALUE;
 
+    private float mOriginalMagnificationPersistedScale;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -166,6 +168,13 @@
         when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
         when(mockController.getAnimationDuration()).thenReturn(1000L);
         when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true);
+        mOriginalMagnificationPersistedScale = Settings.Secure.getFloatForUser(
+                mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
+                UserHandle.USER_SYSTEM);
+        Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
+                UserHandle.USER_SYSTEM);
         mFullScreenMagnificationController = new FullScreenMagnificationController(
                 mockController,
                 new Object(),
@@ -192,6 +201,10 @@
         mMgh.onDestroy();
         mFullScreenMagnificationController.unregister(DISPLAY_0);
         verify(mWindowMagnificationPromptController).onDestroy();
+        Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                mOriginalMagnificationPersistedScale,
+                UserHandle.USER_SYSTEM);
     }
 
     @NonNull
@@ -525,10 +538,9 @@
         final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
                 .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
         final float persistedScale = (1.0f + threshold) * scale + 1.0f;
-        mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
-                DEFAULT_Y, /* animate= */ false,
-                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-        mFullScreenMagnificationController.persistScale(DISPLAY_0);
+        Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
+                UserHandle.USER_SYSTEM);
         mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
                 DEFAULT_Y, /* animate= */ false,
                 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -547,10 +559,9 @@
         final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
                 .CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
         final float persistedScale = (1.0f + threshold) * scale - 0.1f;
-        mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
-                DEFAULT_Y, /* animate= */ false,
-                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-        mFullScreenMagnificationController.persistScale(DISPLAY_0);
+        Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
+                UserHandle.USER_SYSTEM);
         mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
                 DEFAULT_Y, /* animate= */ false,
                 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index e75fc21..d4c6fad 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -606,9 +606,10 @@
     public void onPerformScaleAction_fullScreenMagnifierEnabled_handleScaleChange()
             throws RemoteException {
         final float newScale = 4.0f;
+        final boolean updatePersistence = true;
         setMagnificationEnabled(MODE_FULLSCREEN);
 
-        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, newScale);
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, newScale, updatePersistence);
 
         verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), eq(newScale),
                 anyFloat(), anyFloat(), anyBoolean(), anyInt());
@@ -619,12 +620,13 @@
     public void onPerformScaleAction_windowMagnifierEnabled_handleScaleChange()
             throws RemoteException {
         final float newScale = 4.0f;
+        final boolean updatePersistence = false;
         setMagnificationEnabled(MODE_WINDOW);
 
-        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, newScale);
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, newScale, updatePersistence);
 
         verify(mWindowMagnificationManager).setScale(eq(TEST_DISPLAY), eq(newScale));
-        verify(mWindowMagnificationManager).persistScale(eq(TEST_DISPLAY));
+        verify(mWindowMagnificationManager, never()).persistScale(eq(TEST_DISPLAY));
     }
 
     @Test
@@ -666,6 +668,21 @@
         assertEquals(config.getCenterX(), actualConfig.getCenterX(), 0);
         assertEquals(config.getCenterY(), actualConfig.getCenterY(), 0);
         assertEquals(config.getScale(), actualConfig.getScale(), 0);
+
+        verify(mWindowMagnificationManager).onUserMagnificationScaleChanged(
+                /* userId= */ anyInt(), eq(TEST_DISPLAY), eq(config.getScale()));
+    }
+
+    @Test
+    public void onSourceBoundChanged_windowEnabled_notifyMagnificationChanged()
+            throws RemoteException {
+        setMagnificationEnabled(MODE_WINDOW);
+        reset(mWindowMagnificationManager);
+
+        mMagnificationController.onSourceBoundsChanged(TEST_DISPLAY, TEST_RECT);
+
+        verify(mWindowMagnificationManager).onUserMagnificationScaleChanged(
+                /* userId= */ anyInt(), eq(TEST_DISPLAY), eq(DEFAULT_SCALE));
     }
 
     @Test
@@ -794,6 +811,7 @@
         verify(mMagnificationController).onWindowMagnificationActivationState(
                 eq(TEST_DISPLAY), eq(true));
     }
+
     @Test
     public void deactivateWindowMagnification_windowActivated_triggerCallbackAndLogUsage()
             throws RemoteException {
@@ -1294,9 +1312,9 @@
         }
 
         @Override
-        public void onPerformScaleAction(int displayId, float scale) {
+        public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
             if (mCallback != null) {
-                mCallback.onPerformScaleAction(displayId, scale);
+                mCallback.onPerformScaleAction(displayId, scale, updatePersistence);
             }
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
index 3baa102..8faddf8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
@@ -187,6 +187,29 @@
                 .addView(eq(mMagnificationThumbnail.mThumbnailLayout), any());
         verify(mMockWindowManager, never())
                 .removeView(eq(mMagnificationThumbnail.mThumbnailLayout));
+        verify(mMockWindowManager, never())
+                .updateViewLayout(eq(mMagnificationThumbnail.mThumbnailLayout), any());
+    }
+
+    @Test
+    public void whenVisible_setBoundsUpdatesLayout() throws InterruptedException {
+        runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
+                /* scale=   */ 2f,
+                /* centerX= */ 5,
+                /* centerY= */ 10
+        ));
+        runOnMainSync(() -> mMagnificationThumbnail.setThumbnailBounds(
+                new Rect(),
+                /* scale=   */ 2f,
+                /* centerX= */ 5,
+                /* centerY= */ 10
+        ));
+        idle();
+
+        verify(mMockWindowManager).updateViewLayout(
+                eq(mMagnificationThumbnail.mThumbnailLayout),
+                /* params= */ any()
+        );
     }
 
     private static void idle() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
index 2357e65..8608199 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
@@ -122,6 +122,15 @@
     }
 
     @Test
+    public void onUserMagnificationScaleChanged() throws RemoteException {
+        final int testUserId = 1;
+        final float testScale = 3f;
+        mConnectionWrapper.onUserMagnificationScaleChanged(testUserId, TEST_DISPLAY, testScale);
+        verify(mConnection).onUserMagnificationScaleChanged(
+                eq(testUserId), eq(TEST_DISPLAY), eq(testScale));
+    }
+
+    @Test
     public void setMirrorWindowCallback() throws RemoteException {
         mConnectionWrapper.setConnectionCallback(mCallback);
         verify(mConnection).setConnectionCallback(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index c98de7c..27e6ef1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -548,6 +548,18 @@
     }
 
     @Test
+    public void onUserMagnificationScaleChanged_hasConnection_invokeConnectionMethod()
+            throws RemoteException {
+        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+
+        final float testScale = 3f;
+        mWindowMagnificationManager.onUserMagnificationScaleChanged(
+                CURRENT_USER_ID, TEST_DISPLAY, testScale);
+        verify(mMockConnection.getConnection()).onUserMagnificationScaleChanged(
+                eq(CURRENT_USER_ID), eq(TEST_DISPLAY), eq(testScale));
+    }
+
+    @Test
     public void pointersInWindow_magnifierEnabled_returnCorrectValue() throws RemoteException {
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
@@ -564,12 +576,15 @@
     @Test
     public void onPerformScaleAction_magnifierEnabled_notifyAction() throws RemoteException {
         final float newScale = 4.0f;
+        final boolean updatePersistence = true;
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
 
-        mMockConnection.getConnectionCallback().onPerformScaleAction(TEST_DISPLAY, newScale);
+        mMockConnection.getConnectionCallback().onPerformScaleAction(
+                TEST_DISPLAY, newScale, updatePersistence);
 
-        verify(mMockCallback).onPerformScaleAction(eq(TEST_DISPLAY), eq(newScale));
+        verify(mMockCallback).onPerformScaleAction(
+                eq(TEST_DISPLAY), eq(newScale), eq(updatePersistence));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index dccacb4..24a628e 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -1342,6 +1342,10 @@
             Message copy = new Message();
             copy.copyFrom(msg);
             mMessages.add(copy);
+            if (msg.getCallback() != null) {
+                msg.getCallback().run();
+                msg.setCallback(null);
+            }
             return super.sendMessageAtTime(msg, uptimeMillis);
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index 31599ee..aba24fb 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -29,13 +29,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.BluetoothProfileConnectionInfo;
 import android.util.Log;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
@@ -54,7 +55,6 @@
     private static final String TAG = "AudioDeviceBrokerTest";
     private static final int MAX_MESSAGE_HANDLING_DELAY_MS = 100;
 
-    private Context mContext;
     // the actual class under test
     private AudioDeviceBroker mAudioDeviceBroker;
 
@@ -67,13 +67,13 @@
 
     @Before
     public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
 
         mMockAudioService = mock(AudioService.class);
         mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
         mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem));
         mSpySystemServer = spy(new NoOpSystemServerAdapter());
-        mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory,
+        mAudioDeviceBroker = new AudioDeviceBroker(context, mMockAudioService, mSpyDevInventory,
                 mSpySystemServer, mSpyAudioSystem);
         mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker);
 
@@ -197,6 +197,37 @@
                 any(Intent.class));
     }
 
+    /**
+     * Test that constructing an AdiDeviceState instance requires a non-null address for a
+     * wireless type, but can take null for a non-wireless type;
+     * @throws Exception
+     */
+    @Test
+    public void testAdiDeviceStateNullAddressCtor() throws Exception {
+        try {
+            new AdiDeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+                    AudioManager.DEVICE_OUT_SPEAKER, null);
+            new AdiDeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+                    AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, null);
+            Assert.fail();
+        } catch (NullPointerException e) { }
+    }
+
+    @Test
+    public void testAdiDeviceStateStringSerialization() throws Exception {
+        Log.i(TAG, "starting testAdiDeviceStateStringSerialization");
+        final AdiDeviceState devState = new AdiDeviceState(
+                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, AudioManager.DEVICE_OUT_SPEAKER, "bla");
+        devState.setHasHeadTracker(false);
+        devState.setHeadTrackerEnabled(false);
+        devState.setSAEnabled(true);
+        final String persistString = devState.toPersistableString();
+        final AdiDeviceState result = AdiDeviceState.fromPersistedString(persistString);
+        Log.i(TAG, "original:" + devState);
+        Log.i(TAG, "result  :" + result);
+        Assert.assertEquals(devState, result);
+    }
+
     private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection,
             boolean mockMediaPlayback, boolean guaranteeSingleConnection) throws Exception {
         when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC))
diff --git a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
index 3ad24de..ad09ef0 100644
--- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java
@@ -15,8 +15,6 @@
  */
 package com.android.server.audio;
 
-import com.android.server.audio.SpatializerHelper.SADeviceState;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.doNothing;
@@ -26,12 +24,12 @@
 
 import android.media.AudioAttributes;
 import android.media.AudioDeviceAttributes;
-import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 import android.media.AudioSystem;
 import android.util.Log;
 
 import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Assert;
@@ -55,72 +53,25 @@
 
     @Mock private AudioService mMockAudioService;
     @Spy private AudioSystemAdapter mSpyAudioSystem;
-    @Mock private AudioSystemAdapter mMockAudioSystem;
+    @Spy private AudioDeviceBroker mSpyDeviceBroker;
 
     @Before
     public void setUp() throws Exception {
         mMockAudioService = mock(AudioService.class);
-    }
 
-    /**
-     * Initializes mSpatHelper, the SpatizerHelper instance under test, to use the mock or spy
-     * AudioSystemAdapter
-     * @param useSpyAudioSystem true to use the spy adapter, mSpyAudioSystem, or false to use
-     *                          the mock adapter, mMockAudioSystem.
-     */
-    private void setUpSpatHelper(boolean useSpyAudioSystem) {
-        final AudioSystemAdapter asAdapter;
-        if (useSpyAudioSystem) {
-            mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
-            asAdapter = mSpyAudioSystem;
-            mMockAudioSystem = null;
-        } else {
-            mSpyAudioSystem = null;
-            mMockAudioSystem = mock(NoOpAudioSystemAdapter.class);
-            asAdapter = mMockAudioSystem;
-        }
-        mSpatHelper = new SpatializerHelper(mMockAudioService, asAdapter,
-                true /*binauralEnabledDefault*/,
-                true /*transauralEnabledDefault*/,
-                false /*headTrackingEnabledDefault*/);
-
-    }
-
-    /**
-     * Test that constructing an SADeviceState instance requires a non-null address for a
-     * wireless type, but can take null for a non-wireless type;
-     * @throws Exception
-     */
-    @Test
-    public void testSADeviceStateNullAddressCtor() throws Exception {
-        setUpSpatHelper(true /*useSpyAudioSystem*/);
-        try {
-            SADeviceState devState = new SADeviceState(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null);
-            devState = new SADeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, null);
-            Assert.fail();
-        } catch (NullPointerException e) { }
+        mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
+        mSpyDeviceBroker = spy(
+                new AudioDeviceBroker(
+                        InstrumentationRegistry.getInstrumentation().getTargetContext(),
+                        mMockAudioService, mSpyAudioSystem));
+        mSpatHelper = new SpatializerHelper(mMockAudioService, mSpyAudioSystem,
+                mSpyDeviceBroker, /*binauralEnabledDefault=*/true, /*transauralEnabledDefault=*/
+                true, /*headTrackingEnabledDefault*/false);
     }
 
     @Test
-    public void testSADeviceStateStringSerialization() throws Exception {
-        Log.i(TAG, "starting testSADeviceStateStringSerialization");
-        setUpSpatHelper(true /*useSpyAudioSystem*/);
-        final SADeviceState devState = new SADeviceState(
-                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "bla");
-        devState.mHasHeadTracker = false;
-        devState.mHeadTrackerEnabled = false;
-        devState.mEnabled = true;
-        final String persistString = devState.toPersistableString();
-        final SADeviceState result = SADeviceState.fromPersistedString(persistString);
-        Log.i(TAG, "original:" + devState);
-        Log.i(TAG, "result  :" + result);
-        Assert.assertEquals(devState, result);
-    }
-
-    @Test
-    public void testSADeviceSettings() throws Exception {
+    public void testAdiDeviceStateSettings() throws Exception {
         Log.i(TAG, "starting testSADeviceSettings");
-        setUpSpatHelper(true /*useSpyAudioSystem*/);
         final AudioDeviceAttributes dev1 =
                 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, "");
         final AudioDeviceAttributes dev2 =
@@ -128,7 +79,7 @@
         final AudioDeviceAttributes dev3 =
                 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "R2:D2:bloop");
 
-        doNothing().when(mMockAudioService).persistSpatialAudioDeviceSettings();
+        doNothing().when(mSpyDeviceBroker).persistAudioDeviceSettings();
         mSpatHelper.initForTest(true /*binaural*/, true /*transaural*/);
 
         // test with single device
@@ -163,11 +114,11 @@
      * the original one.
      */
     private void checkAddSettings() throws Exception {
-        String settings = mSpatHelper.getSADeviceSettings();
+        String settings = mSpyDeviceBroker.getDeviceSettings();
         Log.i(TAG, "device settings: " + settings);
-        mSpatHelper.clearSADevices();
-        mSpatHelper.setSADeviceSettings(settings);
-        String settingsRestored = mSpatHelper.getSADeviceSettings();
+        mSpyDeviceBroker.clearDeviceInventory();
+        mSpyDeviceBroker.setDeviceSettings(settings);
+        String settingsRestored = mSpyDeviceBroker.getDeviceSettings();
         Log.i(TAG, "device settingsRestored: " + settingsRestored);
         Assert.assertEquals(settings, settingsRestored);
     }
@@ -179,7 +130,6 @@
     @Test
     public void testNoRoutingCanBeSpatialized() throws Exception {
         Log.i(TAG, "Starting testNoRoutingCanBeSpatialized");
-        setUpSpatHelper(false /*useSpyAudioSystem*/);
         mSpatHelper.forceStateForTest(SpatializerHelper.STATE_ENABLED_AVAILABLE);
 
         final ArrayList<AudioDeviceAttributes> emptyList = new ArrayList<>(0);
@@ -191,12 +141,12 @@
                 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                 .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1).build();
 
-        when(mMockAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
+        when(mSpyAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
                 .thenReturn(emptyList);
         Assert.assertFalse("can be spatialized on empty routing",
                 mSpatHelper.canBeSpatialized(media, spatialFormat));
 
-        when(mMockAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
+        when(mSpyAudioSystem.getDevicesForAttributes(any(AudioAttributes.class), anyBoolean()))
                 .thenReturn(listWithNull);
         Assert.assertFalse("can be spatialized on null routing",
                 mSpatHelper.canBeSpatialized(media, spatialFormat));
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 0cfddd3..769be17 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -351,6 +351,8 @@
         assertEquals(startFingerprintNow ? BiometricSensor.STATE_AUTHENTICATING
                         : BiometricSensor.STATE_COOKIE_RETURNED,
                 session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
+        verify(mBiometricContext).updateContext((OperationContextExt) anyObject(),
+                eq(session.isCrypto()));
 
         // start fingerprint sensor if it was delayed
         if (!startFingerprintNow) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
new file mode 100644
index 0000000..746fb53
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+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 static java.util.Collections.emptySet;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.server.biometrics.sensors.BiometricNotification;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+
+@Presubmit
+@SmallTest
+public class AuthenticationStatsCollectorTest {
+
+    private AuthenticationStatsCollector mAuthenticationStatsCollector;
+    private static final float FRR_THRESHOLD = 0.2f;
+    private static final int USER_ID_1 = 1;
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private FingerprintManager mFingerprintManager;
+    @Mock
+    private FaceManager mFaceManager;
+    @Mock
+    private SharedPreferences mSharedPreferences;
+    @Mock
+    private BiometricNotification mBiometricNotification;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getFraction(eq(R.fraction.config_biometricNotificationFrrThreshold),
+                anyInt(), anyInt())).thenReturn(FRR_THRESHOLD);
+
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+
+        when(mContext.getSystemServiceName(FingerprintManager.class))
+                .thenReturn(Context.FINGERPRINT_SERVICE);
+        when(mContext.getSystemService(Context.FINGERPRINT_SERVICE))
+                .thenReturn(mFingerprintManager);
+        when(mContext.getSystemServiceName(FaceManager.class)).thenReturn(Context.FACE_SERVICE);
+        when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
+
+        when(mContext.getSharedPreferences(any(File.class), anyInt()))
+                .thenReturn(mSharedPreferences);
+        when(mSharedPreferences.getStringSet(anyString(), anySet())).thenReturn(emptySet());
+
+        mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+                0 /* modality */, mBiometricNotification);
+    }
+
+
+    @Test
+    public void authenticate_authenticationSucceeded_mapShouldBeUpdated() {
+        // Assert that the user doesn't exist in the map initially.
+        assertThat(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1)).isNull();
+
+        mAuthenticationStatsCollector.authenticate(USER_ID_1, true /* authenticated */);
+
+        AuthenticationStats authenticationStats =
+                mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
+        assertThat(authenticationStats.getUserId()).isEqualTo(USER_ID_1);
+        assertThat(authenticationStats.getTotalAttempts()).isEqualTo(1);
+        assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
+    }
+
+    @Test
+    public void authenticate_authenticationFailed_mapShouldBeUpdated() {
+        // Assert that the user doesn't exist in the map initially.
+        assertThat(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1)).isNull();
+
+        mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
+
+        AuthenticationStats authenticationStats =
+                mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
+
+        assertThat(authenticationStats.getUserId()).isEqualTo(USER_ID_1);
+        assertThat(authenticationStats.getTotalAttempts()).isEqualTo(1);
+        assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(1);
+        assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
+    }
+
+    @Test
+    public void authenticate_frrNotExceeded_notificationNotExceeded_shouldNotSendNotification() {
+
+        mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
+                new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
+                        40 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+                        0 /* modality */));
+
+        mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
+
+        // Assert that no notification should be sent.
+        verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
+        verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
+        // Assert that data has been reset.
+        AuthenticationStats authenticationStats = mAuthenticationStatsCollector
+                .getAuthenticationStatsForUser(USER_ID_1);
+        assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+    }
+
+    @Test
+    public void authenticate_frrExceeded_notificationExceeded_shouldNotSendNotification() {
+
+        mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
+                new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
+                        400 /* rejectedAttempts */, 2 /* enrollmentNotifications */,
+                        0 /* modality */));
+
+        mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
+
+        // Assert that no notification should be sent.
+        verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
+        verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
+        // Assert that data has been reset.
+        AuthenticationStats authenticationStats = mAuthenticationStatsCollector
+                .getAuthenticationStatsForUser(USER_ID_1);
+        assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+    }
+
+    @Test
+    public void authenticate_frrExceeded_bothBiometricsEnrolled_shouldNotSendNotification() {
+
+        mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
+                new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
+                        400 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+                        0 /* modality */));
+
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+                .thenReturn(true);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+
+        mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
+
+        // Assert that no notification should be sent.
+        verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
+        verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
+        // Assert that data has been reset.
+        AuthenticationStats authenticationStats = mAuthenticationStatsCollector
+                .getAuthenticationStatsForUser(USER_ID_1);
+        assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+    }
+
+    @Test
+    public void authenticate_frrExceeded_singleModality_shouldNotSendNotification() {
+
+        mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
+                new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
+                        400 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+                        0 /* modality */));
+
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+                .thenReturn(true);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
+        when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+
+        mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
+
+        // Assert that no notification should be sent.
+        verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
+        verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
+        // Assert that data has been reset.
+        AuthenticationStats authenticationStats = mAuthenticationStatsCollector
+                .getAuthenticationStatsForUser(USER_ID_1);
+        assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+    }
+
+    @Test
+    public void authenticate_frrExceeded_faceEnrolled_shouldSendFpNotification() {
+        mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
+                new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
+                        400 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+                        0 /* modality */));
+
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+                .thenReturn(true);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+
+        mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
+
+        // Assert that fingerprint enrollment notification should be sent.
+        verify(mBiometricNotification, times(1))
+                .sendFpEnrollNotification(mContext);
+        verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
+        // Assert that data has been reset.
+        AuthenticationStats authenticationStats = mAuthenticationStatsCollector
+                .getAuthenticationStatsForUser(USER_ID_1);
+        assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+    }
+
+    @Test
+    public void authenticate_frrExceeded_fpEnrolled_shouldSendFaceNotification() {
+        mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
+                new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
+                        400 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+                        0 /* modality */));
+
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
+                .thenReturn(true);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+
+        mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
+
+        // Assert that fingerprint enrollment notification should be sent.
+        verify(mBiometricNotification, times(1))
+                .sendFaceEnrollNotification(mContext);
+        verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
+        // Assert that data has been reset.
+        AuthenticationStats authenticationStats = mAuthenticationStatsCollector
+                .getAuthenticationStatsForUser(USER_ID_1);
+        assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+        assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
new file mode 100644
index 0000000..dde2a3c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+@Presubmit
+@SmallTest
+public class AuthenticationStatsPersisterTest {
+
+    @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+    private static final int USER_ID_1 = 1;
+    private static final int USER_ID_2 = 2;
+    private static final String USER_ID = "user_id";
+    private static final String FACE_ATTEMPTS = "face_attempts";
+    private static final String FACE_REJECTIONS = "face_rejections";
+    private static final String FINGERPRINT_ATTEMPTS = "fingerprint_attempts";
+    private static final String FINGERPRINT_REJECTIONS = "fingerprint_rejections";
+    private static final String ENROLLMENT_NOTIFICATIONS = "enrollment_notifications";
+    private static final String KEY = "frr_stats";
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private SharedPreferences mSharedPreferences;
+    @Mock
+    private SharedPreferences.Editor mEditor;
+    private AuthenticationStatsPersister mAuthenticationStatsPersister;
+
+    @Captor
+    private ArgumentCaptor<Set<String>> mStringSetArgumentCaptor;
+
+    @Before
+    public void setUp() {
+        when(mContext.getSharedPreferences(any(File.class), anyInt()))
+                .thenReturn(mSharedPreferences);
+        when(mSharedPreferences.edit()).thenReturn(mEditor);
+        when(mEditor.putStringSet(anyString(), anySet())).thenReturn(mEditor);
+
+        mAuthenticationStatsPersister = new AuthenticationStatsPersister(mContext);
+    }
+
+    @Test
+    public void getAllFrrStats_face_shouldListAllFrrStats() throws JSONException {
+        AuthenticationStats stats1 = new AuthenticationStats(USER_ID_1,
+                300 /* totalAttempts */, 10 /* rejectedAttempts */,
+                0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+        AuthenticationStats stats2 = new AuthenticationStats(USER_ID_2,
+                200 /* totalAttempts */, 20 /* rejectedAttempts */,
+                0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+        when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+                Set.of(buildFrrStats(stats1), buildFrrStats(stats2)));
+
+        List<AuthenticationStats> authenticationStatsList =
+                mAuthenticationStatsPersister.getAllFrrStats(BiometricsProtoEnums.MODALITY_FACE);
+
+        assertThat(authenticationStatsList.size()).isEqualTo(2);
+        AuthenticationStats expectedStats2 = new AuthenticationStats(USER_ID_2,
+                0 /* totalAttempts */, 0 /* rejectedAttempts */,
+                0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+        assertThat(authenticationStatsList).contains(stats1);
+        assertThat(authenticationStatsList).contains(expectedStats2);
+    }
+
+    @Test
+    public void getAllFrrStats_fingerprint_shouldListAllFrrStats() throws JSONException {
+        // User 1 with fingerprint authentication stats.
+        AuthenticationStats stats1 = new AuthenticationStats(USER_ID_1,
+                200 /* totalAttempts */, 20 /* rejectedAttempts */,
+                0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+        // User 2 without fingerprint authentication stats.
+        AuthenticationStats stats2 = new AuthenticationStats(USER_ID_2,
+                300 /* totalAttempts */, 10 /* rejectedAttempts */,
+                0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+        when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+                Set.of(buildFrrStats(stats1), buildFrrStats(stats2)));
+
+        List<AuthenticationStats> authenticationStatsList =
+                mAuthenticationStatsPersister
+                        .getAllFrrStats(BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
+        assertThat(authenticationStatsList.size()).isEqualTo(2);
+        AuthenticationStats expectedStats2 = new AuthenticationStats(USER_ID_2,
+                0 /* totalAttempts */, 0 /* rejectedAttempts */,
+                0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+        assertThat(authenticationStatsList).contains(stats1);
+        assertThat(authenticationStatsList).contains(expectedStats2);
+    }
+
+    @Test
+    public void persistFrrStats_newUser_face_shouldSuccess() throws JSONException {
+        AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
+                300 /* totalAttempts */, 10 /* rejectedAttempts */,
+                0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+
+        mAuthenticationStatsPersister.persistFrrStats(authenticationStats.getUserId(),
+                authenticationStats.getTotalAttempts(),
+                authenticationStats.getRejectedAttempts(),
+                authenticationStats.getEnrollmentNotifications(),
+                authenticationStats.getModality());
+
+        verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+        assertThat(mStringSetArgumentCaptor.getValue())
+                .contains(buildFrrStats(authenticationStats));
+    }
+
+    @Test
+    public void persistFrrStats_newUser_fingerprint_shouldSuccess() throws JSONException {
+        AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
+                300 /* totalAttempts */, 10 /* rejectedAttempts */,
+                0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
+        mAuthenticationStatsPersister.persistFrrStats(authenticationStats.getUserId(),
+                authenticationStats.getTotalAttempts(),
+                authenticationStats.getRejectedAttempts(),
+                authenticationStats.getEnrollmentNotifications(),
+                authenticationStats.getModality());
+
+        verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+        assertThat(mStringSetArgumentCaptor.getValue())
+                .contains(buildFrrStats(authenticationStats));
+    }
+
+    @Test
+    public void persistFrrStats_existingUser_shouldUpdateRecord() throws JSONException {
+        AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
+                300 /* totalAttempts */, 10 /* rejectedAttempts */,
+                0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+        AuthenticationStats newAuthenticationStats = new AuthenticationStats(USER_ID_1,
+                500 /* totalAttempts */, 30 /* rejectedAttempts */,
+                1 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+        when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+                Set.of(buildFrrStats(authenticationStats)));
+
+        mAuthenticationStatsPersister.persistFrrStats(newAuthenticationStats.getUserId(),
+                newAuthenticationStats.getTotalAttempts(),
+                newAuthenticationStats.getRejectedAttempts(),
+                newAuthenticationStats.getEnrollmentNotifications(),
+                newAuthenticationStats.getModality());
+
+        verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+        assertThat(mStringSetArgumentCaptor.getValue())
+                .contains(buildFrrStats(newAuthenticationStats));
+    }
+
+    @Test
+    public void persistFrrStats_existingUserWithFingerprint_faceAuthenticate_shouldUpdateRecord()
+            throws JSONException {
+        // User with fingerprint authentication stats.
+        AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
+                200 /* totalAttempts */, 20 /* rejectedAttempts */,
+                0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+        // The same user with face authentication stats.
+        AuthenticationStats newAuthenticationStats = new AuthenticationStats(USER_ID_1,
+                500 /* totalAttempts */, 30 /* rejectedAttempts */,
+                1 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+        when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+                Set.of(buildFrrStats(authenticationStats)));
+
+        mAuthenticationStatsPersister.persistFrrStats(newAuthenticationStats.getUserId(),
+                newAuthenticationStats.getTotalAttempts(),
+                newAuthenticationStats.getRejectedAttempts(),
+                newAuthenticationStats.getEnrollmentNotifications(),
+                newAuthenticationStats.getModality());
+
+        String expectedFrrStats = new JSONObject(buildFrrStats(authenticationStats))
+                .put(ENROLLMENT_NOTIFICATIONS, newAuthenticationStats.getEnrollmentNotifications())
+                .put(FACE_ATTEMPTS, newAuthenticationStats.getTotalAttempts())
+                .put(FACE_REJECTIONS, newAuthenticationStats.getRejectedAttempts()).toString();
+        verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+        assertThat(mStringSetArgumentCaptor.getValue()).contains(expectedFrrStats);
+    }
+
+    @Test
+    public void removeFrrStats_existingUser_shouldUpdateRecord() throws JSONException {
+        AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
+                300 /* totalAttempts */, 10 /* rejectedAttempts */,
+                0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+        when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+                Set.of(buildFrrStats(authenticationStats)));
+
+        mAuthenticationStatsPersister.removeFrrStats(USER_ID_1);
+
+        verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+        assertThat(mStringSetArgumentCaptor.getValue()).doesNotContain(authenticationStats);
+    }
+
+    private String buildFrrStats(AuthenticationStats authenticationStats)
+            throws JSONException {
+        if (authenticationStats.getModality() == BiometricsProtoEnums.MODALITY_FACE) {
+            return new JSONObject()
+                    .put(USER_ID, authenticationStats.getUserId())
+                    .put(FACE_ATTEMPTS, authenticationStats.getTotalAttempts())
+                    .put(FACE_REJECTIONS, authenticationStats.getRejectedAttempts())
+                    .put(ENROLLMENT_NOTIFICATIONS, authenticationStats.getEnrollmentNotifications())
+                    .toString();
+        } else if (authenticationStats.getModality() == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
+            return new JSONObject()
+                    .put(USER_ID, authenticationStats.getUserId())
+                    .put(FINGERPRINT_ATTEMPTS, authenticationStats.getTotalAttempts())
+                    .put(FINGERPRINT_REJECTIONS, authenticationStats.getRejectedAttempts())
+                    .put(ENROLLMENT_NOTIFICATIONS, authenticationStats.getEnrollmentNotifications())
+                    .toString();
+        }
+        return "";
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java
new file mode 100644
index 0000000..e8e72cb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class AuthenticationStatsTest {
+
+    @Test
+    public void authenticate_statsShouldBeUpdated() {
+        AuthenticationStats authenticationStats =
+                new AuthenticationStats(1 /* userId */ , 0 /* totalAttempts */,
+                        0 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+                        0 /* modality */);
+
+        authenticationStats.authenticate(true /* authenticated */);
+
+        assertEquals(authenticationStats.getTotalAttempts(), 1);
+        assertEquals(authenticationStats.getRejectedAttempts(), 0);
+
+        authenticationStats.authenticate(false /* authenticated */);
+
+        assertEquals(authenticationStats.getTotalAttempts(), 2);
+        assertEquals(authenticationStats.getRejectedAttempts(), 1);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index fc62e75..e79ac09 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_CREDENTIAL;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
@@ -116,9 +117,9 @@
     private static final String ERROR_LOCKOUT = "error_lockout";
     private static final String FACE_SUBTITLE = "face_subtitle";
     private static final String FINGERPRINT_SUBTITLE = "fingerprint_subtitle";
+    private static final String CREDENTIAL_SUBTITLE = "credential_subtitle";
     private static final String DEFAULT_SUBTITLE = "default_subtitle";
 
-
     private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty";
 
     private static final int SENSOR_ID_FINGERPRINT = 0;
@@ -143,6 +144,8 @@
     @Mock
     IBiometricAuthenticator mFaceAuthenticator;
     @Mock
+    IBiometricAuthenticator mCredentialAuthenticator;
+    @Mock
     ITrustManager mTrustManager;
     @Mock
     DevicePolicyManager mDevicePolicyManager;
@@ -196,10 +199,12 @@
                 .thenReturn(ERROR_NOT_RECOGNIZED);
         when(mResources.getString(R.string.biometric_error_user_canceled))
                 .thenReturn(ERROR_USER_CANCELED);
-        when(mContext.getString(R.string.biometric_dialog_face_subtitle))
+        when(mContext.getString(R.string.face_dialog_default_subtitle))
                 .thenReturn(FACE_SUBTITLE);
-        when(mContext.getString(R.string.biometric_dialog_fingerprint_subtitle))
+        when(mContext.getString(R.string.fingerprint_dialog_default_subtitle))
                 .thenReturn(FINGERPRINT_SUBTITLE);
+        when(mContext.getString(R.string.screen_lock_dialog_default_subtitle))
+                .thenReturn(CREDENTIAL_SUBTITLE);
         when(mContext.getString(R.string.biometric_dialog_default_subtitle))
                 .thenReturn(DEFAULT_SUBTITLE);
 
@@ -292,7 +297,8 @@
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                Authenticators.DEVICE_CREDENTIAL);
+                Authenticators.DEVICE_CREDENTIAL, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(BiometricAuthenticator.TYPE_CREDENTIAL),
@@ -312,7 +318,8 @@
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                Authenticators.DEVICE_CREDENTIAL);
+                Authenticators.DEVICE_CREDENTIAL, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         assertNotNull(mBiometricService.mAuthSession);
@@ -338,7 +345,8 @@
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(BiometricAuthenticator.TYPE_NONE),
@@ -357,7 +365,8 @@
                 mFingerprintAuthenticator);
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(TYPE_FINGERPRINT),
@@ -370,7 +379,8 @@
         setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                Authenticators.BIOMETRIC_STRONG);
+                Authenticators.BIOMETRIC_STRONG, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(BiometricAuthenticator.TYPE_NONE),
@@ -429,7 +439,8 @@
                 mFingerprintAuthenticator);
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(TYPE_FINGERPRINT),
@@ -441,9 +452,9 @@
     public void testAuthenticateFace_shouldShowSubtitleForFace() throws Exception {
         setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
 
-        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */,
-                null);
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                null /* authenticators */, true /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         assertEquals(FACE_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
@@ -453,9 +464,9 @@
     public void testAuthenticateFingerprint_shouldShowSubtitleForFingerprint() throws Exception {
         setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
 
-        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */,
-                null);
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                null /* authenticators */, true /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         assertEquals(FINGERPRINT_SUBTITLE,
@@ -463,6 +474,19 @@
     }
 
     @Test
+    public void testAuthenticateFingerprint_shouldShowSubtitleForCredential() throws Exception {
+        setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL);
+
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                null /* authenticators */, true /* useDefaultSubtitle */,
+                true /* deviceCredentialAllowed */);
+        waitForIdle();
+
+        assertEquals(CREDENTIAL_SUBTITLE,
+                mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+    }
+
+    @Test
     public void testAuthenticateBothFpAndFace_shouldShowDefaultSubtitle() throws Exception {
         final int[] modalities = new int[] {
                 TYPE_FINGERPRINT,
@@ -476,9 +500,9 @@
 
         setupAuthForMultiple(modalities, strengths);
 
-        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */,
-                null);
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                null /* authenticators */, true /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         assertEquals(DEFAULT_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
@@ -492,7 +516,8 @@
         // Disabled in user settings receives onError
         when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(BiometricAuthenticator.TYPE_NONE),
@@ -506,7 +531,8 @@
                 anyInt() /* modality */, anyInt() /* userId */))
                 .thenReturn(true);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
         final byte[] HAT = generateRandomHAT();
@@ -524,7 +550,8 @@
                 anyInt() /* modality */, anyInt() /* userId */))
                 .thenReturn(false);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
                 SENSOR_ID_FACE,
@@ -552,7 +579,8 @@
             throws Exception {
         // Start testing the happy path
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         // Creates a pending auth session with the correct initial states
@@ -632,7 +660,8 @@
                 .thenReturn(true);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                 true /* requireConfirmation */,
-                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK,
+                false /* useDefaultSubtitle*/, false /* deviceCredentialAllowed */);
         waitForIdle();
 
         assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
@@ -702,7 +731,8 @@
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                 true /* requireConfirmation */,
-                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG);
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG,
+                false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */);
         waitForIdle();
 
         verify(mReceiver1).onError(anyInt() /* modality */,
@@ -754,7 +784,8 @@
                 false /* requireConfirmation */, null /* authenticators */);
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver2, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         verify(mReceiver1).onError(
@@ -887,7 +918,8 @@
         setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */,
-                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK,
+                false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */);
         waitForIdle();
 
         assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState());
@@ -920,8 +952,9 @@
     public void testErrorFromHal_whilePreparingAuthentication_credentialNotAllowed()
             throws Exception {
         setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
-        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, null /* authenticators */);
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         mBiometricService.mAuthSession.mSensorReceiver.onError(
@@ -957,8 +990,9 @@
         setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
                 .thenReturn(lockoutMode);
-        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, null /* authenticators */);
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         // Modality and error are sent
@@ -996,8 +1030,9 @@
         when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
                 .thenReturn(lockoutMode);
         when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
-        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, null /* authenticators */);
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         // The lockout error should be sent, instead of ERROR_NONE_ENROLLED. See b/286923477.
@@ -1014,7 +1049,8 @@
                 .thenReturn(LockoutTracker.LOCKOUT_PERMANENT);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */,
-                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG);
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG,
+                false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */);
         waitForIdle();
 
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
@@ -1503,7 +1539,8 @@
         assertEquals(BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
                 invokeCanAuthenticate(mBiometricService, authenticators));
         long requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, authenticators);
+                false /* requireConfirmation */, authenticators, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(TYPE_FINGERPRINT),
@@ -1539,7 +1576,8 @@
                 invokeCanAuthenticate(mBiometricService, authenticators));
         requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */,
-                authenticators);
+                authenticators, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         assertTrue(Utils.isCredentialRequested(mBiometricService.mAuthSession.mPromptInfo));
         verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
@@ -1749,6 +1787,11 @@
             mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality, strength,
                     mFaceAuthenticator);
         }
+
+        if ((modality & TYPE_CREDENTIAL) != 0) {
+            when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
+                    .thenReturn(true);
+        }
     }
 
     // TODO: Reduce duplicated code, currently we cannot start the BiometricService in setUp() for
@@ -1799,7 +1842,8 @@
             Integer authenticators) throws Exception {
         // Request auth, creates a pending session
         final long requestId = invokeAuthenticate(
-                service, receiver, requireConfirmation, authenticators);
+                service, receiver, requireConfirmation, authenticators,
+                false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */);
         waitForIdle();
 
         startPendingAuthSession(mBiometricService);
@@ -1827,7 +1871,8 @@
 
     private static long invokeAuthenticate(IBiometricService.Stub service,
             IBiometricServiceReceiver receiver, boolean requireConfirmation,
-            Integer authenticators) throws Exception {
+            Integer authenticators, boolean useDefaultSubtitle,
+            boolean deviceCredentialAllowed) throws Exception {
         return service.authenticate(
                 new Binder() /* token */,
                 0 /* operationId */,
@@ -1835,7 +1880,8 @@
                 receiver,
                 TEST_PACKAGE_NAME /* packageName */,
                 createTestPromptInfo(requireConfirmation, authenticators,
-                        false /* checkDevicePolicy */));
+                        false /* checkDevicePolicy */, useDefaultSubtitle,
+                        deviceCredentialAllowed));
     }
 
     private static long invokeAuthenticateForWorkApp(IBiometricService.Stub service,
@@ -1847,16 +1893,19 @@
                 receiver,
                 TEST_PACKAGE_NAME /* packageName */,
                 createTestPromptInfo(false /* requireConfirmation */, authenticators,
-                        true /* checkDevicePolicy */));
+                        true /* checkDevicePolicy */, false /* useDefaultSubtitle */,
+                        false /* deviceCredentialAllowed */));
     }
 
     private static PromptInfo createTestPromptInfo(
             boolean requireConfirmation,
             Integer authenticators,
-            boolean checkDevicePolicy) {
+            boolean checkDevicePolicy,
+            boolean useDefaultSubtitle,
+            boolean deviceCredentialAllowed) {
         final PromptInfo promptInfo = new PromptInfo();
         promptInfo.setConfirmationRequested(requireConfirmation);
-        promptInfo.setUseDefaultSubtitle(true);
+        promptInfo.setUseDefaultSubtitle(useDefaultSubtitle);
 
         if (authenticators != null) {
             promptInfo.setAuthenticators(authenticators);
@@ -1864,6 +1913,7 @@
         if (checkDevicePolicy) {
             promptInfo.setDisallowBiometricsIfPolicyExists(checkDevicePolicy);
         }
+        promptInfo.setDeviceCredentialAllowed(deviceCredentialAllowed);
         return promptInfo;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index 612f717..a508718 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -39,6 +39,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 
 import org.junit.Before;
@@ -65,6 +66,8 @@
     @Mock
     private BiometricFrameworkStatsLogger mSink;
     @Mock
+    private AuthenticationStatsCollector mAuthenticationStatsCollector;
+    @Mock
     private SensorManager mSensorManager;
     @Mock
     private BaseClientMonitor mClient;
@@ -87,7 +90,8 @@
     }
 
     private BiometricLogger createLogger(int statsModality, int statsAction, int statsClient) {
-        return new BiometricLogger(statsModality, statsAction, statsClient, mSink, mSensorManager);
+        return new BiometricLogger(statsModality, statsAction, statsClient, mSink,
+                mAuthenticationStatsCollector, mSensorManager);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
index 21c9c75..5012335 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
@@ -61,7 +61,7 @@
 
     @Test
     public void noopWhenBothNull() {
-        final SensorOverlays useless = new SensorOverlays(null, null, null);
+        final SensorOverlays useless = new SensorOverlays(null, null);
         useless.show(SENSOR_ID, 2, null);
         useless.hide(SENSOR_ID);
     }
@@ -69,12 +69,12 @@
     @Test
     public void testProvidesUdfps() {
         final List<IUdfpsOverlayController> udfps = new ArrayList<>();
-        SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController, null);
+        SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController);
 
         sensorOverlays.ifUdfps(udfps::add);
         assertThat(udfps).isEmpty();
 
-        sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController, null);
+        sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController);
         sensorOverlays.ifUdfps(udfps::add);
         assertThat(udfps).containsExactly(mUdfpsOverlayController);
     }
@@ -96,7 +96,7 @@
 
     private void testShow(IUdfpsOverlayController udfps, ISidefpsController sidefps)
             throws Exception {
-        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps, null);
+        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
         final int reason = BiometricOverlayConstants.REASON_UNKNOWN;
         sensorOverlays.show(SENSOR_ID, reason, mAcquisitionClient);
 
@@ -126,7 +126,7 @@
 
     private void testHide(IUdfpsOverlayController udfps, ISidefpsController sidefps)
             throws Exception {
-        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps, null);
+        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
         sensorOverlays.hide(SENSOR_ID);
 
         if (udfps != null) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 31a58cd..f43120d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.biometrics.common.CommonProps;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
@@ -36,10 +37,10 @@
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -60,11 +61,15 @@
 
     private static final String TAG = "FaceProviderTest";
 
+    private static final float FRR_THRESHOLD = 0.2f;
+
     @Mock
     private Context mContext;
     @Mock
     private UserManager mUserManager;
     @Mock
+    private Resources mResources;
+    @Mock
     private IFace mDaemon;
     @Mock
     private BiometricContext mBiometricContext;
@@ -73,7 +78,7 @@
 
     private SensorProps[] mSensorProps;
     private LockoutResetDispatcher mLockoutResetDispatcher;
-    private TestableFaceProvider mFaceProvider;
+    private FaceProvider mFaceProvider;
 
     private static void waitForIdle() {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -87,6 +92,10 @@
         when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
         when(mDaemon.createSession(anyInt(), anyInt(), any())).thenReturn(mock(ISession.class));
 
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+                .thenReturn(FRR_THRESHOLD);
+
         final SensorProps sensor1 = new SensorProps();
         sensor1.commonProps = new CommonProps();
         sensor1.commonProps.sensorId = 0;
@@ -98,8 +107,9 @@
 
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
-        mFaceProvider = new TestableFaceProvider(mDaemon, mContext, mBiometricStateCallback,
-                mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext);
+        mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
+                mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext,
+                mDaemon);
     }
 
     @Test
@@ -130,6 +140,7 @@
         for (SensorProps prop : mSensorProps) {
             final BiometricScheduler scheduler =
                     mFaceProvider.mFaceSensors.get(prop.commonProps.sensorId).getScheduler();
+            scheduler.reset();
             for (int i = 0; i < numFakeOperations; i++) {
                 final HalClientMonitor testMonitor = mock(HalClientMonitor.class);
                 when(testMonitor.getFreshDaemon()).thenReturn(new Object());
@@ -142,7 +153,7 @@
         for (SensorProps prop : mSensorProps) {
             final BiometricScheduler scheduler =
                     mFaceProvider.mFaceSensors.get(prop.commonProps.sensorId).getScheduler();
-            assertEquals(numFakeOperations, scheduler.getCurrentPendingCount());
+            assertEquals(numFakeOperations - 1, scheduler.getCurrentPendingCount());
             assertNotNull(scheduler.getCurrentClient());
         }
 
@@ -159,25 +170,4 @@
             assertEquals(0, scheduler.getCurrentPendingCount());
         }
     }
-
-    private static class TestableFaceProvider extends FaceProvider {
-        private final IFace mDaemon;
-
-        TestableFaceProvider(@NonNull IFace daemon,
-                @NonNull Context context,
-                @NonNull BiometricStateCallback biometricStateCallback,
-                @NonNull SensorProps[] props,
-                @NonNull String halInstanceName,
-                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-                @NonNull BiometricContext biometricContext) {
-            super(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
-                    biometricContext);
-            mDaemon = daemon;
-        }
-
-        @Override
-        synchronized IFace getHalInstance() {
-            return mDaemon;
-        }
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index d174533..e558c4d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.face.FaceSensorProperties;
@@ -41,6 +42,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -65,12 +67,15 @@
     private static final String TAG = "Face10Test";
     private static final int SENSOR_ID = 1;
     private static final int USER_ID = 20;
+    private static final float FRR_THRESHOLD = 0.2f;
 
     @Mock
     private Context mContext;
     @Mock
     private UserManager mUserManager;
     @Mock
+    private Resources mResources;
+    @Mock
     private BiometricScheduler mScheduler;
     @Mock
     private BiometricContext mBiometricContext;
@@ -93,6 +98,10 @@
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
         when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
 
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+                .thenReturn(FRR_THRESHOLD);
+
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
         final int maxEnrollmentsPerUser = 1;
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 c383a96..46af905 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
@@ -436,7 +436,7 @@
                 mBiometricLogger, mBiometricContext,
                 true /* isStrongBiometric */,
                 null /* taskStackListener */, null /* lockoutCache */,
-                mUdfpsOverlayController, mSideFpsController, null, allowBackgroundAuthentication,
+                mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,
                 mSensorProps,
                 new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) {
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 6dfdd87..20d5f93 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -163,6 +163,6 @@
                         .setOpPackageName("a-test")
                         .build(),
                 mBiometricLogger, mBiometricContext,
-                mUdfpsOverlayController, null, true /* isStrongBiometric */);
+                mUdfpsOverlayController, true /* isStrongBiometric */);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 3c89278..ef25380 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -296,6 +296,6 @@
         mClientMonitorCallbackConverter, 0 /* userId */,
         HAT, "owner", mBiometricUtils, 8 /* sensorId */,
         mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController,
-        mSideFpsController, null, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
+        mSideFpsController, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 9c01de6..8f6efff 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -39,7 +39,6 @@
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
@@ -81,7 +80,7 @@
 
     private SensorProps[] mSensorProps;
     private LockoutResetDispatcher mLockoutResetDispatcher;
-    private TestableFingerprintProvider mFingerprintProvider;
+    private FingerprintProvider mFingerprintProvider;
 
     private static void waitForIdle() {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -110,17 +109,13 @@
 
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
-        mFingerprintProvider = new TestableFingerprintProvider(mDaemon, mContext,
+        mFingerprintProvider = new FingerprintProvider(mContext,
                 mBiometricStateCallback, mSensorProps, TAG, mLockoutResetDispatcher,
-                mGestureAvailabilityDispatcher, mBiometricContext);
+                mGestureAvailabilityDispatcher, mBiometricContext, mDaemon);
     }
 
     @Test
     public void testAddingSensors() {
-        mFingerprintProvider = new TestableFingerprintProvider(mDaemon, mContext,
-                mBiometricStateCallback, mSensorProps, TAG, mLockoutResetDispatcher,
-                mGestureAvailabilityDispatcher, mBiometricContext);
-
         waitForIdle();
 
         for (SensorProps prop : mSensorProps) {
@@ -147,6 +142,7 @@
             final BiometricScheduler scheduler =
                     mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
                             .getScheduler();
+            scheduler.reset();
             for (int i = 0; i < numFakeOperations; i++) {
                 final HalClientMonitor testMonitor = mock(HalClientMonitor.class);
                 when(testMonitor.getFreshDaemon()).thenReturn(new Object());
@@ -160,7 +156,7 @@
             final BiometricScheduler scheduler =
                     mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
                             .getScheduler();
-            assertEquals(numFakeOperations, scheduler.getCurrentPendingCount());
+            assertEquals(numFakeOperations - 1, scheduler.getCurrentPendingCount());
             assertNotNull(scheduler.getCurrentClient());
         }
 
@@ -178,26 +174,4 @@
             assertEquals(0, scheduler.getCurrentPendingCount());
         }
     }
-
-    private static class TestableFingerprintProvider extends FingerprintProvider {
-        private final IFingerprint mDaemon;
-
-        TestableFingerprintProvider(@NonNull IFingerprint daemon,
-                @NonNull Context context,
-                @NonNull BiometricStateCallback biometricStateCallback,
-                @NonNull SensorProps[] props,
-                @NonNull String halInstanceName,
-                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
-                @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
-                @NonNull BiometricContext biometricContext) {
-            super(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
-                    gestureAvailabilityDispatcher, biometricContext);
-            mDaemon = daemon;
-        }
-
-        @Override
-        synchronized IFingerprint getHalInstance() {
-            return mDaemon;
-        }
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/utils/OWNERS b/services/tests/servicestests/src/com/android/server/companion/utils/OWNERS
new file mode 100644
index 0000000..008a53f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/utils/OWNERS
@@ -0,0 +1 @@
+include /services/companion/java/com/android/server/companion/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java
new file mode 100644
index 0000000..56bb2eb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.utils;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.res.Resources;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.companion.PackageUtils;
+import com.android.server.pm.pkg.AndroidPackage;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+public class PackageUtilsTest {
+
+    private static final String[] ALLOWED_PACKAGE_NAMES = new String[]{
+            "allowed_app",
+    };
+
+    private static final Signature[] ALLOWED_PACKAGE_SIGNATURES = new Signature[]{
+            new Signature("001122"),
+    };
+
+    private static final String[] DISALLOWED_PACKAGE_NAMES = new String[]{
+            "disallowed_app",
+    };
+
+    private static final Signature[] DISALLOWED_PACKAGE_SIGNATURES = new Signature[]{
+            new Signature("778899"),
+    };
+
+    @Test
+    public void isAllowlisted_true() {
+        Context context = spy(
+                new ContextWrapper(
+                        InstrumentationRegistry.getInstrumentation().getTargetContext()));
+        final Resources res = spy(context.getResources());
+        doReturn(ALLOWED_PACKAGE_NAMES).when(res).getStringArray(
+                com.android.internal.R.array.config_companionDevicePackages);
+        doReturn(android.util.PackageUtils.computeSignaturesSha256Digests(
+                ALLOWED_PACKAGE_SIGNATURES)).when(res).getStringArray(
+                com.android.internal.R.array.config_companionDeviceCerts);
+        doReturn(res).when(context).getResources();
+
+        PackageManagerInternal pm = mock(PackageManagerInternal.class);
+        AndroidPackage ap = mock(AndroidPackage.class);
+        SigningDetails sd = new SigningDetails(
+                ALLOWED_PACKAGE_SIGNATURES,
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                null,
+                null);
+        doReturn(ap).when(pm).getPackage(ALLOWED_PACKAGE_NAMES[0]);
+        doReturn(sd).when(ap).getSigningDetails();
+
+        assertTrue(PackageUtils.isPackageAllowlisted(context, pm, ALLOWED_PACKAGE_NAMES[0]));
+    }
+
+    @Test
+    public void isAllowlisted_package_disallowed() {
+        Context context = spy(
+                new ContextWrapper(
+                        InstrumentationRegistry.getInstrumentation().getTargetContext()));
+        final Resources res = spy(context.getResources());
+        doReturn(ALLOWED_PACKAGE_NAMES).when(res).getStringArray(
+                com.android.internal.R.array.config_companionDevicePackages);
+        doReturn(android.util.PackageUtils.computeSignaturesSha256Digests(
+                ALLOWED_PACKAGE_SIGNATURES)).when(res).getStringArray(
+                com.android.internal.R.array.config_companionDeviceCerts);
+        doReturn(res).when(context).getResources();
+
+        PackageManagerInternal pm = mock(PackageManagerInternal.class);
+        AndroidPackage ap = mock(AndroidPackage.class);
+        SigningDetails sd = new SigningDetails(
+                ALLOWED_PACKAGE_SIGNATURES, // Giving the package a wrong signature
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                null,
+                null);
+        doReturn(ap).when(pm).getPackage(DISALLOWED_PACKAGE_NAMES[0]);
+        doReturn(sd).when(ap).getSigningDetails();
+
+        assertFalse(PackageUtils.isPackageAllowlisted(context, pm, DISALLOWED_PACKAGE_NAMES[0]));
+    }
+
+    @Test
+    public void isAllowlisted_signature_mismatch() {
+        Context context = spy(
+                new ContextWrapper(
+                        InstrumentationRegistry.getInstrumentation().getTargetContext()));
+        final Resources res = spy(context.getResources());
+        doReturn(ALLOWED_PACKAGE_NAMES).when(res).getStringArray(
+                com.android.internal.R.array.config_companionDevicePackages);
+        doReturn(android.util.PackageUtils.computeSignaturesSha256Digests(
+                ALLOWED_PACKAGE_SIGNATURES)).when(res).getStringArray(
+                com.android.internal.R.array.config_companionDeviceCerts);
+        doReturn(res).when(context).getResources();
+
+        PackageManagerInternal pm = mock(PackageManagerInternal.class);
+        AndroidPackage ap = mock(AndroidPackage.class);
+        SigningDetails sd = new SigningDetails(
+                DISALLOWED_PACKAGE_SIGNATURES, // Giving the package a wrong signature
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                null,
+                null);
+        doReturn(ap).when(pm).getPackage(ALLOWED_PACKAGE_NAMES[0]);
+        doReturn(sd).when(ap).getSigningDetails();
+
+        assertFalse(PackageUtils.isPackageAllowlisted(context, pm, ALLOWED_PACKAGE_NAMES[0]));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 8884dba..2336374 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -789,13 +789,6 @@
     }
 
     @Test
-    public void onVirtualDisplayRemovedLocked_unknownDisplayId_throwsException() {
-        final int unknownDisplayId = 999;
-        assertThrows(IllegalStateException.class,
-                () -> mDeviceImpl.onVirtualDisplayRemoved(unknownDisplayId));
-    }
-
-    @Test
     public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
         addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
         ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
new file mode 100644
index 0000000..e7777f7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) 2013 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.contentcapture;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.UserInfo;
+import android.service.contentcapture.ContentCaptureServiceInfo;
+import android.view.contentcapture.ContentCaptureEvent;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionConsentManager;
+import com.android.server.contentprotection.RemoteContentProtectionService;
+import com.android.server.pm.UserManagerInternal;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test for {@link ContentCaptureManagerService}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:com.android.server.contentcapture.ContentCaptureManagerServiceTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@SuppressWarnings("GuardedBy") // Service not really running, no need to expose locks
+public class ContentCaptureManagerServiceTest {
+
+    private static final int USER_ID = 1234;
+
+    private static final String PACKAGE_NAME = "com.test.package";
+
+    private static final ComponentName COMPONENT_NAME =
+            new ComponentName(PACKAGE_NAME, "TestClass");
+
+    private static final ContentCaptureEvent EVENT =
+            new ContentCaptureEvent(/* sessionId= */ 100, /* type= */ 200);
+
+    private static final ParceledListSlice<ContentCaptureEvent> PARCELED_EVENTS =
+            new ParceledListSlice<>(ImmutableList.of(EVENT));
+
+    private static final Context sContext = ApplicationProvider.getApplicationContext();
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private UserManagerInternal mMockUserManagerInternal;
+
+    @Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager;
+
+    @Mock private ContentCaptureServiceInfo mMockContentCaptureServiceInfo;
+
+    @Mock private RemoteContentProtectionService mMockRemoteContentProtectionService;
+
+    @Mock private ContentProtectionConsentManager mMockContentProtectionConsentManager;
+
+    private boolean mDevCfgEnableContentProtectionReceiver;
+
+    private int mContentProtectionBlocklistManagersCreated;
+
+    private int mContentProtectionServiceInfosCreated;
+
+    private int mRemoteContentProtectionServicesCreated;
+
+    private int mContentProtectionConsentManagersCreated;
+
+    private String mConfigDefaultContentProtectionService = COMPONENT_NAME.flattenToString();
+
+    private boolean mContentProtectionServiceInfoConstructorShouldThrow;
+
+    private ContentCaptureManagerService mContentCaptureManagerService;
+
+    @Before
+    public void setup() {
+        when(mMockUserManagerInternal.getUserInfos()).thenReturn(new UserInfo[0]);
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+    }
+
+    @Test
+    public void constructor_contentProtection_flagDisabled_noManagers() {
+        assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
+        verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
+    }
+
+    @Test
+    public void constructor_contentProtection_componentNameNull_noManagers() {
+        mConfigDefaultContentProtectionService = null;
+
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
+        verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
+    }
+
+    @Test
+    public void constructor_contentProtection_componentNameBlank_noManagers() {
+        mConfigDefaultContentProtectionService = "   ";
+
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
+        verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
+    }
+
+    @Test
+    public void constructor_contentProtection_enabled_createsManagers() {
+        mDevCfgEnableContentProtectionReceiver = true;
+
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionConsentManagersCreated).isEqualTo(1);
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
+        verifyZeroInteractions(mMockContentProtectionConsentManager);
+    }
+
+    @Test
+    public void setFineTuneParamsFromDeviceConfig_doesNotUpdateContentProtectionBlocklist() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mDevCfgContentProtectionAppsBlocklistSize += 100;
+        verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
+
+        mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+
+        verifyNoMoreInteractions(mMockContentProtectionBlocklistManager);
+    }
+
+    @Test
+    public void getOptions_contentCaptureDisabled_contentProtectionDisabled() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        ContentCaptureOptions actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isNull();
+        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void getOptions_contentCaptureDisabled_contentProtectionEnabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
+        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        ContentCaptureOptions actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isNotNull();
+        assertThat(actual.enableReceiver).isFalse();
+        assertThat(actual.contentProtectionOptions).isNotNull();
+        assertThat(actual.contentProtectionOptions.enableReceiver).isTrue();
+        assertThat(actual.whitelistedComponents).isNull();
+    }
+
+    @Test
+    public void getOptions_contentCaptureEnabled_contentProtectionDisabled() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+        ContentCaptureOptions actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isNotNull();
+        assertThat(actual.enableReceiver).isTrue();
+        assertThat(actual.contentProtectionOptions).isNotNull();
+        assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
+        assertThat(actual.whitelistedComponents).isNull();
+        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void getOptions_contentCaptureEnabled_contentProtectionEnabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
+        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+        ContentCaptureOptions actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isNotNull();
+        assertThat(actual.enableReceiver).isTrue();
+        assertThat(actual.contentProtectionOptions).isNotNull();
+        assertThat(actual.contentProtectionOptions.enableReceiver).isTrue();
+        assertThat(actual.whitelistedComponents).isNull();
+    }
+
+    @Test
+    public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionNotGranted() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionDisabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+    }
+
+    @Test
+    public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionEnabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
+        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void isWhitelisted_packageName_contentCaptureEnabled_contentProtectionNotChecked() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isTrue();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionNotGranted() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, COMPONENT_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionDisabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, COMPONENT_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+    }
+
+    @Test
+    public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionEnabled() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
+        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, COMPONENT_NAME);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void isWhitelisted_componentName_contentCaptureEnabled_contentProtectionNotChecked() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+                USER_ID, /* packageNames= */ null, ImmutableList.of(COMPONENT_NAME));
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, COMPONENT_NAME);
+
+        assertThat(actual).isTrue();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isContentProtectionReceiverEnabled_withoutManagers() {
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void isContentProtectionReceiverEnabled_disabledWithFlag() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false;
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
+    public void onLoginDetected_disabledAfterConstructor() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+        mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false;
+
+        mContentCaptureManagerService
+                .getContentCaptureManagerServiceStub()
+                .onLoginDetected(PARCELED_EVENTS);
+
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+        assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(0);
+        verifyZeroInteractions(mMockRemoteContentProtectionService);
+    }
+
+    @Test
+    public void onLoginDetected_invalidPermissions() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentProtectionServiceInfoConstructorShouldThrow = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        mContentCaptureManagerService
+                .getContentCaptureManagerServiceStub()
+                .onLoginDetected(PARCELED_EVENTS);
+
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1);
+        assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(0);
+        verifyZeroInteractions(mMockRemoteContentProtectionService);
+    }
+
+    @Test
+    public void onLoginDetected_enabled() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        mContentCaptureManagerService
+                .getContentCaptureManagerServiceStub()
+                .onLoginDetected(PARCELED_EVENTS);
+
+        assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1);
+        assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(1);
+        verify(mMockRemoteContentProtectionService).onLoginDetected(PARCELED_EVENTS);
+    }
+
+    private class TestContentCaptureManagerService extends ContentCaptureManagerService {
+
+        TestContentCaptureManagerService() {
+            super(sContext);
+            this.mDevCfgEnableContentProtectionReceiver =
+                    ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+        }
+
+        @Override
+        protected boolean getEnableContentProtectionReceiverLocked() {
+            return ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+        }
+
+        @Override
+        protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() {
+            mContentProtectionBlocklistManagersCreated++;
+            return mMockContentProtectionBlocklistManager;
+        }
+
+        @Override
+        protected String getContentProtectionServiceFlatComponentName() {
+            return mConfigDefaultContentProtectionService;
+        }
+
+        @Override
+        protected ContentCaptureServiceInfo createContentProtectionServiceInfo(
+                @NonNull ComponentName componentName) throws PackageManager.NameNotFoundException {
+            mContentProtectionServiceInfosCreated++;
+            if (mContentProtectionServiceInfoConstructorShouldThrow) {
+                throw new RuntimeException("TEST RUNTIME EXCEPTION");
+            }
+            return mMockContentCaptureServiceInfo;
+        }
+
+        @Override
+        protected RemoteContentProtectionService createRemoteContentProtectionService(
+                @NonNull ComponentName componentName) {
+            mRemoteContentProtectionServicesCreated++;
+            return mMockRemoteContentProtectionService;
+        }
+
+        @Override
+        protected ContentProtectionConsentManager createContentProtectionConsentManager() {
+            mContentProtectionConsentManagersCreated++;
+            return mMockContentProtectionConsentManager;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
new file mode 100644
index 0000000..0ffa891
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.contentcapture"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
new file mode 100644
index 0000000..ba9956a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test for {@link ContentProtectionBlocklistManager}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:
+ * com.android.server.contentprotection.ContentProtectionBlocklistManagerTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionBlocklistManagerTest {
+
+    private static final String FIRST_PACKAGE_NAME = "com.test.first.package.name";
+
+    private static final String SECOND_PACKAGE_NAME = "com.test.second.package.name";
+
+    private static final String UNLISTED_PACKAGE_NAME = "com.test.unlisted.package.name";
+
+    private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
+            "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
+
+    private static final PackageInfo PACKAGE_INFO = new PackageInfo();
+
+    private static final List<String> LINES =
+            ImmutableList.of(FIRST_PACKAGE_NAME, SECOND_PACKAGE_NAME);
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private ContentProtectionPackageManager mMockContentProtectionPackageManager;
+
+    private final List<String> mReadRawFiles = new ArrayList<>();
+
+    private ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
+
+    @Before
+    public void setup() {
+        mContentProtectionBlocklistManager = new TestContentProtectionBlocklistManager();
+    }
+
+    @Test
+    public void isAllowed_blocklistNotLoaded() {
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        assertThat(mReadRawFiles).isEmpty();
+        verifyZeroInteractions(mMockContentProtectionPackageManager);
+    }
+
+    @Test
+    public void isAllowed_inBlocklist() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verifyZeroInteractions(mMockContentProtectionPackageManager);
+    }
+
+    @Test
+    public void isAllowed_packageInfoNotFound() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+                .thenReturn(null);
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionPackageManager, never())
+                .hasRequestedInternetPermissions(any());
+        verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
+        verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
+    }
+
+    @Test
+    public void isAllowed_notRequestedInternet() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+                .thenReturn(PACKAGE_INFO);
+        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+                .thenReturn(false);
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
+        verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
+    }
+
+    @Test
+    public void isAllowed_systemApp() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+                .thenReturn(PACKAGE_INFO);
+        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+                .thenReturn(true);
+        when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
+    }
+
+    @Test
+    public void isAllowed_updatedSystemApp() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+                .thenReturn(PACKAGE_INFO);
+        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+                .thenReturn(true);
+        when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
+        when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
+                .thenReturn(true);
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void isAllowed_allowed() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
+        when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
+                .thenReturn(PACKAGE_INFO);
+        when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
+                .thenReturn(true);
+        when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(false);
+        when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
+                .thenReturn(false);
+
+        boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void updateBlocklist_negativeSize() {
+        mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ -1);
+        assertThat(mReadRawFiles).isEmpty();
+
+        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+        verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void updateBlocklist_zeroSize() {
+        mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 0);
+        assertThat(mReadRawFiles).isEmpty();
+
+        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+        verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void updateBlocklist_positiveSize_belowTotal() {
+        mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 1);
+        assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
+
+        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+        mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
+
+        verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
+        verify(mMockContentProtectionPackageManager).getPackageInfo(SECOND_PACKAGE_NAME);
+    }
+
+    @Test
+    public void updateBlocklist_positiveSize_aboveTotal() {
+        mContentProtectionBlocklistManager.updateBlocklist(LINES.size() + 1);
+        assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
+
+        mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
+        mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
+
+        verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
+        verify(mMockContentProtectionPackageManager, never()).getPackageInfo(SECOND_PACKAGE_NAME);
+    }
+
+    private final class TestContentProtectionBlocklistManager
+            extends ContentProtectionBlocklistManager {
+
+        TestContentProtectionBlocklistManager() {
+            super(mMockContentProtectionPackageManager);
+        }
+
+        @Override
+        protected List<String> readLinesFromRawFile(@NonNull String filename) {
+            mReadRawFiles.add(filename);
+            return LINES;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java
new file mode 100644
index 0000000..0e80bfd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionConsentManagerTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.testing.TestableContentResolver;
+import android.testing.TestableContext;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test for {@link ContentProtectionConsentManager}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:com.android.server.contentprotection.ContentProtectionConsentManagerTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionConsentManagerTest {
+
+    private static final String KEY_PACKAGE_VERIFIER_USER_CONSENT = "package_verifier_user_consent";
+
+    private static final Uri URI_PACKAGE_VERIFIER_USER_CONSENT =
+            Settings.Global.getUriFor(KEY_PACKAGE_VERIFIER_USER_CONSENT);
+
+    private static final int VALUE_TRUE = 1;
+
+    private static final int VALUE_FALSE = -1;
+
+    private static final int VALUE_DEFAULT = 0;
+
+    private static final int TEST_USER_ID = 1234;
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Rule
+    public final TestableContext mTestableContext =
+            new TestableContext(ApplicationProvider.getApplicationContext());
+
+    private final TestableContentResolver mTestableContentResolver =
+            mTestableContext.getContentResolver();
+
+    @Mock private ContentResolver mMockContentResolver;
+
+    @Mock private DevicePolicyManagerInternal mMockDevicePolicyManagerInternal;
+
+    @Test
+    public void constructor_registersContentObserver() {
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(mMockContentResolver);
+
+        assertThat(manager.mContentObserver).isNotNull();
+        verify(mMockContentResolver)
+                .registerContentObserver(
+                        URI_PACKAGE_VERIFIER_USER_CONSENT,
+                        /* notifyForDescendants= */ false,
+                        manager.mContentObserver,
+                        UserHandle.USER_ALL);
+    }
+
+    @Test
+    public void isConsentGranted_packageVerifierNotGranted() {
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_FALSE);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isFalse();
+        verifyZeroInteractions(mMockDevicePolicyManagerInternal);
+    }
+
+    @Test
+    public void isConsentGranted_packageVerifierGranted_userNotManaged() {
+        ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isTrue();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+    }
+
+    @Test
+    public void isConsentGranted_packageVerifierGranted_userManaged() {
+        when(mMockDevicePolicyManagerInternal.isUserOrganizationManaged(TEST_USER_ID))
+                .thenReturn(true);
+        ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void isConsentGranted_packageVerifierDefault() {
+        ContentProtectionConsentManager manager =
+                createContentProtectionConsentManager(VALUE_DEFAULT);
+
+        boolean actual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(actual).isFalse();
+        verifyZeroInteractions(mMockDevicePolicyManagerInternal);
+    }
+
+    @Test
+    public void contentObserver() throws Exception {
+        ContentProtectionConsentManager manager = createContentProtectionConsentManager(VALUE_TRUE);
+        boolean firstActual = manager.isConsentGranted(TEST_USER_ID);
+
+        Settings.Global.putInt(
+                mTestableContentResolver, KEY_PACKAGE_VERIFIER_USER_CONSENT, VALUE_FALSE);
+        // Observer has to be called manually, mTestableContentResolver is not propagating
+        manager.mContentObserver.onChange(
+                /* selfChange= */ false, URI_PACKAGE_VERIFIER_USER_CONSENT, TEST_USER_ID);
+        boolean secondActual = manager.isConsentGranted(TEST_USER_ID);
+
+        assertThat(firstActual).isTrue();
+        assertThat(secondActual).isFalse();
+        verify(mMockDevicePolicyManagerInternal).isUserOrganizationManaged(TEST_USER_ID);
+    }
+
+    private ContentProtectionConsentManager createContentProtectionConsentManager(
+            ContentResolver contentResolver) {
+        return new ContentProtectionConsentManager(
+                new Handler(Looper.getMainLooper()),
+                contentResolver,
+                mMockDevicePolicyManagerInternal);
+    }
+
+    private ContentProtectionConsentManager createContentProtectionConsentManager(
+            int valuePackageVerifierUserConsent) {
+        Settings.Global.putInt(
+                mTestableContentResolver,
+                KEY_PACKAGE_VERIFIER_USER_CONSENT,
+                valuePackageVerifierUserConsent);
+        return createContentProtectionConsentManager(mTestableContentResolver);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java
new file mode 100644
index 0000000..7d45ea4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.Manifest.permission;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManager.PackageInfoFlags;
+import android.testing.TestableContext;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test for {@link ContentProtectionPackageManager}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:com.android.server.contentprotection.ContentProtectionPackageManagerTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionPackageManagerTest {
+    private static final String PACKAGE_NAME = "PACKAGE_NAME";
+
+    private static final PackageInfo EMPTY_PACKAGE_INFO = new PackageInfo();
+
+    private static final PackageInfo SYSTEM_APP_PACKAGE_INFO = createSystemAppPackageInfo();
+
+    private static final PackageInfo UPDATED_SYSTEM_APP_PACKAGE_INFO =
+            createUpdatedSystemAppPackageInfo();
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Rule
+    public final TestableContext mContext =
+            new TestableContext(ApplicationProvider.getApplicationContext());
+
+    @Mock private PackageManager mMockPackageManager;
+
+    private ContentProtectionPackageManager mContentProtectionPackageManager;
+
+    @Before
+    public void setup() {
+        mContext.setMockPackageManager(mMockPackageManager);
+        mContentProtectionPackageManager = new ContentProtectionPackageManager(mContext);
+    }
+
+    @Test
+    public void getPackageInfo_found() throws Exception {
+        PackageInfo expected = createPackageInfo(/* flags= */ 0);
+        when(mMockPackageManager.getPackageInfo(eq(PACKAGE_NAME), any(PackageInfoFlags.class)))
+                .thenReturn(expected);
+
+        PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void getPackageInfo_notFound() throws Exception {
+        when(mMockPackageManager.getPackageInfo(eq(PACKAGE_NAME), any(PackageInfoFlags.class)))
+                .thenThrow(new NameNotFoundException());
+
+        PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
+
+        assertThat(actual).isNull();
+    }
+
+    @Test
+    public void getPackageInfo_null() {
+        PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
+
+        assertThat(actual).isNull();
+    }
+
+    @Test
+    public void isSystemApp_true() {
+        boolean actual = mContentProtectionPackageManager.isSystemApp(SYSTEM_APP_PACKAGE_INFO);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void isSystemApp_false() {
+        boolean actual =
+                mContentProtectionPackageManager.isSystemApp(UPDATED_SYSTEM_APP_PACKAGE_INFO);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void isSystemApp_noApplicationInfo() {
+        boolean actual = mContentProtectionPackageManager.isSystemApp(EMPTY_PACKAGE_INFO);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void isUpdatedSystemApp_true() {
+        boolean actual =
+                mContentProtectionPackageManager.isUpdatedSystemApp(
+                        UPDATED_SYSTEM_APP_PACKAGE_INFO);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void isUpdatedSystemApp_false() {
+        boolean actual =
+                mContentProtectionPackageManager.isUpdatedSystemApp(SYSTEM_APP_PACKAGE_INFO);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void isUpdatedSystemApp_noApplicationInfo() {
+        boolean actual = mContentProtectionPackageManager.isUpdatedSystemApp(EMPTY_PACKAGE_INFO);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void hasRequestedInternetPermissions_true() {
+        PackageInfo packageInfo = createPackageInfo(new String[] {permission.INTERNET});
+
+        boolean actual =
+                mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void hasRequestedInternetPermissions_false() {
+        PackageInfo packageInfo = createPackageInfo(new String[] {permission.ACCESS_FINE_LOCATION});
+
+        boolean actual =
+                mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo);
+
+        assertThat(actual).isFalse();
+    }
+
+    @Test
+    public void hasRequestedInternetPermissions_noRequestedPermissions() {
+        boolean actual =
+                mContentProtectionPackageManager.hasRequestedInternetPermissions(
+                        EMPTY_PACKAGE_INFO);
+
+        assertThat(actual).isFalse();
+    }
+
+    private static PackageInfo createSystemAppPackageInfo() {
+        return createPackageInfo(ApplicationInfo.FLAG_SYSTEM);
+    }
+
+    private static PackageInfo createUpdatedSystemAppPackageInfo() {
+        return createPackageInfo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
+    }
+
+    private static PackageInfo createPackageInfo(int flags) {
+        return createPackageInfo(flags, /* requestedPermissions= */ new String[0]);
+    }
+
+    private static PackageInfo createPackageInfo(String[] requestedPermissions) {
+        return createPackageInfo(/* flags= */ 0, requestedPermissions);
+    }
+
+    private static PackageInfo createPackageInfo(int flags, String[] requestedPermissions) {
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = PACKAGE_NAME;
+        packageInfo.applicationInfo = new ApplicationInfo();
+        packageInfo.applicationInfo.packageName = PACKAGE_NAME;
+        packageInfo.applicationInfo.flags = flags;
+        packageInfo.requestedPermissions = requestedPermissions;
+        return packageInfo;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
new file mode 100644
index 0000000..9135ef3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.UserHandle;
+import android.service.contentcapture.IContentProtectionService;
+import android.view.contentcapture.ContentCaptureEvent;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test for {@link RemoteContentProtectionService}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:com.android.server.contentprotection.RemoteContentProtectionServiceTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RemoteContentProtectionServiceTest {
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+
+    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock private IContentProtectionService mMockContentProtectionService;
+
+    private RemoteContentProtectionService mRemoteContentProtectionService;
+
+    private int mConnectCallCount = 0;
+
+    @Before
+    public void setup() {
+        ComponentName componentName = new ComponentName(mContext.getPackageName(), "TestClass");
+        mRemoteContentProtectionService =
+                new TestRemoteContentProtectionService(mContext, componentName);
+    }
+
+    @Test
+    public void doesNotAutoConnect() {
+        assertThat(mConnectCallCount).isEqualTo(0);
+        verifyZeroInteractions(mMockContentProtectionService);
+    }
+
+    @Test
+    public void getAutoDisconnectTimeoutMs() {
+        long actual = mRemoteContentProtectionService.getAutoDisconnectTimeoutMs();
+
+        assertThat(actual).isEqualTo(3000L);
+    }
+
+    @Test
+    public void onLoginDetected() throws Exception {
+        ContentCaptureEvent event =
+                new ContentCaptureEvent(/* sessionId= */ 1111, /* type= */ 2222);
+        ParceledListSlice<ContentCaptureEvent> events =
+                new ParceledListSlice<>(ImmutableList.of(event));
+
+        mRemoteContentProtectionService.onLoginDetected(events);
+
+        verify(mMockContentProtectionService).onLoginDetected(events);
+    }
+
+    private final class TestRemoteContentProtectionService extends RemoteContentProtectionService {
+
+        TestRemoteContentProtectionService(Context context, ComponentName componentName) {
+            super(context, componentName, UserHandle.myUserId(), /* bindAllowInstant= */ false);
+        }
+
+        @Override // from ServiceConnector
+        public synchronized AndroidFuture<IContentProtectionService> connect() {
+            mConnectCallCount++;
+            return AndroidFuture.completedFuture(mMockContentProtectionService);
+        }
+
+        @Override // from ServiceConnector
+        public boolean run(@NonNull ServiceConnector.VoidJob<IContentProtectionService> job) {
+            try {
+                job.run(mMockContentProtectionService);
+            } catch (Exception ex) {
+                fail("Unexpected exception: " + ex);
+            }
+            return true;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
new file mode 100644
index 0000000..419508c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.contentprotection"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
deleted file mode 100644
index 2c4fe53..0000000
--- a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java
+++ /dev/null
@@ -1,444 +0,0 @@
-/*
- * Copyright 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.server.display;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.hardware.display.AmbientBrightnessDayStats;
-import android.os.SystemClock;
-import android.os.UserManager;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.time.LocalDate;
-import java.util.ArrayList;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AmbientBrightnessStatsTrackerTest {
-
-    private TestInjector mTestInjector;
-
-    @Before
-    public void setUp() {
-        mTestInjector = new TestInjector();
-    }
-
-    @Test
-    public void testBrightnessStatsTrackerOverSingleDay() {
-        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
-        ArrayList<AmbientBrightnessDayStats> userStats;
-        float[] expectedStats;
-        // Test case where no user data
-        userStats = statsTracker.getUserStats(0);
-        assertNull(userStats);
-        // Test after adding some user data
-        statsTracker.start();
-        statsTracker.add(0, 0);
-        mTestInjector.incrementTime(1000);
-        statsTracker.stop();
-        userStats = statsTracker.getUserStats(0);
-        assertEquals(1, userStats.size());
-        assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 1;
-        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
-        // Test after adding some more user data
-        statsTracker.start();
-        statsTracker.add(0, 0.05f);
-        mTestInjector.incrementTime(1000);
-        statsTracker.add(0, 0.2f);
-        mTestInjector.incrementTime(1500);
-        statsTracker.add(0, 50000);
-        mTestInjector.incrementTime(2500);
-        statsTracker.stop();
-        userStats = statsTracker.getUserStats(0);
-        assertEquals(1, userStats.size());
-        assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 2;
-        expectedStats[1] = 1.5f;
-        expectedStats[11] = 2.5f;
-        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
-    }
-
-    @Test
-    public void testBrightnessStatsTrackerOverMultipleDays() {
-        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
-        ArrayList<AmbientBrightnessDayStats> userStats;
-        float[] expectedStats;
-        // Add data for day 1
-        statsTracker.start();
-        statsTracker.add(0, 0.05f);
-        mTestInjector.incrementTime(1000);
-        statsTracker.add(0, 0.2f);
-        mTestInjector.incrementTime(1500);
-        statsTracker.add(0, 1);
-        mTestInjector.incrementTime(2500);
-        statsTracker.stop();
-        // Add data for day 2
-        mTestInjector.incrementDate(1);
-        statsTracker.start();
-        statsTracker.add(0, 0);
-        mTestInjector.incrementTime(3500);
-        statsTracker.add(0, 5);
-        mTestInjector.incrementTime(5000);
-        statsTracker.stop();
-        // Test that the data is tracked as expected
-        userStats = statsTracker.getUserStats(0);
-        assertEquals(2, userStats.size());
-        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 1;
-        expectedStats[1] = 1.5f;
-        expectedStats[3] = 2.5f;
-        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
-        assertEquals(mTestInjector.getLocalDate(), userStats.get(1).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 3.5f;
-        expectedStats[4] = 5;
-        assertArrayEquals(expectedStats, userStats.get(1).getStats(), 0);
-    }
-
-    @Test
-    public void testBrightnessStatsTrackerOverMultipleUsers() {
-        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
-        ArrayList<AmbientBrightnessDayStats> userStats;
-        float[] expectedStats;
-        // Add data for user 1
-        statsTracker.start();
-        statsTracker.add(0, 0.05f);
-        mTestInjector.incrementTime(1000);
-        statsTracker.add(0, 0.2f);
-        mTestInjector.incrementTime(1500);
-        statsTracker.add(0, 1);
-        mTestInjector.incrementTime(2500);
-        statsTracker.stop();
-        // Add data for user 2
-        mTestInjector.incrementDate(1);
-        statsTracker.start();
-        statsTracker.add(1, 0);
-        mTestInjector.incrementTime(3500);
-        statsTracker.add(1, 5);
-        mTestInjector.incrementTime(5000);
-        statsTracker.stop();
-        // Test that the data is tracked as expected
-        userStats = statsTracker.getUserStats(0);
-        assertEquals(1, userStats.size());
-        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 1;
-        expectedStats[1] = 1.5f;
-        expectedStats[3] = 2.5f;
-        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
-        userStats = statsTracker.getUserStats(1);
-        assertEquals(1, userStats.size());
-        assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 3.5f;
-        expectedStats[4] = 5;
-        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
-    }
-
-    @Test
-    public void testBrightnessStatsTrackerOverMaxDays() {
-        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
-        ArrayList<AmbientBrightnessDayStats> userStats;
-        // Add 10 extra days of data over the buffer limit
-        for (int i = 0; i < AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK + 10; i++) {
-            mTestInjector.incrementDate(1);
-            statsTracker.start();
-            statsTracker.add(0, 10);
-            mTestInjector.incrementTime(1000);
-            statsTracker.add(0, 20);
-            mTestInjector.incrementTime(1000);
-            statsTracker.stop();
-        }
-        // Assert that we are only tracking last "MAX_DAYS_TO_TRACK"
-        userStats = statsTracker.getUserStats(0);
-        assertEquals(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK, userStats.size());
-        LocalDate runningDate = mTestInjector.getLocalDate();
-        for (int i = AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK - 1; i >= 0; i--) {
-            assertEquals(runningDate, userStats.get(i).getLocalDate());
-            runningDate = runningDate.minusDays(1);
-        }
-    }
-
-    @Test
-    public void testReadAmbientBrightnessStats() throws IOException {
-        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
-        LocalDate date = mTestInjector.getLocalDate();
-        ArrayList<AmbientBrightnessDayStats> userStats;
-        String statsFile =
-                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
-                        + "<ambient-brightness-stats>\r\n"
-                        // Old stats that shouldn't be read
-                        + "<ambient-brightness-day-stats user=\"10\" local-date=\""
-                        + date.minusDays(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK)
-                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
-                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
-                        + "0.0,0.0,0.0\" />\r\n"
-                        // Valid stats that should get read
-                        + "<ambient-brightness-day-stats user=\"10\" local-date=\""
-                        + date.minusDays(1)
-                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
-                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
-                        + "0.0,0.0,0.0\" />\r\n"
-                        // Valid stats that should get read
-                        + "<ambient-brightness-day-stats user=\"10\" local-date=\"" + date
-                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
-                        + "3000.0,10000.0\" bucket-stats=\"0.0,0.0,0.0,0.0,4.482,0.0,0.0,0.0,0.0,"
-                        + "0.0\" />\r\n"
-                        + "</ambient-brightness-stats>";
-        statsTracker.readStats(getInputStream(statsFile));
-        userStats = statsTracker.getUserStats(0);
-        assertEquals(2, userStats.size());
-        assertEquals(new AmbientBrightnessDayStats(date.minusDays(1),
-                new float[]{0, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000},
-                new float[]{1.088f, 0, 0.726f, 0, 25.868f, 0, 0, 0, 0, 0}), userStats.get(0));
-        assertEquals(new AmbientBrightnessDayStats(date,
-                new float[]{0, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000},
-                new float[]{0, 0, 0, 0, 4.482f, 0, 0, 0, 0, 0}), userStats.get(1));
-    }
-
-    @Test
-    public void testFailedReadAmbientBrightnessStatsWithException() {
-        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
-        LocalDate date = mTestInjector.getLocalDate();
-        String statsFile;
-        // Test with parse error
-        statsFile =
-                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
-                        + "<ambient-brightness-stats>\r\n"
-                        // Incorrect since bucket boundaries not parsable
-                        + "<ambient-brightness-day-stats user=\"10\" local-date=\"" + date
-                        + "\" bucket-boundaries=\"asdf,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
-                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
-                        + "0.0,0.0,0.0\" />\r\n"
-                        + "</ambient-brightness-stats>";
-        try {
-            statsTracker.readStats(getInputStream(statsFile));
-        } catch (IOException e) {
-            // Expected
-        }
-        assertNull(statsTracker.getUserStats(0));
-        // Test with incorrect data (bucket boundaries length not equal to stats length)
-        statsFile =
-                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
-                        + "<ambient-brightness-stats>\r\n"
-                        // Correct data
-                        + "<ambient-brightness-day-stats user=\"10\" local-date=\""
-                        + date.minusDays(1)
-                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
-                        + "3000.0,10000.0\" bucket-stats=\"0.0,0.0,0.0,0.0,4.482,0.0,0.0,0.0,0.0,"
-                        + "0.0\" />\r\n"
-                        // Incorrect data
-                        + "<ambient-brightness-day-stats user=\"10\" local-date=\"" + date
-                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,1000.0,"
-                        + "3000.0,10000.0\" bucket-stats=\"1.088,0.0,0.726,0.0,25.868,0.0,0.0,"
-                        + "0.0,0.0,0.0\" />\r\n"
-                        + "</ambient-brightness-stats>";
-        try {
-            statsTracker.readStats(getInputStream(statsFile));
-        } catch (Exception e) {
-            // Expected
-        }
-        assertNull(statsTracker.getUserStats(0));
-        // Test with missing attribute
-        statsFile =
-                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\r\n"
-                        + "<ambient-brightness-stats>\r\n"
-                        + "<ambientBrightnessDayStats user=\"10\" local-date=\"" + date
-                        + "\" bucket-boundaries=\"0.0,1.0,3.0,10.0,30.0,100.0,300.0,1000.0,"
-                        + "3000.0,10000.0\" />\r\n"
-                        + "</ambient-brightness-stats>";
-        try {
-            statsTracker.readStats(getInputStream(statsFile));
-        } catch (Exception e) {
-            // Expected
-        }
-        assertNull(statsTracker.getUserStats(0));
-    }
-
-    @Test
-    public void testWriteThenReadAmbientBrightnessStats() throws IOException {
-        AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker();
-        ArrayList<AmbientBrightnessDayStats> userStats;
-        float[] expectedStats;
-        // Generate some placeholder data
-        // Data: very old which should not be read
-        statsTracker.start();
-        statsTracker.add(0, 0.05f);
-        mTestInjector.incrementTime(1000);
-        statsTracker.add(0, 0.2f);
-        mTestInjector.incrementTime(1500);
-        statsTracker.add(0, 1);
-        mTestInjector.incrementTime(2500);
-        statsTracker.stop();
-        // Data: day 1 user 1
-        mTestInjector.incrementDate(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK - 1);
-        statsTracker.start();
-        statsTracker.add(0, 0.05f);
-        mTestInjector.incrementTime(1000);
-        statsTracker.add(0, 0.2f);
-        mTestInjector.incrementTime(1500);
-        statsTracker.add(0, 1);
-        mTestInjector.incrementTime(2500);
-        statsTracker.stop();
-        // Data: day 1 user 2
-        statsTracker.start();
-        statsTracker.add(1, 0);
-        mTestInjector.incrementTime(3500);
-        statsTracker.add(1, 5);
-        mTestInjector.incrementTime(5000);
-        statsTracker.stop();
-        // Data: day 2 user 1
-        mTestInjector.incrementDate(1);
-        statsTracker.start();
-        statsTracker.add(0, 0);
-        mTestInjector.incrementTime(3500);
-        statsTracker.add(0, 50000);
-        mTestInjector.incrementTime(5000);
-        statsTracker.stop();
-        // Write them
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        statsTracker.writeStats(baos);
-        baos.flush();
-        // Read them back and assert that it's the same
-        ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray());
-        AmbientBrightnessStatsTracker newStatsTracker = getTestStatsTracker();
-        newStatsTracker.readStats(input);
-        userStats = newStatsTracker.getUserStats(0);
-        assertEquals(2, userStats.size());
-        // Check day 1 user 1
-        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 1;
-        expectedStats[1] = 1.5f;
-        expectedStats[3] = 2.5f;
-        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
-        // Check day 2 user 1
-        assertEquals(mTestInjector.getLocalDate(), userStats.get(1).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 3.5f;
-        expectedStats[11] = 5;
-        assertArrayEquals(expectedStats, userStats.get(1).getStats(), 0);
-        userStats = newStatsTracker.getUserStats(1);
-        assertEquals(1, userStats.size());
-        // Check day 1 user 2
-        assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate());
-        expectedStats = getEmptyStatsArray();
-        expectedStats[0] = 3.5f;
-        expectedStats[4] = 5;
-        assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0);
-    }
-
-    @Test
-    public void testTimer() {
-        AmbientBrightnessStatsTracker.Timer timer = new AmbientBrightnessStatsTracker.Timer(
-                () -> mTestInjector.elapsedRealtimeMillis());
-        assertEquals(0, timer.totalDurationSec(), 0);
-        mTestInjector.incrementTime(1000);
-        assertEquals(0, timer.totalDurationSec(), 0);
-        assertFalse(timer.isRunning());
-        // Start timer
-        timer.start();
-        assertTrue(timer.isRunning());
-        assertEquals(0, timer.totalDurationSec(), 0);
-        mTestInjector.incrementTime(1000);
-        assertTrue(timer.isRunning());
-        assertEquals(1, timer.totalDurationSec(), 0);
-        // Reset timer
-        timer.reset();
-        assertEquals(0, timer.totalDurationSec(), 0);
-        assertFalse(timer.isRunning());
-        // Start again
-        timer.start();
-        assertTrue(timer.isRunning());
-        assertEquals(0, timer.totalDurationSec(), 0);
-        mTestInjector.incrementTime(2000);
-        assertTrue(timer.isRunning());
-        assertEquals(2, timer.totalDurationSec(), 0);
-        // Reset again
-        timer.reset();
-        assertEquals(0, timer.totalDurationSec(), 0);
-        assertFalse(timer.isRunning());
-    }
-
-    private class TestInjector extends AmbientBrightnessStatsTracker.Injector {
-
-        private long mElapsedRealtimeMillis = SystemClock.elapsedRealtime();
-        private LocalDate mLocalDate = LocalDate.now();
-
-        public void incrementTime(long timeMillis) {
-            mElapsedRealtimeMillis += timeMillis;
-        }
-
-        public void incrementDate(int numDays) {
-            mLocalDate = mLocalDate.plusDays(numDays);
-        }
-
-        @Override
-        public long elapsedRealtimeMillis() {
-            return mElapsedRealtimeMillis;
-        }
-
-        @Override
-        public int getUserSerialNumber(UserManager userManager, int userId) {
-            return userId + 10;
-        }
-
-        @Override
-        public int getUserId(UserManager userManager, int userSerialNumber) {
-            return userSerialNumber - 10;
-        }
-
-        @Override
-        public LocalDate getLocalDate() {
-            return mLocalDate;
-        }
-    }
-
-    private AmbientBrightnessStatsTracker getTestStatsTracker() {
-        return new AmbientBrightnessStatsTracker(
-                InstrumentationRegistry.getContext().getSystemService(UserManager.class),
-                mTestInjector);
-    }
-
-    private float[] getEmptyStatsArray() {
-        return new float[AmbientBrightnessStatsTracker.BUCKET_BOUNDARIES_FOR_NEW_STATS.length];
-    }
-
-    private InputStream getInputStream(String data) {
-        return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
deleted file mode 100644
index 5f81869..0000000
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ /dev/null
@@ -1,710 +0,0 @@
-/*
- * Copyright 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.display;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.hardware.display.BrightnessConfiguration;
-import android.os.PowerManager;
-import android.util.MathUtils;
-import android.util.Spline;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-
-import java.util.Arrays;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class BrightnessMappingStrategyTest {
-
-    private static final float[] LUX_LEVELS = {
-        0,
-        5,
-        20,
-        40,
-        100,
-        325,
-        600,
-        1250,
-        2200,
-        4000,
-        5000
-    };
-
-    private static final int[] LUX_LEVELS_IDLE = {
-        0,
-        10,
-        40,
-        80,
-        200,
-        655,
-        1200,
-        2500,
-        4400,
-        8000,
-        10000
-    };
-
-    private static final float[] DISPLAY_LEVELS_NITS = {
-        13.25f,
-        54.0f,
-        78.85f,
-        105.02f,
-        132.7f,
-        170.12f,
-        212.1f,
-        265.2f,
-        335.8f,
-        415.2f,
-        478.5f,
-    };
-
-    private static final float[] DISPLAY_LEVELS_NITS_IDLE = {
-        23.25f,
-        64.0f,
-        88.85f,
-        115.02f,
-        142.7f,
-        180.12f,
-        222.1f,
-        275.2f,
-        345.8f,
-        425.2f,
-        468.5f,
-    };
-
-    private static final int[] DISPLAY_LEVELS_BACKLIGHT = {
-        9,
-        30,
-        45,
-        62,
-        78,
-        96,
-        119,
-        146,
-        178,
-        221,
-        255
-    };
-
-    private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
-    private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f };
-    private static final float[] DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT = { 0.03149606299f, 1.0f };
-
-    private static final float[] EMPTY_FLOAT_ARRAY = new float[0];
-    private static final int[] EMPTY_INT_ARRAY = new int[0];
-
-    private static final float MAXIMUM_GAMMA = 3.0f;
-
-    private static final float[] GAMMA_CORRECTION_LUX = {
-        0,
-        100,
-        1000,
-        2500,
-        4000,
-        4900,
-        5000
-    };
-    private static final float[] GAMMA_CORRECTION_NITS = {
-        1.0f,
-        10.55f,
-        96.5f,
-        239.75f,
-        383.0f,
-        468.95f,
-        478.5f,
-    };
-    private static final Spline GAMMA_CORRECTION_SPLINE = Spline.createSpline(
-            new float[] { 0.0f, 100.0f, 1000.0f, 2500.0f, 4000.0f, 4900.0f, 5000.0f },
-            new float[] { 0.0475f, 0.0475f, 0.2225f, 0.5140f, 0.8056f, 0.9805f, 1.0f });
-
-    private static final float TOLERANCE = 0.0001f;
-
-    @Mock
-    DisplayWhiteBalanceController mMockDwbc;
-
-    @Test
-    public void testSimpleStrategyMappingAtControlPoints() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
-        DisplayDeviceConfig ddc = createDdc();
-        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNotNull("BrightnessMappingStrategy should not be null", simple);
-        for (int i = 0; i < LUX_LEVELS.length; i++) {
-            final float expectedLevel = MathUtils.map(PowerManager.BRIGHTNESS_OFF + 1,
-                    PowerManager.BRIGHTNESS_ON, PowerManager.BRIGHTNESS_MIN,
-                    PowerManager.BRIGHTNESS_MAX, DISPLAY_LEVELS_BACKLIGHT[i]);
-            assertEquals(expectedLevel,
-                    simple.getBrightness(LUX_LEVELS[i]), 0.0001f /*tolerance*/);
-        }
-    }
-
-    @Test
-    public void testSimpleStrategyMappingBetweenControlPoints() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
-        DisplayDeviceConfig ddc = createDdc();
-        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNotNull("BrightnessMappingStrategy should not be null", simple);
-        for (int i = 1; i < LUX_LEVELS.length; i++) {
-            final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
-            final float backlight = simple.getBrightness(lux) * PowerManager.BRIGHTNESS_ON;
-            assertTrue("Desired brightness should be between adjacent control points.",
-                    backlight > DISPLAY_LEVELS_BACKLIGHT[i - 1]
-                            && backlight < DISPLAY_LEVELS_BACKLIGHT[i]);
-        }
-    }
-
-    @Test
-    public void testSimpleStrategyIgnoresNewConfiguration() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
-        DisplayDeviceConfig ddc = createDdc();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-
-        final float[] lux = { 0f, 1f };
-        final float[] nits = { 0, PowerManager.BRIGHTNESS_ON };
-
-        BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
-                .build();
-        strategy.setBrightnessConfiguration(config);
-        assertNotEquals(1.0f, strategy.getBrightness(1f), 0.0001f /*tolerance*/);
-    }
-
-    @Test
-    public void testSimpleStrategyIgnoresNullConfiguration() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
-        DisplayDeviceConfig ddc = createDdc();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-
-        strategy.setBrightnessConfiguration(null);
-        final int N = DISPLAY_LEVELS_BACKLIGHT.length;
-        final float expectedBrightness =
-                (float) DISPLAY_LEVELS_BACKLIGHT[N - 1] / PowerManager.BRIGHTNESS_ON;
-        assertEquals(expectedBrightness,
-                strategy.getBrightness(LUX_LEVELS[N - 1]), 0.0001f /*tolerance*/);
-    }
-
-    @Test
-    public void testPhysicalStrategyMappingAtControlPoints() {
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT,
-                LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNotNull("BrightnessMappingStrategy should not be null", physical);
-        for (int i = 0; i < LUX_LEVELS.length; i++) {
-            final float expectedLevel = MathUtils.map(DISPLAY_RANGE_NITS[0], DISPLAY_RANGE_NITS[1],
-                    DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT[0],
-                    DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT[1],
-                    DISPLAY_LEVELS_NITS[i]);
-            assertEquals(expectedLevel,
-                    physical.getBrightness(LUX_LEVELS[i]),
-                    0.0001f /*tolerance*/);
-        }
-    }
-
-    @Test
-    public void testPhysicalStrategyMappingBetweenControlPoints() {
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
-                LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNotNull("BrightnessMappingStrategy should not be null", physical);
-        Spline brightnessToNits =
-                Spline.createSpline(BACKLIGHT_RANGE_ZERO_TO_ONE, DISPLAY_RANGE_NITS);
-        for (int i = 1; i < LUX_LEVELS.length; i++) {
-            final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2.0f;
-            final float brightness = physical.getBrightness(lux);
-            final float nits = brightnessToNits.interpolate(brightness);
-            assertTrue("Desired brightness should be between adjacent control points: " + nits,
-                    nits > DISPLAY_LEVELS_NITS[i - 1] && nits < DISPLAY_LEVELS_NITS[i]);
-        }
-    }
-
-    @Test
-    public void testPhysicalStrategyUsesNewConfigurations() {
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-
-        final float[] lux = {0f, 1f};
-        final float[] nits = {
-                DISPLAY_RANGE_NITS[0],
-                DISPLAY_RANGE_NITS[DISPLAY_RANGE_NITS.length - 1]
-        };
-
-        BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
-                .build();
-        strategy.setBrightnessConfiguration(config);
-        assertEquals(1.0f, strategy.getBrightness(1f), 0.0001f /*tolerance*/);
-
-        // Check that null returns us to the default configuration.
-        strategy.setBrightnessConfiguration(null);
-        final int N = DISPLAY_LEVELS_NITS.length;
-        final float expectedBrightness = DISPLAY_LEVELS_NITS[N - 1] / DISPLAY_RANGE_NITS[1];
-        assertEquals(expectedBrightness,
-                strategy.getBrightness(LUX_LEVELS[N - 1]), 0.0001f /*tolerance*/);
-    }
-
-    @Test
-    public void testPhysicalStrategyRecalculateSplines() {
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length];
-        for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) {
-            adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f;
-        }
-
-        // Default
-        assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
-                TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
-                TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[0],
-                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[1],
-                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
-
-        // Adjustment is turned on
-        strategy.recalculateSplines(true, adjustedNits50p);
-        assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
-                TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
-                TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[0] / 2,
-                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[1] / 2,
-                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
-
-        // Adjustment is turned off
-        strategy.recalculateSplines(false, adjustedNits50p);
-        assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
-                TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
-                TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[0],
-                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
-        assertEquals(DISPLAY_RANGE_NITS[1],
-                strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
-    }
-
-    @Test
-    public void testDefaultStrategyIsPhysical() {
-        Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy);
-    }
-
-    @Test
-    public void testNonStrictlyIncreasingLuxLevelsFails() {
-        final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length);
-        final int idx = lux.length / 2;
-        float tmp = lux[idx];
-        lux[idx] = lux[idx + 1];
-        lux[idx + 1] = tmp;
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(strategy);
-
-        // And make sure we get the same result even if it's monotone but not increasing.
-        lux[idx] = lux[idx + 1];
-        ddc = createDdc(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux,
-                DISPLAY_LEVELS_NITS);
-        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(strategy);
-    }
-
-    @Test
-    public void testDifferentNumberOfControlPointValuesFails() {
-        //Extra lux level
-        final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length + 1);
-        // Make sure it's strictly increasing so that the only failure is the differing array
-        // lengths
-        lux[lux.length - 1] = lux[lux.length - 2] + 1;
-        Resources res = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(strategy);
-
-        res = createResources(DISPLAY_LEVELS_BACKLIGHT);
-        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(strategy);
-
-        // Extra backlight level
-        final int[] backlight = Arrays.copyOf(
-                DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length + 1);
-        backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
-        res = createResources(backlight);
-        ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, EMPTY_FLOAT_ARRAY);
-        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(strategy);
-
-        // Extra nits level
-        final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length + 1);
-        nits[nits.length - 1] = nits[nits.length - 2] + 1;
-        res = createResources(EMPTY_INT_ARRAY);
-        ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, nits);
-        strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(strategy);
-    }
-
-    @Test
-    public void testPhysicalStrategyRequiresNitsMapping() {
-        Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
-        DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY /*nitsRange*/);
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(physical);
-
-        res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
-        physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(physical);
-
-        res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
-        physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
-        assertNull(physical);
-    }
-
-    @Test
-    public void testStrategiesAdaptToUserDataPoint() {
-        Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
-                LUX_LEVELS, DISPLAY_LEVELS_NITS);
-        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
-        ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
-        res = createResources(DISPLAY_LEVELS_BACKLIGHT);
-        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
-    }
-
-    @Test
-    public void testIdleModeConfigLoadsCorrectly() {
-        Resources res = createResourcesIdle(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
-
-        // Create an idle mode bms
-        // This will fail if it tries to fetch the wrong configuration.
-        BrightnessMappingStrategy bms = BrightnessMappingStrategy.createForIdleMode(res, ddc,
-                mMockDwbc);
-        assertNotNull("BrightnessMappingStrategy should not be null", bms);
-
-        // Ensure that the config is the one we set
-        // Ensure that the lux -> brightness -> nits path works. ()
-        for (int i = 0; i < DISPLAY_LEVELS_NITS_IDLE.length; i++) {
-            assertEquals(LUX_LEVELS_IDLE[i], bms.getDefaultConfig().getCurve().first[i], TOLERANCE);
-            assertEquals(DISPLAY_LEVELS_NITS_IDLE[i], bms.getDefaultConfig().getCurve().second[i],
-                    TOLERANCE);
-            assertEquals(bms.convertToNits(bms.getBrightness(LUX_LEVELS_IDLE[i])),
-                    DISPLAY_LEVELS_NITS_IDLE[i], TOLERANCE);
-        }
-    }
-
-    private static void assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy strategy) {
-        // Save out all of the initial brightness data for comparison after reset.
-        float[] initialBrightnessLevels = new float[LUX_LEVELS.length];
-        for (int i = 0; i < LUX_LEVELS.length; i++) {
-            initialBrightnessLevels[i] = strategy.getBrightness(LUX_LEVELS[i]);
-        }
-
-        // Add a data point in the middle of the curve where the user has set the brightness max
-        final int idx = LUX_LEVELS.length / 2;
-        strategy.addUserDataPoint(LUX_LEVELS[idx], 1.0f);
-
-        // Then make sure that all control points after the middle lux level are also set to max...
-        for (int i = idx; i < LUX_LEVELS.length; i++) {
-            assertEquals(strategy.getBrightness(LUX_LEVELS[idx]), 1.0, 0.0001f /*tolerance*/);
-        }
-
-        // ...and that all control points before the middle lux level are strictly less than the
-        // previous one still.
-        float prevBrightness = strategy.getBrightness(LUX_LEVELS[idx]);
-        for (int i = idx - 1; i >= 0; i--) {
-            float brightness = strategy.getBrightness(LUX_LEVELS[i]);
-            assertTrue("Brightness levels must be monotonic after adapting to user data",
-                    prevBrightness >= brightness);
-            prevBrightness = brightness;
-        }
-
-        // Now reset the curve and make sure we go back to the initial brightness levels recorded
-        // before adding the user data point.
-        strategy.clearUserDataPoints();
-        for (int i = 0; i < LUX_LEVELS.length; i++) {
-            assertEquals(initialBrightnessLevels[i], strategy.getBrightness(LUX_LEVELS[i]),
-                    0.0001f /*tolerance*/);
-        }
-
-        // Now set the middle of the lux range to something just above the minimum.
-        float minBrightness = strategy.getBrightness(LUX_LEVELS[0]);
-        strategy.addUserDataPoint(LUX_LEVELS[idx], minBrightness + 0.0001f);
-
-        // Then make sure the curve is still monotonic.
-        prevBrightness = 0f;
-        for (float lux : LUX_LEVELS) {
-            float brightness = strategy.getBrightness(lux);
-            assertTrue("Brightness levels must be monotonic after adapting to user data",
-                    prevBrightness <= brightness);
-            prevBrightness = brightness;
-        }
-
-        // And that the lowest lux level still gives the absolute minimum brightness. This should
-        // be true assuming that there are more than two lux levels in the curve since we picked a
-        // brightness just barely above the minimum for the middle of the curve.
-        minBrightness = (float) MathUtils.pow(minBrightness, MAXIMUM_GAMMA); // Gamma correction.
-        assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/);
-    }
-
-    private Resources createResources(int[] brightnessLevelsBacklight) {
-        return createResources(brightnessLevelsBacklight, EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
-    }
-
-    private Resources createResourcesIdle(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
-        return createResources(EMPTY_INT_ARRAY,
-                luxLevelsIdle, brightnessLevelsNitsIdle);
-    }
-
-    private Resources createResources(int[] brightnessLevelsBacklight, int[] luxLevelsIdle,
-            float[] brightnessLevelsNitsIdle) {
-
-        Resources mockResources = mock(Resources.class);
-        if (luxLevelsIdle.length > 0) {
-            int[] luxLevelsIdleResource = Arrays.copyOfRange(luxLevelsIdle, 1,
-                    luxLevelsIdle.length);
-            when(mockResources.getIntArray(
-                    com.android.internal.R.array.config_autoBrightnessLevelsIdle))
-                    .thenReturn(luxLevelsIdleResource);
-        }
-
-        when(mockResources.getIntArray(
-                com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
-                .thenReturn(brightnessLevelsBacklight);
-
-        TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle);
-        when(mockResources.obtainTypedArray(
-                com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle))
-                .thenReturn(mockBrightnessLevelNitsIdle);
-
-        when(mockResources.getInteger(
-                com.android.internal.R.integer.config_screenBrightnessSettingMinimum))
-                .thenReturn(1);
-        when(mockResources.getInteger(
-                com.android.internal.R.integer.config_screenBrightnessSettingMaximum))
-                .thenReturn(255);
-        when(mockResources.getFraction(
-                com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1))
-                .thenReturn(MAXIMUM_GAMMA);
-        return mockResources;
-    }
-
-    private DisplayDeviceConfig createDdc() {
-        return createDdc(DISPLAY_RANGE_NITS);
-    }
-
-    private DisplayDeviceConfig createDdc(float[] nitsArray) {
-        return createDdc(nitsArray, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT);
-    }
-
-    private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray) {
-        DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
-        when(mockDdc.getNits()).thenReturn(nitsArray);
-        when(mockDdc.getBrightness()).thenReturn(backlightArray);
-        when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS);
-        when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
-        return mockDdc;
-    }
-
-    private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray,
-            float[] luxLevelsFloat, float[] brightnessLevelsNits) {
-        DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
-        when(mockDdc.getNits()).thenReturn(nitsArray);
-        when(mockDdc.getBrightness()).thenReturn(backlightArray);
-        when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevelsFloat);
-        when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(brightnessLevelsNits);
-        return mockDdc;
-    }
-
-    private TypedArray createFloatTypedArray(float[] vals) {
-        TypedArray mockArray = mock(TypedArray.class);
-        when(mockArray.length()).thenAnswer(invocation -> {
-            return vals.length;
-        });
-        when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
-            final float def = (float) invocation.getArguments()[1];
-            if (vals == null) {
-                return def;
-            }
-            int idx = (int) invocation.getArguments()[0];
-            if (idx >= 0 && idx < vals.length) {
-                return vals[idx];
-            } else {
-                return def;
-            }
-        });
-        return mockArray;
-    }
-
-    // Gamma correction tests.
-    // x0 = 100   y0 = ~0.01
-    // x1 = 1000  y1 = ~0.20
-    // x2 = 2500  y2 = ~0.50
-    // x3 = 4000  y3 = ~0.80
-    // x4 = 4900  y4 = ~0.99
-
-    @Test
-    public void testGammaCorrectionLowChangeAtCenter() {
-        // If we set a user data point at (x2, y2^0.5), i.e. gamma = 0.5, it should bump the rest
-        // of the spline accordingly.
-        final int x1 = 1000;
-        final int x2 = 2500;
-        final int x3 = 4000;
-        final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
-        final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
-        final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
-
-        Resources resources = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
-                GAMMA_CORRECTION_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                mMockDwbc);
-        // Let's start with a validity check:
-        assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
-        assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
-        assertEquals(y3, strategy.getBrightness(x3), 0.0001f /* tolerance */);
-        // OK, let's roll:
-        float gamma = 0.5f;
-        strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma));
-        assertEquals(MathUtils.pow(y1, gamma), strategy.getBrightness(x1), 0.0001f /* tolerance */);
-        assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */);
-        assertEquals(MathUtils.pow(y3, gamma), strategy.getBrightness(x3), 0.0001f /* tolerance */);
-        // The adjustment should be +0.6308 (manual calculation).
-        assertEquals(+0.6308f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
-    }
-
-    @Test
-    public void testGammaCorrectionHighChangeAtCenter() {
-        // This time we set a user data point at (x2, y2^0.25), i.e. gamma = 0.3 (the minimum),
-        // which should bump the rest of the spline accordingly, and further correct x2 to hit
-        // y2^0.25 (not y2^0.3).
-        final int x1 = 1000;
-        final int x2 = 2500;
-        final int x3 = 4000;
-        final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
-        final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
-        final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
-        Resources resources = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
-                GAMMA_CORRECTION_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                mMockDwbc);
-        // Validity check:
-        assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
-        assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
-        assertEquals(y3, strategy.getBrightness(x3), 0.0001f /* tolerance */);
-        // Let's roll:
-        float gamma = 0.25f;
-        final float minGamma = 1.0f / MAXIMUM_GAMMA;
-        strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma));
-        assertEquals(MathUtils.pow(y1, minGamma),
-                strategy.getBrightness(x1), 0.0001f /* tolerance */);
-        assertEquals(MathUtils.pow(y2, gamma),
-                strategy.getBrightness(x2), 0.0001f /* tolerance */);
-        assertEquals(MathUtils.pow(y3, minGamma),
-                strategy.getBrightness(x3), 0.0001f /* tolerance */);
-        // The adjustment should be +1.0 (maximum adjustment).
-        assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
-    }
-
-    @Test
-    public void testGammaCorrectionExtremeChangeAtCenter() {
-        // Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we
-        // just make sure the adjustment reflects the change.
-        Resources resources = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
-                GAMMA_CORRECTION_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                mMockDwbc);
-        assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
-        strategy.addUserDataPoint(2500, 1.0f);
-        assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
-        strategy.addUserDataPoint(2500, 0.0f);
-        assertEquals(-1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
-    }
-
-    @Test
-    public void testGammaCorrectionChangeAtEdges() {
-        // The algorithm behaves differently at the edges, because gamma correction there tends to
-        // be extreme. If we add a user data point at (x0, y0+0.3), the adjustment should be 0.3,
-        // resulting in a gamma of 3**-0.6 = ~0.52.
-        final int x0 = 100;
-        final int x2 = 2500;
-        final int x4 = 4900;
-        final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0);
-        final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
-        final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4);
-        Resources resources = createResources(EMPTY_INT_ARRAY);
-        DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
-                DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
-                GAMMA_CORRECTION_NITS);
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                mMockDwbc);
-        // Validity, as per tradition:
-        assertEquals(y0, strategy.getBrightness(x0), 0.0001f /* tolerance */);
-        assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
-        assertEquals(y4, strategy.getBrightness(x4), 0.0001f /* tolerance */);
-        // Rollin':
-        float adjustment = 0.3f;
-        float gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment);
-        strategy.addUserDataPoint(x0, y0 + adjustment);
-        assertEquals(y0 + adjustment, strategy.getBrightness(x0), 0.0001f /* tolerance */);
-        assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */);
-        assertEquals(MathUtils.pow(y4, gamma), strategy.getBrightness(x4), 0.0001f /* tolerance */);
-        assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
-        // Similarly, if we set a user data point at (x4, 1.0), the adjustment should be 1 - y4.
-        adjustment = 1.0f - y4;
-        gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment);
-        strategy.addUserDataPoint(x4, 1.0f);
-        assertEquals(MathUtils.pow(y0, gamma), strategy.getBrightness(x0), 0.0001f /* tolerance */);
-        assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */);
-        assertEquals(1.0f, strategy.getBrightness(x4), 0.0001f /* tolerance */);
-        assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
deleted file mode 100644
index 46956d7..0000000
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ /dev/null
@@ -1,493 +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.server.display;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.hardware.display.BrightnessInfo;
-import android.os.Handler;
-import android.os.IThermalEventListener;
-import android.os.IThermalService;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.Temperature;
-import android.os.Temperature.ThrottlingStatus;
-import android.os.test.TestLooper;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.os.BackgroundThread;
-import com.android.server.display.BrightnessThrottler.Injector;
-import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
-import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
-import com.android.server.display.mode.DisplayModeDirectorTest;
-
-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 java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class BrightnessThrottlerTest {
-    private static final float EPSILON = 0.000001f;
-
-    private Handler mHandler;
-    private TestLooper mTestLooper;
-
-    @Mock IThermalService mThermalServiceMock;
-    @Mock Injector mInjectorMock;
-
-    DisplayModeDirectorTest.FakeDeviceConfig mDeviceConfigFake;
-
-    @Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        when(mInjectorMock.getThermalService()).thenReturn(mThermalServiceMock);
-        mTestLooper = new TestLooper();
-        mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() {
-            @Override
-            public boolean handleMessage(Message msg) {
-                return true;
-            }
-        });
-        mDeviceConfigFake = new DisplayModeDirectorTest.FakeDeviceConfig();
-        when(mInjectorMock.getDeviceConfig()).thenReturn(mDeviceConfigFake);
-
-    }
-
-    /////////////////
-    // Test Methods
-    /////////////////
-
-    @Test
-    public void testThermalBrightnessThrottlingData() {
-        List<ThrottlingLevel> singleLevel = new ArrayList<>();
-        singleLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
-
-        List<ThrottlingLevel> validLevels = new ArrayList<>();
-        validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f));
-        validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
-
-        List<ThrottlingLevel> unsortedThermalLevels = new ArrayList<>();
-        unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f));
-        unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f));
-
-        List<ThrottlingLevel> unsortedBrightnessLevels = new ArrayList<>();
-        unsortedBrightnessLevels.add(
-                new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f));
-        unsortedBrightnessLevels.add(
-                new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f));
-
-        List<ThrottlingLevel> unsortedLevels = new ArrayList<>();
-        unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f));
-        unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f));
-
-        List<ThrottlingLevel> invalidLevel = new ArrayList<>();
-        invalidLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-                PowerManager.BRIGHTNESS_MAX + EPSILON));
-
-        // Test invalid data
-        ThermalBrightnessThrottlingData data;
-        data = ThermalBrightnessThrottlingData.create((List<ThrottlingLevel>) null);
-        assertEquals(data, null);
-        data = ThermalBrightnessThrottlingData.create(new ArrayList<ThrottlingLevel>());
-        assertEquals(data, null);
-        data = ThermalBrightnessThrottlingData.create(unsortedThermalLevels);
-        assertEquals(data, null);
-        data = ThermalBrightnessThrottlingData.create(unsortedBrightnessLevels);
-        assertEquals(data, null);
-        data = ThermalBrightnessThrottlingData.create(unsortedLevels);
-        assertEquals(data, null);
-        data = ThermalBrightnessThrottlingData.create(invalidLevel);
-        assertEquals(data, null);
-
-        // Test valid data
-        data = ThermalBrightnessThrottlingData.create(singleLevel);
-        assertNotEquals(data, null);
-        assertThrottlingLevelsEquals(singleLevel, data.throttlingLevels);
-
-        data = ThermalBrightnessThrottlingData.create(validLevels);
-        assertNotEquals(data, null);
-        assertThrottlingLevelsEquals(validLevels, data.throttlingLevels);
-    }
-
-    @Test
-    public void testThermalThrottlingUnsupported() {
-        final BrightnessThrottler throttler = createThrottlerUnsupported();
-        assertFalse(throttler.deviceSupportsThrottling());
-
-        // Thermal listener shouldn't be registered if throttling is unsupported
-        verify(mInjectorMock, never()).getThermalService();
-
-        // Ensure that brightness is uncapped when the device doesn't support throttling
-        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
-    }
-
-    @Test
-    public void testThermalThrottlingSingleLevel() throws Exception {
-        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-            0.25f);
-
-        List<ThrottlingLevel> levels = new ArrayList<>();
-        levels.add(level);
-        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
-        final BrightnessThrottler throttler = createThrottlerSupported(data);
-        assertTrue(throttler.deviceSupportsThrottling());
-
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
-
-        // Set status too low to trigger throttling
-        listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
-        assertFalse(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
-
-        // Set status just high enough to trigger throttling
-        listener.notifyThrottling(getSkinTemp(level.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Set status more than high enough to trigger throttling
-        listener.notifyThrottling(getSkinTemp(level.thermalStatus + 1));
-        mTestLooper.dispatchAll();
-        assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-           throttler.getBrightnessMaxReason());
-
-        // Return to the lower throttling level
-        listener.notifyThrottling(getSkinTemp(level.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(level.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Cool down
-        listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
-        assertFalse(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
-            throttler.getBrightnessMaxReason());
-    }
-
-    @Test
-    public void testThermalThrottlingMultiLevel() throws Exception {
-        final ThrottlingLevel levelLo = new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE,
-            0.62f);
-        final ThrottlingLevel levelHi = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-            0.25f);
-
-        List<ThrottlingLevel> levels = new ArrayList<>();
-        levels.add(levelLo);
-        levels.add(levelHi);
-        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
-        final BrightnessThrottler throttler = createThrottlerSupported(data);
-        assertTrue(throttler.deviceSupportsThrottling());
-
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
-
-        // Set status too low to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
-        assertFalse(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
-
-        // Set status just high enough to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Set status to an intermediate throttling level
-        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1));
-        mTestLooper.dispatchAll();
-        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Set status to the highest configured throttling level
-        listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Set status to exceed the highest configured throttling level
-        listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus + 1));
-        mTestLooper.dispatchAll();
-        assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Return to an intermediate throttling level
-        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1));
-        mTestLooper.dispatchAll();
-        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Return to the lowest configured throttling level
-        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL,
-            throttler.getBrightnessMaxReason());
-
-        // Cool down
-        listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
-        assertFalse(throttler.isThrottled());
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
-    }
-
-    @Test public void testUpdateThermalThrottlingData() throws Exception {
-        // Initialise brightness throttling levels
-        // Ensure that they are overridden by setting the data through device config.
-        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-                0.25f);
-        List<ThrottlingLevel> levels = new ArrayList<>();
-        levels.add(level);
-        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,0.4");
-        final BrightnessThrottler throttler = createThrottlerSupported(data);
-
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.4f);
-
-        // Set new (valid) data from device config
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,0.8");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f);
-
-        mDeviceConfigFake.setThermalBrightnessThrottlingData(
-                "123,1,critical,0.75;123,1,critical,0.99,id_2");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.75f);
-        mDeviceConfigFake.setThermalBrightnessThrottlingData(
-                "123,1,critical,0.8,default;123,1,critical,0.99,id_2");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f);
-    }
-
-    @Test public void testInvalidThrottlingStrings() throws Exception {
-        // Initialise brightness throttling levels
-        // Ensure that they are not overridden by invalid data through device config.
-        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-                0.25f);
-        List<ThrottlingLevel> levels = new ArrayList<>();
-        levels.add(level);
-        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
-        final BrightnessThrottler throttler = createThrottlerSupported(data);
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
-
-        // None of these are valid so shouldn't override the original data
-
-        // Not the current id
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("321,1,critical,0.4");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Incorrect number
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,0,critical,0.4");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Incorrect number
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,2,critical,0.4");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid level
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,invalid,0.4");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid brightness
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,none");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid brightness
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,-3");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid format
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("invalid string");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid format
-        mDeviceConfigFake.setThermalBrightnessThrottlingData("");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid string format
-        mDeviceConfigFake.setThermalBrightnessThrottlingData(
-                "123,default,1,critical,0.75,1,critical,0.99");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid level string and number string
-        mDeviceConfigFake.setThermalBrightnessThrottlingData(
-                "123,1,1,critical,0.75,id_2,1,critical,0.99");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-        // Invalid format - (two default ids for same display)
-        mDeviceConfigFake.setThermalBrightnessThrottlingData(
-                "123,1,critical,0.75,default;123,1,critical,0.99");
-        testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
-    }
-
-    private void testThermalThrottling(BrightnessThrottler throttler,
-            IThermalEventListener listener, float tooLowCap, float tooHighCap) throws Exception {
-        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-                tooHighCap);
-
-        // Set status too low to trigger throttling
-        listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(tooLowCap, throttler.getBrightnessCap(), 0f);
-        assertFalse(throttler.isThrottled());
-
-        // Set status high enough to trigger throttling
-        listener.notifyThrottling(getSkinTemp(level.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(tooHighCap, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-    }
-
-    @Test public void testMultipleConfigPoints() throws Exception {
-        // Initialise brightness throttling levels
-        final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
-                0.25f);
-        List<ThrottlingLevel> levels = new ArrayList<>();
-        levels.add(level);
-        final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels);
-
-        // These are identical to the string set below
-        final ThrottlingLevel levelSevere = new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE,
-                0.9f);
-        final ThrottlingLevel levelCritical = new ThrottlingLevel(
-                PowerManager.THERMAL_STATUS_CRITICAL, 0.5f);
-        final ThrottlingLevel levelEmergency = new ThrottlingLevel(
-                PowerManager.THERMAL_STATUS_EMERGENCY, 0.1f);
-
-        mDeviceConfigFake.setThermalBrightnessThrottlingData(
-                "123,3,severe,0.9,critical,0.5,emergency,0.1");
-        final BrightnessThrottler throttler = createThrottlerSupported(data);
-
-        verify(mThermalServiceMock).registerThermalEventListenerWithType(
-                mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
-        final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
-
-        // Ensure that the multiple levels set via the string through the device config correctly
-        // override the original display device config ones.
-
-        // levelSevere
-        // Set status too low to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelSevere.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
-        assertFalse(throttler.isThrottled());
-
-        // Set status high enough to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelSevere.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(0.9f, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-
-        // levelCritical
-        // Set status too low to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelCritical.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(0.9f, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-
-        // Set status high enough to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelCritical.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(0.5f, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-
-        //levelEmergency
-        // Set status too low to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelEmergency.thermalStatus - 1));
-        mTestLooper.dispatchAll();
-        assertEquals(0.5f, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-
-        // Set status high enough to trigger throttling
-        listener.notifyThrottling(getSkinTemp(levelEmergency.thermalStatus));
-        mTestLooper.dispatchAll();
-        assertEquals(0.1f, throttler.getBrightnessCap(), 0f);
-        assertTrue(throttler.isThrottled());
-    }
-
-    private void assertThrottlingLevelsEquals(
-            List<ThrottlingLevel> expected,
-            List<ThrottlingLevel> actual) {
-        assertEquals(expected.size(), actual.size());
-
-        for (int i = 0; i < expected.size(); i++) {
-            ThrottlingLevel expectedLevel = expected.get(i);
-            ThrottlingLevel actualLevel = actual.get(i);
-
-            assertEquals(expectedLevel.thermalStatus, actualLevel.thermalStatus);
-            assertEquals(expectedLevel.brightness, actualLevel.brightness, 0.0f);
-        }
-    }
-
-    private BrightnessThrottler createThrottlerUnsupported() {
-        return new BrightnessThrottler(mInjectorMock, mHandler, mHandler,
-                /* throttlingChangeCallback= */ () -> {}, /* uniqueDisplayId= */ null,
-                /* thermalThrottlingDataId= */ null,
-                /* thermalThrottlingDataMap= */ new HashMap<>(1));
-    }
-
-    private BrightnessThrottler createThrottlerSupported(ThermalBrightnessThrottlingData data) {
-        assertNotNull(data);
-        HashMap<String, ThermalBrightnessThrottlingData> throttlingDataMap = new HashMap<>(1);
-        throttlingDataMap.put("default", data);
-        return new BrightnessThrottler(mInjectorMock, mHandler, BackgroundThread.getHandler(),
-                () -> {}, "123", "default", throttlingDataMap);
-    }
-
-    private Temperature getSkinTemp(@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/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
deleted file mode 100644
index 021f2d1..0000000
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ /dev/null
@@ -1,1247 +0,0 @@
-/*
- * Copyright 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.display;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-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;
-
-import android.app.ActivityTaskManager.RootTaskInfo;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ParceledListSlice;
-import android.database.ContentObserver;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.display.AmbientBrightnessDayStats;
-import android.hardware.display.BrightnessChangeEvent;
-import android.hardware.display.BrightnessConfiguration;
-import android.hardware.display.ColorDisplayManager;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayedContentSample;
-import android.hardware.display.DisplayedContentSamplingAttributes;
-import android.hardware.input.InputSensorInfo;
-import android.os.BatteryManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.MessageQueue;
-import android.os.Parcel;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.AtomicFile;
-import android.view.Display;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.R;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Constructor;
-import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class BrightnessTrackerTest {
-    private static final float DEFAULT_INITIAL_BRIGHTNESS = 2.5f;
-    private static final boolean DEFAULT_COLOR_SAMPLING_ENABLED = true;
-    private static final String DEFAULT_DISPLAY_ID = "123";
-    private static final float FLOAT_DELTA = 0.01f;
-
-    @Mock private InputSensorInfo mInputSensorInfoMock;
-
-    private BrightnessTracker mTracker;
-    private TestInjector mInjector;
-    private Sensor mLightSensorFake;
-
-    private static Object sHandlerLock = new Object();
-    private static Handler sHandler;
-    private static HandlerThread sThread =
-            new HandlerThread("brightness.test", android.os.Process.THREAD_PRIORITY_BACKGROUND);
-
-    private int mDefaultNightModeColorTemperature;
-    private float mRbcOffsetFactor;
-
-    private static Handler ensureHandler() {
-        synchronized (sHandlerLock) {
-            if (sHandler == null) {
-                sThread.start();
-                sHandler = new Handler(sThread.getLooper());
-            }
-            return sHandler;
-        }
-    }
-
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mInjector = new TestInjector(ensureHandler());
-        mLightSensorFake = new Sensor(mInputSensorInfoMock);
-
-        mTracker = new BrightnessTracker(InstrumentationRegistry.getContext(), mInjector);
-        mTracker.setLightSensor(mLightSensorFake);
-        mDefaultNightModeColorTemperature =
-                InstrumentationRegistry.getContext().getResources().getInteger(
-                R.integer.config_nightDisplayColorTemperatureDefault);
-        mRbcOffsetFactor = InstrumentationRegistry.getContext()
-                .getSystemService(ColorDisplayManager.class).getReduceBrightColorsOffsetFactor();
-    }
-
-    @Test
-    public void testStartStopTrackerScreenOnOff() {
-        mInjector.mInteractive = false;
-        startTracker(mTracker);
-        assertNull(mInjector.mSensorListener);
-        assertNotNull(mInjector.mBroadcastReceiver);
-        assertTrue(mInjector.mIdleScheduled);
-        mInjector.sendScreenChange(/* screenOn= */ true);
-        assertNotNull(mInjector.mSensorListener);
-        assertTrue(mInjector.mColorSamplingEnabled);
-
-        mInjector.sendScreenChange(/* screenOn= */ false);
-        assertNull(mInjector.mSensorListener);
-        assertFalse(mInjector.mColorSamplingEnabled);
-
-        // Turn screen on while brightness mode is manual
-        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
-        mInjector.sendScreenChange(/* screenOn= */ true);
-        assertNull(mInjector.mSensorListener);
-        assertFalse(mInjector.mColorSamplingEnabled);
-
-        // Set brightness mode to automatic while screen is off.
-        mInjector.sendScreenChange(/* screenOn= */ false);
-        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
-        assertNull(mInjector.mSensorListener);
-        assertFalse(mInjector.mColorSamplingEnabled);
-
-        // Turn on screen while brightness mode is automatic.
-        mInjector.sendScreenChange(/* screenOn= */ true);
-        assertNotNull(mInjector.mSensorListener);
-        assertTrue(mInjector.mColorSamplingEnabled);
-
-        mTracker.stop();
-        assertNull(mInjector.mSensorListener);
-        assertNull(mInjector.mBroadcastReceiver);
-        assertFalse(mInjector.mIdleScheduled);
-        assertFalse(mInjector.mColorSamplingEnabled);
-    }
-
-    @Test
-    public void testModifyBrightnessConfiguration() {
-        mInjector.mInteractive = true;
-        // Start with tracker not listening for color samples.
-        startTracker(mTracker, DEFAULT_INITIAL_BRIGHTNESS, /* collectColorSamples= */ false);
-        assertFalse(mInjector.mColorSamplingEnabled);
-
-        // Update brightness config to enabled color sampling.
-        mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true);
-        mInjector.waitForHandler();
-        assertTrue(mInjector.mColorSamplingEnabled);
-
-        // Update brightness config to disable color sampling.
-        mTracker.setShouldCollectColorSample(/* collectColorSamples= */ false);
-        mInjector.waitForHandler();
-        assertFalse(mInjector.mColorSamplingEnabled);
-
-        // Pretend screen is off, update config to turn on color sampling.
-        mInjector.sendScreenChange(/* screenOn= */ false);
-        mTracker.setShouldCollectColorSample(/* collectColorSamples= */ true);
-        mInjector.waitForHandler();
-        assertFalse(mInjector.mColorSamplingEnabled);
-
-        // Pretend screen is on.
-        mInjector.sendScreenChange(/* screenOn= */ true);
-        assertTrue(mInjector.mColorSamplingEnabled);
-
-        mTracker.stop();
-        assertFalse(mInjector.mColorSamplingEnabled);
-    }
-
-    @Test
-    public void testNoColorSampling_WrongPixelFormat() {
-        mInjector.mDefaultSamplingAttributes =
-                new DisplayedContentSamplingAttributes(
-                        0x23,
-                        mInjector.mDefaultSamplingAttributes.getDataspace(),
-                        mInjector.mDefaultSamplingAttributes.getComponentMask());
-        startTracker(mTracker);
-        assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
-    }
-
-    @Test
-    public void testNoColorSampling_MissingComponent() {
-        mInjector.mDefaultSamplingAttributes =
-                new DisplayedContentSamplingAttributes(
-                        mInjector.mDefaultSamplingAttributes.getPixelFormat(),
-                        mInjector.mDefaultSamplingAttributes.getDataspace(),
-                        0x2);
-        startTracker(mTracker);
-        assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
-    }
-
-    @Test
-    public void testNoColorSampling_NoSupport() {
-        mInjector.mDefaultSamplingAttributes = null;
-        startTracker(mTracker);
-        assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
-    }
-
-    @Test
-    public void testColorSampling_FrameRateChange() {
-        startTracker(mTracker);
-        assertTrue(mInjector.mColorSamplingEnabled);
-        assertNotNull(mInjector.mDisplayListener);
-        int noFramesSampled = mInjector.mNoColorSamplingFrames;
-        mInjector.mFrameRate = 120.0f;
-        // Wrong display
-        mInjector.mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY + 10);
-        assertEquals(noFramesSampled, mInjector.mNoColorSamplingFrames);
-        // Correct display
-        mInjector.mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY);
-        assertEquals(noFramesSampled * 2, mInjector.mNoColorSamplingFrames);
-    }
-
-    @Test
-    public void testAdaptiveOnOff() {
-        mInjector.mInteractive = true;
-        mInjector.mIsBrightnessModeAutomatic = false;
-        startTracker(mTracker);
-        assertNull(mInjector.mSensorListener);
-        assertNotNull(mInjector.mBroadcastReceiver);
-        assertNotNull(mInjector.mContentObserver);
-        assertTrue(mInjector.mIdleScheduled);
-        assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
-
-        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
-        assertNotNull(mInjector.mSensorListener);
-        assertTrue(mInjector.mColorSamplingEnabled);
-        assertNotNull(mInjector.mDisplayListener);
-
-        SensorEventListener listener = mInjector.mSensorListener;
-        DisplayManager.DisplayListener displayListener = mInjector.mDisplayListener;
-        mInjector.mSensorListener = null;
-        mInjector.mColorSamplingEnabled = false;
-        mInjector.mDisplayListener = null;
-        // Duplicate notification
-        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
-        // Sensor shouldn't have been registered as it was already registered.
-        assertNull(mInjector.mSensorListener);
-        assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
-        mInjector.mDisplayListener = displayListener;
-        mInjector.mColorSamplingEnabled = true;
-
-        mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
-        assertNull(mInjector.mSensorListener);
-        assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
-
-        mTracker.stop();
-        assertNull(mInjector.mSensorListener);
-        assertNull(mInjector.mBroadcastReceiver);
-        assertNull(mInjector.mContentObserver);
-        assertFalse(mInjector.mIdleScheduled);
-        assertFalse(mInjector.mColorSamplingEnabled);
-        assertNull(mInjector.mDisplayListener);
-    }
-
-    @Test
-    public void testBrightnessEvent() {
-        final float brightness = 0.5f;
-        final String displayId = "1234";
-
-        startTracker(mTracker);
-        final long sensorTime = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-        final long currentTime = mInjector.currentTimeMillis();
-        notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1.0f},
-                new long[] {sensorTime});
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        mTracker.stop();
-
-        assertEquals(1, events.size());
-        BrightnessChangeEvent event = events.get(0);
-        assertEquals(currentTime, event.timeStamp);
-        assertEquals(displayId, event.uniqueDisplayId);
-        assertEquals(1, event.luxValues.length);
-        assertEquals(1.0f, event.luxValues[0], FLOAT_DELTA);
-        assertEquals(currentTime - TimeUnit.SECONDS.toMillis(2),
-                event.luxTimestamps[0]);
-        assertEquals(brightness, event.brightness, FLOAT_DELTA);
-        assertEquals(DEFAULT_INITIAL_BRIGHTNESS, event.lastBrightness, FLOAT_DELTA);
-
-        // System had no data so these should all be at defaults.
-        assertEquals(Float.NaN, event.batteryLevel, 0.0);
-        assertFalse(event.nightMode);
-        assertEquals(mDefaultNightModeColorTemperature, event.colorTemperature);
-    }
-
-    @Test
-    public void testMultipleBrightnessEvents() {
-        final float brightnessOne = 0.2f;
-        final float brightnessTwo = 0.4f;
-        final float brightnessThree = 0.6f;
-        final float brightnessFour = 0.3f;
-        final String displayId = "1234";
-        final float[] luxValues = new float[]{1.0f};
-
-        startTracker(mTracker);
-        final long sensorTime = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
-        final long sensorTime2 = sensorTime + TimeUnit.SECONDS.toMillis(20);
-        final long sensorTime3 = sensorTime2 + TimeUnit.SECONDS.toMillis(30);
-        final long sensorTime4 = sensorTime3 + TimeUnit.SECONDS.toMillis(40);
-        final long originalTime = mInjector.currentTimeMillis();
-
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-        notifyBrightnessChanged(mTracker, brightnessOne, displayId, luxValues,
-                new long[] {sensorTime});
-
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(20));
-        notifyBrightnessChanged(mTracker, brightnessTwo, displayId, luxValues,
-                new long[] {sensorTime2});
-
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(30));
-        notifyBrightnessChanged(mTracker, brightnessThree, displayId, luxValues,
-                new long[] {sensorTime3});
-
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(40));
-        notifyBrightnessChanged(mTracker, brightnessFour, displayId, luxValues,
-                new long[] {sensorTime4});
-        mTracker.stop();
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        assertEquals(4, events.size());
-        BrightnessChangeEvent eventOne = events.get(0);
-        assertEquals(brightnessOne, eventOne.brightness, FLOAT_DELTA);
-        assertEquals(originalTime,
-                eventOne.luxTimestamps[0]);
-
-        BrightnessChangeEvent eventTwo = events.get(1);
-        assertEquals(brightnessTwo, eventTwo.brightness, FLOAT_DELTA);
-        assertEquals(originalTime + TimeUnit.SECONDS.toMillis(20),
-                eventTwo.luxTimestamps[0]);
-
-        BrightnessChangeEvent eventThree = events.get(2);
-        assertEquals(brightnessThree, eventThree.brightness, FLOAT_DELTA);
-        assertEquals(originalTime + TimeUnit.SECONDS.toMillis(50),
-                eventThree.luxTimestamps[0]);
-
-        BrightnessChangeEvent eventFour = events.get(3);
-        assertEquals(brightnessFour, eventFour.brightness, FLOAT_DELTA);
-        assertEquals(originalTime + TimeUnit.SECONDS.toMillis(90),
-                eventFour.luxTimestamps[0]);
-    }
-
-    @Test
-    public void testBrightnessFullPopulatedEvent() {
-        final int initialBrightness = 230;
-        final int brightness = 130;
-        final String displayId = "1234";
-
-        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
-        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3333);
-
-        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
-        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40);
-
-        startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED);
-        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
-                batteryChangeEvent(30, 60));
-        final long currentTime = mInjector.currentTimeMillis();
-        notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1000.0f},
-                new long[] {TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos())});
-        List<BrightnessChangeEvent> eventsNoPackage
-                = mTracker.getEvents(0, false).getList();
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        mTracker.stop();
-
-        assertEquals(1, events.size());
-        BrightnessChangeEvent event = events.get(0);
-        assertEquals(event.timeStamp, currentTime);
-        assertEquals(displayId, event.uniqueDisplayId);
-        assertArrayEquals(new float[] {1000.0f}, event.luxValues, FLOAT_DELTA);
-        assertArrayEquals(new long[] {currentTime}, event.luxTimestamps);
-        assertEquals(brightness, event.brightness, FLOAT_DELTA);
-        assertEquals(initialBrightness, event.lastBrightness, FLOAT_DELTA);
-        assertEquals(0.5, event.batteryLevel, FLOAT_DELTA);
-        assertTrue(event.nightMode);
-        assertEquals(3333, event.colorTemperature);
-        assertTrue(event.reduceBrightColors);
-        assertEquals(40, event.reduceBrightColorsStrength);
-        assertEquals(brightness * mRbcOffsetFactor, event.reduceBrightColorsOffset, FLOAT_DELTA);
-        assertEquals("a.package", event.packageName);
-        assertEquals(0, event.userId);
-        assertArrayEquals(new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, event.colorValueBuckets);
-        assertEquals(10000, event.colorSampleDuration);
-
-        assertEquals(1, eventsNoPackage.size());
-        assertNull(eventsNoPackage.get(0).packageName);
-    }
-
-    @Test
-    public void testIgnoreAutomaticBrightnessChange() {
-        final int initialBrightness = 30;
-        startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED);
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
-
-        final int systemUpdatedBrightness = 20;
-        notifyBrightnessChanged(mTracker, systemUpdatedBrightness, /* userInitiated= */ false,
-                /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ false,
-                /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID);
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        // No events because we filtered out our change.
-        assertEquals(0, events.size());
-
-        final int firstUserUpdateBrightness = 20;
-        // Then change comes from somewhere else so we shouldn't filter.
-        notifyBrightnessChanged(mTracker, firstUserUpdateBrightness);
-
-        // and with a different brightness value.
-        final int secondUserUpdateBrightness = 34;
-        notifyBrightnessChanged(mTracker, secondUserUpdateBrightness);
-        events = mTracker.getEvents(0, true).getList();
-
-        assertEquals(2, events.size());
-        // First event is change from system update (20) to first user update (20)
-        assertEquals(systemUpdatedBrightness, events.get(0).lastBrightness, FLOAT_DELTA);
-        assertEquals(firstUserUpdateBrightness, events.get(0).brightness, FLOAT_DELTA);
-        // Second event is from first to second user update.
-        assertEquals(firstUserUpdateBrightness, events.get(1).lastBrightness, FLOAT_DELTA);
-        assertEquals(secondUserUpdateBrightness, events.get(1).brightness, FLOAT_DELTA);
-
-        mTracker.stop();
-    }
-
-    @Test
-    public void testLimitedBufferSize() {
-        startTracker(mTracker);
-
-        for (int brightness = 0; brightness <= 255; ++brightness) {
-            mInjector.incrementTime(TimeUnit.SECONDS.toNanos(1));
-            notifyBrightnessChanged(mTracker, brightness);
-        }
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        mTracker.stop();
-
-        // Should be capped at 100 events, and they should be the most recent 100.
-        assertEquals(100, events.size());
-        for (int i = 0; i < events.size(); i++) {
-            BrightnessChangeEvent event = events.get(i);
-            assertEquals(156 + i, event.brightness, FLOAT_DELTA);
-        }
-    }
-
-    @Test
-    public void testReadEvents() throws Exception {
-        BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
-                mInjector);
-        mInjector.mCurrentTimeMillis = System.currentTimeMillis();
-        long someTimeAgo = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(12);
-        long twoMonthsAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60);
-        // 3 Events in the file but one too old to read.
-        String eventFile =
-                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<events>\n"
-                + "<event nits=\"194.2\" timestamp=\""
-                + Long.toString(someTimeAgo) + "\" packageName=\""
-                + "com.example.app\" user=\"10\" "
-                + "lastNits=\"32.333\" "
-                + "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\" "
-                + "reduceBrightColors=\"false\" reduceBrightColorsStrength=\"40\" "
-                + "reduceBrightColorsOffset=\"0\"\n"
-                + "uniqueDisplayId=\"123\""
-                + "lux=\"32.2,31.1\" luxTimestamps=\""
-                + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\""
-                + "defaultConfig=\"true\" powerSaveFactor=\"0.5\" userPoint=\"true\" />"
-                + "<event nits=\"71\" timestamp=\""
-                + Long.toString(someTimeAgo) + "\" packageName=\""
-                + "com.android.anapp\" user=\"11\" "
-                + "lastNits=\"32\" "
-                + "batteryLevel=\"0.5\" nightMode=\"true\" colorTemperature=\"3235\" "
-                + "reduceBrightColors=\"true\" reduceBrightColorsStrength=\"40\" "
-                + "reduceBrightColorsOffset=\"0\"\n"
-                + "uniqueDisplayId=\"456\""
-                + "lux=\"132.2,131.1\" luxTimestamps=\""
-                + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\""
-                + "colorSampleDuration=\"3456\" colorValueBuckets=\"123,598,23,19\"/>"
-                // Event that is too old so shouldn't show up.
-                + "<event nits=\"142\" timestamp=\""
-                + Long.toString(twoMonthsAgo) + "\" packageName=\""
-                + "com.example.app\" user=\"10\" "
-                + "lastNits=\"32\" "
-                + "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\" "
-                + "reduceBrightColors=\"false\" reduceBrightColorsStrength=\"40\" "
-                + "reduceBrightColorsOffset=\"0\"\n"
-                + "uniqueDisplayId=\"789\""
-                + "lux=\"32.2,31.1\" luxTimestamps=\""
-                + Long.toString(twoMonthsAgo) + "," + Long.toString(twoMonthsAgo) + "\"/>"
-                + "</events>";
-        tracker.readEventsLocked(getInputStream(eventFile));
-        List<BrightnessChangeEvent> events = tracker.getEvents(0, true).getList();
-        assertEquals(1, events.size());
-        BrightnessChangeEvent event = events.get(0);
-        assertEquals(someTimeAgo, event.timeStamp);
-        assertEquals(194.2, event.brightness, FLOAT_DELTA);
-        assertEquals("123", event.uniqueDisplayId);
-        assertArrayEquals(new float[] {32.2f, 31.1f}, event.luxValues, FLOAT_DELTA);
-        assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps);
-        assertEquals(32.333, event.lastBrightness, FLOAT_DELTA);
-        assertEquals(0, event.userId);
-        assertFalse(event.nightMode);
-        assertFalse(event.reduceBrightColors);
-        assertEquals(1.0f, event.batteryLevel, FLOAT_DELTA);
-        assertEquals("com.example.app", event.packageName);
-        assertTrue(event.isDefaultBrightnessConfig);
-        assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA);
-        assertTrue(event.isUserSetBrightness);
-        assertNull(event.colorValueBuckets);
-
-        events = tracker.getEvents(1, true).getList();
-        assertEquals(1, events.size());
-        event = events.get(0);
-        assertEquals(someTimeAgo, event.timeStamp);
-        assertEquals(71, event.brightness, FLOAT_DELTA);
-        assertEquals("456", event.uniqueDisplayId);
-        assertArrayEquals(new float[] {132.2f, 131.1f}, event.luxValues, FLOAT_DELTA);
-        assertArrayEquals(new long[] {someTimeAgo, someTimeAgo}, event.luxTimestamps);
-        assertEquals(32, event.lastBrightness, FLOAT_DELTA);
-        assertEquals(1, event.userId);
-        assertTrue(event.nightMode);
-        assertEquals(3235, event.colorTemperature);
-        assertTrue(event.reduceBrightColors);
-        assertEquals(0.5f, event.batteryLevel, FLOAT_DELTA);
-        assertEquals("com.android.anapp", event.packageName);
-        // Not present in the event so default to false.
-        assertFalse(event.isDefaultBrightnessConfig);
-        assertEquals(1.0, event.powerBrightnessFactor, FLOAT_DELTA);
-        assertFalse(event.isUserSetBrightness);
-        assertEquals(3456L, event.colorSampleDuration);
-        assertArrayEquals(new long[] {123L, 598L, 23L, 19L}, event.colorValueBuckets);
-
-        // Pretend user 1 is a profile of user 0.
-        mInjector.mProfiles = new int[]{0, 1};
-        events = tracker.getEvents(0, true).getList();
-        // Both events should now be returned.
-        assertEquals(2, events.size());
-        BrightnessChangeEvent userZeroEvent;
-        BrightnessChangeEvent userOneEvent;
-        if (events.get(0).userId == 0) {
-            userZeroEvent = events.get(0);
-            userOneEvent = events.get(1);
-        } else {
-            userZeroEvent = events.get(1);
-            userOneEvent = events.get(0);
-        }
-        assertEquals(0, userZeroEvent.userId);
-        assertEquals("com.example.app", userZeroEvent.packageName);
-        assertEquals(1, userOneEvent.userId);
-        // Events from user 1 should have the package name redacted
-        assertNull(userOneEvent.packageName);
-    }
-
-    @Test
-    public void testFailedRead() {
-        String someTimeAgo =
-                Long.toString(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(12));
-        mInjector.mCurrentTimeMillis = System.currentTimeMillis();
-
-        BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
-                mInjector);
-        String eventFile = "junk in the file";
-        try {
-            tracker.readEventsLocked(getInputStream(eventFile));
-        } catch (IOException e) {
-            // Expected;
-        }
-        assertEquals(0, tracker.getEvents(0, true).getList().size());
-
-        // Missing lux value.
-        eventFile =
-                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                        + "<events>\n"
-                        + "<event nits=\"194\" timestamp=\"" + someTimeAgo + "\" packageName=\""
-                        + "com.example.app\" user=\"10\" "
-                        + "batteryLevel=\"0.7\" nightMode=\"false\" colorTemperature=\"0\" />\n"
-                        + "</events>";
-        try {
-            tracker.readEventsLocked(getInputStream(eventFile));
-        } catch (IOException e) {
-            // Expected;
-        }
-        assertEquals(0, tracker.getEvents(0, true).getList().size());
-    }
-
-    @Test
-    public void testWriteThenRead() throws Exception {
-        final int brightness = 20;
-        final String displayId = "1234";
-
-        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
-        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
-
-        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
-        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40);
-
-        startTracker(mTracker);
-        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
-                batteryChangeEvent(30, 100));
-        final long elapsedTime1 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
-        final long currentTime1 = mInjector.currentTimeMillis();
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-        final long elapsedTime2 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
-        final long currentTime2 = mInjector.currentTimeMillis();
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(3));
-        notifyBrightnessChanged(mTracker, brightness, /* userInitiated= */ true,
-                /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ true,
-                /* isDefaultBrightnessConfig= */ false, displayId, new float[] {2000.0f, 3000.0f},
-                new long[] {elapsedTime1, elapsedTime2});
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        mTracker.writeEventsLocked(baos);
-        mTracker.stop();
-
-        baos.flush();
-        ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray());
-        BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
-                mInjector);
-        tracker.readEventsLocked(input);
-        List<BrightnessChangeEvent> events = tracker.getEvents(0, true).getList();
-
-        assertEquals(1, events.size());
-        BrightnessChangeEvent event = events.get(0);
-        assertEquals(displayId, event.uniqueDisplayId);
-        assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA);
-        assertArrayEquals(new long[] {currentTime1, currentTime2}, event.luxTimestamps);
-        assertEquals(brightness, event.brightness, FLOAT_DELTA);
-        assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
-        assertTrue(event.nightMode);
-        assertEquals(3339, event.colorTemperature);
-        assertTrue(event.reduceBrightColors);
-        assertEquals(40, event.reduceBrightColorsStrength);
-        assertEquals(brightness * mRbcOffsetFactor, event.reduceBrightColorsOffset, FLOAT_DELTA);
-        assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA);
-        assertTrue(event.isUserSetBrightness);
-        assertFalse(event.isDefaultBrightnessConfig);
-        assertArrayEquals(new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, event.colorValueBuckets);
-        assertEquals(10000, event.colorSampleDuration);
-    }
-
-    @Test
-    public void testParcelUnParcel() {
-        Parcel parcel = Parcel.obtain();
-        BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder();
-        builder.setBrightness(23f);
-        builder.setTimeStamp(345L);
-        builder.setPackageName("com.example");
-        builder.setUserId(12);
-        builder.setUniqueDisplayId("9876");
-        float[] luxValues = new float[2];
-        luxValues[0] = 3000.0f;
-        luxValues[1] = 4000.0f;
-        builder.setLuxValues(luxValues);
-        long[] luxTimestamps = new long[2];
-        luxTimestamps[0] = 325L;
-        luxTimestamps[1] = 315L;
-        builder.setLuxTimestamps(luxTimestamps);
-        builder.setBatteryLevel(0.7f);
-        builder.setNightMode(false);
-        builder.setColorTemperature(345);
-        builder.setReduceBrightColors(false);
-        builder.setReduceBrightColorsStrength(40);
-        builder.setReduceBrightColorsOffset(20f);
-        builder.setLastBrightness(50f);
-        builder.setColorValues(new long[] {23, 34, 45}, 1000L);
-        BrightnessChangeEvent event = builder.build();
-
-        event.writeToParcel(parcel, 0);
-        byte[] parceled = parcel.marshall();
-        parcel.recycle();
-
-        parcel = Parcel.obtain();
-        parcel.unmarshall(parceled, 0, parceled.length);
-        parcel.setDataPosition(0);
-
-        BrightnessChangeEvent event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel);
-        parcel.recycle();
-        assertEquals(event.brightness, event2.brightness, FLOAT_DELTA);
-        assertEquals(event.timeStamp, event2.timeStamp);
-        assertEquals(event.packageName, event2.packageName);
-        assertEquals(event.userId, event2.userId);
-        assertEquals(event.uniqueDisplayId, event2.uniqueDisplayId);
-        assertArrayEquals(event.luxValues, event2.luxValues, FLOAT_DELTA);
-        assertArrayEquals(event.luxTimestamps, event2.luxTimestamps);
-        assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA);
-        assertEquals(event.nightMode, event2.nightMode);
-        assertEquals(event.colorTemperature, event2.colorTemperature);
-        assertEquals(event.reduceBrightColors, event2.reduceBrightColors);
-        assertEquals(event.reduceBrightColorsStrength, event2.reduceBrightColorsStrength);
-        assertEquals(event.reduceBrightColorsOffset, event2.reduceBrightColorsOffset, FLOAT_DELTA);
-        assertEquals(event.lastBrightness, event2.lastBrightness, FLOAT_DELTA);
-        assertArrayEquals(event.colorValueBuckets, event2.colorValueBuckets);
-        assertEquals(event.colorSampleDuration, event2.colorSampleDuration);
-
-        parcel = Parcel.obtain();
-        builder.setBatteryLevel(Float.NaN);
-        event = builder.build();
-        event.writeToParcel(parcel, 0);
-        parceled = parcel.marshall();
-        parcel.recycle();
-
-        parcel = Parcel.obtain();
-        parcel.unmarshall(parceled, 0, parceled.length);
-        parcel.setDataPosition(0);
-        event2 = BrightnessChangeEvent.CREATOR.createFromParcel(parcel);
-        assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA);
-    }
-
-    @Test
-    public void testNonNullAmbientStats() {
-        // getAmbientBrightnessStats should return an empty list rather than null when
-        // tracker isn't started or hasn't collected any data.
-        ParceledListSlice<AmbientBrightnessDayStats> slice = mTracker.getAmbientBrightnessStats(0);
-        assertNotNull(slice);
-        assertTrue(slice.getList().isEmpty());
-        startTracker(mTracker);
-        slice = mTracker.getAmbientBrightnessStats(0);
-        assertNotNull(slice);
-        assertTrue(slice.getList().isEmpty());
-    }
-
-    @Test
-    public void testBackgroundHandlerDelay() {
-        final int brightness = 20;
-
-        // Setup tracker.
-        startTracker(mTracker);
-        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-
-        // Block handler from running.
-        final CountDownLatch latch = new CountDownLatch(1);
-        mInjector.mHandler.post(
-                () -> {
-                    try {
-                        latch.await();
-                    } catch (InterruptedException e) {
-                        fail(e.getMessage());
-                    }
-                });
-
-        // Send an event.
-        long eventTime = mInjector.currentTimeMillis();
-        mTracker.notifyBrightnessChanged(brightness, /* userInitiated= */ true,
-                /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
-                /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID, new float[10],
-                new long[10]);
-
-        // Time passes before handler can run.
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-
-        // Let the handler run.
-        latch.countDown();
-        mInjector.waitForHandler();
-
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        mTracker.stop();
-
-        // Check event was recorded with time it was sent rather than handler ran.
-        assertEquals(1, events.size());
-        BrightnessChangeEvent event = events.get(0);
-        assertEquals(eventTime, event.timeStamp);
-    }
-
-    @Test
-    public void testDisplayIdChange() {
-        float firstBrightness = 0.5f;
-        float secondBrightness = 0.75f;
-        String firstDisplayId = "123";
-        String secondDisplayId = "456";
-
-        startTracker(mTracker);
-        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
-
-        notifyBrightnessChanged(mTracker, firstBrightness, firstDisplayId);
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-        List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
-        assertEquals(1, events.size());
-        BrightnessChangeEvent firstEvent = events.get(0);
-        assertEquals(firstDisplayId, firstEvent.uniqueDisplayId);
-        assertEquals(firstBrightness, firstEvent.brightness, 0.001f);
-
-        notifyBrightnessChanged(mTracker, secondBrightness, secondDisplayId);
-        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
-        events = mTracker.getEvents(0, true).getList();
-        assertEquals(2, events.size());
-        BrightnessChangeEvent secondEvent = events.get(1);
-        assertEquals(secondDisplayId, secondEvent.uniqueDisplayId);
-        assertEquals(secondBrightness, secondEvent.brightness, 0.001f);
-
-        mTracker.stop();
-    }
-
-    @Test
-    public void testLightSensorChange() {
-        // verify the tracker started correctly and a listener registered
-        startTracker(mTracker);
-        assertNotNull(mInjector.mSensorListener);
-        assertEquals(mInjector.mLightSensor, mLightSensorFake);
-
-        // Setting the sensor to null should stop the registered listener.
-        mTracker.setLightSensor(null);
-        mInjector.waitForHandler();
-        assertNull(mInjector.mSensorListener);
-        assertNull(mInjector.mLightSensor);
-
-        // Resetting sensor should start listener again
-        mTracker.setLightSensor(mLightSensorFake);
-        mInjector.waitForHandler();
-        assertNotNull(mInjector.mSensorListener);
-        assertEquals(mInjector.mLightSensor, mLightSensorFake);
-
-        Sensor secondSensor = new Sensor(mInputSensorInfoMock);
-        // Setting a different listener should keep things working
-        mTracker.setLightSensor(secondSensor);
-        mInjector.waitForHandler();
-        assertNotNull(mInjector.mSensorListener);
-        assertEquals(mInjector.mLightSensor, secondSensor);
-    }
-
-    @Test
-    public void testSetLightSensorDoesntStartListener() {
-        mTracker.setLightSensor(mLightSensorFake);
-        assertNull(mInjector.mSensorListener);
-    }
-
-    @Test
-    public void testNullLightSensorWontRegister() {
-        mTracker.setLightSensor(null);
-        startTracker(mTracker);
-        assertNull(mInjector.mSensorListener);
-        assertNull(mInjector.mLightSensor);
-    }
-
-    @Test
-    public void testOnlyOneReceiverRegistered() {
-        assertNull(mInjector.mLightSensor);
-        assertNull(mInjector.mSensorListener);
-        assertNull(mInjector.mContentObserver);
-        assertNull(mInjector.mBroadcastReceiver);
-        assertFalse(mInjector.mIdleScheduled);
-        startTracker(mTracker, 0.3f, false);
-
-        assertNotNull(mInjector.mLightSensor);
-        assertNotNull(mInjector.mSensorListener);
-        assertNotNull(mInjector.mContentObserver);
-        assertNotNull(mInjector.mBroadcastReceiver);
-        assertTrue(mInjector.mIdleScheduled);
-        Sensor registeredLightSensor = mInjector.mLightSensor;
-        SensorEventListener registeredSensorListener = mInjector.mSensorListener;
-        ContentObserver registeredContentObserver = mInjector.mContentObserver;
-        BroadcastReceiver registeredBroadcastReceiver = mInjector.mBroadcastReceiver;
-
-        mTracker.start(0.3f);
-        assertSame(registeredLightSensor, mInjector.mLightSensor);
-        assertSame(registeredSensorListener, mInjector.mSensorListener);
-        assertSame(registeredContentObserver, mInjector.mContentObserver);
-        assertSame(registeredBroadcastReceiver, mInjector.mBroadcastReceiver);
-
-        mTracker.stop();
-        assertNull(mInjector.mLightSensor);
-        assertNull(mInjector.mSensorListener);
-        assertNull(mInjector.mContentObserver);
-        assertNull(mInjector.mBroadcastReceiver);
-        assertFalse(mInjector.mIdleScheduled);
-
-        // 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));
-    }
-
-    private Intent batteryChangeEvent(int level, int scale) {
-        Intent intent = new Intent();
-        intent.setAction(Intent.ACTION_BATTERY_CHANGED);
-        intent.putExtra(BatteryManager.EXTRA_LEVEL, level);
-        intent.putExtra(BatteryManager.EXTRA_SCALE, scale);
-        return intent;
-    }
-
-    private SensorEvent createSensorEvent(float lux) {
-        SensorEvent event;
-        try {
-            Constructor<SensorEvent> constr =
-                    SensorEvent.class.getDeclaredConstructor(Integer.TYPE);
-            constr.setAccessible(true);
-            event = constr.newInstance(1);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-        event.values[0] = lux;
-        event.timestamp = mInjector.mElapsedRealtimeNanos;
-
-        return event;
-    }
-
-    private void startTracker(BrightnessTracker tracker) {
-        startTracker(tracker, DEFAULT_INITIAL_BRIGHTNESS,  DEFAULT_COLOR_SAMPLING_ENABLED);
-    }
-
-    private void startTracker(BrightnessTracker tracker, float initialBrightness,
-            boolean collectColorSamples) {
-        tracker.start(initialBrightness);
-        tracker.setShouldCollectColorSample(collectColorSamples);
-        mInjector.waitForHandler();
-    }
-
-    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness) {
-        notifyBrightnessChanged(tracker, brightness, DEFAULT_DISPLAY_ID);
-    }
-
-    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
-            String displayId) {
-        notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true,
-                /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
-                /* isDefaultBrightnessConfig= */ false, displayId, new float[10], new long[10]);
-    }
-
-    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
-            String displayId, float[] luxValues, long[] luxTimestamps) {
-        notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true,
-                /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
-                /* isDefaultBrightnessConfig= */ false, displayId, luxValues, luxTimestamps);
-    }
-
-    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
-            boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
-            boolean isDefaultBrightnessConfig, String displayId) {
-        tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
-                isUserSetBrightness, isDefaultBrightnessConfig, displayId, new float[10],
-                new long[10]);
-        mInjector.waitForHandler();
-    }
-
-    private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
-            boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
-            boolean isDefaultBrightnessConfig, String displayId, float[] luxValues,
-            long[] luxTimestamps) {
-        tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
-                isUserSetBrightness, isDefaultBrightnessConfig, displayId, luxValues,
-                luxTimestamps);
-        mInjector.waitForHandler();
-    }
-
-    private BrightnessConfiguration buildBrightnessConfiguration(boolean collectColorSamples) {
-        BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(
-                /* lux= */ new float[] {0f, 10f, 100f},
-                /* nits= */ new float[] {1f, 90f, 100f});
-        builder.setShouldCollectColorSamples(collectColorSamples);
-        return builder.build();
-    }
-
-    private static final class Idle implements MessageQueue.IdleHandler {
-        private boolean mIdle;
-
-        @Override
-        public boolean queueIdle() {
-            synchronized (this) {
-                mIdle = true;
-                notifyAll();
-            }
-            return false;
-        }
-
-        public synchronized void waitForIdle() {
-            while (!mIdle) {
-                try {
-                    wait();
-                } catch (InterruptedException e) {
-                }
-            }
-        }
-    }
-
-    private class TestInjector extends BrightnessTracker.Injector {
-        SensorEventListener mSensorListener;
-        Sensor mLightSensor;
-        BroadcastReceiver mBroadcastReceiver;
-        DisplayManager.DisplayListener mDisplayListener;
-        Map<String, Integer> mSecureIntSettings = new HashMap<>();
-        long mCurrentTimeMillis = System.currentTimeMillis();
-        long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
-        Handler mHandler;
-        boolean mIdleScheduled;
-        boolean mInteractive = true;
-        int[] mProfiles;
-        ContentObserver mContentObserver;
-        boolean mIsBrightnessModeAutomatic = true;
-        boolean mColorSamplingEnabled = false;
-        DisplayedContentSamplingAttributes mDefaultSamplingAttributes =
-                new DisplayedContentSamplingAttributes(0x37, 0, 0x4);
-        float mFrameRate = 60.0f;
-        int mNoColorSamplingFrames;
-
-
-        public TestInjector(Handler handler) {
-            mHandler = handler;
-        }
-
-        void incrementTime(long timeMillis) {
-            mCurrentTimeMillis += timeMillis;
-            mElapsedRealtimeNanos += TimeUnit.MILLISECONDS.toNanos(timeMillis);
-        }
-
-        void setBrightnessMode(boolean isBrightnessModeAutomatic) {
-          mIsBrightnessModeAutomatic = isBrightnessModeAutomatic;
-          mContentObserver.dispatchChange(false, null);
-          waitForHandler();
-        }
-
-        void sendScreenChange(boolean screenOn) {
-            mInteractive = screenOn;
-            Intent intent = new Intent();
-            intent.setAction(screenOn ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
-            mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(), intent);
-            waitForHandler();
-        }
-
-        void waitForHandler() {
-            Idle idle = new Idle();
-            mHandler.getLooper().getQueue().addIdleHandler(idle);
-            mHandler.post(() -> {});
-            idle.waitForIdle();
-        }
-
-        @Override
-        public void registerSensorListener(Context context,
-                SensorEventListener sensorListener, Sensor lightSensor, Handler handler) {
-            mSensorListener = sensorListener;
-            mLightSensor = lightSensor;
-        }
-
-        @Override
-        public void unregisterSensorListener(Context context,
-                SensorEventListener sensorListener) {
-            mSensorListener = null;
-            mLightSensor = null;
-        }
-
-        @Override
-        public void registerBrightnessModeObserver(ContentResolver resolver,
-                ContentObserver settingsObserver) {
-            mContentObserver = settingsObserver;
-        }
-
-        @Override
-        public void unregisterBrightnessModeObserver(Context context,
-                ContentObserver settingsObserver) {
-            mContentObserver = null;
-        }
-
-        @Override
-        public void registerReceiver(Context context,
-                BroadcastReceiver shutdownReceiver, IntentFilter shutdownFilter) {
-            mBroadcastReceiver = shutdownReceiver;
-        }
-
-        @Override
-        public void unregisterReceiver(Context context,
-                BroadcastReceiver broadcastReceiver) {
-            assertEquals(mBroadcastReceiver, broadcastReceiver);
-            mBroadcastReceiver = null;
-        }
-
-        @Override
-        public Handler getBackgroundHandler() {
-            return mHandler;
-        }
-
-        @Override
-        public boolean isBrightnessModeAutomatic(ContentResolver resolver) {
-            return mIsBrightnessModeAutomatic;
-        }
-
-        @Override
-        public int getSecureIntForUser(ContentResolver resolver, String setting, int defaultValue,
-                int userId) {
-            Integer value = mSecureIntSettings.get(setting);
-            if (value == null) {
-                return defaultValue;
-            } else {
-                return value;
-            }
-        }
-
-        @Override
-        public AtomicFile getFile(String filename) {
-            // Don't have the test write / read from anywhere.
-            return null;
-        }
-
-        @Override
-        public AtomicFile getLegacyFile(String filename) {
-            // Don't have the test write / read from anywhere.
-            return null;
-        }
-
-        @Override
-        public long currentTimeMillis() {
-            return mCurrentTimeMillis;
-        }
-
-        @Override
-        public long elapsedRealtimeNanos() {
-            return mElapsedRealtimeNanos;
-        }
-
-        @Override
-        public int getUserSerialNumber(UserManager userManager, int userId) {
-            return userId + 10;
-        }
-
-        @Override
-        public int getUserId(UserManager userManager, int userSerialNumber) {
-            return userSerialNumber - 10;
-        }
-
-        @Override
-        public int[] getProfileIds(UserManager userManager, int userId) {
-            if (mProfiles != null) {
-                return mProfiles;
-            } else {
-                return new int[]{userId};
-            }
-        }
-
-        @Override
-        public RootTaskInfo getFocusedStack() throws RemoteException {
-            RootTaskInfo focusedStack = new RootTaskInfo();
-            focusedStack.userId = 0;
-            focusedStack.topActivity = new ComponentName("a.package", "a.class");
-            return focusedStack;
-        }
-
-        @Override
-        public void scheduleIdleJob(Context context) {
-            // Don't actually schedule jobs during unit tests.
-            mIdleScheduled = true;
-        }
-
-        @Override
-        public void cancelIdleJob(Context context) {
-            mIdleScheduled = false;
-        }
-
-        @Override
-        public boolean isInteractive(Context context) {
-            return mInteractive;
-        }
-
-        @Override
-        public int getNightDisplayColorTemperature(Context context) {
-          return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
-                  mDefaultNightModeColorTemperature);
-        }
-
-        @Override
-        public boolean isNightDisplayActivated(Context context) {
-            return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_ACTIVATED,
-                    0) == 1;
-        }
-
-        @Override
-        public int getReduceBrightColorsStrength(Context context) {
-            return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL,
-                    0);
-        }
-
-        @Override
-        public boolean isReduceBrightColorsActivated(Context context) {
-            return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
-                    0) == 1;
-        }
-
-        @Override
-        public DisplayedContentSample sampleColor(int noFramesToSample) {
-            return new DisplayedContentSample(600L,
-                    null,
-                    null,
-                     new long[] {1, 10, 100, 1000, 300, 30, 10, 1},
-                    null);
-        }
-
-        @Override
-        public float getFrameRate(Context context) {
-            return mFrameRate;
-        }
-
-        @Override
-        public DisplayedContentSamplingAttributes getSamplingAttributes() {
-            return mDefaultSamplingAttributes;
-        }
-
-        @Override
-        public boolean enableColorSampling(boolean enable, int noFrames) {
-            mColorSamplingEnabled = enable;
-            mNoColorSamplingFrames = noFrames;
-            return true;
-        }
-
-        @Override
-        public void registerDisplayListener(Context context,
-                DisplayManager.DisplayListener listener, Handler handler) {
-            mDisplayListener = listener;
-        }
-
-        @Override
-        public void unRegisterDisplayListener(Context context,
-                DisplayManager.DisplayListener listener) {
-            mDisplayListener = null;
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
deleted file mode 100644
index 130e6ad..0000000
--- a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ /dev/null
@@ -1,252 +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.server.display;
-
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import android.view.Display;
-import android.view.DisplayAddress;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.server.display.layout.DisplayIdProducer;
-import com.android.server.display.layout.Layout;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-
-@SmallTest
-public class DeviceStateToLayoutMapTest {
-    private DeviceStateToLayoutMap mDeviceStateToLayoutMap;
-
-    @Mock DisplayIdProducer mDisplayIdProducerMock;
-
-    @Before
-    public void setUp() throws IOException {
-        MockitoAnnotations.initMocks(this);
-
-        Mockito.when(mDisplayIdProducerMock.getId(false)).thenReturn(1);
-
-        setupDeviceStateToLayoutMap();
-    }
-
-    //////////////////
-    // Test Methods //
-    //////////////////
-
-    @Test
-    public void testInitialState() {
-        Layout configLayout = mDeviceStateToLayoutMap.get(0);
-
-        Layout testLayout = new Layout();
-        createDefaultDisplay(testLayout, 123456L);
-        createNonDefaultDisplay(testLayout, 78910L, /* enabled= */ false, /* group= */ null);
-        createNonDefaultDisplay(testLayout, 98765L, /* enabled= */ true, /* group= */ "group1");
-        createNonDefaultDisplay(testLayout, 786L, /* enabled= */ false, /* group= */ "group2");
-
-        assertEquals(testLayout, configLayout);
-    }
-
-    @Test
-    public void testSwitchedState() {
-        Layout configLayout = mDeviceStateToLayoutMap.get(1);
-
-        Layout testLayout = new Layout();
-        createDefaultDisplay(testLayout, 78910L);
-        createNonDefaultDisplay(testLayout, 123456L, /* enabled= */ false, /* group= */ null);
-
-        assertEquals(testLayout, configLayout);
-    }
-
-    @Test
-    public void testThermalBrightnessThrottlingMapId() {
-        Layout configLayout = mDeviceStateToLayoutMap.get(2);
-
-        assertEquals("concurrent1", configLayout.getAt(0).getThermalBrightnessThrottlingMapId());
-        assertEquals("concurrent2", configLayout.getAt(1).getThermalBrightnessThrottlingMapId());
-    }
-
-    @Test
-    public void testRearDisplayLayout() {
-        Layout configLayout = mDeviceStateToLayoutMap.get(2);
-
-        assertEquals(Layout.Display.POSITION_FRONT, configLayout.getAt(0).getPosition());
-        assertEquals(Layout.Display.POSITION_REAR, configLayout.getAt(1).getPosition());
-    }
-
-    @Test
-    public void testRefreshRateZoneId() {
-        Layout configLayout = mDeviceStateToLayoutMap.get(3);
-
-        assertEquals("test1", configLayout.getAt(0).getRefreshRateZoneId());
-        assertNull(configLayout.getAt(1).getRefreshRateZoneId());
-    }
-
-    @Test
-    public void testThermalRefreshRateThrottlingMapId() {
-        Layout configLayout = mDeviceStateToLayoutMap.get(4);
-
-        assertEquals("test2", configLayout.getAt(0).getRefreshRateThermalThrottlingMapId());
-        assertNull(configLayout.getAt(1).getRefreshRateThermalThrottlingMapId());
-    }
-
-    @Test
-    public void testWholeStateConfig() {
-        Layout configLayout = mDeviceStateToLayoutMap.get(99);
-
-        Layout testLayout = new Layout();
-        testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(345L),
-                /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
-                mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT, Display.DEFAULT_DISPLAY,
-                /* brightnessThrottlingMapId= */ "brightness1",
-                /* refreshRateZoneId= */ "zone1",
-                /* refreshRateThermalThrottlingMapId= */ "rr1");
-        testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L),
-                /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1",
-                mDisplayIdProducerMock, Layout.Display.POSITION_REAR, Display.DEFAULT_DISPLAY,
-                /* brightnessThrottlingMapId= */ "brightness2",
-                /* refreshRateZoneId= */ "zone2",
-                /* refreshRateThermalThrottlingMapId= */ "rr2");
-
-        assertEquals(testLayout, configLayout);
-    }
-
-    ////////////////////
-    // Helper Methods //
-    ////////////////////
-
-    private void createDefaultDisplay(Layout layout, long id) {
-        layout.createDefaultDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id),
-                mDisplayIdProducerMock);
-    }
-
-    private void createNonDefaultDisplay(Layout layout, long id, boolean enabled, String group) {
-        layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id), /* isDefault= */ false,
-                enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
-                Display.DEFAULT_DISPLAY, /* brightnessThrottlingMapId= */ null,
-                /* refreshRateZoneId= */ null,
-                /* refreshRateThermalThrottlingMapId= */ null);
-    }
-
-    private void setupDeviceStateToLayoutMap() throws IOException {
-        Path tempFile = Files.createTempFile("device_state_layout_map", ".tmp");
-        Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
-        mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(mDisplayIdProducerMock,
-                tempFile.toFile());
-    }
-
-    private String getContent() {
-        return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                +  "<layouts>\n"
-                +    "<layout>\n"
-                +      "<state>0</state> \n"
-                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
-                +        "<address>123456</address>\n"
-                +      "</display>\n"
-                +      "<display enabled=\"false\">\n"
-                +        "<address>78910</address>\n"
-                +      "</display>\n"
-                +      "<display enabled=\"true\" displayGroup=\"group1\">\n"
-                +        "<address>98765</address>\n"
-                +      "</display>\n"
-                +      "<display enabled=\"false\" displayGroup=\"group2\">\n"
-                +        "<address>786</address>\n"
-                +      "</display>\n"
-                +    "</layout>\n"
-
-                +    "<layout>\n"
-                +      "<state>1</state> \n"
-                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
-                +        "<address>78910</address>\n"
-                +      "</display>\n"
-                +      "<display enabled=\"false\">\n"
-                +        "<address>123456</address>\n"
-                +      "</display>\n"
-                +    "</layout>\n"
-
-                +    "<layout>\n"
-                +      "<state>2</state> \n"
-                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
-                +        "<address>345</address>\n"
-                +        "<position>front</position>\n"
-                +        "<brightnessThrottlingMapId>concurrent1</brightnessThrottlingMapId>\n"
-                +      "</display>\n"
-                +      "<display enabled=\"true\">\n"
-                +        "<address>678</address>\n"
-                +        "<position>rear</position>\n"
-                +        "<brightnessThrottlingMapId>concurrent2</brightnessThrottlingMapId>\n"
-                +      "</display>\n"
-                +    "</layout>\n"
-
-                +    "<layout>\n"
-                +      "<state>3</state> \n"
-                +      "<display enabled=\"true\" defaultDisplay=\"true\" "
-                +                                               "refreshRateZoneId=\"test1\">\n"
-                +        "<address>345</address>\n"
-                +      "</display>\n"
-                +      "<display enabled=\"true\">\n"
-                +        "<address>678</address>\n"
-                +      "</display>\n"
-                +    "</layout>\n"
-
-                +    "<layout>\n"
-                +      "<state>4</state> \n"
-                +      "<display enabled=\"true\" defaultDisplay=\"true\" >\n"
-                +        "<address>345</address>\n"
-                +        "<refreshRateThermalThrottlingMapId>"
-                +          "test2"
-                +        "</refreshRateThermalThrottlingMapId>"
-                +      "</display>\n"
-                +      "<display enabled=\"true\">\n"
-                +        "<address>678</address>\n"
-                +      "</display>\n"
-                +    "</layout>\n"
-                +    "<layout>\n"
-                +      "<state>99</state> \n"
-                +      "<display enabled=\"true\" defaultDisplay=\"true\" "
-                +                                          "refreshRateZoneId=\"zone1\">\n"
-                +         "<address>345</address>\n"
-                +         "<position>front</position>\n"
-                +         "<brightnessThrottlingMapId>brightness1</brightnessThrottlingMapId>\n"
-                +         "<refreshRateThermalThrottlingMapId>"
-                +           "rr1"
-                +         "</refreshRateThermalThrottlingMapId>"
-                +       "</display>\n"
-                +       "<display enabled=\"false\" displayGroup=\"group1\" "
-                +                                           "refreshRateZoneId=\"zone2\">\n"
-                +         "<address>678</address>\n"
-                +         "<position>rear</position>\n"
-                +         "<brightnessThrottlingMapId>brightness2</brightnessThrottlingMapId>\n"
-                +         "<refreshRateThermalThrottlingMapId>"
-                +           "rr2"
-                +         "</refreshRateThermalThrottlingMapId>"
-                +       "</display>\n"
-                +     "</layout>\n"
-                +   "</layouts>\n";
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
deleted file mode 100644
index 708421d..0000000
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ /dev/null
@@ -1,953 +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.server.display;
-
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.os.Temperature;
-import android.util.SparseArray;
-import android.view.SurfaceControl;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.R;
-import com.android.server.display.config.ThermalStatus;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-@SmallTest
-@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 DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE = 55;
-    private static final int DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE = 95;
-    private static final int DEFAULT_REFRESH_RATE_IN_HBM_HDR = 90;
-    private static final int DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT = 100;
-    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;
-    @Mock
-    private Context mContext;
-
-    @Mock
-    private Resources mResources;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        when(mContext.getResources()).thenReturn(mResources);
-        mockDeviceConfigs();
-    }
-
-    @Test
-    public void testConfigValuesFromDisplayConfig() throws IOException {
-        setupDisplayDeviceConfigFromDisplayConfigFile();
-
-        assertEquals(mDisplayDeviceConfig.getName(), "Example Display");
-        assertEquals(mDisplayDeviceConfig.getAmbientHorizonLong(), 5000);
-        assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, ZERO_DELTA);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, ZERO_DELTA);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, ZERO_DELTA);
-        assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, ZERO_DELTA);
-        assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getBrightness(), new float[]{0.0f, 0.62f, 1.0f},
-                ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f},
-                ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getBacklight(), new float[]{0.0f, 0.62f, 1.0f},
-                ZERO_DELTA);
-        assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
-        assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
-        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
-                float[]{0.0f, 50.0f, 80.0f}, ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
-                float[]{45.32f, 75.43f}, ZERO_DELTA);
-
-        // Test thresholds
-        assertEquals(10, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(),
-                ZERO_DELTA);
-        assertEquals(20, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
-                ZERO_DELTA);
-        assertEquals(30, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
-        assertEquals(40, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
-
-        assertEquals(0.1f, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
-        assertEquals(0.2f, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
-        assertEquals(0.3f, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
-        assertEquals(0.4f, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
-
-        assertArrayEquals(new float[]{0, 0.10f, 0.20f},
-                mDisplayDeviceConfig.getScreenBrighteningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{9, 10, 11},
-                mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 0.11f, 0.21f},
-                mDisplayDeviceConfig.getScreenDarkeningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{11, 12, 13},
-                mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
-
-        assertArrayEquals(new float[]{0, 100, 200},
-                mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{13, 14, 15},
-                mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 300, 400},
-                mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{15, 16, 17},
-                mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
-
-        assertArrayEquals(new float[]{0, 0.12f, 0.22f},
-                mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{17, 18, 19},
-                mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 0.13f, 0.23f},
-                mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{19, 20, 21},
-                mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
-
-        assertArrayEquals(new float[]{0, 500, 600},
-                mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{21, 22, 23},
-                mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 700, 800},
-                mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{23, 24, 25},
-                mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
-
-        assertEquals(75, mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate());
-        assertEquals(90, mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate());
-        assertEquals(85, mDisplayDeviceConfig.getDefaultPeakRefreshRate());
-        assertEquals(45, mDisplayDeviceConfig.getDefaultRefreshRate());
-        assertEquals(2, mDisplayDeviceConfig.getRefreshRangeProfiles().size());
-        assertEquals(60, mDisplayDeviceConfig.getRefreshRange("test1").min, SMALL_DELTA);
-        assertEquals(60, mDisplayDeviceConfig.getRefreshRange("test1").max, SMALL_DELTA);
-        assertEquals(80, mDisplayDeviceConfig.getRefreshRange("test2").min, SMALL_DELTA);
-        assertEquals(90, mDisplayDeviceConfig.getRefreshRange("test2").max, SMALL_DELTA);
-        assertEquals(82, mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr());
-        assertEquals(83, mDisplayDeviceConfig.getDefaultRefreshRateInHbmSunlight());
-        assertArrayEquals(new int[]{45, 55},
-                mDisplayDeviceConfig.getLowDisplayBrightnessThresholds());
-        assertArrayEquals(new int[]{50, 60},
-                mDisplayDeviceConfig.getLowAmbientBrightnessThresholds());
-        assertArrayEquals(new int[]{65, 75},
-                mDisplayDeviceConfig.getHighDisplayBrightnessThresholds());
-        assertArrayEquals(new int[]{70, 80},
-                mDisplayDeviceConfig.getHighAmbientBrightnessThresholds());
-
-        assertEquals("sensor_12345",
-                mDisplayDeviceConfig.getScreenOffBrightnessSensor().type);
-        assertEquals("Sensor 12345",
-                mDisplayDeviceConfig.getScreenOffBrightnessSensor().name);
-
-        assertArrayEquals(new int[]{-1, 10, 20, 30, 40},
-                mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux());
-
-        List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel>
-                defaultThrottlingLevels = new ArrayList<>();
-        defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.4f
-        ));
-        defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.3f
-        ));
-        defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.2f
-        ));
-        defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.1f
-        ));
-        defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.05f
-        ));
-        defaultThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.025f
-        ));
-
-        DisplayDeviceConfig.ThermalBrightnessThrottlingData defaultThrottlingData =
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData(defaultThrottlingLevels);
-
-        List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel>
-                concurrentThrottlingLevels = new ArrayList<>();
-        concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.2f
-        ));
-        concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.15f
-        ));
-        concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.1f
-        ));
-        concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.05f
-        ));
-        concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.025f
-        ));
-        concurrentThrottlingLevels.add(
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.0125f
-        ));
-        DisplayDeviceConfig.ThermalBrightnessThrottlingData concurrentThrottlingData =
-                new DisplayDeviceConfig.ThermalBrightnessThrottlingData(concurrentThrottlingLevels);
-
-        HashMap<String, DisplayDeviceConfig.ThermalBrightnessThrottlingData> throttlingDataMap =
-                new HashMap<>(2);
-        throttlingDataMap.put("default", defaultThrottlingData);
-        throttlingDataMap.put("concurrent", concurrentThrottlingData);
-
-        assertEquals(throttlingDataMap,
-                mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
-
-        assertNotNull(mDisplayDeviceConfig.getHostUsiVersion());
-        assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2);
-        assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMinorVersion(), 0);
-
-        // Max desired Hdr/SDR ratio upper-bounds the HDR brightness.
-        assertEquals(1.0f,
-                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, Float.POSITIVE_INFINITY),
-                ZERO_DELTA);
-        assertEquals(0.62f,
-                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.0f),
-                ZERO_DELTA);
-        assertEquals(0.77787f,
-                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.25f),
-                SMALL_DELTA);
-
-        // Todo: Add asserts for DensityMapping,
-        // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
-    }
-
-    @Test
-    public void testConfigValuesFromConfigResource() {
-        setupDisplayDeviceConfigFromConfigResourceFile();
-        assertNull(mDisplayDeviceConfig.getName());
-        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
-                float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
-                float[]{0.0f, 0.0f, 110.0f, 500.0f}, ZERO_DELTA);
-
-        // Test thresholds
-        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
-                ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
-
-        assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
-
-        // screen levels will be considered "old screen brightness scale"
-        // and therefore will divide by 255
-        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
-                mDisplayDeviceConfig.getScreenBrighteningLevels(), SMALL_DELTA);
-        assertArrayEquals(new float[]{35, 36, 37},
-                mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
-                mDisplayDeviceConfig.getScreenDarkeningLevels(), SMALL_DELTA);
-        assertArrayEquals(new float[]{37, 38, 39},
-                mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
-
-        assertArrayEquals(new float[]{0, 30, 31},
-                mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{27, 28, 29},
-                mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 30, 31},
-                mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{29, 30, 31},
-                mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
-
-        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
-                mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), SMALL_DELTA);
-        assertArrayEquals(new float[]{35, 36, 37},
-                mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
-                mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), SMALL_DELTA);
-        assertArrayEquals(new float[]{37, 38, 39},
-                mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
-
-        assertArrayEquals(new float[]{0, 30, 31},
-                mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{27, 28, 29},
-                mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{0, 30, 31},
-                mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{29, 30, 31},
-                mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
-        assertEquals(mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(),
-                DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
-        assertEquals(mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(),
-                DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
-        assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
-        assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE);
-        assertEquals(0, mDisplayDeviceConfig.getRefreshRangeProfiles().size());
-        assertEquals(mDisplayDeviceConfig.getDefaultRefreshRateInHbmSunlight(),
-                DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT);
-        assertEquals(mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr(),
-                DEFAULT_REFRESH_RATE_IN_HBM_HDR);
-        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: Add asserts for ThermalBrightnessThrottlingData, DensityMapping,
-        // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
-    }
-
-    @Test
-    public void testThermalRefreshRateThrottlingFromDisplayConfig() throws IOException {
-        setupDisplayDeviceConfigFromDisplayConfigFile();
-
-        SparseArray<SurfaceControl.RefreshRateRange> defaultMap =
-                mDisplayDeviceConfig.getThermalRefreshRateThrottlingData(null);
-        assertNotNull(defaultMap);
-        assertEquals(2, defaultMap.size());
-        assertEquals(30, defaultMap.get(Temperature.THROTTLING_CRITICAL).min, SMALL_DELTA);
-        assertEquals(60, defaultMap.get(Temperature.THROTTLING_CRITICAL).max, SMALL_DELTA);
-        assertEquals(0, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).min, SMALL_DELTA);
-        assertEquals(30, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).max, SMALL_DELTA);
-
-        SparseArray<SurfaceControl.RefreshRateRange> testMap =
-                mDisplayDeviceConfig.getThermalRefreshRateThrottlingData("test");
-        assertNotNull(testMap);
-        assertEquals(1, testMap.size());
-        assertEquals(60, testMap.get(Temperature.THROTTLING_EMERGENCY).min, SMALL_DELTA);
-        assertEquals(90, testMap.get(Temperature.THROTTLING_EMERGENCY).max, SMALL_DELTA);
-    }
-
-    @Test
-    public void testValidLuxThrottling() throws Exception {
-        setupDisplayDeviceConfigFromDisplayConfigFile();
-
-        Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
-                mDisplayDeviceConfig.getLuxThrottlingData();
-        assertEquals(2, luxThrottlingData.size());
-
-        Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
-                DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
-        assertEquals(2, adaptiveOnBrightnessPoints.size());
-        assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
-        assertEquals(0.5f, adaptiveOnBrightnessPoints.get(5000f), SMALL_DELTA);
-
-        Map<Float, Float> adaptiveOffBrightnessPoints = luxThrottlingData.get(
-                DisplayDeviceConfig.BrightnessLimitMapType.DEFAULT);
-        assertEquals(2, adaptiveOffBrightnessPoints.size());
-        assertEquals(0.35f, adaptiveOffBrightnessPoints.get(1500f), SMALL_DELTA);
-        assertEquals(0.55f, adaptiveOffBrightnessPoints.get(5500f), SMALL_DELTA);
-    }
-
-    @Test
-    public void testInvalidLuxThrottling() throws Exception {
-        setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getInvalidLuxThrottling()));
-
-        Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
-                mDisplayDeviceConfig.getLuxThrottlingData();
-        assertEquals(1, luxThrottlingData.size());
-
-        Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
-                DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
-        assertEquals(1, adaptiveOnBrightnessPoints.size());
-        assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
-    }
-
-    private String getValidLuxThrottling() {
-        return "<luxThrottling>\n"
-                + "    <brightnessLimitMap>\n"
-                + "        <type>adaptive</type>\n"
-                + "        <map>\n"
-                + "            <point>"
-                + "                <first>1000</first>\n"
-                + "                <second>0.3</second>\n"
-                + "            </point>"
-                + "            <point>"
-                + "                <first>5000</first>\n"
-                + "                <second>0.5</second>\n"
-                + "            </point>"
-                + "        </map>\n"
-                + "    </brightnessLimitMap>\n"
-                + "    <brightnessLimitMap>\n"
-                + "        <type>default</type>\n"
-                + "        <map>\n"
-                + "            <point>"
-                + "                <first>1500</first>\n"
-                + "                <second>0.35</second>\n"
-                + "            </point>"
-                + "            <point>"
-                + "                <first>5500</first>\n"
-                + "                <second>0.55</second>\n"
-                + "            </point>"
-                + "        </map>\n"
-                + "    </brightnessLimitMap>\n"
-                + "</luxThrottling>";
-    }
-
-    private String getInvalidLuxThrottling() {
-        return "<luxThrottling>\n"
-                + "    <brightnessLimitMap>\n"
-                + "        <type>adaptive</type>\n"
-                + "        <map>\n"
-                + "            <point>"
-                + "                <first>1000</first>\n"
-                + "                <second>0.3</second>\n"
-                + "            </point>"
-                + "            <point>" // second > hbm.transitionPoint, skipped
-                + "                <first>1500</first>\n"
-                + "                <second>0.9</second>\n"
-                + "            </point>"
-                + "            <point>" // same lux value, skipped
-                + "                <first>1000</first>\n"
-                + "                <second>0.5</second>\n"
-                + "            </point>"
-                + "        </map>\n"
-                + "    </brightnessLimitMap>\n"
-                + "    <brightnessLimitMap>\n" // Same type, skipped
-                + "        <type>adaptive</type>\n"
-                + "        <map>\n"
-                + "            <point>"
-                + "                <first>2000</first>\n"
-                + "                <second>0.35</second>\n"
-                + "            </point>"
-                + "            <point>"
-                + "                <first>6000</first>\n"
-                + "                <second>0.55</second>\n"
-                + "            </point>"
-                + "        </map>\n"
-                + "    </brightnessLimitMap>\n"
-                + "    <brightnessLimitMap>\n" // Invalid points only, skipped
-                + "        <type>default</type>\n"
-                + "        <map>\n"
-                + "            <point>"
-                + "                <first>2500</first>\n"
-                + "                <second>0.99</second>\n"
-                + "            </point>"
-                + "        </map>\n"
-                + "    </brightnessLimitMap>\n"
-                + "</luxThrottling>";
-    }
-
-    private String getRefreshThermalThrottlingMaps() {
-        return "<refreshRateThrottlingMap>\n"
-               + "    <refreshRateThrottlingPoint>\n"
-               + "        <thermalStatus>critical</thermalStatus>\n"
-               + "        <refreshRateRange>\n"
-               + "            <minimum>30</minimum>\n"
-               + "            <maximum>60</maximum>\n"
-               + "        </refreshRateRange>\n"
-               + "    </refreshRateThrottlingPoint>\n"
-               + "    <refreshRateThrottlingPoint>\n"
-               + "        <thermalStatus>shutdown</thermalStatus>\n"
-               + "        <refreshRateRange>\n"
-               + "            <minimum>0</minimum>\n"
-               + "            <maximum>30</maximum>\n"
-               + "        </refreshRateRange>\n"
-               + "    </refreshRateThrottlingPoint>\n"
-               + "</refreshRateThrottlingMap>\n"
-               + "<refreshRateThrottlingMap id=\"test\">\n"
-               + "    <refreshRateThrottlingPoint>\n"
-               + "        <thermalStatus>emergency</thermalStatus>\n"
-               + "        <refreshRateRange>\n"
-               + "            <minimum>60</minimum>\n"
-               + "            <maximum>90</maximum>\n"
-               + "        </refreshRateRange>\n"
-               + "    </refreshRateThrottlingPoint>\n"
-               + "</refreshRateThrottlingMap>\n";
-    }
-
-    private String getContent() {
-        return getContent(getValidLuxThrottling());
-    }
-
-    private String getContent(String brightnessCapConfig) {
-        return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<displayConfiguration>\n"
-                +   "<name>Example Display</name>"
-                +   "<screenBrightnessMap>\n"
-                +       "<point>\n"
-                +           "<value>0.0</value>\n"
-                +           "<nits>2.0</nits>\n"
-                +       "</point>\n"
-                +       "<point>\n"
-                +           "<value>0.62</value>\n"
-                +           "<nits>500.0</nits>\n"
-                +       "</point>\n"
-                +       "<point>\n"
-                +           "<value>1.0</value>\n"
-                +           "<nits>800.0</nits>\n"
-                +       "</point>\n"
-                +   "</screenBrightnessMap>\n"
-                +   "<autoBrightness>\n"
-                +       "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n"
-                +       "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n"
-                +       "<displayBrightnessMapping>\n"
-                +            "<displayBrightnessPoint>\n"
-                +                "<lux>50</lux>\n"
-                +                "<nits>45.32</nits>\n"
-                +            "</displayBrightnessPoint>\n"
-                +            "<displayBrightnessPoint>\n"
-                +                "<lux>80</lux>\n"
-                +                "<nits>75.43</nits>\n"
-                +            "</displayBrightnessPoint>\n"
-                +       "</displayBrightnessMapping>\n"
-                +   "</autoBrightness>\n"
-                +   "<highBrightnessMode enabled=\"true\">\n"
-                +       "<transitionPoint>0.62</transitionPoint>\n"
-                +       "<minimumLux>10000</minimumLux>\n"
-                +       "<timing>\n"
-                +           "<!-- allow for 5 minutes out of every 30 minutes -->\n"
-                +           "<timeWindowSecs>1800</timeWindowSecs>\n"
-                +           "<timeMaxSecs>300</timeMaxSecs>\n"
-                +           "<timeMinSecs>60</timeMinSecs>\n"
-                +       "</timing>\n"
-                +       "<refreshRate>\n"
-                +           "<minimum>120</minimum>\n"
-                +           "<maximum>120</maximum>\n"
-                +       "</refreshRate>\n"
-                +       "<thermalStatusLimit>light</thermalStatusLimit>\n"
-                +       "<allowInLowPowerMode>false</allowInLowPowerMode>\n"
-                +       "<sdrHdrRatioMap>\n"
-                +            "<point>\n"
-                +                "<sdrNits>2.000</sdrNits>\n"
-                +                "<hdrRatio>4.000</hdrRatio>\n"
-                +            "</point>\n"
-                +            "<point>\n"
-                +                "<sdrNits>500.0</sdrNits>\n"
-                +                "<hdrRatio>1.6</hdrRatio>\n"
-                +            "</point>\n"
-                +       "</sdrHdrRatioMap>\n"
-                +   "</highBrightnessMode>\n"
-                + brightnessCapConfig
-                +   "<screenOffBrightnessSensor>\n"
-                +       "<type>sensor_12345</type>\n"
-                +       "<name>Sensor 12345</name>\n"
-                +   "</screenOffBrightnessSensor>\n"
-                +   "<ambientBrightnessChangeThresholds>\n"
-                +       "<brighteningThresholds>\n"
-                +           "<minimum>10</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold><percentage>13</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>100</threshold><percentage>14</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>200</threshold><percentage>15</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</brighteningThresholds>\n"
-                +       "<darkeningThresholds>\n"
-                +           "<minimum>30</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold><percentage>15</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>300</threshold><percentage>16</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>400</threshold><percentage>17</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</darkeningThresholds>\n"
-                +   "</ambientBrightnessChangeThresholds>\n"
-                +   "<displayBrightnessChangeThresholds>\n"
-                +       "<brighteningThresholds>\n"
-                +           "<minimum>0.1</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold>\n"
-                +                   "<percentage>9</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.10</threshold>\n"
-                +                   "<percentage>10</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.20</threshold>\n"
-                +                   "<percentage>11</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</brighteningThresholds>\n"
-                +       "<darkeningThresholds>\n"
-                +           "<minimum>0.3</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold><percentage>11</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.11</threshold><percentage>12</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.21</threshold><percentage>13</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</darkeningThresholds>\n"
-                +   "</displayBrightnessChangeThresholds>\n"
-                +   "<ambientBrightnessChangeThresholdsIdle>\n"
-                +       "<brighteningThresholds>\n"
-                +           "<minimum>20</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold><percentage>21</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>500</threshold><percentage>22</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>600</threshold><percentage>23</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</brighteningThresholds>\n"
-                +       "<darkeningThresholds>\n"
-                +           "<minimum>40</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold><percentage>23</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>700</threshold><percentage>24</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>800</threshold><percentage>25</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</darkeningThresholds>\n"
-                +   "</ambientBrightnessChangeThresholdsIdle>\n"
-                +   "<displayBrightnessChangeThresholdsIdle>\n"
-                +       "<brighteningThresholds>\n"
-                +           "<minimum>0.2</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold><percentage>17</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.12</threshold><percentage>18</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.22</threshold><percentage>19</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</brighteningThresholds>\n"
-                +       "<darkeningThresholds>\n"
-                +           "<minimum>0.4</minimum>\n"
-                +           "<brightnessThresholdPoints>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0</threshold><percentage>19</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.13</threshold><percentage>20</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +               "<brightnessThresholdPoint>\n"
-                +                   "<threshold>0.23</threshold><percentage>21</percentage>\n"
-                +               "</brightnessThresholdPoint>\n"
-                +           "</brightnessThresholdPoints>\n"
-                +       "</darkeningThresholds>\n"
-                +   "</displayBrightnessChangeThresholdsIdle>\n"
-                +   "<screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease> "
-                +   "<screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease>  "
-                +   "<screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease>"
-                +   "<screenBrightnessRampSlowIncrease>0.04</screenBrightnessRampSlowIncrease>"
-                +   "<screenBrightnessRampIncreaseMaxMillis>"
-                +       "2000"
-                +   "</screenBrightnessRampIncreaseMaxMillis>"
-                +   "<screenBrightnessRampDecreaseMaxMillis>"
-                +       "3000"
-                +   "</screenBrightnessRampDecreaseMaxMillis>"
-                +   "<ambientLightHorizonLong>5000</ambientLightHorizonLong>\n"
-                +   "<ambientLightHorizonShort>50</ambientLightHorizonShort>\n"
-                +   "<screenBrightnessRampIncreaseMaxMillis>"
-                +       "2000"
-                +   "</screenBrightnessRampIncreaseMaxMillis>\n"
-                +   "<thermalThrottling>\n"
-                +       "<brightnessThrottlingMap>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>light</thermalStatus>\n"
-                +               "<brightness>0.4</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>moderate</thermalStatus>\n"
-                +               "<brightness>0.3</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>severe</thermalStatus>\n"
-                +               "<brightness>0.2</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>critical</thermalStatus>\n"
-                +               "<brightness>0.1</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>emergency</thermalStatus>\n"
-                +               "<brightness>0.05</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>shutdown</thermalStatus>\n"
-                +               "<brightness>0.025</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +       "</brightnessThrottlingMap>\n"
-                +       "<brightnessThrottlingMap id=\"concurrent\">\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>light</thermalStatus>\n"
-                +               "<brightness>0.2</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>moderate</thermalStatus>\n"
-                +               "<brightness>0.15</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>severe</thermalStatus>\n"
-                +               "<brightness>0.1</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>critical</thermalStatus>\n"
-                +               "<brightness>0.05</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>emergency</thermalStatus>\n"
-                +               "<brightness>0.025</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +           "<brightnessThrottlingPoint>\n"
-                +               "<thermalStatus>shutdown</thermalStatus>\n"
-                +               "<brightness>0.0125</brightness>\n"
-                +           "</brightnessThrottlingPoint>\n"
-                +       "</brightnessThrottlingMap>\n"
-                +  getRefreshThermalThrottlingMaps()
-                +   "</thermalThrottling>\n"
-                +   "<refreshRate>\n"
-                +       "<defaultRefreshRate>45</defaultRefreshRate>\n"
-                +       "<defaultPeakRefreshRate>85</defaultPeakRefreshRate>\n"
-                +       "<refreshRateZoneProfiles>"
-                +           "<refreshRateZoneProfile id=\"test1\">"
-                +               "<refreshRateRange>\n"
-                +                   "<minimum>60</minimum>\n"
-                +                   "<maximum>60</maximum>\n"
-                +               "</refreshRateRange>\n"
-                +           "</refreshRateZoneProfile>\n"
-                +           "<refreshRateZoneProfile id=\"test2\">"
-                +               "<refreshRateRange>\n"
-                +                   "<minimum>80</minimum>\n"
-                +                   "<maximum>90</maximum>\n"
-                +               "</refreshRateRange>\n"
-                +           "</refreshRateZoneProfile>\n"
-                +       "</refreshRateZoneProfiles>"
-                +       "<defaultRefreshRateInHbmHdr>82</defaultRefreshRateInHbmHdr>\n"
-                +       "<defaultRefreshRateInHbmSunlight>83</defaultRefreshRateInHbmSunlight>\n"
-                +       "<lowerBlockingZoneConfigs>\n"
-                +           "<defaultRefreshRate>75</defaultRefreshRate>\n"
-                +           "<blockingZoneThreshold>\n"
-                +               "<displayBrightnessPoint>\n"
-                +                   "<lux>50</lux>\n"
-                // This number will be rounded to integer when read by the system
-                +                   "<nits>45.3</nits>\n"
-                +               "</displayBrightnessPoint>\n"
-                +               "<displayBrightnessPoint>\n"
-                +                   "<lux>60</lux>\n"
-                // This number will be rounded to integer when read by the system
-                +                   "<nits>55.2</nits>\n"
-                +               "</displayBrightnessPoint>\n"
-                +           "</blockingZoneThreshold>\n"
-                +       "</lowerBlockingZoneConfigs>\n"
-                +       "<higherBlockingZoneConfigs>\n"
-                +           "<defaultRefreshRate>90</defaultRefreshRate>\n"
-                +           "<blockingZoneThreshold>\n"
-                +               "<displayBrightnessPoint>\n"
-                +                   "<lux>70</lux>\n"
-                // This number will be rounded to integer when read by the system
-                +                   "<nits>65.6</nits>\n"
-                +               "</displayBrightnessPoint>\n"
-                +               "<displayBrightnessPoint>\n"
-                +                   "<lux>80</lux>\n"
-                // This number will be rounded to integer when read by the system
-                +                   "<nits>75</nits>\n"
-                +               "</displayBrightnessPoint>\n"
-                +           "</blockingZoneThreshold>\n"
-                +       "</higherBlockingZoneConfigs>\n"
-                +   "</refreshRate>\n"
-                +   "<screenOffBrightnessSensorValueToLux>\n"
-                +       "<item>-1</item>\n"
-                +       "<item>10</item>\n"
-                +       "<item>20</item>\n"
-                +       "<item>30</item>\n"
-                +       "<item>40</item>\n"
-                +   "</screenOffBrightnessSensorValueToLux>\n"
-                +   "<usiVersion>\n"
-                +       "<majorVersion>2</majorVersion>\n"
-                +       "<minorVersion>0</minorVersion>\n"
-                +   "</usiVersion>\n"
-                + "</displayConfiguration>\n";
-    }
-
-    private void mockDeviceConfigs() {
-        when(mResources.getFloat(com.android.internal.R.dimen
-                .config_screenBrightnessSettingDefaultFloat)).thenReturn(0.5f);
-        when(mResources.getFloat(com.android.internal.R.dimen
-                .config_screenBrightnessSettingMaximumFloat)).thenReturn(1.0f);
-    }
-
-    private void setupDisplayDeviceConfigFromDisplayConfigFile() throws IOException {
-        setupDisplayDeviceConfigFromDisplayConfigFile(getContent());
-    }
-
-    private void setupDisplayDeviceConfigFromDisplayConfigFile(String content) throws IOException {
-        Path tempFile = Files.createTempFile("display_config", ".tmp");
-        Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8));
-        mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
-        mDisplayDeviceConfig.initFromFile(tempFile.toFile());
-    }
-
-    private void setupDisplayDeviceConfigFromConfigResourceFile() {
-        TypedArray screenBrightnessNits = createFloatTypedArray(new float[]{2.0f, 250.0f, 650.0f});
-        when(mResources.obtainTypedArray(
-                com.android.internal.R.array.config_screenBrightnessNits))
-                .thenReturn(screenBrightnessNits);
-        TypedArray screenBrightnessBacklight = createFloatTypedArray(new
-                float[]{0.0f, 120.0f, 255.0f});
-        when(mResources.obtainTypedArray(
-                com.android.internal.R.array.config_screenBrightnessBacklight))
-                .thenReturn(screenBrightnessBacklight);
-        when(mResources.getIntArray(com.android.internal.R.array
-                .config_screenBrightnessBacklight)).thenReturn(new int[]{0, 120, 255});
-
-        when(mResources.getIntArray(com.android.internal.R.array
-                .config_autoBrightnessLevels)).thenReturn(new int[]{30, 80});
-        when(mResources.getIntArray(com.android.internal.R.array
-                .config_autoBrightnessDisplayValuesNits)).thenReturn(new int[]{25, 55});
-
-        TypedArray screenBrightnessLevelNits = createFloatTypedArray(new
-                float[]{2.0f, 200.0f, 600.0f});
-        when(mResources.obtainTypedArray(
-                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
-                .thenReturn(screenBrightnessLevelNits);
-        int[] screenBrightnessLevelLux = new int[]{0, 110, 500};
-        when(mResources.getIntArray(
-                com.android.internal.R.array.config_autoBrightnessLevels))
-                .thenReturn(screenBrightnessLevelLux);
-
-        // Thresholds
-        // Config.xml requires the levels arrays to be of length N and the thresholds arrays to be
-        // of length N+1
-        when(mResources.getIntArray(com.android.internal.R.array.config_ambientThresholdLevels))
-                .thenReturn(new int[]{30, 31});
-        when(mResources.getIntArray(com.android.internal.R.array.config_screenThresholdLevels))
-                .thenReturn(new int[]{42, 43});
-        when(mResources.getIntArray(
-                com.android.internal.R.array.config_ambientBrighteningThresholds))
-                .thenReturn(new int[]{270, 280, 290});
-        when(mResources.getIntArray(com.android.internal.R.array.config_ambientDarkeningThresholds))
-                .thenReturn(new int[]{290, 300, 310});
-        when(mResources.getIntArray(R.array.config_screenBrighteningThresholds))
-                .thenReturn(new int[]{350, 360, 370});
-        when(mResources.getIntArray(R.array.config_screenDarkeningThresholds))
-                .thenReturn(new int[]{370, 380, 390});
-
-        // Configs related to refresh rates and blocking zones
-        when(mResources.getInteger(R.integer.config_defaultPeakRefreshRate))
-                .thenReturn(DEFAULT_PEAK_REFRESH_RATE);
-        when(mResources.getInteger(R.integer.config_defaultRefreshRate))
-                .thenReturn(DEFAULT_REFRESH_RATE);
-        when(mResources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
-            .thenReturn(DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
-        when(mResources.getInteger(R.integer.config_defaultRefreshRateInZone))
-            .thenReturn(DEFAULT_LOW_BLOCKING_ZONE_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);
-        when(mResources.getInteger(
-            R.integer.config_defaultRefreshRateInHbmHdr))
-            .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_HDR);
-        when(mResources.getInteger(
-            R.integer.config_defaultRefreshRateInHbmSunlight))
-            .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT);
-
-        mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
-    }
-
-    private TypedArray createFloatTypedArray(float[] vals) {
-        TypedArray mockArray = mock(TypedArray.class);
-        when(mockArray.length()).thenAnswer(invocation -> {
-            return vals.length;
-        });
-        when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
-            final float def = (float) invocation.getArguments()[1];
-            if (vals == null) {
-                return def;
-            }
-            int idx = (int) invocation.getArguments()[0];
-            if (idx >= 0 && idx < vals.length) {
-                return vals[idx];
-            } else {
-                return def;
-            }
-        });
-        return mockArray;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
deleted file mode 100644
index 0e775d5..0000000
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ /dev/null
@@ -1,2242 +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.display;
-
-import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
-import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
-import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
-import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
-
-import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doReturn;
-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.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.PropertyInvalidatedCache;
-import android.companion.virtual.IVirtualDevice;
-import android.companion.virtual.IVirtualDeviceManager;
-import android.companion.virtual.VirtualDeviceManager;
-import android.compat.testing.PlatformCompatChangeRule;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Insets;
-import android.graphics.Rect;
-import android.hardware.display.BrightnessConfiguration;
-import android.hardware.display.Curve;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManagerGlobal;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayViewport;
-import android.hardware.display.DisplayedContentSample;
-import android.hardware.display.DisplayedContentSamplingAttributes;
-import android.hardware.display.HdrConversionMode;
-import android.hardware.display.IDisplayManagerCallback;
-import android.hardware.display.IVirtualDisplayCallback;
-import android.hardware.display.VirtualDisplayConfig;
-import android.media.projection.IMediaProjection;
-import android.media.projection.IMediaProjectionManager;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.MessageQueue;
-import android.os.Process;
-import android.os.RemoteException;
-import android.view.ContentRecordingSession;
-import android.view.Display;
-import android.view.DisplayCutout;
-import android.view.DisplayEventReceiver;
-import android.view.DisplayInfo;
-import android.view.Surface;
-import android.view.SurfaceControl;
-import android.window.DisplayWindowPolicyController;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.R;
-import com.android.server.LocalServices;
-import com.android.server.SystemService;
-import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
-import com.android.server.display.DisplayManagerService.DeviceStateListener;
-import com.android.server.display.DisplayManagerService.SyncRoot;
-import com.android.server.input.InputManagerInternal;
-import com.android.server.lights.LightsManager;
-import com.android.server.sensors.SensorManagerInternal;
-import com.android.server.wm.WindowManagerInternal;
-
-import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
-import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
-import com.google.common.collect.ImmutableMap;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.time.Duration;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.LongStream;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class DisplayManagerServiceTest {
-    private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
-    private static final long SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS = 10;
-    private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display";
-    private static final String PACKAGE_NAME = "com.android.frameworks.servicestests";
-    private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
-
-    @Rule
-    public TestRule compatChangeRule = new PlatformCompatChangeRule();
-
-    private Context mContext;
-
-    private int mHdrConversionMode;
-
-    private int mPreferredHdrOutputType;
-
-    private final DisplayManagerService.Injector mShortMockedInjector =
-            new DisplayManagerService.Injector() {
-                @Override
-                VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot,
-                        Context context, Handler handler, DisplayAdapter.Listener listener) {
-                    return mMockVirtualDisplayAdapter;
-                }
-
-                @Override
-                LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
-                        Handler handler, DisplayAdapter.Listener displayAdapterListener) {
-                    return new LocalDisplayAdapter(syncRoot, context, handler,
-                            displayAdapterListener, new LocalDisplayAdapter.Injector() {
-                        @Override
-                        public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
-                            return mSurfaceControlProxy;
-                        }
-                    });
-                }
-
-                @Override
-                long getDefaultDisplayDelayTimeout() {
-                    return SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS;
-                }
-            };
-
-    class BasicInjector extends DisplayManagerService.Injector {
-        @Override
-        IMediaProjectionManager getProjectionService() {
-            return mMockProjectionService;
-        }
-
-        @Override
-        VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
-                Handler handler, DisplayAdapter.Listener displayAdapterListener) {
-            return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
-                    new VirtualDisplayAdapter.SurfaceControlDisplayFactory() {
-                        @Override
-                        public IBinder createDisplay(String name, boolean secure,
-                                float requestedRefreshRate) {
-                            return mMockDisplayToken;
-                        }
-
-                        @Override
-                        public void destroyDisplay(IBinder displayToken) {
-                        }
-                    });
-        }
-
-        @Override
-        LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
-                Handler handler, DisplayAdapter.Listener displayAdapterListener) {
-            return new LocalDisplayAdapter(syncRoot, context, handler,
-                    displayAdapterListener, new LocalDisplayAdapter.Injector() {
-                        @Override
-                        public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
-                            return mSurfaceControlProxy;
-                        }
-                    });
-        }
-
-        @Override
-        int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
-                int[] autoHdrTypes) {
-            mHdrConversionMode = conversionMode;
-            mPreferredHdrOutputType = preferredHdrOutputType;
-            return Display.HdrCapabilities.HDR_TYPE_INVALID;
-        }
-
-        @Override
-        int[] getSupportedHdrOutputTypes() {
-            return new int[]{};
-        }
-
-        @Override
-        int[] getHdrOutputTypesWithLatency() {
-            return new int[]{Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION};
-        }
-
-        boolean getHdrOutputConversionSupport() {
-            return true;
-        }
-   }
-
-    private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
-
-    @Mock IMediaProjectionManager mMockProjectionService;
-    @Mock IVirtualDeviceManager mIVirtualDeviceManager;
-    @Mock InputManagerInternal mMockInputManagerInternal;
-    @Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
-    @Mock IVirtualDisplayCallback.Stub mMockAppToken;
-    @Mock IVirtualDisplayCallback.Stub mMockAppToken2;
-    @Mock WindowManagerInternal mMockWindowManagerInternal;
-    @Mock LightsManager mMockLightsManager;
-    @Mock VirtualDisplayAdapter mMockVirtualDisplayAdapter;
-    @Mock LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy;
-    @Mock IBinder mMockDisplayToken;
-    @Mock SensorManagerInternal mMockSensorManagerInternal;
-
-    @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        LocalServices.removeServiceForTest(InputManagerInternal.class);
-        LocalServices.addService(InputManagerInternal.class, mMockInputManagerInternal);
-        LocalServices.removeServiceForTest(WindowManagerInternal.class);
-        LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerInternal);
-        LocalServices.removeServiceForTest(LightsManager.class);
-        LocalServices.addService(LightsManager.class, mMockLightsManager);
-        LocalServices.removeServiceForTest(SensorManagerInternal.class);
-        LocalServices.addService(SensorManagerInternal.class, mMockSensorManagerInternal);
-        LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
-        LocalServices.addService(
-                VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);
-
-        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
-
-        VirtualDeviceManager vdm = new VirtualDeviceManager(mIVirtualDeviceManager, mContext);
-        when(mContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
-        // Disable binder caches in this process.
-        PropertyInvalidatedCache.disableForTestMode();
-        setUpDisplay();
-    }
-
-    private void setUpDisplay() {
-        long[] ids = new long[] {100};
-        when(mSurfaceControlProxy.getPhysicalDisplayIds()).thenReturn(ids);
-        when(mSurfaceControlProxy.getPhysicalDisplayToken(anyLong()))
-                .thenReturn(mMockDisplayToken);
-        SurfaceControl.StaticDisplayInfo staticDisplayInfo = new SurfaceControl.StaticDisplayInfo();
-        staticDisplayInfo.isInternal = true;
-        when(mSurfaceControlProxy.getStaticDisplayInfo(anyLong()))
-                .thenReturn(staticDisplayInfo);
-        SurfaceControl.DynamicDisplayInfo dynamicDisplayMode =
-                new SurfaceControl.DynamicDisplayInfo();
-        SurfaceControl.DisplayMode displayMode = new SurfaceControl.DisplayMode();
-        displayMode.width = 100;
-        displayMode.height = 200;
-        displayMode.supportedHdrTypes = new int[]{1, 2};
-        dynamicDisplayMode.supportedDisplayModes = new SurfaceControl.DisplayMode[] {displayMode};
-        when(mSurfaceControlProxy.getDynamicDisplayInfo(anyLong()))
-                .thenReturn(dynamicDisplayMode);
-        when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(mMockDisplayToken))
-                .thenReturn(new SurfaceControl.DesiredDisplayModeSpecs());
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_sentToInputManager() throws RemoteException {
-        // This is to update the display device config such that DisplayManagerService can ignore
-        // the usage of SensorManager, which is available only after the PowerManagerService
-        // is ready.
-        resetConfigToIgnoreSensorManager(mContext);
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.systemReady(false /* safeMode */);
-        displayManager.windowManagerAndInputReady();
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-
-        String uniqueId = "uniqueId --- Test";
-        String uniqueIdPrefix = UNIQUE_ID_PREFIX + mContext.getPackageName() + ":";
-        int width = 600;
-        int height = 800;
-        int dpi = 320;
-        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi);
-        builder.setUniqueId(uniqueId);
-        builder.setFlags(flags);
-        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
-                null /* projection */, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-
-        ArgumentCaptor<List<DisplayViewport>> viewportCaptor = ArgumentCaptor.forClass(List.class);
-        verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
-        List<DisplayViewport> viewports = viewportCaptor.getValue();
-
-        // Expect to receive at least 2 viewports: at least 1 internal, and 1 virtual
-        assertTrue(viewports.size() >= 2);
-
-        DisplayViewport virtualViewport = null;
-        DisplayViewport internalViewport = null;
-        for (int i = 0; i < viewports.size(); i++) {
-            DisplayViewport v = viewports.get(i);
-            switch (v.type) {
-                case DisplayViewport.VIEWPORT_INTERNAL: {
-                    // If more than one internal viewport, this will get overwritten several times,
-                    // which for the purposes of this test is fine.
-                    internalViewport = v;
-                    assertTrue(internalViewport.valid);
-                    break;
-                }
-                case DisplayViewport.VIEWPORT_EXTERNAL: {
-                    // External view port is present for auto devices in the form of instrument
-                    // cluster.
-                    break;
-                }
-                case DisplayViewport.VIEWPORT_VIRTUAL: {
-                    virtualViewport = v;
-                    break;
-                }
-            }
-        }
-        // INTERNAL viewport gets created upon access.
-        assertNotNull(internalViewport);
-        assertNotNull(virtualViewport);
-
-        // VIRTUAL
-        assertEquals(height, virtualViewport.deviceHeight);
-        assertEquals(width, virtualViewport.deviceWidth);
-        assertEquals(uniqueIdPrefix + uniqueId, virtualViewport.uniqueId);
-        assertEquals(displayId, virtualViewport.displayId);
-    }
-
-    @Test
-    public void testPhysicalViewports() {
-        // This is to update the display device config such that DisplayManagerService can ignore
-        // the usage of SensorManager, which is available only after the PowerManagerService
-        // is ready.
-        resetConfigToIgnoreSensorManager(mContext);
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.systemReady(false /* safeMode */);
-        displayManager.windowManagerAndInputReady();
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        final int[] displayIds = bs.getDisplayIds(/* includeDisabled= */ true);
-        final int size = displayIds.length;
-        assertTrue(size > 0);
-
-        Map<Integer, Integer> expectedDisplayTypeToViewPortTypeMapping = ImmutableMap.of(
-                Display.TYPE_INTERNAL, DisplayViewport.VIEWPORT_INTERNAL,
-                Display.TYPE_EXTERNAL, DisplayViewport.VIEWPORT_EXTERNAL
-        );
-        for (int i = 0; i < size; i++) {
-            DisplayInfo info = bs.getDisplayInfo(displayIds[i]);
-            assertTrue(expectedDisplayTypeToViewPortTypeMapping.keySet().contains(info.type));
-        }
-
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-
-        ArgumentCaptor<List<DisplayViewport>> viewportCaptor = ArgumentCaptor.forClass(List.class);
-        verify(mMockInputManagerInternal).setDisplayViewports(viewportCaptor.capture());
-        List<DisplayViewport> viewports = viewportCaptor.getValue();
-
-        // Due to the nature of foldables, we may have a different number of viewports than
-        // displays, just verify there's at least one.
-        final int viewportSize = viewports.size();
-        assertTrue(viewportSize > 0);
-
-        // Now verify that each viewport's displayId is valid.
-        Arrays.sort(displayIds);
-        for (int i = 0; i < viewportSize; i++) {
-            DisplayViewport viewport = viewports.get(i);
-            assertNotNull(viewport);
-            DisplayInfo displayInfo = bs.getDisplayInfo(viewport.displayId);
-            assertTrue(expectedDisplayTypeToViewPortTypeMapping.get(displayInfo.type)
-                    == viewport.type);
-            assertTrue(viewport.valid);
-            assertTrue(Arrays.binarySearch(displayIds, viewport.displayId) >= 0);
-        }
-    }
-
-    @Test
-    public void testCreateVirtualDisplayRotatesWithContent() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-
-        String uniqueId = "uniqueId --- Rotates With Content Test";
-        int width = 600;
-        int height = 800;
-        int dpi = 320;
-        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi);
-        builder.setFlags(flags);
-        builder.setUniqueId(uniqueId);
-        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
-                null /* projection */, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-
-        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
-        assertNotNull(ddi);
-        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0);
-    }
-
-    @Test
-    public void testCreateVirtualDisplayOwnFocus() throws RemoteException {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-
-        String uniqueId = "uniqueId --- Own Focus Test";
-        int width = 600;
-        int height = 800;
-        int dpi = 320;
-        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
-                | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
-
-        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
-                PackageManager.PERMISSION_GRANTED);
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi);
-        builder.setFlags(flags);
-        builder.setUniqueId(uniqueId);
-        int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
-                /* projection= */ null, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
-
-        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
-        assertNotNull(ddi);
-        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0);
-    }
-
-    @Test
-    public void testCreateVirtualDisplayOwnFocus_nonTrustedDisplay() throws RemoteException {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-
-        String uniqueId = "uniqueId --- Own Focus Test -- nonTrustedDisplay";
-        int width = 600;
-        int height = 800;
-        int dpi = 320;
-        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi);
-        builder.setFlags(flags);
-        builder.setUniqueId(uniqueId);
-        int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
-                /* projection= */ null, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
-
-        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
-        assertNotNull(ddi);
-        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) == 0);
-    }
-
-    /**
-     * Tests that the virtual display is created along-side the default display.
-     */
-    @Test
-    public void testStartVirtualDisplayWithDefaultDisplay_Succeeds() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-    }
-
-    /**
-     * Tests that we send the device state to window manager
-     */
-    @Test
-    public void testOnStateChanged_sendsStateChangedEventToWm() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-        displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-        DeviceStateListener listener = displayManager.new DeviceStateListener();
-        Handler handler = displayManager.getDisplayHandler();
-        IDisplayManagerCallback displayChangesCallback = registerDisplayChangeCallback(
-                displayManager);
-
-        listener.onStateChanged(123);
-        waitForIdleHandler(handler);
-
-        InOrder inOrder = inOrder(mMockWindowManagerInternal, displayChangesCallback);
-        // Verify there are no display events before WM call
-        inOrder.verify(displayChangesCallback, never()).onDisplayEvent(anyInt(), anyInt());
-        inOrder.verify(mMockWindowManagerInternal).onDisplayManagerReceivedDeviceState(123);
-    }
-
-    /**
-     * Tests that there should be a display change notification to WindowManager to update its own
-     * internal state for things like display cutout when nonOverrideDisplayInfo is changed.
-     */
-    @Test
-    public void testShouldNotifyChangeWhenNonOverrideDisplayInfoChanged() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        // Add the FakeDisplayDevice
-        FakeDisplayDevice displayDevice = new FakeDisplayDevice();
-        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
-        displayDeviceInfo.width = 100;
-        displayDeviceInfo.height = 200;
-        displayDeviceInfo.supportedModes = new Display.Mode[1];
-        displayDeviceInfo.supportedModes[0] = new Display.Mode(1, 100, 200, 60f);
-        displayDeviceInfo.modeId = 1;
-        final Rect zeroRect = new Rect();
-        displayDeviceInfo.displayCutout = new DisplayCutout(
-                Insets.of(0, 10, 0, 0),
-                zeroRect, new Rect(0, 0, 10, 10), zeroRect, zeroRect);
-        displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
-        displayDeviceInfo.address = new TestUtils.TestDisplayAddress();
-        displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
-        displayManager.getDisplayDeviceRepository()
-                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
-
-        // Find the display id of the added FakeDisplayDevice
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-        int displayId = getDisplayIdForDisplayDevice(displayManager, bs, displayDevice);
-        // Setup override DisplayInfo
-        DisplayInfo overrideInfo = bs.getDisplayInfo(displayId);
-        displayManager.setDisplayInfoOverrideFromWindowManagerInternal(displayId, overrideInfo);
-
-        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(
-                displayManager, bs, displayDevice);
-
-        // Simulate DisplayDevice change
-        DisplayDeviceInfo displayDeviceInfo2 = new DisplayDeviceInfo();
-        displayDeviceInfo2.copyFrom(displayDeviceInfo);
-        displayDeviceInfo2.displayCutout = null;
-        displayDevice.setDisplayDeviceInfo(displayDeviceInfo2);
-        displayManager.getDisplayDeviceRepository()
-                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED);
-
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-        assertTrue(callback.mDisplayChangedCalled);
-    }
-
-    /**
-     * Tests that we get a Runtime exception when we cannot initialize the default display.
-     */
-    @Test
-    public void testStartVirtualDisplayWithDefDisplay_NoDefaultDisplay() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-
-        try {
-            displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-        } catch (RuntimeException e) {
-            return;
-        }
-        fail("Expected DisplayManager to throw RuntimeException when it cannot initialize the"
-                + " default display");
-    }
-
-    /**
-     * Tests that we get a Runtime exception when we cannot initialize the virtual display.
-     */
-    @Test
-    public void testStartVirtualDisplayWithDefDisplay_NoVirtualDisplayAdapter() throws Exception {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext,
-                new DisplayManagerService.Injector() {
-                    @Override
-                    VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot,
-                            Context context, Handler handler, DisplayAdapter.Listener listener) {
-                        return null;  // return null for the adapter.  This should cause a failure.
-                    }
-
-                    @Override
-                    long getDefaultDisplayDelayTimeout() {
-                        return SHORT_DEFAULT_DISPLAY_TIMEOUT_MILLIS;
-                    }
-                });
-        try {
-            displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-        } catch (RuntimeException e) {
-            return;
-        }
-        fail("Expected DisplayManager to throw RuntimeException when it cannot initialize the"
-                + " virtual display adapter");
-    }
-
-    /**
-     * Tests that an exception is raised for too dark a brightness configuration.
-     */
-    @Test
-    public void testTooDarkBrightnessConfigurationThrowException() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        Curve minimumBrightnessCurve = displayManager.getMinimumBrightnessCurveInternal();
-        float[] lux = minimumBrightnessCurve.getX();
-        float[] minimumNits = minimumBrightnessCurve.getY();
-        float[] nits = new float[minimumNits.length];
-        // For every control point, assert that making it slightly lower than the minimum throws an
-        // exception.
-        for (int i = 0; i < nits.length; i++) {
-            for (int j = 0; j < nits.length; j++) {
-                nits[j] = minimumNits[j];
-                if (j == i) {
-                    nits[j] -= 0.1f;
-                }
-                if (nits[j] < 0) {
-                    nits[j] = 0;
-                }
-            }
-            BrightnessConfiguration config =
-                    new BrightnessConfiguration.Builder(lux, nits).build();
-            Exception thrown = null;
-            try {
-                displayManager.validateBrightnessConfiguration(config);
-            } catch (IllegalArgumentException e) {
-                thrown = e;
-            }
-            assertNotNull("Building too dark a brightness configuration must throw an exception");
-        }
-    }
-
-    /**
-     * Tests that no exception is raised for not too dark a brightness configuration.
-     */
-    @Test
-    public void testBrightEnoughBrightnessConfigurationDoesNotThrowException() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        Curve minimumBrightnessCurve = displayManager.getMinimumBrightnessCurveInternal();
-        float[] lux = minimumBrightnessCurve.getX();
-        float[] nits = minimumBrightnessCurve.getY();
-        BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits).build();
-        displayManager.validateBrightnessConfiguration(config);
-    }
-
-    /**
-     * Tests that null brightness configurations are alright.
-     */
-    @Test
-    public void testNullBrightnessConfiguration() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        displayManager.validateBrightnessConfiguration(null);
-    }
-
-    /**
-     * Tests that collection of display color sampling results are sensible.
-     */
-    @Test
-    public void testDisplayedContentSampling() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        registerDefaultDisplays(displayManager);
-
-        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(0);
-        assertNotNull(ddi);
-
-        DisplayedContentSamplingAttributes attr =
-                displayManager.getDisplayedContentSamplingAttributesInternal(0);
-        if (attr == null) return; //sampling not supported on device, skip remainder of test.
-
-        boolean enabled = displayManager.setDisplayedContentSamplingEnabledInternal(0, true, 0, 0);
-        assertTrue(enabled);
-
-        displayManager.setDisplayedContentSamplingEnabledInternal(0, false, 0, 0);
-        DisplayedContentSample sample = displayManager.getDisplayedContentSampleInternal(0, 0, 0);
-        assertNotNull(sample);
-
-        long numPixels = ddi.width * ddi.height * sample.getNumFrames();
-        long[] samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL0);
-        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
-
-        samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL1);
-        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
-
-        samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL2);
-        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
-
-        samples = sample.getSampleComponent(DisplayedContentSample.ColorComponent.CHANNEL3);
-        assertTrue(samples.length == 0 || LongStream.of(samples).sum() == numPixels);
-    }
-
-    /**
-     * Tests that the virtual display is created with
-     * {@link VirtualDisplayConfig.Builder#setDisplayIdToMirror(int)}
-     */
-    @Test
-    @FlakyTest(bugId = 127687569)
-    public void testCreateVirtualDisplay_displayIdToMirror() throws Exception {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        DisplayManagerService.LocalService localDisplayManager = displayManager.new LocalService();
-
-        final String uniqueId = "uniqueId --- displayIdToMirrorTest";
-        final int width = 600;
-        final int height = 800;
-        final int dpi = 320;
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi);
-        builder.setUniqueId(uniqueId);
-        final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        // The second virtual display requests to mirror the first virtual display.
-        final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
-        when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2);
-        final VirtualDisplayConfig.Builder builder2 = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi).setUniqueId(uniqueId2);
-        builder2.setUniqueId(uniqueId2);
-        builder2.setDisplayIdToMirror(firstDisplayId);
-        final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
-                mMockAppToken2 /* callback */, null /* projection */,
-                PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-
-        // The displayId to mirror should be a default display if there is none initially.
-        assertEquals(localDisplayManager.getDisplayIdToMirror(firstDisplayId),
-                Display.DEFAULT_DISPLAY);
-        assertEquals(localDisplayManager.getDisplayIdToMirror(secondDisplayId),
-                firstDisplayId);
-    }
-
-    /** Tests that the virtual device is created in a device display group. */
-    @Test
-    public void createVirtualDisplay_addsDisplaysToDeviceDisplayGroups() throws Exception {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerInternal localService = displayManager.new LocalService();
-
-        registerDefaultDisplays(displayManager);
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
-        when(virtualDevice.getDeviceId()).thenReturn(1);
-        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
-        // Create a first virtual display. A display group should be created for this display on the
-        // virtual device.
-        final VirtualDisplayConfig.Builder builder1 =
-                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setUniqueId("uniqueId --- device display group 1");
-
-        int displayId1 =
-                localService.createVirtualDisplay(
-                        builder1.build(),
-                        mMockAppToken /* callback */,
-                        virtualDevice /* virtualDeviceToken */,
-                        mock(DisplayWindowPolicyController.class),
-                        PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
-
-        // Create a second virtual display. This should be added to the previously created display
-        // group.
-        final VirtualDisplayConfig.Builder builder2 =
-                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setUniqueId("uniqueId --- device display group 1");
-
-        int displayId2 =
-                localService.createVirtualDisplay(
-                        builder2.build(),
-                        mMockAppToken /* callback */,
-                        virtualDevice /* virtualDeviceToken */,
-                        mock(DisplayWindowPolicyController.class),
-                        PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
-
-        assertEquals(
-                "Both displays should be added to the same displayGroup.",
-                displayGroupId1,
-                displayGroupId2);
-    }
-
-    /**
-     * Tests that the virtual display is not added to the device display group when
-     * VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is set.
-     */
-    @Test
-    public void createVirtualDisplay_addsDisplaysToOwnDisplayGroups() throws Exception {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerInternal localService = displayManager.new LocalService();
-
-        registerDefaultDisplays(displayManager);
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
-        when(virtualDevice.getDeviceId()).thenReturn(1);
-        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
-
-        // Create a first virtual display. A display group should be created for this display on the
-        // virtual device.
-        final VirtualDisplayConfig.Builder builder1 =
-                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setUniqueId("uniqueId --- device display group");
-
-        int displayId1 =
-                localService.createVirtualDisplay(
-                        builder1.build(),
-                        mMockAppToken /* callback */,
-                        virtualDevice /* virtualDeviceToken */,
-                        mock(DisplayWindowPolicyController.class),
-                        PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
-
-        // Create a second virtual display. With the flag VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
-        // the display should not be added to the previously created display group.
-        final VirtualDisplayConfig.Builder builder2 =
-                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
-                        .setUniqueId("uniqueId --- own display group");
-
-        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
-
-        int displayId2 =
-                localService.createVirtualDisplay(
-                        builder2.build(),
-                        mMockAppToken /* callback */,
-                        virtualDevice /* virtualDeviceToken */,
-                        mock(DisplayWindowPolicyController.class),
-                        PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
-
-        assertNotEquals(
-                "Display 1 should be in the device display group and display 2 in its own display"
-                        + " group.",
-                displayGroupId1,
-                displayGroupId2);
-    }
-
-    @Test
-    public void displaysInDeviceOrOwnDisplayGroupShouldPreserveAlwaysUnlockedFlag()
-            throws Exception {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerInternal localService = displayManager.new LocalService();
-
-        registerDefaultDisplays(displayManager);
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
-        when(virtualDevice.getDeviceId()).thenReturn(1);
-        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
-
-        // Allow an ALWAYS_UNLOCKED display to be created.
-        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
-
-        when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
-
-        // Create a virtual display in a device display group.
-        final VirtualDisplayConfig deviceDisplayGroupDisplayConfig =
-                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setUniqueId("uniqueId --- device display group 1")
-                        .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
-                        .build();
-
-        int deviceDisplayGroupDisplayId =
-                localService.createVirtualDisplay(
-                        deviceDisplayGroupDisplayConfig,
-                        mMockAppToken /* callback */,
-                        virtualDevice /* virtualDeviceToken */,
-                        mock(DisplayWindowPolicyController.class),
-                        PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        // Check that FLAG_ALWAYS_UNLOCKED is set.
-        assertNotEquals(
-                "FLAG_ALWAYS_UNLOCKED should be set for displays created in a device display"
-                        + " group.",
-                (displayManager.getDisplayDeviceInfoInternal(deviceDisplayGroupDisplayId).flags
-                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
-                0);
-
-        // Create a virtual display in its own display group.
-        final VirtualDisplayConfig ownDisplayGroupConfig =
-                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setUniqueId("uniqueId --- own display group 1")
-                        .setFlags(
-                                VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
-                                        | VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
-                        .build();
-
-        int ownDisplayGroupDisplayId =
-                localService.createVirtualDisplay(
-                        ownDisplayGroupConfig,
-                        mMockAppToken /* callback */,
-                        virtualDevice /* virtualDeviceToken */,
-                        mock(DisplayWindowPolicyController.class),
-                        PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        // Check that FLAG_ALWAYS_UNLOCKED is set.
-        assertNotEquals(
-                "FLAG_ALWAYS_UNLOCKED should be set for displays created in their own display"
-                        + " group.",
-                (displayManager.getDisplayDeviceInfoInternal(ownDisplayGroupDisplayId).flags
-                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
-                0);
-
-        // Create a virtual display in a device display group.
-        final VirtualDisplayConfig defaultDisplayGroupConfig =
-                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setUniqueId("uniqueId --- default display group 1")
-                        .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
-                        .build();
-
-        int defaultDisplayGroupDisplayId =
-                localService.createVirtualDisplay(
-                        defaultDisplayGroupConfig,
-                        mMockAppToken /* callback */,
-                        null /* virtualDeviceToken */,
-                        mock(DisplayWindowPolicyController.class),
-                        PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        // Check that FLAG_ALWAYS_UNLOCKED is not set.
-        assertEquals(
-                "FLAG_ALWAYS_UNLOCKED should not be set for displays created in the default"
-                        + " display group.",
-                (displayManager.getDisplayDeviceInfoInternal(defaultDisplayGroupDisplayId).flags
-                        & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
-                0);
-    }
-
-    @Test
-    public void testGetDisplayIdToMirror() throws Exception {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        DisplayManagerService.LocalService localDisplayManager = displayManager.new LocalService();
-
-        final String uniqueId = "uniqueId --- displayIdToMirrorTest";
-        final int width = 600;
-        final int height = 800;
-        final int dpi = 320;
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi)
-                .setUniqueId(uniqueId)
-                .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
-        final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        // The second virtual display requests to mirror the first virtual display.
-        final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
-        when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2);
-        final VirtualDisplayConfig.Builder builder2 = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi)
-                .setUniqueId(uniqueId2)
-                .setWindowManagerMirroringEnabled(true);
-        final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
-                mMockAppToken2 /* callback */, null /* projection */,
-                PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-
-        // The displayId to mirror should be a invalid since the display had flag OWN_CONTENT_ONLY
-        assertEquals(localDisplayManager.getDisplayIdToMirror(firstDisplayId),
-                Display.INVALID_DISPLAY);
-        // The second display has mirroring managed by WindowManager so the mirror displayId should
-        // be invalid.
-        assertEquals(localDisplayManager.getDisplayIdToMirror(secondDisplayId),
-                Display.INVALID_DISPLAY);
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_isValidProjection_notValid()
-            throws RemoteException {
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        IMediaProjection projection = mock(IMediaProjection.class);
-        doReturn(false).when(projection).isValid();
-        when(mMockProjectionService
-                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
-                .thenReturn(true);
-        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
-
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setUniqueId("uniqueId --- isValid false");
-
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-
-        // Pass in a non-null projection.
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, projection, PACKAGE_NAME);
-
-        // VirtualDisplay is created for mirroring.
-        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
-        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
-                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
-        // But mirroring doesn't begin.
-        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
-                mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
-        ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
-        assertThat(session.isWaitingForConsent()).isTrue();
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_setContentRecordingSessionSuccess()
-            throws RemoteException {
-        final int displayToRecord = 50;
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        IMediaProjection projection = mock(IMediaProjection.class);
-        doReturn(true).when(projection).isValid();
-        when(mMockProjectionService
-                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
-                .thenReturn(true);
-        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
-
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setUniqueId("uniqueId --- setContentRecordingSession true");
-        builder.setDisplayIdToMirror(displayToRecord);
-
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, projection, PACKAGE_NAME);
-
-        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
-        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
-                mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
-        ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
-        assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_DISPLAY);
-        assertThat(session.getVirtualDisplayId()).isEqualTo(displayId);
-        assertThat(session.getDisplayToRecord()).isEqualTo(displayToRecord);
-        assertThat(session.isWaitingForConsent()).isFalse();
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_setContentRecordingSessionFail() throws RemoteException {
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        IMediaProjection projection = mock(IMediaProjection.class);
-        doReturn(true).when(projection).isValid();
-        when(mMockProjectionService
-                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
-                .thenReturn(false);
-        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
-
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
-
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, projection, PACKAGE_NAME);
-
-        assertThat(displayId).isEqualTo(Display.INVALID_DISPLAY);
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_setContentRecordingSession_taskSession()
-            throws RemoteException {
-        final int displayToRecord = 50;
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        IMediaProjection projection = mock(IMediaProjection.class);
-        doReturn(true).when(projection).isValid();
-        when(mMockProjectionService
-                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
-                .thenReturn(true);
-        doReturn(mock(IBinder.class)).when(projection).getLaunchCookie();
-        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
-
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
-        builder.setDisplayIdToMirror(displayToRecord);
-
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, projection, PACKAGE_NAME);
-
-        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
-        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
-                mContentRecordingSessionCaptor.capture(), nullable(IMediaProjection.class));
-        ContentRecordingSession session = mContentRecordingSessionCaptor.getValue();
-        assertThat(session.getContentToRecord()).isEqualTo(RECORD_CONTENT_TASK);
-        assertThat(session.getVirtualDisplayId()).isEqualTo(displayId);
-        assertThat(session.getTokenToRecord()).isNotNull();
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noFlags()
-            throws RemoteException {
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        // Set no flags for the VirtualDisplay.
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
-
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-
-        // Pass in a null projection.
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
-
-        // VirtualDisplay is created but not for mirroring.
-        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
-        verify(mMockProjectionService, never()).setContentRecordingSession(
-                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noMirroringFlag()
-            throws RemoteException {
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        // Set a non-mirroring flag for the VirtualDisplay.
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
-        builder.setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
-
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-
-        // Pass in a null projection.
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
-
-        // VirtualDisplay is created but not for mirroring.
-        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
-        verify(mMockProjectionService, never()).setContentRecordingSession(
-                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
-    }
-
-    @Test
-    public void testCreateVirtualDisplay_setContentRecordingSession_projection_noMirroringFlag()
-            throws RemoteException {
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        IMediaProjection projection = mock(IMediaProjection.class);
-        doReturn(true).when(projection).isValid();
-        when(mMockProjectionService
-                .setContentRecordingSession(any(ContentRecordingSession.class), eq(projection)))
-                .thenReturn(true);
-        doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection));
-
-        // Set no flags for the VirtualDisplay.
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setUniqueId("uniqueId --- setContentRecordingSession false");
-
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-        displayManager.windowManagerAndInputReady();
-
-        // Pass in a non-null projection.
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, projection, PACKAGE_NAME);
-
-        // VirtualDisplay is created for mirroring.
-        assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
-        verify(mMockProjectionService, atLeastOnce()).setContentRecordingSession(
-                any(ContentRecordingSession.class), nullable(IMediaProjection.class));
-    }
-
-    /**
-     * Tests that the virtual display is created with
-     * {@link VirtualDisplayConfig.Builder#setSurface(Surface)}
-     */
-    @Test
-    @FlakyTest(bugId = 127687569)
-    public void testCreateVirtualDisplay_setSurface() throws Exception {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        // This is effectively the DisplayManager service published to ServiceManager.
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-
-        final String uniqueId = "uniqueId --- setSurface";
-        final int width = 600;
-        final int height = 800;
-        final int dpi = 320;
-        final Surface surface = new Surface();
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, width, height, dpi);
-        builder.setSurface(surface);
-        builder.setUniqueId(uniqueId);
-        final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-
-        // flush the handler
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-
-        assertEquals(displayManager.getVirtualDisplaySurfaceInternal(mMockAppToken), surface);
-    }
-
-    /**
-     * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when the permission
-     * ADD_TRUSTED_DISPLAY is granted.
-     */
-    @Test
-    public void testOwnDisplayGroup_allowCreationWithAddTrustedDisplayPermission()
-            throws RemoteException {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
-                PackageManager.PERMISSION_GRANTED);
-
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP);
-        builder.setUniqueId("uniqueId --- OWN_DISPLAY_GROUP");
-
-        int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
-                null /* projection */, PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
-        assertNotNull(ddi);
-        assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
-    }
-
-    /**
-     * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is blocked when the permission
-     * ADD_TRUSTED_DISPLAY is denied.
-     */
-    @Test
-    public void testOwnDisplayGroup_withoutAddTrustedDisplayPermission_throwsSecurityException() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        registerDefaultDisplays(displayManager);
-
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
-                PackageManager.PERMISSION_DENIED);
-
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP);
-        builder.setUniqueId("uniqueId --- OWN_DISPLAY_GROUP");
-
-        try {
-            bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
-                    null /* projection */, PACKAGE_NAME);
-            fail("Creating virtual display with VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP without "
-                    + "ADD_TRUSTED_DISPLAY permission should throw SecurityException.");
-        } catch (SecurityException e) {
-            // SecurityException is expected
-        }
-    }
-
-    /**
-     * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when called with
-     * a virtual device, even if ADD_TRUSTED_DISPLAY is not granted.
-     */
-    @Test
-    public void testOwnDisplayGroup_allowCreationWithVirtualDevice()  throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerInternal localService = displayManager.new LocalService();
-
-        registerDefaultDisplays(displayManager);
-
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-
-        when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
-                PackageManager.PERMISSION_DENIED);
-
-        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
-                VIRTUAL_DISPLAY_NAME, 600, 800, 320);
-        builder.setFlags(DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP);
-        builder.setUniqueId("uniqueId --- OWN_DISPLAY_GROUP");
-
-        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
-        when(virtualDevice.getDeviceId()).thenReturn(1);
-        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
-
-        int displayId = localService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, virtualDevice /* virtualDeviceToken */,
-                mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
-        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
-                nullable(IMediaProjection.class));
-        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
-        displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
-        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
-        assertNotNull(ddi);
-        assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
-    }
-
-    /**
-     * Tests that there is a display change notification if the frame rate override
-     * list is updated.
-     */
-    @Test
-    public void testShouldNotifyChangeWhenDisplayInfoFrameRateOverrideChanged() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
-        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
-                displayManagerBinderService, displayDevice);
-
-        int myUid = Process.myUid();
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
-                });
-        assertTrue(callback.mDisplayChangedCalled);
-        callback.clear();
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
-                        new DisplayEventReceiver.FrameRateOverride(1234, 30f),
-                });
-        assertFalse(callback.mDisplayChangedCalled);
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(myUid, 20f),
-                        new DisplayEventReceiver.FrameRateOverride(1234, 30f),
-                        new DisplayEventReceiver.FrameRateOverride(5678, 30f),
-                });
-        assertTrue(callback.mDisplayChangedCalled);
-        callback.clear();
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(1234, 30f),
-                        new DisplayEventReceiver.FrameRateOverride(5678, 30f),
-                });
-        assertTrue(callback.mDisplayChangedCalled);
-        callback.clear();
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(5678, 30f),
-                });
-        assertFalse(callback.mDisplayChangedCalled);
-    }
-
-    /**
-     * Tests that the DisplayInfo is updated correctly with a frame rate override
-     */
-    @Test
-    public void testDisplayInfoFrameRateOverride() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f, 30f, 20f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid(), 20f),
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid() + 1, 30f)
-                });
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
-
-        // Changing the mode to 30Hz should not override the refresh rate to 20Hz anymore
-        // as 20 is not a divider of 30.
-        updateModeId(displayManager, displayDevice, 2);
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(30f, displayInfo.getRefreshRate(), 0.01f);
-    }
-
-    /**
-     * Tests that the frame rate override is returning the correct value from
-     * DisplayInfo#getRefreshRate
-     */
-    @Test
-    public void testDisplayInfoNonNativeFrameRateOverride() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid(), 20f)
-                });
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
-    }
-
-    /**
-     * Tests that the mode reflects the frame rate override is in compat mode
-     */
-    @Test
-    @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public  void testDisplayInfoFrameRateOverrideModeCompat() throws Exception {
-        testDisplayInfoFrameRateOverrideModeCompat(/*compatChangeEnabled*/ false);
-    }
-
-    /**
-     * Tests that the mode reflects the physical display refresh rate when not in compat mode.
-     */
-    @Test
-    @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public  void testDisplayInfoFrameRateOverrideMode() throws Exception {
-        testDisplayInfoFrameRateOverrideModeCompat(/*compatChangeEnabled*/ true);
-    }
-
-    /**
-     * Tests that the mode reflects the frame rate override is in compat mode and accordingly to the
-     * allowNonNativeRefreshRateOverride policy.
-     */
-    @Test
-    @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public void testDisplayInfoNonNativeFrameRateOverrideModeCompat() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ false);
-    }
-
-    /**
-     * Tests that the mode reflects the physical display refresh rate when not in compat mode.
-     */
-    @Test
-    @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public void testDisplayInfoNonNativeFrameRateOverrideMode() throws Exception {
-        testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ true);
-    }
-
-    /**
-     * Tests that there is a display change notification if the render frame rate is updated
-     */
-    @Test
-    public void testShouldNotifyChangeWhenDisplayInfoRenderFrameRateChanged() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
-        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
-                displayManagerBinderService, displayDevice);
-
-        updateRenderFrameRate(displayManager, displayDevice, 30f);
-        assertTrue(callback.mDisplayChangedCalled);
-        callback.clear();
-
-        updateRenderFrameRate(displayManager, displayDevice, 30f);
-        assertFalse(callback.mDisplayChangedCalled);
-
-        updateRenderFrameRate(displayManager, displayDevice, 20f);
-        assertTrue(callback.mDisplayChangedCalled);
-        callback.clear();
-    }
-
-    /**
-     * Tests that the DisplayInfo is updated correctly with a render frame rate
-     */
-    @Test
-    public void testDisplayInfoRenderFrameRate() throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f, 30f, 20f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateRenderFrameRate(displayManager, displayDevice, 20f);
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
-    }
-
-    /**
-     * Tests that the mode reflects the render frame rate is in compat mode
-     */
-    @Test
-    @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public  void testDisplayInfoRenderFrameRateModeCompat() throws Exception {
-        testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ false);
-    }
-
-    /**
-     * Tests that the mode reflects the physical display refresh rate when not in compat mode.
-     */
-    @Test
-    @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public  void testDisplayInfoRenderFrameRateMode() throws Exception {
-        testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ true);
-    }
-
-    /**
-     * Tests that EVENT_DISPLAY_ADDED is sent when a display is added.
-     */
-    @Test
-    public void testShouldNotifyDisplayAdded_WhenNewDisplayDeviceIsAdded() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-
-        // register display listener callback
-        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
-        displayManagerBinderService.registerCallbackWithEventMask(
-                callback, STANDARD_DISPLAY_EVENTS);
-
-        waitForIdleHandler(handler);
-
-        createFakeDisplayDevice(displayManager, new float[]{60f});
-
-        waitForIdleHandler(handler);
-
-        assertFalse(callback.mDisplayChangedCalled);
-        assertFalse(callback.mDisplayRemovedCalled);
-        assertTrue(callback.mDisplayAddedCalled);
-    }
-
-    /**
-     * Tests that EVENT_DISPLAY_ADDED is not sent when a display is added and the
-     * client has a callback which is not subscribed to this event type.
-     */
-    @Test
-    public void testShouldNotNotifyDisplayAdded_WhenClientIsNotSubscribed() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-
-        // register display listener callback
-        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
-        long allEventsExceptDisplayAdded = STANDARD_DISPLAY_EVENTS
-                & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED;
-        displayManagerBinderService.registerCallbackWithEventMask(callback,
-                allEventsExceptDisplayAdded);
-
-        waitForIdleHandler(handler);
-
-        createFakeDisplayDevice(displayManager, new float[]{60f});
-
-        waitForIdleHandler(handler);
-
-        assertFalse(callback.mDisplayChangedCalled);
-        assertFalse(callback.mDisplayRemovedCalled);
-        assertFalse(callback.mDisplayAddedCalled);
-    }
-
-    /**
-     * Tests that EVENT_DISPLAY_REMOVED is sent when a display is removed.
-     */
-    @Test
-    public void testShouldNotifyDisplayRemoved_WhenDisplayDeviceIsRemoved() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f});
-
-        waitForIdleHandler(handler);
-
-        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(
-                displayManager, displayManagerBinderService, displayDevice);
-
-        waitForIdleHandler(handler);
-
-        displayManager.getDisplayDeviceRepository()
-                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
-
-        waitForIdleHandler(handler);
-
-        assertFalse(callback.mDisplayChangedCalled);
-        assertTrue(callback.mDisplayRemovedCalled);
-        assertFalse(callback.mDisplayAddedCalled);
-    }
-
-    /**
-     * Tests that EVENT_DISPLAY_REMOVED is not sent when a display is added and the
-     * client has a callback which is not subscribed to this event type.
-     */
-    @Test
-    public void testShouldNotNotifyDisplayRemoved_WhenClientIsNotSubscribed() {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f});
-
-        waitForIdleHandler(handler);
-
-        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
-        long allEventsExceptDisplayRemoved = STANDARD_DISPLAY_EVENTS
-                & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
-        displayManagerBinderService.registerCallbackWithEventMask(callback,
-                allEventsExceptDisplayRemoved);
-
-        waitForIdleHandler(handler);
-
-        displayManager.getDisplayDeviceRepository()
-                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
-
-        waitForIdleHandler(handler);
-
-        assertFalse(callback.mDisplayChangedCalled);
-        assertFalse(callback.mDisplayRemovedCalled);
-        assertFalse(callback.mDisplayAddedCalled);
-    }
-
-
-
-    @Test
-    public void testSettingTwoBrightnessConfigurationsOnMultiDisplay() {
-        Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
-        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
-
-        // get the first two internal displays
-        Display[] displays = displayManager.getDisplays();
-        Display internalDisplayOne = null;
-        Display internalDisplayTwo = null;
-        for (Display display : displays) {
-            if (display.getType() == Display.TYPE_INTERNAL) {
-                if (internalDisplayOne == null) {
-                    internalDisplayOne = display;
-                } else {
-                    internalDisplayTwo = display;
-                    break;
-                }
-            }
-        }
-
-        // return if there are fewer than 2 displays on this device
-        if (internalDisplayOne == null || internalDisplayTwo == null) {
-            return;
-        }
-
-        final String uniqueDisplayIdOne = internalDisplayOne.getUniqueId();
-        final String uniqueDisplayIdTwo = internalDisplayTwo.getUniqueId();
-
-        BrightnessConfiguration configOne =
-                new BrightnessConfiguration.Builder(
-                        new float[]{0.0f, 12345.0f}, new float[]{15.0f, 400.0f})
-                        .setDescription("model:1").build();
-        BrightnessConfiguration configTwo =
-                new BrightnessConfiguration.Builder(
-                        new float[]{0.0f, 6789.0f}, new float[]{12.0f, 300.0f})
-                        .setDescription("model:2").build();
-
-        displayManager.setBrightnessConfigurationForDisplay(configOne,
-                uniqueDisplayIdOne);
-        displayManager.setBrightnessConfigurationForDisplay(configTwo,
-                uniqueDisplayIdTwo);
-
-        BrightnessConfiguration configFromOne =
-                displayManager.getBrightnessConfigurationForDisplay(uniqueDisplayIdOne);
-        BrightnessConfiguration configFromTwo =
-                displayManager.getBrightnessConfigurationForDisplay(uniqueDisplayIdTwo);
-
-        assertNotNull(configFromOne);
-        assertEquals(configOne, configFromOne);
-        assertEquals(configTwo, configFromTwo);
-
-    }
-
-    @Test
-    public void testHdrConversionModeEquals() {
-        assertEquals(
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2));
-        assertNotEquals(
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 3));
-        assertEquals(
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM),
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
-        assertNotEquals(
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
-                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
-    }
-
-    @Test
-    public void testCreateHdrConversionMode_withInvalidArguments_throwsException() {
-        assertThrows(
-                "preferredHdrOutputType must not be set if the conversion mode is "
-                        + "HDR_CONVERSION_PASSTHROUGH",
-                IllegalArgumentException.class,
-                () -> new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH,
-                        Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION));
-    }
-
-    @Test
-    public void testSetHdrConversionModeInternal_withInvalidArguments_throwsException() {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        assertThrows("Expected DisplayManager to throw IllegalArgumentException when "
-                        + "preferredHdrOutputType is set and the conversion mode is "
-                        + "HDR_CONVERSION_SYSTEM",
-                IllegalArgumentException.class,
-                () -> displayManager.setHdrConversionModeInternal(new HdrConversionMode(
-                        HdrConversionMode.HDR_CONVERSION_SYSTEM,
-                        Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION)));
-    }
-
-    @Test
-    public void testSetAndGetHdrConversionModeInternal() {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        final HdrConversionMode mode = new HdrConversionMode(
-                HdrConversionMode.HDR_CONVERSION_FORCE,
-                Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION);
-        displayManager.setHdrConversionModeInternal(mode);
-        assertEquals(mode, displayManager.getHdrConversionModeSettingInternal());
-        assertEquals(mode.getConversionMode(), mHdrConversionMode);
-        assertEquals(mode.getPreferredHdrOutputType(), mPreferredHdrOutputType);
-    }
-
-    @Test
-    public void testHdrConversionMode_withMinimalPostProcessing() {
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f, 30f, 20f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-
-        final HdrConversionMode mode = new HdrConversionMode(
-                HdrConversionMode.HDR_CONVERSION_FORCE,
-                Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION);
-        displayManager.setHdrConversionModeInternal(mode);
-        assertEquals(mode, displayManager.getHdrConversionModeSettingInternal());
-
-        displayManager.setMinimalPostProcessingAllowed(true);
-        displayManager.setDisplayPropertiesInternal(displayId, false /* hasContent */,
-                30.0f /* requestedRefreshRate */,
-                displayDevice.getDisplayDeviceInfoLocked().modeId /* requestedModeId */,
-                30.0f /* requestedMinRefreshRate */, 120.0f /* requestedMaxRefreshRate */,
-                true /* preferMinimalPostProcessing */, false /* disableHdrConversion */,
-                true /* inTraversal */);
-
-        assertEquals(new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH),
-                displayManager.getHdrConversionModeInternal());
-
-        displayManager.setDisplayPropertiesInternal(displayId, false /* hasContent */,
-                30.0f /* requestedRefreshRate */,
-                displayDevice.getDisplayDeviceInfoLocked().modeId /* requestedModeId */,
-                30.0f /* requestedMinRefreshRate */, 120.0f /* requestedMaxRefreshRate */,
-                false /* preferMinimalPostProcessing */, false /* disableHdrConversion */,
-                true /* inTraversal */);
-        assertEquals(mode, displayManager.getHdrConversionModeInternal());
-    }
-
-    private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled)
-            throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f, 30f, 20f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid(), 20f),
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid() + 1, 30f)
-                });
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
-        Display.Mode expectedMode;
-        if (compatChangeEnabled) {
-            expectedMode = new Display.Mode(1, 100, 200, 60f);
-        } else {
-            expectedMode = new Display.Mode(3, 100, 200, 20f);
-        }
-        assertEquals(expectedMode, displayInfo.getMode());
-    }
-
-    private void testDisplayInfoRenderFrameRateModeCompat(boolean compatChangeEnabled)
-            throws Exception {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mShortMockedInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f, 30f, 20f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateRenderFrameRate(displayManager, displayDevice, 20f);
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
-        Display.Mode expectedMode;
-        if (compatChangeEnabled) {
-            expectedMode = new Display.Mode(1, 100, 200, 60f);
-        } else {
-            expectedMode = new Display.Mode(3, 100, 200, 20f);
-        }
-        assertEquals(expectedMode, displayInfo.getMode());
-    }
-
-    private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) {
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerService.BinderService displayManagerBinderService =
-                displayManager.new BinderService();
-        registerDefaultDisplays(displayManager);
-        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
-        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
-                new float[]{60f});
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
-        updateFrameRateOverride(displayManager, displayDevice,
-                new DisplayEventReceiver.FrameRateOverride[]{
-                        new DisplayEventReceiver.FrameRateOverride(
-                                Process.myUid(), 20f)
-                });
-        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
-        Display.Mode expectedMode;
-        if (compatChangeEnabled) {
-            expectedMode = new Display.Mode(1, 100, 200, 60f);
-        } else {
-            expectedMode = new Display.Mode(255, 100, 200, 20f);
-        }
-        assertEquals(expectedMode, displayInfo.getMode());
-    }
-
-    private int getDisplayIdForDisplayDevice(
-            DisplayManagerService displayManager,
-            DisplayManagerService.BinderService displayManagerBinderService,
-            FakeDisplayDevice displayDevice) {
-
-        final int[] displayIds = displayManagerBinderService.getDisplayIds(
-                /* includeDisabled= */ true);
-        assertTrue(displayIds.length > 0);
-        int displayId = Display.INVALID_DISPLAY;
-        for (int i = 0; i < displayIds.length; i++) {
-            DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayIds[i]);
-            if (displayDevice.getDisplayDeviceInfoLocked().equals(ddi)) {
-                displayId = displayIds[i];
-                break;
-            }
-        }
-        assertFalse(displayId == Display.INVALID_DISPLAY);
-        return displayId;
-    }
-
-    private void updateDisplayDeviceInfo(DisplayManagerService displayManager,
-            FakeDisplayDevice displayDevice,
-            DisplayDeviceInfo displayDeviceInfo) {
-        displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
-        displayManager.getDisplayDeviceRepository()
-                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED);
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-    }
-
-    private void updateFrameRateOverride(DisplayManagerService displayManager,
-            FakeDisplayDevice displayDevice,
-            DisplayEventReceiver.FrameRateOverride[] frameRateOverrides) {
-        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
-        displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
-        displayDeviceInfo.frameRateOverrides = frameRateOverrides;
-        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
-    }
-
-    private void updateRenderFrameRate(DisplayManagerService displayManager,
-            FakeDisplayDevice displayDevice,
-            float renderFrameRate) {
-        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
-        displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
-        displayDeviceInfo.renderFrameRate = renderFrameRate;
-        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
-    }
-
-    private void updateModeId(DisplayManagerService displayManager,
-            FakeDisplayDevice displayDevice,
-            int modeId) {
-        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
-        displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
-        displayDeviceInfo.modeId = modeId;
-        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
-    }
-
-    private IDisplayManagerCallback registerDisplayChangeCallback(
-            DisplayManagerService displayManager) {
-        IDisplayManagerCallback displayChangesCallback = mock(IDisplayManagerCallback.class);
-        when(displayChangesCallback.asBinder()).thenReturn(new Binder());
-        DisplayManagerService.BinderService binderService = displayManager.new BinderService();
-        binderService.registerCallback(displayChangesCallback);
-        return displayChangesCallback;
-    }
-
-    private FakeDisplayManagerCallback registerDisplayListenerCallback(
-            DisplayManagerService displayManager,
-            DisplayManagerService.BinderService displayManagerBinderService,
-            FakeDisplayDevice displayDevice) {
-        // Find the display id of the added FakeDisplayDevice
-        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
-                displayDevice);
-
-        Handler handler = displayManager.getDisplayHandler();
-        waitForIdleHandler(handler);
-
-        // register display listener callback
-        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(displayId);
-        displayManagerBinderService.registerCallbackWithEventMask(
-                callback, STANDARD_DISPLAY_EVENTS);
-        return callback;
-    }
-
-    private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
-            float[] refreshRates) {
-        FakeDisplayDevice displayDevice = new FakeDisplayDevice();
-        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
-        int width = 100;
-        int height = 200;
-        displayDeviceInfo.supportedModes = new Display.Mode[refreshRates.length];
-        for (int i = 0; i < refreshRates.length; i++) {
-            displayDeviceInfo.supportedModes[i] =
-                    new Display.Mode(i + 1, width, height, refreshRates[i]);
-        }
-        displayDeviceInfo.modeId = 1;
-        displayDeviceInfo.renderFrameRate = displayDeviceInfo.supportedModes[0].getRefreshRate();
-        displayDeviceInfo.width = width;
-        displayDeviceInfo.height = height;
-        final Rect zeroRect = new Rect();
-        displayDeviceInfo.displayCutout = new DisplayCutout(
-                Insets.of(0, 10, 0, 0),
-                zeroRect, new Rect(0, 0, 10, 10), zeroRect, zeroRect);
-        displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
-        displayDeviceInfo.address = new TestUtils.TestDisplayAddress();
-        displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
-        displayManager.getDisplayDeviceRepository()
-                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
-        return displayDevice;
-    }
-
-    private void registerDefaultDisplays(DisplayManagerService displayManager) {
-        Handler handler = displayManager.getDisplayHandler();
-        // Would prefer to call displayManager.onStart() directly here but it performs binderService
-        // registration which triggers security exceptions when running from a test.
-        handler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS);
-        waitForIdleHandler(handler);
-    }
-
-    private void waitForIdleHandler(Handler handler) {
-        waitForIdleHandler(handler, Duration.ofSeconds(1));
-    }
-
-    private void waitForIdleHandler(Handler handler, Duration timeout) {
-        final MessageQueue queue = handler.getLooper().getQueue();
-        final CountDownLatch latch = new CountDownLatch(1);
-        queue.addIdleHandler(() -> {
-            latch.countDown();
-            // Remove idle handler
-            return false;
-        });
-        try {
-            latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            fail("Interrupted unexpectedly: " + e);
-        }
-    }
-
-    private void resetConfigToIgnoreSensorManager(Context context) {
-        final Resources res = Mockito.spy(context.getResources());
-        doReturn(new int[]{-1}).when(res).getIntArray(R.array
-                .config_ambientThresholdsOfPeakRefreshRate);
-        doReturn(new int[]{-1}).when(res).getIntArray(R.array
-                .config_brightnessThresholdsOfPeakRefreshRate);
-        doReturn(new int[]{-1}).when(res).getIntArray(R.array
-                .config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
-        doReturn(new int[]{-1}).when(res).getIntArray(R.array
-                .config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
-
-        when(context.getResources()).thenReturn(res);
-    }
-
-    private class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub {
-        int mDisplayId;
-        boolean mDisplayAddedCalled = false;
-        boolean mDisplayChangedCalled = false;
-        boolean mDisplayRemovedCalled = false;
-
-        FakeDisplayManagerCallback(int displayId) {
-            mDisplayId = displayId;
-        }
-
-        FakeDisplayManagerCallback() {
-            mDisplayId = -1;
-        }
-
-        @Override
-        public void onDisplayEvent(int displayId, int event) {
-            if (mDisplayId != -1 && displayId != mDisplayId) {
-                return;
-            }
-
-            if (event == DisplayManagerGlobal.EVENT_DISPLAY_ADDED) {
-                mDisplayAddedCalled = true;
-            }
-
-            if (event == DisplayManagerGlobal.EVENT_DISPLAY_CHANGED) {
-                mDisplayChangedCalled = true;
-            }
-
-            if (event == DisplayManagerGlobal.EVENT_DISPLAY_REMOVED) {
-                mDisplayRemovedCalled = true;
-            }
-        }
-
-        public void clear() {
-            mDisplayAddedCalled = false;
-            mDisplayChangedCalled = false;
-            mDisplayRemovedCalled = false;
-        }
-    }
-
-    private class FakeDisplayDevice extends DisplayDevice {
-        private DisplayDeviceInfo mDisplayDeviceInfo;
-
-        FakeDisplayDevice() {
-            super(null, null, "", mContext);
-        }
-
-        public void setDisplayDeviceInfo(DisplayDeviceInfo displayDeviceInfo) {
-            mDisplayDeviceInfo = displayDeviceInfo;
-        }
-
-        @Override
-        public boolean hasStableUniqueId() {
-            return false;
-        }
-
-        @Override
-        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
-            return mDisplayDeviceInfo;
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
deleted file mode 100644
index e2a66f0..0000000
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ /dev/null
@@ -1,736 +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.server.display;
-
-import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
-import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
-import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
-
-import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
-import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
-import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
-import static com.android.server.display.DisplayDeviceConfig.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
-import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.anyFloat;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.hardware.display.BrightnessInfo;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.Message;
-import android.os.test.TestLooper;
-import android.test.mock.MockContentResolver;
-import android.util.MathUtils;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
-import com.android.server.display.HighBrightnessModeController.Injector;
-import com.android.server.testutils.OffsettableClock;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class HighBrightnessModeControllerTest {
-
-    private static final int MINIMUM_LUX = 100;
-    private static final float TRANSITION_POINT = 0.763f;
-    private static final long TIME_WINDOW_MILLIS = 55 * 1000;
-    private static final long TIME_ALLOWED_IN_WINDOW_MILLIS = 12 * 1000;
-    private static final long TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS = 5 * 1000;
-    private static final boolean ALLOW_IN_LOW_POWER_MODE = false;
-
-    private static final float DEFAULT_MIN = 0.01f;
-    private static final float DEFAULT_MAX = 0.80f;
-
-    private static final int DISPLAY_WIDTH = 900;
-    private static final int DISPLAY_HEIGHT = 1600;
-
-    private static final float EPSILON = 0.000001f;
-
-    private OffsettableClock mClock;
-    private TestLooper mTestLooper;
-    private Handler mHandler;
-    private Binder mDisplayToken;
-    private String mDisplayUniqueId;
-    private Context mContextSpy;
-    private HighBrightnessModeMetadata mHighBrightnessModeMetadata;
-
-    @Rule
-    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-
-    @Mock Injector mInjectorMock;
-    @Mock HighBrightnessModeController.HdrBrightnessDeviceConfig mHdrBrightnessDeviceConfigMock;
-
-    private static final HighBrightnessModeData DEFAULT_HBM_DATA =
-            new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS,
-                    TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS,
-                    ALLOW_IN_LOW_POWER_MODE, HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT);
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mClock = new OffsettableClock.Stopped();
-        mTestLooper = new TestLooper(mClock::now);
-        mDisplayToken = null;
-        mDisplayUniqueId = "unique_id";
-
-        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
-        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
-        when(mContextSpy.getContentResolver()).thenReturn(resolver);
-    }
-
-    /////////////////
-    // Test Methods
-    /////////////////
-
-    @Test
-    public void testNoHbmData() {
-        initHandler(null);
-        final HighBrightnessModeController hbmc = new HighBrightnessModeController(
-                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
-                mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {},
-                null, mContextSpy);
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
-        assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
-    }
-
-    @Test
-    public void testNoHbmData_Enabled() {
-        initHandler(null);
-        final HighBrightnessModeController hbmc = new HighBrightnessModeController(
-                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken,
-                mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {},
-                null, mContextSpy);
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
-        assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
-    }
-
-    @Test
-    public void testOffByDefault() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-    }
-
-    @Test
-    public void testAutoBrightnessEnabled_NoLux() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-    }
-
-    @Test
-    public void testAutoBrightnessEnabled_LowLux() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-    }
-
-    @Test
-    public void testAutoBrightnessEnabled_HighLux() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-    }
-
-    @Test
-    public void testAutoBrightnessEnabled_HighLux_ThenDisable() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_DISABLED);
-
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-    }
-
-    @Test
-    public void testWithinHighRange_thenOverTime_thenEarnBackTime() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-
-        // Verify we are in HBM
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        // Use up all the time in the window.
-        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS);
-
-        // Verify we are not out of HBM
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        // Shift time so that the HBM event is at the beginning of the current window
-        advanceTime(TIME_WINDOW_MILLIS - TIME_ALLOWED_IN_WINDOW_MILLIS);
-        // Shift time again so that we are just below the minimum allowable
-        advanceTime(TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS - 1);
-
-        // Verify we are not out of HBM
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        // Advance the necessary millisecond
-        advanceTime(1);
-
-        // Verify we are allowed HBM again.
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-    }
-
-    @Test
-    public void testInHBM_ThenLowLux() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-
-        // Verify we are in HBM
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
-
-        // Verify we are in HBM
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        hbmc.onAmbientLuxChange(1);
-        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1);
-
-        // Verify we are out of HBM
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-
-    }
-
-    @Test
-    public void testInHBM_TestMultipleEvents_DueToAutoBrightness() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
-
-        // Verify we are in HBM
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT - 0.01f);
-        advanceTime(1);
-
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
-
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        advanceTime(2);
-
-        // Now we should be out again.
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-    }
-
-    @Test
-    public void testInHBM_TestMultipleEvents_DueToLux() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-
-        // Go into HBM for half the allowed window
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        // Move lux below threshold (ending first event);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX - 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT);
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-
-        // Move up some amount of time so that there's still time in the window even after a
-        // second event.
-        advanceTime((TIME_WINDOW_MILLIS - TIME_ALLOWED_IN_WINDOW_MILLIS) / 2);
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-
-        // Go into HBM for just under the second half of allowed window
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 1);
-        advanceTime((TIME_ALLOWED_IN_WINDOW_MILLIS / 2) - 1);
-
-        assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
-
-        // Now exhaust the time
-        advanceTime(2);
-        assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
-    }
-
-    @Test
-    public void testHbmIsNotTurnedOffInHighThermalState() throws Exception {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-
-        // Disabled thermal throttling
-        hbmc.onBrightnessChanged(/*brightness=*/ 1f, /*unthrottledBrightness*/ 1f,
-                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
-
-        assertFalse(hbmc.isThermalThrottlingActive());
-
-        // Try to go into HBM mode
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        advanceTime(1);
-
-        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
-
-        // Enable thermal throttling
-        hbmc.onBrightnessChanged(/*brightness=*/ TRANSITION_POINT - 0.01f,
-                /*unthrottledBrightness*/ 1f, BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL);
-        advanceTime(10);
-        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
-        assertTrue(hbmc.isThermalThrottlingActive());
-
-        // Disabled thermal throttling
-        hbmc.onBrightnessChanged(/*brightness=*/ 1f, /*unthrottledBrightness*/ 1f,
-                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
-        advanceTime(1);
-        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
-        assertFalse(hbmc.isThermalThrottlingActive());
-    }
-
-    @Test
-    public void testHdrRequires50PercentOfScreen() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-
-        final int layerWidth = DISPLAY_WIDTH;
-        final int smallLayerHeight = DISPLAY_HEIGHT / 2 - 1; // height to use for <50%
-        final int largeLayerHeight = DISPLAY_HEIGHT / 2 + 1; // height to use for >50%
-
-        // ensure hdr doesn't turn on if layer is too small
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                layerWidth, smallLayerHeight, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
-
-        // Now check with layer larger than 50%
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                layerWidth, largeLayerHeight, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
-    }
-
-    @Test
-    public void testHdrRespectsMaxDesiredHdrSdrRatio() {
-        final HighBrightnessModeController hbmc = new TestHbmBuilder()
-                .setClock(new OffsettableClock())
-                .setHdrBrightnessConfig(mHdrBrightnessDeviceConfigMock)
-                .build();
-
-        // Passthrough return the max desired hdr/sdr ratio
-        when(mHdrBrightnessDeviceConfigMock.getHdrBrightnessFromSdr(anyFloat(), anyFloat()))
-                .thenAnswer(i -> i.getArgument(1));
-
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 2.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(2.0f, hbmc.getHdrBrightnessValue(), EPSILON);
-
-        // The hdr ratio cannot be less than 1.
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 0.5f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(1.0f, hbmc.getHdrBrightnessValue(), EPSILON);
-
-        // The hdr ratio can be as much as positive infinity
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/,
-                Float.POSITIVE_INFINITY /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(Float.POSITIVE_INFINITY, hbmc.getHdrBrightnessValue(), 0.0);
-    }
-
-
-    @Test
-    public void testHdrTrumpsSunlight() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-
-        // Turn on sunlight
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
-        assertEquals(DEFAULT_MAX, hbmc.getCurrentBrightnessMax(), EPSILON);
-
-        // turn on hdr
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
-        assertEquals(TRANSITION_POINT, hbmc.getCurrentBrightnessMax(), EPSILON);
-    }
-
-    @Test
-    public void testHdrBrightnessLimitSameAsNormalLimit() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-
-        // Check limit when HBM is off
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
-        assertEquals(TRANSITION_POINT, hbmc.getCurrentBrightnessMax(), EPSILON);
-
-        // Check limit with HBM is set to HDR
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_OFF, hbmc.getHighBrightnessMode());
-        assertEquals(TRANSITION_POINT, hbmc.getCurrentBrightnessMax(), EPSILON);
-    }
-
-    @Test
-    public void testHdrBrightnessScaledNormalBrightness() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
-
-        // verify things are scaled for 0.5f
-        float brightness = 0.5f;
-        float expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT,
-                DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range
-        hbmcOnBrightnessChanged(hbmc, brightness);
-        advanceTime(0);
-        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
-
-        // Try another value
-        brightness = 0.33f;
-        expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT,
-                DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range
-        hbmcOnBrightnessChanged(hbmc, brightness);
-        advanceTime(0);
-        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
-
-        // Try the min value
-        brightness = DEFAULT_MIN;
-        expectedHdrBrightness = DEFAULT_MIN;
-        hbmcOnBrightnessChanged(hbmc, brightness);
-        advanceTime(0);
-        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
-
-        // Try the max value
-        brightness = TRANSITION_POINT;
-        expectedHdrBrightness = DEFAULT_MAX;
-        hbmcOnBrightnessChanged(hbmc, brightness);
-        advanceTime(0);
-        assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON);
-    }
-
-    @Test
-    public void testHbmStats_StateChange() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT);
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
-
-        // Verify Stats HBM_ON_HDR
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/,
-                0, 0, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-
-        // Verify Stats HBM_OFF
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-
-        // Verify Stats HBM_ON_SUNLIGHT
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        hbmc.onAmbientLuxChange(1);
-        advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1);
-
-        // Verify Stats HBM_OFF
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP));
-    }
-
-    @Test
-    public void testHbmStats_NbmHdrNoReport() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-        assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode());
-
-        // Verify Stats HBM_ON_HDR not report
-        verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
-            anyInt());
-    }
-
-    @Test
-    public void testHbmStats_HighLuxLowBrightnessNoReport() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
-        advanceTime(0);
-        // verify in HBM sunlight mode
-        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
-
-        // Verify Stats HBM_ON_SUNLIGHT not report
-        verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            anyInt());
-    }
-
-    // Test reporting of thermal throttling when triggered externally through
-    // HighBrightnessModeController.onBrightnessChanged()
-    @Test
-    public void testHbmStats_ExternalThermalOff() throws Exception {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-        final float hbmBrightness = TRANSITION_POINT + 0.01f;
-        final float nbmBrightness = TRANSITION_POINT - 0.01f;
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        // Brightness is unthrottled, HBM brightness granted
-        hbmc.onBrightnessChanged(hbmBrightness, hbmBrightness,
-                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
-        advanceTime(1);
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        // Brightness is thermally throttled, HBM brightness denied (NBM brightness granted)
-        hbmc.onBrightnessChanged(nbmBrightness, hbmBrightness,
-                BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL);
-        advanceTime(1);
-        // We expect HBM mode to remain set to sunlight, indicating that HBMC *allows* this mode.
-        // However, we expect the HBM state reported by HBMC to be off, since external thermal
-        // throttling (reported to HBMC through onBrightnessChanged()) lowers brightness to below
-        // the HBM transition point.
-        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT));
-    }
-
-    @Test
-    public void testHbmStats_TimeOut() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(0);
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        // Use up all the time in the window.
-        advanceTime(TIME_WINDOW_MILLIS + 1);
-
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT));
-    }
-
-    @Test
-    public void testHbmStats_DisplayOff() {
-        final HighBrightnessModeController hbmc = createDefaultHbm();
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(0);
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE);
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF));
-    }
-
-    @Test
-    public void testHbmStats_HdrPlaying() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(0);
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/);
-        advanceTime(0);
-
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING));
-    }
-
-    @Test
-    public void tetHbmStats_LowRequestedBrightness() {
-        final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock());
-        final int displayStatsId = mDisplayUniqueId.hashCode();
-
-        hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED);
-        hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f);
-        advanceTime(0);
-        // verify in HBM sunlight mode
-        assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode());
-        // verify HBM_ON_SUNLIGHT
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN));
-
-        hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN);
-        // verify HBM_SV_OFF due to LOW_REQUESTED_BRIGHTNESS
-        verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId),
-            eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF),
-            eq(FrameworkStatsLog
-                      .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS));
-    }
-
-    private void assertState(HighBrightnessModeController hbmc,
-            float brightnessMin, float brightnessMax, int hbmMode) {
-        assertEquals(brightnessMin, hbmc.getCurrentBrightnessMin(), EPSILON);
-        assertEquals(brightnessMax, hbmc.getCurrentBrightnessMax(), EPSILON);
-        assertEquals(hbmMode, hbmc.getHighBrightnessMode());
-    }
-
-    private class TestHbmBuilder {
-        OffsettableClock mClock;
-        HighBrightnessModeController.HdrBrightnessDeviceConfig mHdrBrightnessCfg;
-
-        TestHbmBuilder setClock(OffsettableClock clock) {
-            mClock = clock;
-            return this;
-        }
-
-        TestHbmBuilder setHdrBrightnessConfig(
-                HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg
-        ) {
-            mHdrBrightnessCfg = hdrBrightnessCfg;
-            return this;
-        }
-
-        HighBrightnessModeController build() {
-            initHandler(mClock);
-            if (mHighBrightnessModeMetadata == null) {
-                mHighBrightnessModeMetadata = new HighBrightnessModeMetadata();
-            }
-            return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH,
-                    DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX,
-                    DEFAULT_HBM_DATA, mHdrBrightnessCfg, () -> {}, mHighBrightnessModeMetadata,
-                    mContextSpy);
-        }
-
-    }
-
-    private HighBrightnessModeController createDefaultHbm() {
-        return new TestHbmBuilder().build();
-    }
-
-    // Creates instance with standard initialization values.
-    private HighBrightnessModeController createDefaultHbm(OffsettableClock clock) {
-        return new TestHbmBuilder().setClock(clock).build();
-    }
-
-    private void initHandler(OffsettableClock clock) {
-        mClock = clock != null ? clock : new OffsettableClock.Stopped();
-        when(mInjectorMock.getClock()).thenReturn(mClock::now);
-        mTestLooper = new TestLooper(mClock::now);
-        mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() {
-            @Override
-            public boolean handleMessage(Message msg) {
-                return true;
-            }
-        });
-    }
-
-    private void advanceTime(long timeMs) {
-        mClock.fastForward(timeMs);
-        mTestLooper.dispatchAll();
-    }
-
-    private void hbmcOnBrightnessChanged(HighBrightnessModeController hbmc, float brightness) {
-        hbmc.onBrightnessChanged(brightness, brightness, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
deleted file mode 100644
index 1eec70d..0000000
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ /dev/null
@@ -1,981 +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.server.display;
-
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.DEFAULT_DISPLAY_GROUP;
-import static android.view.Display.FLAG_REAR;
-import static android.view.Display.TYPE_EXTERNAL;
-import static android.view.Display.TYPE_INTERNAL;
-import static android.view.Display.TYPE_VIRTUAL;
-
-import static com.android.server.display.DeviceStateToLayoutMap.STATE_DEFAULT;
-import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED;
-import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED;
-import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
-import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
-import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
-import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
-import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
-import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.PropertyInvalidatedCache;
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.Handler;
-import android.os.IPowerManager;
-import android.os.IThermalService;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.test.TestLooper;
-import android.view.Display;
-import android.view.DisplayAddress;
-import android.view.DisplayInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.server.display.layout.DisplayIdProducer;
-import com.android.server.display.layout.Layout;
-
-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.Spy;
-
-import java.io.File;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Arrays;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class LogicalDisplayMapperTest {
-    private static int sUniqueTestDisplayId = 0;
-    private static final int DEVICE_STATE_CLOSED = 0;
-    private static final int DEVICE_STATE_OPEN = 2;
-    private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
-    private static final File NON_EXISTING_FILE = new File("/non_existing_folder/should_not_exist");
-
-    private DisplayDeviceRepository mDisplayDeviceRepo;
-    private LogicalDisplayMapper mLogicalDisplayMapper;
-    private TestLooper mLooper;
-    private Handler mHandler;
-    private PowerManager mPowerManager;
-
-    private final DisplayIdProducer mIdProducer = (isDefault) ->
-            isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
-
-    @Mock LogicalDisplayMapper.Listener mListenerMock;
-    @Mock Context mContextMock;
-    @Mock Resources mResourcesMock;
-    @Mock IPowerManager mIPowerManagerMock;
-    @Mock IThermalService mIThermalServiceMock;
-    @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy =
-            new DeviceStateToLayoutMap(mIdProducer, NON_EXISTING_FILE);
-
-    @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
-
-    @Before
-    public void setUp() {
-        // Share classloader to allow package private access.
-        System.setProperty("dexmaker.share_classloader", "true");
-        MockitoAnnotations.initMocks(this);
-
-        mDisplayDeviceRepo = new DisplayDeviceRepository(
-                new DisplayManagerService.SyncRoot(),
-                new PersistentDataStore(new PersistentDataStore.Injector() {
-                    @Override
-                    public InputStream openRead() {
-                        return null;
-                    }
-
-                    @Override
-                    public OutputStream startWrite() {
-                        return null;
-                    }
-
-                    @Override
-                    public void finishWrite(OutputStream os, boolean success) {}
-                }));
-
-        // Disable binder caches in this process.
-        PropertyInvalidatedCache.disableForTestMode();
-
-        mPowerManager = new PowerManager(mContextMock, mIPowerManagerMock, mIThermalServiceMock,
-                null);
-
-        when(mContextMock.getSystemServiceName(PowerManager.class))
-                .thenReturn(Context.POWER_SERVICE);
-        when(mContextMock.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
-        when(mContextMock.getResources()).thenReturn(mResourcesMock);
-        when(mResourcesMock.getBoolean(
-                com.android.internal.R.bool.config_supportsConcurrentInternalDisplays))
-                .thenReturn(true);
-        when(mResourcesMock.getIntArray(
-                com.android.internal.R.array.config_deviceStatesOnWhichToWakeUp))
-                .thenReturn(new int[]{1, 2});
-        when(mResourcesMock.getIntArray(
-                com.android.internal.R.array.config_deviceStatesOnWhichToSleep))
-                .thenReturn(new int[]{0});
-
-        mLooper = new TestLooper();
-        mHandler = new Handler(mLooper.getLooper());
-        mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
-                mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
-                mDeviceStateToLayoutMapSpy);
-    }
-
-
-    /////////////////
-    // Test Methods
-    /////////////////
-
-    @Test
-    public void testDisplayDeviceAddAndRemove_Internal() {
-        DisplayDevice device = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        // add
-        LogicalDisplay displayAdded = add(device);
-        assertEquals(info(displayAdded).address, info(device).address);
-        assertEquals(DEFAULT_DISPLAY, id(displayAdded));
-
-        // remove
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
-        verify(mListenerMock).onLogicalDisplayEventLocked(
-                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
-        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
-        assertEquals(DEFAULT_DISPLAY, id(displayRemoved));
-        assertEquals(displayAdded, displayRemoved);
-    }
-
-    @Test
-    public void testDisplayDeviceAddAndRemove_NonInternalTypes() {
-        testDisplayDeviceAddAndRemove_NonInternal(TYPE_EXTERNAL);
-        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_WIFI);
-        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_OVERLAY);
-        testDisplayDeviceAddAndRemove_NonInternal(TYPE_VIRTUAL);
-        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_UNKNOWN);
-
-        // Call the internal test again, just to verify that adding non-internal displays
-        // doesn't affect the ability for an internal display to become the default display.
-        testDisplayDeviceAddAndRemove_Internal();
-    }
-
-    @Test
-    public void testDisplayDeviceAdd_TwoInternalOneDefault() {
-        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800, 0);
-        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        LogicalDisplay display1 = add(device1);
-        assertEquals(info(display1).address, info(device1).address);
-        assertNotEquals(DEFAULT_DISPLAY, id(display1));
-
-        LogicalDisplay display2 = add(device2);
-        assertEquals(info(display2).address, info(device2).address);
-        assertEquals(DEFAULT_DISPLAY, id(display2));
-    }
-
-    @Test
-    public void testDisplayDeviceAdd_TwoInternalBothDefault() {
-        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        LogicalDisplay display1 = add(device1);
-        assertEquals(info(display1).address, info(device1).address);
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-
-        LogicalDisplay display2 = add(device2);
-        assertEquals(info(display2).address, info(device2).address);
-        // Despite the flags, we can only have one default display
-        assertNotEquals(DEFAULT_DISPLAY, id(display2));
-    }
-
-    @Test
-    public void testDisplayDeviceAddAndRemove_OneExternalDefault() {
-        DisplayDevice device = createDisplayDevice(TYPE_EXTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        // add
-        LogicalDisplay displayAdded = add(device);
-        assertEquals(info(displayAdded).address, info(device).address);
-        assertEquals(Display.DEFAULT_DISPLAY, id(displayAdded));
-
-        // remove
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
-        verify(mListenerMock).onLogicalDisplayEventLocked(
-                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
-        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
-        assertEquals(DEFAULT_DISPLAY, id(displayRemoved));
-        assertEquals(displayAdded, displayRemoved);
-    }
-
-    @Test
-    public void testDisplayDeviceAddAndRemove_SwitchDefault() {
-        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        LogicalDisplay display1 = add(device1);
-        assertEquals(info(display1).address, info(device1).address);
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-
-        LogicalDisplay display2 = add(device2);
-        assertEquals(info(display2).address, info(device2).address);
-        // We can only have one default display
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-
-        // remove
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device1, DISPLAY_DEVICE_EVENT_REMOVED);
-
-        verify(mListenerMock).onLogicalDisplayEventLocked(
-                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
-        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
-        // Display 1 is still the default logical display
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-        // The logical displays had their devices swapped and Display 2 was removed
-        assertEquals(display2, displayRemoved);
-        assertEquals(info(display1).address, info(device2).address);
-    }
-
-    @Test
-    public void testGetDisplayIdsLocked() {
-        add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        add(createDisplayDevice(TYPE_EXTERNAL, 600, 800, 0));
-        add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
-
-        int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID,
-                /* includeDisabled= */ true);
-        assertEquals(3, ids.length);
-        Arrays.sort(ids);
-        assertEquals(DEFAULT_DISPLAY, ids[0]);
-    }
-
-    @Test
-    public void testGetDisplayInfoForStateLocked_defaultLayout() {
-        final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        add(device1);
-        add(device2);
-
-        Layout layout1 = new Layout();
-        createDefaultDisplay(layout1, device1);
-        createNonDefaultDisplay(layout1, device2, /* enabled= */ true, /* group= */ null);
-        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
-        assertThat(layout1.size()).isEqualTo(2);
-        final int logicalId2 = layout1.getByAddress(info(device2).address).getLogicalDisplayId();
-
-        final DisplayInfo displayInfoDefault = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                STATE_DEFAULT, DEFAULT_DISPLAY);
-        assertThat(displayInfoDefault.displayId).isEqualTo(DEFAULT_DISPLAY);
-        assertThat(displayInfoDefault.logicalWidth).isEqualTo(width(device1));
-        assertThat(displayInfoDefault.logicalHeight).isEqualTo(height(device1));
-
-        final DisplayInfo displayInfoOther = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                STATE_DEFAULT, logicalId2);
-        assertThat(displayInfoOther).isNotNull();
-        assertThat(displayInfoOther.displayId).isEqualTo(logicalId2);
-        assertThat(displayInfoOther.logicalWidth).isEqualTo(width(device2));
-        assertThat(displayInfoOther.logicalHeight).isEqualTo(height(device2));
-    }
-
-    @Test
-    public void testGetDisplayInfoForStateLocked_multipleLayouts() {
-        final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        final DisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 700, 800,
-                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
-
-        add(device1);
-        add(device2);
-        add(device3);
-
-        Layout layout1 = new Layout();
-        createDefaultDisplay(layout1, device1);
-        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
-
-        final int layoutState2 = 2;
-        Layout layout2 = new Layout();
-        createNonDefaultDisplay(layout2, device2, /* enabled= */ true, /* group= */ null);
-        // Device3 is the default display.
-        createDefaultDisplay(layout2, device3);
-        when(mDeviceStateToLayoutMapSpy.get(layoutState2)).thenReturn(layout2);
-        assertThat(layout2.size()).isEqualTo(2);
-        final int logicalId2 = layout2.getByAddress(info(device2).address).getLogicalDisplayId();
-
-        // Default layout.
-        final DisplayInfo displayInfoLayout1Default =
-                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                        STATE_DEFAULT, DEFAULT_DISPLAY);
-        assertThat(displayInfoLayout1Default.displayId).isEqualTo(DEFAULT_DISPLAY);
-        assertThat(displayInfoLayout1Default.logicalWidth).isEqualTo(width(device1));
-        assertThat(displayInfoLayout1Default.logicalHeight).isEqualTo(height(device1));
-
-        // Second layout, where device3 is the default display.
-        final DisplayInfo displayInfoLayout2Default =
-                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                        layoutState2, DEFAULT_DISPLAY);
-        assertThat(displayInfoLayout2Default.displayId).isEqualTo(DEFAULT_DISPLAY);
-        assertThat(displayInfoLayout2Default.logicalWidth).isEqualTo(width(device3));
-        assertThat(displayInfoLayout2Default.logicalHeight).isEqualTo(height(device3));
-
-        final DisplayInfo displayInfoLayout2Other =
-                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                        layoutState2, logicalId2);
-        assertThat(displayInfoLayout2Other).isNotNull();
-        assertThat(displayInfoLayout2Other.displayId).isEqualTo(logicalId2);
-        assertThat(displayInfoLayout2Other.logicalWidth).isEqualTo(width(device2));
-        assertThat(displayInfoLayout2Other.logicalHeight).isEqualTo(height(device2));
-    }
-
-    @Test
-    public void testGetDisplayInfoForStateLocked_multipleDisplayGroups() {
-        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device3 = createDisplayDevice(TYPE_INTERNAL, 700, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device4 = createDisplayDevice(TYPE_INTERNAL, 400, 600,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        Layout layout = new Layout();
-        createDefaultDisplay(layout, device1);
-        createNonDefaultDisplay(layout, device2, /* enabled= */  true, /* group= */ "group1");
-        createNonDefaultDisplay(layout, device3, /* enabled= */  true, /* group= */ "group1");
-        createNonDefaultDisplay(layout, device4, /* enabled= */  true, /* group= */ "group2");
-
-        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout);
-
-        LogicalDisplay display1 = add(device1);
-        LogicalDisplay display2 = add(device2);
-        LogicalDisplay display3 = add(device3);
-        LogicalDisplay display4 = add(device4);
-
-        int displayGroupId1 =
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1));
-        int displayGroupId2 =
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2));
-        int displayGroupId3 =
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3));
-        int displayGroupId4 =
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display4));
-        assertThat(displayGroupId1).isEqualTo(DEFAULT_DISPLAY_GROUP);
-        assertThat(displayGroupId2).isNotEqualTo(DEFAULT_DISPLAY_GROUP);
-        assertThat(displayGroupId2).isEqualTo(displayGroupId3);
-        assertThat(displayGroupId3).isNotEqualTo(DEFAULT_DISPLAY_GROUP);
-        assertThat(displayGroupId2).isNotEqualTo(displayGroupId4);
-    }
-
-    @Test
-    public void testSingleDisplayGroup() {
-        LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
-        LogicalDisplay display3 = add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
-
-        assertEquals(DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
-        assertEquals(DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
-        assertEquals(DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
-    }
-
-    @Test
-    public void testMultipleDisplayGroups() {
-        LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
-
-
-        TestDisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 600, 800,
-                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
-        LogicalDisplay display3 = add(device3);
-
-        assertEquals(DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
-        assertEquals(DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2)));
-        assertNotEquals(DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
-
-        // Now switch it back to the default group by removing the flag and issuing an update
-        DisplayDeviceInfo info = device3.getSourceInfo();
-        info.flags = info.flags & ~DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
-
-        // Verify the new group is correct.
-        assertEquals(DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3)));
-    }
-
-    @Test
-    public void testDevicesAreAddedToDeviceDisplayGroups() {
-        // Create the default internal display of the device.
-        LogicalDisplay defaultDisplay =
-                add(
-                        createDisplayDevice(
-                                Display.TYPE_INTERNAL,
-                                600,
-                                800,
-                                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-
-        // Create 3 virtual displays associated with a first virtual device.
-        int deviceId1 = 1;
-        TestDisplayDevice display1 =
-                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display1", 600, 800, 0);
-        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display1, deviceId1);
-        LogicalDisplay virtualDevice1Display1 = add(display1);
-
-        TestDisplayDevice display2 =
-                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display2", 600, 800, 0);
-        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display2, deviceId1);
-        LogicalDisplay virtualDevice1Display2 = add(display2);
-
-        TestDisplayDevice display3 =
-                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice1Display3", 600, 800, 0);
-        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display3, deviceId1);
-        LogicalDisplay virtualDevice1Display3 = add(display3);
-
-        // Create another 3 virtual displays associated with a second virtual device.
-        int deviceId2 = 2;
-        TestDisplayDevice display4 =
-                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice2Display1", 600, 800, 0);
-        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display4, deviceId2);
-        LogicalDisplay virtualDevice2Display1 = add(display4);
-
-        TestDisplayDevice display5 =
-                createDisplayDevice(Display.TYPE_VIRTUAL, "virtualDevice2Display2", 600, 800, 0);
-        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display5, deviceId2);
-        LogicalDisplay virtualDevice2Display2 = add(display5);
-
-        // The final display is created with FLAG_OWN_DISPLAY_GROUP set.
-        TestDisplayDevice display6 =
-                createDisplayDevice(
-                        Display.TYPE_VIRTUAL,
-                        "virtualDevice2Display3",
-                        600,
-                        800,
-                        DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
-        mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(display6, deviceId2);
-        LogicalDisplay virtualDevice2Display3 = add(display6);
-
-        // Verify that the internal display is in the default display group.
-        assertEquals(
-                DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(defaultDisplay)));
-
-        // Verify that all the displays for virtual device 1 are in the same (non-default) display
-        // group.
-        int virtualDevice1DisplayGroupId =
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
-                        id(virtualDevice1Display1));
-        assertNotEquals(DEFAULT_DISPLAY_GROUP, virtualDevice1DisplayGroupId);
-        assertEquals(
-                virtualDevice1DisplayGroupId,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
-                        id(virtualDevice1Display2)));
-        assertEquals(
-                virtualDevice1DisplayGroupId,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
-                        id(virtualDevice1Display3)));
-
-        // The first 2 displays for virtual device 2 should be in the same non-default group.
-        int virtualDevice2DisplayGroupId =
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
-                        id(virtualDevice2Display1));
-        assertNotEquals(DEFAULT_DISPLAY_GROUP, virtualDevice2DisplayGroupId);
-        assertEquals(
-                virtualDevice2DisplayGroupId,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
-                        id(virtualDevice2Display2)));
-        // virtualDevice2Display3 was created with FLAG_OWN_DISPLAY_GROUP and shouldn't be grouped
-        // with other displays of this device or be in the default display group.
-        assertNotEquals(
-                virtualDevice2DisplayGroupId,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
-                        id(virtualDevice2Display3)));
-        assertNotEquals(
-                DEFAULT_DISPLAY_GROUP,
-                mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(
-                        id(virtualDevice2Display3)));
-    }
-
-    @Test
-    public void testDeviceShouldBeWoken() {
-        assertTrue(mLogicalDisplayMapper.shouldDeviceBeWoken(DEVICE_STATE_OPEN,
-                DEVICE_STATE_CLOSED,
-                /* isInteractive= */false,
-                /* isBootCompleted= */true));
-    }
-
-    @Test
-    public void testDeviceShouldNotBeWoken() {
-        assertFalse(mLogicalDisplayMapper.shouldDeviceBeWoken(DEVICE_STATE_CLOSED,
-                DEVICE_STATE_OPEN,
-                /* isInteractive= */false,
-                /* isBootCompleted= */true));
-    }
-
-    @Test
-    public void testDeviceShouldBePutToSleep() {
-        assertTrue(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
-                DEVICE_STATE_OPEN,
-                /* isOverrideActive= */false,
-                /* isInteractive= */true,
-                /* isBootCompleted= */true));
-    }
-
-    @Test
-    public void testDeviceShouldNotBePutToSleep() {
-        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_OPEN,
-                DEVICE_STATE_CLOSED,
-                /* isOverrideActive= */false,
-                /* isInteractive= */true,
-                /* isBootCompleted= */true));
-        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
-                INVALID_DEVICE_STATE,
-                /* isOverrideActive= */false,
-                /* isInteractive= */true,
-                /* isBootCompleted= */true));
-    }
-
-    @Test
-    public void testDeviceShouldNotBePutToSleepDifferentBaseState() {
-        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
-                DEVICE_STATE_OPEN,
-                /* isOverrideActive= */true,
-                /* isInteractive= */true,
-                /* isBootCompleted= */true));
-    }
-
-    @Test
-    public void testDeviceStateLocked() {
-        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
-        Layout layout = new Layout();
-        layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
-                /* isDefault= */ true, /* isEnabled= */ true, /* displayGroup= */ null,
-                mIdProducer, POSITION_UNKNOWN,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY,
-                /* brightnessThrottlingMapId= */ "concurrent",
-                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
-        layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
-                /* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null,
-                mIdProducer, POSITION_UNKNOWN,
-                /* leadDisplayId= */ Display.DEFAULT_DISPLAY,
-                /* brightnessThrottlingMapId= */ "concurrent",
-                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null);
-        when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
-
-        layout = new Layout();
-        createNonDefaultDisplay(layout, device1, /* enabled= */ false, /* group= */ null);
-        createDefaultDisplay(layout, device2);
-        when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout);
-        when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout);
-
-        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(4);
-
-        LogicalDisplay display1 = add(device1);
-        assertEquals(info(display1).address, info(device1).address);
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-
-        LogicalDisplay display2 = add(device2);
-        assertEquals(info(display2).address, info(device2).address);
-        // We can only have one default display
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-
-        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
-        advanceTime(1000);
-        // The new state is not applied until the boot is completed
-        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
-
-        mLogicalDisplayMapper.onBootCompleted();
-        advanceTime(1000);
-        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
-        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
-        assertEquals(-1, mLogicalDisplayMapper.getDisplayLocked(device1)
-                .getLeadDisplayIdLocked());
-        assertEquals(0, mLogicalDisplayMapper.getDisplayLocked(device2)
-                .getLeadDisplayIdLocked());
-        assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device1)
-                .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
-        assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2)
-                .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
-
-        mLogicalDisplayMapper.setDeviceStateLocked(1, false);
-        advanceTime(1000);
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
-        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
-        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
-                mLogicalDisplayMapper.getDisplayLocked(device1)
-                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
-        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
-                mLogicalDisplayMapper.getDisplayLocked(device2)
-                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
-
-        mLogicalDisplayMapper.setDeviceStateLocked(2, false);
-        advanceTime(1000);
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
-        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device1).isInTransitionLocked());
-        assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked());
-        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
-                mLogicalDisplayMapper.getDisplayLocked(device1)
-                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
-        assertEquals(DisplayDeviceConfig.DEFAULT_ID,
-                mLogicalDisplayMapper.getDisplayLocked(device2)
-                        .getDisplayInfoLocked().thermalBrightnessThrottlingDataId);
-    }
-
-    @Test
-    public void testEnabledAndDisabledDisplays() {
-        DisplayAddress displayAddressOne = new TestUtils.TestDisplayAddress();
-        DisplayAddress displayAddressTwo = new TestUtils.TestDisplayAddress();
-        DisplayAddress displayAddressThree = new TestUtils.TestDisplayAddress();
-
-        TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, "one",
-                TYPE_INTERNAL, 600, 800, DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, "two",
-                TYPE_INTERNAL, 200, 800, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
-        TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, "three",
-                TYPE_INTERNAL, 600, 900, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
-
-        Layout threeDevicesEnabledLayout = new Layout();
-        createDefaultDisplay(threeDevicesEnabledLayout, displayAddressOne);
-        createNonDefaultDisplay(threeDevicesEnabledLayout, displayAddressTwo,
-                /* enabled= */ true, /* group= */ null);
-        createNonDefaultDisplay(threeDevicesEnabledLayout, displayAddressThree,
-                /* enabled= */ true, /* group= */ null);
-
-        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT))
-                .thenReturn(threeDevicesEnabledLayout);
-
-        LogicalDisplay display1 = add(device1);
-        LogicalDisplay display2 = add(device2);
-        LogicalDisplay display3 = add(device3);
-
-        // ensure 3 displays are returned
-        int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID, false);
-        assertEquals(3, ids.length);
-        Arrays.sort(ids);
-        assertEquals(DEFAULT_DISPLAY, ids[0]);
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1,
-                /* includeDisabled= */ false));
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device2,
-                /* includeDisabled= */ false));
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device3,
-                /* includeDisabled= */ false));
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
-                threeDevicesEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(),
-                /* includeDisabled= */ false));
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
-                threeDevicesEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(),
-                /* includeDisabled= */ false));
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
-                threeDevicesEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(),
-                /* includeDisabled= */ false));
-
-        Layout oneDeviceEnabledLayout = new Layout();
-        createDefaultDisplay(oneDeviceEnabledLayout, displayAddressOne);
-        createNonDefaultDisplay(oneDeviceEnabledLayout, displayAddressTwo,
-                /* enabled= */ false, /* group= */ null);
-        createNonDefaultDisplay(oneDeviceEnabledLayout, displayAddressThree,
-                /* enabled= */ false, /* group= */ null);
-
-        when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout);
-        when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout);
-
-        // 1) Set the new state
-        // 2) Mark the displays as STATE_OFF so that it can continue with transition
-        // 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state
-        // 4) Dispatch handler events.
-        mLogicalDisplayMapper.onBootCompleted();
-        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
-        advanceTime(1000);
-        final int[] allDisplayIds = mLogicalDisplayMapper.getDisplayIdsLocked(
-                Process.SYSTEM_UID, false);
-        if (allDisplayIds.length != 1) {
-            throw new RuntimeException("Displays: \n"
-                    + mLogicalDisplayMapper.getDisplayLocked(device1).toString()
-                    + "\n" + mLogicalDisplayMapper.getDisplayLocked(device2).toString()
-                    + "\n" + mLogicalDisplayMapper.getDisplayLocked(device3).toString());
-        }
-        // ensure only one display is returned
-        assertEquals(1, allDisplayIds.length);
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(device1,
-                /* includeDisabled= */ false));
-        assertNull(mLogicalDisplayMapper.getDisplayLocked(device2,
-                /* includeDisabled= */ false));
-        assertNull(mLogicalDisplayMapper.getDisplayLocked(device3,
-                /* includeDisabled= */ false));
-        assertNotNull(mLogicalDisplayMapper.getDisplayLocked(
-                oneDeviceEnabledLayout.getByAddress(displayAddressOne).getLogicalDisplayId(),
-                /* includeDisabled= */ false));
-        assertNull(mLogicalDisplayMapper.getDisplayLocked(
-                oneDeviceEnabledLayout.getByAddress(displayAddressTwo).getLogicalDisplayId(),
-                /* includeDisabled= */ false));
-        assertNull(mLogicalDisplayMapper.getDisplayLocked(
-                oneDeviceEnabledLayout.getByAddress(displayAddressThree).getLogicalDisplayId(),
-                /* includeDisabled= */ false));
-
-        // Now do it again to go back to state 1
-        mLogicalDisplayMapper.setDeviceStateLocked(1, false);
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
-        advanceTime(1000);
-        final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked(
-                Process.SYSTEM_UID, false);
-
-        // ensure all three displays are returned
-        assertEquals(3, threeDisplaysEnabled.length);
-    }
-
-    @Test
-    public void testCreateNewLogicalDisplay() {
-        DisplayDevice device1 = createDisplayDevice(TYPE_EXTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
-        LogicalDisplay display1 = add(device1);
-
-        assertTrue(display1.isEnabledLocked());
-
-        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(2);
-        LogicalDisplay display2 = add(device2);
-
-        assertFalse(display2.isEnabledLocked());
-    }
-    @Test
-    public void testDisplayFlagRear() {
-        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
-                FLAG_REAR);
-
-        Layout layout = new Layout();
-        layout.createDefaultDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
-                mIdProducer);
-        layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
-                /* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null,
-                mIdProducer, POSITION_REAR, Display.DEFAULT_DISPLAY,
-                /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
-                /* refreshRateThermalThrottlingMapId= */null);
-        when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
-
-        when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1);
-
-        LogicalDisplay display1 = add(device1);
-        assertEquals(info(display1).address, info(device1).address);
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-
-        LogicalDisplay display2 = add(device2);
-        assertEquals(info(display2).address, info(device2).address);
-        // We can only have one default display
-        assertEquals(DEFAULT_DISPLAY, id(display1));
-
-        mLogicalDisplayMapper.setDeviceStateLocked(0, false);
-        advanceTime(1000);
-        mLogicalDisplayMapper.onBootCompleted();
-        advanceTime(1000);
-
-        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device1).isEnabledLocked());
-        assertTrue(mLogicalDisplayMapper.getDisplayLocked(device2).isEnabledLocked());
-
-        assertEquals(POSITION_UNKNOWN,
-                mLogicalDisplayMapper.getDisplayLocked(device1).getDevicePositionLocked());
-        assertEquals(POSITION_REAR,
-                mLogicalDisplayMapper.getDisplayLocked(device2).getDevicePositionLocked());
-    }
-
-    /////////////////
-    // Helper Methods
-    /////////////////
-
-    private void createDefaultDisplay(Layout layout, DisplayDevice device) {
-        createDefaultDisplay(layout, info(device).address);
-    }
-
-    private void createDefaultDisplay(Layout layout, DisplayAddress address) {
-        layout.createDefaultDisplayLocked(address, mIdProducer);
-    }
-
-    private void createNonDefaultDisplay(Layout layout, DisplayDevice device, boolean enabled,
-            String group) {
-        createNonDefaultDisplay(layout, info(device).address, enabled, group);
-    }
-
-    private void createNonDefaultDisplay(Layout layout, DisplayAddress address, boolean enabled,
-            String group) {
-        layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer,
-                Layout.Display.POSITION_UNKNOWN, Display.DEFAULT_DISPLAY,
-                /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null,
-                /* refreshRateThermalThrottlingMapId= */ null);
-    }
-
-    private void advanceTime(long timeMs) {
-        mLooper.moveTimeForward(1000);
-        mLooper.dispatchAll();
-    }
-
-    private TestDisplayDevice createDisplayDevice(int type, int width, int height, int flags) {
-        return createDisplayDevice(
-                new TestUtils.TestDisplayAddress(), /*  uniqueId */ "", type, width, height, flags);
-    }
-
-    private TestDisplayDevice createDisplayDevice(
-            int type, String uniqueId, int width, int height, int flags) {
-        return createDisplayDevice(
-                new TestUtils.TestDisplayAddress(), uniqueId, type, width, height, flags);
-    }
-
-    private TestDisplayDevice createDisplayDevice(
-            DisplayAddress address, String uniqueId, int type, int width, int height, int flags) {
-        TestDisplayDevice device = new TestDisplayDevice();
-        DisplayDeviceInfo displayDeviceInfo = device.getSourceInfo();
-        displayDeviceInfo.type = type;
-        displayDeviceInfo.uniqueId = uniqueId;
-        displayDeviceInfo.width = width;
-        displayDeviceInfo.height = height;
-        displayDeviceInfo.flags = flags;
-        displayDeviceInfo.supportedModes = new Display.Mode[1];
-        displayDeviceInfo.supportedModes[0] = new Display.Mode(1, width, height, 60f);
-        displayDeviceInfo.modeId = 1;
-        displayDeviceInfo.address = address;
-        return device;
-    }
-
-    private DisplayDeviceInfo info(DisplayDevice device) {
-        return device.getDisplayDeviceInfoLocked();
-    }
-
-    private int width(DisplayDevice device) {
-        return info(device).width;
-    }
-
-    private int height(DisplayDevice device) {
-        return info(device).height;
-    }
-
-    private DisplayInfo info(LogicalDisplay display) {
-        return display.getDisplayInfoLocked();
-    }
-
-    private int id(LogicalDisplay display) {
-        return display.getDisplayIdLocked();
-    }
-
-    private LogicalDisplay add(DisplayDevice device) {
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_ADDED);
-        ArgumentCaptor<LogicalDisplay> displayCaptor =
-                ArgumentCaptor.forClass(LogicalDisplay.class);
-        verify(mListenerMock).onLogicalDisplayEventLocked(
-                displayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_ADDED));
-        clearInvocations(mListenerMock);
-        return displayCaptor.getValue();
-    }
-
-    private void testDisplayDeviceAddAndRemove_NonInternal(int type) {
-        DisplayDevice device = createDisplayDevice(type, 600, 800, 0);
-
-        // add
-        LogicalDisplay displayAdded = add(device);
-        assertEquals(info(displayAdded).address, info(device).address);
-        assertNotEquals(DEFAULT_DISPLAY, id(displayAdded));
-
-        // remove
-        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
-        verify(mListenerMock).onLogicalDisplayEventLocked(
-                mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED));
-        LogicalDisplay displayRemoved = mDisplayCaptor.getValue();
-        assertNotEquals(DEFAULT_DISPLAY, id(displayRemoved));
-    }
-
-    class TestDisplayDevice extends DisplayDevice {
-        private DisplayDeviceInfo mInfo;
-        private DisplayDeviceInfo mSentInfo;
-        private int mState;
-
-        TestDisplayDevice() {
-            super(null, null, "test_display_" + sUniqueTestDisplayId++, mContextMock);
-            mInfo = new DisplayDeviceInfo();
-        }
-
-        @Override
-        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
-            if (mSentInfo == null) {
-                mSentInfo = new DisplayDeviceInfo();
-                mSentInfo.copyFrom(mInfo);
-            }
-            return mSentInfo;
-        }
-
-        @Override
-        public void applyPendingDisplayDeviceInfoChangesLocked() {
-            mSentInfo = null;
-        }
-
-        @Override
-        public boolean hasStableUniqueId() {
-            return true;
-        }
-
-        public DisplayDeviceInfo getSourceInfo() {
-            return mInfo;
-        }
-    }
-}
-
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
deleted file mode 100644
index 30ff8ba..0000000
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ /dev/null
@@ -1,287 +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.server.display;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.PropertyInvalidatedCache;
-import android.graphics.Point;
-import android.util.SparseArray;
-import android.view.Display;
-import android.view.DisplayInfo;
-import android.view.Surface;
-import android.view.SurfaceControl;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.server.display.layout.Layout;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-
-@SmallTest
-public class LogicalDisplayTest {
-    private static final int DISPLAY_ID = 0;
-    private static final int LAYER_STACK = 0;
-    private static final int DISPLAY_WIDTH = 100;
-    private static final int DISPLAY_HEIGHT = 200;
-    private static final int MODE_ID = 1;
-
-    private LogicalDisplay mLogicalDisplay;
-    private DisplayDevice mDisplayDevice;
-    private DisplayDeviceRepository mDeviceRepo;
-    private final DisplayDeviceInfo mDisplayDeviceInfo = new DisplayDeviceInfo();
-
-    @Before
-    public void setUp() {
-        // Share classloader to allow package private access.
-        System.setProperty("dexmaker.share_classloader", "true");
-        mDisplayDevice = mock(DisplayDevice.class);
-        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
-
-        mDisplayDeviceInfo.copyFrom(new DisplayDeviceInfo());
-        mDisplayDeviceInfo.width = DISPLAY_WIDTH;
-        mDisplayDeviceInfo.height = DISPLAY_HEIGHT;
-        mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
-        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
-        mDisplayDeviceInfo.modeId = MODE_ID;
-        mDisplayDeviceInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, /* refreshRate= */ 60)};
-        when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(mDisplayDeviceInfo);
-
-        // Disable binder caches in this process.
-        PropertyInvalidatedCache.disableForTestMode();
-
-        mDeviceRepo = new DisplayDeviceRepository(
-                new DisplayManagerService.SyncRoot(),
-                new PersistentDataStore(new PersistentDataStore.Injector() {
-                    @Override
-                    public InputStream openRead() {
-                        return null;
-                    }
-
-                    @Override
-                    public OutputStream startWrite() {
-                        return null;
-                    }
-
-                    @Override
-                    public void finishWrite(OutputStream os, boolean success) {}
-                }));
-        mDeviceRepo.onDisplayDeviceEvent(mDisplayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-    }
-
-    @Test
-    public void testGetDisplayPosition() {
-        Point expectedPosition = new Point();
-
-        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
-
-        expectedPosition.set(20, 40);
-        mLogicalDisplay.setDisplayOffsetsLocked(20, 40);
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
-
-        expectedPosition.set(40, -20);
-        DisplayInfo displayInfo = new DisplayInfo();
-        displayInfo.logicalWidth = DISPLAY_HEIGHT;
-        displayInfo.logicalHeight = DISPLAY_WIDTH;
-        displayInfo.rotation = Surface.ROTATION_90;
-        mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
-    }
-
-    @Test
-    public void testDisplayInputFlags() {
-        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
-        reset(t);
-
-        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        verify(t).setDisplayFlags(any(), eq(0));
-        reset(t);
-
-        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_VIRTUAL;
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
-        reset(t);
-
-        mLogicalDisplay.setEnabledLocked(false);
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        verify(t).setDisplayFlags(any(), eq(0));
-        reset(t);
-
-        mLogicalDisplay.setEnabledLocked(true);
-        mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
-        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
-        verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
-        reset(t);
-    }
-
-    @Test
-    public void testRearDisplaysArePresentationDisplaysThatDestroyContentOnRemoval() {
-        // Assert that the display isn't a presentation display by default, with a default remove
-        // mode
-        assertEquals(0, mLogicalDisplay.getDisplayInfoLocked().flags);
-        assertEquals(Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY,
-                mLogicalDisplay.getDisplayInfoLocked().removeMode);
-
-        // Update position and test to see that it's been updated to a rear, presentation display
-        // that destroys content on removal
-        mLogicalDisplay.setDevicePositionLocked(Layout.Display.POSITION_REAR);
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        assertEquals(Display.FLAG_REAR | Display.FLAG_PRESENTATION,
-                mLogicalDisplay.getDisplayInfoLocked().flags);
-        assertEquals(Display.REMOVE_MODE_DESTROY_CONTENT,
-                mLogicalDisplay.getDisplayInfoLocked().removeMode);
-
-        // And then check the unsetting the position resets both
-        mLogicalDisplay.setDevicePositionLocked(Layout.Display.POSITION_UNKNOWN);
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        assertEquals(0, mLogicalDisplay.getDisplayInfoLocked().flags);
-        assertEquals(Display.REMOVE_MODE_MOVE_CONTENT_TO_PRIMARY,
-                mLogicalDisplay.getDisplayInfoLocked().removeMode);
-    }
-
-    @Test
-    public void testUpdateLayoutLimitedRefreshRate() {
-        SurfaceControl.RefreshRateRange layoutLimitedRefreshRate =
-                new SurfaceControl.RefreshRateRange(0, 120);
-        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
-        mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(layoutLimitedRefreshRate);
-        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
-        // Display info should only be updated when updateLocked is called
-        assertEquals(info2, info1);
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
-        assertNotEquals(info3, info2);
-        assertEquals(layoutLimitedRefreshRate, info3.layoutLimitedRefreshRate);
-    }
-
-    @Test
-    public void testUpdateLayoutLimitedRefreshRate_setsDirtyFlag() {
-        SurfaceControl.RefreshRateRange layoutLimitedRefreshRate =
-                new SurfaceControl.RefreshRateRange(0, 120);
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(layoutLimitedRefreshRate);
-        assertTrue(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-    }
-
-    @Test
-    public void testUpdateRefreshRateThermalThrottling() {
-        SparseArray<SurfaceControl.RefreshRateRange> refreshRanges = new SparseArray<>();
-        refreshRanges.put(0, new SurfaceControl.RefreshRateRange(0, 120));
-        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
-        mLogicalDisplay.updateThermalRefreshRateThrottling(refreshRanges);
-        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
-        // Display info should only be updated when updateLocked is called
-        assertEquals(info2, info1);
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
-        assertNotEquals(info3, info2);
-        assertTrue(refreshRanges.contentEquals(info3.thermalRefreshRateThrottling));
-    }
-
-    @Test
-    public void testUpdateRefreshRateThermalThrottling_setsDirtyFlag() {
-        SparseArray<SurfaceControl.RefreshRateRange> refreshRanges = new SparseArray<>();
-        refreshRanges.put(0, new SurfaceControl.RefreshRateRange(0, 120));
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.updateThermalRefreshRateThrottling(refreshRanges);
-        assertTrue(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-    }
-
-    @Test
-    public void testUpdateDisplayGroupIdLocked() {
-        int newId = 999;
-        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
-        mLogicalDisplay.updateDisplayGroupIdLocked(newId);
-        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
-        // Display info should only be updated when updateLocked is called
-        assertEquals(info2, info1);
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
-        assertNotEquals(info3, info2);
-        assertEquals(newId, info3.displayGroupId);
-    }
-
-    @Test
-    public void testUpdateDisplayGroupIdLocked_setsDirtyFlag() {
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.updateDisplayGroupIdLocked(99);
-        assertTrue(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-    }
-
-    @Test
-    public void testSetThermalBrightnessThrottlingDataId() {
-        String brightnessThrottlingDataId = "throttling_data_id";
-        DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
-        mLogicalDisplay.setThermalBrightnessThrottlingDataIdLocked(brightnessThrottlingDataId);
-        DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
-        // Display info should only be updated when updateLocked is called
-        assertEquals(info2, info1);
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
-        assertNotEquals(info3, info2);
-        assertEquals(brightnessThrottlingDataId, info3.thermalBrightnessThrottlingDataId);
-    }
-
-    @Test
-    public void testSetThermalBrightnessThrottlingDataId_setsDirtyFlag() {
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.setThermalBrightnessThrottlingDataIdLocked("99");
-        assertTrue(mLogicalDisplay.isDirtyLocked());
-
-        mLogicalDisplay.updateLocked(mDeviceRepo);
-        assertFalse(mLogicalDisplay.isDirtyLocked());
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
deleted file mode 100644
index 817b245..0000000
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Copyright 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.display;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.hardware.display.BrightnessConfiguration;
-import android.os.Handler;
-import android.os.test.TestLooper;
-import android.util.Pair;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class PersistentDataStoreTest {
-    private PersistentDataStore mDataStore;
-    private TestInjector mInjector;
-    private TestLooper mTestLooper;
-
-    @Before
-    public void setUp() {
-        mInjector = new TestInjector();
-        mTestLooper = new TestLooper();
-        Handler handler = new Handler(mTestLooper.getLooper());
-        mDataStore = new PersistentDataStore(mInjector, handler);
-    }
-
-    @Test
-    public void testLoadingBrightnessConfigurations() {
-        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<display-manager-state>\n"
-                + "  <brightness-configurations>\n"
-                + "    <brightness-configuration"
-                + "         user-serial=\"1\""
-                + "         package-name=\"example.com\""
-                + "         timestamp=\"123456\">\n"
-                + "      <brightness-curve description=\"something\">\n"
-                + "        <brightness-point lux=\"0\" nits=\"13.25\"/>\n"
-                + "        <brightness-point lux=\"25\" nits=\"35.94\"/>\n"
-                + "      </brightness-curve>\n"
-                + "    </brightness-configuration>\n"
-                + "    <brightness-configuration user-serial=\"3\">\n"
-                + "      <brightness-curve>\n"
-                + "        <brightness-point lux=\"0\" nits=\"13.25\"/>\n"
-                + "        <brightness-point lux=\"10.2\" nits=\"15\"/>\n"
-                + "      </brightness-curve>\n"
-                + "    </brightness-configuration>\n"
-                + "  </brightness-configurations>\n"
-                + "</display-manager-state>\n";
-        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
-        mInjector.setReadStream(is);
-        mDataStore.loadIfNeeded();
-        BrightnessConfiguration config = mDataStore.getBrightnessConfiguration(1 /*userSerial*/);
-        Pair<float[], float[]> curve = config.getCurve();
-        float[] expectedLux = { 0f, 25f };
-        float[] expectedNits = { 13.25f, 35.94f };
-        assertArrayEquals(expectedLux, curve.first, "lux");
-        assertArrayEquals(expectedNits, curve.second, "nits");
-        assertEquals("something", config.getDescription());
-
-        config = mDataStore.getBrightnessConfiguration(3 /*userSerial*/);
-        curve = config.getCurve();
-        expectedLux = new float[] { 0f, 10.2f };
-        expectedNits = new float[] { 13.25f, 15f };
-        assertArrayEquals(expectedLux, curve.first, "lux");
-        assertArrayEquals(expectedNits, curve.second, "nits");
-        assertNull(config.getDescription());
-    }
-
-    @Test
-    public void testBrightnessConfigWithInvalidCurveIsIgnored() {
-        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<display-manager-state>\n"
-                + "  <brightness-configurations>\n"
-                + "    <brightness-configuration user-serial=\"0\">\n"
-                + "      <brightness-curve>\n"
-                + "        <brightness-point lux=\"1\" nits=\"13.25\"/>\n"
-                + "        <brightness-point lux=\"25\" nits=\"35.94\"/>\n"
-                + "      </brightness-curve>\n"
-                + "    </brightness-configuration>\n"
-                + "  </brightness-configurations>\n"
-                + "</display-manager-state>\n";
-        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
-        mInjector.setReadStream(is);
-        mDataStore.loadIfNeeded();
-        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
-    }
-
-    @Test
-    public void testBrightnessConfigWithInvalidFloatsIsIgnored() {
-        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<display-manager-state>\n"
-                + "  <brightness-configurations>\n"
-                + "    <brightness-configuration user-serial=\"0\">\n"
-                + "      <brightness-curve>\n"
-                + "        <brightness-point lux=\"0\" nits=\"13.25\"/>\n"
-                + "        <brightness-point lux=\"0xFF\" nits=\"foo\"/>\n"
-                + "      </brightness-curve>\n"
-                + "    </brightness-configuration>\n"
-                + "  </brightness-configurations>\n"
-                + "</display-manager-state>\n";
-        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
-        mInjector.setReadStream(is);
-        mDataStore.loadIfNeeded();
-        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
-    }
-
-    @Test
-    public void testEmptyBrightnessConfigurationsDoesntCrash() {
-        String contents = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<display-manager-state>\n"
-                + "  <brightness-configurations />\n"
-                + "</display-manager-state>\n";
-        InputStream is = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8));
-        mInjector.setReadStream(is);
-        mDataStore.loadIfNeeded();
-        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
-    }
-
-    @Test
-    public void testStoreAndReloadOfDisplayBrightnessConfigurations() throws InterruptedException {
-        final String uniqueDisplayId = "test:123";
-        int userSerial = 0;
-        String packageName = "pdsTestPackage";
-        final float[] lux = { 0f, 10f };
-        final float[] nits = {1f, 100f };
-        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
-                .setDescription("a description")
-                .build();
-        mDataStore.loadIfNeeded();
-        assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
-                userSerial));
-
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
-            @Override
-            public boolean hasStableUniqueId() {
-                return true;
-            }
-
-            @Override
-            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
-                return null;
-            }
-        };
-
-        mDataStore.setBrightnessConfigurationForDisplayLocked(config, testDisplayDevice, userSerial,
-                packageName);
-
-        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.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
-                userSerial));
-        assertEquals(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
-                userSerial), newDataStore.getBrightnessConfigurationForDisplayLocked(
-                        uniqueDisplayId, userSerial));
-    }
-
-    @Test
-    public void testSetBrightnessConfigurationFailsWithUnstableId() {
-        final String uniqueDisplayId = "test:123";
-        int userSerial = 0;
-        String packageName = "pdsTestPackage";
-        final float[] lux = { 0f, 10f };
-        final float[] nits = {1f, 100f };
-        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
-                .setDescription("a description")
-                .build();
-        mDataStore.loadIfNeeded();
-        assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
-                userSerial));
-
-        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
-            @Override
-            public boolean hasStableUniqueId() {
-                return false;
-            }
-
-            @Override
-            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
-                return null;
-            }
-        };
-
-        assertFalse(mDataStore.setBrightnessConfigurationForDisplayLocked(
-                config, testDisplayDevice, userSerial, packageName));
-    }
-
-    @Test
-    public void testStoreAndReloadOfBrightnessConfigurations() throws InterruptedException {
-        final float[] lux = { 0f, 10f };
-        final float[] nits = {1f, 100f };
-        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
-                .setDescription("a description")
-                .build();
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        String packageName = context.getPackageName();
-
-        mDataStore.loadIfNeeded();
-        assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
-        mDataStore.setBrightnessConfigurationForUser(config, 0, packageName);
-
-        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.getBrightnessConfiguration(0 /*userSerial*/));
-        assertEquals(mDataStore.getBrightnessConfiguration(0 /*userSerial*/),
-                newDataStore.getBrightnessConfiguration(0 /*userSerial*/));
-    }
-
-    @Test
-    public void testNullBrightnessConfiguration() {
-        final float[] lux = { 0f, 10f };
-        final float[] nits = {1f, 100f };
-        int userSerial = 0;
-        final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
-                .setDescription("a description")
-                .build();
-        mDataStore.loadIfNeeded();
-        assertNull(mDataStore.getBrightnessConfiguration(userSerial));
-
-        mDataStore.setBrightnessConfigurationForUser(config, userSerial, "packagename");
-        assertNotNull(mDataStore.getBrightnessConfiguration(userSerial));
-
-        mDataStore.setBrightnessConfigurationForUser(null, userSerial, "packagename");
-        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);
-    }
-
-    @Test
-    public void testBrightnessInitialisesWithInvalidFloat() {
-        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;
-            }
-        };
-
-        // Set any value which initialises Display state
-        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();
-        assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice)));
-    }
-
-    @Test
-    public void testStoreAndRestoreBrightnessNitsForDefaultDisplay() {
-        float brightnessNitsForDefaultDisplay = 190;
-        mDataStore.loadIfNeeded();
-        mDataStore.setBrightnessNitsForDefaultDisplay(brightnessNitsForDefaultDisplay);
-
-        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();
-        assertEquals(brightnessNitsForDefaultDisplay,
-                mDataStore.getBrightnessNitsForDefaultDisplay(), 0);
-        assertEquals(brightnessNitsForDefaultDisplay,
-                newDataStore.getBrightnessNitsForDefaultDisplay(), 0);
-    }
-
-    @Test
-    public void testInitialBrightnessNitsForDefaultDisplay() {
-        mDataStore.loadIfNeeded();
-        assertEquals(-1, mDataStore.getBrightnessNitsForDefaultDisplay(), 0);
-    }
-
-    public class TestInjector extends PersistentDataStore.Injector {
-        private InputStream mReadStream;
-        private OutputStream mWriteStream;
-
-        private boolean mWasSuccessful;
-
-        @Override
-        public InputStream openRead() throws FileNotFoundException {
-            if (mReadStream != null) {
-                return mReadStream;
-            } else {
-                throw new FileNotFoundException();
-            }
-        }
-
-        @Override
-        public OutputStream startWrite() {
-            return mWriteStream;
-        }
-
-        @Override
-        public void finishWrite(OutputStream os, boolean success) {
-            mWasSuccessful = success;
-            try {
-                os.close();
-            } catch (IOException e) {
-                // This method can't throw IOException since the super implementation doesn't, so
-                // we just wrap it in a RuntimeException so we end up crashing the test all the
-                // same.
-                throw new RuntimeException(e);
-            }
-        }
-
-        public void setReadStream(InputStream is) {
-            mReadStream = is;
-        }
-
-        public void setWriteStream(OutputStream os) {
-            mWriteStream = os;
-        }
-
-        public boolean wasWriteSuccessful() {
-            return mWasSuccessful;
-        }
-    }
-
-    private static void assertArrayEquals(float[] expected, float[] actual, String name) {
-        assertEquals("Expected " + name + " arrays to be the same length!",
-                expected.length, actual.length);
-        for (int i = 0; i < expected.length; i++) {
-            assertEquals("Expected " + name + " arrays to be equivalent when value " + i
-                    + "differs", expected[i], actual[i], 0.01 /*tolerance*/);
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING
deleted file mode 100644
index 92d8abd..0000000
--- a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {"include-filter": "com.android.server.display"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
-    }
-  ]
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/TestUtils.java b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
index 90d9bae..8b45145 100644
--- a/services/tests/servicestests/src/com/android/server/display/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
@@ -18,6 +18,7 @@
 
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
+import android.hardware.input.InputSensorInfo;
 import android.os.Parcel;
 import android.os.SystemClock;
 import android.view.DisplayAddress;
@@ -75,6 +76,12 @@
         return sensor;
     }
 
+    public static Sensor createSensor(String type, String name) {
+        return new Sensor(new InputSensorInfo(
+                name, "vendor", 0, 0, 0, 1f, 1f, 1, 1, 1, 1,
+                type, "", 0, 0, 0));
+    }
+
     /**
      * Create a custom {@link DisplayAddress} to ensure we're not relying on any specific
      * display-address implementation in our code. Intentionally uses default object (reference)
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
deleted file mode 100644
index e6d3bbc..0000000
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ /dev/null
@@ -1,361 +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.server.display.brightness;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.os.HandlerExecutor;
-import android.os.PowerManager;
-import android.view.Display;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.display.AutomaticBrightnessController;
-import com.android.server.display.BrightnessSetting;
-import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
-import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
-
-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 final class DisplayBrightnessControllerTest {
-    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
-    private static final float DEFAULT_BRIGHTNESS = 0.15f;
-
-    @Mock
-    private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
-    @Mock
-    private Context mContext;
-    @Mock
-    private Resources mResources;
-    @Mock
-    private BrightnessSetting mBrightnessSetting;
-    @Mock
-    private Runnable mOnBrightnessChangeRunnable;
-
-    @Mock
-    private HandlerExecutor mBrightnessChangeExecutor;
-
-    private final DisplayBrightnessController.Injector mInjector = new
-            DisplayBrightnessController.Injector() {
-        @Override
-        DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(
-                Context context, int displayId) {
-            return mDisplayBrightnessStrategySelector;
-        }
-    };
-
-    private DisplayBrightnessController mDisplayBrightnessController;
-
-    @Before
-    public void before() {
-        MockitoAnnotations.initMocks(this);
-        when(mContext.getResources()).thenReturn(mResources);
-        when(mBrightnessSetting.getBrightness()).thenReturn(Float.NaN);
-        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(-1f);
-        when(mResources.getBoolean(
-                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
-                .thenReturn(true);
-        mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector,
-                DISPLAY_ID, DEFAULT_BRIGHTNESS, mBrightnessSetting, mOnBrightnessChangeRunnable,
-                mBrightnessChangeExecutor);
-    }
-
-    @Test
-    public void testIfFirstScreenBrightnessIsDefault() {
-        assertEquals(DEFAULT_BRIGHTNESS, mDisplayBrightnessController.getCurrentBrightness(),
-                /* delta= */ 0.0f);
-    }
-
-    @Test
-    public void testUpdateBrightness() {
-        DisplayPowerRequest displayPowerRequest = mock(DisplayPowerRequest.class);
-        DisplayBrightnessStrategy displayBrightnessStrategy = mock(DisplayBrightnessStrategy.class);
-        int targetDisplayState = Display.STATE_DOZE;
-        when(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                targetDisplayState)).thenReturn(displayBrightnessStrategy);
-        mDisplayBrightnessController.updateBrightness(displayPowerRequest, targetDisplayState);
-        verify(displayBrightnessStrategy).updateBrightness(displayPowerRequest);
-        assertEquals(mDisplayBrightnessController.getCurrentDisplayBrightnessStrategy(),
-                displayBrightnessStrategy);
-    }
-
-    @Test
-    public void isAllowAutoBrightnessWhileDozingConfigDelegatesToDozeBrightnessStrategy() {
-        mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig();
-        verify(mDisplayBrightnessStrategySelector).isAllowAutoBrightnessWhileDozingConfig();
-    }
-
-    @Test
-    public void setTemporaryBrightness() {
-        float temporaryBrightness = 0.4f;
-        TemporaryBrightnessStrategy temporaryBrightnessStrategy = mock(
-                TemporaryBrightnessStrategy.class);
-        when(mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()).thenReturn(
-                temporaryBrightnessStrategy);
-        mDisplayBrightnessController.setTemporaryBrightness(temporaryBrightness);
-        verify(temporaryBrightnessStrategy).setTemporaryScreenBrightness(temporaryBrightness);
-    }
-
-    @Test
-    public void setCurrentScreenBrightness() {
-        // Current Screen brightness is set as expected when a different value than the current
-        // is set
-        float currentScreenBrightness = 0.4f;
-        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentScreenBrightness);
-        assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
-                currentScreenBrightness, /* delta= */ 0.0f);
-        verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
-
-        // No change to the current screen brightness is same as the existing one
-        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentScreenBrightness);
-        verifyNoMoreInteractions(mBrightnessChangeExecutor);
-    }
-
-    @Test
-    public void setPendingScreenBrightnessSetting() {
-        float pendingScreenBrightness = 0.4f;
-        mDisplayBrightnessController.setPendingScreenBrightness(pendingScreenBrightness);
-        assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
-                pendingScreenBrightness, /* delta= */ 0.0f);
-    }
-
-    @Test
-    public void updateUserSetScreenBrightness() {
-        // No brightness is set if the pending brightness is invalid
-        mDisplayBrightnessController.setPendingScreenBrightness(Float.NaN);
-        assertFalse(mDisplayBrightnessController.updateUserSetScreenBrightness());
-
-        // user set brightness is not set if the current and the pending brightness are same.
-        float currentBrightness = 0.4f;
-        TemporaryBrightnessStrategy temporaryBrightnessStrategy = mock(
-                TemporaryBrightnessStrategy.class);
-        when(mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()).thenReturn(
-                temporaryBrightnessStrategy);
-        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentBrightness);
-        mDisplayBrightnessController.setPendingScreenBrightness(currentBrightness);
-        mDisplayBrightnessController.setTemporaryBrightness(currentBrightness);
-        assertFalse(mDisplayBrightnessController.updateUserSetScreenBrightness());
-        verify(temporaryBrightnessStrategy).setTemporaryScreenBrightness(
-                PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
-                PowerManager.BRIGHTNESS_INVALID_FLOAT, /* delta= */ 0.0f);
-
-        // user set brightness is set as expected
-        currentBrightness = 0.4f;
-        float pendingScreenBrightness = 0.3f;
-        float temporaryScreenBrightness = 0.2f;
-        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentBrightness);
-        mDisplayBrightnessController.setPendingScreenBrightness(pendingScreenBrightness);
-        mDisplayBrightnessController.setTemporaryBrightness(temporaryScreenBrightness);
-        assertTrue(mDisplayBrightnessController.updateUserSetScreenBrightness());
-        assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
-                pendingScreenBrightness, /* delta= */ 0.0f);
-        assertEquals(mDisplayBrightnessController.getLastUserSetScreenBrightness(),
-                pendingScreenBrightness, /* delta= */ 0.0f);
-        verify(mBrightnessChangeExecutor, times(2))
-                .execute(mOnBrightnessChangeRunnable);
-        verify(temporaryBrightnessStrategy, times(2))
-                .setTemporaryScreenBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT);
-        assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
-                PowerManager.BRIGHTNESS_INVALID_FLOAT, /* delta= */ 0.0f);
-    }
-
-    @Test
-    public void registerBrightnessSettingChangeListener() {
-        BrightnessSetting.BrightnessSettingListener brightnessSettingListener = mock(
-                BrightnessSetting.BrightnessSettingListener.class);
-        mDisplayBrightnessController.registerBrightnessSettingChangeListener(
-                brightnessSettingListener);
-        verify(mBrightnessSetting).registerListener(brightnessSettingListener);
-        assertEquals(mDisplayBrightnessController.getBrightnessSettingListener(),
-                brightnessSettingListener);
-    }
-
-    @Test
-    public void getScreenBrightnessSetting() {
-        // getScreenBrightnessSetting returns the value relayed by BrightnessSetting, if the
-        // valid is valid and in range
-        float brightnessSetting = 0.2f;
-        when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
-        assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), brightnessSetting,
-                /* delta= */ 0.0f);
-
-        // getScreenBrightnessSetting value is clamped if BrightnessSetting returns value beyond max
-        brightnessSetting = 1.1f;
-        when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
-        assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), 1.0f,
-                /* delta= */ 0.0f);
-
-        // getScreenBrightnessSetting returns default value is BrightnessSetting returns invalid
-        // value.
-        brightnessSetting = Float.NaN;
-        when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
-        assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), DEFAULT_BRIGHTNESS,
-                /* delta= */ 0.0f);
-    }
-
-    @Test
-    public void setBrightnessSetsInBrightnessSetting() {
-        float brightnessValue = 0.3f;
-        mDisplayBrightnessController.setBrightness(brightnessValue);
-        verify(mBrightnessSetting).setBrightness(brightnessValue);
-    }
-
-    @Test
-    public void updateScreenBrightnessSetting() {
-        // This interaction happens in the constructor itself
-        verify(mBrightnessSetting).getBrightness();
-
-        // Sets the appropriate value when valid, and not equal to the current brightness
-        float brightnessValue = 0.3f;
-        mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
-        assertEquals(mDisplayBrightnessController.getCurrentBrightness(), brightnessValue,
-                0.0f);
-        verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
-        verify(mBrightnessSetting).setBrightness(brightnessValue);
-
-        // Does nothing if the value is invalid
-        mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN);
-        verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting);
-
-        // Does nothing if the value is same as the current brightness
-        brightnessValue = 0.2f;
-        mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(brightnessValue);
-        verify(mBrightnessChangeExecutor, times(2))
-                .execute(mOnBrightnessChangeRunnable);
-        mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue);
-        verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting);
-    }
-
-    @Test
-    public void testConvertToNits() {
-        final float brightness = 0.5f;
-        final float nits = 300;
-        final float adjustedNits = 200;
-
-        // ABC is null
-        assertEquals(-1f, mDisplayBrightnessController.convertToNits(brightness),
-                /* delta= */ 0);
-        assertEquals(-1f, mDisplayBrightnessController.convertToAdjustedNits(brightness),
-                /* delta= */ 0);
-
-        AutomaticBrightnessController automaticBrightnessController =
-                mock(AutomaticBrightnessController.class);
-        when(automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
-        when(automaticBrightnessController.convertToAdjustedNits(brightness))
-                .thenReturn(adjustedNits);
-        mDisplayBrightnessController.setAutomaticBrightnessController(
-                automaticBrightnessController);
-
-        assertEquals(nits, mDisplayBrightnessController.convertToNits(brightness), /* delta= */ 0);
-        assertEquals(adjustedNits, mDisplayBrightnessController.convertToAdjustedNits(brightness),
-                /* delta= */ 0);
-    }
-
-    @Test
-    public void testConvertToFloatScale() {
-        float brightness = 0.5f;
-        float nits = 300;
-
-        // ABC is null
-        assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT,
-                mDisplayBrightnessController.convertToFloatScale(nits), /* delta= */ 0);
-
-        AutomaticBrightnessController automaticBrightnessController =
-                mock(AutomaticBrightnessController.class);
-        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
-        mDisplayBrightnessController.setAutomaticBrightnessController(
-                automaticBrightnessController);
-
-        assertEquals(brightness, mDisplayBrightnessController.convertToFloatScale(nits),
-                /* delta= */ 0);
-    }
-
-    @Test
-    public void stop() {
-        BrightnessSetting.BrightnessSettingListener brightnessSettingListener = mock(
-                BrightnessSetting.BrightnessSettingListener.class);
-        mDisplayBrightnessController.registerBrightnessSettingChangeListener(
-                brightnessSettingListener);
-        mDisplayBrightnessController.stop();
-        verify(mBrightnessSetting).unregisterListener(brightnessSettingListener);
-    }
-
-    @Test
-    public void testLoadNitBasedBrightnessSetting() {
-        // When the nits value is valid, the brightness is set from the old default display nits
-        // value
-        float nits = 200f;
-        float brightness = 0.3f;
-        AutomaticBrightnessController automaticBrightnessController =
-                mock(AutomaticBrightnessController.class);
-        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
-        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
-        mDisplayBrightnessController.setAutomaticBrightnessController(
-                automaticBrightnessController);
-        verify(mBrightnessSetting).setBrightness(brightness);
-        assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
-        clearInvocations(automaticBrightnessController, mBrightnessSetting);
-
-        // When the nits value is invalid, the brightness is resumed from where it was last set
-        nits = -1;
-        brightness = 0.4f;
-        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
-        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
-        when(mBrightnessSetting.getBrightness()).thenReturn(brightness);
-        mDisplayBrightnessController.setAutomaticBrightnessController(
-                automaticBrightnessController);
-        verify(mBrightnessSetting, never()).setBrightness(brightness);
-        assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
-        clearInvocations(automaticBrightnessController, mBrightnessSetting);
-
-        // When the display is a non-default display, the brightness is resumed from where it was
-        // last set
-        int nonDefaultDisplayId = 1;
-        mDisplayBrightnessController = new DisplayBrightnessController(mContext, mInjector,
-                nonDefaultDisplayId, DEFAULT_BRIGHTNESS, mBrightnessSetting,
-                mOnBrightnessChangeRunnable, mBrightnessChangeExecutor);
-        brightness = 0.5f;
-        when(mBrightnessSetting.getBrightness()).thenReturn(brightness);
-        mDisplayBrightnessController.setAutomaticBrightnessController(
-                automaticBrightnessController);
-        assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f);
-        verifyZeroInteractions(automaticBrightnessController);
-        verify(mBrightnessSetting, never()).getBrightnessNitsForDefaultDisplay();
-        verify(mBrightnessSetting, never()).setBrightness(brightness);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
deleted file mode 100644
index a9e616d..0000000
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ /dev/null
@@ -1,191 +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.server.display.brightness;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.display.DisplayManagerInternal;
-import android.view.Display;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.R;
-import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
-import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
-import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
-import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
-import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy;
-import com.android.server.display.brightness.strategy.ScreenOffBrightnessStrategy;
-import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
-
-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 final class DisplayBrightnessStrategySelectorTest {
-    private static final boolean DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING = false;
-    private static final int DISPLAY_ID = 1;
-
-    @Mock
-    private ScreenOffBrightnessStrategy mScreenOffBrightnessModeStrategy;
-    @Mock
-    private DozeBrightnessStrategy mDozeBrightnessModeStrategy;
-    @Mock
-    private OverrideBrightnessStrategy mOverrideBrightnessStrategy;
-    @Mock
-    private TemporaryBrightnessStrategy mTemporaryBrightnessStrategy;
-    @Mock
-    private BoostBrightnessStrategy mBoostBrightnessStrategy;
-    @Mock
-    private InvalidBrightnessStrategy mInvalidBrightnessStrategy;
-    @Mock
-    private FollowerBrightnessStrategy mFollowerBrightnessStrategy;
-    @Mock
-    private Context mContext;
-    @Mock
-    private Resources mResources;
-
-    private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
-
-    @Before
-    public void before() {
-        MockitoAnnotations.initMocks(this);
-        when(mContext.getResources()).thenReturn(mResources);
-        when(mInvalidBrightnessStrategy.getName()).thenReturn("InvalidBrightnessStrategy");
-        DisplayBrightnessStrategySelector.Injector injector =
-                new DisplayBrightnessStrategySelector.Injector() {
-                    @Override
-                    ScreenOffBrightnessStrategy getScreenOffBrightnessStrategy() {
-                        return mScreenOffBrightnessModeStrategy;
-                    }
-
-                    @Override
-                    DozeBrightnessStrategy getDozeBrightnessStrategy() {
-                        return mDozeBrightnessModeStrategy;
-                    }
-
-                    @Override
-                    OverrideBrightnessStrategy getOverrideBrightnessStrategy() {
-                        return mOverrideBrightnessStrategy;
-                    }
-
-                    @Override
-                    TemporaryBrightnessStrategy getTemporaryBrightnessStrategy() {
-                        return mTemporaryBrightnessStrategy;
-                    }
-
-                    @Override
-                    BoostBrightnessStrategy getBoostBrightnessStrategy() {
-                        return mBoostBrightnessStrategy;
-                    }
-
-                    @Override
-                    FollowerBrightnessStrategy getFollowerBrightnessStrategy(int displayId) {
-                        return mFollowerBrightnessStrategy;
-                    }
-
-                    @Override
-                    InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
-                        return mInvalidBrightnessStrategy;
-                    }
-                };
-        mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
-                injector, DISPLAY_ID);
-
-    }
-
-    @Test
-    public void selectStrategySelectsDozeStrategyWhenValid() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
-        when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn(
-                DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING);
-        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                Display.STATE_DOZE), mDozeBrightnessModeStrategy);
-    }
-
-    @Test
-    public void selectStrategySelectsScreenOffStrategyWhenValid() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                Display.STATE_OFF), mScreenOffBrightnessModeStrategy);
-    }
-
-    @Test
-    public void selectStrategySelectsOverrideStrategyWhenValid() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        displayPowerRequest.screenBrightnessOverride = 0.4f;
-        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
-        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                Display.STATE_ON), mOverrideBrightnessStrategy);
-    }
-
-    @Test
-    public void selectStrategySelectsTemporaryStrategyWhenValid() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        displayPowerRequest.screenBrightnessOverride = Float.NaN;
-        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
-        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(0.3f);
-        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                Display.STATE_ON), mTemporaryBrightnessStrategy);
-    }
-
-    @Test
-    public void selectStrategySelectsBoostStrategyWhenValid() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        displayPowerRequest.boostScreenBrightness = true;
-        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
-        displayPowerRequest.screenBrightnessOverride = Float.NaN;
-        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
-        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                Display.STATE_ON), mBoostBrightnessStrategy);
-    }
-
-    @Test
-    public void selectStrategySelectsInvalidStrategyWhenNoStrategyIsValid() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        displayPowerRequest.screenBrightnessOverride = Float.NaN;
-        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
-        when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
-        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                Display.STATE_ON), mInvalidBrightnessStrategy);
-    }
-
-    @Test
-    public void selectStrategySelectsFollowerStrategyWhenValid() {
-        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
-                DisplayManagerInternal.DisplayPowerRequest.class);
-        when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f);
-        assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
-                Display.STATE_ON), mFollowerBrightnessStrategy);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
deleted file mode 100644
index d9cf15b..0000000
--- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.display.brightness.strategy;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
-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.content.Context;
-import android.content.ContextWrapper;
-import android.hardware.display.BrightnessConfiguration;
-import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
-import android.view.Display;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.display.AutomaticBrightnessController;
-import com.android.server.display.brightness.BrightnessEvent;
-import com.android.server.display.brightness.BrightnessReason;
-
-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.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AutomaticBrightnessStrategyTest {
-    private static final int DISPLAY_ID = 0;
-    @Rule
-    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-
-    @Mock
-    private AutomaticBrightnessController mAutomaticBrightnessController;
-
-    private BrightnessConfiguration mBrightnessConfiguration;
-    private float mDefaultScreenAutoBrightnessAdjustment;
-    private Context mContext;
-    private AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
-
-    @Before
-    public void before() {
-        MockitoAnnotations.initMocks(this);
-        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
-        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
-        when(mContext.getContentResolver()).thenReturn(resolver);
-        mDefaultScreenAutoBrightnessAdjustment = Settings.System.getFloat(
-                mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Float.NaN);
-        Settings.System.putFloat(mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.5f);
-        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, DISPLAY_ID);
-
-        mBrightnessConfiguration = new BrightnessConfiguration.Builder(
-                new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
-        when(mAutomaticBrightnessController.hasUserDataPoints()).thenReturn(true);
-        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
-                mAutomaticBrightnessController);
-        mAutomaticBrightnessStrategy.setBrightnessConfiguration(mBrightnessConfiguration,
-                true);
-    }
-
-    @After
-    public void after() {
-        Settings.System.putFloat(mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, mDefaultScreenAutoBrightnessAdjustment);
-    }
-
-    @Test
-    public void setAutoBrightnessWhenDisabled() {
-        mAutomaticBrightnessStrategy.setUseAutoBrightness(false);
-        int targetDisplayState = Display.STATE_ON;
-        boolean allowAutoBrightnessWhileDozing = false;
-        float brightnessState = Float.NaN;
-        int brightnessReason = BrightnessReason.REASON_OVERRIDE;
-        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
-        float lastUserSetBrightness = 0.2f;
-        boolean userSetBrightnessChanged = true;
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
-        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessState, brightnessReason, policy,
-                lastUserSetBrightness, userSetBrightnessChanged);
-        verify(mAutomaticBrightnessController)
-                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
-                        mBrightnessConfiguration,
-                        lastUserSetBrightness,
-                        userSetBrightnessChanged, 0.5f,
-                        false, policy, true);
-    }
-
-    @Test
-    public void setAutoBrightnessWhenEnabledAndDisplayIsDozing() {
-        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
-        int targetDisplayState = Display.STATE_DOZE;
-        float brightnessState = Float.NaN;
-        boolean allowAutoBrightnessWhileDozing = true;
-        int brightnessReason = BrightnessReason.REASON_DOZE;
-        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
-        float lastUserSetBrightness = 0.2f;
-        boolean userSetBrightnessChanged = true;
-        Settings.System.putFloat(mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
-        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessState, brightnessReason, policy,
-                lastUserSetBrightness, userSetBrightnessChanged);
-        verify(mAutomaticBrightnessController)
-                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
-                        mBrightnessConfiguration,
-                        lastUserSetBrightness,
-                        userSetBrightnessChanged, 0.4f,
-                        true, policy, true);
-    }
-
-    @Test
-    public void setAutoBrightnessWhenEnabledAndDisplayIsOn() {
-        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
-        int targetDisplayState = Display.STATE_ON;
-        float brightnessState = Float.NaN;
-        boolean allowAutoBrightnessWhileDozing = false;
-        int brightnessReason = BrightnessReason.REASON_OVERRIDE;
-        float lastUserSetBrightness = 0.2f;
-        boolean userSetBrightnessChanged = true;
-        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
-        float pendingBrightnessAdjustment = 0.1f;
-        Settings.System.putFloat(mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
-        mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
-                allowAutoBrightnessWhileDozing, brightnessState, brightnessReason, policy,
-                lastUserSetBrightness, userSetBrightnessChanged);
-        verify(mAutomaticBrightnessController)
-                .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
-                        mBrightnessConfiguration,
-                        lastUserSetBrightness,
-                        userSetBrightnessChanged, pendingBrightnessAdjustment,
-                        true, policy, true);
-    }
-
-    @Test
-    public void accommodateUserBrightnessChangesWorksAsExpected() {
-        // Verify the state if automaticBrightnessController is configured.
-        assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
-        boolean userSetBrightnessChanged = true;
-        float lastUserSetScreenBrightness = 0.2f;
-        int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
-        BrightnessConfiguration brightnessConfiguration = new BrightnessConfiguration.Builder(
-                new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
-        int autoBrightnessState = AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
-        float temporaryAutoBrightnessAdjustments = 0.4f;
-        mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
-        setTemporaryAutoBrightnessAdjustment(temporaryAutoBrightnessAdjustments);
-        mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
-                lastUserSetScreenBrightness, policy, brightnessConfiguration,
-                autoBrightnessState);
-        verify(mAutomaticBrightnessController).configure(autoBrightnessState,
-                brightnessConfiguration,
-                lastUserSetScreenBrightness,
-                userSetBrightnessChanged, temporaryAutoBrightnessAdjustments,
-                /* userChangedAutoBrightnessAdjustment= */ false, policy,
-                /* shouldResetShortTermModel= */ true);
-        assertTrue(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
-        assertFalse(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
-        assertTrue(mAutomaticBrightnessStrategy.isShortTermModelActive());
-        // Verify the state when automaticBrightnessController is not configured
-        setTemporaryAutoBrightnessAdjustment(Float.NaN);
-        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(null);
-        mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
-        mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
-                lastUserSetScreenBrightness, policy, brightnessConfiguration,
-                autoBrightnessState);
-        assertFalse(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
-        assertTrue(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
-        assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
-    }
-
-    @Test
-    public void adjustAutomaticBrightnessStateIfValid() throws Settings.SettingNotFoundException {
-        float brightnessState = 0.3f;
-        float autoBrightnessAdjustment = 0.2f;
-        when(mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()).thenReturn(
-                autoBrightnessAdjustment);
-        mAutomaticBrightnessStrategy.adjustAutomaticBrightnessStateIfValid(brightnessState);
-        assertTrue(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
-        assertEquals(autoBrightnessAdjustment,
-                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
-        assertEquals(autoBrightnessAdjustment, Settings.System.getFloatForUser(
-                mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
-                UserHandle.USER_CURRENT), 0.0f);
-        assertEquals(BrightnessReason.ADJUSTMENT_AUTO,
-                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
-        float invalidBrightness = -0.5f;
-        mAutomaticBrightnessStrategy
-                .adjustAutomaticBrightnessStateIfValid(invalidBrightness);
-        assertFalse(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
-        assertEquals(autoBrightnessAdjustment,
-                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
-        assertEquals(0,
-                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
-    }
-
-    @Test
-    public void updatePendingAutoBrightnessAdjustments() {
-        // Verify the state when the pendingAutoBrightnessAdjustments are not present
-        setPendingAutoBrightnessAdjustment(Float.NaN);
-        assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
-        assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
-        // Verify the state when the pendingAutoBrightnessAdjustments are present, but
-        // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are the same
-        float autoBrightnessAdjustment = 0.3f;
-        setPendingAutoBrightnessAdjustment(autoBrightnessAdjustment);
-        setAutoBrightnessAdjustment(autoBrightnessAdjustment);
-        assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
-        assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
-        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
-                0.0f);
-        // Verify the state when the pendingAutoBrightnessAdjustments are present, and
-        // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are not the same
-        float pendingAutoBrightnessAdjustment = 0.2f;
-        setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustment);
-        setTemporaryAutoBrightnessAdjustment(0.1f);
-        assertTrue(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
-        assertTrue(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
-        assertEquals(pendingAutoBrightnessAdjustment,
-                mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
-        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
-                0.0f);
-        assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(),
-                0.0f);
-    }
-
-    @Test
-    public void setAutomaticBrightnessWorksAsExpected() {
-        float automaticScreenBrightness = 0.3f;
-        AutomaticBrightnessController automaticBrightnessController = mock(
-                AutomaticBrightnessController.class);
-        when(automaticBrightnessController.getAutomaticScreenBrightness(any(BrightnessEvent.class)))
-                .thenReturn(automaticScreenBrightness);
-        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
-                automaticBrightnessController);
-        assertEquals(automaticScreenBrightness,
-                mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
-                        new BrightnessEvent(DISPLAY_ID)), 0.0f);
-    }
-
-    @Test
-    public void shouldUseAutoBrightness() {
-        mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
-        assertTrue(mAutomaticBrightnessStrategy.shouldUseAutoBrightness());
-    }
-
-    @Test
-    public void setPendingAutoBrightnessAdjustments() throws Settings.SettingNotFoundException {
-        float pendingAutoBrightnessAdjustments = 0.3f;
-        setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustments);
-        assertEquals(pendingAutoBrightnessAdjustments,
-                mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(), 0.0f);
-        assertEquals(pendingAutoBrightnessAdjustments, Settings.System.getFloatForUser(
-                mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
-                UserHandle.USER_CURRENT), 0.0f);
-    }
-
-    @Test
-    public void setTemporaryAutoBrightnessAdjustment() {
-        float temporaryAutoBrightnessAdjustment = 0.3f;
-        mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
-                temporaryAutoBrightnessAdjustment);
-        assertEquals(temporaryAutoBrightnessAdjustment,
-                mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(), 0.0f);
-    }
-
-    @Test
-    public void setAutoBrightnessApplied() {
-        mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
-        assertTrue(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
-    }
-
-    @Test
-    public void testVerifyNoAutoBrightnessAdjustmentsArePopulatedForNonDefaultDisplay() {
-        int newDisplayId = 1;
-        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, newDisplayId);
-        mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(0.3f);
-        assertEquals(0.5f, mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(),
-                0.0f);
-    }
-
-    private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
-        Settings.System.putFloat(mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
-        mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
-    }
-
-    private void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
-        mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
-                temporaryAutoBrightnessAdjustment);
-    }
-
-    private void setAutoBrightnessAdjustment(float autoBrightnessAdjustment) {
-        mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(autoBrightnessAdjustment);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
deleted file mode 100644
index 081f19d..0000000
--- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
+++ /dev/null
@@ -1,64 +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.server.display.brightness.strategy;
-
-import static org.junit.Assert.assertEquals;
-
-import android.hardware.display.DisplayManagerInternal;
-import android.view.Display;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.display.DisplayBrightnessState;
-import com.android.server.display.brightness.BrightnessReason;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class FollowerBrightnessStrategyTest {
-    private FollowerBrightnessStrategy mFollowerBrightnessStrategy;
-
-    @Before
-    public void before() {
-        mFollowerBrightnessStrategy = new FollowerBrightnessStrategy(Display.DEFAULT_DISPLAY);
-    }
-
-    @Test
-    public void testUpdateBrightness() {
-        DisplayManagerInternal.DisplayPowerRequest
-                displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
-        float brightnessToFollow = 0.2f;
-        mFollowerBrightnessStrategy.setBrightnessToFollow(brightnessToFollow);
-        BrightnessReason brightnessReason = new BrightnessReason();
-        brightnessReason.setReason(BrightnessReason.REASON_FOLLOWER);
-        DisplayBrightnessState expectedDisplayBrightnessState =
-                new DisplayBrightnessState.Builder()
-                        .setBrightness(brightnessToFollow)
-                        .setBrightnessReason(brightnessReason)
-                        .setSdrBrightness(brightnessToFollow)
-                        .setDisplayBrightnessStrategyName(mFollowerBrightnessStrategy.getName())
-                        .build();
-        DisplayBrightnessState updatedDisplayBrightnessState =
-                mFollowerBrightnessStrategy.updateBrightness(displayPowerRequest);
-        assertEquals(expectedDisplayBrightnessState, updatedDisplayBrightnessState);
-    }
-
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
deleted file mode 100644
index 618ab1b..0000000
--- a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
+++ /dev/null
@@ -1,1406 +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.display.color;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-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.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.NonNull;
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.res.Resources;
-import android.hardware.display.ColorDisplayManager;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.Time;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.provider.Settings.System;
-import android.test.mock.MockContentResolver;
-import android.view.Display;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.R;
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.LocalServices;
-import com.android.server.SystemService;
-import com.android.server.twilight.TwilightListener;
-import com.android.server.twilight.TwilightManager;
-import com.android.server.twilight.TwilightState;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.time.ZoneId;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-public class ColorDisplayServiceTest {
-
-    private Context mContext;
-    private int mUserId;
-
-    private MockTwilightManager mTwilightManager;
-    private DisplayTransformManager mDisplayTransformManager;
-    private DisplayManagerInternal mDisplayManagerInternal;
-
-    private ColorDisplayService mCds;
-    private ColorDisplayService.BinderService mBinderService;
-
-    private Resources mResourcesSpy;
-
-    private static final int[] MINIMAL_COLOR_MODES = new int[] {
-        ColorDisplayManager.COLOR_MODE_NATURAL,
-        ColorDisplayManager.COLOR_MODE_BOOSTED,
-    };
-
-    @Before
-    public void setUp() {
-        mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
-        doReturn(mContext).when(mContext).getApplicationContext();
-
-        final Resources res = Mockito.spy(mContext.getResources());
-        doReturn(MINIMAL_COLOR_MODES).when(res).getIntArray(R.array.config_availableColorModes);
-        doReturn(true).when(res).getBoolean(R.bool.config_nightDisplayAvailable);
-        doReturn(true).when(res).getBoolean(R.bool.config_displayWhiteBalanceAvailable);
-        when(mContext.getResources()).thenReturn(res);
-        mResourcesSpy = res;
-
-        mUserId = ActivityManager.getCurrentUser();
-
-        final MockContentResolver cr = new MockContentResolver(mContext);
-        cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
-        doReturn(cr).when(mContext).getContentResolver();
-
-        final AlarmManager am = Mockito.mock(AlarmManager.class);
-        doReturn(am).when(mContext).getSystemService(Context.ALARM_SERVICE);
-
-        mTwilightManager = new MockTwilightManager();
-        LocalServices.addService(TwilightManager.class, mTwilightManager);
-
-        mDisplayTransformManager = Mockito.mock(DisplayTransformManager.class);
-        doReturn(true).when(mDisplayTransformManager).needsLinearColorMatrix();
-        LocalServices.addService(DisplayTransformManager.class, mDisplayTransformManager);
-
-        mDisplayManagerInternal = Mockito.mock(DisplayManagerInternal.class);
-        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
-        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal);
-
-        mCds = new ColorDisplayService(mContext);
-        mBinderService = mCds.new BinderService();
-        LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
-                mCds.new ColorDisplayServiceInternal());
-    }
-
-    @After
-    public void tearDown() {
-        /*
-         * Wait for internal {@link Handler} to finish processing pending messages, so that test
-         * code can safelyremove {@link DisplayTransformManager} mock from {@link LocalServices}.
-         */
-        mCds.mHandler.runWithScissors(() -> { /* nop */ }, /* timeout */ 1000);
-        mCds = null;
-
-        LocalServices.removeServiceForTest(TwilightManager.class);
-        mTwilightManager = null;
-
-        LocalServices.removeServiceForTest(DisplayTransformManager.class);
-
-        mUserId = UserHandle.USER_NULL;
-        mContext = null;
-
-        FakeSettingsProvider.clearSettingsProvider();
-
-        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
-        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOffAfterNight_turnsOff() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOffBeforeNight_turnsOff() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, -180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOffDuringNight_turnsOff() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOffInFuture_turnsOff() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOnAfterNight_turnsOn() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOnBeforeNight_turnsOff() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, -180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOnDuringNight_turnsOff() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedAfterNight_ifOnInFuture_turnsOff() {
-        setAutoModeCustom(-120 /* startTimeOffset */, -60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOffAfterNight_turnsOff() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, 180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOffBeforeNight_turnsOff() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOffDuringNight_turnsOff() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOffInPast_turnsOff() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOnAfterNight_turnsOff() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, 180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOnBeforeNight_turnsOff() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOnDuringNight_turnsOff() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedBeforeNight_ifOnInPast_turnsOn() {
-        setAutoModeCustom(60 /* startTimeOffset */, 120 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOffAfterNight_turnsOn() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOffBeforeNight_turnsOn() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOffDuringNightInFuture_turnsOn() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOffDuringNightInPast_turnsOff() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOnAfterNight_turnsOn() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOnBeforeNight_turnsOn() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOnDuringNightInFuture_turnsOn() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void customSchedule_whenStartedDuringNight_ifOnDuringNightInPast_turnsOn() {
-        setAutoModeCustom(-60 /* startTimeOffset */, 60 /* endTimeOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOffAfterNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOffBeforeNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOffDuringNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOffInFuture_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOnAfterNight_turnsOn() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOnBeforeNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOnDuringNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedAfterNight_ifOnInFuture_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOffAfterNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOffBeforeNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOffDuringNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOffInPast_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOnAfterNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 180 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOnBeforeNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOnDuringNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedBeforeNight_ifOnInPast_turnsOn() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOffAfterNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOffBeforeNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOffDuringNightInFuture_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOffDuringNightInPast_turnsOff() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOnAfterNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOnBeforeNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOnDuringNightInFuture_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenStartedDuringNight_ifOnDuringNightInPast_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        startService();
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOffAfterNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOffBeforeNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -180 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOffDuringNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOffInFuture_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOnAfterNight_turnsOn() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOnBeforeNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -180 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOnDuringNight_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedAfterNight_ifOnInFuture_turnsOff() {
-        setAutoModeTwilight(-120 /* sunsetOffset */, -60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOffAfterNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 180 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOffBeforeNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOffDuringNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOffInPast_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOnAfterNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 180 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOnBeforeNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOnDuringNight_turnsOff() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedBeforeNight_ifOnInPast_turnsOn() {
-        setAutoModeTwilight(60 /* sunsetOffset */, 120 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOffAfterNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOffBeforeNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOffDuringNightInFuture_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOffDuringNightInPast_turnsOff() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(false /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(false /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOnAfterNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOnBeforeNight_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -90 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOnDuringNightInFuture_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, 30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void twilightSchedule_whenRebootedDuringNight_ifOnDuringNightInPast_turnsOn() {
-        setAutoModeTwilight(-60 /* sunsetOffset */, 60 /* sunriseOffset */);
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        final TwilightState state = mTwilightManager.getLastTwilightState();
-        mTwilightManager.setTwilightState(null);
-
-        startService();
-        assertActivated(true /* activated */);
-
-        mTwilightManager.setTwilightState(state);
-        assertActivated(true /* activated */);
-    }
-
-    @Test
-    public void accessibility_colorInversion_transformActivated() {
-        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
-            return;
-        }
-
-        setAccessibilityColorInversion(true);
-        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-
-        startService();
-        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        assertActiveColorMode(mContext.getResources().getInteger(
-                R.integer.config_accessibilityColorMode));
-    }
-
-    @Test
-    public void accessibility_colorCorrection_transformActivated() {
-        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
-            return;
-        }
-
-        setAccessibilityColorCorrection(true);
-        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-
-        startService();
-        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        assertActiveColorMode(mContext.getResources().getInteger(
-                R.integer.config_accessibilityColorMode));
-    }
-
-    @Test
-    public void accessibility_all_transformActivated() {
-        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
-            return;
-        }
-
-        setAccessibilityColorCorrection(true);
-        setAccessibilityColorInversion(true);
-        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-
-        startService();
-        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        assertActiveColorMode(mContext.getResources().getInteger(
-                R.integer.config_accessibilityColorMode));
-    }
-
-    @Test
-    public void accessibility_none_transformActivated() {
-        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
-            return;
-        }
-
-        setAccessibilityColorCorrection(false);
-        setAccessibilityColorInversion(false);
-        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-
-        startService();
-        assertUserColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        assertActiveColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-    }
-
-    @Test
-    public void displayWhiteBalance_enabled() {
-        setDisplayWhiteBalanceEnabled(true);
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-        mBinderService.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        startService();
-        assertDwbActive(true);
-    }
-
-    @Test
-    public void displayWhiteBalance_disabledAfterNightDisplayEnabled() {
-        setDisplayWhiteBalanceEnabled(true);
-        startService();
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        /* Since we are using FakeSettingsProvider which could not trigger observer change,
-         * force an update here.*/
-        mCds.updateDisplayWhiteBalanceStatus();
-        assertDwbActive(false);
-    }
-
-    @Test
-    public void displayWhiteBalance_enabledAfterNightDisplayDisabled() {
-        setDisplayWhiteBalanceEnabled(true);
-        startService();
-        setNightDisplayActivated(true /* activated */, -30 /* lastActivatedTimeOffset */);
-
-        mCds.updateDisplayWhiteBalanceStatus();
-        assertDwbActive(false);
-
-        setNightDisplayActivated(false /* activated */, -30 /* lastActivatedTimeOffset */);
-        mCds.updateDisplayWhiteBalanceStatus();
-        assertDwbActive(true);
-    }
-
-    @Test
-    public void displayWhiteBalance_enabledAfterLinearColorModeSelected() {
-        if (!isColorModeValid(ColorDisplayManager.COLOR_MODE_SATURATED)) {
-            return;
-        }
-        setDisplayWhiteBalanceEnabled(true);
-        mBinderService.setColorMode(ColorDisplayManager.COLOR_MODE_SATURATED);
-        startService();
-        assertDwbActive(false);
-
-        mBinderService.setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        mCds.updateDisplayWhiteBalanceStatus();
-        assertDwbActive(true);
-    }
-
-    @Test
-    public void displayWhiteBalance_disabledWhileAccessibilityColorCorrectionEnabled() {
-        setDisplayWhiteBalanceEnabled(true);
-        setAccessibilityColorCorrection(true);
-        startService();
-        assertDwbActive(false);
-
-        setAccessibilityColorCorrection(false);
-        mCds.updateDisplayWhiteBalanceStatus();
-        assertDwbActive(true);
-    }
-
-    @Test
-    public void displayWhiteBalance_disabledWhileAccessibilityColorInversionEnabled() {
-        setDisplayWhiteBalanceEnabled(true);
-        setAccessibilityColorInversion(true);
-        startService();
-        assertDwbActive(false);
-
-        setAccessibilityColorInversion(false);
-        mCds.updateDisplayWhiteBalanceStatus();
-        assertDwbActive(true);
-    }
-
-    @Test
-    public void compositionColorSpaces_noResources() {
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
-            .thenReturn(new int[] {});
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
-            .thenReturn(new int[] {});
-        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        startService();
-        verify(mDisplayTransformManager).setColorMode(
-                eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_INVALID));
-    }
-
-    @Test
-    public void compositionColorSpaces_invalidResources() {
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
-            .thenReturn(new int[] {
-               ColorDisplayManager.COLOR_MODE_NATURAL,
-               // Missing second color mode
-            });
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
-            .thenReturn(new int[] {
-               Display.COLOR_MODE_SRGB,
-               Display.COLOR_MODE_DISPLAY_P3
-            });
-        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        startService();
-        verify(mDisplayTransformManager).setColorMode(
-                eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_INVALID));
-    }
-
-    @Test
-    public void compositionColorSpaces_validResources_validColorMode() {
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
-            .thenReturn(new int[] {
-               ColorDisplayManager.COLOR_MODE_NATURAL
-            });
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
-            .thenReturn(new int[] {
-               Display.COLOR_MODE_SRGB,
-            });
-        setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL);
-        startService();
-        verify(mDisplayTransformManager).setColorMode(
-                eq(ColorDisplayManager.COLOR_MODE_NATURAL), any(), eq(Display.COLOR_MODE_SRGB));
-    }
-
-    @Test
-    public void compositionColorSpaces_validResources_invalidColorMode() {
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes))
-            .thenReturn(new int[] {
-               ColorDisplayManager.COLOR_MODE_NATURAL
-            });
-        when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces))
-            .thenReturn(new int[] {
-               Display.COLOR_MODE_SRGB,
-            });
-        setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED);
-        startService();
-        verify(mDisplayTransformManager).setColorMode(
-                eq(ColorDisplayManager.COLOR_MODE_BOOSTED), any(), eq(Display.COLOR_MODE_INVALID));
-    }
-
-    @Test
-    public void getColorMode_noAvailableModes_returnsNotSet() {
-        when(mResourcesSpy.getIntArray(R.array.config_availableColorModes))
-                .thenReturn(new int[] {});
-        startService();
-        verify(mDisplayTransformManager, never()).setColorMode(anyInt(), any(), anyInt());
-        assertThat(mBinderService.getColorMode()).isEqualTo(-1);
-    }
-
-    /**
-     * Configures Night display to use a custom schedule.
-     *
-     * @param startTimeOffset the offset relative to now to activate Night display (in minutes)
-     * @param endTimeOffset the offset relative to now to deactivate Night display (in minutes)
-     */
-    private void setAutoModeCustom(int startTimeOffset, int endTimeOffset) {
-        mBinderService.setNightDisplayAutoMode(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
-        mBinderService.setNightDisplayCustomStartTime(
-                new Time(getLocalTimeRelativeToNow(startTimeOffset)));
-        mBinderService
-                .setNightDisplayCustomEndTime(new Time(getLocalTimeRelativeToNow(endTimeOffset)));
-    }
-
-    /**
-     * Configures Night display to use the twilight schedule.
-     *
-     * @param sunsetOffset the offset relative to now for sunset (in minutes)
-     * @param sunriseOffset the offset relative to now for sunrise (in minutes)
-     */
-    private void setAutoModeTwilight(int sunsetOffset, int sunriseOffset) {
-        mBinderService.setNightDisplayAutoMode(ColorDisplayManager.AUTO_MODE_TWILIGHT);
-        mTwilightManager.setTwilightState(
-                getTwilightStateRelativeToNow(sunsetOffset, sunriseOffset));
-    }
-
-    /**
-     * Configures the Night display activated state.
-     *
-     * @param activated {@code true} if Night display should be activated
-     * @param lastActivatedTimeOffset the offset relative to now to record that Night display was
-     * activated (in minutes)
-     */
-    private void setNightDisplayActivated(boolean activated, int lastActivatedTimeOffset) {
-        mBinderService.setNightDisplayActivated(activated);
-        Secure.putStringForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
-                LocalDateTime.now().plusMinutes(lastActivatedTimeOffset).toString(),
-                mUserId);
-    }
-
-    /**
-     * Configures the Accessibility color correction setting state.
-     *
-     * @param state {@code true} if color inversion should be activated
-     */
-    private void setAccessibilityColorCorrection(boolean state) {
-        Secure.putIntForUser(mContext.getContentResolver(),
-                Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, state ? 1 : 0, mUserId);
-    }
-
-    /**
-     * Configures the Accessibility color inversion setting state.
-     *
-     * @param state {@code true} if color inversion should be activated
-     */
-    private void setAccessibilityColorInversion(boolean state) {
-        Secure.putIntForUser(mContext.getContentResolver(),
-                Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, state ? 1 : 0, mUserId);
-    }
-
-    /**
-     * Configures the Display White Balance setting state.
-     *
-     * @param enabled {@code true} if display white balance should be enabled
-     */
-    private void setDisplayWhiteBalanceEnabled(boolean enabled) {
-        Secure.putIntForUser(mContext.getContentResolver(),
-                Secure.DISPLAY_WHITE_BALANCE_ENABLED, enabled ? 1 : 0, mUserId);
-    }
-
-    /**
-     * Configures color mode.
-     */
-    private void setColorMode(int colorMode) {
-        mBinderService.setColorMode(colorMode);
-    }
-
-    /**
-     * Returns whether the color mode is valid on the device the tests are running on.
-     */
-    private boolean isColorModeValid(int mode) {
-        final int[] availableColorModes = mContext.getResources().getIntArray(
-                R.array.config_availableColorModes);
-        if (availableColorModes != null) {
-            for (int availableMode : availableColorModes) {
-                if (mode == availableMode) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Convenience method to start {@link #mCds}.
-     */
-    private void startService() {
-        Secure.putIntForUser(mContext.getContentResolver(), Secure.USER_SETUP_COMPLETE, 1, mUserId);
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-            mCds.onUserChanged(mUserId);
-        });
-    }
-
-    /**
-     * Convenience method for asserting whether Night display should be activated.
-     *
-     * @param activated the expected activated state of Night display
-     */
-    private void assertActivated(boolean activated) {
-        assertWithMessage("Incorrect Night display activated state")
-                .that(mBinderService.isNightDisplayActivated())
-                .isEqualTo(activated);
-    }
-
-    /**
-     * Convenience method for asserting that the active color mode matches expectation.
-     *
-     * @param mode the expected active color mode.
-     */
-    private void assertActiveColorMode(int mode) {
-        assertWithMessage("Unexpected color mode setting")
-                .that(mBinderService.getColorMode())
-                .isEqualTo(mode);
-    }
-
-    /**
-     * Convenience method for asserting that the user chosen color mode matches expectation.
-     *
-     * @param mode the expected color mode setting.
-     */
-    private void assertUserColorMode(int mode) {
-        final int actualMode = System.getIntForUser(mContext.getContentResolver(),
-                System.DISPLAY_COLOR_MODE, -1, mUserId);
-        assertWithMessage("Unexpected color mode setting")
-                .that(actualMode)
-                .isEqualTo(mode);
-    }
-
-    /**
-     * Convenience method for asserting that the DWB active status matches expectation.
-     *
-     * @param enabled the expected active status.
-     */
-    private void assertDwbActive(boolean enabled) {
-        assertWithMessage("Incorrect Display White Balance state")
-                .that(mCds.mDisplayWhiteBalanceTintController.isActivated())
-                .isEqualTo(enabled);
-    }
-
-    /**
-     * Convenience for making a {@link LocalTime} instance with an offset relative to now.
-     *
-     * @param offsetMinutes the offset relative to now (in minutes)
-     * @return the LocalTime instance
-     */
-    private static LocalTime getLocalTimeRelativeToNow(int offsetMinutes) {
-        final Calendar c = Calendar.getInstance();
-        c.add(Calendar.MINUTE, offsetMinutes);
-        return LocalTime.of(c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE));
-    }
-
-    /**
-     * Convenience for making a {@link TwilightState} instance with sunrise/sunset relative to now.
-     *
-     * @param sunsetOffset the offset relative to now for sunset (in minutes)
-     * @param sunriseOffset the offset relative to now for sunrise (in minutes)
-     * @return the TwilightState instance
-     */
-    private static TwilightState getTwilightStateRelativeToNow(int sunsetOffset,
-            int sunriseOffset) {
-        final LocalTime sunset = getLocalTimeRelativeToNow(sunsetOffset);
-        final LocalTime sunrise = getLocalTimeRelativeToNow(sunriseOffset);
-
-        final LocalDateTime now = LocalDateTime.now();
-        final ZoneId zoneId = ZoneId.systemDefault();
-
-        long sunsetMillis = ColorDisplayService.getDateTimeBefore(sunset, now)
-                .atZone(zoneId)
-                .toInstant()
-                .toEpochMilli();
-        long sunriseMillis = ColorDisplayService.getDateTimeBefore(sunrise, now)
-                .atZone(zoneId)
-                .toInstant()
-                .toEpochMilli();
-        if (sunsetMillis < sunriseMillis) {
-            sunsetMillis = ColorDisplayService.getDateTimeAfter(sunset, now)
-                    .atZone(zoneId)
-                    .toInstant()
-                    .toEpochMilli();
-        } else {
-            sunriseMillis = ColorDisplayService.getDateTimeAfter(sunrise, now)
-                    .atZone(zoneId)
-                    .toInstant()
-                    .toEpochMilli();
-        }
-
-        return new TwilightState(sunriseMillis, sunsetMillis);
-    }
-
-    private static class MockTwilightManager implements TwilightManager {
-
-        private final Map<TwilightListener, Handler> mListeners = new HashMap<>();
-        private TwilightState mTwilightState;
-
-        /**
-         * Updates the TwilightState and notifies any registered listeners.
-         *
-         * @param state the new TwilightState to use
-         */
-        void setTwilightState(TwilightState state) {
-            synchronized (mListeners) {
-                mTwilightState = state;
-
-                final CountDownLatch latch = new CountDownLatch(mListeners.size());
-                for (Map.Entry<TwilightListener, Handler> entry : mListeners.entrySet()) {
-                    entry.getValue().post(new Runnable() {
-                        @Override
-                        public void run() {
-                            entry.getKey().onTwilightStateChanged(state);
-                            latch.countDown();
-                        }
-                    });
-                }
-
-                try {
-                    latch.await(5, TimeUnit.SECONDS);
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-
-        @Override
-        public void registerListener(@NonNull TwilightListener listener, @NonNull Handler handler) {
-            synchronized (mListeners) {
-                mListeners.put(listener, handler);
-            }
-        }
-
-        @Override
-        public void unregisterListener(@NonNull TwilightListener listener) {
-            synchronized (mListeners) {
-                mListeners.remove(listener);
-            }
-        }
-
-        @Override
-        public TwilightState getLastTwilightState() {
-            return mTwilightState;
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
deleted file mode 100644
index e0bef1a..0000000
--- a/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display.color;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.Mockito.mock;
-
-import android.hardware.display.DisplayManagerInternal;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Arrays;
-
-public class DisplayWhiteBalanceTintControllerTest {
-
-    private DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
-
-    @Before
-    public void setUp() {
-        DisplayManagerInternal displayManagerInternal = mock(DisplayManagerInternal.class);
-        mDisplayWhiteBalanceTintController =
-                new DisplayWhiteBalanceTintController(displayManagerInternal);
-        mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true);
-        mDisplayWhiteBalanceTintController.setActivated(true);
-    }
-
-    @Test
-    public void displayWhiteBalance_setTemperatureOverMax() {
-        final int max = mDisplayWhiteBalanceTintController.mTemperatureMax;
-        mDisplayWhiteBalanceTintController.setMatrix(max + 1);
-        assertWithMessage("Unexpected temperature set")
-                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
-                .isEqualTo(max);
-    }
-
-    @Test
-    public void displayWhiteBalance_setTemperatureBelowMin() {
-        final int min = mDisplayWhiteBalanceTintController.mTemperatureMin;
-        mDisplayWhiteBalanceTintController.setMatrix(min - 1);
-        assertWithMessage("Unexpected temperature set")
-                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
-                .isEqualTo(min);
-    }
-
-    @Test
-    public void displayWhiteBalance_setValidTemperature() {
-        final int colorTemperature = (mDisplayWhiteBalanceTintController.mTemperatureMin
-                + mDisplayWhiteBalanceTintController.mTemperatureMax) / 2;
-        mDisplayWhiteBalanceTintController.setMatrix(colorTemperature);
-
-        assertWithMessage("Unexpected temperature set")
-                .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
-                .isEqualTo(colorTemperature);
-    }
-
-    @Test
-    public void displayWhiteBalance_setMatrixValidDwbCalculation() {
-        float[] currentMatrix = mDisplayWhiteBalanceTintController.getMatrix();
-        float[] oldMatrix = Arrays.copyOf(currentMatrix, currentMatrix.length);
-
-        mDisplayWhiteBalanceTintController
-                .setMatrix(mDisplayWhiteBalanceTintController.mCurrentColorTemperature + 1);
-        assertWithMessage("DWB matrix did not change when setting a new temperature")
-                .that(Arrays.equals(oldMatrix, currentMatrix))
-                .isFalse();
-    }
-
-    @Test
-    public void displayWhiteBalance_setMatrixInvalidDwbCalculation() {
-        Arrays.fill(mDisplayWhiteBalanceTintController.mDisplayNominalWhiteXYZ, 0);
-        mDisplayWhiteBalanceTintController
-            .setMatrix(mDisplayWhiteBalanceTintController.mCurrentColorTemperature + 1);
-        assertWithMessage("DWB matrix not set to identity after an invalid DWB calculation")
-                .that(Arrays.equals(mDisplayWhiteBalanceTintController.getMatrix(),
-                    new float[] {
-                        1, 0, 0, 0,
-                        0, 1, 0, 0,
-                        0, 0, 1, 0,
-                        0, 0, 0, 1
-                    })
-                ).isTrue();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
deleted file mode 100644
index 04273d6..0000000
--- a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ /dev/null
@@ -1,2911 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display.mode;
-
-import static android.hardware.display.DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA;
-import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS;
-import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS;
-import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS;
-import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS;
-import static android.hardware.display.DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT;
-import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR;
-import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT;
-import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
-import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
-
-import static com.android.server.display.mode.Vote.INVALID_SIZE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-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;
-import static org.mockito.Mockito.when;
-import static org.mockito.internal.verification.VerificationModeFactory.times;
-
-import android.annotation.NonNull;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.hardware.Sensor;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.hardware.display.BrightnessInfo;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
-import android.hardware.display.DisplayManagerGlobal;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
-import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
-import android.os.Handler;
-import android.os.IThermalEventListener;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.Temperature;
-import android.provider.DeviceConfig;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.TypedValue;
-import android.view.Display;
-import android.view.DisplayInfo;
-import android.view.SurfaceControl.RefreshRateRange;
-import android.view.SurfaceControl.RefreshRateRanges;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.R;
-import com.android.internal.display.BrightnessSynchronizer;
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.LocalServices;
-import com.android.server.display.DisplayDeviceConfig;
-import com.android.server.display.TestUtils;
-import com.android.server.display.mode.DisplayModeDirector.BrightnessObserver;
-import com.android.server.display.mode.DisplayModeDirector.DesiredDisplayModeSpecs;
-import com.android.server.sensors.SensorManagerInternal;
-import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
-import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.testutils.FakeDeviceConfigInterface;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
-
-@SmallTest
-@RunWith(JUnitParamsRunner.class)
-public class DisplayModeDirectorTest {
-    // The tolerance within which we consider something approximately equals.
-    private static final String TAG = "DisplayModeDirectorTest";
-    private static final boolean DEBUG = false;
-    private static final float FLOAT_TOLERANCE = 0.01f;
-    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
-    private static final int MODE_ID = 1;
-    private static final float TRANSITION_POINT = 0.763f;
-
-    private static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
-
-    private Context mContext;
-    private FakesInjector mInjector;
-    private Handler mHandler;
-    @Rule
-    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
-    @Mock
-    public StatusBarManagerInternal mStatusBarMock;
-    @Mock
-    public SensorManagerInternal mSensorManagerInternalMock;
-    @Mock
-    public DisplayManagerInternal mDisplayManagerInternalMock;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
-        final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
-        when(mContext.getContentResolver()).thenReturn(resolver);
-        mInjector = spy(new FakesInjector());
-        mHandler = new Handler(Looper.getMainLooper());
-
-        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
-        LocalServices.addService(StatusBarManagerInternal.class, mStatusBarMock);
-        LocalServices.removeServiceForTest(SensorManagerInternal.class);
-        LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
-        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
-        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
-    }
-
-    private DisplayModeDirector createDirectorFromRefreshRateArray(
-            float[] refreshRates, int baseModeId) {
-        return createDirectorFromRefreshRateArray(refreshRates, baseModeId, refreshRates[0]);
-    }
-
-    private DisplayModeDirector createDirectorFromRefreshRateArray(
-            float[] refreshRates, int baseModeId, float defaultRefreshRate) {
-        Display.Mode[] modes = new Display.Mode[refreshRates.length];
-        Display.Mode defaultMode = null;
-        for (int i = 0; i < refreshRates.length; i++) {
-            modes[i] = new Display.Mode(
-                    /*modeId=*/baseModeId + i, /*width=*/1000, /*height=*/1000, refreshRates[i]);
-            if (refreshRates[i] == defaultRefreshRate) {
-                defaultMode = modes[i];
-            }
-        }
-        assertThat(defaultMode).isNotNull();
-        return createDirectorFromModeArray(modes, defaultMode);
-    }
-
-    private DisplayModeDirector createDirectorFromModeArray(Display.Mode[] modes,
-            Display.Mode defaultMode) {
-        DisplayModeDirector director =
-                new DisplayModeDirector(mContext, mHandler, mInjector);
-        director.setLoggingEnabled(true);
-        SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
-        supportedModesByDisplay.put(DISPLAY_ID, modes);
-        director.injectSupportedModesByDisplay(supportedModesByDisplay);
-        SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<>();
-        defaultModesByDisplay.put(DISPLAY_ID, defaultMode);
-        director.injectDefaultModeByDisplay(defaultModesByDisplay);
-        return director;
-    }
-
-    private DisplayModeDirector createDirectorFromFpsRange(int minFps, int maxFps) {
-        int numRefreshRates = maxFps - minFps + 1;
-        float[] refreshRates = new float[numRefreshRates];
-        for (int i = 0; i < numRefreshRates; i++) {
-            refreshRates[i] = minFps + i;
-        }
-        return createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/minFps,
-                /*defaultRefreshRate=*/minFps);
-    }
-
-    @Test
-    public void testDisplayModeVoting() {
-        // With no votes present, DisplayModeDirector should allow any refresh rate.
-        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
-        DesiredDisplayModeSpecs modeSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(modeSpecs.baseModeId).isEqualTo(60);
-        assertThat(modeSpecs.primary.physical.min).isEqualTo(0f);
-        assertThat(modeSpecs.primary.physical.max).isEqualTo(Float.POSITIVE_INFINITY);
-        assertThat(modeSpecs.primary.render.min).isEqualTo(0f);
-        assertThat(modeSpecs.primary.render.max).isEqualTo(Float.POSITIVE_INFINITY);
-        assertThat(modeSpecs.appRequest.physical.min).isEqualTo(0f);
-        assertThat(modeSpecs.appRequest.physical.max).isEqualTo(Float.POSITIVE_INFINITY);
-        assertThat(modeSpecs.appRequest.render.min).isEqualTo(0f);
-        assertThat(modeSpecs.appRequest.render.max).isEqualTo(Float.POSITIVE_INFINITY);
-
-        int numPriorities =  Vote.MAX_PRIORITY - Vote.MIN_PRIORITY + 1;
-
-        // Ensure vote priority works as expected. As we add new votes with higher priority, they
-        // should take precedence over lower priority votes.
-        {
-            int minFps = 60;
-            int maxFps = 90;
-            director = createDirectorFromFpsRange(60, 90);
-            assertTrue(2 * numPriorities < maxFps - minFps + 1);
-            SparseArray<Vote> votes = new SparseArray<>();
-            SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-            votesByDisplay.put(DISPLAY_ID, votes);
-            for (int i = 0; i < numPriorities; i++) {
-                int priority = Vote.MIN_PRIORITY + i;
-                votes.put(priority, Vote.forPhysicalRefreshRates(minFps + i, maxFps - i));
-                director.injectVotesByDisplay(votesByDisplay);
-                modeSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-                assertThat(modeSpecs.baseModeId).isEqualTo(minFps + i);
-                assertThat(modeSpecs.primary.physical.min)
-                        .isEqualTo((float) (minFps + i));
-                assertThat(modeSpecs.primary.physical.max)
-                        .isEqualTo((float) (maxFps - i));
-                assertThat(modeSpecs.primary.render.min).isZero();
-                assertThat(modeSpecs.primary.render.max)
-                        .isEqualTo((float) (maxFps - i));
-                if (priority >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF) {
-                    assertThat(modeSpecs.appRequest.physical.min)
-                            .isEqualTo((float) (minFps + i));
-                    assertThat(modeSpecs.appRequest.physical.max)
-                            .isEqualTo((float) (maxFps - i));
-                    assertThat(modeSpecs.appRequest.render.min).isZero();
-                    assertThat(modeSpecs.appRequest.render.max).isEqualTo(
-                            (float) (maxFps - i));
-                } else {
-                    assertThat(modeSpecs.appRequest.physical.min).isZero();
-                    assertThat(modeSpecs.appRequest.physical.max).isPositiveInfinity();
-                    assertThat(modeSpecs.appRequest.render.min).isZero();
-                    assertThat(modeSpecs.appRequest.render.max).isPositiveInfinity();
-                }
-
-            }
-        }
-
-        // Ensure lower priority votes are able to influence the final decision, even in the
-        // presence of higher priority votes.
-        {
-            assertTrue(numPriorities >= 2);
-            director = createDirectorFromFpsRange(60, 90);
-            SparseArray<Vote> votes = new SparseArray<>();
-            SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-            votesByDisplay.put(DISPLAY_ID, votes);
-            votes.put(Vote.MAX_PRIORITY, Vote.forPhysicalRefreshRates(65, 85));
-            votes.put(Vote.MIN_PRIORITY, Vote.forPhysicalRefreshRates(70, 80));
-            director.injectVotesByDisplay(votesByDisplay);
-            modeSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-            assertThat(modeSpecs.baseModeId).isEqualTo(70);
-            assertThat(modeSpecs.primary.physical.min).isEqualTo(70f);
-            assertThat(modeSpecs.primary.physical.max).isEqualTo(80f);
-        }
-    }
-
-    @Test
-    public void testVotingWithFloatingPointErrors() {
-        DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        float error = FLOAT_TOLERANCE / 4;
-        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(0, 60));
-        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE,
-                Vote.forPhysicalRefreshRates(60 + error, 60 + error));
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forPhysicalRefreshRates(60 - error, 60 - error));
-        director.injectVotesByDisplay(votesByDisplay);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
-
-        votes.clear();
-        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(0, 60 - error));
-        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(60 + error, Float.POSITIVE_INFINITY));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
-    }
-
-    @Test
-    public void testFlickerHasLowerPriorityThanUserAndRangeIsSingle() {
-        assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE
-                < Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
-        assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE
-                < Vote.PRIORITY_APP_REQUEST_SIZE);
-
-        assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH
-                > Vote.PRIORITY_LOW_POWER_MODE);
-
-        Display.Mode[] modes = new Display.Mode[4];
-        modes[0] = new Display.Mode(
-                /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
-        modes[1] = new Display.Mode(
-                /*modeId=*/2, /*width=*/2000, /*height=*/2000, 60);
-        modes[2] = new Display.Mode(
-                /*modeId=*/3, /*width=*/1000, /*height=*/1000, 90);
-        modes[3] = new Display.Mode(
-                /*modeId=*/4, /*width=*/2000, /*height=*/2000, 90);
-
-        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        Display.Mode appRequestedMode = modes[1];
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
-        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
-                appRequestedMode.getPhysicalHeight()));
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(60, 60));
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
-        director.injectVotesByDisplay(votesByDisplay);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(2);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.physical.max)
-                .isWithin(FLOAT_TOLERANCE).of(desiredSpecs.primary.physical.min);
-
-        votes.clear();
-        appRequestedMode = modes[3];
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
-        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
-                appRequestedMode.getPhysicalHeight()));
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(90, 90));
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(4);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.physical.max)
-                .isWithin(FLOAT_TOLERANCE).of(desiredSpecs.primary.physical.min);
-
-        votes.clear();
-        appRequestedMode = modes[3];
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
-        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
-                appRequestedMode.getPhysicalHeight()));
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(60, 60));
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(4);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.physical.max)
-                .isWithin(FLOAT_TOLERANCE).of(desiredSpecs.primary.physical.min);
-
-        votes.clear();
-        appRequestedMode = modes[1];
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
-        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
-                appRequestedMode.getPhysicalHeight()));
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(90, 90));
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(2);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.physical.max)
-                .isWithin(FLOAT_TOLERANCE).of(desiredSpecs.primary.physical.min);
-    }
-
-    @Test
-    public void testLPMHasHigherPriorityThanUser() {
-        assertTrue(Vote.PRIORITY_LOW_POWER_MODE
-                > Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
-        assertTrue(Vote.PRIORITY_LOW_POWER_MODE
-                > Vote.PRIORITY_APP_REQUEST_SIZE);
-
-        Display.Mode[] modes = new Display.Mode[4];
-        modes[0] = new Display.Mode(
-                /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
-        modes[1] = new Display.Mode(
-                /*modeId=*/2, /*width=*/2000, /*height=*/2000, 60);
-        modes[2] = new Display.Mode(
-                /*modeId=*/3, /*width=*/1000, /*height=*/1000, 90);
-        modes[3] = new Display.Mode(
-                /*modeId=*/4, /*width=*/2000, /*height=*/2000, 90);
-
-        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        Display.Mode appRequestedMode = modes[1];
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
-        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
-                appRequestedMode.getPhysicalHeight()));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(60, 60));
-        director.injectVotesByDisplay(votesByDisplay);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(2);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-
-        votes.clear();
-        appRequestedMode = modes[3];
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
-        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
-                appRequestedMode.getPhysicalHeight()));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(4);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(90);
-
-        votes.clear();
-        appRequestedMode = modes[3];
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
-        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
-                appRequestedMode.getPhysicalHeight()));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(60, 60));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(2);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-
-        votes.clear();
-        appRequestedMode = modes[1];
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
-        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
-                appRequestedMode.getPhysicalHeight()));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(4);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(90);
-    }
-
-    @Test
-    public void testAppRequestRefreshRateRange() {
-        // Confirm that the app request range doesn't include flicker or min refresh rate settings,
-        // but does include everything else.
-        assertTrue(
-                Vote.PRIORITY_FLICKER_REFRESH_RATE
-                        < Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
-        assertTrue(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE
-                < Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
-        assertTrue(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE
-                >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
-
-        Display.Mode[] modes = new Display.Mode[3];
-        modes[0] = new Display.Mode(
-                /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60);
-        modes[1] = new Display.Mode(
-                /*modeId=*/75, /*width=*/2000, /*height=*/2000, 75);
-        modes[2] = new Display.Mode(
-                /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90);
-
-        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(60, 60));
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
-        director.injectVotesByDisplay(votesByDisplay);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
-        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
-
-        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(90, Float.POSITIVE_INFINITY));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.physical.max).isAtLeast(90f);
-        assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
-        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
-
-        Display.Mode appRequestedMode = modes[1];
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
-        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
-                appRequestedMode.getPhysicalHeight()));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(75);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(75);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(75);
-        assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
-        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
-    }
-
-    void verifySpecsWithRefreshRateSettings(DisplayModeDirector director, float minFps,
-            float peakFps, float defaultFps, RefreshRateRanges primary,
-            RefreshRateRanges appRequest) {
-        DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(
-                minFps, peakFps, defaultFps);
-        assertThat(specs.primary).isEqualTo(primary);
-        assertThat(specs.appRequest).isEqualTo(appRequest);
-    }
-
-    @Test
-    public void testSpecsFromRefreshRateSettings() {
-        // Confirm that, with varying settings for min, peak, and default refresh rate,
-        // DesiredDisplayModeSpecs is calculated correctly.
-        float[] refreshRates = {30.f, 60.f, 90.f, 120.f, 150.f};
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/0);
-
-        float inf = Float.POSITIVE_INFINITY;
-        RefreshRateRange rangeAll = new RefreshRateRange(0, inf);
-        RefreshRateRange range0to60 = new RefreshRateRange(0, 60);
-        RefreshRateRange range0to90 = new RefreshRateRange(0, 90);
-        RefreshRateRange range0to120 = new RefreshRateRange(0, 120);
-        RefreshRateRange range60to90 = new RefreshRateRange(60, 90);
-        RefreshRateRange range90to90 = new RefreshRateRange(90, 90);
-        RefreshRateRange range90to120 = new RefreshRateRange(90, 120);
-        RefreshRateRange range60toInf = new RefreshRateRange(60, inf);
-        RefreshRateRange range90toInf = new RefreshRateRange(90, inf);
-
-        RefreshRateRanges frameRateAll = new RefreshRateRanges(rangeAll, rangeAll);
-        RefreshRateRanges frameRate90toInf = new RefreshRateRanges(range90toInf, range90toInf);
-        RefreshRateRanges frameRate0to60 = new RefreshRateRanges(rangeAll, range0to60);
-        RefreshRateRanges frameRate0to90 = new RefreshRateRanges(rangeAll, range0to90);
-        RefreshRateRanges frameRate0to120 = new RefreshRateRanges(rangeAll, range0to120);
-        RefreshRateRanges frameRate60to90 = new RefreshRateRanges(range60toInf, range60to90);
-        RefreshRateRanges frameRate90to90 = new RefreshRateRanges(range90toInf, range90to90);
-        RefreshRateRanges frameRate90to120 = new RefreshRateRanges(range90toInf, range90to120);
-
-        verifySpecsWithRefreshRateSettings(director, 0, 0, 0, frameRateAll, frameRateAll);
-        verifySpecsWithRefreshRateSettings(director, 0, 0, 90, frameRate0to90, frameRateAll);
-        verifySpecsWithRefreshRateSettings(director, 0, 90, 0, frameRate0to90, frameRate0to90);
-        verifySpecsWithRefreshRateSettings(director, 0, 90, 60, frameRate0to60, frameRate0to90);
-        verifySpecsWithRefreshRateSettings(director, 0, 90, 120, frameRate0to90,
-                frameRate0to90);
-        verifySpecsWithRefreshRateSettings(director, 90, 0, 0, frameRate90toInf, frameRateAll);
-        verifySpecsWithRefreshRateSettings(director, 90, 0, 120, frameRate90to120,
-                frameRateAll);
-        verifySpecsWithRefreshRateSettings(director, 90, 0, 60, frameRate90toInf, frameRateAll);
-        verifySpecsWithRefreshRateSettings(director, 90, 120, 0, frameRate90to120,
-                frameRate0to120);
-        verifySpecsWithRefreshRateSettings(director, 90, 60, 0, frameRate90to90,
-                frameRate0to90);
-        verifySpecsWithRefreshRateSettings(director, 60, 120, 90, frameRate60to90,
-                frameRate0to120);
-    }
-
-    void verifyBrightnessObserverCall(DisplayModeDirector director, float minFps, float peakFps,
-            float defaultFps, float brightnessObserverMin, float brightnessObserverMax) {
-        BrightnessObserver brightnessObserver = mock(BrightnessObserver.class);
-        director.injectBrightnessObserver(brightnessObserver);
-        director.getDesiredDisplayModeSpecsWithInjectedFpsSettings(minFps, peakFps, defaultFps);
-        verify(brightnessObserver)
-                .onRefreshRateSettingChangedLocked(brightnessObserverMin, brightnessObserverMax);
-    }
-
-    @Test
-    public void testBrightnessObserverCallWithRefreshRateSettings() {
-        // Confirm that, with varying settings for min, peak, and default refresh rate, we make the
-        // correct call to the brightness observer.
-        float[] refreshRates = {60.f, 90.f, 120.f};
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(refreshRates, /*baseModeId=*/0);
-        verifyBrightnessObserverCall(director, 0, 0, 0, 0, 0);
-        verifyBrightnessObserverCall(director, 0, 0, 90, 0, 90);
-        verifyBrightnessObserverCall(director, 0, 90, 0, 0, 90);
-        verifyBrightnessObserverCall(director, 0, 90, 60, 0, 60);
-        verifyBrightnessObserverCall(director, 90, 90, 0, 90, 90);
-        verifyBrightnessObserverCall(director, 120, 90, 0, 120, 90);
-    }
-
-    @Test
-    public void testVotingWithAlwaysRespectAppRequest() {
-        Display.Mode[] modes = new Display.Mode[3];
-        modes[0] = new Display.Mode(
-                /*modeId=*/50, /*width=*/1000, /*height=*/1000, 50);
-        modes[1] = new Display.Mode(
-                /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60);
-        modes[2] = new Display.Mode(
-                /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90);
-
-        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]);
-
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(0, 60));
-        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(60, 90));
-        Display.Mode appRequestedMode = modes[2];
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
-        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(60, 60));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
-        director.injectVotesByDisplay(votesByDisplay);
-
-        assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse();
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
-
-        director.setShouldAlwaysRespectAppRequestedMode(true);
-        assertThat(director.shouldAlwaysRespectAppRequestedMode()).isTrue();
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isAtMost(50);
-        assertThat(desiredSpecs.primary.physical.max).isAtLeast(90);
-        assertThat(desiredSpecs.primary.render.min).isAtMost(50);
-        assertThat(desiredSpecs.primary.render.max).isAtLeast(90);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
-
-        director.setShouldAlwaysRespectAppRequestedMode(false);
-        assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse();
-
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
-    }
-
-    @Test
-    public void testVotingWithSwitchingTypeNone() {
-        DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(30, 90));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
-
-        director.injectVotesByDisplay(votesByDisplay);
-        assertThat(director.getModeSwitchingType())
-                .isNotEqualTo(DisplayManager.SWITCHING_TYPE_NONE);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
-        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(30);
-
-        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_NONE);
-        assertThat(director.getModeSwitchingType())
-                .isEqualTo(DisplayManager.SWITCHING_TYPE_NONE);
-
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(30);
-    }
-
-    @Test
-    public void testVotingWithSwitchingTypeRenderFrameRateOnly() {
-        DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(30, 90));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
-
-        director.injectVotesByDisplay(votesByDisplay);
-        assertThat(director.getModeSwitchingType())
-                .isNotEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
-        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(30);
-
-        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
-        assertThat(director.getModeSwitchingType())
-                .isEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
-
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(30);
-
-        assertThat(desiredSpecs.baseModeId).isEqualTo(30);
-    }
-
-    @Test
-    public void testVotingWithSwitchingTypeRenderFrameRateOnlyRenderRateIsNotPhysicalRefreshRate() {
-        DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(30, 90));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
-
-        director.injectVotesByDisplay(votesByDisplay);
-        assertThat(director.getModeSwitchingType())
-                .isNotEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(30);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
-        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
-
-        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
-        assertThat(director.getModeSwitchingType())
-                .isEqualTo(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
-
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-
-        assertThat(desiredSpecs.baseModeId).isEqualTo(60);
-    }
-
-    @Test
-    public void testVotingWithSwitchingTypeWithinGroups() {
-        DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
-
-        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
-        assertThat(director.getModeSwitchingType())
-                .isEqualTo(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.allowGroupSwitching).isFalse();
-    }
-
-    @Test
-    public void testVotingWithSwitchingTypeWithinAndAcrossGroups() {
-        DisplayModeDirector director = createDirectorFromFpsRange(0, 90);
-
-        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
-        assertThat(director.getModeSwitchingType())
-                .isEqualTo(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.allowGroupSwitching).isTrue();
-    }
-
-    @Test
-    public void testDefaultDisplayModeIsSelectedIfAvailable() {
-        final float[] refreshRates = new float[]{24f, 25f, 30f, 60f, 90f};
-        final int defaultModeId = 3;
-        DisplayModeDirector director = createDirectorFromRefreshRateArray(
-                refreshRates, /*baseModeId=*/0, refreshRates[defaultModeId]);
-
-        DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(specs.baseModeId).isEqualTo(defaultModeId);
-    }
-
-    @Test
-    public void testStaleAppRequestSize() {
-        DisplayModeDirector director =
-                new DisplayModeDirector(mContext, mHandler, mInjector);
-        Display.Mode[] modes = new Display.Mode[] {
-                new Display.Mode(1, 1280, 720, 60),
-        };
-        Display.Mode defaultMode = modes[0];
-
-        // Inject supported modes
-        SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
-        supportedModesByDisplay.put(DISPLAY_ID, modes);
-        director.injectSupportedModesByDisplay(supportedModesByDisplay);
-
-        // Inject default mode
-        SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<>();
-        defaultModesByDisplay.put(DISPLAY_ID, defaultMode);
-        director.injectDefaultModeByDisplay(defaultModesByDisplay);
-
-        // Inject votes
-        SparseArray<Vote> votes = new SparseArray<>();
-        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(1920, 1080));
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(60));
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        director.injectVotesByDisplay(votesByDisplay);
-
-        director.setShouldAlwaysRespectAppRequestedMode(true);
-
-        // We should return the only available mode
-        DesiredDisplayModeSpecs specs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(specs.baseModeId).isEqualTo(defaultMode.getModeId());
-    }
-
-    @Test
-    public void testBrightnessObserverGetsUpdatedRefreshRatesForZone() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
-        SensorManager sensorManager = createMockSensorManager(createLightSensor());
-
-        final int initialRefreshRate = 60;
-        mInjector.getDeviceConfig().setRefreshRateInLowZone(initialRefreshRate);
-        director.start(sensorManager);
-        assertThat(director.getBrightnessObserver().getRefreshRateInLowZone())
-                .isEqualTo(initialRefreshRate);
-
-        final int updatedRefreshRate = 90;
-        mInjector.getDeviceConfig().setRefreshRateInLowZone(updatedRefreshRate);
-        // Need to wait for the property change to propagate to the main thread.
-        waitForIdleSync();
-        assertThat(director.getBrightnessObserver().getRefreshRateInLowZone())
-                .isEqualTo(updatedRefreshRate);
-    }
-
-    @Test
-    public void testBrightnessObserverThresholdsInZone() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
-        SensorManager sensorManager = createMockSensorManager(createLightSensor());
-
-        final int[] initialDisplayThresholds = { 10 };
-        final int[] initialAmbientThresholds = { 20 };
-
-        final FakeDeviceConfig config = mInjector.getDeviceConfig();
-        config.setLowDisplayBrightnessThresholds(initialDisplayThresholds);
-        config.setLowAmbientBrightnessThresholds(initialAmbientThresholds);
-        director.start(sensorManager);
-
-        assertThat(director.getBrightnessObserver().getLowDisplayBrightnessThresholds())
-                .isEqualTo(initialDisplayThresholds);
-        assertThat(director.getBrightnessObserver().getLowAmbientBrightnessThresholds())
-                .isEqualTo(initialAmbientThresholds);
-
-        final int[] updatedDisplayThresholds = { 9, 14 };
-        final int[] updatedAmbientThresholds = { -1, 19 };
-        config.setLowDisplayBrightnessThresholds(updatedDisplayThresholds);
-        config.setLowAmbientBrightnessThresholds(updatedAmbientThresholds);
-        // Need to wait for the property change to propagate to the main thread.
-        waitForIdleSync();
-        assertThat(director.getBrightnessObserver().getLowDisplayBrightnessThresholds())
-                .isEqualTo(updatedDisplayThresholds);
-        assertThat(director.getBrightnessObserver().getLowAmbientBrightnessThresholds())
-                .isEqualTo(updatedAmbientThresholds);
-    }
-
-    @Test
-    public void setBrightness_doesNotLockFpsIfSmoothDisplayIsOff() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        setPeakRefreshRate(60); // set smooth display ON
-        director.getSettingsObserver().setDefaultRefreshRate(90);
-        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
-
-        final FakeDeviceConfig config = mInjector.getDeviceConfig();
-        config.setRefreshRateInLowZone(90);
-        config.setLowDisplayBrightnessThresholds(new int[] { 10 });
-        config.setLowAmbientBrightnessThresholds(new int[] { 20 });
-
-        director.start(createMockSensorManager(createLightSensor()));
-
-        ArgumentCaptor<DisplayListener> displayListenerCaptor =
-                ArgumentCaptor.forClass(DisplayListener.class);
-        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
-                any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
-        DisplayListener displayListener = displayListenerCaptor.getValue();
-
-        setBrightness(10, 10, displayListener);
-
-        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
-        assertThat(vote).isNull();
-        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
-        assertThat(vote).isNull();
-    }
-
-    @Test
-    public void setBrightness_locksFpsIfSmoothDisplayIsOn() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        setPeakRefreshRate(90); // set smooth display ON
-        director.getSettingsObserver().setDefaultRefreshRate(90);
-        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
-
-        final FakeDeviceConfig config = mInjector.getDeviceConfig();
-        config.setRefreshRateInLowZone(90);
-        config.setLowDisplayBrightnessThresholds(new int[] { 10 });
-        config.setLowAmbientBrightnessThresholds(new int[] { 20 });
-
-        director.start(createMockSensorManager(createLightSensor()));
-
-        ArgumentCaptor<DisplayListener> displayListenerCaptor =
-                ArgumentCaptor.forClass(DisplayListener.class);
-        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
-                any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
-        DisplayListener displayListener = displayListenerCaptor.getValue();
-
-        setBrightness(10, 10, displayListener);
-
-        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
-        assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/);
-        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
-        assertThat(vote).isNotNull();
-        assertThat(vote.disableRefreshRateSwitching).isTrue();
-    }
-
-    @Test
-    public void testLockFpsForLowZone() throws Exception {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        setPeakRefreshRate(90);
-        director.getSettingsObserver().setDefaultRefreshRate(90);
-        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
-
-        final FakeDeviceConfig config = mInjector.getDeviceConfig();
-        config.setRefreshRateInLowZone(90);
-        config.setLowDisplayBrightnessThresholds(new int[] { 10 });
-        config.setLowAmbientBrightnessThresholds(new int[] { 20 });
-
-        Sensor lightSensor = createLightSensor();
-        SensorManager sensorManager = createMockSensorManager(lightSensor);
-
-        director.start(sensorManager);
-
-        ArgumentCaptor<DisplayListener> displayListenerCaptor =
-                  ArgumentCaptor.forClass(DisplayListener.class);
-        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
-                any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
-        DisplayListener displayListener = displayListenerCaptor.getValue();
-
-        ArgumentCaptor<SensorEventListener> sensorListenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
-                .registerListener(
-                        sensorListenerCaptor.capture(),
-                        eq(lightSensor),
-                        anyInt(),
-                        any(Handler.class));
-        SensorEventListener sensorListener = sensorListenerCaptor.getValue();
-
-        setBrightness(10, 10, displayListener);
-        // Sensor reads 20 lux,
-        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 20 /*lux*/));
-
-        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
-        assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/);
-        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
-        assertThat(vote).isNotNull();
-        assertThat(vote.disableRefreshRateSwitching).isTrue();
-
-        // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
-        // parameter to the necessary threshold
-        setBrightness(10, 125, displayListener);
-        // Sensor reads 1000 lux,
-        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000 /*lux*/));
-
-        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
-        assertThat(vote).isNull();
-        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
-        assertThat(vote).isNull();
-    }
-
-    @Test
-    public void testLockFpsForHighZone() throws Exception {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        setPeakRefreshRate(90 /*fps*/);
-        director.getSettingsObserver().setDefaultRefreshRate(90);
-        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
-
-        final FakeDeviceConfig config = mInjector.getDeviceConfig();
-        config.setRefreshRateInHighZone(60);
-        config.setHighDisplayBrightnessThresholds(new int[] { 255 });
-        config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
-
-        Sensor lightSensor = createLightSensor();
-        SensorManager sensorManager = createMockSensorManager(lightSensor);
-
-        director.start(sensorManager);
-
-        ArgumentCaptor<DisplayListener> displayListenerCaptor =
-                  ArgumentCaptor.forClass(DisplayListener.class);
-        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
-                any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
-        DisplayListener displayListener = displayListenerCaptor.getValue();
-
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
-                .registerListener(
-                        listenerCaptor.capture(),
-                        eq(lightSensor),
-                        anyInt(),
-                        any(Handler.class));
-        SensorEventListener sensorListener = listenerCaptor.getValue();
-
-        setBrightness(100, 100, displayListener);
-        // Sensor reads 2000 lux,
-        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
-
-        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
-        assertThat(vote).isNull();
-        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
-        assertThat(vote).isNull();
-
-        // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
-        // parameter to the necessary threshold
-        setBrightness(100, 255, displayListener);
-        // Sensor reads 9000 lux,
-        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000));
-
-        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
-        assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/);
-        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
-        assertThat(vote).isNotNull();
-        assertThat(vote.disableRefreshRateSwitching).isTrue();
-    }
-
-    @Test
-    public void testSensorRegistration() {
-        // First, configure brightness zones or DMD won't register for sensor data.
-        final FakeDeviceConfig config = mInjector.getDeviceConfig();
-        config.setRefreshRateInHighZone(60);
-        config.setHighDisplayBrightnessThresholds(new int[] { 255 });
-        config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
-
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        setPeakRefreshRate(90 /*fps*/);
-        director.getSettingsObserver().setDefaultRefreshRate(90);
-        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
-
-        Sensor lightSensor = createLightSensor();
-        SensorManager sensorManager = createMockSensorManager(lightSensor);
-
-        director.start(sensorManager);
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
-                .registerListener(
-                        listenerCaptor.capture(),
-                        eq(lightSensor),
-                        anyInt(),
-                        any(Handler.class));
-
-        // Display state changed from On to Doze
-        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_DOZE);
-        verify(sensorManager)
-                .unregisterListener(listenerCaptor.capture());
-
-        // Display state changed from Doze to On
-        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
-        verify(sensorManager, times(2))
-                .registerListener(
-                        listenerCaptor.capture(),
-                        eq(lightSensor),
-                        anyInt(),
-                        any(Handler.class));
-
-    }
-
-    @Test
-    public void testUdfpsListenerGetsRegistered() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0);
-        verify(mStatusBarMock, never()).setUdfpsRefreshRateCallback(any());
-
-        director.onBootCompleted();
-        verify(mStatusBarMock).setUdfpsRefreshRateCallback(eq(director.getUdpfsObserver()));
-    }
-
-    @Test
-    public void testGbhmVotesFor60hz() throws Exception {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0);
-        director.start(createMockSensorManager());
-        director.onBootCompleted();
-        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
-                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
-        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
-
-        // Should be no vote initially
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
-        assertNull(vote);
-    }
-
-    @Test
-    public void testAppRequestMinRefreshRate() {
-        // Confirm that the app min request range doesn't include flicker or min refresh rate
-        // settings but does include everything else.
-        assertTrue(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE
-                >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
-
-        Display.Mode[] modes = new Display.Mode[3];
-        modes[0] = new Display.Mode(
-                /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60);
-        modes[1] = new Display.Mode(
-                /*modeId=*/75, /*width=*/1000, /*height=*/1000, 75);
-        modes[2] = new Display.Mode(
-                /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90);
-
-        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[1]);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(60, 60));
-        director.injectVotesByDisplay(votesByDisplay);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
-        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
-
-        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(90, Float.POSITIVE_INFINITY));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.physical.max).isAtLeast(90f);
-        assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
-        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
-
-        votes.put(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
-                Vote.forRenderFrameRates(75, Float.POSITIVE_INFINITY));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(75);
-        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
-    }
-
-    @Test
-    public void testAppRequestMaxRefreshRate() {
-        // Confirm that the app max request range doesn't include flicker or min refresh rate
-        // settings but does include everything else.
-        assertTrue(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE
-                >= Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF);
-
-        Display.Mode[] modes = new Display.Mode[3];
-        modes[0] = new Display.Mode(
-                /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60);
-        modes[1] = new Display.Mode(
-                /*modeId=*/75, /*width=*/1000, /*height=*/1000, 75);
-        modes[2] = new Display.Mode(
-                /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90);
-
-        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[1]);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE, Vote.forPhysicalRefreshRates(60, 60));
-        director.injectVotesByDisplay(votesByDisplay);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.render.min).isZero();
-        assertThat(desiredSpecs.primary.render.max).isAtMost(60);
-        assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
-        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
-        assertThat(desiredSpecs.appRequest.render.min).isZero();
-        assertThat(desiredSpecs.appRequest.render.max).isAtLeast(90f);
-
-        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(90, Float.POSITIVE_INFINITY));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.render.max).isAtLeast(90f);
-        assertThat(desiredSpecs.appRequest.physical.min).isAtMost(60f);
-        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
-        assertThat(desiredSpecs.appRequest.render.min).isAtMost(60f);
-        assertThat(desiredSpecs.appRequest.render.max).isAtLeast(90f);
-
-        votes.put(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
-                Vote.forRenderFrameRates(0, 75));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(75);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(75);
-        assertThat(desiredSpecs.primary.render.min).isZero();
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(75);
-        assertThat(desiredSpecs.appRequest.physical.min).isZero();
-        assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f);
-        assertThat(desiredSpecs.appRequest.render.min).isZero();
-        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(75);
-    }
-
-    @Test
-    public void testAppRequestObserver_modeId() {
-        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
-        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 0, 0);
-
-        Vote appRequestRefreshRate =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
-        assertNotNull(appRequestRefreshRate);
-        assertThat(appRequestRefreshRate.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRate.refreshRateRanges.physical.max).isPositiveInfinity();
-        assertThat(appRequestRefreshRate.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestRefreshRate.refreshRateRanges.render.max).isPositiveInfinity();
-        assertThat(appRequestRefreshRate.disableRefreshRateSwitching).isFalse();
-        assertThat(appRequestRefreshRate.appRequestBaseModeRefreshRate)
-                .isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE);
-
-        Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
-        assertNotNull(appRequestSize);
-        assertThat(appRequestSize.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestSize.refreshRateRanges.physical.max).isPositiveInfinity();
-        assertThat(appRequestSize.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestSize.refreshRateRanges.render.max).isPositiveInfinity();
-        assertThat(appRequestSize.disableRefreshRateSwitching).isFalse();
-        assertThat(appRequestSize.appRequestBaseModeRefreshRate).isZero();
-        assertThat(appRequestSize.height).isEqualTo(1000);
-        assertThat(appRequestSize.width).isEqualTo(1000);
-
-        Vote appRequestRefreshRateRange =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
-        assertNull(appRequestRefreshRateRange);
-
-        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 90, 0, 0);
-
-        appRequestRefreshRate =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
-        assertNotNull(appRequestRefreshRate);
-        assertThat(appRequestRefreshRate.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRate.refreshRateRanges.physical.max).isPositiveInfinity();
-        assertThat(appRequestRefreshRate.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestRefreshRate.refreshRateRanges.render.max).isPositiveInfinity();
-        assertThat(appRequestRefreshRate.disableRefreshRateSwitching).isFalse();
-        assertThat(appRequestRefreshRate.appRequestBaseModeRefreshRate)
-                .isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE);
-
-        appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
-        assertNotNull(appRequestSize);
-        assertThat(appRequestSize.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestSize.refreshRateRanges.physical.max).isPositiveInfinity();
-        assertThat(appRequestSize.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestSize.refreshRateRanges.render.max).isPositiveInfinity();
-        assertThat(appRequestSize.height).isEqualTo(1000);
-        assertThat(appRequestSize.width).isEqualTo(1000);
-
-        appRequestRefreshRateRange =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
-        assertNull(appRequestRefreshRateRange);
-    }
-
-    @Test
-    public void testAppRequestObserver_minRefreshRate() {
-        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
-        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 60, 0);
-        Vote appRequestRefreshRate =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
-        assertNull(appRequestRefreshRate);
-
-        Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
-        assertNull(appRequestSize);
-
-        Vote appRequestRefreshRateRange =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
-        assertNotNull(appRequestRefreshRateRange);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                .isPositiveInfinity();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min)
-                .isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max).isAtLeast(90);
-        assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE);
-
-        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 90, 0);
-        appRequestRefreshRate =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
-        assertNull(appRequestRefreshRate);
-
-        appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
-        assertNull(appRequestSize);
-
-        appRequestRefreshRateRange =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
-        assertNotNull(appRequestRefreshRateRange);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                .isPositiveInfinity();
-
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min)
-                .isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max).isAtLeast(90);
-        assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE);
-    }
-
-    @Test
-    public void testAppRequestObserver_maxRefreshRate() {
-        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
-        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 0, 90);
-        Vote appRequestRefreshRate =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
-        assertNull(appRequestRefreshRate);
-
-        Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
-        assertNull(appRequestSize);
-
-        Vote appRequestRefreshRateRange =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
-        assertNotNull(appRequestRefreshRateRange);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                .isPositiveInfinity();
-
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max)
-                .isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE);
-
-        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 0, 60);
-        appRequestRefreshRate =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
-        assertNull(appRequestRefreshRate);
-
-        appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
-        assertNull(appRequestSize);
-
-        appRequestRefreshRateRange =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
-        assertNotNull(appRequestRefreshRateRange);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                .isPositiveInfinity();
-
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max)
-                .isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE);
-    }
-
-    @Test
-    public void testAppRequestObserver_invalidRefreshRateRange() {
-        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
-        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, -1, 90, 60);
-        Vote appRequestRefreshRate =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
-        assertNull(appRequestRefreshRate);
-
-        Vote appRequestSize = director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
-        assertNull(appRequestSize);
-
-        Vote appRequestRefreshRateRange =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
-        assertNull(appRequestRefreshRateRange);
-    }
-
-    @Test
-    public void testAppRequestObserver_modeIdAndRefreshRateRange() {
-        DisplayModeDirector director = createDirectorFromFpsRange(60, 90);
-        director.getAppRequestObserver().setAppRequest(DISPLAY_ID, 60, 90, 90);
-
-        Vote appRequestRefreshRate =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE);
-        assertNotNull(appRequestRefreshRate);
-        assertThat(appRequestRefreshRate.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRate.refreshRateRanges.physical.max).isPositiveInfinity();
-        assertThat(appRequestRefreshRate.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestRefreshRate.refreshRateRanges.render.max).isPositiveInfinity();
-        assertThat(appRequestRefreshRate.disableRefreshRateSwitching).isFalse();
-        assertThat(appRequestRefreshRate.appRequestBaseModeRefreshRate)
-                .isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(appRequestRefreshRate.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRate.width).isEqualTo(INVALID_SIZE);
-
-        Vote appRequestSize =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_SIZE);
-        assertNotNull(appRequestSize);
-        assertThat(appRequestSize.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestSize.refreshRateRanges.physical.max).isPositiveInfinity();
-        assertThat(appRequestSize.refreshRateRanges.render.min).isZero();
-        assertThat(appRequestSize.refreshRateRanges.render.max).isPositiveInfinity();
-        assertThat(appRequestSize.height).isEqualTo(1000);
-        assertThat(appRequestSize.width).isEqualTo(1000);
-
-        Vote appRequestRefreshRateRange =
-                director.getVote(DISPLAY_ID, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE);
-        assertNotNull(appRequestRefreshRateRange);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.min).isZero();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.physical.max)
-                .isPositiveInfinity();
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.min)
-                .isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(appRequestRefreshRateRange.refreshRateRanges.render.max)
-                .isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(appRequestRefreshRateRange.height).isEqualTo(INVALID_SIZE);
-        assertThat(appRequestRefreshRateRange.width).isEqualTo(INVALID_SIZE);
-    }
-
-    @Test
-    public void testAppRequestsIsTheDefaultMode() {
-        Display.Mode[] modes = new Display.Mode[2];
-        modes[0] = new Display.Mode(
-                /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
-        modes[1] = new Display.Mode(
-                /*modeId=*/2, /*width=*/1000, /*height=*/1000, 90);
-
-        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(1);
-        assertThat(desiredSpecs.primary.physical.min).isAtMost(60);
-        assertThat(desiredSpecs.primary.physical.max).isAtLeast(90);
-
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        Display.Mode appRequestedMode = modes[1];
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
-        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
-                appRequestedMode.getPhysicalHeight()));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(2);
-        assertThat(desiredSpecs.primary.physical.min).isAtMost(60);
-        assertThat(desiredSpecs.primary.physical.max).isAtLeast(90);
-    }
-
-    @Test
-    public void testDisableRefreshRateSwitchingVote() {
-        DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(90, Float.POSITIVE_INFINITY));
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
-        director.injectVotesByDisplay(votesByDisplay);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(50);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(50);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(50);
-
-        votes.clear();
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE,
-                Vote.forPhysicalRefreshRates(70, Float.POSITIVE_INFINITY));
-        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(80, Float.POSITIVE_INFINITY));
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 90));
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(80);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(80);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(80);
-
-        votes.clear();
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE,
-                Vote.forPhysicalRefreshRates(90, Float.POSITIVE_INFINITY));
-        votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(80, Float.POSITIVE_INFINITY));
-        votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching());
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 90));
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
-    }
-
-    @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testBaseModeIdInPrimaryRange(boolean supportsFrameRateOverride) {
-        when(mInjector.supportsFrameRateOverride()).thenReturn(supportsFrameRateOverride);
-        DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(70));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
-        director.injectVotesByDisplay(votesByDisplay);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        if (supportsFrameRateOverride) {
-            assertThat(desiredSpecs.baseModeId).isEqualTo(70);
-        } else {
-            assertThat(desiredSpecs.baseModeId).isEqualTo(50);
-
-        }
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-
-        votes.clear();
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(55));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(55);
-
-        votes.clear();
-        votes.put(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
-                Vote.forRenderFrameRates(0, 52));
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(55));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        if (supportsFrameRateOverride) {
-            assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(52);
-        } else {
-            assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        }
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(55);
-
-        votes.clear();
-        votes.put(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
-                Vote.forRenderFrameRates(0, 58));
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(55));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.primary.physical.max).isPositiveInfinity();
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(58);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(55);
-    }
-
-    @Test
-    public void testStaleAppVote() {
-        Display.Mode[] modes = new Display.Mode[4];
-        modes[0] = new Display.Mode(
-                /*modeId=*/1, /*width=*/1000, /*height=*/1000, 60);
-        modes[1] = new Display.Mode(
-                /*modeId=*/2, /*width=*/2000, /*height=*/2000, 60);
-        modes[2] = new Display.Mode(
-                /*modeId=*/3, /*width=*/1000, /*height=*/1000, 90);
-        modes[3] = new Display.Mode(
-                /*modeId=*/4, /*width=*/2000, /*height=*/2000, 90);
-
-        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[0]);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        Display.Mode appRequestedMode = modes[3];
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate()));
-        votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(),
-                appRequestedMode.getPhysicalHeight()));
-        director.injectVotesByDisplay(votesByDisplay);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(4);
-
-        // Change mode Id's to simulate that a hotplug has occurred.
-        Display.Mode[] newModes = new Display.Mode[4];
-        newModes[0] = new Display.Mode(
-                /*modeId=*/5, /*width=*/1000, /*height=*/1000, 60);
-        newModes[1] = new Display.Mode(
-                /*modeId=*/6, /*width=*/2000, /*height=*/2000, 60);
-        newModes[2] = new Display.Mode(
-                /*modeId=*/7, /*width=*/1000, /*height=*/1000, 90);
-        newModes[3] = new Display.Mode(
-                /*modeId=*/8, /*width=*/2000, /*height=*/2000, 90);
-
-        SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
-        supportedModesByDisplay.put(DISPLAY_ID, newModes);
-        director.injectSupportedModesByDisplay(supportedModesByDisplay);
-        SparseArray<Display.Mode> defaultModesByDisplay = new SparseArray<>();
-        defaultModesByDisplay.put(DISPLAY_ID, newModes[0]);
-        director.injectDefaultModeByDisplay(defaultModesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(8);
-    }
-
-    @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testRefreshRateIsSubsetOfFrameRate(boolean supportsFrameRateOverride) {
-        when(mInjector.supportsFrameRateOverride()).thenReturn(supportsFrameRateOverride);
-        DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-
-
-        votes.put(Vote.PRIORITY_UDFPS, Vote.forPhysicalRefreshRates(90, 120));
-        director.injectVotesByDisplay(votesByDisplay);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
-        assertThat(desiredSpecs.appRequest.render.min).isZero();
-        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
-
-        votes.clear();
-        votes.put(Vote.PRIORITY_UDFPS, Vote.forPhysicalRefreshRates(90, 120));
-        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(0, 60));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
-        assertThat(desiredSpecs.appRequest.render.min).isZero();
-        if (supportsFrameRateOverride) {
-            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        } else {
-            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
-        }
-
-        votes.clear();
-        votes.put(Vote.PRIORITY_UDFPS, Vote.forPhysicalRefreshRates(90, 120));
-        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(60, 60));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
-        if (supportsFrameRateOverride) {
-            assertThat(desiredSpecs.appRequest.render.min).isWithin(FLOAT_TOLERANCE).of(60);
-            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        } else {
-            assertThat(desiredSpecs.appRequest.render.min).isZero();
-            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
-        }
-
-        votes.clear();
-        votes.put(Vote.PRIORITY_UDFPS, Vote.forPhysicalRefreshRates(90, 120));
-        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(140, 140));
-        director.injectVotesByDisplay(votesByDisplay);
-        desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
-        assertThat(desiredSpecs.appRequest.render.min).isZero();
-        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
-    }
-
-    @Test
-    public void testRenderFrameRateIsAchievableByPhysicalRefreshRate() {
-        DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-
-
-        votes.put(Vote.PRIORITY_UDFPS, Vote.forPhysicalRefreshRates(120, 120));
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90));
-        director.injectVotesByDisplay(votesByDisplay);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(120);
-        assertThat(desiredSpecs.appRequest.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
-        assertThat(desiredSpecs.appRequest.render.min).isZero();
-        assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(120);
-    }
-
-    @Test
-    @Parameters({
-            "true",
-            "false"
-    })
-    public void testRenderFrameRateIncludesPhysicalRefreshRate(boolean supportsFrameRateOverride) {
-        when(mInjector.supportsFrameRateOverride()).thenReturn(supportsFrameRateOverride);
-        DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-
-        votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60));
-        votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
-                Vote.forRenderFrameRates(0, 30));
-        director.injectVotesByDisplay(votesByDisplay);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.appRequest.physical.min).isZero();
-        assertThat(desiredSpecs.appRequest.physical.max).isPositiveInfinity();
-        assertThat(desiredSpecs.appRequest.render.min).isZero();
-        if (supportsFrameRateOverride) {
-            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(30);
-        } else {
-            assertThat(desiredSpecs.appRequest.render.max).isWithin(FLOAT_TOLERANCE).of(60);
-        }
-    }
-
-    @Test
-    public void testRenderFrameRateIsDroppedIfLowerPriorityThenBaseModeRefreshRate() {
-        DisplayModeDirector director = createDirectorFromFpsRange(60, 120);
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        votes.put(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
-                Vote.forRenderFrameRates(120, 120));
-        votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
-                Vote.forBaseModeRefreshRate(90));
-        votes.put(Vote.PRIORITY_PROXIMITY, Vote.forPhysicalRefreshRates(60, 120));
-        director.injectVotesByDisplay(votesByDisplay);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(60);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(120);
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(0);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(120);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
-    }
-
-    @Test
-    public void testRenderFrameRateIsAchievableWhenSwitchingTypeNone() {
-        Display.Mode[] modes = new Display.Mode[2];
-        modes[0] = new Display.Mode(
-                /*modeId=*/60, /*width=*/1000, /*height=*/1000, 60);
-        modes[1] = new Display.Mode(
-                /*modeId=*/90, /*width=*/1000, /*height=*/1000, 90);
-
-        DisplayModeDirector director = createDirectorFromModeArray(modes, modes[1]);
-        director.setModeSwitchingType(DisplayManager.SWITCHING_TYPE_NONE);
-
-        SparseArray<Vote> votes = new SparseArray<>();
-        SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
-        votesByDisplay.put(DISPLAY_ID, votes);
-        votes.put(Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
-                Vote.forRenderFrameRates(0, 60));
-        director.injectVotesByDisplay(votesByDisplay);
-        DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID);
-        assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.render.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(desiredSpecs.baseModeId).isEqualTo(90);
-    }
-
-    @Test
-    public void testProximitySensorVoting() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[]{60.f, 90.f}, 0);
-        director.start(createMockSensorManager());
-
-        ArgumentCaptor<ProximityActiveListener> ProximityCaptor =
-                ArgumentCaptor.forClass(ProximityActiveListener.class);
-        verify(mSensorManagerInternalMock).addProximityActiveListener(any(Executor.class),
-                ProximityCaptor.capture());
-        ProximityActiveListener proximityListener = ProximityCaptor.getValue();
-
-        ArgumentCaptor<DisplayListener> DisplayCaptor =
-                ArgumentCaptor.forClass(DisplayListener.class);
-        verify(mInjector, times(2)).registerDisplayListener(DisplayCaptor.capture(),
-                any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
-        DisplayListener displayListener = DisplayCaptor.getAllValues().get(0);
-
-        // Verify that there is no proximity vote initially
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_PROXIMITY);
-        assertNull(vote);
-
-        when(mDisplayManagerInternalMock.getRefreshRateForDisplayAndSensor(eq(DISPLAY_ID), eq(null),
-                  eq(Sensor.STRING_TYPE_PROXIMITY))).thenReturn(new RefreshRateRange(60, 60));
-
-        when(mInjector.isDozeState(any(Display.class))).thenReturn(false);
-
-        // Set the proximity to active and verify that we added a vote.
-        proximityListener.onProximityActive(true);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_PROXIMITY);
-        assertVoteForPhysicalRefreshRate(vote, 60.f);
-
-        // Set the display state to doze and verify that the vote is gone
-        when(mInjector.isDozeState(any(Display.class))).thenReturn(true);
-        displayListener.onDisplayAdded(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_PROXIMITY);
-        assertNull(vote);
-
-        // Set the display state to on and verify that we added the vote back.
-        when(mInjector.isDozeState(any(Display.class))).thenReturn(false);
-        displayListener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_PROXIMITY);
-        assertVoteForPhysicalRefreshRate(vote, 60.f);
-
-        // Set the display state to doze and verify that the vote is gone
-        when(mInjector.isDozeState(any(Display.class))).thenReturn(true);
-        displayListener.onDisplayAdded(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_PROXIMITY);
-        assertNull(vote);
-
-        // Remove the display to cause the doze state to be removed
-        displayListener.onDisplayRemoved(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_PROXIMITY);
-        assertVoteForPhysicalRefreshRate(vote, 60.f);
-
-        // Turn prox off and verify vote is gone.
-        proximityListener.onProximityActive(false);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_PROXIMITY);
-        assertNull(vote);
-    }
-
-    @Test
-    public void testHbmVoting_forHdr() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
-        final int hbmRefreshRate = 72;
-
-        // Specify limitation before starting DisplayModeDirector to avoid waiting on property
-        // propagation
-        mInjector.getDeviceConfig().setRefreshRateInHbmHdr(hbmRefreshRate);
-
-        director.start(createMockSensorManager());
-
-        ArgumentCaptor<DisplayListener> captor =
-                  ArgumentCaptor.forClass(DisplayListener.class);
-        verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
-        DisplayListener listener = captor.getValue();
-
-        // Specify Limitation
-        when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn(
-                List.of(new RefreshRateLimitation(
-                        DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
-                        60.f, 60.f)));
-
-        // Verify that there is no HBM vote initially
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-
-        // Turn on HBM, with brightness in the HBM range
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
-                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
-                    BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertVoteForPhysicalRefreshRate(vote, hbmRefreshRate);
-
-        // Turn on HBM, with brightness below the HBM range
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
-                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
-                    BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-
-        // Turn off HBM
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
-                    TRANSITION_POINT,
-                    BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-
-        // Turn on HBM, with brightness in the HBM range
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
-                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
-                    BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertVoteForPhysicalRefreshRate(vote, hbmRefreshRate);
-
-        // Turn off HBM
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
-                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-
-        // Turn on HBM, with brightness below the HBM range
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
-                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT,
-                    BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-
-        // Turn off HBM
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
-                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-    }
-
-    @Test
-    public void testHbmObserverGetsUpdatedRefreshRateInHbmSunlight() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
-
-        final int initialRefreshRate = 60;
-        mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(initialRefreshRate);
-        director.start(createMockSensorManager());
-        assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight())
-                .isEqualTo(initialRefreshRate);
-
-        final int updatedRefreshRate = 90;
-        mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(updatedRefreshRate);
-        // Need to wait for the property change to propagate to the main thread.
-        waitForIdleSync();
-        assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight())
-                .isEqualTo(updatedRefreshRate);
-    }
-
-    @Test
-    public void testHbmObserverGetsUpdatedRefreshRateInHbmHdr() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
-
-        final int initialRefreshRate = 60;
-        mInjector.getDeviceConfig().setRefreshRateInHbmHdr(initialRefreshRate);
-        director.start(createMockSensorManager());
-        assertThat(director.getHbmObserver().getRefreshRateInHbmHdr())
-                .isEqualTo(initialRefreshRate);
-
-        final int updatedRefreshRate = 90;
-        mInjector.getDeviceConfig().setRefreshRateInHbmHdr(updatedRefreshRate);
-        // Need to wait for the property change to propagate to the main thread.
-        waitForIdleSync();
-        assertThat(director.getHbmObserver().getRefreshRateInHbmHdr())
-                .isEqualTo(updatedRefreshRate);
-    }
-
-    @Test
-    public void testHbmVoting_forSunlight() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
-        director.start(createMockSensorManager());
-
-        ArgumentCaptor<DisplayListener> captor =
-                  ArgumentCaptor.forClass(DisplayListener.class);
-        verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
-        DisplayListener listener = captor.getValue();
-
-        final int initialRefreshRate = 60;
-        // Specify Limitation
-        when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn(
-                List.of(new RefreshRateLimitation(
-                        DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
-                        initialRefreshRate, initialRefreshRate)));
-
-        // Verify that there is no HBM vote initially
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-
-        // Turn on HBM
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
-                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertVoteForPhysicalRefreshRate(vote, initialRefreshRate);
-
-        // Change refresh rate vote value through DeviceConfig, ensure it takes precedence
-        final int updatedRefreshRate = 90;
-        mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(updatedRefreshRate);
-        // Need to wait for the property change to propagate to the main thread.
-        waitForIdleSync();
-        assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight())
-                .isEqualTo(updatedRefreshRate);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertVoteForPhysicalRefreshRate(vote, updatedRefreshRate);
-
-        // Turn off HBM
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
-                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-
-        // Turn HBM on again and ensure the updated vote value stuck
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
-                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertVoteForPhysicalRefreshRate(vote, updatedRefreshRate);
-
-        // Reset DeviceConfig refresh rate, ensure vote falls back to the initial value
-        mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(0);
-        // Need to wait for the property change to propagate to the main thread.
-        waitForIdleSync();
-        assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight()).isEqualTo(0);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertVoteForPhysicalRefreshRate(vote, initialRefreshRate);
-
-        // Turn off HBM
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
-                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-    }
-
-    @Test
-    public void testHbmVoting_forSunlight_NoLimitation() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
-        director.start(createMockSensorManager());
-
-        ArgumentCaptor<DisplayListener> captor =
-                  ArgumentCaptor.forClass(DisplayListener.class);
-        verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
-        DisplayListener listener = captor.getValue();
-
-        // Specify Limitation for different display
-        when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID + 1)).thenReturn(
-                List.of(new RefreshRateLimitation(
-                        DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
-                        60.f, 60.f)));
-
-        // Verify that there is no HBM vote initially
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-
-        // Turn on HBM
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
-                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-
-        // Turn off HBM
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
-                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-    }
-
-    @Test
-    public void testHbmVoting_HbmUnsupported() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
-        director.start(createMockSensorManager());
-
-        ArgumentCaptor<DisplayListener> captor =
-                  ArgumentCaptor.forClass(DisplayListener.class);
-        verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
-        DisplayListener listener = captor.getValue();
-
-        // Specify Limitation
-        when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn(
-                List.of(new RefreshRateLimitation(
-                        DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
-                        60.0f, 60.0f)));
-
-        // Verify that there is no HBM vote initially
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-
-        // Turn on HBM when HBM is supported; expect a valid transition point and a vote.
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
-                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertVoteForPhysicalRefreshRate(vote, 60.0f);
-
-        // Turn off HBM
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
-                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-
-        // Turn on Sunlight HBM when HBM is unsupported; expect an invalid transition point and
-        // no vote.
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
-                    HBM_TRANSITION_POINT_INVALID, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-
-        // Turn on HDR HBM when HBM is unsupported; expect an invalid transition point and
-        // no vote.
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
-                    HBM_TRANSITION_POINT_INVALID, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-
-        // Turn off HBM
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
-                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-    }
-
-    private void setHbmAndAssertRefreshRate(
-            DisplayModeDirector director, DisplayListener listener, int mode, float rr) {
-        when(mInjector.getBrightnessInfo(DISPLAY_ID))
-                .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode, TRANSITION_POINT,
-                    BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-
-        final Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        if (Float.isNaN(rr)) {
-            assertNull(vote);
-        } else {
-            assertVoteForPhysicalRefreshRate(vote, rr);
-        }
-    }
-
-    @Test
-    public void testHbmVoting_forSunlightAndHdr() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
-
-        // Specify HDR limitation before starting DisplayModeDirector to avoid waiting on property
-        // propagation
-        final int hdrRr = 60;
-        mInjector.getDeviceConfig().setRefreshRateInHbmHdr(hdrRr);
-        director.start(createMockSensorManager());
-
-        ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class);
-        verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
-        DisplayListener listener = captor.getValue();
-
-        // Specify Sunlight limitations
-        final float sunlightRr = 90.0f;
-        when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID))
-                .thenReturn(List.of(new RefreshRateLimitation(
-                        DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, sunlightRr,
-                        sunlightRr)));
-
-        // Verify that there is no HBM vote initially
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-
-        // Verify all state transitions
-        setHbmAndAssertRefreshRate(
-                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, sunlightRr);
-        setHbmAndAssertRefreshRate(
-                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, hdrRr);
-        setHbmAndAssertRefreshRate(
-                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, Float.NaN);
-        setHbmAndAssertRefreshRate(
-                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, hdrRr);
-        setHbmAndAssertRefreshRate(
-                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, sunlightRr);
-        setHbmAndAssertRefreshRate(
-                director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, Float.NaN);
-    }
-
-    @Test
-    public void testHbmVoting_RemovedDisplay() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
-        director.start(createMockSensorManager());
-
-        ArgumentCaptor<DisplayListener> captor =
-                  ArgumentCaptor.forClass(DisplayListener.class);
-        verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
-        DisplayListener listener = captor.getValue();
-
-        // Specify Limitation for different display
-        when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn(
-                List.of(new RefreshRateLimitation(
-                        DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
-                        60.f, 60.f)));
-
-        // Verify that there is no HBM vote initially
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-
-        // Turn on HBM
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
-                    TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertVoteForPhysicalRefreshRate(vote, 60.f);
-
-        // Turn off HBM
-        listener.onDisplayRemoved(DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
-        assertNull(vote);
-    }
-
-    @Test
-    public void testSkinTemperature() throws RemoteException {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
-        director.start(createMockSensorManager());
-
-        ArgumentCaptor<IThermalEventListener> thermalEventListener =
-                ArgumentCaptor.forClass(IThermalEventListener.class);
-
-        verify(mInjector).registerThermalServiceListener(thermalEventListener.capture());
-        final IThermalEventListener listener = thermalEventListener.getValue();
-
-        // Verify that there is no skin temperature vote initially.
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
-        assertNull(vote);
-
-        // Set the skin temperature to critical and verify that we added a vote.
-        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
-        BackgroundThread.getHandler().runWithScissors(() -> { }, 500 /*timeout*/);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
-        assertVoteForRenderFrameRateRange(vote, 0f, 60.f);
-
-        // Set the skin temperature to severe and verify that the vote is gone.
-        listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_SEVERE));
-        BackgroundThread.getHandler().runWithScissors(() -> { }, 500 /*timeout*/);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
-        assertNull(vote);
-    }
-
-    @Test
-    public void testNotifyDefaultDisplayDeviceUpdated() {
-        Resources resources = mock(Resources.class);
-        when(mContext.getResources()).thenReturn(resources);
-        when(resources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
-            .thenReturn(75);
-        when(resources.getInteger(R.integer.config_defaultRefreshRate))
-            .thenReturn(45);
-        when(resources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
-            .thenReturn(65);
-        when(resources.getInteger(R.integer.config_defaultRefreshRateInZone))
-            .thenReturn(85);
-        when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr))
-            .thenReturn(95);
-        when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight))
-            .thenReturn(100);
-        when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
-            .thenReturn(new int[]{5});
-        when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
-            .thenReturn(new int[]{10});
-        when(
-            resources.getIntArray(R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
-            .thenReturn(new int[]{250});
-        when(
-            resources.getIntArray(R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
-            .thenReturn(new int[]{7000});
-        when(resources.getInteger(
-            com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
-            .thenReturn(3);
-        ArgumentCaptor<TypedValue> valueArgumentCaptor = ArgumentCaptor.forClass(TypedValue.class);
-        doAnswer((Answer<Void>) invocation -> {
-            valueArgumentCaptor.getValue().type = 4;
-            valueArgumentCaptor.getValue().data = 13;
-            return null;
-        }).when(resources).getValue(eq(com.android.internal.R.dimen
-                .config_displayWhiteBalanceBrightnessFilterIntercept),
-                valueArgumentCaptor.capture(), eq(true));
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
-        SensorManager sensorManager = createMockSensorManager(createLightSensor());
-        director.start(sensorManager);
-        // We don't expect any interaction with DeviceConfig when the director is initialized
-        // because we explicitly avoid doing this as this can lead to a latency spike in the
-        // startup of DisplayManagerService
-        // Verify all the loaded values are from config.xml
-        assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 45, 0.0);
-        assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 75,
-                0.0);
-        assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 65);
-        assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 85);
-        assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 95);
-        assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 100);
-        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
-                new int[]{250});
-        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
-                new int[]{7000});
-        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
-                new int[]{5});
-        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
-                new int[]{10});
-
-
-        // Notify that the default display is updated, such that DisplayDeviceConfig has new values
-        DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
-        when(displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50);
-        when(displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55);
-        when(displayDeviceConfig.getDefaultRefreshRate()).thenReturn(60);
-        when(displayDeviceConfig.getDefaultPeakRefreshRate()).thenReturn(65);
-        when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
-        when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
-        when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
-        when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
-        when(displayDeviceConfig.getDefaultRefreshRateInHbmHdr()).thenReturn(65);
-        when(displayDeviceConfig.getDefaultRefreshRateInHbmSunlight()).thenReturn(75);
-        director.defaultDisplayDeviceUpdated(displayDeviceConfig);
-
-        // Verify the new values are from the freshly loaded DisplayDeviceConfig.
-        assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
-        assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65,
-                0.0);
-        assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 55);
-        assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 50);
-        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
-                new int[]{210});
-        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
-                new int[]{2100});
-        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
-                new int[]{25});
-        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
-                new int[]{30});
-        assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 65);
-        assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 75);
-
-        // Notify that the default display is updated, such that DeviceConfig has new values
-        FakeDeviceConfig config = mInjector.getDeviceConfig();
-        config.setDefaultPeakRefreshRate(60);
-        config.setRefreshRateInHighZone(65);
-        config.setRefreshRateInLowZone(70);
-        config.setLowAmbientBrightnessThresholds(new int[]{20});
-        config.setLowDisplayBrightnessThresholds(new int[]{10});
-        config.setHighDisplayBrightnessThresholds(new int[]{255});
-        config.setHighAmbientBrightnessThresholds(new int[]{8000});
-        config.setRefreshRateInHbmHdr(70);
-        config.setRefreshRateInHbmSunlight(80);
-        // Need to wait for the property change to propagate to the main thread.
-        waitForIdleSync();
-
-        // Verify the values are loaded from the DeviceConfig.
-        assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
-        assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 60,
-                0.0);
-        assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 65);
-        assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 70);
-        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
-                new int[]{255});
-        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
-                new int[]{8000});
-        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
-                new int[]{10});
-        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
-                new int[]{20});
-        assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 70);
-        assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 80);
-
-        // Reset the DeviceConfig
-        config.setDefaultPeakRefreshRate(null);
-        config.setRefreshRateInHighZone(null);
-        config.setRefreshRateInLowZone(null);
-        config.setLowAmbientBrightnessThresholds(new int[]{});
-        config.setLowDisplayBrightnessThresholds(new int[]{});
-        config.setHighDisplayBrightnessThresholds(new int[]{});
-        config.setHighAmbientBrightnessThresholds(new int[]{});
-        config.setRefreshRateInHbmHdr(null);
-        config.setRefreshRateInHbmSunlight(null);
-        waitForIdleSync();
-
-        // verify the new values now fallback to DisplayDeviceConfig
-        assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
-        assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65,
-                0.0);
-        assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 55);
-        assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 50);
-        assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
-                new int[]{210});
-        assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
-                new int[]{2100});
-        assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
-                new int[]{25});
-        assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
-                new int[]{30});
-        assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 65);
-        assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 75);
-    }
-
-    @Test
-    public void testSensorReloadOnDeviceSwitch() throws Exception {
-        // First, configure brightness zones or DMD won't register for sensor data.
-        final FakeDeviceConfig config = mInjector.getDeviceConfig();
-        config.setRefreshRateInHighZone(60);
-        config.setHighDisplayBrightnessThresholds(new int[] { 255 });
-        config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
-
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
-        setPeakRefreshRate(90 /*fps*/);
-        director.getSettingsObserver().setDefaultRefreshRate(90);
-        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
-
-        Sensor lightSensorOne = TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
-        Sensor lightSensorTwo = TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
-        SensorManager sensorManager = createMockSensorManager(lightSensorOne, lightSensorTwo);
-        when(sensorManager.getDefaultSensor(5)).thenReturn(lightSensorOne, lightSensorTwo);
-        director.start(sensorManager);
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
-                .registerListener(
-                        listenerCaptor.capture(),
-                        eq(lightSensorOne),
-                        anyInt(),
-                        any(Handler.class));
-
-        DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class);
-        when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50);
-        when(ddcMock.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55);
-        when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
-        when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
-        when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
-        when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
-
-        Resources resMock = mock(Resources.class);
-        when(resMock.getInteger(
-                com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
-                .thenReturn(3);
-        ArgumentCaptor<TypedValue> valueArgumentCaptor = ArgumentCaptor.forClass(TypedValue.class);
-        doAnswer((Answer<Void>) invocation -> {
-            valueArgumentCaptor.getValue().type = 4;
-            valueArgumentCaptor.getValue().data = 13;
-            return null;
-        }).when(resMock).getValue(anyInt(), valueArgumentCaptor.capture(), eq(true));
-        when(mContext.getResources()).thenReturn(resMock);
-
-        director.defaultDisplayDeviceUpdated(ddcMock);
-
-        verify(sensorManager).unregisterListener(any(SensorEventListener.class));
-        verify(sensorManager).registerListener(any(SensorEventListener.class),
-                eq(lightSensorTwo), anyInt(), any(Handler.class));
-    }
-
-    @Test
-    public void testAuthenticationPossibleSetsPhysicalRateRangesToMax() throws RemoteException {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
-        // don't call director.start(createMockSensorManager());
-        // DisplayObserver will reset mSupportedModesByDisplay
-        director.onBootCompleted();
-        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
-                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
-        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
-
-        captor.getValue().onAuthenticationPossible(DISPLAY_ID, true);
-
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE);
-        assertThat(vote.refreshRateRanges.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(vote.refreshRateRanges.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
-    }
-
-    @Test
-    public void testAuthenticationPossibleUnsetsVote() throws RemoteException {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
-        director.start(createMockSensorManager());
-        director.onBootCompleted();
-        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
-                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
-        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
-        captor.getValue().onAuthenticationPossible(DISPLAY_ID, true);
-        captor.getValue().onAuthenticationPossible(DISPLAY_ID, false);
-
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE);
-        assertNull(vote);
-    }
-
-    @Test
-    public void testUdfpsRequestSetsPhysicalRateRangesToMax() throws RemoteException {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
-        // don't call director.start(createMockSensorManager());
-        // DisplayObserver will reset mSupportedModesByDisplay
-        director.onBootCompleted();
-        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
-                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
-        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
-
-        captor.getValue().onRequestEnabled(DISPLAY_ID);
-
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
-        assertThat(vote.refreshRateRanges.physical.min).isWithin(FLOAT_TOLERANCE).of(90);
-        assertThat(vote.refreshRateRanges.physical.max).isWithin(FLOAT_TOLERANCE).of(90);
-    }
-
-    @Test
-    public void testUdfpsRequestUnsetsUnsetsVote() throws RemoteException {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
-        director.start(createMockSensorManager());
-        director.onBootCompleted();
-        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
-                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
-        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
-        captor.getValue().onRequestEnabled(DISPLAY_ID);
-        captor.getValue().onRequestDisabled(DISPLAY_ID);
-
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
-        assertNull(vote);
-    }
-
-    @Test
-    public void testUpdateLayoutLimitedRefreshRate_validDisplayInfo() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
-        director.start(createMockSensorManager());
-
-        ArgumentCaptor<DisplayListener> displayListenerCaptor =
-                ArgumentCaptor.forClass(DisplayListener.class);
-        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
-                any(Handler.class));
-        DisplayListener displayListener = displayListenerCaptor.getValue();
-
-        float refreshRate = 60;
-        mInjector.mDisplayInfo.layoutLimitedRefreshRate =
-                new RefreshRateRange(refreshRate, refreshRate);
-        displayListener.onDisplayChanged(DISPLAY_ID);
-
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
-        assertVoteForPhysicalRefreshRate(vote, /* refreshRate= */ refreshRate);
-
-        mInjector.mDisplayInfo.layoutLimitedRefreshRate = null;
-        displayListener.onDisplayChanged(DISPLAY_ID);
-
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
-        assertNull(vote);
-    }
-
-    @Test
-    public void testUpdateLayoutLimitedRefreshRate_invalidDisplayInfo() {
-        DisplayModeDirector director =
-                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
-        director.start(createMockSensorManager());
-
-        ArgumentCaptor<DisplayListener> displayListenerCaptor =
-                ArgumentCaptor.forClass(DisplayListener.class);
-        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
-                any(Handler.class));
-        DisplayListener displayListener = displayListenerCaptor.getValue();
-
-        mInjector.mDisplayInfo.layoutLimitedRefreshRate = new RefreshRateRange(10, 10);
-        mInjector.mDisplayInfoValid = false;
-        displayListener.onDisplayChanged(DISPLAY_ID);
-
-        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE);
-        assertNull(vote);
-    }
-
-    private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
-        return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
-    }
-
-    private void assertVoteForPhysicalRefreshRate(Vote vote, float refreshRate) {
-        assertThat(vote).isNotNull();
-        final RefreshRateRange expectedRange = new RefreshRateRange(refreshRate, refreshRate);
-        assertThat(vote.refreshRateRanges.physical).isEqualTo(expectedRange);
-    }
-
-    private void assertVoteForRenderFrameRateRange(
-            Vote vote, float frameRateLow, float frameRateHigh) {
-        assertThat(vote).isNotNull();
-        final RefreshRateRange expectedRange =
-                new RefreshRateRange(frameRateLow, frameRateHigh);
-        assertThat(vote.refreshRateRanges.render).isEqualTo(expectedRange);
-    }
-
-    public static class FakeDeviceConfig extends FakeDeviceConfigInterface {
-        @Override
-        public String getProperty(String namespace, String name) {
-            Preconditions.checkArgument(DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace));
-            return super.getProperty(namespace, name);
-        }
-
-        @Override
-        public void addOnPropertiesChangedListener(
-                String namespace,
-                Executor executor,
-                DeviceConfig.OnPropertiesChangedListener listener) {
-            Preconditions.checkArgument(DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace));
-            super.addOnPropertiesChangedListener(namespace, executor, listener);
-        }
-
-        void setRefreshRateInLowZone(Integer fps) {
-            putPropertyAndNotify(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_LOW_ZONE,
-                    String.valueOf(fps));
-        }
-
-        void setRefreshRateInHbmSunlight(Integer fps) {
-            putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, String.valueOf(fps));
-        }
-
-        void setRefreshRateInHbmHdr(Integer fps) {
-            putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps));
-        }
-
-        public void setThermalBrightnessThrottlingData(String brightnessThrottlingData) {
-            putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    KEY_BRIGHTNESS_THROTTLING_DATA, brightnessThrottlingData);
-        }
-
-        void setLowDisplayBrightnessThresholds(int[] brightnessThresholds) {
-            String thresholds = toPropertyValue(brightnessThresholds);
-
-            if (DEBUG) {
-                Slog.e(TAG, "Brightness Thresholds = " + thresholds);
-            }
-
-            putPropertyAndNotify(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS,
-                    thresholds);
-        }
-
-        void setLowAmbientBrightnessThresholds(int[] ambientThresholds) {
-            String thresholds = toPropertyValue(ambientThresholds);
-
-            if (DEBUG) {
-                Slog.e(TAG, "Ambient Thresholds = " + thresholds);
-            }
-
-            putPropertyAndNotify(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS,
-                    thresholds);
-        }
-
-        void setRefreshRateInHighZone(Integer fps) {
-            putPropertyAndNotify(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_HIGH_ZONE,
-                    String.valueOf(fps));
-        }
-
-        void setDefaultPeakRefreshRate(Integer fps) {
-            putPropertyAndNotify(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_PEAK_REFRESH_RATE_DEFAULT,
-                    String.valueOf(fps));
-        }
-
-        void setHighDisplayBrightnessThresholds(int[] brightnessThresholds) {
-            String thresholds = toPropertyValue(brightnessThresholds);
-
-            if (DEBUG) {
-                Slog.e(TAG, "Brightness Thresholds = " + thresholds);
-            }
-
-            putPropertyAndNotify(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS,
-                    thresholds);
-        }
-
-        void setHighAmbientBrightnessThresholds(int[] ambientThresholds) {
-            String thresholds = toPropertyValue(ambientThresholds);
-
-            if (DEBUG) {
-                Slog.e(TAG, "Ambient Thresholds = " + thresholds);
-            }
-
-            putPropertyAndNotify(
-                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
-                    KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS,
-                    thresholds);
-        }
-
-        @NonNull
-        private static String toPropertyValue(@NonNull int[] intArray) {
-            return Arrays.stream(intArray)
-                    .mapToObj(Integer::toString)
-                    .collect(Collectors.joining(","));
-        }
-    }
-
-    private void setBrightness(int brightness, int adjustedBrightness, DisplayListener listener) {
-        float floatBri = BrightnessSynchronizer.brightnessIntToFloat(brightness);
-        float floatAdjBri = BrightnessSynchronizer.brightnessIntToFloat(adjustedBrightness);
-
-        when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
-                new BrightnessInfo(floatBri, floatAdjBri, 0.0f, 1.0f,
-                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT,
-                    BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
-        listener.onDisplayChanged(DISPLAY_ID);
-    }
-
-    private void setPeakRefreshRate(float fps) {
-        Settings.System.putFloat(mContext.getContentResolver(), Settings.System.PEAK_REFRESH_RATE,
-                 fps);
-        mInjector.notifyPeakRefreshRateChanged();
-        waitForIdleSync();
-    }
-
-    private static SensorManager createMockSensorManager(Sensor... sensors) {
-        SensorManager sensorManager = mock(SensorManager.class);
-        when(sensorManager.getSensorList(anyInt())).then((invocation) -> {
-            List<Sensor> requestedSensors = new ArrayList<>();
-            int type = invocation.getArgument(0);
-            for (Sensor sensor : sensors) {
-                if (sensor.getType() == type || type == Sensor.TYPE_ALL) {
-                    requestedSensors.add(sensor);
-                }
-            }
-            return requestedSensors;
-        });
-
-        when(sensorManager.getDefaultSensor(anyInt())).then((invocation) -> {
-            int type = invocation.getArgument(0);
-            for (Sensor sensor : sensors) {
-                if (sensor.getType() == type) {
-                    return sensor;
-                }
-            }
-            return null;
-        });
-        return sensorManager;
-    }
-
-    private static Sensor createLightSensor() {
-        try {
-            return TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
-        } catch (Exception e) {
-            // There's nothing we can do if this fails, just throw a RuntimeException so that we
-            // don't have to mark every function that might call this as throwing Exception
-            throw new RuntimeException("Failed to create a light sensor", e);
-        }
-    }
-
-    private void waitForIdleSync() {
-        mHandler.runWithScissors(() -> { }, 500 /*timeout*/);
-    }
-
-    public static class FakesInjector implements DisplayModeDirector.Injector {
-        private final FakeDeviceConfig mDeviceConfig;
-        private final DisplayInfo mDisplayInfo;
-        private final Display mDisplay;
-        private boolean mDisplayInfoValid = true;
-        private ContentObserver mBrightnessObserver;
-        private ContentObserver mPeakRefreshRateObserver;
-
-        FakesInjector() {
-            mDeviceConfig = new FakeDeviceConfig();
-            mDisplayInfo = new DisplayInfo();
-            mDisplayInfo.defaultModeId = MODE_ID;
-            mDisplayInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
-                    800, 600, /* refreshRate= */ 60)};
-            mDisplay = createDisplay(DISPLAY_ID);
-        }
-
-        @NonNull
-        public FakeDeviceConfig getDeviceConfig() {
-            return mDeviceConfig;
-        }
-
-        @Override
-        public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
-                @NonNull ContentObserver observer) {
-            mPeakRefreshRateObserver = observer;
-        }
-
-        @Override
-        public void registerDisplayListener(DisplayListener listener, Handler handler) {}
-
-        @Override
-        public void registerDisplayListener(DisplayListener listener, Handler handler, long flag) {}
-
-        @Override
-        public Display getDisplay(int displayId) {
-            return mDisplay;
-        }
-
-        @Override
-        public Display[] getDisplays() {
-            return new Display[] { mDisplay };
-        }
-
-        @Override
-        public boolean getDisplayInfo(int displayId, DisplayInfo displayInfo) {
-            displayInfo.copyFrom(mDisplayInfo);
-            return mDisplayInfoValid;
-        }
-
-        @Override
-        public BrightnessInfo getBrightnessInfo(int displayId) {
-            return null;
-        }
-
-        @Override
-        public boolean isDozeState(Display d) {
-            return false;
-        }
-
-        @Override
-        public boolean registerThermalServiceListener(IThermalEventListener listener) {
-            return true;
-        }
-
-        @Override
-        public boolean supportsFrameRateOverride() {
-            return true;
-        }
-
-        protected Display createDisplay(int id) {
-            return new Display(DisplayManagerGlobal.getInstance(), id, mDisplayInfo,
-                    ApplicationProvider.getApplicationContext().getResources());
-        }
-
-        void notifyPeakRefreshRateChanged() {
-            if (mPeakRefreshRateObserver != null) {
-                mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/,
-                        PEAK_REFRESH_RATE_URI);
-            }
-        }
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
deleted file mode 100644
index ac97911..0000000
--- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
+++ /dev/null
@@ -1,559 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display.whitebalance;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.content.ContextWrapper;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.hardware.Sensor;
-import android.hardware.SensorManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.TypedValue;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.R;
-import com.android.server.LocalServices;
-import com.android.server.display.TestUtils;
-import com.android.server.display.color.ColorDisplayService;
-import com.android.server.display.utils.AmbientFilter;
-import com.android.server.display.utils.AmbientFilterStubber;
-
-import com.google.common.collect.ImmutableList;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public final class AmbientLuxTest {
-
-    private static final float ALLOWED_ERROR_DELTA = 0.001f;
-    private static final int AMBIENT_COLOR_TYPE = 20705;
-    private static final String AMBIENT_COLOR_TYPE_STR = "colorSensoryDensoryDoc";
-    private static final float LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE = 5432.1f;
-    private static final float HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE = 3456.7f;
-
-    private Handler mHandler = new Handler(Looper.getMainLooper());
-    private Sensor mLightSensor;
-    private Sensor mAmbientColorSensor;
-    private ContextWrapper mContextSpy;
-    private Resources mResourcesSpy;
-
-    @Mock private SensorManager mSensorManagerMock;
-
-    @Mock private TypedArray mBrightnesses;
-    @Mock private TypedArray mBiases;
-    @Mock private TypedArray mHighLightBrightnesses;
-    @Mock private TypedArray mHighLightBiases;
-    @Mock private TypedArray mAmbientColorTemperatures;
-    @Mock private TypedArray mDisplayColorTemperatures;
-    @Mock private TypedArray mStrongAmbientColorTemperatures;
-    @Mock private TypedArray mStrongDisplayColorTemperatures;
-    @Mock private ColorDisplayService.ColorDisplayServiceInternal mColorDisplayServiceInternalMock;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mLightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, null);
-        mAmbientColorSensor = TestUtils.createSensor(AMBIENT_COLOR_TYPE, AMBIENT_COLOR_TYPE_STR);
-        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
-        mResourcesSpy = spy(mContextSpy.getResources());
-        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
-        when(mSensorManagerMock.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(mLightSensor);
-        final List<Sensor> sensorList = ImmutableList.of(mLightSensor, mAmbientColorSensor);
-        when(mSensorManagerMock.getSensorList(Sensor.TYPE_ALL)).thenReturn(sensorList);
-        when(mResourcesSpy.getString(
-                R.string.config_displayWhiteBalanceColorTemperatureSensorName))
-                .thenReturn(AMBIENT_COLOR_TYPE_STR);
-        when(mResourcesSpy.getInteger(
-                R.integer.config_displayWhiteBalanceDecreaseDebounce))
-                .thenReturn(0);
-        when(mResourcesSpy.getInteger(
-                R.integer.config_displayWhiteBalanceIncreaseDebounce))
-                .thenReturn(0);
-        mockResourcesFloat(R.dimen.config_displayWhiteBalanceLowLightAmbientColorTemperature,
-                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE);
-        mockResourcesFloat(R.dimen.config_displayWhiteBalanceHighLightAmbientColorTemperature,
-                HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceAmbientColorTemperatures))
-                .thenReturn(mAmbientColorTemperatures);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceDisplayColorTemperatures))
-                .thenReturn(mDisplayColorTemperatures);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceStrongAmbientColorTemperatures))
-                .thenReturn(mStrongAmbientColorTemperatures);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceStrongDisplayColorTemperatures))
-                .thenReturn(mStrongDisplayColorTemperatures);
-
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceLowLightAmbientBrightnesses))
-                .thenReturn(mBrightnesses);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceLowLightAmbientBiases))
-                .thenReturn(mBiases);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceHighLightAmbientBrightnesses))
-                .thenReturn(mHighLightBrightnesses);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceHighLightAmbientBiases))
-                .thenReturn(mHighLightBiases);
-        mockThrottler();
-        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
-        LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
-                mColorDisplayServiceInternalMock);
-    }
-
-    @Test
-    public void testCalculateAdjustedBrightnessNits() {
-        doReturn(0.9f).when(mColorDisplayServiceInternalMock).getDisplayWhiteBalanceLuminance();
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float adjustedNits = controller.calculateAdjustedBrightnessNits(500f);
-        assertEquals(/* expected= */ 550f, adjustedNits, /* delta= */ 0.001);
-    }
-
-    @Test
-    public void testNoSpline() throws Exception {
-        setBrightnesses();
-        setBiases();
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 8000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
-            setEstimatedBrightnessAndUpdate(controller, luxOverride);
-            assertEquals(controller.mPendingAmbientColorTemperature,
-                    ambientColorTemperature, 0.001);
-        }
-    }
-
-    @Test
-    public void testSpline_OneSegment() throws Exception {
-        final float lowerBrightness = 10.0f;
-        final float upperBrightness = 50.0f;
-        setBrightnesses(lowerBrightness, upperBrightness);
-        setBiases(0.0f, 1.0f);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 8000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
-            setEstimatedBrightnessAndUpdate(controller,
-                    mix(lowerBrightness, upperBrightness, t));
-            assertEquals(controller.mPendingAmbientColorTemperature,
-                    mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, t), 0.001);
-        }
-
-        setEstimatedBrightnessAndUpdate(controller, 0.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
-
-        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
-    }
-
-    @Test
-    public void testSpline_TwoSegments() throws Exception {
-        final float brightness0 = 10.0f;
-        final float brightness1 = 50.0f;
-        final float brightness2 = 60.0f;
-        setBrightnesses(brightness0, brightness1, brightness2);
-        final float bias0 = 0.0f;
-        final float bias1 = 0.25f;
-        final float bias2 = 1.0f;
-        setBiases(bias0, bias1, bias2);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 8000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
-            float luxOverride = mix(brightness0, brightness1, t);
-            setEstimatedBrightnessAndUpdate(controller, luxOverride);
-            float bias = mix(bias0, bias1, t);
-            assertEquals(controller.mPendingAmbientColorTemperature,
-                    mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, bias), 0.001);
-        }
-
-        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
-            float luxOverride = mix(brightness1, brightness2, t);
-            setEstimatedBrightnessAndUpdate(controller, luxOverride);
-            float bias = mix(bias1, bias2, t);
-            assertEquals(controller.mPendingAmbientColorTemperature,
-                    mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, bias), 0.001);
-        }
-
-        setEstimatedBrightnessAndUpdate(controller, 0.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
-
-        setEstimatedBrightnessAndUpdate(controller, brightness2 + 1.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
-    }
-
-    @Test
-    public void testSpline_VerticalSegment() throws Exception {
-        final float lowerBrightness = 10.0f;
-        final float upperBrightness = 10.0f;
-        setBrightnesses(lowerBrightness, upperBrightness);
-        setBiases(0.0f, 1.0f);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 8000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        setEstimatedBrightnessAndUpdate(controller, 0.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
-
-        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
-    }
-
-    @Test
-    public void testSpline_InvalidEndBias() throws Exception {
-        setBrightnesses(10.0f, 1000.0f);
-        setBiases(0.0f, 2.0f);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 8000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
-        setEstimatedBrightnessAndUpdate(controller, luxOverride);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                ambientColorTemperature, 0.001);
-        }
-    }
-
-    @Test
-    public void testSpline_InvalidBeginBias() throws Exception {
-        setBrightnesses(10.0f, 1000.0f);
-        setBiases(0.1f, 1.0f);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 8000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
-        setEstimatedBrightnessAndUpdate(controller, luxOverride);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                ambientColorTemperature, 0.001);
-        }
-    }
-
-    @Test
-    public void testSpline_OneSegmentHighLight() throws Exception {
-        final float lowerBrightness = 10.0f;
-        final float upperBrightness = 50.0f;
-        setHighLightBrightnesses(lowerBrightness, upperBrightness);
-        setHighLightBiases(0.0f, 1.0f);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 8000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
-            setEstimatedBrightnessAndUpdate(controller,
-                    mix(lowerBrightness, upperBrightness, t));
-            assertEquals(controller.mPendingAmbientColorTemperature,
-                    mix(HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, 1.0f - t),
-                    0.001);
-        }
-
-        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
-
-        setEstimatedBrightnessAndUpdate(controller, 0.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
-    }
-
-    @Test
-    public void testSpline_TwoSegmentsHighLight() throws Exception {
-        final float brightness0 = 10.0f;
-        final float brightness1 = 50.0f;
-        final float brightness2 = 60.0f;
-        setHighLightBrightnesses(brightness0, brightness1, brightness2);
-        final float bias0 = 0.0f;
-        final float bias1 = 0.25f;
-        final float bias2 = 1.0f;
-        setHighLightBiases(bias0, bias1, bias2);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = 6000.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
-            float luxOverride = mix(brightness0, brightness1, t);
-            setEstimatedBrightnessAndUpdate(controller, luxOverride);
-            float bias = mix(bias0, bias1, t);
-            assertEquals(controller.mPendingAmbientColorTemperature,
-                    mix(HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, 1.0f - bias),
-                    0.01);
-        }
-
-        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
-            float luxOverride = mix(brightness1, brightness2, t);
-            setEstimatedBrightnessAndUpdate(controller, luxOverride);
-            float bias = mix(bias1, bias2, t);
-            assertEquals(controller.mPendingAmbientColorTemperature,
-                    mix(HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, ambientColorTemperature, 1.0f - bias),
-                    0.01);
-        }
-
-        setEstimatedBrightnessAndUpdate(controller, brightness2 + 1.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature,
-                HIGH_LIGHT_AMBIENT_COLOR_TEMPERATURE, 0.001);
-
-        setEstimatedBrightnessAndUpdate(controller, 0.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
-    }
-
-    @Test
-    public void testSpline_InvalidCombinations() throws Exception {
-            setBrightnesses(100.0f, 200.0f);
-            setBiases(0.0f, 1.0f);
-            setHighLightBrightnesses(150.0f, 250.0f);
-            setHighLightBiases(0.0f, 1.0f);
-
-            DisplayWhiteBalanceController controller =
-                    DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-            final float ambientColorTemperature = 8000.0f;
-            setEstimatedColorTemperature(controller, ambientColorTemperature);
-            controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-            for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
-                setEstimatedBrightnessAndUpdate(controller, luxOverride);
-                assertEquals(controller.mPendingAmbientColorTemperature,
-                        ambientColorTemperature, 0.001);
-            }
-    }
-
-    @Test
-    public void testStrongMode() {
-        final float lowerBrightness = 10.0f;
-        final float upperBrightness = 50.0f;
-        setBrightnesses(lowerBrightness, upperBrightness);
-        setBiases(0.0f, 1.0f);
-        final int ambientColorTempLow = 6000;
-        final int ambientColorTempHigh = 8000;
-        final int displayColorTempLow = 6400;
-        final int displayColorTempHigh = 7400;
-        setStrongAmbientColorTemperatures(ambientColorTempLow, ambientColorTempHigh);
-        setStrongDisplayColorTemperatures(displayColorTempLow, displayColorTempHigh);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        controller.setStrongModeEnabled(true);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float ambientTempFraction = 0.0f; ambientTempFraction <= 1.0f;
-                ambientTempFraction += 0.1f) {
-            final float ambientTemp =
-                    (ambientColorTempHigh - ambientColorTempLow) * ambientTempFraction
-                            + ambientColorTempLow;
-            setEstimatedColorTemperature(controller, ambientTemp);
-            for (float brightnessFraction = 0.0f; brightnessFraction <= 1.0f;
-                    brightnessFraction += 0.1f) {
-                setEstimatedBrightnessAndUpdate(controller,
-                        mix(lowerBrightness, upperBrightness, brightnessFraction));
-                assertEquals(controller.mPendingAmbientColorTemperature,
-                        mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE,
-                                mix(displayColorTempLow, displayColorTempHigh, ambientTempFraction),
-                                brightnessFraction),
-                        ALLOWED_ERROR_DELTA);
-            }
-        }
-    }
-
-    @Test
-    public void testLowLight_DefaultAmbient() throws Exception {
-        final float lowerBrightness = 10.0f;
-        final float upperBrightness = 50.0f;
-        setBrightnesses(lowerBrightness, upperBrightness);
-        setBiases(0.0f, 1.0f);
-
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        final float ambientColorTemperature = -1.0f;
-        setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
-
-        for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
-            setEstimatedBrightnessAndUpdate(controller,
-                    mix(lowerBrightness, upperBrightness, t));
-            assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature,
-                        0.001);
-        }
-
-        setEstimatedBrightnessAndUpdate(controller, 0.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
-
-        setEstimatedBrightnessAndUpdate(controller, upperBrightness + 1.0f);
-        assertEquals(controller.mPendingAmbientColorTemperature, ambientColorTemperature, 0.001);
-    }
-
-    @Test
-    public void testWhiteBalance_updateWithEmptyFilter() throws Exception {
-        setAmbientColorTemperatures(5300.0f, 6000.0f, 7000.0f, 8000.0f);
-        setDisplayColorTemperatures(6300.0f, 6400.0f, 6850.0f, 7450.0f);
-        DisplayWhiteBalanceController controller =
-                DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
-        controller.updateAmbientColorTemperature();
-        assertEquals(-1.0f, controller.mPendingAmbientColorTemperature, 0);
-    }
-
-    void mockThrottler() {
-        when(mResourcesSpy.getInteger(
-                R.integer.config_displayWhiteBalanceDecreaseDebounce)).thenReturn(0);
-        when(mResourcesSpy.getInteger(
-                R.integer.config_displayWhiteBalanceIncreaseDebounce)).thenReturn(0);
-        TypedArray base = mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceBaseThresholds);
-        TypedArray inc = mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceIncreaseThresholds);
-        TypedArray dec = mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceDecreaseThresholds);
-        base = spy(base);
-        inc = spy(inc);
-        dec = spy(dec);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceBaseThresholds)).thenReturn(base);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceIncreaseThresholds)).thenReturn(inc);
-        when(mResourcesSpy.obtainTypedArray(
-                R.array.config_displayWhiteBalanceDecreaseThresholds)).thenReturn(dec);
-        setFloatArrayResource(base, new float[]{0.0f});
-        setFloatArrayResource(inc, new float[]{0.0f});
-        setFloatArrayResource(dec, new float[]{0.0f});
-    }
-
-    private void mockResourcesFloat(int id, float floatValue) {
-        doAnswer(new Answer<Void>() {
-            public Void answer(InvocationOnMock invocation) {
-                TypedValue value = (TypedValue)invocation.getArgument(1);
-                value.type = TypedValue.TYPE_FLOAT;
-                value.data = Float.floatToIntBits(floatValue);
-                return null;
-            }
-        }).when(mResourcesSpy).getValue(
-                eq(id),
-                any(TypedValue.class), eq(true));
-    }
-
-    private void setEstimatedColorTemperature(DisplayWhiteBalanceController controller,
-                                              float ambientColorTemperature) {
-        AmbientFilter colorTemperatureFilter = spy(new AmbientFilterStubber());
-        controller.mColorTemperatureFilter = colorTemperatureFilter;
-        when(colorTemperatureFilter.getEstimate(anyLong())).thenReturn(ambientColorTemperature);
-    }
-
-    private void setEstimatedBrightnessAndUpdate(DisplayWhiteBalanceController controller,
-                                                 float brightness) {
-        when(controller.mBrightnessFilter.getEstimate(anyLong())).thenReturn(brightness);
-        controller.updateAmbientColorTemperature();
-    }
-
-    private void setBrightnesses(float... vals) {
-        setFloatArrayResource(mBrightnesses, vals);
-    }
-
-    private void setBiases(float... vals) {
-        setFloatArrayResource(mBiases, vals);
-    }
-
-    private void setHighLightBrightnesses(float... vals) {
-        setFloatArrayResource(mHighLightBrightnesses, vals);
-    }
-
-    private void setHighLightBiases(float... vals) {
-        setFloatArrayResource(mHighLightBiases, vals);
-    }
-
-    private void setAmbientColorTemperatures(float... vals) {
-        setFloatArrayResource(mAmbientColorTemperatures, vals);
-    }
-
-    private void setDisplayColorTemperatures(float... vals) {
-        setFloatArrayResource(mDisplayColorTemperatures, vals);
-    }
-
-    private void setStrongAmbientColorTemperatures(float... vals) {
-        setFloatArrayResource(mStrongAmbientColorTemperatures, vals);
-    }
-
-    private void setStrongDisplayColorTemperatures(float... vals) {
-        setFloatArrayResource(mStrongDisplayColorTemperatures, vals);
-    }
-
-    private void setFloatArrayResource(TypedArray array, float[] vals) {
-        when(array.length()).thenReturn(vals.length);
-        for (int i = 0; i < vals.length; i++) {
-            when(array.getFloat(i, Float.NaN)).thenReturn(vals[i]);
-        }
-    }
-
-    private TypedArray createTypedArray() throws Exception {
-        TypedArray mockArray = mock(TypedArray.class);
-        return mockArray;
-    }
-
-    private static float mix(float a, float b, float t) {
-        return (1.0f - t) * a + t * b;
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java b/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java
new file mode 100644
index 0000000..df4731f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FeatureFlagsServiceTest {
+    private static final String NS = "ns";
+    private static final String NAME = "name";
+    private static final String PROP_NAME = FlagOverrideStore.getPropName(NS, NAME);
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private FlagOverrideStore mFlagStore;
+    @Mock
+    private FlagsShellCommand mFlagCommand;
+    @Mock
+    private IFeatureFlagsCallback mIFeatureFlagsCallback;
+    @Mock
+    private IBinder mIFeatureFlagsCallbackAsBinder;
+    @Mock
+    private FeatureFlagsService.PermissionsChecker mPermissionsChecker;
+
+    private FeatureFlagsBinder mFeatureFlagsService;
+
+    @Before
+    public void setup() {
+        when(mIFeatureFlagsCallback.asBinder()).thenReturn(mIFeatureFlagsCallbackAsBinder);
+        mFeatureFlagsService = new FeatureFlagsBinder(
+                mFlagStore, mFlagCommand, mPermissionsChecker);
+    }
+
+    @Test
+    public void testRegisterCallback() {
+        mFeatureFlagsService.registerCallback(mIFeatureFlagsCallback);
+        try {
+            verify(mIFeatureFlagsCallbackAsBinder).linkToDeath(any(), eq(0));
+        } catch (RemoteException e) {
+            fail("Our mock threw a Remote Exception?");
+        }
+    }
+
+    @Test
+    public void testOverrideFlag_requiresWritePermission() {
+        SecurityException exc = new SecurityException("not allowed");
+        doThrow(exc).when(mPermissionsChecker).assertWritePermission();
+
+        SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+        try {
+            mFeatureFlagsService.overrideFlag(f);
+            fail("Should have thrown exception");
+        } catch (SecurityException e) {
+            assertThat(exc).isEqualTo(e);
+        } catch (Exception e) {
+            fail("should have thrown a security exception");
+        }
+    }
+
+    @Test
+    public void testResetFlag_requiresWritePermission() {
+        SecurityException exc = new SecurityException("not allowed");
+        doThrow(exc).when(mPermissionsChecker).assertWritePermission();
+
+        SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+        try {
+            mFeatureFlagsService.resetFlag(f);
+            fail("Should have thrown exception");
+        } catch (SecurityException e) {
+            assertThat(exc).isEqualTo(e);
+        } catch (Exception e) {
+            fail("should have thrown a security exception");
+        }
+    }
+
+    @Test
+    public void testSyncFlags_noOverrides() {
+        List<SyncableFlag> inputFlags = List.of(
+                new SyncableFlag(NS, "a", "false", false),
+                new SyncableFlag(NS, "b", "true", false),
+                new SyncableFlag(NS, "c", "false", false)
+        );
+
+        List<SyncableFlag> outputFlags = mFeatureFlagsService.syncFlags(inputFlags);
+
+        assertThat(inputFlags.size()).isEqualTo(outputFlags.size());
+
+        for (SyncableFlag inpF: inputFlags) {
+            boolean found = false;
+            for (SyncableFlag outF : outputFlags) {
+                if (compareSyncableFlagsNames(inpF, outF)) {
+                    found = true;
+                    break;
+                }
+            }
+            assertWithMessage("Failed to find input flag " + inpF + " in the output")
+                    .that(found).isTrue();
+        }
+    }
+
+    @Test
+    public void testSyncFlags_withSomeOverrides() {
+        List<SyncableFlag> inputFlags = List.of(
+                new SyncableFlag(NS, "a", "false", false),
+                new SyncableFlag(NS, "b", "true", false),
+                new SyncableFlag(NS, "c", "false", false)
+        );
+
+        assertThat(mFlagStore).isNotNull();
+        when(mFlagStore.get(NS, "c")).thenReturn("true");
+        List<SyncableFlag> outputFlags = mFeatureFlagsService.syncFlags(inputFlags);
+
+        assertThat(inputFlags.size()).isEqualTo(outputFlags.size());
+
+        for (SyncableFlag inpF: inputFlags) {
+            boolean found = false;
+            for (SyncableFlag outF : outputFlags) {
+                if (compareSyncableFlagsNames(inpF, outF)) {
+                    found = true;
+
+                    // Once we've found "c", do an extra check
+                    if (outF.getName().equals("c")) {
+                        assertWithMessage("Flag " + outF + "was not returned with an override")
+                                .that(outF.getValue()).isEqualTo("true");
+                    }
+                    break;
+                }
+            }
+            assertWithMessage("Failed to find input flag " + inpF + " in the output")
+                    .that(found).isTrue();
+        }
+    }
+
+    @Test
+    public void testSyncFlags_twoCallsWithDifferentDefaults() {
+        List<SyncableFlag> inputFlagsFirst = List.of(
+                new SyncableFlag(NS, "a", "false", false)
+        );
+        List<SyncableFlag> inputFlagsSecond = List.of(
+                new SyncableFlag(NS, "a", "true", false),
+                new SyncableFlag(NS, "b", "false", false)
+        );
+
+        List<SyncableFlag> outputFlagsFirst = mFeatureFlagsService.syncFlags(inputFlagsFirst);
+        List<SyncableFlag> outputFlagsSecond = mFeatureFlagsService.syncFlags(inputFlagsSecond);
+
+        assertThat(inputFlagsFirst.size()).isEqualTo(outputFlagsFirst.size());
+        assertThat(inputFlagsSecond.size()).isEqualTo(outputFlagsSecond.size());
+
+        // This test only cares that the "a" flag passed in the second time came out with the
+        // same value that was passed in the first time.
+
+        boolean found = false;
+        for (SyncableFlag second : outputFlagsSecond) {
+            if (compareSyncableFlagsNames(second, inputFlagsFirst.get(0))) {
+                found = true;
+                assertThat(second.getValue()).isEqualTo(inputFlagsFirst.get(0).getValue());
+                break;
+            }
+        }
+
+        assertWithMessage(
+                "Failed to find flag " + inputFlagsFirst.get(0) + " in the second calls output")
+                .that(found).isTrue();
+    }
+
+    @Test
+    public void testQueryFlags_onlyOnce() {
+        List<SyncableFlag> inputFlags = List.of(
+                new SyncableFlag(NS, "a", "false", false),
+                new SyncableFlag(NS, "b", "true", false),
+                new SyncableFlag(NS, "c", "false", false)
+        );
+
+        List<SyncableFlag> outputFlags = mFeatureFlagsService.queryFlags(inputFlags);
+
+        assertThat(inputFlags.size()).isEqualTo(outputFlags.size());
+
+        for (SyncableFlag inpF: inputFlags) {
+            boolean found = false;
+            for (SyncableFlag outF : outputFlags) {
+                if (compareSyncableFlagsNames(inpF, outF)) {
+                    found = true;
+                    break;
+                }
+            }
+            assertWithMessage("Failed to find input flag " + inpF + " in the output")
+                    .that(found).isTrue();
+        }
+    }
+
+    @Test
+    public void testQueryFlags_twoCallsWithDifferentDefaults() {
+        List<SyncableFlag> inputFlagsFirst = List.of(
+                new SyncableFlag(NS, "a", "false", false)
+        );
+        List<SyncableFlag> inputFlagsSecond = List.of(
+                new SyncableFlag(NS, "a", "true", false),
+                new SyncableFlag(NS, "b", "false", false)
+        );
+
+        List<SyncableFlag> outputFlagsFirst = mFeatureFlagsService.queryFlags(inputFlagsFirst);
+        List<SyncableFlag> outputFlagsSecond = mFeatureFlagsService.queryFlags(inputFlagsSecond);
+
+        assertThat(inputFlagsFirst.size()).isEqualTo(outputFlagsFirst.size());
+        assertThat(inputFlagsSecond.size()).isEqualTo(outputFlagsSecond.size());
+
+        // This test only cares that the "a" flag passed in the second time came out with the
+        // same value that was passed in (i.e. it wasn't cached).
+
+        boolean found = false;
+        for (SyncableFlag second : outputFlagsSecond) {
+            if (compareSyncableFlagsNames(second, inputFlagsSecond.get(0))) {
+                found = true;
+                assertThat(second.getValue()).isEqualTo(inputFlagsSecond.get(0).getValue());
+                break;
+            }
+        }
+
+        assertWithMessage(
+                "Failed to find flag " + inputFlagsSecond.get(0) + " in the second calls output")
+                .that(found).isTrue();
+    }
+
+    @Test
+    public void testOverrideFlag() {
+        SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+        mFeatureFlagsService.overrideFlag(f);
+
+        verify(mFlagStore).set(f.getNamespace(), f.getName(), f.getValue());
+    }
+
+    @Test
+    public void testResetFlag() {
+        SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+        mFeatureFlagsService.resetFlag(f);
+
+        verify(mFlagStore).erase(f.getNamespace(), f.getName());
+    }
+
+
+    private static boolean compareSyncableFlagsNames(SyncableFlag a, SyncableFlag b) {
+        return a.getNamespace().equals(b.getNamespace())
+                && a.getName().equals(b.getName())
+                && a.isDynamic() == b.isDynamic();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java b/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java
new file mode 100644
index 0000000..c2cf540
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class FlagCacheTest {
+    private static final String NS = "ns";
+    private static final String NAME = "name";
+
+    FlagCache mFlagCache = new FlagCache();
+
+    @Test
+    public void testGetOrNull_unset() {
+        assertThat(mFlagCache.getOrNull(NS, NAME)).isNull();
+    }
+
+    @Test
+    public void testGetOrSet_unset() {
+        assertThat(mFlagCache.getOrSet(NS, NAME, "value")).isEqualTo("value");
+    }
+
+    @Test
+    public void testGetOrSet_alreadySet() {
+        mFlagCache.setIfChanged(NS, NAME, "value");
+        assertThat(mFlagCache.getOrSet(NS, NAME, "newvalue")).isEqualTo("value");
+    }
+
+    @Test
+    public void testSetIfChanged_unset() {
+        assertThat(mFlagCache.setIfChanged(NS, NAME, "value")).isTrue();
+    }
+
+    @Test
+    public void testSetIfChanged_noChange() {
+        mFlagCache.setIfChanged(NS, NAME, "value");
+        assertThat(mFlagCache.setIfChanged(NS, NAME, "value")).isFalse();
+    }
+
+    @Test
+    public void testSetIfChanged_changing() {
+        mFlagCache.setIfChanged(NS, NAME, "value");
+        assertThat(mFlagCache.setIfChanged(NS, NAME, "newvalue")).isTrue();
+    }
+
+    @Test
+    public void testContainsNamespace_unset() {
+        assertThat(mFlagCache.containsNamespace(NS)).isFalse();
+    }
+
+    @Test
+    public void testContainsNamespace_set() {
+        mFlagCache.setIfChanged(NS, NAME, "value");
+        assertThat(mFlagCache.containsNamespace(NS)).isTrue();
+    }
+
+    @Test
+    public void testContains_unset() {
+        assertThat(mFlagCache.contains(NS, NAME)).isFalse();
+    }
+
+    @Test
+    public void testContains_set() {
+        mFlagCache.setIfChanged(NS, NAME, "value");
+        assertThat(mFlagCache.contains(NS, NAME)).isTrue();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java b/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java
new file mode 100644
index 0000000..6cc3acf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FlagOverrideStoreTest {
+    private static final String NS = "ns";
+    private static final String NAME = "name";
+    private static final String PROP_NAME = FlagOverrideStore.getPropName(NS, NAME);
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private SettingsProxy mSettingsProxy;
+    @Mock
+    private FlagOverrideStore.FlagChangeCallback mCallback;
+
+    private FlagOverrideStore mFlagStore;
+
+    @Before
+    public void setup() {
+        mFlagStore = new FlagOverrideStore(mSettingsProxy);
+        mFlagStore.setChangeCallback(mCallback);
+    }
+
+    @Test
+    public void testSet_unset() {
+        mFlagStore.set(NS, NAME, "value");
+        verify(mSettingsProxy).putString(PROP_NAME, "value");
+    }
+
+    @Test
+    public void testSet_setTwice() {
+        mFlagStore.set(NS, NAME, "value");
+        mFlagStore.set(NS, NAME, "newvalue");
+        verify(mSettingsProxy).putString(PROP_NAME, "value");
+        verify(mSettingsProxy).putString(PROP_NAME, "newvalue");
+    }
+
+    @Test
+    public void testGet_unset() {
+        assertThat(mFlagStore.get(NS, NAME)).isNull();
+    }
+
+    @Test
+    public void testGet_set() {
+        when(mSettingsProxy.getString(PROP_NAME)).thenReturn("value");
+        assertThat(mFlagStore.get(NS, NAME)).isEqualTo("value");
+    }
+
+    @Test
+    public void testErase() {
+        mFlagStore.erase(NS, NAME);
+        verify(mSettingsProxy).putString(PROP_NAME, null);
+    }
+
+    @Test
+    public void testContains_unset() {
+        assertThat(mFlagStore.contains(NS, NAME)).isFalse();
+    }
+
+    @Test
+    public void testContains_set() {
+        when(mSettingsProxy.getString(PROP_NAME)).thenReturn("value");
+        assertThat(mFlagStore.contains(NS, NAME)).isTrue();
+    }
+
+    @Test
+    public void testCallback_onSet() {
+        mFlagStore.set(NS, NAME, "value");
+        verify(mCallback).onFlagChanged(NS, NAME, "value");
+    }
+
+    @Test
+    public void testCallback_onErase() {
+        mFlagStore.erase(NS, NAME);
+        verify(mCallback).onFlagChanged(NS, NAME, null);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/flags/OWNERS b/services/tests/servicestests/src/com/android/server/flags/OWNERS
new file mode 100644
index 0000000..7ed369e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/OWNERS
@@ -0,0 +1 @@
+include /services/flags/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt
new file mode 100644
index 0000000..98b4628
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.res.Resources
+import android.hardware.Sensor
+import android.hardware.SensorEvent
+import android.hardware.SensorEventListener
+import android.hardware.SensorManager
+import android.hardware.display.DisplayManagerInternal
+import android.hardware.input.InputSensorInfo
+import android.os.Handler
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.util.TypedValue
+import android.view.Display
+import android.view.DisplayInfo
+import androidx.test.core.app.ApplicationProvider
+import com.android.internal.R
+import com.android.server.LocalServices
+import com.android.server.input.AmbientKeyboardBacklightController.HYSTERESIS_THRESHOLD
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+
+/**
+ * Tests for {@link AmbientKeyboardBacklightController}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:AmbientKeyboardBacklightControllerTests
+ */
+@Presubmit
+class AmbientKeyboardBacklightControllerTests {
+
+    companion object {
+        const val DEFAULT_DISPLAY_UNIQUE_ID = "uniqueId_1"
+        const val SENSOR_NAME = "test_sensor_name"
+        const val SENSOR_TYPE = "test_sensor_type"
+    }
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    private lateinit var context: Context
+    private lateinit var testLooper: TestLooper
+    private lateinit var ambientController: AmbientKeyboardBacklightController
+
+    @Mock
+    private lateinit var resources: Resources
+
+    @Mock
+    private lateinit var lightSensorInfo: InputSensorInfo
+
+    @Mock
+    private lateinit var sensorManager: SensorManager
+
+    @Mock
+    private lateinit var displayManagerInternal: DisplayManagerInternal
+    private lateinit var lightSensor: Sensor
+
+    private var currentDisplayInfo = DisplayInfo()
+    private var lastBrightnessCallback: Int = 0
+    private var listenerRegistered: Boolean = false
+    private var listenerRegistrationCount: Int = 0
+
+    @Before
+    fun setup() {
+        context = spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        `when`(context.resources).thenReturn(resources)
+        setupBrightnessSteps()
+        setupSensor()
+        testLooper = TestLooper()
+        ambientController = AmbientKeyboardBacklightController(context, testLooper.looper)
+        ambientController.systemRunning()
+        testLooper.dispatchAll()
+    }
+
+    private fun setupBrightnessSteps() {
+        val brightnessValues = intArrayOf(100, 200, 0)
+        val decreaseThresholds = intArrayOf(-1, 900, 1900)
+        val increaseThresholds = intArrayOf(1000, 2000, -1)
+        `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightBrightnessValues))
+            .thenReturn(brightnessValues)
+        `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightDecreaseLuxThreshold))
+            .thenReturn(decreaseThresholds)
+        `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightIncreaseLuxThreshold))
+            .thenReturn(increaseThresholds)
+        `when`(
+            resources.getValue(
+                eq(R.dimen.config_autoKeyboardBrightnessSmoothingConstant),
+                any(TypedValue::class.java),
+                anyBoolean()
+            )
+        ).then {
+            val args = it.arguments
+            val outValue = args[1] as TypedValue
+            outValue.data = java.lang.Float.floatToRawIntBits(1.0f)
+            Unit
+        }
+    }
+
+    private fun setupSensor() {
+        LocalServices.removeServiceForTest(DisplayManagerInternal::class.java)
+        LocalServices.addService(DisplayManagerInternal::class.java, displayManagerInternal)
+        currentDisplayInfo.uniqueId = DEFAULT_DISPLAY_UNIQUE_ID
+        `when`(displayManagerInternal.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(
+            currentDisplayInfo
+        )
+        val sensorData = DisplayManagerInternal.AmbientLightSensorData(SENSOR_NAME, SENSOR_TYPE)
+        `when`(displayManagerInternal.getAmbientLightSensorData(Display.DEFAULT_DISPLAY))
+            .thenReturn(sensorData)
+
+        `when`(lightSensorInfo.name).thenReturn(SENSOR_NAME)
+        `when`(lightSensorInfo.stringType).thenReturn(SENSOR_TYPE)
+        lightSensor = Sensor(lightSensorInfo)
+        `when`(context.getSystemService(eq(Context.SENSOR_SERVICE))).thenReturn(sensorManager)
+        `when`(sensorManager.getSensorList(anyInt())).thenReturn(listOf(lightSensor))
+        `when`(
+            sensorManager.registerListener(
+                any(),
+                eq(lightSensor),
+                anyInt(),
+                any(Handler::class.java)
+            )
+        ).then {
+            listenerRegistered = true
+            listenerRegistrationCount++
+            true
+        }
+        `when`(
+            sensorManager.unregisterListener(
+                any(SensorEventListener::class.java),
+                eq(lightSensor)
+            )
+        ).then {
+            listenerRegistered = false
+            Unit
+        }
+    }
+
+    private fun setupSensorWithInitialLux(luxValue: Float) {
+        ambientController.registerAmbientBacklightListener { brightnessValue: Int ->
+            lastBrightnessCallback = brightnessValue
+        }
+        sendAmbientLuxValue(luxValue)
+        testLooper.dispatchAll()
+    }
+
+    @Test
+    fun testInitialAmbientLux_sendsCallbackImmediately() {
+        setupSensorWithInitialLux(500F)
+
+        assertEquals(
+            "Should receive immediate callback for first lux change",
+            100,
+            lastBrightnessCallback
+        )
+    }
+
+    @Test
+    fun testBrightnessIncrease_afterInitialLuxChanges() {
+        setupSensorWithInitialLux(500F)
+
+        // Current state: Step 1 [value = 100, increaseThreshold = 1000, decreaseThreshold = -1]
+        repeat(HYSTERESIS_THRESHOLD) {
+            sendAmbientLuxValue(1500F)
+        }
+        testLooper.dispatchAll()
+
+        assertEquals(
+            "Should receive brightness change callback for increasing lux change",
+            200,
+            lastBrightnessCallback
+        )
+    }
+
+    @Test
+    fun testBrightnessDecrease_afterInitialLuxChanges() {
+        setupSensorWithInitialLux(1500F)
+
+        // Current state: Step 2 [value = 200, increaseThreshold = 2000, decreaseThreshold = 900]
+        repeat(HYSTERESIS_THRESHOLD) {
+            sendAmbientLuxValue(500F)
+        }
+        testLooper.dispatchAll()
+
+        assertEquals(
+            "Should receive brightness change callback for decreasing lux change",
+            100,
+            lastBrightnessCallback
+        )
+    }
+
+    @Test
+    fun testRegisterAmbientListener_throwsExceptionOnRegisteringDuplicate() {
+        val ambientListener =
+            AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { }
+        ambientController.registerAmbientBacklightListener(ambientListener)
+
+        assertThrows(IllegalStateException::class.java) {
+            ambientController.registerAmbientBacklightListener(
+                ambientListener
+            )
+        }
+    }
+
+    @Test
+    fun testUnregisterAmbientListener_throwsExceptionOnUnregisteringNonExistent() {
+        val ambientListener =
+            AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { }
+        assertThrows(IllegalStateException::class.java) {
+            ambientController.unregisterAmbientBacklightListener(
+                ambientListener
+            )
+        }
+    }
+
+    @Test
+    fun testSensorListenerRegistered_onRegisterUnregisterAmbientListener() {
+        assertEquals(
+            "Should not have a sensor listener registered at init",
+            0,
+            listenerRegistrationCount
+        )
+        assertFalse("Should not have a sensor listener registered at init", listenerRegistered)
+
+        val ambientListener1 =
+            AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { }
+        ambientController.registerAmbientBacklightListener(ambientListener1)
+        assertEquals(
+            "Should register a new sensor listener", 1, listenerRegistrationCount
+        )
+        assertTrue("Should have sensor listener registered", listenerRegistered)
+
+        val ambientListener2 =
+            AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { }
+        ambientController.registerAmbientBacklightListener(ambientListener2)
+        assertEquals(
+            "Should not register a new sensor listener when adding a second ambient listener",
+            1,
+            listenerRegistrationCount
+        )
+        assertTrue("Should have sensor listener registered", listenerRegistered)
+
+        ambientController.unregisterAmbientBacklightListener(ambientListener1)
+        assertTrue("Should have sensor listener registered", listenerRegistered)
+
+        ambientController.unregisterAmbientBacklightListener(ambientListener2)
+        assertFalse(
+            "Should not have sensor listener registered if there are no ambient listeners",
+            listenerRegistered
+        )
+    }
+
+    @Test
+    fun testDisplayChange_shouldNotReRegisterListener_ifUniqueIdSame() {
+        setupSensorWithInitialLux(0F)
+
+        val count = listenerRegistrationCount
+        ambientController.onDisplayChanged(Display.DEFAULT_DISPLAY)
+        testLooper.dispatchAll()
+
+        assertEquals(
+            "Should not re-register listener on display change if unique is same",
+            count,
+            listenerRegistrationCount
+        )
+    }
+
+    @Test
+    fun testDisplayChange_shouldReRegisterListener_ifUniqueIdChanges() {
+        setupSensorWithInitialLux(0F)
+
+        val count = listenerRegistrationCount
+        currentDisplayInfo.uniqueId = "xyz"
+        ambientController.onDisplayChanged(Display.DEFAULT_DISPLAY)
+        testLooper.dispatchAll()
+
+        assertEquals(
+            "Should re-register listener on display change if unique id changed",
+            count + 1,
+            listenerRegistrationCount
+        )
+    }
+
+    @Test
+    fun testBrightnessDoesntChange_betweenIncreaseAndDecreaseThresholds() {
+        setupSensorWithInitialLux(1001F)
+
+        // Previous state: Step 1 [value = 100, increaseThreshold = 1000, decreaseThreshold = -1]
+        // Current state: Step 2 [value = 200, increaseThreshold = 2000, decreaseThreshold = 900]
+        lastBrightnessCallback = -1
+        repeat(HYSTERESIS_THRESHOLD) {
+            sendAmbientLuxValue(999F)
+        }
+        testLooper.dispatchAll()
+
+        assertEquals(
+            "Should not receive any callback for brightness change",
+            -1,
+            lastBrightnessCallback
+        )
+    }
+
+    @Test
+    fun testBrightnessDoesntChange_onChangeOccurringLessThanHysteresisThreshold() {
+        setupSensorWithInitialLux(1001F)
+
+        // Previous state: Step 1 [value = 100, increaseThreshold = 1000, decreaseThreshold = -1]
+        // Current state: Step 2 [value = 200, increaseThreshold = 2000, decreaseThreshold = 900]
+        lastBrightnessCallback = -1
+        repeat(HYSTERESIS_THRESHOLD - 1) {
+            sendAmbientLuxValue(2001F)
+        }
+        testLooper.dispatchAll()
+
+        assertEquals(
+            "Should not receive any callback for brightness change",
+            -1,
+            lastBrightnessCallback
+        )
+    }
+
+    private fun sendAmbientLuxValue(luxValue: Float) {
+        ambientController.onSensorChanged(SensorEvent(lightSensor, 0, 0, floatArrayOf(luxValue)))
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index 416b1f4..c36122b 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -40,8 +40,7 @@
 import com.android.server.input.BatteryController.BluetoothBatteryManager
 import com.android.server.input.BatteryController.BluetoothBatteryManager.BluetoothBatteryListener
 import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS
-import com.android.server.input.BatteryController.UEventManager
-import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener
+import com.android.server.input.BatteryController.UEventBatteryListener
 import com.android.server.input.BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS
 import org.hamcrest.Description
 import org.hamcrest.Matcher
diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
index 677144c..72066ea 100644
--- a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
@@ -23,9 +23,12 @@
 import android.os.IInputConstants
 import android.os.test.TestLooper
 import android.platform.test.annotations.Presubmit
+import android.provider.Settings
+import android.test.mock.MockContentResolver
 import android.view.Display
 import android.view.PointerIcon
 import androidx.test.InstrumentationRegistry
+import com.android.internal.util.test.FakeSettingsProvider
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -33,6 +36,8 @@
 import org.junit.Rule
 import org.junit.Test
 import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyFloat
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
@@ -44,7 +49,9 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.junit.MockitoJUnit
+import org.mockito.stubbing.OngoingStubbing
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 
@@ -58,7 +65,10 @@
 class InputManagerServiceTests {
 
     @get:Rule
-    val rule = MockitoJUnit.rule()!!
+    val mockitoRule = MockitoJUnit.rule()!!
+
+    @get:Rule
+    val fakeSettingsProviderRule = FakeSettingsProvider.rule()!!
 
     @Mock
     private lateinit var native: NativeInputManagerService
@@ -66,17 +76,25 @@
     @Mock
     private lateinit var wmCallbacks: InputManagerService.WindowManagerCallbacks
 
+    @Mock
+    private lateinit var uEventManager: UEventManager
+
     private lateinit var service: InputManagerService
     private lateinit var localService: InputManagerInternal
     private lateinit var context: Context
     private lateinit var testLooper: TestLooper
+    private lateinit var contentResolver: MockContentResolver
 
     @Before
     fun setup() {
         context = spy(ContextWrapper(InstrumentationRegistry.getContext()))
+        contentResolver = MockContentResolver(context)
+        contentResolver.addProvider(Settings.AUTHORITY, FakeSettingsProvider())
+        whenever(context.contentResolver).thenReturn(contentResolver)
         testLooper = TestLooper()
         service =
-            InputManagerService(object : InputManagerService.Injector(context, testLooper.looper) {
+            InputManagerService(object : InputManagerService.Injector(
+                    context, testLooper.looper, uEventManager) {
                 override fun getNativeService(
                     service: InputManagerService?
                 ): NativeInputManagerService {
@@ -92,9 +110,36 @@
     }
 
     @Test
+    fun testStart() {
+        verifyZeroInteractions(native)
+
+        service.start()
+        verify(native).start()
+    }
+
+    @Test
+    fun testInputSettingsUpdatedOnSystemRunning() {
+        verifyZeroInteractions(native)
+
+        service.systemRunning()
+
+        verify(native).setPointerSpeed(anyInt())
+        verify(native).setTouchpadPointerSpeed(anyInt())
+        verify(native).setTouchpadNaturalScrollingEnabled(anyBoolean())
+        verify(native).setTouchpadTapToClickEnabled(anyBoolean())
+        verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
+        verify(native).setShowTouches(anyBoolean())
+        verify(native).reloadPointerIcons()
+        verify(native).notifyKeyGestureTimeoutsChanged()
+        verify(native).setMotionClassifierEnabled(anyBoolean())
+        verify(native).setMaximumObscuringOpacityForTouch(anyFloat())
+        verify(native).setStylusPointerIconEnabled(anyBoolean())
+    }
+
+    @Test
     fun testPointerDisplayUpdatesWhenDisplayViewportsChanged() {
         val displayId = 123
-        `when`(wmCallbacks.pointerDisplayId).thenReturn(displayId)
+        whenever(wmCallbacks.pointerDisplayId).thenReturn(displayId)
         val viewports = listOf<DisplayViewport>()
         localService.setDisplayViewports(viewports)
         verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java))
@@ -337,3 +382,5 @@
         thread.join(100 /*millis*/)
     }
 }
+
+private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
index 64c05dc..3f4a4fb 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.input
 
+import android.animation.ValueAnimator
 import android.content.Context
 import android.content.ContextWrapper
 import android.graphics.Color
@@ -29,11 +30,15 @@
 import android.os.test.TestLooper
 import android.platform.test.annotations.Presubmit
 import android.view.InputDevice
+import androidx.test.annotation.UiThreadTest
 import androidx.test.core.app.ApplicationProvider
-import com.android.server.input.KeyboardBacklightController.BRIGHTNESS_VALUE_FOR_LEVEL
+import com.android.server.input.KeyboardBacklightController.DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL
+import com.android.server.input.KeyboardBacklightController.MAX_BRIGHTNESS_CHANGE_STEPS
 import com.android.server.input.KeyboardBacklightController.USER_INACTIVITY_THRESHOLD_MILLIS
 import org.junit.After
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertNull
 import org.junit.Assert.assertTrue
@@ -63,12 +68,20 @@
         .build()
 
 private fun createLight(lightId: Int, lightType: Int): Light =
+    createLight(
+        lightId,
+        lightType,
+        null
+    )
+
+private fun createLight(lightId: Int, lightType: Int, suggestedBrightnessLevels: IntArray?): Light =
     Light(
         lightId,
         "Light $lightId",
         1,
         lightType,
-        Light.LIGHT_CAPABILITY_BRIGHTNESS
+        Light.LIGHT_CAPABILITY_BRIGHTNESS,
+        suggestedBrightnessLevels
     )
 /**
  * Tests for {@link KeyboardBacklightController}.
@@ -92,6 +105,8 @@
     private lateinit var iInputManager: IInputManager
     @Mock
     private lateinit var native: NativeInputManagerService
+    @Mock
+    private lateinit var uEventManager: UEventManager
     private lateinit var keyboardBacklightController: KeyboardBacklightController
     private lateinit var context: Context
     private lateinit var dataStore: PersistentDataStore
@@ -99,6 +114,7 @@
     private var lightColorMap: HashMap<Int, Int> = HashMap()
     private var lastBacklightState: KeyboardBacklightState? = null
     private var sysfsNodeChanges = 0
+    private var lastAnimationValues = IntArray(2)
 
     @Before
     fun setup() {
@@ -115,8 +131,8 @@
             override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
         })
         testLooper = TestLooper()
-        keyboardBacklightController =
-            KeyboardBacklightController(context, native, dataStore, testLooper.looper)
+        keyboardBacklightController = KeyboardBacklightController(context, native, dataStore,
+                testLooper.looper, FakeAnimatorFactory(), uEventManager)
         InputManagerGlobal.resetInstance(iInputManager)
         val inputManager = InputManager(context)
         `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
@@ -125,6 +141,10 @@
             val args = it.arguments
             lightColorMap.put(args[1] as Int, args[2] as Int)
         }
+        `when`(native.getLightColor(anyInt(), anyInt())).thenAnswer {
+            val args = it.arguments
+            lightColorMap.getOrDefault(args[1] as Int, 0)
+        }
         lightColorMap.clear()
         `when`(native.sysfsNodeChanged(any())).then {
             sysfsNodeChanges++
@@ -138,271 +158,257 @@
 
     @Test
     fun testKeyboardBacklightIncrementDecrement() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
 
-        for (level in 1 until BRIGHTNESS_VALUE_FOR_LEVEL.size) {
-            incrementKeyboardBacklight(DEVICE_ID)
-            assertEquals(
-                "Light value for level $level mismatched",
-                Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
-                lightColorMap[LIGHT_ID]
-            )
-            assertEquals(
-                "Light value for level $level must be correctly stored in the datastore",
-                BRIGHTNESS_VALUE_FOR_LEVEL[level],
-                dataStore.getKeyboardBacklightBrightness(
-                    keyboardWithBacklight.descriptor,
-                    LIGHT_ID
-                ).asInt
-            )
+            assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+                    DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
         }
-
-        // Increment above max level
-        incrementKeyboardBacklight(DEVICE_ID)
-        assertEquals(
-            "Light value for max level mismatched",
-            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
-            lightColorMap[LIGHT_ID]
-        )
-        assertEquals(
-            "Light value for max level must be correctly stored in the datastore",
-            MAX_BRIGHTNESS,
-            dataStore.getKeyboardBacklightBrightness(
-                keyboardWithBacklight.descriptor,
-                LIGHT_ID
-            ).asInt
-        )
-
-        for (level in BRIGHTNESS_VALUE_FOR_LEVEL.size - 2 downTo 0) {
-            decrementKeyboardBacklight(DEVICE_ID)
-            assertEquals(
-                "Light value for level $level mismatched",
-                Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
-                lightColorMap[LIGHT_ID]
-            )
-            assertEquals(
-                "Light value for level $level must be correctly stored in the datastore",
-                BRIGHTNESS_VALUE_FOR_LEVEL[level],
-                dataStore.getKeyboardBacklightBrightness(
-                    keyboardWithBacklight.descriptor,
-                    LIGHT_ID
-                ).asInt
-            )
-        }
-
-        // Decrement below min level
-        decrementKeyboardBacklight(DEVICE_ID)
-        assertEquals(
-            "Light value for min level mismatched",
-            Color.argb(0, 0, 0, 0),
-            lightColorMap[LIGHT_ID]
-        )
-        assertEquals(
-            "Light value for min level must be correctly stored in the datastore",
-            0,
-            dataStore.getKeyboardBacklightBrightness(
-                keyboardWithBacklight.descriptor,
-                LIGHT_ID
-            ).asInt
-        )
     }
 
     @Test
     fun testKeyboardWithoutBacklight() {
-        val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
-        val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
+            val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
 
-        incrementKeyboardBacklight(DEVICE_ID)
-        assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty())
+            incrementKeyboardBacklight(DEVICE_ID)
+            assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty())
+        }
     }
 
     @Test
     fun testKeyboardWithMultipleLight() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(
-            listOf(
-                keyboardBacklight,
-                keyboardInputLight
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(
+                listOf(
+                    keyboardBacklight,
+                    keyboardInputLight
+                )
             )
-        )
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
 
-        incrementKeyboardBacklight(DEVICE_ID)
-        assertEquals("Only keyboard backlights should change", 1, lightColorMap.size)
-        assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID])
-        assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID])
+            incrementKeyboardBacklight(DEVICE_ID)
+            assertEquals("Only keyboard backlights should change", 1, lightColorMap.size)
+            assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID])
+            assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID])
+        }
     }
 
     @Test
     fun testRestoreBacklightOnInputDeviceAdded() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
 
-        for (level in 1 until BRIGHTNESS_VALUE_FOR_LEVEL.size) {
-            dataStore.setKeyboardBacklightBrightness(
+            for (level in 1 until DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size) {
+                dataStore.setKeyboardBacklightBrightness(
                     keyboardWithBacklight.descriptor,
                     LIGHT_ID,
-                    BRIGHTNESS_VALUE_FOR_LEVEL[level] - 1
+                    DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level] - 1
+                )
+
+                keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+                keyboardBacklightController.notifyUserActivity()
+                testLooper.dispatchNext()
+                assertEquals(
+                    "Keyboard backlight level should be restored to the level saved in the " +
+                            "data store",
+                    Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
+                    lightColorMap[LIGHT_ID]
+                )
+                keyboardBacklightController.onInputDeviceRemoved(DEVICE_ID)
+            }
+        }
+    }
+
+    @Test
+    fun testRestoreBacklightOnInputDeviceChanged() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            dataStore.setKeyboardBacklightBrightness(
+                keyboardWithBacklight.descriptor,
+                LIGHT_ID,
+                MAX_BRIGHTNESS
+            )
+
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            keyboardBacklightController.notifyUserActivity()
+            testLooper.dispatchNext()
+            assertTrue(
+                "Keyboard backlight should not be changed until its added",
+                lightColorMap.isEmpty()
+            )
+
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceChanged(DEVICE_ID)
+            keyboardBacklightController.notifyUserActivity()
+            testLooper.dispatchNext()
+            assertEquals(
+                "Keyboard backlight level should be restored to the level saved in the data store",
+                Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklight_registerUnregisterListener() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            val maxLevel = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size - 1
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+            // Register backlight listener
+            val listener = KeyboardBacklightListener()
+            keyboardBacklightController.registerKeyboardBacklightListener(listener, 0)
+
+            lastBacklightState = null
+            keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
+            testLooper.dispatchNext()
+
+            assertEquals(
+                "Backlight state device Id should be $DEVICE_ID",
+                DEVICE_ID,
+                lastBacklightState!!.deviceId
+            )
+            assertEquals(
+                "Backlight state brightnessLevel should be 1",
+                1,
+                lastBacklightState!!.brightnessLevel
+            )
+            assertEquals(
+                "Backlight state maxBrightnessLevel should be $maxLevel",
+                maxLevel,
+                lastBacklightState!!.maxBrightnessLevel
+            )
+            assertEquals(
+                "Backlight state isTriggeredByKeyPress should be true",
+                true,
+                lastBacklightState!!.isTriggeredByKeyPress
+            )
+
+            // Unregister listener
+            keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
+
+            lastBacklightState = null
+            incrementKeyboardBacklight(DEVICE_ID)
+
+            assertNull("Listener should not receive any updates", lastBacklightState)
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklight_userActivity() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            dataStore.setKeyboardBacklightBrightness(
+                keyboardWithBacklight.descriptor,
+                LIGHT_ID,
+                MAX_BRIGHTNESS
             )
 
             keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
             keyboardBacklightController.notifyUserActivity()
             testLooper.dispatchNext()
             assertEquals(
-                    "Keyboard backlight level should be restored to the level saved in the data " +
-                            "store",
-                    Color.argb(BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0),
-                    lightColorMap[LIGHT_ID]
+                "Keyboard backlight level should be restored to the level saved in the data store",
+                Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
+                lightColorMap[LIGHT_ID]
             )
-            keyboardBacklightController.onInputDeviceRemoved(DEVICE_ID)
+
+            testLooper.moveTimeForward(USER_INACTIVITY_THRESHOLD_MILLIS + 1000)
+            testLooper.dispatchNext()
+            assertEquals(
+                "Keyboard backlight level should be turned off after inactivity",
+                0,
+                lightColorMap[LIGHT_ID]
+            )
         }
     }
 
     @Test
-    fun testRestoreBacklightOnInputDeviceChanged() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        dataStore.setKeyboardBacklightBrightness(
-            keyboardWithBacklight.descriptor,
-            LIGHT_ID,
-            MAX_BRIGHTNESS
-        )
-
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-        keyboardBacklightController.notifyUserActivity()
-        testLooper.dispatchNext()
-        assertTrue(
-            "Keyboard backlight should not be changed until its added",
-            lightColorMap.isEmpty()
-        )
-
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-        keyboardBacklightController.onInputDeviceChanged(DEVICE_ID)
-        keyboardBacklightController.notifyUserActivity()
-        testLooper.dispatchNext()
-        assertEquals(
-            "Keyboard backlight level should be restored to the level saved in the data store",
-            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
-            lightColorMap[LIGHT_ID]
-        )
-    }
-
-    @Test
-    fun testKeyboardBacklight_registerUnregisterListener() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-
-        // Register backlight listener
-        val listener = KeyboardBacklightListener()
-        keyboardBacklightController.registerKeyboardBacklightListener(listener, 0)
-
-        lastBacklightState = null
-        keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
-        testLooper.dispatchNext()
-
-        assertEquals(
-            "Backlight state device Id should be $DEVICE_ID",
-            DEVICE_ID,
-            lastBacklightState!!.deviceId
-        )
-        assertEquals(
-            "Backlight state brightnessLevel should be " + 1,
-            1,
-            lastBacklightState!!.brightnessLevel
-        )
-        assertEquals(
-            "Backlight state maxBrightnessLevel should be " + (BRIGHTNESS_VALUE_FOR_LEVEL.size - 1),
-            (BRIGHTNESS_VALUE_FOR_LEVEL.size - 1),
-            lastBacklightState!!.maxBrightnessLevel
-        )
-        assertEquals(
-            "Backlight state isTriggeredByKeyPress should be true",
-            true,
-            lastBacklightState!!.isTriggeredByKeyPress
-        )
-
-        // Unregister listener
-        keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
-
-        lastBacklightState = null
-        incrementKeyboardBacklight(DEVICE_ID)
-
-        assertNull("Listener should not receive any updates", lastBacklightState)
-    }
-
-    @Test
-    fun testKeyboardBacklight_userActivity() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-        dataStore.setKeyboardBacklightBrightness(
-            keyboardWithBacklight.descriptor,
-            LIGHT_ID,
-            MAX_BRIGHTNESS
-        )
-
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-        keyboardBacklightController.notifyUserActivity()
-        testLooper.dispatchNext()
-        assertEquals(
-            "Keyboard backlight level should be restored to the level saved in the data store",
-            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
-            lightColorMap[LIGHT_ID]
-        )
-
-        testLooper.moveTimeForward(USER_INACTIVITY_THRESHOLD_MILLIS + 1000)
-        testLooper.dispatchNext()
-        assertEquals(
-            "Keyboard backlight level should be turned off after inactivity",
-            0,
-            lightColorMap[LIGHT_ID]
-        )
-    }
-
-    @Test
     fun testKeyboardBacklight_displayOnOff() {
-        val keyboardWithBacklight = createKeyboard(DEVICE_ID)
-        val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
-        `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
-        `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
-        dataStore.setKeyboardBacklightBrightness(
-            keyboardWithBacklight.descriptor,
-            LIGHT_ID,
-            MAX_BRIGHTNESS
-        )
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            dataStore.setKeyboardBacklightBrightness(
+                keyboardWithBacklight.descriptor,
+                LIGHT_ID,
+                MAX_BRIGHTNESS
+            )
 
-        keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
-        keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */)
-        assertEquals(
-            "Keyboard backlight level should be restored to the level saved in the data " +
-                    "store when display turned on",
-            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
-            lightColorMap[LIGHT_ID]
-        )
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */)
+            assertEquals(
+                "Keyboard backlight level should be restored to the level saved in the data " +
+                        "store when display turned on",
+                Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
 
-        keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */)
-        assertEquals(
-            "Keyboard backlight level should be turned off after display is turned off",
-            0,
-            lightColorMap[LIGHT_ID]
-        )
+            keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */)
+            assertEquals(
+                "Keyboard backlight level should be turned off after display is turned off",
+                0,
+                lightColorMap[LIGHT_ID]
+            )
+        }
     }
 
     @Test
@@ -463,6 +469,316 @@
         )
     }
 
+    @Test
+    @UiThreadTest
+    fun testKeyboardBacklightAnimation_onChangeLevels() {
+        KeyboardBacklightFlags(
+            animationEnabled = true,
+            customLevelsEnabled = false,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+            incrementKeyboardBacklight(DEVICE_ID)
+            assertEquals(
+                "Should start animation from level 0",
+                DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[0],
+                lastAnimationValues[0]
+            )
+            assertEquals(
+                "Should start animation to level 1",
+                DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1],
+                lastAnimationValues[1]
+            )
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklightPreferredLevels() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = true,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val suggestedLevels = intArrayOf(0, 22, 63, 135, 196, 255)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+                    suggestedLevels)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+            assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+                    suggestedLevels)
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklightPreferredLevels_moreThanMax_shouldUseDefault() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = true,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val suggestedLevels = IntArray(MAX_BRIGHTNESS_CHANGE_STEPS + 1) { 10 * (it + 1) }
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+                    suggestedLevels)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+            assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+                    DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL)
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklightPreferredLevels_mustHaveZeroAndMaxBrightnessAsBounds() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = true,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val suggestedLevels = intArrayOf(22, 63, 135, 196)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+                    suggestedLevels)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+            // Framework will add the lowest and maximum levels if not provided via config
+            assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+                    intArrayOf(0, 22, 63, 135, 196, 255))
+        }
+    }
+
+    @Test
+    fun testKeyboardBacklightPreferredLevels_dropsOutOfBoundsLevels() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = true,
+            ambientControlEnabled = false
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val suggestedLevels = intArrayOf(22, 63, 135, 400, 196, 1000)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
+                    suggestedLevels)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+
+            // Framework will drop out of bound levels in the config
+            assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight,
+                    intArrayOf(0, 22, 63, 135, 196, 255))
+        }
+    }
+
+    @Test
+    fun testAmbientBacklightControl_doesntRestoreBacklightLevel() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = true
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+
+            dataStore.setKeyboardBacklightBrightness(
+                keyboardWithBacklight.descriptor,
+                LIGHT_ID,
+                DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1]
+            )
+
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            keyboardBacklightController.notifyUserActivity()
+            testLooper.dispatchNext()
+            assertNull(
+                "Keyboard backlight level should not be restored to the saved level",
+                lightColorMap[LIGHT_ID]
+            )
+        }
+    }
+
+    @Test
+    fun testAmbientBacklightControl_doesntBackupBacklightLevel() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = true
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            incrementKeyboardBacklight(DEVICE_ID)
+            assertFalse(
+                "Light value should not be backed up if ambient control is enabled",
+                dataStore.getKeyboardBacklightBrightness(
+                    keyboardWithBacklight.descriptor, LIGHT_ID
+                ).isPresent
+            )
+        }
+    }
+
+    @Test
+    fun testAmbientBacklightControl_incrementLevel_afterAmbientChange() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = true
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            sendAmbientBacklightValue(1)
+            assertEquals(
+                "Light value should be changed to ambient provided value",
+                Color.argb(1, 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+
+            incrementKeyboardBacklight(DEVICE_ID)
+
+            assertEquals(
+                "Light value for level after increment post Ambient change is mismatched",
+                Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+        }
+    }
+
+    @Test
+    fun testAmbientBacklightControl_decrementLevel_afterAmbientChange() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = true
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            sendAmbientBacklightValue(254)
+            assertEquals(
+                "Light value should be changed to ambient provided value",
+                Color.argb(254, 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+
+            decrementKeyboardBacklight(DEVICE_ID)
+
+            val numLevels = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size
+            assertEquals(
+                "Light value for level after decrement post Ambient change is mismatched",
+                Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[numLevels - 2], 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+        }
+    }
+
+    @Test
+    fun testAmbientBacklightControl_ambientChanges_afterManualChange() {
+        KeyboardBacklightFlags(
+            animationEnabled = false,
+            customLevelsEnabled = false,
+            ambientControlEnabled = true
+        ).use {
+            val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+            val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+            `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+            `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+            keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+            incrementKeyboardBacklight(DEVICE_ID)
+            assertEquals(
+                "Light value should be changed to the first level",
+                Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+
+            sendAmbientBacklightValue(100)
+            assertNotEquals(
+                "Light value should not change based on ambient changes after manual changes",
+                Color.argb(100, 0, 0, 0),
+                lightColorMap[LIGHT_ID]
+            )
+        }
+    }
+
+    private fun assertIncrementDecrementForLevels(
+        device: InputDevice,
+        light: Light,
+        expectedLevels: IntArray
+    ) {
+        val deviceId = device.id
+        val lightId = light.id
+        for (level in 1 until expectedLevels.size) {
+            incrementKeyboardBacklight(deviceId)
+            assertEquals(
+                "Light value for level $level mismatched",
+                Color.argb(expectedLevels[level], 0, 0, 0),
+                lightColorMap[lightId]
+            )
+            assertEquals(
+                "Light value for level $level must be correctly stored in the datastore",
+                expectedLevels[level],
+                dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
+            )
+        }
+
+        // Increment above max level
+        incrementKeyboardBacklight(deviceId)
+        assertEquals(
+            "Light value for max level mismatched",
+            Color.argb(MAX_BRIGHTNESS, 0, 0, 0),
+            lightColorMap[lightId]
+        )
+        assertEquals(
+            "Light value for max level must be correctly stored in the datastore",
+            MAX_BRIGHTNESS,
+            dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
+        )
+
+        for (level in expectedLevels.size - 2 downTo 0) {
+            decrementKeyboardBacklight(deviceId)
+            assertEquals(
+                "Light value for level $level mismatched",
+                Color.argb(expectedLevels[level], 0, 0, 0),
+                lightColorMap[lightId]
+            )
+            assertEquals(
+                "Light value for level $level must be correctly stored in the datastore",
+                expectedLevels[level],
+                dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
+            )
+        }
+
+        // Decrement below min level
+        decrementKeyboardBacklight(deviceId)
+        assertEquals(
+            "Light value for min level mismatched",
+            Color.argb(0, 0, 0, 0),
+            lightColorMap[lightId]
+        )
+        assertEquals(
+            "Light value for min level must be correctly stored in the datastore",
+            0,
+            dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt
+        )
+    }
+
     inner class KeyboardBacklightListener : IKeyboardBacklightListener.Stub() {
         override fun onBrightnessChanged(
             deviceId: Int,
@@ -490,10 +806,41 @@
         testLooper.dispatchAll()
     }
 
+    private fun sendAmbientBacklightValue(brightnessValue: Int) {
+        keyboardBacklightController.handleAmbientLightValueChanged(brightnessValue)
+        keyboardBacklightController.notifyUserActivity()
+        testLooper.dispatchAll()
+    }
+
     class KeyboardBacklightState(
         val deviceId: Int,
         val brightnessLevel: Int,
         val maxBrightnessLevel: Int,
         val isTriggeredByKeyPress: Boolean
     )
+
+    private inner class KeyboardBacklightFlags constructor(
+        animationEnabled: Boolean,
+        customLevelsEnabled: Boolean,
+        ambientControlEnabled: Boolean
+    ) : AutoCloseable {
+        init {
+            InputFeatureFlagProvider.setKeyboardBacklightAnimationEnabled(animationEnabled)
+            InputFeatureFlagProvider.setKeyboardBacklightCustomLevelsEnabled(customLevelsEnabled)
+            InputFeatureFlagProvider
+                .setAmbientKeyboardBacklightControlEnabled(ambientControlEnabled)
+        }
+
+        override fun close() {
+            InputFeatureFlagProvider.clearOverrides()
+        }
+    }
+
+    private inner class FakeAnimatorFactory : KeyboardBacklightController.AnimatorFactory {
+        override fun makeIntAnimator(from: Int, to: Int): ValueAnimator {
+            lastAnimationValues[0] = from
+            lastAnimationValues[1] = to
+            return ValueAnimator.ofInt(from, to)
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
new file mode 100644
index 0000000..84af9dd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input
+
+import android.hardware.input.KeyboardLayout
+import android.icu.util.ULocale
+import android.platform.test.annotations.Presubmit
+import android.view.InputDevice
+import android.view.inputmethod.InputMethodSubtype
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+private fun createKeyboard(
+    deviceId: Int,
+    vendorId: Int,
+    productId: Int,
+    languageTag: String?,
+    layoutType: String?
+): InputDevice =
+    InputDevice.Builder()
+        .setId(deviceId)
+        .setName("Device $deviceId")
+        .setDescriptor("descriptor $deviceId")
+        .setSources(InputDevice.SOURCE_KEYBOARD)
+        .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+        .setExternal(true)
+        .setVendorId(vendorId)
+        .setProductId(productId)
+        .setKeyboardLanguageTag(languageTag)
+        .setKeyboardLayoutType(layoutType)
+        .build()
+
+private fun createImeSubtype(
+    imeSubtypeId: Int,
+    languageTag: ULocale?,
+    layoutType: String
+): InputMethodSubtype =
+    InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(imeSubtypeId)
+        .setPhysicalKeyboardHint(languageTag, layoutType).build()
+
+/**
+ * Tests for {@link KeyboardMetricsCollector}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:KeyboardMetricsCollectorTests
+ */
+@Presubmit
+class KeyboardMetricsCollectorTests {
+
+    companion object {
+        const val DEVICE_ID = 1
+        const val DEFAULT_VENDOR_ID = 123
+        const val DEFAULT_PRODUCT_ID = 456
+    }
+
+    @Test
+    fun testCreateKeyboardConfigurationEvent_throwsExceptionWithoutAnyLayoutConfiguration() {
+        assertThrows(IllegalStateException::class.java) {
+            KeyboardMetricsCollector.KeyboardConfigurationEvent.Builder(
+                createKeyboard(
+                    DEVICE_ID,
+                    DEFAULT_VENDOR_ID,
+                    DEFAULT_PRODUCT_ID,
+                    null,
+                    null
+                )
+            ).build()
+        }
+    }
+
+    @Test
+    fun testCreateKeyboardConfigurationEvent_throwsExceptionWithInvalidLayoutSelectionCriteria() {
+        assertThrows(IllegalStateException::class.java) {
+            KeyboardMetricsCollector.KeyboardConfigurationEvent.Builder(
+                createKeyboard(
+                    DEVICE_ID,
+                    DEFAULT_VENDOR_ID,
+                    DEFAULT_PRODUCT_ID,
+                    null,
+                    null
+                )
+            ).addLayoutSelection(createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
+             null, 123).build()
+        }
+    }
+
+    @Test
+    fun testCreateKeyboardConfigurationEvent_withMultipleConfigurations() {
+        val builder = KeyboardMetricsCollector.KeyboardConfigurationEvent.Builder(
+            createKeyboard(
+                DEVICE_ID,
+                DEFAULT_VENDOR_ID,
+                DEFAULT_PRODUCT_ID,
+                "de-CH",
+                "qwertz"
+            )
+        )
+        val event = builder.addLayoutSelection(
+            createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
+            KeyboardLayout(null, "English(US)(Qwerty)", null, 0, null, 0, 0, 0),
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+        ).addLayoutSelection(
+            createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"),
+            null, // Default layout type
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
+        ).addLayoutSelection(
+            createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"),
+            KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+        ).setIsFirstTimeConfiguration(true).build()
+
+        assertEquals(
+            "KeyboardConfigurationEvent should pick vendor ID from provided InputDevice",
+            DEFAULT_VENDOR_ID,
+            event.vendorId
+        )
+        assertEquals(
+            "KeyboardConfigurationEvent should pick product ID from provided InputDevice",
+            DEFAULT_PRODUCT_ID,
+            event.productId
+        )
+        assertTrue(event.isFirstConfiguration)
+
+        assertEquals(
+            "KeyboardConfigurationEvent should contain 3 configurations provided",
+            3,
+            event.layoutConfigurations.size
+        )
+        assertExpectedLayoutConfiguration(
+            event.layoutConfigurations[0],
+            "de-CH",
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+            "English(US)(Qwerty)",
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+            "en-US",
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
+        )
+        assertExpectedLayoutConfiguration(
+            event.layoutConfigurations[1],
+            "de-CH",
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+            KeyboardMetricsCollector.DEFAULT_LAYOUT_NAME,
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER,
+            "en-US",
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
+        )
+        assertExpectedLayoutConfiguration(
+            event.layoutConfigurations[2],
+            "de-CH",
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+            "German",
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+            "en-US",
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
+        )
+    }
+
+    @Test
+    fun testCreateKeyboardConfigurationEvent_withDefaultLanguageTag() {
+        val builder = KeyboardMetricsCollector.KeyboardConfigurationEvent.Builder(
+            createKeyboard(
+                DEVICE_ID,
+                DEFAULT_VENDOR_ID,
+                DEFAULT_PRODUCT_ID,
+                "und", // Undefined language tag
+                "azerty"
+            )
+        )
+        val event = builder.addLayoutSelection(
+            createImeSubtype(4, null, "qwerty"), // Default language tag
+            KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+        ).build()
+
+        assertExpectedLayoutConfiguration(
+            event.layoutConfigurations[0],
+            KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
+            "German",
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+            KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
+        )
+    }
+
+    private fun assertExpectedLayoutConfiguration(
+        configuration: KeyboardMetricsCollector.LayoutConfiguration,
+        expectedKeyboardLanguageTag: String,
+        expectedKeyboardLayoutType: Int,
+        expectedSelectedLayout: String,
+        expectedLayoutSelectionCriteria: Int,
+        expectedImeLanguageTag: String,
+        expectedImeLayoutType: Int
+    ) {
+        assertEquals(expectedKeyboardLanguageTag, configuration.keyboardLanguageTag)
+        assertEquals(expectedKeyboardLayoutType, configuration.keyboardLayoutType)
+        assertEquals(expectedSelectedLayout, configuration.keyboardLayoutName)
+        assertEquals(expectedLayoutSelectionCriteria, configuration.layoutSelectionCriteria)
+        assertEquals(expectedImeLanguageTag, configuration.imeLanguageTag)
+        assertEquals(expectedImeLayoutType, configuration.imeLayoutType)
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index e960e99..fe2ac17 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -254,6 +254,8 @@
                 .thenReturn(true);
         when(res.getBoolean(eq(com.android.internal.R.bool.config_strongAuthRequiredOnBoot)))
                 .thenReturn(true);
+        when(res.getBoolean(eq(com.android.internal.R.bool.config_repairModeSupported)))
+                .thenReturn(true);
         return res;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index 36dc6c5..a029db9 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -84,6 +84,11 @@
     }
 
     @Override
+    File getRepairModePersistentDataFile() {
+        return remapToStorageDir(super.getRepairModePersistentDataFile());
+    }
+
+    @Override
     PersistentDataBlockManagerInternal getPersistentDataBlockManager() {
         return mPersistentDataBlockManager;
     }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 10ed882..23f14f8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -457,6 +457,31 @@
         assertEquals(2, PersistentData.TYPE_SP_WEAVER);
     }
 
+    @Test
+    public void testRepairMode_emptyPersistentData() {
+        assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
+    }
+
+    @Test
+    public void testRepairMode_writeGatekeeperPersistentData() {
+        mStorage.writeRepairModePersistentData(
+                PersistentData.TYPE_SP_GATEKEEPER, SOME_USER_ID, PAYLOAD);
+
+        final PersistentData data = mStorage.readRepairModePersistentData();
+        assertEquals(PersistentData.TYPE_SP_GATEKEEPER, data.type);
+        assertArrayEquals(PAYLOAD, data.payload);
+    }
+
+    @Test
+    public void testRepairMode_writeWeaverPersistentData() {
+        mStorage.writeRepairModePersistentData(
+                PersistentData.TYPE_SP_WEAVER, SOME_USER_ID, PAYLOAD);
+
+        final PersistentData data = mStorage.readRepairModePersistentData();
+        assertEquals(PersistentData.TYPE_SP_WEAVER, data.type);
+        assertArrayEquals(PAYLOAD, data.payload);
+    }
+
     private static void assertArrayEquals(byte[] expected, byte[] actual) {
         if (!Arrays.equals(expected, actual)) {
             fail("expected:<" + Arrays.toString(expected) +
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java
new file mode 100644
index 0000000..70150c5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenRepairModeTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import static com.android.internal.widget.LockPatternUtils.USER_REPAIR_MODE;
+import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import android.app.PropertyInvalidatedCache;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LockscreenRepairModeTest extends BaseLockSettingsServiceTests {
+
+    @Before
+    public void setUp() throws Exception {
+        PropertyInvalidatedCache.disableForTestMode();
+        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void verifyPin_writeRepairModePW() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+    }
+
+    @Test
+    public void verifyPattern_writeRepairModePW() {
+        mService.setLockCredential(newPattern("4321"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPattern("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+    }
+
+    @Test
+    public void verifyPassword_writeRepairModePW() {
+        mService.setLockCredential(newPassword("4321"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPassword("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                mService.getCredentialType(USER_REPAIR_MODE));
+    }
+
+    @Test
+    public void verifyCredential_writeRepairModePW_repairModeActive() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+
+        setRepairModeActive(true);
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+                mService.verifyCredential(
+                                newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+    }
+
+    @Test
+    public void deleteRepairModePersistentData() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                                newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+
+        mService.deleteRepairModePersistentDataIfNeeded();
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+    }
+
+    @Test
+    public void verifyPin_userRepairMode() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        setRepairModeActive(true);
+
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(newPin("1234"), USER_REPAIR_MODE, 0 /* flags */)
+                        .getResponseCode());
+    }
+
+    @Test
+    public void verifyPattern_userRepairMode() {
+        mService.setLockCredential(newPattern("4321"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPattern("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        setRepairModeActive(true);
+
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(newPattern("4321"), USER_REPAIR_MODE, 0 /* flags */)
+                        .getResponseCode());
+    }
+
+    @Test
+    public void verifyPassword_userRepairMode() {
+        mService.setLockCredential(newPassword("4321"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                        newPassword("4321"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        setRepairModeActive(true);
+
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                mService.getCredentialType(USER_REPAIR_MODE));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(newPassword("4321"), USER_REPAIR_MODE, 0 /* flags */)
+                        .getResponseCode());
+    }
+
+    @Test
+    public void verifyCredential_userRepairMode_repairModeIsNotActive() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                                newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+                mService.verifyCredential(newPin("1234"), USER_REPAIR_MODE, 0 /* flags */)
+                        .getResponseCode());
+    }
+
+    @Test
+    public void verifyCredential_userRepairMode_wrongPin() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
+        assertSame(PersistentData.NONE, mStorage.readRepairModePersistentData());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(
+                                newPin("1234"), PRIMARY_USER_ID, VERIFY_FLAG_WRITE_REPAIR_MODE_PW)
+                        .getResponseCode());
+        setRepairModeActive(true);
+
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PIN,
+                mService.getCredentialType(USER_REPAIR_MODE));
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+                mService.verifyCredential(newPin("5678"), USER_REPAIR_MODE, 0 /* flags */)
+                        .getResponseCode());
+    }
+
+    private void setRepairModeActive(boolean active) {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.REPAIR_MODE_ACTIVE, active ? 1 : 0);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index c42928e..ddd1221 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -38,6 +38,7 @@
 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 org.testng.Assert.assertThrows;
 
 import android.app.ActivityManagerInternal;
@@ -49,14 +50,19 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionCallback;
+import android.media.projection.IMediaProjectionWatcherCallback;
 import android.media.projection.ReviewGrantedConsentResult;
+import android.os.Binder;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.view.ContentRecordingSession;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -86,6 +92,7 @@
     private static final int UID = 10;
     private static final String PACKAGE_NAME = "test.package";
     private final ApplicationInfo mAppInfo = new ApplicationInfo();
+    private final TestLooper mTestLooper = new TestLooper();
     private static final ContentRecordingSession DISPLAY_SESSION =
             ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
     // Callback registered by an app on a MediaProjection instance.
@@ -110,6 +117,14 @@
                 }
             };
 
+    private final MediaProjectionManagerService.Injector mTestLooperInjector =
+            new MediaProjectionManagerService.Injector() {
+                @Override
+                Looper createCallbackLooper() {
+                    return mTestLooper.getLooper();
+                }
+            };
+
     private Context mContext;
     private MediaProjectionManagerService mService;
     private OffsettableClock mClock;
@@ -122,12 +137,15 @@
     private WindowManagerInternal mWindowManagerInternal;
     @Mock
     private PackageManager mPackageManager;
+    @Mock
+    private IMediaProjectionWatcherCallback mWatcherCallback;
     @Captor
     private ArgumentCaptor<ContentRecordingSession> mSessionCaptor;
 
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
+        when(mWatcherCallback.asBinder()).thenReturn(new Binder());
 
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
         LocalServices.addService(ActivityManagerInternal.class, mAmInternal);
@@ -671,6 +689,60 @@
         assertThat(mService.isCurrentProjection(projection)).isTrue();
     }
 
+    @FlakyTest(bugId = 288342281)
+    @Test
+    public void setContentRecordingSession_successful_notifiesListeners()
+            throws Exception {
+        mService.addCallback(mWatcherCallback);
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+        mService.setContentRecordingSession(DISPLAY_SESSION);
+
+        verify(mWatcherCallback).onRecordingSessionSet(
+                projection.getProjectionInfo(),
+                DISPLAY_SESSION
+        );
+    }
+
+    @Test
+    public void setContentRecordingSession_notifiesListenersOnCallbackLooper()
+            throws Exception {
+        mService = new MediaProjectionManagerService(mContext, mTestLooperInjector);
+        mService.addCallback(mWatcherCallback);
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+
+        mService.setContentRecordingSession(DISPLAY_SESSION);
+        // Callback not notified yet, as test looper hasn't dispatched the message yet
+        verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any());
+
+        mTestLooper.dispatchAll();
+        // Message dispatched on test looper. Callback should now be notified.
+        verify(mWatcherCallback).onRecordingSessionSet(
+                projection.getProjectionInfo(),
+                DISPLAY_SESSION
+        );
+    }
+
+    @Test
+    public void setContentRecordingSession_failure_doesNotNotifyListeners()
+            throws Exception {
+        mService.addCallback(mWatcherCallback);
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        projection.start(mIMediaProjectionCallback);
+
+        doReturn(false).when(mWindowManagerInternal).setContentRecordingSession(
+                any(ContentRecordingSession.class));
+        mService.setContentRecordingSession(DISPLAY_SESSION);
+
+        verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any());
+    }
+
     private void verifySetSessionWithContent(@ContentRecordingSession.RecordContent int content) {
         verify(mWindowManagerInternal, atLeastOnce()).setContentRecordingSession(
                 mSessionCaptor.capture());
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index 52c6777..24029b1 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -16,15 +16,18 @@
 
 package com.android.server.os;
 
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
 
+import android.app.role.RoleManager;
 import android.content.Context;
 import android.os.Binder;
 import android.os.BugreportManager.BugreportCallback;
 import android.os.IBinder;
 import android.os.IDumpstateListener;
+import android.os.Process;
 import android.os.RemoteException;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -37,21 +40,23 @@
 import org.junit.runner.RunWith;
 
 import java.io.FileDescriptor;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 @RunWith(AndroidJUnit4.class)
 public class BugreportManagerServiceImplTest {
 
-    Context mContext;
-    BugreportManagerServiceImpl mService;
-    BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
+    private Context mContext;
+    private BugreportManagerServiceImpl mService;
+    private BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
 
-    int mCallingUid = 1234;
-    String mCallingPackage  = "test.package";
+    private int mCallingUid = 1234;
+    private String mCallingPackage  = "test.package";
 
-    String mBugreportFile = "bugreport-file.zip";
-    String mBugreportFile2 = "bugreport-file2.zip";
+    private String mBugreportFile = "bugreport-file.zip";
+    private String mBugreportFile2 = "bugreport-file2.zip";
 
     @Before
     public void setUp() {
@@ -109,6 +114,36 @@
                 BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
     }
 
+    @Test
+    public void testCancelBugreportWithoutRole() throws Exception {
+        // Clear out allowlisted packages.
+        mService = new BugreportManagerServiceImpl(
+                new BugreportManagerServiceImpl.Injector(mContext, new ArraySet<>()));
+
+        assertThrows(SecurityException.class, () -> mService.cancelBugreport(
+                Binder.getCallingUid(), mContext.getPackageName()));
+    }
+
+    @Test
+    public void testCancelBugreportWithRole() throws Exception {
+        // Clear out allowlisted packages.
+        mService = new BugreportManagerServiceImpl(
+                new BugreportManagerServiceImpl.Injector(mContext, new ArraySet<>()));
+        RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+        CallbackFuture future = new CallbackFuture();
+        runWithShellPermissionIdentity(() -> roleManager.setBypassingRoleQualification(true));
+        runWithShellPermissionIdentity(() -> roleManager.addRoleHolderAsUser(
+                "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION",
+                mContext.getPackageName(),
+                /* flags= */ 0,
+                Process.myUserHandle(),
+                mContext.getMainExecutor(),
+                future));
+
+        assertThat(future.get()).isEqualTo(true);
+        mService.cancelBugreport(Binder.getCallingUid(), mContext.getPackageName());
+    }
+
     private static class Listener implements IDumpstateListener {
         CountDownLatch mLatch;
         int mErrorCode;
@@ -149,4 +184,12 @@
             return mErrorCode;
         }
     }
+
+    private static class CallbackFuture extends CompletableFuture<Boolean>
+            implements Consumer<Boolean> {
+        @Override
+        public void accept(Boolean successful) {
+            complete(successful);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
index 45fff48..2cd9198 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
@@ -57,6 +57,7 @@
     private static final String PACKAGE_3 = "pkg3";
     private static final String CLASS_1 = "cls1";
     private static final String CLASS_2 = "cls2";
+    private static final String CHOOSER_ACTIVITY = "ChooserActivity";
     private static final double DELTA = 1e-6;
     private static final long NOW = System.currentTimeMillis();
     private static final Range<Long> WITHIN_ONE_DAY = new Range(
@@ -246,7 +247,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         // Verification
         assertEquals(0.514f, mShareTarget1.getScore(), DELTA);
@@ -278,7 +279,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         verify(mDataManager, times(1)).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
                 anySet());
@@ -311,7 +312,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         verify(mDataManager, times(1)).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
                 anySet());
@@ -349,7 +350,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 4, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 4, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         verify(mDataManager, never()).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
                 anySet());
@@ -377,7 +378,7 @@
                 anyLong())).thenReturn(
                 List.of(createUsageEvent(PACKAGE_2),
                         createUsageEvent(PACKAGE_3),
-                        createUsageEvent(SharesheetModelScorer.CHOOSER_ACTIVITY),
+                        createUsageEvent(CHOOSER_ACTIVITY),
                         createUsageEvent(PACKAGE_3),
                         createUsageEvent(PACKAGE_3))
         );
@@ -385,7 +386,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         verify(mDataManager, times(1)).queryAppMovingToForegroundEvents(anyInt(), anyLong(),
                 anyLong());
@@ -413,7 +414,7 @@
                 anyLong())).thenReturn(
                 List.of(createUsageEvent(PACKAGE_3),
                         createUsageEvent(PACKAGE_3),
-                        createUsageEvent(SharesheetModelScorer.CHOOSER_ACTIVITY),
+                        createUsageEvent(CHOOSER_ACTIVITY),
                         createUsageEvent(PACKAGE_3),
                         createUsageEvent(PACKAGE_3))
         );
@@ -421,7 +422,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         verify(mDataManager, times(1)).queryAppMovingToForegroundEvents(anyInt(), anyLong(),
                 anyLong());
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
index fdf94be..39cc653 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
@@ -182,7 +182,7 @@
         UserInfo secondaryUser = addUser();
         UserInfo profile = addProfile(secondaryUser);
         // Add the profile it to the users being removed.
-        mUserManagerService.addRemovingUserIdLocked(profile.id);
+        mUserManagerService.addRemovingUserId(profile.id);
         // We should reuse the badge from the profile being removed.
         assertEquals("Badge index not reused while removing a user", 0,
                 mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id,
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java
index 1f4c9f8..b6fd65e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java
@@ -111,7 +111,7 @@
 
     private void removeUser(int userId) {
         mUserManagerService.removeUserInfo(userId);
-        mUserManagerService.addRemovingUserIdLocked(userId);
+        mUserManagerService.addRemovingUserId(userId);
     }
 
     private void assertNoNextIdAvailable(String message) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 4af0323..592be2d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -70,6 +70,9 @@
 
         LocalServices.removeServiceForTest(UserManagerInternal.class);
         mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext());
+        // Put the current user to mUsers. UMS can't find userlist.xml, and fallbackToSingleUserLP.
+        mUserManagerService.putUserInfo(
+                new UserInfo(ActivityManager.getCurrentUser(), "Current User", 0));
 
         restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml");
         restrictionsFile.delete();
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 6bcda3f..dc86e5f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -53,6 +53,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -545,23 +547,42 @@
     public void testRemoveUserWhenPossible_withProfiles() throws Exception {
         assumeHeadlessModeEnabled();
         assumeCloneEnabled();
-        final UserInfo parentUser = createUser("Human User", /* flags= */ 0);
-        final UserInfo cloneProfileUser = createProfileForUser("Clone Profile user",
+        final List<String> profileTypesToCreate = Arrays.asList(
                 UserManager.USER_TYPE_PROFILE_CLONE,
-                parentUser.id);
+                UserManager.USER_TYPE_PROFILE_MANAGED
+        );
 
-        final UserInfo workProfileUser = createProfileForUser("Work Profile user",
-                UserManager.USER_TYPE_PROFILE_MANAGED,
-                parentUser.id);
+        final UserInfo parentUser = createUser("Human User", /* flags= */ 0);
+        assertWithMessage("Could not create parent user")
+                .that(parentUser).isNotNull();
+
+        final List<Integer> profileIds = new ArrayList<>();
+        for (String profileType : profileTypesToCreate) {
+            final String name = profileType.substring(profileType.lastIndexOf('.') + 1);
+            if (mUserManager.canAddMoreProfilesToUser(profileType, parentUser.id)) {
+                final UserInfo profile = createProfileForUser(name, profileType, parentUser.id);
+                assertWithMessage("Could not create " + name)
+                        .that(profile).isNotNull();
+                profileIds.add(profile.id);
+            } else {
+                Slog.w(TAG, "Can not add " + name + " to user #" + parentUser.id);
+            }
+        }
+
+        // Test shouldn't pass or fail unless it's allowed to add profiles to secondary users.
+        assumeTrue("Not possible to create any profiles to user #" + parentUser.id,
+                profileIds.size() > 0);
 
         assertThat(mUserManager.removeUserWhenPossible(parentUser.getUserHandle(),
                 /* overrideDevicePolicy= */ false))
                 .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
         waitForUserRemoval(parentUser.id);
 
-        assertThat(hasUser(parentUser.id)).isFalse();
-        assertThat(hasUser(cloneProfileUser.id)).isFalse();
-        assertThat(hasUser(workProfileUser.id)).isFalse();
+        assertWithMessage("Parent user still exists")
+                .that(hasUser(parentUser.id)).isFalse();
+        profileIds.forEach(id ->
+                assertWithMessage("Profile still exists")
+                        .that(hasUser(id)).isFalse());
     }
 
     /** Tests creating a FULL user via specifying userType. */
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index 0b13f9a..5f84e9e 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -30,6 +30,7 @@
 import android.provider.Settings.Global;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
 import android.util.ArrayMap;
 
 import com.android.frameworks.servicestests.R;
@@ -115,6 +116,7 @@
         testServiceDefaultValue_On(ServiceType.NULL);
     }
 
+    @Suppress
     @SmallTest
     public void testGetBatterySaverPolicy_PolicyVibration_DefaultValueCorrect() {
         testServiceDefaultValue_Off(ServiceType.VIBRATION);
@@ -200,6 +202,7 @@
         testServiceDefaultValue_On(ServiceType.QUICK_DOZE);
     }
 
+    @Suppress
     @SmallTest
     public void testUpdateConstants_getCorrectData() {
         mBatterySaverPolicy.updateConstantsLocked(BATTERY_SAVER_CONSTANTS, "");
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 541739d..2136811 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1933,6 +1933,36 @@
         }, 20, 30);
     }
 
+    @Test
+    public void isComponentEnabledForCurrentProfiles_profileUserId() {
+        final int profileUserId = 10;
+        when(mUserProfiles.isProfileUser(profileUserId)).thenReturn(true);
+        // Only approve for parent user (0)
+        mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", 0, true);
+
+        // Test that the component is enabled after calling rebindServices with profile userId (10)
+        mService.rebindServices(false, profileUserId);
+        assertThat(mService.isComponentEnabledForCurrentProfiles(
+                new ComponentName("pkg1", "cmp1"))).isTrue();
+    }
+
+    @Test
+    public void isComponentEnabledForCurrentProfiles_profileUserId_NAS() {
+        final int profileUserId = 10;
+        when(mUserProfiles.isProfileUser(profileUserId)).thenReturn(true);
+        // Do not rebind for parent users (NAS use-case)
+        ManagedServices service = spy(mService);
+        when(service.allowRebindForParentUser()).thenReturn(false);
+
+        // Only approve for parent user (0)
+        service.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", 0, true);
+
+        // Test that the component is disabled after calling rebindServices with profile userId (10)
+        service.rebindServices(false, profileUserId);
+        assertThat(service.isComponentEnabledForCurrentProfiles(
+                new ComponentName("pkg1", "cmp1"))).isFalse();
+    }
+
     private void mockServiceInfoWithMetaData(List<ComponentName> componentNames,
             ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas)
             throws RemoteException {
@@ -2276,6 +2306,11 @@
         protected String getRequiredPermission() {
             return null;
         }
+
+        @Override
+        protected boolean allowRebindForParentUser() {
+            return true;
+        }
     }
 
     class TestManagedServicesSettings extends TestManagedServices {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java
index 6f7bace..082b675 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java
@@ -16,24 +16,44 @@
 
 package com.android.server.notification;
 
+import android.annotation.Nullable;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
 
+import com.google.common.base.MoreObjects;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 public class NotificationChannelLoggerFake implements NotificationChannelLogger {
     static class CallRecord {
-        public NotificationChannelEvent event;
-        CallRecord(NotificationChannelEvent event) {
+        public final NotificationChannelEvent event;
+        @Nullable public final String channelId;
+
+        CallRecord(NotificationChannelEvent event, @Nullable String channelId) {
             this.event = event;
+            this.channelId = channelId;
         }
 
         @Override
         public String toString() {
-            return "CallRecord{" +
-                    "event=" + event +
-                    '}';
+            return MoreObjects.toStringHelper(this)
+                    .add("event", event)
+                    .add(channelId, channelId)
+                    .toString();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return (obj instanceof CallRecord other)
+                    && Objects.equals(this.event, other.event)
+                    && Objects.equals(this.channelId, other.channelId);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(event, channelId);
         }
     }
 
@@ -47,20 +67,24 @@
         return mCalls.get(index);
     }
 
+    void clear() {
+        mCalls.clear();
+    }
+
     @Override
     public void logNotificationChannel(NotificationChannelEvent event, NotificationChannel channel,
             int uid, String pkg, int oldImportance, int newImportance) {
-        mCalls.add(new CallRecord(event));
+        mCalls.add(new CallRecord(event, channel.getId()));
     }
 
     @Override
     public void logNotificationChannelGroup(NotificationChannelEvent event,
             NotificationChannelGroup channelGroup, int uid, String pkg, boolean wasBlocked) {
-        mCalls.add(new CallRecord(event));
+        mCalls.add(new CallRecord(event, channelGroup.getId()));
     }
 
     @Override
     public void logAppEvent(NotificationChannelEvent event, int uid, String pkg) {
-        mCalls.add(new CallRecord(event));
+        mCalls.add(new CallRecord(event, null));
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 95fae07..7b16500 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -15,17 +15,20 @@
  */
 package com.android.server.notification;
 
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.NO_SORT_BY_INTERRUPTIVENESS;
+import static android.telecom.TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 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.Notification;
@@ -33,8 +36,10 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Person;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -47,6 +52,8 @@
 import android.telecom.TelecomManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
 import com.android.server.UiServiceTestCase;
 
@@ -54,16 +61,17 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 
 @SmallTest
-@RunWith(Parameterized.class)
+@RunWith(AndroidJUnit4.class)
 public class NotificationComparatorTest extends UiServiceTestCase {
     @Mock Context mMockContext;
     @Mock TelecomManager mTm;
@@ -97,24 +105,9 @@
     private NotificationRecord mRecordColorized;
     private NotificationRecord mRecordColorizedCall;
 
-    @Parameterized.Parameters(name = "sortByInterruptiveness={0}")
-    public static Boolean[] getSortByInterruptiveness() {
-        return new Boolean[] { true, false };
-    }
-
-    @Parameterized.Parameter
-    public boolean mSortByInterruptiveness;
-
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        SystemUiSystemPropertiesFlags.TEST_RESOLVER = flag -> {
-            if (flag.mSysPropKey.equals(NO_SORT_BY_INTERRUPTIVENESS.mSysPropKey)) {
-                return !mSortByInterruptiveness;
-            }
-            return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag);
-        };
-
         int userId = UserHandle.myUserId();
 
         final Resources res = mContext.getResources();
@@ -309,13 +302,8 @@
         expected.add(mNoMediaSessionMedia);
         expected.add(mRecordCheater);
         expected.add(mRecordCheaterColorized);
-        if (mSortByInterruptiveness) {
-            expected.add(mRecordMinCall);
-            expected.add(mRecordMinCallNonInterruptive);
-        } else {
-            expected.add(mRecordMinCallNonInterruptive);
-            expected.add(mRecordMinCall);
-        }
+        expected.add(mRecordMinCallNonInterruptive);
+        expected.add(mRecordMinCall);
 
         List<NotificationRecord> actual = new ArrayList<>();
         actual.addAll(expected);
@@ -330,11 +318,7 @@
     public void testRankingScoreOverrides() {
         NotificationComparator comp = new NotificationComparator(mMockContext);
         NotificationRecord recordMinCallNonInterruptive = spy(mRecordMinCallNonInterruptive);
-        if (mSortByInterruptiveness) {
-            assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) < 0);
-        } else {
-            assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
-        }
+        assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
 
         when(recordMinCallNonInterruptive.getRankingScore()).thenReturn(1f);
         assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
@@ -360,6 +344,73 @@
         assertTrue(comp.isImportantPeople(mRecordContact));
     }
 
+    @Test
+    public void testChangeDialerPackageWhileSorting() throws InterruptedException {
+        final int halfList = 100;
+        int userId = UserHandle.myUserId();
+        when(mTm.getDefaultDialerPackage()).thenReturn("B");
+
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
+                BroadcastReceiver.class);
+        NotificationComparator comparator = new NotificationComparator(mMockContext);
+        verify(mMockContext).registerReceiver(broadcastReceiverCaptor.capture(), any());
+        BroadcastReceiver dialerChangedBroadcastReceiver = broadcastReceiverCaptor.getValue();
+
+        ArrayList<NotificationRecord> records = new ArrayList<>();
+        for (int i = 0; i < halfList; i++) {
+            Notification notifCallFromPkgA = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
+                    .setCategory(Notification.CATEGORY_CALL)
+                    .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                    .build();
+            records.add(new NotificationRecord(mMockContext,
+                    new StatusBarNotification("A", "A", 2 * i, "callA", callUid, callUid,
+                            notifCallFromPkgA, new UserHandle(userId), "", 0),
+                    getDefaultChannel()));
+
+            Notification notifCallFromPkgB = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
+                    .setCategory(Notification.CATEGORY_CALL)
+                    .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                    .build();
+            records.add(new NotificationRecord(mMockContext,
+                    new StatusBarNotification("B", "B", 2 * i + 1, "callB", callUid, callUid,
+                            notifCallFromPkgB, new UserHandle(userId), "", 0),
+                    getDefaultChannel()));
+        }
+
+        CountDownLatch allDone = new CountDownLatch(2);
+        new Thread(() -> {
+            // The lock prevents the other thread from changing the dialer package mid-sort, so:
+            // 1) Results should be "all B before all A" (asserted below).
+            // 2) No "IllegalArgumentException: Comparison method violates its general contract!"
+            synchronized (comparator.mStateLock) {
+                records.sort(comparator);
+                allDone.countDown();
+            }
+        }).start();
+
+        new Thread(() -> {
+            String nextDialer = "A";
+            while (allDone.getCount() == 2) {
+                Intent dialerChangedIntent = new Intent();
+                dialerChangedIntent.putExtra(EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, nextDialer);
+                dialerChangedBroadcastReceiver.onReceive(mMockContext, dialerChangedIntent);
+                nextDialer = nextDialer.equals("A") ? "B" : "A";
+            }
+            allDone.countDown();
+        }).start();
+
+        allDone.await();
+
+        for (int i = 0; i < halfList; i++) {
+            assertWithMessage("Wrong element in position #" + i)
+                    .that(records.get(i).getSbn().getPackageName()).isEqualTo("B");
+        }
+        for (int i = halfList; i < 2 * halfList; i++) {
+            assertWithMessage("Wrong element in position #" + i)
+                    .that(records.get(i).getSbn().getPackageName()).isEqualTo("A");
+        }
+    }
+
     private NotificationChannel getDefaultChannel() {
         return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
                 NotificationManager.IMPORTANCE_LOW);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 4849665..bdee99b 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -62,6 +62,8 @@
 import static android.os.Build.VERSION_CODES.O_MR1;
 import static android.os.Build.VERSION_CODES.P;
 import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
+import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
 import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
@@ -71,6 +73,7 @@
 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;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
@@ -78,11 +81,14 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
+import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
+import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
 import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
 import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
 import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
@@ -185,6 +191,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.Process;
@@ -398,6 +405,7 @@
     UriGrantsManagerInternal mUgmInternal;
     @Mock
     AppOpsManager mAppOpsManager;
+    private AppOpsManager.OnOpChangedListener mOnPermissionChangeListener;
     @Mock
     private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback
             mNotificationAssistantAccessGrantedCallback;
@@ -414,6 +422,7 @@
     @Mock
     MultiRateLimiter mToastRateLimiter;
     BroadcastReceiver mPackageIntentReceiver;
+    BroadcastReceiver mUserSwitchIntentReceiver;
     NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
     TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker;
 
@@ -597,6 +606,13 @@
         tr.addOverride(com.android.internal.R.string.config_defaultSearchSelectorPackageName,
                 SEARCH_SELECTOR_PKG);
 
+        doAnswer(invocation -> {
+            mOnPermissionChangeListener = invocation.getArgument(2);
+            return null;
+        }).when(mAppOpsManager).startWatchingMode(eq(AppOpsManager.OP_POST_NOTIFICATION), any(),
+                any());
+        when(mUmInternal.isUserInitialized(anyInt())).thenReturn(true);
+
         mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
         mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
                 mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
@@ -644,8 +660,12 @@
                     && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) {
                 mPackageIntentReceiver = broadcastReceivers.get(i);
             }
+            if (filter.hasAction(Intent.ACTION_USER_SWITCHED)) {
+                mUserSwitchIntentReceiver = broadcastReceivers.get(i);
+            }
         }
         assertNotNull("package intent receiver should exist", mPackageIntentReceiver);
+        assertNotNull("User-switch receiver should exist", mUserSwitchIntentReceiver);
 
         // Pretend the shortcut exists
         List<ShortcutInfo> shortcutInfos = new ArrayList<>();
@@ -685,6 +705,11 @@
 
         mTestFlagResolver.setFlagOverride(FSI_FORCE_DEMOTE, false);
         mTestFlagResolver.setFlagOverride(SHOW_STICKY_HUN_FOR_DENIED_FSI, false);
+
+        var checker = mock(TestableNotificationManagerService.ComponentPermissionChecker.class);
+        mService.permissionChecker = checker;
+        when(checker.check(anyString(), anyInt(), anyInt(), anyBoolean()))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
     }
 
     @After
@@ -705,6 +730,7 @@
 
     @After
     public void assertAllTrackersFinishedOrCancelled() {
+        waitForIdle(); // Finish async work.
         // Verify that no trackers were left dangling.
         for (PostNotificationTracker tracker : mPostNotificationTrackerFactory.mCreatedTrackers) {
             assertThat(tracker.isOngoing()).isFalse();
@@ -714,6 +740,7 @@
 
     @After
     public void assertAllWakeLocksReleased() {
+        waitForIdle(); // Finish async work.
         for (WakeLock wakeLock : mAcquiredWakeLocks) {
             assertThat(wakeLock.isHeld()).isFalse();
         }
@@ -865,13 +892,18 @@
     }
 
     private NotificationRecord generateNotificationRecord(NotificationChannel channel, int userId) {
+        return generateNotificationRecord(channel, 1, userId);
+    }
+
+    private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
+            int userId) {
         if (channel == null) {
             channel = mTestNotificationChannel;
         }
         Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, "tag", mUid, 0,
                 nb.build(), new UserHandle(userId), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
@@ -1988,10 +2020,10 @@
         final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
                 sbn.getNotification(), sbn.getUserId());
-        Thread.sleep(1);  // make sure the system clock advances before the next step
+        mTestableLooper.moveTimeForward(1);
         // THEN it is canceled
         mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
-        Thread.sleep(1);  // here too
+        mTestableLooper.moveTimeForward(1);
         // THEN it is posted again (before the cancel has a chance to finish)
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
                 sbn.getNotification(), sbn.getUserId());
@@ -2281,8 +2313,8 @@
                 mTestNotificationChannel, 1, "group", true);
         notif.getNotification().flags |= Notification.FLAG_NO_CLEAR;
         mService.addNotification(notif);
-        mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true,
-                notif.getUserId(), 0, null);
+        mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0,
+                notif.getUserId(), REASON_CANCEL);
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
@@ -3020,7 +3052,7 @@
         notif.getNotification().flags |= Notification.FLAG_NO_CLEAR;
         mService.addNotification(notif);
         mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0,
-                Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), 0, null);
+                Notification.FLAG_ONGOING_EVENT, notif.getUserId(), REASON_CANCEL);
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
@@ -3047,8 +3079,8 @@
                 mTestNotificationChannel, 1, "group", true);
         notif.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
         mService.addNotification(notif);
-        mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true,
-                notif.getUserId(), 0, null);
+        mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0,
+                notif.getUserId(), REASON_CANCEL);
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
@@ -3202,6 +3234,108 @@
     }
 
     @Test
+    public void onOpChanged_permissionRevoked_cancelsAllNotificationsFromPackage()
+            throws RemoteException {
+        // Have preexisting posted notifications from revoked package and other packages.
+        mService.addNotification(new NotificationRecord(mContext,
+                generateSbn("revoked", 1001, 1, 0), mTestNotificationChannel));
+        mService.addNotification(new NotificationRecord(mContext,
+                generateSbn("other", 1002, 2, 0), mTestNotificationChannel));
+        // Have preexisting enqueued notifications from revoked package and other packages.
+        mService.addEnqueuedNotification(new NotificationRecord(mContext,
+                generateSbn("revoked", 1001, 3, 0), mTestNotificationChannel));
+        mService.addEnqueuedNotification(new NotificationRecord(mContext,
+                generateSbn("other", 1002, 4, 0), mTestNotificationChannel));
+        assertThat(mService.mNotificationList).hasSize(2);
+        assertThat(mService.mEnqueuedNotifications).hasSize(2);
+
+        when(mPackageManagerInternal.getPackageUid("revoked", 0, 0)).thenReturn(1001);
+        when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false);
+
+        mOnPermissionChangeListener.onOpChanged(
+                AppOpsManager.OPSTR_POST_NOTIFICATION, "revoked", 0);
+        waitForIdle();
+
+        assertThat(mService.mNotificationList).hasSize(1);
+        assertThat(mService.mNotificationList.get(0).getSbn().getPackageName()).isEqualTo("other");
+        assertThat(mService.mEnqueuedNotifications).hasSize(1);
+        assertThat(mService.mEnqueuedNotifications.get(0).getSbn().getPackageName()).isEqualTo(
+                "other");
+    }
+
+    @Test
+    public void onOpChanged_permissionStillGranted_notificationsAreNotAffected()
+            throws RemoteException {
+        // NOTE: This combination (receiving the onOpChanged broadcast for a package, the permission
+        // being now granted, AND having previously posted notifications from said package) should
+        // never happen (if we trust the broadcasts are correct). So this test is for a what-if
+        // scenario, to verify we still handle it reasonably.
+
+        // Have preexisting posted notifications from specific package and other packages.
+        mService.addNotification(new NotificationRecord(mContext,
+                generateSbn("granted", 1001, 1, 0), mTestNotificationChannel));
+        mService.addNotification(new NotificationRecord(mContext,
+                generateSbn("other", 1002, 2, 0), mTestNotificationChannel));
+        // Have preexisting enqueued notifications from specific package and other packages.
+        mService.addEnqueuedNotification(new NotificationRecord(mContext,
+                generateSbn("granted", 1001, 3, 0), mTestNotificationChannel));
+        mService.addEnqueuedNotification(new NotificationRecord(mContext,
+                generateSbn("other", 1002, 4, 0), mTestNotificationChannel));
+        assertThat(mService.mNotificationList).hasSize(2);
+        assertThat(mService.mEnqueuedNotifications).hasSize(2);
+
+        when(mPackageManagerInternal.getPackageUid("granted", 0, 0)).thenReturn(1001);
+        when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true);
+
+        mOnPermissionChangeListener.onOpChanged(
+                AppOpsManager.OPSTR_POST_NOTIFICATION, "granted", 0);
+        waitForIdle();
+
+        assertThat(mService.mNotificationList).hasSize(2);
+        assertThat(mService.mEnqueuedNotifications).hasSize(2);
+    }
+
+    @Test
+    public void onOpChanged_notInitializedUser_ignored() throws RemoteException {
+        when(mUmInternal.isUserInitialized(eq(0))).thenReturn(false);
+
+        mOnPermissionChangeListener.onOpChanged(
+                AppOpsManager.OPSTR_POST_NOTIFICATION, "package", 0);
+        waitForIdle();
+
+        // We early-exited and didn't even query PM for package details.
+        verify(mPackageManagerInternal, never()).getPackageUid(any(), anyLong(), anyInt());
+    }
+
+    @Test
+    public void setNotificationsEnabledForPackage_disabling_clearsNotifications() throws Exception {
+        mService.addNotification(new NotificationRecord(mContext,
+                generateSbn("package", 1001, 1, 0), mTestNotificationChannel));
+        assertThat(mService.mNotificationList).hasSize(1);
+        when(mPackageManagerInternal.getPackageUid("package", 0, 0)).thenReturn(1001);
+        when(mPermissionHelper.hasRequestedPermission(any(), eq("package"), anyInt())).thenReturn(
+                true);
+
+        // Start with granted permission and simulate effect of revoking it.
+        when(mPermissionHelper.hasPermission(1001)).thenReturn(true);
+        doAnswer(invocation -> {
+            when(mPermissionHelper.hasPermission(1001)).thenReturn(false);
+            mOnPermissionChangeListener.onOpChanged(
+                    AppOpsManager.OPSTR_POST_NOTIFICATION, "package", 0);
+            return null;
+        }).when(mPermissionHelper).setNotificationPermission("package", 0, false, true);
+
+        mBinderService.setNotificationsEnabledForPackage("package", 1001, false);
+        waitForIdle();
+
+        assertThat(mService.mNotificationList).hasSize(0);
+
+        mTestableLooper.moveTimeForward(500);
+        waitForIdle();
+        verify(mContext).sendBroadcastAsUser(any(), eq(UserHandle.of(0)), eq(null));
+    }
+
+    @Test
     public void testUpdateAppNotifyCreatorBlock() throws Exception {
         when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
 
@@ -4221,6 +4355,7 @@
     @Test
     public void testSetNASMigrationDoneAndResetDefault_enableNAS() throws Exception {
         int userId = 10;
+        setNASMigrationDone(false, userId);
         when(mUm.getProfileIds(userId, false)).thenReturn(new int[]{userId});
 
         mBinderService.setNASMigrationDoneAndResetDefault(userId, true);
@@ -4232,6 +4367,7 @@
     @Test
     public void testSetNASMigrationDoneAndResetDefault_disableNAS() throws Exception {
         int userId = 10;
+        setNASMigrationDone(false, userId);
         when(mUm.getProfileIds(userId, false)).thenReturn(new int[]{userId});
 
         mBinderService.setNASMigrationDoneAndResetDefault(userId, false);
@@ -4244,6 +4380,8 @@
     public void testSetNASMigrationDoneAndResetDefault_multiProfile() throws Exception {
         int userId1 = 11;
         int userId2 = 12; //work profile
+        setNASMigrationDone(false, userId1);
+        setNASMigrationDone(false, userId2);
         setUsers(new int[]{userId1, userId2});
         when(mUm.isManagedProfile(userId2)).thenReturn(true);
         when(mUm.getProfileIds(userId1, false)).thenReturn(new int[]{userId1, userId2});
@@ -4257,6 +4395,8 @@
     public void testSetNASMigrationDoneAndResetDefault_multiUser() throws Exception {
         int userId1 = 11;
         int userId2 = 12;
+        setNASMigrationDone(false, userId1);
+        setNASMigrationDone(false, userId2);
         setUsers(new int[]{userId1, userId2});
         when(mUm.getProfileIds(userId1, false)).thenReturn(new int[]{userId1});
         when(mUm.getProfileIds(userId2, false)).thenReturn(new int[]{userId2});
@@ -10104,7 +10244,7 @@
 
         try {
             mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(),
-                    r.getSbn().getTag(), r,false);
+                    r.getSbn().getTag(), r, false, false);
             fail("Allowed a contextual direct reply with an immutable intent to be posted");
         } catch (IllegalArgumentException e) {
             // good
@@ -10135,7 +10275,7 @@
         r.applyAdjustments();
 
         mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(),
-                r.getSbn().getTag(), r,false);
+                r.getSbn().getTag(), r, false, false);
     }
 
     @Test
@@ -10169,7 +10309,7 @@
         r.applyAdjustments();
 
         mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(),
-                    r.getSbn().getTag(), r,false);
+                    r.getSbn().getTag(), r, false, false);
     }
 
     @Test
@@ -10382,7 +10522,7 @@
 
         // normal blocked notifications - blocked
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse();
+                r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isFalse();
 
         // just using the style - blocked
         nb.setStyle(new Notification.MediaStyle());
@@ -10391,7 +10531,7 @@
         r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse();
+                r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isFalse();
 
         // using the style, but incorrect type in session - blocked
         nb.setStyle(new Notification.MediaStyle());
@@ -10403,7 +10543,7 @@
         r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse();
+                r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isFalse();
 
         // style + media session - bypasses block
         nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class)));
@@ -10412,7 +10552,7 @@
         r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue();
+                r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
     }
 
     @Test
@@ -10495,7 +10635,7 @@
 
         // normal blocked notifications - blocked
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse();
+                r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isFalse();
 
         // just using the style - blocked
         Person person = new Person.Builder()
@@ -10509,36 +10649,36 @@
         r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse();
+                r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isFalse();
 
         // style + managed call - bypasses block
         when(mTelecomManager.isInManagedCall()).thenReturn(true);
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue();
+                r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
 
         // style + self managed call - bypasses block
         when(mTelecomManager.isInSelfManagedCall(
                 r.getSbn().getPackageName(), r.getUser())).thenReturn(true);
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue();
+                r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
 
         // set telecom manager to null - blocked
         mService.setTelecomManager(null);
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                           r.getSbn().getId(), r.getSbn().getTag(), r, false))
+                           r.getSbn().getId(), r.getSbn().getTag(), r, false, false))
                 .isFalse();
 
         // set telecom feature to false - blocked
         when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(false);
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                           r.getSbn().getId(), r.getSbn().getTag(), r, false))
+                           r.getSbn().getId(), r.getSbn().getTag(), r, false, false))
                 .isFalse();
 
         // telecom manager is not ready - blocked
         mService.setTelecomManager(mTelecomManager);
         when(mTelecomManager.isInCall()).thenThrow(new IllegalStateException("not ready"));
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                r.getSbn().getId(), r.getSbn().getTag(), r, false))
+                r.getSbn().getId(), r.getSbn().getTag(), r, false, false))
                 .isFalse();
     }
 
@@ -10752,6 +10892,51 @@
     }
 
     @Test
+    public void testUngroupingAutoSummary_differentUsers() throws Exception {
+        NotificationRecord nr0 =
+                generateNotificationRecord(mTestNotificationChannel, 0, USER_SYSTEM);
+        NotificationRecord nr1 =
+                generateNotificationRecord(mTestNotificationChannel, 1, USER_SYSTEM);
+
+        // add notifications + summary for USER_SYSTEM
+        mService.addNotification(nr0);
+        mService.addNotification(nr1);
+        mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
+                nr1.getSbn().getPackageName(), nr1.getKey(), GroupHelper.BASE_FLAGS));
+
+        // add notifications + summary for USER_ALL
+        NotificationRecord nr0_all =
+                generateNotificationRecord(mTestNotificationChannel, 2, UserHandle.USER_ALL);
+        NotificationRecord nr1_all =
+                generateNotificationRecord(mTestNotificationChannel, 3, UserHandle.USER_ALL);
+
+        mService.addNotification(nr0_all);
+        mService.addNotification(nr1_all);
+        mService.addNotification(mService.createAutoGroupSummary(nr0_all.getUserId(),
+                nr0_all.getSbn().getPackageName(), nr0_all.getKey(), GroupHelper.BASE_FLAGS));
+
+        // cancel both children for USER_ALL
+        mBinderService.cancelNotificationWithTag(PKG, PKG, nr0_all.getSbn().getTag(),
+                nr0_all.getSbn().getId(), UserHandle.USER_ALL);
+        mBinderService.cancelNotificationWithTag(PKG, PKG, nr1_all.getSbn().getTag(),
+                nr1_all.getSbn().getId(), UserHandle.USER_ALL);
+        waitForIdle();
+
+        // group helper would send 'remove summary' event
+        mService.clearAutogroupSummaryLocked(UserHandle.USER_ALL,
+                nr0_all.getSbn().getPackageName());
+        waitForIdle();
+
+        // make sure the right summary was removed
+        assertThat(mService.getNotificationCount(nr0_all.getSbn().getPackageName(),
+                UserHandle.USER_ALL, 0, null)).isEqualTo(0);
+
+        // the USER_SYSTEM notifications + summary were not removed
+        assertThat(mService.getNotificationCount(nr0.getSbn().getPackageName(),
+                USER_SYSTEM, 0, null)).isEqualTo(3);
+    }
+
+    @Test
     public void testStrongAuthTracker_isInLockDownMode() {
         mStrongAuthTracker.setGetStrongAuthForUserReturnValue(
                 STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
@@ -11058,7 +11243,7 @@
 
         try {
             mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                    r.getSbn().getId(), r.getSbn().getTag(), r, false);
+                    r.getSbn().getId(), r.getSbn().getTag(), r, false, false);
             assertFalse("CallStyle should not be allowed without a valid use case", true);
         } catch (IllegalArgumentException error) {
             assertThat(error.getMessage()).contains("CallStyle");
@@ -11078,7 +11263,25 @@
         NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue();
+                r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
+    }
+
+    @Test
+    public void checkCallStyleNotification_allowedForByForegroundService() throws Exception {
+        Person person = new Person.Builder().setName("caller").build();
+        Notification n = new Notification.Builder(mContext, "test")
+                // Without FLAG_FOREGROUND_SERVICE.
+                //.setFlag(FLAG_FOREGROUND_SERVICE, true)
+                .setStyle(Notification.CallStyle.forOngoingCall(
+                        person, mock(PendingIntent.class)))
+                .build();
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
+                n, UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
+                r.getSbn().getId(), r.getSbn().getTag(), r, false,
+                true /* byForegroundService */)).isTrue();
     }
 
     @Test
@@ -11094,7 +11297,7 @@
         NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue();
+                r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
     }
 
     @Test
@@ -11110,7 +11313,7 @@
         NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue();
+                r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
     }
 
     @Test
@@ -11126,7 +11329,7 @@
         NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
-                r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue();
+                r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
     }
 
     @Test
@@ -11144,8 +11347,6 @@
                 ai.packageName)).thenReturn(AppOpsManager.MODE_IGNORED);
         // Given: a notification from an app on the system partition has the flag
         // FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .build();
@@ -11161,8 +11362,6 @@
     public void fixMediaNotification_withOnGoingFlag_shouldBeNonDismissible()
             throws Exception {
         // Given: a media notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .setStyle(new Notification.MediaStyle()
@@ -11191,8 +11390,6 @@
                 ai.packageName)).thenReturn(AppOpsManager.MODE_IGNORED);
         // Given: a notification from an app on the system partition has the flag
         // FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .build();
@@ -11208,9 +11405,6 @@
     public void fixCallNotification_withOnGoingFlag_shouldNotBeNonDismissible()
             throws Exception {
         // Given: a call notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
-
         Person person = new Person.Builder()
                 .setName("caller")
                 .build();
@@ -11231,8 +11425,6 @@
     @Test
     public void fixNonExemptNotification_withOnGoingFlag_shouldBeDismissible() throws Exception {
         // Given: a non-exempt notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .build();
@@ -11249,8 +11441,6 @@
             throws Exception {
         // Given: a non-exempt notification has the flag FLAG_NO_DISMISS set (even though this is
         // not allowed)
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .build();
         n.flags |= Notification.FLAG_NO_DISMISS;
@@ -11265,8 +11455,6 @@
     @Test
     public void fixMediaNotification_withoutOnGoingFlag_shouldBeDismissible() throws Exception {
         // Given: a media notification doesn't have the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(false)
                 .setStyle(new Notification.MediaStyle()
@@ -11285,8 +11473,6 @@
             throws Exception {
         // Given: a media notification doesn't have the flag FLAG_ONGOING_EVENT set,
         // but has the flag FLAG_NO_DISMISS set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(false)
                 .setStyle(new Notification.MediaStyle()
@@ -11305,8 +11491,6 @@
     public void fixNonExempt_Notification_withoutOnGoingFlag_shouldBeDismissible()
             throws Exception {
         // Given: a non-exempt notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(false)
                 .build();
@@ -11323,8 +11507,6 @@
             throws Exception {
         when(mDevicePolicyManager.isActiveDeviceOwner(mUid)).thenReturn(true);
         // Given: a notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         setDpmAppOppsExemptFromDismissal(false);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
@@ -11351,8 +11533,6 @@
                 AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, mUid,
                 PKG)).thenReturn(AppOpsManager.MODE_ALLOWED);
         // Given: a notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         setDpmAppOppsExemptFromDismissal(true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
@@ -11372,8 +11552,6 @@
                 AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, mUid,
                 PKG)).thenReturn(AppOpsManager.MODE_ALLOWED);
         // Given: a notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: ALLOW_DISMISS_ONGOING is on
-        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         setDpmAppOppsExemptFromDismissal(false);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
@@ -11967,6 +12145,306 @@
         assertFalse(n.isUserInitiatedJob());
     }
 
+    @Test
+    public void enqueue_updatesEnqueueRate() throws Exception {
+        Notification n = generateNotificationRecord(null).getNotification();
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId);
+        // Don't waitForIdle() here. We want to verify the "intermediate" state.
+
+        verify(mUsageStats).registerEnqueuedByApp(eq(PKG));
+        verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG));
+        verify(mUsageStats, never()).registerPostedByApp(any());
+
+        waitForIdle();
+    }
+
+    @Test
+    public void enqueue_withPost_updatesEnqueueRateAndPost() throws Exception {
+        Notification n = generateNotificationRecord(null).getNotification();
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId);
+        waitForIdle();
+
+        verify(mUsageStats).registerEnqueuedByApp(eq(PKG));
+        verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG));
+        verify(mUsageStats).registerPostedByApp(any());
+    }
+
+    @Test
+    public void enqueueNew_whenOverEnqueueRate_accepts() throws Exception {
+        Notification n = generateNotificationRecord(null).getNotification();
+        when(mUsageStats.getAppEnqueueRate(eq(PKG)))
+                .thenReturn(DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE + 1f);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, mUserId);
+        waitForIdle();
+
+        assertThat(mService.mNotificationsByKey).hasSize(1);
+        verify(mUsageStats).registerEnqueuedByApp(eq(PKG));
+        verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG));
+        verify(mUsageStats).registerPostedByApp(any());
+    }
+
+    @Test
+    public void enqueueUpdate_whenBelowMaxEnqueueRate_accepts() throws Exception {
+        // Post the first version.
+        Notification original = generateNotificationRecord(null).getNotification();
+        original.when = 111;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, original, mUserId);
+        waitForIdle();
+        assertThat(mService.mNotificationList).hasSize(1);
+        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111);
+
+        reset(mUsageStats);
+        when(mUsageStats.getAppEnqueueRate(eq(PKG)))
+                .thenReturn(DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE - 1f);
+
+        // Post the update.
+        Notification update = generateNotificationRecord(null).getNotification();
+        update.when = 222;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, update, mUserId);
+        waitForIdle();
+
+        verify(mUsageStats).registerEnqueuedByApp(eq(PKG));
+        verify(mUsageStats).registerEnqueuedByAppAndAccepted(eq(PKG));
+        verify(mUsageStats, never()).registerPostedByApp(any());
+        verify(mUsageStats).registerUpdatedByApp(any(), any());
+        assertThat(mService.mNotificationList).hasSize(1);
+        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(222);
+    }
+
+    @Test
+    public void enqueueUpdate_whenAboveMaxEnqueueRate_rejects() throws Exception {
+        // Post the first version.
+        Notification original = generateNotificationRecord(null).getNotification();
+        original.when = 111;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, original, mUserId);
+        waitForIdle();
+        assertThat(mService.mNotificationList).hasSize(1);
+        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111);
+
+        reset(mUsageStats);
+        when(mUsageStats.getAppEnqueueRate(eq(PKG)))
+                .thenReturn(DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE + 1f);
+
+        // Post the update.
+        Notification update = generateNotificationRecord(null).getNotification();
+        update.when = 222;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, update, mUserId);
+        waitForIdle();
+
+        verify(mUsageStats).registerEnqueuedByApp(eq(PKG));
+        verify(mUsageStats, never()).registerEnqueuedByAppAndAccepted(any());
+        verify(mUsageStats, never()).registerPostedByApp(any());
+        verify(mUsageStats, never()).registerUpdatedByApp(any(), any());
+        assertThat(mService.mNotificationList).hasSize(1);
+        assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old
+    }
+
+    @Test
+    public void enqueueNotification_allowlistsPendingIntents() throws RemoteException {
+        PendingIntent contentIntent = createPendingIntent("content");
+        PendingIntent actionIntent1 = createPendingIntent("action1");
+        PendingIntent actionIntent2 = createPendingIntent("action2");
+        Notification n = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+                .setContentIntent(contentIntent)
+                .addAction(new Notification.Action.Builder(null, "action1", actionIntent1).build())
+                .addAction(new Notification.Action.Builder(null, "action2", actionIntent2).build())
+                .build();
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 1,
+                parcelAndUnparcel(n, Notification.CREATOR), mUserId);
+
+        verify(mAmi, times(3)).setPendingIntentAllowlistDuration(
+                any(), any(), anyLong(),
+                eq(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED),
+                eq(REASON_NOTIFICATION_SERVICE), any());
+        verify(mAmi, times(3)).setPendingIntentAllowBgActivityStarts(any(),
+                any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+    }
+
+    @Test
+    public void enqueueNotification_allowlistsPendingIntents_includingFromPublicVersion()
+            throws RemoteException {
+        PendingIntent contentIntent = createPendingIntent("content");
+        PendingIntent actionIntent = createPendingIntent("action");
+        PendingIntent publicContentIntent = createPendingIntent("publicContent");
+        PendingIntent publicActionIntent = createPendingIntent("publicAction");
+        Notification source = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+                .setContentIntent(contentIntent)
+                .addAction(new Notification.Action.Builder(null, "action", actionIntent).build())
+                .setPublicVersion(new Notification.Builder(mContext, "channel")
+                        .setContentIntent(publicContentIntent)
+                        .addAction(new Notification.Action.Builder(
+                                null, "publicAction", publicActionIntent).build())
+                        .build())
+                .build();
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 1,
+                parcelAndUnparcel(source, Notification.CREATOR), mUserId);
+
+        verify(mAmi, times(4)).setPendingIntentAllowlistDuration(
+                any(), any(), anyLong(),
+                eq(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED),
+                eq(REASON_NOTIFICATION_SERVICE), any());
+        verify(mAmi, times(4)).setPendingIntentAllowBgActivityStarts(any(),
+                any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+    }
+
+    @Test
+    public void onUserSwitched_updatesZenModeAndChannelsBypassingDnd() {
+        Intent intent = new Intent(Intent.ACTION_USER_SWITCHED);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, 20);
+        mService.mZenModeHelper = mock(ZenModeHelper.class);
+        mService.setPreferencesHelper(mPreferencesHelper);
+
+        mUserSwitchIntentReceiver.onReceive(mContext, intent);
+
+        InOrder inOrder = inOrder(mPreferencesHelper, mService.mZenModeHelper);
+        inOrder.verify(mService.mZenModeHelper).onUserSwitched(eq(20));
+        inOrder.verify(mPreferencesHelper).syncChannelsBypassingDnd();
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void isNotificationPolicyAccessGranted_invalidPackage() throws Exception {
+        final String notReal = "NOT REAL";
+        final var checker = mService.permissionChecker;
+
+        when(mPackageManagerClient.getPackageUidAsUser(eq(notReal), anyInt())).thenThrow(
+                PackageManager.NameNotFoundException.class);
+
+        assertThat(mBinderService.isNotificationPolicyAccessGranted(notReal)).isFalse();
+        verify(mPackageManagerClient).getPackageUidAsUser(eq(notReal), anyInt());
+        verify(checker, never()).check(any(), anyInt(), anyInt(), anyBoolean());
+        verify(mConditionProviders, never()).isPackageOrComponentAllowed(eq(notReal), anyInt());
+        verify(mListeners, never()).isComponentEnabledForPackage(any());
+        verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt());
+    }
+
+    @Test
+    public void isNotificationPolicyAccessGranted_hasPermission() throws Exception {
+        final String packageName = "target";
+        final int uid = 123;
+        final var checker = mService.permissionChecker;
+
+        when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+        when(checker.check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue();
+        verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+        verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+        verify(mConditionProviders, never()).isPackageOrComponentAllowed(eq(packageName), anyInt());
+        verify(mListeners, never()).isComponentEnabledForPackage(any());
+        verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt());
+    }
+
+    @Test
+    public void isNotificationPolicyAccessGranted_isPackageAllowed() throws Exception {
+        final String packageName = "target";
+        final int uid = 123;
+        final var checker = mService.permissionChecker;
+
+        when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+        when(mConditionProviders.isPackageOrComponentAllowed(eq(packageName), anyInt()))
+                .thenReturn(true);
+
+        assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue();
+        verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+        verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+        verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt());
+        verify(mListeners, never()).isComponentEnabledForPackage(any());
+        verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt());
+    }
+
+    @Test
+    public void isNotificationPolicyAccessGranted_isComponentEnabled() throws Exception {
+        final String packageName = "target";
+        final int uid = 123;
+        final var checker = mService.permissionChecker;
+
+        when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+        when(mListeners.isComponentEnabledForPackage(packageName)).thenReturn(true);
+
+        assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue();
+        verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+        verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+        verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt());
+        verify(mListeners).isComponentEnabledForPackage(packageName);
+        verify(mDevicePolicyManager, never()).isActiveDeviceOwner(anyInt());
+    }
+
+    @Test
+    public void isNotificationPolicyAccessGranted_isDeviceOwner() throws Exception {
+        final String packageName = "target";
+        final int uid = 123;
+        final var checker = mService.permissionChecker;
+
+        when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+        when(mDevicePolicyManager.isActiveDeviceOwner(uid)).thenReturn(true);
+
+        assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isTrue();
+        verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+        verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+        verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt());
+        verify(mListeners).isComponentEnabledForPackage(packageName);
+        verify(mDevicePolicyManager).isActiveDeviceOwner(uid);
+    }
+
+    /**
+     * b/292163859
+     */
+    @Test
+    public void isNotificationPolicyAccessGranted_callerIsDeviceOwner() throws Exception {
+        final String packageName = "target";
+        final int uid = 123;
+        final int callingUid = Binder.getCallingUid();
+        final var checker = mService.permissionChecker;
+
+        when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+        when(mDevicePolicyManager.isActiveDeviceOwner(callingUid)).thenReturn(true);
+
+        assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isFalse();
+        verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+        verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+        verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt());
+        verify(mListeners).isComponentEnabledForPackage(packageName);
+        verify(mDevicePolicyManager).isActiveDeviceOwner(uid);
+        verify(mDevicePolicyManager, never()).isActiveDeviceOwner(callingUid);
+    }
+
+    @Test
+    public void isNotificationPolicyAccessGranted_notGranted() throws Exception {
+        final String packageName = "target";
+        final int uid = 123;
+        final var checker = mService.permissionChecker;
+
+        when(mPackageManagerClient.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
+
+        assertThat(mBinderService.isNotificationPolicyAccessGranted(packageName)).isFalse();
+        verify(mPackageManagerClient).getPackageUidAsUser(eq(packageName), anyInt());
+        verify(checker).check(android.Manifest.permission.MANAGE_NOTIFICATIONS, uid, -1, true);
+        verify(mConditionProviders).isPackageOrComponentAllowed(eq(packageName), anyInt());
+        verify(mListeners).isComponentEnabledForPackage(packageName);
+        verify(mDevicePolicyManager).isActiveDeviceOwner(uid);
+    }
+
+    private static <T extends Parcelable> T parcelAndUnparcel(T source,
+            Parcelable.Creator<T> creator) {
+        Parcel parcel = Parcel.obtain();
+        source.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        return creator.createFromParcel(parcel);
+    }
+
+    private PendingIntent createPendingIntent(String action) {
+        return PendingIntent.getActivity(mContext, 0,
+                new Intent(action).setPackage(mContext.getPackageName()),
+                PendingIntent.FLAG_MUTABLE);
+    }
+
     private void setDpmAppOppsExemptFromDismissal(boolean isOn) {
         DeviceConfig.setProperty(
                 DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
index 40f34a6..b522cab 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
@@ -18,7 +18,6 @@
 
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
 import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
@@ -27,6 +26,8 @@
 import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_CLICK;
 import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED;
 import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_OTHER;
+import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
+import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -42,6 +43,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.UiServiceTestCase;
+import com.android.internal.util.FrameworkStatsLog;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -166,6 +168,56 @@
     }
 
     @Test
+    public void testGetFsiState_stickyHunFlagDisabled_zero() {
+        final int fsiState = NotificationRecordLogger.getFsiState(
+                /* isStickyHunFlagEnabled= */ false,
+                /* hasFullScreenIntent= */ true,
+                /* hasFsiRequestedButDeniedFlag= */ true,
+                /* eventType= */ NOTIFICATION_POSTED);
+        assertEquals(0, fsiState);
+    }
+
+    @Test
+    public void testGetFsiState_isUpdate_zero() {
+        final int fsiState = NotificationRecordLogger.getFsiState(
+                /* isStickyHunFlagEnabled= */ true,
+                /* hasFullScreenIntent= */ true,
+                /* hasFsiRequestedButDeniedFlag= */ true,
+                /* eventType= */ NOTIFICATION_UPDATED);
+        assertEquals(0, fsiState);
+    }
+
+    @Test
+    public void testGetFsiState_hasFsi_allowedEnum() {
+        final int fsiState = NotificationRecordLogger.getFsiState(
+                /* isStickyHunFlagEnabled= */ true,
+                /* hasFullScreenIntent= */ true,
+                /* hasFsiRequestedButDeniedFlag= */ false,
+                /* eventType= */ NOTIFICATION_POSTED);
+        assertEquals(FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__FSI_ALLOWED, fsiState);
+    }
+
+    @Test
+    public void testGetFsiState_fsiPermissionDenied_deniedEnum() {
+        final int fsiState = NotificationRecordLogger.getFsiState(
+                /* isStickyHunFlagEnabled= */ true,
+                /* hasFullScreenIntent= */ false,
+                /* hasFsiRequestedButDeniedFlag= */ true,
+                /* eventType= */ NOTIFICATION_POSTED);
+        assertEquals(FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__FSI_DENIED, fsiState);
+    }
+
+    @Test
+    public void testGetFsiState_noFsi_noFsiEnum() {
+        final int fsiState = NotificationRecordLogger.getFsiState(
+                /* isStickyHunFlagEnabled= */ true,
+                /* hasFullScreenIntent= */ false,
+                /* hasFsiRequestedButDeniedFlag= */ false,
+                /* eventType= */ NOTIFICATION_POSTED);
+        assertEquals(FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__NO_FSI, fsiState);
+    }
+
+    @Test
     public void testBubbleGroupSummaryDismissal() {
         assertEquals(NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED,
                 NotificationRecordLogger.NotificationCancelledEvent.fromCancelReason(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index fae92d9..f83a1df 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -36,7 +36,7 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -44,7 +44,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
-import android.app.IActivityManager;
 import android.app.Notification;
 import android.app.Notification.Builder;
 import android.app.NotificationChannel;
@@ -76,6 +75,7 @@
 
 import com.android.internal.R;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.server.LocalServices;
 import com.android.server.UiServiceTestCase;
 import com.android.server.uri.UriGrantsManagerInternal;
 
@@ -850,84 +850,78 @@
 
     @Test
     public void testCalculateGrantableUris_PappProvided() {
-        IActivityManager am = mock(IActivityManager.class);
         UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
         when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
                 anyInt(), anyInt())).thenThrow(new SecurityException());
 
+        LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+        LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
         channel.setSound(null, null);
         Notification n = new Notification.Builder(mContext, channel.getId())
                 .setSmallIcon(Icon.createWithContentUri(Uri.parse("content://something")))
                 .build();
         StatusBarNotification sbn =
                 new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid);
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
-        record.mAm = am;
-        record.mUgmInternal = ugm;
 
-        try {
-            record.calculateGrantableUris();
-            fail("App provided uri for p targeting app should throw exception");
-        } catch (SecurityException e) {
-            // expected
-        }
+        assertThrows("App provided uri for p targeting app should throw exception",
+                SecurityException.class,
+                () -> new NotificationRecord(mMockContext, sbn, channel));
     }
 
     @Test
     public void testCalculateGrantableUris_PappProvided_invalidSound() {
-        IActivityManager am = mock(IActivityManager.class);
         UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
         when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
                 anyInt(), anyInt())).thenThrow(new SecurityException());
 
+        LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+        LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
         channel.setSound(Uri.parse("content://something"), mock(AudioAttributes.class));
 
         Notification n = mock(Notification.class);
         when(n.getChannelId()).thenReturn(channel.getId());
         StatusBarNotification sbn =
                 new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid);
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
-        record.mAm = am;
-        record.mUgmInternal = ugm;
 
-        record.calculateGrantableUris();
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
         assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, record.getSound());
     }
 
     @Test
     public void testCalculateGrantableUris_PuserOverridden() {
-        IActivityManager am = mock(IActivityManager.class);
         UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
         when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
                 anyInt(), anyInt())).thenThrow(new SecurityException());
 
+        LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+        LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
         channel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
         Notification n = mock(Notification.class);
         when(n.getChannelId()).thenReturn(channel.getId());
         StatusBarNotification sbn =
                 new StatusBarNotification(PKG_P, PKG_P, id1, tag1, uid, uid, n, mUser, null, uid);
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
-        record.mAm = am;
 
-        record.calculateGrantableUris();
+        new NotificationRecord(mMockContext, sbn, channel); // should not throw
     }
 
     @Test
     public void testCalculateGrantableUris_prePappProvided() {
-        IActivityManager am = mock(IActivityManager.class);
         UriGrantsManagerInternal ugm = mock(UriGrantsManagerInternal.class);
         when(ugm.checkGrantUriPermission(anyInt(), eq(null), any(Uri.class),
                 anyInt(), anyInt())).thenThrow(new SecurityException());
 
+        LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
+        LocalServices.addService(UriGrantsManagerInternal.class, ugm);
+
         Notification n = mock(Notification.class);
         when(n.getChannelId()).thenReturn(channel.getId());
         StatusBarNotification sbn =
                 new StatusBarNotification(PKG_O, PKG_O, id1, tag1, uid, uid, n, mUser, null, uid);
-        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
-        record.mAm = am;
 
-        record.calculateGrantableUris();
-        // should not throw
+        new NotificationRecord(mMockContext, sbn, channel); // should not throw
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index 024c38e..121e296 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -34,6 +34,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.RemoteViews;
 
@@ -96,7 +97,8 @@
     // Types that we can't really produce. No methods receiving these parameters will be invoked.
     private static final ImmutableSet<Class<?>> UNUSABLE_TYPES =
             ImmutableSet.of(Consumer.class, IBinder.class, MediaSession.Token.class, Parcel.class,
-                    PrintWriter.class, Resources.Theme.class, View.class);
+                    PrintWriter.class, Resources.Theme.class, View.class,
+                    LayoutInflater.Factory2.class);
 
     // Maximum number of times we allow generating the same class recursively.
     // E.g. new RemoteViews.addView(new RemoteViews()) but stop there.
@@ -116,11 +118,19 @@
             PREFERRED_CONSTRUCTORS = ImmutableMap.of(
                     Notification.Builder.class,
                     Notification.Builder.class.getConstructor(Context.class, String.class));
+
+            EXCLUDED_SETTERS_OVERLOADS = ImmutableMultimap.<Class<?>, Method>builder()
+                    .put(RemoteViews.class,
+                            // b/245950570: Tries to connect to service and will crash.
+                            RemoteViews.class.getMethod("setRemoteAdapter",
+                                    int.class, Intent.class))
+                    .build();
         } catch (NoSuchMethodException e) {
             throw new RuntimeException(e);
         }
     }
 
+    // Setters that shouldn't be called, for various reasons (but NOT because they are KNOWN_BAD).
     private static final Multimap<Class<?>, String> EXCLUDED_SETTERS =
             ImmutableMultimap.<Class<?>, String>builder()
                     // Handled by testAllStyles().
@@ -135,6 +145,9 @@
                     .put(RemoteViews.class, "mergeRemoteViews")
                     .build();
 
+    // Same as above, but specific overloads that should not be called.
+    private static final Multimap<Class<?>, Method> EXCLUDED_SETTERS_OVERLOADS;
+
     private Context mContext;
 
     @Rule
@@ -147,8 +160,10 @@
 
     @Test // This is a meta-test, checks that the generators are not broken.
     public void verifyTest() {
-        Generated<Notification> notification = buildNotification(mContext, /* styleClass= */ null,
-                /* extenderClass= */ null, /* actionExtenderClass= */ null,
+        Generated<Notification> notification = buildNotification(mContext,
+                /* styleClass= */ Notification.MessagingStyle.class,
+                /* extenderClass= */ Notification.WearableExtender.class,
+                /* actionExtenderClass= */ Notification.Action.WearableExtender.class,
                 /* includeRemoteViews= */ true);
         assertThat(notification.includedUris.size()).isAtLeast(900);
     }
@@ -504,6 +519,7 @@
                         || method.getReturnType().equals(clazz))
                         && method.getParameterCount() >= 1
                         && !EXCLUDED_SETTERS.containsEntry(clazz, method.getName())
+                        && !EXCLUDED_SETTERS_OVERLOADS.containsEntry(clazz, method)
                         && Arrays.stream(method.getParameterTypes())
                             .noneMatch(excludingParameterTypes::contains)) {
                     methods.put(method.getName(), method);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 48ad86d..81d939f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -18,8 +18,19 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_DEFAULT;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
+import static android.app.Notification.VISIBILITY_PRIVATE;
+import static android.app.Notification.VISIBILITY_SECRET;
+import static android.app.NotificationChannel.ALLOW_BUBBLE_ON;
 import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
+import static android.app.NotificationChannel.DEFAULT_ALLOW_BUBBLE;
+import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
 import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
+import static android.app.NotificationChannel.USER_LOCKED_LIGHTS;
+import static android.app.NotificationChannel.USER_LOCKED_PRIORITY;
+import static android.app.NotificationChannel.USER_LOCKED_SHOW_BADGE;
+import static android.app.NotificationChannel.USER_LOCKED_SOUND;
+import static android.app.NotificationChannel.USER_LOCKED_VIBRATION;
+import static android.app.NotificationChannel.USER_LOCKED_VISIBILITY;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
@@ -29,13 +40,18 @@
 import static android.app.NotificationManager.IMPORTANCE_MAX;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
 import static android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION;
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
 
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_ID_FIELD_NUMBER;
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_NAME_FIELD_NUMBER;
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IMPORTANCE_FIELD_NUMBER;
@@ -44,6 +60,7 @@
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_DEMOTED_CONVERSATION_FIELD_NUMBER;
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IS_IMPORTANT_CONVERSATION_FIELD_NUMBER;
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.UID_FIELD_NUMBER;
+import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
 import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
 import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;
 import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT;
@@ -102,6 +119,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.permission.PermissionManager;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
@@ -121,6 +139,8 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+import com.android.internal.config.sysui.TestableFlagResolver;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.os.AtomsProto.PackageNotificationPreferences;
@@ -131,6 +151,7 @@
 
 import org.json.JSONArray;
 import org.json.JSONObject;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -178,6 +199,9 @@
     private static final Uri FILE_SOUND_URI =
             Uri.parse("file://" + TEST_AUTHORITY + "/product/media/test.ogg");
 
+    private static final Uri DEFAULT_SOUND_URI = Uri.parse(
+            "content://settings/system/notification_sound");
+
     @Mock PermissionHelper mPermissionHelper;
     @Mock RankingHandler mHandler;
     @Mock PackageManager mPm;
@@ -185,6 +209,7 @@
     @Mock Context mContext;
     @Mock ZenModeHelper mMockZenModeHelper;
     @Mock AppOpsManager mAppOpsManager;
+    @Mock PermissionManager mPermissionManager;
 
     private NotificationManager.Policy mTestNotificationPolicy;
 
@@ -218,6 +243,8 @@
                 InstrumentationRegistry.getContext().getResources());
         when(mContext.getContentResolver()).thenReturn(
                 InstrumentationRegistry.getContext().getContentResolver());
+        when(mPm.getPermissionFlags(any(), any(), any()))
+                .thenReturn(PackageManager.FLAG_PERMISSION_USER_SET);
         when(mContext.getPackageManager()).thenReturn(mPm);
         when(mContext.getApplicationInfo()).thenReturn(legacy);
         // most tests assume badging is enabled
@@ -303,7 +330,8 @@
         mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory();
 
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager,
+                mStatsEventBuilderFactory, false);
         resetZenModeHelper();
 
         mAudioAttributes = new AudioAttributes.Builder()
@@ -318,6 +346,11 @@
                 NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN);
     }
 
+    @After
+    public void tearDown() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
+    }
+
     private ByteArrayOutputStream writeXmlAndPurge(
             String pkg, int uid, boolean forBackup, int userId, String... channelIds)
             throws Exception {
@@ -502,7 +535,7 @@
         channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         channel2.enableLights(true);
         channel2.setBypassDnd(true);
-        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel2.setLockscreenVisibility(VISIBILITY_SECRET);
         channel2.enableVibration(true);
         channel2.setGroup(ncg.getId());
         channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
@@ -567,10 +600,10 @@
         NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
         channel2.setDescription("descriptions for all");
-        channel2.setSound(SOUND_URI, mAudioAttributes);
+        channel2.setSound(CANONICAL_SOUND_URI, mAudioAttributes);
         channel2.enableLights(true);
         channel2.setBypassDnd(true);
-        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel2.setLockscreenVisibility(VISIBILITY_SECRET);
         channel2.enableVibration(false);
         channel2.setGroup(ncg.getId());
         channel2.setLightColor(Color.BLUE);
@@ -645,7 +678,7 @@
     @Test
     public void testReadXml_oldXml_migrates() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
 
         String xml = "<ranking version=\"2\">\n"
                 + "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1
@@ -711,7 +744,7 @@
     @Test
     public void testReadXml_oldXml_backup_migratesWhenPkgInstalled() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         when(mPm.getPackageUidAsUser("pkg1", USER_SYSTEM)).thenReturn(UNKNOWN_UID);
         when(mPm.getPackageUidAsUser("pkg2", USER_SYSTEM)).thenReturn(UNKNOWN_UID);
@@ -789,7 +822,7 @@
     @Test
     public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
 
         String xml = "<ranking version=\"3\">\n"
                 + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -846,7 +879,7 @@
     @Test
     public void testReadXml_newXml_permissionNotificationOff() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         String xml = "<ranking version=\"3\">\n"
                 + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -903,7 +936,7 @@
     @Test
     public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
 
         String xml = "<ranking version=\"4\">\n"
                 + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -959,7 +992,7 @@
     @Test
     public void testReadXml_oldXml_migration_NoUid() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID);
         String xml = "<ranking version=\"2\">\n"
@@ -992,7 +1025,7 @@
     @Test
     public void testReadXml_newXml_noMigration_NoUid() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID);
         String xml = "<ranking version=\"3\">\n"
@@ -1024,7 +1057,7 @@
     @Test
     public void testChannelXmlForNonBackup_postMigration() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
         appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false));
@@ -1047,7 +1080,7 @@
         channel2.setSound(null, null);
         channel2.enableLights(true);
         channel2.setBypassDnd(true);
-        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel2.setLockscreenVisibility(VISIBILITY_SECRET);
         channel2.enableVibration(false);
         channel2.setGroup(ncg.getId());
         channel2.setLightColor(Color.BLUE);
@@ -1110,7 +1143,7 @@
     @Test
     public void testChannelXmlForBackup_postMigration() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
         appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false));
@@ -1133,7 +1166,7 @@
         channel2.setSound(null, null);
         channel2.enableLights(true);
         channel2.setBypassDnd(true);
-        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel2.setLockscreenVisibility(VISIBILITY_SECRET);
         channel2.enableVibration(false);
         channel2.setGroup(ncg.getId());
         channel2.setLightColor(Color.BLUE);
@@ -1202,7 +1235,7 @@
     @Test
     public void testChannelXmlForBackup_postMigration_noExternal() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
         appPermissions.put(new Pair<>(UID_P, PKG_P), new Pair<>(true, false));
@@ -1221,7 +1254,7 @@
         channel2.setSound(null, null);
         channel2.enableLights(true);
         channel2.setBypassDnd(true);
-        channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel2.setLockscreenVisibility(VISIBILITY_SECRET);
         channel2.enableVibration(false);
         channel2.setGroup(ncg.getId());
         channel2.setLightColor(Color.BLUE);
@@ -1287,7 +1320,7 @@
     @Test
     public void testChannelXmlForBackup_postMigration_noLocalSettings() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
         appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false));
@@ -1341,6 +1374,8 @@
                 .when(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
         doReturn(localUri)
                 .when(mTestIContentProvider).uncanonicalize(any(), eq(canonicalBasedOnLocal));
+        doReturn(canonicalBasedOnLocal)
+                .when(mTestIContentProvider).canonicalize(any(), eq(localUri));
 
         NotificationChannel channel =
                 new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -1354,7 +1389,7 @@
 
         NotificationChannel actualChannel = mHelper.getNotificationChannel(
                 PKG_N_MR1, UID_N_MR1, channel.getId(), false);
-        assertEquals(localUri, actualChannel.getSound());
+        assertEquals(canonicalBasedOnLocal, actualChannel.getSound());
     }
 
     @Test
@@ -1498,7 +1533,7 @@
                 new FileNotFoundException("")).thenReturn(resId);
 
         mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+                mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
 
         NotificationChannel channel =
                 new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -1614,7 +1649,7 @@
                 NotificationChannel.DEFAULT_CHANNEL_ID, false);
         assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
         assertFalse(updated.canBypassDnd());
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
+        assertEquals(VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
         assertEquals(0, updated.getUserLockedFields());
     }
 
@@ -1642,9 +1677,9 @@
                 + "<package name=\"" + PKG_N_MR1
                 + "\" importance=\"" + NotificationManager.IMPORTANCE_HIGH
                 + "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
-                + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + UID_N_MR1 + "\" />\n"
+                + VISIBILITY_SECRET + "\"" + " uid=\"" + UID_N_MR1 + "\" />\n"
                 + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" visibility=\""
-                + Notification.VISIBILITY_PRIVATE + "\" />\n"
+                + VISIBILITY_PRIVATE + "\" />\n"
                 + "</ranking>";
         TypedXmlPullParser parser = Xml.newFastPullParser();
         parser.setInput(new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())),
@@ -1656,10 +1691,10 @@
             mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID, false);
         assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
         assertTrue(updated1.canBypassDnd());
-        assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
+        assertEquals(VISIBILITY_SECRET, updated1.getLockscreenVisibility());
         assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
-                | NotificationChannel.USER_LOCKED_PRIORITY
-                | NotificationChannel.USER_LOCKED_VISIBILITY,
+                | USER_LOCKED_PRIORITY
+                | USER_LOCKED_VISIBILITY,
                 updated1.getUserLockedFields());
 
         // No Default Channel created for updated packages
@@ -1804,7 +1839,7 @@
         channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         channel.enableLights(true);
         channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
 
         assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, false, false,
                 SYSTEM_UID, true));
@@ -1831,7 +1866,7 @@
     public void testUpdate_preUpgrade_updatesAppFields() throws Exception {
         assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
         assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+        assertEquals(VISIBILITY_NO_OVERRIDE,
                 mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1));
 
         NotificationChannel defaultChannel = mHelper.getNotificationChannel(
@@ -1840,7 +1875,7 @@
         defaultChannel.setShowBadge(false);
         defaultChannel.setImportance(IMPORTANCE_NONE);
         defaultChannel.setBypassDnd(true);
-        defaultChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        defaultChannel.setLockscreenVisibility(VISIBILITY_SECRET);
 
         mHelper.setAppImportanceLocked(PKG_N_MR1, UID_N_MR1);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, defaultChannel, true,
@@ -1849,7 +1884,7 @@
         // ensure app level fields are changed
         assertFalse(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
         assertEquals(Notification.PRIORITY_MAX, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
-        assertEquals(Notification.VISIBILITY_SECRET, mHelper.getPackageVisibility(PKG_N_MR1,
+        assertEquals(VISIBILITY_SECRET, mHelper.getPackageVisibility(PKG_N_MR1,
                 UID_N_MR1));
     }
 
@@ -1861,13 +1896,13 @@
                 SYSTEM_UID, true);
         assertTrue(mHelper.canShowBadge(PKG_O, UID_O));
         assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_O, UID_O));
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+        assertEquals(VISIBILITY_NO_OVERRIDE,
                 mHelper.getPackageVisibility(PKG_O, UID_O));
 
         channel.setShowBadge(false);
         channel.setImportance(IMPORTANCE_NONE);
         channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
 
         mHelper.updateNotificationChannel(PKG_O, UID_O, channel, true,
                 SYSTEM_UID, true);
@@ -1875,7 +1910,7 @@
         // ensure app level fields are not changed
         assertTrue(mHelper.canShowBadge(PKG_O, UID_O));
         assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_O, UID_O));
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+        assertEquals(VISIBILITY_NO_OVERRIDE,
                 mHelper.getPackageVisibility(PKG_O, UID_O));
     }
 
@@ -1887,13 +1922,13 @@
                 SYSTEM_UID, true);
         assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
         assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+        assertEquals(VISIBILITY_NO_OVERRIDE,
                 mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1));
 
         channel.setShowBadge(false);
         channel.setImportance(IMPORTANCE_NONE);
         channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
 
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true,
                 SYSTEM_UID, true);
@@ -1904,7 +1939,7 @@
         defaultChannel.setShowBadge(false);
         defaultChannel.setImportance(IMPORTANCE_NONE);
         defaultChannel.setBypassDnd(true);
-        defaultChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        defaultChannel.setLockscreenVisibility(VISIBILITY_SECRET);
 
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, defaultChannel, true,
                 SYSTEM_UID, true);
@@ -1912,7 +1947,7 @@
         // ensure app level fields are not changed
         assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
         assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG_N_MR1, UID_N_MR1));
-        assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+        assertEquals(VISIBILITY_NO_OVERRIDE,
                 mHelper.getPackageVisibility(PKG_N_MR1, UID_N_MR1));
     }
 
@@ -1928,7 +1963,7 @@
         channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         channel.enableLights(true);
         channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
         channel.setShowBadge(true);
         channel.setAllowBubbles(false);
         channel.setImportantConversation(true);
@@ -1947,7 +1982,7 @@
         assertEquals(channel.getName(), savedChannel.getName());
         assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
         assertFalse(savedChannel.canBypassDnd());
-        assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
+        assertFalse(VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
         assertFalse(channel.isImportantConversation());
         assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
         assertEquals(channel.canBubble(), savedChannel.canBubble());
@@ -1962,7 +1997,7 @@
         channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         channel.enableLights(true);
         channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
         channel.setShowBadge(true);
         channel.setAllowBubbles(false);
         int lockMask = 0;
@@ -1980,7 +2015,7 @@
         assertEquals(channel.getName(), savedChannel.getName());
         assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
         assertFalse(savedChannel.canBypassDnd());
-        assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
+        assertFalse(VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
         assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
         assertEquals(channel.canBubble(), savedChannel.canBubble());
     }
@@ -1991,7 +2026,7 @@
         mHelper.clearLockedFieldsLocked(channel);
         assertEquals(0, channel.getUserLockedFields());
 
-        channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY
+        channel.lockFields(USER_LOCKED_PRIORITY
                 | NotificationChannel.USER_LOCKED_IMPORTANCE);
         mHelper.clearLockedFieldsLocked(channel);
         assertEquals(0, channel.getUserLockedFields());
@@ -2005,19 +2040,19 @@
         final NotificationChannel update1 = getChannel();
         update1.setSound(new Uri.Builder().scheme("test").build(),
                 new AudioAttributes.Builder().build());
-        update1.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+        update1.lockFields(USER_LOCKED_PRIORITY);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update1, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
-                | NotificationChannel.USER_LOCKED_SOUND,
+        assertEquals(USER_LOCKED_PRIORITY
+                | USER_LOCKED_SOUND,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update1.getId(), false)
                         .getUserLockedFields());
 
         NotificationChannel update2 = getChannel();
         update2.enableVibration(true);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update2, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
-                        | NotificationChannel.USER_LOCKED_SOUND
-                        | NotificationChannel.USER_LOCKED_VIBRATION,
+        assertEquals(USER_LOCKED_PRIORITY
+                        | USER_LOCKED_SOUND
+                        | USER_LOCKED_VIBRATION,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update2.getId(), false)
                         .getUserLockedFields());
     }
@@ -2030,15 +2065,15 @@
         final NotificationChannel update1 = getChannel();
         update1.setVibrationPattern(new long[]{7945, 46 ,246});
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update1, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_VIBRATION,
+        assertEquals(USER_LOCKED_VIBRATION,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update1.getId(), false)
                         .getUserLockedFields());
 
         final NotificationChannel update2 = getChannel();
         update2.enableLights(true);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update2, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_VIBRATION
-                        | NotificationChannel.USER_LOCKED_LIGHTS,
+        assertEquals(USER_LOCKED_VIBRATION
+                        | USER_LOCKED_LIGHTS,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update2.getId(), false)
                         .getUserLockedFields());
     }
@@ -2051,7 +2086,7 @@
         final NotificationChannel update1 = getChannel();
         update1.setLightColor(Color.GREEN);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update1, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_LIGHTS,
+        assertEquals(USER_LOCKED_LIGHTS,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update1.getId(), false)
                         .getUserLockedFields());
 
@@ -2059,7 +2094,7 @@
         update2.setImportance(IMPORTANCE_DEFAULT);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update2, true,
                 SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_LIGHTS
+        assertEquals(USER_LOCKED_LIGHTS
                         | NotificationChannel.USER_LOCKED_IMPORTANCE,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update2.getId(), false)
                         .getUserLockedFields());
@@ -2076,24 +2111,24 @@
         final NotificationChannel update1 = getChannel();
         update1.setBypassDnd(true);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update1, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY,
+        assertEquals(USER_LOCKED_PRIORITY,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update1.getId(), false)
                         .getUserLockedFields());
 
         final NotificationChannel update2 = getChannel();
-        update2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        update2.setLockscreenVisibility(VISIBILITY_SECRET);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update2, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
-                        | NotificationChannel.USER_LOCKED_VISIBILITY,
+        assertEquals(USER_LOCKED_PRIORITY
+                        | USER_LOCKED_VISIBILITY,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update2.getId(), false)
                         .getUserLockedFields());
 
         final NotificationChannel update3 = getChannel();
         update3.setShowBadge(false);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update3, true, SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
-                        | NotificationChannel.USER_LOCKED_VISIBILITY
-                        | NotificationChannel.USER_LOCKED_SHOW_BADGE,
+        assertEquals(USER_LOCKED_PRIORITY
+                        | USER_LOCKED_VISIBILITY
+                        | USER_LOCKED_SHOW_BADGE,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update3.getId(), false)
                         .getUserLockedFields());
     }
@@ -2110,7 +2145,7 @@
         update.setAllowBubbles(true);
         mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, update, true,
                 SYSTEM_UID, true);
-        assertEquals(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE,
+        assertEquals(USER_LOCKED_ALLOW_BUBBLE,
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, update.getId(), false)
                         .getUserLockedFields());
     }
@@ -2146,7 +2181,7 @@
         channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         channel.enableLights(true);
         channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+        channel.setLockscreenVisibility(VISIBILITY_SECRET);
         channel.enableVibration(true);
         channel.setVibrationPattern(new long[]{100, 67, 145, 156});
 
@@ -2174,7 +2209,7 @@
         channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         channel.enableLights(true);
         channel.setBypassDnd(true);
-        channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
+        channel.setLockscreenVisibility(VISIBILITY_PRIVATE);
         channel.enableVibration(true);
         channel.setVibrationPattern(new long[]{100, 67, 145, 156});
         channelMap.put(channel.getId(), channel);
@@ -2443,9 +2478,9 @@
                 NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
-
+        mHelper.syncChannelsBypassingDnd();
 
         // create notification channel that can bypass dnd, but app is blocked
         // expected result: areChannelsBypassingDnd = false
@@ -2474,8 +2509,9 @@
                 NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
+        mHelper.syncChannelsBypassingDnd();
 
         // create notification channel that can bypass dnd, but app is blocked
         // expected result: areChannelsBypassingDnd = false
@@ -2498,8 +2534,9 @@
                 NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
+        mHelper.syncChannelsBypassingDnd();
 
         // create notification channel that can bypass dnd, but app is blocked
         // expected result: areChannelsBypassingDnd = false
@@ -2552,8 +2589,9 @@
                 NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
+        mHelper.syncChannelsBypassingDnd();
         assertFalse(mHelper.areChannelsBypassingDnd());
         verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
         resetZenModeHelper();
@@ -2565,7 +2603,7 @@
         mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0);
         when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         assertFalse(mHelper.areChannelsBypassingDnd());
         verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
@@ -3668,7 +3706,7 @@
                 + "</package>\n"
                 + "</ranking>\n";
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadByteArrayXml(preQXml.getBytes(), true, USER_SYSTEM);
 
@@ -3682,7 +3720,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
@@ -3752,7 +3790,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
@@ -3765,7 +3803,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
@@ -3779,7 +3817,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
@@ -3795,7 +3833,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
@@ -3851,7 +3889,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
@@ -3889,7 +3927,7 @@
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         loadStreamXml(baos, false, UserHandle.USER_ALL);
 
@@ -4547,7 +4585,7 @@
     @Test
     public void testPlaceholderConversationId_shortcutRequired() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         final String xml = "<ranking version=\"1\">\n"
@@ -4567,7 +4605,7 @@
     @Test
     public void testNormalConversationId_shortcutRequired() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         final String xml = "<ranking version=\"1\">\n"
@@ -4587,7 +4625,7 @@
     @Test
     public void testNoConversationId_shortcutRequired() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         final String xml = "<ranking version=\"1\">\n"
@@ -4607,7 +4645,7 @@
     @Test
     public void testDeleted_noTime() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         final String xml = "<ranking version=\"1\">\n"
@@ -4627,7 +4665,7 @@
     @Test
     public void testDeleted_twice() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         mHelper.createNotificationChannel(
@@ -4642,7 +4680,7 @@
     @Test
     public void testDeleted_recentTime() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         mHelper.createNotificationChannel(
@@ -4661,7 +4699,7 @@
                 null);
         parser.nextTag();
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
         mHelper.readXml(parser, true, USER_SYSTEM);
 
@@ -4673,7 +4711,7 @@
     @Test
     public void testUnDelete_time() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         mHelper.createNotificationChannel(
@@ -4695,7 +4733,7 @@
     @Test
     public void testDeleted_longTime() throws Exception {
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
-                mPermissionHelper, mLogger,
+                mPermissionHelper, mPermissionManager, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory, false);
 
         long time = System.currentTimeMillis() - (DateUtils.DAY_IN_MILLIS * 30);
@@ -5108,6 +5146,231 @@
     }
 
     @Test
+    public void testUpdateConversationParent_updatesConversations() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = new TestableFlagResolver()
+                .setFlagOverride(PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS, true);
+
+        NotificationChannel parent =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel convoA = new NotificationChannel("A", "With A", IMPORTANCE_DEFAULT);
+        convoA.setConversationId(parent.getId(), "A");
+        mHelper.createNotificationChannel(PKG_O, UID_O, convoA, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel convoB = new NotificationChannel("B", "With B", IMPORTANCE_DEFAULT);
+        convoB.setConversationId(parent.getId(), "B");
+        mHelper.createNotificationChannel(PKG_O, UID_O, convoB, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, "messages", /* includeDeleted= */
+                false).shouldVibrate()).isFalse();
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, "A",
+                /* includeDeleted= */ false).shouldVibrate()).isFalse();
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, "B",
+                /* includeDeleted= */ false).shouldVibrate()).isFalse();
+        mLogger.clear();
+
+        NotificationChannel parentUpdate = cloneChannel(parent);
+        parentUpdate.enableVibration(true);
+        mHelper.updateNotificationChannel(PKG_O, UID_O, parentUpdate, /* fromUser= */ true, UID_O,
+                /* fromSystemOrSystemUi= */ true);
+
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, "messages",
+                /* includeDeleted= */ false).shouldVibrate()).isTrue();
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, "A",
+                /* includeDeleted= */ false).shouldVibrate()).isTrue();
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, "B",
+                /* includeDeleted= */ false).shouldVibrate()).isTrue();
+
+        // Verify that the changes to parent and children were logged.
+        assertThat(mLogger.getCalls()).containsExactly(
+                        new NotificationChannelLoggerFake.CallRecord(
+                                NOTIFICATION_CHANNEL_UPDATED_BY_USER, "messages"),
+                        new NotificationChannelLoggerFake.CallRecord(
+                                NOTIFICATION_CHANNEL_UPDATED_BY_USER, "A"),
+                        new NotificationChannelLoggerFake.CallRecord(
+                                NOTIFICATION_CHANNEL_UPDATED_BY_USER, "B"))
+                .inOrder();
+    }
+
+    @Test
+    public void testUpdateConversationParent_updatesUnlockedFields() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = new TestableFlagResolver()
+                .setFlagOverride(PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS, true);
+
+        NotificationChannel parent =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel convo = new NotificationChannel("A", "With A", IMPORTANCE_DEFAULT);
+        convo.setConversationId(parent.getId(), "A");
+        mHelper.createNotificationChannel(PKG_O, UID_O, convo, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel originalChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                convo.getId(), /* includeDeleted= */ false);
+        assertThat(originalChild.canBypassDnd()).isFalse();
+        assertThat(originalChild.getLockscreenVisibility()).isEqualTo(VISIBILITY_NO_OVERRIDE);
+        assertThat(originalChild.getImportance()).isEqualTo(IMPORTANCE_DEFAULT);
+        assertThat(originalChild.shouldShowLights()).isFalse();
+        assertThat(originalChild.getSound()).isEqualTo(DEFAULT_SOUND_URI);
+        assertThat(originalChild.shouldVibrate()).isFalse();
+        assertThat(originalChild.canShowBadge()).isTrue();
+        assertThat(originalChild.getAllowBubbles()).isEqualTo(DEFAULT_ALLOW_BUBBLE);
+
+        NotificationChannel parentUpdate = cloneChannel(parent);
+        parentUpdate.setBypassDnd(true);
+        parentUpdate.setLockscreenVisibility(VISIBILITY_SECRET);
+        parentUpdate.setImportance(IMPORTANCE_HIGH);
+        parentUpdate.enableLights(true);
+        parentUpdate.setSound(SOUND_URI, mAudioAttributes);
+        parentUpdate.enableVibration(true);
+        parentUpdate.setShowBadge(false);
+        parentUpdate.setAllowBubbles(true);
+        mHelper.updateNotificationChannel(PKG_O, UID_O, parentUpdate, /* fromUser= */ true,
+                UID_O, /* fromSystemOrSystemUi= */ true);
+
+        NotificationChannel updatedChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                "A", /* includeDeleted= */ false);
+        assertThat(updatedChild.canBypassDnd()).isTrue();
+        assertThat(updatedChild.getLockscreenVisibility()).isEqualTo(VISIBILITY_SECRET);
+        assertThat(updatedChild.getImportance()).isEqualTo(IMPORTANCE_HIGH);
+        assertThat(updatedChild.shouldShowLights()).isTrue();
+        assertThat(updatedChild.getSound()).isEqualTo(SOUND_URI);
+        assertThat(updatedChild.shouldVibrate()).isTrue();
+        assertThat(updatedChild.canShowBadge()).isFalse();
+        assertThat(updatedChild.getAllowBubbles()).isEqualTo(ALLOW_BUBBLE_ON);
+    }
+
+    @Test
+    public void testUpdateConversationParent_doesNotUpdateLockedFields() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = new TestableFlagResolver()
+                .setFlagOverride(PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS, true);
+        NotificationChannel parent =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel convo = new NotificationChannel("A", "With A", IMPORTANCE_DEFAULT);
+        convo.setConversationId(parent.getId(), "A");
+        mHelper.createNotificationChannel(PKG_O, UID_O, convo, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        // Directly update the child to lock every field.
+        // Normally this would be the result of one or more "fromUser" updates with modified fields.
+        convo.lockFields(
+                USER_LOCKED_PRIORITY | USER_LOCKED_VISIBILITY | USER_LOCKED_IMPORTANCE
+                        | USER_LOCKED_LIGHTS | USER_LOCKED_VIBRATION | USER_LOCKED_SOUND
+                        | USER_LOCKED_SHOW_BADGE | USER_LOCKED_ALLOW_BUBBLE);
+        mLogger.clear();
+
+        NotificationChannel parentUpdate = cloneChannel(parent);
+        parentUpdate.setBypassDnd(true);
+        parentUpdate.setLockscreenVisibility(VISIBILITY_SECRET);
+        parentUpdate.setImportance(IMPORTANCE_HIGH);
+        parentUpdate.enableLights(true);
+        parentUpdate.setSound(SOUND_URI, mAudioAttributes);
+        parentUpdate.enableVibration(true);
+        parentUpdate.setShowBadge(false);
+        parentUpdate.setAllowBubbles(true);
+        mHelper.updateNotificationChannel(PKG_O, UID_O, parentUpdate, /* fromUser= */ true,
+                UID_O, /* fromSystemOrSystemUi= */ true);
+
+        NotificationChannel updatedChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                "A", /* includeDeleted= */ false);
+        assertThat(updatedChild.canBypassDnd()).isFalse();
+        assertThat(updatedChild.getLockscreenVisibility()).isEqualTo(VISIBILITY_NO_OVERRIDE);
+        assertThat(updatedChild.getImportance()).isEqualTo(IMPORTANCE_DEFAULT);
+        assertThat(updatedChild.shouldShowLights()).isFalse();
+        assertThat(updatedChild.getSound()).isEqualTo(DEFAULT_SOUND_URI);
+        assertThat(updatedChild.shouldVibrate()).isFalse();
+        assertThat(updatedChild.canShowBadge()).isTrue();
+        assertThat(updatedChild.getAllowBubbles()).isEqualTo(DEFAULT_ALLOW_BUBBLE);
+
+        // Verify that only the changes to the parent were logged.
+        assertThat(mLogger.getCalls()).containsExactly(
+                        new NotificationChannelLoggerFake.CallRecord(
+                                NOTIFICATION_CHANNEL_UPDATED_BY_USER, "messages"));
+    }
+
+    @Test
+    public void testUpdateConversationParent_updatesDemotedConversation() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = new TestableFlagResolver()
+                .setFlagOverride(PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS, true);
+        NotificationChannel parent =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel convo = new NotificationChannel("A", "With A", IMPORTANCE_DEFAULT);
+        convo.setConversationId(parent.getId(), "A");
+        convo.setDemoted(true);
+        mHelper.createNotificationChannel(PKG_O, UID_O, convo, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel originalChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                convo.getId(), /* includeDeleted= */ false);
+        assertThat(originalChild.shouldVibrate()).isFalse();
+
+        NotificationChannel parentUpdate = cloneChannel(parent);
+        parentUpdate.enableVibration(true);
+        mHelper.updateNotificationChannel(PKG_O, UID_O, parentUpdate, /* fromUser= */ true,
+                UID_O, /* fromSystemOrSystemUi= */ true);
+
+        NotificationChannel updatedChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                "A", /* includeDeleted= */ false);
+        assertThat(updatedChild.shouldVibrate()).isTrue();
+    }
+
+    @Test
+    public void testUpdateConversationParent_updatesDeletedConversation() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = new TestableFlagResolver()
+                .setFlagOverride(PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS, true);
+        NotificationChannel parent =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel convo = new NotificationChannel("A", "With A", IMPORTANCE_DEFAULT);
+        convo.setConversationId(parent.getId(), "A");
+        mHelper.createNotificationChannel(PKG_O, UID_O, convo, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        mHelper.deleteNotificationChannel(PKG_O, UID_O, "A", UID_O,
+                /* fromSystemOrSystemUi= */ false);
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, "A",
+                /* includeDeleted= */ false)).isNull();
+
+        NotificationChannel parentUpdate = cloneChannel(parent);
+        parentUpdate.enableVibration(true);
+        mHelper.updateNotificationChannel(PKG_O, UID_O, parentUpdate, /* fromUser= */ true,
+                UID_O, /* fromSystemOrSystemUi= */ true);
+
+        NotificationChannel updatedChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                "A", /* includeDeleted= */ true);
+        assertThat(updatedChild.shouldVibrate()).isTrue();
+    }
+
+    @Test
+    public void testUpdateConversationParent_flagOff_doesNotUpdateConversations() {
+        SystemUiSystemPropertiesFlags.TEST_RESOLVER = new TestableFlagResolver()
+                .setFlagOverride(PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS, false);
+        NotificationChannel parent =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel convo = new NotificationChannel("A", "With A", IMPORTANCE_DEFAULT);
+        convo.setConversationId(parent.getId(), "A");
+        mHelper.createNotificationChannel(PKG_O, UID_O, convo, /* fromTargetApp= */ true,
+                /* hasDndAccess= */ false, UID_O, /* fromSystemOrSystemUi= */ false);
+        NotificationChannel originalChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                convo.getId(), /* includeDeleted= */ false);
+        assertThat(originalChild.shouldVibrate()).isFalse();
+
+        NotificationChannel parentUpdate = cloneChannel(parent);
+        parentUpdate.enableVibration(true);
+        mHelper.updateNotificationChannel(PKG_O, UID_O, parentUpdate, /* fromUser= */ true,
+                UID_O, /* fromSystemOrSystemUi= */ true);
+
+        NotificationChannel untouchedChild = mHelper.getNotificationChannel(PKG_O, UID_O,
+                "A", /* includeDeleted= */ false);
+        assertThat(untouchedChild.shouldVibrate()).isFalse();
+    }
+
+    @Test
     public void testInvalidMessageSent() {
         // create package preferences
         mHelper.canShowBadge(PKG_P, UID_P);
@@ -5440,11 +5703,7 @@
         clearInvocations(mHandler);
         // Note: Creating a NotificationChannel identical to the original is not equals(), because
         // of mOriginalImportance. So we create a "true copy" instead.
-        Parcel parcel = Parcel.obtain();
-        original.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        NotificationChannel same = NotificationChannel.CREATOR.createFromParcel(parcel);
-        parcel.recycle();
+        NotificationChannel same = cloneChannel(original);
 
         mHelper.updateNotificationChannel(PKG_P, 0, same, false, 0, false);
 
@@ -5464,4 +5723,104 @@
 
         verifyZeroInteractions(mHandler);
     }
+
+    @Test
+    public void testGetFsiState_flagDisabled_zero() {
+        final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
+                /* requestedFsiPermission */ true, /* isFlagEnabled= */ false);
+
+        assertEquals(0, fsiState);
+    }
+
+    @Test
+    public void testGetFsiState_appDidNotRequest_enumNotRequested() {
+        final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
+                /* requestedFsiPermission */ false, /* isFlagEnabled= */ true);
+
+        assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED, fsiState);
+    }
+
+    @Test
+    public void testGetFsiState_permissionGranted_enumGranted() {
+        when(mPermissionManager.checkPermissionForPreflight(any(), any()))
+                .thenReturn(PermissionManager.PERMISSION_GRANTED);
+
+        final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
+                /* requestedFsiPermission= */ true, /* isFlagEnabled= */ true);
+
+        assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED, fsiState);
+    }
+
+    @Test
+    public void testGetFsiState_permissionSoftDenied_enumDenied() {
+        when(mPermissionManager.checkPermissionForPreflight(any(), any()))
+                .thenReturn(PermissionManager.PERMISSION_SOFT_DENIED);
+
+        final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
+                /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true);
+
+        assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState);
+    }
+
+    @Test
+    public void testGetFsiState_permissionHardDenied_enumDenied() {
+        when(mPermissionManager.checkPermissionForPreflight(any(), any()))
+                .thenReturn(PermissionManager.PERMISSION_HARD_DENIED);
+
+        final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
+                /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true);
+
+        assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState);
+    }
+
+    @Test
+    public void testIsFsiPermissionUserSet_appDidNotRequest_false() {
+        final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
+                /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED,
+                /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
+                /* isStickyHunFlagEnabled= */ true);
+
+        assertFalse(isUserSet);
+    }
+
+    @Test
+    public void testIsFsiPermissionUserSet_flagDisabled_false() {
+        final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
+                /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
+                /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
+                /* isStickyHunFlagEnabled= */ false);
+
+        assertFalse(isUserSet);
+    }
+
+    @Test
+    public void testIsFsiPermissionUserSet_userSet_true() {
+        final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
+                /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
+                /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
+                /* isStickyHunFlagEnabled= */ true);
+
+        assertTrue(isUserSet);
+    }
+
+    @Test
+    public void testIsFsiPermissionUserSet_notUserSet_false() {
+        final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
+                /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
+                /* currentPermissionFlags= */ ~PackageManager.FLAG_PERMISSION_USER_SET,
+                /* isStickyHunFlagEnabled= */ true);
+
+        assertFalse(isUserSet);
+    }
+
+    private static NotificationChannel cloneChannel(NotificationChannel original) {
+        Parcel parcel = Parcel.obtain();
+        try {
+            original.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            return NotificationChannel.CREATOR.createFromParcel(parcel);
+        } finally {
+            parcel.recycle();
+        }
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java
index 68aa1dc..131aaa0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RateEstimatorTest.java
@@ -15,8 +15,9 @@
  */
 package com.android.server.notification;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.util.concurrent.TimeUnit.HOURS;
 
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -42,110 +43,120 @@
 
     @Test
     public void testRunningTimeBackwardDoesntExplodeUpdate() throws Exception {
-        assertUpdateTime(mTestStartTime);
-        assertUpdateTime(mTestStartTime - 1000L);
+        updateAndVerifyRate(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime - 1000L);
     }
 
     @Test
     public void testRunningTimeBackwardDoesntExplodeGet() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         final float rate = mEstimator.getRate(mTestStartTime - 1000L);
-        assertFalse(Float.isInfinite(rate));
-        assertFalse(Float.isNaN(rate));
+        assertThat(rate).isFinite();
     }
 
     @Test
     public void testInstantaneousEventsDontExplodeUpdate() throws Exception {
-        assertUpdateTime(mTestStartTime);
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
     }
 
     @Test
     public void testInstantaneousEventsDontExplodeGet() throws Exception {
-        assertUpdateTime(mTestStartTime);
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         final float rate = mEstimator.getRate(mTestStartTime);
-        assertFalse(Float.isInfinite(rate));
-        assertFalse(Float.isNaN(rate));
+        assertThat(rate).isFinite();
     }
 
     @Test
     public void testInstantaneousBurstIsEstimatedUnderTwoPercent() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         long eventStart = mTestStartTime + 1000; // start event a long time after initialization
         long nextEventTime = postEvents(eventStart, 0, 5); // five events at \inf
         final float rate = mEstimator.getRate(nextEventTime);
-        assertLessThan("Rate", rate, 20f);
+        assertThat(rate).isLessThan(20f);
     }
 
     @Test
     public void testCompactBurstIsEstimatedUnderTwoPercent() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         long eventStart = mTestStartTime + 1000; // start event a long time after initialization
         long nextEventTime = postEvents(eventStart, 1, 5); // five events at 1000Hz
         final float rate = mEstimator.getRate(nextEventTime);
-        assertLessThan("Rate", rate, 20f);
+        assertThat(rate).isLessThan(20f);
     }
 
     @Test
     public void testSustained1000HzBurstIsEstimatedOverNinetyPercent() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         long eventStart = mTestStartTime + 1000; // start event a long time after initialization
         long nextEventTime = postEvents(eventStart, 1, 100); // one hundred events at 1000Hz
         final float rate = mEstimator.getRate(nextEventTime);
-        assertGreaterThan("Rate", rate, 900f);
+        assertThat(rate).isGreaterThan(900f);
     }
 
     @Test
     public void testSustained100HzBurstIsEstimatedOverNinetyPercent() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         long eventStart = mTestStartTime + 1000; // start event a long time after initialization
         long nextEventTime = postEvents(eventStart, 10, 100); // one hundred events at 100Hz
         final float rate = mEstimator.getRate(nextEventTime);
 
-        assertGreaterThan("Rate", rate, 90f);
+        assertThat(rate).isGreaterThan(90f);
     }
 
     @Test
     public void testRecoverQuicklyAfterSustainedBurst() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         long eventStart = mTestStartTime + 1000; // start event a long time after initialization
-        long nextEventTime = postEvents(eventStart, 10, 1000); // one hundred events at 100Hz
-        final float rate = mEstimator.getRate(nextEventTime + 5000L); // two seconds later
-        assertLessThan("Rate", rate, 2f);
+        long nextEventTime = postEvents(eventStart, 10, 1000); // one thousand events at 100Hz
+        final float rate = mEstimator.getRate(nextEventTime + 5000L); // five seconds later
+        assertThat(rate).isLessThan(2f);
     }
 
     @Test
     public void testEstimateShouldNotOvershoot() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         long eventStart = mTestStartTime + 1000; // start event a long time after initialization
-        long nextEventTime = postEvents(eventStart, 1, 1000); // one thousand events at 1000Hz
+        long nextEventTime = postEvents(eventStart, 1, 5000); // five thousand events at 1000Hz
         final float rate = mEstimator.getRate(nextEventTime);
-        assertLessThan("Rate", rate, 1000f);
+        assertThat(rate).isAtMost(1000f);
     }
 
     @Test
     public void testGetRateWithoutUpdate() throws Exception {
         final float rate = mEstimator.getRate(mTestStartTime);
-        assertLessThan("Rate", rate, 0.1f);
+        assertThat(rate).isLessThan(0.1f);
     }
 
     @Test
     public void testGetRateWithOneUpdate() throws Exception {
-        assertUpdateTime(mTestStartTime);
+        updateAndVerifyRate(mTestStartTime);
         final float rate = mEstimator.getRate(mTestStartTime+1);
-        assertLessThan("Rate", rate, 1f);
+        assertThat(rate).isLessThan(1f);
     }
 
-    private void assertLessThan(String label, float a, float b)  {
-        assertTrue(String.format("%s was %f, but should be less than %f", label, a, b), a <= b);
+    @Test
+    public void testEstimateCatchesUpQuickly() {
+        long nextEventTime = postEvents(mTestStartTime, 100, 30); // 30 events at 10Hz
+
+        final float firstBurstRate = mEstimator.getRate(nextEventTime);
+        assertThat(firstBurstRate).isWithin(2f).of(10);
+
+        nextEventTime += HOURS.toMillis(3); // 3 hours later...
+        nextEventTime = postEvents(nextEventTime, 100, 30); // same burst of 30 events at 10Hz
+
+        // Catching up. Rate is not yet 10, since we had a long period of inactivity...
+        float secondBurstRate = mEstimator.getRate(nextEventTime);
+        assertThat(secondBurstRate).isWithin(1f).of(6);
+
+        // ... but after a few more events, we are there.
+        nextEventTime = postEvents(nextEventTime, 100, 10); // 10 more events at 10Hz
+        secondBurstRate = mEstimator.getRate(nextEventTime);
+        assertThat(secondBurstRate).isWithin(1f).of(10);
     }
 
-    private void assertGreaterThan(String label, float a, float b)  {
-        assertTrue(String.format("%s was %f, but should be more than %f", label, a, b), a >= b);
-    }
-
-    /** @returns the next event time. */
+    /** @return the next event time. */
     private long postEvents(long start, long dt, int num) {
         long time = start;
         for (int i = 0; i < num; i++) {
@@ -155,9 +166,8 @@
         return time;
     }
 
-    private void assertUpdateTime(long time) {
-        final float rate = mEstimator.update(time);
-        assertFalse(Float.isInfinite(rate));
-        assertFalse(Float.isNaN(rate));
+    private void updateAndVerifyRate(long time) {
+        mEstimator.update(time);
+        assertThat(mEstimator.getRate(time)).isFinite();
     }
-}
+}
\ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 9f4eee7..27e8f36 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -43,6 +43,8 @@
     @Nullable
     Boolean mIsVisibleToListenerReturnValue = null;
 
+    ComponentPermissionChecker permissionChecker;
+
     TestableNotificationManagerService(Context context, NotificationRecordLogger logger,
             InstanceIdSequence notificationInstanceIdSequence) {
         super(context, logger, notificationInstanceIdSequence);
@@ -150,6 +152,12 @@
         return super.isVisibleToListener(sbn, notificationType, listener);
     }
 
+    @Override
+    protected int checkComponentPermission(String permission, int uid, int owningUid,
+            boolean exported) {
+        return permissionChecker.check(permission, uid, owningUid, exported);
+    }
+
     public class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker {
         private int mGetStrongAuthForUserReturnValue = 0;
         StrongAuthTrackerFake(Context context) {
@@ -165,4 +173,8 @@
             return mGetStrongAuthForUserReturnValue;
         }
     }
+
+    public interface ComponentPermissionChecker {
+        int check(String permission, int uid, int owningUid, boolean exported);
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index dedb8f1..3ee75de 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -771,7 +771,7 @@
         mZenModeHelper.mConfig = null; // will evaluate config to zen mode off
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
-            mZenModeHelper.evaluateZenMode("test", true);
+            mZenModeHelper.evaluateZenModeLocked("test", true);
         }
         verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
                 mZenModeHelper.TAG);
@@ -798,7 +798,7 @@
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
-            mZenModeHelper.evaluateZenMode("test", true);
+            mZenModeHelper.evaluateZenModeLocked("test", true);
         }
         verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
                 mZenModeHelper.TAG);
@@ -825,7 +825,7 @@
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         for (int i = 0; i < 3; i++) {
             // if zen doesn't change, zen should not reapply itself to the ringer
-            mZenModeHelper.evaluateZenMode("test", true);
+            mZenModeHelper.evaluateZenModeLocked("test", true);
         }
         verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
                 mZenModeHelper.TAG);
@@ -2269,7 +2269,7 @@
         // Artificially turn zen mode "on". Re-evaluating zen mode should cause it to turn back off
         // given that we don't have any zen rules active.
         mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-        mZenModeHelper.evaluateZenMode("test", true);
+        mZenModeHelper.evaluateZenModeLocked("test", true);
 
         // Check that the change actually took: zen mode should be off now
         assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode);
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index e5371975..6aa5989 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -51,6 +51,7 @@
         "services.core",
         "androidx.test.runner",
         "androidx.test.rules",
+        "junit-params",
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "servicestests-utils",
@@ -82,7 +83,10 @@
     ],
 
     platform_apis: true,
-    test_suites: ["device-tests"],
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
 
     certificate: "platform",
 
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 37e5da5..9c79375 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -96,6 +96,19 @@
             android:theme="@style/WhiteBackgroundTheme"
             android:exported="true"/>
 
+        <activity android:name="com.android.server.wm.TrustedPresentationCallbackTest$TestActivity"
+            android:exported="true"
+            android:showWhenLocked="true"
+            android:turnScreenOn="true" />
+
+        <activity
+            android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
         <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
             android:foregroundServiceType="mediaProjection"
             android:enabled="true">
diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
index 5863e9d..6a9f283 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
@@ -58,9 +58,20 @@
         mPhoneWindowManager.overrideCanStartDreaming(true);
         sendKey(KEYCODE_POWER);
         mPhoneWindowManager.assertDreamRequest();
+        mPhoneWindowManager.overrideIsDreaming(true);
         mPhoneWindowManager.assertLockedAfterAppTransitionFinished();
     }
 
+    @Test
+    public void testAppTransitionFinishedCalledAfterDreamStoppedWillNotLockAgain() {
+        mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_DREAM_OR_SLEEP);
+        mPhoneWindowManager.overrideCanStartDreaming(true);
+        sendKey(KEYCODE_POWER);
+        mPhoneWindowManager.assertDreamRequest();
+        mPhoneWindowManager.overrideIsDreaming(false);
+        mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
+    }
+
     /**
      * Power double-press to launch camera does not lock device when the single press behavior is to
      * dream.
@@ -72,7 +83,7 @@
         sendKey(KEYCODE_POWER);
         sendKey(KEYCODE_POWER);
         mPhoneWindowManager.assertCameraLaunch();
-        mPhoneWindowManager.assertWillNotLockAfterAppTransitionFinished();
+        mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 676bfb0..bf88ce4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -39,6 +39,7 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
 
 import static java.util.Collections.unmodifiableMap;
@@ -59,10 +60,10 @@
 
 class ShortcutKeyTestBase {
     TestPhoneWindowManager mPhoneWindowManager;
-    final Context mContext = getInstrumentation().getTargetContext();
+    final Context mContext = spy(getInstrumentation().getTargetContext());
 
     /** Modifier key to meta state */
-    private static final Map<Integer, Integer> MODIFIER;
+    protected static final Map<Integer, Integer> MODIFIER;
     static {
         final Map<Integer, Integer> map = new ArrayMap<>();
         map.put(KEYCODE_CTRL_LEFT, META_CTRL_LEFT_ON | META_CTRL_ON);
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
new file mode 100644
index 0000000..feca326
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import android.platform.test.annotations.Presubmit;
+import android.view.KeyEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@Presubmit
+@SmallTest
+@RunWith(JUnitParamsRunner.class)
+public class ShortcutLoggingTests extends ShortcutKeyTestBase {
+
+    private static final int VENDOR_ID = 0x123;
+    private static final int PRODUCT_ID = 0x456;
+    private static final int META_KEY = KeyEvent.KEYCODE_META_LEFT;
+    private static final int META_ON = MODIFIER.get(KeyEvent.KEYCODE_META_LEFT);
+    private static final int ALT_KEY = KeyEvent.KEYCODE_ALT_LEFT;
+    private static final int ALT_ON = MODIFIER.get(KeyEvent.KEYCODE_ALT_LEFT);
+    private static final int CTRL_KEY = KeyEvent.KEYCODE_CTRL_LEFT;
+    private static final int CTRL_ON = MODIFIER.get(KeyEvent.KEYCODE_CTRL_LEFT);
+    private static final int SHIFT_KEY = KeyEvent.KEYCODE_SHIFT_LEFT;
+    private static final int SHIFT_ON = MODIFIER.get(KeyEvent.KEYCODE_SHIFT_LEFT);
+
+    @Keep
+    private static Object[][] shortcutTestArguments() {
+        // testName, testKeys, expectedLogEvent, expectedKey, expectedModifierState
+        return new Object[][]{
+                {"Meta + H -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_H},
+                        KeyboardLogEvent.HOME, KeyEvent.KEYCODE_H, META_ON},
+                {"Meta + Enter -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
+                        KeyboardLogEvent.HOME, KeyEvent.KEYCODE_ENTER, META_ON},
+                {"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME}, KeyboardLogEvent.HOME,
+                        KeyEvent.KEYCODE_HOME, 0},
+                {"RECENT_APPS key -> Open Overview", new int[]{KeyEvent.KEYCODE_RECENT_APPS},
+                        KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_RECENT_APPS, 0},
+                {"Meta + Tab -> Open OVerview", new int[]{META_KEY, KeyEvent.KEYCODE_TAB},
+                        KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, META_ON},
+                {"Alt + Tab -> Open Overview", new int[]{ALT_KEY, KeyEvent.KEYCODE_TAB},
+                        KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, ALT_ON},
+                {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK}, KeyboardLogEvent.BACK,
+                        KeyEvent.KEYCODE_BACK, 0},
+                {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
+                        KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_APP_SWITCH, 0},
+                {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},
+                        KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_ASSIST, 0},
+                {"Meta + A -> Launch assistant", new int[]{META_KEY, KeyEvent.KEYCODE_A},
+                        KeyboardLogEvent.LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A, META_ON},
+                {"VOICE_ASSIST key -> Launch Voice Assistant",
+                        new int[]{KeyEvent.KEYCODE_VOICE_ASSIST},
+                        KeyboardLogEvent.LAUNCH_VOICE_ASSISTANT, KeyEvent.KEYCODE_VOICE_ASSIST, 0},
+                {"Meta + I -> Launch System Settings", new int[]{META_KEY, KeyEvent.KEYCODE_I},
+                        KeyboardLogEvent.LAUNCH_SYSTEM_SETTINGS, KeyEvent.KEYCODE_I, META_ON},
+                {"Meta + N -> Toggle Notification panel", new int[]{META_KEY, KeyEvent.KEYCODE_N},
+                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_N, META_ON},
+                {"NOTIFICATION key -> Toggle Notification Panel",
+                        new int[]{KeyEvent.KEYCODE_NOTIFICATION},
+                        KeyboardLogEvent.TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_NOTIFICATION,
+                        0},
+                {"Meta + T -> Toggle Taskbar", new int[]{META_KEY, KeyEvent.KEYCODE_T},
+                        KeyboardLogEvent.TOGGLE_TASKBAR, KeyEvent.KEYCODE_T, META_ON},
+                {"Meta + Ctrl + S -> Take Screenshot",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_S},
+                        KeyboardLogEvent.TAKE_SCREENSHOT, KeyEvent.KEYCODE_S, META_ON | CTRL_ON},
+                {"Meta + / -> Open Shortcut Helper", new int[]{META_KEY, KeyEvent.KEYCODE_SLASH},
+                        KeyboardLogEvent.OPEN_SHORTCUT_HELPER, KeyEvent.KEYCODE_SLASH, META_ON},
+                {"BRIGHTNESS_UP key -> Increase Brightness",
+                        new int[]{KeyEvent.KEYCODE_BRIGHTNESS_UP}, KeyboardLogEvent.BRIGHTNESS_UP,
+                        KeyEvent.KEYCODE_BRIGHTNESS_UP, 0},
+                {"BRIGHTNESS_DOWN key -> Decrease Brightness",
+                        new int[]{KeyEvent.KEYCODE_BRIGHTNESS_DOWN},
+                        KeyboardLogEvent.BRIGHTNESS_DOWN, KeyEvent.KEYCODE_BRIGHTNESS_DOWN, 0},
+                {"KEYBOARD_BACKLIGHT_UP key -> Increase Keyboard Backlight",
+                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP},
+                        KeyboardLogEvent.KEYBOARD_BACKLIGHT_UP,
+                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, 0},
+                {"KEYBOARD_BACKLIGHT_DOWN key -> Decrease Keyboard Backlight",
+                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN},
+                        KeyboardLogEvent.KEYBOARD_BACKLIGHT_DOWN,
+                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, 0},
+                {"KEYBOARD_BACKLIGHT_TOGGLE key -> Toggle Keyboard Backlight",
+                        new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE},
+                        KeyboardLogEvent.KEYBOARD_BACKLIGHT_TOGGLE,
+                        KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, 0},
+                {"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP},
+                        KeyboardLogEvent.VOLUME_UP, KeyEvent.KEYCODE_VOLUME_UP, 0},
+                {"VOLUME_DOWN key -> Decrease Volume", new int[]{KeyEvent.KEYCODE_VOLUME_DOWN},
+                        KeyboardLogEvent.VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN, 0},
+                {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE},
+                        KeyboardLogEvent.VOLUME_MUTE, KeyEvent.KEYCODE_VOLUME_MUTE, 0},
+                {"ALL_APPS key -> Open App Drawer", new int[]{KeyEvent.KEYCODE_ALL_APPS},
+                        KeyboardLogEvent.ALL_APPS, KeyEvent.KEYCODE_ALL_APPS, 0},
+                {"SEARCH key -> Launch Search Activity", new int[]{KeyEvent.KEYCODE_SEARCH},
+                        KeyboardLogEvent.LAUNCH_SEARCH, KeyEvent.KEYCODE_SEARCH, 0},
+                {"LANGUAGE_SWITCH key -> Switch Keyboard Language",
+                        new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH},
+                        KeyboardLogEvent.LANGUAGE_SWITCH, KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0},
+                {"Meta + Space -> Switch Keyboard Language",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_SPACE},
+                        KeyboardLogEvent.LANGUAGE_SWITCH, KeyEvent.KEYCODE_SPACE, META_ON},
+                {"Meta + Shift + Space -> Switch Keyboard Language",
+                        new int[]{META_KEY, SHIFT_KEY, KeyEvent.KEYCODE_SPACE},
+                        KeyboardLogEvent.LANGUAGE_SWITCH, KeyEvent.KEYCODE_SPACE,
+                        META_ON | SHIFT_ON},
+                {"META key -> Open App Drawer in Accessibility mode", new int[]{META_KEY},
+                        KeyboardLogEvent.ACCESSIBILITY_ALL_APPS, META_KEY, META_ON},
+                {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY},
+                        KeyboardLogEvent.TOGGLE_CAPS_LOCK, ALT_KEY, META_ON | ALT_ON},
+                {"Alt + Meta -> Toggle CapsLock", new int[]{ALT_KEY, META_KEY},
+                        KeyboardLogEvent.TOGGLE_CAPS_LOCK, META_KEY, META_ON | ALT_ON},
+                {"CAPS_LOCK key -> Toggle CapsLock", new int[]{KeyEvent.KEYCODE_CAPS_LOCK},
+                        KeyboardLogEvent.TOGGLE_CAPS_LOCK, KeyEvent.KEYCODE_CAPS_LOCK, 0},
+                {"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE},
+                        KeyboardLogEvent.SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE, 0},
+                {"Meta + Ctrl + DPAD_UP -> Split screen navigation",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP},
+                        KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_UP,
+                        META_ON | CTRL_ON},
+                {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
+                        KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_LEFT,
+                        META_ON | CTRL_ON},
+                {"Meta + Ctrl + DPAD_RIGHT -> Split screen navigation",
+                        new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_RIGHT},
+                        KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_RIGHT,
+                        META_ON | CTRL_ON},
+                {"Shift + Menu -> Trigger Bug Report", new int[]{SHIFT_KEY, KeyEvent.KEYCODE_MENU},
+                        KeyboardLogEvent.TRIGGER_BUG_REPORT, KeyEvent.KEYCODE_MENU, SHIFT_ON},
+                {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L},
+                        KeyboardLogEvent.LOCK_SCREEN, KeyEvent.KEYCODE_L, META_ON},
+                {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N},
+                        KeyboardLogEvent.OPEN_NOTES, KeyEvent.KEYCODE_N, META_ON | CTRL_ON},
+                {"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER},
+                        KeyboardLogEvent.TOGGLE_POWER, KeyEvent.KEYCODE_POWER, 0},
+                {"TV_POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_TV_POWER},
+                        KeyboardLogEvent.TOGGLE_POWER, KeyEvent.KEYCODE_TV_POWER, 0},
+                {"SYSTEM_NAVIGATION_DOWN key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN},
+                        KeyboardLogEvent.SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
+                        0},
+                {"SYSTEM_NAVIGATION_UP key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP},
+                        KeyboardLogEvent.SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
+                        0},
+                {"SYSTEM_NAVIGATION_LEFT key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT},
+                        KeyboardLogEvent.SYSTEM_NAVIGATION, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
+                        0},
+                {"SYSTEM_NAVIGATION_RIGHT key -> System Navigation",
+                        new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT},
+                        KeyboardLogEvent.SYSTEM_NAVIGATION,
+                        KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT, 0},
+                {"SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SLEEP},
+                        KeyboardLogEvent.SLEEP, KeyEvent.KEYCODE_SLEEP, 0},
+                {"SOFT_SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SOFT_SLEEP},
+                        KeyboardLogEvent.SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP, 0},
+                {"WAKEUP key -> System Wakeup", new int[]{KeyEvent.KEYCODE_WAKEUP},
+                        KeyboardLogEvent.WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0},
+                {"MEDIA_PLAY key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY},
+                        KeyboardLogEvent.MEDIA_KEY, KeyEvent.KEYCODE_MEDIA_PLAY, 0},
+                {"MEDIA_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PAUSE},
+                        KeyboardLogEvent.MEDIA_KEY, KeyEvent.KEYCODE_MEDIA_PAUSE, 0},
+                {"MEDIA_PLAY_PAUSE key -> Media Control",
+                        new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE}, KeyboardLogEvent.MEDIA_KEY,
+                        KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0},
+                {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_BROWSER, KeyEvent.KEYCODE_B, META_ON},
+                {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_BROWSER, KeyEvent.KEYCODE_EXPLORER, 0},
+                {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_CONTACTS, KeyEvent.KEYCODE_C, META_ON},
+                {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_CONTACTS, KeyEvent.KEYCODE_CONTACTS, 0},
+                {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_EMAIL, KeyEvent.KEYCODE_E, META_ON},
+                {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_EMAIL, KeyEvent.KEYCODE_ENVELOPE, 0},
+                {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_CALENDAR, KeyEvent.KEYCODE_K, META_ON},
+                {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_CALENDAR, KeyEvent.KEYCODE_CALENDAR, 0},
+                {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_MUSIC, KeyEvent.KEYCODE_P, META_ON},
+                {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_MUSIC, KeyEvent.KEYCODE_MUSIC, 0},
+                {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_CALCULATOR, KeyEvent.KEYCODE_U, META_ON},
+                {"CALCULATOR key -> Launch Default Calculator",
+                        new int[]{KeyEvent.KEYCODE_CALCULATOR},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_CALCULATOR, KeyEvent.KEYCODE_CALCULATOR, 0},
+                {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_MAPS, KeyEvent.KEYCODE_M, META_ON},
+                {"Meta + S -> Launch Default Messaging App",
+                        new int[]{META_KEY, KeyEvent.KEYCODE_S},
+                        KeyboardLogEvent.LAUNCH_DEFAULT_MESSAGING, KeyEvent.KEYCODE_S, META_ON}};
+    }
+
+    @Before
+    @Override
+    public void setUp() {
+        super.setUp();
+        mPhoneWindowManager.overrideKeyEventSource(VENDOR_ID, PRODUCT_ID);
+        mPhoneWindowManager.overrideLaunchHome();
+        mPhoneWindowManager.overrideSearchKeyBehavior(
+                PhoneWindowManager.SEARCH_BEHAVIOR_TARGET_ACTIVITY);
+        mPhoneWindowManager.overrideEnableBugReportTrigger(true);
+        mPhoneWindowManager.overrideStatusBarManagerInternal();
+        mPhoneWindowManager.overrideStartActivity();
+        mPhoneWindowManager.overrideUserSetupComplete();
+    }
+
+    @Test
+    @Parameters(method = "shortcutTestArguments")
+    public void testShortcuts(String testName, int[] testKeys, KeyboardLogEvent expectedLogEvent,
+            int expectedKey, int expectedModifierState) {
+        sendKeyCombination(testKeys, 0);
+        mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
+                expectedKey, expectedModifierState, "Failed while executing " + testName);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
index 280afba..3bb86a7 100644
--- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -316,4 +316,16 @@
         pressKey(KEYCODE_POWER, 0 /* pressTime */);
         assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
     }
+
+    // Verify short press should not be triggered if no very long press behavior defined but the
+    // press time exceeded the very long press timeout.
+    @Test
+    public void testTimeoutExceedVeryLongPress() throws InterruptedException {
+        mVeryLongPressOnPowerBehavior = false;
+
+        pressKey(KEYCODE_POWER, mVeryLongPressTime + 50);
+        assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS));
+        assertEquals(mVeryLongPressed.getCount(), 1);
+        assertEquals(mShortPressed.getCount(), 1);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
new file mode 100644
index 0000000..fe8017e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+/**
+ * Test class for stem key gesture.
+ *
+ * Build/Install/Run:
+ * atest WmTests:StemKeyGestureTests
+ */
+public class StemKeyGestureTests extends ShortcutKeyTestBase {
+    @Mock private Resources mResources;
+
+    /**
+     * Stem single key should not launch behavior during set up.
+     */
+    @Test
+    public void stemSingleKey_duringSetup_doNothing() {
+        stemKeySetup(
+                () -> overrideBehavior(
+                        com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior,
+                        SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS));
+        mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
+        mPhoneWindowManager.overrideIsUserSetupComplete(false);
+
+        sendKey(KEYCODE_STEM_PRIMARY);
+
+        mPhoneWindowManager.assertNotOpenAllAppView();
+    }
+
+    /**
+     * Stem single key should launch all app after set up.
+     */
+    @Test
+    public void stemSingleKey_AfterSetup_openAllApp() {
+        stemKeySetup(
+                () -> overrideBehavior(
+                        com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior,
+                        SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS));
+        mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
+        mPhoneWindowManager.overrideIsUserSetupComplete(true);
+
+        sendKey(KEYCODE_STEM_PRIMARY);
+
+        mPhoneWindowManager.assertOpenAllAppView();
+    }
+
+    private void stemKeySetup(Runnable behaviorOverrideRunnable) {
+        super.tearDown();
+        setupResourcesMock();
+        behaviorOverrideRunnable.run();
+        super.setUp();
+    }
+
+    private void setupResourcesMock() {
+        Resources realResources = mContext.getResources();
+
+        mResources = Mockito.mock(Resources.class);
+        doReturn(mResources).when(mContext).getResources();
+
+        doAnswer(invocation -> realResources.getXml((Integer) invocation.getArguments()[0]))
+                .when(mResources).getXml(anyInt());
+        doAnswer(invocation -> realResources.getString((Integer) invocation.getArguments()[0]))
+                .when(mResources).getString(anyInt());
+        doAnswer(invocation -> realResources.getBoolean((Integer) invocation.getArguments()[0]))
+                .when(mResources).getBoolean(anyInt());
+    }
+
+    private void overrideBehavior(int resId, int expectedBehavior) {
+        doReturn(expectedBehavior).when(mResources).getInteger(eq(resId));
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index d383024..1866767 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.policy;
 
+import static android.os.Build.HW_TIMEOUT_MULTIPLIER;
 import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.STATE_ON;
@@ -25,6 +26,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.description;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
@@ -34,6 +36,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT;
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS;
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GO_TO_VOICE_ASSIST;
@@ -43,10 +46,12 @@
 import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mockingDetails;
 import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.withSettings;
 
 import android.app.ActivityManagerInternal;
@@ -56,29 +61,37 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.hardware.SensorPrivacyManager;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.InputManager;
 import android.media.AudioManagerInternal;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.os.Vibrator;
 import android.service.dreams.DreamManagerInternal;
 import android.telecom.TelecomManager;
 import android.util.FeatureFlagUtils;
 import android.view.Display;
+import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.autofill.AutofillManagerInternal;
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.internal.accessibility.AccessibilityShortcutController;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.GestureLauncherService;
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
+import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.policy.keyguard.KeyguardServiceDelegate;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -95,11 +108,14 @@
 import org.mockito.MockSettings;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.quality.Strictness;
 
 import java.util.function.Supplier;
 
 class TestPhoneWindowManager {
     private static final long SHORTCUT_KEY_DELAY_MILLIS = 150;
+    private static final long TEST_SINGLE_KEY_DELAY_MILLIS
+            = SingleKeyGestureDetector.MULTI_PRESS_TIMEOUT + 1000L * HW_TIMEOUT_MULTIPLIER;
 
     private PhoneWindowManager mPhoneWindowManager;
     private Context mContext;
@@ -108,6 +124,8 @@
     @Mock private ActivityManagerInternal mActivityManagerInternal;
     @Mock private ActivityTaskManagerInternal mActivityTaskManagerInternal;
     @Mock private InputManagerInternal mInputManagerInternal;
+    @Mock private InputManager mInputManager;
+    @Mock private SensorPrivacyManager mSensorPrivacyManager;
     @Mock private DreamManagerInternal mDreamManagerInternal;
     @Mock private PowerManagerInternal mPowerManagerInternal;
     @Mock private DisplayManagerInternal mDisplayManagerInternal;
@@ -134,6 +152,8 @@
 
     @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
 
+    @Mock private KeyguardServiceDelegate mKeyguardServiceDelegate;
+
     private StaticMockitoSession mMockitoSession;
     private HandlerThread mHandlerThread;
     private Handler mHandler;
@@ -151,6 +171,10 @@
         Supplier<GlobalActions> getGlobalActionsFactory() {
             return () -> mGlobalActions;
         }
+
+        KeyguardServiceDelegate getKeyguardServiceDelegate() {
+            return mKeyguardServiceDelegate;
+        }
     }
 
     TestPhoneWindowManager(Context context) {
@@ -158,12 +182,12 @@
         mHandlerThread = new HandlerThread("fake window manager");
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
-        mHandler.runWithScissors(()-> setUp(context),  0 /* timeout */);
+        mContext = mockingDetails(context).isSpy() ? context : spy(context);
+        mHandler.runWithScissors(this::setUp,  0 /* timeout */);
     }
 
-    private void setUp(Context context) {
+    private void setUp() {
         mPhoneWindowManager = spy(new PhoneWindowManager());
-        mContext = spy(context);
 
         // Use stubOnly() to reduce memory usage if it doesn't need verification.
         final MockSettings spyStubOnly = withSettings().stubOnly()
@@ -171,6 +195,8 @@
         // Return mocked services: LocalServices.getService
         mMockitoSession = mockitoSession()
                 .mockStatic(LocalServices.class, spyStubOnly)
+                .mockStatic(FrameworkStatsLog.class)
+                .strictness(Strictness.LENIENT)
                 .startMocking();
 
         doReturn(mWindowManagerInternal).when(
@@ -198,7 +224,10 @@
 
         doReturn(mAppOpsManager).when(mContext).getSystemService(eq(AppOpsManager.class));
         doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
+        doReturn(mInputManager).when(mContext).getSystemService(eq(InputManager.class));
         doReturn(mPackageManager).when(mContext).getPackageManager();
+        doReturn(mSensorPrivacyManager).when(mContext).getSystemService(
+                eq(SensorPrivacyManager.class));
         doReturn(false).when(mPackageManager).hasSystemFeature(any());
         try {
             doThrow(new PackageManager.NameNotFoundException("test")).when(mPackageManager)
@@ -251,6 +280,7 @@
         overrideLaunchAccessibility();
         doReturn(false).when(mPhoneWindowManager).keyguardOn();
         doNothing().when(mContext).startActivityAsUser(any(), any());
+        doNothing().when(mContext).startActivityAsUser(any(), any(), any());
         Mockito.reset(mContext);
     }
 
@@ -329,6 +359,10 @@
         doReturn(canDream).when(mDreamManagerInternal).canStartDreaming(anyBoolean());
     }
 
+    void overrideIsDreaming(boolean isDreaming) {
+        doReturn(isDreaming).when(mDreamManagerInternal).isDreaming();
+    }
+
     void overrideDisplayState(int state) {
         doReturn(state).when(mDisplay).getState();
         doReturn(state == STATE_ON).when(mDisplayPolicy).isAwake();
@@ -381,6 +415,39 @@
         doNothing().when(mPhoneWindowManager).launchHomeFromHotKey(anyInt());
     }
 
+    void overrideIsUserSetupComplete(boolean isCompleted) {
+        doReturn(isCompleted).when(mPhoneWindowManager).isUserSetupComplete();
+    }
+
+    void setKeyguardServiceDelegateIsShowing(boolean isShowing) {
+        doReturn(isShowing).when(mKeyguardServiceDelegate).isShowing();
+    }
+
+    void overrideKeyEventSource(int vendorId, int productId) {
+        InputDevice device = new InputDevice.Builder().setId(1).setVendorId(vendorId).setProductId(
+                productId).setSources(InputDevice.SOURCE_KEYBOARD).setKeyboardType(
+                InputDevice.KEYBOARD_TYPE_ALPHABETIC).build();
+        doReturn(mInputManager).when(mContext).getSystemService(eq(InputManager.class));
+        doReturn(device).when(mInputManager).getInputDevice(anyInt());
+    }
+
+    void overrideSearchKeyBehavior(int behavior) {
+        mPhoneWindowManager.mSearchKeyBehavior = behavior;
+    }
+
+    void overrideEnableBugReportTrigger(boolean enable) {
+        mPhoneWindowManager.mEnableShiftMenuBugReports = enable;
+    }
+
+    void overrideStartActivity() {
+        doNothing().when(mContext).startActivityAsUser(any(), any());
+        doNothing().when(mContext).startActivityAsUser(any(), any(), any());
+    }
+
+    void overrideUserSetupComplete() {
+        doReturn(true).when(mPhoneWindowManager).isUserSetupComplete();
+    }
+
     /**
      * Below functions will check the policy behavior could be invoked.
      */
@@ -497,21 +564,50 @@
         verify(mInputManagerInternal).toggleCapsLock(anyInt());
     }
 
-    void assertWillNotLockAfterAppTransitionFinished() {
-        Assert.assertFalse(mPhoneWindowManager.mLockAfterAppTransitionFinished);
-    }
-
     void assertLockedAfterAppTransitionFinished() {
         ArgumentCaptor<AppTransitionListener> transitionCaptor =
                 ArgumentCaptor.forClass(AppTransitionListener.class);
         verify(mWindowManagerInternal).registerAppTransitionListener(
                 transitionCaptor.capture());
-        transitionCaptor.getValue().onAppTransitionFinishedLocked(any());
+        final IBinder token = mock(IBinder.class);
+        transitionCaptor.getValue().onAppTransitionFinishedLocked(token);
         verify(mPhoneWindowManager).lockNow(null);
     }
 
+    void assertDidNotLockAfterAppTransitionFinished() {
+        ArgumentCaptor<AppTransitionListener> transitionCaptor =
+                ArgumentCaptor.forClass(AppTransitionListener.class);
+        verify(mWindowManagerInternal).registerAppTransitionListener(
+                transitionCaptor.capture());
+        final IBinder token = mock(IBinder.class);
+        transitionCaptor.getValue().onAppTransitionFinishedLocked(token);
+        verify(mPhoneWindowManager, never()).lockNow(null);
+    }
+
     void assertGoToHomescreen() {
         waitForIdle();
         verify(mPhoneWindowManager).launchHomeFromHotKey(anyInt());
     }
+
+    void assertOpenAllAppView() {
+        waitForIdle();
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
+                .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
+        Assert.assertEquals(Intent.ACTION_ALL_APPS, intentCaptor.getValue().getAction());
+    }
+
+    void assertNotOpenAllAppView() {
+        waitForIdle();
+        verify(mContext, after(TEST_SINGLE_KEY_DELAY_MILLIS).never())
+                .startActivityAsUser(any(Intent.class), any(), any(UserHandle.class));
+    }
+
+    void assertShortcutLogged(int vendorId, int productId, KeyboardLogEvent logEvent,
+            int expectedKey, int expectedModifierState, String errorMsg) {
+        waitForIdle();
+        verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
+                        vendorId, productId, logEvent.getIntValue(), new int[]{expectedKey},
+                        expectedModifierState), description(errorMsg));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index e881039..3eed0b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2939,6 +2939,9 @@
         // transform to activity1.
         int rotation = (mDisplayContent.getRotation() + 1) % 4;
         mDisplayContent.setFixedRotationLaunchingApp(activity, rotation);
+        // The configuration with rotation change should not trigger task-association.
+        assertNotNull(activity.mStartingData);
+        assertNull(activity.mStartingData.mAssociatedTask);
         doReturn(rotation).when(mDisplayContent)
                 .rotationForActivityInDifferentOrientation(topActivity);
 
@@ -3334,7 +3337,7 @@
 
         // app1 requests IME visible.
         app1.setRequestedVisibleTypes(ime(), ime());
-        mDisplayContent.getInsetsStateController().onInsetsModified(app1);
+        mDisplayContent.getInsetsStateController().onRequestedVisibleTypesChanged(app1);
 
         // Verify app1's IME insets is visible and app2's IME insets frozen flag set.
         assertTrue(app1.getInsetsState().peekSource(ID_IME).isVisible());
@@ -3403,7 +3406,7 @@
         assertFalse(activity2.mImeInsetsFrozenUntilStartInput);
 
         app1.setRequestedVisibleTypes(ime());
-        controller.onInsetsModified(app1);
+        controller.onRequestedVisibleTypesChanged(app1);
 
         // Expect all activities in split-screen will get IME insets visible state
         assertTrue(app1.getInsetsState().peekSource(ID_IME).isVisible());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
index 0eca8c9..98f1843 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
@@ -18,6 +18,9 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.junit.Assert.assertEquals;
 
 import android.platform.test.annotations.Presubmit;
@@ -28,6 +31,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+
 /**
  * Test class for {@link ActivitySnapshotController}.
  *
@@ -42,13 +47,13 @@
     private ActivitySnapshotController mActivitySnapshotController;
     @Before
     public void setUp() throws Exception {
+        spyOn(mWm.mSnapshotController.mActivitySnapshotController);
         mActivitySnapshotController = mWm.mSnapshotController.mActivitySnapshotController;
+        doReturn(false).when(mActivitySnapshotController).shouldDisableSnapshots();
         mActivitySnapshotController.resetTmpFields();
     }
     @Test
     public void testOpenActivityTransition() {
-        final SnapshotController.TransitionState transitionState =
-                new SnapshotController.TransitionState();
         final Task task = createTask(mDisplayContent);
         // note for createAppWindow: the new child is added at index 0
         final WindowState openingWindow = createAppWindow(task,
@@ -59,14 +64,12 @@
                 "closingWindow");
         closingWindow.mActivityRecord.commitVisibility(
                 false /* visible */, true /* performLayout */);
-        transitionState.addParticipant(closingWindow.mActivityRecord, false);
-        transitionState.addParticipant(openingWindow.mActivityRecord, true);
-        mActivitySnapshotController.handleOpenActivityTransition(transitionState);
+        final ArrayList<WindowContainer> windows = new ArrayList<>();
+        windows.add(openingWindow.mActivityRecord);
+        windows.add(closingWindow.mActivityRecord);
+        mActivitySnapshotController.handleTransitionFinish(windows);
 
-        assertEquals(1, mActivitySnapshotController.mPendingCaptureActivity.size());
         assertEquals(0, mActivitySnapshotController.mPendingRemoveActivity.size());
-        assertEquals(closingWindow.mActivityRecord,
-                mActivitySnapshotController.mPendingCaptureActivity.valueAt(0));
         mActivitySnapshotController.resetTmpFields();
 
         // simulate three activity
@@ -74,19 +77,15 @@
                 "belowClose");
         belowClose.mActivityRecord.commitVisibility(
                 false /* visible */, true /* performLayout */);
-        mActivitySnapshotController.handleOpenActivityTransition(transitionState);
-        assertEquals(1, mActivitySnapshotController.mPendingCaptureActivity.size());
+        windows.add(belowClose.mActivityRecord);
+        mActivitySnapshotController.handleTransitionFinish(windows);
         assertEquals(1, mActivitySnapshotController.mPendingRemoveActivity.size());
-        assertEquals(closingWindow.mActivityRecord,
-                mActivitySnapshotController.mPendingCaptureActivity.valueAt(0));
         assertEquals(belowClose.mActivityRecord,
                 mActivitySnapshotController.mPendingRemoveActivity.valueAt(0));
     }
 
     @Test
     public void testCloseActivityTransition() {
-        final SnapshotController.TransitionState transitionState =
-                new SnapshotController.TransitionState();
         final Task task = createTask(mDisplayContent);
         // note for createAppWindow: the new child is added at index 0
         final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD,
@@ -97,10 +96,10 @@
                 ACTIVITY_TYPE_STANDARD, "openingWindow");
         openingWindow.mActivityRecord.commitVisibility(
                 true /* visible */, true /* performLayout */);
-        transitionState.addParticipant(closingWindow.mActivityRecord, false);
-        transitionState.addParticipant(openingWindow.mActivityRecord, true);
-        mActivitySnapshotController.handleCloseActivityTransition(transitionState);
-        assertEquals(0, mActivitySnapshotController.mPendingCaptureActivity.size());
+        final ArrayList<WindowContainer> windows = new ArrayList<>();
+        windows.add(openingWindow.mActivityRecord);
+        windows.add(closingWindow.mActivityRecord);
+        mActivitySnapshotController.handleTransitionFinish(windows);
         assertEquals(1, mActivitySnapshotController.mPendingDeleteActivity.size());
         assertEquals(openingWindow.mActivityRecord,
                 mActivitySnapshotController.mPendingDeleteActivity.valueAt(0));
@@ -111,8 +110,8 @@
                 "belowOpen");
         belowOpen.mActivityRecord.commitVisibility(
                 false /* visible */, true /* performLayout */);
-        mActivitySnapshotController.handleCloseActivityTransition(transitionState);
-        assertEquals(0, mActivitySnapshotController.mPendingCaptureActivity.size());
+        windows.add(belowOpen.mActivityRecord);
+        mActivitySnapshotController.handleTransitionFinish(windows);
         assertEquals(1, mActivitySnapshotController.mPendingDeleteActivity.size());
         assertEquals(1, mActivitySnapshotController.mPendingLoadActivity.size());
         assertEquals(openingWindow.mActivityRecord,
@@ -123,10 +122,6 @@
 
     @Test
     public void testTaskTransition() {
-        final SnapshotController.TransitionState taskCloseTransition =
-                new SnapshotController.TransitionState();
-        final SnapshotController.TransitionState taskOpenTransition =
-                new SnapshotController.TransitionState();
         final Task closeTask = createTask(mDisplayContent);
         // note for createAppWindow: the new child is added at index 0
         final WindowState closingWindow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD,
@@ -147,10 +142,10 @@
                 "openingWindowBelow");
         openingWindowBelow.mActivityRecord.commitVisibility(
                 false /* visible */, true /* performLayout */);
-        taskCloseTransition.addParticipant(closeTask, false);
-        taskOpenTransition.addParticipant(openTask, true);
-        mActivitySnapshotController.handleCloseTaskTransition(taskCloseTransition);
-        mActivitySnapshotController.handleOpenTaskTransition(taskOpenTransition);
+        final ArrayList<WindowContainer> windows = new ArrayList<>();
+        windows.add(closeTask);
+        windows.add(openTask);
+        mActivitySnapshotController.handleTransitionFinish(windows);
 
         assertEquals(1, mActivitySnapshotController.mPendingRemoveActivity.size());
         assertEquals(closingWindowBelow.mActivityRecord,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 9842d70..1ba8f7d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1747,6 +1747,9 @@
 
     @Test
     public void testRecordActivityMovementBeforeDeliverToTop() {
+        // Mock recents as task is only marked to be in recents
+        mAtm.mTaskSupervisor.setRecentTasks(mock(RecentTasks.class));
+
         final Task task = new TaskBuilder(mAtm.mTaskSupervisor).build();
         final ActivityRecord activityBot = new ActivityBuilder(mAtm).setTask(task).build();
         final ActivityRecord activityTop = new ActivityBuilder(mAtm).setTask(task).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppSnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/AppSnapshotControllerTests.java
deleted file mode 100644
index 83af1814..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/AppSnapshotControllerTests.java
+++ /dev/null
@@ -1,171 +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.server.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-
-import static com.android.server.wm.SnapshotController.ACTIVITY_CLOSE;
-import static com.android.server.wm.SnapshotController.ACTIVITY_OPEN;
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
-import static com.android.server.wm.SnapshotController.TASK_OPEN;
-
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-import android.util.ArraySet;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-
-/**
- * Test class for {@link SnapshotController}.
- *
- * Build/Install/Run:
- *  *  atest WmTests:AppSnapshotControllerTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class AppSnapshotControllerTests extends WindowTestsBase {
-    final ArraySet<ActivityRecord> mClosingApps = new ArraySet<>();
-    final ArraySet<ActivityRecord> mOpeningApps = new ArraySet<>();
-
-    final TransitionMonitor mOpenActivityMonitor = new TransitionMonitor();
-    final TransitionMonitor mCloseActivityMonitor = new TransitionMonitor();
-    final TransitionMonitor mOpenTaskMonitor = new TransitionMonitor();
-    final TransitionMonitor mCloseTaskMonitor = new TransitionMonitor();
-
-    @Before
-    public void setUp() throws Exception {
-        resetStatus();
-        mWm.mSnapshotController.registerTransitionStateConsumer(
-                ACTIVITY_CLOSE, mCloseActivityMonitor::handleTransition);
-        mWm.mSnapshotController.registerTransitionStateConsumer(
-                ACTIVITY_OPEN, mOpenActivityMonitor::handleTransition);
-        mWm.mSnapshotController.registerTransitionStateConsumer(
-                TASK_CLOSE, mCloseTaskMonitor::handleTransition);
-        mWm.mSnapshotController.registerTransitionStateConsumer(
-                TASK_OPEN, mOpenTaskMonitor::handleTransition);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mWm.mSnapshotController.unregisterTransitionStateConsumer(
-                ACTIVITY_CLOSE, mCloseActivityMonitor::handleTransition);
-        mWm.mSnapshotController.unregisterTransitionStateConsumer(
-                ACTIVITY_OPEN, mOpenActivityMonitor::handleTransition);
-        mWm.mSnapshotController.unregisterTransitionStateConsumer(
-                TASK_CLOSE, mCloseTaskMonitor::handleTransition);
-        mWm.mSnapshotController.unregisterTransitionStateConsumer(
-                TASK_OPEN, mOpenTaskMonitor::handleTransition);
-    }
-
-    private static class TransitionMonitor {
-        private final ArraySet<WindowContainer> mOpenParticipant = new ArraySet<>();
-        private final ArraySet<WindowContainer> mCloseParticipant = new ArraySet<>();
-        void handleTransition(SnapshotController.TransitionState<ActivityRecord> state) {
-            mOpenParticipant.addAll(state.getParticipant(true /* open */));
-            mCloseParticipant.addAll(state.getParticipant(false /* close */));
-        }
-        void reset() {
-            mOpenParticipant.clear();
-            mCloseParticipant.clear();
-        }
-    }
-
-    private void resetStatus() {
-        mClosingApps.clear();
-        mOpeningApps.clear();
-        mOpenActivityMonitor.reset();
-        mCloseActivityMonitor.reset();
-        mOpenTaskMonitor.reset();
-        mCloseTaskMonitor.reset();
-    }
-
-    @Test
-    public void testHandleAppTransition_openActivityTransition() {
-        final Task task = createTask(mDisplayContent);
-        // note for createAppWindow: the new child is added at index 0
-        final WindowState openingWindow = createAppWindow(task,
-                ACTIVITY_TYPE_STANDARD, "openingWindow");
-        openingWindow.mActivityRecord.commitVisibility(
-                true /* visible */, true /* performLayout */);
-        final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD,
-                "closingWindow");
-        closingWindow.mActivityRecord.commitVisibility(
-                false /* visible */, true /* performLayout */);
-        mClosingApps.add(closingWindow.mActivityRecord);
-        mOpeningApps.add(openingWindow.mActivityRecord);
-        mWm.mSnapshotController.handleAppTransition(mClosingApps, mOpeningApps);
-        assertTrue(mOpenActivityMonitor.mCloseParticipant.contains(closingWindow.mActivityRecord));
-        assertTrue(mOpenActivityMonitor.mOpenParticipant.contains(openingWindow.mActivityRecord));
-    }
-
-    @Test
-    public void testHandleAppTransition_closeActivityTransition() {
-        final Task task = createTask(mDisplayContent);
-        // note for createAppWindow: the new child is added at index 0
-        final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD,
-                "closingWindow");
-        closingWindow.mActivityRecord.commitVisibility(
-                false /* visible */, true /* performLayout */);
-        final WindowState openingWindow = createAppWindow(task,
-                ACTIVITY_TYPE_STANDARD, "openingWindow");
-        openingWindow.mActivityRecord.commitVisibility(
-                true /* visible */, true /* performLayout */);
-        mClosingApps.add(closingWindow.mActivityRecord);
-        mOpeningApps.add(openingWindow.mActivityRecord);
-        mWm.mSnapshotController.handleAppTransition(mClosingApps, mOpeningApps);
-        assertTrue(mCloseActivityMonitor.mCloseParticipant.contains(closingWindow.mActivityRecord));
-        assertTrue(mCloseActivityMonitor.mOpenParticipant.contains(openingWindow.mActivityRecord));
-    }
-
-    @Test
-    public void testHandleAppTransition_TaskTransition() {
-        final Task closeTask = createTask(mDisplayContent);
-        // note for createAppWindow: the new child is added at index 0
-        final WindowState closingWindow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD,
-                "closingWindow");
-        closingWindow.mActivityRecord.commitVisibility(
-                false /* visible */, true /* performLayout */);
-        final WindowState closingWindowBelow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD,
-                "closingWindowBelow");
-        closingWindowBelow.mActivityRecord.commitVisibility(
-                false /* visible */, true /* performLayout */);
-
-        final Task openTask = createTask(mDisplayContent);
-        final WindowState openingWindow = createAppWindow(openTask, ACTIVITY_TYPE_STANDARD,
-                "openingWindow");
-        openingWindow.mActivityRecord.commitVisibility(
-                true /* visible */, true /* performLayout */);
-        final WindowState openingWindowBelow = createAppWindow(openTask, ACTIVITY_TYPE_STANDARD,
-                "openingWindowBelow");
-        openingWindowBelow.mActivityRecord.commitVisibility(
-                false /* visible */, true /* performLayout */);
-
-        mClosingApps.add(closingWindow.mActivityRecord);
-        mOpeningApps.add(openingWindow.mActivityRecord);
-        mWm.mSnapshotController.handleAppTransition(mClosingApps, mOpeningApps);
-        assertTrue(mCloseTaskMonitor.mCloseParticipant.contains(closeTask));
-        assertTrue(mOpenTaskMonitor.mOpenParticipant.contains(openTask));
-    }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
index 7b4392b..56c3ec0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
@@ -317,7 +317,8 @@
         assertEquals("Expected " + numPendingScreenshots + " pending screenshots, got "
                         + mDataRequester.getPendingScreenshotCount(),
                 numPendingScreenshots, mDataRequester.getPendingScreenshotCount());
-        assertFalse("Expected request NOT completed", mCallbacks.mRequestCompleted);
+        assertEquals("Expected request NOT completed, unless no pending data",
+                numPendingData == 0 && numPendingScreenshots == 0, mCallbacks.mRequestCompleted);
         mGate.countDown();
         waitForIdle(mHandler);
         assertEquals("Expected " + numReceivedData + " data, received "
@@ -376,14 +377,7 @@
 
         @Override
         public void onAssistRequestCompleted() {
-            mHandler.post(() -> {
-                try {
-                    mGate.await(10, TimeUnit.SECONDS);
-                    mRequestCompleted = true;
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "Failed to wait", e);
-                }
-            });
+            mRequestCompleted = true;
         }
     }
 }
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 6d7f2c1..1c86758 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -570,6 +570,7 @@
         final ContextWrapper contextSpy = Mockito.spy(new ContextWrapper(mWm.mContext));
         final Resources resourcesSpy = Mockito.spy(contextSpy.getResources());
 
+        spyOn(mAtm.mTaskOrganizerController);
         when(contextSpy.getResources()).thenReturn(resourcesSpy);
 
         MockitoSession mockitoSession = mockitoSession().mockStatic(BackNavigationController.class)
@@ -597,7 +598,8 @@
                         mBackAnimationAdapter, task, mRootHomeTask, bottomActivity, homeActivity);
         assertTrue(toHomeBuilder.mIsLaunchBehind);
         toHomeBuilder.build();
-        verify(animationHandler, never()).createStartingSurface(any());
+        verify(mAtm.mTaskOrganizerController, never())
+                .addWindowlessStartingSurface(any(), any(), any(), any(), any());
         animationHandler.clearBackAnimateTarget();
 
         // Back to ACTIVITY and TASK have the same logic, just with different target.
@@ -609,9 +611,11 @@
         assertFalse(toActivityBuilder.mIsLaunchBehind);
         toActivityBuilder.build();
         if (preferWindowlessSurface) {
-            verify(animationHandler).createStartingSurface(any());
+            verify(mAtm.mTaskOrganizerController)
+                    .addWindowlessStartingSurface(any(), any(), any(), any(), any());
         } else {
-            verify(animationHandler, never()).createStartingSurface(any());
+            verify(mAtm.mTaskOrganizerController, never())
+                    .addWindowlessStartingSurface(any(), any(), any(), any(), any());
         }
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
index 52226c2..4473a31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
@@ -123,6 +123,7 @@
         controller.setContentRecordingSessionLocked(mWaitingDisplaySession, mWm);
         verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(
                 mWaitingDisplaySession);
+        verify(mVirtualDisplayContent).updateRecording();
 
         // WHEN updating the session on the same display, so no longer waiting to record.
         ContentRecordingSession sessionUpdate = ContentRecordingSession.createTaskSession(
@@ -135,7 +136,7 @@
         // THEN the session was accepted.
         assertThat(resultingSession).isEqualTo(sessionUpdate);
         verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(sessionUpdate);
-        verify(mVirtualDisplayContent).updateRecording();
+        verify(mVirtualDisplayContent, atLeastOnce()).updateRecording();
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index 3a456fb..dc4e47d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.util.DisplayMetrics.DENSITY_DEFAULT;
 
@@ -80,13 +81,27 @@
     }
 
     @Test
-    public void testReturnsSkipIfTaskNotUsingActivityTypeStandard() {
+    public void testReturnsSkipIfTaskNotUsingActivityTypeStandardOrUndefined() {
         final Task task = new TaskBuilder(mSupervisor).setActivityType(
                 ACTIVITY_TYPE_ASSISTANT).build();
         assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).calculate());
     }
 
     @Test
+    public void testReturnsDoneIfTaskUsingActivityTypeStandard() {
+        final Task task = new TaskBuilder(mSupervisor).setActivityType(
+                ACTIVITY_TYPE_STANDARD).build();
+        assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+    }
+
+    @Test
+    public void testReturnsDoneIfTaskUsingActivityTypeUndefined() {
+        final Task task = new TaskBuilder(mSupervisor).setActivityType(
+                ACTIVITY_TYPE_UNDEFINED).build();
+        assertEquals(RESULT_DONE, new CalculateRequestBuilder().setTask(task).calculate());
+    }
+
+    @Test
     public void testReturnsSkipIfCurrentParamsHasBounds() {
         final Task task = new TaskBuilder(mSupervisor).setActivityType(
                 ACTIVITY_TYPE_STANDARD).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 340b591..9cc4117 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -797,6 +797,7 @@
         final int baseDensity = 320;
         final float baseXDpi = 60;
         final float baseYDpi = 60;
+        final int originalMinTaskSizeDp = displayContent.mMinSizeOfResizeableTaskDp;
 
         displayContent.mInitialDisplayWidth = baseWidth;
         displayContent.mInitialDisplayHeight = baseHeight;
@@ -814,6 +815,9 @@
         displayContent.setForcedDensity(forcedDensity, 0 /* userId */);
         verifySizes(displayContent, baseWidth, baseHeight, forcedDensity);
 
+        // Verify that minimal task size (dp) doesn't change with density of display.
+        assertEquals(originalMinTaskSizeDp, displayContent.mMinSizeOfResizeableTaskDp);
+
         // Verify that forcing resolution won't affect the already forced density.
         displayContent.setForcedSize(1800, 1200);
         verifySizes(displayContent, 1800, 1200, forcedDensity);
@@ -1811,30 +1815,6 @@
         assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
     }
 
-    /**
-     * Creates different types of displays, verifies that minimal task size doesn't change
-     * with density of display.
-     */
-    @Test
-    public void testCalculatesDisplaySpecificMinTaskSizes() {
-        DisplayContent defaultTestDisplay =
-                new TestDisplayContent.Builder(mAtm, 1000, 2000).build();
-        final int defaultMinTaskSize = defaultTestDisplay.mMinSizeOfResizeableTaskDp;
-        DisplayContent firstDisplay = new TestDisplayContent.Builder(mAtm, 1000, 2000)
-                .setDensityDpi(300)
-                .updateDisplayMetrics()
-                .setDefaultMinTaskSizeDp(defaultMinTaskSize + 10)
-                .build();
-        assertEquals(defaultMinTaskSize + 10, firstDisplay.mMinSizeOfResizeableTaskDp);
-
-        DisplayContent secondDisplay = new TestDisplayContent.Builder(mAtm, 200, 200)
-                .setDensityDpi(320)
-                .updateDisplayMetrics()
-                .setDefaultMinTaskSizeDp(defaultMinTaskSize + 20)
-                .build();
-        assertEquals(defaultMinTaskSize + 20, secondDisplay.mMinSizeOfResizeableTaskDp);
-    }
-
     @Test
     public void testRecentsNotRotatingWithFixedRotation() {
         unblockDisplayRotation(mDisplayContent);
@@ -2066,6 +2046,7 @@
 
         // Once transition starts, rotation is applied and transition shows DC rotating.
         testPlayer.startTransition();
+        waitUntilHandlersIdle();
         assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
         assertNotNull(testPlayer.mLastReady);
         assertTrue(testPlayer.mController.isPlaying());
@@ -2073,6 +2054,17 @@
         assertNotEquals(testPlayer.mLastReady.getChange(dcToken).getEndRotation(),
                 testPlayer.mLastReady.getChange(dcToken).getStartRotation());
         testPlayer.finish();
+
+        // The AsyncRotationController should only exist if there is an ongoing rotation change.
+        dc.finishAsyncRotationIfPossible();
+        dc.setLastHasContent();
+        doReturn(dr.getRotation() + 1).when(dr).rotationForOrientation(anyInt(), anyInt());
+        dr.updateRotationUnchecked(true /* forceUpdate */);
+        assertNotNull(dc.getAsyncRotationController());
+        doReturn(dr.getRotation() - 1).when(dr).rotationForOrientation(anyInt(), anyInt());
+        dr.updateRotationUnchecked(true /* forceUpdate */);
+        assertNull("Cancel AsyncRotationController for the intermediate rotation changes 0->1->0",
+                dc.getAsyncRotationController());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index b0609dd..dd90e04 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -182,6 +182,44 @@
                 dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
     }
 
+    @Test
+    public void testChooseNavigationBackgroundWindow() {
+        final WindowState drawBarWin = createOpaqueFullscreen(false);
+        final WindowState nonDrawBarWin = createDimmingDialogWindow(true);
+
+        final WindowState visibleIme = createInputMethodWindow(true, true, false);
+        final WindowState invisibleIme = createInputMethodWindow(false, true, false);
+        final WindowState nonDrawBarIme = createInputMethodWindow(true, false, false);
+
+        assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow(
+                drawBarWin, null, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                null, null, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                nonDrawBarWin, null, NAV_BAR_BOTTOM));
+
+        assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow(
+                drawBarWin, visibleIme, NAV_BAR_BOTTOM));
+        assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow(
+                null, visibleIme, NAV_BAR_BOTTOM));
+        assertEquals(visibleIme, DisplayPolicy.chooseNavigationBackgroundWindow(
+                nonDrawBarWin, visibleIme, NAV_BAR_BOTTOM));
+
+        assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow(
+                drawBarWin, invisibleIme, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                null, invisibleIme, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                nonDrawBarWin, invisibleIme, NAV_BAR_BOTTOM));
+
+        assertEquals(drawBarWin, DisplayPolicy.chooseNavigationBackgroundWindow(
+                drawBarWin, nonDrawBarIme, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                null, nonDrawBarIme, NAV_BAR_BOTTOM));
+        assertNull(DisplayPolicy.chooseNavigationBackgroundWindow(
+                nonDrawBarWin, nonDrawBarIme, NAV_BAR_BOTTOM));
+    }
+
     @SetupWindows(addWindows = W_NAVIGATION_BAR)
     @Test
     public void testUpdateLightNavigationBarLw() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 8e015d4..769a309 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -103,8 +103,7 @@
     public void setUp() throws Exception {
         mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration;
         spyOn(mLetterboxConfiguration);
-        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                    /* checkDeviceConfig */ anyBoolean()))
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled())
                 .thenReturn(true);
         when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
                 .thenReturn(true);
@@ -177,8 +176,7 @@
 
     @Test
     public void testOnScreenRotationAnimationFinished_treatmentNotEnabled_doNotShowToast() {
-        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                    /* checkDeviceConfig */ anyBoolean()))
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled())
                 .thenReturn(false);
         spyOn(mDisplayRotationCompatPolicy);
 
@@ -238,8 +236,7 @@
 
     @Test
     public void testTreatmentNotEnabled_noForceRotationOrRefresh() throws Exception {
-        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                    /* checkDeviceConfig */ anyBoolean()))
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled())
                 .thenReturn(false);
 
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -253,8 +250,7 @@
 
     @Test
     public void testTreatmentDisabledViaDeviceConfig_noForceRotationOrRefresh() throws Exception {
-        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
-                    /* checkDeviceConfig */ true))
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled())
                 .thenReturn(false);
 
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
index d29b18f..b105703 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
@@ -31,7 +31,6 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 
 import android.platform.test.annotations.Presubmit;
 import android.view.Surface;
@@ -79,8 +78,11 @@
         when(mDisplayContent.getIgnoreOrientationRequest()).thenReturn(true);
 
         mMockLetterboxConfiguration = mock(LetterboxConfiguration.class);
-        when(mMockLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled(
-                /* checkDeviceConfig */ anyBoolean())).thenReturn(true);
+        when(mMockLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled())
+                .thenReturn(true);
+        when(mMockLetterboxConfiguration
+                .isDisplayRotationImmersiveAppCompatPolicyEnabledAtBuildTime())
+                    .thenReturn(true);
 
         mPolicy = DisplayRotationImmersiveAppCompatPolicy.createIfNeeded(
                 mMockLetterboxConfiguration, createDisplayRotationMock(),
@@ -204,8 +206,8 @@
 
     @Test
     public void testRotationChoiceEnforcedOnly_featureFlagDisabled_lockNotEnforced() {
-        when(mMockLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled(
-                /* checkDeviceConfig */ true)).thenReturn(false);
+        when(mMockLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled())
+                .thenReturn(false);
 
         assertIsRotationLockEnforcedReturnsFalseForAllRotations();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 2a8f0ffc..f757330 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -73,6 +73,7 @@
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.LocalServices;
 import com.android.server.UiThread;
@@ -879,6 +880,33 @@
                 SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
     }
 
+    @Test
+    public void sensorRotation_locked_halfFolded_configOff_rotationUnchanged() throws Exception {
+        mBuilder.setIsFoldable(true);
+        mBuilder.setSupportHalfFoldAutoRotateOverride(false);
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        enableOrientationSensor();
+
+        mTarget.foldStateChanged(DeviceStateController.DeviceState.OPEN);
+        freezeRotation(Surface.ROTATION_270);
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_0));
+        assertTrue(waitForUiHandler());
+        // No rotation...
+        assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+
+        // ... half-fold -> still no rotation
+        mTarget.foldStateChanged(DeviceStateController.DeviceState.HALF_FOLDED);
+        assertTrue(waitForUiHandler());
+        verify(sMockWm).updateRotation(false, false);
+        assertTrue(waitForUiHandler());
+        assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
     // =================================
     // Tests for Policy based Rotation
     // =================================
@@ -1016,7 +1044,7 @@
 
     @Test
     public void testSensorRotationAfterDisplayChangeBeforeTimeout_ignoresSensor() throws Exception {
-        mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+        mBuilder.setIsFoldable(true)
                 .setPauseRotationWhenUnfolding(true)
                 .setDisplaySwitchRotationBlockTimeMs(1000)
                 .build();
@@ -1034,7 +1062,7 @@
 
     @Test
     public void testSensorRotationAfterDisplayChangeAfterTimeout_usesSensor() throws Exception {
-        mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+        mBuilder.setIsFoldable(true)
                 .setPauseRotationWhenUnfolding(true)
                 .setDisplaySwitchRotationBlockTimeMs(1000)
                 .build();
@@ -1052,7 +1080,7 @@
 
     @Test
     public void testSensorRotationAfterHingeEventBeforeTimeout_ignoresSensor() throws Exception {
-        mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+        mBuilder.setIsFoldable(true)
                 .setPauseRotationWhenUnfolding(true)
                 .setMaxHingeAngle(165)
                 .setHingeAngleRotationBlockTimeMs(400)
@@ -1072,7 +1100,7 @@
     @Test
     public void testSensorRotationAfterHingeEventBeforeTimeoutFlagDisabled_usesSensorData()
             throws Exception {
-        mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+        mBuilder.setIsFoldable(true)
                 .setPauseRotationWhenUnfolding(false)
                 .setMaxHingeAngle(165)
                 .setHingeAngleRotationBlockTimeMs(400)
@@ -1091,7 +1119,7 @@
 
     @Test
     public void testSensorRotationAfterHingeEventAfterTimeout_usesSensorData() throws Exception {
-        mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+        mBuilder.setIsFoldable(true)
                 .setPauseRotationWhenUnfolding(true)
                 .setMaxHingeAngle(165)
                 .setHingeAngleRotationBlockTimeMs(400)
@@ -1111,7 +1139,7 @@
 
     @Test
     public void testSensorRotationAfterLargeHingeEventBeforeTimeout_usesSensor() throws Exception {
-        mBuilder.setSupportHalfFoldAutoRotateOverride(true)
+        mBuilder.setIsFoldable(true)
                 .setPauseRotationWhenUnfolding(true)
                 .setMaxHingeAngle(165)
                 .setHingeAngleRotationBlockTimeMs(400)
@@ -1228,6 +1256,7 @@
         private int mCarDockRotation;
         private int mDeskDockRotation;
         private int mUndockedHdmiRotation;
+        private boolean mIsFoldable;
 
         private DisplayRotationBuilder setIsDefaultDisplay(boolean isDefaultDisplay) {
             mIsDefaultDisplay = isDefaultDisplay;
@@ -1282,9 +1311,17 @@
             return this;
         }
 
+        private DisplayRotationBuilder setIsFoldable(boolean value) {
+            mIsFoldable = value;
+            return this;
+        }
+
         private DisplayRotationBuilder setSupportHalfFoldAutoRotateOverride(
                 boolean supportHalfFoldAutoRotateOverride) {
             mSupportHalfFoldAutoRotateOverride = supportHalfFoldAutoRotateOverride;
+            if (supportHalfFoldAutoRotateOverride) {
+                mIsFoldable = true;
+            }
             return this;
         }
 
@@ -1429,6 +1466,11 @@
             when(mMockContext.getResources().getBoolean(
                     com.android.internal.R.bool.config_windowManagerHalfFoldAutoRotateOverride))
                     .thenReturn(mSupportHalfFoldAutoRotateOverride);
+
+            when(mMockContext.getResources().getIntArray(
+                    R.array.config_foldedDeviceStates))
+                    .thenReturn(mIsFoldable ? new int[]{0} : new int[]{});
+
             mMockDisplayRotationReversionController =
                     mock(DisplayRotationReversionController.class);
             when(mMockDisplayContent.getRotationReversionController())
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index a8fc25f..994dcf1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -31,17 +31,12 @@
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.verify;
 
 import android.app.StatusBarManager;
@@ -268,8 +263,6 @@
         navBar.setHasSurface(true);
         navBarProvider.setServerVisible(true);
         final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
-        spyOn(policy);
-        doNothing().when(policy).startAnimation(anyBoolean(), any());
 
         // Make both system bars invisible.
         mAppWindow.setRequestedVisibleTypes(
@@ -305,8 +298,6 @@
         addNavigationBar().getControllableInsetProvider().setServerVisible(true);
 
         final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
-        spyOn(policy);
-        doNothing().when(policy).startAnimation(anyBoolean(), any());
         policy.updateBarControlTarget(mAppWindow);
         policy.showTransient(navigationBars() | statusBars(),
                 true /* isGestureOnSystemBar */);
@@ -341,8 +332,6 @@
         mAppWindow.mAboveInsetsState.addSource(navBarSource);
         mAppWindow.mAboveInsetsState.addSource(statusBarSource);
         final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
-        spyOn(policy);
-        doNothing().when(policy).startAnimation(anyBoolean(), any());
         policy.updateBarControlTarget(mAppWindow);
         policy.showTransient(navigationBars() | statusBars(),
                 true /* isGestureOnSystemBar */);
@@ -370,7 +359,7 @@
 
         mAppWindow.setRequestedVisibleTypes(
                 navigationBars() | statusBars(), navigationBars() | statusBars());
-        policy.onInsetsModified(mAppWindow);
+        policy.onRequestedVisibleTypesChanged(mAppWindow);
         waitUntilWindowAnimatorIdle();
 
         controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow);
@@ -390,8 +379,6 @@
         final WindowState app2 = addWindow(TYPE_APPLICATION, "app");
 
         final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
-        spyOn(policy);
-        doNothing().when(policy).startAnimation(anyBoolean(), any());
         policy.updateBarControlTarget(app);
         policy.showTransient(navigationBars() | statusBars(),
                 true /* isGestureOnSystemBar */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 3934b02..4034dbc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -62,28 +62,31 @@
     @Test
     public void testPostLayout() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+        statusBar.setBounds(0, 0, 500, 1000);
         statusBar.getFrame().set(0, 0, 500, 100);
         statusBar.mHasSurface = true;
         mProvider.setWindowContainer(statusBar, null, null);
         mProvider.updateSourceFrame(statusBar.getFrame());
         mProvider.onPostLayout();
         assertEquals(new Rect(0, 0, 500, 100), mProvider.getSource().getFrame());
-        assertEquals(Insets.of(0, 100, 0, 0),
-                mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
-                        false /* ignoreVisibility */));
-        assertEquals(Insets.of(0, 100, 0, 0),
-                mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500)));
+        assertEquals(Insets.of(0, 100, 0, 0), mProvider.getInsetsHint());
+
+        // Change the bounds and call onPostLayout. Make sure the insets hint gets updated.
+        statusBar.setBounds(0, 10, 500, 1000);
+        mProvider.onPostLayout();
+        assertEquals(Insets.of(0, 90, 0, 0), mProvider.getInsetsHint());
     }
 
     @Test
     public void testPostLayout_invisible() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+        statusBar.setBounds(0, 0, 500, 1000);
         statusBar.getFrame().set(0, 0, 500, 100);
         mProvider.setWindowContainer(statusBar, null, null);
         mProvider.updateSourceFrame(statusBar.getFrame());
         mProvider.onPostLayout();
-        assertEquals(Insets.NONE, mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
-                false /* ignoreVisibility */));
+        assertTrue(mProvider.getSource().getFrame().isEmpty());
+        assertEquals(Insets.NONE, mProvider.getInsetsHint());
     }
 
     @Test
@@ -160,6 +163,36 @@
     }
 
     @Test
+    public void testUpdateSourceFrame() {
+        final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+        mProvider.setWindowContainer(statusBar, null, null);
+        statusBar.setBounds(0, 0, 500, 1000);
+
+        mProvider.setServerVisible(true);
+        statusBar.getFrame().set(0, 0, 500, 100);
+        mProvider.updateSourceFrame(statusBar.getFrame());
+        assertEquals(statusBar.getFrame(), mProvider.getSource().getFrame());
+        assertEquals(Insets.of(0, 100, 0, 0), mProvider.getInsetsHint());
+
+        // Only change the source frame but not the visibility.
+        statusBar.getFrame().set(0, 0, 500, 90);
+        mProvider.updateSourceFrame(statusBar.getFrame());
+        assertEquals(statusBar.getFrame(), mProvider.getSource().getFrame());
+        assertEquals(Insets.of(0, 90, 0, 0), mProvider.getInsetsHint());
+
+        mProvider.setServerVisible(false);
+        statusBar.getFrame().set(0, 0, 500, 80);
+        mProvider.updateSourceFrame(statusBar.getFrame());
+        assertTrue(mProvider.getSource().getFrame().isEmpty());
+        assertEquals(Insets.of(0, 90, 0, 0), mProvider.getInsetsHint());
+
+        // Only change the visibility but not the frame.
+        mProvider.setServerVisible(true);
+        assertEquals(statusBar.getFrame(), mProvider.getSource().getFrame());
+        assertEquals(Insets.of(0, 80, 0, 0), mProvider.getInsetsHint());
+    }
+
+    @Test
     public void testUpdateSourceFrameForIme() {
         final WindowState inputMethod = createWindow(null, TYPE_INPUT_METHOD, "inputMethod");
 
@@ -184,7 +217,7 @@
     }
 
     @Test
-    public void testInsetsModified() {
+    public void testSetRequestedVisibleTypes() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         statusBar.getFrame().set(0, 0, 500, 100);
@@ -196,7 +229,7 @@
     }
 
     @Test
-    public void testInsetsModified_noControl() {
+    public void testSetRequestedVisibleTypes_noControl() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         statusBar.getFrame().set(0, 0, 500, 100);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index d7e736d..114796d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -192,17 +192,16 @@
         // This can be the IME z-order target while app cannot be the IME z-order target.
         // This is also the only IME control target in this test, so IME won't be invisible caused
         // by the control-target change.
-        mDisplayContent.updateImeInputAndControlTarget(
-                createWindow(null, TYPE_APPLICATION, "base"));
+        final WindowState base = createWindow(null, TYPE_APPLICATION, "base");
+        mDisplayContent.updateImeInputAndControlTarget(base);
 
         // Make IME and stay visible during the test.
         mImeWindow.setHasSurface(true);
         getController().getOrCreateSourceProvider(ID_IME, ime())
                 .setWindowContainer(mImeWindow, null, null);
-        getController().onImeControlTargetChanged(
-                mDisplayContent.getImeInputTarget().getWindowState());
-        mDisplayContent.getImeInputTarget().getWindowState().setRequestedVisibleTypes(ime(), ime());
-        getController().onInsetsModified(mDisplayContent.getImeInputTarget().getWindowState());
+        getController().onImeControlTargetChanged(base);
+        base.setRequestedVisibleTypes(ime(), ime());
+        getController().onRequestedVisibleTypesChanged(base);
 
         // Send our spy window (app) into the system so that we can detect the invocation.
         final WindowState win = createWindow(null, TYPE_APPLICATION, "app");
@@ -485,7 +484,7 @@
         mDisplayContent.updateImeInputAndControlTarget(app);
 
         app.setRequestedVisibleTypes(ime(), ime());
-        getController().onInsetsModified(app);
+        getController().onRequestedVisibleTypesChanged(app);
         assertTrue(ime.getControllableInsetProvider().getSource().isVisible());
 
         getController().updateAboveInsetsState(true /* notifyInsetsChange */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 7cb7c79d..2b19ad9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -107,6 +107,9 @@
                 InstrumentationRegistry.getInstrumentation().getContext().getCacheDir();
         mFolder = new File(cacheFolder, "launch_params_tests");
         deleteRecursively(mFolder);
+        mFolder.mkdir();
+        mUserFolderGetter.apply(TEST_USER_ID).mkdir();
+        mUserFolderGetter.apply(ALTERNATIVE_USER_ID).mkdir();
 
         mDisplayUniqueId = "test:" + sNextUniqueId++;
         mTestDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1500)
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationDeviceConfigTests.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationDeviceConfigTests.java
deleted file mode 100644
index 2b7a06b..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationDeviceConfigTests.java
+++ /dev/null
@@ -1,109 +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.server.wm;
-
-import static com.android.server.wm.LetterboxConfigurationDeviceConfig.sKeyToDefaultValueMap;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-import android.provider.DeviceConfig;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.modules.utils.testing.TestableDeviceConfig;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.util.Map;
-
-/**
- * Test class for {@link LetterboxConfigurationDeviceConfig}.
- *
- * atest WmTests:LetterboxConfigurationDeviceConfigTests
- */
-@SmallTest
-@Presubmit
-public class LetterboxConfigurationDeviceConfigTests {
-
-    private LetterboxConfigurationDeviceConfig mDeviceConfig;
-
-    @Rule
-    public final TestableDeviceConfig.TestableDeviceConfigRule
-            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
-
-    @Before
-    public void setUp() {
-        mDeviceConfig = new LetterboxConfigurationDeviceConfig(/* executor */ Runnable::run);
-    }
-
-    @Test
-    public void testGetFlag_flagIsActive_flagChanges() throws Throwable {
-        for (Map.Entry<String, Boolean> entry : sKeyToDefaultValueMap.entrySet()) {
-            testGetFlagForKey_flagIsActive_flagChanges(entry.getKey(), entry.getValue());
-        }
-    }
-
-    private void testGetFlagForKey_flagIsActive_flagChanges(final String key, boolean defaultValue)
-            throws InterruptedException {
-        mDeviceConfig.updateFlagActiveStatus(/* isActive */ true, key);
-
-        assertEquals("Unexpected default value for " + key,
-                mDeviceConfig.getFlag(key), defaultValue);
-
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
-                /* value */ Boolean.TRUE.toString(), /* makeDefault */ false);
-
-        assertTrue("Flag " + key + "is not true after change", mDeviceConfig.getFlag(key));
-
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
-                /* value */ Boolean.FALSE.toString(), /* makeDefault */ false);
-
-        assertFalse("Flag " + key + "is not false after change", mDeviceConfig.getFlag(key));
-    }
-
-    @Test
-    public void testGetFlag_flagIsNotActive_alwaysReturnDefaultValue() throws Throwable {
-        for (Map.Entry<String, Boolean> entry : sKeyToDefaultValueMap.entrySet()) {
-            testGetFlagForKey_flagIsNotActive_alwaysReturnDefaultValue(
-                    entry.getKey(), entry.getValue());
-        }
-    }
-
-    private void testGetFlagForKey_flagIsNotActive_alwaysReturnDefaultValue(final String key,
-            boolean defaultValue) throws InterruptedException {
-        assertEquals("Unexpected default value for " + key,
-                mDeviceConfig.getFlag(key), defaultValue);
-
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
-                /* value */ Boolean.TRUE.toString(), /* makeDefault */ false);
-
-        assertEquals("Flag " + key + "is not set to default after change",
-                mDeviceConfig.getFlag(key), defaultValue);
-
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
-                /* value */ Boolean.FALSE.toString(), /* makeDefault */ false);
-
-        assertEquals("Flag " + key + "is not set to default after change",
-                mDeviceConfig.getFlag(key), defaultValue);
-    }
-
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
index 51a7e74..06033c7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -20,7 +20,6 @@
 
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
-import static com.android.server.wm.LetterboxConfigurationPersister.LETTERBOX_CONFIGURATION_FILENAME;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -42,13 +41,26 @@
 import java.io.File;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
+/**
+ * Tests for the {@link LetterboxConfigurationPersister} class.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:LetterboxConfigurationPersisterTest
+ */
 @SmallTest
 @Presubmit
 public class LetterboxConfigurationPersisterTest {
 
     private static final long TIMEOUT = 2000L; // 2 secs
 
+    private static final int DEFAULT_REACHABILITY_TEST = -1;
+    private static final Supplier<Integer> DEFAULT_REACHABILITY_SUPPLIER_TEST =
+            () -> DEFAULT_REACHABILITY_TEST;
+
+    private static final String LETTERBOX_CONFIGURATION_TEST_FILENAME = "letterbox_config_test";
+
     private LetterboxConfigurationPersister mLetterboxConfigurationPersister;
     private Context mContext;
     private PersisterQueue mPersisterQueue;
@@ -62,7 +74,7 @@
         mConfigFolder = mContext.getFilesDir();
         mPersisterQueue = new PersisterQueue();
         mQueueState = new QueueState();
-        mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(mContext,
+        mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(
                 () -> mContext.getResources().getInteger(
                         R.integer.config_letterboxDefaultPositionForHorizontalReachability),
                 () -> mContext.getResources().getInteger(
@@ -72,7 +84,8 @@
                 () -> mContext.getResources().getInteger(
                         R.integer.config_letterboxDefaultPositionForTabletopModeReachability
                 ),
-                mConfigFolder, mPersisterQueue, mQueueState);
+                mConfigFolder, mPersisterQueue, mQueueState,
+                LETTERBOX_CONFIGURATION_TEST_FILENAME);
         mQueueListener = queueEmpty -> mQueueState.onItemAdded();
         mPersisterQueue.addListener(mQueueListener);
         mLetterboxConfigurationPersister.start();
@@ -127,8 +140,10 @@
     public void test_whenUpdatedWithNewValues_valuesAreReadAfterRestart() {
         final PersisterQueue firstPersisterQueue = new PersisterQueue();
         final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
-                mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(),
-                firstPersisterQueue, mQueueState);
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                mContext.getFilesDir(), firstPersisterQueue, mQueueState,
+                LETTERBOX_CONFIGURATION_TEST_FILENAME);
         firstPersister.start();
         firstPersister.setLetterboxPositionForHorizontalReachability(false,
                 LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
@@ -138,8 +153,10 @@
         stopPersisterSafe(firstPersisterQueue);
         final PersisterQueue secondPersisterQueue = new PersisterQueue();
         final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
-                mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(),
-                secondPersisterQueue, mQueueState);
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                mContext.getFilesDir(), secondPersisterQueue, mQueueState,
+                LETTERBOX_CONFIGURATION_TEST_FILENAME);
         secondPersister.start();
         final int newPositionForHorizontalReachability =
                 secondPersister.getLetterboxPositionForHorizontalReachability(false);
@@ -156,37 +173,46 @@
 
     @Test
     public void test_whenUpdatedWithNewValuesAndDeleted_valuesAreDefaults() {
-        mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(false,
+        final PersisterQueue firstPersisterQueue = new PersisterQueue();
+        final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                mContext.getFilesDir(), firstPersisterQueue, mQueueState,
+                LETTERBOX_CONFIGURATION_TEST_FILENAME);
+        firstPersister.start();
+        firstPersister.setLetterboxPositionForHorizontalReachability(false,
                 LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
-        mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(false,
+        firstPersister.setLetterboxPositionForVerticalReachability(false,
                 LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
         waitForCompletion(mPersisterQueue);
         final int newPositionForHorizontalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
-                        false);
+                firstPersister.getLetterboxPositionForHorizontalReachability(false);
         final int newPositionForVerticalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
+                firstPersister.getLetterboxPositionForVerticalReachability(false);
         Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
                 newPositionForHorizontalReachability);
         Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
                 newPositionForVerticalReachability);
-        deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
-        waitForCompletion(mPersisterQueue);
+        deleteConfiguration(firstPersister, firstPersisterQueue);
+        waitForCompletion(firstPersisterQueue);
+        stopPersisterSafe(firstPersisterQueue);
+
+        final PersisterQueue secondPersisterQueue = new PersisterQueue();
+        final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+                mContext.getFilesDir(), secondPersisterQueue, mQueueState,
+                LETTERBOX_CONFIGURATION_TEST_FILENAME);
+        secondPersister.start();
         final int positionForHorizontalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
-                        false);
-        final int defaultPositionForHorizontalReachability =
-                mContext.getResources().getInteger(
-                        R.integer.config_letterboxDefaultPositionForHorizontalReachability);
-        Assert.assertEquals(defaultPositionForHorizontalReachability,
-                positionForHorizontalReachability);
+                secondPersister.getLetterboxPositionForHorizontalReachability(false);
         final int positionForVerticalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
-        final int defaultPositionForVerticalReachability =
-                mContext.getResources().getInteger(
-                        R.integer.config_letterboxDefaultPositionForVerticalReachability);
-        Assert.assertEquals(defaultPositionForVerticalReachability,
-                positionForVerticalReachability);
+                secondPersister.getLetterboxPositionForVerticalReachability(false);
+        Assert.assertEquals(DEFAULT_REACHABILITY_TEST, positionForHorizontalReachability);
+        Assert.assertEquals(DEFAULT_REACHABILITY_TEST, positionForVerticalReachability);
+        deleteConfiguration(secondPersister, secondPersisterQueue);
+        waitForCompletion(secondPersisterQueue);
+        stopPersisterSafe(secondPersisterQueue);
     }
 
     private void stopPersisterSafe(PersisterQueue persisterQueue) {
@@ -222,7 +248,7 @@
     private void deleteConfiguration(LetterboxConfigurationPersister persister,
             PersisterQueue persisterQueue) {
         final AtomicFile fileToDelete = new AtomicFile(
-                new File(mConfigFolder, LETTERBOX_CONFIGURATION_FILENAME));
+                new File(mConfigFolder, LETTERBOX_CONFIGURATION_TEST_FILENAME));
         persisterQueue.addItem(
                 new DeleteFileCommand(fileToDelete, mQueueState.andThen(
                         s -> persister.useDefaultValue())), true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 7d507e9..2ad9fa0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -36,8 +36,12 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
@@ -46,6 +50,8 @@
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
 import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
 
@@ -62,7 +68,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.verify;
 
@@ -146,7 +151,9 @@
     @Test
     @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
     public void testShouldIgnoreRequestedOrientation_cameraCompatTreatment_returnsTrue() {
-        doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean());
+        doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled();
+        doReturn(true).when(mLetterboxConfiguration)
+                .isCameraCompatTreatmentEnabledAtBuildTime();
 
         // Recreate DisplayContent with DisplayRotationCompatPolicy
         mActivity = setUpActivityWithComponent();
@@ -305,7 +312,7 @@
     @Test
     public void testShouldRefreshActivityForCameraCompat_flagIsDisabled_returnsFalse() {
         doReturn(false).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
 
         assertFalse(mController.shouldRefreshActivityForCameraCompat());
     }
@@ -314,7 +321,7 @@
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
     public void testShouldRefreshActivityForCameraCompat_overrideEnabled_returnsFalse() {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
 
         assertFalse(mController.shouldRefreshActivityForCameraCompat());
     }
@@ -324,7 +331,7 @@
     public void testShouldRefreshActivityForCameraCompat_propertyIsTrueAndOverride_returnsFalse()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ true);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -336,7 +343,7 @@
     public void testShouldRefreshActivityForCameraCompat_propertyIsFalse_returnsFalse()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ false);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -348,7 +355,7 @@
     public void testShouldRefreshActivityForCameraCompat_propertyIsTrue_returnsTrue()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH, /* value */ true);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -362,7 +369,7 @@
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
     public void testShouldRefreshActivityViaPauseForCameraCompat_flagIsDisabled_returnsFalse() {
         doReturn(false).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
 
         assertFalse(mController.shouldRefreshActivityViaPauseForCameraCompat());
     }
@@ -371,7 +378,7 @@
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
     public void testShouldRefreshActivityViaPauseForCameraCompat_overrideEnabled_returnsTrue() {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
 
         assertTrue(mController.shouldRefreshActivityViaPauseForCameraCompat());
     }
@@ -381,7 +388,7 @@
     public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsFalseAndOverride_returnFalse()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, /* value */ false);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -393,7 +400,7 @@
     public void testShouldRefreshActivityViaPauseForCameraCompat_propertyIsTrue_returnsTrue()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, /* value */ true);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -406,7 +413,7 @@
     @Test
     public void testShouldForceRotateForCameraCompat_flagIsDisabled_returnsFalse() {
         doReturn(false).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
 
         assertFalse(mController.shouldForceRotateForCameraCompat());
     }
@@ -415,7 +422,7 @@
     @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
     public void testShouldForceRotateForCameraCompat_overrideEnabled_returnsFalse() {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
 
         assertFalse(mController.shouldForceRotateForCameraCompat());
     }
@@ -425,7 +432,7 @@
     public void testShouldForceRotateForCameraCompat_propertyIsTrueAndOverride_returnsFalse()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ true);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -437,7 +444,7 @@
     public void testShouldForceRotateForCameraCompat_propertyIsFalse_returnsFalse()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ false);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -449,7 +456,7 @@
     public void testShouldForceRotateForCameraCompat_propertyIsTrue_returnsTrue()
             throws Exception {
         doReturn(true).when(mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true);
+                .isCameraCompatTreatmentEnabled();
         mockThatProperty(PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION, /* value */ true);
 
         mController = new LetterboxUiController(mWm, mActivity);
@@ -461,7 +468,7 @@
     public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() {
         final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
                  WindowInsets.Type.navigationBars());
-        taskbar.setInsetsRoundedCornerFrame(true);
+        taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
         final Rect opaqueBounds = new Rect(0, 0, 500, 300);
         doReturn(opaqueBounds).when(mActivity).getBounds();
@@ -505,7 +512,7 @@
     public void testGetCropBoundsIfNeeded_appliesCrop() {
         final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
                 WindowInsets.Type.navigationBars());
-        taskbar.setInsetsRoundedCornerFrame(true);
+        taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
 
         // Apply crop if taskbar is expanded
@@ -528,7 +535,7 @@
     public void testGetCropBoundsIfNeeded_appliesCropWithSizeCompatScaling() {
         final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
                 WindowInsets.Type.navigationBars());
-        taskbar.setInsetsRoundedCornerFrame(true);
+        taskbar.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
         final float scaling = 2.0f;
 
@@ -740,7 +747,9 @@
     @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT,
             OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
     public void testOverrideOrientationIfNeeded_whenCameraNotActive_returnsUnchanged() {
-        doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean());
+        doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled();
+        doReturn(true).when(mLetterboxConfiguration)
+                .isCameraCompatTreatmentEnabledAtBuildTime();
 
         // Recreate DisplayContent with DisplayRotationCompatPolicy
         mActivity = setUpActivityWithComponent();
@@ -758,7 +767,9 @@
     @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT,
             OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
     public void testOverrideOrientationIfNeeded_whenCameraActive_returnsPortrait() {
-        doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean());
+        doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled();
+        doReturn(true).when(mLetterboxConfiguration)
+                .isCameraCompatTreatmentEnabledAtBuildTime();
 
         // Recreate DisplayContent with DisplayRotationCompatPolicy
         mActivity = setUpActivityWithComponent();
@@ -772,6 +783,136 @@
                 /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
     }
 
+    @Test
+    public void testOverrideOrientationIfNeeded_userFullscreenOverride_returnsUser() {
+        spyOn(mController);
+        doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
+
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_USER);
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION})
+    public void testOverrideOrientationIfNeeded_userFullScreenOverrideOverSystem_returnsUser() {
+        spyOn(mController);
+        doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
+
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_USER);
+    }
+
+    @Test
+    public void testOverrideOrientationIfNeeded_userFullScreenOverrideDisabled_returnsUnchanged() {
+        spyOn(mController);
+        doReturn(false).when(mController).shouldApplyUserFullscreenOverride();
+
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
+    }
+
+    // shouldApplyUser...Override
+    @Test
+    public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() throws Exception {
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+                /* value */ true);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+        doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
+
+        assertFalse(mController.shouldApplyUserFullscreenOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_falseFullscreenProperty_returnsFalse()
+            throws Exception {
+        prepareActivityThatShouldApplyUserFullscreenOverride();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+                /* value */ false);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldApplyUserFullscreenOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_falseSettingsProperty_returnsFalse()
+            throws Exception {
+        prepareActivityThatShouldApplyUserFullscreenOverride();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldApplyUserFullscreenOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_disabledIgnoreOrientationRequest() {
+        prepareActivityThatShouldApplyUserFullscreenOverride();
+        mDisplayContent.setIgnoreOrientationRequest(false);
+
+        assertFalse(mController.shouldApplyUserFullscreenOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_returnsTrue() {
+        prepareActivityThatShouldApplyUserFullscreenOverride();
+
+        assertTrue(mController.shouldApplyUserFullscreenOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse()
+            throws Exception {
+        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_trueProperty_returnsFalse()
+            throws Exception {
+        doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_disabledIgnoreOrientationRequest() {
+        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+        mDisplayContent.setIgnoreOrientationRequest(false);
+
+        assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() {
+        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+
+        assertTrue(mController.shouldApplyUserMinAspectRatioOverride());
+    }
+
+    private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() {
+        spyOn(mController);
+        doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
+        mDisplayContent.setIgnoreOrientationRequest(true);
+        doReturn(USER_MIN_ASPECT_RATIO_3_2).when(mController).getUserMinAspectRatioOverrideCode();
+    }
+
+    private void prepareActivityThatShouldApplyUserFullscreenOverride() {
+        spyOn(mController);
+        doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
+        mDisplayContent.setIgnoreOrientationRequest(true);
+        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mController)
+                .getUserMinAspectRatioOverrideCode();
+    }
+
     // shouldUseDisplayLandscapeNaturalOrientation
 
     @Test
@@ -1059,7 +1200,9 @@
     @Test
     public void testgetFixedOrientationLetterboxAspectRatio_splitScreenAspectEnabled() {
         doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration)
-                .isCameraCompatTreatmentEnabled(anyBoolean());
+                .isCameraCompatTreatmentEnabled();
+        doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration)
+                .isCameraCompatTreatmentEnabledAtBuildTime();
         doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration)
                 .isCameraCompatSplitScreenAspectRatioEnabled();
         doReturn(false).when(mActivity.mWmService.mLetterboxConfiguration)
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 0ccb0d0..df0808f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -49,10 +49,12 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 import static java.lang.Integer.MAX_VALUE;
 
@@ -1139,6 +1141,40 @@
     }
 
     @Test
+    public void addTask_taskAlreadyInRecentsMovedToTop_callsTaskNotificationController() {
+        final Task firstTask = createTaskBuilder(".Task").build();
+        final Task secondTask = createTaskBuilder(".Task2").build();
+
+        mRecentTasks.add(firstTask);
+        mRecentTasks.add(secondTask);
+
+        TaskChangeNotificationController controller =
+                mAtm.getTaskChangeNotificationController();
+        clearInvocations(controller);
+
+        // Add firstTask back to top
+        mRecentTasks.add(firstTask);
+        verify(controller).notifyTaskListUpdated();
+    }
+
+    @Test
+    public void addTask_taskAlreadyInRecentsOnTop_doesNotNotify() {
+        final Task firstTask = createTaskBuilder(".Task").build();
+        final Task secondTask = createTaskBuilder(".Task2").build();
+
+        mRecentTasks.add(firstTask);
+        mRecentTasks.add(secondTask);
+
+        TaskChangeNotificationController controller =
+                mAtm.getTaskChangeNotificationController();
+        clearInvocations(controller);
+
+        // Add secondTask to top again
+        mRecentTasks.add(secondTask);
+        verifyZeroInteractions(controller);
+    }
+
+    @Test
     public void removeTask_callsTaskNotificationController() {
         final Task task = createTaskBuilder(".Task").build();
 
@@ -1279,6 +1315,26 @@
         assertTrue(info.supportsMultiWindow);
     }
 
+    @Test
+    public void testRemoveCompatibleRecentTask() {
+        final Task task1 = createTaskBuilder(".Task").setWindowingMode(
+                WINDOWING_MODE_FULLSCREEN).build();
+        mRecentTasks.add(task1);
+        final Task task2 = createTaskBuilder(".Task").setWindowingMode(
+                WINDOWING_MODE_MULTI_WINDOW).build();
+        mRecentTasks.add(task2);
+        assertEquals(2, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
+                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
+
+        // Set windowing mode and ensure the same fullscreen task that created earlier is removed.
+        task2.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        mRecentTasks.removeCompatibleRecentTask(task2);
+        assertEquals(1, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
+                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
+        assertEquals(task2.mTaskId, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
+                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().get(0).taskId);
+    }
+
     private TaskSnapshot createSnapshot(Point taskSize, Point bufferSize) {
         HardwareBuffer buffer = null;
         if (bufferSize != null) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index abf21a5..7eab06a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -656,7 +656,7 @@
                 topSplitPrimary.getVisibility(null /* starting */));
         // Make primary split root transient-hide.
         spyOn(splitPrimary.mTransitionController);
-        doReturn(true).when(splitPrimary.mTransitionController).isTransientHide(
+        doReturn(true).when(splitPrimary.mTransitionController).isTransientVisible(
                 organizer.mPrimary);
         // The split root and its top become visible.
         assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 6a89b55..d5afe3b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -27,9 +27,15 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
+import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
@@ -90,9 +96,12 @@
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
@@ -2254,6 +2263,169 @@
     }
 
     @Test
+    public void testUserOverrideSplitScreenAspectRatioForLandscapeDisplay() {
+        final int displayWidth = 1600;
+        final int displayHeight = 1400;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+
+        float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
+
+        testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN);
+    }
+
+    @Test
+    public void testUserOverrideSplitScreenAspectRatioForPortraitDisplay() {
+        final int displayWidth = 1400;
+        final int displayHeight = 1600;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+
+        float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
+
+        testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN);
+    }
+
+    @Test
+    public void testUserOverrideDisplaySizeAspectRatioForLandscapeDisplay() {
+        final int displayWidth = 1600;
+        final int displayHeight = 1400;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+
+        float expectedAspectRatio = 1f * displayWidth / displayHeight;
+
+        testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE);
+    }
+
+    @Test
+    public void testUserOverrideDisplaySizeAspectRatioForPortraitDisplay() {
+        final int displayWidth = 1400;
+        final int displayHeight = 1600;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+
+        float expectedAspectRatio = 1f * displayHeight / displayWidth;
+
+        testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE);
+    }
+
+    @Test
+    public void testUserOverride32AspectRatioForPortraitDisplay() {
+        setUpDisplaySizeWithApp(/* dw */ 1400, /* dh */ 1600);
+        testUserOverrideAspectRatio(3 / 2f, USER_MIN_ASPECT_RATIO_3_2);
+    }
+
+    @Test
+    public void testUserOverride32AspectRatioForLandscapeDisplay() {
+        setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);
+        testUserOverrideAspectRatio(3 / 2f, USER_MIN_ASPECT_RATIO_3_2);
+    }
+
+    @Test
+    public void testUserOverride43AspectRatioForPortraitDisplay() {
+        setUpDisplaySizeWithApp(/* dw */ 1400, /* dh */ 1600);
+        testUserOverrideAspectRatio(4 / 3f, USER_MIN_ASPECT_RATIO_4_3);
+    }
+
+    @Test
+    public void testUserOverride43AspectRatioForLandscapeDisplay() {
+        setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);
+        testUserOverrideAspectRatio(4 / 3f, USER_MIN_ASPECT_RATIO_4_3);
+    }
+
+    @Test
+    public void testUserOverride169AspectRatioForPortraitDisplay() {
+        setUpDisplaySizeWithApp(/* dw */ 1800, /* dh */ 1500);
+        testUserOverrideAspectRatio(16 / 9f, USER_MIN_ASPECT_RATIO_16_9);
+    }
+
+    @Test
+    public void testUserOverride169AspectRatioForLandscapeDisplay() {
+        setUpDisplaySizeWithApp(/* dw */ 1500, /* dh */ 1800);
+        testUserOverrideAspectRatio(16 / 9f, USER_MIN_ASPECT_RATIO_16_9);
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
+    public void testUserOverrideAspectRatioOverSystemOverride() {
+        setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);
+
+        testUserOverrideAspectRatio(false,
+                SCREEN_ORIENTATION_PORTRAIT,
+                3 / 2f,
+                USER_MIN_ASPECT_RATIO_3_2,
+                true);
+    }
+
+    @Test
+    public void testUserOverrideAspectRatioNotEnabled() {
+        setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);
+
+        // App aspect ratio doesn't change
+        testUserOverrideAspectRatio(false,
+                SCREEN_ORIENTATION_PORTRAIT,
+                1f * 1600 / 1400,
+                USER_MIN_ASPECT_RATIO_3_2,
+                false);
+    }
+
+    private void testUserOverrideAspectRatio(float expectedAspectRatio,
+            @PackageManager.UserMinAspectRatio int aspectRatio) {
+        testUserOverrideAspectRatio(true,
+                SCREEN_ORIENTATION_PORTRAIT,
+                expectedAspectRatio,
+                aspectRatio,
+                true);
+
+        testUserOverrideAspectRatio(false,
+                SCREEN_ORIENTATION_PORTRAIT,
+                expectedAspectRatio,
+                aspectRatio,
+                true);
+
+        testUserOverrideAspectRatio(true,
+                SCREEN_ORIENTATION_LANDSCAPE,
+                expectedAspectRatio,
+                aspectRatio,
+                true);
+
+        testUserOverrideAspectRatio(false,
+                SCREEN_ORIENTATION_LANDSCAPE,
+                expectedAspectRatio,
+                aspectRatio,
+                true);
+    }
+
+    private void testUserOverrideAspectRatio(boolean isUnresizable, int screenOrientation,
+            float expectedAspectRatio, @PackageManager.UserMinAspectRatio int aspectRatio,
+            boolean enabled) {
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setTask(mTask)
+                .setComponent(ComponentName.createRelative(mContext,
+                        SizeCompatTests.class.getName()))
+                .setUid(android.os.Process.myUid())
+                .build();
+        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        activity.mWmService.mLetterboxConfiguration
+                .setUserAppAspectRatioSettingsOverrideEnabled(enabled);
+        // Set user aspect ratio override
+        final IPackageManager pm = mAtm.getPackageManager();
+        try {
+            doReturn(aspectRatio).when(pm)
+                    .getUserMinAspectRatio(activity.packageName, activity.mUserId);
+        } catch (RemoteException ignored) {
+        }
+
+        prepareLimitedBounds(activity, screenOrientation, isUnresizable);
+
+        final Rect afterBounds = activity.getBounds();
+        final int width = afterBounds.width();
+        final int height = afterBounds.height();
+        final float afterAspectRatio =
+                (float) Math.max(width, height) / (float) Math.min(width, height);
+
+        assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+    }
+
+    @Test
     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
     public void testOverrideSplitScreenAspectRatioForUnresizablePortraitApps() {
@@ -3548,7 +3720,7 @@
 
         final InsetsSource navSource = new InsetsSource(
                 InsetsSource.createId(null, 0, navigationBars()), navigationBars());
-        navSource.setInsetsRoundedCornerFrame(true);
+        navSource.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
 
         mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15);
@@ -3596,7 +3768,7 @@
 
         final InsetsSource navSource = new InsetsSource(
                 InsetsSource.createId(null, 0, navigationBars()), navigationBars());
-        navSource.setInsetsRoundedCornerFrame(true);
+        navSource.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
         // Immersive activity has transient navbar
         navSource.setVisible(!immersive);
         navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index fc5e9ca..810cbe8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -40,9 +40,7 @@
 import static org.mockito.Mockito.spy;
 
 import android.platform.test.annotations.Presubmit;
-import android.util.MergedConfiguration;
 import android.view.SurfaceControl;
-import android.window.ClientWindowFrames;
 
 import androidx.test.filters.SmallTest;
 
@@ -333,8 +331,7 @@
         w.reparent(botChildWC, POSITION_TOP);
         parentWC.prepareSync();
         // Assume the window has drawn with the latest configuration.
-        w.fillClientWindowFramesAndConfiguration(new ClientWindowFrames(),
-                new MergedConfiguration(), true /* useLatestConfig */, true /* relayoutVisible */);
+        makeLastConfigReportedToClient(w, true /* visible */);
         assertTrue(w.onSyncFinishedDrawing());
         assertEquals(SYNC_STATE_READY, w.mSyncState);
         w.reparent(topChildWC, POSITION_TOP);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SynchedDeviceConfigTests.java b/services/tests/wmtests/src/com/android/server/wm/SynchedDeviceConfigTests.java
new file mode 100644
index 0000000..7d44e11
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/SynchedDeviceConfigTests.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+
+import android.app.ActivityThread;
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.testing.TestableDeviceConfig;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test class for {@link SynchedDeviceConfig}.
+ *
+ * atest WmTests:SynchedDeviceConfigTests
+ */
+@SmallTest
+@Presubmit
+public class SynchedDeviceConfigTests {
+
+    private static final long WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec
+    private static final String NAMESPACE_FOR_TEST = "TestingNameSpace";
+
+    private SynchedDeviceConfig mDeviceConfig;
+
+    private Executor mExecutor;
+
+    @Rule
+    public final TestableDeviceConfig.TestableDeviceConfigRule
+            mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+    @Before
+    public void setUp() {
+        mExecutor = Objects.requireNonNull(ActivityThread.currentApplication()).getMainExecutor();
+        mDeviceConfig = SynchedDeviceConfig
+                .builder(/* nameSpace */ NAMESPACE_FOR_TEST, /* executor */ mExecutor)
+                .addDeviceConfigEntry(/* key */ "key1", /* default */ true, /* enabled */ true)
+                .addDeviceConfigEntry(/* key */ "key2", /* default */ false, /* enabled */ true)
+                .addDeviceConfigEntry(/* key */ "key3",  /* default */ true, /* enabled */ false)
+                .addDeviceConfigEntry(/* key */ "key4",  /* default */ false, /* enabled */ false)
+                .build();
+    }
+
+    @After
+    public void tearDown() {
+        DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfig);
+    }
+
+    @Test
+    public void testWhenStarted_initialValuesAreDefaultOrFalseIfDisabled() {
+        assertFlagValue(/* key */ "key1", /* expected */ true); // enabled
+        assertFlagValue(/* key */ "key2", /* expected */ false); // enabled
+        assertFlagValue(/* key */ "key3", /* expected */ false); // disabled
+        assertFlagValue(/* key */ "key4", /* expected */ false); // disabled
+    }
+
+    @Test
+    public void testIsEnabled() {
+        assertFlagEnabled(/* key */ "key1", /* expected */ true);
+        assertFlagEnabled(/* key */ "key2", /* expected */ true);
+        assertFlagEnabled(/* key */ "key3", /* expected */ false);
+        assertFlagEnabled(/* key */ "key4", /* expected */ false);
+    }
+
+    @Test
+    public void testWhenUpdated_onlyEnabledChanges() {
+        final CountDownLatch countDownLatch = new CountDownLatch(4);
+        spyOn(mDeviceConfig);
+        doAnswer(invocation -> {
+            invocation.callRealMethod();
+            countDownLatch.countDown();
+            return null;
+        }).when(mDeviceConfig).onPropertiesChanged(any());
+        try {
+            // We update all the keys
+            updateProperty(/* key */ "key1", /* value */ false);
+            updateProperty(/* key */ "key2", /* value */ true);
+            updateProperty(/* key */ "key3", /* value */ false);
+            updateProperty(/* key */ "key4", /* value */ true);
+
+            assertThat(countDownLatch.await(
+                    WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+
+            // We update all the flags but only the enabled ones change
+            assertFlagValue(/* key */ "key1", /* expected */ false); // changes
+            assertFlagValue(/* key */ "key2", /* expected */ true); // changes
+            assertFlagValue(/* key */ "key3", /* expected */ false); // disabled
+            assertFlagValue(/* key */ "key4", /* expected */ false); // disabled
+        } catch (InterruptedException e) {
+            Assert.fail(e.getMessage());
+        }
+    }
+
+    private void assertFlagValue(String key, boolean expectedValue) {
+        assertEquals(/* message */"Flag " + key + " value is not " + expectedValue, /* expected */
+                expectedValue, /* actual */ mDeviceConfig.getFlagValue(key));
+    }
+
+
+    private void assertFlagEnabled(String key, boolean expectedValue) {
+        assertEquals(/* message */
+                "Flag " + key + " enabled is not " + expectedValue, /* expected */
+                expectedValue, /* actual */ mDeviceConfig.isBuildTimeFlagEnabled(key));
+    }
+
+    private void updateProperty(String key, Boolean value) {
+        DeviceConfig.setProperty(NAMESPACE_FOR_TEST, key, /* value */
+                value.toString(), /* makeDefault */ false);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 99ab715..54b9351 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -26,6 +26,7 @@
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
 import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
@@ -1590,6 +1591,46 @@
         assertEquals(taskFragmentBounds, mTaskFragment.getBounds());
     }
 
+    @Test
+    public void testApplyTransaction_reorderTaskFragmentToFront() {
+        final Task task = createTask(mDisplayContent);
+        // Create a TaskFragment.
+        final IBinder token0 = new Binder();
+        final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .setFragmentToken(token0)
+                .setOrganizer(mOrganizer)
+                .createActivityCount(1)
+                .build();
+        // Create another TaskFragment
+        final IBinder token1 = new Binder();
+        final TaskFragment tf1 = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .setFragmentToken(token1)
+                .setOrganizer(mOrganizer)
+                .createActivityCount(1)
+                .build();
+        // Create a non-embedded Activity on top.
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+                .setTask(task)
+                .build();
+        mWindowOrganizerController.mLaunchTaskFragments.put(token0, tf0);
+        mWindowOrganizerController.mLaunchTaskFragments.put(token1, tf1);
+
+        // Reorder TaskFragment to front
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_REORDER_TO_FRONT).build();
+        mTransaction.addTaskFragmentOperation(token0, operation);
+        assertApplyTransactionAllowed(mTransaction);
+
+        // Ensure the non-embedded activity still on top.
+        assertEquals(topActivity, task.getTopChild().asActivityRecord());
+
+        // Ensure the TaskFragment is moved to front.
+        final TaskFragment frontMostTaskFragment = task.getTaskFragment(tf -> tf.asTask() == null);
+        assertEquals(frontMostTaskFragment, tf0);
+    }
+
     /**
      * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls
      * {@link WindowOrganizerController#applyTransaction(WindowContainerTransaction)} to apply the
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 9d597b1..6a9bb6c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -653,4 +653,23 @@
         assertEquals(mDisplayContent.getImeContainer().getParent().getSurfaceControl(),
                 mDisplayContent.computeImeParent());
     }
+
+    @Test
+    public void testIsolatedNavigation() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .createActivityCount(1)
+                .setOrganizer(mOrganizer)
+                .setFragmentToken(new Binder())
+                .build();
+
+        // Cannot be isolated if not embedded.
+        task.setIsolatedNav(true);
+        assertFalse(task.isIsolatedNav());
+
+        // Ensure the TaskFragment is isolated once set.
+        tf0.setIsolatedNav(true);
+        assertTrue(tf0.isIsolatedNav());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 28241d3..f332b69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -563,6 +563,36 @@
         assertEquals(freeformBounds, task.getBounds());
     }
 
+    @Test
+    public void testTopActivityEligibleForUserAspectRatioButton() {
+        DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay();
+        final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build();
+        final Task task = rootTask.getBottomMostTask();
+        final ActivityRecord root = task.getTopNonFinishingActivity();
+        spyOn(mWm.mLetterboxConfiguration);
+
+        // When device config flag is disabled the button is not enabled
+        doReturn(false).when(mWm.mLetterboxConfiguration)
+                .isUserAppAspectRatioSettingsEnabled();
+        doReturn(false).when(mWm.mLetterboxConfiguration)
+                .isTranslucentLetterboxingEnabled();
+        assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
+
+        // The flag is enabled
+        doReturn(true).when(mWm.mLetterboxConfiguration)
+                .isUserAppAspectRatioSettingsEnabled();
+        spyOn(root);
+        doReturn(task).when(root).getOrganizedTask();
+        // When the flag is enabled and the top activity is not in size compat mode.
+        doReturn(false).when(root).inSizeCompatMode();
+        assertTrue(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
+
+        // When in size compat mode the button is not enabled
+        doReturn(true).when(root).inSizeCompatMode();
+        assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
+    }
+
     /**
      * Tests that a task with forced orientation has orientation-consistent bounds within the
      * parent.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 7e4a9de..45ecc3f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -30,30 +30,18 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.Context;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManagerGlobal;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 
 import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
 
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
 class TestDisplayContent extends DisplayContent {
 
     public static final int DEFAULT_LOGICAL_DISPLAY_DENSITY = 300;
@@ -89,6 +77,10 @@
         spyOn(inputMonitor);
         doNothing().when(inputMonitor).resumeDispatchingLw(any());
 
+        final InsetsPolicy insetsPolicy = getInsetsPolicy();
+        WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget());
+        WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getTransientControlTarget());
+
         // For devices that set the sysprop ro.bootanim.set_orientation_<display_id>
         // See DisplayRotation#readDefaultDisplayRotation for context.
         // Without that, meaning of height and width in context of the tests can be swapped if
@@ -105,13 +97,8 @@
         private boolean mSystemDecorations = false;
         private int mStatusBarHeight = 0;
         private SettingsEntry mOverrideSettings;
-        private DisplayMetrics mDisplayMetrics;
         @NonNull
         private DeviceStateController mDeviceStateController = mock(DeviceStateController.class);
-        @Mock
-        Context mMockContext;
-        @Mock
-        Resources mResources;
 
         Builder(ActivityTaskManagerService service, int width, int height) {
             mService = service;
@@ -124,8 +111,6 @@
             // Set unique ID so physical display overrides are not inheritted from
             // DisplayWindowSettings.
             mInfo.uniqueId = generateUniqueId();
-            mDisplayMetrics = new DisplayMetrics();
-            updateDisplayMetrics();
         }
         Builder(ActivityTaskManagerService service, DisplayInfo info) {
             mService = service;
@@ -195,31 +180,7 @@
             mInfo.logicalDensityDpi = dpi;
             return this;
         }
-        Builder updateDisplayMetrics() {
-            mInfo.getAppMetrics(mDisplayMetrics);
-            return this;
-        }
-        Builder setDefaultMinTaskSizeDp(int valueDp) {
-            MockitoAnnotations.initMocks(this);
-            doReturn(mMockContext).when(mService.mContext).createConfigurationContext(any());
-            doReturn(mResources).when(mMockContext).getResources();
-            doAnswer(
-                    new Answer() {
-                        @Override
-                        public Object answer(InvocationOnMock i) {
-                            Object[] args = i.getArguments();
-                            TypedValue v = (TypedValue) args[1];
-                            v.type = TypedValue.TYPE_DIMENSION;
-                            v.data = TypedValue.createComplexDimension(valueDp,
-                                    TypedValue.COMPLEX_UNIT_DIP);
-                            return null;
-                        }
-                    }
-            ).when(mResources).getValue(
-                    eq(com.android.internal.R.dimen.default_minimal_size_resizable_task),
-                    any(TypedValue.class), eq(true));
-            return this;
-        }
+
         Builder setDeviceStateController(@NonNull DeviceStateController deviceStateController) {
             mDeviceStateController = deviceStateController;
             return this;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index d4fabf4..b4f1176 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -46,7 +46,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static org.junit.Assert.assertEquals;
@@ -1240,6 +1239,27 @@
     }
 
     @Test
+    public void testFinishRotationControllerWithFixedRotation() {
+        final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        mDisplayContent.setFixedRotationLaunchingAppUnchecked(app);
+        registerTestTransitionPlayer();
+        mDisplayContent.setLastHasContent();
+        mDisplayContent.requestChangeTransitionIfNeeded(1 /* changes */, null /* displayChange */);
+        assertNotNull(mDisplayContent.getAsyncRotationController());
+        mDisplayContent.setFixedRotationLaunchingAppUnchecked(null);
+        assertNull("Clear rotation controller if rotation is not changed",
+                mDisplayContent.getAsyncRotationController());
+
+        mDisplayContent.setFixedRotationLaunchingAppUnchecked(app);
+        assertNotNull(mDisplayContent.getAsyncRotationController());
+        mDisplayContent.getDisplayRotation().setRotation(
+                mDisplayContent.getWindowConfiguration().getRotation() + 1);
+        mDisplayContent.setFixedRotationLaunchingAppUnchecked(null);
+        assertNotNull("Keep rotation controller if rotation will be changed",
+                mDisplayContent.getAsyncRotationController());
+    }
+
+    @Test
     public void testDeferRotationForTransientLaunch() {
         final TestTransitionPlayer player = registerTestTransitionPlayer();
         assumeFalse(mDisplayContent.mTransitionController.useShellTransitionsRotation());
@@ -1362,8 +1382,6 @@
     @Test
     public void testTransientLaunch() {
         spyOn(mWm.mSnapshotController.mTaskSnapshotController);
-        mWm.mSnapshotController.registerTransitionStateConsumer(TASK_CLOSE,
-                mWm.mSnapshotController.mTaskSnapshotController::handleTaskClose);
         final ArrayList<ActivityRecord> enteringAnimReports = new ArrayList<>();
         final TransitionController controller = new TestTransitionController(mAtm) {
             @Override
@@ -1484,6 +1502,47 @@
     }
 
     @Test
+    public void testIsTransientVisible() {
+        final ActivityRecord appB = new ActivityBuilder(mAtm).setCreateTask(true)
+                .setVisible(false).build();
+        final ActivityRecord recent = new ActivityBuilder(mAtm).setCreateTask(true)
+                .setVisible(false).build();
+        final ActivityRecord appA = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Task taskA = appA.getTask();
+        final Task taskB = appB.getTask();
+        final Task taskRecent = recent.getTask();
+        registerTestTransitionPlayer();
+        final TransitionController controller = mRootWindowContainer.mTransitionController;
+        final Transition transition = createTestTransition(TRANSIT_OPEN, controller);
+        controller.moveToCollecting(transition);
+        transition.collect(recent);
+        transition.collect(taskA);
+        transition.setTransientLaunch(recent, taskA);
+        taskRecent.moveToFront("move-recent-to-front");
+
+        // During collecting and playing, the recent is on top so it is visible naturally.
+        // While B needs isTransientVisible to keep visibility because it is occluded by recents.
+        assertFalse(controller.isTransientVisible(taskB));
+        assertTrue(controller.isTransientVisible(taskA));
+        assertFalse(controller.isTransientVisible(taskRecent));
+        // Switch to playing state.
+        transition.onTransactionReady(transition.getSyncId(), mMockT);
+        assertTrue(controller.isTransientVisible(taskA));
+
+        // Switch to another task. For example, use gesture navigation to switch tasks.
+        taskB.moveToFront("move-b-to-front");
+        // The previous app (taskA) should be paused first so it loses transient visible. Because
+        // visually it is taskA -> taskB, the pause -> resume order should be the same.
+        assertFalse(controller.isTransientVisible(taskA));
+        // Keep the recent visible so there won't be 2 activities pausing at the same time. It is
+        // to avoid the latency to resume the current top, i.e. appB.
+        assertTrue(controller.isTransientVisible(taskRecent));
+        // The recent is paused after the transient transition is finished.
+        controller.finishTransition(transition);
+        assertFalse(controller.isTransientVisible(taskRecent));
+    }
+
+    @Test
     public void testNotReadyPushPop() {
         final TransitionController controller = new TestTransitionController(mAtm);
         controller.setSyncEngine(mWm.mSyncEngine);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
new file mode 100644
index 0000000..df11a44
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
+import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.TrustedPresentationThresholds;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * TODO (b/287076178): Move these tests to
+ * {@link android.view.surfacecontrol.cts.TrustedPresentationCallbackTest} when API is made public
+ */
+@Presubmit
+public class TrustedPresentationCallbackTest {
+    private static final String TAG = "TrustedPresentationCallbackTest";
+    private static final int STABILITY_REQUIREMENT_MS = 500;
+    private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 2000L;
+
+    private static final float FRACTION_VISIBLE = 0.1f;
+
+    @Rule
+    public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule(
+            TestActivity.class);
+
+    private TestActivity mActivity;
+
+    @Before
+    public void setup() {
+        mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
+    }
+
+    @Test
+    public void testAddTrustedPresentationListenerOnWindow() throws InterruptedException {
+        boolean[] results = new boolean[1];
+        CountDownLatch receivedResults = new CountDownLatch(1);
+        TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
+                1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
+                Runnable::run, inTrustedPresentationState -> {
+                    Log.d(TAG, "onTrustedPresentationChanged " + inTrustedPresentationState);
+                    results[0] = inTrustedPresentationState;
+                    receivedResults.countDown();
+                });
+        t.apply();
+
+        assertTrue("Timed out waiting for results",
+                receivedResults.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        assertTrue(results[0]);
+    }
+
+    @Test
+    public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException {
+        final Object resultsLock = new Object();
+        boolean[] results = new boolean[1];
+        boolean[] receivedResults = new boolean[1];
+        TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
+                1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
+        Consumer<Boolean> trustedPresentationCallback = inTrustedPresentationState -> {
+            synchronized (resultsLock) {
+                results[0] = inTrustedPresentationState;
+                receivedResults[0] = true;
+                resultsLock.notify();
+            }
+        };
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
+                Runnable::run, trustedPresentationCallback);
+        t.apply();
+
+        synchronized (resultsLock) {
+            if (!receivedResults[0]) {
+                resultsLock.wait(WAIT_TIME_MS);
+            }
+            // Make sure we received the results and not just timed out
+            assertTrue("Timed out waiting for results", receivedResults[0]);
+            assertTrue(results[0]);
+
+            // reset the state
+            receivedResults[0] = false;
+        }
+
+        mActivity.getWindow().getRootSurfaceControl().removeTrustedPresentationCallback(t,
+                trustedPresentationCallback);
+        t.apply();
+
+        synchronized (resultsLock) {
+            if (!receivedResults[0]) {
+                resultsLock.wait(WAIT_TIME_MS);
+            }
+            // Ensure we waited the full time and never received a notify on the result from the
+            // callback.
+            assertFalse("Should never have received a callback", receivedResults[0]);
+            // results shouldn't have changed.
+            assertTrue(results[0]);
+        }
+    }
+
+    public static class TestActivity extends Activity {
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 4afcd05..e3d1b9c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -52,7 +52,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.util.MergedConfiguration;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 import android.view.DisplayShape;
@@ -63,7 +62,6 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
-import android.window.ClientWindowFrames;
 
 import androidx.test.filters.SmallTest;
 
@@ -380,8 +378,7 @@
         wallpaperWindow.mLayoutSeq = mDisplayContent.mLayoutSeq;
         // Assume the token was invisible and the latest config was reported.
         wallpaperToken.commitVisibility(false);
-        wallpaperWindow.fillClientWindowFramesAndConfiguration(new ClientWindowFrames(),
-                new MergedConfiguration(), true /* useLatestConfig */, false /* relayoutVisible */);
+        makeLastConfigReportedToClient(wallpaperWindow, false /* visible */);
         assertTrue(wallpaperWindow.isLastConfigReportedToClient());
 
         final Rect bounds = wallpaperToken.getBounds();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 2d8ddfa..a82459f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -64,10 +64,19 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.clearInvocations;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.os.Binder;
+import android.os.DeadObjectException;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.platform.test.annotations.Presubmit;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
@@ -87,8 +96,10 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
+import java.io.FileDescriptor;
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.NoSuchElementException;
 
 
 /**
@@ -1404,7 +1415,7 @@
     }
 
     @Test
-    public void testAddLocalInsetsSourceProvider() {
+    public void testAddLocalInsetsFrameProvider() {
          /*
                 ___ rootTask _______________________________________________
                |        |                |                                  |
@@ -1435,19 +1446,20 @@
                 TYPE_BASE_APPLICATION);
         attrs2.setTitle("AppWindow2");
         activity2.addWindow(createWindowState(attrs2, activity2));
+        final Binder owner = new Binder();
         Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
         Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200);
         final InsetsFrameProvider provider1 =
-                new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect1);
         final InsetsFrameProvider provider2 =
-                new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 2, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect2);
         final int sourceId1 = provider1.getId();
         final int sourceId2 = provider2.getId();
 
-        rootTask.addLocalInsetsFrameProvider(provider1);
-        container.addLocalInsetsFrameProvider(provider2);
+        rootTask.addLocalInsetsFrameProvider(provider1, owner);
+        container.addLocalInsetsFrameProvider(provider2, owner);
 
         InsetsSource genericOverlayInsetsProvider1Source = new InsetsSource(
                 sourceId1, systemOverlays());
@@ -1479,7 +1491,7 @@
     }
 
     @Test
-    public void testAddLocalInsetsSourceProvider_sameType_replacesInsets() {
+    public void testAddLocalInsetsFrameProvider_sameType_replacesInsets() {
          /*
                 ___ rootTask ________________________________________
                |                  |                                  |
@@ -1494,24 +1506,25 @@
         attrs.setTitle("AppWindow0");
         activity0.addWindow(createWindowState(attrs, activity0));
 
+        final Binder owner = new Binder();
         final Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
         final Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200);
         final InsetsFrameProvider provider1 =
-                new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect1);
         final InsetsFrameProvider provider2 =
-                new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect2);
         final int sourceId1 = provider1.getId();
         final int sourceId2 = provider2.getId();
 
-        rootTask.addLocalInsetsFrameProvider(provider1);
+        rootTask.addLocalInsetsFrameProvider(provider1, owner);
         activity0.forAllWindows(window -> {
             assertEquals(genericOverlayInsetsRect1,
                     window.getInsetsState().peekSource(sourceId1).getFrame());
         }, true);
 
-        rootTask.addLocalInsetsFrameProvider(provider2);
+        rootTask.addLocalInsetsFrameProvider(provider2, owner);
 
         activity0.forAllWindows(window -> {
             assertEquals(genericOverlayInsetsRect2,
@@ -1520,7 +1533,7 @@
     }
 
     @Test
-    public void testRemoveLocalInsetsSourceProvider() {
+    public void testRemoveLocalInsetsFrameProvider() {
          /*
                 ___ rootTask _______________________________________________
                |        |                |                                  |
@@ -1554,21 +1567,22 @@
 
         activity2.addWindow(createWindowState(attrs2, activity2));
 
+        final Binder owner = new Binder();
         final Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700);
         final Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
         final InsetsFrameProvider provider1 =
-                new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 1, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(navigationBarInsetsRect1);
         final InsetsFrameProvider provider2 =
-                new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays())
+                new InsetsFrameProvider(owner, 2, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(navigationBarInsetsRect2);
         final int sourceId1 = provider1.getId();
         final int sourceId2 = provider2.getId();
 
-        rootTask.addLocalInsetsFrameProvider(provider1);
-        container.addLocalInsetsFrameProvider(provider2);
+        rootTask.addLocalInsetsFrameProvider(provider1, owner);
+        container.addLocalInsetsFrameProvider(provider2, owner);
         mDisplayContent.getInsetsStateController().onPostLayout();
-        rootTask.removeLocalInsetsFrameProvider(provider1);
+        rootTask.removeLocalInsetsFrameProvider(provider1, owner);
         mDisplayContent.getInsetsStateController().onPostLayout();
 
         activity0.forAllWindows(window -> {
@@ -1593,6 +1607,67 @@
         }, true);
     }
 
+    @Test
+    public void testAddLocalInsetsFrameProvider_ownerDiesAfterAdding() {
+        final Task task = createTask(mDisplayContent);
+        final TestBinder owner = new TestBinder();
+        final InsetsFrameProvider provider =
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemOverlays())
+                        .setArbitraryRectangle(new Rect());
+        task.addLocalInsetsFrameProvider(provider, owner);
+
+        assertTrue("The death recipient must exist.", owner.hasDeathRecipient());
+        assertTrue("The source must be added.", hasLocalSource(task, provider.getId()));
+
+        // The owner dies after adding the source.
+        owner.die();
+
+        assertFalse("The death recipient must be removed.", owner.hasDeathRecipient());
+        assertFalse("The source must be removed.", hasLocalSource(task, provider.getId()));
+    }
+
+    @Test
+    public void testAddLocalInsetsFrameProvider_ownerDiesBeforeAdding() {
+        final Task task = createTask(mDisplayContent);
+        final TestBinder owner = new TestBinder();
+
+        // The owner dies before adding the source.
+        owner.die();
+
+        final InsetsFrameProvider provider =
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemOverlays())
+                        .setArbitraryRectangle(new Rect());
+        task.addLocalInsetsFrameProvider(provider, owner);
+
+        assertFalse("The death recipient must not exist.", owner.hasDeathRecipient());
+        assertFalse("The source must not be added.", hasLocalSource(task, provider.getId()));
+    }
+
+    @Test
+    public void testRemoveLocalInsetsFrameProvider_removeDeathRecipient() {
+        final Task task = createTask(mDisplayContent);
+        final TestBinder owner = new TestBinder();
+        final InsetsFrameProvider provider =
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemOverlays())
+                        .setArbitraryRectangle(new Rect());
+        task.addLocalInsetsFrameProvider(provider, owner);
+
+        assertTrue("The death recipient must exist.", owner.hasDeathRecipient());
+        assertTrue("The source must be added.", hasLocalSource(task, provider.getId()));
+
+        task.removeLocalInsetsFrameProvider(provider, owner);
+
+        assertFalse("The death recipient must be removed.", owner.hasDeathRecipient());
+        assertFalse("The source must be removed.", hasLocalSource(task, provider.getId()));
+    }
+
+    private static boolean hasLocalSource(WindowContainer container, int sourceId) {
+        if (container.mLocalInsetsSources == null) {
+            return false;
+        }
+        return container.mLocalInsetsSources.contains(sourceId);
+    }
+
     /* Used so we can gain access to some protected members of the {@link WindowContainer} class */
     private static class TestWindowContainer extends WindowContainer<TestWindowContainer> {
         private final int mLayer;
@@ -1797,4 +1872,82 @@
             mIsVisibleRequested = isVisibleRequested;
         }
     }
+
+    private static class TestBinder implements IBinder {
+
+        private boolean mDead;
+        private final ArrayList<IBinder.DeathRecipient> mDeathRecipients = new ArrayList<>();
+
+        public void die() {
+            mDead = true;
+            for (int i = mDeathRecipients.size() - 1; i >= 0; i--) {
+                final DeathRecipient recipient = mDeathRecipients.get(i);
+                recipient.binderDied(this);
+            }
+        }
+
+        public boolean hasDeathRecipient() {
+            return !mDeathRecipients.isEmpty();
+        }
+
+        @Override
+        public void linkToDeath(@NonNull DeathRecipient recipient, int flags)
+                throws RemoteException {
+            if (mDead) {
+                throw new DeadObjectException();
+            }
+            mDeathRecipients.add(recipient);
+        }
+
+        @Override
+        public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
+            final boolean successes = mDeathRecipients.remove(recipient);
+            if (successes || mDead) {
+                return successes;
+            }
+            throw new NoSuchElementException("Given recipient has not been registered.");
+        }
+
+        @Override
+        public boolean isBinderAlive() {
+            return !mDead;
+        }
+
+        @Override
+        public boolean pingBinder() {
+            return !mDead;
+        }
+
+        @Nullable
+        @Override
+        public String getInterfaceDescriptor() throws RemoteException {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public IInterface queryLocalInterface(@NonNull String descriptor) {
+            return null;
+        }
+
+        @Override
+        public void dump(@NonNull FileDescriptor fd, @Nullable String[] args)
+                throws RemoteException { }
+
+        @Override
+        public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args)
+                throws RemoteException { }
+
+        @Override
+        public void shellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+                @Nullable FileDescriptor err, @NonNull String[] args,
+                @Nullable ShellCallback shellCallback,
+                @NonNull ResultReceiver resultReceiver) throws RemoteException { }
+
+        @Override
+        public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
+                throws RemoteException {
+            return false;
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index d3f6818..b77e4cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -52,6 +52,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
@@ -80,6 +81,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.DisplayMetrics;
 import android.util.MergedConfiguration;
+import android.view.ContentRecordingSession;
 import android.view.IWindow;
 import android.view.IWindowSessionCallback;
 import android.view.InputChannel;
@@ -99,6 +101,8 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.android.internal.os.IResultReceiver;
+import com.android.server.LocalServices;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -759,6 +763,63 @@
     }
 
     @Test
+    public void setContentRecordingSession_sessionNull_returnsTrue() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+
+        boolean result = wmInternal.setContentRecordingSession(/* incomingSession= */ null);
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    public void setContentRecordingSession_sessionContentDisplay_returnsTrue() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
+                DEFAULT_DISPLAY);
+
+        boolean result = wmInternal.setContentRecordingSession(session);
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    public void setContentRecordingSession_sessionContentTask_noMatchingTask_returnsFalse() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        IBinder launchCookie = new Binder();
+        ContentRecordingSession session = ContentRecordingSession.createTaskSession(launchCookie);
+
+        boolean result = wmInternal.setContentRecordingSession(session);
+
+        assertThat(result).isFalse();
+    }
+
+    @Test
+    public void setContentRecordingSession_sessionContentTask_matchingTask_returnsTrue() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        ActivityRecord activityRecord = createActivityRecord(createTask(mDefaultDisplay));
+        ContentRecordingSession session = ContentRecordingSession.createTaskSession(
+                activityRecord.mLaunchCookie);
+
+        boolean result = wmInternal.setContentRecordingSession(session);
+
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() {
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        Task task = createTask(mDefaultDisplay);
+        ActivityRecord activityRecord = createActivityRecord(task);
+        ContentRecordingSession session = ContentRecordingSession.createTaskSession(
+                activityRecord.mLaunchCookie);
+
+        wmInternal.setContentRecordingSession(session);
+
+        assertThat(session.getTokenToRecord()).isEqualTo(
+                task.mRemoteToken.toWindowContainerToken().asBinder());
+    }
+
+    @Test
     public void testisLetterboxBackgroundMultiColored() {
         assertThat(setupLetterboxConfigurationWithBackgroundType(
                 LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue();
@@ -905,6 +966,56 @@
                 argThat(h -> (h.inputConfig & InputConfig.SPY) == InputConfig.SPY));
     }
 
+    @Test
+    public void testRequestKeyboardShortcuts_noWindow() {
+        doNothing().when(mWm.mContext).enforceCallingOrSelfPermission(anyString(), anyString());
+        doReturn(null).when(mWm).getFocusedWindowLocked();
+        doReturn(null).when(mWm.mRoot).getCurrentInputMethodWindow();
+
+        TestResultReceiver receiver = new TestResultReceiver();
+        mWm.requestAppKeyboardShortcuts(receiver, 0);
+        assertNotNull(receiver.resultData);
+        assertTrue(receiver.resultData.isEmpty());
+
+        receiver = new TestResultReceiver();
+        mWm.requestImeKeyboardShortcuts(receiver, 0);
+        assertNotNull(receiver.resultData);
+        assertTrue(receiver.resultData.isEmpty());
+    }
+
+    @Test
+    public void testRequestKeyboardShortcuts() throws RemoteException {
+        final IWindow window = mock(IWindow.class);
+        final IBinder binder = mock(IBinder.class);
+        doReturn(binder).when(window).asBinder();
+        final WindowState windowState =
+                createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appWin", window);
+        doNothing().when(mWm.mContext).enforceCallingOrSelfPermission(anyString(), anyString());
+        doReturn(windowState).when(mWm).getFocusedWindowLocked();
+        doReturn(windowState).when(mWm.mRoot).getCurrentInputMethodWindow();
+
+        TestResultReceiver receiver = new TestResultReceiver();
+        mWm.requestAppKeyboardShortcuts(receiver, 0);
+        mWm.requestImeKeyboardShortcuts(receiver, 0);
+        verify(window, times(2)).requestAppKeyboardShortcuts(receiver, 0);
+    }
+
+    class TestResultReceiver implements IResultReceiver {
+        public android.os.Bundle resultData;
+        private final IBinder mBinder = mock(IBinder.class);
+
+        @Override
+        public void send(int resultCode, android.os.Bundle resultData)
+                throws android.os.RemoteException {
+            this.resultData = resultData;
+        }
+
+        @Override
+        public android.os.IBinder asBinder() {
+            return mBinder;
+        }
+    }
+
     private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
         final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
         when(remoteToken.toWindowContainerToken()).thenReturn(wct);
@@ -917,7 +1028,7 @@
 
     private boolean setupLetterboxConfigurationWithBackgroundType(
             @LetterboxConfiguration.LetterboxBackgroundType int letterboxBackgroundType) {
-        mWm.mLetterboxConfiguration.setLetterboxBackgroundType(letterboxBackgroundType);
+        mWm.mLetterboxConfiguration.setLetterboxBackgroundTypeOverride(letterboxBackgroundType);
         return mWm.isLetterboxBackgroundMultiColored();
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 600681f..7168670 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1247,6 +1247,7 @@
 
         // A drawn window can complete the sync state automatically.
         w1.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
+        makeLastConfigReportedToClient(w1, true /* visible */);
         mWm.mSyncEngine.onSurfacePlacement();
         verify(mockCallback).onTransactionReady(anyInt(), any());
         assertFalse(w1.useBLASTSync());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index cf83981..ebe40b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -170,9 +170,13 @@
     }
 
     @Test
-    public void testSetRunningRecentsAnimation() {
-        mWpc.setRunningRecentsAnimation(true);
-        mWpc.setRunningRecentsAnimation(false);
+    public void testSetAnimatingReason() {
+        mWpc.addAnimatingReason(WindowProcessController.ANIMATING_REASON_REMOTE_ANIMATION);
+        assertTrue(mWpc.isRunningRemoteTransition());
+        mWpc.addAnimatingReason(WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
+        mWpc.removeAnimatingReason(WindowProcessController.ANIMATING_REASON_REMOTE_ANIMATION);
+        assertFalse(mWpc.isRunningRemoteTransition());
+        mWpc.removeAnimatingReason(WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
         waitHandlerIdle(mAtm.mH);
 
         InOrder orderVerifier = Mockito.inOrder(mMockListener);
@@ -201,7 +205,7 @@
         waitHandlerIdle(mAtm.mH);
 
         InOrder orderVerifier = Mockito.inOrder(mMockListener);
-        orderVerifier.verify(mMockListener, times(3)).setRunningRemoteAnimation(eq(true));
+        orderVerifier.verify(mMockListener, times(1)).setRunningRemoteAnimation(eq(true));
         orderVerifier.verify(mMockListener, times(1)).setRunningRemoteAnimation(eq(false));
         orderVerifier.verifyNoMoreInteractions();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index be8ee78..873c7f4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -85,6 +85,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.voice.IVoiceInteractionSession;
+import android.util.MergedConfiguration;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
@@ -102,6 +103,7 @@
 import android.view.WindowManager;
 import android.view.WindowManager.DisplayImePolicy;
 import android.view.inputmethod.ImeTracker;
+import android.window.ClientWindowFrames;
 import android.window.ITransitionPlayer;
 import android.window.ScreenCapture;
 import android.window.StartingWindowInfo;
@@ -222,6 +224,10 @@
         displayPolicy.finishWindowsDrawn();
         displayPolicy.finishScreenTurningOn();
 
+        final InsetsPolicy insetsPolicy = mDefaultDisplay.getInsetsPolicy();
+        suppressInsetsAnimation(insetsPolicy.getTransientControlTarget());
+        suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget());
+
         mTransaction = mSystemServicesTestRule.mTransaction;
         mMockSession = mock(Session.class);
 
@@ -278,6 +284,15 @@
         checkDeviceSpecificOverridesNotApplied();
     }
 
+    /**
+     * The test doesn't create real SurfaceControls, but mocked ones. This prevents the target from
+     * controlling them, or it will cause {@link NullPointerException}.
+     */
+    static void suppressInsetsAnimation(InsetsControlTarget target) {
+        spyOn(target);
+        Mockito.doNothing().when(target).notifyInsetsControlChanged();
+    }
+
     @After
     public void tearDown() throws Exception {
         if (mUseFakeSettingsProvider) {
@@ -611,6 +626,11 @@
         }
     }
 
+    static void makeLastConfigReportedToClient(WindowState w, boolean visible) {
+        w.fillClientWindowFramesAndConfiguration(new ClientWindowFrames(),
+                new MergedConfiguration(), true /* useLatestConfig */, visible);
+    }
+
     /**
      * Gets the order of the given {@link Task} as its z-order in the hierarchy below this TDA.
      * The Task can be a direct child of a child TaskDisplayArea. {@code -1} if not found.
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 13945a1..b3db2de 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -365,7 +365,7 @@
             // Validate package name
             try {
                 int uid = mPackageManager.getPackageUid(mOriginatorIdentity.packageName,
-                        PackageManager.PackageInfoFlags.of(0));
+                        PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ANY_USER));
                 if (!UserHandle.isSameApp(uid, mOriginatorIdentity.uid)) {
                     throw new SecurityException("Uid " + mOriginatorIdentity.uid +
                             " attempted to spoof package name " +
@@ -407,7 +407,9 @@
                 var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE,
                         "SoundTriggerSessionLogs for package: "
                         + Objects.requireNonNull(originatorIdentity.packageName)
-                        + "#" + sessionId);
+                        + "#" + sessionId
+                        + " - " + originatorIdentity.uid
+                        + "|" + originatorIdentity.pid);
                 return new SoundTriggerSessionStub(client,
                         newSoundTriggerHelper(moduleProperties, eventLogger), eventLogger);
             }
@@ -428,7 +430,9 @@
                 var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE,
                         "SoundTriggerSessionLogs for package: "
                         + Objects.requireNonNull(originatorIdentity.packageName) + "#"
-                        + sessionId);
+                        + sessionId
+                        + " - " + originatorIdentity.uid
+                        + "|" + originatorIdentity.pid);
                 return new SoundTriggerSessionStub(client,
                         newSoundTriggerHelper(moduleProperties, eventLogger), eventLogger);
             }
@@ -1801,7 +1805,9 @@
                         ServiceEvent.Type.ATTACH, identity.packageName + "#" + sessionId));
             var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE,
                     "LocalSoundTriggerEventLogger for package: " +
-                    identity.packageName + "#" + sessionId);
+                    identity.packageName + "#" + sessionId
+                        + " - " + identity.uid
+                        + "|" + identity.pid);
 
             return new SessionImpl(newSoundTriggerHelper(underlyingModule, eventLogger, isTrusted),
                     client, eventLogger, identity);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java
index 2f2cb59..55cbf29 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java
@@ -22,7 +22,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -62,7 +62,7 @@
                         android.hardware.soundtrigger3.ISoundTriggerHw.class.getCanonicalName()
                                 + "/default";
                 if (ServiceManager.isDeclared(aidlServiceName)) {
-                    Log.i(TAG, "Connecting to default soundtrigger3.ISoundTriggerHw");
+                    Slog.i(TAG, "Connecting to default soundtrigger3.ISoundTriggerHw");
                     return new SoundTriggerHw3Compat(ServiceManager.waitForService(aidlServiceName),
                             () -> {
                                 // This property needs to be defined in an init.rc script and
@@ -72,7 +72,7 @@
                 }
 
                 // Fallback to soundtrigger-V2.x (HIDL).
-                Log.i(TAG, "Connecting to default soundtrigger-V2.x.ISoundTriggerHw");
+                Slog.i(TAG, "Connecting to default soundtrigger-V2.x.ISoundTriggerHw");
                 ISoundTriggerHw driver = ISoundTriggerHw.getService(true);
                 return SoundTriggerHw2Compat.create(driver, () -> {
                     // This property needs to be defined in an init.rc script and
@@ -81,7 +81,7 @@
                 }, mCaptureStateNotifier);
             } else if (mockHal == USE_MOCK_HAL_V2) {
                 // Use V2 mock.
-                Log.i(TAG, "Connecting to mock soundtrigger-V2.x.ISoundTriggerHw");
+                Slog.i(TAG, "Connecting to mock soundtrigger-V2.x.ISoundTriggerHw");
                 HwBinder.setTrebleTestingOverride(true);
                 try {
                     ISoundTriggerHw driver = ISoundTriggerHw.getService("mock", true);
@@ -89,7 +89,7 @@
                         try {
                             driver.debug(null, new ArrayList<>(Arrays.asList("reboot")));
                         } catch (Exception e) {
-                            Log.e(TAG, "Failed to reboot mock HAL", e);
+                            Slog.e(TAG, "Failed to reboot mock HAL", e);
                         }
                     }, mCaptureStateNotifier);
                 } finally {
@@ -100,14 +100,14 @@
                 final String aidlServiceName =
                         android.hardware.soundtrigger3.ISoundTriggerHw.class.getCanonicalName()
                                 + "/mock";
-                Log.i(TAG, "Connecting to mock soundtrigger3.ISoundTriggerHw");
+                Slog.i(TAG, "Connecting to mock soundtrigger3.ISoundTriggerHw");
                 return new SoundTriggerHw3Compat(ServiceManager.waitForService(aidlServiceName),
                         () -> {
                             try {
                                 ServiceManager.waitForService(aidlServiceName).shellCommand(null,
                                         null, null, new String[]{"reboot"}, null, null);
                             } catch (Exception e) {
-                                Log.e(TAG, "Failed to reboot mock HAL", e);
+                                Slog.e(TAG, "Failed to reboot mock HAL", e);
                             }
                         });
             } else {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
index d195fbe..e3d64d4 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
@@ -17,7 +17,7 @@
 package com.android.server.soundtrigger_middleware;
 
 import android.annotation.NonNull;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.LinkedList;
 import java.util.List;
@@ -94,7 +94,7 @@
                 }
             }
         } catch (Exception e) {
-            Log.e(TAG, "Exception caught while setting capture state", e);
+            Slog.e(TAG, "Exception caught while setting capture state", e);
         }
     }
 
@@ -102,7 +102,7 @@
      * Called by native code when the remote service died.
      */
     private void binderDied() {
-        Log.w(TAG, "Audio policy service died");
+        Slog.w(TAG, "Audio policy service died");
         mNeedToConnect.release();
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
index c3e0a3c..0f63347 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java
@@ -27,7 +27,7 @@
 import android.media.soundtrigger_middleware.RecognitionEventSys;
 import android.os.DeadObjectException;
 import android.os.IBinder;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -227,10 +227,10 @@
         }
         if (e.getCause() instanceof DeadObjectException) {
             // Server is dead, no need to reboot.
-            Log.e(TAG, "HAL died");
+            Slog.e(TAG, "HAL died");
             throw new RecoverableException(Status.DEAD_OBJECT);
         }
-        Log.e(TAG, "Exception caught from HAL, rebooting HAL");
+        Slog.e(TAG, "Exception caught from HAL, rebooting HAL");
         reboot();
         throw e;
     }
@@ -257,14 +257,14 @@
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(model);
                 if (state == null) {
-                    Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
+                    Slog.wtfStack(TAG, "Unexpected recognition event for model: " + model);
                     reboot();
                     return;
                 }
                 if (event.recognitionEvent.recognitionStillActive
                         && event.recognitionEvent.status != RecognitionStatus.SUCCESS
                         && event.recognitionEvent.status != RecognitionStatus.FORCED) {
-                    Log.wtfStack(TAG,
+                    Slog.wtfStack(TAG,
                             "recognitionStillActive is only allowed when the recognition status "
                                     + "is SUCCESS");
                     reboot();
@@ -283,14 +283,14 @@
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(model);
                 if (state == null) {
-                    Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
+                    Slog.wtfStack(TAG, "Unexpected recognition event for model: " + model);
                     reboot();
                     return;
                 }
                 if (event.phraseRecognitionEvent.common.recognitionStillActive
                         && event.phraseRecognitionEvent.common.status != RecognitionStatus.SUCCESS
                         && event.phraseRecognitionEvent.common.status != RecognitionStatus.FORCED) {
-                    Log.wtfStack(TAG,
+                    Slog.wtfStack(TAG,
                             "recognitionStillActive is only allowed when the recognition status "
                                     + "is SUCCESS");
                     reboot();
@@ -309,13 +309,13 @@
             synchronized (mModelStates) {
                 ModelState state = mModelStates.get(modelHandle);
                 if (state == null) {
-                    Log.wtfStack(TAG, "Unexpected unload event for model: " + modelHandle);
+                    Slog.wtfStack(TAG, "Unexpected unload event for model: " + modelHandle);
                     reboot();
                     return;
                 }
 
                 if (state == ModelState.ACTIVE) {
-                    Log.wtfStack(TAG, "Trying to unload an active model: " + modelHandle);
+                    Slog.wtfStack(TAG, "Trying to unload an active model: " + modelHandle);
                     reboot();
                     return;
                 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java
index 0390f03..5e525e0 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java
@@ -23,7 +23,7 @@
 import android.media.soundtrigger.RecognitionConfig;
 import android.media.soundtrigger.SoundModel;
 import android.os.IBinder;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.Objects;
 
@@ -172,7 +172,7 @@
 
         Watchdog() {
             mTask = mTimer.createTask(() -> {
-                Log.e(TAG, "HAL deadline expired. Rebooting.", mException);
+                Slog.e(TAG, "HAL deadline expired. Rebooting.", mException);
                 reboot();
             }, TIMEOUT_MS);
         }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
index df2e9b4..730e92c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -32,7 +32,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.system.OsConstants;
-import android.util.Log;
+import android.util.Slog;
 
 import java.io.IOException;
 import java.util.HashMap;
@@ -240,7 +240,7 @@
                 try {
                     hidlModel.data.close();
                 } catch (IOException e) {
-                    Log.e(TAG, "Failed to close file", e);
+                    Slog.e(TAG, "Failed to close file", e);
                 }
             }
         }
@@ -276,7 +276,7 @@
                 try {
                     hidlModel.common.data.close();
                 } catch (IOException e) {
-                    Log.e(TAG, "Failed to close file", e);
+                    Slog.e(TAG, "Failed to close file", e);
                 }
             }
         }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
index 3b800de..5a064da 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -20,7 +20,7 @@
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -85,7 +85,7 @@
             try {
                 modules.add(new SoundTriggerModule(halFactory, audioSessionProvider));
             } catch (Exception e) {
-                Log.e(TAG, "Failed to add a SoundTriggerModule instance", e);
+                Slog.e(TAG, "Failed to add a SoundTriggerModule instance", e);
             }
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 7ec2d9f..0b9ed8c 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -36,7 +36,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
-import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.util.Preconditions;
@@ -150,7 +150,7 @@
                     e.getMessage());
         }
 
-        Log.wtf(TAG, "Unexpected exception", e);
+        Slog.wtf(TAG, "Unexpected exception", e);
         throw new ServiceSpecificException(Status.INTERNAL_ERROR, e.getMessage());
     }
 
@@ -701,7 +701,7 @@
                 try {
                     mCallback.onRecognition(modelHandle, event, captureSession);
                 } catch (Exception e) {
-                    Log.w(TAG, "Client callback exception.", e);
+                    Slog.w(TAG, "Client callback exception.", e);
                }
             }
 
@@ -719,7 +719,7 @@
                 try {
                     mCallback.onPhraseRecognition(modelHandle, event, captureSession);
                 } catch (Exception e) {
-                    Log.w(TAG, "Client callback exception.", e);
+                    Slog.w(TAG, "Client callback exception.", e);
                }
             }
 
@@ -734,7 +734,7 @@
                 try {
                     mCallback.onModelUnloaded(modelHandle);
                 } catch (Exception e) {
-                    Log.w(TAG, "Client callback exception.", e);
+                    Slog.w(TAG, "Client callback exception.", e);
                 }
             }
 
@@ -746,7 +746,7 @@
                 } catch (RemoteException e) {
                     // Dead client will be handled by binderDied() - no need to handle here.
                     // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback exception.", e);
+                    Slog.e(TAG, "Client callback exception.", e);
                 }
             }
 
@@ -761,7 +761,7 @@
                 } catch (RemoteException e) {
                     // Dead client will be handled by binderDied() - no need to handle here.
                     // In any case, client callbacks are considered best effort.
-                    Log.e(TAG, "Client callback exception.", e);
+                    Slog.e(TAG, "Client callback exception.", e);
                 }
             }
 
@@ -795,11 +795,11 @@
                    // Check if state updated unexpectedly to log race conditions.
                     for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
                         if (cachedMap.get(entry.getKey()) != entry.getValue().activityState) {
-                            Log.e(TAG, "Unexpected state update in binderDied. Race occurred!");
+                            Slog.e(TAG, "Unexpected state update in binderDied. Race occurred!");
                         }
                     }
                     if (mLoadedModels.size() != cachedMap.size()) {
-                        Log.e(TAG, "Unexpected state update in binderDied. Race occurred!");
+                        Slog.e(TAG, "Unexpected state update in binderDied. Race occurred!");
                     }
                     try {
                         // Detach
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index e793f31..45a7faf 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -31,7 +31,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.util.Log;
+import android.util.Slog;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -136,7 +136,7 @@
 
     @Override
     public void binderDied() {
-        Log.w(TAG, "Underlying HAL driver died.");
+        Slog.w(TAG, "Underlying HAL driver died.");
         List<ISoundTriggerCallback> callbacks;
         synchronized (this) {
             callbacks = new ArrayList<>(mActiveSessions.size());
@@ -270,7 +270,7 @@
                     try {
                         mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
                     } catch (Exception ee) {
-                        Log.e(TAG, "Failed to release session.", ee);
+                        Slog.e(TAG, "Failed to release session.", ee);
                     }
                     throw e;
                 }
@@ -286,7 +286,7 @@
                     checkValid();
                     Model loadedModel = new Model();
                     int result = loadedModel.load(model, audioSession);
-                    Log.d(TAG, String.format("loadPhraseModel()->%d", result));
+                    Slog.d(TAG, String.format("loadPhraseModel()->%d", result));
                     return result;
                 } catch (Exception e) {
                     // We must do this outside the lock, to avoid possible deadlocks with the remote
@@ -294,7 +294,7 @@
                     try {
                         mAudioSessionProvider.releaseSession(audioSession.mSessionHandle);
                     } catch (Exception ee) {
-                        Log.e(TAG, "Failed to release session.", ee);
+                        Slog.e(TAG, "Failed to release session.", ee);
                     }
                     throw e;
                 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 248cc26..58da4b43 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -27,6 +27,7 @@
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH;
 
+import android.app.AppOpsManager;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.ChangeId;
@@ -367,29 +368,29 @@
     /**
      * This method is only used by VisualQueryDetector.
      */
-    void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+    boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "startPerceivingLocked");
         }
         final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
         if (session == null) {
-            return;
+            return false;
         }
-        session.startPerceivingLocked(callback);
+        return session.startPerceivingLocked(callback);
     }
 
     /**
      * This method is only used by VisaulQueryDetector.
      */
-    void stopPerceivingLocked() {
+    boolean stopPerceivingLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopPerceivingLocked");
         }
         final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
         if (session == null) {
-            return;
+            return false;
         }
-        session.stopPerceivingLocked();
+        return session.stopPerceivingLocked();
     }
 
     public void startListeningFromExternalSourceLocked(
@@ -548,13 +549,15 @@
     static final class SoundTriggerCallback extends IRecognitionStatusCallback.Stub {
         private final HotwordDetectionConnection mHotwordDetectionConnection;
         private final IHotwordRecognitionStatusCallback mExternalCallback;
-        private final int mVoiceInteractionServiceUid;
+        private final Identity mVoiceInteractorIdentity;
+        private final Context mContext;
 
-        SoundTriggerCallback(IHotwordRecognitionStatusCallback callback,
-                HotwordDetectionConnection connection, int uid) {
+        SoundTriggerCallback(Context context, IHotwordRecognitionStatusCallback callback,
+                HotwordDetectionConnection connection, Identity voiceInteractorIdentity) {
+            mContext = context;
             mHotwordDetectionConnection = connection;
             mExternalCallback = callback;
-            mVoiceInteractionServiceUid = uid;
+            mVoiceInteractorIdentity = voiceInteractorIdentity;
         }
 
         @Override
@@ -568,15 +571,30 @@
                 HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP,
                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
-                        mVoiceInteractionServiceUid);
+                        mVoiceInteractorIdentity.uid);
                 mHotwordDetectionConnection.detectFromDspSource(
                         recognitionEvent, mExternalCallback);
             } else {
-                HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
-                        mVoiceInteractionServiceUid);
-                mExternalCallback.onKeyphraseDetected(recognitionEvent, null);
+                // We have to attribute ops here, since we configure all st clients as trusted to
+                // enable a partial exemption.
+                // TODO (b/292012931) remove once trusted uniformly required.
+                int result = mContext.getSystemService(AppOpsManager.class)
+                        .noteOpNoThrow(AppOpsManager.OP_RECORD_AUDIO_HOTWORD,
+                            mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+                            mVoiceInteractorIdentity.attributionTag,
+                            "Non-HDS keyphrase recognition to VoiceInteractionService");
+
+                if (result != AppOpsManager.MODE_ALLOWED) {
+                    Slog.w(TAG, "onKeyphraseDetected suppressed, permission check returned: "
+                            + result);
+                    mExternalCallback.onRecognitionPaused();
+                } else {
+                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR,
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
+                            mVoiceInteractorIdentity.uid);
+                    mExternalCallback.onKeyphraseDetected(recognitionEvent, null);
+                }
             }
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
index 2e05e20..4720d27 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -95,7 +95,7 @@
     }
 
     @SuppressWarnings("GuardedBy")
-    void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+    boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "startPerceivingLocked");
         }
@@ -198,15 +198,16 @@
                 mQueryStreaming = false;
             }
         };
-        mRemoteDetectionService.run(service -> service.detectWithVisualSignals(internalCallback));
+        return mRemoteDetectionService.run(
+                service -> service.detectWithVisualSignals(internalCallback));
     }
 
     @SuppressWarnings("GuardedBy")
-    void stopPerceivingLocked() {
+    boolean stopPerceivingLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopPerceivingLocked");
         }
-        mRemoteDetectionService.run(ISandboxedDetectionService::stopDetection);
+        return mRemoteDetectionService.run(ISandboxedDetectionService::stopDetection);
     }
 
     @Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 423a81a..0de9255 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -23,7 +23,6 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
-import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.role.OnRoleHoldersChangedListener;
 import android.app.role.RoleManager;
@@ -51,7 +50,6 @@
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.media.AudioFormat;
 import android.media.permission.Identity;
-import android.media.permission.IdentityContext;
 import android.media.permission.PermissionUtil;
 import android.media.permission.SafeCloseable;
 import android.os.Binder;
@@ -61,7 +59,6 @@
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
-import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -88,6 +85,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.app.IVisualQueryRecognitionStatusListener;
 import com.android.internal.app.IVoiceActionCheckCallback;
 import com.android.internal.app.IVoiceInteractionManagerService;
 import com.android.internal.app.IVoiceInteractionSessionListener;
@@ -104,6 +102,7 @@
 import com.android.server.UiThread;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
+import com.android.server.policy.AppOpsPolicy;
 import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -138,6 +137,7 @@
 
     private final RemoteCallbackList<IVoiceInteractionSessionListener>
             mVoiceInteractionSessionListeners = new RemoteCallbackList<>();
+    private IVisualQueryRecognitionStatusListener mVisualQueryRecognitionStatusListener;
 
     public VoiceInteractionManagerService(Context context) {
         super(context);
@@ -336,6 +336,9 @@
         /** The start value of showSessionId */
         private static final int SHOW_SESSION_START_ID = 0;
 
+        private final boolean IS_HDS_REQUIRED = AppOpsPolicy.isHotwordDetectionServiceRequired(
+                mContext.getPackageManager());
+
         @GuardedBy("this")
         private int mShowSessionId = SHOW_SESSION_START_ID;
 
@@ -393,8 +396,14 @@
             }
             try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
                     originatorIdentity)) {
+                if (!IS_HDS_REQUIRED) {
+                    // For devices which still have hotword exemption, any client (not just HDS
+                    // clients) are trusted.
+                    // TODO (b/292012931) remove once trusted uniformly required.
+                    forHotwordDetectionService = true;
+                }
                 return new SoundTriggerSession(mSoundTriggerInternal.attach(client,
-                            moduleProperties, forHotwordDetectionService));
+                            moduleProperties, forHotwordDetectionService), originatorIdentity);
             }
         }
 
@@ -1336,6 +1345,17 @@
         @android.annotation.EnforcePermission(
                 android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
+        public void subscribeVisualQueryRecognitionStatus(IVisualQueryRecognitionStatusListener
+                listener) {
+            super.subscribeVisualQueryRecognitionStatus_enforcePermission();
+            synchronized (this) {
+                mVisualQueryRecognitionStatusListener = listener;
+            }
+        }
+
+        @android.annotation.EnforcePermission(
+                android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
+        @Override
         public void enableVisualQueryDetection(
                 IVisualQueryDetectionAttentionListener listener) {
             super.enableVisualQueryDetection_enforcePermission();
@@ -1381,7 +1401,10 @@
                 }
                 final long caller = Binder.clearCallingIdentity();
                 try {
-                    mImpl.startPerceivingLocked(callback);
+                    boolean success = mImpl.startPerceivingLocked(callback);
+                    if (success && mVisualQueryRecognitionStatusListener != null) {
+                        mVisualQueryRecognitionStatusListener.onStartPerceiving();
+                    }
                 } finally {
                     Binder.restoreCallingIdentity(caller);
                 }
@@ -1399,7 +1422,10 @@
                 }
                 final long caller = Binder.clearCallingIdentity();
                 try {
-                    mImpl.stopPerceivingLocked();
+                    boolean success = mImpl.stopPerceivingLocked();
+                    if (success && mVisualQueryRecognitionStatusListener != null) {
+                        mVisualQueryRecognitionStatusListener.onStopPerceiving();
+                    }
                 } finally {
                     Binder.restoreCallingIdentity(caller);
                 }
@@ -1674,10 +1700,13 @@
             final SoundTriggerInternal.Session mSession;
             private IHotwordRecognitionStatusCallback mSessionExternalCallback;
             private IRecognitionStatusCallback mSessionInternalCallback;
+            private final Identity mVoiceInteractorIdentity;
 
             SoundTriggerSession(
-                    SoundTriggerInternal.Session session) {
+                    SoundTriggerInternal.Session session,
+                    Identity voiceInteractorIdentity) {
                 mSession = session;
+                mVoiceInteractorIdentity = voiceInteractorIdentity;
             }
 
             @Override
@@ -1731,7 +1760,8 @@
                         if (mSessionExternalCallback == null
                                 || mSessionInternalCallback == null
                                 || callback.asBinder() != mSessionExternalCallback.asBinder()) {
-                            mSessionInternalCallback = createSoundTriggerCallbackLocked(callback);
+                            mSessionInternalCallback = createSoundTriggerCallbackLocked(callback,
+                                    mVoiceInteractorIdentity);
                             mSessionExternalCallback = callback;
                         }
                     }
@@ -1752,7 +1782,8 @@
                     if (mSessionExternalCallback == null
                             || mSessionInternalCallback == null
                             || callback.asBinder() != mSessionExternalCallback.asBinder()) {
-                        soundTriggerCallback = createSoundTriggerCallbackLocked(callback);
+                        soundTriggerCallback = createSoundTriggerCallbackLocked(callback,
+                                mVoiceInteractorIdentity);
                         Slog.w(TAG, "stopRecognition() called with a different callback than"
                                 + "startRecognition()");
                     } else {
@@ -2090,6 +2121,7 @@
                 pw.println("  mTemporarilyDisabled: " + mTemporarilyDisabled);
                 pw.println("  mCurUser: " + mCurUser);
                 pw.println("  mCurUserSupported: " + mCurUserSupported);
+                pw.println("  mIsHdsRequired: " + IS_HDS_REQUIRED);
                 dumpSupportedUsers(pw, "  ");
                 mDbHelper.dump(pw);
                 if (mImpl == null) {
@@ -2165,11 +2197,13 @@
         }
 
         private IRecognitionStatusCallback createSoundTriggerCallbackLocked(
-                IHotwordRecognitionStatusCallback callback) {
+                IHotwordRecognitionStatusCallback callback,
+                Identity voiceInteractorIdentity) {
             if (mImpl == null) {
                 return null;
             }
-            return mImpl.createSoundTriggerCallbackLocked(callback);
+            return mImpl.createSoundTriggerCallbackLocked(mContext, callback,
+                    voiceInteractorIdentity);
         }
 
         class RoleObserver implements OnRoleHoldersChangedListener {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 0ad86c1..471acc1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -784,30 +784,30 @@
         mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener);
     }
 
-    public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+    public boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "startPerceivingLocked");
         }
 
         if (mHotwordDetectionConnection == null) {
             // TODO: callback.onError();
-            return;
+            return false;
         }
 
-        mHotwordDetectionConnection.startPerceivingLocked(callback);
+        return mHotwordDetectionConnection.startPerceivingLocked(callback);
     }
 
-    public void stopPerceivingLocked() {
+    public boolean stopPerceivingLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopPerceivingLocked");
         }
 
         if (mHotwordDetectionConnection == null) {
             Slog.w(TAG, "stopPerceivingLocked() called but connection isn't established");
-            return;
+            return false;
         }
 
-        mHotwordDetectionConnection.stopPerceivingLocked();
+        return mHotwordDetectionConnection.stopPerceivingLocked();
     }
 
     public void startListeningFromMicLocked(
@@ -877,12 +877,13 @@
     }
 
     public IRecognitionStatusCallback createSoundTriggerCallbackLocked(
-            IHotwordRecognitionStatusCallback callback) {
+            Context context, IHotwordRecognitionStatusCallback callback,
+            Identity voiceInteractorIdentity) {
         if (DEBUG) {
             Slog.d(TAG, "createSoundTriggerCallbackLocked");
         }
-        return new HotwordDetectionConnection.SoundTriggerCallback(callback,
-                mHotwordDetectionConnection, mInfo.getServiceInfo().applicationInfo.uid);
+        return new HotwordDetectionConnection.SoundTriggerCallback(context, callback,
+                mHotwordDetectionConnection, voiceInteractorIdentity);
     }
 
     private static ServiceInfo getServiceInfoLocked(@NonNull ComponentName componentName,
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 5b1c6b1..3703349 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -50,7 +50,6 @@
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.feature.RcsFeature;
-import android.telephony.ims.stub.ImsRegistrationImplBase;
 
 import com.android.internal.telephony.ICarrierConfigLoader;
 import com.android.telephony.Rlog;
@@ -5813,6 +5812,21 @@
          */
         public static final int NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED = 3;
 
+        /**
+         * This specifies whether the carrier support the global number format or not.
+         * {@link SubscriptionManager#getPhoneNumber(int)},
+         * {@link SubscriptionManager#getPhoneNumber(int, int)} with
+         * {@link SubscriptionManager#PHONE_NUMBER_SOURCE_IMS}
+         * In order to provide the phone number to the APIs, the framework extracts the phone
+         * number from the message received from the carrier server. If the carrier does not use
+         * global number format, the framework could not provide phone number.
+         * <p>
+         * If not set or set to false value, the framework handle only global number format URI.
+         * @hide
+         */
+        public static final String KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL =
+                KEY_PREFIX + "allow_non_global_phone_number_format_bool";
+
         private Ims() {}
 
         private static PersistableBundle getDefaults() {
@@ -5925,6 +5939,8 @@
             defaults.putString(KEY_IMS_USER_AGENT_STRING,
                                "#MANUFACTURER#_#MODEL#_Android#AV#_#BUILD#");
 
+            defaults.putBoolean(KEY_ALLOW_NON_GLOBAL_PHONE_NUMBER_FORMAT_BOOL, false);
+
             return defaults;
         }
     }
@@ -9384,6 +9400,39 @@
             "missed_incoming_call_sms_pattern_string_array";
 
     /**
+     * Indicate the satellite services supported per provider by a carrier.
+     *
+     * Key is the PLMN of a satellite provider. Value should be an integer array of supported
+     * services with the following value:
+     * <ul>
+     * <li>1 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VOICE}</li>
+     * <li>2 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_DATA}</li>
+     * <li>3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS}</li>
+     * <li>4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO}</li>
+     * <li>5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}</li>
+     * </ul>
+     * <p>
+     * If this carrier config is not present, the overlay config
+     * {@code config_satellite_services_supported_by_providers} will be used. If the carrier config
+     * is present, the supported satellite services will be identified as follows:
+     * <ul>
+     * <li>For the PLMN that exists in both provider supported satellite services and carrier
+     * supported satellite services, the supported services will be the intersection of the two
+     * sets.</li>
+     * <li>For the PLMN that is present in provider supported satellite services but not in carrier
+     * supported satellite services, the provider supported satellite services will be used.</li>
+     * <li>For the PLMN that is present in carrier supported satellite services but not in provider
+     * supported satellite services, the PLMN will be ignored.</li>
+     * </ul>
+     *
+     * This config is empty by default.
+     *
+     * @hide
+     */
+    public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE =
+            "carrier_supported_satellite_services_per_provider_bundle";
+
+    /**
      * Indicating whether DUN APN should be disabled when the device is roaming. In that case,
      * the default APN (i.e. internet) will be used for tethering.
      *
@@ -9605,6 +9654,7 @@
      *
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
      * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
+     * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
      */
     public static final String
             KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
@@ -9730,6 +9780,27 @@
     public static final String KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY =
             "iwlan_handover_policy_string_array";
 
+    /**
+     * Score table for {@link TelephonyManager#MOBILE_DATA_POLICY_AUTO_DATA_SWITCH}. The score is
+     * used in conjunction with a tolerance value defined in resource config
+     * {@code auto_data_switch_score_tolerance}, greater than which device will switch to the sub
+     * with higher score.
+     * Possible keys are network type name string(also see {@link #KEY_BANDWIDTH_STRING_ARRAY}).
+     * Value should be "score_level_0, score_level_1, score_level_2, score_level_3,score_level_4".
+     * Each network type must have 5 scores correspond to {@link CellSignalStrength}, where score is
+     * a non-negative integer. A score of 0 is treated the same as data out of service.
+     *
+     * For NR (5G), the following network names should be used:
+     * - NR_NSA: NR NSA, sub-6 frequencies
+     * - NR_NSA_MMWAVE: NR NSA, mmwave frequencies
+     * - NR_SA: NR SA, sub-6 frequencies
+     * - NR_SA_MMWAVE: NR SA, mmwave frequencies
+     *
+     * @hide
+     */
+    public static final String KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_BUNDLE =
+            "auto_data_switch_rat_signal_score_string_bundle";
+
     /** The default value for every variable. */
     private static final PersistableBundle sDefaults;
 
@@ -9876,9 +9947,9 @@
         sDefaults.putString(KEY_CARRIER_ERI_FILE_NAME_STRING, "eri.xml");
         sDefaults.putInt(KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT, 7200);
         sDefaults.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS,
-                new String[]{"default", "mms", "dun", "supl"});
+                new String[]{"default", "mms", "dun", "supl", "enterprise"});
         sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
-                new String[]{"default", "mms", "dun", "supl"});
+                new String[]{"default", "mms", "dun", "supl", "enterprise"});
         sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY,
                 new int[] {TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_1xRTT,
                         TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyManager.NETWORK_TYPE_EVDO_A,
@@ -10367,6 +10438,9 @@
                 });
         sDefaults.putBoolean(KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL, false);
         sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]);
+        sDefaults.putPersistableBundle(
+                KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                PersistableBundle.EMPTY);
         sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
         sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
         sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
@@ -10404,6 +10478,51 @@
         sDefaults.putStringArray(KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY, new String[]{
                 "source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, "
                         + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"});
+        PersistableBundle auto_data_switch_rat_signal_score_string_bundle = new PersistableBundle();
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "NR_SA_MMWAVE", new int[]{10000, 13227, 16000, 18488, 20017});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "NR_NSA_MMWAVE", new int[]{8000, 10227, 12488, 15017, 15278});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "LTE", new int[]{3731, 5965, 8618, 11179, 13384});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "NR_SA", new int[]{5288, 6795, 6955, 7562, 9713});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "NR_NSA", new int[]{5463, 6827, 8029, 9007, 9428});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "UMTS", new int[]{100, 169, 183, 192, 300});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "eHRPD", new int[]{10, 400, 600, 800, 1000});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "TD_SCDMA", new int[]{1, 100, 500, 1000});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "iDEN", new int[]{1, 2, 10, 50, 100});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "EvDo_B", new int[]{1000, 1495, 2186, 2532, 2600});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "HSPA+", new int[]{1619, 2500, 3393, 4129, 4212});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "HSPA", new int[]{1000, 1495, 2186, 2532, 2544});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "HSUPA", new int[]{1500, 1919, 2132, 2362, 2704});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "HSDPA", new int[]{1500, 1732, 4000, 7000, 8000});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "EvDo_A", new int[]{600, 840, 1200, 1300, 1400});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "EvDo_0", new int[]{300, 600, 1000, 1500, 2000});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "1xRTT", new int[]{50, 60, 70, 80});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "EDGE", new int[]{154, 169, 183, 192, 267});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "GPRS", new int[]{15, 30, 40, 45, 50});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "CDMA", new int[]{1, 50, 100, 300, 2000});
+        auto_data_switch_rat_signal_score_string_bundle.putIntArray(
+                "GSM", new int[]{1, 2, 10, 50, 100});
+        sDefaults.putPersistableBundle(KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_BUNDLE,
+                auto_data_switch_rat_signal_score_string_bundle);
         sDefaults.putInt(KEY_CELLULAR_USAGE_SETTING_INT,
                 SubscriptionManager.USAGE_SETTING_UNKNOWN);
         // Default data stall recovery configurations.
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index b0552b4..f012ab5 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -203,6 +203,12 @@
      */
     public static final int SERVICE_TYPE_EMERGENCY  = 5;
 
+    /** @hide  */
+    public static final int FIRST_SERVICE_TYPE = SERVICE_TYPE_VOICE;
+
+    /** @hide  */
+    public static final int LAST_SERVICE_TYPE = SERVICE_TYPE_EMERGENCY;
+
     @Domain
     private final int mDomain;
 
@@ -240,7 +246,7 @@
     private final boolean mEmergencyOnly;
 
     @ServiceType
-    private final ArrayList<Integer> mAvailableServices;
+    private ArrayList<Integer> mAvailableServices;
 
     @Nullable
     private CellIdentity mCellIdentity;
@@ -257,6 +263,9 @@
     // Updated based on the accessNetworkTechnology
     private boolean mIsUsingCarrierAggregation;
 
+    // Set to {@code true} when network is a non-terrestrial network.
+    private boolean mIsNonTerrestrialNetwork;
+
     /**
      * @param domain Network domain. Must be a {@link Domain}. For transport type
      * {@link AccessNetworkConstants#TRANSPORT_TYPE_WLAN}, this must set to {@link #DOMAIN_PS}.
@@ -280,6 +289,7 @@
      * @param rplmn the registered plmn or the last plmn for attempted registration if reg failed.
      * @param voiceSpecificInfo Voice specific registration information.
      * @param dataSpecificInfo Data specific registration information.
+     * @param isNonTerrestrialNetwork {@code true} if network is a non-terrestrial network.
      */
     private NetworkRegistrationInfo(@Domain int domain, @TransportType int transportType,
             @RegistrationState int registrationState,
@@ -287,7 +297,8 @@
             boolean emergencyOnly, @Nullable @ServiceType List<Integer> availableServices,
             @Nullable CellIdentity cellIdentity, @Nullable String rplmn,
             @Nullable VoiceSpecificRegistrationInfo voiceSpecificInfo,
-            @Nullable DataSpecificRegistrationInfo dataSpecificInfo) {
+            @Nullable DataSpecificRegistrationInfo dataSpecificInfo,
+            boolean isNonTerrestrialNetwork) {
         mDomain = domain;
         mTransportType = transportType;
         mRegistrationState = registrationState;
@@ -304,6 +315,7 @@
         mRplmn = rplmn;
         mVoiceSpecificInfo = voiceSpecificInfo;
         mDataSpecificInfo = dataSpecificInfo;
+        mIsNonTerrestrialNetwork = isNonTerrestrialNetwork;
 
         updateNrState();
     }
@@ -322,7 +334,7 @@
         this(domain, transportType, registrationState, accessNetworkTechnology, rejectCause,
                 emergencyOnly, availableServices, cellIdentity, rplmn,
                 new VoiceSpecificRegistrationInfo(cssSupported, roamingIndicator,
-                        systemIsInPrl, defaultRoamingIndicator), null);
+                        systemIsInPrl, defaultRoamingIndicator), null, false);
     }
 
     /**
@@ -344,7 +356,7 @@
                         .setNrAvailable(isNrAvailable)
                         .setEnDcAvailable(isEndcAvailable)
                         .setVopsSupportInfo(vopsSupportInfo)
-                        .build());
+                        .build(), false);
     }
 
     private NetworkRegistrationInfo(Parcel source) {
@@ -366,6 +378,7 @@
         mNrState = source.readInt();
         mRplmn = source.readString();
         mIsUsingCarrierAggregation = source.readBoolean();
+        mIsNonTerrestrialNetwork = source.readBoolean();
     }
 
     /**
@@ -382,6 +395,7 @@
         mRoamingType = nri.mRoamingType;
         mAccessNetworkTechnology = nri.mAccessNetworkTechnology;
         mIsUsingCarrierAggregation = nri.mIsUsingCarrierAggregation;
+        mIsNonTerrestrialNetwork = nri.mIsNonTerrestrialNetwork;
         mRejectCause = nri.mRejectCause;
         mEmergencyOnly = nri.mEmergencyOnly;
         mAvailableServices = new ArrayList<>(nri.mAvailableServices);
@@ -596,6 +610,16 @@
     }
 
     /**
+     * Set available service types.
+     *
+     * @param availableServices The list of available services for this network.
+     * @hide
+     */
+    public void setAvailableServices(@NonNull @ServiceType List<Integer> availableServices) {
+        mAvailableServices = new ArrayList<>(availableServices);
+    }
+
+    /**
      * @return The access network technology {@link NetworkType}.
      */
     public @NetworkType int getAccessNetworkTechnology() {
@@ -658,6 +682,27 @@
     }
 
     /**
+     * Set whether the network is a non-terrestrial network.
+     *
+     * @param isNonTerrestrialNetwork {@code true} if network is a non-terrestrial network
+     *                                            else {@code false}.
+     * @hide
+     */
+    public void setIsNonTerrestrialNetwork(boolean isNonTerrestrialNetwork) {
+        mIsNonTerrestrialNetwork = isNonTerrestrialNetwork;
+    }
+
+    /**
+     * Get whether the network is a non-terrestrial network.
+     *
+     * @return {@code true} if network is a non-terrestrial network else {@code false}.
+     * @hide
+     */
+    public boolean isNonTerrestrialNetwork() {
+        return mIsNonTerrestrialNetwork;
+    }
+
+    /**
      * @hide
      */
     @Nullable
@@ -769,6 +814,7 @@
                         ? nrStateToString(mNrState) : "****")
                 .append(" rRplmn=").append(mRplmn)
                 .append(" isUsingCarrierAggregation=").append(mIsUsingCarrierAggregation)
+                .append(" isNonTerrestrialNetwork=").append(mIsNonTerrestrialNetwork)
                 .append("}").toString();
     }
 
@@ -777,7 +823,7 @@
         return Objects.hash(mDomain, mTransportType, mRegistrationState, mNetworkRegistrationState,
                 mRoamingType, mAccessNetworkTechnology, mRejectCause, mEmergencyOnly,
                 mAvailableServices, mCellIdentity, mVoiceSpecificInfo, mDataSpecificInfo, mNrState,
-                mRplmn, mIsUsingCarrierAggregation);
+                mRplmn, mIsUsingCarrierAggregation, mIsNonTerrestrialNetwork);
     }
 
     @Override
@@ -803,7 +849,8 @@
                 && Objects.equals(mVoiceSpecificInfo, other.mVoiceSpecificInfo)
                 && Objects.equals(mDataSpecificInfo, other.mDataSpecificInfo)
                 && TextUtils.equals(mRplmn, other.mRplmn)
-                && mNrState == other.mNrState;
+                && mNrState == other.mNrState
+                && mIsNonTerrestrialNetwork == other.mIsNonTerrestrialNetwork;
     }
 
     /**
@@ -827,6 +874,7 @@
         dest.writeInt(mNrState);
         dest.writeString(mRplmn);
         dest.writeBoolean(mIsUsingCarrierAggregation);
+        dest.writeBoolean(mIsNonTerrestrialNetwork);
     }
 
     /**
@@ -936,6 +984,8 @@
         @Nullable
         private VoiceSpecificRegistrationInfo mVoiceSpecificRegistrationInfo;
 
+        private boolean mIsNonTerrestrialNetwork;
+
         /**
          * Default constructor for Builder.
          */
@@ -964,6 +1014,7 @@
                 mVoiceSpecificRegistrationInfo = new VoiceSpecificRegistrationInfo(
                         nri.mVoiceSpecificInfo);
             }
+            mIsNonTerrestrialNetwork = nri.mIsNonTerrestrialNetwork;
         }
 
         /**
@@ -1111,6 +1162,19 @@
         }
 
         /**
+         * Set whether the network is a non-terrestrial network.
+         *
+         * @param isNonTerrestrialNetwork {@code true} if network is a non-terrestrial network
+         *                                            else {@code false}.
+         * @return The builder.
+         * @hide
+         */
+        public @NonNull Builder setIsNonTerrestrialNetwork(boolean isNonTerrestrialNetwork) {
+            mIsNonTerrestrialNetwork = isNonTerrestrialNetwork;
+            return this;
+        }
+
+        /**
          * Build the NetworkRegistrationInfo.
          * @return the NetworkRegistrationInfo object.
          * @hide
@@ -1120,7 +1184,7 @@
             return new NetworkRegistrationInfo(mDomain, mTransportType, mNetworkRegistrationState,
                     mAccessNetworkTechnology, mRejectCause, mEmergencyOnly, mAvailableServices,
                     mCellIdentity, mRplmn, mVoiceSpecificRegistrationInfo,
-                    mDataSpecificRegistrationInfo);
+                    mDataSpecificRegistrationInfo, mIsNonTerrestrialNetwork);
         }
     }
 }
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 523d0b0..74cfbea 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -2250,4 +2250,19 @@
             return false;
         }
     }
+
+    /**
+     * Get whether device is connected to a non-terrestrial network.
+     *
+     * @return {@code true} if device is connected to a non-terrestrial network else {@code false}.
+     * @hide
+     */
+    public boolean isUsingNonTerrestrialNetwork() {
+        synchronized (mNetworkRegistrationInfos) {
+            for (NetworkRegistrationInfo nri : mNetworkRegistrationInfos) {
+                if (nri.isNonTerrestrialNetwork()) return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2a6099a..340e4ab 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17608,6 +17608,16 @@
     public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15;
 
     /**
+     * Purchase premium capability failed because the user disabled the feature.
+     * Subsequent attempts will be throttled for the amount of time specified by
+     * {@link CarrierConfigManager
+     * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
+     * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
+     * @hide
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16;
+
+    /**
      * Results of the purchase premium capability request.
      * @hide
      */
@@ -17626,7 +17636,8 @@
             PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION,
-            PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP})
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED})
     public @interface PurchasePremiumCapabilityResult {}
 
     /**
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 4ba538e..1d423ca 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -23,14 +23,66 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-android_test {
-    name: "FlickerTests",
+filegroup {
+    name: "FlickerTestsBase-src",
+    srcs: ["src/com/android/server/wm/flicker/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsAppClose-src",
+    srcs: ["src/**/close/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsActivityEmbedding-src",
     srcs: [
-        "src/**/*.java",
-        "src/**/*.kt",
+        "src/**/activityembedding/*.kt",
+        "src/**/activityembedding/open/*.kt",
+        "src/**/activityembedding/close/*.kt",
+        "src/**/activityembedding/layoutchange/*.kt",
+        "src/**/activityembedding/pip/*.kt",
+        "src/**/activityembedding/rotation/*.kt",
+        "src/**/activityembedding/rtl/*.kt",
+        "src/**/activityembedding/splitscreen/*.kt",
     ],
-    manifest: "AndroidManifest.xml",
-    test_config: "AndroidTest.xml",
+}
+
+filegroup {
+    name: "FlickerTestsIme-src",
+    srcs: ["src/**/ime/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsAppLaunch-src",
+    srcs: ["src/**/launch/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsNotification-src",
+    srcs: ["src/**/notification/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsQuickswitch-src",
+    srcs: ["src/**/quickswitch/*.kt"],
+}
+
+filegroup {
+    name: "FlickerTestsRotation-src",
+    srcs: ["src/**/rotation/*.kt"],
+}
+
+filegroup {
+    name: "FlickerServiceTests-src",
+    srcs: [
+        "src/com/android/server/wm/flicker/service/**/*.kt",
+    ],
+}
+
+java_defaults {
+    name: "FlickerTestsDefault",
+    manifest: "manifests/AndroidManifest.xml",
+    test_config_template: "AndroidTestTemplate.xml",
     platform_apis: true,
     certificate: "platform",
     optimize: {
@@ -42,16 +94,127 @@
         "androidx.test.ext.junit",
         "flickertestapplib",
         "flickerlib",
-        "flickerlib-apphelpers",
         "flickerlib-helpers",
-        "truth-prebuilt",
-        "launcher-helper-lib",
-        "launcher-aosp-tapl",
         "platform-test-annotations",
-        "wm-flicker-window-extensions",
+        "wm-flicker-common-app-helpers",
     ],
     data: [
         ":FlickerTestApp",
+        "trace_config/*",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsOther",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestOther.xml"],
+    package_name: "com.android.server.wm.flicker",
+    instrumentation_target_package: "com.android.server.wm.flicker",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsAppClose-src",
+        ":FlickerTestsIme-src",
+        ":FlickerTestsAppLaunch-src",
+        ":FlickerTestsQuickswitch-src",
+        ":FlickerTestsRotation-src",
+        ":FlickerTestsNotification-src",
+        ":FlickerServiceTests-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsAppClose",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestAppClose.xml"],
+    package_name: "com.android.server.wm.flicker.close",
+    instrumentation_target_package: "com.android.server.wm.flicker.close",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsAppClose-src",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsActivityEmbedding-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsIme",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestIme.xml"],
+    package_name: "com.android.server.wm.flicker.ime",
+    instrumentation_target_package: "com.android.server.wm.flicker.ime",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsIme-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsAppLaunch",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestAppLaunch.xml"],
+    package_name: "com.android.server.wm.flicker.launch",
+    instrumentation_target_package: "com.android.server.wm.flicker.launch",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsAppLaunch-src",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsActivityEmbedding-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsNotification",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestNotification.xml"],
+    package_name: "com.android.server.wm.flicker.notification",
+    instrumentation_target_package: "com.android.server.wm.flicker.notification",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsNotification-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsQuickswitch",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestQuickswitch.xml"],
+    package_name: "com.android.server.wm.flicker.quickswitch",
+    instrumentation_target_package: "com.android.server.wm.flicker.quickswitch",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsQuickswitch-src",
+    ],
+}
+
+android_test {
+    name: "FlickerTestsRotation",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestRotation.xml"],
+    package_name: "com.android.server.wm.flicker.rotation",
+    instrumentation_target_package: "com.android.server.wm.flicker.rotation",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerTestsRotation-src",
+    ],
+    exclude_srcs: [
+        ":FlickerTestsActivityEmbedding-src",
+    ],
+}
+
+android_test {
+    name: "FlickerServiceTests",
+    defaults: ["FlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestService.xml"],
+    package_name: "com.android.server.wm.flicker.service",
+    instrumentation_target_package: "com.android.server.wm.flicker.service",
+    srcs: [
+        ":FlickerTestsBase-src",
+        ":FlickerServiceTests-src",
     ],
 }
 
diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/AndroidManifest.xml
deleted file mode 100644
index 462f91b..0000000
--- a/tests/FlickerTests/AndroidManifest.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.server.wm.flicker">
-
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
-    <!-- Read and write traces from external storage -->
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <!-- Allow the test to write directly to /sdcard/ -->
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
-    <!-- Write secure settings -->
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <!-- Capture screen contents -->
-    <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
-    <!-- Enable / Disable tracing !-->
-    <uses-permission android:name="android.permission.DUMP" />
-    <!-- Force-stop test apps -->
-    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
-    <!-- Run layers trace -->
-    <uses-permission android:name="android.permission.HARDWARE_TEST"/>
-    <!-- Capture screen recording -->
-    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
-    <!-- Workaround grant runtime permission exception from b/152733071 -->
-    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
-    <uses-permission android:name="android.permission.READ_LOGS"/>
-    <!-- ATM.removeRootTasksWithActivityTypes() -->
-    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
-    <!-- ActivityOptions.makeCustomTaskAnimation() -->
-    <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
-    <!-- Allow the test to write directly to /sdcard/ -->
-    <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
-        <uses-library android:name="android.test.runner"/>
-        <uses-library android:name="androidx.window.extensions" android:required="false"/>
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.server.wm.flicker"
-                     android:label="WindowManager Flicker Tests">
-    </instrumentation>
-</manifest>
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
deleted file mode 100644
index 32ff243..0000000
--- a/tests/FlickerTests/AndroidTest.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright 2018 Google Inc. All Rights Reserved.
- -->
-<configuration description="Runs WindowManager Flicker Tests">
-    <option name="test-tag" value="FlickerTests" />
-    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
-        <!-- keeps the screen on during tests -->
-        <option name="screen-always-on" value="on" />
-        <!-- prevents the phone from restarting -->
-        <option name="force-skip-system-props" value="true" />
-        <!-- set WM tracing verbose level to all -->
-        <option name="run-command" value="cmd window tracing level all" />
-        <!-- set WM tracing to frame (avoid incomplete states) -->
-        <option name="run-command" value="cmd window tracing frame" />
-        <!-- ensure lock screen mode is swipe -->
-        <option name="run-command" value="locksettings set-disabled false" />
-        <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
-        <option name="run-command" value="pm disable com.google.android.internal.betterbug" />
-        <!-- restart launcher to activate TAPL -->
-        <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
-        <!-- Ensure output directory is empty at the start -->
-        <option name="run-command" value="rm -rf /sdcard/flicker" />
-        <!-- Increase trace size: 20mb for WM and 80mb for SF -->
-        <option name="run-command" value="cmd window tracing size 20480" />
-        <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
-        <option name="run-command" value="settings put system show_touches 1" />
-        <option name="run-command" value="settings put system pointer_location 1" />
-        <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
-        <option name="teardown-command" value="settings delete system show_touches" />
-        <option name="teardown-command" value="settings delete system pointer_location" />
-        <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true"/>
-        <option name="test-file-name" value="FlickerTests.apk"/>
-        <option name="test-file-name" value="FlickerTestApp.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="package" value="com.android.server.wm.flicker"/>
-        <option name="shell-timeout" value="6600s" />
-        <option name="test-timeout" value="6600s" />
-        <option name="hidden-api-checks" value="false" />
-    </test>
-    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
-        <option name="directory-keys" value="/sdcard/flicker" />
-        <option name="collect-on-run-ended-only" value="true" />
-        <option name="clean-up" value="true" />
-    </metrics_collector>
-</configuration>
diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml
new file mode 100644
index 0000000..44a8245
--- /dev/null
+++ b/tests/FlickerTests/AndroidTestTemplate.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright 2018 Google Inc. All Rights Reserved.
+ -->
+<configuration description="Runs WindowManager {MODULE}">
+    <option name="test-tag" value="FlickerTests"/>
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false"/>
+
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <!-- keeps the screen on during tests -->
+        <option name="screen-always-on" value="on"/>
+        <!-- prevents the phone from restarting -->
+        <option name="force-skip-system-props" value="true"/>
+        <!-- set WM tracing verbose level to all -->
+        <option name="run-command" value="cmd window tracing level all"/>
+        <!-- set WM tracing to frame (avoid incomplete states) -->
+        <option name="run-command" value="cmd window tracing frame"/>
+        <!-- ensure lock screen mode is swipe -->
+        <option name="run-command" value="locksettings set-disabled false"/>
+        <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
+        <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
+        <!-- restart launcher to activate TAPL -->
+        <option name="run-command"
+                value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
+        <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+        <option name="run-command" value="cmd window tracing size 20480"/>
+        <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="test-user-token" value="%TEST_USER%"/>
+        <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
+        <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
+        <option name="run-command" value="settings put system show_touches 1"/>
+        <option name="run-command" value="settings put system pointer_location 1"/>
+        <option name="teardown-command"
+                value="settings delete secure show_ime_with_hard_keyboard"/>
+        <option name="teardown-command" value="settings delete system show_touches"/>
+        <option name="teardown-command" value="settings delete system pointer_location"/>
+        <option name="teardown-command"
+                value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true"/>
+        <option name="test-file-name" value="{MODULE}.apk"/>
+        <option name="test-file-name" value="FlickerTestApp.apk"/>
+    </target_preparer>
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file"
+                key="trace_config.textproto"
+                value="/data/misc/perfetto-traces/trace_config.textproto"
+        />
+        <!--Install the content provider automatically when we push some file in sdcard folder.-->
+        <!--Needed to avoid the installation during the test suite.-->
+        <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="{PACKAGE}"/>
+        <option name="shell-timeout" value="6600s"/>
+        <option name="test-timeout" value="6600s"/>
+        <option name="hidden-api-checks" value="false"/>
+        <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
+        <option name="instrumentation-arg"
+                key="perfetto_config_file"
+                value="trace_config.textproto"
+        />
+        <option name="instrumentation-arg" key="per_run" value="true"/>
+    </test>
+    <!-- Needed for pulling the collected trace config on to the host -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker.close/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker.ime/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker.launch/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker.quickswitch/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker.rotation/files"/>
+        <option name="directory-keys"
+                value="/data/user/0/com.android.server.wm.flicker.notification/files"/>
+        <option name="directory-keys"
+            value="/data/user/0/com.android.server.wm.flicker.service/files"/>
+        <option name="collect-on-run-ended-only" value="true"/>
+        <option name="clean-up" value="true"/>
+    </metrics_collector>
+</configuration>
diff --git a/tests/FlickerTests/manifests/AndroidManifest.xml b/tests/FlickerTests/manifests/AndroidManifest.xml
new file mode 100644
index 0000000..1a34d9e
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifest.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.android.server.wm.flicker">
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+    <!-- Read and write traces from external storage -->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <!-- Allow the test to write directly to /sdcard/ -->
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <!-- Write secure settings -->
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <!-- Capture screen contents -->
+    <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+    <!-- Enable / Disable tracing !-->
+    <uses-permission android:name="android.permission.DUMP" />
+    <!-- Force-stop test apps -->
+    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
+    <!-- Run layers trace -->
+    <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+    <!-- Capture screen recording -->
+    <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
+    <!-- Workaround grant runtime permission exception from b/152733071 -->
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+    <uses-permission android:name="android.permission.READ_LOGS"/>
+    <!-- ATM.removeRootTasksWithActivityTypes() -->
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+    <!-- ActivityOptions.makeCustomTaskAnimation() -->
+    <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
+    <!-- Allow the test to write directly to /sdcard/ -->
+    <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="androidx.window.extensions" android:required="false"/>
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+    </application>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestAppClose.xml b/tests/FlickerTests/manifests/AndroidManifestAppClose.xml
new file mode 100644
index 0000000..4cdcb90
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestAppClose.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.wm.flicker.close">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.close"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestAppLaunch.xml b/tests/FlickerTests/manifests/AndroidManifestAppLaunch.xml
new file mode 100644
index 0000000..659a745
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestAppLaunch.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.wm.flicker.launch">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.launch"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestIme.xml b/tests/FlickerTests/manifests/AndroidManifestIme.xml
new file mode 100644
index 0000000..abd03af
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestIme.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.wm.flicker.ime">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.ime"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestNotification.xml b/tests/FlickerTests/manifests/AndroidManifestNotification.xml
new file mode 100644
index 0000000..ad33dee
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestNotification.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.wm.flicker.close">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.notification"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestOther.xml b/tests/FlickerTests/manifests/AndroidManifestOther.xml
new file mode 100644
index 0000000..47749b8
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestOther.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.wm.flicker">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestQuickswitch.xml b/tests/FlickerTests/manifests/AndroidManifestQuickswitch.xml
new file mode 100644
index 0000000..203035d
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestQuickswitch.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.wm.flicker.quickswitch">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.quickswitch"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestRotation.xml b/tests/FlickerTests/manifests/AndroidManifestRotation.xml
new file mode 100644
index 0000000..2852cf2
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestRotation.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.wm.flicker.rotation">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.rotation"
+                     android:label="WindowManager Flicker Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/manifests/AndroidManifestService.xml b/tests/FlickerTests/manifests/AndroidManifestService.xml
new file mode 100644
index 0000000..3a7bc509
--- /dev/null
+++ b/tests/FlickerTests/manifests/AndroidManifestService.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.wm.flicker.service">
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.wm.flicker.service"
+                     android:label="WindowManager Flicker Service Tests">
+    </instrumentation>
+</manifest>
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index efd9b00..7c9c05d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -21,7 +21,7 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerBuilderProvider
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.util.Log
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.tapl.LauncherInstrumentation
@@ -37,7 +37,7 @@
 abstract class BaseTest
 @JvmOverloads
 constructor(
-    protected val flicker: FlickerTest,
+    protected val flicker: LegacyFlickerTest,
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
     protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
 ) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index ed9e14f..b18a1a8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -23,14 +23,14 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.common.traces.component.IComponentNameMatcher
 import android.tools.common.traces.wm.WindowManagerTrace
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.helpers.WindowUtils
 
 /**
  * Checks that [ComponentNameMatcher.STATUS_BAR] window is visible and above the app windows in all
  * WM trace entries
  */
-fun FlickerTest.statusBarWindowIsAlwaysVisible() {
+fun LegacyFlickerTest.statusBarWindowIsAlwaysVisible() {
     assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.STATUS_BAR) }
 }
 
@@ -38,7 +38,7 @@
  * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows in all WM
  * trace entries
  */
-fun FlickerTest.navBarWindowIsAlwaysVisible() {
+fun LegacyFlickerTest.navBarWindowIsAlwaysVisible() {
     assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
@@ -46,7 +46,7 @@
  * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
  * start and end of the WM trace
  */
-fun FlickerTest.navBarWindowIsVisibleAtStartAndEnd() {
+fun LegacyFlickerTest.navBarWindowIsVisibleAtStartAndEnd() {
     this.navBarWindowIsVisibleAtStart()
     this.navBarWindowIsVisibleAtEnd()
 }
@@ -55,7 +55,7 @@
  * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
  * start of the WM trace
  */
-fun FlickerTest.navBarWindowIsVisibleAtStart() {
+fun LegacyFlickerTest.navBarWindowIsVisibleAtStart() {
     assertWmStart { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
@@ -63,7 +63,7 @@
  * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the end
  * of the WM trace
  */
-fun FlickerTest.navBarWindowIsVisibleAtEnd() {
+fun LegacyFlickerTest.navBarWindowIsVisibleAtEnd() {
     assertWmEnd { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
@@ -71,7 +71,7 @@
  * Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in all WM
  * trace entries
  */
-fun FlickerTest.taskBarWindowIsAlwaysVisible() {
+fun LegacyFlickerTest.taskBarWindowIsAlwaysVisible() {
     assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
@@ -79,7 +79,7 @@
  * Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in all WM
  * trace entries
  */
-fun FlickerTest.taskBarWindowIsVisibleAtEnd() {
+fun LegacyFlickerTest.taskBarWindowIsVisibleAtEnd() {
     assertWmEnd { this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
@@ -93,43 +93,45 @@
  * @param allStates if all states should be checked, othersie, just initial and final
  */
 @JvmOverloads
-fun FlickerTest.entireScreenCovered(allStates: Boolean = true) {
+fun LegacyFlickerTest.entireScreenCovered(allStates: Boolean = true) {
     if (allStates) {
         assertLayers {
             this.invoke("entireScreenCovered") { entry ->
-                entry.entry.displays.filter { it.isOn }.forEach { display ->
-                    entry.visibleRegion().coversAtLeast(display.layerStackSpace)
-                }
+                entry.entry.displays
+                    .filter { it.isOn }
+                    .forEach { display ->
+                        entry.visibleRegion().coversAtLeast(display.layerStackSpace)
+                    }
             }
         }
     } else {
         assertLayersStart {
-            this.entry.displays.filter { it.isOn }.forEach { display ->
-                this.visibleRegion().coversAtLeast(display.layerStackSpace)
-            }
+            this.entry.displays
+                .filter { it.isOn }
+                .forEach { display -> this.visibleRegion().coversAtLeast(display.layerStackSpace) }
         }
         assertLayersEnd {
-            this.entry.displays.filter { it.isOn }.forEach { display ->
-                this.visibleRegion().coversAtLeast(display.layerStackSpace)
-            }
+            this.entry.displays
+                .filter { it.isOn }
+                .forEach { display -> this.visibleRegion().coversAtLeast(display.layerStackSpace) }
         }
     }
 }
 
 /** Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start of the SF trace */
-fun FlickerTest.navBarLayerIsVisibleAtStart() {
+fun LegacyFlickerTest.navBarLayerIsVisibleAtStart() {
     assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
 /** Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the end of the SF trace */
-fun FlickerTest.navBarLayerIsVisibleAtEnd() {
+fun LegacyFlickerTest.navBarLayerIsVisibleAtEnd() {
     assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
 }
 
 /**
  * Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start and end of the SF trace
  */
-fun FlickerTest.navBarLayerIsVisibleAtStartAndEnd() {
+fun LegacyFlickerTest.navBarLayerIsVisibleAtStartAndEnd() {
     this.navBarLayerIsVisibleAtStart()
     this.navBarLayerIsVisibleAtEnd()
 }
@@ -137,18 +139,18 @@
 /**
  * Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start and end of the SF trace
  */
-fun FlickerTest.taskBarLayerIsVisibleAtStartAndEnd() {
+fun LegacyFlickerTest.taskBarLayerIsVisibleAtStartAndEnd() {
     this.taskBarLayerIsVisibleAtStart()
     this.taskBarLayerIsVisibleAtEnd()
 }
 
 /** Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start of the SF trace */
-fun FlickerTest.taskBarLayerIsVisibleAtStart() {
+fun LegacyFlickerTest.taskBarLayerIsVisibleAtStart() {
     assertLayersStart { this.isVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
 /** Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the end of the SF trace */
-fun FlickerTest.taskBarLayerIsVisibleAtEnd() {
+fun LegacyFlickerTest.taskBarLayerIsVisibleAtEnd() {
     assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
 }
 
@@ -156,7 +158,7 @@
  * Checks that [ComponentNameMatcher.STATUS_BAR] layer is visible at the start and end of the SF
  * trace
  */
-fun FlickerTest.statusBarLayerIsVisibleAtStartAndEnd() {
+fun LegacyFlickerTest.statusBarLayerIsVisibleAtStartAndEnd() {
     assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
     assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
 }
@@ -165,7 +167,7 @@
  * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start of
  * the SF trace
  */
-fun FlickerTest.navBarLayerPositionAtStart() {
+fun LegacyFlickerTest.navBarLayerPositionAtStart() {
     assertLayersStart {
         val display =
             this.entry.displays.firstOrNull { !it.isVirtual } ?: error("There is no display!")
@@ -180,7 +182,7 @@
  * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the end of
  * the SF trace
  */
-fun FlickerTest.navBarLayerPositionAtEnd() {
+fun LegacyFlickerTest.navBarLayerPositionAtEnd() {
     assertLayersEnd {
         val display =
             this.entry.displays.minByOrNull { it.id }
@@ -196,7 +198,7 @@
  * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start and
  * end of the SF trace
  */
-fun FlickerTest.navBarLayerPositionAtStartAndEnd() {
+fun LegacyFlickerTest.navBarLayerPositionAtStartAndEnd() {
     navBarLayerPositionAtStart()
     navBarLayerPositionAtEnd()
 }
@@ -205,7 +207,7 @@
  * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the start
  * of the SF trace
  */
-fun FlickerTest.statusBarLayerPositionAtStart(
+fun LegacyFlickerTest.statusBarLayerPositionAtStart(
     wmTrace: WindowManagerTrace? = this.reader.readWmTrace()
 ) {
     // collect navbar position for the equivalent WM state
@@ -221,7 +223,7 @@
  * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the end of
  * the SF trace
  */
-fun FlickerTest.statusBarLayerPositionAtEnd(
+fun LegacyFlickerTest.statusBarLayerPositionAtEnd(
     wmTrace: WindowManagerTrace? = this.reader.readWmTrace()
 ) {
     // collect navbar position for the equivalent WM state
@@ -237,7 +239,7 @@
  * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the start
  * and end of the SF trace
  */
-fun FlickerTest.statusBarLayerPositionAtStartAndEnd() {
+fun LegacyFlickerTest.statusBarLayerPositionAtStartAndEnd() {
     statusBarLayerPositionAtStart()
     statusBarLayerPositionAtEnd()
 }
@@ -246,25 +248,25 @@
  * Asserts that the visibleRegion of the [ComponentNameMatcher.SNAPSHOT] layer can cover the
  * visibleRegion of the given app component exactly
  */
-fun FlickerTest.snapshotStartingWindowLayerCoversExactlyOnApp(component: IComponentNameMatcher) {
+fun LegacyFlickerTest.snapshotStartingWindowLayerCoversExactlyOnApp(
+    component: IComponentNameMatcher
+) {
     assertLayers {
         invoke("snapshotStartingWindowLayerCoversExactlyOnApp") {
             val snapshotLayers =
                 it.subjects.filter { subject ->
-                    subject.name.contains(ComponentNameMatcher.SNAPSHOT.toLayerName()) &&
+                    ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(subject.layer) &&
                         subject.isVisible
                 }
+            val visibleAreas =
+                snapshotLayers
+                    .mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion }
+                    .toTypedArray()
+            val snapshotRegion = RegionSubject(visibleAreas, timestamp)
             // Verify the size of snapshotRegion covers appVisibleRegion exactly in animation.
-            if (snapshotLayers.isNotEmpty()) {
-                val visibleAreas =
-                    snapshotLayers
-                        .mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion }
-                        .toTypedArray()
-                val snapshotRegion = RegionSubject(visibleAreas, timestamp)
+            if (snapshotRegion.region.isNotEmpty) {
                 val appVisibleRegion = it.visibleRegion(component)
-                if (snapshotRegion.region.isNotEmpty) {
-                    snapshotRegion.coversExactly(appVisibleRegion.region)
-                }
+                snapshotRegion.coversExactly(appVisibleRegion.region)
             }
         }
     }
@@ -307,7 +309,7 @@
  *      otherwise we won't and the layer must appear immediately.
  * ```
  */
-fun FlickerTest.replacesLayer(
+fun LegacyFlickerTest.replacesLayer(
     originalLayer: IComponentNameMatcher,
     newLayer: IComponentNameMatcher,
     ignoreEntriesWithRotationLayer: Boolean = false,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
index 7ef4d93..4517644 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
@@ -16,12 +16,15 @@
 
 package com.android.server.wm.flicker.activityembedding
 
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
 import org.junit.Before
+import org.junit.Test
 
-abstract class ActivityEmbeddingTestBase(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class ActivityEmbeddingTestBase(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     val testApp = ActivityEmbeddingAppHelper(instrumentation)
 
     @Before
@@ -29,4 +32,14 @@
         // The test should only be run on devices that support ActivityEmbedding.
         ActivityEmbeddingAppHelper.assumeActivityEmbeddingSupportedDevice()
     }
+
+    /** Asserts the background animation layer is never visible during bounds change transition. */
+    @Presubmit
+    @Test
+    fun backgroundLayerNeverVisible() {
+        val backgroundColorLayer = ComponentNameMatcher("", "Animation Background")
+        flicker.assertLayers {
+            isInvisible(backgroundColorLayer)
+        }
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt
deleted file mode 100644
index ed17059..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt
+++ /dev/null
@@ -1,129 +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.server.wm.flicker.activityembedding
-
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test opening an activity that will launch another activity as ActivityEmbedding placeholder in
- * split.
- *
- * To run this test: `atest FlickerTests:OpenActivityEmbeddingPlaceholderSplitTest`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenActivityEmbeddingPlaceholderSplitTest(flicker: FlickerTest) :
-    ActivityEmbeddingTestBase(flicker) {
-
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit = {
-        setup {
-            tapl.setExpectedRotationCheckEnabled(false)
-            testApp.launchViaIntent(wmHelper)
-        }
-        transitions { testApp.launchPlaceholderSplit(wmHelper) }
-        teardown {
-            tapl.goHome()
-            testApp.exit(wmHelper)
-        }
-    }
-
-    /** Main activity should become invisible after launching the placeholder primary activity. */
-    @Presubmit
-    @Test
-    fun mainActivityWindowBecomesInvisible() {
-        flicker.assertWm {
-            isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
-                .then()
-                .isAppWindowInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
-        }
-    }
-
-    /** Main activity should become invisible after launching the placeholder primary activity. */
-    @Presubmit
-    @Test
-    fun mainActivityLayerBecomesInvisible() {
-        flicker.assertLayers {
-            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
-                .then()
-                .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
-        }
-    }
-
-    /**
-     * Placeholder primary and secondary should become visible after launch. The windows are not
-     * necessarily to become visible at the same time.
-     */
-    @Presubmit
-    @Test
-    fun placeholderSplitWindowsBecomeVisible() {
-        flicker.assertWm {
-            notContains(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
-                .then()
-                .isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
-                .then()
-                .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
-        }
-        flicker.assertWm {
-            notContains(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
-                .then()
-                .isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
-                .then()
-                .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
-        }
-    }
-
-    /** Placeholder primary and secondary should become visible together after launch. */
-    @Presubmit
-    @Test
-    fun placeholderSplitLayersBecomeVisible() {
-        flicker.assertLayers {
-            isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
-            isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
-                .then()
-                .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
-                .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
-        }
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt
deleted file mode 100644
index 8638288..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.activityembedding
-
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test opening a secondary activity that will split with the main activity.
- *
- * To run this test: `atest FlickerTests:OpenActivityEmbeddingSecondaryToSplitTest`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenActivityEmbeddingSecondaryToSplitTest(flicker: FlickerTest) :
-    ActivityEmbeddingTestBase(flicker) {
-
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit = {
-        setup {
-            tapl.setExpectedRotationCheckEnabled(false)
-            testApp.launchViaIntent(wmHelper)
-        }
-        transitions { testApp.launchSecondaryActivity(wmHelper) }
-        teardown {
-            tapl.goHome()
-            testApp.exit(wmHelper)
-        }
-    }
-
-    /** Main activity should remain visible when enter split from fullscreen. */
-    @Presubmit
-    @Test
-    fun mainActivityWindowIsAlwaysVisible() {
-        flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
-    }
-
-    /**
-     * Main activity surface is animated from fullscreen to ActivityEmbedding split. During the
-     * transition, there is a period of time that it is covered by a snapshot of itself.
-     */
-    @Presubmit
-    @Test
-    fun mainActivityLayerIsAlwaysVisible() {
-        flicker.assertLayers {
-            isVisible(
-                ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT.or(
-                    ComponentNameMatcher.TRANSITION_SNAPSHOT
-                )
-            )
-        }
-        flicker.assertLayersEnd {
-            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
-                .isInvisible(ComponentNameMatcher.TRANSITION_SNAPSHOT)
-        }
-    }
-
-    /** Secondary activity should become visible after launching into split. */
-    @Presubmit
-    @Test
-    fun secondaryActivityWindowBecomesVisible() {
-        flicker.assertWm {
-            notContains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
-                .then()
-                .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
-                .then()
-                .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
-        }
-    }
-
-    /** Secondary activity should become visible after launching into split. */
-    @Presubmit
-    @Test
-    fun secondaryActivityLayerBecomesVisible() {
-        flicker.assertLayers {
-            isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
-                .then()
-                .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
-        }
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
new file mode 100644
index 0000000..0c36c59
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding.close
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test closing a secondary activity in a split.
+ *
+ * Setup: Launch A|B in split with B being the secondary activity. Transitions: Finish B and expect
+ * A to become fullscreen.
+ *
+ * To run this test: `atest FlickerTestsOther:CloseSecondaryActivityInSplitTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class CloseSecondaryActivityInSplitTest(flicker: LegacyFlickerTest) :
+    ActivityEmbeddingTestBase(flicker) {
+
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            // Launches fullscreen A.
+            testApp.launchViaIntent(wmHelper)
+            // Launches a split A|B and waits for both activities to show.
+            testApp.launchSecondaryActivity(wmHelper)
+            // Get fullscreen bounds
+            startDisplayBounds =
+                wmHelper.currentState.layerState.physicalDisplayBounds
+                    ?: error("Can't get display bounds")
+        }
+        transitions {
+            // Finish secondary activity B.
+            testApp.finishSecondaryActivity(wmHelper)
+            // Expect the main activity A to expand into fullscreen.
+            wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /** Main activity is always visible and becomes fullscreen in the end. */
+    @Presubmit
+    @Test
+    fun mainActivityWindowBecomesFullScreen() {
+        flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+        flicker.assertWmEnd {
+            this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .coversExactly(startDisplayBounds)
+        }
+    }
+
+    /** Main activity surface is animated from split to fullscreen. */
+    @Presubmit
+    @Test
+    fun mainActivityLayerIsAlwaysVisible() {
+        flicker.assertLayers {
+            isVisible(
+                ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT.or(
+                    ComponentNameMatcher.TRANSITION_SNAPSHOT
+                )
+            )
+        }
+        flicker.assertLayersEnd {
+            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .isInvisible(ComponentNameMatcher.TRANSITION_SNAPSHOT)
+        }
+    }
+
+    /** Secondary activity should destroy and become invisible. */
+    @Presubmit
+    @Test
+    fun secondaryActivityWindowFinishes() {
+        flicker.assertWm {
+            contains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .notContains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    @Presubmit
+    @Test
+    fun secondaryActivityLayerFinishes() {
+        flicker.assertLayers {
+            isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    companion object {
+        /** {@inheritDoc} */
+        private var startDisplayBounds = Rect.EMPTY
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
new file mode 100644
index 0000000..cfc0d8b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding.layoutchange
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import androidx.test.filters.RequiresDevice
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test changing split ratio at runtime on a horizona split.
+ *
+ * Setup: Launch A|B in horizontal split with B being the secondary activity, by default A and B
+ * windows are equal in size. B is on the top and A is on the bottom.
+ * Transitions:
+ * Change the split ratio to A:B=0.7:0.3, expect bounds change for both A and B.
+ *
+ * To run this test: `atest FlickerTestsOther:HorizontalSplitChangeRatioTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class HorizontalSplitChangeRatioTest(flicker: LegacyFlickerTest) :
+        ActivityEmbeddingTestBase(flicker) {
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            testApp.launchViaIntent(wmHelper)
+            testApp.launchSecondaryActivityHorizontally(wmHelper)
+            startDisplayBounds =
+                    wmHelper.currentState.layerState.physicalDisplayBounds
+                            ?: error("Display not found")
+        }
+        transitions {
+            testApp.changeSecondaryActivityRatio(wmHelper)
+        }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /** Assert the Main activity window is always visible. */
+    @Presubmit
+    @Test
+    fun mainActivityWindowIsAlwaysVisible() {
+        flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+    }
+
+    /** Assert the Main activity window is always visible. */
+    @Presubmit
+    @Test
+    fun mainActivityLayerIsAlwaysVisible() {
+        flicker.assertLayers { isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+    }
+
+    /** Assert the Secondary activity window is always visible. */
+    @Presubmit
+    @Test
+    fun secondaryActivityWindowIsAlwaysVisible() {
+        flicker.assertWm {
+            isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) }
+    }
+
+    /** Assert the Secondary activity window is always visible. */
+    @Presubmit
+    @Test
+    fun secondaryActivityLayerIsAlwaysVisible() {
+        flicker.assertLayers { isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) }
+    }
+
+    /** Assert the Main and Secondary activity change height during the transition. */
+    @Presubmit
+    @Test
+    fun secondaryActivityAdjustsHeightRuntime() {
+        flicker.assertLayersStart {
+            val topLayerRegion =
+                    this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            val bottomLayerRegion =
+                    this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            // Compare dimensions of two splits, given we're using default split attributes,
+            // both activities take up the same visible size on the display.
+            check { "height" }
+                    .that(topLayerRegion.region.height).isEqual(bottomLayerRegion.region.height)
+            check { "width" }
+                    .that(topLayerRegion.region.width).isEqual(bottomLayerRegion.region.width)
+            topLayerRegion.notOverlaps(bottomLayerRegion.region)
+            // Layers of two activities sum to be fullscreen size on display.
+            topLayerRegion.plus(bottomLayerRegion.region).coversExactly(startDisplayBounds)
+        }
+
+        flicker.assertLayersEnd {
+            val topLayerRegion =
+                    this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            val bottomLayerRegion =
+                    this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            // Compare dimensions of two splits, given we're using default split attributes,
+            // both activities take up the same visible size on the display.
+            check { "height" }
+                    .that(topLayerRegion.region.height).isLower(bottomLayerRegion.region.height)
+            check { "height" }
+                    .that(
+                            topLayerRegion.region.height / 0.3f -
+                                    bottomLayerRegion.region.height / 0.7f)
+                    .isLower(0.1f)
+            check { "width" }
+                    .that(topLayerRegion.region.width).isEqual(bottomLayerRegion.region.width)
+            topLayerRegion.notOverlaps(bottomLayerRegion.region)
+            // Layers of two activities sum to be fullscreen size on display.
+            topLayerRegion.plus(bottomLayerRegion.region).coversExactly(startDisplayBounds)
+        }
+    }
+
+    companion object {
+        /** {@inheritDoc} */
+        private var startDisplayBounds = Rect.EMPTY
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
new file mode 100644
index 0000000..b0ae738
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding.open
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching an activity with AlwaysExpand rule.
+ *
+ * Setup: Launch A|B in split with B being the secondary activity. Transitions: A start C with
+ * alwaysExpand=true, expect C to launch in fullscreen and cover split A|B.
+ *
+ * To run this test: `atest FlickerTestsOther:MainActivityStartsSecondaryWithAlwaysExpandTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class MainActivityStartsSecondaryWithAlwaysExpandTest(flicker: LegacyFlickerTest) :
+    ActivityEmbeddingTestBase(flicker) {
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            // Launch a split
+            testApp.launchViaIntent(wmHelper)
+            testApp.launchSecondaryActivity(wmHelper)
+            startDisplayBounds =
+                wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+        }
+        transitions {
+            // Launch C with alwaysExpand
+            testApp.launchAlwaysExpandActivity(wmHelper)
+        }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    @Ignore("Not applicable to this CUJ.") override fun navBarWindowIsVisibleAtStartAndEnd() {}
+
+    @Ignore("Not applicable to this CUJ.") override fun statusBarWindowIsAlwaysVisible() {}
+
+    @Ignore("Not applicable to this CUJ.") override fun statusBarLayerPositionAtStartAndEnd() {}
+
+    /** Transition begins with a split. */
+    @FlakyTest(bugId = 286952194)
+    @Test
+    fun startsWithSplit() {
+        flicker.assertWmStart {
+            this.isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+        flicker.assertWmStart {
+            this.isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Main activity should become invisible after being covered by always expand activity. */
+    @FlakyTest(bugId = 286952194)
+    @Test
+    fun mainActivityLayerBecomesInvisible() {
+        flicker.assertLayers {
+            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .then()
+                .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Secondary activity should become invisible after being covered by always expand activity. */
+    @FlakyTest(bugId = 286952194)
+    @Test
+    fun secondaryActivityLayerBecomesInvisible() {
+        flicker.assertLayers {
+            isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** At the end of transition always expand activity is in fullscreen. */
+    @FlakyTest(bugId = 286952194)
+    @Test
+    fun endsWithAlwaysExpandActivityCoveringFullScreen() {
+        flicker.assertWmEnd {
+            this.visibleRegion(ActivityEmbeddingAppHelper.ALWAYS_EXPAND_ACTIVITY_COMPONENT)
+                .coversExactly(startDisplayBounds)
+        }
+    }
+
+    /** Always expand activity is on top of the split. */
+    @FlakyTest(bugId = 286952194)
+    @Presubmit
+    @Test
+    fun endsWithAlwaysExpandActivityOnTop() {
+        flicker.assertWmEnd {
+            this.isAppWindowOnTop(ActivityEmbeddingAppHelper.ALWAYS_EXPAND_ACTIVITY_COMPONENT)
+        }
+    }
+
+    companion object {
+        /** {@inheritDoc} */
+        private var startDisplayBounds = Rect.EMPTY
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
new file mode 100644
index 0000000..48edf6d
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.wm.flicker.activityembedding.open
+
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test opening an activity that will launch another activity as ActivityEmbedding placeholder in
+ * split.
+ *
+ * To run this test: `atest FlickerTestsOther:OpenActivityEmbeddingPlaceholderSplitTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenActivityEmbeddingPlaceholderSplitTest(flicker: LegacyFlickerTest) :
+    ActivityEmbeddingTestBase(flicker) {
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            testApp.launchViaIntent(wmHelper)
+        }
+        transitions { testApp.launchPlaceholderSplit(wmHelper) }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /** Main activity should become invisible after launching the placeholder primary activity. */
+    @Presubmit
+    @Test
+    fun mainActivityWindowBecomesInvisible() {
+        flicker.assertWm {
+            isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Main activity should become invisible after launching the placeholder primary activity. */
+    @Presubmit
+    @Test
+    fun mainActivityLayerBecomesInvisible() {
+        flicker.assertLayers {
+            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .then()
+                .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /**
+     * Placeholder primary and secondary should become visible after launch. The windows are not
+     * necessarily to become visible at the same time.
+     */
+    @Presubmit
+    @Test
+    fun placeholderSplitWindowsBecomeVisible() {
+        flicker.assertWm {
+            notContains(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+                .then()
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+        }
+        flicker.assertWm {
+            notContains(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+                .then()
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+        }
+    }
+
+    /** Placeholder primary and secondary should become visible together after launch. */
+    @Presubmit
+    @Test
+    fun placeholderSplitLayersBecomeVisible() {
+        flicker.assertLayers {
+            isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+            isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+                .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
new file mode 100644
index 0000000..3657820
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding.open
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test opening a secondary activity that will split with the main activity.
+ *
+ * To run this test: `atest FlickerTestsOther:OpenActivityEmbeddingSecondaryToSplitTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenActivityEmbeddingSecondaryToSplitTest(flicker: LegacyFlickerTest) :
+    ActivityEmbeddingTestBase(flicker) {
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            testApp.launchViaIntent(wmHelper)
+        }
+        transitions { testApp.launchSecondaryActivity(wmHelper) }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /** Main activity should remain visible when enter split from fullscreen. */
+    @Presubmit
+    @Test
+    fun mainActivityWindowIsAlwaysVisible() {
+        flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+    }
+
+    /**
+     * Main activity surface is animated from fullscreen to ActivityEmbedding split. During the
+     * transition, there is a period of time that it is covered by a snapshot of itself.
+     */
+    @Presubmit
+    @Test
+    fun mainActivityLayerIsAlwaysVisible() {
+        flicker.assertLayers {
+            isVisible(
+                ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT.or(
+                    ComponentNameMatcher.TRANSITION_SNAPSHOT
+                )
+            )
+        }
+        flicker.assertLayersEnd {
+            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .isInvisible(ComponentNameMatcher.TRANSITION_SNAPSHOT)
+        }
+    }
+
+    /** Secondary activity should become visible after launching into split. */
+    @Presubmit
+    @Test
+    fun secondaryActivityWindowBecomesVisible() {
+        flicker.assertWm {
+            notContains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Secondary activity should become visible after launching into split. */
+    @Presubmit
+    @Test
+    fun secondaryActivityLayerBecomesVisible() {
+        flicker.assertLayers {
+            isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
new file mode 100644
index 0000000..5551aa6
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding.open
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a secondary activity over an existing split. By default the new secondary activity
+ * will stack over the previous secondary activity.
+ *
+ * Setup: From Activity A launch a split A|B.
+ *
+ * Transitions: Let B start C, expect C to cover B and end up in split A|C.
+ *
+ * To run this test: `atest FlickerTestsOther:OpenThirdActivityOverSplitTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenThirdActivityOverSplitTest(flicker: LegacyFlickerTest) :
+    ActivityEmbeddingTestBase(flicker) {
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            // Launch a split.
+            testApp.launchViaIntent(wmHelper)
+            testApp.launchSecondaryActivity(wmHelper)
+
+            startDisplayBounds =
+                wmHelper.currentState.layerState.physicalDisplayBounds
+                    ?: error("Can't get display bounds")
+        }
+        transitions { testApp.launchThirdActivity(wmHelper) }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /** Main activity remains visible throughout the transition. */
+    @Presubmit
+    @Test
+    fun mainActivityWindowAlwaysVisible() {
+        flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+    }
+
+    /** Main activity remains visible throughout the transition and takes up half of the screen. */
+    @Presubmit
+    @Test
+    fun mainActivityLayersAlwaysVisible() {
+        flicker.assertLayers { isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+
+        flicker.assertLayersStart {
+            val display =
+                this.entry.displays.firstOrNull { it.isOn && !it.isVirtual }
+                    ?: error("No non-virtual and on display found")
+            val mainActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val secondaryActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT).region
+            mainActivityRegion.plus(secondaryActivityRegion).coversExactly(display.layerStackSpace)
+        }
+
+        flicker.assertLayersEnd {
+            val display =
+                this.entry.displays.firstOrNull { it.isOn && !it.isVirtual }
+                    ?: error("No non-virtual and on display found")
+            val mainActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val secondaryActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            secondaryActivityRegion.isEmpty()
+            val thirdActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+            mainActivityRegion
+                .plus(thirdActivityRegion.region)
+                .coversExactly(display.layerStackSpace)
+        }
+    }
+
+    /** Third activity launches during the transition and covers up secondary activity. */
+    @Presubmit
+    @Test
+    fun thirdActivityWindowLaunchesIntoSplit() {
+        flicker.assertWm {
+            isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .isAppWindowInvisible(
+                    ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT
+                ) // expectation
+        }
+    }
+
+    /** Third activity launches during the transition and covers up secondary activity. */
+    @Presubmit
+    @Test
+    fun thirdActivityLayerLaunchesIntoSplit() {
+        flicker.assertLayers {
+            isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .isInvisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .isVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT)
+                .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    companion object {
+        /** {@inheritDoc} */
+        private var startDisplayBounds = Rect.EMPTY
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
new file mode 100644
index 0000000..6432f1f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding.open
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.Region
+import android.tools.common.flicker.subject.region.RegionSubject
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a trampoline activity and resulting in a split state.
+ *
+ * Setup: Launch Activity A in fullscreen.
+ *
+ * Transitions: From A launch a trampoline Activity T, T launches secondary Activity B and
+ * finishes itself, end up in split A|B.
+ *
+ * To run this test: `atest FlickerTestsOther:OpenTrampolineActivityTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenTrampolineActivityTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBase(flicker) {
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            testApp.launchViaIntent(wmHelper)
+            startDisplayBounds =
+                    wmHelper.currentState.layerState.physicalDisplayBounds
+                            ?: error("Can't get display bounds")
+        }
+        transitions {
+            testApp.launchTrampolineActivity(wmHelper)
+        }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /** Trampoline activity should finish itself before the end of this test. */
+    @Presubmit
+    @Test
+    fun trampolineActivityFinishes() {
+        flicker.assertWmEnd {
+            notContains(ActivityEmbeddingAppHelper.TRAMPOLINE_ACTIVITY_COMPONENT)
+        }
+    }
+
+    @Presubmit
+    @Test
+    fun trampolineLayerNeverVisible() {
+        flicker.assertLayers {
+            isInvisible(ActivityEmbeddingAppHelper.TRAMPOLINE_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Main activity is always visible throughout this test. */
+    @Presubmit
+    @Test
+    fun mainActivityWindowAlwaysVisible() {
+        flicker.assertWm {
+            isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+    }
+
+    // TODO(b/289140963): After this is fixed, assert the main Activity window is visible
+    //  throughout the test instead.
+    /** Main activity layer is visible before and after the transition. */
+    @Presubmit
+    @Test
+    fun mainActivityLayerAlwaysVisible() {
+        flicker.assertLayersStart {
+            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+        flicker.assertLayersEnd {
+            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Secondary activity is launched from the trampoline activity. */
+    @Presubmit
+    @Test
+    fun secondaryActivityWindowLaunchedFromTrampoline() {
+        flicker.assertWm {
+            notContains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                    .then()
+                    .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                    .then()
+                    .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Secondary activity is launched from the trampoline activity. */
+    @Presubmit
+    @Test
+    fun secondaryActivityLayerLaunchedFromTrampoline() {
+        flicker.assertLayers {
+            isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                    .then()
+                    .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    /** Main activity should go from fullscreen to being a split with secondary activity. */
+    @Presubmit
+    @Test
+    fun mainActivityWindowGoesFromFullscreenToSplit() {
+        flicker.assertWm {
+            this.invoke("mainActivityStartsInFullscreen") {
+                it.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                        .coversExactly(startDisplayBounds)
+            }
+                    // Begin of transition.
+                    .then()
+                    .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                    .then()
+                    .invoke("mainAndSecondaryInSplit") {
+                        val mainActivityRegion =
+                                RegionSubject(
+                                        it.visibleRegion(
+                                                ActivityEmbeddingAppHelper
+                                                        .MAIN_ACTIVITY_COMPONENT).region,
+                                        it.timestamp)
+                        val secondaryActivityRegion =
+                                RegionSubject(
+                                        it.visibleRegion(
+                                                ActivityEmbeddingAppHelper
+                                                        .SECONDARY_ACTIVITY_COMPONENT).region,
+                                        it.timestamp)
+                        check { "height" }
+                                .that(mainActivityRegion.region.height)
+                                .isEqual(secondaryActivityRegion.region.height)
+                        check { "width" }
+                                .that(mainActivityRegion.region.width)
+                                .isEqual(secondaryActivityRegion.region.width)
+                        mainActivityRegion
+                                .plus(secondaryActivityRegion.region)
+                                .coversExactly(startDisplayBounds)
+                    }
+        }
+    }
+
+    @FlakyTest(bugId = 290736037)
+    /** Main activity should go from fullscreen to being a split with secondary activity. */
+    @Presubmit
+    @Test
+    fun mainActivityLayerGoesFromFullscreenToSplit() {
+        flicker.assertLayers {
+            this.invoke("mainActivityStartsInFullscreen") {
+                it.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                        .coversExactly(startDisplayBounds)
+            }
+                    .then()
+                    .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                    .then()
+                    .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+        flicker.assertLayersEnd {
+            val leftLayerRegion = visibleRegion(
+                    ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val rightLayerRegion =
+                    visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            // Compare dimensions of two splits, given we're using default split attributes,
+            // both activities take up the same visible size on the display.
+            check { "height" }
+                    .that(leftLayerRegion.region.height)
+                    .isEqual(rightLayerRegion.region.height)
+            check { "width" }
+                    .that(leftLayerRegion.region.width)
+                    .isEqual(rightLayerRegion.region.width)
+            leftLayerRegion.notOverlaps(rightLayerRegion.region)
+            // Layers of two activities sum to be fullscreen size on display.
+            leftLayerRegion.plus(rightLayerRegion.region).coversExactly(startDisplayBounds)
+        }
+    }
+
+    companion object {
+        /** {@inheritDoc} */
+        private var startDisplayBounds = Rect.EMPTY
+
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
new file mode 100644
index 0000000..9ad3edd
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding.pip
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.common.traces.component.ComponentNameMatcher.Companion.TRANSITION_SNAPSHOT
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a secondary Activity into Picture-In-Picture mode.
+ *
+ * Setup: Start from a split A|B.
+ * Transition: B enters PIP, observe the window shrink to the bottom right corner on screen.
+ *
+ * To run this test: `atest FlickerTestsOther:SecondaryActivityEnterPipTest`
+ *
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SecondaryActivityEnterPipTest (flicker: LegacyFlickerTest) :
+        ActivityEmbeddingTestBase(flicker) {
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            testApp.launchViaIntent(wmHelper)
+            testApp.launchSecondaryActivity(wmHelper)
+            startDisplayBounds =
+                    wmHelper.currentState.layerState.physicalDisplayBounds
+                            ?: error("Can't get display bounds")
+        }
+        transitions {
+            testApp.secondaryActivityEnterPip(wmHelper)
+        }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /**
+     * Main and secondary activity start from a split each taking half of the screen.
+     */
+    @Presubmit
+    @Test
+    fun layersStartFromEqualSplit() {
+        flicker.assertLayersStart {
+            val leftLayerRegion =
+                    visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val rightLayerRegion =
+                    visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            // Compare dimensions of two splits, given we're using default split attributes,
+            // both activities take up the same visible size on the display.
+            check { "height" }
+                    .that(leftLayerRegion.region.height).isEqual(rightLayerRegion.region.height)
+            check { "width" }
+                    .that(leftLayerRegion.region.width).isEqual(rightLayerRegion.region.width)
+            leftLayerRegion.notOverlaps(rightLayerRegion.region)
+            leftLayerRegion.plus(rightLayerRegion.region).coversExactly(startDisplayBounds)
+        }
+        flicker.assertLayersEnd {
+            visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                    .coversExactly(startDisplayBounds)
+        }
+    }
+
+    /**
+     * Main Activity is visible throughout the transition and becomes fullscreen.
+     */
+    @Presubmit
+    @Test
+    fun mainActivityWindowBecomesFullScreen() {
+        flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+        flicker.assertWmEnd {
+            visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                    .coversExactly(startDisplayBounds)
+        }
+    }
+
+    /**
+     * Main Activity is visible throughout the transition and becomes fullscreen.
+     */
+    @Presubmit
+    @Test
+    fun mainActivityLayerBecomesFullScreen() {
+        flicker.assertLayers {
+            isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                    .then()
+                    .isVisible(TRANSITION_SNAPSHOT)
+                    .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                    .then()
+                    .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+        flicker.assertLayersEnd {
+            visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                    .coversExactly(startDisplayBounds)
+        }
+    }
+
+    /**
+     * Secondary Activity is visible throughout the transition and shrinks to the bottom right
+     * corner.
+     */
+    @Presubmit
+    @Test
+    fun secondaryWindowShrinks() {
+        flicker.assertWm {
+            isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+        flicker.assertWmEnd {
+            val pipWindowRegion =
+                    visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            check{"height"}
+                    .that(pipWindowRegion.region.height)
+                    .isLower(startDisplayBounds.height / 2)
+            check{"width"}
+                    .that(pipWindowRegion.region.width).isLower(startDisplayBounds.width)
+        }
+    }
+
+    /**
+     * During the transition Secondary Activity shrinks to the bottom right corner.
+     */
+    @Presubmit
+    @Test
+    fun secondaryLayerShrinks() {
+        flicker.assertLayers {
+            val pipLayerList = layers {
+                ComponentNameMatcher.PIP_CONTENT_OVERLAY.layerMatchesAnyOf(it) && it.isVisible
+            }
+            pipLayerList.zipWithNext { previous, current ->
+                // TODO(b/290987990): Add checks for visibleRegion.
+                current.screenBounds.isToTheRightBottom(previous.screenBounds.region, 3)
+                current.screenBounds.notBiggerThan(previous.screenBounds.region)
+            }
+        }
+        flicker.assertLayersEnd {
+            val pipRegion = visibleRegion(
+                    ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            check { "height" }
+                    .that(pipRegion.region.height)
+                    .isLower(startDisplayBounds.height / 2)
+            check { "width" }
+                    .that(pipRegion.region.width).isLower(startDisplayBounds.width)
+        }
+    }
+
+    companion object {
+        /** {@inheritDoc} */
+        private var startDisplayBounds = Rect.EMPTY
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
new file mode 100644
index 0000000..4f7d8a4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding.rotation
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import com.android.server.wm.flicker.rotation.RotationTransition
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Tests rotating two activities in an Activity Embedding split.
+ *
+ * Setup: Launch A|B in split with B being the secondary activity. Transitions: Rotate display, and
+ * expect A and B to split evenly in new rotation.
+ *
+ * To run this test: `atest FlickerTestsOther:RotateSplitNoChangeTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class RotateSplitNoChangeTest(flicker: LegacyFlickerTest) : RotationTransition(flicker) {
+
+    override val testApp = ActivityEmbeddingAppHelper(instrumentation)
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                testApp.launchViaIntent(wmHelper)
+                testApp.launchSecondaryActivity(wmHelper)
+            }
+        }
+
+    /**
+     * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
+     * flicker, and disappears before the transition is complete
+     */
+    @Presubmit
+    @Test
+    fun rotationLayerAppearsAndVanishes() {
+        flicker.assertLayers {
+            this.isVisible(testApp)
+                .then()
+                .isVisible(ComponentNameMatcher.ROTATION)
+                .then()
+                .isVisible(testApp)
+                .isInvisible(ComponentNameMatcher.ROTATION)
+        }
+    }
+
+    /**
+     * Overrides inherited assertion because in AE Split, the main and secondary activity are
+     * separate layers, each covering up exactly half of the display.
+     */
+    @Presubmit
+    @Test
+    override fun appLayerRotates_StartingPos() {
+        flicker.assertLayersStart {
+            this.entry.displays.map { display ->
+                val leftLayerRegion =
+                    this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                val rightLayerRegion =
+                    this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                // Compare dimensions of two splits, given we're using default split attributes,
+                // both activities take up the same visible size on the display.
+                check { "height" }
+                    .that(leftLayerRegion.region.height)
+                    .isEqual(rightLayerRegion.region.height)
+                check { "width" }
+                    .that(leftLayerRegion.region.width)
+                    .isEqual(rightLayerRegion.region.width)
+                leftLayerRegion.notOverlaps(rightLayerRegion.region)
+                // Layers of two activities sum to be fullscreen size on display.
+                leftLayerRegion.plus(rightLayerRegion.region).coversExactly(display.layerStackSpace)
+            }
+        }
+    }
+
+    /** Verifies dimensions of both split activities hold their invariance after transition too. */
+    @Presubmit
+    @Test
+    override fun appLayerRotates_EndingPos() {
+        flicker.assertLayersEnd {
+            this.entry.displays.map { display ->
+                val leftLayerRegion =
+                    this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                val rightLayerRegion =
+                    this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                check { "height" }
+                    .that(leftLayerRegion.region.height)
+                    .isEqual(rightLayerRegion.region.height)
+                check { "width" }
+                    .that(leftLayerRegion.region.width)
+                    .isEqual(rightLayerRegion.region.width)
+                leftLayerRegion.notOverlaps(rightLayerRegion.region)
+                leftLayerRegion.plus(rightLayerRegion.region).coversExactly(display.layerStackSpace)
+            }
+        }
+    }
+
+    /** Both activities in split should remain visible during rotation. */
+    @Presubmit
+    @Test
+    fun bothActivitiesAreAlwaysVisible() {
+        flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+        flicker.assertWm {
+            isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.rotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt
new file mode 100644
index 0000000..6be78f8
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding.rtl
+
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a placeholder split over a normal split, both splits are configured in RTL.
+ *
+ * Setup: From A launch a split in RTL - resulting in B|A. Transitions: From A start
+ * PlaceholderPrimary, which is configured to launch with PlaceholderSecondary in RTL. Expect split
+ * PlaceholderSecondary|PlaceholderPrimary covering split B|A.
+ *
+ * To run this test: `atest FlickerTestsOther:RTLStartSecondaryWithPlaceholderTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RTLStartSecondaryWithPlaceholderTest(flicker: LegacyFlickerTest) :
+    ActivityEmbeddingTestBase(flicker) {
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotationCheckEnabled(false)
+            testApp.launchViaIntent(wmHelper)
+            testApp.launchSecondaryActivityRTL(wmHelper)
+        }
+        transitions { testApp.launchPlaceholderSplitRTL(wmHelper) }
+        teardown {
+            tapl.goHome()
+            testApp.exit(wmHelper)
+        }
+    }
+
+    /**
+     * Main activity and Secondary activity will become invisible because they are covered by
+     * PlaceholderPrimary activity and PlaceholderSecondary activity.
+     */
+    @Presubmit
+    @Test
+    fun assertWindowVisibilities() {
+        flicker.assertWm {
+            isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+        flicker.assertWm {
+            isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+        flicker.assertWm {
+            isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+        }
+        flicker.assertWm {
+            isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+                .then()
+                .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+        }
+    }
+
+    /**
+     * Main activity and Secondary activity will become invisible because they are covered by
+     * PlaceholderPrimary activity and PlaceholderSecondary activity.
+     */
+    @Presubmit
+    @Test
+    fun assertLayerVisibilities() {
+        flicker.assertLayers {
+            this.isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+                .then()
+                .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+        }
+        flicker.assertLayers {
+            this.isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+                .then()
+                .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+        }
+        flicker.assertLayers {
+            isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+        }
+        flicker.assertLayers {
+            isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+                .then()
+                .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+        }
+    }
+
+    /** Main activity and Secondary activity split is in right-to-left layout direction. */
+    @Presubmit
+    @Test
+    fun assertWMRTLBeforeTransition() {
+        flicker.assertWmStart {
+            val mainActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val secondaryActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            mainActivityRegion.notOverlaps(secondaryActivityRegion.region)
+            // secondary activity is on the left, main activity is on the right.
+            check { "isRTLBeforeTransition" }
+                .that(mainActivityRegion.region.bounds.left)
+                .isEqual(secondaryActivityRegion.region.bounds.right)
+        }
+    }
+
+    /** Main activity and Secondary activity split is in right-to-left layout direction. */
+    @Presubmit
+    @Test
+    fun assertLayerRTLBeforeTransition() {
+        flicker.assertLayersStart {
+            val mainActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val secondaryActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            mainActivityRegion.notOverlaps(secondaryActivityRegion.region)
+            // secondary activity is on the left, main activity is on the right.
+            check { "isRTLBeforeTransition" }
+                .that(mainActivityRegion.region.bounds.left)
+                .isEqual(secondaryActivityRegion.region.bounds.right)
+        }
+    }
+
+    /**
+     * PlaceholderPrimary activity and PlaceholderSecondary activity split are in right-to-left
+     * layout direction.
+     */
+    @Presubmit
+    @Test
+    fun assertWMRTLAfterTransition() {
+        flicker.assertWmEnd {
+            val mainActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+            val secondaryActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+            mainActivityRegion.notOverlaps(secondaryActivityRegion.region)
+            // secondary activity is on the left, main activity is on the right.
+            check { "isRTLBeforeTransition" }
+                .that(mainActivityRegion.region.bounds.left)
+                .isEqual(secondaryActivityRegion.region.bounds.right)
+        }
+    }
+
+    /**
+     * PlaceholderPrimary activity and PlaceholderSecondary activity split are in right-to-left
+     * layout direction.
+     */
+    @Presubmit
+    @Test
+    fun assertLayerRTLAfterTransition() {
+        flicker.assertLayersEnd {
+            val mainActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
+            val secondaryActivityRegion =
+                this.visibleRegion(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
+            mainActivityRegion.notOverlaps(secondaryActivityRegion.region)
+            // Placeholder secondary activity is on the left, placeholder primary activity is on the
+            // right.
+            check { "isRTLAfterTransition" }
+                .that(mainActivityRegion.region.bounds.left)
+                .isEqual(secondaryActivityRegion.region.bounds.right)
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index 10b71ff..dbbc771 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -17,11 +17,11 @@
 package com.android.server.wm.flicker.close
 
 import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -76,7 +76,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseAppBackButtonTest(flicker: FlickerTest) : CloseAppTransition(flicker) {
+open class CloseAppBackButtonTest(flicker: LegacyFlickerTest) : CloseAppTransition(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -96,13 +96,11 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
index 9fa84019..566f393 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt
@@ -16,10 +16,10 @@
 
 package com.android.server.wm.flicker.close
 
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -29,18 +29,16 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseAppBackButtonTestCfArm(flicker: FlickerTest) : CloseAppBackButtonTest(flicker) {
+class CloseAppBackButtonTestCfArm(flicker: LegacyFlickerTest) : CloseAppBackButtonTest(flicker) {
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): List<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index e5bd350..ed930fc 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -17,11 +17,11 @@
 package com.android.server.wm.flicker.close
 
 import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -76,7 +76,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseAppHomeButtonTest(flicker: FlickerTest) : CloseAppTransition(flicker) {
+open class CloseAppHomeButtonTest(flicker: LegacyFlickerTest) : CloseAppTransition(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -101,8 +101,6 @@
         /** Creates the test configurations. */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
index 136995a..49ed183 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt
@@ -16,10 +16,10 @@
 
 package com.android.server.wm.flicker.close
 
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -29,13 +29,11 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseAppHomeButtonTestCfArm(flicker: FlickerTest) : CloseAppHomeButtonTest(flicker) {
+class CloseAppHomeButtonTestCfArm(flicker: LegacyFlickerTest) : CloseAppHomeButtonTest(flicker) {
     companion object {
         /** Creates the test configurations. */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index 4570fa2..8737edb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -16,11 +16,14 @@
 
 package com.android.server.wm.flicker.close
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
+import android.tools.common.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
+import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.common.traces.component.ComponentNameMatcher.Companion.LAUNCHER
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
@@ -28,7 +31,7 @@
 import org.junit.Test
 
 /** Base test class for transitions that close an app back to the launcher screen */
-abstract class CloseAppTransition(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class CloseAppTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -71,4 +74,21 @@
             ignoreEntriesWithRotationLayer = flicker.scenario.isLandscapeOrSeascapeAtStart
         )
     }
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        flicker.assertLayers {
+            this.visibleLayersShownMoreThanOneConsecutiveEntry(
+                VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + listOf(ComponentNameMatcher.NAV_BAR)
+            )
+        }
+    }
+
+    @FlakyTest(bugId = 288369951)
+    @Test
+    fun visibleLayersShownMoreThanOneConsecutiveEntryWithNavBar() {
+        flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index daecfe7..883c7e6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -45,21 +45,152 @@
      * based on the split pair rule.
      */
     fun launchSecondaryActivity(wmHelper: WindowManagerStateHelper) {
+        launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_button")
+    }
+
+    /**
+     * Clicks the button to launch the secondary activity in RTL, which should split with the main
+     * activity based on the split pair rule.
+     */
+    fun launchSecondaryActivityRTL(wmHelper: WindowManagerStateHelper) {
+        launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_rtl_button")
+    }
+
+    /**
+     * Clicks the button to launch the secondary activity in a horizontal split.
+     */
+    fun launchSecondaryActivityHorizontally(wmHelper: WindowManagerStateHelper) {
+        launchSecondaryActivityFromButton(wmHelper, "launch_secondary_activity_horizontally_button")
+    }
+
+    /** Clicks the button to launch a third activity over a secondary activity. */
+    fun launchThirdActivity(wmHelper: WindowManagerStateHelper) {
         val launchButton =
             uiDevice.wait(
-                Until.findObject(By.res(getPackage(), "launch_secondary_activity_button")),
+                Until.findObject(By.res(getPackage(), "launch_third_activity_button")),
                 FIND_TIMEOUT
             )
-        require(launchButton != null) { "Can't find launch secondary activity button on screen." }
+        require(launchButton != null) { "Can't find launch third activity button on screen." }
         launchButton.click()
         wmHelper
             .StateSyncBuilder()
-            .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
             .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_STOPPED)
+            .withActivityState(THIRD_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
             .waitForAndVerify()
     }
 
     /**
+     * Clicks the button to launch the trampoline activity, which should launch the secondary
+     * activity and finish itself.
+     */
+    fun launchTrampolineActivity(wmHelper: WindowManagerStateHelper) {
+        val launchButton =
+                uiDevice.wait(
+                        Until.findObject(By.res(getPackage(), "launch_trampoline_button")),
+                        FIND_TIMEOUT
+                )
+        require(launchButton != null) { "Can't find launch trampoline activity button on screen." }
+        launchButton.click()
+        wmHelper
+                .StateSyncBuilder()
+                .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+                .withActivityRemoved(TRAMPOLINE_ACTIVITY_COMPONENT)
+                .waitForAndVerify()
+    }
+
+    /**
+     * Clicks the button to finishes the secondary activity launched through
+     * [launchSecondaryActivity], waits for the main activity to resume.
+     */
+    fun finishSecondaryActivity(wmHelper: WindowManagerStateHelper) {
+        val finishButton =
+                uiDevice.wait(
+                        Until.findObject(By.res(getPackage(), "finish_secondary_activity_button")),
+                        FIND_TIMEOUT
+                )
+        require(finishButton != null) { "Can't find finish secondary activity button on screen." }
+        finishButton.click()
+        wmHelper
+                .StateSyncBuilder()
+                .withActivityRemoved(SECONDARY_ACTIVITY_COMPONENT)
+                .waitForAndVerify()
+     }
+
+    /**
+     * Clicks the button to toggle the split ratio of secondary activity.
+     */
+    fun changeSecondaryActivityRatio(wmHelper: WindowManagerStateHelper) {
+        val launchButton =
+                uiDevice.wait(
+                        Until.findObject(
+                                By.res(getPackage(),
+                                        "toggle_split_ratio_button")),
+                        FIND_TIMEOUT
+                )
+        require(launchButton != null) {
+            "Can't find toggle ratio for secondary activity button on screen."
+        }
+        launchButton.click()
+        wmHelper
+                .StateSyncBuilder()
+                .withAppTransitionIdle()
+                .withTransitionSnapshotGone()
+                .waitForAndVerify()
+    }
+
+    fun secondaryActivityEnterPip(wmHelper: WindowManagerStateHelper) {
+        val pipButton =
+                uiDevice.wait(
+                        Until.findObject(By.res(getPackage(), "secondary_enter_pip_button")),
+                        FIND_TIMEOUT
+                )
+        require(pipButton != null) { "Can't find enter pip button on screen." }
+        pipButton.click()
+        wmHelper
+                .StateSyncBuilder()
+                .withAppTransitionIdle()
+                .withPipShown()
+                .waitForAndVerify()
+    }
+
+    /**
+     * Clicks the button to launch a secondary activity with alwaysExpand enabled, which will launch
+     * a fullscreen window on top of the visible region.
+     */
+    fun launchAlwaysExpandActivity(wmHelper: WindowManagerStateHelper) {
+        val launchButton =
+            uiDevice.wait(
+                Until.findObject(By.res(getPackage(), "launch_always_expand_activity_button")),
+                FIND_TIMEOUT
+            )
+        require(launchButton != null) {
+            "Can't find launch always expand activity button on screen."
+        }
+        launchButton.click()
+        wmHelper
+            .StateSyncBuilder()
+            .withActivityState(ALWAYS_EXPAND_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_PAUSED)
+            .waitForAndVerify()
+    }
+
+    private fun launchSecondaryActivityFromButton(
+            wmHelper: WindowManagerStateHelper, buttonName: String) {
+        val launchButton =
+                uiDevice.wait(Until.findObject(By.res(getPackage(), buttonName)), FIND_TIMEOUT)
+        require(launchButton != null) {
+            "Can't find launch secondary activity button : " + buttonName + "on screen."
+        }
+        launchButton.click()
+        wmHelper
+                .StateSyncBuilder()
+                .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+                .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+                .waitForAndVerify()
+    }
+
+    /**
      * Clicks the button to launch the placeholder primary activity, which should launch the
      * placeholder secondary activity based on the placeholder rule.
      */
@@ -78,6 +209,25 @@
             .waitForAndVerify()
     }
 
+    /**
+     * Clicks the button to launch the placeholder primary activity in RTL, which should launch the
+     * placeholder secondary activity based on the placeholder rule.
+     */
+    fun launchPlaceholderSplitRTL(wmHelper: WindowManagerStateHelper) {
+        val launchButton =
+            uiDevice.wait(
+                Until.findObject(By.res(getPackage(), "launch_placeholder_split_rtl_button")),
+                FIND_TIMEOUT
+            )
+        require(launchButton != null) { "Can't find launch placeholder split button on screen." }
+        launchButton.click()
+        wmHelper
+            .StateSyncBuilder()
+            .withActivityState(PLACEHOLDER_PRIMARY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .withActivityState(PLACEHOLDER_SECONDARY_COMPONENT, PlatformConsts.STATE_RESUMED)
+            .waitForAndVerify()
+    }
+
     companion object {
         private const val TAG = "ActivityEmbeddingAppHelper"
 
@@ -87,6 +237,12 @@
         val SECONDARY_ACTIVITY_COMPONENT =
             ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent()
 
+        val THIRD_ACTIVITY_COMPONENT =
+            ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT.toFlickerComponent()
+
+        val ALWAYS_EXPAND_ACTIVITY_COMPONENT =
+            ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT.toFlickerComponent()
+
         val PLACEHOLDER_PRIMARY_COMPONENT =
             ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
                 .toFlickerComponent()
@@ -95,6 +251,9 @@
             ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT
                 .toFlickerComponent()
 
+        val TRAMPOLINE_ACTIVITY_COMPONENT =
+                ActivityOptions.ActivityEmbedding.TrampolineActivity.COMPONENT.toFlickerComponent()
+
         @JvmStatic
         fun getWindowExtensions(): WindowExtensions? {
             try {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
index 9227e07..5c8cbe4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -19,7 +19,7 @@
 package com.android.server.wm.flicker.helpers
 
 import android.tools.common.Rotation
-import android.tools.device.flicker.legacy.IFlickerTestData
+import android.tools.device.flicker.legacy.FlickerTestData
 import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
 
 /**
@@ -27,7 +27,7 @@
  *
  * @param rotation New device rotation
  */
-fun IFlickerTestData.setRotation(rotation: Rotation) =
+fun FlickerTestData.setRotation(rotation: Rotation) =
     ChangeDisplayOrientationRule.setRotation(
         rotation,
         instrumentation,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
index a8f1b3d..eeee7b4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
@@ -62,6 +62,33 @@
     }
 
     /**
+     * Injects a series of {@link MotionEvent}s to simulate tapping.
+     *
+     * @param point coordinates of pointer to tap
+     * @param times the number of times to tap
+     */
+    public boolean tap(@NonNull Tuple point, int times) throws InterruptedException {
+        PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
+        PointerCoords ptrCoord = getPointerCoord(point.x, point.y, 1, 1);
+
+        for (int i = 0; i <= times; i++) {
+            // If already tapped, inject delay in between movements
+            if (times > 0) {
+                SystemClock.sleep(50L);
+            }
+            if (!primaryPointerDown(ptrProp, ptrCoord, SystemClock.uptimeMillis())) {
+                return false;
+            }
+            // Delay before releasing tap
+            SystemClock.sleep(100L);
+            if (!primaryPointerUp(ptrProp, ptrCoord, SystemClock.uptimeMillis())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Injects a series of {@link MotionEvent}s to simulate a drag gesture without pointer release.
      *
      * Simulates a drag gesture without releasing the primary pointer. The primary pointer info
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
index a670d68..d83b6d3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
@@ -17,6 +17,8 @@
 package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
+import android.tools.common.datatypes.Rect
+import android.tools.common.datatypes.Region
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.helpers.FIND_TIMEOUT
@@ -36,6 +38,8 @@
         ActivityOptions.NonResizeablePortraitActivity.COMPONENT.toFlickerComponent()
 ) : StandardAppHelper(instr, launcherName, component) {
 
+    private val gestureHelper: GestureHelper = GestureHelper(mInstrumentation)
+
     fun clickRestart(wmHelper: WindowManagerStateHelper) {
         val restartButton =
             uiDevice.wait(
@@ -56,4 +60,74 @@
             ?: error("Restart dialog button not found")
         wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
     }
+
+    fun repositionHorizontally(displayBounds: Rect, right: Boolean) {
+        val x = if (right) displayBounds.right - BOUNDS_OFFSET else BOUNDS_OFFSET
+        reposition(x.toFloat(), displayBounds.centerY().toFloat())
+    }
+
+    fun repositionVertically(displayBounds: Rect, bottom: Boolean) {
+        val y = if (bottom) displayBounds.bottom - BOUNDS_OFFSET else BOUNDS_OFFSET
+        reposition(displayBounds.centerX().toFloat(), y.toFloat())
+    }
+
+    private fun reposition(x: Float, y: Float) {
+        val coords = GestureHelper.Tuple(x, y)
+        require(gestureHelper.tap(coords, 2)) { "Failed to reposition letterbox app" }
+    }
+
+    fun waitForAppToMoveHorizontallyTo(
+        wmHelper: WindowManagerStateHelper,
+        displayBounds: Rect,
+        right: Boolean
+    ) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("letterboxAppRepositioned") {
+                val letterboxAppWindow = getWindowRegion(wmHelper)
+                val appRegionBounds = letterboxAppWindow.bounds
+                val appWidth = appRegionBounds.width
+                return@add if (right)
+                    appRegionBounds.left == displayBounds.right - appWidth &&
+                        appRegionBounds.right == displayBounds.right
+                else
+                    appRegionBounds.left == displayBounds.left &&
+                        appRegionBounds.right == displayBounds.left + appWidth
+            }
+            .waitForAndVerify()
+    }
+
+    fun waitForAppToMoveVerticallyTo(
+        wmHelper: WindowManagerStateHelper,
+        displayBounds: Rect,
+        navBarHeight: Int,
+        bottom: Boolean
+    ) {
+        wmHelper
+            .StateSyncBuilder()
+            .add("letterboxAppRepositioned") {
+                val letterboxAppWindow = getWindowRegion(wmHelper)
+                val appRegionBounds = letterboxAppWindow.bounds
+                val appHeight = appRegionBounds.height
+                return@add if (bottom)
+                    appRegionBounds.bottom == displayBounds.bottom &&
+                        appRegionBounds.top == (displayBounds.bottom - appHeight + navBarHeight)
+                else
+                    appRegionBounds.top == displayBounds.top &&
+                        appRegionBounds.bottom == displayBounds.top + appHeight
+            }
+            .waitForAndVerify()
+    }
+
+    private fun getWindowRegion(wmHelper: WindowManagerStateHelper): Region {
+        val windowRegion = wmHelper.getWindowRegion(this)
+        require(!windowRegion.isEmpty) {
+            "Unable to find letterbox app window in the current state"
+        }
+        return windowRegion
+    }
+
+    companion object {
+        private const val BOUNDS_OFFSET: Int = 100
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 24e231c..c6b86f2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -250,10 +250,13 @@
             waitConditions = arrayOf(ConditionsFactory.hasPipWindow())
         )
 
+        val windowRegion = wmHelper.getWindowRegion(this)
+
         wmHelper
             .StateSyncBuilder()
             .withWindowSurfaceAppeared(this)
             .withPipShown()
+            .withSurfaceVisibleRegion(this, windowRegion)
             .waitForAndVerify()
     }
 
@@ -383,8 +386,11 @@
                     it.wmState.visibleWindows.firstOrNull { window ->
                         this.windowMatchesAnyOf(window)
                     }
-                        ?: return@add false
+                Log.d(TAG, "window " + pipAppWindow)
+                if (pipAppWindow == null) return@add false
                 val pipRegion = pipAppWindow.frameRegion
+                Log.d(TAG, "region " + pipRegion +
+                        " covers " + windowRect.coversMoreThan(pipRegion))
                 return@add windowRect.coversMoreThan(pipRegion)
             }
             .waitForAndVerify()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
index 7e0632d..98446c1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
@@ -22,8 +22,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeEditorPopupDialogAppHelper
@@ -37,7 +37,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeOnDismissPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeOnDismissPopupDialogTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -101,10 +101,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt
index c355e27..d87a1da 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt
@@ -18,8 +18,8 @@
 
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -28,15 +28,14 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeOnDismissPopupDialogTestCfArm(flicker: FlickerTest) :
+class CloseImeOnDismissPopupDialogTestCfArm(flicker: LegacyFlickerTest) :
     CloseImeOnDismissPopupDialogTest(flicker) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
index 537238b..b995d3df 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt
@@ -22,8 +22,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeAppHelper
@@ -41,7 +41,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeOnGoHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeOnGoHomeTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = ImeAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -115,10 +115,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt
index 0fe52df..3b5bfa9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt
@@ -17,7 +17,7 @@
 package com.android.server.wm.flicker.ime
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -26,4 +26,4 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeOnGoHomeTestCfArm(flicker: FlickerTest) : CloseImeOnGoHomeTest(flicker)
+class CloseImeOnGoHomeTestCfArm(flicker: LegacyFlickerTest) : CloseImeOnGoHomeTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
index cbe03dc..765bb4c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt
@@ -21,8 +21,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
@@ -49,7 +49,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeShownOnAppStartOnGoHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeShownOnAppStartOnGoHomeTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
@@ -94,11 +94,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // b/190352379 (IME doesn't show on app launch in 90 degrees)
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt
index 5aacb30..58411cc 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt
@@ -17,7 +17,7 @@
 package com.android.server.wm.flicker.ime
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -26,5 +26,5 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeShownOnAppStartOnGoHomeTestCfArm(flicker: FlickerTest) :
+open class CloseImeShownOnAppStartOnGoHomeTestCfArm(flicker: LegacyFlickerTest) :
     CloseImeShownOnAppStartOnGoHomeTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
index 82c390b..c87217b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
@@ -21,8 +21,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
@@ -49,7 +49,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeShownOnAppStartToAppOnPressBackTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeShownOnAppStartToAppOnPressBackTest(flicker: LegacyFlickerTest) :
+    BaseTest(flicker) {
     private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
@@ -88,11 +89,10 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 // b/190352379 (IME doesn't show on app launch in 90 degrees)
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt
index eb81aed..41b06c0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt
@@ -17,7 +17,7 @@
 package com.android.server.wm.flicker.ime
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -26,5 +26,5 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeShownOnAppStartToAppOnPressBackTestCfArm(flicker: FlickerTest) :
+class CloseImeShownOnAppStartToAppOnPressBackTestCfArm(flicker: LegacyFlickerTest) :
     CloseImeShownOnAppStartToAppOnPressBackTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
index 8d80759..c74870b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt
@@ -21,8 +21,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeAppHelper
@@ -42,7 +42,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeToAppOnPressBackTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeToAppOnPressBackTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = ImeAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -115,8 +115,6 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt
index db1440b..104af22 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt
@@ -17,7 +17,7 @@
 package com.android.server.wm.flicker.ime
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -26,5 +26,5 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeToAppOnPressBackTestCfArm(flicker: FlickerTest) :
+class CloseImeToAppOnPressBackTestCfArm(flicker: LegacyFlickerTest) :
     CloseImeToAppOnPressBackTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index 1526295..21fd590 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -22,8 +22,8 @@
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeAppHelper
@@ -44,7 +44,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeToHomeOnFinishActivityTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class CloseImeToHomeOnFinishActivityTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val simpleApp = SimpleAppHelper(instrumentation)
     private val testApp = ImeAppHelper(instrumentation)
 
@@ -90,10 +90,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt
index 405ab6b..0e18385 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt
@@ -17,7 +17,7 @@
 package com.android.server.wm.flicker.ime
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -26,5 +26,5 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeToHomeOnFinishActivityTestCfArm(flicker: FlickerTest) :
+open class CloseImeToHomeOnFinishActivityTestCfArm(flicker: LegacyFlickerTest) :
     CloseImeToHomeOnFinishActivityTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
index 8e33719..777231e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
@@ -19,21 +19,21 @@
 package com.android.server.wm.flicker.ime
 
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 
-fun FlickerTest.imeLayerBecomesVisible() {
+fun LegacyFlickerTest.imeLayerBecomesVisible() {
     assertLayers {
         this.isInvisible(ComponentNameMatcher.IME).then().isVisible(ComponentNameMatcher.IME)
     }
 }
 
-fun FlickerTest.imeLayerBecomesInvisible() {
+fun LegacyFlickerTest.imeLayerBecomesInvisible() {
     assertLayers {
         this.isVisible(ComponentNameMatcher.IME).then().isInvisible(ComponentNameMatcher.IME)
     }
 }
 
-fun FlickerTest.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) {
+fun LegacyFlickerTest.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) {
     if (rotatesScreen) {
         assertWm {
             this.isNonAppWindowVisible(ComponentNameMatcher.IME)
@@ -47,7 +47,7 @@
     }
 }
 
-fun FlickerTest.imeWindowBecomesVisible() {
+fun LegacyFlickerTest.imeWindowBecomesVisible() {
     assertWm {
         this.isNonAppWindowInvisible(ComponentNameMatcher.IME)
             .then()
@@ -55,7 +55,7 @@
     }
 }
 
-fun FlickerTest.imeWindowBecomesInvisible() {
+fun LegacyFlickerTest.imeWindowBecomesInvisible() {
     assertWm {
         this.isNonAppWindowVisible(ComponentNameMatcher.IME)
             .then()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
index 19bbf0c..976ac82 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
@@ -22,8 +22,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
@@ -42,7 +42,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowToFixedPortraitAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+class OpenImeWindowToFixedPortraitAppTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
@@ -99,19 +99,18 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations =
                     listOf(
                         Rotation.ROTATION_90,
                     ),
                 supportedNavigationModes = listOf(NavBar.MODE_3BUTTON, NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppCfArmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppCfArmTest.kt
index 03f21f9..db80001 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppCfArmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppCfArmTest.kt
@@ -17,7 +17,7 @@
 package com.android.server.wm.flicker.ime
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -26,5 +26,5 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeOnAppStartWhenLaunchingAppCfArmTest(flicker: FlickerTest) :
+class ShowImeOnAppStartWhenLaunchingAppCfArmTest(flicker: LegacyFlickerTest) :
     ShowImeOnAppStartWhenLaunchingAppTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index 496165a..44bd8c8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -16,17 +16,17 @@
 
 package com.android.server.wm.flicker.ime
 
+import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.platform.test.annotations.Postsubmit
 import android.tools.common.Timestamp
-import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.common.flicker.subject.exceptions.ExceptionMessageBuilder
 import android.tools.common.flicker.subject.exceptions.InvalidPropertyException
+import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
@@ -47,7 +47,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker: FlickerTest) :
+open class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker: LegacyFlickerTest) :
     BaseTest(flicker) {
     private val imeTestApp =
         ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
@@ -93,33 +93,35 @@
 
         layerTraceEntries.zipWithNext { prev, next ->
             val prevSnapshotLayerVisible =
-                    ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(prev.visibleLayers)
+                ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(prev.visibleLayers)
             val nextSnapshotLayerVisible =
-                    ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(next.visibleLayers)
+                ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(next.visibleLayers)
 
-            if (imeSnapshotRemovedTimestamp == null &&
-                    (prevSnapshotLayerVisible && !nextSnapshotLayerVisible)) {
+            if (
+                imeSnapshotRemovedTimestamp == null &&
+                    (prevSnapshotLayerVisible && !nextSnapshotLayerVisible)
+            ) {
                 imeSnapshotRemovedTimestamp = next.timestamp
             }
         }
 
         // if so, make an assertion
         imeSnapshotRemovedTimestamp?.let { timestamp ->
-            val stateAfterSnapshot = layerTrace?.getEntryAt(timestamp)
-                    ?: error("State not found for $timestamp")
+            val stateAfterSnapshot =
+                layerTrace?.getEntryAt(timestamp) ?: error("State not found for $timestamp")
 
-            val imeLayers = ComponentNameMatcher.IME
-                    .filterLayers(stateAfterSnapshot.visibleLayers.toList())
+            val imeLayers =
+                ComponentNameMatcher.IME.filterLayers(stateAfterSnapshot.visibleLayers.toList())
 
             require(imeLayers.isNotEmpty()) { "IME layer not found" }
             if (imeLayers.any { it.color.a != 1.0f }) {
-                val errorMsgBuilder = ExceptionMessageBuilder()
+                val errorMsgBuilder =
+                    ExceptionMessageBuilder()
                         .setTimestamp(timestamp)
                         .forInvalidProperty("IME layer alpha")
                         .setExpected("is 1.0")
                         .setActual("not 1.0")
-                        .addExtraDescription("Filter",
-                                ComponentNameMatcher.IME.toLayerIdentifier())
+                        .addExtraDescription("Filter", ComponentNameMatcher.IME.toLayerIdentifier())
                 throw InvalidPropertyException(errorMsgBuilder)
             }
         }
@@ -129,15 +131,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_90)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt
index 3aca2a0..6d9ea22 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt
@@ -17,7 +17,7 @@
 package com.android.server.wm.flicker.ime
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -26,5 +26,5 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm(flicker: FlickerTest) :
+class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm(flicker: LegacyFlickerTest) :
     ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
index f64ad53..ae05e37 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
@@ -21,8 +21,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.reopenAppFromOverview
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
@@ -41,7 +41,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowImeOnAppStartWhenLaunchingAppFromOverviewTest(flicker: FlickerTest) :
+open class ShowImeOnAppStartWhenLaunchingAppFromOverviewTest(flicker: LegacyFlickerTest) :
     BaseTest(flicker) {
     private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
 
@@ -140,10 +140,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt
index e1aa418..92b3968 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt
@@ -18,8 +18,8 @@
 
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -28,16 +28,15 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm(flicker: FlickerTest) :
+class ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm(flicker: LegacyFlickerTest) :
     ShowImeOnAppStartWhenLaunchingAppFromOverviewTest(flicker) {
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
index 11bc7b9..c991651 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
@@ -22,8 +22,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
@@ -44,7 +44,7 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Presubmit
-open class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker: FlickerTest) :
+open class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker: LegacyFlickerTest) :
     BaseTest(flicker) {
     private val testApp = SimpleAppHelper(instrumentation)
     private val imeTestApp =
@@ -121,12 +121,11 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
 
         private const val TAG_IME_VISIBLE = "imeVisible"
         private const val TAG_IME_INVISIBLE = "imeInVisible"
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt
index 0f57467..09bfacc 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt
@@ -18,7 +18,7 @@
 
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -28,5 +28,5 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Presubmit
-open class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm(flicker: FlickerTest) :
+open class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm(flicker: LegacyFlickerTest) :
     ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
index 46967db..9763521 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
@@ -21,8 +21,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
@@ -79,7 +79,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowImeOnAppStartWhenLaunchingAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class ShowImeOnAppStartWhenLaunchingAppTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
     private val initializeApp = ImeStateInitializeHelper(instrumentation)
 
@@ -123,15 +123,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
index 827110d..7bd5825 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt
@@ -21,8 +21,8 @@
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeAppHelper
@@ -37,7 +37,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowImeWhenFocusingOnInputFieldTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class ShowImeWhenFocusingOnInputFieldTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = ImeAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -79,10 +79,9 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt
index f5b2294..f8c8149 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt
@@ -17,7 +17,7 @@
 package com.android.server.wm.flicker.ime
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -26,5 +26,5 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeWhenFocusingOnInputFieldTestCfArm(flicker: FlickerTest) :
+class ShowImeWhenFocusingOnInputFieldTestCfArm(flicker: LegacyFlickerTest) :
     ShowImeWhenFocusingOnInputFieldTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
index 277b9155..0b168ba 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
@@ -21,8 +21,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.view.WindowInsets.Type.ime
 import android.view.WindowInsets.Type.navigationBars
 import android.view.WindowInsets.Type.statusBars
@@ -45,7 +45,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowImeWhileDismissingThemedPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class ShowImeWhileDismissingThemedPopupDialogTest(flicker: LegacyFlickerTest) :
+    BaseTest(flicker) {
     private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
 
     /** {@inheritDoc} */
@@ -84,15 +85,14 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt
index 8891d26..ad41b81 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt
@@ -18,8 +18,8 @@
 
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -28,21 +28,20 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeWhileDismissingThemedPopupDialogTestCfArm(flicker: FlickerTest) :
+class ShowImeWhileDismissingThemedPopupDialogTestCfArm(flicker: LegacyFlickerTest) :
     ShowImeWhileDismissingThemedPopupDialogTest(flicker) {
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
index 9275d6a..7722c93 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
@@ -21,8 +21,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.traces.parsers.WindowManagerStateHelper
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
@@ -45,7 +45,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ShowImeWhileEnteringOverviewTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class ShowImeWhileEnteringOverviewTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val imeTestApp =
         ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation)
 
@@ -205,13 +205,11 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt
index fc39713..90fbbf9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt
@@ -17,7 +17,7 @@
 package com.android.server.wm.flicker.ime
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -26,5 +26,5 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ShowImeWhileEnteringOverviewTestCfArm(flicker: FlickerTest) :
+class ShowImeWhileEnteringOverviewTestCfArm(flicker: LegacyFlickerTest) :
     ShowImeWhileEnteringOverviewTest(flicker)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
deleted file mode 100644
index e6594c9..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ /dev/null
@@ -1,131 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.traces.parsers.toFlickerComponent
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper
-import com.android.server.wm.flicker.testapp.ActivityOptions
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test the back and forward transition between 2 activities.
- *
- * To run this test: `atest FlickerTests:ActivitiesTransitionTest`
- *
- * Actions:
- * ```
- *     Launch an app
- *     Launch a secondary activity within the app
- *     Close the secondary activity back to the initial one
- * ```
- *
- * Notes:
- * ```
- *     1. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- * ```
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ActivitiesTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
-    private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation)
-
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit = {
-        setup {
-            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
-            testApp.launchViaIntent(wmHelper)
-        }
-        teardown { testApp.exit(wmHelper) }
-        transitions {
-            testApp.openSecondActivity(device, wmHelper)
-            tapl.pressBack()
-            wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
-        }
-    }
-
-    /**
-     * Checks that the [ActivityOptions.LaunchNewActivity] activity is visible at the start of the
-     * transition, that [ActivityOptions.SimpleActivity] becomes visible during the transition, and
-     * that [ActivityOptions.LaunchNewActivity] is again visible at the end
-     */
-    @Presubmit
-    @Test
-    fun finishSubActivity() {
-        val buttonActivityComponent =
-            ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
-        val imeAutoFocusActivityComponent =
-            ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
-        flicker.assertWm {
-            this.isAppWindowOnTop(buttonActivityComponent)
-                .then()
-                .isAppWindowOnTop(imeAutoFocusActivityComponent)
-                .then()
-                .isAppWindowOnTop(buttonActivityComponent)
-        }
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.LAUNCHER] window is not on top. The launcher cannot be
-     * asserted with `isAppWindowVisible` because it contains 2 windows with the exact same name,
-     * and both are never simultaneously visible
-     */
-    @Presubmit
-    @Test
-    fun launcherWindowNotOnTop() {
-        flicker.assertWm { this.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) }
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.LAUNCHER] layer is never visible during the transition
-     */
-    @Presubmit
-    @Test
-    fun launcherLayerNotVisible() {
-        flicker.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTestCfArm.kt
deleted file mode 100644
index 8b89a8b..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTestCfArm.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ActivitiesTransitionTestCfArm(flicker: FlickerTest) : ActivitiesTransitionTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt
new file mode 100644
index 0000000..3461907
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.server.wm.flicker.launch
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test the back and forward transition between 2 activities.
+ *
+ * To run this test: `atest FlickerTests:ActivitiesTransitionTest`
+ *
+ * Actions:
+ * ```
+ *     Launch an app
+ *     Launch a secondary activity within the app
+ *     Close the secondary activity back to the initial one
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Part of the test setup occurs automatically via
+ *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class ActivityTransitionTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
+    private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation)
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+            testApp.launchViaIntent(wmHelper)
+        }
+        teardown { testApp.exit(wmHelper) }
+        transitions {
+            testApp.openSecondActivity(device, wmHelper)
+            tapl.pressBack()
+            wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        }
+    }
+
+    /**
+     * Checks that the [ActivityOptions.LaunchNewActivity] activity is visible at the start of the
+     * transition, that [ActivityOptions.SimpleActivity] becomes visible during the transition, and
+     * that [ActivityOptions.LaunchNewActivity] is again visible at the end
+     */
+    @Presubmit
+    @Test
+    fun finishSubActivity() {
+        val buttonActivityComponent =
+            ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
+        val imeAutoFocusActivityComponent =
+            ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
+        flicker.assertWm {
+            this.isAppWindowOnTop(buttonActivityComponent)
+                .then()
+                .isAppWindowOnTop(imeAutoFocusActivityComponent)
+                .then()
+                .isAppWindowOnTop(buttonActivityComponent)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] window is not on top. The launcher cannot be
+     * asserted with `isAppWindowVisible` because it contains 2 windows with the exact same name,
+     * and both are never simultaneously visible
+     */
+    @Presubmit
+    @Test
+    fun launcherWindowNotOnTop() {
+        flicker.assertWm { this.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.LAUNCHER] layer is never visible during the transition
+     */
+    @Presubmit
+    @Test
+    fun launcherLayerNotVisible() {
+        flicker.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivityTransitionTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivityTransitionTestCfArm.kt
new file mode 100644
index 0000000..6bbf40e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivityTransitionTestCfArm.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ActivityTransitionTestCfArm(flicker: LegacyFlickerTest) : ActivityTransitionTest(flicker) {
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
deleted file mode 100644
index 549183f..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
+++ /dev/null
@@ -1,73 +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.server.wm.flicker.launch
-
-import android.tools.device.apphelpers.CameraAppHelper
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launching an app after cold opening camera
- *
- * To run this test: `atest FlickerTests:OpenAppAfterCameraTest`
- *
- * Notes: Some default assertions are inherited [OpenAppTransition]
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppAfterCameraTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
-    private val cameraApp = CameraAppHelper(instrumentation)
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            super.transition(this)
-            setup {
-                tapl.setExpectedRotationCheckEnabled(false)
-                // 1. Open camera - cold -> close it first
-                cameraApp.exit(wmHelper)
-                cameraApp.launchViaIntent(wmHelper)
-                // Can't use TAPL due to Recents not showing in 3 Button Nav in full screen mode
-                device.pressHome()
-                tapl.getWorkspace()
-            }
-            teardown { testApp.exit(wmHelper) }
-            transitions { testApp.launchViaIntent(wmHelper) }
-        }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt
deleted file mode 100644
index ac05c76..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppAfterCameraTestCfArm(flicker: FlickerTest) : OpenAppAfterCameraTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
deleted file mode 100644
index 3a80c66..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ /dev/null
@@ -1,219 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.tools.common.Rotation
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
-import androidx.test.filters.RequiresDevice
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from launcher
- *
- * To run this test: `atest FlickerTests:OpenAppColdFromIcon`
- *
- * Actions:
- * ```
- *     Make sure no apps are running on the device
- *     Launch an app [testApp] by clicking it's icon on all apps and wait animation to complete
- * ```
- *
- * Notes:
- * ```
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- * ```
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            super.transition(this)
-            setup {
-                if (flicker.scenario.isTablet) {
-                    tapl.setExpectedRotation(flicker.scenario.startRotation.value)
-                } else {
-                    tapl.setExpectedRotation(Rotation.ROTATION_0.value)
-                }
-                RemoveAllTasksButHomeRule.removeAllTasksButHome()
-            }
-            transitions {
-                tapl
-                    .goHome()
-                    .switchToAllApps()
-                    .getAppIcon(testApp.launcherName)
-                    .launch(testApp.`package`)
-            }
-            teardown { testApp.exit(wmHelper) }
-        }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun focusChanges() {
-        super.focusChanges()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appWindowReplacesLauncherAsTopWindow() {
-        super.appWindowReplacesLauncherAsTopWindow()
-    }
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appWindowAsTopWindowAtEnd() {
-        super.appWindowAsTopWindowAtEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appWindowBecomesTopWindow() {
-        super.appWindowBecomesTopWindow()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appWindowBecomesVisible() {
-        super.appWindowBecomesVisible()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appWindowIsTopWindowAtEnd() {
-        super.appWindowIsTopWindowAtEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appLayerBecomesVisible() {
-        super.appLayerBecomesVisible()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun appLayerReplacesLauncher() {
-        super.appLayerReplacesLauncher()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun cujCompleted() {
-        super.cujCompleted()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun entireScreenCovered() {
-        super.entireScreenCovered()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() {
-        super.navBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() {
-        super.navBarLayerPositionAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun navBarWindowIsAlwaysVisible() {
-        super.navBarWindowIsAlwaysVisible()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun navBarWindowIsVisibleAtStartAndEnd() {
-        super.navBarWindowIsVisibleAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun statusBarLayerIsVisibleAtStartAndEnd() {
-        super.statusBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun statusBarLayerPositionAtStartAndEnd() {
-        super.statusBarLayerPositionAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun statusBarWindowIsAlwaysVisible() {
-        super.statusBarWindowIsAlwaysVisible()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() {
-        super.taskBarLayerIsVisibleAtStartAndEnd()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun taskBarWindowIsAlwaysVisible() {
-        super.taskBarWindowIsAlwaysVisible()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-    }
-
-    @FlakyTest(bugId = 240916028)
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
-        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
deleted file mode 100644
index d33a272..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/** Some assertions will fail because of b/264415996 */
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppColdFromIconCfArm(flicker: FlickerTest) : OpenAppColdFromIcon(flicker) {
-    @Test
-    @FlakyTest
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
deleted file mode 100644
index 26f88d2..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ /dev/null
@@ -1,94 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.Postsubmit
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.setRotation
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from launcher
- *
- * To run this test: `atest FlickerTests:OpenAppColdTest`
- *
- * Actions:
- * ```
- *     Make sure no apps are running on the device
- *     Launch an app [testApp] and wait animation to complete
- * ```
- *
- * Notes:
- * ```
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- * ```
- */
-@RequiresDevice
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppColdTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            super.transition(this)
-            setup {
-                removeAllTasksButHome()
-                this.setRotation(flicker.scenario.startRotation)
-            }
-            teardown { testApp.exit(wmHelper) }
-            transitions { testApp.launchViaIntent(wmHelper) }
-        }
-
-    /** {@inheritDoc} */
-    @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
-
-    @Postsubmit
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt
deleted file mode 100644
index d9a99da..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppColdTestCfArm(flicker: FlickerTest) : OpenAppColdTest(flicker) {
-    @FlakyTest(bugId = 273696733)
-    @Test
-    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
new file mode 100644
index 0000000..ae1f78a
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
@@ -0,0 +1,218 @@
+/*
+ * 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.wm.flicker.launch
+
+import android.platform.test.annotations.FlakyTest
+import android.tools.common.Rotation
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.filters.RequiresDevice
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from launcher
+ *
+ * To run this test: `atest FlickerTests:OpenAppColdFromIcon`
+ *
+ * Actions:
+ * ```
+ *     Make sure no apps are running on the device
+ *     Launch an app [testApp] by clicking it's icon on all apps and wait animation to complete
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited [OpenAppTransition]
+ *     2. Part of the test setup occurs automatically via
+ *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class OpenAppFromIconColdTest(flicker: LegacyFlickerTest) :
+    OpenAppFromLauncherTransition(flicker) {
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                if (flicker.scenario.isTablet) {
+                    tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+                } else {
+                    tapl.setExpectedRotation(Rotation.ROTATION_0.value)
+                }
+                RemoveAllTasksButHomeRule.removeAllTasksButHome()
+            }
+            transitions {
+                tapl
+                    .goHome()
+                    .switchToAllApps()
+                    .getAppIcon(testApp.launcherName)
+                    .launch(testApp.`package`)
+            }
+            teardown { testApp.exit(wmHelper) }
+        }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun focusChanges() {
+        super.focusChanges()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowReplacesLauncherAsTopWindow() {
+        super.appWindowReplacesLauncherAsTopWindow()
+    }
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowAsTopWindowAtEnd() {
+        super.appWindowAsTopWindowAtEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowBecomesTopWindow() {
+        super.appWindowBecomesTopWindow()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowBecomesVisible() {
+        super.appWindowBecomesVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appWindowIsTopWindowAtEnd() {
+        super.appWindowIsTopWindowAtEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appLayerBecomesVisible() {
+        super.appLayerBecomesVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun appLayerReplacesLauncher() {
+        super.appLayerReplacesLauncher()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun cujCompleted() {
+        super.cujCompleted()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun entireScreenCovered() {
+        super.entireScreenCovered()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() {
+        super.navBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() {
+        super.navBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarWindowIsAlwaysVisible() {
+        super.navBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun navBarWindowIsVisibleAtStartAndEnd() {
+        super.navBarWindowIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() {
+        super.statusBarLayerPositionAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() {
+        super.statusBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() {
+        super.taskBarWindowIsAlwaysVisible()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
+    @FlakyTest(bugId = 240916028)
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt
new file mode 100644
index 0000000..4fd4a61
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.platform.test.annotations.FlakyTest
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/** Some assertions will fail because of b/264415996 */
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppFromIconColdTestCfArm(flicker: LegacyFlickerTest) : OpenAppFromIconColdTest(flicker) {
+    @Test
+    @FlakyTest
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
new file mode 100644
index 0000000..c95c548
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.wm.flicker.launch
+
+import android.tools.device.apphelpers.CameraAppHelper
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching an app after cold opening camera
+ *
+ * To run this test: `atest FlickerTests:OpenAppAfterCameraTest`
+ *
+ * Notes: Some default assertions are inherited [OpenAppTransition]
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class OpenAppFromIntentColdAfterCameraTest(flicker: LegacyFlickerTest) :
+    OpenAppFromLauncherTransition(flicker) {
+    private val cameraApp = CameraAppHelper(instrumentation)
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                tapl.setExpectedRotationCheckEnabled(false)
+                // 1. Open camera - cold -> close it first
+                cameraApp.exit(wmHelper)
+                cameraApp.launchViaIntent(wmHelper)
+                // Can't use TAPL due to Recents not showing in 3 Button Nav in full screen mode
+                device.pressHome()
+                tapl.getWorkspace()
+            }
+            teardown { testApp.exit(wmHelper) }
+            transitions { testApp.launchViaIntent(wmHelper) }
+        }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTestCfArm.kt
new file mode 100644
index 0000000..117cff2
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTestCfArm.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppFromIntentColdAfterCameraTestCfArm(flicker: LegacyFlickerTest) :
+    OpenAppFromIntentColdAfterCameraTest(flicker) {
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
new file mode 100644
index 0000000..e39a578
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.server.wm.flicker.launch
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.setRotation
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from launcher
+ *
+ * To run this test: `atest FlickerTests:OpenAppColdTest`
+ *
+ * Actions:
+ * ```
+ *     Make sure no apps are running on the device
+ *     Launch an app [testApp] and wait animation to complete
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited [OpenAppTransition]
+ *     2. Part of the test setup occurs automatically via
+ *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class OpenAppFromIntentColdTest(flicker: LegacyFlickerTest) :
+    OpenAppFromLauncherTransition(flicker) {
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                removeAllTasksButHome()
+                this.setRotation(flicker.scenario.startRotation)
+            }
+            teardown { testApp.exit(wmHelper) }
+            transitions { testApp.launchViaIntent(wmHelper) }
+        }
+
+    /** {@inheritDoc} */
+    @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+
+    @Postsubmit
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt
new file mode 100644
index 0000000..6d0b6f4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.platform.test.annotations.FlakyTest
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppFromIntentColdTestCfArm(flicker: LegacyFlickerTest) :
+    OpenAppFromIntentColdTest(flicker) {
+    @FlakyTest(bugId = 273696733)
+    @Test
+    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
new file mode 100644
index 0000000..d2c3807
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.server.wm.flicker.launch
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.setRotation
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test warm launching an app from launcher
+ *
+ * To run this test: `atest FlickerTests:OpenAppWarmTest`
+ *
+ * Actions:
+ * ```
+ *     Launch [testApp]
+ *     Press home
+ *     Relaunch an app [testApp] and wait animation to complete (only this action is traced)
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited [OpenAppTransition]
+ *     2. Part of the test setup occurs automatically via
+ *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class OpenAppFromIntentWarmTest(flicker: LegacyFlickerTest) :
+    OpenAppFromLauncherTransition(flicker) {
+    /** Defines the transition used to run the test */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                tapl.setExpectedRotationCheckEnabled(false)
+                testApp.launchViaIntent(wmHelper)
+                tapl.goHome()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+                this.setRotation(flicker.scenario.startRotation)
+            }
+            teardown { testApp.exit(wmHelper) }
+            transitions { testApp.launchViaIntent(wmHelper) }
+        }
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 206753786)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun appLayerBecomesVisible() = super.appLayerBecomesVisible_warmStart()
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun appWindowBecomesVisible() = super.appWindowBecomesVisible_warmStart()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt
new file mode 100644
index 0000000..3e0958a
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppFromIntentWarmTestCfArm(flicker: LegacyFlickerTest) :
+    OpenAppFromIntentWarmTest(flicker) {
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
index 3d5a8e3..62fb570 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
@@ -18,12 +18,13 @@
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import com.android.server.wm.flicker.replacesLayer
 import org.junit.Test
 
 /** Base class for app launch tests */
-abstract class OpenAppFromLauncherTransition(flicker: FlickerTest) : OpenAppTransition(flicker) {
+abstract class OpenAppFromLauncherTransition(flicker: LegacyFlickerTest) :
+    OpenAppTransition(flicker) {
 
     /** Checks that the focus changes from the [ComponentNameMatcher.LAUNCHER] to [testApp] */
     @Presubmit
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
deleted file mode 100644
index b21777b..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ /dev/null
@@ -1,135 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.Postsubmit
-import android.platform.test.rule.SettingOverrideRule
-import android.provider.Settings
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from a notification from the lock screen.
- *
- * This test assumes the device doesn't have AOD enabled
- *
- * To run this test: `atest FlickerTests:OpenAppFromLockNotificationCold`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Postsubmit
-open class OpenAppFromLockNotificationCold(flicker: FlickerTest) :
-    OpenAppFromNotificationCold(flicker) {
-
-    override val openingNotificationsFromLockScreen = true
-
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            // Needs to run at start of transition,
-            // so before the transition defined in super.transition
-            transitions { device.wakeUp() }
-
-            super.transition(this)
-
-            // Needs to run at the end of the setup, so after the setup defined in super.transition
-            setup {
-                device.sleep()
-                wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
-            }
-        }
-
-    /** {@inheritDoc} */
-    @Test @Ignore("Display is off at the start") override fun navBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Display is off at the start")
-    override fun statusBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Display is off at the start")
-    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test @Ignore("Display is off at the start") override fun taskBarWindowIsAlwaysVisible() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Display is off at the start")
-    override fun statusBarLayerIsVisibleAtStartAndEnd() =
-        super.statusBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarWindowIsAlwaysVisible() {}
-
-    /** {@inheritDoc} */
-    @Postsubmit
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-
-        /**
-         * Ensures that posted notifications will be visible on the lockscreen and not suppressed
-         * due to being marked as seen.
-         */
-        @ClassRule
-        @JvmField
-        val disableUnseenNotifFilterRule =
-            SettingOverrideRule(
-                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                /* value = */ "0",
-            )
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
deleted file mode 100644
index ec92ca6..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
+++ /dev/null
@@ -1,166 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
-import android.platform.test.rule.SettingOverrideRule
-import android.provider.Settings
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
-import org.junit.ClassRule
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test warm launching an app from a notification from the lock screen.
- *
- * This test assumes the device doesn't have AOD enabled
- *
- * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWarm`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppFromLockNotificationWarm(flicker: FlickerTest) : OpenAppFromNotificationWarm(flicker) {
-
-    override val openingNotificationsFromLockScreen = true
-
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            // Needs to run at start of transition,
-            // so before the transition defined in super.transition
-            transitions { device.wakeUp() }
-
-            super.transition(this)
-
-            // Needs to run at the end of the setup, so after the setup defined in super.transition
-            setup {
-                device.sleep()
-                wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
-            }
-        }
-
-    /**
-     * Checks that we start of with no top windows and then [testApp] becomes the first and only top
-     * window of the transition, with snapshot or splash screen windows optionally showing first.
-     */
-    @Test
-    @Presubmit
-    fun appWindowBecomesFirstAndOnlyTopWindow() {
-        flicker.assertWm {
-            this.hasNoVisibleAppWindow()
-                .then()
-                .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                .then()
-                .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
-                .then()
-                .isAppWindowOnTop(testApp)
-        }
-    }
-
-    /** Checks that the screen is locked at the start of the transition */
-    @Test
-    @Presubmit
-    fun screenLockedStart() {
-        flicker.assertWmStart { isKeyguardShowing() }
-    }
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun taskBarWindowIsAlwaysVisible() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarLayerIsVisibleAtStartAndEnd() =
-        super.statusBarLayerIsVisibleAtStartAndEnd()
-
-    /**
-     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
-     * transition
-     */
-    @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarWindowIsAlwaysVisible() {}
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 246284526)
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-
-        /**
-         * Ensures that posted notifications will be visible on the lockscreen and not suppressed
-         * due to being marked as seen.
-         */
-        @ClassRule
-        @JvmField
-        val disableUnseenNotifFilterRule =
-            SettingOverrideRule(
-                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                /* value= */ "0",
-            )
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
deleted file mode 100644
index 009d617..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
+++ /dev/null
@@ -1,136 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Postsubmit
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.helpers.wakeUpAndGoToHomeScreen
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from a notification from the lock screen when there is an app overlaid
- * on the lock screen.
- *
- * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWithLockOverlayApp`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Postsubmit
-class OpenAppFromLockNotificationWithLockOverlayApp(flicker: FlickerTest) :
-    OpenAppFromLockNotificationCold(flicker) {
-    private val showWhenLockedApp = ShowWhenLockedAppHelper(instrumentation)
-
-    // Although we are technically still locked here, the overlay app means we should open the
-    // notification shade as if we were unlocked.
-    override val openingNotificationsFromLockScreen = false
-
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            super.transition(this)
-
-            setup {
-                device.wakeUpAndGoToHomeScreen()
-
-                // Launch an activity that is shown when the device is locked
-                showWhenLockedApp.launchViaIntent(wmHelper)
-                wmHelper.StateSyncBuilder().withFullScreenApp(showWhenLockedApp).waitForAndVerify()
-
-                device.sleep()
-                wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
-            }
-
-            teardown { showWhenLockedApp.exit(wmHelper) }
-        }
-
-    @Test
-    @FlakyTest(bugId = 227143265)
-    fun showWhenLockedAppWindowBecomesVisible() {
-        flicker.assertWm {
-            this.hasNoVisibleAppWindow()
-                .then()
-                .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                .then()
-                .isAppWindowOnTop(showWhenLockedApp)
-        }
-    }
-
-    @Test
-    @FlakyTest(bugId = 227143265)
-    fun showWhenLockedAppLayerBecomesVisible() {
-        flicker.assertLayers {
-            this.isInvisible(showWhenLockedApp)
-                .then()
-                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                .then()
-                .isVisible(showWhenLockedApp)
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Presubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 227143265)
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 209599395)
-    @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
-
-    @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
-
-    @FlakyTest(bugId = 278227468)
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
-        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
deleted file mode 100644
index eae9ca1..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
+++ /dev/null
@@ -1,127 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import com.android.server.wm.flicker.navBarLayerPositionAtEnd
-import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
-import org.junit.Assume
-import org.junit.Ignore
-import org.junit.Test
-
-/** Base class for app launch tests from lock screen */
-abstract class OpenAppFromLockTransition(flicker: FlickerTest) : OpenAppTransition(flicker) {
-
-    /** Defines the transition used to run the test */
-    override val transition: FlickerBuilder.() -> Unit = {
-        super.transition(this)
-        setup {
-            device.sleep()
-            wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
-        }
-        teardown { testApp.exit(wmHelper) }
-        transitions { testApp.launchViaIntent(wmHelper) }
-    }
-
-    /** Check that we go from no focus to focus on the [testApp] */
-    @Presubmit
-    @Test
-    open fun focusChanges() {
-        flicker.assertEventLog { this.focusChanges("", testApp.`package`) }
-    }
-
-    /**
-     * Checks that we start of with no top windows and then [testApp] becomes the first and only top
-     * window of the transition, with snapshot or splash screen windows optionally showing first.
-     */
-    @FlakyTest(bugId = 203538234)
-    @Test
-    open fun appWindowBecomesFirstAndOnlyTopWindow() {
-        flicker.assertWm {
-            this.hasNoVisibleAppWindow()
-                .then()
-                .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                .then()
-                .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
-                .then()
-                .isAppWindowOnTop(testApp)
-        }
-    }
-
-    /** Checks that the screen is locked at the start of the transition */
-    @Presubmit
-    @Test
-    fun screenLockedStart() {
-        flicker.assertLayersStart { isEmpty() }
-    }
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 203538234)
-    @Test
-    override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun navBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun taskBarWindowIsAlwaysVisible() {}
-
-    /** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */
-    @Presubmit
-    @Test
-    open fun navBarLayerPositionAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarLayerPositionAtEnd()
-    }
-
-    /** Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the end of the transition */
-    @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarLayerIsVisibleAtStartAndEnd() {}
-
-    /**
-     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the end of the trace
-     *
-     * It is not possible to check at the start because the screen is off
-     */
-    @Presubmit
-    @Test
-    fun statusBarLayerIsVisibleAtEnd() {
-        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
new file mode 100644
index 0000000..687bc19
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.server.wm.flicker.launch
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import com.android.server.wm.flicker.navBarLayerPositionAtEnd
+import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
+import org.junit.Assume
+import org.junit.Ignore
+import org.junit.Test
+
+/** Base class for app launch tests from lock screen */
+abstract class OpenAppFromLockscreenTransition(flicker: LegacyFlickerTest) :
+    OpenAppTransition(flicker) {
+
+    /** Defines the transition used to run the test */
+    override val transition: FlickerBuilder.() -> Unit = {
+        super.transition(this)
+        setup {
+            device.sleep()
+            wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+        }
+        teardown { testApp.exit(wmHelper) }
+        transitions { testApp.launchViaIntent(wmHelper) }
+    }
+
+    /** Check that we go from no focus to focus on the [testApp] */
+    @Presubmit
+    @Test
+    open fun focusChanges() {
+        flicker.assertEventLog { this.focusChanges("", testApp.`package`) }
+    }
+
+    /**
+     * Checks that we start of with no top windows and then [testApp] becomes the first and only top
+     * window of the transition, with snapshot or splash screen windows optionally showing first.
+     */
+    @FlakyTest(bugId = 203538234)
+    @Test
+    open fun appWindowBecomesFirstAndOnlyTopWindow() {
+        flicker.assertWm {
+            this.hasNoVisibleAppWindow()
+                .then()
+                .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+                .then()
+                .isAppWindowOnTop(testApp)
+        }
+    }
+
+    /** Checks that the screen is locked at the start of the transition */
+    @Presubmit
+    @Test
+    fun screenLockedStart() {
+        flicker.assertLayersStart { isEmpty() }
+    }
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 203538234)
+    @Test
+    override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun navBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun statusBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun taskBarWindowIsAlwaysVisible() {}
+
+    /** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */
+    @Presubmit
+    @Test
+    open fun navBarLayerPositionAtEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerPositionAtEnd()
+    }
+
+    /** Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the end of the transition */
+    @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {}
+
+    /**
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the end of the trace
+     *
+     * It is not possible to check at the start because the screen is off
+     */
+    @Presubmit
+    @Test
+    fun statusBarLayerIsVisibleAtEnd() {
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
new file mode 100644
index 0000000..7a16060
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -0,0 +1,224 @@
+/*
+ * 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.server.wm.flicker.launch
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching an app while the device is locked
+ *
+ * This test assumes the device doesn't have AOD enabled
+ *
+ * To run this test: `atest FlickerTests:OpenAppNonResizeableTest`
+ *
+ * Actions:
+ * ```
+ *     Lock the device.
+ *     Launch an app on top of the lock screen [testApp] and wait animation to complete
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited [OpenAppTransition]
+ *     2. Part of the test setup occurs automatically via
+ *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class OpenAppFromLockscreenViaIntentTest(flicker: LegacyFlickerTest) :
+    OpenAppFromLockscreenTransition(flicker) {
+    override val testApp = NonResizeableAppHelper(instrumentation)
+
+    /**
+     * Checks that the [ComponentNameMatcher.NAV_BAR] layer starts invisible, becomes visible during
+     * unlocking animation and remains visible at the end
+     */
+    @Presubmit
+    @Test
+    fun navBarLayerVisibilityChanges() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.assertLayers {
+            this.isInvisible(ComponentNameMatcher.NAV_BAR)
+                .then()
+                .isVisible(ComponentNameMatcher.NAV_BAR)
+        }
+    }
+
+    /** Checks if [testApp] is visible at the end of the transition */
+    @Presubmit
+    @Test
+    fun appWindowBecomesVisibleAtEnd() {
+        flicker.assertWmEnd { this.isAppWindowVisible(testApp) }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.NAV_BAR] starts the transition invisible, then becomes
+     * visible during the unlocking animation and remains visible at the end of the transition
+     */
+    @Presubmit
+    @Test
+    fun navBarWindowsVisibilityChanges() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.assertWm {
+            this.isNonAppWindowInvisible(ComponentNameMatcher.NAV_BAR)
+                .then()
+                .isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
+        }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.TASK_BAR] starts the transition invisible, then becomes
+     * visible during the unlocking animation and remains visible at the end of the transition
+     */
+    @Presubmit
+    @Test
+    fun taskBarLayerIsVisibleAtEnd() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the end of the trace
+     *
+     * It is not possible to check at the start because the screen is off
+     */
+    @Presubmit
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+    }
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun navBarLayerIsVisibleAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun taskBarWindowIsAlwaysVisible() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun navBarWindowIsAlwaysVisible() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun statusBarWindowIsAlwaysVisible() {}
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun appWindowBecomesFirstAndOnlyTopWindow() =
+        super.appWindowBecomesFirstAndOnlyTopWindow()
+
+    /** {@inheritDoc} */
+    @Presubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+
+    /** Checks the [ComponentNameMatcher.NAV_BAR] is visible at the end of the transition */
+    @Presubmit
+    @Test
+    fun navBarLayerIsVisibleAtEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
+    }
+
+    /** {@inheritDoc} */
+    @FlakyTest
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun appLayerBecomesVisible() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        super.appLayerBecomesVisible()
+    }
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 227143265)
+    @Test
+    fun appLayerBecomesVisibleTablet() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        super.appLayerBecomesVisible()
+    }
+
+    @Presubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    @FlakyTest(bugId = 251217585)
+    @Test
+    override fun focusChanges() {
+        super.focusChanges()
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
+                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
+                supportedRotations = listOf(Rotation.ROTATION_0)
+            )
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
deleted file mode 100644
index 7bcb910..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
+++ /dev/null
@@ -1,113 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.Postsubmit
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from a notification.
- *
- * This test assumes the device doesn't have AOD enabled
- *
- * To run this test: `atest FlickerTests:OpenAppFromNotificationCold`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Postsubmit
-open class OpenAppFromNotificationCold(flicker: FlickerTest) :
-    OpenAppFromNotificationWarm(flicker) {
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            super.transition(this)
-
-            setup {
-                // Close the app that posted the notification to trigger a cold start next time
-                // it is open - can't just kill it because that would remove the notification.
-                tapl.setExpectedRotationCheckEnabled(false)
-                tapl.goHome()
-                tapl.workspace.switchToOverview()
-                tapl.overview.dismissAllTasks()
-            }
-        }
-
-    @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
-
-    @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarLayerPositionAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarLayerIsVisibleAtStartAndEnd() =
-        super.statusBarLayerIsVisibleAtStartAndEnd()
-
-    /**
-     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
-     * transition
-     */
-    @Presubmit @Test open fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarWindowIsAlwaysVisible() {}
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt
deleted file mode 100644
index 8b4a613..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.platform.test.annotations.Postsubmit
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Postsubmit
-class OpenAppFromNotificationColdCfArm(flicker: FlickerTest) :
-    OpenAppFromNotificationCold(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
deleted file mode 100644
index 425e674d..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
+++ /dev/null
@@ -1,207 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.Postsubmit
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.helpers.wakeUpAndGoToHomeScreen
-import android.view.WindowInsets
-import android.view.WindowManager
-import androidx.test.filters.RequiresDevice
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.helpers.NotificationAppHelper
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd
-import com.android.server.wm.flicker.navBarLayerPositionAtEnd
-import com.android.server.wm.flicker.navBarWindowIsVisibleAtEnd
-import com.android.server.wm.flicker.taskBarLayerIsVisibleAtEnd
-import com.android.server.wm.flicker.taskBarWindowIsVisibleAtEnd
-import org.junit.Assume
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from a notification.
- *
- * This test assumes the device doesn't have AOD enabled
- *
- * To run this test: `atest FlickerTests:OpenAppFromNotificationWarm`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppFromNotificationWarm(flicker: FlickerTest) : OpenAppTransition(flicker) {
-    override val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
-
-    open val openingNotificationsFromLockScreen = false
-
-    /** {@inheritDoc} */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            setup {
-                device.wakeUpAndGoToHomeScreen()
-                this.setRotation(flicker.scenario.startRotation)
-                testApp.launchViaIntent(wmHelper)
-                wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
-                testApp.postNotification(wmHelper)
-                device.pressHome()
-                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-            }
-
-            transitions {
-                var startY = 10
-                var endY = 3 * device.displayHeight / 4
-                var steps = 25
-                if (openingNotificationsFromLockScreen) {
-                    val wm: WindowManager =
-                        instrumentation.context.getSystemService(WindowManager::class.java)
-                            ?: error("Unable to connect to WindowManager service")
-                    val metricInsets = wm.currentWindowMetrics.windowInsets
-                    val insets =
-                        metricInsets.getInsetsIgnoringVisibility(
-                            WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout()
-                        )
-
-                    startY = insets.top + 100
-                    endY = device.displayHeight / 2
-                    steps = 4
-                }
-
-                // Swipe down to show the notification shade
-                val x = device.displayWidth / 2
-                device.swipe(x, startY, x, endY, steps)
-                device.waitForIdle(2000)
-                instrumentation.uiAutomation.syncInputTransactions()
-
-                // Launch the activity by clicking the notification
-                val notification =
-                    device.wait(Until.findObject(By.text("Flicker Test Notification")), 2000L)
-                notification?.click() ?: error("Notification not found")
-                instrumentation.uiAutomation.syncInputTransactions()
-
-                // Wait for the app to launch
-                wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
-            }
-
-            teardown { testApp.exit(wmHelper) }
-        }
-
-    @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart()
-
-    @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart()
-
-    @Presubmit
-    @Test
-    open fun notificationAppWindowVisibleAtEnd() {
-        flicker.assertWmEnd { this.isAppWindowVisible(testApp) }
-    }
-
-    @Presubmit
-    @Test
-    open fun notificationAppWindowOnTopAtEnd() {
-        flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
-    }
-
-    @Presubmit
-    @Test
-    open fun notificationAppLayerVisibleAtEnd() {
-        flicker.assertLayersEnd { this.isVisible(testApp) }
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible at the end of the
-     * transition
-     *
-     * Note: Large screen only
-     */
-    @Presubmit
-    @Test
-    open fun taskBarWindowIsVisibleAtEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
-        flicker.taskBarWindowIsVisibleAtEnd()
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible at the end of the transition
-     *
-     * Note: Large screen only
-     */
-    @Presubmit
-    @Test
-    open fun taskBarLayerIsVisibleAtEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
-        flicker.taskBarLayerIsVisibleAtEnd()
-    }
-
-    /** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */
-    @Presubmit
-    @Test
-    open fun navBarLayerPositionAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarLayerPositionAtEnd()
-    }
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    open fun navBarLayerIsVisibleAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarLayerIsVisibleAtEnd()
-    }
-
-    @Presubmit
-    @Test
-    open fun navBarWindowIsVisibleAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.navBarWindowIsVisibleAtEnd()
-    }
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Display is off at the start")
-    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Postsubmit
-    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt
deleted file mode 100644
index 43d28fa..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppFromNotificationWarmCfArm(flicker: FlickerTest) :
-    OpenAppFromNotificationWarm(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 8e1b059..eec6bfd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -19,11 +19,11 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.FixMethodOrder
@@ -60,7 +60,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppFromOverviewTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
+open class OpenAppFromOverviewTest(flicker: LegacyFlickerTest) :
+    OpenAppFromLauncherTransition(flicker) {
 
     /** Defines the transition used to run the test */
     override val transition: FlickerBuilder.() -> Unit
@@ -106,13 +107,11 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
index ff24190..ab6a1ea 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt
@@ -16,10 +16,10 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -30,18 +30,17 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppFromOverviewTestCfArm(flicker: FlickerTest) : OpenAppFromOverviewTest(flicker) {
+open class OpenAppFromOverviewTestCfArm(flicker: LegacyFlickerTest) :
+    OpenAppFromOverviewTest(flicker) {
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
          * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
deleted file mode 100644
index 1383ae3..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ /dev/null
@@ -1,224 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
-import android.tools.common.NavBar
-import android.tools.common.Rotation
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
-import org.junit.Assume
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launching an app while the device is locked
- *
- * This test assumes the device doesn't have AOD enabled
- *
- * To run this test: `atest FlickerTests:OpenAppNonResizeableTest`
- *
- * Actions:
- * ```
- *     Lock the device.
- *     Launch an app on top of the lock screen [testApp] and wait animation to complete
- * ```
- *
- * Notes:
- * ```
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- * ```
- */
-@RequiresDevice
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppNonResizeableTest(flicker: FlickerTest) : OpenAppFromLockTransition(flicker) {
-    override val testApp = NonResizeableAppHelper(instrumentation)
-
-    /**
-     * Checks that the [ComponentNameMatcher.NAV_BAR] layer starts invisible, becomes visible during
-     * unlocking animation and remains visible at the end
-     */
-    @Presubmit
-    @Test
-    fun navBarLayerVisibilityChanges() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.assertLayers {
-            this.isInvisible(ComponentNameMatcher.NAV_BAR)
-                .then()
-                .isVisible(ComponentNameMatcher.NAV_BAR)
-        }
-    }
-
-    /** Checks if [testApp] is visible at the end of the transition */
-    @Presubmit
-    @Test
-    fun appWindowBecomesVisibleAtEnd() {
-        flicker.assertWmEnd { this.isAppWindowVisible(testApp) }
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.NAV_BAR] starts the transition invisible, then becomes
-     * visible during the unlocking animation and remains visible at the end of the transition
-     */
-    @Presubmit
-    @Test
-    fun navBarWindowsVisibilityChanges() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.assertWm {
-            this.isNonAppWindowInvisible(ComponentNameMatcher.NAV_BAR)
-                .then()
-                .isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
-        }
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.TASK_BAR] starts the transition invisible, then becomes
-     * visible during the unlocking animation and remains visible at the end of the transition
-     */
-    @Presubmit
-    @Test
-    fun taskBarLayerIsVisibleAtEnd() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
-        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
-    }
-
-    /**
-     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the end of the trace
-     *
-     * It is not possible to check at the start because the screen is off
-     */
-    @Presubmit
-    @Test
-    override fun statusBarLayerIsVisibleAtStartAndEnd() {
-        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
-    }
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun navBarLayerIsVisibleAtStartAndEnd() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun taskBarWindowIsAlwaysVisible() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun navBarWindowIsAlwaysVisible() {}
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
-    override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Test
-    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
-    override fun statusBarWindowIsAlwaysVisible() {}
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun appWindowBecomesFirstAndOnlyTopWindow() =
-        super.appWindowBecomesFirstAndOnlyTopWindow()
-
-    /** {@inheritDoc} */
-    @Presubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
-
-    /** Checks the [ComponentNameMatcher.NAV_BAR] is visible at the end of the transition */
-    @Presubmit
-    @Test
-    fun navBarLayerIsVisibleAtEnd() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
-    }
-
-    /** {@inheritDoc} */
-    @FlakyTest
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun appLayerBecomesVisible() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
-        super.appLayerBecomesVisible()
-    }
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 227143265)
-    @Test
-    fun appLayerBecomesVisibleTablet() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
-        super.appLayerBecomesVisible()
-    }
-
-    @Presubmit
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
-        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
-    @FlakyTest(bugId = 251217585)
-    @Test
-    override fun focusChanges() {
-        super.focusChanges()
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
-                supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
-                supportedRotations = listOf(Rotation.ROTATION_0)
-            )
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index 87a14c6..bb11be5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -20,7 +20,7 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -28,7 +28,7 @@
 import org.junit.Test
 
 /** Base class for app launch tests */
-abstract class OpenAppTransition(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class OpenAppTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
 
     /** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
deleted file mode 100644
index 3385830..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ /dev/null
@@ -1,105 +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.server.wm.flicker.launch
-
-import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.Presubmit
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.setRotation
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test warm launching an app from launcher
- *
- * To run this test: `atest FlickerTests:OpenAppWarmTest`
- *
- * Actions:
- * ```
- *     Launch [testApp]
- *     Press home
- *     Relaunch an app [testApp] and wait animation to complete (only this action is traced)
- * ```
- *
- * Notes:
- * ```
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- * ```
- */
-@RequiresDevice
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppWarmTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
-    /** Defines the transition used to run the test */
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            super.transition(this)
-            setup {
-                tapl.setExpectedRotationCheckEnabled(false)
-                testApp.launchViaIntent(wmHelper)
-                tapl.goHome()
-                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-                this.setRotation(flicker.scenario.startRotation)
-            }
-            teardown { testApp.exit(wmHelper) }
-            transitions { testApp.launchViaIntent(wmHelper) }
-        }
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 206753786)
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun appLayerBecomesVisible() = super.appLayerBecomesVisible_warmStart()
-
-    /** {@inheritDoc} */
-    @Presubmit
-    @Test
-    override fun appWindowBecomesVisible() = super.appWindowBecomesVisible_warmStart()
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt
deleted file mode 100644
index d8b38b3..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-@FlickerServiceCompatible
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppWarmTestCfArm(flicker: FlickerTest) : OpenAppWarmTest(flicker) {
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
new file mode 100644
index 0000000..1bdb6e71
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.wm.flicker.launch
+
+import android.os.SystemClock
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.CameraAppHelper
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import android.view.KeyEvent
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.setRotation
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching camera from launcher by double pressing power button
+ *
+ * To run this test: `atest FlickerTests:OpenCameraOnDoubleClickPowerButton`
+ *
+ * Actions:
+ * ```
+ *     Make sure no apps are running on the device
+ *     Launch an app [testApp] and wait animation to complete
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited [OpenAppTransition]
+ *     2. Part of the test setup occurs automatically via
+ *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ *        including configuring navigation mode, initial orientation and ensuring no
+ *        apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenCameraFromHomeOnDoubleClickPowerButtonTest(flicker: LegacyFlickerTest) :
+    OpenAppFromLauncherTransition(flicker) {
+    private val cameraApp = CameraAppHelper(instrumentation)
+    override val testApp: StandardAppHelper
+        get() = cameraApp
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                RemoveAllTasksButHomeRule.removeAllTasksButHome()
+                this.setRotation(flicker.scenario.startRotation)
+            }
+            transitions {
+                device.pressKeyCode(KeyEvent.KEYCODE_POWER)
+                SystemClock.sleep(100)
+                device.pressKeyCode(KeyEvent.KEYCODE_POWER)
+                wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(cameraApp).waitForAndVerify()
+            }
+            teardown { RemoveAllTasksButHomeRule.removeAllTasksButHome() }
+        }
+
+    @Postsubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
+
+    @Postsubmit @Test override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
+
+    @Postsubmit @Test override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
+
+    @Postsubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+
+    @Postsubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+
+    @Postsubmit @Test override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
+
+    @Postsubmit
+    @Test
+    override fun appWindowReplacesLauncherAsTopWindow() =
+        super.appWindowReplacesLauncherAsTopWindow()
+
+    @Postsubmit @Test override fun focusChanges() = super.focusChanges()
+
+    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+    @Ignore("Not applicable to this CUJ. App is full screen at the end")
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+    @Postsubmit
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
+    @Postsubmit
+    @Test
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+    @Ignore("Status bar visibility depends on whether the permission dialog is displayed or not")
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    @Ignore("Status bar visibility depends on whether the permission dialog is displayed or not")
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
+
+    @Ignore("Status bar visibility depends on whether the permission dialog is displayed or not")
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
+
+    @Ignore("Not applicable to this CUJ. App is full screen at the end")
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+
+    @Ignore("Not applicable to this CUJ. App is full screen at the end")
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+        flicker.assertLayers {
+            this.visibleLayersShownMoreThanOneConsecutiveEntry(
+                LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS +
+                    listOf(CAMERA_BACKGROUND)
+            )
+        }
+    }
+
+    @Postsubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    @Ignore("Not applicable to this CUJ. App is full screen at the end")
+    @Test
+    override fun navBarWindowIsVisibleAtStartAndEnd() {
+        super.navBarWindowIsVisibleAtStartAndEnd()
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+
+        private val CAMERA_BACKGROUND =
+            ComponentNameMatcher(
+                "Background for SurfaceView" +
+                    "[com.google.android.GoogleCamera/" +
+                    "com.google.android.apps.camera.legacy.app.activity.main.CameraActivity]"
+            )
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
deleted file mode 100644
index ae9ca80..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
+++ /dev/null
@@ -1,167 +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.server.wm.flicker.launch
-
-import android.os.SystemClock
-import android.platform.test.annotations.Postsubmit
-import android.tools.device.apphelpers.CameraAppHelper
-import android.tools.device.apphelpers.StandardAppHelper
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
-import android.view.KeyEvent
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.setRotation
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching camera from launcher by double pressing power button
- *
- * To run this test: `atest FlickerTests:OpenCameraOnDoubleClickPowerButton`
- *
- * Actions:
- * ```
- *     Make sure no apps are running on the device
- *     Launch an app [testApp] and wait animation to complete
- * ```
- *
- * Notes:
- * ```
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- * ```
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenCameraOnDoubleClickPowerButton(flicker: FlickerTest) :
-    OpenAppFromLauncherTransition(flicker) {
-    private val cameraApp = CameraAppHelper(instrumentation)
-    override val testApp: StandardAppHelper
-        get() = cameraApp
-
-    override val transition: FlickerBuilder.() -> Unit
-        get() = {
-            setup {
-                RemoveAllTasksButHomeRule.removeAllTasksButHome()
-                this.setRotation(flicker.scenario.startRotation)
-            }
-            transitions {
-                device.pressKeyCode(KeyEvent.KEYCODE_POWER)
-                SystemClock.sleep(100)
-                device.pressKeyCode(KeyEvent.KEYCODE_POWER)
-                wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(cameraApp).waitForAndVerify()
-            }
-            teardown { RemoveAllTasksButHomeRule.removeAllTasksButHome() }
-        }
-
-    @Postsubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
-
-    @Postsubmit @Test override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd()
-
-    @Postsubmit @Test override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
-
-    @Postsubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
-
-    @Postsubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
-
-    @Postsubmit @Test override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd()
-
-    @Postsubmit
-    @Test
-    override fun appWindowReplacesLauncherAsTopWindow() =
-        super.appWindowReplacesLauncherAsTopWindow()
-
-    @Postsubmit @Test override fun focusChanges() = super.focusChanges()
-
-    @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
-
-    @Ignore("Not applicable to this CUJ. App is full screen at the end")
-    @Test
-    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
-    @Postsubmit
-    @Test
-    override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
-
-    @Postsubmit
-    @Test
-    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
-
-    @Ignore("Status bar visibility depends on whether the permission dialog is displayed or not")
-    @Test
-    override fun statusBarLayerIsVisibleAtStartAndEnd() =
-        super.statusBarLayerIsVisibleAtStartAndEnd()
-
-    @Ignore("Status bar visibility depends on whether the permission dialog is displayed or not")
-    @Test
-    override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
-
-    @Ignore("Status bar visibility depends on whether the permission dialog is displayed or not")
-    @Test
-    override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
-
-    @Ignore("Not applicable to this CUJ. App is full screen at the end")
-    @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
-
-    @Ignore("Not applicable to this CUJ. App is full screen at the end")
-    @Test
-    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
-
-    @Postsubmit
-    @Test
-    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
-        super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
-    @Postsubmit
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
-        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
-    @Ignore("Not applicable to this CUJ. App is full screen at the end")
-    @Test
-    override fun navBarWindowIsVisibleAtStartAndEnd() {
-        super.navBarWindowIsVisibleAtStartAndEnd()
-    }
-
-    companion object {
-        /**
-         * Creates the test configurations.
-         *
-         * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
-         * navigation modes.
-         */
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
index b48611e..98e3646 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -25,8 +25,8 @@
 import android.tools.device.flicker.junit.FlickerBuilderProvider
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
 import android.tools.device.helpers.wakeUpAndGoToHomeScreen
 import androidx.test.filters.RequiresDevice
@@ -54,7 +54,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OverrideTaskTransitionTest(val flicker: FlickerTest) {
+class OverrideTaskTransitionTest(val flicker: LegacyFlickerTest) {
 
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val testApp = SimpleAppHelper(instrumentation)
@@ -121,8 +121,6 @@
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index d0fd732..39c8ca0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -28,8 +28,8 @@
 import android.tools.common.traces.component.IComponentMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import android.tools.device.traces.parsers.toFlickerComponent
 import androidx.test.filters.RequiresDevice
@@ -58,7 +58,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
+class TaskTransitionTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val launchNewTaskApp = NewTasksAppHelper(instrumentation)
     private val simpleApp = SimpleAppHelper(instrumentation)
     private val wallpaper by lazy { getWallpaperPackage(instrumentation) }
@@ -214,8 +214,6 @@
 
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt
new file mode 100644
index 0000000..6f5daeb
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.notification
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.rule.SettingOverrideRule
+import android.provider.Settings
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from a notification from the lock screen.
+ *
+ * This test assumes the device doesn't have AOD enabled
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromLockNotificationCold`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Postsubmit
+open class OpenAppFromLockscreenNotificationColdTest(flicker: LegacyFlickerTest) :
+    OpenAppFromNotificationColdTest(flicker) {
+
+    override val openingNotificationsFromLockScreen = true
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            // Needs to run at start of transition,
+            // so before the transition defined in super.transition
+            transitions { device.wakeUp() }
+
+            super.transition(this)
+
+            // Needs to run at the end of the setup, so after the setup defined in super.transition
+            setup {
+                device.sleep()
+                wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+            }
+        }
+
+    /** {@inheritDoc} */
+    @Test @Ignore("Display is off at the start") override fun navBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Display is off at the start")
+    override fun statusBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Display is off at the start")
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test @Ignore("Display is off at the start") override fun taskBarWindowIsAlwaysVisible() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Display is off at the start")
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarWindowIsAlwaysVisible() {}
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+
+        /**
+         * Ensures that posted notifications will be visible on the lockscreen and not suppressed
+         * due to being marked as seen.
+         */
+        @ClassRule
+        @JvmField
+        val disableUnseenNotifFilterRule =
+            SettingOverrideRule(
+                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                /* value = */ "0",
+            )
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
new file mode 100644
index 0000000..483caa7
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.notification
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
+import android.platform.test.rule.SettingOverrideRule
+import android.provider.Settings
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
+import org.junit.ClassRule
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test warm launching an app from a notification from the lock screen.
+ *
+ * This test assumes the device doesn't have AOD enabled
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWarm`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppFromLockscreenNotificationWarmTest(flicker: LegacyFlickerTest) :
+    OpenAppFromNotificationWarmTest(flicker) {
+
+    override val openingNotificationsFromLockScreen = true
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            // Needs to run at start of transition,
+            // so before the transition defined in super.transition
+            transitions { device.wakeUp() }
+
+            super.transition(this)
+
+            // Needs to run at the end of the setup, so after the setup defined in super.transition
+            setup {
+                device.sleep()
+                wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+            }
+        }
+
+    /**
+     * Checks that we start of with no top windows and then [testApp] becomes the first and only top
+     * window of the transition, with snapshot or splash screen windows optionally showing first.
+     */
+    @Test
+    @Presubmit
+    fun appWindowBecomesFirstAndOnlyTopWindow() {
+        flicker.assertWm {
+            this.hasNoVisibleAppWindow()
+                .then()
+                .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+                .then()
+                .isAppWindowOnTop(testApp)
+        }
+    }
+
+    /** Checks that the screen is locked at the start of the transition */
+    @Test
+    @Presubmit
+    fun screenLockedStart() {
+        flicker.assertWmStart { isKeyguardShowing() }
+    }
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun taskBarWindowIsAlwaysVisible() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun statusBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /**
+     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
+     * transition
+     */
+    @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarWindowIsAlwaysVisible() {}
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 246284526)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+
+        /**
+         * Ensures that posted notifications will be visible on the lockscreen and not suppressed
+         * due to being marked as seen.
+         */
+        @ClassRule
+        @JvmField
+        val disableUnseenNotifFilterRule =
+            SettingOverrideRule(
+                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+                /* value= */ "0",
+            )
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
new file mode 100644
index 0000000..4e8a697
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.notification
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from a notification from the lock screen when there is an app overlaid
+ * on the lock screen.
+ *
+ * This test assumes the device doesn't have AOD enabled
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWithLockOverlayApp`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Postsubmit
+class OpenAppFromLockscreenNotificationWithOverlayAppTest(flicker: LegacyFlickerTest) :
+    OpenAppFromLockscreenNotificationColdTest(flicker) {
+    private val showWhenLockedApp = ShowWhenLockedAppHelper(instrumentation)
+
+    // Although we are technically still locked here, the overlay app means we should open the
+    // notification shade as if we were unlocked.
+    override val openingNotificationsFromLockScreen = false
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+
+            setup {
+                device.wakeUpAndGoToHomeScreen()
+
+                // Launch an activity that is shown when the device is locked
+                showWhenLockedApp.launchViaIntent(wmHelper)
+                wmHelper.StateSyncBuilder().withFullScreenApp(showWhenLockedApp).waitForAndVerify()
+
+                device.sleep()
+                wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+            }
+
+            teardown { showWhenLockedApp.exit(wmHelper) }
+        }
+
+    @Test
+    @FlakyTest(bugId = 227143265)
+    fun showWhenLockedAppWindowBecomesVisible() {
+        flicker.assertWm {
+            this.hasNoVisibleAppWindow()
+                .then()
+                .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isAppWindowOnTop(showWhenLockedApp)
+        }
+    }
+
+    @Test
+    @FlakyTest(bugId = 227143265)
+    fun showWhenLockedAppLayerBecomesVisible() {
+        flicker.assertLayers {
+            this.isInvisible(showWhenLockedApp)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isVisible(showWhenLockedApp)
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Presubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 227143265)
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun navBarLayerIsVisibleAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+    @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+
+    @FlakyTest(bugId = 278227468)
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt
new file mode 100644
index 0000000..50dec3b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.notification
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from a notification.
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromNotificationCold`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Postsubmit
+open class OpenAppFromNotificationColdTest(flicker: LegacyFlickerTest) :
+    OpenAppFromNotificationWarmTest(flicker) {
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+
+            setup {
+                // Close the app that posted the notification to trigger a cold start next time
+                // it is open - can't just kill it because that would remove the notification.
+                tapl.setExpectedRotationCheckEnabled(false)
+                tapl.goHome()
+                tapl.workspace.switchToOverview()
+                tapl.overview.dismissAllTasks()
+            }
+        }
+
+    @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
+
+    @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun statusBarLayerPositionAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /**
+     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
+     * transition
+     */
+    @Presubmit @Test open fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
+    override fun navBarWindowIsAlwaysVisible() {}
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTestCfArm.kt
new file mode 100644
index 0000000..a147171
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTestCfArm.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.notification
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Postsubmit
+class OpenAppFromNotificationColdTestCfArm(flicker: LegacyFlickerTest) :
+    OpenAppFromNotificationColdTest(flicker) {
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
new file mode 100644
index 0000000..19a070b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.notification
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.view.WindowInsets
+import android.view.WindowManager
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd
+import com.android.server.wm.flicker.navBarLayerPositionAtEnd
+import com.android.server.wm.flicker.navBarWindowIsVisibleAtEnd
+import com.android.server.wm.flicker.taskBarLayerIsVisibleAtEnd
+import com.android.server.wm.flicker.taskBarWindowIsVisibleAtEnd
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from a notification.
+ *
+ * To run this test: `atest FlickerTests:OpenAppFromNotificationWarm`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) :
+    OpenAppTransition(flicker) {
+    override val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    open val openingNotificationsFromLockScreen = false
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            setup {
+                device.wakeUpAndGoToHomeScreen()
+                this.setRotation(flicker.scenario.startRotation)
+                testApp.launchViaIntent(wmHelper)
+                wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+                testApp.postNotification(wmHelper)
+                device.pressHome()
+                wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+            }
+
+            transitions {
+                var startY = 10
+                var endY = 3 * device.displayHeight / 4
+                var steps = 25
+                if (openingNotificationsFromLockScreen) {
+                    val wm: WindowManager =
+                        instrumentation.context.getSystemService(WindowManager::class.java)
+                            ?: error("Unable to connect to WindowManager service")
+                    val metricInsets = wm.currentWindowMetrics.windowInsets
+                    val insets =
+                        metricInsets.getInsetsIgnoringVisibility(
+                            WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout()
+                        )
+
+                    startY = insets.top + 100
+                    endY = device.displayHeight / 2
+                    steps = 4
+                }
+
+                // Swipe down to show the notification shade
+                val x = device.displayWidth / 2
+                device.swipe(x, startY, x, endY, steps)
+                device.waitForIdle(2000)
+                instrumentation.uiAutomation.syncInputTransactions()
+
+                // Launch the activity by clicking the notification
+                val notification =
+                    device.wait(Until.findObject(By.text("Flicker Test Notification")), 2000L)
+                notification?.click() ?: error("Notification not found")
+                instrumentation.uiAutomation.syncInputTransactions()
+
+                // Wait for the app to launch
+                wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+            }
+
+            teardown { testApp.exit(wmHelper) }
+        }
+
+    @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart()
+
+    @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart()
+
+    @Presubmit
+    @Test
+    open fun notificationAppWindowVisibleAtEnd() {
+        flicker.assertWmEnd { this.isAppWindowVisible(testApp) }
+    }
+
+    @Presubmit
+    @Test
+    open fun notificationAppWindowOnTopAtEnd() {
+        flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
+    }
+
+    @Presubmit
+    @Test
+    open fun notificationAppLayerVisibleAtEnd() {
+        flicker.assertLayersEnd { this.isVisible(testApp) }
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible at the end of the
+     * transition
+     *
+     * Note: Large screen only
+     */
+    @Presubmit
+    @Test
+    open fun taskBarWindowIsVisibleAtEnd() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarWindowIsVisibleAtEnd()
+    }
+
+    /**
+     * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible at the end of the transition
+     *
+     * Note: Large screen only
+     */
+    @Presubmit
+    @Test
+    open fun taskBarLayerIsVisibleAtEnd() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarLayerIsVisibleAtEnd()
+    }
+
+    /** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */
+    @Presubmit
+    @Test
+    open fun navBarLayerPositionAtEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerPositionAtEnd()
+    }
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    open fun navBarLayerIsVisibleAtEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarLayerIsVisibleAtEnd()
+    }
+
+    @Presubmit
+    @Test
+    open fun navBarWindowIsVisibleAtEnd() {
+        Assume.assumeFalse(flicker.scenario.isTablet)
+        flicker.navBarWindowIsVisibleAtEnd()
+    }
+
+    /** {@inheritDoc} */
+    @Test
+    @Ignore("Display is off at the start")
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
+
+    /** {@inheritDoc} */
+    @Test
+    @Postsubmit
+    override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTestCfArm.kt
new file mode 100644
index 0000000..98356d7
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTestCfArm.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.notification
+
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppFromNotificationWarmTestCfArm(flicker: LegacyFlickerTest) :
+    OpenAppFromNotificationWarmTest(flicker) {
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppTransition.kt
new file mode 100644
index 0000000..684b4b7
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppTransition.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.server.wm.flicker.notification
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.setRotation
+import org.junit.Test
+
+/** Base class for app launch tests */
+abstract class OpenAppTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
+    protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
+
+    /** {@inheritDoc} */
+    override val transition: FlickerBuilder.() -> Unit = {
+        setup {
+            tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+            device.wakeUpAndGoToHomeScreen()
+            this.setRotation(flicker.scenario.startRotation)
+        }
+        teardown { testApp.exit(wmHelper) }
+    }
+
+    /**
+     * Checks that the [testApp] layer doesn't exist or is invisible at the start of the transition,
+     * but is created and/or becomes visible during the transition.
+     */
+    @Presubmit
+    @Test
+    open fun appLayerBecomesVisible() {
+        appLayerBecomesVisible_coldStart()
+    }
+
+    protected fun appLayerBecomesVisible_coldStart() {
+        flicker.assertLayers {
+            this.notContains(testApp)
+                .then()
+                .isInvisible(testApp, isOptional = true)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+                .then()
+                .isVisible(testApp)
+        }
+    }
+
+    protected fun appLayerBecomesVisible_warmStart() {
+        flicker.assertLayers {
+            this.isInvisible(testApp)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+                .then()
+                .isVisible(testApp)
+        }
+    }
+
+    /**
+     * Checks that the [testApp] window doesn't exist at the start of the transition, that it is
+     * created (invisible - optional) and becomes visible during the transition
+     *
+     * The `isAppWindowInvisible` step is optional because we log once per frame, upon logging, the
+     * window may be visible or not depending on what was processed until that moment.
+     */
+    @Presubmit @Test open fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
+
+    protected fun appWindowBecomesVisible_coldStart() {
+        flicker.assertWm {
+            this.notContains(testApp)
+                .then()
+                .isAppWindowInvisible(testApp, isOptional = true)
+                .then()
+                .isAppWindowVisible(testApp)
+        }
+    }
+
+    protected fun appWindowBecomesVisible_warmStart() {
+        flicker.assertWm {
+            this.isAppWindowInvisible(testApp)
+                .then()
+                .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isAppWindowVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+                .then()
+                .isAppWindowVisible(testApp)
+        }
+    }
+
+    /**
+     * Checks that [testApp] window is not on top at the start of the transition, and then becomes
+     * the top visible window until the end of the transition.
+     */
+    @Presubmit
+    @Test
+    open fun appWindowBecomesTopWindow() {
+        flicker.assertWm {
+            this.isAppWindowNotOnTop(testApp)
+                .then()
+                .isAppWindowOnTop(
+                    testApp.or(ComponentNameMatcher.SNAPSHOT).or(ComponentNameMatcher.SPLASH_SCREEN)
+                )
+        }
+    }
+
+    /**
+     * Checks that [testApp] window is not on top at the start of the transition, and then becomes
+     * the top visible window until the end of the transition.
+     */
+    @Presubmit
+    @Test
+    open fun appWindowIsTopWindowAtEnd() {
+        flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index a8b80ad..7883910 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -23,8 +23,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
@@ -52,7 +52,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsBackTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class QuickSwitchBetweenTwoAppsBackTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
 
@@ -243,10 +243,9 @@
 
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTestCfArm.kt
index f970a79..f68cd5c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTestCfArm.kt
@@ -18,8 +18,8 @@
 
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -28,15 +28,14 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class QuickSwitchBetweenTwoAppsBackTestCfArm(flicker: FlickerTest) :
+class QuickSwitchBetweenTwoAppsBackTestCfArm(flicker: LegacyFlickerTest) :
     QuickSwitchBetweenTwoAppsBackTest(flicker) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 96cd8ff..1c4c7cd 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -23,8 +23,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
@@ -53,7 +53,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsForwardTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
 
@@ -261,10 +261,9 @@
 
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestCfArm.kt
index 9f48cda..3de58ac 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestCfArm.kt
@@ -18,8 +18,8 @@
 
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -28,15 +28,14 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class QuickSwitchBetweenTwoAppsForwardTestCfArm(flicker: FlickerTest) :
+class QuickSwitchBetweenTwoAppsForwardTestCfArm(flicker: LegacyFlickerTest) :
     QuickSwitchBetweenTwoAppsForwardTest(flicker) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 7e935f0..6417456 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -24,8 +24,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -52,7 +52,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchFromLauncherTest(flicker: FlickerTest) : BaseTest(flicker) {
+open class QuickSwitchFromLauncherTest(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     private val testApp = SimpleAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -272,12 +272,11 @@
 
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
                 // TODO: Test with 90 rotation
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTestCfArm.kt
index af671df..84fc754 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTestCfArm.kt
@@ -19,8 +19,8 @@
 import android.tools.common.NavBar
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -29,17 +29,16 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchFromLauncherTestCfArm(flicker: FlickerTest) :
+open class QuickSwitchFromLauncherTestCfArm(flicker: LegacyFlickerTest) :
     QuickSwitchFromLauncherTest(flicker) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
                 // TODO: Test with 90 rotation
                 supportedRotations = listOf(Rotation.ROTATION_0)
             )
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index ef75c61..842ece3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -21,8 +21,8 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import org.junit.FixMethodOrder
@@ -34,7 +34,7 @@
 /**
  * Test opening an app and cycling through app rotations
  *
- * Currently runs:
+ * Currently, runs:
  * ```
  *      0 -> 90 degrees
  *      90 -> 0 degrees
@@ -85,7 +85,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ChangeAppRotationTest(flicker: FlickerTest) : RotationTransition(flicker) {
+open class ChangeAppRotationTest(flicker: LegacyFlickerTest) : RotationTransition(flicker) {
     override val testApp = SimpleAppHelper(instrumentation)
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -140,13 +140,11 @@
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation
-         * modes.
+         * See [LegacyFlickerTestFactory.rotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.rotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.rotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt
index 0e6b20f..1ab5c5a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt
@@ -17,8 +17,8 @@
 package com.android.server.wm.flicker.rotation
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -27,18 +27,16 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ChangeAppRotationTestCfArm(flicker: FlickerTest) : ChangeAppRotationTest(flicker) {
+class ChangeAppRotationTestCfArm(flicker: LegacyFlickerTest) : ChangeAppRotationTest(flicker) {
     companion object {
         /**
          * Creates the test configurations.
          *
-         * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation
-         * modes.
+         * See [LegacyFlickerTestFactory.rotationTests] for configuring screen orientation and
+         * navigation modes.
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.rotationTests()
-        }
+        fun getParams() = LegacyFlickerTestFactory.rotationTests()
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index fe9da33..b0ca4d2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -20,13 +20,13 @@
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.Test
 
 /** Base class for app rotation tests */
-abstract class RotationTransition(flicker: FlickerTest) : BaseTest(flicker) {
+abstract class RotationTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
     protected abstract val testApp: StandardAppHelper
 
     /** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index f654bdd4..b6ad3cc7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -19,11 +19,12 @@
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.ScenarioBuilder
+import android.tools.common.ScenarioImpl
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.view.WindowManager
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
@@ -91,7 +92,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SeamlessAppRotationTest(flicker: FlickerTest) : RotationTransition(flicker) {
+open class SeamlessAppRotationTest(flicker: LegacyFlickerTest) : RotationTransition(flicker) {
     override val testApp = SeamlessRotationAppHelper(instrumentation)
 
     /** {@inheritDoc} */
@@ -236,43 +237,50 @@
     }
 
     companion object {
-        private val FlickerTest.starveUiThread
+        private val LegacyFlickerTest.starveUiThread
             get() =
-                getConfigValue<Boolean>(ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD)
+                scenario.getConfigValue<Boolean>(
+                    ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD
+                )
                     ?: false
 
         @JvmStatic
         protected fun createConfig(
-            sourceConfig: FlickerTest,
+            sourceConfig: LegacyFlickerTest,
             starveUiThread: Boolean
-        ): FlickerTest {
-            val originalScenario = sourceConfig.initialize("createConfig")
+        ): LegacyFlickerTest {
+            val originalScenario = sourceConfig.initialize("createConfig") as ScenarioImpl
             val nameExt = if (starveUiThread) "_BUSY_UI_THREAD" else ""
             val newConfig =
                 ScenarioBuilder()
-                    .fromScenario(originalScenario)
+                    .forClass(originalScenario.testClass)
+                    .withStartRotation(originalScenario.startRotation)
+                    .withEndRotation(originalScenario.endRotation)
+                    .withNavBarMode(originalScenario.navBarMode)
+                    .withExtraConfigs(originalScenario.extraConfig)
+                    .withDescriptionOverride(originalScenario.description)
                     .withExtraConfig(
                         ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD,
                         starveUiThread
                     )
                     .withDescriptionOverride("${originalScenario.description}$nameExt")
-            return FlickerTest(newConfig)
+            return LegacyFlickerTest(newConfig)
         }
 
         /**
          * Creates the test configurations for seamless rotation based on the default rotation tests
-         * from [FlickerTestFactory.rotationTests], but adding a flag (
+         * from [LegacyFlickerTestFactory.rotationTests], but adding a flag (
          * [ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate if the app should
          * starve the UI thread of not
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.rotationTests().flatMap { sourceConfig ->
-                val defaultRun = createConfig(sourceConfig, starveUiThread = false)
-                val busyUiRun = createConfig(sourceConfig, starveUiThread = true)
+        fun getParams() =
+            LegacyFlickerTestFactory.rotationTests().flatMap { sourceCfg ->
+                val legacyCfg = sourceCfg as LegacyFlickerTest
+                val defaultRun = createConfig(legacyCfg, starveUiThread = false)
+                val busyUiRun = createConfig(legacyCfg, starveUiThread = true)
                 listOf(defaultRun, busyUiRun)
             }
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt
index b236d87..592be05 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt
@@ -17,8 +17,8 @@
 package com.android.server.wm.flicker.rotation
 
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerTest
-import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
@@ -29,22 +29,23 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SeamlessAppRotationTestCfArm(flicker: FlickerTest) : SeamlessAppRotationTest(flicker) {
+open class SeamlessAppRotationTestCfArm(flicker: LegacyFlickerTest) :
+    SeamlessAppRotationTest(flicker) {
     companion object {
         /**
          * Creates the test configurations for seamless rotation based on the default rotation tests
-         * from [FlickerTestFactory.rotationTests], but adding a flag (
+         * from [LegacyFlickerTestFactory.rotationTests], but adding a flag (
          * [ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate if the app should
          * starve the UI thread of not
          */
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams(): Collection<FlickerTest> {
-            return FlickerTestFactory.rotationTests().flatMap { sourceConfig ->
-                val defaultRun = createConfig(sourceConfig, starveUiThread = false)
-                val busyUiRun = createConfig(sourceConfig, starveUiThread = true)
+        fun getParams() =
+            LegacyFlickerTestFactory.rotationTests().flatMap { sourceCfg ->
+                val legacyCfg = sourceCfg as LegacyFlickerTest
+                val defaultRun = createConfig(legacyCfg, starveUiThread = false)
+                val busyUiRun = createConfig(legacyCfg, starveUiThread = true)
                 listOf(defaultRun, busyUiRun)
             }
-        }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt
new file mode 100644
index 0000000..8242e9a
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/Utils.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service
+
+import android.app.Instrumentation
+import android.platform.test.rule.NavigationModeRule
+import android.platform.test.rule.PressHomeRule
+import android.platform.test.rule.UnlockScreenRule
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.apphelpers.MessagingAppHelper
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.device.flicker.rules.LaunchAppRule
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.RuleChain
+
+object Utils {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    fun testSetupRule(navigationMode: NavBar, rotation: Rotation): RuleChain {
+        return RuleChain.outerRule(UnlockScreenRule())
+            .around(
+                NavigationModeRule(navigationMode.value, /* changeNavigationModeAfterTest */ false)
+            )
+            .around(
+                LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false)
+            )
+            .around(RemoveAllTasksButHomeRule())
+            .around(
+                ChangeDisplayOrientationRule(
+                    rotation,
+                    resetOrientationAfterTest = false,
+                    clearCacheAfterParsing = false
+                )
+            )
+            .around(PressHomeRule())
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
new file mode 100644
index 0000000..030b292
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButton3ButtonLandscape :
+    CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
new file mode 100644
index 0000000..770da143
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButton3ButtonPortrait :
+    CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
new file mode 100644
index 0000000..4b2206d7
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButtonGesturalNavLandscape :
+    CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
new file mode 100644
index 0000000..54b471e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppBackButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppBackButtonGesturalNavPortrait :
+    CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonLandscape.kt
new file mode 100644
index 0000000..47c2529
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonLandscape.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppHomeButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppHomeButton3ButtonLandscape : CloseAppHomeButton(Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppHomeButton() = super.closeAppHomeButton()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonPortrait.kt
new file mode 100644
index 0000000..b18148f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppHomeButton3ButtonPortrait.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppHomeButton
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppHomeButton3ButtonPortrait : CloseAppHomeButton(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppHomeButton() = super.closeAppHomeButton()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..8543015
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavLandscape.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppSwipeToHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppSwipeToHomeGesturalNavLandscape : CloseAppSwipeToHome(Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppSwipeToHome() = super.closeAppSwipeToHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..f88963b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppSwipeToHomeGesturalNavPortrait.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.close.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.close.scenarios.CloseAppSwipeToHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CloseAppSwipeToHomeGesturalNavPortrait : CloseAppSwipeToHome(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+    @Test
+    override fun closeAppSwipeToHome() = super.closeAppSwipeToHome()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppBackButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppBackButton.kt
new file mode 100644
index 0000000..2aaacde
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppBackButton.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.server.wm.flicker.service.close.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CloseAppBackButton(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    val rotation: Rotation = Rotation.ROTATION_0
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp = SimpleAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotation(rotation.value)
+        testApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun closeAppBackButtonTest() {
+        tapl.pressBack()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppHomeButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppHomeButton.kt
new file mode 100644
index 0000000..08683a3
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppHomeButton.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.server.wm.flicker.service.close.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CloseAppHomeButton(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp = SimpleAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_3BUTTON, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotation(rotation.value)
+        testApp.launchViaIntent(wmHelper)
+        tapl.setExpectedRotationCheckEnabled(false)
+    }
+
+    @Test
+    open fun closeAppHomeButton() {
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppSwipeToHome.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppSwipeToHome.kt
new file mode 100644
index 0000000..360e114
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/scenarios/CloseAppSwipeToHome.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.server.wm.flicker.service.close.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class CloseAppSwipeToHome(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp = SimpleAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotation(rotation.value)
+        testApp.launchViaIntent(wmHelper)
+        tapl.setExpectedRotationCheckEnabled(false)
+    }
+
+    @Test
+    open fun closeAppSwipeToHome() {
+        tapl.goHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavLandscape.kt
new file mode 100644
index 0000000..bb18770
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationCold3ButtonNavLandscape :
+    OpenAppFromLockscreenNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationCold() =
+        super.openAppFromLockscreenNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavPortrait.kt
new file mode 100644
index 0000000..1c3cc21
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationCold3ButtonNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationCold3ButtonNavPortrait :
+    OpenAppFromLockscreenNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationCold() =
+        super.openAppFromLockscreenNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavLandscape.kt
new file mode 100644
index 0000000..46d36db
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationColdGesturalNavLandscape :
+    OpenAppFromLockscreenNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationCold() =
+        super.openAppFromLockscreenNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavPortrait.kt
new file mode 100644
index 0000000..f6a668fe
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationColdGesturalNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationColdGesturalNavPortrait :
+    OpenAppFromLockscreenNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationCold() =
+        super.openAppFromLockscreenNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape.kt
new file mode 100644
index 0000000..93200ba
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarm3ButtonNavLandscape :
+    OpenAppFromLockscreenNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWarm() =
+        super.openAppFromLockscreenNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait.kt
new file mode 100644
index 0000000..f5d41d2
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarm3ButtonNavPortrait :
+    OpenAppFromLockscreenNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWarm() =
+        super.openAppFromLockscreenNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavLandscape.kt
new file mode 100644
index 0000000..28f322b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarmGesturalNavLandscape :
+    OpenAppFromLockscreenNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWarm() =
+        super.openAppFromLockscreenNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavPortrait.kt
new file mode 100644
index 0000000..beb3fae
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWarmGesturalNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromLockscreenNotificationWarmGesturalNavPortrait :
+    OpenAppFromLockscreenNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWarm() =
+        super.openAppFromLockscreenNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt
new file mode 100644
index 0000000..4adcc8b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+@Ignore("b/294418322: no notification launch animation exists when keyguard is occluded")
+class OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape :
+    OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWithOverlayApp() =
+        super.openAppFromLockscreenNotificationWithOverlayApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt
new file mode 100644
index 0000000..f7211e7
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+@Ignore("b/294418322: no notification launch animation exists when keyguard is occluded")
+class OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait :
+    OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWithOverlayApp() =
+        super.openAppFromLockscreenNotificationWithOverlayApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt
new file mode 100644
index 0000000..1ade956
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+@Ignore("b/294418322: no notification launch animation exists when keyguard is occluded")
+class OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape :
+    OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWithOverlayApp() =
+        super.openAppFromLockscreenNotificationWithOverlayApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt
new file mode 100644
index 0000000..ea26f08
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+@Ignore("b/294418322: no notification launch animation exists when keyguard is occluded")
+class OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait :
+    OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromLockscreenNotificationWithOverlayApp() =
+        super.openAppFromLockscreenNotificationWithOverlayApp()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavLandscape.kt
new file mode 100644
index 0000000..866190f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationCold3ButtonNavLandscape :
+    OpenAppFromNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavPortrait.kt
new file mode 100644
index 0000000..a8d6283
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationCold3ButtonNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationCold3ButtonNavPortrait :
+    OpenAppFromNotificationCold(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavLandscape.kt
new file mode 100644
index 0000000..ef84c3f
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationColdGesturalNavLandscape :
+    OpenAppFromNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavPortrait.kt
new file mode 100644
index 0000000..5bb6b37
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationColdGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationCold
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationColdGesturalNavPortrait :
+    OpenAppFromNotificationCold(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationCold() = super.openAppFromNotificationCold()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavLandscape.kt
new file mode 100644
index 0000000..39f25e9
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarm3ButtonNavLandscape :
+    OpenAppFromNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavPortrait.kt
new file mode 100644
index 0000000..28816bc
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarm3ButtonNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarm3ButtonNavPortrait :
+    OpenAppFromNotificationWarm(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavLandscape.kt
new file mode 100644
index 0000000..94ffe4e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarmGesturalNavLandscape :
+    OpenAppFromNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavPortrait.kt
new file mode 100644
index 0000000..e5f5ec4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromNotificationWarmGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.flicker
+
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromNotificationWarm
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppFromNotificationWarmGesturalNavPortrait :
+    OpenAppFromNotificationWarm(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
+    @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
+    @Test
+    override fun openAppFromNotificationWarm() = super.openAppFromNotificationWarm()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/NotificationUtils.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/NotificationUtils.kt
new file mode 100644
index 0000000..ac4e169
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/NotificationUtils.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import android.view.WindowInsets
+import android.view.WindowManager
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
+
+object NotificationUtils {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+
+    fun openNotification(openingNotificationsFromLockScreen: Boolean) {
+        var startY = 10
+        var endY = 3 * device.displayHeight / 4
+        var steps = 25
+        if (openingNotificationsFromLockScreen) {
+            val wm: WindowManager =
+                instrumentation.context.getSystemService(WindowManager::class.java)
+                    ?: error("Unable to connect to WindowManager service")
+            val metricInsets = wm.currentWindowMetrics.windowInsets
+            val insets =
+                metricInsets.getInsetsIgnoringVisibility(
+                    WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout()
+                )
+
+            startY = insets.top + 100
+            endY = device.displayHeight / 2
+            steps = 4
+        }
+
+        // Swipe down to show the notification shade
+        val x = device.displayWidth / 2
+        device.swipe(x, startY, x, endY, steps)
+        device.waitForIdle(2000)
+        instrumentation.uiAutomation.syncInputTransactions()
+
+        // Launch the activity by clicking the notification
+        val notification =
+            device.wait(Until.findObject(By.text("Flicker Test Notification")), 2000L)
+        notification?.click() ?: error("Notification not found")
+        instrumentation.uiAutomation.syncInputTransactions()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationCold.kt
new file mode 100644
index 0000000..9c53ab3
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationCold.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromLockscreenNotificationCold(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    val rotation: Rotation = Rotation.ROTATION_0
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    val openingNotificationsFromLockScreen = true
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    @Before
+    fun setup() {
+        device.wakeUpAndGoToHomeScreen()
+        testApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        testApp.postNotification(wmHelper)
+        device.pressHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+        // Close the app that posted the notification to trigger a cold start next time
+        // it is open - can't just kill it because that would remove the notification.
+        tapl.setExpectedRotationCheckEnabled(false)
+        tapl.goHome()
+        tapl.workspace.switchToOverview()
+        tapl.overview.dismissAllTasks()
+
+        device.sleep()
+        wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+    }
+
+    @Test
+    open fun openAppFromLockscreenNotificationCold() {
+        device.wakeUp()
+
+        NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+        // Wait for the app to launch
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWarm.kt
new file mode 100644
index 0000000..31f55d9
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWarm.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromLockscreenNotificationWarm(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    val rotation: Rotation = Rotation.ROTATION_0
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    private val openingNotificationsFromLockScreen = true
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    @Before
+    fun setup() {
+        device.wakeUpAndGoToHomeScreen()
+        testApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        testApp.postNotification(wmHelper)
+        device.pressHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+        device.sleep()
+        wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+    }
+
+    @Test
+    open fun openAppFromLockscreenNotificationWarm() {
+        device.wakeUp()
+
+        NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+        // Wait for the app to launch
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWithOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWithOverlayApp.kt
new file mode 100644
index 0000000..b971555
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromLockscreenNotificationWithOverlayApp.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromLockscreenNotificationWithOverlayApp(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    val rotation: Rotation = Rotation.ROTATION_0
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val showWhenLockedApp = ShowWhenLockedAppHelper(instrumentation)
+    private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    // Although we are technically still locked here, the overlay app means we should open the
+    // notification shade as if we were unlocked.
+    private val openingNotificationsFromLockScreen = false
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    @Before
+    fun setup() {
+        device.wakeUpAndGoToHomeScreen()
+        testApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        testApp.postNotification(wmHelper)
+        device.pressHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+        // Close the app that posted the notification to trigger a cold start next time
+        // it is open - can't just kill it because that would remove the notification.
+        tapl.setExpectedRotationCheckEnabled(false)
+        tapl.goHome()
+        tapl.workspace.switchToOverview()
+        tapl.overview.dismissAllTasks()
+
+        device.sleep()
+        wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+
+        device.wakeUpAndGoToHomeScreen()
+
+        // Launch an activity that is shown when the device is locked
+        showWhenLockedApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(showWhenLockedApp).waitForAndVerify()
+
+        device.sleep()
+        wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify()
+    }
+
+    @Test
+    open fun openAppFromLockscreenNotificationWithOverlayApp() {
+        device.wakeUp()
+        NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+        // Wait for the app to launch
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+        showWhenLockedApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationCold.kt
new file mode 100644
index 0000000..fed077e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationCold.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromNotificationCold(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    val rotation: Rotation = Rotation.ROTATION_0
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val device = UiDevice.getInstance(instrumentation)
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    private val openingNotificationsFromLockScreen = false
+
+    @Before
+    fun setup() {
+        device.wakeUpAndGoToHomeScreen()
+        testApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        testApp.postNotification(wmHelper)
+        device.pressHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+
+        // Close the app that posted the notification to trigger a cold start next time
+        // it is open - can't just kill it because that would remove the notification.
+        tapl.setExpectedRotationCheckEnabled(false)
+        tapl.goHome()
+        tapl.workspace.switchToOverview()
+        tapl.overview.dismissAllTasks()
+    }
+
+    @Test
+    open fun openAppFromNotificationCold() {
+        NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+        // Wait for the app to launch
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationWarm.kt
new file mode 100644
index 0000000..403790e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/scenarios/OpenAppFromNotificationWarm.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.notification.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+abstract class OpenAppFromNotificationWarm(
+    val gestureMode: NavBar = NavBar.MODE_GESTURAL,
+    val rotation: Rotation = Rotation.ROTATION_0
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val device = UiDevice.getInstance(instrumentation)
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
+
+    private val openingNotificationsFromLockScreen = false
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(gestureMode, rotation)
+
+    @Before
+    fun setup() {
+        device.wakeUpAndGoToHomeScreen()
+        testApp.launchViaIntent(wmHelper)
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+        testApp.postNotification(wmHelper)
+        device.pressHome()
+        wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
+    }
+
+    @Test
+    open fun openAppFromNotificationWarm() {
+        NotificationUtils.openNotification(openingNotificationsFromLockScreen)
+        // Wait for the app to launch
+        wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavLandscape.kt
new file mode 100644
index 0000000..d611a42
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsBack
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsBackGesturalNavLandscape :
+    QuickSwitchBetweenTwoAppsBack(Rotation.ROTATION_90) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchBetweenTwoAppsBack() = super.quickSwitchBetweenTwoAppsBack()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavPortrait.kt
new file mode 100644
index 0000000..e6bcbba
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsBackGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsBack
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsBackGesturalNavPortrait :
+    QuickSwitchBetweenTwoAppsBack(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchBetweenTwoAppsBack() = super.quickSwitchBetweenTwoAppsBack()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape.kt
new file mode 100644
index 0000000..2a26d63
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsForward
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsForwardGesturalNavLandscape :
+    QuickSwitchBetweenTwoAppsForward(Rotation.ROTATION_90) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchBetweenTwoAppsForward() = super.quickSwitchBetweenTwoAppsForward()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait.kt
new file mode 100644
index 0000000..5ce7143
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchBetweenTwoAppsForward
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchBetweenTwoAppsForwardGesturalNavPortrait :
+    QuickSwitchBetweenTwoAppsForward(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchBetweenTwoAppsForward() = super.quickSwitchBetweenTwoAppsForward()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavLandscape.kt
new file mode 100644
index 0000000..cff47bb
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavLandscape.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchFromLauncher
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchFromLauncherGesturalNavLandscape : QuickSwitchFromLauncher(Rotation.ROTATION_90) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchFromLauncher() = super.quickSwitchFromLauncher()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavPortrait.kt
new file mode 100644
index 0000000..33095a6
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/flicker/QuickSwitchFromLauncherGesturalNavPortrait.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.service.quickswitch.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.server.wm.flicker.service.quickswitch.scenarios.QuickSwitchFromLauncher
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class QuickSwitchFromLauncherGesturalNavPortrait : QuickSwitchFromLauncher(Rotation.ROTATION_0) {
+    @ExpectedScenarios(["QUICKSWITCH"])
+    @Test
+    override fun quickSwitchFromLauncher() = super.quickSwitchFromLauncher()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt
new file mode 100644
index 0000000..93130c4
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.server.wm.flicker.service.quickswitch.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class QuickSwitchBetweenTwoAppsBack(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp1 = SimpleAppHelper(instrumentation)
+    private val testApp2 = NonResizeableAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotation(rotation.value)
+        tapl.setIgnoreTaskbarVisibility(true)
+        testApp1.launchViaIntent(wmHelper)
+        testApp2.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun quickSwitchBetweenTwoAppsBack() {
+        tapl.launchedAppState.quickSwitchToPreviousApp()
+        wmHelper
+            .StateSyncBuilder()
+            .withFullScreenApp(testApp1)
+            .withNavOrTaskBarVisible()
+            .withStatusBarVisible()
+            .waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp1.exit(wmHelper)
+        testApp2.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt
new file mode 100644
index 0000000..4941eea
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.server.wm.flicker.service.quickswitch.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class QuickSwitchBetweenTwoAppsForward(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp1 = SimpleAppHelper(instrumentation)
+    private val testApp2 = NonResizeableAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotation(rotation.value)
+
+        testApp1.launchViaIntent(wmHelper)
+        testApp2.launchViaIntent(wmHelper)
+        tapl.launchedAppState.quickSwitchToPreviousApp()
+        wmHelper
+            .StateSyncBuilder()
+            .withFullScreenApp(testApp1)
+            .withNavOrTaskBarVisible()
+            .withStatusBarVisible()
+            .waitForAndVerify()
+    }
+
+    @Test
+    open fun quickSwitchBetweenTwoAppsForward() {
+        tapl.launchedAppState.quickSwitchToPreviousAppSwipeLeft()
+        wmHelper
+            .StateSyncBuilder()
+            .withFullScreenApp(testApp2)
+            .withNavOrTaskBarVisible()
+            .withStatusBarVisible()
+            .waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp1.exit(wmHelper)
+        testApp2.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchFromLauncher.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchFromLauncher.kt
new file mode 100644
index 0000000..7afe526
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchFromLauncher.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.server.wm.flicker.service.quickswitch.scenarios
+
+import android.app.Instrumentation
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.service.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Base Test Class")
+abstract class QuickSwitchFromLauncher(val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val testApp = SimpleAppHelper(instrumentation)
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        tapl.setExpectedRotationCheckEnabled(false)
+
+        tapl.setExpectedRotation(rotation.value)
+
+        testApp.launchViaIntent(wmHelper)
+        tapl.goHome()
+        wmHelper
+            .StateSyncBuilder()
+            .withHomeActivityVisible()
+            .withWindowSurfaceDisappeared(testApp)
+            .waitForAndVerify()
+    }
+
+    @Test
+    open fun quickSwitchFromLauncher() {
+        tapl.workspace.quickSwitchToPreviousApp()
+        wmHelper
+            .StateSyncBuilder()
+            .withFullScreenApp(testApp)
+            .withNavOrTaskBarVisible()
+            .withStatusBarVisible()
+            .waitForAndVerify()
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 1ec9ec9..ff9799a 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -22,7 +22,9 @@
     <application android:allowBackup="false"
                  android:supportsRtl="true">
         <uses-library android:name="androidx.window.extensions" android:required="false"/>
-
+        <property
+            android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
+            android:value="true" />
         <activity android:name=".SimpleActivity"
                   android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity"
                   android:theme="@style/CutoutShortEdges"
@@ -100,6 +102,24 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name=".LaunchTransparentActivity"
+                  android:resizeableActivity="false"
+                  android:screenOrientation="portrait"
+                  android:theme="@android:style/Theme"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity"
+                  android:label="LaunchTransparentActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".TransparentActivity"
+                  android:theme="@style/TransparentTheme"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.TransparentActivity"
+                  android:label="TransparentActivity"
+                  android:exported="false">
+        </activity>
         <activity android:name=".LaunchNewActivity"
                   android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewActivity"
                   android:theme="@style/CutoutShortEdges"
@@ -191,11 +211,34 @@
             </intent-filter>
         </activity>
         <activity
+            android:name=".ActivityEmbeddingTrampolineActivity"
+            android:label="ActivityEmbedding Trampoline"
+            android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
+            android:theme="@style/CutoutShortEdges"
+            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+            android:exported="false">
+        </activity>
+        <activity
             android:name=".ActivityEmbeddingSecondaryActivity"
             android:label="ActivityEmbedding Secondary"
             android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
             android:theme="@style/CutoutShortEdges"
             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+            android:supportsPictureInPicture="true"
+            android:exported="false"/>
+        <activity
+            android:name=".ActivityEmbeddingThirdActivity"
+            android:label="ActivityEmbedding Third"
+            android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
+            android:theme="@style/CutoutShortEdges"
+            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+            android:exported="false"/>
+        <activity
+            android:name=".ActivityEmbeddingAlwaysExpandActivity"
+            android:label="ActivityEmbedding AlwaysExpand"
+            android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
+            android:theme="@style/CutoutShortEdges"
+            android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
             android:exported="false"/>
         <activity
             android:name=".ActivityEmbeddingPlaceholderPrimaryActivity"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml
index 3a02cad..f0dfdfc 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml
@@ -20,5 +20,4 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
-
 </LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
index d78b9a8..86c21906 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
@@ -25,16 +25,55 @@
         android:id="@+id/launch_secondary_activity_button"
         android:layout_width="wrap_content"
         android:layout_height="48dp"
-        android:layout_centerHorizontal="true"
         android:onClick="launchSecondaryActivity"
+        android:tag="LEFT_TO_RIGHT"
         android:text="Launch Secondary Activity" />
 
     <Button
+        android:id="@+id/launch_secondary_activity_rtl_button"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:onClick="launchSecondaryActivity"
+        android:tag="RIGHT_TO_LEFT"
+        android:text="Launch Secondary Activity in RTL" />
+
+    <Button
+        android:id="@+id/launch_secondary_activity_horizontally_button"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:onClick="launchSecondaryActivity"
+        android:tag="BOTTOM_TO_TOP"
+        android:text="Launch Secondary Activity Horizontally" />
+
+    <Button
         android:id="@+id/launch_placeholder_split_button"
         android:layout_width="wrap_content"
         android:layout_height="48dp"
-        android:layout_centerHorizontal="true"
         android:onClick="launchPlaceholderSplit"
+        android:tag="LEFT_TO_RIGHT"
         android:text="Launch Placeholder Split" />
 
+    <Button
+        android:id="@+id/launch_always_expand_activity_button"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:onClick="launchAlwaysExpandActivity"
+        android:text="Launch Always Expand Activity" />
+
+    <Button
+        android:id="@+id/launch_placeholder_split_rtl_button"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:onClick="launchPlaceholderSplit"
+        android:tag="RIGHT_TO_LEFT"
+        android:text="Launch Placeholder Split in RTL" />
+
+    <Button
+        android:id="@+id/launch_trampoline_button"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:onClick="launchTrampolineActivity"
+        android:tag="LEFT_TO_RIGHT"
+        android:text="Launch Trampoline Activity" />
+
 </LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
new file mode 100644
index 0000000..6d4de99
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/secondary_activity_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+  <Button
+      android:id="@+id/finish_secondary_activity_button"
+      android:layout_width="wrap_content"
+      android:layout_height="48dp"
+      android:text="Finish" />
+
+  <Button
+      android:id="@+id/launch_third_activity_button"
+      android:layout_width="wrap_content"
+      android:layout_height="48dp"
+      android:layout_centerHorizontal="true"
+      android:onClick="launchThirdActivity"
+      android:text="Launch a third activity" />
+
+  <ToggleButton
+      android:id="@+id/toggle_split_ratio_button"
+      android:layout_width="wrap_content"
+      android:layout_height="48dp"
+      android:textOn="Ratio 0.5"
+      android:textOff="Ratio 0.3"
+      android:checked="false" />
+
+  <Button
+      android:id="@+id/secondary_enter_pip_button"
+      android:layout_width="wrap_content"
+      android:layout_height="48dp"
+      android:text="Enter pip" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml
new file mode 100644
index 0000000..0730ded
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+</FrameLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml
new file mode 100644
index 0000000..ff4ead9
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:background="@android:color/black">
+
+        <Button
+            android:id="@+id/button_launch_transparent"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:layout_centerVertical="true"
+            android:text="Launch Transparent" />
+        <Button
+            android:id="@+id/button_request_permission"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:layout_centerVertical="true"
+            android:text="Request Permission" />
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 1d21fd5..e51ed29 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -43,6 +43,13 @@
         <item name="android:windowSoftInputMode">stateUnchanged</item>
     </style>
 
+    <style name="TransparentTheme" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:backgroundDimEnabled">false</item>
+    </style>
+
     <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault">
         <item name="android:windowDisablePreview">true</item>
     </style>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java
new file mode 100644
index 0000000..d9b24ed
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+
+/**
+ * Activity with alwaysExpand=true (launched via R.id.launch_always_expand_activity_button)
+ */
+public class ActivityEmbeddingAlwaysExpandActivity extends Activity {
+  @Override
+  public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_embedding_base_layout);
+    findViewById(R.id.root_activity_layout).setBackgroundColor(Color.GREEN);
+  }
+
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
index 6a7a2cc..23fa91c 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
@@ -16,87 +16,149 @@
 
 package com.android.server.wm.flicker.testapp;
 
+import androidx.annotation.NonNull;
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
-import android.util.ArraySet;
-import android.util.Log;
 import android.view.View;
 
-import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
-import androidx.window.extensions.embedding.EmbeddingRule;
-import androidx.window.extensions.embedding.SplitPairRule;
-import androidx.window.extensions.embedding.SplitPlaceholderRule;
+import androidx.annotation.NonNull;
+import androidx.window.embedding.ActivityFilter;
+import androidx.window.embedding.ActivityRule;
+import androidx.window.embedding.EmbeddingAspectRatio;
+import androidx.window.embedding.RuleController;
+import androidx.window.embedding.SplitAttributes;
+import androidx.window.embedding.SplitAttributes.LayoutDirection;
+import androidx.window.embedding.SplitController;
+import androidx.window.embedding.SplitPairFilter;
+import androidx.window.embedding.SplitPairRule;
+import androidx.window.embedding.SplitPlaceholderRule;
+import androidx.window.embedding.SplitRule;
 
-import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper;
-
+import java.util.HashSet;
 import java.util.Set;
 
 /** Main activity of the ActivityEmbedding test app to launch other embedding activities. */
 public class ActivityEmbeddingMainActivity extends Activity {
     private static final String TAG = "ActivityEmbeddingMainActivity";
     private static final float DEFAULT_SPLIT_RATIO = 0.5f;
+    private RuleController mRuleController;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_embedding_main_layout);
+        final SplitController.SplitSupportStatus status = SplitController.getInstance(
+                this).getSplitSupportStatus();
+        if (status != SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
+            throw new RuntimeException(
+                    "Unable to initiate SplitController in ActivityEmbeddingMainActivity, "
+                            + "splitSupportStatus = " + status);
+        }
+        mRuleController = RuleController.getInstance(this);
+    }
+
+    /** R.id.launch_trampoline_button onClick */
+    public void launchTrampolineActivity(View view) {
+        final String layoutDirection = view.getTag().toString();
+        mRuleController.clearRules();
+        mRuleController.addRule(createSplitPairRules(layoutDirection));
+        startActivity(new Intent().setComponent(
+                ActivityOptions.ActivityEmbedding.TrampolineActivity.COMPONENT));
     }
 
     /** R.id.launch_secondary_activity_button onClick */
     public void launchSecondaryActivity(View view) {
-        initializeSplitRules(createSplitPairRules());
+        final String layoutDirection = view.getTag().toString();
+        mRuleController.clearRules();
+        mRuleController.addRule(createSplitPairRules(layoutDirection));
         startActivity(new Intent().setComponent(
                 ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT));
     }
 
+    /** R.id.launch_always_expand_activity_button onClick */
+    public void launchAlwaysExpandActivity(View view) {
+        final Set<ActivityFilter> activityFilters = new HashSet<>();
+        activityFilters.add(
+                new ActivityFilter(ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT,
+                        null));
+        final ActivityRule activityRule = new ActivityRule.Builder(activityFilters)
+                .setAlwaysExpand(true)
+                .build();
+
+        RuleController rc = RuleController.getInstance(this);
+
+        rc.addRule(activityRule);
+        startActivity(new Intent().setComponent(
+                ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT));
+    }
+
     /** R.id.launch_placeholder_split_button onClick */
     public void launchPlaceholderSplit(View view) {
-        initializeSplitRules(createSplitPlaceholderRules());
+        final String layoutDirection = view.getTag().toString();
+        mRuleController.clearRules();
+        mRuleController.addRule(createSplitPlaceholderRules(layoutDirection));
         startActivity(new Intent().setComponent(
                 ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT));
     }
 
-    private void initializeSplitRules(Set<EmbeddingRule> rules) {
-        ActivityEmbeddingComponent embeddingComponent =
-                ActivityEmbeddingAppHelper.getActivityEmbeddingComponent();
-        if (embeddingComponent == null) {
-            // Embedding not supported
-            Log.d(TAG, "ActivityEmbedding is not supported on this device");
-            finish();
-            return;
+    private static SplitPairRule createSplitPairRules(@NonNull String layoutDirection) {
+        final Set<SplitPairFilter> pairFilters = new HashSet<>();
+        final SplitPairFilter activitiesPair = new SplitPairFilter(
+                ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT,
+                ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT,
+                null /* secondaryActivityIntentAction */);
+        pairFilters.add(activitiesPair);
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.SPLIT_TYPE_EQUAL)
+                .setLayoutDirection(parseLayoutDirection(layoutDirection))
+                .build();
+        // Setting thresholds to ALWAYS_ALLOW values to make it easy for running on all devices.
+        final SplitPairRule rule = new SplitPairRule.Builder(pairFilters)
+                .setDefaultSplitAttributes(splitAttributes)
+                .setMinWidthDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+                .setMinHeightDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+                .setMinSmallestWidthDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+                .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ALWAYS_ALLOW)
+                .setMaxAspectRatioInLandscape(EmbeddingAspectRatio.ALWAYS_ALLOW)
+                .build();
+        return rule;
+    }
+
+    private static SplitPlaceholderRule createSplitPlaceholderRules(
+            @NonNull String layoutDirection) {
+        final Set<ActivityFilter> activityFilters = new HashSet<>();
+        activityFilters.add(new ActivityFilter(
+                ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT,
+                null /* intentAction */));
+        final Intent intent = new Intent();
+        intent.setComponent(
+                ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT);
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.SPLIT_TYPE_EQUAL)
+                .setLayoutDirection(parseLayoutDirection(layoutDirection))
+                .build();
+        final SplitPlaceholderRule rule = new SplitPlaceholderRule.Builder(activityFilters, intent)
+                .setDefaultSplitAttributes(splitAttributes)
+                .setMinWidthDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+                .setMinHeightDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+                .setMinSmallestWidthDp(SplitRule.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+                .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ALWAYS_ALLOW)
+                .setMaxAspectRatioInLandscape(EmbeddingAspectRatio.ALWAYS_ALLOW)
+                .build();
+        return rule;
+    }
+
+    private static LayoutDirection parseLayoutDirection(@NonNull String layoutDirectionStr) {
+        if (layoutDirectionStr.equals(LayoutDirection.LEFT_TO_RIGHT.toString())) {
+            return LayoutDirection.LEFT_TO_RIGHT;
         }
-        embeddingComponent.setEmbeddingRules(rules);
-    }
-
-    private Set<EmbeddingRule> createSplitPairRules() {
-        final Set<EmbeddingRule> rules = new ArraySet<>();
-        final SplitPairRule rule = new SplitPairRule.Builder(
-                activitiesPair -> activitiesPair.first instanceof ActivityEmbeddingMainActivity
-                        && activitiesPair.second instanceof ActivityEmbeddingSecondaryActivity,
-                activityIntentPair ->
-                        activityIntentPair.first instanceof ActivityEmbeddingMainActivity
-                                && activityIntentPair.second.getComponent().equals(ActivityOptions
-                                .ActivityEmbedding.SecondaryActivity.COMPONENT),
-                windowMetrics -> true)
-                .setSplitRatio(DEFAULT_SPLIT_RATIO)
-                .build();
-        rules.add(rule);
-        return rules;
-    }
-
-    private Set<EmbeddingRule> createSplitPlaceholderRules() {
-        final Set<EmbeddingRule> rules = new ArraySet<>();
-        final SplitPlaceholderRule rule = new SplitPlaceholderRule.Builder(
-                new Intent().setComponent(
-                        ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT),
-                activity -> activity instanceof ActivityEmbeddingPlaceholderPrimaryActivity,
-                intent -> intent.getComponent().equals(
-                        ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT),
-                windowMetrics -> true)
-                .setSplitRatio(DEFAULT_SPLIT_RATIO)
-                .build();
-        rules.add(rule);
-        return rules;
+        if (layoutDirectionStr.equals(LayoutDirection.BOTTOM_TO_TOP.toString())) {
+            return LayoutDirection.BOTTOM_TO_TOP;
+        }
+        if (layoutDirectionStr.equals(LayoutDirection.RIGHT_TO_LEFT.toString())) {
+            return LayoutDirection.RIGHT_TO_LEFT;
+        }
+        return LayoutDirection.LOCALE;
     }
 }
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
index 00f4c25..29cbf01 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
@@ -16,15 +16,73 @@
 
 package com.android.server.wm.flicker.testapp;
 
+import android.app.Activity;
+import android.content.Intent;
+import android.app.PictureInPictureParams;
 import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ToggleButton;
+
+import androidx.window.embedding.SplitAttributes;
+import androidx.window.embedding.SplitAttributesCalculatorParams;
+import androidx.window.embedding.SplitController;
 
 /**
  * Activity to be used as the secondary activity to split with
  * {@link ActivityEmbeddingMainActivity}.
  */
-public class ActivityEmbeddingSecondaryActivity extends ActivityEmbeddingBaseActivity {
+public class ActivityEmbeddingSecondaryActivity extends Activity {
+
+    private SplitController mSplitController;
+
     @Override
-    int getBackgroundColor() {
-        return Color.YELLOW;
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_embedding_secondary_activity_layout);
+        findViewById(R.id.secondary_activity_layout).setBackgroundColor(Color.YELLOW);
+        findViewById(R.id.finish_secondary_activity_button).setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        finish();
+                    }
+                });
+        mSplitController = SplitController.getInstance(this);
+        final ToggleButton splitRatio = findViewById(R.id.toggle_split_ratio_button);
+        mSplitController.setSplitAttributesCalculator(params -> {
+            return new SplitAttributes.Builder()
+                    .setSplitType(
+                            SplitAttributes.SplitType.ratio(
+                                    splitRatio.isChecked() ? 0.7f : 0.5f)
+                    )
+                    .setLayoutDirection(
+                            params.getDefaultSplitAttributes()
+                                    .getLayoutDirection())
+                    .build();
+        });
+        splitRatio.setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        // This triggers a recalcuation of splitatributes.
+                        mSplitController.invalidateTopVisibleSplitAttributes();
+                    }
+        });
+        findViewById(R.id.secondary_enter_pip_button).setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        PictureInPictureParams.Builder picInPicParamsBuilder =
+                                new PictureInPictureParams.Builder();
+                        enterPictureInPictureMode(picInPicParamsBuilder.build());
+                    }
+                }
+        );
+    }
+
+    public void launchThirdActivity(View view) {
+        startActivity(new Intent().setComponent(
+                ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT));
     }
 }
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java
new file mode 100644
index 0000000..3bd7281
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+
+/**
+ * Activity to be used also as a secondary activity to split with
+ * {@link ActivityEmbeddingMainActivity}.
+ */
+public class ActivityEmbeddingThirdActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_embedding_base_layout);
+        findViewById(R.id.root_activity_layout).setBackgroundColor(Color.RED);
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingTrampolineActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingTrampolineActivity.java
new file mode 100644
index 0000000..67eac2e
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingTrampolineActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * A Trampoline Activity that launches {@link ActivityEmbeddingSecondaryActivity} and then
+ * finishes itself.
+ */
+public class ActivityEmbeddingTrampolineActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // Trampoline activity doesn't have a view.
+        startActivity(new Intent().setComponent(
+                ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT));
+        finish();
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 9c3226b..2795a6c 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -73,6 +73,18 @@
                 FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity");
     }
 
+    public static class TransparentActivity {
+        public static final String LABEL = "TransparentActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".TransparentActivity");
+    }
+
+    public static class LaunchTransparentActivity {
+        public static final String LABEL = "LaunchTransparentActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".LaunchTransparentActivity");
+    }
+
     public static class DialogThemedActivity {
         public static final String LABEL = "DialogThemedActivity";
         public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
@@ -99,6 +111,18 @@
                     FLICKER_APP_PACKAGE + ".ActivityEmbeddingSecondaryActivity");
         }
 
+        public static class ThirdActivity {
+            public static final String LABEL = "ActivityEmbeddingThirdActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".ActivityEmbeddingThirdActivity");
+        }
+
+        public static class AlwaysExpandActivity {
+            public static final String LABEL = "ActivityEmbeddingAlwaysExpandActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".ActivityEmbeddingAlwaysExpandActivity");
+        }
+
         public static class PlaceholderPrimaryActivity {
             public static final String LABEL = "ActivityEmbeddingPlaceholderPrimaryActivity";
             public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
@@ -110,6 +134,12 @@
             public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderSecondaryActivity");
         }
+
+        public static class TrampolineActivity {
+            public static final String LABEL = "ActivityEmbeddingTrampolineActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".ActivityEmbeddingTrampolineActivity");
+        }
     }
 
     public static class Notification {
@@ -180,6 +210,8 @@
             public static final String LABEL = "SplitScreenPrimaryActivity";
             public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".SplitScreenActivity");
+
+            public static final String EXTRA_LAUNCH_ADJACENT = "launch_adjacent";
         }
 
         public static class Secondary {
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java
new file mode 100644
index 0000000..7c161fd
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class LaunchTransparentActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(R.layout.activity_transparent_launch);
+        findViewById(R.id.button_launch_transparent)
+                .setOnClickListener(v -> launchTransparentActivity());
+    }
+
+    private void launchTransparentActivity() {
+        startActivity(new Intent(this, TransparentActivity.class));
+    }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java
index 70196ae..8a27252 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java
@@ -16,9 +16,14 @@
 
 package com.android.server.wm.flicker.testapp;
 
+import static com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary.EXTRA_LAUNCH_ADJACENT;
+
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
 
+import androidx.annotation.Nullable;
+
 public class SplitScreenActivity extends Activity {
 
     @Override
@@ -26,4 +31,17 @@
         super.onCreate(icicle);
         setContentView(R.layout.activity_splitscreen);
     }
+
+    @Override
+    protected void onPostCreate(@Nullable Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        final boolean startSecondaryActivity = getIntent().hasExtra(EXTRA_LAUNCH_ADJACENT);
+        if (startSecondaryActivity) {
+            final Intent intent = new Intent(this, SplitScreenSecondaryActivity.class);
+            intent.addCategory(Intent.CATEGORY_LAUNCHER);
+            intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+            startActivity(intent);
+        }
+    }
 }
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java
new file mode 100644
index 0000000..1bac8bd
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class TransparentActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(R.layout.activity_transparent);
+    }
+}
diff --git a/tests/FlickerTests/trace_config/trace_config.textproto b/tests/FlickerTests/trace_config/trace_config.textproto
new file mode 100644
index 0000000..c9a35ac
--- /dev/null
+++ b/tests/FlickerTests/trace_config/trace_config.textproto
@@ -0,0 +1,77 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# proto-message: TraceConfig
+
+# Enable periodic flushing of the trace buffer into the output file.
+write_into_file: true
+
+# Writes the userspace buffer into the file every 1s.
+file_write_period_ms: 2500
+
+# See b/126487238 - we need to guarantee ordering of events.
+flush_period_ms: 30000
+
+# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# trace data. The trace buffer sizing depends on the number of trace categories
+# enabled and the device activity.
+
+# RSS events
+buffers: {
+  size_kb: 63488
+  fill_policy: RING_BUFFER
+}
+
+data_sources {
+  config {
+    name: "linux.process_stats"
+    target_buffer: 0
+    # polled per-process memory counters and process/thread names.
+    # If you don't want the polled counters, remove the "process_stats_config"
+    # section, but keep the data source itself as it still provides on-demand
+    # thread/process naming for ftrace data below.
+    process_stats_config {
+      scan_all_processes_on_start: true
+    }
+  }
+}
+
+data_sources: {
+  config {
+    name: "linux.ftrace"
+    ftrace_config {
+      ftrace_events: "ftrace/print"
+      ftrace_events: "task/task_newtask"
+      ftrace_events: "task/task_rename"
+      atrace_categories: "ss"
+      atrace_categories: "wm"
+      atrace_categories: "am"
+      atrace_categories: "aidl"
+      atrace_categories: "input"
+      atrace_categories: "binder_driver"
+      atrace_categories: "sched_process_exit"
+      atrace_apps: "com.android.server.wm.flicker"
+      atrace_apps: "com.android.server.wm.flicker.other"
+      atrace_apps: "com.android.server.wm.flicker.close"
+      atrace_apps: "com.android.server.wm.flicker.ime"
+      atrace_apps: "com.android.server.wm.flicker.launch"
+      atrace_apps: "com.android.server.wm.flicker.quickswitch"
+      atrace_apps: "com.android.server.wm.flicker.rotation"
+      atrace_apps: "com.android.server.wm.flicker.testapp"
+      atrace_apps: "com.android.systemui"
+      atrace_apps: "com.google.android.apps.nexuslauncher"
+    }
+  }
+}
+
diff --git a/tests/Input/src/com/android/test/input/MotionPredictorTest.kt b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
index 24a5671..d3eeac1 100644
--- a/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
+++ b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
@@ -129,7 +129,7 @@
         // Prediction will happen for t=12 (since it is the next input interval after the requested
         // time, 8, plus the model offset, 1).
         assertEquals(12, predicted!!.eventTime)
-        assertEquals(30f, predicted.x, /*delta=*/5f)
+        assertEquals(30f, predicted.x, /*delta=*/10f)
         assertEquals(60f, predicted.y, /*delta=*/15f)
     }
 }
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
index 0c267b2..320daee 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
@@ -17,8 +17,9 @@
 package com.android.inputmethod.stresstest;
 
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
 
+import static com.android.compatibility.common.util.SystemUtil.eventually;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync;
@@ -26,11 +27,16 @@
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.content.Intent;
 import android.platform.test.annotations.RootPermissionTest;
 import android.platform.test.rule.UnlockScreenRule;
+import android.support.test.uiautomator.UiDevice;
 import android.widget.EditText;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,6 +45,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Test IME visibility by using system default IME to ensure the behavior is consistent
@@ -59,8 +66,12 @@
     public ScreenCaptureRule mScreenCaptureRule =
             new ScreenCaptureRule("/sdcard/InputMethodStressTest");
 
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(3);
+
     private static final int NUM_TEST_ITERATIONS = 10;
 
+    private final boolean mIsPortrait;
+
     @Parameterized.Parameters(name = "isPortrait={0}")
     public static List<Boolean> isPortraitCases() {
         // Test in both portrait and landscape mode.
@@ -68,6 +79,7 @@
     }
 
     public DefaultImeVisibilityTest(boolean isPortrait) {
+        mIsPortrait = isPortrait;
         mImeStressTestRule.setIsPortrait(isPortrait);
     }
 
@@ -75,14 +87,26 @@
     public void showHideDefaultIme() {
         Intent intent =
                 createIntent(
-                        0x0, /* No window focus flags */
-                        SOFT_INPUT_STATE_UNSPECIFIED | SOFT_INPUT_ADJUST_RESIZE,
+                        0x0 /* No window focus flags */,
+                        SOFT_INPUT_STATE_HIDDEN | SOFT_INPUT_ADJUST_RESIZE,
                         Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
         ImeStressTestUtil.TestActivity activity = ImeStressTestUtil.TestActivity.start(intent);
         EditText editText = activity.getEditText();
+
+        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        eventually(
+                () ->
+                        assertWithMessage("Display rotation should be updated.")
+                                .that(uiDevice.getDisplayRotation())
+                                .isEqualTo(mIsPortrait ? 0 : 1),
+                TIMEOUT);
+
         for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
-            callOnMainSync(activity::showImeWithInputMethodManager);
+            // TODO(b/291752364): Remove the explicit focus request once the issue with view focus
+            //  change between fullscreen IME and actual editText is fixed.
+            callOnMainSync(editText::requestFocus);
             verifyWindowAndViewFocus(editText, true, true);
+            callOnMainSync(activity::showImeWithInputMethodManager);
             waitOnMainUntilImeIsShown(editText);
 
             callOnMainSync(activity::hideImeWithInputMethodManager);
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java
index 12104b2..c746321 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java
@@ -20,9 +20,9 @@
 import android.os.RemoteException;
 import android.support.test.uiautomator.UiDevice;
 
+import androidx.annotation.NonNull;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import org.checkerframework.checker.nullness.qual.NonNull;
 import org.junit.rules.TestWatcher;
 import org.junit.runner.Description;
 
@@ -35,8 +35,6 @@
 public class ImeStressTestRule extends TestWatcher {
     private static final String LOCK_SCREEN_OFF_COMMAND = "locksettings set-disabled true";
     private static final String LOCK_SCREEN_ON_COMMAND = "locksettings set-disabled false";
-    private static final String SET_PORTRAIT_MODE_COMMAND = "settings put system user_rotation 0";
-    private static final String SET_LANDSCAPE_MODE_COMMAND = "settings put system user_rotation 1";
     private static final String SIMPLE_IME_ID =
             "com.android.apps.inputmethod.simpleime/.SimpleInputMethodService";
     private static final String ENABLE_IME_COMMAND = "ime enable " + SIMPLE_IME_ID;
@@ -44,8 +42,10 @@
     private static final String DISABLE_IME_COMMAND = "ime disable " + SIMPLE_IME_ID;
     private static final String RESET_IME_COMMAND = "ime reset";
 
-    @NonNull private final Instrumentation mInstrumentation;
-    @NonNull private final UiDevice mUiDevice;
+    @NonNull
+    private final Instrumentation mInstrumentation;
+    @NonNull
+    private final UiDevice mUiDevice;
     // Whether the screen orientation is set to portrait.
     private boolean mIsPortrait;
     // Whether to use a simple test Ime or system default Ime for test.
@@ -105,12 +105,13 @@
     private void setOrientation() {
         try {
             mUiDevice.freezeRotation();
-            executeShellCommand(
-                    mIsPortrait ? SET_PORTRAIT_MODE_COMMAND : SET_LANDSCAPE_MODE_COMMAND);
-        } catch (IOException e) {
-            throw new RuntimeException("Could not set screen orientation.", e);
+            if (mIsPortrait) {
+                mUiDevice.setOrientationNatural();
+            } else {
+                mUiDevice.setOrientationLeft();
+            }
         } catch (RemoteException e) {
-            throw new RuntimeException("Could not freeze rotation.", e);
+            throw new RuntimeException("Could not freeze rotation or set screen orientation.", e);
         }
     }
 
@@ -147,7 +148,8 @@
         }
     }
 
-    private @NonNull String executeShellCommand(@NonNull String cmd) throws IOException {
+    @NonNull
+    private String executeShellCommand(@NonNull String cmd) throws IOException {
         return mUiDevice.executeShellCommand(cmd);
     }
 }
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
index f3c8194..c9b5c96 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
@@ -392,7 +392,7 @@
         public boolean showImeWithInputMethodManager() {
             boolean showResult =
                     getInputMethodManager()
-                            .showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT);
+                            .showSoftInput(mEditText, 0 /* flags */);
             if (showResult) {
                 Log.i(TAG, "IMM#showSoftInput successfully");
             } else {
@@ -404,7 +404,8 @@
         /** Hide IME with InputMethodManager. */
         public boolean hideImeWithInputMethodManager() {
             boolean hideResult =
-                    getInputMethodManager().hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
+                    getInputMethodManager()
+                            .hideSoftInputFromWindow(mEditText.getWindowToken(), 0 /* flags */);
             if (hideResult) {
                 Log.i(TAG, "IMM#hideSoftInput successfully");
             } else {
diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
index f656881..c0d7cb4 100644
--- a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
+++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
@@ -63,6 +63,25 @@
     }
 
     @Test
+    public void testTransformImeLanguageTagToLocaleInfo_duplicateTagFilter() {
+        List<InputMethodSubtype> list = List.of(
+                new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("zh-TW").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("ja-JP").build());
+
+        Set<LocaleInfo> localeSet = LocaleStore.transformImeLanguageTagToLocaleInfo(list);
+
+        Set<String> expectedLanguageTag = Set.of("en-US", "zh-TW", "ja-JP");
+        assertEquals(localeSet.size(), expectedLanguageTag.size());
+        for (LocaleInfo info : localeSet) {
+            assertEquals(info.mSuggestionFlags, LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE);
+            assertTrue(expectedLanguageTag.contains(info.getId()));
+        }
+    }
+
+    @Test
     public void convertExplicitLocales_noExplicitLcoales_returnEmptyHashMap() {
         Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
 
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index edd6dd3..82e40b1 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -32,6 +32,7 @@
 import java.lang.annotation.Target;
 import java.lang.reflect.Field;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * This is a wrapper around {@link TestLooperManager} to make it easier to manage
@@ -55,7 +56,6 @@
     private MessageHandler mMessageHandler;
 
     private Handler mHandler;
-    private Runnable mEmptyMessage;
     private TestLooperManager mQueueWrapper;
 
     static {
@@ -121,8 +121,12 @@
      * @param num Number of messages to parse
      */
     public int processMessages(int num) {
+        return processMessagesInternal(num, null);
+    }
+
+    private int processMessagesInternal(int num, Runnable barrierRunnable) {
         for (int i = 0; i < num; i++) {
-            if (!parseMessageInt()) {
+            if (!processSingleMessage(barrierRunnable)) {
                 return i + 1;
             }
         }
@@ -130,6 +134,27 @@
     }
 
     /**
+     * Process up to a certain number of messages, not blocking if the queue has less messages than
+     * that
+     * @param num the maximum number of messages to process
+     * @return the number of messages processed. This will be at most {@code num}.
+     */
+
+    public int processMessagesNonBlocking(int num) {
+        final AtomicBoolean reachedBarrier = new AtomicBoolean(false);
+        Runnable barrierRunnable = () -> {
+            reachedBarrier.set(true);
+        };
+        mHandler.post(barrierRunnable);
+        waitForMessage(mQueueWrapper, mHandler, barrierRunnable);
+        try {
+            return processMessagesInternal(num, barrierRunnable) + (reachedBarrier.get() ? -1 : 0);
+        } finally {
+            mHandler.removeCallbacks(barrierRunnable);
+        }
+    }
+
+    /**
      * Process messages in the queue until no more are found.
      */
     public void processAllMessages() {
@@ -165,19 +190,20 @@
 
     private int processQueuedMessages() {
         int count = 0;
-        mEmptyMessage = () -> { };
-        mHandler.post(mEmptyMessage);
-        waitForMessage(mQueueWrapper, mHandler, mEmptyMessage);
-        while (parseMessageInt()) count++;
+        Runnable barrierRunnable = () -> { };
+        mHandler.post(barrierRunnable);
+        waitForMessage(mQueueWrapper, mHandler, barrierRunnable);
+        while (processSingleMessage(barrierRunnable)) count++;
         return count;
     }
 
-    private boolean parseMessageInt() {
+    private boolean processSingleMessage(Runnable barrierRunnable) {
         try {
             Message result = mQueueWrapper.next();
             if (result != null) {
                 // This is a break message.
-                if (result.getCallback() == mEmptyMessage) {
+                if (result.getCallback() == barrierRunnable) {
+                    mQueueWrapper.execute(result);
                     mQueueWrapper.recycle(result);
                     return false;
                 }
diff --git a/tests/testables/src/android/testing/TestableResources.java b/tests/testables/src/android/testing/TestableResources.java
index 27d5b66..0ec106e 100644
--- a/tests/testables/src/android/testing/TestableResources.java
+++ b/tests/testables/src/android/testing/TestableResources.java
@@ -15,9 +15,11 @@
 package android.testing;
 
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.util.Log;
 import android.util.SparseArray;
@@ -54,6 +56,16 @@
     }
 
     /**
+     * Sets a configuration for {@link #getResources()} to return to allow custom configs to
+     * be set and tested.
+     *
+     * @param configuration the configuration to return from resources.
+     */
+    public void overrideConfiguration(Configuration configuration) {
+        when(mResources.getConfiguration()).thenReturn(configuration);
+    }
+
+    /**
      * Sets the return value for the specified resource id.
      * <p>
      * Since resource ids are unique there is a single addOverride that will override the value
diff --git a/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java
index a8815dc..6a6ab00 100644
--- a/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java
+++ b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java
@@ -27,7 +27,9 @@
         return mOverrides.getOrDefault(flag.mSysPropKey, flag.mDefaultValue);
     }
 
-    public void setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag, boolean isEnabled) {
+    public TestableFlagResolver setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag,
+            boolean isEnabled) {
         mOverrides.put(flag.mSysPropKey, isEnabled);
+        return this;
     }
 }
diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java
index 0f491b8..a02eb6b 100644
--- a/tests/testables/tests/src/android/testing/TestableLooperTest.java
+++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java
@@ -27,12 +27,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InOrder;
-
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -40,6 +34,11 @@
 import android.testing.TestableLooper.MessageHandler;
 import android.testing.TestableLooper.RunWithLooper;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -240,4 +239,33 @@
         inOrder.verify(handler).dispatchMessage(messageC);
     }
 
+    @Test
+    public void testProcessMessagesNonBlocking_onlyArgNumber() {
+        Handler h = new Handler(mTestableLooper.getLooper());
+        Runnable r = mock(Runnable.class);
+
+        h.post(r);
+        h.post(r);
+        h.post(r);
+
+        int processed = mTestableLooper.processMessagesNonBlocking(2);
+
+        verify(r, times(2)).run();
+        assertEquals(2, processed);
+    }
+
+    @Test
+    public void testProcessMessagesNonBlocking_lessMessagesThanArg() {
+        Handler h = new Handler(mTestableLooper.getLooper());
+        Runnable r = mock(Runnable.class);
+
+        h.post(r);
+        h.post(r);
+        h.post(r);
+
+        int processed = mTestableLooper.processMessagesNonBlocking(5);
+
+        verify(r, times(3)).run();
+        assertEquals(3, processed);
+    }
 }
diff --git a/tools/protologtool/Android.bp b/tools/protologtool/Android.bp
index 039bb4e..46745e9 100644
--- a/tools/protologtool/Android.bp
+++ b/tools/protologtool/Android.bp
@@ -11,7 +11,7 @@
     name: "protologtool-lib",
     srcs: [
         "src/com/android/protolog/tool/**/*.kt",
-        ":protolog-common-src",
+        ":protolog-common-no-android-src",
     ],
     static_libs: [
         "javaparser",
diff --git a/wifi/java/src/android/net/wifi/nl80211/InstantWifi.java b/wifi/java/src/android/net/wifi/nl80211/InstantWifi.java
deleted file mode 100644
index 433e88c..0000000
--- a/wifi/java/src/android/net/wifi/nl80211/InstantWifi.java
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.net.wifi.nl80211;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.AlarmManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * @hide
- */
-public class InstantWifi {
-    private static final String INSTANT_WIFI_TAG = "InstantWifi";
-    private static final int OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS = 1000;
-    private static final int WIFI_NETWORK_EXPIRED_MS = 7 * 24 * 60 * 60 * 1000; // a week
-    private static final String NO_CONNECTION_TIMEOUT_ALARM_TAG =
-            INSTANT_WIFI_TAG + " No Connection Timeout";
-
-    private Context mContext;
-    private AlarmManager mAlarmManager;
-    private Handler mEventHandler;
-    private ConnectivityManager mConnectivityManager;
-    private WifiManager mWifiManager;
-    private PowerManager mPowerManager;
-    private long mLastWifiOnSinceBootMs;
-    private long mLastScreenOnSinceBootMs;
-    private boolean mIsWifiConnected = false;
-    private boolean mScreenOn = false;
-    private boolean mWifiEnabled = false;
-    private boolean mIsNoConnectionAlarmSet = false;
-    private ArrayList<WifiNetwork> mConnectedWifiNetworkList = new ArrayList<>();
-    private AlarmManager.OnAlarmListener mNoConnectionTimeoutCallback =
-            new AlarmManager.OnAlarmListener() {
-                public void onAlarm() {
-                    Log.i(INSTANT_WIFI_TAG, "Timed out waiting for wifi connection");
-                    mIsNoConnectionAlarmSet = false;
-                    mWifiManager.startScan();
-                }
-            };
-
-    public InstantWifi(Context context, AlarmManager alarmManager, Handler eventHandler) {
-        mContext = context;
-        mAlarmManager = alarmManager;
-        mEventHandler = eventHandler;
-        mWifiManager = mContext.getSystemService(WifiManager.class);
-        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
-        mConnectivityManager.registerNetworkCallback(
-                new NetworkRequest.Builder()
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                .build(), new WifiNetworkCallback());
-        // System power service was initialized before wifi nl80211 service.
-        mPowerManager = mContext.getSystemService(PowerManager.class);
-        IntentFilter screenEventfilter = new IntentFilter();
-        screenEventfilter.addAction(Intent.ACTION_SCREEN_ON);
-        screenEventfilter.addAction(Intent.ACTION_SCREEN_OFF);
-        mContext.registerReceiver(
-                new BroadcastReceiver() {
-                @Override
-                    public void onReceive(Context context, Intent intent) {
-                        String action = intent.getAction();
-                        if (action.equals(Intent.ACTION_SCREEN_ON)) {
-                            if (!mScreenOn) {
-                                mLastScreenOnSinceBootMs = getMockableElapsedRealtime();
-                            }
-                            mScreenOn = true;
-                        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
-                            mScreenOn = false;
-                        }
-                        Log.d(INSTANT_WIFI_TAG, "mScreenOn is changed to " + mScreenOn);
-                    }
-                }, screenEventfilter, null, mEventHandler);
-        mScreenOn = mPowerManager.isInteractive();
-        mContext.registerReceiver(
-                new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
-                                WifiManager.WIFI_STATE_UNKNOWN);
-                        mWifiEnabled = state == WifiManager.WIFI_STATE_ENABLED;
-                        if (mWifiEnabled) {
-                            mLastWifiOnSinceBootMs = getMockableElapsedRealtime();
-                        }
-                        Log.d(INSTANT_WIFI_TAG, "mWifiEnabled is changed to " + mWifiEnabled);
-                    }
-                },
-                new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION),
-                null, mEventHandler);
-    }
-
-    @VisibleForTesting
-    protected long getMockableElapsedRealtime() {
-        return SystemClock.elapsedRealtime();
-    }
-
-    private class WifiNetwork {
-        private final int mNetId;
-        private Set<Integer> mConnectedFrequencies = new HashSet<Integer>();
-        private int[] mLastTwoConnectedFrequencies = new int[2];
-        private long mLastConnectedTimeMillis;
-        WifiNetwork(int netId) {
-            mNetId = netId;
-        }
-
-        public int getNetId() {
-            return mNetId;
-        }
-
-        public boolean addConnectedFrequency(int channelFrequency) {
-            mLastConnectedTimeMillis = getMockableElapsedRealtime();
-            if (mLastTwoConnectedFrequencies[0] != channelFrequency
-                    && mLastTwoConnectedFrequencies[1] != channelFrequency) {
-                mLastTwoConnectedFrequencies[0] = mLastTwoConnectedFrequencies[1];
-                mLastTwoConnectedFrequencies[1] = channelFrequency;
-            }
-            return mConnectedFrequencies.add(channelFrequency);
-        }
-
-        public Set<Integer> getConnectedFrequencies() {
-            return mConnectedFrequencies;
-        }
-
-        public int[] getLastTwoConnectedFrequencies() {
-            if ((getMockableElapsedRealtime() - mLastConnectedTimeMillis)
-                    > WIFI_NETWORK_EXPIRED_MS) {
-                return new int[0];
-            }
-            return mLastTwoConnectedFrequencies;
-        }
-
-        public long getLastConnectedTimeMillis() {
-            return mLastConnectedTimeMillis;
-        }
-    }
-
-    private class WifiNetworkCallback extends NetworkCallback {
-        @Override
-        public void onAvailable(@NonNull Network network) {
-        }
-
-        @Override
-        public void onCapabilitiesChanged(Network network,
-                NetworkCapabilities networkCapabilities) {
-            if (networkCapabilities != null && network != null) {
-                WifiInfo wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
-                if (wifiInfo == null || mWifiManager == null) {
-                    return;
-                }
-                WifiConfiguration config = mWifiManager.getPrivilegedConnectedNetwork();
-                if (config == null) {
-                    return;
-                }
-                final int currentNetworkId = config.networkId;
-                final int connectecFrequency = wifiInfo.getFrequency();
-                if (connectecFrequency < 0 || currentNetworkId < 0) {
-                    return;
-                }
-                mIsWifiConnected = true;
-                if (mIsNoConnectionAlarmSet) {
-                    mAlarmManager.cancel(mNoConnectionTimeoutCallback);
-                }
-                Log.d(INSTANT_WIFI_TAG, "Receive Wifi is connected, freq =  " + connectecFrequency
-                        + " and currentNetworkId : " + currentNetworkId
-                        + ", wifiinfo = " + wifiInfo);
-                boolean isExist = false;
-                for (WifiNetwork wifiNetwork : mConnectedWifiNetworkList) {
-                    if (wifiNetwork.getNetId() == currentNetworkId) {
-                        if (wifiNetwork.addConnectedFrequency(connectecFrequency)) {
-                            Log.d(INSTANT_WIFI_TAG, "Update connected frequency: "
-                                    + connectecFrequency + " to Network currentNetworkId : "
-                                    + currentNetworkId);
-                        }
-                        isExist = true;
-                    }
-                }
-                if (!isExist) {
-                    WifiNetwork currentNetwork = new WifiNetwork(currentNetworkId);
-                    currentNetwork.addConnectedFrequency(connectecFrequency);
-                    if (mConnectedWifiNetworkList.size() < 5) {
-                        mConnectedWifiNetworkList.add(currentNetwork);
-                    } else {
-                        ArrayList<WifiNetwork> lastConnectedWifiNetworkList = new ArrayList<>();
-                        WifiNetwork legacyNetwork = mConnectedWifiNetworkList.get(0);
-                        for (WifiNetwork connectedNetwork : mConnectedWifiNetworkList) {
-                            if (connectedNetwork.getNetId() == legacyNetwork.getNetId()) {
-                                continue;
-                            }
-                            // Keep the used recently network in the last connected list
-                            if (connectedNetwork.getLastConnectedTimeMillis()
-                                    > legacyNetwork.getLastConnectedTimeMillis()) {
-                                lastConnectedWifiNetworkList.add(connectedNetwork);
-                            } else {
-                                lastConnectedWifiNetworkList.add(legacyNetwork);
-                                legacyNetwork = connectedNetwork;
-                            }
-                        }
-                        mConnectedWifiNetworkList = lastConnectedWifiNetworkList;
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void onLost(@NonNull Network network) {
-            mIsWifiConnected = false;
-        }
-    }
-
-    /**
-     * Returns whether or not the scan freqs should be overrided by using predicted channels.
-     */
-    public boolean isUsePredictedScanningChannels() {
-        if (mIsWifiConnected || mConnectedWifiNetworkList.size() == 0
-                || !mWifiManager.isWifiEnabled() || !mPowerManager.isInteractive()) {
-            return false;
-        }
-        if (!mWifiEnabled || !mScreenOn) {
-            Log.d(INSTANT_WIFI_TAG, "WiFi/Screen State mis-match, run instant Wifi anyway!");
-            return true;
-        }
-        return (((getMockableElapsedRealtime() - mLastWifiOnSinceBootMs)
-                        < OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS)
-                || ((getMockableElapsedRealtime() - mLastScreenOnSinceBootMs)
-                        < OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS));
-    }
-
-    /**
-     * Overrides the frequenies in SingleScanSetting
-     *
-     * @param settings the SingleScanSettings will be overrided.
-     * @param freqs new frequencies of SingleScanSettings
-     */
-    @Nullable
-    public void overrideFreqsForSingleScanSettingsIfNecessary(
-            @Nullable SingleScanSettings settings, @Nullable Set<Integer> freqs) {
-        if (!isUsePredictedScanningChannels() || settings == null || freqs == null
-                || freqs.size() == 0) {
-            return;
-        }
-        if (settings.channelSettings == null) {
-            settings.channelSettings = new ArrayList<>();
-        } else {
-            settings.channelSettings.clear();
-        }
-        for (int freq : freqs) {
-            if (freq > 0) {
-                ChannelSettings channel = new ChannelSettings();
-                channel.frequency = freq;
-                settings.channelSettings.add(channel);
-            }
-        }
-        // Monitor connection after last override scan request.
-        if (mIsNoConnectionAlarmSet) {
-            mAlarmManager.cancel(mNoConnectionTimeoutCallback);
-        }
-        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                getMockableElapsedRealtime() + OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS,
-                NO_CONNECTION_TIMEOUT_ALARM_TAG, mNoConnectionTimeoutCallback, mEventHandler);
-        mIsNoConnectionAlarmSet = true;
-    }
-
-    /**
-     * Returns the predicted scanning chcnnels set.
-     */
-    @NonNull
-    public Set<Integer> getPredictedScanningChannels() {
-        Set<Integer> predictedScanChannels = new HashSet<>();
-        if (!isUsePredictedScanningChannels()) {
-            Log.d(INSTANT_WIFI_TAG, "Drop, size: " + mConnectedWifiNetworkList.size());
-            return predictedScanChannels;
-        }
-        for (WifiNetwork network : mConnectedWifiNetworkList) {
-            for (int connectedFrequency : network.getLastTwoConnectedFrequencies()) {
-                if (connectedFrequency > 0) {
-                    predictedScanChannels.add(connectedFrequency);
-                    Log.d(INSTANT_WIFI_TAG, "Add channel: " + connectedFrequency
-                            + " to predicted channel");
-                }
-            }
-        }
-        return predictedScanChannels;
-    }
-}
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index ca12c4c..2a199d2 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -105,8 +105,6 @@
 
     // Cached wificond binder handlers.
     private IWificond mWificond;
-    private Context mContext;
-    private InstantWifi mInstantWifi;
     private WificondEventHandler mWificondEventHandler = new WificondEventHandler();
     private HashMap<String, IClientInterface> mClientInterfaces = new HashMap<>();
     private HashMap<String, IApInterface> mApInterfaces = new HashMap<>();
@@ -176,12 +174,6 @@
 
     /** @hide */
     @VisibleForTesting
-    protected InstantWifi getInstantWifiMockable() {
-        return mInstantWifi;
-    }
-
-    /** @hide */
-    @VisibleForTesting
     public class WificondEventHandler extends IWificondEventCallback.Stub {
         private Map<CountryCodeChangedListener, Executor> mCountryCodeChangedListenerHolder =
                 new HashMap<>();
@@ -427,7 +419,6 @@
     public WifiNl80211Manager(Context context) {
         mAlarmManager = context.getSystemService(AlarmManager.class);
         mEventHandler = new Handler(context.getMainLooper());
-        mContext = context;
     }
 
     /**
@@ -443,7 +434,6 @@
         if (mWificond == null) {
             Log.e(TAG, "Failed to get reference to wificond");
         }
-        mContext = context;
     }
 
     /** @hide */
@@ -451,7 +441,6 @@
     public WifiNl80211Manager(Context context, IWificond wificond) {
         this(context);
         mWificond = wificond;
-        mContext = context;
     }
 
     /** @hide */
@@ -755,9 +744,6 @@
             Log.e(TAG, "Failed to refresh wificond scanner due to remote exception");
         }
 
-        if (getInstantWifiMockable() == null) {
-            mInstantWifi = new InstantWifi(mContext, mAlarmManager, mEventHandler);
-        }
         return true;
     }
 
@@ -1085,10 +1071,6 @@
         if (settings == null) {
             return false;
         }
-        if (getInstantWifiMockable() != null) {
-            getInstantWifiMockable().overrideFreqsForSingleScanSettingsIfNecessary(settings,
-                    getInstantWifiMockable().getPredictedScanningChannels());
-        }
         try {
             return scannerImpl.scan(settings);
         } catch (RemoteException e1) {
@@ -1133,10 +1115,6 @@
         if (settings == null) {
             return WifiScanner.REASON_INVALID_ARGS;
         }
-        if (getInstantWifiMockable() != null) {
-            getInstantWifiMockable().overrideFreqsForSingleScanSettingsIfNecessary(settings,
-                    getInstantWifiMockable().getPredictedScanningChannels());
-        }
         try {
             int status = scannerImpl.scanRequest(settings);
             return toFrameworkScanStatusCode(status);
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index feef049..d41c019 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -81,6 +81,19 @@
             mCallback = callback;
         }
 
+        @Override
+        public void onServiceConnected() {
+            if (mCallback != null) {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mExecutor.execute(() -> mCallback.onServiceConnected());
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        }
+
+        @Override
         public void onHotspotNetworksUpdated(@NonNull List<HotspotNetwork> networks) {
             if (mCallback != null) {
                 final long token = Binder.clearCallingIdentity();
@@ -117,6 +130,7 @@
             }
         }
 
+        @Override
         public void onHotspotNetworkConnectionStatusChanged(
                 @NonNull HotspotNetworkConnectionStatus status) {
             if (mCallback != null) {
@@ -251,7 +265,6 @@
             synchronized (mProxyDataLock) {
                 mProxyMap.put(callback, proxy);
             }
-            callback.onServiceConnected();
         } catch (RemoteException e) {
             Log.e(TAG, "Exception in registerCallback", e);
             callback.onRegisterCallbackFailed(e);
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
index 5ad3ede..c828de9f 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
@@ -196,7 +196,7 @@
     public String toString() {
         return new StringBuilder("SharedConnectivitySettingsState[")
                 .append("instantTetherEnabled=").append(mInstantTetherEnabled)
-                .append("PendingIntent=").append(mInstantTetherSettingsPendingIntent.toString())
+                .append("PendingIntent=").append(mInstantTetherSettingsPendingIntent)
                 .append("extras=").append(mExtras.toString())
                 .append("]").toString();
     }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
index 737aa6d..521f943 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
@@ -31,4 +31,5 @@
     oneway void onKnownNetworksUpdated(in List<KnownNetwork> networks);
     oneway void onKnownNetworkConnectionStatusChanged(in KnownNetworkConnectionStatus status);
     oneway void onSharedConnectivitySettingsChanged(in SharedConnectivitySettingsState state);
+    oneway void onServiceConnected();
 }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
index 2bbe919..ebda6f1 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
@@ -276,6 +276,11 @@
 
     private void onRegisterCallback(ISharedConnectivityCallback callback) {
         mRemoteCallbackList.register(callback);
+        try {
+            callback.onServiceConnected();
+        } catch (RemoteException e) {
+            if (DEBUG) Log.w(TAG, "Exception in onRegisterCallback", e);
+        }
         if (mCountDownLatch != null) {
             mCountDownLatch.countDown();
         }
diff --git a/wifi/tests/src/android/net/wifi/nl80211/InstantWifiTest.java b/wifi/tests/src/android/net/wifi/nl80211/InstantWifiTest.java
deleted file mode 100644
index ebff0e2..0000000
--- a/wifi/tests/src/android/net/wifi/nl80211/InstantWifiTest.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.net.wifi.nl80211;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyLong;
-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.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.AlarmManager;
-import android.app.test.TestAlarmManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.os.IPowerManager;
-import android.os.IThermalService;
-import android.os.PowerManager;
-import android.os.test.TestLooper;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Unit tests for {@link android.net.wifi.nl80211.InstantWifi}.
- */
-@SmallTest
-public class InstantWifiTest {
-    @Mock private Context mContext;
-    @Mock private ConnectivityManager mMockConnectivityManager;
-    @Mock private WifiManager mMockWifiManager;
-    @Mock private Network mMockWifiNetwork;
-    @Mock private WifiInfo mMockWifiInfo;
-    @Mock private WifiConfiguration mMockWifiConfiguration;
-    @Mock private IPowerManager mPowerManagerService;
-    private InstantWifi mInstantWifi;
-    private TestLooper mLooper;
-    private Handler mHandler;
-    private TestAlarmManager mTestAlarmManager;
-    private AlarmManager mAlarmManager;
-    private PowerManager mMockPowerManager;
-
-    private final ArgumentCaptor<NetworkCallback> mWifiNetworkCallbackCaptor =
-            ArgumentCaptor.forClass(NetworkCallback.class);
-    private final ArgumentCaptor<BroadcastReceiver> mScreenBroadcastReceiverCaptor =
-            ArgumentCaptor.forClass(BroadcastReceiver.class);
-    private final ArgumentCaptor<BroadcastReceiver> mWifiStateBroadcastReceiverCaptor =
-            ArgumentCaptor.forClass(BroadcastReceiver.class);
-
-    private static final int TEST_NETWORK_ID = 1;
-    private static final int TEST_24G_FREQUENCY = 2412;
-    private static final int TEST_5G_FREQUENCY = 5745;
-    private long mTimeOffsetMs = 0;
-
-    private class InstantWifiSpy extends InstantWifi {
-        InstantWifiSpy(Context context, AlarmManager alarmManager, Handler handler) {
-            super(context, alarmManager, handler);
-        }
-
-        @Override
-        protected long getMockableElapsedRealtime() {
-            return mTimeOffsetMs;
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mLooper = new TestLooper();
-        mHandler = new Handler(mLooper.getLooper());
-
-        mTestAlarmManager = new TestAlarmManager();
-        mAlarmManager = mTestAlarmManager.getAlarmManager();
-        when(mContext.getSystemServiceName(AlarmManager.class)).thenReturn(Context.ALARM_SERVICE);
-        when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
-        when(mContext.getSystemServiceName(WifiManager.class)).thenReturn(Context.WIFI_SERVICE);
-        when(mContext.getSystemService(WifiManager.class)).thenReturn(mMockWifiManager);
-        when(mContext.getSystemServiceName(ConnectivityManager.class))
-                .thenReturn(Context.CONNECTIVITY_SERVICE);
-        when(mContext.getSystemService(ConnectivityManager.class))
-                .thenReturn(mMockConnectivityManager);
-        mMockPowerManager = new PowerManager(mContext, mPowerManagerService,
-                mock(IThermalService.class), mHandler);
-        when(mContext.getSystemServiceName(PowerManager.class)).thenReturn(Context.POWER_SERVICE);
-        when(mContext.getSystemService(PowerManager.class)).thenReturn(mMockPowerManager);
-        when(mPowerManagerService.isInteractive()).thenReturn(true);
-
-        doReturn(mMockWifiInfo).when(mMockWifiInfo).makeCopy(anyLong());
-        mTimeOffsetMs = 0;
-        mInstantWifi = new InstantWifiSpy(mContext, mAlarmManager, mHandler);
-        verifyInstantWifiInitialization();
-    }
-
-    private void verifyInstantWifiInitialization() {
-        verify(mMockConnectivityManager).registerNetworkCallback(any(),
-                mWifiNetworkCallbackCaptor.capture());
-        verify(mContext).registerReceiver(mScreenBroadcastReceiverCaptor.capture(),
-                argThat((IntentFilter filter) ->
-                        filter.hasAction(Intent.ACTION_SCREEN_ON)
-                                && filter.hasAction(Intent.ACTION_SCREEN_OFF)), eq(null), any());
-
-        verify(mContext).registerReceiver(mWifiStateBroadcastReceiverCaptor.capture(),
-                argThat((IntentFilter filter) ->
-                        filter.hasAction(WifiManager.WIFI_STATE_CHANGED_ACTION)), eq(null), any());
-    }
-
-    private void mockWifiConnectedEvent(int networkId, int connectedFrequency) {
-        // Send wifi connected event
-        NetworkCapabilities mockWifiNetworkCapabilities =
-                new NetworkCapabilities.Builder().setTransportInfo(mMockWifiInfo).build();
-        mMockWifiConfiguration.networkId = networkId;
-        when(mMockWifiManager.getPrivilegedConnectedNetwork()).thenReturn(mMockWifiConfiguration);
-        when(mMockWifiInfo.getFrequency()).thenReturn(connectedFrequency);
-        mWifiNetworkCallbackCaptor.getValue().onCapabilitiesChanged(mMockWifiNetwork,
-                mockWifiNetworkCapabilities);
-        mLooper.dispatchAll();
-    }
-
-    private void mockWifiOnScreenOnBroadcast(boolean isWifiOn, boolean isScreenOn)
-            throws Exception {
-        // Send Wifi On broadcast
-        Intent wifiOnIntent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
-        wifiOnIntent.putExtra(WifiManager.EXTRA_WIFI_STATE,
-                isWifiOn ? WifiManager.WIFI_STATE_ENABLED : WifiManager.WIFI_STATE_DISABLED);
-        mWifiStateBroadcastReceiverCaptor.getValue().onReceive(mContext, wifiOnIntent);
-        mLooper.dispatchAll();
-        // Send Screen On broadcast
-        Intent screenOnIntent =
-                new Intent(isScreenOn ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
-        mScreenBroadcastReceiverCaptor.getValue().onReceive(mContext, screenOnIntent);
-        mLooper.dispatchAll();
-        when(mMockWifiManager.isWifiEnabled()).thenReturn(isWifiOn);
-        when(mPowerManagerService.isInteractive()).thenReturn(isScreenOn);
-    }
-
-    @Test
-    public void testisUsePredictedScanningChannels() throws Exception {
-        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
-        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, false /* isScreenOn */);
-        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
-        mockWifiOnScreenOnBroadcast(false /* isWifiOn */, true /* isScreenOn */);
-        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
-        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
-        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
-        // Send wifi connected event
-        mockWifiConnectedEvent(TEST_NETWORK_ID, TEST_24G_FREQUENCY);
-        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
-        // Send wifi disconnect
-        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
-        assertTrue(mInstantWifi.isUsePredictedScanningChannels());
-        // Shift time to make it expired
-        mTimeOffsetMs = 1100;
-        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
-    }
-
-    @Test
-    public void testGetPredictedScanningChannels() throws Exception {
-        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
-        // Send wifi connected event on T0
-        mockWifiConnectedEvent(TEST_NETWORK_ID, TEST_24G_FREQUENCY);
-        // Send wifi disconnect
-        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
-        assertTrue(mInstantWifi.isUsePredictedScanningChannels());
-        assertTrue(mInstantWifi.getPredictedScanningChannels().contains(TEST_24G_FREQUENCY));
-        mTimeOffsetMs += 1000; // T1 = 1000 ms
-        // Send wifi connected event
-        mockWifiConnectedEvent(TEST_NETWORK_ID + 1, TEST_5G_FREQUENCY);
-        // Send wifi disconnect
-        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
-        // isUsePredictedScanningChannels is false since wifi on & screen on is expired
-        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
-        // Override the Wifi On & Screen on time
-        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
-        assertTrue(mInstantWifi.getPredictedScanningChannels().contains(TEST_5G_FREQUENCY));
-        mTimeOffsetMs += 7 * 24 * 60 * 60 * 1000; // Make T0 expired
-        // Override the Wifi On & Screen on time
-        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
-        assertFalse(mInstantWifi.getPredictedScanningChannels().contains(TEST_24G_FREQUENCY));
-        assertTrue(mInstantWifi.getPredictedScanningChannels().contains(TEST_5G_FREQUENCY));
-    }
-
-    @Test
-    public void testOverrideFreqsForSingleScanSettings() throws Exception {
-        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
-        // Send wifi connected event
-        mockWifiConnectedEvent(TEST_NETWORK_ID, TEST_24G_FREQUENCY);
-        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
-        // Send wifi disconnect
-        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
-        assertTrue(mInstantWifi.isUsePredictedScanningChannels());
-
-        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
-                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
-        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
-                alarmListenerCaptor.capture(), any());
-        Set<Integer> testFreqs = Set.of(
-                TEST_24G_FREQUENCY, TEST_5G_FREQUENCY);
-        SingleScanSettings testSingleScanSettings = new SingleScanSettings();
-        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(
-                testSingleScanSettings, new HashSet<Integer>());
-        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(
-                testSingleScanSettings, null);
-        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(null, null);
-        verify(mAlarmManager, never()).set(anyInt(), anyLong(), any(), any(), any());
-        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(testSingleScanSettings,
-                testFreqs);
-        verify(mAlarmManager).set(anyInt(), anyLong(), any(), any(), any());
-        Set<Integer> overridedFreqs = new HashSet<Integer>();
-        for (ChannelSettings channel : testSingleScanSettings.channelSettings) {
-            overridedFreqs.add(channel.frequency);
-        }
-        assertEquals(testFreqs, overridedFreqs);
-        alarmListenerCaptor.getValue().onAlarm();
-        verify(mMockWifiManager).startScan();
-    }
-}
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index f12818f..362eb14 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -27,7 +26,6 @@
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyLong;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
@@ -39,7 +37,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.AlarmManager;
-import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.app.test.TestAlarmManager;
 import android.content.Context;
 import android.net.MacAddress;
@@ -107,9 +104,6 @@
     private WifiNl80211Manager.CountryCodeChangedListener mCountryCodeChangedListener2;
     @Mock
     private Context mContext;
-    @Mock
-    private InstantWifi mMockInstantWifi;
-
     private TestLooper mLooper;
     private TestAlarmManager mTestAlarmManager;
     private AlarmManager mAlarmManager;
@@ -173,17 +167,6 @@
             0x00, 0x00
     };
 
-    private class WifiNl80211ManagerSpy extends WifiNl80211Manager {
-        WifiNl80211ManagerSpy(Context context, IWificond wificond) {
-            super(context, wificond);
-        }
-
-        @Override
-        protected InstantWifi getInstantWifiMockable() {
-            return mMockInstantWifi;
-        }
-    }
-
     @Before
     public void setUp() throws Exception {
         // Setup mocks for successful WificondControl operation. Failure case mocks should be
@@ -198,8 +181,6 @@
         mLooper = new TestLooper();
         when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
 
-        doNothing().when(mMockInstantWifi).overrideFreqsForSingleScanSettingsIfNecessary(
-                any(), any());
         when(mWificond.asBinder()).thenReturn(mWifiCondBinder);
         when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
         when(mWificond.createClientInterface(any())).thenReturn(mClientInterface);
@@ -208,7 +189,7 @@
         when(mWificond.tearDownApInterface(any())).thenReturn(true);
         when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
         when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
-        mWificondControl = new WifiNl80211ManagerSpy(mContext, mWificond);
+        mWificondControl = new WifiNl80211Manager(mContext, mWificond);
         mWificondEventHandler = mWificondControl.getWificondEventHandler();
         assertEquals(true,
                 mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
@@ -1178,40 +1159,6 @@
         verify(mWificond).notifyCountryCodeChanged();
     }
 
-    @Test
-    public void testInstantWifi() throws Exception {
-        doAnswer(new AnswerWithArguments() {
-            public void answer(SingleScanSettings settings, Set<Integer> freqs) {
-                if (settings.channelSettings == null) {
-                    settings.channelSettings = new ArrayList<>();
-                } else {
-                    settings.channelSettings.clear();
-                }
-                for (int freq : freqs) {
-                    if (freq > 0) {
-                        ChannelSettings channel = new ChannelSettings();
-                        channel.frequency = freq;
-                        settings.channelSettings.add(channel);
-                    }
-                }
-            }
-        }).when(mMockInstantWifi).overrideFreqsForSingleScanSettingsIfNecessary(
-                any(), any());
-        Set<Integer> testPredictedChannelsSet = Set.of(2412, 5745);
-        assertNotEquals(testPredictedChannelsSet, SCAN_FREQ_SET);
-        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
-        when(mMockInstantWifi.getPredictedScanningChannels()).thenReturn(testPredictedChannelsSet);
-
-        // Trigger scan to check scan settings are changed
-        assertTrue(mWificondControl.startScan(
-                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
-                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
-        verify(mMockInstantWifi).getPredictedScanningChannels();
-        verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
-                IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
-                testPredictedChannelsSet, SCAN_HIDDEN_NETWORK_SSID_LIST, false, null)));
-    }
-
     // Create a ArgumentMatcher which captures a SingleScanSettings parameter and checks if it
     // matches the provided frequency set and ssid set.
     private class ScanMatcher implements ArgumentMatcher<SingleScanSettings> {